zelari-code 0.7.5 → 0.7.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/bin/zelari-code.js +1 -1
  2. package/dist/agents/councilApi.js +7 -7
  3. package/dist/agents/councilApi.js.map +1 -1
  4. package/dist/agents/councilDirectives.js +3 -3
  5. package/dist/agents/promoteMember.js +2 -2
  6. package/dist/agents/roles.js +32 -50
  7. package/dist/agents/roles.js.map +1 -1
  8. package/dist/agents/skills/builtin/debugging.js +4 -4
  9. package/dist/agents/skills/builtin/debugging.js.map +1 -1
  10. package/dist/agents/skills/builtin/docs.js +13 -23
  11. package/dist/agents/skills/builtin/docs.js.map +1 -1
  12. package/dist/agents/skills/builtin/git-ops.js +3 -3
  13. package/dist/agents/skills/builtin/git-ops.js.map +1 -1
  14. package/dist/agents/skills/builtin/planning.js +4 -4
  15. package/dist/agents/skills/builtin/planning.js.map +1 -1
  16. package/dist/agents/skills/builtin/refactoring.js +3 -3
  17. package/dist/agents/skills/builtin/refactoring.js.map +1 -1
  18. package/dist/agents/skills/builtin/review.js +9 -9
  19. package/dist/agents/skills/builtin/review.js.map +1 -1
  20. package/dist/agents/skills/builtin/testing.js +3 -3
  21. package/dist/agents/skills/builtin/testing.js.map +1 -1
  22. package/dist/agents/tools.js +9 -2
  23. package/dist/agents/tools.js.map +1 -1
  24. package/dist/cli/app.js +2 -1
  25. package/dist/cli/app.js.map +1 -1
  26. package/dist/cli/components/CollapsibleToolOutput.js +26 -8
  27. package/dist/cli/components/CollapsibleToolOutput.js.map +1 -1
  28. package/dist/cli/components/Header.js +18 -6
  29. package/dist/cli/components/Header.js.map +1 -1
  30. package/dist/cli/components/Sidebar.js +31 -33
  31. package/dist/cli/components/Sidebar.js.map +1 -1
  32. package/dist/cli/components/SplashScreen.js +158 -0
  33. package/dist/cli/components/SplashScreen.js.map +1 -0
  34. package/dist/cli/councilDispatcher.js +20 -0
  35. package/dist/cli/councilDispatcher.js.map +1 -1
  36. package/dist/cli/main.bundled.js +983 -171
  37. package/dist/cli/main.bundled.js.map +4 -4
  38. package/dist/cli/main.js +9 -2
  39. package/dist/cli/main.js.map +1 -1
  40. package/dist/cli/mcp/mcpClient.js +1 -1
  41. package/dist/cli/oauthCallbackServer.js +207 -0
  42. package/dist/cli/oauthCallbackServer.js.map +1 -0
  43. package/dist/cli/wizard/index.js +1 -1
  44. package/dist/cli/wizard/index.js.map +1 -1
  45. package/dist/cli/workspace/completeDesign.js +128 -0
  46. package/dist/cli/workspace/completeDesign.js.map +1 -0
  47. package/dist/cli/workspace/postCouncilHook.js +146 -20
  48. package/dist/cli/workspace/postCouncilHook.js.map +1 -1
  49. package/dist/cli/workspace/stubs.js +231 -95
  50. package/dist/cli/workspace/stubs.js.map +1 -1
  51. package/dist/main/core/AgentHarness.js +7 -36
  52. package/dist/main/core/AgentHarness.js.map +1 -1
  53. package/dist/main/core/tools/builtin/_walk.js +129 -0
  54. package/dist/main/core/tools/builtin/_walk.js.map +1 -0
  55. package/dist/main/core/tools/builtin/diff.js +477 -0
  56. package/dist/main/core/tools/builtin/diff.js.map +1 -0
  57. package/dist/main/core/tools/builtin/listFiles.js +5 -43
  58. package/dist/main/core/tools/builtin/listFiles.js.map +1 -1
  59. package/dist/main/core/tools/builtin/search.js +120 -28
  60. package/dist/main/core/tools/builtin/search.js.map +1 -1
  61. package/package.json +2 -2
@@ -141,12 +141,13 @@ var init_skills = __esm({
141
141
  color: "#10b981",
142
142
  enabledByDefault: true,
143
143
  builtin: true,
144
- requiredTools: ["createTask", "createPhase", "createMilestone", "updateTask"],
144
+ requiredTools: ["createPlan", "createTask", "createPhase", "createMilestone", "updateTask"],
145
145
  systemPromptFragment: `You plan projects hierarchically.
146
146
  - Decompose work into phases, then tasks within phases.
147
147
  - For each task include: title, description, priority (low|medium|high|critical), and optional tags/subtasks.
148
148
  - Define milestones to mark key deliverables.
149
- - When relevant, reference file paths and acceptance criteria.`
149
+ - When relevant, reference file paths and acceptance criteria.
150
+ - Prefer ONE createPlan call (phases with nested tasks + milestone) over many itemized createPhase/createTask calls.`
150
151
  },
151
152
  {
152
153
  id: "idea-synthesizer",
@@ -17043,6 +17044,61 @@ var init_toolSchemas = __esm({
17043
17044
  "use strict";
17044
17045
  init_tools();
17045
17046
  PARAM_SCHEMAS = {
17047
+ createPlan: {
17048
+ type: "object",
17049
+ properties: {
17050
+ phases: {
17051
+ type: "array",
17052
+ description: "Ordered list of plan phases, each with its tasks nested (aim for 3 tasks per phase)",
17053
+ items: {
17054
+ type: "object",
17055
+ properties: {
17056
+ name: { type: "string", description: "Phase name" },
17057
+ description: { type: "string", description: "What this phase delivers and its exit criterion" },
17058
+ order: { type: "number", description: "Position of the phase in the plan (1-based)" },
17059
+ color: { type: "string", description: 'Hex color, e.g. "#3b82f6"' },
17060
+ tasks: {
17061
+ type: "array",
17062
+ description: "Concrete tasks for this phase",
17063
+ items: {
17064
+ type: "object",
17065
+ properties: {
17066
+ title: { type: "string", description: "Concise verb-led task title" },
17067
+ description: { type: "string", description: "2-3 sentences of context" },
17068
+ fileRefs: {
17069
+ type: "array",
17070
+ items: { type: "string" },
17071
+ description: 'File paths with line ranges, e.g. "src/App.tsx:L10-L40"'
17072
+ },
17073
+ acceptance: {
17074
+ type: "array",
17075
+ items: { type: "string" },
17076
+ description: "Concrete, testable acceptance criteria"
17077
+ },
17078
+ qaScenario: { type: "string", description: "Step-by-step manual QA scenario" },
17079
+ priority: { type: "string", enum: ["low", "medium", "high", "critical"] }
17080
+ },
17081
+ required: ["title"]
17082
+ }
17083
+ }
17084
+ },
17085
+ required: ["name"]
17086
+ }
17087
+ },
17088
+ milestone: {
17089
+ type: "object",
17090
+ description: "The milestone this plan ships (e.g. the design-complete milestone)",
17091
+ properties: {
17092
+ title: { type: "string", description: "Milestone title" },
17093
+ description: { type: "string", description: "Milestone description" },
17094
+ targetVersion: { type: "string", description: 'Target version, e.g. "v0.1.0"' },
17095
+ dueDate: { type: "string", description: "Due date (ISO string)" }
17096
+ },
17097
+ required: ["title"]
17098
+ }
17099
+ },
17100
+ required: ["phases"]
17101
+ },
17046
17102
  createTask: {
17047
17103
  type: "object",
17048
17104
  properties: {
@@ -17051,6 +17107,17 @@ var init_toolSchemas = __esm({
17051
17107
  priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
17052
17108
  phaseId: { type: "string", description: "ID of the phase this task belongs to" },
17053
17109
  parentId: { type: "string", description: "ID of the parent task, if nested" },
17110
+ fileRefs: {
17111
+ type: "array",
17112
+ items: { type: "string" },
17113
+ description: 'File paths with line ranges where the work lands, e.g. "src/App.tsx:L10-L40"'
17114
+ },
17115
+ acceptance: {
17116
+ type: "array",
17117
+ items: { type: "string" },
17118
+ description: "Concrete, testable acceptance criteria"
17119
+ },
17120
+ qaScenario: { type: "string", description: "Step-by-step manual QA scenario" },
17054
17121
  tags: { type: "array", items: { type: "string" } },
17055
17122
  subtasks: { type: "array", items: { type: "string" }, description: "Subtask titles" }
17056
17123
  },
@@ -17180,6 +17247,7 @@ var init_toolSchemas = __esm({
17180
17247
  properties: {
17181
17248
  title: { type: "string", description: "Milestone title" },
17182
17249
  description: { type: "string", description: "Milestone description" },
17250
+ targetVersion: { type: "string", description: 'Target version this milestone ships, e.g. "v0.1.0"' },
17183
17251
  dueDate: { type: "string", description: "Due date (ISO string)" }
17184
17252
  },
17185
17253
  required: ["title"]
@@ -17543,7 +17611,7 @@ async function refreshGrokToken(options) {
17543
17611
  return parseTokenResponseBody(obj, accessToken);
17544
17612
  }
17545
17613
  async function openBrowser(url2) {
17546
- const { spawn: spawn4 } = await import("node:child_process");
17614
+ const { spawn: spawn5 } = await import("node:child_process");
17547
17615
  const cmd = (() => {
17548
17616
  switch (process.platform) {
17549
17617
  case "darwin":
@@ -17556,7 +17624,7 @@ async function openBrowser(url2) {
17556
17624
  })();
17557
17625
  return new Promise((resolve, reject) => {
17558
17626
  try {
17559
- const child = spawn4(cmd.bin, cmd.args, {
17627
+ const child = spawn5(cmd.bin, cmd.args, {
17560
17628
  stdio: "ignore",
17561
17629
  detached: true
17562
17630
  });
@@ -19202,6 +19270,7 @@ __export(stubs_exports, {
19202
19270
  createWorkspaceContext: () => createWorkspaceContext,
19203
19271
  createWorkspaceStubs: () => createWorkspaceStubs,
19204
19272
  deriveProjectName: () => projectName,
19273
+ readPlanSummary: () => readPlanSummary,
19205
19274
  resolveWorkspaceRoot: () => resolveWorkspaceRoot
19206
19275
  });
19207
19276
  import { existsSync as existsSync9, readdirSync as readdirSync3, writeFileSync as writeFileSync6, readFileSync as readFileSync6, mkdirSync as mkdirSync7, renameSync as renameSync3 } from "node:fs";
@@ -19316,8 +19385,108 @@ function nextAdrId(ctx) {
19316
19385
  function slugify2(s) {
19317
19386
  return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
19318
19387
  }
19388
+ function addPhaseRecord(summary, input) {
19389
+ const id = slugify2(input.name) || `phase-${input.order}`;
19390
+ if (summary.phases.some((p3) => p3.id === id)) {
19391
+ return { id, created: false };
19392
+ }
19393
+ summary.phases.push({
19394
+ kind: "phase",
19395
+ id,
19396
+ name: input.name,
19397
+ description: input.description,
19398
+ order: input.order,
19399
+ color: input.color
19400
+ });
19401
+ return { id, created: true };
19402
+ }
19403
+ function addTaskRecord(ctx, summary, phaseId, t, options) {
19404
+ const slug = slugify2(t.title);
19405
+ if (options.dedupe) {
19406
+ const existing = summary.tasks.find(
19407
+ (k) => k.phaseId === phaseId && k.id.replace(/-\d+$/, "") === `${phaseId}-${slug}`
19408
+ );
19409
+ if (existing) return { id: existing.id, created: false };
19410
+ }
19411
+ const id = `${phaseId}-${slug}-${summary.tasks.filter((k) => k.phaseId === phaseId).length + 1}`;
19412
+ summary.tasks.push({
19413
+ kind: "task",
19414
+ id,
19415
+ // v0.7.3: keep the human-readable title in plan.json so
19416
+ // buildPlanSummary can render it in the system prompt.
19417
+ name: t.title,
19418
+ phaseId,
19419
+ status: "pending",
19420
+ priority: t.priority
19421
+ });
19422
+ const taskPath = join4(ctx.rootDir, "plan-tasks", `${id}.md`);
19423
+ const meta3 = {
19424
+ kind: "task",
19425
+ id,
19426
+ phaseId,
19427
+ status: "pending",
19428
+ priority: t.priority,
19429
+ tags: t.fileRefs
19430
+ };
19431
+ const body = [
19432
+ `# ${t.title}`,
19433
+ "",
19434
+ t.description,
19435
+ "",
19436
+ `## File references`,
19437
+ ...t.fileRefs.map((f) => `- \`${f}\``),
19438
+ "",
19439
+ `## Acceptance criteria`,
19440
+ ...t.acceptance.map((a) => `- ${a}`),
19441
+ "",
19442
+ `## QA scenario`,
19443
+ "",
19444
+ t.qaScenario || "_(not specified)_",
19445
+ ""
19446
+ ].join("\n");
19447
+ ctx.storage.write(taskPath, meta3, body);
19448
+ return { id, created: true };
19449
+ }
19450
+ function addMilestoneRecord(ctx, summary, input) {
19451
+ const id = `m-${slugify2(input.title)}`;
19452
+ if (summary.milestones.some((m) => m.id === id)) {
19453
+ return { id, created: false };
19454
+ }
19455
+ const version2 = input.targetVersion ?? input.dueDate ?? "TBD";
19456
+ summary.milestones.push({
19457
+ kind: "milestone",
19458
+ id,
19459
+ name: input.title,
19460
+ description: input.description,
19461
+ dueDate: input.dueDate,
19462
+ targetVersion: version2
19463
+ });
19464
+ const path22 = join4(ctx.rootDir, "milestones", `${id}.md`);
19465
+ const meta3 = {
19466
+ kind: "milestone",
19467
+ id,
19468
+ name: input.title,
19469
+ description: input.description,
19470
+ dueDate: input.dueDate,
19471
+ targetVersion: version2
19472
+ };
19473
+ const body = [
19474
+ `# Milestone: ${input.title}`,
19475
+ "",
19476
+ input.description,
19477
+ "",
19478
+ `Target version: ${version2}`,
19479
+ ""
19480
+ ].join("\n");
19481
+ ctx.storage.write(path22, meta3, body);
19482
+ return { id, created: true };
19483
+ }
19484
+ function readPlanSummary(ctx) {
19485
+ return readPlan(ctx);
19486
+ }
19319
19487
  function createWorkspaceStubs(ctx) {
19320
19488
  return [
19489
+ createPlanStub(ctx),
19321
19490
  createPhaseStub(ctx),
19322
19491
  createTaskStub(ctx),
19323
19492
  updateTaskStub(ctx),
@@ -19329,6 +19498,67 @@ function createWorkspaceStubs(ctx) {
19329
19498
  getDocumentBacklinksStub(ctx)
19330
19499
  ];
19331
19500
  }
19501
+ function createPlanStub(ctx) {
19502
+ return {
19503
+ name: "createPlan",
19504
+ description: "Create the FULL project plan in one call: all phases, the tasks nested under each phase, and the target milestone. Preferred over itemized createPhase/createTask/createMilestone calls \u2014 one createPlan call persists everything atomically.",
19505
+ category: "project",
19506
+ parameters: [],
19507
+ execute: async (args) => {
19508
+ return workspaceMutex.run(`${ctx.rootDir}:plan`, () => {
19509
+ const rawPhases = Array.isArray(args["phases"]) ? args["phases"] : [];
19510
+ if (rawPhases.length === 0) {
19511
+ return 'createPlan requires a non-empty "phases" array (each phase: { name, description, order, color, tasks: [{ title, description, fileRefs, acceptance, qaScenario, priority }] }).';
19512
+ }
19513
+ const summary = readPlan(ctx);
19514
+ let phasesCreated = 0;
19515
+ let tasksCreated = 0;
19516
+ const phaseIds = [];
19517
+ rawPhases.forEach((rawPhase, idx) => {
19518
+ const { id: phaseId, created } = addPhaseRecord(summary, {
19519
+ name: rawPhase["name"] ?? `Phase ${idx + 1}`,
19520
+ description: rawPhase["description"] ?? "",
19521
+ order: rawPhase["order"] ?? idx + 1,
19522
+ color: rawPhase["color"] ?? "#3b82f6"
19523
+ });
19524
+ if (created) phasesCreated += 1;
19525
+ phaseIds.push(phaseId);
19526
+ const rawTasks = Array.isArray(rawPhase["tasks"]) ? rawPhase["tasks"] : [];
19527
+ for (const rawTask of rawTasks) {
19528
+ const { created: taskCreated } = addTaskRecord(
19529
+ ctx,
19530
+ summary,
19531
+ phaseId,
19532
+ {
19533
+ title: rawTask["title"] ?? "New Task",
19534
+ description: rawTask["description"] ?? "",
19535
+ fileRefs: rawTask["fileRefs"] ?? rawTask["files"] ?? [],
19536
+ acceptance: rawTask["acceptance"] ?? [],
19537
+ qaScenario: rawTask["qaScenario"] ?? rawTask["qa"] ?? "",
19538
+ priority: rawTask["priority"] ?? "medium"
19539
+ },
19540
+ { dedupe: true }
19541
+ );
19542
+ if (taskCreated) tasksCreated += 1;
19543
+ }
19544
+ });
19545
+ let milestonesCreated = 0;
19546
+ const rawMilestone = args["milestone"];
19547
+ if (rawMilestone && typeof rawMilestone === "object") {
19548
+ const { created } = addMilestoneRecord(ctx, summary, {
19549
+ title: rawMilestone["title"] ?? "v0.1.0 design-complete",
19550
+ description: rawMilestone["description"] ?? "",
19551
+ dueDate: rawMilestone["dueDate"],
19552
+ targetVersion: rawMilestone["targetVersion"]
19553
+ });
19554
+ if (created) milestonesCreated += 1;
19555
+ }
19556
+ writePlan(ctx, summary);
19557
+ return `Plan created: ${phasesCreated} phases, ${tasksCreated} tasks, ${milestonesCreated} milestone(s). Phase ids: ${phaseIds.join(", ")}.`;
19558
+ });
19559
+ }
19560
+ };
19561
+ }
19332
19562
  function createPhaseStub(ctx) {
19333
19563
  return {
19334
19564
  name: "createPhase",
@@ -19338,22 +19568,17 @@ function createPhaseStub(ctx) {
19338
19568
  execute: async (args) => {
19339
19569
  return workspaceMutex.run(`${ctx.rootDir}:plan`, () => {
19340
19570
  const name = args["name"] ?? "New Phase";
19341
- const description = args["description"] ?? "";
19342
19571
  const order = args["order"] ?? 0;
19343
- const color = args["color"] ?? "#3b82f6";
19344
- const id = slugify2(name) || `phase-${order}`;
19345
19572
  const summary = readPlan(ctx);
19346
- if (summary.phases.some((p3) => p3.id === id)) {
19347
- return `Phase "${id}" already exists.`;
19348
- }
19349
- summary.phases.push({
19350
- kind: "phase",
19351
- id,
19573
+ const { id, created } = addPhaseRecord(summary, {
19352
19574
  name,
19353
- description,
19575
+ description: args["description"] ?? "",
19354
19576
  order,
19355
- color
19577
+ color: args["color"] ?? "#3b82f6"
19356
19578
  });
19579
+ if (!created) {
19580
+ return `Phase "${id}" already exists.`;
19581
+ }
19357
19582
  writePlan(ctx, summary);
19358
19583
  return `Phase "${name}" created (id: ${id}, order: ${order}).`;
19359
19584
  });
@@ -19370,54 +19595,26 @@ function createTaskStub(ctx) {
19370
19595
  return workspaceMutex.run(`${ctx.rootDir}:plan`, () => {
19371
19596
  const phaseId = args["phaseId"] ?? args["phase"];
19372
19597
  const title = args["title"] ?? "New Task";
19373
- const description = args["description"] ?? "";
19374
- const fileRefs = args["fileRefs"] ?? args["files"] ?? [];
19375
- const acceptance = args["acceptance"] ?? [];
19376
- const qaScenario = args["qaScenario"] ?? args["qa"] ?? "";
19377
- const priority = args["priority"] ?? "medium";
19378
19598
  if (!phaseId) return "Task requires a phaseId.";
19379
19599
  const summary = readPlan(ctx);
19380
19600
  if (!summary.phases.some((p3) => p3.id === phaseId)) {
19381
19601
  return `Phase "${phaseId}" not found. Create it first via /createPhase.`;
19382
19602
  }
19383
- const id = `${phaseId}-${slugify2(title)}-${summary.tasks.filter((t) => t.phaseId === phaseId).length + 1}`;
19384
- summary.tasks.push({
19385
- kind: "task",
19386
- id,
19387
- // v0.7.3: keep the human-readable title in plan.json so
19388
- // buildPlanSummary can render it in the system prompt.
19389
- name: title,
19603
+ const { id } = addTaskRecord(
19604
+ ctx,
19605
+ summary,
19390
19606
  phaseId,
19391
- status: "pending",
19392
- priority
19393
- });
19607
+ {
19608
+ title,
19609
+ description: args["description"] ?? "",
19610
+ fileRefs: args["fileRefs"] ?? args["files"] ?? [],
19611
+ acceptance: args["acceptance"] ?? [],
19612
+ qaScenario: args["qaScenario"] ?? args["qa"] ?? "",
19613
+ priority: args["priority"] ?? "medium"
19614
+ },
19615
+ { dedupe: false }
19616
+ );
19394
19617
  writePlan(ctx, summary);
19395
- const taskPath = join4(ctx.rootDir, "plan-tasks", `${id}.md`);
19396
- const meta3 = {
19397
- kind: "task",
19398
- id,
19399
- phaseId,
19400
- status: "pending",
19401
- priority,
19402
- tags: fileRefs
19403
- };
19404
- const body = [
19405
- `# ${title}`,
19406
- "",
19407
- description,
19408
- "",
19409
- `## File references`,
19410
- ...fileRefs.map((f) => `- \`${f}\``),
19411
- "",
19412
- `## Acceptance criteria`,
19413
- ...acceptance.map((a) => `- ${a}`),
19414
- "",
19415
- `## QA scenario`,
19416
- "",
19417
- qaScenario || "_(not specified)_",
19418
- ""
19419
- ].join("\n");
19420
- ctx.storage.write(taskPath, meta3, body);
19421
19618
  return `Task "${title}" created (id: ${id}) under phase "${phaseId}".`;
19422
19619
  });
19423
19620
  }
@@ -19523,39 +19720,16 @@ function createMilestoneStub(ctx) {
19523
19720
  execute: async (args) => {
19524
19721
  return workspaceMutex.run(`${ctx.rootDir}:plan`, () => {
19525
19722
  const title = args["title"] ?? "New Milestone";
19526
- const description = args["description"] ?? "";
19527
- const dueDate = args["dueDate"];
19528
- const id = `m-${slugify2(title)}`;
19529
19723
  const summary = readPlan(ctx);
19530
- if (summary.milestones.some((m) => m.id === id)) return `Milestone "${id}" already exists.`;
19531
- const version2 = args["targetVersion"] ?? dueDate ?? "TBD";
19532
- summary.milestones.push({
19533
- kind: "milestone",
19534
- id,
19535
- name: title,
19536
- description,
19537
- dueDate,
19538
- targetVersion: version2
19724
+ const { id, created } = addMilestoneRecord(ctx, summary, {
19725
+ title,
19726
+ description: args["description"] ?? "",
19727
+ dueDate: args["dueDate"],
19728
+ targetVersion: args["targetVersion"]
19539
19729
  });
19730
+ if (!created) return `Milestone "${id}" already exists.`;
19540
19731
  writePlan(ctx, summary);
19541
- const path22 = join4(ctx.rootDir, "milestones", `${id}.md`);
19542
- const meta3 = {
19543
- kind: "milestone",
19544
- id,
19545
- name: title,
19546
- description,
19547
- dueDate,
19548
- targetVersion: version2
19549
- };
19550
- const body = [
19551
- `# Milestone: ${title}`,
19552
- "",
19553
- description,
19554
- "",
19555
- `Target version: ${version2}`,
19556
- ""
19557
- ].join("\n");
19558
- ctx.storage.write(path22, meta3, body);
19732
+ const version2 = summary.milestones.find((m) => m.id === id)?.targetVersion ?? "TBD";
19559
19733
  return `Milestone "${title}" created (id: ${id}, version: ${version2}).`;
19560
19734
  });
19561
19735
  }
@@ -19572,7 +19746,8 @@ function createDocumentStub(ctx) {
19572
19746
  const title = args["title"] ?? "New Document";
19573
19747
  const content = args["content"] ?? "";
19574
19748
  const tags = args["tags"] ?? [];
19575
- const id = slugify2(title) || `doc-${Date.now()}`;
19749
+ const normalizedTitle = title.replace(/\.(md|markdown)\s*$/i, "");
19750
+ const id = slugify2(normalizedTitle) || `doc-${Date.now()}`;
19576
19751
  const path22 = workspaceArtifact(ctx.rootDir, "docs", id);
19577
19752
  const meta3 = {
19578
19753
  kind: "doc",
@@ -19658,8 +19833,8 @@ function linkDocumentsStub(ctx) {
19658
19833
  parameters: [],
19659
19834
  execute: async (args) => {
19660
19835
  return workspaceMutex.run(`${ctx.rootDir}:link`, () => {
19661
- const fromId = args["fromId"];
19662
- const toId = args["toId"];
19836
+ const fromId = args["fromId"] ?? args["sourceId"];
19837
+ const toId = args["toId"] ?? args["targetId"] ?? args["targetPathOrTitle"];
19663
19838
  if (!fromId || !toId) return "linkDocuments requires fromId and toId.";
19664
19839
  const allFiles = [
19665
19840
  ...ctx.storage.listMarkdown(join4(ctx.rootDir, "decisions")),
@@ -19692,7 +19867,7 @@ function getDocumentBacklinksStub(ctx) {
19692
19867
  category: "analysis",
19693
19868
  parameters: [],
19694
19869
  execute: async (args) => {
19695
- const targetId = args["targetId"];
19870
+ const targetId = args["targetId"] ?? args["id"];
19696
19871
  if (!targetId) return "getDocumentBacklinks requires targetId.";
19697
19872
  const allFiles = [
19698
19873
  ...ctx.storage.listMarkdown(join4(ctx.rootDir, "decisions")),
@@ -19820,7 +19995,7 @@ var init_mcpClient = __esm({
19820
19995
  {
19821
19996
  protocolVersion: MCP_PROTOCOL_VERSION,
19822
19997
  capabilities: {},
19823
- clientInfo: { name: "zelari-code", version: "0.7.5" }
19998
+ clientInfo: { name: "zelari-code", version: "0.7.8" }
19824
19999
  },
19825
20000
  INIT_TIMEOUT_MS
19826
20001
  );
@@ -20135,7 +20310,40 @@ Example format:
20135
20310
  - Make dependencies explicit (task B depends on task A).
20136
20311
  - If the stack/platform is unknown, ask ONCE (see clarification protocol) rather than guessing.
20137
20312
 
20138
- Keep plans hierarchical and practical. Stay under 250 words.${CLARIFICATION_PROTOCOL8}`,
20313
+ Keep plans hierarchical and practical. Stay under 250 words.${CLARIFICATION_PROTOCOL8}
20314
+
20315
+ ## Design-phase mandatory plan artifact
20316
+ In design-phase mode (TASK mentions design/architecture/spec, no existing codebase to edit), you MUST persist the plan through workspace tools. The plan exists ONLY if it was persisted via a tool call \u2014 prose does not count, and the post-run check will flag your run if the plan is missing.
20317
+
20318
+ PREFERRED \u2014 emit ONE single \`createPlan\` call containing the whole plan: 4 phases, 12 concrete tasks (3 per phase), and 1 milestone, all in the same call:
20319
+
20320
+ \`\`\`
20321
+ createPlan({
20322
+ phases: [
20323
+ {
20324
+ name: "Foundation & Technical Blueprint",
20325
+ description: "Lock the stack and the non-negotiable quality budget. Exit: baseline builds green.",
20326
+ order: 1,
20327
+ color: "#3b82f6",
20328
+ tasks: [
20329
+ { title: "Lock stack baseline", description: "2-3 sentences of context", fileRefs: ["src/main.tsx:L1-L40"], acceptance: ["App boots with strict TS", "CI build green"], qaScenario: "Run npm run build and confirm zero errors", priority: "high" },
20330
+ { title: "Define NFR budget", description: "...", fileRefs: ["docs/nfr.md"], acceptance: ["LCP/WCAG/Lighthouse targets documented"], qaScenario: "...", priority: "high" },
20331
+ { title: "Document design-phase exit criteria", description: "...", fileRefs: ["docs/exit-criteria.md"], acceptance: ["Each phase has a testable exit row"], qaScenario: "...", priority: "medium" }
20332
+ ]
20333
+ },
20334
+ { name: "...", order: 2, tasks: [ /* 3 tasks */ ] },
20335
+ { name: "...", order: 3, tasks: [ /* 3 tasks */ ] },
20336
+ { name: "...", order: 4, tasks: [ /* 3 tasks */ ] }
20337
+ ],
20338
+ milestone: { title: "v0.1.0 design-complete", description: "All design artifacts exist and the green-light checklist passes.", targetVersion: "v0.1.0" }
20339
+ })
20340
+ \`\`\`
20341
+
20342
+ Every task MUST include fileRefs, acceptance, and qaScenario \u2014 the QA scenario proves the task is verifiable.
20343
+
20344
+ FALLBACK \u2014 only if you cannot batch, use the itemized tools: \`createPhase\` once per phase; each createPhase response returns the new phase id \u2014 use that exact id as \`phaseId\` in the 3 \`createTask\` calls for that phase; finish with one \`createMilestone\` ({ title, description, targetVersion }). If you forget an id, call \`searchDocuments\` (limit 1) to look it up, then continue.
20345
+
20346
+ Do NOT output tasks as prose. The system will refuse any task that does not pass a valid phaseId.`,
20139
20347
  // v0.7.2: read/search the codebase to ground the plan in reality.
20140
20348
  tools: ["list_files", "read_file", "grep_content"],
20141
20349
  skills: ["project-planner", "vault-manager"]
@@ -20166,7 +20374,18 @@ Keep plans hierarchical and practical. Stay under 250 words.${CLARIFICATION_PROT
20166
20374
  ## Themes
20167
20375
  ## Top picks (with feasibility/novelty + de-risk note)
20168
20376
 
20169
- Stay under 200 words.${CLARIFICATION_PROTOCOL8}`,
20377
+ Stay under 200 words.${CLARIFICATION_PROTOCOL8}
20378
+
20379
+ ## Design-phase artifact (mandatory when running council in design-phase mode)
20380
+ If the council is in design-phase mode (TASK mentions design/architecture/spec, no existing codebase to edit), you MUST also persist your ideation output as workspace documents via the \`createDocument\` tool. Emit AT LEAST 3 separate \`createDocument\` calls, each tagged with one of these categories:
20381
+
20382
+ - \`customer-journey-map\` \u2014 2-3 personas (give them names, demographics, goals), journey table (stage \u2192 action \u2192 touchpoint \u2192 emotion), pain points per stage. This is a customer journey deliverable.
20383
+ - \`information-architecture\` \u2014 site map tree (root \u2192 section \u2192 page), navigation model (primary nav, breadcrumbs, footer), URL patterns. This is an information architecture deliverable.
20384
+ - \`design-tokens\` \u2014 color palette (semantic + hex), typography scale, spacing scale, motion principles. This is a design tokens deliverable.
20385
+
20386
+ You may optionally add a 4th design system doc if the project warrants it.
20387
+
20388
+ Pass the tool call as: \`createDocument({ title: "<category>", content: "<markdown body>" })\`. Do NOT summarize these into prose \u2014 they are the deliverable.`,
20170
20389
  // v0.7.2: explore the codebase to ground ideas in what exists.
20171
20390
  tools: ["list_files", "read_file"],
20172
20391
  skills: ["idea-synthesizer", "mind-mapper", "document-writer"]
@@ -20226,7 +20445,19 @@ Describe the proposed structure (root \u2192 branches \u2192 leaves) in text and
20226
20445
  ## Contradictions
20227
20446
  ## Top improvements
20228
20447
 
20229
- Stay under 200 words. (You do not create workspace artifacts, so you do not emit a tools block \u2014 but you MAY ask the user a clarifying question if a core ambiguity blocks judgment.)${CLARIFICATION_PROTOCOL8}`,
20448
+ Stay under 200 words.${CLARIFICATION_PROTOCOL8}
20449
+
20450
+ ## Design-phase risks artifact (mandatory when running council in design-phase mode)
20451
+ When the council is in design-phase mode (TASK mentions design/architecture/spec, no existing codebase to edit), you ALSO write a \`risks\` document via the \`createDocument\` tool. This is your one allowed artifact emission. Pass the tool call as:
20452
+
20453
+ \`\`\`
20454
+ createDocument({
20455
+ title: "risks",
20456
+ content: \`# Risks\\n\\n## 1. <Risk title>\\n- Impact: <high|medium|low>\\n- Likelihood: <high|medium|low>\\n- Mitigation: <one-line mitigation>\\n\\n## 2. ...\`
20457
+ })
20458
+ \`\`\`
20459
+
20460
+ Include AT LEAST 5 risks, each scored on Impact and Likelihood, with a one-line mitigation. Cover: technical (e.g. stack risk), product (e.g. scope creep), accessibility, performance, security. Do NOT emit other workspace artifacts \u2014 your role is to evaluate, not to build.`,
20230
20461
  tools: [],
20231
20462
  skills: ["research-analyst"]
20232
20463
  },
@@ -20251,7 +20482,19 @@ Stay under 200 words. (You do not create workspace artifacts, so you do not emit
20251
20482
  - Apply Minosse's highest-value improvements; drop descoped items.
20252
20483
  - After making changes, verify they work (compile, run tests, etc.) when feasible.
20253
20484
 
20254
- Use the tools available to you (read_file, write_file, edit_file, bash, list_files) directly as tool calls \u2014 the harness handles execution.${CLARIFICATION_PROTOCOL8}`,
20485
+ Use the tools available to you (read_file, write_file, edit_file, bash, list_files) directly as tool calls \u2014 the harness handles execution.${CLARIFICATION_PROTOCOL8}
20486
+
20487
+ ## Design-phase synthesis artifact (mandatory when running council in design-phase mode)
20488
+ When the council is in design-phase mode (TASK mentions design/architecture/spec, no existing codebase to edit), your final deliverable is a \`synthesis\` document via the \`createDocument\` tool (NOT \`write_file\` and NOT \`list_files\`):
20489
+
20490
+ \`\`\`
20491
+ createDocument({
20492
+ title: "synthesis",
20493
+ content: \`# Council Synthesis\\n\\n## Executive summary\\n<2-3 paragraphs>\\n\\n## Stack and key decisions\\n- <ADR-by-ADR summary>\\n\\n## Phases\\n<1-2 line summary per phase>\\n\\n## Top risks\\n<top 3 risks from Minosse>\\n\\n## Green-light checklist\\n- [ ] All ADRs accepted\\n- [ ] Risks have mitigations\\n- [ ] Tasks have acceptance criteria\`
20494
+ })
20495
+ \`\`\`
20496
+
20497
+ DO NOT call \`list_files\` \u2014 it is NOT a workspace tool. Use \`searchDocuments\` if you need to look something up (limit 2-3 searches, then act on the results). Your deliverable is the synthesis document; emit it via \`createDocument\`, not via prose.`,
20255
20498
  // v0.7.2: implementation tools — the synthesizer writes/edits files and runs commands.
20256
20499
  tools: ["read_file", "write_file", "edit_file", "bash", "list_files"],
20257
20500
  skills: ["vault-manager", "project-planner", "idea-synthesizer"]
@@ -20707,8 +20950,14 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
20707
20950
  };
20708
20951
  callbacks.onMemberCost?.(cost);
20709
20952
  };
20710
- const executableNames = config2.tools ? new Set(config2.tools.list()) : null;
20711
- const filterExecutable = (names) => executableNames ? names.filter((n) => executableNames.has(n)) : names;
20953
+ const executorToolNames = config2.tools ? config2.tools.list() : [];
20954
+ const executableNames = config2.tools ? new Set(executorToolNames) : null;
20955
+ const filterExecutable = (names) => {
20956
+ if (!executableNames)
20957
+ return names;
20958
+ const merged = Array.from(/* @__PURE__ */ new Set([...names, ...executorToolNames]));
20959
+ return merged.filter((n) => executableNames.has(n));
20960
+ };
20712
20961
  const allSpecialists = agents.filter((a) => a.id !== "lucifer" && a.id !== "minos");
20713
20962
  const specialists = config2.feedbackStore ? config2.feedbackStore.ranked(allSpecialists) : allSpecialists;
20714
20963
  const oracle = agents.find((a) => a.id === "minos");
@@ -20751,12 +21000,14 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
20751
21000
  let toolCalls = 0;
20752
21001
  let usage = null;
20753
21002
  let errored = false;
21003
+ const emittedToolNames = [];
20754
21004
  const memberStart = Date.now();
20755
21005
  try {
20756
21006
  for await (const event of harness.run()) {
20757
21007
  yield event;
20758
21008
  if (event.type === "tool_execution_start") {
20759
21009
  toolCalls += 1;
21010
+ emittedToolNames.push(event.toolName);
20760
21011
  }
20761
21012
  if (event.type === "message_end" && event.usage) {
20762
21013
  usage = event.usage;
@@ -20774,6 +21025,27 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
20774
21025
  fullText = `Error: ${err instanceof Error ? err.message : "Unknown"}`;
20775
21026
  errored = true;
20776
21027
  }
21028
+ if (!errored && !NON_RETRY_AGENTS.has(agent.id)) {
21029
+ const specialistCheck = enforceDesignPhaseToolEmissions(agent.id, emittedToolNames);
21030
+ yield* applyRetryIfMissing({
21031
+ agent,
21032
+ check: specialistCheck,
21033
+ requirements: DESIGN_PHASE_REQUIREMENTS[agent.id],
21034
+ emittedToolNames,
21035
+ executableNames,
21036
+ sessionId,
21037
+ userMessage,
21038
+ agentOutputs,
21039
+ config: config2,
21040
+ effectiveProvider,
21041
+ effectiveModel,
21042
+ onToolCall: () => {
21043
+ toolCalls += 1;
21044
+ }
21045
+ });
21046
+ } else {
21047
+ enforceDesignPhaseToolEmissions(agent.id, emittedToolNames);
21048
+ }
20777
21049
  const memberDuration = Date.now() - memberStart;
20778
21050
  emitMemberCost({
20779
21051
  memberId: agent.id,
@@ -20819,7 +21091,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
20819
21091
  });
20820
21092
  }
20821
21093
  }
20822
- if (config2.debateMode && oracle && !completedIds.has(oracle.id)) {
21094
+ if (oracle && !completedIds.has(oracle.id)) {
20823
21095
  callbacks.onAgentStart?.(oracle);
20824
21096
  const override = config2.agentModels?.[oracle.id];
20825
21097
  const effectiveProvider = override?.providerId ?? config2.provider ?? "minimax";
@@ -20852,12 +21124,14 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
20852
21124
  let toolCalls = 0;
20853
21125
  let usage = null;
20854
21126
  let errored = false;
21127
+ const emittedToolNames = [];
20855
21128
  const memberStart = Date.now();
20856
21129
  try {
20857
21130
  for await (const event of harness.run()) {
20858
21131
  yield event;
20859
21132
  if (event.type === "tool_execution_start") {
20860
21133
  toolCalls += 1;
21134
+ emittedToolNames.push(event.toolName);
20861
21135
  }
20862
21136
  if (event.type === "message_end" && event.usage) {
20863
21137
  usage = event.usage;
@@ -20875,6 +21149,27 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
20875
21149
  fullText = `Review error: ${err instanceof Error ? err.message : "Unknown"}`;
20876
21150
  errored = true;
20877
21151
  }
21152
+ if (!errored) {
21153
+ const oracleCheck = enforceDesignPhaseToolEmissions(oracle.id, emittedToolNames);
21154
+ yield* applyRetryIfMissing({
21155
+ agent: oracle,
21156
+ check: oracleCheck,
21157
+ requirements: DESIGN_PHASE_REQUIREMENTS[oracle.id],
21158
+ emittedToolNames,
21159
+ executableNames,
21160
+ sessionId,
21161
+ userMessage: `Review these proposals for: "${userMessage}"`,
21162
+ agentOutputs,
21163
+ config: config2,
21164
+ effectiveProvider,
21165
+ effectiveModel,
21166
+ onToolCall: () => {
21167
+ toolCalls += 1;
21168
+ }
21169
+ });
21170
+ } else {
21171
+ enforceDesignPhaseToolEmissions(oracle.id, emittedToolNames);
21172
+ }
20878
21173
  const memberDuration = Date.now() - memberStart;
20879
21174
  emitMemberCost({
20880
21175
  memberId: oracle.id,
@@ -20948,12 +21243,14 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
20948
21243
  let usage = null;
20949
21244
  let errored = false;
20950
21245
  let lastErrorMessage = "";
21246
+ const emittedToolNames = [];
20951
21247
  const memberStart = Date.now();
20952
21248
  try {
20953
21249
  for await (const event of chairmanHarness.run()) {
20954
21250
  yield event;
20955
21251
  if (event.type === "tool_execution_start") {
20956
21252
  toolCalls += 1;
21253
+ emittedToolNames.push(event.toolName);
20957
21254
  }
20958
21255
  if (event.type === "message_end" && event.usage) {
20959
21256
  usage = event.usage;
@@ -20975,6 +21272,27 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
20975
21272
  errored = true;
20976
21273
  lastErrorMessage = err instanceof Error ? err.message : String(err);
20977
21274
  }
21275
+ if (!errored) {
21276
+ const chairmanCheck = enforceDesignPhaseToolEmissions(chairman.id, emittedToolNames);
21277
+ yield* applyRetryIfMissing({
21278
+ agent: chairman,
21279
+ check: chairmanCheck,
21280
+ requirements: DESIGN_PHASE_REQUIREMENTS[chairman.id],
21281
+ emittedToolNames,
21282
+ executableNames,
21283
+ sessionId,
21284
+ userMessage,
21285
+ agentOutputs,
21286
+ config: config2,
21287
+ effectiveProvider,
21288
+ effectiveModel,
21289
+ onToolCall: () => {
21290
+ toolCalls += 1;
21291
+ }
21292
+ });
21293
+ } else {
21294
+ enforceDesignPhaseToolEmissions(chairman.id, emittedToolNames);
21295
+ }
20978
21296
  const memberDuration = Date.now() - memberStart;
20979
21297
  const finalSynthesis = errored && fullText.length === 0 ? `[Chairman synthesis failed: ${lastErrorMessage || "unknown error"}]` : fullText;
20980
21298
  callbacks.onSynthesisDone?.(finalSynthesis, void 0, void 0);
@@ -21008,7 +21326,142 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
21008
21326
  durationMs: 0
21009
21327
  };
21010
21328
  }
21011
- var QUESTION_MARKER, QUESTION_END_MARKER;
21329
+ function checkMemberToolEmissions(_memberId, emittedToolNames, requirements) {
21330
+ if (requirements.length === 0) {
21331
+ return { ok: true, missing: [] };
21332
+ }
21333
+ const counts = /* @__PURE__ */ new Map();
21334
+ for (const name of emittedToolNames) {
21335
+ counts.set(name, (counts.get(name) ?? 0) + 1);
21336
+ }
21337
+ const missing = [];
21338
+ for (const req of requirements) {
21339
+ const got = counts.get(req.name) ?? 0;
21340
+ if (got < req.min) {
21341
+ missing.push(`${req.name} (got ${got}, need >= ${req.min})`);
21342
+ }
21343
+ }
21344
+ return { ok: missing.length === 0, missing };
21345
+ }
21346
+ function checkMemberToolEmissionSets(memberId, emittedToolNames, sets) {
21347
+ if (sets.length === 0) {
21348
+ return { ok: true, missing: [] };
21349
+ }
21350
+ const results = sets.map((set2) => checkMemberToolEmissions(memberId, emittedToolNames, set2));
21351
+ if (results.some((r) => r.ok)) {
21352
+ return { ok: true, missing: [] };
21353
+ }
21354
+ return results[0];
21355
+ }
21356
+ function enforceDesignPhaseToolEmissions(memberId, emittedToolNames) {
21357
+ const sets = DESIGN_PHASE_REQUIREMENT_SETS[memberId];
21358
+ if (!sets || sets.length === 0) {
21359
+ return { ok: true, missing: [] };
21360
+ }
21361
+ const result = checkMemberToolEmissionSets(memberId, emittedToolNames, sets);
21362
+ if (!result.ok) {
21363
+ console.warn(`[council] member "${memberId}" did not emit required tools: ${result.missing.join(", ")}. The downstream .zelari/ deliverable may be incomplete. (A forced retry turn scoped to the missing tools follows; the deterministic complete-design fallback covers any remaining gap.)`);
21364
+ }
21365
+ return result;
21366
+ }
21367
+ function shouldRetryMember(missingToolNames, attemptsSoFar) {
21368
+ if (missingToolNames.length === 0)
21369
+ return false;
21370
+ if (attemptsSoFar >= MAX_RETRY_PER_MEMBER)
21371
+ return false;
21372
+ return true;
21373
+ }
21374
+ function buildRetryPrompt(missingToolNames) {
21375
+ const names = missingToolNames.join(", ");
21376
+ return `You did not emit the required workspace tools: ${names}. Call ${names} NOW with concrete arguments. No prose. No search.`;
21377
+ }
21378
+ async function* runRetryTurnForMember(args) {
21379
+ const executableMissing = args.executableTools ? args.missingToolNames.filter((n) => args.executableTools.has(n)) : args.missingToolNames;
21380
+ if (executableMissing.length === 0) {
21381
+ return [];
21382
+ }
21383
+ const retryToolNames = executableMissing;
21384
+ const retryToolSpecs = getProviderTools(retryToolNames).map((t) => ({
21385
+ name: t.function.name,
21386
+ description: t.function.description,
21387
+ parameters: t.function.parameters
21388
+ }));
21389
+ const baseMessages = buildAgentMessages(args.agent, args.userMessage, args.ragContext, args.workspaceContext, args.priorOutputs, args.aiConfig, args.executableTools);
21390
+ const retryMessages = [
21391
+ ...baseMessages,
21392
+ { role: "user", content: buildRetryPrompt(executableMissing) }
21393
+ ];
21394
+ const maxToolCalls = args.minPerTool !== void 0 ? Object.entries(args.minPerTool).filter(([name]) => executableMissing.includes(name)).reduce((sum, [, min]) => sum + min, 0) : retryToolNames.length;
21395
+ const retryHarness = new AgentHarness({
21396
+ model: args.effectiveModel,
21397
+ provider: args.effectiveProvider,
21398
+ sessionId: args.sessionId,
21399
+ messages: retryMessages,
21400
+ tools: retryToolSpecs,
21401
+ eventBus: args.eventBus,
21402
+ toolRegistry: args.toolRegistry,
21403
+ // Budget the retry so the model can satisfy every requirement in
21404
+ // ONE tool_calls turn. For createTask min:12 this needs 12 calls.
21405
+ maxToolCallsPerTurn: maxToolCalls,
21406
+ memberId: args.agent.id,
21407
+ memberName: args.agent.name,
21408
+ providerStream: (params) => args.providerStream(params)
21409
+ });
21410
+ const retryEmitted = [];
21411
+ for await (const event of retryHarness.run()) {
21412
+ if (event.type === "tool_execution_start") {
21413
+ retryEmitted.push(event.toolName);
21414
+ }
21415
+ yield event;
21416
+ }
21417
+ return retryEmitted;
21418
+ }
21419
+ async function* applyRetryIfMissing(args) {
21420
+ if (args.check.ok)
21421
+ return;
21422
+ const missingToolNames = args.check.missing.map((m) => m.split(" ")[0]);
21423
+ if (!shouldRetryMember(missingToolNames, 0))
21424
+ return;
21425
+ console.warn(`[council] ${args.agent.id} retrying missing tools: ${missingToolNames.join(", ")}`);
21426
+ const minPerTool = {};
21427
+ if (args.requirements) {
21428
+ for (const req of args.requirements) {
21429
+ if (missingToolNames.includes(req.name)) {
21430
+ minPerTool[req.name] = req.min;
21431
+ }
21432
+ }
21433
+ }
21434
+ try {
21435
+ const retryGenerator = runRetryTurnForMember({
21436
+ agent: args.agent,
21437
+ missingToolNames,
21438
+ minPerTool,
21439
+ executableTools: args.executableNames,
21440
+ userMessage: args.userMessage,
21441
+ ragContext: args.config.ragContext,
21442
+ workspaceContext: args.config.workspaceContext,
21443
+ priorOutputs: args.agentOutputs,
21444
+ aiConfig: args.config.aiConfig,
21445
+ sessionId: args.sessionId,
21446
+ effectiveModel: args.effectiveModel,
21447
+ effectiveProvider: args.effectiveProvider,
21448
+ eventBus: args.config.eventBus,
21449
+ toolRegistry: args.config.tools,
21450
+ providerStream: args.config.providerStream
21451
+ });
21452
+ for await (const event of retryGenerator) {
21453
+ if (event.type === "tool_execution_start") {
21454
+ args.onToolCall();
21455
+ args.emittedToolNames.push(event.toolName);
21456
+ }
21457
+ yield event;
21458
+ }
21459
+ } catch (retryErr) {
21460
+ console.error(`[council] ${args.agent.id} retry failed:`, retryErr);
21461
+ }
21462
+ enforceDesignPhaseToolEmissions(args.agent.id, args.emittedToolNames);
21463
+ }
21464
+ var NON_RETRY_AGENTS, QUESTION_MARKER, QUESTION_END_MARKER, DESIGN_PHASE_REQUIREMENT_SETS, DESIGN_PHASE_REQUIREMENTS, MAX_RETRY_PER_MEMBER;
21012
21465
  var init_councilApi = __esm({
21013
21466
  "packages/core/dist/agents/councilApi.js"() {
21014
21467
  "use strict";
@@ -21018,8 +21471,30 @@ var init_councilApi = __esm({
21018
21471
  init_tools();
21019
21472
  init_events();
21020
21473
  init_AgentHarness();
21474
+ NON_RETRY_AGENTS = /* @__PURE__ */ new Set([]);
21021
21475
  QUESTION_MARKER = "---QUESTION---";
21022
21476
  QUESTION_END_MARKER = "---END---";
21477
+ DESIGN_PHASE_REQUIREMENT_SETS = {
21478
+ nettun: [
21479
+ [{ name: "createPlan", min: 1 }],
21480
+ [
21481
+ { name: "createPhase", min: 3 },
21482
+ { name: "createTask", min: 6 },
21483
+ { name: "createMilestone", min: 1 }
21484
+ ]
21485
+ ],
21486
+ geryon: [
21487
+ [{ name: "createDocument", min: 3 }]
21488
+ ],
21489
+ minos: [
21490
+ [{ name: "createDocument", min: 1 }]
21491
+ ],
21492
+ lucifer: [
21493
+ [{ name: "createDocument", min: 1 }]
21494
+ ]
21495
+ };
21496
+ DESIGN_PHASE_REQUIREMENTS = Object.fromEntries(Object.entries(DESIGN_PHASE_REQUIREMENT_SETS).map(([id, sets]) => [id, sets[0]]));
21497
+ MAX_RETRY_PER_MEMBER = 1;
21023
21498
  }
21024
21499
  });
21025
21500
 
@@ -21186,16 +21661,25 @@ var council_exports = {};
21186
21661
  __export(council_exports, {
21187
21662
  AGENT_ROLES: () => AGENT_ROLES,
21188
21663
  COLLABORATION_DIRECTIVE: () => COLLABORATION_DIRECTIVE,
21664
+ DESIGN_PHASE_REQUIREMENTS: () => DESIGN_PHASE_REQUIREMENTS,
21665
+ DESIGN_PHASE_REQUIREMENT_SETS: () => DESIGN_PHASE_REQUIREMENT_SETS,
21666
+ MAX_RETRY_PER_MEMBER: () => MAX_RETRY_PER_MEMBER,
21667
+ NON_RETRY_AGENTS: () => NON_RETRY_AGENTS,
21189
21668
  OUTPUT_QUALITY_DIRECTIVE: () => OUTPUT_QUALITY_DIRECTIVE,
21190
21669
  PROMPT_MODULES: () => PROMPT_MODULES,
21191
21670
  STRUCTURED_REASONING_DIRECTIVE: () => STRUCTURED_REASONING_DIRECTIVE,
21192
21671
  TOOL_USE_PROTOCOL_DIRECTIVE: () => TOOL_USE_PROTOCOL_DIRECTIVE,
21193
21672
  UnknownMemberError: () => UnknownMemberError,
21673
+ applyRetryIfMissing: () => applyRetryIfMissing,
21674
+ buildRetryPrompt: () => buildRetryPrompt,
21194
21675
  buildSkillDefinition: () => buildSkillDefinition,
21195
21676
  buildSystemPrompt: () => buildSystemPrompt,
21677
+ checkMemberToolEmissionSets: () => checkMemberToolEmissionSets,
21678
+ checkMemberToolEmissions: () => checkMemberToolEmissions,
21196
21679
  cleanAgentContent: () => cleanAgentContent,
21197
21680
  computeAgentSkills: () => computeAgentSkills,
21198
21681
  computeAgentTools: () => computeAgentTools,
21682
+ enforceDesignPhaseToolEmissions: () => enforceDesignPhaseToolEmissions,
21199
21683
  getAgent: () => getAgent,
21200
21684
  getBasePromptModules: () => getBasePromptModules,
21201
21685
  getCouncilAgents: () => getCouncilAgents,
@@ -21207,6 +21691,8 @@ __export(council_exports, {
21207
21691
  promoteMember: () => promoteMember,
21208
21692
  renderSkillMarkdown: () => renderSkillMarkdown,
21209
21693
  runCouncilPure: () => runCouncilPure,
21694
+ runRetryTurnForMember: () => runRetryTurnForMember,
21695
+ shouldRetryMember: () => shouldRetryMember,
21210
21696
  slugify: () => slugify3,
21211
21697
  stripClarificationProtocol: () => stripClarificationProtocol,
21212
21698
  swapMembers: () => swapMembers
@@ -21256,6 +21742,12 @@ async function* dispatchCouncil(userMessage, options) {
21256
21742
  maxToolCallsPerTurn: options.maxToolCallsPerTurn,
21257
21743
  feedbackStore: options.feedbackStore
21258
21744
  };
21745
+ if (!options.disableWorkspaceTools) {
21746
+ const { setWorkspaceStubs: setWorkspaceStubs2 } = await Promise.resolve().then(() => (init_skills2(), skills_exports));
21747
+ const { createWorkspaceContext: buildCtx, createWorkspaceStubs: buildStubs } = await Promise.resolve().then(() => (init_stubs(), stubs_exports));
21748
+ const wsCtx = options.workspaceRoot ? buildCtx(options.workspaceRoot) : buildCtx();
21749
+ setWorkspaceStubs2(buildStubs(wsCtx));
21750
+ }
21259
21751
  yield* runCouncilPure(userMessage, config2, {});
21260
21752
  }
21261
21753
  var CouncilDispatchError;
@@ -21524,36 +22016,220 @@ var init_agentsMd = __esm({
21524
22016
  }
21525
22017
  });
21526
22018
 
22019
+ // src/cli/workspace/completeDesign.ts
22020
+ function curatedTasksForPhase(phase) {
22021
+ const label = (phase.name ?? phase.id).trim();
22022
+ const scope = phase.description?.trim() ? ` Scope: ${phase.description.trim()}` : "";
22023
+ return [
22024
+ {
22025
+ title: `Specify ${label} deliverables`,
22026
+ description: `Turn the council documents for "${label}" into a concrete specification: enumerate deliverables, interfaces, constraints, and open decisions, grounding each item in the ADRs and design docs already present in .zelari/.${scope}`,
22027
+ fileRefs: [".zelari/docs/", ".zelari/decisions/"],
22028
+ acceptance: [
22029
+ `A written spec for "${label}" exists and references at least one ADR or design doc`,
22030
+ "Every deliverable in the spec has an owner artifact (file, doc, or component) named"
22031
+ ],
22032
+ qaScenario: `Open the spec for "${label}" and confirm each deliverable maps to a concrete artifact and no open decision is left unresolved.`,
22033
+ priority: "high"
22034
+ },
22035
+ {
22036
+ title: `Implement ${label} deliverables`,
22037
+ description: `Produce the artifacts the "${label}" phase promises, following the spec task of this phase and the decisions recorded in .zelari/decisions/.${scope}`,
22038
+ fileRefs: ["src/"],
22039
+ acceptance: [
22040
+ `Every deliverable listed in the "${label}" spec exists and builds/renders without errors`,
22041
+ "No implementation contradicts an accepted ADR"
22042
+ ],
22043
+ qaScenario: `Walk the "${label}" spec top to bottom and check each deliverable off against the produced artifact.`,
22044
+ priority: "high"
22045
+ },
22046
+ {
22047
+ title: `Verify ${label} exit criteria`,
22048
+ description: `Run the QA pass for "${label}": execute the QA scenarios of the phase tasks, check the phase row in the synthesis green-light checklist, and record any gaps as follow-up tasks.${scope}`,
22049
+ fileRefs: [".zelari/docs/synthesis.md"],
22050
+ acceptance: [
22051
+ `The "${label}" row of the synthesis green-light checklist passes`,
22052
+ "All QA scenarios of this phase have been executed with recorded outcomes"
22053
+ ],
22054
+ qaScenario: `Open .zelari/docs/synthesis.md, locate the "${label}" checklist row, and confirm every criterion is checked with evidence.`,
22055
+ priority: "medium"
22056
+ }
22057
+ ];
22058
+ }
22059
+ async function runBuiltinCompleteDesign(ctx) {
22060
+ const summary = readPlanSummary(ctx);
22061
+ if (summary.phases.length === 0) {
22062
+ return { ran: false, tasksAdded: 0, milestonesAdded: 0, reason: "plan has no phases" };
22063
+ }
22064
+ const stubs = createWorkspaceStubs(ctx);
22065
+ const createTask = stubs.find((s) => s.name === "createTask");
22066
+ const createMilestone = stubs.find((s) => s.name === "createMilestone");
22067
+ const stubCtx = ctx;
22068
+ let tasksAdded = 0;
22069
+ for (const phase of summary.phases) {
22070
+ const existingTasks = summary.tasks.filter((t) => t.phaseId === phase.id);
22071
+ if (existingTasks.length >= MIN_TASKS_PER_PHASE) continue;
22072
+ const existingTitles = new Set(existingTasks.map((t) => t.name ?? ""));
22073
+ const templates = curatedTasksForPhase(phase).filter((t) => !existingTitles.has(t.title)).slice(0, MIN_TASKS_PER_PHASE - existingTasks.length);
22074
+ for (const t of templates) {
22075
+ await createTask.execute(
22076
+ {
22077
+ phaseId: phase.id,
22078
+ title: t.title,
22079
+ description: t.description,
22080
+ fileRefs: t.fileRefs,
22081
+ acceptance: t.acceptance,
22082
+ qaScenario: t.qaScenario,
22083
+ priority: t.priority
22084
+ },
22085
+ stubCtx
22086
+ );
22087
+ tasksAdded += 1;
22088
+ }
22089
+ }
22090
+ let milestonesAdded = 0;
22091
+ if (summary.milestones.length === 0) {
22092
+ await createMilestone.execute(
22093
+ {
22094
+ title: "v0.1.0 design-complete",
22095
+ description: "All design-phase artifacts (phases, tasks, ADRs, design docs, risks, synthesis) exist and the green-light checklist passes.",
22096
+ targetVersion: "v0.1.0"
22097
+ },
22098
+ stubCtx
22099
+ );
22100
+ milestonesAdded = 1;
22101
+ }
22102
+ return { ran: true, tasksAdded, milestonesAdded };
22103
+ }
22104
+ var MIN_TASKS_PER_PHASE;
22105
+ var init_completeDesign = __esm({
22106
+ "src/cli/workspace/completeDesign.ts"() {
22107
+ "use strict";
22108
+ init_stubs();
22109
+ MIN_TASKS_PER_PHASE = 3;
22110
+ }
22111
+ });
22112
+
21527
22113
  // src/cli/workspace/postCouncilHook.ts
21528
22114
  var postCouncilHook_exports = {};
21529
22115
  __export(postCouncilHook_exports, {
22116
+ runCompleteDesignPostProcessor: () => runCompleteDesignPostProcessor,
21530
22117
  runPostCouncilHook: () => runPostCouncilHook
21531
22118
  });
21532
- async function runPostCouncilHook(ctx) {
21533
- if (process.env["ZELARI_AGENTS_MD"] === "0") {
21534
- return { ran: false, changed: false, sections: [], reason: "ZELARI_AGENTS_MD=0 (disabled)" };
22119
+ import { spawn as spawn3 } from "node:child_process";
22120
+ import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
22121
+ import { join as join7 } from "node:path";
22122
+ async function runCompleteDesignPostProcessor(ctx) {
22123
+ if (process.env["ZELARI_COMPLETE_DESIGN"] === "0") {
22124
+ return { ran: false, reason: "ZELARI_COMPLETE_DESIGN=0 (disabled)" };
22125
+ }
22126
+ const planJsonPath2 = join7(ctx.rootDir, "plan.json");
22127
+ const scriptPath = join7(ctx.projectRoot, "complete-design.mjs");
22128
+ if (!existsSync12(planJsonPath2)) {
22129
+ return { ran: false, reason: ".zelari/plan.json missing (not design-phase)" };
21535
22130
  }
22131
+ let phaseCount = 0;
21536
22132
  try {
21537
- const result = await updateAgentsMd(ctx, ctx.projectRoot);
21538
- return {
21539
- ran: true,
21540
- changed: result.changed,
21541
- sections: result.sections,
21542
- ...result.reason ? { reason: result.reason } : {}
21543
- };
21544
- } catch (err) {
21545
- return {
22133
+ const parsed = JSON.parse(readFileSync9(planJsonPath2, "utf8"));
22134
+ phaseCount = Array.isArray(parsed.phases) ? parsed.phases.length : 0;
22135
+ } catch {
22136
+ return { ran: false, reason: ".zelari/plan.json corrupt" };
22137
+ }
22138
+ if (phaseCount === 0) {
22139
+ return { ran: false, reason: ".zelari/plan.json has no phases" };
22140
+ }
22141
+ if (!existsSync12(scriptPath)) {
22142
+ try {
22143
+ const builtin = await runBuiltinCompleteDesign(ctx);
22144
+ return {
22145
+ ran: builtin.ran,
22146
+ ...builtin.ran ? { exitCode: 0 } : {},
22147
+ output: `builtin complete-design: +${builtin.tasksAdded} tasks, +${builtin.milestonesAdded} milestones`,
22148
+ ...builtin.reason ? { reason: builtin.reason } : {}
22149
+ };
22150
+ } catch (err) {
22151
+ return {
22152
+ ran: true,
22153
+ exitCode: -1,
22154
+ reason: `builtin complete-design error: ${err instanceof Error ? err.message : String(err)}`
22155
+ };
22156
+ }
22157
+ }
22158
+ return await new Promise((resolveRun) => {
22159
+ const child = spawn3(process.execPath, [scriptPath], {
22160
+ cwd: ctx.projectRoot,
22161
+ stdio: ["ignore", "pipe", "pipe"],
22162
+ env: process.env
22163
+ });
22164
+ let stdout = "";
22165
+ let stderr = "";
22166
+ child.stdout?.on("data", (chunk) => {
22167
+ stdout += chunk.toString("utf8");
22168
+ });
22169
+ child.stderr?.on("data", (chunk) => {
22170
+ stderr += chunk.toString("utf8");
22171
+ });
22172
+ child.on("error", (err) => {
22173
+ resolveRun({
22174
+ ran: true,
22175
+ exitCode: -1,
22176
+ output: stderr,
22177
+ reason: `spawn error: ${err.message}`
22178
+ });
22179
+ });
22180
+ child.on("close", (code) => {
22181
+ const exitCode = code ?? -1;
22182
+ const ok = exitCode === 0;
22183
+ resolveRun({
22184
+ ran: true,
22185
+ exitCode,
22186
+ output: stdout + stderr,
22187
+ ...ok ? {} : { reason: `complete-design exited with code ${exitCode}` }
22188
+ });
22189
+ });
22190
+ });
22191
+ }
22192
+ async function runPostCouncilHook(ctx) {
22193
+ let agentsMdResult;
22194
+ if (process.env["ZELARI_AGENTS_MD"] === "0") {
22195
+ agentsMdResult = {
21546
22196
  ran: false,
21547
22197
  changed: false,
21548
22198
  sections: [],
21549
- reason: `error: ${err instanceof Error ? err.message : String(err)}`
22199
+ reason: "ZELARI_AGENTS_MD=0 (disabled)"
21550
22200
  };
22201
+ } else {
22202
+ try {
22203
+ const result = await updateAgentsMd(ctx, ctx.projectRoot);
22204
+ agentsMdResult = {
22205
+ ran: true,
22206
+ changed: result.changed,
22207
+ sections: result.sections,
22208
+ ...result.reason ? { reason: result.reason } : {}
22209
+ };
22210
+ } catch (err) {
22211
+ agentsMdResult = {
22212
+ ran: false,
22213
+ changed: false,
22214
+ sections: [],
22215
+ reason: `error: ${err instanceof Error ? err.message : String(err)}`
22216
+ };
22217
+ }
21551
22218
  }
22219
+ const completeDesign = await runCompleteDesignPostProcessor(ctx);
22220
+ return {
22221
+ ran: agentsMdResult.ran || completeDesign.ran,
22222
+ changed: agentsMdResult.changed,
22223
+ sections: agentsMdResult.sections,
22224
+ ...agentsMdResult.reason ? { reason: agentsMdResult.reason } : {},
22225
+ completeDesign
22226
+ };
21552
22227
  }
21553
22228
  var init_postCouncilHook = __esm({
21554
22229
  "src/cli/workspace/postCouncilHook.ts"() {
21555
22230
  "use strict";
21556
22231
  init_agentsMd();
22232
+ init_completeDesign();
21557
22233
  }
21558
22234
  });
21559
22235
 
@@ -21564,8 +22240,8 @@ __export(councilFeedback_exports, {
21564
22240
  });
21565
22241
  import {
21566
22242
  promises as fs11,
21567
- existsSync as existsSync12,
21568
- readFileSync as readFileSync9,
22243
+ existsSync as existsSync13,
22244
+ readFileSync as readFileSync10,
21569
22245
  writeFileSync as writeFileSync8,
21570
22246
  mkdirSync as mkdirSync8
21571
22247
  } from "node:fs";
@@ -21673,9 +22349,9 @@ var init_councilFeedback = __esm({
21673
22349
  }
21674
22350
  // --- persistence ---------------------------------------------------------
21675
22351
  load() {
21676
- if (!existsSync12(this.file)) return;
22352
+ if (!existsSync13(this.file)) return;
21677
22353
  try {
21678
- const raw = readFileSync9(this.file, "utf-8");
22354
+ const raw = readFileSync10(this.file, "utf-8");
21679
22355
  const parsed = JSON.parse(raw);
21680
22356
  if (parsed && Array.isArray(parsed.entries)) {
21681
22357
  this.entries = parsed.entries.filter(
@@ -21721,7 +22397,7 @@ __export(updater_exports, {
21721
22397
  performUpdate: () => performUpdate
21722
22398
  });
21723
22399
  import { createRequire } from "node:module";
21724
- import { spawn as spawn3 } from "node:child_process";
22400
+ import { spawn as spawn4 } from "node:child_process";
21725
22401
  import path16 from "node:path";
21726
22402
  import { fileURLToPath } from "node:url";
21727
22403
  function getCurrentVersion() {
@@ -21786,7 +22462,7 @@ async function checkForUpdate(fetcher = fetch, registryUrl) {
21786
22462
  updateAvailable: cmp < 0
21787
22463
  };
21788
22464
  }
21789
- async function performUpdate(packageName = "zelari-code", executor = spawn3) {
22465
+ async function performUpdate(packageName = "zelari-code", executor = spawn4) {
21790
22466
  return new Promise((resolve) => {
21791
22467
  const args = ["install", "-g", `${packageName}@latest`];
21792
22468
  let stdout = "";
@@ -21831,7 +22507,7 @@ var init_updater = __esm({
21831
22507
  });
21832
22508
 
21833
22509
  // src/cli/main.ts
21834
- import React9 from "react";
22510
+ import React10 from "react";
21835
22511
  import { render } from "ink";
21836
22512
 
21837
22513
  // src/cli/app.tsx
@@ -26651,7 +27327,7 @@ async function handlePromoteMember(ctx, memberId) {
26651
27327
  }
26652
27328
 
26653
27329
  // src/cli/branchManager.ts
26654
- import { promises as fs13, existsSync as existsSync13, readFileSync as readFileSync10, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, statSync as statSync4, rmSync } from "node:fs";
27330
+ import { promises as fs13, existsSync as existsSync14, readFileSync as readFileSync11, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, statSync as statSync4, rmSync } from "node:fs";
26655
27331
  import path18 from "node:path";
26656
27332
  import os9 from "node:os";
26657
27333
  var META_FILENAME = "meta.json";
@@ -26673,11 +27349,11 @@ function sessionsPathFor(name, baseDir) {
26673
27349
  }
26674
27350
  function readBranchMeta(name, baseDir) {
26675
27351
  const metaPath = metaPathFor(name, baseDir);
26676
- if (!existsSync13(metaPath)) {
27352
+ if (!existsSync14(metaPath)) {
26677
27353
  throw new BranchNotFoundError(`Branch "${name}" not found`);
26678
27354
  }
26679
27355
  try {
26680
- const raw = readFileSync10(metaPath, "utf-8");
27356
+ const raw = readFileSync11(metaPath, "utf-8");
26681
27357
  const parsed = JSON.parse(raw);
26682
27358
  if (!parsed || typeof parsed !== "object" || typeof parsed.name !== "string" || typeof parsed.createdAt !== "number" || typeof parsed.fromSessionId !== "string") {
26683
27359
  throw new BranchCorruptError(`Branch "${name}" meta.json is malformed`);
@@ -26733,7 +27409,7 @@ var SessionNotFoundError = class extends Error {
26733
27409
  };
26734
27410
  function branchExists(name, baseDir = getBranchesBaseDir()) {
26735
27411
  const bp = branchPathFor(name, baseDir);
26736
- return existsSync13(bp) && existsSync13(metaPathFor(name, baseDir));
27412
+ return existsSync14(bp) && existsSync14(metaPathFor(name, baseDir));
26737
27413
  }
26738
27414
  async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(), sessionsBaseDir = getSessionsBaseDir()) {
26739
27415
  if (!name || name.trim().length === 0) {
@@ -26746,7 +27422,7 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
26746
27422
  throw new BranchAlreadyExistsError(name);
26747
27423
  }
26748
27424
  const sourcePath = path18.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
26749
- if (!existsSync13(sourcePath)) {
27425
+ if (!existsSync14(sourcePath)) {
26750
27426
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
26751
27427
  }
26752
27428
  const branchPath = branchPathFor(name, baseDir);
@@ -26779,7 +27455,7 @@ async function listBranches(baseDir = getBranchesBaseDir()) {
26779
27455
  const results = [];
26780
27456
  for (const entry of entries) {
26781
27457
  const metaPath = metaPathFor(entry, baseDir);
26782
- if (!existsSync13(metaPath)) continue;
27458
+ if (!existsSync14(metaPath)) continue;
26783
27459
  try {
26784
27460
  const meta3 = readBranchMeta(entry, baseDir);
26785
27461
  const sessionCount = await countSessions(entry, baseDir);
@@ -27017,7 +27693,7 @@ async function validateApiKey(providerId, apiKey, options = {}) {
27017
27693
  init_providerConfig();
27018
27694
 
27019
27695
  // src/cli/modelDiscovery.ts
27020
- import { promises as fs15, existsSync as existsSync14, readFileSync as readFileSync11 } from "node:fs";
27696
+ import { promises as fs15, existsSync as existsSync15, readFileSync as readFileSync12 } from "node:fs";
27021
27697
  import { homedir as homedir3 } from "node:os";
27022
27698
  import path20 from "node:path";
27023
27699
  var PROVIDER_BASE_URLS = {
@@ -27033,9 +27709,9 @@ function getModelsFilePath() {
27033
27709
  return defaultModelsFilePath();
27034
27710
  }
27035
27711
  function loadModelsRegistry(file2 = getModelsFilePath()) {
27036
- if (!existsSync14(file2)) return {};
27712
+ if (!existsSync15(file2)) return {};
27037
27713
  try {
27038
- const raw = readFileSync11(file2, "utf-8");
27714
+ const raw = readFileSync12(file2, "utf-8");
27039
27715
  const parsed = JSON.parse(raw);
27040
27716
  if (!parsed || typeof parsed !== "object") return {};
27041
27717
  return parsed;
@@ -27395,7 +28071,7 @@ import path21 from "node:path";
27395
28071
  import os10 from "node:os";
27396
28072
 
27397
28073
  // src/cli/skillHistory.ts
27398
- import { promises as fs16, existsSync as existsSync15, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync2, mkdirSync as mkdirSync11 } from "node:fs";
28074
+ import { promises as fs16, existsSync as existsSync16, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync2, mkdirSync as mkdirSync11 } from "node:fs";
27399
28075
  var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
27400
28076
  async function readSkillHistory(file2) {
27401
28077
  let raw = "";
@@ -28019,7 +28695,7 @@ function App() {
28019
28695
  id: "banner-once",
28020
28696
  role: "system",
28021
28697
  ts: 0,
28022
- content: `zelari-code v0.7.3 \u2014 ${activeProviderSpec.id}/${activeModel}
28698
+ content: `zelari-code v${VERSION} \u2014 ${activeProviderSpec.id}/${activeModel}
28023
28699
  ${skills.length} skills available. Type /help for the list, or /skill <name>.
28024
28700
  \u2500\u2500 skills \u2500\u2500
28025
28701
  ${skillList}`
@@ -28050,13 +28726,147 @@ ${skillList}`
28050
28726
  )));
28051
28727
  }
28052
28728
 
28729
+ // src/cli/components/SplashScreen.tsx
28730
+ import React7, { useEffect as useEffect4, useState as useState5 } from "react";
28731
+ import { Box as Box7, Text as Text7, useInput, useStdin } from "ink";
28732
+ var SPLASH_DURATION_MS = 2e3;
28733
+ var EMBLEM_LARGE = ` =%@*-
28734
+ =%@@@@@#:
28735
+ *%%%%%%@@%-
28736
+ :#%%%%%%%%%@@*
28737
+ :*%%%%%%%%%%%%%@%*.
28738
+ =%%%%%%%%%%%%%%%%@@%.
28739
+ =%%%%%%%%%%%%%%%%%%%@%-
28740
+ *%%%%%%%%%%%%%%%%%%%%%@@=
28741
+ .#%%%%%%%%@@@@@@@@%%%%%%@@@+
28742
+ :#%%%%%%@@@@@@@@@@@@@@@%%%%@@+
28743
+ -%%%%%@@@@@@@@@@@@@@@@@@@@@%%@@#.
28744
+ -%%%%%@@@@@@@@@@@@@@@@@@@@@@@%%@@%:
28745
+ =%%%%@@@@@@@@@@@@@@@@@@@@@@@@@@@%@@%.
28746
+ +%%%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%@@%:
28747
+ =%%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-
28748
+ -%%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%:
28749
+ :%%%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%
28750
+ #%%%%%@@@@@@@@@@@@@@@%--#%@@@@@@@@@@@@@%%@@@=
28751
+ #@@%%%%@@@@@@@@@@@@@@%: -*@@@@@@@@@@@%%@@@@=
28752
+ #@@%%%%@@@@@@@@@@@@@%: .. -#@@@@@@@%%%%@@@=
28753
+ +@@@%%%@@@@@@@@@@@@%: :#= =%@@@@%%%@@@%-
28754
+ =@@@@%%%@@@@@@@@@@%: -%%*: +%@@%%@@@#
28755
+ +%@@@@%%@@@@@@@@@%: -%@@%*=.:+#@@@@#:
28756
+ .+*@@@@@@@@@%%%@@@@@@%: -%@@@@@#- +@@@@@@*=
28757
+ =*%%%%@@@@@@@@@@@%%@@@@%: -#@@@@@@@*: :*@@@@@@%+=
28758
+ -*%%%%%%%%%%@@@@@@@@@@@@@@@: -%@@@@@@@@#- -#@@@@@@@@*
28759
+ #%%%%%%%%%%%%%@@@@@@@@@@@@@: -#@@@@@%+-::. *@@@@@@@@=
28760
+ =%%%%%%%%%%%%%%%@@@@@@@@@@@@: :*+--=%#. :+++++*@@@@@@@@%
28761
+ #@%%%%%%%%%%%%%@@@@@@@@@@@@%: .. - -## *@@@@@@@@@@@@@@=
28762
+ :@@%%%%%%%%%%%%@@@@@@@@@@@@@%: .**= *%= :%@@@@@@@@@@@@@%
28763
+ #@@@%%%%%%%%%%%@@@@@@@@@@@@@@- =#@@%- :**: =%@@@@@@@@@@@@@+
28764
+ =@@@@@%%%@@%%%%@@@@@@@@@@@@@@@@@@@@@@%- :: .-#@@@@@@@@@@@@@%.
28765
+ :@@@@@@%%@@@%%@@@@@@@@@@@@@@@@@@@@@@@@@%==*%%@@@@@@@@@@@@@@@@#
28766
+ *@@@@@@@%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:
28767
+ :@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+
28768
+ =##########*######**##**#*********##**#*#######################*`;
28769
+ var EMBLEM_SMALL = ` =%%*:
28770
+ :#%%@@@+
28771
+ =%%%%%%%@#.
28772
+ .*%%%%%%%%%@%-
28773
+ =%%%%%%%%%%%%%@*.
28774
+ +%%%%%%@@@@@%%%%@%:
28775
+ *%%%%@@@@@@@@@@@%%@%:
28776
+ *%%%@@@@@@@@@@@@@@@%@%-
28777
+ :#%%@@@@@@@@@@@@@@@@@@@@@+
28778
+ :%%%@@@@@@@@@@@@@@@@@@@@@@@+
28779
+ #%%@@@@@@@@@@@@@@@@@@@@@@@@@-
28780
+ +%%@@@@@@@@@@@@#@@@@@@@@@@@@@%:
28781
+ %%%%@@@@@@@@@@%::*@@@@@@@@@%@@=
28782
+ -%@%%@@@@@@@@@%:.::*@@@@@%%@@*.
28783
+ :%@@%%@@@@@@@%.:%#::*@@%@@@-
28784
+ :#@@@%@@@@@@%.:%@%+.-#@@@=
28785
+ :+#@@@@@@@%@@@@%.:%@@@%=.+@@@%*-
28786
+ .*#%%%%%%@@@@@@@@@@.:%@@@@@*:.+%@@@@#-
28787
+ +%%%%%%%%%%@@@@@@@@::##*%* :----%@@@@%.
28788
+ .@%%%%%%%%%@@@@@@@@@:.-..-#=:%@@@@@@@@@+
28789
+ *@@%%%%%%%%@@@@@@@@@: =#+ +#.-@@@@@@@@@@.
28790
+ -@@@%%%%%%%@@@@@@@@@@#%@@@- -.:*@@@@@@@@@*
28791
+ %@@@@%@@%@@@@@@@@@@@@@@@@@%**%@@@@@@@@@@@@:
28792
+ =@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*`;
28793
+ var FOOTER_ROWS = 5;
28794
+ function measure(art) {
28795
+ const lines = art.split("\n");
28796
+ return {
28797
+ art,
28798
+ width: Math.max(...lines.map((l) => l.length)),
28799
+ height: lines.length
28800
+ };
28801
+ }
28802
+ var VARIANTS = [measure(EMBLEM_LARGE), measure(EMBLEM_SMALL)];
28803
+ function pickSplashArt(columns, rows) {
28804
+ for (const v of VARIANTS) {
28805
+ if (v.width <= columns - 2 && v.height + FOOTER_ROWS <= rows) return v;
28806
+ }
28807
+ return null;
28808
+ }
28809
+ function shouldShowSplash(opts) {
28810
+ if (!opts.isTTY) return false;
28811
+ if (opts.env["ZELARI_NO_SPLASH"] === "1") return false;
28812
+ return pickSplashArt(opts.columns, opts.rows) !== null;
28813
+ }
28814
+ function Splash({ onDone, version: version2 }) {
28815
+ const { isRawModeSupported } = useStdin();
28816
+ const columns = process.stdout.columns ?? 80;
28817
+ const rows = process.stdout.rows ?? 24;
28818
+ const picked = pickSplashArt(columns, rows);
28819
+ useEffect4(() => {
28820
+ const t = setTimeout(onDone, SPLASH_DURATION_MS);
28821
+ return () => clearTimeout(t);
28822
+ }, [onDone]);
28823
+ useInput(
28824
+ () => {
28825
+ onDone();
28826
+ },
28827
+ { isActive: isRawModeSupported === true }
28828
+ );
28829
+ if (!picked) return null;
28830
+ return /* @__PURE__ */ React7.createElement(
28831
+ Box7,
28832
+ {
28833
+ flexDirection: "column",
28834
+ alignItems: "center",
28835
+ justifyContent: "center",
28836
+ width: columns,
28837
+ height: rows - 1
28838
+ },
28839
+ /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, picked.art),
28840
+ /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "white" }, "Z E L A R I C O D E")),
28841
+ /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, `${version2 ? `v${version2} \u2014 ` : ""}N-THEM Studio`),
28842
+ /* @__PURE__ */ React7.createElement(Text7, { dimColor: true, italic: true }, "press any key to skip")
28843
+ );
28844
+ }
28845
+ function SplashGate({
28846
+ children,
28847
+ version: version2
28848
+ }) {
28849
+ const [show, setShow] = useState5(
28850
+ () => shouldShowSplash({
28851
+ isTTY: process.stdout.isTTY === true,
28852
+ env: process.env,
28853
+ columns: process.stdout.columns ?? 80,
28854
+ rows: process.stdout.rows ?? 24
28855
+ })
28856
+ );
28857
+ if (show) {
28858
+ return /* @__PURE__ */ React7.createElement(Splash, { onDone: () => setShow(false), version: version2 });
28859
+ }
28860
+ return /* @__PURE__ */ React7.createElement(React7.Fragment, null, children);
28861
+ }
28862
+
28053
28863
  // src/cli/main.ts
28054
28864
  init_providerConfig();
28055
28865
 
28056
28866
  // src/cli/wizard/firstRun.ts
28057
- import { existsSync as existsSync16 } from "node:fs";
28867
+ import { existsSync as existsSync17 } from "node:fs";
28058
28868
  function shouldRunWizard(input) {
28059
- const exists = input.exists ?? existsSync16;
28869
+ const exists = input.exists ?? existsSync17;
28060
28870
  if (input.hasResetConfigFlag) {
28061
28871
  return { shouldRun: true, reason: "--reset-config flag forced wizard" };
28062
28872
  }
@@ -28091,15 +28901,15 @@ function parseWizardFlags(argv) {
28091
28901
  }
28092
28902
 
28093
28903
  // src/cli/wizard/runWizard.tsx
28094
- import React8, { useEffect as useEffect4, useState as useState5 } from "react";
28095
- import { Box as Box8, Text as Text8 } from "ink";
28096
- import { useInput } from "ink";
28904
+ import React9, { useEffect as useEffect5, useState as useState6 } from "react";
28905
+ import { Box as Box9, Text as Text9 } from "ink";
28906
+ import { useInput as useInput2 } from "ink";
28097
28907
  init_keyStore();
28098
28908
  init_providerConfig();
28099
28909
 
28100
28910
  // src/cli/wizard/index.tsx
28101
- import React7 from "react";
28102
- import { Box as Box7, Text as Text7 } from "ink";
28911
+ import React8 from "react";
28912
+ import { Box as Box8, Text as Text8 } from "ink";
28103
28913
 
28104
28914
  // src/cli/wizard/useWizardState.ts
28105
28915
  init_providerConfig();
@@ -28214,18 +29024,17 @@ function createWizardState(opts) {
28214
29024
  var API_KEY_OPTIONS = ["env", "keystore", "skip"];
28215
29025
 
28216
29026
  // src/cli/wizard/index.tsx
28217
- var VERSION = "0.7.2";
28218
29027
  function Frame({ children }) {
28219
- return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1 }, children);
29028
+ return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1 }, children);
28220
29029
  }
28221
29030
  function Step(props) {
28222
- return /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(Text7, { color: props.active ? "cyan" : "gray", inverse: props.active }, " ", props.index, "/", props.total, " ", props.name, " "));
29031
+ return /* @__PURE__ */ React8.createElement(Box8, null, /* @__PURE__ */ React8.createElement(Text8, { color: props.active ? "cyan" : "gray", inverse: props.active }, " ", props.index, "/", props.total, " ", props.name, " "));
28223
29032
  }
28224
29033
  function renderProviderList(providers, cursor) {
28225
- return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column" }, providers.map((p3, i) => {
29034
+ return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column" }, providers.map((p3, i) => {
28226
29035
  const arrow = i === cursor ? "\u279C " : " ";
28227
29036
  const color = i === cursor ? "cyan" : void 0;
28228
- return /* @__PURE__ */ React7.createElement(Text7, { key: p3.id, color }, arrow, p3.displayName, " ", /* @__PURE__ */ React7.createElement(Text7, { color: "gray" }, "(", p3.id, ", uses env ", p3.envVar, ")"));
29037
+ return /* @__PURE__ */ React8.createElement(Text8, { key: p3.id, color }, arrow, p3.displayName, " ", /* @__PURE__ */ React8.createElement(Text8, { color: "gray" }, "(", p3.id, ", uses env ", p3.envVar, ")"));
28229
29038
  }));
28230
29039
  }
28231
29040
  function renderApiKeyOptions(cursor) {
@@ -28234,37 +29043,37 @@ function renderApiKeyOptions(cursor) {
28234
29043
  keystore: "Save to local keyStore (encrypted)",
28235
29044
  skip: "Skip for now (chat will fail until key is set)"
28236
29045
  };
28237
- return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column" }, API_KEY_OPTIONS.map((choice, i) => {
29046
+ return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column" }, API_KEY_OPTIONS.map((choice, i) => {
28238
29047
  const arrow = i === cursor ? "\u279C " : " ";
28239
29048
  const color = i === cursor ? "cyan" : void 0;
28240
- return /* @__PURE__ */ React7.createElement(Text7, { key: choice, color }, arrow, labels[choice]);
29049
+ return /* @__PURE__ */ React8.createElement(Text8, { key: choice, color }, arrow, labels[choice]);
28241
29050
  }));
28242
29051
  }
28243
29052
  function Wizard({ state: state2, providers }) {
28244
29053
  const s = state2.state;
28245
29054
  if (s.committed) {
28246
- return /* @__PURE__ */ React7.createElement(Frame, null, /* @__PURE__ */ React7.createElement(Text7, { color: "green" }, "\u2713 Setup complete!"), /* @__PURE__ */ React7.createElement(Text7, null, "Provider: ", /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, s.providerId), " | ", "Model: ", /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, s.model), " | ", "API key: ", /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, s.apiKeyChoice ?? "n/a")), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { color: "gray" }, "Launching zelari-code\u2026 (press any key)")));
29055
+ return /* @__PURE__ */ React8.createElement(Frame, null, /* @__PURE__ */ React8.createElement(Text8, { color: "green" }, "\u2713 Setup complete!"), /* @__PURE__ */ React8.createElement(Text8, null, "Provider: ", /* @__PURE__ */ React8.createElement(Text8, { color: "cyan" }, s.providerId), " | ", "Model: ", /* @__PURE__ */ React8.createElement(Text8, { color: "cyan" }, s.model), " | ", "API key: ", /* @__PURE__ */ React8.createElement(Text8, { color: "cyan" }, s.apiKeyChoice ?? "n/a")), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, { color: "gray" }, "Launching zelari-code\u2026 (press any key)")));
28247
29056
  }
28248
- return /* @__PURE__ */ React7.createElement(Frame, null, /* @__PURE__ */ React7.createElement(Text7, { color: "cyan", bold: true }, "zelari-code v", VERSION, " \u2014 first-time setup"), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, marginBottom: 1, flexDirection: "row" }, /* @__PURE__ */ React7.createElement(Step, { index: 1, total: 5, name: "welcome", active: s.step === "welcome" }), /* @__PURE__ */ React7.createElement(Step, { index: 2, total: 5, name: "provider", active: s.step === "provider" }), /* @__PURE__ */ React7.createElement(Step, { index: 3, total: 5, name: "model", active: s.step === "model" }), /* @__PURE__ */ React7.createElement(Step, { index: 4, total: 5, name: "apikey", active: s.step === "apikey" }), /* @__PURE__ */ React7.createElement(Step, { index: 5, total: 5, name: "confirm", active: s.step === "confirm" })), s.step === "welcome" && /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(Text7, null, "Welcome! Let's get you coding in under two minutes."), /* @__PURE__ */ React7.createElement(Text7, { color: "gray" }, "We'll pick a provider, default model, and how to handle your API key."), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, null, "Press "), /* @__PURE__ */ React7.createElement(Text7, { color: "cyan", inverse: true }, " Enter "), /* @__PURE__ */ React7.createElement(Text7, null, " to continue, or "), /* @__PURE__ */ React7.createElement(Text7, { color: "red", inverse: true }, " Q "), /* @__PURE__ */ React7.createElement(Text7, null, " to quit (re-run with "), /* @__PURE__ */ React7.createElement(Text7, { color: "gray" }, "--no-wizard"), /* @__PURE__ */ React7.createElement(Text7, null, " to skip later)."))), s.step === "provider" && /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(Text7, null, "Choose your LLM provider:"), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, renderProviderList(providers, s.providerCursor)), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { color: "gray" }, "\u2191/\u2193 to move, Enter to confirm"))), s.step === "model" && /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(Text7, null, "Model for ", /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, s.providerId), ":"), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, borderStyle: "single", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React7.createElement(Text7, null, s.model ?? "(empty)")), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, null, "Press ", /* @__PURE__ */ React7.createElement(Text7, { color: "cyan", inverse: true }, " Enter "), "to accept, or type a new name. ", /* @__PURE__ */ React7.createElement(Text7, { color: "gray" }, "(default kept on empty input)")))), s.step === "apikey" && /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(Text7, null, "How should we handle the API key?"), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, renderApiKeyOptions(s.apiKeyCursor)), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { color: "gray" }, "\u2191/\u2193 to move, Enter to confirm"))), s.step === "confirm" && /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(Text7, null, "Confirm your setup:"), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, null, "Provider: ", /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, s.providerId)), /* @__PURE__ */ React7.createElement(Text7, null, "Model: ", /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, s.model)), /* @__PURE__ */ React7.createElement(Text7, null, "API key: ", /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, s.apiKeyChoice ?? "(unset)"))), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, null, "Press ", /* @__PURE__ */ React7.createElement(Text7, { color: "green", inverse: true }, " Enter "), "to save and launch, or ", /* @__PURE__ */ React7.createElement(Text7, { color: "yellow", inverse: true }, " B "), "to go back."))));
29057
+ return /* @__PURE__ */ React8.createElement(Frame, null, /* @__PURE__ */ React8.createElement(Text8, { color: "cyan", bold: true }, "zelari-code v", VERSION, " \u2014 first-time setup"), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1, marginBottom: 1, flexDirection: "row" }, /* @__PURE__ */ React8.createElement(Step, { index: 1, total: 5, name: "welcome", active: s.step === "welcome" }), /* @__PURE__ */ React8.createElement(Step, { index: 2, total: 5, name: "provider", active: s.step === "provider" }), /* @__PURE__ */ React8.createElement(Step, { index: 3, total: 5, name: "model", active: s.step === "model" }), /* @__PURE__ */ React8.createElement(Step, { index: 4, total: 5, name: "apikey", active: s.step === "apikey" }), /* @__PURE__ */ React8.createElement(Step, { index: 5, total: 5, name: "confirm", active: s.step === "confirm" })), s.step === "welcome" && /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Text8, null, "Welcome! Let's get you coding in under two minutes."), /* @__PURE__ */ React8.createElement(Text8, { color: "gray" }, "We'll pick a provider, default model, and how to handle your API key."), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, null, "Press "), /* @__PURE__ */ React8.createElement(Text8, { color: "cyan", inverse: true }, " Enter "), /* @__PURE__ */ React8.createElement(Text8, null, " to continue, or "), /* @__PURE__ */ React8.createElement(Text8, { color: "red", inverse: true }, " Q "), /* @__PURE__ */ React8.createElement(Text8, null, " to quit (re-run with "), /* @__PURE__ */ React8.createElement(Text8, { color: "gray" }, "--no-wizard"), /* @__PURE__ */ React8.createElement(Text8, null, " to skip later)."))), s.step === "provider" && /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Text8, null, "Choose your LLM provider:"), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1 }, renderProviderList(providers, s.providerCursor)), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, { color: "gray" }, "\u2191/\u2193 to move, Enter to confirm"))), s.step === "model" && /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Text8, null, "Model for ", /* @__PURE__ */ React8.createElement(Text8, { color: "cyan" }, s.providerId), ":"), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1, borderStyle: "single", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React8.createElement(Text8, null, s.model ?? "(empty)")), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, null, "Press ", /* @__PURE__ */ React8.createElement(Text8, { color: "cyan", inverse: true }, " Enter "), "to accept, or type a new name. ", /* @__PURE__ */ React8.createElement(Text8, { color: "gray" }, "(default kept on empty input)")))), s.step === "apikey" && /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Text8, null, "How should we handle the API key?"), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1 }, renderApiKeyOptions(s.apiKeyCursor)), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, { color: "gray" }, "\u2191/\u2193 to move, Enter to confirm"))), s.step === "confirm" && /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Text8, null, "Confirm your setup:"), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React8.createElement(Text8, null, "Provider: ", /* @__PURE__ */ React8.createElement(Text8, { color: "cyan" }, s.providerId)), /* @__PURE__ */ React8.createElement(Text8, null, "Model: ", /* @__PURE__ */ React8.createElement(Text8, { color: "cyan" }, s.model)), /* @__PURE__ */ React8.createElement(Text8, null, "API key: ", /* @__PURE__ */ React8.createElement(Text8, { color: "cyan" }, s.apiKeyChoice ?? "(unset)"))), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, null, "Press ", /* @__PURE__ */ React8.createElement(Text8, { color: "green", inverse: true }, " Enter "), "to save and launch, or ", /* @__PURE__ */ React8.createElement(Text8, { color: "yellow", inverse: true }, " B "), "to go back."))));
28249
29058
  }
28250
29059
 
28251
29060
  // src/cli/wizard/runWizard.tsx
28252
29061
  function RunWizard(_props) {
28253
- const [wiz] = useState5(
29062
+ const [wiz] = useState6(
28254
29063
  () => createWizardState({
28255
29064
  providers: PROVIDERS,
28256
29065
  defaultModelFor: (id) => getModelForProvider(id)
28257
29066
  })
28258
29067
  );
28259
- const [, force] = useState5(0);
28260
- useEffect4(() => {
29068
+ const [, force] = useState6(0);
29069
+ useEffect5(() => {
28261
29070
  if (typeof wiz.subscribe === "function") {
28262
29071
  const sub = wiz.subscribe(() => force((n) => n + 1));
28263
29072
  return () => sub();
28264
29073
  }
28265
29074
  return void 0;
28266
29075
  }, [wiz]);
28267
- useInput((input, key) => {
29076
+ useInput2((input, key) => {
28268
29077
  if (wiz.state.committed) return;
28269
29078
  const s = wiz.state;
28270
29079
  if (key.escape) {
@@ -28321,20 +29130,20 @@ function RunWizard(_props) {
28321
29130
  }
28322
29131
  });
28323
29132
  if (wiz.state.committed) {
28324
- return /* @__PURE__ */ React8.createElement(PostCommitBridge, null);
29133
+ return /* @__PURE__ */ React9.createElement(PostCommitBridge, null);
28325
29134
  }
28326
- return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React8.createElement(Wizard, { state: wiz, providers: PROVIDERS }));
29135
+ return /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React9.createElement(Wizard, { state: wiz, providers: PROVIDERS }));
28327
29136
  }
28328
29137
  function PostCommitBridge() {
28329
- const [showApp, setShowApp] = useState5(false);
28330
- useEffect4(() => {
29138
+ const [showApp, setShowApp] = useState6(false);
29139
+ useEffect5(() => {
28331
29140
  const t = setTimeout(() => setShowApp(true), 1200);
28332
29141
  return () => clearTimeout(t);
28333
29142
  }, []);
28334
29143
  if (showApp) {
28335
- return /* @__PURE__ */ React8.createElement(App, null);
29144
+ return /* @__PURE__ */ React9.createElement(App, null);
28336
29145
  }
28337
- return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React8.createElement(Box8, { borderStyle: "round", borderColor: "green", paddingX: 2, paddingY: 1, flexDirection: "column" }, /* @__PURE__ */ React8.createElement(Text8, { color: "green", bold: true }, "\u2713 Setup complete! Launching zelari-code\u2026"), /* @__PURE__ */ React8.createElement(Text8, { color: "gray" }, "Press Ctrl+C any time to exit.")));
29146
+ return /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React9.createElement(Box9, { borderStyle: "round", borderColor: "green", paddingX: 2, paddingY: 1, flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text9, { color: "green", bold: true }, "\u2713 Setup complete! Launching zelari-code\u2026"), /* @__PURE__ */ React9.createElement(Text9, { color: "gray" }, "Press Ctrl+C any time to exit.")));
28338
29147
  }
28339
29148
 
28340
29149
  // src/cli/headless.ts
@@ -28546,8 +29355,8 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
28546
29355
 
28547
29356
  // src/cli/skillsMd.ts
28548
29357
  init_skills2();
28549
- import { existsSync as existsSync17, readdirSync as readdirSync4, readFileSync as readFileSync12 } from "node:fs";
28550
- import { join as join7 } from "node:path";
29358
+ import { existsSync as existsSync18, readdirSync as readdirSync4, readFileSync as readFileSync13 } from "node:fs";
29359
+ import { join as join8 } from "node:path";
28551
29360
  import { homedir as homedir4 } from "node:os";
28552
29361
  var CODING_CATEGORIES = /* @__PURE__ */ new Set([
28553
29362
  "plan",
@@ -28563,10 +29372,10 @@ var CODING_CATEGORIES = /* @__PURE__ */ new Set([
28563
29372
  ]);
28564
29373
  function skillMdSearchDirs(projectRoot = process.cwd()) {
28565
29374
  return [
28566
- join7(projectRoot, ".zelari", "skills"),
28567
- join7(projectRoot, ".claude", "skills"),
28568
- join7(projectRoot, ".opencode", "skills"),
28569
- join7(homedir4(), ".zelari-code", "skills")
29375
+ join8(projectRoot, ".zelari", "skills"),
29376
+ join8(projectRoot, ".claude", "skills"),
29377
+ join8(projectRoot, ".opencode", "skills"),
29378
+ join8(homedir4(), ".zelari-code", "skills")
28570
29379
  ];
28571
29380
  }
28572
29381
  function parseSkillMd(content, sourcePath) {
@@ -28624,7 +29433,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
28624
29433
  const summary = { loaded: [], skipped: [] };
28625
29434
  const seen = new Set(options.existingIds ?? []);
28626
29435
  for (const dir of skillMdSearchDirs(projectRoot)) {
28627
- if (!existsSync17(dir)) continue;
29436
+ if (!existsSync18(dir)) continue;
28628
29437
  let entries;
28629
29438
  try {
28630
29439
  entries = readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -28632,10 +29441,10 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
28632
29441
  continue;
28633
29442
  }
28634
29443
  for (const entry of entries) {
28635
- const skillPath = join7(dir, entry, "SKILL.md");
28636
- if (!existsSync17(skillPath)) continue;
29444
+ const skillPath = join8(dir, entry, "SKILL.md");
29445
+ if (!existsSync18(skillPath)) continue;
28637
29446
  try {
28638
- const parsed = parseSkillMd(readFileSync12(skillPath, "utf8"), skillPath);
29447
+ const parsed = parseSkillMd(readFileSync13(skillPath, "utf8"), skillPath);
28639
29448
  if (!parsed) {
28640
29449
  summary.skipped.push({ path: skillPath, reason: "missing/invalid frontmatter (name, description) or empty body" });
28641
29450
  continue;
@@ -28657,7 +29466,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
28657
29466
 
28658
29467
  // src/cli/main.ts
28659
29468
  init_skills2();
28660
- var VERSION2 = "0.7.5";
29469
+ var VERSION = "0.7.8";
28661
29470
  async function backgroundUpdateCheck() {
28662
29471
  if (process.env.ANATHEMA_DEV === "1") return;
28663
29472
  await new Promise((resolve) => setTimeout(resolve, 3e3));
@@ -28687,7 +29496,7 @@ async function shutdown() {
28687
29496
  function pickRootComponent() {
28688
29497
  const argv = process.argv.slice(2);
28689
29498
  if (argv.includes("--version") || argv.includes("-v")) {
28690
- console.log(`zelari-code v${VERSION2}`);
29499
+ console.log(`zelari-code v${VERSION}`);
28691
29500
  process.exit(0);
28692
29501
  }
28693
29502
  if (argv.includes("--help") || argv.includes("-h")) {
@@ -28713,9 +29522,12 @@ function pickRootComponent() {
28713
29522
  });
28714
29523
  if (decision.shouldRun) {
28715
29524
  console.error(`[zelari-code] starting wizard: ${decision.reason}`);
28716
- return { kind: "wizard", element: React9.createElement(RunWizard) };
29525
+ return { kind: "wizard", element: React10.createElement(RunWizard) };
28717
29526
  }
28718
- return { kind: "app", element: React9.createElement(App) };
29527
+ return {
29528
+ kind: "app",
29529
+ element: React10.createElement(SplashGate, { version: VERSION }, React10.createElement(App))
29530
+ };
28719
29531
  }
28720
29532
  function loadUserSkills() {
28721
29533
  try {
@@ -28758,6 +29570,6 @@ function main() {
28758
29570
  }
28759
29571
  main();
28760
29572
  export {
28761
- VERSION2 as VERSION
29573
+ VERSION
28762
29574
  };
28763
29575
  //# sourceMappingURL=main.bundled.js.map