@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.
- package/bin/codebus.js +2 -0
- package/dist/binding-guard.js +97 -0
- package/dist/binding-guard.js.map +1 -0
- package/dist/client.js +94 -0
- package/dist/client.js.map +1 -0
- package/dist/commands/ack.js +47 -0
- package/dist/commands/ack.js.map +1 -0
- package/dist/commands/archive.js +21 -0
- package/dist/commands/archive.js.map +1 -0
- package/dist/commands/as.js +299 -0
- package/dist/commands/as.js.map +1 -0
- package/dist/commands/ask.js +189 -0
- package/dist/commands/ask.js.map +1 -0
- package/dist/commands/auth.js +33 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/checkin.js +235 -0
- package/dist/commands/checkin.js.map +1 -0
- package/dist/commands/decision.js +52 -0
- package/dist/commands/decision.js.map +1 -0
- package/dist/commands/digest.js +117 -0
- package/dist/commands/digest.js.map +1 -0
- package/dist/commands/doctor.js +106 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/factory.js +191 -0
- package/dist/commands/factory.js.map +1 -0
- package/dist/commands/github-login.js +205 -0
- package/dist/commands/github-login.js.map +1 -0
- package/dist/commands/graph.js +88 -0
- package/dist/commands/graph.js.map +1 -0
- package/dist/commands/health.js +34 -0
- package/dist/commands/health.js.map +1 -0
- package/dist/commands/inbox.js +83 -0
- package/dist/commands/inbox.js.map +1 -0
- package/dist/commands/ingest.js +553 -0
- package/dist/commands/ingest.js.map +1 -0
- package/dist/commands/init.js +97 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/install.js +520 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/invite.js +152 -0
- package/dist/commands/invite.js.map +1 -0
- package/dist/commands/jira.js +237 -0
- package/dist/commands/jira.js.map +1 -0
- package/dist/commands/join.js +96 -0
- package/dist/commands/join.js.map +1 -0
- package/dist/commands/links.js +66 -0
- package/dist/commands/links.js.map +1 -0
- package/dist/commands/login.js +84 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/morning.js +121 -0
- package/dist/commands/morning.js.map +1 -0
- package/dist/commands/next.js +66 -0
- package/dist/commands/next.js.map +1 -0
- package/dist/commands/onboarding.js +109 -0
- package/dist/commands/onboarding.js.map +1 -0
- package/dist/commands/pack.js +276 -0
- package/dist/commands/pack.js.map +1 -0
- package/dist/commands/plan-gh.js +425 -0
- package/dist/commands/plan-gh.js.map +1 -0
- package/dist/commands/plan.js +465 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/commands/pr.js +88 -0
- package/dist/commands/pr.js.map +1 -0
- package/dist/commands/question.js +142 -0
- package/dist/commands/question.js.map +1 -0
- package/dist/commands/read.js +84 -0
- package/dist/commands/read.js.map +1 -0
- package/dist/commands/refs.js +357 -0
- package/dist/commands/refs.js.map +1 -0
- package/dist/commands/reply.js +38 -0
- package/dist/commands/reply.js.map +1 -0
- package/dist/commands/repo.js +1198 -0
- package/dist/commands/repo.js.map +1 -0
- package/dist/commands/report.js +41 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/resolve-refs.js +21 -0
- package/dist/commands/resolve-refs.js.map +1 -0
- package/dist/commands/resolve.js +93 -0
- package/dist/commands/resolve.js.map +1 -0
- package/dist/commands/send.js +168 -0
- package/dist/commands/send.js.map +1 -0
- package/dist/commands/sent.js +22 -0
- package/dist/commands/sent.js.map +1 -0
- package/dist/commands/session.js +426 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/statusline.js +43 -0
- package/dist/commands/statusline.js.map +1 -0
- package/dist/commands/task.js +140 -0
- package/dist/commands/task.js.map +1 -0
- package/dist/commands/tick.js +233 -0
- package/dist/commands/tick.js.map +1 -0
- package/dist/commands/upgrade.js +76 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/commands/watch.js +169 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/commands/whoami.js +27 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/config.js +103 -0
- package/dist/config.js.map +1 -0
- package/dist/directives.js +102 -0
- package/dist/directives.js.map +1 -0
- package/dist/errors.js +110 -0
- package/dist/errors.js.map +1 -0
- package/dist/git-hooks.js +116 -0
- package/dist/git-hooks.js.map +1 -0
- package/dist/graph-bootstrap.js +95 -0
- package/dist/graph-bootstrap.js.map +1 -0
- package/dist/graph-remote.js +889 -0
- package/dist/graph-remote.js.map +1 -0
- package/dist/graph.js +335 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.js +154 -0
- package/dist/index.js.map +1 -0
- package/dist/kit/graph-publish.mjs +400 -0
- package/dist/mail-latency.js +120 -0
- package/dist/mail-latency.js.map +1 -0
- package/dist/mcp-registry.js +859 -0
- package/dist/mcp-registry.js.map +1 -0
- package/dist/mcp.js +193 -0
- package/dist/mcp.js.map +1 -0
- package/dist/output.js +12 -0
- package/dist/output.js.map +1 -0
- package/dist/positioning.js +21 -0
- package/dist/positioning.js.map +1 -0
- package/dist/rolestore.js +89 -0
- package/dist/rolestore.js.map +1 -0
- package/dist/session.js +49 -0
- package/dist/session.js.map +1 -0
- package/dist/sessionstore.js +736 -0
- package/dist/sessionstore.js.map +1 -0
- package/dist/version-check.js +97 -0
- package/dist/version-check.js.map +1 -0
- 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":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAI3C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAQ/C;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,QAAgB,EAChB,OAAqD,EAAE;IAEvD,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,IAAI,UAAU,CAAC;IAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC,eAAe;IAEzD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,gCAAgC;QAChC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE;YAC7C,QAAQ;YACR,KAAK,EAAE,QAAQ,EAAE,sBAAsB;SACxC,CAAC,CAAC;QAEH,uBAAuB;QACvB,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YAC1B,MAAM,CAAC,IAAI,YAAY,CAAC,6BAA6B,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;QAEH,6CAA6C;QAC7C,IAAI,QAAQ,EAAE,CAAC;YACb,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;YACzD,OAAO,EAAE,CAAC;QACZ,CAAC;aAAM,CAAC;YACN,0CAA0C;YAC1C,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBAC1B,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;oBACf,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;oBACzD,OAAO,EAAE,CAAC;gBACZ,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,IAAI,YAAY,CAAC,6BAA6B,IAAI,EAAE,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;gBAC3E,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAOD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,QAAwB,EACxB,WAAmB,EACnB,SAAiB,EACjB,OAAqB,EAAE;IAEvB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAEhD,kCAAkC;IAClC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAC7C,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,YAAY,CAAC,8CAA8C,EAAE,CAAC,CAAC,CAAC;IAC5E,CAAC;IAED,MAAM,aAAa,GAAG,QAAQ,CAAC,eAAe,CAAC;IAE/C,yCAAyC;IACzC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC;IAC9D,CAAC;IAED,iFAAiF;IACjF,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,QAAQ;SACzD,IAAI,CAAC,WAAW,CAAC;SACjB,MAAM,CAAC,IAAI,CAAC;SACZ,EAAE,CAAC,cAAc,EAAE,WAAW,CAAC;SAC/B,EAAE,CAAC,MAAM,EAAE,gBAAgB,CAAC;SAC5B,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,8BAA8B;SACxD,WAAW,EAAE,CAAC;IAEjB,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,IAAI,YAAY,CAAC,uCAAuC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IACzF,CAAC;IAED,IAAI,QAAQ,EAAE,CAAC;QACb,qDAAqD;QACrD,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC;IAC9D,CAAC;IAED,sBAAsB;IACtB,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,MAAM,QAAQ;SAC1C,IAAI,CAAC,WAAW,CAAC;SACjB,MAAM,CAAC;QACN,YAAY,EAAE,WAAW;QACzB,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE,qBAAqB,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE;QACvD,IAAI,EAAE,aAAa,EAAE,+CAA+C;KACrE,CAAC,CAAC;IAEL,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,IAAI,YAAY,CAAC,sCAAsC,WAAW,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IACzF,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC;AAC7D,CAAC"}
|