@indigoai-us/hq-cli 5.310.0 → 5.311.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/jobs-validate.sh +1 -1
- package/dist/command-catalog.generated.d.ts +2 -2
- package/dist/command-catalog.generated.js +2 -2
- package/dist/commands/channels.d.ts +1 -9
- package/dist/commands/channels.js +2 -2
- package/dist/commands/company-transfer.d.ts +4 -28
- package/dist/commands/company-transfer.js +5 -49
- package/dist/commands/core-checkpoint-stop-gate-native.d.ts +34 -0
- package/dist/commands/core-checkpoint-stop-gate-native.js +713 -0
- package/dist/commands/core-checkpoint.d.ts +0 -24
- package/dist/commands/core-checkpoint.js +4 -4
- package/dist/commands/core-command-catalog.js +1 -1
- package/dist/commands/core-delegate-w1b.js +111 -14
- package/dist/commands/core-delegate.d.ts +9 -0
- package/dist/commands/core-delegate.js +44 -23
- package/dist/commands/core-hq-session-native.d.ts +39 -0
- package/dist/commands/core-hq-session-native.js +579 -0
- package/dist/commands/core-jobs-native.js +1 -4
- package/dist/commands/core-native-session-flag.d.ts +4 -0
- package/dist/commands/core-native-session-flag.js +7 -0
- package/dist/commands/core-native-worker-skills-flag.d.ts +4 -0
- package/dist/commands/core-native-worker-skills-flag.js +7 -0
- package/dist/commands/core-worker-lint-native.d.ts +4 -0
- package/dist/commands/core-worker-lint-native.js +160 -0
- package/dist/commands/core-worker-share-native.d.ts +5 -0
- package/dist/commands/core-worker-share-native.js +260 -0
- package/dist/commands/core-worker-skills-command.d.ts +8 -0
- package/dist/commands/core-worker-skills-command.js +33 -0
- package/dist/commands/core-worker-skills-runtime.d.ts +33 -0
- package/dist/commands/core-worker-skills-runtime.js +86 -0
- package/dist/commands/core.js +4 -2
- package/dist/commands/dm.d.ts +3 -7
- package/dist/commands/dm.js +3 -3
- package/dist/commands/integrations-core.d.ts +2 -0
- package/dist/commands/integrations-core.js +4 -0
- package/dist/commands/integrations-import.d.ts +1 -1
- package/dist/commands/integrations-import.js +34 -19
- package/dist/commands/integrations.js +2 -2
- package/dist/commands/lanes-accounts.js +3 -1
- package/dist/commands/lanes-message.js +84 -10
- package/dist/commands/lanes-shared.d.ts +4 -1
- package/dist/commands/lanes.d.ts +4 -1
- package/dist/commands/lanes.js +48 -7
- package/dist/commands/personal-connector-settings-flag.d.ts +4 -0
- package/dist/commands/personal-connector-settings-flag.js +7 -0
- package/dist/commands/report.d.ts +1 -26
- package/dist/commands/report.js +6 -6
- package/dist/fast-core.js +15 -2
- package/dist/fast-scaffold.js +25 -2
- package/dist/index.js +8 -0
- package/dist/lib/bot/continuity.d.ts +2 -1
- package/dist/lib/bot/job-completions.d.ts +0 -2
- package/dist/lib/bot/job-completions.js +2 -2
- package/dist/lib/bot/local-config.d.ts +0 -10
- package/dist/lib/bot/local-config.js +3 -9
- package/dist/lib/bot/owner-context.d.ts +2 -6
- package/dist/lib/bot/owner-context.js +3 -3
- package/dist/lib/bot/setup-explain.d.ts +0 -4
- package/dist/lib/bot/setup-explain.js +2 -2
- package/dist/lib/daemon/catalog.d.ts +3 -2
- package/dist/lib/daemon/hq-cloud.d.ts +2 -2
- package/dist/lib/daemon/hq-cloud.js +1 -1
- package/dist/lib/daemon/machine-type.d.ts +3 -4
- package/dist/lib/daemon/machine-type.js +2 -2
- package/dist/lib/daemon/remote-control.d.ts +0 -1
- package/dist/lib/daemon/remote-control.js +1 -1
- package/dist/lib/daemon/supervisor.d.ts +4 -9
- package/dist/lib/daemon/supervisor.js +5 -5
- package/dist/lib/lanes/admission-host.d.ts +3 -0
- package/dist/lib/lanes/admission-host.js +4 -0
- package/dist/lib/lanes/autopilot-registry.d.ts +62 -0
- package/dist/lib/lanes/autopilot-registry.js +192 -0
- package/dist/lib/lanes/autopilot.d.ts +2 -0
- package/dist/lib/lanes/autopilot.js +18 -5
- package/dist/lib/lanes/claude-balance.d.ts +6 -0
- package/dist/lib/lanes/claude-balance.js +17 -2
- package/dist/lib/lanes/keep-alive.d.ts +3 -0
- package/dist/lib/lanes/keep-alive.js +3 -0
- package/dist/lib/lanes/reconcile.js +14 -6
- package/dist/lib/lanes/spawn.js +2 -0
- package/dist/lib/mesh/live/daemon/credentials.d.ts +2 -17
- package/dist/lib/mesh/live/daemon/credentials.js +9 -9
- package/dist/lib/mesh/live/daemon/index.d.ts +5 -5
- package/dist/lib/mesh/live/daemon/index.js +2 -2
- package/dist/lib/mesh/live/daemon/state.d.ts +1 -1
- package/dist/lib/mesh/live/daemon/transcript-watch.d.ts +4 -11
- package/dist/lib/mesh/live/daemon/transcript-watch.js +5 -5
- package/dist/lib/work-context/company.d.ts +2 -25
- package/dist/lib/work-context/company.js +3 -3
- package/dist/lib/work-context/config.d.ts +3 -8
- package/dist/lib/work-context/config.js +2 -13
- package/dist/lib/work-context/state.d.ts +4 -13
- package/dist/lib/work-context/state.js +2 -30
- package/dist/utils/run-bundled-script.d.ts +6 -0
- package/dist/utils/run-bundled-script.js +14 -3
- package/package.json +1 -1
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/** Native port of lint-shared-worker-skills.sh. */
|
|
2
|
+
import * as crypto from "node:crypto";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { errorCode, missingOrUnreadable, resolveWorkerCallerRoot, } from "./core-worker-skills-runtime.js";
|
|
5
|
+
export const WORKER_SKILL_LINT_ASSET = "core/scripts/lint-shared-worker-skills.sh";
|
|
6
|
+
const DEFAULT_ROOTS = ["core/workers", "core/packages"];
|
|
7
|
+
function joinFindPath(parent, child) {
|
|
8
|
+
return `${parent}${parent.endsWith("/") ? "" : "/"}${child}`;
|
|
9
|
+
}
|
|
10
|
+
function matchesFindPredicate(displayPath) {
|
|
11
|
+
// GNU/BSD find's `-path '*/skills/*.md'` glob lets `*` span `/`, but still
|
|
12
|
+
// requires a slash before `skills/`. In particular `skills/x.md` does not
|
|
13
|
+
// match while `./skills/x.md` and `workers/a/skills/x.md` do.
|
|
14
|
+
if (!/.+\/skills\/.*\.md$/.test(displayPath))
|
|
15
|
+
return false;
|
|
16
|
+
const name = displayPath.slice(displayPath.lastIndexOf("/") + 1);
|
|
17
|
+
if (name.startsWith("._"))
|
|
18
|
+
return false;
|
|
19
|
+
// Mirrors find's `-not -path '*/.*/*'`: a hidden directory is excluded only
|
|
20
|
+
// when it has a slash before and after the dot-prefixed component.
|
|
21
|
+
return !/\/\.[^/]*\//.test(displayPath);
|
|
22
|
+
}
|
|
23
|
+
function collectEntries(rootArg, cwd, fileSystem) {
|
|
24
|
+
const entries = [];
|
|
25
|
+
const visit = (displayPath) => {
|
|
26
|
+
const absolutePath = path.resolve(cwd, displayPath);
|
|
27
|
+
let info;
|
|
28
|
+
try {
|
|
29
|
+
info = fileSystem.lstatSync(absolutePath);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (missingOrUnreadable(error))
|
|
33
|
+
return;
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
if (info.isDirectory()) {
|
|
37
|
+
let names;
|
|
38
|
+
try {
|
|
39
|
+
names = fileSystem.readdirSync(absolutePath);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (missingOrUnreadable(error))
|
|
43
|
+
return;
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
for (const name of names)
|
|
47
|
+
visit(joinFindPath(displayPath, name));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if ((info.isFile() || info.isSymbolicLink()) && matchesFindPredicate(displayPath)) {
|
|
51
|
+
entries.push({ displayPath, absolutePath, symlink: info.isSymbolicLink() });
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
visit(rootArg);
|
|
55
|
+
return entries;
|
|
56
|
+
}
|
|
57
|
+
function ownershipDomain(displayPath) {
|
|
58
|
+
const value = displayPath.startsWith("./") ? displayPath.slice(2) : displayPath;
|
|
59
|
+
if (value.startsWith("core/packages/")) {
|
|
60
|
+
const rest = value.slice("core/packages/".length);
|
|
61
|
+
return `core/packages/${rest.split("/", 1)[0]}`;
|
|
62
|
+
}
|
|
63
|
+
if (value.startsWith("companies/")) {
|
|
64
|
+
const rest = value.slice("companies/".length);
|
|
65
|
+
return `companies/${rest.split("/", 1)[0]}`;
|
|
66
|
+
}
|
|
67
|
+
if (value === "core/workers" || value.startsWith("core/workers/"))
|
|
68
|
+
return "core/workers";
|
|
69
|
+
return value.split("/", 1)[0] ?? "";
|
|
70
|
+
}
|
|
71
|
+
function readFileForHash(filePath, fileSystem) {
|
|
72
|
+
const content = fileSystem.readFileSync(filePath);
|
|
73
|
+
return Buffer.isBuffer(content) ? content : Buffer.from(content);
|
|
74
|
+
}
|
|
75
|
+
function brokenLink(filePath, fileSystem) {
|
|
76
|
+
try {
|
|
77
|
+
fileSystem.statSync(filePath);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (missingOrUnreadable(error))
|
|
82
|
+
return true;
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function sorted(entries) {
|
|
87
|
+
// The legacy command pipes `find` into `sort`; the project environment uses
|
|
88
|
+
// the C locale, so preserve its byte ordering instead of ICU collation.
|
|
89
|
+
return entries.sort((left, right) => left.displayPath < right.displayPath ? -1 : left.displayPath > right.displayPath ? 1 : 0);
|
|
90
|
+
}
|
|
91
|
+
function resultError(error) {
|
|
92
|
+
const code = errorCode(error);
|
|
93
|
+
if (code === "EACCES" || code === "EPERM") {
|
|
94
|
+
return { code: 1, stdout: "", stderr: `md5sum: failed to read file: ${error instanceof Error ? error.message : String(error)}\n` };
|
|
95
|
+
}
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
export function runWorkerSkillLint(args, dependencies) {
|
|
99
|
+
const { root, fs: fileSystem } = resolveWorkerCallerRoot(dependencies);
|
|
100
|
+
const roots = args.length > 0 ? args : DEFAULT_ROOTS;
|
|
101
|
+
const entries = sorted(roots.flatMap((entry) => collectEntries(entry, root, fileSystem)));
|
|
102
|
+
if (entries.length === 0) {
|
|
103
|
+
return {
|
|
104
|
+
code: 0,
|
|
105
|
+
stdout: `OK: worker-scoped skills are single-sourced (0 skill entries under: ${roots.join(" ")})\n`,
|
|
106
|
+
stderr: "",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const broken = [];
|
|
110
|
+
const hashGroups = new Map();
|
|
111
|
+
try {
|
|
112
|
+
for (const entry of entries) {
|
|
113
|
+
if (entry.symlink) {
|
|
114
|
+
if (brokenLink(entry.absolutePath, fileSystem))
|
|
115
|
+
broken.push(entry.displayPath);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const digest = crypto.createHash("md5").update(readFileForHash(entry.absolutePath, fileSystem)).digest("hex");
|
|
119
|
+
const domain = ownershipDomain(entry.displayPath);
|
|
120
|
+
const key = `${domain}|${digest}`;
|
|
121
|
+
const group = hashGroups.get(key);
|
|
122
|
+
if (group)
|
|
123
|
+
group.paths.push(entry.displayPath);
|
|
124
|
+
else
|
|
125
|
+
hashGroups.set(key, { domain, paths: [entry.displayPath] });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
return resultError(error);
|
|
130
|
+
}
|
|
131
|
+
const duplicates = [...hashGroups.values()].filter((group) => group.paths.length >= 2);
|
|
132
|
+
const findings = duplicates.length + broken.length;
|
|
133
|
+
if (findings === 0) {
|
|
134
|
+
return {
|
|
135
|
+
code: 0,
|
|
136
|
+
stdout: `OK: worker-scoped skills are single-sourced (${entries.length} skill entries scanned under: ${roots.join(" ")})\n`,
|
|
137
|
+
stderr: "",
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const lines = [];
|
|
141
|
+
if (duplicates.length > 0) {
|
|
142
|
+
lines.push("lint-shared-worker-skills: FAIL — duplicated worker skills (single-source these via _shared-skills/ + relative symlinks):");
|
|
143
|
+
}
|
|
144
|
+
for (const group of duplicates) {
|
|
145
|
+
lines.push(` DUPLICATE in domain '${group.domain}' (identical content, ${group.paths.length} copies — pick one canonical and symlink the rest):`);
|
|
146
|
+
// The shell prefixes the newline-joined group only once, so only the first
|
|
147
|
+
// path carries these spaces in the resulting stderr.
|
|
148
|
+
lines.push(` ${group.paths.join("\n")}`);
|
|
149
|
+
}
|
|
150
|
+
if (broken.length > 0) {
|
|
151
|
+
if (duplicates.length === 0)
|
|
152
|
+
lines.push("lint-shared-worker-skills: FAIL — broken shared-skill symlink(s):");
|
|
153
|
+
lines.push(" BROKEN (symlink target does not resolve):");
|
|
154
|
+
for (const filePath of broken)
|
|
155
|
+
lines.push(` ${filePath}`);
|
|
156
|
+
}
|
|
157
|
+
lines.push("", " Fix: keep ONE canonical file in the narrowest-scope _shared-skills/ store", " (core/workers/_shared-skills, core/packages/<pack>/workers/_shared-skills,", " or companies/<co>/workers/_shared-skills) and replace each duplicate with a", " relative symlink. Helper: hq core worker share", " Doc: core/knowledge/public/hq-core/shared-worker-skills.md");
|
|
158
|
+
return { code: 1, stdout: "", stderr: `${lines.join("\n")}\n` };
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=core-worker-lint-native.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type WorkerSkillDependencies, type WorkerSkillResult } from "./core-worker-skills-runtime.js";
|
|
2
|
+
export declare const WORKER_SKILL_SHARE_ASSET = "core/scripts/share-worker-skill.sh";
|
|
3
|
+
export declare const WORKER_SKILL_SHARE_USAGE = "usage: $0 [--apply] [--force] <scope-dir> <skill-name> <worker-skill-path>...\n";
|
|
4
|
+
export declare function runWorkerSkillShare(argv: string[], dependencies: WorkerSkillDependencies): WorkerSkillResult;
|
|
5
|
+
//# sourceMappingURL=core-worker-share-native.d.ts.map
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/** Native port of share-worker-skill.sh. */
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { resolveBundledAsset } from "../utils/hq-roots.js";
|
|
4
|
+
import { existsFollowingLinks, isDirectory, isSymlink, resolveWorkerCallerRoot, runGit, errorCode, } from "./core-worker-skills-runtime.js";
|
|
5
|
+
export const WORKER_SKILL_SHARE_ASSET = "core/scripts/share-worker-skill.sh";
|
|
6
|
+
export const WORKER_SKILL_SHARE_USAGE = "usage: $0 [--apply] [--force] <scope-dir> <skill-name> <worker-skill-path>...\n";
|
|
7
|
+
function error(message, code = 2) {
|
|
8
|
+
return { code, stdout: "", stderr: `${message}\n` };
|
|
9
|
+
}
|
|
10
|
+
function toolFailure(output, tool, target, caught, destination) {
|
|
11
|
+
const code = errorCode(caught);
|
|
12
|
+
const reason = code === "EACCES" || code === "EPERM" ? "Permission denied"
|
|
13
|
+
: code === "ENOENT" ? "No such file or directory"
|
|
14
|
+
: code === "ENOTDIR" ? "Not a directory"
|
|
15
|
+
: code === "EEXIST" ? "File exists"
|
|
16
|
+
: code === "ENOSPC" ? "No space left on device"
|
|
17
|
+
: code === "EXDEV" ? "Invalid cross-device link"
|
|
18
|
+
: caught instanceof Error ? caught.message : String(caught);
|
|
19
|
+
const message = tool === "mkdir" ? `mkdir: cannot create directory ‘${target}’: ${reason}`
|
|
20
|
+
: tool === "mv" ? `mv: cannot move '${target}' to '${destination ?? ""}': ${reason}`
|
|
21
|
+
: tool === "rm" ? `rm: cannot remove '${target}': ${reason}`
|
|
22
|
+
: `ln: failed to create symbolic link '${target}': ${reason}`;
|
|
23
|
+
return { code: 1, stdout: `${output.join("\n")}\n`, stderr: `${message}\n` };
|
|
24
|
+
}
|
|
25
|
+
function removeOneTrailingSlash(value) {
|
|
26
|
+
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
27
|
+
}
|
|
28
|
+
function stripOneDotSlash(value) {
|
|
29
|
+
return value.startsWith("./") ? value.slice(2) : value;
|
|
30
|
+
}
|
|
31
|
+
function isUnderScope(workerPath, scopeDir) {
|
|
32
|
+
const worker = stripOneDotSlash(workerPath);
|
|
33
|
+
const scope = stripOneDotSlash(scopeDir);
|
|
34
|
+
return worker.startsWith(`${scope}/`);
|
|
35
|
+
}
|
|
36
|
+
function relativePath(fromDirectory, toFile) {
|
|
37
|
+
const from = stripOneDotSlash(fromDirectory);
|
|
38
|
+
const to = stripOneDotSlash(toFile);
|
|
39
|
+
const fromParts = from.split("/");
|
|
40
|
+
const toParts = to.split("/");
|
|
41
|
+
let index = 0;
|
|
42
|
+
while (index < fromParts.length && index < toParts.length && fromParts[index] === toParts[index])
|
|
43
|
+
index += 1;
|
|
44
|
+
const up = "../".repeat(fromParts.length - index);
|
|
45
|
+
const down = toParts.slice(index).join("/");
|
|
46
|
+
return `${up}${down}`;
|
|
47
|
+
}
|
|
48
|
+
function isRegularFile(filePath, fileSystem) {
|
|
49
|
+
if (isSymlink(filePath, fileSystem))
|
|
50
|
+
return false;
|
|
51
|
+
try {
|
|
52
|
+
return fileSystem.statSync(filePath).isFile();
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function readLink(filePath, fileSystem) {
|
|
59
|
+
return fileSystem.readlinkSync(filePath);
|
|
60
|
+
}
|
|
61
|
+
function sameBytes(leftPath, rightPath, fileSystem) {
|
|
62
|
+
try {
|
|
63
|
+
const left = fileSystem.readFileSync(leftPath);
|
|
64
|
+
const right = fileSystem.readFileSync(rightPath);
|
|
65
|
+
return Buffer.from(left).equals(Buffer.from(right));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// The shell sends cmp's diagnostics to /dev/null and treats every nonzero
|
|
69
|
+
// result as a drifted copy.
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function unlinkIfPresent(filePath, fileSystem) {
|
|
74
|
+
try {
|
|
75
|
+
fileSystem.unlinkSync(filePath);
|
|
76
|
+
}
|
|
77
|
+
catch (caught) {
|
|
78
|
+
const code = caught && typeof caught === "object" && "code" in caught
|
|
79
|
+
? caught.code
|
|
80
|
+
: undefined;
|
|
81
|
+
if (code !== "ENOENT")
|
|
82
|
+
throw caught;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function forceSymlink(target, filePath, fileSystem) {
|
|
86
|
+
unlinkIfPresent(filePath, fileSystem);
|
|
87
|
+
fileSystem.symlinkSync(target, filePath);
|
|
88
|
+
}
|
|
89
|
+
function runtimeScriptPath(dependencies) {
|
|
90
|
+
return dependencies.scriptPath ?? resolveBundledAsset(WORKER_SKILL_SHARE_ASSET);
|
|
91
|
+
}
|
|
92
|
+
export function runWorkerSkillShare(argv, dependencies) {
|
|
93
|
+
let apply = false;
|
|
94
|
+
let force = false;
|
|
95
|
+
const args = [];
|
|
96
|
+
for (const value of argv) {
|
|
97
|
+
if (value === "--apply")
|
|
98
|
+
apply = true;
|
|
99
|
+
else if (value === "--force")
|
|
100
|
+
force = true;
|
|
101
|
+
else if (value.startsWith("-"))
|
|
102
|
+
return error(`unknown flag: ${value}`);
|
|
103
|
+
else
|
|
104
|
+
args.push(value);
|
|
105
|
+
}
|
|
106
|
+
if (args.length < 3) {
|
|
107
|
+
return error(WORKER_SKILL_SHARE_USAGE.replace("$0", runtimeScriptPath(dependencies)).trimEnd());
|
|
108
|
+
}
|
|
109
|
+
const { root, env, fs: fileSystem } = resolveWorkerCallerRoot(dependencies);
|
|
110
|
+
const scopeDir = removeOneTrailingSlash(args[0]);
|
|
111
|
+
const skillName = args[1];
|
|
112
|
+
const workerPaths = args.slice(2);
|
|
113
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(skillName)) {
|
|
114
|
+
return error(`error: skill-name '${skillName}' is not a bare skill name (no slashes, no .md)`);
|
|
115
|
+
}
|
|
116
|
+
if (scopeDir === "" || !isDirectory(path.resolve(root, scopeDir), fileSystem)) {
|
|
117
|
+
return error(`error: scope dir does not exist: ${scopeDir}`);
|
|
118
|
+
}
|
|
119
|
+
const canonical = `${scopeDir}/_shared-skills/${skillName}.md`;
|
|
120
|
+
const canonicalPath = path.resolve(root, canonical);
|
|
121
|
+
for (const originalPath of workerPaths) {
|
|
122
|
+
const workerPath = stripOneDotSlash(originalPath);
|
|
123
|
+
if (!isUnderScope(workerPath, scopeDir)) {
|
|
124
|
+
return error(`error: worker path '${workerPath}' is not under scope dir '${scopeDir}'\n canonical must live in the narrowest scope covering all sharers;\n cross-scope symlinks are disallowed. Aborting (no changes made).`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const output = [`Canonical: ${canonical}`, `Scope: ${scopeDir}`];
|
|
128
|
+
if (!apply)
|
|
129
|
+
output.push("(dry run — pass --apply to make changes)");
|
|
130
|
+
const note = (message) => { output.push(message); };
|
|
131
|
+
const would = (message) => {
|
|
132
|
+
note(apply ? ` did: ${message}` : ` would: ${message}`);
|
|
133
|
+
};
|
|
134
|
+
const canonicalIsReusable = existsFollowingLinks(canonicalPath, fileSystem) && !isSymlink(canonicalPath, fileSystem);
|
|
135
|
+
if (canonicalIsReusable) {
|
|
136
|
+
note("canonical already exists (regular file) — reusing it");
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
const seed = workerPaths.find((workerPath) => isRegularFile(path.resolve(root, stripOneDotSlash(workerPath)), fileSystem));
|
|
140
|
+
if (seed === undefined) {
|
|
141
|
+
return {
|
|
142
|
+
...error("error: no canonical exists and no worker path is a regular file to seed it from"),
|
|
143
|
+
stdout: `${output.join("\n")}\n`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
would(`mkdir -p ${scopeDir}/_shared-skills`);
|
|
147
|
+
would(`git mv ${seed} ${canonical} (promote first copy to canonical)`);
|
|
148
|
+
if (apply) {
|
|
149
|
+
try {
|
|
150
|
+
fileSystem.mkdirSync(path.resolve(root, `${scopeDir}/_shared-skills`), { recursive: true });
|
|
151
|
+
}
|
|
152
|
+
catch (caught) {
|
|
153
|
+
return toolFailure(output, "mkdir", `${scopeDir}/_shared-skills`, caught);
|
|
154
|
+
}
|
|
155
|
+
let moved;
|
|
156
|
+
try {
|
|
157
|
+
moved = runGit(["mv", seed, canonical], root, env, dependencies.runGit);
|
|
158
|
+
}
|
|
159
|
+
catch (caught) {
|
|
160
|
+
if (errorCode(caught) === undefined)
|
|
161
|
+
throw caught;
|
|
162
|
+
moved = { code: 127, stdout: "" };
|
|
163
|
+
}
|
|
164
|
+
if (moved.code !== 0) {
|
|
165
|
+
try {
|
|
166
|
+
fileSystem.renameSync(path.resolve(root, stripOneDotSlash(seed)), canonicalPath);
|
|
167
|
+
}
|
|
168
|
+
catch (caught) {
|
|
169
|
+
return toolFailure(output, "mv", seed, caught, canonical);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
for (const originalPath of workerPaths) {
|
|
175
|
+
const workerPath = stripOneDotSlash(originalPath);
|
|
176
|
+
const targetDir = path.posix.dirname(workerPath);
|
|
177
|
+
const relative = relativePath(targetDir, canonical);
|
|
178
|
+
const absoluteWorkerPath = path.resolve(root, workerPath);
|
|
179
|
+
if (isSymlink(absoluteWorkerPath, fileSystem)) {
|
|
180
|
+
const current = readLink(absoluteWorkerPath, fileSystem);
|
|
181
|
+
if (current === relative) {
|
|
182
|
+
note(`ok: ${workerPath} already links to canonical`);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
would(`relink ${workerPath} -> ${relative} (was: ${current})`);
|
|
186
|
+
if (apply) {
|
|
187
|
+
try {
|
|
188
|
+
forceSymlink(relative, absoluteWorkerPath, fileSystem);
|
|
189
|
+
}
|
|
190
|
+
catch (caught) {
|
|
191
|
+
return toolFailure(output, "ln", workerPath, caught);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (existsFollowingLinks(absoluteWorkerPath, fileSystem)) {
|
|
197
|
+
if (sameBytes(absoluteWorkerPath, canonicalPath, fileSystem)) {
|
|
198
|
+
would(`replace identical copy ${workerPath} with symlink -> ${relative}`);
|
|
199
|
+
if (apply) {
|
|
200
|
+
try {
|
|
201
|
+
unlinkIfPresent(absoluteWorkerPath, fileSystem);
|
|
202
|
+
}
|
|
203
|
+
catch (caught) {
|
|
204
|
+
return toolFailure(output, "rm", workerPath, caught);
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
fileSystem.symlinkSync(relative, absoluteWorkerPath);
|
|
208
|
+
}
|
|
209
|
+
catch (caught) {
|
|
210
|
+
return toolFailure(output, "ln", workerPath, caught);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else if (force) {
|
|
215
|
+
would(`FORCE replace DRIFTED copy ${workerPath} with symlink -> ${relative} (content discarded)`);
|
|
216
|
+
if (apply) {
|
|
217
|
+
try {
|
|
218
|
+
unlinkIfPresent(absoluteWorkerPath, fileSystem);
|
|
219
|
+
}
|
|
220
|
+
catch (caught) {
|
|
221
|
+
return toolFailure(output, "rm", workerPath, caught);
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
fileSystem.symlinkSync(relative, absoluteWorkerPath);
|
|
225
|
+
}
|
|
226
|
+
catch (caught) {
|
|
227
|
+
return toolFailure(output, "ln", workerPath, caught);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
return {
|
|
233
|
+
code: 3,
|
|
234
|
+
stdout: `${output.join("\n")}\n`,
|
|
235
|
+
stderr: ` DRIFT: ${workerPath} differs from canonical — reconcile by hand, then re-run\n (diff ${workerPath} ${canonical}), or pass --force to accept canonical.\n`,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
would(`create symlink ${workerPath} -> ${relative}`);
|
|
241
|
+
if (apply) {
|
|
242
|
+
try {
|
|
243
|
+
fileSystem.mkdirSync(path.resolve(root, targetDir), { recursive: true });
|
|
244
|
+
}
|
|
245
|
+
catch (caught) {
|
|
246
|
+
return toolFailure(output, "mkdir", targetDir, caught);
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
fileSystem.symlinkSync(relative, absoluteWorkerPath);
|
|
250
|
+
}
|
|
251
|
+
catch (caught) {
|
|
252
|
+
return toolFailure(output, "ln", workerPath, caught);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
output.push("done.");
|
|
258
|
+
return { code: 0, stdout: `${output.join("\n")}\n`, stderr: "" };
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=core-worker-share-native.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type WorkerSkillCommandName = "lint" | "share";
|
|
2
|
+
export type WorkerSkillCommandContext = {
|
|
3
|
+
cwd: string;
|
|
4
|
+
hqRoot?: string;
|
|
5
|
+
};
|
|
6
|
+
/** Keep the shell rollback route byte-transparent while the family flag is off. */
|
|
7
|
+
export declare function runWorkerSkillCommand(name: WorkerSkillCommandName, args: string[], context: WorkerSkillCommandContext): Promise<number>;
|
|
8
|
+
//# sourceMappingURL=core-worker-skills-command.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { runBundledScript } from "../utils/run-bundled-script.js";
|
|
2
|
+
import { coreNativeWorkerSkillsEnabled } from "./core-native-worker-skills-flag.js";
|
|
3
|
+
import { runWorkerSkillLint, WORKER_SKILL_LINT_ASSET } from "./core-worker-lint-native.js";
|
|
4
|
+
import { runWorkerSkillShare, WORKER_SKILL_SHARE_ASSET } from "./core-worker-share-native.js";
|
|
5
|
+
const assets = {
|
|
6
|
+
lint: WORKER_SKILL_LINT_ASSET,
|
|
7
|
+
share: WORKER_SKILL_SHARE_ASSET,
|
|
8
|
+
};
|
|
9
|
+
/** Keep the shell rollback route byte-transparent while the family flag is off. */
|
|
10
|
+
export async function runWorkerSkillCommand(name, args, context) {
|
|
11
|
+
if (!(await coreNativeWorkerSkillsEnabled())) {
|
|
12
|
+
const result = runBundledScript({
|
|
13
|
+
asset: assets[name],
|
|
14
|
+
args,
|
|
15
|
+
cwd: context.cwd,
|
|
16
|
+
hqRoot: context.hqRoot,
|
|
17
|
+
});
|
|
18
|
+
if (result.code !== 0)
|
|
19
|
+
process.exit(result.code);
|
|
20
|
+
return result.code;
|
|
21
|
+
}
|
|
22
|
+
const result = name === "lint"
|
|
23
|
+
? runWorkerSkillLint(args, { cwd: context.cwd })
|
|
24
|
+
: runWorkerSkillShare(args, { cwd: context.cwd });
|
|
25
|
+
if (result.stdout)
|
|
26
|
+
process.stdout.write(result.stdout);
|
|
27
|
+
if (result.stderr)
|
|
28
|
+
process.stderr.write(result.stderr);
|
|
29
|
+
if (result.code !== 0)
|
|
30
|
+
process.exit(result.code);
|
|
31
|
+
return result.code;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=core-worker-skills-command.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
export type WorkerSkillResult = {
|
|
3
|
+
code: number;
|
|
4
|
+
stdout: string;
|
|
5
|
+
stderr: string;
|
|
6
|
+
};
|
|
7
|
+
export type WorkerSkillFs = Pick<typeof fs, "lstatSync" | "statSync" | "readdirSync" | "readFileSync" | "readlinkSync" | "existsSync" | "mkdirSync" | "renameSync" | "unlinkSync" | "symlinkSync">;
|
|
8
|
+
export type GitResult = {
|
|
9
|
+
code: number;
|
|
10
|
+
stdout: string;
|
|
11
|
+
};
|
|
12
|
+
export type GitRunner = (args: string[], cwd: string, env: NodeJS.ProcessEnv) => GitResult;
|
|
13
|
+
export type WorkerSkillDependencies = {
|
|
14
|
+
cwd: string;
|
|
15
|
+
env?: NodeJS.ProcessEnv;
|
|
16
|
+
fs?: WorkerSkillFs;
|
|
17
|
+
runGit?: GitRunner;
|
|
18
|
+
/** The bundled shell's absolute $0, used for byte-identical usage text. */
|
|
19
|
+
scriptPath?: string;
|
|
20
|
+
};
|
|
21
|
+
export declare function runGit(args: string[], cwd: string, env: NodeJS.ProcessEnv, injected?: GitRunner): GitResult;
|
|
22
|
+
/** Match `git rev-parse --show-toplevel || pwd` without consulting HQ_ROOT. */
|
|
23
|
+
export declare function resolveWorkerCallerRoot(dependencies: WorkerSkillDependencies): {
|
|
24
|
+
root: string;
|
|
25
|
+
env: NodeJS.ProcessEnv;
|
|
26
|
+
fs: WorkerSkillFs;
|
|
27
|
+
};
|
|
28
|
+
export declare function errorCode(error: unknown): string | undefined;
|
|
29
|
+
export declare function missingOrUnreadable(error: unknown): boolean;
|
|
30
|
+
export declare function isDirectory(filePath: string, fileSystem: WorkerSkillFs): boolean;
|
|
31
|
+
export declare function isSymlink(filePath: string, fileSystem: WorkerSkillFs): boolean;
|
|
32
|
+
export declare function existsFollowingLinks(filePath: string, fileSystem: WorkerSkillFs): boolean;
|
|
33
|
+
//# sourceMappingURL=core-worker-skills-runtime.d.ts.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
const SYSTEM_FS = fs;
|
|
4
|
+
function processGit(args, cwd, env) {
|
|
5
|
+
// Tests that set this marker must inject a runner. This guard catches an
|
|
6
|
+
// accidental escape from a synthetic fixture before any real executable can
|
|
7
|
+
// be reached through PATH.
|
|
8
|
+
if (env.HQ_CORE_STUBS === "1") {
|
|
9
|
+
throw new Error("real git execution is disabled while HQ_CORE_STUBS=1");
|
|
10
|
+
}
|
|
11
|
+
const result = spawnSync("git", args, { cwd, env, encoding: "utf8" });
|
|
12
|
+
return {
|
|
13
|
+
code: result.status ?? 127,
|
|
14
|
+
stdout: result.stdout ?? "",
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function runGit(args, cwd, env, injected) {
|
|
18
|
+
return (injected ?? processGit)(args, cwd, env);
|
|
19
|
+
}
|
|
20
|
+
function shellPwd(cwd, env, fileSystem) {
|
|
21
|
+
const logical = env.PWD;
|
|
22
|
+
if (!logical || !logical.startsWith("/"))
|
|
23
|
+
return cwd;
|
|
24
|
+
try {
|
|
25
|
+
const logicalStat = fileSystem.statSync(logical);
|
|
26
|
+
const cwdStat = fileSystem.statSync(cwd);
|
|
27
|
+
if (logicalStat.dev === cwdStat.dev && logicalStat.ino === cwdStat.ino)
|
|
28
|
+
return logical;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Bash falls back to its physical cwd when PWD no longer names this inode.
|
|
32
|
+
}
|
|
33
|
+
return cwd;
|
|
34
|
+
}
|
|
35
|
+
/** Match `git rev-parse --show-toplevel || pwd` without consulting HQ_ROOT. */
|
|
36
|
+
export function resolveWorkerCallerRoot(dependencies) {
|
|
37
|
+
const env = dependencies.env ?? process.env;
|
|
38
|
+
const fileSystem = dependencies.fs ?? SYSTEM_FS;
|
|
39
|
+
const git = runGit(["rev-parse", "--show-toplevel"], dependencies.cwd, env, dependencies.runGit);
|
|
40
|
+
if (git.code === 0) {
|
|
41
|
+
// Command substitution removes every trailing LF and no other whitespace.
|
|
42
|
+
return { root: git.stdout.replace(/\n+$/, ""), env, fs: fileSystem };
|
|
43
|
+
}
|
|
44
|
+
return { root: shellPwd(dependencies.cwd, env, fileSystem), env, fs: fileSystem };
|
|
45
|
+
}
|
|
46
|
+
export function errorCode(error) {
|
|
47
|
+
if (!error || typeof error !== "object" || !("code" in error))
|
|
48
|
+
return undefined;
|
|
49
|
+
const code = error.code;
|
|
50
|
+
return typeof code === "string" ? code : undefined;
|
|
51
|
+
}
|
|
52
|
+
export function missingOrUnreadable(error) {
|
|
53
|
+
return ["ENOENT", "ENOTDIR", "EACCES", "EPERM", "ELOOP"].includes(errorCode(error) ?? "");
|
|
54
|
+
}
|
|
55
|
+
export function isDirectory(filePath, fileSystem) {
|
|
56
|
+
try {
|
|
57
|
+
return fileSystem.statSync(filePath).isDirectory();
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (missingOrUnreadable(error))
|
|
61
|
+
return false;
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function isSymlink(filePath, fileSystem) {
|
|
66
|
+
try {
|
|
67
|
+
return fileSystem.lstatSync(filePath).isSymbolicLink();
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (missingOrUnreadable(error))
|
|
71
|
+
return false;
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export function existsFollowingLinks(filePath, fileSystem) {
|
|
76
|
+
try {
|
|
77
|
+
fileSystem.statSync(filePath);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (missingOrUnreadable(error))
|
|
82
|
+
return false;
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=core-worker-skills-runtime.js.map
|
package/dist/commands/core.js
CHANGED
|
@@ -65,6 +65,7 @@ import { readSentryReportStdin, reportSentryEvent } from "../lib/core-utils/sent
|
|
|
65
65
|
import { CLEAN_HANDOFF_ASSET, RESUME_LOCK_ASSET, VERIFY_DELIVERABLES_ASSET, WORKTREE_GC_ASSET, runCleanWorktreeReconcileHandoff, runResumeThreadLock, runVerifyStoryDeliverables, runWorktreeGc, } from "../lib/core-utils/worktree-resume.js";
|
|
66
66
|
import { coreNativeHandoffEnabled } from "./core-native-handoff-flag.js";
|
|
67
67
|
import { coreCompanyIndexScopeEnabled } from "./core-company-index-scope-flag.js";
|
|
68
|
+
import { runWorkerSkillCommand } from "./core-worker-skills-command.js";
|
|
68
69
|
import { describeCoreCommand, registeredCoreCommandListing, } from "./core-command-catalog.js";
|
|
69
70
|
/**
|
|
70
71
|
* These commands are now native implementations. Their retained shell assets
|
|
@@ -357,6 +358,7 @@ export function registerCoreCommands(program) {
|
|
|
357
358
|
const core = program
|
|
358
359
|
.command("core", { hidden: true })
|
|
359
360
|
.description("HQ scaffold scripts hosted by the CLI (core; not a stable surface)")
|
|
361
|
+
.addHelpText("afterAll", "exact-company-directory-match-v1")
|
|
360
362
|
// The root override lives on the GROUP, not the subcommands, so it can never
|
|
361
363
|
// collide with a wrapped script's own flags — everything after the
|
|
362
364
|
// subcommand name is passed through untouched.
|
|
@@ -478,10 +480,10 @@ export function registerCoreCommands(program) {
|
|
|
478
480
|
.allowExcessArguments()
|
|
479
481
|
.helpOption(false)
|
|
480
482
|
.argument("[args...]", "arguments passed through to the script")
|
|
481
|
-
.action((args = [], _opts, cmd) => {
|
|
483
|
+
.action(async (args = [], _opts, cmd) => {
|
|
482
484
|
const scope = core.opts();
|
|
483
485
|
const operands = cmd.args.length > 0 ? cmd.args : args;
|
|
484
|
-
|
|
486
|
+
await runWorkerSkillCommand(entry.name, operands, resolveEntryRoot(entry, scope));
|
|
485
487
|
});
|
|
486
488
|
describeCoreCommand(command, {
|
|
487
489
|
execution: interpreterFor(entry.asset) === "node" ? "bundled-node" : "bundled-shell",
|
package/dist/commands/dm.d.ts
CHANGED
|
@@ -71,7 +71,7 @@ export interface ChannelMember {
|
|
|
71
71
|
handle?: string;
|
|
72
72
|
}
|
|
73
73
|
/** A structured mention as the POST messages route expects it. */
|
|
74
|
-
|
|
74
|
+
interface ChannelMention {
|
|
75
75
|
participantUid: string;
|
|
76
76
|
participantType: "agent" | "human";
|
|
77
77
|
displayName: string;
|
|
@@ -133,7 +133,7 @@ export declare function resolveMentions(tokens: string[], members: ChannelMember
|
|
|
133
133
|
* Returns null on anything that doesn't match. Pure → unit-testable.
|
|
134
134
|
*/
|
|
135
135
|
export declare function parseDuration(input: string): number | null;
|
|
136
|
-
|
|
136
|
+
type DmAudience = "human" | "agent" | "both";
|
|
137
137
|
export interface DmSendBody {
|
|
138
138
|
toEmail?: string;
|
|
139
139
|
toPersonUid?: string;
|
|
@@ -321,7 +321,6 @@ export interface CallerIdentity {
|
|
|
321
321
|
* Pure → unit-testable.
|
|
322
322
|
*/
|
|
323
323
|
export declare function callerFromToken(token: string): CallerIdentity;
|
|
324
|
-
export declare function isCallerMessage(m: ChannelMessageItem, me: CallerIdentity): boolean;
|
|
325
324
|
/**
|
|
326
325
|
* Newest-first channel history → the caller's most recent top-level eventId.
|
|
327
326
|
* Replies (those with rootEventId) are skipped. Pure → unit-testable.
|
|
@@ -366,9 +365,6 @@ export declare function formatChannelTranscript(messages: ChannelMessageItem[],
|
|
|
366
365
|
*/
|
|
367
366
|
export declare function agentCallerUid(token: string, machine?: () => boolean): string | null;
|
|
368
367
|
export declare const AGENT_THREAD_UNAVAILABLE: string;
|
|
369
|
-
/** One-line summary of an agent inbox item (DM, email, … — shapes vary by channel). Pure. */
|
|
370
|
-
export declare function formatAgentInboxItem(item: Record<string, unknown>): string;
|
|
371
|
-
/** Remove delegated execution context before agent inbox items can be emitted. */
|
|
372
|
-
export declare function redactAgentInboxItem<T extends object>(item: T): Omit<T, "actingContext">;
|
|
373
368
|
export declare function registerDmCommand(program: Command): void;
|
|
369
|
+
export {};
|
|
374
370
|
//# sourceMappingURL=dm.d.ts.map
|