@holdpoint/cli 0.1.0-alpha.2 → 0.1.0-alpha.5

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.
@@ -5,8 +5,8 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>holdpoint builder</title>
8
- <script type="module" crossorigin src="/assets/index-BxfWKnb5.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-DkLHZ-in.css">
8
+ <script type="module" crossorigin src="/assets/index-BqVxTIWS.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-3J1uDBNl.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
package/dist/index.js CHANGED
@@ -4,23 +4,42 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/commands/init.ts
7
- import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync, copyFileSync } from "fs";
7
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync, copyFileSync } from "fs";
8
8
  import { join, dirname } from "path";
9
9
  import { fileURLToPath } from "url";
10
10
  import chalk from "chalk";
11
11
  import ora from "ora";
12
- import { buildHookJson, buildCheckScript, buildConfigJson } from "@holdpoint/engine-copilot";
12
+ import {
13
+ buildHookJson,
14
+ buildCheckScript,
15
+ buildConfigJson,
16
+ buildEngine
17
+ } from "@holdpoint/engine-copilot";
13
18
  import { buildEngineJson as buildClaudeEngineJson } from "@holdpoint/engine-claude";
14
19
  import { buildEngine as buildCursorEngine } from "@holdpoint/engine-cursor";
20
+ import {
21
+ buildHooksJson as buildCodexHooksJson,
22
+ buildCheckScript as buildCodexCheckScript,
23
+ spliceAgentsMd
24
+ } from "@holdpoint/engine-codex";
15
25
  import { parseHoldpointYaml } from "@holdpoint/yaml-core";
16
26
 
17
27
  // src/detect.ts
18
- import { existsSync } from "fs";
19
- function detectAgent() {
20
- if (existsSync(".github/extensions")) return "copilot";
21
- if (existsSync(".claude")) return "claude";
22
- if (existsSync(".cursorrules")) return "cursor";
23
- return "unknown";
28
+ import { existsSync, readFileSync } from "fs";
29
+ function detectInstalledAgents() {
30
+ const agents = [];
31
+ if (existsSync(".github/hooks/holdpoint.json")) agents.push("copilot");
32
+ if (existsSync(".claude/settings.json")) agents.push("claude");
33
+ if (existsSync(".cursorrules")) {
34
+ try {
35
+ if (readFileSync(".cursorrules", "utf8").includes("Holdpoint Rules")) {
36
+ agents.push("cursor");
37
+ }
38
+ } catch {
39
+ }
40
+ }
41
+ if (existsSync(".codex/holdpoint-check.mjs")) agents.push("codex");
42
+ return agents;
24
43
  }
25
44
  function detectStack() {
26
45
  const hasNext = existsSync("next.config.ts") || existsSync("next.config.js") || existsSync("next.config.mjs");
@@ -84,35 +103,39 @@ checks:
84
103
  async function initCommand(options) {
85
104
  const spinner = ora("Initialising Holdpoint\u2026").start();
86
105
  const stack = options.stack ?? detectStack();
87
- const agent = options.agent ?? detectAgent();
88
- spinner.text = `Detected stack: ${chalk.cyan(stack)}, agent: ${chalk.cyan(agent)}`;
106
+ const agentOpt = options.agent;
107
+ const agents = !agentOpt || agentOpt === "all" ? ["copilot", "claude", "cursor", "codex"] : [agentOpt];
108
+ spinner.text = `Stack: ${chalk.cyan(stack)} \u2014 installing for: ${chalk.cyan(agents.join(", "))}`;
89
109
  let yamlContent = MINIMAL_CHECKS_YAML;
90
110
  if (!existsSync2("checks.yaml")) {
91
111
  const templatePath = getTemplatePath(stack);
92
112
  if (templatePath) {
93
- yamlContent = readFileSync(templatePath, "utf8");
113
+ yamlContent = readFileSync2(templatePath, "utf8");
94
114
  }
95
115
  writeFileSync("checks.yaml", yamlContent, "utf8");
96
116
  } else {
97
- yamlContent = readFileSync("checks.yaml", "utf8");
117
+ yamlContent = readFileSync2("checks.yaml", "utf8");
98
118
  }
99
119
  const config = parseHoldpointYaml(yamlContent);
100
120
  const generatedDir = ".github/holdpoint/generated";
101
121
  mkdirSync(generatedDir, { recursive: true });
102
122
  writeFileSync(`${generatedDir}/checks.immutable.json`, buildConfigJson(config), "utf8");
103
- if (agent === "copilot" || agent === "unknown") {
123
+ if (agents.includes("copilot")) {
104
124
  const hooksDir = ".github/hooks";
105
125
  mkdirSync(hooksDir, { recursive: true });
106
126
  writeFileSync(join(hooksDir, "holdpoint.json"), buildHookJson(config), "utf8");
107
127
  writeFileSync(join(hooksDir, "holdpoint-check.mjs"), buildCheckScript(config), "utf8");
128
+ const extDir = ".github/extensions/holdpoint";
129
+ mkdirSync(extDir, { recursive: true });
130
+ writeFileSync(join(extDir, "extension.mjs"), buildEngine(config), "utf8");
108
131
  }
109
- if (agent === "claude") {
132
+ if (agents.includes("claude")) {
110
133
  mkdirSync(".claude", { recursive: true });
111
134
  const settingsPath = ".claude/settings.json";
112
135
  let existing = {};
113
136
  if (existsSync2(settingsPath)) {
114
137
  try {
115
- existing = JSON.parse(readFileSync(settingsPath, "utf8"));
138
+ existing = JSON.parse(readFileSync2(settingsPath, "utf8"));
116
139
  } catch {
117
140
  }
118
141
  }
@@ -123,11 +146,11 @@ async function initCommand(options) {
123
146
  "utf8"
124
147
  );
125
148
  }
126
- if (agent === "cursor") {
149
+ if (agents.includes("cursor")) {
127
150
  const cursorRules = buildCursorEngine(config);
128
151
  const cursorPath = ".cursorrules";
129
152
  if (existsSync2(cursorPath)) {
130
- const existing = readFileSync(cursorPath, "utf8");
153
+ const existing = readFileSync2(cursorPath, "utf8");
131
154
  if (!existing.includes("Holdpoint Rules")) {
132
155
  writeFileSync(cursorPath, existing + "\n" + cursorRules, "utf8");
133
156
  }
@@ -135,6 +158,14 @@ async function initCommand(options) {
135
158
  writeFileSync(cursorPath, cursorRules, "utf8");
136
159
  }
137
160
  }
161
+ if (agents.includes("codex")) {
162
+ mkdirSync(".codex", { recursive: true });
163
+ writeFileSync(".codex/hooks.json", buildCodexHooksJson(config), "utf8");
164
+ writeFileSync(".codex/holdpoint-check.mjs", buildCodexCheckScript(), "utf8");
165
+ const agentsMdPath = "AGENTS.md";
166
+ const existing = existsSync2(agentsMdPath) ? readFileSync2(agentsMdPath, "utf8") : "";
167
+ writeFileSync(agentsMdPath, spliceAgentsMd(existing, config), "utf8");
168
+ }
138
169
  if (!existsSync2("MASTER_PROMPT.md")) {
139
170
  const guidePath = getMasterPromptPath();
140
171
  if (guidePath) {
@@ -155,12 +186,13 @@ ${chalk.cyan("Next steps:")}
155
186
  3. Run ${chalk.yellow("npx @holdpoint/cli@alpha check")} at any time to validate
156
187
 
157
188
  Visual builder: ${chalk.yellow("npx @holdpoint/cli@alpha builder")} (opens localhost:4321)
158
- Stack: ${chalk.cyan(stack)} Agent: ${chalk.cyan(agent)}
189
+ Stack: ${chalk.cyan(stack)} Agents: ${chalk.cyan(agents.join(", "))}
159
190
  `);
160
191
  }
161
192
 
162
193
  // src/commands/check.ts
163
- import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
194
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
195
+ import { join as join4 } from "path";
164
196
  import chalk2 from "chalk";
165
197
  import ora2 from "ora";
166
198
  import { parseHoldpointYaml as parseHoldpointYaml2, matchesWhen } from "@holdpoint/yaml-core";
@@ -168,19 +200,19 @@ import { runDeterministicChecks } from "@holdpoint/yaml-core/runner";
168
200
  import { execSync as execSync3 } from "child_process";
169
201
 
170
202
  // src/evolve/scanner.ts
171
- import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync } from "fs";
203
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync } from "fs";
172
204
  import { join as join2 } from "path";
173
205
  import { execSync } from "child_process";
174
206
  function tryReadJson(path) {
175
207
  try {
176
- return JSON.parse(readFileSync2(path, "utf8"));
208
+ return JSON.parse(readFileSync3(path, "utf8"));
177
209
  } catch {
178
210
  return null;
179
211
  }
180
212
  }
181
213
  function tryReadText(path) {
182
214
  try {
183
- return readFileSync2(path, "utf8");
215
+ return readFileSync3(path, "utf8");
184
216
  } catch {
185
217
  return "";
186
218
  }
@@ -523,6 +555,10 @@ function detectStaleChecks(config, repoFiles) {
523
555
  }
524
556
 
525
557
  // src/commands/check.ts
558
+ var COMMIT_CACHE_PATH = ".holdpoint/checked-commits.json";
559
+ var COMMIT_CACHE_MAX = 100;
560
+ var CHECK_REPORTS_PATH = ".holdpoint/check-reports.json";
561
+ var CHECK_REPORTS_MAX = 50;
526
562
  function getStagedFiles() {
527
563
  try {
528
564
  const out = execSync3("git diff --cached --name-only", {
@@ -545,12 +581,70 @@ function getAllChangedFiles() {
545
581
  return [];
546
582
  }
547
583
  }
584
+ function getLastCommitFiles() {
585
+ try {
586
+ const out = execSync3("git diff --name-only HEAD~1 HEAD", {
587
+ encoding: "utf8",
588
+ stdio: ["pipe", "pipe", "ignore"]
589
+ });
590
+ return out.trim().split("\n").filter(Boolean);
591
+ } catch {
592
+ return [];
593
+ }
594
+ }
595
+ function getHeadSha() {
596
+ try {
597
+ return execSync3("git rev-parse HEAD", {
598
+ encoding: "utf8",
599
+ stdio: ["pipe", "pipe", "ignore"]
600
+ }).trim();
601
+ } catch {
602
+ return null;
603
+ }
604
+ }
605
+ function readCommitCache() {
606
+ try {
607
+ const raw = readFileSync4(COMMIT_CACHE_PATH, "utf8");
608
+ const parsed = JSON.parse(raw);
609
+ return new Set(Array.isArray(parsed.verified) ? parsed.verified : []);
610
+ } catch {
611
+ return /* @__PURE__ */ new Set();
612
+ }
613
+ }
614
+ function recordCommitCache(sha) {
615
+ try {
616
+ const existing = readCommitCache();
617
+ const updated = [sha, ...[...existing].filter((s) => s !== sha)].slice(0, COMMIT_CACHE_MAX);
618
+ mkdirSync2(join4(COMMIT_CACHE_PATH, ".."), { recursive: true });
619
+ writeFileSync2(COMMIT_CACHE_PATH, JSON.stringify({ verified: updated }, null, 2) + "\n", "utf8");
620
+ } catch {
621
+ }
622
+ }
623
+ function recordCheckReport(run) {
624
+ try {
625
+ mkdirSync2(join4(CHECK_REPORTS_PATH, ".."), { recursive: true });
626
+ let existing = { runs: [] };
627
+ if (existsSync5(CHECK_REPORTS_PATH)) {
628
+ try {
629
+ existing = JSON.parse(readFileSync4(CHECK_REPORTS_PATH, "utf8"));
630
+ if (!Array.isArray(existing.runs)) existing.runs = [];
631
+ } catch {
632
+ existing = { runs: [] };
633
+ }
634
+ }
635
+ const updated = {
636
+ runs: [run, ...existing.runs].slice(0, CHECK_REPORTS_MAX)
637
+ };
638
+ writeFileSync2(CHECK_REPORTS_PATH, JSON.stringify(updated, null, 2) + "\n", "utf8");
639
+ } catch {
640
+ }
641
+ }
548
642
  async function checkCommand(options) {
549
643
  if (!existsSync5("checks.yaml")) {
550
644
  console.error(chalk2.red("No checks.yaml found. Run `holdpoint init` first."));
551
645
  process.exit(1);
552
646
  }
553
- const yamlContent = readFileSync3("checks.yaml", "utf8");
647
+ const yamlContent = readFileSync4("checks.yaml", "utf8");
554
648
  let config;
555
649
  try {
556
650
  config = parseHoldpointYaml2(yamlContent);
@@ -558,7 +652,45 @@ async function checkCommand(options) {
558
652
  console.error(chalk2.red("Invalid checks.yaml:"), err.message);
559
653
  process.exit(1);
560
654
  }
561
- const changedFiles = options.staged ? getStagedFiles() : getAllChangedFiles();
655
+ const headSha = getHeadSha();
656
+ let changedFiles;
657
+ let usedHeadShaForCache = false;
658
+ if (options.staged) {
659
+ const staged = getStagedFiles();
660
+ if (staged.length > 0) {
661
+ changedFiles = staged;
662
+ } else {
663
+ if (headSha && readCommitCache().has(headSha)) {
664
+ console.log(
665
+ chalk2.green(`\u2713 Commit ${headSha.slice(0, 8)} already verified \u2014 nothing to re-check.`)
666
+ );
667
+ process.exit(0);
668
+ }
669
+ const lastCommit = getLastCommitFiles();
670
+ if (lastCommit.length > 0) {
671
+ changedFiles = lastCommit;
672
+ usedHeadShaForCache = true;
673
+ console.log(
674
+ chalk2.yellow("No staged files. Running checks scoped to the most recent commit's files.")
675
+ );
676
+ } else {
677
+ console.log(chalk2.green("\u2713 No staged changes and no recent commit \u2014 nothing to check."));
678
+ process.exit(0);
679
+ }
680
+ }
681
+ } else {
682
+ changedFiles = getAllChangedFiles();
683
+ if (changedFiles.length === 0) {
684
+ console.log(
685
+ chalk2.yellow("No changed files detected. Running all checks with no file filter.")
686
+ );
687
+ console.log(
688
+ chalk2.dim(
689
+ " Tip: if you just ran `holdpoint init`, commit the generated files to clear the git-commit check."
690
+ )
691
+ );
692
+ }
693
+ }
562
694
  const guides = Object.entries(config.context?.guides ?? {});
563
695
  if (guides.length > 0) {
564
696
  console.log(chalk2.cyan("\nProject guides:"));
@@ -567,9 +699,6 @@ async function checkCommand(options) {
567
699
  }
568
700
  console.log("");
569
701
  }
570
- if (changedFiles.length === 0) {
571
- console.log(chalk2.yellow("No changed files detected. Running all checks with no file filter."));
572
- }
573
702
  const taskCount = config.checks.filter((c) => c.cmd !== void 0).length;
574
703
  const spinner = ora2(`Running ${taskCount} task(s)\u2026`).start();
575
704
  const effectiveFiles = changedFiles.length > 0 ? changedFiles : ["__all__"];
@@ -625,9 +754,43 @@ ${chalk2.cyan("Agent prompts to act on:")}`);
625
754
  console.log(` ${chalk2.yellow("\u25A1")} [${c.label}] ${c.prompt ?? ""}`);
626
755
  }
627
756
  }
757
+ const reportResults = [
758
+ ...results.map((r) => ({
759
+ id: r.check.id,
760
+ label: r.check.label,
761
+ kind: "cmd",
762
+ status: r.status,
763
+ ...r.output !== void 0 ? { output: r.output } : {},
764
+ ...r.exitCode !== void 0 ? { exitCode: r.exitCode } : {},
765
+ ...r.skipReason !== void 0 ? { skipReason: r.skipReason } : {}
766
+ })),
767
+ ...promptChecks.map((c) => ({
768
+ id: c.id,
769
+ label: c.label,
770
+ kind: "prompt",
771
+ status: "shown"
772
+ }))
773
+ ];
774
+ const run = {
775
+ sha: headSha,
776
+ shortSha: headSha ? headSha.slice(0, 8) : null,
777
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
778
+ files: changedFiles.length > 0 ? changedFiles : [],
779
+ results: reportResults,
780
+ summary: {
781
+ passed: passed.length,
782
+ failed: failed.length,
783
+ skipped: skipped.length,
784
+ shown: promptChecks.length
785
+ }
786
+ };
787
+ recordCheckReport(run);
628
788
  if (failed.length > 0) {
629
789
  process.exit(1);
630
790
  }
791
+ if (usedHeadShaForCache && headSha) {
792
+ recordCommitCache(headSha);
793
+ }
631
794
  }
632
795
  function printResult(result) {
633
796
  const icon = result.status === "pass" ? chalk2.green("\u2713") : result.status === "fail" ? chalk2.red("\u2717") : result.status === "skip" ? chalk2.gray("\u25CC") : chalk2.yellow("\u2026");
@@ -643,7 +806,7 @@ function printResult(result) {
643
806
  }
644
807
 
645
808
  // src/commands/validate.ts
646
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
809
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
647
810
  import chalk3 from "chalk";
648
811
  import { parseHoldpointYaml as parseHoldpointYaml3, validateConfig } from "@holdpoint/yaml-core";
649
812
  async function validateCommand() {
@@ -651,7 +814,7 @@ async function validateCommand() {
651
814
  console.error(chalk3.red("No checks.yaml found. Run `holdpoint init` first."));
652
815
  process.exit(1);
653
816
  }
654
- const text = readFileSync4("checks.yaml", "utf8");
817
+ const text = readFileSync5("checks.yaml", "utf8");
655
818
  let config;
656
819
  try {
657
820
  config = parseHoldpointYaml3(text);
@@ -677,67 +840,91 @@ async function validateCommand() {
677
840
  }
678
841
 
679
842
  // src/commands/update.ts
680
- import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
843
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
681
844
  import chalk4 from "chalk";
682
845
  import ora3 from "ora";
683
846
  import { parseHoldpointYaml as parseHoldpointYaml4 } from "@holdpoint/yaml-core";
684
- import { buildHookJson as buildHookJson2, buildCheckScript as buildCheckScript2, buildConfigJson as buildConfigJson2 } from "@holdpoint/engine-copilot";
847
+ import {
848
+ buildHookJson as buildHookJson2,
849
+ buildCheckScript as buildCheckScript2,
850
+ buildConfigJson as buildConfigJson2,
851
+ buildEngine as buildEngine2
852
+ } from "@holdpoint/engine-copilot";
685
853
  import { buildEngineJson as buildClaudeEngineJson2 } from "@holdpoint/engine-claude";
686
854
  import { buildEngine as buildCursorEngine2 } from "@holdpoint/engine-cursor";
855
+ import {
856
+ buildHooksJson as buildCodexHooksJson2,
857
+ buildCheckScript as buildCodexCheckScript2,
858
+ spliceAgentsMd as spliceAgentsMd2
859
+ } from "@holdpoint/engine-codex";
687
860
  async function updateCommand() {
688
861
  if (!existsSync7("checks.yaml")) {
689
862
  console.error(chalk4.red("No checks.yaml found. Run `holdpoint init` first."));
690
863
  process.exit(1);
691
864
  }
692
865
  const spinner = ora3("Updating Holdpoint engine files\u2026").start();
693
- const agent = detectAgent();
694
- const config = parseHoldpointYaml4(readFileSync5("checks.yaml", "utf8"));
866
+ const config = parseHoldpointYaml4(readFileSync6("checks.yaml", "utf8"));
867
+ const detected = detectInstalledAgents();
868
+ const agents = detected.length > 0 ? detected : ["copilot", "claude", "cursor", "codex"];
695
869
  const generatedDir = ".github/holdpoint/generated";
696
- mkdirSync2(generatedDir, { recursive: true });
697
- writeFileSync2(`${generatedDir}/checks.immutable.json`, buildConfigJson2(config), "utf8");
698
- if (agent === "copilot" || agent === "unknown") {
870
+ mkdirSync3(generatedDir, { recursive: true });
871
+ writeFileSync3(`${generatedDir}/checks.immutable.json`, buildConfigJson2(config), "utf8");
872
+ if (agents.includes("copilot")) {
699
873
  const hooksDir = ".github/hooks";
700
- mkdirSync2(hooksDir, { recursive: true });
701
- writeFileSync2(`${hooksDir}/holdpoint.json`, buildHookJson2(config), "utf8");
702
- writeFileSync2(`${hooksDir}/holdpoint-check.mjs`, buildCheckScript2(config), "utf8");
703
- spinner.text = `Updated ${chalk4.green(".github/hooks/holdpoint.json")} and ${chalk4.green(".github/hooks/holdpoint-check.mjs")}`;
874
+ mkdirSync3(hooksDir, { recursive: true });
875
+ writeFileSync3(`${hooksDir}/holdpoint.json`, buildHookJson2(config), "utf8");
876
+ writeFileSync3(`${hooksDir}/holdpoint-check.mjs`, buildCheckScript2(config), "utf8");
877
+ const extDir = ".github/extensions/holdpoint";
878
+ mkdirSync3(extDir, { recursive: true });
879
+ writeFileSync3(`${extDir}/extension.mjs`, buildEngine2(config), "utf8");
704
880
  }
705
- if (agent === "claude") {
706
- mkdirSync2(".claude", { recursive: true });
881
+ if (agents.includes("claude")) {
882
+ mkdirSync3(".claude", { recursive: true });
707
883
  const settingsPath = ".claude/settings.json";
708
884
  let existing = {};
709
885
  if (existsSync7(settingsPath)) {
710
886
  try {
711
- existing = JSON.parse(readFileSync5(settingsPath, "utf8"));
887
+ existing = JSON.parse(readFileSync6(settingsPath, "utf8"));
712
888
  } catch {
713
889
  }
714
890
  }
715
891
  const hooks = JSON.parse(buildClaudeEngineJson2(config));
716
- writeFileSync2(settingsPath, JSON.stringify({ ...existing, hooks: hooks.hooks }, null, 2));
892
+ writeFileSync3(
893
+ settingsPath,
894
+ JSON.stringify({ ...existing, hooks: hooks.hooks }, null, 2) + "\n"
895
+ );
717
896
  }
718
- if (agent === "cursor") {
897
+ if (agents.includes("cursor")) {
719
898
  const cursorRules = buildCursorEngine2(config);
720
899
  const cursorPath = ".cursorrules";
721
900
  if (existsSync7(cursorPath)) {
722
- const content = readFileSync5(cursorPath, "utf8");
901
+ const content = readFileSync6(cursorPath, "utf8");
723
902
  const start = content.indexOf("# \u2500\u2500\u2500 Holdpoint Rules");
724
903
  const end = content.indexOf("# \u2500\u2500\u2500 End Holdpoint Rules \u2500\u2500\u2500");
725
904
  if (start !== -1 && end !== -1) {
726
905
  const afterEnd = content.indexOf("\n", end);
727
906
  const updated = content.slice(0, start) + cursorRules + content.slice(afterEnd === -1 ? end : afterEnd + 1);
728
- writeFileSync2(cursorPath, updated);
907
+ writeFileSync3(cursorPath, updated);
729
908
  } else {
730
- writeFileSync2(cursorPath, content + "\n" + cursorRules);
909
+ writeFileSync3(cursorPath, content + "\n" + cursorRules);
731
910
  }
732
911
  }
733
912
  }
913
+ if (agents.includes("codex")) {
914
+ mkdirSync3(".codex", { recursive: true });
915
+ writeFileSync3(".codex/hooks.json", buildCodexHooksJson2(config), "utf8");
916
+ writeFileSync3(".codex/holdpoint-check.mjs", buildCodexCheckScript2(), "utf8");
917
+ const agentsMdPath = "AGENTS.md";
918
+ const existing = existsSync7(agentsMdPath) ? readFileSync6(agentsMdPath, "utf8") : "";
919
+ writeFileSync3(agentsMdPath, spliceAgentsMd2(existing, config), "utf8");
920
+ }
734
921
  spinner.succeed(chalk4.green("Engine files updated from current checks.yaml"));
735
922
  }
736
923
 
737
924
  // src/commands/build.ts
738
925
  import { createServer } from "http";
739
926
  import { createReadStream, existsSync as existsSync8 } from "fs";
740
- import { join as join4, extname, dirname as dirname2 } from "path";
927
+ import { join as join5, extname, dirname as dirname2 } from "path";
741
928
  import { fileURLToPath as fileURLToPath2 } from "url";
742
929
  import { execSync as execSync4 } from "child_process";
743
930
  import chalk5 from "chalk";
@@ -763,7 +950,7 @@ function serveFile(res, filePath) {
763
950
  function handleRequest(req, res, uiDir) {
764
951
  const url = (req.url ?? "/").split("?")[0] ?? "/";
765
952
  if (url === "/__holdpoint/initial-yaml") {
766
- const checksPath = join4(process.cwd(), "checks.yaml");
953
+ const checksPath = join5(process.cwd(), "checks.yaml");
767
954
  if (existsSync8(checksPath)) {
768
955
  res.writeHead(200, { "Content-Type": "text/yaml; charset=utf-8" });
769
956
  createReadStream(checksPath).pipe(res);
@@ -773,13 +960,24 @@ function handleRequest(req, res, uiDir) {
773
960
  }
774
961
  return;
775
962
  }
776
- const candidate = join4(uiDir, url === "/" ? "index.html" : url);
777
- const filePath = existsSync8(candidate) ? candidate : join4(uiDir, "index.html");
963
+ if (url === "/__holdpoint/initial-reports") {
964
+ const reportsPath = join5(process.cwd(), ".holdpoint", "check-reports.json");
965
+ if (existsSync8(reportsPath)) {
966
+ res.writeHead(200, { "Content-Type": "application/json" });
967
+ createReadStream(reportsPath).pipe(res);
968
+ } else {
969
+ res.writeHead(404, { "Content-Type": "text/plain" });
970
+ res.end("No check reports found");
971
+ }
972
+ return;
973
+ }
974
+ const candidate = join5(uiDir, url === "/" ? "index.html" : url);
975
+ const filePath = existsSync8(candidate) ? candidate : join5(uiDir, "index.html");
778
976
  serveFile(res, filePath);
779
977
  }
780
978
  async function buildCommand() {
781
979
  const port = 4321;
782
- const uiDir = join4(__dirname2, "builder-ui");
980
+ const uiDir = join5(__dirname2, "builder-ui");
783
981
  if (!existsSync8(uiDir)) {
784
982
  console.error(chalk5.red("\u2717 Builder UI not found.\n"));
785
983
  console.log(chalk5.dim(" This is unexpected for a published build of @holdpoint/cli."));
@@ -810,7 +1008,7 @@ ${chalk5.green("\u2713")} Holdpoint builder running at ${chalk5.cyan(`http://loc
810
1008
  }
811
1009
 
812
1010
  // src/commands/evolve.ts
813
- import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
1011
+ import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
814
1012
  import { execSync as execSync5 } from "child_process";
815
1013
  import chalk6 from "chalk";
816
1014
  import ora4 from "ora";
@@ -843,7 +1041,7 @@ async function evolveCommand(options) {
843
1041
  const cwd = process.cwd();
844
1042
  const profile = scanProject(cwd);
845
1043
  const repoFiles = getRepoFiles(cwd);
846
- const yamlContent = readFileSync6("checks.yaml", "utf8");
1044
+ const yamlContent = readFileSync7("checks.yaml", "utf8");
847
1045
  let config;
848
1046
  try {
849
1047
  config = parseHoldpointYaml5(yamlContent);
@@ -957,7 +1155,7 @@ async function evolveCommand(options) {
957
1155
  };
958
1156
  const header = extractHeader(yamlContent);
959
1157
  const newYaml = withHeader(header, generateYaml(updatedConfig));
960
- writeFileSync3("checks.yaml", newYaml, "utf8");
1158
+ writeFileSync4("checks.yaml", newYaml, "utf8");
961
1159
  applySpinner.text = "Running holdpoint update\u2026";
962
1160
  try {
963
1161
  execSync5("npx @holdpoint/cli@alpha update", { stdio: "pipe" });
@@ -985,7 +1183,10 @@ function printAppliedSummary(added, wrapped) {
985
1183
  // src/index.ts
986
1184
  var program = new Command();
987
1185
  program.name("holdpoint").description("Universal eval-guard for AI coding agents (alpha)").version("0.1.0-alpha.2");
988
- program.command("init").description("Initialise Holdpoint in the current project").option("--stack <stack>", "Stack type: typescript | python | nextjs | fullstack").option("--agent <agent>", "Agent type: copilot | claude | cursor").action(initCommand);
1186
+ program.command("init").description("Initialise Holdpoint in the current project").option("--stack <stack>", "Stack type: typescript | python | nextjs | fullstack").option(
1187
+ "--agent <agent>",
1188
+ "Agent to install for: copilot | claude | cursor | codex (default: all four)"
1189
+ ).action(initCommand);
989
1190
  program.command("check").description("Run task checks from checks.yaml").option("--staged", "Only check against git-staged files").action(checkCommand);
990
1191
  program.command("validate").description("Validate checks.yaml schema and print any errors").action(validateCommand);
991
1192
  program.command("update").description("Regenerate engine files from current checks.yaml (preserves checks.yaml)").action(updateCommand);