agents-united 0.12.0 → 0.14.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/README.md +466 -411
- package/dist/cli.d.ts +26 -1
- package/dist/cli.js +3805 -891
- package/dist/cli.js.map +1 -1
- package/package.json +3 -3
- package/registry/agents/orchestrator-business.md +50 -16
- package/registry/agents/orchestrator-design.md +50 -12
- package/registry/agents/orchestrator-digital-agency.md +32 -27
- package/registry/agents/orchestrator-engineering.md +46 -20
- package/registry/agents/orchestrator-marketing.md +43 -13
- package/registry/agents/orchestrator-research.md +43 -17
- package/registry/agents/orchestrator-security.md +44 -16
- package/registry/agents/orchestrator-system-architecture.md +43 -11
- package/registry/agents/orchestrator-universal.md +43 -0
- package/registry/agents/subagent-accessibility-lead.md +57 -2
- package/registry/agents/subagent-ai-model-architect.md +55 -1
- package/registry/agents/subagent-android-architect.md +61 -2
- package/registry/agents/subagent-appsec-penetration-tester.md +34 -0
- package/registry/agents/subagent-backend-architect.md +78 -229
- package/registry/agents/subagent-business-panel-experts.md +13 -0
- package/registry/agents/subagent-cloud-infrastructure-architect.md +30 -0
- package/registry/agents/subagent-cloud-security-architect.md +14 -0
- package/registry/agents/subagent-code-reviewer.md +54 -11
- package/registry/agents/subagent-compliance-grc-specialist.md +22 -1
- package/registry/agents/subagent-cross-platform-specialist.md +62 -2
- package/registry/agents/subagent-data-engineer.md +61 -2
- package/registry/agents/subagent-database-administrator.md +14 -0
- package/registry/agents/subagent-deep-research.md +12 -0
- package/registry/agents/subagent-design-ops-lead.md +13 -0
- package/registry/agents/subagent-design-researcher.md +13 -0
- package/registry/agents/subagent-design-systems-architect.md +13 -0
- package/registry/agents/subagent-designer-toolkit-expert.md +12 -0
- package/registry/agents/subagent-devops-engineer.md +75 -287
- package/registry/agents/subagent-distributed-systems-architect.md +62 -2
- package/registry/agents/subagent-e2e-tester.md +59 -2
- package/registry/agents/subagent-financial-analyst.md +13 -0
- package/registry/agents/subagent-finops-cost-engineer.md +12 -0
- package/registry/agents/subagent-frontend-architect.md +73 -231
- package/registry/agents/subagent-interaction-designer.md +13 -0
- package/registry/agents/subagent-ios-architect.md +61 -2
- package/registry/agents/subagent-legal-contract-analyst.md +13 -0
- package/registry/agents/subagent-lifecycle-email-specialist.md +13 -0
- package/registry/agents/subagent-literature-patent-analyst.md +13 -0
- package/registry/agents/subagent-market-intelligence-analyst.md +13 -0
- package/registry/agents/subagent-marketing-campaign-specialist.md +21 -1
- package/registry/agents/subagent-marketing-content-strategist.md +21 -1
- package/registry/agents/subagent-marketing-conversion-specialist.md +21 -1
- package/registry/agents/subagent-marketing-creative-designer.md +21 -1
- package/registry/agents/subagent-marketing-growth-strategist.md +21 -1
- package/registry/agents/subagent-ml-platform-engineer.md +55 -1
- package/registry/agents/subagent-operations-strategist.md +13 -0
- package/registry/agents/subagent-paid-acquisition-specialist.md +13 -0
- package/registry/agents/subagent-plg-strategist.md +13 -0
- package/registry/agents/subagent-prototype-tester.md +12 -0
- package/registry/agents/subagent-qa-automation-lead.md +50 -78
- package/registry/agents/subagent-repo-index.md +44 -1
- package/registry/agents/subagent-security-engineer.md +45 -0
- package/registry/agents/subagent-seo-specialist.md +21 -2
- package/registry/agents/subagent-socratic-mentor.md +13 -0
- package/registry/agents/subagent-statistical-analyst.md +12 -0
- package/registry/agents/subagent-sysops-sre-lead.md +14 -0
- package/registry/agents/subagent-system-architect.md +14 -0
- package/registry/agents/subagent-ui-designer.md +13 -0
- package/registry/agents/subagent-ux-strategist.md +13 -0
- package/registry/bundles.json +364 -34
- package/registry/core/orchestrator-engineering.core.md +34 -0
- package/registry/core/subagent-backend-architect.core.md +71 -0
- package/registry/core/subagent-code-reviewer.core.md +84 -0
- package/registry/core/subagent-frontend-architect.core.md +57 -0
- package/registry/core/subagent-repo-index.core.md +74 -0
- package/registry/profiles/claude@2.1.271.json +48 -0
- package/registry/realizations/claude/orchestrator-engineering.json +14 -0
- package/registry/realizations/claude/subagent-backend-architect.json +16 -0
- package/registry/realizations/claude/subagent-code-reviewer.json +15 -0
- package/registry/realizations/claude/subagent-frontend-architect.json +15 -0
- package/registry/realizations/claude/subagent-repo-index.json +15 -0
- package/registry/skills/ai-prototype-refactoring/SKILL.md +24 -0
- package/registry/skills/ai-prototype-refactoring/references/backend-frontend-devops-exemplars.md +104 -0
- package/registry/skills/azure-infrastructure-bicep/SKILL.md +24 -0
- package/registry/skills/azure-infrastructure-bicep/references/backend-frontend-devops-exemplars.md +173 -0
- package/registry/skills/banner-design/SKILL.md +170 -0
- package/registry/skills/banner-design/references/banner-sizes-and-styles.md +66 -0
- package/registry/skills/brand-identity/SKILL.md +212 -0
- package/registry/skills/brand-identity/references/approval-checklist.md +17 -0
- package/registry/skills/brand-identity/references/asset-organization.md +30 -0
- package/registry/skills/brand-identity/references/brand-guideline-template.md +57 -0
- package/registry/skills/brand-identity/references/color-palette-management.md +37 -0
- package/registry/skills/brand-identity/references/consistency-checklist.md +26 -0
- package/registry/skills/brand-identity/references/logo-usage-rules.md +23 -0
- package/registry/skills/brand-identity/references/messaging-framework.md +22 -0
- package/registry/skills/brand-identity/references/typography-specifications.md +29 -0
- package/registry/skills/brand-identity/references/visual-identity.md +23 -0
- package/registry/skills/brand-identity/references/voice-framework.md +27 -0
- package/registry/skills/brand-identity/scripts/extract-colors.mjs +104 -0
- package/registry/skills/brand-identity/scripts/inject-brand-context.mjs +112 -0
- package/registry/skills/brand-identity/scripts/lib/confirm-gate.mjs +28 -0
- package/registry/skills/brand-identity/scripts/sync-brand-to-tokens.mjs +205 -0
- package/registry/skills/brand-identity/scripts/validate-asset.mjs +137 -0
- package/registry/skills/clickhouse-architecture-advisor/SKILL.md +202 -0
- package/registry/skills/codeql-scanning/LICENSE +427 -0
- package/registry/skills/codeql-scanning/NOTICE.md +31 -0
- package/registry/skills/codeql-scanning/SKILL.md +136 -0
- package/registry/skills/codeql-scanning/references/build-fixes.md +105 -0
- package/registry/skills/codeql-scanning/references/diagnostic-query-templates.md +325 -0
- package/registry/skills/codeql-scanning/references/extension-yaml-format.md +202 -0
- package/registry/skills/codeql-scanning/references/important-only-suite.md +119 -0
- package/registry/skills/codeql-scanning/references/language-details.md +220 -0
- package/registry/skills/codeql-scanning/references/macos-arm64e-workaround.md +201 -0
- package/registry/skills/codeql-scanning/references/performance-tuning.md +81 -0
- package/registry/skills/codeql-scanning/references/quality-assessment.md +197 -0
- package/registry/skills/codeql-scanning/references/ruleset-catalog.md +68 -0
- package/registry/skills/codeql-scanning/references/run-all-suite.md +106 -0
- package/registry/skills/codeql-scanning/references/sarif-processing.md +82 -0
- package/registry/skills/codeql-scanning/references/threat-models.md +36 -0
- package/registry/skills/codeql-scanning/references/upstream-method.md +288 -0
- package/registry/skills/codeql-scanning/references/workflow-build-database.md +338 -0
- package/registry/skills/codeql-scanning/references/workflow-create-data-extensions.md +275 -0
- package/registry/skills/codeql-scanning/references/workflow-run-analysis.md +327 -0
- package/registry/skills/codeql-scanning/scripts/build_log.py +91 -0
- package/registry/skills/codeql-scanning/scripts/check_db_quality.py +366 -0
- package/registry/skills/codeql-scanning/scripts/find_databases.py +90 -0
- package/registry/skills/codeql-scanning/scripts/generate_suite.py +101 -0
- package/registry/skills/codeql-scanning/scripts/verify_query_suite.py +86 -0
- package/registry/skills/edge-security-audit/SKILL.md +225 -0
- package/registry/skills/expo-cicd-workflows/SKILL.md +228 -0
- package/registry/skills/hf-managed-jobs/SKILL.md +198 -0
- package/registry/skills/hf-model-training/SKILL.md +204 -0
- package/registry/skills/mutation-testing/LICENSE +427 -0
- package/registry/skills/mutation-testing/NOTICE.md +31 -0
- package/registry/skills/mutation-testing/SKILL.md +122 -0
- package/registry/skills/mutation-testing/references/blockchain-patterns.md +103 -0
- package/registry/skills/mutation-testing/references/equivalent-mutants.md +101 -0
- package/registry/skills/mutation-testing/references/input-formats.md +84 -0
- package/registry/skills/mutation-testing/references/optimization-strategies.md +327 -0
- package/registry/skills/mutation-testing/references/report-template.md +140 -0
- package/registry/skills/mutation-testing/references/severity-classification.md +132 -0
- package/registry/skills/mutation-testing/references/upstream-method.md +105 -0
- package/registry/skills/mutation-testing/references/workflow-analyzing-results.md +123 -0
- package/registry/skills/mutation-testing/references/workflow-bug-hunter.md +139 -0
- package/registry/skills/mutation-testing/references/workflow-configuration.md +338 -0
- package/registry/skills/playwright-best-practices/SKILL.md +5 -0
- package/registry/skills/playwright-best-practices/references/conversion-funnel-test.md +79 -0
- package/registry/skills/postgres-best-practices/SKILL.md +205 -0
- package/registry/skills/property-based-testing/LICENSE +427 -0
- package/registry/skills/property-based-testing/NOTICE.md +31 -0
- package/registry/skills/property-based-testing/SKILL.md +119 -0
- package/registry/skills/property-based-testing/references/generating.md +82 -0
- package/registry/skills/property-based-testing/references/interpreting-failures.md +83 -0
- package/registry/skills/property-based-testing/references/libraries.md +80 -0
- package/registry/skills/property-based-testing/references/refactoring.md +123 -0
- package/registry/skills/property-based-testing/references/reviewing.md +81 -0
- package/registry/skills/property-based-testing/references/upstream-method.md +65 -0
- package/registry/skills/react-best-practices/SKILL.md +5 -0
- package/registry/skills/react-best-practices/references/nextjs-server-actions-and-isr.md +85 -0
- package/registry/skills/sarif-triage/LICENSE +427 -0
- package/registry/skills/sarif-triage/NOTICE.md +31 -0
- package/registry/skills/sarif-triage/SKILL.md +117 -0
- package/registry/skills/sarif-triage/references/jq-queries.md +210 -0
- package/registry/skills/sarif-triage/references/upstream-method.md +490 -0
- package/registry/skills/sarif-triage/scripts/sarif_helpers.py +400 -0
- package/registry/skills/security-best-practices/LICENSE +201 -0
- package/registry/skills/security-best-practices/NOTICE.md +30 -0
- package/registry/skills/security-best-practices/SKILL.md +116 -0
- package/registry/skills/security-best-practices/references/golang-general-backend-security.md +829 -0
- package/registry/skills/security-best-practices/references/javascript-express-web-server-security.md +1161 -0
- package/registry/skills/security-best-practices/references/javascript-general-web-frontend-security.md +750 -0
- package/registry/skills/security-best-practices/references/javascript-jquery-web-frontend-security.md +681 -0
- package/registry/skills/security-best-practices/references/javascript-typescript-nextjs-web-server-security.md +1147 -0
- package/registry/skills/security-best-practices/references/javascript-typescript-react-web-frontend-security.md +993 -0
- package/registry/skills/security-best-practices/references/javascript-typescript-vue-web-frontend-security.md +794 -0
- package/registry/skills/security-best-practices/references/python-django-web-server-security.md +885 -0
- package/registry/skills/security-best-practices/references/python-fastapi-web-server-security.md +1039 -0
- package/registry/skills/security-best-practices/references/python-flask-web-server-security.md +708 -0
- package/registry/skills/security-best-practices/references/upstream-method.md +86 -0
- package/registry/skills/security-diff-review/LICENSE +427 -0
- package/registry/skills/security-diff-review/NOTICE.md +31 -0
- package/registry/skills/security-diff-review/SKILL.md +125 -0
- package/registry/skills/security-diff-review/references/adversarial.md +206 -0
- package/registry/skills/security-diff-review/references/methodology.md +237 -0
- package/registry/skills/security-diff-review/references/patterns.md +303 -0
- package/registry/skills/security-diff-review/references/reporting.md +372 -0
- package/registry/skills/security-diff-review/references/upstream-method.md +222 -0
- package/registry/skills/semgrep-scanning/LICENSE +427 -0
- package/registry/skills/semgrep-scanning/NOTICE.md +31 -0
- package/registry/skills/semgrep-scanning/SKILL.md +139 -0
- package/registry/skills/semgrep-scanning/references/rulesets.md +165 -0
- package/registry/skills/semgrep-scanning/references/scan-modes.md +153 -0
- package/registry/skills/semgrep-scanning/references/upstream-method.md +262 -0
- package/registry/skills/semgrep-scanning/references/workflow-scan-workflow.md +467 -0
- package/registry/skills/semgrep-scanning/scripts/merge_sarif.py +339 -0
- package/registry/skills/semgrep-scanning/scripts/run_scans.py +482 -0
- package/registry/skills/sentry-incident-triage/SKILL.md +193 -0
- package/registry/skills/supabase-backend-architecture/SKILL.md +24 -0
- package/registry/skills/supabase-backend-architecture/references/backend-frontend-devops-exemplars.md +140 -0
- package/registry/skills/supply-chain-risk-audit/LICENSE +427 -0
- package/registry/skills/supply-chain-risk-audit/NOTICE.md +31 -0
- package/registry/skills/supply-chain-risk-audit/SKILL.md +119 -0
- package/registry/skills/supply-chain-risk-audit/references/upstream-method.md +123 -0
- package/registry/skills/supply-chain-risk-audit/scripts/collect.py +1568 -0
- package/registry/skills/supply-chain-risk-audit/scripts/model.py +402 -0
- package/registry/skills/supply-chain-risk-audit/scripts/pyproject.toml +20 -0
- package/registry/skills/supply-chain-risk-audit/scripts/render.py +809 -0
- package/registry/skills/supply-chain-risk-audit/scripts/sources.py +683 -0
- package/registry/skills/supply-chain-risk-audit/scripts/uv.lock +12 -0
- package/registry/skills/terraform-style-guide/LICENSE +373 -0
- package/registry/skills/terraform-style-guide/NOTICE.md +31 -0
- package/registry/skills/terraform-style-guide/SKILL.md +126 -0
- package/registry/skills/terraform-style-guide/references/security.md +167 -0
- package/registry/skills/terraform-style-guide/references/style-conventions.md +312 -0
- package/registry/skills/terraform-test-patterns/LICENSE +373 -0
- package/registry/skills/terraform-test-patterns/NOTICE.md +31 -0
- package/registry/skills/terraform-test-patterns/SKILL.md +133 -0
- package/registry/skills/terraform-test-patterns/references/ci-cd.md +83 -0
- package/registry/skills/terraform-test-patterns/references/examples.md +317 -0
- package/registry/skills/terraform-test-patterns/references/mock-providers.md +174 -0
- package/registry/skills/terraform-test-patterns/references/test-syntax.md +445 -0
- package/registry/skills/threat-modeling/LICENSE +201 -0
- package/registry/skills/threat-modeling/NOTICE.md +31 -0
- package/registry/skills/threat-modeling/SKILL.md +136 -0
- package/registry/skills/threat-modeling/references/prompt-template.md +258 -0
- package/registry/skills/threat-modeling/references/security-controls-and-assets.md +35 -0
- package/registry/skills/threat-modeling/references/upstream-method.md +81 -0
- package/registry/skills/turso-distributed-sqlite/SKILL.md +24 -0
- package/registry/skills/turso-distributed-sqlite/references/backend-frontend-devops-exemplars.md +92 -0
- package/registry/skills/ux-writing/SKILL.md +183 -0
- package/registry/skills/ux-writing/references/interface-patterns.md +41 -0
- package/registry/skills/ux-writing/references/localization-accessibility.md +31 -0
- package/registry/skills/ux-writing/references/research-measurement.md +27 -0
- package/registry/skills/ux-writing/references/source-runtime-workflow.md +31 -0
- package/registry/skills/ux-writing/references/voice-tone-terminology.md +26 -0
- package/registry/skills/variant-analysis/LICENSE +427 -0
- package/registry/skills/variant-analysis/NOTICE.md +31 -0
- package/registry/skills/variant-analysis/SKILL.md +119 -0
- package/registry/skills/variant-analysis/assets/codeql/cpp.ql +119 -0
- package/registry/skills/variant-analysis/assets/codeql/go.ql +69 -0
- package/registry/skills/variant-analysis/assets/codeql/java.ql +71 -0
- package/registry/skills/variant-analysis/assets/codeql/javascript.ql +63 -0
- package/registry/skills/variant-analysis/assets/codeql/python.ql +80 -0
- package/registry/skills/variant-analysis/assets/semgrep/cpp.yaml +98 -0
- package/registry/skills/variant-analysis/assets/semgrep/go.yaml +63 -0
- package/registry/skills/variant-analysis/assets/semgrep/java.yaml +61 -0
- package/registry/skills/variant-analysis/assets/semgrep/javascript.yaml +60 -0
- package/registry/skills/variant-analysis/assets/semgrep/python.yaml +72 -0
- package/registry/skills/variant-analysis/references/reporting.md +41 -0
- package/registry/skills/variant-analysis/references/root-cause.md +101 -0
- package/registry/skills/variant-analysis/references/searching.md +162 -0
- package/registry/skills/variant-analysis/references/triage.md +95 -0
- package/registry/skills/variant-analysis/references/upstream-method.md +74 -0
- package/registry/skills/variant-analysis/references/variant-report-template.md +78 -0
- package/registry/skills/vercel-deploy-best-practices/SKILL.md +24 -0
- package/registry/skills/vercel-deploy-best-practices/references/backend-frontend-devops-exemplars.md +109 -0
- package/registry/translation-ledger.json +69 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<!-- Adapted for agents-united from https://github.com/trailofbits/skills/blob/0cc1c73a5e96749ab32d7ea5e14892fafa6972ae/plugins/property-based-testing/skills/property-based-testing/references/interpreting-failures.md
|
|
2
|
+
Licence: see ../LICENSE; changes: see ../NOTICE.md. -->
|
|
3
|
+
|
|
4
|
+
# Interpreting Property-Based Test Failures
|
|
5
|
+
|
|
6
|
+
A property test that fails has told you one of three things, and they need different
|
|
7
|
+
responses:
|
|
8
|
+
|
|
9
|
+
- **The property is wrong** — you asserted something the code never promised.
|
|
10
|
+
- **The spec is ambiguous** — behaviour at this edge was never decided.
|
|
11
|
+
- **The code is wrong** — a documented guarantee is violated.
|
|
12
|
+
|
|
13
|
+
Most of the work is telling them apart. Skipping that step is how PBT gets a
|
|
14
|
+
reputation for noise.
|
|
15
|
+
|
|
16
|
+
## Ground the property before you trust the failure
|
|
17
|
+
|
|
18
|
+
Shrunk input in hand, check what the code actually promises. In descending order of
|
|
19
|
+
authority:
|
|
20
|
+
|
|
21
|
+
| Source | What it settles |
|
|
22
|
+
|---|---|
|
|
23
|
+
| External spec (RFC, format definition) | The real contract, when one exists |
|
|
24
|
+
| Type annotations | Return type, nullability, domain |
|
|
25
|
+
| Docstrings | Explicit guarantees and preconditions |
|
|
26
|
+
| Existing tests | The contract maintainers believe they have |
|
|
27
|
+
| Function name | Weak, but `sort` really does imply ordering |
|
|
28
|
+
|
|
29
|
+
The name is the weakest signal and the one most likely to mislead you — plenty of
|
|
30
|
+
functions called `normalize` do something narrower than the word suggests.
|
|
31
|
+
|
|
32
|
+
Worked example. Hypothesis reports `test_normalize(s='\x00')` failing idempotence:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
def normalize(s: str) -> str:
|
|
36
|
+
"""Normalize a string to NFC form.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
s: Input string (any unicode)
|
|
40
|
+
Returns:
|
|
41
|
+
NFC-normalized string
|
|
42
|
+
"""
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
"Any unicode" includes null bytes, so the input is in-domain and the property is
|
|
46
|
+
grounded. This one is a real bug.
|
|
47
|
+
|
|
48
|
+
Change the docstring to "ASCII printable only" and the same failure becomes a
|
|
49
|
+
strategy bug — the fix is `st.text(alphabet=...)`, not a bug report.
|
|
50
|
+
|
|
51
|
+
## Classification
|
|
52
|
+
|
|
53
|
+
| Symptom | Cause | Action |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| Violates a documented guarantee | Code bug | Report with the shrunk input and a quote from the doc |
|
|
56
|
+
| Input violates a documented precondition | Over-broad strategy | Constrain the strategy |
|
|
57
|
+
| Property contradicts the docstring or type | Wrong property | Fix the property |
|
|
58
|
+
| Edge case the spec never addresses | Ambiguous spec | Ask the maintainer; a discussion, not a bug report |
|
|
59
|
+
| Disappears under realistic constraints | Test artifact | Fix the strategy |
|
|
60
|
+
| Behaviour differs from a sibling function | Possible inconsistency | Worth raising, flag the uncertainty |
|
|
61
|
+
|
|
62
|
+
Precondition violations and explicitly-undefined behaviour are not bugs. Passing `-1`
|
|
63
|
+
to a function documented as taking positive integers tells you nothing.
|
|
64
|
+
|
|
65
|
+
Report what you find with the classification attached, including the cases you are
|
|
66
|
+
unsure about — say "ambiguous spec, needs a maintainer decision" rather than staying
|
|
67
|
+
quiet. A suppressed finding cannot be triaged by anyone else.
|
|
68
|
+
|
|
69
|
+
## Failure patterns that recur
|
|
70
|
+
|
|
71
|
+
**Lone surrogates break text roundtrips.** `decode(encode(s)) == s` fails on
|
|
72
|
+
`'\uD800'`. Whether that is a bug turns entirely on whether the format claims to
|
|
73
|
+
accept arbitrary `str` or only valid UTF-8.
|
|
74
|
+
|
|
75
|
+
**Denormals break numeric invariants.** A probability function returning a negative
|
|
76
|
+
value for `x=1e-320` is a genuine bug against a documented `[0, 1]` range, and it is
|
|
77
|
+
exactly the input no human writes by hand.
|
|
78
|
+
|
|
79
|
+
**Hash/equality divergence violates a language contract**, not just a docstring —
|
|
80
|
+
`a == b` must imply `hash(a) == hash(b)` in Python. No grounding required; report it.
|
|
81
|
+
|
|
82
|
+
**Off-by-one in custom iterators** shows up as `list(it(xs)) == xs` dropping the last
|
|
83
|
+
element. Almost always real.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<!-- Adapted for agents-united from https://github.com/trailofbits/skills/blob/0cc1c73a5e96749ab32d7ea5e14892fafa6972ae/plugins/property-based-testing/skills/property-based-testing/references/libraries.md
|
|
2
|
+
Licence: see ../LICENSE; changes: see ../NOTICE.md. -->
|
|
3
|
+
|
|
4
|
+
# PBT Libraries by Language
|
|
5
|
+
|
|
6
|
+
Match the project's existing choice. Introducing a second PBT library into a codebase
|
|
7
|
+
that already has one is not worth the property you wanted to write.
|
|
8
|
+
|
|
9
|
+
| Language | Default | Also in use |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Python | Hypothesis | — |
|
|
12
|
+
| TypeScript / JavaScript | fast-check | — |
|
|
13
|
+
| Rust | proptest | quickcheck (simpler API, per-type shrinking) |
|
|
14
|
+
| Go | rapid | gopter (ScalaCheck-style, more explicit) |
|
|
15
|
+
| Java | jqwik | — |
|
|
16
|
+
| Scala | ScalaCheck | — |
|
|
17
|
+
| C# | FsCheck | — |
|
|
18
|
+
| Elixir | StreamData | — |
|
|
19
|
+
| Haskell | QuickCheck | Hedgehog (integrated shrinking, no type classes) |
|
|
20
|
+
| Clojure | test.check | — |
|
|
21
|
+
| Ruby | PropCheck | — |
|
|
22
|
+
| Kotlin | Kotest | — |
|
|
23
|
+
| C++ | RapidCheck | — |
|
|
24
|
+
| Swift | SwiftCheck | unmaintained — check before recommending |
|
|
25
|
+
|
|
26
|
+
Detect what a repo already uses before proposing anything:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
rg "from hypothesis import|fast-check|use proptest|pgregory.net/rapid|net.jqwik|echidna_|invariant_"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Smart contracts (EVM / Solidity)
|
|
33
|
+
|
|
34
|
+
This is where PBT earns the most, because contract state is adversarial and the input
|
|
35
|
+
domain is every possible call sequence. Trail of Bits maintains both tools:
|
|
36
|
+
|
|
37
|
+
- **Echidna** — property fuzzer, mature, the default choice.
|
|
38
|
+
- **Medusa** — parallel execution, coverage-guided; faster on large contract suites.
|
|
39
|
+
|
|
40
|
+
Two testing modes, and picking the wrong one is the usual mistake:
|
|
41
|
+
|
|
42
|
+
**Property mode** — a function returning `bool` that must never become false.
|
|
43
|
+
|
|
44
|
+
```solidity
|
|
45
|
+
// Echidna calls this after every transaction sequence.
|
|
46
|
+
function echidna_total_matches_sum() public view returns (bool) {
|
|
47
|
+
return token.totalSupply() == trackedSum;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Assertion mode** — an `assert` inside a function the fuzzer is allowed to call
|
|
52
|
+
directly, for properties about a specific operation rather than global state.
|
|
53
|
+
|
|
54
|
+
```solidity
|
|
55
|
+
function testDepositIncreasesBalance(uint256 amount) public {
|
|
56
|
+
uint256 before = vault.balanceOf(address(this));
|
|
57
|
+
vault.deposit(amount);
|
|
58
|
+
assert(vault.balanceOf(address(this)) >= before);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Contract invariants worth asserting
|
|
63
|
+
|
|
64
|
+
Solvency (`sum(balances) <= totalAssets`), supply conservation, access control (a
|
|
65
|
+
non-owner call sequence never reaches an owner-only state change), monotonic
|
|
66
|
+
counters, and round-trip on share/asset conversion (`convertToShares` then
|
|
67
|
+
`convertToAssets` never returns more than you put in).
|
|
68
|
+
|
|
69
|
+
### Tautologies specific to Solidity
|
|
70
|
+
|
|
71
|
+
Type bounds are not properties. `uint256 x >= 0` is always true, and so is
|
|
72
|
+
`address(this).balance >= 0` — the compiler guarantees it. Likewise a property that
|
|
73
|
+
only reads state the fuzzer cannot reach is vacuous: if no call sequence can enter
|
|
74
|
+
the branch, the invariant is never exercised. Check Echidna's coverage output rather
|
|
75
|
+
than assuming.
|
|
76
|
+
|
|
77
|
+
`echidna_` functions must be `view`/`pure` and take no arguments — a property that
|
|
78
|
+
mutates state silently changes what it is testing.
|
|
79
|
+
|
|
80
|
+
Tutorials: [secure-contracts.com](https://secure-contracts.com).
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<!-- Adapted for agents-united from https://github.com/trailofbits/skills/blob/0cc1c73a5e96749ab32d7ea5e14892fafa6972ae/plugins/property-based-testing/skills/property-based-testing/references/refactoring.md
|
|
2
|
+
Licence: see ../LICENSE; changes: see ../NOTICE.md. -->
|
|
3
|
+
|
|
4
|
+
# Refactoring to Expose a Property
|
|
5
|
+
|
|
6
|
+
"This code has no algebraic shape" is often a fact about how the code is *arranged*
|
|
7
|
+
rather than about what it does. A function that mixes a pure calculation with a database
|
|
8
|
+
write has a property; it just does not have a seam to assert it through. These are the
|
|
9
|
+
rearrangements that expose one, strongest first.
|
|
10
|
+
|
|
11
|
+
Suggest the refactor, name the property it unlocks, and let the author decide. A change
|
|
12
|
+
to production code to make a test possible is their call, not yours — the same rule that
|
|
13
|
+
governs adding a PBT dependency at all.
|
|
14
|
+
|
|
15
|
+
## 1. Extract the pure core
|
|
16
|
+
|
|
17
|
+
The highest-value one by a wide margin, and the reason most "untestable" code is
|
|
18
|
+
testable. I/O at the edges, calculation in the middle, assert against the middle.
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
# Before: the arithmetic is real but unreachable without a database
|
|
22
|
+
def process_order(order_id: str) -> None:
|
|
23
|
+
order = db.fetch(order_id)
|
|
24
|
+
total = apply_discount(order, calculate_discount(order))
|
|
25
|
+
db.save(order_id, total)
|
|
26
|
+
|
|
27
|
+
# After: the pure core takes arguments and returns a value
|
|
28
|
+
def order_total(order: Order, rules: DiscountRules) -> Decimal:
|
|
29
|
+
return apply_discount(order, calculate_discount(order, rules))
|
|
30
|
+
|
|
31
|
+
def process_order(order_id: str) -> None:
|
|
32
|
+
order = db.fetch(order_id)
|
|
33
|
+
db.save(order_id, order_total(order, get_discount_rules()))
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`order_total` now supports invariants (never negative, never above the undiscounted
|
|
37
|
+
total), monotonicity in the discount rate, and an oracle against a reference
|
|
38
|
+
calculation. `process_order` keeps example tests with a mocked `db`, which is the right
|
|
39
|
+
tool for a two-line wrapper.
|
|
40
|
+
|
|
41
|
+
The same move applies to anything whose observable is a side effect: build the message,
|
|
42
|
+
the request, the query object — then send it. Construction is testable; delivery is
|
|
43
|
+
mocked.
|
|
44
|
+
|
|
45
|
+
## 2. Add the missing inverse
|
|
46
|
+
|
|
47
|
+
A one-way operation has no roundtrip by definition. Sometimes the inverse is worth
|
|
48
|
+
having in production anyway, and sometimes it is worth having *only* for the test — say
|
|
49
|
+
which.
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
def encode_message(msg: dict) -> bytes: ...
|
|
53
|
+
def decode_message(data: bytes) -> dict: ... # unlocks decode(encode(x)) == x
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Unlocks roundtrip, the strongest property in the catalog. Worth asking for even when the
|
|
57
|
+
production code never decodes: a serializer nobody can read back is usually a latent
|
|
58
|
+
bug, not a design.
|
|
59
|
+
|
|
60
|
+
## 3. Structured representation plus a renderer
|
|
61
|
+
|
|
62
|
+
String building by concatenation has nothing to assert beyond "contains a substring".
|
|
63
|
+
Split the value from its rendering and the inverse becomes available.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
# Before
|
|
67
|
+
def build_query(table: str, filters: dict) -> str:
|
|
68
|
+
q = f"SELECT * FROM {table}"
|
|
69
|
+
...
|
|
70
|
+
|
|
71
|
+
# After
|
|
72
|
+
@dataclass
|
|
73
|
+
class Query:
|
|
74
|
+
table: str
|
|
75
|
+
filters: dict
|
|
76
|
+
|
|
77
|
+
def render(q: Query) -> str: ...
|
|
78
|
+
def parse(sql: str) -> Query: ... # now render/parse is a roundtrip
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
This is pattern 2 wearing different clothes, and it is where escaping bugs live: a
|
|
82
|
+
roundtrip over generated filter values finds quoting errors that no hand-written example
|
|
83
|
+
will.
|
|
84
|
+
|
|
85
|
+
## 4. Return a value instead of mutating
|
|
86
|
+
|
|
87
|
+
An in-place mutation gives you nothing to compare against, because the input is gone by
|
|
88
|
+
the time you want to assert on it.
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
def sort_tasks(tasks: list[Task]) -> None: ... # before/after comparison impossible
|
|
92
|
+
def sorted_tasks(tasks: list[Task]) -> list[Task]: # unlocks is_sorted, permutation,
|
|
93
|
+
... # idempotence, length preservation
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
If the mutating signature has to stay, a wrapper that copies and returns is enough for
|
|
97
|
+
the test to have something to hold.
|
|
98
|
+
|
|
99
|
+
## 5. Inject the dependency
|
|
100
|
+
|
|
101
|
+
A function reading a global, a module constant, or `os.environ` can only be tested at
|
|
102
|
+
whatever those happen to be, so the edges of its input domain are unreachable.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
def validate(data: str) -> bool: return len(data) <= CONFIG.max_length
|
|
106
|
+
def validate(data: str, max_len: int): return len(data) <= max_len
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Parameterising the bound is what lets a generator drive `max_len` to 0, to 1, and to the
|
|
110
|
+
maximum representable value — the boundaries where validators actually break.
|
|
111
|
+
|
|
112
|
+
## When not to suggest this
|
|
113
|
+
|
|
114
|
+
- **The property you would unlock is "no crash".** Restructuring production code to
|
|
115
|
+
enable the weakest property in the catalog is a bad trade. Say the code is a poor PBT
|
|
116
|
+
candidate and stop.
|
|
117
|
+
- **The module needs wholesale restructuring.** Say that once, plainly. Twenty
|
|
118
|
+
individually-reasonable suggestions on one file is noise, and it reads as a rewrite
|
|
119
|
+
request rather than a testing recommendation.
|
|
120
|
+
- **The refactor breaks a public API.** Flag it as breaking and offer the
|
|
121
|
+
backwards-compatible version, even when the clean version is obviously nicer.
|
|
122
|
+
- **Existing tests cover the code.** Run them after any refactor and say you did.
|
|
123
|
+
"Enabled a property test and broke two example tests" is not progress.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<!-- Adapted for agents-united from https://github.com/trailofbits/skills/blob/0cc1c73a5e96749ab32d7ea5e14892fafa6972ae/plugins/property-based-testing/skills/property-based-testing/references/reviewing.md
|
|
2
|
+
Licence: see ../LICENSE; changes: see ../NOTICE.md. -->
|
|
3
|
+
|
|
4
|
+
# Reviewing Property-Based Tests
|
|
5
|
+
|
|
6
|
+
A property test can pass for years while asserting nothing. These are the ways that
|
|
7
|
+
happens, worst first.
|
|
8
|
+
|
|
9
|
+
Report every issue you find with its severity attached. Do not decide on the author's
|
|
10
|
+
behalf that a MEDIUM is not worth mentioning.
|
|
11
|
+
|
|
12
|
+
| Issue | Severity | How it shows up |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| Tautological | CRITICAL | Assertion is true regardless of the implementation |
|
|
15
|
+
| Vacuous | CRITICAL | `assume()` filters out nearly everything, or contradicts itself |
|
|
16
|
+
| No assertion | HIGH | Body calls the function and stops |
|
|
17
|
+
| Reimplementation | HIGH | Assertion recomputes the function's own logic |
|
|
18
|
+
| Weaker property available | MEDIUM | Length checked, ordering not |
|
|
19
|
+
| Over-filtered | MEDIUM | Stacked `assume()` where a strategy constraint belongs |
|
|
20
|
+
| Settings | LOW | `max_examples=5`, or no deadline on an expensive strategy |
|
|
21
|
+
|
|
22
|
+
## Tautological
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
@given(st.integers())
|
|
26
|
+
def test_useless(x):
|
|
27
|
+
result = compute(x)
|
|
28
|
+
assert result == result
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Nothing about `compute` can make this fail.
|
|
32
|
+
|
|
33
|
+
**But `f(x) == f(x)` is not automatically tautological.** It is a real determinism
|
|
34
|
+
property whenever `f` is not obviously pure — serializers over dicts or sets, anything
|
|
35
|
+
touching iteration order, hashing, or time. `pickle.dumps(obj) == pickle.dumps(obj)`
|
|
36
|
+
genuinely fails for objects with a nondeterministic `__reduce__`. Ask whether a broken
|
|
37
|
+
implementation could falsify it. If yes, it is a property; if no, it is noise.
|
|
38
|
+
|
|
39
|
+
## Vacuous
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
@given(st.integers())
|
|
43
|
+
def test_vacuous(x):
|
|
44
|
+
assume(x > 100)
|
|
45
|
+
assume(x < 50)
|
|
46
|
+
assert compute(x) > 0
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Hypothesis reports this as passing before it eventually errors on exhausted filters —
|
|
50
|
+
and in CI nobody reads the warning. `assume(x == 42)` is the subtler version: it runs,
|
|
51
|
+
it passes, and it is an example test wearing a `@given` decorator.
|
|
52
|
+
|
|
53
|
+
## Reimplementation
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
@given(st.integers(), st.integers())
|
|
57
|
+
def test_reimplements(a, b):
|
|
58
|
+
assert add(a, b) == a + b
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
If `add` is `a + b`, this asserts `a + b == a + b`. The test survives any bug the two
|
|
62
|
+
expressions share. Reach for an algebraic property instead — commutativity, identity,
|
|
63
|
+
associativity — which constrains the function without restating it.
|
|
64
|
+
|
|
65
|
+
## Finding the tests
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
rg "@given\(|from hypothesis import" --type py
|
|
69
|
+
rg "fc\.(assert|property)" --type ts --type js
|
|
70
|
+
rg "proptest!|#\[quickcheck\]" --type rust
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## What to push for
|
|
74
|
+
|
|
75
|
+
Compare each test against the property catalog in SKILL.md and name the strongest
|
|
76
|
+
property the code supports but the suite does not assert. A suite that checks
|
|
77
|
+
`len(sort(xs)) == len(xs)` and never checks ordering is the common case.
|
|
78
|
+
|
|
79
|
+
Also worth flagging: floating-point equality without a tolerance, assertions on
|
|
80
|
+
dict/set iteration order, and anything reading the clock — these produce flakes that
|
|
81
|
+
get blamed on Hypothesis and then get deleted.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<!-- Adapted for agents-united from https://github.com/trailofbits/skills/blob/0cc1c73a5e96749ab32d7ea5e14892fafa6972ae/plugins/property-based-testing/skills/property-based-testing/SKILL.md
|
|
2
|
+
Licence: see ../LICENSE; changes: see ../NOTICE.md. -->
|
|
3
|
+
|
|
4
|
+
# Property-Based Testing
|
|
5
|
+
|
|
6
|
+
An example test asserts one point. A property asserts a rule over the whole input
|
|
7
|
+
domain and lets the generator hunt for the counterexample. That trade is worth making
|
|
8
|
+
when the code has an algebraic shape — an inverse, an invariant, an oracle — and not
|
|
9
|
+
otherwise. Code with no such shape gets example tests; saying so is a valid outcome.
|
|
10
|
+
|
|
11
|
+
Check first whether the shape is missing or merely buried. A calculation wrapped in I/O,
|
|
12
|
+
a string built by concatenation, an in-place mutation — each has a property and no seam
|
|
13
|
+
to assert it through. See [references/refactoring.md](refactoring.md) before
|
|
14
|
+
concluding there is nothing to assert.
|
|
15
|
+
|
|
16
|
+
## Property catalog
|
|
17
|
+
|
|
18
|
+
| Property | Formula | Where it applies |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| Roundtrip | `decode(encode(x)) == x` | Serialization, conversion pairs |
|
|
21
|
+
| Inverse | `f(g(x)) == x` | encrypt/decrypt, compress/decompress |
|
|
22
|
+
| Oracle | `new(x) == reference(x)` | Optimization, refactoring, reimplementation |
|
|
23
|
+
| Idempotence | `f(f(x)) == f(x)` | Normalization, formatting, sorting |
|
|
24
|
+
| Invariant | Holds before and after | Any transformation, contract state |
|
|
25
|
+
| Easy to verify | `is_sorted(sort(x))` | Complex algorithms with cheap checkers |
|
|
26
|
+
| Commutativity | `f(a, b) == f(b, a)` | Binary and set operations |
|
|
27
|
+
| Associativity | `f(f(a,b), c) == f(a, f(b,c))` | Combining operations |
|
|
28
|
+
| Identity | `f(x, e) == x` | Operations with a neutral element |
|
|
29
|
+
|
|
30
|
+
Strength ordering, weakest to strongest:
|
|
31
|
+
`no crash → type preservation → invariant → idempotence → roundtrip / oracle`.
|
|
32
|
+
|
|
33
|
+
Assert the strongest property the code supports. "No crash" alone rarely justifies the
|
|
34
|
+
dependency — if that is all you can find, either a small rearrangement exposes something
|
|
35
|
+
stronger, or the honest report is that this code is a poor PBT candidate. Rule out the
|
|
36
|
+
first before settling for the second.
|
|
37
|
+
|
|
38
|
+
## The two ways a property test asserts nothing
|
|
39
|
+
|
|
40
|
+
- **Tautology.** `assert add(a, b) == a + b` restates the implementation; no bug they
|
|
41
|
+
share can fail it. Pick a property that constrains the function without recomputing
|
|
42
|
+
it. Note the exception: `f(x) == f(x)` is a genuine determinism property when `f`
|
|
43
|
+
is not obviously pure — serializers over dicts or sets, hashing, anything reading
|
|
44
|
+
the clock.
|
|
45
|
+
- **Vacuity.** `assume()` that filters out nearly every input passes without
|
|
46
|
+
exercising anything, and self-contradictory `assume()` passes having run zero cases.
|
|
47
|
+
Push constraints into the strategy so the generator produces valid inputs directly.
|
|
48
|
+
|
|
49
|
+
## Where to look next
|
|
50
|
+
|
|
51
|
+
Load the one that matches the task in front of you:
|
|
52
|
+
|
|
53
|
+
| Task | File |
|
|
54
|
+
|---|---|
|
|
55
|
+
| Writing new tests, designing strategies | [references/generating.md](generating.md) |
|
|
56
|
+
| The code has no property to assert yet | [references/refactoring.md](refactoring.md) |
|
|
57
|
+
| Reviewing existing property tests | [references/reviewing.md](reviewing.md) |
|
|
58
|
+
| A property test just failed | [references/interpreting-failures.md](interpreting-failures.md) |
|
|
59
|
+
| Library choice, Echidna and Medusa | [references/libraries.md](libraries.md) |
|
|
60
|
+
|
|
61
|
+
## Introducing PBT to a project that lacks it
|
|
62
|
+
|
|
63
|
+
If the project already uses a PBT library, just write the tests in it. If it does not,
|
|
64
|
+
adding one is a dependency decision that belongs to the user — offer it once with the
|
|
65
|
+
specific property you would write, and take the answer either way.
|
|
@@ -34,6 +34,11 @@ disable-slash-command: true
|
|
|
34
34
|
### Phase 3 — Verification
|
|
35
35
|
- Audit Next.js production build bundle sizes with `@next/bundle-analyzer`.
|
|
36
36
|
|
|
37
|
+
## Code & Config Exemplars
|
|
38
|
+
- `references/nextjs-server-actions-and-isr.md` — Server Action with Zod validation and cache
|
|
39
|
+
revalidation; Incremental Static Regeneration route (Plan 025 extraction from
|
|
40
|
+
`subagent-frontend-architect`).
|
|
41
|
+
|
|
37
42
|
## Verification Checklist
|
|
38
43
|
- [ ] Minimal client-side JavaScript bundle footprint.
|
|
39
44
|
- [ ] No layout shift (CLS = 0) during page load.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Next.js Server Actions & Incremental Static Regeneration Exemplars
|
|
2
|
+
|
|
3
|
+
> Extracted verbatim from `registry/agents/subagent-frontend-architect.md` per Plan 025
|
|
4
|
+
> Objective 4 (code exemplars live in the skill, not the specialist body). Consulted by
|
|
5
|
+
> `subagent-frontend-architect` via its Skill Consultation Map.
|
|
6
|
+
|
|
7
|
+
## 1. Next.js Server Action with Zod Validation & Cache Revalidation
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
'use server';
|
|
11
|
+
|
|
12
|
+
import { revalidatePath, revalidateTag } from 'next/cache';
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
|
|
15
|
+
const UpdateProfileSchema = z.object({
|
|
16
|
+
userId: z.string().uuid(),
|
|
17
|
+
displayName: z.string().min(2).max(50),
|
|
18
|
+
email: z.string().email(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export type UpdateProfileState = {
|
|
22
|
+
success: boolean;
|
|
23
|
+
message?: string;
|
|
24
|
+
errors?: Record<string, string[]>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export async function updateProfileAction(
|
|
28
|
+
prevState: UpdateProfileState,
|
|
29
|
+
formData: FormData
|
|
30
|
+
): Promise<UpdateProfileState> {
|
|
31
|
+
const rawData = {
|
|
32
|
+
userId: formData.get('userId'),
|
|
33
|
+
displayName: formData.get('displayName'),
|
|
34
|
+
email: formData.get('email'),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const parsed = UpdateProfileSchema.safeParse(rawData);
|
|
38
|
+
if (!parsed.success) {
|
|
39
|
+
return {
|
|
40
|
+
success: false,
|
|
41
|
+
errors: parsed.error.flatten().fieldErrors,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Execute database mutation
|
|
46
|
+
await db.user.update({
|
|
47
|
+
where: { id: parsed.data.userId },
|
|
48
|
+
data: { displayName: parsed.data.displayName, email: parsed.data.email },
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Deterministic cache invalidation
|
|
52
|
+
revalidateTag(`user-${parsed.data.userId}`);
|
|
53
|
+
revalidatePath('/settings/profile');
|
|
54
|
+
|
|
55
|
+
return { success: true, message: 'Profile updated successfully.' };
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 2. Incremental Static Regeneration (ISR) Route
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// src/app/products/[slug]/page.tsx
|
|
63
|
+
import { notFound } from 'next/navigation';
|
|
64
|
+
|
|
65
|
+
// Revalidate page cache every 1 hour (3600 seconds)
|
|
66
|
+
export const revalidate = 3600;
|
|
67
|
+
export const dynamicParams = true;
|
|
68
|
+
|
|
69
|
+
export async function generateStaticParams() {
|
|
70
|
+
const topProducts = await getTopProducts();
|
|
71
|
+
return topProducts.map((p) => ({ slug: p.slug }));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export default async function ProductPage({ params }: { params: { slug: string } }) {
|
|
75
|
+
const product = await getProductBySlug(params.slug);
|
|
76
|
+
if (!product) notFound();
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<article className="max-w-4xl mx-auto p-6">
|
|
80
|
+
<h1 className="text-3xl font-bold tracking-tight text-foreground">{product.title}</h1>
|
|
81
|
+
<p className="mt-4 text-muted-foreground">{product.description}</p>
|
|
82
|
+
</article>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
```
|