@mrciphersmith/keryx 0.2.144 → 0.2.145

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.
Files changed (3) hide show
  1. package/README.md +6 -3
  2. package/dist/cli.js +37 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -255,9 +255,12 @@ What is in it today:
255
255
  completed, blocked, and skipped steps in the sidebar, and restored sessions
256
256
  continue from the same plan instead of rebuilding it from transcript text.
257
257
  Clicking the sidebar's Plan section (its header or any row) opens the whole
258
- plan in a modal: a Steps tab grouped and coloured by state, a Meta tab with the
259
- revision, per-status counts, the active item, the blocked ids, and the plan
260
- file it lives in.
258
+ plan in a modal: a Plan tab that spells each item's status out and colours it
259
+ by state, a Meta tab with the revision, per-status counts, the active item, the
260
+ blocked ids, and the plan file it lives in. A plan can also be published FOR
261
+ APPROVAL — items marked `proposed` await a human, never force the agent to
262
+ continue, and are shown as `◇ awaiting approval` in both the sidebar and the
263
+ modal.
261
264
  - **Theme-aware rich transcripts.** Assistant prose, headings, emphasis,
262
265
  inline code, fenced code, diffs, and GFM pipe tables use semantic colors
263
266
  derived from the selected `/theme`. Use a language fence such as
package/dist/cli.js CHANGED
@@ -24214,6 +24214,9 @@ async function updateExecutionPlan(dir, input) {
24214
24214
  function hasActionableExecutionPlanItems(plan) {
24215
24215
  return plan?.items.some((item) => item.status === "pending" || item.status === "in_progress") ?? false;
24216
24216
  }
24217
+ function executionPlanApprovalItems(plan) {
24218
+ return plan?.items.filter((item) => item.status === "proposed") ?? [];
24219
+ }
24217
24220
  function renderExecutionPlanSnapshot(plan, maxItems = 7) {
24218
24221
  if (plan === undefined || plan.items.length === 0)
24219
24222
  return;
@@ -24231,7 +24234,7 @@ var EXECUTION_PLAN_STATUSES, listeners, ExecutionPlanConflictError, ExecutionPla
24231
24234
  var init_execution_plan = __esm(() => {
24232
24235
  init_fs();
24233
24236
  init_slate();
24234
- EXECUTION_PLAN_STATUSES = ["pending", "in_progress", "completed", "blocked", "skipped"];
24237
+ EXECUTION_PLAN_STATUSES = ["proposed", "pending", "in_progress", "completed", "blocked", "skipped"];
24235
24238
  listeners = new Set;
24236
24239
  ExecutionPlanConflictError = class ExecutionPlanConflictError extends Error {
24237
24240
  constructor(expected, actual) {
@@ -29527,7 +29530,7 @@ function buildAgentSystemInstruction(orient, ctx = {}) {
29527
29530
  ` + locateRule + "- read_file returns a bounded window starting at `start_line` (default 1). A large file " + "is read by paging: its truncation notice names the start_line to continue from, and a " + `line number from ${hasGraph ? "search_code or graph_symbol" : "search_code"} can be read directly \u2014 do not re-read the ` + `same head hoping for more.
29528
29531
  ` + "- Prefer ONE correct shell_exec over many exploratory tool calls when the user asks " + `to run a known keryx workflow.
29529
29532
  ` + "- Tool-call budget: shell_exec, file-mutating shell, workspace_create/workspace_propose, and spawn_subagent " + "all share ONE small per-turn pool (distinct non-read actions), separate from the much larger read-tool pool. " + (hasGraph ? "search_code/graph_*/memory_search/read_wiki/wiki_*/test_related/health_status/flow_status/repomap/read_file/" : "search_code/read_file/") + "list_dir do NOT touch it. Conserve the small pool: batch multiple shell steps into ONE call with `&&` instead " + "of issuing them one at a time, get a command's arguments right the first time instead of trying variants, and " + "for any check covered by a read tool above, use that tool instead of shelling out to the equivalent `keryx \u2026` " + `CLI command.
29530
- ` + "- For multi-step work, use **plan_set** to publish a structured plan, **plan_update** after each real status " + "change, and **plan_get** before resolving a revision conflict. Keep stable item ids, at most one " + "`in_progress` item, and do not mark work complete before verification. These tools update session metadata " + "only; `/plan` remains the operator's separate read-only permission mode.\n" + "- This session has its own Slate (working-set scratch, not project knowledge): " + "**slate_read** shows the Course (if a Flow is bound) and Seeds recorded so far \u2014 nothing " + "here is auto-injected, so call it if you want to see it. **slate_write_seed** with " + "`{ text, kind? }` records a draft hypothesis/decision/follow-up worth a later human " + "review. WRITE A SEED when you: found a root cause or a bug worth remembering; changed or " + "added code (summarize WHAT changed and WHY); took a design/architecture decision; " + "identified a risk; or discovered a constraint/lesson. Use the `kind` that fits: " + "`decision` (a choice made), `wiki-update` (something a wiki page should say), " + "`memory-entry` (a lesson/constraint), `follow-up` (a TODO for a later session), " + "`risk`, or `contract-change`. Keep each Seed to 2-3 sentences, concrete and specific. " + "Do NOT write Seeds for routine progress notes, one-shot operational requests (e.g. " + '"run git pull", "count files"), or trivia \u2014 those need no workspace and no proposal. ' + "Seeds are the ONLY input wrap-up proposes from: a session whose Slate has zero Seeds " + `produces zero proposals. A Seed is never accepted knowledge by itself.
29533
+ ` + "- For multi-step work, use **plan_set** to publish a structured plan, **plan_update** after each real status " + "change, and **plan_get** before resolving a revision conflict. Keep stable item ids, at most one " + "`in_progress` item, and do not mark work complete before verification. These tools update session metadata " + "only; `/plan` remains the operator's separate read-only permission mode.\n" + "- **Publishing a plan FOR APPROVAL is a real, supported stopping point.** Mark the items `proposed`, " + "state the plan in your reply, and END THE TURN: `proposed` is not work in progress, so it never forces " + "another round on its own. When the operator approves, move those items to `pending`/`in_progress` and " + "continue. Use `pending` (not `proposed`) only when you are going to execute the plan in this same turn.\n" + "- This session has its own Slate (working-set scratch, not project knowledge): " + "**slate_read** shows the Course (if a Flow is bound) and Seeds recorded so far \u2014 nothing " + "here is auto-injected, so call it if you want to see it. **slate_write_seed** with " + "`{ text, kind? }` records a draft hypothesis/decision/follow-up worth a later human " + "review. WRITE A SEED when you: found a root cause or a bug worth remembering; changed or " + "added code (summarize WHAT changed and WHY); took a design/architecture decision; " + "identified a risk; or discovered a constraint/lesson. Use the `kind` that fits: " + "`decision` (a choice made), `wiki-update` (something a wiki page should say), " + "`memory-entry` (a lesson/constraint), `follow-up` (a TODO for a later session), " + "`risk`, or `contract-change`. Keep each Seed to 2-3 sentences, concrete and specific. " + "Do NOT write Seeds for routine progress notes, one-shot operational requests (e.g. " + '"run git pull", "count files"), or trivia \u2014 those need no workspace and no proposal. ' + "Seeds are the ONLY input wrap-up proposes from: a session whose Slate has zero Seeds " + `produces zero proposals. A Seed is never accepted knowledge by itself.
29531
29534
  ` + "- Shared Agent Context (SAC) workspaces hold accepted, evidence-backed project context " + "beyond this codebase. **workspace_list** with `{ includeArchived? }` shows every workspace " + "visible to you \u2014 call it first when the user references a shared team workspace or accepted " + "project context, or before creating a new workspace, to judge whether an existing one " + "already fits the current topic. **workspace_show** with `{ workspaceId }` shows one " + "workspace's manifest. **workspace_overview** with `{ workspaceId }`, then **workspace_read** " + "with `{ workspaceId, itemId }` for one specific item, reads its accepted Facts/Work/Know-how. " + "**workspace_create** with `{ title, component? }` creates a new workspace AND binds it to " + "this session's slate (wrap-up then proposes into it) \u2014 only when workspace_list found no " + "fitting one and the session has real, durable results worth persisting; a workspace is " + "meant to persist across sessions, so prefer an existing one over creating another for the " + "same topic, and do NOT create one for one-shot operational requests. **workspace_propose** with " + "`{ workspaceId, kind, sessionId?, note? }` (sessionId defaults to this session) proposes a decision/wiki-update/memory-entry/" + "follow-up/contract-change/risk from this session for later human review \u2014 it never accepts " + "anything by itself; accepting always requires a human running `keryx workspace review` at a " + `real terminal, never this tool.
29532
29535
  ` + "- When you need a decision, interview step, or clarification: use **ask_user** with " + "2\u20136 options `{ id, label, description, recommended? }` (mark one recommended). " + `Do not dump long prose questions without options.
29533
29536
  ` + "- For a focused independent subtask (investigate X, review Y, research Z): use " + "**spawn_subagent** with `{ task, mode?: 'read_only'|'general', label? }`. " + "Default mode is read_only (no shell). Prefer spawn for work that can finish " + `without your intermediate turns; do not spawn for trivial one-line answers.
@@ -30093,6 +30096,19 @@ ${planSnapshot}`;
30093
30096
  }
30094
30097
  continue;
30095
30098
  }
30099
+ const approvalItems = executionPlanApprovalItems(currentPlan);
30100
+ if (approvalItems.length > 0 && !hasActionableExecutionPlanItems(currentPlan)) {
30101
+ const shown = approvalItems.slice(0, 7).map((item) => `- ${item.id}: ${item.title.length > 120 ? `${item.title.slice(0, 119)}\u2026` : item.title}`);
30102
+ if (approvalItems.length > shown.length) {
30103
+ shown.push(`- \u2026 ${approvalItems.length - shown.length} more`);
30104
+ }
30105
+ system(`
30106
+ [plan] Published for your approval \u2014 nothing is in progress, so this turn ends here:
30107
+ ${shown.join(`
30108
+ `)}
30109
+ ` + `Approve by continuing (the agent moves them to pending/in_progress), or ask for changes.
30110
+ `);
30111
+ }
30096
30112
  if (!planFollowThroughUsed && hasActionableExecutionPlanItems(currentPlan)) {
30097
30113
  planFollowThroughUsed = true;
30098
30114
  history.push({
@@ -51447,7 +51463,7 @@ import path47 from "path";
51447
51463
  // package.json
51448
51464
  var package_default = {
51449
51465
  name: "@mrciphersmith/keryx",
51450
- version: "0.2.144",
51466
+ version: "0.2.145",
51451
51467
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
51452
51468
  private: false,
51453
51469
  publishConfig: {
@@ -102987,6 +103003,7 @@ init_execution_plan();
102987
103003
  // src/tui/execution-plan-panel.ts
102988
103004
  init_execution_plan();
102989
103005
  var GLYPHS = {
103006
+ proposed: "\u25C7",
102990
103007
  completed: "\u2713",
102991
103008
  in_progress: "\u25B6",
102992
103009
  pending: "\u25CB",
@@ -103013,8 +103030,12 @@ function projectExecutionPlanPanel(plan, options) {
103013
103030
  return { visible: false, rows: [], revision: undefined };
103014
103031
  }
103015
103032
  const size = Math.max(1, Math.min(options.maxRows ?? 7, 7, plan.items.length));
103016
- const active = Math.max(0, plan.items.findIndex((item) => item.status === "in_progress"));
103017
- const start = Math.max(0, Math.min(active - Math.floor(size / 2), plan.items.length - size));
103033
+ const firstOf = (status) => plan.items.findIndex((i) => i.status === status);
103034
+ const anchorIndex = firstOf("in_progress");
103035
+ const pendingIndex = firstOf("pending");
103036
+ const proposedIndex = firstOf("proposed");
103037
+ const anchor = anchorIndex >= 0 ? anchorIndex : pendingIndex >= 0 ? pendingIndex : Math.max(0, proposedIndex);
103038
+ const start = Math.max(0, Math.min(anchor - Math.floor(size / 2), plan.items.length - size));
103018
103039
  const rows = plan.items.slice(start, start + size).map((item) => {
103019
103040
  const glyph = GLYPHS[item.status];
103020
103041
  return { id: item.id, status: item.status, glyph, text: truncate7(`${glyph} ${item.title}`, options.width) };
@@ -103085,6 +103106,7 @@ var PLAN_INSPECTOR_FOOTER = [
103085
103106
  { key: "esc", label: "close" }
103086
103107
  ];
103087
103108
  var PLAN_STATUS_GLYPH = {
103109
+ proposed: "\u25C7",
103088
103110
  completed: "\u2713",
103089
103111
  in_progress: "\u25B6",
103090
103112
  pending: "\u25CB",
@@ -103092,6 +103114,7 @@ var PLAN_STATUS_GLYPH = {
103092
103114
  skipped: "\u2212"
103093
103115
  };
103094
103116
  var PLAN_STATUS_LABEL = {
103117
+ proposed: "awaiting approval",
103095
103118
  completed: "completed",
103096
103119
  in_progress: "in progress",
103097
103120
  pending: "pending",
@@ -103099,6 +103122,7 @@ var PLAN_STATUS_LABEL = {
103099
103122
  skipped: "skipped"
103100
103123
  };
103101
103124
  var PLAN_STATUS_ORDER = [
103125
+ "proposed",
103102
103126
  "in_progress",
103103
103127
  "pending",
103104
103128
  "blocked",
@@ -103108,6 +103132,7 @@ var PLAN_STATUS_ORDER = [
103108
103132
  var PLAN_EMPTY_TEXT = "No execution plan in this session. The agent publishes one with `plan_set` before multi-step work.";
103109
103133
  function planCounts(plan) {
103110
103134
  const counts = {
103135
+ proposed: 0,
103111
103136
  completed: 0,
103112
103137
  in_progress: 0,
103113
103138
  pending: 0,
@@ -103136,6 +103161,7 @@ function formatPlanSummary(plan) {
103136
103161
  const parts = [
103137
103162
  `revision ${plan.revision}`,
103138
103163
  `${counts.completed}/${plan.items.length} done`,
103164
+ ...counts.proposed > 0 ? [`${counts.proposed} awaiting approval`] : [],
103139
103165
  ...counts.in_progress > 0 ? [`${counts.in_progress} in progress`] : [],
103140
103166
  ...counts.blocked > 0 ? [`${counts.blocked} blocked`] : [],
103141
103167
  ...counts.pending > 0 ? [`${counts.pending} pending`] : [],
@@ -103143,8 +103169,9 @@ function formatPlanSummary(plan) {
103143
103169
  ];
103144
103170
  return `Plan \xB7 ${parts.join(" \xB7 ")}`;
103145
103171
  }
103172
+ var PLAN_LABEL_WIDTH = Math.max(...Object.values(PLAN_STATUS_LABEL).map((label) => label.length));
103146
103173
  function formatPlanRow(item) {
103147
- return `${PLAN_STATUS_GLYPH[item.status]} ${PLAN_STATUS_LABEL[item.status].padEnd(12)} ${item.title}`;
103174
+ return `${PLAN_STATUS_GLYPH[item.status]} ${PLAN_STATUS_LABEL[item.status].padEnd(PLAN_LABEL_WIDTH)} ${item.title}`;
103148
103175
  }
103149
103176
  function formatPlanLegend() {
103150
103177
  return PLAN_STATUS_ORDER.map((status) => `${PLAN_STATUS_GLYPH[status]} ${PLAN_STATUS_LABEL[status]}`).join(" ");
@@ -103164,9 +103191,10 @@ function formatPlanMeta(plan, dir) {
103164
103191
  const blocked = plan.items.filter((item) => item.status === "blocked").map((item) => item.id);
103165
103192
  return [
103166
103193
  `Revision ${plan.revision}`,
103167
- `Items ${plan.items.length} \u2014 ${counts.completed} completed, ${counts.in_progress} in progress, ${counts.pending} pending, ${counts.blocked} blocked, ${counts.skipped} skipped`,
103194
+ `Items ${plan.items.length} \u2014 ${counts.completed} completed, ${counts.in_progress} in progress, ${counts.pending} pending, ${counts.blocked} blocked, ${counts.skipped} skipped, ${counts.proposed} awaiting approval`,
103168
103195
  `Active ${active === undefined ? "(none \u2014 no item is in_progress)" : `${active.id} \u2014 ${active.title}`}`,
103169
103196
  ...blocked.length > 0 ? [`Blocked ${blocked.join(", ")}`] : [],
103197
+ ...counts.proposed > 0 ? [`Approval ${counts.proposed} item(s) published for your approval \u2014 nothing is running; they start once you approve`] : [],
103170
103198
  "",
103171
103199
  "Storage <session>/plan.json \u2014 a sibling of slate.json, so a completed",
103172
103200
  " Flow closing its slate can no longer take the plan with it.",
@@ -103176,6 +103204,8 @@ function formatPlanMeta(plan, dir) {
103176
103204
  }
103177
103205
  function toneFor(status) {
103178
103206
  switch (status) {
103207
+ case "proposed":
103208
+ return "yellow";
103179
103209
  case "in_progress":
103180
103210
  return "cyan";
103181
103211
  case "blocked":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.2.144",
3
+ "version": "0.2.145",
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": {