claude-mcp-workflow 0.1.8 → 0.1.9
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.
- package/.claude-plugin/plugin.json +4 -2
- package/README.md +37 -0
- package/build/engine.d.ts +13 -1
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +41 -2
- package/build/engine.js.map +1 -1
- package/build/types.d.ts +20 -20
- package/build/types.js +1 -1
- package/build/types.js.map +1 -1
- package/hooks/hooks.json +4 -2
- package/hooks/workflow-cleanup.sh +8 -4
- package/hooks/workflow-start.sh +60 -32
- package/package.json +1 -1
- package/templates/bug-fix.yaml +98 -16
- package/templates/code-review.yaml +30 -12
- package/templates/coding.yaml +49 -4
- package/templates/debugging.yaml +39 -14
- package/templates/explore.yaml +48 -14
- package/templates/file-code.yaml +13 -4
- package/templates/file-review.yaml +25 -6
- package/templates/github-init.yaml +24 -8
- package/templates/investigate.yaml +13 -4
- package/templates/master.yaml +16 -13
- package/templates/new-feature.yaml +24 -26
- package/templates/planning.yaml +34 -7
- package/templates/reflection.yaml +11 -4
- package/templates/review-push.yaml +26 -7
- package/templates/skills/architecture/SKILL.md +131 -1
- package/templates/skills/aws-lambda/SKILL.md +9 -9
- package/templates/skills/browser-verify/SKILL.md +58 -0
- package/templates/skills/build-cmake/SKILL.md +24 -8
- package/templates/skills/ci-github-actions/SKILL.md +34 -18
- package/templates/skills/claude-code-config/SKILL.md +41 -19
- package/templates/skills/cleanup/SKILL.md +57 -0
- package/templates/skills/coding-skill-selector/SKILL.md +2 -1
- package/templates/skills/debug-bridge-scaffold/SKILL.md +98 -0
- package/templates/skills/domain-gamedev/SKILL.md +56 -6
- package/templates/skills/domain-pixi/SKILL.md +256 -7
- package/templates/skills/domain-reid/SKILL.md +39 -5
- package/templates/skills/domain-yolo/SKILL.md +18 -7
- package/templates/skills/ide-zed/SKILL.md +23 -0
- package/templates/skills/lang-as3/SKILL.md +7 -5
- package/templates/skills/lang-haxe/SKILL.md +538 -19
- package/templates/skills/lang-python/SKILL.md +6 -2
- package/templates/skills/math/SKILL.md +64 -2
- package/templates/skills/mcp-setup/SKILL.md +14 -2
- package/templates/skills/preferences/SKILL.md +10 -10
- package/templates/skills/skill-manager/SKILL.md +264 -0
- package/templates/skills/target-openfl-native/SKILL.md +52 -17
- package/templates/skills/target-openfl-native/references/build-and-versions.md +7 -4
- package/templates/skills/task-delegation/SKILL.md +76 -4
- package/templates/skills/web-reading/SKILL.md +33 -25
- package/templates/skills/workflow-authoring/SKILL.md +47 -12
- package/templates/subagent.yaml +29 -8
- package/templates/testing.yaml +64 -10
- package/templates/web-research.yaml +23 -13
|
@@ -18,14 +18,14 @@ description: GitHub Actions workflow gotchas
|
|
|
18
18
|
The action resolves the most recent tag **reachable from the current commit** via git history. With the default `fetch-depth: 1`, no tags are reachable — the action returns empty or stale results.
|
|
19
19
|
|
|
20
20
|
```yaml
|
|
21
|
-
- uses: actions/checkout@
|
|
21
|
+
- uses: actions/checkout@v7
|
|
22
22
|
with:
|
|
23
23
|
fetch-depth: 0 # required for tag resolution
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
Same applies to `git describe`, `git log --follow`, `gitversion`, and any changelog generator that walks commit history.
|
|
27
27
|
|
|
28
|
-
## `actions/checkout
|
|
28
|
+
## `actions/checkout` Default Fetches Only 1 Commit (v4+)
|
|
29
29
|
|
|
30
30
|
Unless `fetch-depth: 0` (full history) or `fetch-depth: N` is set, you get a shallow clone with depth 1. Breaks: tag lookup, `git log`-based changelogs, `git blame`, branch comparison diffs.
|
|
31
31
|
|
|
@@ -53,10 +53,12 @@ deploy:
|
|
|
53
53
|
# RIGHT — run regardless, check status explicitly
|
|
54
54
|
deploy:
|
|
55
55
|
needs: [build]
|
|
56
|
-
if:
|
|
56
|
+
if: ${{ !cancelled() && needs.build.result == 'success' }}
|
|
57
57
|
steps: ...
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
Prefer `!cancelled()` over `always()`: `always()` forces the job to run even when the workflow run is being cancelled; `!cancelled()` lifts the "dependency failed/skipped" gate but still respects cancellation. (The `${{ }}` is required here — a bare `if:` starting with `!` is invalid YAML.)
|
|
61
|
+
|
|
60
62
|
## `$GITHUB_ENV` Changes Are Not Visible in the Same Step
|
|
61
63
|
|
|
62
64
|
`echo "X=value" >> $GITHUB_ENV` exports `X` for all **subsequent** steps. The current step cannot read it.
|
|
@@ -99,46 +101,60 @@ jobs:
|
|
|
99
101
|
test:
|
|
100
102
|
continue-on-error: true # whole matrix ignores failures
|
|
101
103
|
|
|
102
|
-
#
|
|
103
|
-
strategy:
|
|
104
|
-
matrix:
|
|
105
|
-
os: [ubuntu, windows]
|
|
106
|
-
fail-fast: false
|
|
104
|
+
# Cell-level — only the flagged cell continues, others still gate the job
|
|
107
105
|
jobs:
|
|
108
106
|
test:
|
|
107
|
+
strategy:
|
|
108
|
+
fail-fast: false
|
|
109
|
+
matrix:
|
|
110
|
+
os: [ubuntu, windows]
|
|
109
111
|
continue-on-error: ${{ matrix.os == 'windows' }} # only windows cell is optional
|
|
110
112
|
```
|
|
111
113
|
|
|
114
|
+
Note `strategy:` lives under `jobs.<job>` — it is not a top-level workflow key.
|
|
115
|
+
|
|
112
116
|
`fail-fast: false` (strategy level) keeps sibling cells running after one fails. `continue-on-error: true` (job level) prevents the job from being marked failed. They're orthogonal.
|
|
113
117
|
|
|
114
|
-
## Artifact Names Must Be Unique Within a Run (actions/upload-artifact
|
|
118
|
+
## Artifact Names Must Be Unique Within a Run (actions/upload-artifact v4+)
|
|
115
119
|
|
|
116
|
-
v4
|
|
120
|
+
v4 made artifacts immutable — uploading two artifacts with the same name in one workflow run is an error (v3 silently merged files into one artifact). Since v4.4 an explicit `overwrite: true` input deletes and replaces the existing artifact; the default is still an error.
|
|
117
121
|
|
|
118
122
|
```yaml
|
|
119
123
|
# WRONG — both matrix cells upload "test-results", second one errors
|
|
120
|
-
- uses: actions/upload-artifact@
|
|
124
|
+
- uses: actions/upload-artifact@v7
|
|
121
125
|
with:
|
|
122
126
|
name: test-results
|
|
123
127
|
|
|
124
128
|
# RIGHT — include matrix dimension in the name
|
|
125
|
-
- uses: actions/upload-artifact@
|
|
129
|
+
- uses: actions/upload-artifact@v7
|
|
126
130
|
with:
|
|
127
131
|
name: test-results-${{ matrix.os }}
|
|
128
132
|
```
|
|
129
133
|
|
|
130
|
-
## `if:` Expressions
|
|
134
|
+
## `if:` Expressions: Single Quotes Only, Comparison Is Case-INsensitive
|
|
131
135
|
|
|
132
136
|
```yaml
|
|
133
|
-
# WRONG —
|
|
134
|
-
if: github.event_name == "
|
|
137
|
+
# WRONG — double quotes are a validation error in expressions
|
|
138
|
+
if: github.event_name == "push"
|
|
135
139
|
|
|
136
140
|
# RIGHT
|
|
137
141
|
if: github.event_name == 'push'
|
|
138
142
|
```
|
|
139
143
|
|
|
140
|
-
|
|
144
|
+
String literals in expressions must use single quotes — double quotes fail workflow validation. String comparison with `==` ignores case (`'Push' == 'push'` is true). The operator is `==`; there is no `===`.
|
|
145
|
+
|
|
146
|
+
`if:` expressions do NOT require `${{ }}` wrapping (they're evaluated as expressions already). Adding `${{ }}` works but is redundant — except when the expression starts with `!` (reserved in YAML), e.g. `if: ${{ !cancelled() }}`.
|
|
147
|
+
|
|
148
|
+
## `env:` Does Not Propagate Into Reusable Workflow Calls (`jobs.<job>.uses`)
|
|
149
|
+
|
|
150
|
+
A reusable workflow is called at the JOB level (`jobs.<job>.uses`), not as a step. Environment variables set at workflow or job level are available to `run:` steps but are NOT passed into the called reusable workflow. Pass values explicitly via `with:` inputs or `secrets:` instead.
|
|
151
|
+
|
|
152
|
+
## Default Community Health Files (FUNDING.yml, CODEOWNERS, ISSUE_TEMPLATE) Load ONLY From a Repo Named `.github`
|
|
153
|
+
|
|
154
|
+
Account-wide defaults for community health files come **only** from a public repo named literally `.github` (e.g. `owner/.github`), in its root or a `.github`/`docs` folder. A repo's own file overrides the default.
|
|
155
|
+
|
|
156
|
+
**Wrong:** Putting `FUNDING.yml` in the username/profile repo (`owner/owner`, the one with the profile README) expecting it to apply org-wide — it does NOT. It only affects that single repo.
|
|
141
157
|
|
|
142
|
-
|
|
158
|
+
**Right:** Create `owner/.github` (must be public) → its `FUNDING.yml` gives every repo without its own a Sponsor button.
|
|
143
159
|
|
|
144
|
-
|
|
160
|
+
`FUNDING.yml` keys are platform-specific usernames, not URLs: `buy_me_a_coffee: <user>`, `patreon: <user>`. There is **no** `paypal:` key — PayPal goes under `custom: ['https://paypal.me/<user>']`.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: claude-code-config
|
|
3
|
+
description: Claude Code configuration gotchas — permission rule syntax and evaluation order, settings hierarchy, plugin install scopes, hook behavior
|
|
3
4
|
---
|
|
4
5
|
|
|
5
6
|
# Claude Code Configuration Gotchas
|
|
@@ -14,9 +15,11 @@ name: claude-code-config
|
|
|
14
15
|
**Common mistake**: Writing `Read(/tmp/**)` when you mean absolute path. Correct: `Read(//tmp/**)`.
|
|
15
16
|
|
|
16
17
|
**Pattern format must match tool signature**:
|
|
17
|
-
- Correct: `Read(//tmp/**)`, `Bash(npm run
|
|
18
|
+
- Correct: `Read(//tmp/**)`, `Bash(npm run:*)`
|
|
18
19
|
- Wrong: `Read(file_path://tmp/**)` — tool name only, no parameter labels
|
|
19
20
|
|
|
21
|
+
**Bash rules use `:*` for prefix matching**: `Bash(npm run:*)` matches any command starting with `npm run `. `Bash(npm run *)` (space-star) is NOT the prefix syntax and matches unreliably.
|
|
22
|
+
|
|
20
23
|
## Wildcard Matching
|
|
21
24
|
|
|
22
25
|
**Recursive vs Single Level**:
|
|
@@ -29,8 +32,8 @@ name: claude-code-config
|
|
|
29
32
|
|
|
30
33
|
**Deny always wins**:
|
|
31
34
|
1. Deny rules checked first (block regardless of anything else)
|
|
32
|
-
2.
|
|
33
|
-
3.
|
|
35
|
+
2. Ask rules checked second (prompt user if matched)
|
|
36
|
+
3. Allow rules checked last (permit if matched)
|
|
34
37
|
|
|
35
38
|
**First match wins** within each category.
|
|
36
39
|
|
|
@@ -48,12 +51,8 @@ name: claude-code-config
|
|
|
48
51
|
|
|
49
52
|
## Known Bugs (as of 2026)
|
|
50
53
|
|
|
51
|
-
**Deny rules not enforced**: Multiple reported issues (GH #13785, #6631, #4467, #6699) where deny patterns in settings.json are ignored. Tool still gets access despite explicit deny.
|
|
52
|
-
|
|
53
54
|
**Allow rules ignored**: Issue #18160 reports global allow permissions being ignored, requiring manual approval despite being in allowlist.
|
|
54
55
|
|
|
55
|
-
**Wildcard pattern bugs**: `Bash(command *)` patterns may fail to match commands with flags or tilde expansion.
|
|
56
|
-
|
|
57
56
|
## Domain-Specific Rules
|
|
58
57
|
|
|
59
58
|
**WebFetch requires domain syntax**:
|
|
@@ -64,9 +63,7 @@ name: claude-code-config
|
|
|
64
63
|
|
|
65
64
|
**Overly broad wildcards**: Starting with `Bash(*)` or `Read(**)` creates security holes. Be specific.
|
|
66
65
|
|
|
67
|
-
**
|
|
68
|
-
|
|
69
|
-
**Environment variables in Bash**: Don't persist between commands. Use `CLAUDE_ENV_FILE` or hooks instead.
|
|
66
|
+
**Environment variables in Bash**: Don't persist between commands. Use the `env` map in settings.json or hooks instead.
|
|
70
67
|
|
|
71
68
|
**Testing permissions**: Use Shift+Tab to switch permission modes mid-session without editing files.
|
|
72
69
|
|
|
@@ -76,16 +73,16 @@ name: claude-code-config
|
|
|
76
73
|
{
|
|
77
74
|
"permissions": {
|
|
78
75
|
"allow": [
|
|
79
|
-
"Read(
|
|
76
|
+
"Read(./config.json)", // relative: config.json in settings file dir
|
|
80
77
|
"Read(//tmp/**)", // absolute: all files in /tmp
|
|
81
78
|
"Read(~/.config/**)", // home: all files in ~/.config
|
|
82
|
-
"Read(
|
|
83
|
-
"Bash(npm run
|
|
79
|
+
"Read(**/config.json)", // recursive: config.json anywhere in subtree
|
|
80
|
+
"Bash(npm run:*)", // command prefix matching (:* suffix)
|
|
84
81
|
"WebFetch(domain:docs.anthropic.com)" // domain-specific
|
|
85
82
|
],
|
|
86
83
|
"deny": [
|
|
87
84
|
"Read(**/secrets/**)", // block entire directory tree
|
|
88
|
-
"Bash(rm
|
|
85
|
+
"Bash(rm:*)" // block dangerous commands
|
|
89
86
|
]
|
|
90
87
|
}
|
|
91
88
|
}
|
|
@@ -94,14 +91,14 @@ name: claude-code-config
|
|
|
94
91
|
## Debugging Permission Issues
|
|
95
92
|
|
|
96
93
|
1. Check hierarchy: local > project > user > enterprise
|
|
97
|
-
2. Check deny
|
|
94
|
+
2. Check rule order: deny → ask → allow (deny always wins)
|
|
98
95
|
3. Test pattern manually: Does it match the exact tool signature shown in prompt?
|
|
99
|
-
4. Check for known bugs (
|
|
96
|
+
4. Check for known bugs (allow rules may be ignored — #18160)
|
|
100
97
|
5. Use `claude mcp list` to verify MCP server status if permission involves MCP tool
|
|
101
98
|
|
|
102
99
|
## Plugin Install Scope
|
|
103
100
|
|
|
104
|
-
`claude
|
|
101
|
+
`claude plugin install <pkg>@<marketplace>` (singular `plugin`, not `plugins`) takes `-s, --scope <user|project|local>` (default: `user`).
|
|
105
102
|
|
|
106
103
|
- `user` — global, ~/.claude/plugins/installed_plugins.json with `"scope": "user"`
|
|
107
104
|
- `project` — must run from within the target project dir; registry entry gets `"scope": "project"` + `"projectPath": "<cwd>"`. Plugin activates only when Claude Code runs inside that dir
|
|
@@ -109,14 +106,39 @@ name: claude-code-config
|
|
|
109
106
|
|
|
110
107
|
Swap scope = uninstall + reinstall (no in-place conversion):
|
|
111
108
|
```bash
|
|
112
|
-
claude
|
|
113
|
-
cd /path/to/project && claude
|
|
109
|
+
claude plugin uninstall <pkg>@<mkt> --scope user
|
|
110
|
+
cd /path/to/project && claude plugin install <pkg>@<mkt> --scope project
|
|
114
111
|
```
|
|
115
112
|
|
|
116
113
|
All scopes share the same `~/.claude/plugins/cache/` payload — only the registry entry differs.
|
|
117
114
|
|
|
115
|
+
## Stop Hook Fires Per Turn, Not on Process Exit
|
|
116
|
+
|
|
117
|
+
**Wrong assumption**: `Stop` hook = "CLI process is shutting down / session ending".
|
|
118
|
+
**Correct**: `Stop` fires **each time the main agent finishes responding** (end of every turn). One CLI process lifetime can trigger it dozens of times. Docs: *"when Claude finishes responding"* — [docs.claude.com/en/docs/claude-code/hooks](https://docs.claude.com/en/docs/claude-code/hooks).
|
|
119
|
+
|
|
120
|
+
`SubagentStop` fires when a subagent (Task tool) finishes — separate from the main agent's Stop.
|
|
121
|
+
|
|
122
|
+
**Practical implication**: Any state registered on `UserPromptSubmit` and released on `Stop` churns every turn. For per-process state (e.g. a background daemon, a lock file, an IPC socket) key by **CLI PID** — `session_id` is wrong because it also changes on `/clear`, `/compact`, or resume within the same process.
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
UserPromptSubmit → fires before each user turn
|
|
126
|
+
Stop → fires after each assistant response (not at exit)
|
|
127
|
+
SubagentStop → fires when a Task-tool subagent completes
|
|
128
|
+
PostToolUse → fires after each individual tool call within a turn
|
|
129
|
+
```
|
|
130
|
+
|
|
118
131
|
## npx Argument Re-parsing Under PreToolUse Hooks
|
|
119
132
|
|
|
120
133
|
PreToolUse Bash hooks that rewrite commands can mis-parse `npx -y <pkg>@latest <subcmd>`, with `<pkg>@latest` landing as an **npm** subcommand, producing `Unknown command: "<pkg>@latest"`.
|
|
121
134
|
|
|
122
135
|
Workaround: invoke through a wrapper that bypasses the rewriter (e.g. an explicit `proxy` subcommand of the hook tool, or call the binary directly without `npx -y`). The same `npx` call may succeed via workflow-engine exec while failing via Bash tool — the difference is whether the hook intercepts.
|
|
136
|
+
|
|
137
|
+
## Multiple Hook Entries for the Same Event Run in Parallel
|
|
138
|
+
|
|
139
|
+
**Wrong assumption**: Two `PreToolUse` entries in `hooks.json` run in declaration order — first entry first, second entry second.
|
|
140
|
+
**Correct**: All entries for the same event fire **simultaneously**. Declaration order is not execution order.
|
|
141
|
+
|
|
142
|
+
**Race-condition trap**: If entry A writes a marker file and entry B deletes it, the outcome depends on timing, not position. A fast B (~0 ms) completes before a slow A (~30–50 ms), so A's delete wins even when B is declared last. Symptom: your write appears to have no effect.
|
|
143
|
+
|
|
144
|
+
**Fix**: One hook entry per event. Inspect stdin JSON inside that single script/command and branch on `tool_name` or other fields to dispatch logic. This guarantees deterministic ordering because it's a single process.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cleanup
|
|
3
|
+
description: Clean .claude directory data
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## .claude directory cleanup
|
|
7
|
+
|
|
8
|
+
Run steps in order. **Ask user confirmation before each step.**
|
|
9
|
+
|
|
10
|
+
### Step 1: Temp data (always safe)
|
|
11
|
+
|
|
12
|
+
Delete contents of (keep directories themselves):
|
|
13
|
+
- `~/.claude/debug/` — session debug logs
|
|
14
|
+
- `~/.claude/shell-snapshots/` — zsh state snapshots
|
|
15
|
+
- `~/.claude/todos/` — task lists from past sessions
|
|
16
|
+
- `~/.claude/plans/` — plan files from past sessions
|
|
17
|
+
|
|
18
|
+
Show size before/after.
|
|
19
|
+
|
|
20
|
+
### Step 2: Project session logs
|
|
21
|
+
|
|
22
|
+
For each project in `~/.claude/projects/`:
|
|
23
|
+
- **DELETE**: `*.jsonl` files (conversation logs), `UUID/` directories (session caches), `agent-*.jsonl` (subagent logs)
|
|
24
|
+
- **KEEP**: `memory/` directory, `CLAUDE.md` file
|
|
25
|
+
|
|
26
|
+
⚠️ This breaks `/resume` for old sessions. Warn user.
|
|
27
|
+
|
|
28
|
+
Show size before/after.
|
|
29
|
+
|
|
30
|
+
### Step 3: Empty project directories
|
|
31
|
+
|
|
32
|
+
After step 2, list projects that have no remaining files (no memory/, no CLAUDE.md).
|
|
33
|
+
Offer to remove them — they recreate automatically when Claude Code runs in that project.
|
|
34
|
+
|
|
35
|
+
### Step 4: Memory audit
|
|
36
|
+
|
|
37
|
+
For each remaining `memory/MEMORY.md`:
|
|
38
|
+
1. Read its contents
|
|
39
|
+
2. Check each entry against existing skills — is it already covered?
|
|
40
|
+
3. Check if Claude already knows this without any skill
|
|
41
|
+
4. Present table to user:
|
|
42
|
+
|
|
43
|
+
| Entry | Duplicates? | Verdict |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| ... | skill X / common knowledge / project-specific | delete / keep |
|
|
46
|
+
|
|
47
|
+
Only modify after user approval.
|
|
48
|
+
|
|
49
|
+
### Other cleanable files
|
|
50
|
+
|
|
51
|
+
Also check and report sizes of:
|
|
52
|
+
- `~/.claude/history.jsonl` — conversation history (can be large)
|
|
53
|
+
- `~/.claude/file-history/` — file change history
|
|
54
|
+
- `~/.claude/paste-cache/` — clipboard cache
|
|
55
|
+
- `~/.claude/tasks/` — task data
|
|
56
|
+
|
|
57
|
+
These are less critical. Mention sizes, let user decide.
|
|
@@ -21,7 +21,7 @@ Load coding skills for files/context in this task.
|
|
|
21
21
|
|
|
22
22
|
3. By domain (detect from imports, paths, or task description):
|
|
23
23
|
- YOLO / object detection → `Skill("domain-yolo")`
|
|
24
|
-
- Pixi.js
|
|
24
|
+
- Pixi.js (imports from `pixi.js`, PIXI globals) → `Skill("domain-pixi")`
|
|
25
25
|
- Person re-identification (ReID) → `Skill("domain-reid")`
|
|
26
26
|
- Game dev (physics, precision, camera, sprites) → `Skill("domain-gamedev")`
|
|
27
27
|
- OpenFL / hxcpp native target → `Skill("target-openfl-native")`
|
|
@@ -35,6 +35,7 @@ Load coding skills for files/context in this task.
|
|
|
35
35
|
- Claude Code settings.json / hooks / permissions / plugin config → `Skill("claude-code-config")`
|
|
36
36
|
- Workflow YAML in templates/ or .claude/workflows/ → `Skill("workflow-authoring")`
|
|
37
37
|
- Fetching web docs / external content → `Skill("web-reading")`
|
|
38
|
+
- Verifying a web/canvas app via Playwright or a browser MCP (screenshots, captures, perf) → `Skill("browser-verify")`
|
|
38
39
|
|
|
39
40
|
5. If 3+ independent files or parallel work → `Skill("task-delegation")`
|
|
40
41
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: debug-bridge-scaffold
|
|
3
|
+
description: Meta-skill for creating debug bridges for new technologies
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Debug Bridge Scaffold
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
When you need runtime inspection of an application built with a technology that has no existing debug bridge.
|
|
11
|
+
|
|
12
|
+
## Step 1: Check Existing Solutions First
|
|
13
|
+
|
|
14
|
+
Before building a custom bridge, check if a standard tool already works:
|
|
15
|
+
|
|
16
|
+
| Technology | Existing Solution |
|
|
17
|
+
|-----------|-------------------|
|
|
18
|
+
| Web/Electron/browser apps | Playwright MCP, browser DevTools |
|
|
19
|
+
| Native apps with standard UI toolkit | Accessibility API MCP |
|
|
20
|
+
| Any app with visible UI | Computer Use (screenshot + click) |
|
|
21
|
+
| OpenFL/hxcpp | `debug-bridge-openfl` skill (already exists) |
|
|
22
|
+
|
|
23
|
+
**If an existing solution covers your needs — use it. Don't build a bridge.**
|
|
24
|
+
|
|
25
|
+
## Step 2: Build a Custom Bridge
|
|
26
|
+
|
|
27
|
+
If no existing tool works, create an HTTP bridge following this pattern:
|
|
28
|
+
|
|
29
|
+
### Architecture
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
[Background Thread] HTTP Server (127.0.0.1:PORT)
|
|
33
|
+
↓ accept()
|
|
34
|
+
[Worker Thread] Parse request → dispatch to main thread
|
|
35
|
+
↓ runOnMainThread(fn)
|
|
36
|
+
[Main Thread] Execute handler → return result
|
|
37
|
+
↑ result via Lock/Mutex
|
|
38
|
+
[Worker Thread] Send HTTP response
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Required Endpoints
|
|
42
|
+
|
|
43
|
+
| Endpoint | Method | Purpose |
|
|
44
|
+
|----------|--------|---------|
|
|
45
|
+
| `/ping` | GET | Health check |
|
|
46
|
+
| `/screenshot` | GET | Capture visual state → save to /tmp → return path |
|
|
47
|
+
| `/shutdown` | POST | Graceful exit |
|
|
48
|
+
|
|
49
|
+
### Framework-Specific Endpoints
|
|
50
|
+
|
|
51
|
+
Add endpoints relevant to the technology:
|
|
52
|
+
- **UI frameworks**: display tree, component inspection, event simulation
|
|
53
|
+
- **Game engines**: scene graph, entity list, physics state
|
|
54
|
+
- **Servers**: request log, connection pool, cache state
|
|
55
|
+
|
|
56
|
+
### Implementation Checklist
|
|
57
|
+
|
|
58
|
+
1. HTTP server on background thread (127.0.0.1 only — no external access)
|
|
59
|
+
2. Main-thread dispatch for all UI/rendering operations
|
|
60
|
+
3. JSON envelope: `{"ok": true, "data": ...}` / `{"ok": false, "error": "..."}`
|
|
61
|
+
4. Conditional compilation (only in debug builds)
|
|
62
|
+
5. Graceful shutdown (close socket, flush stdout, exit)
|
|
63
|
+
|
|
64
|
+
## Step 3: Create a Tech-Specific Skill
|
|
65
|
+
|
|
66
|
+
After building the bridge, create `~/.claude/skills/debug-bridge-<tech>/SKILL.md` with:
|
|
67
|
+
|
|
68
|
+
1. Build command with the debug flag
|
|
69
|
+
2. Binary path pattern
|
|
70
|
+
3. All endpoints with parameters and example responses
|
|
71
|
+
4. Technology-specific gotchas (threading, rendering pipeline, etc.)
|
|
72
|
+
|
|
73
|
+
Register it in `coding-skill-selector` under the appropriate domain.
|
|
74
|
+
|
|
75
|
+
## Step 4: Create an Interactive Debugger Agent
|
|
76
|
+
|
|
77
|
+
Create `~/.claude/agents/interactive-<tech>-debugger.md` with pre-loaded skills:
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
---
|
|
81
|
+
name: interactive-<tech>-debugger
|
|
82
|
+
description: <Tech> debug agent with visual debug bridge — ...
|
|
83
|
+
tools: Read, Glob, Grep, Bash, Skill
|
|
84
|
+
model: sonnet
|
|
85
|
+
skills:
|
|
86
|
+
- debugging
|
|
87
|
+
- debug-bridge
|
|
88
|
+
- debug-bridge-<tech>
|
|
89
|
+
- <app-specific-interactive-debug-skill if exists>
|
|
90
|
+
---
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Body: describe the debug loop for the agent — PID tracking, separate build and run steps, timeout rules.
|
|
94
|
+
|
|
95
|
+
Then update:
|
|
96
|
+
1. `task-delegation` — add agent to Available Agents table and routing rules
|
|
97
|
+
2. `testing` — reference the new agent for integration verification
|
|
98
|
+
3. `debugging` — reference the new agent in Step 4 (Debug Bridge)
|
|
@@ -12,19 +12,21 @@ See also: math skill for overflow boundaries (factorial, Fibonacci, float absorp
|
|
|
12
12
|
| Distance from origin | ULP (precision) | Effect |
|
|
13
13
|
|---------------------|-----------------|--------|
|
|
14
14
|
| 100km | 0.78cm | Fine for most games |
|
|
15
|
-
| 524km (≈2^19m) | 3.1cm → 6.25cm | Unity
|
|
15
|
+
| 524km (≈2^19m) | 3.1cm → 6.25cm | Unity world edge zone |
|
|
16
16
|
| 2,097km (2^21m) | 25cm | 5m/s @ 60fps movement ABSORBED — character stops moving |
|
|
17
17
|
| 8,389km (≈2^23m) | 1m | Positions snap to meter grid |
|
|
18
18
|
|
|
19
19
|
**Absorption formula**: movement absorbed when `velocity * dt < ULP(position) / 2`.
|
|
20
20
|
|
|
21
|
+
**Unreal gotcha**: Unreal units are CENTIMETERS, not meters — the same ULP boundaries apply per UNIT, so the metric distances above divide by 100 (e.g. 2^21 units ≈ 21km, not 2,097km).
|
|
22
|
+
|
|
21
23
|
## Deterministic Physics
|
|
22
24
|
|
|
23
25
|
**DLL FPU contamination**: Direct3D, printer drivers, sound libraries can silently change FPU control word (precision/rounding mode) without restoring. Assert FPU state after every external call.
|
|
24
26
|
|
|
25
27
|
**Debug vs release breaks replays**: compiler optimization level alone changes float results even with identical source. All replay participants must use identical binary.
|
|
26
28
|
|
|
27
|
-
**FMA cross-platform**:
|
|
29
|
+
**FMA cross-platform**: fused multiply-add (one rounding) vs separate mul+add (two roundings) → different results from identical math. (The classic PowerPC-has-FMA / Intel-doesn't framing is stale — modern x86 has FMA3 since Haswell and ARM has FMA; today the divergence comes from whether the COMPILER contracts `a*b+c` into FMA, which varies by compiler and flags like `-ffp-contract`.) Cross-platform determinism requires controlling instruction selection.
|
|
28
30
|
|
|
29
31
|
**x87 vs SSE**: x87 uses 80-bit extended precision internally → double rounding when storing to 64-bit. SSE uses exact 32/64-bit → more predictable. But SSE FTZ/DAZ modes may differ across platforms.
|
|
30
32
|
|
|
@@ -32,7 +34,7 @@ See also: math skill for overflow boundaries (factorial, Fibonacci, float absorp
|
|
|
32
34
|
|
|
33
35
|
## CCD Threshold (verified)
|
|
34
36
|
|
|
35
|
-
Enable continuous collision detection when: `velocity × dt
|
|
37
|
+
Enable continuous collision detection when: `velocity × dt >= object_size`.
|
|
36
38
|
- 1m object at 60m/s, 60fps: travels 1m/frame → equals object size → CCD needed
|
|
37
39
|
- Below threshold: discrete detection misses contacts (tunneling)
|
|
38
40
|
|
|
@@ -49,7 +51,7 @@ When a state machine waits for an animation-completion event (e.g. xstate `on.AN
|
|
|
49
51
|
|
|
50
52
|
## Parallax Tile Assets: Native Width = Integration Signal
|
|
51
53
|
|
|
52
|
-
When integrating an artist-authored asset (e.g. `
|
|
54
|
+
When integrating an artist-authored asset (e.g. `landmark.png`) into a row of cyclically-stitched parallax tiles (e.g. `tile_1..6.png`), **check native dimensions first**.
|
|
53
55
|
|
|
54
56
|
- If the asset has the **same native width** as the cyclic tiles → it was designed as ONE of the tiles. Insert it into the stitch sequence at a specific anchor position. Don't overlay.
|
|
55
57
|
- If the asset has **different dimensions** → it's a prop sprite meant to sit on top of the tile row (e.g. a character, tree, bush).
|
|
@@ -86,7 +88,7 @@ In most 2D engines (Pixi, Phaser, etc.), `sprite.anchor.set(ax, ay)` serves TWO
|
|
|
86
88
|
|
|
87
89
|
**Use case**: to rotate a sprite around a specific feature of its PNG (e.g. the left rim of a hazard/foot of a character), put the anchor at that feature's normalized texture coords. Then set `sprite.position` to the world location of that feature and `sprite.rotation` — the feature stays locked in place while the rest swings.
|
|
88
90
|
|
|
89
|
-
**Gotcha**: anchor and
|
|
91
|
+
**Gotcha**: in Pixi the Sprite's `anchor` drives BOTH roles — placement point and rotation origin are not independently settable through it (a separate `pivot` property exists on every Container, but changing it shifts where `position` lands too). If you need placement and rotation origin decoupled, wrap the Sprite in a Container and rotate the Container.
|
|
90
92
|
|
|
91
93
|
## Heightmap From PNG Alpha — Pre-sample Once
|
|
92
94
|
|
|
@@ -127,7 +129,7 @@ When the follow formula is `cameraY = targetScreenY − worldY × zoom` (standar
|
|
|
127
129
|
- Ball RISES in world → `worldY` DECREASES → `cameraY` INCREASES.
|
|
128
130
|
- `Math.min(flightTarget, idleCameraY)` reads as "never go ABOVE idle" in human terms, but `cameraY` for a higher ball is NUMERICALLY LARGER than the idle value — so `Math.min` pins the camera to idle and BLOCKS the follow-up. The correct direction is `Math.max` (if clamping against an upper bound on camera Y) or no clamp at all.
|
|
129
131
|
|
|
130
|
-
**Verify before shipping**: plug in two concrete numbers — ball at rest and ball at peak — and compute `cameraY` for each.
|
|
132
|
+
**Verify before shipping**: plug in two concrete numbers — ball at rest and ball at peak — and compute `cameraY` for each. With `cameraY = targetScreenY − worldY × zoom`, the endpoint with the LARGER `worldY` (ball lower in the world) yields the SMALLER `cameraY`. If the clamp picks the wrong side, the camera becomes useless at exactly the moment it needs to work.
|
|
131
133
|
|
|
132
134
|
**Rule**: every time you combine `screenAnchor − worldPos × zoom` with a `Math.min`/`Math.max`, write the two endpoints on paper. Don't trust the human-language reading of the clamp.
|
|
133
135
|
|
|
@@ -160,3 +162,51 @@ Sub-pixel for flat terrain, but visible (1+ px sliver) on noticeably tilted band
|
|
|
160
162
|
| > 40ms | Unacceptable for rhythm/FPS |
|
|
161
163
|
|
|
162
164
|
Humans react to audio faster than visual → audio lag more noticeable than dropped frames.
|
|
165
|
+
|
|
166
|
+
## Crash / Multiplier-Game RTP (Aviator / Bustabit style)
|
|
167
|
+
|
|
168
|
+
**Canonical crash math**: `crashPoint = (1−he)/(1−U)`, `U∈[0,1)`. This gives `P(crashPoint ≥ x) = (1−he)/x` for `x≥1`. EV at any cashout target `x` = `P(survive to x)·x = (1−he)/x · x = 1−he`. RTP = `1−houseEdge`, **strategy-independent** — the only clean way to get that property.
|
|
169
|
+
|
|
170
|
+
**GOTCHA — forced/consolation payouts break RTP and strategy-independence.**
|
|
171
|
+
Any guaranteed payout on a step the round did NOT crash on (a "soft-loss step saves your accumulated winnings" mechanic, forced cashout on miss, consolation bonus) is EV-positive: it pays value the crash-CDF budget never debited. Effect:
|
|
172
|
+
- Global RTP > target.
|
|
173
|
+
- "Ride longer" becomes the dominant strategy → strategy-independence destroyed.
|
|
174
|
+
|
|
175
|
+
Only voluntary cashouts and a correctly-priced cap/jackpot are EV-neutral. "Soft-loss that saves X" is fundamentally incompatible with strategy-independent crash RTP without full game-math re-optimization around the save mechanics.
|
|
176
|
+
|
|
177
|
+
**GOTCHA — flat auto-paid cap inflates AFK strategy EV.**
|
|
178
|
+
A jackpot cap paid regardless of player action gives the never-cash (AFK) strategy `EV = P(cap)·cap`. EV-neutral only if `P(cap) = (1−he)/cap` exactly (the same CDF). Combine with ANY other forced payout and AFK RTP overshoots the target by tens of percent.
|
|
179
|
+
|
|
180
|
+
**GOTCHA — finite step chain caps reachable multiplier.**
|
|
181
|
+
An N-step chain with per-step multiplier `∈[lo,hi]` tops out at `≈hi^(N−1)`. Crash points above that are structurally unreachable → RTP sags BELOW target for high cashout targets and for never-cash. "Exactly `1−he`, globally strategy-independent" is FALSE for a finite chain; it only holds approximately for reachable targets.
|
|
182
|
+
|
|
183
|
+
| N steps | per-step max | reachable ceiling |
|
|
184
|
+
|---------|-------------|------------------|
|
|
185
|
+
| 5 | ×3 | ×81 |
|
|
186
|
+
| 7 | ×2.5 | ×244 |
|
|
187
|
+
| 10 | ×2 | ×512 |
|
|
188
|
+
|
|
189
|
+
**GOTCHA — PRNG stream independence for crashPoint.**
|
|
190
|
+
`crashPoint` must be statistically independent of the per-step multiplier draws. Drawing it as the first output then the curve from subsequent draws of the same stream is fine (good PRNG outputs are uncorrelated). A separate stream keyed by a near-identical seed through a weak hash (e.g. `seed` vs `seed+":crash"` via FNV-1a → mulberry32) can silently correlate and inflate or deflate RTP. Verify empirically with a large Monte Carlo sweep — don't assume independence from the construction.
|
|
191
|
+
|
|
192
|
+
**GOTCHA — conditional sub-population EV slices carry structural bias.**
|
|
193
|
+
Filtering a Monte Carlo run to "rounds containing event X" and asserting the per-target RTP corridor of the slice ≈ global RTP is wrong whenever X correlates with round length or terminal outcome. Two independent causes:
|
|
194
|
+
(a) *Chain-truncating event*: bonus steps that mostly emit at the crash-branch terminal — the round ends there — so high targets are structurally unreachable. Per-target RTPs of the slice decline monotonically with target; no tolerance widening fixes it.
|
|
195
|
+
(b) *Compensation/dampening gate*: forced-miss rounds replace the event step with a miss variant and never enter the filtered slice — rounds that do enter carry inherited selection bias → the slice mean lands well below the global RTP.
|
|
196
|
+
Pin only the upper bound (no target > 1.10–1.15) to catch +EV regressions, plus a wide mean band (±0.10). Drop per-target corridor, spread, and ride-RTP assertions on any slice with structural correlation between the filter condition and the round's terminal outcome.
|
|
197
|
+
|
|
198
|
+
**PROCESS GOTCHA — Monte Carlo BEFORE porting or regenerating fixtures.**
|
|
199
|
+
Validate the EV model with a sweep across multiple fixed cashout targets before porting to a second language or regenerating golden fixtures. An analytically "correct" model can hide EV-design bugs (forced payouts, chain ceiling, PRNG correlation) that only a per-target simulation exposes. Porting first = double rework when the math turns out wrong.
|
|
200
|
+
|
|
201
|
+
**GOTCHA — economic model must not silently override a player-visible physics event.**
|
|
202
|
+
When a deterministic outcome layer (pre-drawn crash point / book-based result) is draped over a physics renderer, scope any economic reclassification to outcomes the player CANNOT SEE. A physical impact the player watches happen (the ball rams an obstacle and bounces) MUST terminate consistently with what was shown — overriding it back to "safe, continue" so the book stays tidy produces a contradiction caught in the first playtest (observed: the ball visibly hit an obstacle yet the multiplier kept climbing). Rule: renderer-derived classification wins for any event the player can see; the economic model may only override outcomes that are visually indistinguishable.
|
|
203
|
+
|
|
204
|
+
## Virtual-Scroll / Treadmill Takeover — Match Speed at the Handoff Seam
|
|
205
|
+
|
|
206
|
+
When forward motion transitions from **real translation** (object moves in world-X, camera follows) to a **virtual scroll / treadmill** (object parked at a fixed screen X, background streamed past it to fake motion), the streamed background speed must MATCH the object's prior real forward speed at the seam, then ease down. A speed discontinuity — especially a collapse — reads as the object STOPPING, not as continued motion.
|
|
207
|
+
|
|
208
|
+
**Concrete case**: a flying ball soared at ~1700 px/s (real X travel, camera following), then a "cruise" phase parked the ball and streamed the sky at a constant 280 px/s — a ~6× speed collapse at the seam. Players read it as "the ball stopped." Fix: start the treadmill at the soar's exit speed (`≈ (end.x − start.x) / duration`, clamped to a sane max), then smoothstep-decelerate to the idle drift speed (280 px/s). The background keeps moving at the prior rate and gently settles — reads as continuous forward flight arriving into a hover.
|
|
209
|
+
|
|
210
|
+
**Measurement gotcha**: if the engine time-scales animation (e.g. `gsap.globalTimeline.timeScale(speed)` for a `?speed` param), the sampled background px/s is `game-speed × timeScale`. Multiply back by `1/timeScale` before comparing to your intended game-time speed, or a correct value reads as "too slow."
|
|
211
|
+
|
|
212
|
+
**Secondary motion gotcha**: a vertical overlay (e.g. wind weave) at large amplitude (±70 px) on a now-slow background DOMINATES perception — reads as "bobbing in place." Keep secondary motion small relative to apparent forward speed (±70 → ±26 px fixed it).
|