@plainconceptsplatform/agent-harness 2.2.1 → 2.4.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 +23 -12
- package/{src → cli}/commands/join.js +3 -4
- package/{src → cli}/commands/migrate.js +1 -1
- package/{src → cli}/commands/update.js +13 -1
- package/{src → cli}/commands/wizard.js +1 -1
- package/{src → cli}/index.js +1 -1
- package/{src → cli}/presets/agents-content.json +4 -4
- package/cli/presets/browser.json +10 -0
- package/{src → cli}/presets/clean.json +21 -21
- package/{src → cli}/presets/optimization.json +37 -37
- package/{src → cli}/presets/platforms.json +76 -76
- package/{src → cli}/presets/quota.json +16 -16
- package/{src → cli}/presets/source.json +23 -23
- package/cli/steps/browser/index.js +48 -0
- package/{src → cli}/steps/clean/index.js +120 -120
- package/{src → cli}/steps/copy/agents.js +119 -119
- package/{src → cli}/steps/copy/commands.js +95 -95
- package/{src → cli}/steps/copy/fullstack-engineer.js +5 -1
- package/{src → cli}/steps/copy/index.js +1 -1
- package/{src → cli}/steps/copy/opencode-json.js +50 -1
- package/{src → cli}/steps/copy/skills.js +2 -2
- package/{src → cli}/steps/metadata/index.js +4 -0
- package/{src → cli}/steps/models/format.js +88 -88
- package/{src → cli}/steps/models/index.js +64 -64
- package/{src → cli}/steps/openspec/index.js +136 -136
- package/{src → cli}/steps/optimization/codegraph.js +127 -127
- package/cli/steps/optimization/detect.js +66 -0
- package/{src → cli}/steps/optimization/humanizer.js +17 -17
- package/{src → cli}/steps/optimization/index.js +163 -163
- package/{src → cli}/steps/optimization/memory.js +88 -88
- package/{src → cli}/steps/optimization/patch-guardrails.js +109 -109
- package/{src → cli}/steps/optimization/quota.js +119 -119
- package/{src → cli}/steps/optimization/simple-english.js +17 -17
- package/{src → cli}/steps/optimization/skills-lock.js +30 -30
- package/{src → cli}/steps/platform/index.js +109 -109
- package/{src → cli}/steps/source/index.js +123 -123
- package/cli/utils/agent-color.js +111 -0
- package/{src → cli}/utils/exec-spinner.js +47 -47
- package/{src → cli}/utils/exec.js +134 -134
- package/{src → cli}/utils/models-pricing.js +42 -42
- package/{src → cli}/utils/paths.js +1 -1
- package/{src → cli}/utils/process.js +3 -3
- package/{src → cli}/utils/terminal.js +6 -6
- package/harness/.agents/skills/browser-automation/SKILL.md +72 -0
- package/{src/content → harness}/.agents/skills/pc-make-engineer/SKILL.md +1 -1
- package/{src/content → harness}/.agents/skills/pc-make-engineer/template.md +1 -2
- package/{src/content → harness}/.agents/skills/pc-plan-archive/SKILL.md +3 -0
- package/{src/content → harness}/.agents/skills/pc-plan-explore/SKILL.md +3 -0
- package/{src/content → harness}/.agents/skills/pc-plan-goal/SKILL.md +5 -16
- package/{src/content → harness}/.agents/skills/pc-plan-goal/branching.md +1 -1
- package/{src/content → harness}/.agents/skills/pc-plan-goal/output.md +5 -8
- package/{src/content → harness}/.agents/skills/pc-plan-story/SKILL.md +3 -0
- package/{src/content → harness}/.agents/skills/pc-userstory-browser/SKILL.md +136 -132
- package/{src/content → harness}/.opencode/package.json +9 -10
- package/{src/content → harness}/.opencode/plugins/pc-subagent-tiers.js +91 -4
- package/{src/content → harness}/ARCHITECTURE.md +16 -16
- package/{src/content → harness}/DESIGN.md +16 -16
- package/{src/content → harness}/opencode.jsonc +11 -3
- package/{src/content → harness}/openspec/config.yaml +20 -20
- package/{src/content → harness}/skills-lock.json +17 -17
- package/package.json +7 -7
- package/src/content/.agents/skills/browser-automation/SKILL.md +0 -66
- package/src/presets/browser.json +0 -22
- package/src/steps/browser/index.js +0 -91
- /package/{src → cli}/commands/shared.js +0 -0
- /package/{src → cli}/commands/single.js +0 -0
- /package/{src → cli}/fragments/archive/az.md +0 -0
- /package/{src → cli}/fragments/archive/gh.md +0 -0
- /package/{src → cli}/fragments/archive/gl.md +0 -0
- /package/{src → cli}/fragments/archive/none.md +0 -0
- /package/{src → cli}/fragments/guardrails/codegraph.md +0 -0
- /package/{src → cli}/fragments/guardrails/humanizer.md +0 -0
- /package/{src → cli}/fragments/guardrails/memory.md +0 -0
- /package/{src → cli}/fragments/guardrails/rtk.md +0 -0
- /package/{src → cli}/fragments/guardrails/simple-english.md +0 -0
- /package/{src → cli}/fragments/ops-backlog/az.md +0 -0
- /package/{src → cli}/fragments/ops-backlog/gh.md +0 -0
- /package/{src → cli}/fragments/ops-backlog/jira.md +0 -0
- /package/{src → cli}/fragments/ops-evidence/az.md +0 -0
- /package/{src → cli}/fragments/ops-evidence/gh.md +0 -0
- /package/{src → cli}/fragments/ops-evidence/jira.md +0 -0
- /package/{src → cli}/fragments/ops-review/az.md +0 -0
- /package/{src → cli}/fragments/ops-review/gh.md +0 -0
- /package/{src → cli}/fragments/ops-review/gl.md +0 -0
- /package/{src → cli}/fragments/ops-ship/az.md +0 -0
- /package/{src → cli}/fragments/ops-ship/gh.md +0 -0
- /package/{src → cli}/fragments/ops-ship/gl.md +0 -0
- /package/{src → cli}/presets/models.json +0 -0
- /package/{src → cli}/presets/openspec.json +0 -0
- /package/{src → cli}/steps/models/write.js +0 -0
- /package/{src → cli}/utils/copy.js +0 -0
- /package/{src → cli}/utils/legacy-check.js +0 -0
- /package/{src → cli}/utils/models-cache.js +0 -0
- /package/{src → cli}/utils/update-manifest.js +0 -0
- /package/{src/content → harness}/.agents/skills/pc-guardrails-generic/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-guardrails-project/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-architecture/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-architecture/structure-template.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-design/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-engineer/signal-mapping.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-evidence-scaffold/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-evidence-scaffold/evidence-contract.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-guardrails/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-guardrails/category-reference.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-merge-risk-assess/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-merge-risk-assess/category-reference.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-user-model/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-ops-evidence/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-ops-ship/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-plan-apply/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-plan-apply/simple-mode.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-plan-goal/failure-policy.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-plan-goal/output-mode.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-plan-propose/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-plan-propose/task-annotation.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-plan-quick/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-repo-audit/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-repo-help/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-repo-initialize/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-repo-onboard/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-repo-verify/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-userstory-az/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-userstory-gh/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-userstory-jira/SKILL.md +0 -0
- /package/{src/content → harness}/.opencode/_gitignore +0 -0
- /package/{src/content → harness}/.opencode/commands/init.md +0 -0
- /package/{src/content → harness}/.opencode/commands/make-architecture.md +0 -0
- /package/{src/content → harness}/.opencode/commands/make-design.md +0 -0
- /package/{src/content → harness}/.opencode/commands/make-engineer.md +0 -0
- /package/{src/content → harness}/.opencode/commands/make-evidence-scaffold.md +0 -0
- /package/{src/content → harness}/.opencode/commands/make-guardrails.md +0 -0
- /package/{src/content → harness}/.opencode/commands/make-user-model.md +0 -0
- /package/{src/content → harness}/.opencode/commands/ops-backlog.md +0 -0
- /package/{src/content → harness}/.opencode/commands/ops-evidence.md +0 -0
- /package/{src/content → harness}/.opencode/commands/ops-review.md +0 -0
- /package/{src/content → harness}/.opencode/commands/ops-ship.md +0 -0
- /package/{src/content → harness}/.opencode/commands/plan-apply.md +0 -0
- /package/{src/content → harness}/.opencode/commands/plan-archive.md +0 -0
- /package/{src/content → harness}/.opencode/commands/plan-explore.md +0 -0
- /package/{src/content → harness}/.opencode/commands/plan-goal.md +0 -0
- /package/{src/content → harness}/.opencode/commands/plan-propose.md +0 -0
- /package/{src/content → harness}/.opencode/commands/plan-quick.md +0 -0
- /package/{src/content → harness}/.opencode/commands/plan-story.md +0 -0
- /package/{src/content → harness}/.opencode/commands/repo-audit.md +0 -0
- /package/{src/content → harness}/.opencode/commands/repo-help.md +0 -0
- /package/{src/content → harness}/.opencode/commands/repo-initialize.md +0 -0
- /package/{src/content → harness}/.opencode/commands/repo-onboard.md +0 -0
- /package/{src/content → harness}/.opencode/commands/repo-verify.md +0 -0
- /package/{src/content → harness}/.opencode/plugins/pc-subagent-monitor.js +0 -0
- /package/{src/content → harness}/.opencode/plugins/pc-system-reminders.js +0 -0
- /package/{src/content → harness}/.opencode/tui/pc-subagents.tsx +0 -0
- /package/{src/content → harness}/.opencode/tui.json +0 -0
- /package/{src/content → harness}/AGENTS.md +0 -0
- /package/{src/content → harness}/openspec/changes/archive/.gitkeep +0 -0
- /package/{src/content → harness}/openspec/specs/.gitkeep +0 -0
|
@@ -1,95 +1,95 @@
|
|
|
1
|
-
import fse from 'fs-extra'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import { fileURLToPath } from 'url'
|
|
4
|
-
import { success } from '../../utils/exec.js'
|
|
5
|
-
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
7
|
-
|
|
8
|
-
const _archive = {
|
|
9
|
-
azure: await fse.readFile(path.resolve(__dirname, '../../fragments/archive/az.md'), 'utf-8'),
|
|
10
|
-
github: await fse.readFile(path.resolve(__dirname, '../../fragments/archive/gh.md'), 'utf-8'),
|
|
11
|
-
gitlab: await fse.readFile(path.resolve(__dirname, '../../fragments/archive/gl.md'), 'utf-8'),
|
|
12
|
-
none: await fse.readFile(path.resolve(__dirname, '../../fragments/archive/none.md'), 'utf-8'),
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const _ship = {
|
|
16
|
-
github: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-ship/gh.md'), 'utf-8'),
|
|
17
|
-
azure: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-ship/az.md'), 'utf-8'),
|
|
18
|
-
gitlab: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-ship/gl.md'), 'utf-8'),
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const _review = {
|
|
22
|
-
github: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-review/gh.md'), 'utf-8'),
|
|
23
|
-
azure: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-review/az.md'), 'utf-8'),
|
|
24
|
-
gitlab: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-review/gl.md'), 'utf-8'),
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const _backlog = {
|
|
28
|
-
github: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-backlog/gh.md'), 'utf-8'),
|
|
29
|
-
azure: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-backlog/az.md'), 'utf-8'),
|
|
30
|
-
jira: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-backlog/jira.md'), 'utf-8'),
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Evidence comments target the backlog platform (where the issue/work-item lives).
|
|
34
|
-
const _evidence = {
|
|
35
|
-
github: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-evidence/gh.md'), 'utf-8'),
|
|
36
|
-
azure: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-evidence/az.md'), 'utf-8'),
|
|
37
|
-
jira: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-evidence/jira.md'), 'utf-8'),
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// relativePath is POSIX-style relative to the project root; commands live in
|
|
41
|
-
// .opencode/commands/, skill-backed procedures (plan-archive, ops-ship) in
|
|
42
|
-
// .agents/skills/<name>/SKILL.md.
|
|
43
|
-
function patchFile(relativePath, startMarker, endMarker, content, platform, cwd = process.cwd()) {
|
|
44
|
-
const targetPath = path.join(cwd, ...relativePath.split('/'))
|
|
45
|
-
if (!fse.pathExistsSync(targetPath)) return
|
|
46
|
-
|
|
47
|
-
let fileContent = fse.readFileSync(targetPath, 'utf-8')
|
|
48
|
-
if (!fileContent.includes(startMarker) || !fileContent.includes(endMarker)) return
|
|
49
|
-
|
|
50
|
-
const pattern = new RegExp(`${startMarker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${endMarker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`)
|
|
51
|
-
// Replacer function, not a replacement string: the injected fragments contain
|
|
52
|
-
// shell quoting like $'...' and a string replacement expands $' as "everything
|
|
53
|
-
// after the match", silently truncating the command. That is how the ops-ship
|
|
54
|
-
// screenshot comment lost its entire --body argument.
|
|
55
|
-
fileContent = fileContent.replace(pattern, () => `${startMarker}\n${content.trim()}\n${endMarker}`)
|
|
56
|
-
fse.writeFileSync(targetPath, `${fileContent.replace(/\s*$/, '')}\n`, 'utf-8')
|
|
57
|
-
success(`${relativePath} content injected for platform: ${platform}`)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export async function patchArchiveCommand(platform, cwd = process.cwd()) {
|
|
61
|
-
const repoPlatform = typeof platform === 'object' ? (platform.repoPlatform ?? platform.backlogPlatform ?? 'github') : platform
|
|
62
|
-
const replacement = _archive[repoPlatform]
|
|
63
|
-
if (!replacement) return
|
|
64
|
-
patchFile('.agents/skills/pc-plan-archive/SKILL.md', '<!-- PC-PLATFORM-ARCHIVE-START -->', '<!-- PC-PLATFORM-ARCHIVE-END -->', replacement, repoPlatform, cwd)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export async function patchOpsShip(platform, cwd = process.cwd()) {
|
|
68
|
-
const repoPlatform = typeof platform === 'object' ? (platform.repoPlatform ?? 'github') : platform
|
|
69
|
-
const replacement = _ship[repoPlatform]
|
|
70
|
-
if (!replacement) return
|
|
71
|
-
patchFile('.agents/skills/pc-ops-ship/SKILL.md', '<!-- PC-PLATFORM-SHIP-START -->', '<!-- PC-PLATFORM-SHIP-END -->', replacement, repoPlatform, cwd)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export async function patchOpsReview(platform, cwd = process.cwd()) {
|
|
75
|
-
const repoPlatform = typeof platform === 'object' ? (platform.repoPlatform ?? 'github') : platform
|
|
76
|
-
const replacement = _review[repoPlatform]
|
|
77
|
-
if (!replacement) return
|
|
78
|
-
patchFile('.opencode/commands/ops-review.md', '<!-- PC-PLATFORM-REVIEW-START -->', '<!-- PC-PLATFORM-REVIEW-END -->', replacement, repoPlatform, cwd)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export async function patchOpsBacklog(platform, cwd = process.cwd()) {
|
|
82
|
-
const backlogPlatform = typeof platform === 'object' ? (platform.backlogPlatform ?? 'github') : platform
|
|
83
|
-
const replacement = _backlog[backlogPlatform]
|
|
84
|
-
if (!replacement) return
|
|
85
|
-
patchFile('.opencode/commands/ops-backlog.md', '<!-- PC-PLATFORM-BACKLOG-START -->', '<!-- PC-PLATFORM-BACKLOG-END -->', replacement, backlogPlatform, cwd)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export async function patchOpsEvidence(platform, cwd = process.cwd()) {
|
|
89
|
-
const backlogPlatform = typeof platform === 'object' ? (platform.backlogPlatform ?? 'github') : platform
|
|
90
|
-
const replacement = _evidence[backlogPlatform]
|
|
91
|
-
// browser/none have no CLI to post with: leave the markers empty (the skill skips commenting).
|
|
92
|
-
if (!replacement) return
|
|
93
|
-
patchFile('.agents/skills/pc-ops-evidence/SKILL.md', '<!-- PC-PLATFORM-EVIDENCE-START -->', '<!-- PC-PLATFORM-EVIDENCE-END -->', replacement, backlogPlatform, cwd)
|
|
94
|
-
}
|
|
95
|
-
|
|
1
|
+
import fse from 'fs-extra'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { fileURLToPath } from 'url'
|
|
4
|
+
import { success } from '../../utils/exec.js'
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
|
|
8
|
+
const _archive = {
|
|
9
|
+
azure: await fse.readFile(path.resolve(__dirname, '../../fragments/archive/az.md'), 'utf-8'),
|
|
10
|
+
github: await fse.readFile(path.resolve(__dirname, '../../fragments/archive/gh.md'), 'utf-8'),
|
|
11
|
+
gitlab: await fse.readFile(path.resolve(__dirname, '../../fragments/archive/gl.md'), 'utf-8'),
|
|
12
|
+
none: await fse.readFile(path.resolve(__dirname, '../../fragments/archive/none.md'), 'utf-8'),
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const _ship = {
|
|
16
|
+
github: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-ship/gh.md'), 'utf-8'),
|
|
17
|
+
azure: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-ship/az.md'), 'utf-8'),
|
|
18
|
+
gitlab: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-ship/gl.md'), 'utf-8'),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const _review = {
|
|
22
|
+
github: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-review/gh.md'), 'utf-8'),
|
|
23
|
+
azure: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-review/az.md'), 'utf-8'),
|
|
24
|
+
gitlab: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-review/gl.md'), 'utf-8'),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const _backlog = {
|
|
28
|
+
github: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-backlog/gh.md'), 'utf-8'),
|
|
29
|
+
azure: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-backlog/az.md'), 'utf-8'),
|
|
30
|
+
jira: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-backlog/jira.md'), 'utf-8'),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Evidence comments target the backlog platform (where the issue/work-item lives).
|
|
34
|
+
const _evidence = {
|
|
35
|
+
github: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-evidence/gh.md'), 'utf-8'),
|
|
36
|
+
azure: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-evidence/az.md'), 'utf-8'),
|
|
37
|
+
jira: await fse.readFile(path.resolve(__dirname, '../../fragments/ops-evidence/jira.md'), 'utf-8'),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// relativePath is POSIX-style relative to the project root; commands live in
|
|
41
|
+
// .opencode/commands/, skill-backed procedures (plan-archive, ops-ship) in
|
|
42
|
+
// .agents/skills/<name>/SKILL.md.
|
|
43
|
+
function patchFile(relativePath, startMarker, endMarker, content, platform, cwd = process.cwd()) {
|
|
44
|
+
const targetPath = path.join(cwd, ...relativePath.split('/'))
|
|
45
|
+
if (!fse.pathExistsSync(targetPath)) return
|
|
46
|
+
|
|
47
|
+
let fileContent = fse.readFileSync(targetPath, 'utf-8')
|
|
48
|
+
if (!fileContent.includes(startMarker) || !fileContent.includes(endMarker)) return
|
|
49
|
+
|
|
50
|
+
const pattern = new RegExp(`${startMarker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${endMarker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`)
|
|
51
|
+
// Replacer function, not a replacement string: the injected fragments contain
|
|
52
|
+
// shell quoting like $'...' and a string replacement expands $' as "everything
|
|
53
|
+
// after the match", silently truncating the command. That is how the ops-ship
|
|
54
|
+
// screenshot comment lost its entire --body argument.
|
|
55
|
+
fileContent = fileContent.replace(pattern, () => `${startMarker}\n${content.trim()}\n${endMarker}`)
|
|
56
|
+
fse.writeFileSync(targetPath, `${fileContent.replace(/\s*$/, '')}\n`, 'utf-8')
|
|
57
|
+
success(`${relativePath} content injected for platform: ${platform}`)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function patchArchiveCommand(platform, cwd = process.cwd()) {
|
|
61
|
+
const repoPlatform = typeof platform === 'object' ? (platform.repoPlatform ?? platform.backlogPlatform ?? 'github') : platform
|
|
62
|
+
const replacement = _archive[repoPlatform]
|
|
63
|
+
if (!replacement) return
|
|
64
|
+
patchFile('.agents/skills/pc-plan-archive/SKILL.md', '<!-- PC-PLATFORM-ARCHIVE-START -->', '<!-- PC-PLATFORM-ARCHIVE-END -->', replacement, repoPlatform, cwd)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function patchOpsShip(platform, cwd = process.cwd()) {
|
|
68
|
+
const repoPlatform = typeof platform === 'object' ? (platform.repoPlatform ?? 'github') : platform
|
|
69
|
+
const replacement = _ship[repoPlatform]
|
|
70
|
+
if (!replacement) return
|
|
71
|
+
patchFile('.agents/skills/pc-ops-ship/SKILL.md', '<!-- PC-PLATFORM-SHIP-START -->', '<!-- PC-PLATFORM-SHIP-END -->', replacement, repoPlatform, cwd)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function patchOpsReview(platform, cwd = process.cwd()) {
|
|
75
|
+
const repoPlatform = typeof platform === 'object' ? (platform.repoPlatform ?? 'github') : platform
|
|
76
|
+
const replacement = _review[repoPlatform]
|
|
77
|
+
if (!replacement) return
|
|
78
|
+
patchFile('.opencode/commands/ops-review.md', '<!-- PC-PLATFORM-REVIEW-START -->', '<!-- PC-PLATFORM-REVIEW-END -->', replacement, repoPlatform, cwd)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function patchOpsBacklog(platform, cwd = process.cwd()) {
|
|
82
|
+
const backlogPlatform = typeof platform === 'object' ? (platform.backlogPlatform ?? 'github') : platform
|
|
83
|
+
const replacement = _backlog[backlogPlatform]
|
|
84
|
+
if (!replacement) return
|
|
85
|
+
patchFile('.opencode/commands/ops-backlog.md', '<!-- PC-PLATFORM-BACKLOG-START -->', '<!-- PC-PLATFORM-BACKLOG-END -->', replacement, backlogPlatform, cwd)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function patchOpsEvidence(platform, cwd = process.cwd()) {
|
|
89
|
+
const backlogPlatform = typeof platform === 'object' ? (platform.backlogPlatform ?? 'github') : platform
|
|
90
|
+
const replacement = _evidence[backlogPlatform]
|
|
91
|
+
// browser/none have no CLI to post with: leave the markers empty (the skill skips commenting).
|
|
92
|
+
if (!replacement) return
|
|
93
|
+
patchFile('.agents/skills/pc-ops-evidence/SKILL.md', '<!-- PC-PLATFORM-EVIDENCE-START -->', '<!-- PC-PLATFORM-EVIDENCE-END -->', replacement, backlogPlatform, cwd)
|
|
94
|
+
}
|
|
95
|
+
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fse from 'fs-extra'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import { success } from '../../utils/exec.js'
|
|
4
|
+
import { agentColor, yamlColor } from '../../utils/agent-color.js'
|
|
4
5
|
|
|
5
6
|
const FULLSTACK_FILE = 'fullstack-engineer.md'
|
|
6
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.'
|
|
@@ -59,7 +60,10 @@ export async function generateFullstackEngineer({ cwd = process.cwd(), updateMod
|
|
|
59
60
|
// subagent, not primary: build.md and plan.md are the primaries, and
|
|
60
61
|
// pc-subagent-tiers generates both from this file on every startup.
|
|
61
62
|
'mode: subagent',
|
|
62
|
-
|
|
63
|
+
// warning is reserved for plan, so fullstack takes its name-derived hex
|
|
64
|
+
// like every other subagent.
|
|
65
|
+
// Quoted: a bare #hex is a YAML comment, which opencode rejects.
|
|
66
|
+
`color: ${yamlColor(agentColor('fullstack-engineer'))}`,
|
|
63
67
|
'permission:',
|
|
64
68
|
' edit: allow',
|
|
65
69
|
' bash: allow',
|
|
@@ -11,7 +11,7 @@ import { generateFullstackEngineer, removeLegacyStartupDirectives } from "./full
|
|
|
11
11
|
import { patchOpencodeJson, patchOpencodePackage } from "./opencode-json.js"
|
|
12
12
|
|
|
13
13
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
14
|
-
const CONTENT_DIR = path.resolve(__dirname, "
|
|
14
|
+
const CONTENT_DIR = path.resolve(__dirname, "../../../harness")
|
|
15
15
|
|
|
16
16
|
export async function copyContentStep(platform, ctx = {}) {
|
|
17
17
|
header("Step 5, Copying agent-harness files")
|
|
@@ -10,6 +10,15 @@ const SHARED_PERMISSIONS = [
|
|
|
10
10
|
['todowrite', 'allow'],
|
|
11
11
|
]
|
|
12
12
|
|
|
13
|
+
// agent-browser replaced the @different-ai/opencode-browser plugin; any entry
|
|
14
|
+
// with that prefix in the plugin array is stale and gets stripped on update.
|
|
15
|
+
const STALE_BROWSER_PLUGIN_PREFIX = '@different-ai/opencode-browser'
|
|
16
|
+
const AGENT_BROWSER_MCP = {
|
|
17
|
+
type: 'local',
|
|
18
|
+
command: ['agent-browser', 'mcp', '--tools', 'core'],
|
|
19
|
+
enabled: true,
|
|
20
|
+
}
|
|
21
|
+
|
|
13
22
|
function applyModify(text, jsonPath, value) {
|
|
14
23
|
const edits = modify(text, jsonPath, value, {
|
|
15
24
|
formattingOptions: { insertSpaces: true, tabSize: 2 },
|
|
@@ -47,6 +56,14 @@ export async function patchOpencodeJson(cwd = process.cwd()) {
|
|
|
47
56
|
parsed?.agent?.plan?.mode === 'primary' &&
|
|
48
57
|
parsed?.agent?.plan?.permission?.edit === 'deny'
|
|
49
58
|
)
|
|
59
|
+
|
|
60
|
+
// default_agent pointing at fullstack-engineer is now invalid: it became a
|
|
61
|
+
// subagent when build and plan took over as the only primaries, so opencode
|
|
62
|
+
// has no valid default to open a session with. A deliberate choice of build
|
|
63
|
+
// or plan is left alone; anything else is repointed at plan, so a session
|
|
64
|
+
// starts read-only.
|
|
65
|
+
const currentDefault = parsed?.default_agent
|
|
66
|
+
const needsDefaultAgent = currentDefault !== 'plan' && currentDefault !== 'build'
|
|
50
67
|
const needsSkillPermission = parsed?.permission?.skill !== 'allow'
|
|
51
68
|
const missingSharedPermissions = SHARED_PERMISSIONS.filter(
|
|
52
69
|
([key, value]) => parsed?.permission?.[key] !== value,
|
|
@@ -60,8 +77,11 @@ export async function patchOpencodeJson(cwd = process.cwd()) {
|
|
|
60
77
|
parsed?.compaction?.auto === true &&
|
|
61
78
|
parsed?.compaction?.prune === true
|
|
62
79
|
)
|
|
80
|
+
const staleBrowserPlugins = (Array.isArray(parsed?.plugin) ? parsed.plugin : [])
|
|
81
|
+
.filter(entry => typeof entry === 'string' && entry.startsWith(STALE_BROWSER_PLUGIN_PREFIX))
|
|
82
|
+
const needsBrowserMcp = JSON.stringify(parsed?.mcp?.['agent-browser']) !== JSON.stringify(AGENT_BROWSER_MCP)
|
|
63
83
|
|
|
64
|
-
if (!needsAgentOverride && !needsSkillPermission && missingSharedPermissions.length === 0 && !needsSkillsPaths && !needsCompaction) {
|
|
84
|
+
if (!needsAgentOverride && !needsDefaultAgent && !needsSkillPermission && missingSharedPermissions.length === 0 && !needsSkillsPaths && !needsCompaction && staleBrowserPlugins.length === 0 && !needsBrowserMcp) {
|
|
65
85
|
return { patched: false }
|
|
66
86
|
}
|
|
67
87
|
|
|
@@ -78,6 +98,9 @@ export async function patchOpencodeJson(cwd = process.cwd()) {
|
|
|
78
98
|
text = applyModify(text, ['agent', 'plan', 'mode'], 'primary')
|
|
79
99
|
text = applyModify(text, ['agent', 'plan', 'permission', 'edit'], 'deny')
|
|
80
100
|
}
|
|
101
|
+
if (needsDefaultAgent) {
|
|
102
|
+
text = applyModify(text, ['default_agent'], 'plan')
|
|
103
|
+
}
|
|
81
104
|
if (needsSkillPermission) text = applyModify(text, ['permission', 'skill'], 'allow')
|
|
82
105
|
for (const [key, value] of missingSharedPermissions) {
|
|
83
106
|
text = applyModify(text, ['permission', key], value)
|
|
@@ -99,10 +122,22 @@ export async function patchOpencodeJson(cwd = process.cwd()) {
|
|
|
99
122
|
text = applyModify(text, ['compaction', 'reserved'], 10000)
|
|
100
123
|
}
|
|
101
124
|
|
|
125
|
+
// Swap the retired opencode-browser plugin for the agent-browser MCP server
|
|
126
|
+
if (staleBrowserPlugins.length > 0) {
|
|
127
|
+
const keptPlugins = parsed.plugin.filter(entry => !staleBrowserPlugins.includes(entry))
|
|
128
|
+
text = applyModify(text, ['plugin'], keptPlugins)
|
|
129
|
+
}
|
|
130
|
+
if (needsBrowserMcp) {
|
|
131
|
+
text = applyModify(text, ['mcp', 'agent-browser'], AGENT_BROWSER_MCP)
|
|
132
|
+
}
|
|
133
|
+
|
|
102
134
|
await fse.writeFile(opencodePath, text, 'utf-8')
|
|
103
135
|
if (needsAgentOverride) {
|
|
104
136
|
success('Set build/plan as the primary agents in opencode.jsonc (plan is read-only)')
|
|
105
137
|
}
|
|
138
|
+
if (needsDefaultAgent) {
|
|
139
|
+
success(`default_agent -> plan${currentDefault ? ` (was ${currentDefault})` : ''}`)
|
|
140
|
+
}
|
|
106
141
|
if (needsSkillPermission) {
|
|
107
142
|
success('Allowed skill loading in opencode.jsonc')
|
|
108
143
|
}
|
|
@@ -115,6 +150,12 @@ export async function patchOpencodeJson(cwd = process.cwd()) {
|
|
|
115
150
|
if (needsCompaction) {
|
|
116
151
|
success('Enabled context pruning (compaction.prune) in opencode.jsonc')
|
|
117
152
|
}
|
|
153
|
+
if (staleBrowserPlugins.length > 0) {
|
|
154
|
+
success(`Removed ${staleBrowserPlugins.length} stale opencode-browser plugin entr${staleBrowserPlugins.length === 1 ? 'y' : 'ies'} in opencode.jsonc`)
|
|
155
|
+
}
|
|
156
|
+
if (needsBrowserMcp) {
|
|
157
|
+
success('Configured agent-browser MCP server in opencode.jsonc')
|
|
158
|
+
}
|
|
118
159
|
|
|
119
160
|
return { patched: true }
|
|
120
161
|
}
|
|
@@ -126,6 +167,9 @@ const OPENCODE_PACKAGE_DEPENDENCIES = {
|
|
|
126
167
|
'solid-js': '1.9.12',
|
|
127
168
|
}
|
|
128
169
|
|
|
170
|
+
// agent-browser replaced this plugin; remove it from consumer installs on update
|
|
171
|
+
const STALE_PACKAGE_DEPENDENCIES = ['@different-ai/opencode-browser']
|
|
172
|
+
|
|
129
173
|
export async function patchOpencodePackage(cwd = process.cwd()) {
|
|
130
174
|
const packagePath = path.join(cwd, '.opencode', 'package.json')
|
|
131
175
|
if (!await fse.pathExists(packagePath)) return { patched: false }
|
|
@@ -139,6 +183,11 @@ export async function patchOpencodePackage(cwd = process.cwd()) {
|
|
|
139
183
|
packageJson.dependencies[name] = version
|
|
140
184
|
patched = true
|
|
141
185
|
}
|
|
186
|
+
for (const name of STALE_PACKAGE_DEPENDENCIES) {
|
|
187
|
+
if (!(name in packageJson.dependencies)) continue
|
|
188
|
+
delete packageJson.dependencies[name]
|
|
189
|
+
patched = true
|
|
190
|
+
}
|
|
142
191
|
if (!patched) return { patched: false }
|
|
143
192
|
|
|
144
193
|
await fse.writeJson(packagePath, packageJson, { spaces: 2 })
|
|
@@ -5,8 +5,8 @@ import { info, success } from '../../utils/exec.js'
|
|
|
5
5
|
import { canUpdateManagedFile, readUpdateManifest, recordManagedFile, writeUpdateManifest } from '../../utils/update-manifest.js'
|
|
6
6
|
|
|
7
7
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
8
|
-
const CONTENT_SKILLS_DIR = path.resolve(__dirname, '
|
|
9
|
-
const CONTENT_SKILLS_LOCK = path.resolve(__dirname, '
|
|
8
|
+
const CONTENT_SKILLS_DIR = path.resolve(__dirname, '../../../harness/.agents/skills')
|
|
9
|
+
const CONTENT_SKILLS_LOCK = path.resolve(__dirname, '../../../harness/skills-lock.json')
|
|
10
10
|
|
|
11
11
|
// Userstory skills parse backlog work items: selected by backlogPlatform only.
|
|
12
12
|
// Mixing the two axes here installs the wrong variant on mixed setups, because
|
|
@@ -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 = {
|
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
import { search, select } from '@inquirer/prompts'
|
|
2
|
-
import fse from 'fs-extra'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import { fileURLToPath } from 'url'
|
|
5
|
-
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const MODELS_PRESET_PATH = path.resolve(__dirname, '../../presets/models.json');
|
|
8
|
-
const modelsPreset = await fse.readJson(MODELS_PRESET_PATH);
|
|
9
|
-
|
|
10
|
-
function costTier(input) {
|
|
11
|
-
if (input === undefined || input === null) return '';
|
|
12
|
-
const tier = modelsPreset.costTiers.find(t => t.max === undefined || input <= t.max);
|
|
13
|
-
return tier ? ` ${tier.label}` : '';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function costTierDisplay(cost, canonicalCost) {
|
|
17
|
-
return costTier(canonicalCost !== undefined ? canonicalCost : cost);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function formatPrice(price) {
|
|
21
|
-
if (price === undefined || price === null) return '?';
|
|
22
|
-
if (price === 0) return '$0 (subscription)';
|
|
23
|
-
return `$${price}/M`;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function buildDisplayModels(rawModels) {
|
|
27
|
-
return rawModels.map(m => {
|
|
28
|
-
const priceStr = formatPrice(m.cost);
|
|
29
|
-
const canonicalNote = m.canonicalCost !== undefined
|
|
30
|
-
? ` · official price: ${formatPrice(m.canonicalCost)}/M`
|
|
31
|
-
: '';
|
|
32
|
-
const context = m.context ? `${m.context / 1000}k` : '?';
|
|
33
|
-
return {
|
|
34
|
-
...m,
|
|
35
|
-
label: `${m.name}${costTierDisplay(m.cost, m.canonicalCost)}, ${m.id}`,
|
|
36
|
-
description: `${priceStr}${canonicalNote} · context: ${context}`,
|
|
37
|
-
};
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function buildModelChoices(input, models) {
|
|
42
|
-
const q = (input || '').toLowerCase();
|
|
43
|
-
const filtered = q
|
|
44
|
-
? models.filter(m =>
|
|
45
|
-
m.label.toLowerCase().includes(q) ||
|
|
46
|
-
m.id.toLowerCase().includes(q)
|
|
47
|
-
)
|
|
48
|
-
: models;
|
|
49
|
-
|
|
50
|
-
return [
|
|
51
|
-
{
|
|
52
|
-
name: 'None',
|
|
53
|
-
value: null,
|
|
54
|
-
description: 'Leave this model unset',
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
name: '← Change provider',
|
|
58
|
-
value: '__back__',
|
|
59
|
-
description: 'Go back to provider selection',
|
|
60
|
-
},
|
|
61
|
-
...filtered.slice(0, 50).map(m => ({
|
|
62
|
-
name: m.label,
|
|
63
|
-
value: m.id,
|
|
64
|
-
description: m.description,
|
|
65
|
-
})),
|
|
66
|
-
];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function pickModel(message, models) {
|
|
70
|
-
return search({
|
|
71
|
-
message,
|
|
72
|
-
source: input => buildModelChoices(input, models),
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function filterModelsByProvider(models, provider) {
|
|
77
|
-
if (!provider) return models
|
|
78
|
-
return models.filter(m => m.id.startsWith(`${provider}/`))
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function pickProvider(message) {
|
|
82
|
-
return select({
|
|
83
|
-
message,
|
|
84
|
-
choices: modelsPreset.providers.map(p => ({ value: p.value, name: p.name })),
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export { modelsPreset };
|
|
1
|
+
import { search, select } from '@inquirer/prompts'
|
|
2
|
+
import fse from 'fs-extra'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const MODELS_PRESET_PATH = path.resolve(__dirname, '../../presets/models.json');
|
|
8
|
+
const modelsPreset = await fse.readJson(MODELS_PRESET_PATH);
|
|
9
|
+
|
|
10
|
+
function costTier(input) {
|
|
11
|
+
if (input === undefined || input === null) return '';
|
|
12
|
+
const tier = modelsPreset.costTiers.find(t => t.max === undefined || input <= t.max);
|
|
13
|
+
return tier ? ` ${tier.label}` : '';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function costTierDisplay(cost, canonicalCost) {
|
|
17
|
+
return costTier(canonicalCost !== undefined ? canonicalCost : cost);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function formatPrice(price) {
|
|
21
|
+
if (price === undefined || price === null) return '?';
|
|
22
|
+
if (price === 0) return '$0 (subscription)';
|
|
23
|
+
return `$${price}/M`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function buildDisplayModels(rawModels) {
|
|
27
|
+
return rawModels.map(m => {
|
|
28
|
+
const priceStr = formatPrice(m.cost);
|
|
29
|
+
const canonicalNote = m.canonicalCost !== undefined
|
|
30
|
+
? ` · official price: ${formatPrice(m.canonicalCost)}/M`
|
|
31
|
+
: '';
|
|
32
|
+
const context = m.context ? `${m.context / 1000}k` : '?';
|
|
33
|
+
return {
|
|
34
|
+
...m,
|
|
35
|
+
label: `${m.name}${costTierDisplay(m.cost, m.canonicalCost)}, ${m.id}`,
|
|
36
|
+
description: `${priceStr}${canonicalNote} · context: ${context}`,
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function buildModelChoices(input, models) {
|
|
42
|
+
const q = (input || '').toLowerCase();
|
|
43
|
+
const filtered = q
|
|
44
|
+
? models.filter(m =>
|
|
45
|
+
m.label.toLowerCase().includes(q) ||
|
|
46
|
+
m.id.toLowerCase().includes(q)
|
|
47
|
+
)
|
|
48
|
+
: models;
|
|
49
|
+
|
|
50
|
+
return [
|
|
51
|
+
{
|
|
52
|
+
name: 'None',
|
|
53
|
+
value: null,
|
|
54
|
+
description: 'Leave this model unset',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: '← Change provider',
|
|
58
|
+
value: '__back__',
|
|
59
|
+
description: 'Go back to provider selection',
|
|
60
|
+
},
|
|
61
|
+
...filtered.slice(0, 50).map(m => ({
|
|
62
|
+
name: m.label,
|
|
63
|
+
value: m.id,
|
|
64
|
+
description: m.description,
|
|
65
|
+
})),
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function pickModel(message, models) {
|
|
70
|
+
return search({
|
|
71
|
+
message,
|
|
72
|
+
source: input => buildModelChoices(input, models),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function filterModelsByProvider(models, provider) {
|
|
77
|
+
if (!provider) return models
|
|
78
|
+
return models.filter(m => m.id.startsWith(`${provider}/`))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function pickProvider(message) {
|
|
82
|
+
return select({
|
|
83
|
+
message,
|
|
84
|
+
choices: modelsPreset.providers.map(p => ({ value: p.value, name: p.name })),
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { modelsPreset };
|