@holdpoint/cli 0.1.0-alpha.18 → 0.1.0-alpha.19
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/chunk-COPLLMYJ.js +496 -0
- package/dist/chunk-COPLLMYJ.js.map +1 -0
- package/dist/index.js +284 -720
- package/dist/index.js.map +1 -1
- package/dist/init-FNQ5GQBD.js +8 -0
- package/dist/init-FNQ5GQBD.js.map +1 -0
- package/dist/prompt-EQ5IFADN.js +23 -0
- package/dist/prompt-EQ5IFADN.js.map +1 -0
- package/dist/templates/default.yaml +14 -4
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,495 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
detectInstalledAgents,
|
|
4
|
+
ensureBundledFile,
|
|
5
|
+
initCommand,
|
|
6
|
+
mergeClaudeSettings,
|
|
7
|
+
mergeCursorHooks,
|
|
8
|
+
spliceBreadcrumb
|
|
9
|
+
} from "./chunk-COPLLMYJ.js";
|
|
2
10
|
|
|
3
11
|
// src/index.ts
|
|
4
12
|
import { Command } from "commander";
|
|
5
13
|
|
|
6
|
-
// src/commands/init.ts
|
|
7
|
-
import { execSync as execSync2 } from "child_process";
|
|
8
|
-
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
9
|
-
import { join as join2, dirname as dirname3 } from "path";
|
|
10
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11
|
-
import chalk2 from "chalk";
|
|
12
|
-
import ora from "ora";
|
|
13
|
-
import { buildConfigJson, buildEngine } from "@holdpoint/engine-copilot";
|
|
14
|
-
import { buildEngineJson as buildClaudeEngineJson } from "@holdpoint/engine-claude";
|
|
15
|
-
import {
|
|
16
|
-
buildCheckScript as buildCursorCheckScript,
|
|
17
|
-
buildEngine as buildCursorEngine,
|
|
18
|
-
buildHooksJson as buildCursorHooksJson
|
|
19
|
-
} from "@holdpoint/engine-cursor";
|
|
20
|
-
import {
|
|
21
|
-
buildConfigToml as buildCodexConfigToml,
|
|
22
|
-
buildHooksJson as buildCodexHooksJson,
|
|
23
|
-
buildCheckScript as buildCodexCheckScript
|
|
24
|
-
} from "@holdpoint/engine-codex";
|
|
25
|
-
import { parseHoldpointYaml } from "@holdpoint/yaml-core";
|
|
26
|
-
|
|
27
|
-
// src/detect.ts
|
|
28
|
-
import { existsSync, readFileSync } from "fs";
|
|
29
|
-
function detectPackageManager() {
|
|
30
|
-
if (existsSync("pnpm-lock.yaml")) return "pnpm";
|
|
31
|
-
if (existsSync("yarn.lock")) return "yarn";
|
|
32
|
-
return "npm";
|
|
33
|
-
}
|
|
34
|
-
function detectInstalledAgents() {
|
|
35
|
-
const agents = [];
|
|
36
|
-
if (existsSync(".github/extensions/holdpoint/extension.mjs")) agents.push("copilot");
|
|
37
|
-
if (existsSync(".claude/settings.json")) agents.push("claude");
|
|
38
|
-
if (existsSync(".cursor/hooks.json")) {
|
|
39
|
-
try {
|
|
40
|
-
if (readFileSync(".cursor/hooks.json", "utf8").includes("HOLDPOINT_MANAGED=cursor")) {
|
|
41
|
-
agents.push("cursor");
|
|
42
|
-
}
|
|
43
|
-
} catch {
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
if (existsSync(".cursorrules")) {
|
|
47
|
-
try {
|
|
48
|
-
if (!agents.includes("cursor") && readFileSync(".cursorrules", "utf8").includes("Holdpoint Rules")) {
|
|
49
|
-
agents.push("cursor");
|
|
50
|
-
}
|
|
51
|
-
} catch {
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (existsSync(".codex/holdpoint-check.mjs")) agents.push("codex");
|
|
55
|
-
return agents;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// src/templates.ts
|
|
59
|
-
import { copyFileSync, existsSync as existsSync2, writeFileSync } from "fs";
|
|
60
|
-
import { join, dirname } from "path";
|
|
61
|
-
import { fileURLToPath } from "url";
|
|
62
|
-
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
63
|
-
function getBundledTemplatePath(filename) {
|
|
64
|
-
const candidates = [
|
|
65
|
-
join(__dirname, "templates", filename),
|
|
66
|
-
// dist/templates/ (published package)
|
|
67
|
-
join(__dirname, "../../../templates", filename),
|
|
68
|
-
// monorepo dev fallback
|
|
69
|
-
join(process.cwd(), "templates", filename)
|
|
70
|
-
// cwd fallback
|
|
71
|
-
];
|
|
72
|
-
for (const candidate of candidates) {
|
|
73
|
-
if (existsSync2(candidate)) return candidate;
|
|
74
|
-
}
|
|
75
|
-
return "";
|
|
76
|
-
}
|
|
77
|
-
function ensureBundledFile(outputPath, templateFilename, fallbackContent) {
|
|
78
|
-
if (existsSync2(outputPath)) {
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
const templatePath = getBundledTemplatePath(templateFilename);
|
|
82
|
-
if (templatePath) {
|
|
83
|
-
copyFileSync(templatePath, outputPath);
|
|
84
|
-
} else {
|
|
85
|
-
writeFileSync(outputPath, fallbackContent, "utf8");
|
|
86
|
-
}
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// src/lib/preflight.ts
|
|
91
|
-
import { execSync } from "child_process";
|
|
92
|
-
import chalk from "chalk";
|
|
93
|
-
function silentExec(cmd) {
|
|
94
|
-
try {
|
|
95
|
-
const stdout = execSync(cmd, { stdio: ["ignore", "pipe", "ignore"] }).toString();
|
|
96
|
-
return { ok: true, stdout };
|
|
97
|
-
} catch {
|
|
98
|
-
return { ok: false, stdout: "" };
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
function checkCopilot() {
|
|
102
|
-
const gh = silentExec("gh --version");
|
|
103
|
-
if (!gh.ok) {
|
|
104
|
-
return {
|
|
105
|
-
agent: "copilot",
|
|
106
|
-
status: "action_required",
|
|
107
|
-
message: "GitHub CLI not found on PATH",
|
|
108
|
-
command: "brew install gh # or: https://cli.github.com/"
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
const copilot = silentExec("gh copilot --version");
|
|
112
|
-
if (!copilot.ok) {
|
|
113
|
-
return {
|
|
114
|
-
agent: "copilot",
|
|
115
|
-
status: "action_required",
|
|
116
|
-
message: "Copilot CLI extension not installed",
|
|
117
|
-
command: "gh extension install github/gh-copilot"
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
return {
|
|
121
|
-
agent: "copilot",
|
|
122
|
-
status: "action_required",
|
|
123
|
-
message: "Copilot CLI detected \u2014 experimental mode required for EXTENSIONS",
|
|
124
|
-
command: "Inside Copilot CLI, run: /experimental on"
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
function checkClaude() {
|
|
128
|
-
const claude = silentExec("claude --version");
|
|
129
|
-
if (!claude.ok) {
|
|
130
|
-
return {
|
|
131
|
-
agent: "claude",
|
|
132
|
-
status: "unknown",
|
|
133
|
-
message: "Claude Code binary not on PATH (hooks still written for when it is)"
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
return {
|
|
137
|
-
agent: "claude",
|
|
138
|
-
status: "ok",
|
|
139
|
-
message: "Claude Code detected \u2014 hooks installed at .claude/settings.json"
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
function checkCursor() {
|
|
143
|
-
return {
|
|
144
|
-
agent: "cursor",
|
|
145
|
-
status: "ok",
|
|
146
|
-
message: "Cursor \u2014 .cursor/hooks.json gate + .cursor/rules breadcrumb installed",
|
|
147
|
-
docs: "https://holdpoint.dev/docs#cursor"
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
function checkCodex() {
|
|
151
|
-
const codex = silentExec("codex --version");
|
|
152
|
-
if (!codex.ok) {
|
|
153
|
-
return {
|
|
154
|
-
agent: "codex",
|
|
155
|
-
status: "action_required",
|
|
156
|
-
message: "Codex CLI not found on PATH",
|
|
157
|
-
command: "Install Codex: https://github.com/openai/codex"
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
return {
|
|
161
|
-
agent: "codex",
|
|
162
|
-
status: "action_required",
|
|
163
|
-
message: "Codex detected \u2014 project-level hooks require trust approval",
|
|
164
|
-
command: "In the Codex TUI: codex trust (or /hooks to review)"
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
var CHECKS = {
|
|
168
|
-
copilot: checkCopilot,
|
|
169
|
-
claude: checkClaude,
|
|
170
|
-
cursor: checkCursor,
|
|
171
|
-
codex: checkCodex
|
|
172
|
-
};
|
|
173
|
-
function runPreflight(agents) {
|
|
174
|
-
return agents.flatMap((agent) => {
|
|
175
|
-
const check = CHECKS[agent];
|
|
176
|
-
return check ? [check()] : [];
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
function printPreflight(results) {
|
|
180
|
-
if (results.length === 0) return;
|
|
181
|
-
const ok = results.filter((r) => r.status === "ok");
|
|
182
|
-
const unknown = results.filter((r) => r.status === "unknown");
|
|
183
|
-
const action = results.filter((r) => r.status === "action_required");
|
|
184
|
-
console.log("");
|
|
185
|
-
console.log(chalk.bold("Agent preflight:"));
|
|
186
|
-
for (const r of ok) {
|
|
187
|
-
console.log(` ${chalk.green("\u2713")} ${r.agent.padEnd(7)} ${chalk.dim(r.message)}`);
|
|
188
|
-
}
|
|
189
|
-
for (const r of unknown) {
|
|
190
|
-
console.log(` ${chalk.dim("?")} ${r.agent.padEnd(7)} ${chalk.dim(r.message)}`);
|
|
191
|
-
}
|
|
192
|
-
for (const r of action) {
|
|
193
|
-
console.log(` ${chalk.yellow("\u2192")} ${chalk.bold(r.agent.padEnd(7))} ${r.message}`);
|
|
194
|
-
if (r.command) console.log(` ${chalk.cyan(r.command)}`);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// src/claude-settings.ts
|
|
199
|
-
var HOLDPOINT_CLAUDE_HOOK_MARKER = "HOLDPOINT_MANAGED=claude";
|
|
200
|
-
function isObject(value) {
|
|
201
|
-
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
202
|
-
}
|
|
203
|
-
function asHookArray(value) {
|
|
204
|
-
return Array.isArray(value) ? value : [];
|
|
205
|
-
}
|
|
206
|
-
function isManagedHookCommand(value) {
|
|
207
|
-
return isObject(value) && typeof value.command === "string" && value.command.includes(HOLDPOINT_CLAUDE_HOOK_MARKER);
|
|
208
|
-
}
|
|
209
|
-
function isLegacyManagedHookCommand(value) {
|
|
210
|
-
if (!isObject(value) || typeof value.command !== "string") return false;
|
|
211
|
-
return value.command === "node_modules/.bin/holdpoint event --engine claude --from-hook || true" || value.command === "node_modules/.bin/holdpoint check --staged";
|
|
212
|
-
}
|
|
213
|
-
function isManagedHookEntry(value) {
|
|
214
|
-
if (!isObject(value)) return false;
|
|
215
|
-
const hooks = asHookArray(value.hooks);
|
|
216
|
-
return hooks.length > 0 && (hooks.every(isManagedHookCommand) || hooks.every(isLegacyManagedHookCommand));
|
|
217
|
-
}
|
|
218
|
-
function mergeClaudeSettings(existing, generated) {
|
|
219
|
-
const existingHooks = isObject(existing.hooks) ? existing.hooks : {};
|
|
220
|
-
const generatedHooks = isObject(generated.hooks) ? generated.hooks : {};
|
|
221
|
-
const mergedHooks = {};
|
|
222
|
-
for (const eventName of /* @__PURE__ */ new Set([
|
|
223
|
-
...Object.keys(existingHooks),
|
|
224
|
-
...Object.keys(generatedHooks)
|
|
225
|
-
])) {
|
|
226
|
-
const preserved = asHookArray(existingHooks[eventName]).filter(
|
|
227
|
-
(entry) => !isManagedHookEntry(entry)
|
|
228
|
-
);
|
|
229
|
-
const next = asHookArray(generatedHooks[eventName]);
|
|
230
|
-
if (preserved.length > 0 || next.length > 0) {
|
|
231
|
-
mergedHooks[eventName] = [...preserved, ...next];
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
return { ...existing, ...generated, hooks: mergedHooks };
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// src/cursor-hooks.ts
|
|
238
|
-
var HOLDPOINT_CURSOR_HOOK_MARKER = "HOLDPOINT_MANAGED=cursor";
|
|
239
|
-
function isObject2(value) {
|
|
240
|
-
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
241
|
-
}
|
|
242
|
-
function asHookArray2(value) {
|
|
243
|
-
return Array.isArray(value) ? value : [];
|
|
244
|
-
}
|
|
245
|
-
function isManagedCursorHook(value) {
|
|
246
|
-
return isObject2(value) && typeof value.command === "string" && (value.command.includes(HOLDPOINT_CURSOR_HOOK_MARKER) || value.command.includes(".cursor/holdpoint-hook.mjs"));
|
|
247
|
-
}
|
|
248
|
-
function mergeCursorHooks(existing, generated) {
|
|
249
|
-
const existingHooks = isObject2(existing.hooks) ? existing.hooks : {};
|
|
250
|
-
const generatedHooks = isObject2(generated.hooks) ? generated.hooks : {};
|
|
251
|
-
const mergedHooks = {};
|
|
252
|
-
for (const eventName of /* @__PURE__ */ new Set([
|
|
253
|
-
...Object.keys(existingHooks),
|
|
254
|
-
...Object.keys(generatedHooks)
|
|
255
|
-
])) {
|
|
256
|
-
const preserved = asHookArray2(existingHooks[eventName]).filter(
|
|
257
|
-
(entry) => !isManagedCursorHook(entry)
|
|
258
|
-
);
|
|
259
|
-
const next = asHookArray2(generatedHooks[eventName]);
|
|
260
|
-
if (preserved.length > 0 || next.length > 0) {
|
|
261
|
-
mergedHooks[eventName] = [...preserved, ...next];
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
return { ...existing, ...generated, hooks: mergedHooks };
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// src/lib/instructions-breadcrumb.ts
|
|
268
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync } from "fs";
|
|
269
|
-
import { dirname as dirname2 } from "path";
|
|
270
|
-
var START_MARKER = "<!-- HOLDPOINT_MANAGED \u2014 content between these markers is auto-generated by holdpoint init / holdpoint update -->";
|
|
271
|
-
var END_MARKER = "<!-- /HOLDPOINT_MANAGED -->";
|
|
272
|
-
var BREADCRUMB_BODY = `## Holdpoint workflow
|
|
273
|
-
|
|
274
|
-
This repo uses [Holdpoint](https://holdpoint.dev) to gate task completion on deterministic checks.
|
|
275
|
-
|
|
276
|
-
Before marking any task done:
|
|
277
|
-
|
|
278
|
-
1. Run \`holdpoint check\` (or it will run automatically via Stop / TaskCompleted hooks).
|
|
279
|
-
2. Fix any failures. Re-run until exit 0.
|
|
280
|
-
3. Never bypass with \`--no-verify\` or by skipping the agent's stop hook.
|
|
281
|
-
|
|
282
|
-
Full workflow reference: [\`MASTER_PROMPT.md\`](./MASTER_PROMPT.md) (always injected at session start).
|
|
283
|
-
Deep reference: [\`HOLDPOINT_REFERENCE.md\`](./HOLDPOINT_REFERENCE.md) (read on demand).
|
|
284
|
-
Active checks: [\`checks.yaml\`](./checks.yaml).`;
|
|
285
|
-
function spliceBreadcrumb(filePath, body = BREADCRUMB_BODY, createIfMissing = true) {
|
|
286
|
-
const block = `${START_MARKER}
|
|
287
|
-
|
|
288
|
-
${body}
|
|
289
|
-
|
|
290
|
-
${END_MARKER}`;
|
|
291
|
-
if (!existsSync3(filePath)) {
|
|
292
|
-
if (!createIfMissing) return;
|
|
293
|
-
mkdirSync(dirname2(filePath), { recursive: true });
|
|
294
|
-
writeFileSync2(filePath, block + "\n", "utf8");
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
const existing = readFileSync2(filePath, "utf8");
|
|
298
|
-
const startIdx = existing.indexOf(START_MARKER);
|
|
299
|
-
const endIdx = existing.indexOf(END_MARKER);
|
|
300
|
-
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
301
|
-
const before = existing.slice(0, startIdx);
|
|
302
|
-
const after = existing.slice(endIdx + END_MARKER.length);
|
|
303
|
-
writeFileSync2(filePath, before + block + after, "utf8");
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
const sep2 = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
307
|
-
writeFileSync2(filePath, existing + sep2 + block + "\n", "utf8");
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// src/commands/init.ts
|
|
311
|
-
var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
|
|
312
|
-
function getDefaultTemplatePath() {
|
|
313
|
-
const candidates = [
|
|
314
|
-
join2(__dirname2, "templates", "default.yaml"),
|
|
315
|
-
// dist/templates/ (published package)
|
|
316
|
-
join2(__dirname2, "../../../templates", "default.yaml"),
|
|
317
|
-
// monorepo dev fallback
|
|
318
|
-
join2(process.cwd(), "templates", "default.yaml")
|
|
319
|
-
// cwd fallback
|
|
320
|
-
];
|
|
321
|
-
for (const p of candidates) {
|
|
322
|
-
if (existsSync4(p)) return p;
|
|
323
|
-
}
|
|
324
|
-
return "";
|
|
325
|
-
}
|
|
326
|
-
var MINIMAL_CHECKS_YAML = `version: 1
|
|
327
|
-
context:
|
|
328
|
-
guides: {}
|
|
329
|
-
conditions: []
|
|
330
|
-
checks:
|
|
331
|
-
- id: lint
|
|
332
|
-
label: "Lint codebase"
|
|
333
|
-
cmd: "echo 'Add your lint command here'"
|
|
334
|
-
|
|
335
|
-
- id: jsdoc
|
|
336
|
-
label: "JSDoc on changed public functions"
|
|
337
|
-
prompt: "Ensure all changed public functions and exports have JSDoc comments."
|
|
338
|
-
`;
|
|
339
|
-
var MINIMAL_MASTER_PROMPT = `# Holdpoint
|
|
340
|
-
|
|
341
|
-
Run \`holdpoint check\` before marking any task complete.
|
|
342
|
-
See \`checks.yaml\` for the full list of checks.
|
|
343
|
-
`;
|
|
344
|
-
var MINIMAL_HOLDPOINT_REFERENCE = `# Holdpoint reference
|
|
345
|
-
|
|
346
|
-
Read \`MASTER_PROMPT.md\` first for the mandatory workflow, then use this file for deeper project-specific Holdpoint notes.
|
|
347
|
-
`;
|
|
348
|
-
var MINIMAL_PREREQUISITES = `# Holdpoint prerequisites
|
|
349
|
-
|
|
350
|
-
Holdpoint installed repo-local engine integrations for one or more AI coding agents. Before relying on them locally, review these setup notes:
|
|
351
|
-
|
|
352
|
-
- **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.
|
|
353
|
-
- **Cursor** \u2014 project-level hooks run in trusted workspaces. After opening the repo in Cursor, confirm the workspace is trusted and review Settings \u2192 Hooks if hooks do not fire.
|
|
354
|
-
- **OpenAI Codex** \u2014 project-level hooks require trust approval. Run \`codex trust\` in the Codex TUI or review the hook with \`/hooks\`.
|
|
355
|
-
- **General** \u2014 Holdpoint expects Node.js 18+ and a git repository so \`holdpoint init\`, \`holdpoint update\`, and \`holdpoint check\` can run normally.
|
|
356
|
-
|
|
357
|
-
Docs: https://holdpoint.dev/docs
|
|
358
|
-
`;
|
|
359
|
-
async function initCommand(options) {
|
|
360
|
-
const spinner = ora("Initialising Holdpoint\u2026").start();
|
|
361
|
-
const agentOpt = options.agent;
|
|
362
|
-
const agents = !agentOpt || agentOpt === "all" ? ["copilot", "claude", "cursor", "codex"] : [agentOpt];
|
|
363
|
-
spinner.text = `Installing for: ${chalk2.cyan(agents.join(", "))}`;
|
|
364
|
-
const pm = detectPackageManager();
|
|
365
|
-
let yamlContent = MINIMAL_CHECKS_YAML;
|
|
366
|
-
if (!existsSync4("checks.yaml")) {
|
|
367
|
-
const templatePath = getDefaultTemplatePath();
|
|
368
|
-
if (templatePath) {
|
|
369
|
-
yamlContent = readFileSync3(templatePath, "utf8");
|
|
370
|
-
}
|
|
371
|
-
if (pm !== "pnpm") {
|
|
372
|
-
yamlContent = yamlContent.replace(/\bpnpm\b/g, pm);
|
|
373
|
-
}
|
|
374
|
-
writeFileSync3("checks.yaml", yamlContent, "utf8");
|
|
375
|
-
} else {
|
|
376
|
-
yamlContent = readFileSync3("checks.yaml", "utf8");
|
|
377
|
-
}
|
|
378
|
-
const config = parseHoldpointYaml(yamlContent);
|
|
379
|
-
const generatedDir = ".github/holdpoint/generated";
|
|
380
|
-
mkdirSync2(generatedDir, { recursive: true });
|
|
381
|
-
writeFileSync3(`${generatedDir}/checks.immutable.json`, buildConfigJson(config), "utf8");
|
|
382
|
-
if (agents.includes("copilot")) {
|
|
383
|
-
const extDir = ".github/extensions/holdpoint";
|
|
384
|
-
mkdirSync2(extDir, { recursive: true });
|
|
385
|
-
writeFileSync3(join2(extDir, "extension.mjs"), buildEngine(config), "utf8");
|
|
386
|
-
spliceBreadcrumb(".github/copilot-instructions.md");
|
|
387
|
-
}
|
|
388
|
-
if (agents.includes("claude")) {
|
|
389
|
-
mkdirSync2(".claude", { recursive: true });
|
|
390
|
-
const settingsPath = ".claude/settings.json";
|
|
391
|
-
let existing = {};
|
|
392
|
-
if (existsSync4(settingsPath)) {
|
|
393
|
-
try {
|
|
394
|
-
existing = JSON.parse(readFileSync3(settingsPath, "utf8"));
|
|
395
|
-
} catch {
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
const holdpointHooks = JSON.parse(buildClaudeEngineJson(config));
|
|
399
|
-
writeFileSync3(
|
|
400
|
-
settingsPath,
|
|
401
|
-
JSON.stringify(mergeClaudeSettings(existing, holdpointHooks), null, 2),
|
|
402
|
-
"utf8"
|
|
403
|
-
);
|
|
404
|
-
spliceBreadcrumb("CLAUDE.md");
|
|
405
|
-
}
|
|
406
|
-
if (agents.includes("cursor")) {
|
|
407
|
-
mkdirSync2(".cursor", { recursive: true });
|
|
408
|
-
const cursorHooksPath = ".cursor/hooks.json";
|
|
409
|
-
let existingHooks = {};
|
|
410
|
-
if (existsSync4(cursorHooksPath)) {
|
|
411
|
-
try {
|
|
412
|
-
existingHooks = JSON.parse(readFileSync3(cursorHooksPath, "utf8"));
|
|
413
|
-
} catch {
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
const cursorHooks = JSON.parse(buildCursorHooksJson(config));
|
|
417
|
-
writeFileSync3(
|
|
418
|
-
cursorHooksPath,
|
|
419
|
-
JSON.stringify(mergeCursorHooks(existingHooks, cursorHooks), null, 2) + "\n",
|
|
420
|
-
"utf8"
|
|
421
|
-
);
|
|
422
|
-
writeFileSync3(".cursor/holdpoint-hook.mjs", buildCursorCheckScript(), "utf8");
|
|
423
|
-
const cursorRules = buildCursorEngine(config);
|
|
424
|
-
const cursorPath = ".cursorrules";
|
|
425
|
-
if (existsSync4(cursorPath)) {
|
|
426
|
-
const existing = readFileSync3(cursorPath, "utf8");
|
|
427
|
-
if (!existing.includes("Holdpoint Rules")) {
|
|
428
|
-
writeFileSync3(cursorPath, `${existing.trimEnd()}
|
|
429
|
-
|
|
430
|
-
${cursorRules}`, "utf8");
|
|
431
|
-
}
|
|
432
|
-
} else {
|
|
433
|
-
writeFileSync3(cursorPath, cursorRules, "utf8");
|
|
434
|
-
}
|
|
435
|
-
spliceBreadcrumb(".cursor/rules/holdpoint.md");
|
|
436
|
-
}
|
|
437
|
-
if (agents.includes("codex")) {
|
|
438
|
-
mkdirSync2(".codex", { recursive: true });
|
|
439
|
-
writeFileSync3(".codex/hooks.json", buildCodexHooksJson(config), "utf8");
|
|
440
|
-
writeFileSync3(".codex/holdpoint-check.mjs", buildCodexCheckScript(config), "utf8");
|
|
441
|
-
writeFileSync3(".codex/config.toml", buildCodexConfigToml(), "utf8");
|
|
442
|
-
spliceBreadcrumb("AGENTS.md");
|
|
443
|
-
}
|
|
444
|
-
ensureBundledFile("MASTER_PROMPT.md", "MASTER_PROMPT.md", MINIMAL_MASTER_PROMPT);
|
|
445
|
-
ensureBundledFile(
|
|
446
|
-
"HOLDPOINT_REFERENCE.md",
|
|
447
|
-
"HOLDPOINT_REFERENCE.md",
|
|
448
|
-
MINIMAL_HOLDPOINT_REFERENCE
|
|
449
|
-
);
|
|
450
|
-
ensureBundledFile(
|
|
451
|
-
"HOLDPOINT_PREREQUISITES.md",
|
|
452
|
-
"HOLDPOINT_PREREQUISITES.md",
|
|
453
|
-
MINIMAL_PREREQUISITES
|
|
454
|
-
);
|
|
455
|
-
spinner.text = "Installing holdpoint as a devDependency\u2026";
|
|
456
|
-
const installCmds = {
|
|
457
|
-
pnpm: "pnpm add -D holdpoint",
|
|
458
|
-
yarn: "yarn add --dev holdpoint",
|
|
459
|
-
npm: "npm install --save-dev holdpoint"
|
|
460
|
-
};
|
|
461
|
-
const installCmd = installCmds[pm];
|
|
462
|
-
try {
|
|
463
|
-
execSync2(installCmd, { stdio: "pipe" });
|
|
464
|
-
spinner.succeed(chalk2.bold.green("Holdpoint initialised!"));
|
|
465
|
-
} catch {
|
|
466
|
-
spinner.warn(
|
|
467
|
-
chalk2.yellow(`Holdpoint initialised, but could not install the package automatically.`) + `
|
|
468
|
-
Run manually: ${chalk2.yellow(installCmd)}`
|
|
469
|
-
);
|
|
470
|
-
}
|
|
471
|
-
const preflight = runPreflight(agents);
|
|
472
|
-
printPreflight(preflight);
|
|
473
|
-
console.log(`
|
|
474
|
-
${chalk2.cyan("Next steps:")}
|
|
475
|
-
1. Edit ${chalk2.yellow("checks.yaml")} to customise your eval checkpoints
|
|
476
|
-
2. Address any ${chalk2.yellow("\u2192")} items above (full notes in ${chalk2.yellow("HOLDPOINT_PREREQUISITES.md")})
|
|
477
|
-
3. Commit ${chalk2.yellow("checks.yaml")}, ${chalk2.yellow("HOLDPOINT_PREREQUISITES.md")}, and the generated engine files
|
|
478
|
-
4. Run ${chalk2.yellow("holdpoint check")} at any time to validate
|
|
479
|
-
|
|
480
|
-
Visual builder: ${chalk2.yellow("holdpoint builder")} (opens the daemon at /builder)
|
|
481
|
-
Agents: ${chalk2.cyan(agents.join(", "))}
|
|
482
|
-
`);
|
|
483
|
-
}
|
|
484
|
-
|
|
485
14
|
// src/commands/check.ts
|
|
486
|
-
import { existsSync
|
|
487
|
-
import { join
|
|
488
|
-
import
|
|
489
|
-
import
|
|
490
|
-
import { parseHoldpointYaml
|
|
15
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
16
|
+
import { join } from "path";
|
|
17
|
+
import chalk from "chalk";
|
|
18
|
+
import ora from "ora";
|
|
19
|
+
import { parseHoldpointYaml, matchesWhen } from "@holdpoint/yaml-core";
|
|
491
20
|
import { runDeterministicChecks } from "@holdpoint/yaml-core/runner";
|
|
492
|
-
import { execSync
|
|
21
|
+
import { execSync } from "child_process";
|
|
493
22
|
import { randomUUID } from "crypto";
|
|
494
23
|
import { identifyProject } from "@holdpoint/live-daemon";
|
|
495
24
|
import { BridgeClient } from "@holdpoint/sdk";
|
|
@@ -499,7 +28,7 @@ var CHECK_REPORTS_PATH = ".holdpoint/check-reports.json";
|
|
|
499
28
|
var CHECK_REPORTS_MAX = 50;
|
|
500
29
|
function getStagedFiles() {
|
|
501
30
|
try {
|
|
502
|
-
const out =
|
|
31
|
+
const out = execSync("git diff --cached --name-only", {
|
|
503
32
|
encoding: "utf8",
|
|
504
33
|
stdio: ["pipe", "pipe", "ignore"]
|
|
505
34
|
});
|
|
@@ -510,7 +39,7 @@ function getStagedFiles() {
|
|
|
510
39
|
}
|
|
511
40
|
function getAllChangedFiles() {
|
|
512
41
|
try {
|
|
513
|
-
const out =
|
|
42
|
+
const out = execSync("git diff --name-only HEAD", {
|
|
514
43
|
encoding: "utf8",
|
|
515
44
|
stdio: ["pipe", "pipe", "ignore"]
|
|
516
45
|
});
|
|
@@ -521,7 +50,7 @@ function getAllChangedFiles() {
|
|
|
521
50
|
}
|
|
522
51
|
function getLastCommitFiles() {
|
|
523
52
|
try {
|
|
524
|
-
const out =
|
|
53
|
+
const out = execSync("git diff --name-only HEAD~1 HEAD", {
|
|
525
54
|
encoding: "utf8",
|
|
526
55
|
stdio: ["pipe", "pipe", "ignore"]
|
|
527
56
|
});
|
|
@@ -532,7 +61,7 @@ function getLastCommitFiles() {
|
|
|
532
61
|
}
|
|
533
62
|
function getHeadSha() {
|
|
534
63
|
try {
|
|
535
|
-
return
|
|
64
|
+
return execSync("git rev-parse HEAD", {
|
|
536
65
|
encoding: "utf8",
|
|
537
66
|
stdio: ["pipe", "pipe", "ignore"]
|
|
538
67
|
}).trim();
|
|
@@ -542,7 +71,7 @@ function getHeadSha() {
|
|
|
542
71
|
}
|
|
543
72
|
function readCommitCache() {
|
|
544
73
|
try {
|
|
545
|
-
const raw =
|
|
74
|
+
const raw = readFileSync(COMMIT_CACHE_PATH, "utf8");
|
|
546
75
|
const parsed = JSON.parse(raw);
|
|
547
76
|
return new Set(Array.isArray(parsed.verified) ? parsed.verified : []);
|
|
548
77
|
} catch {
|
|
@@ -553,18 +82,18 @@ function recordCommitCache(sha) {
|
|
|
553
82
|
try {
|
|
554
83
|
const existing = readCommitCache();
|
|
555
84
|
const updated = [sha, ...[...existing].filter((s) => s !== sha)].slice(0, COMMIT_CACHE_MAX);
|
|
556
|
-
|
|
557
|
-
|
|
85
|
+
mkdirSync(join(COMMIT_CACHE_PATH, ".."), { recursive: true });
|
|
86
|
+
writeFileSync(COMMIT_CACHE_PATH, JSON.stringify({ verified: updated }, null, 2) + "\n", "utf8");
|
|
558
87
|
} catch {
|
|
559
88
|
}
|
|
560
89
|
}
|
|
561
90
|
function recordCheckReport(run) {
|
|
562
91
|
try {
|
|
563
|
-
|
|
92
|
+
mkdirSync(join(CHECK_REPORTS_PATH, ".."), { recursive: true });
|
|
564
93
|
let existing = { runs: [] };
|
|
565
|
-
if (
|
|
94
|
+
if (existsSync(CHECK_REPORTS_PATH)) {
|
|
566
95
|
try {
|
|
567
|
-
existing = JSON.parse(
|
|
96
|
+
existing = JSON.parse(readFileSync(CHECK_REPORTS_PATH, "utf8"));
|
|
568
97
|
if (!Array.isArray(existing.runs)) existing.runs = [];
|
|
569
98
|
} catch {
|
|
570
99
|
existing = { runs: [] };
|
|
@@ -573,21 +102,38 @@ function recordCheckReport(run) {
|
|
|
573
102
|
const updated = {
|
|
574
103
|
runs: [run, ...existing.runs].slice(0, CHECK_REPORTS_MAX)
|
|
575
104
|
};
|
|
576
|
-
|
|
105
|
+
writeFileSync(CHECK_REPORTS_PATH, JSON.stringify(updated, null, 2) + "\n", "utf8");
|
|
577
106
|
} catch {
|
|
578
107
|
}
|
|
579
108
|
}
|
|
580
109
|
async function checkCommand(options) {
|
|
581
|
-
if (!
|
|
582
|
-
|
|
583
|
-
|
|
110
|
+
if (!existsSync("checks.yaml")) {
|
|
111
|
+
if (options.staged || !process.stdout.isTTY || !process.stdin.isTTY) {
|
|
112
|
+
console.error(chalk.red("No checks.yaml found. Run `holdpoint init` first."));
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
const { promptYesNo } = await import("./prompt-EQ5IFADN.js");
|
|
116
|
+
console.log(
|
|
117
|
+
chalk.yellow("No checks.yaml in this directory.") + chalk.dim(" (") + process.cwd() + chalk.dim(")")
|
|
118
|
+
);
|
|
119
|
+
const shouldInit = await promptYesNo(chalk.bold("Initialise Holdpoint here?"), true);
|
|
120
|
+
if (!shouldInit) {
|
|
121
|
+
console.error(chalk.dim("Skipped. Run `holdpoint init` when you're ready."));
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
const { initCommand: initCommand2 } = await import("./init-FNQ5GQBD.js");
|
|
125
|
+
await initCommand2({});
|
|
126
|
+
console.log(
|
|
127
|
+
chalk.dim("\nReview ") + chalk.yellow("checks.yaml") + chalk.dim(" and run ") + chalk.yellow("holdpoint check") + chalk.dim(" again when you're ready.")
|
|
128
|
+
);
|
|
129
|
+
return;
|
|
584
130
|
}
|
|
585
|
-
const yamlContent =
|
|
131
|
+
const yamlContent = readFileSync("checks.yaml", "utf8");
|
|
586
132
|
let config;
|
|
587
133
|
try {
|
|
588
|
-
config =
|
|
134
|
+
config = parseHoldpointYaml(yamlContent);
|
|
589
135
|
} catch (err) {
|
|
590
|
-
console.error(
|
|
136
|
+
console.error(chalk.red("Invalid checks.yaml:"), err.message);
|
|
591
137
|
process.exit(1);
|
|
592
138
|
}
|
|
593
139
|
const headSha = getHeadSha();
|
|
@@ -600,7 +146,7 @@ async function checkCommand(options) {
|
|
|
600
146
|
} else {
|
|
601
147
|
if (headSha && readCommitCache().has(headSha)) {
|
|
602
148
|
console.log(
|
|
603
|
-
|
|
149
|
+
chalk.green(`\u2713 Commit ${headSha.slice(0, 8)} already verified \u2014 nothing to re-check.`)
|
|
604
150
|
);
|
|
605
151
|
process.exit(0);
|
|
606
152
|
}
|
|
@@ -609,10 +155,10 @@ async function checkCommand(options) {
|
|
|
609
155
|
changedFiles = lastCommit;
|
|
610
156
|
usedHeadShaForCache = true;
|
|
611
157
|
console.log(
|
|
612
|
-
|
|
158
|
+
chalk.yellow("No staged files. Running checks scoped to the most recent commit's files.")
|
|
613
159
|
);
|
|
614
160
|
} else {
|
|
615
|
-
console.log(
|
|
161
|
+
console.log(chalk.green("\u2713 No staged changes and no recent commit \u2014 nothing to check."));
|
|
616
162
|
process.exit(0);
|
|
617
163
|
}
|
|
618
164
|
}
|
|
@@ -620,10 +166,10 @@ async function checkCommand(options) {
|
|
|
620
166
|
changedFiles = getAllChangedFiles();
|
|
621
167
|
if (changedFiles.length === 0) {
|
|
622
168
|
console.log(
|
|
623
|
-
|
|
169
|
+
chalk.yellow("No changed files detected. Running all checks with no file filter.")
|
|
624
170
|
);
|
|
625
171
|
console.log(
|
|
626
|
-
|
|
172
|
+
chalk.dim(
|
|
627
173
|
" Tip: if you just ran `holdpoint init`, commit the generated files to clear the git-commit check."
|
|
628
174
|
)
|
|
629
175
|
);
|
|
@@ -631,14 +177,14 @@ async function checkCommand(options) {
|
|
|
631
177
|
}
|
|
632
178
|
const guides = Object.entries(config.context?.guides ?? {});
|
|
633
179
|
if (guides.length > 0) {
|
|
634
|
-
console.log(
|
|
180
|
+
console.log(chalk.cyan("\nProject guides:"));
|
|
635
181
|
for (const [key, text] of guides) {
|
|
636
|
-
console.log(
|
|
182
|
+
console.log(chalk.bold(` ${key}:`), chalk.dim(String(text).trim()));
|
|
637
183
|
}
|
|
638
184
|
console.log("");
|
|
639
185
|
}
|
|
640
186
|
const taskCount = config.checks.filter((c) => c.cmd !== void 0).length;
|
|
641
|
-
const spinner =
|
|
187
|
+
const spinner = ora(`Running ${taskCount} task(s)\u2026`).start();
|
|
642
188
|
const effectiveFiles = changedFiles.length > 0 ? changedFiles : ["__all__"];
|
|
643
189
|
const results = runDeterministicChecks(config, effectiveFiles);
|
|
644
190
|
const passed = results.filter((r) => r.status === "pass");
|
|
@@ -651,19 +197,29 @@ async function checkCommand(options) {
|
|
|
651
197
|
console.log("");
|
|
652
198
|
console.log(
|
|
653
199
|
[
|
|
654
|
-
|
|
655
|
-
failed.length > 0 ?
|
|
656
|
-
skipped.length > 0 ?
|
|
200
|
+
chalk.green(`\u2713 ${passed.length} passed`),
|
|
201
|
+
failed.length > 0 ? chalk.red(`\u2717 ${failed.length} failed`) : "",
|
|
202
|
+
skipped.length > 0 ? chalk.gray(`\u25CC ${skipped.length} skipped`) : ""
|
|
657
203
|
].filter(Boolean).join(" ")
|
|
658
204
|
);
|
|
659
|
-
const promptChecks = config.checks.filter(
|
|
660
|
-
(c) => c.prompt !== void 0 && matchesWhen(c.when, changedFiles
|
|
661
|
-
);
|
|
205
|
+
const promptChecks = changedFiles.length > 0 ? config.checks.filter(
|
|
206
|
+
(c) => c.prompt !== void 0 && matchesWhen(c.when, changedFiles, config.patterns)
|
|
207
|
+
) : [];
|
|
662
208
|
if (promptChecks.length > 0) {
|
|
663
209
|
console.log(`
|
|
664
|
-
${
|
|
210
|
+
${chalk.cyan("Agent prompts to act on:")}`);
|
|
665
211
|
for (const c of promptChecks) {
|
|
666
|
-
console.log(` ${
|
|
212
|
+
console.log(` ${chalk.yellow("\u25A1")} [${c.label}] ${c.prompt ?? ""}`);
|
|
213
|
+
}
|
|
214
|
+
} else if (changedFiles.length === 0) {
|
|
215
|
+
const totalPromptChecks = config.checks.filter((c) => c.prompt !== void 0).length;
|
|
216
|
+
if (totalPromptChecks > 0) {
|
|
217
|
+
console.log(
|
|
218
|
+
chalk.dim(
|
|
219
|
+
`
|
|
220
|
+
(${totalPromptChecks} prompt-style checks defined; they fire relative to changed files \u2014 none surfaced with no diff context)`
|
|
221
|
+
)
|
|
222
|
+
);
|
|
667
223
|
}
|
|
668
224
|
}
|
|
669
225
|
const reportResults = [
|
|
@@ -729,70 +285,70 @@ ${chalk3.cyan("Agent prompts to act on:")}`);
|
|
|
729
285
|
}
|
|
730
286
|
}
|
|
731
287
|
function printResult(result) {
|
|
732
|
-
const icon = result.status === "pass" ?
|
|
288
|
+
const icon = result.status === "pass" ? chalk.green("\u2713") : result.status === "fail" ? chalk.red("\u2717") : result.status === "skip" ? chalk.gray("\u25CC") : chalk.yellow("\u2026");
|
|
733
289
|
const label = result.check.label;
|
|
734
290
|
console.log(`${icon} ${label}`);
|
|
735
291
|
if (result.status === "fail" && result.output) {
|
|
736
292
|
const trimmed = result.output.trim().split("\n").slice(0, 10).join("\n");
|
|
737
|
-
console.log(
|
|
293
|
+
console.log(chalk.dim(trimmed.replace(/^/gm, " ")));
|
|
738
294
|
}
|
|
739
295
|
if (result.status === "skip" && result.skipReason) {
|
|
740
|
-
console.log(
|
|
296
|
+
console.log(chalk.dim(` ${result.skipReason}`));
|
|
741
297
|
}
|
|
742
298
|
}
|
|
743
299
|
|
|
744
300
|
// src/commands/validate.ts
|
|
745
|
-
import { existsSync as
|
|
746
|
-
import
|
|
747
|
-
import { parseHoldpointYaml as
|
|
301
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
302
|
+
import chalk2 from "chalk";
|
|
303
|
+
import { parseHoldpointYaml as parseHoldpointYaml2, validateConfig } from "@holdpoint/yaml-core";
|
|
748
304
|
async function validateCommand() {
|
|
749
|
-
if (!
|
|
750
|
-
console.error(
|
|
305
|
+
if (!existsSync2("checks.yaml")) {
|
|
306
|
+
console.error(chalk2.red("No checks.yaml found. Run `holdpoint init` first."));
|
|
751
307
|
process.exit(1);
|
|
752
308
|
}
|
|
753
|
-
const text =
|
|
309
|
+
const text = readFileSync2("checks.yaml", "utf8");
|
|
754
310
|
let config;
|
|
755
311
|
try {
|
|
756
|
-
config =
|
|
312
|
+
config = parseHoldpointYaml2(text);
|
|
757
313
|
} catch (err) {
|
|
758
|
-
console.error(
|
|
314
|
+
console.error(chalk2.red("Parse error:"), err.message);
|
|
759
315
|
process.exit(1);
|
|
760
316
|
}
|
|
761
317
|
const result = validateConfig(config);
|
|
762
318
|
if (result.valid) {
|
|
763
|
-
console.log(
|
|
319
|
+
console.log(chalk2.green("\u2713 checks.yaml is valid"));
|
|
764
320
|
console.log(
|
|
765
|
-
|
|
321
|
+
chalk2.dim(
|
|
766
322
|
` ${config.checks.filter((c) => c.cmd !== void 0).length} tasks, ${config.checks.filter((c) => c.prompt !== void 0).length} prompts, ${config.conditions.length} conditions`
|
|
767
323
|
)
|
|
768
324
|
);
|
|
769
325
|
} else {
|
|
770
|
-
console.error(
|
|
326
|
+
console.error(chalk2.red("\u2717 checks.yaml has errors:"));
|
|
771
327
|
for (const err of result.errors) {
|
|
772
|
-
console.error(` ${
|
|
328
|
+
console.error(` ${chalk2.yellow(err.path)}: ${err.message}`);
|
|
773
329
|
}
|
|
774
330
|
process.exit(1);
|
|
775
331
|
}
|
|
776
332
|
}
|
|
777
333
|
|
|
778
334
|
// src/commands/update.ts
|
|
779
|
-
import { existsSync as
|
|
780
|
-
import
|
|
781
|
-
import
|
|
782
|
-
import { parseHoldpointYaml as
|
|
783
|
-
import { buildConfigJson
|
|
784
|
-
import { buildEngineJson as
|
|
335
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
336
|
+
import chalk3 from "chalk";
|
|
337
|
+
import ora2 from "ora";
|
|
338
|
+
import { parseHoldpointYaml as parseHoldpointYaml3 } from "@holdpoint/yaml-core";
|
|
339
|
+
import { buildConfigJson, buildEngine } from "@holdpoint/engine-copilot";
|
|
340
|
+
import { buildEngineJson as buildClaudeEngineJson } from "@holdpoint/engine-claude";
|
|
785
341
|
import {
|
|
786
|
-
buildCheckScript as
|
|
787
|
-
buildEngine as
|
|
788
|
-
buildHooksJson as
|
|
342
|
+
buildCheckScript as buildCursorCheckScript,
|
|
343
|
+
buildEngine as buildCursorEngine,
|
|
344
|
+
buildHooksJson as buildCursorHooksJson
|
|
789
345
|
} from "@holdpoint/engine-cursor";
|
|
790
346
|
import {
|
|
791
|
-
buildConfigToml as
|
|
792
|
-
buildHooksJson as
|
|
793
|
-
buildCheckScript as
|
|
347
|
+
buildConfigToml as buildCodexConfigToml,
|
|
348
|
+
buildHooksJson as buildCodexHooksJson,
|
|
349
|
+
buildCheckScript as buildCodexCheckScript
|
|
794
350
|
} from "@holdpoint/engine-codex";
|
|
795
|
-
var
|
|
351
|
+
var MINIMAL_PREREQUISITES = `# Holdpoint prerequisites
|
|
796
352
|
|
|
797
353
|
Holdpoint installed repo-local engine integrations for one or more AI coding agents. Before relying on them locally, review these setup notes:
|
|
798
354
|
|
|
@@ -803,66 +359,66 @@ Holdpoint installed repo-local engine integrations for one or more AI coding age
|
|
|
803
359
|
|
|
804
360
|
Docs: https://holdpoint.dev/docs
|
|
805
361
|
`;
|
|
806
|
-
var
|
|
362
|
+
var MINIMAL_HOLDPOINT_REFERENCE = `# Holdpoint reference
|
|
807
363
|
|
|
808
364
|
Read \`MASTER_PROMPT.md\` first for the mandatory workflow, then use this file for deeper project-specific Holdpoint notes.
|
|
809
365
|
`;
|
|
810
366
|
async function updateCommand() {
|
|
811
|
-
if (!
|
|
812
|
-
console.error(
|
|
367
|
+
if (!existsSync3("checks.yaml")) {
|
|
368
|
+
console.error(chalk3.red("No checks.yaml found. Run `holdpoint init` first."));
|
|
813
369
|
process.exit(1);
|
|
814
370
|
}
|
|
815
|
-
const spinner =
|
|
816
|
-
const config =
|
|
371
|
+
const spinner = ora2("Updating Holdpoint engine files\u2026").start();
|
|
372
|
+
const config = parseHoldpointYaml3(readFileSync3("checks.yaml", "utf8"));
|
|
817
373
|
const detected = detectInstalledAgents();
|
|
818
374
|
const agents = detected.length > 0 ? detected : ["copilot", "claude", "cursor", "codex"];
|
|
819
375
|
const generatedDir = ".github/holdpoint/generated";
|
|
820
|
-
|
|
821
|
-
|
|
376
|
+
mkdirSync2(generatedDir, { recursive: true });
|
|
377
|
+
writeFileSync2(`${generatedDir}/checks.immutable.json`, buildConfigJson(config), "utf8");
|
|
822
378
|
if (agents.includes("copilot")) {
|
|
823
379
|
const extDir = ".github/extensions/holdpoint";
|
|
824
|
-
|
|
825
|
-
|
|
380
|
+
mkdirSync2(extDir, { recursive: true });
|
|
381
|
+
writeFileSync2(`${extDir}/extension.mjs`, buildEngine(config), "utf8");
|
|
826
382
|
spliceBreadcrumb(".github/copilot-instructions.md");
|
|
827
383
|
}
|
|
828
384
|
if (agents.includes("claude")) {
|
|
829
|
-
|
|
385
|
+
mkdirSync2(".claude", { recursive: true });
|
|
830
386
|
const settingsPath = ".claude/settings.json";
|
|
831
387
|
let existing = {};
|
|
832
|
-
if (
|
|
388
|
+
if (existsSync3(settingsPath)) {
|
|
833
389
|
try {
|
|
834
|
-
existing = JSON.parse(
|
|
390
|
+
existing = JSON.parse(readFileSync3(settingsPath, "utf8"));
|
|
835
391
|
} catch {
|
|
836
392
|
}
|
|
837
393
|
}
|
|
838
|
-
const hooks = JSON.parse(
|
|
839
|
-
|
|
394
|
+
const hooks = JSON.parse(buildClaudeEngineJson(config));
|
|
395
|
+
writeFileSync2(
|
|
840
396
|
settingsPath,
|
|
841
397
|
JSON.stringify(mergeClaudeSettings(existing, hooks), null, 2) + "\n"
|
|
842
398
|
);
|
|
843
399
|
spliceBreadcrumb("CLAUDE.md");
|
|
844
400
|
}
|
|
845
401
|
if (agents.includes("cursor")) {
|
|
846
|
-
|
|
402
|
+
mkdirSync2(".cursor", { recursive: true });
|
|
847
403
|
const cursorHooksPath = ".cursor/hooks.json";
|
|
848
404
|
let existingHooks = {};
|
|
849
|
-
if (
|
|
405
|
+
if (existsSync3(cursorHooksPath)) {
|
|
850
406
|
try {
|
|
851
|
-
existingHooks = JSON.parse(
|
|
407
|
+
existingHooks = JSON.parse(readFileSync3(cursorHooksPath, "utf8"));
|
|
852
408
|
} catch {
|
|
853
409
|
}
|
|
854
410
|
}
|
|
855
|
-
const cursorHooks = JSON.parse(
|
|
856
|
-
|
|
411
|
+
const cursorHooks = JSON.parse(buildCursorHooksJson(config));
|
|
412
|
+
writeFileSync2(
|
|
857
413
|
cursorHooksPath,
|
|
858
414
|
JSON.stringify(mergeCursorHooks(existingHooks, cursorHooks), null, 2) + "\n",
|
|
859
415
|
"utf8"
|
|
860
416
|
);
|
|
861
|
-
|
|
862
|
-
const cursorRules =
|
|
417
|
+
writeFileSync2(".cursor/holdpoint-hook.mjs", buildCursorCheckScript(), "utf8");
|
|
418
|
+
const cursorRules = buildCursorEngine(config);
|
|
863
419
|
const cursorPath = ".cursorrules";
|
|
864
|
-
if (
|
|
865
|
-
const content =
|
|
420
|
+
if (existsSync3(cursorPath)) {
|
|
421
|
+
const content = readFileSync3(cursorPath, "utf8");
|
|
866
422
|
const start = content.indexOf("# \u2500\u2500\u2500 Holdpoint Rules");
|
|
867
423
|
const end = content.indexOf("# \u2500\u2500\u2500 End Holdpoint Rules \u2500\u2500\u2500");
|
|
868
424
|
if (start !== -1 && end !== -1) {
|
|
@@ -873,28 +429,28 @@ async function updateCommand() {
|
|
|
873
429
|
|
|
874
430
|
` : "") + cursorRules + (suffix ? `
|
|
875
431
|
${suffix}` : "");
|
|
876
|
-
|
|
432
|
+
writeFileSync2(cursorPath, updated);
|
|
877
433
|
} else {
|
|
878
|
-
|
|
434
|
+
writeFileSync2(cursorPath, `${content.trimEnd()}
|
|
879
435
|
|
|
880
436
|
${cursorRules}`);
|
|
881
437
|
}
|
|
882
438
|
} else {
|
|
883
|
-
|
|
439
|
+
writeFileSync2(cursorPath, cursorRules);
|
|
884
440
|
}
|
|
885
441
|
spliceBreadcrumb(".cursor/rules/holdpoint.md");
|
|
886
442
|
}
|
|
887
443
|
if (agents.includes("codex")) {
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
444
|
+
mkdirSync2(".codex", { recursive: true });
|
|
445
|
+
writeFileSync2(".codex/hooks.json", buildCodexHooksJson(config), "utf8");
|
|
446
|
+
writeFileSync2(".codex/holdpoint-check.mjs", buildCodexCheckScript(config), "utf8");
|
|
891
447
|
const configTomlPath = ".codex/config.toml";
|
|
892
|
-
if (!
|
|
893
|
-
|
|
448
|
+
if (!existsSync3(configTomlPath)) {
|
|
449
|
+
writeFileSync2(configTomlPath, buildCodexConfigToml(), "utf8");
|
|
894
450
|
} else {
|
|
895
|
-
const existing =
|
|
451
|
+
const existing = readFileSync3(configTomlPath, "utf8");
|
|
896
452
|
if (!existing.includes("[features]")) {
|
|
897
|
-
|
|
453
|
+
writeFileSync2(configTomlPath, existing.trimEnd() + "\n\n" + buildCodexConfigToml(), "utf8");
|
|
898
454
|
}
|
|
899
455
|
}
|
|
900
456
|
spliceBreadcrumb("AGENTS.md");
|
|
@@ -902,22 +458,22 @@ ${cursorRules}`);
|
|
|
902
458
|
const wroteReference = ensureBundledFile(
|
|
903
459
|
"HOLDPOINT_REFERENCE.md",
|
|
904
460
|
"HOLDPOINT_REFERENCE.md",
|
|
905
|
-
|
|
461
|
+
MINIMAL_HOLDPOINT_REFERENCE
|
|
906
462
|
);
|
|
907
463
|
const wrotePrerequisites = ensureBundledFile(
|
|
908
464
|
"HOLDPOINT_PREREQUISITES.md",
|
|
909
465
|
"HOLDPOINT_PREREQUISITES.md",
|
|
910
|
-
|
|
466
|
+
MINIMAL_PREREQUISITES
|
|
911
467
|
);
|
|
912
|
-
spinner.succeed(
|
|
468
|
+
spinner.succeed(chalk3.green("Engine files updated from current checks.yaml"));
|
|
913
469
|
if (wroteReference) {
|
|
914
470
|
console.log(
|
|
915
|
-
|
|
471
|
+
chalk3.cyan("Created HOLDPOINT_REFERENCE.md with the full Holdpoint workflow reference.")
|
|
916
472
|
);
|
|
917
473
|
}
|
|
918
474
|
if (wrotePrerequisites) {
|
|
919
475
|
console.log(
|
|
920
|
-
|
|
476
|
+
chalk3.cyan(
|
|
921
477
|
"Created HOLDPOINT_PREREQUISITES.md with Copilot experimental-mode and other agent setup notes."
|
|
922
478
|
)
|
|
923
479
|
);
|
|
@@ -925,7 +481,7 @@ ${cursorRules}`);
|
|
|
925
481
|
}
|
|
926
482
|
|
|
927
483
|
// src/commands/build.ts
|
|
928
|
-
import
|
|
484
|
+
import chalk4 from "chalk";
|
|
929
485
|
|
|
930
486
|
// src/lib/ensure-daemon.ts
|
|
931
487
|
import { spawn } from "child_process";
|
|
@@ -960,27 +516,27 @@ async function ensureDaemon(timeoutMs = 5e3) {
|
|
|
960
516
|
}
|
|
961
517
|
|
|
962
518
|
// src/lib/open-browser.ts
|
|
963
|
-
import { execSync as
|
|
519
|
+
import { execSync as execSync2 } from "child_process";
|
|
964
520
|
function openBrowser(url) {
|
|
965
521
|
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
966
522
|
try {
|
|
967
|
-
|
|
523
|
+
execSync2(`${openCmd} ${JSON.stringify(url)}`, { stdio: "ignore" });
|
|
968
524
|
} catch {
|
|
969
525
|
}
|
|
970
526
|
}
|
|
971
527
|
|
|
972
528
|
// src/lib/project.ts
|
|
973
|
-
import { existsSync as
|
|
974
|
-
import { dirname
|
|
529
|
+
import { existsSync as existsSync4 } from "fs";
|
|
530
|
+
import { dirname, join as join2 } from "path";
|
|
975
531
|
import { identifyProject as identifyProject2 } from "@holdpoint/live-daemon";
|
|
976
532
|
function findChecksYaml(startDir) {
|
|
977
533
|
let current = startDir;
|
|
978
534
|
for (; ; ) {
|
|
979
|
-
const candidate =
|
|
980
|
-
if (
|
|
535
|
+
const candidate = join2(current, "checks.yaml");
|
|
536
|
+
if (existsSync4(candidate)) {
|
|
981
537
|
return candidate;
|
|
982
538
|
}
|
|
983
|
-
const parent =
|
|
539
|
+
const parent = dirname(current);
|
|
984
540
|
if (parent === current) {
|
|
985
541
|
return null;
|
|
986
542
|
}
|
|
@@ -990,7 +546,7 @@ function findChecksYaml(startDir) {
|
|
|
990
546
|
function tryResolveCurrentProject() {
|
|
991
547
|
const checksYaml = findChecksYaml(process.cwd());
|
|
992
548
|
if (checksYaml) {
|
|
993
|
-
return identifyProject2(
|
|
549
|
+
return identifyProject2(dirname(checksYaml));
|
|
994
550
|
}
|
|
995
551
|
try {
|
|
996
552
|
return identifyProject2(process.cwd());
|
|
@@ -1016,50 +572,50 @@ async function buildCommand() {
|
|
|
1016
572
|
appendProjectAuthParams(url, tryResolveCurrentProject());
|
|
1017
573
|
openBrowser(url.toString());
|
|
1018
574
|
console.log(
|
|
1019
|
-
|
|
575
|
+
chalk4.green(
|
|
1020
576
|
started ? "\u2713 Started Holdpoint Live and opened the builder" : "\u2713 Opened Holdpoint builder"
|
|
1021
577
|
)
|
|
1022
578
|
);
|
|
1023
|
-
console.log(` url: ${
|
|
579
|
+
console.log(` url: ${chalk4.cyan(url.toString())}`);
|
|
1024
580
|
}
|
|
1025
581
|
|
|
1026
582
|
// src/commands/evolve.ts
|
|
1027
|
-
import { existsSync as
|
|
1028
|
-
import { execSync as
|
|
1029
|
-
import
|
|
1030
|
-
import
|
|
1031
|
-
import { parseHoldpointYaml as
|
|
583
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
584
|
+
import { execSync as execSync5 } from "child_process";
|
|
585
|
+
import chalk5 from "chalk";
|
|
586
|
+
import ora3 from "ora";
|
|
587
|
+
import { parseHoldpointYaml as parseHoldpointYaml4, generateYaml } from "@holdpoint/yaml-core";
|
|
1032
588
|
|
|
1033
589
|
// src/evolve/scanner.ts
|
|
1034
|
-
import { existsSync as
|
|
1035
|
-
import { join as
|
|
1036
|
-
import { execSync as
|
|
590
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync } from "fs";
|
|
591
|
+
import { join as join3 } from "path";
|
|
592
|
+
import { execSync as execSync3 } from "child_process";
|
|
1037
593
|
function tryReadJson(path) {
|
|
1038
594
|
try {
|
|
1039
|
-
return JSON.parse(
|
|
595
|
+
return JSON.parse(readFileSync4(path, "utf8"));
|
|
1040
596
|
} catch {
|
|
1041
597
|
return null;
|
|
1042
598
|
}
|
|
1043
599
|
}
|
|
1044
600
|
function tryReadText(path) {
|
|
1045
601
|
try {
|
|
1046
|
-
return
|
|
602
|
+
return readFileSync4(path, "utf8");
|
|
1047
603
|
} catch {
|
|
1048
604
|
return "";
|
|
1049
605
|
}
|
|
1050
606
|
}
|
|
1051
607
|
function scanProject(cwd = process.cwd()) {
|
|
1052
|
-
const exists = (p) =>
|
|
608
|
+
const exists = (p) => existsSync5(join3(cwd, p));
|
|
1053
609
|
const packageManager = exists("pnpm-lock.yaml") ? "pnpm" : exists("yarn.lock") ? "yarn" : exists("bun.lockb") ? "bun" : "npm";
|
|
1054
|
-
const pkg = tryReadJson(
|
|
610
|
+
const pkg = tryReadJson(join3(cwd, "package.json"));
|
|
1055
611
|
const scripts = pkg?.scripts ?? {};
|
|
1056
612
|
const deps = /* @__PURE__ */ new Set([
|
|
1057
613
|
...Object.keys(pkg?.dependencies ?? {}),
|
|
1058
614
|
...Object.keys(pkg?.devDependencies ?? {})
|
|
1059
615
|
]);
|
|
1060
|
-
const pyprojectText = tryReadText(
|
|
1061
|
-
const requirementsText = tryReadText(
|
|
1062
|
-
const pipfileText = tryReadText(
|
|
616
|
+
const pyprojectText = tryReadText(join3(cwd, "pyproject.toml"));
|
|
617
|
+
const requirementsText = tryReadText(join3(cwd, "requirements.txt"));
|
|
618
|
+
const pipfileText = tryReadText(join3(cwd, "Pipfile"));
|
|
1063
619
|
const allPyText = pyprojectText + requirementsText + pipfileText;
|
|
1064
620
|
const hasPytest = exists("pytest.ini") || exists("setup.cfg") || allPyText.includes("pytest") || allPyText.includes("[tool.pytest");
|
|
1065
621
|
const hasRuff = allPyText.includes("ruff") || deps.has("ruff");
|
|
@@ -1104,6 +660,10 @@ function scanProject(cwd = process.cwd()) {
|
|
|
1104
660
|
hasOpenApi: exists("openapi.yaml") || exists("openapi.yml") || exists("openapi.json") || exists("api/openapi.yaml"),
|
|
1105
661
|
// CI
|
|
1106
662
|
hasGithubActions: exists(".github/workflows"),
|
|
663
|
+
// Release tooling — gates the `changelog-update` suggest template,
|
|
664
|
+
// since projects using changesets get release notes from .changeset
|
|
665
|
+
// files automatically and don't want a manual-CHANGELOG-entry check.
|
|
666
|
+
hasChangesets: exists(".changeset/config.json"),
|
|
1107
667
|
packageManager,
|
|
1108
668
|
scripts,
|
|
1109
669
|
deps
|
|
@@ -1111,9 +671,9 @@ function scanProject(cwd = process.cwd()) {
|
|
|
1111
671
|
}
|
|
1112
672
|
|
|
1113
673
|
// src/evolve/dead-checker.ts
|
|
1114
|
-
import { execSync as
|
|
1115
|
-
import { readdirSync as readdirSync2, existsSync as
|
|
1116
|
-
import { join as
|
|
674
|
+
import { execSync as execSync4 } from "child_process";
|
|
675
|
+
import { readdirSync as readdirSync2, existsSync as existsSync6 } from "fs";
|
|
676
|
+
import { join as join4 } from "path";
|
|
1117
677
|
var NAMED_SCOPES = /* @__PURE__ */ new Set([
|
|
1118
678
|
"frontend",
|
|
1119
679
|
"backend",
|
|
@@ -1158,7 +718,7 @@ function walkDir(dir, root, depth, maxDepth) {
|
|
|
1158
718
|
const results = [];
|
|
1159
719
|
for (const entry of entries) {
|
|
1160
720
|
if (WALK_IGNORED.has(entry) || entry.startsWith(".")) continue;
|
|
1161
|
-
const full =
|
|
721
|
+
const full = join4(dir, entry);
|
|
1162
722
|
const rel = full.slice(root.length + 1);
|
|
1163
723
|
results.push(rel);
|
|
1164
724
|
const children = walkDir(full, root, depth + 1, maxDepth);
|
|
@@ -1168,7 +728,7 @@ function walkDir(dir, root, depth, maxDepth) {
|
|
|
1168
728
|
}
|
|
1169
729
|
function getRepoFiles(cwd) {
|
|
1170
730
|
try {
|
|
1171
|
-
const out =
|
|
731
|
+
const out = execSync4("git ls-files", {
|
|
1172
732
|
cwd,
|
|
1173
733
|
encoding: "utf8",
|
|
1174
734
|
stdio: ["pipe", "pipe", "ignore"]
|
|
@@ -1205,7 +765,7 @@ function detectStaleChecks(config, repoFiles) {
|
|
|
1205
765
|
if (matches.length === 0) {
|
|
1206
766
|
const label = patternAlias ? `Pattern '${patternAlias}' (= '${regexStr}')` : `Regex '${regexStr}'`;
|
|
1207
767
|
const suggestedConditionPath = extractPathFromRegex(regexStr);
|
|
1208
|
-
const pathGone = !suggestedConditionPath || !
|
|
768
|
+
const pathGone = !suggestedConditionPath || !existsSync6(join4(process.cwd(), suggestedConditionPath));
|
|
1209
769
|
if (pathGone) {
|
|
1210
770
|
stale.push({
|
|
1211
771
|
check,
|
|
@@ -1240,7 +800,11 @@ function getTemplates(profile) {
|
|
|
1240
800
|
id: "changelog-update",
|
|
1241
801
|
label: "Add a CHANGELOG.md entry for this session",
|
|
1242
802
|
prompt: "Before committing, add an entry to CHANGELOG.md describing what was done. Use Keep a Changelog format \u2014 add under ## [Unreleased] (create the file and that section if absent). Group entries as Added, Changed, Fixed, or Removed. Be concise but specific. The entry text will serve as the commit message.",
|
|
1243
|
-
|
|
803
|
+
// Don't propose this for changesets-using projects — those get
|
|
804
|
+
// release notes from .changeset/*.md files automatically and the
|
|
805
|
+
// sibling `changelog-changeset` check is what they should use
|
|
806
|
+
// instead. Proposing both would be confusing and contradictory.
|
|
807
|
+
trigger: (p) => !p.hasChangesets
|
|
1244
808
|
},
|
|
1245
809
|
{
|
|
1246
810
|
id: "readme-sync",
|
|
@@ -1409,20 +973,20 @@ function withHeader(header, newYaml) {
|
|
|
1409
973
|
return header + "\n\n" + newYaml;
|
|
1410
974
|
}
|
|
1411
975
|
async function evolveCommand(options) {
|
|
1412
|
-
if (!
|
|
1413
|
-
console.error(
|
|
976
|
+
if (!existsSync7("checks.yaml")) {
|
|
977
|
+
console.error(chalk5.red("No checks.yaml found. Run `holdpoint init` first."));
|
|
1414
978
|
process.exit(1);
|
|
1415
979
|
}
|
|
1416
|
-
const spinner =
|
|
980
|
+
const spinner = ora3("Scanning project profile\u2026").start();
|
|
1417
981
|
const cwd = process.cwd();
|
|
1418
982
|
const profile = scanProject(cwd);
|
|
1419
983
|
const repoFiles = getRepoFiles(cwd);
|
|
1420
|
-
const yamlContent =
|
|
984
|
+
const yamlContent = readFileSync5("checks.yaml", "utf8");
|
|
1421
985
|
let config;
|
|
1422
986
|
try {
|
|
1423
|
-
config =
|
|
987
|
+
config = parseHoldpointYaml4(yamlContent);
|
|
1424
988
|
} catch (err) {
|
|
1425
|
-
spinner.fail(
|
|
989
|
+
spinner.fail(chalk5.red("Invalid checks.yaml:") + " " + err.message);
|
|
1426
990
|
process.exit(1);
|
|
1427
991
|
}
|
|
1428
992
|
spinner.stop();
|
|
@@ -1431,7 +995,7 @@ async function evolveCommand(options) {
|
|
|
1431
995
|
const allTemplates = getTemplates(profile);
|
|
1432
996
|
const proposals = allTemplates.filter((t) => t.trigger(profile) && !existingIds.has(t.id));
|
|
1433
997
|
const staleChecks = detectStaleChecks(config, repoFiles);
|
|
1434
|
-
console.log(
|
|
998
|
+
console.log(chalk5.bold("\n\u{1F4CB} Project profile:"));
|
|
1435
999
|
const traits = [
|
|
1436
1000
|
["TypeScript", profile.hasTypeScript, "tsconfig.json"],
|
|
1437
1001
|
["ESLint", profile.hasEslint, "eslint.config.*"],
|
|
@@ -1457,44 +1021,44 @@ async function evolveCommand(options) {
|
|
|
1457
1021
|
];
|
|
1458
1022
|
const detected = traits.filter(([, yes]) => yes);
|
|
1459
1023
|
if (detected.length === 0) {
|
|
1460
|
-
console.log(
|
|
1024
|
+
console.log(chalk5.dim(" (empty project \u2014 only universal checks apply)"));
|
|
1461
1025
|
} else {
|
|
1462
1026
|
for (const [name, , hint] of detected) {
|
|
1463
|
-
console.log(` ${
|
|
1027
|
+
console.log(` ${chalk5.green("\u2713")} ${name.padEnd(18)} ${chalk5.dim(hint)}`);
|
|
1464
1028
|
}
|
|
1465
1029
|
}
|
|
1466
1030
|
if (staleChecks.length > 0) {
|
|
1467
|
-
console.log(
|
|
1031
|
+
console.log(chalk5.bold(`
|
|
1468
1032
|
\u26A0\uFE0F Stale checks (${staleChecks.length}):`));
|
|
1469
1033
|
for (const { check, reason, suggestedConditionPath } of staleChecks) {
|
|
1470
|
-
const fix = suggestedConditionPath ?
|
|
1471
|
-
console.log(` ${
|
|
1034
|
+
const fix = suggestedConditionPath ? chalk5.dim(` \u2192 will wrap with conditionId: file_exists: ${suggestedConditionPath}`) : chalk5.dim(" \u2192 no path inferred; review manually");
|
|
1035
|
+
console.log(` ${chalk5.yellow("\u25CC")} ${chalk5.bold(check.id)} ${chalk5.dim(reason)}${fix}`);
|
|
1472
1036
|
}
|
|
1473
1037
|
}
|
|
1474
1038
|
if (proposals.length === 0 && staleChecks.length === 0) {
|
|
1475
|
-
console.log(
|
|
1039
|
+
console.log(chalk5.green("\n\u2713 checks.yaml is fully in sync with the project profile."));
|
|
1476
1040
|
return;
|
|
1477
1041
|
}
|
|
1478
1042
|
if (proposals.length > 0) {
|
|
1479
|
-
console.log(
|
|
1043
|
+
console.log(chalk5.bold(`
|
|
1480
1044
|
\u{1F4A1} Proposed additions (${proposals.length}):`));
|
|
1481
1045
|
for (const t of proposals) {
|
|
1482
|
-
const scope = t.when ?
|
|
1483
|
-
const type = t.cmd ?
|
|
1484
|
-
const preview = t.cmd ?
|
|
1485
|
-
console.log(` ${
|
|
1046
|
+
const scope = t.when ? chalk5.cyan(` when: ${t.when}`) : "";
|
|
1047
|
+
const type = t.cmd ? chalk5.dim("cmd") : chalk5.dim("prompt");
|
|
1048
|
+
const preview = t.cmd ? chalk5.dim(` ${t.cmd.slice(0, 80)}${t.cmd.length > 80 ? "\u2026" : ""}`) : chalk5.dim(` ${(t.prompt ?? "").slice(0, 80)}${(t.prompt?.length ?? 0) > 80 ? "\u2026" : ""}`);
|
|
1049
|
+
console.log(` ${chalk5.green("+")} ${chalk5.bold(t.id.padEnd(24))} [${type}]${scope}`);
|
|
1486
1050
|
console.log(` ${preview}`);
|
|
1487
1051
|
}
|
|
1488
1052
|
}
|
|
1489
1053
|
if (!options.apply) {
|
|
1490
1054
|
console.log(
|
|
1491
|
-
|
|
1055
|
+
chalk5.red(`
|
|
1492
1056
|
\u2717 checks.yaml is out of sync with the project profile.`) + `
|
|
1493
|
-
Run ${
|
|
1057
|
+
Run ${chalk5.bold("npx @holdpoint/cli suggest --apply")} to apply these changes.`
|
|
1494
1058
|
);
|
|
1495
1059
|
process.exit(1);
|
|
1496
1060
|
}
|
|
1497
|
-
const applySpinner =
|
|
1061
|
+
const applySpinner = ora3("Applying changes to checks.yaml\u2026").start();
|
|
1498
1062
|
const newConditions = [...config.conditions];
|
|
1499
1063
|
for (const t of proposals) {
|
|
1500
1064
|
if (t.condition && !existingConditionIds.has(t.condition.id)) {
|
|
@@ -1531,33 +1095,33 @@ async function evolveCommand(options) {
|
|
|
1531
1095
|
};
|
|
1532
1096
|
const header = extractHeader(yamlContent);
|
|
1533
1097
|
const newYaml = withHeader(header, generateYaml(updatedConfig));
|
|
1534
|
-
|
|
1098
|
+
writeFileSync3("checks.yaml", newYaml, "utf8");
|
|
1535
1099
|
applySpinner.text = "Running holdpoint update\u2026";
|
|
1536
1100
|
try {
|
|
1537
|
-
|
|
1101
|
+
execSync5("npx @holdpoint/cli update", { stdio: "pipe" });
|
|
1538
1102
|
} catch {
|
|
1539
1103
|
applySpinner.warn(
|
|
1540
|
-
|
|
1104
|
+
chalk5.yellow("checks.yaml updated, but `holdpoint update` failed \u2014 run it manually.")
|
|
1541
1105
|
);
|
|
1542
1106
|
printAppliedSummary(proposals.length, staleChecks.length);
|
|
1543
1107
|
return;
|
|
1544
1108
|
}
|
|
1545
|
-
applySpinner.succeed(
|
|
1109
|
+
applySpinner.succeed(chalk5.green("checks.yaml updated and engine files regenerated."));
|
|
1546
1110
|
printAppliedSummary(proposals.length, staleChecks.length);
|
|
1547
1111
|
}
|
|
1548
1112
|
function printAppliedSummary(added, wrapped) {
|
|
1549
1113
|
const parts = [];
|
|
1550
|
-
if (added > 0) parts.push(
|
|
1114
|
+
if (added > 0) parts.push(chalk5.green(`${added} check${added === 1 ? "" : "s"} added`));
|
|
1551
1115
|
if (wrapped > 0)
|
|
1552
|
-
parts.push(
|
|
1116
|
+
parts.push(chalk5.yellow(`${wrapped} stale check${wrapped === 1 ? "" : "s"} wrapped`));
|
|
1553
1117
|
if (parts.length > 0) console.log(" " + parts.join(" \xB7 "));
|
|
1554
1118
|
console.log(
|
|
1555
|
-
|
|
1119
|
+
chalk5.dim("\n Review checks.yaml, then commit: ") + chalk5.yellow("git add checks.yaml && git commit -m 'chore: update holdpoint checks'")
|
|
1556
1120
|
);
|
|
1557
1121
|
}
|
|
1558
1122
|
|
|
1559
1123
|
// src/commands/live.ts
|
|
1560
|
-
import
|
|
1124
|
+
import chalk6 from "chalk";
|
|
1561
1125
|
async function liveCommand(options = {}) {
|
|
1562
1126
|
const { info, started } = await ensureDaemon();
|
|
1563
1127
|
const baseUrl = new URL(`/__holdpoint/live-auth`, `http://127.0.0.1:${info.port}`);
|
|
@@ -1571,15 +1135,15 @@ async function liveCommand(options = {}) {
|
|
|
1571
1135
|
}
|
|
1572
1136
|
openBrowser(baseUrl.toString());
|
|
1573
1137
|
console.log(
|
|
1574
|
-
|
|
1138
|
+
chalk6.green(
|
|
1575
1139
|
started ? "\u2713 Started Holdpoint Live and opened the browser" : "\u2713 Opened Holdpoint Live"
|
|
1576
1140
|
)
|
|
1577
1141
|
);
|
|
1578
|
-
console.log(` url: ${
|
|
1142
|
+
console.log(` url: ${chalk6.cyan(baseUrl.toString())}`);
|
|
1579
1143
|
}
|
|
1580
1144
|
|
|
1581
1145
|
// src/commands/daemon.ts
|
|
1582
|
-
import
|
|
1146
|
+
import chalk7 from "chalk";
|
|
1583
1147
|
import {
|
|
1584
1148
|
DaemonAlreadyRunningError,
|
|
1585
1149
|
isProcessAlive,
|
|
@@ -1620,38 +1184,38 @@ async function daemonStartCommand() {
|
|
|
1620
1184
|
const { info, started } = await ensureDaemon();
|
|
1621
1185
|
const sessionCount = await fetchSessionCount(info);
|
|
1622
1186
|
const headline = started ? "Started Holdpoint Live daemon" : "Reused existing Holdpoint Live daemon";
|
|
1623
|
-
console.log(
|
|
1624
|
-
console.log(` pid: ${
|
|
1625
|
-
console.log(` port: ${
|
|
1626
|
-
console.log(` uptime: ${
|
|
1187
|
+
console.log(chalk7.green(`\u2713 ${headline}`));
|
|
1188
|
+
console.log(` pid: ${chalk7.cyan(String(info.pid))}`);
|
|
1189
|
+
console.log(` port: ${chalk7.cyan(String(info.port))}`);
|
|
1190
|
+
console.log(` uptime: ${chalk7.cyan(formatUptime(info))}`);
|
|
1627
1191
|
if (sessionCount !== null) {
|
|
1628
|
-
console.log(` sessions: ${
|
|
1192
|
+
console.log(` sessions: ${chalk7.cyan(String(sessionCount))}`);
|
|
1629
1193
|
}
|
|
1630
1194
|
}
|
|
1631
1195
|
async function daemonStatusCommand() {
|
|
1632
1196
|
const lock = await readHealthyDaemonLock2();
|
|
1633
1197
|
if (!lock) {
|
|
1634
|
-
console.log(
|
|
1198
|
+
console.log(chalk7.yellow("Holdpoint Live daemon is not running."));
|
|
1635
1199
|
return;
|
|
1636
1200
|
}
|
|
1637
1201
|
const sessionCount = await fetchSessionCount(lock);
|
|
1638
|
-
console.log(
|
|
1639
|
-
console.log(` pid: ${
|
|
1640
|
-
console.log(` port: ${
|
|
1641
|
-
console.log(` uptime: ${
|
|
1202
|
+
console.log(chalk7.green("\u2713 Holdpoint Live daemon is running"));
|
|
1203
|
+
console.log(` pid: ${chalk7.cyan(String(lock.pid))}`);
|
|
1204
|
+
console.log(` port: ${chalk7.cyan(String(lock.port))}`);
|
|
1205
|
+
console.log(` uptime: ${chalk7.cyan(formatUptime(lock))}`);
|
|
1642
1206
|
if (sessionCount !== null) {
|
|
1643
|
-
console.log(` sessions: ${
|
|
1207
|
+
console.log(` sessions: ${chalk7.cyan(String(sessionCount))}`);
|
|
1644
1208
|
}
|
|
1645
1209
|
}
|
|
1646
1210
|
async function daemonStopCommand() {
|
|
1647
1211
|
const lock = readDaemonLock();
|
|
1648
1212
|
if (!lock) {
|
|
1649
|
-
console.log(
|
|
1213
|
+
console.log(chalk7.yellow("Holdpoint Live daemon is not running."));
|
|
1650
1214
|
return;
|
|
1651
1215
|
}
|
|
1652
1216
|
if (!isProcessAlive(lock.pid)) {
|
|
1653
1217
|
removeDaemonLock(void 0, lock.token);
|
|
1654
|
-
console.log(
|
|
1218
|
+
console.log(chalk7.yellow("Removed stale Holdpoint Live lockfile."));
|
|
1655
1219
|
return;
|
|
1656
1220
|
}
|
|
1657
1221
|
process.kill(lock.pid, "SIGTERM");
|
|
@@ -1659,7 +1223,7 @@ async function daemonStopCommand() {
|
|
|
1659
1223
|
while (Date.now() < deadline) {
|
|
1660
1224
|
if (!isProcessAlive(lock.pid)) {
|
|
1661
1225
|
removeDaemonLock(void 0, lock.token);
|
|
1662
|
-
console.log(
|
|
1226
|
+
console.log(chalk7.green(`\u2713 Stopped Holdpoint Live daemon (${lock.pid})`));
|
|
1663
1227
|
return;
|
|
1664
1228
|
}
|
|
1665
1229
|
await sleep2(100);
|
|
@@ -1667,7 +1231,7 @@ async function daemonStopCommand() {
|
|
|
1667
1231
|
process.kill(lock.pid, "SIGKILL");
|
|
1668
1232
|
await sleep2(100);
|
|
1669
1233
|
removeDaemonLock(void 0, lock.token);
|
|
1670
|
-
console.log(
|
|
1234
|
+
console.log(chalk7.green(`\u2713 Force-stopped Holdpoint Live daemon (${lock.pid})`));
|
|
1671
1235
|
}
|
|
1672
1236
|
async function daemonServeCommand(options) {
|
|
1673
1237
|
try {
|
|
@@ -1685,16 +1249,16 @@ async function daemonServeCommand(options) {
|
|
|
1685
1249
|
}
|
|
1686
1250
|
|
|
1687
1251
|
// src/commands/engines.ts
|
|
1688
|
-
import
|
|
1252
|
+
import chalk8 from "chalk";
|
|
1689
1253
|
|
|
1690
1254
|
// src/engines.ts
|
|
1691
|
-
import { existsSync as
|
|
1692
|
-
import { dirname as
|
|
1255
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
1256
|
+
import { dirname as dirname2, join as join5, resolve, sep } from "path";
|
|
1693
1257
|
import { createRequire } from "module";
|
|
1694
|
-
import { fileURLToPath
|
|
1258
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
1695
1259
|
import { parseEventV1 } from "@holdpoint/live-protocol";
|
|
1696
1260
|
var require2 = createRequire(import.meta.url);
|
|
1697
|
-
var CLI_SRC_DIR =
|
|
1261
|
+
var CLI_SRC_DIR = dirname2(fileURLToPath(import.meta.url));
|
|
1698
1262
|
var MONOREPO_ROOT = resolve(CLI_SRC_DIR, "../../..");
|
|
1699
1263
|
var BUILTIN_LIVE_ENGINE_PACKAGES = [
|
|
1700
1264
|
"@holdpoint/engine-claude",
|
|
@@ -1702,19 +1266,19 @@ var BUILTIN_LIVE_ENGINE_PACKAGES = [
|
|
|
1702
1266
|
"@holdpoint/engine-cursor"
|
|
1703
1267
|
];
|
|
1704
1268
|
var HOLDPOINT_ENGINE_KEYWORD = "holdpoint-engine";
|
|
1705
|
-
function
|
|
1269
|
+
function isObject(value) {
|
|
1706
1270
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
1707
1271
|
}
|
|
1708
1272
|
function isExternalLiveEnginePackageName(packageName) {
|
|
1709
1273
|
return /^holdpoint-engine-[a-z0-9-]+$/.test(packageName) || /^@[a-z0-9_.-]+\/holdpoint-engine-[a-z0-9-]+$/.test(packageName);
|
|
1710
1274
|
}
|
|
1711
1275
|
function readJsonFile(path) {
|
|
1712
|
-
if (!
|
|
1276
|
+
if (!existsSync8(path)) {
|
|
1713
1277
|
return null;
|
|
1714
1278
|
}
|
|
1715
1279
|
try {
|
|
1716
|
-
const parsed = JSON.parse(
|
|
1717
|
-
return
|
|
1280
|
+
const parsed = JSON.parse(readFileSync6(path, "utf8"));
|
|
1281
|
+
return isObject(parsed) ? parsed : null;
|
|
1718
1282
|
} catch {
|
|
1719
1283
|
return null;
|
|
1720
1284
|
}
|
|
@@ -1722,10 +1286,10 @@ function readJsonFile(path) {
|
|
|
1722
1286
|
function findNearestPackageRoot(startDir) {
|
|
1723
1287
|
let current = resolve(startDir);
|
|
1724
1288
|
while (true) {
|
|
1725
|
-
if (
|
|
1289
|
+
if (existsSync8(join5(current, "package.json"))) {
|
|
1726
1290
|
return current;
|
|
1727
1291
|
}
|
|
1728
|
-
const parent =
|
|
1292
|
+
const parent = dirname2(current);
|
|
1729
1293
|
if (parent === current) {
|
|
1730
1294
|
return resolve(startDir);
|
|
1731
1295
|
}
|
|
@@ -1733,12 +1297,12 @@ function findNearestPackageRoot(startDir) {
|
|
|
1733
1297
|
}
|
|
1734
1298
|
}
|
|
1735
1299
|
function findPackageRootFromFile(path) {
|
|
1736
|
-
let current =
|
|
1300
|
+
let current = dirname2(path);
|
|
1737
1301
|
while (true) {
|
|
1738
|
-
if (
|
|
1302
|
+
if (existsSync8(join5(current, "package.json"))) {
|
|
1739
1303
|
return current;
|
|
1740
1304
|
}
|
|
1741
|
-
const parent =
|
|
1305
|
+
const parent = dirname2(current);
|
|
1742
1306
|
if (parent === current) {
|
|
1743
1307
|
return null;
|
|
1744
1308
|
}
|
|
@@ -1746,14 +1310,14 @@ function findPackageRootFromFile(path) {
|
|
|
1746
1310
|
}
|
|
1747
1311
|
}
|
|
1748
1312
|
function getDependencyEnginePackageNames(projectRoot) {
|
|
1749
|
-
const packageJson = readJsonFile(
|
|
1313
|
+
const packageJson = readJsonFile(join5(projectRoot, "package.json"));
|
|
1750
1314
|
if (!packageJson) {
|
|
1751
1315
|
return [];
|
|
1752
1316
|
}
|
|
1753
1317
|
const packageNames = /* @__PURE__ */ new Set();
|
|
1754
1318
|
for (const field of ["dependencies", "devDependencies", "optionalDependencies"]) {
|
|
1755
1319
|
const deps = packageJson[field];
|
|
1756
|
-
if (!
|
|
1320
|
+
if (!isObject(deps)) {
|
|
1757
1321
|
continue;
|
|
1758
1322
|
}
|
|
1759
1323
|
for (const packageName of Object.keys(deps)) {
|
|
@@ -1781,13 +1345,13 @@ function resolvePackageRoot(packageName, projectRoot) {
|
|
|
1781
1345
|
const packageJsonPath = require2.resolve(`${packageName}/package.json`, {
|
|
1782
1346
|
paths: [projectRoot, process.cwd()]
|
|
1783
1347
|
});
|
|
1784
|
-
return
|
|
1348
|
+
return dirname2(packageJsonPath);
|
|
1785
1349
|
} catch {
|
|
1786
1350
|
if (packageName.startsWith("@holdpoint/")) {
|
|
1787
1351
|
const scopedName = packageName.split("/")[1];
|
|
1788
1352
|
if (scopedName) {
|
|
1789
1353
|
const packageDir = resolve(MONOREPO_ROOT, "packages", scopedName);
|
|
1790
|
-
if (
|
|
1354
|
+
if (existsSync8(join5(packageDir, "package.json"))) {
|
|
1791
1355
|
return packageDir;
|
|
1792
1356
|
}
|
|
1793
1357
|
}
|
|
@@ -1799,7 +1363,7 @@ function formatImportError(error) {
|
|
|
1799
1363
|
return error instanceof Error && error.message ? error.message : String(error);
|
|
1800
1364
|
}
|
|
1801
1365
|
function parseManifest(value) {
|
|
1802
|
-
if (!
|
|
1366
|
+
if (!isObject(value)) {
|
|
1803
1367
|
return null;
|
|
1804
1368
|
}
|
|
1805
1369
|
if (value.manifestVersion !== 1) {
|
|
@@ -1818,7 +1382,7 @@ function parseManifest(value) {
|
|
|
1818
1382
|
};
|
|
1819
1383
|
}
|
|
1820
1384
|
function parseLiveCapabilities(value) {
|
|
1821
|
-
if (!
|
|
1385
|
+
if (!isObject(value)) {
|
|
1822
1386
|
return null;
|
|
1823
1387
|
}
|
|
1824
1388
|
const capabilities = {};
|
|
@@ -1841,7 +1405,7 @@ function parseLiveCapabilities(value) {
|
|
|
1841
1405
|
return capabilities;
|
|
1842
1406
|
}
|
|
1843
1407
|
function parseLiveAdapter(value, manifest) {
|
|
1844
|
-
if (!
|
|
1408
|
+
if (!isObject(value)) {
|
|
1845
1409
|
return null;
|
|
1846
1410
|
}
|
|
1847
1411
|
if (typeof value.id !== "string" || typeof value.displayName !== "string") {
|
|
@@ -1889,10 +1453,10 @@ function resolvePackageAssetPath(packageRoot, relativePath) {
|
|
|
1889
1453
|
packageRoot,
|
|
1890
1454
|
relativePath.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts")
|
|
1891
1455
|
);
|
|
1892
|
-
if (packageRoot.startsWith(resolve(MONOREPO_ROOT, "packages") + sep) &&
|
|
1456
|
+
if (packageRoot.startsWith(resolve(MONOREPO_ROOT, "packages") + sep) && existsSync8(sourceFallback)) {
|
|
1893
1457
|
return sourceFallback;
|
|
1894
1458
|
}
|
|
1895
|
-
if (
|
|
1459
|
+
if (existsSync8(declaredPath)) {
|
|
1896
1460
|
return declaredPath;
|
|
1897
1461
|
}
|
|
1898
1462
|
return sourceFallback;
|
|
@@ -1907,7 +1471,7 @@ async function resolveCandidate(packageName, source, projectRoot) {
|
|
|
1907
1471
|
reason: "package could not be resolved from this project"
|
|
1908
1472
|
};
|
|
1909
1473
|
}
|
|
1910
|
-
const packageJson = readJsonFile(
|
|
1474
|
+
const packageJson = readJsonFile(join5(packageRoot, "package.json"));
|
|
1911
1475
|
if (!packageJson) {
|
|
1912
1476
|
return {
|
|
1913
1477
|
packageName,
|
|
@@ -1925,7 +1489,7 @@ async function resolveCandidate(packageName, source, projectRoot) {
|
|
|
1925
1489
|
reason: `missing \`${HOLDPOINT_ENGINE_KEYWORD}\` keyword`
|
|
1926
1490
|
};
|
|
1927
1491
|
}
|
|
1928
|
-
const metadata =
|
|
1492
|
+
const metadata = isObject(packageJson.holdpoint) ? packageJson.holdpoint : void 0;
|
|
1929
1493
|
if (!metadata?.manifest) {
|
|
1930
1494
|
return {
|
|
1931
1495
|
packageName,
|
|
@@ -1944,7 +1508,7 @@ async function resolveCandidate(packageName, source, projectRoot) {
|
|
|
1944
1508
|
}
|
|
1945
1509
|
const manifestPath = resolvePackageAssetPath(packageRoot, metadata.manifest);
|
|
1946
1510
|
const adapterPath = resolvePackageAssetPath(packageRoot, metadata.adapter);
|
|
1947
|
-
if (!
|
|
1511
|
+
if (!existsSync8(manifestPath)) {
|
|
1948
1512
|
return {
|
|
1949
1513
|
packageName,
|
|
1950
1514
|
source,
|
|
@@ -1952,7 +1516,7 @@ async function resolveCandidate(packageName, source, projectRoot) {
|
|
|
1952
1516
|
reason: "manifest file does not exist"
|
|
1953
1517
|
};
|
|
1954
1518
|
}
|
|
1955
|
-
if (!
|
|
1519
|
+
if (!existsSync8(adapterPath)) {
|
|
1956
1520
|
return {
|
|
1957
1521
|
packageName,
|
|
1958
1522
|
source,
|
|
@@ -2064,22 +1628,22 @@ async function enginesCommand(options = {}) {
|
|
|
2064
1628
|
for (const engine of engines) {
|
|
2065
1629
|
if (engine.status === "loaded" && engine.manifest) {
|
|
2066
1630
|
console.log(
|
|
2067
|
-
`${
|
|
1631
|
+
`${chalk8.green("loaded")} ${chalk8.cyan(engine.manifest.id)} (${engine.manifest.displayName}) from ${chalk8.yellow(engine.packageName)} [${engine.source}]`
|
|
2068
1632
|
);
|
|
2069
1633
|
continue;
|
|
2070
1634
|
}
|
|
2071
1635
|
console.log(
|
|
2072
|
-
`${
|
|
1636
|
+
`${chalk8.yellow("ignored")} ${chalk8.yellow(engine.packageName)} [${engine.source}] \u2014 ${engine.reason ?? "unknown reason"}`
|
|
2073
1637
|
);
|
|
2074
1638
|
}
|
|
2075
1639
|
}
|
|
2076
1640
|
|
|
2077
1641
|
// src/commands/event.ts
|
|
2078
|
-
import { readFileSync as
|
|
1642
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
2079
1643
|
import { parseEventV1 as parseEventV12, parseEventsBatch } from "@holdpoint/live-protocol";
|
|
2080
1644
|
import { BridgeClient as BridgeClient2 } from "@holdpoint/sdk";
|
|
2081
1645
|
function readStdin() {
|
|
2082
|
-
return
|
|
1646
|
+
return readFileSync7(0, "utf8");
|
|
2083
1647
|
}
|
|
2084
1648
|
async function eventCommand(options) {
|
|
2085
1649
|
const stdin = readStdin().trim();
|
|
@@ -2129,10 +1693,10 @@ async function eventCommand(options) {
|
|
|
2129
1693
|
}
|
|
2130
1694
|
|
|
2131
1695
|
// src/commands/changeset.ts
|
|
2132
|
-
import { execSync as
|
|
2133
|
-
import { existsSync as
|
|
2134
|
-
import { join as
|
|
2135
|
-
import
|
|
1696
|
+
import { execSync as execSync6 } from "child_process";
|
|
1697
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync } from "fs";
|
|
1698
|
+
import { join as join6, relative } from "path";
|
|
1699
|
+
import chalk9 from "chalk";
|
|
2136
1700
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
2137
1701
|
".git",
|
|
2138
1702
|
".next",
|
|
@@ -2144,7 +1708,7 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
2144
1708
|
]);
|
|
2145
1709
|
function runGit(command) {
|
|
2146
1710
|
try {
|
|
2147
|
-
const out =
|
|
1711
|
+
const out = execSync6(command, {
|
|
2148
1712
|
encoding: "utf8",
|
|
2149
1713
|
stdio: ["pipe", "pipe", "ignore"]
|
|
2150
1714
|
});
|
|
@@ -2155,7 +1719,7 @@ function runGit(command) {
|
|
|
2155
1719
|
}
|
|
2156
1720
|
function readJson(path) {
|
|
2157
1721
|
try {
|
|
2158
|
-
return JSON.parse(
|
|
1722
|
+
return JSON.parse(readFileSync8(path, "utf8"));
|
|
2159
1723
|
} catch {
|
|
2160
1724
|
return null;
|
|
2161
1725
|
}
|
|
@@ -2198,22 +1762,22 @@ function getChangedFiles(options) {
|
|
|
2198
1762
|
return runGit("git diff --name-only HEAD~1 HEAD");
|
|
2199
1763
|
}
|
|
2200
1764
|
function parsePnpmWorkspacePatterns() {
|
|
2201
|
-
if (!
|
|
2202
|
-
const lines =
|
|
1765
|
+
if (!existsSync9("pnpm-workspace.yaml")) return [];
|
|
1766
|
+
const lines = readFileSync8("pnpm-workspace.yaml", "utf8").split(/\r?\n/);
|
|
2203
1767
|
return lines.map((line) => line.match(/^\s*-\s*['"]?([^'"]+)['"]?\s*$/)?.[1]).filter((line) => Boolean(line)).filter((line) => !line.startsWith("!"));
|
|
2204
1768
|
}
|
|
2205
1769
|
function expandOneLevelWorkspacePattern(pattern) {
|
|
2206
1770
|
const normalized = normalizePath(pattern).replace(/\/package\.json$/, "");
|
|
2207
1771
|
if (!normalized.includes("*")) {
|
|
2208
|
-
return
|
|
1772
|
+
return existsSync9(join6(normalized, "package.json")) ? [normalized] : [];
|
|
2209
1773
|
}
|
|
2210
1774
|
const starIndex = normalized.indexOf("*");
|
|
2211
1775
|
const parent = normalized.slice(0, starIndex).replace(/\/$/, "");
|
|
2212
1776
|
const suffix = normalized.slice(starIndex + 1).replace(/^\//, "");
|
|
2213
|
-
if (!parent || suffix.includes("*") || !
|
|
1777
|
+
if (!parent || suffix.includes("*") || !existsSync9(parent)) {
|
|
2214
1778
|
return [];
|
|
2215
1779
|
}
|
|
2216
|
-
return readdirSync3(parent).map((entry) =>
|
|
1780
|
+
return readdirSync3(parent).map((entry) => join6(parent, entry, suffix)).map(normalizePath).filter((candidate) => existsSync9(join6(candidate, "package.json")));
|
|
2217
1781
|
}
|
|
2218
1782
|
function walkPackageRoots(start, roots) {
|
|
2219
1783
|
let entries;
|
|
@@ -2222,13 +1786,13 @@ function walkPackageRoots(start, roots) {
|
|
|
2222
1786
|
} catch {
|
|
2223
1787
|
return;
|
|
2224
1788
|
}
|
|
2225
|
-
if (start !== "." &&
|
|
1789
|
+
if (start !== "." && existsSync9(join6(start, "package.json"))) {
|
|
2226
1790
|
roots.push(normalizePath(start));
|
|
2227
1791
|
return;
|
|
2228
1792
|
}
|
|
2229
1793
|
for (const entry of entries) {
|
|
2230
1794
|
if (IGNORED_DIRS.has(entry)) continue;
|
|
2231
|
-
const candidate =
|
|
1795
|
+
const candidate = join6(start, entry);
|
|
2232
1796
|
let stats;
|
|
2233
1797
|
try {
|
|
2234
1798
|
stats = statSync(candidate);
|
|
@@ -2241,7 +1805,7 @@ function walkPackageRoots(start, roots) {
|
|
|
2241
1805
|
}
|
|
2242
1806
|
}
|
|
2243
1807
|
function readPackageRoot(path) {
|
|
2244
|
-
const pkg = readJson(
|
|
1808
|
+
const pkg = readJson(join6(path, "package.json"));
|
|
2245
1809
|
if (!pkg) return null;
|
|
2246
1810
|
return {
|
|
2247
1811
|
path: normalizePath(path === "." ? "" : path),
|
|
@@ -2269,7 +1833,7 @@ function discoverPackageRoots(includePatterns = []) {
|
|
|
2269
1833
|
}
|
|
2270
1834
|
const discovered = [];
|
|
2271
1835
|
walkPackageRoots(".", discovered);
|
|
2272
|
-
const roots = discovered.length > 0 ? discovered :
|
|
1836
|
+
const roots = discovered.length > 0 ? discovered : existsSync9("package.json") ? ["."] : [];
|
|
2273
1837
|
return uniquePackageRoots(
|
|
2274
1838
|
roots.map(readPackageRoot).filter((pkg) => Boolean(pkg)).filter((pkg) => !pkg.private)
|
|
2275
1839
|
);
|
|
@@ -2330,30 +1894,30 @@ function analyzeChangesetRequirement(input) {
|
|
|
2330
1894
|
async function requireChangesetCommand(options) {
|
|
2331
1895
|
const changedFiles = getChangedFiles(options);
|
|
2332
1896
|
if (changedFiles.length === 0) {
|
|
2333
|
-
console.log(
|
|
1897
|
+
console.log(chalk9.green("\u2713 No changed files detected \u2014 no changeset required."));
|
|
2334
1898
|
return;
|
|
2335
1899
|
}
|
|
2336
1900
|
const packageRoots = discoverPackageRoots(options.include ?? []);
|
|
2337
1901
|
if (packageRoots.length === 0) {
|
|
2338
|
-
console.log(
|
|
1902
|
+
console.log(chalk9.green("\u2713 No package roots detected \u2014 no changeset required."));
|
|
2339
1903
|
return;
|
|
2340
1904
|
}
|
|
2341
|
-
const hasChangesetSetup =
|
|
1905
|
+
const hasChangesetSetup = existsSync9(".changeset");
|
|
2342
1906
|
const { requiredFiles, hasChangeset } = analyzeChangesetRequirement({
|
|
2343
1907
|
changedFiles,
|
|
2344
1908
|
packageRoots
|
|
2345
1909
|
});
|
|
2346
1910
|
if (requiredFiles.length === 0) {
|
|
2347
|
-
console.log(
|
|
1911
|
+
console.log(chalk9.green("\u2713 No release-affecting package files changed."));
|
|
2348
1912
|
return;
|
|
2349
1913
|
}
|
|
2350
1914
|
if (hasChangeset) {
|
|
2351
|
-
console.log(
|
|
1915
|
+
console.log(chalk9.green("\u2713 Package changes include a changeset."));
|
|
2352
1916
|
return;
|
|
2353
1917
|
}
|
|
2354
|
-
console.error(
|
|
1918
|
+
console.error(chalk9.red("\u2717 Package changes need a changeset."));
|
|
2355
1919
|
console.error("");
|
|
2356
|
-
console.error(
|
|
1920
|
+
console.error(chalk9.bold("Changed package files:"));
|
|
2357
1921
|
for (const item of requiredFiles.slice(0, 12)) {
|
|
2358
1922
|
console.error(` - ${item.file} (${item.packageName})`);
|
|
2359
1923
|
}
|
|
@@ -2365,12 +1929,12 @@ async function requireChangesetCommand(options) {
|
|
|
2365
1929
|
console.error(
|
|
2366
1930
|
"No .changeset directory was found. Create one and add a changeset before finishing:"
|
|
2367
1931
|
);
|
|
2368
|
-
console.error(
|
|
1932
|
+
console.error(chalk9.yellow(" mkdir -p .changeset"));
|
|
2369
1933
|
} else {
|
|
2370
1934
|
console.error("Add a changeset before finishing:");
|
|
2371
1935
|
}
|
|
2372
|
-
console.error(
|
|
2373
|
-
console.error(
|
|
1936
|
+
console.error(chalk9.yellow(" pnpm changeset"));
|
|
1937
|
+
console.error(chalk9.dim(" or add a .changeset/<name>.md file manually"));
|
|
2374
1938
|
process.exit(1);
|
|
2375
1939
|
}
|
|
2376
1940
|
|