@plainconceptsplatform/agent-harness 3.0.3 → 3.1.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 +434 -438
- package/cli/commands/update.js +121 -121
- package/cli/presets/optimization.json +0 -5
- package/cli/steps/copy/commands.js +79 -95
- package/cli/steps/copy/index.js +88 -88
- package/cli/steps/copy/opencode-json.js +6 -7
- package/cli/steps/copy/skills.js +307 -299
- package/cli/steps/metadata/index.js +115 -112
- package/cli/steps/models/write.js +38 -34
- package/cli/steps/optimization/index.js +1 -6
- package/cli/utils/paths.js +80 -67
- package/package.json +1 -1
- package/skills/pc-plan-goal/SKILL.md +5 -1
- package/skills/pc-repo-help/SKILL.md +87 -89
- package/skills/pc-repo-initialize/SKILL.md +11 -4
- package/skills/pc-repo-onboard/SKILL.md +32 -32
- package/skills/pc-repo-verify/SKILL.md +150 -123
- package/cli/fragments/ops-evidence/az.md +0 -44
- package/cli/fragments/ops-evidence/gh.md +0 -53
- package/cli/fragments/ops-evidence/jira.md +0 -42
- package/cli/presets/quota.json +0 -20
- package/cli/steps/optimization/quota.js +0 -135
- package/harness/.opencode/commands/ops-evidence.md +0 -9
- package/skills/pc-ops-evidence/SKILL.md +0 -131
package/cli/commands/update.js
CHANGED
|
@@ -1,121 +1,121 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
|
-
import { execa } from 'execa'
|
|
3
|
-
import fse from 'fs-extra'
|
|
4
|
-
import path from 'node:path'
|
|
5
|
-
import { fileURLToPath } from 'url'
|
|
6
|
-
import { readHarnessConfig } from './shared.js'
|
|
7
|
-
import { copyContentStep } from '../steps/copy/index.js'
|
|
8
|
-
import { writeModelsToConfigs } from '../steps/models/write.js'
|
|
9
|
-
import { patchGuardrails } from '../steps/optimization/patch-guardrails.js'
|
|
10
|
-
import { reconcileTools } from '../steps/optimization/detect.js'
|
|
11
|
-
import { writeHarnessConfig } from '../steps/metadata/index.js'
|
|
12
|
-
import { info } from '../utils/exec.js'
|
|
13
|
-
import { exit } from '../utils/process.js'
|
|
14
|
-
|
|
15
|
-
const SIMPLE_ENGLISH_ENTRY = {
|
|
16
|
-
source: 'AminBlg/SimpleEnglish',
|
|
17
|
-
sourceType: 'github',
|
|
18
|
-
skillPath: 'skills/simple-english/SKILL.md',
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
22
|
-
const platformsPreset = await fse.readJson(path.resolve(__dirname, '../presets/platforms.json'))
|
|
23
|
-
const VALID_PLATFORMS = new Set(platformsPreset.map(p => p.value))
|
|
24
|
-
|
|
25
|
-
function resolvePlatform(value, fallback = 'github') {
|
|
26
|
-
return VALID_PLATFORMS.has(value) ? value : fallback
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export async function runUpdate() {
|
|
30
|
-
const saved = await readHarnessConfig()
|
|
31
|
-
if (!saved?.platform) {
|
|
32
|
-
console.log(chalk.red('No agent-harness config found. Run the wizard first.'))
|
|
33
|
-
exit(1)
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const backlogPlatform = resolvePlatform(saved.platform.backlog)
|
|
38
|
-
const repoPlatform = resolvePlatform(saved.platform.repo)
|
|
39
|
-
|
|
40
|
-
const ctx = {
|
|
41
|
-
hasDesign: !!saved.preexisting?.design,
|
|
42
|
-
hasArchitecture: !!saved.preexisting?.architecture,
|
|
43
|
-
hasOpenspec: !!saved.preexisting?.openspec,
|
|
44
|
-
sourceMode: saved.source?.mode ?? 'current',
|
|
45
|
-
sourceRoots: Array.isArray(saved.source?.roots) ? saved.source.roots : [],
|
|
46
|
-
maxConcurrentAgents: saved.agents?.maxConcurrent ?? 3,
|
|
47
|
-
installScope: 'local',
|
|
48
|
-
skipSkills: false,
|
|
49
|
-
updateMode: true,
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
console.log()
|
|
53
|
-
console.log(chalk.bold('Updating project from saved config'))
|
|
54
|
-
console.log(chalk.dim(` backlog: ${backlogPlatform} repo: ${repoPlatform} agents: ${ctx.maxConcurrentAgents}`))
|
|
55
|
-
console.log()
|
|
56
|
-
|
|
57
|
-
await copyContentStep({ backlogPlatform, repoPlatform }, ctx)
|
|
58
|
-
|
|
59
|
-
if (saved.models) {
|
|
60
|
-
await writeModelsToConfigs({ buildModel: saved.models.build })
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Reconcile against what is actually installed before patching guardrails.
|
|
64
|
-
// The saved flags drift: a project can be running the codegraph and
|
|
65
|
-
// agentmemory MCP servers with harness.json still recording them as false,
|
|
66
|
-
// and patchGuardrails would then empty those marker sections and strip the
|
|
67
|
-
// guidance for tools the project genuinely uses.
|
|
68
|
-
const tools = reconcileTools(saved.tools ?? {})
|
|
69
|
-
const gained = Object.keys(tools).filter(k => tools[k] && !saved.tools?.[k])
|
|
70
|
-
if (gained.length > 0) {
|
|
71
|
-
info(`Detected installed tools missing from harness.json: ${gained.join(', ')}`)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
await migrateCavemanSkill(tools)
|
|
75
|
-
await patchGuardrails({
|
|
76
|
-
rtk: !!tools.rtk,
|
|
77
|
-
simpleEnglish: !!(tools.simpleEnglish || tools.caveman),
|
|
78
|
-
codegraph: !!tools.codegraph,
|
|
79
|
-
memory: !!tools.memory,
|
|
80
|
-
humanizer: !!tools.humanizer,
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
await writeHarnessConfig({
|
|
84
|
-
...ctx,
|
|
85
|
-
backlogPlatform,
|
|
86
|
-
repoPlatform,
|
|
87
|
-
maxConcurrentAgents: ctx.maxConcurrentAgents,
|
|
88
|
-
planModel: saved.models?.plan ?? null,
|
|
89
|
-
buildModel: saved.models?.build ?? null,
|
|
90
|
-
fastModel: saved.models?.fast ?? null,
|
|
91
|
-
optionalTools: tools,
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
console.log()
|
|
95
|
-
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
96
|
-
console.log(chalk.bold.green(' Update complete!'))
|
|
97
|
-
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
98
|
-
console.log()
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async function migrateCavemanSkill(tools) {
|
|
102
|
-
if (!tools.caveman || tools.simpleEnglish) return
|
|
103
|
-
|
|
104
|
-
const lockPath = path.join(process.cwd(), 'skills-lock.json')
|
|
105
|
-
const lock = await fse.readJson(lockPath).catch(() => ({ version: 1, skills: {} }))
|
|
106
|
-
lock.skills ??= {}
|
|
107
|
-
delete lock.skills.caveman
|
|
108
|
-
lock.skills['simple-english'] = SIMPLE_ENGLISH_ENTRY
|
|
109
|
-
await fse.writeJson(lockPath, lock, { spaces: 2 })
|
|
110
|
-
await fse.remove(path.join(process.cwd(), '.agents', 'skills', 'caveman'))
|
|
111
|
-
|
|
112
|
-
const result = await execa('npx', ['skills', 'experimental_install', '--yes'], {
|
|
113
|
-
cwd: process.cwd(),
|
|
114
|
-
reject: false,
|
|
115
|
-
stdio: 'pipe',
|
|
116
|
-
timeout: 600000,
|
|
117
|
-
})
|
|
118
|
-
if (result.exitCode !== 0) {
|
|
119
|
-
console.log(chalk.yellow('Simple English was added to skills-lock.json but could not be installed. Run npx skills experimental_install --yes.'))
|
|
120
|
-
}
|
|
121
|
-
}
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import { execa } from 'execa'
|
|
3
|
+
import fse from 'fs-extra'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'url'
|
|
6
|
+
import { readHarnessConfig } from './shared.js'
|
|
7
|
+
import { copyContentStep } from '../steps/copy/index.js'
|
|
8
|
+
import { writeModelsToConfigs } from '../steps/models/write.js'
|
|
9
|
+
import { patchGuardrails } from '../steps/optimization/patch-guardrails.js'
|
|
10
|
+
import { reconcileTools } from '../steps/optimization/detect.js'
|
|
11
|
+
import { writeHarnessConfig } from '../steps/metadata/index.js'
|
|
12
|
+
import { info } from '../utils/exec.js'
|
|
13
|
+
import { exit } from '../utils/process.js'
|
|
14
|
+
|
|
15
|
+
const SIMPLE_ENGLISH_ENTRY = {
|
|
16
|
+
source: 'AminBlg/SimpleEnglish',
|
|
17
|
+
sourceType: 'github',
|
|
18
|
+
skillPath: 'skills/simple-english/SKILL.md',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
22
|
+
const platformsPreset = await fse.readJson(path.resolve(__dirname, '../presets/platforms.json'))
|
|
23
|
+
const VALID_PLATFORMS = new Set(platformsPreset.map(p => p.value))
|
|
24
|
+
|
|
25
|
+
function resolvePlatform(value, fallback = 'github') {
|
|
26
|
+
return VALID_PLATFORMS.has(value) ? value : fallback
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function runUpdate() {
|
|
30
|
+
const saved = await readHarnessConfig()
|
|
31
|
+
if (!saved?.platform) {
|
|
32
|
+
console.log(chalk.red('No agent-harness config found. Run the wizard first.'))
|
|
33
|
+
exit(1)
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const backlogPlatform = resolvePlatform(saved.platform.backlog)
|
|
38
|
+
const repoPlatform = resolvePlatform(saved.platform.repo)
|
|
39
|
+
|
|
40
|
+
const ctx = {
|
|
41
|
+
hasDesign: !!saved.preexisting?.design,
|
|
42
|
+
hasArchitecture: !!saved.preexisting?.architecture,
|
|
43
|
+
hasOpenspec: !!saved.preexisting?.openspec,
|
|
44
|
+
sourceMode: saved.source?.mode ?? 'current',
|
|
45
|
+
sourceRoots: Array.isArray(saved.source?.roots) ? saved.source.roots : [],
|
|
46
|
+
maxConcurrentAgents: saved.agents?.maxConcurrent ?? 3,
|
|
47
|
+
installScope: 'local',
|
|
48
|
+
skipSkills: false,
|
|
49
|
+
updateMode: true,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log()
|
|
53
|
+
console.log(chalk.bold('Updating project from saved config'))
|
|
54
|
+
console.log(chalk.dim(` backlog: ${backlogPlatform} repo: ${repoPlatform} agents: ${ctx.maxConcurrentAgents}`))
|
|
55
|
+
console.log()
|
|
56
|
+
|
|
57
|
+
await copyContentStep({ backlogPlatform, repoPlatform }, ctx)
|
|
58
|
+
|
|
59
|
+
if (saved.models) {
|
|
60
|
+
await writeModelsToConfigs({ buildModel: saved.models.build, keepExisting: true })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Reconcile against what is actually installed before patching guardrails.
|
|
64
|
+
// The saved flags drift: a project can be running the codegraph and
|
|
65
|
+
// agentmemory MCP servers with harness.json still recording them as false,
|
|
66
|
+
// and patchGuardrails would then empty those marker sections and strip the
|
|
67
|
+
// guidance for tools the project genuinely uses.
|
|
68
|
+
const tools = reconcileTools(saved.tools ?? {})
|
|
69
|
+
const gained = Object.keys(tools).filter(k => tools[k] && !saved.tools?.[k])
|
|
70
|
+
if (gained.length > 0) {
|
|
71
|
+
info(`Detected installed tools missing from harness.json: ${gained.join(', ')}`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await migrateCavemanSkill(tools)
|
|
75
|
+
await patchGuardrails({
|
|
76
|
+
rtk: !!tools.rtk,
|
|
77
|
+
simpleEnglish: !!(tools.simpleEnglish || tools.caveman),
|
|
78
|
+
codegraph: !!tools.codegraph,
|
|
79
|
+
memory: !!tools.memory,
|
|
80
|
+
humanizer: !!tools.humanizer,
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
await writeHarnessConfig({
|
|
84
|
+
...ctx,
|
|
85
|
+
backlogPlatform,
|
|
86
|
+
repoPlatform,
|
|
87
|
+
maxConcurrentAgents: ctx.maxConcurrentAgents,
|
|
88
|
+
planModel: saved.models?.plan ?? null,
|
|
89
|
+
buildModel: saved.models?.build ?? null,
|
|
90
|
+
fastModel: saved.models?.fast ?? null,
|
|
91
|
+
optionalTools: tools,
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
console.log()
|
|
95
|
+
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
96
|
+
console.log(chalk.bold.green(' Update complete!'))
|
|
97
|
+
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
98
|
+
console.log()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function migrateCavemanSkill(tools) {
|
|
102
|
+
if (!tools.caveman || tools.simpleEnglish) return
|
|
103
|
+
|
|
104
|
+
const lockPath = path.join(process.cwd(), 'skills-lock.json')
|
|
105
|
+
const lock = await fse.readJson(lockPath).catch(() => ({ version: 1, skills: {} }))
|
|
106
|
+
lock.skills ??= {}
|
|
107
|
+
delete lock.skills.caveman
|
|
108
|
+
lock.skills['simple-english'] = SIMPLE_ENGLISH_ENTRY
|
|
109
|
+
await fse.writeJson(lockPath, lock, { spaces: 2 })
|
|
110
|
+
await fse.remove(path.join(process.cwd(), '.agents', 'skills', 'caveman'))
|
|
111
|
+
|
|
112
|
+
const result = await execa('npx', ['skills', 'experimental_install', '--yes'], {
|
|
113
|
+
cwd: process.cwd(),
|
|
114
|
+
reject: false,
|
|
115
|
+
stdio: 'pipe',
|
|
116
|
+
timeout: 600000,
|
|
117
|
+
})
|
|
118
|
+
if (result.exitCode !== 0) {
|
|
119
|
+
console.log(chalk.yellow('Simple English was added to skills-lock.json but could not be installed. Run npx skills experimental_install --yes.'))
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -1,95 +1,79 @@
|
|
|
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
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export async function
|
|
61
|
-
const repoPlatform = typeof platform === 'object' ? (platform.repoPlatform ??
|
|
62
|
-
const replacement =
|
|
63
|
-
if (!replacement) return
|
|
64
|
-
patchFile('.agents/skills/pc-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export async function
|
|
68
|
-
const repoPlatform = typeof platform === 'object' ? (platform.repoPlatform ?? 'github') : platform
|
|
69
|
-
const replacement =
|
|
70
|
-
if (!replacement) return
|
|
71
|
-
patchFile('.
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export async function
|
|
75
|
-
const
|
|
76
|
-
const replacement =
|
|
77
|
-
if (!replacement) return
|
|
78
|
-
patchFile('.opencode/commands/ops-
|
|
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
|
+
// relativePath is POSIX-style relative to the project root; commands live in
|
|
34
|
+
// .opencode/commands/, skill-backed procedures (plan-archive, ops-ship) in
|
|
35
|
+
// .agents/skills/<name>/SKILL.md.
|
|
36
|
+
function patchFile(relativePath, startMarker, endMarker, content, platform, cwd = process.cwd()) {
|
|
37
|
+
const targetPath = path.join(cwd, ...relativePath.split('/'))
|
|
38
|
+
if (!fse.pathExistsSync(targetPath)) return
|
|
39
|
+
|
|
40
|
+
let fileContent = fse.readFileSync(targetPath, 'utf-8')
|
|
41
|
+
if (!fileContent.includes(startMarker) || !fileContent.includes(endMarker)) return
|
|
42
|
+
|
|
43
|
+
const pattern = new RegExp(`${startMarker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${endMarker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`)
|
|
44
|
+
// Replacer function, not a replacement string: the injected fragments contain
|
|
45
|
+
// shell quoting like $'...' and a string replacement expands $' as "everything
|
|
46
|
+
// after the match", silently truncating the command. That is how the ops-ship
|
|
47
|
+
// screenshot comment lost its entire --body argument.
|
|
48
|
+
fileContent = fileContent.replace(pattern, () => `${startMarker}\n${content.trim()}\n${endMarker}`)
|
|
49
|
+
fse.writeFileSync(targetPath, `${fileContent.replace(/\s*$/, '')}\n`, 'utf-8')
|
|
50
|
+
success(`${relativePath} content injected for platform: ${platform}`)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function patchArchiveCommand(platform, cwd = process.cwd()) {
|
|
54
|
+
const repoPlatform = typeof platform === 'object' ? (platform.repoPlatform ?? platform.backlogPlatform ?? 'github') : platform
|
|
55
|
+
const replacement = _archive[repoPlatform]
|
|
56
|
+
if (!replacement) return
|
|
57
|
+
patchFile('.agents/skills/pc-plan-archive/SKILL.md', '<!-- PC-PLATFORM-ARCHIVE-START -->', '<!-- PC-PLATFORM-ARCHIVE-END -->', replacement, repoPlatform, cwd)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function patchOpsShip(platform, cwd = process.cwd()) {
|
|
61
|
+
const repoPlatform = typeof platform === 'object' ? (platform.repoPlatform ?? 'github') : platform
|
|
62
|
+
const replacement = _ship[repoPlatform]
|
|
63
|
+
if (!replacement) return
|
|
64
|
+
patchFile('.agents/skills/pc-ops-ship/SKILL.md', '<!-- PC-PLATFORM-SHIP-START -->', '<!-- PC-PLATFORM-SHIP-END -->', replacement, repoPlatform, cwd)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function patchOpsReview(platform, cwd = process.cwd()) {
|
|
68
|
+
const repoPlatform = typeof platform === 'object' ? (platform.repoPlatform ?? 'github') : platform
|
|
69
|
+
const replacement = _review[repoPlatform]
|
|
70
|
+
if (!replacement) return
|
|
71
|
+
patchFile('.opencode/commands/ops-review.md', '<!-- PC-PLATFORM-REVIEW-START -->', '<!-- PC-PLATFORM-REVIEW-END -->', replacement, repoPlatform, cwd)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function patchOpsBacklog(platform, cwd = process.cwd()) {
|
|
75
|
+
const backlogPlatform = typeof platform === 'object' ? (platform.backlogPlatform ?? 'github') : platform
|
|
76
|
+
const replacement = _backlog[backlogPlatform]
|
|
77
|
+
if (!replacement) return
|
|
78
|
+
patchFile('.opencode/commands/ops-backlog.md', '<!-- PC-PLATFORM-BACKLOG-START -->', '<!-- PC-PLATFORM-BACKLOG-END -->', replacement, backlogPlatform, cwd)
|
|
79
|
+
}
|
package/cli/steps/copy/index.js
CHANGED
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
import fse from "fs-extra"
|
|
2
|
-
import path from "path"
|
|
3
|
-
import { fileURLToPath } from "url"
|
|
4
|
-
import { copyContent, recordManagedContent } from "../../utils/copy.js"
|
|
5
|
-
import { error, header, success } from "../../utils/exec.js"
|
|
6
|
-
import { exit } from "../../utils/process.js"
|
|
7
|
-
import { patchAgentGuidance, patchAgentsMd } from "./agents.js"
|
|
8
|
-
import { patchArchiveCommand, patchOpsShip, patchOpsReview, patchOpsBacklog
|
|
9
|
-
import { installSkills } from "./skills.js"
|
|
10
|
-
import { generateFullstackEngineer, removeLegacyStartupDirectives } from "./fullstack-engineer.js"
|
|
11
|
-
import { patchOpencodeJson, patchOpencodePackage } from "./opencode-json.js"
|
|
12
|
-
|
|
13
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
14
|
-
const CONTENT_DIR = path.resolve(__dirname, "../../../harness")
|
|
15
|
-
|
|
16
|
-
export async function copyContentStep(platform, ctx = {}) {
|
|
17
|
-
header("Step 5, Copying agent-harness files")
|
|
18
|
-
|
|
19
|
-
const dest = process.cwd()
|
|
20
|
-
|
|
21
|
-
// Support both old single-string platform and new { backlogPlatform, repoPlatform }
|
|
22
|
-
const backlogPlatform = typeof platform === 'object' ? platform.backlogPlatform : platform
|
|
23
|
-
const repoPlatform = typeof platform === 'object' ? platform.repoPlatform : platform
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
await copyContent(CONTENT_DIR, dest, platform, ctx)
|
|
27
|
-
|
|
28
|
-
// .gitignore is stripped by npm during publish, so the source file is _gitignore.
|
|
29
|
-
// Also, fse.copy with overwrite:false won't overwrite an existing .gitignore,
|
|
30
|
-
// so merge manually to preserve both opencode's defaults and our additions.
|
|
31
|
-
const srcGitignore = path.join(CONTENT_DIR, ".opencode", "_gitignore")
|
|
32
|
-
const destGitignore = path.join(dest, ".opencode", ".gitignore")
|
|
33
|
-
if (await fse.pathExists(srcGitignore)) {
|
|
34
|
-
const srcLines = (await fse.readFile(srcGitignore, "utf-8")).split("\n").map(l => l.trim()).filter(Boolean)
|
|
35
|
-
const destLines = (await fse.pathExists(destGitignore))
|
|
36
|
-
? (await fse.readFile(destGitignore, "utf-8")).split("\n").map(l => l.trim()).filter(Boolean)
|
|
37
|
-
: []
|
|
38
|
-
const merged = Array.from(new Set([...destLines, ...srcLines]))
|
|
39
|
-
await fse.writeFile(destGitignore, `${merged.join("\n")}\n`, "utf-8")
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Remove the _gitignore template from dest: it was only needed for the merge above.
|
|
43
|
-
const destGitignoreTemplate = path.join(dest, ".opencode", "_gitignore")
|
|
44
|
-
if (await fse.pathExists(destGitignoreTemplate)) {
|
|
45
|
-
await fse.remove(destGitignoreTemplate)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const rootsFile = path.join(dest, ".opencode", "source-roots.json")
|
|
49
|
-
if (!ctx.updateMode || !await fse.pathExists(rootsFile)) {
|
|
50
|
-
await fse.writeJson(
|
|
51
|
-
rootsFile,
|
|
52
|
-
{
|
|
53
|
-
mode: ctx.sourceMode || "current",
|
|
54
|
-
// An empty array is truthy, so `|| [dest]` never kicked in and reruns
|
|
55
|
-
// wrote `roots: []`, leaving agents with no source scope.
|
|
56
|
-
roots: ctx.sourceRoots?.length ? ctx.sourceRoots : [dest],
|
|
57
|
-
},
|
|
58
|
-
{ spaces: 2 },
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
await patchAgentGuidance(backlogPlatform, repoPlatform)
|
|
63
|
-
await patchOpsReview({ backlogPlatform, repoPlatform })
|
|
64
|
-
await patchOpsBacklog({ backlogPlatform, repoPlatform })
|
|
65
|
-
await patchOpencodeJson()
|
|
66
|
-
await patchOpencodePackage()
|
|
67
|
-
if (!ctx.updateMode) await patchAgentsMd(ctx)
|
|
68
|
-
|
|
69
|
-
if (!ctx.skipSkills) {
|
|
70
|
-
await installSkills(backlogPlatform, repoPlatform, {
|
|
71
|
-
forceOverwrite: ctx.forceOverwrite,
|
|
72
|
-
updateMode: ctx.updateMode,
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
// These patch SKILL.md files (pc-plan-archive, pc-ops-ship
|
|
76
|
-
// so they must run after installSkills has copied the skills into the project.
|
|
77
|
-
await patchArchiveCommand({ backlogPlatform, repoPlatform })
|
|
78
|
-
await patchOpsShip({ backlogPlatform, repoPlatform })
|
|
79
|
-
await
|
|
80
|
-
await generateFullstackEngineer({ updateMode: ctx.updateMode })
|
|
81
|
-
await removeLegacyStartupDirectives()
|
|
82
|
-
await recordManagedContent(CONTENT_DIR, dest, { updateMode: ctx.updateMode })
|
|
83
|
-
success("Files copied to project root")
|
|
84
|
-
} catch (err) {
|
|
85
|
-
error(`Failed to copy content: ${err.message}`)
|
|
86
|
-
exit(1)
|
|
87
|
-
}
|
|
88
|
-
}
|
|
1
|
+
import fse from "fs-extra"
|
|
2
|
+
import path from "path"
|
|
3
|
+
import { fileURLToPath } from "url"
|
|
4
|
+
import { copyContent, recordManagedContent } from "../../utils/copy.js"
|
|
5
|
+
import { error, header, success } from "../../utils/exec.js"
|
|
6
|
+
import { exit } from "../../utils/process.js"
|
|
7
|
+
import { patchAgentGuidance, patchAgentsMd } from "./agents.js"
|
|
8
|
+
import { patchArchiveCommand, patchOpsShip, patchOpsReview, patchOpsBacklog } from "./commands.js"
|
|
9
|
+
import { installSkills, removeRetiredFiles } from "./skills.js"
|
|
10
|
+
import { generateFullstackEngineer, removeLegacyStartupDirectives } from "./fullstack-engineer.js"
|
|
11
|
+
import { patchOpencodeJson, patchOpencodePackage } from "./opencode-json.js"
|
|
12
|
+
|
|
13
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
14
|
+
const CONTENT_DIR = path.resolve(__dirname, "../../../harness")
|
|
15
|
+
|
|
16
|
+
export async function copyContentStep(platform, ctx = {}) {
|
|
17
|
+
header("Step 5, Copying agent-harness files")
|
|
18
|
+
|
|
19
|
+
const dest = process.cwd()
|
|
20
|
+
|
|
21
|
+
// Support both old single-string platform and new { backlogPlatform, repoPlatform }
|
|
22
|
+
const backlogPlatform = typeof platform === 'object' ? platform.backlogPlatform : platform
|
|
23
|
+
const repoPlatform = typeof platform === 'object' ? platform.repoPlatform : platform
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
await copyContent(CONTENT_DIR, dest, platform, ctx)
|
|
27
|
+
|
|
28
|
+
// .gitignore is stripped by npm during publish, so the source file is _gitignore.
|
|
29
|
+
// Also, fse.copy with overwrite:false won't overwrite an existing .gitignore,
|
|
30
|
+
// so merge manually to preserve both opencode's defaults and our additions.
|
|
31
|
+
const srcGitignore = path.join(CONTENT_DIR, ".opencode", "_gitignore")
|
|
32
|
+
const destGitignore = path.join(dest, ".opencode", ".gitignore")
|
|
33
|
+
if (await fse.pathExists(srcGitignore)) {
|
|
34
|
+
const srcLines = (await fse.readFile(srcGitignore, "utf-8")).split("\n").map(l => l.trim()).filter(Boolean)
|
|
35
|
+
const destLines = (await fse.pathExists(destGitignore))
|
|
36
|
+
? (await fse.readFile(destGitignore, "utf-8")).split("\n").map(l => l.trim()).filter(Boolean)
|
|
37
|
+
: []
|
|
38
|
+
const merged = Array.from(new Set([...destLines, ...srcLines]))
|
|
39
|
+
await fse.writeFile(destGitignore, `${merged.join("\n")}\n`, "utf-8")
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Remove the _gitignore template from dest: it was only needed for the merge above.
|
|
43
|
+
const destGitignoreTemplate = path.join(dest, ".opencode", "_gitignore")
|
|
44
|
+
if (await fse.pathExists(destGitignoreTemplate)) {
|
|
45
|
+
await fse.remove(destGitignoreTemplate)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const rootsFile = path.join(dest, ".opencode", "source-roots.json")
|
|
49
|
+
if (!ctx.updateMode || !await fse.pathExists(rootsFile)) {
|
|
50
|
+
await fse.writeJson(
|
|
51
|
+
rootsFile,
|
|
52
|
+
{
|
|
53
|
+
mode: ctx.sourceMode || "current",
|
|
54
|
+
// An empty array is truthy, so `|| [dest]` never kicked in and reruns
|
|
55
|
+
// wrote `roots: []`, leaving agents with no source scope.
|
|
56
|
+
roots: ctx.sourceRoots?.length ? ctx.sourceRoots : [dest],
|
|
57
|
+
},
|
|
58
|
+
{ spaces: 2 },
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await patchAgentGuidance(backlogPlatform, repoPlatform)
|
|
63
|
+
await patchOpsReview({ backlogPlatform, repoPlatform })
|
|
64
|
+
await patchOpsBacklog({ backlogPlatform, repoPlatform })
|
|
65
|
+
await patchOpencodeJson()
|
|
66
|
+
await patchOpencodePackage()
|
|
67
|
+
if (!ctx.updateMode) await patchAgentsMd(ctx)
|
|
68
|
+
|
|
69
|
+
if (!ctx.skipSkills) {
|
|
70
|
+
await installSkills(backlogPlatform, repoPlatform, {
|
|
71
|
+
forceOverwrite: ctx.forceOverwrite,
|
|
72
|
+
updateMode: ctx.updateMode,
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
// These patch SKILL.md files (pc-plan-archive, pc-ops-ship),
|
|
76
|
+
// so they must run after installSkills has copied the skills into the project.
|
|
77
|
+
await patchArchiveCommand({ backlogPlatform, repoPlatform })
|
|
78
|
+
await patchOpsShip({ backlogPlatform, repoPlatform })
|
|
79
|
+
await removeRetiredFiles()
|
|
80
|
+
await generateFullstackEngineer({ updateMode: ctx.updateMode })
|
|
81
|
+
await removeLegacyStartupDirectives()
|
|
82
|
+
await recordManagedContent(CONTENT_DIR, dest, { updateMode: ctx.updateMode })
|
|
83
|
+
success("Files copied to project root")
|
|
84
|
+
} catch (err) {
|
|
85
|
+
error(`Failed to copy content: ${err.message}`)
|
|
86
|
+
exit(1)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -24,9 +24,12 @@ const CONFIG_SCHEMA = 'https://opencode.ai/config.json'
|
|
|
24
24
|
|
|
25
25
|
// Third-party plugins the harness configures, and how they move to V2. The
|
|
26
26
|
// notifier is dropped: V2 has native attention notifications and sounds.
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
const DROPPED_PLUGIN_PREFIXES = [
|
|
28
|
+
'@opencode-ai/',
|
|
29
|
+
'@mohak34/opencode-notifier',
|
|
30
|
+
'@different-ai/opencode-browser',
|
|
31
|
+
'@slkiser/opencode-quota',
|
|
32
|
+
]
|
|
30
33
|
|
|
31
34
|
// V1 permission keys -> V2 permission actions.
|
|
32
35
|
const ACTION_RENAMES = {
|
|
@@ -76,10 +79,6 @@ function migratePluginArray(obj) {
|
|
|
76
79
|
}
|
|
77
80
|
const base = basePackageName(entry)
|
|
78
81
|
if (DROPPED_PLUGIN_PREFIXES.some(prefix => base.startsWith(prefix))) continue
|
|
79
|
-
if (base === OPENCODE_QUOTA_PACKAGE) {
|
|
80
|
-
plugins.push(`${OPENCODE_QUOTA_PACKAGE}@${OPENCODE_QUOTA_VERSION}`)
|
|
81
|
-
continue
|
|
82
|
-
}
|
|
83
82
|
plugins.push(entry)
|
|
84
83
|
}
|
|
85
84
|
obj.plugins = plugins
|