@wpro-eng/opencode-config 1.6.0 → 1.7.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.
package/README.md CHANGED
@@ -62,9 +62,36 @@ Create `~/.config/opencode/wpromote.json` or `~/.config/opencode/wpromote.jsonc`
62
62
  |--------|------|---------|-------------|
63
63
  | `disable` | `string[]` | `[]` | Assets to disable (see below) |
64
64
  | `installMethod` | `"link"` \| `"copy"` | `"link"` | How to install skills/plugins/MCPs |
65
+ | `agentNames` | `"lotr"` \| `"descriptive"` | `"descriptive"` | Agent naming theme (see below) |
65
66
  | `dryRun` | `boolean` | `false` | Preview changes without installing |
66
67
  | `orchestration` | `object` | defaults | Runtime orchestration controls, limits, and fallback policy |
67
68
 
69
+ ### Agent Name Themes
70
+
71
+ Agents can be displayed with either plain role-based names or Tolkien character names. Set `agentNames` in your config:
72
+
73
+ ```json
74
+ {
75
+ "agentNames": "lotr"
76
+ }
77
+ ```
78
+
79
+ | Role | `"descriptive"` (default) | `"lotr"` |
80
+ |------|--------------------------|----------|
81
+ | Orchestrator | `orchestrator` | `gandalf` |
82
+ | Explorer | `explorer` | `legolas` |
83
+ | Researcher | `researcher` | `radagast` |
84
+ | Planner | `planner` | `treebeard` |
85
+ | Implementer | `implementer` | `celebrimbor` |
86
+ | Architect | `architect` | `elrond` |
87
+ | Analyst | `analyst` | `galadriel` |
88
+ | TDD | `tdd` | `aragorn` |
89
+ | Deep Work | `deepwork` | `samwise` |
90
+
91
+ The theme affects agent names everywhere: in the agent list, slash commands, delegation routing, and cross-references between agents. Agent markdown files use template variables (e.g. `{{explorer}}`) so cross-references resolve correctly regardless of theme.
92
+
93
+ When using the disable list, use the **themed name** (the name that appears after applying your chosen theme). For example, with `"descriptive"` theme, disable with `"agent:orchestrator"` instead of `"agent:gandalf"`.
94
+
68
95
  ### Orchestration Runtime Configuration
69
96
 
70
97
  All fields below show their **defaults**. You only need to include fields you want to override.
@@ -251,26 +278,26 @@ EOF
251
278
 
252
279
  ### Agents (`agent/`)
253
280
 
254
- Pre-configured agent definitions with model, temperature, and system prompts. There are two **primary** agents (you talk to them directly) and seven **subagents** (they run in the background, delegated by the primary agent).
281
+ Pre-configured agent definitions with model, temperature, and system prompts. There are two **primary** agents (you talk to them directly) and seven **subagents** (they run in the background, delegated by the primary agent). Names shown below use the default `"descriptive"` theme, with `"lotr"` names in parentheses.
255
282
 
256
283
  #### Primary Agents
257
284
 
258
285
  | Agent | Model | What it does |
259
286
  |-------|-------|--------------|
260
- | `gandalf` | Claude Opus 4 | The default orchestrator. Classifies your request, breaks it into tasks, delegates to the right subagents in parallel, and assembles the final result. Handles everything from quick fixes to multi-step projects. |
261
- | `samwise` | Claude Opus 4 | A higher-rigor variant of Gandalf for long-running complex work. Runs a sustained execution loop with tighter milestone verification, aggressive parallelism, and continuous health monitoring. Use when a task needs hours of autonomous work without hand-holding. |
287
+ | `orchestrator` (`gandalf`) | Claude Opus 4 | The default orchestrator. Classifies your request, breaks it into tasks, delegates to the right subagents in parallel, and assembles the final result. Handles everything from quick fixes to multi-step projects. |
288
+ | `deepwork` (`samwise`) | Claude Opus 4 | A higher-rigor variant for long-running complex work. Runs a sustained execution loop with tighter milestone verification, aggressive parallelism, and continuous health monitoring. Use when a task needs hours of autonomous work without hand-holding. |
262
289
 
263
290
  #### Subagents
264
291
 
265
- | Agent | Model | When Gandalf calls it | What it does |
266
- |-------|-------|-----------------------|--------------|
267
- | `legolas` | Claude Sonnet 4.6 | "Where is X in the codebase?" | Searches the repo fast. Uses LSP, grep, glob, and git to find files, symbols, call paths, and code patterns. Returns structured results with absolute paths and a direct answer, not just a file list. Read-only by default. |
268
- | `radagast` | GPT-5.2 | "How does library X work?" | Researches external docs, OSS repos, and the web. Classifies the question (conceptual, implementation, history, or comprehensive), fetches official sources, and returns evidence-backed recommendations with links. |
269
- | `treebeard` | GPT-5.2 | "Is this plan solid?" | Reviews plans for ambiguity, risk, and executability before implementation starts. Surfaces hidden assumptions, produces concrete acceptance criteria, and gives a clear OKAY or REJECT verdict with at most 3 blocking issues. |
270
- | `celebrimbor` | GPT-5.2 Codex | "Build this end-to-end." | The workhorse implementation agent. Operates like a senior staff engineer: explores context, plans edits, executes changes, runs tests, and iterates until done. Does not stop at partial progress or ask permission for normal engineering work. |
271
- | `elrond` | GPT-5.2 | "What's the right architecture?" | Read-only strategic advisor. Analyzes codebases, evaluates tradeoffs, and returns a bottom-line recommendation with an action plan and effort estimate. Biases toward simplicity and existing patterns over speculative complexity. |
272
- | `galadriel` | Gemini 3.1 Pro | "What's in this image/PDF?" | Multimodal analysis specialist. Handles PDFs, images, diagrams, and charts that other agents cannot process as plain text. Extracts targeted information and returns concise findings with confidence notes. |
273
- | `aragorn` | GPT-5.2 Codex | "Write tests for this." | Strict TDD specialist following Red-Green-Refactor. Writes a failing test first, makes it pass with minimal code, then refactors under green. Produces a test plan, TDD log, execution results, and a quality scorecard. Never writes production code without a failing test. |
292
+ | Agent | Model | When called | What it does |
293
+ |-------|-------|-------------|--------------|
294
+ | `explorer` (`legolas`) | Claude Sonnet 4.6 | "Where is X in the codebase?" | Searches the repo fast. Uses LSP, grep, glob, and git to find files, symbols, call paths, and code patterns. Returns structured results with absolute paths and a direct answer, not just a file list. Read-only by default. |
295
+ | `researcher` (`radagast`) | GPT-5.2 | "How does library X work?" | Researches external docs, OSS repos, and the web. Classifies the question (conceptual, implementation, history, or comprehensive), fetches official sources, and returns evidence-backed recommendations with links. |
296
+ | `planner` (`treebeard`) | GPT-5.2 | "Is this plan solid?" | Reviews plans for ambiguity, risk, and executability before implementation starts. Surfaces hidden assumptions, produces concrete acceptance criteria, and gives a clear OKAY or REJECT verdict with at most 3 blocking issues. |
297
+ | `implementer` (`celebrimbor`) | GPT-5.2 Codex | "Build this end-to-end." | The workhorse implementation agent. Operates like a senior staff engineer: explores context, plans edits, executes changes, runs tests, and iterates until done. Does not stop at partial progress or ask permission for normal engineering work. |
298
+ | `architect` (`elrond`) | GPT-5.2 | "What's the right architecture?" | Read-only strategic advisor. Analyzes codebases, evaluates tradeoffs, and returns a bottom-line recommendation with an action plan and effort estimate. Biases toward simplicity and existing patterns over speculative complexity. |
299
+ | `analyst` (`galadriel`) | Gemini 3.1 Pro | "What's in this image/PDF?" | Multimodal analysis specialist. Handles PDFs, images, diagrams, and charts that other agents cannot process as plain text. Extracts targeted information and returns concise findings with confidence notes. |
300
+ | `tdd` (`aragorn`) | GPT-5.2 Codex | "Write tests for this." | Strict TDD specialist following Red-Green-Refactor. Writes a failing test first, makes it pass with minimal code, then refactors under green. Produces a test plan, TDD log, execution results, and a quality scorecard. Never writes production code without a failing test. |
274
301
 
275
302
  ### Commands (`command/`)
276
303
 
@@ -25,12 +25,12 @@ Orchestration workflow:
25
25
 
26
26
  Delegation routing:
27
27
 
28
- - `legolas` for internal codebase discovery.
29
- - `radagast` for external docs and OSS references.
30
- - `treebeard` for pre-planning analysis and plan review.
31
- - `celebrimbor` for end-to-end implementation execution.
32
- - `elrond` for architecture/security/performance tradeoff consultation.
33
- - `galadriel` for image/PDF/diagram interpretation.
28
+ - `{{explorer}}` for internal codebase discovery.
29
+ - `{{researcher}}` for external docs and OSS references.
30
+ - `{{planner}}` for pre-planning analysis and plan review.
31
+ - `{{implementer}}` for end-to-end implementation execution.
32
+ - `{{architect}}` for architecture/security/performance tradeoff consultation.
33
+ - `{{analyst}}` for image/PDF/diagram interpretation.
34
34
 
35
35
  Delegation prompt quality:
36
36
 
@@ -17,15 +17,15 @@ Instructions:
17
17
 
18
18
  4. Spawn 4 subagent probes in parallel using the `task` tool. Each probe must be a trivial, fast, read-only task that proves the delegation pipeline works end-to-end. Do NOT run destructive or write operations.
19
19
 
20
- **Probe A — legolas (codebase exploration)**:
20
+ **Probe A — {{explorer}} (codebase exploration)**:
21
21
  - Task: "List the top-level directories and count of files in this repo. Return the directory listing."
22
22
  - Expected: completes with a directory listing.
23
23
 
24
- **Probe B — radagast (external research)**:
24
+ **Probe B — {{researcher}} (external research)**:
25
25
  - Task: "What license does the tmux terminal multiplexer use? Search the web and return the license name (e.g. MIT, BSD, ISC, GPL)."
26
26
  - Expected: completes with a license name (ISC).
27
27
 
28
- **Probe C — treebeard (planning)**:
28
+ **Probe C — {{planner}} (planning)**:
29
29
  - Task: "Review this single-sentence plan and identify one risk: 'We will migrate all configs to YAML next sprint.' Return your risk assessment in 2-3 sentences."
30
30
  - Expected: completes with a short risk assessment.
31
31
 
package/dist/index.js CHANGED
@@ -8346,6 +8346,7 @@ function logWarn(message) {
8346
8346
  var UserConfigSchema = exports_external.object({
8347
8347
  disable: exports_external.array(exports_external.string()).default([]),
8348
8348
  installMethod: exports_external.enum(["link", "copy"]).default("link"),
8349
+ agentNames: exports_external.enum(["lotr", "descriptive"]).default("descriptive"),
8349
8350
  dryRun: exports_external.boolean().default(false),
8350
8351
  orchestration: exports_external.object({
8351
8352
  providerMode: exports_external.enum(["copilot", "native"]).default("copilot"),
@@ -8396,6 +8397,7 @@ var UserConfigSchema = exports_external.object({
8396
8397
  var DEFAULT_CONFIG = {
8397
8398
  disable: [],
8398
8399
  installMethod: "link",
8400
+ agentNames: "descriptive",
8399
8401
  dryRun: false,
8400
8402
  orchestration: {
8401
8403
  providerMode: "copilot",
@@ -8610,16 +8612,50 @@ function discoverInstructions(repoPath) {
8610
8612
  return instructions;
8611
8613
  }
8612
8614
 
8615
+ // src/agent-names.ts
8616
+ var AGENT_NAME_TABLE = [
8617
+ { canonical: "orchestrator", role: "orchestrator", themed: { lotr: "gandalf", descriptive: "orchestrator" } },
8618
+ { canonical: "explorer", role: "explorer", themed: { lotr: "legolas", descriptive: "explorer" } },
8619
+ { canonical: "researcher", role: "researcher", themed: { lotr: "radagast", descriptive: "researcher" } },
8620
+ { canonical: "planner", role: "planner", themed: { lotr: "treebeard", descriptive: "planner" } },
8621
+ { canonical: "implementer", role: "implementer", themed: { lotr: "celebrimbor", descriptive: "implementer" } },
8622
+ { canonical: "architect", role: "architect", themed: { lotr: "elrond", descriptive: "architect" } },
8623
+ { canonical: "analyst", role: "analyst", themed: { lotr: "galadriel", descriptive: "analyst" } },
8624
+ { canonical: "tdd", role: "tdd", themed: { lotr: "aragorn", descriptive: "tdd" } },
8625
+ { canonical: "deepwork", role: "deepwork", themed: { lotr: "samwise", descriptive: "deepwork" } }
8626
+ ];
8627
+ var BY_CANONICAL = new Map(AGENT_NAME_TABLE.map((e) => [e.canonical, e]));
8628
+ function resolveAgentName(canonical, theme) {
8629
+ const entry = BY_CANONICAL.get(canonical);
8630
+ if (!entry)
8631
+ return canonical;
8632
+ return entry.themed[theme];
8633
+ }
8634
+ function buildTemplateVars(theme) {
8635
+ const vars = {};
8636
+ for (const entry of AGENT_NAME_TABLE) {
8637
+ vars[entry.role] = entry.themed[theme];
8638
+ }
8639
+ return vars;
8640
+ }
8641
+ function resolveTemplateVars(text, vars) {
8642
+ return text.replace(/\{\{(\w+)\}\}/g, (match, key) => {
8643
+ if (key in vars)
8644
+ return vars[key];
8645
+ return match;
8646
+ });
8647
+ }
8648
+
8613
8649
  // src/assets.ts
8614
8650
  var DISCOVERY_LIMITS = {
8615
8651
  maxFiles: 100,
8616
8652
  maxFileSize: 256 * 1024,
8617
8653
  maxDepth: 10
8618
8654
  };
8619
- async function discoverAssets(packageRoot) {
8655
+ async function discoverAssets(packageRoot, theme = "descriptive") {
8620
8656
  const skills = await discoverSkills(packageRoot);
8621
- const agents = await discoverAgents(packageRoot);
8622
- const commands = await discoverCommands(packageRoot);
8657
+ const agents = await discoverAgents(packageRoot, theme);
8658
+ const commands = await discoverCommands(packageRoot, theme);
8623
8659
  const plugins = await discoverPlugins(packageRoot, REPO_SHORT_NAME);
8624
8660
  const instructions = discoverInstructions(packageRoot);
8625
8661
  const mcps = await discoverMcps(packageRoot);
@@ -8668,13 +8704,14 @@ async function discoverSkills(repoPath) {
8668
8704
  findSkills(skillDir, 0);
8669
8705
  return skills;
8670
8706
  }
8671
- async function discoverAgents(repoPath) {
8707
+ async function discoverAgents(repoPath, theme = "descriptive") {
8672
8708
  const agents = [];
8673
8709
  let filesProcessed = 0;
8674
8710
  let warned = false;
8675
8711
  const agentDir = path.join(repoPath, "agent");
8676
8712
  if (!fs.existsSync(agentDir))
8677
8713
  return agents;
8714
+ const templateVars = buildTemplateVars(theme);
8678
8715
  const walk = (dir, depth) => {
8679
8716
  if (depth > DISCOVERY_LIMITS.maxDepth) {
8680
8717
  if (!warned) {
@@ -8708,7 +8745,7 @@ async function discoverAgents(repoPath) {
8708
8745
  }
8709
8746
  filesProcessed++;
8710
8747
  const content = fs.readFileSync(fullPath, "utf-8");
8711
- const parsed = parseAgentMarkdown(fullPath, content, agentDir);
8748
+ const parsed = parseAgentMarkdown(fullPath, content, agentDir, theme, templateVars);
8712
8749
  if (parsed)
8713
8750
  agents.push(parsed);
8714
8751
  } catch (err) {
@@ -8720,7 +8757,7 @@ async function discoverAgents(repoPath) {
8720
8757
  walk(agentDir, 0);
8721
8758
  return agents;
8722
8759
  }
8723
- function parseAgentMarkdown(filePath, content, agentDir) {
8760
+ function parseAgentMarkdown(filePath, content, agentDir, theme, templateVars) {
8724
8761
  let md;
8725
8762
  try {
8726
8763
  const yamlEngine = require_engines().yaml;
@@ -8754,15 +8791,21 @@ function parseAgentMarkdown(filePath, content, agentDir) {
8754
8791
  logError(`Invalid agent config in ${filePath}: ${JSON.stringify(result.error.format())}`);
8755
8792
  return null;
8756
8793
  }
8757
- return { name: agentName, path: filePath, config: result.data };
8794
+ const themedName = resolveAgentName(agentName, theme);
8795
+ const resolvedConfig = { ...result.data };
8796
+ if (resolvedConfig.prompt) {
8797
+ resolvedConfig.prompt = resolveTemplateVars(resolvedConfig.prompt, templateVars);
8798
+ }
8799
+ return { name: themedName, path: filePath, config: resolvedConfig };
8758
8800
  }
8759
- async function discoverCommands(repoPath) {
8801
+ async function discoverCommands(repoPath, theme = "descriptive") {
8760
8802
  const commands = [];
8761
8803
  let filesProcessed = 0;
8762
8804
  let warned = false;
8763
8805
  const commandDir = path.join(repoPath, "command");
8764
8806
  if (!fs.existsSync(commandDir))
8765
8807
  return commands;
8808
+ const templateVars = buildTemplateVars(theme);
8766
8809
  const walk = (dir, depth) => {
8767
8810
  if (depth > DISCOVERY_LIMITS.maxDepth) {
8768
8811
  if (!warned) {
@@ -8796,7 +8839,7 @@ async function discoverCommands(repoPath) {
8796
8839
  }
8797
8840
  filesProcessed++;
8798
8841
  const content = fs.readFileSync(fullPath, "utf-8");
8799
- const parsed = parseCommandMarkdown(fullPath, content, commandDir);
8842
+ const parsed = parseCommandMarkdown(fullPath, content, commandDir, templateVars);
8800
8843
  if (parsed)
8801
8844
  commands.push(parsed);
8802
8845
  } catch (err) {
@@ -8808,7 +8851,7 @@ async function discoverCommands(repoPath) {
8808
8851
  walk(commandDir, 0);
8809
8852
  return commands;
8810
8853
  }
8811
- function parseCommandMarkdown(filePath, content, commandDir) {
8854
+ function parseCommandMarkdown(filePath, content, commandDir, templateVars) {
8812
8855
  let md;
8813
8856
  try {
8814
8857
  const yamlEngine = require_engines().yaml;
@@ -8840,7 +8883,11 @@ function parseCommandMarkdown(filePath, content, commandDir) {
8840
8883
  logError(`Invalid command config in ${filePath}: ${JSON.stringify(result.error.format())}`);
8841
8884
  return null;
8842
8885
  }
8843
- return { name: commandName, path: filePath, config: result.data };
8886
+ const resolvedConfig = { ...result.data };
8887
+ if (resolvedConfig.template) {
8888
+ resolvedConfig.template = resolveTemplateVars(resolvedConfig.template, templateVars);
8889
+ }
8890
+ return { name: commandName, path: filePath, config: resolvedConfig };
8844
8891
  }
8845
8892
  async function discoverPlugins(repoPath, repoShortName) {
8846
8893
  const plugins = [];
@@ -10083,7 +10130,7 @@ var DEFAULT_OPENCODE_AGENTS_TO_DISABLE = ["build", "plan"];
10083
10130
  async function performLoad(config) {
10084
10131
  const disableMatcher = createDisableMatcher(config.disable);
10085
10132
  log(config.dryRun ? "[DRY-RUN] Analyzing team configuration..." : "Loading team configuration...");
10086
- const result = await discoverAssets(PACKAGE_ROOT);
10133
+ const result = await discoverAssets(PACKAGE_ROOT, config.agentNames);
10087
10134
  if (!config.dryRun) {
10088
10135
  const brokenUnmanagedLinks = findBrokenUnmanagedPluginLinks();
10089
10136
  if (brokenUnmanagedLinks.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wpro-eng/opencode-config",
3
- "version": "1.6.0",
3
+ "version": "1.7.1",
4
4
  "description": "Wpromote OpenCode plugin to sync team config assets on startup",
5
5
  "repository": {
6
6
  "type": "git",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes