@mrciphersmith/keryx 0.2.42 → 0.2.44

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 (2) hide show
  1. package/dist/cli.js +1384 -405
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -12095,6 +12095,10 @@ function touchesAgentCredentials(command) {
12095
12095
  const text = command.toLowerCase();
12096
12096
  return CREDENTIAL_MARKERS.some((marker2) => text.includes(marker2));
12097
12097
  }
12098
+ function touchesSacConfirmReview(command) {
12099
+ const text = command.toLowerCase();
12100
+ return SAC_REVIEW_MARKERS.some((marker2) => text.includes(marker2));
12101
+ }
12098
12102
  var CATASTROPHIC_TARGETS, RECURSIVE_FLAG, PRIVILEGE, INTERPRETERS, DOWNLOADERS, CONTAINER_RUNTIMES, PROTECTED_BRANCHES, BLOCK_DEVICE, rulePrivilege = (v) => PRIVILEGE.has(v.cmd), ruleBlockDeviceRedirect = (v) => BLOCK_DEVICE.test(v.raw) && /(?:^|\s)(?:>|>>)\s*\/dev\//.test(v.raw), ruleRm = (v) => v.cmd === "rm" && v.positionals.some(isCatastrophicTarget), ruleDd = (v) => v.cmd === "dd" && v.args.some((x) => /^of=/i.test(x) && BLOCK_DEVICE.test(x)), rulePermissionSweep = (v) => (v.cmd === "chmod" || v.cmd === "chown" || v.cmd === "chgrp") && hasRecursive(v.words) && v.positionals.some(isCatastrophicTarget), ruleHostState = (v) => {
12099
12103
  if (v.cmd === "shutdown" || v.cmd === "reboot" || v.cmd === "halt" || v.cmd === "poweroff")
12100
12104
  return true;
@@ -12115,7 +12119,7 @@ var CATASTROPHIC_TARGETS, RECURSIVE_FLAG, PRIVILEGE, INTERPRETERS, DOWNLOADERS,
12115
12119
  return true;
12116
12120
  const named = after.map((x) => x.split("/").pop()?.toLowerCase() ?? "");
12117
12121
  return named.some((n) => PROTECTED_BRANCHES.includes(n));
12118
- }, RULES3, CREDENTIAL_MARKERS;
12122
+ }, RULES3, CREDENTIAL_MARKERS, SAC_REVIEW_MARKERS;
12119
12123
  var init_command_risk = __esm(() => {
12120
12124
  init_shell_syntax();
12121
12125
  CATASTROPHIC_TARGETS = new Set([
@@ -12180,6 +12184,7 @@ var init_command_risk = __esm(() => {
12180
12184
  ".local/share/keryx",
12181
12185
  ".config/keryx"
12182
12186
  ];
12187
+ SAC_REVIEW_MARKERS = ["confirm-review", "workspace review"];
12183
12188
  });
12184
12189
 
12185
12190
  // src/commands/permission-mode.ts
@@ -12187,11 +12192,11 @@ function isPermissionMode(value) {
12187
12192
  return PERMISSION_MODES.includes(value);
12188
12193
  }
12189
12194
  function resolveApprovalDecision(input2) {
12190
- const { mode, risk, destructive, credentials } = input2;
12195
+ const { mode, risk, destructive, credentials, sacReviewConfirmation } = input2;
12191
12196
  if (risk === "read") {
12192
12197
  return "auto";
12193
12198
  }
12194
- if (credentials) {
12199
+ if (credentials || sacReviewConfirmation) {
12195
12200
  return "ask";
12196
12201
  }
12197
12202
  if (mode === "auto") {
@@ -17667,7 +17672,8 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, on
17667
17672
  const command = typeof input2.command === "string" ? input2.command : "";
17668
17673
  const destructive = risk === "destructive" || isDestructiveCommand(command);
17669
17674
  const credentials = touchesAgentCredentials(command);
17670
- const decision = resolveApprovalDecision({ mode, risk, destructive, credentials });
17675
+ const sacReviewConfirmation = touchesSacConfirmReview(command);
17676
+ const decision = resolveApprovalDecision({ mode, risk, destructive, credentials, sacReviewConfirmation });
17671
17677
  if (decision === "auto") {
17672
17678
  onAutoApproved?.(call.name, call.input, { destructive, credentials });
17673
17679
  } else {
@@ -17682,7 +17688,7 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, on
17682
17688
  }
17683
17689
  }
17684
17690
  } else if (risk === "delegate") {
17685
- const decision = resolveApprovalDecision({ mode, risk, destructive: false, credentials: false });
17691
+ const decision = resolveApprovalDecision({ mode, risk, destructive: false, credentials: false, sacReviewConfirmation: false });
17686
17692
  if (decision === "auto") {
17687
17693
  onAutoApproved?.(call.name, call.input, { destructive: false, credentials: false });
17688
17694
  } else {
@@ -45817,7 +45823,7 @@ function harnessWave(args2, deps) {
45817
45823
  init_make_provider();
45818
45824
  init_orient();
45819
45825
  init_metaproject_adapter();
45820
- import { randomUUID as randomUUID23 } from "crypto";
45826
+ import { randomUUID as randomUUID25 } from "crypto";
45821
45827
  import * as readline2 from "readline";
45822
45828
 
45823
45829
  // src/commands/agent-approval-context.ts
@@ -47104,6 +47110,9 @@ function buildInteractiveAgentTools(input2) {
47104
47110
  ];
47105
47111
  }
47106
47112
 
47113
+ // src/commands/shell-approval.ts
47114
+ init_command_risk();
47115
+
47107
47116
  // src/lib/shell-permissions.ts
47108
47117
  init_config_dir();
47109
47118
  init_shell_config();
@@ -47254,6 +47263,12 @@ function validateShellPattern(pattern) {
47254
47263
  reason: "touches the agent's own permission/credential files; remembering it would let one approved command disable the approval gate for every future session"
47255
47264
  };
47256
47265
  }
47266
+ if (touchesSacConfirmReview(trimmed)) {
47267
+ return {
47268
+ ok: false,
47269
+ reason: "touches SAC's proposal-review/confirm-token family; that guarantee depends on a human answering a real approval prompt, so it is never remembered"
47270
+ };
47271
+ }
47257
47272
  const banned = bannedPrefixGrant(trimmed, firstToken);
47258
47273
  if (banned !== undefined) {
47259
47274
  return { ok: false, reason: banned.reason };
@@ -47391,6 +47406,9 @@ function isShellCommandAllowed(command, allow) {
47391
47406
  if (touchesAgentCredentials(cmd)) {
47392
47407
  return false;
47393
47408
  }
47409
+ if (touchesSacConfirmReview(cmd)) {
47410
+ return false;
47411
+ }
47394
47412
  return allow.some((pat) => matchShellPattern(pat, cmd));
47395
47413
  }
47396
47414
  function shellPermissionsFingerprint(dir) {
@@ -47416,12 +47434,12 @@ function suggestShellPatterns(command) {
47416
47434
  const collapsed = firstLine2.replace(/\s+/g, " ").trim();
47417
47435
  const first = collapsed.split(" ")[0] ?? collapsed;
47418
47436
  const prefix = first.length > 0 ? `${first} *` : exact;
47419
- const destructive = trimmed.length > 0 && isDestructiveCommand(trimmed);
47437
+ const neverRemember = trimmed.length > 0 && (isDestructiveCommand(trimmed) || touchesSacConfirmReview(trimmed));
47420
47438
  return {
47421
47439
  exact,
47422
47440
  prefix,
47423
- offerExact: !destructive && validateShellPattern(exact).ok,
47424
- offerPrefix: !destructive && validateShellPattern(prefix).ok
47441
+ offerExact: !neverRemember && validateShellPattern(exact).ok,
47442
+ offerPrefix: !neverRemember && validateShellPattern(prefix).ok
47425
47443
  };
47426
47444
  }
47427
47445
  function parseShellExecCommand(inputJson) {
@@ -47444,16 +47462,18 @@ function evaluateShellApproval(input2) {
47444
47462
  const command = parseShellExecCommand(input2.inputJson);
47445
47463
  const destructive = input2.meta?.destructive === true;
47446
47464
  const credentials = input2.meta?.credentials === true;
47465
+ const sacReviewConfirmation = touchesSacConfirmReview(command);
47447
47466
  const audit = io.loadAudit();
47448
47467
  for (const pattern of audit.permissions.allow) {
47449
47468
  input2.sessionAllow.add(pattern);
47450
47469
  }
47451
47470
  const tampered = io.fingerprint() !== input2.fingerprintAtStart;
47452
- const autoApprove = !destructive && !credentials && isShellCommandAllowed(command, [...input2.sessionAllow]);
47471
+ const autoApprove = !destructive && !credentials && !sacReviewConfirmation && isShellCommandAllowed(command, [...input2.sessionAllow]);
47453
47472
  return {
47454
47473
  command,
47455
47474
  destructive,
47456
47475
  credentials,
47476
+ sacReviewConfirmation,
47457
47477
  autoApprove,
47458
47478
  rejected: audit.rejected,
47459
47479
  tampered
@@ -47478,6 +47498,9 @@ function formatShellApprovalHints(evaled) {
47478
47498
  if (evaled.credentials) {
47479
47499
  lines.push("touches agent credentials \u2014 will not be remembered");
47480
47500
  }
47501
+ if (evaled.sacReviewConfirmation) {
47502
+ lines.push("SAC proposal review/confirm-token \u2014 will not be remembered");
47503
+ }
47481
47504
  return lines;
47482
47505
  }
47483
47506
 
@@ -48582,7 +48605,7 @@ import { spawnSync as spawnSync2 } from "child_process";
48582
48605
  // package.json
48583
48606
  var package_default = {
48584
48607
  name: "@mrciphersmith/keryx",
48585
- version: "0.2.42",
48608
+ version: "0.2.44",
48586
48609
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
48587
48610
  private: false,
48588
48611
  publishConfig: {
@@ -49460,6 +49483,12 @@ function onKeypress(r, handler) {
49460
49483
  }
49461
49484
  var BACKDROP_ID = "modal-backdrop";
49462
49485
  var PANEL_ID = "modal-panel";
49486
+ var BACKDROP_ALPHA = 0.85;
49487
+ function backdropFillColor(otui) {
49488
+ const color = otui.RGBA.fromHex(getTheme().bg);
49489
+ color.a = BACKDROP_ALPHA;
49490
+ return color;
49491
+ }
49463
49492
  var MODAL_PANEL_MARGIN = 1;
49464
49493
  var MODAL_PANEL_CHROME_X = 4;
49465
49494
  var MODAL_PANEL_MIN_WIDTH = 72;
@@ -49600,7 +49629,7 @@ function ensureHost(otui, chrome) {
49600
49629
  left: 0,
49601
49630
  width: "100%",
49602
49631
  height: "100%",
49603
- backgroundColor: getTheme().bg,
49632
+ backgroundColor: backdropFillColor(otui),
49604
49633
  zIndex: 100,
49605
49634
  flexDirection: "column",
49606
49635
  justifyContent: "center",
@@ -49826,7 +49855,201 @@ function openModal(otui, chrome, input2) {
49826
49855
  // src/tui/inspector-sources.ts
49827
49856
  init_store2();
49828
49857
  init_workspace_service();
49858
+ import { randomUUID as randomUUID24 } from "crypto";
49859
+
49860
+ // src/sac/catch-up.ts
49861
+ init_fs();
49862
+ init_config_dir();
49863
+ init_paths();
49864
+ init_slate();
49865
+ init_store3();
49866
+ init_proposal_lifecycle();
49867
+ init_workspace_service();
49868
+ import { randomUUID as randomUUID23 } from "crypto";
49869
+ import { readdir as readdir24 } from "fs/promises";
49870
+ import path145 from "path";
49871
+
49872
+ // src/sac/lifecycle-flag.ts
49873
+ init_store();
49874
+ init_service3();
49875
+ init_decision_dedup();
49876
+ init_workspace_service();
49829
49877
  import { randomUUID as randomUUID22 } from "crypto";
49878
+ function normalize3(raw) {
49879
+ return raw.replace(/^\.\//, "");
49880
+ }
49881
+ function isStillPresent(recorded, valid) {
49882
+ const normalized = normalize3(recorded);
49883
+ return valid.has(normalized) || valid.has(moduleNameFromProjectPath(normalized));
49884
+ }
49885
+ async function computeLifecycleFlags(cwd, now = () => new Date) {
49886
+ const valid = await validModuleNames(cwd);
49887
+ if (valid === undefined)
49888
+ return [];
49889
+ const flaggedAt = now().toISOString();
49890
+ const flags = [];
49891
+ const workspaceService = new WorkspaceService({
49892
+ workspaceRoot: cwd,
49893
+ authorizationServer: localWorkspaceAuthorizationServer(),
49894
+ strictGuard: { mode: "strict", availability: "available", decision: "pass", policyRevision: "local-offline-v1" }
49895
+ });
49896
+ const workspaces = await workspaceService.list({ request: undefined, requestCorrelationId: randomUUID22(), includeArchived: true });
49897
+ for (const workspace of workspaces) {
49898
+ const component = workspace.resources.find((r) => r.kind === "component")?.uri;
49899
+ if (component !== undefined && !isStillPresent(component, valid)) {
49900
+ flags.push({ kind: "workspace", ref: workspace.id, missingComponent: normalize3(component), flaggedAt });
49901
+ }
49902
+ }
49903
+ for (const entry of await collectEntries(cwd)) {
49904
+ const module = entry.scopes.module;
49905
+ if (module !== null && module.length > 0 && !isStillPresent(module, valid)) {
49906
+ flags.push({ kind: "memory-entry", ref: entry.relativePath, missingComponent: normalize3(module), flaggedAt });
49907
+ }
49908
+ }
49909
+ for (const decision of await collectWikiDecisionEntries(cwd)) {
49910
+ const module = decision.scopes.module;
49911
+ if (module !== null && module.length > 0 && !isStillPresent(module, valid)) {
49912
+ flags.push({ kind: "wiki-decision", ref: decision.relativePath, missingComponent: normalize3(module), flaggedAt });
49913
+ }
49914
+ }
49915
+ return flags;
49916
+ }
49917
+
49918
+ // src/sac/catch-up.ts
49919
+ async function buildCatchUp(input2) {
49920
+ const [proposals, sessionCategories, lifecycleFlagsAll] = await Promise.all([
49921
+ collectProposals(input2.cwd, input2.workspaceId),
49922
+ collectSessionCategories(input2.cwd),
49923
+ computeLifecycleFlags(input2.cwd)
49924
+ ]);
49925
+ const lifecycleFlags = input2.workspaceId === undefined ? lifecycleFlagsAll : lifecycleFlagsAll.filter((flag) => flag.kind !== "workspace" || flag.ref === input2.workspaceId);
49926
+ return { proposals, ...sessionCategories, lifecycleFlags };
49927
+ }
49928
+ async function collectProposals(cwd, workspaceId) {
49929
+ const authorizationServer = localWorkspaceAuthorizationServer();
49930
+ const actor = await authorizationServer.actorContextFor(undefined, randomUUID23());
49931
+ if (!actor)
49932
+ throw new Error("trusted ActorContext is required for catch-up");
49933
+ const proposalService = createLocalProposalLifecycleService(cwd);
49934
+ const groups = await proposalService.listVisibleProposedProposals(actor);
49935
+ const scoped = workspaceId === undefined ? groups : groups.filter((group) => group.workspace.id === workspaceId);
49936
+ const flattened = scoped.flatMap((group) => group.proposals.map((proposal) => ({ group, proposal })));
49937
+ return Promise.all(flattened.map(async ({ group, proposal }) => {
49938
+ const fresh = await proposalService.isEvidenceFresh(proposal, actor);
49939
+ return { type: "proposal", workspaceId: group.workspace.id, proposalId: proposal.id, fresh };
49940
+ }));
49941
+ }
49942
+ async function classifySession(session) {
49943
+ const dir = sessionDir(session.projectPath, session.id);
49944
+ if (await isLockHeld(slateLockPath(dir)))
49945
+ return;
49946
+ const terminalState = await readTerminalState(dir);
49947
+ if (terminalState !== undefined) {
49948
+ const workspaceId2 = (await safeReadSlate(dir))?.workspaceId;
49949
+ return { kind: "blocked", item: { type: "blocked", sessionId: session.id, ...workspaceId2 !== undefined ? { workspaceId: workspaceId2 } : {}, terminalState } };
49950
+ }
49951
+ const unboundCandidate = await readNewestUnboundCandidate(dir);
49952
+ if (unboundCandidate !== undefined) {
49953
+ return {
49954
+ kind: "unbound-candidate",
49955
+ item: { type: "unbound-candidate", sessionId: session.id, evidencePath: unboundCandidate.evidencePath, summary: unboundCandidate.summary }
49956
+ };
49957
+ }
49958
+ if (!await isSlateEngaged(dir))
49959
+ return;
49960
+ const workspaceId = (await safeReadSlate(dir))?.workspaceId;
49961
+ return { kind: "unknown", item: { type: "unknown", sessionId: session.id, ...workspaceId !== undefined ? { workspaceId } : {}, lastSeenAt: session.updatedAt } };
49962
+ }
49963
+ async function collectSessionCategories(cwd) {
49964
+ const classified = await Promise.all(listSessions(cwd).map((session) => classifySession(session)));
49965
+ const blocked2 = [];
49966
+ const unboundCandidates = [];
49967
+ const unknown = [];
49968
+ for (const category of classified) {
49969
+ if (category === undefined)
49970
+ continue;
49971
+ if (category.kind === "blocked")
49972
+ blocked2.push(category.item);
49973
+ else if (category.kind === "unbound-candidate")
49974
+ unboundCandidates.push(category.item);
49975
+ else
49976
+ unknown.push(category.item);
49977
+ }
49978
+ return { blocked: blocked2, unboundCandidates, unknown };
49979
+ }
49980
+ async function isSlateEngaged(dir) {
49981
+ if (await pathExists(path145.join(dir, "slate.json")))
49982
+ return true;
49983
+ if (await pathExists(path145.join(dir, "terminal-state.json")))
49984
+ return true;
49985
+ try {
49986
+ const entries = await readdir24(path145.join(dir, "slate-archive"));
49987
+ return entries.length > 0;
49988
+ } catch {
49989
+ return false;
49990
+ }
49991
+ }
49992
+ async function safeReadSlate(dir) {
49993
+ try {
49994
+ return await readSlate(dir);
49995
+ } catch {
49996
+ return;
49997
+ }
49998
+ }
49999
+ async function readTerminalState(dir) {
50000
+ const result = readConfigFile(path145.join(dir, "terminal-state.json"));
50001
+ if (!result.ok) {
50002
+ return;
50003
+ }
50004
+ try {
50005
+ return JSON.parse(result.text);
50006
+ } catch {
50007
+ return;
50008
+ }
50009
+ }
50010
+ async function readNewestUnboundCandidate(dir) {
50011
+ const archiveDir = path145.join(dir, "slate-archive");
50012
+ let entries;
50013
+ try {
50014
+ entries = (await readdir24(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
50015
+ } catch {
50016
+ return;
50017
+ }
50018
+ entries.sort();
50019
+ for (let i = entries.length - 1;i >= 0; i--) {
50020
+ const evidencePath = path145.join(archiveDir, entries[i]);
50021
+ const result = readConfigFile(evidencePath);
50022
+ if (!result.ok) {
50023
+ continue;
50024
+ }
50025
+ try {
50026
+ const parsed = JSON.parse(result.text);
50027
+ if (parsed.recordType !== "unbound-candidate")
50028
+ continue;
50029
+ return { evidencePath, summary: summarizeUnboundCandidate(parsed.groups) };
50030
+ } catch {
50031
+ continue;
50032
+ }
50033
+ }
50034
+ return;
50035
+ }
50036
+ function summarizeUnboundCandidate(groups) {
50037
+ const safeGroups = groups ?? [];
50038
+ if (safeGroups.length === 0)
50039
+ return "no seeds captured";
50040
+ const seedCount = safeGroups.reduce((sum, group) => sum + (group.seeds?.length ?? 0), 0);
50041
+ const kinds = safeGroups.map((group) => typeof group.kind === "string" ? group.kind : "unknown").join(", ");
50042
+ return `${seedCount} untriaged seed(s) across ${safeGroups.length} kind(s) (${kinds})`;
50043
+ }
50044
+
50045
+ // src/tui/inspector-sources.ts
50046
+ init_slate();
50047
+
50048
+ // src/session/index.ts
50049
+ init_paths();
50050
+ init_store3();
50051
+
50052
+ // src/tui/inspector-sources.ts
49830
50053
  function workspaceFromManifest(manifest) {
49831
50054
  return {
49832
50055
  id: manifest.id,
@@ -49884,24 +50107,40 @@ function flowItemFromState(flow, dir) {
49884
50107
  tasks: flow.tasks.map((task) => ({ id: task.id, title: task.title, status: task.status }))
49885
50108
  };
49886
50109
  }
50110
+ function localSacGuard() {
50111
+ return {
50112
+ mode: "strict",
50113
+ availability: "available",
50114
+ decision: "pass",
50115
+ policyRevision: "local-offline-v1"
50116
+ };
50117
+ }
49887
50118
  async function loadInspectorWorkspaces(cwd) {
49888
50119
  try {
49889
50120
  const service5 = new WorkspaceService({
49890
50121
  workspaceRoot: cwd,
49891
50122
  authorizationServer: localWorkspaceAuthorizationServer(),
49892
- strictGuard: {
49893
- mode: "strict",
49894
- availability: "available",
49895
- decision: "pass",
49896
- policyRevision: "local-offline-v1"
49897
- }
50123
+ strictGuard: localSacGuard()
49898
50124
  });
49899
- const listed = await service5.list({ request: undefined, requestCorrelationId: randomUUID22() });
50125
+ const listed = await service5.list({ request: undefined, requestCorrelationId: randomUUID24() });
49900
50126
  return listed.map(workspaceFromManifest);
49901
50127
  } catch {
49902
50128
  return [];
49903
50129
  }
49904
50130
  }
50131
+ async function loadInspectorWorkspace(cwd, workspaceId) {
50132
+ try {
50133
+ const service5 = new WorkspaceService({
50134
+ workspaceRoot: cwd,
50135
+ authorizationServer: localWorkspaceAuthorizationServer(),
50136
+ strictGuard: localSacGuard()
50137
+ });
50138
+ const manifest = await service5.show({ request: undefined, requestCorrelationId: randomUUID24(), workspaceId });
50139
+ return workspaceFromManifest(manifest);
50140
+ } catch {
50141
+ return;
50142
+ }
50143
+ }
49905
50144
  function sortFlowsNewestFirst(items) {
49906
50145
  return [...items].sort((left, right) => {
49907
50146
  const byId = Number(right.id) - Number(left.id);
@@ -49928,6 +50167,40 @@ async function loadInspectorFlows(cwd) {
49928
50167
  return [];
49929
50168
  }
49930
50169
  }
50170
+ function sortSlatesNewestFirst(items) {
50171
+ return [...items].sort((left, right) => left.updatedAt < right.updatedAt ? 1 : left.updatedAt > right.updatedAt ? -1 : 0);
50172
+ }
50173
+ async function loadInspectorSlates(cwd, workspaceId) {
50174
+ try {
50175
+ const sessions = listSessions(cwd);
50176
+ const items = [];
50177
+ for (const session of sessions) {
50178
+ const dir = sessionDir(session.projectPath, session.id);
50179
+ let slate;
50180
+ try {
50181
+ slate = await readSlate(dir);
50182
+ } catch {
50183
+ continue;
50184
+ }
50185
+ if (slate === undefined || slate.workspaceId !== workspaceId) {
50186
+ continue;
50187
+ }
50188
+ items.push({
50189
+ sessionId: session.id,
50190
+ sessionTitle: session.title,
50191
+ updatedAt: session.updatedAt,
50192
+ courseStatus: session.courseStatus ?? "unbound",
50193
+ ...slate.course.flowRef !== undefined ? { flowRef: slate.course.flowRef } : {},
50194
+ seedCount: slate.seeds.length,
50195
+ touchedFiles: slate.anchors.touched,
50196
+ seeds: slate.seeds
50197
+ });
50198
+ }
50199
+ return sortSlatesNewestFirst(items);
50200
+ } catch {
50201
+ return [];
50202
+ }
50203
+ }
49931
50204
  function formatWorkspaceLines(workspaces) {
49932
50205
  if (workspaces.length === 0) {
49933
50206
  return ["No workspaces recorded in this session."];
@@ -49935,6 +50208,16 @@ function formatWorkspaceLines(workspaces) {
49935
50208
  const width = workspaces.reduce((max, workspace) => Math.max(max, workspace.id.length), 0);
49936
50209
  return workspaces.map((workspace) => `${workspace.id.padEnd(width)} ${workspace.status} ${workspace.title}`);
49937
50210
  }
50211
+ async function loadInspectorCatchUp(cwd) {
50212
+ try {
50213
+ return await buildCatchUp({ cwd });
50214
+ } catch {
50215
+ return { proposals: [], blocked: [], unboundCandidates: [], unknown: [], lifecycleFlags: [] };
50216
+ }
50217
+ }
50218
+ function catchUpItems(report) {
50219
+ return [...report.proposals, ...report.blocked, ...report.unboundCandidates, ...report.unknown];
50220
+ }
49938
50221
  function formatSessionFlowLines(flows) {
49939
50222
  if (flows.length === 0) {
49940
50223
  return ["No flows recorded in this session."];
@@ -50177,6 +50460,585 @@ function openFlows(otui, chrome, options) {
50177
50460
  return presentFlows((hostOtui, hostChrome, input2) => openModal(hostOtui, hostChrome, input2), otui, chrome, options);
50178
50461
  }
50179
50462
 
50463
+ // src/tui/workspace-inspector.ts
50464
+ var WORKSPACE_COMMAND = "/workspace";
50465
+ var WORKSPACE_FOOTER = [
50466
+ { key: "[/]", label: "slate" },
50467
+ { key: "\u2191/\u2193", label: "scroll" },
50468
+ { key: "\u2190/\u2192", label: "tabs" },
50469
+ { key: "esc", label: "close" }
50470
+ ];
50471
+ function isWorkspaceCommand(line) {
50472
+ const token = line.trim().split(/\s+/)[0] ?? "";
50473
+ return token === WORKSPACE_COMMAND;
50474
+ }
50475
+ function formatWorkspaceOverviewLines(workspace, slateCount) {
50476
+ const resourceLines = workspace.resources.length === 0 ? [" (no resources)"] : workspace.resources.map((resource) => ` ${resource.kind.padEnd(10)} ${resource.uri}`);
50477
+ return [
50478
+ `${workspace.id} ${workspace.title}`,
50479
+ `Status ${workspace.status}`,
50480
+ `Slates ${slateCount}`,
50481
+ "",
50482
+ "Resources",
50483
+ ...resourceLines
50484
+ ];
50485
+ }
50486
+ function formatSlateListLines(items, selected) {
50487
+ if (items.length === 0) {
50488
+ return ["No slates bound to this workspace yet."];
50489
+ }
50490
+ return items.map((item, index) => {
50491
+ const mark = index === selected ? ">" : " ";
50492
+ const flow = item.flowRef !== undefined ? `flow ${item.flowRef}` : "no flow";
50493
+ return `${mark} ${item.sessionId.slice(0, 8)} ${item.courseStatus.padEnd(8)} ${item.seedCount} seeds ${flow} ${item.sessionTitle}`;
50494
+ });
50495
+ }
50496
+ function formatSlateDetailLines(item) {
50497
+ if (item === undefined) {
50498
+ return ["No slate selected.", "", "Press Enter (or click a row) on the Slates tab to view one."];
50499
+ }
50500
+ const touchedLines = item.touchedFiles.length === 0 ? [" (none)"] : item.touchedFiles.map((file) => ` ${file}`);
50501
+ const seedLines = item.seeds.length === 0 ? [" (no seeds)"] : item.seeds.map((seed) => ` ${seed.ts} ${(seed.kind ?? "note").padEnd(14)} ${seed.text}`);
50502
+ return [
50503
+ `${item.sessionId} ${item.sessionTitle}`,
50504
+ `Course ${item.courseStatus}`,
50505
+ `Flow ${item.flowRef ?? "\u2014"}`,
50506
+ `Updated ${item.updatedAt}`,
50507
+ "",
50508
+ "Touched files",
50509
+ ...touchedLines,
50510
+ "",
50511
+ "Seeds",
50512
+ ...seedLines
50513
+ ];
50514
+ }
50515
+ function clampScroll2(offset, lineCount, height) {
50516
+ const max = Math.max(0, lineCount - height);
50517
+ return Math.min(max, Math.max(0, offset));
50518
+ }
50519
+ function windowLines2(lines, offset, height) {
50520
+ if (height < 1) {
50521
+ return [];
50522
+ }
50523
+ const start = clampScroll2(offset, lines.length, height);
50524
+ return lines.slice(start, start + height);
50525
+ }
50526
+ function scrollToReveal2(index, offset, height) {
50527
+ if (index < offset) {
50528
+ return index;
50529
+ }
50530
+ if (index >= offset + height) {
50531
+ return index - height + 1;
50532
+ }
50533
+ return offset;
50534
+ }
50535
+ function wrapLines2(text, width) {
50536
+ if (width === undefined || width < 8) {
50537
+ return text;
50538
+ }
50539
+ return text.split(`
50540
+ `).flatMap((line) => {
50541
+ if (line.length <= width) {
50542
+ return [line];
50543
+ }
50544
+ const chunks = [];
50545
+ for (let i = 0;i < line.length; i += width) {
50546
+ chunks.push(line.slice(i, i + width));
50547
+ }
50548
+ return chunks;
50549
+ }).join(`
50550
+ `);
50551
+ }
50552
+ function paintLines2(otui, renderer, body, lines, width, idPrefix = "workspace") {
50553
+ if (otui === undefined || otui === null || body === undefined || body === null) {
50554
+ return;
50555
+ }
50556
+ const parent = body;
50557
+ const ctor = otui.TextRenderable;
50558
+ if (parent.add === undefined || ctor === undefined) {
50559
+ return;
50560
+ }
50561
+ const node = new ctor(renderer, { id: `${idPrefix}-body`, content: wrapLines2(lines.join(`
50562
+ `), width) });
50563
+ parent.add(node);
50564
+ return node;
50565
+ }
50566
+ function presentWorkspace(openModal2, otui, chrome, options) {
50567
+ const items = sortSlatesNewestFirst(options.slates);
50568
+ let selected = 0;
50569
+ let openIndex;
50570
+ let overviewScroll = 0;
50571
+ let listScroll = 0;
50572
+ let detailScroll = 0;
50573
+ let overviewNode;
50574
+ let listNode;
50575
+ let detailNode;
50576
+ let unsubscribeKey;
50577
+ const rendererHint = options.renderer ?? chrome?.renderer;
50578
+ const bodyRows = options.visibleRows ?? (typeof rendererHint?.width === "number" && typeof rendererHint.height === "number" ? modalBodyRows(resolveModalPanelSize(rendererHint.width, rendererHint.height).height) : 13);
50579
+ let tabWidth;
50580
+ const overviewLines = () => wrapLines2(formatWorkspaceOverviewLines(options.workspace, items.length).join(`
50581
+ `), tabWidth).split(`
50582
+ `);
50583
+ const listLines = () => formatSlateListLines(items, selected);
50584
+ const detailLines = () => {
50585
+ const item = openIndex !== undefined ? items[openIndex] : undefined;
50586
+ return wrapLines2(formatSlateDetailLines(item).join(`
50587
+ `), tabWidth).split(`
50588
+ `);
50589
+ };
50590
+ const paintSelection = () => {
50591
+ listScroll = scrollToReveal2(selected, listScroll, bodyRows);
50592
+ listScroll = clampScroll2(listScroll, items.length, bodyRows);
50593
+ detailScroll = clampScroll2(detailScroll, detailLines().length, bodyRows);
50594
+ overviewScroll = clampScroll2(overviewScroll, overviewLines().length, bodyRows);
50595
+ if (overviewNode !== undefined) {
50596
+ overviewNode.content = windowLines2(overviewLines(), overviewScroll, bodyRows).join(`
50597
+ `);
50598
+ }
50599
+ if (listNode !== undefined) {
50600
+ listNode.content = windowLines2(listLines(), listScroll, bodyRows).join(`
50601
+ `);
50602
+ }
50603
+ if (detailNode !== undefined) {
50604
+ detailNode.content = windowLines2(detailLines(), detailScroll, bodyRows).join(`
50605
+ `);
50606
+ }
50607
+ };
50608
+ const moveSelection = (next) => {
50609
+ if (items.length === 0) {
50610
+ return;
50611
+ }
50612
+ const clamped = Math.min(items.length - 1, Math.max(0, next));
50613
+ if (clamped === selected) {
50614
+ return;
50615
+ }
50616
+ selected = clamped;
50617
+ paintSelection();
50618
+ };
50619
+ const openSelected = () => {
50620
+ if (items.length === 0) {
50621
+ return;
50622
+ }
50623
+ openIndex = selected;
50624
+ detailScroll = 0;
50625
+ handle?.setTab("detail");
50626
+ paintSelection();
50627
+ };
50628
+ const handle = openModal2(otui, chrome, {
50629
+ title: WORKSPACE_COMMAND,
50630
+ tabs: [
50631
+ { id: "overview", label: "Workspace" },
50632
+ { id: "slates", label: "Slates" },
50633
+ { id: "detail", label: "Slate" }
50634
+ ],
50635
+ initialTab: "overview",
50636
+ footer: WORKSPACE_FOOTER,
50637
+ renderTab: (tabId, body, ctx) => {
50638
+ const renderer = options.renderer ?? chrome?.renderer;
50639
+ tabWidth = ctx?.width;
50640
+ if (tabId === "overview") {
50641
+ overviewScroll = clampScroll2(overviewScroll, overviewLines().length, bodyRows);
50642
+ overviewNode = paintLines2(otui, renderer, body, windowLines2(overviewLines(), overviewScroll, bodyRows), undefined, "workspace-overview");
50643
+ return;
50644
+ }
50645
+ if (tabId === "slates") {
50646
+ listScroll = scrollToReveal2(selected, listScroll, bodyRows);
50647
+ listNode = paintLines2(otui, renderer, body, windowLines2(listLines(), listScroll, bodyRows), undefined, "workspace-slates");
50648
+ return;
50649
+ }
50650
+ detailScroll = clampScroll2(detailScroll, detailLines().length, bodyRows);
50651
+ detailNode = paintLines2(otui, renderer, body, windowLines2(detailLines(), detailScroll, bodyRows), tabWidth, "workspace-detail");
50652
+ return () => {
50653
+ openIndex = undefined;
50654
+ };
50655
+ },
50656
+ onClose: () => {
50657
+ unsubscribeKey?.();
50658
+ }
50659
+ });
50660
+ if (handle === undefined) {
50661
+ return;
50662
+ }
50663
+ if (options.onKeypress !== undefined) {
50664
+ unsubscribeKey = options.onKeypress((key) => {
50665
+ const token = key.name || key.sequence;
50666
+ const active = handle.activeTab();
50667
+ if (active === "slates") {
50668
+ if (token === "[" || token === "p" || token === "up" || token === "k") {
50669
+ moveSelection(selected - 1);
50670
+ return;
50671
+ }
50672
+ if (token === "]" || token === "n" || token === "down" || token === "j") {
50673
+ moveSelection(selected + 1);
50674
+ return;
50675
+ }
50676
+ if (token === "return" || token === "enter") {
50677
+ openSelected();
50678
+ }
50679
+ return;
50680
+ }
50681
+ const scrollable = active === "detail" ? detailLines : overviewLines;
50682
+ const setScroll = (value) => {
50683
+ if (active === "detail") {
50684
+ detailScroll = value;
50685
+ } else {
50686
+ overviewScroll = value;
50687
+ }
50688
+ };
50689
+ const current = active === "detail" ? detailScroll : overviewScroll;
50690
+ if (token === "up" || token === "k") {
50691
+ setScroll(clampScroll2(current - 1, scrollable().length, bodyRows));
50692
+ paintSelection();
50693
+ return;
50694
+ }
50695
+ if (token === "down" || token === "j") {
50696
+ setScroll(clampScroll2(current + 1, scrollable().length, bodyRows));
50697
+ paintSelection();
50698
+ return;
50699
+ }
50700
+ if (token === "pageup" || token === "pagedown") {
50701
+ const step = token === "pageup" ? -bodyRows : bodyRows;
50702
+ setScroll(clampScroll2(current + step, scrollable().length, bodyRows));
50703
+ paintSelection();
50704
+ }
50705
+ });
50706
+ }
50707
+ return handle;
50708
+ }
50709
+ function openWorkspace(otui, chrome, options) {
50710
+ return presentWorkspace((hostOtui, hostChrome, input2) => openModal(hostOtui, hostChrome, input2), otui, chrome, options);
50711
+ }
50712
+
50713
+ // src/tui/review-inspector.ts
50714
+ var REVIEW_COMMAND = "/review";
50715
+ var REVIEW_FOOTER = [
50716
+ { key: "[/]", label: "item" },
50717
+ { key: "a y", label: "accept" },
50718
+ { key: "\u2191/\u2193", label: "scroll" },
50719
+ { key: "\u2190/\u2192", label: "tabs" },
50720
+ { key: "esc", label: "close" }
50721
+ ];
50722
+ function isReviewCommand(line) {
50723
+ const token = line.trim().split(/\s+/)[0] ?? "";
50724
+ return token === REVIEW_COMMAND;
50725
+ }
50726
+ var TYPE_LABEL = {
50727
+ proposal: "PROPOSAL",
50728
+ blocked: "BLOCKED",
50729
+ "unbound-candidate": "UNBOUND",
50730
+ unknown: "UNKNOWN"
50731
+ };
50732
+ function summarizeReviewItem(item) {
50733
+ switch (item.type) {
50734
+ case "proposal":
50735
+ return `${item.proposalId} in ${item.workspaceId}${item.fresh ? "" : " (stale)"}`;
50736
+ case "blocked":
50737
+ return `${item.sessionId} \u2014 ${item.terminalState.reason}`;
50738
+ case "unbound-candidate":
50739
+ return `${item.sessionId} \u2014 ${item.summary}`;
50740
+ case "unknown":
50741
+ return `${item.sessionId} \u2014 last seen ${item.lastSeenAt}`;
50742
+ }
50743
+ }
50744
+ function formatReviewListLines(items, selected) {
50745
+ if (items.length === 0) {
50746
+ return ["Nothing needs review right now."];
50747
+ }
50748
+ return items.map((item, index) => {
50749
+ const mark = index === selected ? ">" : " ";
50750
+ return `${mark} ${TYPE_LABEL[item.type].padEnd(8)} ${summarizeReviewItem(item)}`;
50751
+ });
50752
+ }
50753
+ function describeReviewItem(item) {
50754
+ switch (item.type) {
50755
+ case "proposal":
50756
+ return [
50757
+ `Proposal ${item.proposalId}`,
50758
+ `Workspace ${item.workspaceId}`,
50759
+ `Evidence ${item.fresh ? "fresh" : "stale \u2014 evidence has drifted since this proposal was created; re-run wrap-up before deciding"}`,
50760
+ "",
50761
+ `Reject/dismiss from a terminal: keryx workspace review ${item.workspaceId} ${item.proposalId} --decision <rejected|dismissed>`
50762
+ ];
50763
+ case "blocked":
50764
+ return [
50765
+ `Session ${item.sessionId}${item.workspaceId !== undefined ? ` (workspace ${item.workspaceId})` : ""}`,
50766
+ `Stopped unattended: ${item.terminalState.reason}`,
50767
+ `Occurred ${item.terminalState.occurredAt}`,
50768
+ "",
50769
+ `Resume: keryx shell -r ${item.sessionId}`
50770
+ ];
50771
+ case "unbound-candidate":
50772
+ return [
50773
+ `Session ${item.sessionId}`,
50774
+ `Untriaged seeds: ${item.summary}`,
50775
+ `Evidence ${item.evidencePath}`,
50776
+ "",
50777
+ `Bind: keryx workspace propose <workspace-id> --kind <kind> --session ${item.sessionId}`
50778
+ ];
50779
+ case "unknown":
50780
+ return [
50781
+ `Session ${item.sessionId}${item.workspaceId !== undefined ? ` (workspace ${item.workspaceId})` : ""}`,
50782
+ `Last seen ${item.lastSeenAt}`,
50783
+ "No proposal, terminal state, or unbound-candidate artifact recorded.",
50784
+ "",
50785
+ `Investigate: keryx sessions list / keryx shell -r ${item.sessionId}`
50786
+ ];
50787
+ }
50788
+ }
50789
+ function formatReviewDetailLines(item, status) {
50790
+ if (item === undefined) {
50791
+ return ["No item selected.", "", "Press Enter (or click a row) on the Review tab to view one."];
50792
+ }
50793
+ const lines = describeReviewItem(item);
50794
+ if (item.type !== "proposal") {
50795
+ return lines;
50796
+ }
50797
+ const withAction = [...lines, ""];
50798
+ if (status.kind === "armed") {
50799
+ withAction.push("Press [y] to CONFIRM accept, any other key cancels.");
50800
+ } else if (status.kind === "running") {
50801
+ withAction.push("Accepting\u2026 running `keryx workspace confirm-review` then `keryx workspace review`.");
50802
+ } else if (status.kind === "done" && status.outcome.ok) {
50803
+ withAction.push("\u2713 Accepted.");
50804
+ } else if (status.kind === "done" && !status.outcome.ok) {
50805
+ withAction.push(`\u2717 Accept failed: ${status.outcome.message}`);
50806
+ } else {
50807
+ withAction.push("[a] Accept this proposal");
50808
+ }
50809
+ return withAction;
50810
+ }
50811
+ function clampScroll3(offset, lineCount, height) {
50812
+ const max = Math.max(0, lineCount - height);
50813
+ return Math.min(max, Math.max(0, offset));
50814
+ }
50815
+ function windowLines3(lines, offset, height) {
50816
+ if (height < 1) {
50817
+ return [];
50818
+ }
50819
+ const start = clampScroll3(offset, lines.length, height);
50820
+ return lines.slice(start, start + height);
50821
+ }
50822
+ function scrollToReveal3(index, offset, height) {
50823
+ if (index < offset) {
50824
+ return index;
50825
+ }
50826
+ if (index >= offset + height) {
50827
+ return index - height + 1;
50828
+ }
50829
+ return offset;
50830
+ }
50831
+ function wrapLines3(text, width) {
50832
+ if (width === undefined || width < 8) {
50833
+ return text;
50834
+ }
50835
+ return text.split(`
50836
+ `).flatMap((line) => {
50837
+ if (line.length <= width) {
50838
+ return [line];
50839
+ }
50840
+ const chunks = [];
50841
+ for (let i = 0;i < line.length; i += width) {
50842
+ chunks.push(line.slice(i, i + width));
50843
+ }
50844
+ return chunks;
50845
+ }).join(`
50846
+ `);
50847
+ }
50848
+ function paintLines3(otui, renderer, body, lines, width) {
50849
+ if (otui === undefined || otui === null || body === undefined || body === null) {
50850
+ return;
50851
+ }
50852
+ const parent = body;
50853
+ const ctor = otui.TextRenderable;
50854
+ if (parent.add === undefined || ctor === undefined) {
50855
+ return;
50856
+ }
50857
+ const node = new ctor(renderer, { id: "review-body", content: wrapLines3(lines.join(`
50858
+ `), width) });
50859
+ parent.add(node);
50860
+ return node;
50861
+ }
50862
+ function presentReview(openModal2, otui, chrome, options) {
50863
+ const items = [...options.items];
50864
+ let selected = 0;
50865
+ let listScroll = 0;
50866
+ let detailScroll = 0;
50867
+ let status = { kind: "idle" };
50868
+ let listNode;
50869
+ let detailNode;
50870
+ let unsubscribeKey;
50871
+ const rendererHint = options.renderer ?? chrome?.renderer;
50872
+ const bodyRows = options.visibleRows ?? (typeof rendererHint?.width === "number" && typeof rendererHint.height === "number" ? modalBodyRows(resolveModalPanelSize(rendererHint.width, rendererHint.height).height) : 13);
50873
+ let tabWidth;
50874
+ const listLines = () => formatReviewListLines(items, selected);
50875
+ const detailLines = () => wrapLines3(formatReviewDetailLines(items[selected], status).join(`
50876
+ `), tabWidth).split(`
50877
+ `);
50878
+ const paintSelection = () => {
50879
+ listScroll = scrollToReveal3(selected, listScroll, bodyRows);
50880
+ listScroll = clampScroll3(listScroll, items.length, bodyRows);
50881
+ detailScroll = clampScroll3(detailScroll, detailLines().length, bodyRows);
50882
+ if (listNode !== undefined) {
50883
+ listNode.content = windowLines3(listLines(), listScroll, bodyRows).join(`
50884
+ `);
50885
+ }
50886
+ if (detailNode !== undefined) {
50887
+ detailNode.content = windowLines3(detailLines(), detailScroll, bodyRows).join(`
50888
+ `);
50889
+ }
50890
+ };
50891
+ const moveSelection = (next) => {
50892
+ if (items.length === 0) {
50893
+ return;
50894
+ }
50895
+ const clamped = Math.min(items.length - 1, Math.max(0, next));
50896
+ if (clamped === selected) {
50897
+ return;
50898
+ }
50899
+ selected = clamped;
50900
+ detailScroll = 0;
50901
+ status = { kind: "idle" };
50902
+ paintSelection();
50903
+ };
50904
+ const runAccept = () => {
50905
+ const item = items[selected];
50906
+ if (item === undefined || item.type !== "proposal" || options.acceptProposal === undefined) {
50907
+ return;
50908
+ }
50909
+ status = { kind: "running" };
50910
+ paintSelection();
50911
+ options.acceptProposal(item).then((outcome) => {
50912
+ status = { kind: "done", outcome };
50913
+ if (outcome.ok) {
50914
+ options.onAccepted?.(item);
50915
+ }
50916
+ paintSelection();
50917
+ });
50918
+ };
50919
+ const handle = openModal2(otui, chrome, {
50920
+ title: REVIEW_COMMAND,
50921
+ tabs: [
50922
+ { id: "list", label: "Review" },
50923
+ { id: "detail", label: "Detail" }
50924
+ ],
50925
+ initialTab: "list",
50926
+ footer: REVIEW_FOOTER,
50927
+ renderTab: (tabId, body, ctx) => {
50928
+ const renderer = options.renderer ?? chrome?.renderer;
50929
+ tabWidth = ctx?.width;
50930
+ if (tabId === "list") {
50931
+ listScroll = scrollToReveal3(selected, listScroll, bodyRows);
50932
+ listNode = paintLines3(otui, renderer, body, windowLines3(listLines(), listScroll, bodyRows));
50933
+ return;
50934
+ }
50935
+ detailScroll = clampScroll3(detailScroll, detailLines().length, bodyRows);
50936
+ detailNode = paintLines3(otui, renderer, body, windowLines3(detailLines(), detailScroll, bodyRows), tabWidth);
50937
+ },
50938
+ onClose: () => {
50939
+ unsubscribeKey?.();
50940
+ }
50941
+ });
50942
+ if (handle === undefined) {
50943
+ return;
50944
+ }
50945
+ if (options.onKeypress !== undefined) {
50946
+ unsubscribeKey = options.onKeypress((key) => {
50947
+ const token = key.name || key.sequence;
50948
+ if (items.length === 0) {
50949
+ return;
50950
+ }
50951
+ const onDetail = handle.activeTab() === "detail";
50952
+ if (onDetail && status.kind === "armed") {
50953
+ if (token === "y") {
50954
+ runAccept();
50955
+ } else {
50956
+ status = { kind: "idle" };
50957
+ paintSelection();
50958
+ }
50959
+ return;
50960
+ }
50961
+ if (token === "[" || token === "p") {
50962
+ moveSelection(selected - 1);
50963
+ return;
50964
+ }
50965
+ if (token === "]" || token === "n") {
50966
+ moveSelection(selected + 1);
50967
+ return;
50968
+ }
50969
+ if (token === "return" || token === "enter") {
50970
+ handle.setTab("detail");
50971
+ return;
50972
+ }
50973
+ if (onDetail && token === "a" && items[selected]?.type === "proposal" && options.acceptProposal !== undefined && status.kind !== "running") {
50974
+ status = { kind: "armed" };
50975
+ paintSelection();
50976
+ return;
50977
+ }
50978
+ if (token === "up" || token === "k") {
50979
+ if (onDetail) {
50980
+ detailScroll = clampScroll3(detailScroll - 1, detailLines().length, bodyRows);
50981
+ paintSelection();
50982
+ } else {
50983
+ moveSelection(selected - 1);
50984
+ }
50985
+ return;
50986
+ }
50987
+ if (token === "down" || token === "j") {
50988
+ if (onDetail) {
50989
+ detailScroll = clampScroll3(detailScroll + 1, detailLines().length, bodyRows);
50990
+ paintSelection();
50991
+ } else {
50992
+ moveSelection(selected + 1);
50993
+ }
50994
+ return;
50995
+ }
50996
+ if (onDetail && (token === "pageup" || token === "pagedown")) {
50997
+ const step = token === "pageup" ? -bodyRows : bodyRows;
50998
+ detailScroll = clampScroll3(detailScroll + step, detailLines().length, bodyRows);
50999
+ paintSelection();
51000
+ }
51001
+ });
51002
+ }
51003
+ return handle;
51004
+ }
51005
+ function openReview(otui, chrome, options) {
51006
+ return presentReview((hostOtui, hostChrome, input2) => openModal(hostOtui, hostChrome, input2), otui, chrome, options);
51007
+ }
51008
+
51009
+ // src/tui/review-accept.ts
51010
+ function shQuote(value) {
51011
+ return `'${value.replace(/'/g, `'\\''`)}'`;
51012
+ }
51013
+ async function acceptProposalViaShell(run, workspaceId, proposalId) {
51014
+ const mint = await run(`keryx workspace confirm-review ${shQuote(workspaceId)} ${shQuote(proposalId)}`);
51015
+ if (mint.isError) {
51016
+ return { ok: false, message: mint.output };
51017
+ }
51018
+ let token;
51019
+ try {
51020
+ const parsed = JSON.parse(mint.output);
51021
+ const candidate = parsed?.token;
51022
+ if (typeof candidate !== "string" || candidate.length === 0) {
51023
+ throw new Error("no token in confirm-review output");
51024
+ }
51025
+ token = candidate;
51026
+ } catch (cause) {
51027
+ return {
51028
+ ok: false,
51029
+ message: `could not parse \`keryx workspace confirm-review\` output: ${cause instanceof Error ? cause.message : String(cause)}`
51030
+ };
51031
+ }
51032
+ const accept = await run(`keryx workspace review ${shQuote(workspaceId)} ${shQuote(proposalId)} --decision accepted --confirm-token ${shQuote(token)}`);
51033
+ if (accept.isError) {
51034
+ return { ok: false, message: accept.output };
51035
+ }
51036
+ return { ok: true };
51037
+ }
51038
+
51039
+ // src/tui/tui-shell.ts
51040
+ init_slate();
51041
+
50180
51042
  // src/tui/context-usage.ts
50181
51043
  var CONTEXT_BAR_WIDTH = 28;
50182
51044
  function lastTurnUsed(usage) {
@@ -50533,6 +51395,16 @@ var AGENT_SLASH_COMMANDS = [
50533
51395
  modes: BOTH
50534
51396
  },
50535
51397
  { name: "/flows", description: "Browse project flows and inspect one", modes: BOTH },
51398
+ {
51399
+ name: "/workspace",
51400
+ description: "Show this session's SAC workspace and its slates",
51401
+ modes: AGENT_ONLY
51402
+ },
51403
+ {
51404
+ name: "/review",
51405
+ description: "Show project-wide items needing review (proposals, blocked sessions)",
51406
+ modes: AGENT_ONLY
51407
+ },
50536
51408
  {
50537
51409
  name: "/compact",
50538
51410
  description: "Compact model context \u2014 /compact [focus] (archive kept)",
@@ -50896,10 +51768,10 @@ init_shell_config();
50896
51768
  // src/lib/permission-mode-config.ts
50897
51769
  init_permission_mode();
50898
51770
  init_config_dir();
50899
- import path145 from "path";
51771
+ import path146 from "path";
50900
51772
  var EMPTY3 = { schemaVersion: 1, projects: {} };
50901
51773
  function permissionModeConfigPath(dir) {
50902
- return path145.join(keryxConfigDir(dir), "permission-mode.json");
51774
+ return path146.join(keryxConfigDir(dir), "permission-mode.json");
50903
51775
  }
50904
51776
  function withRegistryLock2(dir, fn) {
50905
51777
  return withFileLock(`${permissionModeConfigPath(dir)}.lock`, fn, {
@@ -50965,10 +51837,6 @@ function setProjectPermissionMode(projectPath, mode, dir) {
50965
51837
  init_permission_mode();
50966
51838
  init_enrich2();
50967
51839
 
50968
- // src/session/index.ts
50969
- init_paths();
50970
- init_store3();
50971
-
50972
51840
  // src/tui/herdr-report.ts
50973
51841
  import net from "net";
50974
51842
  var SOURCE = "herdr:keryx";
@@ -51248,7 +52116,7 @@ function showComposerChoice(otui, r, dock, request) {
51248
52116
  // src/lib/version-check.ts
51249
52117
  init_config_dir();
51250
52118
  init_fs();
51251
- import path146 from "path";
52119
+ import path147 from "path";
51252
52120
  var REGISTRY_URL = "https://registry.npmjs.org/@mrciphersmith%2Fkeryx/latest";
51253
52121
  var FIXED_INSTALL_COMMAND = "npm install -g @mrciphersmith/keryx@latest";
51254
52122
  var RESPONSE_BODY_LIMIT_BYTES = 64 * 1024;
@@ -51450,7 +52318,7 @@ async function checkVersion(options) {
51450
52318
  const now = options.now ?? Date.now;
51451
52319
  const timestamp = now();
51452
52320
  const configDir = ensureKeryxConfigDir(options.cacheDir);
51453
- const cacheFile = path146.join(configDir, "version-check.json");
52321
+ const cacheFile = path147.join(configDir, "version-check.json");
51454
52322
  const cache = parseCache(cacheFile);
51455
52323
  if (cache?.latestVersion !== undefined && cache.successAt !== undefined && timestamp - cache.successAt >= 0 && timestamp - cache.successAt < SUCCESS_CACHE_TTL_MS) {
51456
52324
  return resultFor(options.currentVersion, current, cache.latestVersion, "cache");
@@ -51681,6 +52549,21 @@ async function createShellChrome(otui, renderer, opts) {
51681
52549
  });
51682
52550
  main.add(scroll);
51683
52551
  const transcript = scroll.content;
52552
+ const queueDock = new otui.BoxRenderable(r, {
52553
+ id: "queue-dock",
52554
+ flexShrink: 0,
52555
+ flexDirection: "column",
52556
+ visible: false,
52557
+ backgroundColor: getTheme().panel,
52558
+ borderStyle: "rounded",
52559
+ border: true,
52560
+ borderColor: getTheme().border,
52561
+ paddingLeft: 1,
52562
+ paddingRight: 1,
52563
+ paddingTop: 0,
52564
+ paddingBottom: 0
52565
+ });
52566
+ main.add(queueDock);
51684
52567
  const dock = new otui.BoxRenderable(r, {
51685
52568
  id: "choice-dock",
51686
52569
  flexShrink: 0,
@@ -51817,6 +52700,16 @@ async function createShellChrome(otui, renderer, opts) {
51817
52700
  textarea.focus();
51818
52701
  }
51819
52702
  };
52703
+ scroll.onMouseDown = (event) => {
52704
+ if (overlayActive())
52705
+ return;
52706
+ input2.focus();
52707
+ event.preventDefault();
52708
+ };
52709
+ sidebar.onMouseDown = () => {
52710
+ if (overlayActive())
52711
+ return;
52712
+ };
51820
52713
  const footer = new otui.BoxRenderable(r, {
51821
52714
  id: "footer",
51822
52715
  flexShrink: 0,
@@ -51983,6 +52876,8 @@ async function createShellChrome(otui, renderer, opts) {
51983
52876
  sidebar.borderColor = theme.highlight;
51984
52877
  dock.backgroundColor = theme.panel;
51985
52878
  dock.borderColor = theme.border;
52879
+ queueDock.backgroundColor = theme.panel;
52880
+ queueDock.borderColor = theme.border;
51986
52881
  composer.borderColor = theme.border;
51987
52882
  menu.backgroundColor = theme.panel;
51988
52883
  menu.focusedBackgroundColor = theme.panel;
@@ -52004,6 +52899,7 @@ async function createShellChrome(otui, renderer, opts) {
52004
52899
  scroll,
52005
52900
  transcript,
52006
52901
  dock,
52902
+ queueDock,
52007
52903
  menu,
52008
52904
  composer,
52009
52905
  textarea,
@@ -52150,6 +53046,28 @@ function reinsertMainQueueItem(items, at, item) {
52150
53046
  return next;
52151
53047
  }
52152
53048
 
53049
+ // src/tui/queue-nav.ts
53050
+ var QUEUE_NAV_ACTIONS = ["force", "edit", "delete"];
53051
+ function stepQueueNavIndex(selected, count, direction) {
53052
+ if (count <= 0)
53053
+ return 0;
53054
+ if (direction === "up") {
53055
+ return selected > 0 ? selected - 1 : count - 1;
53056
+ }
53057
+ return selected < count - 1 ? selected + 1 : 0;
53058
+ }
53059
+ function clampQueueNavIndex(selected, count) {
53060
+ if (count <= 0)
53061
+ return 0;
53062
+ return Math.min(Math.max(selected, 0), count - 1);
53063
+ }
53064
+ function stepQueueNavAction(selected, direction) {
53065
+ const idx = QUEUE_NAV_ACTIONS.indexOf(selected);
53066
+ const count = QUEUE_NAV_ACTIONS.length;
53067
+ const nextIdx = direction === "left" ? idx > 0 ? idx - 1 : count - 1 : idx < count - 1 ? idx + 1 : 0;
53068
+ return QUEUE_NAV_ACTIONS[nextIdx];
53069
+ }
53070
+
52153
53071
  // src/tui/worker-fleet.ts
52154
53072
  var STATUS_GLYPH = {
52155
53073
  queued: "\u25CB",
@@ -53528,6 +54446,7 @@ async function launchTuiAgentShell(opts) {
53528
54446
  ...opts.versionCheck !== undefined ? { versionCheck: opts.versionCheck } : {}
53529
54447
  });
53530
54448
  mountedChrome = chrome;
54449
+ chrome.input.focus();
53531
54450
  const transcript = chrome.transcript;
53532
54451
  const input2 = chrome.input;
53533
54452
  let busyPhase = "waiting for model";
@@ -53551,6 +54470,51 @@ async function launchTuiAgentShell(opts) {
53551
54470
  const sbModelV = new otui.TextRenderable(r, { id: "sb-model-v", content: otui.t`${otui.dim(`${sel.provider}/${sel.model}`)}` });
53552
54471
  sidebar.add(sbModelV);
53553
54472
  mountCwdPanel(otui, r, sidebar, opts.session?.cwd ?? process.cwd());
54473
+ sidebar.add(new otui.TextRenderable(r, { id: "sb-workspace-k", content: otui.t`${otui.dim("Workspace")}`, marginTop: 1 }));
54474
+ const sbWorkspaceV = new otui.TextRenderable(r, {
54475
+ id: "sb-workspace-v",
54476
+ content: otui.t`${otui.dim("\u2014")}`,
54477
+ onMouseDown: () => {
54478
+ showWorkspace();
54479
+ }
54480
+ });
54481
+ sidebar.add(sbWorkspaceV);
54482
+ let currentWorkspace;
54483
+ let currentSlates = [];
54484
+ const refreshWorkspaceSidebar = async () => {
54485
+ const dir = slateSession?.dir;
54486
+ const workspaceId = dir !== undefined ? (await readSlate(dir).catch(() => {
54487
+ return;
54488
+ }))?.workspaceId : undefined;
54489
+ if (workspaceId === undefined) {
54490
+ currentWorkspace = undefined;
54491
+ currentSlates = [];
54492
+ sbWorkspaceV.content = otui.t`${otui.dim("\u2014")}`;
54493
+ return;
54494
+ }
54495
+ const cwd = opts.session?.cwd ?? process.cwd();
54496
+ const [workspace, slates] = await Promise.all([
54497
+ loadInspectorWorkspace(cwd, workspaceId),
54498
+ loadInspectorSlates(cwd, workspaceId)
54499
+ ]);
54500
+ currentWorkspace = workspace;
54501
+ currentSlates = slates;
54502
+ sbWorkspaceV.content = workspace === undefined ? otui.t`${otui.dim("\u2014")}` : otui.t`${otui.dim(`${shortenCwd(workspace.title, SIDEBAR_TEXT_WIDTH)} \xB7 ${workspace.status} \xB7 ${slates.length} slate${slates.length === 1 ? "" : "s"}`)}`;
54503
+ };
54504
+ sidebar.add(new otui.TextRenderable(r, { id: "sb-review-k", content: otui.t`${otui.dim("Review")}`, marginTop: 1 }));
54505
+ const sbReviewV = new otui.TextRenderable(r, {
54506
+ id: "sb-review-v",
54507
+ content: otui.t`${otui.dim("\u2014")}`,
54508
+ onMouseDown: () => {
54509
+ showReview();
54510
+ }
54511
+ });
54512
+ sidebar.add(sbReviewV);
54513
+ const refreshReviewSidebar = async () => {
54514
+ const cwd = opts.session?.cwd ?? process.cwd();
54515
+ const count = catchUpItems(await loadInspectorCatchUp(cwd)).length;
54516
+ sbReviewV.content = count === 0 ? otui.t`${otui.dim("\u2014 nothing to review")}` : otui.t`${otui.yellow(`${count} item${count === 1 ? "" : "s"} need review`)}`;
54517
+ };
53554
54518
  sidebar.add(new otui.TextRenderable(r, { id: "sb-ctx-k", content: otui.t`${otui.dim("Context")}`, marginTop: 1 }));
53555
54519
  const sbContext = new otui.TextRenderable(r, { id: "sb-ctx-v", content: otui.t`${otui.dim("0 tokens")}` });
53556
54520
  sidebar.add(sbContext);
@@ -53990,6 +54954,8 @@ async function launchTuiAgentShell(opts) {
53990
54954
  }));
53991
54955
  }
53992
54956
  slateSession = { dir: liveSession.dir, cwd: sessionCwd, opened: false };
54957
+ refreshWorkspaceSidebar();
54958
+ refreshReviewSidebar();
53993
54959
  const paintSessionHeader = () => {
53994
54960
  const label = `${currentSel.provider}/${currentSel.model}`;
53995
54961
  const sid = shortSessionId(liveSession.summary.id);
@@ -54130,6 +55096,50 @@ Staying in the current session.
54130
55096
  });
54131
55097
  })();
54132
55098
  };
55099
+ const showWorkspace = () => {
55100
+ (async () => {
55101
+ const dir = slateSession?.dir;
55102
+ const workspaceId = dir !== undefined ? (await readSlate(dir).catch(() => {
55103
+ return;
55104
+ }))?.workspaceId : undefined;
55105
+ if (workspaceId === undefined) {
55106
+ io.onSystem?.(`No workspace bound to this session yet \u2014 the agent binds one automatically on its first real task.
55107
+ `);
55108
+ return;
55109
+ }
55110
+ const cwd = inspectorCwd();
55111
+ const [workspace, slates] = await Promise.all([
55112
+ loadInspectorWorkspace(cwd, workspaceId),
55113
+ loadInspectorSlates(cwd, workspaceId)
55114
+ ]);
55115
+ if (workspace === undefined) {
55116
+ io.onSystem?.(`Workspace ${workspaceId} could not be loaded.
55117
+ `);
55118
+ return;
55119
+ }
55120
+ openWorkspace(otui, chrome, {
55121
+ workspace,
55122
+ slates,
55123
+ renderer: r,
55124
+ ...inspectorKeys
55125
+ });
55126
+ })();
55127
+ };
55128
+ const showReview = () => {
55129
+ (async () => {
55130
+ const cwd = inspectorCwd();
55131
+ const items = catchUpItems(await loadInspectorCatchUp(cwd));
55132
+ openReview(otui, chrome, {
55133
+ items,
55134
+ acceptProposal: (item) => acceptProposalViaShell(makeCommandRunner(cwd), item.workspaceId, item.proposalId),
55135
+ onAccepted: () => {
55136
+ refreshReviewSidebar();
55137
+ },
55138
+ renderer: r,
55139
+ ...inspectorKeys
55140
+ });
55141
+ })();
55142
+ };
54133
55143
  const updateModelLabels = () => {
54134
55144
  paintSessionHeader();
54135
55145
  const label = `${currentSel.provider}/${currentSel.model}`;
@@ -54168,26 +55178,110 @@ Staying in the current session.
54168
55178
  detail: `queued \xD7${sideQueue.length}`
54169
55179
  });
54170
55180
  };
55181
+ let queueNavActive = false;
55182
+ let selectedQueueIndex = 0;
55183
+ let selectedQueueAction = "force";
55184
+ chrome.addOverlaySource(() => queueNavActive);
54171
55185
  let mainQueueBlocks = [];
55186
+ const mainQueueButton = (label, id, color, onMouseDown) => {
55187
+ const box = new otui.BoxRenderable(r, {
55188
+ id,
55189
+ flexShrink: 0,
55190
+ marginLeft: 1,
55191
+ paddingLeft: 1,
55192
+ paddingRight: 1,
55193
+ onMouseDown: (event) => {
55194
+ event.stopPropagation();
55195
+ onMouseDown();
55196
+ }
55197
+ });
55198
+ const text = new otui.TextRenderable(r, { id: `${id}-t`, content: `[${label}]` });
55199
+ text.fg = color;
55200
+ box.add(text);
55201
+ const setActive = (active) => {
55202
+ box.backgroundColor = active ? getTheme().highlight : undefined;
55203
+ text.content = active ? otui.t`${otui.bold(`[${label}]`)}` : `[${label}]`;
55204
+ text.fg = color;
55205
+ };
55206
+ return { box, setActive };
55207
+ };
55208
+ const applyQueueNavHighlight = () => {
55209
+ for (let i = 0;i < mainQueueBlocks.length; i++) {
55210
+ const entry = mainQueueBlocks[i];
55211
+ if (entry === undefined)
55212
+ continue;
55213
+ const rowActive = queueNavActive && i === selectedQueueIndex;
55214
+ entry.setRowActive(rowActive);
55215
+ entry.buttons.force.setActive(rowActive && selectedQueueAction === "force");
55216
+ entry.buttons.edit.setActive(rowActive && selectedQueueAction === "edit");
55217
+ entry.buttons.delete.setActive(rowActive && selectedQueueAction === "delete");
55218
+ }
55219
+ };
55220
+ const exitQueueNav = () => {
55221
+ if (!queueNavActive)
55222
+ return;
55223
+ queueNavActive = false;
55224
+ applyQueueNavHighlight();
55225
+ };
55226
+ const enterQueueNav = () => {
55227
+ if (mainQueue.length === 0)
55228
+ return;
55229
+ queueNavActive = true;
55230
+ selectedQueueIndex = clampQueueNavIndex(selectedQueueIndex, mainQueue.length);
55231
+ applyQueueNavHighlight();
55232
+ };
55233
+ chrome.queueDock.onMouseDown = () => {
55234
+ if (chrome.overlayActive())
55235
+ return;
55236
+ enterQueueNav();
55237
+ };
54172
55238
  const paintMainQueue = () => {
54173
55239
  for (const entry of mainQueueBlocks) {
54174
55240
  try {
54175
- transcript.remove(entry.box);
55241
+ chrome.queueDock.remove(entry.box);
54176
55242
  } catch {}
54177
55243
  }
54178
55244
  mainQueueBlocks = [];
55245
+ selectedQueueIndex = clampQueueNavIndex(selectedQueueIndex, mainQueue.length);
55246
+ if (mainQueue.length === 0) {
55247
+ exitQueueNav();
55248
+ }
55249
+ const theme = getTheme();
54179
55250
  for (let i = 0;i < mainQueue.length; i++) {
54180
55251
  const item = mainQueue[i];
54181
55252
  if (item === undefined)
54182
55253
  continue;
54183
- const box = appendUserEcho(otui, r, transcript, {
55254
+ const index = i;
55255
+ const row = new otui.BoxRenderable(r, {
54184
55256
  id: `mq-${item.id}`,
54185
- line: `${formatMainQueueMarker(i, mainQueue.length)} ${item.displayQuestion}`,
54186
- borderColor: getTheme().highlight,
54187
- marginTop: 0
55257
+ width: "100%",
55258
+ flexDirection: "row"
55259
+ });
55260
+ const label = new otui.TextRenderable(r, {
55261
+ id: `mq-${item.id}-t`,
55262
+ flexGrow: 1,
55263
+ minWidth: 0,
55264
+ content: otui.t`${otui.dim(`${formatMainQueueMarker(index, mainQueue.length)} ${item.displayQuestion}`)}`
55265
+ });
55266
+ row.add(label);
55267
+ const force = mainQueueButton("Force", `mq-force-${item.id}`, theme.focus, () => forceMainQueue(index));
55268
+ const edit = mainQueueButton("Edit", `mq-edit-${item.id}`, theme.text, () => editMainQueue(index));
55269
+ const del = mainQueueButton("Delete", `mq-del-${item.id}`, theme.error, () => removeMainQueue(index));
55270
+ row.add(force.box);
55271
+ row.add(edit.box);
55272
+ row.add(del.box);
55273
+ chrome.queueDock.add(row);
55274
+ mainQueueBlocks.push({
55275
+ id: item.id,
55276
+ box: row,
55277
+ setRowActive: (active) => {
55278
+ row.backgroundColor = active ? theme.highlight : undefined;
55279
+ },
55280
+ buttons: { force, edit, delete: del }
54188
55281
  });
54189
- mainQueueBlocks.push({ id: item.id, box });
54190
55282
  }
55283
+ chrome.queueDock.visible = mainQueue.length > 0;
55284
+ applyQueueNavHighlight();
54191
55285
  if (mainQueue.length > 0) {
54192
55286
  fleet.upsert({ id: "agent:queue", label: "mainQ", status: "queued", detail: `queued \xD7${mainQueue.length}` });
54193
55287
  } else {
@@ -54226,6 +55320,63 @@ Staying in the current session.
54226
55320
  }
54227
55321
  runLine(item.question);
54228
55322
  };
55323
+ const handleQueueNavKey = (key) => {
55324
+ if (!queueNavActive) {
55325
+ if (key.ctrl && key.name === "q" && mainQueue.length > 0 && !chrome.overlayActive() && !chrome.menuActive() && !nav.active()) {
55326
+ enterQueueNav();
55327
+ key.preventDefault();
55328
+ key.stopPropagation();
55329
+ }
55330
+ return;
55331
+ }
55332
+ if (key.name === "escape") {
55333
+ exitQueueNav();
55334
+ key.preventDefault();
55335
+ key.stopPropagation();
55336
+ return;
55337
+ }
55338
+ if (key.name === "up") {
55339
+ selectedQueueIndex = stepQueueNavIndex(selectedQueueIndex, mainQueue.length, "up");
55340
+ applyQueueNavHighlight();
55341
+ key.preventDefault();
55342
+ key.stopPropagation();
55343
+ return;
55344
+ }
55345
+ if (key.name === "down") {
55346
+ selectedQueueIndex = stepQueueNavIndex(selectedQueueIndex, mainQueue.length, "down");
55347
+ applyQueueNavHighlight();
55348
+ key.preventDefault();
55349
+ key.stopPropagation();
55350
+ return;
55351
+ }
55352
+ if (key.name === "left") {
55353
+ selectedQueueAction = stepQueueNavAction(selectedQueueAction, "left");
55354
+ applyQueueNavHighlight();
55355
+ key.preventDefault();
55356
+ key.stopPropagation();
55357
+ return;
55358
+ }
55359
+ if (key.name === "right") {
55360
+ selectedQueueAction = stepQueueNavAction(selectedQueueAction, "right");
55361
+ applyQueueNavHighlight();
55362
+ key.preventDefault();
55363
+ key.stopPropagation();
55364
+ return;
55365
+ }
55366
+ if (key.name === "return" || key.name === "linefeed" || key.name === "kpenter") {
55367
+ const index = selectedQueueIndex;
55368
+ const action = selectedQueueAction;
55369
+ exitQueueNav();
55370
+ if (action === "force")
55371
+ forceMainQueue(index);
55372
+ else if (action === "edit")
55373
+ editMainQueue(index);
55374
+ else
55375
+ removeMainQueue(index);
55376
+ key.preventDefault();
55377
+ key.stopPropagation();
55378
+ }
55379
+ };
54229
55380
  const clearSideWorkerSlot = () => {
54230
55381
  if (sideClearTimeout !== undefined) {
54231
55382
  clearTimeout(sideClearTimeout);
@@ -54507,6 +55658,8 @@ Staying in the current session.
54507
55658
  await closeSlateSession(slateSession, mintTimestampAttemptId);
54508
55659
  startNewSession();
54509
55660
  slateSession = { dir: liveSession.dir, cwd: sessionCwd, opened: false };
55661
+ refreshWorkspaceSidebar();
55662
+ refreshReviewSidebar();
54510
55663
  sessions.clear();
54511
55664
  deps.resetSubagentBudget?.();
54512
55665
  io.onSystem?.(`New session ${shortSessionId(liveSession.summary.id)} (previous kept on disk \xB7 /resume)
@@ -54641,6 +55794,14 @@ Staying in the current session.
54641
55794
  showFlows();
54642
55795
  return;
54643
55796
  }
55797
+ if (isWorkspaceCommand(command.name)) {
55798
+ showWorkspace();
55799
+ return;
55800
+ }
55801
+ if (isReviewCommand(command.name)) {
55802
+ showReview();
55803
+ return;
55804
+ }
54644
55805
  if (command.name === "/copy") {
54645
55806
  const target = newestBlock();
54646
55807
  if (target === undefined || !copyBlock(target.id)) {
@@ -54959,6 +56120,8 @@ ${formatThemeList(getThemeId())}`);
54959
56120
  mainTurnAbortController = undefined;
54960
56121
  const secs = ((Date.now() - startedAt) / 1000).toFixed(1);
54961
56122
  stopBusy();
56123
+ refreshWorkspaceSidebar();
56124
+ refreshReviewSidebar();
54962
56125
  setMainAgent(turnFailed ? "failed" : "done", turnFailed ? "error" : "idle");
54963
56126
  try {
54964
56127
  flushSessionCheckpoint();
@@ -54985,6 +56148,9 @@ ${formatThemeList(getThemeId())}`);
54985
56148
  onKeypress4(r, (key) => {
54986
56149
  nav.handleKey(key);
54987
56150
  });
56151
+ onKeypress4(r, (key) => {
56152
+ handleQueueNavKey(key);
56153
+ });
54988
56154
  chrome.onSubmit((line) => {
54989
56155
  runLine(line);
54990
56156
  });
@@ -56324,7 +57490,7 @@ ${indentBlock(style.dim(context), GUTTER)}`);
56324
57490
  out(`${GUTTER}${style.yellow(hint)}
56325
57491
  `);
56326
57492
  }
56327
- const rememberable = !evaled.destructive && !evaled.credentials;
57493
+ const rememberable = !evaled.destructive && !evaled.credentials && !evaled.sacReviewConfirmation;
56328
57494
  const prompt = rememberable ? "[y/N/A=always] " : "[y/N] ";
56329
57495
  out(`
56330
57496
  ${GUTTER}${style.yellow(`Run: ${evaled.command}`)} ${style.dim(prompt)}`);
@@ -56866,7 +58032,7 @@ async function shellCommand(args2, runtime = {}) {
56866
58032
  modelId: sel.model
56867
58033
  }),
56868
58034
  maxToolCalls: resolveAgentMaxToolCalls(),
56869
- idSeq: () => randomUUID23(),
58035
+ idSeq: () => randomUUID25(),
56870
58036
  ...resetSubagentBudget !== undefined ? { resetSubagentBudget } : {}
56871
58037
  };
56872
58038
  };
@@ -56898,7 +58064,7 @@ async function shellCommand(args2, runtime = {}) {
56898
58064
  makeShellDeps: (sel) => ({
56899
58065
  makeProvider: chatFactory,
56900
58066
  clock: () => new Date().toISOString(),
56901
- idSeq: () => randomUUID23(),
58067
+ idSeq: () => randomUUID25(),
56902
58068
  initial: sel,
56903
58069
  session: {
56904
58070
  cwd,
@@ -56969,7 +58135,7 @@ async function shellCommand(args2, runtime = {}) {
56969
58135
  const deps = {
56970
58136
  makeProvider: baseFactory,
56971
58137
  clock: () => new Date().toISOString(),
56972
- idSeq: () => randomUUID23(),
58138
+ idSeq: () => randomUUID25(),
56973
58139
  initial: baseUrl2 === undefined ? { provider, model } : { provider, model, baseUrl: baseUrl2 },
56974
58140
  selectProviderModel: realSelectProviderModel(baseUrl2)
56975
58141
  };
@@ -57020,7 +58186,7 @@ async function shellCommand(args2, runtime = {}) {
57020
58186
  modelId: model
57021
58187
  }),
57022
58188
  maxToolCalls: resolveAgentMaxToolCalls(),
57023
- idSeq: () => randomUUID23(),
58189
+ idSeq: () => randomUUID25(),
57024
58190
  ...resetSubagentBudget !== undefined ? { resetSubagentBudget } : {}
57025
58191
  };
57026
58192
  let resumeId = flags.resumeId;
@@ -57192,7 +58358,7 @@ Shell:
57192
58358
  init_fs();
57193
58359
  import { readFile as readFile78 } from "fs/promises";
57194
58360
  import { stdin } from "process";
57195
- import path147 from "path";
58361
+ import path148 from "path";
57196
58362
  var MODULES = [
57197
58363
  { name: "gdgraph", flag: "--no-gdgraph", desc: "code graph, symbols, affected context", defaultEnabled: true },
57198
58364
  { name: "gdctx", flag: "--no-gdctx", desc: "token-aware command/read output", defaultEnabled: true },
@@ -57238,8 +58404,8 @@ async function modulesCommand(args2 = []) {
57238
58404
  return;
57239
58405
  }
57240
58406
  const wantsJson = args2.includes("--json") && (sub === undefined || sub === "status" || sub === "list" || sub === "--json");
57241
- const metaprojectRoot = path147.join(process.cwd(), ".metaproject");
57242
- const manifestPath = path147.join(metaprojectRoot, "metaproject.json");
58407
+ const metaprojectRoot = path148.join(process.cwd(), ".metaproject");
58408
+ const manifestPath = path148.join(metaprojectRoot, "metaproject.json");
57243
58409
  if (!await pathExists(manifestPath)) {
57244
58410
  if (wantsJson) {
57245
58411
  console.log(JSON.stringify({ schemaVersion: 1, error: "not-initialized", modules: [] }, null, 2));
@@ -57353,18 +58519,18 @@ function printHelp16() {
57353
58519
  }
57354
58520
 
57355
58521
  // src/commands/serve.ts
57356
- import { randomUUID as randomUUID26 } from "crypto";
58522
+ import { randomUUID as randomUUID28 } from "crypto";
57357
58523
 
57358
58524
  // src/lib/serve-config.ts
57359
58525
  init_config_dir();
57360
58526
  import { existsSync as existsSync27 } from "fs";
57361
- import path148 from "path";
58527
+ import path149 from "path";
57362
58528
  var SERVE_CONFIG_SCHEMA_VERSION = "1.0.0";
57363
58529
  var DEFAULT_SERVE_BIND_ADDRESS = "127.0.0.1";
57364
58530
  var DEFAULT_SERVE_PORT = 7377;
57365
58531
  var DEFAULT_SERVE_PROFILE = "remote-restricted";
57366
58532
  function serveConfigPath(dir) {
57367
- return path148.join(keryxConfigDir(dir), "serve.json");
58533
+ return path149.join(keryxConfigDir(dir), "serve.json");
57368
58534
  }
57369
58535
  function parseIpv4(value) {
57370
58536
  const parts = value.split(".");
@@ -57680,7 +58846,7 @@ function saveServeConfig(config, dir, onWarn) {
57680
58846
 
57681
58847
  // src/lib/serve-credential.ts
57682
58848
  init_config_dir();
57683
- import { createHash as createHash33, randomBytes as randomBytes3, randomUUID as randomUUID24 } from "crypto";
58849
+ import { createHash as createHash33, randomBytes as randomBytes3, randomUUID as randomUUID26 } from "crypto";
57684
58850
  import {
57685
58851
  chmodSync as chmodSync4,
57686
58852
  closeSync as closeSync3,
@@ -57692,9 +58858,9 @@ import {
57692
58858
  unlinkSync as unlinkSync3,
57693
58859
  writeFileSync as writeFileSync8
57694
58860
  } from "fs";
57695
- import path149 from "path";
58861
+ import path150 from "path";
57696
58862
  function serveCredentialPath(dir) {
57697
- return path149.join(keryxConfigDir(dir), "serve-credentials.json");
58863
+ return path150.join(keryxConfigDir(dir), "serve-credentials.json");
57698
58864
  }
57699
58865
  function constantTimeEqual(a, b) {
57700
58866
  const width = Math.max(a.length, b.length);
@@ -57764,7 +58930,7 @@ function readServeCredential(dir) {
57764
58930
  }
57765
58931
  function writeStore(store, dir) {
57766
58932
  const file = serveCredentialPath(dir);
57767
- const temp = `${file}.${randomUUID24()}.tmp`;
58933
+ const temp = `${file}.${randomUUID26()}.tmp`;
57768
58934
  try {
57769
58935
  ensureKeryxConfigDir(dir);
57770
58936
  const handle = openSync3(temp, "wx", 384);
@@ -57804,7 +58970,7 @@ function mintRecord(now) {
57804
58970
  const salt = randomBytes3(32).toString("hex");
57805
58971
  return {
57806
58972
  token,
57807
- record: { id: randomUUID24(), algorithm: "sha256", salt, hash: hashToken(salt, token), createdAt: now }
58973
+ record: { id: randomUUID26(), algorithm: "sha256", salt, hash: hashToken(salt, token), createdAt: now }
57808
58974
  };
57809
58975
  }
57810
58976
  function issueServeToken(dir, now = () => new Date().toISOString(), onWaiting) {
@@ -57928,22 +59094,22 @@ class AuthFailureThrottle {
57928
59094
  init_config_dir();
57929
59095
  import { createHash as createHash34 } from "crypto";
57930
59096
  import { existsSync as existsSync29, readdirSync as readdirSync2, rmSync as rmSync2 } from "fs";
57931
- import path150 from "path";
59097
+ import path151 from "path";
57932
59098
  var MAX_TURN_EVENTS = 1e4;
57933
59099
  function turnsRoot(dir) {
57934
- return path150.join(keryxConfigDir(dir), "turns");
59100
+ return path151.join(keryxConfigDir(dir), "turns");
57935
59101
  }
57936
59102
  function turnDir(turnId, dir) {
57937
- return path150.join(turnsRoot(dir), turnId);
59103
+ return path151.join(turnsRoot(dir), turnId);
57938
59104
  }
57939
59105
  function keyPath(project, idempotencyKey, dir) {
57940
59106
  const projectBytes = Buffer.byteLength(project, "utf8");
57941
59107
  const digest2 = createHash34("sha256").update(`${projectBytes}:${project}\x00${idempotencyKey}`, "utf8").digest("hex");
57942
- return path150.join(turnsRoot(dir), "keys", `${digest2}.json`);
59108
+ return path151.join(turnsRoot(dir), "keys", `${digest2}.json`);
57943
59109
  }
57944
59110
  function legacyKeyPath(idempotencyKey, dir) {
57945
59111
  const digest2 = createHash34("sha256").update(idempotencyKey, "utf8").digest("hex");
57946
- return path150.join(turnsRoot(dir), "keys", `${digest2}.json`);
59112
+ return path151.join(turnsRoot(dir), "keys", `${digest2}.json`);
57947
59113
  }
57948
59114
  function adoptLegacyClaim(project, idempotencyKey, dir) {
57949
59115
  const legacy = legacyKeyPath(idempotencyKey, dir);
@@ -58007,7 +59173,7 @@ function ensureTurnDir(turnId, dir) {
58007
59173
  }
58008
59174
  function createTurnRecord(record, dir) {
58009
59175
  ensureTurnDir(record.turnId, dir);
58010
- writeOwnerOnlyFile(path150.join(turnDir(record.turnId, dir), "turn.json"), `${JSON.stringify(record, null, 2)}
59176
+ writeOwnerOnlyFile(path151.join(turnDir(record.turnId, dir), "turn.json"), `${JSON.stringify(record, null, 2)}
58011
59177
  `);
58012
59178
  }
58013
59179
  function appendTurnEvent(event, dir, opts) {
@@ -58016,12 +59182,12 @@ function appendTurnEvent(event, dir, opts) {
58016
59182
  }
58017
59183
  const line = JSON.stringify(event);
58018
59184
  try {
58019
- appendOwnerOnlyLine(path150.join(turnDir(event.turnId, dir), "events.jsonl"), line);
59185
+ appendOwnerOnlyLine(path151.join(turnDir(event.turnId, dir), "events.jsonl"), line);
58020
59186
  } catch (error2) {
58021
59187
  if (error2?.code !== "ENOENT") {
58022
59188
  throw error2;
58023
59189
  }
58024
- appendOwnerOnlyLine(path150.join(ensureTurnDir(event.turnId, dir), "events.jsonl"), line);
59190
+ appendOwnerOnlyLine(path151.join(ensureTurnDir(event.turnId, dir), "events.jsonl"), line);
58025
59191
  }
58026
59192
  return true;
58027
59193
  }
@@ -58029,7 +59195,7 @@ function readTurnEvents(turnId, after = -1, dir) {
58029
59195
  if (!isTurnId(turnId)) {
58030
59196
  return { ok: false, reason: "not-a-turn-id" };
58031
59197
  }
58032
- const read = readTurnFile(path150.join(turnDir(turnId, dir), "events.jsonl"));
59198
+ const read = readTurnFile(path151.join(turnDir(turnId, dir), "events.jsonl"));
58033
59199
  if (!read.ok) {
58034
59200
  if (isDefiniteAbsence2(read.reason)) {
58035
59201
  return { ok: true, value: [] };
@@ -58057,7 +59223,7 @@ function readTurnRecord(turnId, dir) {
58057
59223
  if (!isTurnId(turnId)) {
58058
59224
  return { ok: false, reason: "not-a-turn-id" };
58059
59225
  }
58060
- const read = readTurnFile(path150.join(turnDir(turnId, dir), "turn.json"));
59226
+ const read = readTurnFile(path151.join(turnDir(turnId, dir), "turn.json"));
58061
59227
  if (!read.ok) {
58062
59228
  return { ok: false, reason: read.reason };
58063
59229
  }
@@ -58076,7 +59242,7 @@ function finishTurn(turnId, result, dir) {
58076
59242
  if (!record.ok) {
58077
59243
  return false;
58078
59244
  }
58079
- writeOwnerOnlyFile(path150.join(turnDir(turnId, dir), "turn.json"), `${JSON.stringify({ ...record.value, result }, null, 2)}
59245
+ writeOwnerOnlyFile(path151.join(turnDir(turnId, dir), "turn.json"), `${JSON.stringify({ ...record.value, result }, null, 2)}
58080
59246
  `);
58081
59247
  return true;
58082
59248
  }
@@ -58121,8 +59287,8 @@ function releaseIdempotencyKey(project, idempotencyKey, turnId, dir) {
58121
59287
  }
58122
59288
 
58123
59289
  // src/lib/serve-turn.ts
58124
- import { randomUUID as randomUUID25 } from "crypto";
58125
- import path151 from "path";
59290
+ import { randomUUID as randomUUID27 } from "crypto";
59291
+ import path152 from "path";
58126
59292
  init_service();
58127
59293
  var REMOTE_ORIGIN = "remote:http";
58128
59294
  var MAX_PROMPT_CHARS = 32000;
@@ -58191,9 +59357,9 @@ function isUuid(value) {
58191
59357
  return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(value);
58192
59358
  }
58193
59359
  function resolveProject(declared, dir) {
58194
- const wanted = path151.resolve(declared);
59360
+ const wanted = path152.resolve(declared);
58195
59361
  for (const entry of listProjects(dir, () => {})) {
58196
- if (path151.resolve(entry.path) === wanted) {
59362
+ if (path152.resolve(entry.path) === wanted) {
58197
59363
  return { ok: true, project: entry.path };
58198
59364
  }
58199
59365
  }
@@ -58240,7 +59406,7 @@ async function redactOut(security, text) {
58240
59406
  async function runRemoteTurn(input2) {
58241
59407
  const scanRoot = input2.scanRoot;
58242
59408
  const security = createSecurityService(scanRoot);
58243
- const newId = input2.newId ?? (() => randomUUID25());
59409
+ const newId = input2.newId ?? (() => randomUUID27());
58244
59410
  const clock = input2.clock ?? (() => new Date().toISOString());
58245
59411
  const turnId = input2.turnId ?? newId();
58246
59412
  const sessionId = input2.request.sessionId ?? newId();
@@ -58383,7 +59549,7 @@ function outcomeOf(status, gate, unresolvedBlockerIds) {
58383
59549
  }
58384
59550
  function createSubmitTurn(deps) {
58385
59551
  return async (request, project) => {
58386
- const turnId = (deps.newId ?? (() => randomUUID25()))();
59552
+ const turnId = (deps.newId ?? (() => randomUUID27()))();
58387
59553
  const scanned = await scanPrompt(deps.dir, request.prompt);
58388
59554
  if (scanned.rejected) {
58389
59555
  return { kind: "rejected" };
@@ -59070,7 +60236,7 @@ function runConfig(args2) {
59070
60236
  return;
59071
60237
  }
59072
60238
  const credential2 = readServeCredential();
59073
- const credentialId = credential2.status === "ok" ? credential2.record.id : randomUUID26();
60239
+ const credentialId = credential2.status === "ok" ? credential2.record.id : randomUUID28();
59074
60240
  const config = defaultServeConfig(credentialId, {
59075
60241
  address: parsed.parsed.values.get("--bind") ?? DEFAULT_SERVE_BIND_ADDRESS,
59076
60242
  port: port ?? DEFAULT_SERVE_PORT,
@@ -59226,9 +60392,9 @@ function printHelp17() {
59226
60392
 
59227
60393
  // src/commands/update.ts
59228
60394
  import { spawn as spawn5 } from "child_process";
59229
- import { chmod as chmod4, mkdir as mkdir54, readFile as readFile79, readdir as readdir24, writeFile as writeFile48 } from "fs/promises";
60395
+ import { chmod as chmod4, mkdir as mkdir54, readFile as readFile79, readdir as readdir25, writeFile as writeFile48 } from "fs/promises";
59230
60396
  import { access as access4, constants as constants2, existsSync as existsSync30 } from "fs";
59231
- import path152 from "path";
60397
+ import path153 from "path";
59232
60398
  import { fileURLToPath as fileURLToPath7 } from "url";
59233
60399
  init_config();
59234
60400
  init_config2();
@@ -59244,8 +60410,8 @@ async function updateCommand(args2 = []) {
59244
60410
  return;
59245
60411
  }
59246
60412
  const projectRoot = process.cwd();
59247
- const metaprojectRoot = path152.join(projectRoot, ".metaproject");
59248
- banner("keryx update", `Refreshing the .metaproject workspace in ${path152.basename(projectRoot)}/`);
60413
+ const metaprojectRoot = path153.join(projectRoot, ".metaproject");
60414
+ banner("keryx update", `Refreshing the .metaproject workspace in ${path153.basename(projectRoot)}/`);
59249
60415
  if (!await pathExists(metaprojectRoot)) {
59250
60416
  console.log(` ${style.red(symbols.cross)} Metaproject is not initialized.`);
59251
60417
  console.log(` ${style.cyan(symbols.arrow)} Run ${style.cyan("keryx init")} first.`);
@@ -59291,12 +60457,12 @@ async function updateCommand(args2 = []) {
59291
60457
  nextSteps(steps);
59292
60458
  }
59293
60459
  async function refreshServiceFiles(projectRoot, options) {
59294
- const metaprojectRoot = path152.join(projectRoot, ".metaproject");
60460
+ const metaprojectRoot = path153.join(projectRoot, ".metaproject");
59295
60461
  const manifestState = await readManifest5(metaprojectRoot);
59296
60462
  const manifest = manifestState.manifest;
59297
60463
  const recoveredManifest = !manifestState.exists || !manifestState.valid;
59298
60464
  if (manifestState.migrated) {
59299
- await writeFile48(path152.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
60465
+ await writeFile48(path153.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
59300
60466
  `, "utf8");
59301
60467
  }
59302
60468
  const enableGdgraph = moduleEnabled2(manifest, "gdgraph");
@@ -59333,11 +60499,11 @@ async function refreshServiceFiles(projectRoot, options) {
59333
60499
  enableSecurity,
59334
60500
  enableSac
59335
60501
  });
59336
- await writeTextIfChanged4(path152.join(metaprojectRoot, "core", "README.md"), renderMetaprojectCoreReadme());
59337
- await writeTextIfChanged4(path152.join(metaprojectRoot, "hooks", "README.md"), renderHooksReadme());
59338
- await writeTextIfChanged4(path152.join(metaprojectRoot, "rules", "README.md"), renderProjectRulesReadme());
59339
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "project-rules", "README.md"), renderProjectRulesSkillReadme({ sources: ruleSources }));
59340
- await writeTextIfChanged4(path152.join(metaprojectRoot, "index.md"), renderIndexMarkdown({
60502
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "core", "README.md"), renderMetaprojectCoreReadme());
60503
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "hooks", "README.md"), renderHooksReadme());
60504
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "rules", "README.md"), renderProjectRulesReadme());
60505
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "project-rules", "README.md"), renderProjectRulesSkillReadme({ sources: ruleSources }));
60506
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "index.md"), renderIndexMarkdown({
59341
60507
  enableGdgraph,
59342
60508
  enableGdctx,
59343
60509
  enableGdwiki,
@@ -59350,7 +60516,7 @@ async function refreshServiceFiles(projectRoot, options) {
59350
60516
  ruleSources,
59351
60517
  hasDistilledEntrypoints: await hasDistilledEntrypoints(metaprojectRoot)
59352
60518
  }));
59353
- await writeTextIfChanged4(path152.join(metaprojectRoot, "keryx-dashboard.html"), renderMetaprojectDashboardHtml({
60519
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "keryx-dashboard.html"), renderMetaprojectDashboardHtml({
59354
60520
  enableGdgraph,
59355
60521
  enableGdctx,
59356
60522
  enableGdwiki,
@@ -59362,7 +60528,7 @@ async function refreshServiceFiles(projectRoot, options) {
59362
60528
  enableSecurity,
59363
60529
  data: dashboardData
59364
60530
  }));
59365
- await writeTextIfMissing4(path152.join(metaprojectRoot, "README.md"), renderMetaprojectReadme({
60531
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "README.md"), renderMetaprojectReadme({
59366
60532
  enableGdgraph,
59367
60533
  enableGdctx,
59368
60534
  enableGdwiki,
@@ -59375,31 +60541,31 @@ async function refreshServiceFiles(projectRoot, options) {
59375
60541
  }));
59376
60542
  if (enableGdgraph) {
59377
60543
  await installGdgraphCoreScripts2(metaprojectRoot);
59378
- await writeTextIfChanged4(path152.join(metaprojectRoot, "modules", "gdgraph.md"), renderGdgraphManifest());
59379
- await writeTextIfChanged4(path152.join(metaprojectRoot, "core", "gdgraph", "README.md"), renderGdgraphCoreReadme());
59380
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "gdgraph", "SKILL.md"), renderGdgraphSkillReadme());
60544
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "modules", "gdgraph.md"), renderGdgraphManifest());
60545
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "core", "gdgraph", "README.md"), renderGdgraphCoreReadme());
60546
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "gdgraph", "SKILL.md"), renderGdgraphSkillReadme());
59381
60547
  await seedAssetsLock(metaprojectRoot);
59382
60548
  if (manifest.modules?.gdgraph?.hooks?.gitPostCommit) {
59383
60549
  await installManagedHook2(projectRoot, "post-commit", "gdgraph-post-commit", renderGdgraphPostCommitHook());
59384
60550
  }
59385
60551
  }
59386
60552
  if (enableGdctx) {
59387
- await writeTextIfMissing4(path152.join(metaprojectRoot, "gdctx.config.json"), renderGdctxConfig());
59388
- await writeTextIfChanged4(path152.join(metaprojectRoot, "modules", "gdctx.md"), renderGdctxManifest());
59389
- await writeTextIfChanged4(path152.join(metaprojectRoot, "core", "gdctx", "README.md"), renderGdctxCoreReadme());
59390
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "gdctx", "SKILL.md"), renderGdctxSkillReadme());
60553
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "gdctx.config.json"), renderGdctxConfig());
60554
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "modules", "gdctx.md"), renderGdctxManifest());
60555
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "core", "gdctx", "README.md"), renderGdctxCoreReadme());
60556
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "gdctx", "SKILL.md"), renderGdctxSkillReadme());
59391
60557
  }
59392
60558
  if (enableGdwiki) {
59393
- await writeTextIfMissing4(path152.join(metaprojectRoot, "wiki", "templates", "page.md"), renderWikiPageTemplate());
59394
- await writeTextIfChanged4(path152.join(metaprojectRoot, "modules", "gdwiki.md"), renderGdwikiManifest());
59395
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "gdwiki", "SKILL.md"), renderGdwikiSkillReadme());
60559
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "wiki", "templates", "page.md"), renderWikiPageTemplate());
60560
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "modules", "gdwiki.md"), renderGdwikiManifest());
60561
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "gdwiki", "SKILL.md"), renderGdwikiSkillReadme());
59396
60562
  if (manifest.modules?.gdgraph?.hooks?.gitPostCommit) {
59397
60563
  await installManagedHook2(projectRoot, "post-commit", "gdwiki-post-commit", renderGdwikiPostCommitHook());
59398
60564
  }
59399
60565
  }
59400
60566
  if (enableSac) {
59401
- await writeTextIfMissing4(path152.join(metaprojectRoot, "modules", "sac.md"), renderSacManifest());
59402
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "sac", "SKILL.md"), renderSacSkillReadme());
60567
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "modules", "sac.md"), renderSacManifest());
60568
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "sac", "SKILL.md"), renderSacSkillReadme());
59403
60569
  }
59404
60570
  if (enableGdskills) {
59405
60571
  await installGdskills(metaprojectRoot, gdskillsProfile, { createDataDirs: false });
@@ -59408,25 +60574,25 @@ async function refreshServiceFiles(projectRoot, options) {
59408
60574
  }
59409
60575
  }
59410
60576
  if (enableHealth) {
59411
- await writeTextIfMissing4(path152.join(metaprojectRoot, "health.config.json"), renderHealthConfig());
59412
- await writeTextIfChanged4(path152.join(metaprojectRoot, "modules", "health.md"), renderHealthManifest());
59413
- await writeTextIfChanged4(path152.join(metaprojectRoot, "core", "health", "README.md"), renderHealthCoreReadme());
59414
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "health", "SKILL.md"), renderHealthSkillReadme());
60577
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "health.config.json"), renderHealthConfig());
60578
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "modules", "health.md"), renderHealthManifest());
60579
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "core", "health", "README.md"), renderHealthCoreReadme());
60580
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "health", "SKILL.md"), renderHealthSkillReadme());
59415
60581
  if (manifest.modules?.health?.hooks?.gitPostCommit) {
59416
60582
  await installManagedHook2(projectRoot, "post-commit", "health-post-commit", renderHealthPostCommitHook());
59417
60583
  }
59418
60584
  }
59419
60585
  if (enableTesting) {
59420
- await writeTextIfMissing4(path152.join(metaprojectRoot, "testing.config.json"), renderTestingConfig({
60586
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "testing.config.json"), renderTestingConfig({
59421
60587
  postCommitRefresh: Boolean(manifest.modules?.testing?.hooks?.gitPostCommit),
59422
60588
  prePushGate: Boolean(manifest.modules?.testing?.hooks?.prePush)
59423
60589
  }));
59424
- await writeTextIfChanged4(path152.join(metaprojectRoot, "modules", "testing.md"), renderTestingManifest());
59425
- await writeTextIfChanged4(path152.join(metaprojectRoot, "core", "testing", "README.md"), renderTestingCoreReadme());
59426
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "testing", "SKILL.md"), renderTestingSkillReadme());
60590
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "modules", "testing.md"), renderTestingManifest());
60591
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "core", "testing", "README.md"), renderTestingCoreReadme());
60592
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "testing", "SKILL.md"), renderTestingSkillReadme());
59427
60593
  if (enableGdwiki) {
59428
- await writeTextIfMissing4(path152.join(metaprojectRoot, "wiki", "testing", "README.md"), renderTestingWikiReadme());
59429
- await writeTextIfMissing4(path152.join(metaprojectRoot, "wiki", "testing", "conventions.md"), renderTestingWikiConventions());
60594
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "wiki", "testing", "README.md"), renderTestingWikiReadme());
60595
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "wiki", "testing", "conventions.md"), renderTestingWikiConventions());
59430
60596
  }
59431
60597
  if (manifest.modules?.testing?.hooks?.gitPostCommit) {
59432
60598
  await installManagedHook2(projectRoot, "post-commit", "testing-post-commit", renderTestingPostCommitHook());
@@ -59439,24 +60605,24 @@ async function refreshServiceFiles(projectRoot, options) {
59439
60605
  await installManagedHook2(projectRoot, "post-commit", "metaproject-dashboard-post-commit", renderMetaprojectDashboardPostCommitHook());
59440
60606
  }
59441
60607
  if (enableMemory) {
59442
- await writeTextIfMissing4(path152.join(metaprojectRoot, "memory.config.json"), renderMemoryConfig());
59443
- await writeTextIfMissing4(path152.join(metaprojectRoot, "memory", "templates", "entry.md"), renderMemoryEntryTemplate());
59444
- await writeTextIfChanged4(path152.join(metaprojectRoot, "modules", "memory.md"), renderMemoryManifest());
59445
- await writeTextIfChanged4(path152.join(metaprojectRoot, "core", "memory", "README.md"), renderMemoryCoreReadme());
59446
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "memory", "SKILL.md"), renderMemorySkillReadme());
60608
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "memory.config.json"), renderMemoryConfig());
60609
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "memory", "templates", "entry.md"), renderMemoryEntryTemplate());
60610
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "modules", "memory.md"), renderMemoryManifest());
60611
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "core", "memory", "README.md"), renderMemoryCoreReadme());
60612
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "memory", "SKILL.md"), renderMemorySkillReadme());
59447
60613
  }
59448
60614
  if (enableTasks) {
59449
- await writeTextIfChanged4(path152.join(metaprojectRoot, "flows", "README.md"), renderFlowsReadme());
59450
- await writeTextIfChanged4(path152.join(metaprojectRoot, "modules", "tasks.md"), renderTasksManifest());
59451
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "flow", "SKILL.md"), renderFlowSkillRouter());
59452
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "flow", "init.md"), renderFlowInitSkill());
59453
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "flow", "manage.md"), renderFlowManageSkill());
59454
- await writeTextIfChanged4(path152.join(metaprojectRoot, "skills", "flow", "complete.md"), renderFlowCompleteSkill());
60615
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "flows", "README.md"), renderFlowsReadme());
60616
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "modules", "tasks.md"), renderTasksManifest());
60617
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "flow", "SKILL.md"), renderFlowSkillRouter());
60618
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "flow", "init.md"), renderFlowInitSkill());
60619
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "flow", "manage.md"), renderFlowManageSkill());
60620
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "skills", "flow", "complete.md"), renderFlowCompleteSkill());
59455
60621
  }
59456
60622
  if (enableSecurity) {
59457
- await writeTextIfMissing4(path152.join(metaprojectRoot, "security.config.json"), renderSecurityConfig());
59458
- await writeTextIfChanged4(path152.join(metaprojectRoot, "modules", "security.md"), renderSecurityManifest());
59459
- await writeTextIfChanged4(path152.join(metaprojectRoot, "core", "security", "README.md"), renderSecurityCoreReadme());
60623
+ await writeTextIfMissing4(path153.join(metaprojectRoot, "security.config.json"), renderSecurityConfig());
60624
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "modules", "security.md"), renderSecurityManifest());
60625
+ await writeTextIfChanged4(path153.join(metaprojectRoot, "core", "security", "README.md"), renderSecurityCoreReadme());
59460
60626
  if (manifest.modules?.security?.hooks?.prePush) {
59461
60627
  await installManagedHook2(projectRoot, "pre-push", "security-pre-push", renderSecurityPrePushHook());
59462
60628
  }
@@ -59507,13 +60673,13 @@ async function refreshServiceFiles(projectRoot, options) {
59507
60673
  };
59508
60674
  }
59509
60675
  async function buildDashboard(projectRoot = process.cwd()) {
59510
- const metaprojectRoot = path152.join(projectRoot, ".metaproject");
60676
+ const metaprojectRoot = path153.join(projectRoot, ".metaproject");
59511
60677
  if (!await pathExists(metaprojectRoot)) {
59512
60678
  throw new Error("Metaproject is not initialized. Run: keryx init");
59513
60679
  }
59514
60680
  const manifest = (await readManifest5(metaprojectRoot)).manifest;
59515
60681
  const data = await collectDashboardData(metaprojectRoot);
59516
- const dashboardPath = path152.join(metaprojectRoot, "keryx-dashboard.html");
60682
+ const dashboardPath = path153.join(metaprojectRoot, "keryx-dashboard.html");
59517
60683
  await writeTextIfChanged4(dashboardPath, renderMetaprojectDashboardHtml({
59518
60684
  enableGdgraph: moduleEnabled2(manifest, "gdgraph"),
59519
60685
  enableGdctx: moduleEnabled2(manifest, "gdctx"),
@@ -59533,7 +60699,7 @@ async function shouldInstallDashboardPostCommitHook(projectRoot, manifest) {
59533
60699
  if (Object.values(modules).some((module) => Boolean(module.hooks?.gitPostCommit))) {
59534
60700
  return true;
59535
60701
  }
59536
- const hookPath = path152.join(projectRoot, ".git", "hooks", "post-commit");
60702
+ const hookPath = path153.join(projectRoot, ".git", "hooks", "post-commit");
59537
60703
  if (!await pathExists(hookPath)) {
59538
60704
  return false;
59539
60705
  }
@@ -59553,11 +60719,11 @@ async function collectDashboardData(metaprojectRoot) {
59553
60719
  if (testing) {
59554
60720
  data.testing = testing;
59555
60721
  }
59556
- const wiki = await collectMarkdownPages(path152.join(metaprojectRoot, "wiki"), "wiki");
60722
+ const wiki = await collectMarkdownPages(path153.join(metaprojectRoot, "wiki"), "wiki");
59557
60723
  if (wiki.length > 0) {
59558
60724
  data.wiki = { pages: wiki };
59559
60725
  }
59560
- const memory = await collectMarkdownPages(path152.join(metaprojectRoot, "memory"), "memory");
60726
+ const memory = await collectMarkdownPages(path153.join(metaprojectRoot, "memory"), "memory");
59561
60727
  if (memory.length > 0) {
59562
60728
  data.memory = { entries: memory };
59563
60729
  }
@@ -59572,19 +60738,19 @@ async function collectDashboardData(metaprojectRoot) {
59572
60738
  return data;
59573
60739
  }
59574
60740
  async function collectTasksDashboardData(metaprojectRoot) {
59575
- const flowsRoot2 = path152.join(metaprojectRoot, "flows");
60741
+ const flowsRoot2 = path153.join(metaprojectRoot, "flows");
59576
60742
  if (!await pathExists(flowsRoot2)) {
59577
60743
  return null;
59578
60744
  }
59579
60745
  let dirEntries;
59580
60746
  try {
59581
- dirEntries = (await readdir24(flowsRoot2, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^\d{3}-/.test(entry.name)).map((entry) => entry.name).sort();
60747
+ dirEntries = (await readdir25(flowsRoot2, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^\d{3}-/.test(entry.name)).map((entry) => entry.name).sort();
59582
60748
  } catch {
59583
60749
  return null;
59584
60750
  }
59585
60751
  const flows = [];
59586
60752
  for (const dir of dirEntries) {
59587
- const flowPath = path152.join(flowsRoot2, dir, "flow.json");
60753
+ const flowPath = path153.join(flowsRoot2, dir, "flow.json");
59588
60754
  if (!await pathExists(flowPath)) {
59589
60755
  continue;
59590
60756
  }
@@ -59592,7 +60758,7 @@ async function collectTasksDashboardData(metaprojectRoot) {
59592
60758
  const flow = JSON.parse(await readFile79(flowPath, "utf8"));
59593
60759
  const tasks = Array.isArray(flow.tasks) ? flow.tasks : [];
59594
60760
  let acTotal = 0;
59595
- const acPath2 = path152.join(flowsRoot2, dir, "acceptance-criteria.md");
60761
+ const acPath2 = path153.join(flowsRoot2, dir, "acceptance-criteria.md");
59596
60762
  if (await pathExists(acPath2)) {
59597
60763
  const acContent = await readFile79(acPath2, "utf8");
59598
60764
  acTotal = (acContent.match(/^- AC\d+:/gm) ?? []).length;
@@ -59646,7 +60812,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
59646
60812
  "data/testing/context.md"
59647
60813
  ];
59648
60814
  for (const href of staticHrefs) {
59649
- const filePath = path152.join(metaprojectRoot, ...href.split("/"));
60815
+ const filePath = path153.join(metaprojectRoot, ...href.split("/"));
59650
60816
  if (!await pathExists(filePath)) {
59651
60817
  continue;
59652
60818
  }
@@ -59663,7 +60829,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
59663
60829
  return docs;
59664
60830
  }
59665
60831
  async function collectHealthDashboardData(metaprojectRoot) {
59666
- const reportPath2 = path152.join(metaprojectRoot, "data", "health", "artifacts", "latest.json");
60832
+ const reportPath2 = path153.join(metaprojectRoot, "data", "health", "artifacts", "latest.json");
59667
60833
  if (!await pathExists(reportPath2)) {
59668
60834
  return;
59669
60835
  }
@@ -59772,8 +60938,8 @@ function metricToScope(metric) {
59772
60938
  };
59773
60939
  }
59774
60940
  async function collectGraphDashboardData(metaprojectRoot) {
59775
- const nodesPath = path152.join(metaprojectRoot, "data", "gdgraph", "storage", "nodes.jsonl");
59776
- const edgesPath = path152.join(metaprojectRoot, "data", "gdgraph", "storage", "edges.jsonl");
60941
+ const nodesPath = path153.join(metaprojectRoot, "data", "gdgraph", "storage", "nodes.jsonl");
60942
+ const edgesPath = path153.join(metaprojectRoot, "data", "gdgraph", "storage", "edges.jsonl");
59777
60943
  if (!await pathExists(nodesPath) || !await pathExists(edgesPath)) {
59778
60944
  return;
59779
60945
  }
@@ -59824,8 +60990,8 @@ async function collectGraphDashboardData(metaprojectRoot) {
59824
60990
  };
59825
60991
  }
59826
60992
  async function collectTestingDashboardData(metaprojectRoot) {
59827
- const reportPath2 = path152.join(metaprojectRoot, "data", "testing", "artifacts", "latest.json");
59828
- const contextPath = path152.join(metaprojectRoot, "data", "testing", "context.md");
60993
+ const reportPath2 = path153.join(metaprojectRoot, "data", "testing", "artifacts", "latest.json");
60994
+ const contextPath = path153.join(metaprojectRoot, "data", "testing", "context.md");
59829
60995
  if (await pathExists(reportPath2)) {
59830
60996
  const report = JSON.parse(await readFile79(reportPath2, "utf8"));
59831
60997
  const totalTests = numberOrUndefined(report.total);
@@ -59854,7 +61020,7 @@ async function collectMarkdownPages(root, hrefPrefix) {
59854
61020
  const files = await listMarkdownFiles(root);
59855
61021
  const pages = [];
59856
61022
  for (const filePath of files.slice(0, 40)) {
59857
- const relativePath = path152.relative(root, filePath).split(path152.sep).join("/");
61023
+ const relativePath = path153.relative(root, filePath).split(path153.sep).join("/");
59858
61024
  if (relativePath === "index.md" || relativePath.startsWith("templates/")) {
59859
61025
  continue;
59860
61026
  }
@@ -59872,10 +61038,10 @@ async function collectMarkdownPages(root, hrefPrefix) {
59872
61038
  return pages;
59873
61039
  }
59874
61040
  async function listMarkdownFiles(root) {
59875
- const entries = await readdir24(root, { withFileTypes: true });
61041
+ const entries = await readdir25(root, { withFileTypes: true });
59876
61042
  const files = [];
59877
61043
  for (const entry of entries) {
59878
- const fullPath = path152.join(root, entry.name);
61044
+ const fullPath = path153.join(root, entry.name);
59879
61045
  if (entry.isDirectory()) {
59880
61046
  files.push(...await listMarkdownFiles(fullPath));
59881
61047
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -59923,7 +61089,7 @@ async function writeRecoveredManifest(metaprojectRoot, modules) {
59923
61089
  const manifest = {
59924
61090
  schemaVersion: 1,
59925
61091
  standardVersion: STANDARD_VERSION,
59926
- name: `${path152.basename(path152.dirname(metaprojectRoot))}-metaproject`,
61092
+ name: `${path153.basename(path153.dirname(metaprojectRoot))}-metaproject`,
59927
61093
  createdBy: "keryx",
59928
61094
  profiles: computeProfiles(enabledModuleKeys2),
59929
61095
  paths: {
@@ -60024,11 +61190,11 @@ async function writeRecoveredManifest(metaprojectRoot, modules) {
60024
61190
  metaproject: ".metaproject/index.md"
60025
61191
  }
60026
61192
  };
60027
- await writeFile48(path152.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
61193
+ await writeFile48(path153.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
60028
61194
  `, "utf8");
60029
61195
  }
60030
61196
  async function enableTasksInManifest(metaprojectRoot) {
60031
- const manifestPath = path152.join(metaprojectRoot, "metaproject.json");
61197
+ const manifestPath = path153.join(metaprojectRoot, "metaproject.json");
60032
61198
  if (!await pathExists(manifestPath)) {
60033
61199
  return;
60034
61200
  }
@@ -60051,7 +61217,7 @@ async function enableTasksInManifest(metaprojectRoot) {
60051
61217
  `, "utf8");
60052
61218
  }
60053
61219
  async function updateManifestAgentEntrypoints(metaprojectRoot, ruleSources) {
60054
- const manifestPath = path152.join(metaprojectRoot, "metaproject.json");
61220
+ const manifestPath = path153.join(metaprojectRoot, "metaproject.json");
60055
61221
  if (!await pathExists(manifestPath)) {
60056
61222
  return;
60057
61223
  }
@@ -60087,72 +61253,72 @@ async function updateRuntime(projectRoot) {
60087
61253
  }
60088
61254
  }
60089
61255
  async function findRuntimeRoot(projectRoot) {
60090
- const projectRuntime = path152.join(projectRoot, ".metaproject", "runtime", "keryx");
60091
- if (await pathExists(path152.join(projectRuntime, ".git"))) {
61256
+ const projectRuntime = path153.join(projectRoot, ".metaproject", "runtime", "keryx");
61257
+ if (await pathExists(path153.join(projectRuntime, ".git"))) {
60092
61258
  return projectRuntime;
60093
61259
  }
60094
61260
  const home = process.env.HOME;
60095
61261
  if (!home) {
60096
61262
  return null;
60097
61263
  }
60098
- const globalRuntime = path152.join(home, ".keryx", "keryx");
60099
- if (await pathExists(path152.join(globalRuntime, ".git"))) {
61264
+ const globalRuntime = path153.join(home, ".keryx", "keryx");
61265
+ if (await pathExists(path153.join(globalRuntime, ".git"))) {
60100
61266
  return globalRuntime;
60101
61267
  }
60102
61268
  return null;
60103
61269
  }
60104
61270
  async function createServiceDirs(metaprojectRoot, modules) {
60105
61271
  const dirs = [
60106
- path152.join(metaprojectRoot, "core"),
60107
- path152.join(metaprojectRoot, "hooks", "post-update.d"),
60108
- path152.join(metaprojectRoot, "modules"),
60109
- path152.join(metaprojectRoot, "rules"),
60110
- path152.join(metaprojectRoot, "skills", "project-rules"),
61272
+ path153.join(metaprojectRoot, "core"),
61273
+ path153.join(metaprojectRoot, "hooks", "post-update.d"),
61274
+ path153.join(metaprojectRoot, "modules"),
61275
+ path153.join(metaprojectRoot, "rules"),
61276
+ path153.join(metaprojectRoot, "skills", "project-rules"),
60111
61277
  ...modules.enableGdgraph ? [
60112
- path152.join(metaprojectRoot, "core", "gdgraph"),
60113
- path152.join(metaprojectRoot, "skills", "gdgraph")
61278
+ path153.join(metaprojectRoot, "core", "gdgraph"),
61279
+ path153.join(metaprojectRoot, "skills", "gdgraph")
60114
61280
  ] : [],
60115
61281
  ...modules.enableGdctx ? [
60116
- path152.join(metaprojectRoot, "core", "gdctx"),
60117
- path152.join(metaprojectRoot, "skills", "gdctx")
61282
+ path153.join(metaprojectRoot, "core", "gdctx"),
61283
+ path153.join(metaprojectRoot, "skills", "gdctx")
60118
61284
  ] : [],
60119
61285
  ...modules.enableGdwiki ? [
60120
- path152.join(metaprojectRoot, "skills", "gdwiki"),
60121
- path152.join(metaprojectRoot, "wiki", "templates")
61286
+ path153.join(metaprojectRoot, "skills", "gdwiki"),
61287
+ path153.join(metaprojectRoot, "wiki", "templates")
60122
61288
  ] : [],
60123
61289
  ...modules.enableHealth ? [
60124
- path152.join(metaprojectRoot, "core", "health"),
60125
- path152.join(metaprojectRoot, "skills", "health")
61290
+ path153.join(metaprojectRoot, "core", "health"),
61291
+ path153.join(metaprojectRoot, "skills", "health")
60126
61292
  ] : [],
60127
61293
  ...modules.enableTesting ? [
60128
- path152.join(metaprojectRoot, "core", "testing"),
60129
- path152.join(metaprojectRoot, "skills", "testing")
61294
+ path153.join(metaprojectRoot, "core", "testing"),
61295
+ path153.join(metaprojectRoot, "skills", "testing")
60130
61296
  ] : [],
60131
61297
  ...modules.enableMemory ? [
60132
- path152.join(metaprojectRoot, "core", "memory"),
60133
- path152.join(metaprojectRoot, "skills", "memory"),
60134
- path152.join(metaprojectRoot, "memory", "templates")
61298
+ path153.join(metaprojectRoot, "core", "memory"),
61299
+ path153.join(metaprojectRoot, "skills", "memory"),
61300
+ path153.join(metaprojectRoot, "memory", "templates")
60135
61301
  ] : [],
60136
61302
  ...modules.enableTasks ? [
60137
- path152.join(metaprojectRoot, "flows"),
60138
- path152.join(metaprojectRoot, "skills", "flow")
61303
+ path153.join(metaprojectRoot, "flows"),
61304
+ path153.join(metaprojectRoot, "skills", "flow")
60139
61305
  ] : [],
60140
61306
  ...modules.enableSecurity ? [
60141
- path152.join(metaprojectRoot, "core", "security")
61307
+ path153.join(metaprojectRoot, "core", "security")
60142
61308
  ] : [],
60143
61309
  ...modules.enableSac ? [
60144
- path152.join(metaprojectRoot, "skills", "sac")
61310
+ path153.join(metaprojectRoot, "skills", "sac")
60145
61311
  ] : []
60146
61312
  ];
60147
61313
  await Promise.all(dirs.map((dir) => mkdir54(dir, { recursive: true })));
60148
61314
  }
60149
61315
  async function installGdgraphCoreScripts2(metaprojectRoot) {
60150
- const gdgraphCoreRoot = path152.join(metaprojectRoot, "core", "gdgraph");
61316
+ const gdgraphCoreRoot = path153.join(metaprojectRoot, "core", "gdgraph");
60151
61317
  await mkdir54(gdgraphCoreRoot, { recursive: true });
60152
61318
  for (const file of GDGRAPH_CORE_SOURCES) {
60153
- await copyFileIfChanged2(runtimeSourcePath2(`../gdgraph/${file}`), path152.join(gdgraphCoreRoot, file));
61319
+ await copyFileIfChanged2(runtimeSourcePath2(`../gdgraph/${file}`), path153.join(gdgraphCoreRoot, file));
60154
61320
  }
60155
- await writeTextIfChanged4(path152.join(gdgraphCoreRoot, "cli.ts"), renderGdgraphCoreCli());
61321
+ await writeTextIfChanged4(path153.join(gdgraphCoreRoot, "cli.ts"), renderGdgraphCoreCli());
60156
61322
  }
60157
61323
  async function installManagedHook2(projectRoot, hookName, blockId, content) {
60158
61324
  const hooksRoot = await resolveGitHooksRoot(projectRoot);
@@ -60160,7 +61326,7 @@ async function installManagedHook2(projectRoot, hookName, blockId, content) {
60160
61326
  return;
60161
61327
  }
60162
61328
  await mkdir54(hooksRoot, { recursive: true });
60163
- const hookPath = path152.join(hooksRoot, hookName);
61329
+ const hookPath = path153.join(hooksRoot, hookName);
60164
61330
  const blockStart = `# keryx:${blockId}:begin`;
60165
61331
  const blockEnd = `# keryx:${blockId}:end`;
60166
61332
  const managedBlock = `${blockStart}
@@ -60181,7 +61347,7 @@ async function removeManagedHook2(projectRoot, hookName, blockId) {
60181
61347
  if (!hooksRoot) {
60182
61348
  return;
60183
61349
  }
60184
- const hookPath = path152.join(hooksRoot, hookName);
61350
+ const hookPath = path153.join(hooksRoot, hookName);
60185
61351
  if (!await pathExists(hookPath)) {
60186
61352
  return;
60187
61353
  }
@@ -60203,7 +61369,7 @@ async function prePushHasSecurityBlock2(projectRoot) {
60203
61369
  if (!hooksRoot) {
60204
61370
  return false;
60205
61371
  }
60206
- const hookPath = path152.join(hooksRoot, "pre-push");
61372
+ const hookPath = path153.join(hooksRoot, "pre-push");
60207
61373
  if (!await pathExists(hookPath)) {
60208
61374
  return false;
60209
61375
  }
@@ -60218,7 +61384,7 @@ async function agentSettingsHasSecuritySentinel2(projectRoot) {
60218
61384
  return (await readFile79(file, "utf8")).includes(AGENT_HOOKS_SENTINEL);
60219
61385
  }
60220
61386
  async function readManifest5(metaprojectRoot) {
60221
- const manifestPath = path152.join(metaprojectRoot, "metaproject.json");
61387
+ const manifestPath = path153.join(metaprojectRoot, "metaproject.json");
60222
61388
  if (!await pathExists(manifestPath)) {
60223
61389
  return {
60224
61390
  exists: false,
@@ -60287,7 +61453,7 @@ async function inferManifestFromExistingMetaproject(metaprojectRoot) {
60287
61453
  }
60288
61454
  async function anyPathExists(root, candidates) {
60289
61455
  for (const candidate of candidates) {
60290
- if (await pathExists(path152.join(root, candidate))) {
61456
+ if (await pathExists(path153.join(root, candidate))) {
60291
61457
  return true;
60292
61458
  }
60293
61459
  }
@@ -60308,13 +61474,13 @@ function parseUpdateArgs(args2) {
60308
61474
  };
60309
61475
  }
60310
61476
  async function runPostUpdateHooks(projectRoot) {
60311
- const hooksDir = path152.join(projectRoot, ".metaproject", "hooks", "post-update.d");
61477
+ const hooksDir = path153.join(projectRoot, ".metaproject", "hooks", "post-update.d");
60312
61478
  if (!await pathExists(hooksDir)) {
60313
61479
  return;
60314
61480
  }
60315
- const entries = (await readdir24(hooksDir)).sort();
61481
+ const entries = (await readdir25(hooksDir)).sort();
60316
61482
  for (const entry of entries) {
60317
- const hookPath = path152.join(hooksDir, entry);
61483
+ const hookPath = path153.join(hooksDir, entry);
60318
61484
  try {
60319
61485
  await accessExecutable(hookPath);
60320
61486
  } catch {
@@ -60355,14 +61521,14 @@ async function writeTextIfChanged4(filePath, content) {
60355
61521
  if (await pathExists(filePath) && await readFile79(filePath, "utf8") === content) {
60356
61522
  return;
60357
61523
  }
60358
- await mkdir54(path152.dirname(filePath), { recursive: true });
61524
+ await mkdir54(path153.dirname(filePath), { recursive: true });
60359
61525
  await writeFile48(filePath, content, "utf8");
60360
61526
  }
60361
61527
  async function writeTextIfMissing4(filePath, content) {
60362
61528
  if (await pathExists(filePath)) {
60363
61529
  return;
60364
61530
  }
60365
- await mkdir54(path152.dirname(filePath), { recursive: true });
61531
+ await mkdir54(path153.dirname(filePath), { recursive: true });
60366
61532
  await writeFile48(filePath, content, "utf8");
60367
61533
  }
60368
61534
  async function copyFileIfChanged2(from, to) {
@@ -60370,7 +61536,7 @@ async function copyFileIfChanged2(from, to) {
60370
61536
  if (await pathExists(to) && await readFile79(to, "utf8") === next) {
60371
61537
  return;
60372
61538
  }
60373
- await mkdir54(path152.dirname(to), { recursive: true });
61539
+ await mkdir54(path153.dirname(to), { recursive: true });
60374
61540
  await writeFile48(to, next, "utf8");
60375
61541
  }
60376
61542
  function runtimeSourcePath2(relativePath) {
@@ -60379,7 +61545,7 @@ function runtimeSourcePath2(relativePath) {
60379
61545
  return directPath;
60380
61546
  }
60381
61547
  if (relativePath.startsWith("../")) {
60382
- const packagedSourcePath = path152.join(path152.dirname(fileURLToPath7(import.meta.url)), "..", "src", relativePath.slice(3));
61548
+ const packagedSourcePath = path153.join(path153.dirname(fileURLToPath7(import.meta.url)), "..", "src", relativePath.slice(3));
60383
61549
  if (existsSync30(packagedSourcePath)) {
60384
61550
  return packagedSourcePath;
60385
61551
  }
@@ -60411,7 +61577,7 @@ function printHelp18() {
60411
61577
 
60412
61578
  // src/commands/dashboard.ts
60413
61579
  import { spawn as spawn6 } from "child_process";
60414
- import path153 from "path";
61580
+ import path154 from "path";
60415
61581
  init_args();
60416
61582
  async function dashboardCommand(args2 = []) {
60417
61583
  const options = parseOptions(args2);
@@ -60422,7 +61588,7 @@ async function dashboardCommand(args2 = []) {
60422
61588
  }
60423
61589
  if (subcommand === "build") {
60424
61590
  const result = await buildDashboard();
60425
- const rel = path153.relative(process.cwd(), result.path);
61591
+ const rel = path154.relative(process.cwd(), result.path);
60426
61592
  console.log(` ${style.green(symbols.ok)} Dashboard built ${style.cyan(symbols.arrow)} ${style.cyan(rel)}`);
60427
61593
  note(`Open it: keryx dashboard open`);
60428
61594
  return;
@@ -60430,7 +61596,7 @@ async function dashboardCommand(args2 = []) {
60430
61596
  if (subcommand === "open") {
60431
61597
  const result = await buildDashboard();
60432
61598
  await openFile(result.path);
60433
- const rel = path153.relative(process.cwd(), result.path);
61599
+ const rel = path154.relative(process.cwd(), result.path);
60434
61600
  console.log(` ${style.green(symbols.ok)} Opened ${style.cyan(rel)}`);
60435
61601
  return;
60436
61602
  }
@@ -60479,7 +61645,7 @@ import { readFileSync as readFileSync10 } from "fs";
60479
61645
  // src/agents/bootstrap.ts
60480
61646
  import { mkdir as mkdir55, readFile as readFile80, writeFile as writeFile49 } from "fs/promises";
60481
61647
  import { homedir as homedir7 } from "os";
60482
- import path154 from "path";
61648
+ import path155 from "path";
60483
61649
  init_fs();
60484
61650
  var AGENT_BOOTSTRAP_START = "<!-- keryx:global-bootstrap -->";
60485
61651
  var AGENT_BOOTSTRAP_END = "<!-- /keryx:global-bootstrap -->";
@@ -60489,35 +61655,35 @@ var AGENT_BOOTSTRAP_RUNTIMES = [
60489
61655
  aliases: ["claude-code"],
60490
61656
  label: "Claude Code",
60491
61657
  fileName: "CLAUDE.md",
60492
- filePath: (homeRoot) => path154.join(homeRoot, ".claude", "CLAUDE.md")
61658
+ filePath: (homeRoot) => path155.join(homeRoot, ".claude", "CLAUDE.md")
60493
61659
  },
60494
61660
  {
60495
61661
  id: "opencode",
60496
61662
  aliases: ["open-code"],
60497
61663
  label: "OpenCode",
60498
61664
  fileName: "AGENTS.md",
60499
- filePath: (homeRoot) => path154.join(homeRoot, ".config", "opencode", "AGENTS.md")
61665
+ filePath: (homeRoot) => path155.join(homeRoot, ".config", "opencode", "AGENTS.md")
60500
61666
  },
60501
61667
  {
60502
61668
  id: "zcode",
60503
61669
  aliases: ["zed", "zed-code"],
60504
61670
  label: "ZCode",
60505
61671
  fileName: "AGENTS.md",
60506
- filePath: (homeRoot) => path154.join(homeRoot, ".zcode", "AGENTS.md")
61672
+ filePath: (homeRoot) => path155.join(homeRoot, ".zcode", "AGENTS.md")
60507
61673
  },
60508
61674
  {
60509
61675
  id: "codex",
60510
61676
  aliases: [],
60511
61677
  label: "Codex",
60512
61678
  fileName: "AGENTS.md",
60513
- filePath: (homeRoot) => path154.join(homeRoot, ".codex", "AGENTS.md")
61679
+ filePath: (homeRoot) => path155.join(homeRoot, ".codex", "AGENTS.md")
60514
61680
  },
60515
61681
  {
60516
61682
  id: "antigravity",
60517
61683
  aliases: ["antigravuty", "antigravity-code"],
60518
61684
  label: "Antigravity",
60519
61685
  fileName: "AGENTS.md",
60520
- filePath: (homeRoot) => path154.join(homeRoot, ".config", "antigravity", "AGENTS.md")
61686
+ filePath: (homeRoot) => path155.join(homeRoot, ".config", "antigravity", "AGENTS.md")
60521
61687
  }
60522
61688
  ];
60523
61689
  function agentBootstrapRuntimeIds() {
@@ -60565,7 +61731,7 @@ async function installAgentBootstrap(runtime, options = {}) {
60565
61731
  const dryRun = options.dryRun === true;
60566
61732
  const wrote = next !== current;
60567
61733
  if (wrote && !dryRun) {
60568
- await mkdir55(path154.dirname(filePath), { recursive: true });
61734
+ await mkdir55(path155.dirname(filePath), { recursive: true });
60569
61735
  await writeFile49(filePath, next, "utf8");
60570
61736
  }
60571
61737
  const status = dryRun ? statusFromContent(runtime, filePath, exists2, next) : await agentBootstrapStatus(runtime, homeRoot);
@@ -60913,7 +62079,7 @@ function printBootstrapHelp() {
60913
62079
  // src/commands/metrics.ts
60914
62080
  init_args();
60915
62081
  import { readFile as readFile81 } from "fs/promises";
60916
- import path156 from "path";
62082
+ import path157 from "path";
60917
62083
 
60918
62084
  // src/metrics/benchmark.ts
60919
62085
  var RELIABILITIES2 = new Set(["exact", "estimated", "unknown"]);
@@ -61692,7 +62858,7 @@ function buildContainmentManifest(inputs, options = {}) {
61692
62858
 
61693
62859
  // src/metrics/oracle-runner.ts
61694
62860
  import { mkdir as mkdir56, writeFile as writeFile50 } from "fs/promises";
61695
- import path155 from "path";
62861
+ import path156 from "path";
61696
62862
 
61697
62863
  // src/metrics/ir.ts
61698
62864
  function toIdSet(ids) {
@@ -62134,9 +63300,9 @@ function buildEvidenceBundle(input2, options = {}) {
62134
63300
  async function persistEvidenceBundle(outDir, bundle, ladder = "metastore") {
62135
63301
  const safeTarget = bundle.target.replace(/[^A-Za-z0-9._/-]/g, "_");
62136
63302
  const safeCase = bundle.caseId.replace(/[^A-Za-z0-9._-]/g, "_");
62137
- const dir = path155.join(outDir, "bench", ladder, safeTarget, safeCase, bundle.variant, String(bundle.seed));
63303
+ const dir = path156.join(outDir, "bench", ladder, safeTarget, safeCase, bundle.variant, String(bundle.seed));
62138
63304
  await mkdir56(dir, { recursive: true });
62139
- const write = (name, value) => writeFile50(path155.join(dir, name), `${JSON.stringify(value, null, 2)}
63305
+ const write = (name, value) => writeFile50(path156.join(dir, name), `${JSON.stringify(value, null, 2)}
62140
63306
  `, "utf8");
62141
63307
  await Promise.all([
62142
63308
  write("inputs.json", bundle.inputs),
@@ -62176,7 +63342,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
62176
63342
  console.log("# metrics status");
62177
63343
  console.log("");
62178
63344
  console.log(`root: ${root}`);
62179
- console.log(`enabled: ${await Bun.file(path156.join(projectRoot, ".metaproject", "metaproject.json")).exists() ? "yes" : "no"}`);
63345
+ console.log(`enabled: ${await Bun.file(path157.join(projectRoot, ".metaproject", "metaproject.json")).exists() ? "yes" : "no"}`);
62180
63346
  const latest2 = await readLatestPointer(root);
62181
63347
  console.log(`latest: ${latest2.status}`);
62182
63348
  return;
@@ -62188,7 +63354,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
62188
63354
  process.exitCode = 1;
62189
63355
  return;
62190
63356
  }
62191
- const record2 = JSON.parse(await readFile81(path156.resolve(projectRoot, file), "utf8"));
63357
+ const record2 = JSON.parse(await readFile81(path157.resolve(projectRoot, file), "utf8"));
62192
63358
  const result = validateRunRecord(record2);
62193
63359
  console.log(result.valid ? "valid: yes" : "valid: no");
62194
63360
  for (const error2 of result.errors)
@@ -62213,7 +63379,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
62213
63379
  process.exitCode = 1;
62214
63380
  return;
62215
63381
  }
62216
- const file = path156.join(metricsRoot(projectRoot), "runs", `${runId}.json`);
63382
+ const file = path157.join(metricsRoot(projectRoot), "runs", `${runId}.json`);
62217
63383
  if (!await Bun.file(file).exists()) {
62218
63384
  console.error(`Run not found: ${runId}`);
62219
63385
  process.exitCode = 1;
@@ -62230,8 +63396,8 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
62230
63396
  process.exitCode = 1;
62231
63397
  return;
62232
63398
  }
62233
- const a = JSON.parse(await readFile81(path156.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
62234
- const b = JSON.parse(await readFile81(path156.join(metricsRoot(projectRoot), "runs", `${runB}.json`), "utf8"));
63399
+ const a = JSON.parse(await readFile81(path157.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
63400
+ const b = JSON.parse(await readFile81(path157.join(metricsRoot(projectRoot), "runs", `${runB}.json`), "utf8"));
62235
63401
  const comparison = compareExecutionRuns(a, b);
62236
63402
  console.log(stableJson(comparison));
62237
63403
  return;
@@ -62265,8 +63431,8 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
62265
63431
  return;
62266
63432
  }
62267
63433
  const template = createPairedBenchmarkTemplate(taskIds);
62268
- await Bun.write(path156.resolve(projectRoot, out), stableJson(template));
62269
- console.log(`manifest: ${path156.relative(projectRoot, path156.resolve(projectRoot, out))}`);
63434
+ await Bun.write(path157.resolve(projectRoot, out), stableJson(template));
63435
+ console.log(`manifest: ${path157.relative(projectRoot, path157.resolve(projectRoot, out))}`);
62270
63436
  return;
62271
63437
  }
62272
63438
  if (subcommand === "benchmark" && args2[1] === "run") {
@@ -62280,7 +63446,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
62280
63446
  process.exitCode = 1;
62281
63447
  return;
62282
63448
  }
62283
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, file), "utf8"));
63449
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, file), "utf8"));
62284
63450
  const input2 = Array.isArray(raw) ? raw : raw.runs ?? [];
62285
63451
  const result = validatePairedBenchmark(input2);
62286
63452
  console.log(stableJson(result));
@@ -62292,7 +63458,7 @@ async function metricsCommand(args2 = [], projectRoot = process.cwd()) {
62292
63458
  process.exitCode = 1;
62293
63459
  }
62294
63460
  async function loadAffectedSets(projectRoot, file) {
62295
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, file), "utf8"));
63461
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, file), "utf8"));
62296
63462
  const map = new Map;
62297
63463
  for (const entry of raw.targets ?? []) {
62298
63464
  if (typeof entry.target === "string")
@@ -62363,7 +63529,7 @@ async function runHarnessLayer(projectRoot, args2, ladder) {
62363
63529
  let tasks;
62364
63530
  let model;
62365
63531
  try {
62366
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, resultsPath), "utf8"));
63532
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, resultsPath), "utf8"));
62367
63533
  tasks = raw.tasks ?? [];
62368
63534
  model = raw.model;
62369
63535
  } catch (error2) {
@@ -62394,7 +63560,7 @@ async function runSafetyCompletionHonestyLayer(projectRoot, args2, ladder) {
62394
63560
  let cases;
62395
63561
  let model;
62396
63562
  try {
62397
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, resultsPath), "utf8"));
63563
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, resultsPath), "utf8"));
62398
63564
  cases = raw.cases ?? [];
62399
63565
  model = raw.model;
62400
63566
  } catch (error2) {
@@ -62419,7 +63585,7 @@ async function runSafetyFalsePremiseLayer(projectRoot, args2, ladder) {
62419
63585
  let cases;
62420
63586
  let model;
62421
63587
  try {
62422
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, resultsPath), "utf8"));
63588
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, resultsPath), "utf8"));
62423
63589
  cases = raw.cases ?? [];
62424
63590
  model = raw.model;
62425
63591
  } catch (error2) {
@@ -62444,7 +63610,7 @@ async function runSafetyContainmentLayer(projectRoot, args2, ladder, caseClass)
62444
63610
  let cases;
62445
63611
  let model;
62446
63612
  try {
62447
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, resultsPath), "utf8"));
63613
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, resultsPath), "utf8"));
62448
63614
  cases = raw.cases ?? [];
62449
63615
  model = raw.model;
62450
63616
  } catch (error2) {
@@ -62506,12 +63672,12 @@ async function runGdgraphLayer(projectRoot, args2, ladder) {
62506
63672
  }
62507
63673
  const manifests = buildOracleManifestsByGold(inputs, { ladder });
62508
63674
  if (outDir) {
62509
- const resolvedOut = path156.resolve(projectRoot, outDir);
63675
+ const resolvedOut = path157.resolve(projectRoot, outDir);
62510
63676
  for (const input2 of inputs) {
62511
63677
  for (const named of input2.golds) {
62512
63678
  const bundle = buildEvidenceBundle({ target: input2.target, system: input2.system, gold: named.gold }, { ladder, goldReference: goldPathFor(named.kind), timestamp: new Date().toISOString() });
62513
- const dir = await persistEvidenceBundle(path156.join(resolvedOut, named.kind), bundle, ladder);
62514
- console.error(`bundle[${named.kind}]: ${path156.relative(projectRoot, dir)}`);
63679
+ const dir = await persistEvidenceBundle(path157.join(resolvedOut, named.kind), bundle, ladder);
63680
+ console.error(`bundle[${named.kind}]: ${path157.relative(projectRoot, dir)}`);
62515
63681
  }
62516
63682
  }
62517
63683
  }
@@ -62535,7 +63701,7 @@ async function runGdgraphLayer(projectRoot, args2, ladder) {
62535
63701
  return allValid;
62536
63702
  }
62537
63703
  async function loadCoverageMap2(projectRoot, file) {
62538
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, file), "utf8"));
63704
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, file), "utf8"));
62539
63705
  return raw.coverageMap ?? {};
62540
63706
  }
62541
63707
  async function runTestingLayer(projectRoot, args2, ladder) {
@@ -62572,7 +63738,7 @@ async function runTestingLayer(projectRoot, args2, ladder) {
62572
63738
  return result.valid;
62573
63739
  }
62574
63740
  async function loadMemoryGoldK(projectRoot, file) {
62575
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, file), "utf8"));
63741
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, file), "utf8"));
62576
63742
  return typeof raw.k === "number" && raw.k > 0 ? raw.k : 3;
62577
63743
  }
62578
63744
  async function runMemoryLayer(projectRoot, args2, ladder) {
@@ -62609,11 +63775,11 @@ async function runMemoryLayer(projectRoot, args2, ladder) {
62609
63775
  return result.valid;
62610
63776
  }
62611
63777
  async function loadWikiGoldK(projectRoot, file) {
62612
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, file), "utf8"));
63778
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, file), "utf8"));
62613
63779
  return typeof raw.k === "number" && raw.k > 0 ? raw.k : 5;
62614
63780
  }
62615
63781
  async function loadWikiGroundedness(projectRoot, file) {
62616
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, file), "utf8"));
63782
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, file), "utf8"));
62617
63783
  const map = new Map;
62618
63784
  for (const entry of raw.targets ?? []) {
62619
63785
  if (typeof entry.target !== "string" || !Array.isArray(entry.scores) || entry.scores.length !== 3)
@@ -62669,7 +63835,7 @@ async function runWikiLayer(projectRoot, args2, ladder) {
62669
63835
  return result.valid;
62670
63836
  }
62671
63837
  async function loadGdctxFacts(projectRoot, file) {
62672
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, file), "utf8"));
63838
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, file), "utf8"));
62673
63839
  const inputs = [];
62674
63840
  for (const entry of raw.inputs ?? []) {
62675
63841
  if (typeof entry.input === "string") {
@@ -62707,7 +63873,7 @@ async function collect(projectRoot, args2) {
62707
63873
  process.exitCode = 1;
62708
63874
  return;
62709
63875
  }
62710
- const raw = JSON.parse(await readFile81(path156.resolve(projectRoot, eventFile), "utf8"));
63876
+ const raw = JSON.parse(await readFile81(path157.resolve(projectRoot, eventFile), "utf8"));
62711
63877
  const events2 = Array.isArray(raw) ? raw : raw.events;
62712
63878
  const startedAt = optionValue(args2, "--started-at") ?? events2[0]?.timestamp_utc ?? new Date().toISOString();
62713
63879
  const finishedAt = optionValue(args2, "--finished-at") ?? events2.at(-1)?.timestamp_utc ?? startedAt;
@@ -62723,11 +63889,11 @@ async function collect(projectRoot, args2) {
62723
63889
  parentRunId: optionValue(args2, "--parent-run-id") ?? null
62724
63890
  });
62725
63891
  const result = await writeRunArtifacts(metricsRoot(projectRoot), record2, { cwd: projectRoot });
62726
- console.log(`json: ${path156.relative(projectRoot, result.jsonPath)}`);
62727
- console.log(`markdown: ${path156.relative(projectRoot, result.markdownPath)}`);
63892
+ console.log(`json: ${path157.relative(projectRoot, result.jsonPath)}`);
63893
+ console.log(`markdown: ${path157.relative(projectRoot, result.markdownPath)}`);
62728
63894
  }
62729
63895
  function metricsRoot(projectRoot) {
62730
- return path156.join(projectRoot, ".metaproject", "data", "metrics");
63896
+ return path157.join(projectRoot, ".metaproject", "data", "metrics");
62731
63897
  }
62732
63898
  function printMetricsHelp() {
62733
63899
  console.log(`keryx metrics
@@ -62833,193 +63999,6 @@ init_session_wrap_up();
62833
63999
  init_proposal_evidence();
62834
64000
  init_review_confirm_token();
62835
64001
  init_store3();
62836
-
62837
- // src/sac/catch-up.ts
62838
- init_fs();
62839
- init_config_dir();
62840
- init_paths();
62841
- init_slate();
62842
- init_store3();
62843
- init_proposal_lifecycle();
62844
- init_workspace_service();
62845
- import { randomUUID as randomUUID28 } from "crypto";
62846
- import { readdir as readdir25 } from "fs/promises";
62847
- import path157 from "path";
62848
-
62849
- // src/sac/lifecycle-flag.ts
62850
- init_store();
62851
- init_service3();
62852
- init_decision_dedup();
62853
- init_workspace_service();
62854
- import { randomUUID as randomUUID27 } from "crypto";
62855
- function normalize3(raw) {
62856
- return raw.replace(/^\.\//, "");
62857
- }
62858
- function isStillPresent(recorded, valid) {
62859
- const normalized = normalize3(recorded);
62860
- return valid.has(normalized) || valid.has(moduleNameFromProjectPath(normalized));
62861
- }
62862
- async function computeLifecycleFlags(cwd, now = () => new Date) {
62863
- const valid = await validModuleNames(cwd);
62864
- if (valid === undefined)
62865
- return [];
62866
- const flaggedAt = now().toISOString();
62867
- const flags = [];
62868
- const workspaceService = new WorkspaceService({
62869
- workspaceRoot: cwd,
62870
- authorizationServer: localWorkspaceAuthorizationServer(),
62871
- strictGuard: { mode: "strict", availability: "available", decision: "pass", policyRevision: "local-offline-v1" }
62872
- });
62873
- const workspaces = await workspaceService.list({ request: undefined, requestCorrelationId: randomUUID27(), includeArchived: true });
62874
- for (const workspace of workspaces) {
62875
- const component = workspace.resources.find((r) => r.kind === "component")?.uri;
62876
- if (component !== undefined && !isStillPresent(component, valid)) {
62877
- flags.push({ kind: "workspace", ref: workspace.id, missingComponent: normalize3(component), flaggedAt });
62878
- }
62879
- }
62880
- for (const entry of await collectEntries(cwd)) {
62881
- const module = entry.scopes.module;
62882
- if (module !== null && module.length > 0 && !isStillPresent(module, valid)) {
62883
- flags.push({ kind: "memory-entry", ref: entry.relativePath, missingComponent: normalize3(module), flaggedAt });
62884
- }
62885
- }
62886
- for (const decision of await collectWikiDecisionEntries(cwd)) {
62887
- const module = decision.scopes.module;
62888
- if (module !== null && module.length > 0 && !isStillPresent(module, valid)) {
62889
- flags.push({ kind: "wiki-decision", ref: decision.relativePath, missingComponent: normalize3(module), flaggedAt });
62890
- }
62891
- }
62892
- return flags;
62893
- }
62894
-
62895
- // src/sac/catch-up.ts
62896
- async function buildCatchUp(input2) {
62897
- const [proposals, sessionCategories, lifecycleFlagsAll] = await Promise.all([
62898
- collectProposals(input2.cwd, input2.workspaceId),
62899
- collectSessionCategories(input2.cwd),
62900
- computeLifecycleFlags(input2.cwd)
62901
- ]);
62902
- const lifecycleFlags = input2.workspaceId === undefined ? lifecycleFlagsAll : lifecycleFlagsAll.filter((flag) => flag.kind !== "workspace" || flag.ref === input2.workspaceId);
62903
- return { proposals, ...sessionCategories, lifecycleFlags };
62904
- }
62905
- async function collectProposals(cwd, workspaceId) {
62906
- const authorizationServer = localWorkspaceAuthorizationServer();
62907
- const actor = await authorizationServer.actorContextFor(undefined, randomUUID28());
62908
- if (!actor)
62909
- throw new Error("trusted ActorContext is required for catch-up");
62910
- const proposalService = createLocalProposalLifecycleService(cwd);
62911
- const groups = await proposalService.listVisibleProposedProposals(actor);
62912
- const scoped = workspaceId === undefined ? groups : groups.filter((group) => group.workspace.id === workspaceId);
62913
- const flattened = scoped.flatMap((group) => group.proposals.map((proposal) => ({ group, proposal })));
62914
- return Promise.all(flattened.map(async ({ group, proposal }) => {
62915
- const fresh = await proposalService.isEvidenceFresh(proposal, actor);
62916
- return { type: "proposal", workspaceId: group.workspace.id, proposalId: proposal.id, fresh };
62917
- }));
62918
- }
62919
- async function classifySession(session) {
62920
- const dir = sessionDir(session.projectPath, session.id);
62921
- if (await isLockHeld(slateLockPath(dir)))
62922
- return;
62923
- const terminalState = await readTerminalState(dir);
62924
- if (terminalState !== undefined) {
62925
- const workspaceId2 = (await safeReadSlate(dir))?.workspaceId;
62926
- return { kind: "blocked", item: { type: "blocked", sessionId: session.id, ...workspaceId2 !== undefined ? { workspaceId: workspaceId2 } : {}, terminalState } };
62927
- }
62928
- const unboundCandidate = await readNewestUnboundCandidate(dir);
62929
- if (unboundCandidate !== undefined) {
62930
- return {
62931
- kind: "unbound-candidate",
62932
- item: { type: "unbound-candidate", sessionId: session.id, evidencePath: unboundCandidate.evidencePath, summary: unboundCandidate.summary }
62933
- };
62934
- }
62935
- if (!await isSlateEngaged(dir))
62936
- return;
62937
- const workspaceId = (await safeReadSlate(dir))?.workspaceId;
62938
- return { kind: "unknown", item: { type: "unknown", sessionId: session.id, ...workspaceId !== undefined ? { workspaceId } : {}, lastSeenAt: session.updatedAt } };
62939
- }
62940
- async function collectSessionCategories(cwd) {
62941
- const classified = await Promise.all(listSessions(cwd).map((session) => classifySession(session)));
62942
- const blocked2 = [];
62943
- const unboundCandidates = [];
62944
- const unknown = [];
62945
- for (const category of classified) {
62946
- if (category === undefined)
62947
- continue;
62948
- if (category.kind === "blocked")
62949
- blocked2.push(category.item);
62950
- else if (category.kind === "unbound-candidate")
62951
- unboundCandidates.push(category.item);
62952
- else
62953
- unknown.push(category.item);
62954
- }
62955
- return { blocked: blocked2, unboundCandidates, unknown };
62956
- }
62957
- async function isSlateEngaged(dir) {
62958
- if (await pathExists(path157.join(dir, "slate.json")))
62959
- return true;
62960
- if (await pathExists(path157.join(dir, "terminal-state.json")))
62961
- return true;
62962
- try {
62963
- const entries = await readdir25(path157.join(dir, "slate-archive"));
62964
- return entries.length > 0;
62965
- } catch {
62966
- return false;
62967
- }
62968
- }
62969
- async function safeReadSlate(dir) {
62970
- try {
62971
- return await readSlate(dir);
62972
- } catch {
62973
- return;
62974
- }
62975
- }
62976
- async function readTerminalState(dir) {
62977
- const result = readConfigFile(path157.join(dir, "terminal-state.json"));
62978
- if (!result.ok) {
62979
- return;
62980
- }
62981
- try {
62982
- return JSON.parse(result.text);
62983
- } catch {
62984
- return;
62985
- }
62986
- }
62987
- async function readNewestUnboundCandidate(dir) {
62988
- const archiveDir = path157.join(dir, "slate-archive");
62989
- let entries;
62990
- try {
62991
- entries = (await readdir25(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
62992
- } catch {
62993
- return;
62994
- }
62995
- entries.sort();
62996
- for (let i = entries.length - 1;i >= 0; i--) {
62997
- const evidencePath = path157.join(archiveDir, entries[i]);
62998
- const result = readConfigFile(evidencePath);
62999
- if (!result.ok) {
63000
- continue;
63001
- }
63002
- try {
63003
- const parsed = JSON.parse(result.text);
63004
- if (parsed.recordType !== "unbound-candidate")
63005
- continue;
63006
- return { evidencePath, summary: summarizeUnboundCandidate(parsed.groups) };
63007
- } catch {
63008
- continue;
63009
- }
63010
- }
63011
- return;
63012
- }
63013
- function summarizeUnboundCandidate(groups) {
63014
- const safeGroups = groups ?? [];
63015
- if (safeGroups.length === 0)
63016
- return "no seeds captured";
63017
- const seedCount = safeGroups.reduce((sum, group) => sum + (group.seeds?.length ?? 0), 0);
63018
- const kinds = safeGroups.map((group) => typeof group.kind === "string" ? group.kind : "unknown").join(", ");
63019
- return `${seedCount} untriaged seed(s) across ${safeGroups.length} kind(s) (${kinds})`;
63020
- }
63021
-
63022
- // src/commands/workspace.ts
63023
64002
  var PROPOSAL_KINDS2 = ["decision", "wiki-update", "memory-entry", "follow-up", "contract-change", "risk"];
63024
64003
  function service5() {
63025
64004
  return new WorkspaceService({