@mrciphersmith/keryx 0.2.84 → 0.2.94
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
|
@@ -3467,9 +3467,17 @@ var init_secrets = __esm(() => {
|
|
|
3467
3467
|
confidence: 0.9,
|
|
3468
3468
|
remediation: "Treat the JWT as a live credential; do not persist it."
|
|
3469
3469
|
},
|
|
3470
|
+
{
|
|
3471
|
+
policyId: "secrets.credential-file-assignment",
|
|
3472
|
+
regex: /\b((?:aws_)?secret_access_key|aws_session_token|aws_secret_key|client_secret)\s*[:=]\s*["']?([^\s"'#]{6,})/gi,
|
|
3473
|
+
severity: "critical",
|
|
3474
|
+
confidence: 0.95,
|
|
3475
|
+
remediation: "Rotate the credential; it was read from a credentials file into agent-visible output.",
|
|
3476
|
+
valueGroup: 2
|
|
3477
|
+
},
|
|
3470
3478
|
{
|
|
3471
3479
|
policyId: "secrets.env-assignment",
|
|
3472
|
-
regex: /\b([A-Z0-9_]*(?:DATABASE_URL|JWT_SECRET|SECRET(?:_KEY)?|API_?KEY|ACCESS_?TOKEN|AUTH_?TOKEN|PASSWORD|PRIVATE_?KEY|TOKEN))["']?\s*[:=]\s*["']?([^\s"'#]{6,})/g,
|
|
3480
|
+
regex: /\b([A-Z0-9_]*(?:DATABASE_URL|JWT_SECRET|SECRET(?:_KEY)?|API_?KEY|ACCESS_?KEY|ACCESS_?TOKEN|AUTH_?TOKEN|PASSWORD|PRIVATE_?KEY|TOKEN))["']?\s*[:=]\s*["']?([^\s"'#]{6,})/g,
|
|
3473
3481
|
severity: "high",
|
|
3474
3482
|
confidence: 0.85,
|
|
3475
3483
|
remediation: "Move the secret to an untracked env file or secrets manager.",
|
|
@@ -6880,6 +6888,7 @@ __export(exports_service2, {
|
|
|
6880
6888
|
analyze: () => analyze,
|
|
6881
6889
|
createSecurityService: () => createSecurityService,
|
|
6882
6890
|
memoizeResolved: () => memoizeResolved,
|
|
6891
|
+
redactSensitiveText: () => redactSensitiveText,
|
|
6883
6892
|
runGate: () => runGate,
|
|
6884
6893
|
runReport: () => runReport,
|
|
6885
6894
|
runScan: () => runScan,
|
|
@@ -7100,6 +7109,7 @@ var init_service = __esm(() => {
|
|
|
7100
7109
|
init_incidents();
|
|
7101
7110
|
init_self_protect();
|
|
7102
7111
|
init_path_scan();
|
|
7112
|
+
init_redact();
|
|
7103
7113
|
RECOGNIZED_GATES = new Set(["pass", "needs-approval", "incomplete", "fail"]);
|
|
7104
7114
|
});
|
|
7105
7115
|
|
|
@@ -10291,6 +10301,7 @@ var init_wiki_layer = __esm(() => {
|
|
|
10291
10301
|
init_fs();
|
|
10292
10302
|
var exports_service = {};
|
|
10293
10303
|
__export(exports_service, {
|
|
10304
|
+
baseBranchCondition: () => baseBranchCondition,
|
|
10294
10305
|
createFlowService: () => createFlowService
|
|
10295
10306
|
});
|
|
10296
10307
|
import { mkdir as mkdir5, readFile as readFile9, rename as rename3 } from "fs/promises";
|
|
@@ -10478,6 +10489,14 @@ function validateNode(value, schema, docRoot, valuePath, ctx, errors) {
|
|
|
10478
10489
|
if (maximum !== undefined && value > maximum) {
|
|
10479
10490
|
errors.push({ path: valuePath, message: `Expected number <= ${maximum}` });
|
|
10480
10491
|
}
|
|
10492
|
+
const exclusiveMinimum = asNumber(schema.exclusiveMinimum);
|
|
10493
|
+
if (exclusiveMinimum !== undefined && value <= exclusiveMinimum) {
|
|
10494
|
+
errors.push({ path: valuePath, message: `Expected number > ${exclusiveMinimum}` });
|
|
10495
|
+
}
|
|
10496
|
+
const exclusiveMaximum = asNumber(schema.exclusiveMaximum);
|
|
10497
|
+
if (exclusiveMaximum !== undefined && value >= exclusiveMaximum) {
|
|
10498
|
+
errors.push({ path: valuePath, message: `Expected number < ${exclusiveMaximum}` });
|
|
10499
|
+
}
|
|
10481
10500
|
}
|
|
10482
10501
|
if (Array.isArray(value)) {
|
|
10483
10502
|
if (schema.items !== undefined) {
|
|
@@ -10526,6 +10545,11 @@ function validateNode(value, schema, docRoot, valuePath, ctx, errors) {
|
|
|
10526
10545
|
}
|
|
10527
10546
|
}
|
|
10528
10547
|
}
|
|
10548
|
+
if (isPlainObject(schema.propertyNames)) {
|
|
10549
|
+
for (const key of Object.keys(value)) {
|
|
10550
|
+
validateNode(key, schema.propertyNames, docRoot, `${valuePath}.${key}`, ctx, errors);
|
|
10551
|
+
}
|
|
10552
|
+
}
|
|
10529
10553
|
const properties = isPlainObject(schema.properties) ? schema.properties : {};
|
|
10530
10554
|
const additional = schema.additionalProperties;
|
|
10531
10555
|
for (const [key, nested] of Object.entries(value)) {
|
|
@@ -13098,6 +13122,7 @@ function createFlowService(deps) {
|
|
|
13098
13122
|
acChecksum: null,
|
|
13099
13123
|
acConfirmed: {},
|
|
13100
13124
|
pr: { url: null },
|
|
13125
|
+
...input.baseBranch === undefined ? {} : { baseBranch: input.baseBranch },
|
|
13101
13126
|
tasks: DEFAULT_TASKS.map((task) => ({ ...task, status: "todo" })),
|
|
13102
13127
|
history: [{ at: createdAt, event: "created" }]
|
|
13103
13128
|
};
|
|
@@ -13306,6 +13331,10 @@ function createFlowService(deps) {
|
|
|
13306
13331
|
if (!pr.isDraft) {
|
|
13307
13332
|
detail += " (warning: PR is not a draft)";
|
|
13308
13333
|
}
|
|
13334
|
+
if (flow.baseBranch === undefined && typeof pr.baseRefName === "string" && pr.baseRefName !== "") {
|
|
13335
|
+
flow.baseBranch = pr.baseRefName;
|
|
13336
|
+
detail += ` (base: ${pr.baseRefName})`;
|
|
13337
|
+
}
|
|
13309
13338
|
} else {
|
|
13310
13339
|
detail += " (tracker unavailable: existence not verified)";
|
|
13311
13340
|
}
|
|
@@ -13357,6 +13386,7 @@ function createFlowService(deps) {
|
|
|
13357
13386
|
detail: "tracker unavailable; verify PR checks manually"
|
|
13358
13387
|
});
|
|
13359
13388
|
}
|
|
13389
|
+
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
13390
|
gates.push(taskGate(flow));
|
|
13361
13391
|
try {
|
|
13362
13392
|
gates.push(await reviewGate({
|
|
@@ -13661,6 +13691,70 @@ function buildIssueComment(flow, gates) {
|
|
|
13661
13691
|
].join(`
|
|
13662
13692
|
`);
|
|
13663
13693
|
}
|
|
13694
|
+
async function baseBranchCondition(cwd, flow, mergedCommit, prBase, containedIn) {
|
|
13695
|
+
const recorded = flow.baseBranch;
|
|
13696
|
+
if (recorded === undefined || recorded.trim() === "") {
|
|
13697
|
+
return {
|
|
13698
|
+
name: "base-branch",
|
|
13699
|
+
status: "skipped",
|
|
13700
|
+
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."
|
|
13701
|
+
};
|
|
13702
|
+
}
|
|
13703
|
+
if (mergedCommit !== undefined) {
|
|
13704
|
+
const contained = await containedIn(cwd, mergedCommit, `origin/${recorded}`);
|
|
13705
|
+
if (contained === null) {
|
|
13706
|
+
return {
|
|
13707
|
+
name: "base-branch",
|
|
13708
|
+
status: "fail",
|
|
13709
|
+
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.`
|
|
13710
|
+
};
|
|
13711
|
+
}
|
|
13712
|
+
return contained ? {
|
|
13713
|
+
name: "base-branch",
|
|
13714
|
+
status: "pass",
|
|
13715
|
+
detail: `${mergedCommit} is contained in origin/${recorded}, the base this flow recorded`
|
|
13716
|
+
} : {
|
|
13717
|
+
name: "base-branch",
|
|
13718
|
+
status: "fail",
|
|
13719
|
+
detail: `violated: this flow recorded base ${recorded}, but ${mergedCommit} is not contained in origin/${recorded}. The merge landed somewhere else.`
|
|
13720
|
+
};
|
|
13721
|
+
}
|
|
13722
|
+
if (prBase === undefined || prBase === null || prBase === "") {
|
|
13723
|
+
return {
|
|
13724
|
+
name: "base-branch",
|
|
13725
|
+
status: "fail",
|
|
13726
|
+
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.`
|
|
13727
|
+
};
|
|
13728
|
+
}
|
|
13729
|
+
return prBase === recorded ? {
|
|
13730
|
+
name: "base-branch",
|
|
13731
|
+
status: "pass",
|
|
13732
|
+
detail: `the pull request targets ${prBase}, the base this flow recorded`
|
|
13733
|
+
} : {
|
|
13734
|
+
name: "base-branch",
|
|
13735
|
+
status: "fail",
|
|
13736
|
+
detail: `violated: this flow recorded base ${recorded}, but the pull request now targets ${prBase}. It was retargeted after the base was recorded.`
|
|
13737
|
+
};
|
|
13738
|
+
}
|
|
13739
|
+
async function commitContainedIn(cwd, commit, ref) {
|
|
13740
|
+
if (!/^[0-9a-f]{7,64}$/i.test(commit)) {
|
|
13741
|
+
return null;
|
|
13742
|
+
}
|
|
13743
|
+
const resolved = Bun.spawn(["git", "rev-parse", "--verify", `${ref}^{commit}`], {
|
|
13744
|
+
cwd,
|
|
13745
|
+
stdout: "ignore",
|
|
13746
|
+
stderr: "ignore"
|
|
13747
|
+
});
|
|
13748
|
+
if (await resolved.exited !== 0) {
|
|
13749
|
+
return null;
|
|
13750
|
+
}
|
|
13751
|
+
const process2 = Bun.spawn(["git", "merge-base", "--is-ancestor", commit, ref], {
|
|
13752
|
+
cwd,
|
|
13753
|
+
stdout: "ignore",
|
|
13754
|
+
stderr: "ignore"
|
|
13755
|
+
});
|
|
13756
|
+
return await process2.exited === 0;
|
|
13757
|
+
}
|
|
13664
13758
|
async function verifyCommitOnMain(cwd, commit) {
|
|
13665
13759
|
if (!/^[0-9a-f]{7,64}$/i.test(commit)) {
|
|
13666
13760
|
return { status: "fail", detail: "merged commit must be a hexadecimal Git commit id" };
|
|
@@ -18423,63 +18517,112 @@ var CONTRACTS = [
|
|
|
18423
18517
|
{
|
|
18424
18518
|
name: "agent-event",
|
|
18425
18519
|
fileName: "agent-event.schema.json",
|
|
18426
|
-
description: "Append-only lifecycle event emitted by orchestrators and subagents."
|
|
18520
|
+
description: "Append-only lifecycle event emitted by orchestrators and subagents.",
|
|
18521
|
+
enforcement: {
|
|
18522
|
+
kind: "none",
|
|
18523
|
+
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."
|
|
18524
|
+
}
|
|
18427
18525
|
},
|
|
18428
18526
|
{
|
|
18429
18527
|
name: "flow-orchestrator-input",
|
|
18430
18528
|
fileName: "flow-orchestrator-input-contract.schema.json",
|
|
18431
18529
|
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"
|
|
18530
|
+
sourcePath: "src/gdskills/bundled/skills/orchestration/flow-orchestrator/input-contract.schema.json",
|
|
18531
|
+
enforcement: {
|
|
18532
|
+
kind: "none",
|
|
18533
|
+
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."
|
|
18534
|
+
}
|
|
18433
18535
|
},
|
|
18434
18536
|
{
|
|
18435
18537
|
name: "review-pr-feedback-input",
|
|
18436
18538
|
fileName: "review-pr-feedback-input-contract.schema.json",
|
|
18437
18539
|
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"
|
|
18540
|
+
sourcePath: "src/gdskills/bundled/skills/review/review-pr-feedback/input-contract.schema.json",
|
|
18541
|
+
enforcement: {
|
|
18542
|
+
kind: "none",
|
|
18543
|
+
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."
|
|
18544
|
+
}
|
|
18439
18545
|
},
|
|
18440
18546
|
{
|
|
18441
18547
|
name: "review-pr-feedback-output",
|
|
18442
18548
|
fileName: "review-pr-feedback-output-contract.schema.json",
|
|
18443
18549
|
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"
|
|
18550
|
+
sourcePath: "src/gdskills/bundled/skills/review/review-pr-feedback/output-contract.schema.json",
|
|
18551
|
+
enforcement: {
|
|
18552
|
+
kind: "opt-in",
|
|
18553
|
+
module: "src/commands/review.ts",
|
|
18554
|
+
switchedOnBy: "keryx review comments reply --result <file>",
|
|
18555
|
+
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."
|
|
18556
|
+
}
|
|
18445
18557
|
},
|
|
18446
18558
|
{
|
|
18447
18559
|
name: "job-orchestrator-state",
|
|
18448
18560
|
fileName: "job-orchestrator-state.schema.json",
|
|
18449
18561
|
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"
|
|
18562
|
+
sourcePath: "src/gdskills/bundled/skills/orchestration/job-orchestrator/state.schema.json",
|
|
18563
|
+
enforcement: {
|
|
18564
|
+
kind: "production",
|
|
18565
|
+
module: "src/job/store.ts",
|
|
18566
|
+
refuses: "`writeJob` refuses to write a state file that does not satisfy the schema, so an invalid job package cannot reach disk."
|
|
18567
|
+
}
|
|
18451
18568
|
},
|
|
18452
18569
|
{
|
|
18453
18570
|
name: "orchestrator-state",
|
|
18454
18571
|
fileName: "orchestrator-state.schema.json",
|
|
18455
|
-
description: "Persisted resumable orchestrator state."
|
|
18572
|
+
description: "Persisted resumable orchestrator state.",
|
|
18573
|
+
enforcement: {
|
|
18574
|
+
kind: "none",
|
|
18575
|
+
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."
|
|
18576
|
+
}
|
|
18456
18577
|
},
|
|
18457
18578
|
{
|
|
18458
18579
|
name: "review-finding",
|
|
18459
18580
|
fileName: "review-finding.schema.json",
|
|
18460
|
-
description: "Normalized reviewer finding consumed by review-orchestrator and learning flows."
|
|
18581
|
+
description: "Normalized reviewer finding consumed by review-orchestrator and learning flows.",
|
|
18582
|
+
enforcement: {
|
|
18583
|
+
kind: "production",
|
|
18584
|
+
module: "src/review/managed.ts",
|
|
18585
|
+
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."
|
|
18586
|
+
}
|
|
18461
18587
|
},
|
|
18462
18588
|
{
|
|
18463
18589
|
name: "subagent-dispatch",
|
|
18464
18590
|
fileName: "subagent-dispatch.schema.json",
|
|
18465
|
-
description: "Orchestrator-to-subagent dispatch payload."
|
|
18591
|
+
description: "Orchestrator-to-subagent dispatch payload.",
|
|
18592
|
+
enforcement: {
|
|
18593
|
+
kind: "none",
|
|
18594
|
+
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."
|
|
18595
|
+
}
|
|
18466
18596
|
},
|
|
18467
18597
|
{
|
|
18468
18598
|
name: "subagent-result",
|
|
18469
18599
|
fileName: "subagent-result.schema.json",
|
|
18470
|
-
description: "Subagent-to-orchestrator result payload."
|
|
18600
|
+
description: "Subagent-to-orchestrator result payload.",
|
|
18601
|
+
enforcement: {
|
|
18602
|
+
kind: "production",
|
|
18603
|
+
module: "src/harness/external/runtime.ts",
|
|
18604
|
+
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."
|
|
18605
|
+
}
|
|
18471
18606
|
},
|
|
18472
18607
|
{
|
|
18473
18608
|
name: "task-implementer-input",
|
|
18474
18609
|
fileName: "task-implementer-input-contract.schema.json",
|
|
18475
18610
|
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"
|
|
18611
|
+
sourcePath: "src/gdskills/bundled/skills/orchestration/task-implementer/input-contract.schema.json",
|
|
18612
|
+
enforcement: {
|
|
18613
|
+
kind: "none",
|
|
18614
|
+
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."
|
|
18615
|
+
}
|
|
18477
18616
|
},
|
|
18478
18617
|
{
|
|
18479
18618
|
name: "task-implementer-output",
|
|
18480
18619
|
fileName: "task-implementer-output-contract.schema.json",
|
|
18481
18620
|
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"
|
|
18621
|
+
sourcePath: "src/gdskills/bundled/skills/orchestration/task-implementer/output-contract.schema.json",
|
|
18622
|
+
enforcement: {
|
|
18623
|
+
kind: "none",
|
|
18624
|
+
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."
|
|
18625
|
+
}
|
|
18483
18626
|
}
|
|
18484
18627
|
];
|
|
18485
18628
|
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.94",
|
|
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": {
|
|
@@ -49,11 +49,12 @@
|
|
|
49
49
|
"test:client:terminal": "bun test src/tui/ src/commands/shell",
|
|
50
50
|
"test:client:streaming": "bun test src/harness/provider/",
|
|
51
51
|
"test:client:cancel-resume": "bun test src/harness/run/ src/harness/resume/ src/harness/session/ src/session/ src/commands/sessions",
|
|
52
|
-
"test:client:runtime": "bun test src/harness/ src/mcp-client/ src/agents/ src/commands/agent src/commands/harness src/commands/providers",
|
|
52
|
+
"test:client:runtime": "bun test src/harness/ src/mcp-client/ src/mcp-servers/ src/agents/ src/commands/agent src/commands/harness src/commands/providers",
|
|
53
53
|
"test:client": "bun run test:client:terminal && bun run test:client:streaming && bun run test:client:cancel-resume && bun run test:client:runtime",
|
|
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
|