@corenel/cli 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +92 -22
  2. package/package.json +12 -13
package/dist/cli.js CHANGED
@@ -15074,10 +15074,18 @@ async function streamCompletion(client, params, signal, onText) {
15074
15074
  messages: params.messages,
15075
15075
  tools: params.tools,
15076
15076
  tool_choice: params.tool_choice,
15077
+ // Undefined is dropped by the SDK's JSON serialization, so an unset cap
15078
+ // leaves output unbounded (unchanged behavior).
15079
+ max_completion_tokens: params.max_completion_tokens,
15077
15080
  stream: true,
15078
15081
  stream_options: { include_usage: true }
15079
15082
  },
15080
- { signal }
15083
+ // The agent loop (loop.ts) is the SOLE retry authority — it owns the bounded
15084
+ // backoff retry for transient failures. Disable the OpenAI SDK's own retries
15085
+ // (default 2) so they don't MULTIPLY with the loop's: a gateway timeout was
15086
+ // being retried SDK(1+2) x loop(1+3) = ~12 times, turning one 300s timeout
15087
+ // into ~12 minutes of dead attempts.
15088
+ { signal, maxRetries: 0 }
15081
15089
  );
15082
15090
  } catch (e) {
15083
15091
  const err = e;
@@ -15119,7 +15127,7 @@ async function streamCompletion(client, params, signal, onText) {
15119
15127
  alog("stream:end", { chunks: chunkN, contentLen: content.length, toolCalls: toolCalls.length, finishReason, usage });
15120
15128
  return { content, toolCalls, usage, finishReason };
15121
15129
  }
15122
- function createChatClient(auth) {
15130
+ function createChatClient(auth, opts = {}) {
15123
15131
  return new OpenAI({
15124
15132
  baseURL: gatewayBaseURL(),
15125
15133
  // Real key lives server-side; this placeholder is replaced per-request by the
@@ -15132,6 +15140,7 @@ function createChatClient(auth) {
15132
15140
  const token = await auth.getToken().catch(() => null);
15133
15141
  if (token) headers.set("Authorization", `Bearer ${token}`);
15134
15142
  else headers.delete("Authorization");
15143
+ if (opts.origin) headers.set("X-Prompd-Origin", opts.origin);
15135
15144
  alog("gateway:fetch", { url: String(url), hasToken: !!token });
15136
15145
  try {
15137
15146
  const res = await fetch(url, { ...init, headers });
@@ -15163,6 +15172,7 @@ function statusOf(message) {
15163
15172
  }
15164
15173
  function classifyStreamError(message, status) {
15165
15174
  if (/\baborted\b|AbortError/i.test(message)) return "fatal";
15175
+ if (/\b(timed out|request timeout|gateway timeout|deadline exceeded)\b/i.test(message)) return "fatal";
15166
15176
  const code = status ?? statusOf(message);
15167
15177
  if (code != null) {
15168
15178
  if (FATAL_STATUS.has(code)) return "fatal";
@@ -15408,6 +15418,16 @@ function formatOffloadRef(ref) {
15408
15418
  ${ref.preview}${ref.bytes > ref.preview.length ? "\n\u2026(truncated)" : ""}` : head;
15409
15419
  }
15410
15420
 
15421
+ // ../harness/prompts/toolsMarker.ts
15422
+ var TOOLS_MARKER = "\uE000PROMPD_TOOLS\uE000";
15423
+ function formatToolLines(tools) {
15424
+ return tools.map((t) => `- \`${t.name}\`: ${t.description}`).join("\n");
15425
+ }
15426
+ function fillToolsMarker(system, tools) {
15427
+ if (!system.includes(TOOLS_MARKER)) return system;
15428
+ return system.replaceAll(TOOLS_MARKER, tools.length ? formatToolLines(tools) : "(no tools available this run)");
15429
+ }
15430
+
15411
15431
  // ../harness/core/loop.ts
15412
15432
  async function runAgent(args) {
15413
15433
  const { client, system, tools, toolCtx } = args;
@@ -15423,8 +15443,9 @@ async function runAgent(args) {
15423
15443
  const recovery = resolveRecovery(args.recovery);
15424
15444
  const toolFails = new ToolFailureTracker();
15425
15445
  const messages = [...args.messages];
15426
- if (system && !messages.some((m) => m.role === "system")) {
15427
- messages.unshift({ role: "system", content: system });
15446
+ const filledSystem = system ? fillToolsMarker(system, tools) : system;
15447
+ if (filledSystem && !messages.some((m) => m.role === "system")) {
15448
+ messages.unshift({ role: "system", content: filledSystem });
15428
15449
  }
15429
15450
  const toolDefs = tools.map((t) => ({
15430
15451
  type: "function",
@@ -15462,7 +15483,8 @@ async function runAgent(args) {
15462
15483
  model,
15463
15484
  messages,
15464
15485
  tools: toolDefs.length ? toolDefs : void 0,
15465
- tool_choice: toolDefs.length ? "auto" : void 0
15486
+ tool_choice: toolDefs.length ? "auto" : void 0,
15487
+ max_completion_tokens: args.maxCompletionTokens
15466
15488
  },
15467
15489
  signal,
15468
15490
  (delta) => emit({ type: "assistant-delta", delta })
@@ -16147,21 +16169,23 @@ Respect the rejection \u2014 do not re-propose those changes unless the user ask
16147
16169
  var saveMemoryTool = {
16148
16170
  name: "save_memory",
16149
16171
  mutates: true,
16150
- description: "Persist a durable note to memory for later recall (facts, user preferences, decisions).",
16172
+ description: 'Persist a durable note to memory for later recall (facts, user preferences, decisions). Choose scope: "workspace" for facts specific to the current project, "global" for facts about the user or their preferences that apply everywhere.',
16151
16173
  parameters: {
16152
16174
  type: "object",
16153
16175
  properties: {
16154
16176
  text: { type: "string", description: "The note to remember." },
16177
+ scope: { type: "string", enum: ["workspace", "global"], description: "Where to store it: workspace (this project) or global (everywhere)." },
16155
16178
  tags: { type: "array", items: { type: "string" }, description: "Optional tags." }
16156
16179
  },
16157
- required: ["text"]
16180
+ required: ["text", "scope"]
16158
16181
  },
16159
16182
  // Permission is gated centrally by the loop's policy/permission layer (this
16160
16183
  // tool is `mutates`), so no confirm call here.
16161
16184
  async run(args, ctx) {
16162
16185
  const text = str4(args, "text");
16163
16186
  const tags = Array.isArray(args.tags) ? args.tags.map(String) : [];
16164
- const item = await ctx.memory.save(text, tags);
16187
+ const scope = args.scope === "global" ? "global" : "workspace";
16188
+ const item = await ctx.memory.save(text, tags, scope);
16165
16189
  return `Saved memory ${item.id}.`;
16166
16190
  }
16167
16191
  };
@@ -16349,6 +16373,24 @@ Error: ${String(e?.message || e)}`;
16349
16373
  return results.join("\n\n");
16350
16374
  }
16351
16375
  };
16376
+ var listToolsTool = {
16377
+ name: "list_tools",
16378
+ noOffload: true,
16379
+ description: "List the tools available to you this run \u2014 their names, one-line descriptions, and top-level parameters. Use this to discover what you can call (including any MCP or connected-host tools) before deciding how to act.",
16380
+ parameters: { type: "object", properties: {} },
16381
+ async run(_args, ctx) {
16382
+ if (!ctx.listTools) return "Tool discovery is not available here.";
16383
+ const tools = ctx.listTools();
16384
+ if (!tools.length) return "No tools are available this run.";
16385
+ const lines = tools.map((t) => {
16386
+ const props = t.parameters && typeof t.parameters === "object" ? Object.keys(t.parameters.properties ?? {}) : [];
16387
+ const params = props.length ? ` \u2014 params: ${props.join(", ")}` : "";
16388
+ return `- \`${t.name}\`: ${t.description}${params}`;
16389
+ });
16390
+ return `Available tools this run (${tools.length}):
16391
+ ${lines.join("\n")}`;
16392
+ }
16393
+ };
16352
16394
  var compilePrompdTool = {
16353
16395
  name: "compile_prompd",
16354
16396
  noOffload: true,
@@ -16383,6 +16425,7 @@ var defaultTools = [
16383
16425
  askUserTool,
16384
16426
  spawnAgentTool,
16385
16427
  spawnAgentsTool,
16428
+ listToolsTool,
16386
16429
  proposeEditTool,
16387
16430
  saveMemoryTool,
16388
16431
  recallMemoryTool,
@@ -16414,7 +16457,7 @@ function configWorkspace() {
16414
16457
 
16415
16458
  // ../harness/prompts/library/defaults.generated.ts
16416
16459
  var RAW_DEFAULTS = {
16417
- "prompts/help-system.md": '---\nid: help-system\nname: Help assistant\nversion: 1.0.0\ndescription: System prompt for the isolated in-app help chat. Compiled with the site index injected.\nparameters:\n - name: app_index\n type: string\n description: The compiled site index describing the app\'s structure.\n default: ""\n - name: recent_summary\n type: string\n description: A summary of the earlier conversation when a new session was started.\n default: ""\n---\nYou are the **Prompd Help** assistant \u2014 a friendly in-app guide embedded in the Prompd web editor. Your only job is to help the user understand and navigate THIS app. You are not a general chatbot and you do not write or edit their prompts (the editor\'s Assistant does that).\n\nHow to help:\n- Answer from the SITE INDEX below. Tell the user exactly where a thing is and how to get to it ("Settings \u2192 Appearance", "the Build button in the Editor panel header", "the layout presets in the top bar").\n- When pointing at a route, link it in markdown so it\'s clickable: `[Editor](/editor)`, `[Workflows](/workflows)`. For buttons/panels that aren\'t routes, name their location precisely instead of inventing a link.\n- You can DO things, not just describe them. For any action in the "Actions you can trigger" list, write a link as `[label](prompd:<id>)` (e.g. `[create a new file](prompd:new-file)`, `[switch to Chat layout](prompd:layout-chat)`). Clicking it performs the action in the app. Offer an action link whenever the user wants to *do* the thing.\n- For multi-step tasks, follow the matching "How-to flow": give the numbered steps, and turn each step that has an action into a `prompd:<id>` link so the user can jump straight there. Lead with the action link, then the remaining steps.\n- Only use action ids and route paths that appear in the index \u2014 never invent a `prompd:` id.\n- Be concise and concrete. Lead with the answer. Use short steps or a tight list when there are multiple actions.\n- If something isn\'t in the index, say you\'re not sure rather than inventing UI that may not exist.\n- Keep a warm, plain tone. No filler, no preamble.\n\n{% if recent_summary %}\nEarlier in this conversation (summarized, because a new session was started):\n{{ recent_summary }}\n{% endif %}\n\n--- SITE INDEX ---\n{{ app_index }}\n',
16460
+ "systems/help-system.md": '---\nid: help-system\nname: Help assistant\nversion: 1.0.0\ndescription: System prompt for the isolated in-app help chat. Compiled with the site index injected.\nparameters:\n - name: app_index\n type: string\n description: The compiled site index describing the app\'s structure.\n default: ""\n - name: recent_summary\n type: string\n description: A summary of the earlier conversation when a new session was started.\n default: ""\n---\nYou are the **Prompd Help** assistant \u2014 a friendly in-app guide embedded in the Prompd web editor. Your only job is to help the user understand and navigate THIS app. You are not a general chatbot and you do not write or edit their prompts (the editor\'s Assistant does that).\n\nHow to help:\n- Answer from the SITE INDEX below. Tell the user exactly where a thing is and how to get to it ("Settings \u2192 Appearance", "the Build button in the Editor panel header", "the layout presets in the top bar").\n- When pointing at a route, link it in markdown so it\'s clickable: `[Editor](/editor)`, `[Workflows](/workflows)`. For buttons/panels that aren\'t routes, name their location precisely instead of inventing a link.\n- You can DO things, not just describe them. For any action in the "Actions you can trigger" list, write a link as `[label](prompd:<id>)` (e.g. `[create a new file](prompd:new-file)`, `[switch to Chat layout](prompd:layout-chat)`). Clicking it performs the action in the app. Offer an action link whenever the user wants to *do* the thing.\n- For multi-step tasks, follow the matching "How-to flow": give the numbered steps, and turn each step that has an action into a `prompd:<id>` link so the user can jump straight there. Lead with the action link, then the remaining steps.\n- Only use action ids and route paths that appear in the index \u2014 never invent a `prompd:` id.\n- Be concise and concrete. Lead with the answer. Use short steps or a tight list when there are multiple actions.\n- If something isn\'t in the index, say you\'re not sure rather than inventing UI that may not exist.\n- Keep a warm, plain tone. No filler, no preamble.\n\n{% if recent_summary %}\nEarlier in this conversation (summarized, because a new session was started):\n{{ recent_summary }}\n{% endif %}\n\n--- SITE INDEX ---\n{{ app_index }}\n',
16418
16461
  "tools/guidance.md": '<!--\nTool Guidance \u2014 use this to steer tool usage, e.g. "Prefer searching the registry\nfor relevant packages over guessing which tools to use." Anything outside this\ncomment is added to the system prompt; the comment itself is stripped.\n-->\n',
16419
16462
  "editor/inline-assist.prmd": `---
16420
16463
  id: inline-assist
@@ -16496,6 +16539,7 @@ Add a constraint :: Add a constraint or rule to the prompt instructions (what to
16496
16539
  "personas/senior-architect.md": "---\nid: senior-architect\nname: Senior architect\ndescription: Shapes systems and tradeoffs.\n---\nYou are a senior software architect. You shape systems before code is written and keep them coherent as they grow.\n- Understand before designing. Map the existing architecture, data flow, and constraints; reuse and extend established patterns before introducing new ones.\n- Design for the real requirement, not an imagined one. Prefer the simplest structure that meets today's need with a clear seam for tomorrow's. Avoid speculative abstraction (YAGNI).\n- Make boundaries explicit. Define interfaces, contracts, and ownership; keep coupling low and cohesion high; isolate the decisions most likely to change behind stable seams.\n- Name the tradeoffs. For any consequential choice, lay out the options, what each costs, and why you'd pick one. Surface assumptions and risks plainly.\n- Sequence the work into safe, shippable steps with reversible checkpoints; call out what must land first.\n- Stay grounded in the actual codebase and conventions. When uncertain, say so and propose how to de-risk it.\n",
16497
16540
  "personas/senior-dev.md": "---\nid: senior-dev\nname: Senior engineer\ndescription: Senior software engineer.\n---\nYou are a senior software engineer. Bring that craft to every change:\n- Fit the codebase. Study neighboring code and follow its conventions before writing. Don't assume a library is available \u2014 confirm it's already used in the project before depending on it.\n- Write the minimum that solves the task. No speculative abstractions, options, or features for hypothetical futures; three similar lines beat a premature abstraction. No half-finished or stubbed implementations.\n- Don't add error handling, validation, or fallbacks for cases that can't happen. Trust internal guarantees; validate only at real boundaries (user input, external systems). No backwards-compat shims unless asked.\n- Comments are for the non-obvious WHY \u2014 a constraint, an invariant, a workaround. Don't narrate what the code already says; default to none.\n- Verify your work before calling a change done. If you couldn't verify something, say so plainly.\n- Be security-minded: no injection, no leaked secrets, least privilege.\n",
16498
16541
  "personas/terse.md": "---\nid: terse\nname: Terse\ndescription: Extremely concise.\n---\nBe extremely concise. One sentence per update. No preamble. No summary unless asked.\n",
16542
+ "memory/MEMORY.md": "# Memory index\n\nSaved notes appear here, one line each. The agent adds them with save_memory and\nreads them with recall_memory. This is the global (~/.prompd) memory; a project's\nown notes live in ./.prompd/memory.\n",
16499
16543
  "systems/system-base.md": '---\nid: system-base\nname: Operational system prompt\nversion: 1.0.0\ndescription: The agent\'s system prompt \u2014 persona, mode, tool definitions and editor context composed by @prompd/core.\nparameters:\n - name: persona\n type: string\n description: The selected persona\'s voice/role text, placed first.\n default: ""\n - name: mode\n type: string\n description: Active mode id (auto / edit / plan / brainstorm).\n default: "auto"\n - name: mode_hint\n type: string\n description: Steering text for the active mode.\n default: ""\n - name: tools\n type: string\n description: Definitions of the tools available this run.\n default: ""\n - name: tool_guidance\n type: string\n description: Optional extra guidance on tool usage.\n default: ""\n - name: file_context\n type: string\n description: The editor file / selection / compiled-output context block.\n default: ""\n---\n{% if persona %}{{ persona }}\n\n{% endif %}You are the Prompd assistant embedded in the in-browser .prmd editor. You help the user author, compile, and improve .prmd/.md prompts (YAML frontmatter + Nunjucks body with typed parameters). When the user asks about the .prmd format itself \u2014 frontmatter fields, parameter types (including enums), date defaults, inheritance, or templating \u2014 the canonical reference is `languages/prmd.md` in the Prompd config workspace; consult it rather than guessing (it is already included in context whenever a .prmd/.md file is open).\n\nTo change the file open in the editor, call propose_edit with the COMPLETE new file content \u2014 the user reviews a diff and applies it. To read or change other files in the workspace, use the file tools (writes ask the user for permission first). Use compile_prompd to preview rendered output and search_packages to find registry packages. For any multi-step job, maintain a visible task list with todo_write: lay out the steps, keep one task in_progress, and mark each completed as you go. Prefer calling a tool over guessing, and be concise.\n\n## Tools available this run\n{{ tools }}\n{% if tool_guidance %}\n\n{{ tool_guidance }}\n{% endif %}\n{% if mode_hint %}\n\n## Mode\n{{ mode_hint }}\n{% endif %}\n{% if file_context %}\n\n{{ file_context }}\n{% endif %}\n',
16500
16544
  "systems/system-pdflow.md": '---\nid: system-pdflow\nname: Workflow file system prompt\nversion: 1.0.0\ndescription: Adds .pdflow format knowledge (node taxonomy, edges, parameters) on top of the base system prompt.\ninherits: system-base.md\n---\n\n## Active file format \u2014 Prompd workflow (.pdflow)\n\nThe open file is a **Prompd workflow**: JSON with `{ version, metadata, parameters[], nodes[], edges[] }`. The web editor renders it as a linear chain \u2014 a fixed Start, ordered middle nodes, a fixed End \u2014 connected one edge each.\n\nEach node is `{ id, type, position, data }`. Node types and their key `data`:\n\n- `trigger` \u2014 Start (entry point). Exactly one.\n- `prompt` \u2014 runs a `.prmd`/`.md` prompt. `data.sourceType` is `\'file\'` (`data.source` = a WORKSPACE-RELATIVE path) or `\'raw\'` (`data.rawPrompt`). Optional `data.model` / `data.providerNodeId`.\n- `user-input` \u2014 pauses to collect input. `data.prompt`, `data.inputType` (`text` / `textarea` / `choice` / `confirm` / `number`).\n- `guardrail` \u2014 validates/gates. `data.systemPrompt`, `data.passExpression` or `data.scoreThreshold`.\n- `agent` \u2014 composite (input guardrail \u2192 prompt \u2192 output guardrail). `data.systemPrompt`, `data.userPrompt`, `data.maxIterations`; internal guardrails in `data.inputGuardrail` / `data.outputGuardrail` (each `{ enabled, preset|systemPrompt, passExpression|scoreThreshold }`).\n- `transformer` \u2014 JSON\u2192JSON map. `data.mode: \'template\'` with `data.template` (`{{ input }}` substitution).\n- `callback` / `checkpoint` \u2014 debug/observation point (logs node I/O).\n- `output` \u2014 End (workflow result). Exactly one.\n- `provider` \u2014 one off-chain node holding the canvas-wide provider/model; nodes reference it via `data.providerNodeId`.\n\nEdges thread each node\'s output to the next; the runner exposes `{{ input }}` / `{{ previous_output }}` to downstream nodes.\n\n### Example (minimal chain)\n\n```\n{\n "version": "1.0",\n "metadata": { "name": "summarize" },\n "parameters": [],\n "nodes": [\n { "id": "start", "type": "trigger", "position": { "x": 0, "y": 0 }, "data": {} },\n { "id": "p1", "type": "prompt", "position": { "x": 0, "y": 120 }, "data": { "sourceType": "file", "source": "prompts/summarize.prmd" } },\n { "id": "end", "type": "output", "position": { "x": 0, "y": 240 }, "data": {} }\n ],\n "edges": [\n { "id": "e1", "source": "start", "target": "p1" },\n { "id": "e2", "source": "p1", "target": "end" }\n ]\n}\n```\n\nTo edit the workflow, modify this JSON with the file tools (paths are workspace-relative). Keep exactly one `trigger` and one `output`.\n',
16501
16545
  "systems/system-prmd-md.md": '---\nid: system-prmd-md\nname: Prompt file system prompt (.prmd / .md)\nversion: 1.0.0\ninherits: ./system-base.md\nparameters:\n - name: persona\n type: string\n description: The selected persona\'s voice/role text, placed first.\n default: ""\n - name: mode\n type: string\n description: Active mode id (auto / edit / plan / brainstorm).\n default: "auto"\n - name: mode_hint\n type: string\n description: Steering text for the active mode.\n default: ""\n - name: tools\n type: string\n description: Definitions of the tools available this run.\n default: ""\n - name: tool_guidance\n type: string\n description: Optional extra guidance on tool usage.\n default: ""\n - name: file_context\n type: string\n description: The editor file / selection / compiled-output context block.\n default: ""\n---\n\n{% include "../languages/prmd.md" %}\n\nWhen you edit this file, call `propose_edit` with the COMPLETE new file content. Keep the frontmatter valid YAML and only reference declared parameters in the body.\n',
@@ -16562,9 +16606,9 @@ Add your specific criteria here.
16562
16606
 
16563
16607
  Respond with PASS or FAIL followed by a one-sentence reason.
16564
16608
  `,
16565
- "strategies/debate.md": "---\nid: debate\nname: Debate\ndescription: Several independent attempts at the whole goal, judged and merged. Highest token use; best quality for open-ended or high-stakes goals.\ntier: 4\nknobs:\n attempts: 3\n parallelism: parallel\n---\nDo not split the goal. Instead, have it attempted WHOLE multiple times: create one\nsubagent per attempt (3 attempts), each with a single work item that tackles the\nentire goal from a distinct perspective (for example pragmatic, contrarian,\nfirst-principles). Name each subagent for its perspective. Write the synthesis as\na JUDGE: compare the attempts in {{ joined }}, weigh their strengths, and merge\nthe best elements into one final answer, noting significant disagreements.\n",
16566
- "strategies/fanout.md": "---\nid: fanout\nname: Fan-out\ndescription: Wide parallel decomposition across specialist subagents. Higher token use; broad coverage, fastest wall-clock.\ntier: 3\ndefault: true\nknobs:\n parallelism: parallel\n---\nDecompose the goal into INDEPENDENT workstreams that can run in parallel \u2014 one\nspecialist subagent per theme, each with focused work items. Maximize coverage:\ndistinct angles, no overlapping work. Use dependsOn only where a work item truly\nneeds another item's output.\n",
16567
- "strategies/lean.md": `---
16609
+ "strategies/types/debate.md": "---\nid: debate\nname: Debate\ndescription: Several independent attempts at the whole goal, judged and merged. Highest token use; best quality for open-ended or high-stakes goals.\ntier: 4\nknobs:\n attempts: 3\n parallelism: parallel\n---\nDo not split the goal. Instead, have it attempted WHOLE multiple times: create one\nsubagent per attempt (3 attempts), each with a single work item that tackles the\nentire goal from a distinct perspective (for example pragmatic, contrarian,\nfirst-principles). Name each subagent for its perspective. Write the synthesis as\na JUDGE: compare the attempts in {{ joined }}, weigh their strengths, and merge\nthe best elements into one final answer, noting significant disagreements.\n",
16610
+ "strategies/types/fanout.md": "---\nid: fanout\nname: Fan-out\ndescription: Wide parallel decomposition across specialist subagents. Higher token use; broad coverage, fastest wall-clock.\ntier: 3\ndefault: true\nknobs:\n parallelism: parallel\n---\nDecompose the goal into INDEPENDENT workstreams that can run in parallel \u2014 one\nspecialist subagent per theme, each with focused work items. Maximize coverage:\ndistinct angles, no overlapping work. Use dependsOn only where a work item truly\nneeds another item's output.\n",
16611
+ "strategies/types/lean.md": `---
16568
16612
  id: lean
16569
16613
  name: Lean
16570
16614
  description: One subagent, a few sequential steps. Lowest token use \u2014 best for focused, well-defined goals.
@@ -16582,7 +16626,7 @@ reserve a stronger model only for a work item that is genuinely hard.
16582
16626
  If a single work item covers the goal, set "synthesis" to exactly "{{ joined }}"
16583
16627
  so the runner returns that output directly without a synthesis call.
16584
16628
  `,
16585
- "strategies/pipeline.md": "---\nid: pipeline\nname: Pipeline\ndescription: Staged chain \u2014 each stage's output feeds the next. Moderate token use; best for transform-and-refine goals.\ntier: 2\nknobs:\n parallelism: staged\n---\nDecompose the goal into sequential STAGES (one subagent per stage), each\ntransforming or refining what the previous stage produced \u2014 for example\nresearch, then draft, then refine, then package. Keep it to 2-4 stages. Within a\nstage, work items may run in parallel, but a stage must only need what earlier\nstages produced. Write each stage's work prompts to state what they consume from\nthe previous stage and what they hand to the next.\n",
16629
+ "strategies/types/pipeline.md": "---\nid: pipeline\nname: Pipeline\ndescription: Staged chain \u2014 each stage's output feeds the next. Moderate token use; best for transform-and-refine goals.\ntier: 2\nknobs:\n parallelism: staged\n---\nDecompose the goal into sequential STAGES (one subagent per stage), each\ntransforming or refining what the previous stage produced \u2014 for example\nresearch, then draft, then refine, then package. Keep it to 2-4 stages. Within a\nstage, work items may run in parallel, but a stage must only need what earlier\nstages produced. Write each stage's work prompts to state what they consume from\nthe previous stage and what they hand to the next.\n",
16586
16630
  "skills/strategy-planning/SKILL.prmd": `---
16587
16631
  id: strategy-planning
16588
16632
  name: strategy-planning
@@ -16631,7 +16675,19 @@ the goal's stakes and vagueness:
16631
16675
  or context gaps with edit_strategy. Then tell the user what you gathered, what
16632
16676
  you decided, and where the spec/plan files live \u2014 the canvas graph plus those
16633
16677
  two files ARE the deliverable.
16634
- `
16678
+ `,
16679
+ "roles/document-writer/PERSONA.md": "Clear, plain-spoken technical writer. Values accuracy and brevity over flourish.\n",
16680
+ "roles/document-writer/POLICY.yaml": "version: 1\nname: role:document-writer\ndefaultPermission: ask\nmutatingDefault: ask\npermissions:\n read_file: allow\n list_files: allow\n search_files: allow\n stat_file: allow\n write_file: ask\n create_file: ask\n",
16681
+ "roles/document-writer/ROLE.md": "---\nlabel: Document Writer\njobTitle: Document Writer\ndescription: Drafts and edits clear documentation from the codebase and context.\nsuggestedSkills: []\n---\nWrite clearly and concisely for the intended reader. Ground every claim in the source. Prefer short sentences and concrete examples.\n",
16682
+ "roles/research-analyst/PERSONA.md": "Rigorous analyst. Sources claims, flags uncertainty, distinguishes fact from inference.\n",
16683
+ "roles/research-analyst/POLICY.yaml": "version: 1\nname: role:research-analyst\ndefaultPermission: ask\nmutatingDefault: deny\npermissions:\n read_file: allow\n list_files: allow\n search_files: allow\n stat_file: allow\n web_search: allow\n recall_memory: allow\n",
16684
+ "roles/research-analyst/ROLE.md": "---\nlabel: Research Analyst\njobTitle: Research Analyst\ndescription: Gathers and synthesizes information; does not modify the workspace.\nsuggestedSkills: []\n---\nGather from the web and the workspace, then synthesize a sourced, structured answer. Separate findings from assumptions. Do not modify files.\n",
16685
+ "roles/senior-software-engineer/PERSONA.md": "Pragmatic senior engineer. Precise, terse, correctness-first. Prefers the smallest change that fully solves the problem.\n",
16686
+ "roles/senior-software-engineer/POLICY.yaml": "version: 1\nname: role:senior-software-engineer\ndefaultPermission: ask\nmutatingDefault: ask\npermissions:\n read_file: allow\n list_files: allow\n search_files: allow\n stat_file: allow\n compile_prompd: allow\n write_file: ask\n create_file: ask\n rename_file: ask\n",
16687
+ "roles/senior-software-engineer/ROLE.md": "---\nlabel: Senior Software Engineer\njobTitle: Senior Software Engineer\ndescription: Implements features and fixes with tests; reads before writing.\nsuggestedSkills: []\n---\nImplement changes test-first. Read the surrounding code and match its conventions. Keep changes minimal and focused; explain non-obvious decisions.\n",
16688
+ "roles/senior-test-engineer/PERSONA.md": "Meticulous, adversarial about correctness. Thinks in failure modes and boundary conditions.\n",
16689
+ "roles/senior-test-engineer/POLICY.yaml": "version: 1\nname: role:senior-test-engineer\ndefaultPermission: ask\nmutatingDefault: deny\npermissions:\n read_file: allow\n list_files: allow\n search_files: allow\n stat_file: allow\n compile_prompd: allow\n",
16690
+ "roles/senior-test-engineer/ROLE.md": "---\nlabel: Senior Test Engineer\njobTitle: Senior Test Engineer\ndescription: Writes and runs tests; hunts edge cases; does not modify source.\nsuggestedSkills: []\n---\nFocus on coverage and edge cases. Write tests that fail before a fix and pass after. Do not modify production source; report gaps you find.\n"
16635
16691
  };
16636
16692
 
16637
16693
  // ../harness/prompts/library/index.ts
@@ -16884,8 +16940,7 @@ async function compileConfigTemplate(path, params) {
16884
16940
  }
16885
16941
  }
16886
16942
  function formatTools(tools) {
16887
- if (!tools || !tools.length) return "";
16888
- return tools.map((t) => `- \`${t.name}\`: ${t.description}`).join("\n");
16943
+ return tools && tools.length ? formatToolLines(tools) : "";
16889
16944
  }
16890
16945
  async function composeAgentSystem(args) {
16891
16946
  const mode = args.mode || "auto";
@@ -16902,21 +16957,37 @@ async function composeAgentSystem(args) {
16902
16957
  ]);
16903
16958
  const tool_guidance = toolGuidanceRaw.replace(/<!--[\s\S]*?-->/g, "").trim();
16904
16959
  const skillsBlock = await enabledSkillInstructions().catch(() => "");
16960
+ const memoryIdx = args.memoryIndex ? await args.memoryIndex().catch(() => "") : "";
16961
+ const memoryBlock = memoryIdx.trim() ? `## Memory
16962
+ Notes you've saved. Call recall_memory to read one in full; save_memory to add one.
16963
+
16964
+ ${memoryIdx.trim()}` : "";
16905
16965
  return compileConfigTemplate(systemPath, {
16906
16966
  persona: persona.trim(),
16907
16967
  mode,
16908
16968
  mode_hint,
16909
16969
  tool_guidance,
16910
- tools: formatTools(args.tools),
16911
- file_context: [(args.contextText || "").trim(), skillsBlock].filter(Boolean).join("\n\n"),
16970
+ tools: args.toolsText ?? formatTools(args.tools),
16971
+ file_context: [(args.contextText || "").trim(), memoryBlock, skillsBlock].filter(Boolean).join("\n\n"),
16912
16972
  ...args.params || {}
16913
16973
  });
16914
16974
  }
16915
16975
 
16916
16976
  // ../tools-node/fileService.ts
16917
16977
  import { promises as fs } from "node:fs";
16918
- import { join, resolve, relative, dirname, sep, basename } from "node:path";
16978
+ import { join, resolve, relative, dirname, sep, basename, isAbsolute } from "node:path";
16919
16979
  var SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", ".cache", ".turbo"]);
16980
+ function escapesRoot(rel, platformSep = sep) {
16981
+ if (rel === "") return false;
16982
+ if (rel === ".." || rel.startsWith(".." + platformSep)) return true;
16983
+ if (rel.split(platformSep)[0] === "..") return true;
16984
+ if (isAbsolute(rel)) return true;
16985
+ if (platformSep === "\\") {
16986
+ if (/^[a-zA-Z]:/.test(rel)) return true;
16987
+ if (rel.startsWith("\\\\")) return true;
16988
+ }
16989
+ return false;
16990
+ }
16920
16991
  var NodeFileService = class {
16921
16992
  kind = "directory";
16922
16993
  label;
@@ -16930,8 +17001,7 @@ var NodeFileService = class {
16930
17001
  }
16931
17002
  /** True if `a` is lexically within the root (the root itself counts). */
16932
17003
  within(a) {
16933
- const rel = relative(this.root, a);
16934
- return rel === "" || rel !== ".." && !rel.startsWith(".." + sep) && rel.split(sep)[0] !== "..";
17004
+ return !escapesRoot(relative(this.root, a));
16935
17005
  }
16936
17006
  /** Absolute path for a folder-relative one, rejecting anything that escapes root
16937
17007
  * LEXICALLY (../, absolute). Symlink escapes are caught by assertReal/realAbs. */
@@ -16950,7 +17020,7 @@ var NodeFileService = class {
16950
17020
  try {
16951
17021
  const real = await fs.realpath(probe);
16952
17022
  const rel = relative(realRoot, real);
16953
- if (rel !== "" && (rel === ".." || rel.startsWith(".." + sep) || rel.split(sep)[0] === "..")) {
17023
+ if (escapesRoot(rel)) {
16954
17024
  throw new Error(`path escapes the workspace root via symlink: ${a}`);
16955
17025
  }
16956
17026
  return;
package/package.json CHANGED
@@ -1,29 +1,22 @@
1
1
  {
2
2
  "name": "@corenel/cli",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "Corenel CLI — runs the harness in node, in-proc, no transport. The headless proof the kernel is host-agnostic. (corenel run/ask/chat/login + start --sidecar to follow.)",
6
6
  "bin": {
7
7
  "corenel": "dist/cli.js"
8
8
  },
9
- "scripts": {
10
- "typecheck": "tsc --noEmit",
11
- "start": "tsx src/cli.ts",
12
- "verify": "tsx src/verify.ts",
13
- "build": "node build.mjs",
14
- "prepublishOnly": "node build.mjs"
15
- },
16
9
  "dependencies": {
17
10
  "openai": "^4.77.0"
18
11
  },
19
12
  "devDependencies": {
20
- "@corenel/harness": "workspace:*",
21
- "@corenel/protocol": "workspace:*",
22
- "@corenel/tools-node": "workspace:*",
23
13
  "esbuild": "^0.21.5",
24
14
  "@types/node": "^24.13.1",
25
15
  "tsx": "^4.19.2",
26
- "typescript": "^5.9.3"
16
+ "typescript": "^5.9.3",
17
+ "@corenel/harness": "0.2.0",
18
+ "@corenel/tools-node": "0.2.0",
19
+ "@corenel/protocol": "0.2.0"
27
20
  },
28
21
  "publishConfig": {
29
22
  "access": "public"
@@ -35,5 +28,11 @@
35
28
  ],
36
29
  "engines": {
37
30
  "node": ">=18"
31
+ },
32
+ "scripts": {
33
+ "typecheck": "tsc --noEmit",
34
+ "start": "tsx src/cli.ts",
35
+ "verify": "tsx src/verify.ts",
36
+ "build": "node build.mjs"
38
37
  }
39
- }
38
+ }