@bragi-gmbh/codebus 1.2.0

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 (133) hide show
  1. package/bin/codebus.js +2 -0
  2. package/dist/binding-guard.js +97 -0
  3. package/dist/binding-guard.js.map +1 -0
  4. package/dist/client.js +94 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/commands/ack.js +47 -0
  7. package/dist/commands/ack.js.map +1 -0
  8. package/dist/commands/archive.js +21 -0
  9. package/dist/commands/archive.js.map +1 -0
  10. package/dist/commands/as.js +299 -0
  11. package/dist/commands/as.js.map +1 -0
  12. package/dist/commands/ask.js +189 -0
  13. package/dist/commands/ask.js.map +1 -0
  14. package/dist/commands/auth.js +33 -0
  15. package/dist/commands/auth.js.map +1 -0
  16. package/dist/commands/checkin.js +235 -0
  17. package/dist/commands/checkin.js.map +1 -0
  18. package/dist/commands/decision.js +52 -0
  19. package/dist/commands/decision.js.map +1 -0
  20. package/dist/commands/digest.js +117 -0
  21. package/dist/commands/digest.js.map +1 -0
  22. package/dist/commands/doctor.js +106 -0
  23. package/dist/commands/doctor.js.map +1 -0
  24. package/dist/commands/factory.js +191 -0
  25. package/dist/commands/factory.js.map +1 -0
  26. package/dist/commands/github-login.js +205 -0
  27. package/dist/commands/github-login.js.map +1 -0
  28. package/dist/commands/graph.js +88 -0
  29. package/dist/commands/graph.js.map +1 -0
  30. package/dist/commands/health.js +34 -0
  31. package/dist/commands/health.js.map +1 -0
  32. package/dist/commands/inbox.js +83 -0
  33. package/dist/commands/inbox.js.map +1 -0
  34. package/dist/commands/ingest.js +553 -0
  35. package/dist/commands/ingest.js.map +1 -0
  36. package/dist/commands/init.js +97 -0
  37. package/dist/commands/init.js.map +1 -0
  38. package/dist/commands/install.js +520 -0
  39. package/dist/commands/install.js.map +1 -0
  40. package/dist/commands/invite.js +152 -0
  41. package/dist/commands/invite.js.map +1 -0
  42. package/dist/commands/jira.js +237 -0
  43. package/dist/commands/jira.js.map +1 -0
  44. package/dist/commands/join.js +96 -0
  45. package/dist/commands/join.js.map +1 -0
  46. package/dist/commands/links.js +66 -0
  47. package/dist/commands/links.js.map +1 -0
  48. package/dist/commands/login.js +84 -0
  49. package/dist/commands/login.js.map +1 -0
  50. package/dist/commands/morning.js +121 -0
  51. package/dist/commands/morning.js.map +1 -0
  52. package/dist/commands/next.js +66 -0
  53. package/dist/commands/next.js.map +1 -0
  54. package/dist/commands/onboarding.js +109 -0
  55. package/dist/commands/onboarding.js.map +1 -0
  56. package/dist/commands/pack.js +276 -0
  57. package/dist/commands/pack.js.map +1 -0
  58. package/dist/commands/plan-gh.js +425 -0
  59. package/dist/commands/plan-gh.js.map +1 -0
  60. package/dist/commands/plan.js +465 -0
  61. package/dist/commands/plan.js.map +1 -0
  62. package/dist/commands/pr.js +88 -0
  63. package/dist/commands/pr.js.map +1 -0
  64. package/dist/commands/question.js +142 -0
  65. package/dist/commands/question.js.map +1 -0
  66. package/dist/commands/read.js +84 -0
  67. package/dist/commands/read.js.map +1 -0
  68. package/dist/commands/refs.js +357 -0
  69. package/dist/commands/refs.js.map +1 -0
  70. package/dist/commands/reply.js +38 -0
  71. package/dist/commands/reply.js.map +1 -0
  72. package/dist/commands/repo.js +1198 -0
  73. package/dist/commands/repo.js.map +1 -0
  74. package/dist/commands/report.js +41 -0
  75. package/dist/commands/report.js.map +1 -0
  76. package/dist/commands/resolve-refs.js +21 -0
  77. package/dist/commands/resolve-refs.js.map +1 -0
  78. package/dist/commands/resolve.js +93 -0
  79. package/dist/commands/resolve.js.map +1 -0
  80. package/dist/commands/send.js +168 -0
  81. package/dist/commands/send.js.map +1 -0
  82. package/dist/commands/sent.js +22 -0
  83. package/dist/commands/sent.js.map +1 -0
  84. package/dist/commands/session.js +426 -0
  85. package/dist/commands/session.js.map +1 -0
  86. package/dist/commands/statusline.js +43 -0
  87. package/dist/commands/statusline.js.map +1 -0
  88. package/dist/commands/task.js +140 -0
  89. package/dist/commands/task.js.map +1 -0
  90. package/dist/commands/tick.js +233 -0
  91. package/dist/commands/tick.js.map +1 -0
  92. package/dist/commands/upgrade.js +76 -0
  93. package/dist/commands/upgrade.js.map +1 -0
  94. package/dist/commands/watch.js +169 -0
  95. package/dist/commands/watch.js.map +1 -0
  96. package/dist/commands/whoami.js +27 -0
  97. package/dist/commands/whoami.js.map +1 -0
  98. package/dist/config.js +103 -0
  99. package/dist/config.js.map +1 -0
  100. package/dist/directives.js +102 -0
  101. package/dist/directives.js.map +1 -0
  102. package/dist/errors.js +110 -0
  103. package/dist/errors.js.map +1 -0
  104. package/dist/git-hooks.js +116 -0
  105. package/dist/git-hooks.js.map +1 -0
  106. package/dist/graph-bootstrap.js +95 -0
  107. package/dist/graph-bootstrap.js.map +1 -0
  108. package/dist/graph-remote.js +889 -0
  109. package/dist/graph-remote.js.map +1 -0
  110. package/dist/graph.js +335 -0
  111. package/dist/graph.js.map +1 -0
  112. package/dist/index.js +154 -0
  113. package/dist/index.js.map +1 -0
  114. package/dist/kit/graph-publish.mjs +400 -0
  115. package/dist/mail-latency.js +120 -0
  116. package/dist/mail-latency.js.map +1 -0
  117. package/dist/mcp-registry.js +859 -0
  118. package/dist/mcp-registry.js.map +1 -0
  119. package/dist/mcp.js +193 -0
  120. package/dist/mcp.js.map +1 -0
  121. package/dist/output.js +12 -0
  122. package/dist/output.js.map +1 -0
  123. package/dist/positioning.js +21 -0
  124. package/dist/positioning.js.map +1 -0
  125. package/dist/rolestore.js +89 -0
  126. package/dist/rolestore.js.map +1 -0
  127. package/dist/session.js +49 -0
  128. package/dist/session.js.map +1 -0
  129. package/dist/sessionstore.js +736 -0
  130. package/dist/sessionstore.js.map +1 -0
  131. package/dist/version-check.js +97 -0
  132. package/dist/version-check.js.map +1 -0
  133. package/package.json +47 -0
package/dist/config.js ADDED
@@ -0,0 +1,103 @@
1
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { CodebusError, DeadSessionError } from "./errors.js";
5
+ import { boundIdentity } from "./rolestore.js";
6
+ import { resolveSession } from "./sessionstore.js";
7
+ // Paths are computed per call so HOME overrides (tests, sandboxes) are honored.
8
+ export const configDir = () => join(homedir(), ".codebus");
9
+ export const configFile = () => join(configDir(), "config.json");
10
+ // Legacy constant exports (display use); prefer the functions above.
11
+ export const CONFIG_DIR = configDir();
12
+ export const CONFIG_FILE = configFile();
13
+ export function loadFileConfig() {
14
+ try {
15
+ return JSON.parse(readFileSync(configFile(), "utf8"));
16
+ }
17
+ catch {
18
+ return {};
19
+ }
20
+ }
21
+ export function saveFileConfig(patch) {
22
+ const merged = { ...loadFileConfig(), ...patch };
23
+ mkdirSync(configDir(), { recursive: true });
24
+ writeFileSync(configFile(), JSON.stringify(merged, null, 2) + "\n", { mode: 0o600 });
25
+ return merged;
26
+ }
27
+ export function resolveConfig(o, env = process.env) {
28
+ const file = loadFileConfig();
29
+ const url = o.url ?? env.CODEBUS_URL ?? file.url;
30
+ // Identity precedence: flags > env > THIS SESSION's checkin > cwd role
31
+ // binding > config file.
32
+ // The binding tier is what makes `codebus as --bind` in a directory MEAN
33
+ // that identity there: without it, tick (binding-aware) previews the
34
+ // role's mail while inbox/read/reply (file-fallback) silently act as the
35
+ // AUTHOR — the checked-in agent reads the wrong inbox and concludes its
36
+ // mail doesn't exist (observed live, first Stop-hook field test).
37
+ // The SESSION tier above it is what lets N agents share one repository
38
+ // (researcher/planner/coder/verifier on the same checkout): a dir has one
39
+ // binding slot, but each running session has its own durable identity, and
40
+ // a checkin binds THAT (sessionstore.ts) without touching the dir.
41
+ // Explicit assume (flag/env, e.g. from `codebus as` running an {assume}
42
+ // entry) suppresses inference exactly like an explicit token does: the
43
+ // caller has already named the identity.
44
+ // noRoleBinding (author mode, e.g. `codebus checkin`) drops an AMBIENT
45
+ // CODEBUS_TOKEN too, not just the agent/assume pins: a stray env pin from
46
+ // the shell must not let checkin mint or rebind a role under the wrong
47
+ // author (field incident, FM-6). An explicit --token flag still wins —
48
+ // that's the caller deliberately naming a credential, not ambient leakage.
49
+ const explicitAssume = o.noRoleBinding ? undefined : (o.assume ?? env.CODEBUS_ASSUME);
50
+ const ambientToken = o.noRoleBinding ? undefined : env.CODEBUS_TOKEN;
51
+ const inferAllowed = !o.token && !ambientToken && !explicitAssume && !o.noRoleBinding;
52
+ // Dead-tier rule (§2.4 precedent 3): a session-tier entry that EXISTED for
53
+ // this session but no longer resolves to a usable credential must stop
54
+ // resolution outright — never fall through to the directory-binding tier
55
+ // (or further, to the file). "No entry at all" (this session was simply
56
+ // never checked in) is the normal "none" case and dir inference proceeds.
57
+ const sessionRes = inferAllowed ? resolveSession() : { status: "none" };
58
+ if (sessionRes.status === "dead")
59
+ throw new DeadSessionError(sessionRes.role);
60
+ const session = sessionRes.status === "ok" ? sessionRes : null;
61
+ const bound = inferAllowed && !session ? boundIdentity(process.cwd()) : null;
62
+ if (session) {
63
+ process.stderr.write(`codebus: acting as ${session.role} (this session's checkin${session.entry.assume ? ", assumed via author credential" : ""})\n`);
64
+ }
65
+ else if (bound) {
66
+ // Inferred identity is always announced — silently acting as an inferred
67
+ // agent is how you act as the wrong one without noticing. stderr, so
68
+ // piped/JSON stdout stays clean.
69
+ process.stderr.write(`codebus: acting as ${bound.role} (cwd binding '${bound.pattern}'${bound.assume ? ", assumed via author credential" : ""})\n`);
70
+ }
71
+ // An {assume} binding stores no credential: the wire token stays the
72
+ // AUTHOR's (config.json) and the auth edge function mints the session AS
73
+ // the role after checking ownership. A raw-token binding never assumes.
74
+ const token = o.token ?? ambientToken ?? session?.entry.token ?? bound?.token ?? file.token;
75
+ const assume = explicitAssume ?? session?.entry.assume ?? bound?.assume;
76
+ const tokenFromFile = !o.token && !ambientToken && !session && !bound && !!file.token;
77
+ const workspace = o.workspace ?? env.CODEBUS_WORKSPACE ?? file.workspace ?? "default";
78
+ const anonKey = o.anonKey ?? env.SUPABASE_ANON_KEY ?? file.anonKey;
79
+ if (assume && !token) {
80
+ throw new CodebusError(`${bound ? `binding '${bound.pattern}'` : "this session"} assumes role '${assume}' with your ` +
81
+ `author credential, but there is none — run \`codebus login\` first.`);
82
+ }
83
+ if (!token)
84
+ throw new CodebusError("no token — run `codebus login --token <token>` (or set CODEBUS_TOKEN).");
85
+ if (!url)
86
+ throw new CodebusError("no server URL — run `codebus init` (or set CODEBUS_URL).");
87
+ // file.agent describes the FILE's token; never pin it onto a flag/env token,
88
+ // or acting as another agent via CODEBUS_TOKEN would fail the identity check.
89
+ // A bound role's agent IS pinned: the store recorded which agent that token
90
+ // authenticates as, so a mismatch should fail loudly (as --set verified it).
91
+ // When assuming, the session authenticates as the ASSUMED slug (never as the
92
+ // file token's author), so that is what gets pinned.
93
+ // Author mode pins NOTHING: the server decides authorization (person-linked
94
+ // check), and the file's agent label is known to drift from its token's true
95
+ // owner (observed: label said nihviid-default, token authenticated as
96
+ // nihviid-web) — a stale label must not be able to fail checkin.
97
+ const agent = o.noRoleBinding
98
+ ? undefined
99
+ : (o.agent ?? env.CODEBUS_AGENT ?? (assume ?? (session ? session.role : bound ? bound.role : tokenFromFile ? file.agent : undefined)));
100
+ const identitySource = o.token || ambientToken || explicitAssume ? "explicit" : session ? "session" : bound ? "cwd-binding" : tokenFromFile ? "file" : "none";
101
+ return { url, token, agent, workspace, anonKey, assume, identitySource };
102
+ }
103
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ // Agent-facing directives, shared so every surface that delivers mail teaches
2
+ // the SAME behavior. Field incident (2026-08-13): agents in the standing watch
3
+ // loop received work and replied "acknowledged" without doing anything — the
4
+ // event line said WHAT arrived but no surface said what receiving it MEANS.
5
+ // The rule, everywhere mail lands: a message that ASKS something of you is a
6
+ // work order — do the work and reply with the outcome; a bare acknowledgement
7
+ // is never a substitute for that.
8
+ //
9
+ // RC2 refinement (2026-08-20): "asks something of you" is the operative test,
10
+ // not "is addressed to you". CodeBus already draws this line in the data model
11
+ // and the CLI: an ask/handoff (or any message with required_action set) is a
12
+ // work order; a plain note/plan is FYI. And `codebus ack` has ALWAYS been the
13
+ // lawful close "for mail that needed no reply (an FYI, a broadcast note)"
14
+ // (ack.ts) — so FYI mail requiring no outcome reply is pre-existing contract,
15
+ // not a new escape hatch. Delivery surfaces still guarantee mail is never
16
+ // silently unseen; they simply tell the truth about which class arrived, so a
17
+ // firehose of notes can't drown the actual asks (the field incident that
18
+ // motivated this: a fresh agent facing 500 unread, 501 notes / 4 asks).
19
+ // Two flavors, not one: a message and a task are followed up with different
20
+ // commands (`codebus read <id>` is meaningless for a task id — AI-review
21
+ // finding on this PR, since a shared directive text had been applying the
22
+ // message wording to both). Both share the same "ACT NOW" framing.
23
+ // Message flavor — used verbatim by surfaces (MCP tools) that have no CLI
24
+ // re-arm step of their own; codebus_inbox only ever returns messages, so
25
+ // this is also its exported name.
26
+ export const ACT_DIRECTIVE_CORE = "ACT NOW — this is a work order, not a notification. Read the full message " +
27
+ "(codebus read <id>), then do what it asks in this session: execute the work, " +
28
+ "answer the question with substance, or claim the task and start it. Do NOT " +
29
+ "reply with a bare acknowledgement — reply when you have the outcome, or a " +
30
+ "concrete blocker plus what you need to clear it. This is delivered OLDEST " +
31
+ "unhandled mail first — if more is pending, run codebus inbox --unread " +
32
+ "afterward and act on the rest that are work orders before re-arming; plain " +
33
+ "FYI notes among them need only a skim or `codebus ack <id>`, not a reply.";
34
+ export const TASK_ACT_DIRECTIVE_CORE = "ACT NOW — this is a work order, not a notification. Claim it and start the " +
35
+ "work in this session (codebus task claim <id>), then finish with " +
36
+ "codebus task done <id> --evidence <ref>. Do NOT reply with a bare " +
37
+ "acknowledgement — report the outcome, or a concrete blocker plus what you " +
38
+ "need to clear it.";
39
+ // The CLI standing-loop variants: same rules, plus the re-arm step that only
40
+ // applies to a `watch` invocation. Fire on every event watch delivers, not
41
+ // only under --first (AI-review finding: plain `watch --mine` streamed bare
42
+ // event lines forever with no instruction at all).
43
+ const REARM = "Then re-arm: codebus watch --mine --first --timeout 280000";
44
+ export const ACT_DIRECTIVE = `${ACT_DIRECTIVE_CORE} ${REARM}`;
45
+ export const TASK_ACT_DIRECTIVE = `${TASK_ACT_DIRECTIVE_CORE} ${REARM}`;
46
+ // RC2 field incident (2026-08-20): a freshly checked-in agent walked into an
47
+ // inbox of 507 inbound / 500 unread / 504 unhandled, of which 501 were
48
+ // type=note with required_action NULL and only 4 were type=ask. Senders
49
+ // routinely send actionable requests as bare `note`s — every "needs a reply"
50
+ // surface (bus_digest's message class, `inbox --unread` counts) keys off
51
+ // type/required_action, not prose, so a work order sent as a note is
52
+ // structurally indistinguishable from FYI chatter on every one of those
53
+ // surfaces. This is the sender-side half of the fix: a PROSE heuristic that
54
+ // flags a `note` whose body/subject reads like a request, so the CLI/MCP
55
+ // layer can nudge the sender toward `--type ask`/`--type handoff` or
56
+ // `--required-action` — never a hard block (type is sender-declared per
57
+ // DEC-CB-004(B); this module infers nothing, it only advises).
58
+ const REQUEST_PHRASES = /\b(please|can you|could you|would you|need (?:you|u) to|make sure (?:you|to)|you (?:should|must|need to)|action required|action item|let me know|waiting on you|blocked on you|review (?:this|the|it)|reply (?:with|when)|respond (?:with|when)|asap)\b/i;
59
+ /** True when `text` reads like a request/work-order rather than plain FYI prose. */
60
+ export function looksActionable(text) {
61
+ const t = (text ?? "").trim();
62
+ if (!t)
63
+ return false;
64
+ if (/\?\s*$/.test(t))
65
+ return true; // a trailing question mark expects an answer
66
+ return REQUEST_PHRASES.test(t);
67
+ }
68
+ // Reader-side mirror of the same rule (kept in lockstep with bus_digest's
69
+ // class-7/class-8 split, supabase/migrations/0061_actionable_messages.sql):
70
+ // a message is actionable — needs a reply, not just FYI — when it's typed
71
+ // ask/handoff, or ANY type carries an explicit required_action. Used by
72
+ // `codebus inbox` (which reads message_recipients directly, not through
73
+ // bus_digest) to print a needs-reply/FYI split alongside the raw count.
74
+ export function isActionableMessage(m) {
75
+ if (m.type === "ask" || m.type === "handoff")
76
+ return true;
77
+ return !!(m.required_action && m.required_action.trim().length > 0);
78
+ }
79
+ export const NOTE_ACTIONABLE_WARNING = "this reads like a work order sent as a bare note — 'note' is FYI-only and " +
80
+ "will NOT surface as unhandled work on any actionable-mail surface " +
81
+ "(bus_digest, inbox --unread counts). If you need a reply or action, resend " +
82
+ "with --type ask (or --type handoff), and/or --required-action \"<what the " +
83
+ "recipient owes>\".";
84
+ // FYI directive (0061 — RC2) — review finding: every delivery surface that
85
+ // used to hand back ACT_DIRECTIVE_CORE for "you have unread mail" did so
86
+ // regardless of what the mail actually was, so a class-8 FYI note (unread,
87
+ // but nothing owed) got the SAME "ACT NOW — this is a work order... do the
88
+ // work it asks or answer with substance" instruction as a genuine ask. That
89
+ // directly contradicts bus_digest's own class-7/8 split. This is the counter-
90
+ // part for mail that is NOT actionable (isActionableMessage() is false):
91
+ // still tells the reader something arrived (never silent — mail must not go
92
+ // unseen), but never frames it as a mandatory work order. Used by
93
+ // mcp.ts (codebus_inbox, when every unread item is FYI-only) and watch.ts
94
+ // (per-event, when the specific message/backlog item isn't actionable).
95
+ export const FYI_DIRECTIVE_CORE = "Delivered mail — read it (codebus read <id>); do not leave it unseen. This is " +
96
+ "plain note/plan mail (no ask/handoff type, no required_action set), so it asks " +
97
+ "nothing of you: once read, codebus ack <id> to clear it — the same lawful close " +
98
+ "as a reply, for mail that needs no answer. No outcome reply is required. But if " +
99
+ "on reading it DOES ask something of you, treat it as a work order: do the work " +
100
+ "and reply with the outcome — a bare acknowledgement never counts.";
101
+ export const FYI_DIRECTIVE = `${FYI_DIRECTIVE_CORE} ${REARM}`;
102
+ //# sourceMappingURL=directives.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directives.js","sourceRoot":"","sources":["../src/directives.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,+EAA+E;AAC/E,6EAA6E;AAC7E,4EAA4E;AAC5E,6EAA6E;AAC7E,8EAA8E;AAC9E,kCAAkC;AAClC,EAAE;AACF,8EAA8E;AAC9E,+EAA+E;AAC/E,6EAA6E;AAC7E,8EAA8E;AAC9E,0EAA0E;AAC1E,8EAA8E;AAC9E,0EAA0E;AAC1E,8EAA8E;AAC9E,yEAAyE;AACzE,wEAAwE;AAExE,4EAA4E;AAC5E,yEAAyE;AACzE,0EAA0E;AAC1E,mEAAmE;AAEnE,0EAA0E;AAC1E,yEAAyE;AACzE,kCAAkC;AAClC,MAAM,CAAC,MAAM,kBAAkB,GAC7B,4EAA4E;IAC5E,+EAA+E;IAC/E,6EAA6E;IAC7E,4EAA4E;IAC5E,4EAA4E;IAC5E,wEAAwE;IACxE,6EAA6E;IAC7E,2EAA2E,CAAC;AAE9E,MAAM,CAAC,MAAM,uBAAuB,GAClC,6EAA6E;IAC7E,mEAAmE;IACnE,oEAAoE;IACpE,4EAA4E;IAC5E,mBAAmB,CAAC;AAEtB,6EAA6E;AAC7E,2EAA2E;AAC3E,4EAA4E;AAC5E,mDAAmD;AACnD,MAAM,KAAK,GAAG,4DAA4D,CAAC;AAC3E,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,kBAAkB,IAAI,KAAK,EAAE,CAAC;AAC9D,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,uBAAuB,IAAI,KAAK,EAAE,CAAC;AAExE,6EAA6E;AAC7E,uEAAuE;AACvE,wEAAwE;AACxE,6EAA6E;AAC7E,yEAAyE;AACzE,qEAAqE;AACrE,wEAAwE;AACxE,4EAA4E;AAC5E,yEAAyE;AACzE,qEAAqE;AACrE,wEAAwE;AACxE,+DAA+D;AAC/D,MAAM,eAAe,GACnB,0PAA0P,CAAC;AAE7P,oFAAoF;AACpF,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrB,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,6CAA6C;IAChF,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjC,CAAC;AAED,0EAA0E;AAC1E,4EAA4E;AAC5E,0EAA0E;AAC1E,wEAAwE;AACxE,wEAAwE;AACxE,wEAAwE;AACxE,MAAM,UAAU,mBAAmB,CAAC,CAA4D;IAC9F,IAAI,CAAC,CAAC,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC1D,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,IAAI,CAAC,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,CAAC,MAAM,uBAAuB,GAClC,4EAA4E;IAC5E,oEAAoE;IACpE,6EAA6E;IAC7E,4EAA4E;IAC5E,oBAAoB,CAAC;AAEvB,2EAA2E;AAC3E,yEAAyE;AACzE,2EAA2E;AAC3E,2EAA2E;AAC3E,4EAA4E;AAC5E,8EAA8E;AAC9E,yEAAyE;AACzE,4EAA4E;AAC5E,kEAAkE;AAClE,0EAA0E;AAC1E,wEAAwE;AACxE,MAAM,CAAC,MAAM,kBAAkB,GAC7B,gFAAgF;IAChF,iFAAiF;IACjF,kFAAkF;IAClF,kFAAkF;IAClF,iFAAiF;IACjF,mEAAmE,CAAC;AACtE,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,kBAAkB,IAAI,KAAK,EAAE,CAAC"}
package/dist/errors.js ADDED
@@ -0,0 +1,110 @@
1
+ export class CodebusError extends Error {
2
+ exitCode;
3
+ constructor(message, exitCode = 1) {
4
+ super(message);
5
+ this.exitCode = exitCode;
6
+ this.name = "CodebusError";
7
+ }
8
+ }
9
+ /**
10
+ * Round-13 review finding 8 (CONFIRMED, MAJOR): `repo connect --build`'s
11
+ * previous revision embedded the freshly-minted plaintext ingest token
12
+ * directly in a thrown `CodebusError`'s `message` — the ONE thing every
13
+ * generic error handler in this codebase (`cli/src/index.ts`'s top-level
14
+ * `catch`) prints verbatim to stderr on any uncaught failure. That handler
15
+ * exists precisely to be generic (it has no idea a failure might be
16
+ * carrying a secret), so the secret ended up in stderr — exactly the kind
17
+ * of captured-log/CI-log exposure `--json` mode's "token only inside the
18
+ * one JSON object" contract was designed to prevent for the SUCCESS path,
19
+ * now silently reappearing on a failure path via a different channel.
20
+ *
21
+ * Round-14 review finding 4 (CONFIRMED, MAJOR): the original fix only
22
+ * covered the `--build` failure sites, named `BuildFailedWithTokenError`.
23
+ * But `repoConnectFull`'s own doc already establishes that ANY step
24
+ * AFTER a successful mint (currently: the `ci_wired` transition, then
25
+ * `--build`) risks the identical loss on failure — a transition failure
26
+ * was still throwing a plain, token-free `CodebusError`, silently
27
+ * discarding the operator's only copy exactly like the original bug did,
28
+ * just one step earlier in the sequence. Renamed to `PostMintFailureError`
29
+ * and now used by EVERY throw site between a successful mint and the
30
+ * point `repoConnectFull` returns normally — not enumerated by name here
31
+ * (that list is expected to grow if more post-mint steps are ever added;
32
+ * the rule is positional — "after mint, before return" — not a fixed
33
+ * enumeration of today's two steps) — so a truly generic
34
+ * `catch (e) { console.error(e.message) }` is safe by construction no
35
+ * matter which post-mint step actually failed.
36
+ *
37
+ * `message` never contains the token. The one caller that needs it
38
+ * (`repo.ts`'s own connect-command catch, via `handleConnectError`) checks
39
+ * for this specific class, renders the token deliberately (once, clearly
40
+ * marked, matching the print-once discipline `printConnectResult` already
41
+ * established for the success path), and re-throws a plain, token-free
42
+ * `CodebusError` with the same message for the generic handler to print
43
+ * as usual.
44
+ *
45
+ * Round-15 review finding 2 (CONFIRMED, MINOR): `message` used to be a
46
+ * hand-written string composed at each throw site, and `handleConnectError`
47
+ * additionally hardcoded its OWN generic "--build failed" framing on top of
48
+ * that — accurate for the `--build` throw sites, but wrong for the
49
+ * transition-failure site (round-14 finding 4 added), which fails before
50
+ * any build ever starts. `stage` now names which post-mint step actually
51
+ * failed (`'transition'` | `'build'`), and both `handleConnectError` and
52
+ * this class's own `message` are built from it, so the rendered diagnostic
53
+ * always names the step that actually failed instead of assuming it was
54
+ * always `--build`.
55
+ */
56
+ export class PostMintFailureError extends CodebusError {
57
+ token;
58
+ stage;
59
+ constructor(message, token, stage) {
60
+ super(message);
61
+ this.token = token;
62
+ this.stage = stage;
63
+ this.name = "PostMintFailureError";
64
+ }
65
+ }
66
+ /**
67
+ * The session-tier dead-tier rule (docs/plans/codebus-v2-reliability.md §2.4
68
+ * precedent 3): a session-tier entry existed (this session WAS checked in)
69
+ * but the role it points at has no usable credential anymore. Resolution
70
+ * stops here — it must never fall through to the directory-binding tier, or
71
+ * further, to the config file. A distinct class (rather than a message
72
+ * substring) so callers that need to tell "an identity died, say so loudly"
73
+ * apart from "there was never any identity here, stay quiet" (tick.ts's
74
+ * hook-mode contract) can do it structurally.
75
+ */
76
+ export class DeadSessionError extends CodebusError {
77
+ role;
78
+ constructor(role) {
79
+ super(`no identity — this session checked in as '${role}', but that role's credential is gone ` +
80
+ `(forgotten via \`codebus as --forget\`, rotated, or never fully stored). Re-checkin: codebus checkin <name>`);
81
+ this.role = role;
82
+ this.name = "DeadSessionError";
83
+ }
84
+ }
85
+ /**
86
+ * The session store (~/.codebus/session-roles.json) exists but could not be
87
+ * trusted: unreadable for a reason other than "doesn't exist yet" (ENOENT),
88
+ * truncated/corrupt JSON, an unexpected shape, or a lock that could not be
89
+ * acquired. This is DELIBERATELY not the same as "no session-tier entry" —
90
+ * that "none" case is what lets a never-checked-in session fall through to
91
+ * the directory-binding tier. A store we can't parse might well have HAD a
92
+ * durable entry for this exact session before it got mangled (a truncated
93
+ * write, a half-finished editor save); treating that as "none" would let
94
+ * resolution silently fall through to a cwd binding and hand this session a
95
+ * DIFFERENT agent's identity — the exact foreign-identity takeover the
96
+ * session tier exists to prevent (dead-tier rule, §2.4 precedent 3). So an
97
+ * unreadable store fails closed: loud in plain mode, and in --hook mode the
98
+ * existing swallow-everything wrapper still applies, but it swallows to
99
+ * SILENCE (never resolves), never to an inferred fallback identity.
100
+ */
101
+ export class SessionStoreError extends CodebusError {
102
+ constructor(reason) {
103
+ super(`session store unreadable (${reason}) — refusing to guess this session's identity from a possibly-` +
104
+ `corrupt ~/.codebus/session-roles.json. Falling back to a directory binding here could hand this ` +
105
+ `session a DIFFERENT agent's identity. Restore the file from backup, or remove it if it truly cannot ` +
106
+ `be recovered (this session will re-checkin from scratch), then retry.`);
107
+ this.name = "SessionStoreError";
108
+ }
109
+ }
110
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,YAAa,SAAQ,KAAK;IACD;IAApC,YAAY,OAAe,EAAS,WAAW,CAAC;QAC9C,KAAK,CAAC,OAAO,CAAC,CAAC;QADmB,aAAQ,GAAR,QAAQ,CAAI;QAE9C,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,MAAM,OAAO,oBAAqB,SAAQ,YAAY;IAChB;IAAsB;IAA1D,YAAY,OAAe,EAAS,KAAa,EAAS,KAA6B;QACrF,KAAK,CAAC,OAAO,CAAC,CAAC;QADmB,UAAK,GAAL,KAAK,CAAQ;QAAS,UAAK,GAAL,KAAK,CAAwB;QAErF,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,gBAAiB,SAAQ,YAAY;IAC7B;IAAnB,YAAmB,IAAY;QAC7B,KAAK,CACH,6CAA6C,IAAI,wCAAwC;YACzF,6GAA6G,CAC9G,CAAC;QAJe,SAAI,GAAJ,IAAI,CAAQ;QAK7B,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,iBAAkB,SAAQ,YAAY;IACjD,YAAY,MAAc;QACxB,KAAK,CACH,6BAA6B,MAAM,gEAAgE;YACnG,kGAAkG;YAClG,sGAAsG;YACtG,uEAAuE,CACxE,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF"}
@@ -0,0 +1,116 @@
1
+ import { existsSync, readFileSync, writeFileSync, chmodSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ // CodeBus marker: used to identify hooks we installed (avoid clobbering user hooks)
4
+ const CODEBUS_MARKER = "# CODEBUS-INSTALLED";
5
+ /**
6
+ * Generate the post-merge/post-commit hook script that triggers incremental graphify updates.
7
+ * The hook:
8
+ * - Detaches immediately (async, non-blocking)
9
+ * - Uses a lock file to defer concurrent triggers (no stacking)
10
+ * - Time-caps the graphify build (timeout)
11
+ * - Runs with nice to limit CPU impact
12
+ * - Includes the CODEBUS_MARKER for idempotent identification
13
+ *
14
+ * @returns the bash script content
15
+ */
16
+ export function generateGitHookScript() {
17
+ return `#!/bin/bash
18
+ ${CODEBUS_MARKER}
19
+ # Auto-generated by CodeBus: local graph freshness hook
20
+ # Triggers incremental graphify update on post-merge and post-commit
21
+ # ADVISORY ONLY: always exits 0 to never block git operations.
22
+
23
+ REPO_ROOT="\$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
24
+ LOCK_FILE="\${REPO_ROOT}/.codebus/hooks.lock"
25
+ LOCK_TIMEOUT=60 # seconds
26
+
27
+ # Ensure lock directory exists
28
+ mkdir -p "\$(dirname "\$LOCK_FILE")" 2>/dev/null || true
29
+
30
+ # Acquire or defer: if another build is already running, exit silently
31
+ exec 9>"$LOCK_FILE" 2>/dev/null || true
32
+ if ! flock -n 9 2>/dev/null; then
33
+ # Another process holds the lock; defer by exiting
34
+ exit 0
35
+ fi
36
+
37
+ # Non-blocking detach: fork, disown, and return immediately
38
+ (
39
+ # Release the lock in the parent's FD before forking the actual work
40
+ exec 9>&- 2>/dev/null || true
41
+
42
+ # Now in the child process; run the actual build with time cap and nice
43
+ # Redirect all output to /dev/null so failures don't spam the user.
44
+ (
45
+ cd "\$REPO_ROOT" 2>/dev/null || exit 1
46
+ # Timeout 30s, niced to -10 (low priority)
47
+ # Build failure is silently swallowed (failure containment).
48
+ timeout 30 nice -n 10 graphify update . > /dev/null 2>&1
49
+ ) &
50
+
51
+ # Child forks another child and exits immediately (double fork for true detach)
52
+ disown 2>/dev/null || true
53
+ ) &
54
+
55
+ # Parent returns immediately (lock held until child fully detached)
56
+ wait $! 2>/dev/null || true
57
+
58
+ # ALWAYS exit 0 to never block git (advisory hook, failure containment)
59
+ exit 0
60
+ `;
61
+ }
62
+ /**
63
+ * Install the CodeBus git hook into a git repository.
64
+ * If the hook already exists and is a CodeBus hook (contains CODEBUS_MARKER), skip (idempotent).
65
+ * If it exists but is NOT a CodeBus hook, chain it (append with a call to the original).
66
+ * Throws on errors or user hook detection.
67
+ *
68
+ * @param repoRoot — git repository root (contains .git/)
69
+ * @param hookName — 'post-merge' or 'post-commit'
70
+ * @returns { installed: boolean; chained?: boolean }
71
+ */
72
+ export function installGitHook(repoRoot, hookName) {
73
+ const hookPath = join(repoRoot, ".git", "hooks", hookName);
74
+ const script = generateGitHookScript();
75
+ // Check if hook already exists
76
+ if (existsSync(hookPath)) {
77
+ const existing = readFileSync(hookPath, "utf8");
78
+ // Already installed by CodeBus: idempotent, skip
79
+ if (existing.includes(CODEBUS_MARKER)) {
80
+ return { installed: false, message: `${hookName} hook already installed (CodeBus)` };
81
+ }
82
+ // User has an existing hook: chain it
83
+ // Append a call to the original hook at the end of the new hook
84
+ const chainedScript = script.replace("exit 0\n", `# Chain: run the pre-existing user hook if it exists
85
+ if [ -x "${hookPath}.user" ]; then
86
+ "\${${hookPath}.user}" "$@"
87
+ USER_HOOK_EXIT=$?
88
+ if [ $USER_HOOK_EXIT -ne 0 ]; then
89
+ exit $USER_HOOK_EXIT
90
+ fi
91
+ fi
92
+
93
+ exit 0
94
+ `);
95
+ // Back up the original hook
96
+ writeFileSync(join(repoRoot, ".git", "hooks", `${hookName}.user`), existing);
97
+ writeFileSync(hookPath, chainedScript);
98
+ chmodSync(hookPath, 0o755);
99
+ return { installed: true, chained: true, message: `${hookName} hook installed (chained with existing)` };
100
+ }
101
+ // Fresh install
102
+ writeFileSync(hookPath, script);
103
+ chmodSync(hookPath, 0o755);
104
+ return { installed: true, message: `${hookName} hook installed` };
105
+ }
106
+ /**
107
+ * Install both post-merge and post-commit hooks.
108
+ * Returns a summary of what was installed.
109
+ */
110
+ export function installGitHooks(repoRoot) {
111
+ return {
112
+ postMerge: installGitHook(repoRoot, "post-merge"),
113
+ postCommit: installGitHook(repoRoot, "post-commit"),
114
+ };
115
+ }
116
+ //# sourceMappingURL=git-hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-hooks.js","sourceRoot":"","sources":["../src/git-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,oFAAoF;AACpF,MAAM,cAAc,GAAG,qBAAqB,CAAC;AAE7C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB;IACnC,OAAO;EACP,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0Cf,CAAC;AACF,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,QAAsC;IAKrF,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,qBAAqB,EAAE,CAAC;IAEvC,+BAA+B;IAC/B,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAEhD,iDAAiD;QACjD,IAAI,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YACtC,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,QAAQ,mCAAmC,EAAE,CAAC;QACvF,CAAC;QAED,sCAAsC;QACtC,gEAAgE;QAChE,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAClC,UAAU,EACV;WACK,QAAQ;QACX,QAAQ;;;;;;;;CAQf,CACI,CAAC;QAEF,4BAA4B;QAC5B,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC7E,aAAa,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;QACvC,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAE3B,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,QAAQ,yCAAyC,EAAE,CAAC;IAC3G,CAAC;IAED,gBAAgB;IAChB,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAChC,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAE3B,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,QAAQ,iBAAiB,EAAE,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAI9C,OAAO;QACL,SAAS,EAAE,cAAc,CAAC,QAAQ,EAAE,YAAY,CAAC;QACjD,UAAU,EAAE,cAAc,CAAC,QAAQ,EAAE,aAAa,CAAC;KACpD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,95 @@
1
+ import { spawn } from "node:child_process";
2
+ import { CodebusError } from "./errors.js";
3
+ import { loadGraphSnapshot } from "./graph.js";
4
+ /**
5
+ * Trigger an out-of-band graphify build.
6
+ * Spawns `graphify update <repoRoot>` as a detached child process and returns immediately.
7
+ * Logs a single completion notice only (no streaming of build output).
8
+ *
9
+ * @param repoRoot — directory containing .graphify_root or graphify-out/
10
+ * @param opts — builder command (default: 'graphify') and detach mode
11
+ */
12
+ export async function buildGraphOutOfBand(repoRoot, opts = {}) {
13
+ const cmd = opts.graphifyCmd ?? "graphify";
14
+ const detached = opts.detached !== false; // Default true
15
+ return new Promise((resolve, reject) => {
16
+ // Spawn detached (non-blocking)
17
+ const child = spawn(cmd, ["update", repoRoot], {
18
+ detached,
19
+ stdio: "ignore", // Suppress all output
20
+ });
21
+ // Set up error handler
22
+ child.once("error", (err) => {
23
+ reject(new CodebusError(`Failed to spawn graphify: ${err.message}`, 1));
24
+ });
25
+ // If detached, unref immediately and resolve
26
+ if (detached) {
27
+ child.unref();
28
+ console.log("Building knowledge graph in background...");
29
+ resolve();
30
+ }
31
+ else {
32
+ // For testing: wait for process to finish
33
+ child.once("exit", (code) => {
34
+ if (code === 0) {
35
+ console.log("Building knowledge graph in background...");
36
+ resolve();
37
+ }
38
+ else {
39
+ reject(new CodebusError(`graphify exited with code ${code}`, code || 1));
40
+ }
41
+ });
42
+ }
43
+ });
44
+ }
45
+ /**
46
+ * Register the graphify snapshot as an artifact in the registry.
47
+ * Idempotent: checks if a current snapshot artifact already exists before inserting.
48
+ *
49
+ * @param supabase — authenticated Supabase client
50
+ * @param workspaceId — workspace UUID
51
+ * @param agentSlug — agent that produced this artifact
52
+ * @param opts — repoRoot and dryRun for testing
53
+ */
54
+ export async function registerGraphSnapshot(supabase, workspaceId, agentSlug, opts = {}) {
55
+ const repoRoot = opts.repoRoot || process.cwd();
56
+ // Load the current graph snapshot
57
+ const snapshot = loadGraphSnapshot(repoRoot);
58
+ if (!snapshot) {
59
+ throw new CodebusError("No graphify-out/graph.json found to register", 1);
60
+ }
61
+ const builtAtCommit = snapshot.built_at_commit;
62
+ // In dryRun mode, skip all DB operations
63
+ if (opts.dryRun) {
64
+ return { registered: false, snapshotCommit: builtAtCommit };
65
+ }
66
+ // Check if an artifact with the same built_at_commit already exists (idempotent)
67
+ const { data: existing, error: queryError } = await supabase
68
+ .from("artifacts")
69
+ .select("id")
70
+ .eq("workspace_id", workspaceId)
71
+ .eq("kind", "graph-snapshot")
72
+ .eq("body", builtAtCommit) // Body stores the commit hash
73
+ .maybeSingle();
74
+ if (queryError) {
75
+ throw new CodebusError(`Failed to query existing artifacts: ${queryError.message}`, 1);
76
+ }
77
+ if (existing) {
78
+ // Already registered for this commit; skip insertion
79
+ return { registered: false, snapshotCommit: builtAtCommit };
80
+ }
81
+ // Insert new artifact
82
+ const { error: insertError } = await supabase
83
+ .from("artifacts")
84
+ .insert({
85
+ workspace_id: workspaceId,
86
+ kind: "graph-snapshot",
87
+ title: `Graph snapshot at ${builtAtCommit.slice(0, 7)}`,
88
+ body: builtAtCommit, // Store the commit hash for idempotency checks
89
+ });
90
+ if (insertError) {
91
+ throw new CodebusError(`Failed to register graph snapshot: ${insertError.message}`, 1);
92
+ }
93
+ return { registered: true, snapshotCommit: builtAtCommit };
94
+ }
95
+ //# sourceMappingURL=graph-bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-bootstrap.js","sourceRoot":"","sources":["../src/graph-bootstrap.ts"],"names":[],"mappings":"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"}