@bunshin-cc/bunshin 0.4.0 → 0.4.2

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/bin.js +48 -3
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -879,6 +879,8 @@ function firstHeading(text2) {
879
879
  function applyTextOp(text2, op, legacyAnchor = "") {
880
880
  const anchor = "anchor" in op && op.anchor || legacyAnchor;
881
881
  switch (op.op) {
882
+ case "situation":
883
+ return { ok: true, text: text2 };
882
884
  case "rewrite":
883
885
  return { ok: true, text: op.text };
884
886
  case "create":
@@ -1744,6 +1746,11 @@ ${robot ? `- Values that belong to THIS robot or cell (calibration, pixel column
1744
1746
  - Code that should change (a hard-coded constant, a helper) \u2192 describe it as an append to the
1745
1747
  skill's section, stating the change; reconciliation turns it into a harness patch.` : `- Machine specifics and personal preferences \u2192 your own user layer.`}
1746
1748
  - Never include secrets, credentials or customer data.
1749
+ - A retrieval miss: if a document in the list above would have helped, but the agent did not use
1750
+ it (the user had to point at it, asked why it was not used, or the agent found it only late), add
1751
+ op "situation" to that document: the request as the user actually phrased it, made general (names
1752
+ and ticket ids replaced by what they stand for), e.g. "run me through an e2e test and what it
1753
+ does". Recall learns from it, so the next such request finds the document.
1747
1754
 
1748
1755
  ## Output
1749
1756
 
@@ -1755,7 +1762,8 @@ At most ${max} suggestions. Schema:
1755
1762
  | {"op": "create", "section": {"kind": "skill"|"procedure"|"spec"|"verification"|"rule"|"guardrail"|"reference",
1756
1763
  "title": string, "body": string, "when": string,
1757
1764
  "paths"?: string[], "entities"?: string[], "skills"?: string[]}}
1758
- | {"op": "param", "key": string, "value": string|number|boolean},
1765
+ | {"op": "param", "key": string, "value": string|number|boolean}
1766
+ | {"op": "situation", "text": string},
1759
1767
  "rationale": string, "urgency": "normal"|"urgent"}]}`;
1760
1768
  }
1761
1769
  function extractJson(text2) {
@@ -1797,6 +1805,10 @@ function parseOp(raw) {
1797
1805
  section: { kind, title, body, when: when || title, paths: strList(s.paths), entities: strList(s.entities).map((e) => e.toLowerCase()), skills: strList(s.skills) }
1798
1806
  };
1799
1807
  }
1808
+ case "situation": {
1809
+ const text2 = clean(o.text);
1810
+ return text2 ? { op: "situation", text: text2.slice(0, 500) } : null;
1811
+ }
1800
1812
  case "param": {
1801
1813
  const key = typeof o.key === "string" ? o.key.trim() : "";
1802
1814
  const v = o.value;
@@ -3599,6 +3611,8 @@ function summarize(op) {
3599
3611
  return clip(op.content === null ? `delete ${op.path}` : `file ${op.path}`);
3600
3612
  case "retire":
3601
3613
  return clip(`retire: ${op.reason}`);
3614
+ case "situation":
3615
+ return clip(`also applies to: ${op.text}`);
3602
3616
  }
3603
3617
  }
3604
3618
  function describeTarget(s, view) {
@@ -8037,7 +8051,7 @@ async function handleV2(event, ctx) {
8037
8051
  case "session-start":
8038
8052
  return onSessionStart(ctx);
8039
8053
  case "user-prompt-submit":
8040
- return onPrompt(ctx);
8054
+ return await onPrompt(ctx);
8041
8055
  case "pre-tool-use":
8042
8056
  return onPreToolUse(ctx);
8043
8057
  case "stop":
@@ -8117,12 +8131,28 @@ function liveUpdate(v2, ctx, session) {
8117
8131
  return "";
8118
8132
  }
8119
8133
  }
8134
+ function skillHint(sec, others) {
8135
+ const also = others.length ? ` Also possibly relevant: ${others.map((t) => `"${t}"`).join(", ")}.` : "";
8136
+ return `Bunshin: the \`${sec.skill.name}\` skill may apply: ${sec.skill.description.replace(/\s+/g, " ").trim()} If it does, invoke it (Skill tool, or \`/${sec.skill.name}\`) before doing this by hand.${also}`;
8137
+ }
8138
+ async function remoteAuto(ctx, prompt, exclude) {
8139
+ const config = ctx.store.config();
8140
+ if (!canCall(config) || !config.recall.useMothership) return null;
8141
+ const budget = Math.min(2500, ctx.deadline - Date.now() - 600);
8142
+ if (budget < 300) return null;
8143
+ try {
8144
+ const res = await v2api(config, "POST", "/recall/auto", { prompt: prompt.slice(0, 8e3), exclude }, { timeoutMs: budget });
8145
+ return res.available ? res : null;
8146
+ } catch {
8147
+ return null;
8148
+ }
8149
+ }
8120
8150
  function hintLine(titles, harness) {
8121
8151
  const quoted = titles.map((t) => `"${t}"`).join(", ");
8122
8152
  const how = harness === "claude-code" ? "If one could change your approach, call the bunshin_recall tool (describe the task abstractly) and load only what you need." : 'If one could change your approach, run `bunshin recall "<the task, abstractly>" --hypothetical "<the lesson you would want>"` and `bunshin load <id>`.';
8123
8153
  return `Bunshin: possibly relevant library sections: ${quoted}. ${how}`;
8124
8154
  }
8125
- function onPrompt(ctx) {
8155
+ async function onPrompt(ctx) {
8126
8156
  const prompt = ctx.input.prompt ?? "";
8127
8157
  if (prompt.trimStart().startsWith(REFLECT_MARKER)) return {};
8128
8158
  const v2 = new V2Store(ctx.store);
@@ -8143,6 +8173,19 @@ function onPrompt(ctx) {
8143
8173
  const view = localView(v2, config, ctx.cwd);
8144
8174
  const injected = [...session.loaded, ...session.pushed];
8145
8175
  const res = auto(view.sections, view.reader, prompt, { exclude: injected });
8176
+ const remote = await remoteAuto(ctx, prompt, injected);
8177
+ if (remote) {
8178
+ const local = (id) => view.sections.find((s) => s.id === id);
8179
+ if (remote.section) {
8180
+ res.section = local(remote.section.id) ?? remote.section;
8181
+ res.anchor = void 0;
8182
+ res.reason = `dense ${remote.reason ?? ""}`.trim();
8183
+ } else {
8184
+ res.section = void 0;
8185
+ res.hint = (remote.hint ?? []).map((h) => ({ id: h.id, title: local(h.id)?.title ?? h.title }));
8186
+ res.reason = `dense ${remote.reason ?? ""}`.trim();
8187
+ }
8188
+ }
8146
8189
  if (res.section) {
8147
8190
  const sec = res.section;
8148
8191
  v2.updateSession(session.sessionId, ctx.cwd, (s) => {
@@ -8159,6 +8202,8 @@ function onPrompt(ctx) {
8159
8202
  addUnique(s.hinted, ...fresh.map((h) => h.id));
8160
8203
  addUnique(s.served, ...fresh.map((h) => h.id));
8161
8204
  });
8205
+ const topSkill = view.sections.find((s) => s.id === fresh[0].id && s.skill);
8206
+ if (topSkill && deployEnabled(config, view.ctx)) return withLive(additional("UserPromptSubmit", skillHint(topSkill, fresh.slice(1).map((h) => h.title))));
8162
8207
  return withLive(additional("UserPromptSubmit", hintLine(fresh.map((h) => h.title), config.harness)));
8163
8208
  }
8164
8209
  function toolPaths(input) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bunshin-cc/bunshin",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Shared memory for company AI agents. Connect Codex, Claude Code, or Pi to Bunshin.",
5
5
  "type": "module",
6
6
  "bin": {