magento2-hyva-dev
Skill5 starsChanged 3 months agoMITView on GitHub
This skill should be used when the user asks to "create a Hyvä theme", "set up a child theme", "build an Alpine.js component", "make this CSP-compliant", "add Tailwind CSS classes", "convert Luma to Hyvä", "migrate Knockout to Alpine", work on "Hyvä checkout" or "Hyvä React components", or handle "Tailwind configuration" or "CSP nonce registration". Provides expert Hyvä theme development for Magento 2. This is a SPECIALIZED skill for Hyvä-specific patterns. DEPENDENT on magento2-dev-core for PHP/backend patterns.
- Pipes a download into a shell
- Sends data out
In this repository32 other files it gives its agents.
- AGENTS.mdAGENTS.md
- brainstormingSkill
- diagnosing-superpowersSkill
- diagram-studioSkill
- dispatching-parallel-agentsSkill
- executing-plansSkill
- finishing-a-development-branchSkill
- govard-laravelSkill
- govard-magentoSkill
- govard-symfonySkill
- govard-toolboxSkill
- govard-wordpressSkill
- maestro-designSkill
- magento2-backend-devSkill
- magento2-code-reviewSkill
- magento2-dev-coreSkill
- magento2-frontend-devSkill
- magento2-linterSkill
- magento2-performance-auditSkill
- magento2-security-scanSkill
- php-dev-coreSkill
- receiving-code-reviewSkill
- requesting-code-reviewSkill
- review-in-worktreeSkill
- subagent-driven-developmentSkill
- systematic-debuggingSkill
- test-driven-developmentSkill
- using-git-worktreesSkill
- using-superpowersSkill
- verification-before-completionSkill
- writing-plansSkill
- writing-skillsSkill
The file
We split this skill into the rules it gives an agent
10 rules. About 0 are common, 2 are distinctive, and 8 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 55
Always copy web/ from the parent theme rather than creating it from scratch — it carries the Tailwind config and build tooling the theme …
- Distinctive: line 491
When reviewing a diff against a Hyvä theme, never assert that a utility class "is missing", "compiles to nothing", or "will be purged" …
- Very specific: line 495
Read <theme>/web/tailwind/package.json for the tailwindcss major version.
- Very specific: line 524
Before claiming Alpine Expression Error: … is not defined or "the value is never submitted":
- Distinctive: line 527
Check the sibling fields in the same component.
- Very specific: line 528
Only when no ancestor declares the property, and Alpine.store does not supply it, report the binding as broken.
- Very specific: line 637
Use Hyva\Theme\ViewModel\Media::getResponsivePictureHtml() to generate <picture> markup instead of hand-rolling srcset.
- Very specific: line 687
Every inline <script> needs $hyvaCsp->registerInlineScript(), or it silently fails under CSP.
- Very specific: line 688
Prefer the CSP-safe Alpine pattern (named Alpine.data() functions, no inline expression logic like @click="count++") even on a non-CSP …
- Very specific: line 690
Always escape output in .phtml with $escaper, same as any other Magento template.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Magento 2 Hyvä Developer
Hyvä is a modern Magento 2 frontend framework with dramatically simplified JavaScript and CSS. This skill covers Hyvä-specific patterns.
Related Skills
REQUIRED BACKGROUND: Load magento2-dev-core first — it defines the PHP/backend patterns (DI, escaping, repositories) this skill assumes for any ViewModel or backend code behind a Hyvä template.
Hyvä and Luma (magento2-frontend-dev) are mutually exclusive theme stacks — check the theme's theme.xml parent (Hyva/default/Hyva/reset vs Magento/blank) and composer.json for hyva-themes/* packages before assuming either applies. Hybrid projects (e.g. dual-stack) keep both: app/design/frontend/<Vendor>/hyva_* → this skill, app/design/frontend/<Vendor>/luma_child → magento2-frontend-dev. Pair with govard-magento for the container/CLI side.
Detect the project's actual setup first
Otherwise: read theme.xml parent,
<theme>/web/tailwind/package.json, tailwind.config.js or tailwind-source.css+hyva.config.json, and composer.lock — verify, don't memorize table (verified 2026-08-26).
Hyvä/Tailwind conventions vary a lot by project age — check before applying a pattern:
- Tailwind version: v4 uses CSS-based config and
hyva.config.jsondesign tokens; v2/v3 usetailwind.config.js. Checkweb/tailwind/package.json. - CSP build:
Hyva/default-cspvs the plainHyva/default/Hyva/resetparent intheme.xml. Applying CSP-only nonce patterns to a non-CSP theme (or vice versa) wastes effort. - Parent theme:
Hyva/reset(built from scratch) vsHyva/default(full starter) changes how much markup/CSS already exists to extend rather than rewrite.
Hyvä vs Luma Comparison
| Aspect | Luma | Hyvä |
| JavaScript | ~200 resources (RequireJS/Knockout) | 2 resources (Alpine.js) |
| CSS | LESS-based | Tailwind CSS |
| Bundle Size | 500KB+ | <50KB |
| Core Web Vitals | Challenging | Optimized |
| Learning Curve | Steep | Gentle |
| Maintenance | Complex | Simple |
Theme Structure
Creating a Child Theme
Always copy web/ from the parent theme rather than creating it from scratch — it carries the Tailwind config and build tooling the theme needs:
mkdir -p app/design/frontend/<Vendor>/<Theme>/web
cp -r vendor/hyva-themes/magento2-default-theme/web/* app/design/frontend/<Vendor>/<Theme>/web/
# For a CSP theme, copy from magento2-default-theme-csp insteadThen add registration.php, theme.xml (parent: Hyva/default, Hyva/reset, or Hyva/default-csp), and composer.json, install Tailwind deps and build, then bin/magento setup:upgrade && bin/magento cache:flush to pick up the new theme.
app/design/frontend/Vendor/Theme/
├── registration.php
├── theme.xml
├── composer.json
├── package.json
├── tailwind.config.js
├── package.json
├── web/
│ ├── tailwind/
│ │ ├── base/ # Preflight, resets
│ │ ├── components/ # Reusable components
│ │ │ ├── buttons.css
│ │ │ ├── forms.css
│ │ │ └── messages.css
│ │ ├── utilities/ # Custom utilities
│ │ └── theme/ # Page-specific
│ └── js/
│ └── alpinejs/ # Alpine components
├── layout/
│ └── default.xml
└── templates/
└── ...CSP (Content Security Policy) Compliance
Critical: PCI-DSS 4.0 (Required since April 2025)
Payment pages MUST NOT use:
unsafe-evalCSP directiveunsafe-inlineCSP directive
CSP Nonce Registration
Every inline script MUST register with CSP:
<?php
/** @var Hyva\Theme\ViewModel\HyvaCsp $hyvaCsp */
use Hyva\Theme\ViewModel\HyvaCsp;
?>
<!-- Register script before using nonce -->
<?php $hyvaCsp->registerInlineScript() ?>
<script nonce="<?= $cspNonce ?>">
// CSP-compliant code
</script>$hyvaCsp is ambient — never demand an assignment. The Hyvä theme provides the
$hyvaCspview model to every template: a/** @var */docblock (as above) is the complete setup, no$viewModels->require(HyvaCsp::class)needed.isset($hyvaCsp)guards are the correct pattern for templates that must also render without Hyvä/CSP — never flag them as dead code, and never claimisset()is always false from template text alone. If CSP registration genuinely looks unwired, check layout XML / theme wiring first (a missing call to a mechanism the project doesn't have is "not applicable", not a finding).
CSP-Compatible Alpine.js Patterns
WRONG (CSP violations):
<!-- These patterns break CSP -->
<button @click="count++">Add</button>
<span x-show="!loading">Ready</span>
<input :value="name" @input="name = $event.target.value">CORRECT (CSP-compliant):
<!-- Use methods for mutations -->
<button @click="increment">Add</button>
<span x-show="isNotLoading">Ready</span>
<input :value="name" @input="updateName">function initComponent() {
return {
count: 0,
loading: true,
name: '',
increment() {
this.count++;
},
isNotLoading() {
return !this.loading;
},
updateName(event) {
this.name = event.target.value;
}
}
}
window.addEventListener('alpine:init', () => {
Alpine.data('initComponent', initComponent);
}, {once: true})Registering Alpine Components
<?php
/** @var Hyva\Theme\ViewModel\HyvaCsp $hyvaCsp */
?>
<script>
function initProductSlider() {
return {
products: [],
currentIndex: 0,
init() {
// Initialization
},
next() {
this.currentIndex = (this.currentIndex + 1) % this.products.length;
},
prev() {
this.currentIndex = (this.currentIndex - 1 + this.products.length) % this.products.length;
}
}
}
window.addEventListener('alpine:init', () => Alpine.data('initProductSlider', initProductSlider), {once: true})
</script>
<?php $hyvaCsp->registerInlineScript() ?>Alpine.js Component Structure
Basic Component
// web/js/alpinejs/Example.js
function initExample() {
return {
// Observable state
isOpen: false,
items: [],
selectedId: null,
// Computed (reactive)
get hasItems() {
return this.items.length > 0;
},
// Methods
toggle() {
this.isOpen = !this.isOpen;
},
select(id) {
this.selectedId = id;
},
// Lifecycle
init() {
// Called when component initializes
this.loadData();
},
loadData() {
fetch('/api/data')
.then(res => res.json())
.then(data => this.items = data);
}
}
}
window.addEventListener('alpine:init', () => Alpine.data('initExample', initExample), {once: true})Template Usage
<div x-data="initExample">
<button @click="toggle">Toggle</button>
<div x-show="isOpen">
<template x-for="item in items" :key="item.id">
<div @click="select(item.id)" :class="{ 'selected': selectedId === item.id }">
<span x-text="item.name"></span>
</div>
</template>
</div>
</div>
<script>
function initExample() {
// ... component logic
}
window.addEventListener('alpine:init', () => Alpine.data('initExample', initExample), {once: true})
</script>
<?php $hyvaCsp->registerInlineScript() ?>Passing Data from PHP
<?php
/** @var \Magento\Framework\Escaper $escaper */
/** @var \Hyva\Theme\ViewModel\HyvaCsp $hyvaCsp */
$productsJson = json_encode($products, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
?>
<div x-data="initProductList"
data-products="<?= $escaper->escapeHtmlAttr($productsJson) ?>">
</div>
<script>
function initProductList() {
return {
products: [],
init() {
this.products = JSON.parse(this.$root.dataset.products || '[]');
}
}
}
window.addEventListener('alpine:init', () => Alpine.data('initProductList', initProductList), {once: true})
</script>
<?php $hyvaCsp->registerInlineScript() ?>Hyvä Utilities
Hyvä provides global utilities via the hyva object:
Form Handling
// Get form key
hyva.getFormKey()
// Submit form via POST
hyva.postForm({
action: '/checkout',
data: { product_id: 123, qty: 1 }
})
// Alternative with fetch
async function submitForm(url, data) {
const formKey = hyva.getFormKey();
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({ ...data, form_key: formKey })
});
return response.json();
}Cookies
hyva.getCookie('customer_segment')
hyva.setCookie('recent_viewed', productId, 30)Formatting
hyva.formatPrice(price, showSign)
hyva.str('Hello {0}', name)
hyva.safeParseNumber(value)DOM Manipulation
hyva.replaceDomElement('#target', '<span>New content</span>')
hyva.trapFocus(modalElement)Events
// After Alpine initialization
hyva.alpineInitialized(function() {
console.log('Alpine ready');
})View Models
Prefer view models (Hyva\Theme\Model\ViewModelInterface / Magento's ArgumentInterface) over blocks for passing data to templates — they keep PHP logic out of the theme directory (which should hold only templates, layout, i18n, and web/ assets) and in a proper app/code module where Magento's DI can autoload the class.
// app/code/Vendor/Module/ViewModel/ProductInfo.php
declare(strict_types=1);
namespace Vendor\Module\ViewModel;
use Hyva\Theme\Model\ViewModelInterface;
use Magento\Framework\View\LayoutInterface;
class ProductInfo implements ViewModelInterface
{
public function __construct(
private readonly LayoutInterface $layout
) {}
public function isInStock(): bool
{
$product = $this->layout->getBlock('product.info')->getProduct();
return $product && $product->isInStock();
}
}<!-- layout/default.xml -->
<referenceBlock name="product.info">
<arguments>
<argument name="product_info_view_model" xsi:type="object">Vendor\Module\ViewModel\ProductInfo</argument>
</arguments>
</referenceBlock><?php /** @var \Vendor\Module\ViewModel\ProductInfo $productInfoViewModel */ ?>
<?php if ($productInfoViewModel->isInStock()): ?>
<button class="btn-cart">Add to Cart</button>
<?php endif; ?>Tailwind CSS
Tailwind v4 (CSS-based config)
Newer Hyvä themes use Tailwind v4, which drops tailwind.config.js for a CSS-based config plus a hyva.config.json design-token file — check web/tailwind/package.json first, the two configs are not interchangeable.
/* web/tailwind/tailwind-source.css */
@import "tailwindcss";
@theme {
--color-primary: oklch(46% 0.2 265);
--spacing-xs: 0.5rem;
}
@layer components {
.btn-primary {
@apply bg-primary text-white px-4 py-2 rounded;
}
}// hyva.config.json
{
"tokens": {
"src": "hyva.design.tokens.json",
"format": "default",
"cssSelector": "@theme"
}
}Generate tokens/sources with npx hyva-sources / npx hyva-tokens rather than hand-rolling them. Alpine gating: Tailwind v4 sources come from CSS @source directives and hyva.config.json tailwind.include/exclude; editing hyva.config.json on a v3 setup is a silent no-op — verify web/tailwind/package.json tailwindcss major before touching either config.
Directory Structure
web/tailwind/
├── base/
│ └── _styles.pcss # Preflight, typography
├── components/
│ ├── _buttons.pcss
│ ├── _forms.pcss
│ └── _messages.pcss
├── utilities/
│ └── _custom-utilities.pcss
├── theme/
│ ├── _header.pcss
│ └── _footer.pcss
└── app.css # Main entryBuild Commands
# Development with watch
npm run watch
# Production build
npm run build
# PurgeCSS config (auto-included)
# Tailwind automatically removes unused classesCommon Classes
<!-- Buttons -->
<button class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
Add to Cart
</button>
<!-- Forms -->
<input type="text"
class="w-full border border-gray-300 rounded px-3 py-2 focus:border-blue-500 focus:outline-none">
<!-- Cards -->
<div class="bg-white rounded-lg shadow-md p-4">
<!-- Content -->
</div>
<!-- Grid -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<!-- Items -->
</div>Responsive Design
<!-- Mobile first -->
<div class="w-full md:w-1/2 lg:w-1/3">
<!-- Shows 100% on mobile, 50% on tablet, 33% on desktop -->
</div>Reviewing Tailwind/Hyvä Diffs — Verify Before Claiming
When reviewing a diff against a Hyvä theme, never assert that a utility class "is missing", "compiles to nothing", or "will be purged" without verifying against the actual project. Work through this gate:
1. Detect the stack first
Read <theme>/web/tailwind/package.json for the tailwindcss major version. Hyvä default theme 1.4.x/1.5.x ships Tailwind v4, 1.2.x/1.3.x v3, older v2. The version decides what "exists":
- Tailwind v3: a utility exists only if it is in the default scale OR
theme.extend.*OR produced by a plugin/safelist entry intailwind.config.js. - Tailwind v4: numeric spacing utilities are dynamic (
w-18,py-3.75work out of the box); sources come from CSS@sourcedirectives andhyva.config.json(tailwind.include/exclude). Editinghyva.config.jsonon a v3 setup is a silent no-op.
2. Check the real config before claiming absence
Grep/read the theme's actual tailwind.config.js for theme.extend.*, safelist, and the content globs before any existence claim. Three traps:
safelistentries survive purge scanning even when no scanned source uses them — "no usage found in markup" is NOT evidence that a class is missing.contentglobs may exclude whole trees (stock Hyvä child themes ship with theapp/code/**glob commented out); classes outside scan scope need safelist or arbitrary-value syntax.- CMS-content classes can live in
Hyva_CmsTailwindJitdatabase-stored CSS injected at render time — they never appear inweb/css/styles.css, so absence from built CSS is not proof of absence.
3. Built CSS: only if actually present
If a compiled stylesheet is available (committed, or built locally), grep it for the escaped selector as final confirmation. Review worktrees usually have NO built CSS and NO vendor/ — do not attempt to read either; infer composer dependencies from composer.json/composer.lock. If verification is impossible from where you stand, downgrade the finding to a question ("confirm X survives the build") instead of an assertion.
4. Dynamic class names
PHP-interpolated utilities (class="columns-<?= $n ?>") are invisible to the scanner. Suggest the fix ladder: CSS-variable binding via arbitrary-value syntax, or a co-located PHP comment listing every variant (Tailwind scans comments), or safelist / @source inline() reserved for DB-sourced values only.
5. Alpine under strict CSP
Directive values must be dot paths — no operators, literals, globals, or spaces. Violations fail silently (console.warn only), so static review is the primary gate. Treat vendor guidance like "x-model is unsupported" as recommended style: dot-path x-model works on shipped CSP builds, so warn rather than fail.
6. Alpine scope follows the DOM
Alpine 3 resolves an expression through every ancestor x-data in the rendered DOM, not only the closest component: a nested component reads the parent's properties, and this.prop = value writes back to the parent. A property missing from a component's own return {...} is therefore not evidence that a binding is undefined.
Before claiming Alpine Expression Error: … is not defined or "the value is never submitted":
- Find where the template is rendered. A child
.phtmlincluded through$block->getLayout()->createBlock()orgetChildHtml()sits inside whatever element wraps that call, so read the caller and match the opening and closing tags around it. - Check the sibling fields in the same component. If they use the same pattern (bound in the child, declared only on an ancestor) and work, the new binding works too.
- Only when no ancestor declares the property, and
Alpine.storedoes not supply it, report the binding as broken. If the render chain cannot be traced from the worktree, ask instead of asserting.
The same rule holds for Knockout $parent / $root in Luma templates.
Idea credits: verification-gate patterns distilled from makotokimura96/hyva-skills (CC BY 4.0) and hyva-themes/hyva-ai-tools (OSL-3.0).
Layout XML
Hyvä-Specific Handles
<!-- Add Hyvä modal support -->
<update handle="hyva_modal"/>
<!-- Register custom Alpine component -->
<head>
<script src="Hyva_Theme::js/alpinejs/my-component.js"/>
</head>Override Template
<referenceBlock name="product.info" template="MyCompany_MyTheme::product/view.phtml"/>Migration from Luma
Step 1: Analyze Dependencies
# List jQuery dependencies
grep -r "require.*jquery" app/design/frontend/Vendor/Theme/web/js/
# Check Knockout bindings
grep -r "data-bind=" app/design/frontend/Vendor/Theme/templates/Step 2: Replace JavaScript
// Luma Knockout
define(['ko'], function(ko) {
return {
items: ko.observableArray([]),
addItem: function(item) {
this.items.push(item);
}
};
});
// Hyvä Alpine
function initComponent() {
return {
items: [],
addItem(item) {
this.items.push(item);
}
}
}Step 3: Replace LESS with Tailwind
// Luma LESS
.product-card {
.lib-card();
.lib-respond-to(@mobile, { width: 100%; });
}
// Hyvä Tailwind
<div class="bg-white rounded-lg shadow-md p-4 w-full md:w-1/2">Step 4: Update Templates
// Luma (Knockout)
<span data-bind="text: product.name"></span>
// Hyvä (Alpine)
<span x-text="product.name"></span>Third-Party Compatibility Modules
A Luma-built third-party extension needs a Hyvä compatibility module to override its templates and JS — check the vendor's GitHub for an existing one (many ship under hyva-themes/*) before writing your own.
To build one: create a module that requires the original module, copy only the .phtml templates you need to override, replace any jQuery/Knockout JS with CSP-compatible Alpine, and sequence it after both the original module and Hyva_Theme in module.xml. Then register it so Hyvä actually picks it up:
<!-- etc/frontend/di.xml -->
<type name="Hyva\CompatModuleFallback\Model\CompatModuleRegistry">
<arguments>
<argument name="compatModules" xsi:type="array">
<item name="hyva_vendor_module" xsi:type="array">
<item name="original_module" xsi:type="string">Vendor_Module</item>
<item name="compat_module" xsi:type="string">Vendor_ModuleHyva</item>
</item>
</argument>
</arguments>
</type>Without this CompatModuleRegistry registration, Hyvä has no way to know the compat module should override the original's frontend output — the templates get copied but never actually take effect.
Hyvä UI & CMS Components
- UI components (
hyva-themes/hyva-ui): prebuilt, template-based components installed into a theme — copysrc/*into the theme, merge any layout XML, and add config toetc/view.xml. - CMS components: custom Hyvä CMS blocks live in a module depending on
Hyva_CmsBase, declared in acomponents.jsonschema. Key gotchas:childrenis a root-level property (not a field type), validation lives underattributes, and the default-value key isdefault_value, notdefault.
Responsive Images
Use Hyva\Theme\ViewModel\Media::getResponsivePictureHtml() to generate <picture> markup instead of hand-rolling srcset. Set loading="eager" fetchpriority="high" on the LCP image (hero/first product image) and loading="lazy" on everything below the fold — getting this backwards is a common, easy-to-miss LCP regression.
Testing with Playwright
Hyvä pages scatter hidden x-show elements around the DOM — always scope message assertions to #messages, never a bare .message.error selector, or the test will match a hidden element and give a false pass/fail. Prefer getByRole / getByLabel / scoped getByText over raw CSS selectors, and use web-first assertions with a longer timeout to account for Alpine's reactive re-render delay after form submits.
Official Hyvä AI Tools
Hyvä provides official AI skills for various assistants:
| Tool | Purpose | Install |
| hyva-alpine-component | CSP-compatible Alpine components | .opencode/skills/ |
| hyva-child-theme | Theme creation | .opencode/skills/ |
| hyva-cms-component | CMS blocks | .opencode/skills/ |
| hyva-ui-component | UI component installation | .opencode/skills/ |
# Install Hyvä AI tools
curl -fsSL https://raw.githubusercontent.com/hyva-themes/hyva-ai-tools/main/install.sh | sh -s opencodeVerification
# Build CSS
cd web/tailwind && npm run build
# Clear cache
bin/magento cache:clean layout block_html full_page
# Static content deploy
bin/magento setup:static-content:deploy -fCSP Console Check
Chrome DevTools MCP (preferred, if available): navigate to the page,
then list_console_messages — filter for CSP violation reports
(Content-Security-Policy directive errors, "Refused to execute inline
script" / "Refused to load the stylesheet" messages). No manual browser
interaction needed; test with CSP headers enabled the same way.
Manual fallback (no MCP connected): open the page in a browser, check the DevTools console for CSP errors, and confirm again with CSP headers enabled.
Pitfalls recap
- Hyvä replaces Luma entirely: no RequireJS, Knockout, UI-component JS, jQuery, or LESS in Hyvä templates.
- Every inline
<script>needs$hyvaCsp->registerInlineScript(), or it silently fails under CSP. - Prefer the CSP-safe Alpine pattern (named
Alpine.data()functions, no inline expression logic like@click="count++") even on a non-CSP build — it's the cleaner default and avoids a rewrite if the project later enables CSP. - Rebuild Tailwind (
npm run build) after any style change, or the new classes won't be in the compiled CSS. - Always escape output in
.phtmlwith$escaper, same as any other Magento template.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

