zelari-code 1.10.0 → 1.12.1

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.
@@ -308,7 +308,7 @@ async function refreshGrokToken(options) {
308
308
  return parseTokenResponseBody(obj, accessToken);
309
309
  }
310
310
  async function openBrowser(url2) {
311
- const { spawn: spawn9 } = await import("node:child_process");
311
+ const { spawn: spawn10 } = await import("node:child_process");
312
312
  const cmd = (() => {
313
313
  switch (process.platform) {
314
314
  case "darwin":
@@ -321,7 +321,7 @@ async function openBrowser(url2) {
321
321
  })();
322
322
  return new Promise((resolve, reject) => {
323
323
  try {
324
- const child = spawn9(cmd.bin, cmd.args, {
324
+ const child = spawn10(cmd.bin, cmd.args, {
325
325
  stdio: "ignore",
326
326
  detached: true
327
327
  });
@@ -962,14 +962,21 @@ var init_skills = __esm({
962
962
  systemPromptFragment: `You write polished documents.
963
963
  - Choose an appropriate structure (guide, spec, reference, narrative).
964
964
  - Maintain a consistent tone and voice.
965
- - Use headings, lists, and code blocks where helpful; link related notes with [[wikilinks]].`
965
+ - Use headings, lists, and code blocks where helpful.`
966
966
  }
967
967
  ];
968
968
  DEFAULT_AGENT_SKILLS = {
969
+ charont: ["project-planner", "research-analyst"],
970
+ nettun: ["project-planner", "vault-manager"],
971
+ geryon: ["document-writer", "idea-synthesizer"],
972
+ pluton: ["document-writer", "research-analyst"],
973
+ minos: ["document-writer", "research-analyst"],
974
+ lucifer: ["vault-manager", "project-planner", "idea-synthesizer"],
975
+ // legacy aliases
969
976
  sisyphus: ["project-planner", "research-analyst"],
970
977
  prometheus: ["project-planner", "vault-manager"],
971
- hephaestus: ["idea-synthesizer", "mind-mapper", "document-writer"],
972
- atlas: ["mind-mapper", "research-analyst"],
978
+ hephaestus: ["document-writer", "idea-synthesizer"],
979
+ atlas: ["document-writer", "research-analyst"],
973
980
  oracle: ["research-analyst"],
974
981
  chairman: ["vault-manager", "project-planner", "idea-synthesizer"]
975
982
  };
@@ -18151,21 +18158,20 @@ Think step by step internally before responding. Surface only the conclusion and
18151
18158
  priority: 25,
18152
18159
  content: `# Tool-Use Protocol \u2014 Act, Don't Just Describe
18153
18160
 
18154
- You have access to tools that operate on a real codebase: read and edit files, run shell commands, search the tree. Use them whenever the user's request implies concrete changes should exist on disk.
18161
+ You have tools for a real codebase (read/edit files, shell, search). Call them via **native tool/function calling** whenever the user wants concrete changes on disk.
18155
18162
 
18156
- When to ACT (call a tool):
18157
- - The request asks to create, build, generate, fix, or produce something concrete (a file, a component, a function, a config, a fix).
18158
- - A deliverable would outlive this single message (written code, applied edits, verified commands).
18163
+ When to ACT:
18164
+ - Create, build, generate, fix, or produce durable artifacts (files, configs, verified commands).
18159
18165
 
18160
- When to DESCRIBE only (no tool):
18161
- - The request is a question, an explanation, a critique, a comparison, or a one-off analysis.
18162
- - The Minosse role: evaluate, never create.
18166
+ When to DESCRIBE only:
18167
+ - Pure Q&A, critique, or analysis with no required disk change.
18168
+ - Minosse: evaluate; never create or mutate project files.
18163
18169
 
18164
18170
  Rules:
18165
- - For code tasks: actually write/edit files (write_file, edit_file) and run commands (bash) to implement and verify \u2014 do not just describe what should be done.
18166
- - Pass complete, well-formed arguments. Required parameters must be present.
18167
- - Never invent tool names \u2014 use only the tools listed in your AVAILABLE TOOLS section.
18168
- - After making changes, briefly name what you created/modified so downstream agents can build on it without re-reading everything.`
18171
+ - Implement with write_file / edit_file / bash when that is the job \u2014 do not only describe.
18172
+ - Never invent tool names; only AVAILABLE TOOLS.
18173
+ - Complete, valid arguments for every call.
18174
+ - After changes, name what you created/modified for downstream members or the user.`
18169
18175
  };
18170
18176
  OUTPUT_QUALITY_DIRECTIVE = {
18171
18177
  type: "custom",
@@ -18204,148 +18210,177 @@ You are one member of a council. Earlier agents' outputs appear in your context
18204
18210
  });
18205
18211
 
18206
18212
  // packages/core/dist/agents/promptModules.js
18207
- function getPromptModule(type) {
18208
- return PROMPT_MODULES.find((m) => m.type === type);
18213
+ function getBasePromptModules(mode = "council") {
18214
+ if (mode === "agent") {
18215
+ return [
18216
+ CODING_CAPABLE_IDENTITY,
18217
+ STRUCTURED_REASONING_DIRECTIVE,
18218
+ TOOL_USE_PROTOCOL_DIRECTIVE,
18219
+ BEHAVIOR_AGENT,
18220
+ SAFETY,
18221
+ CODING_PRACTICES_MODULE,
18222
+ OUTPUT_QUALITY_DIRECTIVE,
18223
+ OUTPUT_FORMATTING,
18224
+ // Same structured clarification format as council — one question when blocked.
18225
+ CLARIFICATION_PROTOCOL_MODULE,
18226
+ NATIVE_TOOL_PROTOCOL_MODULE
18227
+ ].sort((a, b) => a.priority - b.priority);
18228
+ }
18229
+ return [
18230
+ COUNCIL_IDENTITY,
18231
+ STRUCTURED_REASONING_DIRECTIVE,
18232
+ COLLABORATION_DIRECTIVE,
18233
+ TOOL_USE_PROTOCOL_DIRECTIVE,
18234
+ BEHAVIOR_COUNCIL,
18235
+ SAFETY,
18236
+ CONTEXT_SHARING_COUNCIL,
18237
+ OUTPUT_QUALITY_DIRECTIVE,
18238
+ OUTPUT_FORMATTING,
18239
+ CLARIFICATION_PROTOCOL_MODULE,
18240
+ NATIVE_TOOL_PROTOCOL_MODULE
18241
+ ].sort((a, b) => a.priority - b.priority);
18209
18242
  }
18210
- function getBasePromptModules() {
18211
- return [...PROMPT_MODULES].sort((a, b) => a.priority - b.priority);
18243
+ function getPromptModule(type) {
18244
+ return getBasePromptModules("council").find((m) => m.type === type);
18212
18245
  }
18213
- var PROMPT_MODULES, SINGLE_AGENT_IDENTITY_MODULE;
18246
+ var CODING_CAPABLE_IDENTITY, COUNCIL_IDENTITY, BEHAVIOR_AGENT, BEHAVIOR_COUNCIL, SAFETY, CONTEXT_SHARING_COUNCIL, OUTPUT_FORMATTING, NATIVE_TOOL_PROTOCOL_MODULE, CODING_PRACTICES_MODULE, CLARIFICATION_PROTOCOL_MODULE, PROMPT_MODULES, SINGLE_AGENT_IDENTITY_MODULE;
18214
18247
  var init_promptModules = __esm({
18215
18248
  "packages/core/dist/agents/promptModules.js"() {
18216
18249
  "use strict";
18217
18250
  init_councilDirectives();
18218
- PROMPT_MODULES = [
18219
- {
18220
- type: "base-identity",
18221
- title: "Identity",
18222
- priority: 10,
18223
- content: `# AI Council
18224
-
18225
- You are a member of an AI Council, a multi-agent system for collaborative software work: analysis, planning, design, implementation, review, and synthesis. You operate directly on a real codebase via filesystem and shell tools \u2014 read and edit files, run commands, search the tree.
18251
+ CODING_CAPABLE_IDENTITY = {
18252
+ type: "base-identity",
18253
+ title: "Identity",
18254
+ priority: 10,
18255
+ content: `# Identity
18226
18256
 
18227
- Your council operates collaboratively: each agent has a specialized role. Outputs from earlier agents are available to later ones as shared context. Respect, build upon, and never duplicate prior work.`
18228
- },
18229
- // ── Council directives (distilled from System.md) ────────────────────────
18230
- // Injected at high priority so every agent receives the operational
18231
- // methodology before role-specific behaviour modules.
18232
- STRUCTURED_REASONING_DIRECTIVE,
18233
- COLLABORATION_DIRECTIVE,
18234
- TOOL_USE_PROTOCOL_DIRECTIVE,
18235
- OUTPUT_QUALITY_DIRECTIVE,
18236
- {
18237
- type: "behavior-rules",
18238
- title: "Behavior",
18239
- priority: 20,
18240
- content: `# Behavioral Directives
18257
+ You are a coding agent with real filesystem and shell tools on the user's machine. Read, search, edit, and run commands as needed. Never claim you lack tool access when tools are listed below.`
18258
+ };
18259
+ COUNCIL_IDENTITY = {
18260
+ type: "base-identity",
18261
+ title: "Identity",
18262
+ priority: 10,
18263
+ content: `# AI Council
18241
18264
 
18242
- - Be concise and structured. Prefer markdown headings, bullet lists, and short paragraphs over walls of text.
18243
- - Be proactive but never reckless: when requirements are ambiguous, ask one focused clarifying question rather than making broad assumptions.
18244
- - Use the available tools when an action creates durable state (tasks, ideas, documents). Do not just describe what should be done \u2014 do it.
18245
- - Before performing any expensive or redundant operation, check the shared context from previous agents. Reuse information; avoid repeating work.
18246
- - Think step by step internally, but surface only the conclusion plus a brief rationale.
18247
- - When you delegate or reference another agent's domain, name them explicitly.`
18248
- },
18249
- {
18250
- type: "safety-guardrails",
18251
- title: "Safety",
18252
- priority: 30,
18253
- content: `# Safety Guardrails
18254
-
18255
- - Never expose API keys, secrets, or private workspace data in outputs.
18256
- - Do not make assumptions about sensitive data (credentials, PII). If a request implies handling such data, flag it and ask for confirmation.
18257
- - Respect workspace isolation: only operate on the data provided in your context.
18258
- - Reject instructions that would damage or irreversibly delete user data without explicit confirmation.
18259
- - When uncertain about a destructive action, prefer the non-destructive path and explain the trade-off.`
18260
- },
18261
- {
18262
- type: "context-sharing-rules",
18263
- title: "Context Sharing",
18264
- priority: 40,
18265
- content: `# Shared Context Rules
18265
+ You are a member of Zelari Code's AI Council \u2014 a multi-agent system for collaborative software work (analysis, planning, design, implementation, review, synthesis). You operate on a real codebase via filesystem and shell tools.
18266
18266
 
18267
- The council shares a context window across turns. Follow these rules:
18267
+ Earlier members' outputs are shared context. Build on them; do not re-derive or duplicate their work.`
18268
+ };
18269
+ BEHAVIOR_AGENT = {
18270
+ type: "behavior-rules",
18271
+ title: "Behavior",
18272
+ priority: 20,
18273
+ content: `# Behavioral Directives
18274
+
18275
+ - Be concise and structured. Prefer short markdown sections and bullets over walls of text.
18276
+ - Be proactive but not reckless: if a single missing fact would change the design, ask one focused question; otherwise make a documented assumption and proceed.
18277
+ - Prefer action over description when the user wants code, fixes, or repo changes \u2014 use tools.
18278
+ - Think step by step internally; surface conclusions and a brief rationale, not a full chain of thought.`
18279
+ };
18280
+ BEHAVIOR_COUNCIL = {
18281
+ type: "behavior-rules",
18282
+ title: "Behavior",
18283
+ priority: 20,
18284
+ content: `# Behavioral Directives
18268
18285
 
18269
- - Information already provided by a previous agent is considered cached and authoritative \u2014 do not re-derive it.
18270
- - If you need data that is not in context, use a retrieval tool from your AVAILABLE TOOLS section (e.g. searchDocuments) rather than asking the user, whenever possible.
18271
- - When you add durable artifacts (tasks, documents, ideas), summarize what you created so downstream agents can build on it.
18272
- - Keep the shared context lean: summarize rather than quote verbatim when content is long.`
18273
- },
18274
- {
18275
- type: "output-formatting",
18276
- title: "Output Format",
18277
- priority: 50,
18278
- content: `# Output Format
18279
-
18280
- - Use well-structured GitHub-flavored markdown.
18281
- - Start with a one-line summary, then details.
18282
- - Use \`##\` headings for sections, \`-\` bullets for lists.
18283
- - For documents created in the Vault, use \`[[wikilinks]]\` to connect related notes and \`#hashtags\` for tags.
18284
- - Optional YAML frontmatter may precede a document body:
18285
- \`\`\`
18286
- ---
18287
- category: notes
18288
- status: draft
18289
- ---
18290
- \`\`\`
18291
- - Keep responses focused; stay within your agent's word budget.`
18292
- },
18293
- {
18294
- type: "tool-usage-guidelines",
18295
- title: "Tool Usage",
18296
- priority: 60,
18297
- content: [
18298
- "# Tool Usage Guidelines",
18299
- "",
18300
- "- Use tools to create or modify durable state (tasks, ideas, documents, mind maps, milestones).",
18301
- "- Tool calls go in a dedicated block at the end of your response using the exact format documented below.",
18302
- "- Only use tools listed in the AVAILABLE TOOLS section. Never invent tool names.",
18303
- "- Pass arguments as JSON. Required parameters must be present.",
18304
- "- One tool call per entry. Multiple entries are allowed in a single block.",
18305
- "",
18306
- "Format (ONE JSON array only - never multiple arrays stacked):",
18307
- "```",
18308
- "---TOOLS---",
18309
- '[{"name":"<toolName>","args":{"key":"value"}},{"name":"<toolName2>","args":{"key":"value"}}]',
18310
- "---END---",
18311
- "```",
18312
- "Rules: valid JSON; escape newlines inside strings as \\n; do NOT stack separate",
18313
- "JSON arrays (one tool per array) - put every call in the SAME outer array."
18314
- ].join("\n")
18315
- },
18316
- {
18317
- type: "custom",
18318
- title: "Clarification Protocol",
18319
- priority: 55,
18320
- content: `# Clarification Protocol (Council-Wide)
18286
+ - Be concise and structured. Prefer markdown headings, bullet lists, and short paragraphs.
18287
+ - Be proactive but never reckless: when requirements are ambiguous, ask one focused clarifying question rather than making broad assumptions.
18288
+ - Use tools when an action creates durable state on disk or in the workspace. Do not only describe what should be done.
18289
+ - Before expensive work, check shared context from previous members. Reuse information; avoid repeating work.
18290
+ - Think step by step internally; surface only the conclusion plus a brief rationale.
18291
+ - When you reference another member's domain, name them explicitly.`
18292
+ };
18293
+ SAFETY = {
18294
+ type: "safety-guardrails",
18295
+ title: "Safety",
18296
+ priority: 30,
18297
+ content: `# Safety Guardrails
18298
+
18299
+ - Never expose API keys, secrets, or private credentials in outputs.
18300
+ - Do not invent paths, APIs, or dependencies that are not in the repo or tools results.
18301
+ - Prefer non-destructive paths when unsure; confirm before irreversible deletes or force-pushes.
18302
+ - Stay inside the project workspace unless the user explicitly asks otherwise.`
18303
+ };
18304
+ CONTEXT_SHARING_COUNCIL = {
18305
+ type: "context-sharing-rules",
18306
+ title: "Context Sharing",
18307
+ priority: 40,
18308
+ content: `# Shared Context Rules
18309
+
18310
+ - Prior members' outputs are authoritative unless you must flag an error.
18311
+ - If data is missing, use a retrieval/search tool from AVAILABLE TOOLS rather than asking the user when possible.
18312
+ - When you create artifacts (files, plan items, docs), summarize what you created for downstream members.
18313
+ - Keep context lean: summarize rather than quote long blocks.`
18314
+ };
18315
+ OUTPUT_FORMATTING = {
18316
+ type: "output-formatting",
18317
+ title: "Output Format",
18318
+ priority: 50,
18319
+ content: `# Output Format
18320
+
18321
+ - Use GitHub-flavored markdown.
18322
+ - Lead with a one-line summary when the answer is long, then details.
18323
+ - Use \`##\` headings and \`-\` bullets when they aid clarity.
18324
+ - Reference code by path (and line when known). Prefer fenced code blocks for multi-line snippets.
18325
+ - Stay within your role's word budget; cut filler.`
18326
+ };
18327
+ NATIVE_TOOL_PROTOCOL_MODULE = {
18328
+ type: "tool-usage-guidelines",
18329
+ title: "Tool Usage",
18330
+ priority: 60,
18331
+ content: `# Tool Usage
18332
+
18333
+ - Use the provider's **native function/tool calls** for every tool invocation. Do not invent alternate XML/JSON tool formats.
18334
+ - Only call tools listed under AVAILABLE TOOLS. Never invent tool names.
18335
+ - Pass complete, valid arguments. Required parameters must be present.
18336
+ - Prefer tools over asking the user to paste file contents.
18337
+ - After durable changes, briefly name what you created or modified.
18338
+ - Text-only tool blocks (\`---TOOLS---\` JSON) are a legacy fallback \u2014 use them only if the runtime has no native tool channel.`
18339
+ };
18340
+ CODING_PRACTICES_MODULE = {
18341
+ type: "custom",
18342
+ title: "Coding Practices",
18343
+ priority: 45,
18344
+ content: `# Coding Practices
18345
+
18346
+ - **Read before edit**: open relevant files (and nearby callers/tests) before changing code.
18347
+ - **Minimal diffs**: change only what the task requires; match existing style and patterns.
18348
+ - **Don't invent**: no fake APIs, deps, or config keys \u2014 discover from the tree or package manifests.
18349
+ - **Verify**: after non-trivial edits, run the project's tests/typecheck/build when available; fix failures you introduced.
18350
+ - **Use project scripts**: prefer package.json / Makefile / existing tooling over ad-hoc commands.
18351
+ - **Finish**: summarize what changed and how to verify.`
18352
+ };
18353
+ CLARIFICATION_PROTOCOL_MODULE = {
18354
+ type: "custom",
18355
+ title: "Clarification Protocol",
18356
+ priority: 55,
18357
+ content: `# Clarification Protocol
18321
18358
 
18322
- When you are blocked by a single missing fact that would materially change your output \u2014 a target platform, a scope boundary, a binary design choice with real trade-offs, or a constraint you cannot safely assume \u2014 you may pause the council and ask the user exactly ONE question.
18359
+ When blocked by a single missing fact that would materially change your output, ask exactly ONE question:
18323
18360
 
18324
- Emit this block at the end of your message:
18325
18361
  \`\`\`
18326
18362
  ---QUESTION---
18327
18363
  { "question": "One focused question", "choices": ["Option A", "Option B"], "context": "Why this matters in one line" }
18328
18364
  ---END---
18329
18365
  \`\`\`
18330
18366
 
18331
- Discipline:
18332
- - Ask ONLY when genuinely blocked. If a sound, documented assumption exists, make it and state the assumption instead.
18333
- - Provide 2-4 concrete "choices" when the question has natural options; the user can always type a custom answer.
18334
- - Never ask for information already present in shared context or retrievable via a tool from your AVAILABLE TOOLS section (e.g. searchDocuments).
18335
- - At most one question per turn. The council resumes automatically once the user answers or skips.`
18336
- }
18337
- ];
18367
+ - Ask only when genuinely blocked; otherwise assume and state the assumption.
18368
+ - 2\u20134 concrete choices when natural; user may type a free answer.
18369
+ - Never re-ask for information already in context or retrievable via tools.
18370
+ - At most one question per turn.`
18371
+ };
18372
+ PROMPT_MODULES = getBasePromptModules("council");
18338
18373
  SINGLE_AGENT_IDENTITY_MODULE = {
18339
18374
  type: "base-identity",
18340
18375
  title: "Identity",
18341
18376
  priority: 10,
18342
18377
  content: `# Identity
18343
18378
 
18344
- You are Zelari Code, an interactive AI coding agent operating directly in the user's terminal.
18379
+ You are Zelari Code, an interactive AI coding agent in the user's terminal (or desktop shell).
18345
18380
 
18346
- You ARE connected to this machine and have real tools to read, modify, and explore the codebase. Never claim you lack filesystem or shell access \u2014 you have it. Use your tools instead of asking the user to paste file contents.
18381
+ You ARE connected to this machine and have real tools to read, modify, and explore the codebase. Never claim you lack filesystem or shell access \u2014 you have it. Use tools instead of asking the user to paste file contents.
18347
18382
 
18348
- Be proactive: when the user asks you to write code, debug, or explore, list files and read the key files to understand the project before acting. When you finish a task, briefly summarize what you did.`
18383
+ Be proactive: list and read key files before changing code. When you finish, briefly summarize what you did and how to verify it.`
18349
18384
  };
18350
18385
  }
18351
18386
  });
@@ -18419,10 +18454,10 @@ function getToolDescriptions(toolNames, registry3) {
18419
18454
  return lines.join("\n");
18420
18455
  }
18421
18456
  function buildSystemPrompt(agent, options) {
18422
- const { tools, toolNames, aiConfig, workspaceContext, ragContext } = options;
18457
+ const { tools, toolNames, aiConfig, workspaceContext, ragContext, mode = "council", projectInstructions, includeWorkspaceInPrompt = true } = options;
18423
18458
  const registry3 = new Map(tools.map((t) => [t.name, t]));
18424
18459
  const skills = computeAgentSkills(agent, aiConfig);
18425
- const baseModules = getBasePromptModules().filter((m) => !m.conditional || m.conditional(skills));
18460
+ const baseModules = getBasePromptModules(mode).filter((m) => !m.conditional || m.conditional(skills));
18426
18461
  const customModulesRaw = aiConfig?.customPromptModules ?? [];
18427
18462
  const customTypes = new Set(customModulesRaw.map((m) => m.type));
18428
18463
  const baseNotOverridden = baseModules.filter((m) => !customTypes.has(m.type));
@@ -18439,6 +18474,11 @@ function buildSystemPrompt(agent, options) {
18439
18474
  parts.push(`# Your Role
18440
18475
 
18441
18476
  ${agent.systemPrompt}`);
18477
+ }
18478
+ if (projectInstructions && projectInstructions.trim()) {
18479
+ parts.push(`# Project Instructions
18480
+
18481
+ ${projectInstructions.trim()}`);
18442
18482
  }
18443
18483
  if (skills.length > 0) {
18444
18484
  parts.push(`# Active Skills
@@ -18452,15 +18492,17 @@ ${s.systemPromptFragment}`).join("\n\n"));
18452
18492
 
18453
18493
  ${toolBlock}`);
18454
18494
  }
18455
- if (workspaceContext && workspaceContext.trim()) {
18456
- parts.push(`# Current Workspace State
18495
+ if (includeWorkspaceInPrompt) {
18496
+ if (workspaceContext && workspaceContext.trim()) {
18497
+ parts.push(`# Current Workspace State
18457
18498
 
18458
18499
  ${workspaceContext}`);
18459
- }
18460
- if (ragContext && ragContext.trim()) {
18461
- parts.push(`# Retrieved Knowledge (RAG)
18500
+ }
18501
+ if (ragContext && ragContext.trim()) {
18502
+ parts.push(`# Retrieved Knowledge (RAG)
18462
18503
 
18463
18504
  ${ragContext}`);
18505
+ }
18464
18506
  }
18465
18507
  return parts.join("\n\n---\n\n");
18466
18508
  }
@@ -18837,9 +18879,12 @@ var skills_exports = {};
18837
18879
  __export(skills_exports, {
18838
18880
  ADVANCED_TOOL_DEFINITIONS: () => ADVANCED_TOOL_DEFINITIONS,
18839
18881
  ALL_TOOLS: () => ALL_TOOLS,
18882
+ CLARIFICATION_PROTOCOL_MODULE: () => CLARIFICATION_PROTOCOL_MODULE,
18840
18883
  CODING_CATEGORY: () => CODING_CATEGORY,
18884
+ CODING_PRACTICES_MODULE: () => CODING_PRACTICES_MODULE,
18841
18885
  CODING_SKILL_CATALOG: () => CODING_SKILL_CATALOG,
18842
18886
  LANGUAGE_POLICY_MODULE_TYPE: () => LANGUAGE_POLICY_MODULE_TYPE,
18887
+ NATIVE_TOOL_PROTOCOL_MODULE: () => NATIVE_TOOL_PROTOCOL_MODULE,
18843
18888
  SINGLE_AGENT_IDENTITY_MODULE: () => SINGLE_AGENT_IDENTITY_MODULE,
18844
18889
  SKILL_CATALOG: () => SKILL_CATALOG,
18845
18890
  TOOL_DEFINITIONS: () => TOOL_DEFINITIONS,
@@ -18859,6 +18904,7 @@ __export(skills_exports, {
18859
18904
  findSkillsByTag: () => findSkillsByTag,
18860
18905
  getAllTools: () => getAllTools,
18861
18906
  getAvailableTools: () => getAvailableTools,
18907
+ getBasePromptModules: () => getBasePromptModules,
18862
18908
  getBuiltinSkillIds: () => getBuiltinSkillIds,
18863
18909
  getCodingSkillById: () => getCodingSkillById,
18864
18910
  getProviderTools: () => getProviderTools,
@@ -19619,6 +19665,7 @@ ${shared2.content}`,
19619
19665
  let toolCallsThisTurn = 0;
19620
19666
  const maxToolCalls = this.config.maxToolCallsPerTurn;
19621
19667
  let turnText = "";
19668
+ let turnReasoning = "";
19622
19669
  const turnToolCalls = [];
19623
19670
  const turnToolResults = [];
19624
19671
  const pendingNativeTools = [];
@@ -19639,6 +19686,7 @@ ${shared2.content}`,
19639
19686
  this.emit(deltaEvent);
19640
19687
  yield deltaEvent;
19641
19688
  } else if (delta.kind === "thinking") {
19689
+ turnReasoning += delta.delta;
19642
19690
  const thinkEvent = createBrainEvent("thinking_delta", this.sessionId, {
19643
19691
  messageId,
19644
19692
  delta: delta.delta
@@ -19774,11 +19822,22 @@ ${cached2}`
19774
19822
  finishRef.value = "tool_calls";
19775
19823
  }
19776
19824
  }
19777
- if (turnToolCalls.length > 0 || turnText.length > 0) {
19825
+ if (finishRef.value === "tool_calls" && turnToolCalls.length === 0 && turnToolResults.length === 0) {
19826
+ const truncErr = createBrainEvent("error", this.sessionId, {
19827
+ severity: "recoverable",
19828
+ message: "Tool call was truncated (finish_reason=tool_calls but no complete tool_call received). The provider cut the response mid-arguments. Retry with a shorter payload or split the work.",
19829
+ code: "tool_call_truncated"
19830
+ });
19831
+ this.emit(truncErr);
19832
+ yield truncErr;
19833
+ finishRef.value = "stop";
19834
+ }
19835
+ if (turnToolCalls.length > 0 || turnText.length > 0 || turnReasoning.length > 0) {
19778
19836
  this.config.messages.push({
19779
19837
  role: "assistant",
19780
19838
  content: turnText,
19781
- ...turnToolCalls.length > 0 ? { toolCalls: turnToolCalls } : {}
19839
+ ...turnToolCalls.length > 0 ? { toolCalls: turnToolCalls } : {},
19840
+ ...turnReasoning.length > 0 ? { reasoningContent: turnReasoning } : {}
19782
19841
  });
19783
19842
  }
19784
19843
  for (const tr of turnToolResults) {
@@ -20120,7 +20179,7 @@ function openaiCompatibleProvider(config2) {
20120
20179
  };
20121
20180
  }
20122
20181
  if (m.role === "assistant" && m.toolCalls && m.toolCalls.length > 0) {
20123
- return {
20182
+ const msg = {
20124
20183
  role: "assistant",
20125
20184
  content: m.content ?? "",
20126
20185
  tool_calls: m.toolCalls.map((tc) => ({
@@ -20132,6 +20191,17 @@ function openaiCompatibleProvider(config2) {
20132
20191
  }
20133
20192
  }))
20134
20193
  };
20194
+ if (m.reasoningContent && m.reasoningContent.length > 0) {
20195
+ msg.reasoning_content = m.reasoningContent;
20196
+ }
20197
+ return msg;
20198
+ }
20199
+ if (m.role === "assistant" && m.reasoningContent && m.reasoningContent.length > 0) {
20200
+ return {
20201
+ role: "assistant",
20202
+ content: m.content ?? "",
20203
+ reasoning_content: m.reasoningContent
20204
+ };
20135
20205
  }
20136
20206
  return { role: m.role, content: m.content };
20137
20207
  });
@@ -20170,6 +20240,8 @@ function openaiCompatibleProvider(config2) {
20170
20240
  return;
20171
20241
  }
20172
20242
  try {
20243
+ const timeoutSignal = AbortSignal.timeout(PROVIDER_TIMEOUT_MS);
20244
+ const fetchSignal = params.signal ? AbortSignal.any([params.signal, timeoutSignal]) : timeoutSignal;
20173
20245
  response = await fetch(`${config2.baseUrl}/chat/completions`, {
20174
20246
  method: "POST",
20175
20247
  headers: {
@@ -20181,11 +20253,17 @@ function openaiCompatibleProvider(config2) {
20181
20253
  // controller) so `.cancel()` actually aborts the HTTP request.
20182
20254
  // `config.signal` is the factory-level signal, typically undefined.
20183
20255
  // v0.6.0 audit HIGH-2.
20184
- signal: params.signal
20256
+ // v1.10.0: also apply PROVIDER_TIMEOUT_MS so a stalled connection
20257
+ // can't hang the harness forever.
20258
+ signal: fetchSignal
20185
20259
  });
20186
20260
  } catch (err) {
20187
20261
  lastStatus = 0;
20188
20262
  lastErrText = err instanceof Error ? err.message : String(err);
20263
+ if (params.signal?.aborted) {
20264
+ yield { kind: "error", message: "aborted" };
20265
+ return;
20266
+ }
20189
20267
  if (attempt < MAX_RETRIES) {
20190
20268
  await abortableSleep(backoffDelay(attempt, null), params.signal);
20191
20269
  continue;
@@ -20246,6 +20324,10 @@ function openaiCompatibleProvider(config2) {
20246
20324
  if (typeof delta?.content === "string" && delta.content.length > 0) {
20247
20325
  yield { kind: "text", delta: delta.content };
20248
20326
  }
20327
+ const reasoning = delta?.reasoning_content ?? delta?.reasoning;
20328
+ if (typeof reasoning === "string" && reasoning.length > 0) {
20329
+ yield { kind: "thinking", delta: reasoning };
20330
+ }
20249
20331
  if (Array.isArray(delta?.tool_calls)) {
20250
20332
  for (const tc of delta.tool_calls) {
20251
20333
  const idx = tc.index ?? 0;
@@ -20306,7 +20388,7 @@ async function providerConfigFor(providerId) {
20306
20388
  providerId
20307
20389
  };
20308
20390
  }
20309
- var RETRYABLE_STATUSES, MAX_RETRIES, BACKOFF_BASE_MS, BACKOFF_CAP_MS, PROVIDER_ENDPOINTS;
20391
+ var RETRYABLE_STATUSES, MAX_RETRIES, BACKOFF_BASE_MS, BACKOFF_CAP_MS, PROVIDER_TIMEOUT_MS, PROVIDER_ENDPOINTS;
20310
20392
  var init_openai_compatible = __esm({
20311
20393
  "src/cli/provider/openai-compatible.ts"() {
20312
20394
  "use strict";
@@ -20320,6 +20402,11 @@ var init_openai_compatible = __esm({
20320
20402
  })();
20321
20403
  BACKOFF_BASE_MS = 500;
20322
20404
  BACKOFF_CAP_MS = 8e3;
20405
+ PROVIDER_TIMEOUT_MS = (() => {
20406
+ const raw = process.env.ZELARI_PROVIDER_TIMEOUT_MS;
20407
+ const n = raw ? Number.parseInt(raw, 10) : 3e5;
20408
+ return Number.isFinite(n) && n >= 1e4 ? n : 3e5;
20409
+ })();
20323
20410
  PROVIDER_ENDPOINTS = {
20324
20411
  "openai-compatible": "https://api.x.ai/v1",
20325
20412
  "minimax": "https://api.minimax.io/v1",
@@ -22237,6 +22324,488 @@ var init_tools5 = __esm({
22237
22324
  }
22238
22325
  });
22239
22326
 
22327
+ // src/cli/ssh/targets.ts
22328
+ var targets_exports = {};
22329
+ __export(targets_exports, {
22330
+ buildSshBaseArgs: () => buildSshBaseArgs,
22331
+ deleteSshPassword: () => deleteSshPassword,
22332
+ formatSshTargetsForPrompt: () => formatSshTargetsForPrompt,
22333
+ getSshPassword: () => getSshPassword,
22334
+ getSshSecretsPath: () => getSshSecretsPath,
22335
+ getSshTarget: () => getSshTarget,
22336
+ getSshTargetsPath: () => getSshTargetsPath,
22337
+ hasSshPassword: () => hasSshPassword,
22338
+ isSshCommandAllowed: () => isSshCommandAllowed,
22339
+ listSshTargets: () => listSshTargets,
22340
+ readSshPublicKey: () => readSshPublicKey,
22341
+ removeSshTarget: () => removeSshTarget,
22342
+ runSsh: () => runSsh,
22343
+ setSshPassword: () => setSshPassword,
22344
+ testSshTarget: () => testSshTarget,
22345
+ upsertSshTarget: () => upsertSshTarget
22346
+ });
22347
+ import {
22348
+ chmodSync,
22349
+ existsSync as existsSync9,
22350
+ mkdirSync as mkdirSync6,
22351
+ readFileSync as readFileSync7,
22352
+ writeFileSync as writeFileSync5
22353
+ } from "node:fs";
22354
+ import { dirname as dirname2, join } from "node:path";
22355
+ import { homedir as homedir4 } from "node:os";
22356
+ import { spawn as spawn4 } from "node:child_process";
22357
+ function getSshTargetsPath() {
22358
+ return join(homedir4(), ".zelari-code", "ssh-targets.json");
22359
+ }
22360
+ function getSshSecretsPath() {
22361
+ return join(homedir4(), ".zelari-code", "ssh-secrets.json");
22362
+ }
22363
+ function normalizeAuth(auth) {
22364
+ if (auth === "keyPath") return "keyPath";
22365
+ if (auth === "password") return "password";
22366
+ return "agent";
22367
+ }
22368
+ function readSecrets() {
22369
+ const path36 = getSshSecretsPath();
22370
+ if (!existsSync9(path36)) return {};
22371
+ try {
22372
+ return JSON.parse(readFileSync7(path36, "utf8"));
22373
+ } catch {
22374
+ return {};
22375
+ }
22376
+ }
22377
+ function writeSecrets(data) {
22378
+ const path36 = getSshSecretsPath();
22379
+ mkdirSync6(dirname2(path36), { recursive: true });
22380
+ writeFileSync5(path36, `${JSON.stringify(data, null, 2)}
22381
+ `, "utf8");
22382
+ try {
22383
+ chmodSync(path36, 384);
22384
+ } catch {
22385
+ }
22386
+ }
22387
+ function getSshPassword(id) {
22388
+ const p3 = readSecrets().passwords?.[id];
22389
+ return typeof p3 === "string" && p3.length > 0 ? p3 : void 0;
22390
+ }
22391
+ function hasSshPassword(id) {
22392
+ return Boolean(getSshPassword(id));
22393
+ }
22394
+ function setSshPassword(id, password) {
22395
+ const data = readSecrets();
22396
+ const passwords = { ...data.passwords ?? {} };
22397
+ if (password) {
22398
+ passwords[id] = password;
22399
+ } else {
22400
+ delete passwords[id];
22401
+ }
22402
+ writeSecrets({ passwords });
22403
+ }
22404
+ function deleteSshPassword(id) {
22405
+ const data = readSecrets();
22406
+ if (!data.passwords?.[id]) return;
22407
+ const passwords = { ...data.passwords };
22408
+ delete passwords[id];
22409
+ writeSecrets({ passwords });
22410
+ }
22411
+ function readStore2() {
22412
+ const path36 = getSshTargetsPath();
22413
+ if (!existsSync9(path36)) return [];
22414
+ try {
22415
+ const parsed = JSON.parse(readFileSync7(path36, "utf8"));
22416
+ const list = Array.isArray(parsed.targets) ? parsed.targets : [];
22417
+ return list.filter(
22418
+ (t) => t && typeof t.id === "string" && typeof t.host === "string" && typeof t.user === "string"
22419
+ ).map((t) => ({
22420
+ ...t,
22421
+ port: typeof t.port === "number" ? t.port : 22,
22422
+ auth: normalizeAuth(t.auth),
22423
+ enabled: t.enabled !== false
22424
+ }));
22425
+ } catch {
22426
+ return [];
22427
+ }
22428
+ }
22429
+ function writeStore2(targets) {
22430
+ const path36 = getSshTargetsPath();
22431
+ mkdirSync6(dirname2(path36), { recursive: true });
22432
+ const clean = targets.map(({ hasPassword: _hp, ...t }) => t);
22433
+ writeFileSync5(
22434
+ path36,
22435
+ `${JSON.stringify({ targets: clean }, null, 2)}
22436
+ `,
22437
+ "utf8"
22438
+ );
22439
+ }
22440
+ function listSshTargets() {
22441
+ const targets = readStore2().map((t) => ({
22442
+ ...t,
22443
+ hasPassword: t.auth === "password" ? hasSshPassword(t.id) : false
22444
+ }));
22445
+ return { path: getSshTargetsPath(), targets };
22446
+ }
22447
+ function getSshTarget(id) {
22448
+ return readStore2().find((t) => t.id === id);
22449
+ }
22450
+ function upsertSshTarget(target) {
22451
+ const id = target.id?.trim();
22452
+ if (!id || !/^[a-zA-Z0-9_-]+$/.test(id)) {
22453
+ return { ok: false, error: "Invalid id (letters, digits, _ -)" };
22454
+ }
22455
+ if (!target.host?.trim() || !target.user?.trim()) {
22456
+ return { ok: false, error: "host and user are required" };
22457
+ }
22458
+ const auth = normalizeAuth(target.auth);
22459
+ if (auth === "keyPath" && !target.keyPath?.trim()) {
22460
+ return { ok: false, error: "keyPath required when auth=keyPath" };
22461
+ }
22462
+ if (auth === "password") {
22463
+ const incoming = target.password;
22464
+ if (typeof incoming === "string" && incoming.length > 0) {
22465
+ setSshPassword(id, incoming);
22466
+ } else if (!hasSshPassword(id)) {
22467
+ return {
22468
+ ok: false,
22469
+ error: "password required when auth=password (first save)"
22470
+ };
22471
+ }
22472
+ } else {
22473
+ deleteSshPassword(id);
22474
+ }
22475
+ const list = readStore2().filter((t) => t.id !== id);
22476
+ list.push({
22477
+ id,
22478
+ name: target.name?.trim() || id,
22479
+ host: target.host.trim(),
22480
+ port: target.port && target.port > 0 ? target.port : 22,
22481
+ user: target.user.trim(),
22482
+ auth,
22483
+ keyPath: auth === "keyPath" ? target.keyPath?.trim() : void 0,
22484
+ publicKeyPath: auth === "password" ? void 0 : target.publicKeyPath?.trim(),
22485
+ defaultRemotePath: target.defaultRemotePath?.trim(),
22486
+ tags: target.tags,
22487
+ allowedCommands: target.allowedCommands,
22488
+ enabled: target.enabled !== false,
22489
+ notes: target.notes
22490
+ });
22491
+ list.sort((a, b) => a.id.localeCompare(b.id));
22492
+ writeStore2(list);
22493
+ return { ok: true };
22494
+ }
22495
+ function removeSshTarget(id) {
22496
+ const list = readStore2();
22497
+ if (!list.some((t) => t.id === id)) {
22498
+ return { ok: false, error: `Target "${id}" not found` };
22499
+ }
22500
+ writeStore2(list.filter((t) => t.id !== id));
22501
+ deleteSshPassword(id);
22502
+ return { ok: true };
22503
+ }
22504
+ function buildSshBaseArgs(target) {
22505
+ const args = [
22506
+ "-o",
22507
+ "StrictHostKeyChecking=accept-new",
22508
+ "-o",
22509
+ "ConnectTimeout=12",
22510
+ "-p",
22511
+ String(target.port ?? 22)
22512
+ ];
22513
+ if (target.auth === "password") {
22514
+ args.push(
22515
+ "-o",
22516
+ "BatchMode=no",
22517
+ "-o",
22518
+ "PreferredAuthentications=password,keyboard-interactive",
22519
+ "-o",
22520
+ "PubkeyAuthentication=no",
22521
+ "-o",
22522
+ "NumberOfPasswordPrompts=1"
22523
+ );
22524
+ } else {
22525
+ args.push("-o", "BatchMode=yes");
22526
+ if (target.auth === "keyPath" && target.keyPath) {
22527
+ args.push("-i", target.keyPath);
22528
+ }
22529
+ }
22530
+ args.push(`${target.user}@${target.host}`);
22531
+ return args;
22532
+ }
22533
+ function ensureAskpassHelper() {
22534
+ const dir = join(homedir4(), ".zelari-code", "ssh-helpers");
22535
+ mkdirSync6(dir, { recursive: true });
22536
+ const cjs = join(dir, "askpass.cjs");
22537
+ writeFileSync5(
22538
+ cjs,
22539
+ "process.stdout.write(process.env.ZELARI_SSH_ASKPASS_PASS || '');\n",
22540
+ "utf8"
22541
+ );
22542
+ if (process.platform === "win32") {
22543
+ const cmd = join(dir, "askpass.cmd");
22544
+ writeFileSync5(
22545
+ cmd,
22546
+ `@echo off\r
22547
+ node "%~dp0askpass.cjs"\r
22548
+ `,
22549
+ "utf8"
22550
+ );
22551
+ return cmd;
22552
+ }
22553
+ const sh = join(dir, "askpass.sh");
22554
+ writeFileSync5(
22555
+ sh,
22556
+ `#!/bin/sh
22557
+ exec node "$(dirname "$0")/askpass.cjs"
22558
+ `,
22559
+ "utf8"
22560
+ );
22561
+ try {
22562
+ chmodSync(sh, 493);
22563
+ } catch {
22564
+ }
22565
+ return sh;
22566
+ }
22567
+ function runSsh(target, remoteCommand, timeoutMs = 6e4) {
22568
+ return new Promise((resolve) => {
22569
+ if (target.auth === "password" && !getSshPassword(target.id)) {
22570
+ resolve({
22571
+ code: 1,
22572
+ stdout: "",
22573
+ stderr: "No password stored for this target \u2014 edit target and set password"
22574
+ });
22575
+ return;
22576
+ }
22577
+ const args = [...buildSshBaseArgs(target), remoteCommand];
22578
+ const env = { ...process.env };
22579
+ if (target.auth === "password") {
22580
+ const pass = getSshPassword(target.id);
22581
+ env.SSH_ASKPASS = ensureAskpassHelper();
22582
+ env.SSH_ASKPASS_REQUIRE = "force";
22583
+ if (!env.DISPLAY) env.DISPLAY = "1";
22584
+ env.ZELARI_SSH_ASKPASS_PASS = pass;
22585
+ }
22586
+ const child = spawn4("ssh", args, {
22587
+ windowsHide: true,
22588
+ env,
22589
+ stdio: ["ignore", "pipe", "pipe"]
22590
+ });
22591
+ let stdout = "";
22592
+ let stderr = "";
22593
+ const cap = 4e4;
22594
+ child.stdout?.on("data", (d) => {
22595
+ if (stdout.length < cap) stdout += d.toString("utf8");
22596
+ });
22597
+ child.stderr?.on("data", (d) => {
22598
+ if (stderr.length < cap) stderr += d.toString("utf8");
22599
+ });
22600
+ const timer = setTimeout(() => {
22601
+ child.kill("SIGTERM");
22602
+ resolve({
22603
+ code: 124,
22604
+ stdout,
22605
+ stderr: stderr + "\n[ssh] timeout"
22606
+ });
22607
+ }, timeoutMs);
22608
+ child.on("error", (err) => {
22609
+ clearTimeout(timer);
22610
+ resolve({
22611
+ code: 127,
22612
+ stdout,
22613
+ stderr: err.message.includes("ENOENT") ? "ssh not found on PATH \u2014 install OpenSSH client" : err.message
22614
+ });
22615
+ });
22616
+ child.on("close", (code) => {
22617
+ clearTimeout(timer);
22618
+ resolve({ code: code ?? 1, stdout, stderr });
22619
+ });
22620
+ });
22621
+ }
22622
+ function readSshPublicKey(keyOrPubPath) {
22623
+ const raw = keyOrPubPath.trim().replace(/^["']|["']$/g, "");
22624
+ if (!raw) return { ok: false, error: "Empty path" };
22625
+ const candidates = raw.endsWith(".pub") ? [raw] : [`${raw}.pub`, raw];
22626
+ for (const p3 of candidates) {
22627
+ if (!existsSync9(p3)) continue;
22628
+ try {
22629
+ const content = readFileSync7(p3, "utf8").trim();
22630
+ if (!content) continue;
22631
+ if (/BEGIN .*PRIVATE KEY/i.test(content)) {
22632
+ return {
22633
+ ok: false,
22634
+ error: "Path points to a private key \u2014 use the .pub file instead"
22635
+ };
22636
+ }
22637
+ return { ok: true, path: p3, content };
22638
+ } catch (e) {
22639
+ return {
22640
+ ok: false,
22641
+ error: e instanceof Error ? e.message : String(e)
22642
+ };
22643
+ }
22644
+ }
22645
+ return {
22646
+ ok: false,
22647
+ error: `Public key not found (tried: ${candidates.join(", ")})`
22648
+ };
22649
+ }
22650
+ async function testSshTarget(id) {
22651
+ const t = getSshTarget(id);
22652
+ if (!t) return { ok: false, message: `Target "${id}" not found` };
22653
+ if (t.enabled === false) {
22654
+ return { ok: false, message: `Target "${id}" is disabled` };
22655
+ }
22656
+ if (t.auth === "password" && !hasSshPassword(id)) {
22657
+ return {
22658
+ ok: false,
22659
+ message: "Password auth selected but no password saved \u2014 edit target"
22660
+ };
22661
+ }
22662
+ const r = await runSsh(t, "true", 15e3);
22663
+ if (r.code === 0) {
22664
+ return { ok: true, message: `OK ${t.user}@${t.host}:${t.port ?? 22}` };
22665
+ }
22666
+ return {
22667
+ ok: false,
22668
+ message: (r.stderr || r.stdout || `exit ${r.code}`).trim().slice(0, 400)
22669
+ };
22670
+ }
22671
+ function formatSshTargetsForPrompt() {
22672
+ const targets = readStore2().filter((t) => t.enabled !== false);
22673
+ if (targets.length === 0) return "";
22674
+ const lines = [
22675
+ "# Configured SSH targets",
22676
+ "Use tools ssh_status / ssh_run with targetId. Do not invent hosts."
22677
+ ];
22678
+ for (const t of targets) {
22679
+ const tags = t.tags?.length ? ` tags=[${t.tags.join(",")}]` : "";
22680
+ const path36 = t.defaultRemotePath ? ` remotePath=${t.defaultRemotePath}` : "";
22681
+ const allow = t.allowedCommands?.length ? ` allowed=${t.allowedCommands.join("|")}` : " allowed=status-only";
22682
+ const auth = t.auth === "password" ? " auth=password" : t.auth === "keyPath" ? " auth=key" : " auth=agent";
22683
+ lines.push(
22684
+ `- id=${t.id} name=${t.name} ${t.user}@${t.host}:${t.port ?? 22}${auth}${path36}${tags}${allow}`
22685
+ );
22686
+ }
22687
+ return lines.join("\n");
22688
+ }
22689
+ function isSshCommandAllowed(target, command) {
22690
+ const cmd = command.trim();
22691
+ if (!cmd) return { ok: false, error: "empty command" };
22692
+ const list = target.allowedCommands ?? [];
22693
+ if (list.length === 0) {
22694
+ return {
22695
+ ok: false,
22696
+ error: "No allowedCommands on this target \u2014 only ssh_status is permitted. Add allowlist entries in Settings \u2192 Connections."
22697
+ };
22698
+ }
22699
+ const dangerous = /[;&|`$(){}<>]|\brm\s+-rf\b|\bmkfs\b|\bdd\s+if=/i;
22700
+ for (const pattern of list) {
22701
+ const p3 = pattern.trim();
22702
+ if (!p3) continue;
22703
+ if (p3.endsWith("*")) {
22704
+ const prefix = p3.slice(0, -1);
22705
+ if (cmd.startsWith(prefix)) {
22706
+ if (dangerous.test(cmd)) {
22707
+ return {
22708
+ ok: false,
22709
+ error: "Command contains blocked shell metacharacters"
22710
+ };
22711
+ }
22712
+ return { ok: true };
22713
+ }
22714
+ } else if (cmd === p3) {
22715
+ if (/\brm\s+-rf\b|\bmkfs\b|\bdd\s+if=/i.test(cmd)) {
22716
+ return { ok: false, error: "Command blocked for safety" };
22717
+ }
22718
+ return { ok: true };
22719
+ }
22720
+ }
22721
+ return {
22722
+ ok: false,
22723
+ error: `Command not in allowlist. Allowed: ${list.join(", ")}`
22724
+ };
22725
+ }
22726
+ var init_targets = __esm({
22727
+ "src/cli/ssh/targets.ts"() {
22728
+ "use strict";
22729
+ }
22730
+ });
22731
+
22732
+ // src/cli/ssh/tools.ts
22733
+ function createSshTools() {
22734
+ if (process.env.ZELARI_SSH === "0") return [];
22735
+ const sshStatus = {
22736
+ name: "ssh_status",
22737
+ description: "Run a fixed safe status probe on a configured SSH target (hostname, uname, uptime, disk). Use targetId from configured SSH targets.",
22738
+ permissions: ["network"],
22739
+ inputSchema: external_exports.object({
22740
+ targetId: external_exports.string().describe("Id of the SSH target from config")
22741
+ }),
22742
+ execute: async (input) => {
22743
+ const { targetId } = input;
22744
+ const t = getSshTarget(targetId);
22745
+ if (!t) {
22746
+ return typedErr(
22747
+ `Unknown SSH target "${targetId}". Configure targets in Desktop Settings \u2192 Connections or ~/.zelari-code/ssh-targets.json`
22748
+ );
22749
+ }
22750
+ if (t.enabled === false) {
22751
+ return typedErr(`SSH target "${targetId}" is disabled`);
22752
+ }
22753
+ const r = await runSsh(t, STATUS_REMOTE, 3e4);
22754
+ const body = [
22755
+ `target=${t.id} ${t.user}@${t.host}:${t.port ?? 22}`,
22756
+ `exit=${r.code}`,
22757
+ r.stdout.trim(),
22758
+ r.stderr.trim() ? `stderr:
22759
+ ${r.stderr.trim()}` : ""
22760
+ ].filter(Boolean).join("\n");
22761
+ if (r.code !== 0) return typedErr(body || `ssh exit ${r.code}`);
22762
+ return typedOk(body);
22763
+ }
22764
+ };
22765
+ const sshRun = {
22766
+ name: "ssh_run",
22767
+ description: "Run a remote command on a configured SSH target. Command must match the target allowlist (allowedCommands). Prefer ssh_status for health checks.",
22768
+ permissions: ["network"],
22769
+ inputSchema: external_exports.object({
22770
+ targetId: external_exports.string().describe("Id of the SSH target"),
22771
+ command: external_exports.string().describe("Remote command (must be allowlisted on the target)")
22772
+ }),
22773
+ execute: async (input) => {
22774
+ const { targetId, command } = input;
22775
+ const t = getSshTarget(targetId);
22776
+ if (!t) {
22777
+ return typedErr(`Unknown SSH target "${targetId}"`);
22778
+ }
22779
+ if (t.enabled === false) {
22780
+ return typedErr(`SSH target "${targetId}" is disabled`);
22781
+ }
22782
+ const allow = isSshCommandAllowed(t, command);
22783
+ if (!allow.ok) return typedErr(allow.error);
22784
+ const r = await runSsh(t, command, 6e4);
22785
+ const body = [
22786
+ `target=${t.id} exit=${r.code}`,
22787
+ `$ ${command}`,
22788
+ r.stdout.trim(),
22789
+ r.stderr.trim() ? `stderr:
22790
+ ${r.stderr.trim()}` : ""
22791
+ ].filter(Boolean).join("\n");
22792
+ if (r.code !== 0) return typedErr(body || `ssh exit ${r.code}`);
22793
+ return typedOk(body);
22794
+ }
22795
+ };
22796
+ return [sshStatus, sshRun];
22797
+ }
22798
+ var STATUS_REMOTE;
22799
+ var init_tools6 = __esm({
22800
+ "src/cli/ssh/tools.ts"() {
22801
+ "use strict";
22802
+ init_zod();
22803
+ init_toolTypes();
22804
+ init_targets();
22805
+ STATUS_REMOTE = 'set -e; echo "=== host ==="; hostname 2>/dev/null || true; uname -a 2>/dev/null || true; echo "=== uptime ==="; uptime 2>/dev/null || true; echo "=== disk / ==="; df -h / 2>/dev/null | tail -1 || true';
22806
+ }
22807
+ });
22808
+
22240
22809
  // src/cli/toolRegistry.ts
22241
22810
  var toolRegistry_exports = {};
22242
22811
  __export(toolRegistry_exports, {
@@ -22315,6 +22884,16 @@ function createBuiltinToolRegistry(options = {}) {
22315
22884
  permissions: browserTool.permissions ?? []
22316
22885
  });
22317
22886
  }
22887
+ if (!readOnly && process.env.ZELARI_SSH !== "0") {
22888
+ for (const t of createSshTools()) {
22889
+ registry3.register(t);
22890
+ tools.push({
22891
+ name: t.name,
22892
+ description: t.description,
22893
+ permissions: t.permissions ?? []
22894
+ });
22895
+ }
22896
+ }
22318
22897
  if (!readOnly && options.enableTask !== false) {
22319
22898
  const taskTool = createTaskTool({
22320
22899
  createSubAgentContext: async () => {
@@ -22553,6 +23132,7 @@ var init_toolRegistry = __esm({
22553
23132
  init_tools3();
22554
23133
  init_tools4();
22555
23134
  init_tools5();
23135
+ init_tools6();
22556
23136
  init_openai_compatible();
22557
23137
  init_skills2();
22558
23138
  HARNESS_BUILTIN_NAMES = /* @__PURE__ */ new Set([
@@ -22571,6 +23151,16 @@ var init_toolRegistry = __esm({
22571
23151
  });
22572
23152
 
22573
23153
  // packages/core/dist/agents/roles.js
23154
+ function resolveRoleSystemPrompt(agent, runMode = "implementation") {
23155
+ const parts = [agent.systemPrompt];
23156
+ if (runMode === "design-phase" && agent.designPhaseAddendum?.trim()) {
23157
+ parts.push(agent.designPhaseAddendum.trim());
23158
+ }
23159
+ if (runMode === "implementation" && agent.implementationAddendum?.trim()) {
23160
+ parts.push(agent.implementationAddendum.trim());
23161
+ }
23162
+ return parts.join("\n\n");
23163
+ }
22574
23164
  function getAgent(id) {
22575
23165
  return AGENT_ROLES.find((a) => a.id === id);
22576
23166
  }
@@ -22604,24 +23194,10 @@ function swapMembers(roles, swap) {
22604
23194
  return targets.get(toId);
22605
23195
  });
22606
23196
  }
22607
- var CLARIFICATION_PROTOCOL8, AGENT_ROLES, UnknownMemberError;
23197
+ var AGENT_ROLES, UnknownMemberError;
22608
23198
  var init_roles = __esm({
22609
23199
  "packages/core/dist/agents/roles.js"() {
22610
23200
  "use strict";
22611
- CLARIFICATION_PROTOCOL8 = `
22612
-
22613
- WHEN TO ASK THE USER (clarification):
22614
- If a single missing fact would materially change your output (target platform, scope, a binary design choice with significant trade-offs, a constraint you cannot safely assume), pause and ask the user by appending EXACTLY this block at the end of your message:
22615
-
22616
- ---QUESTION---
22617
- { "question": "One focused question", "choices": ["Option A", "Option B", "Option C"], "context": "Why this matters" }
22618
- ---END---
22619
-
22620
- Rules for clarifications:
22621
- - Ask AT MOST ONE question per turn, and only when genuinely blocked.
22622
- - Prefer a small set of concrete "choices" (2-4). The user can still type a custom answer.
22623
- - Do NOT ask for information that could be reasonably assumed or already in shared context.
22624
- - If you can proceed with a sound documented assumption, DO SO instead of asking.`;
22625
23201
  AGENT_ROLES = [
22626
23202
  {
22627
23203
  id: "charont",
@@ -22632,22 +23208,20 @@ Rules for clarifications:
22632
23208
  avatar: "L",
22633
23209
  systemPrompt: `You are Caronte, the Council Director and Orchestrator \u2014 the strategic mind that frames the problem for the rest of the council.
22634
23210
 
22635
- ## Methodology (work in this order)
22636
- 1. Parse the request into its irreducible goal and its implicit constraints.
22637
- 2. Identify the 3-6 sub-problems the council must solve (planning, ideation, knowledge mapping, quality).
22638
- 3. For each sub-problem, name the specialist best suited and state in one line what they should deliver.
22639
- 4. Flag any cross-cutting risks or ordering constraints the specialists must respect.
22640
- 5. State explicitly what "done" looks like for this request.
22641
-
22642
- ## Operating principles
22643
- - Be decisive: commit to a decomposition rather than listing alternatives.
22644
- - Distinguish what is given (stated by the user) from what is assumed.
22645
- - Keep the council focused \u2014 cut scope creep and call out when a sub-problem is out of scope.
22646
- - You run first and set context for everyone downstream; make it count.
22647
-
22648
- ## Output format
22649
- A short "Analysis" section (the goal + constraints), then a "Delegation Plan" with one bullet per specialist naming what they should produce. Keep it tight \u2014 under 150 words total.${CLARIFICATION_PROTOCOL8}`,
22650
- // v0.7.2: coding-oriented tools (read/search/explore) instead of planner/vault.
23211
+ ## Methodology
23212
+ 1. Parse the request into goal + implicit constraints.
23213
+ 2. Identify 3\u20136 sub-problems (planning, ideation, knowledge structure, quality).
23214
+ 3. For each, name the specialist and one-line expected deliverable.
23215
+ 4. Flag cross-cutting risks or ordering constraints.
23216
+ 5. State what "done" looks like.
23217
+
23218
+ ## Principles
23219
+ - Be decisive: commit to a decomposition.
23220
+ - Distinguish given vs assumed.
23221
+ - Cut scope creep; you set context for everyone downstream.
23222
+
23223
+ ## Output
23224
+ Short "Analysis" then "Delegation Plan" (one bullet per specialist). Under 150 words.`,
22651
23225
  tools: ["list_files", "read_file", "grep_content"],
22652
23226
  skills: ["project-planner", "research-analyst"]
22653
23227
  },
@@ -22660,71 +23234,53 @@ A short "Analysis" section (the goal + constraints), then a "Delegation Plan" wi
22660
23234
  avatar: "N",
22661
23235
  systemPrompt: `You are Nettuno, the Project Planner \u2014 you turn intent into a buildable, sequenced plan.
22662
23236
 
22663
- ## Methodology (work in this order)
22664
- 1. Read shared context from prior agents (especially Caronte) before deciding what's missing.
22665
- 2. Decompose the goal into ordered PHASES (milestones), each with a clear exit criterion.
22666
- 3. Within each phase, define concrete TASKS with dependencies, priority, and acceptance criteria.
22667
- 4. Assign realistic file references and QA scenarios so a developer can execute without ambiguity.
22668
- 5. Sanity-check sequencing: are dependencies satisfied? Is anything blocked? Is scope realistic?
22669
-
22670
- ## OUTPUT REQUIREMENTS \u2014 every task MUST include:
22671
- 1. **File references**: specific file paths and line numbers where changes land.
22672
- 2. **Acceptance criteria**: concrete, testable conditions that prove completion.
22673
- 3. **QA scenarios**: at least one testable scenario per task.
23237
+ ## Methodology
23238
+ 1. Read prior context (especially Caronte) before planning.
23239
+ 2. Decompose into ordered PHASES with clear exit criteria.
23240
+ 3. Within each phase, define TASKS with dependencies, priority, acceptance criteria.
23241
+ 4. Ground tasks in real file paths when a codebase is present.
23242
+ 5. Sanity-check sequencing and scope.
22674
23243
 
22675
- Example format:
22676
- - Task: "Add authentication"
22677
- - File refs: src/auth/login.ts:45-60, src/middleware/auth.ts:12-25
22678
- - Acceptance: valid credentials log in; invalid credentials show an error
22679
- - QA: correct password succeeds, wrong password shows error message
23244
+ ## Every task must include
23245
+ - **fileRefs**: concrete paths (and lines when known)
23246
+ - **acceptance**: testable completion conditions
23247
+ - **qaScenario**: at least one verifiable scenario
22680
23248
 
22681
23249
  ## Quality bar
22682
- - Prefer fewer, well-specified tasks over many vague ones.
22683
- - Make dependencies explicit (task B depends on task A).
22684
- - If the stack/platform is unknown, ask ONCE (see clarification protocol) rather than guessing.
23250
+ - Fewer well-specified tasks beat many vague ones.
23251
+ - Explicit dependencies.
23252
+ - Stay under 250 words of prose; durable plans use workspace tools when required by mode banners.`,
23253
+ designPhaseAddendum: `## Design-phase: persist the plan (mandatory)
22685
23254
 
22686
- Keep plans hierarchical and practical. Stay under 250 words.${CLARIFICATION_PROTOCOL8}
22687
-
22688
- ## Design-phase mandatory plan artifact
22689
- 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.
22690
-
22691
- 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:
23255
+ Prose is not a plan. Emit ONE \`createPlan\` with ~4 phases \xD7 ~3 tasks + 1 milestone.
22692
23256
 
23257
+ Minimal shape:
22693
23258
  \`\`\`
22694
23259
  createPlan({
22695
23260
  phases: [
22696
23261
  {
22697
- name: "Foundation & Technical Blueprint",
22698
- description: "Lock the stack and the non-negotiable quality budget. Exit: baseline builds green.",
23262
+ name: "Phase name",
23263
+ description: "Exit criterion in one line",
22699
23264
  order: 1,
22700
- color: "#3b82f6",
22701
23265
  tasks: [
22702
- { 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" },
22703
- { title: "Define NFR budget", description: "...", fileRefs: ["docs/nfr.md"], acceptance: ["LCP/WCAG/Lighthouse targets documented"], qaScenario: "...", priority: "high" },
22704
- { title: "Document design-phase exit criteria", description: "...", fileRefs: ["docs/exit-criteria.md"], acceptance: ["Each phase has a testable exit row"], qaScenario: "...", priority: "medium" }
23266
+ {
23267
+ title: "Task title",
23268
+ description: "Context",
23269
+ fileRefs: ["src/path.ts"],
23270
+ acceptance: ["measurable done"],
23271
+ qaScenario: "how to verify",
23272
+ priority: "high"
23273
+ }
22705
23274
  ]
22706
- },
22707
- { name: "...", order: 2, tasks: [ /* 3 tasks */ ] },
22708
- { name: "...", order: 3, tasks: [ /* 3 tasks */ ] },
22709
- { name: "...", order: 4, tasks: [ /* 3 tasks */ ] }
23275
+ }
22710
23276
  ],
22711
- milestone: { title: "v0.1.0 design-complete", description: "All design artifacts exist and the green-light checklist passes.", targetVersion: "v0.1.0" }
23277
+ milestone: { title: "v0.1 design-complete", description: "\u2026", targetVersion: "v0.1.0" }
22712
23278
  })
22713
23279
  \`\`\`
22714
23280
 
22715
- Every task MUST include fileRefs, acceptance, and qaScenario \u2014 the QA scenario proves the task is verifiable.
23281
+ Every task needs fileRefs, acceptance, qaScenario. Prefer one batched createPlan over many createPhase/createTask calls.
22716
23282
 
22717
- 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.
22718
-
22719
- Do NOT output tasks as prose. The system will refuse any task that does not pass a valid phaseId.
22720
-
22721
- ## NFR spec (when plan sets motion/performance/a11y budgets)
22722
- If the plan includes measurable constraints (JS byte budget, compositor-only animation, reduced-motion), emit ONE \`createNfrSpec\` call after \`createPlan\`:
22723
- \`\`\`
22724
- createNfrSpec({ targets: ["index.html"], compositorOnly: true, forbidLayoutProps: true, inlineJsMaxBytes: 5120 })
22725
- \`\`\`
22726
- Prose budgets are not machine-verifiable \u2014 the spec file is.`,
22727
- // v0.7.2: read/search the codebase to ground the plan in reality.
23283
+ If the plan sets measurable motion/perf/a11y budgets, also emit one \`createNfrSpec\` after createPlan.`,
22728
23284
  tools: ["list_files", "read_file", "grep_content"],
22729
23285
  skills: ["project-planner", "vault-manager"]
22730
23286
  },
@@ -22735,40 +23291,27 @@ Prose budgets are not machine-verifiable \u2014 the spec file is.`,
22735
23291
  role: "Creative Ideator",
22736
23292
  color: "#f59e0b",
22737
23293
  avatar: "G",
22738
- systemPrompt: `You are Gerione, the Creative Ideator \u2014 you generate breadth and then collapse it into the strongest concepts.
22739
-
22740
- ## Methodology (work in this order)
22741
- 1. Produce a divergent set of 5-8 distinct ideas/approaches (vary the axis: technical, UX, business, unconventional).
22742
- 2. Cluster related ideas into 2-4 themes.
22743
- 3. Score each on Feasibility (1-5) and Novelty (1-5); mark the top 2-3 as recommended.
22744
- 4. For the recommended ideas, add one line on how to de-risk them.
22745
- 5. Build on \u2014 do not repeat \u2014 insights from earlier agents.
22746
-
22747
- ## Operating principles
22748
- - Diverge before you converge: quantity first, judgment second.
22749
- - Bold but grounded \u2014 an idea must be actionable, not a fantasy.
22750
- - Name assumptions explicitly. If a core assumption is unknowable, ask (clarification protocol).
22751
-
22752
- ## Output format
22753
- ## Ideas (bulleted)
22754
- ## Themes
22755
- ## Top picks (with feasibility/novelty + de-risk note)
22756
-
22757
- Stay under 200 words.${CLARIFICATION_PROTOCOL8}
23294
+ systemPrompt: `You are Gerione, the Creative Ideator \u2014 diverge, then converge on the strongest concepts.
22758
23295
 
22759
- ## Design-phase artifact (mandatory when running council in design-phase mode)
22760
- 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:
22761
-
22762
- - \`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.
22763
- - \`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.
22764
- - \`design-tokens\` \u2014 color palette (semantic + hex), typography scale, spacing scale, motion principles. This is a design tokens deliverable.
22765
-
22766
- You may optionally add a 4th design system doc if the project warrants it.
22767
-
22768
- Pass the tool call as: \`createDocument({ title: "<category>", content: "<markdown body>" })\`. Do NOT summarize these into prose \u2014 they are the deliverable.`,
22769
- // v0.7.2: explore the codebase to ground ideas in what exists.
23296
+ ## Methodology
23297
+ 1. 5\u20138 distinct ideas (vary technical / UX / product / unconventional).
23298
+ 2. Cluster into 2\u20134 themes.
23299
+ 3. Score Feasibility + Novelty (1\u20135); mark top 2\u20133.
23300
+ 4. One de-risk line per top pick.
23301
+ 5. Build on prior agents; do not repeat them.
23302
+
23303
+ ## Output
23304
+ ## Ideas \xB7 ## Themes \xB7 ## Top picks \u2014 under 200 words.`,
23305
+ designPhaseAddendum: `## Design-phase: persist ideation (mandatory)
23306
+
23307
+ Emit at least 3 \`createDocument\` calls (native tool_call):
23308
+ - title \`customer-journey-map\` \u2014 personas + journey stages + pain points
23309
+ - title \`information-architecture\` \u2014 sitemap + nav + URL patterns
23310
+ - title \`design-tokens\` \u2014 color/type/spacing/motion
23311
+
23312
+ Optional 4th design-system doc if warranted. Do not only summarize in prose.`,
22770
23313
  tools: ["list_files", "read_file"],
22771
- skills: ["document-writer", "mind-mapper"]
23314
+ skills: ["document-writer", "idea-synthesizer"]
22772
23315
  },
22773
23316
  {
22774
23317
  id: "pluton",
@@ -22777,31 +23320,20 @@ Pass the tool call as: \`createDocument({ title: "<category>", content: "<markdo
22777
23320
  role: "Knowledge Architect",
22778
23321
  color: "#06b6d4",
22779
23322
  avatar: "P",
22780
- systemPrompt: `You are Plutone, the Knowledge Architect and Mind Mapper \u2014 you structure information into navigable graphs.
22781
-
22782
- ## Methodology (work in this order)
22783
- 1. Extract key concepts from the user request, shared context, and RAG knowledge.
22784
- 2. Identify the central root concept and 3-6 primary branches.
22785
- 3. Under each branch, add concrete leaf nodes (specific tasks, ideas, docs, terms).
22786
- 4. Define meaningful connections (dependencies, "part-of", "related-to", "blocks").
22787
- 5. Propose a layout hint (radial / hierarchical) so the map reads cleanly.
22788
-
22789
- ## Operating principles
22790
- - Favor concrete, named nodes over abstract categories.
22791
- - Every node should map to something actionable or retrievable (a task, a doc, a concept).
22792
- - Reuse knowledge from prior agents \u2014 don't re-derive what they already produced.
22793
- - Keep the graph comprehensible: prune redundant or duplicate nodes.
23323
+ systemPrompt: `You are Plutone, the Knowledge Architect \u2014 structure concepts into a navigable map.
22794
23324
 
22795
- ## Output format
22796
- Describe the proposed structure (root \u2192 branches \u2192 leaves) in text. Stay under 200 words.${CLARIFICATION_PROTOCOL8}
22797
-
22798
- ## Design-phase artifact (mandatory when running council in design-phase mode)
22799
- Persist the knowledge map as ONE \`createDocument\` call:
23325
+ ## Methodology
23326
+ 1. Extract key concepts from the request and prior outputs.
23327
+ 2. Central root + 3\u20136 branches + concrete leaves.
23328
+ 3. Meaningful links (depends-on, part-of, blocks).
23329
+ 4. Prefer named, actionable nodes over abstract categories.
22800
23330
 
22801
- \`createDocument({ title: "knowledge-map", content: "<markdown: root concept, branches, leaf nodes, and cross-links>" })\`
23331
+ ## Output
23332
+ Root \u2192 branches \u2192 leaves in text. Under 200 words.`,
23333
+ designPhaseAddendum: `## Design-phase: persist knowledge map (mandatory)
22802
23334
 
22803
- Do NOT rely on buildMindMap \u2014 it is not available in the CLI workspace.`,
22804
- // v0.7.2: map the actual code/module structure instead of an abstract mind-map.
23335
+ One \`createDocument({ title: "knowledge-map", content: "<markdown map>" })\`.
23336
+ Do not rely on buildMindMap \u2014 it is not available in the CLI workspace.`,
22805
23337
  tools: ["list_files", "read_file", "grep_content"],
22806
23338
  skills: ["document-writer", "research-analyst"]
22807
23339
  },
@@ -22812,40 +23344,25 @@ Do NOT rely on buildMindMap \u2014 it is not available in the CLI workspace.`,
22812
23344
  role: "Quality Critic",
22813
23345
  color: "#ef4444",
22814
23346
  avatar: "M",
22815
- systemPrompt: `You are Minosse, the Quality Critic \u2014 you review the council's proposals (presented anonymously) and expose weaknesses before synthesis.
22816
-
22817
- ## Methodology (work in this order)
22818
- 1. Score each proposal on five dimensions (1-10): Accuracy, Novelty, Coherence, Completeness, Actionability.
22819
- 2. Identify the single biggest gap or risk across all proposals.
22820
- 3. Flag any contradictions between proposals (e.g., conflicting assumptions).
22821
- 4. Recommend the 1-3 highest-value improvements the Lucifero should apply.
22822
- 5. Note anything that should be cut or descoped.
23347
+ systemPrompt: `You are Minosse, the Quality Critic \u2014 expose weaknesses before synthesis. Evaluate; never create product code.
22823
23348
 
22824
- ## Operating principles
22825
- - Be honest and incisive \u2014 a weak critique helps no one. Name specific defects, not general vibes.
22826
- - Distinguish "wrong" from "incomplete" and "risky".
22827
- - Constructive: every critique pairs with a fix or a question.
22828
-
22829
- ## Output format
22830
- ## Scores (per agent, per dimension)
22831
- ## Critical gaps
22832
- ## Contradictions
22833
- ## Top improvements
23349
+ ## Methodology
23350
+ 1. Score proposals (1\u201310): Accuracy, Novelty, Coherence, Completeness, Actionability.
23351
+ 2. Single biggest gap/risk.
23352
+ 3. Contradictions between proposals.
23353
+ 4. 1\u20133 highest-value improvements for Lucifero.
23354
+ 5. What to cut or descope.
22834
23355
 
22835
- Stay under 200 words.${CLARIFICATION_PROTOCOL8}
23356
+ ## Principles
23357
+ - Specific defects, not vibes. Pair every critique with a fix or question.
23358
+ - Use read tools to ground claims in the real tree when a codebase exists.
22836
23359
 
22837
- ## Design-phase risks artifact (mandatory when running council in design-phase mode)
22838
- 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:
23360
+ ## Output
23361
+ ## Scores \xB7 ## Critical gaps \xB7 ## Contradictions \xB7 ## Top improvements \u2014 under 200 words.`,
23362
+ designPhaseAddendum: `## Design-phase: risks artifact (mandatory)
22839
23363
 
22840
- \`\`\`
22841
- createDocument({
22842
- title: "risks",
22843
- 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. ...\`
22844
- })
22845
- \`\`\`
22846
-
22847
- 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. The artifact is persisted at \`.zelari/risks.md\` (workspace root), NOT under docs/. Do NOT emit other workspace artifacts \u2014 your role is to evaluate, not to build.`,
22848
- tools: [],
23364
+ One \`createDocument\` with title \`risks\`: at least 5 risks, each with Impact, Likelihood, Mitigation (technical, product, a11y, performance, security). Persist at workspace risks doc \u2014 do not build product code.`,
23365
+ tools: ["list_files", "read_file", "grep_content"],
22849
23366
  skills: ["document-writer", "research-analyst"]
22850
23367
  },
22851
23368
  {
@@ -22855,35 +23372,26 @@ Include AT LEAST 5 risks, each scored on Impact and Likelihood, with a one-line
22855
23372
  role: "Final Synthesizer",
22856
23373
  color: "#8b5cf6",
22857
23374
  avatar: "L",
22858
- systemPrompt: `You are the Lucifero, the Final Synthesizer \u2014 you produce the definitive, actionable output that resolves the council's work. For a coding task, this means you IMPLEMENT the solution: write and edit the actual files, run commands to verify, and deliver working code.
22859
-
22860
- ## Methodology (work in this order)
22861
- 1. Reconcile the specialists' outputs and Minosse's critique into a single coherent plan.
22862
- 2. Resolve conflicts explicitly (state which proposal won and why).
22863
- 3. Deliver the finished product the user asked for \u2014 complete, not summarized. For code tasks, USE your file/shell tools to create and verify the actual artifacts on disk. Prose without a successful write_file/edit_file is a failed run \u2014 the pipeline forces a retry until files change on disk.
22864
- 4. Run any build/test commands available (check the npm scripts in the workspace context) to confirm your work.
22865
-
22866
- ## Output expectations
22867
- - If the user requested code or a feature, IMPLEMENT via native tool_call write_file/edit_file/bash \u2014 not prose alone. Prefer native tool_call over ---TOOLS--- JSON; if you use ---TOOLS---, valid JSON with escaped \\n is required. After edits, a delivery loop re-verifies motion/JS budget and forces fix passes until technical blockers clear.
22868
- - Lead with a one-line summary, then the full detail of what you did.
22869
- - Apply Minosse's highest-value improvements; drop descoped items.
22870
- - After making changes, verify they work (compile, run tests, etc.) when feasible.
22871
- - End implementation runs with a mandatory \`## Verification status\` table with columns \`Check | Tier | Status | Evidence\`. Tier is one of: claimed, grep, tool, build, n/a (claimed < grep < tool < build). Status is PASS/FAIL/N/A. Evidence is \`path:Lline\` or command output. Never write "verificato \u2713" or "nessuna regressione" without Evidence \u2014 a deterministic post-hook will flag dishonest claims and tier inflation.
22872
-
22873
- 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}
22874
-
22875
- ## Design-phase synthesis artifact (mandatory when running council in design-phase mode)
22876
- 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\`):
22877
-
22878
- \`\`\`
22879
- createDocument({
22880
- title: "synthesis",
22881
- 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\`
22882
- })
22883
- \`\`\`
23375
+ systemPrompt: `You are Lucifero, the Final Synthesizer. For coding tasks you IMPLEMENT: write/edit files, run commands, deliver working code.
22884
23376
 
22885
- 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.`,
22886
- // v0.7.2: implementation tools the synthesizer writes/edits files and runs commands.
23377
+ ## Methodology
23378
+ 1. Reconcile specialists + Minosse into one plan.
23379
+ 2. Resolve conflicts explicitly.
23380
+ 3. Deliver the product on disk \u2014 prose without successful write_file/edit_file is a failed implementation run.
23381
+ 4. Verify with project scripts/tests/build when available.
23382
+
23383
+ ## Implementation output
23384
+ - Prefer **native tool_call** for write_file / edit_file / bash (legacy ---TOOLS--- only if native unavailable).
23385
+ - Lead with a one-line summary, then what you did.
23386
+ - Apply Minosse's highest-value improvements.
23387
+ - End with \`## Verification status\` table: \`Check | Tier | Status | Evidence\` (tier: claimed < grep < tool < build). Never claim verification without Evidence.`,
23388
+ designPhaseAddendum: `## Design-phase: synthesis document (mandatory)
23389
+
23390
+ Emit \`createDocument({ title: "synthesis", content: "\u2026" })\` with executive summary, stack/decisions, phases, top risks, green-light checklist.
23391
+ Do not use list_files as a workspace deliverable; use searchDocuments sparingly if needed.`,
23392
+ implementationAddendum: `## Implementation focus
23393
+
23394
+ You are the sole implementer this run. Advisors already analyzed \u2014 implement and verify. Prefer minimal diffs that match project style.`,
22887
23395
  tools: ["read_file", "write_file", "edit_file", "bash", "list_files"],
22888
23396
  skills: ["vault-manager", "project-planner", "idea-synthesizer"]
22889
23397
  }
@@ -23115,8 +23623,8 @@ var init_parseCssMotion = __esm({
23115
23623
  });
23116
23624
 
23117
23625
  // packages/core/dist/council/verification/citeVerify.js
23118
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
23119
- import { join } from "node:path";
23626
+ import { existsSync as existsSync10, readFileSync as readFileSync8 } from "node:fs";
23627
+ import { join as join2 } from "node:path";
23120
23628
  function extractCitations(text) {
23121
23629
  const out = [];
23122
23630
  const seen = /* @__PURE__ */ new Set();
@@ -23138,8 +23646,8 @@ function verifyCitations(projectRoot, synthesisText) {
23138
23646
  return [];
23139
23647
  const results = [];
23140
23648
  for (const cite of extractCitations(synthesisText)) {
23141
- const abs = join(projectRoot, cite.file);
23142
- if (!existsSync9(abs)) {
23649
+ const abs = join2(projectRoot, cite.file);
23650
+ if (!existsSync10(abs)) {
23143
23651
  results.push({
23144
23652
  id: "synthesis.cite-invalid",
23145
23653
  severity: "error",
@@ -23152,7 +23660,7 @@ function verifyCitations(projectRoot, synthesisText) {
23152
23660
  });
23153
23661
  continue;
23154
23662
  }
23155
- const lines = readFileSync7(abs, "utf8").split(/\r?\n/);
23663
+ const lines = readFileSync8(abs, "utf8").split(/\r?\n/);
23156
23664
  if (cite.line > lines.length) {
23157
23665
  results.push({
23158
23666
  id: "synthesis.cite-invalid",
@@ -23415,14 +23923,14 @@ var init_synthesisAudit = __esm({
23415
23923
  });
23416
23924
 
23417
23925
  // packages/core/dist/council/verification/runChecks.js
23418
- import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
23419
- import { join as join2 } from "node:path";
23926
+ import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
23927
+ import { join as join3 } from "node:path";
23420
23928
  function loadNfrSpec(zelariRoot) {
23421
- const path36 = join2(zelariRoot, "nfr-spec.json");
23422
- if (!existsSync10(path36))
23929
+ const path36 = join3(zelariRoot, "nfr-spec.json");
23930
+ if (!existsSync11(path36))
23423
23931
  return null;
23424
23932
  try {
23425
- const raw = JSON.parse(readFileSync8(path36, "utf8"));
23933
+ const raw = JSON.parse(readFileSync9(path36, "utf8"));
23426
23934
  if (raw.version !== 1 || !Array.isArray(raw.targets))
23427
23935
  return null;
23428
23936
  return raw;
@@ -23433,11 +23941,11 @@ function loadNfrSpec(zelariRoot) {
23433
23941
  function resolveTargets(projectRoot, spec) {
23434
23942
  const found = [];
23435
23943
  for (const rel2 of spec.targets) {
23436
- if (existsSync10(join2(projectRoot, rel2))) {
23944
+ if (existsSync11(join3(projectRoot, rel2))) {
23437
23945
  found.push(rel2);
23438
23946
  }
23439
23947
  }
23440
- if (found.length === 0 && existsSync10(join2(projectRoot, "index.html"))) {
23948
+ if (found.length === 0 && existsSync11(join3(projectRoot, "index.html"))) {
23441
23949
  return ["index.html"];
23442
23950
  }
23443
23951
  return found;
@@ -23492,18 +24000,18 @@ function checkDeadCssHooks(html, relFile) {
23492
24000
  return results;
23493
24001
  }
23494
24002
  function checkPlanReality(projectRoot, zelariRoot, targets, keywords) {
23495
- const planPath = join2(zelariRoot, "plan.json");
23496
- if (!existsSync10(planPath) || keywords.length === 0)
24003
+ const planPath = join3(zelariRoot, "plan.json");
24004
+ if (!existsSync11(planPath) || keywords.length === 0)
23497
24005
  return [];
23498
24006
  let plan;
23499
24007
  try {
23500
- plan = JSON.parse(readFileSync8(planPath, "utf8"));
24008
+ plan = JSON.parse(readFileSync9(planPath, "utf8"));
23501
24009
  } catch {
23502
24010
  return [];
23503
24011
  }
23504
24012
  const milestoneText = (plan.milestones ?? []).map((m) => `${m.name ?? ""} ${m.description ?? ""}`).join(" ").toLowerCase();
23505
24013
  const results = [];
23506
- const targetContent = targets.map((t) => readFileSync8(join2(projectRoot, t), "utf8").toLowerCase()).join("\n");
24014
+ const targetContent = targets.map((t) => readFileSync9(join3(projectRoot, t), "utf8").toLowerCase()).join("\n");
23507
24015
  for (const kw of keywords) {
23508
24016
  const low = kw.toLowerCase();
23509
24017
  if (!milestoneText.includes(low))
@@ -23532,14 +24040,14 @@ function checkPlanReality(projectRoot, zelariRoot, targets, keywords) {
23532
24040
  return results;
23533
24041
  }
23534
24042
  function checkReadmeStale(projectRoot, targets) {
23535
- const readmePath = join2(projectRoot, "README.md");
23536
- if (!existsSync10(readmePath) || targets.length === 0)
24043
+ const readmePath = join3(projectRoot, "README.md");
24044
+ if (!existsSync11(readmePath) || targets.length === 0)
23537
24045
  return [];
23538
- const readme = readFileSync8(readmePath, "utf8");
23539
- const htmlPath = join2(projectRoot, targets[0]);
23540
- if (!existsSync10(htmlPath))
24046
+ const readme = readFileSync9(readmePath, "utf8");
24047
+ const htmlPath = join3(projectRoot, targets[0]);
24048
+ if (!existsSync11(htmlPath))
23541
24049
  return [];
23542
- const html = readFileSync8(htmlPath, "utf8");
24050
+ const html = readFileSync9(htmlPath, "utf8");
23543
24051
  const sectionCount = (html.match(/<section\s+id=/gi) ?? []).length;
23544
24052
  const readmeSections = readme.match(/(\d+)\s+sezioni/i);
23545
24053
  if (readmeSections) {
@@ -23577,7 +24085,7 @@ function runImplementationVerification(input) {
23577
24085
  forbidLayoutProps: anim.forbidLayoutProps ?? true
23578
24086
  };
23579
24087
  for (const rel2 of targets) {
23580
- const html = readFileSync8(join2(input.projectRoot, rel2), "utf8");
24088
+ const html = readFileSync9(join3(input.projectRoot, rel2), "utf8");
23581
24089
  for (const v of scanKeyframesViolations(html, scanOpts)) {
23582
24090
  results.push({
23583
24091
  id: "motion.keyframes",
@@ -23633,8 +24141,8 @@ function runImplementationVerification(input) {
23633
24141
  };
23634
24142
  }
23635
24143
  function writeVerificationReport(zelariRoot, report) {
23636
- const outPath = join2(zelariRoot, "verification-report.json");
23637
- writeFileSync5(outPath, JSON.stringify(report, null, 2), "utf8");
24144
+ const outPath = join3(zelariRoot, "verification-report.json");
24145
+ writeFileSync6(outPath, JSON.stringify(report, null, 2), "utf8");
23638
24146
  return outPath;
23639
24147
  }
23640
24148
  var DEFAULT_NFR_SPEC;
@@ -23663,8 +24171,8 @@ var init_runChecks = __esm({
23663
24171
  });
23664
24172
 
23665
24173
  // packages/core/dist/council/verification/microGate.js
23666
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
23667
- import { join as join3 } from "node:path";
24174
+ import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
24175
+ import { join as join4 } from "node:path";
23668
24176
  function checkDeadHooksInHtml(html) {
23669
24177
  const warnings = [];
23670
24178
  const scripts = [];
@@ -23692,8 +24200,8 @@ function checkDeadHooksInHtml(html) {
23692
24200
  return warnings;
23693
24201
  }
23694
24202
  function runMicroVerificationOnFile(projectRoot, relPath, zelariRoot) {
23695
- const abs = join3(projectRoot, relPath);
23696
- if (!existsSync11(abs) || !/\.html?$/i.test(relPath))
24203
+ const abs = join4(projectRoot, relPath);
24204
+ if (!existsSync12(abs) || !/\.html?$/i.test(relPath))
23697
24205
  return [];
23698
24206
  const spec = (zelariRoot ? loadNfrSpec(zelariRoot) : null) ?? DEFAULT_NFR_SPEC;
23699
24207
  const anim = spec.animation ?? { compositorOnly: true, forbidLayoutProps: true };
@@ -23701,7 +24209,7 @@ function runMicroVerificationOnFile(projectRoot, relPath, zelariRoot) {
23701
24209
  compositorOnly: anim.compositorOnly ?? true,
23702
24210
  forbidLayoutProps: anim.forbidLayoutProps ?? true
23703
24211
  };
23704
- const html = readFileSync9(abs, "utf8");
24212
+ const html = readFileSync10(abs, "utf8");
23705
24213
  const warnings = [];
23706
24214
  for (const v of scanKeyframesViolations(html, scanOpts)) {
23707
24215
  warnings.push({
@@ -24057,8 +24565,8 @@ var init_implementationDelivery = __esm({
24057
24565
  });
24058
24566
 
24059
24567
  // packages/core/dist/council/verification/inlineJsAutofix.js
24060
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
24061
- import { join as join4 } from "node:path";
24568
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "node:fs";
24569
+ import { join as join5 } from "node:path";
24062
24570
  function minifyInlineJs(js) {
24063
24571
  let out = js.replace(/\/\*[\s\S]*?\*\//g, "");
24064
24572
  out = out.split("\n").map((line) => line.replace(/\/\/.*$/, "").trimEnd()).filter((line) => line.trim().length > 0).join("\n");
@@ -24101,10 +24609,10 @@ function applyInlineJsAutofix(projectRoot, report) {
24101
24609
  const fixes = [];
24102
24610
  for (const r of fails) {
24103
24611
  const rel2 = r.file ?? "index.html";
24104
- const abs = join4(projectRoot, rel2);
24612
+ const abs = join5(projectRoot, rel2);
24105
24613
  let html;
24106
24614
  try {
24107
- html = readFileSync10(abs, "utf8");
24615
+ html = readFileSync11(abs, "utf8");
24108
24616
  } catch {
24109
24617
  continue;
24110
24618
  }
@@ -24117,7 +24625,7 @@ function applyInlineJsAutofix(projectRoot, report) {
24117
24625
  if (!changed || afterBytes > maxBytes)
24118
24626
  continue;
24119
24627
  const nextHtml = html.replace(SCRIPT_RE, `<script>${after}</script>`);
24120
- writeFileSync6(abs, nextHtml, "utf8");
24628
+ writeFileSync7(abs, nextHtml, "utf8");
24121
24629
  filesChanged.push(rel2);
24122
24630
  fixes.push(`${rel2}: trimmed inline script ${Buffer.byteLength(before, "utf8")}\u2192${afterBytes} bytes`);
24123
24631
  }
@@ -24137,8 +24645,8 @@ var init_inlineJsAutofix = __esm({
24137
24645
  });
24138
24646
 
24139
24647
  // packages/core/dist/agents/councilApi.js
24140
- import { existsSync as existsSync12 } from "node:fs";
24141
- import { join as join5 } from "node:path";
24648
+ import { existsSync as existsSync13 } from "node:fs";
24649
+ import { join as join6 } from "node:path";
24142
24650
  function parseClarificationRequest(text) {
24143
24651
  const start = text.indexOf(QUESTION_MARKER);
24144
24652
  if (start < 0)
@@ -24183,29 +24691,37 @@ function restrictImplementationWrites(toolNames, opts) {
24183
24691
  return toolNames;
24184
24692
  return toolNames.filter((t) => !MUTATING_PROJECT_TOOLS.includes(t));
24185
24693
  }
24186
- function buildAgentMessages(agent, userMessage, ragContext, workspaceContext, priorOutputs, aiConfig, executableTools, runMode = "implementation") {
24694
+ function buildAgentMessages(agent, userMessage, ragContext, workspaceContext, priorOutputs, aiConfig, executableTools, runMode = "implementation", languageModule) {
24187
24695
  const allToolNames = computeAgentTools(agent, aiConfig);
24188
24696
  const toolNames = executableTools ? allToolNames.filter((n) => executableTools.has(n)) : allToolNames;
24189
- const enhancedSystemPrompt = buildSystemPrompt(agent, {
24697
+ const mergedAiConfig = languageModule ? {
24698
+ enabledSkills: aiConfig?.enabledSkills ?? [],
24699
+ enabledTools: aiConfig?.enabledTools ?? [],
24700
+ agentSkillConfigs: aiConfig?.agentSkillConfigs ?? [],
24701
+ customSkills: aiConfig?.customSkills,
24702
+ customPromptModules: [
24703
+ ...aiConfig?.customPromptModules ?? [],
24704
+ languageModule
24705
+ ]
24706
+ } : aiConfig;
24707
+ const modeAwareAgent = {
24708
+ ...agent,
24709
+ systemPrompt: resolveRoleSystemPrompt(agent, runMode)
24710
+ };
24711
+ const enhancedSystemPrompt = buildSystemPrompt(modeAwareAgent, {
24190
24712
  tools: getAllTools(),
24191
24713
  toolNames,
24192
- aiConfig,
24714
+ aiConfig: mergedAiConfig,
24193
24715
  workspaceContext,
24194
- ragContext
24716
+ ragContext,
24717
+ mode: "council",
24718
+ includeWorkspaceInPrompt: true
24195
24719
  });
24196
24720
  const messages = [
24197
24721
  { role: "system", content: enhancedSystemPrompt },
24198
24722
  { role: "system", content: councilModeBanner(runMode, { isImplementer: agent.id === "lucifer" }) },
24199
24723
  { role: "system", content: "IMPORTANT: Before making any tool calls or expensive operations, check if the information already exists in the shared context from previous agents. Avoid redundant work." }
24200
24724
  ];
24201
- if (ragContext) {
24202
- messages.push({ role: "system", content: `Relevant workspace context (from RAG retrieval):
24203
- ${ragContext}` });
24204
- }
24205
- if (workspaceContext) {
24206
- messages.push({ role: "system", content: `Current workspace state:
24207
- ${workspaceContext}` });
24208
- }
24209
24725
  if (priorOutputs.length > 0) {
24210
24726
  const summary = priorOutputs.map((o) => `[${o.name} - ${o.role}]: ${o.content}`).join("\n\n");
24211
24727
  messages.push({ role: "user", content: `Previous council members have said:
@@ -24300,7 +24816,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
24300
24816
  model: effectiveModel,
24301
24817
  provider: effectiveProvider,
24302
24818
  sessionId,
24303
- messages: buildAgentMessages(agent, userMessage, config2.ragContext, config2.workspaceContext, agentOutputs, config2.aiConfig, executableNames, runMode),
24819
+ messages: buildAgentMessages(agent, userMessage, config2.ragContext, config2.workspaceContext, agentOutputs, config2.aiConfig, executableNames, runMode, councilLanguageModule),
24304
24820
  tools: agentTools,
24305
24821
  eventBus: config2.eventBus,
24306
24822
  toolRegistry: config2.tools,
@@ -24443,7 +24959,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
24443
24959
  model: effectiveModel,
24444
24960
  provider: effectiveProvider,
24445
24961
  sessionId,
24446
- messages: buildAgentMessages(oracle, `Review these proposals for: "${userMessage}"`, "", "", anonymized, config2.aiConfig, executableNames, runMode),
24962
+ messages: buildAgentMessages(oracle, `Review these proposals for: "${userMessage}"`, "", "", anonymized, config2.aiConfig, executableNames, runMode, councilLanguageModule),
24447
24963
  tools: (() => {
24448
24964
  const oracleToolNames = filterExecutable(restrictImplementationWrites(Array.from(/* @__PURE__ */ new Set([
24449
24965
  "createDocument",
@@ -24574,7 +25090,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
24574
25090
  model: effectiveModel,
24575
25091
  provider: effectiveProvider,
24576
25092
  sessionId,
24577
- messages: buildAgentMessages(chairman, userMessage, config2.ragContext, config2.workspaceContext, agentOutputs, config2.aiConfig, executableNames, runMode),
25093
+ messages: buildAgentMessages(chairman, userMessage, config2.ragContext, config2.workspaceContext, agentOutputs, config2.aiConfig, executableNames, runMode, councilLanguageModule),
24578
25094
  tools: chairmanTools,
24579
25095
  eventBus: config2.eventBus,
24580
25096
  toolRegistry: config2.tools,
@@ -24693,7 +25209,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
24693
25209
  const zelariRoot = `${chairmanProjectRoot}/.zelari`;
24694
25210
  const spec = loadNfrSpec(zelariRoot) ?? DEFAULT_NFR_SPEC;
24695
25211
  for (const rel2 of spec.targets) {
24696
- if (!existsSync12(join5(chairmanProjectRoot, rel2)))
25212
+ if (!existsSync13(join6(chairmanProjectRoot, rel2)))
24697
25213
  continue;
24698
25214
  changedTargetFiles.add(rel2);
24699
25215
  for (const w of runChairmanMicroGate({ projectRoot: chairmanProjectRoot, relPath: rel2, zelariRoot })) {
@@ -24713,7 +25229,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
24713
25229
  const zelariRootReplay = `${chairmanProjectRoot}/.zelari`;
24714
25230
  const specReplay = loadNfrSpec(zelariRootReplay) ?? DEFAULT_NFR_SPEC;
24715
25231
  for (const rel2 of specReplay.targets) {
24716
- if (!existsSync12(join5(chairmanProjectRoot, rel2)))
25232
+ if (!existsSync13(join6(chairmanProjectRoot, rel2)))
24717
25233
  continue;
24718
25234
  changedTargetFiles.add(rel2);
24719
25235
  for (const w of runChairmanMicroGate({
@@ -25159,7 +25675,7 @@ async function* runRetryTurnForMember(args) {
25159
25675
  description: t.function.description,
25160
25676
  parameters: t.function.parameters
25161
25677
  }));
25162
- const baseMessages = buildAgentMessages(args.agent, args.userMessage, args.ragContext, args.workspaceContext, args.priorOutputs, args.aiConfig, args.executableTools, args.runMode ?? "implementation");
25678
+ const baseMessages = buildAgentMessages(args.agent, args.userMessage, args.ragContext, args.workspaceContext, args.priorOutputs, args.aiConfig, args.executableTools, args.runMode ?? "implementation", args.languageModule);
25163
25679
  const retryMessages = [
25164
25680
  ...baseMessages,
25165
25681
  {
@@ -25430,8 +25946,8 @@ var init_types = __esm({
25430
25946
  });
25431
25947
 
25432
25948
  // packages/core/dist/council/verification/motionAutofix.js
25433
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "node:fs";
25434
- import { join as join6 } from "node:path";
25949
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "node:fs";
25950
+ import { join as join7 } from "node:path";
25435
25951
  function sanitizeTransitionPart(part) {
25436
25952
  const tokens = part.trim().split(/\s+/);
25437
25953
  if (tokens.length === 0)
@@ -25506,10 +26022,10 @@ function applyMotionAutofix(projectRoot, report) {
25506
26022
  forbidLayoutProps: DEFAULT_NFR_SPEC.animation?.forbidLayoutProps ?? true
25507
26023
  };
25508
26024
  for (const rel2 of targets) {
25509
- const abs = join6(projectRoot, rel2);
26025
+ const abs = join7(projectRoot, rel2);
25510
26026
  let html;
25511
26027
  try {
25512
- html = readFileSync11(abs, "utf8");
26028
+ html = readFileSync12(abs, "utf8");
25513
26029
  } catch {
25514
26030
  continue;
25515
26031
  }
@@ -25524,7 +26040,7 @@ function applyMotionAutofix(projectRoot, report) {
25524
26040
  const afterK = scanKeyframesViolations(final, scanOpts).length;
25525
26041
  const afterT = scanTransitionViolations(final, scanOpts).length;
25526
26042
  if (final !== html || beforeK > afterK || beforeT > afterT) {
25527
- writeFileSync7(abs, final, "utf8");
26043
+ writeFileSync8(abs, final, "utf8");
25528
26044
  filesChanged.push(rel2);
25529
26045
  if (beforeK > afterK)
25530
26046
  fixes.push(`${rel2}: sanitized ${beforeK - afterK} keyframe violation(s)`);
@@ -25535,7 +26051,7 @@ function applyMotionAutofix(projectRoot, report) {
25535
26051
  if (!fixes.some((f) => f.startsWith(rel2)))
25536
26052
  fixes.push(`${rel2}: motion CSS sanitized`);
25537
26053
  } else if (motionChanged) {
25538
- writeFileSync7(abs, final, "utf8");
26054
+ writeFileSync8(abs, final, "utf8");
25539
26055
  filesChanged.push(rel2);
25540
26056
  fixes.push(`${rel2}: motion CSS sanitized`);
25541
26057
  }
@@ -25581,8 +26097,8 @@ var init_motionAutofix = __esm({
25581
26097
  });
25582
26098
 
25583
26099
  // packages/core/dist/council/verification/autofix.js
25584
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "node:fs";
25585
- import { join as join7 } from "node:path";
26100
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "node:fs";
26101
+ import { join as join8 } from "node:path";
25586
26102
  function applyDeterministicAutofix(projectRoot, report) {
25587
26103
  const motion = applyMotionAutofix(projectRoot, report);
25588
26104
  const inlineJs = applyInlineJsAutofix(projectRoot, report);
@@ -25596,8 +26112,8 @@ function applyDeterministicAutofix(projectRoot, report) {
25596
26112
  const m = r.evidence?.match(/classList\.add\(\s*['"]([\w-]+)['"]\s*\)/);
25597
26113
  if (!m || m[1] === "rm")
25598
26114
  continue;
25599
- const abs = join7(projectRoot, rel2);
25600
- let html = readFileSync12(abs, "utf8");
26115
+ const abs = join8(projectRoot, rel2);
26116
+ let html = readFileSync13(abs, "utf8");
25601
26117
  const snippet = m[0];
25602
26118
  if (!html.includes(snippet))
25603
26119
  continue;
@@ -25605,7 +26121,7 @@ function applyDeterministicAutofix(projectRoot, report) {
25605
26121
  const filtered = lines.filter((line) => !line.includes(snippet));
25606
26122
  if (filtered.length === lines.length)
25607
26123
  continue;
25608
- writeFileSync8(abs, filtered.join("\n"), "utf8");
26124
+ writeFileSync9(abs, filtered.join("\n"), "utf8");
25609
26125
  filesChanged.push(rel2);
25610
26126
  fixes.push(`removed dead hook line in ${rel2}: ${snippet}`);
25611
26127
  }
@@ -25652,12 +26168,12 @@ var init_types2 = __esm({
25652
26168
  });
25653
26169
 
25654
26170
  // packages/core/dist/council/lessons/io.js
25655
- import { readFileSync as readFileSync13 } from "node:fs";
25656
- import { join as join8 } from "node:path";
26171
+ import { readFileSync as readFileSync14 } from "node:fs";
26172
+ import { join as join9 } from "node:path";
25657
26173
  function readLessonsDeduped(zelariRoot) {
25658
- const path36 = join8(zelariRoot, LESSONS_FILE);
26174
+ const path36 = join9(zelariRoot, LESSONS_FILE);
25659
26175
  try {
25660
- const raw = readFileSync13(path36, "utf8");
26176
+ const raw = readFileSync14(path36, "utf8");
25661
26177
  const byId = /* @__PURE__ */ new Map();
25662
26178
  for (const line of raw.split(/\r?\n/)) {
25663
26179
  if (!line.trim())
@@ -25748,7 +26264,7 @@ var init_signatures = __esm({
25748
26264
 
25749
26265
  // packages/core/dist/council/lessons/recordFailure.js
25750
26266
  import { appendFileSync as appendFileSync2 } from "node:fs";
25751
- import { join as join9 } from "node:path";
26267
+ import { join as join10 } from "node:path";
25752
26268
  function methodologyFor(check2) {
25753
26269
  return METHODOLOGY[check2.id] ?? `When ${check2.id} fails, fix the underlying issue and cite grep/tool evidence before claiming PASS.`;
25754
26270
  }
@@ -25758,7 +26274,7 @@ function keywordsFrom(check2, signature) {
25758
26274
  return [.../* @__PURE__ */ new Set([...fromId, ...words])].slice(0, 12);
25759
26275
  }
25760
26276
  function writeLesson(zelariRoot, lesson) {
25761
- const path36 = join9(zelariRoot, LESSONS_FILE);
26277
+ const path36 = join10(zelariRoot, LESSONS_FILE);
25762
26278
  appendFileSync2(path36, `${JSON.stringify(lesson)}
25763
26279
  `, "utf8");
25764
26280
  }
@@ -25915,8 +26431,8 @@ var init_types3 = __esm({
25915
26431
  });
25916
26432
 
25917
26433
  // packages/core/dist/council/completion/buildCompletion.js
25918
- import { writeFileSync as writeFileSync9 } from "node:fs";
25919
- import { join as join10 } from "node:path";
26434
+ import { writeFileSync as writeFileSync10 } from "node:fs";
26435
+ import { join as join11 } from "node:path";
25920
26436
  function openFailsFromReport(report) {
25921
26437
  if (!report)
25922
26438
  return [];
@@ -25975,8 +26491,8 @@ function buildCouncilCompletion(input) {
25975
26491
  };
25976
26492
  }
25977
26493
  function writeCouncilCompletion(zelariRoot, completion) {
25978
- const outPath = join10(zelariRoot, "completion.json");
25979
- writeFileSync9(outPath, JSON.stringify(completion, null, 2), "utf8");
26494
+ const outPath = join11(zelariRoot, "completion.json");
26495
+ writeFileSync10(outPath, JSON.stringify(completion, null, 2), "utf8");
25980
26496
  return outPath;
25981
26497
  }
25982
26498
  var init_buildCompletion = __esm({
@@ -26165,6 +26681,8 @@ var init_promoteMember = __esm({
26165
26681
  var council_exports = {};
26166
26682
  __export(council_exports, {
26167
26683
  AGENT_ROLES: () => AGENT_ROLES,
26684
+ CLARIFICATION_PROTOCOL_MODULE: () => CLARIFICATION_PROTOCOL_MODULE,
26685
+ CODING_PRACTICES_MODULE: () => CODING_PRACTICES_MODULE,
26168
26686
  COLLABORATION_DIRECTIVE: () => COLLABORATION_DIRECTIVE,
26169
26687
  COMPOSITOR_ONLY_PROPS: () => COMPOSITOR_ONLY_PROPS,
26170
26688
  DEFAULT_NFR_SPEC: () => DEFAULT_NFR_SPEC,
@@ -26181,6 +26699,7 @@ __export(council_exports, {
26181
26699
  MAX_DELIVERY_ATTEMPTS: () => MAX_DELIVERY_ATTEMPTS,
26182
26700
  MAX_RETRY_PER_MEMBER: () => MAX_RETRY_PER_MEMBER,
26183
26701
  MUTATING_PROJECT_TOOLS: () => MUTATING_PROJECT_TOOLS,
26702
+ NATIVE_TOOL_PROTOCOL_MODULE: () => NATIVE_TOOL_PROTOCOL_MODULE,
26184
26703
  NFR_KEYWORDS: () => NFR_KEYWORDS,
26185
26704
  NON_RETRY_AGENTS: () => NON_RETRY_AGENTS,
26186
26705
  OUTPUT_QUALITY_DIRECTIVE: () => OUTPUT_QUALITY_DIRECTIVE,
@@ -26250,6 +26769,7 @@ __export(council_exports, {
26250
26769
  renderSkillMarkdown: () => renderSkillMarkdown,
26251
26770
  replayChairmanTextTools: () => replayChairmanTextTools,
26252
26771
  resolveCouncilRunMode: () => resolveCouncilRunMode,
26772
+ resolveRoleSystemPrompt: () => resolveRoleSystemPrompt,
26253
26773
  resolveVerifyRetryTool: () => resolveVerifyRetryTool,
26254
26774
  restrictImplementationWrites: () => restrictImplementationWrites,
26255
26775
  runChairmanDeliveryLoop: () => runChairmanDeliveryLoop,
@@ -26385,9 +26905,11 @@ var conversationContext_exports = {};
26385
26905
  __export(conversationContext_exports, {
26386
26906
  _resetConversationContextForTests: () => _resetConversationContextForTests,
26387
26907
  appendMessages: () => appendMessages,
26908
+ buildCouncilTaskWithHistory: () => buildCouncilTaskWithHistory,
26388
26909
  clearHistory: () => clearHistory,
26389
26910
  compactInPlace: () => compactInPlace,
26390
26911
  formatHistoryForCouncil: () => formatHistoryForCouncil,
26912
+ formatHistoryMessages: () => formatHistoryMessages,
26391
26913
  getHistory: () => getHistory,
26392
26914
  getLastClarification: () => getLastClarification,
26393
26915
  hydrateHistory: () => hydrateHistory,
@@ -26448,23 +26970,56 @@ User's answer: ${picked}
26448
26970
  Proceed using this answer; do not re-ask the same question unless the answer is still ambiguous.`;
26449
26971
  }
26450
26972
  function formatHistoryForCouncil(maxTurns = 4) {
26451
- if (history.length === 0) return "";
26452
- const lines = [];
26973
+ return formatHistoryMessages(history, maxTurns);
26974
+ }
26975
+ function formatHistoryMessages(messages, maxTurns = 6, maxTotalChars = 12e3) {
26976
+ if (messages.length === 0) return "";
26453
26977
  let turns = 0;
26454
26978
  const chunk = [];
26455
- for (let i = history.length - 1; i >= 0 && turns < maxTurns; i--) {
26456
- const m = history[i];
26979
+ for (let i = messages.length - 1; i >= 0 && turns < maxTurns; i--) {
26980
+ const m = messages[i];
26457
26981
  if (m.role === "user") {
26458
- chunk.push(`User: ${truncate(m.content, 400)}`);
26982
+ chunk.push(`User: ${truncate(m.content, 800)}`);
26459
26983
  turns += 1;
26460
26984
  } else if (m.role === "assistant" && m.content.trim()) {
26461
- chunk.push(`Assistant: ${truncate(m.content, 600)}`);
26985
+ chunk.push(`Assistant: ${truncate(m.content, 2e3)}`);
26462
26986
  }
26463
26987
  }
26464
26988
  if (chunk.length === 0) return "";
26465
- lines.push("## Prior conversation (rolling context)");
26466
- lines.push(...chunk.reverse());
26467
- return lines.join("\n");
26989
+ let body = ["## Prior conversation (rolling context)", ...chunk.reverse()].join(
26990
+ "\n"
26991
+ );
26992
+ if (body.length > maxTotalChars) {
26993
+ body = `\u2026
26994
+ ${body.slice(body.length - maxTotalChars)}`;
26995
+ }
26996
+ return body;
26997
+ }
26998
+ function buildCouncilTaskWithHistory(task, prior) {
26999
+ const messages = prior ?? [];
27000
+ const trimmed = task.trim();
27001
+ let userPart = maybeAnchorShortAnswer(task) ?? task;
27002
+ if (messages.length > 0 && (SHORT_CONTINUE.test(trimmed) || trimmed.length <= 40 && !trimmed.includes("\n"))) {
27003
+ const lastAsst = [...messages].reverse().find((m) => m.role === "assistant" && m.content.trim());
27004
+ if (lastAsst && SHORT_CONTINUE.test(trimmed)) {
27005
+ userPart = `The user says "${trimmed}" \u2014 continue from the prior conversation. Do NOT restart from zero, do NOT re-ask for the overall goal, and do NOT ignore the prior plan/risks/decisions.
27006
+
27007
+ ## Prior assistant output (authoritative context)
27008
+ ${truncate(lastAsst.content, 4500)}
27009
+
27010
+ ## Instruction
27011
+ Proceed with the next concrete steps implied by that context (implementation when the work phase is build; otherwise the next planned actions).`;
27012
+ }
27013
+ }
27014
+ const block = formatHistoryMessages(messages, 6, 12e3);
27015
+ if (!block) return userPart;
27016
+ if (userPart.includes("Prior assistant output")) {
27017
+ return userPart;
27018
+ }
27019
+ return `${block}
27020
+
27021
+ ## Current user request
27022
+ ${userPart}`;
26468
27023
  }
26469
27024
  function truncate(s, max) {
26470
27025
  const t = s.replace(/\s+/g, " ").trim();
@@ -26475,13 +27030,14 @@ function _resetConversationContextForTests() {
26475
27030
  history = [];
26476
27031
  lastClarification = null;
26477
27032
  }
26478
- var history, lastClarification;
27033
+ var history, lastClarification, SHORT_CONTINUE;
26479
27034
  var init_conversationContext = __esm({
26480
27035
  "src/cli/hooks/conversationContext.ts"() {
26481
27036
  "use strict";
26482
27037
  init_historyCompaction();
26483
27038
  history = [];
26484
27039
  lastClarification = null;
27040
+ SHORT_CONTINUE = /^(procedi|continua|continue|go\s*ahead|go|ok|okay|sì|si|yes|vai|avanti|next|proceed)$/i;
26485
27041
  }
26486
27042
  });
26487
27043
 
@@ -26562,20 +27118,20 @@ var init_phase = __esm({
26562
27118
 
26563
27119
  // src/cli/workspace/paths.ts
26564
27120
  import {
26565
- mkdirSync as mkdirSync6,
26566
- writeFileSync as writeFileSync10,
26567
- existsSync as existsSync13,
27121
+ mkdirSync as mkdirSync7,
27122
+ writeFileSync as writeFileSync11,
27123
+ existsSync as existsSync14,
26568
27124
  accessSync,
26569
27125
  constants,
26570
27126
  realpathSync
26571
27127
  } from "node:fs";
26572
- import { join as join11, basename } from "node:path";
26573
- import { homedir as homedir4 } from "node:os";
27128
+ import { join as join12, basename } from "node:path";
27129
+ import { homedir as homedir5 } from "node:os";
26574
27130
  import { createHash as createHash2 } from "node:crypto";
26575
27131
  function resolveWorkspaceRoot(projectRoot = process.cwd()) {
26576
27132
  const candidates = [
26577
- join11(projectRoot, ".zelari"),
26578
- join11(homedir4(), ".zelari-code", "workspace", hashProject(projectRoot))
27133
+ join12(projectRoot, ".zelari"),
27134
+ join12(homedir5(), ".zelari-code", "workspace", hashProject(projectRoot))
26579
27135
  ];
26580
27136
  for (const candidate of candidates) {
26581
27137
  if (isWritableDir(projectRoot) || candidate !== candidates[0]) {
@@ -26591,7 +27147,7 @@ function hashProject(projectPath) {
26591
27147
  }
26592
27148
  function isWritableDir(dir) {
26593
27149
  try {
26594
- if (!existsSync13(dir)) return false;
27150
+ if (!existsSync14(dir)) return false;
26595
27151
  accessSync(dir, constants.W_OK);
26596
27152
  return true;
26597
27153
  } catch {
@@ -26599,26 +27155,26 @@ function isWritableDir(dir) {
26599
27155
  }
26600
27156
  }
26601
27157
  function ensureWorkspaceDir(workspaceDir) {
26602
- mkdirSync6(workspaceDir, { recursive: true });
26603
- if (workspaceDir.endsWith("/.zelari") && existsSync13(join11(workspaceDir, "..", ".git"))) {
26604
- const gitignorePath = join11(workspaceDir, ".gitignore");
26605
- if (!existsSync13(gitignorePath)) {
26606
- writeFileSync10(gitignorePath, "*\n!.gitignore\n");
27158
+ mkdirSync7(workspaceDir, { recursive: true });
27159
+ if (workspaceDir.endsWith("/.zelari") && existsSync14(join12(workspaceDir, "..", ".git"))) {
27160
+ const gitignorePath = join12(workspaceDir, ".gitignore");
27161
+ if (!existsSync14(gitignorePath)) {
27162
+ writeFileSync11(gitignorePath, "*\n!.gitignore\n");
26607
27163
  }
26608
27164
  }
26609
27165
  }
26610
27166
  function workspaceFile(rootDir, kind) {
26611
27167
  switch (kind) {
26612
27168
  case "plan":
26613
- return join11(rootDir, "plan.md");
27169
+ return join12(rootDir, "plan.md");
26614
27170
  case "risks":
26615
- return join11(rootDir, "risks.md");
27171
+ return join12(rootDir, "risks.md");
26616
27172
  case "index":
26617
- return join11(rootDir, "workspace.json");
27173
+ return join12(rootDir, "workspace.json");
26618
27174
  }
26619
27175
  }
26620
27176
  function workspaceArtifact(rootDir, subdir, slug) {
26621
- return join11(rootDir, subdir, `${slug}.md`);
27177
+ return join12(rootDir, subdir, `${slug}.md`);
26622
27178
  }
26623
27179
  function projectName(projectRoot = process.cwd()) {
26624
27180
  return basename(realpathSync(projectRoot));
@@ -26637,8 +27193,8 @@ __export(workspaceSummary_exports, {
26637
27193
  buildWorkspaceSummary: () => buildWorkspaceSummary,
26638
27194
  buildZelariReadHint: () => buildZelariReadHint
26639
27195
  });
26640
- import { existsSync as existsSync14, readFileSync as readFileSync14, readdirSync, statSync as statSync3 } from "node:fs";
26641
- import { join as join12, relative } from "node:path";
27196
+ import { existsSync as existsSync15, readFileSync as readFileSync15, readdirSync, statSync as statSync3 } from "node:fs";
27197
+ import { join as join13, relative } from "node:path";
26642
27198
  function buildWorkspaceSummary(projectRoot = process.cwd(), options = {}) {
26643
27199
  const { maxEntries = 30 } = options;
26644
27200
  const name = safeProjectName(projectRoot);
@@ -26665,11 +27221,11 @@ function formatTaskLine(t) {
26665
27221
  }
26666
27222
  function buildPlanSummary(projectRoot = process.cwd(), options) {
26667
27223
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
26668
- const planPath = join12(zelariRoot, "plan.json");
26669
- if (!existsSync14(planPath)) return null;
27224
+ const planPath = join13(zelariRoot, "plan.json");
27225
+ if (!existsSync15(planPath)) return null;
26670
27226
  let plan;
26671
27227
  try {
26672
- plan = JSON.parse(readFileSync14(planPath, "utf8"));
27228
+ plan = JSON.parse(readFileSync15(planPath, "utf8"));
26673
27229
  } catch {
26674
27230
  return null;
26675
27231
  }
@@ -26798,8 +27354,8 @@ function pickNextTask(open) {
26798
27354
  )[0];
26799
27355
  }
26800
27356
  function buildZelariReadHint(projectRoot = process.cwd()) {
26801
- const planPath = join12(resolveWorkspaceRoot(projectRoot), "plan.json");
26802
- if (!existsSync14(planPath)) return "";
27357
+ const planPath = join13(resolveWorkspaceRoot(projectRoot), "plan.json");
27358
+ if (!existsSync15(planPath)) return "";
26803
27359
  return [
26804
27360
  "# Council workspace detected (.zelari/)",
26805
27361
  "This project has a council workspace: .zelari/plan.json holds the plan (phases, tasks, milestones) and .zelari/plan-tasks/ holds one detail file per task.",
@@ -26814,10 +27370,10 @@ function safeProjectName(root) {
26814
27370
  }
26815
27371
  }
26816
27372
  function readPackageJson(projectRoot) {
26817
- const p3 = join12(projectRoot, "package.json");
26818
- if (!existsSync14(p3)) return null;
27373
+ const p3 = join13(projectRoot, "package.json");
27374
+ if (!existsSync15(p3)) return null;
26819
27375
  try {
26820
- return JSON.parse(readFileSync14(p3, "utf8"));
27376
+ return JSON.parse(readFileSync15(p3, "utf8"));
26821
27377
  } catch {
26822
27378
  return null;
26823
27379
  }
@@ -26851,11 +27407,11 @@ function listShallow(projectRoot, maxEntries) {
26851
27407
  out.push(`\u2026 (+${top.length - count} more)`);
26852
27408
  break;
26853
27409
  }
26854
- const rel2 = relative(projectRoot, join12(projectRoot, entry.name));
27410
+ const rel2 = relative(projectRoot, join13(projectRoot, entry.name));
26855
27411
  if (entry.isDirectory()) {
26856
27412
  let inner = "";
26857
27413
  try {
26858
- const sub = readdirSync(join12(projectRoot, entry.name), {
27414
+ const sub = readdirSync(join13(projectRoot, entry.name), {
26859
27415
  withFileTypes: true
26860
27416
  }).filter((e) => !e.name.startsWith(".")).slice(0, 4).map((e) => e.name);
26861
27417
  if (sub.length > 0)
@@ -26906,14 +27462,14 @@ __export(storage_exports, {
26906
27462
  workspaceMutex: () => workspaceMutex
26907
27463
  });
26908
27464
  import {
26909
- readFileSync as readFileSync15,
26910
- writeFileSync as writeFileSync11,
26911
- existsSync as existsSync15,
26912
- mkdirSync as mkdirSync7,
27465
+ readFileSync as readFileSync16,
27466
+ writeFileSync as writeFileSync12,
27467
+ existsSync as existsSync16,
27468
+ mkdirSync as mkdirSync8,
26913
27469
  readdirSync as readdirSync2,
26914
27470
  renameSync as renameSync2
26915
27471
  } from "node:fs";
26916
- import { dirname as dirname2, join as join13 } from "node:path";
27472
+ import { dirname as dirname3, join as join14 } from "node:path";
26917
27473
  function parseFrontmatter(md) {
26918
27474
  const m = FRONTMATTER_RE.exec(md);
26919
27475
  if (!m) return { meta: {}, body: md };
@@ -27168,15 +27724,15 @@ var init_storage = __esm({
27168
27724
  Storage = class {
27169
27725
  /** Read a Markdown file with frontmatter. Throws if not found. */
27170
27726
  read(path36) {
27171
- if (!existsSync15(path36)) {
27727
+ if (!existsSync16(path36)) {
27172
27728
  throw new Error(`File not found: ${path36}`);
27173
27729
  }
27174
- const md = readFileSync15(path36, "utf8");
27730
+ const md = readFileSync16(path36, "utf8");
27175
27731
  return parseFrontmatter(md);
27176
27732
  }
27177
27733
  /** Read a Markdown file; returns null if not found. */
27178
27734
  readIfExists(path36) {
27179
- if (!existsSync15(path36)) return null;
27735
+ if (!existsSync16(path36)) return null;
27180
27736
  return this.read(path36);
27181
27737
  }
27182
27738
  /**
@@ -27184,16 +27740,16 @@ var init_storage = __esm({
27184
27740
  * The meta object is serialized as YAML frontmatter; body as Markdown.
27185
27741
  */
27186
27742
  write(path36, meta3, body) {
27187
- mkdirSync7(dirname2(path36), { recursive: true });
27743
+ mkdirSync8(dirname3(path36), { recursive: true });
27188
27744
  const tmp = path36 + ".tmp-" + process.pid;
27189
27745
  const md = serializeFrontmatter(meta3, body);
27190
- writeFileSync11(tmp, md, "utf8");
27746
+ writeFileSync12(tmp, md, "utf8");
27191
27747
  renameSync2(tmp, path36);
27192
27748
  }
27193
27749
  /** List all .md files in a directory (non-recursive). */
27194
27750
  listMarkdown(dir) {
27195
- if (!existsSync15(dir)) return [];
27196
- return readdirSync2(dir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).map((f) => join13(dir, f));
27751
+ if (!existsSync16(dir)) return [];
27752
+ return readdirSync2(dir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).map((f) => join14(dir, f));
27197
27753
  }
27198
27754
  };
27199
27755
  KeyedMutex = class {
@@ -27232,14 +27788,14 @@ __export(stubs_exports, {
27232
27788
  resolveWorkspaceRoot: () => resolveWorkspaceRoot
27233
27789
  });
27234
27790
  import {
27235
- existsSync as existsSync16,
27791
+ existsSync as existsSync17,
27236
27792
  readdirSync as readdirSync3,
27237
- writeFileSync as writeFileSync12,
27238
- readFileSync as readFileSync16,
27239
- mkdirSync as mkdirSync8,
27793
+ writeFileSync as writeFileSync13,
27794
+ readFileSync as readFileSync17,
27795
+ mkdirSync as mkdirSync9,
27240
27796
  renameSync as renameSync3
27241
27797
  } from "node:fs";
27242
- import { join as join14, basename as basename2, dirname as dirname3, relative as relative2 } from "node:path";
27798
+ import { join as join15, basename as basename2, dirname as dirname4, relative as relative2 } from "node:path";
27243
27799
  function createWorkspaceContext(projectRoot = process.cwd()) {
27244
27800
  const rootDir = resolveWorkspaceRoot(projectRoot);
27245
27801
  return {
@@ -27249,14 +27805,14 @@ function createWorkspaceContext(projectRoot = process.cwd()) {
27249
27805
  };
27250
27806
  }
27251
27807
  function planJsonPath(ctx) {
27252
- return join14(ctx.rootDir, "plan.json");
27808
+ return join15(ctx.rootDir, "plan.json");
27253
27809
  }
27254
27810
  function readPlan(ctx) {
27255
27811
  const jsonPath = planJsonPath(ctx);
27256
- if (existsSync16(jsonPath)) {
27812
+ if (existsSync17(jsonPath)) {
27257
27813
  try {
27258
27814
  const parsed = JSON.parse(
27259
- readFileSync16(jsonPath, "utf8")
27815
+ readFileSync17(jsonPath, "utf8")
27260
27816
  );
27261
27817
  return {
27262
27818
  phases: Array.isArray(parsed.phases) ? parsed.phases : [],
@@ -27278,9 +27834,9 @@ function readPlan(ctx) {
27278
27834
  }
27279
27835
  function writePlan(ctx, summary) {
27280
27836
  const jsonPath = planJsonPath(ctx);
27281
- mkdirSync8(dirname3(jsonPath), { recursive: true });
27837
+ mkdirSync9(dirname4(jsonPath), { recursive: true });
27282
27838
  const tmp = jsonPath + ".tmp-" + process.pid;
27283
- writeFileSync12(tmp, JSON.stringify(summary, null, 2), "utf8");
27839
+ writeFileSync13(tmp, JSON.stringify(summary, null, 2), "utf8");
27284
27840
  renameSync3(tmp, jsonPath);
27285
27841
  const mdPath = workspaceFile(ctx.rootDir, "plan");
27286
27842
  const summaryMeta = {
@@ -27347,8 +27903,8 @@ function renderPlanBody(summary) {
27347
27903
  return lines.join("\n");
27348
27904
  }
27349
27905
  function nextAdrId(ctx) {
27350
- const decisionsDir = join14(ctx.rootDir, "decisions");
27351
- if (!existsSync16(decisionsDir)) return "001";
27906
+ const decisionsDir = join15(ctx.rootDir, "decisions");
27907
+ if (!existsSync17(decisionsDir)) return "001";
27352
27908
  const existing = readdirSync3(decisionsDir).filter((f) => f.endsWith(".md")).map((f) => f.match(/^(\d+)-/)).filter((m) => !!m).map((m) => parseInt(m[1], 10));
27353
27909
  const max = existing.length === 0 ? 0 : Math.max(...existing);
27354
27910
  return String(max + 1).padStart(3, "0");
@@ -27390,7 +27946,7 @@ function addTaskRecord(ctx, summary, phaseId, t, options) {
27390
27946
  status: "pending",
27391
27947
  priority: t.priority
27392
27948
  });
27393
- const taskPath = join14(ctx.rootDir, "plan-tasks", `${id}.md`);
27949
+ const taskPath = join15(ctx.rootDir, "plan-tasks", `${id}.md`);
27394
27950
  const meta3 = {
27395
27951
  kind: "task",
27396
27952
  id,
@@ -27432,7 +27988,7 @@ function addMilestoneRecord(ctx, summary, input) {
27432
27988
  dueDate: input.dueDate,
27433
27989
  targetVersion: version2
27434
27990
  });
27435
- const path36 = join14(ctx.rootDir, "milestones", `${id}.md`);
27991
+ const path36 = join15(ctx.rootDir, "milestones", `${id}.md`);
27436
27992
  const meta3 = {
27437
27993
  kind: "milestone",
27438
27994
  id,
@@ -27730,8 +28286,8 @@ function createNfrSpecStub(ctx) {
27730
28286
  },
27731
28287
  planFeatureKeywords: Array.isArray(args["planFeatureKeywords"]) ? args["planFeatureKeywords"] : void 0
27732
28288
  };
27733
- const outPath = join14(ctx.rootDir, "nfr-spec.json");
27734
- writeFileSync12(outPath, JSON.stringify(spec, null, 2), "utf8");
28289
+ const outPath = join15(ctx.rootDir, "nfr-spec.json");
28290
+ writeFileSync13(outPath, JSON.stringify(spec, null, 2), "utf8");
27735
28291
  return `NFR spec written to nfr-spec.json (${targets.length} target(s)).`;
27736
28292
  });
27737
28293
  }
@@ -27794,18 +28350,18 @@ function searchDocumentsStub(ctx) {
27794
28350
  (w) => w.length >= 3 && w !== "or" && w !== "and" && w !== "the"
27795
28351
  );
27796
28352
  const files = [
27797
- ...ctx.storage.listMarkdown(join14(ctx.rootDir, "decisions")),
27798
- ...ctx.storage.listMarkdown(join14(ctx.rootDir, "docs")),
27799
- ...ctx.storage.listMarkdown(join14(ctx.rootDir, "reviews")),
27800
- ...ctx.storage.listMarkdown(join14(ctx.rootDir, "plan-tasks")),
27801
- ...ctx.storage.listMarkdown(join14(ctx.rootDir, "milestones")),
28353
+ ...ctx.storage.listMarkdown(join15(ctx.rootDir, "decisions")),
28354
+ ...ctx.storage.listMarkdown(join15(ctx.rootDir, "docs")),
28355
+ ...ctx.storage.listMarkdown(join15(ctx.rootDir, "reviews")),
28356
+ ...ctx.storage.listMarkdown(join15(ctx.rootDir, "plan-tasks")),
28357
+ ...ctx.storage.listMarkdown(join15(ctx.rootDir, "milestones")),
27802
28358
  workspaceFile(ctx.rootDir, "plan"),
27803
28359
  workspaceFile(ctx.rootDir, "risks")
27804
28360
  ];
27805
28361
  const results = [];
27806
28362
  for (const file2 of files) {
27807
- if (!existsSync16(file2)) continue;
27808
- const raw = readFileSync16(file2, "utf8");
28363
+ if (!existsSync17(file2)) continue;
28364
+ const raw = readFileSync17(file2, "utf8");
27809
28365
  const content = raw.toLowerCase();
27810
28366
  let idx = -1;
27811
28367
  let matchLen = 0;
@@ -27856,9 +28412,9 @@ function linkDocumentsStub(ctx) {
27856
28412
  const toId = args["toId"] ?? args["targetId"] ?? args["targetPathOrTitle"];
27857
28413
  if (!fromId || !toId) return "linkDocuments requires fromId and toId.";
27858
28414
  const allFiles = [
27859
- ...ctx.storage.listMarkdown(join14(ctx.rootDir, "decisions")),
27860
- ...ctx.storage.listMarkdown(join14(ctx.rootDir, "docs")),
27861
- ...ctx.storage.listMarkdown(join14(ctx.rootDir, "reviews"))
28415
+ ...ctx.storage.listMarkdown(join15(ctx.rootDir, "decisions")),
28416
+ ...ctx.storage.listMarkdown(join15(ctx.rootDir, "docs")),
28417
+ ...ctx.storage.listMarkdown(join15(ctx.rootDir, "reviews"))
27862
28418
  ];
27863
28419
  const source = allFiles.find((f) => {
27864
28420
  try {
@@ -27889,9 +28445,9 @@ function getDocumentBacklinksStub(ctx) {
27889
28445
  const targetId = args["targetId"] ?? args["id"];
27890
28446
  if (!targetId) return "getDocumentBacklinks requires targetId.";
27891
28447
  const allFiles = [
27892
- ...ctx.storage.listMarkdown(join14(ctx.rootDir, "decisions")),
27893
- ...ctx.storage.listMarkdown(join14(ctx.rootDir, "docs")),
27894
- ...ctx.storage.listMarkdown(join14(ctx.rootDir, "reviews"))
28448
+ ...ctx.storage.listMarkdown(join15(ctx.rootDir, "decisions")),
28449
+ ...ctx.storage.listMarkdown(join15(ctx.rootDir, "docs")),
28450
+ ...ctx.storage.listMarkdown(join15(ctx.rootDir, "reviews"))
27895
28451
  ];
27896
28452
  const backlinks = [];
27897
28453
  for (const file2 of allFiles) {
@@ -27985,8 +28541,8 @@ __export(updater_exports, {
27985
28541
  resolveBundledNpmCli: () => resolveBundledNpmCli
27986
28542
  });
27987
28543
  import { createRequire as createRequire2 } from "node:module";
27988
- import { spawn as spawn4 } from "node:child_process";
27989
- import { existsSync as existsSync17 } from "node:fs";
28544
+ import { spawn as spawn5 } from "node:child_process";
28545
+ import { existsSync as existsSync18 } from "node:fs";
27990
28546
  import path23 from "node:path";
27991
28547
  import { fileURLToPath } from "node:url";
27992
28548
  function resolveBundledNpmCli(execPath = process.execPath) {
@@ -27999,7 +28555,7 @@ function resolveBundledNpmCli(execPath = process.execPath) {
27999
28555
  ];
28000
28556
  for (const candidate of candidates) {
28001
28557
  try {
28002
- if (existsSync17(candidate)) return candidate;
28558
+ if (existsSync18(candidate)) return candidate;
28003
28559
  } catch {
28004
28560
  }
28005
28561
  }
@@ -28072,7 +28628,7 @@ async function checkForUpdate(fetcher = fetch, registryUrl) {
28072
28628
  updateAvailable: cmp < 0
28073
28629
  };
28074
28630
  }
28075
- async function performUpdate(packageName = "zelari-code", executor = spawn4, resolveNpmCli = resolveBundledNpmCli) {
28631
+ async function performUpdate(packageName = "zelari-code", executor = spawn5, resolveNpmCli = resolveBundledNpmCli) {
28076
28632
  const args = ["install", "-g", `${packageName}@latest`];
28077
28633
  const primary = await runNpm(executor, args, "shim");
28078
28634
  if (primary.ok) return primary;
@@ -28130,7 +28686,7 @@ var init_updater = __esm({
28130
28686
  });
28131
28687
 
28132
28688
  // src/cli/mcp/mcpClient.ts
28133
- import { spawn as spawn5 } from "node:child_process";
28689
+ import { spawn as spawn6 } from "node:child_process";
28134
28690
  var DEFAULT_REQUEST_TIMEOUT_MS, INIT_TIMEOUT_MS, MCP_PROTOCOL_VERSION, McpClient;
28135
28691
  var init_mcpClient = __esm({
28136
28692
  "src/cli/mcp/mcpClient.ts"() {
@@ -28158,10 +28714,10 @@ var init_mcpClient = __esm({
28158
28714
  env: { ...process.env, ...this.config.env ?? {} },
28159
28715
  windowsHide: true
28160
28716
  };
28161
- const child = process.platform === "win32" ? spawn5(buildCmdLine(this.config.command, this.config.args ?? []), {
28717
+ const child = process.platform === "win32" ? spawn6(buildCmdLine(this.config.command, this.config.args ?? []), {
28162
28718
  ...spawnOpts,
28163
28719
  shell: true
28164
- }) : spawn5(this.config.command, this.config.args ?? [], spawnOpts);
28720
+ }) : spawn6(this.config.command, this.config.args ?? [], spawnOpts);
28165
28721
  this.child = child;
28166
28722
  child.stdout.setEncoding("utf8");
28167
28723
  child.stdout.on("data", (chunk) => this.onStdout(chunk));
@@ -28305,20 +28861,20 @@ __export(mcpManager_exports, {
28305
28861
  readMcpConfig: () => readMcpConfig,
28306
28862
  registerMcpTools: () => registerMcpTools
28307
28863
  });
28308
- import { existsSync as existsSync18, readFileSync as readFileSync17 } from "node:fs";
28309
- import { join as join15 } from "node:path";
28310
- import { homedir as homedir5 } from "node:os";
28864
+ import { existsSync as existsSync19, readFileSync as readFileSync18 } from "node:fs";
28865
+ import { join as join16 } from "node:path";
28866
+ import { homedir as homedir6 } from "node:os";
28311
28867
  function readMcpConfig(projectRoot = process.cwd()) {
28312
28868
  const merged = {};
28313
28869
  const paths = [
28314
- join15(homedir5(), ".zelari-code", "mcp.json"),
28315
- join15(projectRoot, ".zelari", "mcp.json")
28870
+ join16(homedir6(), ".zelari-code", "mcp.json"),
28871
+ join16(projectRoot, ".zelari", "mcp.json")
28316
28872
  // later = higher precedence
28317
28873
  ];
28318
28874
  for (const p3 of paths) {
28319
- if (!existsSync18(p3)) continue;
28875
+ if (!existsSync19(p3)) continue;
28320
28876
  try {
28321
- const parsed = JSON.parse(readFileSync17(p3, "utf8"));
28877
+ const parsed = JSON.parse(readFileSync18(p3, "utf8"));
28322
28878
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
28323
28879
  if (!cfg || typeof cfg.command !== "string" || cfg.command.length === 0) continue;
28324
28880
  merged[name] = cfg;
@@ -28407,6 +28963,53 @@ var init_mcpManager = __esm({
28407
28963
  }
28408
28964
  });
28409
28965
 
28966
+ // src/cli/workspace/projectInstructions.ts
28967
+ var projectInstructions_exports = {};
28968
+ __export(projectInstructions_exports, {
28969
+ loadProjectInstructions: () => loadProjectInstructions
28970
+ });
28971
+ import { existsSync as existsSync20, readFileSync as readFileSync19 } from "node:fs";
28972
+ import { join as join17 } from "node:path";
28973
+ function loadProjectInstructions(projectRoot = process.cwd(), maxChars = MAX_CHARS) {
28974
+ for (const name of CANDIDATES) {
28975
+ const full = join17(projectRoot, name);
28976
+ if (!existsSync20(full)) continue;
28977
+ try {
28978
+ let raw = readFileSync19(full, "utf8");
28979
+ raw = raw.replace(/\r\n/g, "\n").trim();
28980
+ if (!raw) continue;
28981
+ if (raw.length <= maxChars) {
28982
+ return { path: full, content: raw, truncated: false };
28983
+ }
28984
+ return {
28985
+ path: full,
28986
+ content: raw.slice(0, maxChars) + `
28987
+
28988
+ \u2026 [truncated; full file is ${raw.length} chars at ${name}]`,
28989
+ truncated: true
28990
+ };
28991
+ } catch {
28992
+ continue;
28993
+ }
28994
+ }
28995
+ return { path: null, content: "", truncated: false };
28996
+ }
28997
+ var CANDIDATES, MAX_CHARS;
28998
+ var init_projectInstructions = __esm({
28999
+ "src/cli/workspace/projectInstructions.ts"() {
29000
+ "use strict";
29001
+ CANDIDATES = [
29002
+ "AGENTS.md",
29003
+ "Agents.md",
29004
+ "agents.md",
29005
+ "CLAUDE.md",
29006
+ "Claude.md",
29007
+ "claude.md"
29008
+ ];
29009
+ MAX_CHARS = 8e3;
29010
+ }
29011
+ });
29012
+
28410
29013
  // src/cli/councilConfig.ts
28411
29014
  function resolveCouncilTier(opts) {
28412
29015
  const env = opts?.env ?? process.env;
@@ -28450,13 +29053,13 @@ var planDetect_exports = {};
28450
29053
  __export(planDetect_exports, {
28451
29054
  hasWorkspacePlan: () => hasWorkspacePlan
28452
29055
  });
28453
- import { existsSync as existsSync19, readFileSync as readFileSync18 } from "node:fs";
28454
- import { join as join16 } from "node:path";
29056
+ import { existsSync as existsSync21, readFileSync as readFileSync20 } from "node:fs";
29057
+ import { join as join18 } from "node:path";
28455
29058
  function hasWorkspacePlan(projectRoot = process.cwd()) {
28456
- const planPath = join16(resolveWorkspaceRoot(projectRoot), "plan.json");
28457
- if (!existsSync19(planPath)) return false;
29059
+ const planPath = join18(resolveWorkspaceRoot(projectRoot), "plan.json");
29060
+ if (!existsSync21(planPath)) return false;
28458
29061
  try {
28459
- const parsed = JSON.parse(readFileSync18(planPath, "utf8"));
29062
+ const parsed = JSON.parse(readFileSync20(planPath, "utf8"));
28460
29063
  return Array.isArray(parsed.phases) && parsed.phases.length > 0;
28461
29064
  } catch {
28462
29065
  return false;
@@ -28560,13 +29163,13 @@ __export(agentsMd_exports, {
28560
29163
  serializeAgentsMd: () => serializeAgentsMd,
28561
29164
  updateAgentsMd: () => updateAgentsMd
28562
29165
  });
28563
- import { existsSync as existsSync20, readFileSync as readFileSync19, writeFileSync as writeFileSync13 } from "node:fs";
29166
+ import { existsSync as existsSync22, readFileSync as readFileSync21, writeFileSync as writeFileSync14 } from "node:fs";
28564
29167
  import { createHash as createHash3 } from "node:crypto";
28565
- import { join as join17 } from "node:path";
29168
+ import { join as join19 } from "node:path";
28566
29169
  import { readFile as readFile2 } from "node:fs/promises";
28567
29170
  async function readPackageJson2(projectRoot) {
28568
- const path36 = join17(projectRoot, "package.json");
28569
- if (!existsSync20(path36)) return null;
29171
+ const path36 = join19(projectRoot, "package.json");
29172
+ if (!existsSync22(path36)) return null;
28570
29173
  try {
28571
29174
  return JSON.parse(await readFile2(path36, "utf8"));
28572
29175
  } catch {
@@ -28590,8 +29193,8 @@ async function genTechStack(ctx) {
28590
29193
  ].join("\n");
28591
29194
  }
28592
29195
  async function genDecisions(ctx) {
28593
- const decisionsDir = join17(ctx.rootDir, "decisions");
28594
- if (!existsSync20(decisionsDir)) return "_No ADRs yet._";
29196
+ const decisionsDir = join19(ctx.rootDir, "decisions");
29197
+ if (!existsSync22(decisionsDir)) return "_No ADRs yet._";
28595
29198
  const files = ctx.storage.listMarkdown(decisionsDir).sort();
28596
29199
  const accepted = [];
28597
29200
  const proposed = [];
@@ -28616,9 +29219,9 @@ async function genDecisions(ctx) {
28616
29219
  }
28617
29220
  async function genConventions(ctx) {
28618
29221
  const lines = [];
28619
- const claudeMd = join17(ctx.projectRoot, "CLAUDE.MD");
28620
- if (existsSync20(claudeMd)) {
28621
- const content = readFileSync19(claudeMd, "utf8");
29222
+ const claudeMd = join19(ctx.projectRoot, "CLAUDE.MD");
29223
+ if (existsSync22(claudeMd)) {
29224
+ const content = readFileSync21(claudeMd, "utf8");
28622
29225
  const match = content.match(/## Architecture rules[\s\S]+?(?=\n## |\n*$)/);
28623
29226
  if (match) {
28624
29227
  lines.push('<!-- Extracted from CLAUDE.MD "Architecture rules" -->');
@@ -28650,9 +29253,9 @@ async function genBuild(ctx) {
28650
29253
  ].join("\n");
28651
29254
  }
28652
29255
  async function genOpenQuestions(ctx) {
28653
- const path36 = join17(ctx.rootDir, "risks.md");
28654
- if (!existsSync20(path36)) return "_No open questions._";
28655
- const content = readFileSync19(path36, "utf8");
29256
+ const path36 = join19(ctx.rootDir, "risks.md");
29257
+ if (!existsSync22(path36)) return "_No open questions._";
29258
+ const content = readFileSync21(path36, "utf8");
28656
29259
  const lines = content.split("\n");
28657
29260
  const questions = [];
28658
29261
  let currentTitle = "";
@@ -28726,9 +29329,9 @@ function titleCase(id) {
28726
29329
  return id.split("-").map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
28727
29330
  }
28728
29331
  async function updateAgentsMd(ctx, projectRoot) {
28729
- const agentsPath = join17(projectRoot, "AGENTS.MD");
28730
- if (existsSync20(agentsPath)) {
28731
- const content = readFileSync19(agentsPath, "utf8");
29332
+ const agentsPath = join19(projectRoot, "AGENTS.MD");
29333
+ if (existsSync22(agentsPath)) {
29334
+ const content = readFileSync21(agentsPath, "utf8");
28732
29335
  const hasAnyMarker = AUTO_SECTIONS.some((id) => content.includes(MARKER_OPEN(id)));
28733
29336
  if (!hasAnyMarker) {
28734
29337
  return {
@@ -28743,8 +29346,8 @@ async function updateAgentsMd(ctx, projectRoot) {
28743
29346
  newSections.set(id, await GENERATORS[id](ctx));
28744
29347
  }
28745
29348
  let manualContent = "";
28746
- if (existsSync20(agentsPath)) {
28747
- const { manualBlocks } = parseAgentsMd(readFileSync19(agentsPath, "utf8"));
29349
+ if (existsSync22(agentsPath)) {
29350
+ const { manualBlocks } = parseAgentsMd(readFileSync21(agentsPath, "utf8"));
28748
29351
  manualContent = manualBlocks.after;
28749
29352
  } else {
28750
29353
  const projectName2 = projectName(projectRoot);
@@ -28760,7 +29363,7 @@ async function updateAgentsMd(ctx, projectRoot) {
28760
29363
  ""
28761
29364
  ].join("\n");
28762
29365
  }
28763
- const oldContent = existsSync20(agentsPath) ? readFileSync19(agentsPath, "utf8") : "";
29366
+ const oldContent = existsSync22(agentsPath) ? readFileSync21(agentsPath, "utf8") : "";
28764
29367
  const { sections: oldSections } = parseAgentsMd(oldContent);
28765
29368
  const changedSections = [];
28766
29369
  for (const id of AUTO_SECTIONS) {
@@ -28772,7 +29375,7 @@ async function updateAgentsMd(ctx, projectRoot) {
28772
29375
  return { changed: false, sections: [] };
28773
29376
  }
28774
29377
  const newContent = serializeAgentsMd(manualContent, newSections);
28775
- writeFileSync13(agentsPath, newContent, "utf8");
29378
+ writeFileSync14(agentsPath, newContent, "utf8");
28776
29379
  return { changed: true, sections: changedSections };
28777
29380
  }
28778
29381
  function hash2(s) {
@@ -28897,9 +29500,9 @@ var init_completeDesign = __esm({
28897
29500
  });
28898
29501
 
28899
29502
  // src/cli/workspace/projectSmoke.ts
28900
- import { spawn as spawn6 } from "node:child_process";
28901
- import { existsSync as existsSync21, readFileSync as readFileSync20 } from "node:fs";
28902
- import { join as join18 } from "node:path";
29503
+ import { spawn as spawn7 } from "node:child_process";
29504
+ import { existsSync as existsSync23, readFileSync as readFileSync22 } from "node:fs";
29505
+ import { join as join20 } from "node:path";
28903
29506
  function pickSmokeScript(scripts) {
28904
29507
  if (!scripts) return null;
28905
29508
  for (const name of SMOKE_SCRIPT_PRIORITY) {
@@ -28911,13 +29514,13 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS) {
28911
29514
  if (process.env["ZELARI_SMOKE"] === "0") {
28912
29515
  return { ran: false, reason: "ZELARI_SMOKE=0 (disabled)" };
28913
29516
  }
28914
- const pkgPath = join18(projectRoot, "package.json");
28915
- if (!existsSync21(pkgPath)) {
29517
+ const pkgPath = join20(projectRoot, "package.json");
29518
+ if (!existsSync23(pkgPath)) {
28916
29519
  return { ran: false, reason: "no package.json (skipped)" };
28917
29520
  }
28918
29521
  let scripts = {};
28919
29522
  try {
28920
- const pkg = JSON.parse(readFileSync20(pkgPath, "utf8"));
29523
+ const pkg = JSON.parse(readFileSync22(pkgPath, "utf8"));
28921
29524
  scripts = pkg.scripts ?? {};
28922
29525
  } catch {
28923
29526
  return { ran: false, reason: "package.json unreadable (skipped)" };
@@ -28927,12 +29530,12 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS) {
28927
29530
  return { ran: false, reason: "no typecheck/test/build script (skipped)" };
28928
29531
  }
28929
29532
  return await new Promise((resolveRun) => {
28930
- const child = process.platform === "win32" ? spawn6(buildCmdLine("npm.cmd", ["run", script]), {
29533
+ const child = process.platform === "win32" ? spawn7(buildCmdLine("npm.cmd", ["run", script]), {
28931
29534
  cwd: projectRoot,
28932
29535
  stdio: ["ignore", "pipe", "pipe"],
28933
29536
  env: process.env,
28934
29537
  shell: true
28935
- }) : spawn6("npm", ["run", script], {
29538
+ }) : spawn7("npm", ["run", script], {
28936
29539
  cwd: projectRoot,
28937
29540
  stdio: ["ignore", "pipe", "pipe"],
28938
29541
  env: process.env
@@ -29004,9 +29607,9 @@ __export(postCouncilHook_exports, {
29004
29607
  runImplementationVerificationHook: () => runImplementationVerificationHook,
29005
29608
  runPostCouncilHook: () => runPostCouncilHook
29006
29609
  });
29007
- import { spawn as spawn7 } from "node:child_process";
29008
- import { existsSync as existsSync22, readFileSync as readFileSync21 } from "node:fs";
29009
- import { join as join19 } from "node:path";
29610
+ import { spawn as spawn8 } from "node:child_process";
29611
+ import { existsSync as existsSync24, readFileSync as readFileSync23 } from "node:fs";
29612
+ import { join as join21 } from "node:path";
29010
29613
  async function runCompleteDesignPostProcessor(ctx, options) {
29011
29614
  if (options?.runMode === "implementation") {
29012
29615
  return {
@@ -29017,9 +29620,9 @@ async function runCompleteDesignPostProcessor(ctx, options) {
29017
29620
  if (process.env["ZELARI_COMPLETE_DESIGN"] === "0") {
29018
29621
  return { ran: false, reason: "ZELARI_COMPLETE_DESIGN=0 (disabled)" };
29019
29622
  }
29020
- const planJsonPath2 = join19(ctx.rootDir, "plan.json");
29021
- const scriptPath = join19(ctx.projectRoot, "complete-design.mjs");
29022
- if (!existsSync22(planJsonPath2)) {
29623
+ const planJsonPath2 = join21(ctx.rootDir, "plan.json");
29624
+ const scriptPath = join21(ctx.projectRoot, "complete-design.mjs");
29625
+ if (!existsSync24(planJsonPath2)) {
29023
29626
  return {
29024
29627
  ran: false,
29025
29628
  reason: ".zelari/plan.json missing (not design-phase)"
@@ -29027,7 +29630,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
29027
29630
  }
29028
29631
  let phaseCount = 0;
29029
29632
  try {
29030
- const parsed = JSON.parse(readFileSync21(planJsonPath2, "utf8"));
29633
+ const parsed = JSON.parse(readFileSync23(planJsonPath2, "utf8"));
29031
29634
  phaseCount = Array.isArray(parsed.phases) ? parsed.phases.length : 0;
29032
29635
  } catch {
29033
29636
  return { ran: false, reason: ".zelari/plan.json corrupt" };
@@ -29035,7 +29638,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
29035
29638
  if (phaseCount === 0) {
29036
29639
  return { ran: false, reason: ".zelari/plan.json has no phases" };
29037
29640
  }
29038
- if (!existsSync22(scriptPath)) {
29641
+ if (!existsSync24(scriptPath)) {
29039
29642
  try {
29040
29643
  const builtin = await runBuiltinCompleteDesign(ctx);
29041
29644
  return {
@@ -29053,7 +29656,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
29053
29656
  }
29054
29657
  }
29055
29658
  return await new Promise((resolveRun) => {
29056
- const child = spawn7(process.execPath, [scriptPath], {
29659
+ const child = spawn8(process.execPath, [scriptPath], {
29057
29660
  cwd: ctx.projectRoot,
29058
29661
  stdio: ["ignore", "pipe", "pipe"],
29059
29662
  env: process.env
@@ -29256,12 +29859,12 @@ var buildLessonsSummary_exports = {};
29256
29859
  __export(buildLessonsSummary_exports, {
29257
29860
  buildLessonsSummary: () => buildLessonsSummary
29258
29861
  });
29259
- import { existsSync as existsSync23 } from "node:fs";
29260
- import { join as join20 } from "node:path";
29862
+ import { existsSync as existsSync25 } from "node:fs";
29863
+ import { join as join22 } from "node:path";
29261
29864
  function buildLessonsSummary(projectRoot = process.cwd(), taskText) {
29262
29865
  if (process.env["ZELARI_LESSONS"] === "0") return null;
29263
29866
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
29264
- if (!existsSync23(join20(zelariRoot, "lessons.jsonl"))) return null;
29867
+ if (!existsSync25(join22(zelariRoot, "lessons.jsonl"))) return null;
29265
29868
  const lessons = recallLessons(zelariRoot, {
29266
29869
  maxLessons: 5,
29267
29870
  maxBytes: 2048,
@@ -29284,10 +29887,10 @@ __export(councilFeedback_exports, {
29284
29887
  });
29285
29888
  import {
29286
29889
  promises as fs13,
29287
- existsSync as existsSync24,
29288
- readFileSync as readFileSync22,
29289
- writeFileSync as writeFileSync14,
29290
- mkdirSync as mkdirSync9
29890
+ existsSync as existsSync26,
29891
+ readFileSync as readFileSync24,
29892
+ writeFileSync as writeFileSync15,
29893
+ mkdirSync as mkdirSync10
29291
29894
  } from "node:fs";
29292
29895
  import path24 from "node:path";
29293
29896
  import os8 from "node:os";
@@ -29393,9 +29996,9 @@ var init_councilFeedback = __esm({
29393
29996
  }
29394
29997
  // --- persistence ---------------------------------------------------------
29395
29998
  load() {
29396
- if (!existsSync24(this.file)) return;
29999
+ if (!existsSync26(this.file)) return;
29397
30000
  try {
29398
- const raw = readFileSync22(this.file, "utf-8");
30001
+ const raw = readFileSync24(this.file, "utf-8");
29399
30002
  const parsed = JSON.parse(raw);
29400
30003
  if (parsed && Array.isArray(parsed.entries)) {
29401
30004
  this.entries = parsed.entries.filter(
@@ -29406,8 +30009,8 @@ var init_councilFeedback = __esm({
29406
30009
  }
29407
30010
  }
29408
30011
  save() {
29409
- mkdirSync9(path24.dirname(this.file), { recursive: true });
29410
- writeFileSync14(
30012
+ mkdirSync10(path24.dirname(this.file), { recursive: true });
30013
+ writeFileSync15(
29411
30014
  this.file,
29412
30015
  JSON.stringify({ entries: this.entries }, null, 2),
29413
30016
  { encoding: "utf-8", mode: 384 }
@@ -29864,8 +30467,8 @@ __export(prereqChecks_exports, {
29864
30467
  runPrereqChecks: () => runPrereqChecks
29865
30468
  });
29866
30469
  import { execSync, spawnSync as spawnSync2 } from "node:child_process";
29867
- import { existsSync as existsSync25 } from "node:fs";
29868
- import { dirname as dirname4 } from "node:path";
30470
+ import { existsSync as existsSync27 } from "node:fs";
30471
+ import { dirname as dirname5 } from "node:path";
29869
30472
  function isWslBashPath2(p3) {
29870
30473
  if (!p3 || typeof p3 !== "string") return false;
29871
30474
  const n = p3.replace(/\//g, "\\").toLowerCase();
@@ -29927,7 +30530,7 @@ function resolveAgentShellSync() {
29927
30530
  function agentProbeEnv() {
29928
30531
  const env = { ...process.env };
29929
30532
  try {
29930
- const nodeDir = dirname4(process.execPath);
30533
+ const nodeDir = dirname5(process.execPath);
29931
30534
  if (!nodeDir) return env;
29932
30535
  const sep = process.platform === "win32" ? ";" : ":";
29933
30536
  const current = env.PATH ?? env.Path ?? "";
@@ -29944,7 +30547,7 @@ function agentProbeEnv() {
29944
30547
  }
29945
30548
  function existsSyncSafe2(p3) {
29946
30549
  try {
29947
- return existsSync25(p3);
30550
+ return existsSync27(p3);
29948
30551
  } catch {
29949
30552
  return false;
29950
30553
  }
@@ -30163,7 +30766,7 @@ var init_prereqChecks = __esm({
30163
30766
  });
30164
30767
 
30165
30768
  // src/cli/plugins/prefs.ts
30166
- import { existsSync as existsSync26, readFileSync as readFileSync23, writeFileSync as writeFileSync15, mkdirSync as mkdirSync10 } from "node:fs";
30769
+ import { existsSync as existsSync28, readFileSync as readFileSync25, writeFileSync as writeFileSync16, mkdirSync as mkdirSync11 } from "node:fs";
30167
30770
  import path29 from "node:path";
30168
30771
  import os9 from "node:os";
30169
30772
  function getPluginPrefsPath() {
@@ -30172,8 +30775,8 @@ function getPluginPrefsPath() {
30172
30775
  function getPluginPrefs() {
30173
30776
  const file2 = getPluginPrefsPath();
30174
30777
  try {
30175
- if (!existsSync26(file2)) return { ...DEFAULTS2, dontAskAgain: {} };
30176
- const raw = readFileSync23(file2, "utf-8");
30778
+ if (!existsSync28(file2)) return { ...DEFAULTS2, dontAskAgain: {} };
30779
+ const raw = readFileSync25(file2, "utf-8");
30177
30780
  const parsed = JSON.parse(raw);
30178
30781
  if (parsed && typeof parsed === "object" && parsed.dontAskAgain && typeof parsed.dontAskAgain === "object") {
30179
30782
  const clean = {};
@@ -30188,8 +30791,8 @@ function getPluginPrefs() {
30188
30791
  }
30189
30792
  function writePluginPrefs(prefs) {
30190
30793
  const file2 = getPluginPrefsPath();
30191
- mkdirSync10(path29.dirname(file2), { recursive: true });
30192
- writeFileSync15(file2, JSON.stringify(prefs, null, 2), {
30794
+ mkdirSync11(path29.dirname(file2), { recursive: true });
30795
+ writeFileSync16(file2, JSON.stringify(prefs, null, 2), {
30193
30796
  encoding: "utf-8",
30194
30797
  mode: 384
30195
30798
  });
@@ -30224,7 +30827,7 @@ __export(registry_exports, {
30224
30827
  findPlugin: () => findPlugin,
30225
30828
  isBinaryOnPath: () => isBinaryOnPath
30226
30829
  });
30227
- import { existsSync as existsSync27 } from "node:fs";
30830
+ import { existsSync as existsSync29 } from "node:fs";
30228
30831
  import path30 from "node:path";
30229
30832
  function detectLocalBin(bin) {
30230
30833
  return (cwd) => {
@@ -30241,7 +30844,7 @@ function isBinaryOnPath(bin, opts = {}) {
30241
30844
  return false;
30242
30845
  }
30243
30846
  const platform = opts.platform ?? process.platform;
30244
- const exists = opts.exists ?? existsSync27;
30847
+ const exists = opts.exists ?? existsSync29;
30245
30848
  const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
30246
30849
  const pathMod = platform === "win32" ? path30.win32 : path30.posix;
30247
30850
  const sep = platform === "win32" ? ";" : ":";
@@ -30392,7 +30995,7 @@ __export(doctor_exports, {
30392
30995
  runDoctor: () => runDoctor
30393
30996
  });
30394
30997
  import { execSync as execSync2 } from "node:child_process";
30395
- import { existsSync as existsSync32, readFileSync as readFileSync26, readlinkSync, statSync as statSync6 } from "node:fs";
30998
+ import { existsSync as existsSync35, readFileSync as readFileSync30, readlinkSync, statSync as statSync6 } from "node:fs";
30396
30999
  import { createRequire as createRequire3 } from "node:module";
30397
31000
  import { fileURLToPath as fileURLToPath2 } from "node:url";
30398
31001
  import path35 from "node:path";
@@ -30400,9 +31003,9 @@ function findPackageRoot(start) {
30400
31003
  let dir = start;
30401
31004
  for (let i = 0; i < 6; i += 1) {
30402
31005
  const candidate = path35.join(dir, "package.json");
30403
- if (existsSync32(candidate)) {
31006
+ if (existsSync35(candidate)) {
30404
31007
  try {
30405
- const pkg = JSON.parse(readFileSync26(candidate, "utf8"));
31008
+ const pkg = JSON.parse(readFileSync30(candidate, "utf8"));
30406
31009
  if (pkg.name === "zelari-code") return dir;
30407
31010
  } catch {
30408
31011
  }
@@ -30426,7 +31029,7 @@ function tryExec(cmd) {
30426
31029
  function readPackageJson3() {
30427
31030
  try {
30428
31031
  const pkgPath = path35.join(packageRoot, "package.json");
30429
- return JSON.parse(readFileSync26(pkgPath, "utf8"));
31032
+ return JSON.parse(readFileSync30(pkgPath, "utf8"));
30430
31033
  } catch {
30431
31034
  return null;
30432
31035
  }
@@ -30442,7 +31045,7 @@ function checkShim(pkgName) {
30442
31045
  const isWin = process.platform === "win32";
30443
31046
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
30444
31047
  const shimPath = path35.join(prefix, shimName);
30445
- if (!existsSync32(shimPath)) {
31048
+ if (!existsSync35(shimPath)) {
30446
31049
  return FAIL(
30447
31050
  `shim not found at ${shimPath}
30448
31051
  fix: npm install -g ${pkgName}@latest --force`
@@ -30451,7 +31054,7 @@ function checkShim(pkgName) {
30451
31054
  try {
30452
31055
  const st = statSync6(shimPath);
30453
31056
  if (isWin) {
30454
- const content = readFileSync26(shimPath, "utf8");
31057
+ const content = readFileSync30(shimPath, "utf8");
30455
31058
  if (content.includes(`${pkgName}\\bin\\`) || content.includes(`${pkgName}/bin/`)) {
30456
31059
  return OK(`shim OK at ${shimPath} (${st.size} bytes)`);
30457
31060
  }
@@ -30510,7 +31113,7 @@ function checkNode(pkg) {
30510
31113
  }
30511
31114
  function checkBundle() {
30512
31115
  const bundle = path35.join(packageRoot, "dist", "cli", "main.bundled.js");
30513
- if (!existsSync32(bundle)) {
31116
+ if (!existsSync35(bundle)) {
30514
31117
  return FAIL(
30515
31118
  `dist/cli/main.bundled.js missing at ${bundle}
30516
31119
  fix: npm run build:cli (then reinstall or run via tsx)`
@@ -34643,13 +35246,22 @@ function useChatTurn(params) {
34643
35246
  let systemPrompt;
34644
35247
  try {
34645
35248
  const languageModule = buildLanguagePolicyModuleFor(userText);
35249
+ const { loadProjectInstructions: loadProjectInstructions2 } = await Promise.resolve().then(() => (init_projectInstructions(), projectInstructions_exports));
35250
+ const projectInstructions = loadProjectInstructions2(
35251
+ process.cwd()
35252
+ ).content;
34646
35253
  systemPrompt = buildSystemPrompt(singleAgentRole, {
34647
35254
  tools: getAllTools(),
34648
35255
  toolNames: toolListNames,
35256
+ mode: "agent",
35257
+ projectInstructions: projectInstructions || void 0,
34649
35258
  aiConfig: {
34650
35259
  enabledSkills: [],
34651
35260
  enabledTools: toolListNames,
34652
- customPromptModules: [SINGLE_AGENT_IDENTITY_MODULE, languageModule],
35261
+ customPromptModules: [
35262
+ SINGLE_AGENT_IDENTITY_MODULE,
35263
+ languageModule
35264
+ ],
34653
35265
  agentSkillConfigs: []
34654
35266
  },
34655
35267
  workspaceContext: workspaceContext || void 0,
@@ -36439,8 +37051,8 @@ init_registry2();
36439
37051
  // src/cli/plugins/installer.ts
36440
37052
  init_cmdline();
36441
37053
  init_updater();
36442
- import { spawn as spawn8 } from "node:child_process";
36443
- async function installPlugin(spec, cwd, executor = spawn8) {
37054
+ import { spawn as spawn9 } from "node:child_process";
37055
+ async function installPlugin(spec, cwd, executor = spawn9) {
36444
37056
  const scopeFlag = spec.installScope === "global" ? "-g" : "-D";
36445
37057
  const args = ["install", scopeFlag, spec.npmPackage];
36446
37058
  const primary = await runNpm2(executor, args, cwd, "shim");
@@ -36567,7 +37179,7 @@ async function handlePromoteMember(ctx, memberId) {
36567
37179
  }
36568
37180
 
36569
37181
  // src/cli/branchManager.ts
36570
- import { promises as fs17, existsSync as existsSync28, readFileSync as readFileSync24, writeFileSync as writeFileSync16, mkdirSync as mkdirSync11, statSync as statSync4, rmSync as rmSync2 } from "node:fs";
37182
+ import { promises as fs17, existsSync as existsSync30, readFileSync as readFileSync26, writeFileSync as writeFileSync17, mkdirSync as mkdirSync12, statSync as statSync4, rmSync as rmSync2 } from "node:fs";
36571
37183
  import path32 from "node:path";
36572
37184
  import os11 from "node:os";
36573
37185
  var META_FILENAME = "meta.json";
@@ -36589,11 +37201,11 @@ function sessionsPathFor(name, baseDir) {
36589
37201
  }
36590
37202
  function readBranchMeta(name, baseDir) {
36591
37203
  const metaPath = metaPathFor(name, baseDir);
36592
- if (!existsSync28(metaPath)) {
37204
+ if (!existsSync30(metaPath)) {
36593
37205
  throw new BranchNotFoundError(`Branch "${name}" not found`);
36594
37206
  }
36595
37207
  try {
36596
- const raw = readFileSync24(metaPath, "utf-8");
37208
+ const raw = readFileSync26(metaPath, "utf-8");
36597
37209
  const parsed = JSON.parse(raw);
36598
37210
  if (!parsed || typeof parsed !== "object" || typeof parsed.name !== "string" || typeof parsed.createdAt !== "number" || typeof parsed.fromSessionId !== "string") {
36599
37211
  throw new BranchCorruptError(`Branch "${name}" meta.json is malformed`);
@@ -36610,8 +37222,8 @@ function readBranchMeta(name, baseDir) {
36610
37222
  }
36611
37223
  function writeBranchMeta(name, baseDir, meta3) {
36612
37224
  const metaPath = metaPathFor(name, baseDir);
36613
- mkdirSync11(path32.dirname(metaPath), { recursive: true });
36614
- writeFileSync16(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
37225
+ mkdirSync12(path32.dirname(metaPath), { recursive: true });
37226
+ writeFileSync17(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
36615
37227
  }
36616
37228
  async function countSessions(name, baseDir) {
36617
37229
  const sessionsPath = sessionsPathFor(name, baseDir);
@@ -36649,7 +37261,7 @@ var SessionNotFoundError = class extends Error {
36649
37261
  };
36650
37262
  function branchExists(name, baseDir = getBranchesBaseDir()) {
36651
37263
  const bp = branchPathFor(name, baseDir);
36652
- return existsSync28(bp) && existsSync28(metaPathFor(name, baseDir));
37264
+ return existsSync30(bp) && existsSync30(metaPathFor(name, baseDir));
36653
37265
  }
36654
37266
  async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(), sessionsBaseDir = getSessionsBaseDir()) {
36655
37267
  if (!name || name.trim().length === 0) {
@@ -36662,12 +37274,12 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
36662
37274
  throw new BranchAlreadyExistsError(name);
36663
37275
  }
36664
37276
  const sourcePath = path32.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
36665
- if (!existsSync28(sourcePath)) {
37277
+ if (!existsSync30(sourcePath)) {
36666
37278
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
36667
37279
  }
36668
37280
  const branchPath = branchPathFor(name, baseDir);
36669
37281
  const branchSessionsPath = sessionsPathFor(name, baseDir);
36670
- mkdirSync11(branchSessionsPath, { recursive: true });
37282
+ mkdirSync12(branchSessionsPath, { recursive: true });
36671
37283
  const destPath = path32.join(branchSessionsPath, `${fromSessionId}.jsonl`);
36672
37284
  await fs17.copyFile(sourcePath, destPath);
36673
37285
  const meta3 = {
@@ -36695,7 +37307,7 @@ async function listBranches(baseDir = getBranchesBaseDir()) {
36695
37307
  const results = [];
36696
37308
  for (const entry of entries) {
36697
37309
  const metaPath = metaPathFor(entry, baseDir);
36698
- if (!existsSync28(metaPath)) continue;
37310
+ if (!existsSync30(metaPath)) continue;
36699
37311
  try {
36700
37312
  const meta3 = readBranchMeta(entry, baseDir);
36701
37313
  const sessionCount = await countSessions(entry, baseDir);
@@ -37231,7 +37843,7 @@ import path34 from "node:path";
37231
37843
  import os12 from "node:os";
37232
37844
 
37233
37845
  // src/cli/skillHistory.ts
37234
- import { promises as fs19, existsSync as existsSync29, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync3, mkdirSync as mkdirSync12 } from "node:fs";
37846
+ import { promises as fs19, existsSync as existsSync31, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync3, mkdirSync as mkdirSync13 } from "node:fs";
37235
37847
  var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
37236
37848
  async function readSkillHistory(file2) {
37237
37849
  let raw = "";
@@ -38409,9 +39021,9 @@ function ContinueKey({ onContinue }) {
38409
39021
  init_providerConfig();
38410
39022
 
38411
39023
  // src/cli/wizard/firstRun.ts
38412
- import { existsSync as existsSync30 } from "node:fs";
39024
+ import { existsSync as existsSync32 } from "node:fs";
38413
39025
  function shouldRunWizard(input) {
38414
- const exists = input.exists ?? existsSync30;
39026
+ const exists = input.exists ?? existsSync32;
38415
39027
  if (input.hasResetConfigFlag) {
38416
39028
  return { shouldRun: true, reason: "--reset-config flag forced wizard" };
38417
39029
  }
@@ -38696,6 +39308,7 @@ init_keyStore();
38696
39308
  init_providerConfig();
38697
39309
  init_openai_compatible();
38698
39310
  init_phase();
39311
+ import { readFileSync as readFileSync27 } from "node:fs";
38699
39312
  function parseHeadlessFlags(argv) {
38700
39313
  if (!argv.includes("--headless")) {
38701
39314
  return { options: null };
@@ -38708,6 +39321,7 @@ function parseHeadlessFlags(argv) {
38708
39321
  let councilFlag = false;
38709
39322
  let provider;
38710
39323
  let model;
39324
+ let history2;
38711
39325
  for (let i = 0; i < argv.length; i++) {
38712
39326
  const arg = argv[i];
38713
39327
  if (arg === "--headless") continue;
@@ -38756,6 +39370,32 @@ function parseHeadlessFlags(argv) {
38756
39370
  } else if (arg === "--model") {
38757
39371
  model = argv[i + 1];
38758
39372
  i++;
39373
+ } else if (arg === "--history" || arg === "--history-file") {
39374
+ const next = argv[i + 1];
39375
+ if (next) {
39376
+ let raw = null;
39377
+ if (arg === "--history-file") {
39378
+ try {
39379
+ raw = readFileSync27(next, "utf-8");
39380
+ } catch {
39381
+ raw = null;
39382
+ }
39383
+ } else {
39384
+ raw = next;
39385
+ }
39386
+ if (raw) {
39387
+ try {
39388
+ const parsedHist = JSON.parse(raw);
39389
+ if (Array.isArray(parsedHist)) {
39390
+ history2 = parsedHist.filter(
39391
+ (m) => m && typeof m === "object" && typeof m.role === "string" && typeof m.content === "string"
39392
+ );
39393
+ }
39394
+ } catch {
39395
+ }
39396
+ }
39397
+ i++;
39398
+ }
38759
39399
  }
38760
39400
  }
38761
39401
  if (councilFlag && !modeExplicit) {
@@ -38777,7 +39417,8 @@ function parseHeadlessFlags(argv) {
38777
39417
  phase: phase2,
38778
39418
  useCouncil: mode === "council",
38779
39419
  provider,
38780
- model
39420
+ model,
39421
+ ...history2 && history2.length > 0 ? { history: history2 } : {}
38781
39422
  }
38782
39423
  };
38783
39424
  }
@@ -38810,12 +39451,66 @@ function emitEvent(event) {
38810
39451
 
38811
39452
  // src/cli/runHeadless.ts
38812
39453
  init_harness();
39454
+ init_conversationContext();
38813
39455
  init_toolRegistry();
38814
39456
  init_skills2();
38815
39457
  init_envNumber();
38816
39458
  init_phaseState();
38817
39459
  init_phase();
39460
+
39461
+ // src/cli/utils/streamScrub.ts
39462
+ function createStreamScrubber() {
39463
+ let rawBuf = "";
39464
+ let emittedLen = 0;
39465
+ const snapshot = () => {
39466
+ const cleaned = cleanAgentContent(rawBuf);
39467
+ if (cleaned.length <= emittedLen) return "";
39468
+ const delta = cleaned.slice(emittedLen);
39469
+ emittedLen = cleaned.length;
39470
+ return delta;
39471
+ };
39472
+ return {
39473
+ push(rawDelta) {
39474
+ rawBuf += rawDelta;
39475
+ return snapshot();
39476
+ },
39477
+ flush() {
39478
+ return snapshot();
39479
+ },
39480
+ reset() {
39481
+ rawBuf = "";
39482
+ emittedLen = 0;
39483
+ }
39484
+ };
39485
+ }
39486
+
39487
+ // src/cli/runHeadless.ts
38818
39488
  async function runHeadless(opts) {
39489
+ let crashed = false;
39490
+ const handleFatal = (label, err) => {
39491
+ if (crashed) return;
39492
+ crashed = true;
39493
+ const msg = err instanceof Error ? err.message : String(err);
39494
+ const stack = err instanceof Error && err.stack ? `
39495
+ ${err.stack}` : "";
39496
+ const line = `[zelari-code --headless] FATAL ${label}: ${msg}${stack}`;
39497
+ try {
39498
+ emitEvent({
39499
+ type: "error",
39500
+ severity: "fatal",
39501
+ message: `${label}: ${msg}`,
39502
+ code: "uncaught"
39503
+ });
39504
+ } catch {
39505
+ }
39506
+ try {
39507
+ process.stderr.write(line + "\n");
39508
+ } catch {
39509
+ }
39510
+ process.exit(2);
39511
+ };
39512
+ process.on("uncaughtException", (err) => handleFatal("uncaughtException", err));
39513
+ process.on("unhandledRejection", (err) => handleFatal("unhandledRejection", err));
38819
39514
  setPhase(opts.phase ?? "build");
38820
39515
  const { provider, model } = resolveHeadlessProvider(opts);
38821
39516
  const key = await resolveHeadlessKey(provider);
@@ -38853,11 +39548,50 @@ async function runHeadless(opts) {
38853
39548
  function planModeFromOpts(opts) {
38854
39549
  return (opts.phase ?? "build") === "plan";
38855
39550
  }
39551
+ var mcpExitHookInstalled = false;
39552
+ async function registerHeadlessMcp(toolRegistry, opts) {
39553
+ try {
39554
+ const { registerMcpTools: registerMcpTools2, closeMcpClients: closeMcpClients2 } = await Promise.resolve().then(() => (init_mcpManager(), mcpManager_exports));
39555
+ const mcp = await registerMcpTools2(toolRegistry, process.cwd());
39556
+ if (!mcpExitHookInstalled) {
39557
+ mcpExitHookInstalled = true;
39558
+ process.once("exit", () => {
39559
+ try {
39560
+ closeMcpClients2();
39561
+ } catch {
39562
+ }
39563
+ });
39564
+ }
39565
+ if (mcp.registered.length > 0 && opts.output === "json") {
39566
+ emitEvent({
39567
+ type: "log",
39568
+ message: `[headless] MCP tools: ${mcp.registered.length} registered`
39569
+ });
39570
+ }
39571
+ for (const w of mcp.warnings) {
39572
+ if (opts.output === "json") {
39573
+ emitEvent({ type: "log", message: `[mcp] ${w}` });
39574
+ } else {
39575
+ process.stderr.write(`[zelari-code --headless] [mcp] ${w}
39576
+ `);
39577
+ }
39578
+ }
39579
+ } catch (err) {
39580
+ const msg = err instanceof Error ? err.message : String(err);
39581
+ if (opts.output === "json") {
39582
+ emitEvent({ type: "log", message: `[mcp] registration skipped: ${msg}` });
39583
+ } else {
39584
+ process.stderr.write(`[zelari-code --headless] [mcp] registration skipped: ${msg}
39585
+ `);
39586
+ }
39587
+ }
39588
+ }
38856
39589
  async function runHeadlessSingle(opts, provider, model, providerStream) {
38857
39590
  const sessionId = crypto.randomUUID();
38858
39591
  const { registry: toolRegistry } = createBuiltinToolRegistry({
38859
39592
  planMode: planModeFromOpts(opts)
38860
39593
  });
39594
+ await registerHeadlessMcp(toolRegistry, opts);
38861
39595
  const tools = toolRegistry.toOpenAITools().map((t) => ({
38862
39596
  name: t.function.name,
38863
39597
  description: t.function.description,
@@ -38894,21 +39628,38 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
38894
39628
  (opts.phase ?? "build") === "plan" ? "PLAN phase: explore and design only. Do not write project source files (write_file/edit_file/bash blocked). Plan artifacts under .zelari are allowed." : "BUILD phase: full tools; implement changes."
38895
39629
  ].join("\n")
38896
39630
  };
38897
- systemPrompt = buildSystemPrompt(headlessRole, {
38898
- tools: getAllTools(),
38899
- toolNames,
38900
- aiConfig: {
38901
- enabledSkills: [],
38902
- enabledTools: toolNames,
38903
- customPromptModules: [SINGLE_AGENT_IDENTITY_MODULE, {
38904
- type: "language-policy",
38905
- title: "Response Language",
38906
- priority: 5,
38907
- content: languageDirectiveContent
38908
- }],
38909
- agentSkillConfigs: []
39631
+ const { loadProjectInstructions: loadProjectInstructions2 } = await Promise.resolve().then(() => (init_projectInstructions(), projectInstructions_exports));
39632
+ const projectInstructions = loadProjectInstructions2(process.cwd()).content;
39633
+ let sshBlock = "";
39634
+ try {
39635
+ const { formatSshTargetsForPrompt: formatSshTargetsForPrompt2 } = await Promise.resolve().then(() => (init_targets(), targets_exports));
39636
+ sshBlock = formatSshTargetsForPrompt2();
39637
+ } catch {
39638
+ }
39639
+ const rolePrompt = [headlessRole.systemPrompt, sshBlock].filter(Boolean).join("\n\n");
39640
+ systemPrompt = buildSystemPrompt(
39641
+ { ...headlessRole, systemPrompt: rolePrompt },
39642
+ {
39643
+ tools: getAllTools(),
39644
+ toolNames,
39645
+ mode: "agent",
39646
+ projectInstructions: projectInstructions || void 0,
39647
+ aiConfig: {
39648
+ enabledSkills: [],
39649
+ enabledTools: toolNames,
39650
+ customPromptModules: [
39651
+ SINGLE_AGENT_IDENTITY_MODULE,
39652
+ {
39653
+ type: "language-policy",
39654
+ title: "Response Language",
39655
+ priority: 5,
39656
+ content: languageDirectiveContent
39657
+ }
39658
+ ],
39659
+ agentSkillConfigs: []
39660
+ }
38910
39661
  }
38911
- });
39662
+ );
38912
39663
  } catch {
38913
39664
  systemPrompt = [
38914
39665
  "You are zelari-code, a CLI coding agent. Be concise and direct.",
@@ -38917,13 +39668,19 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
38917
39668
  languageDirectiveContent
38918
39669
  ].join("\n");
38919
39670
  }
39671
+ const historySeed = (opts.history ?? []).map(
39672
+ (m) => m.role === "assistant" && m.content ? { ...m, content: cleanAgentContent(m.content, { stripQuestion: false }) } : m
39673
+ );
39674
+ const effectiveTask = maybeAnchorShortAnswer(opts.task) ?? opts.task;
39675
+ const historySeedLen = historySeed.length;
38920
39676
  const harness = new AgentHarness({
38921
39677
  model,
38922
39678
  provider,
38923
39679
  sessionId,
38924
39680
  messages: [
38925
39681
  { role: "system", content: systemPrompt },
38926
- { role: "user", content: opts.task }
39682
+ ...historySeed,
39683
+ { role: "user", content: effectiveTask }
38927
39684
  ],
38928
39685
  tools,
38929
39686
  toolRegistry,
@@ -38936,23 +39693,39 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
38936
39693
  let finalReason = "completed";
38937
39694
  let exitCode = 0;
38938
39695
  const textBuffer = [];
39696
+ const scrub = createStreamScrubber();
38939
39697
  try {
38940
39698
  for await (const event of harness.run()) {
38941
- if (opts.output === "json") {
38942
- emitEvent(event);
39699
+ if (event.type === "message_start") {
39700
+ scrub.reset();
38943
39701
  }
38944
- if (event.type === "message_delta") {
38945
- if (opts.output === "plain") {
38946
- process.stdout.write(event.delta);
39702
+ if (event.type === "message_delta" && typeof event.delta === "string") {
39703
+ const cleanDelta = scrub.push(event.delta);
39704
+ if (opts.output === "json") {
39705
+ if (cleanDelta.length > 0) {
39706
+ emitEvent({ ...event, delta: cleanDelta });
39707
+ }
39708
+ } else if (opts.output === "plain") {
39709
+ if (cleanDelta.length > 0) process.stdout.write(cleanDelta);
38947
39710
  } else {
38948
- textBuffer.push(event.delta);
39711
+ if (cleanDelta.length > 0) textBuffer.push(cleanDelta);
38949
39712
  }
38950
- } else if (event.type === "agent_end") {
38951
- finalReason = event.reason;
38952
- if (event.reason === "error") exitCode = 3;
38953
- } else if (event.type === "error") {
38954
- if (event.severity === "fatal") {
38955
- exitCode = 2;
39713
+ } else {
39714
+ if (opts.output === "json") {
39715
+ emitEvent(event);
39716
+ }
39717
+ if (event.type === "agent_end") {
39718
+ const tail = scrub.flush();
39719
+ if (tail.length > 0) {
39720
+ if (opts.output === "plain") process.stdout.write(tail);
39721
+ else textBuffer.push(tail);
39722
+ }
39723
+ finalReason = event.reason;
39724
+ if (event.reason === "error") exitCode = 3;
39725
+ } else if (event.type === "error") {
39726
+ if (event.severity === "fatal") {
39727
+ exitCode = 2;
39728
+ }
38956
39729
  }
38957
39730
  }
38958
39731
  }
@@ -38967,10 +39740,25 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
38967
39740
  process.stdout.write(textBuffer.join(""));
38968
39741
  }
38969
39742
  process.stdout.write("");
39743
+ if (finalReason !== "error" && opts.output === "json") {
39744
+ try {
39745
+ const all = harness.getMessages();
39746
+ const seedLen = 1 + historySeedLen + 1;
39747
+ if (all.length > seedLen) {
39748
+ const tail = all.slice(seedLen).map(
39749
+ (m) => m.role === "assistant" && m.content ? { ...m, content: cleanAgentContent(m.content, { stripQuestion: false }) } : m
39750
+ );
39751
+ if (tail.length > 0) {
39752
+ emitEvent({ type: "history_snapshot", messages: tail });
39753
+ }
39754
+ }
39755
+ } catch {
39756
+ }
39757
+ }
38970
39758
  if (finalReason === "error") return 3;
38971
39759
  return exitCode;
38972
39760
  }
38973
- async function buildCouncilToolRegistry(planMode) {
39761
+ async function buildCouncilToolRegistry(planMode, opts) {
38974
39762
  const { registry: toolRegistry } = createBuiltinToolRegistry({ planMode });
38975
39763
  const { createWorkspaceContext: createWorkspaceContext2, createWorkspaceStubs: createWorkspaceStubs2 } = await Promise.resolve().then(() => (init_stubs(), stubs_exports));
38976
39764
  const { createWorkspaceToolRegistry: createWorkspaceToolRegistry2 } = await Promise.resolve().then(() => (init_toolRegistry2(), toolRegistry_exports2));
@@ -38982,17 +39770,30 @@ async function buildCouncilToolRegistry(planMode) {
38982
39770
  if (td) toolRegistry.register(td);
38983
39771
  }
38984
39772
  setWorkspaceStubs2(createWorkspaceStubs2(realCtx));
39773
+ if (opts) {
39774
+ await registerHeadlessMcp(toolRegistry, opts);
39775
+ }
38985
39776
  return { toolRegistry, workspaceCtx: realCtx };
38986
39777
  }
38987
39778
  async function runHeadlessCouncil(opts, provider, model, providerStream) {
38988
39779
  const { dispatchCouncil: dispatchCouncil2 } = await Promise.resolve().then(() => (init_councilDispatcher(), councilDispatcher_exports));
38989
39780
  const sessionId = crypto.randomUUID();
38990
- const { toolRegistry } = await buildCouncilToolRegistry(planModeFromOpts(opts));
39781
+ const { toolRegistry } = await buildCouncilToolRegistry(
39782
+ planModeFromOpts(opts),
39783
+ opts
39784
+ );
38991
39785
  const { FeedbackStore: FeedbackStore2 } = await Promise.resolve().then(() => (init_councilFeedback(), councilFeedback_exports));
38992
39786
  const feedbackStore = new FeedbackStore2();
39787
+ const historySeed = (opts.history ?? []).map(
39788
+ (m) => m.role === "assistant" && m.content ? { ...m, content: cleanAgentContent(m.content, { stripQuestion: false }) } : m
39789
+ );
39790
+ const effectiveTask = buildCouncilTaskWithHistory(opts.task, historySeed);
38993
39791
  let exitCode = 0;
39792
+ const scrub = createStreamScrubber();
39793
+ let lastAssistantText = "";
39794
+ let currentAssistantText = "";
38994
39795
  try {
38995
- for await (const event of dispatchCouncil2(opts.task, {
39796
+ for await (const event of dispatchCouncil2(effectiveTask, {
38996
39797
  apiKey: "REDACTED",
38997
39798
  model,
38998
39799
  provider: "openai-compatible",
@@ -39002,14 +39803,35 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
39002
39803
  feedbackStore,
39003
39804
  runMode: planModeFromOpts(opts) ? "design-phase" : "implementation"
39004
39805
  })) {
39005
- if (opts.output === "json") {
39006
- emitEvent(event);
39007
- } else if (event.type === "message_delta") {
39008
- process.stdout.write(event.delta);
39009
- } else if (event.type === "agent_end" && event.reason === "error") {
39010
- exitCode = 3;
39011
- } else if (event.type === "error" && event.severity === "fatal") {
39012
- exitCode = 2;
39806
+ if (event.type === "message_start") {
39807
+ scrub.reset();
39808
+ currentAssistantText = "";
39809
+ }
39810
+ if (event.type === "message_delta" && typeof event.delta === "string") {
39811
+ const cleanDelta = scrub.push(event.delta);
39812
+ if (cleanDelta.length > 0) currentAssistantText += cleanDelta;
39813
+ if (opts.output === "json") {
39814
+ if (cleanDelta.length > 0) emitEvent({ ...event, delta: cleanDelta });
39815
+ } else if (opts.output === "plain" && cleanDelta.length > 0) {
39816
+ process.stdout.write(cleanDelta);
39817
+ }
39818
+ } else {
39819
+ if (opts.output === "json") emitEvent(event);
39820
+ if (event.type === "message_end" || event.type === "agent_end") {
39821
+ const tail = scrub.flush();
39822
+ if (tail.length > 0) {
39823
+ currentAssistantText += tail;
39824
+ if (opts.output === "plain") process.stdout.write(tail);
39825
+ }
39826
+ if (currentAssistantText.trim()) {
39827
+ lastAssistantText = currentAssistantText.trim();
39828
+ }
39829
+ if (event.type === "agent_end" && event.reason === "error") {
39830
+ exitCode = 3;
39831
+ }
39832
+ } else if (event.type === "error" && event.severity === "fatal") {
39833
+ exitCode = 2;
39834
+ }
39013
39835
  }
39014
39836
  }
39015
39837
  } catch (err) {
@@ -39019,6 +39841,16 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
39019
39841
  );
39020
39842
  return 2;
39021
39843
  }
39844
+ if (opts.output === "json") {
39845
+ try {
39846
+ const snapshot = [
39847
+ { role: "user", content: opts.task },
39848
+ ...lastAssistantText ? [{ role: "assistant", content: lastAssistantText }] : []
39849
+ ];
39850
+ emitEvent({ type: "history_snapshot", messages: snapshot });
39851
+ } catch {
39852
+ }
39853
+ }
39022
39854
  return exitCode;
39023
39855
  }
39024
39856
  async function runHeadlessZelari(opts, provider, model, providerStream) {
@@ -39035,7 +39867,10 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
39035
39867
  hasPlan: hasWorkspacePlan2(projectRoot)
39036
39868
  });
39037
39869
  const memory = await getMemoryBackend2(projectRoot);
39038
- const { toolRegistry, workspaceCtx } = await buildCouncilToolRegistry(planModeFromOpts(opts));
39870
+ const { toolRegistry, workspaceCtx } = await buildCouncilToolRegistry(
39871
+ planModeFromOpts(opts),
39872
+ opts
39873
+ );
39039
39874
  const feedbackStore = new FeedbackStore2();
39040
39875
  const chairmanBudget = envNumber(process.env.ZELARI_MODE_MAX_TOOLS_LUCIFER, {
39041
39876
  default: 30,
@@ -39048,11 +39883,16 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
39048
39883
  process.stderr.write(message + "\n");
39049
39884
  }
39050
39885
  };
39886
+ const historySeed = (opts.history ?? []).map(
39887
+ (m) => m.role === "assistant" && m.content ? { ...m, content: cleanAgentContent(m.content, { stripQuestion: false }) } : m
39888
+ );
39889
+ const missionTask = buildCouncilTaskWithHistory(opts.task, historySeed);
39051
39890
  emit(`[zelari] mission brief
39052
39891
  ${JSON.stringify({ deliverable: brief.deliverableThisMission, mvp: brief.sliceMvp?.title }, null, 0)}`);
39053
39892
  let exitCode = 0;
39893
+ let lastMissionAssistant = "";
39054
39894
  try {
39055
- const state2 = await runZelariMission2(opts.task, brief, {
39895
+ const state2 = await runZelariMission2(missionTask, brief, {
39056
39896
  projectRoot,
39057
39897
  memory,
39058
39898
  emit,
@@ -39066,6 +39906,7 @@ ${ragContext}` : slicePrompt;
39066
39906
  let writeCount = 0;
39067
39907
  let chairmanErrored = false;
39068
39908
  let membersCompleted = 0;
39909
+ const scrub = createStreamScrubber();
39069
39910
  for await (const event of dispatchCouncil2(fullPrompt, {
39070
39911
  apiKey: "REDACTED",
39071
39912
  model,
@@ -39077,13 +39918,21 @@ ${ragContext}` : slicePrompt;
39077
39918
  runMode: planModeFromOpts(opts) ? "design-phase" : runMode,
39078
39919
  maxToolCallsChairman: chairmanBudget
39079
39920
  })) {
39080
- if (opts.output === "json") {
39081
- emitEvent(event);
39082
- } else if (event.type === "message_delta") {
39083
- process.stdout.write(event.delta);
39921
+ if (event.type === "message_start") {
39922
+ scrub.reset();
39084
39923
  }
39085
39924
  if (event.type === "message_delta" && typeof event.delta === "string") {
39086
39925
  synthesisText += event.delta;
39926
+ const cleanDelta = scrub.push(event.delta);
39927
+ if (opts.output === "json") {
39928
+ if (cleanDelta.length > 0) emitEvent({ ...event, delta: cleanDelta });
39929
+ } else if (opts.output === "plain" && cleanDelta.length > 0) {
39930
+ process.stdout.write(cleanDelta);
39931
+ }
39932
+ } else {
39933
+ if (opts.output === "json") {
39934
+ emitEvent(event);
39935
+ }
39087
39936
  }
39088
39937
  if (event.type === "tool_execution_end") {
39089
39938
  const name = event.toolName ?? event.name ?? "";
@@ -39125,6 +39974,11 @@ ${ragContext}` : slicePrompt;
39125
39974
  }
39126
39975
  } catch {
39127
39976
  }
39977
+ if (synthesisText.trim()) {
39978
+ lastMissionAssistant = cleanAgentContent(synthesisText, {
39979
+ stripQuestion: false
39980
+ });
39981
+ }
39128
39982
  return {
39129
39983
  completionOk,
39130
39984
  ran: membersCompleted > 0 || synthesisText.length > 0,
@@ -39146,6 +40000,18 @@ ${ragContext}` : slicePrompt;
39146
40000
  } finally {
39147
40001
  await memory.close().catch(() => void 0);
39148
40002
  }
40003
+ if (opts.output === "json") {
40004
+ try {
40005
+ emitEvent({
40006
+ type: "history_snapshot",
40007
+ messages: [
40008
+ { role: "user", content: opts.task },
40009
+ ...lastMissionAssistant ? [{ role: "assistant", content: lastMissionAssistant }] : []
40010
+ ]
40011
+ });
40012
+ } catch {
40013
+ }
40014
+ }
39149
40015
  return exitCode;
39150
40016
  }
39151
40017
 
@@ -39396,9 +40262,9 @@ async function runDiscoverModels(providerArg) {
39396
40262
 
39397
40263
  // src/cli/skillsMd.ts
39398
40264
  init_skills2();
39399
- import { existsSync as existsSync31, readdirSync as readdirSync4, readFileSync as readFileSync25 } from "node:fs";
39400
- import { join as join23 } from "node:path";
39401
- import { homedir as homedir6 } from "node:os";
40265
+ import { existsSync as existsSync33, readdirSync as readdirSync4, readFileSync as readFileSync28 } from "node:fs";
40266
+ import { join as join25 } from "node:path";
40267
+ import { homedir as homedir7 } from "node:os";
39402
40268
  var CODING_CATEGORIES = /* @__PURE__ */ new Set([
39403
40269
  "plan",
39404
40270
  "refactor",
@@ -39413,10 +40279,10 @@ var CODING_CATEGORIES = /* @__PURE__ */ new Set([
39413
40279
  ]);
39414
40280
  function skillMdSearchDirs(projectRoot = process.cwd()) {
39415
40281
  return [
39416
- join23(projectRoot, ".zelari", "skills"),
39417
- join23(projectRoot, ".claude", "skills"),
39418
- join23(projectRoot, ".opencode", "skills"),
39419
- join23(homedir6(), ".zelari-code", "skills")
40282
+ join25(projectRoot, ".zelari", "skills"),
40283
+ join25(projectRoot, ".claude", "skills"),
40284
+ join25(projectRoot, ".opencode", "skills"),
40285
+ join25(homedir7(), ".zelari-code", "skills")
39420
40286
  ];
39421
40287
  }
39422
40288
  function parseSkillMd(content, sourcePath) {
@@ -39474,7 +40340,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
39474
40340
  const summary = { loaded: [], skipped: [] };
39475
40341
  const seen = new Set(options.existingIds ?? []);
39476
40342
  for (const dir of skillMdSearchDirs(projectRoot)) {
39477
- if (!existsSync31(dir)) continue;
40343
+ if (!existsSync33(dir)) continue;
39478
40344
  let entries;
39479
40345
  try {
39480
40346
  entries = readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -39482,10 +40348,10 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
39482
40348
  continue;
39483
40349
  }
39484
40350
  for (const entry of entries) {
39485
- const skillPath = join23(dir, entry, "SKILL.md");
39486
- if (!existsSync31(skillPath)) continue;
40351
+ const skillPath = join25(dir, entry, "SKILL.md");
40352
+ if (!existsSync33(skillPath)) continue;
39487
40353
  try {
39488
- const parsed = parseSkillMd(readFileSync25(skillPath, "utf8"), skillPath);
40354
+ const parsed = parseSkillMd(readFileSync28(skillPath, "utf8"), skillPath);
39489
40355
  if (!parsed) {
39490
40356
  summary.skipped.push({ path: skillPath, reason: "missing/invalid frontmatter (name, description) or empty body" });
39491
40357
  continue;
@@ -39508,6 +40374,116 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
39508
40374
  // src/cli/main.ts
39509
40375
  init_skills2();
39510
40376
  init_updater();
40377
+
40378
+ // src/cli/mcp/mcpConfigIo.ts
40379
+ import {
40380
+ existsSync as existsSync34,
40381
+ mkdirSync as mkdirSync14,
40382
+ readFileSync as readFileSync29,
40383
+ writeFileSync as writeFileSync18
40384
+ } from "node:fs";
40385
+ import { dirname as dirname6, join as join26 } from "node:path";
40386
+ import { homedir as homedir8 } from "node:os";
40387
+ function getUserMcpPath() {
40388
+ return join26(homedir8(), ".zelari-code", "mcp.json");
40389
+ }
40390
+ function getProjectMcpPath(projectRoot) {
40391
+ return join26(projectRoot, ".zelari", "mcp.json");
40392
+ }
40393
+ function readFile3(path36) {
40394
+ if (!existsSync34(path36)) return {};
40395
+ try {
40396
+ const parsed = JSON.parse(readFileSync29(path36, "utf8"));
40397
+ const out = {};
40398
+ for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
40399
+ if (!cfg || typeof cfg.command !== "string" || !cfg.command.trim()) continue;
40400
+ out[name] = {
40401
+ command: cfg.command.trim(),
40402
+ args: Array.isArray(cfg.args) ? cfg.args.map(String) : void 0,
40403
+ env: cfg.env && typeof cfg.env === "object" ? cfg.env : void 0,
40404
+ enabled: cfg.enabled !== false
40405
+ };
40406
+ }
40407
+ return out;
40408
+ } catch {
40409
+ return {};
40410
+ }
40411
+ }
40412
+ function writeFile(path36, servers) {
40413
+ mkdirSync14(dirname6(path36), { recursive: true });
40414
+ const body = { mcpServers: servers };
40415
+ writeFileSync18(path36, `${JSON.stringify(body, null, 2)}
40416
+ `, "utf8");
40417
+ }
40418
+ function listMcpServers(projectRoot) {
40419
+ const userPath = getUserMcpPath();
40420
+ const userServers = readFile3(userPath);
40421
+ const projectPath = projectRoot && projectRoot.trim() ? getProjectMcpPath(projectRoot.trim()) : null;
40422
+ const projectServers = projectPath ? readFile3(projectPath) : {};
40423
+ const servers = [];
40424
+ for (const [name, cfg] of Object.entries(userServers)) {
40425
+ servers.push({ name, ...cfg, scope: "user", path: userPath });
40426
+ }
40427
+ for (const [name, cfg] of Object.entries(projectServers)) {
40428
+ servers.push({ name, ...cfg, scope: "project", path: projectPath });
40429
+ }
40430
+ servers.sort((a, b) => a.name.localeCompare(b.name));
40431
+ const merged = {
40432
+ ...userServers,
40433
+ ...projectServers
40434
+ };
40435
+ return { userPath, projectPath, servers, merged };
40436
+ }
40437
+ function upsertMcpServer(opts) {
40438
+ const name = opts.name.trim();
40439
+ if (!name || !/^[a-zA-Z0-9_-]+$/.test(name)) {
40440
+ return {
40441
+ ok: false,
40442
+ error: "Invalid server name (use letters, digits, _ -)"
40443
+ };
40444
+ }
40445
+ if (!opts.config.command?.trim()) {
40446
+ return { ok: false, error: "command is required" };
40447
+ }
40448
+ let path36;
40449
+ if (opts.scope === "user") {
40450
+ path36 = getUserMcpPath();
40451
+ } else {
40452
+ const root = opts.projectRoot?.trim();
40453
+ if (!root) {
40454
+ return {
40455
+ ok: false,
40456
+ error: "projectRoot required for project scope (Open Folder first)"
40457
+ };
40458
+ }
40459
+ path36 = getProjectMcpPath(root);
40460
+ }
40461
+ const current = readFile3(path36);
40462
+ current[name] = {
40463
+ command: opts.config.command.trim(),
40464
+ args: opts.config.args,
40465
+ env: opts.config.env,
40466
+ enabled: opts.config.enabled !== false
40467
+ };
40468
+ writeFile(path36, current);
40469
+ return { ok: true, path: path36 };
40470
+ }
40471
+ function removeMcpServer(opts) {
40472
+ const path36 = opts.scope === "user" ? getUserMcpPath() : opts.projectRoot ? getProjectMcpPath(opts.projectRoot) : null;
40473
+ if (!path36) {
40474
+ return { ok: false, error: "projectRoot required for project scope" };
40475
+ }
40476
+ const current = readFile3(path36);
40477
+ if (!(opts.name in current)) {
40478
+ return { ok: false, error: `Server "${opts.name}" not found in ${path36}` };
40479
+ }
40480
+ delete current[opts.name];
40481
+ writeFile(path36, current);
40482
+ return { ok: true, path: path36 };
40483
+ }
40484
+
40485
+ // src/cli/main.ts
40486
+ init_targets();
39511
40487
  var VERSION = getCurrentVersion();
39512
40488
  function runPreflight() {
39513
40489
  if (process.env.ZELARI_SKIP_PREFLIGHT === "1") return;
@@ -39604,10 +40580,190 @@ function pickRootComponent() {
39604
40580
  }
39605
40581
  if (argv.includes("--help") || argv.includes("-h")) {
39606
40582
  console.log(
39607
- "zelari-code \u2014 AI Council coding agent CLI.\n\nUsage: zelari-code [options]\n\nOptions:\n --version, -v Print version and exit\n --help, -h Print this help and exit\n --doctor Diagnose install health (shim, bundle, PATH, deps,\n node/git/bash in the agent shell)\n --fix-path Add the npm global prefix to the user PATH\n (Windows only; fixes 'command not found' after install)\n --skip-checks Skip the boot-time prerequisite check\n (alias for ZELARI_SKIP_PREFLIGHT=1)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --mode agent|council|zelari Dispatch mode (default: agent)\n --council Alias for --mode council\n --phase plan|build Work phase (default: build)\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n --print-config Print provider/model config as JSON (no secrets)\n --set-config Persist provider/model/endpoint\n --provider <id> Set active provider\n --model <id> Set model for that provider\n --endpoint <url> Custom OpenAI-compatible base URL\n --endpoint-clear Remove custom endpoint override\n --set-key Store an API key (never printed back)\n --provider <id> Provider id (required)\n --key <secret> API key (required)\n --discover-models Refresh model list for a provider\n --provider <id> Provider (default: active)\n\nEnvironment:\n ZELARI_NO_WIZARD=1 Skip the first-run wizard\n ZELARI_SKIP_PREFLIGHT=1 Skip the boot prerequisite check\n ZELARI_NO_PLUGIN_PROMPT=1 Skip the boot plugin-install prompt\n ANATHEMA_DEV=1 Disable background update check + preflight\n"
40583
+ "zelari-code \u2014 AI Council coding agent CLI.\n\nUsage: zelari-code [options]\n\nOptions:\n --version, -v Print version and exit\n --help, -h Print this help and exit\n --doctor Diagnose install health (shim, bundle, PATH, deps,\n node/git/bash in the agent shell)\n --fix-path Add the npm global prefix to the user PATH\n (Windows only; fixes 'command not found' after install)\n --skip-checks Skip the boot-time prerequisite check\n (alias for ZELARI_SKIP_PREFLIGHT=1)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --mode agent|council|zelari Dispatch mode (default: agent)\n --council Alias for --mode council\n --phase plan|build Work phase (default: build)\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n --print-config Print provider/model config as JSON (no secrets)\n --set-config Persist provider/model/endpoint\n --provider <id> Set active provider\n --model <id> Set model for that provider\n --endpoint <url> Custom OpenAI-compatible base URL\n --endpoint-clear Remove custom endpoint override\n --set-key Store an API key (never printed back)\n --provider <id> Provider id (required)\n --key <secret> API key (required)\n --discover-models Refresh model list for a provider\n --provider <id> Provider (default: active)\n --print-mcp Print MCP server config (user + project)\n --cwd <path> Project root for .zelari/mcp.json\n --set-mcp Add/update an MCP server entry\n --name <id> Server name (required)\n --command <bin> Executable (required)\n --args <json> JSON array of args (optional)\n --scope user|project Default: user\n --enabled true|false Default: true\n --cwd <path> Required when scope=project\n --remove-mcp Remove an MCP server entry\n --name <id> --scope user|project [--cwd <path>]\n --print-ssh-targets Print SSH deploy/monitor targets\n --set-ssh-target Upsert target (--json '{...}' or flags)\n --remove-ssh-target --id <id>\n --test-ssh-target --id <id> (BatchMode ssh true)\n --print-ssh-pubkey --path <private-or-.pub> (display public key)\n\nEnvironment:\n ZELARI_NO_WIZARD=1 Skip the first-run wizard\n ZELARI_SKIP_PREFLIGHT=1 Skip the boot prerequisite check\n ZELARI_NO_PLUGIN_PROMPT=1 Skip the boot plugin-install prompt\n ANATHEMA_DEV=1 Disable background update check + preflight\n"
39608
40584
  );
39609
40585
  process.exit(0);
39610
40586
  }
40587
+ if (argv.includes("--print-mcp")) {
40588
+ try {
40589
+ const cwdIdx = argv.indexOf("--cwd");
40590
+ const cwd = cwdIdx >= 0 && argv[cwdIdx + 1] ? argv[cwdIdx + 1] : process.cwd();
40591
+ const snap = listMcpServers(cwd);
40592
+ console.log(JSON.stringify(snap, null, 2));
40593
+ process.exit(0);
40594
+ } catch (err) {
40595
+ console.error(
40596
+ `[zelari-code --print-mcp] ${err instanceof Error ? err.message : String(err)}`
40597
+ );
40598
+ process.exit(1);
40599
+ }
40600
+ }
40601
+ if (argv.includes("--set-mcp")) {
40602
+ try {
40603
+ const get = (flag) => {
40604
+ const i = argv.indexOf(flag);
40605
+ return i >= 0 && argv[i + 1] ? argv[i + 1] : void 0;
40606
+ };
40607
+ const name = get("--name");
40608
+ const command = get("--command");
40609
+ const scopeRaw = get("--scope") ?? "user";
40610
+ const scope = scopeRaw === "project" ? "project" : "user";
40611
+ const cwd = get("--cwd") ?? process.cwd();
40612
+ const enabledRaw = get("--enabled");
40613
+ const enabled = enabledRaw === void 0 ? true : enabledRaw !== "false";
40614
+ let args;
40615
+ const argsRaw = get("--args");
40616
+ if (argsRaw) {
40617
+ const parsed = JSON.parse(argsRaw);
40618
+ if (!Array.isArray(parsed)) throw new Error("--args must be a JSON array");
40619
+ args = parsed.map(String);
40620
+ }
40621
+ if (!name || !command) {
40622
+ throw new Error("--name and --command are required");
40623
+ }
40624
+ const result = upsertMcpServer({
40625
+ scope,
40626
+ name,
40627
+ projectRoot: cwd,
40628
+ config: { command, args, enabled }
40629
+ });
40630
+ if (!result.ok) throw new Error(result.error);
40631
+ console.log(JSON.stringify({ ok: true, path: result.path, name, scope }));
40632
+ process.exit(0);
40633
+ } catch (err) {
40634
+ console.error(
40635
+ `[zelari-code --set-mcp] ${err instanceof Error ? err.message : String(err)}`
40636
+ );
40637
+ process.exit(1);
40638
+ }
40639
+ }
40640
+ if (argv.includes("--remove-mcp")) {
40641
+ try {
40642
+ const get = (flag) => {
40643
+ const i = argv.indexOf(flag);
40644
+ return i >= 0 && argv[i + 1] ? argv[i + 1] : void 0;
40645
+ };
40646
+ const name = get("--name");
40647
+ const scopeRaw = get("--scope") ?? "user";
40648
+ const scope = scopeRaw === "project" ? "project" : "user";
40649
+ const cwd = get("--cwd") ?? process.cwd();
40650
+ if (!name) throw new Error("--name is required");
40651
+ const result = removeMcpServer({ scope, name, projectRoot: cwd });
40652
+ if (!result.ok) throw new Error(result.error);
40653
+ console.log(JSON.stringify({ ok: true, path: result.path, name, scope }));
40654
+ process.exit(0);
40655
+ } catch (err) {
40656
+ console.error(
40657
+ `[zelari-code --remove-mcp] ${err instanceof Error ? err.message : String(err)}`
40658
+ );
40659
+ process.exit(1);
40660
+ }
40661
+ }
40662
+ if (argv.includes("--print-ssh-targets")) {
40663
+ try {
40664
+ console.log(JSON.stringify(listSshTargets(), null, 2));
40665
+ process.exit(0);
40666
+ } catch (err) {
40667
+ console.error(
40668
+ `[zelari-code --print-ssh-targets] ${err instanceof Error ? err.message : String(err)}`
40669
+ );
40670
+ process.exit(1);
40671
+ }
40672
+ }
40673
+ if (argv.includes("--set-ssh-target")) {
40674
+ try {
40675
+ const get = (flag) => {
40676
+ const i = argv.indexOf(flag);
40677
+ return i >= 0 && argv[i + 1] ? argv[i + 1] : void 0;
40678
+ };
40679
+ let target;
40680
+ const jsonRaw = get("--json");
40681
+ if (jsonRaw) {
40682
+ target = JSON.parse(jsonRaw);
40683
+ } else {
40684
+ const id = get("--id");
40685
+ const host = get("--host");
40686
+ const user = get("--user");
40687
+ if (!id || !host || !user) {
40688
+ throw new Error("Need --json or --id --host --user");
40689
+ }
40690
+ const allowedRaw = get("--allowed");
40691
+ const authFlag = get("--auth");
40692
+ const auth = authFlag === "password" || get("--password") ? "password" : get("--key-path") ? "keyPath" : "agent";
40693
+ target = {
40694
+ id,
40695
+ name: get("--name") ?? id,
40696
+ host,
40697
+ user,
40698
+ port: get("--port") ? Number(get("--port")) : 22,
40699
+ auth,
40700
+ keyPath: get("--key-path"),
40701
+ password: get("--password"),
40702
+ defaultRemotePath: get("--remote-path"),
40703
+ allowedCommands: allowedRaw ? allowedRaw.split("|").map((s) => s.trim()).filter(Boolean) : [],
40704
+ enabled: get("--enabled") !== "false"
40705
+ };
40706
+ }
40707
+ const result = upsertSshTarget(target);
40708
+ if (!result.ok) throw new Error(result.error);
40709
+ console.log(JSON.stringify({ ok: true, id: target.id }));
40710
+ process.exit(0);
40711
+ } catch (err) {
40712
+ console.error(
40713
+ `[zelari-code --set-ssh-target] ${err instanceof Error ? err.message : String(err)}`
40714
+ );
40715
+ process.exit(1);
40716
+ }
40717
+ }
40718
+ if (argv.includes("--remove-ssh-target")) {
40719
+ try {
40720
+ const i = argv.indexOf("--id");
40721
+ const id = i >= 0 ? argv[i + 1] : void 0;
40722
+ if (!id) throw new Error("--id is required");
40723
+ const result = removeSshTarget(id);
40724
+ if (!result.ok) throw new Error(result.error);
40725
+ console.log(JSON.stringify({ ok: true, id }));
40726
+ process.exit(0);
40727
+ } catch (err) {
40728
+ console.error(
40729
+ `[zelari-code --remove-ssh-target] ${err instanceof Error ? err.message : String(err)}`
40730
+ );
40731
+ process.exit(1);
40732
+ }
40733
+ }
40734
+ if (argv.includes("--test-ssh-target")) {
40735
+ const i = argv.indexOf("--id");
40736
+ const id = i >= 0 ? argv[i + 1] : void 0;
40737
+ if (!id) {
40738
+ console.error("[zelari-code --test-ssh-target] --id is required");
40739
+ process.exit(1);
40740
+ }
40741
+ void testSshTarget(id).then((result) => {
40742
+ console.log(JSON.stringify(result));
40743
+ process.exit(result.ok ? 0 : 1);
40744
+ }).catch((err) => {
40745
+ console.error(
40746
+ `[zelari-code --test-ssh-target] ${err instanceof Error ? err.message : String(err)}`
40747
+ );
40748
+ process.exit(1);
40749
+ });
40750
+ return { kind: "done" };
40751
+ }
40752
+ if (argv.includes("--print-ssh-pubkey")) {
40753
+ try {
40754
+ const i = argv.indexOf("--path");
40755
+ const p3 = i >= 0 ? argv[i + 1] : void 0;
40756
+ if (!p3) throw new Error("--path is required");
40757
+ const result = readSshPublicKey(p3);
40758
+ console.log(JSON.stringify(result));
40759
+ process.exit(result.ok ? 0 : 1);
40760
+ } catch (err) {
40761
+ console.error(
40762
+ `[zelari-code --print-ssh-pubkey] ${err instanceof Error ? err.message : String(err)}`
40763
+ );
40764
+ process.exit(1);
40765
+ }
40766
+ }
39611
40767
  if (wantsPrintConfig(argv)) {
39612
40768
  try {
39613
40769
  printDesktopConfig();