@anhlp/alp 0.0.1-beta.4 → 0.0.1-beta.6
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/README.md +14 -412
- package/bin/alp.js +5 -0
- package/lib/launcher.js +136 -0
- package/package.json +18 -105
- package/dist/THIRD-PARTY-NOTICES.txt +0 -626
- package/dist/acp.js +0 -19936
- package/dist/alpd.js +0 -49562
- package/docs/modular-monorepo.md +0 -214
- package/packages/cli/README.md +0 -7
- package/packages/cli/dist/THIRD-PARTY-NOTICES.txt +0 -651
- package/packages/cli/dist/acp/agent.d.ts +0 -37
- package/packages/cli/dist/acp/agent.js +0 -489
- package/packages/cli/dist/acp/main.d.ts +0 -4
- package/packages/cli/dist/acp/main.js +0 -23
- package/packages/cli/dist/acp.js +0 -19936
- package/packages/cli/dist/alp-web/THIRD-PARTY-NOTICES.txt +0 -2590
- package/packages/cli/dist/alp-web/alp-boundary.json +0 -15
- package/packages/cli/dist/alp-web/apple-touch-icon.png +0 -0
- package/packages/cli/dist/alp-web/assets/TerminalPanel-DLuoa74B.css +0 -1
- package/packages/cli/dist/alp-web/assets/TerminalPanel-PrU2l3u5.js +0 -16
- package/packages/cli/dist/alp-web/assets/index-BJdBTDFX.css +0 -1
- package/packages/cli/dist/alp-web/assets/index-DpfiPv5s.js +0 -52
- package/packages/cli/dist/alp-web/favicon-attention.svg +0 -6
- package/packages/cli/dist/alp-web/favicon-running.svg +0 -6
- package/packages/cli/dist/alp-web/favicon.svg +0 -5
- package/packages/cli/dist/alp-web/index.html +0 -20
- package/packages/cli/dist/alp-web.js +0 -9640
- package/packages/cli/dist/alpd.js +0 -49562
- package/packages/cli/dist/backup.d.ts +0 -4
- package/packages/cli/dist/backup.js +0 -146
- package/packages/cli/dist/build-manifest.json +0 -240
- package/packages/cli/dist/cli.d.ts +0 -2
- package/packages/cli/dist/cli.js +0 -30
- package/packages/cli/dist/daemon.d.ts +0 -35
- package/packages/cli/dist/daemon.js +0 -126
- package/packages/cli/dist/index.d.ts +0 -1
- package/packages/cli/dist/index.js +0 -5
- package/packages/cli/dist/interfaces.d.ts +0 -24
- package/packages/cli/dist/interfaces.js +0 -156
- package/packages/cli/dist/library.d.ts +0 -140
- package/packages/cli/dist/library.js +0 -319
- package/packages/cli/dist/paths.d.ts +0 -3
- package/packages/cli/dist/paths.js +0 -18
- package/packages/cli/dist/projects.d.ts +0 -9
- package/packages/cli/dist/projects.js +0 -159
- package/packages/cli/dist/schedules.d.ts +0 -2
- package/packages/cli/dist/schedules.js +0 -45
- package/packages/cli/dist/sessions.d.ts +0 -43
- package/packages/cli/dist/sessions.js +0 -458
- package/packages/cli/dist/tasks.d.ts +0 -29
- package/packages/cli/dist/tasks.js +0 -345
- package/packages/cli/dist/terminal.d.ts +0 -8
- package/packages/cli/dist/terminal.js +0 -79
- package/packages/cli/dist/usage.d.ts +0 -3
- package/packages/cli/dist/usage.js +0 -78
- package/packages/cli/dist/web-service.d.ts +0 -38
- package/packages/cli/dist/web-service.js +0 -119
- package/packages/client/README.md +0 -29
- package/packages/client/dist/acp-probe.d.ts +0 -44
- package/packages/client/dist/acp-probe.js +0 -37
- package/packages/client/dist/antigravity-probe.d.ts +0 -34
- package/packages/client/dist/antigravity-probe.js +0 -95
- package/packages/client/dist/command.d.ts +0 -5
- package/packages/client/dist/command.js +0 -33
- package/packages/client/dist/doctor.d.ts +0 -13
- package/packages/client/dist/doctor.js +0 -587
- package/packages/client/dist/index.d.ts +0 -151
- package/packages/client/dist/index.js +0 -533
- package/packages/client/dist/integration-lock.d.ts +0 -35
- package/packages/client/dist/integration-lock.js +0 -179
- package/packages/client/dist/ipc.d.ts +0 -42
- package/packages/client/dist/ipc.js +0 -69
- package/packages/client/dist/library-test.d.ts +0 -94
- package/packages/client/dist/library-test.js +0 -37
- package/packages/client/dist/mcp-probe.d.ts +0 -24
- package/packages/client/dist/mcp-probe.js +0 -147
- package/packages/client/dist/portable.d.ts +0 -19
- package/packages/client/dist/portable.js +0 -33
- package/packages/client/dist/prompt.d.ts +0 -32
- package/packages/client/dist/prompt.js +0 -59
- package/packages/client/dist/render.d.ts +0 -6
- package/packages/client/dist/render.js +0 -120
- package/packages/client/dist/rpc/client.d.ts +0 -82
- package/packages/client/dist/rpc/client.js +0 -292
- package/packages/client/dist/rpc/errors.d.ts +0 -42
- package/packages/client/dist/rpc/errors.js +0 -34
- package/packages/client/dist/rpc/framing.d.ts +0 -21
- package/packages/client/dist/rpc/framing.js +0 -57
- package/packages/client/dist/rpc/handshake.d.ts +0 -19
- package/packages/client/dist/rpc/handshake.js +0 -8
- package/packages/client/dist/rpc/reconnect.d.ts +0 -97
- package/packages/client/dist/rpc/reconnect.js +0 -227
- package/packages/client/dist/rpc/registry.d.ts +0 -211
- package/packages/client/dist/rpc/registry.js +0 -145
- package/packages/client/dist/rpc/timers.d.ts +0 -11
- package/packages/client/dist/rpc/timers.js +0 -11
- package/packages/client/dist/service.d.ts +0 -50
- package/packages/client/dist/service.js +0 -143
- package/packages/client/dist/setup.d.ts +0 -2
- package/packages/client/dist/setup.js +0 -146
- package/packages/client/dist/supervise.d.ts +0 -14
- package/packages/client/dist/supervise.js +0 -87
- package/packages/client/dist/websocket.d.ts +0 -51
- package/packages/client/dist/websocket.js +0 -190
- package/packages/core/README.md +0 -7
- package/packages/core/dist/adapter.d.ts +0 -20
- package/packages/core/dist/adapter.js +0 -30
- package/packages/core/dist/atomic.d.ts +0 -2
- package/packages/core/dist/atomic.js +0 -28
- package/packages/core/dist/backup.d.ts +0 -27
- package/packages/core/dist/backup.js +0 -681
- package/packages/core/dist/beads.d.ts +0 -65
- package/packages/core/dist/beads.js +0 -213
- package/packages/core/dist/delegation.d.ts +0 -2
- package/packages/core/dist/delegation.js +0 -32
- package/packages/core/dist/errors.d.ts +0 -4
- package/packages/core/dist/errors.js +0 -11
- package/packages/core/dist/formulas.d.ts +0 -25
- package/packages/core/dist/formulas.js +0 -201
- package/packages/core/dist/hook-run.d.ts +0 -44
- package/packages/core/dist/hook-run.js +0 -60
- package/packages/core/dist/hooks.d.ts +0 -35
- package/packages/core/dist/hooks.js +0 -30
- package/packages/core/dist/index.d.ts +0 -31
- package/packages/core/dist/index.js +0 -65
- package/packages/core/dist/init.d.ts +0 -30
- package/packages/core/dist/init.js +0 -89
- package/packages/core/dist/library-edit.d.ts +0 -93
- package/packages/core/dist/library-edit.js +0 -368
- package/packages/core/dist/library.d.ts +0 -28
- package/packages/core/dist/library.js +0 -108
- package/packages/core/dist/log.d.ts +0 -37
- package/packages/core/dist/log.js +0 -156
- package/packages/core/dist/migrations.d.ts +0 -9
- package/packages/core/dist/migrations.js +0 -95
- package/packages/core/dist/models.d.ts +0 -11
- package/packages/core/dist/models.js +0 -44
- package/packages/core/dist/permissions.d.ts +0 -14
- package/packages/core/dist/permissions.js +0 -229
- package/packages/core/dist/profiles.d.ts +0 -27
- package/packages/core/dist/profiles.js +0 -122
- package/packages/core/dist/promptsafe.d.ts +0 -1
- package/packages/core/dist/promptsafe.js +0 -13
- package/packages/core/dist/providers.d.ts +0 -90
- package/packages/core/dist/providers.js +0 -101
- package/packages/core/dist/resolver.d.ts +0 -41
- package/packages/core/dist/resolver.js +0 -240
- package/packages/core/dist/safe-path.d.ts +0 -2
- package/packages/core/dist/safe-path.js +0 -24
- package/packages/core/dist/shell.d.ts +0 -16
- package/packages/core/dist/shell.js +0 -22
- package/packages/core/dist/state-schema.d.ts +0 -9
- package/packages/core/dist/state-schema.js +0 -54
- package/packages/core/dist/task-rows.d.ts +0 -47
- package/packages/core/dist/task-rows.js +0 -45
- package/packages/core/dist/tasks.d.ts +0 -128
- package/packages/core/dist/tasks.js +0 -835
- package/packages/core/dist/teams.d.ts +0 -80
- package/packages/core/dist/teams.js +0 -159
- package/packages/core/dist/template-path.d.ts +0 -2
- package/packages/core/dist/template-path.js +0 -10
- package/packages/core/dist/trust.d.ts +0 -8
- package/packages/core/dist/trust.js +0 -47
- package/packages/core/dist/types.d.ts +0 -51
- package/packages/core/dist/upgrade.d.ts +0 -10
- package/packages/core/dist/upgrade.js +0 -200
- package/packages/core/dist/user-settings.d.ts +0 -6
- package/packages/core/dist/user-settings.js +0 -72
- package/packages/core/dist/user-worktree.d.ts +0 -29
- package/packages/core/dist/user-worktree.js +0 -242
- package/packages/core/dist/validation.d.ts +0 -17
- package/packages/core/dist/validation.js +0 -296
- package/packages/core/dist/verify.d.ts +0 -15
- package/packages/core/dist/verify.js +0 -123
- package/packages/core/dist/workflow.d.ts +0 -45
- package/packages/core/dist/workflow.js +0 -33
- package/templates/ALP.md +0 -52
- package/templates/agents/lead/AGENT.md +0 -90
- package/templates/agents/main/AGENT.md +0 -150
- package/templates/agents/oracle/AGENT.md +0 -44
- package/templates/agents/peer/AGENT.md +0 -55
- package/templates/agents/reviewer/AGENT.md +0 -80
- package/templates/agents/supervisor/AGENT.md +0 -72
- package/templates/providers/antigravity.json +0 -6
- package/templates/providers/opencode.json +0 -7
- package/templates/role-skills.json +0 -8
- package/templates/skills/bug-loop/SKILL.md +0 -44
- package/templates/skills/bug-loop/references/test-proof.md +0 -25
- package/templates/skills/goal-griller/SKILL.md +0 -49
- package/templates/skills/prompt-leverage/SKILL.md +0 -37
- package/templates/skills/prompt-leverage/references/brief-template.md +0 -27
- package/templates/skills/sequence-execution-plan/SKILL.md +0 -42
- package/templates/skills/sequence-execution-plan/references/plan-template.md +0 -22
- package/templates/skills/smart-commits/SKILL.md +0 -40
- package/templates/skills/xia/SKILL.md +0 -42
- package/templates/teams/cafe/HOUSE_RULES.md +0 -1
- package/templates/teams/cafe/team.json +0 -15
- package/templates/teams/pho/HOUSE_RULES.md +0 -1
- package/templates/teams/pho/team.json +0 -14
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export function repositoryIdentity(directory: any): Promise<{
|
|
2
|
-
gitDir: string;
|
|
3
|
-
commonDir: string;
|
|
4
|
-
branch: string | null;
|
|
5
|
-
}>;
|
|
6
|
-
/** User workspace primitives. Assignment cleanup's force/rm semantics must never be used here. */
|
|
7
|
-
export function userWorktreeGit({ executable, timeoutMs, maxBytes }?: {
|
|
8
|
-
executable?: string | undefined;
|
|
9
|
-
timeoutMs?: number | undefined;
|
|
10
|
-
maxBytes?: number | undefined;
|
|
11
|
-
}): {
|
|
12
|
-
facts: (source: any) => Promise<{
|
|
13
|
-
commonDir: string;
|
|
14
|
-
gitDir: string;
|
|
15
|
-
head: any;
|
|
16
|
-
branch: any;
|
|
17
|
-
}>;
|
|
18
|
-
inspect: (source: any, target: any, branch: any) => Promise<any>;
|
|
19
|
-
assertClean: (target: any) => Promise<void>;
|
|
20
|
-
checkoutFacts(directory: any): Promise<{
|
|
21
|
-
isGit: true;
|
|
22
|
-
repoRoot: any;
|
|
23
|
-
currentBranch: any;
|
|
24
|
-
remoteUrl: any;
|
|
25
|
-
isDirty: boolean;
|
|
26
|
-
}>;
|
|
27
|
-
create(source: any, parent: any, name: any, branch: any, head: any): Promise<string>;
|
|
28
|
-
remove(source: any, target: any, branch: any): Promise<void>;
|
|
29
|
-
};
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
// packages/core/src/user-worktree.js
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import { lstat, realpath, open } from "node:fs/promises";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { constants } from "node:fs";
|
|
6
|
-
async function control(file) {
|
|
7
|
-
const handle = await open(file, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
8
|
-
try {
|
|
9
|
-
const info = await handle.stat();
|
|
10
|
-
if (!info.isFile() || info.nlink !== 1 || info.size > 4096) throw new Error("Invalid Git administrative file");
|
|
11
|
-
const bytes = Buffer.alloc(4097);
|
|
12
|
-
const { bytesRead } = await handle.read(bytes, 0, bytes.length, 0);
|
|
13
|
-
if (bytesRead > 4096) throw new Error("Git administrative file exceeds limit");
|
|
14
|
-
return bytes.subarray(0, bytesRead).toString("utf8").replace(/\r?\n$/, "");
|
|
15
|
-
} finally {
|
|
16
|
-
await handle.close();
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
async function repositoryIdentity(directory) {
|
|
20
|
-
if (await realpath(directory) !== directory) throw new Error("Repository root is not canonical");
|
|
21
|
-
const entry = path.join(directory, ".git");
|
|
22
|
-
const info = await lstat(entry);
|
|
23
|
-
let gitDir;
|
|
24
|
-
if (info.isDirectory()) gitDir = await realpath(entry);
|
|
25
|
-
else {
|
|
26
|
-
const value = await control(entry);
|
|
27
|
-
if (!value.startsWith("gitdir: ") || /[\r\n\0]/.test(value)) throw new Error("Invalid Git control link");
|
|
28
|
-
gitDir = await realpath(path.resolve(directory, value.slice(8)));
|
|
29
|
-
}
|
|
30
|
-
const commonEntry = path.join(gitDir, "commondir");
|
|
31
|
-
const hasCommon = await lstat(commonEntry).catch((error) => {
|
|
32
|
-
if (error.code === "ENOENT") return void 0;
|
|
33
|
-
throw error;
|
|
34
|
-
});
|
|
35
|
-
const commonDir = hasCommon ? await realpath(path.resolve(gitDir, await control(commonEntry))) : gitDir;
|
|
36
|
-
if (gitDir !== commonDir) {
|
|
37
|
-
if (path.dirname(gitDir) !== path.join(commonDir, "worktrees") || path.resolve(gitDir, await control(path.join(gitDir, "gitdir"))) !== entry)
|
|
38
|
-
throw new Error("Git worktree backlink changed");
|
|
39
|
-
}
|
|
40
|
-
const head = await control(path.join(gitDir, "HEAD"));
|
|
41
|
-
const branch = head.startsWith("ref: refs/heads/") ? head.slice(16) : null;
|
|
42
|
-
if (branch !== null && (!branch || /[\r\n\0]/.test(branch))) throw new Error("Invalid branch reference");
|
|
43
|
-
return { gitDir, commonDir, branch };
|
|
44
|
-
}
|
|
45
|
-
function userWorktreeGit({ executable = "/usr/bin/git", timeoutMs = 15e3, maxBytes = 2 * 1024 * 1024 } = {}) {
|
|
46
|
-
if (process.platform === "win32") throw new Error("User worktree operations are unavailable on this host");
|
|
47
|
-
if (!path.isAbsolute(executable)) throw new Error("Git executable must be host configured and absolute");
|
|
48
|
-
const disabled = [
|
|
49
|
-
"-c",
|
|
50
|
-
"core.hooksPath=/dev/null",
|
|
51
|
-
"-c",
|
|
52
|
-
"core.fsmonitor=false",
|
|
53
|
-
"-c",
|
|
54
|
-
"core.untrackedCache=false",
|
|
55
|
-
"-c",
|
|
56
|
-
"core.pager=cat",
|
|
57
|
-
"-c",
|
|
58
|
-
"maintenance.auto=false",
|
|
59
|
-
"-c",
|
|
60
|
-
"gc.auto=0",
|
|
61
|
-
"-c",
|
|
62
|
-
"protocol.allow=never",
|
|
63
|
-
"-c",
|
|
64
|
-
"submodule.recurse=false"
|
|
65
|
-
];
|
|
66
|
-
const env = {
|
|
67
|
-
PATH: "/usr/bin:/bin",
|
|
68
|
-
GIT_CONFIG_NOSYSTEM: "1",
|
|
69
|
-
GIT_CONFIG_GLOBAL: "/dev/null",
|
|
70
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
71
|
-
GIT_OPTIONAL_LOCKS: "0",
|
|
72
|
-
LANG: "C",
|
|
73
|
-
LC_ALL: "C"
|
|
74
|
-
};
|
|
75
|
-
async function raw(cwd, args, extra = []) {
|
|
76
|
-
return new Promise((resolve, reject) => {
|
|
77
|
-
const child = spawn(executable, [...disabled, ...extra, ...args], {
|
|
78
|
-
cwd,
|
|
79
|
-
env,
|
|
80
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
81
|
-
windowsHide: true,
|
|
82
|
-
detached: true
|
|
83
|
-
});
|
|
84
|
-
let size = 0;
|
|
85
|
-
let overflow = false;
|
|
86
|
-
const stdout = [];
|
|
87
|
-
const stderr = [];
|
|
88
|
-
const kill = () => {
|
|
89
|
-
try {
|
|
90
|
-
process.kill(-child.pid, "SIGKILL");
|
|
91
|
-
} catch {
|
|
92
|
-
child.kill("SIGKILL");
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
const timer = setTimeout(() => {
|
|
96
|
-
overflow = true;
|
|
97
|
-
kill();
|
|
98
|
-
}, timeoutMs);
|
|
99
|
-
const collect = (sink) => (chunk) => {
|
|
100
|
-
size += chunk.length;
|
|
101
|
-
if (size > maxBytes) {
|
|
102
|
-
overflow = true;
|
|
103
|
-
kill();
|
|
104
|
-
} else sink.push(chunk);
|
|
105
|
-
};
|
|
106
|
-
child.stdout.on("data", collect(stdout));
|
|
107
|
-
child.stderr.on("data", collect(stderr));
|
|
108
|
-
child.once("error", (error) => {
|
|
109
|
-
clearTimeout(timer);
|
|
110
|
-
reject(error);
|
|
111
|
-
});
|
|
112
|
-
child.once("close", (code) => {
|
|
113
|
-
clearTimeout(timer);
|
|
114
|
-
if (overflow) reject(new Error("Git operation exceeded its host limit"));
|
|
115
|
-
else
|
|
116
|
-
resolve({
|
|
117
|
-
code,
|
|
118
|
-
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
119
|
-
stderr: Buffer.concat(stderr).toString("utf8")
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
async function filterOverrides(cwd) {
|
|
125
|
-
const filters = await raw(cwd, [
|
|
126
|
-
"config",
|
|
127
|
-
"--null",
|
|
128
|
-
"--name-only",
|
|
129
|
-
"--get-regexp",
|
|
130
|
-
"^filter\\..*\\.(smudge|clean|process|required)$"
|
|
131
|
-
]);
|
|
132
|
-
if (filters.code !== 0 && filters.code !== 1) throw new Error("Cannot inspect repository filters");
|
|
133
|
-
const neutral = filters.stdout.split("\0").filter(Boolean).flatMap((key) => ["-c", `${key}=${key.endsWith(".required") ? "false" : ""}`]);
|
|
134
|
-
return neutral;
|
|
135
|
-
}
|
|
136
|
-
async function run(cwd, args, extra = []) {
|
|
137
|
-
return raw(cwd, args, [...await filterOverrides(cwd), ...extra]);
|
|
138
|
-
}
|
|
139
|
-
async function checked(cwd, args, extra = []) {
|
|
140
|
-
const result = await run(cwd, args, extra);
|
|
141
|
-
if (result.code !== 0) throw new Error("Git workspace operation refused");
|
|
142
|
-
return result.stdout.trim();
|
|
143
|
-
}
|
|
144
|
-
async function directory(value) {
|
|
145
|
-
const canonical = await realpath(value);
|
|
146
|
-
if (canonical !== value || !(await lstat(value)).isDirectory())
|
|
147
|
-
throw new Error("Workspace paths must be canonical directories");
|
|
148
|
-
return canonical;
|
|
149
|
-
}
|
|
150
|
-
async function facts(source) {
|
|
151
|
-
await directory(source);
|
|
152
|
-
const top = await checked(source, ["rev-parse", "--show-toplevel"]);
|
|
153
|
-
if (await realpath(top) !== source) throw new Error("Workspace must be a repository top level");
|
|
154
|
-
const commonDir = await realpath(
|
|
155
|
-
await checked(source, ["rev-parse", "--path-format=absolute", "--git-common-dir"])
|
|
156
|
-
);
|
|
157
|
-
const identity = await repositoryIdentity(source);
|
|
158
|
-
if (identity.commonDir !== commonDir) throw new Error("Repository identity changed");
|
|
159
|
-
const head = await checked(source, ["rev-parse", "--verify", "HEAD"]);
|
|
160
|
-
const branch = await run(source, ["symbolic-ref", "--quiet", "--short", "HEAD"]);
|
|
161
|
-
return { commonDir, gitDir: identity.gitDir, head, branch: branch.code === 0 ? branch.stdout.trim() : null };
|
|
162
|
-
}
|
|
163
|
-
async function list(source) {
|
|
164
|
-
const result = await checked(source, ["worktree", "list", "--porcelain", "-z"]);
|
|
165
|
-
return result.split("\0\0").filter(Boolean).map(
|
|
166
|
-
(block) => Object.fromEntries(
|
|
167
|
-
block.split("\0").filter(Boolean).map((line) => {
|
|
168
|
-
const i = line.indexOf(" ");
|
|
169
|
-
return i < 0 ? [line, true] : [line.slice(0, i), line.slice(i + 1)];
|
|
170
|
-
})
|
|
171
|
-
)
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
async function inspect(source, target, branch) {
|
|
175
|
-
const row = (await list(source)).find((row2) => row2.worktree === target);
|
|
176
|
-
if (!row) return void 0;
|
|
177
|
-
await directory(target);
|
|
178
|
-
if (row.branch !== `refs/heads/${branch}` || row.locked || row.prunable)
|
|
179
|
-
throw new Error("Managed worktree identity changed");
|
|
180
|
-
const origin = await facts(source);
|
|
181
|
-
const destination = await facts(target);
|
|
182
|
-
if (origin.commonDir !== destination.commonDir) throw new Error("Managed worktree repository changed");
|
|
183
|
-
return row;
|
|
184
|
-
}
|
|
185
|
-
async function assertClean(target) {
|
|
186
|
-
const flags = await checked(target, ["ls-files", "-v", "-z"]);
|
|
187
|
-
if (flags.split("\0").some((row) => row && (row[0] === "S" || /[a-z]/.test(row[0]))))
|
|
188
|
-
throw new Error("Worktree has hidden index state");
|
|
189
|
-
if ((await checked(target, ["ls-files", "--stage", "-z"])).split("\0").some((row) => row.startsWith("160000 ")))
|
|
190
|
-
throw new Error("Worktree has submodules");
|
|
191
|
-
if ((await checked(target, ["status", "--porcelain=v1", "--untracked-files=all", "--ignored"])).length)
|
|
192
|
-
throw new Error("Worktree contains uncommitted or ignored files");
|
|
193
|
-
}
|
|
194
|
-
return {
|
|
195
|
-
facts,
|
|
196
|
-
inspect,
|
|
197
|
-
assertClean,
|
|
198
|
-
async checkoutFacts(directory2) {
|
|
199
|
-
const canonical = await realpath(directory2);
|
|
200
|
-
const repoRoot = await checked(canonical, ["rev-parse", "--show-toplevel"]);
|
|
201
|
-
const branch = await run(canonical, ["symbolic-ref", "--quiet", "--short", "HEAD"]);
|
|
202
|
-
const remote = await run(canonical, ["config", "--get", "remote.origin.url"]);
|
|
203
|
-
const status = await checked(canonical, ["status", "--porcelain=v1", "--untracked-files=all"]);
|
|
204
|
-
return {
|
|
205
|
-
isGit: (
|
|
206
|
-
/** @type {const} */
|
|
207
|
-
true
|
|
208
|
-
),
|
|
209
|
-
repoRoot,
|
|
210
|
-
currentBranch: branch.code === 0 ? branch.stdout.trim() : null,
|
|
211
|
-
remoteUrl: remote.code === 0 ? remote.stdout.trim() || null : null,
|
|
212
|
-
isDirty: !!status
|
|
213
|
-
};
|
|
214
|
-
},
|
|
215
|
-
async create(source, parent, name, branch, head) {
|
|
216
|
-
await directory(parent);
|
|
217
|
-
if (!/^wks_[0-9a-f]{16}$/.test(name) || branch !== `alp/workspace/${name}` || !/^[0-9a-f]{40,64}$/.test(head))
|
|
218
|
-
throw new Error("Invalid generated worktree identity");
|
|
219
|
-
const target = path.join(parent, name);
|
|
220
|
-
if (await lstat(target).catch((error) => {
|
|
221
|
-
if (error.code === "ENOENT") return void 0;
|
|
222
|
-
throw error;
|
|
223
|
-
}))
|
|
224
|
-
throw new Error("Worktree destination already exists");
|
|
225
|
-
await checked(source, ["worktree", "add", "--quiet", "--no-checkout", "-b", branch, target, head]);
|
|
226
|
-
await checked(target, ["reset", "--hard", "--no-recurse-submodules", head]);
|
|
227
|
-
if (!await inspect(source, target, branch)) throw new Error("Worktree creation needs reconciliation");
|
|
228
|
-
return target;
|
|
229
|
-
},
|
|
230
|
-
async remove(source, target, branch) {
|
|
231
|
-
if (!await inspect(source, target, branch)) throw new Error("Managed worktree is missing");
|
|
232
|
-
await assertClean(target);
|
|
233
|
-
await checked(source, ["worktree", "remove", target], await filterOverrides(target));
|
|
234
|
-
if ((await list(source)).some((row) => row.worktree === target))
|
|
235
|
-
throw new Error("Worktree removal needs reconciliation");
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
export {
|
|
240
|
-
repositoryIdentity,
|
|
241
|
-
userWorktreeGit
|
|
242
|
-
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export const PROJECT_SETTINGS: string[];
|
|
2
|
-
export const USER_SETTINGS: string[];
|
|
3
|
-
export const RETIRED_SETTINGS: { project: Record<string, string>; user: Record<string, string> };
|
|
4
|
-
export function settingsKeys(settings: unknown, known: string[], retired?: Record<string, string>): { unknown: Array<{ key: string; suggestion?: string }>; retired: Array<{ key: string; instead: string }> };
|
|
5
|
-
export function settingsWarnings(settings: unknown, scope: 'project' | 'user'): string[];
|
|
6
|
-
export function validateSettings(settings: unknown, source: string): { provider?: string; model?: string; reasoning?: string };
|
|
7
|
-
export function validateUserSettings(settings: unknown, source: string): { language?: string; permissions?: unknown; limits?: { autoResume?: boolean }; recovery?: { autoResume?: boolean } };
|
|
8
|
-
export function normalizeMcp(raw: unknown, directory: string, source: string): { mcpServers: Record<string, Record<string, unknown>> };
|
|
9
|
-
export function validateResolvedAgent(agent: unknown): unknown;
|
|
10
|
-
export const AGENT_SETTINGS: string[];
|
|
11
|
-
export const CONTEXT_TOKENS: { min: number; max: number };
|
|
12
|
-
export function validateAgentConfig(config: unknown, source: string): { description?: string; provider?: string; model?: string; thinking?: string; mode?: 'read-only' | 'workspace-write' | 'full-access'; skills?: string[]; mcp?: string[]; hooks?: string[] };
|
|
13
|
-
export const HOOK_SETTINGS: string[];
|
|
14
|
-
export const HOOK_EVENTS: string[];
|
|
15
|
-
export const BLOCKING_HOOK_EVENTS: string[];
|
|
16
|
-
export function validateHook(config: unknown, source: string): { description?: string; event: string; command: string; blocking?: boolean; timeoutSec?: number; match?: { agent?: string; label?: string } };
|
|
17
|
-
export function unknownMessage(unknown: { key: string; suggestion?: string }, known: string[]): string;
|
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
// packages/core/src/validation.js
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { AlpError } from "./errors.js";
|
|
4
|
-
import { MODELS } from "./models.js";
|
|
5
|
-
import { validatePermissions } from "./permissions.js";
|
|
6
|
-
import { validateVerify } from "./verify.js";
|
|
7
|
-
var object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8
|
-
var nonempty = (value) => typeof value === "string" && value.trim().length > 0;
|
|
9
|
-
var stringMap = (value) => object(value) && Object.values(value).every((v) => typeof v === "string");
|
|
10
|
-
function requireValue(condition, code, source, message) {
|
|
11
|
-
if (!condition) throw new AlpError(code, `${source}: ${message}`);
|
|
12
|
-
}
|
|
13
|
-
var EFFORTS = ["none", "off", "low", "medium", "high", "xhigh", "max", "ultra", "ultracode"];
|
|
14
|
-
var PROJECT_SETTINGS = ["$schema", "defaultAgent", "workflow", "runtime", "permissions", "verify", "delegation", "team", "profile", "templateDefaults"];
|
|
15
|
-
var USER_SETTINGS = ["$schema", "language", "permissions", "limits", "recovery", "web"];
|
|
16
|
-
var RETIRED_SETTINGS = { project: {}, user: {} };
|
|
17
|
-
function distance(a, b) {
|
|
18
|
-
const row = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
19
|
-
for (let i = 1; i <= a.length; i++) {
|
|
20
|
-
let previous = row[0];
|
|
21
|
-
row[0] = i;
|
|
22
|
-
for (let j = 1; j <= b.length; j++) {
|
|
23
|
-
const kept = row[j];
|
|
24
|
-
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1].toLowerCase() === b[j - 1].toLowerCase() ? 0 : 1));
|
|
25
|
-
previous = kept;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return row[b.length];
|
|
29
|
-
}
|
|
30
|
-
function settingsKeys(settings, known, retired = {}) {
|
|
31
|
-
const unknown = [];
|
|
32
|
-
const old = [];
|
|
33
|
-
for (const key of Object.keys(object(settings) ? settings : {})) {
|
|
34
|
-
if (known.includes(key)) continue;
|
|
35
|
-
if (Object.hasOwn(retired, key)) {
|
|
36
|
-
old.push({ key, instead: retired[key] });
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
const near = known.filter((candidate) => !candidate.startsWith("$")).map((candidate) => ({ candidate, d: distance(key, candidate) })).sort((a, b) => a.d - b.d)[0];
|
|
40
|
-
unknown.push({ key, ...near && near.d <= 2 ? { suggestion: near.candidate } : {} });
|
|
41
|
-
}
|
|
42
|
-
return { unknown, retired: old };
|
|
43
|
-
}
|
|
44
|
-
var unknownMessage = (unknown, known) => `unknown setting '${unknown.key}'${unknown.suggestion ? `; did you mean '${unknown.suggestion}'?` : `; known settings are ${known.filter((key) => !key.startsWith("$")).join(", ")}`}`;
|
|
45
|
-
function settingsWarnings(settings, scope) {
|
|
46
|
-
return settingsKeys(settings, scope === "user" ? USER_SETTINGS : PROJECT_SETTINGS, RETIRED_SETTINGS[scope]).retired.map(({ key, instead }) => `setting '${key}' is no longer read; ${instead}`);
|
|
47
|
-
}
|
|
48
|
-
function validateSettings(settings, source) {
|
|
49
|
-
const check = (ok, message) => requireValue(ok, "INVALID_SETTINGS", source, message);
|
|
50
|
-
check(object(settings), "expected an object");
|
|
51
|
-
const { unknown } = settingsKeys(settings, PROJECT_SETTINGS, RETIRED_SETTINGS.project);
|
|
52
|
-
check(!unknown.length, unknown.length ? unknownMessage(unknown[0], PROJECT_SETTINGS) : "");
|
|
53
|
-
if (settings.defaultAgent !== void 0) check(nonempty(settings.defaultAgent), "defaultAgent must be a nonempty string");
|
|
54
|
-
if (settings.workflow !== void 0) {
|
|
55
|
-
check(object(settings.workflow), "workflow must be an object");
|
|
56
|
-
check(Object.keys(settings.workflow).every((key) => ["mode", "maxPeers", "supervisor"].includes(key)), "unsupported workflow field");
|
|
57
|
-
if (settings.workflow.mode !== void 0) check(typeof settings.workflow.mode === "string" && /^[\w.-]+$/.test(settings.workflow.mode) && settings.workflow.mode !== "custom", "workflow.mode must name a team, such as pho or cafe");
|
|
58
|
-
if (settings.workflow.maxPeers !== void 0) check(Number.isSafeInteger(settings.workflow.maxPeers) && settings.workflow.maxPeers > 0, "workflow.maxPeers must be a positive integer");
|
|
59
|
-
if (settings.workflow.supervisor !== void 0) check(typeof settings.workflow.supervisor === "boolean", "workflow.supervisor must be true or false");
|
|
60
|
-
}
|
|
61
|
-
if (settings.templateDefaults !== void 0) {
|
|
62
|
-
check(object(settings.templateDefaults), "templateDefaults must map teams to profile fingerprints");
|
|
63
|
-
for (const [team, agents] of Object.entries(settings.templateDefaults)) {
|
|
64
|
-
check(/^[\w.-]+$/.test(team) && object(agents), "templateDefaults must map teams to profile fingerprints");
|
|
65
|
-
for (const [agent, hash] of Object.entries(agents)) check(/^[\w.-]+$/.test(agent) && typeof hash === "string" && /^[a-f0-9]{64}$/.test(hash), "templateDefaults requires profile fingerprints");
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
for (const key of ["team", "profile"]) {
|
|
69
|
-
if (settings[key] === void 0) continue;
|
|
70
|
-
check(object(settings[key]), `${key} must map team names to agent configurations`);
|
|
71
|
-
const validateChoice = (config, where) => {
|
|
72
|
-
check(object(config), `${where} must be an object`);
|
|
73
|
-
check(Object.keys(config).every((key2) => ["provider", "model", "effort"].includes(key2)), `${where}: use provider, model and effort`);
|
|
74
|
-
if (config.provider !== void 0) check(["codex", "claude", "antigravity", "acp"].includes(config.provider), `${where}.provider must be codex, claude or antigravity`);
|
|
75
|
-
if (config.model !== void 0) {
|
|
76
|
-
check(nonempty(config.model) && !/\s/.test(config.model), `${where}.model must be a model ID`);
|
|
77
|
-
check(config.provider !== void 0 || /^(codex|claude|antigravity|acp):.+$/.test(config.model), `${where}: model needs a provider`);
|
|
78
|
-
if (config.model.includes(":")) check(/^(codex|claude|antigravity|acp):.+$/.test(config.model) && (!config.provider || config.model.startsWith(`${config.provider}:`)), `${where}: model and provider disagree`);
|
|
79
|
-
}
|
|
80
|
-
if (config.effort !== void 0) check(EFFORTS.includes(config.effort), `${where}.effort is unsupported`);
|
|
81
|
-
};
|
|
82
|
-
for (const [team, agents] of Object.entries(settings[key])) {
|
|
83
|
-
check(/^[\w.-]+$/.test(team) && object(agents), `${key}.${team} must map agent names to configurations`);
|
|
84
|
-
for (const [agent, config] of Object.entries(agents)) {
|
|
85
|
-
const where = `${key}.${team}.${agent}`;
|
|
86
|
-
check(object(config), `${where} must be an object`);
|
|
87
|
-
if (config.autocompact !== void 0) validateAutoCompact(config.autocompact, check, `${where}.autocompact`);
|
|
88
|
-
if (Object.hasOwn(config, "profiles")) {
|
|
89
|
-
check(Object.keys(config).every((key2) => ["profiles", "autocompact"].includes(key2)), `${where}: use profiles and autocompact`);
|
|
90
|
-
check(Array.isArray(config.profiles) && config.profiles.length > 0, `${where}.profiles must be a nonempty list`);
|
|
91
|
-
const models = [];
|
|
92
|
-
for (const [index, entry] of config.profiles.entries()) {
|
|
93
|
-
if (typeof entry === "string") {
|
|
94
|
-
const at = `${where}.profiles[${index}]`;
|
|
95
|
-
check(/^(codex|claude|antigravity):(\*|[^\s:*]+(:[a-z]+)?)$/.test(entry) || /^acp:[\w.-]+(?:\/[^\s:*]+)?$/.test(entry), `${at} must be "provider:model", "provider:model:effort" (such as "claude:claude-opus-5-5:high"), "acp:provider/model", or "codex:*" / "claude:*" for any model of that provider`);
|
|
96
|
-
const [provider, model, effort] = entry.split(":");
|
|
97
|
-
check(model !== "*" || agent !== "oracle" && agent !== "main", `${at}: ${agent} runs on a named model, not any model`);
|
|
98
|
-
if (effort !== void 0) {
|
|
99
|
-
const known = MODELS.find((candidate) => candidate.runtime === provider && candidate.id === model);
|
|
100
|
-
check(EFFORTS.includes(effort), `${at}: effort ${effort} is unsupported`);
|
|
101
|
-
check(!known?.efforts.length || known.efforts.includes(effort), `${at}: ${provider}:${model} takes effort ${known?.efforts.join(", ")}`);
|
|
102
|
-
}
|
|
103
|
-
models.push(`${provider}:${model}`);
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
validateChoice(entry, `${where}.profiles[${index}]`);
|
|
107
|
-
check(["claude", "codex", "antigravity"].includes(entry.provider) && nonempty(entry.model), `${where}.profiles[${index}]: specify provider and model`);
|
|
108
|
-
models.push(entry.model.includes(":") ? entry.model : `${entry.provider}:${entry.model}`);
|
|
109
|
-
}
|
|
110
|
-
check(new Set(models).size === models.length, `${where}.profiles must not repeat the same model`);
|
|
111
|
-
check(agent !== "main" || config.profiles.length === 1, `${where}.profiles: main runs on exactly one model`);
|
|
112
|
-
} else if (agent === "oracle") {
|
|
113
|
-
const entries = Object.entries(config).filter(([key2]) => key2 !== "autocompact");
|
|
114
|
-
check(entries.length > 0 && entries.every(([provider]) => ["claude", "codex"].includes(provider)), `${where} must define profiles or claude/codex lenses`);
|
|
115
|
-
for (const [provider, lens] of entries) {
|
|
116
|
-
validateChoice(lens, `${where}.${provider}`);
|
|
117
|
-
check(lens.provider === provider && nonempty(lens.model), `${where}.${provider}: specify matching provider and model`);
|
|
118
|
-
}
|
|
119
|
-
} else {
|
|
120
|
-
const { autocompact, ...legacy } = config;
|
|
121
|
-
validateChoice(legacy, where);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
validatePermissions(settings.permissions, source);
|
|
127
|
-
validateVerify(settings.verify, source);
|
|
128
|
-
const runtime = settings.runtime ?? {};
|
|
129
|
-
if (settings.runtime !== void 0) check(object(settings.runtime), "runtime must be an object");
|
|
130
|
-
for (const key of Object.keys(runtime)) {
|
|
131
|
-
check(["provider", "model", "reasoning"].includes(key), `unsupported runtime field '${key}'`);
|
|
132
|
-
check(nonempty(runtime[key]), `runtime.${key} must be a nonempty string`);
|
|
133
|
-
}
|
|
134
|
-
return { ...runtime };
|
|
135
|
-
}
|
|
136
|
-
function validateUserSettings(settings, source) {
|
|
137
|
-
const check = (ok, message) => requireValue(ok, "INVALID_SETTINGS", source, message);
|
|
138
|
-
check(object(settings), "expected an object");
|
|
139
|
-
const { unknown } = settingsKeys(settings, USER_SETTINGS, RETIRED_SETTINGS.user);
|
|
140
|
-
check(!unknown.length, unknown.length ? unknownMessage(unknown[0], USER_SETTINGS) : "");
|
|
141
|
-
for (const key of ["limits", "recovery"]) {
|
|
142
|
-
if (settings[key] === void 0) continue;
|
|
143
|
-
check(object(settings[key]), `${key} must be an object`);
|
|
144
|
-
const extra = Object.keys(settings[key]).find((field) => field !== "autoResume");
|
|
145
|
-
check(!extra, `unsupported ${key} field '${extra}'; use autoResume`);
|
|
146
|
-
if (settings[key].autoResume !== void 0) check(typeof settings[key].autoResume === "boolean", `${key}.autoResume must be true or false`);
|
|
147
|
-
}
|
|
148
|
-
if (settings.web !== void 0) {
|
|
149
|
-
check(object(settings.web), "web must be an object");
|
|
150
|
-
const extra = Object.keys(settings.web).find((field) => !["enabled", "port", "relay", "permissions"].includes(field));
|
|
151
|
-
check(!extra, `unsupported web field '${extra}'; use enabled, port, relay or permissions`);
|
|
152
|
-
if (settings.web.permissions !== void 0) check(Array.isArray(settings.web.permissions) && settings.web.permissions.every((entry) => typeof entry === "string" && /^[a-z][a-z.]*$/.test(entry)), "web.permissions must be a list of permission names, such as files.read");
|
|
153
|
-
const relay = settings.web.relay;
|
|
154
|
-
if (relay !== void 0) {
|
|
155
|
-
check(object(relay), "web.relay must be an object");
|
|
156
|
-
const unsupported = Object.keys(relay).find((field) => !["enabled", "endpoint", "useTls"].includes(field));
|
|
157
|
-
check(!unsupported, `unsupported web.relay field '${unsupported}'; use enabled, endpoint or useTls`);
|
|
158
|
-
if (relay.enabled !== void 0) check(typeof relay.enabled === "boolean", "web.relay.enabled must be true or false");
|
|
159
|
-
if (relay.endpoint !== void 0) check(typeof relay.endpoint === "string" && /^[a-z0-9.-]+:\d{1,5}$/i.test(relay.endpoint), "web.relay.endpoint must be host:port, such as relay.paseo.sh:443");
|
|
160
|
-
if (relay.useTls !== void 0) check(typeof relay.useTls === "boolean", "web.relay.useTls must be true or false");
|
|
161
|
-
}
|
|
162
|
-
if (settings.web.enabled !== void 0) check(typeof settings.web.enabled === "boolean", "web.enabled must be true or false");
|
|
163
|
-
if (settings.web.port !== void 0) check(Number.isInteger(settings.web.port) && settings.web.port >= 1024 && settings.web.port <= 65535, "web.port must be a port from 1024 to 65535");
|
|
164
|
-
}
|
|
165
|
-
if (settings.language !== void 0) check(nonempty(settings.language) && settings.language.trim().length <= 40, "language must be a language name of at most 40 characters, such as Vietnamese or English");
|
|
166
|
-
validatePermissions(settings.permissions, source);
|
|
167
|
-
return settings;
|
|
168
|
-
}
|
|
169
|
-
var AGENT_SETTINGS = ["$schema", "description", "provider", "model", "thinking", "mode", "context", "autocompact", "skills", "mcp", "hooks"];
|
|
170
|
-
var CONTEXT_TOKENS = { min: 1e5, max: 1e6 };
|
|
171
|
-
function validateAutoCompact(value, check, where) {
|
|
172
|
-
check(
|
|
173
|
-
value === "auto" || Number.isSafeInteger(value) && value >= CONTEXT_TOKENS.min && value <= CONTEXT_TOKENS.max,
|
|
174
|
-
`${where} must be "auto" or a number of tokens from ${CONTEXT_TOKENS.min} to ${CONTEXT_TOKENS.max}`
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
var AGENT_MODES = ["read-only", "workspace-write", "full-access"];
|
|
178
|
-
var entryName = (value) => typeof value === "string" && /^[\w.-]+$/.test(value) && value !== "." && value !== "..";
|
|
179
|
-
var HOOK_SETTINGS = ["$schema", "description", "event", "command", "blocking", "timeoutSec", "match"];
|
|
180
|
-
var HOOK_EVENTS = ["session.start", "turn.end", "assignment.start", "assignment.end", "handoff", "task.close", "merge"];
|
|
181
|
-
var BLOCKING_HOOK_EVENTS = ["handoff", "task.close", "merge"];
|
|
182
|
-
function validateHook(config, source) {
|
|
183
|
-
const check = (ok, message) => requireValue(ok, "INVALID_HOOK", source, message);
|
|
184
|
-
check(object(config), "expected an object");
|
|
185
|
-
const { unknown } = settingsKeys(config, HOOK_SETTINGS);
|
|
186
|
-
check(!unknown.length, unknown.length ? unknownMessage(unknown[0], HOOK_SETTINGS) : "");
|
|
187
|
-
if (config.description !== void 0) check(typeof config.description === "string", "description must be a string");
|
|
188
|
-
check(HOOK_EVENTS.includes(config.event), `event must be one of ${HOOK_EVENTS.join(", ")}`);
|
|
189
|
-
check(nonempty(config.command), "command must be a nonempty string");
|
|
190
|
-
if (config.blocking !== void 0) {
|
|
191
|
-
check(typeof config.blocking === "boolean", "blocking must be true or false");
|
|
192
|
-
check(!config.blocking || BLOCKING_HOOK_EVENTS.includes(config.event), `only ${BLOCKING_HOOK_EVENTS.join(", ")} hooks can block`);
|
|
193
|
-
}
|
|
194
|
-
if (config.timeoutSec !== void 0) check(Number.isInteger(config.timeoutSec) && config.timeoutSec >= 1 && config.timeoutSec <= 3600, "timeoutSec must be a whole number of seconds from 1 to 3600");
|
|
195
|
-
if (config.match !== void 0) {
|
|
196
|
-
check(object(config.match) && Object.keys(config.match).every((key) => ["agent", "label"].includes(key)), "match may hold only agent and label");
|
|
197
|
-
for (const key of ["agent", "label"]) if (config.match[key] !== void 0) check(nonempty(config.match[key]), `match.${key} must be a nonempty string`);
|
|
198
|
-
}
|
|
199
|
-
return config;
|
|
200
|
-
}
|
|
201
|
-
function validateAgentConfig(config, source) {
|
|
202
|
-
const check = (ok, message) => requireValue(ok, "INVALID_AGENT_CONFIG", source, message);
|
|
203
|
-
check(object(config), "expected an object");
|
|
204
|
-
const { unknown } = settingsKeys(config, AGENT_SETTINGS);
|
|
205
|
-
check(!unknown.length, unknown.length ? unknownMessage(unknown[0], AGENT_SETTINGS) : "");
|
|
206
|
-
if (config.description !== void 0) check(typeof config.description === "string", "description must be a string");
|
|
207
|
-
for (const key of ["provider", "model", "thinking"]) if (config[key] !== void 0) check(nonempty(config[key]), `${key} must be a nonempty string`);
|
|
208
|
-
if (config.mode !== void 0) check(AGENT_MODES.includes(config.mode), `mode must be one of ${AGENT_MODES.join(", ")}`);
|
|
209
|
-
if (config.context !== void 0) {
|
|
210
|
-
check(
|
|
211
|
-
config.context === "auto" || Number.isSafeInteger(config.context) && config.context >= CONTEXT_TOKENS.min && config.context <= CONTEXT_TOKENS.max,
|
|
212
|
-
`context must be "auto" or a number of tokens from ${CONTEXT_TOKENS.min} to ${CONTEXT_TOKENS.max}`
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
|
-
if (config.autocompact !== void 0) validateAutoCompact(config.autocompact, check, "autocompact");
|
|
216
|
-
for (const key of ["skills", "mcp", "hooks"]) {
|
|
217
|
-
if (config[key] === void 0) continue;
|
|
218
|
-
check(Array.isArray(config[key]) && config[key].every(entryName), `${key} must list names of letters, digits, '.', '_' or '-'`);
|
|
219
|
-
check(new Set(config[key]).size === config[key].length, `${key} lists a name twice`);
|
|
220
|
-
}
|
|
221
|
-
return config;
|
|
222
|
-
}
|
|
223
|
-
function normalizeMcp(raw, directory, source) {
|
|
224
|
-
const check = (ok, message) => requireValue(ok, "INVALID_MCP", source, message);
|
|
225
|
-
check(object(raw), "expected an object");
|
|
226
|
-
check(Object.keys(raw).every((k) => k === "mcpServers"), "only mcpServers is supported");
|
|
227
|
-
const servers = raw.mcpServers === void 0 ? {} : raw.mcpServers;
|
|
228
|
-
check(object(servers), "mcpServers must be an object");
|
|
229
|
-
const normalized = [];
|
|
230
|
-
for (const [name, server] of Object.entries(servers)) {
|
|
231
|
-
check(nonempty(name) && object(server), `invalid server '${name}'`);
|
|
232
|
-
check(Object.keys(server).every((k) => ["command", "args", "cwd", "env", "url", "headers"].includes(k)), `${name}: unsupported server field`);
|
|
233
|
-
check(nonempty(server.command) !== nonempty(server.url), `${name}: specify exactly one of command or url`);
|
|
234
|
-
const result = { ...server };
|
|
235
|
-
if (server.command !== void 0) check(nonempty(server.command), `${name}.command must be nonempty`);
|
|
236
|
-
if (server.url !== void 0) {
|
|
237
|
-
check(nonempty(server.url), `${name}.url must be nonempty`);
|
|
238
|
-
let url;
|
|
239
|
-
try {
|
|
240
|
-
url = new URL(server.url);
|
|
241
|
-
} catch {
|
|
242
|
-
}
|
|
243
|
-
check(url && ["https:", "http:"].includes(url.protocol), `${name}.url must be an HTTP(S) URL`);
|
|
244
|
-
check(server.args === void 0 && server.cwd === void 0 && server.env === void 0, `${name}: args/cwd/env require command`);
|
|
245
|
-
}
|
|
246
|
-
if (server.args !== void 0) check(Array.isArray(server.args) && server.args.every((v) => typeof v === "string"), `${name}.args must be strings`);
|
|
247
|
-
for (const key of ["env", "headers"]) if (server[key] !== void 0) check(stringMap(server[key]), `${name}.${key} must map strings to strings`);
|
|
248
|
-
if (server.command !== void 0) check(server.headers === void 0, `${name}: headers require url`);
|
|
249
|
-
if (server.cwd !== void 0) {
|
|
250
|
-
check(nonempty(server.cwd), `${name}.cwd must be nonempty`);
|
|
251
|
-
result.cwd = path.resolve(directory, server.cwd);
|
|
252
|
-
}
|
|
253
|
-
if (server.command && /[\\/]/.test(server.command)) result.command = path.resolve(directory, server.command);
|
|
254
|
-
normalized.push([name, result]);
|
|
255
|
-
}
|
|
256
|
-
return { mcpServers: Object.fromEntries(normalized) };
|
|
257
|
-
}
|
|
258
|
-
function validateResolvedAgent(agent) {
|
|
259
|
-
const check = (ok, message) => requireValue(ok, "INVALID_RESOLVED_AGENT", "ResolvedAgent", message);
|
|
260
|
-
check(object(agent), "expected an object");
|
|
261
|
-
check(nonempty(agent.name), "name is required");
|
|
262
|
-
check(typeof agent.projectRoot === "string" && path.isAbsolute(agent.projectRoot), "projectRoot must be absolute");
|
|
263
|
-
check(object(agent.instructions) && typeof agent.instructions.project === "string" && typeof agent.instructions.agent === "string", "instructions must contain project and agent strings");
|
|
264
|
-
for (const key of ["skills", "hooks"]) {
|
|
265
|
-
check(Array.isArray(agent[key]), `${key} must be an array`);
|
|
266
|
-
for (const resource of agent[key]) check(object(resource) && nonempty(resource.name) && typeof resource.path === "string" && path.isAbsolute(resource.path), `${key} must contain named absolute paths`);
|
|
267
|
-
}
|
|
268
|
-
check(object(agent.runtime), "runtime must be an object");
|
|
269
|
-
validateSettings({ runtime: agent.runtime }, "ResolvedAgent.runtime");
|
|
270
|
-
normalizeMcp(agent.mcp, agent.projectRoot, "ResolvedAgent.mcp");
|
|
271
|
-
check(object(agent.mcp.mcpServers), "MCP must contain a normalized mcpServers map");
|
|
272
|
-
for (const server of Object.values(agent.mcp.mcpServers)) {
|
|
273
|
-
if (server.cwd !== void 0) check(path.isAbsolute(server.cwd), "MCP cwd must already be absolute");
|
|
274
|
-
if (server.command && /[\\/]/.test(server.command)) check(path.isAbsolute(server.command), "MCP command paths must already be absolute");
|
|
275
|
-
}
|
|
276
|
-
return agent;
|
|
277
|
-
}
|
|
278
|
-
export {
|
|
279
|
-
AGENT_SETTINGS,
|
|
280
|
-
BLOCKING_HOOK_EVENTS,
|
|
281
|
-
CONTEXT_TOKENS,
|
|
282
|
-
HOOK_EVENTS,
|
|
283
|
-
HOOK_SETTINGS,
|
|
284
|
-
PROJECT_SETTINGS,
|
|
285
|
-
RETIRED_SETTINGS,
|
|
286
|
-
USER_SETTINGS,
|
|
287
|
-
normalizeMcp,
|
|
288
|
-
settingsKeys,
|
|
289
|
-
settingsWarnings,
|
|
290
|
-
unknownMessage,
|
|
291
|
-
validateAgentConfig,
|
|
292
|
-
validateHook,
|
|
293
|
-
validateResolvedAgent,
|
|
294
|
-
validateSettings,
|
|
295
|
-
validateUserSettings
|
|
296
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export const VERIFY_STEPS: readonly ['setup', 'typecheck', 'test'];
|
|
2
|
-
export const VERIFY_TIMEOUT_SEC: number;
|
|
3
|
-
export const VERIFY_GRACE_MS: number;
|
|
4
|
-
export const VERIFY_INFRA_EXIT: number;
|
|
5
|
-
|
|
6
|
-
export type VerifyConfig = { setup?: string; typecheck?: string; test?: string; timeoutSec: number; idleSec?: number };
|
|
7
|
-
|
|
8
|
-
export type VerifyCommand = { step: 'setup' | 'typecheck' | 'test'; command: string; exitCode: number; ms: number; output: string; timedOut?: boolean; idle?: boolean };
|
|
9
|
-
|
|
10
|
-
export type Verification = { passed: boolean; cwd: string; commands: VerifyCommand[]; skipped?: string };
|
|
11
|
-
|
|
12
|
-
export function validateVerify(verify: unknown, source: string): VerifyConfig | undefined;
|
|
13
|
-
export function verifyConfig(projectRoot: string): Promise<VerifyConfig | undefined>;
|
|
14
|
-
export function runVerify(cwd: string, config: VerifyConfig, options?: { env?: NodeJS.ProcessEnv }): Promise<Verification>;
|
|
15
|
-
export function describeVerification(verified: { passed: boolean; commands: Array<{ step: string; exitCode: number; timedOut?: boolean; idle?: boolean }>; skipped?: string }): string;
|