@holdpoint/cli 0.1.0-alpha.15 → 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
@@ -5,9 +5,9 @@ import { Command } from "commander";
5
5
 
6
6
  // src/commands/init.ts
7
7
  import { execSync } from "child_process";
8
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync, copyFileSync } from "fs";
9
- import { join, dirname } from "path";
10
- import { fileURLToPath } from "url";
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";
11
11
  import chalk from "chalk";
12
12
  import ora from "ora";
13
13
  import { buildConfigJson, buildEngine } from "@holdpoint/engine-copilot";
@@ -58,34 +58,52 @@ function detectStack() {
58
58
  return "unknown";
59
59
  }
60
60
 
61
- // 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";
62
65
  var __dirname = dirname(fileURLToPath(import.meta.url));
63
- function getTemplatePath(stack) {
64
- const name = stack === "unknown" ? "_base" : stack;
66
+ function getBundledTemplatePath(filename) {
65
67
  const candidates = [
66
- join(__dirname, "templates", `${name}.yaml`),
68
+ join(__dirname, "templates", filename),
67
69
  // dist/templates/ (published package)
68
- join(__dirname, "../../../templates", `${name}.yaml`),
70
+ join(__dirname, "../../../templates", filename),
69
71
  // monorepo dev fallback
70
- join(process.cwd(), "templates", `${name}.yaml`)
72
+ join(process.cwd(), "templates", filename)
71
73
  // cwd fallback
72
74
  ];
73
- for (const p of candidates) {
74
- if (existsSync2(p)) return p;
75
+ for (const candidate of candidates) {
76
+ if (existsSync2(candidate)) return candidate;
75
77
  }
76
78
  return "";
77
79
  }
78
- 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;
79
97
  const candidates = [
80
- join(__dirname, "templates/MASTER_PROMPT.md"),
98
+ join2(__dirname2, "templates", `${name}.yaml`),
81
99
  // dist/templates/ (published package)
82
- join(__dirname, "../../../templates/MASTER_PROMPT.md"),
100
+ join2(__dirname2, "../../../templates", `${name}.yaml`),
83
101
  // monorepo dev fallback
84
- join(process.cwd(), "templates/MASTER_PROMPT.md")
102
+ join2(process.cwd(), "templates", `${name}.yaml`)
85
103
  // cwd fallback
86
104
  ];
87
105
  for (const p of candidates) {
88
- if (existsSync2(p)) return p;
106
+ if (existsSync3(p)) return p;
89
107
  }
90
108
  return "";
91
109
  }
@@ -102,6 +120,21 @@ checks:
102
120
  label: "JSDoc on changed public functions"
103
121
  prompt: "Ensure all changed public functions and exports have JSDoc comments."
104
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
+ `;
105
138
  async function initCommand(options) {
106
139
  const spinner = ora("Initialising Holdpoint\u2026").start();
107
140
  const stack = options.stack ?? detectStack();
@@ -110,7 +143,7 @@ async function initCommand(options) {
110
143
  spinner.text = `Stack: ${chalk.cyan(stack)} \u2014 installing for: ${chalk.cyan(agents.join(", "))}`;
111
144
  const pm = detectPackageManager();
112
145
  let yamlContent = MINIMAL_CHECKS_YAML;
113
- if (!existsSync2("checks.yaml")) {
146
+ if (!existsSync3("checks.yaml")) {
114
147
  const templatePath = getTemplatePath(stack);
115
148
  if (templatePath) {
116
149
  yamlContent = readFileSync2(templatePath, "utf8");
@@ -118,31 +151,31 @@ async function initCommand(options) {
118
151
  if (pm !== "pnpm") {
119
152
  yamlContent = yamlContent.replace(/\bpnpm\b/g, pm);
120
153
  }
121
- writeFileSync("checks.yaml", yamlContent, "utf8");
154
+ writeFileSync2("checks.yaml", yamlContent, "utf8");
122
155
  } else {
123
156
  yamlContent = readFileSync2("checks.yaml", "utf8");
124
157
  }
125
158
  const config = parseHoldpointYaml(yamlContent);
126
159
  const generatedDir = ".github/holdpoint/generated";
127
160
  mkdirSync(generatedDir, { recursive: true });
128
- writeFileSync(`${generatedDir}/checks.immutable.json`, buildConfigJson(config), "utf8");
161
+ writeFileSync2(`${generatedDir}/checks.immutable.json`, buildConfigJson(config), "utf8");
129
162
  if (agents.includes("copilot")) {
130
163
  const extDir = ".github/extensions/holdpoint";
131
164
  mkdirSync(extDir, { recursive: true });
132
- writeFileSync(join(extDir, "extension.mjs"), buildEngine(config), "utf8");
165
+ writeFileSync2(join2(extDir, "extension.mjs"), buildEngine(config), "utf8");
133
166
  }
134
167
  if (agents.includes("claude")) {
135
168
  mkdirSync(".claude", { recursive: true });
136
169
  const settingsPath = ".claude/settings.json";
137
170
  let existing = {};
138
- if (existsSync2(settingsPath)) {
171
+ if (existsSync3(settingsPath)) {
139
172
  try {
140
173
  existing = JSON.parse(readFileSync2(settingsPath, "utf8"));
141
174
  } catch {
142
175
  }
143
176
  }
144
177
  const holdpointHooks = JSON.parse(buildClaudeEngineJson(config));
145
- writeFileSync(
178
+ writeFileSync2(
146
179
  settingsPath,
147
180
  JSON.stringify({ ...existing, hooks: holdpointHooks.hooks }, null, 2),
148
181
  "utf8"
@@ -151,36 +184,30 @@ async function initCommand(options) {
151
184
  if (agents.includes("cursor")) {
152
185
  const cursorRules = buildCursorEngine(config);
153
186
  const cursorPath = ".cursorrules";
154
- if (existsSync2(cursorPath)) {
187
+ if (existsSync3(cursorPath)) {
155
188
  const existing = readFileSync2(cursorPath, "utf8");
156
189
  if (!existing.includes("Holdpoint Rules")) {
157
- writeFileSync(cursorPath, existing + "\n" + cursorRules, "utf8");
190
+ writeFileSync2(cursorPath, existing + "\n" + cursorRules, "utf8");
158
191
  }
159
192
  } else {
160
- writeFileSync(cursorPath, cursorRules, "utf8");
193
+ writeFileSync2(cursorPath, cursorRules, "utf8");
161
194
  }
162
195
  }
163
196
  if (agents.includes("codex")) {
164
197
  mkdirSync(".codex", { recursive: true });
165
- writeFileSync(".codex/hooks.json", buildCodexHooksJson(config), "utf8");
166
- writeFileSync(".codex/holdpoint-check.mjs", buildCodexCheckScript(config), "utf8");
167
- 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");
168
201
  const agentsMdPath = "AGENTS.md";
169
- const existing = existsSync2(agentsMdPath) ? readFileSync2(agentsMdPath, "utf8") : "";
170
- writeFileSync(agentsMdPath, spliceAgentsMd(existing, config), "utf8");
171
- }
172
- if (!existsSync2("MASTER_PROMPT.md")) {
173
- const guidePath = getMasterPromptPath();
174
- if (guidePath) {
175
- copyFileSync(guidePath, "MASTER_PROMPT.md");
176
- } else {
177
- writeFileSync(
178
- "MASTER_PROMPT.md",
179
- "# Holdpoint\n\nRun `holdpoint check` before marking any task complete.\nSee `checks.yaml` for the full list of checks.\n",
180
- "utf8"
181
- );
182
- }
202
+ const existing = existsSync3(agentsMdPath) ? readFileSync2(agentsMdPath, "utf8") : "";
203
+ writeFileSync2(agentsMdPath, spliceAgentsMd(existing, config), "utf8");
183
204
  }
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
+ );
184
211
  spinner.text = "Installing holdpoint as a devDependency\u2026";
185
212
  const installCmds = {
186
213
  pnpm: "pnpm add -D holdpoint@alpha",
@@ -200,8 +227,13 @@ async function initCommand(options) {
200
227
  console.log(`
201
228
  ${chalk.cyan("Next steps:")}
202
229
  1. Edit ${chalk.yellow("checks.yaml")} to customise your eval checkpoints
203
- 2. Commit ${chalk.yellow("checks.yaml")} and the generated engine files
204
- 3. Run ${chalk.yellow("holdpoint 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
233
+
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.
205
237
 
206
238
  Visual builder: ${chalk.yellow("holdpoint builder")} (opens localhost:4321)
207
239
  Stack: ${chalk.cyan(stack)} Agents: ${chalk.cyan(agents.join(", "))}
@@ -209,13 +241,16 @@ ${chalk.cyan("Next steps:")}
209
241
  }
210
242
 
211
243
  // src/commands/check.ts
212
- import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
213
- 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";
214
246
  import chalk2 from "chalk";
215
247
  import ora2 from "ora";
216
248
  import { parseHoldpointYaml as parseHoldpointYaml2, matchesWhen } from "@holdpoint/yaml-core";
217
249
  import { runDeterministicChecks } from "@holdpoint/yaml-core/runner";
218
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";
219
254
  var COMMIT_CACHE_PATH = ".holdpoint/checked-commits.json";
220
255
  var COMMIT_CACHE_MAX = 100;
221
256
  var CHECK_REPORTS_PATH = ".holdpoint/check-reports.json";
@@ -276,16 +311,16 @@ function recordCommitCache(sha) {
276
311
  try {
277
312
  const existing = readCommitCache();
278
313
  const updated = [sha, ...[...existing].filter((s) => s !== sha)].slice(0, COMMIT_CACHE_MAX);
279
- mkdirSync2(join2(COMMIT_CACHE_PATH, ".."), { recursive: true });
280
- 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");
281
316
  } catch {
282
317
  }
283
318
  }
284
319
  function recordCheckReport(run) {
285
320
  try {
286
- mkdirSync2(join2(CHECK_REPORTS_PATH, ".."), { recursive: true });
321
+ mkdirSync2(join3(CHECK_REPORTS_PATH, ".."), { recursive: true });
287
322
  let existing = { runs: [] };
288
- if (existsSync3(CHECK_REPORTS_PATH)) {
323
+ if (existsSync4(CHECK_REPORTS_PATH)) {
289
324
  try {
290
325
  existing = JSON.parse(readFileSync3(CHECK_REPORTS_PATH, "utf8"));
291
326
  if (!Array.isArray(existing.runs)) existing.runs = [];
@@ -296,12 +331,12 @@ function recordCheckReport(run) {
296
331
  const updated = {
297
332
  runs: [run, ...existing.runs].slice(0, CHECK_REPORTS_MAX)
298
333
  };
299
- writeFileSync2(CHECK_REPORTS_PATH, JSON.stringify(updated, null, 2) + "\n", "utf8");
334
+ writeFileSync3(CHECK_REPORTS_PATH, JSON.stringify(updated, null, 2) + "\n", "utf8");
300
335
  } catch {
301
336
  }
302
337
  }
303
338
  async function checkCommand(options) {
304
- if (!existsSync3("checks.yaml")) {
339
+ if (!existsSync4("checks.yaml")) {
305
340
  console.error(chalk2.red("No checks.yaml found. Run `holdpoint init` first."));
306
341
  process.exit(1);
307
342
  }
@@ -420,6 +455,30 @@ ${chalk2.cyan("Agent prompts to act on:")}`);
420
455
  }
421
456
  };
422
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
+ }
423
482
  if (failed.length > 0) {
424
483
  process.exit(1);
425
484
  }
@@ -441,11 +500,11 @@ function printResult(result) {
441
500
  }
442
501
 
443
502
  // src/commands/validate.ts
444
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
503
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
445
504
  import chalk3 from "chalk";
446
505
  import { parseHoldpointYaml as parseHoldpointYaml3, validateConfig } from "@holdpoint/yaml-core";
447
506
  async function validateCommand() {
448
- if (!existsSync4("checks.yaml")) {
507
+ if (!existsSync5("checks.yaml")) {
449
508
  console.error(chalk3.red("No checks.yaml found. Run `holdpoint init` first."));
450
509
  process.exit(1);
451
510
  }
@@ -475,7 +534,7 @@ async function validateCommand() {
475
534
  }
476
535
 
477
536
  // src/commands/update.ts
478
- 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";
479
538
  import chalk4 from "chalk";
480
539
  import ora3 from "ora";
481
540
  import { parseHoldpointYaml as parseHoldpointYaml4 } from "@holdpoint/yaml-core";
@@ -488,8 +547,18 @@ import {
488
547
  buildCheckScript as buildCodexCheckScript2,
489
548
  spliceAgentsMd as spliceAgentsMd2
490
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
+ `;
491
560
  async function updateCommand() {
492
- if (!existsSync5("checks.yaml")) {
561
+ if (!existsSync6("checks.yaml")) {
493
562
  console.error(chalk4.red("No checks.yaml found. Run `holdpoint init` first."));
494
563
  process.exit(1);
495
564
  }
@@ -499,24 +568,24 @@ async function updateCommand() {
499
568
  const agents = detected.length > 0 ? detected : ["copilot", "claude", "cursor", "codex"];
500
569
  const generatedDir = ".github/holdpoint/generated";
501
570
  mkdirSync3(generatedDir, { recursive: true });
502
- writeFileSync3(`${generatedDir}/checks.immutable.json`, buildConfigJson2(config), "utf8");
571
+ writeFileSync4(`${generatedDir}/checks.immutable.json`, buildConfigJson2(config), "utf8");
503
572
  if (agents.includes("copilot")) {
504
573
  const extDir = ".github/extensions/holdpoint";
505
574
  mkdirSync3(extDir, { recursive: true });
506
- writeFileSync3(`${extDir}/extension.mjs`, buildEngine2(config), "utf8");
575
+ writeFileSync4(`${extDir}/extension.mjs`, buildEngine2(config), "utf8");
507
576
  }
508
577
  if (agents.includes("claude")) {
509
578
  mkdirSync3(".claude", { recursive: true });
510
579
  const settingsPath = ".claude/settings.json";
511
580
  let existing = {};
512
- if (existsSync5(settingsPath)) {
581
+ if (existsSync6(settingsPath)) {
513
582
  try {
514
583
  existing = JSON.parse(readFileSync5(settingsPath, "utf8"));
515
584
  } catch {
516
585
  }
517
586
  }
518
587
  const hooks = JSON.parse(buildClaudeEngineJson2(config));
519
- writeFileSync3(
588
+ writeFileSync4(
520
589
  settingsPath,
521
590
  JSON.stringify({ ...existing, hooks: hooks.hooks }, null, 2) + "\n"
522
591
  );
@@ -524,47 +593,59 @@ async function updateCommand() {
524
593
  if (agents.includes("cursor")) {
525
594
  const cursorRules = buildCursorEngine2(config);
526
595
  const cursorPath = ".cursorrules";
527
- if (existsSync5(cursorPath)) {
596
+ if (existsSync6(cursorPath)) {
528
597
  const content = readFileSync5(cursorPath, "utf8");
529
598
  const start = content.indexOf("# \u2500\u2500\u2500 Holdpoint Rules");
530
599
  const end = content.indexOf("# \u2500\u2500\u2500 End Holdpoint Rules \u2500\u2500\u2500");
531
600
  if (start !== -1 && end !== -1) {
532
601
  const afterEnd = content.indexOf("\n", end);
533
602
  const updated = content.slice(0, start) + cursorRules + content.slice(afterEnd === -1 ? end : afterEnd + 1);
534
- writeFileSync3(cursorPath, updated);
603
+ writeFileSync4(cursorPath, updated);
535
604
  } else {
536
- writeFileSync3(cursorPath, content + "\n" + cursorRules);
605
+ writeFileSync4(cursorPath, content + "\n" + cursorRules);
537
606
  }
538
607
  }
539
608
  }
540
609
  if (agents.includes("codex")) {
541
610
  mkdirSync3(".codex", { recursive: true });
542
- writeFileSync3(".codex/hooks.json", buildCodexHooksJson2(config), "utf8");
543
- 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");
544
613
  const configTomlPath = ".codex/config.toml";
545
- if (!existsSync5(configTomlPath)) {
546
- writeFileSync3(configTomlPath, buildCodexConfigToml2(), "utf8");
614
+ if (!existsSync6(configTomlPath)) {
615
+ writeFileSync4(configTomlPath, buildCodexConfigToml2(), "utf8");
547
616
  } else {
548
617
  const existing2 = readFileSync5(configTomlPath, "utf8");
549
618
  if (!existing2.includes("[features]")) {
550
- writeFileSync3(configTomlPath, existing2.trimEnd() + "\n\n" + buildCodexConfigToml2(), "utf8");
619
+ writeFileSync4(configTomlPath, existing2.trimEnd() + "\n\n" + buildCodexConfigToml2(), "utf8");
551
620
  }
552
621
  }
553
622
  const agentsMdPath = "AGENTS.md";
554
- const existing = existsSync5(agentsMdPath) ? readFileSync5(agentsMdPath, "utf8") : "";
555
- writeFileSync3(agentsMdPath, spliceAgentsMd2(existing, config), "utf8");
623
+ const existing = existsSync6(agentsMdPath) ? readFileSync5(agentsMdPath, "utf8") : "";
624
+ writeFileSync4(agentsMdPath, spliceAgentsMd2(existing, config), "utf8");
556
625
  }
626
+ const wrotePrerequisites = ensureBundledFile(
627
+ "HOLDPOINT_PREREQUISITES.md",
628
+ "HOLDPOINT_PREREQUISITES.md",
629
+ MINIMAL_PREREQUISITES2
630
+ );
557
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
+ }
558
639
  }
559
640
 
560
641
  // src/commands/build.ts
561
642
  import { createServer } from "http";
562
- import { createReadStream, existsSync as existsSync6 } from "fs";
563
- import { join as join3, extname, dirname as dirname2 } from "path";
564
- import { fileURLToPath as fileURLToPath2 } from "url";
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";
565
646
  import { execSync as execSync3 } from "child_process";
566
647
  import chalk5 from "chalk";
567
- var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
648
+ var __dirname3 = dirname3(fileURLToPath3(import.meta.url));
568
649
  var MIME = {
569
650
  ".html": "text/html; charset=utf-8",
570
651
  ".js": "text/javascript",
@@ -586,8 +667,8 @@ function serveFile(res, filePath) {
586
667
  function handleRequest(req, res, uiDir) {
587
668
  const url = (req.url ?? "/").split("?")[0] ?? "/";
588
669
  if (url === "/__holdpoint/initial-yaml") {
589
- const checksPath = join3(process.cwd(), "checks.yaml");
590
- if (existsSync6(checksPath)) {
670
+ const checksPath = join4(process.cwd(), "checks.yaml");
671
+ if (existsSync7(checksPath)) {
591
672
  res.writeHead(200, { "Content-Type": "text/yaml; charset=utf-8" });
592
673
  createReadStream(checksPath).pipe(res);
593
674
  } else {
@@ -597,8 +678,8 @@ function handleRequest(req, res, uiDir) {
597
678
  return;
598
679
  }
599
680
  if (url === "/__holdpoint/initial-reports") {
600
- const reportsPath = join3(process.cwd(), ".holdpoint", "check-reports.json");
601
- if (existsSync6(reportsPath)) {
681
+ const reportsPath = join4(process.cwd(), ".holdpoint", "check-reports.json");
682
+ if (existsSync7(reportsPath)) {
602
683
  res.writeHead(200, { "Content-Type": "application/json" });
603
684
  createReadStream(reportsPath).pipe(res);
604
685
  } else {
@@ -607,21 +688,21 @@ function handleRequest(req, res, uiDir) {
607
688
  }
608
689
  return;
609
690
  }
610
- const candidate = join3(uiDir, url === "/" ? "index.html" : url);
611
- 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");
612
693
  serveFile(res, filePath);
613
694
  }
614
695
  async function buildCommand() {
615
696
  const port = 4321;
616
- const uiDir = join3(__dirname2, "builder-ui");
617
- if (!existsSync6(uiDir)) {
697
+ const uiDir = join4(__dirname3, "builder-ui");
698
+ if (!existsSync7(uiDir)) {
618
699
  console.error(chalk5.red("\u2717 Builder UI not found.\n"));
619
700
  console.log(chalk5.dim(" This is unexpected for a published build of @holdpoint/cli."));
620
701
  console.log(chalk5.dim(" If you installed from source, rebuild with: pnpm turbo build\n"));
621
702
  process.exit(1);
622
703
  }
623
704
  const server = createServer((req, res) => handleRequest(req, res, uiDir));
624
- await new Promise((resolve, reject) => {
705
+ await new Promise((resolve2, reject) => {
625
706
  server.listen(port, () => {
626
707
  console.log(
627
708
  `
@@ -638,21 +719,21 @@ ${chalk5.green("\u2713")} Holdpoint builder running at ${chalk5.cyan(`http://loc
638
719
  server.on("error", reject);
639
720
  process.on("SIGINT", () => {
640
721
  console.log(chalk5.dim("\n Stopping builder\u2026"));
641
- server.close(() => resolve());
722
+ server.close(() => resolve2());
642
723
  });
643
724
  });
644
725
  }
645
726
 
646
727
  // src/commands/evolve.ts
647
- import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
728
+ import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
648
729
  import { execSync as execSync6 } from "child_process";
649
730
  import chalk6 from "chalk";
650
731
  import ora4 from "ora";
651
732
  import { parseHoldpointYaml as parseHoldpointYaml5, generateYaml } from "@holdpoint/yaml-core";
652
733
 
653
734
  // src/evolve/scanner.ts
654
- import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync } from "fs";
655
- import { join as join4 } from "path";
735
+ import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync } from "fs";
736
+ import { join as join5 } from "path";
656
737
  import { execSync as execSync4 } from "child_process";
657
738
  function tryReadJson(path) {
658
739
  try {
@@ -669,17 +750,17 @@ function tryReadText(path) {
669
750
  }
670
751
  }
671
752
  function scanProject(cwd = process.cwd()) {
672
- const exists = (p) => existsSync7(join4(cwd, p));
753
+ const exists = (p) => existsSync8(join5(cwd, p));
673
754
  const packageManager = exists("pnpm-lock.yaml") ? "pnpm" : exists("yarn.lock") ? "yarn" : exists("bun.lockb") ? "bun" : "npm";
674
- const pkg = tryReadJson(join4(cwd, "package.json"));
755
+ const pkg = tryReadJson(join5(cwd, "package.json"));
675
756
  const scripts = pkg?.scripts ?? {};
676
757
  const deps = /* @__PURE__ */ new Set([
677
758
  ...Object.keys(pkg?.dependencies ?? {}),
678
759
  ...Object.keys(pkg?.devDependencies ?? {})
679
760
  ]);
680
- const pyprojectText = tryReadText(join4(cwd, "pyproject.toml"));
681
- const requirementsText = tryReadText(join4(cwd, "requirements.txt"));
682
- 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"));
683
764
  const allPyText = pyprojectText + requirementsText + pipfileText;
684
765
  const hasPytest = exists("pytest.ini") || exists("setup.cfg") || allPyText.includes("pytest") || allPyText.includes("[tool.pytest");
685
766
  const hasRuff = allPyText.includes("ruff") || deps.has("ruff");
@@ -732,8 +813,8 @@ function scanProject(cwd = process.cwd()) {
732
813
 
733
814
  // src/evolve/dead-checker.ts
734
815
  import { execSync as execSync5 } from "child_process";
735
- import { readdirSync as readdirSync2, existsSync as existsSync8 } from "fs";
736
- import { join as join5 } from "path";
816
+ import { readdirSync as readdirSync2, existsSync as existsSync9 } from "fs";
817
+ import { join as join6 } from "path";
737
818
  var NAMED_SCOPES = /* @__PURE__ */ new Set([
738
819
  "frontend",
739
820
  "backend",
@@ -778,7 +859,7 @@ function walkDir(dir, root, depth, maxDepth) {
778
859
  const results = [];
779
860
  for (const entry of entries) {
780
861
  if (WALK_IGNORED.has(entry) || entry.startsWith(".")) continue;
781
- const full = join5(dir, entry);
862
+ const full = join6(dir, entry);
782
863
  const rel = full.slice(root.length + 1);
783
864
  results.push(rel);
784
865
  const children = walkDir(full, root, depth + 1, maxDepth);
@@ -825,7 +906,7 @@ function detectStaleChecks(config, repoFiles) {
825
906
  if (matches.length === 0) {
826
907
  const label = patternAlias ? `Pattern '${patternAlias}' (= '${regexStr}')` : `Regex '${regexStr}'`;
827
908
  const suggestedConditionPath = extractPathFromRegex(regexStr);
828
- const pathGone = !suggestedConditionPath || !existsSync8(join5(process.cwd(), suggestedConditionPath));
909
+ const pathGone = !suggestedConditionPath || !existsSync9(join6(process.cwd(), suggestedConditionPath));
829
910
  if (pathGone) {
830
911
  stale.push({
831
912
  check,
@@ -1026,7 +1107,7 @@ function withHeader(header, newYaml) {
1026
1107
  return header + "\n\n" + newYaml;
1027
1108
  }
1028
1109
  async function evolveCommand(options) {
1029
- if (!existsSync9("checks.yaml")) {
1110
+ if (!existsSync10("checks.yaml")) {
1030
1111
  console.error(chalk6.red("No checks.yaml found. Run `holdpoint init` first."));
1031
1112
  process.exit(1);
1032
1113
  }
@@ -1148,7 +1229,7 @@ async function evolveCommand(options) {
1148
1229
  };
1149
1230
  const header = extractHeader(yamlContent);
1150
1231
  const newYaml = withHeader(header, generateYaml(updatedConfig));
1151
- writeFileSync4("checks.yaml", newYaml, "utf8");
1232
+ writeFileSync5("checks.yaml", newYaml, "utf8");
1152
1233
  applySpinner.text = "Running holdpoint update\u2026";
1153
1234
  try {
1154
1235
  execSync6("npx @holdpoint/cli@alpha update", { stdio: "pipe" });
@@ -1173,9 +1254,652 @@ function printAppliedSummary(added, wrapped) {
1173
1254
  );
1174
1255
  }
1175
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
+
1176
1898
  // src/index.ts
1177
1899
  var program = new Command();
1178
- 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
+ });
1179
1903
  program.command("init").description("Initialise Holdpoint in the current project").option("--stack <stack>", "Stack type: typescript | python | nextjs | fullstack").option(
1180
1904
  "--agent <agent>",
1181
1905
  "Agent to install for: copilot | claude | cursor | codex (default: all four)"
@@ -1184,6 +1908,14 @@ program.command("check").description("Run task checks from checks.yaml").option(
1184
1908
  program.command("validate").description("Validate checks.yaml schema and print any errors").action(validateCommand);
1185
1909
  program.command("update").description("Regenerate engine files from current checks.yaml (preserves checks.yaml)").action(updateCommand);
1186
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);
1187
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);
1188
1920
  program.parse();
1189
1921
  //# sourceMappingURL=index.js.map