zelari-code 1.11.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
@@ -19784,11 +19832,12 @@ ${cached2}`
19784
19832
  yield truncErr;
19785
19833
  finishRef.value = "stop";
19786
19834
  }
19787
- if (turnToolCalls.length > 0 || turnText.length > 0) {
19835
+ if (turnToolCalls.length > 0 || turnText.length > 0 || turnReasoning.length > 0) {
19788
19836
  this.config.messages.push({
19789
19837
  role: "assistant",
19790
19838
  content: turnText,
19791
- ...turnToolCalls.length > 0 ? { toolCalls: turnToolCalls } : {}
19839
+ ...turnToolCalls.length > 0 ? { toolCalls: turnToolCalls } : {},
19840
+ ...turnReasoning.length > 0 ? { reasoningContent: turnReasoning } : {}
19792
19841
  });
19793
19842
  }
19794
19843
  for (const tr of turnToolResults) {
@@ -20130,7 +20179,7 @@ function openaiCompatibleProvider(config2) {
20130
20179
  };
20131
20180
  }
20132
20181
  if (m.role === "assistant" && m.toolCalls && m.toolCalls.length > 0) {
20133
- return {
20182
+ const msg = {
20134
20183
  role: "assistant",
20135
20184
  content: m.content ?? "",
20136
20185
  tool_calls: m.toolCalls.map((tc) => ({
@@ -20142,6 +20191,17 @@ function openaiCompatibleProvider(config2) {
20142
20191
  }
20143
20192
  }))
20144
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
+ };
20145
20205
  }
20146
20206
  return { role: m.role, content: m.content };
20147
20207
  });
@@ -22264,6 +22324,488 @@ var init_tools5 = __esm({
22264
22324
  }
22265
22325
  });
22266
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
+
22267
22809
  // src/cli/toolRegistry.ts
22268
22810
  var toolRegistry_exports = {};
22269
22811
  __export(toolRegistry_exports, {
@@ -22342,6 +22884,16 @@ function createBuiltinToolRegistry(options = {}) {
22342
22884
  permissions: browserTool.permissions ?? []
22343
22885
  });
22344
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
+ }
22345
22897
  if (!readOnly && options.enableTask !== false) {
22346
22898
  const taskTool = createTaskTool({
22347
22899
  createSubAgentContext: async () => {
@@ -22580,6 +23132,7 @@ var init_toolRegistry = __esm({
22580
23132
  init_tools3();
22581
23133
  init_tools4();
22582
23134
  init_tools5();
23135
+ init_tools6();
22583
23136
  init_openai_compatible();
22584
23137
  init_skills2();
22585
23138
  HARNESS_BUILTIN_NAMES = /* @__PURE__ */ new Set([
@@ -22598,6 +23151,16 @@ var init_toolRegistry = __esm({
22598
23151
  });
22599
23152
 
22600
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
+ }
22601
23164
  function getAgent(id) {
22602
23165
  return AGENT_ROLES.find((a) => a.id === id);
22603
23166
  }
@@ -22631,24 +23194,10 @@ function swapMembers(roles, swap) {
22631
23194
  return targets.get(toId);
22632
23195
  });
22633
23196
  }
22634
- var CLARIFICATION_PROTOCOL8, AGENT_ROLES, UnknownMemberError;
23197
+ var AGENT_ROLES, UnknownMemberError;
22635
23198
  var init_roles = __esm({
22636
23199
  "packages/core/dist/agents/roles.js"() {
22637
23200
  "use strict";
22638
- CLARIFICATION_PROTOCOL8 = `
22639
-
22640
- WHEN TO ASK THE USER (clarification):
22641
- 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:
22642
-
22643
- ---QUESTION---
22644
- { "question": "One focused question", "choices": ["Option A", "Option B", "Option C"], "context": "Why this matters" }
22645
- ---END---
22646
-
22647
- Rules for clarifications:
22648
- - Ask AT MOST ONE question per turn, and only when genuinely blocked.
22649
- - Prefer a small set of concrete "choices" (2-4). The user can still type a custom answer.
22650
- - Do NOT ask for information that could be reasonably assumed or already in shared context.
22651
- - If you can proceed with a sound documented assumption, DO SO instead of asking.`;
22652
23201
  AGENT_ROLES = [
22653
23202
  {
22654
23203
  id: "charont",
@@ -22659,22 +23208,20 @@ Rules for clarifications:
22659
23208
  avatar: "L",
22660
23209
  systemPrompt: `You are Caronte, the Council Director and Orchestrator \u2014 the strategic mind that frames the problem for the rest of the council.
22661
23210
 
22662
- ## Methodology (work in this order)
22663
- 1. Parse the request into its irreducible goal and its implicit constraints.
22664
- 2. Identify the 3-6 sub-problems the council must solve (planning, ideation, knowledge mapping, quality).
22665
- 3. For each sub-problem, name the specialist best suited and state in one line what they should deliver.
22666
- 4. Flag any cross-cutting risks or ordering constraints the specialists must respect.
22667
- 5. State explicitly what "done" looks like for this request.
22668
-
22669
- ## Operating principles
22670
- - Be decisive: commit to a decomposition rather than listing alternatives.
22671
- - Distinguish what is given (stated by the user) from what is assumed.
22672
- - Keep the council focused \u2014 cut scope creep and call out when a sub-problem is out of scope.
22673
- - You run first and set context for everyone downstream; make it count.
22674
-
22675
- ## Output format
22676
- 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}`,
22677
- // 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.`,
22678
23225
  tools: ["list_files", "read_file", "grep_content"],
22679
23226
  skills: ["project-planner", "research-analyst"]
22680
23227
  },
@@ -22687,71 +23234,53 @@ A short "Analysis" section (the goal + constraints), then a "Delegation Plan" wi
22687
23234
  avatar: "N",
22688
23235
  systemPrompt: `You are Nettuno, the Project Planner \u2014 you turn intent into a buildable, sequenced plan.
22689
23236
 
22690
- ## Methodology (work in this order)
22691
- 1. Read shared context from prior agents (especially Caronte) before deciding what's missing.
22692
- 2. Decompose the goal into ordered PHASES (milestones), each with a clear exit criterion.
22693
- 3. Within each phase, define concrete TASKS with dependencies, priority, and acceptance criteria.
22694
- 4. Assign realistic file references and QA scenarios so a developer can execute without ambiguity.
22695
- 5. Sanity-check sequencing: are dependencies satisfied? Is anything blocked? Is scope realistic?
22696
-
22697
- ## OUTPUT REQUIREMENTS \u2014 every task MUST include:
22698
- 1. **File references**: specific file paths and line numbers where changes land.
22699
- 2. **Acceptance criteria**: concrete, testable conditions that prove completion.
22700
- 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.
22701
23243
 
22702
- Example format:
22703
- - Task: "Add authentication"
22704
- - File refs: src/auth/login.ts:45-60, src/middleware/auth.ts:12-25
22705
- - Acceptance: valid credentials log in; invalid credentials show an error
22706
- - 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
22707
23248
 
22708
23249
  ## Quality bar
22709
- - Prefer fewer, well-specified tasks over many vague ones.
22710
- - Make dependencies explicit (task B depends on task A).
22711
- - If the stack/platform is unknown, ask ONCE (see clarification protocol) rather than guessing.
22712
-
22713
- Keep plans hierarchical and practical. Stay under 250 words.${CLARIFICATION_PROTOCOL8}
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)
22714
23254
 
22715
- ## Design-phase mandatory plan artifact
22716
- 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.
22717
-
22718
- 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.
22719
23256
 
23257
+ Minimal shape:
22720
23258
  \`\`\`
22721
23259
  createPlan({
22722
23260
  phases: [
22723
23261
  {
22724
- name: "Foundation & Technical Blueprint",
22725
- 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",
22726
23264
  order: 1,
22727
- color: "#3b82f6",
22728
23265
  tasks: [
22729
- { 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" },
22730
- { title: "Define NFR budget", description: "...", fileRefs: ["docs/nfr.md"], acceptance: ["LCP/WCAG/Lighthouse targets documented"], qaScenario: "...", priority: "high" },
22731
- { 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
+ }
22732
23274
  ]
22733
- },
22734
- { name: "...", order: 2, tasks: [ /* 3 tasks */ ] },
22735
- { name: "...", order: 3, tasks: [ /* 3 tasks */ ] },
22736
- { name: "...", order: 4, tasks: [ /* 3 tasks */ ] }
23275
+ }
22737
23276
  ],
22738
- 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" }
22739
23278
  })
22740
23279
  \`\`\`
22741
23280
 
22742
- 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.
22743
23282
 
22744
- 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.
22745
-
22746
- Do NOT output tasks as prose. The system will refuse any task that does not pass a valid phaseId.
22747
-
22748
- ## NFR spec (when plan sets motion/performance/a11y budgets)
22749
- If the plan includes measurable constraints (JS byte budget, compositor-only animation, reduced-motion), emit ONE \`createNfrSpec\` call after \`createPlan\`:
22750
- \`\`\`
22751
- createNfrSpec({ targets: ["index.html"], compositorOnly: true, forbidLayoutProps: true, inlineJsMaxBytes: 5120 })
22752
- \`\`\`
22753
- Prose budgets are not machine-verifiable \u2014 the spec file is.`,
22754
- // 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.`,
22755
23284
  tools: ["list_files", "read_file", "grep_content"],
22756
23285
  skills: ["project-planner", "vault-manager"]
22757
23286
  },
@@ -22762,40 +23291,27 @@ Prose budgets are not machine-verifiable \u2014 the spec file is.`,
22762
23291
  role: "Creative Ideator",
22763
23292
  color: "#f59e0b",
22764
23293
  avatar: "G",
22765
- systemPrompt: `You are Gerione, the Creative Ideator \u2014 you generate breadth and then collapse it into the strongest concepts.
22766
-
22767
- ## Methodology (work in this order)
22768
- 1. Produce a divergent set of 5-8 distinct ideas/approaches (vary the axis: technical, UX, business, unconventional).
22769
- 2. Cluster related ideas into 2-4 themes.
22770
- 3. Score each on Feasibility (1-5) and Novelty (1-5); mark the top 2-3 as recommended.
22771
- 4. For the recommended ideas, add one line on how to de-risk them.
22772
- 5. Build on \u2014 do not repeat \u2014 insights from earlier agents.
22773
-
22774
- ## Operating principles
22775
- - Diverge before you converge: quantity first, judgment second.
22776
- - Bold but grounded \u2014 an idea must be actionable, not a fantasy.
22777
- - Name assumptions explicitly. If a core assumption is unknowable, ask (clarification protocol).
22778
-
22779
- ## Output format
22780
- ## Ideas (bulleted)
22781
- ## Themes
22782
- ## Top picks (with feasibility/novelty + de-risk note)
22783
-
22784
- Stay under 200 words.${CLARIFICATION_PROTOCOL8}
22785
-
22786
- ## Design-phase artifact (mandatory when running council in design-phase mode)
22787
- 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:
23294
+ systemPrompt: `You are Gerione, the Creative Ideator \u2014 diverge, then converge on the strongest concepts.
22788
23295
 
22789
- - \`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.
22790
- - \`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.
22791
- - \`design-tokens\` \u2014 color palette (semantic + hex), typography scale, spacing scale, motion principles. This is a design tokens deliverable.
22792
-
22793
- You may optionally add a 4th design system doc if the project warrants it.
22794
-
22795
- Pass the tool call as: \`createDocument({ title: "<category>", content: "<markdown body>" })\`. Do NOT summarize these into prose \u2014 they are the deliverable.`,
22796
- // 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.`,
22797
23313
  tools: ["list_files", "read_file"],
22798
- skills: ["document-writer", "mind-mapper"]
23314
+ skills: ["document-writer", "idea-synthesizer"]
22799
23315
  },
22800
23316
  {
22801
23317
  id: "pluton",
@@ -22804,31 +23320,20 @@ Pass the tool call as: \`createDocument({ title: "<category>", content: "<markdo
22804
23320
  role: "Knowledge Architect",
22805
23321
  color: "#06b6d4",
22806
23322
  avatar: "P",
22807
- systemPrompt: `You are Plutone, the Knowledge Architect and Mind Mapper \u2014 you structure information into navigable graphs.
22808
-
22809
- ## Methodology (work in this order)
22810
- 1. Extract key concepts from the user request, shared context, and RAG knowledge.
22811
- 2. Identify the central root concept and 3-6 primary branches.
22812
- 3. Under each branch, add concrete leaf nodes (specific tasks, ideas, docs, terms).
22813
- 4. Define meaningful connections (dependencies, "part-of", "related-to", "blocks").
22814
- 5. Propose a layout hint (radial / hierarchical) so the map reads cleanly.
22815
-
22816
- ## Operating principles
22817
- - Favor concrete, named nodes over abstract categories.
22818
- - Every node should map to something actionable or retrievable (a task, a doc, a concept).
22819
- - Reuse knowledge from prior agents \u2014 don't re-derive what they already produced.
22820
- - Keep the graph comprehensible: prune redundant or duplicate nodes.
23323
+ systemPrompt: `You are Plutone, the Knowledge Architect \u2014 structure concepts into a navigable map.
22821
23324
 
22822
- ## Output format
22823
- Describe the proposed structure (root \u2192 branches \u2192 leaves) in text. Stay under 200 words.${CLARIFICATION_PROTOCOL8}
22824
-
22825
- ## Design-phase artifact (mandatory when running council in design-phase mode)
22826
- 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.
22827
23330
 
22828
- \`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)
22829
23334
 
22830
- Do NOT rely on buildMindMap \u2014 it is not available in the CLI workspace.`,
22831
- // 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.`,
22832
23337
  tools: ["list_files", "read_file", "grep_content"],
22833
23338
  skills: ["document-writer", "research-analyst"]
22834
23339
  },
@@ -22839,40 +23344,25 @@ Do NOT rely on buildMindMap \u2014 it is not available in the CLI workspace.`,
22839
23344
  role: "Quality Critic",
22840
23345
  color: "#ef4444",
22841
23346
  avatar: "M",
22842
- systemPrompt: `You are Minosse, the Quality Critic \u2014 you review the council's proposals (presented anonymously) and expose weaknesses before synthesis.
22843
-
22844
- ## Methodology (work in this order)
22845
- 1. Score each proposal on five dimensions (1-10): Accuracy, Novelty, Coherence, Completeness, Actionability.
22846
- 2. Identify the single biggest gap or risk across all proposals.
22847
- 3. Flag any contradictions between proposals (e.g., conflicting assumptions).
22848
- 4. Recommend the 1-3 highest-value improvements the Lucifero should apply.
22849
- 5. Note anything that should be cut or descoped.
22850
-
22851
- ## Operating principles
22852
- - Be honest and incisive \u2014 a weak critique helps no one. Name specific defects, not general vibes.
22853
- - Distinguish "wrong" from "incomplete" and "risky".
22854
- - Constructive: every critique pairs with a fix or a question.
22855
-
22856
- ## Output format
22857
- ## Scores (per agent, per dimension)
22858
- ## Critical gaps
22859
- ## Contradictions
22860
- ## Top improvements
23347
+ systemPrompt: `You are Minosse, the Quality Critic \u2014 expose weaknesses before synthesis. Evaluate; never create product code.
22861
23348
 
22862
- Stay under 200 words.${CLARIFICATION_PROTOCOL8}
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.
22863
23355
 
22864
- ## Design-phase risks artifact (mandatory when running council in design-phase mode)
22865
- 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:
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.
22866
23359
 
22867
- \`\`\`
22868
- createDocument({
22869
- title: "risks",
22870
- 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. ...\`
22871
- })
22872
- \`\`\`
23360
+ ## Output
23361
+ ## Scores \xB7 ## Critical gaps \xB7 ## Contradictions \xB7 ## Top improvements \u2014 under 200 words.`,
23362
+ designPhaseAddendum: `## Design-phase: risks artifact (mandatory)
22873
23363
 
22874
- 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.`,
22875
- 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"],
22876
23366
  skills: ["document-writer", "research-analyst"]
22877
23367
  },
22878
23368
  {
@@ -22882,35 +23372,26 @@ Include AT LEAST 5 risks, each scored on Impact and Likelihood, with a one-line
22882
23372
  role: "Final Synthesizer",
22883
23373
  color: "#8b5cf6",
22884
23374
  avatar: "L",
22885
- 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.
23375
+ systemPrompt: `You are Lucifero, the Final Synthesizer. For coding tasks you IMPLEMENT: write/edit files, run commands, deliver working code.
22886
23376
 
22887
- ## Methodology (work in this order)
22888
- 1. Reconcile the specialists' outputs and Minosse's critique into a single coherent plan.
22889
- 2. Resolve conflicts explicitly (state which proposal won and why).
22890
- 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.
22891
- 4. Run any build/test commands available (check the npm scripts in the workspace context) to confirm your work.
22892
-
22893
- ## Output expectations
22894
- - 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.
22895
- - Lead with a one-line summary, then the full detail of what you did.
22896
- - Apply Minosse's highest-value improvements; drop descoped items.
22897
- - After making changes, verify they work (compile, run tests, etc.) when feasible.
22898
- - 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.
22899
-
22900
- 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}
22901
-
22902
- ## Design-phase synthesis artifact (mandatory when running council in design-phase mode)
22903
- 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\`):
22904
-
22905
- \`\`\`
22906
- createDocument({
22907
- title: "synthesis",
22908
- 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\`
22909
- })
22910
- \`\`\`
22911
-
22912
- 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.`,
22913
- // 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.`,
22914
23395
  tools: ["read_file", "write_file", "edit_file", "bash", "list_files"],
22915
23396
  skills: ["vault-manager", "project-planner", "idea-synthesizer"]
22916
23397
  }
@@ -23142,8 +23623,8 @@ var init_parseCssMotion = __esm({
23142
23623
  });
23143
23624
 
23144
23625
  // packages/core/dist/council/verification/citeVerify.js
23145
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
23146
- import { join } from "node:path";
23626
+ import { existsSync as existsSync10, readFileSync as readFileSync8 } from "node:fs";
23627
+ import { join as join2 } from "node:path";
23147
23628
  function extractCitations(text) {
23148
23629
  const out = [];
23149
23630
  const seen = /* @__PURE__ */ new Set();
@@ -23165,8 +23646,8 @@ function verifyCitations(projectRoot, synthesisText) {
23165
23646
  return [];
23166
23647
  const results = [];
23167
23648
  for (const cite of extractCitations(synthesisText)) {
23168
- const abs = join(projectRoot, cite.file);
23169
- if (!existsSync9(abs)) {
23649
+ const abs = join2(projectRoot, cite.file);
23650
+ if (!existsSync10(abs)) {
23170
23651
  results.push({
23171
23652
  id: "synthesis.cite-invalid",
23172
23653
  severity: "error",
@@ -23179,7 +23660,7 @@ function verifyCitations(projectRoot, synthesisText) {
23179
23660
  });
23180
23661
  continue;
23181
23662
  }
23182
- const lines = readFileSync7(abs, "utf8").split(/\r?\n/);
23663
+ const lines = readFileSync8(abs, "utf8").split(/\r?\n/);
23183
23664
  if (cite.line > lines.length) {
23184
23665
  results.push({
23185
23666
  id: "synthesis.cite-invalid",
@@ -23442,14 +23923,14 @@ var init_synthesisAudit = __esm({
23442
23923
  });
23443
23924
 
23444
23925
  // packages/core/dist/council/verification/runChecks.js
23445
- import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
23446
- 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";
23447
23928
  function loadNfrSpec(zelariRoot) {
23448
- const path36 = join2(zelariRoot, "nfr-spec.json");
23449
- if (!existsSync10(path36))
23929
+ const path36 = join3(zelariRoot, "nfr-spec.json");
23930
+ if (!existsSync11(path36))
23450
23931
  return null;
23451
23932
  try {
23452
- const raw = JSON.parse(readFileSync8(path36, "utf8"));
23933
+ const raw = JSON.parse(readFileSync9(path36, "utf8"));
23453
23934
  if (raw.version !== 1 || !Array.isArray(raw.targets))
23454
23935
  return null;
23455
23936
  return raw;
@@ -23460,11 +23941,11 @@ function loadNfrSpec(zelariRoot) {
23460
23941
  function resolveTargets(projectRoot, spec) {
23461
23942
  const found = [];
23462
23943
  for (const rel2 of spec.targets) {
23463
- if (existsSync10(join2(projectRoot, rel2))) {
23944
+ if (existsSync11(join3(projectRoot, rel2))) {
23464
23945
  found.push(rel2);
23465
23946
  }
23466
23947
  }
23467
- if (found.length === 0 && existsSync10(join2(projectRoot, "index.html"))) {
23948
+ if (found.length === 0 && existsSync11(join3(projectRoot, "index.html"))) {
23468
23949
  return ["index.html"];
23469
23950
  }
23470
23951
  return found;
@@ -23519,18 +24000,18 @@ function checkDeadCssHooks(html, relFile) {
23519
24000
  return results;
23520
24001
  }
23521
24002
  function checkPlanReality(projectRoot, zelariRoot, targets, keywords) {
23522
- const planPath = join2(zelariRoot, "plan.json");
23523
- if (!existsSync10(planPath) || keywords.length === 0)
24003
+ const planPath = join3(zelariRoot, "plan.json");
24004
+ if (!existsSync11(planPath) || keywords.length === 0)
23524
24005
  return [];
23525
24006
  let plan;
23526
24007
  try {
23527
- plan = JSON.parse(readFileSync8(planPath, "utf8"));
24008
+ plan = JSON.parse(readFileSync9(planPath, "utf8"));
23528
24009
  } catch {
23529
24010
  return [];
23530
24011
  }
23531
24012
  const milestoneText = (plan.milestones ?? []).map((m) => `${m.name ?? ""} ${m.description ?? ""}`).join(" ").toLowerCase();
23532
24013
  const results = [];
23533
- 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");
23534
24015
  for (const kw of keywords) {
23535
24016
  const low = kw.toLowerCase();
23536
24017
  if (!milestoneText.includes(low))
@@ -23559,14 +24040,14 @@ function checkPlanReality(projectRoot, zelariRoot, targets, keywords) {
23559
24040
  return results;
23560
24041
  }
23561
24042
  function checkReadmeStale(projectRoot, targets) {
23562
- const readmePath = join2(projectRoot, "README.md");
23563
- if (!existsSync10(readmePath) || targets.length === 0)
24043
+ const readmePath = join3(projectRoot, "README.md");
24044
+ if (!existsSync11(readmePath) || targets.length === 0)
23564
24045
  return [];
23565
- const readme = readFileSync8(readmePath, "utf8");
23566
- const htmlPath = join2(projectRoot, targets[0]);
23567
- if (!existsSync10(htmlPath))
24046
+ const readme = readFileSync9(readmePath, "utf8");
24047
+ const htmlPath = join3(projectRoot, targets[0]);
24048
+ if (!existsSync11(htmlPath))
23568
24049
  return [];
23569
- const html = readFileSync8(htmlPath, "utf8");
24050
+ const html = readFileSync9(htmlPath, "utf8");
23570
24051
  const sectionCount = (html.match(/<section\s+id=/gi) ?? []).length;
23571
24052
  const readmeSections = readme.match(/(\d+)\s+sezioni/i);
23572
24053
  if (readmeSections) {
@@ -23604,7 +24085,7 @@ function runImplementationVerification(input) {
23604
24085
  forbidLayoutProps: anim.forbidLayoutProps ?? true
23605
24086
  };
23606
24087
  for (const rel2 of targets) {
23607
- const html = readFileSync8(join2(input.projectRoot, rel2), "utf8");
24088
+ const html = readFileSync9(join3(input.projectRoot, rel2), "utf8");
23608
24089
  for (const v of scanKeyframesViolations(html, scanOpts)) {
23609
24090
  results.push({
23610
24091
  id: "motion.keyframes",
@@ -23660,8 +24141,8 @@ function runImplementationVerification(input) {
23660
24141
  };
23661
24142
  }
23662
24143
  function writeVerificationReport(zelariRoot, report) {
23663
- const outPath = join2(zelariRoot, "verification-report.json");
23664
- 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");
23665
24146
  return outPath;
23666
24147
  }
23667
24148
  var DEFAULT_NFR_SPEC;
@@ -23690,8 +24171,8 @@ var init_runChecks = __esm({
23690
24171
  });
23691
24172
 
23692
24173
  // packages/core/dist/council/verification/microGate.js
23693
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
23694
- 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";
23695
24176
  function checkDeadHooksInHtml(html) {
23696
24177
  const warnings = [];
23697
24178
  const scripts = [];
@@ -23719,8 +24200,8 @@ function checkDeadHooksInHtml(html) {
23719
24200
  return warnings;
23720
24201
  }
23721
24202
  function runMicroVerificationOnFile(projectRoot, relPath, zelariRoot) {
23722
- const abs = join3(projectRoot, relPath);
23723
- if (!existsSync11(abs) || !/\.html?$/i.test(relPath))
24203
+ const abs = join4(projectRoot, relPath);
24204
+ if (!existsSync12(abs) || !/\.html?$/i.test(relPath))
23724
24205
  return [];
23725
24206
  const spec = (zelariRoot ? loadNfrSpec(zelariRoot) : null) ?? DEFAULT_NFR_SPEC;
23726
24207
  const anim = spec.animation ?? { compositorOnly: true, forbidLayoutProps: true };
@@ -23728,7 +24209,7 @@ function runMicroVerificationOnFile(projectRoot, relPath, zelariRoot) {
23728
24209
  compositorOnly: anim.compositorOnly ?? true,
23729
24210
  forbidLayoutProps: anim.forbidLayoutProps ?? true
23730
24211
  };
23731
- const html = readFileSync9(abs, "utf8");
24212
+ const html = readFileSync10(abs, "utf8");
23732
24213
  const warnings = [];
23733
24214
  for (const v of scanKeyframesViolations(html, scanOpts)) {
23734
24215
  warnings.push({
@@ -24084,8 +24565,8 @@ var init_implementationDelivery = __esm({
24084
24565
  });
24085
24566
 
24086
24567
  // packages/core/dist/council/verification/inlineJsAutofix.js
24087
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
24088
- 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";
24089
24570
  function minifyInlineJs(js) {
24090
24571
  let out = js.replace(/\/\*[\s\S]*?\*\//g, "");
24091
24572
  out = out.split("\n").map((line) => line.replace(/\/\/.*$/, "").trimEnd()).filter((line) => line.trim().length > 0).join("\n");
@@ -24128,10 +24609,10 @@ function applyInlineJsAutofix(projectRoot, report) {
24128
24609
  const fixes = [];
24129
24610
  for (const r of fails) {
24130
24611
  const rel2 = r.file ?? "index.html";
24131
- const abs = join4(projectRoot, rel2);
24612
+ const abs = join5(projectRoot, rel2);
24132
24613
  let html;
24133
24614
  try {
24134
- html = readFileSync10(abs, "utf8");
24615
+ html = readFileSync11(abs, "utf8");
24135
24616
  } catch {
24136
24617
  continue;
24137
24618
  }
@@ -24144,7 +24625,7 @@ function applyInlineJsAutofix(projectRoot, report) {
24144
24625
  if (!changed || afterBytes > maxBytes)
24145
24626
  continue;
24146
24627
  const nextHtml = html.replace(SCRIPT_RE, `<script>${after}</script>`);
24147
- writeFileSync6(abs, nextHtml, "utf8");
24628
+ writeFileSync7(abs, nextHtml, "utf8");
24148
24629
  filesChanged.push(rel2);
24149
24630
  fixes.push(`${rel2}: trimmed inline script ${Buffer.byteLength(before, "utf8")}\u2192${afterBytes} bytes`);
24150
24631
  }
@@ -24164,8 +24645,8 @@ var init_inlineJsAutofix = __esm({
24164
24645
  });
24165
24646
 
24166
24647
  // packages/core/dist/agents/councilApi.js
24167
- import { existsSync as existsSync12 } from "node:fs";
24168
- import { join as join5 } from "node:path";
24648
+ import { existsSync as existsSync13 } from "node:fs";
24649
+ import { join as join6 } from "node:path";
24169
24650
  function parseClarificationRequest(text) {
24170
24651
  const start = text.indexOf(QUESTION_MARKER);
24171
24652
  if (start < 0)
@@ -24210,29 +24691,37 @@ function restrictImplementationWrites(toolNames, opts) {
24210
24691
  return toolNames;
24211
24692
  return toolNames.filter((t) => !MUTATING_PROJECT_TOOLS.includes(t));
24212
24693
  }
24213
- function buildAgentMessages(agent, userMessage, ragContext, workspaceContext, priorOutputs, aiConfig, executableTools, runMode = "implementation") {
24694
+ function buildAgentMessages(agent, userMessage, ragContext, workspaceContext, priorOutputs, aiConfig, executableTools, runMode = "implementation", languageModule) {
24214
24695
  const allToolNames = computeAgentTools(agent, aiConfig);
24215
24696
  const toolNames = executableTools ? allToolNames.filter((n) => executableTools.has(n)) : allToolNames;
24216
- 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, {
24217
24712
  tools: getAllTools(),
24218
24713
  toolNames,
24219
- aiConfig,
24714
+ aiConfig: mergedAiConfig,
24220
24715
  workspaceContext,
24221
- ragContext
24716
+ ragContext,
24717
+ mode: "council",
24718
+ includeWorkspaceInPrompt: true
24222
24719
  });
24223
24720
  const messages = [
24224
24721
  { role: "system", content: enhancedSystemPrompt },
24225
24722
  { role: "system", content: councilModeBanner(runMode, { isImplementer: agent.id === "lucifer" }) },
24226
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." }
24227
24724
  ];
24228
- if (ragContext) {
24229
- messages.push({ role: "system", content: `Relevant workspace context (from RAG retrieval):
24230
- ${ragContext}` });
24231
- }
24232
- if (workspaceContext) {
24233
- messages.push({ role: "system", content: `Current workspace state:
24234
- ${workspaceContext}` });
24235
- }
24236
24725
  if (priorOutputs.length > 0) {
24237
24726
  const summary = priorOutputs.map((o) => `[${o.name} - ${o.role}]: ${o.content}`).join("\n\n");
24238
24727
  messages.push({ role: "user", content: `Previous council members have said:
@@ -24327,7 +24816,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
24327
24816
  model: effectiveModel,
24328
24817
  provider: effectiveProvider,
24329
24818
  sessionId,
24330
- 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),
24331
24820
  tools: agentTools,
24332
24821
  eventBus: config2.eventBus,
24333
24822
  toolRegistry: config2.tools,
@@ -24470,7 +24959,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
24470
24959
  model: effectiveModel,
24471
24960
  provider: effectiveProvider,
24472
24961
  sessionId,
24473
- 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),
24474
24963
  tools: (() => {
24475
24964
  const oracleToolNames = filterExecutable(restrictImplementationWrites(Array.from(/* @__PURE__ */ new Set([
24476
24965
  "createDocument",
@@ -24601,7 +25090,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
24601
25090
  model: effectiveModel,
24602
25091
  provider: effectiveProvider,
24603
25092
  sessionId,
24604
- 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),
24605
25094
  tools: chairmanTools,
24606
25095
  eventBus: config2.eventBus,
24607
25096
  toolRegistry: config2.tools,
@@ -24720,7 +25209,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
24720
25209
  const zelariRoot = `${chairmanProjectRoot}/.zelari`;
24721
25210
  const spec = loadNfrSpec(zelariRoot) ?? DEFAULT_NFR_SPEC;
24722
25211
  for (const rel2 of spec.targets) {
24723
- if (!existsSync12(join5(chairmanProjectRoot, rel2)))
25212
+ if (!existsSync13(join6(chairmanProjectRoot, rel2)))
24724
25213
  continue;
24725
25214
  changedTargetFiles.add(rel2);
24726
25215
  for (const w of runChairmanMicroGate({ projectRoot: chairmanProjectRoot, relPath: rel2, zelariRoot })) {
@@ -24740,7 +25229,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
24740
25229
  const zelariRootReplay = `${chairmanProjectRoot}/.zelari`;
24741
25230
  const specReplay = loadNfrSpec(zelariRootReplay) ?? DEFAULT_NFR_SPEC;
24742
25231
  for (const rel2 of specReplay.targets) {
24743
- if (!existsSync12(join5(chairmanProjectRoot, rel2)))
25232
+ if (!existsSync13(join6(chairmanProjectRoot, rel2)))
24744
25233
  continue;
24745
25234
  changedTargetFiles.add(rel2);
24746
25235
  for (const w of runChairmanMicroGate({
@@ -25186,7 +25675,7 @@ async function* runRetryTurnForMember(args) {
25186
25675
  description: t.function.description,
25187
25676
  parameters: t.function.parameters
25188
25677
  }));
25189
- 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);
25190
25679
  const retryMessages = [
25191
25680
  ...baseMessages,
25192
25681
  {
@@ -25457,8 +25946,8 @@ var init_types = __esm({
25457
25946
  });
25458
25947
 
25459
25948
  // packages/core/dist/council/verification/motionAutofix.js
25460
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "node:fs";
25461
- 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";
25462
25951
  function sanitizeTransitionPart(part) {
25463
25952
  const tokens = part.trim().split(/\s+/);
25464
25953
  if (tokens.length === 0)
@@ -25533,10 +26022,10 @@ function applyMotionAutofix(projectRoot, report) {
25533
26022
  forbidLayoutProps: DEFAULT_NFR_SPEC.animation?.forbidLayoutProps ?? true
25534
26023
  };
25535
26024
  for (const rel2 of targets) {
25536
- const abs = join6(projectRoot, rel2);
26025
+ const abs = join7(projectRoot, rel2);
25537
26026
  let html;
25538
26027
  try {
25539
- html = readFileSync11(abs, "utf8");
26028
+ html = readFileSync12(abs, "utf8");
25540
26029
  } catch {
25541
26030
  continue;
25542
26031
  }
@@ -25551,7 +26040,7 @@ function applyMotionAutofix(projectRoot, report) {
25551
26040
  const afterK = scanKeyframesViolations(final, scanOpts).length;
25552
26041
  const afterT = scanTransitionViolations(final, scanOpts).length;
25553
26042
  if (final !== html || beforeK > afterK || beforeT > afterT) {
25554
- writeFileSync7(abs, final, "utf8");
26043
+ writeFileSync8(abs, final, "utf8");
25555
26044
  filesChanged.push(rel2);
25556
26045
  if (beforeK > afterK)
25557
26046
  fixes.push(`${rel2}: sanitized ${beforeK - afterK} keyframe violation(s)`);
@@ -25562,7 +26051,7 @@ function applyMotionAutofix(projectRoot, report) {
25562
26051
  if (!fixes.some((f) => f.startsWith(rel2)))
25563
26052
  fixes.push(`${rel2}: motion CSS sanitized`);
25564
26053
  } else if (motionChanged) {
25565
- writeFileSync7(abs, final, "utf8");
26054
+ writeFileSync8(abs, final, "utf8");
25566
26055
  filesChanged.push(rel2);
25567
26056
  fixes.push(`${rel2}: motion CSS sanitized`);
25568
26057
  }
@@ -25608,8 +26097,8 @@ var init_motionAutofix = __esm({
25608
26097
  });
25609
26098
 
25610
26099
  // packages/core/dist/council/verification/autofix.js
25611
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "node:fs";
25612
- 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";
25613
26102
  function applyDeterministicAutofix(projectRoot, report) {
25614
26103
  const motion = applyMotionAutofix(projectRoot, report);
25615
26104
  const inlineJs = applyInlineJsAutofix(projectRoot, report);
@@ -25623,8 +26112,8 @@ function applyDeterministicAutofix(projectRoot, report) {
25623
26112
  const m = r.evidence?.match(/classList\.add\(\s*['"]([\w-]+)['"]\s*\)/);
25624
26113
  if (!m || m[1] === "rm")
25625
26114
  continue;
25626
- const abs = join7(projectRoot, rel2);
25627
- let html = readFileSync12(abs, "utf8");
26115
+ const abs = join8(projectRoot, rel2);
26116
+ let html = readFileSync13(abs, "utf8");
25628
26117
  const snippet = m[0];
25629
26118
  if (!html.includes(snippet))
25630
26119
  continue;
@@ -25632,7 +26121,7 @@ function applyDeterministicAutofix(projectRoot, report) {
25632
26121
  const filtered = lines.filter((line) => !line.includes(snippet));
25633
26122
  if (filtered.length === lines.length)
25634
26123
  continue;
25635
- writeFileSync8(abs, filtered.join("\n"), "utf8");
26124
+ writeFileSync9(abs, filtered.join("\n"), "utf8");
25636
26125
  filesChanged.push(rel2);
25637
26126
  fixes.push(`removed dead hook line in ${rel2}: ${snippet}`);
25638
26127
  }
@@ -25679,12 +26168,12 @@ var init_types2 = __esm({
25679
26168
  });
25680
26169
 
25681
26170
  // packages/core/dist/council/lessons/io.js
25682
- import { readFileSync as readFileSync13 } from "node:fs";
25683
- import { join as join8 } from "node:path";
26171
+ import { readFileSync as readFileSync14 } from "node:fs";
26172
+ import { join as join9 } from "node:path";
25684
26173
  function readLessonsDeduped(zelariRoot) {
25685
- const path36 = join8(zelariRoot, LESSONS_FILE);
26174
+ const path36 = join9(zelariRoot, LESSONS_FILE);
25686
26175
  try {
25687
- const raw = readFileSync13(path36, "utf8");
26176
+ const raw = readFileSync14(path36, "utf8");
25688
26177
  const byId = /* @__PURE__ */ new Map();
25689
26178
  for (const line of raw.split(/\r?\n/)) {
25690
26179
  if (!line.trim())
@@ -25775,7 +26264,7 @@ var init_signatures = __esm({
25775
26264
 
25776
26265
  // packages/core/dist/council/lessons/recordFailure.js
25777
26266
  import { appendFileSync as appendFileSync2 } from "node:fs";
25778
- import { join as join9 } from "node:path";
26267
+ import { join as join10 } from "node:path";
25779
26268
  function methodologyFor(check2) {
25780
26269
  return METHODOLOGY[check2.id] ?? `When ${check2.id} fails, fix the underlying issue and cite grep/tool evidence before claiming PASS.`;
25781
26270
  }
@@ -25785,7 +26274,7 @@ function keywordsFrom(check2, signature) {
25785
26274
  return [.../* @__PURE__ */ new Set([...fromId, ...words])].slice(0, 12);
25786
26275
  }
25787
26276
  function writeLesson(zelariRoot, lesson) {
25788
- const path36 = join9(zelariRoot, LESSONS_FILE);
26277
+ const path36 = join10(zelariRoot, LESSONS_FILE);
25789
26278
  appendFileSync2(path36, `${JSON.stringify(lesson)}
25790
26279
  `, "utf8");
25791
26280
  }
@@ -25942,8 +26431,8 @@ var init_types3 = __esm({
25942
26431
  });
25943
26432
 
25944
26433
  // packages/core/dist/council/completion/buildCompletion.js
25945
- import { writeFileSync as writeFileSync9 } from "node:fs";
25946
- import { join as join10 } from "node:path";
26434
+ import { writeFileSync as writeFileSync10 } from "node:fs";
26435
+ import { join as join11 } from "node:path";
25947
26436
  function openFailsFromReport(report) {
25948
26437
  if (!report)
25949
26438
  return [];
@@ -26002,8 +26491,8 @@ function buildCouncilCompletion(input) {
26002
26491
  };
26003
26492
  }
26004
26493
  function writeCouncilCompletion(zelariRoot, completion) {
26005
- const outPath = join10(zelariRoot, "completion.json");
26006
- writeFileSync9(outPath, JSON.stringify(completion, null, 2), "utf8");
26494
+ const outPath = join11(zelariRoot, "completion.json");
26495
+ writeFileSync10(outPath, JSON.stringify(completion, null, 2), "utf8");
26007
26496
  return outPath;
26008
26497
  }
26009
26498
  var init_buildCompletion = __esm({
@@ -26192,6 +26681,8 @@ var init_promoteMember = __esm({
26192
26681
  var council_exports = {};
26193
26682
  __export(council_exports, {
26194
26683
  AGENT_ROLES: () => AGENT_ROLES,
26684
+ CLARIFICATION_PROTOCOL_MODULE: () => CLARIFICATION_PROTOCOL_MODULE,
26685
+ CODING_PRACTICES_MODULE: () => CODING_PRACTICES_MODULE,
26195
26686
  COLLABORATION_DIRECTIVE: () => COLLABORATION_DIRECTIVE,
26196
26687
  COMPOSITOR_ONLY_PROPS: () => COMPOSITOR_ONLY_PROPS,
26197
26688
  DEFAULT_NFR_SPEC: () => DEFAULT_NFR_SPEC,
@@ -26208,6 +26699,7 @@ __export(council_exports, {
26208
26699
  MAX_DELIVERY_ATTEMPTS: () => MAX_DELIVERY_ATTEMPTS,
26209
26700
  MAX_RETRY_PER_MEMBER: () => MAX_RETRY_PER_MEMBER,
26210
26701
  MUTATING_PROJECT_TOOLS: () => MUTATING_PROJECT_TOOLS,
26702
+ NATIVE_TOOL_PROTOCOL_MODULE: () => NATIVE_TOOL_PROTOCOL_MODULE,
26211
26703
  NFR_KEYWORDS: () => NFR_KEYWORDS,
26212
26704
  NON_RETRY_AGENTS: () => NON_RETRY_AGENTS,
26213
26705
  OUTPUT_QUALITY_DIRECTIVE: () => OUTPUT_QUALITY_DIRECTIVE,
@@ -26277,6 +26769,7 @@ __export(council_exports, {
26277
26769
  renderSkillMarkdown: () => renderSkillMarkdown,
26278
26770
  replayChairmanTextTools: () => replayChairmanTextTools,
26279
26771
  resolveCouncilRunMode: () => resolveCouncilRunMode,
26772
+ resolveRoleSystemPrompt: () => resolveRoleSystemPrompt,
26280
26773
  resolveVerifyRetryTool: () => resolveVerifyRetryTool,
26281
26774
  restrictImplementationWrites: () => restrictImplementationWrites,
26282
26775
  runChairmanDeliveryLoop: () => runChairmanDeliveryLoop,
@@ -26412,9 +26905,11 @@ var conversationContext_exports = {};
26412
26905
  __export(conversationContext_exports, {
26413
26906
  _resetConversationContextForTests: () => _resetConversationContextForTests,
26414
26907
  appendMessages: () => appendMessages,
26908
+ buildCouncilTaskWithHistory: () => buildCouncilTaskWithHistory,
26415
26909
  clearHistory: () => clearHistory,
26416
26910
  compactInPlace: () => compactInPlace,
26417
26911
  formatHistoryForCouncil: () => formatHistoryForCouncil,
26912
+ formatHistoryMessages: () => formatHistoryMessages,
26418
26913
  getHistory: () => getHistory,
26419
26914
  getLastClarification: () => getLastClarification,
26420
26915
  hydrateHistory: () => hydrateHistory,
@@ -26475,23 +26970,56 @@ User's answer: ${picked}
26475
26970
  Proceed using this answer; do not re-ask the same question unless the answer is still ambiguous.`;
26476
26971
  }
26477
26972
  function formatHistoryForCouncil(maxTurns = 4) {
26478
- if (history.length === 0) return "";
26479
- const lines = [];
26973
+ return formatHistoryMessages(history, maxTurns);
26974
+ }
26975
+ function formatHistoryMessages(messages, maxTurns = 6, maxTotalChars = 12e3) {
26976
+ if (messages.length === 0) return "";
26480
26977
  let turns = 0;
26481
26978
  const chunk = [];
26482
- for (let i = history.length - 1; i >= 0 && turns < maxTurns; i--) {
26483
- const m = history[i];
26979
+ for (let i = messages.length - 1; i >= 0 && turns < maxTurns; i--) {
26980
+ const m = messages[i];
26484
26981
  if (m.role === "user") {
26485
- chunk.push(`User: ${truncate(m.content, 400)}`);
26982
+ chunk.push(`User: ${truncate(m.content, 800)}`);
26486
26983
  turns += 1;
26487
26984
  } else if (m.role === "assistant" && m.content.trim()) {
26488
- chunk.push(`Assistant: ${truncate(m.content, 600)}`);
26985
+ chunk.push(`Assistant: ${truncate(m.content, 2e3)}`);
26489
26986
  }
26490
26987
  }
26491
26988
  if (chunk.length === 0) return "";
26492
- lines.push("## Prior conversation (rolling context)");
26493
- lines.push(...chunk.reverse());
26494
- 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}`;
26495
27023
  }
26496
27024
  function truncate(s, max) {
26497
27025
  const t = s.replace(/\s+/g, " ").trim();
@@ -26502,13 +27030,14 @@ function _resetConversationContextForTests() {
26502
27030
  history = [];
26503
27031
  lastClarification = null;
26504
27032
  }
26505
- var history, lastClarification;
27033
+ var history, lastClarification, SHORT_CONTINUE;
26506
27034
  var init_conversationContext = __esm({
26507
27035
  "src/cli/hooks/conversationContext.ts"() {
26508
27036
  "use strict";
26509
27037
  init_historyCompaction();
26510
27038
  history = [];
26511
27039
  lastClarification = null;
27040
+ SHORT_CONTINUE = /^(procedi|continua|continue|go\s*ahead|go|ok|okay|sì|si|yes|vai|avanti|next|proceed)$/i;
26512
27041
  }
26513
27042
  });
26514
27043
 
@@ -26589,20 +27118,20 @@ var init_phase = __esm({
26589
27118
 
26590
27119
  // src/cli/workspace/paths.ts
26591
27120
  import {
26592
- mkdirSync as mkdirSync6,
26593
- writeFileSync as writeFileSync10,
26594
- existsSync as existsSync13,
27121
+ mkdirSync as mkdirSync7,
27122
+ writeFileSync as writeFileSync11,
27123
+ existsSync as existsSync14,
26595
27124
  accessSync,
26596
27125
  constants,
26597
27126
  realpathSync
26598
27127
  } from "node:fs";
26599
- import { join as join11, basename } from "node:path";
26600
- import { homedir as homedir4 } from "node:os";
27128
+ import { join as join12, basename } from "node:path";
27129
+ import { homedir as homedir5 } from "node:os";
26601
27130
  import { createHash as createHash2 } from "node:crypto";
26602
27131
  function resolveWorkspaceRoot(projectRoot = process.cwd()) {
26603
27132
  const candidates = [
26604
- join11(projectRoot, ".zelari"),
26605
- join11(homedir4(), ".zelari-code", "workspace", hashProject(projectRoot))
27133
+ join12(projectRoot, ".zelari"),
27134
+ join12(homedir5(), ".zelari-code", "workspace", hashProject(projectRoot))
26606
27135
  ];
26607
27136
  for (const candidate of candidates) {
26608
27137
  if (isWritableDir(projectRoot) || candidate !== candidates[0]) {
@@ -26618,7 +27147,7 @@ function hashProject(projectPath) {
26618
27147
  }
26619
27148
  function isWritableDir(dir) {
26620
27149
  try {
26621
- if (!existsSync13(dir)) return false;
27150
+ if (!existsSync14(dir)) return false;
26622
27151
  accessSync(dir, constants.W_OK);
26623
27152
  return true;
26624
27153
  } catch {
@@ -26626,26 +27155,26 @@ function isWritableDir(dir) {
26626
27155
  }
26627
27156
  }
26628
27157
  function ensureWorkspaceDir(workspaceDir) {
26629
- mkdirSync6(workspaceDir, { recursive: true });
26630
- if (workspaceDir.endsWith("/.zelari") && existsSync13(join11(workspaceDir, "..", ".git"))) {
26631
- const gitignorePath = join11(workspaceDir, ".gitignore");
26632
- if (!existsSync13(gitignorePath)) {
26633
- 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");
26634
27163
  }
26635
27164
  }
26636
27165
  }
26637
27166
  function workspaceFile(rootDir, kind) {
26638
27167
  switch (kind) {
26639
27168
  case "plan":
26640
- return join11(rootDir, "plan.md");
27169
+ return join12(rootDir, "plan.md");
26641
27170
  case "risks":
26642
- return join11(rootDir, "risks.md");
27171
+ return join12(rootDir, "risks.md");
26643
27172
  case "index":
26644
- return join11(rootDir, "workspace.json");
27173
+ return join12(rootDir, "workspace.json");
26645
27174
  }
26646
27175
  }
26647
27176
  function workspaceArtifact(rootDir, subdir, slug) {
26648
- return join11(rootDir, subdir, `${slug}.md`);
27177
+ return join12(rootDir, subdir, `${slug}.md`);
26649
27178
  }
26650
27179
  function projectName(projectRoot = process.cwd()) {
26651
27180
  return basename(realpathSync(projectRoot));
@@ -26664,8 +27193,8 @@ __export(workspaceSummary_exports, {
26664
27193
  buildWorkspaceSummary: () => buildWorkspaceSummary,
26665
27194
  buildZelariReadHint: () => buildZelariReadHint
26666
27195
  });
26667
- import { existsSync as existsSync14, readFileSync as readFileSync14, readdirSync, statSync as statSync3 } from "node:fs";
26668
- 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";
26669
27198
  function buildWorkspaceSummary(projectRoot = process.cwd(), options = {}) {
26670
27199
  const { maxEntries = 30 } = options;
26671
27200
  const name = safeProjectName(projectRoot);
@@ -26692,11 +27221,11 @@ function formatTaskLine(t) {
26692
27221
  }
26693
27222
  function buildPlanSummary(projectRoot = process.cwd(), options) {
26694
27223
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
26695
- const planPath = join12(zelariRoot, "plan.json");
26696
- if (!existsSync14(planPath)) return null;
27224
+ const planPath = join13(zelariRoot, "plan.json");
27225
+ if (!existsSync15(planPath)) return null;
26697
27226
  let plan;
26698
27227
  try {
26699
- plan = JSON.parse(readFileSync14(planPath, "utf8"));
27228
+ plan = JSON.parse(readFileSync15(planPath, "utf8"));
26700
27229
  } catch {
26701
27230
  return null;
26702
27231
  }
@@ -26825,8 +27354,8 @@ function pickNextTask(open) {
26825
27354
  )[0];
26826
27355
  }
26827
27356
  function buildZelariReadHint(projectRoot = process.cwd()) {
26828
- const planPath = join12(resolveWorkspaceRoot(projectRoot), "plan.json");
26829
- if (!existsSync14(planPath)) return "";
27357
+ const planPath = join13(resolveWorkspaceRoot(projectRoot), "plan.json");
27358
+ if (!existsSync15(planPath)) return "";
26830
27359
  return [
26831
27360
  "# Council workspace detected (.zelari/)",
26832
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.",
@@ -26841,10 +27370,10 @@ function safeProjectName(root) {
26841
27370
  }
26842
27371
  }
26843
27372
  function readPackageJson(projectRoot) {
26844
- const p3 = join12(projectRoot, "package.json");
26845
- if (!existsSync14(p3)) return null;
27373
+ const p3 = join13(projectRoot, "package.json");
27374
+ if (!existsSync15(p3)) return null;
26846
27375
  try {
26847
- return JSON.parse(readFileSync14(p3, "utf8"));
27376
+ return JSON.parse(readFileSync15(p3, "utf8"));
26848
27377
  } catch {
26849
27378
  return null;
26850
27379
  }
@@ -26878,11 +27407,11 @@ function listShallow(projectRoot, maxEntries) {
26878
27407
  out.push(`\u2026 (+${top.length - count} more)`);
26879
27408
  break;
26880
27409
  }
26881
- const rel2 = relative(projectRoot, join12(projectRoot, entry.name));
27410
+ const rel2 = relative(projectRoot, join13(projectRoot, entry.name));
26882
27411
  if (entry.isDirectory()) {
26883
27412
  let inner = "";
26884
27413
  try {
26885
- const sub = readdirSync(join12(projectRoot, entry.name), {
27414
+ const sub = readdirSync(join13(projectRoot, entry.name), {
26886
27415
  withFileTypes: true
26887
27416
  }).filter((e) => !e.name.startsWith(".")).slice(0, 4).map((e) => e.name);
26888
27417
  if (sub.length > 0)
@@ -26933,14 +27462,14 @@ __export(storage_exports, {
26933
27462
  workspaceMutex: () => workspaceMutex
26934
27463
  });
26935
27464
  import {
26936
- readFileSync as readFileSync15,
26937
- writeFileSync as writeFileSync11,
26938
- existsSync as existsSync15,
26939
- mkdirSync as mkdirSync7,
27465
+ readFileSync as readFileSync16,
27466
+ writeFileSync as writeFileSync12,
27467
+ existsSync as existsSync16,
27468
+ mkdirSync as mkdirSync8,
26940
27469
  readdirSync as readdirSync2,
26941
27470
  renameSync as renameSync2
26942
27471
  } from "node:fs";
26943
- import { dirname as dirname2, join as join13 } from "node:path";
27472
+ import { dirname as dirname3, join as join14 } from "node:path";
26944
27473
  function parseFrontmatter(md) {
26945
27474
  const m = FRONTMATTER_RE.exec(md);
26946
27475
  if (!m) return { meta: {}, body: md };
@@ -27195,15 +27724,15 @@ var init_storage = __esm({
27195
27724
  Storage = class {
27196
27725
  /** Read a Markdown file with frontmatter. Throws if not found. */
27197
27726
  read(path36) {
27198
- if (!existsSync15(path36)) {
27727
+ if (!existsSync16(path36)) {
27199
27728
  throw new Error(`File not found: ${path36}`);
27200
27729
  }
27201
- const md = readFileSync15(path36, "utf8");
27730
+ const md = readFileSync16(path36, "utf8");
27202
27731
  return parseFrontmatter(md);
27203
27732
  }
27204
27733
  /** Read a Markdown file; returns null if not found. */
27205
27734
  readIfExists(path36) {
27206
- if (!existsSync15(path36)) return null;
27735
+ if (!existsSync16(path36)) return null;
27207
27736
  return this.read(path36);
27208
27737
  }
27209
27738
  /**
@@ -27211,16 +27740,16 @@ var init_storage = __esm({
27211
27740
  * The meta object is serialized as YAML frontmatter; body as Markdown.
27212
27741
  */
27213
27742
  write(path36, meta3, body) {
27214
- mkdirSync7(dirname2(path36), { recursive: true });
27743
+ mkdirSync8(dirname3(path36), { recursive: true });
27215
27744
  const tmp = path36 + ".tmp-" + process.pid;
27216
27745
  const md = serializeFrontmatter(meta3, body);
27217
- writeFileSync11(tmp, md, "utf8");
27746
+ writeFileSync12(tmp, md, "utf8");
27218
27747
  renameSync2(tmp, path36);
27219
27748
  }
27220
27749
  /** List all .md files in a directory (non-recursive). */
27221
27750
  listMarkdown(dir) {
27222
- if (!existsSync15(dir)) return [];
27223
- 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));
27224
27753
  }
27225
27754
  };
27226
27755
  KeyedMutex = class {
@@ -27259,14 +27788,14 @@ __export(stubs_exports, {
27259
27788
  resolveWorkspaceRoot: () => resolveWorkspaceRoot
27260
27789
  });
27261
27790
  import {
27262
- existsSync as existsSync16,
27791
+ existsSync as existsSync17,
27263
27792
  readdirSync as readdirSync3,
27264
- writeFileSync as writeFileSync12,
27265
- readFileSync as readFileSync16,
27266
- mkdirSync as mkdirSync8,
27793
+ writeFileSync as writeFileSync13,
27794
+ readFileSync as readFileSync17,
27795
+ mkdirSync as mkdirSync9,
27267
27796
  renameSync as renameSync3
27268
27797
  } from "node:fs";
27269
- 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";
27270
27799
  function createWorkspaceContext(projectRoot = process.cwd()) {
27271
27800
  const rootDir = resolveWorkspaceRoot(projectRoot);
27272
27801
  return {
@@ -27276,14 +27805,14 @@ function createWorkspaceContext(projectRoot = process.cwd()) {
27276
27805
  };
27277
27806
  }
27278
27807
  function planJsonPath(ctx) {
27279
- return join14(ctx.rootDir, "plan.json");
27808
+ return join15(ctx.rootDir, "plan.json");
27280
27809
  }
27281
27810
  function readPlan(ctx) {
27282
27811
  const jsonPath = planJsonPath(ctx);
27283
- if (existsSync16(jsonPath)) {
27812
+ if (existsSync17(jsonPath)) {
27284
27813
  try {
27285
27814
  const parsed = JSON.parse(
27286
- readFileSync16(jsonPath, "utf8")
27815
+ readFileSync17(jsonPath, "utf8")
27287
27816
  );
27288
27817
  return {
27289
27818
  phases: Array.isArray(parsed.phases) ? parsed.phases : [],
@@ -27305,9 +27834,9 @@ function readPlan(ctx) {
27305
27834
  }
27306
27835
  function writePlan(ctx, summary) {
27307
27836
  const jsonPath = planJsonPath(ctx);
27308
- mkdirSync8(dirname3(jsonPath), { recursive: true });
27837
+ mkdirSync9(dirname4(jsonPath), { recursive: true });
27309
27838
  const tmp = jsonPath + ".tmp-" + process.pid;
27310
- writeFileSync12(tmp, JSON.stringify(summary, null, 2), "utf8");
27839
+ writeFileSync13(tmp, JSON.stringify(summary, null, 2), "utf8");
27311
27840
  renameSync3(tmp, jsonPath);
27312
27841
  const mdPath = workspaceFile(ctx.rootDir, "plan");
27313
27842
  const summaryMeta = {
@@ -27374,8 +27903,8 @@ function renderPlanBody(summary) {
27374
27903
  return lines.join("\n");
27375
27904
  }
27376
27905
  function nextAdrId(ctx) {
27377
- const decisionsDir = join14(ctx.rootDir, "decisions");
27378
- if (!existsSync16(decisionsDir)) return "001";
27906
+ const decisionsDir = join15(ctx.rootDir, "decisions");
27907
+ if (!existsSync17(decisionsDir)) return "001";
27379
27908
  const existing = readdirSync3(decisionsDir).filter((f) => f.endsWith(".md")).map((f) => f.match(/^(\d+)-/)).filter((m) => !!m).map((m) => parseInt(m[1], 10));
27380
27909
  const max = existing.length === 0 ? 0 : Math.max(...existing);
27381
27910
  return String(max + 1).padStart(3, "0");
@@ -27417,7 +27946,7 @@ function addTaskRecord(ctx, summary, phaseId, t, options) {
27417
27946
  status: "pending",
27418
27947
  priority: t.priority
27419
27948
  });
27420
- const taskPath = join14(ctx.rootDir, "plan-tasks", `${id}.md`);
27949
+ const taskPath = join15(ctx.rootDir, "plan-tasks", `${id}.md`);
27421
27950
  const meta3 = {
27422
27951
  kind: "task",
27423
27952
  id,
@@ -27459,7 +27988,7 @@ function addMilestoneRecord(ctx, summary, input) {
27459
27988
  dueDate: input.dueDate,
27460
27989
  targetVersion: version2
27461
27990
  });
27462
- const path36 = join14(ctx.rootDir, "milestones", `${id}.md`);
27991
+ const path36 = join15(ctx.rootDir, "milestones", `${id}.md`);
27463
27992
  const meta3 = {
27464
27993
  kind: "milestone",
27465
27994
  id,
@@ -27757,8 +28286,8 @@ function createNfrSpecStub(ctx) {
27757
28286
  },
27758
28287
  planFeatureKeywords: Array.isArray(args["planFeatureKeywords"]) ? args["planFeatureKeywords"] : void 0
27759
28288
  };
27760
- const outPath = join14(ctx.rootDir, "nfr-spec.json");
27761
- 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");
27762
28291
  return `NFR spec written to nfr-spec.json (${targets.length} target(s)).`;
27763
28292
  });
27764
28293
  }
@@ -27821,18 +28350,18 @@ function searchDocumentsStub(ctx) {
27821
28350
  (w) => w.length >= 3 && w !== "or" && w !== "and" && w !== "the"
27822
28351
  );
27823
28352
  const files = [
27824
- ...ctx.storage.listMarkdown(join14(ctx.rootDir, "decisions")),
27825
- ...ctx.storage.listMarkdown(join14(ctx.rootDir, "docs")),
27826
- ...ctx.storage.listMarkdown(join14(ctx.rootDir, "reviews")),
27827
- ...ctx.storage.listMarkdown(join14(ctx.rootDir, "plan-tasks")),
27828
- ...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")),
27829
28358
  workspaceFile(ctx.rootDir, "plan"),
27830
28359
  workspaceFile(ctx.rootDir, "risks")
27831
28360
  ];
27832
28361
  const results = [];
27833
28362
  for (const file2 of files) {
27834
- if (!existsSync16(file2)) continue;
27835
- const raw = readFileSync16(file2, "utf8");
28363
+ if (!existsSync17(file2)) continue;
28364
+ const raw = readFileSync17(file2, "utf8");
27836
28365
  const content = raw.toLowerCase();
27837
28366
  let idx = -1;
27838
28367
  let matchLen = 0;
@@ -27883,9 +28412,9 @@ function linkDocumentsStub(ctx) {
27883
28412
  const toId = args["toId"] ?? args["targetId"] ?? args["targetPathOrTitle"];
27884
28413
  if (!fromId || !toId) return "linkDocuments requires fromId and toId.";
27885
28414
  const allFiles = [
27886
- ...ctx.storage.listMarkdown(join14(ctx.rootDir, "decisions")),
27887
- ...ctx.storage.listMarkdown(join14(ctx.rootDir, "docs")),
27888
- ...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"))
27889
28418
  ];
27890
28419
  const source = allFiles.find((f) => {
27891
28420
  try {
@@ -27916,9 +28445,9 @@ function getDocumentBacklinksStub(ctx) {
27916
28445
  const targetId = args["targetId"] ?? args["id"];
27917
28446
  if (!targetId) return "getDocumentBacklinks requires targetId.";
27918
28447
  const allFiles = [
27919
- ...ctx.storage.listMarkdown(join14(ctx.rootDir, "decisions")),
27920
- ...ctx.storage.listMarkdown(join14(ctx.rootDir, "docs")),
27921
- ...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"))
27922
28451
  ];
27923
28452
  const backlinks = [];
27924
28453
  for (const file2 of allFiles) {
@@ -28012,8 +28541,8 @@ __export(updater_exports, {
28012
28541
  resolveBundledNpmCli: () => resolveBundledNpmCli
28013
28542
  });
28014
28543
  import { createRequire as createRequire2 } from "node:module";
28015
- import { spawn as spawn4 } from "node:child_process";
28016
- import { existsSync as existsSync17 } from "node:fs";
28544
+ import { spawn as spawn5 } from "node:child_process";
28545
+ import { existsSync as existsSync18 } from "node:fs";
28017
28546
  import path23 from "node:path";
28018
28547
  import { fileURLToPath } from "node:url";
28019
28548
  function resolveBundledNpmCli(execPath = process.execPath) {
@@ -28026,7 +28555,7 @@ function resolveBundledNpmCli(execPath = process.execPath) {
28026
28555
  ];
28027
28556
  for (const candidate of candidates) {
28028
28557
  try {
28029
- if (existsSync17(candidate)) return candidate;
28558
+ if (existsSync18(candidate)) return candidate;
28030
28559
  } catch {
28031
28560
  }
28032
28561
  }
@@ -28099,7 +28628,7 @@ async function checkForUpdate(fetcher = fetch, registryUrl) {
28099
28628
  updateAvailable: cmp < 0
28100
28629
  };
28101
28630
  }
28102
- async function performUpdate(packageName = "zelari-code", executor = spawn4, resolveNpmCli = resolveBundledNpmCli) {
28631
+ async function performUpdate(packageName = "zelari-code", executor = spawn5, resolveNpmCli = resolveBundledNpmCli) {
28103
28632
  const args = ["install", "-g", `${packageName}@latest`];
28104
28633
  const primary = await runNpm(executor, args, "shim");
28105
28634
  if (primary.ok) return primary;
@@ -28157,7 +28686,7 @@ var init_updater = __esm({
28157
28686
  });
28158
28687
 
28159
28688
  // src/cli/mcp/mcpClient.ts
28160
- import { spawn as spawn5 } from "node:child_process";
28689
+ import { spawn as spawn6 } from "node:child_process";
28161
28690
  var DEFAULT_REQUEST_TIMEOUT_MS, INIT_TIMEOUT_MS, MCP_PROTOCOL_VERSION, McpClient;
28162
28691
  var init_mcpClient = __esm({
28163
28692
  "src/cli/mcp/mcpClient.ts"() {
@@ -28185,10 +28714,10 @@ var init_mcpClient = __esm({
28185
28714
  env: { ...process.env, ...this.config.env ?? {} },
28186
28715
  windowsHide: true
28187
28716
  };
28188
- 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 ?? []), {
28189
28718
  ...spawnOpts,
28190
28719
  shell: true
28191
- }) : spawn5(this.config.command, this.config.args ?? [], spawnOpts);
28720
+ }) : spawn6(this.config.command, this.config.args ?? [], spawnOpts);
28192
28721
  this.child = child;
28193
28722
  child.stdout.setEncoding("utf8");
28194
28723
  child.stdout.on("data", (chunk) => this.onStdout(chunk));
@@ -28332,20 +28861,20 @@ __export(mcpManager_exports, {
28332
28861
  readMcpConfig: () => readMcpConfig,
28333
28862
  registerMcpTools: () => registerMcpTools
28334
28863
  });
28335
- import { existsSync as existsSync18, readFileSync as readFileSync17 } from "node:fs";
28336
- import { join as join15 } from "node:path";
28337
- 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";
28338
28867
  function readMcpConfig(projectRoot = process.cwd()) {
28339
28868
  const merged = {};
28340
28869
  const paths = [
28341
- join15(homedir5(), ".zelari-code", "mcp.json"),
28342
- join15(projectRoot, ".zelari", "mcp.json")
28870
+ join16(homedir6(), ".zelari-code", "mcp.json"),
28871
+ join16(projectRoot, ".zelari", "mcp.json")
28343
28872
  // later = higher precedence
28344
28873
  ];
28345
28874
  for (const p3 of paths) {
28346
- if (!existsSync18(p3)) continue;
28875
+ if (!existsSync19(p3)) continue;
28347
28876
  try {
28348
- const parsed = JSON.parse(readFileSync17(p3, "utf8"));
28877
+ const parsed = JSON.parse(readFileSync18(p3, "utf8"));
28349
28878
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
28350
28879
  if (!cfg || typeof cfg.command !== "string" || cfg.command.length === 0) continue;
28351
28880
  merged[name] = cfg;
@@ -28434,6 +28963,53 @@ var init_mcpManager = __esm({
28434
28963
  }
28435
28964
  });
28436
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
+
28437
29013
  // src/cli/councilConfig.ts
28438
29014
  function resolveCouncilTier(opts) {
28439
29015
  const env = opts?.env ?? process.env;
@@ -28477,13 +29053,13 @@ var planDetect_exports = {};
28477
29053
  __export(planDetect_exports, {
28478
29054
  hasWorkspacePlan: () => hasWorkspacePlan
28479
29055
  });
28480
- import { existsSync as existsSync19, readFileSync as readFileSync18 } from "node:fs";
28481
- 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";
28482
29058
  function hasWorkspacePlan(projectRoot = process.cwd()) {
28483
- const planPath = join16(resolveWorkspaceRoot(projectRoot), "plan.json");
28484
- if (!existsSync19(planPath)) return false;
29059
+ const planPath = join18(resolveWorkspaceRoot(projectRoot), "plan.json");
29060
+ if (!existsSync21(planPath)) return false;
28485
29061
  try {
28486
- const parsed = JSON.parse(readFileSync18(planPath, "utf8"));
29062
+ const parsed = JSON.parse(readFileSync20(planPath, "utf8"));
28487
29063
  return Array.isArray(parsed.phases) && parsed.phases.length > 0;
28488
29064
  } catch {
28489
29065
  return false;
@@ -28587,13 +29163,13 @@ __export(agentsMd_exports, {
28587
29163
  serializeAgentsMd: () => serializeAgentsMd,
28588
29164
  updateAgentsMd: () => updateAgentsMd
28589
29165
  });
28590
- 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";
28591
29167
  import { createHash as createHash3 } from "node:crypto";
28592
- import { join as join17 } from "node:path";
29168
+ import { join as join19 } from "node:path";
28593
29169
  import { readFile as readFile2 } from "node:fs/promises";
28594
29170
  async function readPackageJson2(projectRoot) {
28595
- const path36 = join17(projectRoot, "package.json");
28596
- if (!existsSync20(path36)) return null;
29171
+ const path36 = join19(projectRoot, "package.json");
29172
+ if (!existsSync22(path36)) return null;
28597
29173
  try {
28598
29174
  return JSON.parse(await readFile2(path36, "utf8"));
28599
29175
  } catch {
@@ -28617,8 +29193,8 @@ async function genTechStack(ctx) {
28617
29193
  ].join("\n");
28618
29194
  }
28619
29195
  async function genDecisions(ctx) {
28620
- const decisionsDir = join17(ctx.rootDir, "decisions");
28621
- if (!existsSync20(decisionsDir)) return "_No ADRs yet._";
29196
+ const decisionsDir = join19(ctx.rootDir, "decisions");
29197
+ if (!existsSync22(decisionsDir)) return "_No ADRs yet._";
28622
29198
  const files = ctx.storage.listMarkdown(decisionsDir).sort();
28623
29199
  const accepted = [];
28624
29200
  const proposed = [];
@@ -28643,9 +29219,9 @@ async function genDecisions(ctx) {
28643
29219
  }
28644
29220
  async function genConventions(ctx) {
28645
29221
  const lines = [];
28646
- const claudeMd = join17(ctx.projectRoot, "CLAUDE.MD");
28647
- if (existsSync20(claudeMd)) {
28648
- const content = readFileSync19(claudeMd, "utf8");
29222
+ const claudeMd = join19(ctx.projectRoot, "CLAUDE.MD");
29223
+ if (existsSync22(claudeMd)) {
29224
+ const content = readFileSync21(claudeMd, "utf8");
28649
29225
  const match = content.match(/## Architecture rules[\s\S]+?(?=\n## |\n*$)/);
28650
29226
  if (match) {
28651
29227
  lines.push('<!-- Extracted from CLAUDE.MD "Architecture rules" -->');
@@ -28677,9 +29253,9 @@ async function genBuild(ctx) {
28677
29253
  ].join("\n");
28678
29254
  }
28679
29255
  async function genOpenQuestions(ctx) {
28680
- const path36 = join17(ctx.rootDir, "risks.md");
28681
- if (!existsSync20(path36)) return "_No open questions._";
28682
- 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");
28683
29259
  const lines = content.split("\n");
28684
29260
  const questions = [];
28685
29261
  let currentTitle = "";
@@ -28753,9 +29329,9 @@ function titleCase(id) {
28753
29329
  return id.split("-").map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
28754
29330
  }
28755
29331
  async function updateAgentsMd(ctx, projectRoot) {
28756
- const agentsPath = join17(projectRoot, "AGENTS.MD");
28757
- if (existsSync20(agentsPath)) {
28758
- const content = readFileSync19(agentsPath, "utf8");
29332
+ const agentsPath = join19(projectRoot, "AGENTS.MD");
29333
+ if (existsSync22(agentsPath)) {
29334
+ const content = readFileSync21(agentsPath, "utf8");
28759
29335
  const hasAnyMarker = AUTO_SECTIONS.some((id) => content.includes(MARKER_OPEN(id)));
28760
29336
  if (!hasAnyMarker) {
28761
29337
  return {
@@ -28770,8 +29346,8 @@ async function updateAgentsMd(ctx, projectRoot) {
28770
29346
  newSections.set(id, await GENERATORS[id](ctx));
28771
29347
  }
28772
29348
  let manualContent = "";
28773
- if (existsSync20(agentsPath)) {
28774
- const { manualBlocks } = parseAgentsMd(readFileSync19(agentsPath, "utf8"));
29349
+ if (existsSync22(agentsPath)) {
29350
+ const { manualBlocks } = parseAgentsMd(readFileSync21(agentsPath, "utf8"));
28775
29351
  manualContent = manualBlocks.after;
28776
29352
  } else {
28777
29353
  const projectName2 = projectName(projectRoot);
@@ -28787,7 +29363,7 @@ async function updateAgentsMd(ctx, projectRoot) {
28787
29363
  ""
28788
29364
  ].join("\n");
28789
29365
  }
28790
- const oldContent = existsSync20(agentsPath) ? readFileSync19(agentsPath, "utf8") : "";
29366
+ const oldContent = existsSync22(agentsPath) ? readFileSync21(agentsPath, "utf8") : "";
28791
29367
  const { sections: oldSections } = parseAgentsMd(oldContent);
28792
29368
  const changedSections = [];
28793
29369
  for (const id of AUTO_SECTIONS) {
@@ -28799,7 +29375,7 @@ async function updateAgentsMd(ctx, projectRoot) {
28799
29375
  return { changed: false, sections: [] };
28800
29376
  }
28801
29377
  const newContent = serializeAgentsMd(manualContent, newSections);
28802
- writeFileSync13(agentsPath, newContent, "utf8");
29378
+ writeFileSync14(agentsPath, newContent, "utf8");
28803
29379
  return { changed: true, sections: changedSections };
28804
29380
  }
28805
29381
  function hash2(s) {
@@ -28924,9 +29500,9 @@ var init_completeDesign = __esm({
28924
29500
  });
28925
29501
 
28926
29502
  // src/cli/workspace/projectSmoke.ts
28927
- import { spawn as spawn6 } from "node:child_process";
28928
- import { existsSync as existsSync21, readFileSync as readFileSync20 } from "node:fs";
28929
- 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";
28930
29506
  function pickSmokeScript(scripts) {
28931
29507
  if (!scripts) return null;
28932
29508
  for (const name of SMOKE_SCRIPT_PRIORITY) {
@@ -28938,13 +29514,13 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS) {
28938
29514
  if (process.env["ZELARI_SMOKE"] === "0") {
28939
29515
  return { ran: false, reason: "ZELARI_SMOKE=0 (disabled)" };
28940
29516
  }
28941
- const pkgPath = join18(projectRoot, "package.json");
28942
- if (!existsSync21(pkgPath)) {
29517
+ const pkgPath = join20(projectRoot, "package.json");
29518
+ if (!existsSync23(pkgPath)) {
28943
29519
  return { ran: false, reason: "no package.json (skipped)" };
28944
29520
  }
28945
29521
  let scripts = {};
28946
29522
  try {
28947
- const pkg = JSON.parse(readFileSync20(pkgPath, "utf8"));
29523
+ const pkg = JSON.parse(readFileSync22(pkgPath, "utf8"));
28948
29524
  scripts = pkg.scripts ?? {};
28949
29525
  } catch {
28950
29526
  return { ran: false, reason: "package.json unreadable (skipped)" };
@@ -28954,12 +29530,12 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS) {
28954
29530
  return { ran: false, reason: "no typecheck/test/build script (skipped)" };
28955
29531
  }
28956
29532
  return await new Promise((resolveRun) => {
28957
- const child = process.platform === "win32" ? spawn6(buildCmdLine("npm.cmd", ["run", script]), {
29533
+ const child = process.platform === "win32" ? spawn7(buildCmdLine("npm.cmd", ["run", script]), {
28958
29534
  cwd: projectRoot,
28959
29535
  stdio: ["ignore", "pipe", "pipe"],
28960
29536
  env: process.env,
28961
29537
  shell: true
28962
- }) : spawn6("npm", ["run", script], {
29538
+ }) : spawn7("npm", ["run", script], {
28963
29539
  cwd: projectRoot,
28964
29540
  stdio: ["ignore", "pipe", "pipe"],
28965
29541
  env: process.env
@@ -29031,9 +29607,9 @@ __export(postCouncilHook_exports, {
29031
29607
  runImplementationVerificationHook: () => runImplementationVerificationHook,
29032
29608
  runPostCouncilHook: () => runPostCouncilHook
29033
29609
  });
29034
- import { spawn as spawn7 } from "node:child_process";
29035
- import { existsSync as existsSync22, readFileSync as readFileSync21 } from "node:fs";
29036
- 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";
29037
29613
  async function runCompleteDesignPostProcessor(ctx, options) {
29038
29614
  if (options?.runMode === "implementation") {
29039
29615
  return {
@@ -29044,9 +29620,9 @@ async function runCompleteDesignPostProcessor(ctx, options) {
29044
29620
  if (process.env["ZELARI_COMPLETE_DESIGN"] === "0") {
29045
29621
  return { ran: false, reason: "ZELARI_COMPLETE_DESIGN=0 (disabled)" };
29046
29622
  }
29047
- const planJsonPath2 = join19(ctx.rootDir, "plan.json");
29048
- const scriptPath = join19(ctx.projectRoot, "complete-design.mjs");
29049
- if (!existsSync22(planJsonPath2)) {
29623
+ const planJsonPath2 = join21(ctx.rootDir, "plan.json");
29624
+ const scriptPath = join21(ctx.projectRoot, "complete-design.mjs");
29625
+ if (!existsSync24(planJsonPath2)) {
29050
29626
  return {
29051
29627
  ran: false,
29052
29628
  reason: ".zelari/plan.json missing (not design-phase)"
@@ -29054,7 +29630,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
29054
29630
  }
29055
29631
  let phaseCount = 0;
29056
29632
  try {
29057
- const parsed = JSON.parse(readFileSync21(planJsonPath2, "utf8"));
29633
+ const parsed = JSON.parse(readFileSync23(planJsonPath2, "utf8"));
29058
29634
  phaseCount = Array.isArray(parsed.phases) ? parsed.phases.length : 0;
29059
29635
  } catch {
29060
29636
  return { ran: false, reason: ".zelari/plan.json corrupt" };
@@ -29062,7 +29638,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
29062
29638
  if (phaseCount === 0) {
29063
29639
  return { ran: false, reason: ".zelari/plan.json has no phases" };
29064
29640
  }
29065
- if (!existsSync22(scriptPath)) {
29641
+ if (!existsSync24(scriptPath)) {
29066
29642
  try {
29067
29643
  const builtin = await runBuiltinCompleteDesign(ctx);
29068
29644
  return {
@@ -29080,7 +29656,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
29080
29656
  }
29081
29657
  }
29082
29658
  return await new Promise((resolveRun) => {
29083
- const child = spawn7(process.execPath, [scriptPath], {
29659
+ const child = spawn8(process.execPath, [scriptPath], {
29084
29660
  cwd: ctx.projectRoot,
29085
29661
  stdio: ["ignore", "pipe", "pipe"],
29086
29662
  env: process.env
@@ -29283,12 +29859,12 @@ var buildLessonsSummary_exports = {};
29283
29859
  __export(buildLessonsSummary_exports, {
29284
29860
  buildLessonsSummary: () => buildLessonsSummary
29285
29861
  });
29286
- import { existsSync as existsSync23 } from "node:fs";
29287
- import { join as join20 } from "node:path";
29862
+ import { existsSync as existsSync25 } from "node:fs";
29863
+ import { join as join22 } from "node:path";
29288
29864
  function buildLessonsSummary(projectRoot = process.cwd(), taskText) {
29289
29865
  if (process.env["ZELARI_LESSONS"] === "0") return null;
29290
29866
  const zelariRoot = resolveWorkspaceRoot(projectRoot);
29291
- if (!existsSync23(join20(zelariRoot, "lessons.jsonl"))) return null;
29867
+ if (!existsSync25(join22(zelariRoot, "lessons.jsonl"))) return null;
29292
29868
  const lessons = recallLessons(zelariRoot, {
29293
29869
  maxLessons: 5,
29294
29870
  maxBytes: 2048,
@@ -29311,10 +29887,10 @@ __export(councilFeedback_exports, {
29311
29887
  });
29312
29888
  import {
29313
29889
  promises as fs13,
29314
- existsSync as existsSync24,
29315
- readFileSync as readFileSync22,
29316
- writeFileSync as writeFileSync14,
29317
- mkdirSync as mkdirSync9
29890
+ existsSync as existsSync26,
29891
+ readFileSync as readFileSync24,
29892
+ writeFileSync as writeFileSync15,
29893
+ mkdirSync as mkdirSync10
29318
29894
  } from "node:fs";
29319
29895
  import path24 from "node:path";
29320
29896
  import os8 from "node:os";
@@ -29420,9 +29996,9 @@ var init_councilFeedback = __esm({
29420
29996
  }
29421
29997
  // --- persistence ---------------------------------------------------------
29422
29998
  load() {
29423
- if (!existsSync24(this.file)) return;
29999
+ if (!existsSync26(this.file)) return;
29424
30000
  try {
29425
- const raw = readFileSync22(this.file, "utf-8");
30001
+ const raw = readFileSync24(this.file, "utf-8");
29426
30002
  const parsed = JSON.parse(raw);
29427
30003
  if (parsed && Array.isArray(parsed.entries)) {
29428
30004
  this.entries = parsed.entries.filter(
@@ -29433,8 +30009,8 @@ var init_councilFeedback = __esm({
29433
30009
  }
29434
30010
  }
29435
30011
  save() {
29436
- mkdirSync9(path24.dirname(this.file), { recursive: true });
29437
- writeFileSync14(
30012
+ mkdirSync10(path24.dirname(this.file), { recursive: true });
30013
+ writeFileSync15(
29438
30014
  this.file,
29439
30015
  JSON.stringify({ entries: this.entries }, null, 2),
29440
30016
  { encoding: "utf-8", mode: 384 }
@@ -29891,8 +30467,8 @@ __export(prereqChecks_exports, {
29891
30467
  runPrereqChecks: () => runPrereqChecks
29892
30468
  });
29893
30469
  import { execSync, spawnSync as spawnSync2 } from "node:child_process";
29894
- import { existsSync as existsSync25 } from "node:fs";
29895
- import { dirname as dirname4 } from "node:path";
30470
+ import { existsSync as existsSync27 } from "node:fs";
30471
+ import { dirname as dirname5 } from "node:path";
29896
30472
  function isWslBashPath2(p3) {
29897
30473
  if (!p3 || typeof p3 !== "string") return false;
29898
30474
  const n = p3.replace(/\//g, "\\").toLowerCase();
@@ -29954,7 +30530,7 @@ function resolveAgentShellSync() {
29954
30530
  function agentProbeEnv() {
29955
30531
  const env = { ...process.env };
29956
30532
  try {
29957
- const nodeDir = dirname4(process.execPath);
30533
+ const nodeDir = dirname5(process.execPath);
29958
30534
  if (!nodeDir) return env;
29959
30535
  const sep = process.platform === "win32" ? ";" : ":";
29960
30536
  const current = env.PATH ?? env.Path ?? "";
@@ -29971,7 +30547,7 @@ function agentProbeEnv() {
29971
30547
  }
29972
30548
  function existsSyncSafe2(p3) {
29973
30549
  try {
29974
- return existsSync25(p3);
30550
+ return existsSync27(p3);
29975
30551
  } catch {
29976
30552
  return false;
29977
30553
  }
@@ -30190,7 +30766,7 @@ var init_prereqChecks = __esm({
30190
30766
  });
30191
30767
 
30192
30768
  // src/cli/plugins/prefs.ts
30193
- 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";
30194
30770
  import path29 from "node:path";
30195
30771
  import os9 from "node:os";
30196
30772
  function getPluginPrefsPath() {
@@ -30199,8 +30775,8 @@ function getPluginPrefsPath() {
30199
30775
  function getPluginPrefs() {
30200
30776
  const file2 = getPluginPrefsPath();
30201
30777
  try {
30202
- if (!existsSync26(file2)) return { ...DEFAULTS2, dontAskAgain: {} };
30203
- const raw = readFileSync23(file2, "utf-8");
30778
+ if (!existsSync28(file2)) return { ...DEFAULTS2, dontAskAgain: {} };
30779
+ const raw = readFileSync25(file2, "utf-8");
30204
30780
  const parsed = JSON.parse(raw);
30205
30781
  if (parsed && typeof parsed === "object" && parsed.dontAskAgain && typeof parsed.dontAskAgain === "object") {
30206
30782
  const clean = {};
@@ -30215,8 +30791,8 @@ function getPluginPrefs() {
30215
30791
  }
30216
30792
  function writePluginPrefs(prefs) {
30217
30793
  const file2 = getPluginPrefsPath();
30218
- mkdirSync10(path29.dirname(file2), { recursive: true });
30219
- writeFileSync15(file2, JSON.stringify(prefs, null, 2), {
30794
+ mkdirSync11(path29.dirname(file2), { recursive: true });
30795
+ writeFileSync16(file2, JSON.stringify(prefs, null, 2), {
30220
30796
  encoding: "utf-8",
30221
30797
  mode: 384
30222
30798
  });
@@ -30251,7 +30827,7 @@ __export(registry_exports, {
30251
30827
  findPlugin: () => findPlugin,
30252
30828
  isBinaryOnPath: () => isBinaryOnPath
30253
30829
  });
30254
- import { existsSync as existsSync27 } from "node:fs";
30830
+ import { existsSync as existsSync29 } from "node:fs";
30255
30831
  import path30 from "node:path";
30256
30832
  function detectLocalBin(bin) {
30257
30833
  return (cwd) => {
@@ -30268,7 +30844,7 @@ function isBinaryOnPath(bin, opts = {}) {
30268
30844
  return false;
30269
30845
  }
30270
30846
  const platform = opts.platform ?? process.platform;
30271
- const exists = opts.exists ?? existsSync27;
30847
+ const exists = opts.exists ?? existsSync29;
30272
30848
  const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
30273
30849
  const pathMod = platform === "win32" ? path30.win32 : path30.posix;
30274
30850
  const sep = platform === "win32" ? ";" : ":";
@@ -30419,7 +30995,7 @@ __export(doctor_exports, {
30419
30995
  runDoctor: () => runDoctor
30420
30996
  });
30421
30997
  import { execSync as execSync2 } from "node:child_process";
30422
- import { existsSync as existsSync32, readFileSync as readFileSync27, readlinkSync, statSync as statSync6 } from "node:fs";
30998
+ import { existsSync as existsSync35, readFileSync as readFileSync30, readlinkSync, statSync as statSync6 } from "node:fs";
30423
30999
  import { createRequire as createRequire3 } from "node:module";
30424
31000
  import { fileURLToPath as fileURLToPath2 } from "node:url";
30425
31001
  import path35 from "node:path";
@@ -30427,9 +31003,9 @@ function findPackageRoot(start) {
30427
31003
  let dir = start;
30428
31004
  for (let i = 0; i < 6; i += 1) {
30429
31005
  const candidate = path35.join(dir, "package.json");
30430
- if (existsSync32(candidate)) {
31006
+ if (existsSync35(candidate)) {
30431
31007
  try {
30432
- const pkg = JSON.parse(readFileSync27(candidate, "utf8"));
31008
+ const pkg = JSON.parse(readFileSync30(candidate, "utf8"));
30433
31009
  if (pkg.name === "zelari-code") return dir;
30434
31010
  } catch {
30435
31011
  }
@@ -30453,7 +31029,7 @@ function tryExec(cmd) {
30453
31029
  function readPackageJson3() {
30454
31030
  try {
30455
31031
  const pkgPath = path35.join(packageRoot, "package.json");
30456
- return JSON.parse(readFileSync27(pkgPath, "utf8"));
31032
+ return JSON.parse(readFileSync30(pkgPath, "utf8"));
30457
31033
  } catch {
30458
31034
  return null;
30459
31035
  }
@@ -30469,7 +31045,7 @@ function checkShim(pkgName) {
30469
31045
  const isWin = process.platform === "win32";
30470
31046
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
30471
31047
  const shimPath = path35.join(prefix, shimName);
30472
- if (!existsSync32(shimPath)) {
31048
+ if (!existsSync35(shimPath)) {
30473
31049
  return FAIL(
30474
31050
  `shim not found at ${shimPath}
30475
31051
  fix: npm install -g ${pkgName}@latest --force`
@@ -30478,7 +31054,7 @@ function checkShim(pkgName) {
30478
31054
  try {
30479
31055
  const st = statSync6(shimPath);
30480
31056
  if (isWin) {
30481
- const content = readFileSync27(shimPath, "utf8");
31057
+ const content = readFileSync30(shimPath, "utf8");
30482
31058
  if (content.includes(`${pkgName}\\bin\\`) || content.includes(`${pkgName}/bin/`)) {
30483
31059
  return OK(`shim OK at ${shimPath} (${st.size} bytes)`);
30484
31060
  }
@@ -30537,7 +31113,7 @@ function checkNode(pkg) {
30537
31113
  }
30538
31114
  function checkBundle() {
30539
31115
  const bundle = path35.join(packageRoot, "dist", "cli", "main.bundled.js");
30540
- if (!existsSync32(bundle)) {
31116
+ if (!existsSync35(bundle)) {
30541
31117
  return FAIL(
30542
31118
  `dist/cli/main.bundled.js missing at ${bundle}
30543
31119
  fix: npm run build:cli (then reinstall or run via tsx)`
@@ -34670,13 +35246,22 @@ function useChatTurn(params) {
34670
35246
  let systemPrompt;
34671
35247
  try {
34672
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;
34673
35253
  systemPrompt = buildSystemPrompt(singleAgentRole, {
34674
35254
  tools: getAllTools(),
34675
35255
  toolNames: toolListNames,
35256
+ mode: "agent",
35257
+ projectInstructions: projectInstructions || void 0,
34676
35258
  aiConfig: {
34677
35259
  enabledSkills: [],
34678
35260
  enabledTools: toolListNames,
34679
- customPromptModules: [SINGLE_AGENT_IDENTITY_MODULE, languageModule],
35261
+ customPromptModules: [
35262
+ SINGLE_AGENT_IDENTITY_MODULE,
35263
+ languageModule
35264
+ ],
34680
35265
  agentSkillConfigs: []
34681
35266
  },
34682
35267
  workspaceContext: workspaceContext || void 0,
@@ -36466,8 +37051,8 @@ init_registry2();
36466
37051
  // src/cli/plugins/installer.ts
36467
37052
  init_cmdline();
36468
37053
  init_updater();
36469
- import { spawn as spawn8 } from "node:child_process";
36470
- async function installPlugin(spec, cwd, executor = spawn8) {
37054
+ import { spawn as spawn9 } from "node:child_process";
37055
+ async function installPlugin(spec, cwd, executor = spawn9) {
36471
37056
  const scopeFlag = spec.installScope === "global" ? "-g" : "-D";
36472
37057
  const args = ["install", scopeFlag, spec.npmPackage];
36473
37058
  const primary = await runNpm2(executor, args, cwd, "shim");
@@ -36594,7 +37179,7 @@ async function handlePromoteMember(ctx, memberId) {
36594
37179
  }
36595
37180
 
36596
37181
  // src/cli/branchManager.ts
36597
- 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";
36598
37183
  import path32 from "node:path";
36599
37184
  import os11 from "node:os";
36600
37185
  var META_FILENAME = "meta.json";
@@ -36616,11 +37201,11 @@ function sessionsPathFor(name, baseDir) {
36616
37201
  }
36617
37202
  function readBranchMeta(name, baseDir) {
36618
37203
  const metaPath = metaPathFor(name, baseDir);
36619
- if (!existsSync28(metaPath)) {
37204
+ if (!existsSync30(metaPath)) {
36620
37205
  throw new BranchNotFoundError(`Branch "${name}" not found`);
36621
37206
  }
36622
37207
  try {
36623
- const raw = readFileSync24(metaPath, "utf-8");
37208
+ const raw = readFileSync26(metaPath, "utf-8");
36624
37209
  const parsed = JSON.parse(raw);
36625
37210
  if (!parsed || typeof parsed !== "object" || typeof parsed.name !== "string" || typeof parsed.createdAt !== "number" || typeof parsed.fromSessionId !== "string") {
36626
37211
  throw new BranchCorruptError(`Branch "${name}" meta.json is malformed`);
@@ -36637,8 +37222,8 @@ function readBranchMeta(name, baseDir) {
36637
37222
  }
36638
37223
  function writeBranchMeta(name, baseDir, meta3) {
36639
37224
  const metaPath = metaPathFor(name, baseDir);
36640
- mkdirSync11(path32.dirname(metaPath), { recursive: true });
36641
- 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");
36642
37227
  }
36643
37228
  async function countSessions(name, baseDir) {
36644
37229
  const sessionsPath = sessionsPathFor(name, baseDir);
@@ -36676,7 +37261,7 @@ var SessionNotFoundError = class extends Error {
36676
37261
  };
36677
37262
  function branchExists(name, baseDir = getBranchesBaseDir()) {
36678
37263
  const bp = branchPathFor(name, baseDir);
36679
- return existsSync28(bp) && existsSync28(metaPathFor(name, baseDir));
37264
+ return existsSync30(bp) && existsSync30(metaPathFor(name, baseDir));
36680
37265
  }
36681
37266
  async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(), sessionsBaseDir = getSessionsBaseDir()) {
36682
37267
  if (!name || name.trim().length === 0) {
@@ -36689,12 +37274,12 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
36689
37274
  throw new BranchAlreadyExistsError(name);
36690
37275
  }
36691
37276
  const sourcePath = path32.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
36692
- if (!existsSync28(sourcePath)) {
37277
+ if (!existsSync30(sourcePath)) {
36693
37278
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
36694
37279
  }
36695
37280
  const branchPath = branchPathFor(name, baseDir);
36696
37281
  const branchSessionsPath = sessionsPathFor(name, baseDir);
36697
- mkdirSync11(branchSessionsPath, { recursive: true });
37282
+ mkdirSync12(branchSessionsPath, { recursive: true });
36698
37283
  const destPath = path32.join(branchSessionsPath, `${fromSessionId}.jsonl`);
36699
37284
  await fs17.copyFile(sourcePath, destPath);
36700
37285
  const meta3 = {
@@ -36722,7 +37307,7 @@ async function listBranches(baseDir = getBranchesBaseDir()) {
36722
37307
  const results = [];
36723
37308
  for (const entry of entries) {
36724
37309
  const metaPath = metaPathFor(entry, baseDir);
36725
- if (!existsSync28(metaPath)) continue;
37310
+ if (!existsSync30(metaPath)) continue;
36726
37311
  try {
36727
37312
  const meta3 = readBranchMeta(entry, baseDir);
36728
37313
  const sessionCount = await countSessions(entry, baseDir);
@@ -37258,7 +37843,7 @@ import path34 from "node:path";
37258
37843
  import os12 from "node:os";
37259
37844
 
37260
37845
  // src/cli/skillHistory.ts
37261
- 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";
37262
37847
  var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
37263
37848
  async function readSkillHistory(file2) {
37264
37849
  let raw = "";
@@ -38436,9 +39021,9 @@ function ContinueKey({ onContinue }) {
38436
39021
  init_providerConfig();
38437
39022
 
38438
39023
  // src/cli/wizard/firstRun.ts
38439
- import { existsSync as existsSync30 } from "node:fs";
39024
+ import { existsSync as existsSync32 } from "node:fs";
38440
39025
  function shouldRunWizard(input) {
38441
- const exists = input.exists ?? existsSync30;
39026
+ const exists = input.exists ?? existsSync32;
38442
39027
  if (input.hasResetConfigFlag) {
38443
39028
  return { shouldRun: true, reason: "--reset-config flag forced wizard" };
38444
39029
  }
@@ -38723,7 +39308,7 @@ init_keyStore();
38723
39308
  init_providerConfig();
38724
39309
  init_openai_compatible();
38725
39310
  init_phase();
38726
- import { readFileSync as readFileSync25 } from "node:fs";
39311
+ import { readFileSync as readFileSync27 } from "node:fs";
38727
39312
  function parseHeadlessFlags(argv) {
38728
39313
  if (!argv.includes("--headless")) {
38729
39314
  return { options: null };
@@ -38791,7 +39376,7 @@ function parseHeadlessFlags(argv) {
38791
39376
  let raw = null;
38792
39377
  if (arg === "--history-file") {
38793
39378
  try {
38794
- raw = readFileSync25(next, "utf-8");
39379
+ raw = readFileSync27(next, "utf-8");
38795
39380
  } catch {
38796
39381
  raw = null;
38797
39382
  }
@@ -38963,11 +39548,50 @@ ${err.stack}` : "";
38963
39548
  function planModeFromOpts(opts) {
38964
39549
  return (opts.phase ?? "build") === "plan";
38965
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
+ }
38966
39589
  async function runHeadlessSingle(opts, provider, model, providerStream) {
38967
39590
  const sessionId = crypto.randomUUID();
38968
39591
  const { registry: toolRegistry } = createBuiltinToolRegistry({
38969
39592
  planMode: planModeFromOpts(opts)
38970
39593
  });
39594
+ await registerHeadlessMcp(toolRegistry, opts);
38971
39595
  const tools = toolRegistry.toOpenAITools().map((t) => ({
38972
39596
  name: t.function.name,
38973
39597
  description: t.function.description,
@@ -39004,21 +39628,38 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
39004
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."
39005
39629
  ].join("\n")
39006
39630
  };
39007
- systemPrompt = buildSystemPrompt(headlessRole, {
39008
- tools: getAllTools(),
39009
- toolNames,
39010
- aiConfig: {
39011
- enabledSkills: [],
39012
- enabledTools: toolNames,
39013
- customPromptModules: [SINGLE_AGENT_IDENTITY_MODULE, {
39014
- type: "language-policy",
39015
- title: "Response Language",
39016
- priority: 5,
39017
- content: languageDirectiveContent
39018
- }],
39019
- 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
+ }
39020
39661
  }
39021
- });
39662
+ );
39022
39663
  } catch {
39023
39664
  systemPrompt = [
39024
39665
  "You are zelari-code, a CLI coding agent. Be concise and direct.",
@@ -39117,7 +39758,7 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
39117
39758
  if (finalReason === "error") return 3;
39118
39759
  return exitCode;
39119
39760
  }
39120
- async function buildCouncilToolRegistry(planMode) {
39761
+ async function buildCouncilToolRegistry(planMode, opts) {
39121
39762
  const { registry: toolRegistry } = createBuiltinToolRegistry({ planMode });
39122
39763
  const { createWorkspaceContext: createWorkspaceContext2, createWorkspaceStubs: createWorkspaceStubs2 } = await Promise.resolve().then(() => (init_stubs(), stubs_exports));
39123
39764
  const { createWorkspaceToolRegistry: createWorkspaceToolRegistry2 } = await Promise.resolve().then(() => (init_toolRegistry2(), toolRegistry_exports2));
@@ -39129,18 +39770,30 @@ async function buildCouncilToolRegistry(planMode) {
39129
39770
  if (td) toolRegistry.register(td);
39130
39771
  }
39131
39772
  setWorkspaceStubs2(createWorkspaceStubs2(realCtx));
39773
+ if (opts) {
39774
+ await registerHeadlessMcp(toolRegistry, opts);
39775
+ }
39132
39776
  return { toolRegistry, workspaceCtx: realCtx };
39133
39777
  }
39134
39778
  async function runHeadlessCouncil(opts, provider, model, providerStream) {
39135
39779
  const { dispatchCouncil: dispatchCouncil2 } = await Promise.resolve().then(() => (init_councilDispatcher(), councilDispatcher_exports));
39136
39780
  const sessionId = crypto.randomUUID();
39137
- const { toolRegistry } = await buildCouncilToolRegistry(planModeFromOpts(opts));
39781
+ const { toolRegistry } = await buildCouncilToolRegistry(
39782
+ planModeFromOpts(opts),
39783
+ opts
39784
+ );
39138
39785
  const { FeedbackStore: FeedbackStore2 } = await Promise.resolve().then(() => (init_councilFeedback(), councilFeedback_exports));
39139
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);
39140
39791
  let exitCode = 0;
39141
39792
  const scrub = createStreamScrubber();
39793
+ let lastAssistantText = "";
39794
+ let currentAssistantText = "";
39142
39795
  try {
39143
- for await (const event of dispatchCouncil2(opts.task, {
39796
+ for await (const event of dispatchCouncil2(effectiveTask, {
39144
39797
  apiKey: "REDACTED",
39145
39798
  model,
39146
39799
  provider: "openai-compatible",
@@ -39152,9 +39805,11 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
39152
39805
  })) {
39153
39806
  if (event.type === "message_start") {
39154
39807
  scrub.reset();
39808
+ currentAssistantText = "";
39155
39809
  }
39156
39810
  if (event.type === "message_delta" && typeof event.delta === "string") {
39157
39811
  const cleanDelta = scrub.push(event.delta);
39812
+ if (cleanDelta.length > 0) currentAssistantText += cleanDelta;
39158
39813
  if (opts.output === "json") {
39159
39814
  if (cleanDelta.length > 0) emitEvent({ ...event, delta: cleanDelta });
39160
39815
  } else if (opts.output === "plain" && cleanDelta.length > 0) {
@@ -39162,10 +39817,18 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
39162
39817
  }
39163
39818
  } else {
39164
39819
  if (opts.output === "json") emitEvent(event);
39165
- if (event.type === "agent_end") {
39820
+ if (event.type === "message_end" || event.type === "agent_end") {
39166
39821
  const tail = scrub.flush();
39167
- if (opts.output === "plain" && tail.length > 0) process.stdout.write(tail);
39168
- if (event.reason === "error") exitCode = 3;
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
+ }
39169
39832
  } else if (event.type === "error" && event.severity === "fatal") {
39170
39833
  exitCode = 2;
39171
39834
  }
@@ -39178,6 +39841,16 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
39178
39841
  );
39179
39842
  return 2;
39180
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
+ }
39181
39854
  return exitCode;
39182
39855
  }
39183
39856
  async function runHeadlessZelari(opts, provider, model, providerStream) {
@@ -39194,7 +39867,10 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
39194
39867
  hasPlan: hasWorkspacePlan2(projectRoot)
39195
39868
  });
39196
39869
  const memory = await getMemoryBackend2(projectRoot);
39197
- const { toolRegistry, workspaceCtx } = await buildCouncilToolRegistry(planModeFromOpts(opts));
39870
+ const { toolRegistry, workspaceCtx } = await buildCouncilToolRegistry(
39871
+ planModeFromOpts(opts),
39872
+ opts
39873
+ );
39198
39874
  const feedbackStore = new FeedbackStore2();
39199
39875
  const chairmanBudget = envNumber(process.env.ZELARI_MODE_MAX_TOOLS_LUCIFER, {
39200
39876
  default: 30,
@@ -39207,11 +39883,16 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
39207
39883
  process.stderr.write(message + "\n");
39208
39884
  }
39209
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);
39210
39890
  emit(`[zelari] mission brief
39211
39891
  ${JSON.stringify({ deliverable: brief.deliverableThisMission, mvp: brief.sliceMvp?.title }, null, 0)}`);
39212
39892
  let exitCode = 0;
39893
+ let lastMissionAssistant = "";
39213
39894
  try {
39214
- const state2 = await runZelariMission2(opts.task, brief, {
39895
+ const state2 = await runZelariMission2(missionTask, brief, {
39215
39896
  projectRoot,
39216
39897
  memory,
39217
39898
  emit,
@@ -39293,6 +39974,11 @@ ${ragContext}` : slicePrompt;
39293
39974
  }
39294
39975
  } catch {
39295
39976
  }
39977
+ if (synthesisText.trim()) {
39978
+ lastMissionAssistant = cleanAgentContent(synthesisText, {
39979
+ stripQuestion: false
39980
+ });
39981
+ }
39296
39982
  return {
39297
39983
  completionOk,
39298
39984
  ran: membersCompleted > 0 || synthesisText.length > 0,
@@ -39314,6 +40000,18 @@ ${ragContext}` : slicePrompt;
39314
40000
  } finally {
39315
40001
  await memory.close().catch(() => void 0);
39316
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
+ }
39317
40015
  return exitCode;
39318
40016
  }
39319
40017
 
@@ -39564,9 +40262,9 @@ async function runDiscoverModels(providerArg) {
39564
40262
 
39565
40263
  // src/cli/skillsMd.ts
39566
40264
  init_skills2();
39567
- import { existsSync as existsSync31, readdirSync as readdirSync4, readFileSync as readFileSync26 } from "node:fs";
39568
- import { join as join23 } from "node:path";
39569
- 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";
39570
40268
  var CODING_CATEGORIES = /* @__PURE__ */ new Set([
39571
40269
  "plan",
39572
40270
  "refactor",
@@ -39581,10 +40279,10 @@ var CODING_CATEGORIES = /* @__PURE__ */ new Set([
39581
40279
  ]);
39582
40280
  function skillMdSearchDirs(projectRoot = process.cwd()) {
39583
40281
  return [
39584
- join23(projectRoot, ".zelari", "skills"),
39585
- join23(projectRoot, ".claude", "skills"),
39586
- join23(projectRoot, ".opencode", "skills"),
39587
- 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")
39588
40286
  ];
39589
40287
  }
39590
40288
  function parseSkillMd(content, sourcePath) {
@@ -39642,7 +40340,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
39642
40340
  const summary = { loaded: [], skipped: [] };
39643
40341
  const seen = new Set(options.existingIds ?? []);
39644
40342
  for (const dir of skillMdSearchDirs(projectRoot)) {
39645
- if (!existsSync31(dir)) continue;
40343
+ if (!existsSync33(dir)) continue;
39646
40344
  let entries;
39647
40345
  try {
39648
40346
  entries = readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -39650,10 +40348,10 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
39650
40348
  continue;
39651
40349
  }
39652
40350
  for (const entry of entries) {
39653
- const skillPath = join23(dir, entry, "SKILL.md");
39654
- if (!existsSync31(skillPath)) continue;
40351
+ const skillPath = join25(dir, entry, "SKILL.md");
40352
+ if (!existsSync33(skillPath)) continue;
39655
40353
  try {
39656
- const parsed = parseSkillMd(readFileSync26(skillPath, "utf8"), skillPath);
40354
+ const parsed = parseSkillMd(readFileSync28(skillPath, "utf8"), skillPath);
39657
40355
  if (!parsed) {
39658
40356
  summary.skipped.push({ path: skillPath, reason: "missing/invalid frontmatter (name, description) or empty body" });
39659
40357
  continue;
@@ -39676,6 +40374,116 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
39676
40374
  // src/cli/main.ts
39677
40375
  init_skills2();
39678
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();
39679
40487
  var VERSION = getCurrentVersion();
39680
40488
  function runPreflight() {
39681
40489
  if (process.env.ZELARI_SKIP_PREFLIGHT === "1") return;
@@ -39772,10 +40580,190 @@ function pickRootComponent() {
39772
40580
  }
39773
40581
  if (argv.includes("--help") || argv.includes("-h")) {
39774
40582
  console.log(
39775
- "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"
39776
40584
  );
39777
40585
  process.exit(0);
39778
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
+ }
39779
40767
  if (wantsPrintConfig(argv)) {
39780
40768
  try {
39781
40769
  printDesktopConfig();