opencodekit 0.21.4 → 0.21.5

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 (31) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/template/.opencode/AGENTS.md +55 -36
  3. package/dist/template/.opencode/agent/build.md +13 -3
  4. package/dist/template/.opencode/agent/explore.md +14 -0
  5. package/dist/template/.opencode/agent/general.md +13 -2
  6. package/dist/template/.opencode/agent/painter.md +9 -0
  7. package/dist/template/.opencode/agent/plan.md +26 -4
  8. package/dist/template/.opencode/agent/review.md +10 -0
  9. package/dist/template/.opencode/agent/scout.md +16 -1
  10. package/dist/template/.opencode/agent/vision.md +23 -0
  11. package/dist/template/.opencode/command/design.md +27 -8
  12. package/dist/template/.opencode/command/plan.md +22 -0
  13. package/dist/template/.opencode/command/ship.md +31 -5
  14. package/dist/template/.opencode/command/status.md +14 -5
  15. package/dist/template/.opencode/command/ui-review.md +38 -18
  16. package/dist/template/.opencode/command/ui-slop-check.md +30 -7
  17. package/dist/template/.opencode/command/verify.md +3 -0
  18. package/dist/template/.opencode/memory.db +0 -0
  19. package/dist/template/.opencode/memory.db-shm +0 -0
  20. package/dist/template/.opencode/memory.db-wal +0 -0
  21. package/dist/template/.opencode/plugin/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +162 -168
  22. package/dist/template/.opencode/plugin/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +16 -16
  23. package/dist/template/.opencode/plugin/sdk/copilot/chat/openai-compatible-chat-language-model.ts +807 -805
  24. package/dist/template/.opencode/plugin/sdk/copilot/chat/openai-compatible-prepare-tools.ts +77 -77
  25. package/dist/template/.opencode/plugin/sdk/copilot/copilot-provider.ts +75 -80
  26. package/dist/template/.opencode/skill/playwright/SKILL.md +51 -2
  27. package/dist/template/.opencode/skill/portless/SKILL.md +109 -0
  28. package/dist/template/.opencode/skill/terse-output-mode/SKILL.md +95 -0
  29. package/dist/template/.opencode/skill/think-in-code/SKILL.md +136 -0
  30. package/dist/template/.opencode/skill/ux-quality-gates/SKILL.md +137 -0
  31. package/package.json +1 -1
@@ -40,11 +40,12 @@ skill({ name: "beads" });
40
40
 
41
41
  ## Available Tools
42
42
 
43
- | Tool | Use When |
44
- | --------------- | --------------------- |
45
- | `br` | Task status and stats |
46
- | `git` | Git state and history |
47
- | `find_sessions` | Recent sessions |
43
+ | Tool | Use When |
44
+ | --------------- | ----------------------------------------------- |
45
+ | `br` | Task status and stats |
46
+ | `git` | Git state and history |
47
+ | `find_sessions` | Recent sessions |
48
+ | `portless` | Optional read-only local URL state if installed |
48
49
 
49
50
  ## Phase 1: Gather State (Parallel)
50
51
 
@@ -62,6 +63,14 @@ git branch --show-current
62
63
  git log --oneline -5
63
64
  ```
64
65
 
66
+ Optionally include Portless local URL state only if the binary is already installed:
67
+
68
+ ```bash
69
+ if command -v portless >/dev/null 2>&1; then portless list; fi
70
+ ```
71
+
72
+ Do not install Portless, start/stop proxies, trust CAs, sync hosts, prune/clean state, or expose LAN services from `/status`.
73
+
65
74
  ```typescript
66
75
  find_sessions({ query: "<project-name or recent-bead-keywords>", limit: 5 });
67
76
  ```
@@ -13,6 +13,7 @@ model: proxypal/gemini-3-pro-preview
13
13
  skill({ name: "visual-analysis" }); // Analysis framework
14
14
  skill({ name: "accessibility-audit" }); // WCAG checklists
15
15
  skill({ name: "frontend-design" }); // Anti-patterns, design quality
16
+ skill({ name: "ux-quality-gates" }); // IA, heuristics, forms, recovery, state coverage
16
17
  ```
17
18
 
18
19
  ## Input
@@ -33,31 +34,50 @@ Use the `visual-analysis` skill to perform deep analysis:
33
34
  - Visual properties (colors, typography, spacing, layout)
34
35
  - Design patterns and potential issues
35
36
 
36
- ### 2. Score Categories
37
+ ### 2. Usability Heuristic Pass
37
38
 
38
- Rate each 1-10 with brief justification:
39
+ Apply `ux-quality-gates` before visual scoring. Check:
40
+
41
+ - System status is visible for async or multi-step work
42
+ - Labels and navigation use user vocabulary, not implementation terms
43
+ - Users can cancel, undo, go back, retry, or recover from failure
44
+ - Controls, names, and layouts are consistent across the surface
45
+ - Dangerous or invalid actions are prevented before they happen
46
+ - Options and relationships are visible without relying on memory
47
+ - High-volume workflows expose efficient paths where appropriate
48
+ - Empty/loading/error/success states are present and useful
49
+
50
+ Any failure that blocks task completion or recovery is **Critical**, even if the screen looks polished.
39
51
 
40
- | Category | What to Evaluate |
41
- | ---------------------- | -------------------------------------------------------------- |
42
- | **Typography** | Hierarchy, readability, weight contrast, intentional choices |
43
- | **Color** | Palette cohesion, contrast, semantic usage, no AI slop |
44
- | **Layout & Spacing** | Visual hierarchy, consistency, alignment, white space |
45
- | **Interactive States** | Hover, focus, active, disabled, loading coverage |
46
- | **Accessibility** | WCAG AA compliance (use `accessibility-audit` skill checklist) |
47
- | **Visual Polish** | Consistency, attention to detail, motion, shadows, icons |
52
+ ### 3. Score Categories
53
+
54
+ Rate each 1-10 with brief justification:
48
55
 
49
- ### 3. Conditional Reviews
56
+ | Category | What to Evaluate |
57
+ | --------------------------------- | -------------------------------------------------------------- |
58
+ | **Information Architecture** | User vocabulary, scope clarity, relationships, navigation |
59
+ | **Task Flow & Recovery** | Primary action, cancellation, undo/retry, error recovery |
60
+ | **Forms & Data Interaction** | Labels, helper text, validation, selection, bulk actions |
61
+ | **Typography** | Hierarchy, readability, weight contrast, intentional choices |
62
+ | **Color** | Palette cohesion, contrast, semantic usage, no AI slop |
63
+ | **Layout & Spacing** | Visual hierarchy, consistency, alignment, white space |
64
+ | **Interactive States** | Hover, focus, active, disabled, loading/error/success coverage |
65
+ | **Accessibility & Semantic HTML** | WCAG AA compliance, native semantics, keyboard/focus behavior |
66
+ | **Component Consistency** | Shared token DNA: radius, height, border, shadow, states |
67
+ | **Visual Polish** | Consistency, attention to detail, motion, shadows, icons |
68
+
69
+ ### 4. Conditional Reviews
50
70
 
51
71
  **If `--responsive`**: Check at 375px, 768px, 1280px, 1536px+. Flag touch targets, horizontal scroll, text sizing.
52
72
 
53
73
  **If `--dark-mode`**: Check contrast on dark backgrounds, adapted colors (not just inverted), shadow adjustments, focus visibility.
54
74
 
55
- ### 4. Report Findings
75
+ ### 5. Report Findings
56
76
 
57
77
  Group by severity:
58
78
 
59
- - **Critical (Must Fix)**: Accessibility failures, broken interactions
60
- - **Warning (Should Fix)**: AI slop patterns, inconsistent spacing, missing states
79
+ - **Critical (Must Fix)**: Accessibility failures, broken interactions, dead-end errors, unsafe destructive actions
80
+ - **Warning (Should Fix)**: AI slop patterns, inconsistent spacing, missing states, confusing IA/naming
61
81
  - **Info (Nice to Have)**: Polish opportunities
62
82
 
63
83
  For each finding: location, impact, and recommended fix.
@@ -85,7 +105,7 @@ observation({
85
105
 
86
106
  ## Related Commands
87
107
 
88
- | Need | Command |
89
- | -------------------- | --------- |
90
- | Design from scratch | `/design` |
91
- | Ship implementation | `/ship` |
108
+ | Need | Command |
109
+ | ------------------- | --------- |
110
+ | Design from scratch | `/design` |
111
+ | Ship implementation | `/ship` |
@@ -15,16 +15,17 @@ Run a focused anti-slop audit against changed UI files using the frontend-design
15
15
  skill({ name: "frontend-design" }); // Anti-pattern taxonomy + design references
16
16
  skill({ name: "visual-analysis" }); // Structured visual/code analysis workflow
17
17
  skill({ name: "accessibility-audit" }); // Keyboard/focus/contrast checks
18
+ skill({ name: "ux-quality-gates" }); // UX correctness gates beyond visual slop
18
19
  ```
19
20
 
20
21
  ## Parse Arguments
21
22
 
22
- | Argument | Default | Description |
23
- | --------------- | ------- | ------------------------------------------------------------- |
24
- | `[path\|auto]` | `auto` | Specific file/dir to audit, or auto-detect changed UI files |
25
- | `--staged` | false | Audit staged changes only (`git diff --cached`) |
26
- | `--since=<ref>` | `HEAD` | Compare against ref (`main`, `HEAD~1`, commit SHA) |
27
- | `--full-report` | false | Include all categories even when no issues found |
23
+ | Argument | Default | Description |
24
+ | --------------- | ------- | ----------------------------------------------------------- |
25
+ | `[path\|auto]` | `auto` | Specific file/dir to audit, or auto-detect changed UI files |
26
+ | `--staged` | false | Audit staged changes only (`git diff --cached`) |
27
+ | `--since=<ref>` | `HEAD` | Compare against ref (`main`, `HEAD~1`, commit SHA) |
28
+ | `--full-report` | false | Include all categories even when no issues found |
28
29
 
29
30
  ## Phase 1: Resolve Target Files
30
31
 
@@ -96,12 +97,34 @@ Evaluate each target file (or rendered screenshot if provided) against these che
96
97
  - Error copy includes what happened + why + how to fix
97
98
  - Empty states include guidance + next action
98
99
  - Terminology is consistent (avoid mixed synonyms for same action)
100
+ - User-facing labels avoid implementation terms, database names, and internal acronyms
99
101
 
100
- ### F) Accessibility Safety Nets
102
+ ### F) UX Quality Gates
103
+
104
+ - One dominant filled primary action per view/section
105
+ - Destructive actions require explicit confirm or undo, with specific entity/count in copy
106
+ - No placeholder-as-label form fields
107
+ - Form errors are associated with controls (`aria-describedby`, `aria-invalid`, `role="alert"`)
108
+ - Submit/loading states prevent double-submit without layout shift
109
+ - Empty, loading, error, and success states exist where async/data flows exist
110
+ - Error toasts/banners persist long enough and include retry/undo/support where applicable
111
+ - Data-heavy UI distinguishes empty state from filtered no-results state
112
+ - Bulk actions show selected count and confirm destructive scope
113
+
114
+ ### G) Accessibility Safety Nets
101
115
 
102
116
  - Keyboard-visible focus treatment (`:focus-visible`)
103
117
  - Contrast baseline expectations (WCAG AA)
104
118
  - Touch targets reasonable (44x44 context where applicable)
119
+ - Native semantic elements are used before ARIA patches (`button`, `a`, `form`, landmarks)
120
+ - Heading structure has one logical `h1` per page/screen context
121
+
122
+ ### H) Component Family Consistency
123
+
124
+ - Buttons and inputs in the same form share height, radius, border, and focus treatment
125
+ - Focus, error, disabled, and loading states use the same token logic across components
126
+ - No one-off radius/shadow/border width unless documented as a system-level exception
127
+ - Semantic color roles are consistent: success, warning, destructive, info, primary
105
128
 
106
129
  ## Phase 3: Severity and Scoring
107
130
 
@@ -13,6 +13,7 @@ Check implementation against PRD before shipping.
13
13
  ```typescript
14
14
  skill({ name: "beads" });
15
15
  skill({ name: "verification-before-completion" });
16
+ // If local web/browser verification needs stable URLs: skill({ name: "portless" });
16
17
  ```
17
18
 
18
19
  ## Parse Arguments
@@ -105,6 +106,8 @@ Follow the [Verification Protocol](../skill/verification-before-completion/refer
105
106
  1. **Parallel**: typecheck + lint (simultaneously)
106
107
  2. **Sequential** (after parallel passes): test, then build (ship only)
107
108
 
109
+ For browser/manual local-web requirements, load the [portless](../skill/portless/SKILL.md) skill when stable named URLs would improve verification. Use only read-only Portless commands unless the user explicitly approves local networking changes; a reachable Portless URL supplements, but never replaces, typecheck/lint/test/build evidence.
110
+
108
111
  Report results with mode column:
109
112
 
110
113
  ```text
Binary file
@@ -1,178 +1,172 @@
1
1
  import {
2
- type LanguageModelV2Prompt,
3
- type SharedV2ProviderMetadata,
4
- UnsupportedFunctionalityError,
2
+ type LanguageModelV3Prompt,
3
+ type SharedV3ProviderOptions,
4
+ UnsupportedFunctionalityError,
5
5
  } from "@ai-sdk/provider";
6
6
  import { convertToBase64 } from "@ai-sdk/provider-utils";
7
7
  import type { OpenAICompatibleChatPrompt } from "./openai-compatible-api-types.js";
8
8
 
9
- function getOpenAIMetadata(message: {
10
- providerOptions?: SharedV2ProviderMetadata;
11
- }) {
12
- return message?.providerOptions?.copilot ?? {};
9
+ function getOpenAIMetadata(message: { providerOptions?: SharedV3ProviderOptions }) {
10
+ return message?.providerOptions?.copilot ?? {};
13
11
  }
14
12
 
15
13
  export function convertToOpenAICompatibleChatMessages(
16
- prompt: LanguageModelV2Prompt,
14
+ prompt: LanguageModelV3Prompt,
17
15
  ): OpenAICompatibleChatPrompt {
18
- const messages: OpenAICompatibleChatPrompt = [];
19
- for (const { role, content, ...message } of prompt) {
20
- const metadata = getOpenAIMetadata({ ...message });
21
- switch (role) {
22
- case "system": {
23
- // v1.1.49 fix: Copilot API expects system message content as plain string,
24
- // not array format. Array format causes AGENTS.md to be silently ignored.
25
- messages.push({
26
- role: "system",
27
- content: content,
28
- ...metadata,
29
- });
30
- break;
31
- }
32
-
33
- case "user": {
34
- if (content.length === 1 && content[0].type === "text") {
35
- messages.push({
36
- role: "user",
37
- content: content[0].text,
38
- ...getOpenAIMetadata(content[0]),
39
- });
40
- break;
41
- }
42
-
43
- messages.push({
44
- role: "user",
45
- content: content.map((part) => {
46
- const partMetadata = getOpenAIMetadata(part);
47
- switch (part.type) {
48
- case "text": {
49
- return {
50
- type: "text" as const,
51
- text: part.text,
52
- ...partMetadata,
53
- };
54
- }
55
- case "file": {
56
- if (part.mediaType.startsWith("image/")) {
57
- const mediaType =
58
- part.mediaType === "image/*"
59
- ? "image/jpeg"
60
- : part.mediaType;
61
-
62
- return {
63
- type: "image_url" as const,
64
- image_url: {
65
- url:
66
- part.data instanceof URL
67
- ? part.data.toString()
68
- : `data:${mediaType};base64,${convertToBase64(part.data)}`,
69
- },
70
- ...partMetadata,
71
- };
72
- } else {
73
- throw new UnsupportedFunctionalityError({
74
- functionality: `file part media type ${part.mediaType}`,
75
- });
76
- }
77
- }
78
- }
79
- }),
80
- ...metadata,
81
- });
82
-
83
- break;
84
- }
85
-
86
- case "assistant": {
87
- let text = "";
88
- let reasoningText: string | undefined;
89
- let reasoningOpaque: string | undefined;
90
- const toolCalls: Array<{
91
- id: string;
92
- type: "function";
93
- function: { name: string; arguments: string };
94
- }> = [];
95
-
96
- for (const part of content) {
97
- const partMetadata = getOpenAIMetadata(part);
98
- // Check for reasoningOpaque on any part (may be attached to text/tool-call)
99
- const partOpaque = (
100
- part.providerOptions as { copilot?: { reasoningOpaque?: string } }
101
- )?.copilot?.reasoningOpaque;
102
- if (partOpaque && !reasoningOpaque) {
103
- reasoningOpaque = partOpaque;
104
- }
105
-
106
- switch (part.type) {
107
- case "text": {
108
- text += part.text;
109
- break;
110
- }
111
- case "reasoning": {
112
- reasoningText = part.text;
113
- break;
114
- }
115
- case "tool-call": {
116
- toolCalls.push({
117
- id: part.toolCallId,
118
- type: "function",
119
- function: {
120
- name: part.toolName,
121
- arguments: JSON.stringify(part.input),
122
- },
123
- ...partMetadata,
124
- });
125
- break;
126
- }
127
- }
128
- }
129
-
130
- messages.push({
131
- role: "assistant",
132
- content: text || null,
133
- tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
134
- reasoning_text: reasoningText,
135
- reasoning_opaque: reasoningOpaque,
136
- ...metadata,
137
- });
138
-
139
- break;
140
- }
141
-
142
- case "tool": {
143
- for (const toolResponse of content) {
144
- const output = toolResponse.output;
145
-
146
- let contentValue: string;
147
- switch (output.type) {
148
- case "text":
149
- case "error-text":
150
- contentValue = output.value;
151
- break;
152
- case "content":
153
- case "json":
154
- case "error-json":
155
- contentValue = JSON.stringify(output.value);
156
- break;
157
- }
158
-
159
- const toolResponseMetadata = getOpenAIMetadata(toolResponse);
160
- messages.push({
161
- role: "tool",
162
- tool_call_id: toolResponse.toolCallId,
163
- content: contentValue,
164
- ...toolResponseMetadata,
165
- });
166
- }
167
- break;
168
- }
169
-
170
- default: {
171
- const _exhaustiveCheck: never = role;
172
- throw new Error(`Unsupported role: ${_exhaustiveCheck}`);
173
- }
174
- }
175
- }
176
-
177
- return messages;
16
+ const messages: OpenAICompatibleChatPrompt = [];
17
+ for (const { role, content, ...message } of prompt) {
18
+ const metadata = getOpenAIMetadata({ ...message });
19
+ switch (role) {
20
+ case "system": {
21
+ messages.push({
22
+ role: "system",
23
+ content: content,
24
+ ...metadata,
25
+ });
26
+ break;
27
+ }
28
+
29
+ case "user": {
30
+ if (content.length === 1 && content[0].type === "text") {
31
+ messages.push({
32
+ role: "user",
33
+ content: content[0].text,
34
+ ...getOpenAIMetadata(content[0]),
35
+ });
36
+ break;
37
+ }
38
+
39
+ messages.push({
40
+ role: "user",
41
+ content: content.map((part) => {
42
+ const partMetadata = getOpenAIMetadata(part);
43
+ switch (part.type) {
44
+ case "text": {
45
+ return { type: "text" as const, text: part.text, ...partMetadata };
46
+ }
47
+ case "file": {
48
+ if (part.mediaType.startsWith("image/")) {
49
+ const mediaType = part.mediaType === "image/*" ? "image/jpeg" : part.mediaType;
50
+
51
+ return {
52
+ type: "image_url" as const,
53
+ image_url: {
54
+ url:
55
+ part.data instanceof URL
56
+ ? part.data.toString()
57
+ : `data:${mediaType};base64,${convertToBase64(part.data)}`,
58
+ },
59
+ ...partMetadata,
60
+ };
61
+ } else {
62
+ throw new UnsupportedFunctionalityError({
63
+ functionality: `file part media type ${part.mediaType}`,
64
+ });
65
+ }
66
+ }
67
+ }
68
+ }),
69
+ ...metadata,
70
+ });
71
+
72
+ break;
73
+ }
74
+
75
+ case "assistant": {
76
+ let text = "";
77
+ let reasoningText: string | undefined;
78
+ let reasoningOpaque: string | undefined;
79
+ const toolCalls: Array<{
80
+ id: string;
81
+ type: "function";
82
+ function: { name: string; arguments: string };
83
+ }> = [];
84
+
85
+ for (const part of content) {
86
+ const partMetadata = getOpenAIMetadata(part);
87
+ // Check for reasoningOpaque on any part (may be attached to text/tool-call)
88
+ const partOpaque = (part.providerOptions as { copilot?: { reasoningOpaque?: string } })
89
+ ?.copilot?.reasoningOpaque;
90
+ if (partOpaque && !reasoningOpaque) {
91
+ reasoningOpaque = partOpaque;
92
+ }
93
+
94
+ switch (part.type) {
95
+ case "text": {
96
+ text += part.text;
97
+ break;
98
+ }
99
+ case "reasoning": {
100
+ if (part.text) reasoningText = part.text;
101
+ break;
102
+ }
103
+ case "tool-call": {
104
+ toolCalls.push({
105
+ id: part.toolCallId,
106
+ type: "function",
107
+ function: {
108
+ name: part.toolName,
109
+ arguments: JSON.stringify(part.input),
110
+ },
111
+ ...partMetadata,
112
+ });
113
+ break;
114
+ }
115
+ }
116
+ }
117
+
118
+ messages.push({
119
+ role: "assistant",
120
+ content: text || null,
121
+ tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
122
+ reasoning_text: reasoningOpaque ? reasoningText : undefined,
123
+ reasoning_opaque: reasoningOpaque,
124
+ ...metadata,
125
+ });
126
+
127
+ break;
128
+ }
129
+
130
+ case "tool": {
131
+ for (const toolResponse of content) {
132
+ if (toolResponse.type === "tool-approval-response") {
133
+ continue;
134
+ }
135
+ const output = toolResponse.output;
136
+
137
+ let contentValue: string;
138
+ switch (output.type) {
139
+ case "text":
140
+ case "error-text":
141
+ contentValue = output.value;
142
+ break;
143
+ case "execution-denied":
144
+ contentValue = output.reason ?? "Tool execution denied.";
145
+ break;
146
+ case "content":
147
+ case "json":
148
+ case "error-json":
149
+ contentValue = JSON.stringify(output.value);
150
+ break;
151
+ }
152
+
153
+ const toolResponseMetadata = getOpenAIMetadata(toolResponse);
154
+ messages.push({
155
+ role: "tool",
156
+ tool_call_id: toolResponse.toolCallId,
157
+ content: contentValue,
158
+ ...toolResponseMetadata,
159
+ });
160
+ }
161
+ break;
162
+ }
163
+
164
+ default: {
165
+ const _exhaustiveCheck: never = role;
166
+ throw new Error(`Unsupported role: ${_exhaustiveCheck}`);
167
+ }
168
+ }
169
+ }
170
+
171
+ return messages;
178
172
  }
@@ -1,19 +1,19 @@
1
- import type { LanguageModelV2FinishReason } from "@ai-sdk/provider";
1
+ import type { LanguageModelV3FinishReason } from "@ai-sdk/provider";
2
2
 
3
3
  export function mapOpenAICompatibleFinishReason(
4
- finishReason: string | null | undefined,
5
- ): LanguageModelV2FinishReason {
6
- switch (finishReason) {
7
- case "stop":
8
- return "stop";
9
- case "length":
10
- return "length";
11
- case "content_filter":
12
- return "content-filter";
13
- case "function_call":
14
- case "tool_calls":
15
- return "tool-calls";
16
- default:
17
- return "unknown";
18
- }
4
+ finishReason: string | null | undefined,
5
+ ): LanguageModelV3FinishReason["unified"] {
6
+ switch (finishReason) {
7
+ case "stop":
8
+ return "stop";
9
+ case "length":
10
+ return "length";
11
+ case "content_filter":
12
+ return "content-filter";
13
+ case "function_call":
14
+ case "tool_calls":
15
+ return "tool-calls";
16
+ default:
17
+ return "other";
18
+ }
19
19
  }