@mrciphersmith/keryx 0.3.48 → 0.3.51
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 +12 -6
- package/dist/cli.js +6890 -4481
- package/dist/core.js +208 -92
- package/package.json +1 -1
package/dist/core.js
CHANGED
|
@@ -23145,6 +23145,7 @@ __export(exports_service2, {
|
|
|
23145
23145
|
buildAcKindReport: () => buildAcKindReport,
|
|
23146
23146
|
classifyAcCriterionNotCheckable: () => classifyAcCriterionNotCheckable,
|
|
23147
23147
|
classifyNotCheckable: () => classifyNotCheckable,
|
|
23148
|
+
completionFixHint: () => completionFixHint,
|
|
23148
23149
|
computeAcFacts: () => computeAcFacts,
|
|
23149
23150
|
confirmPreconditionError: () => confirmPreconditionError,
|
|
23150
23151
|
createFlowService: () => createFlowService,
|
|
@@ -23153,11 +23154,13 @@ __export(exports_service2, {
|
|
|
23153
23154
|
extractCriterionTokens: () => extractCriterionTokens,
|
|
23154
23155
|
factsOnlyVerdict: () => factsOnlyVerdict,
|
|
23155
23156
|
fencedLines: () => fencedLines,
|
|
23157
|
+
flowDeliveryStatementFrom: () => flowDeliveryStatementFrom,
|
|
23156
23158
|
flowStatementFrom: () => flowStatementFrom,
|
|
23157
23159
|
hashDiff: () => hashDiff,
|
|
23158
23160
|
intentNoteForNewFlow: () => intentNoteForNewFlow,
|
|
23159
23161
|
isFrozen: () => isFrozen,
|
|
23160
23162
|
notCheckableVerdict: () => notCheckableVerdict,
|
|
23163
|
+
outcomeBulletsFrom: () => outcomeBulletsFrom,
|
|
23161
23164
|
parseAcceptanceCriteria: () => parseAcceptanceCriteria,
|
|
23162
23165
|
parseOutcomeAuthor: () => parseOutcomeAuthor,
|
|
23163
23166
|
proseOutsideFences: () => proseOutsideFences,
|
|
@@ -25860,6 +25863,25 @@ function statementFrom(markdown, headings) {
|
|
|
25860
25863
|
function flowStatementFrom(description) {
|
|
25861
25864
|
return statementFrom(description, FLOW_STATEMENT_HEADINGS);
|
|
25862
25865
|
}
|
|
25866
|
+
function flowDeliveryStatementFrom(description) {
|
|
25867
|
+
return statementFrom(description, [/^expected outcome$/i, /^problem$/i]);
|
|
25868
|
+
}
|
|
25869
|
+
function outcomeBulletsFrom(markdown) {
|
|
25870
|
+
const body = sectionOf(markdown, /^outcome criteri(?:a|on)$/i);
|
|
25871
|
+
if (body === null)
|
|
25872
|
+
return null;
|
|
25873
|
+
const bullets = [];
|
|
25874
|
+
for (const line of body.split(/\r?\n/)) {
|
|
25875
|
+
const bullet = /^\s*[-*]\s+(.*)$/.exec(line);
|
|
25876
|
+
if ((bullet?.[1] ?? line).trim() === OUTCOME_HINT)
|
|
25877
|
+
continue;
|
|
25878
|
+
if (bullet !== null)
|
|
25879
|
+
bullets.push((bullet[1] ?? "").trim());
|
|
25880
|
+
else if (line.trim().length > 0 && bullets.length > 0)
|
|
25881
|
+
bullets[bullets.length - 1] += ` ${line.trim()}`;
|
|
25882
|
+
}
|
|
25883
|
+
return bullets.filter((bullet) => bullet.length > 0);
|
|
25884
|
+
}
|
|
25863
25885
|
var INIT_INTENT_NOTE = "note: no intent statement extractable from description.md \u2014 the product index will hold no statement for this flow";
|
|
25864
25886
|
function initIntentNote(description) {
|
|
25865
25887
|
return flowStatementFrom(description) === null ? INIT_INTENT_NOTE : null;
|
|
@@ -26334,6 +26356,63 @@ async function deriveAcKinds(cwd, dir, flowId) {
|
|
|
26334
26356
|
const { report } = buildAcKindReport(flowId, await readFile27(acPath(cwd, dir), "utf8"));
|
|
26335
26357
|
return { ...report.criteria };
|
|
26336
26358
|
}
|
|
26359
|
+
function prMergeState(pr, gates) {
|
|
26360
|
+
switch (pr.kind) {
|
|
26361
|
+
case "no-pr":
|
|
26362
|
+
return { state: "no-pr", detail: "no PR recorded on the flow" };
|
|
26363
|
+
case "merged-commit": {
|
|
26364
|
+
const merge = gates.find((gate) => gate.name === "main-merge");
|
|
26365
|
+
return merge?.status === "pass" ? { state: "merged", detail: `direct merge: ${merge.detail}` } : { state: "unknown", detail: `direct merge not verified: ${merge?.detail ?? "main-merge gate missing"}` };
|
|
26366
|
+
}
|
|
26367
|
+
case "tracker-unavailable":
|
|
26368
|
+
return { state: "unknown", detail: "tracker unavailable (is `gh` installed and authenticated?)" };
|
|
26369
|
+
case "unevaluable":
|
|
26370
|
+
return { state: "unknown", detail: "the tracker call failed" };
|
|
26371
|
+
case "observed": {
|
|
26372
|
+
if (!pr.exists)
|
|
26373
|
+
return { state: "not-found", detail: "the tracker found no such PR" };
|
|
26374
|
+
const state = pr.state?.toUpperCase();
|
|
26375
|
+
if (state === "MERGED")
|
|
26376
|
+
return { state: "merged", detail: "PR merged" };
|
|
26377
|
+
if (state === "OPEN")
|
|
26378
|
+
return { state: "open", detail: "PR open, not merged" };
|
|
26379
|
+
if (state === "CLOSED")
|
|
26380
|
+
return { state: "closed", detail: "PR closed without merging" };
|
|
26381
|
+
return { state: "unknown", detail: pr.state === null ? "the tracker did not report a PR state" : `unrecognised PR state: ${pr.state}` };
|
|
26382
|
+
}
|
|
26383
|
+
}
|
|
26384
|
+
}
|
|
26385
|
+
var UNCONFIRMED_PREFIX = "unconfirmed: ";
|
|
26386
|
+
var NO_PR_DETAIL = "no PR recorded";
|
|
26387
|
+
function completionAdmitted(status, merged) {
|
|
26388
|
+
return canTransition(status, "completing") || merged && status === "in-progress";
|
|
26389
|
+
}
|
|
26390
|
+
function completionFixHint(gate, flowId) {
|
|
26391
|
+
if (gate.status !== "fail")
|
|
26392
|
+
return;
|
|
26393
|
+
switch (gate.name) {
|
|
26394
|
+
case "acceptance-criteria": {
|
|
26395
|
+
const unconfirmed = gate.detail.startsWith(UNCONFIRMED_PREFIX) ? gate.detail.slice(UNCONFIRMED_PREFIX.length).split(", ") : [];
|
|
26396
|
+
const first = unconfirmed[0];
|
|
26397
|
+
if (first === undefined)
|
|
26398
|
+
return;
|
|
26399
|
+
const more = unconfirmed.length > 1 ? ` (then ${unconfirmed.slice(1).join(", ")})` : "";
|
|
26400
|
+
return `keryx flow ac confirm ${flowId} ${first} --note "<evidence>"${more}`;
|
|
26401
|
+
}
|
|
26402
|
+
case "pull-request":
|
|
26403
|
+
return gate.detail === NO_PR_DETAIL ? `keryx flow implemented ${flowId} --pr <url>` : undefined;
|
|
26404
|
+
case "tasks":
|
|
26405
|
+
return `keryx flow next ${flowId}`;
|
|
26406
|
+
case "owner":
|
|
26407
|
+
return `keryx flow owner set ${flowId} --owner "<name>" --reason "<why>"`;
|
|
26408
|
+
case "health":
|
|
26409
|
+
return "keryx health run";
|
|
26410
|
+
case "confirmation":
|
|
26411
|
+
return `keryx flow confirm ${flowId} (in a terminal)`;
|
|
26412
|
+
default:
|
|
26413
|
+
return;
|
|
26414
|
+
}
|
|
26415
|
+
}
|
|
26337
26416
|
function recordAttempt(task, outcome, at, detail) {
|
|
26338
26417
|
const attempts = task.attempts ?? { count: 0, log: [] };
|
|
26339
26418
|
attempts.count += 1;
|
|
@@ -26393,6 +26472,106 @@ function createFlowService(deps) {
|
|
|
26393
26472
|
flow.status = "in-progress";
|
|
26394
26473
|
return save(cwd, dir, flow, event, detail);
|
|
26395
26474
|
}
|
|
26475
|
+
async function evaluateCompletionGates(cwd, dir, flow, mergedCommit, confirmToken) {
|
|
26476
|
+
const gates = [];
|
|
26477
|
+
try {
|
|
26478
|
+
await assertAcIntact(cwd, dir, flow);
|
|
26479
|
+
const criteria = await readAcCriteria(cwd, dir);
|
|
26480
|
+
const missing = criteria.filter((criterion) => !flow.acConfirmed[criterion]);
|
|
26481
|
+
gates.push(missing.length === 0 && criteria.length > 0 ? { name: "acceptance-criteria", status: "pass", detail: `${criteria.length} confirmed` } : {
|
|
26482
|
+
name: "acceptance-criteria",
|
|
26483
|
+
status: "fail",
|
|
26484
|
+
detail: criteria.length === 0 ? "no criteria found" : `${UNCONFIRMED_PREFIX}${missing.join(", ")}`
|
|
26485
|
+
});
|
|
26486
|
+
} catch {
|
|
26487
|
+
gates.push(unevaluableGate("acceptance-criteria"));
|
|
26488
|
+
}
|
|
26489
|
+
let confirmationToken;
|
|
26490
|
+
let confirmationOutcome;
|
|
26491
|
+
try {
|
|
26492
|
+
const confirmation = await confirmationGate(cwd, dir, flow, confirmToken, completionTarget({ merged: Boolean(mergedCommit), prUrl: flow.pr.url }), deps.now());
|
|
26493
|
+
confirmationOutcome = confirmation.outcome;
|
|
26494
|
+
confirmationToken = confirmation.stored;
|
|
26495
|
+
} catch {
|
|
26496
|
+
confirmationOutcome = unevaluableGate("confirmation");
|
|
26497
|
+
}
|
|
26498
|
+
let evaluatedHeadCommit = mergedCommit ?? undefined;
|
|
26499
|
+
let pr;
|
|
26500
|
+
if (mergedCommit) {
|
|
26501
|
+
pr = { kind: "merged-commit" };
|
|
26502
|
+
try {
|
|
26503
|
+
const merge = deps.mainMergeGate ? await deps.mainMergeGate(cwd, mergedCommit) : await verifyCommitOnMain(cwd, mergedCommit);
|
|
26504
|
+
gates.push({ name: "main-merge", status: merge.status, detail: merge.detail });
|
|
26505
|
+
} catch {
|
|
26506
|
+
gates.push(unevaluableGate("main-merge"));
|
|
26507
|
+
}
|
|
26508
|
+
} else if (!flow.pr.url) {
|
|
26509
|
+
pr = { kind: "no-pr" };
|
|
26510
|
+
gates.push({ name: "pull-request", status: "fail", detail: NO_PR_DETAIL });
|
|
26511
|
+
} else {
|
|
26512
|
+
pr = { kind: "unevaluable" };
|
|
26513
|
+
try {
|
|
26514
|
+
if (deps.tracker && await deps.tracker.detect()) {
|
|
26515
|
+
const status = await deps.tracker.prStatus(flow.pr.url);
|
|
26516
|
+
pr = { kind: "observed", exists: status.exists, state: status.state ?? null };
|
|
26517
|
+
if (typeof status.headSha === "string" && status.headSha !== "") {
|
|
26518
|
+
evaluatedHeadCommit ??= status.headSha;
|
|
26519
|
+
}
|
|
26520
|
+
gates.push(status.exists && status.checksGreen === true ? { name: "pull-request", status: "pass", detail: "PR exists, checks green" } : {
|
|
26521
|
+
name: "pull-request",
|
|
26522
|
+
status: "fail",
|
|
26523
|
+
detail: !status.exists ? "PR not found" : "PR checks not green"
|
|
26524
|
+
});
|
|
26525
|
+
} else {
|
|
26526
|
+
pr = { kind: "tracker-unavailable" };
|
|
26527
|
+
gates.push({
|
|
26528
|
+
name: "pull-request",
|
|
26529
|
+
status: "skipped",
|
|
26530
|
+
detail: "tracker unavailable; verify PR checks manually"
|
|
26531
|
+
});
|
|
26532
|
+
}
|
|
26533
|
+
} catch {
|
|
26534
|
+
gates.push(unevaluableGate("pull-request"));
|
|
26535
|
+
}
|
|
26536
|
+
}
|
|
26537
|
+
try {
|
|
26538
|
+
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));
|
|
26539
|
+
} catch {
|
|
26540
|
+
gates.push(unevaluableGate("base-branch"));
|
|
26541
|
+
}
|
|
26542
|
+
gates.push(taskGate(flow));
|
|
26543
|
+
gates.push(ownerGate(flow));
|
|
26544
|
+
try {
|
|
26545
|
+
gates.push(await reviewGate({
|
|
26546
|
+
cwd,
|
|
26547
|
+
flowDir: dir,
|
|
26548
|
+
flow,
|
|
26549
|
+
tracker: deps.tracker,
|
|
26550
|
+
...deps.externalCommentsGate ? { externalCommentsGate: deps.externalCommentsGate } : {},
|
|
26551
|
+
...mergedCommit ? { mergedCommit } : {}
|
|
26552
|
+
}));
|
|
26553
|
+
} catch {
|
|
26554
|
+
gates.push(unevaluableGate("review"));
|
|
26555
|
+
}
|
|
26556
|
+
try {
|
|
26557
|
+
const health = await deps.healthGate(cwd);
|
|
26558
|
+
gates.push(healthGateOutcome(health));
|
|
26559
|
+
} catch {
|
|
26560
|
+
gates.push(unevaluableGate("health"));
|
|
26561
|
+
}
|
|
26562
|
+
if (deps.securityGate) {
|
|
26563
|
+
try {
|
|
26564
|
+
const security = await deps.securityGate(cwd);
|
|
26565
|
+
if (security) {
|
|
26566
|
+
gates.push({ name: "security", status: security.status, detail: security.detail });
|
|
26567
|
+
}
|
|
26568
|
+
} catch {
|
|
26569
|
+
gates.push(unevaluableGate("security"));
|
|
26570
|
+
}
|
|
26571
|
+
}
|
|
26572
|
+
gates.push(confirmationOutcome);
|
|
26573
|
+
return { gates, confirmationToken, evaluatedHeadCommit, pr };
|
|
26574
|
+
}
|
|
26396
26575
|
async function mutate(cwd, id, run) {
|
|
26397
26576
|
const dir = await resolveFlowDir(cwd, id);
|
|
26398
26577
|
return withFileLock(flowLockPath(cwd, dir), async () => {
|
|
@@ -26756,6 +26935,27 @@ function createFlowService(deps) {
|
|
|
26756
26935
|
return save(cwd, dir, flow, "implemented", detail);
|
|
26757
26936
|
});
|
|
26758
26937
|
},
|
|
26938
|
+
async checkComplete({ cwd, id, mergedCommit, confirmToken }) {
|
|
26939
|
+
const { dir, flow } = await load(cwd, id);
|
|
26940
|
+
const merged = Boolean(mergedCommit);
|
|
26941
|
+
const allowed = completionAdmitted(flow.status, merged);
|
|
26942
|
+
const transition = allowed ? { allowed, detail: `${flow.status} \u2192 completing` } : {
|
|
26943
|
+
allowed,
|
|
26944
|
+
detail: flow.status === "done" ? "already done" : flow.status === "in-progress" ? `in-progress: record the PR first (\`keryx flow implemented ${flow.id} --pr <url>\`), or pass a merged commit` : `"${flow.status}" cannot move to completing; complete runs from "implemented" (or "in-progress" with a merged commit)`
|
|
26945
|
+
};
|
|
26946
|
+
const evaluation = await evaluateCompletionGates(cwd, dir, flow, mergedCommit ?? undefined, confirmToken);
|
|
26947
|
+
return {
|
|
26948
|
+
id: flow.id,
|
|
26949
|
+
status: flow.status,
|
|
26950
|
+
updatedAt: flow.updatedAt,
|
|
26951
|
+
checkedAt: now(),
|
|
26952
|
+
transition,
|
|
26953
|
+
merge: prMergeState(evaluation.pr, evaluation.gates),
|
|
26954
|
+
gates: evaluation.gates,
|
|
26955
|
+
passed: allowed && evaluation.gates.every((gate) => gate.status !== "fail"),
|
|
26956
|
+
confirmationRequired: flow.gates?.confirmation === true
|
|
26957
|
+
};
|
|
26958
|
+
},
|
|
26759
26959
|
async complete({
|
|
26760
26960
|
cwd,
|
|
26761
26961
|
id,
|
|
@@ -26776,97 +26976,7 @@ function createFlowService(deps) {
|
|
|
26776
26976
|
} else {
|
|
26777
26977
|
flow = await transition(cwd, dir, flow, "completing", "completing");
|
|
26778
26978
|
}
|
|
26779
|
-
const gates =
|
|
26780
|
-
try {
|
|
26781
|
-
await assertAcIntact(cwd, dir, flow);
|
|
26782
|
-
const criteria = await readAcCriteria(cwd, dir);
|
|
26783
|
-
const missing = criteria.filter((criterion) => !flow.acConfirmed[criterion]);
|
|
26784
|
-
gates.push(missing.length === 0 && criteria.length > 0 ? { name: "acceptance-criteria", status: "pass", detail: `${criteria.length} confirmed` } : {
|
|
26785
|
-
name: "acceptance-criteria",
|
|
26786
|
-
status: "fail",
|
|
26787
|
-
detail: criteria.length === 0 ? "no criteria found" : `unconfirmed: ${missing.join(", ")}`
|
|
26788
|
-
});
|
|
26789
|
-
} catch {
|
|
26790
|
-
gates.push(unevaluableGate("acceptance-criteria"));
|
|
26791
|
-
}
|
|
26792
|
-
let confirmationToken;
|
|
26793
|
-
let confirmationOutcome;
|
|
26794
|
-
try {
|
|
26795
|
-
const confirmation = await confirmationGate(cwd, dir, flow, confirmToken, completionTarget({ merged: Boolean(mergedCommit), prUrl: flow.pr.url }), deps.now());
|
|
26796
|
-
confirmationOutcome = confirmation.outcome;
|
|
26797
|
-
confirmationToken = confirmation.stored;
|
|
26798
|
-
} catch {
|
|
26799
|
-
confirmationOutcome = unevaluableGate("confirmation");
|
|
26800
|
-
}
|
|
26801
|
-
let evaluatedHeadCommit = mergedCommit ?? undefined;
|
|
26802
|
-
if (mergedCommit) {
|
|
26803
|
-
try {
|
|
26804
|
-
const merge = deps.mainMergeGate ? await deps.mainMergeGate(cwd, mergedCommit) : await verifyCommitOnMain(cwd, mergedCommit);
|
|
26805
|
-
gates.push({ name: "main-merge", status: merge.status, detail: merge.detail });
|
|
26806
|
-
} catch {
|
|
26807
|
-
gates.push(unevaluableGate("main-merge"));
|
|
26808
|
-
}
|
|
26809
|
-
} else if (!flow.pr.url) {
|
|
26810
|
-
gates.push({ name: "pull-request", status: "fail", detail: "no PR recorded" });
|
|
26811
|
-
} else {
|
|
26812
|
-
try {
|
|
26813
|
-
if (deps.tracker && await deps.tracker.detect()) {
|
|
26814
|
-
const pr = await deps.tracker.prStatus(flow.pr.url);
|
|
26815
|
-
if (typeof pr.headSha === "string" && pr.headSha !== "") {
|
|
26816
|
-
evaluatedHeadCommit ??= pr.headSha;
|
|
26817
|
-
}
|
|
26818
|
-
gates.push(pr.exists && pr.checksGreen === true ? { name: "pull-request", status: "pass", detail: "PR exists, checks green" } : {
|
|
26819
|
-
name: "pull-request",
|
|
26820
|
-
status: "fail",
|
|
26821
|
-
detail: !pr.exists ? "PR not found" : "PR checks not green"
|
|
26822
|
-
});
|
|
26823
|
-
} else {
|
|
26824
|
-
gates.push({
|
|
26825
|
-
name: "pull-request",
|
|
26826
|
-
status: "skipped",
|
|
26827
|
-
detail: "tracker unavailable; verify PR checks manually"
|
|
26828
|
-
});
|
|
26829
|
-
}
|
|
26830
|
-
} catch {
|
|
26831
|
-
gates.push(unevaluableGate("pull-request"));
|
|
26832
|
-
}
|
|
26833
|
-
}
|
|
26834
|
-
try {
|
|
26835
|
-
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));
|
|
26836
|
-
} catch {
|
|
26837
|
-
gates.push(unevaluableGate("base-branch"));
|
|
26838
|
-
}
|
|
26839
|
-
gates.push(taskGate(flow));
|
|
26840
|
-
gates.push(ownerGate(flow));
|
|
26841
|
-
try {
|
|
26842
|
-
gates.push(await reviewGate({
|
|
26843
|
-
cwd,
|
|
26844
|
-
flowDir: dir,
|
|
26845
|
-
flow,
|
|
26846
|
-
tracker: deps.tracker,
|
|
26847
|
-
...deps.externalCommentsGate ? { externalCommentsGate: deps.externalCommentsGate } : {},
|
|
26848
|
-
...mergedCommit ? { mergedCommit } : {}
|
|
26849
|
-
}));
|
|
26850
|
-
} catch {
|
|
26851
|
-
gates.push(unevaluableGate("review"));
|
|
26852
|
-
}
|
|
26853
|
-
try {
|
|
26854
|
-
const health = await deps.healthGate(cwd);
|
|
26855
|
-
gates.push(healthGateOutcome(health));
|
|
26856
|
-
} catch {
|
|
26857
|
-
gates.push(unevaluableGate("health"));
|
|
26858
|
-
}
|
|
26859
|
-
if (deps.securityGate) {
|
|
26860
|
-
try {
|
|
26861
|
-
const security = await deps.securityGate(cwd);
|
|
26862
|
-
if (security) {
|
|
26863
|
-
gates.push({ name: "security", status: security.status, detail: security.detail });
|
|
26864
|
-
}
|
|
26865
|
-
} catch {
|
|
26866
|
-
gates.push(unevaluableGate("security"));
|
|
26867
|
-
}
|
|
26868
|
-
}
|
|
26869
|
-
gates.push(confirmationOutcome);
|
|
26979
|
+
const { gates, confirmationToken, evaluatedHeadCommit } = await evaluateCompletionGates(cwd, dir, flow, mergedCommit ?? undefined, confirmToken);
|
|
26870
26980
|
const passed = gates.every((gate) => gate.status !== "fail");
|
|
26871
26981
|
const tamperDetail = await acTamperDetail(cwd, dir, flow);
|
|
26872
26982
|
const recordedGates = tamperDetail === undefined ? gates : [
|
|
@@ -39815,7 +39925,7 @@ var HELP_GROUPS = [
|
|
|
39815
39925
|
kind: "slash",
|
|
39816
39926
|
name: "/governance",
|
|
39817
39927
|
group: "Automation",
|
|
39818
|
-
summary: "
|
|
39928
|
+
summary: "Flows with their summary and stated effect \u2014 check an open one, complete it once the check passes and the PR is merged; the last report in its own tab, or run one in the background."
|
|
39819
39929
|
},
|
|
39820
39930
|
{
|
|
39821
39931
|
kind: "slash",
|
|
@@ -39835,6 +39945,12 @@ var HELP_GROUPS = [
|
|
|
39835
39945
|
group: "Automation",
|
|
39836
39946
|
summary: "Drive this session from a Telegram topic: send lines, get replies, answer approvals. Off by default; [name|off|status]."
|
|
39837
39947
|
},
|
|
39948
|
+
{
|
|
39949
|
+
kind: "slash",
|
|
39950
|
+
name: "/channels",
|
|
39951
|
+
group: "Automation",
|
|
39952
|
+
summary: "Connect, test or disconnect Telegram for this machine: bot token, one-time code, group. [status]."
|
|
39953
|
+
},
|
|
39838
39954
|
{
|
|
39839
39955
|
kind: "cli",
|
|
39840
39956
|
name: "acp",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.51",
|
|
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": {
|