@samahlstrom/forge-cli 0.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.
Files changed (100) hide show
  1. package/README.md +175 -0
  2. package/bin/forge.js +2 -0
  3. package/dist/addons/index.d.ts +25 -0
  4. package/dist/addons/index.js +139 -0
  5. package/dist/addons/index.js.map +1 -0
  6. package/dist/commands/add.d.ts +1 -0
  7. package/dist/commands/add.js +61 -0
  8. package/dist/commands/add.js.map +1 -0
  9. package/dist/commands/doctor.d.ts +1 -0
  10. package/dist/commands/doctor.js +177 -0
  11. package/dist/commands/doctor.js.map +1 -0
  12. package/dist/commands/ingest.d.ts +24 -0
  13. package/dist/commands/ingest.js +316 -0
  14. package/dist/commands/ingest.js.map +1 -0
  15. package/dist/commands/init.d.ts +8 -0
  16. package/dist/commands/init.js +557 -0
  17. package/dist/commands/init.js.map +1 -0
  18. package/dist/commands/remove.d.ts +1 -0
  19. package/dist/commands/remove.js +42 -0
  20. package/dist/commands/remove.js.map +1 -0
  21. package/dist/commands/status.d.ts +1 -0
  22. package/dist/commands/status.js +48 -0
  23. package/dist/commands/status.js.map +1 -0
  24. package/dist/commands/upgrade.d.ts +5 -0
  25. package/dist/commands/upgrade.js +190 -0
  26. package/dist/commands/upgrade.js.map +1 -0
  27. package/dist/detect/features.d.ts +10 -0
  28. package/dist/detect/features.js +33 -0
  29. package/dist/detect/features.js.map +1 -0
  30. package/dist/detect/go.d.ts +3 -0
  31. package/dist/detect/go.js +38 -0
  32. package/dist/detect/go.js.map +1 -0
  33. package/dist/detect/index.d.ts +25 -0
  34. package/dist/detect/index.js +32 -0
  35. package/dist/detect/index.js.map +1 -0
  36. package/dist/detect/node.d.ts +3 -0
  37. package/dist/detect/node.js +99 -0
  38. package/dist/detect/node.js.map +1 -0
  39. package/dist/detect/python.d.ts +3 -0
  40. package/dist/detect/python.js +86 -0
  41. package/dist/detect/python.js.map +1 -0
  42. package/dist/index.d.ts +1 -0
  43. package/dist/index.js +51 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/render/engine.d.ts +8 -0
  46. package/dist/render/engine.js +71 -0
  47. package/dist/render/engine.js.map +1 -0
  48. package/dist/render/merge.d.ts +5 -0
  49. package/dist/render/merge.js +33 -0
  50. package/dist/render/merge.js.map +1 -0
  51. package/dist/utils/fs.d.ts +8 -0
  52. package/dist/utils/fs.js +42 -0
  53. package/dist/utils/fs.js.map +1 -0
  54. package/dist/utils/git.d.ts +3 -0
  55. package/dist/utils/git.js +31 -0
  56. package/dist/utils/git.js.map +1 -0
  57. package/dist/utils/hash.d.ts +8 -0
  58. package/dist/utils/hash.js +22 -0
  59. package/dist/utils/hash.js.map +1 -0
  60. package/dist/utils/yaml.d.ts +3 -0
  61. package/dist/utils/yaml.js +12 -0
  62. package/dist/utils/yaml.js.map +1 -0
  63. package/package.json +53 -0
  64. package/templates/addons/beads-dolt-backend/files/dolt-setup.sh +267 -0
  65. package/templates/addons/beads-dolt-backend/manifest.yaml +13 -0
  66. package/templates/addons/browser-testing/files/browser-smoke.sh +196 -0
  67. package/templates/addons/browser-testing/files/visual-qa.md +103 -0
  68. package/templates/addons/browser-testing/manifest.yaml +20 -0
  69. package/templates/addons/compliance-hipaa/files/hipaa-checks.sh +184 -0
  70. package/templates/addons/compliance-hipaa/files/hipaa-context.md +91 -0
  71. package/templates/addons/compliance-hipaa/manifest.yaml +15 -0
  72. package/templates/addons/compliance-soc2/files/soc2-checks.sh +232 -0
  73. package/templates/addons/compliance-soc2/files/soc2-context.md +147 -0
  74. package/templates/addons/compliance-soc2/manifest.yaml +15 -0
  75. package/templates/core/CLAUDE.md.hbs +70 -0
  76. package/templates/core/agents/architect.md.hbs +68 -0
  77. package/templates/core/agents/backend.md.hbs +27 -0
  78. package/templates/core/agents/frontend.md.hbs +25 -0
  79. package/templates/core/agents/quality.md.hbs +40 -0
  80. package/templates/core/agents/security.md.hbs +53 -0
  81. package/templates/core/context/project.md.hbs +60 -0
  82. package/templates/core/forge.yaml.hbs +69 -0
  83. package/templates/core/hooks/post-edit.sh.hbs +8 -0
  84. package/templates/core/hooks/pre-edit.sh.hbs +41 -0
  85. package/templates/core/hooks/session-start.sh.hbs +34 -0
  86. package/templates/core/pipeline/classify.sh.hbs +159 -0
  87. package/templates/core/pipeline/decompose.md.hbs +100 -0
  88. package/templates/core/pipeline/deliver.sh.hbs +171 -0
  89. package/templates/core/pipeline/execute.md.hbs +138 -0
  90. package/templates/core/pipeline/intake.sh.hbs +152 -0
  91. package/templates/core/pipeline/orchestrator.sh.hbs +361 -0
  92. package/templates/core/pipeline/verify.sh.hbs +160 -0
  93. package/templates/core/settings.json.hbs +55 -0
  94. package/templates/core/skill-creator.md.hbs +151 -0
  95. package/templates/core/skill-deliver.md.hbs +46 -0
  96. package/templates/core/skill-ingest.md.hbs +245 -0
  97. package/templates/presets/go/stack.md.hbs +133 -0
  98. package/templates/presets/python-fastapi/stack.md.hbs +101 -0
  99. package/templates/presets/react-next-ts/stack.md.hbs +77 -0
  100. package/templates/presets/sveltekit-ts/stack.md.hbs +116 -0
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # dolt-setup.sh — Initialize Dolt database for bead storage
5
+ # Part of the beads-dolt-backend forge addon
6
+
7
+ DOLT_DIR=".forge/beads/dolt"
8
+ BEAD_STATE_SCRIPT=".forge/bead-state.sh"
9
+
10
+ # --- Helpers ---
11
+
12
+ log() { echo "[dolt-setup] $*"; }
13
+ warn() { echo "[dolt-setup] WARN: $*" >&2; }
14
+ die() { echo "[dolt-setup] ERROR: $*" >&2; exit 1; }
15
+
16
+ # --- Check prerequisites ---
17
+
18
+ log "Checking for Dolt installation..."
19
+
20
+ if ! command -v dolt > /dev/null 2>&1; then
21
+ log "Dolt not found. Attempting to install..."
22
+
23
+ if command -v brew > /dev/null 2>&1; then
24
+ log "Installing via Homebrew..."
25
+ brew install dolt
26
+ elif command -v curl > /dev/null 2>&1; then
27
+ log "Installing via install script..."
28
+ sudo bash -c 'curl -L https://github.com/dolthub/dolt/releases/latest/download/install.sh | bash'
29
+ else
30
+ die "Cannot install Dolt automatically. Install manually: https://docs.dolthub.com/introduction/installation"
31
+ fi
32
+
33
+ # Verify installation
34
+ if ! command -v dolt > /dev/null 2>&1; then
35
+ die "Dolt installation failed. Install manually: https://docs.dolthub.com/introduction/installation"
36
+ fi
37
+ fi
38
+
39
+ DOLT_VERSION=$(dolt version 2>/dev/null | head -1)
40
+ log "Found: ${DOLT_VERSION}"
41
+
42
+ # --- Initialize Dolt database ---
43
+
44
+ if [ -d "${DOLT_DIR}/.dolt" ]; then
45
+ log "Dolt database already exists at ${DOLT_DIR}"
46
+ else
47
+ log "Initializing Dolt database at ${DOLT_DIR}..."
48
+ mkdir -p "${DOLT_DIR}"
49
+
50
+ # Configure Dolt user if not already set
51
+ if ! dolt config --global --get user.name > /dev/null 2>&1; then
52
+ GIT_USER=$(git config user.name 2>/dev/null || echo "forge-user")
53
+ GIT_EMAIL=$(git config user.email 2>/dev/null || echo "forge@local")
54
+ dolt config --global --add user.name "${GIT_USER}"
55
+ dolt config --global --add user.email "${GIT_EMAIL}"
56
+ log "Configured Dolt user: ${GIT_USER} <${GIT_EMAIL}>"
57
+ fi
58
+
59
+ cd "${DOLT_DIR}"
60
+ dolt init
61
+ cd - > /dev/null
62
+
63
+ log "Dolt database initialized"
64
+ fi
65
+
66
+ # --- Create beads table schema ---
67
+
68
+ log "Creating beads table schema..."
69
+
70
+ cd "${DOLT_DIR}"
71
+
72
+ # Create the beads table if it doesn't exist
73
+ dolt sql -q "
74
+ CREATE TABLE IF NOT EXISTS beads (
75
+ id VARCHAR(64) PRIMARY KEY,
76
+ title VARCHAR(255) NOT NULL,
77
+ status VARCHAR(32) NOT NULL DEFAULT 'open',
78
+ branch VARCHAR(128),
79
+ parent_id VARCHAR(64),
80
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
81
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
82
+ metadata JSON,
83
+ INDEX idx_status (status),
84
+ INDEX idx_branch (branch),
85
+ INDEX idx_parent (parent_id)
86
+ );
87
+ " 2>/dev/null || log " beads table already exists or schema unchanged"
88
+
89
+ # Create the bead_events table for history tracking
90
+ dolt sql -q "
91
+ CREATE TABLE IF NOT EXISTS bead_events (
92
+ id VARCHAR(64) PRIMARY KEY,
93
+ bead_id VARCHAR(64) NOT NULL,
94
+ event_type VARCHAR(32) NOT NULL,
95
+ actor VARCHAR(128),
96
+ timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
97
+ data JSON,
98
+ INDEX idx_bead_id (bead_id),
99
+ INDEX idx_timestamp (timestamp),
100
+ FOREIGN KEY (bead_id) REFERENCES beads(id)
101
+ );
102
+ " 2>/dev/null || log " bead_events table already exists or schema unchanged"
103
+
104
+ # Create the bead_links table for relationships
105
+ dolt sql -q "
106
+ CREATE TABLE IF NOT EXISTS bead_links (
107
+ source_id VARCHAR(64) NOT NULL,
108
+ target_id VARCHAR(64) NOT NULL,
109
+ link_type VARCHAR(32) NOT NULL DEFAULT 'related',
110
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
111
+ PRIMARY KEY (source_id, target_id, link_type),
112
+ FOREIGN KEY (source_id) REFERENCES beads(id),
113
+ FOREIGN KEY (target_id) REFERENCES beads(id)
114
+ );
115
+ " 2>/dev/null || log " bead_links table already exists or schema unchanged"
116
+
117
+ # Commit the schema
118
+ if dolt status | grep -q "Changes\|new table"; then
119
+ dolt add .
120
+ dolt commit -m "Initialize bead storage schema"
121
+ log "Schema committed to Dolt"
122
+ else
123
+ log "Schema already up to date"
124
+ fi
125
+
126
+ cd - > /dev/null
127
+
128
+ # --- Configure bead-state.sh for Dolt backend ---
129
+
130
+ log "Configuring bead-state.sh to use Dolt backend..."
131
+
132
+ if [ -f "${BEAD_STATE_SCRIPT}" ]; then
133
+ # Check if already configured for dolt
134
+ if grep -q 'BEAD_BACKEND=dolt\|BEAD_BACKEND="dolt"' "${BEAD_STATE_SCRIPT}" 2>/dev/null; then
135
+ log " bead-state.sh already configured for Dolt"
136
+ else
137
+ # Add Dolt backend configuration at the top of the script (after shebang)
138
+ TEMP_FILE=$(mktemp)
139
+ {
140
+ head -1 "${BEAD_STATE_SCRIPT}"
141
+ echo ""
142
+ echo "# --- Dolt backend configuration (added by beads-dolt-backend addon) ---"
143
+ echo "export BEAD_BACKEND=dolt"
144
+ echo "export BEAD_DOLT_DIR=\"${DOLT_DIR}\""
145
+ echo "# --- End Dolt configuration ---"
146
+ echo ""
147
+ tail -n +2 "${BEAD_STATE_SCRIPT}"
148
+ } > "${TEMP_FILE}"
149
+ mv "${TEMP_FILE}" "${BEAD_STATE_SCRIPT}"
150
+ chmod +x "${BEAD_STATE_SCRIPT}"
151
+ log " bead-state.sh updated with Dolt backend config"
152
+ fi
153
+ else
154
+ # Create a minimal bead-state.sh with Dolt backend
155
+ mkdir -p "$(dirname "${BEAD_STATE_SCRIPT}")"
156
+ cat > "${BEAD_STATE_SCRIPT}" <<'BEAD_SCRIPT'
157
+ #!/usr/bin/env bash
158
+ set -euo pipefail
159
+
160
+ # bead-state.sh — Bead state management (Dolt backend)
161
+ # Generated by beads-dolt-backend addon
162
+
163
+ export BEAD_BACKEND=dolt
164
+ BEAD_SCRIPT
165
+
166
+ # Append the DOLT_DIR (not using heredoc to expand variable)
167
+ echo "export BEAD_DOLT_DIR=\"${DOLT_DIR}\"" >> "${BEAD_STATE_SCRIPT}"
168
+
169
+ cat >> "${BEAD_STATE_SCRIPT}" <<'BEAD_SCRIPT'
170
+
171
+ COMMAND="${1:-help}"
172
+ shift || true
173
+
174
+ dolt_query() {
175
+ cd "${BEAD_DOLT_DIR}" && dolt sql -q "$1" -r json 2>/dev/null && cd - > /dev/null
176
+ }
177
+
178
+ dolt_exec() {
179
+ cd "${BEAD_DOLT_DIR}" && dolt sql -q "$1" 2>/dev/null && cd - > /dev/null
180
+ }
181
+
182
+ dolt_commit() {
183
+ cd "${BEAD_DOLT_DIR}" && dolt add . && dolt commit -m "$1" 2>/dev/null && cd - > /dev/null
184
+ }
185
+
186
+ case "$COMMAND" in
187
+ list)
188
+ dolt_query "SELECT id, title, status, branch, created_at FROM beads ORDER BY updated_at DESC"
189
+ ;;
190
+ get)
191
+ [ -z "${1:-}" ] && echo "Usage: bead-state.sh get <id>" && exit 1
192
+ dolt_query "SELECT * FROM beads WHERE id = '$1'"
193
+ ;;
194
+ create)
195
+ ID="${1:-$(uuidgen | tr '[:upper:]' '[:lower:]')}"
196
+ TITLE="${2:-Untitled bead}"
197
+ BRANCH="${3:-$(git branch --show-current 2>/dev/null || echo 'none')}"
198
+ dolt_exec "INSERT INTO beads (id, title, status, branch) VALUES ('${ID}', '${TITLE}', 'open', '${BRANCH}')"
199
+ dolt_exec "INSERT INTO bead_events (id, bead_id, event_type, actor) VALUES ('$(uuidgen | tr '[:upper:]' '[:lower:]')', '${ID}', 'created', '$(whoami)')"
200
+ dolt_commit "Create bead: ${TITLE}"
201
+ echo "${ID}"
202
+ ;;
203
+ update)
204
+ [ -z "${1:-}" ] || [ -z "${2:-}" ] && echo "Usage: bead-state.sh update <id> <status>" && exit 1
205
+ dolt_exec "UPDATE beads SET status = '$2', updated_at = CURRENT_TIMESTAMP WHERE id = '$1'"
206
+ dolt_exec "INSERT INTO bead_events (id, bead_id, event_type, actor, data) VALUES ('$(uuidgen | tr '[:upper:]' '[:lower:]')', '$1', 'status_change', '$(whoami)', '{\"status\":\"$2\"}')"
207
+ dolt_commit "Update bead $1: status -> $2"
208
+ ;;
209
+ history)
210
+ [ -z "${1:-}" ] && echo "Usage: bead-state.sh history <id>" && exit 1
211
+ dolt_query "SELECT * FROM bead_events WHERE bead_id = '$1' ORDER BY timestamp DESC"
212
+ ;;
213
+ diff)
214
+ cd "${BEAD_DOLT_DIR}" && dolt diff "${1:-HEAD~1}" && cd - > /dev/null
215
+ ;;
216
+ sync)
217
+ cd "${BEAD_DOLT_DIR}"
218
+ if dolt remote -v 2>/dev/null | grep -q origin; then
219
+ dolt push origin main 2>/dev/null || echo "Push failed — configure remote with: cd ${BEAD_DOLT_DIR} && dolt remote add origin <url>"
220
+ else
221
+ echo "No remote configured. Add one with: cd ${BEAD_DOLT_DIR} && dolt remote add origin <url>"
222
+ fi
223
+ cd - > /dev/null
224
+ ;;
225
+ help|*)
226
+ echo "Usage: bead-state.sh <command> [args]"
227
+ echo ""
228
+ echo "Commands:"
229
+ echo " list List all beads"
230
+ echo " get <id> Get bead details"
231
+ echo " create [id] [title] [branch] Create a new bead"
232
+ echo " update <id> <status> Update bead status"
233
+ echo " history <id> Show bead event history"
234
+ echo " diff [ref] Show Dolt diff (versioned changes)"
235
+ echo " sync Push to remote Dolt (if configured)"
236
+ ;;
237
+ esac
238
+ BEAD_SCRIPT
239
+
240
+ chmod +x "${BEAD_STATE_SCRIPT}"
241
+ log " Created ${BEAD_STATE_SCRIPT} with Dolt backend"
242
+ fi
243
+
244
+ # --- Add to .gitignore ---
245
+
246
+ GITIGNORE=".gitignore"
247
+ if [ -f "$GITIGNORE" ]; then
248
+ if ! grep -q '.forge/beads/dolt' "$GITIGNORE" 2>/dev/null; then
249
+ echo "" >> "$GITIGNORE"
250
+ echo "# Dolt bead database (local state)" >> "$GITIGNORE"
251
+ echo ".forge/beads/dolt/" >> "$GITIGNORE"
252
+ log "Added .forge/beads/dolt/ to .gitignore"
253
+ fi
254
+ fi
255
+
256
+ # --- Done ---
257
+
258
+ log "Setup complete. Dolt bead backend is ready."
259
+ log ""
260
+ log "Quick start:"
261
+ log " bash ${BEAD_STATE_SCRIPT} create '' 'My first bead'"
262
+ log " bash ${BEAD_STATE_SCRIPT} list"
263
+ log " bash ${BEAD_STATE_SCRIPT} history <id>"
264
+ log ""
265
+ log "For team sync, configure a DoltHub remote:"
266
+ log " cd ${DOLT_DIR} && dolt remote add origin <dolthub-url>"
267
+ log " bash ${BEAD_STATE_SCRIPT} sync"
@@ -0,0 +1,13 @@
1
+ name: beads-dolt-backend
2
+ description: "Upgrade bead storage to Dolt DB for versioned state and team sync"
3
+ version: 1
4
+ patches:
5
+ forge_yaml:
6
+ beads.backend: dolt
7
+ addons:
8
+ - "+beads-dolt-backend"
9
+ files:
10
+ - source: dolt-setup.sh
11
+ target: .forge/addons/dolt-setup.sh
12
+ post_install:
13
+ - "bash .forge/addons/dolt-setup.sh"
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # browser-smoke.sh — Run Playwright visual smoke tests at mobile + desktop viewports
5
+ # Part of the browser-testing forge addon
6
+
7
+ SCREENSHOT_DIR=".forge/state/screenshots"
8
+ DEV_PORT="${DEV_PORT:-5173}"
9
+ DEV_URL="http://localhost:${DEV_PORT}"
10
+ TIMEOUT=30
11
+ STARTED_SERVER=false
12
+ PASS=0
13
+ FAIL=0
14
+ ERRORS=()
15
+
16
+ mkdir -p "${SCREENSHOT_DIR}/mobile" "${SCREENSHOT_DIR}/desktop"
17
+
18
+ # --- Helpers ---
19
+
20
+ log() { echo "[browser-smoke] $*"; }
21
+ warn() { echo "[browser-smoke] WARN: $*" >&2; }
22
+ fail() { echo "[browser-smoke] FAIL: $*" >&2; ERRORS+=("$*"); FAIL=$((FAIL + 1)); }
23
+ pass() { log "PASS: $*"; PASS=$((PASS + 1)); }
24
+
25
+ cleanup() {
26
+ if [ "$STARTED_SERVER" = true ]; then
27
+ log "Stopping dev server (PID ${DEV_PID:-unknown})..."
28
+ kill "$DEV_PID" 2>/dev/null || true
29
+ wait "$DEV_PID" 2>/dev/null || true
30
+ fi
31
+ }
32
+ trap cleanup EXIT
33
+
34
+ # --- Check Playwright ---
35
+
36
+ if ! npx playwright --version > /dev/null 2>&1; then
37
+ log "Playwright not found. Installing chromium..."
38
+ npx playwright install chromium || {
39
+ fail "Could not install Playwright. Aborting."
40
+ exit 1
41
+ }
42
+ fi
43
+
44
+ # --- Dev Server ---
45
+
46
+ check_server() {
47
+ curl -sf --max-time 3 "${DEV_URL}" > /dev/null 2>&1
48
+ }
49
+
50
+ if check_server; then
51
+ log "Dev server already running at ${DEV_URL}"
52
+ else
53
+ log "Starting dev server..."
54
+ npm run dev > /tmp/forge-dev-server.log 2>&1 &
55
+ DEV_PID=$!
56
+ STARTED_SERVER=true
57
+
58
+ # Wait for server to be ready
59
+ for i in $(seq 1 "$TIMEOUT"); do
60
+ if check_server; then
61
+ log "Dev server ready after ${i}s"
62
+ break
63
+ fi
64
+ if ! kill -0 "$DEV_PID" 2>/dev/null; then
65
+ fail "Dev server process exited unexpectedly. Check /tmp/forge-dev-server.log"
66
+ echo '{"pass":0,"fail":1,"errors":["Dev server failed to start"]}' > "${SCREENSHOT_DIR}/results.json"
67
+ exit 1
68
+ fi
69
+ sleep 1
70
+ done
71
+
72
+ if ! check_server; then
73
+ fail "Dev server did not respond within ${TIMEOUT}s"
74
+ echo '{"pass":0,"fail":1,"errors":["Dev server timeout"]}' > "${SCREENSHOT_DIR}/results.json"
75
+ exit 1
76
+ fi
77
+ fi
78
+
79
+ # --- Determine pages to test ---
80
+
81
+ PAGES=("/")
82
+
83
+ # Add pages from changed route files if in a git repo
84
+ if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
85
+ while IFS= read -r file; do
86
+ # Extract route path from SvelteKit-style route files
87
+ if [[ "$file" =~ src/routes/(.+)/\+page ]]; then
88
+ route="/${BASH_REMATCH[1]}"
89
+ # Skip parameterized routes (contain [...] or [[...]])
90
+ if [[ ! "$route" =~ \[ ]]; then
91
+ PAGES+=("$route")
92
+ fi
93
+ fi
94
+ done < <(git diff --name-only HEAD~1 2>/dev/null || true)
95
+ fi
96
+
97
+ # Deduplicate
98
+ PAGES=($(printf '%s\n' "${PAGES[@]}" | sort -u))
99
+
100
+ log "Testing ${#PAGES[@]} page(s): ${PAGES[*]}"
101
+
102
+ # --- Run tests ---
103
+
104
+ run_viewport_test() {
105
+ local page="$1"
106
+ local label="$2"
107
+ local width="$3"
108
+ local height="$4"
109
+
110
+ local safe_name
111
+ safe_name=$(echo "$page" | sed 's|/|_|g; s|^_||')
112
+ [ -z "$safe_name" ] && safe_name="home"
113
+
114
+ local screenshot_path="${SCREENSHOT_DIR}/${label}/${safe_name}.png"
115
+
116
+ log "Testing ${page} at ${label} (${width}x${height})..."
117
+
118
+ local script="
119
+ const { chromium } = require('playwright');
120
+ (async () => {
121
+ const browser = await chromium.launch();
122
+ const context = await browser.newContext({
123
+ viewport: { width: ${width}, height: ${height} }
124
+ });
125
+ const page = await context.newPage();
126
+ try {
127
+ const response = await page.goto('${DEV_URL}${page}', {
128
+ waitUntil: 'networkidle',
129
+ timeout: ${TIMEOUT}000
130
+ });
131
+ const status = response ? response.status() : 0;
132
+ await page.screenshot({ path: '${screenshot_path}', fullPage: true });
133
+ if (status >= 400) {
134
+ console.error('HTTP ' + status);
135
+ process.exit(2);
136
+ }
137
+ // Check for horizontal overflow (mobile)
138
+ if (${width} <= 768) {
139
+ const hasOverflow = await page.evaluate(() => {
140
+ return document.documentElement.scrollWidth > document.documentElement.clientWidth;
141
+ });
142
+ if (hasOverflow) {
143
+ console.error('OVERFLOW');
144
+ process.exit(3);
145
+ }
146
+ }
147
+ process.exit(0);
148
+ } catch (err) {
149
+ console.error(err.message);
150
+ try { await page.screenshot({ path: '${screenshot_path}', fullPage: true }); } catch (_) {}
151
+ process.exit(1);
152
+ } finally {
153
+ await browser.close();
154
+ }
155
+ })();
156
+ "
157
+
158
+ local exit_code=0
159
+ node -e "$script" 2>/tmp/forge-browser-err.txt || exit_code=$?
160
+
161
+ case $exit_code in
162
+ 0) pass "${page} @ ${label}" ;;
163
+ 2)
164
+ local status
165
+ status=$(cat /tmp/forge-browser-err.txt)
166
+ fail "${page} @ ${label}: HTTP error (${status})"
167
+ ;;
168
+ 3) fail "${page} @ ${label}: horizontal overflow detected on mobile" ;;
169
+ *) fail "${page} @ ${label}: $(cat /tmp/forge-browser-err.txt)" ;;
170
+ esac
171
+ }
172
+
173
+ for page in "${PAGES[@]}"; do
174
+ run_viewport_test "$page" "mobile" 375 812
175
+ run_viewport_test "$page" "desktop" 1440 900
176
+ done
177
+
178
+ # --- Report ---
179
+
180
+ TOTAL=$((PASS + FAIL))
181
+
182
+ cat > "${SCREENSHOT_DIR}/results.json" <<EOF
183
+ {
184
+ "total": ${TOTAL},
185
+ "pass": ${PASS},
186
+ "fail": ${FAIL},
187
+ "pages": ${#PAGES[@]},
188
+ "errors": [$(printf '"%s",' "${ERRORS[@]}" 2>/dev/null | sed 's/,$//')]
189
+ }
190
+ EOF
191
+
192
+ log "Results: ${PASS}/${TOTAL} passed, ${FAIL} failed"
193
+ log "Screenshots saved to ${SCREENSHOT_DIR}/"
194
+ log "Results written to ${SCREENSHOT_DIR}/results.json"
195
+
196
+ [ "$FAIL" -eq 0 ] && exit 0 || exit 1
@@ -0,0 +1,103 @@
1
+ # Visual QA Agent — Browser Testing Addon
2
+
3
+ You are a visual QA agent. Your job is to verify UI changes at two viewport sizes using Playwright.
4
+
5
+ ## Viewports
6
+
7
+ | Label | Width | Height |
8
+ |---------|--------|--------|
9
+ | mobile | 375px | 812px |
10
+ | desktop | 1440px | 900px |
11
+
12
+ ## Workflow
13
+
14
+ ### 1. Start the Dev Server
15
+
16
+ Check if the dev server is already running:
17
+
18
+ ```bash
19
+ curl -sf http://localhost:5173 > /dev/null 2>&1 || npm run dev &
20
+ sleep 3
21
+ ```
22
+
23
+ If the project uses a different port, check `forge.yaml` or `package.json` for the correct `dev` command and port.
24
+
25
+ ### 2. Identify Pages to Test
26
+
27
+ Read the current changeset (`git diff --name-only HEAD~1`) and determine which routes/pages are affected. Map changed files to their corresponding URLs. At minimum, always test:
28
+
29
+ - `/` (home page)
30
+ - Any route whose component files were modified
31
+
32
+ ### 3. Take Before Screenshots (Baseline)
33
+
34
+ If a baseline branch is available (e.g., `main`), check it out temporarily and capture screenshots:
35
+
36
+ ```
37
+ .forge/state/screenshots/before/<page>-mobile.png
38
+ .forge/state/screenshots/before/<page>-desktop.png
39
+ ```
40
+
41
+ Switch back to the working branch after capturing baselines.
42
+
43
+ ### 4. Take After Screenshots
44
+
45
+ Capture the current state at both viewports:
46
+
47
+ ```
48
+ .forge/state/screenshots/after/<page>-mobile.png
49
+ .forge/state/screenshots/after/<page>-desktop.png
50
+ ```
51
+
52
+ ### 5. Visual Regression Check
53
+
54
+ Compare before/after screenshots. Flag issues:
55
+
56
+ - **Layout shifts**: Elements that moved unexpectedly between viewports
57
+ - **Overflow**: Content that bleeds outside its container, especially on mobile
58
+ - **Truncation**: Text cut off without ellipsis or scroll
59
+ - **Z-index conflicts**: Overlapping elements that shouldn't overlap
60
+ - **Missing responsive breakpoints**: Desktop layout appearing on mobile
61
+ - **Touch target size**: Interactive elements smaller than 44x44px on mobile
62
+
63
+ ### 6. Responsive Layout Verification
64
+
65
+ For each page, verify at both viewports:
66
+
67
+ - Navigation is accessible (hamburger menu on mobile, full nav on desktop)
68
+ - Images scale proportionally without distortion
69
+ - Forms are usable (inputs are full-width on mobile, appropriately sized on desktop)
70
+ - Modals/dialogs fit within the viewport
71
+ - Scroll behavior is correct (no horizontal scroll on mobile)
72
+ - Font sizes are readable (minimum 14px on mobile)
73
+
74
+ ### 7. Report Results
75
+
76
+ Write a summary to `.forge/state/screenshots/report.md` with:
77
+
78
+ - Pass/fail per page per viewport
79
+ - Screenshot paths for any failures
80
+ - Specific observations about visual issues
81
+ - Recommendations for fixes
82
+
83
+ ## Screenshot Storage
84
+
85
+ All screenshots go in `.forge/state/screenshots/`:
86
+
87
+ ```
88
+ .forge/state/screenshots/
89
+ before/
90
+ home-mobile.png
91
+ home-desktop.png
92
+ after/
93
+ home-mobile.png
94
+ home-desktop.png
95
+ report.md
96
+ ```
97
+
98
+ ## Error Handling
99
+
100
+ - If the dev server fails to start, report the error and skip browser tests
101
+ - If a page returns 404/500, capture the error page screenshot and flag it
102
+ - If Playwright is not installed, run `npx playwright install chromium` first
103
+ - Timeout after 30 seconds per page navigation
@@ -0,0 +1,20 @@
1
+ name: browser-testing
2
+ description: "Playwright visual QA at mobile + desktop viewports"
3
+ version: 1
4
+ requires:
5
+ commands:
6
+ dev: true
7
+ patches:
8
+ forge_yaml:
9
+ verification.browser.enabled: true
10
+ addons:
11
+ - "+browser-testing"
12
+ agents:
13
+ - "+qa-visual"
14
+ files:
15
+ - source: visual-qa.md
16
+ target: .forge/addons/visual-qa.md
17
+ - source: browser-smoke.sh
18
+ target: .forge/addons/browser-smoke.sh
19
+ post_install:
20
+ - "npx playwright install chromium"