the-citadel 0.9.5 → 0.11.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.

Potentially problematic release.


This version of the-citadel might be problematic. Click here for more details.

package/dist/cli.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env bun
2
- // @bun
3
2
  import { createRequire } from "node:module";
4
3
  var __create = Object.create;
5
4
  var __getProtoOf = Object.getPrototypeOf;
@@ -45500,10 +45499,10 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
45500
45499
  });
45501
45500
 
45502
45501
  // src/cli.ts
45503
- import { readFileSync as readFileSync3 } from "fs";
45504
- import { access, mkdir, unlink, writeFile } from "fs/promises";
45505
- import { dirname as dirname5, join as join6, resolve as resolve12 } from "path";
45506
- import { fileURLToPath } from "url";
45502
+ import { readFileSync as readFileSync3 } from "node:fs";
45503
+ import { access, mkdir, unlink, writeFile } from "node:fs/promises";
45504
+ import { dirname as dirname4, join as join6, resolve as resolve9 } from "node:path";
45505
+ import { fileURLToPath } from "node:url";
45507
45506
 
45508
45507
  // node_modules/commander/esm.mjs
45509
45508
  var import__ = __toESM(require_commander(), 1);
@@ -65092,6 +65091,7 @@ var ConfigSchema = exports_external.object({
65092
65091
  maxMessageSize: 1e5
65093
65092
  }),
65094
65093
  hooks: exports_external.object({
65094
+ onPearlStart: exports_external.any().optional(),
65095
65095
  onPearlDone: exports_external.any().optional()
65096
65096
  }).optional(),
65097
65097
  logging: exports_external.object({
@@ -65149,6 +65149,11 @@ async function loadConfig() {
65149
65149
  logger.debug("[Config] Loaded from file/env");
65150
65150
  return config2;
65151
65151
  }
65152
+ function setConfig(config2) {
65153
+ const parsed = ConfigSchema.parse(config2);
65154
+ logger.debug("[Config] Manually set config");
65155
+ setGlobalSingleton(CONFIG_KEY, parsed);
65156
+ }
65152
65157
  function getConfig() {
65153
65158
  const config2 = getGlobalSingleton(CONFIG_KEY, () => null);
65154
65159
  if (!config2) {
@@ -85126,10 +85131,39 @@ ${results.join(`
85126
85131
  }
85127
85132
 
85128
85133
  // src/core/instruction.ts
85134
+ class CustomProtocolProvider {
85135
+ name = "custom-protocol";
85136
+ priority = 5;
85137
+ async getInstructions(_ctx) {
85138
+ const path2 = resolve7(process.cwd(), ".citadel/instructions/protocol.md");
85139
+ if (existsSync7(path2)) {
85140
+ try {
85141
+ const content = await readFile3(path2, "utf-8");
85142
+ return `# ⚠️ PROTOCOL (Highest Priority)
85143
+ ${content}`;
85144
+ } catch (err) {
85145
+ logger.error(`[CustomProtocolProvider] Failed to read ${path2}:`, err);
85146
+ }
85147
+ }
85148
+ return null;
85149
+ }
85150
+ }
85151
+
85129
85152
  class GlobalProvider {
85130
85153
  name = "global";
85131
85154
  priority = 10;
85132
85155
  async getInstructions(ctx) {
85156
+ const parts = [];
85157
+ const projectContext = await getProjectContext().resolveContext(process.cwd(), process.cwd());
85158
+ if (projectContext) {
85159
+ parts.push(`# PROJECT RULES (AGENTS.md)
85160
+ You must follow these rules from the project configuration:
85161
+
85162
+ ## Raw Configuration
85163
+ ${projectContext.config.raw}
85164
+
85165
+ Always prioritize these project-specific instructions over general knowledge.`);
85166
+ }
85133
85167
  if (ctx.pearlId) {
85134
85168
  try {
85135
85169
  const pearl = await getPearls().get(ctx.pearlId);
@@ -85137,62 +85171,65 @@ class GlobalProvider {
85137
85171
  const agentPath = resolve7(process.cwd(), `agents/${pearl.assignee}.md`);
85138
85172
  if (existsSync7(agentPath)) {
85139
85173
  const content = await readFile3(agentPath, "utf-8");
85140
- return `
85141
- # AGENT ROLE: ${pearl.assignee.toUpperCase()}
85142
- You are acting as ${pearl.assignee}. Follow these specific instructions and style guidelines:
85174
+ parts.push(`# AGENT PERSONA: ${pearl.assignee.toUpperCase()}
85175
+ You are embodying the persona of ${pearl.assignee}. Adopt the following style and voice, while still adhering to the project rules above:
85143
85176
 
85144
- ## Role Configuration
85145
- ${content}
85146
- `;
85177
+ ${content}`);
85147
85178
  }
85148
85179
  }
85149
85180
  } catch (err) {
85150
- logger.warn(`[GlobalProvider] Error fetching assignee prompt for ${ctx.pearlId}: ${err}`);
85181
+ logger.warn(`[GlobalProvider] Error fetching assignee persona for ${ctx.pearlId}: ${err}`);
85151
85182
  }
85152
85183
  }
85153
- const projectContext = await getProjectContext().resolveContext(process.cwd(), process.cwd());
85154
- if (!projectContext)
85155
- return null;
85156
- return `
85157
- # PROJECT RULES (AGENTS.md)
85158
- You must follow these rules from the project configuration:
85184
+ return parts.length > 0 ? parts.join(`
85159
85185
 
85160
- ## Raw Configuration
85161
- ${projectContext.config.raw}
85186
+ ---
85162
85187
 
85163
- Always prioritize these project-specific instructions over general knowledge.
85164
- `;
85188
+ `) : null;
85165
85189
  }
85166
85190
  }
85167
85191
 
85168
85192
  class BuiltinProvider {
85169
85193
  name = "builtin";
85170
85194
  priority = 15;
85195
+ static WORKER_TYPE_ROLES = [
85196
+ "worker",
85197
+ "software_developer",
85198
+ "qa",
85199
+ "product",
85200
+ "research"
85201
+ ];
85171
85202
  async getInstructions(ctx) {
85172
- if (ctx.role === "worker") {
85173
- return `
85174
- # Implementation Mode
85175
- You are the Worker. Your primary goal is to write code and fix issues.
85176
-
85177
- # Filesystem Tools
85178
- You have access to the \`filesystem\` MCP server tools.
85179
- - Use \`filesystem_list_directory\` and \`filesystem_read_text_file\` to explore.
85180
- - Use \`filesystem_write_file\` to create or overwrite files.
85181
- - Use \`filesystem_edit_file\` for precise modifications.
85182
-
85183
- # Persistence Rules
85184
- - **Persistence is Mandatory**: You MUST use \`filesystem_write_file\` or \`filesystem_edit_file\` to apply your changes to the disk.
85185
- - **No Fake Completion**: Do NOT call \`submit_work\` and say "I have fixed it" unless you have successfully called the filesystem tools in this turn.
85186
- - **Verify Before Submission**: Always run tests or list the directory after your changes to confirm they were successful.
85187
- `;
85203
+ const parts = [];
85204
+ if (BuiltinProvider.WORKER_TYPE_ROLES.includes(ctx.role)) {
85205
+ parts.push(`## Worker Base Protocol
85206
+
85207
+ ### Skills-First Workflow
85208
+ Before reasoning from scratch, you MUST leverage the company's specialized skills library:
85209
+ - **Discovery**: Use \`skills:list_skills\` to see available methodologies.
85210
+ - **Utilization**: Use \`skills:get_skill\` to read the instructions for a relevant skill and follow them.
85211
+
85212
+ ### Memory Retrieval
85213
+ If no skill exists, use the QMD system:
85214
+ - \`qmd:search\` for keyword lookup in \`memories/\` or \`docs/\`.
85215
+ - \`qmd:vector_search\` for conceptual / semantic search.
85216
+ - \`qmd:deep_search\` for highest quality historical context.
85217
+
85218
+ ### Persistence & Git
85219
+ - Every successful task must result in an atomic Git commit.
85220
+ - Use descriptive messages: \`feat(role): summary [pearlId]\`.
85221
+
85222
+ ### Escalation & Delegation
85223
+ - You are NOT authorized to call \`escalate_to_human\` directly.
85224
+ - Delegate to a C-level role (\`ceo\`, \`cto\`, or \`cfo\`) via \`delegate_task\` if human input is required.`);
85188
85225
  }
85189
85226
  if (ctx.role === "gatekeeper") {
85190
- return `
85191
- # Verification Mode
85192
- You are the Gatekeeper (Evaluator). Your purpose is to verify that the work meets the requirements.
85193
- `;
85227
+ parts.push(`## Verification Mode
85228
+ You are the Gatekeeper (Evaluator). Your purpose is to verify that the work meets the requirements. You MUST finalize with \`approve_work\`, \`reject_work\`, or \`fail_work\`.`);
85194
85229
  }
85195
- return null;
85230
+ return parts.length > 0 ? parts.join(`
85231
+
85232
+ `) : null;
85196
85233
  }
85197
85234
  }
85198
85235
 
@@ -85279,17 +85316,32 @@ ${ctx.context.custom_instructions}`;
85279
85316
  }
85280
85317
  }
85281
85318
 
85319
+ class EnforcementProvider {
85320
+ name = "enforcement";
85321
+ priority = 100;
85322
+ async getInstructions(_ctx) {
85323
+ return `# MANDATORY COMPLETION PROTOCOL
85324
+ You are strictly prohibited from ending your response with text alone.
85325
+ To finalize your work, you MUST call exactly one of the completion tools: \`submit_work\`, \`approve_work\`, \`reject_work\`, or \`fail_work\`.
85326
+
85327
+ **Failure to call a completion tool will result in the task being discarded as incomplete.**
85328
+ Text-only summaries are NOT submissions. You must trigger the framework catalyst tools.`;
85329
+ }
85330
+ }
85331
+
85282
85332
  class InstructionService {
85283
85333
  providers = [];
85284
85334
  constructor() {
85285
85335
  this.providers = [
85336
+ new CustomProtocolProvider,
85286
85337
  new GlobalProvider,
85287
85338
  new BuiltinProvider,
85288
85339
  new RoleProvider,
85289
85340
  new MCPResourceProvider,
85290
85341
  new FormulaProvider,
85291
85342
  new TagProvider,
85292
- new ContextProvider
85343
+ new ContextProvider,
85344
+ new EnforcementProvider
85293
85345
  ].sort((a, b) => a.priority - b.priority);
85294
85346
  }
85295
85347
  async buildPrompt(ctx, basePrompt) {
@@ -94293,7 +94345,7 @@ Request: ${prompt}`
94293
94345
  ];
94294
94346
  let finalResult = "";
94295
94347
  let completionRetryCount = 0;
94296
- const maxCompletionRetries = 3;
94348
+ const maxCompletionRetries = 5;
94297
94349
  let completionToolCalled = false;
94298
94350
  const totalUsage = {
94299
94351
  inputTokens: 0,
@@ -94397,17 +94449,19 @@ Request: ${prompt}`
94397
94449
  completionRetryCount++;
94398
94450
  if (completionRetryCount <= maxCompletionRetries) {
94399
94451
  logger.info(`[${this.role}] Agent exited without completion tool. Providing reminder ${completionRetryCount}/${maxCompletionRetries}.`);
94400
- let hint = `You provided a response but did not call a completion tool (e.g., submit_work, approve_work, reject_work, fail_work).
94401
- If you have finished your task, you MUST call the appropriate tool to finalize the workflow.
94402
- If you are still working, continue with your next step.`;
94452
+ let hint = `# MANDATORY COMPLETION PROTOCOL
94453
+ You provided a response but did not call a completion tool.
94454
+ To finalize your work, you MUST call exactly one of: \`submit_work\`, \`approve_work\`, \`reject_work\`, or \`fail_work\`.
94455
+
94456
+ **Text-only responses are NOT accepted as task completion.**
94457
+ If you are finished, submit your work now. If you are still working, continue with your next tool call.`;
94403
94458
  if (completionRetryCount > 1) {
94404
94459
  const completionTools = Object.keys(this.tools).filter((t) => t.includes("submit_work") || t.includes("approve_work") || t.includes("reject_work"));
94405
94460
  const primaryTool = completionTools[0];
94406
94461
  if (primaryTool) {
94407
94462
  hint += `
94408
94463
 
94409
- Available completion tools for your role: ${completionTools.join(", ")}.
94410
- Here is the schema for the primary completion tool:
94464
+ ### Tool Schema Reference: ${primaryTool}
94411
94465
  \`\`\`json
94412
94466
  ${JSON.stringify(this.schemas[primaryTool], null, 2)}
94413
94467
  \`\`\``;
@@ -94849,23 +94903,7 @@ class EvaluatorAgent extends CoreAgent {
94849
94903
  };
94850
94904
  }
94851
94905
  getSystemPrompt(defaultPrompt) {
94852
- return `
94853
- ${defaultPrompt}
94854
-
94855
- # Context
94856
- You are the Gatekeeper (Evaluator). Your goal is to VERIFY that the work meets requirements.
94857
- The work submitted by the agent is available in the 'submitted_work' context variable.
94858
-
94859
- # Instructions
94860
- - If the work is a PLAN (look for 'step:plan' or 'step:planning' labels), review 'submitted_work' for logic, completeness, and adherence to requirements.
94861
- - If the work is an IMPLEMENTATION (look for 'step:impl' or 'step:code' labels), inspect the filesystem and run tests.
94862
- - Note that planning steps may not result in filesystem changes.
94863
- - Use 'approve_work' or 'reject_work' accordingly.
94864
- - CRITICAL: When using 'reject_work', you MUST provide a clear 'reason' explaining why the work was rejected so the worker can fix it.
94865
- - CRITICAL: When approving work, you MUST provide 'acceptance_test'. This must be a string describing the verification performed. DO NOT pass null.
94866
- - If the work is a plan, extract the acceptance criteria from the plan text.
94867
- - # CRITICAL: You MUST finalize your decision using approve_work, reject_work, or fail_work. Your task is NOT complete until one of these tools is successfully called.
94868
- `;
94906
+ return defaultPrompt;
94869
94907
  }
94870
94908
  }
94871
94909
 
@@ -95143,7 +95181,11 @@ class WorkQueue {
95143
95181
  resetPearl(pearlId) {
95144
95182
  this.db.run("DELETE FROM tickets WHERE pearl_id = ?", [pearlId]);
95145
95183
  }
95146
- listActive() {
95184
+ listActive(timeoutMs) {
95185
+ if (timeoutMs) {
95186
+ const cutoff = Date.now() - timeoutMs;
95187
+ return this.db.query("SELECT * FROM tickets WHERE status = 'processing' AND heartbeat_at > ?").all(cutoff);
95188
+ }
95147
95189
  return this.db.query("SELECT * FROM tickets WHERE status = 'processing'").all();
95148
95190
  }
95149
95191
  getTicketsByStatus(status) {
@@ -95411,15 +95453,7 @@ class WorkerAgent extends CoreAgent {
95411
95453
  return super.run(prompt, context2);
95412
95454
  }
95413
95455
  getSystemPrompt(defaultPrompt) {
95414
- return `
95415
- ${defaultPrompt}
95416
-
95417
- # Guidelines
95418
- - Use filesystem tools to explore and write code.
95419
- - Run tests with run_command if available.
95420
- - Keep the user informed with report_progress.
95421
- - # CRITICAL: You MUST submit your work when done with the submit_work tool. DO NOT simply state that you are finished in text. You are only considered finished once submit_work is successfully called.
95422
- `;
95456
+ return defaultPrompt;
95423
95457
  }
95424
95458
  }
95425
95459
 
@@ -95748,1451 +95782,13 @@ class Conductor {
95748
95782
  assignee: role
95749
95783
  });
95750
95784
  logger.info(`[${role}] Instantiating agent for role: ${role}`, { pearlId: ticket.pearl_id });
95751
- const agent = new WorkerAgent(role);
95752
- try {
95753
- const result = await agent.run(`Process this task: ${pearl.title}`, { pearlId: ticket.pearl_id, pearl });
95754
- const finalPearl = await this.pearls.get(ticket.pearl_id);
95755
- if (finalPearl.status === "in_progress") {
95756
- logger.warn(`[${role}] Agent exited without submitting work for ${ticket.pearl_id}`, { pearlId: ticket.pearl_id });
95757
- await this.pearls.update(ticket.pearl_id, {
95758
- status: "open",
95759
- labels: [...finalPearl.labels || [], "agent-incomplete"]
95760
- });
95761
- }
95762
- return result;
95763
- } catch (error48) {
95764
- logger.error(`[${role}] Agent failed for ${ticket.pearl_id}`, error48);
95765
- const currentLabels = pearl?.labels || [];
95766
- await this.pearls.update(ticket.pearl_id, {
95767
- status: "open",
95768
- labels: [...currentLabels, "failed", "agent-error"]
95769
- });
95770
- }
95771
- }, this.queue, this.config.worker.maxRetries), this.config.worker.min_workers);
95772
- this.pools.set(role, pool);
95773
- }
95774
- this.gatekeeperPool = new PoolClass("gatekeeper", (id) => new Hook(id, "gatekeeper", async (ticket) => {
95775
- logger.info(`[Gatekeeper] Verifying ${ticket.pearl_id}`, {
95776
- pearlId: ticket.pearl_id
95777
- });
95778
- const agent = new EvaluatorAgent;
95779
- const pearl = await this.pearls.get(ticket.pearl_id);
95780
- await this.pearls.update(ticket.pearl_id, { assignee: "gatekeeper" });
95781
- const submittedWork = pearl.output || this.queue.getOutput(ticket.pearl_id);
95782
- if (!submittedWork) {
95783
- logger.warn(`[Gatekeeper] No submitted work found for ${ticket.pearl_id} (retrieved 'null' from queue). Evaluator may reject.`, { pearlId: ticket.pearl_id });
95784
- }
95785
- try {
95786
- await agent.run(`Verify this work: ${pearl.title}`, {
95787
- pearlId: ticket.pearl_id,
95788
- pearl,
95789
- submitted_work: submittedWork
95790
- });
95791
- const finalPearl = await this.pearls.get(ticket.pearl_id);
95792
- if (finalPearl.status === "verify") {
95793
- logger.warn(`[Gatekeeper] Agent exited without decision for ${ticket.pearl_id}`, { pearlId: ticket.pearl_id });
95794
- await this.pearls.update(ticket.pearl_id, {
95795
- status: "verify",
95796
- labels: [...finalPearl.labels || [], "evaluator-incomplete"]
95797
- });
95798
- }
95799
- } catch (error48) {
95800
- logger.error(`[Gatekeeper] Agent failed for ${ticket.pearl_id}`, error48);
95801
- await this.pearls.update(ticket.pearl_id, {
95802
- status: "verify",
95803
- labels: [...pearl.labels || [], "evaluator-error"]
95804
- });
95805
- }
95806
- }, this.queue, 3), this.config.gatekeeper.min_workers);
95807
- }
95808
- async start() {
95809
- if (this.isRunning)
95810
- return;
95811
- this.isRunning = true;
95812
- logger.info("[Conductor] Starting...");
95813
- await getMCPService().initialize();
95814
- const healthy = await this.validateEnvironment();
95815
- if (!healthy) {
95816
- this.isRunning = false;
95817
- await getMCPService().shutdown();
95818
- return;
95819
- }
95820
- for (const pool of this.pools.values()) {
95821
- pool.start();
95822
- }
95823
- this.gatekeeperPool.start();
95824
- this.routerLoop();
95825
- }
95826
- async stop() {
95827
- this.isRunning = false;
95828
- logger.info("[Conductor] Stopping...");
95829
- for (const pool of this.pools.values()) {
95830
- pool.stop();
95831
- }
95832
- this.gatekeeperPool.stop();
95833
- if (this.routerTimer) {
95834
- clearTimeout(this.routerTimer);
95835
- this.routerTimer = null;
95836
- }
95837
- await getMCPService().shutdown();
95838
- }
95839
- async validateEnvironment() {
95840
- logger.info("[Conductor] Validating environment...");
95841
- const healthy = await this.pearls.doctor();
95842
- if (!healthy) {
95843
- logger.error('[Conductor] Environment check failed! "bd doctor" reports issues.');
95844
- logger.error('[Conductor] Please run "bd doctor" and "bd sync" manually to fix data integrity issues.');
95845
- return false;
95846
- }
95847
- return true;
95848
- }
95849
- async routerLoop() {
95850
- if (!this.isRunning)
95851
- return;
95852
- let nextDelay = 5000;
95853
- try {
95854
- await this.cycleRouter();
95855
- await this.scalePools();
95856
- this.consecutiveFailures = 0;
95857
- } catch (error48) {
95858
- this.consecutiveFailures++;
95859
- const backoff = Math.min(5000 * 2 ** this.consecutiveFailures, 300000);
95860
- nextDelay = backoff;
95861
- logger.error(`[Conductor] Cycle failed (attempt ${this.consecutiveFailures}). Backing off for ${Math.round(nextDelay / 1000)}s:`, error48);
95862
- }
95863
- if (this.isRunning) {
95864
- this.routerTimer = setTimeout(() => this.routerLoop(), nextDelay);
95865
- }
95866
- }
95867
- async cycleRouter() {
95868
- const pearlsClient = this.pearls;
95869
- const queue = this.queue;
95870
- try {
95871
- const released = queue.releaseStalled(15 * 60 * 1000);
95872
- if (released > 0) {
95873
- logger.info(`[Router] Released ${released} stalled/zombie tickets`);
95874
- }
95875
- } catch (e) {
95876
- logger.error("[Router] Failed to release stalled tickets", e);
95877
- }
95878
- const readyPearls = await pearlsClient.ready();
95879
- if (!readyPearls) {
95880
- logger.error("[Conductor] readyPearls is undefined!");
95881
- return;
95882
- }
95883
- const inProgressPearls = await pearlsClient.list("in_progress");
95884
- for (const pearl of inProgressPearls) {
95885
- const active = queue.getActiveTicket(pearl.id);
95886
- if (!active) {
95887
- const latest = queue.getLatestTicket(pearl.id);
95888
- const GRACE_PERIOD_MS = 5000;
95889
- if (latest && latest.status === "completed" && latest.completed_at && Date.now() - latest.completed_at < GRACE_PERIOD_MS) {
95890
- logger.debug(`[Router] Deferring reset of pearl ${pearl.id} (within 5s grace period of ticket completion)`, { pearlId: pearl.id });
95891
- continue;
95892
- }
95893
- if (pearl.labels?.includes("hitl") || pearl.labels?.includes("escalation")) {
95894
- continue;
95895
- }
95896
- logger.warn(`[Router] Resetting stuck pearl ${pearl.id} (in_progress with no active ticket)`, { pearlId: pearl.id });
95897
- await pearlsClient.update(pearl.id, {
95898
- status: "open",
95899
- labels: [
95900
- ...(pearl.labels || []).filter((l) => l !== "auto-recovered"),
95901
- "auto-recovered"
95902
- ]
95903
- });
95904
- } else if (pearl.labels?.includes("agent-error") || pearl.labels?.includes("failed")) {
95905
- logger.debug(`[Router] Skipping reset of failed pearl ${pearl.id} - active ticket exists.`, { pearlId: pearl.id });
95906
- }
95907
- }
95908
- for (const pearl of readyPearls) {
95909
- const active = queue.getActiveTicket(pearl.id);
95910
- if (!active) {
95911
- const fresh = await pearlsClient.get(pearl.id);
95912
- if (fresh.status !== "open") {
95913
- logger.info(`[Router] Skipping ${pearl.id} (status changed to ${fresh.status})`, { pearlId: pearl.id });
95914
- continue;
95915
- }
95916
- if (fresh.type === "epic") {
95917
- if (this.config.gatekeeper.auto_close_epics) {
95918
- const blockers = fresh.blockers || [];
95919
- const allPearls = await pearlsClient.getAll();
95920
- const children = allPearls.filter((p) => p.parent === pearl.id);
95921
- const childrenIds = children.map((c) => c.id);
95922
- const combinedBlockers = [...new Set([...blockers, ...childrenIds])];
95923
- if (combinedBlockers.length > 0) {
95924
- const blockerPearls = await Promise.all(combinedBlockers.map((id) => pearlsClient.get(id).catch(() => null)));
95925
- const validBlockers = blockerPearls.filter((b) => b !== null);
95926
- const allDone = validBlockers.every((b) => b.status === "done");
95927
- if (allDone && validBlockers.length > 0) {
95928
- logger.info(`[Router] Auto-closing Epic ${pearl.id} (all blockers and children completed)`, { pearlId: pearl.id });
95929
- await pearlsClient.update(pearl.id, {
95930
- status: "done",
95931
- acceptance_test: "Auto-closed: All subtasks and children completed."
95932
- });
95933
- continue;
95934
- }
95935
- }
95936
- }
95937
- logger.info(`[Router] Skipping container/epic pearl ${pearl.id}`, {
95938
- pearlId: pearl.id
95939
- });
95940
- continue;
95941
- }
95942
- if (fresh.labels?.includes("molecule:cooking")) {
95943
- logger.debug(`[Router] Skipping cooking pearl ${pearl.id}`, {
95944
- pearlId: pearl.id
95945
- });
95946
- continue;
95947
- }
95948
- if (fresh.blockers && fresh.blockers.length > 0) {
95949
- const blockers = await Promise.all(fresh.blockers.map((id) => pearlsClient.get(id)));
95950
- const activeBlockers = blockers.filter((b) => b.status !== "done");
95951
- if (activeBlockers.length > 0) {
95952
- logger.warn(`[Router] Skipping ${pearl.id} - incorrectly marked ready (blocked by ${activeBlockers.map((b) => b.id).join(", ")})`, { pearlId: pearl.id });
95953
- continue;
95954
- }
95955
- }
95956
- if (fresh.labels?.includes("recovery")) {
95957
- const blockers = fresh.blockers || [];
95958
- if (blockers.length > 0) {
95959
- const blockerPearls = await Promise.all(blockers.map((id) => pearlsClient.get(id)));
95960
- const anyFailed = blockerPearls.some((b) => b.labels?.includes("failed"));
95961
- const allDone = blockerPearls.every((b) => b.status === "done");
95962
- if (allDone && !anyFailed) {
95963
- logger.info(`[Router] Skipping recovery pearl ${pearl.id} (all dependencies succeeded)`, { pearlId: pearl.id });
95964
- await pearlsClient.update(pearl.id, {
95965
- status: "done",
95966
- acceptance_test: "Skipped: All dependencies succeeded without failure."
95967
- });
95968
- continue;
95969
- }
95970
- }
95971
- }
95972
- if (fresh.labels?.includes("hitl") || fresh.labels?.includes("escalation")) {
95973
- logger.info(`[Router] Skipping HITL/Escalation pearl ${pearl.id} (awaiting human action)`, {
95974
- pearlId: pearl.id
95975
- });
95976
- continue;
95977
- }
95978
- const piped = await getPiper().pipeData(pearl.id);
95979
- if (piped) {
95980
- logger.info(`[Router] Piped data for ${pearl.id}`);
95981
- }
95982
- const currentPearl = await pearlsClient.get(pearl.id);
95983
- if (currentPearl.context) {
95984
- const ctxString = JSON.stringify(currentPearl.context);
95985
- if (ctxString.includes("{{steps.")) {
95986
- logger.info(`[Router] Skipping ${pearl.id} (waiting for dependency data)`, { pearlId: pearl.id });
95987
- continue;
95988
- }
95989
- }
95990
- const stillNoTicket = queue.getActiveTicket(pearl.id);
95991
- if (stillNoTicket) {
95992
- logger.debug(`[Router] Pearl ${pearl.id} already has ticket (race avoided)`, { pearlId: pearl.id });
95993
- continue;
95994
- }
95995
- let targetRole = currentPearl.assignee || currentPearl.context?.role || currentPearl.metadata?.role || "worker";
95996
- if (!this.config.agents[targetRole]) {
95997
- logger.warn(`[Router] Role '${targetRole}' not found in config. Falling back to 'worker' for ${pearl.id}`);
95998
- targetRole = "worker";
95999
- }
96000
- logger.info(`[Router] Routing pearl ${pearl.id} to ${targetRole}`, {
96001
- pearlId: pearl.id,
96002
- targetRole
96003
- });
96004
- this.queue.enqueue(pearl.id, currentPearl.priority, targetRole);
96005
- }
96006
- }
96007
- const verifyPearls = await pearlsClient.list("verify");
96008
- for (const pearl of verifyPearls) {
96009
- const active = queue.getActiveTicket(pearl.id);
96010
- if (!active) {
96011
- const fresh = await pearlsClient.get(pearl.id);
96012
- if (fresh.status !== "verify") {
96013
- continue;
96014
- }
96015
- const stillNoTicket = queue.getActiveTicket(pearl.id);
96016
- if (stillNoTicket) {
96017
- logger.debug(`[Router] Pearl ${pearl.id} already has ticket (race avoided)`, { pearlId: pearl.id });
96018
- continue;
96019
- }
96020
- logger.info(`[Router] Routing pearl ${pearl.id} to gatekeeper`, {
96021
- pearlId: pearl.id
96022
- });
96023
- queue.enqueue(pearl.id, fresh.priority, "gatekeeper");
96024
- } else {
96025
- if (active.target_role === "worker" && active.status !== "completed") {
96026
- logger.warn(`[Router] Found zombie worker ticket for verify pearl ${pearl.id}. Cleaning up.`, { pearlId: pearl.id, ticketId: active.id });
95785
+ if (this.config.hooks?.onPearlStart) {
96027
95786
  try {
96028
- this.queue.db.run(`UPDATE tickets SET status = 'completed', completed_at = ? WHERE id = ?`, [Date.now(), active.id]);
96029
- } catch (e) {
96030
- logger.error(`[Router] Failed to cleanup zombie ticket ${active.id}`, e);
95787
+ await this.config.hooks.onPearlStart(pearl);
95788
+ } catch (err) {
95789
+ logger.error(`[${role}] Error in onPearlStart hook:`, err);
96031
95790
  }
96032
95791
  }
96033
- }
96034
- }
96035
- }
96036
- async scalePools() {
96037
- for (const [role, pool] of this.pools.entries()) {
96038
- const pending = this.queue.getPendingCount(role);
96039
- let target = Math.ceil(pending * this.config.worker.load_factor);
96040
- target = Math.max(this.config.worker.min_workers, Math.min(target, this.config.worker.max_workers));
96041
- await pool.resize(target);
96042
- }
96043
- const gatekeeperPending = this.queue.getPendingCount("gatekeeper");
96044
- let targetGatekeepers = Math.ceil(gatekeeperPending * this.config.gatekeeper.load_factor);
96045
- targetGatekeepers = Math.max(this.config.gatekeeper.min_workers, Math.min(targetGatekeepers, this.config.gatekeeper.max_workers));
96046
- await this.gatekeeperPool.resize(targetGatekeepers);
96047
- }
96048
- }
96049
-
96050
- // src/bridge/components/Dashboard.tsx
96051
- var import_react27 = __toESM(require_react(), 1);
96052
-
96053
- // src/bridge/components/MoleculeTree.tsx
96054
- var import_react26 = __toESM(require_react(), 1);
96055
- var jsx_dev_runtime = __toESM(require_jsx_dev_runtime(), 1);
96056
- var buildTree = (pearls) => {
96057
- const map4 = new Map;
96058
- const roots = [];
96059
- for (const pearl of pearls) {
96060
- map4.set(pearl.id, { pearl, children: [] });
96061
- }
96062
- for (const pearl of pearls) {
96063
- const node = map4.get(pearl.id);
96064
- if (!node)
96065
- continue;
96066
- if (pearl.parent && map4.has(pearl.parent)) {
96067
- const parent = map4.get(pearl.parent);
96068
- if (parent) {
96069
- parent.children.push(node);
96070
- }
96071
- } else {
96072
- roots.push(node);
96073
- }
96074
- }
96075
- return roots;
96076
- };
96077
- var PearlNode = ({ node, depth }) => {
96078
- const indent = " ".repeat(depth);
96079
- const color = node.pearl.status === "done" ? "green" : node.pearl.status === "verify" ? "magenta" : node.pearl.status === "in_progress" ? "yellow" : "white";
96080
- const icon = node.pearl.status === "done" ? "✓" : node.pearl.status === "verify" ? "?" : node.pearl.status === "in_progress" ? "▶" : "○";
96081
- return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
96082
- flexDirection: "column",
96083
- children: [
96084
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
96085
- color,
96086
- children: [
96087
- indent,
96088
- icon,
96089
- " ",
96090
- node.pearl.title,
96091
- " ",
96092
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
96093
- color: "gray",
96094
- children: [
96095
- "(",
96096
- node.pearl.id,
96097
- ")"
96098
- ]
96099
- }, undefined, true, undefined, this)
96100
- ]
96101
- }, undefined, true, undefined, this),
96102
- node.children.map((child) => /* @__PURE__ */ jsx_dev_runtime.jsxDEV(PearlNode, {
96103
- node: child,
96104
- depth: depth + 1
96105
- }, child.pearl.id, false, undefined, this))
96106
- ]
96107
- }, undefined, true, undefined, this);
96108
- };
96109
- var MoleculeTree = () => {
96110
- const [tree, setTree] = import_react26.useState([]);
96111
- import_react26.useEffect(() => {
96112
- const refresh = async () => {
96113
- try {
96114
- const pearls = await getPearls().getAll();
96115
- setTree(buildTree(pearls));
96116
- } catch (_e) {}
96117
- };
96118
- refresh();
96119
- const timer = setInterval(refresh, 2000);
96120
- return () => clearInterval(timer);
96121
- }, []);
96122
- return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
96123
- flexDirection: "column",
96124
- borderStyle: "round",
96125
- borderColor: "magenta",
96126
- width: "50%",
96127
- height: 15,
96128
- overflowY: "hidden",
96129
- children: [
96130
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
96131
- bold: true,
96132
- children: "Molecules"
96133
- }, undefined, false, undefined, this),
96134
- tree.length === 0 ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
96135
- color: "gray",
96136
- children: "No molecules found"
96137
- }, undefined, false, undefined, this) : null,
96138
- tree.map((node) => /* @__PURE__ */ jsx_dev_runtime.jsxDEV(PearlNode, {
96139
- node,
96140
- depth: 0
96141
- }, node.pearl.id, false, undefined, this))
96142
- ]
96143
- }, undefined, true, undefined, this);
96144
- };
96145
-
96146
- // src/bridge/components/Dashboard.tsx
96147
- var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
96148
- var LogStream = () => {
96149
- const config2 = getConfig();
96150
- const maxLogs = config2.bridge?.maxLogs || 1000;
96151
- const [logs, setLogs] = import_react27.useState([]);
96152
- const [scrollTop, setScrollTop] = import_react27.useState(0);
96153
- const [viewHeight, _setViewHeight] = import_react27.useState(15);
96154
- import_react27.useEffect(() => {
96155
- const handler = (entry) => {
96156
- setLogs((prev) => {
96157
- const updated = [...prev, entry];
96158
- if (updated.length > maxLogs) {
96159
- return updated.slice(updated.length - maxLogs);
96160
- }
96161
- return updated;
96162
- });
96163
- };
96164
- logger.on("log", handler);
96165
- return () => {
96166
- logger.off("log", handler);
96167
- };
96168
- }, [maxLogs]);
96169
- use_input_default((_input, key) => {
96170
- if (key.upArrow) {
96171
- setScrollTop((prev) => Math.min(prev + 1, Math.max(0, logs.length - viewHeight)));
96172
- }
96173
- if (key.downArrow) {
96174
- setScrollTop((prev) => Math.max(0, prev - 1));
96175
- }
96176
- if (key.pageUp) {
96177
- setScrollTop((prev) => Math.min(prev + 10, Math.max(0, logs.length - viewHeight)));
96178
- }
96179
- if (key.pageDown) {
96180
- setScrollTop((prev) => Math.max(0, prev - 10));
96181
- }
96182
- });
96183
- const total = logs.length;
96184
- const effectiveHeight = Math.min(total, viewHeight);
96185
- let startIndex = total - effectiveHeight - scrollTop;
96186
- if (startIndex < 0)
96187
- startIndex = 0;
96188
- const endIndex = startIndex + effectiveHeight;
96189
- const viewLogs = logs.slice(startIndex, endIndex);
96190
- return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
96191
- flexDirection: "column",
96192
- borderStyle: "round",
96193
- borderColor: scrollTop > 0 ? "yellow" : "green",
96194
- width: "100%",
96195
- height: viewHeight + 2,
96196
- children: [
96197
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
96198
- flexDirection: "row",
96199
- justifyContent: "space-between",
96200
- children: [
96201
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96202
- bold: true,
96203
- children: [
96204
- "Live Logs ",
96205
- scrollTop > 0 ? `(Scrolled: -${scrollTop})` : "(Live)"
96206
- ]
96207
- }, undefined, true, undefined, this),
96208
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96209
- color: "gray",
96210
- children: [
96211
- logs.length,
96212
- "/",
96213
- maxLogs
96214
- ]
96215
- }, undefined, true, undefined, this)
96216
- ]
96217
- }, undefined, true, undefined, this),
96218
- viewLogs.map((l, i2) => /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96219
- wrap: "truncate",
96220
- children: [
96221
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96222
- color: "gray",
96223
- children: [
96224
- "[",
96225
- l.timestamp.split("T")[1]?.split(".")[0] || "00:00:00",
96226
- "]"
96227
- ]
96228
- }, undefined, true, undefined, this),
96229
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96230
- color: l.level === "error" ? "red" : l.level === "warn" ? "yellow" : "white",
96231
- children: [
96232
- " ",
96233
- "[",
96234
- l.level.toUpperCase(),
96235
- "]",
96236
- " "
96237
- ]
96238
- }, undefined, true, undefined, this),
96239
- l.message
96240
- ]
96241
- }, `${l.timestamp}-${startIndex + i2}`, true, undefined, this))
96242
- ]
96243
- }, undefined, true, undefined, this);
96244
- };
96245
- var AgentMatrix = () => {
96246
- const [activeTickets, setActiveTickets] = import_react27.useState([]);
96247
- import_react27.useEffect(() => {
96248
- const timer = setInterval(() => {
96249
- const queue = getQueue();
96250
- const rows = queue.getTicketsByStatus("processing");
96251
- if (rows)
96252
- setActiveTickets(rows);
96253
- }, 1000);
96254
- return () => clearInterval(timer);
96255
- }, []);
96256
- return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
96257
- flexDirection: "column",
96258
- borderStyle: "round",
96259
- borderColor: "blue",
96260
- width: "50%",
96261
- children: [
96262
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96263
- bold: true,
96264
- children: "Active Agents"
96265
- }, undefined, false, undefined, this),
96266
- activeTickets.length === 0 ? /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96267
- color: "gray",
96268
- children: "No active agents"
96269
- }, undefined, false, undefined, this) : null,
96270
- activeTickets.map((t) => /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
96271
- flexDirection: "row",
96272
- children: [
96273
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96274
- color: "cyan",
96275
- children: t.target_role
96276
- }, undefined, false, undefined, this),
96277
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96278
- children: " -> "
96279
- }, undefined, false, undefined, this),
96280
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96281
- children: t.pearl_id
96282
- }, undefined, false, undefined, this),
96283
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96284
- color: "yellow",
96285
- children: [
96286
- " (",
96287
- t.status,
96288
- ")"
96289
- ]
96290
- }, undefined, true, undefined, this)
96291
- ]
96292
- }, t.id, true, undefined, this))
96293
- ]
96294
- }, undefined, true, undefined, this);
96295
- };
96296
- var Dashboard = () => {
96297
- const [status] = import_react27.useState("Online");
96298
- return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
96299
- flexDirection: "column",
96300
- padding: 1,
96301
- children: [
96302
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
96303
- children: [
96304
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96305
- children: "The Citadel Bridge "
96306
- }, undefined, false, undefined, this),
96307
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96308
- color: "green",
96309
- children: [
96310
- "[",
96311
- status,
96312
- "]"
96313
- ]
96314
- }, undefined, true, undefined, this)
96315
- ]
96316
- }, undefined, true, undefined, this),
96317
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
96318
- flexDirection: "row",
96319
- children: [
96320
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(AgentMatrix, {}, undefined, false, undefined, this),
96321
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(MoleculeTree, {}, undefined, false, undefined, this)
96322
- ]
96323
- }, undefined, true, undefined, this),
96324
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(LogStream, {}, undefined, false, undefined, this),
96325
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96326
- color: "gray",
96327
- children: "Press Ctrl+C to exit"
96328
- }, undefined, false, undefined, this)
96329
- ]
96330
- }, undefined, true, undefined, this);
96331
- };
96332
-
96333
- // src/bridge/index.tsx
96334
- var jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
96335
- async function startBridge() {
96336
- await loadConfig();
96337
- logger.setConsoleEnabled(false);
96338
- const conductor = new Conductor;
96339
- conductor.start();
96340
- const { waitUntilExit } = render_default(/* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Dashboard, {}, undefined, false, undefined, this));
96341
- try {
96342
- await waitUntilExit();
96343
- } catch (error48) {
96344
- console.error("Bridge crashed:", error48);
96345
- } finally {
96346
- conductor.stop();
96347
- console.log("");
96348
- }
96349
- }
96350
-
96351
- // src/config/index.ts
96352
- var import_dotenv2 = __toESM(require_main(), 1);
96353
- import { existsSync as existsSync8 } from "node:fs";
96354
- import { resolve as resolve9 } from "node:path";
96355
- import_dotenv2.default.config({ quiet: true });
96356
- var CONFIG_KEY2 = "config_cache";
96357
- async function loadConfig2() {
96358
- const existing = getGlobalSingleton(CONFIG_KEY2, () => null);
96359
- if (existing)
96360
- return existing;
96361
- const configPath = resolve9(process.cwd(), "citadel.config.ts");
96362
- let userConfig = {};
96363
- if (existsSync8(configPath)) {
96364
- try {
96365
- const mod = await import(configPath);
96366
- userConfig = mod.default || {};
96367
- } catch (error48) {
96368
- console.warn("Failed to load citadel.config.ts:", error48);
96369
- }
96370
- }
96371
- const rawConfig = {
96372
- ...userConfig,
96373
- env: process.env.CITADEL_ENV || userConfig.env,
96374
- providers: {
96375
- ollama: {
96376
- baseURL: process.env.CITADEL_OLLAMA_BASE_URL || userConfig.providers?.ollama?.baseURL,
96377
- apiKey: process.env.CITADEL_OLLAMA_API_KEY || userConfig.providers?.ollama?.apiKey,
96378
- ...userConfig.providers?.ollama
96379
- },
96380
- openai: {
96381
- apiKey: process.env.OPENAI_API_KEY || userConfig.providers?.openai?.apiKey,
96382
- ...userConfig.providers?.openai
96383
- },
96384
- anthropic: {
96385
- apiKey: process.env.ANTHROPIC_API_KEY || userConfig.providers?.anthropic?.apiKey,
96386
- ...userConfig.providers?.anthropic
96387
- },
96388
- ...userConfig.providers
96389
- }
96390
- };
96391
- const parsed = ConfigSchema.safeParse(rawConfig);
96392
- if (!parsed.success) {
96393
- console.error("Configuration Error:", parsed.error.format());
96394
- throw new Error("Invalid Citadel Configuration");
96395
- }
96396
- const config2 = parsed.data;
96397
- setGlobalSingleton(CONFIG_KEY2, config2);
96398
- logger.debug("[Config] Loaded from file/env");
96399
- return config2;
96400
- }
96401
-
96402
- // src/core/pearls.ts
96403
- import { spawn as spawn4 } from "node:child_process";
96404
- import { resolve as resolve10 } from "node:path";
96405
- var PearlStatusSchema2 = exports_external.enum([
96406
- "open",
96407
- "in_progress",
96408
- "verify",
96409
- "done"
96410
- ]);
96411
- var PearlPrioritySchema2 = exports_external.any().transform((val) => {
96412
- if (typeof val === "number")
96413
- return val;
96414
- if (typeof val === "string") {
96415
- const s = val.replace(/^P/, "");
96416
- const n = parseInt(s, 10);
96417
- if (!Number.isNaN(n))
96418
- return n;
96419
- }
96420
- return 2;
96421
- });
96422
- var RawPearlSchema2 = exports_external.object({
96423
- id: exports_external.string(),
96424
- title: exports_external.string(),
96425
- status: exports_external.string(),
96426
- priority: PearlPrioritySchema2,
96427
- author: exports_external.string().optional(),
96428
- labels: exports_external.array(exports_external.string()).optional(),
96429
- links: exports_external.array(exports_external.object({
96430
- target_id: exports_external.string(),
96431
- link_type: exports_external.string()
96432
- })).optional(),
96433
- deps: exports_external.array(exports_external.object({
96434
- target_id: exports_external.string(),
96435
- dep_type: exports_external.string()
96436
- }).or(exports_external.string())).optional(),
96437
- metadata: exports_external.record(exports_external.string(), exports_external.any()).optional(),
96438
- description: exports_external.string().optional(),
96439
- created_at: exports_external.any(),
96440
- updated_at: exports_external.any()
96441
- });
96442
- var PearlSchema2 = exports_external.object({
96443
- id: exports_external.string(),
96444
- title: exports_external.string(),
96445
- status: PearlStatusSchema2,
96446
- priority: PearlPrioritySchema2,
96447
- assignee: exports_external.string().optional(),
96448
- labels: exports_external.array(exports_external.string()).optional(),
96449
- blockers: exports_external.array(exports_external.string()).optional(),
96450
- acceptance_test: exports_external.string().optional(),
96451
- parent: exports_external.string().optional(),
96452
- type: exports_external.string().optional(),
96453
- description: exports_external.string().optional(),
96454
- context: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
96455
- metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
96456
- output: exports_external.unknown().optional(),
96457
- created_at: exports_external.string(),
96458
- updated_at: exports_external.string()
96459
- });
96460
-
96461
- class PearlsClient2 {
96462
- basePath;
96463
- binary;
96464
- constructor(basePath, binary2) {
96465
- let config2 = null;
96466
- try {
96467
- config2 = getConfig();
96468
- } catch {}
96469
- this.basePath = basePath || config2?.pearls?.path || ".pearls";
96470
- this.binary = binary2 || config2?.pearls?.binary || "prl";
96471
- }
96472
- async runCommand(args) {
96473
- const cwd2 = this.basePath.endsWith(".pearls") ? resolve10(this.basePath, "..") : this.basePath;
96474
- try {
96475
- const { stdout, stderr } = await this.execute(this.binary, args, cwd2);
96476
- if (stderr && !stdout && !stderr.includes("warning")) {}
96477
- return stdout.trim();
96478
- } catch (error48) {
96479
- const err = error48;
96480
- throw new Error(`Pearls command failed: ${this.binary} ${args.join(" ")}
96481
- ${err.message}`);
96482
- }
96483
- }
96484
- async init() {
96485
- await this.runCommand(["init"]);
96486
- }
96487
- async execute(command, args, cwd2) {
96488
- return new Promise((resolve11, reject) => {
96489
- const child = spawn4(command, args, { cwd: cwd2 });
96490
- let stdout = "";
96491
- let stderr = "";
96492
- child.stdout.on("data", (data) => {
96493
- stdout += data.toString();
96494
- });
96495
- child.stderr.on("data", (data) => {
96496
- stderr += data.toString();
96497
- });
96498
- child.on("close", (code) => {
96499
- if (code === 0) {
96500
- resolve11({ stdout, stderr });
96501
- } else {
96502
- reject(new Error(`Process exited with code ${code}
96503
- ${stderr}`));
96504
- }
96505
- });
96506
- child.on("error", (err) => {
96507
- reject(err);
96508
- });
96509
- });
96510
- }
96511
- async sync() {
96512
- await this.runCommand(["sync"]);
96513
- logger.info(`[Pearls] Database synchronized`);
96514
- }
96515
- async doctor() {
96516
- try {
96517
- const output = await this.runCommand(["doctor", "--format", "json"]);
96518
- return !output.toLowerCase().includes("error");
96519
- } catch (_error) {
96520
- return false;
96521
- }
96522
- }
96523
- parseRaw(output) {
96524
- if (!output)
96525
- throw new Error("Empty output from prl");
96526
- let json4;
96527
- try {
96528
- json4 = JSON.parse(output);
96529
- } catch (_e) {
96530
- const start = output.indexOf("{");
96531
- if (start !== -1) {
96532
- let braceCount = 0;
96533
- let end = -1;
96534
- for (let i2 = start;i2 < output.length; i2++) {
96535
- if (output[i2] === "{")
96536
- braceCount++;
96537
- else if (output[i2] === "}") {
96538
- braceCount--;
96539
- if (braceCount === 0) {
96540
- end = i2;
96541
- break;
96542
- }
96543
- }
96544
- }
96545
- if (end !== -1) {
96546
- try {
96547
- json4 = JSON.parse(output.substring(start, end + 1));
96548
- } catch {
96549
- throw new Error(`Failed to parse Pearls JSON (with extraction): ${output}`);
96550
- }
96551
- } else {
96552
- throw new Error(`Failed to parse Pearls JSON (no end brace): ${output}`);
96553
- }
96554
- } else {
96555
- throw new Error(`Failed to parse Pearls JSON: ${output}`);
96556
- }
96557
- }
96558
- const pearl = json4.pearl || json4.issue || (Array.isArray(json4) ? json4[0] : json4);
96559
- if (!pearl || typeof pearl !== "object") {
96560
- if (json4.pearls && Array.isArray(json4.pearls) && json4.pearls.length > 0) {
96561
- return this.mapToDomain(RawPearlSchema2.parse(json4.pearls[0]));
96562
- }
96563
- throw new Error(`Unexpected Pearls output format: ${output}`);
96564
- }
96565
- const raw = RawPearlSchema2.parse(pearl);
96566
- return this.mapToDomain(raw);
96567
- }
96568
- parseRawList(output) {
96569
- if (!output)
96570
- return [];
96571
- let json4;
96572
- try {
96573
- json4 = JSON.parse(output);
96574
- } catch (_e) {
96575
- const start = output.indexOf("[") !== -1 ? output.indexOf("[") : output.indexOf("{");
96576
- if (start !== -1) {
96577
- const opener = output[start];
96578
- const closer = opener === "[" ? "]" : "}";
96579
- let count = 0;
96580
- let end = -1;
96581
- for (let i2 = start;i2 < output.length; i2++) {
96582
- if (output[i2] === opener)
96583
- count++;
96584
- else if (output[i2] === closer) {
96585
- count--;
96586
- if (count === 0) {
96587
- end = i2;
96588
- break;
96589
- }
96590
- }
96591
- }
96592
- if (end !== -1) {
96593
- try {
96594
- json4 = JSON.parse(output.substring(start, end + 1));
96595
- } catch {
96596
- return [];
96597
- }
96598
- } else {
96599
- return [];
96600
- }
96601
- } else {
96602
- return [];
96603
- }
96604
- }
96605
- const list = json4.pearls || json4.ready || json4.issues || (Array.isArray(json4) ? json4 : [json4]);
96606
- if (Array.isArray(list)) {
96607
- return list.map((item) => {
96608
- try {
96609
- return this.mapToDomain(RawPearlSchema2.parse(item));
96610
- } catch (e) {
96611
- console.warn(`[Pearls] Failed to parse pearl item:`, e, item);
96612
- return null;
96613
- }
96614
- }).filter((b) => !!b);
96615
- }
96616
- return [];
96617
- }
96618
- mapToDomain(raw) {
96619
- let status = "open";
96620
- const rawStatus = raw.status.toLowerCase();
96621
- if (rawStatus === "closed") {
96622
- status = "done";
96623
- } else if (rawStatus === "inprogress" || rawStatus === "in_progress") {
96624
- if (raw.labels?.includes("verify")) {
96625
- status = "verify";
96626
- } else {
96627
- status = "in_progress";
96628
- }
96629
- } else {
96630
- status = "open";
96631
- }
96632
- const blockers = [];
96633
- let parent;
96634
- if (raw.links) {
96635
- for (const link2 of raw.links) {
96636
- if (link2.link_type === "blocks") {
96637
- blockers.push(link2.target_id);
96638
- } else if (link2.link_type === "parent_child") {
96639
- parent = link2.target_id;
96640
- }
96641
- }
96642
- }
96643
- if (raw.deps && Array.isArray(raw.deps)) {
96644
- for (const dep of raw.deps) {
96645
- if (typeof dep === "string") {
96646
- blockers.push(dep);
96647
- } else {
96648
- if (dep.dep_type === "blocks") {
96649
- blockers.push(dep.target_id);
96650
- } else if (dep.dep_type === "parent_child") {
96651
- parent = dep.target_id;
96652
- }
96653
- }
96654
- }
96655
- }
96656
- const metadata = raw.metadata || {};
96657
- const acceptance_test = metadata.acceptance_test;
96658
- const type2 = metadata.type || metadata.issue_type;
96659
- let context2 = metadata.context;
96660
- const output = metadata.output;
96661
- const assignee = metadata.assignee || raw.author;
96662
- let description = raw.description || undefined;
96663
- if (description && !context2) {
96664
- const match2 = description.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
96665
- if (match2?.[1] && match2[2]) {
96666
- try {
96667
- const parsed = JSON.parse(match2[1]);
96668
- if (parsed && typeof parsed === "object") {
96669
- context2 = parsed;
96670
- description = match2[2];
96671
- }
96672
- } catch {}
96673
- }
96674
- }
96675
- return {
96676
- id: raw.id,
96677
- title: raw.title,
96678
- status,
96679
- priority: raw.priority,
96680
- assignee,
96681
- labels: raw.labels,
96682
- blockers,
96683
- acceptance_test,
96684
- parent,
96685
- type: type2,
96686
- description,
96687
- context: context2,
96688
- metadata,
96689
- output,
96690
- created_at: typeof raw.created_at === "number" ? new Date(raw.created_at * 1000).toISOString() : raw.created_at,
96691
- updated_at: typeof raw.updated_at === "number" ? new Date(raw.updated_at * 1000).toISOString() : raw.updated_at
96692
- };
96693
- }
96694
- async list(status) {
96695
- const args = ["list"];
96696
- if (status === "done")
96697
- args.push("--status", "closed");
96698
- else if (status === "verify")
96699
- args.push("--status", "in_progress");
96700
- else if (status === "in_progress")
96701
- args.push("--status", "in_progress");
96702
- else if (status === "open")
96703
- args.push("--status", "open");
96704
- args.push("--format", "json");
96705
- const output = await this.runCommand(args);
96706
- const pearls = this.parseRawList(output);
96707
- if (status) {
96708
- return pearls.filter((b) => b.status === status);
96709
- }
96710
- return pearls;
96711
- }
96712
- async ready() {
96713
- const output = await this.runCommand(["ready", "--format", "json"]);
96714
- return this.parseRawList(output);
96715
- }
96716
- async getAll() {
96717
- const output = await this.runCommand(["list", "--format", "json"]);
96718
- return this.parseRawList(output);
96719
- }
96720
- async get(id) {
96721
- const output = await this.runCommand(["show", id, "--format", "json"]);
96722
- return this.parseRaw(output);
96723
- }
96724
- async create(title, options = {}) {
96725
- const args = ["create", title];
96726
- if (options.priority !== undefined)
96727
- args.push("--priority", options.priority.toString());
96728
- if (options.description) {
96729
- args.push("--description", options.description);
96730
- }
96731
- if (options.assignee) {
96732
- args.push("--author", options.assignee);
96733
- }
96734
- args.push("--format", "json");
96735
- const output = await this.runCommand(args);
96736
- const pearl = this.parseRaw(output);
96737
- const updates = {};
96738
- if (options.acceptance_test)
96739
- updates.acceptance_test = options.acceptance_test;
96740
- if (options.type)
96741
- updates.type = options.type;
96742
- if (options.context)
96743
- updates.context = options.context;
96744
- if (options.assignee)
96745
- updates.assignee = options.assignee;
96746
- for (const [key, val] of Object.entries(updates)) {
96747
- const valStr = JSON.stringify(val);
96748
- await this.runCommand(["meta", "set", pearl.id, key, valStr, "--format", "json"]);
96749
- }
96750
- if (options.labels?.length) {
96751
- for (const label of options.labels) {
96752
- await this.runCommand(["update", pearl.id, "--add-label", label, "--format", "json"]);
96753
- }
96754
- }
96755
- if (options.blockers?.length) {
96756
- for (const blockerId of options.blockers) {
96757
- await this.runCommand(["link", pearl.id, blockerId, "blocks", "--format", "json"]);
96758
- }
96759
- }
96760
- if (options.parent) {
96761
- await this.runCommand(["link", pearl.id, options.parent, "parent_child", "--format", "json"]);
96762
- }
96763
- return this.get(pearl.id);
96764
- }
96765
- async update(id, changes) {
96766
- let current;
96767
- if (changes.status) {
96768
- current = await this.get(id);
96769
- this.validateTransition(current, changes.status);
96770
- const isFailed = changes.labels?.includes("failed") || !changes.labels && current.labels?.includes("failed");
96771
- if (changes.status === "done" && !isFailed && !current.acceptance_test && !changes.acceptance_test) {
96772
- throw new Error(`Cannot transition ${id} to 'done': missing acceptance_test`);
96773
- }
96774
- }
96775
- const args = ["update", id];
96776
- if (changes.assignee) {
96777
- await this.runCommand(["meta", "set", id, "assignee", JSON.stringify(changes.assignee), "--format", "json"]);
96778
- }
96779
- if (changes.status) {
96780
- if (!current)
96781
- current = await this.get(id);
96782
- let targetCliStatus = "";
96783
- if (changes.status === "done")
96784
- targetCliStatus = "closed";
96785
- else if (changes.status === "verify")
96786
- targetCliStatus = "in_progress";
96787
- else if (changes.status === "in_progress")
96788
- targetCliStatus = "in_progress";
96789
- else if (changes.status === "open")
96790
- targetCliStatus = "open";
96791
- let currentCliStatus = "";
96792
- if (current.status === "done")
96793
- currentCliStatus = "closed";
96794
- else if (current.status === "verify")
96795
- currentCliStatus = "in_progress";
96796
- else if (current.status === "in_progress")
96797
- currentCliStatus = "in_progress";
96798
- else if (current.status === "open")
96799
- currentCliStatus = "open";
96800
- if (targetCliStatus && targetCliStatus !== currentCliStatus) {
96801
- args.push("--status", targetCliStatus);
96802
- }
96803
- if (changes.status === "verify") {
96804
- args.push("--add-label", "verify");
96805
- } else if (changes.status === "in_progress") {
96806
- args.push("--remove-label", "verify");
96807
- } else if (changes.status === "open") {
96808
- args.push("--remove-label", "verify");
96809
- }
96810
- }
96811
- if (changes.title) {
96812
- args.push("--title", changes.title);
96813
- }
96814
- if (changes.description) {
96815
- args.push("--description", changes.description);
96816
- }
96817
- if (changes.labels) {
96818
- for (const label of changes.labels) {
96819
- args.push("--add-label", label);
96820
- }
96821
- }
96822
- if (changes.remove_labels) {
96823
- for (const label of changes.remove_labels) {
96824
- args.push("--remove-label", label);
96825
- }
96826
- }
96827
- args.push("--format", "json");
96828
- const output = await this.runCommand(args);
96829
- if (changes.acceptance_test) {
96830
- const valStr = JSON.stringify(changes.acceptance_test);
96831
- await this.runCommand(["meta", "set", id, "acceptance_test", valStr, "--format", "json"]);
96832
- }
96833
- if (changes.type) {
96834
- const valStr = JSON.stringify(changes.type);
96835
- await this.runCommand(["meta", "set", id, "type", valStr, "--format", "json"]);
96836
- }
96837
- if (changes.context) {
96838
- const valStr = JSON.stringify(changes.context);
96839
- await this.runCommand(["meta", "set", id, "context", valStr, "--format", "json"]);
96840
- }
96841
- if (changes.output !== undefined) {
96842
- const valStr = JSON.stringify(changes.output);
96843
- await this.runCommand(["meta", "set", id, "output", valStr, "--format", "json"]);
96844
- }
96845
- let finalPearl;
96846
- if (!output) {
96847
- finalPearl = await this.get(id);
96848
- } else {
96849
- finalPearl = this.parseRaw(output);
96850
- }
96851
- if (changes.status === "done" && current?.status !== "done") {
96852
- try {
96853
- const config2 = getConfig();
96854
- if (config2.hooks?.onPearlDone) {
96855
- await config2.hooks.onPearlDone(finalPearl);
96856
- }
96857
- } catch (err) {
96858
- logger.error(`[Pearls] Error executing onPearlDone hook for ${id}:`, err);
96859
- }
96860
- }
96861
- return finalPearl;
96862
- }
96863
- validateTransition(current, next) {
96864
- const validTransitions = {
96865
- open: ["in_progress", "done"],
96866
- in_progress: ["verify", "open"],
96867
- verify: ["done", "in_progress", "open"],
96868
- done: ["in_progress", "open"]
96869
- };
96870
- if (current.status === next)
96871
- return;
96872
- const allowed = validTransitions[current.status];
96873
- if (!allowed.includes(next)) {
96874
- throw new Error(`Invalid state transition for ${current.id}: ${current.status} -> ${next}`);
96875
- }
96876
- }
96877
- async addDependency(childId, parentId) {
96878
- await this.runCommand(["link", childId, parentId, "blocks"]);
96879
- }
96880
- async addComment(id, comment) {
96881
- return this.runCommand(["comments", "add", id, comment]);
96882
- }
96883
- async listComments(id) {
96884
- const output = await this.runCommand(["comments", "list", id, "--format", "json"]);
96885
- if (!output)
96886
- return [];
96887
- try {
96888
- const json4 = JSON.parse(output);
96889
- const comments = json4.comments || (Array.isArray(json4) ? json4 : []);
96890
- return comments.map((c) => ({
96891
- author: c.author || "unknown",
96892
- content: c.content || c.body || "",
96893
- created_at: typeof c.created_at === "number" ? new Date(c.created_at * 1000).toISOString() : c.created_at
96894
- }));
96895
- } catch (error48) {
96896
- logger.error(`[Pearls] Failed to parse comments for ${id}:`, error48);
96897
- return [];
96898
- }
96899
- }
96900
- }
96901
- var PEARLS_KEY2 = "pearls_client";
96902
- function getPearls2(basePath) {
96903
- return getGlobalSingleton(PEARLS_KEY2, () => {
96904
- let path2 = basePath;
96905
- if (!path2) {
96906
- try {
96907
- const config2 = getConfig();
96908
- path2 = config2.pearls.path;
96909
- } catch {
96910
- path2 = ".pearls";
96911
- }
96912
- }
96913
- return new PearlsClient2(path2);
96914
- });
96915
- }
96916
-
96917
- // src/core/queue.ts
96918
- import { Database as Database2 } from "bun:sqlite";
96919
- import { mkdirSync as mkdirSync4 } from "node:fs";
96920
- import { dirname as dirname4, resolve as resolve11 } from "node:path";
96921
- var TicketStatusSchema2 = exports_external.enum([
96922
- "queued",
96923
- "processing",
96924
- "completed",
96925
- "failed"
96926
- ]);
96927
- var TicketSchema2 = exports_external.object({
96928
- id: exports_external.string(),
96929
- pearl_id: exports_external.string(),
96930
- status: TicketStatusSchema2,
96931
- priority: exports_external.number().min(0).max(3),
96932
- target_role: exports_external.enum(["worker", "gatekeeper"]),
96933
- assignee_id: exports_external.string().nullable(),
96934
- created_at: exports_external.number(),
96935
- started_at: exports_external.number().nullable(),
96936
- completed_at: exports_external.number().nullable(),
96937
- heartbeat_at: exports_external.number().nullable(),
96938
- retry_count: exports_external.number(),
96939
- next_attempt_at: exports_external.number().nullable(),
96940
- output: exports_external.unknown().optional()
96941
- });
96942
-
96943
- class WorkQueue2 {
96944
- db;
96945
- constructor(dbPath) {
96946
- const finalPath = dbPath || resolve11(process.cwd(), ".citadel", "queue.sqlite");
96947
- mkdirSync4(dirname4(finalPath), { recursive: true });
96948
- this.db = new Database2(finalPath);
96949
- this.init();
96950
- }
96951
- init() {
96952
- this.db.run(`
96953
- CREATE TABLE IF NOT EXISTS tickets (
96954
- id TEXT PRIMARY KEY,
96955
- pearl_id TEXT NOT NULL,
96956
- status TEXT NOT NULL,
96957
- priority INTEGER NOT NULL,
96958
- target_role TEXT NOT NULL,
96959
- assignee_id TEXT,
96960
- created_at INTEGER NOT NULL,
96961
- started_at INTEGER,
96962
- completed_at INTEGER,
96963
- heartbeat_at INTEGER,
96964
- retry_count INTEGER DEFAULT 0,
96965
- next_attempt_at INTEGER
96966
- )
96967
- `);
96968
- this.db.run(`CREATE INDEX IF NOT EXISTS idx_status_priority ON tickets(status, priority ASC, created_at ASC)`);
96969
- this.db.run(`CREATE INDEX IF NOT EXISTS idx_pearl_id ON tickets(pearl_id)`);
96970
- try {
96971
- this.db.run(`ALTER TABLE tickets ADD COLUMN output TEXT`);
96972
- } catch {}
96973
- try {
96974
- this.db.run(`ALTER TABLE tickets ADD COLUMN next_attempt_at INTEGER`);
96975
- } catch {}
96976
- }
96977
- enqueue(pearlId, priority, targetRole) {
96978
- const id = crypto.randomUUID();
96979
- const now = Date.now();
96980
- const finalPriority = priority ?? 1;
96981
- const finalRole = targetRole ?? "worker";
96982
- this.db.run(`
96983
- INSERT INTO tickets (id, pearl_id, status, priority, target_role, created_at, retry_count)
96984
- VALUES (?, ?, 'queued', ?, ?, ?, 0)
96985
- `, [id, pearlId, finalPriority, finalRole, now]);
96986
- }
96987
- claim(assigneeId, role) {
96988
- const transaction = this.db.transaction(() => {
96989
- const now = Date.now();
96990
- const candidate = this.db.query(`
96991
- SELECT * FROM tickets
96992
- WHERE status = 'queued' AND target_role = ?
96993
- AND (next_attempt_at IS NULL OR next_attempt_at <= ?)
96994
- ORDER BY priority ASC, created_at ASC
96995
- LIMIT 1
96996
- `).get(role, now);
96997
- if (!candidate)
96998
- return null;
96999
- logger.info(`[Queue] Claiming ticket ${candidate.id} for ${assigneeId} (role: ${role})`);
97000
- this.db.run(`
97001
- UPDATE tickets
97002
- SET status = 'processing', assignee_id = ?, started_at = ?, heartbeat_at = ?
97003
- WHERE id = ?
97004
- `, [assigneeId, now, now, candidate.id]);
97005
- return this.db.query(`SELECT * FROM tickets WHERE id = ?`).get(candidate.id);
97006
- });
97007
- return transaction();
97008
- }
97009
- heartbeat(ticketId) {
97010
- this.db.run(`
97011
- UPDATE tickets
97012
- SET heartbeat_at = ?
97013
- WHERE id = ? AND status = 'processing'
97014
- `, [Date.now(), ticketId]);
97015
- }
97016
- complete(ticketId, output) {
97017
- logger.info(`[Queue] Completing ticket ${ticketId}`);
97018
- const now = Date.now();
97019
- let result;
97020
- if (output !== undefined && output !== null) {
97021
- const outputJson = JSON.stringify(output);
97022
- result = this.db.run(`
97023
- UPDATE tickets
97024
- SET status = 'completed', completed_at = ?, output = ?
97025
- WHERE id = ? AND status = 'processing'
97026
- `, [now, outputJson, ticketId]);
97027
- } else {
97028
- result = this.db.run(`
97029
- UPDATE tickets
97030
- SET status = 'completed', completed_at = ?
97031
- WHERE id = ? AND status = 'processing'
97032
- `, [now, ticketId]);
97033
- }
97034
- if (result.changes === 0) {
97035
- const current = this.db.query(`SELECT status FROM tickets WHERE id = ?`).get(ticketId);
97036
- if (current && current.status === "completed") {
97037
- logger.debug(`[Queue] Ticket ${ticketId} already completed (idempotent).`);
97038
- return;
97039
- }
97040
- const status = current?.status || "unknown";
97041
- logger.warn(`[Queue] Failed to complete ticket ${ticketId}: Expected 'processing', found '${status}'.`);
97042
- if (status === "queued") {
97043
- logger.warn(`[Queue] Ticket ${ticketId} was reset to 'queued' while active. Ignoring completion to avoid state corruption.`);
97044
- return;
97045
- }
97046
- throw new Error(`Failed to complete ticket ${ticketId}: Ticket is not in 'processing' state (current: ${status}).`);
97047
- }
97048
- }
97049
- getOutput(pearlId) {
97050
- const result = this.db.query(`
97051
- SELECT output FROM tickets
97052
- WHERE pearl_id = ? AND status = 'completed'
97053
- ORDER BY completed_at DESC
97054
- LIMIT 1
97055
- `).get(pearlId);
97056
- if (result?.output) {
97057
- return JSON.parse(result.output);
97058
- }
97059
- return null;
97060
- }
97061
- fail(ticketId, permanent = false, maxRetries = 10) {
97062
- if (permanent) {
97063
- this.db.run(`
97064
- UPDATE tickets
97065
- SET status = 'failed'
97066
- WHERE id = ? AND status = 'processing'
97067
- `, [ticketId]);
97068
- } else {
97069
- const now = Date.now();
97070
- const current = this.db.query(`SELECT retry_count FROM tickets WHERE id = ?`).get(ticketId);
97071
- const nextRetry = (current?.retry_count || 0) + 1;
97072
- if (nextRetry > maxRetries) {
97073
- logger.warn(`[Queue] Ticket ${ticketId} exceeded max retries (${maxRetries}). Failing permanently.`);
97074
- this.db.run(`
97075
- UPDATE tickets
97076
- SET status = 'failed'
97077
- WHERE id = ? AND status = 'processing'
97078
- `, [ticketId]);
97079
- return;
97080
- }
97081
- const nextAttempt = now + Math.min(1000 * 2 ** nextRetry, 300000);
97082
- logger.info(`[Queue] Failing ticket ${ticketId} (retry ${nextRetry}/${maxRetries}, next attempt at ${new Date(nextAttempt).toISOString()})`);
97083
- this.db.run(`
97084
- UPDATE tickets
97085
- SET status = 'queued', assignee_id = NULL, started_at = NULL, heartbeat_at = NULL,
97086
- retry_count = ?, next_attempt_at = ?
97087
- WHERE id = ? AND status = 'processing'
97088
- `, [nextRetry, nextAttempt, ticketId]);
97089
- }
97090
- }
97091
- releaseStalled(timeoutMs) {
97092
- const cutoff = Date.now() - timeoutMs;
97093
- const stalled = this.db.query(`
97094
- SELECT id FROM tickets
97095
- WHERE status = 'processing' AND heartbeat_at < ?
97096
- `).all(cutoff);
97097
- if (stalled.length === 0)
97098
- return 0;
97099
- const releaseStmt = this.db.prepare(`
97100
- UPDATE tickets
97101
- SET status = 'queued', assignee_id = NULL, started_at = NULL, heartbeat_at = NULL,
97102
- retry_count = retry_count + 1, next_attempt_at = ?
97103
- WHERE id = ?
97104
- `);
97105
- const transaction = this.db.transaction(() => {
97106
- const now = Date.now();
97107
- for (const ticket of stalled) {
97108
- const t = this.db.query(`SELECT retry_count FROM tickets WHERE id = ?`).get(ticket.id);
97109
- const nextRetry = (t?.retry_count || 0) + 1;
97110
- const nextAttempt = now + Math.min(1000 * 2 ** nextRetry, 300000);
97111
- releaseStmt.run(nextAttempt, ticket.id);
97112
- }
97113
- });
97114
- transaction();
97115
- return stalled.length;
97116
- }
97117
- getActiveTicket(pearlId) {
97118
- return this.db.query(`
97119
- SELECT * FROM tickets
97120
- WHERE pearl_id = ? AND status IN ('queued', 'processing')
97121
- `).get(pearlId);
97122
- }
97123
- getLatestTicket(pearlId) {
97124
- return this.db.query(`
97125
- SELECT * FROM tickets
97126
- WHERE pearl_id = ?
97127
- ORDER BY created_at DESC
97128
- LIMIT 1
97129
- `).get(pearlId);
97130
- }
97131
- resetPearl(pearlId) {
97132
- this.db.run("DELETE FROM tickets WHERE pearl_id = ?", [pearlId]);
97133
- }
97134
- listActive() {
97135
- return this.db.query("SELECT * FROM tickets WHERE status = 'processing'").all();
97136
- }
97137
- getTicketsByStatus(status) {
97138
- return this.db.query("SELECT * FROM tickets WHERE status = ?").all(status);
97139
- }
97140
- getAllTickets(status) {
97141
- if (status) {
97142
- return this.db.query("SELECT * FROM tickets WHERE status = ? ORDER BY created_at DESC").all(status);
97143
- }
97144
- return this.db.query("SELECT * FROM tickets ORDER BY created_at DESC").all();
97145
- }
97146
- getPendingCount(role) {
97147
- const result = this.db.query(`
97148
- SELECT COUNT(*) as count
97149
- FROM tickets
97150
- WHERE status = 'queued' AND target_role = ?
97151
- `).get(role);
97152
- return result.count;
97153
- }
97154
- close() {
97155
- this.db.close();
97156
- }
97157
- }
97158
- var QUEUE_KEY2 = "work_queue";
97159
- function getQueue2() {
97160
- return getGlobalSingleton(QUEUE_KEY2, () => new WorkQueue2);
97161
- }
97162
-
97163
- // src/services/conductor.ts
97164
- class Conductor2 {
97165
- isRunning = false;
97166
- routerTimer = null;
97167
- consecutiveFailures = 0;
97168
- config;
97169
- pools = new Map;
97170
- gatekeeperPool;
97171
- pearls;
97172
- queue;
97173
- constructor(pearls, queue, config2, PoolClass = WorkerPool) {
97174
- this.pearls = pearls || getPearls();
97175
- this.queue = queue || getQueue();
97176
- this.config = config2 || getConfig();
97177
- logger.info(`[Conductor] Queue DB: ${this.queue.db?.filename}`);
97178
- logger.info(`[Conductor] Config: min_workers=${this.config.worker.min_workers}`);
97179
- const roles = Object.keys(this.config.agents).filter((r) => r !== "gatekeeper");
97180
- for (const role of roles) {
97181
- const pool = new PoolClass(role, (id) => new Hook(id, role, async (ticket) => {
97182
- logger.info(`[${role}] Processing ${ticket.pearl_id}`, {
97183
- pearlId: ticket.pearl_id
97184
- });
97185
- const pearl = await this.pearls.get(ticket.pearl_id).catch(() => null);
97186
- if (!pearl) {
97187
- logger.error(`[${role}] Failed to retrieve pearl ${ticket.pearl_id} for processing`, { pearlId: ticket.pearl_id });
97188
- this.queue.fail(ticket.id, true);
97189
- return;
97190
- }
97191
- await this.pearls.update(ticket.pearl_id, {
97192
- status: "in_progress",
97193
- assignee: role
97194
- });
97195
- logger.info(`[${role}] Instantiating agent for role: ${role}`, { pearlId: ticket.pearl_id });
97196
95792
  const agent = new WorkerAgent(role);
97197
95793
  try {
97198
95794
  const result = await agent.run(`Process this task: ${pearl.title}`, { pearlId: ticket.pearl_id, pearl });
@@ -97227,6 +95823,13 @@ class Conductor2 {
97227
95823
  if (!submittedWork) {
97228
95824
  logger.warn(`[Gatekeeper] No submitted work found for ${ticket.pearl_id} (retrieved 'null' from queue). Evaluator may reject.`, { pearlId: ticket.pearl_id });
97229
95825
  }
95826
+ if (this.config.hooks?.onPearlStart) {
95827
+ try {
95828
+ await this.config.hooks.onPearlStart(pearl);
95829
+ } catch (err) {
95830
+ logger.error(`[Gatekeeper] Error in onPearlStart hook:`, err);
95831
+ }
95832
+ }
97230
95833
  try {
97231
95834
  await agent.run(`Verify this work: ${pearl.title}`, {
97232
95835
  pearlId: ticket.pearl_id,
@@ -97492,6 +96095,307 @@ class Conductor2 {
97492
96095
  }
97493
96096
  }
97494
96097
 
96098
+ // src/bridge/components/Dashboard.tsx
96099
+ var import_react27 = __toESM(require_react(), 1);
96100
+
96101
+ // src/bridge/components/MoleculeTree.tsx
96102
+ var import_react26 = __toESM(require_react(), 1);
96103
+ var jsx_dev_runtime = __toESM(require_jsx_dev_runtime(), 1);
96104
+ var buildTree = (pearls) => {
96105
+ const map4 = new Map;
96106
+ const roots = [];
96107
+ for (const pearl of pearls) {
96108
+ map4.set(pearl.id, { pearl, children: [] });
96109
+ }
96110
+ for (const pearl of pearls) {
96111
+ const node = map4.get(pearl.id);
96112
+ if (!node)
96113
+ continue;
96114
+ if (pearl.parent && map4.has(pearl.parent)) {
96115
+ const parent = map4.get(pearl.parent);
96116
+ if (parent) {
96117
+ parent.children.push(node);
96118
+ }
96119
+ } else {
96120
+ roots.push(node);
96121
+ }
96122
+ }
96123
+ return roots;
96124
+ };
96125
+ var PearlNode = ({ node, depth }) => {
96126
+ const indent = " ".repeat(depth);
96127
+ const color = node.pearl.status === "done" ? "green" : node.pearl.status === "verify" ? "magenta" : node.pearl.status === "in_progress" ? "yellow" : "white";
96128
+ const icon = node.pearl.status === "done" ? "✓" : node.pearl.status === "verify" ? "?" : node.pearl.status === "in_progress" ? "▶" : "○";
96129
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
96130
+ flexDirection: "column",
96131
+ children: [
96132
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
96133
+ color,
96134
+ children: [
96135
+ indent,
96136
+ icon,
96137
+ " ",
96138
+ node.pearl.title,
96139
+ " ",
96140
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
96141
+ color: "gray",
96142
+ children: [
96143
+ "(",
96144
+ node.pearl.id,
96145
+ ")"
96146
+ ]
96147
+ }, undefined, true, undefined, this)
96148
+ ]
96149
+ }, undefined, true, undefined, this),
96150
+ node.children.map((child) => /* @__PURE__ */ jsx_dev_runtime.jsxDEV(PearlNode, {
96151
+ node: child,
96152
+ depth: depth + 1
96153
+ }, child.pearl.id, false, undefined, this))
96154
+ ]
96155
+ }, undefined, true, undefined, this);
96156
+ };
96157
+ var MoleculeTree = () => {
96158
+ const [tree, setTree] = import_react26.useState([]);
96159
+ import_react26.useEffect(() => {
96160
+ const refresh = async () => {
96161
+ try {
96162
+ const pearls = await getPearls().getAll();
96163
+ setTree(buildTree(pearls));
96164
+ } catch (_e) {}
96165
+ };
96166
+ refresh();
96167
+ const timer = setInterval(refresh, 2000);
96168
+ return () => clearInterval(timer);
96169
+ }, []);
96170
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
96171
+ flexDirection: "column",
96172
+ borderStyle: "round",
96173
+ borderColor: "magenta",
96174
+ width: "50%",
96175
+ height: 15,
96176
+ overflowY: "hidden",
96177
+ children: [
96178
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
96179
+ bold: true,
96180
+ children: "Molecules"
96181
+ }, undefined, false, undefined, this),
96182
+ tree.length === 0 ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
96183
+ color: "gray",
96184
+ children: "No molecules found"
96185
+ }, undefined, false, undefined, this) : null,
96186
+ tree.map((node) => /* @__PURE__ */ jsx_dev_runtime.jsxDEV(PearlNode, {
96187
+ node,
96188
+ depth: 0
96189
+ }, node.pearl.id, false, undefined, this))
96190
+ ]
96191
+ }, undefined, true, undefined, this);
96192
+ };
96193
+
96194
+ // src/bridge/components/Dashboard.tsx
96195
+ var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
96196
+ var LogStream = () => {
96197
+ const config2 = getConfig();
96198
+ const maxLogs = config2.bridge?.maxLogs || 1000;
96199
+ const [logs, setLogs] = import_react27.useState([]);
96200
+ const [scrollTop, setScrollTop] = import_react27.useState(0);
96201
+ const [viewHeight, _setViewHeight] = import_react27.useState(15);
96202
+ import_react27.useEffect(() => {
96203
+ const handler = (entry) => {
96204
+ setLogs((prev) => {
96205
+ const updated = [...prev, entry];
96206
+ if (updated.length > maxLogs) {
96207
+ return updated.slice(updated.length - maxLogs);
96208
+ }
96209
+ return updated;
96210
+ });
96211
+ };
96212
+ logger.on("log", handler);
96213
+ return () => {
96214
+ logger.off("log", handler);
96215
+ };
96216
+ }, [maxLogs]);
96217
+ use_input_default((_input, key) => {
96218
+ if (key.upArrow) {
96219
+ setScrollTop((prev) => Math.min(prev + 1, Math.max(0, logs.length - viewHeight)));
96220
+ }
96221
+ if (key.downArrow) {
96222
+ setScrollTop((prev) => Math.max(0, prev - 1));
96223
+ }
96224
+ if (key.pageUp) {
96225
+ setScrollTop((prev) => Math.min(prev + 10, Math.max(0, logs.length - viewHeight)));
96226
+ }
96227
+ if (key.pageDown) {
96228
+ setScrollTop((prev) => Math.max(0, prev - 10));
96229
+ }
96230
+ });
96231
+ const total = logs.length;
96232
+ const effectiveHeight = Math.min(total, viewHeight);
96233
+ let startIndex = total - effectiveHeight - scrollTop;
96234
+ if (startIndex < 0)
96235
+ startIndex = 0;
96236
+ const endIndex = startIndex + effectiveHeight;
96237
+ const viewLogs = logs.slice(startIndex, endIndex);
96238
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
96239
+ flexDirection: "column",
96240
+ borderStyle: "round",
96241
+ borderColor: scrollTop > 0 ? "yellow" : "green",
96242
+ width: "100%",
96243
+ height: viewHeight + 2,
96244
+ children: [
96245
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
96246
+ flexDirection: "row",
96247
+ justifyContent: "space-between",
96248
+ children: [
96249
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96250
+ bold: true,
96251
+ children: [
96252
+ "Live Logs ",
96253
+ scrollTop > 0 ? `(Scrolled: -${scrollTop})` : "(Live)"
96254
+ ]
96255
+ }, undefined, true, undefined, this),
96256
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96257
+ color: "gray",
96258
+ children: [
96259
+ logs.length,
96260
+ "/",
96261
+ maxLogs
96262
+ ]
96263
+ }, undefined, true, undefined, this)
96264
+ ]
96265
+ }, undefined, true, undefined, this),
96266
+ viewLogs.map((l, i2) => /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96267
+ wrap: "truncate",
96268
+ children: [
96269
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96270
+ color: "gray",
96271
+ children: [
96272
+ "[",
96273
+ l.timestamp.split("T")[1]?.split(".")[0] || "00:00:00",
96274
+ "]"
96275
+ ]
96276
+ }, undefined, true, undefined, this),
96277
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96278
+ color: l.level === "error" ? "red" : l.level === "warn" ? "yellow" : "white",
96279
+ children: [
96280
+ " ",
96281
+ "[",
96282
+ l.level.toUpperCase(),
96283
+ "]",
96284
+ " "
96285
+ ]
96286
+ }, undefined, true, undefined, this),
96287
+ l.message
96288
+ ]
96289
+ }, `${l.timestamp}-${startIndex + i2}`, true, undefined, this))
96290
+ ]
96291
+ }, undefined, true, undefined, this);
96292
+ };
96293
+ var AgentMatrix = () => {
96294
+ const [activeTickets, setActiveTickets] = import_react27.useState([]);
96295
+ import_react27.useEffect(() => {
96296
+ const timer = setInterval(() => {
96297
+ const queue = getQueue();
96298
+ const rows = queue.getTicketsByStatus("processing");
96299
+ if (rows)
96300
+ setActiveTickets(rows);
96301
+ }, 1000);
96302
+ return () => clearInterval(timer);
96303
+ }, []);
96304
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
96305
+ flexDirection: "column",
96306
+ borderStyle: "round",
96307
+ borderColor: "blue",
96308
+ width: "50%",
96309
+ children: [
96310
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96311
+ bold: true,
96312
+ children: "Active Agents"
96313
+ }, undefined, false, undefined, this),
96314
+ activeTickets.length === 0 ? /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96315
+ color: "gray",
96316
+ children: "No active agents"
96317
+ }, undefined, false, undefined, this) : null,
96318
+ activeTickets.map((t) => /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
96319
+ flexDirection: "row",
96320
+ children: [
96321
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96322
+ color: "cyan",
96323
+ children: t.target_role
96324
+ }, undefined, false, undefined, this),
96325
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96326
+ children: " -> "
96327
+ }, undefined, false, undefined, this),
96328
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96329
+ children: t.pearl_id
96330
+ }, undefined, false, undefined, this),
96331
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96332
+ color: "yellow",
96333
+ children: [
96334
+ " (",
96335
+ t.status,
96336
+ ")"
96337
+ ]
96338
+ }, undefined, true, undefined, this)
96339
+ ]
96340
+ }, t.id, true, undefined, this))
96341
+ ]
96342
+ }, undefined, true, undefined, this);
96343
+ };
96344
+ var Dashboard = () => {
96345
+ const [status] = import_react27.useState("Online");
96346
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
96347
+ flexDirection: "column",
96348
+ padding: 1,
96349
+ children: [
96350
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
96351
+ children: [
96352
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96353
+ children: "The Citadel Bridge "
96354
+ }, undefined, false, undefined, this),
96355
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96356
+ color: "green",
96357
+ children: [
96358
+ "[",
96359
+ status,
96360
+ "]"
96361
+ ]
96362
+ }, undefined, true, undefined, this)
96363
+ ]
96364
+ }, undefined, true, undefined, this),
96365
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
96366
+ flexDirection: "row",
96367
+ children: [
96368
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(AgentMatrix, {}, undefined, false, undefined, this),
96369
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(MoleculeTree, {}, undefined, false, undefined, this)
96370
+ ]
96371
+ }, undefined, true, undefined, this),
96372
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(LogStream, {}, undefined, false, undefined, this),
96373
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
96374
+ color: "gray",
96375
+ children: "Press Ctrl+C to exit"
96376
+ }, undefined, false, undefined, this)
96377
+ ]
96378
+ }, undefined, true, undefined, this);
96379
+ };
96380
+
96381
+ // src/bridge/index.tsx
96382
+ var jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
96383
+ async function startBridge() {
96384
+ await loadConfig();
96385
+ logger.setConsoleEnabled(false);
96386
+ const conductor = new Conductor;
96387
+ conductor.start();
96388
+ const { waitUntilExit } = render_default(/* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Dashboard, {}, undefined, false, undefined, this));
96389
+ try {
96390
+ await waitUntilExit();
96391
+ } catch (error48) {
96392
+ console.error("Bridge crashed:", error48);
96393
+ } finally {
96394
+ conductor.stop();
96395
+ console.log("");
96396
+ }
96397
+ }
96398
+
97495
96399
  // src/services/workflow-engine.ts
97496
96400
  class WorkflowEngine {
97497
96401
  registry;
@@ -97655,7 +96559,7 @@ function getWorkflowEngine() {
97655
96559
  }
97656
96560
 
97657
96561
  // src/cli.ts
97658
- var __dirname2 = dirname5(fileURLToPath(import.meta.url));
96562
+ var __dirname2 = dirname4(fileURLToPath(import.meta.url));
97659
96563
  var packageJson = JSON.parse(readFileSync3(join6(__dirname2, "../package.json"), "utf-8"));
97660
96564
  var version2 = packageJson.version;
97661
96565
  var program2 = new Command;
@@ -97663,16 +96567,16 @@ program2.name("citadel").description("The Citadel: A deterministic agent orchest
97663
96567
  function runCLI() {
97664
96568
  program2.command("init").description("Initialize a new Citadel project (Foundry Mode)").action(async () => {
97665
96569
  try {
97666
- console.log("\uD83C\uDFD7\uFE0F Initializing The Citadel...");
96570
+ console.log("\uD83C\uDFD7 Initializing The Citadel...");
97667
96571
  const cwd2 = process.cwd();
97668
96572
  const citadelDir = join6(cwd2, ".citadel");
97669
96573
  const formulasDir = join6(citadelDir, "formulas");
97670
96574
  await mkdir(formulasDir, { recursive: true });
97671
- console.log("\u2705 Created .citadel/ structure");
96575
+ console.log(" Created .citadel/ structure");
97672
96576
  const configPath = join6(cwd2, "citadel.config.ts");
97673
96577
  try {
97674
96578
  await access(configPath);
97675
- console.log("\u2139\uFE0F citadel.config.ts already exists");
96579
+ console.log("ℹ️ citadel.config.ts already exists");
97676
96580
  } catch {
97677
96581
  const configTemplate = `
97678
96582
  export default {
@@ -97723,12 +96627,12 @@ export default {
97723
96627
  };
97724
96628
  `;
97725
96629
  await writeFile(configPath, configTemplate.trim());
97726
- console.log("\u2705 Created citadel.config.ts (Ollama default)");
96630
+ console.log(" Created citadel.config.ts (Ollama default)");
97727
96631
  }
97728
96632
  const agentsPath = join6(cwd2, "AGENTS.md");
97729
96633
  try {
97730
96634
  await access(agentsPath);
97731
- console.log("\u2139\uFE0F AGENTS.md already exists");
96635
+ console.log("ℹ️ AGENTS.md already exists");
97732
96636
  } catch {
97733
96637
  const agentsTemplate = `
97734
96638
  # Project Rules
@@ -97746,7 +96650,7 @@ export default {
97746
96650
  - Always write a plan before coding.
97747
96651
  `;
97748
96652
  await writeFile(agentsPath, agentsTemplate.trim());
97749
- console.log("\u2705 Created AGENTS.md");
96653
+ console.log(" Created AGENTS.md");
97750
96654
  }
97751
96655
  const formulaPath = join6(formulasDir, "hello_world.toml");
97752
96656
  try {
@@ -97767,26 +96671,26 @@ title = "Say Hello to {{name}}"
97767
96671
  description = "Create a file named hello_{{name}}.txt with a greeting."
97768
96672
  `;
97769
96673
  await writeFile(formulaPath, formulaTemplate.trim());
97770
- console.log("\u2705 Created .citadel/formulas/hello_world.toml");
96674
+ console.log(" Created .citadel/formulas/hello_world.toml");
97771
96675
  }
97772
96676
  console.log("\uD83D\uDD04 Initializing Pearls DB...");
97773
- const pearls = getPearls2(join6(cwd2, ".pearls"));
96677
+ const pearls = getPearls(join6(cwd2, ".pearls"));
97774
96678
  await pearls.init();
97775
- console.log("\u2705 Pearls initialized");
96679
+ console.log(" Pearls initialized");
97776
96680
  console.log(`
97777
96681
  \uD83D\uDE80 Citadel initialized successfully!`);
97778
96682
  console.log("Try running:");
97779
96683
  console.log(' citadel create "My First Run" -f hello_world -v name=Developer');
97780
96684
  console.log(" citadel start");
97781
96685
  } catch (error48) {
97782
- console.error("\u274C Init failed:", error48);
96686
+ console.error(" Init failed:", error48);
97783
96687
  process.exit(1);
97784
96688
  }
97785
96689
  });
97786
96690
  program2.command("start").description("Start the Citadel Conductor service").action(async () => {
97787
96691
  try {
97788
- await loadConfig2();
97789
- const conductor = new Conductor2;
96692
+ await loadConfig();
96693
+ const conductor = new Conductor;
97790
96694
  process.on("SIGINT", () => {
97791
96695
  console.log(`
97792
96696
  Received SIGINT. Stopping...`);
@@ -97804,13 +96708,13 @@ Received SIGINT. Stopping...`);
97804
96708
  program2.command("reset-queue [pearlId]").description("Reset the Work Queue (Deletes persistence file or specific pearl tickets)").action(async (pearlId) => {
97805
96709
  try {
97806
96710
  if (pearlId) {
97807
- await loadConfig2();
97808
- const queue = getQueue2();
96711
+ await loadConfig();
96712
+ const queue = getQueue();
97809
96713
  console.log(`Resetting tickets for pearl: ${pearlId}...`);
97810
96714
  queue.resetPearl(pearlId);
97811
96715
  console.log(`Tickets for ${pearlId} have been cleared.`);
97812
96716
  } else {
97813
- const dbPath = resolve12(process.cwd(), ".citadel", "queue.sqlite");
96717
+ const dbPath = resolve9(process.cwd(), ".citadel", "queue.sqlite");
97814
96718
  console.log(`Resetting entire queue at ${dbPath}...`);
97815
96719
  await unlink(dbPath);
97816
96720
  console.log("Queue reset successfully.");
@@ -97824,8 +96728,8 @@ Received SIGINT. Stopping...`);
97824
96728
  }
97825
96729
  });
97826
96730
  program2.command("inspect <pearlId>").description("Inspect the active ticket for a pearl").action(async (pearlId) => {
97827
- await loadConfig2();
97828
- const ticket = getQueue2().getActiveTicket(pearlId);
96731
+ await loadConfig();
96732
+ const ticket = getQueue().getActiveTicket(pearlId);
97829
96733
  if (ticket) {
97830
96734
  console.log(JSON.stringify(ticket, null, 2));
97831
96735
  } else {
@@ -97833,8 +96737,8 @@ Received SIGINT. Stopping...`);
97833
96737
  }
97834
96738
  });
97835
96739
  program2.command("queue").description("List all tickets in the work queue").option("-s, --status <status>", "Filter by status (queued, processing, completed, failed)").option("-j, --json", "Output in JSON format").action(async (options) => {
97836
- await loadConfig2();
97837
- const queue = getQueue2();
96740
+ await loadConfig();
96741
+ const queue = getQueue();
97838
96742
  const tickets = queue.getAllTickets(options.status);
97839
96743
  if (options.json) {
97840
96744
  console.log(JSON.stringify(tickets, null, 2));
@@ -97862,7 +96766,7 @@ Received SIGINT. Stopping...`);
97862
96766
  console.error("Error: --formula is required for citadel create");
97863
96767
  process.exit(1);
97864
96768
  }
97865
- await loadConfig2();
96769
+ await loadConfig();
97866
96770
  const engine = getWorkflowEngine();
97867
96771
  await engine.init();
97868
96772
  const variables = {};
@@ -97892,9 +96796,9 @@ if (__require.main == __require.module) {
97892
96796
  export {
97893
96797
  runCLI,
97894
96798
  program2 as program,
97895
- loadConfig2 as loadConfig,
96799
+ loadConfig,
97896
96800
  getWorkflowEngine,
97897
- getQueue2 as getQueue,
97898
- getPearls2 as getPearls,
97899
- Conductor2 as Conductor
96801
+ getQueue,
96802
+ getPearls,
96803
+ Conductor
97900
96804
  };