@plainconceptsplatform/agent-harness 3.2.0 → 3.3.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/LICENSE +21 -21
- package/README.md +454 -452
- package/cli/commands/join.js +259 -259
- package/cli/commands/migrate.js +310 -310
- package/cli/commands/shared.js +27 -27
- package/cli/commands/single.js +79 -79
- package/cli/commands/update.js +121 -121
- package/cli/commands/wizard.js +134 -134
- package/cli/fragments/archive/az.md +97 -97
- package/cli/fragments/archive/gh.md +96 -96
- package/cli/fragments/archive/gl.md +96 -96
- package/cli/fragments/archive/none.md +75 -75
- package/cli/fragments/guardrails/codegraph.md +5 -5
- package/cli/fragments/guardrails/humanizer.md +4 -4
- package/cli/fragments/guardrails/memory.md +4 -4
- package/cli/fragments/guardrails/rtk.md +3 -3
- package/cli/fragments/guardrails/simple-english.md +4 -4
- package/cli/fragments/ops-backlog/az.md +28 -28
- package/cli/fragments/ops-backlog/gh.md +28 -28
- package/cli/fragments/ops-backlog/jira.md +28 -28
- package/cli/fragments/ops-review/az.md +62 -62
- package/cli/fragments/ops-review/gh.md +51 -51
- package/cli/fragments/ops-review/gl.md +55 -55
- package/cli/fragments/ops-ship/az.md +81 -81
- package/cli/fragments/ops-ship/gh.md +67 -67
- package/cli/fragments/ops-ship/gl.md +84 -84
- package/cli/index.js +116 -116
- package/cli/presets/agents-content.json +34 -34
- package/cli/presets/browser.json +10 -10
- package/cli/presets/clean.json +21 -21
- package/cli/presets/models.json +68 -68
- package/cli/presets/optimization.json +32 -32
- package/cli/presets/platforms.json +76 -76
- package/cli/presets/source.json +23 -23
- package/cli/steps/browser/index.js +48 -48
- package/cli/steps/clean/index.js +120 -120
- package/cli/steps/copy/agents.js +120 -120
- package/cli/steps/copy/commands.js +79 -79
- package/cli/steps/copy/fullstack-engineer.js +89 -89
- package/cli/steps/copy/index.js +88 -88
- package/cli/steps/copy/opencode-json.js +324 -324
- package/cli/steps/copy/skills.js +316 -307
- package/cli/steps/metadata/index.js +115 -115
- package/cli/steps/models/format.js +88 -88
- package/cli/steps/models/index.js +64 -64
- package/cli/steps/models/write.js +38 -38
- package/cli/steps/openspec/index.js +136 -136
- package/cli/steps/optimization/codegraph.js +141 -127
- package/cli/steps/optimization/detect.js +66 -66
- package/cli/steps/optimization/humanizer.js +17 -17
- package/cli/steps/optimization/index.js +158 -158
- package/cli/steps/optimization/memory.js +91 -88
- package/cli/steps/optimization/patch-guardrails.js +111 -111
- package/cli/steps/optimization/simple-english.js +17 -17
- package/cli/steps/optimization/skills-lock.js +30 -30
- package/cli/steps/platform/index.js +109 -109
- package/cli/steps/source/index.js +123 -123
- package/cli/utils/agent-color.js +111 -111
- package/cli/utils/copy.js +134 -134
- package/cli/utils/exec-spinner.js +47 -47
- package/cli/utils/exec.js +134 -134
- package/cli/utils/legacy-check.js +30 -30
- package/cli/utils/models-cache.js +58 -58
- package/cli/utils/models-pricing.js +42 -42
- package/cli/utils/paths.js +80 -80
- package/cli/utils/process.js +3 -3
- package/cli/utils/terminal.js +6 -6
- package/cli/utils/update-manifest.js +75 -75
- package/harness/.opencode/_gitignore +9 -9
- package/harness/.opencode/commands/init.md +5 -5
- package/harness/.opencode/commands/make-architecture.md +5 -5
- package/harness/.opencode/commands/make-design.md +5 -5
- package/harness/.opencode/commands/make-engineer.md +5 -5
- package/harness/.opencode/commands/make-guardrails.md +5 -5
- package/harness/.opencode/commands/make-user-model.md +5 -5
- package/harness/.opencode/commands/ops-backlog.md +10 -10
- package/harness/.opencode/commands/ops-review.md +8 -8
- package/harness/.opencode/commands/ops-ship.md +9 -9
- package/harness/.opencode/commands/plan-apply.md +9 -9
- package/harness/.opencode/commands/plan-archive.md +5 -5
- package/harness/.opencode/commands/plan-explore.md +9 -9
- package/harness/.opencode/commands/plan-goal.md +5 -5
- package/harness/.opencode/commands/plan-propose.md +9 -9
- package/harness/.opencode/commands/plan-quick.md +5 -5
- package/harness/.opencode/commands/plan-story.md +9 -9
- package/harness/.opencode/commands/repo-audit.md +5 -5
- package/harness/.opencode/commands/repo-help.md +5 -5
- package/harness/.opencode/commands/repo-initialize.md +5 -5
- package/harness/.opencode/commands/repo-onboard.md +5 -5
- package/harness/.opencode/commands/repo-verify.md +5 -5
- package/harness/.opencode/package.json +5 -5
- package/harness/.opencode/plugins/pc-subagent-monitor.js +184 -184
- package/harness/.opencode/plugins/pc-subagent-tiers.js +354 -354
- package/harness/.opencode/plugins/pc-system-reminders.js +386 -386
- package/harness/.opencode/scripts/check-diff.mjs +26 -7
- package/harness/.opencode/scripts/code-rules.mjs +288 -280
- package/harness/AGENTS.md +49 -49
- package/harness/ARCHITECTURE.md +13 -13
- package/harness/DESIGN.md +13 -13
- package/harness/opencode.jsonc +36 -36
- package/harness/openspec/config.yaml +20 -20
- package/harness/skills-lock.json +17 -17
- package/package.json +14 -12
- package/skills/README.md +18 -18
- package/skills/agent-harness-cli/SKILL.md +86 -86
- package/skills/browser-automation/SKILL.md +72 -72
- package/skills/pc-guardrails-project/SKILL.md +8 -8
- package/skills/pc-make-architecture/SKILL.md +32 -32
- package/skills/pc-make-architecture/structure-template.md +64 -64
- package/skills/pc-make-design/SKILL.md +45 -45
- package/skills/pc-make-engineer/SKILL.md +59 -59
- package/skills/pc-make-engineer/signal-mapping.md +53 -53
- package/skills/pc-make-engineer/template.md +36 -36
- package/skills/pc-make-guardrails/SKILL.md +44 -44
- package/skills/pc-make-guardrails/category-reference.md +112 -112
- package/skills/pc-make-merge-risk-assess/SKILL.md +70 -70
- package/skills/pc-make-merge-risk-assess/category-reference.md +114 -114
- package/skills/pc-make-user-model/SKILL.md +56 -56
- package/skills/pc-ops-ship/SKILL.md +22 -18
- package/skills/pc-plan-apply/SKILL.md +86 -86
- package/skills/pc-plan-apply/simple-mode.md +21 -21
- package/skills/pc-plan-archive/SKILL.md +66 -66
- package/skills/pc-plan-explore/SKILL.md +21 -21
- package/skills/pc-plan-goal/SKILL.md +91 -91
- package/skills/pc-plan-goal/branching.md +39 -39
- package/skills/pc-plan-goal/failure-policy.md +30 -30
- package/skills/pc-plan-goal/output-mode.md +10 -10
- package/skills/pc-plan-goal/output.md +71 -71
- package/skills/pc-plan-propose/SKILL.md +125 -125
- package/skills/pc-plan-propose/task-annotation.md +39 -39
- package/skills/pc-plan-quick/SKILL.md +46 -46
- package/skills/pc-plan-story/SKILL.md +93 -93
- package/skills/pc-repo-audit/SKILL.md +44 -44
- package/skills/pc-repo-help/SKILL.md +87 -87
- package/skills/pc-repo-initialize/SKILL.md +119 -119
- package/skills/pc-repo-onboard/SKILL.md +32 -32
- package/skills/pc-repo-verify/SKILL.md +150 -150
- package/skills/pc-userstory-az/SKILL.md +71 -71
- package/skills/pc-userstory-browser/SKILL.md +64 -64
- package/skills/pc-userstory-gh/SKILL.md +63 -63
- package/skills/pc-userstory-jira/SKILL.md +74 -74
|
@@ -1,88 +1,91 @@
|
|
|
1
|
-
import { execa } from 'execa'
|
|
2
|
-
import { addSkillToLock } from './skills-lock.js'
|
|
3
|
-
import fse from 'fs-extra'
|
|
4
|
-
import path from 'node:path'
|
|
5
|
-
import { applyEdits, modify, parse as parseJsonc } from 'jsonc-parser'
|
|
6
|
-
import { error, info, loading, success, warn } from '../../utils/exec.js'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Configures the agentmemory MCP server in opencode.jsonc
|
|
10
|
-
* and installs the agentmemory skills.
|
|
11
|
-
*
|
|
12
|
-
* agentmemory runs as a persistent local server on localhost:3111.
|
|
13
|
-
* Requires Node.js 18+ (npm install -g @agentmemory/agentmemory).
|
|
14
|
-
* MCP bridge: npx -y @agentmemory/mcp (proxies to the running server).
|
|
15
|
-
* Skill source: https://github.com/rohitg00/agentmemory
|
|
16
|
-
*/
|
|
17
|
-
export async function installMemory(options = {}) {
|
|
18
|
-
if (!options.skipHeader) info('Configuring agentmemory local memory server...')
|
|
19
|
-
|
|
20
|
-
// Install agentmemory globally so the `agentmemory` command is on PATH
|
|
21
|
-
loading('installing agentmemory (this may take a minute)...')
|
|
22
|
-
try {
|
|
23
|
-
const installResult = await execa('npm', ['install', '-g', '@agentmemory/agentmemory'], {
|
|
24
|
-
reject: false,
|
|
25
|
-
timeout: 300000,
|
|
26
|
-
stdio: 'pipe',
|
|
27
|
-
})
|
|
28
|
-
if (installResult.exitCode === 0) {
|
|
29
|
-
success('agentmemory installed')
|
|
30
|
-
} else {
|
|
31
|
-
warn('agentmemory install exited with non-zero code, MCP may fall back to npx resolution')
|
|
32
|
-
}
|
|
33
|
-
} catch (err) {
|
|
34
|
-
warn(`agentmemory install failed: ${err.message}`)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Configure MCP server in opencode.jsonc
|
|
38
|
-
try {
|
|
39
|
-
const opencodePath = path.join(process.cwd(), 'opencode.jsonc')
|
|
40
|
-
|
|
41
|
-
let opencodeText = JSON.stringify({ $schema: 'https://opencode.ai/config.json' }, null, 2)
|
|
42
|
-
let opencode
|
|
43
|
-
if (await fse.pathExists(opencodePath)) {
|
|
44
|
-
const errors = []
|
|
45
|
-
opencodeText = await fse.readFile(opencodePath, 'utf-8')
|
|
46
|
-
opencode = parseJsonc(opencodeText, errors)
|
|
47
|
-
if (errors.length > 0 || !opencode || typeof opencode !== 'object' || Array.isArray(opencode)) {
|
|
48
|
-
throw new Error('opencode.jsonc could not be parsed')
|
|
49
|
-
}
|
|
50
|
-
} else {
|
|
51
|
-
opencode = { $schema: 'https://opencode.ai/config.json' }
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (!opencode.mcp?.
|
|
55
|
-
const agentmemory = {
|
|
56
|
-
type: 'local',
|
|
57
|
-
command: ['npx', '-y', '@agentmemory/mcp'],
|
|
58
|
-
|
|
59
|
-
AGENTMEMORY_URL: 'http://localhost:3111',
|
|
60
|
-
},
|
|
61
|
-
|
|
62
|
-
timeout:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
1
|
+
import { execa } from 'execa'
|
|
2
|
+
import { addSkillToLock } from './skills-lock.js'
|
|
3
|
+
import fse from 'fs-extra'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { applyEdits, modify, parse as parseJsonc } from 'jsonc-parser'
|
|
6
|
+
import { error, info, loading, success, warn } from '../../utils/exec.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Configures the agentmemory MCP server in opencode.jsonc
|
|
10
|
+
* and installs the agentmemory skills.
|
|
11
|
+
*
|
|
12
|
+
* agentmemory runs as a persistent local server on localhost:3111.
|
|
13
|
+
* Requires Node.js 18+ (npm install -g @agentmemory/agentmemory).
|
|
14
|
+
* MCP bridge: npx -y @agentmemory/mcp (proxies to the running server).
|
|
15
|
+
* Skill source: https://github.com/rohitg00/agentmemory
|
|
16
|
+
*/
|
|
17
|
+
export async function installMemory(options = {}) {
|
|
18
|
+
if (!options.skipHeader) info('Configuring agentmemory local memory server...')
|
|
19
|
+
|
|
20
|
+
// Install agentmemory globally so the `agentmemory` command is on PATH
|
|
21
|
+
loading('installing agentmemory (this may take a minute)...')
|
|
22
|
+
try {
|
|
23
|
+
const installResult = await execa('npm', ['install', '-g', '@agentmemory/agentmemory'], {
|
|
24
|
+
reject: false,
|
|
25
|
+
timeout: 300000,
|
|
26
|
+
stdio: 'pipe',
|
|
27
|
+
})
|
|
28
|
+
if (installResult.exitCode === 0) {
|
|
29
|
+
success('agentmemory installed')
|
|
30
|
+
} else {
|
|
31
|
+
warn('agentmemory install exited with non-zero code, MCP may fall back to npx resolution')
|
|
32
|
+
}
|
|
33
|
+
} catch (err) {
|
|
34
|
+
warn(`agentmemory install failed: ${err.message}`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Configure MCP server in opencode.jsonc
|
|
38
|
+
try {
|
|
39
|
+
const opencodePath = path.join(process.cwd(), 'opencode.jsonc')
|
|
40
|
+
|
|
41
|
+
let opencodeText = JSON.stringify({ $schema: 'https://opencode.ai/config.json' }, null, 2)
|
|
42
|
+
let opencode
|
|
43
|
+
if (await fse.pathExists(opencodePath)) {
|
|
44
|
+
const errors = []
|
|
45
|
+
opencodeText = await fse.readFile(opencodePath, 'utf-8')
|
|
46
|
+
opencode = parseJsonc(opencodeText, errors)
|
|
47
|
+
if (errors.length > 0 || !opencode || typeof opencode !== 'object' || Array.isArray(opencode)) {
|
|
48
|
+
throw new Error('opencode.jsonc could not be parsed')
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
opencode = { $schema: 'https://opencode.ai/config.json' }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!opencode.mcp?.servers?.agentmemory) {
|
|
55
|
+
const agentmemory = {
|
|
56
|
+
type: 'local',
|
|
57
|
+
command: ['npx', '-y', '@agentmemory/mcp'],
|
|
58
|
+
environment: {
|
|
59
|
+
AGENTMEMORY_URL: 'http://localhost:3111',
|
|
60
|
+
},
|
|
61
|
+
disabled: false,
|
|
62
|
+
timeout: {
|
|
63
|
+
"catalog": 120000,
|
|
64
|
+
"execution": 120000
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
opencodeText = applyEdits(opencodeText, modify(opencodeText, ['mcp', 'servers', 'agentmemory'], agentmemory, {
|
|
68
|
+
formattingOptions: { insertSpaces: true, tabSize: 2 },
|
|
69
|
+
}))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await fse.writeFile(opencodePath, opencodeText, 'utf-8')
|
|
73
|
+
success('agentmemory MCP server configured in opencode.jsonc')
|
|
74
|
+
} catch (err) {
|
|
75
|
+
error(`Failed to configure agentmemory MCP: ${err.message}`)
|
|
76
|
+
return { optedIn: true, installed: false }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Add agentmemory skills to skills-lock.json for batch install
|
|
80
|
+
await addSkillToLock('agentmemory-agents', {
|
|
81
|
+
source: 'rohitg00/agentmemory',
|
|
82
|
+
sourceType: 'github',
|
|
83
|
+
skillPath: 'plugin/skills/agentmemory-agents/SKILL.md',
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
info('Memory server: run `agentmemory` to start on localhost:3111')
|
|
87
|
+
info('Real-time viewer: http://localhost:3113')
|
|
88
|
+
info('Health check: curl http://localhost:3111/agentmemory/health')
|
|
89
|
+
|
|
90
|
+
return { optedIn: true, installed: true }
|
|
91
|
+
}
|
|
@@ -1,111 +1,111 @@
|
|
|
1
|
-
import fse from 'fs-extra'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import { fileURLToPath } from 'url'
|
|
4
|
-
import { info, success } from '../../utils/exec.js'
|
|
5
|
-
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
7
|
-
const GUARDRAILS_FRAGMENT_DIR = path.resolve(__dirname, '../../fragments/guardrails')
|
|
8
|
-
|
|
9
|
-
const MARKER_SECTIONS = {
|
|
10
|
-
rtk: 'RTK',
|
|
11
|
-
codegraph: 'CODEGRAPH',
|
|
12
|
-
memory: 'MEMORY',
|
|
13
|
-
simpleEnglish: 'SIMPLE-ENGLISH',
|
|
14
|
-
humanizer: 'HUMANIZER',
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const OPTIONAL_TOOL_GUIDANCE = {
|
|
18
|
-
codegraph: 'Use `codegraph_explore` to refine relevant symbols and file-disjointness for the current wave.',
|
|
19
|
-
memory: 'Agentmemory carries context across sessions and agents: before exploring an unfamiliar area, call `memory_smart_search` with the key concepts of the task (components, error text) and reuse what it returns; when the work completes, call `memory_save` for durable non-obvious learnings (architecture constraints, root causes and fixes, decisions and why). Never store secrets.',
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function replaceMarkerSection(content, startMarker, endMarker, replacement) {
|
|
23
|
-
if (!content.includes(startMarker) || !content.includes(endMarker)) return content
|
|
24
|
-
const escape = value => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
25
|
-
const pattern = new RegExp(`${escape(startMarker)}[\\s\\S]*?${escape(endMarker)}`)
|
|
26
|
-
// Replacer function so $&, $' and $` in injected content stay literal.
|
|
27
|
-
return content.replace(pattern, () => `${startMarker}\n${replacement}\n${endMarker}`)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function patchOptionalToolGuidance(destSkillsDir, selections) {
|
|
31
|
-
const files = []
|
|
32
|
-
|
|
33
|
-
async function collect(dir) {
|
|
34
|
-
for (const entry of await fse.readdir(dir, { withFileTypes: true })) {
|
|
35
|
-
const entryPath = path.join(dir, entry.name)
|
|
36
|
-
if (entry.isDirectory()) await collect(entryPath)
|
|
37
|
-
else if (entry.name.endsWith('.md')) files.push(entryPath)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (!await fse.pathExists(destSkillsDir)) return 0
|
|
42
|
-
await collect(destSkillsDir)
|
|
43
|
-
|
|
44
|
-
let patched = 0
|
|
45
|
-
for (const filePath of files) {
|
|
46
|
-
let content = await fse.readFile(filePath, 'utf-8')
|
|
47
|
-
const original = content
|
|
48
|
-
for (const [key, guidance] of Object.entries(OPTIONAL_TOOL_GUIDANCE)) {
|
|
49
|
-
const suffix = key.toUpperCase()
|
|
50
|
-
content = replaceMarkerSection(
|
|
51
|
-
content,
|
|
52
|
-
`<!-- PC-OPTIMIZATION-${suffix}-START -->`,
|
|
53
|
-
`<!-- PC-OPTIMIZATION-${suffix}-END -->`,
|
|
54
|
-
selections[key] ? guidance : '',
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
if (content !== original) {
|
|
58
|
-
await fse.writeFile(filePath, `${content.replace(/\s*$/, '')}\n`, 'utf-8')
|
|
59
|
-
patched++
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return patched
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export async function patchGuardrails(selections = {}, { cwd = process.cwd() } = {}) {
|
|
66
|
-
const destSkillsDir = path.join(cwd, '.agents', 'skills')
|
|
67
|
-
const guardrailsPath = path.join(destSkillsDir, 'pc-guardrails-generic', 'SKILL.md')
|
|
68
|
-
let patchedCount = 0
|
|
69
|
-
|
|
70
|
-
if (await fse.pathExists(guardrailsPath)) {
|
|
71
|
-
let content = await fse.readFile(guardrailsPath, 'utf-8')
|
|
72
|
-
content = replaceMarkerSection(
|
|
73
|
-
content,
|
|
74
|
-
'<!-- PC-GUARDRAILS-CAVEMAN-START -->',
|
|
75
|
-
'<!-- PC-GUARDRAILS-CAVEMAN-END -->',
|
|
76
|
-
'',
|
|
77
|
-
).replace(
|
|
78
|
-
'<!-- PC-GUARDRAILS-CAVEMAN-START -->\n\n<!-- PC-GUARDRAILS-CAVEMAN-END -->',
|
|
79
|
-
'<!-- PC-GUARDRAILS-SIMPLE-ENGLISH-START -->\n<!-- PC-GUARDRAILS-SIMPLE-ENGLISH-END -->',
|
|
80
|
-
)
|
|
81
|
-
// A third replace used to rewrite the engineer workflow's step 1 into "the
|
|
82
|
-
// plugin has already loaded the skills". That was never true: the plugin
|
|
83
|
-
// appends a reminder and never calls the skill tool. The shipped skill now
|
|
84
|
-
// carries the honest wording, and update refreshes this file, so the
|
|
85
|
-
// migration is gone rather than perpetuated.
|
|
86
|
-
for (const [key, markerSuffix] of Object.entries(MARKER_SECTIONS)) {
|
|
87
|
-
let sectionContent = ''
|
|
88
|
-
if (selections[key]) {
|
|
89
|
-
const presetName = key === 'simpleEnglish' ? 'simple-english' : key
|
|
90
|
-
const presetPath = path.join(GUARDRAILS_FRAGMENT_DIR, `${presetName}.md`)
|
|
91
|
-
if (await fse.pathExists(presetPath)) {
|
|
92
|
-
sectionContent = (await fse.readFile(presetPath, 'utf-8')).trim()
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
content = replaceMarkerSection(
|
|
96
|
-
content,
|
|
97
|
-
`<!-- PC-GUARDRAILS-${markerSuffix}-START -->`,
|
|
98
|
-
`<!-- PC-GUARDRAILS-${markerSuffix}-END -->`,
|
|
99
|
-
sectionContent,
|
|
100
|
-
)
|
|
101
|
-
if (selections[key]) patchedCount++
|
|
102
|
-
}
|
|
103
|
-
await fse.writeFile(guardrailsPath, `${content.replace(/\s*$/, '')}\n`, 'utf-8')
|
|
104
|
-
} else {
|
|
105
|
-
info('pc-guardrails-generic not found, skipping guardrails patching')
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const optionalPatched = await patchOptionalToolGuidance(destSkillsDir, selections)
|
|
109
|
-
success(`Guardrails patched: ${patchedCount} sections injected; ${optionalPatched} optional tool file(s) updated`)
|
|
110
|
-
return { patched: true, count: patchedCount, optionalPatched }
|
|
111
|
-
}
|
|
1
|
+
import fse from 'fs-extra'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { fileURLToPath } from 'url'
|
|
4
|
+
import { info, success } from '../../utils/exec.js'
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
const GUARDRAILS_FRAGMENT_DIR = path.resolve(__dirname, '../../fragments/guardrails')
|
|
8
|
+
|
|
9
|
+
const MARKER_SECTIONS = {
|
|
10
|
+
rtk: 'RTK',
|
|
11
|
+
codegraph: 'CODEGRAPH',
|
|
12
|
+
memory: 'MEMORY',
|
|
13
|
+
simpleEnglish: 'SIMPLE-ENGLISH',
|
|
14
|
+
humanizer: 'HUMANIZER',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const OPTIONAL_TOOL_GUIDANCE = {
|
|
18
|
+
codegraph: 'Use `codegraph_explore` to refine relevant symbols and file-disjointness for the current wave.',
|
|
19
|
+
memory: 'Agentmemory carries context across sessions and agents: before exploring an unfamiliar area, call `memory_smart_search` with the key concepts of the task (components, error text) and reuse what it returns; when the work completes, call `memory_save` for durable non-obvious learnings (architecture constraints, root causes and fixes, decisions and why). Never store secrets.',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function replaceMarkerSection(content, startMarker, endMarker, replacement) {
|
|
23
|
+
if (!content.includes(startMarker) || !content.includes(endMarker)) return content
|
|
24
|
+
const escape = value => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
25
|
+
const pattern = new RegExp(`${escape(startMarker)}[\\s\\S]*?${escape(endMarker)}`)
|
|
26
|
+
// Replacer function so $&, $' and $` in injected content stay literal.
|
|
27
|
+
return content.replace(pattern, () => `${startMarker}\n${replacement}\n${endMarker}`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function patchOptionalToolGuidance(destSkillsDir, selections) {
|
|
31
|
+
const files = []
|
|
32
|
+
|
|
33
|
+
async function collect(dir) {
|
|
34
|
+
for (const entry of await fse.readdir(dir, { withFileTypes: true })) {
|
|
35
|
+
const entryPath = path.join(dir, entry.name)
|
|
36
|
+
if (entry.isDirectory()) await collect(entryPath)
|
|
37
|
+
else if (entry.name.endsWith('.md')) files.push(entryPath)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!await fse.pathExists(destSkillsDir)) return 0
|
|
42
|
+
await collect(destSkillsDir)
|
|
43
|
+
|
|
44
|
+
let patched = 0
|
|
45
|
+
for (const filePath of files) {
|
|
46
|
+
let content = await fse.readFile(filePath, 'utf-8')
|
|
47
|
+
const original = content
|
|
48
|
+
for (const [key, guidance] of Object.entries(OPTIONAL_TOOL_GUIDANCE)) {
|
|
49
|
+
const suffix = key.toUpperCase()
|
|
50
|
+
content = replaceMarkerSection(
|
|
51
|
+
content,
|
|
52
|
+
`<!-- PC-OPTIMIZATION-${suffix}-START -->`,
|
|
53
|
+
`<!-- PC-OPTIMIZATION-${suffix}-END -->`,
|
|
54
|
+
selections[key] ? guidance : '',
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
if (content !== original) {
|
|
58
|
+
await fse.writeFile(filePath, `${content.replace(/\s*$/, '')}\n`, 'utf-8')
|
|
59
|
+
patched++
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return patched
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function patchGuardrails(selections = {}, { cwd = process.cwd() } = {}) {
|
|
66
|
+
const destSkillsDir = path.join(cwd, '.agents', 'skills')
|
|
67
|
+
const guardrailsPath = path.join(destSkillsDir, 'pc-guardrails-generic', 'SKILL.md')
|
|
68
|
+
let patchedCount = 0
|
|
69
|
+
|
|
70
|
+
if (await fse.pathExists(guardrailsPath)) {
|
|
71
|
+
let content = await fse.readFile(guardrailsPath, 'utf-8')
|
|
72
|
+
content = replaceMarkerSection(
|
|
73
|
+
content,
|
|
74
|
+
'<!-- PC-GUARDRAILS-CAVEMAN-START -->',
|
|
75
|
+
'<!-- PC-GUARDRAILS-CAVEMAN-END -->',
|
|
76
|
+
'',
|
|
77
|
+
).replace(
|
|
78
|
+
'<!-- PC-GUARDRAILS-CAVEMAN-START -->\n\n<!-- PC-GUARDRAILS-CAVEMAN-END -->',
|
|
79
|
+
'<!-- PC-GUARDRAILS-SIMPLE-ENGLISH-START -->\n<!-- PC-GUARDRAILS-SIMPLE-ENGLISH-END -->',
|
|
80
|
+
)
|
|
81
|
+
// A third replace used to rewrite the engineer workflow's step 1 into "the
|
|
82
|
+
// plugin has already loaded the skills". That was never true: the plugin
|
|
83
|
+
// appends a reminder and never calls the skill tool. The shipped skill now
|
|
84
|
+
// carries the honest wording, and update refreshes this file, so the
|
|
85
|
+
// migration is gone rather than perpetuated.
|
|
86
|
+
for (const [key, markerSuffix] of Object.entries(MARKER_SECTIONS)) {
|
|
87
|
+
let sectionContent = ''
|
|
88
|
+
if (selections[key]) {
|
|
89
|
+
const presetName = key === 'simpleEnglish' ? 'simple-english' : key
|
|
90
|
+
const presetPath = path.join(GUARDRAILS_FRAGMENT_DIR, `${presetName}.md`)
|
|
91
|
+
if (await fse.pathExists(presetPath)) {
|
|
92
|
+
sectionContent = (await fse.readFile(presetPath, 'utf-8')).trim()
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
content = replaceMarkerSection(
|
|
96
|
+
content,
|
|
97
|
+
`<!-- PC-GUARDRAILS-${markerSuffix}-START -->`,
|
|
98
|
+
`<!-- PC-GUARDRAILS-${markerSuffix}-END -->`,
|
|
99
|
+
sectionContent,
|
|
100
|
+
)
|
|
101
|
+
if (selections[key]) patchedCount++
|
|
102
|
+
}
|
|
103
|
+
await fse.writeFile(guardrailsPath, `${content.replace(/\s*$/, '')}\n`, 'utf-8')
|
|
104
|
+
} else {
|
|
105
|
+
info('pc-guardrails-generic not found, skipping guardrails patching')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const optionalPatched = await patchOptionalToolGuidance(destSkillsDir, selections)
|
|
109
|
+
success(`Guardrails patched: ${patchedCount} sections injected; ${optionalPatched} optional tool file(s) updated`)
|
|
110
|
+
return { patched: true, count: patchedCount, optionalPatched }
|
|
111
|
+
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { addSkillToLock } from './skills-lock.js'
|
|
2
|
-
import { header, success, info } from '../../utils/exec.js'
|
|
3
|
-
|
|
4
|
-
const SKILL_ENTRY = {
|
|
5
|
-
source: 'AminBlg/SimpleEnglish',
|
|
6
|
-
sourceType: 'github',
|
|
7
|
-
skillPath: 'skills/simple-english/SKILL.md',
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export async function installSimpleEnglish(options = {}) {
|
|
11
|
-
if (!options.skipHeader) header('Installing Simple English')
|
|
12
|
-
|
|
13
|
-
info('Adding Simple English to skills-lock.json for batch install')
|
|
14
|
-
await addSkillToLock('simple-english', SKILL_ENTRY)
|
|
15
|
-
success('Simple English queued for installation')
|
|
16
|
-
return { optedIn: true, installed: true }
|
|
17
|
-
}
|
|
1
|
+
import { addSkillToLock } from './skills-lock.js'
|
|
2
|
+
import { header, success, info } from '../../utils/exec.js'
|
|
3
|
+
|
|
4
|
+
const SKILL_ENTRY = {
|
|
5
|
+
source: 'AminBlg/SimpleEnglish',
|
|
6
|
+
sourceType: 'github',
|
|
7
|
+
skillPath: 'skills/simple-english/SKILL.md',
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function installSimpleEnglish(options = {}) {
|
|
11
|
+
if (!options.skipHeader) header('Installing Simple English')
|
|
12
|
+
|
|
13
|
+
info('Adding Simple English to skills-lock.json for batch install')
|
|
14
|
+
await addSkillToLock('simple-english', SKILL_ENTRY)
|
|
15
|
+
success('Simple English queued for installation')
|
|
16
|
+
return { optedIn: true, installed: true }
|
|
17
|
+
}
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import fse from 'fs-extra'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import { info, warn } from '../../utils/exec.js'
|
|
4
|
-
|
|
5
|
-
export async function addSkillToLock(skillName, entry) {
|
|
6
|
-
if (!entry) {
|
|
7
|
-
warn(`No entry provided for skills-lock.json: ${skillName}`)
|
|
8
|
-
return false
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const destLock = path.join(process.cwd(), 'skills-lock.json')
|
|
12
|
-
|
|
13
|
-
let lock = { version: 1, skills: {} }
|
|
14
|
-
if (await fse.pathExists(destLock)) {
|
|
15
|
-
try {
|
|
16
|
-
lock = await fse.readJson(destLock)
|
|
17
|
-
if (!lock.skills) lock.skills = {}
|
|
18
|
-
} catch {
|
|
19
|
-
warn('Could not parse existing skills-lock.json, creating fresh')
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (!lock.skills[skillName]) {
|
|
24
|
-
lock.skills[skillName] = entry
|
|
25
|
-
await fse.writeJson(destLock, lock, { spaces: 2 })
|
|
26
|
-
info(`Added ${skillName} to skills-lock.json`)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return true
|
|
30
|
-
}
|
|
1
|
+
import fse from 'fs-extra'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { info, warn } from '../../utils/exec.js'
|
|
4
|
+
|
|
5
|
+
export async function addSkillToLock(skillName, entry) {
|
|
6
|
+
if (!entry) {
|
|
7
|
+
warn(`No entry provided for skills-lock.json: ${skillName}`)
|
|
8
|
+
return false
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const destLock = path.join(process.cwd(), 'skills-lock.json')
|
|
12
|
+
|
|
13
|
+
let lock = { version: 1, skills: {} }
|
|
14
|
+
if (await fse.pathExists(destLock)) {
|
|
15
|
+
try {
|
|
16
|
+
lock = await fse.readJson(destLock)
|
|
17
|
+
if (!lock.skills) lock.skills = {}
|
|
18
|
+
} catch {
|
|
19
|
+
warn('Could not parse existing skills-lock.json, creating fresh')
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!lock.skills[skillName]) {
|
|
24
|
+
lock.skills[skillName] = entry
|
|
25
|
+
await fse.writeJson(destLock, lock, { spaces: 2 })
|
|
26
|
+
info(`Added ${skillName} to skills-lock.json`)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return true
|
|
30
|
+
}
|