@mrciphersmith/keryx 0.3.40 → 0.3.42
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 +17 -6
- package/dist/cli.js +234 -95
- package/package.json +1 -1
- package/src/gdskills/contracts/subagent-dispatch.schema.json +1 -1
package/README.md
CHANGED
|
@@ -95,6 +95,13 @@ keryx auth login <provider> # subscription login (device code / OAuth) or API k
|
|
|
95
95
|
Inside a running session, `/provider` reopens the same add/reconfigure wizard
|
|
96
96
|
and `/connect` switches between providers you already configured.
|
|
97
97
|
|
|
98
|
+
ChatGPT / Codex model discovery reads the current stable Codex version from npm
|
|
99
|
+
metadata and caches it for one day. **Test** in `/connect` (or
|
|
100
|
+
`keryx providers test openai-codex`) always checks for a newer version before
|
|
101
|
+
listing models. This does not install Codex or poll npm in the background.
|
|
102
|
+
If npm is unavailable, a previously discovered version is reused; without a
|
|
103
|
+
cached version, discovery reports the problem instead of guessing a version.
|
|
104
|
+
|
|
98
105
|
Once you have more than one provider connected, `keryx routing` (and, inside a
|
|
99
106
|
session, `/routing`) maps a task category — `review`, `subagents`, and a
|
|
100
107
|
catalogue of others — to a specific model, so reviews and subagent spawns can
|
|
@@ -480,7 +487,7 @@ What is in it today:
|
|
|
480
487
|
bounded parallel scheduling, and an offline fleet report over a recorded event
|
|
481
488
|
log (`keryx agents monitor <events-file>`).
|
|
482
489
|
- **Vendor CLIs as child agents — off by default.** keryx can hand a bounded,
|
|
483
|
-
**read-only** task (or, for `claude-cli`
|
|
490
|
+
**read-only** task (or, for `claude-cli` and `codex-cli`, a reviewed write — see below)
|
|
484
491
|
to a coding CLI you already have installed (`codex exec`,
|
|
485
492
|
`claude -p`, or Google's Antigravity CLI, `agy -p --output-format
|
|
486
493
|
stream-json`) and host it as a child of the same harness: a disposable git
|
|
@@ -502,16 +509,20 @@ What is in it today:
|
|
|
502
509
|
- **A write from an external agent you review before it lands.**
|
|
503
510
|
`keryx agents external run claude-cli --task "…" --write` runs `claude` in a
|
|
504
511
|
throwaway worktree with only `Read Grep Glob Edit Write` (no shell, no network,
|
|
505
|
-
no MCP)
|
|
512
|
+
no MCP); `codex-cli --write` runs `codex` in the same kind of worktree under an
|
|
513
|
+
OS sandbox (as measured: writes outside the worktree fail, a DNS lookup fails, `.git` read-only)
|
|
514
|
+
that still leaves it a shell able to read files your account can read, so review
|
|
515
|
+
its output too. Its diff is captured, secret-redacted, hashed and stored as a pending
|
|
506
516
|
review — nothing reaches your checkout. `keryx agents external review <run-id>`
|
|
507
517
|
shows it; `apply <run-id>` needs a real terminal, shows the diff and asks you to
|
|
508
518
|
type the first 12 hex digits of the patch hash, then creates a NEW local branch
|
|
509
519
|
`external/<run-id>` with one commit. Your current branch and working tree are
|
|
510
520
|
never touched, nothing is pushed and no pull request is opened;
|
|
511
521
|
`discard <run-id>` drops it. There is no flag that skips the confirmation. Only
|
|
512
|
-
`claude-cli`
|
|
513
|
-
review is a human reading the diff (a model review is not built), and
|
|
514
|
-
|
|
522
|
+
`claude-cli` and `codex-cli` can write (`antigravity-cli` refuses `--write`), the
|
|
523
|
+
review is a human reading the diff (a model review is not built), and neither
|
|
524
|
+
has a long live history (`codex` was verified in a scratch repository on
|
|
525
|
+
`codex` 0.159.2; versions outside 0.159.2 up to 0.160.0 are refused). In the TUI: `/external-diff` and a
|
|
515
526
|
sidebar row while a diff waits. See the [external agent write
|
|
516
527
|
guide](docs/docs/guides/external-agent-write.md).
|
|
517
528
|
- **Completion you can audit.** The completion gate blocks on missing evidence: a
|
|
@@ -941,7 +952,7 @@ untouched.
|
|
|
941
952
|
| No bundled embedding runtime | No semantic ranking in memory search | Lexical memory search remains fully available |
|
|
942
953
|
| ripgrep is external | `keryx ctx rg` needs `rg` on `PATH` | Install ripgrep, or let the agent read files directly |
|
|
943
954
|
| Model commands need a credential | Four of the five commands above exit non-zero without one; `wiki enrich` exits `0` and marks the affected pages skipped | Everything else runs deterministically offline |
|
|
944
|
-
| External agents are read-only, except `claude-cli` write mode, which
|
|
955
|
+
| External agents are read-only, except `claude-cli` and `codex-cli` write mode, which have no long live history | A delegated CLI can read and search; only `claude-cli` and `codex-cli` can write, and only as a stored diff a human reviews (`keryx agents external review\|apply\|discard`) that lands as a new local branch — never on your checkout, never pushed. `claude` is confined by a tool allow-list (no shell, no network); `codex` by an OS sandbox that still leaves it a shell that can read files your user can read (it cannot send them, but they can show up in the run output), and is refused when older than 0.159.0 — live-verified in a scratch repository on `codex` 0.159.0. `antigravity-cli` refuses `--write`. A model review is not built. The parent gets the child's result and nothing before it — supervision of a running external child is not implemented. `claude` and `agy` read-only runs are live-verified | Use keryx's own child agents for work that must mutate the tree, or the [external agent write guide](docs/docs/guides/external-agent-write.md) for a reviewed `claude-cli` or `codex-cli` diff |
|
|
945
956
|
| A dispatched `flow-next` task is "done" by checks, not review | `task done` means normal end + a commit on the trigger branch + `keryx health gate` passing in the worktree; nobody has read the diff | Review and merge the `trigger/<flow>-<task>` branch yourself |
|
|
946
957
|
| `trust` dispatch needs Linux + a working bubblewrap | The hardened unattended sandbox (network off, home hidden, allow-listed env) is bwrap-only; without it — or on macOS — a `trust` dispatch refuses before starting | Use `permissionMode: "ask"` (read-only), or run triggers on a Linux host with bwrap |
|
|
947
958
|
| `dispatch.network: true` is the host's full network | The agent's commands then reach the internet and every host loopback service; the model call never needs it (it is made outside the sandbox) | Leave `network` off; review the `trigger/*` branch before installing or building it |
|
package/dist/cli.js
CHANGED
|
@@ -14119,7 +14119,7 @@ var package_default;
|
|
|
14119
14119
|
var init_package = __esm(() => {
|
|
14120
14120
|
package_default = {
|
|
14121
14121
|
name: "@mrciphersmith/keryx",
|
|
14122
|
-
version: "0.3.
|
|
14122
|
+
version: "0.3.42",
|
|
14123
14123
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
14124
14124
|
private: false,
|
|
14125
14125
|
publishConfig: {
|
|
@@ -24043,6 +24043,90 @@ var init_openai_subscription = __esm(() => {
|
|
|
24043
24043
|
};
|
|
24044
24044
|
});
|
|
24045
24045
|
|
|
24046
|
+
// src/lib/oauth/codex-catalog-version.ts
|
|
24047
|
+
import { join as join2 } from "path";
|
|
24048
|
+
function validVersion(value) {
|
|
24049
|
+
return typeof value === "string" && value.length <= 64 && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value);
|
|
24050
|
+
}
|
|
24051
|
+
function loadCache(file, now) {
|
|
24052
|
+
try {
|
|
24053
|
+
const content = readConfigFile(file);
|
|
24054
|
+
if (!content.ok)
|
|
24055
|
+
return;
|
|
24056
|
+
const cache = JSON.parse(content.text);
|
|
24057
|
+
if (typeof cache !== "object" || cache === null)
|
|
24058
|
+
return;
|
|
24059
|
+
const record = cache;
|
|
24060
|
+
if (!validVersion(record.version) || typeof record.fetchedAt !== "number" || !Number.isFinite(record.fetchedAt) || record.fetchedAt < 0 || record.fetchedAt > now)
|
|
24061
|
+
return;
|
|
24062
|
+
return { version: record.version, fetchedAt: record.fetchedAt };
|
|
24063
|
+
} catch {
|
|
24064
|
+
return;
|
|
24065
|
+
}
|
|
24066
|
+
}
|
|
24067
|
+
async function readMetadata(response) {
|
|
24068
|
+
const declared = response.headers.get("content-length");
|
|
24069
|
+
if (declared !== null && Number(declared) > RESPONSE_LIMIT_BYTES)
|
|
24070
|
+
return;
|
|
24071
|
+
if (response.body === null)
|
|
24072
|
+
return;
|
|
24073
|
+
const reader = response.body.getReader();
|
|
24074
|
+
const chunks = [];
|
|
24075
|
+
let size = 0;
|
|
24076
|
+
try {
|
|
24077
|
+
while (true) {
|
|
24078
|
+
const chunk = await reader.read();
|
|
24079
|
+
if (chunk.done)
|
|
24080
|
+
break;
|
|
24081
|
+
size += chunk.value.byteLength;
|
|
24082
|
+
if (size > RESPONSE_LIMIT_BYTES) {
|
|
24083
|
+
await reader.cancel().catch(() => {});
|
|
24084
|
+
return;
|
|
24085
|
+
}
|
|
24086
|
+
chunks.push(chunk.value);
|
|
24087
|
+
}
|
|
24088
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
24089
|
+
} finally {
|
|
24090
|
+
reader.releaseLock();
|
|
24091
|
+
}
|
|
24092
|
+
}
|
|
24093
|
+
async function resolveCodexCatalogVersion(fetchFn, opts = {}) {
|
|
24094
|
+
if (opts.signal?.aborted)
|
|
24095
|
+
return;
|
|
24096
|
+
const now = (opts.now ?? Date.now)();
|
|
24097
|
+
const file = join2(keryxConfigDir(opts.configDir), CACHE_FILE);
|
|
24098
|
+
const cached = loadCache(file, now);
|
|
24099
|
+
if (!opts.forceRefresh && cached !== undefined && now - cached.fetchedAt < CODEX_VERSION_CACHE_TTL_MS)
|
|
24100
|
+
return cached.version;
|
|
24101
|
+
try {
|
|
24102
|
+
const timeout = AbortSignal.timeout(2000);
|
|
24103
|
+
const response = await fetchFn(CODEX_VERSION_REGISTRY_URL, {
|
|
24104
|
+
headers: { Accept: "application/json" },
|
|
24105
|
+
credentials: "omit",
|
|
24106
|
+
redirect: "error",
|
|
24107
|
+
signal: opts.signal === undefined ? timeout : AbortSignal.any([opts.signal, timeout])
|
|
24108
|
+
});
|
|
24109
|
+
if (response.ok) {
|
|
24110
|
+
const body = await readMetadata(response);
|
|
24111
|
+
if (typeof body === "object" && body !== null && "name" in body && body.name === "@openai/codex" && "version" in body && validVersion(body.version) && !opts.signal?.aborted) {
|
|
24112
|
+
const version = body.version;
|
|
24113
|
+
try {
|
|
24114
|
+
ensureKeryxConfigDir(opts.configDir);
|
|
24115
|
+
writeOwnerOnlyFileAtomic(file, JSON.stringify({ version, fetchedAt: now }));
|
|
24116
|
+
} catch {}
|
|
24117
|
+
return version;
|
|
24118
|
+
}
|
|
24119
|
+
}
|
|
24120
|
+
} catch {}
|
|
24121
|
+
return opts.signal?.aborted ? undefined : cached?.version;
|
|
24122
|
+
}
|
|
24123
|
+
var CODEX_VERSION_REGISTRY_URL = "https://registry.npmjs.org/@openai%2Fcodex/latest", CODEX_VERSION_CACHE_TTL_MS, CACHE_FILE = "codex-catalog-version.json", RESPONSE_LIMIT_BYTES;
|
|
24124
|
+
var init_codex_catalog_version = __esm(() => {
|
|
24125
|
+
init_config_dir();
|
|
24126
|
+
CODEX_VERSION_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
24127
|
+
RESPONSE_LIMIT_BYTES = 64 * 1024;
|
|
24128
|
+
});
|
|
24129
|
+
|
|
24046
24130
|
// src/commands/subscription-models.ts
|
|
24047
24131
|
async function fetchOpenAiCodexModels(fetchFn, opts = {}) {
|
|
24048
24132
|
const controller = new AbortController;
|
|
@@ -24051,7 +24135,17 @@ async function fetchOpenAiCodexModels(fetchFn, opts = {}) {
|
|
|
24051
24135
|
try {
|
|
24052
24136
|
const grant = await ensureOpenAiCodexGrant({ fetch: fetchFn, signal: controller.signal, ...opts.configDir !== undefined ? { configDir: opts.configDir } : {} });
|
|
24053
24137
|
authenticated = true;
|
|
24054
|
-
const
|
|
24138
|
+
const clientVersion = await resolveCodexCatalogVersion(fetchFn, {
|
|
24139
|
+
signal: controller.signal,
|
|
24140
|
+
...opts.refreshCatalogVersion !== undefined ? { forceRefresh: opts.refreshCatalogVersion } : {},
|
|
24141
|
+
...opts.configDir !== undefined ? { configDir: opts.configDir } : {}
|
|
24142
|
+
});
|
|
24143
|
+
if (clientVersion === undefined)
|
|
24144
|
+
return { models: [], source: "fallback", failure: {
|
|
24145
|
+
kind: "unreachable",
|
|
24146
|
+
detail: "could not discover the Codex catalog version from npm; no cached version is available"
|
|
24147
|
+
} };
|
|
24148
|
+
const response = await fetchFn(`${OPENAI_CODEX_PICKER.baseUrl}/models?client_version=${clientVersion}`, {
|
|
24055
24149
|
headers: { Authorization: `Bearer ${grant.access}`, "ChatGPT-Account-ID": grant.accountId, originator: "keryx", Accept: "application/json" },
|
|
24056
24150
|
signal: controller.signal,
|
|
24057
24151
|
redirect: "error"
|
|
@@ -24075,9 +24169,10 @@ async function fetchOpenAiCodexModels(fetchFn, opts = {}) {
|
|
|
24075
24169
|
clearTimeout(timer);
|
|
24076
24170
|
}
|
|
24077
24171
|
}
|
|
24078
|
-
var OPENAI_CODEX_PICKER
|
|
24172
|
+
var OPENAI_CODEX_PICKER;
|
|
24079
24173
|
var init_subscription_models = __esm(() => {
|
|
24080
24174
|
init_openai_subscription();
|
|
24175
|
+
init_codex_catalog_version();
|
|
24081
24176
|
OPENAI_CODEX_PICKER = {
|
|
24082
24177
|
name: "openai-codex",
|
|
24083
24178
|
label: "ChatGPT / Codex",
|
|
@@ -26678,7 +26773,10 @@ function providerApiKey(provider, env = process.env) {
|
|
|
26678
26773
|
}
|
|
26679
26774
|
async function testProviderConnection(provider, fetchFn = globalThis.fetch, env = process.env, profiles) {
|
|
26680
26775
|
if (provider.name === "openai-codex")
|
|
26681
|
-
return fetchOpenAiCodexModels(fetchFn, {
|
|
26776
|
+
return fetchOpenAiCodexModels(fetchFn, {
|
|
26777
|
+
refreshCatalogVersion: true,
|
|
26778
|
+
...profiles?.dir !== undefined ? { configDir: profiles.dir } : {}
|
|
26779
|
+
});
|
|
26682
26780
|
const apiKey = providerApiKey(provider, envWithSavedApiKeys(env, profiles?.dir)) ?? provider.apiKey;
|
|
26683
26781
|
return fetchOpenAiCompatModelsDetailed(fetchFn, provider, apiKey, {
|
|
26684
26782
|
timeoutMs: MODELS_FETCH_TIMEOUT_MS,
|
|
@@ -31306,7 +31404,7 @@ var init_wiki_freshness = () => {};
|
|
|
31306
31404
|
|
|
31307
31405
|
// src/harness/tool/builtin/interactive-tools.ts
|
|
31308
31406
|
import { readdir as readdir9 } from "fs/promises";
|
|
31309
|
-
import { basename, dirname, isAbsolute, join as
|
|
31407
|
+
import { basename, dirname, isAbsolute, join as join3, relative, resolve as resolve2, sep } from "path";
|
|
31310
31408
|
import { lstatSync as lstatSync5, realpathSync as realpathSync3 } from "fs";
|
|
31311
31409
|
async function readFromLine(file, startLine, cap) {
|
|
31312
31410
|
const reader = file.stream().getReader();
|
|
@@ -31391,7 +31489,7 @@ function realpathThroughAncestors(absolute) {
|
|
|
31391
31489
|
for (;; ) {
|
|
31392
31490
|
try {
|
|
31393
31491
|
const real = realpathSync3(current);
|
|
31394
|
-
return missing.length === 0 ? real :
|
|
31492
|
+
return missing.length === 0 ? real : join3(real, ...missing.reverse());
|
|
31395
31493
|
} catch {
|
|
31396
31494
|
try {
|
|
31397
31495
|
lstatSync5(current);
|
|
@@ -51270,13 +51368,15 @@ permission bridge.
|
|
|
51270
51368
|
A line-stream agent (\`claude-cli\`, \`codex-cli\`) is a DIFFERENT thing:
|
|
51271
51369
|
\`run\` refuses it outright ("speaks the one-way line stream. Delegate to it
|
|
51272
51370
|
from \`keryx shell\` with /delegate"). There is no ACP permission bridge and
|
|
51273
|
-
no patch capture for it
|
|
51274
|
-
ACP transport
|
|
51275
|
-
|
|
51276
|
-
|
|
51277
|
-
\`runtime\` block, and
|
|
51278
|
-
|
|
51279
|
-
|
|
51371
|
+
no patch capture for it in those paths \u2014 \`worktree-write\` is implemented for
|
|
51372
|
+
the ACP transport, and for \`claude-cli\` and \`codex-cli\` only through the
|
|
51373
|
+
operator's own \`keryx agents external run <id> --write\`, which captures the
|
|
51374
|
+
diff for human review. A dispatch that asks for \`worktree-write\` on a
|
|
51375
|
+
line-stream agent anywhere else (the internal \`runtime\` block, and
|
|
51376
|
+
\`keryx shell\`'s /delegate) is REFUSED fail-closed with a distinguishable
|
|
51377
|
+
\`not-implemented\` reason \u2014 never silently accepted and its writes
|
|
51378
|
+
discarded. There it stays \`read-only\`, contained by the same disposable
|
|
51379
|
+
worktree as every external run.
|
|
51280
51380
|
|
|
51281
51381
|
See \`docs/docs/guides/acp-client.md\` for the full ACP contract, including the
|
|
51282
51382
|
output size bounds (a stderr-only flood and an output flood both end the run
|
|
@@ -52592,7 +52692,7 @@ var init_github = __esm(() => {
|
|
|
52592
52692
|
|
|
52593
52693
|
// src/harness/tool/metaproject-adapter.ts
|
|
52594
52694
|
import { lstat as lstat8, readdir as readdir21, readFile as readFile60, realpath as realpath11, stat as stat15 } from "fs/promises";
|
|
52595
|
-
import { isAbsolute as isAbsolute2, join as
|
|
52695
|
+
import { isAbsolute as isAbsolute2, join as join4, relative as relative2, resolve as resolve3 } from "path";
|
|
52596
52696
|
function isMissingExecutable(cause) {
|
|
52597
52697
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
52598
52698
|
return /Executable not found|\bENOENT\b|not found in \$?PATH/i.test(message);
|
|
@@ -52633,7 +52733,7 @@ function portableMemoryPath(candidate) {
|
|
|
52633
52733
|
return !isAbsolute2(normalized) && normalized !== ".." && !normalized.startsWith("../") && !normalized.includes("/../") ? normalized : null;
|
|
52634
52734
|
}
|
|
52635
52735
|
function confineToWiki(cwd, candidate) {
|
|
52636
|
-
const wikiRoot =
|
|
52736
|
+
const wikiRoot = join4(cwd, ".metaproject", "wiki");
|
|
52637
52737
|
const target = resolve3(wikiRoot, candidate);
|
|
52638
52738
|
const rel = relative2(wikiRoot, target);
|
|
52639
52739
|
if (rel === "") {
|
|
@@ -52676,7 +52776,7 @@ async function projectRemovalTrail(cwd, query) {
|
|
|
52676
52776
|
async function detectMemoryStoreUnreadable(cwd) {
|
|
52677
52777
|
const root = memoryRoot(cwd);
|
|
52678
52778
|
for (const { folder } of MEMORY_TYPES) {
|
|
52679
|
-
const dir =
|
|
52779
|
+
const dir = join4(root, folder);
|
|
52680
52780
|
try {
|
|
52681
52781
|
await readdir21(dir);
|
|
52682
52782
|
} catch (error) {
|
|
@@ -52690,7 +52790,7 @@ async function detectMemoryStoreUnreadable(cwd) {
|
|
|
52690
52790
|
return null;
|
|
52691
52791
|
}
|
|
52692
52792
|
function confineToSkills(cwd, candidate) {
|
|
52693
|
-
const skillsRoot =
|
|
52793
|
+
const skillsRoot = join4(cwd, ".metaproject", "skills", "gdskills");
|
|
52694
52794
|
const target = resolve3(cwd, candidate);
|
|
52695
52795
|
const rel = relative2(skillsRoot, target);
|
|
52696
52796
|
if (rel === "") {
|
|
@@ -52705,7 +52805,7 @@ async function parseCatalogSummaries(cwd) {
|
|
|
52705
52805
|
const summaries = new Map;
|
|
52706
52806
|
let content;
|
|
52707
52807
|
try {
|
|
52708
|
-
const bytes = await readContainedFile(
|
|
52808
|
+
const bytes = await readContainedFile(join4(cwd, ".metaproject", "skills"), join4(cwd, ".metaproject", "skills", "catalog.md"), { maxBytes: 512 * 1024, requireRegularFile: true });
|
|
52709
52809
|
content = bytes.toString("utf8");
|
|
52710
52810
|
} catch {
|
|
52711
52811
|
return summaries;
|
|
@@ -52726,7 +52826,7 @@ async function parseCatalogSummaries(cwd) {
|
|
|
52726
52826
|
return summaries;
|
|
52727
52827
|
}
|
|
52728
52828
|
async function walkSkillCatalog(cwd) {
|
|
52729
|
-
const root =
|
|
52829
|
+
const root = join4(cwd, ".metaproject", "skills", "gdskills");
|
|
52730
52830
|
const entries = [];
|
|
52731
52831
|
const rootInfo = await lstat8(root).catch(() => null);
|
|
52732
52832
|
if (!rootInfo || !rootInfo.isDirectory() || rootInfo.isSymbolicLink())
|
|
@@ -52744,7 +52844,7 @@ async function walkSkillCatalog(cwd) {
|
|
|
52744
52844
|
for (const categoryDir of categoryDirs) {
|
|
52745
52845
|
if (!categoryDir.isDirectory() && !categoryDir.isSymbolicLink())
|
|
52746
52846
|
continue;
|
|
52747
|
-
const categoryPath =
|
|
52847
|
+
const categoryPath = join4(root, categoryDir.name);
|
|
52748
52848
|
const categoryReal = await realpath11(categoryPath).catch(() => null);
|
|
52749
52849
|
const categoryInfo = await stat15(categoryPath).catch(() => null);
|
|
52750
52850
|
if (!categoryReal || !categoryInfo?.isDirectory() || !isPathInside(rootReal, categoryReal))
|
|
@@ -52756,12 +52856,12 @@ async function walkSkillCatalog(cwd) {
|
|
|
52756
52856
|
continue;
|
|
52757
52857
|
}
|
|
52758
52858
|
for (const skillDir of skillDirs) {
|
|
52759
|
-
const skillPath =
|
|
52859
|
+
const skillPath = join4(categoryPath, skillDir.name);
|
|
52760
52860
|
const skillReal = await realpath11(skillPath).catch(() => null);
|
|
52761
52861
|
const skillInfo = await stat15(skillPath).catch(() => null);
|
|
52762
52862
|
if (!skillReal || !skillInfo?.isDirectory() || !isPathInside(rootReal, skillReal))
|
|
52763
52863
|
continue;
|
|
52764
|
-
const skillMdPath =
|
|
52864
|
+
const skillMdPath = join4(skillPath, "SKILL.md");
|
|
52765
52865
|
let content;
|
|
52766
52866
|
try {
|
|
52767
52867
|
const bytes = await readContainedFile(root, skillMdPath, { maxBytes: 512 * 1024, requireRegularFile: true });
|
|
@@ -53027,7 +53127,7 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
|
|
|
53027
53127
|
exists = false;
|
|
53028
53128
|
}
|
|
53029
53129
|
if (!exists) {
|
|
53030
|
-
const relativePath = toPosix(relative2(
|
|
53130
|
+
const relativePath = toPosix(relative2(join4(cwd, ".metaproject", "wiki"), target));
|
|
53031
53131
|
const registry = await readSectionRegistryState(cwd);
|
|
53032
53132
|
if (registry.state === "unreadable") {
|
|
53033
53133
|
return {
|
|
@@ -53068,7 +53168,7 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
|
|
|
53068
53168
|
};
|
|
53069
53169
|
}
|
|
53070
53170
|
try {
|
|
53071
|
-
const content = (await readContainedFile(
|
|
53171
|
+
const content = (await readContainedFile(join4(cwd, ".metaproject", "wiki"), target, {
|
|
53072
53172
|
maxBytes: 8 * 1024 * 1024,
|
|
53073
53173
|
requireRegularFile: true
|
|
53074
53174
|
})).toString("utf8");
|
|
@@ -53112,7 +53212,7 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
|
|
|
53112
53212
|
}
|
|
53113
53213
|
let hasWikiIndex;
|
|
53114
53214
|
try {
|
|
53115
|
-
await readFile60(
|
|
53215
|
+
await readFile60(join4(cwd, ".metaproject", "wiki", "index.md"), "utf8");
|
|
53116
53216
|
hasWikiIndex = true;
|
|
53117
53217
|
} catch {
|
|
53118
53218
|
hasWikiIndex = false;
|
|
@@ -53387,7 +53487,7 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
|
|
|
53387
53487
|
const byName = catalog.find((entry) => entry.name === input.name);
|
|
53388
53488
|
if (byName !== undefined) {
|
|
53389
53489
|
try {
|
|
53390
|
-
const content = (await readContainedFile(
|
|
53490
|
+
const content = (await readContainedFile(join4(cwd, ".metaproject", "skills", "gdskills"), join4(cwd, byName.path), {
|
|
53391
53491
|
maxBytes: 512 * 1024,
|
|
53392
53492
|
requireRegularFile: true
|
|
53393
53493
|
})).toString("utf8");
|
|
@@ -53400,12 +53500,12 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
|
|
|
53400
53500
|
if (confined === null) {
|
|
53401
53501
|
return { name: input.name, path: "", content: "", found: false };
|
|
53402
53502
|
}
|
|
53403
|
-
const byPath = catalog.find((entry) =>
|
|
53503
|
+
const byPath = catalog.find((entry) => join4(cwd, entry.path) === confined);
|
|
53404
53504
|
if (byPath === undefined) {
|
|
53405
53505
|
return { name: input.name, path: "", content: "", found: false };
|
|
53406
53506
|
}
|
|
53407
53507
|
try {
|
|
53408
|
-
const content = (await readContainedFile(
|
|
53508
|
+
const content = (await readContainedFile(join4(cwd, ".metaproject", "skills", "gdskills"), confined, {
|
|
53409
53509
|
maxBytes: 512 * 1024,
|
|
53410
53510
|
requireRegularFile: true
|
|
53411
53511
|
})).toString("utf8");
|
|
@@ -85041,14 +85141,14 @@ var init_registry3 = __esm(() => {
|
|
|
85041
85141
|
label: "Codex",
|
|
85042
85142
|
binary: "codex",
|
|
85043
85143
|
detect: ["--version"],
|
|
85044
|
-
versionPattern: "codex-cli (\\d+\\.\\d+\\.\\d+)",
|
|
85144
|
+
versionPattern: "codex-cli (\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.]+)?)",
|
|
85045
85145
|
knownGoodRange: { min: "0.147.0" },
|
|
85046
85146
|
sandboxModes: ["read-only", "worktree-write"],
|
|
85047
85147
|
streamingInput: false,
|
|
85048
85148
|
resumable: true,
|
|
85049
85149
|
reportsCost: false,
|
|
85050
85150
|
budgetFlag: false,
|
|
85051
|
-
notes: "Resume requires NOT passing `--ephemeral`: an ephemeral thread fails resume with " + "`no rollout found for thread id \u2026 (code -32600)`. `codex exec resume` also takes a " + "narrower flag set than `codex exec` \u2014 no `-s`, no `-C` \u2014 so
|
|
85151
|
+
notes: "Resume requires NOT passing `--ephemeral`: an ephemeral thread fails resume with " + "`no rollout found for thread id \u2026 (code -32600)`. `codex exec resume` also takes a " + "narrower flag set than `codex exec` \u2014 no `-s`, no `-C` \u2014 so a read-only resume inherits " + "its sandbox, a write run re-asserts its confinement as `-c` overrides, and it must be " + "spawned with cwd already set to the worktree. Write mode needs codex 0.159.2 up to, but not including, 0.160.0."
|
|
85052
85152
|
},
|
|
85053
85153
|
{
|
|
85054
85154
|
id: "claude-cli",
|
|
@@ -85098,10 +85198,27 @@ var init_registry3 = __esm(() => {
|
|
|
85098
85198
|
});
|
|
85099
85199
|
|
|
85100
85200
|
// src/harness/external/dispatch.ts
|
|
85201
|
+
function codecWriteVersionRefusal(entry, version) {
|
|
85202
|
+
const verified = CODEC_WRITE_VERIFIED_VERSIONS[entry.id];
|
|
85203
|
+
if (verified === undefined)
|
|
85204
|
+
return;
|
|
85205
|
+
const range = `\`${entry.binary}\` ${verified.min} up to, but not including, ${verified.before}`;
|
|
85206
|
+
const prefix = `${entry.label} write mode needs ${range}, the versions whose sandbox confinement was measured; `;
|
|
85207
|
+
if (version === undefined)
|
|
85208
|
+
return `${prefix}the installed version could not be read`;
|
|
85209
|
+
if (!/^\d+\.\d+\.\d+$/.test(version))
|
|
85210
|
+
return `${prefix}found ${version}, a pre-release`;
|
|
85211
|
+
if (compareVersions(version, verified.min) < 0)
|
|
85212
|
+
return `${prefix}found ${version}, which is older`;
|
|
85213
|
+
if (compareVersions(version, verified.before) >= 0) {
|
|
85214
|
+
return `${prefix}found ${version}, which is newer and not yet verified: update keryx, or use \`--task\` without \`--write\``;
|
|
85215
|
+
}
|
|
85216
|
+
return;
|
|
85217
|
+
}
|
|
85101
85218
|
function implementedSandboxModesFor(entry, options = {}) {
|
|
85102
85219
|
if (transportOf(entry) === "acp")
|
|
85103
85220
|
return IMPLEMENTED_ACP_SANDBOX_MODES;
|
|
85104
|
-
return entry.id
|
|
85221
|
+
return CODEC_WRITE_AGENT_IDS.includes(entry.id) && options.ownsWriteCapture === true ? IMPLEMENTED_ACP_SANDBOX_MODES : IMPLEMENTED_SANDBOX_MODES;
|
|
85105
85222
|
}
|
|
85106
85223
|
function validateRuntimeBlock(runtime, allowedActions, options = {}) {
|
|
85107
85224
|
if (runtime === undefined || runtime.kind === "keryx") {
|
|
@@ -85143,7 +85260,7 @@ function validateRuntimeBlock(runtime, allowedActions, options = {}) {
|
|
|
85143
85260
|
return {
|
|
85144
85261
|
ok: false,
|
|
85145
85262
|
code: "not-implemented",
|
|
85146
|
-
reason: `sandbox "${sandbox}" is not implemented in this release; only ${implemented.join(", ")} is available` + (sandbox === "worktree-write" && transportOf(entry) !== "acp" ? `; write mode is
|
|
85263
|
+
reason: `sandbox "${sandbox}" is not implemented in this release; only ${implemented.join(", ")} is available` + (sandbox === "worktree-write" && transportOf(entry) !== "acp" ? `; write mode is available for ${CODEC_WRITE_AGENT_IDS.join(", ")} only` + (entry.id === "antigravity-cli" ? ": antigravity-cli's edit tool writes outside the worktree" : "") : "")
|
|
85147
85264
|
};
|
|
85148
85265
|
}
|
|
85149
85266
|
if (sandbox === "read-only") {
|
|
@@ -85169,12 +85286,16 @@ function readRuntimeBlock(dispatch) {
|
|
|
85169
85286
|
return;
|
|
85170
85287
|
return raw;
|
|
85171
85288
|
}
|
|
85172
|
-
var READ_ONLY_FORBIDDEN_ACTIONS, IMPLEMENTED_SANDBOX_MODES, IMPLEMENTED_ACP_SANDBOX_MODES,
|
|
85289
|
+
var READ_ONLY_FORBIDDEN_ACTIONS, IMPLEMENTED_SANDBOX_MODES, IMPLEMENTED_ACP_SANDBOX_MODES, CODEC_WRITE_AGENT_IDS, CODEC_WRITE_VERIFIED_VERSIONS;
|
|
85173
85290
|
var init_dispatch = __esm(() => {
|
|
85174
85291
|
init_registry3();
|
|
85175
85292
|
READ_ONLY_FORBIDDEN_ACTIONS = ["write", "network", "spawn-subagent"];
|
|
85176
85293
|
IMPLEMENTED_SANDBOX_MODES = ["read-only"];
|
|
85177
85294
|
IMPLEMENTED_ACP_SANDBOX_MODES = ["read-only", "worktree-write"];
|
|
85295
|
+
CODEC_WRITE_AGENT_IDS = ["claude-cli", "codex-cli"];
|
|
85296
|
+
CODEC_WRITE_VERIFIED_VERSIONS = {
|
|
85297
|
+
"codex-cli": { min: "0.159.2", before: "0.160.0" }
|
|
85298
|
+
};
|
|
85178
85299
|
});
|
|
85179
85300
|
|
|
85180
85301
|
// src/harness/external/env-deny.ts
|
|
@@ -85708,7 +85829,7 @@ var init_prompt2 = __esm(() => {
|
|
|
85708
85829
|
EXTERNAL_WRITE_RUNTIME_DIRECTIVE = [
|
|
85709
85830
|
"# Runtime directive",
|
|
85710
85831
|
"",
|
|
85711
|
-
"You are running non-interactively as a bounded child agent. Produce the work itself as " + "your final message, in the requested output schema. Do not ask questions, do not offer a " + "choice of modes, do not route to another skill, orchestrator, or flow, do not delegate to " + "another agent. You may create and modify files, only inside your current working " + "directory, using
|
|
85832
|
+
"You are running non-interactively as a bounded child agent. Produce the work itself as " + "your final message, in the requested output schema. Do not ask questions, do not offer a " + "choice of modes, do not route to another skill, orchestrator, or flow, do not delegate to " + "another agent. You may create and modify files, only inside your current working " + "directory, using your file reading, searching and editing tools. Do not use a shell to reach " + "outside it. Do not touch .git, .github, .claude or .metaproject. Your changes are captured as a patch that a " + "human reviews before anything lands, so summarise what you changed in the final message."
|
|
85712
85833
|
].join(`
|
|
85713
85834
|
`);
|
|
85714
85835
|
DIFF_INTRO_COMPLETE = "The operator's uncommitted changes are below. Your worktree is checked out at HEAD and " + "does not contain them, so this diff is the only place they exist for you.";
|
|
@@ -88240,6 +88361,8 @@ function buildCodexArgv(input) {
|
|
|
88240
88361
|
"--ignore-user-config",
|
|
88241
88362
|
"--skip-git-repo-check"
|
|
88242
88363
|
];
|
|
88364
|
+
if (input.sandbox === "worktree-write")
|
|
88365
|
+
argv.push(...CODEX_WRITE_CONFINEMENT_FLAGS);
|
|
88243
88366
|
if (input.resultSchemaPath !== undefined)
|
|
88244
88367
|
argv.push("--output-schema", input.resultSchemaPath);
|
|
88245
88368
|
if (input.model !== undefined)
|
|
@@ -88247,8 +88370,9 @@ function buildCodexArgv(input) {
|
|
|
88247
88370
|
argv.push(input.prompt);
|
|
88248
88371
|
return argv;
|
|
88249
88372
|
}
|
|
88250
|
-
function buildCodexResumeArgv(sessionRef, message) {
|
|
88251
|
-
|
|
88373
|
+
function buildCodexResumeArgv(sessionRef, message, input) {
|
|
88374
|
+
const confinement = input?.sandbox === "worktree-write" ? ["-c", `sandbox_mode="${CODEX_SANDBOX_MODES["worktree-write"]}"`, ...CODEX_WRITE_CONFINEMENT_FLAGS] : [];
|
|
88375
|
+
return ["codex", "exec", "resume", sessionRef, "--json", "--ignore-user-config", "--skip-git-repo-check", ...confinement, message];
|
|
88252
88376
|
}
|
|
88253
88377
|
function parseCodexEvents(line) {
|
|
88254
88378
|
const record = readJsonObject2(line);
|
|
@@ -88403,12 +88527,21 @@ function brief2(text, max = 200) {
|
|
|
88403
88527
|
const flat = text.replace(/\s+/g, " ").trim();
|
|
88404
88528
|
return flat.length <= max ? flat : `${flat.slice(0, max - 1)}\u2026`;
|
|
88405
88529
|
}
|
|
88406
|
-
var CODEX_SANDBOX_MODES, RECOGNISED_TYPES, NARRATED_FAILURE_LINE, CLI_USAGE_PATTERNS, AUTH_PATTERNS2, LIMIT_PATTERNS2, codexCliCodec;
|
|
88530
|
+
var CODEX_SANDBOX_MODES, CODEX_WRITE_CONFINEMENT_FLAGS, RECOGNISED_TYPES, NARRATED_FAILURE_LINE, CLI_USAGE_PATTERNS, AUTH_PATTERNS2, LIMIT_PATTERNS2, codexCliCodec;
|
|
88407
88531
|
var init_codex_cli = __esm(() => {
|
|
88408
88532
|
CODEX_SANDBOX_MODES = {
|
|
88409
88533
|
"read-only": "read-only",
|
|
88410
88534
|
"worktree-write": "workspace-write"
|
|
88411
88535
|
};
|
|
88536
|
+
CODEX_WRITE_CONFINEMENT_FLAGS = [
|
|
88537
|
+
"--ignore-rules",
|
|
88538
|
+
"-c",
|
|
88539
|
+
"sandbox_workspace_write.exclude_slash_tmp=true",
|
|
88540
|
+
"-c",
|
|
88541
|
+
"sandbox_workspace_write.exclude_tmpdir_env_var=true",
|
|
88542
|
+
"-c",
|
|
88543
|
+
"sandbox_workspace_write.network_access=false"
|
|
88544
|
+
];
|
|
88412
88545
|
RECOGNISED_TYPES = new Set([
|
|
88413
88546
|
"thread.started",
|
|
88414
88547
|
"turn.started",
|
|
@@ -88438,7 +88571,7 @@ var init_codex_cli = __esm(() => {
|
|
|
88438
88571
|
parseLine: parseCodexLine,
|
|
88439
88572
|
parseEvents: parseCodexEvents,
|
|
88440
88573
|
classifyFailure: classifyCodexFailure,
|
|
88441
|
-
buildResumeArgv:
|
|
88574
|
+
buildResumeArgv: buildCodexResumeArgv,
|
|
88442
88575
|
isRecognisedLine: isRecognisedCodexLine
|
|
88443
88576
|
};
|
|
88444
88577
|
});
|
|
@@ -88552,15 +88685,21 @@ async function runExternalChild(input, deps) {
|
|
|
88552
88685
|
if (!isAcp && codec === undefined) {
|
|
88553
88686
|
return refuse2("Error", `no codec is registered for external agent "${entry.id}"`);
|
|
88554
88687
|
}
|
|
88688
|
+
let detectedVersion;
|
|
88555
88689
|
if (deps.detect !== undefined) {
|
|
88556
88690
|
const availability = resolveAvailability(entry, await deps.detect(entry.binary, entry.detect));
|
|
88557
88691
|
if (availability.state === "binary-missing") {
|
|
88558
88692
|
return refuse2("Denied", `${entry.label} is not installed (\`${entry.binary}\` not on PATH)`);
|
|
88559
88693
|
}
|
|
88694
|
+
if (availability.state === "available")
|
|
88695
|
+
detectedVersion = availability.version;
|
|
88560
88696
|
if (availability.state === "available" && availability.verdict.state !== "in-range") {
|
|
88561
88697
|
deps.onWarning?.(`${entry.label} version ${availability.version ?? "unknown"} is outside the range this build's fixtures were recorded against`);
|
|
88562
88698
|
}
|
|
88563
88699
|
}
|
|
88700
|
+
const versionRefusal = sandbox === "worktree-write" ? codecWriteVersionRefusal(entry, detectedVersion) : undefined;
|
|
88701
|
+
if (versionRefusal !== undefined)
|
|
88702
|
+
return refuse2("Denied", versionRefusal);
|
|
88564
88703
|
const schemaPrep = await prepareResultSchema();
|
|
88565
88704
|
if (!schemaPrep.ok)
|
|
88566
88705
|
return refuse2("Error", schemaPrep.reason);
|
|
@@ -112748,7 +112887,7 @@ var init_jev_rules = __esm(() => {
|
|
|
112748
112887
|
|
|
112749
112888
|
// src/commands/review-jev-rules.ts
|
|
112750
112889
|
import { readFile as readFile112, readdir as readdir41, stat as stat25 } from "fs/promises";
|
|
112751
|
-
import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute3, join as
|
|
112890
|
+
import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute3, join as join5, relative as relative3 } from "path";
|
|
112752
112891
|
function rejectUnknownJevRulesFlags(args) {
|
|
112753
112892
|
const unknown = args.filter((arg) => arg.startsWith("--") && !JEV_RULES_FLAGS.includes(arg.split("=")[0]));
|
|
112754
112893
|
if (unknown.length > 0) {
|
|
@@ -112790,7 +112929,7 @@ async function walkMatching(root, suffixes) {
|
|
|
112790
112929
|
return;
|
|
112791
112930
|
}
|
|
112792
112931
|
for (const entry of entries) {
|
|
112793
|
-
const full =
|
|
112932
|
+
const full = join5(dir, entry.name);
|
|
112794
112933
|
if (entry.isDirectory()) {
|
|
112795
112934
|
await walk(full);
|
|
112796
112935
|
continue;
|
|
@@ -112818,7 +112957,7 @@ function stripKeryxManagedBlock(text) {
|
|
|
112818
112957
|
}
|
|
112819
112958
|
async function discoverRuleSources(cwd, explicitPaths) {
|
|
112820
112959
|
const discovered = [];
|
|
112821
|
-
for (const root of [
|
|
112960
|
+
for (const root of [join5(cwd, ".metaproject", "rules"), join5(cwd, "rules"), join5(cwd, ".claude", "rules")]) {
|
|
112822
112961
|
for (const file of await walkMatching(root, [".md", ".mdc"])) {
|
|
112823
112962
|
const text = await readIfExists(file);
|
|
112824
112963
|
if (text === undefined)
|
|
@@ -112827,7 +112966,7 @@ async function discoverRuleSources(cwd, explicitPaths) {
|
|
|
112827
112966
|
}
|
|
112828
112967
|
}
|
|
112829
112968
|
for (const name of ["CLAUDE.md", "AGENTS.md"]) {
|
|
112830
|
-
const text = await readIfExists(
|
|
112969
|
+
const text = await readIfExists(join5(cwd, name));
|
|
112831
112970
|
if (text === undefined)
|
|
112832
112971
|
continue;
|
|
112833
112972
|
const body = stripKeryxManagedBlock(text);
|
|
@@ -112836,8 +112975,8 @@ async function discoverRuleSources(cwd, explicitPaths) {
|
|
|
112836
112975
|
break;
|
|
112837
112976
|
}
|
|
112838
112977
|
const skillRoots = [
|
|
112839
|
-
[
|
|
112840
|
-
[
|
|
112978
|
+
[join5(cwd, ".metaproject", "project-skills"), "project-skill"],
|
|
112979
|
+
[join5(cwd, ".metaproject", "skills", "gdskills"), "gdskill"]
|
|
112841
112980
|
];
|
|
112842
112981
|
for (const [root, kind] of skillRoots) {
|
|
112843
112982
|
for (const skillMd of await walkMatching(root, ["SKILL.md"])) {
|
|
@@ -112862,7 +113001,7 @@ async function discoverRuleSources(cwd, explicitPaths) {
|
|
|
112862
113001
|
const explicitFiles = [];
|
|
112863
113002
|
const explicitDirSources = [];
|
|
112864
113003
|
for (const raw of explicitPaths) {
|
|
112865
|
-
const resolved = isAbsolute3(raw) ? raw :
|
|
113004
|
+
const resolved = isAbsolute3(raw) ? raw : join5(cwd, raw);
|
|
112866
113005
|
const stats = await stat25(resolved).catch(() => {
|
|
112867
113006
|
return;
|
|
112868
113007
|
});
|
|
@@ -112905,11 +113044,11 @@ async function discoverRuleSources(cwd, explicitPaths) {
|
|
|
112905
113044
|
return { sources: [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path)), excluded: excluded.sort((a, b) => a.path.localeCompare(b.path)) };
|
|
112906
113045
|
}
|
|
112907
113046
|
async function fixturePrPort(dir) {
|
|
112908
|
-
const raw = await readFile112(
|
|
113047
|
+
const raw = await readFile112(join5(dir, "pr.json"), "utf8");
|
|
112909
113048
|
return createFixtureConformPrPort({ pr: JSON.parse(raw) });
|
|
112910
113049
|
}
|
|
112911
113050
|
async function fixtureJevFetch(dir) {
|
|
112912
|
-
const raw = await readFile112(
|
|
113051
|
+
const raw = await readFile112(join5(dir, "jev-responses.json"), "utf8");
|
|
112913
113052
|
const responses = JSON.parse(raw);
|
|
112914
113053
|
let index = 0;
|
|
112915
113054
|
const fn = async () => {
|
|
@@ -115269,7 +115408,7 @@ var init_jev_scenarios = __esm(() => {
|
|
|
115269
115408
|
|
|
115270
115409
|
// src/commands/review-jev-scenarios.ts
|
|
115271
115410
|
import { readFile as readFile121, readdir as readdir43 } from "fs/promises";
|
|
115272
|
-
import { join as
|
|
115411
|
+
import { join as join6, relative as relative4 } from "path";
|
|
115273
115412
|
function symbolsByPathFromRegions(regions) {
|
|
115274
115413
|
const byPath = new Map;
|
|
115275
115414
|
for (const region of regions) {
|
|
@@ -115322,7 +115461,7 @@ async function walkMarkdown2(root) {
|
|
|
115322
115461
|
return;
|
|
115323
115462
|
}
|
|
115324
115463
|
for (const entry of entries) {
|
|
115325
|
-
const full =
|
|
115464
|
+
const full = join6(dir, entry.name);
|
|
115326
115465
|
if (entry.isDirectory()) {
|
|
115327
115466
|
await walk(full);
|
|
115328
115467
|
continue;
|
|
@@ -115336,19 +115475,19 @@ async function walkMarkdown2(root) {
|
|
|
115336
115475
|
}
|
|
115337
115476
|
async function discoverScenarioSources(cwd) {
|
|
115338
115477
|
const scenarios = [];
|
|
115339
|
-
for (const file of await walkMarkdown2(
|
|
115478
|
+
for (const file of await walkMarkdown2(join6(cwd, ".metaproject", "wiki", "user-scenarios"))) {
|
|
115340
115479
|
const text = await readIfExists2(file);
|
|
115341
115480
|
if (text === undefined)
|
|
115342
115481
|
continue;
|
|
115343
115482
|
scenarios.push(scenarioFromWikiPage(relative4(cwd, file), text));
|
|
115344
115483
|
}
|
|
115345
|
-
for (const file of await walkMarkdown2(
|
|
115484
|
+
for (const file of await walkMarkdown2(join6(cwd, "docs", "requirements"))) {
|
|
115346
115485
|
const text = await readIfExists2(file);
|
|
115347
115486
|
if (text === undefined)
|
|
115348
115487
|
continue;
|
|
115349
115488
|
scenarios.push(...scenariosFromPrd(relative4(cwd, file), text));
|
|
115350
115489
|
}
|
|
115351
|
-
const readmeLikeFiles = [
|
|
115490
|
+
const readmeLikeFiles = [join6(cwd, "README.md"), ...await walkMarkdown2(join6(cwd, "docs", "docs"))];
|
|
115352
115491
|
for (const file of readmeLikeFiles) {
|
|
115353
115492
|
const text = await readIfExists2(file);
|
|
115354
115493
|
if (text === undefined)
|
|
@@ -115358,11 +115497,11 @@ async function discoverScenarioSources(cwd) {
|
|
|
115358
115497
|
return scenarios;
|
|
115359
115498
|
}
|
|
115360
115499
|
async function fixturePrPort4(dir) {
|
|
115361
|
-
const raw = await readFile121(
|
|
115500
|
+
const raw = await readFile121(join6(dir, "pr.json"), "utf8");
|
|
115362
115501
|
return createFixtureConformPrPort({ pr: JSON.parse(raw) });
|
|
115363
115502
|
}
|
|
115364
115503
|
async function fixtureJevFetch5(dir) {
|
|
115365
|
-
const raw = await readFile121(
|
|
115504
|
+
const raw = await readFile121(join6(dir, "jev-responses.json"), "utf8");
|
|
115366
115505
|
const responses = JSON.parse(raw);
|
|
115367
115506
|
let index = 0;
|
|
115368
115507
|
const fn = async () => {
|
|
@@ -115987,7 +116126,7 @@ var init_jev_docs = __esm(() => {
|
|
|
115987
116126
|
|
|
115988
116127
|
// src/commands/review-jev-docs.ts
|
|
115989
116128
|
import { readFile as readFile123, readdir as readdir44 } from "fs/promises";
|
|
115990
|
-
import { join as
|
|
116129
|
+
import { join as join7, relative as relative5 } from "path";
|
|
115991
116130
|
function messageOf4(error) {
|
|
115992
116131
|
return error instanceof Error ? error.message : String(error);
|
|
115993
116132
|
}
|
|
@@ -116049,7 +116188,7 @@ async function walkMatching2(root, suffixes) {
|
|
|
116049
116188
|
return;
|
|
116050
116189
|
}
|
|
116051
116190
|
for (const entry of entries) {
|
|
116052
|
-
const full =
|
|
116191
|
+
const full = join7(dir, entry.name);
|
|
116053
116192
|
if (entry.isDirectory()) {
|
|
116054
116193
|
await walk(full);
|
|
116055
116194
|
continue;
|
|
@@ -116109,7 +116248,7 @@ async function walkAllDocFiles(dir) {
|
|
|
116109
116248
|
return out;
|
|
116110
116249
|
}
|
|
116111
116250
|
for (const entry of entries) {
|
|
116112
|
-
const full =
|
|
116251
|
+
const full = join7(dir, entry.name);
|
|
116113
116252
|
if (entry.isDirectory()) {
|
|
116114
116253
|
if (INCLUDE_WALK_PRUNE_DIRS.has(entry.name))
|
|
116115
116254
|
continue;
|
|
@@ -116123,19 +116262,19 @@ async function walkAllDocFiles(dir) {
|
|
|
116123
116262
|
async function resolveIncludeGlob(cwd, glob) {
|
|
116124
116263
|
const rootDir = includeGlobRootDir(glob);
|
|
116125
116264
|
const regex = includeGlobToRegExp(glob);
|
|
116126
|
-
const candidates = await walkAllDocFiles(
|
|
116265
|
+
const candidates = await walkAllDocFiles(join7(cwd, rootDir));
|
|
116127
116266
|
return candidates.filter((file) => regex.test(relative5(cwd, file)));
|
|
116128
116267
|
}
|
|
116129
116268
|
async function discoverDocFiles(cwd, includeGlobs = []) {
|
|
116130
116269
|
const files = new Set;
|
|
116131
|
-
for (const file of await walkMatching2(
|
|
116270
|
+
for (const file of await walkMatching2(join7(cwd, "docs"), [".md"]))
|
|
116132
116271
|
files.add(file);
|
|
116133
116272
|
for (const name of await matchingFileNames(cwd, /^readme/i)) {
|
|
116134
116273
|
if (/^changelog/i.test(name))
|
|
116135
116274
|
continue;
|
|
116136
|
-
files.add(
|
|
116275
|
+
files.add(join7(cwd, name));
|
|
116137
116276
|
}
|
|
116138
|
-
for (const file of await walkMatching2(
|
|
116277
|
+
for (const file of await walkMatching2(join7(cwd, ".metaproject", "wiki"), [".md"]))
|
|
116139
116278
|
files.add(file);
|
|
116140
116279
|
for (const glob of includeGlobs) {
|
|
116141
116280
|
for (const file of await resolveIncludeGlob(cwd, glob))
|
|
@@ -116163,11 +116302,11 @@ function changedCommandNamesFromRegions(regions) {
|
|
|
116163
116302
|
return names;
|
|
116164
116303
|
}
|
|
116165
116304
|
async function fixturePrPort5(dir) {
|
|
116166
|
-
const raw = await readFile123(
|
|
116305
|
+
const raw = await readFile123(join7(dir, "pr.json"), "utf8");
|
|
116167
116306
|
return createFixtureConformPrPort({ pr: JSON.parse(raw) });
|
|
116168
116307
|
}
|
|
116169
116308
|
async function fixtureJevFetch6(dir) {
|
|
116170
|
-
const raw = await readFile123(
|
|
116309
|
+
const raw = await readFile123(join7(dir, "jev-responses.json"), "utf8");
|
|
116171
116310
|
const responses = JSON.parse(raw);
|
|
116172
116311
|
let index = 0;
|
|
116173
116312
|
const fn = async () => {
|
|
@@ -116585,7 +116724,7 @@ var init_jev_comments = __esm(() => {
|
|
|
116585
116724
|
|
|
116586
116725
|
// src/commands/review-jev-comments.ts
|
|
116587
116726
|
import { readFile as readFile125 } from "fs/promises";
|
|
116588
|
-
import { join as
|
|
116727
|
+
import { join as join8 } from "path";
|
|
116589
116728
|
function rejectUnknownJevCommentsFlags(args) {
|
|
116590
116729
|
const unknown = args.filter((arg) => arg.startsWith("--") && !JEV_COMMENTS_FLAGS.includes(arg.split("=")[0]));
|
|
116591
116730
|
if (unknown.length > 0) {
|
|
@@ -116671,15 +116810,15 @@ function createFixtureJevCommentsFactsPort(facts) {
|
|
|
116671
116810
|
};
|
|
116672
116811
|
}
|
|
116673
116812
|
async function fixturePrPort6(dir) {
|
|
116674
|
-
const raw = await readFile125(
|
|
116813
|
+
const raw = await readFile125(join8(dir, "pr.json"), "utf8");
|
|
116675
116814
|
return createFixtureConformPrPort({ pr: JSON.parse(raw) });
|
|
116676
116815
|
}
|
|
116677
116816
|
async function fixtureFactsPort(dir) {
|
|
116678
|
-
const raw = await readFile125(
|
|
116817
|
+
const raw = await readFile125(join8(dir, "git-facts.json"), "utf8").catch(() => "{}");
|
|
116679
116818
|
return createFixtureJevCommentsFactsPort(JSON.parse(raw));
|
|
116680
116819
|
}
|
|
116681
116820
|
async function fixtureJevFetch7(dir) {
|
|
116682
|
-
const raw = await readFile125(
|
|
116821
|
+
const raw = await readFile125(join8(dir, "jev-responses.json"), "utf8");
|
|
116683
116822
|
const responses = JSON.parse(raw);
|
|
116684
116823
|
let index = 0;
|
|
116685
116824
|
const fn = async () => {
|
|
@@ -116693,7 +116832,7 @@ async function fixtureJevFetch7(dir) {
|
|
|
116693
116832
|
return fn;
|
|
116694
116833
|
}
|
|
116695
116834
|
function advisoryLabelsPath(cwd, repo, number) {
|
|
116696
|
-
return
|
|
116835
|
+
return join8(cwd, ".metaproject", "data", "review-jev-comments", `labels__${repo.replace(/\//g, "__")}__${number}.json`);
|
|
116697
116836
|
}
|
|
116698
116837
|
async function readCommentAdvisoryLabels(cwd, repo, number) {
|
|
116699
116838
|
const file = advisoryLabelsPath(cwd, repo, number);
|
|
@@ -127961,7 +128100,7 @@ var init_narrate = __esm(() => {
|
|
|
127961
128100
|
// src/commands/memory.ts
|
|
127962
128101
|
import { randomUUID as randomUUID34 } from "crypto";
|
|
127963
128102
|
import { readdir as readdir45 } from "fs/promises";
|
|
127964
|
-
import { join as
|
|
128103
|
+
import { join as join9 } from "path";
|
|
127965
128104
|
function getService() {
|
|
127966
128105
|
service4 ??= createMemoryService();
|
|
127967
128106
|
return service4;
|
|
@@ -128068,7 +128207,7 @@ async function runIndex(args) {
|
|
|
128068
128207
|
async function detectMemoryStoreUnreadable2(cwd) {
|
|
128069
128208
|
const root = memoryRoot(cwd);
|
|
128070
128209
|
for (const { folder } of MEMORY_TYPES) {
|
|
128071
|
-
const dir =
|
|
128210
|
+
const dir = join9(root, folder);
|
|
128072
128211
|
try {
|
|
128073
128212
|
await readdir45(dir);
|
|
128074
128213
|
} catch (error) {
|
|
@@ -152693,7 +152832,7 @@ function referencesHash(entry) {
|
|
|
152693
152832
|
}
|
|
152694
152833
|
return hash.digest("hex");
|
|
152695
152834
|
}
|
|
152696
|
-
function
|
|
152835
|
+
function loadCache2(cachePath) {
|
|
152697
152836
|
if (!existsSync51(cachePath))
|
|
152698
152837
|
return {};
|
|
152699
152838
|
try {
|
|
@@ -152821,7 +152960,7 @@ async function runStocktake(root, options = {}) {
|
|
|
152821
152960
|
const now = options.now ?? (() => new Date);
|
|
152822
152961
|
const { entries: catalog, unreadable } = loadSkillCatalogWithDiagnostics(root, { scope });
|
|
152823
152962
|
const cachePath = options.cachePath ?? path295.join(root, ".metaproject", "data", "skills", "stocktake", "cache.json");
|
|
152824
|
-
const cache =
|
|
152963
|
+
const cache = loadCache2(cachePath);
|
|
152825
152964
|
const fingerprint = catalogFingerprint(catalog);
|
|
152826
152965
|
let hits = 0;
|
|
152827
152966
|
let misses = 0;
|
|
@@ -159572,7 +159711,7 @@ var init_post = __esm(() => {
|
|
|
159572
159711
|
|
|
159573
159712
|
// src/commands/review-bot.ts
|
|
159574
159713
|
import { readFile as readFile158 } from "fs/promises";
|
|
159575
|
-
import { join as
|
|
159714
|
+
import { join as join10 } from "path";
|
|
159576
159715
|
function reject(args, valued, boolean, usage) {
|
|
159577
159716
|
const problems = [];
|
|
159578
159717
|
for (let index = 0;index < args.length; index += 1) {
|
|
@@ -159601,7 +159740,7 @@ async function portFor(args) {
|
|
|
159601
159740
|
return createGhPort();
|
|
159602
159741
|
let pull;
|
|
159603
159742
|
try {
|
|
159604
|
-
pull = JSON.parse(await readFile158(
|
|
159743
|
+
pull = JSON.parse(await readFile158(join10(fixtures, "pull.json"), "utf8"));
|
|
159605
159744
|
} catch {
|
|
159606
159745
|
pull = {};
|
|
159607
159746
|
}
|
|
@@ -161041,7 +161180,7 @@ var init_floor = __esm(() => {
|
|
|
161041
161180
|
|
|
161042
161181
|
// src/commands/review.ts
|
|
161043
161182
|
import { mkdir as mkdir66, readdir as readdir58, readFile as readFile162 } from "fs/promises";
|
|
161044
|
-
import path317, { join as
|
|
161183
|
+
import path317, { join as join11 } from "path";
|
|
161045
161184
|
function reviewerList(args) {
|
|
161046
161185
|
return (optionValue(args, "--reviewers") ?? "").split(",").map((item) => item.trim()).filter(Boolean);
|
|
161047
161186
|
}
|
|
@@ -161716,7 +161855,7 @@ async function resolvePort(args) {
|
|
|
161716
161855
|
}
|
|
161717
161856
|
const files = {};
|
|
161718
161857
|
for (const key of ["pull", "pull-comments", "pull-reviews", "issue-comments"]) {
|
|
161719
|
-
const file =
|
|
161858
|
+
const file = join11(fixtures, `${key}.json`);
|
|
161720
161859
|
try {
|
|
161721
161860
|
files[key] = JSON.parse(await readFile162(file, "utf8"));
|
|
161722
161861
|
} catch {
|
|
@@ -161726,13 +161865,13 @@ async function resolvePort(args) {
|
|
|
161726
161865
|
return createFixturePort(files);
|
|
161727
161866
|
}
|
|
161728
161867
|
async function fixtureCiPort(dir) {
|
|
161729
|
-
const runInfo = JSON.parse(await readFile162(
|
|
161730
|
-
const log = await readFile162(
|
|
161731
|
-
const history = await readFile162(
|
|
161732
|
-
const attempts = await readFile162(
|
|
161733
|
-
const changedFiles = await readFile162(
|
|
161734
|
-
const runsByHeadSha = await readFile162(
|
|
161735
|
-
const related = await readFile162(
|
|
161868
|
+
const runInfo = JSON.parse(await readFile162(join11(dir, "ci-run-info.json"), "utf8"));
|
|
161869
|
+
const log = await readFile162(join11(dir, "ci-failed-log.txt"), "utf8").catch(() => "");
|
|
161870
|
+
const history = await readFile162(join11(dir, "ci-history.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
|
|
161871
|
+
const attempts = await readFile162(join11(dir, "ci-attempts.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
|
|
161872
|
+
const changedFiles = await readFile162(join11(dir, "ci-changed-files.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
|
|
161873
|
+
const runsByHeadSha = await readFile162(join11(dir, "ci-runs-by-head-sha.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
|
|
161874
|
+
const related = await readFile162(join11(dir, "ci-related-runs.json"), "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
|
|
161736
161875
|
return createFixtureCiPort({
|
|
161737
161876
|
runs: { [runInfo.runId]: runInfo, ...related.runs },
|
|
161738
161877
|
logs: { [runInfo.runId]: log, ...related.logs },
|
|
@@ -161743,7 +161882,7 @@ async function fixtureCiPort(dir) {
|
|
|
161743
161882
|
});
|
|
161744
161883
|
}
|
|
161745
161884
|
async function fixtureJevFetch9(dir) {
|
|
161746
|
-
const body = await readFile162(
|
|
161885
|
+
const body = await readFile162(join11(dir, "jev-response.json"), "utf8");
|
|
161747
161886
|
const fn = async () => new Response(body, { status: 200, headers: { "content-type": "application/json" } });
|
|
161748
161887
|
return fn;
|
|
161749
161888
|
}
|
|
@@ -161912,12 +162051,12 @@ async function runCiTriageEval(cwd, args, evalFile) {
|
|
|
161912
162051
|
}
|
|
161913
162052
|
}
|
|
161914
162053
|
async function fixtureConformJevFetch(dir) {
|
|
161915
|
-
const body = await readFile162(
|
|
162054
|
+
const body = await readFile162(join11(dir, "jev-response.json"), "utf8");
|
|
161916
162055
|
const fn = async () => new Response(body, { status: 200, headers: { "content-type": "application/json" } });
|
|
161917
162056
|
return fn;
|
|
161918
162057
|
}
|
|
161919
162058
|
async function fixtureConformPrPortFrom(dir) {
|
|
161920
|
-
const pr = JSON.parse(await readFile162(
|
|
162059
|
+
const pr = JSON.parse(await readFile162(join11(dir, "pr.json"), "utf8"));
|
|
161921
162060
|
return createFixtureConformPrPort({ pr });
|
|
161922
162061
|
}
|
|
161923
162062
|
function accumulateJevUsage(acc, usage) {
|
|
@@ -161998,7 +162137,7 @@ async function explainConformVerdicts(cwd, refPath, verdicts, threshold, fixture
|
|
|
161998
162137
|
return {};
|
|
161999
162138
|
if (fixturesDir !== undefined) {
|
|
162000
162139
|
try {
|
|
162001
|
-
return JSON.parse(await readFile162(
|
|
162140
|
+
return JSON.parse(await readFile162(join11(fixturesDir, "explain-response.json"), "utf8"));
|
|
162002
162141
|
} catch {
|
|
162003
162142
|
return {};
|
|
162004
162143
|
}
|
|
@@ -162046,7 +162185,7 @@ function parsePositiveIntFlag(args, flag, fallback) {
|
|
|
162046
162185
|
}
|
|
162047
162186
|
async function readReportFindings2(reportDir) {
|
|
162048
162187
|
try {
|
|
162049
|
-
const raw = JSON.parse(await readFile162(
|
|
162188
|
+
const raw = JSON.parse(await readFile162(join11(reportDir, "findings.json"), "utf8"));
|
|
162050
162189
|
return Array.isArray(raw) ? raw : [];
|
|
162051
162190
|
} catch {
|
|
162052
162191
|
return [];
|
|
@@ -162118,7 +162257,7 @@ async function runConform(args) {
|
|
|
162118
162257
|
const fetchFn = fixturesDir === undefined ? globalThis.fetch : await fixtureConformJevFetch(fixturesDir);
|
|
162119
162258
|
const acc = { jevCalls: 0, inputTokens: 0, outputTokens: 0, costUsd: 0, sawUsage: false };
|
|
162120
162259
|
const clauses = await resolveConformClauseTags(cwd, refPath, docText, rawClauses, fetchFn, model, acc);
|
|
162121
|
-
await writeFileAtomic(
|
|
162260
|
+
await writeFileAtomic(join11(cwd, CONFORM_RECENTS_PATH), `${JSON.stringify(withRecentDoc(await readRecentConformDocs2(cwd), refPath), null, 2)}
|
|
162122
162261
|
`, { mode: 384 }).catch(() => {});
|
|
162123
162262
|
const notCheckable = clauses.filter((c) => !c.checkable);
|
|
162124
162263
|
const checkable = clauses.filter((c) => c.checkable);
|
|
@@ -162150,7 +162289,7 @@ async function runConform(args) {
|
|
|
162150
162289
|
hunkTotalRegions = hunkResult.totalRegions;
|
|
162151
162290
|
} else if (reportDir !== undefined) {
|
|
162152
162291
|
target = { kind: "report", label: reportDir };
|
|
162153
|
-
const reportMarkdown = await readFile162(
|
|
162292
|
+
const reportMarkdown = await readFile162(join11(reportDir, "report.md"), "utf8").catch(() => "");
|
|
162154
162293
|
const findings = await readReportFindings2(reportDir);
|
|
162155
162294
|
const facts = computeReportConformFacts(reportMarkdown, findings);
|
|
162156
162295
|
const redacted = reportRedactedStateText(reportMarkdown);
|
|
@@ -162191,7 +162330,7 @@ async function runConform(args) {
|
|
|
162191
162330
|
}
|
|
162192
162331
|
async function readRecentConformDocs2(cwd) {
|
|
162193
162332
|
try {
|
|
162194
|
-
const raw = JSON.parse(await readFile162(
|
|
162333
|
+
const raw = JSON.parse(await readFile162(join11(cwd, CONFORM_RECENTS_PATH), "utf8"));
|
|
162195
162334
|
return Array.isArray(raw) ? raw : [];
|
|
162196
162335
|
} catch {
|
|
162197
162336
|
return [];
|
|
@@ -162440,7 +162579,7 @@ async function runStack(args) {
|
|
|
162440
162579
|
console.log(renderStackScopingMarkdown(detected, decisions));
|
|
162441
162580
|
}
|
|
162442
162581
|
async function stackScopingForInstalledReviewers(cwd, detected) {
|
|
162443
|
-
const reviewRoot =
|
|
162582
|
+
const reviewRoot = join11(cwd, ".metaproject", "skills", "gdskills", "review");
|
|
162444
162583
|
let entries;
|
|
162445
162584
|
try {
|
|
162446
162585
|
entries = await readdir58(reviewRoot, { withFileTypes: true });
|
|
@@ -162452,7 +162591,7 @@ async function stackScopingForInstalledReviewers(cwd, detected) {
|
|
|
162452
162591
|
if (!entry.isDirectory()) {
|
|
162453
162592
|
continue;
|
|
162454
162593
|
}
|
|
162455
|
-
const skillMdPath =
|
|
162594
|
+
const skillMdPath = join11(reviewRoot, entry.name, "SKILL.md");
|
|
162456
162595
|
let content;
|
|
162457
162596
|
try {
|
|
162458
162597
|
content = await readFile162(skillMdPath, "utf8");
|
|
@@ -179345,7 +179484,7 @@ async function runCommand4(args, deps, log) {
|
|
|
179345
179484
|
maxExternalDepth: DEFAULT_MAX_EXTERNAL_DEPTH,
|
|
179346
179485
|
onWarning: (warning) => console.error(`warning: ${plain(warning)}`)
|
|
179347
179486
|
};
|
|
179348
|
-
if (write && id
|
|
179487
|
+
if (write && CODEC_WRITE_AGENT_IDS.includes(id)) {
|
|
179349
179488
|
let handle;
|
|
179350
179489
|
let interrupted = false;
|
|
179351
179490
|
const onSigint = () => {
|
|
@@ -179619,7 +179758,7 @@ function printExternalHelp() {
|
|
|
179619
179758
|
{ flag: "--no-probe", desc: "Skip detection entirely; every entry reports `not-probed`." },
|
|
179620
179759
|
{ flag: "--task", desc: "run: what the agent should do. It runs in a disposable worktree; your tree is never touched." },
|
|
179621
179760
|
{ flag: "--unattended", desc: "run: refuse every permission that would need a human (also implied without a TTY)." },
|
|
179622
|
-
{ flag: "--write", desc: "run: writes land in the disposable worktree and leave as a never-applied patch. An ACP agent advertises fs.writeTextFile; for a line-stream agent only claude
|
|
179761
|
+
{ flag: "--write", desc: "run: writes land in the disposable worktree and leave as a never-applied patch. An ACP agent advertises fs.writeTextFile; for a line-stream agent only claude-cli and codex-cli are supported. claude gets Edit and Write inside the worktree and no shell; codex runs under its OS sandbox (writes confined to the worktree, network closed as far as measured) and keeps a shell that can read any file you can read. codex needs version 0.159.2 or newer but older than 0.160.0. antigravity-cli is refused: its edit tool writes outside the worktree." },
|
|
179623
179762
|
{ flag: "--timeout", desc: "run: wall-clock ceiling in ms (default: externalAgents.defaultTimeoutMs)." }
|
|
179624
179763
|
]);
|
|
179625
179764
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.42",
|
|
4
4
|
"description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
"minLength": 1
|
|
166
166
|
},
|
|
167
167
|
"sandbox": {
|
|
168
|
-
"description": "Permission level for the external child. `worktree-write` is schema-valid but refused at runtime
|
|
168
|
+
"description": "Permission level for the external child. `worktree-write` is schema-valid but refused at runtime except for an ACP agent and, through the operator's `--write` command only, `claude-cli` and `codex-cli`, with a reason distinguishable from an unsupported-by-this-agent refusal.",
|
|
169
169
|
"type": "string",
|
|
170
170
|
"enum": ["read-only", "worktree-write"]
|
|
171
171
|
},
|