@plainconceptsplatform/agent-harness 3.1.0 → 3.2.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 (65) hide show
  1. package/README.md +452 -434
  2. package/cli/commands/update.js +1 -1
  3. package/cli/fragments/archive/az.md +97 -97
  4. package/cli/fragments/archive/gh.md +96 -96
  5. package/cli/fragments/archive/gl.md +96 -96
  6. package/cli/fragments/archive/none.md +75 -75
  7. package/cli/fragments/guardrails/codegraph.md +5 -5
  8. package/cli/fragments/guardrails/humanizer.md +4 -4
  9. package/cli/fragments/guardrails/memory.md +4 -4
  10. package/cli/fragments/guardrails/rtk.md +3 -3
  11. package/cli/fragments/guardrails/simple-english.md +4 -4
  12. package/cli/fragments/ops-ship/az.md +81 -81
  13. package/cli/index.js +0 -0
  14. package/cli/presets/agents-content.json +34 -34
  15. package/cli/presets/browser.json +10 -10
  16. package/cli/steps/browser/index.js +48 -48
  17. package/cli/steps/copy/commands.js +79 -79
  18. package/cli/steps/copy/index.js +88 -88
  19. package/cli/steps/copy/skills.js +307 -307
  20. package/cli/steps/metadata/index.js +5 -2
  21. package/cli/steps/models/write.js +5 -1
  22. package/cli/utils/copy.js +5 -2
  23. package/cli/utils/paths.js +80 -80
  24. package/harness/.opencode/_gitignore +9 -9
  25. package/harness/.opencode/commands/init.md +5 -5
  26. package/harness/.opencode/commands/make-architecture.md +5 -5
  27. package/harness/.opencode/commands/make-design.md +5 -5
  28. package/harness/.opencode/commands/make-engineer.md +5 -5
  29. package/harness/.opencode/commands/make-guardrails.md +5 -5
  30. package/harness/.opencode/commands/make-user-model.md +5 -5
  31. package/harness/.opencode/commands/plan-apply.md +9 -9
  32. package/harness/.opencode/commands/plan-goal.md +5 -5
  33. package/harness/.opencode/commands/plan-quick.md +5 -5
  34. package/harness/.opencode/commands/plan-story.md +9 -9
  35. package/harness/.opencode/commands/repo-audit.md +5 -5
  36. package/harness/.opencode/commands/repo-initialize.md +5 -5
  37. package/harness/.opencode/commands/repo-onboard.md +5 -5
  38. package/harness/.opencode/commands/repo-verify.md +5 -5
  39. package/harness/.opencode/scripts/check-diff.mjs +225 -0
  40. package/harness/.opencode/scripts/code-rules.mjs +280 -0
  41. package/harness/AGENTS.md +49 -49
  42. package/package.json +12 -14
  43. package/skills/README.md +18 -18
  44. package/skills/browser-automation/SKILL.md +72 -72
  45. package/skills/pc-guardrails-generic/SKILL.md +2 -1
  46. package/skills/pc-make-architecture/SKILL.md +32 -32
  47. package/skills/pc-make-design/SKILL.md +45 -45
  48. package/skills/pc-make-engineer/signal-mapping.md +53 -53
  49. package/skills/pc-make-guardrails/SKILL.md +44 -44
  50. package/skills/pc-make-user-model/SKILL.md +56 -56
  51. package/skills/pc-plan-apply/SKILL.md +1 -1
  52. package/skills/pc-plan-apply/simple-mode.md +21 -21
  53. package/skills/pc-plan-archive/SKILL.md +66 -66
  54. package/skills/pc-plan-goal/branching.md +39 -39
  55. package/skills/pc-plan-goal/output.md +71 -71
  56. package/skills/pc-plan-quick/SKILL.md +46 -46
  57. package/skills/pc-plan-story/SKILL.md +93 -93
  58. package/skills/pc-repo-help/SKILL.md +87 -87
  59. package/skills/pc-repo-initialize/SKILL.md +119 -119
  60. package/skills/pc-repo-onboard/SKILL.md +32 -32
  61. package/skills/pc-repo-verify/SKILL.md +150 -150
  62. package/skills/pc-userstory-az/SKILL.md +71 -71
  63. package/skills/pc-userstory-browser/SKILL.md +64 -64
  64. package/skills/pc-userstory-gh/SKILL.md +63 -63
  65. package/skills/pc-userstory-jira/SKILL.md +74 -74
@@ -0,0 +1,280 @@
1
+ // The same rules Project-Health uses to score a repository (collector/src/metrics/code-rules.ts).
2
+ // Keep the two in step: a rule that differs makes the gate and the dashboard disagree.
3
+
4
+ const SOURCE = /\.(?:[cm]?[jt]sx?|cs)$/
5
+ const SKIP =
6
+ /(?:^|\/)(?:node_modules|dist|build|out|\.next|bin|obj|coverage|Migrations|migrations|wwwroot|vendor|generated|__generated__|\.github|\.opencode|\.agents|\.claude|openspec)(?:\/|$)|(?:^|\/)components\/ui\/|\.d\.ts$|\.g\.cs$|\.Designer\.cs$|\.min\.js$|AssemblyInfo\.cs$/
7
+ const TEST =
8
+ /(?:^|\/)(?:tests?|__tests__|e2e|specs?)\/|\.(?:test|spec)\.[cm]?[jt]sx?$|(?:^|\/)[\w.]+\.(?:Unit|Integration|E2E|Acceptance|Architecture)?Tests?\/|Tests?\.cs$/
9
+ const GENERATED_HEADER =
10
+ /Managed by @plainconceptsplatform|<auto-generated|@generated|This file was (?:auto-)?generated/i
11
+
12
+ export function kindOf(path) {
13
+ if (!SOURCE.test(path) || SKIP.test(path)) return null
14
+ return TEST.test(path) ? 'test' : 'source'
15
+ }
16
+
17
+ export function isGenerated(text) {
18
+ return GENERATED_HEADER.test(text.split('\n', 4).join('\n'))
19
+ }
20
+
21
+ export function lineKinds(text) {
22
+ const kinds = []
23
+ let inBlock = false
24
+ for (const raw of text.split('\n')) {
25
+ const t = raw.trim()
26
+ if (!t) kinds.push('blank')
27
+ else if (inBlock) {
28
+ kinds.push('comment')
29
+ if (t.includes('*/')) inBlock = false
30
+ } else if (t.startsWith('/*')) {
31
+ kinds.push('comment')
32
+ inBlock = !t.includes('*/')
33
+ } else if (t.startsWith('//')) kinds.push('comment')
34
+ else kinds.push('code')
35
+ }
36
+ return kinds
37
+ }
38
+
39
+ export function countLines(text) {
40
+ let codeLines = 0
41
+ let commentLines = 0
42
+ for (const kind of lineKinds(text)) {
43
+ if (kind === 'code') codeLines += 1
44
+ else if (kind === 'comment') commentLines += 1
45
+ }
46
+ return { codeLines, commentLines }
47
+ }
48
+
49
+ const FILLER = new Set(
50
+ 'a an the this that of for to in on is are be or and with by from its it get gets set sets getting setting value values instance class record struct interface enum new initializes initialises creates constructor property field method current specified given provided object function component hook type props'.split(' '),
51
+ )
52
+ const RETURNS_FILLER = new Set([
53
+ ...FILLER,
54
+ 'result', 'results', 'task', 'representing', 'asynchronous', 'async', 'operation', 'returns', 'returned', 'which', 'that',
55
+ ])
56
+
57
+ const words = text =>
58
+ text
59
+ .replace(/<see\s+cref="([^"]+)"\s*\/>/g, ' $1 ')
60
+ .replace(/<paramref\s+name="([^"]+)"\s*\/>/g, ' $1 ')
61
+ .replace(/\{@link\s+([^}\s]+)[^}]*\}/g, ' $1 ')
62
+ .replace(/<[^>]+>/g, ' ')
63
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
64
+ .replace(/([A-Z])([A-Z][a-z])/g, '$1 $2')
65
+ .toLowerCase()
66
+ .split(/[^a-z0-9]+/)
67
+ .filter(Boolean)
68
+
69
+ const stem = w => (w.length > 3 && w.endsWith('s') && !w.endsWith('ss') ? w.slice(0, -1) : w)
70
+
71
+ export function restates(text, identifier, filler = FILLER) {
72
+ const name = new Set(words(identifier.replace(/^I(?=[A-Z])/, '')).map(stem))
73
+ const said = words(text).filter(w => !filler.has(w) && !filler.has(stem(w))).map(stem)
74
+ return said.every(w => name.has(w))
75
+ }
76
+
77
+ const DECL_KEYWORD = /\b(?:class|record|struct|interface|enum|namespace|type|function)\s+([A-Za-z_$][\w$]*)/
78
+ const DECL_VAR = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)/
79
+ const DECL_CALL = /([A-Za-z_$][\w$]*)\s*(?:<[^()]*>)?\s*\(/
80
+ const DECL_PROP = /([A-Za-z_$][\w$]*)\s*[?!]?\s*(?:\{|=>|=|;|:|$)/
81
+
82
+ export function declaredName(line) {
83
+ const l = line.trim()
84
+ if (!l || l.startsWith('[') || l.startsWith('@')) return null
85
+ return (l.match(DECL_KEYWORD) ?? l.match(DECL_VAR) ?? l.match(DECL_CALL) ?? l.match(DECL_PROP))?.[1] ?? null
86
+ }
87
+
88
+ const BANNER = /^\/\/+\s*(?:([=\-*#~_+])\1{2,}.*|.*([=\-*#~_+])\2{2,})\s*$/
89
+ const REGION = /^#(?:region|endregion)\b/
90
+ const TODO = /\/\/.*\b(?:TODO|FIXME|HACK|XXX)\b/
91
+ const TRACKED = /#\d+|https?:\/\/|\b[A-Z][A-Z0-9]+-\d+\b|\(\s*issue\s/i
92
+ const APOLOGETIC =
93
+ /\b(?:not sure (?:if|whether|this|why)|hope this works|might not work|probably wrong|no idea why|i think this (?:works|is right)|quick and dirty)\b/i
94
+ const CODE_BODY =
95
+ /^(?:(?:var|let|const|await|return|using|import|export|if|else|for|foreach|while|switch|throw|new|public|private|protected|internal|static|async)\b.*[;{(]\s*$|[\w.$[\]]+\s*(?:\+\+|--|[+\-*/]?=)\s*[^=].*;\s*$|[\w.$]+(?:<[^>]*>)?\([^)]*\)\s*;\s*$|[{}]\s*;?\s*$|\}\s*else\b.*$)/
96
+ const TOOL_PRAGMA =
97
+ /^(?:nosemgrep|eslint|biome|jscpd|@ts-|prettier|istanbul|c8|v8|webpack|vite|ReSharper|resharper|language=|<auto-generated|#pragma)/
98
+
99
+ const bodyOf = trimmed => trimmed.replace(/^\/\/+\s?/, '')
100
+
101
+ export function trailingComment(line) {
102
+ let quote = null
103
+ for (let i = 0; i < line.length; i++) {
104
+ const c = line[i]
105
+ if (quote) {
106
+ if (c === '\\') i += 1
107
+ else if (c === quote) quote = null
108
+ } else if (c === "'" || c === '"' || c === '`') quote = c
109
+ else if (c === '/' && line[i + 1] === '/' && line[i - 1] !== ':') return line.slice(i)
110
+ }
111
+ return null
112
+ }
113
+
114
+ export function slopFindings(text) {
115
+ const lines = text.split('\n')
116
+ const out = []
117
+ const add = (rule, line) => out.push({ rule, line })
118
+ const nameAfter = from => {
119
+ for (let k = from; k < lines.length && k < from + 6; k++) {
120
+ const name = declaredName(lines[k])
121
+ if (name) return name
122
+ }
123
+ return null
124
+ }
125
+ let codeRun = false
126
+ for (let i = 0; i < lines.length; i++) {
127
+ const t = lines[i].trim()
128
+ const isLineComment = t.startsWith('//') && !t.startsWith('///')
129
+ const body = isLineComment ? bodyOf(t).trim() : ''
130
+ const isCode =
131
+ isLineComment && body !== '' && !TOOL_PRAGMA.test(body) && CODE_BODY.test(body) && !/\s(?:https?:)?\/\//.test(body)
132
+ if (isCode && !codeRun) add('commented-out-code', i + 1)
133
+ codeRun = isCode || (codeRun && isLineComment && !body)
134
+
135
+ if (isLineComment && BANNER.test(t)) add('banner', i + 1)
136
+ if (REGION.test(t)) add('region', i + 1)
137
+ const comment = trailingComment(t)
138
+ if (comment && TODO.test(comment) && !TRACKED.test(comment)) add('untracked-todo', i + 1)
139
+ if ((t.startsWith('//') || t.startsWith('*') || t.startsWith('/*')) && APOLOGETIC.test(t)) add('apologetic', i + 1)
140
+
141
+ if (t.startsWith('///') && t.includes('<summary>')) {
142
+ const start = i
143
+ let doc = ''
144
+ let j = i
145
+ for (; j < lines.length && lines[j].trim().startsWith('///'); j++) {
146
+ doc += ` ${lines[j].trim().replace(/^\/\/\/\s?/, '')}`
147
+ }
148
+ const name = nameAfter(j)
149
+ const summary = doc.match(/<summary>([\s\S]*?)<\/summary>/)?.[1] ?? ''
150
+ if (name && summary.trim() && restates(summary, name)) add('restating-summary', start + 1)
151
+ for (const m of doc.matchAll(/<param\s+name="([^"]+)"\s*>([\s\S]*?)<\/param>/g)) {
152
+ if (m[2].trim() && restates(m[2], m[1])) add('restating-param', start + 1)
153
+ }
154
+ for (const m of doc.matchAll(/<returns>([\s\S]*?)<\/returns>/g)) {
155
+ if (m[1].trim() && restates(m[1], '', RETURNS_FILLER)) add('filler-returns', start + 1)
156
+ }
157
+ i = j - 1
158
+ } else if (t.startsWith('/**') && !t.startsWith('/***')) {
159
+ const start = i
160
+ let j = i
161
+ let doc = ''
162
+ for (; j < lines.length; j++) {
163
+ doc += ` ${lines[j].trim()}`
164
+ if (lines[j].includes('*/')) break
165
+ }
166
+ const text = doc.replace(/^\s*\/\*\*|\*\/\s*$/g, ' ').replace(/\s\*\s?/g, ' ').trim()
167
+ const summary = text.split(/\s@\w+/)[0]?.trim() ?? ''
168
+ const name = nameAfter(j + 1)
169
+ if (name && summary && restates(summary, name)) add('restating-jsdoc', start + 1)
170
+ for (const m of text.matchAll(/@param\s+(?:\{[^}]*\}\s*)?\[?([\w$.]+)\]?\s*(?:-\s*)?([^@]*)/g)) {
171
+ if (m[2].trim() && restates(m[2], m[1])) add('restating-param', start + 1)
172
+ }
173
+ for (const m of text.matchAll(/@returns?\s+(?:\{[^}]*\}\s*)?([^@]*)/g)) {
174
+ if (m[1].trim() && restates(m[1], '', RETURNS_FILLER)) add('filler-returns', start + 1)
175
+ }
176
+ i = j
177
+ }
178
+ }
179
+ return out
180
+ }
181
+
182
+ const ISSUE_REF = /(?:\b[A-Z][\w-]*\s)?#\d+\b|\b[A-Z][A-Z0-9]+-\d+\b/
183
+ const PAST =
184
+ /\b(?:was|were|used to|previously|before|had|broke|regressed|ended up|turned out|once|anymore|no longer|caused)\b/i
185
+ const TS_INTERNAL = /^(?:async\s+)?(?:function|const|let|var|class|interface|type|enum)\b/
186
+ const CS_INTERNAL = /^(?:private|internal|private\s+protected)\b/
187
+
188
+ export function commentBlocks(text) {
189
+ const lines = text.split('\n')
190
+ const blocks = []
191
+ const firstCode = lines.findIndex(l => {
192
+ const t = l.trim()
193
+ return t !== '' && !t.startsWith('//') && !t.startsWith('/*') && !t.startsWith('*')
194
+ })
195
+ let i = 0
196
+ while (i < lines.length) {
197
+ const t = lines[i].trim()
198
+ if (!(t.startsWith('//') || t.startsWith('/*'))) {
199
+ i += 1
200
+ continue
201
+ }
202
+ const start = i
203
+ let inBlock = false
204
+ let body = ''
205
+ for (; i < lines.length; i++) {
206
+ const s = lines[i].trim()
207
+ if (inBlock) {
208
+ body += ` ${s}`
209
+ if (s.includes('*/')) inBlock = false
210
+ continue
211
+ }
212
+ if (s.startsWith('/*')) {
213
+ body += ` ${s}`
214
+ inBlock = !s.includes('*/')
215
+ continue
216
+ }
217
+ if (s.startsWith('//')) {
218
+ body += ` ${s}`
219
+ continue
220
+ }
221
+ break
222
+ }
223
+ const rules = []
224
+ if (ISSUE_REF.test(body) && PAST.test(body)) rules.push('history')
225
+ if (i - start >= 5 && start > firstCode) rules.push('essay')
226
+ if (body.trimStart().startsWith('/**') || body.trimStart().startsWith('///')) {
227
+ const next = lines.slice(i, i + 4).find(l => {
228
+ const s = l.trim()
229
+ return s !== '' && !s.startsWith('[') && !s.startsWith('@')
230
+ })
231
+ const decl = next?.trim() ?? ''
232
+ if (TS_INTERNAL.test(decl) || CS_INTERNAL.test(decl)) rules.push('internal-doc')
233
+ }
234
+ blocks.push({ start: start + 1, end: i, rules })
235
+ }
236
+ return blocks
237
+ }
238
+
239
+ const TS_CASE =
240
+ /(?:^|[^\w.$])(x|f)?(?:it|test)(\.only|\.skip|\.todo|\.concurrent|\.each\s*(?:\([^)]*\)|`[^`]*`))?\s*\(\s*['"`]/g
241
+ const CS_CASE = /\[(?:Fact|Theory|SkippableFact|SkippableTheory|Test|TestMethod)\b([^\]]*)\]/g
242
+ const TS_ASSERT = /\b(?:expect|assert)\w*(?:\.\w+)?\s*\(|\.should\b/g
243
+ const TS_CALL_ASSERT = /\.(?:not\.)?(?:toHaveBeenCalled\w*|toBeCalled\w*|toHaveBeenNthCalledWith)\s*\(/g
244
+ const CS_ASSERT = /\bAssert\w*\.\w+|\.Should\w*\(|\bVerify\w*\(|\bThrows\w*(?:Async)?\s*[<(]|\.Received\(/g
245
+ const CS_CALL_ASSERT = /\.Received\(|\.DidNotReceive\(|\.Verify\(\s*\w+\s*=>/g
246
+ const SNAPSHOT = /\.toMatch(?:Inline|File)?Snapshot\s*\(|\bawait\s+Verify(?:Json|Xml)?\s*\(/
247
+
248
+ const count = (text, re) => (text.match(re) ?? []).length
249
+
250
+ export function testStats(path, text) {
251
+ const cs = path.endsWith('.cs')
252
+ const starts = []
253
+ for (const m of text.matchAll(cs ? CS_CASE : TS_CASE)) {
254
+ if (cs) {
255
+ starts.push({ index: m.index, skipped: /\bSkip\s*=/.test(m[1] ?? ''), focused: false })
256
+ } else {
257
+ const modifier = m[2] ?? ''
258
+ if (modifier === '.todo') continue
259
+ starts.push({ index: m.index, skipped: m[1] === 'x' || modifier === '.skip', focused: m[1] === 'f' || modifier === '.only' })
260
+ }
261
+ }
262
+ const stats = { cases: starts.length, skipped: 0, focused: 0, snapshot: 0, noAssertion: 0, mockOnly: 0 }
263
+ if (cs) stats.skipped += count(text, /\[Ignore\b/g)
264
+ else {
265
+ stats.skipped += count(text, /\b(?:describe\.skip|xdescribe)\s*\(/g)
266
+ stats.focused += count(text, /\b(?:describe\.only|fdescribe)\s*\(/g)
267
+ }
268
+ for (let k = 0; k < starts.length; k++) {
269
+ const s = starts[k]
270
+ const segment = text.slice(s.index, starts[k + 1]?.index ?? text.length)
271
+ if (s.skipped) stats.skipped += 1
272
+ if (s.focused) stats.focused += 1
273
+ if (SNAPSHOT.test(segment)) stats.snapshot += 1
274
+ const asserts = count(segment, cs ? CS_ASSERT : TS_ASSERT)
275
+ const callAsserts = count(segment, cs ? CS_CALL_ASSERT : TS_CALL_ASSERT)
276
+ if (asserts === 0) stats.noAssertion += 1
277
+ else if (callAsserts > 0 && callAsserts >= asserts) stats.mockOnly += 1
278
+ }
279
+ return stats
280
+ }
package/harness/AGENTS.md CHANGED
@@ -1,49 +1,49 @@
1
- # AGENTS.md
2
-
3
- <!-- PC-NOT-INITIALIZED -->
4
-
5
- # Agent operating guide
6
-
7
- The operating contract for AI agents in this repository. Agent-agnostic: OpenCode, Claude Code, Codex, Gemini and others.
8
-
9
- ## Session context
10
-
11
- Before a non-trivial change, read `AGENTS.md` for workflow rules, `ARCHITECTURE.md` for boundaries and component interactions, `DESIGN.md` for UI and design-system work, and the active OpenSpec change or the specification covering the area you are changing.
12
-
13
- Command aliases: OpenSpec skills may reference `/opsx-propose`, `/opsx-apply`, `/opsx-archive`, or `/opsx-explore`. Always substitute them with the `pc-plan-propose`, `pc-plan-apply`, `pc-plan-archive`, and `pc-plan-explore` skills respectively. User-facing command names are `/plan-propose`, `/plan-apply`, `/plan-archive`, and `/plan-explore`. Never mention the `opsx-` names to the user.
14
-
15
- ## Workflow ownership
16
-
17
- <!-- PC-PLATFORM-WORKFLOW-START -->
18
- <!-- PC-PLATFORM-WORKFLOW-END -->
19
-
20
- ## Planning and execution
21
-
22
- - Never combine an unrelated refactor with the work you were asked to do. It arrives under a message that does not mention it, and the reviewer approves both.
23
- - Never introduce a new convention where the repository already has one, and never guess when requirements, architecture or a security constraint are unclear. Ask.
24
-
25
- ## Engineer selection
26
-
27
- Inspect `.opencode/agents/*.md` before spawning. Prefer the most specialized custom engineer. `build` and `plan` are the only primaries and are never spawned; `fullstack-engineer` is the body they share and the fallback worker, so prefer a specialist over it. If no specialist matches, tell the user to create one with `/make-engineer`. Spawn only engineers present in that directory.
28
-
29
- The `pc-plan-apply` skill is authoritative for subagent waves, dependency ordering, retries, and concurrency. A spawn past `agents.maxConcurrent` is denied (`pc-subagent-monitor`); a denied spawn is not a failed task, so re-issue it in the next wave.
30
-
31
- ## Tool and repository safety
32
-
33
- - Never expose or commit secrets, credentials, tokens, or production data.
34
- - Never overwrite a generated file, or uncommitted changes you did not make. The tree may be shared with a person and another agent.
35
- - Never bypass a check, weaken a test, or silence a lint rule to reach a green result. A green run that was arranged is worse than a red one, because nobody looks again.
36
- - Commit, push, open a pull request, change dependencies, or touch deployment configuration only with the user's explicit approval and the repository's stated process.
37
-
38
- ## Verification and completion
39
-
40
- - Never call a bug fixed without a test that would have caught it, where one is practical.
41
- - Never leave a specification, `ARCHITECTURE.md` or `DESIGN.md` asserting something this change made false.
42
- - Never end on a blocker without naming it and the decision it needs. An unattended run that stops quietly looks like one that finished.
43
-
44
- ## Skills
45
-
46
- Skills live in `.agents/skills/`. Always installed: `@pc-guardrails-generic`, `@pc-guardrails-project`, and `@browser-automation`. An entry under `## Abilities` is not a passive reference: call the `skill` tool once per `@skill-name`, guardrails first. Editing, shell and spawning stay blocked until you have (`pc-system-reminders`), and a loaded skill can require further loads.
47
-
48
- <!-- PC-PLATFORM-SKILLS-GUIDE-START -->
49
- <!-- PC-PLATFORM-SKILLS-GUIDE-END -->
1
+ # AGENTS.md
2
+
3
+ <!-- PC-NOT-INITIALIZED -->
4
+
5
+ # Agent operating guide
6
+
7
+ The operating contract for AI agents in this repository. Agent-agnostic: OpenCode, Claude Code, Codex, Gemini and others.
8
+
9
+ ## Session context
10
+
11
+ Before a non-trivial change, read `AGENTS.md` for workflow rules, `ARCHITECTURE.md` for boundaries and component interactions, `DESIGN.md` for UI and design-system work, and the active OpenSpec change or the specification covering the area you are changing.
12
+
13
+ Command aliases: OpenSpec skills may reference `/opsx-propose`, `/opsx-apply`, `/opsx-archive`, or `/opsx-explore`. Always substitute them with the `pc-plan-propose`, `pc-plan-apply`, `pc-plan-archive`, and `pc-plan-explore` skills respectively. User-facing command names are `/plan-propose`, `/plan-apply`, `/plan-archive`, and `/plan-explore`. Never mention the `opsx-` names to the user.
14
+
15
+ ## Workflow ownership
16
+
17
+ <!-- PC-PLATFORM-WORKFLOW-START -->
18
+ <!-- PC-PLATFORM-WORKFLOW-END -->
19
+
20
+ ## Planning and execution
21
+
22
+ - Never combine an unrelated refactor with the work you were asked to do. It arrives under a message that does not mention it, and the reviewer approves both.
23
+ - Never introduce a new convention where the repository already has one, and never guess when requirements, architecture or a security constraint are unclear. Ask.
24
+
25
+ ## Engineer selection
26
+
27
+ Inspect `.opencode/agents/*.md` before spawning. Prefer the most specialized custom engineer. `build` and `plan` are the only primaries and are never spawned; `fullstack-engineer` is the body they share and the fallback worker, so prefer a specialist over it. If no specialist matches, tell the user to create one with `/make-engineer`. Spawn only engineers present in that directory.
28
+
29
+ The `pc-plan-apply` skill is authoritative for subagent waves, dependency ordering, retries, and concurrency. A spawn past `agents.maxConcurrent` is denied (`pc-subagent-monitor`); a denied spawn is not a failed task, so re-issue it in the next wave.
30
+
31
+ ## Tool and repository safety
32
+
33
+ - Never expose or commit secrets, credentials, tokens, or production data.
34
+ - Never overwrite a generated file, or uncommitted changes you did not make. The tree may be shared with a person and another agent.
35
+ - Never bypass a check, weaken a test, or silence a lint rule to reach a green result. A green run that was arranged is worse than a red one, because nobody looks again.
36
+ - Commit, push, open a pull request, change dependencies, or touch deployment configuration only with the user's explicit approval and the repository's stated process.
37
+
38
+ ## Verification and completion
39
+
40
+ - Never call a bug fixed without a test that would have caught it, where one is practical.
41
+ - Never leave a specification, `ARCHITECTURE.md` or `DESIGN.md` asserting something this change made false.
42
+ - Never end on a blocker without naming it and the decision it needs. An unattended run that stops quietly looks like one that finished.
43
+
44
+ ## Skills
45
+
46
+ Skills live in `.agents/skills/`. Always installed: `@pc-guardrails-generic`, `@pc-guardrails-project`, and `@browser-automation`. An entry under `## Abilities` is not a passive reference: call the `skill` tool once per `@skill-name`, guardrails first. Editing, shell and spawning stay blocked until you have (`pc-system-reminders`), and a loaded skill can require further loads.
47
+
48
+ <!-- PC-PLATFORM-SKILLS-GUIDE-START -->
49
+ <!-- PC-PLATFORM-SKILLS-GUIDE-END -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plainconceptsplatform/agent-harness",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Installs the Plain Concepts Platform Harness into any codebase, and keeps it up to date. Wires OpenCode, OpenSpec, codegraph, and agentmemory into a multi-agent workflow that runs on native parallel subagents.",
5
5
  "keywords": [
6
6
  "opencode",
@@ -42,7 +42,6 @@
42
42
  "engines": {
43
43
  "node": ">=18"
44
44
  },
45
- "packageManager": "pnpm@11.10.0",
46
45
  "dependencies": {
47
46
  "@inquirer/prompts": "^7.0.0",
48
47
  "chalk": "^5.0.0",
@@ -51,17 +50,6 @@
51
50
  "jsonc-parser": "^3.3.1",
52
51
  "ora": "^8.0.0"
53
52
  },
54
- "scripts": {
55
- "lint": "eslint .",
56
- "lint:fix": "eslint . --fix",
57
- "lint:md": "markdownlint-cli2 \"**/*.md\"",
58
- "lint:md:fix": "markdownlint-cli2 --fix \"**/*.md\"",
59
- "test": "vitest run",
60
- "test:watch": "vitest",
61
- "prepublishOnly": "npm run lint && npm run lint:md && npm run test",
62
- "release:dry": "pnpm publish --dry-run --no-git-checks --access public",
63
- "release": "pnpm publish --no-git-checks --access public"
64
- },
65
53
  "devDependencies": {
66
54
  "@eslint/js": "^9.0.0",
67
55
  "eslint": "^9.0.0",
@@ -71,5 +59,15 @@
71
59
  },
72
60
  "vitest": {
73
61
  "environment": "node"
62
+ },
63
+ "scripts": {
64
+ "lint": "eslint .",
65
+ "lint:fix": "eslint . --fix",
66
+ "lint:md": "markdownlint-cli2 \"**/*.md\"",
67
+ "lint:md:fix": "markdownlint-cli2 --fix \"**/*.md\"",
68
+ "test": "vitest run",
69
+ "test:watch": "vitest",
70
+ "release:dry": "pnpm publish --dry-run --no-git-checks --access public",
71
+ "release": "pnpm publish --no-git-checks --access public"
74
72
  }
75
- }
73
+ }
package/skills/README.md CHANGED
@@ -1,18 +1,18 @@
1
- # skills/
2
-
3
- Two kinds of skills live here, both discoverable by `npx skills`:
4
-
5
- | Kind | Directories | Purpose |
6
- | --- | --- | --- |
7
- | Installable `pc-*` + `browser-automation` | `pc-*`, `browser-automation` | What the CLI copies into a target project under `.agents/skills/` during onboarding. Plain Concepts' own skills. |
8
- | CLI-self | `agent-harness-cli` | Skills for agents working on THIS CLI repo (driving `npx @plainconceptsplatform/agent-harness`). Never copied into target projects. |
9
-
10
- Discoverability: any `SKILL.md` in a subdirectory here is auto-discovered by OpenCode, Claude Code, and `npx skills` (which scans `./skills/` as a recognized project-skill location).
11
-
12
- ## Using one
13
-
14
- Point your agent at the directory, or copy a skill into wherever your tool discovers skills:
15
-
16
- ```bash
17
- cp -r skills/agent-harness-cli ~/.claude/skills/
18
- ```
1
+ # skills/
2
+
3
+ Two kinds of skills live here, both discoverable by `npx skills`:
4
+
5
+ | Kind | Directories | Purpose |
6
+ | --- | --- | --- |
7
+ | Installable `pc-*` + `browser-automation` | `pc-*`, `browser-automation` | What the CLI copies into a target project under `.agents/skills/` during onboarding. Plain Concepts' own skills. |
8
+ | CLI-self | `agent-harness-cli` | Skills for agents working on THIS CLI repo (driving `npx @plainconceptsplatform/agent-harness`). Never copied into target projects. |
9
+
10
+ Discoverability: any `SKILL.md` in a subdirectory here is auto-discovered by OpenCode, Claude Code, and `npx skills` (which scans `./skills/` as a recognized project-skill location).
11
+
12
+ ## Using one
13
+
14
+ Point your agent at the directory, or copy a skill into wherever your tool discovers skills:
15
+
16
+ ```bash
17
+ cp -r skills/agent-harness-cli ~/.claude/skills/
18
+ ```
@@ -1,72 +1,72 @@
1
- ---
2
- name: browser-automation
3
- description: Reliable, composable browser automation using agent-browser. Use when capturing screenshots of a locally running app, clicking UI elements, reading page content, or automating browser interactions on localhost.
4
- license: MIT
5
- compatibility: Requires agent-browser CLI installed (Rust binary) and running.
6
- metadata:
7
- author: copilots
8
- version: "2.0"
9
- ---
10
-
11
- This skill operates on `localhost` URLs only. Screenshots, clicks, typing, scrolling, and reads on locally running apps are in scope. External services (github.com, dev.azure.com, npmjs.com, etc.) are out of scope for browser tools.
12
-
13
- The only exception: when the user selects "Others (Browser)" as their backlog platform during onboarding, the `pc-userstory` skill may navigate to work item URLs the user explicitly provides. That is the sole case where external navigation is permitted, and only to URLs the user explicitly gives you.
14
-
15
- ## Best-practice workflow
16
-
17
- 1. Open the page: `agent-browser open <url>` (launches its own Chrome; reuses the running daemon)
18
- 2. Take a snapshot: `agent-browser snapshot` to get the accessibility tree with `@ref` handles
19
- 3. Interact by ref: `agent-browser click @e2`, `agent-browser fill @e3 "text"`
20
- 4. Wait when needed: `agent-browser wait --load networkidle` or `agent-browser wait <selector>`
21
- 5. Confirm the result with a fresh `snapshot`, `get text`, or `screenshot`
22
-
23
- Refs like `@e2` come from the last snapshot and go stale after page changes: retake the snapshot after navigation or dynamic updates before reusing them. When a click fails because another element covers the target (consent banner, modal), dismiss or interact with the covering element, then take a fresh snapshot before retrying.
24
-
25
- ## Tool access
26
-
27
- - MCP tools (inside OpenCode): `agent_browser_open`, `agent_browser_snapshot`, `agent_browser_click`, `agent_browser_fill`, `agent_browser_type`, `agent_browser_wait_for_selector`, `agent_browser_screenshot`, `agent_browser_eval`, `agent_browser_close` — configured in `opencode.jsonc` with `--tools core`
28
- - CLI (any agent, any host): every command below works identically via bash
29
- - Diagnose the install: `agent-browser doctor`
30
-
31
- ## Selecting elements
32
-
33
- - Prefer refs from `snapshot` (`@e1`, `@e2`, ...) over CSS selectors
34
- - CSS selectors also work: `agent-browser click "#submit"`, `agent-browser fill "#email" "text"`
35
- - Semantic locators: `agent-browser find role button click --name "Submit"`, `find text "Sign In" click`, `find label "Email" fill "value"`, `find testid <id> click`
36
- - Native selects: `agent-browser select <sel> <value>`
37
-
38
- ## Reading state
39
-
40
- - `agent-browser get text <sel>` — text content of an element
41
- - `agent-browser get value <sel>` — input value
42
- - `agent-browser snapshot` — structured accessibility tree (preferred for parsing)
43
- - `agent-browser screenshot [path]` — `--full` for full page, `--annotate` for numbered labels
44
- - `agent-browser read` — agent-readable markdown of the active tab (no Chrome launch for URL reads)
45
-
46
- ## Tabs and sessions
47
-
48
- - `agent-browser tab` lists tabs with stable `t1`, `t2` ids; `tab new --label <name>` and `tab <label>` switch
49
- - `agent-browser close` closes the browser; `close --all` closes every session
50
-
51
- ## CLI-first debugging
52
-
53
- - `agent-browser doctor` — diagnose install, Chrome, and daemon state
54
- - `agent-browser console` — view console messages; `agent-browser errors` — uncaught exceptions
55
- - `agent-browser eval "<js>"` — run JavaScript in the page
56
- - `agent-browser network requests --filter api` — inspect tracked requests
57
-
58
- ## Troubleshooting
59
-
60
- - Stale ref: take a fresh `snapshot` after any navigation or DOM change
61
- - Covered click: the error names the covering element; dismiss it, re-snapshot, retry
62
- - Selector fails: confirm the content exists with `agent-browser get text` or a snapshot before retrying
63
- - Daemon or Chrome issues: run `agent-browser doctor`
64
-
65
- ## Scope
66
-
67
- - Screenshots of locally running app on `localhost` URLs: in scope
68
- - Click, type, scroll, read on `localhost` pages: in scope
69
- - Navigate to work item URLs the user explicitly provides (when backlog platform is "Others (Browser)"): in scope
70
- - Navigate to external services for non-work-item purposes: out of scope
71
- - DevOps or GitHub CLI operations via browser tools: out of scope
72
- - Reading or modifying production systems via browser tools: out of scope
1
+ ---
2
+ name: browser-automation
3
+ description: Reliable, composable browser automation using agent-browser. Use when capturing screenshots of a locally running app, clicking UI elements, reading page content, or automating browser interactions on localhost.
4
+ license: MIT
5
+ compatibility: Requires agent-browser CLI installed (Rust binary) and running.
6
+ metadata:
7
+ author: copilots
8
+ version: "2.0"
9
+ ---
10
+
11
+ This skill operates on `localhost` URLs only. Screenshots, clicks, typing, scrolling, and reads on locally running apps are in scope. External services (github.com, dev.azure.com, npmjs.com, etc.) are out of scope for browser tools.
12
+
13
+ The only exception: when the user selects "Others (Browser)" as their backlog platform during onboarding, the `pc-userstory` skill may navigate to work item URLs the user explicitly provides. That is the sole case where external navigation is permitted, and only to URLs the user explicitly gives you.
14
+
15
+ ## Best-practice workflow
16
+
17
+ 1. Open the page: `agent-browser open <url>` (launches its own Chrome; reuses the running daemon)
18
+ 2. Take a snapshot: `agent-browser snapshot` to get the accessibility tree with `@ref` handles
19
+ 3. Interact by ref: `agent-browser click @e2`, `agent-browser fill @e3 "text"`
20
+ 4. Wait when needed: `agent-browser wait --load networkidle` or `agent-browser wait <selector>`
21
+ 5. Confirm the result with a fresh `snapshot`, `get text`, or `screenshot`
22
+
23
+ Refs like `@e2` come from the last snapshot and go stale after page changes: retake the snapshot after navigation or dynamic updates before reusing them. When a click fails because another element covers the target (consent banner, modal), dismiss or interact with the covering element, then take a fresh snapshot before retrying.
24
+
25
+ ## Tool access
26
+
27
+ - MCP tools (inside OpenCode): `agent_browser_open`, `agent_browser_snapshot`, `agent_browser_click`, `agent_browser_fill`, `agent_browser_type`, `agent_browser_wait_for_selector`, `agent_browser_screenshot`, `agent_browser_eval`, `agent_browser_close` — configured in `opencode.jsonc` with `--tools core`
28
+ - CLI (any agent, any host): every command below works identically via bash
29
+ - Diagnose the install: `agent-browser doctor`
30
+
31
+ ## Selecting elements
32
+
33
+ - Prefer refs from `snapshot` (`@e1`, `@e2`, ...) over CSS selectors
34
+ - CSS selectors also work: `agent-browser click "#submit"`, `agent-browser fill "#email" "text"`
35
+ - Semantic locators: `agent-browser find role button click --name "Submit"`, `find text "Sign In" click`, `find label "Email" fill "value"`, `find testid <id> click`
36
+ - Native selects: `agent-browser select <sel> <value>`
37
+
38
+ ## Reading state
39
+
40
+ - `agent-browser get text <sel>` — text content of an element
41
+ - `agent-browser get value <sel>` — input value
42
+ - `agent-browser snapshot` — structured accessibility tree (preferred for parsing)
43
+ - `agent-browser screenshot [path]` — `--full` for full page, `--annotate` for numbered labels
44
+ - `agent-browser read` — agent-readable markdown of the active tab (no Chrome launch for URL reads)
45
+
46
+ ## Tabs and sessions
47
+
48
+ - `agent-browser tab` lists tabs with stable `t1`, `t2` ids; `tab new --label <name>` and `tab <label>` switch
49
+ - `agent-browser close` closes the browser; `close --all` closes every session
50
+
51
+ ## CLI-first debugging
52
+
53
+ - `agent-browser doctor` — diagnose install, Chrome, and daemon state
54
+ - `agent-browser console` — view console messages; `agent-browser errors` — uncaught exceptions
55
+ - `agent-browser eval "<js>"` — run JavaScript in the page
56
+ - `agent-browser network requests --filter api` — inspect tracked requests
57
+
58
+ ## Troubleshooting
59
+
60
+ - Stale ref: take a fresh `snapshot` after any navigation or DOM change
61
+ - Covered click: the error names the covering element; dismiss it, re-snapshot, retry
62
+ - Selector fails: confirm the content exists with `agent-browser get text` or a snapshot before retrying
63
+ - Daemon or Chrome issues: run `agent-browser doctor`
64
+
65
+ ## Scope
66
+
67
+ - Screenshots of locally running app on `localhost` URLs: in scope
68
+ - Click, type, scroll, read on `localhost` pages: in scope
69
+ - Navigate to work item URLs the user explicitly provides (when backlog platform is "Others (Browser)"): in scope
70
+ - Navigate to external services for non-work-item purposes: out of scope
71
+ - DevOps or GitHub CLI operations via browser tools: out of scope
72
+ - Reading or modifying production systems via browser tools: out of scope
@@ -15,7 +15,7 @@ The marker sections below name the optimization skills this project selected. Lo
15
15
 
16
16
  ## Code
17
17
 
18
- - The default is zero comments. Add one only where the code is genuinely non-obvious. Past 10% comment-to-code ratio, refactor for clarity. Never add comments that restate the code, label variables, TODOs without a linked issue, commented-out code, separator banners, or auto-generated docstrings/JSDoc/XML docs unless explicitly asked.
18
+ - Zero comments by default. Write one only for what the code cannot show and we do not control: a vendor bug, an external contract, a legal rule. Leave every file you touch with no comment slop, old or new.
19
19
  - Never add a file that collects unrelated things — `constants.js`, `types.ts`, `utils.ts`. One responsibility per file, split by domain. A file importing from many unrelated modules is already the symptom.
20
20
 
21
21
  ## Temporary files
@@ -28,6 +28,7 @@ The marker sections below name the optimization skills this project selected. Lo
28
28
  - Never write more tests than the change requires. A bug fix gets one regression test. A new function gets tests for its public contract, not every internal branch. Never aim for a coverage number.
29
29
  - Never test framework or library internals. Test your code's contract, not that EF Core saves a tracked entity.
30
30
  - Never generate integration, e2e, or snapshot tests unless explicitly asked. Mock nothing you do not own — use a fake that records state. Never add a test that duplicates a type-check or linter rule.
31
+ - `node .opencode/scripts/check-diff.mjs` enforces the comment and test rules on the diff and must exit 0.
31
32
 
32
33
  <!-- PC-GUARDRAILS-RTK-START -->
33
34
  <!-- PC-GUARDRAILS-RTK-END -->