@plainconceptsplatform/agent-harness 2.2.1 → 2.3.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/package.json +1 -1
- package/src/commands/update.js +13 -1
- package/src/content/.agents/skills/pc-make-engineer/SKILL.md +219 -219
- package/src/content/.agents/skills/pc-make-engineer/template.md +80 -81
- package/src/content/.opencode/plugins/pc-subagent-tiers.js +337 -281
- package/src/content/opencode.jsonc +41 -39
- package/src/steps/copy/fullstack-engineer.js +88 -85
- package/src/steps/copy/opencode-json.js +161 -147
- package/src/steps/metadata/index.js +4 -0
- package/src/steps/optimization/detect.js +66 -0
- package/src/utils/agent-color.js +83 -0
|
@@ -1,39 +1,41 @@
|
|
|
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
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
+
// build and plan are the only primaries, and plan is the default: a session
|
|
27
|
+
// starts read-only and you switch to build once you know what to change.
|
|
28
|
+
// The pc-subagent-tiers plugin
|
|
29
|
+
// regenerates .opencode/agents/{build,plan}.md from fullstack-engineer.md on
|
|
30
|
+
// every startup and overrides these two entries with the resolved tier model,
|
|
31
|
+
// so what is here is only the floor if the plugin cannot run. Every engineer
|
|
32
|
+
// is mode: subagent and reached through task(), never picked by a human.
|
|
33
|
+
"default_agent": "plan",
|
|
34
|
+
"agent": {
|
|
35
|
+
"build": { "mode": "primary" },
|
|
36
|
+
"plan": {
|
|
37
|
+
"mode": "primary",
|
|
38
|
+
"permission": { "edit": "deny" }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -1,85 +1,88 @@
|
|
|
1
|
-
import fse from 'fs-extra'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import { success } from '../../utils/exec.js'
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return text
|
|
14
|
-
|
|
15
|
-
.
|
|
16
|
-
.
|
|
17
|
-
.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
let
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
'
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
'
|
|
67
|
-
'
|
|
68
|
-
'
|
|
69
|
-
'
|
|
70
|
-
'
|
|
71
|
-
'
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
1
|
+
import fse from 'fs-extra'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { success } from '../../utils/exec.js'
|
|
4
|
+
import { agentColor } from '../../utils/agent-color.js'
|
|
5
|
+
|
|
6
|
+
const FULLSTACK_FILE = 'fullstack-engineer.md'
|
|
7
|
+
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.'
|
|
8
|
+
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.'
|
|
9
|
+
|
|
10
|
+
// The reminder plugin owns ability bootstrap. Remove the old prompt-level
|
|
11
|
+
// directive when regenerating so it cannot duplicate the plugin reminder.
|
|
12
|
+
function stripStartupDirective(text) {
|
|
13
|
+
if (!text) return text
|
|
14
|
+
return text
|
|
15
|
+
.split(/\r?\n\r?\n+/)
|
|
16
|
+
.filter(paragraph => !paragraph.trim().startsWith('**Startup'))
|
|
17
|
+
.join('\n\n')
|
|
18
|
+
.trim()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function removeLegacyStartupDirectives({ cwd = process.cwd() } = {}) {
|
|
22
|
+
const agentsDir = path.join(cwd, '.opencode', 'agents')
|
|
23
|
+
if (!await fse.pathExists(agentsDir)) return 0
|
|
24
|
+
|
|
25
|
+
let updated = 0
|
|
26
|
+
for (const fileName of await fse.readdir(agentsDir)) {
|
|
27
|
+
if (!fileName.endsWith('.md')) continue
|
|
28
|
+
const filePath = path.join(agentsDir, fileName)
|
|
29
|
+
const content = await fse.readFile(filePath, 'utf-8')
|
|
30
|
+
const cleaned = stripStartupDirective(content)
|
|
31
|
+
if (cleaned === content.trim()) continue
|
|
32
|
+
await fse.writeFile(filePath, `${cleaned}\n`, 'utf-8')
|
|
33
|
+
updated++
|
|
34
|
+
}
|
|
35
|
+
return updated
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function generateFullstackEngineer({ cwd = process.cwd(), updateMode = false } = {}) {
|
|
39
|
+
const agentsDir = path.join(cwd, '.opencode', 'agents')
|
|
40
|
+
await fse.ensureDir(agentsDir)
|
|
41
|
+
const filePath = path.join(agentsDir, FULLSTACK_FILE)
|
|
42
|
+
|
|
43
|
+
if (updateMode && await fse.pathExists(filePath)) {
|
|
44
|
+
return { generated: false, preserved: true }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let existingAbilities = null
|
|
48
|
+
let existingIdentity = null
|
|
49
|
+
if (await fse.pathExists(filePath)) {
|
|
50
|
+
const content = await fse.readFile(filePath, 'utf-8')
|
|
51
|
+
const abilitiesMatch = content.match(/## Abilities\n([\s\S]*?)$/)
|
|
52
|
+
if (abilitiesMatch) existingAbilities = abilitiesMatch[1].trim()
|
|
53
|
+
const identityMatch = content.match(/^---\n[\s\S]*?\n---\n\n(.*?)(?:\n\n## Abilities)/s)
|
|
54
|
+
if (identityMatch) existingIdentity = stripStartupDirective(identityMatch[1])
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const frontmatter = [
|
|
58
|
+
'---',
|
|
59
|
+
`description: ${FULLSTACK_DESCRIPTION}`,
|
|
60
|
+
// subagent, not primary: build.md and plan.md are the primaries, and
|
|
61
|
+
// pc-subagent-tiers generates both from this file on every startup.
|
|
62
|
+
'mode: subagent',
|
|
63
|
+
// warning is reserved for plan, so fullstack takes its name-derived hex
|
|
64
|
+
// like every other subagent.
|
|
65
|
+
`color: ${agentColor('fullstack-engineer')}`,
|
|
66
|
+
'permission:',
|
|
67
|
+
' edit: allow',
|
|
68
|
+
' bash: allow',
|
|
69
|
+
' read: allow',
|
|
70
|
+
' glob: allow',
|
|
71
|
+
' grep: allow',
|
|
72
|
+
' question: allow',
|
|
73
|
+
' todowrite: allow',
|
|
74
|
+
'---',
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
const identity = existingIdentity ?? FULLSTACK_IDENTITY
|
|
78
|
+
|
|
79
|
+
const abilities = existingAbilities ?? [
|
|
80
|
+
'- Guardrails: @pc-guardrails-generic, @pc-guardrails-project',
|
|
81
|
+
].join('\n')
|
|
82
|
+
|
|
83
|
+
const content = `${frontmatter.join('\n')}\n\n${identity}\n\n## Abilities\n${abilities}\n`
|
|
84
|
+
|
|
85
|
+
await fse.writeFile(filePath, content, 'utf-8')
|
|
86
|
+
success(`Generated ${FULLSTACK_FILE}`)
|
|
87
|
+
return { generated: true }
|
|
88
|
+
}
|
|
@@ -1,147 +1,161 @@
|
|
|
1
|
-
import fse from 'fs-extra'
|
|
2
|
-
import { applyEdits, modify, parse } from 'jsonc-parser'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
import { success } from '../../utils/exec.js'
|
|
5
|
-
|
|
6
|
-
// The reminder plugin loads every agent ability before work, including
|
|
7
|
-
// user-installed skills outside the pc-* and openspec-* namespaces.
|
|
8
|
-
const SHARED_PERMISSIONS = [
|
|
9
|
-
['question', 'allow'],
|
|
10
|
-
['todowrite', 'allow'],
|
|
11
|
-
]
|
|
12
|
-
|
|
13
|
-
function applyModify(text, jsonPath, value) {
|
|
14
|
-
const edits = modify(text, jsonPath, value, {
|
|
15
|
-
formattingOptions: { insertSpaces: true, tabSize: 2 },
|
|
16
|
-
})
|
|
17
|
-
return applyEdits(text, edits)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export async function patchOpencodeJson(cwd = process.cwd()) {
|
|
21
|
-
const opencodePath = path.join(cwd, 'opencode.jsonc')
|
|
22
|
-
|
|
23
|
-
let text
|
|
24
|
-
if (await fse.pathExists(opencodePath)) {
|
|
25
|
-
text = await fse.readFile(opencodePath, 'utf-8')
|
|
26
|
-
} else {
|
|
27
|
-
text = JSON.stringify({ $schema: 'https://opencode.ai/config.json' }, null, 2)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const errors = []
|
|
31
|
-
const parsed = parse(text, errors)
|
|
32
|
-
if (errors.length > 0 || typeof parsed !== 'object') {
|
|
33
|
-
return { patched: false, reason: 'parse error' }
|
|
34
|
-
}
|
|
35
|
-
|
|
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'
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
parsed?.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
1
|
+
import fse from 'fs-extra'
|
|
2
|
+
import { applyEdits, modify, parse } from 'jsonc-parser'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { success } from '../../utils/exec.js'
|
|
5
|
+
|
|
6
|
+
// The reminder plugin loads every agent ability before work, including
|
|
7
|
+
// user-installed skills outside the pc-* and openspec-* namespaces.
|
|
8
|
+
const SHARED_PERMISSIONS = [
|
|
9
|
+
['question', 'allow'],
|
|
10
|
+
['todowrite', 'allow'],
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
function applyModify(text, jsonPath, value) {
|
|
14
|
+
const edits = modify(text, jsonPath, value, {
|
|
15
|
+
formattingOptions: { insertSpaces: true, tabSize: 2 },
|
|
16
|
+
})
|
|
17
|
+
return applyEdits(text, edits)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function patchOpencodeJson(cwd = process.cwd()) {
|
|
21
|
+
const opencodePath = path.join(cwd, 'opencode.jsonc')
|
|
22
|
+
|
|
23
|
+
let text
|
|
24
|
+
if (await fse.pathExists(opencodePath)) {
|
|
25
|
+
text = await fse.readFile(opencodePath, 'utf-8')
|
|
26
|
+
} else {
|
|
27
|
+
text = JSON.stringify({ $schema: 'https://opencode.ai/config.json' }, null, 2)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const errors = []
|
|
31
|
+
const parsed = parse(text, errors)
|
|
32
|
+
if (errors.length > 0 || typeof parsed !== 'object') {
|
|
33
|
+
return { patched: false, reason: 'parse error' }
|
|
34
|
+
}
|
|
35
|
+
|
|
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'
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
// default_agent pointing at fullstack-engineer is now invalid: it became a
|
|
52
|
+
// subagent when build and plan took over as the only primaries, so opencode
|
|
53
|
+
// has no valid default to open a session with. A deliberate choice of build
|
|
54
|
+
// or plan is left alone; anything else is repointed at plan, so a session
|
|
55
|
+
// starts read-only.
|
|
56
|
+
const currentDefault = parsed?.default_agent
|
|
57
|
+
const needsDefaultAgent = currentDefault !== 'plan' && currentDefault !== 'build'
|
|
58
|
+
const needsSkillPermission = parsed?.permission?.skill !== 'allow'
|
|
59
|
+
const missingSharedPermissions = SHARED_PERMISSIONS.filter(
|
|
60
|
+
([key, value]) => parsed?.permission?.[key] !== value,
|
|
61
|
+
)
|
|
62
|
+
// opencode only scans .opencode/skills/ by default, but we install to .agents/skills/
|
|
63
|
+
const needsSkillsPaths = !(
|
|
64
|
+
Array.isArray(parsed?.skills?.paths) &&
|
|
65
|
+
parsed.skills.paths.includes('.agents/skills')
|
|
66
|
+
)
|
|
67
|
+
const needsCompaction = !(
|
|
68
|
+
parsed?.compaction?.auto === true &&
|
|
69
|
+
parsed?.compaction?.prune === true
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if (!needsAgentOverride && !needsDefaultAgent && !needsSkillPermission && missingSharedPermissions.length === 0 && !needsSkillsPaths && !needsCompaction) {
|
|
73
|
+
return { patched: false }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Apply edits sequentially so offsets stay correct
|
|
77
|
+
if (needsAgentOverride) {
|
|
78
|
+
// undefined removes the key, clearing the disable left by earlier versions.
|
|
79
|
+
if (parsed?.agent?.build?.disable !== undefined) {
|
|
80
|
+
text = applyModify(text, ['agent', 'build', 'disable'], undefined)
|
|
81
|
+
}
|
|
82
|
+
if (parsed?.agent?.plan?.disable !== undefined) {
|
|
83
|
+
text = applyModify(text, ['agent', 'plan', 'disable'], undefined)
|
|
84
|
+
}
|
|
85
|
+
text = applyModify(text, ['agent', 'build', 'mode'], 'primary')
|
|
86
|
+
text = applyModify(text, ['agent', 'plan', 'mode'], 'primary')
|
|
87
|
+
text = applyModify(text, ['agent', 'plan', 'permission', 'edit'], 'deny')
|
|
88
|
+
}
|
|
89
|
+
if (needsDefaultAgent) {
|
|
90
|
+
text = applyModify(text, ['default_agent'], 'plan')
|
|
91
|
+
}
|
|
92
|
+
if (needsSkillPermission) text = applyModify(text, ['permission', 'skill'], 'allow')
|
|
93
|
+
for (const [key, value] of missingSharedPermissions) {
|
|
94
|
+
text = applyModify(text, ['permission', key], value)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Ensure .agents/skills is in skills.paths so opencode discovers installed skills
|
|
98
|
+
if (needsSkillsPaths) {
|
|
99
|
+
const existingPaths = Array.isArray(parsed?.skills?.paths) ? parsed.skills.paths : []
|
|
100
|
+
if (!existingPaths.includes('.agents/skills')) {
|
|
101
|
+
const updatedPaths = [...existingPaths, '.agents/skills']
|
|
102
|
+
text = applyModify(text, ['skills', 'paths'], updatedPaths)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Enable context pruning to reduce token costs in long agent sessions
|
|
107
|
+
if (needsCompaction) {
|
|
108
|
+
text = applyModify(text, ['compaction', 'auto'], true)
|
|
109
|
+
text = applyModify(text, ['compaction', 'prune'], true)
|
|
110
|
+
text = applyModify(text, ['compaction', 'reserved'], 10000)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
await fse.writeFile(opencodePath, text, 'utf-8')
|
|
114
|
+
if (needsAgentOverride) {
|
|
115
|
+
success('Set build/plan as the primary agents in opencode.jsonc (plan is read-only)')
|
|
116
|
+
}
|
|
117
|
+
if (needsDefaultAgent) {
|
|
118
|
+
success(`default_agent -> plan${currentDefault ? ` (was ${currentDefault})` : ''}`)
|
|
119
|
+
}
|
|
120
|
+
if (needsSkillPermission) {
|
|
121
|
+
success('Allowed skill loading in opencode.jsonc')
|
|
122
|
+
}
|
|
123
|
+
if (missingSharedPermissions.length > 0) {
|
|
124
|
+
success('Allowed question/todowrite tools in opencode.jsonc')
|
|
125
|
+
}
|
|
126
|
+
if (needsSkillsPaths) {
|
|
127
|
+
success('Added .agents/skills to skills.paths in opencode.jsonc')
|
|
128
|
+
}
|
|
129
|
+
if (needsCompaction) {
|
|
130
|
+
success('Enabled context pruning (compaction.prune) in opencode.jsonc')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { patched: true }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const OPENCODE_PACKAGE_DEPENDENCIES = {
|
|
137
|
+
'@opencode-ai/plugin': '1.18.19',
|
|
138
|
+
'@opentui/core': '0.5.6',
|
|
139
|
+
'@opentui/solid': '0.5.6',
|
|
140
|
+
'solid-js': '1.9.12',
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function patchOpencodePackage(cwd = process.cwd()) {
|
|
144
|
+
const packagePath = path.join(cwd, '.opencode', 'package.json')
|
|
145
|
+
if (!await fse.pathExists(packagePath)) return { patched: false }
|
|
146
|
+
|
|
147
|
+
const packageJson = await fse.readJson(packagePath).catch(() => null)
|
|
148
|
+
if (!packageJson?.dependencies) return { patched: false }
|
|
149
|
+
|
|
150
|
+
let patched = false
|
|
151
|
+
for (const [name, version] of Object.entries(OPENCODE_PACKAGE_DEPENDENCIES)) {
|
|
152
|
+
if (!(name in packageJson.dependencies) || packageJson.dependencies[name] === version) continue
|
|
153
|
+
packageJson.dependencies[name] = version
|
|
154
|
+
patched = true
|
|
155
|
+
}
|
|
156
|
+
if (!patched) return { patched: false }
|
|
157
|
+
|
|
158
|
+
await fse.writeJson(packagePath, packageJson, { spaces: 2 })
|
|
159
|
+
success('Updated OpenCode plugin dependencies in .opencode/package.json')
|
|
160
|
+
return { patched: true }
|
|
161
|
+
}
|
|
@@ -59,6 +59,10 @@ export async function writeHarnessConfig(data) {
|
|
|
59
59
|
),
|
|
60
60
|
codegraph: !!(optionalTools.codegraph?.optedIn ?? optionalTools.codegraph),
|
|
61
61
|
memory: !!(optionalTools.memory?.optedIn ?? optionalTools.memory),
|
|
62
|
+
// humanizer was missing here, so a selection never survived a metadata
|
|
63
|
+
// write and patchGuardrails always saw it as off, leaving its marker
|
|
64
|
+
// section empty however the project was actually set up.
|
|
65
|
+
humanizer: !!(optionalTools.humanizer?.optedIn ?? optionalTools.humanizer),
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
const payload = {
|