@kleroterion/koine 0.0.1 → 0.0.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.
package/dist/index.d.ts CHANGED
@@ -117,18 +117,47 @@ declare function decodePrivateKey(raw: string): string;
117
117
  declare function mintToken(auth: GitHubAuth): Promise<string>;
118
118
 
119
119
  type OpKind = "read" | "write";
120
+ interface BudgetSnapshot {
121
+ limit: number;
122
+ remaining: number;
123
+ resetAt: number;
124
+ }
120
125
  interface GitHubClient {
121
126
  rest: Octokit;
122
- /** Run a REST call through the retry/backoff gate. */
127
+ /** Run a REST call through the concurrency+retry gate. */
123
128
  withRest<T>(op: OpKind, fn: (o: Octokit) => Promise<T>): Promise<T>;
124
- /** Run a GraphQL document through the gate. */
129
+ /** Run a GraphQL document through the gate; piggybacks rateLimit{} budget tracking on reads. */
125
130
  graphql<T = unknown>(op: OpKind, query: string, vars?: Record<string, unknown>): Promise<T>;
131
+ budget(): {
132
+ rest: BudgetSnapshot;
133
+ graphql: BudgetSnapshot;
134
+ };
126
135
  }
127
136
  interface ClientOptions {
137
+ readConcurrency?: number;
138
+ writeConcurrency?: number;
128
139
  maxRetries?: number;
129
140
  }
130
141
  declare function createGitHubClient(auth: AuthConfig, log: Logger, opts?: ClientOptions): Promise<GitHubClient>;
131
142
 
143
+ interface ModelUsage {
144
+ inputTokens?: number;
145
+ outputTokens?: number;
146
+ costUSD?: number;
147
+ }
148
+ interface ModelTotals {
149
+ inputTokens: number;
150
+ outputTokens: number;
151
+ costUsd: number;
152
+ }
153
+ declare class CostMeter {
154
+ totalUsd: number;
155
+ private models;
156
+ /** Fold one result message's totals into the meter. */
157
+ record(totalCostUsd: number, modelUsage: Record<string, ModelUsage>): void;
158
+ byModel(): Record<string, ModelTotals>;
159
+ }
160
+
132
161
  type StopReason = "success" | "error_max_turns" | "error_max_budget_usd" | "error_during_execution";
133
162
  interface RunOutcome {
134
163
  ok: boolean;
@@ -136,7 +165,8 @@ interface RunOutcome {
136
165
  sessionId: string;
137
166
  numTurns: number;
138
167
  costUsd: number;
139
- modelUsage: Record<string, unknown>;
168
+ /** Per-model token + cost totals aggregated across the run's result messages. */
169
+ modelUsage: Record<string, ModelTotals>;
140
170
  errors: string[];
141
171
  }
142
172
  interface RunHooks {
@@ -154,4 +184,4 @@ interface LoggerOptions {
154
184
  }
155
185
  declare function createLogger(opts?: LoggerOptions): Logger;
156
186
 
157
- export { type ArtifactKind, type AuthConfig, type BouleBlock, type ClientOptions, DISCUSSION_CATEGORIES, type Fingerprint, type GitHubAuth, type GitHubClient, ISSUE_TYPE_NAMES, type LoggerOptions, type MentionResult, OPERATIONAL_LABELS, type Outbound, PRAKTOR_LABELS, PRIORITY_LABELS, PROJECT_FIELDS, type RunHooks, type RunOutcome, STATUS_LABELS, STATUS_OPTIONS, type ScrubResult, type StatusLabel, type StopReason, allBootstrapLabels, bouleId, cleanOutbound, contentHash, createGitHubClient, createLogger, decodePrivateKey, idLabel, kindLabel, mintToken, parseBouleBlock, parseVerifies, renderBouleBlock, runQuery, sanitizeMentions, scrubSecrets, stripBouleBlock, withBouleBlock };
187
+ export { type ArtifactKind, type AuthConfig, type BouleBlock, type BudgetSnapshot, type ClientOptions, CostMeter, DISCUSSION_CATEGORIES, type Fingerprint, type GitHubAuth, type GitHubClient, ISSUE_TYPE_NAMES, type LoggerOptions, type MentionResult, type ModelTotals, type ModelUsage, OPERATIONAL_LABELS, type Outbound, PRAKTOR_LABELS, PRIORITY_LABELS, PROJECT_FIELDS, type RunHooks, type RunOutcome, STATUS_LABELS, STATUS_OPTIONS, type ScrubResult, type StatusLabel, type StopReason, allBootstrapLabels, bouleId, cleanOutbound, contentHash, createGitHubClient, createLogger, decodePrivateKey, idLabel, kindLabel, mintToken, parseBouleBlock, parseVerifies, renderBouleBlock, runQuery, sanitizeMentions, scrubSecrets, stripBouleBlock, withBouleBlock };
package/dist/index.js CHANGED
@@ -205,24 +205,11 @@ async function mintToken(auth) {
205
205
  import { graphql as octokitGraphql } from "@octokit/graphql";
206
206
  import { throttling } from "@octokit/plugin-throttling";
207
207
  import { Octokit } from "@octokit/rest";
208
+ import pLimit from "p-limit";
208
209
  import pRetry, { AbortError } from "p-retry";
209
210
  var ThrottledOctokit = Octokit.plugin(throttling);
210
- var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
211
211
  var jitter = (ms) => ms * (0.5 + Math.random());
212
- function classifyWait(err, attempt) {
213
- const e = err;
214
- const status = e.status ?? e.response?.status;
215
- const headers = e.response?.headers ?? {};
216
- if (status === 403 || status === 429) {
217
- const ra = Number(headers["retry-after"]);
218
- if (Number.isFinite(ra)) return ra * 1e3;
219
- const reset = Number(headers["x-ratelimit-reset"]);
220
- if (Number.isFinite(reset)) return Math.max(0, reset * 1e3 - Date.now());
221
- return Math.max(6e4, jitter(2 ** attempt * 1e3));
222
- }
223
- if (status && status >= 500) return jitter(2 ** attempt * 500);
224
- throw new AbortError(err);
225
- }
212
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
226
213
  async function createGitHubClient(auth, log, opts = {}) {
227
214
  const token = await mintToken(auth.github);
228
215
  const rest = new ThrottledOctokit({
@@ -241,29 +228,88 @@ async function createGitHubClient(auth, log, opts = {}) {
241
228
  const gql = octokitGraphql.defaults({
242
229
  headers: {
243
230
  authorization: `token ${token}`,
231
+ // Opt into issue-types + sub-issues GraphQL fields (harmless once GA; required while in preview).
244
232
  "GraphQL-Features": "issue_types,sub_issues"
245
233
  }
246
234
  });
247
- const run = (_op, task) => pRetry(
248
- async (attempt) => {
249
- try {
250
- return await task();
251
- } catch (err) {
252
- await sleep(classifyWait(err, attempt));
253
- throw err;
254
- }
255
- },
256
- { retries: opts.maxRetries ?? 6, minTimeout: 1e3, factor: 2 }
235
+ const limits = {
236
+ read: pLimit(opts.readConcurrency ?? 8),
237
+ write: pLimit(opts.writeConcurrency ?? 1)
238
+ // serialize writes ⇒ stay under the ~80/min cap
239
+ };
240
+ const budget = {
241
+ rest: { limit: 5e3, remaining: 5e3, resetAt: 0 },
242
+ graphql: { limit: 5e3, remaining: 5e3, resetAt: 0 }
243
+ };
244
+ function classifyWait(err, attempt) {
245
+ const e = err;
246
+ const status = e.status ?? e.response?.status;
247
+ const headers = e.response?.headers ?? {};
248
+ if (status === 403 || status === 429) {
249
+ const ra = Number(headers["retry-after"]);
250
+ if (Number.isFinite(ra)) return ra * 1e3;
251
+ const reset = Number(headers["x-ratelimit-reset"]);
252
+ if (Number.isFinite(reset)) return Math.max(0, reset * 1e3 - Date.now());
253
+ return Math.max(6e4, jitter(2 ** attempt * 1e3));
254
+ }
255
+ if (status && status >= 500) return jitter(2 ** attempt * 500);
256
+ throw new AbortError(err);
257
+ }
258
+ const run = (op, task) => limits[op](
259
+ () => pRetry(
260
+ async (attempt) => {
261
+ try {
262
+ return await task();
263
+ } catch (err) {
264
+ await sleep(classifyWait(err, attempt));
265
+ throw err;
266
+ }
267
+ },
268
+ { retries: opts.maxRetries ?? 6, minTimeout: 1e3, factor: 2 }
269
+ )
257
270
  );
258
271
  return {
259
272
  rest,
260
273
  withRest: (op, fn) => run(op, () => fn(rest)),
261
- graphql: (op, query2, vars) => run(op, () => gql(query2, vars))
274
+ graphql: (op, query2, vars) => run(op, async () => {
275
+ const track = op === "read" && !query2.includes("rateLimit {");
276
+ const doc = track ? query2.replace(/}\s*$/, " rateLimit { limit remaining resetAt }\n}") : query2;
277
+ const data = await gql(doc, vars);
278
+ if (track && data?.rateLimit) {
279
+ budget.graphql.remaining = data.rateLimit.remaining;
280
+ budget.graphql.limit = data.rateLimit.limit;
281
+ budget.graphql.resetAt = Math.floor(new Date(data.rateLimit.resetAt).getTime() / 1e3);
282
+ }
283
+ return data;
284
+ }),
285
+ budget: () => ({ rest: { ...budget.rest }, graphql: { ...budget.graphql } })
262
286
  };
263
287
  }
264
288
 
265
289
  // src/agent/run.ts
266
290
  import { query } from "@anthropic-ai/claude-agent-sdk";
291
+
292
+ // src/observability/cost.ts
293
+ var CostMeter = class {
294
+ totalUsd = 0;
295
+ models = {};
296
+ /** Fold one result message's totals into the meter. */
297
+ record(totalCostUsd, modelUsage) {
298
+ this.totalUsd += totalCostUsd;
299
+ for (const [model, u] of Object.entries(modelUsage)) {
300
+ const acc = this.models[model] ?? { inputTokens: 0, outputTokens: 0, costUsd: 0 };
301
+ acc.inputTokens += u.inputTokens ?? 0;
302
+ acc.outputTokens += u.outputTokens ?? 0;
303
+ acc.costUsd += u.costUSD ?? 0;
304
+ this.models[model] = acc;
305
+ }
306
+ }
307
+ byModel() {
308
+ return this.models;
309
+ }
310
+ };
311
+
312
+ // src/agent/run.ts
267
313
  function stopReasonOf(subtype) {
268
314
  if (subtype === "success") return "success";
269
315
  if (subtype === "error_max_turns") return "error_max_turns";
@@ -272,11 +318,10 @@ function stopReasonOf(subtype) {
272
318
  }
273
319
  async function runQuery(prompt, options, hooks) {
274
320
  const { log } = hooks;
321
+ const meter = new CostMeter();
275
322
  let stopReason = "error_during_execution";
276
323
  let numTurns = 0;
277
- let costUsd = 0;
278
324
  let sessionId = "";
279
- let modelUsage = {};
280
325
  const errors = [];
281
326
  let gotResult = false;
282
327
  try {
@@ -289,11 +334,10 @@ async function runQuery(prompt, options, hooks) {
289
334
  if (msg.type === "result") {
290
335
  stopReason = stopReasonOf(msg.subtype);
291
336
  numTurns = msg.num_turns;
292
- costUsd = msg.total_cost_usd;
293
- modelUsage = msg.modelUsage ?? {};
337
+ meter.record(msg.total_cost_usd, msg.modelUsage ?? {});
294
338
  if (msg.subtype !== "success") errors.push(...msg.errors ?? []);
295
339
  gotResult = true;
296
- log.info({ stopReason, costUsd, numTurns }, "agent run finished");
340
+ log.info({ stopReason, costUsd: msg.total_cost_usd, numTurns }, "agent run finished");
297
341
  }
298
342
  }
299
343
  } catch (err) {
@@ -306,7 +350,15 @@ async function runQuery(prompt, options, hooks) {
306
350
  stopReason = "error_during_execution";
307
351
  }
308
352
  }
309
- return { ok: stopReason === "success", stopReason, sessionId, numTurns, costUsd, modelUsage, errors };
353
+ return {
354
+ ok: stopReason === "success",
355
+ stopReason,
356
+ sessionId,
357
+ numTurns,
358
+ costUsd: meter.totalUsd,
359
+ modelUsage: meter.byModel(),
360
+ errors
361
+ };
310
362
  }
311
363
 
312
364
  // src/observability/logger.ts
@@ -325,6 +377,7 @@ function createLogger(opts = {}) {
325
377
  return Object.keys(bindings).length ? base.child(bindings) : base;
326
378
  }
327
379
  export {
380
+ CostMeter,
328
381
  DISCUSSION_CATEGORIES,
329
382
  ISSUE_TYPE_NAMES,
330
383
  OPERATIONAL_LABELS,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/taxonomy.ts","../src/identity.ts","../src/security/secrets.ts","../src/security/mentions.ts","../src/security/outbound.ts","../src/github/auth.ts","../src/github/client.ts","../src/agent/run.ts","../src/observability/logger.ts"],"sourcesContent":["// src/taxonomy.ts — THE shared label/field contract written to GitHub. Boule produces it; Praktor reads\n// it; both import it here so it can never drift. The string VALUES are the on-wire format (already on\n// live issues) — change them only with a migration.\nimport type { ArtifactKind } from \"./types.js\";\n\nexport const ISSUE_TYPE_NAMES = {\n design: \"Design\",\n requirement: \"Requirement\",\n competitor: \"Competitor\",\n market: \"Market\",\n gap: \"Gap\",\n epic: \"Epic\",\n feature: \"Feature\",\n task: \"Task\",\n spike: \"Spike\",\n} as const;\n\n/** Fallback kind label used when native Issue Types are unavailable. */\nexport const kindLabel = (kind: ArtifactKind): string => `kind:${kind}`;\n\nexport const OPERATIONAL_LABELS = {\n managed: \"boule:managed\",\n needsHuman: \"boule:needs-human\",\n superseded: \"boule:superseded\",\n /** Kill-switch: an OPEN issue carrying this label halts all autonomous writes. */\n halt: \"boule:halt\",\n} as const;\n\n/** An artifact's ACCEPTANCE lifecycle, carried on the Issue as a label. */\nexport const STATUS_LABELS = [\n \"status:draft\",\n \"status:needs-review\",\n \"status:accepted\",\n \"status:superseded\",\n] as const;\nexport type StatusLabel = (typeof STATUS_LABELS)[number];\n\nexport const PRIORITY_LABELS = [\n \"priority:must\",\n \"priority:should\",\n \"priority:could\",\n \"priority:wont\",\n] as const;\n\n/** Praktor's own progress labels — namespaced so they never collide with Boule's lifecycle. */\nexport const PRAKTOR_LABELS = {\n inProgress: \"praktor:in-progress\",\n done: \"praktor:done\",\n blocked: \"praktor:blocked\",\n} as const;\n\n/** Projects v2 custom field names (canonical keys for field values). */\nexport const PROJECT_FIELDS = {\n status: \"Status\",\n kind: \"Kind\",\n priority: \"Priority\",\n rice: \"RICE\",\n wsjf: \"WSJF\",\n moscow: \"MoSCoW\",\n iteration: \"Iteration\",\n} as const;\n\n/** Projects v2 Status column options (the board workflow state). */\nexport const STATUS_OPTIONS = [\n \"Triage\",\n \"In Design\",\n \"In Review\",\n \"Ready\",\n \"In Progress\",\n \"Blocked\",\n \"Done\",\n] as const;\n\nexport const DISCUSSION_CATEGORIES = {\n dailyStatus: \"Daily Status\",\n handoff: \"Agent Handoffs\",\n designReview: \"Design Review\",\n} as const;\n\n/** Every repo label the tools bootstrap (kinds + operational + status + priority). */\nexport function allBootstrapLabels(): string[] {\n const kinds = Object.keys(ISSUE_TYPE_NAMES) as ArtifactKind[];\n return [\n ...kinds.map(kindLabel),\n ...Object.values(OPERATIONAL_LABELS),\n ...STATUS_LABELS,\n ...PRIORITY_LABELS,\n ];\n}\n","// src/identity.ts — the boule:v1 identity block + content addressing. The crux of safe autonomy:\n// pure, deterministic, network-free. Same logical work ⇒ same id ⇒ idempotent re-runs.\nimport { createHash } from \"node:crypto\";\nimport type { ArtifactKind, Fingerprint } from \"./types.js\";\n\nconst BOULE_BEGIN = \"<!-- boule:v1\";\nconst BOULE_END = \"-->\";\n\nexport interface BouleBlock {\n kind: ArtifactKind;\n bouleId: string;\n contentHash: Fingerprint;\n parent?: string;\n runId?: string;\n generatedBy?: string;\n}\n\n/** Stable, content-independent slug. Same natural key ⇒ same id (NOT random). */\nexport function bouleId(kind: ArtifactKind, naturalKey: string): string {\n const slug = naturalKey\n .toLowerCase()\n .normalize(\"NFKD\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 64);\n return `${kind}:${slug}`;\n}\n\n/** sha256 over the normalized semantic body, EXCLUDING any boule block. */\nexport function contentHash(body: string): Fingerprint {\n const normalized = stripBouleBlock(body)\n .replace(/\\r\\n/g, \"\\n\")\n .replace(/[ \\t]+$/gm, \"\")\n .trim();\n const hex = createHash(\"sha256\").update(normalized, \"utf8\").digest(\"hex\");\n return `sha256:${hex.slice(0, 16)}`;\n}\n\n/** A unique, deterministic dedup label (hashed to stay under GitHub's 50-char label limit). */\nexport function idLabel(id: string): string {\n const hex = createHash(\"sha256\").update(id, \"utf8\").digest(\"hex\");\n return `boule-id-${hex.slice(0, 12)}`;\n}\n\nexport function renderBouleBlock(b: BouleBlock): string {\n return [\n BOULE_BEGIN,\n `kind: ${b.kind}`,\n `boule-id: ${b.bouleId}`,\n `content-hash: ${b.contentHash}`,\n b.parent ? `parent: ${b.parent}` : \"parent:\",\n b.runId ? `run-id: ${b.runId}` : null,\n b.generatedBy ? `generated-by: ${b.generatedBy}` : null,\n BOULE_END,\n ]\n .filter((l): l is string => l !== null)\n .join(\"\\n\");\n}\n\n/** Append the block to a body, recomputing the hash over the body sans-block. */\nexport function withBouleBlock(body: string, meta: Omit<BouleBlock, \"contentHash\">): string {\n const clean = stripBouleBlock(body).trimEnd();\n const block = renderBouleBlock({ ...meta, contentHash: contentHash(clean) });\n return `${clean}\\n\\n${block}\\n`;\n}\n\nexport function parseBouleBlock(body: string): BouleBlock | null {\n const start = body.indexOf(BOULE_BEGIN);\n if (start === -1) return null;\n const end = body.indexOf(BOULE_END, start);\n if (end === -1) return null;\n const inner = body.slice(start + BOULE_BEGIN.length, end);\n const get = (k: string): string | undefined => {\n const m = inner.match(new RegExp(`^${k}:\\\\s*(.+)$`, \"m\"));\n return m?.[1]?.trim() || undefined;\n };\n const kind = get(\"kind\") as ArtifactKind | undefined;\n const id = get(\"boule-id\");\n const hash = get(\"content-hash\");\n if (!kind || !id || !hash) return null;\n return {\n kind,\n bouleId: id,\n contentHash: hash,\n parent: get(\"parent\"),\n runId: get(\"run-id\"),\n generatedBy: get(\"generated-by\"),\n };\n}\n\nexport function stripBouleBlock(body: string): string {\n const start = body.indexOf(BOULE_BEGIN);\n if (start === -1) return body;\n const end = body.indexOf(BOULE_END, start);\n if (end === -1) return body;\n return body.slice(0, start) + body.slice(end + BOULE_END.length);\n}\n\n/** Requirement issue numbers referenced by a `Verifies: #110, #112` link line (Task → Requirement). */\nexport function parseVerifies(body: string): number[] {\n const m = body.match(/^\\s*Verifies:\\s*(.+)$/im);\n if (!m?.[1]) return [];\n return [...m[1].matchAll(/#(\\d+)/g)].map((x) => Number(x[1])).filter((n) => Number.isInteger(n));\n}\n","// src/security/secrets.ts — last-line defense: redact credential-looking strings before any\n// agent-authored text reaches GitHub (a public issue/discussion/PR must never leak a token).\nconst PATTERNS: ReadonlyArray<{ name: string; re: RegExp }> = [\n { name: \"github-token\", re: /\\b(?:gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,})\\b/g },\n { name: \"anthropic-key\", re: /\\bsk-ant-[A-Za-z0-9_-]{20,}\\b/g },\n { name: \"aws-access-key\", re: /\\bAKIA[0-9A-Z]{16}\\b/g },\n { name: \"private-key\", re: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END [A-Z ]*PRIVATE KEY-----/g },\n { name: \"openai-key\", re: /\\bsk-[A-Za-z0-9]{32,}\\b/g },\n];\n\nexport interface ScrubResult {\n clean: string;\n found: string[]; // distinct credential kinds redacted\n}\n\n/** Replace any credential-looking substrings with `[REDACTED:<kind>]`; report the kinds found. */\nexport function scrubSecrets(text: string): ScrubResult {\n let clean = text;\n const found = new Set<string>();\n for (const { name, re } of PATTERNS) {\n clean = clean.replace(re, () => {\n found.add(name);\n return `[REDACTED:${name}]`;\n });\n }\n return { clean, found: [...found] };\n}\n","// src/security/mentions.ts — neutralize @-mentions so autonomous artifacts never ping people.\n// Wrapping a handle in a backtick code span stops GitHub from sending a notification.\n\n// @handle not preceded by a word char/backtick and not part of an email; 1-39 chars, GitHub rules.\nconst MENTION_RE = /(^|[^A-Za-z0-9_`@/])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?)\\b/g;\n\nexport interface MentionResult {\n clean: string;\n stripped: string[]; // handles neutralized\n}\n\nexport function sanitizeMentions(text: string): MentionResult {\n const stripped: string[] = [];\n const clean = text.replace(MENTION_RE, (_m, pre: string, handle: string) => {\n stripped.push(handle);\n return `${pre}\\`@${handle}\\``;\n });\n return { clean, stripped };\n}\n","// src/security/outbound.ts — the single cleanup applied to every agent-authored string before it\n// reaches GitHub: redact credentials, then neutralize @-mentions.\nimport { sanitizeMentions } from \"./mentions.js\";\nimport { scrubSecrets } from \"./secrets.js\";\n\nexport interface Outbound {\n clean: string;\n secrets: string[]; // kinds of credential redacted\n mentions: string[]; // handles neutralized\n}\n\nexport function cleanOutbound(text: string): Outbound {\n const s = scrubSecrets(text);\n const m = sanitizeMentions(s.clean);\n return { clean: m.clean, secrets: s.found, mentions: m.stripped };\n}\n","// src/github/auth.ts — credential TYPES + token minting only. Each tool resolves its OWN env names\n// into an AuthConfig (Boule uses BOULE_APP_*, Praktor uses PRAKTOR_APP_*) and passes it to the client;\n// koine never reads process.env, so it stays identity-agnostic.\nimport { createAppAuth } from \"@octokit/auth-app\";\n\nexport type GitHubAuth =\n | { kind: \"pat\"; token: string }\n | { kind: \"app\"; appId: string; installationId: string; privateKey: string };\n\nexport interface AuthConfig {\n github: GitHubAuth;\n}\n\n/** Decode a base64-or-PEM private key (CI usually stores a single-line base64 blob). */\nexport function decodePrivateKey(raw: string): string {\n const v = raw.trim();\n if (v.includes(\"BEGIN\") && v.includes(\"PRIVATE KEY\")) return v;\n try {\n return Buffer.from(v, \"base64\").toString(\"utf8\");\n } catch {\n return v;\n }\n}\n\n/** Mint a usable token: a PAT passes through; an App mints a short-lived installation token. */\nexport async function mintToken(auth: GitHubAuth): Promise<string> {\n if (auth.kind === \"pat\") return auth.token;\n const appAuth = createAppAuth({\n appId: auth.appId,\n privateKey: auth.privateKey,\n installationId: Number(auth.installationId),\n });\n const { token } = await appAuth({ type: \"installation\" });\n return token;\n}\n","// src/github/client.ts — THE only path to the GitHub API. All backoff/retry + auth live here so both\n// tools share one hardened transport. Reads are concurrency-friendly; writes serialize via p-retry.\nimport { graphql as octokitGraphql } from \"@octokit/graphql\";\nimport { throttling } from \"@octokit/plugin-throttling\";\nimport { Octokit } from \"@octokit/rest\";\nimport pRetry, { AbortError } from \"p-retry\";\nimport type { Logger } from \"pino\";\nimport { type AuthConfig, mintToken } from \"./auth.js\";\n\nconst ThrottledOctokit = Octokit.plugin(throttling);\ntype OpKind = \"read\" | \"write\";\n\nexport interface GitHubClient {\n rest: Octokit;\n /** Run a REST call through the retry/backoff gate. */\n withRest<T>(op: OpKind, fn: (o: Octokit) => Promise<T>): Promise<T>;\n /** Run a GraphQL document through the gate. */\n graphql<T = unknown>(op: OpKind, query: string, vars?: Record<string, unknown>): Promise<T>;\n}\n\nconst sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));\nconst jitter = (ms: number): number => ms * (0.5 + Math.random());\n\nfunction classifyWait(err: unknown, attempt: number): number {\n const e = err as { status?: number; response?: { status?: number; headers?: Record<string, string> } };\n const status = e.status ?? e.response?.status;\n const headers = e.response?.headers ?? {};\n if (status === 403 || status === 429) {\n const ra = Number(headers[\"retry-after\"]);\n if (Number.isFinite(ra)) return ra * 1000;\n const reset = Number(headers[\"x-ratelimit-reset\"]);\n if (Number.isFinite(reset)) return Math.max(0, reset * 1000 - Date.now());\n return Math.max(60_000, jitter(2 ** attempt * 1000));\n }\n if (status && status >= 500) return jitter(2 ** attempt * 500);\n throw new AbortError(err as Error); // 4xx (non-rate) ⇒ don't retry\n}\n\nexport interface ClientOptions {\n maxRetries?: number;\n}\n\nexport async function createGitHubClient(\n auth: AuthConfig,\n log: Logger,\n opts: ClientOptions = {},\n): Promise<GitHubClient> {\n const token = await mintToken(auth.github);\n const rest = new ThrottledOctokit({\n auth: token,\n throttle: {\n onRateLimit: (after, _o, _ok, retryCount) => {\n log.warn({ after, retryCount }, \"primary rate limit\");\n return retryCount < 3;\n },\n onSecondaryRateLimit: (after) => {\n log.warn({ after }, \"secondary rate limit; honoring retry-after\");\n return true;\n },\n },\n });\n const gql = octokitGraphql.defaults({\n headers: {\n authorization: `token ${token}`,\n \"GraphQL-Features\": \"issue_types,sub_issues\",\n },\n });\n\n const run = <T>(_op: OpKind, task: () => Promise<T>): Promise<T> =>\n pRetry(\n async (attempt) => {\n try {\n return await task();\n } catch (err) {\n await sleep(classifyWait(err, attempt));\n throw err;\n }\n },\n { retries: opts.maxRetries ?? 6, minTimeout: 1000, factor: 2 },\n );\n\n return {\n rest,\n withRest: (op, fn) => run(op, () => fn(rest)),\n graphql: <T>(op: OpKind, query: string, vars?: Record<string, unknown>) =>\n run<T>(op, () => gql(query, vars) as Promise<T>),\n };\n}\n","// src/agent/run.ts — the shared Claude Agent SDK run-loop. Resilient to transport noise: the SDK's\n// subprocess can exit non-zero on teardown AFTER emitting a terminal result; that must not override a\n// run whose outcome is already known. A failure BEFORE any result is real and reported as such.\nimport { type Options, query } from \"@anthropic-ai/claude-agent-sdk\";\nimport type { Logger } from \"pino\";\n\nexport type StopReason = \"success\" | \"error_max_turns\" | \"error_max_budget_usd\" | \"error_during_execution\";\n\nexport interface RunOutcome {\n ok: boolean;\n stopReason: StopReason;\n sessionId: string;\n numTurns: number;\n costUsd: number;\n modelUsage: Record<string, unknown>;\n errors: string[];\n}\n\nexport interface RunHooks {\n log: Logger;\n /** Called once with the SDK session id (at init) — e.g. to checkpoint for resume. */\n onSession?: (sessionId: string) => void;\n}\n\nfunction stopReasonOf(subtype: string): StopReason {\n if (subtype === \"success\") return \"success\";\n if (subtype === \"error_max_turns\") return \"error_max_turns\";\n if (subtype === \"error_max_budget_usd\") return \"error_max_budget_usd\";\n return \"error_during_execution\";\n}\n\n/** Drive one query() to completion, returning a normalized outcome. Never throws on transport noise. */\nexport async function runQuery(prompt: string, options: Options, hooks: RunHooks): Promise<RunOutcome> {\n const { log } = hooks;\n let stopReason: StopReason = \"error_during_execution\";\n let numTurns = 0;\n let costUsd = 0;\n let sessionId = \"\";\n let modelUsage: Record<string, unknown> = {};\n const errors: string[] = [];\n let gotResult = false;\n\n try {\n for await (const msg of query({ prompt, options })) {\n if (msg.type === \"system\" && msg.subtype === \"init\") {\n sessionId = msg.session_id;\n log.info({ sessionId }, \"agent run started\");\n hooks.onSession?.(sessionId);\n }\n if (msg.type === \"result\") {\n stopReason = stopReasonOf(msg.subtype);\n numTurns = msg.num_turns;\n costUsd = msg.total_cost_usd;\n modelUsage = (msg.modelUsage ?? {}) as Record<string, unknown>;\n if (msg.subtype !== \"success\") errors.push(...(msg.errors ?? []));\n gotResult = true;\n log.info({ stopReason, costUsd, numTurns }, \"agent run finished\");\n }\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (gotResult) {\n log.warn({ err: message }, \"agent transport error after result; keeping captured outcome\");\n } else {\n log.error({ err: message }, \"agent run failed before producing a result\");\n errors.push(message);\n stopReason = \"error_during_execution\";\n }\n }\n\n return { ok: stopReason === \"success\", stopReason, sessionId, numTurns, costUsd, modelUsage, errors };\n}\n","// src/observability/logger.ts — structured pino logger with credential redaction. Each tool passes its\n// own level + service name; a run-scoped child carries the runId.\nimport pino, { type Logger } from \"pino\";\n\nexport type { Logger };\n\nexport interface LoggerOptions {\n level?: string;\n service?: string;\n runId?: string;\n}\n\nexport function createLogger(opts: LoggerOptions = {}): Logger {\n const base = pino({\n level: opts.level ?? \"info\",\n redact: {\n paths: [\"token\", \"privateKey\", \"*.token\", \"*.privateKey\", \"headers.authorization\"],\n censor: \"[redacted]\",\n },\n });\n const bindings: Record<string, string> = {};\n if (opts.service) bindings.service = opts.service;\n if (opts.runId) bindings.runId = opts.runId;\n return Object.keys(bindings).length ? base.child(bindings) : base;\n}\n"],"mappings":"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","names":["query"]}
1
+ {"version":3,"sources":["../src/taxonomy.ts","../src/identity.ts","../src/security/secrets.ts","../src/security/mentions.ts","../src/security/outbound.ts","../src/github/auth.ts","../src/github/client.ts","../src/agent/run.ts","../src/observability/cost.ts","../src/observability/logger.ts"],"sourcesContent":["// src/taxonomy.ts — THE shared label/field contract written to GitHub. Boule produces it; Praktor reads\n// it; both import it here so it can never drift. The string VALUES are the on-wire format (already on\n// live issues) — change them only with a migration.\nimport type { ArtifactKind } from \"./types.js\";\n\nexport const ISSUE_TYPE_NAMES = {\n design: \"Design\",\n requirement: \"Requirement\",\n competitor: \"Competitor\",\n market: \"Market\",\n gap: \"Gap\",\n epic: \"Epic\",\n feature: \"Feature\",\n task: \"Task\",\n spike: \"Spike\",\n} as const;\n\n/** Fallback kind label used when native Issue Types are unavailable. */\nexport const kindLabel = (kind: ArtifactKind): string => `kind:${kind}`;\n\nexport const OPERATIONAL_LABELS = {\n managed: \"boule:managed\",\n needsHuman: \"boule:needs-human\",\n superseded: \"boule:superseded\",\n /** Kill-switch: an OPEN issue carrying this label halts all autonomous writes. */\n halt: \"boule:halt\",\n} as const;\n\n/** An artifact's ACCEPTANCE lifecycle, carried on the Issue as a label. */\nexport const STATUS_LABELS = [\n \"status:draft\",\n \"status:needs-review\",\n \"status:accepted\",\n \"status:superseded\",\n] as const;\nexport type StatusLabel = (typeof STATUS_LABELS)[number];\n\nexport const PRIORITY_LABELS = [\n \"priority:must\",\n \"priority:should\",\n \"priority:could\",\n \"priority:wont\",\n] as const;\n\n/** Praktor's own progress labels — namespaced so they never collide with Boule's lifecycle. */\nexport const PRAKTOR_LABELS = {\n inProgress: \"praktor:in-progress\",\n done: \"praktor:done\",\n blocked: \"praktor:blocked\",\n} as const;\n\n/** Projects v2 custom field names (canonical keys for field values). */\nexport const PROJECT_FIELDS = {\n status: \"Status\",\n kind: \"Kind\",\n priority: \"Priority\",\n rice: \"RICE\",\n wsjf: \"WSJF\",\n moscow: \"MoSCoW\",\n iteration: \"Iteration\",\n} as const;\n\n/** Projects v2 Status column options (the board workflow state). */\nexport const STATUS_OPTIONS = [\n \"Triage\",\n \"In Design\",\n \"In Review\",\n \"Ready\",\n \"In Progress\",\n \"Blocked\",\n \"Done\",\n] as const;\n\nexport const DISCUSSION_CATEGORIES = {\n dailyStatus: \"Daily Status\",\n handoff: \"Agent Handoffs\",\n designReview: \"Design Review\",\n} as const;\n\n/** Every repo label the tools bootstrap (kinds + operational + status + priority). */\nexport function allBootstrapLabels(): string[] {\n const kinds = Object.keys(ISSUE_TYPE_NAMES) as ArtifactKind[];\n return [\n ...kinds.map(kindLabel),\n ...Object.values(OPERATIONAL_LABELS),\n ...STATUS_LABELS,\n ...PRIORITY_LABELS,\n ];\n}\n","// src/identity.ts — the boule:v1 identity block + content addressing. The crux of safe autonomy:\n// pure, deterministic, network-free. Same logical work ⇒ same id ⇒ idempotent re-runs.\nimport { createHash } from \"node:crypto\";\nimport type { ArtifactKind, Fingerprint } from \"./types.js\";\n\nconst BOULE_BEGIN = \"<!-- boule:v1\";\nconst BOULE_END = \"-->\";\n\nexport interface BouleBlock {\n kind: ArtifactKind;\n bouleId: string;\n contentHash: Fingerprint;\n parent?: string;\n runId?: string;\n generatedBy?: string;\n}\n\n/** Stable, content-independent slug. Same natural key ⇒ same id (NOT random). */\nexport function bouleId(kind: ArtifactKind, naturalKey: string): string {\n const slug = naturalKey\n .toLowerCase()\n .normalize(\"NFKD\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 64);\n return `${kind}:${slug}`;\n}\n\n/** sha256 over the normalized semantic body, EXCLUDING any boule block. */\nexport function contentHash(body: string): Fingerprint {\n const normalized = stripBouleBlock(body)\n .replace(/\\r\\n/g, \"\\n\")\n .replace(/[ \\t]+$/gm, \"\")\n .trim();\n const hex = createHash(\"sha256\").update(normalized, \"utf8\").digest(\"hex\");\n return `sha256:${hex.slice(0, 16)}`;\n}\n\n/** A unique, deterministic dedup label (hashed to stay under GitHub's 50-char label limit). */\nexport function idLabel(id: string): string {\n const hex = createHash(\"sha256\").update(id, \"utf8\").digest(\"hex\");\n return `boule-id-${hex.slice(0, 12)}`;\n}\n\nexport function renderBouleBlock(b: BouleBlock): string {\n return [\n BOULE_BEGIN,\n `kind: ${b.kind}`,\n `boule-id: ${b.bouleId}`,\n `content-hash: ${b.contentHash}`,\n b.parent ? `parent: ${b.parent}` : \"parent:\",\n b.runId ? `run-id: ${b.runId}` : null,\n b.generatedBy ? `generated-by: ${b.generatedBy}` : null,\n BOULE_END,\n ]\n .filter((l): l is string => l !== null)\n .join(\"\\n\");\n}\n\n/** Append the block to a body, recomputing the hash over the body sans-block. */\nexport function withBouleBlock(body: string, meta: Omit<BouleBlock, \"contentHash\">): string {\n const clean = stripBouleBlock(body).trimEnd();\n const block = renderBouleBlock({ ...meta, contentHash: contentHash(clean) });\n return `${clean}\\n\\n${block}\\n`;\n}\n\nexport function parseBouleBlock(body: string): BouleBlock | null {\n const start = body.indexOf(BOULE_BEGIN);\n if (start === -1) return null;\n const end = body.indexOf(BOULE_END, start);\n if (end === -1) return null;\n const inner = body.slice(start + BOULE_BEGIN.length, end);\n const get = (k: string): string | undefined => {\n const m = inner.match(new RegExp(`^${k}:\\\\s*(.+)$`, \"m\"));\n return m?.[1]?.trim() || undefined;\n };\n const kind = get(\"kind\") as ArtifactKind | undefined;\n const id = get(\"boule-id\");\n const hash = get(\"content-hash\");\n if (!kind || !id || !hash) return null;\n return {\n kind,\n bouleId: id,\n contentHash: hash,\n parent: get(\"parent\"),\n runId: get(\"run-id\"),\n generatedBy: get(\"generated-by\"),\n };\n}\n\nexport function stripBouleBlock(body: string): string {\n const start = body.indexOf(BOULE_BEGIN);\n if (start === -1) return body;\n const end = body.indexOf(BOULE_END, start);\n if (end === -1) return body;\n return body.slice(0, start) + body.slice(end + BOULE_END.length);\n}\n\n/** Requirement issue numbers referenced by a `Verifies: #110, #112` link line (Task → Requirement). */\nexport function parseVerifies(body: string): number[] {\n const m = body.match(/^\\s*Verifies:\\s*(.+)$/im);\n if (!m?.[1]) return [];\n return [...m[1].matchAll(/#(\\d+)/g)].map((x) => Number(x[1])).filter((n) => Number.isInteger(n));\n}\n","// src/security/secrets.ts — last-line defense: redact credential-looking strings before any\n// agent-authored text reaches GitHub (a public issue/discussion/PR must never leak a token).\nconst PATTERNS: ReadonlyArray<{ name: string; re: RegExp }> = [\n { name: \"github-token\", re: /\\b(?:gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,})\\b/g },\n { name: \"anthropic-key\", re: /\\bsk-ant-[A-Za-z0-9_-]{20,}\\b/g },\n { name: \"aws-access-key\", re: /\\bAKIA[0-9A-Z]{16}\\b/g },\n { name: \"private-key\", re: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END [A-Z ]*PRIVATE KEY-----/g },\n { name: \"openai-key\", re: /\\bsk-[A-Za-z0-9]{32,}\\b/g },\n];\n\nexport interface ScrubResult {\n clean: string;\n found: string[]; // distinct credential kinds redacted\n}\n\n/** Replace any credential-looking substrings with `[REDACTED:<kind>]`; report the kinds found. */\nexport function scrubSecrets(text: string): ScrubResult {\n let clean = text;\n const found = new Set<string>();\n for (const { name, re } of PATTERNS) {\n clean = clean.replace(re, () => {\n found.add(name);\n return `[REDACTED:${name}]`;\n });\n }\n return { clean, found: [...found] };\n}\n","// src/security/mentions.ts — neutralize @-mentions so autonomous artifacts never ping people.\n// Wrapping a handle in a backtick code span stops GitHub from sending a notification.\n\n// @handle not preceded by a word char/backtick and not part of an email; 1-39 chars, GitHub rules.\nconst MENTION_RE = /(^|[^A-Za-z0-9_`@/])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?)\\b/g;\n\nexport interface MentionResult {\n clean: string;\n stripped: string[]; // handles neutralized\n}\n\nexport function sanitizeMentions(text: string): MentionResult {\n const stripped: string[] = [];\n const clean = text.replace(MENTION_RE, (_m, pre: string, handle: string) => {\n stripped.push(handle);\n return `${pre}\\`@${handle}\\``;\n });\n return { clean, stripped };\n}\n","// src/security/outbound.ts — the single cleanup applied to every agent-authored string before it\n// reaches GitHub: redact credentials, then neutralize @-mentions.\nimport { sanitizeMentions } from \"./mentions.js\";\nimport { scrubSecrets } from \"./secrets.js\";\n\nexport interface Outbound {\n clean: string;\n secrets: string[]; // kinds of credential redacted\n mentions: string[]; // handles neutralized\n}\n\nexport function cleanOutbound(text: string): Outbound {\n const s = scrubSecrets(text);\n const m = sanitizeMentions(s.clean);\n return { clean: m.clean, secrets: s.found, mentions: m.stripped };\n}\n","// src/github/auth.ts — credential TYPES + token minting only. Each tool resolves its OWN env names\n// into an AuthConfig (Boule uses BOULE_APP_*, Praktor uses PRAKTOR_APP_*) and passes it to the client;\n// koine never reads process.env, so it stays identity-agnostic.\nimport { createAppAuth } from \"@octokit/auth-app\";\n\nexport type GitHubAuth =\n | { kind: \"pat\"; token: string }\n | { kind: \"app\"; appId: string; installationId: string; privateKey: string };\n\nexport interface AuthConfig {\n github: GitHubAuth;\n}\n\n/** Decode a base64-or-PEM private key (CI usually stores a single-line base64 blob). */\nexport function decodePrivateKey(raw: string): string {\n const v = raw.trim();\n if (v.includes(\"BEGIN\") && v.includes(\"PRIVATE KEY\")) return v;\n try {\n return Buffer.from(v, \"base64\").toString(\"utf8\");\n } catch {\n return v;\n }\n}\n\n/** Mint a usable token: a PAT passes through; an App mints a short-lived installation token. */\nexport async function mintToken(auth: GitHubAuth): Promise<string> {\n if (auth.kind === \"pat\") return auth.token;\n const appAuth = createAppAuth({\n appId: auth.appId,\n privateKey: auth.privateKey,\n installationId: Number(auth.installationId),\n });\n const { token } = await appAuth({ type: \"installation\" });\n return token;\n}\n","// src/github/client.ts — THE only path to the GitHub API. All backoff/concurrency/budget live here so\n// both tools share one hardened transport: per-op concurrency caps, retry with rate-limit-aware waits,\n// and GraphQL point-budget tracking piggybacked on read queries.\nimport { graphql as octokitGraphql } from \"@octokit/graphql\";\nimport { throttling } from \"@octokit/plugin-throttling\";\nimport { Octokit } from \"@octokit/rest\";\nimport pLimit, { type LimitFunction } from \"p-limit\";\nimport pRetry, { AbortError } from \"p-retry\";\nimport type { Logger } from \"pino\";\nimport { type AuthConfig, mintToken } from \"./auth.js\";\n\nconst ThrottledOctokit = Octokit.plugin(throttling);\n\ntype OpKind = \"read\" | \"write\";\n\nexport interface BudgetSnapshot {\n limit: number;\n remaining: number;\n resetAt: number;\n}\n\nexport interface GitHubClient {\n rest: Octokit;\n /** Run a REST call through the concurrency+retry gate. */\n withRest<T>(op: OpKind, fn: (o: Octokit) => Promise<T>): Promise<T>;\n /** Run a GraphQL document through the gate; piggybacks rateLimit{} budget tracking on reads. */\n graphql<T = unknown>(op: OpKind, query: string, vars?: Record<string, unknown>): Promise<T>;\n budget(): { rest: BudgetSnapshot; graphql: BudgetSnapshot };\n}\n\nconst jitter = (ms: number): number => ms * (0.5 + Math.random());\nconst sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));\n\nexport interface ClientOptions {\n readConcurrency?: number;\n writeConcurrency?: number;\n maxRetries?: number;\n}\n\nexport async function createGitHubClient(\n auth: AuthConfig,\n log: Logger,\n opts: ClientOptions = {},\n): Promise<GitHubClient> {\n const token = await mintToken(auth.github);\n const rest = new ThrottledOctokit({\n auth: token,\n throttle: {\n onRateLimit: (after, _o, _ok, retryCount) => {\n log.warn({ after, retryCount }, \"primary rate limit\");\n return retryCount < 3;\n },\n onSecondaryRateLimit: (after) => {\n log.warn({ after }, \"secondary rate limit; honoring retry-after\");\n return true;\n },\n },\n });\n const gql = octokitGraphql.defaults({\n headers: {\n authorization: `token ${token}`,\n // Opt into issue-types + sub-issues GraphQL fields (harmless once GA; required while in preview).\n \"GraphQL-Features\": \"issue_types,sub_issues\",\n },\n });\n\n const limits: Record<OpKind, LimitFunction> = {\n read: pLimit(opts.readConcurrency ?? 8),\n write: pLimit(opts.writeConcurrency ?? 1), // serialize writes ⇒ stay under the ~80/min cap\n };\n const budget = {\n rest: { limit: 5000, remaining: 5000, resetAt: 0 } as BudgetSnapshot,\n graphql: { limit: 5000, remaining: 5000, resetAt: 0 } as BudgetSnapshot,\n };\n\n function classifyWait(err: unknown, attempt: number): number {\n const e = err as { status?: number; response?: { status?: number; headers?: Record<string, string> } };\n const status = e.status ?? e.response?.status;\n const headers = e.response?.headers ?? {};\n if (status === 403 || status === 429) {\n const ra = Number(headers[\"retry-after\"]);\n if (Number.isFinite(ra)) return ra * 1000;\n const reset = Number(headers[\"x-ratelimit-reset\"]);\n if (Number.isFinite(reset)) return Math.max(0, reset * 1000 - Date.now());\n return Math.max(60_000, jitter(2 ** attempt * 1000));\n }\n if (status && status >= 500) return jitter(2 ** attempt * 500);\n throw new AbortError(err as Error); // 4xx (non-rate) ⇒ don't retry\n }\n\n const run = <T>(op: OpKind, task: () => Promise<T>): Promise<T> =>\n limits[op](() =>\n pRetry(\n async (attempt) => {\n try {\n return await task();\n } catch (err) {\n await sleep(classifyWait(err, attempt));\n throw err;\n }\n },\n { retries: opts.maxRetries ?? 6, minTimeout: 1000, factor: 2 },\n ),\n );\n\n return {\n rest,\n withRest: (op, fn) => run(op, () => fn(rest)),\n graphql: <T>(op: OpKind, query: string, vars?: Record<string, unknown>) =>\n run<T>(op, async () => {\n // `rateLimit` exists only on Query — NEVER inject it into mutations (op \"write\").\n const track = op === \"read\" && !query.includes(\"rateLimit {\");\n const doc = track ? query.replace(/}\\s*$/, \" rateLimit { limit remaining resetAt }\\n}\") : query;\n const data = (await gql(doc, vars)) as T & {\n rateLimit?: { limit: number; remaining: number; resetAt: string };\n };\n if (track && data?.rateLimit) {\n budget.graphql.remaining = data.rateLimit.remaining;\n budget.graphql.limit = data.rateLimit.limit;\n budget.graphql.resetAt = Math.floor(new Date(data.rateLimit.resetAt).getTime() / 1000);\n }\n return data;\n }),\n budget: () => ({ rest: { ...budget.rest }, graphql: { ...budget.graphql } }),\n };\n}\n","// src/agent/run.ts — the shared Claude Agent SDK run-loop. Resilient to transport noise: the SDK's\n// subprocess can exit non-zero on teardown AFTER emitting a terminal result; that must not override a\n// run whose outcome is already known. A failure BEFORE any result is real and reported as such.\nimport { type Options, query } from \"@anthropic-ai/claude-agent-sdk\";\nimport type { Logger } from \"pino\";\nimport { CostMeter, type ModelTotals, type ModelUsage } from \"../observability/cost.js\";\n\nexport type StopReason = \"success\" | \"error_max_turns\" | \"error_max_budget_usd\" | \"error_during_execution\";\n\nexport interface RunOutcome {\n ok: boolean;\n stopReason: StopReason;\n sessionId: string;\n numTurns: number;\n costUsd: number;\n /** Per-model token + cost totals aggregated across the run's result messages. */\n modelUsage: Record<string, ModelTotals>;\n errors: string[];\n}\n\nexport interface RunHooks {\n log: Logger;\n /** Called once with the SDK session id (at init) — e.g. to checkpoint for resume. */\n onSession?: (sessionId: string) => void;\n}\n\nfunction stopReasonOf(subtype: string): StopReason {\n if (subtype === \"success\") return \"success\";\n if (subtype === \"error_max_turns\") return \"error_max_turns\";\n if (subtype === \"error_max_budget_usd\") return \"error_max_budget_usd\";\n return \"error_during_execution\";\n}\n\n/** Drive one query() to completion, returning a normalized outcome. Never throws on transport noise. */\nexport async function runQuery(prompt: string, options: Options, hooks: RunHooks): Promise<RunOutcome> {\n const { log } = hooks;\n const meter = new CostMeter();\n let stopReason: StopReason = \"error_during_execution\";\n let numTurns = 0;\n let sessionId = \"\";\n const errors: string[] = [];\n let gotResult = false;\n\n try {\n for await (const msg of query({ prompt, options })) {\n if (msg.type === \"system\" && msg.subtype === \"init\") {\n sessionId = msg.session_id;\n log.info({ sessionId }, \"agent run started\");\n hooks.onSession?.(sessionId);\n }\n if (msg.type === \"result\") {\n stopReason = stopReasonOf(msg.subtype);\n numTurns = msg.num_turns;\n meter.record(msg.total_cost_usd, (msg.modelUsage ?? {}) as Record<string, ModelUsage>);\n if (msg.subtype !== \"success\") errors.push(...(msg.errors ?? []));\n gotResult = true;\n log.info({ stopReason, costUsd: msg.total_cost_usd, numTurns }, \"agent run finished\");\n }\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (gotResult) {\n log.warn({ err: message }, \"agent transport error after result; keeping captured outcome\");\n } else {\n log.error({ err: message }, \"agent run failed before producing a result\");\n errors.push(message);\n stopReason = \"error_during_execution\";\n }\n }\n\n return {\n ok: stopReason === \"success\",\n stopReason,\n sessionId,\n numTurns,\n costUsd: meter.totalUsd,\n modelUsage: meter.byModel(),\n errors,\n };\n}\n","// src/observability/cost.ts — folds SDK result cost/usage into per-model totals. Estimate-only.\nexport interface ModelUsage {\n inputTokens?: number;\n outputTokens?: number;\n costUSD?: number;\n}\n\nexport interface ModelTotals {\n inputTokens: number;\n outputTokens: number;\n costUsd: number;\n}\n\nexport class CostMeter {\n totalUsd = 0;\n private models: Record<string, ModelTotals> = {};\n\n /** Fold one result message's totals into the meter. */\n record(totalCostUsd: number, modelUsage: Record<string, ModelUsage>): void {\n this.totalUsd += totalCostUsd;\n for (const [model, u] of Object.entries(modelUsage)) {\n const acc = this.models[model] ?? { inputTokens: 0, outputTokens: 0, costUsd: 0 };\n acc.inputTokens += u.inputTokens ?? 0;\n acc.outputTokens += u.outputTokens ?? 0;\n acc.costUsd += u.costUSD ?? 0;\n this.models[model] = acc;\n }\n }\n\n byModel(): Record<string, ModelTotals> {\n return this.models;\n }\n}\n","// src/observability/logger.ts — structured pino logger with credential redaction. Each tool passes its\n// own level + service name; a run-scoped child carries the runId.\nimport pino, { type Logger } from \"pino\";\n\nexport type { Logger };\n\nexport interface LoggerOptions {\n level?: string;\n service?: string;\n runId?: string;\n}\n\nexport function createLogger(opts: LoggerOptions = {}): Logger {\n const base = pino({\n level: opts.level ?? \"info\",\n redact: {\n paths: [\"token\", \"privateKey\", \"*.token\", \"*.privateKey\", \"headers.authorization\"],\n censor: \"[redacted]\",\n },\n });\n const bindings: Record<string, string> = {};\n if (opts.service) bindings.service = opts.service;\n if (opts.runId) bindings.runId = opts.runId;\n return Object.keys(bindings).length ? base.child(bindings) : base;\n}\n"],"mappings":"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","names":["query"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kleroterion/koine",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "The common tongue of the Kleroterion tools — shared building blocks for Boule and Praktor: GitHub-artifact taxonomy, the boule:v1 identity block, a gated GitHub client (App/PAT), the resilient Claude Agent SDK run-loop, structured logging, and outbound secret/mention scrubbing.",
5
5
  "keywords": ["kleroterion", "boule", "praktor", "github", "claude", "agent-sdk", "shared", "library"],
6
6
  "homepage": "https://github.com/kleroterionlabs/koine#readme",
@@ -42,6 +42,7 @@
42
42
  "@octokit/graphql": "^8.1.0",
43
43
  "@octokit/auth-app": "^7.1.0",
44
44
  "@octokit/plugin-throttling": "^9.3.0",
45
+ "p-limit": "^6.1.0",
45
46
  "p-retry": "^6.2.0",
46
47
  "pino": "^9.4.0"
47
48
  },