@indigoai-us/hq-cli 5.311.4 → 5.312.1
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/CHANGELOG.md +20 -0
- package/assets/scaffold/core/scripts/lib/work-mesh-live-hook.sh +378 -0
- package/assets/scaffold/core/scripts/lib/work-mesh-live-rebind.sh +141 -0
- package/assets/scaffold/core/scripts/migrate-policy-triggers.sh +373 -0
- package/assets/scaffold/core/scripts/register-project.sh +262 -0
- package/assets/scaffold/core/scripts/work-mesh-live-rebind.sh +59 -0
- package/dist/command-catalog.generated.d.ts +160 -0
- package/dist/command-catalog.generated.js +200 -0
- package/dist/command-registration-plan.d.ts +6 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/company-rename-flag.d.ts +4 -0
- package/dist/commands/company-rename-flag.js +7 -0
- package/dist/commands/company-rename.d.ts +4 -0
- package/dist/commands/company-rename.js +90 -0
- package/dist/commands/company.js +2 -0
- package/dist/commands/core-checkpoint-stop-gate-native.js +12 -0
- package/dist/commands/core-eval-trigger-native.d.ts +4 -0
- package/dist/commands/core-eval-trigger-native.js +12 -0
- package/dist/commands/core-migrate-policy-triggers-native.d.ts +21 -0
- package/dist/commands/core-migrate-policy-triggers-native.js +378 -0
- package/dist/commands/core-native-hook-helper-commands.d.ts +4 -0
- package/dist/commands/core-native-hook-helper-commands.js +56 -0
- package/dist/commands/core-native-hook-helpers-flag.d.ts +4 -0
- package/dist/commands/core-native-hook-helpers-flag.js +7 -0
- package/dist/commands/core-register-project-native.d.ts +30 -0
- package/dist/commands/core-register-project-native.js +347 -0
- package/dist/commands/core-work-mesh-live-rebind-native.d.ts +18 -0
- package/dist/commands/core-work-mesh-live-rebind-native.js +262 -0
- package/dist/commands/core.js +7 -0
- package/dist/commands/daemon.js +22 -0
- package/dist/commands/dm.js +22 -6
- package/dist/commands/files-browse.js +16 -4
- package/dist/commands/lanes-message.js +17 -2
- package/dist/commands/lanes-shared.d.ts +1 -0
- package/dist/commands/lanes.js +96 -20
- package/dist/commands/vault-retention-flag.d.ts +3 -0
- package/dist/commands/vault-retention-flag.js +9 -0
- package/dist/commands/vault-retention.d.ts +11 -0
- package/dist/commands/vault-retention.js +69 -0
- package/dist/fast-core.js +10 -3
- package/dist/fast-scaffold.d.ts +2 -0
- package/dist/fast-scaffold.js +11 -0
- package/dist/lib/agents/model-catalog.d.ts +3 -2
- package/dist/lib/company-skill-wrapper.d.ts +0 -8
- package/dist/lib/core-utils/codex-skill-bridge.d.ts +2 -1
- package/dist/lib/core-utils/sentry-report.js +21 -4
- package/dist/lib/daemon/catalog.d.ts +2 -0
- package/dist/lib/daemon/catalog.js +13 -1
- package/dist/lib/daemon/config.d.ts +2 -0
- package/dist/lib/daemon/config.js +4 -0
- package/dist/lib/daemon/run.d.ts +2 -0
- package/dist/lib/daemon/run.js +5 -0
- package/dist/lib/deploy-api.d.ts +0 -1
- package/dist/lib/deploy-api.js +1 -1
- package/dist/lib/doctor/checks/work-context.js +9 -1
- package/dist/lib/lanes/autopilot.d.ts +38 -1
- package/dist/lib/lanes/autopilot.js +353 -5
- package/dist/lib/lanes/flag-keys.d.ts +9 -1
- package/dist/lib/lanes/flag-keys.js +9 -1
- package/dist/lib/lanes/keep-alive.d.ts +2 -0
- package/dist/lib/lanes/keep-alive.js +2 -0
- package/dist/lib/lanes/reconcile.d.ts +5 -0
- package/dist/lib/lanes/reconcile.js +57 -4
- package/dist/lib/lanes/watcher-main.js +20 -1
- package/dist/lib/lanes/watcher.d.ts +9 -0
- package/dist/lib/lanes/watcher.js +47 -1
- package/dist/lib/mesh/client.js +23 -5
- package/dist/lib/mesh/denial-codes.d.ts +5 -0
- package/dist/lib/mesh/denial-codes.js +14 -0
- package/dist/lib/mesh/live/daemon/doctor.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/doctor.js +11 -1
- package/dist/lib/report-run.d.ts +3 -2
- package/dist/lib/report-templates.d.ts +0 -1
- package/dist/lib/report-templates.js +0 -3
- package/dist/lib/scaffold-cli-floor.d.ts +0 -1
- package/dist/lib/scaffold-cli-floor.js +1 -1
- package/dist/lib/search-index/embed-lock.d.ts +2 -0
- package/dist/lib/search-index/embed-lock.js +10 -1
- package/dist/lib/work-context/contract.d.ts +3 -0
- package/dist/lib/work-context/contract.schema.json +3 -1
- package/dist/lib/work-context/outbox.d.ts +14 -5
- package/dist/lib/work-context/outbox.js +48 -5
- package/dist/lib/work-context/reconcile.js +26 -5
- package/dist/main.d.ts +20 -0
- package/dist/main.js +8 -1
- package/dist/run/discover-schemas.d.ts +2 -1
- package/dist/utils/core-eval-trigger.d.ts +4 -0
- package/dist/utils/core-eval-trigger.js +88 -0
- package/dist/utils/hq-roots.d.ts +0 -2
- package/dist/utils/hq-roots.js +1 -8
- package/dist/utils/incomplete-install-error.d.ts +7 -6
- package/dist/utils/large-file-guard.d.ts +0 -10
- package/dist/utils/large-file-guard.js +1 -1
- package/dist/utils/package-root-diagnostics.d.ts +1 -1
- package/dist/utils/self-update.d.ts +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { checkTriggerExpressions, evaluateTrigger } from "../utils/core-eval-trigger.js";
|
|
2
|
+
export declare const EVAL_TRIGGER_ASSET = "core/scripts/eval-trigger.sh";
|
|
3
|
+
export declare function runEvalTriggerFast(args: readonly string[], stdin: string, _cwd: string, _root: string | undefined): Promise<number>;
|
|
4
|
+
//# sourceMappingURL=core-eval-trigger-native.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Native implementation of core/scripts/eval-trigger.sh. */
|
|
2
|
+
import { checkTriggerExpressions, evaluateTrigger } from "../utils/core-eval-trigger.js";
|
|
3
|
+
export { checkTriggerExpressions, evaluateTrigger } from "../utils/core-eval-trigger.js";
|
|
4
|
+
export const EVAL_TRIGGER_ASSET = "core/scripts/eval-trigger.sh";
|
|
5
|
+
export async function runEvalTriggerFast(args, stdin, _cwd, _root) {
|
|
6
|
+
if (args[0] === "--check") {
|
|
7
|
+
process.stdout.write(checkTriggerExpressions(stdin));
|
|
8
|
+
return 0;
|
|
9
|
+
}
|
|
10
|
+
return evaluateTrigger(args[0] ?? "", args[1] ?? "");
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=core-eval-trigger-native.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
export declare const MIGRATE_POLICY_TRIGGERS_ASSET = "core/scripts/migrate-policy-triggers.sh";
|
|
3
|
+
export type MigratePolicyDependencies = {
|
|
4
|
+
root: string;
|
|
5
|
+
cwd: string;
|
|
6
|
+
env: NodeJS.ProcessEnv;
|
|
7
|
+
now: () => Date;
|
|
8
|
+
fs: typeof fs;
|
|
9
|
+
evaluatorExists?: () => boolean;
|
|
10
|
+
runFlock?: (lockFd: number, cwd: string, pathValue: string) => {
|
|
11
|
+
status: number | null;
|
|
12
|
+
error?: Error;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export declare function runMigratePolicyTriggers(args: readonly string[], deps: MigratePolicyDependencies): {
|
|
16
|
+
code: number;
|
|
17
|
+
stdout: string;
|
|
18
|
+
stderr: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function runMigratePolicyTriggersFast(args: readonly string[], _stdin: string, cwd: string, root?: string): Promise<number>;
|
|
21
|
+
//# sourceMappingURL=core-migrate-policy-triggers-native.d.ts.map
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/** Native implementation of core/scripts/migrate-policy-triggers.sh. */
|
|
2
|
+
import * as crypto from "node:crypto";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { evaluateTrigger } from "../utils/core-eval-trigger.js";
|
|
8
|
+
export const MIGRATE_POLICY_TRIGGERS_ASSET = "core/scripts/migrate-policy-triggers.sh";
|
|
9
|
+
const ON_LIVE = "[PreToolUse, PostToolUse, UserPromptSubmit, AssistantIntent]";
|
|
10
|
+
const ON_START = "[SessionStart]";
|
|
11
|
+
const TAG_STOP = new Set(("infrastructure consolidated ux safety hq hq-core hq-cli hq-packages " +
|
|
12
|
+
"hq-discipline basic-users knowledge-repos quiet-mode capabilities narration voice intent delight " +
|
|
13
|
+
"orchestration abstraction workflow deliverable routing anchor rulesets data-handling shell-injection " +
|
|
14
|
+
"promotion docs knowledge").split(/\s+/));
|
|
15
|
+
function extractCompany(cwd) {
|
|
16
|
+
const match = cwd.match(/.*companies\/([^/]+).*/);
|
|
17
|
+
return match?.[1] ?? "";
|
|
18
|
+
}
|
|
19
|
+
function defaultDirs(deps) {
|
|
20
|
+
const dirs = [path.join(deps.root, "core/policies"), path.join(deps.root, "personal/policies")];
|
|
21
|
+
for (const entry of safeDirNames(deps.fs, path.join(deps.root, "personal/workers"))) {
|
|
22
|
+
const candidate = path.join(deps.root, "personal/workers", entry, "policies");
|
|
23
|
+
if (isDirectory(deps.fs, candidate))
|
|
24
|
+
dirs.push(candidate);
|
|
25
|
+
}
|
|
26
|
+
const logicalCwd = deps.env.CLAUDE_PROJECT_DIR || deps.cwd;
|
|
27
|
+
const company = deps.env.HQ_POLICY_COMPANY || extractCompany(logicalCwd);
|
|
28
|
+
if (company && company !== "." && company !== ".." && !company.includes("/")) {
|
|
29
|
+
const companyRoot = path.join(deps.root, "companies", company);
|
|
30
|
+
dirs.push(path.join(companyRoot, "policies"));
|
|
31
|
+
for (const entry of safeDirNames(deps.fs, path.join(companyRoot, "workers"))) {
|
|
32
|
+
const candidate = path.join(companyRoot, "workers", entry, "policies");
|
|
33
|
+
if (isDirectory(deps.fs, candidate))
|
|
34
|
+
dirs.push(candidate);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const repoMatch = logicalCwd.match(/.*repos\/(public|private)\/([^/]+).*/);
|
|
38
|
+
if (repoMatch)
|
|
39
|
+
dirs.push(path.join(deps.root, "repos", repoMatch[1], repoMatch[2], ".claude/policies"));
|
|
40
|
+
return dirs;
|
|
41
|
+
}
|
|
42
|
+
function safeDirNames(fileSystem, directory) {
|
|
43
|
+
try {
|
|
44
|
+
return fileSystem.readdirSync(directory).sort();
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function isDirectory(fileSystem, value) {
|
|
51
|
+
try {
|
|
52
|
+
return fileSystem.statSync(value).isDirectory();
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function executableInPath(name, env, fileSystem) {
|
|
59
|
+
for (const directory of (env.PATH ?? "").split(path.delimiter)) {
|
|
60
|
+
if (!directory)
|
|
61
|
+
continue;
|
|
62
|
+
try {
|
|
63
|
+
const stat = fileSystem.statSync(path.join(directory, name));
|
|
64
|
+
if (stat.isFile() && (stat.mode & 0o111) !== 0)
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
catch { /* continue through PATH */ }
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
function runNonblockingFlock(lockFd, cwd, pathValue) {
|
|
72
|
+
const stdio = [
|
|
73
|
+
"ignore", "ignore", "ignore", "ignore", "ignore", "ignore", "ignore", "ignore", "ignore", lockFd,
|
|
74
|
+
];
|
|
75
|
+
const result = spawnSync("flock", ["-n", "9"], { cwd, env: { PATH: pathValue }, stdio });
|
|
76
|
+
return { status: result.status, error: result.error };
|
|
77
|
+
}
|
|
78
|
+
function frontmatterValue(content, key) {
|
|
79
|
+
let depth = 0;
|
|
80
|
+
for (const line of content.split("\n")) {
|
|
81
|
+
if (line === "---") {
|
|
82
|
+
depth += 1;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (depth === 1 && line.startsWith(`${key}:`))
|
|
86
|
+
return line.replace(new RegExp(`^${key}:[\\t\\n\\v\\f\\r ]*`), "");
|
|
87
|
+
}
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
function triggerExpression(trigger) {
|
|
91
|
+
const t = trigger.toLowerCase();
|
|
92
|
+
if (t.includes("deploy"))
|
|
93
|
+
return "deploy";
|
|
94
|
+
if (["credential", "secret", "op://", "aws_profile", ".env"].some((needle) => t.includes(needle)))
|
|
95
|
+
return "secret || credential";
|
|
96
|
+
if (["git push", "gh pr merge", " push ", "push to"].some((needle) => t.includes(needle)))
|
|
97
|
+
return "git && push";
|
|
98
|
+
if (t.includes("commitment"))
|
|
99
|
+
return "";
|
|
100
|
+
if (t.includes("commit"))
|
|
101
|
+
return "git && commit";
|
|
102
|
+
if (t.includes("checkout"))
|
|
103
|
+
return "git && checkout";
|
|
104
|
+
if (t.includes("rebase"))
|
|
105
|
+
return "git && rebase";
|
|
106
|
+
if (t.includes("git stash") || t.includes(" stash "))
|
|
107
|
+
return "git && stash";
|
|
108
|
+
if (t.includes("gh pr") || t.includes("pull request") || t.includes("pr merge"))
|
|
109
|
+
return "pr";
|
|
110
|
+
if (t.includes("merge"))
|
|
111
|
+
return "git && merge";
|
|
112
|
+
if (["migration", "migrate", "schema", "prisma"].some((needle) => t.includes(needle)))
|
|
113
|
+
return "migrate || migration || schema";
|
|
114
|
+
if (["npm install", "pnpm ", "yarn ", " install ", "package install"].some((needle) => t.includes(needle)))
|
|
115
|
+
return "install";
|
|
116
|
+
if (t.includes("grep"))
|
|
117
|
+
return "grep";
|
|
118
|
+
if (t.includes("slack"))
|
|
119
|
+
return "slack";
|
|
120
|
+
if (t.includes("email"))
|
|
121
|
+
return "email";
|
|
122
|
+
if (t.includes(" git ") || t.startsWith("git ") || t.endsWith(" git"))
|
|
123
|
+
return "git";
|
|
124
|
+
return "";
|
|
125
|
+
}
|
|
126
|
+
function deriveWhen(trigger, rawTags, enforcementRaw) {
|
|
127
|
+
let enforcement = enforcementRaw.split("#", 1)[0].toLowerCase().replace(/[\s\t\n\r\v\f]/g, "");
|
|
128
|
+
enforcement = enforcement.replace(/^"/, "").replace(/"$/, "").replace(/^'/, "").replace(/'$/, "");
|
|
129
|
+
const triggerExpr = triggerExpression(trigger);
|
|
130
|
+
const terms = [];
|
|
131
|
+
const seen = new Set();
|
|
132
|
+
if (triggerExpr) {
|
|
133
|
+
const exprTokens = triggerExpr.match(/[a-z_][a-z0-9_./-]*/g) ?? [];
|
|
134
|
+
for (const token of exprTokens)
|
|
135
|
+
seen.add(token);
|
|
136
|
+
terms.push(triggerExpr.includes(" ") ? `( ${triggerExpr} )` : triggerExpr);
|
|
137
|
+
}
|
|
138
|
+
const withoutBrackets = rawTags.replace(/^\[/, "").replace(/\]$/, "");
|
|
139
|
+
const rawTagParts = withoutBrackets.split(",");
|
|
140
|
+
for (let index = 0; index < rawTagParts.length; index += 1) {
|
|
141
|
+
// tag_tokens pipes through `while read`; Bash drops the final tag when
|
|
142
|
+
// the source has no trailing comma/newline because that read returns EOF.
|
|
143
|
+
if (index === rawTagParts.length - 1 && !withoutBrackets.endsWith(","))
|
|
144
|
+
break;
|
|
145
|
+
const raw = rawTagParts[index];
|
|
146
|
+
const tag = raw.toLowerCase().replace(/^\s+|\s+$/g, "").replace(/^vendor:/, "");
|
|
147
|
+
if (!tag || TAG_STOP.has(tag) || seen.has(tag))
|
|
148
|
+
continue;
|
|
149
|
+
seen.add(tag);
|
|
150
|
+
terms.push(tag);
|
|
151
|
+
}
|
|
152
|
+
if (!terms.length)
|
|
153
|
+
return enforcement === "hard" ? { when: "always", on: ON_START } : null;
|
|
154
|
+
return { when: terms.join(" || "), on: ON_LIVE };
|
|
155
|
+
}
|
|
156
|
+
function writeAtomic(fileSystem, target, bytes) {
|
|
157
|
+
const temporary = path.join(path.dirname(target), `.migrate-policy-triggers.${process.pid}.${Math.random().toString(16).slice(2)}`);
|
|
158
|
+
try {
|
|
159
|
+
fileSystem.writeFileSync(temporary, bytes, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
160
|
+
fileSystem.chmodSync(temporary, 0o600);
|
|
161
|
+
fileSystem.renameSync(temporary, target);
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
try {
|
|
166
|
+
fileSystem.unlinkSync(temporary);
|
|
167
|
+
}
|
|
168
|
+
catch { /* temporary did not survive */ }
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function unreadablePolicyDiagnostic(filename, error) {
|
|
173
|
+
const code = error.code;
|
|
174
|
+
const message = code === "EACCES" || code === "EPERM" ? "Permission denied" : "No such file or directory";
|
|
175
|
+
const script = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../assets/scaffold/core/scripts/migrate-policy-triggers.sh");
|
|
176
|
+
const awk = `awk: fatal: cannot open file \`${filename}' for reading: ${message}\n`;
|
|
177
|
+
return `${script}: line 309: ${filename}: ${message}\n${awk.repeat(3)}`;
|
|
178
|
+
}
|
|
179
|
+
function shellGlobMd(fileSystem, directory) {
|
|
180
|
+
try {
|
|
181
|
+
return fileSystem.readdirSync(directory).filter((name) => !name.startsWith(".") && name.endsWith(".md"))
|
|
182
|
+
.sort().map((name) => path.join(directory, name)).filter((name) => {
|
|
183
|
+
try {
|
|
184
|
+
return fileSystem.statSync(name).isFile();
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
export function runMigratePolicyTriggers(args, deps) {
|
|
196
|
+
let dryRun = false;
|
|
197
|
+
const explicitDirs = [];
|
|
198
|
+
for (const arg of args) {
|
|
199
|
+
if (arg === "--dry-run")
|
|
200
|
+
dryRun = true;
|
|
201
|
+
else
|
|
202
|
+
explicitDirs.push(arg);
|
|
203
|
+
}
|
|
204
|
+
const dirs = explicitDirs.length ? explicitDirs : defaultDirs(deps);
|
|
205
|
+
const fileSystem = deps.fs;
|
|
206
|
+
let stateDir = deps.env.HQ_MIGRATE_POLICY_TRIGGERS_STATE_DIR ?? "";
|
|
207
|
+
if (!stateDir) {
|
|
208
|
+
if (deps.env.XDG_STATE_HOME)
|
|
209
|
+
stateDir = path.join(deps.env.XDG_STATE_HOME, "hq-migrate-policy-triggers");
|
|
210
|
+
else if (deps.env.HOME)
|
|
211
|
+
stateDir = path.join(deps.env.HOME, ".local/state/hq-migrate-policy-triggers");
|
|
212
|
+
}
|
|
213
|
+
const cooldownText = deps.env.HQ_MIGRATE_POLICY_TRIGGERS_COOLDOWN_SECONDS || "3600";
|
|
214
|
+
const cooldown = /^-?\d+$/.test(cooldownText) ? Number(cooldownText) : 0;
|
|
215
|
+
const scope = Buffer.concat([Buffer.from(`${deps.root}\0`), ...dirs.map((dir) => Buffer.from(`${dir}\0`))]);
|
|
216
|
+
const scopeHash = crypto.createHash("sha256").update(scope).digest("hex");
|
|
217
|
+
const stamp = stateDir ? path.join(stateDir, scopeHash, "last-success") : "";
|
|
218
|
+
const lock = stateDir ? path.join(stateDir, scopeHash, "migrate.lock") : "";
|
|
219
|
+
let stateReady = false;
|
|
220
|
+
if (stateDir) {
|
|
221
|
+
try {
|
|
222
|
+
fileSystem.mkdirSync(path.dirname(stamp), { recursive: true });
|
|
223
|
+
stateReady = true;
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
stateReady = false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
let stderr = "";
|
|
230
|
+
let stdout = "";
|
|
231
|
+
const nowSeconds = () => Math.floor(deps.now().getTime() / 1000);
|
|
232
|
+
const bypass = dryRun || !stateReady || deps.env.HQ_MIGRATE_POLICY_TRIGGERS_FORCE === "1" || cooldown <= 0;
|
|
233
|
+
const cooldownBlocks = () => {
|
|
234
|
+
if (bypass)
|
|
235
|
+
return false;
|
|
236
|
+
try {
|
|
237
|
+
const last = fileSystem.readFileSync(stamp, "utf8");
|
|
238
|
+
if (!/^\d+$/.test(last)) {
|
|
239
|
+
stderr += `migrate-policy-triggers: cooldown stamp is unreadable; running\n`;
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
const age = nowSeconds() - Number(last);
|
|
243
|
+
if (age < 0) {
|
|
244
|
+
stderr += `migrate-policy-triggers: cooldown stamp is in the future; running\n`;
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
return age < cooldown;
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
if (err.code === "ENOENT")
|
|
251
|
+
return false;
|
|
252
|
+
stderr += `migrate-policy-triggers: cooldown stamp is unreadable; running\n`;
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
if (cooldownBlocks())
|
|
257
|
+
return { code: 0, stdout, stderr };
|
|
258
|
+
let lockFd;
|
|
259
|
+
if (stateReady && !dryRun && executableInPath("flock", deps.env, fileSystem)) {
|
|
260
|
+
try {
|
|
261
|
+
lockFd = fileSystem.openSync(lock, "w");
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return { code: 0, stdout, stderr };
|
|
265
|
+
}
|
|
266
|
+
const lockResult = (deps.runFlock ?? runNonblockingFlock)(lockFd, deps.cwd, deps.env.PATH ?? "");
|
|
267
|
+
if (lockResult.error || lockResult.status !== 0) {
|
|
268
|
+
fileSystem.closeSync(lockFd);
|
|
269
|
+
return { code: 0, stdout, stderr };
|
|
270
|
+
}
|
|
271
|
+
if (cooldownBlocks()) {
|
|
272
|
+
fileSystem.closeSync(lockFd);
|
|
273
|
+
return { code: 0, stdout, stderr };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const evaluator = deps.evaluatorExists ?? (() => {
|
|
277
|
+
const evaluatorPath = path.join(deps.root, "core/scripts/eval-trigger.sh");
|
|
278
|
+
try {
|
|
279
|
+
return fileSystem.statSync(evaluatorPath).isFile();
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
let migrated = 0, untriggered = 0, unparseable = 0, skipped = 0, session = 0;
|
|
286
|
+
let errorCode = 0;
|
|
287
|
+
try {
|
|
288
|
+
for (const dir of dirs) {
|
|
289
|
+
if (!isDirectory(fileSystem, dir))
|
|
290
|
+
continue;
|
|
291
|
+
for (const filename of shellGlobMd(fileSystem, dir)) {
|
|
292
|
+
const name = path.basename(filename);
|
|
293
|
+
if (name === "example-policy.md" || name === "README.md")
|
|
294
|
+
continue;
|
|
295
|
+
let original;
|
|
296
|
+
try {
|
|
297
|
+
original = fileSystem.readFileSync(filename, "utf8");
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
stderr += unreadablePolicyDiagnostic(filename, error);
|
|
301
|
+
untriggered += 1;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (original.split("\n").some((line) => line.startsWith("when:"))) {
|
|
305
|
+
skipped += 1;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
const derived = deriveWhen(frontmatterValue(original, "trigger"), frontmatterValue(original, "tags"), frontmatterValue(original, "enforcement"));
|
|
309
|
+
if (!derived) {
|
|
310
|
+
untriggered += 1;
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
const evaluatorAvailable = evaluator();
|
|
314
|
+
const parsedStatus = evaluateTrigger(derived.when, "");
|
|
315
|
+
if (evaluatorAvailable && parsedStatus === 2) {
|
|
316
|
+
unparseable += 1;
|
|
317
|
+
stderr += `migrate-policy-triggers: refusing to write unparseable when: ${name} -> \`${derived.when}\`\n`;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (derived.on === ON_START)
|
|
321
|
+
session += 1;
|
|
322
|
+
if (dryRun) {
|
|
323
|
+
const line = `${name.padEnd(52)} when: ${derived.when.padEnd(44)} on: ${derived.on}\n`;
|
|
324
|
+
stdout += line;
|
|
325
|
+
migrated += 1;
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
const anchor = original.split("\n").some((line) => line.startsWith("trigger:")) ? /^trigger:/ : /^id:/;
|
|
329
|
+
const lines = original.split("\n");
|
|
330
|
+
let inserted = false;
|
|
331
|
+
const rewritten = lines.flatMap((line) => {
|
|
332
|
+
if (!inserted && anchor.test(line)) {
|
|
333
|
+
inserted = true;
|
|
334
|
+
return [line, `when: ${derived.when}`, `on: ${derived.on}`];
|
|
335
|
+
}
|
|
336
|
+
return [line];
|
|
337
|
+
}).join("\n");
|
|
338
|
+
// awk print adds a newline even when the source's last line had none.
|
|
339
|
+
if (!writeAtomic(fileSystem, filename, `${rewritten.endsWith("\n") ? rewritten.slice(0, -1) : rewritten}\n`)) {
|
|
340
|
+
errorCode = 1;
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
migrated += 1;
|
|
344
|
+
}
|
|
345
|
+
if (errorCode)
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
if (migrated > 0)
|
|
349
|
+
stderr += `migrate-policy-triggers: backfilled ${migrated} policy trigger(s) (${session} hard -> SessionStart, ${untriggered} non-hard triggerless left unchanged, ${unparseable} unparseable derivations skipped, ${skipped} already had when)\n`;
|
|
350
|
+
if (!dryRun && stateReady && !errorCode) {
|
|
351
|
+
const stampTmp = path.join(path.dirname(stamp), `.last-success.${process.pid}.${Math.random().toString(16).slice(2)}`);
|
|
352
|
+
try {
|
|
353
|
+
fileSystem.writeFileSync(stampTmp, `${nowSeconds()}\n`, { mode: 0o600, flag: "wx" });
|
|
354
|
+
fileSystem.renameSync(stampTmp, stamp);
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
try {
|
|
358
|
+
fileSystem.unlinkSync(stampTmp);
|
|
359
|
+
}
|
|
360
|
+
catch { /* ignore stamp failure */ }
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
finally {
|
|
365
|
+
if (lockFd !== undefined)
|
|
366
|
+
fileSystem.closeSync(lockFd);
|
|
367
|
+
}
|
|
368
|
+
return { code: errorCode, stdout, stderr };
|
|
369
|
+
}
|
|
370
|
+
export async function runMigratePolicyTriggersFast(args, _stdin, cwd, root) {
|
|
371
|
+
const result = runMigratePolicyTriggers(args, { root: root ?? process.env.HQ_ROOT ?? cwd, cwd, env: process.env, now: () => new Date(), fs });
|
|
372
|
+
if (result.stdout)
|
|
373
|
+
process.stdout.write(result.stdout);
|
|
374
|
+
if (result.stderr)
|
|
375
|
+
process.stderr.write(result.stderr);
|
|
376
|
+
return result.code;
|
|
377
|
+
}
|
|
378
|
+
//# sourceMappingURL=core-migrate-policy-triggers-native.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
2
|
+
import { runBundledScript } from "../utils/run-bundled-script.js";
|
|
3
|
+
import { describeCoreCommand } from "./core-command-catalog.js";
|
|
4
|
+
import { coreNativeHookHelpersEnabled } from "./core-native-hook-helpers-flag.js";
|
|
5
|
+
import { EVAL_TRIGGER_ASSET, runEvalTriggerFast } from "./core-eval-trigger-native.js";
|
|
6
|
+
import { MIGRATE_POLICY_TRIGGERS_ASSET, runMigratePolicyTriggersFast } from "./core-migrate-policy-triggers-native.js";
|
|
7
|
+
import { REGISTER_PROJECT_ASSET, runRegisterProjectFast } from "./core-register-project-native.js";
|
|
8
|
+
import { WORK_MESH_LIVE_REBIND_ASSET, runWorkMeshLiveRebindFast } from "./core-work-mesh-live-rebind-native.js";
|
|
9
|
+
const entries = [
|
|
10
|
+
{ name: "eval-trigger", asset: EVAL_TRIGGER_ASSET, summary: "Evaluate policy trigger expressions", root: "cwd", shellCwd: "caller", shellRoot: "none", readsStdinFor: (args) => args[0] === "--check", run: runEvalTriggerFast },
|
|
11
|
+
{ name: "migrate-policy-triggers", asset: MIGRATE_POLICY_TRIGGERS_ASSET, summary: "Backfill policy trigger frontmatter", root: "live", shellCwd: "caller", shellRoot: "live", readsStdin: false, run: runMigratePolicyTriggersFast },
|
|
12
|
+
{ name: "register-project", asset: REGISTER_PROJECT_ASSET, summary: "Register a project with Work Mesh", root: "live", shellCwd: "caller", shellRoot: "live", readsStdin: false, run: runRegisterProjectFast },
|
|
13
|
+
{ name: "work-mesh-live-rebind", asset: WORK_MESH_LIVE_REBIND_ASSET, summary: "Rebind a live Work Mesh session to its current project", root: "live", shellCwd: "caller", shellRoot: "live", readsStdin: false, run: runWorkMeshLiveRebindFast },
|
|
14
|
+
];
|
|
15
|
+
export function registerCoreHookHelperNativeCommands(core) {
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
const command = core.command(entry.name)
|
|
18
|
+
.description(entry.summary)
|
|
19
|
+
.allowUnknownOption()
|
|
20
|
+
.allowExcessArguments()
|
|
21
|
+
.helpOption(false)
|
|
22
|
+
.argument("[args...]")
|
|
23
|
+
.action(async (args = [], _options, cmd) => {
|
|
24
|
+
const operands = cmd.args.length > 0 ? cmd.args : args;
|
|
25
|
+
const groupRoot = core.opts().hqRoot;
|
|
26
|
+
const root = entry.root === "live" ? resolveLiveRoot({ hqRoot: groupRoot }) : undefined;
|
|
27
|
+
if (!await coreNativeHookHelpersEnabled()) {
|
|
28
|
+
const result = runBundledScript({
|
|
29
|
+
asset: entry.asset,
|
|
30
|
+
args: operands,
|
|
31
|
+
cwd: entry.shellCwd === "live" ? root ?? process.cwd() : process.cwd(),
|
|
32
|
+
...(entry.shellRoot === "live" ? { hqRoot: root } : {}),
|
|
33
|
+
...(entry.name === "migrate-policy-triggers"
|
|
34
|
+
? { env: { CLAUDE_PROJECT_DIR: process.env.CLAUDE_PROJECT_DIR || process.cwd() } }
|
|
35
|
+
: {}),
|
|
36
|
+
});
|
|
37
|
+
if (result.code !== 0)
|
|
38
|
+
process.exitCode = result.code;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const readsStdin = entry.readsStdinFor?.(operands) ?? entry.readsStdin;
|
|
42
|
+
const stdin = readsStdin ? await readStdin() : "";
|
|
43
|
+
const code = await entry.run(operands, stdin, process.cwd(), root);
|
|
44
|
+
if (code !== 0)
|
|
45
|
+
process.exitCode = code;
|
|
46
|
+
});
|
|
47
|
+
describeCoreCommand(command, { execution: "native", asset: entry.asset, root: entry.root });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function readStdin() {
|
|
51
|
+
const chunks = [];
|
|
52
|
+
for await (const chunk of process.stdin)
|
|
53
|
+
chunks.push(chunk);
|
|
54
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=core-native-hook-helper-commands.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Default-off rollout gate for Wave 8 hook-time helper commands. */
|
|
2
|
+
export declare const CORE_NATIVE_HOOK_HELPERS_FLAG = "core-native-hook-helpers";
|
|
3
|
+
export declare function coreNativeHookHelpersEnabled(): Promise<boolean>;
|
|
4
|
+
//# sourceMappingURL=core-native-hook-helpers-flag.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Default-off rollout gate for Wave 8 hook-time helper commands. */
|
|
2
|
+
export const CORE_NATIVE_HOOK_HELPERS_FLAG = "core-native-hook-helpers";
|
|
3
|
+
export async function coreNativeHookHelpersEnabled() {
|
|
4
|
+
const { resolveProcessFlagGate } = await import("../lib/flag-registry.js");
|
|
5
|
+
return resolveProcessFlagGate(CORE_NATIVE_HOOK_HELPERS_FLAG, { fallback: false }, () => false);
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=core-native-hook-helpers-flag.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Native implementation of core/scripts/register-project.sh. */
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
export declare const REGISTER_PROJECT_ASSET = "core/scripts/register-project.sh";
|
|
5
|
+
export type RegisterProjectDeps = {
|
|
6
|
+
root: string;
|
|
7
|
+
cwd: string;
|
|
8
|
+
env: NodeJS.ProcessEnv;
|
|
9
|
+
fs: typeof fs;
|
|
10
|
+
now: () => Date;
|
|
11
|
+
runHq: (args: string[], options: {
|
|
12
|
+
cwd: string;
|
|
13
|
+
env: NodeJS.ProcessEnv;
|
|
14
|
+
capture: boolean;
|
|
15
|
+
}) => {
|
|
16
|
+
code: number;
|
|
17
|
+
stdout: string;
|
|
18
|
+
stderr: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export declare function createRegisterProjectHqRunner(spawn?: typeof spawnSync): RegisterProjectDeps["runHq"];
|
|
22
|
+
type NativeResult = {
|
|
23
|
+
code: number;
|
|
24
|
+
stdout: string;
|
|
25
|
+
stderr: string;
|
|
26
|
+
};
|
|
27
|
+
export declare function runRegisterProject(args: readonly string[], deps: RegisterProjectDeps): NativeResult;
|
|
28
|
+
export declare function runRegisterProjectFast(args: readonly string[], _stdin: string, cwd: string, root?: string): Promise<number>;
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=core-register-project-native.d.ts.map
|