@silverassist/agents-toolkit 2.3.0 → 2.5.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.
- package/LICENSE +2 -2
- package/README.md +166 -33
- package/bin/cli.js +511 -16
- package/package.json +1 -1
- package/src/index.js +49 -9
- package/templates/agents/AGENTS.md +33 -5
- package/templates/shared/hooks/lint-format.json +1 -0
- package/templates/shared/hooks/validate-tsx.json +1 -0
- package/templates/shared/instructions/caching.instructions.md +121 -0
- package/templates/shared/instructions/seo-ai-optimization.instructions.md +272 -0
- package/templates/shared/prompts/_partials/release-node.md +58 -0
- package/templates/shared/prompts/_partials/release-wordpress.md +56 -0
- package/templates/shared/prompts/_partials/validations.md +1 -0
- package/templates/shared/prompts/create-github-pr.prompt.md +147 -0
- package/templates/shared/prompts/create-plan.prompt.md +8 -0
- package/templates/shared/prompts/finalize-github-pr.prompt.md +129 -0
- package/templates/shared/prompts/prepare-github-release.prompt.md +134 -0
- package/templates/shared/prompts/review-code.prompt.md +12 -0
- package/templates/shared/prompts/work-github-issue.prompt.md +1 -1
- package/templates/shared/skills/README.md +25 -3
- package/templates/shared/skills/nextjs-caching/SKILL.md +184 -0
- package/templates/shared/prompts/prepare-release.prompt.md +0 -74
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
agent: agent
|
|
3
|
+
description: Create a pull request for the current branch linked to a GitHub issue
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Create GitHub Pull Request
|
|
7
|
+
|
|
8
|
+
Create a pull request for the current branch linked to GitHub issue **#{issue-number}**.
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
- Run `prepare-pr` first to ensure code is ready
|
|
12
|
+
- GitHub MCP connection or `gh` CLI required
|
|
13
|
+
- Reference: `.github/prompts/_partials/pr-template.md`
|
|
14
|
+
- Reference: `.github/prompts/_partials/git-operations.md`
|
|
15
|
+
- Reference: `.github/prompts/_partials/github-integration.md`
|
|
16
|
+
|
|
17
|
+
## Steps
|
|
18
|
+
|
|
19
|
+
### 1. Verify Current State
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
git branch --show-current
|
|
23
|
+
git status
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Verify:
|
|
27
|
+
- Branch follows convention: `feature/{issue-number}-*` or `bugfix/{issue-number}-*`
|
|
28
|
+
- All changes are committed
|
|
29
|
+
- Not on protected branch
|
|
30
|
+
|
|
31
|
+
### 2. Review Changes
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
BASE_BRANCH=$(node -e "try{const c=require('./.agents-toolkit.json');console.log(c.pr?.targetBranch||c.git?.defaultBranch||'main')}catch{console.log('main')}")
|
|
35
|
+
git diff "$BASE_BRANCH" --name-only
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- Reuse `BASE_BRANCH` in all subsequent steps
|
|
39
|
+
- Summarize the changes made
|
|
40
|
+
- Identify breaking changes or migrations
|
|
41
|
+
|
|
42
|
+
### 3. Read GitHub Issue
|
|
43
|
+
|
|
44
|
+
Fetch issue **#{issue-number}** details:
|
|
45
|
+
- Get title for PR title
|
|
46
|
+
- Extract acceptance criteria
|
|
47
|
+
- Get any context from comments
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
gh issue view {issue-number} | cat
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 4. Run Final Validations
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm run lint --if-present
|
|
57
|
+
npm run type-check --if-present
|
|
58
|
+
if [ -f tsconfig.json ]; then npx tsc --noEmit; fi
|
|
59
|
+
npm run test --if-present
|
|
60
|
+
npm run build --if-present
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Fix any issues before proceeding.
|
|
64
|
+
|
|
65
|
+
### 5. Push Branch
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
git push -u origin $(git branch --show-current)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 6. Create Pull Request
|
|
72
|
+
|
|
73
|
+
#### PR Title
|
|
74
|
+
```
|
|
75
|
+
{Issue title}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### PR Description
|
|
79
|
+
|
|
80
|
+
Use this template:
|
|
81
|
+
|
|
82
|
+
```markdown
|
|
83
|
+
## Summary
|
|
84
|
+
Brief description of what this PR accomplishes.
|
|
85
|
+
|
|
86
|
+
## Related Issue
|
|
87
|
+
Closes #{issue-number}
|
|
88
|
+
|
|
89
|
+
## Changes Made
|
|
90
|
+
- Change 1: Description
|
|
91
|
+
- Change 2: Description
|
|
92
|
+
- Change 3: Description
|
|
93
|
+
|
|
94
|
+
## Type of Change
|
|
95
|
+
- [ ] 🐛 Bug fix
|
|
96
|
+
- [ ] ✨ New feature
|
|
97
|
+
- [ ] 💥 Breaking change
|
|
98
|
+
- [ ] 📝 Documentation
|
|
99
|
+
- [ ] 🔧 Refactoring
|
|
100
|
+
|
|
101
|
+
## Testing
|
|
102
|
+
- [ ] Unit tests added/updated
|
|
103
|
+
- [ ] Manual testing performed
|
|
104
|
+
- Describe test cases here
|
|
105
|
+
|
|
106
|
+
## Screenshots
|
|
107
|
+
(If UI changes, add before/after screenshots)
|
|
108
|
+
|
|
109
|
+
## Checklist
|
|
110
|
+
- [ ] Code follows project style guidelines
|
|
111
|
+
- [ ] Self-review completed
|
|
112
|
+
- [ ] Documentation updated
|
|
113
|
+
- [ ] Tests pass locally
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### Create via CLI
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
gh pr create \
|
|
120
|
+
--title "{Issue title}" \
|
|
121
|
+
--body "$(cat <<'EOF'
|
|
122
|
+
## Summary
|
|
123
|
+
...
|
|
124
|
+
|
|
125
|
+
Closes #{issue-number}
|
|
126
|
+
EOF
|
|
127
|
+
)" \
|
|
128
|
+
--base "$BASE_BRANCH" | cat
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
#### PR Settings
|
|
132
|
+
- **Source**: Current branch
|
|
133
|
+
- **Target**: `<base-branch>` resolved from `.agents-toolkit.json` (fallback: `main`)
|
|
134
|
+
- **Reviewers**: Based on changed files
|
|
135
|
+
|
|
136
|
+
### 7. Comment on GitHub Issue
|
|
137
|
+
|
|
138
|
+
Add a comment linking to the PR:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
gh issue comment {issue-number} --body "## Pull Request Created
|
|
142
|
+
|
|
143
|
+
PR: <pr-url>
|
|
144
|
+
Branch: \`$(git branch --show-current)\`
|
|
145
|
+
|
|
146
|
+
Work in progress. Review requested." | cat
|
|
147
|
+
```
|
|
@@ -55,6 +55,14 @@ High-level description of the solution.
|
|
|
55
55
|
### API Changes
|
|
56
56
|
If applicable, document any API changes.
|
|
57
57
|
|
|
58
|
+
### Caching Impact (Next.js)
|
|
59
|
+
If the feature touches data fetching, routes, or `next.config`, state the caching plan:
|
|
60
|
+
- Reads vs. mutations (mutations never cached; reads cache regardless of GET/POST)
|
|
61
|
+
- `revalidate` tier + cache `tags` for any new fetches
|
|
62
|
+
- Whether new routes export `revalidate`, and any on-demand invalidation (Next.js + CDN) needed
|
|
63
|
+
|
|
64
|
+
See `.github/instructions/caching.instructions.md`.
|
|
65
|
+
|
|
58
66
|
## Risk Assessment
|
|
59
67
|
|
|
60
68
|
| Risk | Probability | Impact | Mitigation |
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
agent: agent
|
|
3
|
+
description: Finalize a pull request after approval and prepare for merge
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Finalize GitHub Pull Request
|
|
7
|
+
|
|
8
|
+
Finalize PR for GitHub issue **#{issue-number}** after approval and prepare for merge.
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
- PR has been approved
|
|
12
|
+
- GitHub MCP connection or `gh` CLI required
|
|
13
|
+
- Reference: `.github/prompts/_partials/git-operations.md`
|
|
14
|
+
- Reference: `.github/prompts/_partials/validations.md`
|
|
15
|
+
- Reference: `.github/prompts/_partials/github-integration.md`
|
|
16
|
+
|
|
17
|
+
## Steps
|
|
18
|
+
|
|
19
|
+
### 1. Verify PR Status
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
gh pr view --json state,reviewDecision,statusCheckRollup | cat
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Check:
|
|
26
|
+
- All required approvals in place
|
|
27
|
+
- CI/CD pipeline passed
|
|
28
|
+
- No unresolved review comments
|
|
29
|
+
|
|
30
|
+
### 2. Address Review Comments
|
|
31
|
+
|
|
32
|
+
If there are unresolved comments:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
gh pr view --json reviews,comments | cat
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- List each unresolved comment
|
|
39
|
+
- Address feedback
|
|
40
|
+
- Push additional commits if needed
|
|
41
|
+
- Request re-review if changes are significant:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
gh pr review --request-changes --body "..." | cat
|
|
45
|
+
# or after fixing:
|
|
46
|
+
gh pr review --approve | cat
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 3. Sync with Base Branch
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
BASE_BRANCH=$(node -e "try{const c=require('./.agents-toolkit.json');console.log(c.pr?.targetBranch||c.git?.defaultBranch||'main')}catch{console.log('main')}")
|
|
53
|
+
git fetch origin
|
|
54
|
+
git rebase "origin/${BASE_BRANCH}"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
If conflicts:
|
|
58
|
+
1. Resolve each conflict
|
|
59
|
+
2. Stage resolved files: `git add <file>`
|
|
60
|
+
3. Continue rebase: `git rebase --continue`
|
|
61
|
+
|
|
62
|
+
Push updated branch:
|
|
63
|
+
```bash
|
|
64
|
+
git push --force-with-lease
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 4. Final Validations
|
|
68
|
+
|
|
69
|
+
Run complete validation suite:
|
|
70
|
+
```bash
|
|
71
|
+
npm run lint --if-present
|
|
72
|
+
npm run type-check --if-present
|
|
73
|
+
if [ -f tsconfig.json ]; then npx tsc --noEmit; fi
|
|
74
|
+
npm run test --if-present
|
|
75
|
+
npm run build --if-present
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Verify:
|
|
79
|
+
- No regressions after rebase
|
|
80
|
+
- All tests still pass
|
|
81
|
+
- No new warnings
|
|
82
|
+
|
|
83
|
+
### 5. Merge Pull Request
|
|
84
|
+
|
|
85
|
+
**Recommended merge strategy**: Squash merge
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
gh pr merge --squash --delete-branch | cat
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Final commit message format**:
|
|
92
|
+
```
|
|
93
|
+
{Issue title} (#{pr-number})
|
|
94
|
+
|
|
95
|
+
- Key change 1
|
|
96
|
+
- Key change 2
|
|
97
|
+
- Key change 3
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 6. Post-Merge Tasks
|
|
101
|
+
|
|
102
|
+
After merge is complete:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Return to base branch and sync
|
|
106
|
+
git checkout "$BASE_BRANCH"
|
|
107
|
+
git pull origin "$BASE_BRANCH"
|
|
108
|
+
|
|
109
|
+
# Delete local branch (force if squash-merged)
|
|
110
|
+
git branch -D <branch-name>
|
|
111
|
+
|
|
112
|
+
# Clean up stale references
|
|
113
|
+
git remote prune origin
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 7. Close GitHub Issue
|
|
117
|
+
|
|
118
|
+
The issue closes automatically if the PR description contains `Closes #{issue-number}`.
|
|
119
|
+
If not, close manually:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
gh issue close {issue-number} --comment "Completed in PR #<pr-number>." | cat
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 8. Clean Up
|
|
126
|
+
|
|
127
|
+
- [ ] Delete temporary planning docs from `docs/` (if applicable)
|
|
128
|
+
- [ ] Ensure final documentation is complete
|
|
129
|
+
- [ ] Verify commit history is clean
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Prepare a GitHub release (version bump, changelog, tag/Release) — auto-detects WordPress vs Node projects and the correct tag-vs-Release flow
|
|
3
|
+
agent: agent
|
|
4
|
+
tools:
|
|
5
|
+
- run_in_terminal
|
|
6
|
+
- read_file
|
|
7
|
+
- replace_string_in_file
|
|
8
|
+
- create_file
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Prepare GitHub Release
|
|
12
|
+
|
|
13
|
+
Prepare a new version release and drive it through the **correct GitHub flow** for the current
|
|
14
|
+
project. This prompt is **project-agnostic**: it detects the ecosystem (WordPress plugin vs Node/npm
|
|
15
|
+
package) and analyzes the repo's GitHub Actions workflows to decide whether a **bare tag** or a full
|
|
16
|
+
**GitHub Release** is required.
|
|
17
|
+
|
|
18
|
+
## Prerequisites
|
|
19
|
+
- Reference: `.github/prompts/_partials/git-operations.md`
|
|
20
|
+
- Reference: `.github/prompts/_partials/release-wordpress.md` (WordPress projects)
|
|
21
|
+
- Reference: `.github/prompts/_partials/release-node.md` (Node/npm projects)
|
|
22
|
+
- `gh` CLI authenticated. Releases are a GitHub concept — if the `origin` remote is **not** GitHub
|
|
23
|
+
(e.g. Bitbucket), stop and tell the user this flow does not apply.
|
|
24
|
+
|
|
25
|
+
## Inputs
|
|
26
|
+
|
|
27
|
+
Ask the user:
|
|
28
|
+
1. **Version type** — `patch`, `minor`, or `major`? (default: patch). Suggest one from the
|
|
29
|
+
`[Unreleased]` changelog content: new features → `minor`, fixes only → `patch`, breaking → `major`.
|
|
30
|
+
2. **Changelog entry** — reuse the existing `[Unreleased]` section if present, otherwise offer to
|
|
31
|
+
generate one from `git log` since the last tag.
|
|
32
|
+
|
|
33
|
+
## Steps
|
|
34
|
+
|
|
35
|
+
### 1. Detect host and project type
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
git remote get-url origin # must be github.com — else stop
|
|
39
|
+
git fetch --tags --quiet
|
|
40
|
+
git tag --sort=-v:refname | head -1 # latest tag (current released version)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Detect the **project type** and follow the matching partial:
|
|
44
|
+
|
|
45
|
+
| Signal | Project type | Use partial |
|
|
46
|
+
|--------|--------------|-------------|
|
|
47
|
+
| `*.php` with a `* Version:` plugin header | WordPress plugin | `release-wordpress.md` |
|
|
48
|
+
| `package.json` (no WP header) | Node / npm | `release-node.md` |
|
|
49
|
+
| neither | Generic | inline fallback: bump a `VERSION` file / changelog only |
|
|
50
|
+
|
|
51
|
+
### 2. Determine the new version
|
|
52
|
+
|
|
53
|
+
- Read the **current** version from the source of truth named in the matching partial (not just the
|
|
54
|
+
git tag — they can drift).
|
|
55
|
+
- Apply the bump type to compute `X.Y.Z`.
|
|
56
|
+
- **NEVER reuse an existing tag** — tags are immutable. If `vX.Y.Z` already exists, bump again.
|
|
57
|
+
|
|
58
|
+
### 3. Run quality checks (per partial)
|
|
59
|
+
|
|
60
|
+
Run the ecosystem's checks from the matching partial (WordPress: phpcs/phpstan; Node: `npm test` +
|
|
61
|
+
lint/type-check/build). **Do not proceed if any check fails.**
|
|
62
|
+
|
|
63
|
+
### 4. Bump the version (per partial)
|
|
64
|
+
|
|
65
|
+
Update every version file listed in the matching partial (WordPress: plugin header / `readme.txt` /
|
|
66
|
+
`composer.json`; Node: `package.json` + `package-lock.json`).
|
|
67
|
+
|
|
68
|
+
### 5. Update CHANGELOG.md
|
|
69
|
+
|
|
70
|
+
Promote the `[Unreleased]` section to the new version, keeping a Keep-a-Changelog structure:
|
|
71
|
+
|
|
72
|
+
```markdown
|
|
73
|
+
## [X.Y.Z] - YYYY-MM-DD
|
|
74
|
+
|
|
75
|
+
### Added/Changed/Fixed
|
|
76
|
+
- ...
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
If there is no `[Unreleased]` section, add a new `## [X.Y.Z]` block at the top with the changes.
|
|
80
|
+
|
|
81
|
+
### 6. Create release branch, commit, and PR
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
BASE_BRANCH=$(node -e "try{const c=require('./.agents-toolkit.json');console.log(c.pr?.targetBranch||c.git?.defaultBranch||'main')}catch{console.log('main')}")
|
|
85
|
+
git checkout -b release/vX.Y.Z
|
|
86
|
+
git add -A
|
|
87
|
+
git commit -m "chore: bump version to X.Y.Z"
|
|
88
|
+
git push -u origin release/vX.Y.Z
|
|
89
|
+
gh pr create --base "$BASE_BRANCH" --title "Release vX.Y.Z" --body "## Changes
|
|
90
|
+
|
|
91
|
+
- changelog entry" | cat
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 7. Analyze workflows → decide tag-only vs GitHub Release
|
|
95
|
+
|
|
96
|
+
**This is the critical step.** Read the repo's workflows and tell the user exactly what to do after
|
|
97
|
+
the release PR merges:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
ls .github/workflows/ 2>/dev/null
|
|
101
|
+
# inspect the `on:` triggers and publish/build steps of each workflow
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Decide from the triggers:
|
|
105
|
+
|
|
106
|
+
| Workflow trigger | Post-merge action | Why |
|
|
107
|
+
|------------------|-------------------|-----|
|
|
108
|
+
| `on: release: [created\|published]` | **Create a GitHub Release** (`gh release create vX.Y.Z`) | A bare tag does **not** fire `release` workflows — publishing/build only runs on the Release event |
|
|
109
|
+
| `on: push: tags: ['v*']` | **Push the tag** (`git push origin vX.Y.Z`) — Release optional | The tag push alone triggers the workflow |
|
|
110
|
+
| neither / no workflow | **Push the tag** for history; optionally `gh release create` for visibility | No automation depends on it |
|
|
111
|
+
|
|
112
|
+
Also scan the workflow **steps** and report what the release produces (e.g. `npm publish`, plugin
|
|
113
|
+
ZIP artifact, Docker image) so the user knows what will happen.
|
|
114
|
+
|
|
115
|
+
### 8. Post-merge instructions (tailored to step 7)
|
|
116
|
+
|
|
117
|
+
Give the exact commands for the detected flow, e.g.:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
git checkout "$BASE_BRANCH" && git pull
|
|
121
|
+
git tag vX.Y.Z
|
|
122
|
+
git push origin vX.Y.Z
|
|
123
|
+
# If a `release:`-triggered workflow exists, ALSO create the Release so it fires:
|
|
124
|
+
gh release create vX.Y.Z --generate-notes
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Important
|
|
128
|
+
|
|
129
|
+
- **NEVER reuse an existing tag** — tags are immutable. If a tag exists, bump to the next version.
|
|
130
|
+
- The `release-management` skill has full documentation for WordPress plugins — use it for troubleshooting.
|
|
131
|
+
- Some projects use `master` instead of `main` — always verify the default branch with
|
|
132
|
+
`gh repo view --json defaultBranchRef | cat`.
|
|
133
|
+
- A bare tag and a GitHub Release are **not** interchangeable: many publish/build workflows only run
|
|
134
|
+
on the `release` event. Always complete step 7 before telling the user the release is done.
|
|
@@ -56,6 +56,18 @@ For each changed file:
|
|
|
56
56
|
- [ ] Efficient data structures
|
|
57
57
|
- [ ] No memory leaks potential
|
|
58
58
|
|
|
59
|
+
### 5. Caching & Data Fetching (Next.js)
|
|
60
|
+
|
|
61
|
+
> See `.github/instructions/caching.instructions.md`. Caching is decided by **read-vs-mutation**, not the HTTP method.
|
|
62
|
+
|
|
63
|
+
- [ ] Reads cache regardless of method — a `POST` read (e.g. `geo-search`) sets `next: { revalidate, tags }` (NOT `revalidate: 0`)
|
|
64
|
+
- [ ] No `next: method === "GET" ? {...} : { revalidate: 0 }` gating (leaves POST reads uncached → route turns dynamic, `private, no-store`)
|
|
65
|
+
- [ ] Mutations (`submit`/lead/`PUT`/`DELETE`) use `cache: "no-store"` and are never cached
|
|
66
|
+
- [ ] `tags` always paired with a `revalidate` duration (no bare `next: { tags }`)
|
|
67
|
+
- [ ] Cacheable routes export `revalidate`; not added to form/personalized routes
|
|
68
|
+
- [ ] `React.cache()` not used as a substitute for cross-request caching
|
|
69
|
+
- [ ] On-demand revalidation invalidates both Next.js (`revalidateTag`/`revalidatePath`) and the CDN
|
|
70
|
+
|
|
59
71
|
## Output
|
|
60
72
|
|
|
61
73
|
### Review Summary
|
|
@@ -8,18 +8,39 @@ Skills are markdown files with YAML frontmatter that provide domain-specific gui
|
|
|
8
8
|
|
|
9
9
|
## Structure
|
|
10
10
|
|
|
11
|
-
Each skill lives in its own folder with a `SKILL.md` file
|
|
11
|
+
Each skill lives in its own folder with a `SKILL.md` file. Following the
|
|
12
|
+
[`npx skills`](https://github.com/vercel-labs/skills) standard, the real files
|
|
13
|
+
are installed **once** into a canonical `.agents/skills/` store, and each agent's
|
|
14
|
+
skills directory contains symlinks to it (single source of truth):
|
|
12
15
|
|
|
13
16
|
```
|
|
14
|
-
.
|
|
17
|
+
.agents/skills/ # canonical store (real files)
|
|
18
|
+
├── ai-seo-optimization/
|
|
19
|
+
│ └── SKILL.md
|
|
15
20
|
├── component-architecture/
|
|
16
21
|
│ └── SKILL.md
|
|
22
|
+
├── create-component/
|
|
23
|
+
│ └── SKILL.md
|
|
17
24
|
├── domain-driven-design/
|
|
18
25
|
│ └── SKILL.md
|
|
26
|
+
├── plugin-creation/
|
|
27
|
+
│ └── SKILL.md
|
|
28
|
+
├── quality-checks/
|
|
29
|
+
│ └── SKILL.md
|
|
30
|
+
├── release-management/
|
|
31
|
+
│ └── SKILL.md
|
|
32
|
+
├── testing/
|
|
33
|
+
│ └── SKILL.md
|
|
19
34
|
└── testing-patterns/
|
|
20
35
|
└── SKILL.md
|
|
36
|
+
|
|
37
|
+
.github/skills/ → symlinks to ../../.agents/skills/* (Copilot, Codex)
|
|
38
|
+
.claude/skills/ → symlinks to ../../.agents/skills/* (Claude Code, read natively)
|
|
21
39
|
```
|
|
22
40
|
|
|
41
|
+
Use `--copy` at install time to materialize real copies instead of symlinks
|
|
42
|
+
(symlinks also fall back to copies automatically on systems that don't support them).
|
|
43
|
+
|
|
23
44
|
## Frontmatter Format
|
|
24
45
|
|
|
25
46
|
```yaml
|
|
@@ -47,7 +68,8 @@ Skills are automatically picked up by agents when relevant to your question. You
|
|
|
47
68
|
|
|
48
69
|
## Creating Custom Skills
|
|
49
70
|
|
|
50
|
-
1. Create a folder: `.
|
|
71
|
+
1. Create a folder: `.agents/skills/your-skill-name/` (canonical store)
|
|
51
72
|
2. Create `SKILL.md` with frontmatter
|
|
73
|
+
3. Run `npx @silverassist/agents-toolkit install --skills-only` to symlink it into `.github/skills/` and `.claude/skills/`
|
|
52
74
|
3. Document patterns, examples, and conventions
|
|
53
75
|
4. Include ✅ CORRECT and ❌ INCORRECT examples
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nextjs-caching
|
|
3
|
+
description: Caching strategy for Next.js (App Router) frontends consuming the CCDS API and headless WordPress. Use when asked about caching, ISR, "revalidate", "stale data", "page not cached", "private / no-store header", POST requests not caching, CloudFront/CDN invalidation, `next: { revalidate, tags }`, or when a page unexpectedly renders dynamically.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Next.js Caching Skill
|
|
7
|
+
|
|
8
|
+
Canonical caching strategy for Silver Side Next.js (App Router) frontends. Use it when touching data
|
|
9
|
+
fetching, route segment configs, the asset/page proxy, image config, or the on-demand revalidate
|
|
10
|
+
route — and when diagnosing "why isn't this page cached / why is it serving stale".
|
|
11
|
+
|
|
12
|
+
> Companion: `caching.instructions.md` (the short, auto-applied rules). This skill is the deep
|
|
13
|
+
> reference and decision guide. If a project keeps a `docs/CACHING.md`, that is its project-specific
|
|
14
|
+
> extension (endpoints, CloudFront IDs, per-route tiers) and is never overwritten by the toolkit.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Core principle
|
|
19
|
+
|
|
20
|
+
**Cache by intent (read vs. mutation), never by the HTTP method.** Some reads must use `POST` because
|
|
21
|
+
they take a body (e.g. CCDS `geo-search`). They still have to cache like a `GET`. Mutations must never
|
|
22
|
+
cache. Getting this wrong silently turns a static page into a per-request dynamic render.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## The caching layers (independent)
|
|
27
|
+
|
|
28
|
+
Fixing one does **not** fix the others.
|
|
29
|
+
|
|
30
|
+
| # | Layer | Where | Purpose |
|
|
31
|
+
|---|-------|-------|---------|
|
|
32
|
+
| 1 | ISR / page cache | `export const revalidate` per route | Time-based regeneration of rendered pages |
|
|
33
|
+
| 2 | Data-fetch cache | `next: { revalidate, tags }` on each `fetch` | Cross-request caching of CCDS/WP responses |
|
|
34
|
+
| 3 | Request dedup | `React.cache()` around client fns | Collapse duplicate calls within one render |
|
|
35
|
+
| 4 | Asset proxy | `src/app/assets/[...path]/route.ts` | Long-lived `Cache-Control` on WP images/CSS/fonts |
|
|
36
|
+
| 5 | Edge / CDN | `src/proxy.ts` + CloudFront | `s-maxage` at the edge + on-demand invalidation |
|
|
37
|
+
|
|
38
|
+
**Layer 2 is the most commonly broken.** A page can be ISR-enabled (layer 1) yet still hit origin on
|
|
39
|
+
every request if its data fetches (layer 2) are not cached — and an uncached fetch also forces the
|
|
40
|
+
whole route into **dynamic rendering**, which emits `cache-control: private, no-cache, no-store`.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## How Next.js 16 actually decides (the rule behind the rule)
|
|
45
|
+
|
|
46
|
+
From `next/dist/server/lib/patch-fetch.js`:
|
|
47
|
+
|
|
48
|
+
- `fetch` is **not cached by default**. A non-`GET` method is "uncacheable" **only when no explicit
|
|
49
|
+
cache config is provided**. Concretely, a POST is auto-no-cached only when it has no `next`/`cache`
|
|
50
|
+
options *and* the segment `revalidate` is `0`.
|
|
51
|
+
- Therefore **a `POST` IS cached cross-request when you pass an explicit `next: { revalidate }`** (and
|
|
52
|
+
the segment isn't `revalidate: 0`). This is why the WordPress GraphQL POST caches fine.
|
|
53
|
+
- The **request body is part of the cache key** (`incremental-cache` → `generateCacheKey` hashes
|
|
54
|
+
`init.body`), so different filter bodies cache independently and never collide.
|
|
55
|
+
|
|
56
|
+
**Implication:** you do **not** need `unstable_cache` or a proxy header override to cache POST reads.
|
|
57
|
+
Just send `next: { revalidate, tags }`.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Canonical API client (read vs. mutation)
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
interface FetchOptions {
|
|
65
|
+
endpoint: string;
|
|
66
|
+
method?: "GET" | "POST" | "PUT" | "DELETE";
|
|
67
|
+
body?: object | null;
|
|
68
|
+
revalidateTag?: string;
|
|
69
|
+
revalidate?: number;
|
|
70
|
+
/** Writes are never cached. Reads (default) cache regardless of method. */
|
|
71
|
+
mutation?: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function fetchData<T>({
|
|
75
|
+
endpoint,
|
|
76
|
+
method = "GET",
|
|
77
|
+
body = null,
|
|
78
|
+
revalidateTag,
|
|
79
|
+
revalidate = 86400, // 24h time-based fallback; pair with tags for surgical invalidation
|
|
80
|
+
mutation = false,
|
|
81
|
+
}: FetchOptions): Promise<T | null> {
|
|
82
|
+
const isMutation = mutation || method === "PUT" || method === "DELETE";
|
|
83
|
+
|
|
84
|
+
const requestOptions: RequestInit = {
|
|
85
|
+
method,
|
|
86
|
+
// Reads (GET or POST) cache cross-request via `next`; mutations opt out.
|
|
87
|
+
...(isMutation
|
|
88
|
+
? { cache: "no-store" as RequestCache }
|
|
89
|
+
: { next: { revalidate, tags: revalidateTag ? [revalidateTag] : [] } }),
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
if ((method === "POST" || method === "PUT") && body) {
|
|
93
|
+
requestOptions.body = JSON.stringify(body);
|
|
94
|
+
}
|
|
95
|
+
// ...fetch + error handling...
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Mark every write explicitly:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
await fetchData({ endpoint: "community/submit-review", method: "POST", body, mutation: true });
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
WordPress GraphQL client (POST read — must carry `next`):
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
await fetch(WP_API_URL, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
body: JSON.stringify({ query, variables }),
|
|
111
|
+
next: { revalidate, ...(tags && { tags }) }, // a POST IS cacheable WITH next options
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## ISR revalidate tiers
|
|
118
|
+
|
|
119
|
+
| Route type | `export const revalidate` |
|
|
120
|
+
|------------|---------------------------|
|
|
121
|
+
| Listing / index | `2592000` (30d) — webhook handles freshness |
|
|
122
|
+
| Detail / city / community (`[slug]`) | `86400` (24h) |
|
|
123
|
+
| State / advisor landing | `604800` (7d) |
|
|
124
|
+
| WP catch-all (`[[...uri]]`) | `604800` (7d) |
|
|
125
|
+
|
|
126
|
+
- Every cacheable route exports `revalidate`. Do **not** add it to form/personalized routes.
|
|
127
|
+
- Prefer `export const revalidate` over `dynamic = "force-static"`: a failed ISR revalidation then
|
|
128
|
+
preserves the last good cache instead of overwriting it with a broken page. Pair with a client that
|
|
129
|
+
**throws on 5xx at runtime** (so ISR keeps the previous version) but returns an error during the
|
|
130
|
+
build phase (so `generateStaticParams` can skip a bad entry without failing the build).
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## On-demand revalidation (dual invalidation)
|
|
135
|
+
|
|
136
|
+
`/api/revalidate` must invalidate **both** layers in one request, or the CDN serves stale until its
|
|
137
|
+
own TTL expires:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
revalidatePath(path, "page"); // or "layout"
|
|
141
|
+
revalidateTag(tag); // granular per-state / per-city tags
|
|
142
|
+
if (invalidateCDN) await invalidateCloudFrontPaths([path]);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Anti-patterns
|
|
148
|
+
|
|
149
|
+
- ❌ `next: method === "GET" ? {...} : { revalidate: 0 }` — leaves POST reads uncached → the route
|
|
150
|
+
renders dynamically (`private, no-store`). This is the exact regression that broke city pages.
|
|
151
|
+
- ❌ Bare `fetch(url, { method: "POST", body })` for a read — refetched from origin every render.
|
|
152
|
+
- ❌ `next: { tags }` with no `revalidate` — holds stale data or doesn't cache at runtime.
|
|
153
|
+
- ❌ Caching a mutation (`submit`, lead, `PUT`/`DELETE`/`PATCH`).
|
|
154
|
+
- ❌ Treating `React.cache()` as cross-request caching (it's request-scoped dedup only).
|
|
155
|
+
- ❌ `dynamic = "force-static"` on a page whose revalidation can fail (caches a broken page).
|
|
156
|
+
- ❌ Mixing `cacheComponents: true` (`"use cache"`) with route-segment `export const revalidate` —
|
|
157
|
+
that is a separate, deliberate migration; do not introduce it ad hoc.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Diagnosing "page is not cached"
|
|
162
|
+
|
|
163
|
+
1. **Check the response header.** `cache-control: private, no-cache, no-store` ⇒ the route is rendering
|
|
164
|
+
dynamically. Something opted it into dynamic rendering.
|
|
165
|
+
2. **Find the dynamic trigger.** Usually an uncached fetch (a POST read without `next`, or
|
|
166
|
+
`revalidate: 0`), or a request-time API (`cookies()`, `headers()`, `searchParams`).
|
|
167
|
+
3. **Fix layer 2 first.** Give read fetches `next: { revalidate, tags }`; mark mutations `no-store`.
|
|
168
|
+
4. **Confirm the route exports `revalidate`.**
|
|
169
|
+
5. **Reconcile CDN vs. ISR TTLs.** If ISR is 24h but CDN `s-maxage` is shorter, an ISR revalidation
|
|
170
|
+
should trigger a CloudFront invalidation for that path.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Verification
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# A POST-read page (e.g. a city) must be publicly cacheable:
|
|
178
|
+
curl -sI https://<host>/<care-type>/<state>/<city> | grep -i cache-control
|
|
179
|
+
# expect: cache-control: public, ... (NOT private/no-store)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
In the build output, `●` (or "Static"/"ISR") means prerendered; `ƒ` ("Dynamic") means it renders per
|
|
183
|
+
request — a POST-read page should be the former. `next.config` `logging.fetches.fullUrl: true` shows
|
|
184
|
+
per-fetch cache decisions in dev.
|