@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.
Files changed (81) hide show
  1. package/README.md +435 -437
  2. package/cli/fragments/archive/az.md +97 -95
  3. package/cli/fragments/archive/gh.md +96 -94
  4. package/cli/fragments/archive/gl.md +96 -94
  5. package/cli/fragments/archive/none.md +75 -73
  6. package/cli/fragments/guardrails/codegraph.md +5 -7
  7. package/cli/fragments/guardrails/humanizer.md +4 -4
  8. package/cli/fragments/guardrails/memory.md +4 -4
  9. package/cli/fragments/guardrails/rtk.md +3 -3
  10. package/cli/fragments/guardrails/simple-english.md +4 -4
  11. package/cli/fragments/ops-backlog/az.md +1 -1
  12. package/cli/fragments/ops-backlog/gh.md +1 -1
  13. package/cli/fragments/ops-backlog/jira.md +1 -1
  14. package/cli/fragments/ops-evidence/az.md +44 -41
  15. package/cli/fragments/ops-evidence/gh.md +54 -53
  16. package/cli/fragments/ops-evidence/jira.md +42 -38
  17. package/cli/fragments/ops-review/az.md +1 -1
  18. package/cli/fragments/ops-review/gh.md +1 -1
  19. package/cli/fragments/ops-review/gl.md +1 -1
  20. package/cli/fragments/ops-ship/az.md +81 -80
  21. package/cli/fragments/ops-ship/gh.md +68 -68
  22. package/cli/fragments/ops-ship/gl.md +85 -85
  23. package/cli/presets/agents-content.json +34 -53
  24. package/cli/steps/copy/agents.js +18 -17
  25. package/cli/steps/copy/opencode-json.js +5 -1
  26. package/cli/steps/copy/skills.js +98 -5
  27. package/cli/steps/optimization/patch-guardrails.js +5 -3
  28. package/cli/utils/copy.js +8 -3
  29. package/cli/utils/update-manifest.js +28 -2
  30. package/harness/.agents/skills/pc-guardrails-generic/SKILL.md +47 -68
  31. package/harness/.agents/skills/pc-make-architecture/SKILL.md +31 -51
  32. package/harness/.agents/skills/pc-make-design/SKILL.md +45 -68
  33. package/harness/.agents/skills/pc-make-engineer/SKILL.md +59 -219
  34. package/harness/.agents/skills/pc-make-engineer/signal-mapping.md +53 -68
  35. package/harness/.agents/skills/pc-make-engineer/template.md +42 -80
  36. package/harness/.agents/skills/pc-make-evidence-scaffold/SKILL.md +18 -18
  37. package/harness/.agents/skills/pc-make-evidence-scaffold/evidence-contract.md +29 -29
  38. package/harness/.agents/skills/pc-make-guardrails/SKILL.md +43 -74
  39. package/harness/.agents/skills/pc-make-guardrails/category-reference.md +10 -5
  40. package/harness/.agents/skills/pc-make-merge-risk-assess/category-reference.md +26 -7
  41. package/harness/.agents/skills/pc-make-user-model/SKILL.md +56 -66
  42. package/harness/.agents/skills/pc-ops-evidence/SKILL.md +133 -127
  43. package/harness/.agents/skills/pc-plan-apply/SKILL.md +14 -5
  44. package/harness/.agents/skills/pc-plan-apply/simple-mode.md +21 -21
  45. package/harness/.agents/skills/pc-plan-archive/SKILL.md +66 -66
  46. package/harness/.agents/skills/pc-plan-explore/SKILL.md +19 -2
  47. package/harness/.agents/skills/pc-plan-goal/SKILL.md +7 -5
  48. package/harness/.agents/skills/pc-plan-goal/output-mode.md +1 -0
  49. package/harness/.agents/skills/pc-plan-goal/output.md +71 -65
  50. package/harness/.agents/skills/pc-plan-propose/SKILL.md +1 -1
  51. package/harness/.agents/skills/pc-plan-quick/SKILL.md +46 -62
  52. package/harness/.agents/skills/pc-plan-story/SKILL.md +48 -149
  53. package/harness/.agents/skills/pc-repo-help/SKILL.md +89 -91
  54. package/harness/.agents/skills/pc-repo-initialize/SKILL.md +112 -130
  55. package/harness/.agents/skills/pc-repo-onboard/SKILL.md +32 -87
  56. package/harness/.agents/skills/pc-repo-verify/SKILL.md +2 -0
  57. package/harness/.agents/skills/pc-userstory-az/SKILL.md +71 -157
  58. package/harness/.agents/skills/pc-userstory-browser/SKILL.md +50 -122
  59. package/harness/.agents/skills/pc-userstory-gh/SKILL.md +63 -120
  60. package/harness/.agents/skills/pc-userstory-jira/SKILL.md +74 -131
  61. package/harness/.opencode/commands/init.md +5 -5
  62. package/harness/.opencode/commands/make-architecture.md +5 -5
  63. package/harness/.opencode/commands/make-design.md +5 -5
  64. package/harness/.opencode/commands/make-engineer.md +5 -5
  65. package/harness/.opencode/commands/make-evidence-scaffold.md +5 -5
  66. package/harness/.opencode/commands/make-guardrails.md +5 -5
  67. package/harness/.opencode/commands/make-user-model.md +5 -5
  68. package/harness/.opencode/commands/plan-apply.md +9 -9
  69. package/harness/.opencode/commands/plan-goal.md +5 -5
  70. package/harness/.opencode/commands/plan-quick.md +5 -5
  71. package/harness/.opencode/commands/plan-story.md +9 -9
  72. package/harness/.opencode/commands/repo-audit.md +5 -5
  73. package/harness/.opencode/commands/repo-initialize.md +5 -5
  74. package/harness/.opencode/commands/repo-onboard.md +5 -5
  75. package/harness/.opencode/commands/repo-verify.md +5 -5
  76. package/harness/.opencode/plugins/pc-subagent-monitor.js +82 -2
  77. package/harness/.opencode/plugins/pc-subagent-tiers.js +9 -6
  78. package/harness/.opencode/plugins/pc-system-reminders.js +312 -3
  79. package/harness/AGENTS.md +49 -71
  80. package/harness/opencode.jsonc +1 -1
  81. package/package.json +1 -1
@@ -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
- // heading level (###/####) and step numbers have drifted before and silently
11
- // broke removal. Matching `^#{3,4} Step N, <title>` survives both.
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 CHAIN_STEP_TITLE = 'Chain make commands'
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} Step \\d+, ${escaped}`)
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
- if (lines[i].trim() === '---') { end = i; break }
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, null,
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.hasDesign || ctx.hasArchitecture, CHAIN_STEP_TITLE, CHAIN_CONFIRM_LINE,
84
- 'Skipped during onboarding: project files already exist. Run /make-architecture or /make-design individually to regenerate.'],
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, confirmLine, note] of skips) {
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')
@@ -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
- } else {
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, path.join('.agents', 'skills', destName), process.cwd(), manifest)
150
- } else if (MARKER_SKILLS.has(destName) || GENERATABLE_SKILLS.has(destName)) {
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, path.join('.agents', 'skills', destName), process.cwd(), manifest)
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, hashFile, readUpdateManifest, recordManagedFile, writeUpdateManifest } from './update-manifest.js'
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
- const sourceHash = await hashFile(sourcePath)
86
- const destinationHash = await hashFile(destinationPath)
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
- return previousHash === await hashFile(destinationPath)
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 hashFile(sourcePath)
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 may contain instructions for selected optimization skills. These are mandatory. If a section says "call `skill("xxx")`", you must call the skill tool with that exact name before doing any work.
10
-
11
- ## Secrets
12
-
13
- - Treat `.env` files as write-only: write to them when configuring, read credentials from the environment or secret store at runtime.
14
- - Keep credentials, API keys, and tokens out of logs and output.
15
- - Stage secrets through environment variables or secret stores, committed only in encrypted or template form.
16
-
17
- ## Code
18
-
19
- - Run tests before marking done.
20
- - Run lint/build before pushing.
21
- - Keep changes small and focused.
22
- - Comments are for WHY, not WHAT. Use them only when the code does something non-obvious or the reason cannot be inferred from context. Keep comment ratio under 10%. If more than 10% of lines in a file are comments, refactor for clarity instead.
23
- - Each file should have one clear responsibility. Split by domain or feature (e.g. `user-constants.ts`, `order-types.ts`, `auth-config.ts`) rather than creating catch-all files like `constants.js`, `types.ts`, `config.js`, or `utils.ts` that collect unrelated things. A file that imports from many unrelated modules is a sign it should be split.
24
-
25
- ## Temporary files
26
-
27
- - Create scratch files only under `$REPO_ROOT/.opencode/.tmp/`; create a task-specific child directory when needed.
28
- - Keep final artifacts in their required repository path. Copy or move a scratch artifact into that path before reporting it.
29
- - Never use operating-system temporary directories or paths outside `$REPO_ROOT`.
30
- - Remove scratch files when the task ends unless they are needed to diagnose a failure.
31
-
32
- ## Security
33
-
34
- - Validate all inputs.
35
- - Escape all outputs.
36
- - Keep credentials in environment variables or secret stores, committed only in encrypted or template form.
37
-
38
- ## Communication
39
-
40
- - Ask for clarification if unclear.
41
- - Report blockers immediately.
42
- - Show progress when asked.
43
-
44
- <!-- PC-GUARDRAILS-RTK-START -->
45
- <!-- PC-GUARDRAILS-RTK-END -->
46
-
47
- <!-- PC-GUARDRAILS-CODEGRAPH-START -->
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
- Analyze the architecture of this codebase and generate or update `ARCHITECTURE.md` in the project root.
10
-
11
- ## Steps
12
-
13
- 1. **Check current state**
14
-
15
- Read `ARCHITECTURE.md`. Determine which mode to use:
16
- - Does not exist or is a placeholder (no real content): Generate mode. Create from scratch.
17
- - Exists with content and has a `<!-- Last updated:` footer: Update mode. Incrementally update (see step 2b).
18
- - Exists with content but no timestamp: warn the user, then proceed in Generate mode (full regeneration).
19
-
20
- 2a. **Generate mode: analyze the codebase**
21
-
22
- Read `.opencode/source-roots.json` when present. Only analyze those roots plus this repo's docs/config files.
23
-
24
- Use file tools to discover the architecture: `glob` for folder structure, `grep` for route/model/schema definitions, `read` config files, CI/CD workflows, Dockerfiles, README, changelogs, ADRs.
25
-
26
- 2b. **Update mode: incremental analysis**
27
-
28
- Extract the `<!-- Last updated: <ISO date> -->` timestamp from the existing file. Then:
29
- - Run `git log --oneline --since="<date>" -- <source roots>` to find what changed since the last analysis.
30
- - If nothing changed: report "Architecture unchanged since last update" and stop.
31
- - For each changed area, understand what's affected.
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
- Analyze the design system of this codebase and generate or update `DESIGN.md` in the project root.
10
-
11
- Reference material:
12
- Overview: https://stitch.withgoogle.com/docs/design-md/overview/
13
- Format: https://stitch.withgoogle.com/docs/design-md/format/
14
- Spec: https://github.com/google-labs-code/design.md
15
-
16
- Examples from the spec repo:
17
- https://github.com/google-labs-code/design.md/blob/main/examples/atmospheric-glass/DESIGN.md
18
- https://github.com/google-labs-code/design.md/blob/main/examples/paws-and-paths/DESIGN.md
19
-
20
- ## Steps
21
-
22
- 1. **Check current state**
23
-
24
- Read `DESIGN.md`. Determine which mode to use:
25
- - Does not exist or is a placeholder (no real content): Generate mode. Create from scratch.
26
- - Exists with content and has a `<!-- Last updated:` footer: Update mode. Incrementally update (see step 2b).
27
- - Exists with content but no timestamp: warn the user, then proceed in Generate mode (full regeneration).
28
-
29
- 2a. **Generate mode: analyze the codebase**
30
-
31
- Read `.opencode/source-roots.json` when present. Only analyze those roots.
32
-
33
- Use file tools to discover the design system: `glob` for CSS files, Tailwind config, PostCSS config, component files, design token definitions (JS/TS/JSON/YAML), theme files, UI framework config (shadcn, MUI, Chakra, etc.).
34
-
35
- 2b. **Update mode: incremental analysis**
36
-
37
- Extract the `<!-- Last updated: <ISO date> -->` timestamp from the existing file. Then:
38
- - Run `git log --oneline --since="<date>" -- <source roots>` to find what changed since the last analysis.
39
- - If nothing changed: report "Design system unchanged since last update" and stop.
40
- - For changed CSS/token/component files, understand what uses them.
41
- - Update only the affected tokens and sections. Preserve manually-added content in unchanged sections.
42
- - If the changes are too pervasive (entire token system replaced), fall back to Generate mode.
43
-
44
- 3. **Write DESIGN.md**
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.