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.
- package/README.md +66 -26
- package/package.json +17 -14
- package/packages/action/prompts/action.md +172 -0
- package/packages/action/src/index.js +33 -5
- package/packages/agent/README.md +107 -18
- package/packages/agent/src/backends/claude.js +111 -11
- package/packages/agent/src/backends/codex.js +78 -5
- package/packages/agent/src/backends/cursor.js +104 -27
- package/packages/agent/src/backends/index.js +162 -5
- package/packages/agent/src/cli.js +80 -3
- package/packages/agent/src/discover.js +396 -0
- package/packages/agent/src/factory.js +39 -34
- package/packages/agent/src/models.js +24 -6
- package/packages/classify/README.md +136 -0
- package/packages/classify/prompts/blocker.md +12 -0
- package/packages/classify/prompts/feedback.md +14 -0
- package/packages/classify/prompts/pane-state.md +20 -0
- package/packages/classify/src/index.js +81 -0
- package/packages/config/README.md +156 -91
- package/packages/config/src/defaults.js +32 -21
- package/packages/config/src/index.js +33 -2
- package/packages/config/src/schema.js +57 -39
- package/packages/hub/src/github.js +59 -20
- package/packages/identity/README.md +1 -1
- package/packages/identity/src/index.js +2 -2
- package/packages/knowledge/README.md +86 -106
- package/packages/knowledge/src/index.js +56 -225
- package/packages/mcp/README.md +51 -7
- package/packages/mcp/instructions.md +6 -1
- package/packages/mcp/scaffolding/loreli.yml +115 -77
- package/packages/mcp/scaffolding/mcp-configs/.codex/config.toml +1 -0
- package/packages/mcp/scaffolding/mcp-configs/.cursor/mcp.json +4 -1
- package/packages/mcp/scaffolding/mcp-configs/.mcp.json +4 -1
- package/packages/mcp/src/index.js +45 -16
- package/packages/mcp/src/tools/agent-context.js +44 -0
- package/packages/mcp/src/tools/agents.js +34 -13
- package/packages/mcp/src/tools/context.js +3 -2
- package/packages/mcp/src/tools/github.js +11 -47
- package/packages/mcp/src/tools/hitl.js +19 -6
- package/packages/mcp/src/tools/index.js +2 -1
- package/packages/mcp/src/tools/refactor.js +227 -0
- package/packages/mcp/src/tools/repo.js +44 -0
- package/packages/mcp/src/tools/start.js +159 -90
- package/packages/mcp/src/tools/status.js +5 -2
- package/packages/mcp/src/tools/work.js +18 -8
- package/packages/orchestrator/src/index.js +345 -79
- package/packages/planner/README.md +84 -1
- package/packages/planner/prompts/plan-reviewer.md +109 -0
- package/packages/planner/prompts/planner.md +191 -0
- package/packages/planner/prompts/tiebreaker-reviewer.md +71 -0
- package/packages/planner/src/index.js +326 -111
- package/packages/review/README.md +2 -2
- package/packages/review/prompts/reviewer.md +158 -0
- package/packages/review/src/index.js +196 -76
- package/packages/risk/README.md +81 -22
- package/packages/risk/prompts/risk.md +272 -0
- package/packages/risk/src/index.js +44 -33
- package/packages/tmux/src/index.js +61 -12
- package/packages/workflow/README.md +18 -14
- package/packages/workflow/prompts/preamble.md +14 -0
- package/packages/workflow/src/index.js +191 -12
- package/packages/workspace/README.md +2 -2
- package/packages/workspace/src/index.js +69 -18
|
@@ -9,6 +9,7 @@ import { ENTRY, CODEX_TOML, prune as pruneWorkspaces } from 'loreli/workspace';
|
|
|
9
9
|
import { Tmux } from 'loreli/tmux';
|
|
10
10
|
import { responder } from 'loreli/workflow';
|
|
11
11
|
import { capability } from 'loreli/identity';
|
|
12
|
+
import { select } from './repo.js';
|
|
12
13
|
|
|
13
14
|
const log = logger('start');
|
|
14
15
|
|
|
@@ -53,8 +54,7 @@ export default {
|
|
|
53
54
|
type: 'array', items: { type: 'string' },
|
|
54
55
|
description: 'GitHub usernames for Human In The Loop. Empty = auto-merge.'
|
|
55
56
|
}
|
|
56
|
-
}
|
|
57
|
-
required: ['repo']
|
|
57
|
+
}
|
|
58
58
|
},
|
|
59
59
|
/**
|
|
60
60
|
* @param {object} args - Tool arguments.
|
|
@@ -62,9 +62,14 @@ export default {
|
|
|
62
62
|
* @returns {Promise<object>} Start result with environment info and templates.
|
|
63
63
|
*/
|
|
64
64
|
async exec(args, ctx) {
|
|
65
|
-
check.repo(args.repo);
|
|
66
65
|
if (args.theme) check.theme(args.theme);
|
|
67
|
-
const
|
|
66
|
+
const repo = select(args.repo, ctx);
|
|
67
|
+
if (!repo) {
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: 'text', text: 'No repository configured. Pass --repo, set LORELI_REPO, or set repo in loreli.yml.' }],
|
|
70
|
+
isError: true
|
|
71
|
+
};
|
|
72
|
+
}
|
|
68
73
|
|
|
69
74
|
// Agent MCP servers (spawned from .mcp.json by Claude Code, Codex, etc.)
|
|
70
75
|
// are hydrated from env vars during _hydrate(). If a session ID is
|
|
@@ -92,21 +97,9 @@ export default {
|
|
|
92
97
|
ctx.hub = createHub({ config: ctx.config });
|
|
93
98
|
}
|
|
94
99
|
|
|
95
|
-
// 1. Discover environment
|
|
100
|
+
// 1. Discover environment + existing files in parallel
|
|
96
101
|
const backendRegistry = ctx.backendRegistry;
|
|
97
|
-
await backendRegistry.discover();
|
|
98
|
-
const backends = backendRegistry.available();
|
|
99
|
-
const providers = backendRegistry.providers();
|
|
100
|
-
const sideInfo = capability(providers);
|
|
101
102
|
|
|
102
|
-
// Determine review strategy
|
|
103
|
-
let strategy = 'error';
|
|
104
|
-
if (sideInfo.mode === 'dual') strategy = 'yin-yang';
|
|
105
|
-
else if (sideInfo.mode === 'single') strategy = 'fresh-instance';
|
|
106
|
-
|
|
107
|
-
log.info(`backends: ${backends.map(function name(b) { return b.name; }).join(', ') || 'none'}, strategy: ${strategy}`);
|
|
108
|
-
|
|
109
|
-
// 2. Discover existing files (inlined from hub.discover)
|
|
110
103
|
const templates = { pr: null, issue: null, codeowners: null, contributing: null, config: null };
|
|
111
104
|
const checks = [
|
|
112
105
|
['.github/pull_request_template.md', 'pr'],
|
|
@@ -118,13 +111,27 @@ export default {
|
|
|
118
111
|
['.agents/skills/loreli-context/SKILL.md', 'contextSkill']
|
|
119
112
|
];
|
|
120
113
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
114
|
+
const [/* discover result */, ...fileResults] = await Promise.all([
|
|
115
|
+
backendRegistry.discover(),
|
|
116
|
+
...checks.map(function probe([path]) {
|
|
117
|
+
return ctx.hub.read(repo, path).then(function found() { return path; }, function miss() { return null; });
|
|
118
|
+
})
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
for (let i = 0; i < checks.length; i += 1) {
|
|
122
|
+
if (fileResults[i]) templates[checks[i][1]] = fileResults[i];
|
|
126
123
|
}
|
|
127
124
|
|
|
125
|
+
const backends = backendRegistry.available();
|
|
126
|
+
const providers = backendRegistry.providers();
|
|
127
|
+
const sideInfo = capability(providers);
|
|
128
|
+
|
|
129
|
+
let strategy = 'error';
|
|
130
|
+
if (sideInfo.mode === 'dual') strategy = 'yin-yang';
|
|
131
|
+
else if (sideInfo.mode === 'single') strategy = 'fresh-instance';
|
|
132
|
+
|
|
133
|
+
log.info(`backends: ${backends.map(function name(b) { return b.name; }).join(', ') || 'none'}, strategy: ${strategy}`);
|
|
134
|
+
|
|
128
135
|
// 3. Scaffold any missing required files
|
|
129
136
|
const scaffolded = [];
|
|
130
137
|
const toScaffold = [];
|
|
@@ -158,27 +165,15 @@ export default {
|
|
|
158
165
|
}
|
|
159
166
|
}
|
|
160
167
|
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
// Handles both cases: no package.json, and existing package.json
|
|
164
|
-
// that doesn't list loreli yet. Reads the existing file (if any),
|
|
165
|
-
// merges the devDependency, and writes back.
|
|
166
|
-
await ensureDependency(ctx.hub, repo, scaffolded);
|
|
167
|
-
|
|
168
|
-
// Ensure MCP configs exist with the loreli server entry for each
|
|
169
|
-
// supported agent CLI (Claude, Cursor, Codex). Config paths come
|
|
170
|
-
// from the backend registry so adding a backend doesn't require
|
|
171
|
-
// touching this tool.
|
|
172
|
-
await ensureMcpConfigs(ctx.hub, repo, scaffolded, backendRegistry.configPaths());
|
|
173
|
-
|
|
174
|
-
// Ensure .gitignore contains Loreli artifact entries so agent-
|
|
175
|
-
// generated files (.loreli/, .claude/, .cursor/hooks.json) never
|
|
176
|
-
// leak into PRs. Creates the file if absent, appends if present.
|
|
177
|
-
await ensureGitignore(ctx.hub, repo, scaffolded);
|
|
178
|
-
|
|
179
|
-
// 4. Load config from loreli.yml (guaranteed to exist after scaffolding)
|
|
168
|
+
// Run ensure* operations and config load in parallel — each
|
|
169
|
+
// targets different files so there are no ordering dependencies.
|
|
180
170
|
const config = new Config();
|
|
181
|
-
await
|
|
171
|
+
await Promise.all([
|
|
172
|
+
ensureDependency(ctx.hub, repo, scaffolded),
|
|
173
|
+
ensureMcpConfigs(ctx.hub, repo, scaffolded, backendRegistry.configPaths()),
|
|
174
|
+
ensureGitignore(ctx.hub, repo, scaffolded),
|
|
175
|
+
config.load(ctx.hub, repo)
|
|
176
|
+
]);
|
|
182
177
|
|
|
183
178
|
// 5. Merge start param overrides
|
|
184
179
|
const overrides = {};
|
|
@@ -188,11 +183,12 @@ export default {
|
|
|
188
183
|
|
|
189
184
|
const theme = config.get('theme');
|
|
190
185
|
const mergeBase = config.get('merge.base') ?? 'main';
|
|
191
|
-
const baseProvision = await ensureMergeBase(ctx.hub, repo, mergeBase);
|
|
192
186
|
|
|
193
|
-
// 6. Ensure
|
|
187
|
+
// 6. Ensure merge base and labels in parallel — both depend on
|
|
188
|
+
// config but are independent of each other.
|
|
194
189
|
let ensuredLabels = [];
|
|
195
|
-
|
|
190
|
+
const labelPromise = (async function ensureLabels() {
|
|
191
|
+
if (config.get('labels.track') === false) return [];
|
|
196
192
|
const labelNames = [
|
|
197
193
|
'loreli',
|
|
198
194
|
'loreli:planner', 'loreli:action', 'loreli:reviewer',
|
|
@@ -203,13 +199,18 @@ export default {
|
|
|
203
199
|
labelNames.push(`loreli:${backend.provider}`);
|
|
204
200
|
}
|
|
205
201
|
}
|
|
206
|
-
// Include user-configured extra labels
|
|
207
202
|
const extra = config.get('labels.extra') ?? [];
|
|
208
203
|
for (const name of extra) labelNames.push(name);
|
|
209
204
|
|
|
210
205
|
const unique = [...new Set(labelNames)];
|
|
211
|
-
|
|
212
|
-
}
|
|
206
|
+
return ctx.hub.ensure(repo, definitions(unique));
|
|
207
|
+
})();
|
|
208
|
+
|
|
209
|
+
const [baseProvision, labels] = await Promise.all([
|
|
210
|
+
ensureMergeBase(ctx.hub, repo, mergeBase),
|
|
211
|
+
labelPromise
|
|
212
|
+
]);
|
|
213
|
+
ensuredLabels = labels;
|
|
213
214
|
|
|
214
215
|
// 7. Initialize session storage
|
|
215
216
|
const sessionId = `${repo.replace('/', '-')}-${Date.now()}`;
|
|
@@ -314,56 +315,34 @@ export default {
|
|
|
314
315
|
}
|
|
315
316
|
|
|
316
317
|
// Knowledge reactor — detect recurring feedback patterns and
|
|
317
|
-
//
|
|
318
|
+
// dispatch planning via planner when threshold is met.
|
|
318
319
|
if (ctx.orchestrator && ctx.hub) {
|
|
319
320
|
ctx.orchestrator.register('knowledge', async function knowledge(repo) {
|
|
320
321
|
const cfg = ctx.orchestrator.cfg;
|
|
321
|
-
|
|
322
|
-
if (!enabled) return;
|
|
322
|
+
if (!(cfg?.get?.('feedback.enabled') ?? true)) return;
|
|
323
323
|
|
|
324
324
|
const threshold = cfg?.get?.('feedback.threshold') ?? 5;
|
|
325
|
-
const
|
|
326
|
-
const { patterns, propose } = await import('loreli/knowledge');
|
|
327
|
-
const found = await patterns(ctx.hub, repo, { threshold, category: categories?.[0] });
|
|
325
|
+
const { patterns, objective } = await import('loreli/knowledge');
|
|
328
326
|
|
|
329
|
-
|
|
330
|
-
const existing = await ctx.hub.searchIssues(repo, `Promotion: ${pattern.category}`);
|
|
331
|
-
const duplicate = existing.some(function match(i) { return i.title.includes(pattern.fingerprint); });
|
|
332
|
-
if (duplicate) continue;
|
|
327
|
+
const found = await patterns(ctx.hub, repo, { threshold });
|
|
333
328
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
329
|
+
const cat = await ctx.hub.category(repo, 'Loreli');
|
|
330
|
+
const discussions = await ctx.hub.discussions(repo, cat.id);
|
|
331
|
+
const openTitles = new Set(
|
|
332
|
+
discussions.filter(function open(d) { return !d.closed; })
|
|
333
|
+
.map(function title(d) { return d.title; })
|
|
334
|
+
);
|
|
338
335
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
ctx.orchestrator.register('promotion-apply', async function promotionApply(repo) {
|
|
343
|
-
const cfg = ctx.orchestrator.cfg;
|
|
344
|
-
if (!(cfg?.get?.('feedback.enabled') ?? true)) return;
|
|
336
|
+
for (const pattern of found) {
|
|
337
|
+
const title = `${pattern.category} feedback pattern`;
|
|
338
|
+
if (openTitles.has(title) || ctx._feedbackDispatched?.has(pattern.category)) continue;
|
|
345
339
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
try {
|
|
353
|
-
const full = await ctx.hub.read(repo, disc.number);
|
|
354
|
-
const data = parse(full.body, 'promotion');
|
|
355
|
-
if (!data || data.status === 'applied') continue;
|
|
356
|
-
|
|
357
|
-
const approved = /- \[x\]\s*Approve/i.test(full.body);
|
|
358
|
-
const rejected = /- \[x\]\s*Reject/i.test(full.body);
|
|
359
|
-
if (!approved || rejected) continue;
|
|
360
|
-
|
|
361
|
-
await apply(ctx.hub, repo, {
|
|
362
|
-
summary: `${data.category} standard`,
|
|
363
|
-
discussion: disc.number,
|
|
364
|
-
body: full.body
|
|
365
|
-
});
|
|
366
|
-
} catch { /* non-fatal */ }
|
|
340
|
+
await ctx.planner.plan(repo, objective(pattern), {
|
|
341
|
+
feedbackCategory: pattern.category
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
ctx._feedbackDispatched ??= new Set();
|
|
345
|
+
ctx._feedbackDispatched.add(pattern.category);
|
|
367
346
|
}
|
|
368
347
|
});
|
|
369
348
|
}
|
|
@@ -605,7 +584,7 @@ async function pause(ms, wait) {
|
|
|
605
584
|
});
|
|
606
585
|
}
|
|
607
586
|
|
|
608
|
-
export { ensureMergeBase };
|
|
587
|
+
export { ensureMergeBase, normalizeCodexEnvVars };
|
|
609
588
|
|
|
610
589
|
/**
|
|
611
590
|
* Ensure `loreli` exists in the target repo's devDependencies.
|
|
@@ -623,8 +602,85 @@ export { ensureMergeBase };
|
|
|
623
602
|
*/
|
|
624
603
|
// MCP config constants (ENTRY, CODEX_TOML) imported from @loreli/workspace
|
|
625
604
|
// LORELI_TOML needs a leading newline for appending to existing TOML files
|
|
605
|
+
const CODEX_ENV_VARS = 'env_vars = ["GITHUB_TOKEN"]';
|
|
626
606
|
const LORELI_TOML = `\n${CODEX_TOML}`;
|
|
627
607
|
|
|
608
|
+
/**
|
|
609
|
+
* Find the next TOML table header index at or after `start`.
|
|
610
|
+
*
|
|
611
|
+
* @param {string[]} lines - TOML content split by line.
|
|
612
|
+
* @param {number} start - Start index.
|
|
613
|
+
* @returns {number} Index of next table header or lines.length.
|
|
614
|
+
*/
|
|
615
|
+
function nextTomlTable(lines, start) {
|
|
616
|
+
for (let index = start; index < lines.length; index += 1) {
|
|
617
|
+
if (/^\[[^\]]+\]\s*$/.test(lines[index].trim())) return index;
|
|
618
|
+
}
|
|
619
|
+
return lines.length;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Find a TOML table header line index.
|
|
624
|
+
*
|
|
625
|
+
* @param {string[]} lines - TOML content split by line.
|
|
626
|
+
* @param {number} start - Start index.
|
|
627
|
+
* @param {string} header - Exact table header text.
|
|
628
|
+
* @returns {number} Header index or -1 when missing.
|
|
629
|
+
*/
|
|
630
|
+
function findTomlHeader(lines, start, header) {
|
|
631
|
+
for (let index = start; index < lines.length; index += 1) {
|
|
632
|
+
if (lines[index].trim() === header) return index;
|
|
633
|
+
}
|
|
634
|
+
return -1;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Normalize `.codex/config.toml` loreli token forwarding.
|
|
639
|
+
*
|
|
640
|
+
* Ensures `env_vars = ["GITHUB_TOKEN"]` is defined in the
|
|
641
|
+
* `[mcp_servers.loreli]` table and not inside the nested
|
|
642
|
+
* `[mcp_servers.loreli.env]` table.
|
|
643
|
+
*
|
|
644
|
+
* @param {string} content - Existing TOML content.
|
|
645
|
+
* @returns {string|null} Updated content when changed, else null.
|
|
646
|
+
*/
|
|
647
|
+
function normalizeCodexEnvVars(content) {
|
|
648
|
+
if (/^\s*mcp_servers\.loreli\.env_vars\s*=.*$/m.test(content)) return null;
|
|
649
|
+
|
|
650
|
+
const lines = content.split('\n');
|
|
651
|
+
const loreli = findTomlHeader(lines, 0, '[mcp_servers.loreli]');
|
|
652
|
+
if (loreli === -1) return null;
|
|
653
|
+
|
|
654
|
+
let changed = false;
|
|
655
|
+
let env = findTomlHeader(lines, loreli + 1, '[mcp_servers.loreli.env]');
|
|
656
|
+
|
|
657
|
+
if (env !== -1) {
|
|
658
|
+
const envEnd = nextTomlTable(lines, env + 1);
|
|
659
|
+
const envLines = lines.slice(env + 1, envEnd);
|
|
660
|
+
const kept = envLines.filter(function keepLine(line) {
|
|
661
|
+
return !/^\s*env_vars\s*=/.test(line);
|
|
662
|
+
});
|
|
663
|
+
if (kept.length !== envLines.length) {
|
|
664
|
+
lines.splice(env + 1, envEnd - env - 1, ...kept);
|
|
665
|
+
changed = true;
|
|
666
|
+
env = findTomlHeader(lines, loreli + 1, '[mcp_servers.loreli.env]');
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const loreliEnd = env === -1 ? nextTomlTable(lines, loreli + 1) : env;
|
|
671
|
+
const hasEnvVars = lines.slice(loreli + 1, loreliEnd).some(function hasLine(line) {
|
|
672
|
+
return /^\s*env_vars\s*=/.test(line);
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
if (!hasEnvVars) {
|
|
676
|
+
lines.splice(loreliEnd, 0, CODEX_ENV_VARS);
|
|
677
|
+
changed = true;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (!changed) return null;
|
|
681
|
+
return lines.join('\n');
|
|
682
|
+
}
|
|
683
|
+
|
|
628
684
|
/**
|
|
629
685
|
* Ensure the loreli MCP server is configured in all supported agent CLI
|
|
630
686
|
* config files (.mcp.json, .cursor/mcp.json, .codex/config.toml).
|
|
@@ -665,6 +721,19 @@ async function ensureMcpConfigs(hub, repo, scaffolded, configs) {
|
|
|
665
721
|
|
|
666
722
|
// File exists — check if loreli is already configured
|
|
667
723
|
if (existing.content.includes(entry.marker)) {
|
|
724
|
+
if (entry.path === '.codex/config.toml') {
|
|
725
|
+
const updated = normalizeCodexEnvVars(existing.content);
|
|
726
|
+
if (updated) {
|
|
727
|
+
await hub.write(repo, entry.path, {
|
|
728
|
+
content: updated,
|
|
729
|
+
message: 'chore: add codex env_vars forwarding for loreli MCP server'
|
|
730
|
+
});
|
|
731
|
+
scaffolded.push(`${entry.path} (added env_vars forwarding)`);
|
|
732
|
+
log.info(`normalized env_vars forwarding in existing ${entry.path}`);
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
668
737
|
log.info(`loreli already in ${entry.path} — skipping`);
|
|
669
738
|
continue;
|
|
670
739
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { check } from 'loreli/config';
|
|
2
1
|
import { has } from 'loreli/marker';
|
|
2
|
+
import { select } from './repo.js';
|
|
3
|
+
import { check } from 'loreli/config';
|
|
3
4
|
|
|
4
5
|
export default {
|
|
5
6
|
team_status: {
|
|
@@ -18,6 +19,8 @@ export default {
|
|
|
18
19
|
* @returns {Promise<object>} Status dashboard.
|
|
19
20
|
*/
|
|
20
21
|
async exec(args, ctx) {
|
|
22
|
+
// Validate repo format eagerly before any other guard so callers always
|
|
23
|
+
// get a thrown error on malformed input regardless of hub state.
|
|
21
24
|
if (args.repo) check.repo(args.repo);
|
|
22
25
|
|
|
23
26
|
// When the user explicitly requests a repo but hub is not initialized,
|
|
@@ -29,7 +32,7 @@ export default {
|
|
|
29
32
|
return { content: [{ type: 'text', text: 'Run start first to initialize the hub.' }], isError: true };
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
const repo = args.repo
|
|
35
|
+
const repo = select(args.repo, ctx);
|
|
33
36
|
const lines = [`Team Status for ${repo ?? 'unknown repo'}`];
|
|
34
37
|
|
|
35
38
|
// --- Agent States ---
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { logger } from 'loreli/log';
|
|
2
|
-
import {
|
|
2
|
+
import { select } from './repo.js';
|
|
3
3
|
|
|
4
4
|
const log = logger('work');
|
|
5
5
|
|
|
@@ -21,7 +21,7 @@ export default {
|
|
|
21
21
|
repo: { type: 'string', description: 'Target repository (owner/name).' },
|
|
22
22
|
objective: { type: 'string', description: 'What should the planner analyze and plan for?' }
|
|
23
23
|
},
|
|
24
|
-
required: ['
|
|
24
|
+
required: ['objective']
|
|
25
25
|
},
|
|
26
26
|
|
|
27
27
|
/**
|
|
@@ -30,8 +30,14 @@ export default {
|
|
|
30
30
|
* @returns {Promise<object>} Planning initiation result.
|
|
31
31
|
*/
|
|
32
32
|
async exec(args, ctx) {
|
|
33
|
-
|
|
34
|
-
const
|
|
33
|
+
const repo = select(args.repo, ctx);
|
|
34
|
+
const objective = args.objective;
|
|
35
|
+
if (!repo) {
|
|
36
|
+
return {
|
|
37
|
+
content: [{ type: 'text', text: 'No repository configured. Pass --repo, run start, set LORELI_REPO, or set repo in loreli.yml.' }],
|
|
38
|
+
isError: true
|
|
39
|
+
};
|
|
40
|
+
}
|
|
35
41
|
|
|
36
42
|
if (!ctx.hub) {
|
|
37
43
|
return { content: [{ type: 'text', text: 'Run start first to initialize the hub.' }], isError: true };
|
|
@@ -72,8 +78,7 @@ export default {
|
|
|
72
78
|
type: 'object',
|
|
73
79
|
properties: {
|
|
74
80
|
repo: { type: 'string', description: 'Target repository (owner/name).' }
|
|
75
|
-
}
|
|
76
|
-
required: ['repo']
|
|
81
|
+
}
|
|
77
82
|
},
|
|
78
83
|
|
|
79
84
|
/**
|
|
@@ -82,8 +87,13 @@ export default {
|
|
|
82
87
|
* @returns {Promise<object>} Work cycle initiation result.
|
|
83
88
|
*/
|
|
84
89
|
async exec(args, ctx) {
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
const repo = select(args.repo, ctx);
|
|
91
|
+
if (!repo) {
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: 'text', text: 'No repository configured. Pass --repo, run start, set LORELI_REPO, or set repo in loreli.yml.' }],
|
|
94
|
+
isError: true
|
|
95
|
+
};
|
|
96
|
+
}
|
|
87
97
|
|
|
88
98
|
if (!ctx.hub) {
|
|
89
99
|
return { content: [{ type: 'text', text: 'Run start first to initialize the hub.' }], isError: true };
|