@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,109 +1,109 @@
|
|
|
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 { code, commandExists, header, info, success, warn } from '../../utils/exec.js'
|
|
7
|
-
|
|
8
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
9
|
-
const PLATFORMS_PRESET_PATH = path.resolve(__dirname, '../../presets/platforms.json')
|
|
10
|
-
const platformsPreset = await fse.readJson(PLATFORMS_PRESET_PATH)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export async function checkPlatform(platform) {
|
|
14
|
-
const preset = platformsPreset.find(p => p.value === platform) || platformsPreset[0]
|
|
15
|
-
if (!preset.cli) {
|
|
16
|
-
header(`Step 4, Checking ${preset.name} CLI`)
|
|
17
|
-
info('No platform integration selected, skipping CLI checks.')
|
|
18
|
-
success(`Platform: ${preset.name}`)
|
|
19
|
-
return
|
|
20
|
-
}
|
|
21
|
-
await checkPlatformCli(preset)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export async function choosePlatform() {
|
|
25
|
-
header('Step 3, Version control platform')
|
|
26
|
-
|
|
27
|
-
const backlogChoices = platformsPreset.filter(p => !p.repoOnly)
|
|
28
|
-
const backlogPlatform = await select({
|
|
29
|
-
message: 'Where is your backlog (work items / issues)?',
|
|
30
|
-
choices: backlogChoices.map(p => ({ name: p.name, value: p.value })),
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
const backlogPreset = platformsPreset.find(p => p.value === backlogPlatform)
|
|
34
|
-
success(`Backlog platform: ${backlogPreset?.name || backlogPlatform}`)
|
|
35
|
-
|
|
36
|
-
let repoPlatform = backlogPlatform
|
|
37
|
-
if (backlogPlatform !== 'none') {
|
|
38
|
-
// Exclude backlog-only platforms (e.g. Jira) from repo choices
|
|
39
|
-
const repoChoices = platformsPreset.filter(p => !p.backlogOnly)
|
|
40
|
-
repoPlatform = await select({
|
|
41
|
-
message: 'Where is your code repository (PRs / code reviews)?',
|
|
42
|
-
choices: repoChoices.map(p => ({ name: p.name, value: p.value })),
|
|
43
|
-
default: backlogPreset.backlogOnly ? 'github' : backlogPlatform,
|
|
44
|
-
})
|
|
45
|
-
const repoPreset = platformsPreset.find(p => p.value === repoPlatform)
|
|
46
|
-
success(`Repo platform: ${repoPreset?.name || repoPlatform}`)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Check CLIs for both platforms (deduped)
|
|
50
|
-
const platformsToCheck = [...new Set([backlogPlatform, repoPlatform])].filter(p => p !== 'none')
|
|
51
|
-
for (const p of platformsToCheck) {
|
|
52
|
-
const preset = platformsPreset.find(x => x.value === p)
|
|
53
|
-
if (preset?.cli) await checkPlatformCli(preset)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return { backlogPlatform, repoPlatform }
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async function checkPlatformCli(platformPreset) {
|
|
60
|
-
const { cli } = platformPreset
|
|
61
|
-
header(`Step 4, Checking ${platformPreset.name} CLI`)
|
|
62
|
-
|
|
63
|
-
const hasCli = await commandExists(cli.command)
|
|
64
|
-
if (!hasCli) {
|
|
65
|
-
warn(`${cli.displayName} not found.`)
|
|
66
|
-
info(`Install from: ${cli.installUrl}`)
|
|
67
|
-
if (cli.authCheck?.commands?.length) {
|
|
68
|
-
console.log()
|
|
69
|
-
info('After installing, authenticate with:')
|
|
70
|
-
code(cli.authCheck.commands)
|
|
71
|
-
}
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
success(`${cli.displayName} available`)
|
|
75
|
-
|
|
76
|
-
if (cli.authCheck) await runAuthCheck(cli)
|
|
77
|
-
if (cli.extensionCheck) await runExtensionCheck(cli)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async function runAuthCheck(cli) {
|
|
81
|
-
try {
|
|
82
|
-
const result = await execa(cli.command, cli.authCheck.args, { reject: false })
|
|
83
|
-
if (result.exitCode === 0) {
|
|
84
|
-
success(`${cli.displayName} authenticated`)
|
|
85
|
-
} else {
|
|
86
|
-
warn(cli.authCheck.notAuthenticatedMessage)
|
|
87
|
-
code(cli.authCheck.commands)
|
|
88
|
-
}
|
|
89
|
-
} catch {
|
|
90
|
-
warn(`Could not check ${cli.command} auth status.`)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async function runExtensionCheck(cli) {
|
|
95
|
-
try {
|
|
96
|
-
const result = await execa(cli.command, cli.extensionCheck.args, { reject: false })
|
|
97
|
-
const hasExtension = result.stdout && result.stdout.includes(cli.extensionCheck.expectedOutput)
|
|
98
|
-
|
|
99
|
-
if (hasExtension) {
|
|
100
|
-
success(`${cli.extensionCheck.expectedOutput} extension installed`)
|
|
101
|
-
} else {
|
|
102
|
-
warn(cli.extensionCheck.missingMessage)
|
|
103
|
-
code(cli.extensionCheck.commands)
|
|
104
|
-
}
|
|
105
|
-
} catch {
|
|
106
|
-
warn(cli.extensionCheck.errorMessage)
|
|
107
|
-
code(cli.extensionCheck.commands)
|
|
108
|
-
}
|
|
109
|
-
}
|
|
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 { code, commandExists, header, info, success, warn } from '../../utils/exec.js'
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
const PLATFORMS_PRESET_PATH = path.resolve(__dirname, '../../presets/platforms.json')
|
|
10
|
+
const platformsPreset = await fse.readJson(PLATFORMS_PRESET_PATH)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export async function checkPlatform(platform) {
|
|
14
|
+
const preset = platformsPreset.find(p => p.value === platform) || platformsPreset[0]
|
|
15
|
+
if (!preset.cli) {
|
|
16
|
+
header(`Step 4, Checking ${preset.name} CLI`)
|
|
17
|
+
info('No platform integration selected, skipping CLI checks.')
|
|
18
|
+
success(`Platform: ${preset.name}`)
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
await checkPlatformCli(preset)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function choosePlatform() {
|
|
25
|
+
header('Step 3, Version control platform')
|
|
26
|
+
|
|
27
|
+
const backlogChoices = platformsPreset.filter(p => !p.repoOnly)
|
|
28
|
+
const backlogPlatform = await select({
|
|
29
|
+
message: 'Where is your backlog (work items / issues)?',
|
|
30
|
+
choices: backlogChoices.map(p => ({ name: p.name, value: p.value })),
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const backlogPreset = platformsPreset.find(p => p.value === backlogPlatform)
|
|
34
|
+
success(`Backlog platform: ${backlogPreset?.name || backlogPlatform}`)
|
|
35
|
+
|
|
36
|
+
let repoPlatform = backlogPlatform
|
|
37
|
+
if (backlogPlatform !== 'none') {
|
|
38
|
+
// Exclude backlog-only platforms (e.g. Jira) from repo choices
|
|
39
|
+
const repoChoices = platformsPreset.filter(p => !p.backlogOnly)
|
|
40
|
+
repoPlatform = await select({
|
|
41
|
+
message: 'Where is your code repository (PRs / code reviews)?',
|
|
42
|
+
choices: repoChoices.map(p => ({ name: p.name, value: p.value })),
|
|
43
|
+
default: backlogPreset.backlogOnly ? 'github' : backlogPlatform,
|
|
44
|
+
})
|
|
45
|
+
const repoPreset = platformsPreset.find(p => p.value === repoPlatform)
|
|
46
|
+
success(`Repo platform: ${repoPreset?.name || repoPlatform}`)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Check CLIs for both platforms (deduped)
|
|
50
|
+
const platformsToCheck = [...new Set([backlogPlatform, repoPlatform])].filter(p => p !== 'none')
|
|
51
|
+
for (const p of platformsToCheck) {
|
|
52
|
+
const preset = platformsPreset.find(x => x.value === p)
|
|
53
|
+
if (preset?.cli) await checkPlatformCli(preset)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { backlogPlatform, repoPlatform }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function checkPlatformCli(platformPreset) {
|
|
60
|
+
const { cli } = platformPreset
|
|
61
|
+
header(`Step 4, Checking ${platformPreset.name} CLI`)
|
|
62
|
+
|
|
63
|
+
const hasCli = await commandExists(cli.command)
|
|
64
|
+
if (!hasCli) {
|
|
65
|
+
warn(`${cli.displayName} not found.`)
|
|
66
|
+
info(`Install from: ${cli.installUrl}`)
|
|
67
|
+
if (cli.authCheck?.commands?.length) {
|
|
68
|
+
console.log()
|
|
69
|
+
info('After installing, authenticate with:')
|
|
70
|
+
code(cli.authCheck.commands)
|
|
71
|
+
}
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
success(`${cli.displayName} available`)
|
|
75
|
+
|
|
76
|
+
if (cli.authCheck) await runAuthCheck(cli)
|
|
77
|
+
if (cli.extensionCheck) await runExtensionCheck(cli)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function runAuthCheck(cli) {
|
|
81
|
+
try {
|
|
82
|
+
const result = await execa(cli.command, cli.authCheck.args, { reject: false })
|
|
83
|
+
if (result.exitCode === 0) {
|
|
84
|
+
success(`${cli.displayName} authenticated`)
|
|
85
|
+
} else {
|
|
86
|
+
warn(cli.authCheck.notAuthenticatedMessage)
|
|
87
|
+
code(cli.authCheck.commands)
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
warn(`Could not check ${cli.command} auth status.`)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function runExtensionCheck(cli) {
|
|
95
|
+
try {
|
|
96
|
+
const result = await execa(cli.command, cli.extensionCheck.args, { reject: false })
|
|
97
|
+
const hasExtension = result.stdout && result.stdout.includes(cli.extensionCheck.expectedOutput)
|
|
98
|
+
|
|
99
|
+
if (hasExtension) {
|
|
100
|
+
success(`${cli.extensionCheck.expectedOutput} extension installed`)
|
|
101
|
+
} else {
|
|
102
|
+
warn(cli.extensionCheck.missingMessage)
|
|
103
|
+
code(cli.extensionCheck.commands)
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
warn(cli.extensionCheck.errorMessage)
|
|
107
|
+
code(cli.extensionCheck.commands)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -1,123 +1,123 @@
|
|
|
1
|
-
import { checkbox, select } from '@inquirer/prompts'
|
|
2
|
-
import fse from 'fs-extra'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import { fileURLToPath } from 'url'
|
|
5
|
-
import { header, info, success, warn } from '../../utils/exec.js'
|
|
6
|
-
|
|
7
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
8
|
-
const SOURCE_PRESET_PATH = path.resolve(__dirname, '../../presets/source.json')
|
|
9
|
-
const sourcePreset = await fse.readJson(SOURCE_PRESET_PATH)
|
|
10
|
-
|
|
11
|
-
async function listChildFolders(cwd) {
|
|
12
|
-
const entries = await fse.readdir(cwd)
|
|
13
|
-
const dirs = []
|
|
14
|
-
|
|
15
|
-
for (const name of entries) {
|
|
16
|
-
if (name.startsWith('.')) continue
|
|
17
|
-
const abs = path.join(cwd, name)
|
|
18
|
-
try {
|
|
19
|
-
const stat = await fse.stat(abs)
|
|
20
|
-
if (!stat.isDirectory()) continue
|
|
21
|
-
dirs.push({ name, abs })
|
|
22
|
-
} catch {
|
|
23
|
-
// ignore invalid entries
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
dirs.sort((a, b) => a.name.localeCompare(b.name))
|
|
28
|
-
return dirs
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function listParentFolders(cwd) {
|
|
32
|
-
const parent = path.resolve(cwd, '..')
|
|
33
|
-
const entries = await fse.readdir(parent)
|
|
34
|
-
const dirs = []
|
|
35
|
-
|
|
36
|
-
for (const name of entries) {
|
|
37
|
-
if (name.startsWith('.')) continue
|
|
38
|
-
const abs = path.join(parent, name)
|
|
39
|
-
try {
|
|
40
|
-
const stat = await fse.stat(abs)
|
|
41
|
-
if (!stat.isDirectory()) continue
|
|
42
|
-
if (path.resolve(abs) === path.resolve(cwd)) continue
|
|
43
|
-
dirs.push({ name, abs })
|
|
44
|
-
} catch {
|
|
45
|
-
// ignore invalid entries
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
dirs.sort((a, b) => a.name.localeCompare(b.name))
|
|
50
|
-
return dirs
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export async function chooseSourceScope() {
|
|
54
|
-
header('Step 1, Source code scope')
|
|
55
|
-
|
|
56
|
-
const cwd = process.cwd()
|
|
57
|
-
info('Choose where agents should read source code from during init analysis.')
|
|
58
|
-
|
|
59
|
-
const mode = await select({
|
|
60
|
-
message: sourcePreset.message,
|
|
61
|
-
default: sourcePreset.default,
|
|
62
|
-
choices: sourcePreset.choices,
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
if (mode === 'current') {
|
|
66
|
-
success(`Source scope: ${cwd}`)
|
|
67
|
-
return { sourceMode: 'current', sourceRoots: [cwd] }
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (mode === 'children') {
|
|
71
|
-
const childFolders = await listChildFolders(cwd)
|
|
72
|
-
if (childFolders.length === 0) {
|
|
73
|
-
warn('No child folders found in current directory. Falling back to current folder.')
|
|
74
|
-
success(`Source scope: ${cwd}`)
|
|
75
|
-
return { sourceMode: 'current', sourceRoots: [cwd] }
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const selected = await checkbox({
|
|
79
|
-
message: sourcePreset.childrenSelectionMessage,
|
|
80
|
-
choices: childFolders.map(d => ({
|
|
81
|
-
name: `./${d.name}`,
|
|
82
|
-
value: d.abs,
|
|
83
|
-
checked: true,
|
|
84
|
-
})),
|
|
85
|
-
required: true,
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
if (!selected || selected.length === 0) {
|
|
89
|
-
warn('No folders selected. Falling back to current folder.')
|
|
90
|
-
success(`Source scope: ${cwd}`)
|
|
91
|
-
return { sourceMode: 'current', sourceRoots: [cwd] }
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
success(`Source scope: ${selected.map(p => path.basename(p)).join(', ')}`)
|
|
95
|
-
return { sourceMode: 'children-selected', sourceRoots: selected }
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const parentFolders = await listParentFolders(cwd)
|
|
99
|
-
if (parentFolders.length === 0) {
|
|
100
|
-
warn('No sibling folders found in parent directory. Falling back to current folder.')
|
|
101
|
-
success(`Source scope: ${cwd}`)
|
|
102
|
-
return { sourceMode: 'current', sourceRoots: [cwd] }
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const selected = await checkbox({
|
|
106
|
-
message: sourcePreset.parentSelectionMessage,
|
|
107
|
-
choices: parentFolders.map(d => ({
|
|
108
|
-
name: `../${d.name}`,
|
|
109
|
-
value: d.abs,
|
|
110
|
-
checked: true,
|
|
111
|
-
})),
|
|
112
|
-
required: true,
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
if (!selected || selected.length === 0) {
|
|
116
|
-
warn('No folders selected. Falling back to current folder.')
|
|
117
|
-
success(`Source scope: ${cwd}`)
|
|
118
|
-
return { sourceMode: 'current', sourceRoots: [cwd] }
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
success(`Source scope: ${selected.map(p => path.basename(p)).join(', ')}`)
|
|
122
|
-
return { sourceMode: 'parent-selected', sourceRoots: selected }
|
|
123
|
-
}
|
|
1
|
+
import { checkbox, select } from '@inquirer/prompts'
|
|
2
|
+
import fse from 'fs-extra'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
import { header, info, success, warn } from '../../utils/exec.js'
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
8
|
+
const SOURCE_PRESET_PATH = path.resolve(__dirname, '../../presets/source.json')
|
|
9
|
+
const sourcePreset = await fse.readJson(SOURCE_PRESET_PATH)
|
|
10
|
+
|
|
11
|
+
async function listChildFolders(cwd) {
|
|
12
|
+
const entries = await fse.readdir(cwd)
|
|
13
|
+
const dirs = []
|
|
14
|
+
|
|
15
|
+
for (const name of entries) {
|
|
16
|
+
if (name.startsWith('.')) continue
|
|
17
|
+
const abs = path.join(cwd, name)
|
|
18
|
+
try {
|
|
19
|
+
const stat = await fse.stat(abs)
|
|
20
|
+
if (!stat.isDirectory()) continue
|
|
21
|
+
dirs.push({ name, abs })
|
|
22
|
+
} catch {
|
|
23
|
+
// ignore invalid entries
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
dirs.sort((a, b) => a.name.localeCompare(b.name))
|
|
28
|
+
return dirs
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function listParentFolders(cwd) {
|
|
32
|
+
const parent = path.resolve(cwd, '..')
|
|
33
|
+
const entries = await fse.readdir(parent)
|
|
34
|
+
const dirs = []
|
|
35
|
+
|
|
36
|
+
for (const name of entries) {
|
|
37
|
+
if (name.startsWith('.')) continue
|
|
38
|
+
const abs = path.join(parent, name)
|
|
39
|
+
try {
|
|
40
|
+
const stat = await fse.stat(abs)
|
|
41
|
+
if (!stat.isDirectory()) continue
|
|
42
|
+
if (path.resolve(abs) === path.resolve(cwd)) continue
|
|
43
|
+
dirs.push({ name, abs })
|
|
44
|
+
} catch {
|
|
45
|
+
// ignore invalid entries
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
dirs.sort((a, b) => a.name.localeCompare(b.name))
|
|
50
|
+
return dirs
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function chooseSourceScope() {
|
|
54
|
+
header('Step 1, Source code scope')
|
|
55
|
+
|
|
56
|
+
const cwd = process.cwd()
|
|
57
|
+
info('Choose where agents should read source code from during init analysis.')
|
|
58
|
+
|
|
59
|
+
const mode = await select({
|
|
60
|
+
message: sourcePreset.message,
|
|
61
|
+
default: sourcePreset.default,
|
|
62
|
+
choices: sourcePreset.choices,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
if (mode === 'current') {
|
|
66
|
+
success(`Source scope: ${cwd}`)
|
|
67
|
+
return { sourceMode: 'current', sourceRoots: [cwd] }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (mode === 'children') {
|
|
71
|
+
const childFolders = await listChildFolders(cwd)
|
|
72
|
+
if (childFolders.length === 0) {
|
|
73
|
+
warn('No child folders found in current directory. Falling back to current folder.')
|
|
74
|
+
success(`Source scope: ${cwd}`)
|
|
75
|
+
return { sourceMode: 'current', sourceRoots: [cwd] }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const selected = await checkbox({
|
|
79
|
+
message: sourcePreset.childrenSelectionMessage,
|
|
80
|
+
choices: childFolders.map(d => ({
|
|
81
|
+
name: `./${d.name}`,
|
|
82
|
+
value: d.abs,
|
|
83
|
+
checked: true,
|
|
84
|
+
})),
|
|
85
|
+
required: true,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
if (!selected || selected.length === 0) {
|
|
89
|
+
warn('No folders selected. Falling back to current folder.')
|
|
90
|
+
success(`Source scope: ${cwd}`)
|
|
91
|
+
return { sourceMode: 'current', sourceRoots: [cwd] }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
success(`Source scope: ${selected.map(p => path.basename(p)).join(', ')}`)
|
|
95
|
+
return { sourceMode: 'children-selected', sourceRoots: selected }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const parentFolders = await listParentFolders(cwd)
|
|
99
|
+
if (parentFolders.length === 0) {
|
|
100
|
+
warn('No sibling folders found in parent directory. Falling back to current folder.')
|
|
101
|
+
success(`Source scope: ${cwd}`)
|
|
102
|
+
return { sourceMode: 'current', sourceRoots: [cwd] }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const selected = await checkbox({
|
|
106
|
+
message: sourcePreset.parentSelectionMessage,
|
|
107
|
+
choices: parentFolders.map(d => ({
|
|
108
|
+
name: `../${d.name}`,
|
|
109
|
+
value: d.abs,
|
|
110
|
+
checked: true,
|
|
111
|
+
})),
|
|
112
|
+
required: true,
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
if (!selected || selected.length === 0) {
|
|
116
|
+
warn('No folders selected. Falling back to current folder.')
|
|
117
|
+
success(`Source scope: ${cwd}`)
|
|
118
|
+
return { sourceMode: 'current', sourceRoots: [cwd] }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
success(`Source scope: ${selected.map(p => path.basename(p)).join(', ')}`)
|
|
122
|
+
return { sourceMode: 'parent-selected', sourceRoots: selected }
|
|
123
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic colour for an agent, derived from its name.
|
|
3
|
+
*
|
|
4
|
+
* opencode accepts either a hex value or one of its theme keywords. Two of
|
|
5
|
+
* those keywords are reserved for the primaries so they stay recognisable
|
|
6
|
+
* across every project: build is `primary`, plan is `warning`. Every other
|
|
7
|
+
* agent gets a hex derived from its name, so a `butterfly-engineer` is the
|
|
8
|
+
* same colour in every repository that has one, with no central registry and
|
|
9
|
+
* no chance of two agents in one project colliding by accident.
|
|
10
|
+
*
|
|
11
|
+
* Saturation and lightness are fixed rather than hashed. That is what keeps
|
|
12
|
+
* the result usable: hashing them too would eventually produce near-grey,
|
|
13
|
+
* near-black or near-white agents that are invisible against one theme or the
|
|
14
|
+
* other. Only the hue varies.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** opencode's theme keywords. Treated as unset so a name-derived hex wins. */
|
|
18
|
+
export const THEME_COLORS = new Set([
|
|
19
|
+
'primary',
|
|
20
|
+
'secondary',
|
|
21
|
+
'accent',
|
|
22
|
+
'success',
|
|
23
|
+
'warning',
|
|
24
|
+
'error',
|
|
25
|
+
'info',
|
|
26
|
+
])
|
|
27
|
+
|
|
28
|
+
/** Reserved: the two primaries, which never take a derived colour. */
|
|
29
|
+
export const RESERVED_COLORS = { build: 'primary', plan: 'warning' }
|
|
30
|
+
|
|
31
|
+
const SATURATION = 68
|
|
32
|
+
const LIGHTNESS = 52
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* FNV-1a, 32 bit. Chosen because it is short enough to duplicate verbatim in
|
|
36
|
+
* the shipped plugin and produces the same hue everywhere: the same name must
|
|
37
|
+
* resolve to the same colour on every machine and in every project.
|
|
38
|
+
*/
|
|
39
|
+
function hash(value) {
|
|
40
|
+
let h = 0x811c9dc5
|
|
41
|
+
for (let i = 0; i < value.length; i++) {
|
|
42
|
+
h ^= value.charCodeAt(i)
|
|
43
|
+
h = Math.imul(h, 0x01000193) >>> 0
|
|
44
|
+
}
|
|
45
|
+
return h >>> 0
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function hslToHex(h, s, l) {
|
|
49
|
+
const sat = s / 100
|
|
50
|
+
const light = l / 100
|
|
51
|
+
const c = (1 - Math.abs(2 * light - 1)) * sat
|
|
52
|
+
const x = c * (1 - Math.abs(((h / 60) % 2) - 1))
|
|
53
|
+
const m = light - c / 2
|
|
54
|
+
|
|
55
|
+
const [r, g, b] =
|
|
56
|
+
h < 60 ? [c, x, 0] :
|
|
57
|
+
h < 120 ? [x, c, 0] :
|
|
58
|
+
h < 180 ? [0, c, x] :
|
|
59
|
+
h < 240 ? [0, x, c] :
|
|
60
|
+
h < 300 ? [x, 0, c] :
|
|
61
|
+
[c, 0, x]
|
|
62
|
+
|
|
63
|
+
const toHex = v => Math.round((v + m) * 255).toString(16).padStart(2, '0')
|
|
64
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The colour for an agent, honouring the two reserved primaries. */
|
|
68
|
+
export function agentColor(name) {
|
|
69
|
+
if (name in RESERVED_COLORS) return RESERVED_COLORS[name]
|
|
70
|
+
return hslToHex(hash(name) % 360, SATURATION, LIGHTNESS)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Render a colour for YAML frontmatter.
|
|
75
|
+
*
|
|
76
|
+
* A hex has to be quoted. `#` opens a comment in YAML, so `color: #D83155`
|
|
77
|
+
* parses as null and opencode rejects the agent outright with "Invalid input
|
|
78
|
+
* color". Theme keywords need no quoting and are left bare.
|
|
79
|
+
*/
|
|
80
|
+
export function yamlColor(value) {
|
|
81
|
+
return value.startsWith('#') ? `"${value}"` : value
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The value of a frontmatter colour, with any quoting removed. */
|
|
85
|
+
export function unquoteColor(raw) {
|
|
86
|
+
return (raw ?? '').trim().replace(/^["']/, '').replace(/["']$/, '')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The frontmatter colour line an agent should have, or null when the current
|
|
91
|
+
* one is already correct. Covers both jobs: choosing the colour, and quoting it
|
|
92
|
+
* so the file actually parses.
|
|
93
|
+
*/
|
|
94
|
+
export function colorLineFor(name, rawCurrent) {
|
|
95
|
+
const current = unquoteColor(rawCurrent)
|
|
96
|
+
const target = shouldDeriveColor(current) ? agentColor(name) : current
|
|
97
|
+
const rendered = yamlColor(target)
|
|
98
|
+
return (rawCurrent ?? '').trim() === rendered ? null : `color: ${rendered}`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Whether a frontmatter colour should be replaced by the derived one.
|
|
103
|
+
*
|
|
104
|
+
* A theme keyword is replaced: those came from the old template, which asked
|
|
105
|
+
* whoever ran /make-engineer to pick one and avoid collisions by hand. An
|
|
106
|
+
* explicit hex is left alone, because someone chose it deliberately.
|
|
107
|
+
*/
|
|
108
|
+
export function shouldDeriveColor(current) {
|
|
109
|
+
if (!current) return true
|
|
110
|
+
return THEME_COLORS.has(current.trim().toLowerCase())
|
|
111
|
+
}
|
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
|
-
import ora from 'ora'
|
|
3
|
-
|
|
4
|
-
// ── Screen / step state ──────────────────────────────────────────────────────
|
|
5
|
-
|
|
6
|
-
const previousSteps = []; // up to 2 completed steps, each is an array of lines
|
|
7
|
-
let currentStepLines = []; // lines accumulated in the current step
|
|
8
|
-
let stepSpinner = null; // ora spinner shown while step is working
|
|
9
|
-
|
|
10
|
-
export function appendLine(line) {
|
|
11
|
-
currentStepLines.push(line);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function stopSpinner() {
|
|
15
|
-
if (stepSpinner) {
|
|
16
|
-
stepSpinner.stop();
|
|
17
|
-
stepSpinner = null;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function startSpinner(text = 'working...') {
|
|
22
|
-
stopSpinner();
|
|
23
|
-
stepSpinner = ora({ text: chalk.dim(text), color: 'red' }).start();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function redraw() {
|
|
27
|
-
if (process.stdout.isTTY) console.clear();
|
|
28
|
-
|
|
29
|
-
// Show up to 2 previous steps dimmed
|
|
30
|
-
for (const stepLines of previousSteps) {
|
|
31
|
-
for (const line of stepLines) {
|
|
32
|
-
process.stdout.write(`${chalk.dim(line)}\n`);
|
|
33
|
-
}
|
|
34
|
-
process.stdout.write('\n');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Current step output
|
|
38
|
-
for (const line of currentStepLines) {
|
|
39
|
-
process.stdout.write(`${line}\n`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function rotateStep() {
|
|
44
|
-
previousSteps.push(currentStepLines);
|
|
45
|
-
if (previousSteps.length > 2) previousSteps.shift();
|
|
46
|
-
currentStepLines = [];
|
|
47
|
-
}
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import ora from 'ora'
|
|
3
|
+
|
|
4
|
+
// ── Screen / step state ──────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
const previousSteps = []; // up to 2 completed steps, each is an array of lines
|
|
7
|
+
let currentStepLines = []; // lines accumulated in the current step
|
|
8
|
+
let stepSpinner = null; // ora spinner shown while step is working
|
|
9
|
+
|
|
10
|
+
export function appendLine(line) {
|
|
11
|
+
currentStepLines.push(line);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function stopSpinner() {
|
|
15
|
+
if (stepSpinner) {
|
|
16
|
+
stepSpinner.stop();
|
|
17
|
+
stepSpinner = null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function startSpinner(text = 'working...') {
|
|
22
|
+
stopSpinner();
|
|
23
|
+
stepSpinner = ora({ text: chalk.dim(text), color: 'red' }).start();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function redraw() {
|
|
27
|
+
if (process.stdout.isTTY) console.clear();
|
|
28
|
+
|
|
29
|
+
// Show up to 2 previous steps dimmed
|
|
30
|
+
for (const stepLines of previousSteps) {
|
|
31
|
+
for (const line of stepLines) {
|
|
32
|
+
process.stdout.write(`${chalk.dim(line)}\n`);
|
|
33
|
+
}
|
|
34
|
+
process.stdout.write('\n');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Current step output
|
|
38
|
+
for (const line of currentStepLines) {
|
|
39
|
+
process.stdout.write(`${line}\n`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function rotateStep() {
|
|
44
|
+
previousSteps.push(currentStepLines);
|
|
45
|
+
if (previousSteps.length > 2) previousSteps.shift();
|
|
46
|
+
currentStepLines = [];
|
|
47
|
+
}
|