zelari-code 2.45.1 → 2.46.1

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.
@@ -21104,7 +21104,7 @@ Rules:
21104
21104
  title: "Kraken Lead Playbook",
21105
21105
  // priority 25 = after BEHAVIOR_AGENT (20). Via aiConfig custom modules get +1000.
21106
21106
  priority: 25,
21107
- content: "# Kraken Lead Playbook (super-agent)\n\nYou are the **parent brain**. Sub-agents spawned with task are tentacles: they cannot see this chat and cannot nest further task calls.\n\n## Default workflow (non-trivial work)\n1. **Orient** - list/read key files; optionally task explore (parallel OK for disjoint questions).\n2. **Decompose** - todo_write with concrete slices and acceptance criteria.\n3. **Implement** - one slice at a time via tools or task agent=general for a bounded unit.\n4. **Verify** - after meaningful writes, run checks yourself (bash / typecheck / tests) or task agent=verify. Do not claim done without on-disk evidence.\n5. **Integrate** - summarize files touched + how to verify; if more remains, checkpoint and ask.\n\n## When to spawn task\n- **explore**: unfamiliar area, multi-file search, map call sites (prefer parallel explores).\n- **general**: isolated implement slice with clear path scope (serial writers unless worktree isolation is on).\n- **verify**: post-implement gate (tests/typecheck/smoke).\n\n## Task contracts (required quality)\nEvery task prompt must be self-contained and include:\n- **Goal** (one sentence)\n- **Scope** (paths / symbols allowed; what is out of scope)\n- **Acceptance** (how the parent will know it succeeded)\n- **Constraints** (no drive-by refactors; match existing style)\n\nOptional tool fields: scope (path allowlist hint), acceptance (checklist). Prefer them when available.\n\n## Caps and discipline\n- Prefer at most 4 explore and 2 general spawns per user turn unless the user asks for more.\n- Do not expand scope beyond the user request.\n- Parallel: many explore OK; general writers stay serial unless ZELARI_KRAKEN_WORKTREE=1.\n- Nested task from children is disabled - you are the only orchestrator.\n- Cheap thoroughness defaults: explore=quick|medium; deep only when stuck.\n- Model routing: explore/verify may use ZELARI_KRAKEN_SUB_MODEL (cheaper); general stays on parent model unless ZELARI_KRAKEN_GENERAL_MODEL is set.\n- After every successful task general, spawn task verify (or run tests yourself) before claiming done - the tool appends a verify-hint footer.\n- Opt-in isolation: ZELARI_KRAKEN_WORKTREE=1 runs general tentacles in a git worktree under .zelari/worktrees/ (KEEP=1 to retain branch for manual merge).\n- Progress bus: tentacle spawns log to .zelari/radio/<session>.jsonl - slash command /kraken shows status.\n\n## Done means verified\nNever end with status theater. Either tools ran and files changed, or you stop with a short report and ask whether to continue."
21107
+ content: "# Kraken Lead Playbook (super-agent)\n\nYou are the **parent brain**. Sub-agents spawned with task are tentacles: they cannot see this chat and cannot nest further task calls.\n\n## Default workflow (non-trivial work)\n1. **Orient** - list/read key files; optionally task explore (parallel OK for disjoint questions).\n2. **Decompose** - todo_write with concrete slices and acceptance criteria.\n3. **Implement** - one slice at a time via tools or task agent=general for a bounded unit.\n4. **Verify** - after meaningful writes, run checks yourself (bash / typecheck / tests) or task agent=verify. Do not claim done without on-disk evidence.\n5. **Integrate** - summarize files touched + how to verify; if more remains, checkpoint and ask.\n\n## When to spawn task\n- **explore**: unfamiliar area, multi-file search, map call sites (prefer parallel explores).\n- **general**: isolated implement slice with clear path scope (serial writers unless worktree isolation is on).\n- **verify**: post-implement gate (tests/typecheck/smoke).\n\n## Task contracts (required quality)\nEvery task prompt must be self-contained and include:\n- **Goal** (one sentence)\n- **Scope** (paths / symbols allowed; what is out of scope)\n- **Acceptance** (how the parent will know it succeeded)\n- **Constraints** (no drive-by refactors; match existing style)\n\nOptional tool fields: scope (path allowlist hint), acceptance (checklist). Prefer them when available.\n\n## Caps and discipline\n- Prefer at most 4 explore and 2 general spawns per user turn unless the user asks for more.\n- Do not expand scope beyond the user request.\n- Parallel: many explore OK; general writers stay serial unless ZELARI_KRAKEN_WORKTREE=1.\n- Nested task from children is disabled - you are the only orchestrator.\n- Cheap thoroughness defaults: explore=quick|medium; deep only when stuck.\n- Model routing: explore/verify may use ZELARI_KRAKEN_SUB_MODEL (cheaper); general stays on parent model unless ZELARI_KRAKEN_GENERAL_MODEL is set.\n- After every successful task general the runtime auto-spawns a verify tentacle (general->verify obligation, ADR-0033); only a parseable verify PASS clears it, and open debt at turn end blocks strict done (exit 4).\n- Opt-in isolation: ZELARI_KRAKEN_WORKTREE=1 runs general tentacles in a git worktree under .zelari/worktrees/ (KEEP=1 to retain branch for manual merge).\n- Progress bus: tentacle spawns log to .zelari/radio/<session>.jsonl - slash command /kraken shows status.\n\n## Done means verified\nNever end with status theater. Either tools ran and files changed, or you stop with a short report and ask whether to continue."
21108
21108
  };
21109
21109
  KRAKEN_SELECTION_PLAYBOOK_MODULE = {
21110
21110
  type: "behavior-rules",
@@ -38898,7 +38898,7 @@ var CORE_VERSION;
38898
38898
  var init_version = __esm({
38899
38899
  "packages/core/dist/version.js"() {
38900
38900
  "use strict";
38901
- CORE_VERSION = "2.45.1";
38901
+ CORE_VERSION = "2.46.1";
38902
38902
  }
38903
38903
  });
38904
38904
 
@@ -40110,6 +40110,7 @@ var init_cmdline = __esm({
40110
40110
  var updater_exports = {};
40111
40111
  __export(updater_exports, {
40112
40112
  REGISTRY_URL: () => REGISTRY_URL,
40113
+ UPDATE_CHECK_DELAY_MS: () => UPDATE_CHECK_DELAY_MS,
40113
40114
  checkForUpdate: () => checkForUpdate,
40114
40115
  compareSemver: () => compareSemver,
40115
40116
  distTagForVersion: () => distTagForVersion,
@@ -40120,7 +40121,8 @@ __export(updater_exports, {
40120
40121
  performUpdate: () => performUpdate,
40121
40122
  registryUrlForTag: () => registryUrlForTag,
40122
40123
  resolveBundledNpmCli: () => resolveBundledNpmCli,
40123
- resolveInstallKind: () => resolveInstallKind
40124
+ resolveInstallKind: () => resolveInstallKind,
40125
+ runDeferredUpdateCheck: () => runDeferredUpdateCheck
40124
40126
  });
40125
40127
  import { createRequire } from "node:module";
40126
40128
  import { execSync, spawn as spawn6 } from "node:child_process";
@@ -40251,6 +40253,23 @@ async function checkForUpdate(fetcher = fetch, registryUrl) {
40251
40253
  updateAvailable: cmp < 0
40252
40254
  };
40253
40255
  }
40256
+ async function runDeferredUpdateCheck(opts = {}) {
40257
+ const delayMs = opts.delayMs ?? UPDATE_CHECK_DELAY_MS;
40258
+ const sleep = opts.sleep ?? ((ms) => new Promise((resolve11) => setTimeout(resolve11, ms)));
40259
+ const report = opts.report ?? ((message) => {
40260
+ console.error(message);
40261
+ });
40262
+ try {
40263
+ await sleep(delayMs);
40264
+ const info = await (opts.check ?? (() => checkForUpdate()))();
40265
+ if (info.updateAvailable && !info.error) {
40266
+ report(
40267
+ `[zelari-code] \u{1F195} v${info.latestVersion} available (current: v${info.currentVersion}). Run \`zelari-code\` then \`/update --yes\` to upgrade.`
40268
+ );
40269
+ }
40270
+ } catch {
40271
+ }
40272
+ }
40254
40273
  async function performUpdate(packageName = "zelari-code", executor = spawn6, resolveNpmCli = resolveBundledNpmCli, channel, installKind = resolveInstallKind()) {
40255
40274
  if (installKind === "npx" || installKind === "local") {
40256
40275
  return {
@@ -40307,7 +40326,7 @@ function runNpm(executor, args, mode, npmCliPath) {
40307
40326
  });
40308
40327
  });
40309
40328
  }
40310
- var require2, __dirname2, PKG_ROOT, REGISTRY_URL;
40329
+ var require2, __dirname2, PKG_ROOT, REGISTRY_URL, UPDATE_CHECK_DELAY_MS;
40311
40330
  var init_updater = __esm({
40312
40331
  "src/cli/updater.ts"() {
40313
40332
  "use strict";
@@ -40316,6 +40335,7 @@ var init_updater = __esm({
40316
40335
  __dirname2 = path32.dirname(fileURLToPath(import.meta.url));
40317
40336
  PKG_ROOT = path32.resolve(__dirname2, "..", "..");
40318
40337
  REGISTRY_URL = "https://registry.npmjs.org/zelari-code/latest";
40338
+ UPDATE_CHECK_DELAY_MS = 12e3;
40319
40339
  }
40320
40340
  });
40321
40341
 
@@ -46155,6 +46175,7 @@ function buildUpstreamContext(graph, node) {
46155
46175
  for (const depId of node.deps) {
46156
46176
  const dep = graph.nodes.get(depId);
46157
46177
  if (!dep || dep.status !== "done") continue;
46178
+ if (REVIEWER_KINDS.has(node.kind) && WRITER_KINDS2.has(dep.kind)) continue;
46158
46179
  const raw = (dep.result ?? "").trim();
46159
46180
  if (!raw) continue;
46160
46181
  const cap3 = Math.min(MAX_UPSTREAM_CHARS_PER_DEP, budget);
@@ -46198,7 +46219,7 @@ function roiTaskKindOf(kind2) {
46198
46219
  if (kind2 === "spec" || kind2 === "conformance") return "review";
46199
46220
  return "implement";
46200
46221
  }
46201
- var DEFAULT_MAX_PARALLEL, DEFAULT_FIX_BUDGET, DEFAULT_TRANSACTIONAL, DEFAULT_MAX_REVIEW_ROUNDS, DEFAULT_GRAPH_TIMEOUT_MS, DEFAULT_NODE_TIMEOUT_MS, DEFAULT_WRITER_NODE_TIMEOUT_MS, DEFAULT_CANCEL_GRACE_MS, MAX_UPSTREAM_CHARS_PER_DEP, MAX_UPSTREAM_CHARS_TOTAL, KrakenGraphExecutor;
46222
+ var DEFAULT_MAX_PARALLEL, DEFAULT_FIX_BUDGET, DEFAULT_TRANSACTIONAL, DEFAULT_MAX_REVIEW_ROUNDS, DEFAULT_GRAPH_TIMEOUT_MS, DEFAULT_NODE_TIMEOUT_MS, DEFAULT_WRITER_NODE_TIMEOUT_MS, DEFAULT_CANCEL_GRACE_MS, MAX_UPSTREAM_CHARS_PER_DEP, MAX_UPSTREAM_CHARS_TOTAL, WRITER_KINDS2, REVIEWER_KINDS, KrakenGraphExecutor;
46202
46223
  var init_executor = __esm({
46203
46224
  "src/cli/kraken/executor.ts"() {
46204
46225
  "use strict";
@@ -46232,6 +46253,12 @@ var init_executor = __esm({
46232
46253
  DEFAULT_CANCEL_GRACE_MS = 3e4;
46233
46254
  MAX_UPSTREAM_CHARS_PER_DEP = 2800;
46234
46255
  MAX_UPSTREAM_CHARS_TOTAL = 8e3;
46256
+ WRITER_KINDS2 = /* @__PURE__ */ new Set(["general", "fix"]);
46257
+ REVIEWER_KINDS = /* @__PURE__ */ new Set([
46258
+ "verify",
46259
+ "spec",
46260
+ "conformance"
46261
+ ]);
46235
46262
  KrakenGraphExecutor = class {
46236
46263
  deps;
46237
46264
  parentCwd;
@@ -46353,7 +46380,7 @@ var init_executor = __esm({
46353
46380
  }
46354
46381
  }
46355
46382
  isReviewerKind(kind2) {
46356
- return kind2 === "verify" || kind2 === "spec" || kind2 === "conformance";
46383
+ return REVIEWER_KINDS.has(kind2);
46357
46384
  }
46358
46385
  /** Execute the graph in place (mutates node statuses) until it settles. */
46359
46386
  async execute(graph) {
@@ -47629,16 +47656,15 @@ function taskVerifyObligation() {
47629
47656
  const g = globalThis;
47630
47657
  return g.__zelariGeneralVerifyDebt ?? null;
47631
47658
  }
47659
+ function outcomeMemoryAllowed() {
47660
+ return taskVerifyObligation() === null;
47661
+ }
47632
47662
  function maxTaskSpawnsPerTurn() {
47633
47663
  const raw = process.env.ZELARI_KRAKEN_MAX_TASK_SPAWNS;
47634
47664
  if (raw === void 0 || raw === "") return 6;
47635
47665
  const n = Number.parseInt(raw, 10);
47636
47666
  return Number.isFinite(n) && n > 0 ? Math.min(n, 32) : 6;
47637
47667
  }
47638
- function verifyHintForGeneral(acceptance) {
47639
- const acc = acceptance && acceptance.length > 0 ? ` Acceptance to check: ${acceptance.join("; ")}.` : "";
47640
- return `[kraken:verify-hint] General tentacle finished. Before claiming done, run checks or spawn task agent=verify.${acc}`;
47641
- }
47642
47668
  function buildTaskAutoVerifyPrompt(args) {
47643
47669
  const taskPrompt = args.prompt.length > MAX_AUTO_VERIFY_TASK_PROMPT_CHARS ? `${args.prompt.slice(0, MAX_AUTO_VERIFY_TASK_PROMPT_CHARS)}
47644
47670
  \u2026 [truncated]` : args.prompt;
@@ -47656,7 +47682,7 @@ function buildTaskAutoVerifyPrompt(args) {
47656
47682
  }
47657
47683
  parts.push(
47658
47684
  "",
47659
- "Read the files involved rather than trusting any summary. Report the commands you ran and every gap you found.",
47685
+ "This brief is BLIND by design: it gives you the goal, the scope and the acceptance criteria ONLY \u2014 never the implementer's summary, self-assessment or claimed command output. Read the files on disk and run the acceptance commands yourself; a PASS must come from output YOU produced. Report the commands you ran and every gap you found.",
47660
47686
  "",
47661
47687
  "## How to report your verdict",
47662
47688
  "End your final message with a line of exactly this form, as the LAST line:",
@@ -47680,6 +47706,36 @@ ${original.prompt}
47680
47706
  ## Reviewer findings (these are what must change)
47681
47707
  ${findings || "(the reviewer reported FAIL without detail)"}`;
47682
47708
  }
47709
+ async function rememberVerifiedGeneralOutcome(opts) {
47710
+ const memoryService = opts.deps.memoryService;
47711
+ if (!memoryService || opts.deps.memoryAutoWrite === false) return;
47712
+ const content = (opts.general.result ?? "").trim();
47713
+ if (!content) return;
47714
+ try {
47715
+ await memoryService.remember({
47716
+ kind: "outcome",
47717
+ content: content.slice(0, 12e3),
47718
+ importance: 0.75,
47719
+ confidence: 0.98,
47720
+ tags: ["kraken", "tentacle:general"],
47721
+ source: {
47722
+ agent: "kraken-general",
47723
+ sessionId: opts.sessionId,
47724
+ ...opts.general.agentId ? { tentacleId: opts.general.agentId } : {},
47725
+ ...opts.general.worktreePath ? { worktree: opts.general.worktreePath } : {}
47726
+ },
47727
+ metadata: {
47728
+ writeClass: "auto",
47729
+ description: opts.original.description,
47730
+ scope: opts.original.scope ?? [],
47731
+ acceptance: opts.original.acceptance ?? [],
47732
+ verified: true
47733
+ },
47734
+ writeClass: "auto"
47735
+ });
47736
+ } catch {
47737
+ }
47738
+ }
47683
47739
  async function runAutoVerifyAfterGeneral(opts) {
47684
47740
  const g = globalThis;
47685
47741
  g.__zelariGeneralVerifyDebt = { description: opts.original.description };
@@ -47769,6 +47825,7 @@ async function runAutoVerifyAfterGeneral(opts) {
47769
47825
  }
47770
47826
  if (verdict === "pass") {
47771
47827
  g.__zelariGeneralVerifyDebt = null;
47828
+ await rememberVerifiedGeneralOutcome(opts);
47772
47829
  emitVerifyPhase("verify PASS", true, "completed");
47773
47830
  return `
47774
47831
 
@@ -48238,8 +48295,6 @@ worktree deferred: branch=${worktree.branch} path=${worktree.path} (executor mer
48238
48295
  ${formatWorktreeFooter(worktree, { kept, merge: merge2 })}`;
48239
48296
  }
48240
48297
  if (agent === "general") {
48241
- footer += `
48242
- ${verifyHintForGeneral(args.acceptance)}`;
48243
48298
  g.__zelariLastGeneralAt = Date.now();
48244
48299
  }
48245
48300
  endTentacle(liveId, {
@@ -48275,30 +48330,33 @@ ${verifyHintForGeneral(args.acceptance)}`;
48275
48330
  if (deps.memoryService && deps.memoryAutoWrite !== false) {
48276
48331
  try {
48277
48332
  const verifyPass = agent === "verify" && /(?:VERDICT:\s*PASS|status:\s*pass)/i.test(result);
48278
- const memory = await deps.memoryService.remember({
48279
- kind: agent === "verify" ? "verification" : agent === "general" ? "outcome" : "finding",
48280
- content: result.slice(0, 12e3),
48281
- importance: agent === "general" ? 0.75 : 0.65,
48282
- confidence: verifyPass ? 0.98 : agent === "verify" ? 0.7 : 0.72,
48283
- tags: ["kraken", `tentacle:${agent}`],
48284
- source: {
48285
- agent: `kraken-${agent}`,
48286
- sessionId: sessionId2,
48287
- tentacleId: opts.nodeId ?? liveId,
48288
- ...worktree?.path ? { worktree: worktree.path } : {}
48289
- },
48290
- metadata: {
48291
- writeClass: agent === "verify" || agent === "general" ? "auto" : "candidate",
48292
- description: args.description,
48293
- scope: args.scope ?? [],
48294
- acceptance: args.acceptance ?? [],
48295
- graphId: opts.graphId,
48296
- nodeId: opts.nodeId,
48297
- verified: verifyPass
48298
- },
48299
- writeClass: agent === "verify" || agent === "general" ? "auto" : "candidate"
48300
- });
48301
- memoryId = memory.id;
48333
+ const allowMemory = agent === "verify" ? verifyPass : agent === "general" ? false : outcomeMemoryAllowed();
48334
+ if (allowMemory) {
48335
+ const memory = await deps.memoryService.remember({
48336
+ kind: agent === "verify" ? "verification" : "finding",
48337
+ content: result.slice(0, 12e3),
48338
+ importance: 0.65,
48339
+ confidence: verifyPass ? 0.98 : 0.72,
48340
+ tags: ["kraken", `tentacle:${agent}`],
48341
+ source: {
48342
+ agent: `kraken-${agent}`,
48343
+ sessionId: sessionId2,
48344
+ tentacleId: opts.nodeId ?? liveId,
48345
+ ...worktree?.path ? { worktree: worktree.path } : {}
48346
+ },
48347
+ metadata: {
48348
+ writeClass: agent === "verify" ? "auto" : "candidate",
48349
+ description: args.description,
48350
+ scope: args.scope ?? [],
48351
+ acceptance: args.acceptance ?? [],
48352
+ graphId: opts.graphId,
48353
+ nodeId: opts.nodeId,
48354
+ verified: verifyPass
48355
+ },
48356
+ writeClass: agent === "verify" ? "auto" : "candidate"
48357
+ });
48358
+ memoryId = memory.id;
48359
+ }
48302
48360
  } catch {
48303
48361
  }
48304
48362
  }
@@ -48501,8 +48559,15 @@ var init_taskTool = __esm({
48501
48559
  ].join("\n");
48502
48560
  VERIFY_PROMPT = [
48503
48561
  "You are a VERIFY tentacle of Kraken. Confirm whether work is correct on disk.",
48504
- "You may read files and run test/build commands via bash. Prefer",
48505
- "targeted checks over full suite when possible.",
48562
+ "You are BLIND: you never see \u2014 and must NEVER trust \u2014 any summary,",
48563
+ 'self-assessment, or "result" reported by the agent that did the work. If such',
48564
+ "text is ever shown to you, treat it as an unverified claim, not evidence.",
48565
+ "You may read files and run test/build commands via bash. Run the acceptance",
48566
+ "commands YOURSELF and derive every verdict ONLY from the real command output",
48567
+ "you observed (exit code + stdout/stderr) and the files as they exist on disk.",
48568
+ "Never mark something pass because it was described as done, or because a",
48569
+ "claim said a command was green: a pass needs evidence YOU produced this run.",
48570
+ "Prefer targeted checks over full suite when possible.",
48506
48571
  "Report: pass/fail, commands run, key output, and gaps vs Acceptance criteria.",
48507
48572
  "If Acceptance criteria are listed, check each one explicitly.",
48508
48573
  "End your final message with ONE <verify-report> block per acceptance",
@@ -60163,6 +60228,9 @@ function emptyResult(enabled, skippedReason) {
60163
60228
  checkProposals: []
60164
60229
  };
60165
60230
  }
60231
+ function skippedOpsKnowledgeResult(reason) {
60232
+ return emptyResult(true, reason);
60233
+ }
60166
60234
  async function openMemory(projectRoot, env) {
60167
60235
  try {
60168
60236
  const factory = await Promise.resolve().then(() => (init_serviceFactory(), serviceFactory_exports));
@@ -70562,6 +70630,7 @@ async function writeProofSafe(gate, meta3, baseDir = process.cwd()) {
70562
70630
  `
70563
70631
  );
70564
70632
  }
70633
+ if (!outcomeMemoryAllowed()) return skippedOpsKnowledgeResult("verify-obligation-open");
70565
70634
  return promoteOpsKnowledgeSafe(gate, { projectRoot: baseDir, sessionId: meta3.sessionId });
70566
70635
  }
70567
70636
  function surfaceOpsKnowledgeNotices(result, opts) {
@@ -71104,7 +71173,7 @@ async function runOneTurn(opts, provider, model, providerStream, extras) {
71104
71173
  } catch {
71105
71174
  }
71106
71175
  }
71107
- if (nativeMemory && memoryAutoWrite && pass.finalReason !== "error") {
71176
+ if (nativeMemory && memoryAutoWrite && pass.finalReason !== "error" && outcomeMemoryAllowed()) {
71108
71177
  try {
71109
71178
  const finalContent = [...pass.messages].reverse().find((message) => message.role === "assistant" && message.content.trim())?.content.trim();
71110
71179
  if (finalContent) {
@@ -77785,26 +77854,59 @@ import { Box as Box9, Static, useInput as useInput2, useStdin as useStdin2 } fro
77785
77854
  import React from "react";
77786
77855
  import { Box, Text } from "ink";
77787
77856
  import TextInput from "ink-text-input";
77788
- function InputBarImpl({ value, onChange, onSubmit, disabled }) {
77857
+
77858
+ // src/cli/components/inputDraft.ts
77859
+ import { useSyncExternalStore } from "react";
77860
+ function createInputDraftStore(initial = "") {
77861
+ let value = initial;
77862
+ const listeners = /* @__PURE__ */ new Set();
77863
+ const emit = () => {
77864
+ for (const listener of listeners) listener();
77865
+ };
77866
+ return {
77867
+ get: () => value,
77868
+ set(next) {
77869
+ if (next === value) return;
77870
+ value = next;
77871
+ emit();
77872
+ },
77873
+ clear() {
77874
+ if (value === "") return;
77875
+ value = "";
77876
+ emit();
77877
+ },
77878
+ subscribe(listener) {
77879
+ listeners.add(listener);
77880
+ return () => {
77881
+ listeners.delete(listener);
77882
+ };
77883
+ }
77884
+ };
77885
+ }
77886
+ function useInputDraft(store6) {
77887
+ return useSyncExternalStore(store6.subscribe, store6.get, store6.get);
77888
+ }
77889
+
77890
+ // src/cli/components/InputBar.tsx
77891
+ function InputBarImpl({ draft, onSubmit, disabled }) {
77892
+ const value = useInputDraft(draft);
77789
77893
  const onSubmitRef = React.useRef(onSubmit);
77790
- const onChangeRef = React.useRef(onChange);
77791
77894
  onSubmitRef.current = onSubmit;
77792
- onChangeRef.current = onChange;
77793
77895
  const stableSubmit = React.useCallback((v) => onSubmitRef.current(v), []);
77794
- const stableChange = React.useCallback((v) => onChangeRef.current(v), []);
77795
77896
  return /* @__PURE__ */ React.createElement(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "cyan", bold: true }, "\u276F "), /* @__PURE__ */ React.createElement(
77796
77897
  TextInput,
77797
77898
  {
77798
77899
  value,
77799
- onChange: stableChange,
77900
+ onChange: draft.set,
77800
77901
  onSubmit: stableSubmit,
77801
77902
  placeholder: disabled ? "..." : "Prompt, /skills, or @path"
77802
77903
  }
77803
77904
  ));
77804
77905
  }
77805
- var InputBar = React.memo(InputBarImpl, (prev2, next) => {
77806
- return prev2.value === next.value && prev2.disabled === next.disabled;
77807
- });
77906
+ function inputBarPropsEqual(prev2, next) {
77907
+ return prev2.draft === next.draft && prev2.disabled === next.disabled;
77908
+ }
77909
+ var InputBar = React.memo(InputBarImpl, inputBarPropsEqual);
77808
77910
 
77809
77911
  // src/cli/components/LiveRegion.tsx
77810
77912
  import React5 from "react";
@@ -77961,7 +78063,7 @@ init_duration();
77961
78063
  init_modelPricing();
77962
78064
  import React6 from "react";
77963
78065
  import { Box as Box5, Text as Text6 } from "ink";
77964
- function StatusBar({
78066
+ function StatusBarImpl({
77965
78067
  model,
77966
78068
  provider,
77967
78069
  sessionId: sessionId2,
@@ -77990,6 +78092,15 @@ function StatusBar({
77990
78092
  const modeColor = mode === "council" ? "magenta" : mode === "zelari" ? "green" : "red";
77991
78093
  return /* @__PURE__ */ React6.createElement(Box5, { paddingX: 1, width: "100%", justifyContent: "space-between", gap: 2 }, /* @__PURE__ */ React6.createElement(Box5, { flexShrink: 2 }, /* @__PURE__ */ React6.createElement(Text6, { wrap: "truncate" }, /* @__PURE__ */ React6.createElement(Text6, { color: sessionActive ? "green" : "gray" }, sessionActive ? "\u25CF" : "\u25CB"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: phase2 === "plan" ? "yellow" : "green" }, phase2 === "plan" ? "\u25C7 plan" : "\u25C6 build"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: modeColor }, modeLabel), verify ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: verify.tone }, verify.label)) : null, permissions ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: permissions.tone }, permissions.label)) : null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), jail ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: jail.tone }, jail.label)) : null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, provider), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, null, model), cwd ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { color: "blue" }, cwd)) : null)), /* @__PURE__ */ React6.createElement(Box5, { flexShrink: 1 }, /* @__PURE__ */ React6.createElement(Text6, { wrap: "truncate" }, busy && elapsedMs !== null ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Spinner, { color: "yellow" }), /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, " ", formatDuration(elapsedMs)), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : lastMs !== null ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "last ", formatDuration(lastMs)), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, queueCount > 0 ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, "queue ", queueCount), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, todoSummary ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, todoSummary), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, krakenLive ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, krakenLive), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, krakenGraph ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, krakenGraph), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, ctxLabel ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "cyan" }, ctxLabel), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, costUsd > 0 ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, formatCost(costUsd)), cachedTokens > 0 ? /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (", formatTokens(cachedTokens), " cached)") : null, cacheHitRate > 0 ? /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " ", Math.round(cacheHitRate * 100), "% hit") : null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "session ", sessionId2))));
77992
78094
  }
78095
+ function statusChipPropsEqual(prev2, next) {
78096
+ if (prev2 === next) return true;
78097
+ if (!prev2 || !next) return false;
78098
+ return prev2.label === next.label && prev2.tone === next.tone;
78099
+ }
78100
+ function statusBarPropsEqual(prev2, next) {
78101
+ return prev2.model === next.model && prev2.provider === next.provider && prev2.sessionId === next.sessionId && prev2.sessionActive === next.sessionActive && prev2.queueCount === next.queueCount && prev2.busy === next.busy && prev2.mode === next.mode && prev2.phase === next.phase && prev2.cwd === next.cwd && prev2.elapsedMs === next.elapsedMs && prev2.lastMs === next.lastMs && prev2.costUsd === next.costUsd && prev2.cachedTokens === next.cachedTokens && prev2.cacheHitRate === next.cacheHitRate && prev2.contextUsed === next.contextUsed && prev2.contextLimit === next.contextLimit && prev2.todoSummary === next.todoSummary && prev2.krakenLive === next.krakenLive && prev2.krakenGraph === next.krakenGraph && statusChipPropsEqual(prev2.verify, next.verify) && statusChipPropsEqual(prev2.permissions, next.permissions) && statusChipPropsEqual(prev2.jail, next.jail);
78102
+ }
78103
+ var StatusBar = React6.memo(StatusBarImpl, statusBarPropsEqual);
77993
78104
 
77994
78105
  // src/cli/components/SelectList.tsx
77995
78106
  import React7, { useState as useState2 } from "react";
@@ -78199,32 +78310,56 @@ async function snapshotGitChanges(cwd) {
78199
78310
  files: mergeChanges(parseNumstat(unstaged), parseNumstat(staged), parseUntracked(status))
78200
78311
  };
78201
78312
  }
78313
+ var GIT_POLL_MS = 4e3;
78314
+ var GIT_IDLE_POLL_MS = 2e4;
78315
+ var GIT_IDLE_AFTER_TICKS = 3;
78316
+ function nextGitPollDelay(quietTicks, opts) {
78317
+ if (opts.hot) return opts.pollMs;
78318
+ return quietTicks >= opts.idleAfterTicks ? Math.max(opts.pollMs, opts.idlePollMs) : opts.pollMs;
78319
+ }
78202
78320
  function useGitChanges(opts = {}) {
78203
- const { pollMs = 4e3, cwd = process.cwd() } = opts;
78321
+ const {
78322
+ pollMs = GIT_POLL_MS,
78323
+ idlePollMs = GIT_IDLE_POLL_MS,
78324
+ idleAfterTicks = GIT_IDLE_AFTER_TICKS,
78325
+ hot = false,
78326
+ cwd = process.cwd(),
78327
+ snapshot = snapshotGitChanges
78328
+ } = opts;
78204
78329
  const [changes, setChanges] = useState3(EMPTY_GIT_CHANGES);
78205
78330
  const lastJson = useRef("");
78206
78331
  useEffect2(() => {
78207
78332
  let cancelled = false;
78208
78333
  let timer = null;
78334
+ let quiet = 0;
78209
78335
  const tick = async () => {
78210
78336
  try {
78211
- const snap = await snapshotGitChanges(cwd);
78337
+ const snap = await snapshot(cwd);
78212
78338
  if (cancelled) return;
78213
78339
  const json3 = JSON.stringify(snap);
78214
78340
  if (json3 !== lastJson.current) {
78215
78341
  lastJson.current = json3;
78342
+ quiet = 0;
78216
78343
  setChanges(snap);
78344
+ } else {
78345
+ quiet += 1;
78217
78346
  }
78218
78347
  } catch {
78348
+ quiet += 1;
78349
+ }
78350
+ if (!cancelled) {
78351
+ timer = setTimeout(
78352
+ tick,
78353
+ nextGitPollDelay(quiet, { pollMs, idlePollMs, idleAfterTicks, hot })
78354
+ );
78219
78355
  }
78220
- if (!cancelled) timer = setTimeout(tick, pollMs);
78221
78356
  };
78222
78357
  void tick();
78223
78358
  return () => {
78224
78359
  cancelled = true;
78225
78360
  if (timer !== null) clearTimeout(timer);
78226
78361
  };
78227
- }, [pollMs, cwd]);
78362
+ }, [pollMs, idlePollMs, idleAfterTicks, hot, cwd, snapshot]);
78228
78363
  return changes;
78229
78364
  }
78230
78365
 
@@ -78258,7 +78393,39 @@ init_paths2();
78258
78393
  init_sessionTodos();
78259
78394
  init_krakenLive();
78260
78395
  init_verifyStatus();
78396
+
78397
+ // src/cli/components/statusChips.ts
78261
78398
  init_osJail();
78399
+ init_policyLoadMode();
78400
+ function jailStatusChip(env = process.env, platform = process.platform) {
78401
+ const mode = activeJailMode(env);
78402
+ if (mode === "off") return null;
78403
+ const probe = probeJailBackend(platform);
78404
+ if (!probe.available) {
78405
+ return { label: `jail: advisory (${probe.backend})`, tone: "yellow" };
78406
+ }
78407
+ return mode === "required" ? { label: `jail: on (${probe.backend})`, tone: "green" } : { label: `jail: advisory (${probe.backend})`, tone: "yellow" };
78408
+ }
78409
+ function jailChipKey(env, platform) {
78410
+ return [
78411
+ platform,
78412
+ env[OS_JAIL_ENV] ?? "",
78413
+ env.ZELARI_PERMISSION_PRESET ?? "",
78414
+ env[POLICY_LOAD_MODE_ENV] ?? "",
78415
+ env.CI ?? "",
78416
+ activePolicyLoadSurface()
78417
+ ].join("|");
78418
+ }
78419
+ var chipCache = null;
78420
+ function cachedJailStatusChip(env = process.env, platform = process.platform) {
78421
+ const key = jailChipKey(env, platform);
78422
+ if (chipCache && chipCache.key === key) return chipCache.chip;
78423
+ const chip = jailStatusChip(env, platform);
78424
+ chipCache = { key, chip };
78425
+ return chip;
78426
+ }
78427
+
78428
+ // src/cli/app.tsx
78262
78429
  init_graphStatus();
78263
78430
 
78264
78431
  // packages/core/dist/agents/skills/builtin/debugging.js
@@ -81375,10 +81542,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
81375
81542
  () => void 0,
81376
81543
  () => void 0
81377
81544
  );
81378
- const opsKnowledge = await promoteOpsKnowledgeSafe(gate, {
81379
- projectRoot: process.cwd(),
81380
- sessionId: sessionId2
81381
- });
81545
+ const opsKnowledge = outcomeMemoryAllowed() ? await promoteOpsKnowledgeSafe(gate, { projectRoot: process.cwd(), sessionId: sessionId2 }) : skippedOpsKnowledgeResult("verify-obligation-open");
81382
81546
  const notices = [
81383
81547
  ...opsKnowledge.proposals,
81384
81548
  ...opsKnowledge.checkProposals.map(formatCheckProposalNotice)
@@ -81707,7 +81871,7 @@ _(${clar.context})_` : "") + "\n\u2192 scegli dalla lista oppure digita la rispo
81707
81871
  } catch {
81708
81872
  }
81709
81873
  }
81710
- if (memoryService && memoryAutoWrite && turnSucceeded && assistantContent.trim()) {
81874
+ if (memoryService && memoryAutoWrite && turnSucceeded && assistantContent.trim() && outcomeMemoryAllowed()) {
81711
81875
  try {
81712
81876
  const cleanedOutcome = cleanAgentContent(assistantContent, {
81713
81877
  stripQuestion: true,
@@ -86580,15 +86744,6 @@ function useTerminalSize(options = {}) {
86580
86744
  // src/cli/app.tsx
86581
86745
  init_chatStats();
86582
86746
  init_duration();
86583
- function jailStatusChip() {
86584
- const mode = activeJailMode();
86585
- if (mode === "off") return null;
86586
- const probe = probeJailBackend();
86587
- if (!probe.available) {
86588
- return { label: `jail: advisory (${probe.backend})`, tone: "yellow" };
86589
- }
86590
- return mode === "required" ? { label: `jail: on (${probe.backend})`, tone: "green" } : { label: `jail: advisory (${probe.backend})`, tone: "yellow" };
86591
- }
86592
86747
  var MODEL = process.env.OPENAI_MODEL ?? "grok-4.5";
86593
86748
  var providerDefaults = {
86594
86749
  "openai-compatible": "grok-4.5",
@@ -86600,7 +86755,8 @@ var providerDefaults = {
86600
86755
  "anthropic": "claude-sonnet-4-5"
86601
86756
  };
86602
86757
  function App() {
86603
- const [input, setInput] = useState8("");
86758
+ const [inputDraft] = useState8(() => createInputDraftStore());
86759
+ const setInput = useCallback5((value) => inputDraft.set(value), [inputDraft]);
86604
86760
  const [busy, setBusy] = useState8(false);
86605
86761
  const [providerConfig, setProviderConfig] = useState8(() => getProviderConfig());
86606
86762
  const [sessionStats, setSessionStats] = useState8({
@@ -86628,7 +86784,6 @@ function App() {
86628
86784
  const session = useSession();
86629
86785
  usePermissionBroker({ setPicker: setPicker2, setMessages: session.setMessages });
86630
86786
  const size = useTerminalSize();
86631
- const gitChanges = useGitChanges();
86632
86787
  const cwd = useMemo(() => shortenCwd(process.cwd(), 32), []);
86633
86788
  const timer = useExecutionTimer(busy);
86634
86789
  const { isRawModeSupported } = useStdin2();
@@ -86742,6 +86897,7 @@ function App() {
86742
86897
  useEffect8(() => {
86743
86898
  setSidebarOpen((prev2) => sidebarVisibility(size.columns, size.rows, prev2));
86744
86899
  }, [size.columns, size.rows]);
86900
+ const gitChanges = useGitChanges({ hot: busy || sidebarOpen });
86745
86901
  const staticKey = `${session.sessionId || "pre-bootstrap"}-${clearEpoch}`;
86746
86902
  const staticItems = session.sessionId ? [banner, ...session.messages] : [];
86747
86903
  return /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(Static, { key: staticKey, items: staticItems }, (item) => item.id === "banner-once" ? /* @__PURE__ */ React10.createElement(
@@ -86776,8 +86932,7 @@ function App() {
86776
86932
  ) : /* @__PURE__ */ React10.createElement(
86777
86933
  InputBar,
86778
86934
  {
86779
- value: input,
86780
- onChange: setInput,
86935
+ draft: inputDraft,
86781
86936
  onSubmit: handleSubmit,
86782
86937
  disabled: busy
86783
86938
  }
@@ -86805,7 +86960,7 @@ function App() {
86805
86960
  krakenGraph: formatKrakenGraphSummary() ?? void 0,
86806
86961
  verify: getVerifyChip() ?? void 0,
86807
86962
  permissions: permissionsChip(phase2),
86808
- jail: jailStatusChip()
86963
+ jail: cachedJailStatusChip()
86809
86964
  }
86810
86965
  )), sidebarOpen && /* @__PURE__ */ React10.createElement(Sidebar, { version: VERSION, changes: gitChanges, rows: size.rows })));
86811
86966
  }
@@ -87761,17 +87916,8 @@ function runPreflight() {
87761
87916
  }
87762
87917
  async function backgroundUpdateCheck() {
87763
87918
  if (process.env.ANATHEMA_DEV === "1") return;
87764
- await new Promise((resolve11) => setTimeout(resolve11, 3e3));
87765
- try {
87766
- const { checkForUpdate: checkForUpdate2 } = await Promise.resolve().then(() => (init_updater(), updater_exports));
87767
- const info = await checkForUpdate2();
87768
- if (info.updateAvailable && !info.error) {
87769
- console.error(
87770
- `[zelari-code] \u{1F195} v${info.latestVersion} available (current: v${info.currentVersion}). Run \`zelari-code\` then \`/update --yes\` to upgrade.`
87771
- );
87772
- }
87773
- } catch {
87774
- }
87919
+ const { runDeferredUpdateCheck: runDeferredUpdateCheck2 } = await Promise.resolve().then(() => (init_updater(), updater_exports));
87920
+ await runDeferredUpdateCheck2();
87775
87921
  }
87776
87922
  async function shutdown() {
87777
87923
  try {