opencode-onboard 0.3.3 → 0.4.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 +266 -214
- package/content/.agents/agents/basic-engineer.md +30 -0
- package/content/.agents/agents/devops-manager.md +38 -29
- package/content/.agents/session-log.json +41 -0
- package/content/.agents/skills/ob-default/SKILL.md +21 -0
- package/content/.agents/skills/ob-generic-guardrails/SKILL.md +32 -0
- package/content/.agents/skills/ob-global/SKILL.md +49 -0
- package/content/.agents/skills/ob-pullrequest-az/SKILL.md +11 -21
- package/content/.agents/skills/ob-pullrequest-gh/SKILL.md +14 -24
- package/content/.agents/skills/ob-userstory-az/SKILL.md +8 -14
- package/content/.agents/skills/ob-userstory-gh/SKILL.md +6 -14
- package/content/.opencode/commands/opsx-apply.md +50 -33
- package/content/.opencode/plugins/session-log.js +1 -1
- package/content/.opencode/skills/openspec-apply-change/SKILL.md +50 -33
- package/content/AGENTS.md +94 -144
- package/content/skills-lock.json +4 -0
- package/package.json +6 -1
- package/src/index.js +13 -47
- package/src/presets/browser.json +18 -0
- package/src/presets/clean.json +21 -0
- package/src/presets/models.json +33 -0
- package/src/presets/optimization.json +22 -0
- package/src/presets/platforms.json +29 -2
- package/src/presets/quota.json +14 -0
- package/src/presets/source.json +17 -0
- package/src/steps/browser/browser.test.js +81 -0
- package/src/steps/{install-browser.js → browser/index.js} +12 -15
- package/src/steps/{__tests__/clean-ai-files.test.js → clean/clean.test.js} +28 -13
- package/src/steps/{clean-ai-files.js → clean/index.js} +32 -30
- package/src/steps/copy/agents.js +106 -0
- package/src/steps/{__tests__/copy-content.test.js → copy/copy.test.js} +10 -1
- package/src/steps/copy/index.js +33 -0
- package/src/steps/copy/skills.js +55 -0
- package/src/steps/{write-onboard-config.js → metadata/index.js} +3 -3
- package/src/steps/metadata/metadata.test.js +96 -0
- package/src/steps/models/format.js +60 -0
- package/src/steps/models/format.test.js +74 -0
- package/src/steps/models/index.js +52 -0
- package/src/steps/models/write.js +54 -0
- package/src/steps/models/write.test.js +119 -0
- package/src/steps/{init-openspec.js → openspec/ensemble.js} +20 -57
- package/src/steps/openspec/ensemble.test.js +79 -0
- package/src/steps/openspec/index.js +32 -0
- package/src/steps/optimization/caveman-guidance.js +11 -0
- package/src/steps/{install-caveman.js → optimization/caveman.js} +5 -19
- package/src/steps/optimization/global.js +64 -0
- package/src/steps/optimization/index.js +101 -0
- package/src/steps/{__tests__/token-optimization.test.js → optimization/optimization.test.js} +19 -24
- package/src/steps/{install-quota.js → optimization/quota.js} +12 -10
- package/src/steps/platform/index.js +81 -0
- package/src/steps/platform/platform.test.js +129 -0
- package/src/steps/{choose-source-scope.js → source/index.js} +11 -17
- package/src/steps/source/source.test.js +89 -0
- package/src/utils/__tests__/copy.test.js +12 -5
- package/src/utils/copy.js +4 -24
- package/src/utils/exec-spinner.js +47 -0
- package/src/utils/exec.js +120 -162
- package/src/utils/models-cache.js +25 -68
- package/src/utils/models-pricing.js +42 -0
- package/src/utils/models-pricing.test.js +94 -0
- package/content/.agents/agents/back-engineer.md +0 -87
- package/content/.agents/agents/front-engineer.md +0 -86
- package/content/.agents/agents/infra-engineer.md +0 -85
- package/content/.agents/agents/quality-engineer.md +0 -86
- package/content/.agents/agents/security-auditor.md +0 -86
- package/src/steps/__tests__/check-env.test.js +0 -70
- package/src/steps/__tests__/check-platform.test.js +0 -104
- package/src/steps/__tests__/check-rtk.test.js +0 -38
- package/src/steps/__tests__/choose-platform.test.js +0 -38
- package/src/steps/check-env.js +0 -26
- package/src/steps/check-platform.js +0 -80
- package/src/steps/check-rtk.js +0 -38
- package/src/steps/choose-models.js +0 -165
- package/src/steps/choose-platform.js +0 -22
- package/src/steps/choose-skills-provider.js +0 -79
- package/src/steps/copy-content.js +0 -89
- package/src/steps/enable-caveman-guidance.js +0 -78
- package/src/steps/patch-agents-md.js +0 -153
- package/src/steps/token-optimization.js +0 -59
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { execa } from 'execa'
|
|
2
|
-
import { code, commandExists, header, info, success, warn } from '../utils/exec.js'
|
|
3
|
-
|
|
4
|
-
export async function checkPlatform(platform) {
|
|
5
|
-
if (platform === 'azure') {
|
|
6
|
-
await checkAzure()
|
|
7
|
-
} else {
|
|
8
|
-
await checkGithub()
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
async function checkAzure() {
|
|
13
|
-
header('Step 5, Checking Azure DevOps CLI')
|
|
14
|
-
|
|
15
|
-
// Check az is installed
|
|
16
|
-
const hasAz = await commandExists('az')
|
|
17
|
-
if (!hasAz) {
|
|
18
|
-
warn('Azure CLI (az) not found.')
|
|
19
|
-
info('Install from: https://learn.microsoft.com/en-us/cli/azure/install-azure-cli')
|
|
20
|
-
return
|
|
21
|
-
}
|
|
22
|
-
success('Azure CLI (az) available')
|
|
23
|
-
|
|
24
|
-
// Check az devops extension
|
|
25
|
-
try {
|
|
26
|
-
const result = await execa('az', ['extension', 'list', '--query', "[?name=='azure-devops']", '-o', 'tsv'], {
|
|
27
|
-
reject: false,
|
|
28
|
-
})
|
|
29
|
-
const hasExtension = result.stdout && result.stdout.includes('azure-devops')
|
|
30
|
-
|
|
31
|
-
if (hasExtension) {
|
|
32
|
-
success('azure-devops extension installed')
|
|
33
|
-
} else {
|
|
34
|
-
warn('azure-devops extension not found. Run:')
|
|
35
|
-
code([
|
|
36
|
-
'az extension add --name azure-devops',
|
|
37
|
-
'az config set extension.dynamic_install_allow_preview=true',
|
|
38
|
-
'az login',
|
|
39
|
-
'az devops login --organization https://dev.azure.com/<your-org>',
|
|
40
|
-
])
|
|
41
|
-
}
|
|
42
|
-
} catch {
|
|
43
|
-
warn('Could not check azure-devops extension. Run:')
|
|
44
|
-
code([
|
|
45
|
-
'az extension add --name azure-devops',
|
|
46
|
-
'az config set extension.dynamic_install_allow_preview=true',
|
|
47
|
-
'az login',
|
|
48
|
-
'az devops login --organization https://dev.azure.com/<your-org>',
|
|
49
|
-
])
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async function checkGithub() {
|
|
54
|
-
header('Step 5, Checking GitHub CLI')
|
|
55
|
-
|
|
56
|
-
const hasGh = await commandExists('gh')
|
|
57
|
-
|
|
58
|
-
if (hasGh) {
|
|
59
|
-
success('GitHub CLI (gh) available')
|
|
60
|
-
|
|
61
|
-
// Check auth status
|
|
62
|
-
try {
|
|
63
|
-
const result = await execa('gh', ['auth', 'status'], { reject: false })
|
|
64
|
-
if (result.exitCode === 0) {
|
|
65
|
-
success('GitHub CLI authenticated')
|
|
66
|
-
} else {
|
|
67
|
-
warn('GitHub CLI not authenticated. Run:')
|
|
68
|
-
code(['gh auth login'])
|
|
69
|
-
}
|
|
70
|
-
} catch {
|
|
71
|
-
warn('Could not check gh auth status.')
|
|
72
|
-
}
|
|
73
|
-
} else {
|
|
74
|
-
warn('GitHub CLI (gh) not found.')
|
|
75
|
-
info('Install from: https://cli.github.com')
|
|
76
|
-
console.log()
|
|
77
|
-
info('After installing, authenticate with:')
|
|
78
|
-
code(['gh auth login'])
|
|
79
|
-
}
|
|
80
|
-
}
|
package/src/steps/check-rtk.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { confirm } from '@inquirer/prompts'
|
|
2
|
-
import { code, commandExists, header, info, loading, success, warn } from '../utils/exec.js'
|
|
3
|
-
|
|
4
|
-
export async function checkRtk(options = {}) {
|
|
5
|
-
if (!options.skipHeader) header('Checking rtk')
|
|
6
|
-
|
|
7
|
-
let shouldCheck = true
|
|
8
|
-
if (!options.skipPrompt) {
|
|
9
|
-
info('Recommended: install and verify rtk for safer agent CLI command execution.')
|
|
10
|
-
shouldCheck = await confirm({
|
|
11
|
-
message: 'Check rtk now?',
|
|
12
|
-
default: true,
|
|
13
|
-
})
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (!shouldCheck) {
|
|
17
|
-
warn('Skipped rtk check (you can install it later)')
|
|
18
|
-
return { optedIn: false, checked: false, available: false }
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
loading('checking rtk...')
|
|
22
|
-
|
|
23
|
-
const available = await commandExists('rtk')
|
|
24
|
-
|
|
25
|
-
if (available) {
|
|
26
|
-
success('rtk is available')
|
|
27
|
-
return { optedIn: true, checked: true, available: true }
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
warn('rtk not found on PATH.')
|
|
31
|
-
console.log()
|
|
32
|
-
info('rtk is required for agents to run CLI commands safely.')
|
|
33
|
-
info('Install it from: https://github.com/rtk-ai/rtk#pre-built-binaries')
|
|
34
|
-
console.log()
|
|
35
|
-
info('After installing, verify with:')
|
|
36
|
-
code(['rtk --version'])
|
|
37
|
-
return { optedIn: true, checked: true, available: false }
|
|
38
|
-
}
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import { search } from '@inquirer/prompts'
|
|
2
|
-
import fse from 'fs-extra'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import { header, info, success, warn } from '../utils/exec.js'
|
|
5
|
-
import { fetchModels } from '../utils/models-cache.js'
|
|
6
|
-
|
|
7
|
-
const COST_TIER = (input) => {
|
|
8
|
-
if (input === undefined || input === null) return ''
|
|
9
|
-
if (input < 1) return ' [$]'
|
|
10
|
-
if (input <= 10) return ' [$$]'
|
|
11
|
-
return ' [$$$]'
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// Use canonical cost for the tier badge so all providers of the same model
|
|
15
|
-
// show the same tier (e.g. github-copilot $0 subscription shows [$$] not [$])
|
|
16
|
-
const COST_TIER_DISPLAY = (cost, canonicalCost) =>
|
|
17
|
-
COST_TIER(canonicalCost !== undefined ? canonicalCost : cost)
|
|
18
|
-
|
|
19
|
-
function formatPrice(price) {
|
|
20
|
-
if (price === undefined || price === null) return '?'
|
|
21
|
-
if (price === 0) return '$0 (subscription)'
|
|
22
|
-
return `$${price}/M`
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function buildDisplayModels(rawModels) {
|
|
26
|
-
return rawModels.map(m => {
|
|
27
|
-
const priceStr = formatPrice(m.cost)
|
|
28
|
-
const canonicalNote = m.canonicalCost !== undefined
|
|
29
|
-
? ` · official price: ${formatPrice(m.canonicalCost)}/M`
|
|
30
|
-
: ''
|
|
31
|
-
return {
|
|
32
|
-
...m,
|
|
33
|
-
label: `${m.name}${COST_TIER_DISPLAY(m.cost, m.canonicalCost)}, ${m.id}`,
|
|
34
|
-
description: `${priceStr}${canonicalNote} · context: ${m.context ? (m.context / 1000) + 'k' : '?'}`,
|
|
35
|
-
}
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function pickModel(message, models) {
|
|
40
|
-
return await search({
|
|
41
|
-
message,
|
|
42
|
-
source: (input) => {
|
|
43
|
-
const q = (input || '').toLowerCase()
|
|
44
|
-
const filtered = q
|
|
45
|
-
? models.filter(m =>
|
|
46
|
-
m.label.toLowerCase().includes(q) ||
|
|
47
|
-
m.id.toLowerCase().includes(q)
|
|
48
|
-
)
|
|
49
|
-
: models
|
|
50
|
-
return filtered.slice(0, 50).map(m => ({
|
|
51
|
-
name: m.label,
|
|
52
|
-
value: m.id,
|
|
53
|
-
description: m.description,
|
|
54
|
-
}))
|
|
55
|
-
},
|
|
56
|
-
})
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async function writeModelToAgent(agentFile, modelId) {
|
|
60
|
-
const content = await fse.readFile(agentFile, 'utf-8')
|
|
61
|
-
const updated = content.replace(
|
|
62
|
-
/^(---\n[\s\S]*?)\n---/m,
|
|
63
|
-
`$1\nmodel: ${modelId}\n---`
|
|
64
|
-
)
|
|
65
|
-
await fse.writeFile(agentFile, updated, 'utf-8')
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export async function chooseModels() {
|
|
69
|
-
header('Step 9, Choose models')
|
|
70
|
-
|
|
71
|
-
info('Fetching available models from models.dev...')
|
|
72
|
-
const { models: rawModels, source } = await fetchModels()
|
|
73
|
-
|
|
74
|
-
if (!rawModels) {
|
|
75
|
-
warn('Could not fetch models (offline and no cache). Skipping model selection.')
|
|
76
|
-
warn('Set models later in .agents/agents/<name>.md and .opencode/opencode.json')
|
|
77
|
-
return
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (source === 'stale-cache') {
|
|
81
|
-
warn('Network unavailable, using cached model list (may be outdated).')
|
|
82
|
-
} else if (source === 'cache') {
|
|
83
|
-
info('Using cached model list (refreshes weekly).')
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const models = buildDisplayModels(rawModels)
|
|
87
|
-
success(`${models.length} models available`)
|
|
88
|
-
console.log()
|
|
89
|
-
info('Cost indicators: [$] cheap [$$] mid [$$$] expensive')
|
|
90
|
-
info('Type to search. Change selections later in .agents/agents/ and .opencode/opencode.json')
|
|
91
|
-
console.log()
|
|
92
|
-
|
|
93
|
-
// Plan model
|
|
94
|
-
info('PLAN model: used by the main agent to read issues, write proposals, coordinate the team.')
|
|
95
|
-
info('This model needs to be strong. Use Claude Sonnet/Opus, GPT-4o, o3, or equivalent.')
|
|
96
|
-
info('A weak model here will silently skip steps and break the workflow.')
|
|
97
|
-
const planModel = await pickModel('Plan model:', models)
|
|
98
|
-
console.log()
|
|
99
|
-
|
|
100
|
-
// Build model
|
|
101
|
-
info('BUILD model: used by front-engineer, back-engineer, infra-engineer, quality-engineer, security-auditor.')
|
|
102
|
-
info('Needs to be capable for implementation work. Claude Sonnet, GPT-4o, or equivalent.')
|
|
103
|
-
const buildModel = await pickModel('Build model:', models)
|
|
104
|
-
console.log()
|
|
105
|
-
|
|
106
|
-
// Fast model
|
|
107
|
-
info('FAST model: used by devops-manager for reading issues and classifying PR comments.')
|
|
108
|
-
info('Something fast and cheap is fine here, no heavy reasoning needed.')
|
|
109
|
-
const fastModel = await pickModel('Fast model:', models)
|
|
110
|
-
console.log()
|
|
111
|
-
|
|
112
|
-
// Write build model to builder agents
|
|
113
|
-
const buildAgents = ['front-engineer', 'back-engineer', 'infra-engineer', 'quality-engineer', 'security-auditor']
|
|
114
|
-
const agentsDir = path.join(process.cwd(), '.agents', 'agents')
|
|
115
|
-
for (const name of buildAgents) {
|
|
116
|
-
const file = path.join(agentsDir, `${name}.md`)
|
|
117
|
-
if (await fse.pathExists(file)) {
|
|
118
|
-
await writeModelToAgent(file, buildModel)
|
|
119
|
-
success(`${name} → ${buildModel}`)
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Write fast model to devops-manager
|
|
124
|
-
const devopsFile = path.join(agentsDir, 'devops-manager.md')
|
|
125
|
-
if (await fse.pathExists(devopsFile)) {
|
|
126
|
-
await writeModelToAgent(devopsFile, fastModel)
|
|
127
|
-
success(`devops-manager → ${fastModel}`)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Write plan model to opencode.json
|
|
131
|
-
const opencodeJsonPath = path.join(process.cwd(), '.opencode', 'opencode.json')
|
|
132
|
-
if (await fse.pathExists(opencodeJsonPath)) {
|
|
133
|
-
const config = await fse.readJson(opencodeJsonPath)
|
|
134
|
-
config.model = buildModel
|
|
135
|
-
await fse.writeJson(opencodeJsonPath, config, { spaces: 2 })
|
|
136
|
-
success(`default model -> ${buildModel} (written to .opencode/opencode.json)`)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Write build and fast models to ensemble.json
|
|
140
|
-
const ensembleJsonPath = path.join(process.cwd(), '.opencode', 'ensemble.json')
|
|
141
|
-
if (await fse.pathExists(ensembleJsonPath)) {
|
|
142
|
-
const ensemble = await fse.readJson(ensembleJsonPath)
|
|
143
|
-
delete ensemble.defaultModel
|
|
144
|
-
ensemble.modelsByAgent = {
|
|
145
|
-
...ensemble.modelsByAgent,
|
|
146
|
-
plan: planModel,
|
|
147
|
-
build: buildModel,
|
|
148
|
-
explore: fastModel,
|
|
149
|
-
}
|
|
150
|
-
await fse.writeJson(ensembleJsonPath, ensemble, { spaces: 2 })
|
|
151
|
-
success(`plan model -> ${planModel} (written to .opencode/ensemble.json)`)
|
|
152
|
-
success(`build model -> ${buildModel} (written to .opencode/ensemble.json)`)
|
|
153
|
-
success(`fast model -> ${fastModel} (written to .opencode/ensemble.json)`)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
console.log()
|
|
157
|
-
warn('Make sure you have API access to the selected models.')
|
|
158
|
-
warn('Change them anytime in .agents/agents/<name>.md and .opencode/opencode.json')
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
planModel,
|
|
162
|
-
buildModel,
|
|
163
|
-
fastModel,
|
|
164
|
-
}
|
|
165
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { select } from '@inquirer/prompts'
|
|
2
|
-
import fse from 'fs-extra'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import { fileURLToPath } from 'url'
|
|
5
|
-
import { header, success } from '../utils/exec.js'
|
|
6
|
-
|
|
7
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
8
|
-
const PLATFORMS_PRESET_PATH = path.resolve(__dirname, '../presets/platforms.json')
|
|
9
|
-
|
|
10
|
-
const platformsPreset = await fse.readJson(PLATFORMS_PRESET_PATH)
|
|
11
|
-
|
|
12
|
-
export async function choosePlatform() {
|
|
13
|
-
header('Step 4, Version control platform')
|
|
14
|
-
|
|
15
|
-
const platform = await select({
|
|
16
|
-
message: 'Which platform are you using?',
|
|
17
|
-
choices: platformsPreset.map(p => ({ name: p.name, value: p.value })),
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
success(`Platform: ${platform === 'github' ? 'GitHub' : 'Azure DevOps'}`)
|
|
21
|
-
return platform
|
|
22
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { select } from '@inquirer/prompts'
|
|
2
|
-
import { execa } from 'execa'
|
|
3
|
-
import fse from 'fs-extra'
|
|
4
|
-
import path from 'path'
|
|
5
|
-
import { fileURLToPath } from 'url'
|
|
6
|
-
import { header, info, success, warn } from '../utils/exec.js'
|
|
7
|
-
|
|
8
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
9
|
-
const CONTENT_SKILLS_DIR = path.resolve(__dirname, '../../content/.agents/skills')
|
|
10
|
-
|
|
11
|
-
async function installObSkills() {
|
|
12
|
-
const destSkillsDir = path.join(process.cwd(), '.agents', 'skills')
|
|
13
|
-
await fse.ensureDir(destSkillsDir)
|
|
14
|
-
|
|
15
|
-
const skills = await fse.readdir(CONTENT_SKILLS_DIR)
|
|
16
|
-
for (const skill of skills) {
|
|
17
|
-
const src = path.join(CONTENT_SKILLS_DIR, skill)
|
|
18
|
-
const dest = path.join(destSkillsDir, skill)
|
|
19
|
-
const stat = await fse.stat(src)
|
|
20
|
-
if (!stat.isDirectory()) continue
|
|
21
|
-
if (await fse.pathExists(dest)) {
|
|
22
|
-
info(`${skill} already exists, skipping`)
|
|
23
|
-
continue
|
|
24
|
-
}
|
|
25
|
-
await fse.copy(src, dest)
|
|
26
|
-
success(`Installed skill: ${skill}`)
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export async function chooseSkillsProvider() {
|
|
31
|
-
header('Step 8, Installing skills')
|
|
32
|
-
|
|
33
|
-
// ob-skills are always installed, mandatory
|
|
34
|
-
info('Installing built-in ob-skills...')
|
|
35
|
-
await installObSkills()
|
|
36
|
-
console.log()
|
|
37
|
-
|
|
38
|
-
info('Skills provide platform and tech-specific knowledge to your agents.')
|
|
39
|
-
info('Agents detect and load skills automatically, you never need to specify them.')
|
|
40
|
-
info('You can add more skills on top of the built-in ones.')
|
|
41
|
-
console.log()
|
|
42
|
-
|
|
43
|
-
const selected = await select({
|
|
44
|
-
message: 'Add additional skills from:',
|
|
45
|
-
choices: [
|
|
46
|
-
{
|
|
47
|
-
name: 'npx skills (vercel-labs/skills)',
|
|
48
|
-
value: 'npx-skills',
|
|
49
|
-
description: 'Install skills from the vercel-labs community skills registry',
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
name: 'None, built-in skills are enough',
|
|
53
|
-
value: 'none',
|
|
54
|
-
},
|
|
55
|
-
],
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
if (selected === 'none') {
|
|
59
|
-
return { additionalSkillsProvider: 'none' }
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (selected === 'npx-skills') {
|
|
63
|
-
info('Running npx skills...')
|
|
64
|
-
console.log()
|
|
65
|
-
try {
|
|
66
|
-
await execa('npx', ['skills'], {
|
|
67
|
-
cwd: process.cwd(),
|
|
68
|
-
stdio: 'inherit',
|
|
69
|
-
reject: false,
|
|
70
|
-
})
|
|
71
|
-
} catch (err) {
|
|
72
|
-
warn(`npx skills failed: ${err.message}`)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return { additionalSkillsProvider: 'npx-skills' }
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return { additionalSkillsProvider: selected }
|
|
79
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import fse from 'fs-extra'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import { fileURLToPath } from 'url'
|
|
4
|
-
import { copyContent } from '../utils/copy.js'
|
|
5
|
-
import { error, header, success } from '../utils/exec.js'
|
|
6
|
-
|
|
7
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
8
|
-
const CONTENT_DIR = path.resolve(__dirname, '../../content')
|
|
9
|
-
|
|
10
|
-
function formatRootsForText(roots = [], cwd = process.cwd()) {
|
|
11
|
-
if (!roots.length) return ['current folder']
|
|
12
|
-
return roots.map(r => {
|
|
13
|
-
const rel = path.relative(cwd, r)
|
|
14
|
-
if (!rel || rel === '') return 'current folder'
|
|
15
|
-
if (!rel.startsWith('..')) return rel
|
|
16
|
-
return rel.replace(/\\/g, '/')
|
|
17
|
-
})
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async function patchSourceScopeFiles(dest, ctx) {
|
|
21
|
-
const roots = formatRootsForText(ctx.sourceRoots || [dest], dest)
|
|
22
|
-
const rootsInline = roots.join(', ')
|
|
23
|
-
const rootsBullets = roots.map(r => ` - ${r}`).join('\n')
|
|
24
|
-
|
|
25
|
-
const agentsPath = path.join(dest, 'AGENTS.md')
|
|
26
|
-
if (await fse.pathExists(agentsPath)) {
|
|
27
|
-
let content = await fse.readFile(agentsPath, 'utf-8')
|
|
28
|
-
content = content.replace(
|
|
29
|
-
'Before scanning, load source roots from `.agents/source-roots.json` when present. Only scan those roots plus this repo\'s docs/config files.',
|
|
30
|
-
`Source roots selected during onboarding (scan these roots plus this repo docs/config): ${rootsInline}.`
|
|
31
|
-
)
|
|
32
|
-
content = content.replace(
|
|
33
|
-
'4. **Analyze the actual codebase**: use `.agents/source-roots.json` as source roots when present, then read CSS files, Tailwind config, component files, token definitions. Do not rely on prior knowledge, read the files.',
|
|
34
|
-
`4. **Analyze the actual codebase** using these source roots:\n${rootsBullets}\n\n Then read CSS files, Tailwind config, component files, token definitions. Do not rely on prior knowledge, read the files.`
|
|
35
|
-
)
|
|
36
|
-
content = content.replace(
|
|
37
|
-
'4. **Analyze the actual codebase**: use `.agents/source-roots.json` as source roots when present, then read folder structure, config files, route definitions, data models, integration points. Do not rely on prior knowledge, read the files.',
|
|
38
|
-
`4. **Analyze the actual codebase** using these source roots:\n${rootsBullets}\n\n Then read folder structure, config files, route definitions, data models, integration points. Do not rely on prior knowledge, read the files.`
|
|
39
|
-
)
|
|
40
|
-
content = content.replace(
|
|
41
|
-
'- Read source scope from `.agents/source-roots.json`.',
|
|
42
|
-
`- Source roots selected during onboarding: ${rootsInline}.`
|
|
43
|
-
)
|
|
44
|
-
await fse.writeFile(agentsPath, content, 'utf-8')
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const designPath = path.join(dest, 'DESIGN.md')
|
|
48
|
-
if (await fse.pathExists(designPath)) {
|
|
49
|
-
let content = await fse.readFile(designPath, 'utf-8')
|
|
50
|
-
const injection = `\nSource roots selected during onboarding:\n${rootsBullets}\n\nWhen analyzing, read UI/design evidence only from these roots.\n\nIn the generated DESIGN.md, add this section near the top:\n\n## Source Roots Used\n${rootsBullets}\n`
|
|
51
|
-
content = content.replace(
|
|
52
|
-
'Analyze the design system of this codebase with the goal of creating a DESIGN.md file in the project root and giving the user a file for easy copy & pasting.',
|
|
53
|
-
'Analyze the design system of this codebase with the goal of creating a DESIGN.md file in the project root and giving the user a file for easy copy & pasting.' + injection
|
|
54
|
-
)
|
|
55
|
-
await fse.writeFile(designPath, content, 'utf-8')
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const architecturePath = path.join(dest, 'ARCHITECTURE.md')
|
|
59
|
-
if (await fse.pathExists(architecturePath)) {
|
|
60
|
-
let content = await fse.readFile(architecturePath, 'utf-8')
|
|
61
|
-
const injection = `\nSource roots selected during onboarding:\n${rootsBullets}\n\nWhen analyzing, read architecture evidence only from these roots.\n\nIn the generated ARCHITECTURE.md, add this section near the top:\n\n## Source Roots Used\n${rootsBullets}\n`
|
|
62
|
-
content = content.replace(
|
|
63
|
-
'Analyze the architecture of this codebase with the goal of creating an ARCHITECTURE.md file in the project root and giving the user a file for easy copy & pasting.',
|
|
64
|
-
'Analyze the architecture of this codebase with the goal of creating an ARCHITECTURE.md file in the project root and giving the user a file for easy copy & pasting.' + injection
|
|
65
|
-
)
|
|
66
|
-
await fse.writeFile(architecturePath, content, 'utf-8')
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export async function copyContentStep(platform, ctx = {}) {
|
|
71
|
-
header('Step 6, Copying opencode-onboard files')
|
|
72
|
-
|
|
73
|
-
const dest = process.cwd()
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
await copyContent(CONTENT_DIR, dest, platform, ctx)
|
|
77
|
-
const rootsFile = path.join(dest, '.agents', 'source-roots.json')
|
|
78
|
-
await fse.ensureDir(path.dirname(rootsFile))
|
|
79
|
-
await fse.writeJson(rootsFile, {
|
|
80
|
-
mode: ctx.sourceMode || 'current',
|
|
81
|
-
roots: ctx.sourceRoots || [dest],
|
|
82
|
-
}, { spaces: 2 })
|
|
83
|
-
await patchSourceScopeFiles(dest, ctx)
|
|
84
|
-
success('Files copied to project root')
|
|
85
|
-
} catch (err) {
|
|
86
|
-
error(`Failed to copy content: ${err.message}`)
|
|
87
|
-
process.exit(1)
|
|
88
|
-
}
|
|
89
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import path from 'node:path'
|
|
2
|
-
import fse from 'fs-extra'
|
|
3
|
-
import { info, success, warn } from '../utils/exec.js'
|
|
4
|
-
|
|
5
|
-
const MARKER_START = '<!-- CAVEMAN-GUIDANCE-START -->'
|
|
6
|
-
const MARKER_END = '<!-- CAVEMAN-GUIDANCE-END -->'
|
|
7
|
-
|
|
8
|
-
const AGENTS_BLOCK = `${MARKER_START}
|
|
9
|
-
## Caveman Mode
|
|
10
|
-
|
|
11
|
-
caveman mode. Active now. Every response. No revert.
|
|
12
|
-
${MARKER_END}`
|
|
13
|
-
|
|
14
|
-
const OPSX_BLOCK = `${MARKER_START}
|
|
15
|
-
## Caveman Mode
|
|
16
|
-
|
|
17
|
-
caveman mode. Active now. Every response. No revert.
|
|
18
|
-
${MARKER_END}`
|
|
19
|
-
|
|
20
|
-
async function appendBlockIfMissing(filePath, block) {
|
|
21
|
-
if (!await fse.pathExists(filePath)) return { ok: false, reason: 'missing-file' }
|
|
22
|
-
|
|
23
|
-
const current = await fse.readFile(filePath, 'utf-8')
|
|
24
|
-
if (current.includes(MARKER_START)) return { ok: true, changed: false }
|
|
25
|
-
|
|
26
|
-
const next = `${current.replace(/\s*$/, '')}\n\n${block}\n`
|
|
27
|
-
await fse.writeFile(filePath, next, 'utf-8')
|
|
28
|
-
return { ok: true, changed: true }
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function enableCavemanGuidance(cavemanResult) {
|
|
32
|
-
if (!cavemanResult?.installed) {
|
|
33
|
-
info('Caveman guidance skipped (caveman not installed)')
|
|
34
|
-
return { enabled: false }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const targets = [
|
|
38
|
-
{ rel: 'AGENTS.md', block: AGENTS_BLOCK },
|
|
39
|
-
]
|
|
40
|
-
|
|
41
|
-
const commandsDir = path.join(process.cwd(), '.opencode', 'commands')
|
|
42
|
-
if (await fse.pathExists(commandsDir)) {
|
|
43
|
-
const entries = await fse.readdir(commandsDir)
|
|
44
|
-
for (const name of entries) {
|
|
45
|
-
if (!/^opsx-.*\.md$/i.test(name)) continue
|
|
46
|
-
targets.push({ rel: path.join('.opencode', 'commands', name), block: OPSX_BLOCK })
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const skillsDir = path.join(process.cwd(), '.opencode', 'skills')
|
|
51
|
-
if (await fse.pathExists(skillsDir)) {
|
|
52
|
-
const skillEntries = await fse.readdir(skillsDir)
|
|
53
|
-
for (const dirName of skillEntries) {
|
|
54
|
-
if (!/^openspec-/i.test(dirName)) continue
|
|
55
|
-
targets.push({ rel: path.join('.opencode', 'skills', dirName, 'SKILL.md'), block: OPSX_BLOCK })
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
let changedCount = 0
|
|
60
|
-
for (const target of targets) {
|
|
61
|
-
const abs = path.join(process.cwd(), target.rel)
|
|
62
|
-
try {
|
|
63
|
-
const res = await appendBlockIfMissing(abs, target.block)
|
|
64
|
-
if (!res.ok) {
|
|
65
|
-
warn(`Could not apply caveman guidance to ${target.rel} (${res.reason})`)
|
|
66
|
-
} else if (res.changed) {
|
|
67
|
-
changedCount++
|
|
68
|
-
}
|
|
69
|
-
} catch (err) {
|
|
70
|
-
warn(`Could not apply caveman guidance to ${target.rel}: ${err.message}`)
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (changedCount > 0) success('Caveman guidance enabled in AGENTS/OpenSpec prompts')
|
|
75
|
-
else info('Caveman guidance already present')
|
|
76
|
-
|
|
77
|
-
return { enabled: true, patchedFiles: changedCount }
|
|
78
|
-
}
|