@plainconceptsplatform/agent-harness 2.4.1 → 2.5.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 +435 -437
- package/cli/fragments/archive/az.md +97 -95
- package/cli/fragments/archive/gh.md +96 -94
- package/cli/fragments/archive/gl.md +96 -94
- package/cli/fragments/archive/none.md +75 -73
- package/cli/fragments/guardrails/codegraph.md +5 -7
- 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 +1 -1
- package/cli/fragments/ops-backlog/gh.md +1 -1
- package/cli/fragments/ops-backlog/jira.md +1 -1
- package/cli/fragments/ops-evidence/az.md +44 -41
- package/cli/fragments/ops-evidence/gh.md +54 -53
- package/cli/fragments/ops-evidence/jira.md +42 -38
- package/cli/fragments/ops-review/az.md +1 -1
- package/cli/fragments/ops-review/gh.md +1 -1
- package/cli/fragments/ops-review/gl.md +1 -1
- package/cli/fragments/ops-ship/az.md +81 -80
- package/cli/fragments/ops-ship/gh.md +68 -68
- package/cli/fragments/ops-ship/gl.md +85 -85
- package/cli/presets/agents-content.json +34 -53
- package/cli/steps/copy/agents.js +18 -17
- package/cli/steps/copy/opencode-json.js +5 -1
- package/cli/steps/copy/skills.js +98 -5
- package/cli/steps/optimization/patch-guardrails.js +5 -3
- package/cli/utils/copy.js +8 -3
- package/cli/utils/update-manifest.js +28 -2
- package/harness/.agents/skills/pc-guardrails-generic/SKILL.md +47 -68
- package/harness/.agents/skills/pc-make-architecture/SKILL.md +31 -51
- package/harness/.agents/skills/pc-make-design/SKILL.md +45 -68
- package/harness/.agents/skills/pc-make-engineer/SKILL.md +59 -219
- package/harness/.agents/skills/pc-make-engineer/signal-mapping.md +53 -68
- package/harness/.agents/skills/pc-make-engineer/template.md +42 -80
- package/harness/.agents/skills/pc-make-evidence-scaffold/SKILL.md +18 -18
- package/harness/.agents/skills/pc-make-evidence-scaffold/evidence-contract.md +29 -29
- package/harness/.agents/skills/pc-make-guardrails/SKILL.md +43 -74
- package/harness/.agents/skills/pc-make-guardrails/category-reference.md +10 -5
- package/harness/.agents/skills/pc-make-merge-risk-assess/category-reference.md +26 -7
- package/harness/.agents/skills/pc-make-user-model/SKILL.md +56 -66
- package/harness/.agents/skills/pc-ops-evidence/SKILL.md +133 -127
- package/harness/.agents/skills/pc-plan-apply/SKILL.md +14 -5
- package/harness/.agents/skills/pc-plan-apply/simple-mode.md +21 -21
- package/harness/.agents/skills/pc-plan-archive/SKILL.md +66 -66
- package/harness/.agents/skills/pc-plan-explore/SKILL.md +19 -2
- package/harness/.agents/skills/pc-plan-goal/SKILL.md +7 -5
- package/harness/.agents/skills/pc-plan-goal/output-mode.md +1 -0
- package/harness/.agents/skills/pc-plan-goal/output.md +71 -65
- package/harness/.agents/skills/pc-plan-propose/SKILL.md +1 -1
- package/harness/.agents/skills/pc-plan-quick/SKILL.md +46 -62
- package/harness/.agents/skills/pc-plan-story/SKILL.md +48 -149
- package/harness/.agents/skills/pc-repo-help/SKILL.md +89 -91
- package/harness/.agents/skills/pc-repo-initialize/SKILL.md +112 -130
- package/harness/.agents/skills/pc-repo-onboard/SKILL.md +32 -87
- package/harness/.agents/skills/pc-repo-verify/SKILL.md +2 -0
- package/harness/.agents/skills/pc-userstory-az/SKILL.md +71 -157
- package/harness/.agents/skills/pc-userstory-browser/SKILL.md +50 -122
- package/harness/.agents/skills/pc-userstory-gh/SKILL.md +63 -120
- package/harness/.agents/skills/pc-userstory-jira/SKILL.md +74 -131
- 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-evidence-scaffold.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/plan-apply.md +9 -9
- package/harness/.opencode/commands/plan-goal.md +5 -5
- 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-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/plugins/pc-subagent-monitor.js +82 -2
- package/harness/.opencode/plugins/pc-subagent-tiers.js +9 -6
- package/harness/.opencode/plugins/pc-system-reminders.js +312 -3
- package/harness/AGENTS.md +49 -71
- package/harness/opencode.jsonc +1 -1
- package/package.json +1 -1
package/cli/steps/copy/agents.js
CHANGED
|
@@ -6,17 +6,17 @@ import { info, success, warn } from '../../utils/exec.js'
|
|
|
6
6
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
7
7
|
const agentsContent = await fse.readJson(path.resolve(__dirname, '../../presets/agents-content.json'))
|
|
8
8
|
|
|
9
|
-
// Steps are matched by their title text, not by exact heading string:
|
|
10
|
-
//
|
|
11
|
-
//
|
|
9
|
+
// Steps are matched by their title text, not by exact heading string: heading
|
|
10
|
+
// level (##/###/####), the `Step N,` prefix and any ` (if Yes)` suffix have all
|
|
11
|
+
// drifted before, and every drift silently turned the patch into a no-op that
|
|
12
|
+
// only showed up as a warning nobody read.
|
|
12
13
|
const HISTORY_STEP_TITLE = 'Archive project history'
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
const CHAIN_CONFIRM_LINE = '- ARCHITECTURE.md generated'
|
|
14
|
+
const ARCHITECTURE_STEP_TITLE = 'Generate ARCHITECTURE.md'
|
|
15
|
+
const DESIGN_STEP_TITLE = 'Generate DESIGN.md'
|
|
16
16
|
|
|
17
17
|
function stepHeadingPattern(title) {
|
|
18
18
|
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
19
|
-
return new RegExp(`^#{2,4}
|
|
19
|
+
return new RegExp(`^#{2,4}\\s+(?:Step\\s+\\d+[a-z]?[,:]\\s+)?${escaped}\\b`, 'i')
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// The step heading is kept and its body replaced with an explicit skip note.
|
|
@@ -29,19 +29,19 @@ export function skipStepBlock(content, title, note) {
|
|
|
29
29
|
const start = lines.findIndex(l => pattern.test(l.trim()))
|
|
30
30
|
if (start === -1) return { content, matched: false }
|
|
31
31
|
|
|
32
|
+
// The block ends at the next heading or rule, NOT at end of file. Scanning
|
|
33
|
+
// only for `---` meant a document without one lost everything below the
|
|
34
|
+
// matched step.
|
|
32
35
|
let end = lines.length
|
|
33
36
|
for (let i = start + 1; i < lines.length; i++) {
|
|
34
|
-
|
|
37
|
+
const line = lines[i].trim()
|
|
38
|
+
if (line === '---' || /^#{1,4}\s/.test(line)) { end = i; break }
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
lines.splice(start + 1, end - start - 1, '', `> ${note}`, '')
|
|
38
42
|
return { content: lines.join('\n'), matched: true }
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
function removeConfirmLine(content, line) {
|
|
42
|
-
return content.split('\n').filter(l => l.trim() !== line.trim()).join('\n')
|
|
43
|
-
}
|
|
44
|
-
|
|
45
45
|
const PLATFORM_WORKFLOW_START = '<!-- PC-PLATFORM-WORKFLOW-START -->'
|
|
46
46
|
const PLATFORM_WORKFLOW_END = '<!-- PC-PLATFORM-WORKFLOW-END -->'
|
|
47
47
|
const PLATFORM_SKILLS_GUIDE_START = '<!-- PC-PLATFORM-SKILLS-GUIDE-START -->'
|
|
@@ -78,13 +78,15 @@ export async function patchAgentsMd(ctx) {
|
|
|
78
78
|
const patches = []
|
|
79
79
|
|
|
80
80
|
const skips = [
|
|
81
|
-
[ctx.hasOpenspec, HISTORY_STEP_TITLE,
|
|
81
|
+
[ctx.hasOpenspec, HISTORY_STEP_TITLE,
|
|
82
82
|
'Skipped during onboarding: this project already had an openspec/ history. Do not archive again; continue with the next step.'],
|
|
83
|
-
[ctx.
|
|
84
|
-
'Skipped during onboarding:
|
|
83
|
+
[ctx.hasArchitecture, ARCHITECTURE_STEP_TITLE,
|
|
84
|
+
'Skipped during onboarding: ARCHITECTURE.md already exists. Run /make-architecture to regenerate it, which keeps its update mode.'],
|
|
85
|
+
[ctx.hasDesign, DESIGN_STEP_TITLE,
|
|
86
|
+
'Skipped during onboarding: DESIGN.md already exists. Run /make-design to regenerate it, which keeps its update mode.'],
|
|
85
87
|
]
|
|
86
88
|
|
|
87
|
-
for (const [enabled, title,
|
|
89
|
+
for (const [enabled, title, note] of skips) {
|
|
88
90
|
if (!enabled) continue
|
|
89
91
|
const result = skipStepBlock(content, title, note)
|
|
90
92
|
if (!result.matched) {
|
|
@@ -92,7 +94,6 @@ export async function patchAgentsMd(ctx) {
|
|
|
92
94
|
continue
|
|
93
95
|
}
|
|
94
96
|
content = result.content
|
|
95
|
-
if (confirmLine) content = removeConfirmLine(content, confirmLine)
|
|
96
97
|
patches.push(`Step "${title}" marked as skipped, file already exists`)
|
|
97
98
|
}
|
|
98
99
|
|
|
@@ -54,7 +54,8 @@ export async function patchOpencodeJson(cwd = process.cwd()) {
|
|
|
54
54
|
const needsAgentOverride = hasStaleDisable || !(
|
|
55
55
|
parsed?.agent?.build?.mode === 'primary' &&
|
|
56
56
|
parsed?.agent?.plan?.mode === 'primary' &&
|
|
57
|
-
parsed?.agent?.plan?.permission?.edit === 'deny'
|
|
57
|
+
parsed?.agent?.plan?.permission?.edit === 'deny' &&
|
|
58
|
+
parsed?.agent?.plan?.permission?.task === 'deny'
|
|
58
59
|
)
|
|
59
60
|
|
|
60
61
|
// default_agent pointing at fullstack-engineer is now invalid: it became a
|
|
@@ -97,6 +98,9 @@ export async function patchOpencodeJson(cwd = process.cwd()) {
|
|
|
97
98
|
text = applyModify(text, ['agent', 'build', 'mode'], 'primary')
|
|
98
99
|
text = applyModify(text, ['agent', 'plan', 'mode'], 'primary')
|
|
99
100
|
text = applyModify(text, ['agent', 'plan', 'permission', 'edit'], 'deny')
|
|
101
|
+
// Read-only has to include spawning: a plan session that can call task()
|
|
102
|
+
// can have a build worker make the change for it.
|
|
103
|
+
text = applyModify(text, ['agent', 'plan', 'permission', 'task'], 'deny')
|
|
100
104
|
}
|
|
101
105
|
if (needsDefaultAgent) {
|
|
102
106
|
text = applyModify(text, ['default_agent'], 'plan')
|
package/cli/steps/copy/skills.js
CHANGED
|
@@ -51,6 +51,75 @@ const MARKER_SKILLS = new Set([
|
|
|
51
51
|
'pc-repo-initialize',
|
|
52
52
|
])
|
|
53
53
|
|
|
54
|
+
// Marker skills whose prose must track the shipped version on update.
|
|
55
|
+
//
|
|
56
|
+
// These are shipped content, not project content: the project-specific part is
|
|
57
|
+
// injected into their marker pairs, and the platform patchers plus
|
|
58
|
+
// patchGuardrails re-inject it immediately after this copy. Treating them as
|
|
59
|
+
// project-owned froze their prose at whatever version a consumer first
|
|
60
|
+
// installed, so a skill rewrite never reached an existing repo. That is also
|
|
61
|
+
// how browser-automation sat on v1.0 in five repos while the harness shipped
|
|
62
|
+
// v2.0.
|
|
63
|
+
//
|
|
64
|
+
// pc-repo-initialize is deliberately absent. patchAgentsMd stamps "skipped
|
|
65
|
+
// during onboarding" notes into it that only run on a fresh install
|
|
66
|
+
// (copy/index.js runs it under `!ctx.updateMode`), so refreshing it would
|
|
67
|
+
// discard decisions made at onboarding.
|
|
68
|
+
const REFRESHABLE_MARKER_SKILLS = new Set([
|
|
69
|
+
'pc-guardrails-generic',
|
|
70
|
+
'pc-plan-archive',
|
|
71
|
+
'pc-ops-ship',
|
|
72
|
+
'pc-ops-evidence',
|
|
73
|
+
])
|
|
74
|
+
|
|
75
|
+
// A project-owned slot inside a shipped skill: the content between the markers
|
|
76
|
+
// survives a refresh, everything around it takes the new shipped version. This
|
|
77
|
+
// is the supported way to adapt one example to a project without forking the
|
|
78
|
+
// file, which consumers have otherwise done by editing shipped prose in place.
|
|
79
|
+
const PROJECT_SLOT_RE = /<!-- (PC-PROJECT-[A-Z0-9-]+)-START -->([\s\S]*?)<!-- \1-END -->/g
|
|
80
|
+
// Separate, non-global: `.test()` on a /g/ regex advances lastIndex, so reusing
|
|
81
|
+
// PROJECT_SLOT_RE for the check would answer false on every other call.
|
|
82
|
+
const HAS_PROJECT_SLOT = /<!-- PC-PROJECT-[A-Z0-9-]+-START -->/
|
|
83
|
+
|
|
84
|
+
function readProjectSlots(text) {
|
|
85
|
+
const slots = new Map()
|
|
86
|
+
for (const match of text.matchAll(PROJECT_SLOT_RE)) slots.set(match[1], match[2])
|
|
87
|
+
return slots
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function writeProjectSlots(text, slots) {
|
|
91
|
+
if (slots.size === 0) return text
|
|
92
|
+
// Replacer function, not a replacement string: carried content can contain
|
|
93
|
+
// shell quoting like $'...', and a string replacement expands $' as
|
|
94
|
+
// "everything after the match" and truncates the file.
|
|
95
|
+
return text.replace(PROJECT_SLOT_RE, (whole, name) => {
|
|
96
|
+
const kept = slots.get(name)
|
|
97
|
+
if (kept === undefined || kept.trim() === '') return whole
|
|
98
|
+
return `<!-- ${name}-START -->${kept}<!-- ${name}-END -->`
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function refreshMarkerSkill(src, dest, relativeRoot) {
|
|
103
|
+
for (const entry of await fse.readdir(src, { withFileTypes: true })) {
|
|
104
|
+
const sourcePath = path.join(src, entry.name)
|
|
105
|
+
const destinationPath = path.join(dest, entry.name)
|
|
106
|
+
const relativePath = path.join(relativeRoot, entry.name)
|
|
107
|
+
if (entry.isDirectory()) {
|
|
108
|
+
await refreshMarkerSkill(sourcePath, destinationPath, relativePath)
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
const shipped = await fse.readFile(sourcePath, 'utf-8')
|
|
112
|
+
const existing = await fse.pathExists(destinationPath)
|
|
113
|
+
? await fse.readFile(destinationPath, 'utf-8')
|
|
114
|
+
: ''
|
|
115
|
+
const merged = writeProjectSlots(shipped, readProjectSlots(existing))
|
|
116
|
+
if (merged === existing) continue
|
|
117
|
+
await fse.ensureDir(path.dirname(destinationPath))
|
|
118
|
+
await fse.writeFile(destinationPath, merged, 'utf-8')
|
|
119
|
+
success(`Refreshed skill: ${relativePath}`)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
54
123
|
async function isGeneratedSkill(dest) {
|
|
55
124
|
const skillMd = path.join(dest, 'SKILL.md')
|
|
56
125
|
if (!await fse.pathExists(skillMd)) return false
|
|
@@ -72,9 +141,26 @@ async function syncSkillFiles(src, dest, relativeRoot, cwd, manifest) {
|
|
|
72
141
|
await fse.copyFile(sourcePath, destinationPath)
|
|
73
142
|
await recordManagedFile(manifest, relativePath, sourcePath)
|
|
74
143
|
success(`Updated skill: ${relativePath}`)
|
|
75
|
-
|
|
76
|
-
info(`Preserving modified skill file: ${relativePath}`)
|
|
144
|
+
continue
|
|
77
145
|
}
|
|
146
|
+
|
|
147
|
+
// A shipped file that declares a PC-PROJECT-* slot states in its own prose
|
|
148
|
+
// that the slot is the project's and everything else is the harness's, so a
|
|
149
|
+
// hand-edited copy still takes the update: merge rather than preserve.
|
|
150
|
+
// Without this the file is frozen at whatever version the project edited,
|
|
151
|
+
// which is how five repos ended up with stale skills.
|
|
152
|
+
const shipped = await fse.readFile(sourcePath, 'utf-8')
|
|
153
|
+
if (HAS_PROJECT_SLOT.test(shipped)) {
|
|
154
|
+
const existing = await fse.readFile(destinationPath, 'utf-8')
|
|
155
|
+
const merged = writeProjectSlots(shipped, readProjectSlots(existing))
|
|
156
|
+
if (merged !== existing) {
|
|
157
|
+
await fse.writeFile(destinationPath, merged, 'utf-8')
|
|
158
|
+
success(`Refreshed skill, project slot kept: ${relativePath}`)
|
|
159
|
+
}
|
|
160
|
+
continue
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
info(`Preserving modified skill file: ${relativePath}`)
|
|
78
164
|
}
|
|
79
165
|
}
|
|
80
166
|
|
|
@@ -145,12 +231,19 @@ async function installObSkills(backlogPlatform = 'github', repoPlatform, { force
|
|
|
145
231
|
continue
|
|
146
232
|
}
|
|
147
233
|
if (updateMode) {
|
|
234
|
+
const relativeRoot = path.join('.agents', 'skills', destName)
|
|
148
235
|
if (!await fse.pathExists(dest)) {
|
|
149
|
-
await syncSkillFiles(src, dest,
|
|
150
|
-
} else if (
|
|
236
|
+
await syncSkillFiles(src, dest, relativeRoot, process.cwd(), manifest)
|
|
237
|
+
} else if (GENERATABLE_SKILLS.has(destName)) {
|
|
238
|
+
info(`Preserving generated skill: ${destName}`)
|
|
239
|
+
} else if (REFRESHABLE_MARKER_SKILLS.has(destName)) {
|
|
240
|
+
// Shipped prose refreshes; the patchers re-inject the marker pairs and
|
|
241
|
+
// any PC-PROJECT-* slot is carried over.
|
|
242
|
+
await refreshMarkerSkill(src, dest, relativeRoot)
|
|
243
|
+
} else if (MARKER_SKILLS.has(destName)) {
|
|
151
244
|
info(`Preserving project-owned skill: ${destName}`)
|
|
152
245
|
} else {
|
|
153
|
-
await syncSkillFiles(src, dest,
|
|
246
|
+
await syncSkillFiles(src, dest, relativeRoot, process.cwd(), manifest)
|
|
154
247
|
}
|
|
155
248
|
continue
|
|
156
249
|
}
|
|
@@ -77,10 +77,12 @@ export async function patchGuardrails(selections = {}, { cwd = process.cwd() } =
|
|
|
77
77
|
).replace(
|
|
78
78
|
'<!-- PC-GUARDRAILS-CAVEMAN-START -->\n\n<!-- PC-GUARDRAILS-CAVEMAN-END -->',
|
|
79
79
|
'<!-- PC-GUARDRAILS-SIMPLE-ENGLISH-START -->\n<!-- PC-GUARDRAILS-SIMPLE-ENGLISH-END -->',
|
|
80
|
-
).replace(
|
|
81
|
-
'1. Load ALL skills listed under your own `## Abilities` now (Guardrails first, then the rest), by calling the `skill` tool once per `@skill-name`.',
|
|
82
|
-
'1. The `pc-system-reminders` plugin has already loaded the skills listed under your `## Abilities`, guardrails first.',
|
|
83
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.
|
|
84
86
|
for (const [key, markerSuffix] of Object.entries(MARKER_SECTIONS)) {
|
|
85
87
|
let sectionContent = ''
|
|
86
88
|
if (selections[key]) {
|
package/cli/utils/copy.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fse from 'fs-extra'
|
|
2
2
|
import path from 'path'
|
|
3
|
-
import { canUpdateManagedFile,
|
|
3
|
+
import { canUpdateManagedFile, hashComparableFile, readUpdateManifest, recordManagedFile, writeUpdateManifest } from './update-manifest.js'
|
|
4
4
|
|
|
5
5
|
// Folders never copied (skills handled separately by installSkills, .bootstrap is internal tooling)
|
|
6
6
|
const ALWAYS_EXCLUDE = ['.bootstrap', 'skills', 'node_modules']
|
|
@@ -82,8 +82,13 @@ export async function recordManagedContent(contentDir, destDir, { updateMode = f
|
|
|
82
82
|
|
|
83
83
|
const destinationPath = path.join(destDir, relativePath)
|
|
84
84
|
if (!await fse.pathExists(destinationPath)) continue
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
// Comparable, not raw: this runs after the patchers, so a managed file's
|
|
86
|
+
// destination never equals its source byte for byte once anything has
|
|
87
|
+
// been injected into a marker pair. Comparing raw hashes here meant no
|
|
88
|
+
// entry was ever recorded for those files, which left them permanently
|
|
89
|
+
// indistinguishable from files a project had edited by hand.
|
|
90
|
+
const sourceHash = await hashComparableFile(sourcePath)
|
|
91
|
+
const destinationHash = await hashComparableFile(destinationPath)
|
|
87
92
|
const manifestPath = relativePath.split(path.sep).join('/')
|
|
88
93
|
const previousHash = manifest.files?.[manifestPath]
|
|
89
94
|
if (!updateMode || destinationHash === sourceHash || destinationHash === previousHash) {
|
|
@@ -17,6 +17,28 @@ export async function hashFile(filePath) {
|
|
|
17
17
|
return hashContent(await fse.readFile(filePath))
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// A file counts as untouched when it differs from what we shipped only in ways
|
|
21
|
+
// the harness itself caused. Two of those, and both made the raw-bytes
|
|
22
|
+
// comparison call an untouched file modified — permanently, because the flag is
|
|
23
|
+
// re-derived on every update:
|
|
24
|
+
//
|
|
25
|
+
// 1. Marker pairs. The patchers rewrite them after the copy, so a file with
|
|
26
|
+
// any PC-* marker never matches its own source again.
|
|
27
|
+
// 2. Line endings. A file that reaches a consumer's tree through git arrives
|
|
28
|
+
// CRLF on Windows while the shipped source is LF.
|
|
29
|
+
//
|
|
30
|
+
// This is the general form of the bug that left five repos on a stale
|
|
31
|
+
// browser-automation skill through repeated updates.
|
|
32
|
+
const MARKER_PAIR = /(<!-- PC-[A-Z0-9-]+-START -->)[\s\S]*?(<!-- PC-[A-Z0-9-]+-END -->)/g
|
|
33
|
+
|
|
34
|
+
function comparableContent(buffer) {
|
|
35
|
+
return buffer.toString('utf-8').replace(/\r\n/g, '\n').replace(MARKER_PAIR, '$1$2')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function hashComparableFile(filePath) {
|
|
39
|
+
return hashContent(comparableContent(await fse.readFile(filePath)))
|
|
40
|
+
}
|
|
41
|
+
|
|
20
42
|
export async function readUpdateManifest(cwd = process.cwd()) {
|
|
21
43
|
const manifestPath = path.join(cwd, MANIFEST_RELATIVE_PATH)
|
|
22
44
|
const manifest = await fse.readJson(manifestPath).catch(() => ({ version: 1, files: {} }))
|
|
@@ -36,12 +58,16 @@ export async function canUpdateManagedFile(relativePath, cwd, manifest) {
|
|
|
36
58
|
if (!await fse.pathExists(destinationPath)) return true
|
|
37
59
|
const previousHash = manifest.files?.[normalizedPath]
|
|
38
60
|
if (!previousHash) return false
|
|
39
|
-
|
|
61
|
+
// Raw is accepted too: manifests written before this comparison existed hold
|
|
62
|
+
// raw source hashes, and re-recording them all would need an update to run
|
|
63
|
+
// first, which is the thing being unblocked.
|
|
64
|
+
return previousHash === await hashComparableFile(destinationPath)
|
|
65
|
+
|| previousHash === await hashFile(destinationPath)
|
|
40
66
|
}
|
|
41
67
|
|
|
42
68
|
export async function recordManagedFile(manifest, relativePath, sourcePath) {
|
|
43
69
|
const files = manifest.files
|
|
44
|
-
files[normalizeRelativePath(relativePath)] = await
|
|
70
|
+
files[normalizeRelativePath(relativePath)] = await hashComparableFile(sourcePath)
|
|
45
71
|
}
|
|
46
72
|
|
|
47
73
|
export function manifestPath() {
|
|
@@ -1,68 +1,47 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pc-guardrails-generic
|
|
3
|
-
description: Generic guardrails, foundational rules that all agents follow. Users add specialized guardrails skills for specific concerns. Covers secrets, code quality, security, tool usage, and engineer workflow.
|
|
4
|
-
license: MIT
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Transitive loads (optimization skills)
|
|
8
|
-
|
|
9
|
-
The marker sections below
|
|
10
|
-
|
|
11
|
-
## Secrets
|
|
12
|
-
|
|
13
|
-
- Treat `.env` files as write-only: write to them when configuring, read credentials from the environment or secret store
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
<!-- PC-GUARDRAILS-CODEGRAPH-END -->
|
|
49
|
-
|
|
50
|
-
<!-- PC-GUARDRAILS-MEMORY-START -->
|
|
51
|
-
<!-- PC-GUARDRAILS-MEMORY-END -->
|
|
52
|
-
|
|
53
|
-
<!-- PC-GUARDRAILS-SIMPLE-ENGLISH-START -->
|
|
54
|
-
<!-- PC-GUARDRAILS-SIMPLE-ENGLISH-END -->
|
|
55
|
-
|
|
56
|
-
<!-- PC-GUARDRAILS-HUMANIZER-START -->
|
|
57
|
-
<!-- PC-GUARDRAILS-HUMANIZER-END -->
|
|
58
|
-
|
|
59
|
-
## Engineer workflow (when spawned)
|
|
60
|
-
|
|
61
|
-
When the lead spawns you via the task tool, your assigned task IDs and text are already in your prompt:
|
|
62
|
-
|
|
63
|
-
1. The `pc-system-reminders` plugin has already loaded the skills listed under your `## Abilities`, guardrails first.
|
|
64
|
-
2. Gather context using the project-selected tools described above.
|
|
65
|
-
3. Implement your assigned tasks in dependency order. Edit only files within your assigned scope.
|
|
66
|
-
4. Run the project's tests/lint before marking done (see Code above).
|
|
67
|
-
5. Record the task result through the project-selected workflow.
|
|
68
|
-
6. Return a summary containing: task IDs done, files changed, tests/lint result, and any decisions made. Then you exit; you do not poll, claim, or wait for more work.
|
|
1
|
+
---
|
|
2
|
+
name: pc-guardrails-generic
|
|
3
|
+
description: Generic guardrails, foundational rules that all agents follow. Users add specialized guardrails skills for specific concerns. Covers secrets, code quality, security, tool usage, and engineer workflow.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Transitive loads (optimization skills)
|
|
8
|
+
|
|
9
|
+
The marker sections below name the optimization skills this project selected. Load each one before doing any work.
|
|
10
|
+
|
|
11
|
+
## Secrets
|
|
12
|
+
|
|
13
|
+
- Treat `.env` files as write-only: write to them when configuring, and read credentials at runtime from the environment or the secret store.
|
|
14
|
+
- Never put a credential, API key or token in a log line, an output, or a commit in anything but encrypted or template form. Anything printed is in a CI log that outlives the run.
|
|
15
|
+
|
|
16
|
+
## Code
|
|
17
|
+
|
|
18
|
+
- Comments are for WHY, not WHAT. Use them only where the code does something non-obvious or the reason cannot be inferred from context. Past a 10% comment ratio in a file, refactor for clarity instead.
|
|
19
|
+
- Never add a file that collects unrelated things — `constants.js`, `types.ts`, `config.js`, `utils.ts`. One responsibility per file, split by domain or feature (`user-constants.ts`, `order-types.ts`, `auth-config.ts`). A file importing from many unrelated modules is already the symptom.
|
|
20
|
+
|
|
21
|
+
## Temporary files
|
|
22
|
+
|
|
23
|
+
- Never write outside `$REPO_ROOT`, and never to an operating-system temporary directory: the next step and the next agent cannot see it, and nobody cleans it up. Scratch goes under `$REPO_ROOT/.opencode/.tmp/`, in a task-specific child directory when needed (enforced by pc-system-reminders).
|
|
24
|
+
- Never report a path under `.tmp/` as a deliverable. Copy or move the artifact to its required repository path first.
|
|
25
|
+
- Never leave scratch files behind at the end of a task, unless they are the evidence for a failure you are reporting.
|
|
26
|
+
|
|
27
|
+
<!-- PC-GUARDRAILS-RTK-START -->
|
|
28
|
+
<!-- PC-GUARDRAILS-RTK-END -->
|
|
29
|
+
|
|
30
|
+
<!-- PC-GUARDRAILS-CODEGRAPH-START -->
|
|
31
|
+
<!-- PC-GUARDRAILS-CODEGRAPH-END -->
|
|
32
|
+
|
|
33
|
+
<!-- PC-GUARDRAILS-MEMORY-START -->
|
|
34
|
+
<!-- PC-GUARDRAILS-MEMORY-END -->
|
|
35
|
+
|
|
36
|
+
<!-- PC-GUARDRAILS-SIMPLE-ENGLISH-START -->
|
|
37
|
+
<!-- PC-GUARDRAILS-SIMPLE-ENGLISH-END -->
|
|
38
|
+
|
|
39
|
+
<!-- PC-GUARDRAILS-HUMANIZER-START -->
|
|
40
|
+
<!-- PC-GUARDRAILS-HUMANIZER-END -->
|
|
41
|
+
|
|
42
|
+
## Engineer workflow (when spawned)
|
|
43
|
+
|
|
44
|
+
The lead put your task IDs and their text in your prompt. Two things about that are not up to you:
|
|
45
|
+
|
|
46
|
+
- Load every skill under your `## Abilities` before you start, guardrails first, one `skill` call per `@skill-name`. Editing, shell and spawning are blocked until you have (pc-system-reminders).
|
|
47
|
+
- Edit only files in your assigned scope, then return a summary: task IDs done, files changed, tests and lint result, decisions made. Then you exit. Never poll for more work, and never claim a task the lead did not give you: the lead spawns with the work in hand, so a worker that waits is a worker that hangs the wave.
|
|
@@ -1,51 +1,31 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pc-make-architecture
|
|
3
|
-
description: Generate or update ARCHITECTURE.md by analyzing the codebase structure. Safe to run at any time. Invoked by the /make-architecture command and the repo-initialize flow.
|
|
4
|
-
license: MIT
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Make Architecture
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
- Update only the affected sections. Preserve manually-added content in unchanged sections.
|
|
33
|
-
- If the changes are too pervasive (more than ~40% of sections affected), fall back to Generate mode.
|
|
34
|
-
|
|
35
|
-
3. **Write ARCHITECTURE.md**
|
|
36
|
-
|
|
37
|
-
Write (or update) `ARCHITECTURE.md` following the [structure template](structure-template.md) reference. That reference defines every section, the rules for writing, and the timestamp footer format.
|
|
38
|
-
|
|
39
|
-
4. **Store summary in configured persistent context**
|
|
40
|
-
|
|
41
|
-
`write_note` MCP tool with title `architecture-summary` containing:
|
|
42
|
-
- The ISO timestamp of this run
|
|
43
|
-
- A bullet list of top-level components found (every top-level component must appear)
|
|
44
|
-
- Any key architectural decisions or risks identified
|
|
45
|
-
|
|
46
|
-
5. **Report**
|
|
47
|
-
|
|
48
|
-
Tell the user:
|
|
49
|
-
- Whether ARCHITECTURE.md was generated or updated (and which sections changed)
|
|
50
|
-
- Top-level components found
|
|
51
|
-
- Tip: "Rerun `/make-architecture` any time the architecture changes significantly."
|
|
1
|
+
---
|
|
2
|
+
name: pc-make-architecture
|
|
3
|
+
description: Generate or update ARCHITECTURE.md by analyzing the codebase structure. Safe to run at any time. Invoked by the /make-architecture command and the repo-initialize flow.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Make Architecture
|
|
8
|
+
|
|
9
|
+
Write `ARCHITECTURE.md` in the project root from what the codebase actually contains, following the [structure template](structure-template.md).
|
|
10
|
+
|
|
11
|
+
## Rules
|
|
12
|
+
|
|
13
|
+
- Never regenerate over a file that carries a `<!-- Last updated:` footer. That footer is what makes the next run incremental, and a full rewrite silently drops whatever a human added by hand. Read the file first and pick the mode.
|
|
14
|
+
- Never analyze outside `.opencode/source-roots.json` when it exists, plus this repo's own docs and config.
|
|
15
|
+
- The footer is the file's last line, and it is the run's own ISO timestamp: `<!-- Last updated: <ISO date> -->`.
|
|
16
|
+
|
|
17
|
+
## Modes
|
|
18
|
+
|
|
19
|
+
| The existing file | Mode |
|
|
20
|
+
|---|---|
|
|
21
|
+
| Missing, or a placeholder with no real content | Generate |
|
|
22
|
+
| Has content and a `<!-- Last updated:` footer | Update |
|
|
23
|
+
| Has content but no footer | Warn the user, then Generate |
|
|
24
|
+
|
|
25
|
+
**Generate.** Discover the architecture with the file tools: `glob` for structure, `grep` for routes, models and schemas, `read` for config, CI workflows, Dockerfiles, README, changelogs and ADRs.
|
|
26
|
+
|
|
27
|
+
**Update.** `git log --oneline --since="<footer date>" -- <source roots>` says what moved. Nothing changed means nothing to write: say "Architecture unchanged since last update" and stop. Otherwise rewrite only the affected sections and leave the rest, including anything hand-written, as it stands. Past roughly 40% of sections affected, fall back to Generate.
|
|
28
|
+
|
|
29
|
+
## Report
|
|
30
|
+
|
|
31
|
+
Whether the file was generated or updated and which sections changed, the top-level components found, and that `/make-architecture` can be re-run whenever the architecture moves.
|
|
@@ -1,68 +1,45 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pc-make-design
|
|
3
|
-
description: Generate or update DESIGN.md by analyzing the codebase design system (Tailwind, CSS vars, tokens, UI framework config). Safe to run at any time. Invoked by the /make-design command and the repo-initialize flow.
|
|
4
|
-
license: MIT
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Make Design
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
Write (or update) `DESIGN.md`. The output must:
|
|
47
|
-
- Begin with YAML frontmatter containing all structured design tokens (colors, typography, spacing, elevation, motion, radii, shadows, etc.)
|
|
48
|
-
- Follow with free-form Markdown describing the look and feel and capturing design intent that token values alone cannot convey
|
|
49
|
-
- Be entirely self-contained: reference no files, variables, or paths from the codebase
|
|
50
|
-
- Use valid YAML design token format for all token values
|
|
51
|
-
|
|
52
|
-
Append at the very end of the file:
|
|
53
|
-
```
|
|
54
|
-
<!-- Last updated: <current ISO timestamp> -->
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
4. **Store summary in configured persistent context**
|
|
58
|
-
|
|
59
|
-
`write_note` MCP tool with title `design-summary` containing:
|
|
60
|
-
- The ISO timestamp of this run
|
|
61
|
-
- Key design tokens found (color palette, fonts, spacing scale)
|
|
62
|
-
|
|
63
|
-
5. **Report**
|
|
64
|
-
|
|
65
|
-
Tell the user:
|
|
66
|
-
- Whether DESIGN.md was generated or updated (and which tokens/sections changed)
|
|
67
|
-
- Key design tokens found (color palette, fonts, spacing scale)
|
|
68
|
-
- Tip: "Rerun `/make-design` any time your design system changes."
|
|
1
|
+
---
|
|
2
|
+
name: pc-make-design
|
|
3
|
+
description: Generate or update DESIGN.md by analyzing the codebase design system (Tailwind, CSS vars, tokens, UI framework config). Safe to run at any time. Invoked by the /make-design command and the repo-initialize flow.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Make Design
|
|
8
|
+
|
|
9
|
+
Write `DESIGN.md` in the project root from the design system the codebase actually uses.
|
|
10
|
+
|
|
11
|
+
The format is Google's design.md spec:
|
|
12
|
+
|
|
13
|
+
- Overview: https://stitch.withgoogle.com/docs/design-md/overview/
|
|
14
|
+
- Format: https://stitch.withgoogle.com/docs/design-md/format/
|
|
15
|
+
- Spec and examples: https://github.com/google-labs-code/design.md
|
|
16
|
+
|
|
17
|
+
## Rules
|
|
18
|
+
|
|
19
|
+
- Never regenerate over a file that carries a `<!-- Last updated:` footer. That footer is what makes the next run incremental, and a full rewrite silently drops whatever a human added by hand. Read the file first and pick the mode.
|
|
20
|
+
- Never reference a file, variable or path from the codebase. `DESIGN.md` is handed to tools that cannot see this repository, so it has to stand alone.
|
|
21
|
+
- Never analyze outside `.opencode/source-roots.json` when it exists.
|
|
22
|
+
|
|
23
|
+
## Contract
|
|
24
|
+
|
|
25
|
+
YAML frontmatter holding every structured token (colours, typography, spacing, elevation, motion, radii, shadows), valid as YAML design tokens. Then free-form Markdown for the look and feel, which is where intent that token values cannot carry belongs. The last line is the run's own ISO timestamp:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
<!-- Last updated: <ISO date> -->
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Modes
|
|
32
|
+
|
|
33
|
+
| The existing file | Mode |
|
|
34
|
+
|---|---|
|
|
35
|
+
| Missing, or a placeholder with no real content | Generate |
|
|
36
|
+
| Has content and a `<!-- Last updated:` footer | Update |
|
|
37
|
+
| Has content but no footer | Warn the user, then Generate |
|
|
38
|
+
|
|
39
|
+
**Generate.** `glob` for CSS, Tailwind and PostCSS config, component files, token definitions (JS, TS, JSON, YAML), theme files, and UI framework config (shadcn, MUI, Chakra).
|
|
40
|
+
|
|
41
|
+
**Update.** `git log --oneline --since="<footer date>" -- <source roots>` says what moved. Nothing changed means nothing to write: say "Design system unchanged since last update" and stop. Otherwise update only the affected tokens and sections. A replaced token system is a Generate.
|
|
42
|
+
|
|
43
|
+
## Report
|
|
44
|
+
|
|
45
|
+
Whether the file was generated or updated and which tokens changed, the palette, fonts and spacing scale found, and that `/make-design` can be re-run whenever the design system moves.
|