@mrciphersmith/keryx 0.2.84 → 0.2.88
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/dist/core.js
CHANGED
|
@@ -10291,6 +10291,7 @@ var init_wiki_layer = __esm(() => {
|
|
|
10291
10291
|
init_fs();
|
|
10292
10292
|
var exports_service = {};
|
|
10293
10293
|
__export(exports_service, {
|
|
10294
|
+
baseBranchCondition: () => baseBranchCondition,
|
|
10294
10295
|
createFlowService: () => createFlowService
|
|
10295
10296
|
});
|
|
10296
10297
|
import { mkdir as mkdir5, readFile as readFile9, rename as rename3 } from "fs/promises";
|
|
@@ -13098,6 +13099,7 @@ function createFlowService(deps) {
|
|
|
13098
13099
|
acChecksum: null,
|
|
13099
13100
|
acConfirmed: {},
|
|
13100
13101
|
pr: { url: null },
|
|
13102
|
+
...input.baseBranch === undefined ? {} : { baseBranch: input.baseBranch },
|
|
13101
13103
|
tasks: DEFAULT_TASKS.map((task) => ({ ...task, status: "todo" })),
|
|
13102
13104
|
history: [{ at: createdAt, event: "created" }]
|
|
13103
13105
|
};
|
|
@@ -13306,6 +13308,10 @@ function createFlowService(deps) {
|
|
|
13306
13308
|
if (!pr.isDraft) {
|
|
13307
13309
|
detail += " (warning: PR is not a draft)";
|
|
13308
13310
|
}
|
|
13311
|
+
if (flow.baseBranch === undefined && typeof pr.baseRefName === "string" && pr.baseRefName !== "") {
|
|
13312
|
+
flow.baseBranch = pr.baseRefName;
|
|
13313
|
+
detail += ` (base: ${pr.baseRefName})`;
|
|
13314
|
+
}
|
|
13309
13315
|
} else {
|
|
13310
13316
|
detail += " (tracker unavailable: existence not verified)";
|
|
13311
13317
|
}
|
|
@@ -13357,6 +13363,7 @@ function createFlowService(deps) {
|
|
|
13357
13363
|
detail: "tracker unavailable; verify PR checks manually"
|
|
13358
13364
|
});
|
|
13359
13365
|
}
|
|
13366
|
+
gates.push(await baseBranchCondition(cwd, flow, mergedCommit ?? undefined, flow.pr.url && deps.tracker && await deps.tracker.detect() ? (await deps.tracker.prStatus(flow.pr.url)).baseRefName : undefined, commitContainedIn));
|
|
13360
13367
|
gates.push(taskGate(flow));
|
|
13361
13368
|
try {
|
|
13362
13369
|
gates.push(await reviewGate({
|
|
@@ -13661,6 +13668,70 @@ function buildIssueComment(flow, gates) {
|
|
|
13661
13668
|
].join(`
|
|
13662
13669
|
`);
|
|
13663
13670
|
}
|
|
13671
|
+
async function baseBranchCondition(cwd, flow, mergedCommit, prBase, containedIn) {
|
|
13672
|
+
const recorded = flow.baseBranch;
|
|
13673
|
+
if (recorded === undefined || recorded.trim() === "") {
|
|
13674
|
+
return {
|
|
13675
|
+
name: "base-branch",
|
|
13676
|
+
status: "skipped",
|
|
13677
|
+
detail: "not recorded: this flow never named a base branch, so there is nothing to compare the merge against. Not a pass \u2014 `keryx flow init --base <branch>` or `flow implemented --pr <url>` records one."
|
|
13678
|
+
};
|
|
13679
|
+
}
|
|
13680
|
+
if (mergedCommit !== undefined) {
|
|
13681
|
+
const contained = await containedIn(cwd, mergedCommit, `origin/${recorded}`);
|
|
13682
|
+
if (contained === null) {
|
|
13683
|
+
return {
|
|
13684
|
+
name: "base-branch",
|
|
13685
|
+
status: "fail",
|
|
13686
|
+
detail: `unobserved: origin/${recorded} could not be resolved, so whether ${mergedCommit} landed there is unknown. Fetch the remote (\`git fetch origin ${recorded}\`) and re-run; an unresolvable base is not a passing one.`
|
|
13687
|
+
};
|
|
13688
|
+
}
|
|
13689
|
+
return contained ? {
|
|
13690
|
+
name: "base-branch",
|
|
13691
|
+
status: "pass",
|
|
13692
|
+
detail: `${mergedCommit} is contained in origin/${recorded}, the base this flow recorded`
|
|
13693
|
+
} : {
|
|
13694
|
+
name: "base-branch",
|
|
13695
|
+
status: "fail",
|
|
13696
|
+
detail: `violated: this flow recorded base ${recorded}, but ${mergedCommit} is not contained in origin/${recorded}. The merge landed somewhere else.`
|
|
13697
|
+
};
|
|
13698
|
+
}
|
|
13699
|
+
if (prBase === undefined || prBase === null || prBase === "") {
|
|
13700
|
+
return {
|
|
13701
|
+
name: "base-branch",
|
|
13702
|
+
status: "fail",
|
|
13703
|
+
detail: `unobserved: this flow recorded base ${recorded}, but the tracker did not report the pull request's base. An unread base is not a matching one.`
|
|
13704
|
+
};
|
|
13705
|
+
}
|
|
13706
|
+
return prBase === recorded ? {
|
|
13707
|
+
name: "base-branch",
|
|
13708
|
+
status: "pass",
|
|
13709
|
+
detail: `the pull request targets ${prBase}, the base this flow recorded`
|
|
13710
|
+
} : {
|
|
13711
|
+
name: "base-branch",
|
|
13712
|
+
status: "fail",
|
|
13713
|
+
detail: `violated: this flow recorded base ${recorded}, but the pull request now targets ${prBase}. It was retargeted after the base was recorded.`
|
|
13714
|
+
};
|
|
13715
|
+
}
|
|
13716
|
+
async function commitContainedIn(cwd, commit, ref) {
|
|
13717
|
+
if (!/^[0-9a-f]{7,64}$/i.test(commit)) {
|
|
13718
|
+
return null;
|
|
13719
|
+
}
|
|
13720
|
+
const resolved = Bun.spawn(["git", "rev-parse", "--verify", `${ref}^{commit}`], {
|
|
13721
|
+
cwd,
|
|
13722
|
+
stdout: "ignore",
|
|
13723
|
+
stderr: "ignore"
|
|
13724
|
+
});
|
|
13725
|
+
if (await resolved.exited !== 0) {
|
|
13726
|
+
return null;
|
|
13727
|
+
}
|
|
13728
|
+
const process2 = Bun.spawn(["git", "merge-base", "--is-ancestor", commit, ref], {
|
|
13729
|
+
cwd,
|
|
13730
|
+
stdout: "ignore",
|
|
13731
|
+
stderr: "ignore"
|
|
13732
|
+
});
|
|
13733
|
+
return await process2.exited === 0;
|
|
13734
|
+
}
|
|
13664
13735
|
async function verifyCommitOnMain(cwd, commit) {
|
|
13665
13736
|
if (!/^[0-9a-f]{7,64}$/i.test(commit)) {
|
|
13666
13737
|
return { status: "fail", detail: "merged commit must be a hexadecimal Git commit id" };
|
|
@@ -18423,63 +18494,112 @@ var CONTRACTS = [
|
|
|
18423
18494
|
{
|
|
18424
18495
|
name: "agent-event",
|
|
18425
18496
|
fileName: "agent-event.schema.json",
|
|
18426
|
-
description: "Append-only lifecycle event emitted by orchestrators and subagents."
|
|
18497
|
+
description: "Append-only lifecycle event emitted by orchestrators and subagents.",
|
|
18498
|
+
enforcement: {
|
|
18499
|
+
kind: "none",
|
|
18500
|
+
reason: "Events are appended by whichever agent is running, in that agent's own runtime. No keryx process sits between the emitter and the log, so there is no point at which a malformed event could be refused rather than written."
|
|
18501
|
+
}
|
|
18427
18502
|
},
|
|
18428
18503
|
{
|
|
18429
18504
|
name: "flow-orchestrator-input",
|
|
18430
18505
|
fileName: "flow-orchestrator-input-contract.schema.json",
|
|
18431
18506
|
description: "Dispatch payload handed to flow-orchestrator (request + base branch + completion outcome + constraints).",
|
|
18432
|
-
sourcePath: "src/gdskills/bundled/skills/orchestration/flow-orchestrator/input-contract.schema.json"
|
|
18507
|
+
sourcePath: "src/gdskills/bundled/skills/orchestration/flow-orchestrator/input-contract.schema.json",
|
|
18508
|
+
enforcement: {
|
|
18509
|
+
kind: "none",
|
|
18510
|
+
reason: "This payload is handed from one AGENT to another. In a session driven by a host agent's own dispatch tool, no keryx process is on that path, so nothing can refuse a malformed dispatch. Same structural position as reviewer-input, recorded for the same reason. Registering it made `keryx skills contracts validate --schema flow-orchestrator-input` possible, which is a validator an agent must remember to run, not an enforcement."
|
|
18511
|
+
}
|
|
18433
18512
|
},
|
|
18434
18513
|
{
|
|
18435
18514
|
name: "review-pr-feedback-input",
|
|
18436
18515
|
fileName: "review-pr-feedback-input-contract.schema.json",
|
|
18437
18516
|
description: "Request handed to review-pr-feedback (PR reference, --fix, and the operator confirmation --fix requires).",
|
|
18438
|
-
sourcePath: "src/gdskills/bundled/skills/review/review-pr-feedback/input-contract.schema.json"
|
|
18517
|
+
sourcePath: "src/gdskills/bundled/skills/review/review-pr-feedback/input-contract.schema.json",
|
|
18518
|
+
enforcement: {
|
|
18519
|
+
kind: "none",
|
|
18520
|
+
reason: "The request is what an operator or a host agent hands the skill before any keryx command runs, so keryx is not yet in the path when the payload would have to be refused. This is the one where that hurts most: `--fix` merges third-party review comments into somebody else's pull request, and `operator_confirmed` is the fence. The conditional is expressed in the schema and `keryx skills contracts validate --schema review-pr-feedback-input` will apply it \u2014 but only when something invokes it."
|
|
18521
|
+
}
|
|
18439
18522
|
},
|
|
18440
18523
|
{
|
|
18441
18524
|
name: "review-pr-feedback-output",
|
|
18442
18525
|
fileName: "review-pr-feedback-output-contract.schema.json",
|
|
18443
18526
|
description: "Result review-pr-feedback returns: verdict counts, the injection-screen record, and what the fix run merged.",
|
|
18444
|
-
sourcePath: "src/gdskills/bundled/skills/review/review-pr-feedback/output-contract.schema.json"
|
|
18527
|
+
sourcePath: "src/gdskills/bundled/skills/review/review-pr-feedback/output-contract.schema.json",
|
|
18528
|
+
enforcement: {
|
|
18529
|
+
kind: "opt-in",
|
|
18530
|
+
module: "src/commands/review.ts",
|
|
18531
|
+
switchedOnBy: "keryx review comments reply --result <file>",
|
|
18532
|
+
refuses: "A result that contradicts itself, before the pass is built and before any network call: an analyze-mode run reporting a branch and a merge, or a record saying the injection screen never ran while claiming it excluded comments."
|
|
18533
|
+
}
|
|
18445
18534
|
},
|
|
18446
18535
|
{
|
|
18447
18536
|
name: "job-orchestrator-state",
|
|
18448
18537
|
fileName: "job-orchestrator-state.schema.json",
|
|
18449
18538
|
description: "Persisted job package state (.metaproject/jobs/<name>/state.json), written by `keryx job`.",
|
|
18450
|
-
sourcePath: "src/gdskills/bundled/skills/orchestration/job-orchestrator/state.schema.json"
|
|
18539
|
+
sourcePath: "src/gdskills/bundled/skills/orchestration/job-orchestrator/state.schema.json",
|
|
18540
|
+
enforcement: {
|
|
18541
|
+
kind: "production",
|
|
18542
|
+
module: "src/job/store.ts",
|
|
18543
|
+
refuses: "`writeJob` refuses to write a state file that does not satisfy the schema, so an invalid job package cannot reach disk."
|
|
18544
|
+
}
|
|
18451
18545
|
},
|
|
18452
18546
|
{
|
|
18453
18547
|
name: "orchestrator-state",
|
|
18454
18548
|
fileName: "orchestrator-state.schema.json",
|
|
18455
|
-
description: "Persisted resumable orchestrator state."
|
|
18549
|
+
description: "Persisted resumable orchestrator state.",
|
|
18550
|
+
enforcement: {
|
|
18551
|
+
kind: "none",
|
|
18552
|
+
reason: "No keryx command reads or writes this file. It describes state an orchestrator agent persists for itself, in its own runtime, so there is no keryx-owned write path to refuse at. Contrast job-orchestrator-state, which looks similar and IS enforced for exactly one reason: `keryx job` writes that one."
|
|
18553
|
+
}
|
|
18456
18554
|
},
|
|
18457
18555
|
{
|
|
18458
18556
|
name: "review-finding",
|
|
18459
18557
|
fileName: "review-finding.schema.json",
|
|
18460
|
-
description: "Normalized reviewer finding consumed by review-orchestrator and learning flows."
|
|
18558
|
+
description: "Normalized reviewer finding consumed by review-orchestrator and learning flows.",
|
|
18559
|
+
enforcement: {
|
|
18560
|
+
kind: "production",
|
|
18561
|
+
module: "src/review/managed.ts",
|
|
18562
|
+
refuses: "`createManagedReviewPackage` refuses to record findings that do not satisfy the schema, and the disposition sub-schema is applied the same way, so a review round cannot be written with a malformed finding or an unreadable outcome."
|
|
18563
|
+
}
|
|
18461
18564
|
},
|
|
18462
18565
|
{
|
|
18463
18566
|
name: "subagent-dispatch",
|
|
18464
18567
|
fileName: "subagent-dispatch.schema.json",
|
|
18465
|
-
description: "Orchestrator-to-subagent dispatch payload."
|
|
18568
|
+
description: "Orchestrator-to-subagent dispatch payload.",
|
|
18569
|
+
enforcement: {
|
|
18570
|
+
kind: "none",
|
|
18571
|
+
reason: "The dispatch is built and consumed inside the harness without the canonical schema being loaded: `subagent-dispatch` appears only as a label on the extension metadata. Its sibling `subagent-result` IS validated, because the harness parses a child's reply back and has to decide whether it is well-formed; nothing performs the equivalent act on the way out."
|
|
18572
|
+
}
|
|
18466
18573
|
},
|
|
18467
18574
|
{
|
|
18468
18575
|
name: "subagent-result",
|
|
18469
18576
|
fileName: "subagent-result.schema.json",
|
|
18470
|
-
description: "Subagent-to-orchestrator result payload."
|
|
18577
|
+
description: "Subagent-to-orchestrator result payload.",
|
|
18578
|
+
enforcement: {
|
|
18579
|
+
kind: "production",
|
|
18580
|
+
module: "src/harness/external/runtime.ts",
|
|
18581
|
+
refuses: "A child's reply that does not parse as a well-formed result is rejected when the harness reads it back, so a malformed result cannot be persisted as if the child had succeeded."
|
|
18582
|
+
}
|
|
18471
18583
|
},
|
|
18472
18584
|
{
|
|
18473
18585
|
name: "task-implementer-input",
|
|
18474
18586
|
fileName: "task-implementer-input-contract.schema.json",
|
|
18475
18587
|
description: "Task request handed to task-implementer (task + workspace + automation), validated before dispatch.",
|
|
18476
|
-
sourcePath: "src/gdskills/bundled/skills/orchestration/task-implementer/input-contract.schema.json"
|
|
18588
|
+
sourcePath: "src/gdskills/bundled/skills/orchestration/task-implementer/input-contract.schema.json",
|
|
18589
|
+
enforcement: {
|
|
18590
|
+
kind: "none",
|
|
18591
|
+
reason: "Dispatched agent-to-agent, like flow-orchestrator-input. The skill's Phase 1.4 lists five `ASSERT \u2026 \u2192 ABORT(\u2026)` refusals; registering the contract is what lets `keryx skills contracts validate` perform them, and nothing forces that call. The comment beside this registration has said so since it was written."
|
|
18592
|
+
}
|
|
18477
18593
|
},
|
|
18478
18594
|
{
|
|
18479
18595
|
name: "task-implementer-output",
|
|
18480
18596
|
fileName: "task-implementer-output-contract.schema.json",
|
|
18481
18597
|
description: "JSON result task-implementer writes in Phase 6.1 before it emits its STATUS line.",
|
|
18482
|
-
sourcePath: "src/gdskills/bundled/skills/orchestration/task-implementer/output-contract.schema.json"
|
|
18598
|
+
sourcePath: "src/gdskills/bundled/skills/orchestration/task-implementer/output-contract.schema.json",
|
|
18599
|
+
enforcement: {
|
|
18600
|
+
kind: "none",
|
|
18601
|
+
reason: "The skill writes this file itself, in its own runtime, and no keryx command reads it back. Contrast subagent-result, which is the same shape of thing and IS enforced for one reason: the harness reads that one back and must decide whether it is well-formed."
|
|
18602
|
+
}
|
|
18483
18603
|
}
|
|
18484
18604
|
];
|
|
18485
18605
|
async function loadSchema(name) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.88",
|
|
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": {
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"check": "bun run lint && bun run typecheck && bun run typecheck:scripts && bun test",
|
|
55
55
|
"check:core": "bun run lint && bun run typecheck && bun run typecheck:scripts && bun run test:core",
|
|
56
56
|
"check:doc-links": "bun scripts/check-doc-links.ts",
|
|
57
|
+
"check:retired-spellings": "bun scripts/check-retired-cli-spellings.ts",
|
|
57
58
|
"baseline:review-precision": "bun scripts/review-precision-baseline.ts",
|
|
58
59
|
"test:guards": "bun test src/lib/config-dir.ast.test.ts src/lib/config-dir.readers.test.ts src/lib/production-graph.test.ts src/harness/policy/profiles.test.ts src/lib/serve-server.test.ts src/gdskills/agent-catalogue-xref.test.ts src/gdskills/enforcement-claims.test.ts",
|
|
59
60
|
"lint": "eslint ."
|
|
@@ -198,13 +198,15 @@ keryx flow init --issue <url>
|
|
|
198
198
|
or:
|
|
199
199
|
|
|
200
200
|
```bash
|
|
201
|
-
keryx flow init --title "<short formalized problem>"
|
|
201
|
+
keryx flow init --title "<short formalized problem>" --base "<branch the work must land on>"
|
|
202
202
|
```
|
|
203
203
|
|
|
204
204
|
5. Run `keryx flow status <id>` and read the flow package.
|
|
205
|
-
6.
|
|
206
|
-
`context.md` and `journal.md
|
|
207
|
-
|
|
205
|
+
6. `--base` records the branch in the flow record itself, where the completion
|
|
206
|
+
gate reads it. Note it in `context.md` and `journal.md` as well if it helps a
|
|
207
|
+
reader, but prose is not what the gate checks: before this flag the base
|
|
208
|
+
survived only as something an agent had written down, which is detectable at
|
|
209
|
+
dispatch and undetectable at completion.
|
|
208
210
|
|
|
209
211
|
## Phase 1: Initialize The Flow Package
|
|
210
212
|
|
|
@@ -442,9 +444,16 @@ How should this flow end?
|
|
|
442
444
|
|
|
443
445
|
3. Follow the selected outcome:
|
|
444
446
|
|
|
445
|
-
- **A - Create PR and merge:** create or confirm a PR in the author's name
|
|
446
|
-
|
|
447
|
-
|
|
447
|
+
- **A - Create PR and merge:** create or confirm a PR in the author's name,
|
|
448
|
+
opened against the base recorded at initialization. Do not mark the flow
|
|
449
|
+
implemented or complete before the PR is merged into that branch.
|
|
450
|
+
|
|
451
|
+
`keryx flow complete` now checks this rather than asking you to confirm it by
|
|
452
|
+
eye: its `base-branch` condition compares where the merge landed against the
|
|
453
|
+
base in the record, and refuses when they differ. It reports three states,
|
|
454
|
+
and only one of them is a pass — a flow that recorded no base gets
|
|
455
|
+
`not recorded`, which is not a pass either. So the useful thing to do here is
|
|
456
|
+
make sure the base WAS recorded, not to re-verify the merge yourself.
|
|
448
457
|
|
|
449
458
|
### A dispatched run answers the question from its input
|
|
450
459
|
|
|
@@ -591,6 +591,7 @@ merge, with every reviewer unanswered.
|
|
|
591
591
|
|
|
592
592
|
```bash
|
|
593
593
|
keryx review comments reply --repo <owner/repo> --pr <n> --outcomes <file|-> \
|
|
594
|
+
--result <this-run's-output.json> \
|
|
594
595
|
--sha <mergedHeadSha> --final [--dry-run] [--flow-link <url>]
|
|
595
596
|
```
|
|
596
597
|
|
|
@@ -598,6 +599,17 @@ Run it **after** the merge, never during the loop: a reply written mid-round sta
|
|
|
598
599
|
an intention, and by the time the reviewer reads it the intention has changed.
|
|
599
600
|
`--final` is required by the command; it is not a reminder that can be skipped.
|
|
600
601
|
|
|
602
|
+
`--result` hands this run's own output to the one keryx-owned point that can
|
|
603
|
+
refuse it. Write the result described in Step 11 to a file first and pass it
|
|
604
|
+
here: the command validates it against `review-pr-feedback-output` BEFORE
|
|
605
|
+
posting anything, so a result that contradicts itself — analyze mode reporting a
|
|
606
|
+
merge, or a screen recorded as never having run while excluding comments — stops
|
|
607
|
+
here instead of being published under your name.
|
|
608
|
+
|
|
609
|
+
Optional in the CLI, and the registry records this contract as `opt-in` rather
|
|
610
|
+
than enforced for exactly that reason: omit the flag and nothing is checked.
|
|
611
|
+
Passing it is the whole of the enforcement.
|
|
612
|
+
|
|
601
613
|
The judgement is yours; the command owns the mechanics. It routes an inline comment
|
|
602
614
|
to its thread (`pulls/{n}/comments/{id}/replies`) and a review-submission body or
|
|
603
615
|
PR-level comment to one top-level comment that names what it answers — GitHub
|