@mrciphersmith/keryx 0.2.14 → 0.2.16
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 +58 -42
- package/dist/cli.js +486 -53
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,15 +93,13 @@ it by reading files at random.
|
|
|
93
93
|
### What it looks like on a real repository
|
|
94
94
|
|
|
95
95
|
Real output from a fresh clone of
|
|
96
|
-
[express](https://github.com/expressjs/express) —
|
|
96
|
+
[express](https://github.com/expressjs/express) — three commands after `init`,
|
|
97
|
+
nothing edited:
|
|
97
98
|
|
|
98
99
|
```console
|
|
99
|
-
$ keryx init --yes
|
|
100
|
-
✓ gdgraph ✓ gdctx ✓ gdwiki ✓ gdskills
|
|
101
|
-
✓ health ✓ testing ✓ memory ✓ tasks ✓ security
|
|
102
|
-
|
|
103
100
|
$ keryx gdgraph build
|
|
104
101
|
gdgraph build complete: 139 nodes, 153 edges
|
|
102
|
+
summary: .metaproject/data/gdgraph/artifacts/summary.md
|
|
105
103
|
|
|
106
104
|
$ keryx gdgraph query cycles
|
|
107
105
|
No cycles found.
|
|
@@ -127,6 +125,7 @@ affected graph supplies deterministically, in one command.
|
|
|
127
125
|
|
|
128
126
|
```text
|
|
129
127
|
.metaproject/
|
|
128
|
+
├── metaproject.json # the module manifest
|
|
130
129
|
├── index.md # the routing index every agent reads first
|
|
131
130
|
├── wiki/ # architecture, domain models, decisions, flows
|
|
132
131
|
├── memory/ # lessons, decisions, constraints, known mistakes
|
|
@@ -136,7 +135,8 @@ affected graph supplies deterministically, in one command.
|
|
|
136
135
|
├── data/gdgraph/ # graph artifacts, module map, query results
|
|
137
136
|
├── data/testing/ # test context, related tests, normalized reports
|
|
138
137
|
├── data/health/ # normalized health artifacts and trends
|
|
139
|
-
|
|
138
|
+
├── flows/ # task flows with frozen acceptance criteria
|
|
139
|
+
└── … # per-module config, hooks, templates, dashboard
|
|
140
140
|
```
|
|
141
141
|
|
|
142
142
|
All Markdown and JSON. All diffable. All yours. And readable as a dashboard when
|
|
@@ -182,31 +182,40 @@ of guessing — the same `ask` the policy engine raises for a guarded action:
|
|
|
182
182
|
|
|
183
183
|
What is in it today:
|
|
184
184
|
|
|
185
|
-
- **Provider-neutral loop.** Anthropic, Ollama,
|
|
185
|
+
- **Provider-neutral loop.** Anthropic, Ollama, and any OpenAI-compatible
|
|
186
|
+
gateway — OpenRouter, DeepSeek, Z.AI, Cerebras, Groq, Moonshot, Grok — plus an
|
|
186
187
|
offline fake provider for deterministic runs. Swapping the model does not
|
|
187
188
|
change the loop, the tools or the policy.
|
|
188
|
-
- **Durable sessions, per project.**
|
|
189
|
-
|
|
190
|
-
`/resume`, `/compact`, `/new
|
|
189
|
+
- **Durable sessions, per project.** JSONL transcripts on disk, resume across a
|
|
190
|
+
process restart, and context compaction that keeps the full archive.
|
|
191
|
+
`/resume`, `/compact`, `/new`, and `keryx sessions list|fork|export` — `fork`
|
|
192
|
+
branches a conversation into a new session that keeps its ancestry, without
|
|
193
|
+
editing a transcript by hand.
|
|
191
194
|
- **A policy engine with three answers, not two.** `allow`, `ask`, `deny` over
|
|
192
|
-
|
|
193
|
-
|
|
195
|
+
seven risk classes — read, write, shell, network, credential, delegate,
|
|
196
|
+
destructive — with path and command rules underneath. Shell and destructive
|
|
197
|
+
actions are default-deny and need an explicit approval before they run.
|
|
194
198
|
- **Kernel-enforced containment underneath.** The OS sandbox sits *below* the
|
|
195
199
|
policy engine — Seatbelt on macOS, bubblewrap on Linux — with network off/on,
|
|
196
200
|
and on macOS a loopback domain allowlist, credential masking behind a per-run
|
|
197
|
-
sentinel, and
|
|
198
|
-
posture is missing rather than quietly doing less.
|
|
201
|
+
sentinel, and TLS termination where masking requires it. It fails closed when a
|
|
202
|
+
launcher or a posture is missing rather than quietly doing less.
|
|
199
203
|
- **Child agents with budgets.** Dispatch over the canonical
|
|
200
204
|
`subagent-dispatch`/`subagent-result` contracts, token budgets per child,
|
|
201
|
-
bounded parallel scheduling, and
|
|
202
|
-
|
|
205
|
+
bounded parallel scheduling, and an offline fleet report over a recorded event
|
|
206
|
+
log (`keryx agents monitor <events-file>`).
|
|
207
|
+
- **Completion you can audit.** The completion gate blocks on missing evidence: a
|
|
203
208
|
run that cannot produce the evidence its flow requires does not get to claim
|
|
204
209
|
it finished.
|
|
205
|
-
- **
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
+
- **Four doors.** The CLI (`keryx harness run|exec|extension|wave|replay`), JSONL/RPC
|
|
211
|
+
and the loopback HTTP entry (`keryx serve`) share one execution loop; the
|
|
212
|
+
interactive TUI runs its own on the same tool registry and the same policy.
|
|
213
|
+
- **A record you can check.** `keryx harness run --record` writes a run's
|
|
214
|
+
recomputable hash surface and `keryx harness replay` validates a fixture
|
|
215
|
+
against it, naming the diverging field when one moves.
|
|
216
|
+
|
|
217
|
+
The full tour — including what the harness does *not* do yet — is in
|
|
218
|
+
[the harness page](docs/docs/harness.md).
|
|
210
219
|
|
|
211
220
|
Provider-neutral means what it says — the same loop, the same tool registry and
|
|
212
221
|
the same policy, with the model swapped out from under it:
|
|
@@ -226,7 +235,7 @@ Grouped by what you are trying to do, not by internal module layout.
|
|
|
226
235
|
**Understand the codebase**
|
|
227
236
|
|
|
228
237
|
- **gdgraph** — language-aware dependency graph for TypeScript/JavaScript, Java
|
|
229
|
-
(Maven/Gradle) and Python: cycle and orphan queries,
|
|
238
|
+
(Maven/Gradle) and Python: cycle and orphan queries, file and symbol search,
|
|
230
239
|
shortest paths, affected-set blast radius, PageRank repo map, and an optional
|
|
231
240
|
tree-sitter symbol/call graph.
|
|
232
241
|
- **gdwiki** — a Markdown architecture wiki with hierarchical indexes, link
|
|
@@ -237,7 +246,7 @@ Grouped by what you are trying to do, not by internal module layout.
|
|
|
237
246
|
**Preserve knowledge**
|
|
238
247
|
|
|
239
248
|
- **memory** — long-term project memory with indexing, lexical search, dedup and
|
|
240
|
-
|
|
249
|
+
as-of validity queries, so a lesson learned once stays learned.
|
|
241
250
|
- **gdskills** — bundled and project-generated agent skills with routing,
|
|
242
251
|
verification, learning from reviews, and export to different agent runtimes.
|
|
243
252
|
|
|
@@ -246,9 +255,11 @@ Grouped by what you are trying to do, not by internal module layout.
|
|
|
246
255
|
- **testing** — testing context, related-test selection, changed-scope runs, and
|
|
247
256
|
an opt-in coverage-map Test Impact Analysis.
|
|
248
257
|
- **health** — normalized reports from TypeScript, tests, audit, complexity,
|
|
249
|
-
coverage and lint (optional SonarQube), plus a quality gate and
|
|
250
|
-
|
|
251
|
-
|
|
258
|
+
coverage and lint (optional SonarQube issue import), plus a quality gate and
|
|
259
|
+
trends.
|
|
260
|
+
- **review** — managed review packages, standalone under `.metaproject/reviews/`
|
|
261
|
+
or inside the flow package when attached to a flow, so review findings become
|
|
262
|
+
durable project artifacts.
|
|
252
263
|
|
|
253
264
|
**Operate agents**
|
|
254
265
|
|
|
@@ -258,13 +269,13 @@ Grouped by what you are trying to do, not by internal module layout.
|
|
|
258
269
|
scanning, redaction, and a policy gate at agent write seams, with a committed
|
|
259
270
|
evaluation corpus.
|
|
260
271
|
- **mcp** — an opt-in [Model Context Protocol](https://modelcontextprotocol.io)
|
|
261
|
-
server exposing read-only module services to agents
|
|
272
|
+
server exposing read-only module services to agents, plus one report-writing
|
|
273
|
+
security scan.
|
|
262
274
|
|
|
263
275
|
**Run agents inside boundaries**
|
|
264
276
|
|
|
265
277
|
- **harness** — the first-party agent runtime described above: provider-neutral
|
|
266
|
-
loop, durable sessions, policy engine, child agents, evidence-gated completion
|
|
267
|
-
deterministic replay.
|
|
278
|
+
loop, durable sessions, policy engine, child agents, evidence-gated completion.
|
|
268
279
|
- **sandbox** — kernel-enforced containment under the policy engine
|
|
269
280
|
(`keryx harness exec`), with filesystem boundaries, network posture and, on
|
|
270
281
|
macOS, a domain allowlist with credential masking.
|
|
@@ -301,7 +312,7 @@ Alternative install paths — the managed installer (`~/.keryx` with a wrapper i
|
|
|
301
312
|
`~/.local/bin`), project-local installs, and running from source — are in the
|
|
302
313
|
[onboarding guide](docs/docs/onboarding.md).
|
|
303
314
|
|
|
304
|
-
Bare `keryx` prints the
|
|
315
|
+
Bare `keryx` prints the main commands; `keryx shell` starts the agent harness
|
|
305
316
|
described [above](#the-agent-harness).
|
|
306
317
|
|
|
307
318
|
## Agent integrations
|
|
@@ -323,18 +334,21 @@ keryx agents bootstrap install --runtime claude
|
|
|
323
334
|
keryx mcp install --runtime cursor # opt-in read-only MCP server
|
|
324
335
|
```
|
|
325
336
|
|
|
337
|
+
Each of those commands has its own `--runtime` vocabulary — run
|
|
338
|
+
`keryx <command> --help` for the values it accepts.
|
|
339
|
+
|
|
326
340
|
## Requirements and compatibility
|
|
327
341
|
|
|
328
342
|
| Requirement | Status |
|
|
329
343
|
|-------------|--------|
|
|
330
344
|
| Bun | >= 1.1.0 |
|
|
331
|
-
| Git | Required |
|
|
345
|
+
| Git | Required for hooks, `--changed` scopes and the managed installer; the core runs without it |
|
|
332
346
|
| ripgrep | Required only for `keryx ctx rg` and the agent's `search_code` tool |
|
|
333
347
|
| Model provider credential | Required only for the optional AI commands below |
|
|
334
348
|
| macOS | Full support, including the complete policy sandbox |
|
|
335
349
|
| Linux | Full core support; filesystem containment and network on/off (needs `bubblewrap`) |
|
|
336
350
|
| Windows | Core CLI is not verified in CI; the OS sandbox is macOS/Linux only |
|
|
337
|
-
| CI | Ubuntu and macOS runners on every push |
|
|
351
|
+
| CI | Ubuntu and macOS runners on every pull request and every push to `main` |
|
|
338
352
|
|
|
339
353
|
## Optional AI features
|
|
340
354
|
|
|
@@ -365,7 +379,7 @@ graph falls back to its deterministic resolver when a grammar is absent.
|
|
|
365
379
|
| Domain allowlist is macOS-only | Domain-level egress policy, credential masking and TLS termination refuse to run on Linux rather than silently doing less | Filesystem containment and network on/off work on both |
|
|
366
380
|
| No bundled embedding runtime | No semantic ranking in memory search | Lexical memory search remains fully available |
|
|
367
381
|
| ripgrep is external | `keryx ctx rg` needs `rg` on `PATH` | Install ripgrep, or let the agent read files directly |
|
|
368
|
-
| Model commands need a credential |
|
|
382
|
+
| 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 |
|
|
369
383
|
|
|
370
384
|
Full detail, including known defects and platform caveats:
|
|
371
385
|
[limitations](docs/docs/limitations.md).
|
|
@@ -383,11 +397,12 @@ keryx serve # bind 127.0.0.1 and listen
|
|
|
383
397
|
keryx serve status --json # configuration state
|
|
384
398
|
```
|
|
385
399
|
|
|
386
|
-
It is off unless you configure it,
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
400
|
+
It is off unless you configure it, and moving it off loopback takes a `--bind`
|
|
401
|
+
address plus an explicit acknowledgement in *both* the stored config and the
|
|
402
|
+
command line — either one alone refuses to start. It authenticates *before*
|
|
403
|
+
routing, so an unauthenticated caller cannot tell a known path from an unknown
|
|
404
|
+
one. The remote policy profile may never be weaker than the local one — it is
|
|
405
|
+
compared at startup, and a weaker profile refuses to bind at all. See
|
|
391
406
|
[drive keryx remotely](docs/docs/guides/drive-keryx-remotely.md) for routes and
|
|
392
407
|
setup.
|
|
393
408
|
|
|
@@ -405,7 +420,9 @@ keryx dashboard build
|
|
|
405
420
|
|
|
406
421
|
`keryx health gate --strict-warn` fails a job on the normalized health gate
|
|
407
422
|
instead of parsing raw linter/test logs, and `keryx security eval --corpus all`
|
|
408
|
-
fails on any detector breaching its committed false-negative threshold
|
|
423
|
+
fails on any detector breaching its committed false-negative threshold — from a
|
|
424
|
+
repository checkout, since the evaluation corpus is not shipped in the npm
|
|
425
|
+
package. See
|
|
409
426
|
[run keryx in CI](docs/docs/guides/run-in-ci.md).
|
|
410
427
|
|
|
411
428
|
## Documentation
|
|
@@ -415,13 +432,12 @@ Full documentation site: **<https://mrciphersmith.github.io/keryx/>**
|
|
|
415
432
|
- **[Onboarding](docs/docs/onboarding.md)** — install paths, first-run walkthrough, the build loop.
|
|
416
433
|
- **[Architecture](docs/docs/architecture.md)** — the four-layer pattern, invariants, cross-module data flows.
|
|
417
434
|
- **[Module reference](docs/docs/modules.md)** — one section per module: purpose, CLI surface, mechanics, data paths.
|
|
418
|
-
- **[CLI reference](docs/docs/cli-reference.md)** —
|
|
435
|
+
- **[CLI reference](docs/docs/cli-reference.md)** — the command surface: subcommands, flags and exit codes.
|
|
419
436
|
- **[Workspace & lifecycle](docs/docs/workspace-and-lifecycle.md)** — the `.metaproject/` contract and `init`/`update` lifecycle.
|
|
420
437
|
- **[Limitations](docs/docs/limitations.md)** — known gaps, platform caveats, and what to do instead.
|
|
421
438
|
- **[Changelog](CHANGELOG.md)** — what has landed since `v0.1.0`.
|
|
422
439
|
|
|
423
|
-
Run `keryx <command> --help`
|
|
424
|
-
surface.
|
|
440
|
+
Run `keryx <command> --help` for the live flag surface of any command.
|
|
425
441
|
|
|
426
442
|
## Local development
|
|
427
443
|
|
package/dist/cli.js
CHANGED
|
@@ -20056,6 +20056,7 @@ async function initCommand(args) {
|
|
|
20056
20056
|
if (enableMcp) {
|
|
20057
20057
|
statusLine("mcp", true, "Model Context Protocol server (opt-in)");
|
|
20058
20058
|
}
|
|
20059
|
+
const gitHooksRoot = await resolveGitHooksRoot(projectRoot);
|
|
20059
20060
|
const hookLines = [];
|
|
20060
20061
|
if (enableGdgraph) {
|
|
20061
20062
|
hookLines.push(["gdgraph post-commit", enableGdgraphHook]);
|
|
@@ -20072,13 +20073,23 @@ async function initCommand(args) {
|
|
|
20072
20073
|
}
|
|
20073
20074
|
if (enableSecurity) {
|
|
20074
20075
|
hookLines.push(["security pre-push", enableSecurityPrePushHook]);
|
|
20075
|
-
hookLines.push(["security agent (.claude)", enableSecurityAgentHook]);
|
|
20076
20076
|
}
|
|
20077
|
-
|
|
20077
|
+
const agentHookLines = enableSecurity ? [["security agent (.claude)", enableSecurityAgentHook]] : [];
|
|
20078
|
+
if (hookLines.length > 0 || agentHookLines.length > 0) {
|
|
20078
20079
|
heading("Git hooks");
|
|
20079
20080
|
for (const [label, on] of hookLines) {
|
|
20081
|
+
if (gitHooksRoot) {
|
|
20082
|
+
statusLine(label, on);
|
|
20083
|
+
} else {
|
|
20084
|
+
statusLine(label, false, "skipped - not a git repository");
|
|
20085
|
+
}
|
|
20086
|
+
}
|
|
20087
|
+
for (const [label, on] of agentHookLines) {
|
|
20080
20088
|
statusLine(label, on);
|
|
20081
20089
|
}
|
|
20090
|
+
if (!gitHooksRoot && hookLines.length > 0) {
|
|
20091
|
+
console.log(` ${style.dim("Run")} ${style.cyan("git init")} ${style.dim("and then")} ${style.cyan("keryx init")} ${style.dim("again to install them.")}`);
|
|
20092
|
+
}
|
|
20082
20093
|
}
|
|
20083
20094
|
const wroteSandboxPolicy = writeProjectSandboxPolicySkeletonIfMissing(projectRoot);
|
|
20084
20095
|
if (wroteSandboxPolicy) {
|
|
@@ -22490,6 +22501,9 @@ function parseRuntimeArg2(args) {
|
|
|
22490
22501
|
const value = optionValue(args, "--runtime") ?? "claude";
|
|
22491
22502
|
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
22492
22503
|
}
|
|
22504
|
+
function isDryRun(args) {
|
|
22505
|
+
return args.includes("--dry-run");
|
|
22506
|
+
}
|
|
22493
22507
|
async function readSettings4(file) {
|
|
22494
22508
|
if (!await pathExists(file))
|
|
22495
22509
|
return {};
|
|
@@ -22526,6 +22540,7 @@ function reportUnsupported2(ids) {
|
|
|
22526
22540
|
}
|
|
22527
22541
|
async function handleInstall(args) {
|
|
22528
22542
|
const cwd = process.cwd();
|
|
22543
|
+
const dryRun = isDryRun(args);
|
|
22529
22544
|
const { runtimes, unknown, unsupported } = resolveOrientRuntimes(parseRuntimeArg2(args));
|
|
22530
22545
|
if (unknown.length > 0) {
|
|
22531
22546
|
console.error(`Unknown runtime(s): ${unknown.join(", ")}`);
|
|
@@ -22533,35 +22548,47 @@ async function handleInstall(args) {
|
|
|
22533
22548
|
process.exitCode = 1;
|
|
22534
22549
|
return;
|
|
22535
22550
|
}
|
|
22536
|
-
console.log(
|
|
22551
|
+
console.log(`# keryx orientation injector ${dryRun ? "install \u2014 dry run, nothing written" : "installed"}`);
|
|
22537
22552
|
console.log("");
|
|
22538
22553
|
console.log("injects: compact code-graph map + wiki index + freshness at turn start");
|
|
22539
22554
|
console.log("");
|
|
22540
22555
|
for (const runtime of runtimes) {
|
|
22556
|
+
const target = path65.relative(cwd, runtime.locate(cwd));
|
|
22557
|
+
if (dryRun) {
|
|
22558
|
+
console.log(` \xB7 ${runtime.id} -> would write ${target}`);
|
|
22559
|
+
continue;
|
|
22560
|
+
}
|
|
22541
22561
|
const errors = await installOne(cwd, runtime);
|
|
22542
22562
|
if (errors.length > 0) {
|
|
22543
22563
|
for (const e of errors)
|
|
22544
22564
|
console.error(` \u2717 ${e}`);
|
|
22545
22565
|
process.exitCode = 1;
|
|
22546
22566
|
} else {
|
|
22547
|
-
console.log(` \u2713 ${runtime.id} -> ${
|
|
22567
|
+
console.log(` \u2713 ${runtime.id} -> ${target}`);
|
|
22548
22568
|
}
|
|
22549
22569
|
}
|
|
22550
22570
|
reportUnsupported2(unsupported);
|
|
22551
22571
|
}
|
|
22552
22572
|
async function handleUninstall(args) {
|
|
22553
22573
|
const cwd = process.cwd();
|
|
22574
|
+
const dryRun = isDryRun(args);
|
|
22554
22575
|
const { runtimes, unknown, unsupported } = resolveOrientRuntimes(parseRuntimeArg2(args));
|
|
22555
22576
|
if (unknown.length > 0) {
|
|
22556
22577
|
console.error(`Unknown runtime(s): ${unknown.join(", ")}`);
|
|
22557
22578
|
process.exitCode = 1;
|
|
22558
22579
|
return;
|
|
22559
22580
|
}
|
|
22560
|
-
console.log(
|
|
22581
|
+
console.log(`# keryx orientation injector uninstall${dryRun ? " \u2014 dry run, nothing written" : ""}`);
|
|
22561
22582
|
console.log("");
|
|
22562
22583
|
for (const runtime of runtimes) {
|
|
22584
|
+
const target = path65.relative(cwd, runtime.locate(cwd));
|
|
22585
|
+
if (dryRun) {
|
|
22586
|
+
const present = await pathExists(runtime.locate(cwd));
|
|
22587
|
+
console.log(` \xB7 ${runtime.id} ${present ? `-> would strip ${target}` : "nothing to remove"}`);
|
|
22588
|
+
continue;
|
|
22589
|
+
}
|
|
22563
22590
|
const removed = await uninstallOne(cwd, runtime);
|
|
22564
|
-
console.log(` ${removed ? "\u2713" : "\xB7"} ${runtime.id} ${removed ? `-> ${
|
|
22591
|
+
console.log(` ${removed ? "\u2713" : "\xB7"} ${runtime.id} ${removed ? `-> ${target}` : "nothing to remove"}`);
|
|
22565
22592
|
}
|
|
22566
22593
|
reportUnsupported2(unsupported);
|
|
22567
22594
|
}
|
|
@@ -22570,8 +22597,11 @@ function printHelp5() {
|
|
|
22570
22597
|
|
|
22571
22598
|
Usage:
|
|
22572
22599
|
keryx orient [<runtime>] emit the orientation block
|
|
22573
|
-
keryx orient install-hook [--runtime <id|all>]
|
|
22574
|
-
keryx orient uninstall-hook [--runtime <id|all>]
|
|
22600
|
+
keryx orient install-hook [--runtime <id|all>] [--dry-run]
|
|
22601
|
+
keryx orient uninstall-hook [--runtime <id|all>] [--dry-run]
|
|
22602
|
+
|
|
22603
|
+
Options:
|
|
22604
|
+
--dry-run report what would be written or stripped; change nothing
|
|
22575
22605
|
|
|
22576
22606
|
Runtimes with a context-injection hook: ${orientRuntimeIds().join(", ")}
|
|
22577
22607
|
(Windsurf/Zed have no context-injection hook \u2014 use their rules/memories.)
|
|
@@ -33807,7 +33837,11 @@ function printMcpHelp() {
|
|
|
33807
33837
|
init_fs();
|
|
33808
33838
|
init_json();
|
|
33809
33839
|
import path111 from "path";
|
|
33810
|
-
async function statusCommand() {
|
|
33840
|
+
async function statusCommand(args = []) {
|
|
33841
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
33842
|
+
printHelp14();
|
|
33843
|
+
return;
|
|
33844
|
+
}
|
|
33811
33845
|
const root = path111.join(process.cwd(), ".metaproject");
|
|
33812
33846
|
const manifestPath = path111.join(root, "metaproject.json");
|
|
33813
33847
|
if (!await pathExists(root)) {
|
|
@@ -33836,13 +33870,73 @@ async function statusCommand() {
|
|
|
33836
33870
|
console.log(` ${name}: ${moduleConfig.enabled ? "enabled" : "disabled"}`);
|
|
33837
33871
|
}
|
|
33838
33872
|
}
|
|
33873
|
+
function printHelp14() {
|
|
33874
|
+
console.log(`keryx status \u2014 whether this project has a .metaproject workspace, and which modules are on
|
|
33875
|
+
|
|
33876
|
+
Usage:
|
|
33877
|
+
keryx status
|
|
33878
|
+
|
|
33879
|
+
Reports the workspace root and one enabled/disabled line per module. Use
|
|
33880
|
+
\`keryx modules\` to toggle a module, and \`keryx init\` to create the workspace.
|
|
33881
|
+
`);
|
|
33882
|
+
}
|
|
33839
33883
|
|
|
33840
33884
|
// src/commands/harness.ts
|
|
33841
|
-
|
|
33842
|
-
import { readFileSync as readFileSync7 } from "fs";
|
|
33885
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
33843
33886
|
import path115 from "path";
|
|
33844
33887
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
33845
33888
|
|
|
33889
|
+
// src/security/harness-scan.ts
|
|
33890
|
+
init_guard();
|
|
33891
|
+
init_config();
|
|
33892
|
+
init_detect();
|
|
33893
|
+
var PERMISSIVE = {
|
|
33894
|
+
scan: () => ({ hasSecret: false }),
|
|
33895
|
+
available: false
|
|
33896
|
+
};
|
|
33897
|
+
var CATEGORY_PRECEDENCE = [
|
|
33898
|
+
"secret",
|
|
33899
|
+
"pii",
|
|
33900
|
+
"prompt-injection",
|
|
33901
|
+
"egress",
|
|
33902
|
+
"artifact-safety",
|
|
33903
|
+
"raw-retention"
|
|
33904
|
+
];
|
|
33905
|
+
async function buildHarnessScanner(cwd) {
|
|
33906
|
+
let config;
|
|
33907
|
+
try {
|
|
33908
|
+
if (!await isSecurityEnabled(cwd)) {
|
|
33909
|
+
return PERMISSIVE;
|
|
33910
|
+
}
|
|
33911
|
+
config = await loadSecurityConfig(cwd);
|
|
33912
|
+
} catch {
|
|
33913
|
+
return PERMISSIVE;
|
|
33914
|
+
}
|
|
33915
|
+
return {
|
|
33916
|
+
available: true,
|
|
33917
|
+
scan: (content) => {
|
|
33918
|
+
if (content.length === 0) {
|
|
33919
|
+
return { hasSecret: false };
|
|
33920
|
+
}
|
|
33921
|
+
let matches2;
|
|
33922
|
+
try {
|
|
33923
|
+
matches2 = runDetectors(content, config);
|
|
33924
|
+
} catch {
|
|
33925
|
+
return { hasSecret: false, scanFailed: true };
|
|
33926
|
+
}
|
|
33927
|
+
if (matches2.length === 0) {
|
|
33928
|
+
return { hasSecret: false };
|
|
33929
|
+
}
|
|
33930
|
+
const present = new Set(matches2.map((m) => m.category));
|
|
33931
|
+
const category = CATEGORY_PRECEDENCE.find((c) => present.has(c)) ?? matches2[0]?.category ?? "unknown";
|
|
33932
|
+
return { hasSecret: true, category };
|
|
33933
|
+
}
|
|
33934
|
+
};
|
|
33935
|
+
}
|
|
33936
|
+
|
|
33937
|
+
// src/commands/harness.ts
|
|
33938
|
+
init_make_provider();
|
|
33939
|
+
|
|
33846
33940
|
// src/harness/policy/profiles.ts
|
|
33847
33941
|
import { createHash as createHash8 } from "crypto";
|
|
33848
33942
|
|
|
@@ -34406,7 +34500,7 @@ async function runOffline(input2, config, deps) {
|
|
|
34406
34500
|
const executed = [];
|
|
34407
34501
|
const blockerIds = [];
|
|
34408
34502
|
const unresolvedRisks = [];
|
|
34409
|
-
const scan = () => ({ hasSecret: false });
|
|
34503
|
+
const scan = deps.scan ?? (() => ({ hasSecret: false }));
|
|
34410
34504
|
const toolNameByCall = new Map;
|
|
34411
34505
|
const actionCounts = new Map;
|
|
34412
34506
|
let executedToolCalls = 0;
|
|
@@ -34538,11 +34632,14 @@ async function runOffline(input2, config, deps) {
|
|
|
34538
34632
|
blockerIds.push(`blocker:tool-${result.errorCode ?? "failed"}:${toolCallId}`);
|
|
34539
34633
|
}
|
|
34540
34634
|
}
|
|
34541
|
-
const requiredGates = [];
|
|
34635
|
+
const requiredGates = [...deps.completionRequirements?.requiredGates ?? []];
|
|
34636
|
+
const requiredEvidenceRefs = [
|
|
34637
|
+
...deps.completionRequirements?.requiredEvidenceRefs ?? []
|
|
34638
|
+
];
|
|
34542
34639
|
const gate = evaluateCompletion({
|
|
34543
34640
|
runId,
|
|
34544
34641
|
requiredGates,
|
|
34545
|
-
requiredEvidenceRefs
|
|
34642
|
+
requiredEvidenceRefs,
|
|
34546
34643
|
presentEvidenceIds: uniqueInOrder2(presentEvidenceIds),
|
|
34547
34644
|
undisposedBlockerIds: uniqueInOrder2(blockerIds),
|
|
34548
34645
|
finalMessageEmitted
|
|
@@ -34676,6 +34773,129 @@ function earlyTermination(input2, config, deps, startedAt, reason) {
|
|
|
34676
34773
|
};
|
|
34677
34774
|
}
|
|
34678
34775
|
|
|
34776
|
+
// src/harness/replay/replay.ts
|
|
34777
|
+
var SCHEMA_VERSION5 = 1;
|
|
34778
|
+
function recomputeHashes(run) {
|
|
34779
|
+
return {
|
|
34780
|
+
sessionManifestHash: run.sessionManifestHash,
|
|
34781
|
+
eventLogHash: run.eventLogHash,
|
|
34782
|
+
toolRegistryHash: run.toolRegistryHash,
|
|
34783
|
+
transcriptHash: run.transcriptHash,
|
|
34784
|
+
expectedStateHash: run.expectedStateHash
|
|
34785
|
+
};
|
|
34786
|
+
}
|
|
34787
|
+
function buildReplayFixture(run, deps) {
|
|
34788
|
+
const hashes = recomputeHashes(run);
|
|
34789
|
+
return {
|
|
34790
|
+
schemaVersion: SCHEMA_VERSION5,
|
|
34791
|
+
fixtureId: deps.idSeq(),
|
|
34792
|
+
mode: "validate-log",
|
|
34793
|
+
sessionManifestHash: hashes.sessionManifestHash,
|
|
34794
|
+
eventLogHash: hashes.eventLogHash,
|
|
34795
|
+
toolRegistryHash: hashes.toolRegistryHash,
|
|
34796
|
+
transcriptHash: hashes.transcriptHash,
|
|
34797
|
+
expectedStateHash: hashes.expectedStateHash,
|
|
34798
|
+
noSideEffects: true
|
|
34799
|
+
};
|
|
34800
|
+
}
|
|
34801
|
+
function toRunRecord(run, meta) {
|
|
34802
|
+
return {
|
|
34803
|
+
schemaVersion: SCHEMA_VERSION5,
|
|
34804
|
+
runId: meta.runId,
|
|
34805
|
+
status: meta.status,
|
|
34806
|
+
recordedAt: meta.recordedAt,
|
|
34807
|
+
sessionManifestHash: run.sessionManifestHash,
|
|
34808
|
+
eventLogHash: run.eventLogHash,
|
|
34809
|
+
toolRegistryHash: run.toolRegistryHash,
|
|
34810
|
+
transcriptHash: run.transcriptHash,
|
|
34811
|
+
expectedStateHash: run.expectedStateHash
|
|
34812
|
+
};
|
|
34813
|
+
}
|
|
34814
|
+
var HASH_FIELDS = [
|
|
34815
|
+
"sessionManifestHash",
|
|
34816
|
+
"eventLogHash",
|
|
34817
|
+
"toolRegistryHash",
|
|
34818
|
+
"transcriptHash",
|
|
34819
|
+
"expectedStateHash"
|
|
34820
|
+
];
|
|
34821
|
+
function nonEmptyString(value) {
|
|
34822
|
+
return typeof value === "string" && value.length > 0;
|
|
34823
|
+
}
|
|
34824
|
+
function hasEveryHash(record) {
|
|
34825
|
+
return HASH_FIELDS.every((field3) => nonEmptyString(record[field3]));
|
|
34826
|
+
}
|
|
34827
|
+
function parseRunRecord(value) {
|
|
34828
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
34829
|
+
return;
|
|
34830
|
+
const record = value;
|
|
34831
|
+
if (!hasEveryHash(record) || !nonEmptyString(record.runId))
|
|
34832
|
+
return;
|
|
34833
|
+
return {
|
|
34834
|
+
schemaVersion: typeof record.schemaVersion === "number" ? record.schemaVersion : SCHEMA_VERSION5,
|
|
34835
|
+
runId: record.runId,
|
|
34836
|
+
status: nonEmptyString(record.status) ? record.status : "unknown",
|
|
34837
|
+
recordedAt: nonEmptyString(record.recordedAt) ? record.recordedAt : "",
|
|
34838
|
+
sessionManifestHash: record.sessionManifestHash,
|
|
34839
|
+
eventLogHash: record.eventLogHash,
|
|
34840
|
+
toolRegistryHash: record.toolRegistryHash,
|
|
34841
|
+
transcriptHash: record.transcriptHash,
|
|
34842
|
+
expectedStateHash: record.expectedStateHash
|
|
34843
|
+
};
|
|
34844
|
+
}
|
|
34845
|
+
function parseReplayFixture(value) {
|
|
34846
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
34847
|
+
return;
|
|
34848
|
+
const record = value;
|
|
34849
|
+
if (!hasEveryHash(record) || !nonEmptyString(record.fixtureId))
|
|
34850
|
+
return;
|
|
34851
|
+
const mode = record.mode;
|
|
34852
|
+
if (mode !== "validate-log" && mode !== "simulate-recorded-results" && mode !== "isolated-re-execute") {
|
|
34853
|
+
return;
|
|
34854
|
+
}
|
|
34855
|
+
return {
|
|
34856
|
+
schemaVersion: typeof record.schemaVersion === "number" ? record.schemaVersion : SCHEMA_VERSION5,
|
|
34857
|
+
fixtureId: record.fixtureId,
|
|
34858
|
+
mode,
|
|
34859
|
+
sessionManifestHash: record.sessionManifestHash,
|
|
34860
|
+
eventLogHash: record.eventLogHash,
|
|
34861
|
+
toolRegistryHash: record.toolRegistryHash,
|
|
34862
|
+
transcriptHash: record.transcriptHash,
|
|
34863
|
+
expectedStateHash: record.expectedStateHash,
|
|
34864
|
+
noSideEffects: record.noSideEffects !== false,
|
|
34865
|
+
...nonEmptyString(record.isolationProfile) ? { isolationProfile: record.isolationProfile } : {}
|
|
34866
|
+
};
|
|
34867
|
+
}
|
|
34868
|
+
var HASH_CHECKS = [
|
|
34869
|
+
{ field: "sessionManifestHash", kind: "state", detail: "sessionManifestHash diverged on replay (session manifest)" },
|
|
34870
|
+
{ field: "eventLogHash", kind: "event-order", detail: "eventLogHash diverged on replay (event order)" },
|
|
34871
|
+
{ field: "toolRegistryHash", kind: "tool-result", detail: "toolRegistryHash diverged on replay (tool registry)" },
|
|
34872
|
+
{ field: "transcriptHash", kind: "provider-transcript", detail: "transcriptHash diverged on replay (provider transcript)" },
|
|
34873
|
+
{ field: "expectedStateHash", kind: "state", detail: "expectedStateHash diverged on replay (terminal state)" }
|
|
34874
|
+
];
|
|
34875
|
+
function replayOffline(fixture, run, deps) {
|
|
34876
|
+
const actual = recomputeHashes(run);
|
|
34877
|
+
for (const check of HASH_CHECKS) {
|
|
34878
|
+
const expectedHash = fixture[check.field];
|
|
34879
|
+
const actualHash = actual[check.field];
|
|
34880
|
+
if (expectedHash !== actualHash) {
|
|
34881
|
+
return {
|
|
34882
|
+
ok: false,
|
|
34883
|
+
mismatch: {
|
|
34884
|
+
schemaVersion: SCHEMA_VERSION5,
|
|
34885
|
+
mismatchId: deps.idSeq(),
|
|
34886
|
+
fixtureId: fixture.fixtureId,
|
|
34887
|
+
kind: check.kind,
|
|
34888
|
+
expectedHash,
|
|
34889
|
+
actualHash,
|
|
34890
|
+
detectedAt: deps.clock(),
|
|
34891
|
+
detail: check.detail
|
|
34892
|
+
}
|
|
34893
|
+
};
|
|
34894
|
+
}
|
|
34895
|
+
}
|
|
34896
|
+
return { ok: true };
|
|
34897
|
+
}
|
|
34898
|
+
|
|
34679
34899
|
// src/harness/tool/registry.ts
|
|
34680
34900
|
import { createHash as createHash13 } from "crypto";
|
|
34681
34901
|
function canonicalize3(value) {
|
|
@@ -35208,7 +35428,7 @@ class SandboxedProcessAdapter {
|
|
|
35208
35428
|
}
|
|
35209
35429
|
spawn(command) {
|
|
35210
35430
|
const { profile, inner, platform, launcherAvailable, bwrapPath } = this.opts;
|
|
35211
|
-
const failClosed = profile.required || (this.opts.failIfUnavailable ?? true);
|
|
35431
|
+
const failClosed = profile.required || profile.network === "restricted" || (this.opts.failIfUnavailable ?? true);
|
|
35212
35432
|
if (profile.mode === "danger-full-access") {
|
|
35213
35433
|
return inner.spawn(command);
|
|
35214
35434
|
}
|
|
@@ -36026,7 +36246,8 @@ var USAGE = [
|
|
|
36026
36246
|
" [--allowed-domains a,b] [--mask-env NAME@host] [--tls-terminate] [--mask-mode auto|manual|off] [--auto-mask]",
|
|
36027
36247
|
" -- <path> [args...]",
|
|
36028
36248
|
" keryx harness extension --spec <path>",
|
|
36029
|
-
" keryx harness wave --spec <path>"
|
|
36249
|
+
" keryx harness wave --spec <path>",
|
|
36250
|
+
" keryx harness replay --record <path> [--fixture <path>] [--write-fixture <path>] [--json]"
|
|
36030
36251
|
].join(`
|
|
36031
36252
|
`);
|
|
36032
36253
|
function readOnlyProfile() {
|
|
@@ -36041,6 +36262,7 @@ function parseArgs2(args) {
|
|
|
36041
36262
|
let provider = "";
|
|
36042
36263
|
let model = "";
|
|
36043
36264
|
let baseUrl;
|
|
36265
|
+
let record;
|
|
36044
36266
|
const positional = [];
|
|
36045
36267
|
for (let i = 1;i < args.length; i++) {
|
|
36046
36268
|
const arg = args[i];
|
|
@@ -36050,6 +36272,8 @@ function parseArgs2(args) {
|
|
|
36050
36272
|
model = args[++i] ?? "";
|
|
36051
36273
|
} else if (arg === "--base-url") {
|
|
36052
36274
|
baseUrl = args[++i];
|
|
36275
|
+
} else if (arg === "--record") {
|
|
36276
|
+
record = args[++i];
|
|
36053
36277
|
} else if (arg !== undefined) {
|
|
36054
36278
|
positional.push(arg);
|
|
36055
36279
|
}
|
|
@@ -36057,6 +36281,8 @@ function parseArgs2(args) {
|
|
|
36057
36281
|
const parsed = { provider, model, prompt: positional.join(" ") };
|
|
36058
36282
|
if (baseUrl !== undefined)
|
|
36059
36283
|
parsed.baseUrl = baseUrl;
|
|
36284
|
+
if (record !== undefined)
|
|
36285
|
+
parsed.record = record;
|
|
36060
36286
|
return parsed;
|
|
36061
36287
|
}
|
|
36062
36288
|
function toStructured(result) {
|
|
@@ -36082,11 +36308,15 @@ async function harnessCommand(args, deps) {
|
|
|
36082
36308
|
harnessWave(args, deps);
|
|
36083
36309
|
return;
|
|
36084
36310
|
}
|
|
36311
|
+
if (subcommand === "replay") {
|
|
36312
|
+
harnessReplay(args, deps);
|
|
36313
|
+
return;
|
|
36314
|
+
}
|
|
36085
36315
|
if (subcommand !== "run") {
|
|
36086
36316
|
console.log(USAGE);
|
|
36087
36317
|
return;
|
|
36088
36318
|
}
|
|
36089
|
-
const { provider, model, baseUrl, prompt } = parseArgs2(args);
|
|
36319
|
+
const { provider, model, baseUrl, prompt, record } = parseArgs2(args);
|
|
36090
36320
|
const validProviders = new Set(["fake", "anthropic", "ollama"]);
|
|
36091
36321
|
if (!validProviders.has(provider) || prompt.length === 0) {
|
|
36092
36322
|
console.log(USAGE);
|
|
@@ -36129,6 +36359,7 @@ async function harnessCommand(args, deps) {
|
|
|
36129
36359
|
policyProfile: "read-only-review",
|
|
36130
36360
|
limits: { maxRunSeconds: 300, maxConcurrentChildren: 1, maxToolOutputBytes: 65536, maxRetries: 1 }
|
|
36131
36361
|
};
|
|
36362
|
+
const { scan } = await buildHarnessScanner(process.cwd());
|
|
36132
36363
|
const runDeps = {
|
|
36133
36364
|
provider: providerPort,
|
|
36134
36365
|
toolRegistry: new ToolRegistry,
|
|
@@ -36136,12 +36367,21 @@ async function harnessCommand(args, deps) {
|
|
|
36136
36367
|
policyProfile: readOnlyProfile(),
|
|
36137
36368
|
clock,
|
|
36138
36369
|
idSeq,
|
|
36139
|
-
interactive: false
|
|
36370
|
+
interactive: false,
|
|
36371
|
+
scan
|
|
36140
36372
|
};
|
|
36141
36373
|
let structured;
|
|
36142
36374
|
try {
|
|
36143
36375
|
const result = await runOffline(input2, config, runDeps);
|
|
36144
36376
|
structured = toStructured(result);
|
|
36377
|
+
if (record !== undefined && record.length > 0) {
|
|
36378
|
+
writeFileSync6(record, `${JSON.stringify(toRunRecord(result, {
|
|
36379
|
+
runId: result.output.runId,
|
|
36380
|
+
status: result.output.status,
|
|
36381
|
+
recordedAt: result.output.startedAt
|
|
36382
|
+
}), null, 2)}
|
|
36383
|
+
`, { encoding: "utf8", mode: 384 });
|
|
36384
|
+
}
|
|
36145
36385
|
} catch (error) {
|
|
36146
36386
|
structured = {
|
|
36147
36387
|
events: [],
|
|
@@ -36152,6 +36392,112 @@ async function harnessCommand(args, deps) {
|
|
|
36152
36392
|
}
|
|
36153
36393
|
console.log(JSON.stringify(structured));
|
|
36154
36394
|
}
|
|
36395
|
+
function parseReplayArgs(args) {
|
|
36396
|
+
const parsed = { json: false };
|
|
36397
|
+
for (let i = 1;i < args.length; i++) {
|
|
36398
|
+
const arg = args[i];
|
|
36399
|
+
if (arg === "--record") {
|
|
36400
|
+
parsed.record = args[++i] ?? "";
|
|
36401
|
+
} else if (arg === "--fixture") {
|
|
36402
|
+
parsed.fixture = args[++i] ?? "";
|
|
36403
|
+
} else if (arg === "--write-fixture") {
|
|
36404
|
+
parsed.writeFixture = args[++i] ?? "";
|
|
36405
|
+
} else if (arg === "--json") {
|
|
36406
|
+
parsed.json = true;
|
|
36407
|
+
}
|
|
36408
|
+
}
|
|
36409
|
+
return parsed;
|
|
36410
|
+
}
|
|
36411
|
+
function readJsonFile2(file) {
|
|
36412
|
+
let text;
|
|
36413
|
+
try {
|
|
36414
|
+
text = readFileSync7(file, "utf8");
|
|
36415
|
+
} catch (error) {
|
|
36416
|
+
return { ok: false, reason: error instanceof Error ? error.message : String(error) };
|
|
36417
|
+
}
|
|
36418
|
+
try {
|
|
36419
|
+
return { ok: true, value: JSON.parse(text) };
|
|
36420
|
+
} catch (error) {
|
|
36421
|
+
return { ok: false, reason: `not valid JSON (${error instanceof Error ? error.message : String(error)})` };
|
|
36422
|
+
}
|
|
36423
|
+
}
|
|
36424
|
+
function harnessReplay(args, deps) {
|
|
36425
|
+
const parsed = parseReplayArgs(args);
|
|
36426
|
+
if (parsed.record === undefined || parsed.record.length === 0) {
|
|
36427
|
+
console.log(USAGE);
|
|
36428
|
+
process.exitCode = 1;
|
|
36429
|
+
return;
|
|
36430
|
+
}
|
|
36431
|
+
const recordRead = readJsonFile2(parsed.record);
|
|
36432
|
+
if (!recordRead.ok) {
|
|
36433
|
+
console.error(`Cannot read run record ${parsed.record}: ${recordRead.reason}`);
|
|
36434
|
+
process.exitCode = 1;
|
|
36435
|
+
return;
|
|
36436
|
+
}
|
|
36437
|
+
const run = parseRunRecord(recordRead.value);
|
|
36438
|
+
if (run === undefined) {
|
|
36439
|
+
console.error(`${parsed.record} is not a harness run record (expected runId plus the five recorded hashes; write one with \`keryx harness run --record\`).`);
|
|
36440
|
+
process.exitCode = 1;
|
|
36441
|
+
return;
|
|
36442
|
+
}
|
|
36443
|
+
const clock = deps?.clock ?? (() => new Date().toISOString());
|
|
36444
|
+
let idCounter = 0;
|
|
36445
|
+
const idSeq = deps?.idSeq ?? (() => `${randomUUID5()}-${idCounter++}`);
|
|
36446
|
+
const fixture = (() => {
|
|
36447
|
+
if (parsed.fixture === undefined || parsed.fixture.length === 0) {
|
|
36448
|
+
return { ok: true, value: buildReplayFixture(run, { idSeq }), built: true };
|
|
36449
|
+
}
|
|
36450
|
+
const read = readJsonFile2(parsed.fixture);
|
|
36451
|
+
if (!read.ok) {
|
|
36452
|
+
return { ok: false, reason: `Cannot read fixture ${parsed.fixture}: ${read.reason}` };
|
|
36453
|
+
}
|
|
36454
|
+
const parsedFixture = parseReplayFixture(read.value);
|
|
36455
|
+
if (parsedFixture === undefined) {
|
|
36456
|
+
return {
|
|
36457
|
+
ok: false,
|
|
36458
|
+
reason: `${parsed.fixture} is not a replay fixture (expected fixtureId, mode and the five hashes).`
|
|
36459
|
+
};
|
|
36460
|
+
}
|
|
36461
|
+
return { ok: true, value: parsedFixture, built: false };
|
|
36462
|
+
})();
|
|
36463
|
+
if (!fixture.ok) {
|
|
36464
|
+
console.error(fixture.reason);
|
|
36465
|
+
process.exitCode = 1;
|
|
36466
|
+
return;
|
|
36467
|
+
}
|
|
36468
|
+
if (parsed.writeFixture !== undefined && parsed.writeFixture.length > 0) {
|
|
36469
|
+
writeFileSync6(parsed.writeFixture, `${JSON.stringify(fixture.value, null, 2)}
|
|
36470
|
+
`, {
|
|
36471
|
+
encoding: "utf8",
|
|
36472
|
+
mode: 384
|
|
36473
|
+
});
|
|
36474
|
+
}
|
|
36475
|
+
const outcome = replayOffline(fixture.value, run, { clock, idSeq });
|
|
36476
|
+
if (parsed.json) {
|
|
36477
|
+
console.log(JSON.stringify({
|
|
36478
|
+
schemaVersion: 1,
|
|
36479
|
+
mode: fixture.value.mode,
|
|
36480
|
+
runId: run.runId,
|
|
36481
|
+
fixtureId: fixture.value.fixtureId,
|
|
36482
|
+
fixtureSource: fixture.built ? "built-from-record" : parsed.fixture,
|
|
36483
|
+
ok: outcome.ok,
|
|
36484
|
+
...outcome.ok ? {} : { mismatch: outcome.mismatch }
|
|
36485
|
+
}));
|
|
36486
|
+
} else if (outcome.ok) {
|
|
36487
|
+
console.log(`Replay OK (${fixture.value.mode}): fixture ${fixture.value.fixtureId} matches run ${run.runId}.`);
|
|
36488
|
+
if (fixture.built) {
|
|
36489
|
+
console.log("Fixture was built from this record, so a match is expected; keep it with --write-fixture.");
|
|
36490
|
+
}
|
|
36491
|
+
} else {
|
|
36492
|
+
console.error(`Replay MISMATCH (${outcome.mismatch.kind}) on run ${run.runId}:`);
|
|
36493
|
+
console.error(` ${outcome.mismatch.detail ?? "hash diverged"}`);
|
|
36494
|
+
console.error(` expected ${outcome.mismatch.expectedHash}`);
|
|
36495
|
+
console.error(` actual ${outcome.mismatch.actualHash}`);
|
|
36496
|
+
}
|
|
36497
|
+
if (!outcome.ok) {
|
|
36498
|
+
process.exitCode = 1;
|
|
36499
|
+
}
|
|
36500
|
+
}
|
|
36155
36501
|
var EXEC_PARENT_REMAINING_MS = 60000;
|
|
36156
36502
|
var EXEC_DEFAULT_RUNTIME_MS = 30000;
|
|
36157
36503
|
var EXEC_OUTPUT_LIMIT_BYTES = 1e6;
|
|
@@ -36340,6 +36686,7 @@ async function harnessExec(args, deps) {
|
|
|
36340
36686
|
}
|
|
36341
36687
|
effectiveAllowEnvKeys = [...allowEnvKeys, ...Object.keys(net.envAdditions)];
|
|
36342
36688
|
}
|
|
36689
|
+
const scanner = await buildHarnessScanner(cwd);
|
|
36343
36690
|
const command = {
|
|
36344
36691
|
path: commandPath,
|
|
36345
36692
|
argv: [commandPath, ...commandArgs],
|
|
@@ -36362,7 +36709,7 @@ async function harnessExec(args, deps) {
|
|
|
36362
36709
|
envAllowlist: effectiveAllowEnvKeys,
|
|
36363
36710
|
profile: shellAllowProfile(),
|
|
36364
36711
|
interactive: true,
|
|
36365
|
-
scanAvailable:
|
|
36712
|
+
scanAvailable: scanner.available,
|
|
36366
36713
|
risk: "shell"
|
|
36367
36714
|
},
|
|
36368
36715
|
budget,
|
|
@@ -39096,7 +39443,7 @@ import {
|
|
|
39096
39443
|
mkdirSync as mkdirSync6,
|
|
39097
39444
|
readdirSync,
|
|
39098
39445
|
renameSync as renameSync2,
|
|
39099
|
-
writeFileSync as
|
|
39446
|
+
writeFileSync as writeFileSync7
|
|
39100
39447
|
} from "fs";
|
|
39101
39448
|
import path117 from "path";
|
|
39102
39449
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
@@ -39137,7 +39484,7 @@ function tighten(target) {
|
|
|
39137
39484
|
}
|
|
39138
39485
|
function atomicWriteText(file, body) {
|
|
39139
39486
|
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
39140
|
-
|
|
39487
|
+
writeFileSync7(tmp, body, { encoding: "utf8", mode: 384 });
|
|
39141
39488
|
renameSync2(tmp, file);
|
|
39142
39489
|
}
|
|
39143
39490
|
function atomicWriteJson(file, value) {
|
|
@@ -39395,6 +39742,39 @@ function compactSession(handle, context, archive, opts) {
|
|
|
39395
39742
|
atomicWriteJson(path117.join(withCount.dir, "summary.json"), withCount.summary);
|
|
39396
39743
|
return { handle: withCount, context: result.context, result };
|
|
39397
39744
|
}
|
|
39745
|
+
|
|
39746
|
+
class UnknownSessionError extends Error {
|
|
39747
|
+
idOrPrefix;
|
|
39748
|
+
constructor(idOrPrefix) {
|
|
39749
|
+
super(`no session matching "${idOrPrefix}" in this project`);
|
|
39750
|
+
this.idOrPrefix = idOrPrefix;
|
|
39751
|
+
this.name = "UnknownSessionError";
|
|
39752
|
+
}
|
|
39753
|
+
}
|
|
39754
|
+
function forkSession(opts) {
|
|
39755
|
+
const source = findSession(opts.cwd, opts.sourceIdOrPrefix, opts.dataDir);
|
|
39756
|
+
if (source === undefined) {
|
|
39757
|
+
throw new UnknownSessionError(opts.sourceIdOrPrefix);
|
|
39758
|
+
}
|
|
39759
|
+
const context = loadContext(opts.cwd, source.id, opts.dataDir);
|
|
39760
|
+
const archive = loadArchive(opts.cwd, source.id, opts.dataDir);
|
|
39761
|
+
const title = opts.title !== undefined && opts.title.trim().length > 0 ? opts.title.trim() : `${source.title} (fork)`;
|
|
39762
|
+
const created = createSession({
|
|
39763
|
+
cwd: opts.cwd,
|
|
39764
|
+
parentSessionId: source.id,
|
|
39765
|
+
title,
|
|
39766
|
+
...opts.dataDir !== undefined ? { dataDir: opts.dataDir } : {},
|
|
39767
|
+
...source.provider !== undefined ? { provider: source.provider } : {},
|
|
39768
|
+
...source.model !== undefined ? { model: source.model } : {}
|
|
39769
|
+
});
|
|
39770
|
+
const handle = persistHistory(created, context, {
|
|
39771
|
+
archive,
|
|
39772
|
+
title,
|
|
39773
|
+
...source.provider !== undefined ? { provider: source.provider } : {},
|
|
39774
|
+
...source.model !== undefined ? { model: source.model } : {}
|
|
39775
|
+
});
|
|
39776
|
+
return { handle, source, messageCount: context.length, archiveCount: archive.length };
|
|
39777
|
+
}
|
|
39398
39778
|
function openSession(opts) {
|
|
39399
39779
|
const cwd = opts.cwd;
|
|
39400
39780
|
const dataDir = opts.dataDir;
|
|
@@ -44039,7 +44419,7 @@ async function shellCommand(args2) {
|
|
|
44039
44419
|
async function sessionsCommand(args2) {
|
|
44040
44420
|
const sub = args2[0] ?? "list";
|
|
44041
44421
|
if (sub === "--help" || sub === "-h" || sub === "help") {
|
|
44042
|
-
|
|
44422
|
+
printHelp15();
|
|
44043
44423
|
return;
|
|
44044
44424
|
}
|
|
44045
44425
|
const cwd = process.cwd();
|
|
@@ -44061,7 +44441,7 @@ async function sessionsCommand(args2) {
|
|
|
44061
44441
|
console.log(pad("ID", 10) + pad("UPDATED", 22) + pad("MSGS", 6) + pad("MODEL", 24) + "TITLE");
|
|
44062
44442
|
for (const s of rows) {
|
|
44063
44443
|
const model = s.provider !== undefined && s.model !== undefined ? `${s.provider}/${s.model}` : s.model ?? "-";
|
|
44064
|
-
console.log(pad(shortSessionId(s.id), 10) + pad(s.updatedAt.slice(0, 19).replace("T", " "), 22) + pad(String(s.messageCount), 6) + pad(clip4(model, 22), 24) + s.title);
|
|
44444
|
+
console.log(pad(shortSessionId(s.id), 10) + pad(s.updatedAt.slice(0, 19).replace("T", " "), 22) + pad(String(s.messageCount), 6) + pad(clip4(model, 22), 24) + (s.parentSessionId !== undefined ? `\u21B3 ${s.title}` : s.title));
|
|
44065
44445
|
}
|
|
44066
44446
|
console.log("");
|
|
44067
44447
|
console.log("Resume: keryx shell -r <id> Continue last: keryx shell -c");
|
|
@@ -44091,12 +44471,61 @@ async function sessionsCommand(args2) {
|
|
|
44091
44471
|
}
|
|
44092
44472
|
return;
|
|
44093
44473
|
}
|
|
44474
|
+
if (sub === "fork") {
|
|
44475
|
+
const id = args2[1];
|
|
44476
|
+
if (id === undefined || id.length === 0 || id.startsWith("-")) {
|
|
44477
|
+
console.error('Usage: keryx sessions fork <id> [--title "<t>"]');
|
|
44478
|
+
process.exitCode = 1;
|
|
44479
|
+
return;
|
|
44480
|
+
}
|
|
44481
|
+
const titleIndex = args2.indexOf("--title");
|
|
44482
|
+
const title = titleIndex >= 0 ? args2[titleIndex + 1] : undefined;
|
|
44483
|
+
if (titleIndex >= 0 && (title === undefined || title.length === 0)) {
|
|
44484
|
+
console.error('Usage: keryx sessions fork <id> [--title "<t>"]');
|
|
44485
|
+
process.exitCode = 1;
|
|
44486
|
+
return;
|
|
44487
|
+
}
|
|
44488
|
+
try {
|
|
44489
|
+
const forked = forkSession({ cwd, sourceIdOrPrefix: id, ...title !== undefined ? { title } : {} });
|
|
44490
|
+
if (args2.includes("--json")) {
|
|
44491
|
+
console.log(JSON.stringify({
|
|
44492
|
+
schemaVersion: 1,
|
|
44493
|
+
id: forked.handle.summary.id,
|
|
44494
|
+
parentSessionId: forked.source.id,
|
|
44495
|
+
title: forked.handle.summary.title,
|
|
44496
|
+
messageCount: forked.messageCount,
|
|
44497
|
+
archiveMessageCount: forked.archiveCount,
|
|
44498
|
+
dir: forked.handle.dir
|
|
44499
|
+
}, null, 2));
|
|
44500
|
+
return;
|
|
44501
|
+
}
|
|
44502
|
+
console.log(`Forked ${shortSessionId(forked.source.id)} -> ${shortSessionId(forked.handle.summary.id)}`);
|
|
44503
|
+
console.log(` title: ${forked.handle.summary.title}`);
|
|
44504
|
+
console.log(` parent: ${forked.source.id}`);
|
|
44505
|
+
console.log(` history: ${forked.messageCount} context / ${forked.archiveCount} archive`);
|
|
44506
|
+
console.log("");
|
|
44507
|
+
console.log(`Resume: keryx shell -r ${shortSessionId(forked.handle.summary.id)}`);
|
|
44508
|
+
} catch (cause) {
|
|
44509
|
+
if (cause instanceof UnknownSessionError) {
|
|
44510
|
+
console.error(`No session "${id}" in this project. Use \`keryx sessions list\`.`);
|
|
44511
|
+
process.exitCode = 1;
|
|
44512
|
+
return;
|
|
44513
|
+
}
|
|
44514
|
+
if (cause instanceof TranscriptUnreadableError) {
|
|
44515
|
+
console.error(`Cannot fork session "${id}": ${cause.message}`);
|
|
44516
|
+
process.exitCode = 1;
|
|
44517
|
+
return;
|
|
44518
|
+
}
|
|
44519
|
+
throw cause;
|
|
44520
|
+
}
|
|
44521
|
+
return;
|
|
44522
|
+
}
|
|
44094
44523
|
if (sub === "path") {
|
|
44095
44524
|
console.log(projectSessionsDir(cwd));
|
|
44096
44525
|
return;
|
|
44097
44526
|
}
|
|
44098
44527
|
console.error(`Unknown sessions subcommand: ${sub}`);
|
|
44099
|
-
|
|
44528
|
+
printHelp15();
|
|
44100
44529
|
process.exitCode = 1;
|
|
44101
44530
|
}
|
|
44102
44531
|
function pad(s, n) {
|
|
@@ -44105,13 +44534,14 @@ function pad(s, n) {
|
|
|
44105
44534
|
function clip4(s, n) {
|
|
44106
44535
|
return s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
|
|
44107
44536
|
}
|
|
44108
|
-
function
|
|
44537
|
+
function printHelp15() {
|
|
44109
44538
|
console.log(`keryx sessions
|
|
44110
44539
|
|
|
44111
44540
|
Per-project interactive shell sessions (isolated by git root / cwd).
|
|
44112
44541
|
|
|
44113
44542
|
Usage:
|
|
44114
44543
|
keryx sessions list [--json] List sessions for the current project
|
|
44544
|
+
keryx sessions fork <id> Branch a session into a new one (--title, --json)
|
|
44115
44545
|
keryx sessions export <id> Export transcript as Markdown
|
|
44116
44546
|
keryx sessions path Print the on-disk sessions directory
|
|
44117
44547
|
|
|
@@ -44160,7 +44590,7 @@ function buildInitFlags(next, profile) {
|
|
|
44160
44590
|
async function modulesCommand(args2 = []) {
|
|
44161
44591
|
const sub = args2[0];
|
|
44162
44592
|
if (sub === "--help" || sub === "-h" || sub === "help") {
|
|
44163
|
-
|
|
44593
|
+
printHelp16();
|
|
44164
44594
|
return;
|
|
44165
44595
|
}
|
|
44166
44596
|
const wantsJson = args2.includes("--json") && (sub === undefined || sub === "status" || sub === "list" || sub === "--json");
|
|
@@ -44217,7 +44647,7 @@ async function modulesCommand(args2 = []) {
|
|
|
44217
44647
|
}
|
|
44218
44648
|
}
|
|
44219
44649
|
} else {
|
|
44220
|
-
|
|
44650
|
+
printHelp16();
|
|
44221
44651
|
process.exitCode = 1;
|
|
44222
44652
|
return;
|
|
44223
44653
|
}
|
|
@@ -44260,7 +44690,7 @@ function setsEqual(a, b) {
|
|
|
44260
44690
|
}
|
|
44261
44691
|
return true;
|
|
44262
44692
|
}
|
|
44263
|
-
function
|
|
44693
|
+
function printHelp16() {
|
|
44264
44694
|
helpTitle("keryx modules", "view and toggle Metaproject modules");
|
|
44265
44695
|
helpUsage([
|
|
44266
44696
|
"keryx modules",
|
|
@@ -44616,7 +45046,7 @@ import {
|
|
|
44616
45046
|
renameSync as renameSync3,
|
|
44617
45047
|
statSync as statSync5,
|
|
44618
45048
|
unlinkSync as unlinkSync2,
|
|
44619
|
-
writeFileSync as
|
|
45049
|
+
writeFileSync as writeFileSync8
|
|
44620
45050
|
} from "fs";
|
|
44621
45051
|
import path120 from "path";
|
|
44622
45052
|
function serveCredentialPath(dir) {
|
|
@@ -44695,7 +45125,7 @@ function writeStore(store, dir) {
|
|
|
44695
45125
|
ensureKeryxConfigDir(dir);
|
|
44696
45126
|
const handle = openSync3(temp, "wx", 384);
|
|
44697
45127
|
try {
|
|
44698
|
-
|
|
45128
|
+
writeFileSync8(handle, `${JSON.stringify(store, null, 2)}
|
|
44699
45129
|
`, { encoding: "utf8" });
|
|
44700
45130
|
fsyncSync2(handle);
|
|
44701
45131
|
} finally {
|
|
@@ -45729,7 +46159,7 @@ var ENABLE_FLAG = "--enable";
|
|
|
45729
46159
|
var DISABLE_FLAG = "--disable";
|
|
45730
46160
|
async function serveCommand(args2 = []) {
|
|
45731
46161
|
if (args2.includes("--help") || args2.includes("-h") || args2[0] === "help") {
|
|
45732
|
-
|
|
46162
|
+
printHelp17();
|
|
45733
46163
|
return;
|
|
45734
46164
|
}
|
|
45735
46165
|
const sub = args2[0];
|
|
@@ -45750,14 +46180,14 @@ async function serveCommand(args2 = []) {
|
|
|
45750
46180
|
return;
|
|
45751
46181
|
}
|
|
45752
46182
|
console.error(`Unknown serve command: ${sanitizeForDisplay(sub)}`);
|
|
45753
|
-
|
|
46183
|
+
printHelp17();
|
|
45754
46184
|
process.exitCode = 1;
|
|
45755
46185
|
}
|
|
45756
46186
|
async function runServe(args2) {
|
|
45757
46187
|
const parsed = parseArgs3(args2, BIND_FLAGS, [ACK_FLAG]);
|
|
45758
46188
|
if (!parsed.ok) {
|
|
45759
46189
|
console.error(parsed.message);
|
|
45760
|
-
|
|
46190
|
+
printHelp17();
|
|
45761
46191
|
process.exitCode = 1;
|
|
45762
46192
|
return;
|
|
45763
46193
|
}
|
|
@@ -45859,7 +46289,7 @@ function runStatus6(args2) {
|
|
|
45859
46289
|
const parsed = parseArgs3(args2, [], ["--json"]);
|
|
45860
46290
|
if (!parsed.ok) {
|
|
45861
46291
|
console.error(parsed.message);
|
|
45862
|
-
|
|
46292
|
+
printHelp17();
|
|
45863
46293
|
process.exitCode = 1;
|
|
45864
46294
|
return;
|
|
45865
46295
|
}
|
|
@@ -45906,7 +46336,7 @@ function runToken(args2) {
|
|
|
45906
46336
|
const rest = parseArgs3(args2.slice(1), [], []);
|
|
45907
46337
|
if (!rest.ok) {
|
|
45908
46338
|
console.error(rest.message);
|
|
45909
|
-
|
|
46339
|
+
printHelp17();
|
|
45910
46340
|
process.exitCode = 1;
|
|
45911
46341
|
return;
|
|
45912
46342
|
}
|
|
@@ -45947,7 +46377,7 @@ function runToken(args2) {
|
|
|
45947
46377
|
return;
|
|
45948
46378
|
}
|
|
45949
46379
|
console.error(sub === undefined ? "Missing token subcommand" : `Unknown token command: ${sanitizeForDisplay(sub)}`);
|
|
45950
|
-
|
|
46380
|
+
printHelp17();
|
|
45951
46381
|
process.exitCode = 1;
|
|
45952
46382
|
}
|
|
45953
46383
|
function printTokenOnce(token) {
|
|
@@ -45972,7 +46402,7 @@ function runConfig(args2) {
|
|
|
45972
46402
|
const parsed = parseArgs3(args2.slice(1), BIND_FLAGS, [ACK_FLAG, FORCE_FLAG]);
|
|
45973
46403
|
if (!parsed.ok) {
|
|
45974
46404
|
console.error(parsed.message);
|
|
45975
|
-
|
|
46405
|
+
printHelp17();
|
|
45976
46406
|
process.exitCode = 1;
|
|
45977
46407
|
return;
|
|
45978
46408
|
}
|
|
@@ -46022,7 +46452,7 @@ function runConfig(args2) {
|
|
|
46022
46452
|
const parsed = parseArgs3(args2.slice(1), [], ["--json"]);
|
|
46023
46453
|
if (!parsed.ok) {
|
|
46024
46454
|
console.error(parsed.message);
|
|
46025
|
-
|
|
46455
|
+
printHelp17();
|
|
46026
46456
|
process.exitCode = 1;
|
|
46027
46457
|
return;
|
|
46028
46458
|
}
|
|
@@ -46039,14 +46469,14 @@ function runConfig(args2) {
|
|
|
46039
46469
|
return;
|
|
46040
46470
|
}
|
|
46041
46471
|
console.error(sub === undefined ? "Missing config subcommand" : `Unknown config command: ${sanitizeForDisplay(sub)}`);
|
|
46042
|
-
|
|
46472
|
+
printHelp17();
|
|
46043
46473
|
process.exitCode = 1;
|
|
46044
46474
|
}
|
|
46045
46475
|
function runConfigSet(args2) {
|
|
46046
46476
|
const parsed = parseArgs3(args2, BIND_FLAGS, [ACK_FLAG, NO_ACK_FLAG, ENABLE_FLAG, DISABLE_FLAG]);
|
|
46047
46477
|
if (!parsed.ok) {
|
|
46048
46478
|
console.error(parsed.message);
|
|
46049
|
-
|
|
46479
|
+
printHelp17();
|
|
46050
46480
|
process.exitCode = 1;
|
|
46051
46481
|
return;
|
|
46052
46482
|
}
|
|
@@ -46117,7 +46547,7 @@ function printConfig(config) {
|
|
|
46117
46547
|
console.log(` approvals: expire after ${config.approval.expirySeconds}s, max ${config.approval.maxPendingPerSession} pending per session`);
|
|
46118
46548
|
console.log(` non-loopback acknowledged: ${config.bind.acknowledgeNonLoopback === true}`);
|
|
46119
46549
|
}
|
|
46120
|
-
function
|
|
46550
|
+
function printHelp17() {
|
|
46121
46551
|
helpTitle("keryx serve", "loopback-bound HTTP entry over this install (off by default)");
|
|
46122
46552
|
helpUsage([
|
|
46123
46553
|
`keryx serve [--bind <addr>] [--port <n>] [--profile <name>] [${ACK_FLAG}]`,
|
|
@@ -46162,7 +46592,7 @@ init_git_hooks();
|
|
|
46162
46592
|
async function updateCommand(args2 = []) {
|
|
46163
46593
|
const options = parseUpdateArgs(args2);
|
|
46164
46594
|
if (options.help) {
|
|
46165
|
-
|
|
46595
|
+
printHelp18();
|
|
46166
46596
|
return;
|
|
46167
46597
|
}
|
|
46168
46598
|
const projectRoot = process.cwd();
|
|
@@ -47272,7 +47702,7 @@ function runtimeSourcePath2(relativePath) {
|
|
|
47272
47702
|
function escapeRegExp5(value) {
|
|
47273
47703
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
47274
47704
|
}
|
|
47275
|
-
function
|
|
47705
|
+
function printHelp18() {
|
|
47276
47706
|
helpTitle("keryx update", "refresh .metaproject service files (data left untouched)");
|
|
47277
47707
|
helpUsage(["keryx update [--skip-runtime] [--hooks] [--no-tasks]"]);
|
|
47278
47708
|
heading("Default behavior");
|
|
@@ -47300,7 +47730,7 @@ async function dashboardCommand(args2 = []) {
|
|
|
47300
47730
|
const options = parseOptions(args2);
|
|
47301
47731
|
const subcommand = options.positionals[0];
|
|
47302
47732
|
if (!subcommand || options.help) {
|
|
47303
|
-
|
|
47733
|
+
printHelp19();
|
|
47304
47734
|
return;
|
|
47305
47735
|
}
|
|
47306
47736
|
if (subcommand === "build") {
|
|
@@ -47318,7 +47748,7 @@ async function dashboardCommand(args2 = []) {
|
|
|
47318
47748
|
return;
|
|
47319
47749
|
}
|
|
47320
47750
|
console.log(` ${style.red(symbols.cross)} Unknown dashboard command: ${subcommand}`);
|
|
47321
|
-
|
|
47751
|
+
printHelp19();
|
|
47322
47752
|
process.exitCode = 1;
|
|
47323
47753
|
}
|
|
47324
47754
|
function parseOptions(args2) {
|
|
@@ -47343,7 +47773,7 @@ async function openFile(filePath) {
|
|
|
47343
47773
|
});
|
|
47344
47774
|
});
|
|
47345
47775
|
}
|
|
47346
|
-
function
|
|
47776
|
+
function printHelp19() {
|
|
47347
47777
|
helpTitle("keryx dashboard", "build and open the human dashboard");
|
|
47348
47778
|
helpUsage([
|
|
47349
47779
|
"keryx dashboard build",
|
|
@@ -48183,7 +48613,7 @@ Usage:
|
|
|
48183
48613
|
// package.json
|
|
48184
48614
|
var package_default = {
|
|
48185
48615
|
name: "@mrciphersmith/keryx",
|
|
48186
|
-
version: "0.2.
|
|
48616
|
+
version: "0.2.16",
|
|
48187
48617
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
48188
48618
|
private: false,
|
|
48189
48619
|
publishConfig: {
|
|
@@ -48255,7 +48685,7 @@ var package_default = {
|
|
|
48255
48685
|
var VERSION2 = package_default.version;
|
|
48256
48686
|
var CLI_ROUTES = {
|
|
48257
48687
|
init: initCommand,
|
|
48258
|
-
status:
|
|
48688
|
+
status: statusCommand,
|
|
48259
48689
|
modules: modulesCommand,
|
|
48260
48690
|
projects: projectsCommand,
|
|
48261
48691
|
serve: serveCommand,
|
|
@@ -48290,7 +48720,7 @@ async function main() {
|
|
|
48290
48720
|
const args2 = process.argv.slice(2);
|
|
48291
48721
|
const command = args2[0];
|
|
48292
48722
|
if (command === "--help" || command === "-h" || command === "help" || !command) {
|
|
48293
|
-
|
|
48723
|
+
printHelp20();
|
|
48294
48724
|
return;
|
|
48295
48725
|
}
|
|
48296
48726
|
if (command === "--version" || command === "-v") {
|
|
@@ -48303,22 +48733,25 @@ async function main() {
|
|
|
48303
48733
|
return;
|
|
48304
48734
|
}
|
|
48305
48735
|
console.error(`Unknown command: ${command}`);
|
|
48306
|
-
|
|
48736
|
+
printHelp20();
|
|
48307
48737
|
process.exitCode = 1;
|
|
48308
48738
|
}
|
|
48309
|
-
function
|
|
48739
|
+
function printHelp20() {
|
|
48310
48740
|
console.log(`keryx ${VERSION2}
|
|
48311
48741
|
|
|
48312
48742
|
Usage:
|
|
48313
48743
|
keryx Show CLI usage
|
|
48314
48744
|
keryx shell [-c|--continue] [-r|--resume [id]] [--provider <p>] [--model <m>] [--base-url <url>] [--agent|--chat] [--tui|--no-tui]
|
|
48315
48745
|
Start TUI agent shell (sessions are per-project)
|
|
48316
|
-
keryx sessions list|export <id>|path
|
|
48317
|
-
|
|
48746
|
+
keryx sessions list|fork <id>|export <id>|path
|
|
48747
|
+
List / branch / export sessions for the current project
|
|
48748
|
+
keryx harness run --provider <fake|anthropic|ollama> --model <m> [--base-url <url>] [--record <path>] "<prompt>"
|
|
48318
48749
|
keryx harness exec [--allow-env KEY]... [--max-runtime-ms N] [--allow-real-subprocess]
|
|
48319
48750
|
[--allowed-domains a,b] [--mask-env NAME@host] [--tls-terminate] [--mask-mode auto|manual|off] [--auto-mask] -- <path> [args...]
|
|
48320
48751
|
keryx harness extension --spec <path>
|
|
48321
48752
|
keryx harness wave --spec <path>
|
|
48753
|
+
keryx harness replay --record <path> [--fixture <path>] [--write-fixture <path>] [--json]
|
|
48754
|
+
Validate a recorded run's log against a fixture (no re-execution)
|
|
48322
48755
|
keryx init [--yes] [--no-gdgraph] [--no-gdctx] [--no-gdwiki] [--no-gdskills] [--gdskills-profile recommended] [--no-health] [--no-testing] [--no-memory] [--no-gdgraph-hook] [--no-gdskills-hook] [--no-health-hook] [--no-testing-post-commit-hook] [--no-testing-pre-push-hook]
|
|
48323
48756
|
keryx status
|
|
48324
48757
|
keryx modules [status | enable <name> | disable <name>]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.16",
|
|
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": {
|