@plainconceptsplatform/agent-harness 3.0.2 → 3.1.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.
@@ -1,123 +1,150 @@
1
- ---
2
- name: pc-repo-verify
3
- description: Write a reproduction plan for the completed change as a journey of agent-browser waypoints stored with the change. Does not run checks, launch a browser, or take screenshots. Invoked by /repo-verify and the plan-goal pipeline.
4
- license: MIT
5
- ---
6
-
7
- # Repo Verify
8
-
9
- Write the verification plan for the current branch's change. The checks gate lives in `pc-plan-apply` step 10 (lint, typecheck, test, build). This skill produces the reproduction plan instead: a journey through the new functionality with observation waypoints, written so a later agent-browser executor skill can follow it verbatim.
10
-
11
- This skill never launches a browser, never takes a screenshot, never starts a server. It only reads the change and writes a plan file. It is the agent-executed counterpart to `pc-ops-evidence`, whose `capturePlan` stays the CI-side screenshot workflow.
12
-
13
- Work only on the current branch: do not switch branches, push, create pull requests, or contact external platforms. Write the plan into the change directory; it is carried into the archive by `pc-plan-archive`.
14
-
15
- ## Step 1: Read the change
16
-
17
- 1. Resolve the change id from the caller (autonomous mode) or from the current branch's unarchived change under `openspec/changes/<change-id>/`. Read `proposal.md`, `tasks.md`, and `design.md` when present.
18
- 2. Read `.opencode/source-roots.json` when it exists; use its non-empty `roots` array to scope frontend source, otherwise use the repository root.
19
- 3. Inspect `git diff` against the branch base and the working tree for context on what changed.
20
-
21
- ## Step 2: Determine UI impact
22
-
23
- Decide whether the change is reachable from something a user sees or does in the browser.
24
-
25
- 1. **Direct** — changed files include user-visible UI: `*.tsx`/`jsx`/`vue`/`svelte`, `*.css`/`scss`/`less`, pages, layouts, components, navigation, routes. The affected surfaces are the routes/components the diff touches.
26
- 2. **Indirect (backend-only diff, frontend-affected)** — the diff touches only API/backend code, but the changed contract is consumed by the frontend. Trace it: for every changed endpoint, route, handler, query, or exported function, search the frontend source roots for references to that name or path. If any reference exists, the frontend **is** affected. Map the surfaces (the routes/components that import or call the changed contract) and build the journey through them, treating the API change as an indirect UI change.
27
- 3. **None** — no path reaches the frontend. Write the stub plan (Step 3, `status: not-applicable`) and stop. Do not fabricate a journey.
28
-
29
- Mixed or unknown counts as affected: be safe.
30
-
31
- ## Step 3: Write the plan
32
-
33
- Write `verification-plan.md` into the change directory:
34
-
35
- ```
36
- openspec/changes/<change-id>/verification-plan.md
37
- ```
38
-
39
- The plan is written in agent-browser idiom so a future executor skill can follow it verbatim. Format:
40
-
41
- ```markdown
42
- # verification-plan.md — <change-id>
43
-
44
- environment:
45
- start: pnpm run dev
46
- url: http://localhost:3000
47
- login: mock-sso
48
- data: |
49
- <seed/data preconditions, or "none">
50
-
51
- journey:
52
- - id: 1
53
- arrive:
54
- - agent-browser open http://localhost:3000/
55
- - agent-browser wait --load networkidle
56
- waypoint: wp-1-home
57
- capture: home
58
- expect: homepage renders; nav is visible
59
-
60
- - id: 2
61
- arrive:
62
- - agent-browser find text "Items" click
63
- waypoint: wp-2-items-list
64
- capture: items-list
65
- expect: the items list shows the seeded rows
66
-
67
- - id: 3
68
- arrive:
69
- - agent-browser find first ".item-row" click
70
- - agent-browser wait --load networkidle
71
- waypoint: wp-3-detail-before
72
- capture: detail-before
73
- expect: the item detail panel is open and shows the record
74
-
75
- - id: 4
76
- arrive:
77
- - agent-browser find role button click --name "Status"
78
- waypoint: wp-4-detail-active
79
- capture: detail-active
80
- expect: the status badge now reads "Active"
81
- ```
82
-
83
- Replace the example routes, captions, locators, and expectations with the actual change. Locators must be agent-browser-native and stable: prefer `find role`, `find text`, `find label`, `find testid`; use CSS selectors only when no semantic locator exists. Use `wait --load networkidle` (or `wait <selector>` / `wait --text "<known>"`) so the executor reaches the state before observing.
84
-
85
- ### Waypoint rules
86
-
87
- 1. The first waypoint is always the baseline at `/`: `wp-1-home`, `capture: home`.
88
- 2. Place waypoints as a pre/post pair around the changed behavior — the state immediately before and immediately after the core change is exercised.
89
- 3. Every waypoint has a `waypoint:` id (`wp-<n>-<slug>`), a `capture:` id (kebab-case, unique), and an `expect:` line stating what must be observable there.
90
- 4. Dynamic data uses `sampleId: first` or `sampleId: any` in the `data:` block; locators that target a record resolve through it.
91
- 5. The number of waypoints is the minimum that reproduces and proves the behavior — not every screenshottable moment, just the meaningful observation points.
92
-
93
- ### When the change is not UI-reachable
94
-
95
- Write the stub and stop:
96
-
97
- ```markdown
98
- # verification-plan.md — <change-id>
99
-
100
- status: not-applicable
101
- reason: |
102
- Change is backend-only and no frontend code references the changed contract
103
- (<names>). No user-visible journey exists; the executor skill should skip.
104
- ```
105
-
106
- Adapt the reason to the actual change. Do not invent a journey when Step 2 concluded `None`.
107
-
108
- ### Rules for writing
109
-
110
- - This skill MUST NOT launch a browser, start the app, take screenshots, or run build/test/lint. Those belong elsewhere.
111
- - Never commit, stage, or push verifications. The caller owns git.
112
- - The plan file is the only artifact written. Do not modify the change's specs, tasks, or proposal.
113
- - Keep locators stable and observable: prefer semantic locators over brittle CSS paths.
114
-
115
- ## Step 4: Result
116
-
117
- Report one of:
118
-
119
- - `PLAN_WRITTEN <change-id>` — a journey with waypoints was written to `verification-plan.md`.
120
- - `STUB_WRITTEN <change-id>` — `status: not-applicable` stub written with a reason.
121
- - `NOT WRITTEN` — hard blocker (unreadable change, missing change directory). Report the blocker and the exact next step.
122
-
123
- A correct plan file written to the change directory is the success condition. Write a stub for non-UI changes; never skip writing the file.
1
+ ---
2
+ name: pc-repo-verify
3
+ description: Write the verification plan for the completed change, a journey through the new functionality as a fenced YAML block that the evidence workflow replays in headless Chromium after the pull request merges. Does not run checks, launch a browser, or take screenshots. Invoked by /repo-verify and the plan-goal pipeline.
4
+ license: MIT
5
+ ---
6
+
7
+ # Repo Verify
8
+
9
+ Write the verification plan for the current branch's change. The checks gate lives in `pc-plan-apply` step 10 (lint, typecheck, test, build). This skill produces the reproduction plan instead: a journey through the new functionality with one screenshot per waypoint.
10
+
11
+ The plan is executed by a machine, not read by one. When the pull request merges, the evidence workflow (`evidence.yml` from `@plainconceptsplatform/workflows`) checks out the pull request head, starts the app, replays every step below in headless Chromium with no model in the loop, and archives one screenshot per waypoint on the pull request. A step it cannot follow is a screenshot of the wrong screen. Write every step so it can be followed literally from a freshly started app.
12
+
13
+ This skill never launches a browser, never takes a screenshot, never starts a server. It only reads the change and writes a plan file.
14
+
15
+ Work only on the current branch: do not switch branches, push, create pull requests, or contact external platforms. Write the plan into the change directory; it is carried into the archive by `pc-plan-archive`.
16
+
17
+ ## Project environment
18
+
19
+ How this project's app starts, where it answers, how a person signs in locally, and what data a fresh start already has. Read it before writing a journey; it is the project's, and an update keeps it.
20
+
21
+ <!-- PC-PROJECT-ENVIRONMENT-START -->
22
+ - Start: `pnpm run dev` at the repository root brings up the whole stack with mock authentication.
23
+ - URL: `http://localhost:3000`.
24
+ - Sign-in: none recorded yet. Find the local sign-in (a mock account picker, a dev login button) in the source and write its steps into the first waypoint.
25
+ - Seeded data: none recorded yet. Create what the journey needs through the UI.
26
+ <!-- PC-PROJECT-ENVIRONMENT-END -->
27
+
28
+ ## Step 1: Read the change
29
+
30
+ 1. Resolve the change id from the caller (autonomous mode) or from the current branch's unarchived change under `openspec/changes/<change-id>/`. Read `proposal.md`, `tasks.md`, and `design.md` when present.
31
+ 2. Read `.opencode/source-roots.json` when it exists; use its non-empty `roots` array to scope frontend source, otherwise use the repository root.
32
+ 3. Inspect `git diff` against the branch base and the working tree for context on what changed.
33
+
34
+ ## Step 2: Determine UI impact
35
+
36
+ Decide whether the change is reachable from something a user sees or does in the browser.
37
+
38
+ 1. **Direct**: changed files include user-visible UI: `*.tsx`/`jsx`/`vue`/`svelte`, `*.css`/`scss`/`less`, pages, layouts, components, navigation, routes. The affected surfaces are the routes/components the diff touches.
39
+ 2. **Indirect (backend-only diff, frontend-affected)**: the diff touches only API/backend code, but the changed contract is consumed by the frontend. Trace it: for every changed endpoint, route, handler, query, or exported function, search the frontend source roots for references to that name or path. If any reference exists, the frontend **is** affected. Map the surfaces (the routes/components that import or call the changed contract) and build the journey through them.
40
+ 3. **None**: no path reaches the frontend. Write the stub plan (Step 3, `status: not-applicable`) and stop. Do not fabricate a journey.
41
+
42
+ Mixed or unknown counts as affected: be safe.
43
+
44
+ ## Step 3: Write the plan
45
+
46
+ Write `verification-plan.md` into the change directory:
47
+
48
+ ```
49
+ openspec/changes/<change-id>/verification-plan.md
50
+ ```
51
+
52
+ Prose around the block is for people and is ignored by the replay. The replay reads the first fenced `yaml` block that holds a `journey`:
53
+
54
+ ````markdown
55
+ # Verification plan: <change-id>
56
+
57
+ One or two sentences on what the journey proves.
58
+
59
+ ```yaml
60
+ url: http://localhost:3000
61
+ journey:
62
+ - capture: home
63
+ expect: signed in, the item list is the home page
64
+ steps:
65
+ - open: /
66
+ - click: { role: button, name: Alice Admin }
67
+ - wait: { role: heading, name: Items }
68
+
69
+ - capture: detail-before
70
+ expect: the detail panel shows the item without a status badge
71
+ steps:
72
+ - click: { text: Seeded item }
73
+ - wait: { load: networkidle }
74
+
75
+ - capture: detail-active
76
+ expect: the status badge reads Active
77
+ steps:
78
+ - click: { role: button, name: Activate }
79
+ - wait: { text: Active }
80
+ ```
81
+ ````
82
+
83
+ ### Keys
84
+
85
+ - `url`: where the app answers once started, from the project environment above. Relative `open:` paths resolve against it.
86
+ - `start` (optional): the command that starts the app, only when it is not the project's default.
87
+ - `viewport` (optional): `1280x800` unless the change is about another size.
88
+ - `journey`: the waypoints, in order, in one browser tab. Each has a `capture` (kebab-case, unique), an `expect` (what is visible in the screenshot), and the `steps` that get there from the previous waypoint.
89
+
90
+ ### Steps
91
+
92
+ Each step is a map with exactly one action:
93
+
94
+ | Step | Does |
95
+ |---|---|
96
+ | `open: /path` | navigate (relative to `url`, or a full URL) |
97
+ | `click: <locator>` | click; add `button: right` inside the locator for a context menu |
98
+ | `dblclick: <locator>`, `hover: <locator>`, `check: <locator>`, `uncheck: <locator>` | as named |
99
+ | `fill: { <locator>, value: text }` | replace the field's text |
100
+ | `select: { <locator>, value: option }` | choose an option |
101
+ | `press: Enter` or `press: { <locator>, key: Enter }` | press a key |
102
+ | `wait: <locator>` | until the element is visible |
103
+ | `wait: { text: Saved }` | until the text is visible |
104
+ | `wait: { load: networkidle }` | until the page settles; never a failure on its own |
105
+ | `wait: { url: /items/ }` | until the address contains it |
106
+ | `wait: { ms: 500 }` | a fixed pause, only for an animation with nothing to wait on |
107
+
108
+ A locator is one of `{ role: button, name: Save }`, `{ text: Save }`, `{ label: Name }`, `{ placeholder: Search }`, `{ testid: save }`, `{ alt: Logo }`, `{ title: Close }`, or `{ css: ".row" }`, optionally with `nth: 1` or `last: true`. The first match is used.
109
+
110
+ ### Rules
111
+
112
+ 1. The first waypoint starts with `open` and signs in, using the sign-in from the project environment. The replay starts from a freshly started app with nothing in the browser.
113
+ 2. Everything the journey needs that a fresh start does not have, create through the UI in the steps (a record to open, a setting to change). Never assume data from a developer's machine.
114
+ 3. Place waypoints as a pre/post pair around the changed behavior: the state immediately before and immediately after the core change is exercised.
115
+ 4. Use the minimum number of waypoints that reproduces and proves the behavior, normally three to six.
116
+ 5. Locators name what a person sees: prefer `role` with `name`, then `label`, `text`, `testid`. Copy visible text from the source's default-language strings exactly, not from memory. Use `css` only when nothing semantic exists.
117
+ 6. Wait for the state before each capture: end every waypoint's steps with a `wait` on something the screenshot is meant to show.
118
+ 7. Only the steps above replay. Network inspection, console reads, and API calls do not; prove those in tests, not here.
119
+
120
+ ### When the change is not UI-reachable
121
+
122
+ Write the stub and stop:
123
+
124
+ ````markdown
125
+ # Verification plan: <change-id>
126
+
127
+ ```yaml
128
+ status: not-applicable
129
+ reason: Backend-only change; no frontend code references the changed contract (<names>).
130
+ ```
131
+ ````
132
+
133
+ Adapt the reason to the actual change. Do not invent a journey when Step 2 concluded `None`.
134
+
135
+ ### Rules for writing
136
+
137
+ - This skill MUST NOT launch a browser, start the app, take screenshots, or run build/test/lint. Those belong elsewhere.
138
+ - Never commit, stage, or push. The caller owns git.
139
+ - The plan file is the only artifact written. Do not modify the change's specs, tasks, or proposal.
140
+ - When the project environment above says `none recorded yet` and you found the real sign-in or start command in the source, say so in the result so a person can record it there.
141
+
142
+ ## Step 4: Result
143
+
144
+ Report one of:
145
+
146
+ - `PLAN_WRITTEN <change-id>`: a journey with waypoints was written to `verification-plan.md`.
147
+ - `STUB_WRITTEN <change-id>`: `status: not-applicable` stub written with a reason.
148
+ - `NOT WRITTEN`: hard blocker (unreadable change, missing change directory). Report the blocker and the exact next step.
149
+
150
+ A correct plan file written to the change directory is the success condition. Write a stub for non-UI changes; never skip writing the file.
@@ -1,44 +0,0 @@
1
- Azure DevOps data comes from the `az` CLI; a page fetch of dev.azure.com is denied (pc-system-reminders). If `az` is unavailable, skip publishing and report it; do not fail the pipeline unless the caller declared publishing a ship gate.
2
-
3
- Publish one status comment for every manifest. A `blocked` or `failed` manifest must include its status and reason, never a success claim.
4
-
5
- ### Step 1 — Image hosting caveat
6
-
7
- Azure DevOps discussion comments do not render an image from a repo blob URL the way GitHub does, and `az boards` cannot upload an attachment inline. Use text evidence and derive a commit-pinned repository URL from `git remote get-url origin` when possible. Otherwise include the committed asset path, branch, and SHA.
8
-
9
- ```
10
- Screenshot committed at: {asset-path} (branch {branch}, commit {sha})
11
- ```
12
-
13
- ### Step 2 — Build the comment with a stable marker (idempotent)
14
-
15
- ```
16
- <!-- pc-visual-evidence:{change-id} -->
17
- <!-- pc-visual-evidence-status:{status} -->
18
-
19
- Status: `{status}`
20
-
21
- {reason?}
22
-
23
- Manifest and assets: {commit-pinned links when available, otherwise committed paths and SHA}
24
-
25
- {prMarkdown}
26
-
27
- {image-line?}
28
- ```
29
-
30
- ### Step 3 — Upsert the discussion comment on the work item (and the PR when provided)
31
-
32
- `az boards work-item update --discussion` appends a comment and cannot edit one in place, so match on the status probe rather than the change id alone. Matching the change id alone leaves a stale `blocked` comment standing after a later run succeeds:
33
-
34
- ```bash
35
- # Skip only when this exact status is already published for this change.
36
- az boards work-item show --id {work-item-id} --query 'fields."System.History"' -o tsv 2>/dev/null | grep -q "pc-visual-evidence-status:{status}" \
37
- || az boards work-item update --id {work-item-id} --discussion "$BODY"
38
- ```
39
-
40
- When an earlier comment carries this change id with a different status, this one supersedes it: open the body with `Supersedes the earlier evidence comment for this change.` so a reader can tell which is current.
41
-
42
- When a PR number is provided, also add the same body as a PR thread comment (`az repos pr` thread APIs) if available.
43
-
44
- - If a comment call fails: report it. Fail the run ONLY when publishing was declared a ship gate; otherwise continue.
@@ -1,53 +0,0 @@
1
- GitHub data comes from the `gh` CLI; a page fetch of github.com is denied (pc-system-reminders). If `gh` is unavailable, skip publishing and report it; do not fail the pipeline unless the caller declared publishing a ship gate. Always pass `--repo {owner}/{repo}` (or `repos/{owner}/{repo}` for `gh api`) explicitly.
2
-
3
- Publish one status comment for every manifest. A `blocked` or `failed` manifest must include its status and reason, never a success claim.
4
-
5
- ### Step 1 — Build commit-pinned repository links
6
-
7
- An embedded image must be a raw URL pinned to the commit that actually contains it, and that commit must be pushed. For each asset in `evidence.json`:
8
-
9
- ```bash
10
- SHA="$(git log -n 1 --format=%H -- '{asset-path}')" # the commit that added this asset
11
- [ -z "$SHA" ] && echo "asset not committed: {asset-path}" && exit-skip
12
- gh api "repos/{owner}/{repo}/contents/{asset-path}?ref=$SHA" --silent # 404 → not pushed yet → skip embedding
13
- ```
14
-
15
- Build a blob link for every committed manifest and asset: `https://github.com/{owner}/{repo}/blob/{SHA}/{asset-path}`. Use the raw URL only for a verified embeddable image: `https://raw.githubusercontent.com/{owner}/{repo}/{SHA}/{asset-path}`. If verification fails, include the asset path and SHA as text; never post a dead link.
16
-
17
- ### Step 2 — Build the comment body with a stable marker (idempotent)
18
-
19
- Prefix the body with a hidden marker so re-runs update the same comment instead of piling on:
20
-
21
- ```
22
- <!-- pc-visual-evidence:{change-id} -->
23
- <!-- pc-visual-evidence-status:{status} -->
24
-
25
- Status: `{status}`
26
-
27
- {reason?}
28
-
29
- Manifest: {commit-pinned evidence.json link}
30
-
31
- Assets: {commit-pinned asset links}
32
-
33
- {prMarkdown}
34
- ```
35
-
36
- ### Step 3 — Upsert the comment on BOTH the issue and the PR
37
-
38
- For each target number (the originating issue, and the PR number when provided), find an existing marked comment and PATCH it, else POST a new one:
39
-
40
- ```bash
41
- # find existing
42
- ID="$(gh api "repos/{owner}/{repo}/issues/{number}/comments" --paginate --jq \
43
- '.[] | select(.body | contains("<!-- pc-visual-evidence:{change-id} -->")) | .id' | head -1)"
44
-
45
- if [ -n "$ID" ]; then
46
- gh api --method PATCH "repos/{owner}/{repo}/issues/comments/$ID" -f body="$BODY" --silent
47
- else
48
- gh api --method POST "repos/{owner}/{repo}/issues/{number}/comments" -f body="$BODY" --silent
49
- fi
50
- ```
51
-
52
- - Text-only (no verified image, or `default` mode / nothing pushed): drop the image lines, keep the summary (tasks N/N, verification result, commits).
53
- - If a comment call fails: report it. Fail the run ONLY when the caller declared publishing a ship gate; otherwise continue.
@@ -1,42 +0,0 @@
1
- Jira data comes from the `acli` CLI; a page fetch of atlassian.net is denied (pc-system-reminders). If `acli` is unavailable, skip publishing and report it; do not fail the pipeline unless the caller declared publishing a ship gate.
2
-
3
- Publish one status comment for every manifest. A `blocked` or `failed` manifest must include its status and reason, never a success claim.
4
-
5
- ### Step 1 — Image hosting caveat
6
-
7
- Jira comments cannot embed an image from a repo blob URL, and `acli` does not upload attachments inline. Use text evidence and derive a commit-pinned repository URL from `git remote get-url origin` when possible. Otherwise include the committed asset path, branch, and SHA:
8
-
9
- ```
10
- Screenshot committed at: {asset-path} (branch {branch}, commit {sha})
11
- ```
12
-
13
- ### Step 2 — Build the comment with a stable marker (idempotent)
14
-
15
- ```
16
- <!-- pc-visual-evidence:{change-id} -->
17
- <!-- pc-visual-evidence-status:{status} -->
18
-
19
- Status: `{status}`
20
-
21
- {reason?}
22
-
23
- Manifest and assets: {commit-pinned links when available, otherwise committed paths and SHA}
24
-
25
- {prMarkdown}
26
-
27
- {image-line?}
28
- ```
29
-
30
- ### Step 3 — Upsert the comment on the issue
31
-
32
- `acli` cannot edit a comment in place, so match on the status probe rather than the change id alone. Matching the change id alone leaves a stale `blocked` comment standing after a later run succeeds:
33
-
34
- ```bash
35
- # Skip only when this exact status is already published for this change.
36
- acli jira issue comment list --key {issue-key} 2>/dev/null | grep -q "pc-visual-evidence-status:{status}" \
37
- || acli jira issue comment --key {issue-key} --body "$BODY"
38
- ```
39
-
40
- When an earlier comment carries this change id with a different status, this one supersedes it: open the body with `Supersedes the earlier evidence comment for this change.` so a reader can tell which is current.
41
-
42
- - If the comment command fails: report it. Fail the run ONLY when publishing was declared a ship gate; otherwise continue.
@@ -1,20 +0,0 @@
1
- {
2
- "plugin": "@slkiser/opencode-quota@5.0.1",
3
- "prompt": {
4
- "message": "Install opencode-quota with recommended defaults?",
5
- "default": true,
6
- "timeoutMs": 20000
7
- },
8
- "defaults": {
9
- "enabledProviders": "auto",
10
- "formatStyle": "singleWindow",
11
- "percentDisplayMode": "used",
12
- "showSessionTokens": true,
13
- "enableToast": false,
14
- "resetNotifications": { "enabled": false },
15
- "tuiCommandDisplay": "inline",
16
- "tuiCompactStatus": { "enabled": false },
17
- "tuiPromptBar": { "enabled": false },
18
- "tuiSidebarPanel": { "enabled": true }
19
- }
20
- }
@@ -1,135 +0,0 @@
1
- import { confirm } from '@inquirer/prompts'
2
- import fse from 'fs-extra'
3
- import path from 'node:path'
4
- import { fileURLToPath } from 'url'
5
- import { applyEdits, modify, parse as parseJsonc } from 'jsonc-parser'
6
- import { error, header, info, loading, success, warn } from '../../utils/exec.js'
7
-
8
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
9
- const QUOTA_PRESET_PATH = path.resolve(__dirname, '../../presets/quota.json')
10
- const quotaPreset = await fse.readJson(QUOTA_PRESET_PATH)
11
- const PLUGIN = quotaPreset.plugin
12
-
13
- export function splitPackageSpec(spec) {
14
- const separator = spec.startsWith('@') ? spec.indexOf('@', 1) : spec.lastIndexOf('@')
15
- if (separator === -1) return { name: spec, version: 'latest' }
16
- return { name: spec.slice(0, separator), version: spec.slice(separator + 1) }
17
- }
18
-
19
- export async function installQuota(options = {}) {
20
- if (!options.skipHeader) header('Installing opencode-quota')
21
-
22
- let shouldInstall = true
23
- if (!options.skipPrompt && process.stdin.isTTY) {
24
- // Same abort-on-timeout pattern as tokenOptimizationStep: never race a
25
- // live inquirer prompt, it leaks raw-mode stdin and unhandled rejections.
26
- const controller = new AbortController()
27
- const timer = setTimeout(() => controller.abort(), quotaPreset.prompt.timeoutMs)
28
- try {
29
- shouldInstall = await confirm(
30
- {
31
- message: quotaPreset.prompt.message,
32
- default: quotaPreset.prompt.default,
33
- },
34
- { signal: controller.signal },
35
- )
36
- } catch (err) {
37
- if (err.name !== 'AbortPromptError') throw err
38
- shouldInstall = true
39
- } finally {
40
- clearTimeout(timer)
41
- }
42
- }
43
-
44
- if (!shouldInstall) {
45
- warn('Skipped opencode-quota installation')
46
- return { optedIn: false, installed: false }
47
- }
48
-
49
- loading('configuring opencode-quota...')
50
-
51
- try {
52
- const opencodeDir = path.join(process.cwd(), '.opencode')
53
- const opencodePath = path.join(process.cwd(), 'opencode.jsonc')
54
- const pkgPath = path.join(opencodeDir, 'package.json')
55
-
56
- // opencode-quota 5.x reads its settings from `opencode-quota/quota-toast.jsonc`
57
- // next to the OpenCode config (project root), not from `.opencode/`. The old
58
- // `.opencode/opencode-quota/quota-toast.json` is the v4 location and is migrated
59
- // below so the settings actually take effect.
60
- const quotaDir = path.join(process.cwd(), 'opencode-quota')
61
- const quotaPath = path.join(quotaDir, 'quota-toast.jsonc')
62
- const legacyQuotaPath = path.join(opencodeDir, 'opencode-quota', 'quota-toast.json')
63
-
64
- let opencodeText = JSON.stringify({ $schema: 'https://opencode.ai/config.json' }, null, 2)
65
- let opencode
66
- if (await fse.pathExists(opencodePath)) {
67
- const errors = []
68
- opencodeText = await fse.readFile(opencodePath, 'utf-8')
69
- opencode = parseJsonc(opencodeText, errors)
70
- if (errors.length > 0 || !opencode || typeof opencode !== 'object' || Array.isArray(opencode)) {
71
- throw new Error('opencode.jsonc could not be parsed')
72
- }
73
- } else {
74
- opencode = { $schema: 'https://opencode.ai/config.json' }
75
- }
76
-
77
- const pkg = await fse.pathExists(pkgPath)
78
- ? await fse.readJson(pkgPath)
79
- : {}
80
-
81
- // V2 configures server plugins under `plugins`. A V1 `plugin` array found
82
- // here is carried over so the quota entry is not lost when the config
83
- // patcher has not run first.
84
- const plugins = Array.isArray(opencode.plugins)
85
- ? [...opencode.plugins]
86
- : Array.isArray(opencode.plugin)
87
- ? [...opencode.plugin]
88
- : []
89
- if (!plugins.includes(PLUGIN)) {
90
- plugins.push(PLUGIN)
91
- opencodeText = applyEdits(opencodeText, modify(opencodeText, ['plugins'], plugins, {
92
- formattingOptions: { insertSpaces: true, tabSize: 2 },
93
- }))
94
- }
95
-
96
- if (!pkg.dependencies) pkg.dependencies = {}
97
- const { name: pkgName, version: pkgVersion } = splitPackageSpec(PLUGIN)
98
- if (pkg.dependencies[pkgName] !== pkgVersion) pkg.dependencies[pkgName] = pkgVersion
99
-
100
- await fse.ensureDir(opencodeDir)
101
- await fse.writeFile(opencodePath, opencodeText, 'utf-8')
102
- await fse.writeJson(pkgPath, pkg, { spaces: 2 })
103
-
104
- // Carry over any existing settings (new location first, then the legacy
105
- // v4 file) and layer the recommended defaults on top. The edge defaults
106
- // disable toasts and reset notifications and keep slash reports inline.
107
- let quotaConfig = {}
108
- if (await fse.pathExists(quotaPath)) {
109
- quotaConfig = await fse.readJson(quotaPath)
110
- } else if (await fse.pathExists(legacyQuotaPath)) {
111
- quotaConfig = await fse.readJson(legacyQuotaPath)
112
- }
113
-
114
- Object.assign(quotaConfig, quotaPreset.defaults)
115
-
116
- await fse.ensureDir(quotaDir)
117
- await fse.writeJson(quotaPath, quotaConfig, { spaces: 2 })
118
-
119
- if (await fse.pathExists(legacyQuotaPath)) {
120
- await fse.remove(legacyQuotaPath)
121
- const legacyDir = path.dirname(legacyQuotaPath)
122
- if ((await fse.readdir(legacyDir)).length === 0) {
123
- await fse.remove(legacyDir)
124
- }
125
- info('Moved opencode-quota settings to opencode-quota/quota-toast.jsonc')
126
- }
127
-
128
- success('opencode-quota configured (popups and notifications disabled)')
129
- info('Restart OpenCode and run /quota to verify')
130
- return { optedIn: true, installed: true }
131
- } catch (err) {
132
- error(`Failed to configure opencode-quota: ${err.message}`)
133
- return { optedIn: true, installed: false }
134
- }
135
- }
@@ -1,9 +0,0 @@
1
- ---
2
- description: Capture a screenshot (or text summary) of a completed change and post it as a comment on the originating issue or work item.
3
- ---
4
-
5
- Load the `pc-ops-evidence` skill and execute it.
6
-
7
- Input:
8
-
9
- $ARGUMENTS