@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.
- package/dist/builder-ui/assets/index-3J1uDBNl.css +1 -0
- package/dist/builder-ui/assets/index-BqVxTIWS.js +457 -0
- package/dist/builder-ui/assets/index-BqVxTIWS.js.map +1 -0
- package/dist/builder-ui/index.html +2 -2
- package/dist/index.js +258 -57
- package/dist/index.js.map +1 -1
- package/dist/templates/MASTER_PROMPT.md +10 -10
- package/dist/templates/_base.yaml +7 -2
- package/package.json +18 -17
- package/LICENSE +0 -21
- package/dist/builder-ui/assets/index-BxfWKnb5.js +0 -437
- package/dist/builder-ui/assets/index-BxfWKnb5.js.map +0 -1
- package/dist/builder-ui/assets/index-DkLHZ-in.css +0 -1
|
@@ -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-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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 {
|
|
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
|
|
20
|
-
|
|
21
|
-
if (existsSync(".
|
|
22
|
-
if (existsSync(".
|
|
23
|
-
|
|
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
|
|
88
|
-
|
|
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 =
|
|
113
|
+
yamlContent = readFileSync2(templatePath, "utf8");
|
|
94
114
|
}
|
|
95
115
|
writeFileSync("checks.yaml", yamlContent, "utf8");
|
|
96
116
|
} else {
|
|
97
|
-
yamlContent =
|
|
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 (
|
|
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 (
|
|
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(
|
|
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 (
|
|
149
|
+
if (agents.includes("cursor")) {
|
|
127
150
|
const cursorRules = buildCursorEngine(config);
|
|
128
151
|
const cursorPath = ".cursorrules";
|
|
129
152
|
if (existsSync2(cursorPath)) {
|
|
130
|
-
const existing =
|
|
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)}
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 {
|
|
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
|
|
694
|
-
const
|
|
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
|
-
|
|
697
|
-
|
|
698
|
-
if (
|
|
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
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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 (
|
|
706
|
-
|
|
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(
|
|
887
|
+
existing = JSON.parse(readFileSync6(settingsPath, "utf8"));
|
|
712
888
|
} catch {
|
|
713
889
|
}
|
|
714
890
|
}
|
|
715
891
|
const hooks = JSON.parse(buildClaudeEngineJson2(config));
|
|
716
|
-
|
|
892
|
+
writeFileSync3(
|
|
893
|
+
settingsPath,
|
|
894
|
+
JSON.stringify({ ...existing, hooks: hooks.hooks }, null, 2) + "\n"
|
|
895
|
+
);
|
|
717
896
|
}
|
|
718
|
-
if (
|
|
897
|
+
if (agents.includes("cursor")) {
|
|
719
898
|
const cursorRules = buildCursorEngine2(config);
|
|
720
899
|
const cursorPath = ".cursorrules";
|
|
721
900
|
if (existsSync7(cursorPath)) {
|
|
722
|
-
const content =
|
|
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
|
-
|
|
907
|
+
writeFileSync3(cursorPath, updated);
|
|
729
908
|
} else {
|
|
730
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
777
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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);
|