loreli 1.0.0 → 2.0.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 (63) hide show
  1. package/README.md +66 -26
  2. package/package.json +17 -14
  3. package/packages/action/prompts/action.md +172 -0
  4. package/packages/action/src/index.js +33 -5
  5. package/packages/agent/README.md +107 -18
  6. package/packages/agent/src/backends/claude.js +111 -11
  7. package/packages/agent/src/backends/codex.js +78 -5
  8. package/packages/agent/src/backends/cursor.js +104 -27
  9. package/packages/agent/src/backends/index.js +162 -5
  10. package/packages/agent/src/cli.js +80 -3
  11. package/packages/agent/src/discover.js +396 -0
  12. package/packages/agent/src/factory.js +39 -34
  13. package/packages/agent/src/models.js +24 -6
  14. package/packages/classify/README.md +136 -0
  15. package/packages/classify/prompts/blocker.md +12 -0
  16. package/packages/classify/prompts/feedback.md +14 -0
  17. package/packages/classify/prompts/pane-state.md +20 -0
  18. package/packages/classify/src/index.js +81 -0
  19. package/packages/config/README.md +156 -91
  20. package/packages/config/src/defaults.js +32 -21
  21. package/packages/config/src/index.js +33 -2
  22. package/packages/config/src/schema.js +57 -39
  23. package/packages/hub/src/github.js +59 -20
  24. package/packages/identity/README.md +1 -1
  25. package/packages/identity/src/index.js +2 -2
  26. package/packages/knowledge/README.md +86 -106
  27. package/packages/knowledge/src/index.js +56 -225
  28. package/packages/mcp/README.md +51 -7
  29. package/packages/mcp/instructions.md +6 -1
  30. package/packages/mcp/scaffolding/loreli.yml +115 -77
  31. package/packages/mcp/scaffolding/mcp-configs/.codex/config.toml +1 -0
  32. package/packages/mcp/scaffolding/mcp-configs/.cursor/mcp.json +4 -1
  33. package/packages/mcp/scaffolding/mcp-configs/.mcp.json +4 -1
  34. package/packages/mcp/src/index.js +45 -16
  35. package/packages/mcp/src/tools/agent-context.js +44 -0
  36. package/packages/mcp/src/tools/agents.js +34 -13
  37. package/packages/mcp/src/tools/context.js +3 -2
  38. package/packages/mcp/src/tools/github.js +11 -47
  39. package/packages/mcp/src/tools/hitl.js +19 -6
  40. package/packages/mcp/src/tools/index.js +2 -1
  41. package/packages/mcp/src/tools/refactor.js +227 -0
  42. package/packages/mcp/src/tools/repo.js +44 -0
  43. package/packages/mcp/src/tools/start.js +159 -90
  44. package/packages/mcp/src/tools/status.js +5 -2
  45. package/packages/mcp/src/tools/work.js +18 -8
  46. package/packages/orchestrator/src/index.js +345 -79
  47. package/packages/planner/README.md +84 -1
  48. package/packages/planner/prompts/plan-reviewer.md +109 -0
  49. package/packages/planner/prompts/planner.md +191 -0
  50. package/packages/planner/prompts/tiebreaker-reviewer.md +71 -0
  51. package/packages/planner/src/index.js +326 -111
  52. package/packages/review/README.md +2 -2
  53. package/packages/review/prompts/reviewer.md +158 -0
  54. package/packages/review/src/index.js +196 -76
  55. package/packages/risk/README.md +81 -22
  56. package/packages/risk/prompts/risk.md +272 -0
  57. package/packages/risk/src/index.js +44 -33
  58. package/packages/tmux/src/index.js +61 -12
  59. package/packages/workflow/README.md +18 -14
  60. package/packages/workflow/prompts/preamble.md +14 -0
  61. package/packages/workflow/src/index.js +191 -12
  62. package/packages/workspace/README.md +2 -2
  63. package/packages/workspace/src/index.js +69 -18
@@ -15,6 +15,8 @@ Before using the CLI tool commands below, configure an MCP server entry named `l
15
15
  ```bash
16
16
  loreli tools list
17
17
  loreli tools start --repo owner/repo
18
+ # or rely on repo fallback from loreli.yml / LORELI_REPO
19
+ loreli tools start
18
20
  loreli tools team_status
19
21
  loreli tools agents --action check
20
22
  ```
@@ -58,7 +60,7 @@ All tools are visible at all times. No progressive disclosure — every tool is
58
60
 
59
61
  | Tool | Description |
60
62
  |------|-------------|
61
- | `start` | Initialize orchestration for a target GitHub repo. Validates access, discovers/scaffolds templates, creates project board. Idempotent — safe to re-run. |
63
+ | `start` | Initialize orchestration for a target GitHub repo. Accepts `--repo`, or falls back to configured `repo` from `loreli.yml` / `LORELI_REPO`. Validates access, discovers/scaffolds templates, creates project board. Idempotent — safe to re-run. |
62
64
  | `environment` | Report detected environment: tmux, backends, providers, review strategy. |
63
65
 
64
66
  ### Team Management
@@ -67,17 +69,59 @@ All tools are visible at all times. No progressive disclosure — every tool is
67
69
  |------|-------------|
68
70
  | `add_agent` | Add an agent. Params: `provider` (`openai`, `anthropic`, `cursor-openai`, `cursor-anthropic`), `role` (`planner`, `action`, `reviewer`), `model`, `theme`. |
69
71
  | `agents` | Query agent team state. Actions: `list` (show all agents with state, identity, role, and terminal output; optional `lines` param, default 40), `check` (liveness check for one or all agents). |
70
- | `start_planning` | Activate planner agents to analyze the repo and create draft work items. Accepts an `objective` to guide the planner. |
71
- | `start_work` | Begin action/review cycle. Action agents claim issues, do work, create PRs. Review agents are auto-assigned via yin/yang pairing. |
72
- | `team_status` | Dashboard: issues, PRs, agent states with terminal snapshots (last 20 lines), review loops, PRs awaiting human review, and GitHub API rate limits. |
73
- | `escalate` | Signal a concern or feature idea to the planner queue. Creates a draft on the project board. |
72
+ | `start_planning` | Activate planner agents to analyze the repo and create draft work items. Accepts an `objective` to guide the planner. Uses explicit `repo` argument when provided, otherwise falls back to configured `repo`. |
73
+ | `start_work` | Begin action/review cycle. Action agents claim issues, do work, create PRs. Review agents are auto-assigned via yin/yang pairing. Uses explicit `repo` argument when provided, otherwise falls back to configured `repo`. |
74
+ | `team_status` | Dashboard: issues, PRs, agent states with terminal snapshots (last 20 lines), review loops, PRs awaiting human review, and GitHub API rate limits. Uses explicit `repo` argument when provided, otherwise falls back to configured `repo`. |
75
+
76
+ ### Agent Tools (Agent-Only)
77
+
78
+ | Tool | Description |
79
+ |------|-------------|
80
+ | `plan` | Plan lifecycle management. Actions: `create` (new discussion), `revise` (update after feedback), `verdict` (approve/reject as reviewer), `escalate` (flag process or architecture concerns). |
81
+ | `pr` | Pull request lifecycle. Actions: `create` (open PR from branch), `review` (submit adversarial review). |
82
+ | `comment` | Post a comment on the agent's current work item. Set `claim: true` to claim an issue, `abandon: true` to abandon. |
83
+ | `read` | Read any issue, PR, or discussion by number. |
84
+ | `refactor` | Flag a bug, code smell, or tech debt discovered during implementation. Action agents only. Creates a deduplicated Loreli discussion labeled `loreli:refactor` that enters the standard review/promote pipeline. |
85
+ | `context` | Resolve code context. Actions: `blame`, `history`, `search`, `patterns`. |
86
+ | `environment` | Report detected environment: tmux, backends, providers, review strategy. |
87
+
88
+ #### `refactor` Tool
89
+
90
+ Enables [opportunistic refactoring](https://martinfowler.com/bliki/OpportunisticRefactoring.html) — action agents report improvements they discover without leaving their assigned scope.
91
+
92
+ **Parameters:**
93
+
94
+ | Parameter | Type | Required | Description |
95
+ |-----------|------|----------|-------------|
96
+ | `kind` | `string` | yes | Category: `bug`, `refactor`, `smell`, or `debt` |
97
+ | `title` | `string` | yes | Concise improvement title |
98
+ | `description` | `string` | yes | Problem statement with stable identifiers (function names, class names, error messages) |
99
+ | `files` | `string[]` | no | Affected file paths, optionally with line range hints |
100
+ | `impact` | `string` | no | `low`, `medium` (default), or `high` |
101
+
102
+ **File path format:**
103
+
104
+ File entries accept plain paths or paths with GitHub-style line range hints:
105
+
106
+ - `src/hub.js` — plain path
107
+ - `packages/hub/src/github.js#R40-R50` — path with line range hint (lines 40–50)
108
+
109
+ Line ranges are advisory hints; they may drift between commits. Always include stable identifiers (function names, error patterns) in the `description`.
110
+
111
+ **Dedup behavior:**
112
+
113
+ Before creating a discussion, the tool searches existing issues and `loreli:refactor`-labeled discussions by normalized title. If a match is found, it returns an "Already tracked" reference instead of creating a duplicate. Dedup is best-effort — parallel reports can race.
114
+
115
+ **Pipeline integration:**
116
+
117
+ Created discussions enter the existing planner review/revise/promote pipeline. When promoted to issues, the `loreli:refactor` label is carried to the new issue for tracking.
74
118
 
75
119
  ### Human In The Loop (HITL)
76
120
 
77
121
  | Tool | Description |
78
122
  |------|-------------|
79
- | `hitl` | Hand off a PR to human reviewers (Human In The Loop). Requests review, assigns users, posts summary comment tagging reviewers. Kills agents to conserve resources. |
80
- | `watch` | Check an HITL PR for new human feedback. Filters out agent comments. Returns comments posted after the HITL timestamp. |
123
+ | `hitl` | Hand off a PR to human reviewers (Human In The Loop). Requests review, assigns users, posts summary comment tagging reviewers. Kills agents to conserve resources. Uses explicit `repo` argument when provided, otherwise falls back to configured `repo`. |
124
+ | `watch` | Check an HITL PR for new human feedback. Filters out agent comments. Returns comments posted after the HITL timestamp. Uses explicit `repo` argument when provided, otherwise falls back to configured `repo`. |
81
125
 
82
126
  ### Agent Lifecycle
83
127
 
@@ -67,7 +67,10 @@ When you are a spawned agent (planner, action, or reviewer), use the Loreli MCP
67
67
  | **plan** | `create`, `revise`, `verdict`, `escalate` | Planner creates/revises; Reviewer renders verdict; Any role escalates |
68
68
  | **pr** | `create`, `review` | Action creates PRs; Reviewer submits reviews |
69
69
  | **comment** | (none) | Any role posts comments on their current work item |
70
+ | **read** | (none) | Any role reads issues, PRs, or discussions by number |
71
+ | **refactor** | (none) | Action flags bugs, smells, debt for planning |
70
72
  | **context** | `blame`, `history`, `search`, `patterns` | Any role looks up code context, decision history, and feedback patterns |
73
+ | **environment** | (none) | Any role checks available backends |
71
74
 
72
75
  ### Context Resolution
73
76
 
@@ -89,8 +92,9 @@ Tools enforce role boundaries server-side:
89
92
  | `plan/verdict` | reviewer | planner, action |
90
93
  | `pr/create` | action | planner, reviewer |
91
94
  | `pr/review` | reviewer | planner, action |
95
+ | `refactor` | action | planner, reviewer |
92
96
  | `plan/escalate`, `comment` | any | — |
93
- | `context/*` | any | — |
97
+ | `context/*`, `read`, `environment` | any | — |
94
98
 
95
99
  An action agent cannot approve its own PR. A planner cannot approve its own plan. The antagonist (opposing-provider reviewer) is always mandatory and auto-enlisted when missing.
96
100
 
@@ -117,5 +121,6 @@ Also available via CLI: `loreli tools context --action blame --file <path> --lin
117
121
  4. **Respect your role**: Only use actions permitted for your role. Attempting unauthorized actions wastes tokens and time.
118
122
  5. **Honor repository policy files**: If `AGENTS.md` exists in the repository root, treat it as authoritative repository policy input.
119
123
  6. **Follow role prompt quality gates**: Testing and style-quality requirements are enforced in role-specific prompts and review flows; do not bypass them.
124
+ 7. **Flag improvements opportunistically**: When you encounter bugs, code smells, or tech debt during your work, use the `refactor` tool immediately. Do not fix out-of-scope issues yourself — flag them and continue your task. The dedup gate prevents duplicate reports. Use `plan` (action: `escalate`) for process or architecture concerns, not code-level findings.
120
125
 
121
126
  </rules>
@@ -24,6 +24,13 @@ theme: transformers # string or list: transformers | pokemon | marvel |
24
24
  # Change when: You want a global quality/cost baseline shift for all agents.
25
25
  model: balanced # fast | balanced | powerful | exact model string
26
26
 
27
+ # repo
28
+ # What: Optional repository slug fallback for standalone tool contexts before start runs.
29
+ # Impact: Enables tools like `loreli tools context`, `start_work`, and `hitl` to resolve repository scope without session hydration.
30
+ # Signal: CLI tools report "No repository configured" outside agent/start sessions.
31
+ # Change when: You regularly run Loreli tools directly from a shell and want a persistent repo default.
32
+ # repo: owner/repo
33
+
27
34
  # --- Merge gate ---
28
35
  # reviewers
29
36
  # What: GitHub usernames for HITL review requests.
@@ -114,6 +121,14 @@ timeouts:
114
121
  # Change when: Startup failures are missed (increase) or false positives occur (decrease).
115
122
  rapidDeath: 15s
116
123
 
124
+ # timeouts.proxyDiscovery
125
+ # What: HTTP timeout for proxy model discovery calls used by claude/codex.
126
+ # Impact: Lower values fail fast on unhealthy proxies; higher values tolerate slower proxy endpoints.
127
+ # Signal: Discovery frequently times out on healthy but slow networks (increase),
128
+ # or startup blocks too long on unreachable proxy endpoints (decrease).
129
+ # Change when: Proxy-backed environments need slower/faster discovery behavior.
130
+ proxyDiscovery: 5s
131
+
117
132
  # timeouts.nudge
118
133
  # What: Enables/disables tier-1 "you appear stalled" message.
119
134
  # Impact: true may interrupt deep work; false keeps escalation signals without message interruption.
@@ -144,15 +159,6 @@ watch:
144
159
  # Change when: Agents are underutilized (increase) or overloaded (decrease).
145
160
  maxClaims: 3
146
161
 
147
- # --- Review policy ---
148
- review:
149
- # review.skipRiskAssessment
150
- # What: Skips mandatory risk verdict checks in review flow.
151
- # Impact: Faster review path with less explicit risk gating.
152
- # Signal: Teams intentionally bypassing risk gates for speed, or conversely incidents from insufficient risk checks (set false).
153
- # Change when: You intentionally prefer speed over formal risk signoff.
154
- skipRiskAssessment: false
155
-
156
162
  # --- Scaling policy ---
157
163
  scaling:
158
164
  # scaling.maxAgents
@@ -162,35 +168,6 @@ scaling:
162
168
  # Change when: You need more throughput (increase) or tighter resource limits (decrease).
163
169
  maxAgents: 8
164
170
 
165
- maxPerRole:
166
- # scaling.maxPerRole.action
167
- # What: Max concurrent action agents.
168
- # Impact: Controls parallel implementation throughput.
169
- # Signal: Large implementation queue with insufficient coding capacity.
170
- # Change when: Work backlog is implementation-heavy.
171
- action: 3
172
-
173
- # scaling.maxPerRole.reviewer
174
- # What: Max concurrent reviewer agents.
175
- # Impact: Controls review bottleneck relief.
176
- # Signal: PRs are ready but waiting on reviewer assignment/completion.
177
- # Change when: PR queue waits on reviews.
178
- reviewer: 2
179
-
180
- # scaling.maxPerRole.risk
181
- # What: Max concurrent risk agents.
182
- # Impact: Controls parallel risk assessment capacity.
183
- # Signal: Reviews blocked on risk verdicts.
184
- # Change when: Risk checks become the bottleneck.
185
- risk: 3
186
-
187
- # scaling.maxPerRole.planner
188
- # What: Max concurrent planner agents.
189
- # Impact: Limits parallel planning/discussion churn.
190
- # Signal: Planning queue grows (increase) or discussion noise overwhelms maintainers (decrease).
191
- # Change when: You want more/fewer simultaneous planning threads.
192
- planner: 1
193
-
194
171
  # scaling.maxPerTick
195
172
  # What: Spawn budget per reactor tick.
196
173
  # Impact: Higher values ramp up faster but can spike load.
@@ -301,6 +278,36 @@ agents:
301
278
  # anthropic: opus-4.6-thinking
302
279
  # openai: gpt-5.1-codex-max
303
280
 
281
+ # --- Classification ---
282
+ classify:
283
+ # classify.model
284
+ # What: Model tier used for non-interactive classification prompts.
285
+ # Impact: Higher tiers may classify more accurately but cost more and respond slower.
286
+ # Signal: Misclassified pane states/feedback or excessive latency/cost.
287
+ # Change when: You want a different speed/accuracy trade-off for classification tasks.
288
+ model: fast
289
+
290
+ # classify.maxLines
291
+ # What: Max pane lines captured for classifier input.
292
+ # Impact: Higher values provide more context but increase token usage and prompt size.
293
+ # Signal: Classifier misses important terminal context (increase), or prompts are noisy/too large (decrease).
294
+ # Change when: Terminal-state classification needs more or less surrounding context.
295
+ maxLines: 100
296
+
297
+ # classify.timeout
298
+ # What: Timeout for non-interactive classifier calls.
299
+ # Impact: Higher values tolerate cold starts; lower values fail fast on stuck backends.
300
+ # Signal: Frequent classifier timeouts on healthy backends (increase), or long stalls waiting for dead ones (decrease).
301
+ # Change when: Your classifier backends are slower or faster than the default.
302
+ timeout: 30s
303
+
304
+ # classify.maxRetries
305
+ # What: Max consecutive classifier failures before the orchestrator gives up and escalates.
306
+ # Impact: Higher values are more tolerant of transient failures; lower values fail closed sooner.
307
+ # Signal: Temporary classifier hiccups cause unnecessary kills/escalations (increase), or repeated failures waste time (decrease).
308
+ # Change when: You need a stricter or more forgiving classifier failure budget.
309
+ maxRetries: 5
310
+
304
311
  # --- Trace capture ---
305
312
  trace:
306
313
  # trace.enabled
@@ -324,34 +331,6 @@ trace:
324
331
  # Change when: Useful context is being truncated too aggressively.
325
332
  maxOutputChars: 8000
326
333
 
327
- workflows:
328
- planner:
329
- # trace.workflows.planner.enabled / maxOutputChars
330
- # What: Planner-specific trace override.
331
- # Impact: Fine-grained planner trace tuning.
332
- # Signal: Planner traces need different verbosity than global behavior.
333
- # Change when: Planner traces need different verbosity than global default.
334
- enabled: true
335
- maxOutputChars: 4000
336
-
337
- reviewer:
338
- # trace.workflows.reviewer.enabled / maxOutputChars
339
- # What: Reviewer-specific trace override.
340
- # Impact: Fine-grained reviewer trace tuning.
341
- # Signal: Reviewer traces are too sparse for diagnosis or too noisy for signal extraction.
342
- # Change when: Reviewer traces are too noisy or too sparse.
343
- enabled: true
344
- maxOutputChars: 4000
345
-
346
- risk:
347
- # trace.workflows.risk.enabled / maxOutputChars
348
- # What: Risk-specific trace override.
349
- # Impact: Fine-grained risk trace tuning.
350
- # Signal: Risk reasoning context is truncated (increase) or over-captured (decrease).
351
- # Change when: Risk traces need tighter/looser capture bounds.
352
- enabled: true
353
- maxOutputChars: 2000
354
-
355
334
  # --- Proof of life ---
356
335
  proofOfLife:
357
336
  # proofOfLife.timeout
@@ -386,17 +365,69 @@ cleanup:
386
365
  # Change when: You want explicit/manual cleanup control (set false).
387
366
  autoprune: true
388
367
 
389
- # --- Prompt overrides ---
390
- # prompts.{role}
391
- # What: Repo-local prompt file overrides for each role.
392
- # Impact: Changes agent behavior/instructions without code changes.
393
- # Signal: Repeated instruction gaps that can be fixed with persistent repo-specific guidance.
394
- # Change when: You need project-specific guardrails or workflow guidance.
395
- # prompts:
396
- # action: .loreli/action.md
397
- # reviewer: .loreli/review.md
398
- # planner: .loreli/planner.md
399
- # risk: .loreli/risk.md
368
+ # --- Per-workflow configuration ---
369
+ # workflows.{role}
370
+ # What: Per-role settings consolidating model tier, agent cap, prompt override, trace, and skip flags.
371
+ # Impact: Each workflow role can be tuned independently — model quality, parallelism, and observability.
372
+ # Signal: Different roles need different cost/quality trade-offs, scaling limits, or trace verbosity.
373
+ # Change when: You want per-role model tiers, scaling, custom prompts, or trace settings.
374
+ workflows:
375
+ action:
376
+ # workflows.action.model
377
+ # What: Model tier for action agents (implementation work).
378
+ # Impact: Controls quality/cost of code generation.
379
+ # Change when: You want stronger/cheaper implementation agents.
380
+ model: balanced
381
+
382
+ # workflows.action.maxAgents
383
+ # What: Max concurrent action agents.
384
+ # Impact: Controls parallel implementation throughput.
385
+ # Change when: Work backlog is implementation-heavy.
386
+ maxAgents: 3
387
+
388
+ # workflows.action.prompt
389
+ # What: Repo-local prompt file override for action agents.
390
+ # Impact: Changes agent instructions without code changes.
391
+ # Change when: You need project-specific coding guardrails.
392
+ # prompt: .loreli/action.md
393
+
394
+ reviewer:
395
+ # workflows.reviewer.model
396
+ # What: Model tier for reviewer agents.
397
+ # Change when: You want stronger/cheaper review agents.
398
+ model: balanced
399
+ maxAgents: 2
400
+ # prompt: .loreli/reviewer.md
401
+ trace:
402
+ enabled: true
403
+ maxOutputChars: 4000
404
+
405
+ risk:
406
+ # workflows.risk.model
407
+ # What: Model tier for risk assessment agents.
408
+ # Change when: Risk evaluation needs deeper reasoning (powerful) or is fine with fast triage (fast).
409
+ model: fast
410
+ maxAgents: 3
411
+
412
+ # workflows.risk.skip
413
+ # What: Skips mandatory risk verdict checks in review flow.
414
+ # Impact: Faster review path with less explicit risk gating.
415
+ # Change when: You intentionally prefer speed over formal risk signoff.
416
+ skip: false
417
+ trace:
418
+ enabled: true
419
+ maxOutputChars: 2000
420
+
421
+ planner:
422
+ # workflows.planner.model
423
+ # What: Model tier for planner agents.
424
+ # Change when: Planning quality needs deeper reasoning.
425
+ model: powerful
426
+ maxAgents: 1
427
+ # prompt: .loreli/planner.md
428
+ trace:
429
+ enabled: true
430
+ maxOutputChars: 4000
400
431
 
401
432
  # --- Feedback and knowledge capture ---
402
433
  feedback:
@@ -427,6 +458,13 @@ feedback:
427
458
  - performance
428
459
  - security
429
460
 
461
+ # feedback.hitl
462
+ # What: Controls Human In The Loop escalation for feedback-driven PRs at merge time.
463
+ # Impact: true gates all feedback PRs on human approval; false allows full automation; array gates only listed categories.
464
+ # Signal: Feedback-driven changes landing without review (set true or list categories), or unnecessary merge friction (set false).
465
+ # Change when: You want human oversight on specific feedback categories (e.g. architecture, security) while letting others auto-merge.
466
+ hitl: false
467
+
430
468
  # --- Tmux ---
431
469
  tmux:
432
470
  # tmux.session
@@ -1,3 +1,4 @@
1
1
  [mcp_servers.loreli]
2
2
  command = "npx"
3
3
  args = ["loreli", "mcp"]
4
+ env_vars = ["GITHUB_TOKEN"]
@@ -5,7 +5,10 @@
5
5
  "args": [
6
6
  "loreli",
7
7
  "mcp"
8
- ]
8
+ ],
9
+ "env": {
10
+ "GITHUB_TOKEN": "${env:GITHUB_TOKEN}"
11
+ }
9
12
  }
10
13
  }
11
14
  }
@@ -5,7 +5,10 @@
5
5
  "args": [
6
6
  "loreli",
7
7
  "mcp"
8
- ]
8
+ ],
9
+ "env": {
10
+ "GITHUB_TOKEN": "${GITHUB_TOKEN}"
11
+ }
9
12
  }
10
13
  }
11
14
  }
@@ -20,6 +20,7 @@ import { PlannerWorkflow } from 'loreli/planner';
20
20
  import { ActionWorkflow } from 'loreli/action';
21
21
  import { RiskWorkflow } from 'loreli/risk';
22
22
  import { ReviewWorkflow } from 'loreli/review';
23
+ import { Config } from 'loreli/config';
23
24
  import { logger } from 'loreli/log';
24
25
 
25
26
  const log = logger('mcp');
@@ -41,12 +42,18 @@ const EMPTY_SCHEMA = { type: 'object', properties: {} };
41
42
  * start_work. Exposing those tools leads to confused agents calling
42
43
  * them, which destroys workspaces and starts duplicate reactor loops.
43
44
  *
44
- * `start` is included so agents that call it receive a no-op response
45
- * ("already configured") instead of an error.
45
+ * `start` was previously included so agents would receive a no-op
46
+ * response ("already configured") instead of an error. However, when
47
+ * agent hydration fails or env vars are missing, the no-op guard
48
+ * (`ctx.sessionId && ctx.agentName`) doesn't fire and the call falls
49
+ * through to the real implementation — which reaps the tmux session,
50
+ * killing every running agent. Observed in E2E: a Cursor Agent called
51
+ * `start` on its own repo, destroying the session it was living in.
52
+ * Safer to block it entirely and return a clean "not available" error.
46
53
  *
47
54
  * @type {Set<string>}
48
55
  */
49
- const AGENT_TOOLS = new Set(['plan', 'pr', 'comment', 'read', 'environment', 'context', 'start']);
56
+ const AGENT_TOOLS = new Set(['plan', 'pr', 'comment', 'read', 'environment', 'context', 'refactor']);
50
57
 
51
58
  /**
52
59
  * Loreli MCP server for agentic team orchestration.
@@ -182,7 +189,7 @@ export class Loreli {
182
189
  if (ctx.agentName && !AGENT_TOOLS.has(name)) {
183
190
  log.warn(`agent ${ctx.agentName} blocked from calling host tool: ${name}`);
184
191
  return {
185
- content: [{ type: 'text', text: `Tool "${name}" is not available to agents. Use plan, pr, comment, or read.` }],
192
+ content: [{ type: 'text', text: `Tool "${name}" is not available to agents. Use plan, pr, comment, read, refactor, environment, or context.` }],
186
193
  isError: true
187
194
  };
188
195
  }
@@ -283,11 +290,16 @@ export class Loreli {
283
290
  */
284
291
  async _prepare() {
285
292
  const home = this.config.home ?? process.env.LORELI_HOME ?? join(homedir(), '.loreli');
293
+ const config = new Config();
294
+ config.loadLocal('loreli.yml');
295
+ config.merge(this.config);
296
+
286
297
  const identityRegistry = new Registry();
287
298
  const backendRegistry = new BackendRegistry();
288
299
  const storage = new Storage({ home });
289
300
 
290
301
  const orchestrator = new Orchestrator({ hub: null, identityRegistry, backendRegistry, storage });
302
+ orchestrator.cfg = config;
291
303
 
292
304
  this.ctx = {
293
305
  hub: null,
@@ -302,8 +314,8 @@ export class Loreli {
302
314
  clientIdentity: null,
303
315
  clientName: null,
304
316
  clientVersion: null,
305
- config: null,
306
- repo: null,
317
+ config,
318
+ repo: config.get('repo') ?? null,
307
319
  categoryId: null,
308
320
  sessionId: null,
309
321
  server: this
@@ -354,7 +366,7 @@ export class Loreli {
354
366
 
355
367
  const data = await storage.load(sessionId, agentName);
356
368
  if (data) {
357
- this.ctx.repo = data.repo ?? process.env.LORELI_REPO ?? null;
369
+ this.ctx.repo = data.repo ?? process.env.LORELI_REPO ?? this.ctx.repo ?? null;
358
370
  if (data.identity) {
359
371
  // Identity.toJSON() stores `name` as full name and `character`
360
372
  // as the raw character. The constructor expects `name` = character.
@@ -371,7 +383,7 @@ export class Loreli {
371
383
  log.info(`agent context hydrated: ${agentName} (session: ${sessionId}, repo: ${this.ctx.repo})`);
372
384
  } else {
373
385
  // Fallback to env var when storage has no data yet (first startup)
374
- this.ctx.repo = process.env.LORELI_REPO ?? null;
386
+ this.ctx.repo = process.env.LORELI_REPO ?? this.ctx.repo ?? null;
375
387
  log.warn(`agent session data not found for ${agentName} in ${sessionId}, using env fallback`);
376
388
  }
377
389
 
@@ -397,9 +409,9 @@ export class Loreli {
397
409
  async _hydrateHub() {
398
410
  let token = process.env.GITHUB_TOKEN;
399
411
 
400
- // cursor-agent CLI does not support ${env:NAME} interpolation or
401
- // reliable envFile loading. Fall back to .git/loreli.env which
402
- // create() writes inside .git/ (inherently unstageable by git).
412
+ // Keep a file-based fallback for startup paths where the host did
413
+ // not provide GITHUB_TOKEN to this process. create() writes
414
+ // .git/loreli.env inside .git/ (inherently unstageable by git).
403
415
  if (!token) {
404
416
  try {
405
417
  const { readFileSync } = await import('node:fs');
@@ -434,9 +446,10 @@ export class Loreli {
434
446
  // Load config from loreli.yml when repo is known
435
447
  if (this.ctx.repo && this.ctx.hub) {
436
448
  try {
437
- const { Config } = await import('loreli/config');
438
449
  const config = new Config();
450
+ config.merge(this.config);
439
451
  await config.load(this.ctx.hub, this.ctx.repo);
452
+ config.merge(this.config);
440
453
  this.ctx.config = config;
441
454
  this.ctx.orchestrator.cfg = config;
442
455
  log.info(`agent config loaded from ${this.ctx.repo}/loreli.yml`);
@@ -523,12 +536,19 @@ export class Loreli {
523
536
  // stub would treat every pane in the session as orphaned and kill
524
537
  // all sibling agents. Only the host process owns the real registry.
525
538
  const self = this;
526
- async function gracefulExit() {
539
+
540
+ /**
541
+ * @param {'SIGINT'|'SIGTERM'|'transport-close'} reason - What triggered shutdown.
542
+ */
543
+ async function gracefulExit(reason) {
527
544
  if (self.ctx?.agentName) {
545
+ log.info(`agent ${self.ctx.agentName} MCP server exiting (${reason})`);
528
546
  process.exit(0);
529
547
  return;
530
548
  }
531
549
 
550
+ log.info(`host MCP server exiting (${reason})`);
551
+
532
552
  self.ctx?.orchestrator?.unwatch?.();
533
553
  self.ctx?.orchestrator?.stopMonitor?.();
534
554
 
@@ -553,16 +573,25 @@ export class Loreli {
553
573
  process.exit(0);
554
574
  }
555
575
 
556
- process.on('SIGINT', gracefulExit);
557
- process.on('SIGTERM', gracefulExit);
576
+ process.on('SIGINT', function onSigint() { gracefulExit('SIGINT'); });
577
+ process.on('SIGTERM', function onSigterm() { gracefulExit('SIGTERM'); });
558
578
 
559
579
  log.info('loreli MCP server starting');
580
+
581
+ transport.onerror = function onTransportError(err) {
582
+ log.error(`MCP transport error: ${err?.message ?? err}`);
583
+ };
584
+
560
585
  await this.server.connect(transport);
561
586
 
587
+ this.server.onerror = function onServerError(err) {
588
+ log.error(`MCP server error: ${err?.message ?? err}`);
589
+ };
590
+
562
591
  // Transport disconnect (client closed pipe, host exited) bypasses
563
592
  // process signals entirely. Wire the SDK's onclose so cleanup runs
564
593
  // regardless of how the connection ends.
565
- this.server.onclose = gracefulExit;
594
+ this.server.onclose = function onTransportClose() { gracefulExit('transport-close'); };
566
595
 
567
596
  log.info('loreli MCP server connected');
568
597
 
@@ -0,0 +1,44 @@
1
+ import { Identity } from 'loreli/identity';
2
+
3
+ /**
4
+ * Resolve agent context from session storage.
5
+ *
6
+ * Reads the agent's persisted session data using the sessionId and
7
+ * agentName from ctx (hydrated at MCP server startup from env vars).
8
+ * Reconstructs a live Identity instance so hub stamping and label
9
+ * methods work correctly. Throws when context is not available — this
10
+ * means the tool was called from a non-agent MCP client.
11
+ *
12
+ * @param {object} ctx - Execution context with sessionId, agentName, storage.
13
+ * @returns {Promise<{repo: string, identity: Identity, role: string, task: object|null, issue: number|null, paneId: string|null}>}
14
+ * @throws {Error} When agent context is not available.
15
+ */
16
+ export async function context(ctx) {
17
+ if (!ctx.sessionId || !ctx.agentName) {
18
+ throw new Error('Agent context not available. This tool can only be called by spawned agents.');
19
+ }
20
+
21
+ const data = await ctx.storage.load(ctx.sessionId, ctx.agentName);
22
+ if (!data) {
23
+ throw new Error(`Session data not found for agent "${ctx.agentName}" in session "${ctx.sessionId}".`);
24
+ }
25
+
26
+ const raw = data.identity;
27
+ const identity = new Identity({
28
+ name: raw.character ?? raw.name,
29
+ instance: raw.instance ?? 0,
30
+ faction: raw.faction,
31
+ provider: raw.provider,
32
+ model: raw.model,
33
+ theme: raw.theme
34
+ });
35
+
36
+ return {
37
+ repo: data.repo,
38
+ identity,
39
+ role: data.role,
40
+ task: data.task ?? null,
41
+ issue: data.claimedIssue ?? data.task?.issue ?? null,
42
+ paneId: data.paneId ?? null
43
+ };
44
+ }