@plainconceptsplatform/agent-harness 2.0.1 → 2.1.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 +8 -5
- package/package.json +1 -1
- package/src/content/.agents/skills/pc-make-engineer/SKILL.md +4 -4
- package/src/content/.agents/skills/pc-make-engineer/template.md +3 -3
- package/src/content/.agents/skills/pc-plan-propose/SKILL.md +1 -1
- package/src/content/.agents/skills/pc-repo-onboard/SKILL.md +1 -1
- package/src/content/.opencode/_gitignore +2 -0
- package/src/content/.opencode/plugins/pc-subagent-tiers.js +117 -15
- package/src/content/AGENTS.md +1 -1
- package/src/content/opencode.jsonc +39 -31
- package/src/steps/copy/fullstack-engineer.js +4 -2
- package/src/steps/copy/opencode-json.js +27 -9
- package/src/utils/paths.js +3 -0
package/README.md
CHANGED
|
@@ -164,12 +164,14 @@ Agents define _how to work_. They are universal personas (same behavior across p
|
|
|
164
164
|
Current baseline uses a generic execution model:
|
|
165
165
|
|
|
166
166
|
```
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
167
|
+
build primary. Implements. Full write access. The default.
|
|
168
|
+
plan primary. Same body as build, but cannot edit files.
|
|
169
|
+
fullstack-engineer subagent. The body build and plan share, and the fallback worker.
|
|
170
|
+
*-engineer subagent. User-created specialists, spawned for parallel implementation.
|
|
171
|
+
*-engineer.<tier> subagent. The same specialist pinned to a plan/build/fast model.
|
|
170
172
|
```
|
|
171
173
|
|
|
172
|
-
`fullstack-engineer`
|
|
174
|
+
`build` and `plan` are the only agents a human selects, and they are overrides of opencode's own two primaries rather than new names. The `pc-subagent-tiers` plugin regenerates both from `fullstack-engineer.md` on every startup, so they always carry the current abilities, each on its own tier model. `plan` differs from `build` in one frontmatter line: `edit: deny`. It can still read the tree, shell out to git and openspec, and spawn engineers, so planning works and cannot write. Everything else is `mode: subagent` and reached through `task()`, never picked from the agent list. Project-specific specialization comes from user-created engineers via `/make-engineer`. During `/plan-apply` the lead inspects the engineers that actually exist in `.opencode/agents/` and spawns matching specialists. Prefer a specialist over `fullstack-engineer`; if none matches, create one.
|
|
173
175
|
|
|
174
176
|
### Skills, platform knowledge
|
|
175
177
|
|
|
@@ -289,7 +291,8 @@ your-project/
|
|
|
289
291
|
│ ├── opencode.json ← default model + plugin config
|
|
290
292
|
│ ├── harness.json ← harness config: platform, models, maxConcurrentAgents
|
|
291
293
|
│ ├── harness-managed.json ← hashes of every managed file, so "update" spares your edits
|
|
292
|
-
│ ├── agents/ ←
|
|
294
|
+
│ ├── agents/ ← build.md + plan.md (generated primaries), fullstack-engineer,
|
|
295
|
+
│ │ and user-created *-engineer files (all subagents)
|
|
293
296
|
│ ├── tui.json ← registers the Subagents sidebar panel
|
|
294
297
|
│ ├── tui/
|
|
295
298
|
│ │ └── pc-subagents.tsx ← TUI plugin: live Subagents panel in the sidebar
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plainconceptsplatform/agent-harness",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Installs the Plain Concepts Platform Harness into any codebase, and keeps it up to date. Wires OpenCode, OpenSpec, codegraph, and agentmemory into a multi-agent workflow that runs on native parallel subagents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opencode",
|
|
@@ -4,9 +4,9 @@ description: Create a custom engineer agent via persona-driven interactive desig
|
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Create one file only: `.opencode/agents/{persona}-engineer.md`. The `pc-subagent-tiers` plugin creates tier variants (`.build.md`, `.fast.md`, `.plan.md`) at startup, so you never write those.
|
|
7
|
+
Create one file only: `.opencode/agents/{persona}-engineer.md`. The `pc-subagent-tiers` plugin creates tier variants (`.build.md`, `.fast.md`, `.plan.md`) at startup, so you never write those. It also generates `build.md` and `plan.md`, the only two primary agents: never create or edit those either.
|
|
8
8
|
|
|
9
|
-
Fidelity to the [template](template.md) is the whole job. The file contains frontmatter plus one identity paragraph plus the `## Abilities` section. No other `##` headings. No expertise notes, architecture details, conventions, file maps, or workflow steps. Those belong in skills. Always set `mode:
|
|
9
|
+
Fidelity to the [template](template.md) is the whole job. The file contains frontmatter plus one identity paragraph plus the `## Abilities` section. No other `##` headings. No expertise notes, architecture details, conventions, file maps, or workflow steps. Those belong in skills. Always set `mode: subagent`. Engineers are reached through `task()`, never picked by a human, so a primary engineer would only clutter the agent picker. Never write `model:`. Only reference skills installed in the project's `.agents/skills/` directory.
|
|
10
10
|
|
|
11
11
|
Skills first: you must complete Step 4 (present the form, discover skills for every detected signal and architecture, let the user confirm the set, then install 5-10 skills) before writing any file.
|
|
12
12
|
|
|
@@ -185,7 +185,7 @@ If either check fails, fix the file and re-validate.
|
|
|
185
185
|
|
|
186
186
|
## Step 7: Update fullstack-engineer.md abilities
|
|
187
187
|
|
|
188
|
-
|
|
188
|
+
`fullstack-engineer.md` is `mode: subagent`, but it is also the body that `pc-subagent-tiers` copies into `build.md` and `plan.md` on every startup. So every skill listed here reaches both primary agents, which is why it accumulates all of them: it plans and delegates rather than doing parallel implementation itself.
|
|
189
189
|
|
|
190
190
|
After creating the persona engineer and validating its references, additively merge new skills into fullstack:
|
|
191
191
|
|
|
@@ -216,4 +216,4 @@ Report:
|
|
|
216
216
|
- Skills that failed validation or install (list each with reason)
|
|
217
217
|
- `fullstack-engineer.md` updated (additive, list new skills added)
|
|
218
218
|
- How to use: "This agent will be spawned by the lead during `/plan-apply` for tasks matching its specialty."
|
|
219
|
-
- "Restart opencode for the `pc-subagent-tiers` plugin to pick up the new engineer."
|
|
219
|
+
- "Restart opencode for the `pc-subagent-tiers` plugin to pick up the new engineer and rebuild `build.md` and `plan.md` from the updated fullstack abilities."
|
|
@@ -5,8 +5,8 @@ The agent file is exactly this structure: frontmatter plus one identity paragrap
|
|
|
5
5
|
```markdown
|
|
6
6
|
---
|
|
7
7
|
description: <one sentence naming the persona + top 3-5 detected technologies>
|
|
8
|
-
mode:
|
|
9
|
-
color: <pick: primary|secondary|accent|error|info: avoid colors used by existing agents; warning is reserved for the
|
|
8
|
+
mode: subagent
|
|
9
|
+
color: <pick: primary|secondary|accent|error|info: avoid colors used by existing agents; warning is reserved for the build and plan primaries>
|
|
10
10
|
permission:
|
|
11
11
|
edit: allow
|
|
12
12
|
bash: allow
|
|
@@ -62,7 +62,7 @@ Rules:
|
|
|
62
62
|
|
|
63
63
|
After writing the agent file, verify:
|
|
64
64
|
|
|
65
|
-
1. Frontmatter exists: starts with `---`, has `description`, `mode:
|
|
65
|
+
1. Frontmatter exists: starts with `---`, has `description`, `mode: subagent`, `color`, `permission` block.
|
|
66
66
|
2. No `model:` field in the frontmatter. The `pc-subagent-tiers` plugin injects it.
|
|
67
67
|
3. `## Abilities` is the only `##` heading. No other `##` sections exist in the file.
|
|
68
68
|
4. One identity paragraph before `## Abilities`: 2-3 sentences max, not multiple paragraphs.
|
|
@@ -58,7 +58,7 @@ Load `@openspec-propose` skill and follow its instructions to generate proposal.
|
|
|
58
58
|
- `description:` from the YAML frontmatter: the engineer's specialization summary
|
|
59
59
|
- `## Abilities` section: the skills listed under Development, Testing, Infrastructure (e.g. `@nodejs-backend`, `@secure-nextjs-api-routes`)
|
|
60
60
|
Build a map of `agent-name -> { description, abilities }`.
|
|
61
|
-
2. For each task, compare the task text and domain against every engineer's description AND abilities. Pick the engineer whose combined profile most closely matches. `fullstack-engineer` is `
|
|
61
|
+
2. For each task, compare the task text and domain against every engineer's description AND abilities. Pick the engineer whose combined profile most closely matches. `fullstack-engineer` is the fallback worker and the body behind `build` and `plan`; prefer a real specialist over it, and never annotate a task with `build` or `plan`, which are the user's own primaries. If no specialist matches a task, leave the agent field blank and record the missing specialization in the proposal. An annotated OpenSpec task needs a real subagent; never substitute the lead or an obsolete generic agent name.
|
|
62
62
|
3. Pick a tier, derive `depends_on`, derive `touches`, and annotate each task line. Follow the [task annotation](task-annotation.md) reference for the full tier selection guide, dependency derivation, touches derivation, and annotation format with examples.
|
|
63
63
|
|
|
64
64
|
## Step 3: Show the plan and ask for confirmation (stop)
|
|
@@ -33,7 +33,7 @@ Present as a table:
|
|
|
33
33
|
Then explain the agent selection model:
|
|
34
34
|
- Primary agents appear in Tab and handle direct user interaction
|
|
35
35
|
- Subagent engineers are spawned by the lead for parallel implementation waves
|
|
36
|
-
- Specialist engineers are preferred when their domain matches the task
|
|
36
|
+
- Specialist engineers are preferred when their domain matches the task. `build` and `plan` are the only agents the user selects, and both run the `fullstack-engineer` body; `plan` cannot edit files. Everything else is `mode: subagent` and spawned. If no specialist matches, create one with `/make-engineer`.
|
|
37
37
|
|
|
38
38
|
## Step 3: Command reference
|
|
39
39
|
|
|
@@ -1,12 +1,45 @@
|
|
|
1
1
|
// pc-subagent-tiers: on startup, reads *-engineer.md templates and creates
|
|
2
|
-
// tier variant files with the resolved model
|
|
3
|
-
//
|
|
2
|
+
// tier variant files with the resolved model, plus the two primary agents the
|
|
3
|
+
// user actually talks to. Everything generated here is gitignored and rebuilt
|
|
4
|
+
// every startup. Model resolution: user override > team config.
|
|
5
|
+
//
|
|
6
|
+
// Agent topology:
|
|
7
|
+
// build.md / plan.md mode: primary the only agents a human selects.
|
|
8
|
+
// Both are fullstack-engineer with a
|
|
9
|
+
// tier model; plan cannot edit.
|
|
10
|
+
// fullstack-engineer.md mode: subagent the shared body of build and plan,
|
|
11
|
+
// and the fallback worker.
|
|
12
|
+
// *-engineer.md mode: subagent specialists, spawned by task().
|
|
13
|
+
// *-engineer.<tier>.md mode: subagent the same specialist pinned to a tier.
|
|
14
|
+
//
|
|
15
|
+
// Overriding opencode's built-in build and plan (rather than disabling them, as
|
|
16
|
+
// earlier versions did) means the agent picker offers exactly two entries and
|
|
17
|
+
// both carry our prompt and abilities.
|
|
4
18
|
|
|
5
19
|
import fs from "node:fs/promises"
|
|
6
20
|
import path from "node:path"
|
|
7
21
|
|
|
8
22
|
const TIERS = ["build", "fast", "plan"]
|
|
9
23
|
|
|
24
|
+
// The two primaries, and the tier each takes its model from. plan denies edit
|
|
25
|
+
// so a planning session cannot mutate the tree; bash stays allowed because the
|
|
26
|
+
// planning skills shell out to git and openspec to read state.
|
|
27
|
+
const PRIMARIES = {
|
|
28
|
+
build: {
|
|
29
|
+
tier: "build",
|
|
30
|
+
description: "Implement changes in this repository. Full write access, spawns specialist engineers for parallel work.",
|
|
31
|
+
permission: null,
|
|
32
|
+
},
|
|
33
|
+
plan: {
|
|
34
|
+
tier: "plan",
|
|
35
|
+
description: "Explore and plan without touching the tree. Read-only: proposes work for build to carry out.",
|
|
36
|
+
permission: { edit: "deny" },
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const FULLSTACK_NAME = "fullstack-engineer"
|
|
41
|
+
const FULLSTACK_TEMPLATE = `${FULLSTACK_NAME}.md`
|
|
42
|
+
|
|
10
43
|
export const PcSubagentTiers = async ({ directory }) => {
|
|
11
44
|
const root = directory || process.cwd()
|
|
12
45
|
const agentsDir = path.join(root, ".opencode", "agents")
|
|
@@ -35,11 +68,12 @@ export const PcSubagentTiers = async ({ directory }) => {
|
|
|
35
68
|
try {
|
|
36
69
|
const entries = await fs.readdir(agentsDir)
|
|
37
70
|
return {
|
|
38
|
-
templates: entries.filter(f => /^[\w-]+-engineer\.md$/.test(f) && f !==
|
|
71
|
+
templates: entries.filter(f => /^[\w-]+-engineer\.md$/.test(f) && f !== FULLSTACK_TEMPLATE).map(f => f.replace(/\.md$/, "")),
|
|
39
72
|
variantFiles: entries.filter(f => /^[\w-]+-engineer\.(build|fast|plan)\.md$/.test(f)),
|
|
73
|
+
hasFullstack: entries.includes(FULLSTACK_TEMPLATE),
|
|
40
74
|
}
|
|
41
75
|
} catch {
|
|
42
|
-
return { templates: [], variantFiles: [] }
|
|
76
|
+
return { templates: [], variantFiles: [], hasFullstack: false }
|
|
43
77
|
}
|
|
44
78
|
}
|
|
45
79
|
|
|
@@ -61,11 +95,13 @@ export const PcSubagentTiers = async ({ directory }) => {
|
|
|
61
95
|
return `---\n${fm}\n---${templateContent.slice(fmMatch[0].length)}`
|
|
62
96
|
}
|
|
63
97
|
|
|
64
|
-
// Ensure template files
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
98
|
+
// Ensure template files are mode: subagent with no stale model: line. Only
|
|
99
|
+
// build and plan are primary; every engineer is reached through task(), never
|
|
100
|
+
// picked by a human, so a primary engineer would just clutter the picker.
|
|
101
|
+
// Templates must never declare a model either: models resolve per tier at
|
|
102
|
+
// startup and are injected into generated files only, so a stale model: (from
|
|
103
|
+
// a prior `stampAgentModels` run, or from when these were primary) is
|
|
104
|
+
// stripped and the agent falls back to the session model in opencode.jsonc.
|
|
69
105
|
function normalizeTemplate(templateContent) {
|
|
70
106
|
const fmMatch = templateContent.match(/^---\r?\n([\s\S]*?)\r?\n---/)
|
|
71
107
|
if (!fmMatch) return templateContent
|
|
@@ -73,8 +109,8 @@ export const PcSubagentTiers = async ({ directory }) => {
|
|
|
73
109
|
let fm = fmMatch[1]
|
|
74
110
|
let changed = false
|
|
75
111
|
|
|
76
|
-
if (!/^mode:\s*
|
|
77
|
-
fm = /^mode:/m.test(fm) ? fm.replace(/^mode:.*$/m, 'mode:
|
|
112
|
+
if (!/^mode:\s*subagent/m.test(fm)) {
|
|
113
|
+
fm = /^mode:/m.test(fm) ? fm.replace(/^mode:.*$/m, 'mode: subagent') : `mode: subagent\n${fm}`
|
|
78
114
|
changed = true
|
|
79
115
|
}
|
|
80
116
|
if (/^model:/m.test(fm)) {
|
|
@@ -91,6 +127,32 @@ export const PcSubagentTiers = async ({ directory }) => {
|
|
|
91
127
|
return templateContent.match(/^description:\s*(.+)$/m)?.[1]?.trim() ?? null
|
|
92
128
|
}
|
|
93
129
|
|
|
130
|
+
// Build a primary from the fullstack body: same identity and the same
|
|
131
|
+
// ## Abilities block, with our own frontmatter on top. The body is taken
|
|
132
|
+
// verbatim so /make-engineer only has to maintain fullstack-engineer.md and
|
|
133
|
+
// both primaries inherit whatever it lists.
|
|
134
|
+
function buildPrimary(name, spec, fullstackContent, model) {
|
|
135
|
+
const body = fullstackContent.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '').trim()
|
|
136
|
+
|
|
137
|
+
const lines = [
|
|
138
|
+
'---',
|
|
139
|
+
`description: ${spec.description}`,
|
|
140
|
+
'mode: primary',
|
|
141
|
+
]
|
|
142
|
+
if (model) lines.push(`model: ${model}`)
|
|
143
|
+
lines.push('color: warning')
|
|
144
|
+
lines.push('permission:')
|
|
145
|
+
// plan denies edit; everything else stays allowed so the planning skills can
|
|
146
|
+
// still read the tree, shell out to git and openspec, and spawn engineers.
|
|
147
|
+
lines.push(` edit: ${spec.permission?.edit ?? 'allow'}`)
|
|
148
|
+
for (const key of ['bash', 'read', 'glob', 'grep', 'question', 'todowrite', 'task', 'skill']) {
|
|
149
|
+
lines.push(` ${key}: ${spec.permission?.[key] ?? 'allow'}`)
|
|
150
|
+
}
|
|
151
|
+
lines.push('---')
|
|
152
|
+
|
|
153
|
+
return `${lines.join('\n')}\n\n${body}\n`
|
|
154
|
+
}
|
|
155
|
+
|
|
94
156
|
// Skip writing if the file already has identical content.
|
|
95
157
|
async function writeIfChanged(filePath, content) {
|
|
96
158
|
try {
|
|
@@ -108,7 +170,43 @@ export const PcSubagentTiers = async ({ directory }) => {
|
|
|
108
170
|
try {
|
|
109
171
|
const models = await resolveModels()
|
|
110
172
|
const available = TIERS.filter(t => models[t])
|
|
111
|
-
const { templates, variantFiles } = await scanEngineers()
|
|
173
|
+
const { templates, variantFiles, hasFullstack } = await scanEngineers()
|
|
174
|
+
|
|
175
|
+
// build.md and plan.md are regenerated from fullstack-engineer.md every
|
|
176
|
+
// startup, which is also how an edit to fullstack propagates to both.
|
|
177
|
+
// normalizeTemplate runs on it too, so a repo carrying the old
|
|
178
|
+
// mode: primary fullstack is migrated in place on first launch.
|
|
179
|
+
if (hasFullstack) {
|
|
180
|
+
const fullstackPath = path.join(agentsDir, FULLSTACK_TEMPLATE)
|
|
181
|
+
const rawFullstack = await fs.readFile(fullstackPath, "utf-8")
|
|
182
|
+
const fullstack = normalizeTemplate(rawFullstack)
|
|
183
|
+
if (fullstack !== rawFullstack) {
|
|
184
|
+
await writeIfChanged(fullstackPath, fullstack)
|
|
185
|
+
console.error(`[pc-subagent-tiers] Normalized ${FULLSTACK_TEMPLATE} (mode: subagent)`)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
for (const [name, spec] of Object.entries(PRIMARIES)) {
|
|
189
|
+
const model = models[spec.tier]
|
|
190
|
+
await writeIfChanged(
|
|
191
|
+
path.join(agentsDir, `${name}.md`),
|
|
192
|
+
buildPrimary(name, spec, fullstack, model),
|
|
193
|
+
)
|
|
194
|
+
if (cfg?.agent) {
|
|
195
|
+
// Override opencode's built-in agent of the same name rather than
|
|
196
|
+
// disabling it, so the picker shows ours with our model.
|
|
197
|
+
cfg.agent[name] = {
|
|
198
|
+
...cfg.agent[name],
|
|
199
|
+
mode: 'primary',
|
|
200
|
+
description: spec.description,
|
|
201
|
+
...(model ? { model } : {}),
|
|
202
|
+
...(spec.permission ? { permission: { ...cfg.agent[name]?.permission, ...spec.permission } } : {}),
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
console.error(`[pc-subagent-tiers] Wrote primaries: build (${models.build ?? 'session model'}), plan (${models.plan ?? 'session model'})`)
|
|
207
|
+
} else {
|
|
208
|
+
console.error(`[pc-subagent-tiers] ${FULLSTACK_TEMPLATE} missing: build and plan not generated`)
|
|
209
|
+
}
|
|
112
210
|
|
|
113
211
|
const templateContents = await Promise.all(
|
|
114
212
|
templates.map(async name => {
|
|
@@ -117,7 +215,7 @@ export const PcSubagentTiers = async ({ directory }) => {
|
|
|
117
215
|
// If the template had the wrong mode or a stale model:, persist the fix to disk
|
|
118
216
|
if (content !== rawContent) {
|
|
119
217
|
await writeIfChanged(path.join(agentsDir, `${name}.md`), content)
|
|
120
|
-
console.error(`[pc-subagent-tiers] Normalized ${name}.md (mode:
|
|
218
|
+
console.error(`[pc-subagent-tiers] Normalized ${name}.md (mode: subagent, model: removed)`)
|
|
121
219
|
}
|
|
122
220
|
return { name, content }
|
|
123
221
|
})
|
|
@@ -144,12 +242,16 @@ export const PcSubagentTiers = async ({ directory }) => {
|
|
|
144
242
|
await Promise.all(variantsToWrite.map(v => writeIfChanged(v.path, v.content)))
|
|
145
243
|
|
|
146
244
|
if (cfg?.agent) {
|
|
147
|
-
// Ensure base templates are always mode:
|
|
245
|
+
// Ensure base templates are always mode: subagent in-memory. A repo
|
|
246
|
+
// upgraded from an earlier version may still have primary in config.
|
|
148
247
|
for (const { name } of templateContents) {
|
|
149
248
|
if (cfg.agent[name]) {
|
|
150
|
-
cfg.agent[name].mode = '
|
|
249
|
+
cfg.agent[name].mode = 'subagent'
|
|
151
250
|
}
|
|
152
251
|
}
|
|
252
|
+
if (cfg.agent[FULLSTACK_NAME]) {
|
|
253
|
+
cfg.agent[FULLSTACK_NAME].mode = 'subagent'
|
|
254
|
+
}
|
|
153
255
|
for (const { name, tier, templateContent } of variantsToWrite) {
|
|
154
256
|
const base = cfg.agent[name]
|
|
155
257
|
cfg.agent[`${name}.${tier}`] = base
|
package/src/content/AGENTS.md
CHANGED
|
@@ -38,7 +38,7 @@ Command aliases: OpenSpec skills may reference `/opsx-propose`, `/opsx-apply`, `
|
|
|
38
38
|
|
|
39
39
|
## Engineer selection
|
|
40
40
|
|
|
41
|
-
Inspect `.opencode/agents/*.md` before spawning. Prefer the most specialized custom engineer. `fullstack-engineer` is
|
|
41
|
+
Inspect `.opencode/agents/*.md` before spawning. Prefer the most specialized custom engineer. `build` and `plan` are the only primaries and are never spawned; `fullstack-engineer` is the body they share and the fallback worker, so prefer a specialist over it. If no specialist matches, tell the user to create one with `/make-engineer`. Spawn only engineers present in that directory.
|
|
42
42
|
|
|
43
43
|
The `pc-plan-apply` skill is authoritative for subagent waves, dependency ordering, retries, and concurrency. Read `agents.maxConcurrent` from `.opencode/harness.json` before spawning workers.
|
|
44
44
|
|
|
@@ -1,31 +1,39 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://opencode.ai/config.json",
|
|
3
|
-
"instructions": [
|
|
4
|
-
"AGENTS.md"
|
|
5
|
-
],
|
|
6
|
-
"plugin": [
|
|
7
|
-
"@different-ai/opencode-browser@4.6.1",
|
|
8
|
-
"@mohak34/opencode-notifier@0.2.8"
|
|
9
|
-
],
|
|
10
|
-
"experimental": {
|
|
11
|
-
"mcp_timeout": 300000
|
|
12
|
-
},
|
|
13
|
-
"compaction": {
|
|
14
|
-
"auto": true,
|
|
15
|
-
"prune": true,
|
|
16
|
-
"reserved": 10000
|
|
17
|
-
},
|
|
18
|
-
"permission": {
|
|
19
|
-
"question": "allow",
|
|
20
|
-
"todowrite": "allow",
|
|
21
|
-
"skill": "allow"
|
|
22
|
-
},
|
|
23
|
-
"skills": {
|
|
24
|
-
"paths": [".agents/skills"]
|
|
25
|
-
},
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://opencode.ai/config.json",
|
|
3
|
+
"instructions": [
|
|
4
|
+
"AGENTS.md"
|
|
5
|
+
],
|
|
6
|
+
"plugin": [
|
|
7
|
+
"@different-ai/opencode-browser@4.6.1",
|
|
8
|
+
"@mohak34/opencode-notifier@0.2.8"
|
|
9
|
+
],
|
|
10
|
+
"experimental": {
|
|
11
|
+
"mcp_timeout": 300000
|
|
12
|
+
},
|
|
13
|
+
"compaction": {
|
|
14
|
+
"auto": true,
|
|
15
|
+
"prune": true,
|
|
16
|
+
"reserved": 10000
|
|
17
|
+
},
|
|
18
|
+
"permission": {
|
|
19
|
+
"question": "allow",
|
|
20
|
+
"todowrite": "allow",
|
|
21
|
+
"skill": "allow"
|
|
22
|
+
},
|
|
23
|
+
"skills": {
|
|
24
|
+
"paths": [".agents/skills"]
|
|
25
|
+
},
|
|
26
|
+
// build and plan are the only primaries. The pc-subagent-tiers plugin
|
|
27
|
+
// regenerates .opencode/agents/{build,plan}.md from fullstack-engineer.md on
|
|
28
|
+
// every startup and overrides these two entries with the resolved tier model,
|
|
29
|
+
// so what is here is only the floor if the plugin cannot run. Every engineer
|
|
30
|
+
// is mode: subagent and reached through task(), never picked by a human.
|
|
31
|
+
"default_agent": "build",
|
|
32
|
+
"agent": {
|
|
33
|
+
"build": { "mode": "primary" },
|
|
34
|
+
"plan": {
|
|
35
|
+
"mode": "primary",
|
|
36
|
+
"permission": { "edit": "deny" }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -4,7 +4,7 @@ import { success } from '../../utils/exec.js'
|
|
|
4
4
|
|
|
5
5
|
const FULLSTACK_FILE = 'fullstack-engineer.md'
|
|
6
6
|
const FULLSTACK_DESCRIPTION = 'Default engineer that accumulates skills from all created persona engineers. Use as fallback when no specialist matches: but prefer spawning a specific engineer for deterministic results.'
|
|
7
|
-
const FULLSTACK_IDENTITY = 'You are the default engineer,
|
|
7
|
+
const FULLSTACK_IDENTITY = 'You are the default engineer, and your body is what the build and plan agents run. You are more complete but less accurate than specialized engineers, so prefer spawning a specialist when one matches the task domain.'
|
|
8
8
|
|
|
9
9
|
// The reminder plugin owns ability bootstrap. Remove the old prompt-level
|
|
10
10
|
// directive when regenerating so it cannot duplicate the plugin reminder.
|
|
@@ -56,7 +56,9 @@ export async function generateFullstackEngineer({ cwd = process.cwd(), updateMod
|
|
|
56
56
|
const frontmatter = [
|
|
57
57
|
'---',
|
|
58
58
|
`description: ${FULLSTACK_DESCRIPTION}`,
|
|
59
|
-
|
|
59
|
+
// subagent, not primary: build.md and plan.md are the primaries, and
|
|
60
|
+
// pc-subagent-tiers generates both from this file on every startup.
|
|
61
|
+
'mode: subagent',
|
|
60
62
|
'color: warning',
|
|
61
63
|
'permission:',
|
|
62
64
|
' edit: allow',
|
|
@@ -33,9 +33,19 @@ export async function patchOpencodeJson(cwd = process.cwd()) {
|
|
|
33
33
|
return { patched: false, reason: 'parse error' }
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
// build and plan are overridden, not disabled. Earlier versions disabled them
|
|
37
|
+
// and pointed default_agent at fullstack-engineer; now they are the only two
|
|
38
|
+
// primaries and pc-subagent-tiers regenerates their agent files from
|
|
39
|
+
// fullstack-engineer.md. A repo patched by an earlier version still carries
|
|
40
|
+
// `disable: true`, which would hide both agents, so that flag is cleared here.
|
|
41
|
+
const hasStaleDisable = (
|
|
42
|
+
parsed?.agent?.build?.disable !== undefined ||
|
|
43
|
+
parsed?.agent?.plan?.disable !== undefined
|
|
44
|
+
)
|
|
45
|
+
const needsAgentOverride = hasStaleDisable || !(
|
|
46
|
+
parsed?.agent?.build?.mode === 'primary' &&
|
|
47
|
+
parsed?.agent?.plan?.mode === 'primary' &&
|
|
48
|
+
parsed?.agent?.plan?.permission?.edit === 'deny'
|
|
39
49
|
)
|
|
40
50
|
const needsSkillPermission = parsed?.permission?.skill !== 'allow'
|
|
41
51
|
const missingSharedPermissions = SHARED_PERMISSIONS.filter(
|
|
@@ -51,14 +61,22 @@ export async function patchOpencodeJson(cwd = process.cwd()) {
|
|
|
51
61
|
parsed?.compaction?.prune === true
|
|
52
62
|
)
|
|
53
63
|
|
|
54
|
-
if (!
|
|
64
|
+
if (!needsAgentOverride && !needsSkillPermission && missingSharedPermissions.length === 0 && !needsSkillsPaths && !needsCompaction) {
|
|
55
65
|
return { patched: false }
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
// Apply edits sequentially so offsets stay correct
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
if (needsAgentOverride) {
|
|
70
|
+
// undefined removes the key, clearing the disable left by earlier versions.
|
|
71
|
+
if (parsed?.agent?.build?.disable !== undefined) {
|
|
72
|
+
text = applyModify(text, ['agent', 'build', 'disable'], undefined)
|
|
73
|
+
}
|
|
74
|
+
if (parsed?.agent?.plan?.disable !== undefined) {
|
|
75
|
+
text = applyModify(text, ['agent', 'plan', 'disable'], undefined)
|
|
76
|
+
}
|
|
77
|
+
text = applyModify(text, ['agent', 'build', 'mode'], 'primary')
|
|
78
|
+
text = applyModify(text, ['agent', 'plan', 'mode'], 'primary')
|
|
79
|
+
text = applyModify(text, ['agent', 'plan', 'permission', 'edit'], 'deny')
|
|
62
80
|
}
|
|
63
81
|
if (needsSkillPermission) text = applyModify(text, ['permission', 'skill'], 'allow')
|
|
64
82
|
for (const [key, value] of missingSharedPermissions) {
|
|
@@ -82,8 +100,8 @@ export async function patchOpencodeJson(cwd = process.cwd()) {
|
|
|
82
100
|
}
|
|
83
101
|
|
|
84
102
|
await fse.writeFile(opencodePath, text, 'utf-8')
|
|
85
|
-
if (
|
|
86
|
-
success('
|
|
103
|
+
if (needsAgentOverride) {
|
|
104
|
+
success('Set build/plan as the primary agents in opencode.jsonc (plan is read-only)')
|
|
87
105
|
}
|
|
88
106
|
if (needsSkillPermission) {
|
|
89
107
|
success('Allowed skill loading in opencode.jsonc')
|
package/src/utils/paths.js
CHANGED
|
@@ -34,6 +34,9 @@ export const IGNORED_ENTRIES = [
|
|
|
34
34
|
MANIFEST_FILE,
|
|
35
35
|
'source-roots.json',
|
|
36
36
|
'*-engineer.*.md',
|
|
37
|
+
// Regenerated from fullstack-engineer.md by pc-subagent-tiers every startup.
|
|
38
|
+
'agents/build.md',
|
|
39
|
+
'agents/plan.md',
|
|
37
40
|
]
|
|
38
41
|
|
|
39
42
|
export function configPath(cwd = process.cwd()) {
|