diff --git a/.changeset/valid-release-skills.md b/.changeset/valid-release-skills.md new file mode 100644 index 00000000..582ed3e8 --- /dev/null +++ b/.changeset/valid-release-skills.md @@ -0,0 +1,5 @@ +--- +'@tanstack/intent': patch +--- + +Make the shipped meta-skills pass Intent validation. Match names to their existing directories, use string metadata, and move detailed procedures and templates into packaged references without changing the meta command names. Resolve relative Markdown links in `intent meta ` output so reference paths work from the caller's directory. diff --git a/.changeset/workspace-policy-boundary.md b/.changeset/workspace-policy-boundary.md new file mode 100644 index 00000000..6e1478fd --- /dev/null +++ b/.changeset/workspace-policy-boundary.md @@ -0,0 +1,5 @@ +--- +'@tanstack/intent': patch +--- + +Reject unreadable and malformed ancestor package manifests while discovering workspace policy, so a nested application's allowlist cannot bypass inherited restrictions when the workspace root is unreadable. Stop ancestor discovery at the first workspace declaration or Git repository boundary, checking that directory's manifest before stopping. Without an independent boundary, malformed ancestors fail closed and identify the file to repair. diff --git a/.gitignore b/.gitignore index b1c20b4a..10538396 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ es artifacts .rpt2_cache coverage +.vitest *.tgz # misc diff --git a/benchmarks/intent/package.json b/benchmarks/intent/package.json index 17b6afa3..fd137853 100644 --- a/benchmarks/intent/package.json +++ b/benchmarks/intent/package.json @@ -8,7 +8,7 @@ "devDependencies": { "@codspeed/vitest-plugin": "^5.5.0", "typescript": "6.0.3", - "vitest": "5.0.0" + "vitest": "4.1.8" }, "nx": { "targets": { diff --git a/benchmarks/intent/vitest.config.ts b/benchmarks/intent/vitest.config.ts index d37b1780..f4b7761c 100644 --- a/benchmarks/intent/vitest.config.ts +++ b/benchmarks/intent/vitest.config.ts @@ -1,6 +1,7 @@ import codspeedPlugin from '@codspeed/vitest-plugin' import { defineConfig } from 'vitest/config' +// CodSpeed's runner requires Vitest 4; the root test suite stays on Vitest 5. export default defineConfig({ plugins: [ !!(process.env.VITEST && process.env.WITH_INSTRUMENTATION) && diff --git a/docs/cli/intent-meta.md b/docs/cli/intent-meta.md index e700f23f..041daae7 100644 --- a/docs/cli/intent-meta.md +++ b/docs/cli/intent-meta.md @@ -1,32 +1,34 @@ ---- -title: intent meta -id: intent-meta ---- - -`intent meta` lists bundled meta-skills or prints one meta-skill file. - -```bash -npx @tanstack/intent@latest meta -npx @tanstack/intent@latest meta -``` - -## Arguments - -- `` is a meta-skill directory under `node_modules/@tanstack/intent/meta/` -- Rejected values: any name containing `..`, `/`, or `\\` - -## Output - -- Without ``: - - one line per meta-skill - - `name` + description from frontmatter - - description is normalized and truncated to 60 characters -- With ``: - - raw markdown from `meta//SKILL.md` - -## Common errors - -- Meta directory not found -- Invalid `` format -- Unknown `` (message suggests running `npx @tanstack/intent meta`) -- Read failure for target `SKILL.md` +--- +title: intent meta +id: intent-meta +--- + +`intent meta` lists bundled meta-skills or prints one meta-skill file. + +```bash +npx @tanstack/intent@latest meta +npx @tanstack/intent@latest meta +``` + +## Arguments + +- `` is a meta-skill directory under `node_modules/@tanstack/intent/meta/` +- Rejected values: any name containing `..`, `/`, or `\\` + +## Output + +- Without ``: + - one line per meta-skill + - `name` + description from frontmatter + - description is normalized and truncated to 60 characters +- With ``: + - Markdown from `meta//SKILL.md` + - relative links within the package resolve from the caller's directory, using absolute paths when needed + - reference files remain separate and are read only when their linked procedure is needed + +## Common errors + +- Meta directory not found +- Invalid `` format +- Unknown `` (message suggests running `npx @tanstack/intent meta`) +- Read failure for target `SKILL.md` diff --git a/docs/concepts/trust-model.md b/docs/concepts/trust-model.md index f4e771f3..48bed8f5 100644 --- a/docs/concepts/trust-model.md +++ b/docs/concepts/trust-model.md @@ -29,7 +29,9 @@ Default `intent install` handles this state through interactive permission setup Intent stops policy-controlled listing, loading, and installation when a policy `package.json` cannot be read, contains invalid JSON, or is not a JSON object. The error names the file. Repair or restore that file, then retry the command. -This also applies to inherited policy within a resolved workspace. Malformed JSON that prevents discovery from identifying the workspace root itself remains a [known limitation](https://github.com/TanStack/intent/issues/240). +This also applies while finding the workspace root: an unreadable or malformed ancestor `package.json` cannot be skipped, because it may contain inherited restrictions. Repair or restore the named manifest before retrying. + +Workspace discovery checks ancestors up to the first workspace declaration or Git repository boundary (`.git` directory or worktree file), including that directory's manifest. It does not inspect manifests above that boundary. Without either boundary, an invalid ancestor stops discovery even if the nearest package is intended to be standalone; Intent cannot determine from an unreadable manifest whether it owns workspace policy. A nested Git repository is treated as a separate project. ## First-run permission review diff --git a/package.json b/package.json index 434b320d..9abeda8e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,11 @@ "test:sherif" ] }, + "sherif": { + "ignoreDependency": [ + "vitest@4.1.8" + ] + }, "devDependencies": { "@changesets/changelog-github": "^0.7.0", "@changesets/cli": "^2.31.0", diff --git a/packages/intent/meta/domain-discovery/SKILL.md b/packages/intent/meta/domain-discovery/SKILL.md index 34a03b09..9bd6d767 100644 --- a/packages/intent/meta/domain-discovery/SKILL.md +++ b/packages/intent/meta/domain-discovery/SKILL.md @@ -1,5 +1,5 @@ --- -name: skill-domain-discovery +name: domain-discovery description: > Analyze library documentation and source code, then interview maintainers to discover capability domains and task-focused skills for AI coding @@ -7,15 +7,12 @@ description: > existing documentation into skill categories, or when a maintainer wants help deciding how to structure their library's agent-facing knowledge. Produces a domain_map.yaml and skill_spec.md that feed directly into - the skill-tree-generator skill. + the tree-generator skill. metadata: version: '3.0' category: meta-tooling - output_artifacts: - - skills/_artifacts/domain_map.yaml - - skills/_artifacts/skill_spec.md - skills: - - tree-generator + output_artifacts: 'skills/_artifacts/domain_map.yaml; skills/_artifacts/skill_spec.md' + skills: 'tree-generator' --- # Domain Discovery & Maintainer Interview @@ -256,326 +253,13 @@ reviewed and confirmed (or corrected) the skill list.** ## Phase 3 — Deep read (autonomous) -You now have the maintainer's task map. Read docs and source to fill -each skill area with concrete content — failure modes, code patterns, -gotchas. - -### Reading order - -Read in this order. Each step builds context for the next. - -Before starting, list every file in the docs directory (and subdirectories). -Use this list as a checklist — every narrative file must be read. Do not -sample a subset and extrapolate. - -1. **Narrative guides** — read as many as needed to build confidence in - your understanding. Prioritize getting-started, migration, and guides - covering the skill areas from Phase 2. Skip exhaustive reading of large - online-only doc sets. -2. **Migration guides** — highest-yield source for failure modes; every - breaking change is exactly what agents trained on older versions produce -3. **API reference** — scan for exports, type signatures, option shapes -4. **Changelog for major versions** — API renames, removed exports, - behavioral changes -5. **GitHub issues and discussions** — this is one of the highest-yield - sources for failure modes and skill content. Docs describe intended - behavior; issues reveal actual behavior and real developer confusion. - - **How to search.** Use `gh search issues` and `gh search prs` (or the - GitHub web search UI) against the library's repo. Run multiple passes: - - **High-engagement issues:** sort by reactions or comments to find the - problems that affect the most developers. These are skill-worthy - even if already fixed — agents trained on older data still hit them. - - **Label-based scans:** look for labels like `bug`, `question`, - `documentation`, `breaking-change`, `good first issue`, `FAQ`, - `help wanted`. Each label category yields different signal: - - `bug` + `closed` → failure modes with known fixes (wrong/correct pairs) - - `question` → developer confusion that skills should preempt - - `breaking-change` → migration-boundary mistakes - - **Keyword searches:** search for the skill's primary APIs, hooks, - and config options by name. E.g. `useQuery stale` or `hydration SSR`. - - **Recent vs. historical:** scan the last 6–12 months of open issues - for current pain points. Then scan older closed issues for patterns - that are now fixed but still appear in agent training data. - - **GitHub Discussions** are equally important when the repo uses them. - Discussions surface "how do I..." patterns and architectural questions - that issues don't capture. Search the Discussions tab (or use - `gh api` to query discussions) for: - - Unanswered or long-thread questions (signal: docs are insufficient) - - Threads marked as "Answered" with a non-obvious solution (skill content) - - Recurring themes across multiple threads (systemic confusion) - - **What to extract from issues/discussions:** - - Frequently reported confusion patterns → candidate failure modes - - Workarounds that developers use before a fix ships → "wrong pattern" - examples that agents will reproduce - - Recurring "how do I X with Y" threads → composition skill candidates - - Misunderstandings about defaults or config → skill content gaps - - Feature requests with many upvotes that change API design → signals - of where the API surface is unintuitive - - What users are implicitly arguing for architecturally — not just - "people are confused about X" but "users keep expecting X to work - like Y, which reveals a tension between [design force] and - [design force]" - - **What NOT to extract:** one-off bugs already fixed, feature requests - unrelated to current API surface, issues about build tooling or CI - that don't affect library usage patterns. - - **Fallback.** If no web access is available, check for FAQ.md, - TROUBLESHOOTING.md, docs/faq, or KNOWN_ISSUES.md as proxies. Also - scan the repo's `.github/ISSUE_TEMPLATE/` for hints about common - issue categories. - -6. **Source code** — verify ambiguities from docs, check defaults, find - assertions and invariant checks. For monorepos, read the 2–3 core - packages deeply. For adapter packages, read one representative adapter - deeply, then scan others for deviations from the pattern. - -### What to log - -Produce a flat concept inventory. One item per line. No grouping yet. - -Log every: - -- Named concept, abstraction, or lifecycle stage -- Public export: function, hook, class, type, constant -- Configuration key, its type, and its default value -- Constraint or invariant (especially any enforced by `throw` or assertion) -- Doc callout: any "note", "warning", "caution", "important", "avoid", "do not" -- Dual API: any place the library has two ways to do the same thing (old/new, - verbose/shorthand, lower-level/higher-level) -- Environment branch: any place behavior depends on SSR/CSR, dev/prod, - framework, bundler, or config flag -- Type gap: any type documented as accepting X but source shows X | Y or - rejects a subtype of X -- Source assertion: any `if (!x) throw`, `invariant()`, or `assert()` with - the error message text -- Issue/discussion pattern: any recurring confusion, workaround, or - misunderstanding surfaced from GitHub issues or discussions — note the - issue/discussion URL, the core misunderstanding, and whether it's - resolved or still active - -### What to extract from migration guides specifically - -For each breaking change between major versions: - -``` -Old pattern: [code that agents trained on older versions will produce] -New pattern: [current correct code] -What changed: [one sentence — the specific mechanism] -Version boundary: [e.g. "v4 → v5"] -``` - -These become high-priority failure modes. - -### 3a — Group concepts into domains - -Move concept inventory items into groups. Two items belong together when: - -- A developer reasons about them together when solving a problem -- Solving one correctly requires understanding how the other works -- They share a lifecycle, configuration scope, or architectural tradeoff -- Getting one wrong tends to produce bugs in the other - -Let library complexity drive the domain count — a focused library may need -only 2–3 domains, while a large framework may need 7+. Validate by asking: -"Would a developer working on a single feature need to load skills from -multiple domains? If so, merge those domains." These are conceptual -groupings, not the final skills. - -Do not create a group for: - -- A single hook, function, or class -- A single doc or reference page -- "Miscellaneous", "Advanced", or "Other" -- Configuration knobs that only affect another group's behavior - -Name each domain as work being performed, not what the library provides. - -**Validation step:** After grouping, check each domain by asking: -"Would a developer working on a single feature need to load skills from -multiple domains?" If yes, merge those domains. Group by developer tasks -(what they're trying to accomplish), not by architecture (how the library -is organized internally). For example, prefer "writing data" over -"producer lifecycle" — the former matches a developer's intent, the latter -matches the codebase structure. - -### 3b — Map domains × tasks → skills - -Merge your conceptual domains with the maintainer's task list from -Phase 2. Each skill should match a specific developer moment while -carrying the conceptual depth of its parent domain(s). - -A skill is well-shaped when: - -- A developer would ask for it by name ("help me set up sync") -- It covers enough for the agent to complete the task end-to-end -- It doesn't require loading 3 other skills to be useful - -Some domains produce multiple skills (a broad domain like "data access" -might yield "live-queries", "mutations", "offline-sync"). Some tasks -span domains (a "go-live" checklist touches security, performance, and -configuration). Both are fine. - -Also consider: - -- **Lifecycle/journey skills** — if the library's docs include a - quickstart guide, go-to-production checklist, or migration path, - suggest these as standalone skills. Don't force them if the docs - don't have the material. -- **Composition skills** — when peer deps or examples show consistent - co-usage with another library, output a full skill for the - integration, not a footnote on a domain. - -### 3c — Flag subsystems within skills - -Check each skill area for internal diversity. A skill may be -conceptually unified but contain multiple independent subsystems with -distinct config interfaces — for example, 5 sync adapters that all -solve "connectivity" but each with unique setup, options, and failure -modes. - -For each skill, ask: "Does this cover 3+ backends, adapters, drivers, -or providers with distinct configuration surfaces?" If yes, list them -as `subsystems`. These tell the skill-tree-generator to produce -per-subsystem reference files. - -Also flag dense API surfaces — if a topic has >10 distinct operators, -option shapes, or patterns (e.g. query operators, schema validation -rules), note it as a `reference_candidates` entry. - -### 3d — Extract failure modes +Before the deep read, load [the Phase 3 procedure](references/deep-read.md). +Follow its reading order and steps 3a–3i to extract sourced failure modes, +domains, task-focused skills, tensions, references, gaps, and compositions. +Use [the artifact formats](references/artifacts.md) for the draft. -For each skill, extract failure modes that pass all three tests: - -- **Plausible** — An agent would generate this because it looks correct - based on the library's design, a similar API, or an older version -- **Silent** — No immediate crash; fails at runtime or under specific conditions -- **Grounded** — Traceable to a specific doc page, source location, or issue - -**Where to find them:** - -| Source | What to extract | -| -------------------- | -------------------------------------------------------------------- | -| Migration guides | Every breaking change → old pattern is the wrong code | -| Doc callouts | Any "note", "warning", "avoid" with surrounding context | -| Source assertions | `throw` and `invariant()` messages describe the failure | -| Default values | Undocumented or surprising defaults that cause wrong behavior | -| Type precision | Source type more restrictive than docs imply | -| Environment branches | `typeof window`, SSR flags, `NODE_ENV` — behavior differs silently | -| GitHub issues | Recurring bug reports with workarounds → wrong/correct code pairs | -| GitHub discussions | "How do I…" threads with non-obvious answers → missing skill content | - -Target 3 failure modes per skill minimum. Complex skills target 5–6. - -**Code patterns.** Every failure mode should include `wrong_pattern` and -`correct_pattern` fields with short code snippets (3–10 lines each). -The wrong pattern is what an agent would generate; the correct pattern -is the fix. These feed directly into SKILL.md Common Mistakes sections -as wrong/correct code pairs. If the failure mode is purely conceptual -(e.g. an architectural choice) rather than a code pattern, omit both -fields and explain in `mechanism` instead. - -**Cross-skill failure modes.** Some failure modes belong to multiple -skills. A developer doing SSR work and a developer doing state management -both need to know about "stale state during hydration" — they load -different skills but need the same advice. When a failure mode spans -skills, list all relevant skill slugs in its `skills` field. The -skill-tree-generator will write it into every corresponding SKILL file. - -List a cross-skill failure mode once, under its primary skill. Set -the `skills` field to all skill slugs it applies to. Do not duplicate -the entry in the YAML — the skill-tree-generator handles duplication -into multiple SKILL files at generation time. - -### 3e — Identify cross-skill tensions - -Look for places where design forces between skills conflict. A tension -is not a failure mode — it's a structural pull where optimizing for one -task makes another harder. Examples: - -- "Getting-started simplicity conflicts with production operational safety" -- "Type-safety strictness conflicts with rapid prototyping flexibility" -- "SSR correctness requires patterns that hurt client-side performance" - -Tensions are where agents fail most because they optimize for one task -without seeing the tradeoff. Each tension should name the skills in -conflict, describe the pull, and state what an agent gets wrong when it -only considers one side. - -Target 2–4 tensions. If you find none, the skills may be too isolated — -revisit whether you're missing cross-connections. - -### 3f — Map cross-references - -Beyond tensions (conflicts) and shared failure modes, identify skills -that illuminate each other without conflicting. A cross-reference means: -"an agent loading skill A would produce better code if it knew about -skill B." These become "See also" pointers in the generated SKILL.md -files. - -For each pair, note: - -- Which skill references which (can be bidirectional) -- Why awareness of the other skill improves output - -Examples: - -- A quickstart skill references the security checklist ("after setup, audit") -- A state management skill references an SSR skill ("state hydration - requires understanding SSR lifecycle") -- A data writing skill references a data reading skill ("writes affect - how queries invalidate") - -Output these in the `cross_references` section of domain_map.yaml. - -### 3g — Identify gaps - -For each skill, explicitly list what you could NOT determine from docs -and source alone. These become interview questions in Phase 4. - -Common gaps: - -- "Docs describe X but don't explain when you'd choose X over Y" -- "Migration guide mentions this changed but doesn't say what the old - behavior was" -- "Source has an assertion here but no doc explains what triggers it" -- "GitHub issues show confusion about X but docs don't address it" -- "I found two patterns for doing X — unclear which is current/preferred" - -### 3h — Discover composition targets - -Scan `package.json` for peer dependencies, optional dependencies, and -`peerDependenciesMeta`. Scan example directories and integration tests -for import patterns. For each frequently co-used library, log: - -- Library name and which features interact -- Whether it's a required or optional integration -- Any example code showing the integration pattern - -These become targeted composition questions in Phase 4e. - -### 3i — Produce the draft - -Write the full `domain_map.yaml` (format in Output Artifacts below) with -a `status: draft` field. Flag every gap in the `gaps` section. - -Present the draft to the maintainer before starting Phase 4: - -> "I've read the docs and source for [library] and produced a draft with -> [N] skills and [M] failure modes. I've flagged [K] specific gaps where -> I need your input." - -Include the full draft domain_map.yaml in your message so the maintainer -can review it. Also include a checklist of all docs files you read. - -**── STOP ── Do not proceed to Phase 4 until the maintainer has -reviewed the draft and responded. Their response to the draft informs -the detail interview questions.** - ---- +**── STOP ── Present the draft and wait for the maintainer's review before +starting Phase 4.** ## Phase 4 — Detail interview (interactive — requires maintainer) @@ -720,199 +404,41 @@ Update `status: draft` to `status: reviewed`. ## Output artifacts -If the maintainer uses a custom skills root, replace `skills/` in the paths -below with their chosen directory. - -**Monorepo layout:** For monorepos, domain map artifacts go at the REPO ROOT -(e.g. `_artifacts/domain_map.yaml`) since they describe the whole library. -Skills are generated per-package later by the tree-generator and generate-skill -steps. - -### 1. skills/\_artifacts/domain_map.yaml - -```yaml -# domain_map.yaml -# Generated by skill-domain-discovery -# Library: [name] -# Version: [version this map targets] -# Date: [ISO date] -# Status: [draft | reviewed] - -library: - name: '[package-name]' - version: '[version]' - repository: '[repo URL]' - description: '[one line]' - primary_framework: '[React | Vue | Svelte | framework-agnostic]' - -domains: - - name: '[work-oriented domain name]' - slug: '[kebab-case]' - description: '[conceptual grouping — what a developer is reasoning about]' - -skills: - - name: '[task-focused skill name]' - slug: '[kebab-case]' - domain: '[parent domain slug]' - description: '[what a developer is doing — matches a specific task/moment]' - type: '[core | framework | lifecycle | composition]' - packages: # required for monorepo; omit for single-package libraries - - '[primary package name]' - - '[secondary package name, if skill spans multiple packages]' - covers: - - '[API/hook/concept 1]' - - '[API/hook/concept 2]' - tasks: - - '[example task 1]' - - '[example task 2]' - - '[example task 3]' - subsystems: # omit if skill has no independent subsystems - - name: '[adapter/backend name]' - package: '[npm package if separate]' - config_surface: '[brief description of unique config]' - reference_candidates: # omit if no dense API surfaces - - topic: '[e.g. query operators, schema validation]' - reason: '[e.g. >10 distinct operators with signatures]' - failure_modes: - - mistake: '[5-10 word phrase]' - mechanism: '[one sentence]' - wrong_pattern: | # the code an agent would incorrectly generate - [short code snippet showing the mistake] - correct_pattern: | # the code that should be generated instead - [short code snippet showing the fix] - source: '[doc page, source file, issue link, or maintainer interview]' - priority: '[CRITICAL | HIGH | MEDIUM]' - status: '[active | fixed-but-legacy-risk | removed]' - version_context: "[e.g. 'Fixed in v5.2 but agents trained on older code still generate this']" - skills: ['[this-skill-slug]'] # list all skills this belongs to; omit if single-skill - compositions: - - library: '[other library name]' - skill: '[composition skill name if applicable]' - -tensions: - - name: '[short phrase describing the pull]' - skills: ['[skill-slug-a]', '[skill-slug-b]'] - description: '[what conflicts — one sentence]' - implication: '[what an agent gets wrong when it only considers one side]' - -cross_references: - - from: '[skill-slug]' - to: '[skill-slug]' - reason: '[why loading one skill benefits from awareness of the other]' - -gaps: - - skill: '[skill slug]' - question: '[what still needs input]' - context: '[why this matters]' - status: '[open | resolved]' -``` - -### 2. skills/\_artifacts/skill_spec.md - -A human-readable companion document. Follow this structure: - -```markdown -# [Library Name] — Skill Spec - -[2–3 sentences: what this library is, what problem it solves. Factual, -not promotional.] - -## Domains - -| Domain | Description | Skills | -| ------ | --------------------- | ----------------------- | -| [name] | [conceptual grouping] | [skill-1, skill-2, ...] | - -## Skill Inventory - -| Skill | Type | Domain | What it covers | Failure modes | -| ------ | -------------------------------------- | -------- | -------------- | ------------- | -| [name] | [core/framework/lifecycle/composition] | [domain] | [list] | [count] | - -## Failure Mode Inventory - -### [Skill name] ([count] failure modes) - -| # | Mistake | Priority | Source | Cross-skill? | -| --- | -------- | -------- | ---------------------- | ------------------------ | -| 1 | [phrase] | CRITICAL | [doc/source/interview] | [other skill slugs or —] | - -[Repeat table for each skill.] - -## Tensions - -| Tension | Skills | Agent implication | -| -------------- | ------------------- | ----------------------- | -| [short phrase] | [slug-a] ↔ [slug-b] | [what agents get wrong] | - -## Cross-References - -| From | To | Reason | -| ------ | ------ | ----------------------------------------- | -| [slug] | [slug] | [why awareness of one improves the other] | - -## Subsystems & Reference Candidates - -| Skill | Subsystems | Reference candidates | -| ------ | ------------------------------ | -------------------------- | -| [slug] | [adapter1, adapter2, ...] or — | [topic needing depth] or — | - -## Remaining Gaps - -| Skill | Question | Status | -| ------ | ------------------------ | ------ | -| [slug] | [what still needs input] | open | - -[Omit this section if all gaps were resolved in the interview.] - -## Recommended Skill File Structure - -- **Core skills:** [list which skills are framework-agnostic] -- **Framework skills:** [list per-framework skills needed] -- **Lifecycle skills:** [list journey/lifecycle skills if applicable] -- **Composition skills:** [list integration seams needing composition skills] -- **Reference files:** [list skills needing references/ based on subsystems - or dense API surfaces] - -## Composition Opportunities - -| Library | Integration points | Composition skill needed? | -| ------- | ------------------ | ----------------------------- | -| [name] | [what interacts] | [yes/no — if yes, skill name] | -``` - ---- +Before writing the draft in Phase 3 or finalizing it in Phase 5, read +[the artifact formats and placement rules](references/artifacts.md). +Produce both `domain_map.yaml` and `skill_spec.md` using those schemas; +retain the documented custom-root and monorepo placement. ## Constraints -| Check | Rule | -| ------------------------------------- | -------------------------------------------------------------------------- | -| Quick scan before interview | Never interview without at least reading README and package structure | -| High-level interview before deep read | The maintainer's task map informs what you read deeply | -| **Interview phases are interactive** | Phases 2 and 4 require sending questions to the maintainer and waiting | -| **Docs are not a substitute** | Documentation cannot replace maintainer answers — even comprehensive docs | -| **Open-ended questions stay open** | Never convert interview questions to multiple-choice or yes/no | -| **Minimum question counts enforced** | Each sub-section's minimum count must be met; zero questions = violation | -| **STOP gates are mandatory** | Do not proceed past a STOP gate without maintainer response | -| Batch only confirmations | Yes/no questions may batch 2–3; open-ended questions get their own message | -| Questions reference findings | No generic questions — cite what you found | -| Skills are task-focused | Each skill matches a developer moment, not a conceptual area | -| 3+ failure modes per skill | Complex skills target 5–6 | -| Every failure mode sourced | Doc page, source file, issue link, or maintainer interview | -| Gaps are explicit | Unknown areas flagged, not guessed | -| No marketing prose | Library description is factual, not promotional | -| domain_map.yaml is valid YAML | Parseable by any YAML parser | -| Draft before detail interview | Present draft for review before Phase 4 | -| **Draft reviewed before Phase 4** | Maintainer must acknowledge or respond to draft before detail interview | -| Agent-specific failures probed | Always ask AI-agent-specific questions in Phase 4c | -| Compositions discovered from code | Scan peer deps and examples before asking composition questions | -| Cross-skill failure modes tagged | Failure modes spanning skills list all relevant slugs | -| Tensions identified | 2–4 cross-skill tensions; if none found, revisit skill boundaries | -| Subsystems flagged | Skills with 3+ adapters/backends list them as subsystems | -| Dense surfaces flagged | Topics with >10 patterns noted as reference_candidates | -| Lifecycle skills considered | Suggest journey skills when docs have the material | -| Cross-references mapped | Skills that illuminate each other get "See also" pointers | -| **All docs files read** | List docs directory contents and read every narrative file — no sampling | +| Check | Rule | +| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| Quick scan before interview | Never interview without at least reading README and package structure | +| High-level interview before deep read | The maintainer's task map informs what you read deeply | +| **Interview phases are interactive** | Phases 2 and 4 require sending questions to the maintainer and waiting | +| **Docs are not a substitute** | Documentation cannot replace maintainer answers — even comprehensive docs | +| **Open-ended questions stay open** | Never convert interview questions to multiple-choice or yes/no | +| **Minimum question counts enforced** | Each sub-section's minimum count must be met; zero questions = violation | +| **STOP gates are mandatory** | Do not proceed past a STOP gate without maintainer response | +| Batch only confirmations | Yes/no questions may batch 2–3; open-ended questions get their own message | +| Questions reference findings | No generic questions — cite what you found | +| Skills are task-focused | Each skill matches a developer moment, not a conceptual area | +| 3+ failure modes per skill | Complex skills target 5–6 | +| Every failure mode sourced | Doc page, source file, issue link, or maintainer interview | +| Gaps are explicit | Unknown areas flagged, not guessed | +| No marketing prose | Library description is factual, not promotional | +| domain_map.yaml is valid YAML | Parseable by any YAML parser | +| Draft before detail interview | Present draft for review before Phase 4 | +| **Draft reviewed before Phase 4** | Maintainer must acknowledge or respond to draft before detail interview | +| Agent-specific failures probed | Always ask AI-agent-specific questions in Phase 4c | +| Compositions discovered from code | Scan peer deps and examples before asking composition questions | +| Cross-skill failure modes tagged | Failure modes spanning skills list all relevant slugs | +| Tensions identified | 2–4 cross-skill tensions; if none found, revisit skill boundaries | +| Subsystems flagged | Skills with 3+ adapters/backends list them as subsystems | +| Dense surfaces flagged | Topics with >10 patterns noted as reference_candidates | +| Lifecycle skills considered | Suggest journey skills when docs have the material | +| Cross-references mapped | Skills that illuminate each other get "See also" pointers | +| **Documentation coverage** | Read all local narrative files; document external sampling under the [Phase 3 reading policy](references/deep-read.md#reading-order) | --- diff --git a/packages/intent/meta/domain-discovery/references/artifacts.md b/packages/intent/meta/domain-discovery/references/artifacts.md new file mode 100644 index 00000000..2d8385a8 --- /dev/null +++ b/packages/intent/meta/domain-discovery/references/artifacts.md @@ -0,0 +1,164 @@ +## Output artifacts + +If the maintainer uses a custom skills root, replace `skills/` in the paths +below with their chosen directory. + +**Monorepo layout:** For monorepos, domain map artifacts go at the REPO ROOT +(e.g. `_artifacts/domain_map.yaml`) since they describe the whole library. +Skills are generated per-package later by the tree-generator and generate-skill +steps. + +### 1. skills/\_artifacts/domain_map.yaml + +```yaml +# domain_map.yaml +# Generated by domain-discovery +# Library: [name] +# Version: [version this map targets] +# Date: [ISO date] +# Status: [draft | reviewed] + +library: + name: '[package-name]' + version: '[version]' + repository: '[repo URL]' + description: '[one line]' + primary_framework: '[React | Vue | Svelte | framework-agnostic]' + +domains: + - name: '[work-oriented domain name]' + slug: '[kebab-case]' + description: '[conceptual grouping — what a developer is reasoning about]' + +skills: + - name: '[task-focused skill name]' + slug: '[kebab-case]' + domain: '[parent domain slug]' + description: '[what a developer is doing — matches a specific task/moment]' + type: '[core | framework | lifecycle | composition]' + packages: # required for monorepo; omit for single-package libraries + - '[primary package name]' + - '[secondary package name, if skill spans multiple packages]' + covers: + - '[API/hook/concept 1]' + - '[API/hook/concept 2]' + tasks: + - '[example task 1]' + - '[example task 2]' + - '[example task 3]' + subsystems: # omit if skill has no independent subsystems + - name: '[adapter/backend name]' + package: '[npm package if separate]' + config_surface: '[brief description of unique config]' + reference_candidates: # omit if no dense API surfaces + - topic: '[e.g. query operators, schema validation]' + reason: '[e.g. >10 distinct operators with signatures]' + failure_modes: + - mistake: '[5-10 word phrase]' + mechanism: '[one sentence]' + wrong_pattern: | # the code an agent would incorrectly generate + [short code snippet showing the mistake] + correct_pattern: | # the code that should be generated instead + [short code snippet showing the fix] + source: '[doc page, source file, issue link, or maintainer interview]' + priority: '[CRITICAL | HIGH | MEDIUM]' + status: '[active | fixed-but-legacy-risk | removed]' + version_context: "[e.g. 'Fixed in v5.2 but agents trained on older code still generate this']" + skills: ['[this-skill-slug]'] # list all skills this belongs to; omit if single-skill + compositions: + - library: '[other library name]' + skill: '[composition skill name if applicable]' + +tensions: + - name: '[short phrase describing the pull]' + skills: ['[skill-slug-a]', '[skill-slug-b]'] + description: '[what conflicts — one sentence]' + implication: '[what an agent gets wrong when it only considers one side]' + +cross_references: + - from: '[skill-slug]' + to: '[skill-slug]' + reason: '[why loading one skill benefits from awareness of the other]' + +gaps: + - skill: '[skill slug]' + question: '[what still needs input]' + context: '[why this matters]' + status: '[open | resolved]' +``` + +### 2. skills/\_artifacts/skill_spec.md + +A human-readable companion document. Follow this structure: + +```markdown +# [Library Name] — Skill Spec + +[2–3 sentences: what this library is, what problem it solves. Factual, +not promotional.] + +## Domains + +| Domain | Description | Skills | +| ------ | --------------------- | ----------------------- | +| [name] | [conceptual grouping] | [skill-1, skill-2, ...] | + +## Skill Inventory + +| Skill | Type | Domain | What it covers | Failure modes | +| ------ | -------------------------------------- | -------- | -------------- | ------------- | +| [name] | [core/framework/lifecycle/composition] | [domain] | [list] | [count] | + +## Failure Mode Inventory + +### [Skill name] ([count] failure modes) + +| # | Mistake | Priority | Source | Cross-skill? | +| --- | -------- | -------- | ---------------------- | ------------------------ | +| 1 | [phrase] | CRITICAL | [doc/source/interview] | [other skill slugs or —] | + +[Repeat table for each skill.] + +## Tensions + +| Tension | Skills | Agent implication | +| -------------- | ------------------- | ----------------------- | +| [short phrase] | [slug-a] ↔ [slug-b] | [what agents get wrong] | + +## Cross-References + +| From | To | Reason | +| ------ | ------ | ----------------------------------------- | +| [slug] | [slug] | [why awareness of one improves the other] | + +## Subsystems & Reference Candidates + +| Skill | Subsystems | Reference candidates | +| ------ | ------------------------------ | -------------------------- | +| [slug] | [adapter1, adapter2, ...] or — | [topic needing depth] or — | + +## Remaining Gaps + +| Skill | Question | Status | +| ------ | ------------------------ | ------ | +| [slug] | [what still needs input] | open | + +[Omit this section if all gaps were resolved in the interview.] + +## Recommended Skill File Structure + +- **Core skills:** [list which skills are framework-agnostic] +- **Framework skills:** [list per-framework skills needed] +- **Lifecycle skills:** [list journey/lifecycle skills if applicable] +- **Composition skills:** [list integration seams needing composition skills] +- **Reference files:** [list skills needing references/ based on subsystems + or dense API surfaces] + +## Composition Opportunities + +| Library | Integration points | Composition skill needed? | +| ------- | ------------------ | ----------------------------- | +| [name] | [what interacts] | [yes/no — if yes, skill name] | +``` + +--- diff --git a/packages/intent/meta/domain-discovery/references/deep-read.md b/packages/intent/meta/domain-discovery/references/deep-read.md new file mode 100644 index 00000000..aed5e7f6 --- /dev/null +++ b/packages/intent/meta/domain-discovery/references/deep-read.md @@ -0,0 +1,331 @@ +## Phase 3 — Deep read (autonomous) + +You now have the maintainer's task map. Read docs and source to fill +each skill area with concrete content — failure modes, code patterns, +gotchas. + +### Reading order + +Read in this order. Each step builds context for the next. + +Before starting, list every file in the local docs directory (and +subdirectories). Use this list as a checklist — read every local narrative +file, regardless of the collection's size. Do not extrapolate from a sample. + +1. **Narrative guides** — read local guides exhaustively. Prioritize + getting-started, migration, and guides covering the skill areas from + Phase 2; priority determines reading order, not which local files to omit. + For online-only docs or exceptionally large external collections, + sampling is allowed only when full reading would exceed an explicit + task budget or access limit. Record that limit and inventory the available + pages from the documentation index. Read the getting-started and migration + guides plus guides for each Phase 2 skill area. Record the selected URLs, + unread areas, and selection rationale in the coverage checklist; flag + inaccessible or uncovered areas as gaps rather than claiming full coverage. + This exception does not apply to local documentation. +2. **Migration guides** — highest-yield source for failure modes; every + breaking change is exactly what agents trained on older versions produce +3. **API reference** — scan for exports, type signatures, option shapes +4. **Changelog for major versions** — API renames, removed exports, + behavioral changes +5. **GitHub issues and discussions** — this is one of the highest-yield + sources for failure modes and skill content. Docs describe intended + behavior; issues reveal actual behavior and real developer confusion. + + **How to search.** Use `gh search issues` and `gh search prs` (or the + GitHub web search UI) against the library's repo. Run multiple passes: + - **High-engagement issues:** sort by reactions or comments to find the + problems that affect the most developers. These are skill-worthy + even if already fixed — agents trained on older data still hit them. + - **Label-based scans:** look for labels like `bug`, `question`, + `documentation`, `breaking-change`, `good first issue`, `FAQ`, + `help wanted`. Each label category yields different signal: + - `bug` + `closed` → failure modes with known fixes (wrong/correct pairs) + - `question` → developer confusion that skills should preempt + - `breaking-change` → migration-boundary mistakes + - **Keyword searches:** search for the skill's primary APIs, hooks, + and config options by name. E.g. `useQuery stale` or `hydration SSR`. + - **Recent vs. historical:** scan the last 6–12 months of open issues + for current pain points. Then scan older closed issues for patterns + that are now fixed but still appear in agent training data. + + **GitHub Discussions** are equally important when the repo uses them. + Discussions surface "how do I..." patterns and architectural questions + that issues don't capture. Search the Discussions tab (or use + `gh api` to query discussions) for: + - Unanswered or long-thread questions (signal: docs are insufficient) + - Threads marked as "Answered" with a non-obvious solution (skill content) + - Recurring themes across multiple threads (systemic confusion) + + **What to extract from issues/discussions:** + - Frequently reported confusion patterns → candidate failure modes + - Workarounds that developers use before a fix ships → "wrong pattern" + examples that agents will reproduce + - Recurring "how do I X with Y" threads → composition skill candidates + - Misunderstandings about defaults or config → skill content gaps + - Feature requests with many upvotes that change API design → signals + of where the API surface is unintuitive + - What users are implicitly arguing for architecturally — not just + "people are confused about X" but "users keep expecting X to work + like Y, which reveals a tension between [design force] and + [design force]" + + **What NOT to extract:** one-off bugs already fixed, feature requests + unrelated to current API surface, issues about build tooling or CI + that don't affect library usage patterns. + + **Fallback.** If no web access is available, check for FAQ.md, + TROUBLESHOOTING.md, docs/faq, or KNOWN_ISSUES.md as proxies. Also + scan the repo's `.github/ISSUE_TEMPLATE/` for hints about common + issue categories. + +6. **Source code** — verify ambiguities from docs, check defaults, find + assertions and invariant checks. For monorepos, read the 2–3 core + packages deeply. For adapter packages, read one representative adapter + deeply, then scan others for deviations from the pattern. + +### What to log + +Produce a flat concept inventory. One item per line. No grouping yet. + +Log every: + +- Named concept, abstraction, or lifecycle stage +- Public export: function, hook, class, type, constant +- Configuration key, its type, and its default value +- Constraint or invariant (especially any enforced by `throw` or assertion) +- Doc callout: any "note", "warning", "caution", "important", "avoid", "do not" +- Dual API: any place the library has two ways to do the same thing (old/new, + verbose/shorthand, lower-level/higher-level) +- Environment branch: any place behavior depends on SSR/CSR, dev/prod, + framework, bundler, or config flag +- Type gap: any type documented as accepting X but source shows X | Y or + rejects a subtype of X +- Source assertion: any `if (!x) throw`, `invariant()`, or `assert()` with + the error message text +- Issue/discussion pattern: any recurring confusion, workaround, or + misunderstanding surfaced from GitHub issues or discussions — note the + issue/discussion URL, the core misunderstanding, and whether it's + resolved or still active + +### What to extract from migration guides specifically + +For each breaking change between major versions: + +``` +Old pattern: [code that agents trained on older versions will produce] +New pattern: [current correct code] +What changed: [one sentence — the specific mechanism] +Version boundary: [e.g. "v4 → v5"] +``` + +These become high-priority failure modes. + +### 3a — Group concepts into domains + +Move concept inventory items into groups. Two items belong together when: + +- A developer reasons about them together when solving a problem +- Solving one correctly requires understanding how the other works +- They share a lifecycle, configuration scope, or architectural tradeoff +- Getting one wrong tends to produce bugs in the other + +Let library complexity drive the domain count — a focused library may need +only 2–3 domains, while a large framework may need 7+. Validate by asking: +"Would a developer working on a single feature need to load skills from +multiple domains? If so, merge those domains." These are conceptual +groupings, not the final skills. + +Do not create a group for: + +- A single hook, function, or class +- A single doc or reference page +- "Miscellaneous", "Advanced", or "Other" +- Configuration knobs that only affect another group's behavior + +Name each domain as work being performed, not what the library provides. + +**Validation step:** After grouping, check each domain by asking: +"Would a developer working on a single feature need to load skills from +multiple domains?" If yes, merge those domains. Group by developer tasks +(what they're trying to accomplish), not by architecture (how the library +is organized internally). For example, prefer "writing data" over +"producer lifecycle" — the former matches a developer's intent, the latter +matches the codebase structure. + +### 3b — Map domains × tasks → skills + +Merge your conceptual domains with the maintainer's task list from +Phase 2. Each skill should match a specific developer moment while +carrying the conceptual depth of its parent domain(s). + +A skill is well-shaped when: + +- A developer would ask for it by name ("help me set up sync") +- It covers enough for the agent to complete the task end-to-end +- It doesn't require loading 3 other skills to be useful + +Some domains produce multiple skills (a broad domain like "data access" +might yield "live-queries", "mutations", "offline-sync"). Some tasks +span domains (a "go-live" checklist touches security, performance, and +configuration). Both are fine. + +Also consider: + +- **Lifecycle/journey skills** — if the library's docs include a + quickstart guide, go-to-production checklist, or migration path, + suggest these as standalone skills. Don't force them if the docs + don't have the material. +- **Composition skills** — when peer deps or examples show consistent + co-usage with another library, output a full skill for the + integration, not a footnote on a domain. + +### 3c — Flag subsystems within skills + +Check each skill area for internal diversity. A skill may be +conceptually unified but contain multiple independent subsystems with +distinct config interfaces — for example, 5 sync adapters that all +solve "connectivity" but each with unique setup, options, and failure +modes. + +For each skill, ask: "Does this cover 3+ backends, adapters, drivers, +or providers with distinct configuration surfaces?" If yes, list them +as `subsystems`. These tell the tree-generator to produce +per-subsystem reference files. + +Also flag dense API surfaces — if a topic has >10 distinct operators, +option shapes, or patterns (e.g. query operators, schema validation +rules), note it as a `reference_candidates` entry. + +### 3d — Extract failure modes + +For each skill, extract failure modes that pass all three tests: + +- **Plausible** — An agent would generate this because it looks correct + based on the library's design, a similar API, or an older version +- **Silent** — No immediate crash; fails at runtime or under specific conditions +- **Grounded** — Traceable to a specific doc page, source location, or issue + +**Where to find them:** + +| Source | What to extract | +| -------------------- | -------------------------------------------------------------------- | +| Migration guides | Every breaking change → old pattern is the wrong code | +| Doc callouts | Any "note", "warning", "avoid" with surrounding context | +| Source assertions | `throw` and `invariant()` messages describe the failure | +| Default values | Undocumented or surprising defaults that cause wrong behavior | +| Type precision | Source type more restrictive than docs imply | +| Environment branches | `typeof window`, SSR flags, `NODE_ENV` — behavior differs silently | +| GitHub issues | Recurring bug reports with workarounds → wrong/correct code pairs | +| GitHub discussions | "How do I…" threads with non-obvious answers → missing skill content | + +Target 3 failure modes per skill minimum. Complex skills target 5–6. + +**Code patterns.** Every failure mode should include `wrong_pattern` and +`correct_pattern` fields with short code snippets (3–10 lines each). +The wrong pattern is what an agent would generate; the correct pattern +is the fix. These feed directly into SKILL.md Common Mistakes sections +as wrong/correct code pairs. If the failure mode is purely conceptual +(e.g. an architectural choice) rather than a code pattern, omit both +fields and explain in `mechanism` instead. + +**Cross-skill failure modes.** Some failure modes belong to multiple +skills. A developer doing SSR work and a developer doing state management +both need to know about "stale state during hydration" — they load +different skills but need the same advice. When a failure mode spans +skills, list all relevant skill slugs in its `skills` field. The +tree-generator will write it into every corresponding SKILL file. + +List a cross-skill failure mode once, under its primary skill. Set +the `skills` field to all skill slugs it applies to. Do not duplicate +the entry in the YAML — the tree-generator handles duplication +into multiple SKILL files at generation time. + +### 3e — Identify cross-skill tensions + +Look for places where design forces between skills conflict. A tension +is not a failure mode — it's a structural pull where optimizing for one +task makes another harder. Examples: + +- "Getting-started simplicity conflicts with production operational safety" +- "Type-safety strictness conflicts with rapid prototyping flexibility" +- "SSR correctness requires patterns that hurt client-side performance" + +Tensions are where agents fail most because they optimize for one task +without seeing the tradeoff. Each tension should name the skills in +conflict, describe the pull, and state what an agent gets wrong when it +only considers one side. + +Target 2–4 tensions. If you find none, the skills may be too isolated — +revisit whether you're missing cross-connections. + +### 3f — Map cross-references + +Beyond tensions (conflicts) and shared failure modes, identify skills +that illuminate each other without conflicting. A cross-reference means: +"an agent loading skill A would produce better code if it knew about +skill B." These become "See also" pointers in the generated SKILL.md +files. + +For each pair, note: + +- Which skill references which (can be bidirectional) +- Why awareness of the other skill improves output + +Examples: + +- A quickstart skill references the security checklist ("after setup, audit") +- A state management skill references an SSR skill ("state hydration + requires understanding SSR lifecycle") +- A data writing skill references a data reading skill ("writes affect + how queries invalidate") + +Output these in the `cross_references` section of domain_map.yaml. + +### 3g — Identify gaps + +For each skill, explicitly list what you could NOT determine from docs +and source alone. These become interview questions in Phase 4. + +Common gaps: + +- "Docs describe X but don't explain when you'd choose X over Y" +- "Migration guide mentions this changed but doesn't say what the old + behavior was" +- "Source has an assertion here but no doc explains what triggers it" +- "GitHub issues show confusion about X but docs don't address it" +- "I found two patterns for doing X — unclear which is current/preferred" + +### 3h — Discover composition targets + +Scan `package.json` for peer dependencies, optional dependencies, and +`peerDependenciesMeta`. Scan example directories and integration tests +for import patterns. For each frequently co-used library, log: + +- Library name and which features interact +- Whether it's a required or optional integration +- Any example code showing the integration pattern + +These become targeted composition questions in Phase 4e. + +### 3i — Produce the draft + +Write the full `domain_map.yaml` (format in [artifact formats](artifacts.md)) with +a `status: draft` field. Flag every gap in the `gaps` section. + +Present the draft to the maintainer before starting Phase 4: + +> "I've read the docs and source for [library] and produced a draft with +> [N] skills and [M] failure modes. I've flagged [K] specific gaps where +> I need your input." + +Include the full draft domain_map.yaml in your message so the maintainer +can review it. Also include the coverage checklist: all local docs files read +and, when external docs were sampled, the limit, selected URLs, unread areas, +and selection rationale. + +**── STOP ── Do not proceed to Phase 4 until the maintainer has +reviewed the draft and responded. Their response to the draft informs +the detail interview questions.** + +--- diff --git a/packages/intent/meta/generate-skill/SKILL.md b/packages/intent/meta/generate-skill/SKILL.md index 8129fe82..354e1ef5 100644 --- a/packages/intent/meta/generate-skill/SKILL.md +++ b/packages/intent/meta/generate-skill/SKILL.md @@ -1,5 +1,5 @@ --- -name: skill-generate +name: generate-skill description: > Generate a complete SKILL.md file for a library from source documentation and skill tree artifacts. Activate when bootstrapping skills for a new @@ -10,16 +10,9 @@ description: > metadata: version: '1.0' category: meta-tooling - input_artifacts: - - skills/_artifacts/skill_tree.yaml - - skills/_artifacts/domain_map.yaml - - skills/_artifacts/skill_spec.md - - source documentation - output_artifacts: - - SKILL.md - skills: - - skill-tree-generator - - skill-domain-discovery + input_artifacts: 'skills/_artifacts/skill_tree.yaml; skills/_artifacts/domain_map.yaml; skills/_artifacts/skill_spec.md; source documentation' + output_artifacts: 'SKILL.md' + skills: 'tree-generator; domain-discovery' --- # Skill Generation @@ -78,7 +71,7 @@ Read the inputs and classify the skill type: | `security` | Audit checklist or security validation | The skill type determines the frontmatter and body structure. See -skill-tree-generator for the full spec of each type. +tree-generator for the full spec of each type. --- diff --git a/packages/intent/meta/skill-staleness-check/SKILL.md b/packages/intent/meta/skill-staleness-check/SKILL.md index 6cc314c4..2b74193b 100644 --- a/packages/intent/meta/skill-staleness-check/SKILL.md +++ b/packages/intent/meta/skill-staleness-check/SKILL.md @@ -4,19 +4,14 @@ description: > Evaluate intent skills for staleness when source files change in upstream TanStack package repos. Matches changed files against metadata.sources, evaluates whether diffs affect documented behavior, rewrites stale skills - using skill-generate, checks cross-skill references, and opens PRs. + using generate-skill, checks cross-skill references, and opens PRs. Silent when nothing needs updating. metadata: version: '1.0' category: meta-tooling - input_artifacts: - - webhook payload (package name, commit SHA, changed files) - output_artifacts: - - updated SKILL.md files - - pull requests - skills: - - skill-generate - - skill-tree-generator + input_artifacts: 'webhook payload (package name, commit SHA, changed files)' + output_artifacts: 'updated SKILL.md files; pull requests' + skills: 'generate-skill; tree-generator' --- # Skill Staleness Check @@ -116,7 +111,7 @@ affect one line of one skill, or none at all. For skills classified as needing content updates: -1. Load the skill-generate meta skill +1. Load the generate-skill meta skill 2. Provide it with: - The existing SKILL.md content - The source diff diff --git a/packages/intent/meta/tree-generator/SKILL.md b/packages/intent/meta/tree-generator/SKILL.md index 95f710f6..b73f7c8b 100644 --- a/packages/intent/meta/tree-generator/SKILL.md +++ b/packages/intent/meta/tree-generator/SKILL.md @@ -1,5 +1,5 @@ --- -name: skill-tree-generator +name: tree-generator description: > Generate, update, and version a complete skill tree (collection of SKILL.md files) for any JavaScript or TypeScript library. Produces core skills @@ -7,17 +7,13 @@ description: > with dependency linking. Activate when producing skill files from a domain map, updating existing skills after a library version change, or auditing skill accuracy. Takes domain_map.yaml and skill_spec.md from - skill-domain-discovery as primary inputs. + domain-discovery as primary inputs. metadata: version: '3.0' category: meta-tooling - input_artifacts: - - skills/_artifacts/domain_map.yaml - - skills/_artifacts/skill_spec.md - output_artifacts: - - skills/_artifacts/skill_tree.yaml - skills: - - skill-domain-discovery + input_artifacts: 'skills/_artifacts/domain_map.yaml; skills/_artifacts/skill_spec.md' + output_artifacts: 'skills/_artifacts/skill_tree.yaml' + skills: 'domain-discovery' --- # Skill Tree Generator @@ -29,7 +25,7 @@ be portable, concise, and grounded in actual library behavior. ### Skill types -Every skill has a `type` field in its frontmatter. Valid types: +Every skill has a `metadata.type` field in its frontmatter. Valid types: | Type | Purpose | Example | | ------------- | ---------------------------------------------------------- | ------------------------- | @@ -58,13 +54,13 @@ There are two workflows. Detect which applies. You need one of: - `skills/_artifacts/domain_map.yaml` and `skills/_artifacts/skill_spec.md` - from skill-domain-discovery + from domain-discovery - Raw library documentation and source code (run a compressed domain discovery first) If starting from raw docs without a domain map, run a compressed discovery. This produces lower-fidelity output than the full -skill-domain-discovery skill — prefer running that when time permits. +domain-discovery skill — prefer running that when time permits. 1. Build a concept inventory (every export, config key, constraint, warning) 2. Group into capability domains using work-oriented names (let library complexity drive the count — 2–3 for focused libraries, more for large frameworks) @@ -166,7 +162,7 @@ If a library has no framework adapters (e.g. Store, DB), produce only core skills. **Framework-integration domain decomposition:** If the domain map from -skill-domain-discovery contains a single "Framework Integration" domain +domain-discovery contains a single "Framework Integration" domain and the library has separate framework adapter packages, decompose it into per-framework skills co-located with each adapter package. Do not produce a single monolithic framework-integration skill that covers @@ -278,517 +274,57 @@ Run `npx @tanstack/intent@latest edit-package-json` to wire each package's `pack automatically (adds `"skills"`, `"bin"`, and `"!skills/_artifacts"` to the `files` array, and adds the `bin` entry if missing). -### Step 2 — Write the core skill +### Steps 2–7 — Write skills -The core skill is the foundational overview for the library. It covers -framework-agnostic concepts and contains the sub-skill registry. +When writing skill files (after the scaffold plan is approved), read +[the skill-writing procedures and templates](references/write-skills.md). +Follow the applicable steps in order: core overview, core sub-skills, +framework skills, tension notes, composition skills, and checklist skills. +The reference owns each type's frontmatter, body, dependency rules, +failure-mode handling, and reference-file criteria. -**Frontmatter:** - -```yaml ---- -name: '[lib]-core' -description: > - [1–3 sentences. What this library does and the framework-agnostic - concepts it provides. Pack with keywords: function names, config - options, concepts. This is a routing key, not a human summary.] -metadata: - type: core - library: '[lib]' - library_version: '[version this targets]' ---- -``` - -**Body template:** - -```markdown -# [Library Name] — Core Concepts - -[One paragraph: what this library is, what problem it solves. Factual, -not promotional. Framework-agnostic.] - -## Sub-Skills - -| Need to... | Read | -| ---------- | ------------------------------ | -| [task 1] | [lib]-core/[domain-1]/SKILL.md | -| [task 2] | [lib]-core/[domain-2]/SKILL.md | - -## Quick Decision Tree - -- Setting up for the first time? → [lib]-core/[setup-domain] -- Working with [concept]? → [lib]-core/[concept-domain] -- Debugging [issue]? → [lib]-core/[domain] § Common Mistakes - -## Version - -Targets [library] v[X.Y.Z]. -``` - -### Step 3 — Write core sub-skills - -One SKILL.md per domain. Follow this structure exactly. - -**Frontmatter:** - -```yaml ---- -name: '[domain-slug]' -description: > - [1–3 sentences. What this domain covers AND when to load it. Name - specific functions, options, or APIs. Dense routing key.] -metadata: - type: sub-skill - library: '[lib]' - library_version: '[version]' -sources: - - '[repo]:docs/[path].md' - - '[repo]:src/[path].ts' ---- -``` - -**Body sections — in this order:** - -**1. Setup** - -Minimum working example for this domain. - -- Use the library's core API, not framework-specific hooks -- Real package imports with exact names -- No `// ...` or `[your code here]` — complete and copy-pasteable -- If a concept is better explained with a framework hook, reference the - framework skill: "For React usage, see `react-[lib]/SKILL.md`" - -**2. Core Patterns** - -2–4 patterns. For each: - -- One-line heading: what it accomplishes -- Complete code block using core API -- One sentence of explanation only if not self-explanatory -- No framework-specific code — use core abstractions - -**3. Common Mistakes** - -Each `failure_mode` entry from the domain map becomes a Common Mistake -entry in the SKILL file. Minimum 3 entries. Complex domains target 5–6. - -**Cross-skill failure modes:** The domain map may contain failure modes -with a `skills` list naming multiple skill slugs. Write these into -every SKILL file whose skill is listed. A developer loading the SSR -skill and a developer loading the state management skill both need to -see "stale state during hydration" — the same advice must appear in -both files. Do not deduplicate across skills at the cost of coverage. - -Format: - -````markdown -### [PRIORITY] [What goes wrong — 5–8 word phrase] - -Wrong: - -```[lang] -// code that looks correct but isn't -``` -```` - -Correct: - -```[lang] -// code that works -``` - -[One sentence: the specific mechanism by which the wrong version fails.] - -Source: [doc page or source file:line] - -```` - -Priority levels: -- **CRITICAL** — Breaks in production. Security risk or data loss. -- **HIGH** — Incorrect behavior under common conditions. -- **MEDIUM** — Incorrect under specific conditions or edge cases. - -Every mistake must be plausible (an agent would generate it), silent -(no immediate crash), and grounded (traceable to doc or source). - -**Failure mode status from domain map:** The domain map may include a -`status` field on failure modes. Handle as follows: -- `active` — Include as a normal Common Mistake entry -- `fixed-but-legacy-risk` — Include with a note: "Fixed in v[X] but - agents trained on older code may still generate this pattern" -- `removed` — Do not include. The bug is fixed and the pattern is no - longer relevant. - -**4. References** (only when needed) - -```markdown -## References - -- [Complete option reference](references/options.md) -```` - -Create reference files when any of these apply — not just length overflow: - -- **Length:** The skill would exceed 500 lines without them -- **Multiple subsystems:** The domain covers 3+ independent backends, - adapters, or providers with distinct config interfaces. Create one - reference file per subsystem (e.g. `references/electric-adapter.md`, - `references/query-adapter.md`) -- **Dense API surface:** A topic has >10 distinct API patterns, operators, - or option shapes that agents need for implementation. Move the full - reference to `references/` and keep only the most common 2–3 in the - SKILL.md -- **Deep validation/schema patterns:** If the library has schema - validation, type transforms (TInput/TOutput), or similar deep - configuration surfaces, give them a dedicated reference file even if - they technically fit in the parent skill - -### Step 4 — Write framework skills - -Framework skills build on their core skill. They cover only what is -specific to the framework — hooks, components, providers, and -framework-specific patterns and mistakes. - -**Frontmatter:** - -```yaml ---- -name: 'react-[lib]' -description: > - [1–3 sentences. React-specific bindings for [library]. Name the hooks, - components, and providers. Mention React-specific patterns like SSR - hydration if applicable.] -metadata: - type: framework - library: '[lib]' - framework: react - library_version: '[version]' -requires: - - '[lib]-core' ---- -``` - -**Body template:** - -```markdown -This skill builds on [lib]-core. Read [lib]-core first for foundational -concepts before applying React-specific patterns. - -# [Library Name] — React - -## Setup - -[React-specific setup: provider, hook wiring, app entry point] - -## Hooks and Components - -[React hooks and components with complete examples] - -## React-Specific Patterns - -[Patterns that only apply in React: concurrent features, Suspense -integration, SSR/hydration, etc.] - -## Common Mistakes - -[Only React-specific mistakes. Do not repeat core mistakes. Examples: -calling hooks outside provider, missing Suspense boundary, hydration -mismatch, etc.] -``` - -**Framework sub-skills** follow the same pattern as core sub-skills but -with the framework frontmatter: - -```yaml ---- -name: '[domain-slug]' -description: > - [React-specific description for this domain.] -metadata: - type: sub-skill - library: '[lib]' - framework: react - library_version: '[version]' -requires: - - '[lib]-core' - - '[lib]-core/[domain-slug]' ---- -This skill builds on [lib]-core/[domain-slug]. Read the core skill first. -``` - -### Step 5 — Write cross-domain tension notes - -The domain map may contain a `tensions` section listing design conflicts -between domains. For each tension, add a brief note to the Common -Mistakes section of every SKILL file whose domain is involved. Format: - -```markdown -### HIGH Tension: [short phrase] - -This domain's patterns conflict with [other domain]. [One sentence -describing the pull.] Agents optimizing for [this domain's goal] -tend to [specific mistake] because they don't account for [other -domain's constraint]. - -See also: [lib]-core/[other-domain]/SKILL.md § Common Mistakes -``` - -The cross-reference ensures agents that load one skill are pointed -toward the related skill where the other side of the tension lives. - -Also check the domain map's `cross_references` section for non-tension -relationships between skills. For each cross-reference, add a "See also" -line at the end of the relevant skill's body: - -```markdown -See also: [other-skill]/SKILL.md — [reason] -``` - -### Step 6 — Write composition skills (if applicable) - -Use the `compositions` entries from `domain_map.yaml` (populated during -skill-domain-discovery Phase 3h) to identify which composition skills -to produce. - -Composition skills cover how two or more libraries work together. These -are framework-specific by default (the integration patterns depend on -framework hooks and providers). - -**Frontmatter:** - -```yaml ---- -name: '[lib-a]-[lib-b]' -description: > - [How lib-a and lib-b wire together. Name the specific integration - points: functions, hooks, patterns.] -metadata: - type: composition - library_version: '[version of primary lib]' -requires: - - '[lib-a]-core' - - 'react-[lib-a]' - - '[lib-b]-core' - - 'react-[lib-b]' ---- -This skill requires familiarity with both [lib-a] and [lib-b]. -Read their core and framework skills first. -``` - -**Body structure:** - -1. **Integration Setup** — How to wire the two libraries together -2. **Core Integration Patterns** — 2–4 patterns showing them working in concert -3. **Common Mistakes** — Mistakes that only occur at the integration boundary - -Do not duplicate content from either library's individual skills. Focus -exclusively on the seam between them. - -### Step 7 — Write checklist/audit skills (where applicable) - -Some skills don't fit the standard body structure (Setup → Core Patterns -→ Common Mistakes). Security, go-live, and some lifecycle skills are -audit-oriented — the agent runs through a checklist to verify correctness -rather than learning patterns. Use the alternative body structure below -for these skill types. - -**When to use the checklist body:** - -- `security` type skills — pre-deploy security validation -- `lifecycle` type skills focused on verification (go-live, migration) -- Any skill where the primary action is "check these things" not "learn - these patterns" - -**Frontmatter:** - -```yaml ---- -name: security -description: > - Go-live security validation for [library]. Checks [specific concerns]. -metadata: - type: security - library: '[lib]' - framework: react - library_version: '[version]' -requires: - - 'react-[lib]' ---- -``` - -**Alternative body template (checklist/audit):** - -````markdown -# [Library Name] — [Security | Go-Live | Migration] Checklist - -Run through each section before [deploying | releasing | migrating]. - -## [Category 1] Checks - -### Check: [what to verify] - -Expected: - -```[lang] -// correct configuration or code -``` -```` - -Fail condition: [what indicates this check failed] -Fix: [one-line remediation] - -### Check: [what to verify] - -[same structure] - -## [Category 2] Checks - -[same structure] - -## Common Security Mistakes - -[Wrong/correct pairs specific to this library, same format as -Common Mistakes in standard skills] - -## Pre-Deploy Summary - -- [ ] [Verification 1] -- [ ] [Verification 2] -- [ ] [Verification 3] - -```` - -The key differences from the standard body: -- No "Setup" section — the agent already has the app running -- Checks replace "Core Patterns" — each check is a verification, not a - teaching pattern -- The summary checklist at the end gives agents a quick pass/fail list -- Common Mistakes section is still present for wrong/correct pairs +For the minimal-library fast path, use Step 3's body format for standalone +core skills. For scaffold-only requests, stop after `skill_tree.yaml`. ### Step 8 — Validate the complete tree Run every check before outputting. Fix any failures before proceeding. -| Check | Rule | -|-------|------| -| Every skill from domain_map has a SKILL.md | No orphaned skills | -| Core/framework split is clean | No framework hooks in core skills | -| Every framework skill has `requires` | Links to its core skill | -| Framework skill opens with dependency note | "builds on [core]" prose line | -| Every skill under 500 lines | Move excess to references/ | -| Every code block has real imports | Exact package name, correct adapter | -| No concept explanations | No "TypeScript is...", no "React hooks are..." | -| No marketing prose | First body line is heading or dependency note | -| Every code block is complete | Works without modification when pasted | -| Common Mistakes are silent | Not obvious compile errors | -| Common Mistakes are library-specific | Not generic TS/React mistakes | -| Common Mistakes are sourced | Every mistake traceable to doc or source | -| Core skills reference framework skills | "For React usage, see..." | -| Framework skills don't repeat core content | Only framework-specific | -| Composition skills don't repeat individual skills | Only the seam | -| `name` matches directory path | `router-core/search-params` → `router-core/search-params/SKILL.md` | -| `sources` filled in sub-skills | At least one repo:path per sub-skill | -| Cross-skill failures in all relevant files | Failure modes with multiple `skills` appear in each listed SKILL.md | -| Tensions noted in affected skills | Each tension has notes in all involved domain skills | -| Framework domains decomposed per-package | No single skill covering multiple framework adapters | -| Adapter-heavy domains have references | 3+ adapters/backends → one reference file per adapter | -| Dense API surfaces in references | >10 distinct patterns → reference file, not inline | -| Checklist skills use audit body | Security/go-live skills use checklist template, not Setup → Core Patterns → Common Mistakes | +| Check | Rule | +| ------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| Every skill from domain_map has a SKILL.md | No orphaned skills | +| Core/framework split is clean | No framework hooks in core skills | +| Every framework skill has `requires` | Links to its core skill | +| Framework skill opens with dependency note | "builds on [core]" prose line | +| Every skill under 500 lines | Move excess to references/ | +| Every code block has real imports | Exact package name, correct adapter | +| No concept explanations | No "TypeScript is...", no "React hooks are..." | +| No marketing prose | First body line is heading or dependency note | +| Every code block is complete | Works without modification when pasted | +| Common Mistakes are silent | Not obvious compile errors | +| Common Mistakes are library-specific | Not generic TS/React mistakes | +| Common Mistakes are sourced | Every mistake traceable to doc or source | +| Core skills reference framework skills | "For React usage, see..." | +| Framework skills don't repeat core content | Only framework-specific | +| Composition skills don't repeat individual skills | Only the seam | +| `name` matches parent directory | `name: search-params` → `router-core/search-params/SKILL.md` | +| `sources` filled in sub-skills | At least one repo:path per sub-skill | +| Cross-skill failures in all relevant files | Failure modes with multiple `skills` appear in each listed SKILL.md | +| Tensions noted in affected skills | Each tension has notes in all involved domain skills | +| Framework domains decomposed per-package | No single skill covering multiple framework adapters | +| Adapter-heavy domains have references | 3+ adapters/backends → one reference file per adapter | +| Dense API surfaces in references | >10 distinct patterns → reference file, not inline | +| Checklist skills use audit body | Security/go-live skills use checklist template, not Setup → Core Patterns → Common Mistakes | --- ## Workflow B — Update existing skills -### Trigger conditions - -Run when: -- The library has released a new version -- A maintainer reports skills produce outdated code -- A changelog or migration guide has been published since skill creation -- Issue reports indicate skill content is inaccurate - -### Step 1 — Detect staleness - -Compare each skill's `library_version` against the current library version. - -1. Read changelog entries between the two versions -2. Read migration guide (if one exists) -3. For each skill, check if its `sources` files have changed - -Produce a staleness report: - -```yaml -# staleness_report.yaml -library: "[name]" -library_version_in_skills: "[old]" -library_version_current: "[new]" - -stale_skills: - - skill: "[skill name]" - reason: "[what changed]" - severity: "[BREAKING | DEPRECATION | BEHAVIORAL | ADDITIVE]" - changelog_entry: "[relevant entry]" - affected_sections: - - "[Setup | Core Patterns | Common Mistakes]" - -current_skills: - - skill: "[skill name]" - reason: "[no changes affect this domain]" -```` - -### Step 2 — Update stale skills - -**BREAKING changes:** - -1. Old pattern becomes a new Common Mistake entry (wrong/correct pair) -2. Update Setup if initialization changed -3. Update Core Patterns if idiomatic approach changed -4. Bump `library_version` in frontmatter -5. Check both core AND framework skills — breaking changes may affect both - -**DEPRECATION changes:** - -1. Add Common Mistake: deprecated API as wrong, replacement as correct -2. Update Core Patterns to use non-deprecated API -3. Bump `library_version` - -**BEHAVIORAL changes:** - -1. Default value changed → add Common Mistake entry -2. Type signature more restrictive → add Common Mistake entry -3. Update affected code blocks -4. Bump `library_version` - -**ADDITIVE changes:** - -1. Evaluate if new feature belongs in existing domain or needs a new skill -2. If existing: add to Core Patterns or references/ -3. If new skill needed: create it and update the parent skill's sub-skill - registry -4. Bump `library_version` - -### Step 3 — Produce a changelog entry - -```markdown -## [date] - -### Updated for [library] v[new version] - -**Breaking changes:** - -- [skill name]: [what changed and why] - -**Deprecation updates:** - -- [skill name]: [old API] → [new API] - -**New skills:** - -- [skill name]: [what it covers] -``` - ---- +When a library version, changelog, migration guide, or accuracy report +requires updating existing skills, read [the update workflow](references/update-skills.md). +Produce its staleness report, update the affected skills according to the +change category, and write the changelog entry. Apply the constraints below +to every updated file. ## Constraints — verify for every file @@ -806,7 +342,7 @@ current_skills: | Framework skills have `requires` | Lists core dependency | | Framework skills open with dependency note | First prose line references core | | Composition skills require all dependencies | Lists all core + framework skills | -| `name` matches directory | `router-core/search-params` → file at that path | +| `name` matches parent directory | `name: search-params` → `router-core/search-params/SKILL.md` | | `library_version` in every frontmatter | Which version the skill targets | | Cross-skill failures duplicated | Each listed skill gets the failure mode | | Tensions cross-referenced | Tension notes in each involved skill point to the other | diff --git a/packages/intent/meta/tree-generator/references/update-skills.md b/packages/intent/meta/tree-generator/references/update-skills.md new file mode 100644 index 00000000..5483dbd6 --- /dev/null +++ b/packages/intent/meta/tree-generator/references/update-skills.md @@ -0,0 +1,92 @@ +## Workflow B — Update existing skills + +### Trigger conditions + +Run when: + +- The library has released a new version +- A maintainer reports skills produce outdated code +- A changelog or migration guide has been published since skill creation +- Issue reports indicate skill content is inaccurate + +### Step 1 — Detect staleness + +Compare each skill's `library_version` against the current library version. + +1. Read changelog entries between the two versions +2. Read migration guide (if one exists) +3. For each skill, check if its `sources` files have changed + +Produce a staleness report: + +```yaml +# staleness_report.yaml +library: '[name]' +library_version_in_skills: '[old]' +library_version_current: '[new]' + +stale_skills: + - skill: '[skill name]' + reason: '[what changed]' + severity: '[BREAKING | DEPRECATION | BEHAVIORAL | ADDITIVE]' + changelog_entry: '[relevant entry]' + affected_sections: + - '[Setup | Core Patterns | Common Mistakes]' + +current_skills: + - skill: '[skill name]' + reason: '[no changes affect this domain]' +``` + +### Step 2 — Update stale skills + +**BREAKING changes:** + +1. Old pattern becomes a new Common Mistake entry (wrong/correct pair) +2. Update Setup if initialization changed +3. Update Core Patterns if idiomatic approach changed +4. Bump `library_version` in frontmatter +5. Check both core AND framework skills — breaking changes may affect both + +**DEPRECATION changes:** + +1. Add Common Mistake: deprecated API as wrong, replacement as correct +2. Update Core Patterns to use non-deprecated API +3. Bump `library_version` + +**BEHAVIORAL changes:** + +1. Default value changed → add Common Mistake entry +2. Type signature more restrictive → add Common Mistake entry +3. Update affected code blocks +4. Bump `library_version` + +**ADDITIVE changes:** + +1. Evaluate if new feature belongs in existing domain or needs a new skill +2. If existing: add to Core Patterns or references/ +3. If new skill needed: create it and update the parent skill's sub-skill + registry +4. Bump `library_version` + +### Step 3 — Produce a changelog entry + +```markdown +## [date] + +### Updated for [library] v[new version] + +**Breaking changes:** + +- [skill name]: [what changed and why] + +**Deprecation updates:** + +- [skill name]: [old API] → [new API] + +**New skills:** + +- [skill name]: [what it covers] +``` + +--- diff --git a/packages/intent/meta/tree-generator/references/write-skills.md b/packages/intent/meta/tree-generator/references/write-skills.md new file mode 100644 index 00000000..242514dd --- /dev/null +++ b/packages/intent/meta/tree-generator/references/write-skills.md @@ -0,0 +1,386 @@ +### Step 2 — Write the core skill + +The core skill is the foundational overview for the library. It covers +framework-agnostic concepts and contains the sub-skill registry. + +**Frontmatter:** + +```yaml +--- +name: '[lib]-core' +description: > + [1–3 sentences. What this library does and the framework-agnostic + concepts it provides. Pack with keywords: function names, config + options, concepts. This is a routing key, not a human summary.] +metadata: + type: core + library: '[lib]' + library_version: '[version this targets]' +--- +``` + +**Body template:** + +```markdown +# [Library Name] — Core Concepts + +[One paragraph: what this library is, what problem it solves. Factual, +not promotional. Framework-agnostic.] + +## Sub-Skills + +| Need to... | Read | +| ---------- | ------------------------------ | +| [task 1] | [lib]-core/[domain-1]/SKILL.md | +| [task 2] | [lib]-core/[domain-2]/SKILL.md | + +## Quick Decision Tree + +- Setting up for the first time? → [lib]-core/[setup-domain] +- Working with [concept]? → [lib]-core/[concept-domain] +- Debugging [issue]? → [lib]-core/[domain] § Common Mistakes + +## Version + +Targets [library] v[X.Y.Z]. +``` + +### Step 3 — Write core sub-skills + +One SKILL.md per domain. Follow this structure exactly. + +**Frontmatter:** + +```yaml +--- +name: '[domain-slug]' +description: > + [1–3 sentences. What this domain covers AND when to load it. Name + specific functions, options, or APIs. Dense routing key.] +metadata: + type: sub-skill + library: '[lib]' + library_version: '[version]' +sources: + - '[repo]:docs/[path].md' + - '[repo]:src/[path].ts' +--- +``` + +**Body sections — in this order:** + +**1. Setup** + +Minimum working example for this domain. + +- Use the library's core API, not framework-specific hooks +- Real package imports with exact names +- No `// ...` or `[your code here]` — complete and copy-pasteable +- If a concept is better explained with a framework hook, reference the + framework skill: "For React usage, see `react-[lib]/SKILL.md`" + +**2. Core Patterns** + +2–4 patterns. For each: + +- One-line heading: what it accomplishes +- Complete code block using core API +- One sentence of explanation only if not self-explanatory +- No framework-specific code — use core abstractions + +**3. Common Mistakes** + +Each `failure_mode` entry from the domain map becomes a Common Mistake +entry in the SKILL file. Minimum 3 entries. Complex domains target 5–6. + +**Cross-skill failure modes:** The domain map may contain failure modes +with a `skills` list naming multiple skill slugs. Write these into +every SKILL file whose skill is listed. A developer loading the SSR +skill and a developer loading the state management skill both need to +see "stale state during hydration" — the same advice must appear in +both files. Do not deduplicate across skills at the cost of coverage. + +Format: + +````markdown +### [PRIORITY] [What goes wrong — 5–8 word phrase] + +Wrong: + +```[lang] +// code that looks correct but isn't +``` + +Correct: + +```[lang] +// code that works +``` + +[One sentence: the specific mechanism by which the wrong version fails.] + +Source: [doc page or source file:line] +```` + +Priority levels: + +- **CRITICAL** — Breaks in production. Security risk or data loss. +- **HIGH** — Incorrect behavior under common conditions. +- **MEDIUM** — Incorrect under specific conditions or edge cases. + +Every mistake must be plausible (an agent would generate it), silent +(no immediate crash), and grounded (traceable to doc or source). + +**Failure mode status from domain map:** The domain map may include a +`status` field on failure modes. Handle as follows: + +- `active` — Include as a normal Common Mistake entry +- `fixed-but-legacy-risk` — Include with a note: "Fixed in v[X] but + agents trained on older code may still generate this pattern" +- `removed` — Do not include. The bug is fixed and the pattern is no + longer relevant. + +**4. References** (only when needed) + +```markdown +## References + +- [Complete option reference](references/options.md) +``` + +Create reference files when any of these apply — not just length overflow: + +- **Length:** The skill would exceed 500 lines without them +- **Multiple subsystems:** The domain covers 3+ independent backends, + adapters, or providers with distinct config interfaces. Create one + reference file per subsystem (e.g. `references/electric-adapter.md`, + `references/query-adapter.md`) +- **Dense API surface:** A topic has >10 distinct API patterns, operators, + or option shapes that agents need for implementation. Move the full + reference to `references/` and keep only the most common 2–3 in the + SKILL.md +- **Deep validation/schema patterns:** If the library has schema + validation, type transforms (TInput/TOutput), or similar deep + configuration surfaces, give them a dedicated reference file even if + they technically fit in the parent skill + +### Step 4 — Write framework skills + +Framework skills build on their core skill. They cover only what is +specific to the framework — hooks, components, providers, and +framework-specific patterns and mistakes. + +**Frontmatter:** + +```yaml +--- +name: 'react-[lib]' +description: > + [1–3 sentences. React-specific bindings for [library]. Name the hooks, + components, and providers. Mention React-specific patterns like SSR + hydration if applicable.] +metadata: + type: framework + library: '[lib]' + framework: react + library_version: '[version]' +requires: + - '[lib]-core' +--- +``` + +**Body template:** + +```markdown +This skill builds on [lib]-core. Read [lib]-core first for foundational +concepts before applying React-specific patterns. + +# [Library Name] — React + +## Setup + +[React-specific setup: provider, hook wiring, app entry point] + +## Hooks and Components + +[React hooks and components with complete examples] + +## React-Specific Patterns + +[Patterns that only apply in React: concurrent features, Suspense +integration, SSR/hydration, etc.] + +## Common Mistakes + +[Only React-specific mistakes. Do not repeat core mistakes. Examples: +calling hooks outside provider, missing Suspense boundary, hydration +mismatch, etc.] +``` + +**Framework sub-skills** follow the same pattern as core sub-skills but +with the framework frontmatter: + +```yaml +--- +name: '[domain-slug]' +description: > + [React-specific description for this domain.] +metadata: + type: sub-skill + library: '[lib]' + framework: react + library_version: '[version]' +requires: + - '[lib]-core' + - '[lib]-core/[domain-slug]' +--- +This skill builds on [lib]-core/[domain-slug]. Read the core skill first. +``` + +### Step 5 — Write cross-domain tension notes + +The domain map may contain a `tensions` section listing design conflicts +between domains. For each tension, add a brief note to the Common +Mistakes section of every SKILL file whose domain is involved. Format: + +```markdown +### HIGH Tension: [short phrase] + +This domain's patterns conflict with [other domain]. [One sentence +describing the pull.] Agents optimizing for [this domain's goal] +tend to [specific mistake] because they don't account for [other +domain's constraint]. + +See also: [lib]-core/[other-domain]/SKILL.md § Common Mistakes +``` + +The cross-reference ensures agents that load one skill are pointed +toward the related skill where the other side of the tension lives. + +Also check the domain map's `cross_references` section for non-tension +relationships between skills. For each cross-reference, add a "See also" +line at the end of the relevant skill's body: + +```markdown +See also: [other-skill]/SKILL.md — [reason] +``` + +### Step 6 — Write composition skills (if applicable) + +Use the `compositions` entries from `domain_map.yaml` (populated during +domain-discovery Phase 3h) to identify which composition skills +to produce. + +Composition skills cover how two or more libraries work together. These +are framework-specific by default (the integration patterns depend on +framework hooks and providers). + +**Frontmatter:** + +```yaml +--- +name: '[lib-a]-[lib-b]' +description: > + [How lib-a and lib-b wire together. Name the specific integration + points: functions, hooks, patterns.] +metadata: + type: composition + library_version: '[version of primary lib]' +requires: + - '[lib-a]-core' + - 'react-[lib-a]' + - '[lib-b]-core' + - 'react-[lib-b]' +--- +This skill requires familiarity with both [lib-a] and [lib-b]. +Read their core and framework skills first. +``` + +**Body structure:** + +1. **Integration Setup** — How to wire the two libraries together +2. **Core Integration Patterns** — 2–4 patterns showing them working in concert +3. **Common Mistakes** — Mistakes that only occur at the integration boundary + +Do not duplicate content from either library's individual skills. Focus +exclusively on the seam between them. + +### Step 7 — Write checklist/audit skills (where applicable) + +Some skills don't fit the standard body structure (Setup → Core Patterns +→ Common Mistakes). Security, go-live, and some lifecycle skills are +audit-oriented — the agent runs through a checklist to verify correctness +rather than learning patterns. Use the alternative body structure below +for these skill types. + +**When to use the checklist body:** + +- `security` type skills — pre-deploy security validation +- `lifecycle` type skills focused on verification (go-live, migration) +- Any skill where the primary action is "check these things" not "learn + these patterns" + +**Frontmatter:** + +```yaml +--- +name: security +description: > + Go-live security validation for [library]. Checks [specific concerns]. +metadata: + type: security + library: '[lib]' + framework: react + library_version: '[version]' +requires: + - 'react-[lib]' +--- +``` + +**Alternative body template (checklist/audit):** + +````markdown +# [Library Name] — [Security | Go-Live | Migration] Checklist + +Run through each section before [deploying | releasing | migrating]. + +## [Category 1] Checks + +### Check: [what to verify] + +Expected: + +```[lang] +// correct configuration or code +``` + +Fail condition: [what indicates this check failed] +Fix: [one-line remediation] + +### Check: [what to verify] + +[same structure] + +## [Category 2] Checks + +[same structure] + +## Common Security Mistakes + +[Wrong/correct pairs specific to this library, same format as +Common Mistakes in standard skills] + +## Pre-Deploy Summary + +- [ ] [Verification 1] +- [ ] [Verification 2] +- [ ] [Verification 3] +```` + +The key differences from the standard body: + +- No "Setup" section — the agent already has the app running +- Checks replace "Core Patterns" — each check is a verification, not a + teaching pattern +- The summary checklist at the end gives agents a quick pass/fail list +- Common Mistakes section is still present for wrong/correct pairs diff --git a/packages/intent/src/commands/meta.ts b/packages/intent/src/commands/meta.ts index d75d49d2..1090ef90 100644 --- a/packages/intent/src/commands/meta.ts +++ b/packages/intent/src/commands/meta.ts @@ -1,6 +1,7 @@ import { existsSync, readFileSync, readdirSync } from 'node:fs' -import { join } from 'node:path' +import { dirname, join } from 'node:path' import { fail } from '../shared/cli-error.js' +import { rewriteLoadedSkillMarkdownDestinations } from '../core/markdown.js' export async function runMetaCommand( name: string | undefined, @@ -23,7 +24,14 @@ export async function runMetaCommand( } try { - console.log(readFileSync(skillFile, 'utf8')) + console.log( + rewriteLoadedSkillMarkdownDestinations({ + content: readFileSync(skillFile, 'utf8'), + cwd: process.cwd(), + packageRoot: dirname(metaDir), + skillFilePath: skillFile, + }), + ) } catch (err) { const msg = err instanceof Error ? err.message : String(err) fail(`Failed to read meta-skill "${name}": ${msg}`) diff --git a/packages/intent/src/core/project-context.ts b/packages/intent/src/core/project-context.ts index 2549587f..6d9ad2b5 100644 --- a/packages/intent/src/core/project-context.ts +++ b/packages/intent/src/core/project-context.ts @@ -18,7 +18,7 @@ export type ProjectContext = { /** * Resolves project structure by walking up from targetPath (or cwd) to find the * owning package.json, then searches for a workspace root from the package root. - * Falls back to searching from cwd when targetPath points deep into a package. + * The target owns its ancestry; caller cwd cannot override a standalone target. */ export function resolveProjectContext({ cwd, @@ -32,9 +32,7 @@ export function resolveProjectContext({ ? resolve(resolvedCwd, targetPath) : resolvedCwd const packageRoot = findOwningPackageRoot(resolvedTargetPath) - const workspaceRoot = - findWorkspaceRoot(packageRoot ?? resolvedTargetPath) ?? - findWorkspaceRoot(resolvedCwd) + const workspaceRoot = findWorkspaceRoot(packageRoot ?? resolvedTargetPath) const workspacePatterns = workspaceRoot ? (readWorkspacePatterns(workspaceRoot) ?? []) : [] @@ -61,6 +59,8 @@ function findOwningPackageRoot(startPath: string): string | null { return dir } + if (existsSync(join(dir, '.git'))) return null + prev = dir dir = dirname(dir) } diff --git a/packages/intent/src/setup/workspace-patterns.ts b/packages/intent/src/setup/workspace-patterns.ts index 08005eed..48a7488f 100644 --- a/packages/intent/src/setup/workspace-patterns.ts +++ b/packages/intent/src/setup/workspace-patterns.ts @@ -3,6 +3,7 @@ import { dirname, join } from 'node:path' import { parse as parseJsonc } from 'jsonc-parser' import { parse as parseYaml } from 'yaml' import { hasAnySkillFile } from '../shared/utils.js' +import { readPackageJson } from '../core/package-json.js' import type { ParseError } from 'jsonc-parser' function normalizeWorkspacePattern(pattern: string): string { @@ -75,10 +76,6 @@ function readYamlFile(path: string): unknown { return parseYaml(readFileSync(path, 'utf8')) } -function readJsonFile(path: string): unknown { - return JSON.parse(readFileSync(path, 'utf8')) -} - function readJsoncFile(path: string): unknown { const errors: Array = [] const value = parseJsonc(readFileSync(path, 'utf8'), errors, { @@ -131,7 +128,7 @@ const workspacePatternSources: Array = [ }, { fileName: 'package.json', - read: readJsonFile, + read: (path) => readPackageJson(dirname(path)), getPatterns: (config) => parseWorkspacePatternField( isRecord(config) ? config.workspaces : undefined, @@ -180,12 +177,18 @@ function readWorkspacePatternsUncached(root: string): Array | null { for (const source of workspacePatternSources) { const path = join(root, source.fileName) - if (!existsSync(path)) { + if (source.fileName !== 'package.json' && !existsSync(path)) { continue } + // An unreadable ancestor may own inherited policy. Never turn it into + // a cached "no workspace" result, even when a child has its own allowlist. + const packageJson = + source.fileName === 'package.json' ? source.read(path) : undefined try { - const patterns = source.getPatterns(source.read(path)) + const patterns = source.getPatterns( + source.fileName === 'package.json' ? packageJson : source.read(path), + ) if (patterns) { return patterns } @@ -345,6 +348,10 @@ export function findWorkspaceRoot(start: string): string | null { return dir } + // Both a checkout directory and a worktree .git file bound ancestry. + // Check this directory's manifest first so root policy still applies. + if (existsSync(join(dir, '.git'))) break + prev = dir dir = dirname(dir) } diff --git a/packages/intent/tests/cli.test.ts b/packages/intent/tests/cli.test.ts index f08d02f9..c57e6348 100644 --- a/packages/intent/tests/cli.test.ts +++ b/packages/intent/tests/cli.test.ts @@ -158,11 +158,26 @@ describe('intent meta', () => { expect(output).toContain('skill-staleness-check') }) - it('prints the requested meta-skill content', async () => { + it('prints meta-skill content with reference paths usable from the caller', async () => { + const root = mkdtempSync(join(realTmpdir, 'intent-meta-reference-')) + tempDirs.push(root) + process.chdir(root) const expected = readFileSync( join(metaDir, 'domain-discovery', 'SKILL.md'), 'utf8', ) + .replaceAll( + '(references/deep-read.md)', + `(${join(metaDir, 'domain-discovery', 'references', 'deep-read.md')})`, + ) + .replaceAll( + '(references/deep-read.md#reading-order)', + `(${join(metaDir, 'domain-discovery', 'references', 'deep-read.md')}#reading-order)`, + ) + .replaceAll( + '(references/artifacts.md)', + `(${join(metaDir, 'domain-discovery', 'references', 'artifacts.md')})`, + ) const exitCode = await main(['meta', 'domain-discovery']) diff --git a/packages/intent/tests/core.test.ts b/packages/intent/tests/core.test.ts index 23b43c65..55f04c87 100644 --- a/packages/intent/tests/core.test.ts +++ b/packages/intent/tests/core.test.ts @@ -160,28 +160,70 @@ describe('listIntentSkills', () => { ).toThrow(packageJsonPath) }) - it('rejects malformed inherited policy even when the child permits the skill', () => { - const appDir = join(root, 'packages', 'app') + it.each(['npm', 'pnpm'])( + 'rejects malformed inherited %s policy even when the child permits the skill', + (manager) => { + const appDir = join(root, 'packages', 'app') + const packageJsonPath = join(root, 'package.json') + if (manager === 'pnpm') + writeFileSync( + join(root, 'pnpm-workspace.yaml'), + 'packages:\n - packages/*\n', + ) + writeFileSync( + packageJsonPath, + '{"workspaces":["packages/*"],"intent":{"exclude":["@tanstack/query"]},', + ) + writeJson(join(appDir, 'package.json'), { + name: 'app', + intent: { skills: ['@tanstack/query'] }, + }) + writeInstalledIntentPackage(appDir, { + name: '@tanstack/query', + version: '5.0.0', + skillName: 'fetching', + description: 'Query data fetching patterns', + }) + + expect(() => listIntentSkills({ cwd: appDir })).toThrow(packageJsonPath) + expect(() => + loadIntentSkill('@tanstack/query#fetching', { cwd: appDir }), + ).toThrow(packageJsonPath) + }, + ) + + it.each(['directory', 'file'])( + 'does not inspect unrelated ancestors above a Git %s boundary', + (marker) => { + writeFileSync(join(root, 'package.json'), '{ invalid ancestor') + const appDir = join(root, 'app') + writeJson(join(appDir, 'package.json'), { + name: 'app', + intent: { skills: ['@tanstack/query'] }, + }) + if (marker === 'directory') mkdirSync(join(appDir, '.git')) + else writeFileSync(join(appDir, '.git'), 'gitdir: /unrelated/git') + writeInstalledIntentPackage(appDir, { + name: '@tanstack/query', + version: '5.0.0', + skillName: 'fetching', + description: 'Query data fetching patterns', + }) + expect(listIntentSkills({ cwd: appDir }).skills).toHaveLength(1) + expect( + loadIntentSkill('@tanstack/query#fetching', { cwd: appDir }).content, + ).toContain('Skill content here.') + }, + ) + + it('refuses an ambiguous malformed ancestor without an independent boundary', () => { const packageJsonPath = join(root, 'package.json') - writeFileSync( - join(root, 'pnpm-workspace.yaml'), - 'packages:\n - packages/*\n', - ) - writeFileSync( - packageJsonPath, - '{"workspaces":["packages/*"],"intent":{"exclude":["@tanstack/query"]},', - ) + writeFileSync(packageJsonPath, '{ invalid ancestor') + const appDir = join(root, 'app') writeJson(join(appDir, 'package.json'), { name: 'app', - intent: { skills: ['@tanstack/query'] }, - }) - writeInstalledIntentPackage(appDir, { - name: '@tanstack/query', - version: '5.0.0', - skillName: 'fetching', - description: 'Query data fetching patterns', + intent: { skills: ['*'] }, }) - expect(() => listIntentSkills({ cwd: appDir })).toThrow(packageJsonPath) expect(() => loadIntentSkill('@tanstack/query#fetching', { cwd: appDir }), diff --git a/packages/intent/tests/integration/packed-release.test.ts b/packages/intent/tests/integration/packed-release.test.ts new file mode 100644 index 00000000..6c4ab312 --- /dev/null +++ b/packages/intent/tests/integration/packed-release.test.ts @@ -0,0 +1,207 @@ +import { execFileSync, spawnSync } from 'node:child_process' +import { + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + readdirSync, + realpathSync, + rmSync, + statSync, + symlinkSync, + writeFileSync, +} from 'node:fs' +import { tmpdir } from 'node:os' +import { dirname, join } from 'node:path' +import { fileURLToPath, pathToFileURL } from 'node:url' +import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest' + +const packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..', '..') +const timeout = 30_000 +let root: string +let installedRoot: string +let cli: string +let cwd: string +let packedFiles: Array + +function run(args: Array) { + return spawnSync(process.execPath, [cli, ...args], { + cwd, + encoding: 'utf8', + timeout, + }) +} + +beforeAll(() => { + root = realpathSync(mkdtempSync(join(tmpdir(), 'intent-packed-release-'))) + const packed = JSON.parse( + execFileSync( + 'npm', + ['pack', '--ignore-scripts', '--json', '--pack-destination', root], + { + cwd: packageRoot, + encoding: 'utf8', + timeout, + env: { ...process.env, npm_config_cache: join(root, 'npm-cache') }, + }, + ), + ) as Array<{ filename: string; files: Array<{ path: string }> }> + packedFiles = packed[0]!.files.map((file) => file.path) + installedRoot = join(root, 'node_modules', '@tanstack', 'intent') + mkdirSync(installedRoot, { recursive: true }) + execFileSync( + 'tar', + [ + '-xzf', + join(root, packed[0]!.filename), + '-C', + installedRoot, + '--strip-components=1', + ], + { timeout }, + ) + // Exercise only packed Intent files; reuse installed runtime dependencies + // without downloading packages or running lifecycle scripts in the test. + symlinkSync( + join(packageRoot, 'node_modules'), + join(installedRoot, 'node_modules'), + 'junction', + ) + cli = join(installedRoot, 'dist', 'cli.mjs') +}, timeout) + +beforeEach(() => { + cwd = mkdtempSync(join(root, 'consumer-')) + writeFileSync( + join(cwd, 'package.json'), + '{"name":"consumer","private":true}\n', + ) + const leaf = join(cwd, 'node_modules', 'release-fixture') + mkdirSync(join(leaf, 'skills', 'core'), { recursive: true }) + writeFileSync( + join(leaf, 'package.json'), + JSON.stringify({ + name: 'release-fixture', + version: '1.0.0', + intent: { version: 1, repo: 'test/fixture', docs: 'docs/' }, + }), + ) + writeFileSync( + join(leaf, 'skills', 'core', 'SKILL.md'), + '---\nname: core\ndescription: Release smoke fixture.\n---\n\nPacked release guidance.\n', + ) +}) + +afterAll(() => { + if (root) rmSync(root, { recursive: true, force: true }) +}) + +describe('packed release', () => { + it('ships every meta resource and validates the extracted skills', () => { + const meta = join(packageRoot, 'meta') + for (const entry of readdirSync(meta, { + recursive: true, + encoding: 'utf8', + })) { + if (!statSync(join(meta, entry)).isFile()) continue + const packedPath = `meta/${entry.replaceAll('\\', '/')}` + expect(packedFiles).toContain(packedPath) + expect(readFileSync(join(installedRoot, packedPath))).toEqual( + readFileSync(join(meta, entry)), + ) + } + const result = run(['validate', join(installedRoot, 'meta')]) + expect(result.status, result.stderr).toBe(0) + }) + + it('resolves meta and scaffold paths from the extracted package', () => { + for (const name of [ + 'domain-discovery', + 'generate-skill', + 'tree-generator', + 'skill-staleness-check', + ]) { + const result = run(['meta', name]) + expect(result.status, result.stderr).toBe(0) + expect(result.stdout).toContain(`name: ${name}\n`) + if (name === 'domain-discovery') { + expect(result.stdout).toContain( + `](${join(installedRoot, 'meta', name, 'references', 'deep-read.md')})`, + ) + expect(result.stdout).toContain( + `](${join(installedRoot, 'meta', name, 'references', 'artifacts.md')})`, + ) + } + } + const scaffold = run(['scaffold']) + expect(scaffold.status, scaffold.stderr).toBe(0) + expect(scaffold.stdout).toContain( + join(installedRoot, 'meta', 'domain-discovery', 'SKILL.md'), + ) + expect(scaffold.stdout).toContain( + join(installedRoot, 'meta', 'tree-generator', 'SKILL.md'), + ) + expect(scaffold.stdout).toContain( + join(installedRoot, 'meta', 'generate-skill', 'SKILL.md'), + ) + }) + + it.each(['cancel', 'confirm'] as const)( + 'preserves the first-install %s contract in the bundle', + (decision) => { + const original = readFileSync(join(cwd, 'package.json'), 'utf8') + // Use the CLI's existing prompt seam; the remaining command path is bundled. + const result = spawnSync( + process.execPath, + [ + '--input-type=module', + '-e', + ` + import { main } from ${JSON.stringify(pathToFileURL(cli).href)}; + process.exitCode = await main(['install'], { + isTTY: true, + permissionPrompts: { + selectPermissions: async () => ['release-fixture#core'], + reviewPermissions: async (_groups, selection) => selection, + confirmWrite: async () => ${decision === 'confirm'}, + }, + }); + `, + ], + { cwd, encoding: 'utf8', timeout }, + ) + expect(result.status, result.stderr).toBe(0) + if (decision === 'cancel') { + expect(readFileSync(join(cwd, 'package.json'), 'utf8')).toBe(original) + expect(existsSync(join(cwd, 'AGENTS.md'))).toBe(false) + return + } + const policy = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8')) + expect(policy.intent.skills).toEqual(['release-fixture#core']) + const guidance = readFileSync(join(cwd, 'AGENTS.md'), 'utf8') + expect(guidance).toContain('intent-skills:start') + const repeat = run(['install']) + expect(repeat.status, repeat.stderr).toBe(0) + expect(readFileSync(join(cwd, 'AGENTS.md'), 'utf8')).toBe(guidance) + const listed = run(['list', '--json']) + expect(listed.status, listed.stderr).toBe(0) + expect( + JSON.parse(listed.stdout).skills.map( + (skill: { use: string }) => skill.use, + ), + ).toEqual(['release-fixture#core']) + const loaded = run(['load', 'release-fixture#core']) + expect(loaded.status, loaded.stderr).toBe(0) + expect(loaded.stdout).toContain('Packed release guidance.') + }, + ) + + it('fails first-time noninteractive installation without writing', () => { + const original = readFileSync(join(cwd, 'package.json'), 'utf8') + const result = run(['install']) + expect(result.status).toBe(1) + expect(result.stderr).toContain('interactive terminal') + expect(readFileSync(join(cwd, 'package.json'), 'utf8')).toBe(original) + expect(existsSync(join(cwd, 'AGENTS.md'))).toBe(false) + }) +}) diff --git a/packages/intent/tests/integration/source-policy-surfaces.test.ts b/packages/intent/tests/integration/source-policy-surfaces.test.ts index 28704eb9..3f1d5f79 100644 --- a/packages/intent/tests/integration/source-policy-surfaces.test.ts +++ b/packages/intent/tests/integration/source-policy-surfaces.test.ts @@ -125,6 +125,38 @@ describe('source policy — all four surfaces filter excluded and unlisted', () ) }) + it.each([ + ['list', '--json'], + ['load', `${LISTED}#core`], + ['load', `${LISTED}#core`, '--json'], + ['load', `${LISTED}#core`, '--path'], + ['install'], + ['install', '--map'], + ])('rejects a malformed npm workspace ancestor for %j', async (...args) => { + const appDir = join(root, 'packages', 'app') + const manifest = join(root, 'package.json') + writeFileSync( + manifest, + `{"workspaces":["packages/*"],"intent":{"exclude":["${LISTED}"]},`, + ) + writeJson(join(appDir, 'package.json'), { + name: 'app', + intent: { skills: [LISTED] }, + }) + writeIntentPackage(appDir, LISTED, 'core') + process.chdir(appDir) + const stdoutSpy = vi + .spyOn(process.stdout, 'write') + .mockImplementation(() => true) + expect(await main(args)).toBe(1) + expect(logSpy).not.toHaveBeenCalled() + expect(stdoutSpy).not.toHaveBeenCalled() + expect(console.error).toHaveBeenCalledWith( + expect.stringContaining(manifest), + ) + expect(existsSync(join(appDir, 'AGENTS.md'))).toBe(false) + }) + it('list and load accept packages matched by an allowlist glob', () => { writeJson(join(root, 'package.json'), { name: 'app', diff --git a/packages/intent/tests/project-context.test.ts b/packages/intent/tests/project-context.test.ts index 0416d917..ba33cc2a 100644 --- a/packages/intent/tests/project-context.test.ts +++ b/packages/intent/tests/project-context.test.ts @@ -47,6 +47,51 @@ afterEach(() => { }) describe('resolveProjectContext', () => { + it('keeps a nested Git repository separate from an enclosing workspace', () => { + const root = createRoot() + createPnpmWorkspaceRoot(root) + const packageRoot = createWorkspacePackage(root, 'standalone') + mkdirSync(join(packageRoot, '.git')) + + expect(resolveProjectContext({ cwd: packageRoot }).workspaceRoot).toBeNull() + expect( + resolveProjectContext({ cwd: root, targetPath: packageRoot }) + .workspaceRoot, + ).toBeNull() + }) + + it('does not borrow an ancestor package across a Git boundary', () => { + const root = createRoot() + writeFileSync(join(root, 'package.json'), '{ unrelated ancestor') + const checkout = join(root, 'checkout') + mkdirSync(join(checkout, '.git'), { recursive: true }) + + const context = resolveProjectContext({ cwd: checkout }) + expect(context.packageRoot).toBeNull() + expect(context.workspaceRoot).toBeNull() + }) + + it('reads the workspace declaration at the Git boundary', () => { + const root = createRoot() + createPnpmWorkspaceRoot(root) + mkdirSync(join(root, '.git')) + const packageRoot = createWorkspacePackage(root, 'router') + + expect(resolveProjectContext({ cwd: packageRoot }).workspaceRoot).toBe(root) + }) + + it('stops at a valid workspace before an unrelated malformed ancestor', () => { + const root = createRoot() + writeFileSync(join(root, 'package.json'), '{ unrelated ancestor') + const workspace = join(root, 'workspace') + createPnpmWorkspaceRoot(workspace) + const packageRoot = createWorkspacePackage(workspace, 'router') + + expect(resolveProjectContext({ cwd: packageRoot }).workspaceRoot).toBe( + workspace, + ) + }) + it('resolves the workspace root from the workspace cwd', () => { const root = createRoot() createPnpmWorkspaceRoot(root) diff --git a/packages/intent/tests/skills.test.ts b/packages/intent/tests/skills.test.ts index 16b04563..97c83e42 100644 --- a/packages/intent/tests/skills.test.ts +++ b/packages/intent/tests/skills.test.ts @@ -1,115 +1,24 @@ -import { readFileSync } from 'node:fs' import { join, relative, sep } from 'node:path' import { describe, expect, it } from 'vitest' -import { parse as parseYaml } from 'yaml' +import { runValidateCommand } from '../src/commands/validate.js' import { findSkillFiles } from '../src/shared/utils.js' -// ── Types ── - -interface SkillFrontmatter { - name: string - description: string - type?: string - library?: string - framework?: string - library_version?: string - requires?: Array - sources?: Array -} - -// ── Helpers ── - const META_DIR = join(__dirname, '..', 'meta') -const MAX_META_SKILL_LINES = 1000 - -function extractFrontmatter( - content: string, -): { frontmatter: SkillFrontmatter; body: string } | null { - const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)/) - if (!match || !match[1] || match[2] === undefined) return null - - try { - const frontmatter = parseYaml(match[1]) as SkillFrontmatter - return { frontmatter, body: match[2] } - } catch { - return null - } -} - -function skillPathFromFile(baseDir: string, filePath: string): string { - return relative(baseDir, filePath) - .replace(/[/\\]SKILL\.md$/, '') - .split(sep) - .join('/') -} - -// ── Collect meta-skills ── - -const metaSkillFiles = findSkillFiles(META_DIR) -const metaSkills = metaSkillFiles.map((filePath) => { - const content = readFileSync(filePath, 'utf-8') - const parsed = extractFrontmatter(content) - const relPath = skillPathFromFile(META_DIR, filePath) - return { filePath, content, parsed, relPath } -}) - -// ── Tests ── - -describe('meta-skill discovery', () => { - it('should find meta-skill files', () => { - expect(metaSkillFiles.length).toBeGreaterThan(0) - }) - it('should include domain-discovery', () => { - expect( - metaSkills.find((s) => s.relPath === 'domain-discovery'), - ).toBeDefined() +describe('shipped meta-skills', () => { + it('keeps every existing meta command available', () => { + const names = findSkillFiles(META_DIR).map((file) => + relative(META_DIR, file).split(sep).slice(0, -1).join('/'), + ) + expect(names.sort()).toEqual([ + 'domain-discovery', + 'generate-skill', + 'skill-staleness-check', + 'tree-generator', + ]) }) - it('should include tree-generator', () => { - expect(metaSkills.find((s) => s.relPath === 'tree-generator')).toBeDefined() + it('passes the same validator used by library maintainers', async () => { + await runValidateCommand(META_DIR) }) - - it('should include generate-skill', () => { - expect(metaSkills.find((s) => s.relPath === 'generate-skill')).toBeDefined() - }) - - it('should include skill-staleness-check', () => { - expect( - metaSkills.find((s) => s.relPath === 'skill-staleness-check'), - ).toBeDefined() - }) -}) - -describe('meta-skill frontmatter', () => { - for (const skill of metaSkills) { - describe(skill.relPath, () => { - it('should have valid frontmatter', () => { - expect(skill.parsed).not.toBeNull() - }) - - if (!skill.parsed) return - - const { frontmatter } = skill.parsed - - it('should have a name', () => { - expect(frontmatter.name).toBeTruthy() - }) - - it('should have a description', () => { - expect(frontmatter.description).toBeTruthy() - }) - }) - } -}) - -describe('meta-skill content', () => { - for (const skill of metaSkills) { - describe(skill.relPath, () => { - it(`should not exceed ${MAX_META_SKILL_LINES} lines`, () => { - const lineCount = skill.content.split(/\r?\n/).length - expect(lineCount).toBeLessThanOrEqual(MAX_META_SKILL_LINES) - }) - }) - } }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 52dbc59f..3d050ead 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,7 +22,7 @@ importers: version: 2.31.1(@types/node@25.9.5) '@tanstack/eslint-config': specifier: 0.4.0 - version: 0.4.0(@typescript-eslint/utils@8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + version: 0.4.0(@typescript-eslint/utils@8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) '@tanstack/typedoc-config': specifier: ^0.3.3 version: 0.3.4(typescript@6.0.3) @@ -31,10 +31,10 @@ importers: version: 25.9.5 eslint: specifier: ^9.39.4 - version: 9.39.5(jiti@2.7.0) + version: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) eslint-plugin-unused-imports: specifier: ^4.4.1 - version: 4.4.1(@typescript-eslint/eslint-plugin@8.68.0(@typescript-eslint/parser@8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0)) + version: 4.4.1(@typescript-eslint/eslint-plugin@8.68.0(@typescript-eslint/parser@8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)) knip: specifier: ^6.16.1 version: 6.33.0 @@ -67,13 +67,13 @@ importers: devDependencies: '@codspeed/vitest-plugin': specifier: ^5.5.0 - version: 5.7.1(tinybench@6.1.4)(vite@8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@5.0.0(@types/node@25.9.5)(vite@8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))) + version: 5.7.1(tinybench@6.1.4)(vite@8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8(@types/node@25.9.5)(vite@8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))) typescript: specifier: 6.0.3 version: 6.0.3 vitest: - specifier: 5.0.0 - version: 5.0.0(@types/node@25.9.5)(vite@8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0)) + specifier: 4.1.8 + version: 4.1.8(@types/node@25.9.5)(vite@8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0)) packages/intent: dependencies: @@ -104,7 +104,7 @@ importers: version: 0.22.14(oxc-resolver@11.24.2)(typescript@6.0.3) verdaccio: specifier: ^6.7.2 - version: 6.10.1(typanion@3.14.0) + version: 6.10.1(supports-color@7.2.0)(typanion@3.14.0) packages: @@ -914,6 +914,9 @@ packages: resolution: {integrity: sha512-2SX/1jW6CIMAiebvVv5ZInoCEuWQmMyBoJXXGC6Vjakjp/fpxP5eHs7/V6WKuPEIbuK06+VpjH+vjLQhr98rDQ==} engines: {node: '>=22'} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@stylistic/eslint-plugin@5.10.0': resolution: {integrity: sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1225,6 +1228,20 @@ packages: '@vitest-evals/report-ui@0.13.1': resolution: {integrity: sha512-uA0OSe8UFhSP8i92hUNSFbdJ7Lwi0b06DVfvPb9lnEADgZrExv8IiHy9mkRuU+aMwo7zQI75ZZz1qx07XzPczA==} + '@vitest/expect@4.1.8': + resolution: {integrity: sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==} + + '@vitest/mocker@4.1.8': + resolution: {integrity: sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/mocker@5.0.0': resolution: {integrity: sha512-66PGTMIiVJP3t4a5yxU9qPtf7MdTBs8jmToMvy+HVflB3Yy13WJZTtPePdvU+wjRV02SKK5doLbSA6o9pwOmiA==} peerDependencies: @@ -1236,9 +1253,24 @@ packages: vite: optional: true + '@vitest/pretty-format@4.1.8': + resolution: {integrity: sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==} + + '@vitest/runner@4.1.8': + resolution: {integrity: sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==} + + '@vitest/snapshot@4.1.8': + resolution: {integrity: sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==} + + '@vitest/spy@4.1.8': + resolution: {integrity: sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==} + '@vitest/spy@5.0.0': resolution: {integrity: sha512-uy+luWBAPw9XfthoHi5AkfHUnuPYEESjl0p/r+meoBnU8bxg5GDQ3Ey8MjcJ6sqahkL4PFyrvfMJJBw7LbU06g==} + '@vitest/utils@4.1.8': + resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==} + '@yarnpkg/lockfile@1.1.0': resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} @@ -1703,6 +1735,9 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-signature@1.0.7: resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} @@ -2691,6 +2726,9 @@ packages: lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magic-string@1.2.3: resolution: {integrity: sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g==} @@ -2994,6 +3032,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + peek-stream@1.1.3: resolution: {integrity: sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==} @@ -3459,6 +3500,9 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinybench@6.1.4: resolution: {integrity: sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==} engines: {node: '>=20.0.0'} @@ -3750,6 +3794,47 @@ packages: zod: optional: true + vitest@4.1.8: + resolution: {integrity: sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.8 + '@vitest/browser-preview': 4.1.8 + '@vitest/browser-webdriverio': 4.1.8 + '@vitest/coverage-istanbul': 4.1.8 + '@vitest/coverage-v8': 4.1.8 + '@vitest/ui': 4.1.8 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vitest@5.0.0: resolution: {integrity: sha512-gpsMNoRhMjMktVxPtstOH4/PJuPyovVaMDr4oDilXaGH1EcqM2OE96SoHT2VIQ6fTGtTjqmHDrEu2X9RQiXf8Q==} engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} @@ -4069,11 +4154,11 @@ snapshots: - debug - supports-color - '@codspeed/vitest-plugin@5.7.1(tinybench@6.1.4)(vite@8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@5.0.0(@types/node@25.9.5)(vite@8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0)))': + '@codspeed/vitest-plugin@5.7.1(tinybench@6.1.4)(vite@8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8(@types/node@25.9.5)(vite@8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0)))': dependencies: '@codspeed/core': 5.7.1 tinybench: 6.1.4 - vitest: 5.0.0(@types/node@25.9.5)(vite@8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0)) + vitest: 4.1.8(@types/node@25.9.5)(vite@8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0)) optionalDependencies: vite: 8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0) transitivePeerDependencies: @@ -4220,14 +4305,14 @@ snapshots: '@esbuild/win32-x64@0.28.2': optional: true - '@eslint-community/eslint-utils@4.10.1(eslint@9.39.5(jiti@2.7.0))': + '@eslint-community/eslint-utils@4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))': dependencies: - eslint: 9.39.5(jiti@2.7.0) + eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.21.2': + '@eslint/config-array@0.21.2(supports-color@7.2.0)': dependencies: '@eslint/object-schema': 2.1.7 debug: 4.4.3(supports-color@7.2.0) @@ -4243,7 +4328,7 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.6': + '@eslint/eslintrc@3.3.6(supports-color@7.2.0)': dependencies: ajv: 6.15.0 debug: 4.4.3(supports-color@7.2.0) @@ -4257,9 +4342,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@10.0.1(eslint@9.39.5(jiti@2.7.0))': + '@eslint/js@10.0.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))': optionalDependencies: - eslint: 9.39.5(jiti@2.7.0) + eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) '@eslint/js@9.39.5': {} @@ -4589,26 +4674,28 @@ snapshots: '@sindresorhus/is@8.1.0': {} - '@stylistic/eslint-plugin@5.10.0(eslint@9.39.5(jiti@2.7.0))': + '@standard-schema/spec@1.1.0': {} + + '@stylistic/eslint-plugin@5.10.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)) '@typescript-eslint/types': 8.68.0 - eslint: 9.39.5(jiti@2.7.0) + eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 picomatch: 4.0.7 - '@tanstack/eslint-config@0.4.0(@typescript-eslint/utils@8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3)': + '@tanstack/eslint-config@0.4.0(@typescript-eslint/utils@8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@eslint/js': 10.0.1(eslint@9.39.5(jiti@2.7.0)) - '@stylistic/eslint-plugin': 5.10.0(eslint@9.39.5(jiti@2.7.0)) - eslint: 9.39.5(jiti@2.7.0) - eslint-plugin-import-x: 4.17.1(@typescript-eslint/utils@8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0)) - eslint-plugin-n: 17.24.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + '@eslint/js': 10.0.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)) + '@stylistic/eslint-plugin': 5.10.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)) + eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) + eslint-plugin-import-x: 4.17.1(@typescript-eslint/utils@8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0) + eslint-plugin-n: 17.24.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(typescript@6.0.3) globals: 17.11.0 - typescript-eslint: 8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) - vue-eslint-parser: 10.4.1(eslint@9.39.5(jiti@2.7.0)) + typescript-eslint: 8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + vue-eslint-parser: 10.4.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0) transitivePeerDependencies: - '@typescript-eslint/utils' - eslint-import-resolver-node @@ -4661,15 +4748,15 @@ snapshots: '@types/unist@3.0.3': {} - '@typescript-eslint/eslint-plugin@8.68.0(@typescript-eslint/parser@8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.68.0(@typescript-eslint/parser@8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.68.0 - '@typescript-eslint/type-utils': 8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/utils': 8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/type-utils': 8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.68.0 - eslint: 9.39.5(jiti@2.7.0) + eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) ignore: 7.0.7 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -4677,14 +4764,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/parser@8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@typescript-eslint/scope-manager': 8.68.0 '@typescript-eslint/types': 8.68.0 '@typescript-eslint/typescript-estree': 8.68.0(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.68.0 debug: 4.4.3(supports-color@7.2.0) - eslint: 9.39.5(jiti@2.7.0) + eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -4707,13 +4794,13 @@ snapshots: dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.68.0 '@typescript-eslint/typescript-estree': 8.68.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(typescript@6.0.3) debug: 4.4.3(supports-color@7.2.0) - eslint: 9.39.5(jiti@2.7.0) + eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: @@ -4736,13 +4823,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/utils@8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)) '@typescript-eslint/scope-manager': 8.68.0 '@typescript-eslint/types': 8.68.0 '@typescript-eslint/typescript-estree': 8.68.0(typescript@6.0.3) - eslint: 9.39.5(jiti@2.7.0) + eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -4822,7 +4909,7 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.12.2': optional: true - '@verdaccio/auth@8.1.3': + '@verdaccio/auth@8.1.3(supports-color@7.2.0)': dependencies: '@verdaccio/config': 8.3.0 '@verdaccio/core': 8.3.0 @@ -4857,7 +4944,7 @@ snapshots: dependencies: lockfile: 1.0.4 - '@verdaccio/hooks@8.1.4': + '@verdaccio/hooks@8.1.4(supports-color@7.2.0)': dependencies: '@verdaccio/core': 8.3.0 '@verdaccio/logger': 8.1.3 @@ -4875,7 +4962,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@verdaccio/local-storage-legacy@11.4.3': + '@verdaccio/local-storage-legacy@11.4.3(supports-color@7.2.0)': dependencies: '@verdaccio/core': 8.3.0 '@verdaccio/file-locking': 13.1.0 @@ -4914,11 +5001,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@verdaccio/middleware@8.1.3': + '@verdaccio/middleware@8.1.3(supports-color@7.2.0)': dependencies: '@verdaccio/config': 8.3.0 '@verdaccio/core': 8.3.0 - '@verdaccio/url': 13.1.3 + '@verdaccio/url': 13.1.3(supports-color@7.2.0) debug: 4.4.3(supports-color@7.2.0) express: 4.22.2 express-rate-limit: 5.5.1 @@ -4927,7 +5014,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@verdaccio/package-filter@13.2.1': + '@verdaccio/package-filter@13.2.1(supports-color@7.2.0)': dependencies: '@verdaccio/core': 8.3.0 debug: 4.4.3(supports-color@7.2.0) @@ -4935,7 +5022,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@verdaccio/search-indexer@8.1.0': + '@verdaccio/search-indexer@8.1.0(supports-color@7.2.0)': dependencies: debug: 4.4.3(supports-color@7.2.0) fuse.js: 7.3.0 @@ -4953,10 +5040,10 @@ snapshots: '@verdaccio/streams@10.3.0': {} - '@verdaccio/tarball@13.1.3': + '@verdaccio/tarball@13.1.3(supports-color@7.2.0)': dependencies: '@verdaccio/core': 8.3.0 - '@verdaccio/url': 13.1.3 + '@verdaccio/url': 13.1.3(supports-color@7.2.0) debug: 4.4.3(supports-color@7.2.0) gunzip-maybe: 1.4.2 tar-stream: 3.2.0 @@ -4966,13 +5053,13 @@ snapshots: - react-native-b4a - supports-color - '@verdaccio/ui-theme@9.0.0-next-9.28': + '@verdaccio/ui-theme@9.0.0-next-9.28(supports-color@7.2.0)': dependencies: debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color - '@verdaccio/url@13.1.3': + '@verdaccio/url@13.1.3(supports-color@7.2.0)': dependencies: '@verdaccio/core': 8.3.0 debug: 4.4.3(supports-color@7.2.0) @@ -4988,6 +5075,23 @@ snapshots: dependencies: '@vitest-evals/core': 0.13.1 + '@vitest/expect@4.1.8': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.8 + '@vitest/utils': 4.1.8 + chai: 6.2.2 + tinyrainbow: 3.1.1 + + '@vitest/mocker@4.1.8(vite@8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))': + dependencies: + '@vitest/spy': 4.1.8 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0) + '@vitest/mocker@5.0.0(vite@8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))': dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -4997,8 +5101,32 @@ snapshots: optionalDependencies: vite: 8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0) + '@vitest/pretty-format@4.1.8': + dependencies: + tinyrainbow: 3.1.1 + + '@vitest/runner@4.1.8': + dependencies: + '@vitest/utils': 4.1.8 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.8': + dependencies: + '@vitest/pretty-format': 4.1.8 + '@vitest/utils': 4.1.8 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.8': {} + '@vitest/spy@5.0.0': {} + '@vitest/utils@4.1.8': + dependencies: + '@vitest/pretty-format': 4.1.8 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.1 + '@yarnpkg/lockfile@1.1.0': {} '@yuku-codegen/binding-android-arm64@0.8.7': @@ -5410,6 +5538,8 @@ snapshots: content-type@1.0.5: {} + convert-source-map@2.0.0: {} + cookie-signature@1.0.7: {} cookie@0.7.2: {} @@ -5633,9 +5763,9 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-compat-utils@0.5.1(eslint@9.39.5(jiti@2.7.0)): + eslint-compat-utils@0.5.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)): dependencies: - eslint: 9.39.5(jiti@2.7.0) + eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) semver: 7.8.5 eslint-import-context@0.1.9(unrs-resolver@1.12.2): @@ -5645,19 +5775,19 @@ snapshots: optionalDependencies: unrs-resolver: 1.12.2 - eslint-plugin-es-x@7.8.0(eslint@9.39.5(jiti@2.7.0)): + eslint-plugin-es-x@7.8.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)) '@eslint-community/regexpp': 4.12.2 - eslint: 9.39.5(jiti@2.7.0) - eslint-compat-utils: 0.5.1(eslint@9.39.5(jiti@2.7.0)) + eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) + eslint-compat-utils: 0.5.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)) - eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0)): + eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0): dependencies: '@typescript-eslint/types': 8.68.0 comment-parser: 1.4.8 debug: 4.4.3(supports-color@7.2.0) - eslint: 9.39.5(jiti@2.7.0) + eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) eslint-import-context: 0.1.9(unrs-resolver@1.12.2) is-glob: 4.0.3 minimatch: 10.2.6 @@ -5665,16 +5795,16 @@ snapshots: stable-hash-x: 0.2.0 unrs-resolver: 1.12.2 optionalDependencies: - '@typescript-eslint/utils': 8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(typescript@6.0.3) transitivePeerDependencies: - supports-color - eslint-plugin-n@17.24.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3): + eslint-plugin-n@17.24.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(typescript@6.0.3): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)) enhanced-resolve: 5.24.5 - eslint: 9.39.5(jiti@2.7.0) - eslint-plugin-es-x: 7.8.0(eslint@9.39.5(jiti@2.7.0)) + eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) + eslint-plugin-es-x: 7.8.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)) get-tsconfig: 4.14.3 globals: 15.15.0 globrex: 0.1.2 @@ -5684,11 +5814,11 @@ snapshots: transitivePeerDependencies: - typescript - eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.68.0(@typescript-eslint/parser@8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0)): + eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.68.0(@typescript-eslint/parser@8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)): dependencies: - eslint: 9.39.5(jiti@2.7.0) + eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.68.0(@typescript-eslint/parser@8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.68.0(@typescript-eslint/parser@8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(typescript@6.0.3) eslint-scope@8.4.0: dependencies: @@ -5708,14 +5838,14 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@9.39.5(jiti@2.7.0): + eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.2 + '@eslint/config-array': 0.21.2(supports-color@7.2.0) '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.6 + '@eslint/eslintrc': 3.3.6(supports-color@7.2.0) '@eslint/js': 9.39.5 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.8 @@ -6413,6 +6543,10 @@ snapshots: lunr@2.3.9: {} + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + magic-string@1.2.3: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 @@ -6813,6 +6947,8 @@ snapshots: path-type@4.0.0: {} + pathe@2.0.3: {} + peek-stream@1.1.3: dependencies: buffer-from: 1.1.2 @@ -7289,6 +7425,8 @@ snapshots: through@2.3.8: {} + tinybench@2.9.0: {} + tinybench@6.1.4: {} tinyexec@1.3.0: {} @@ -7407,13 +7545,13 @@ snapshots: typescript: 6.0.3 yaml: 2.9.0 - typescript-eslint@8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3): + typescript-eslint@8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.68.0(@typescript-eslint/parser@8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/parser': 8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.68.0(@typescript-eslint/parser@8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) '@typescript-eslint/typescript-estree': 8.68.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.68.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) - eslint: 9.39.5(jiti@2.7.0) + '@typescript-eslint/utils': 8.68.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(typescript@6.0.3) + eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -7508,24 +7646,24 @@ snapshots: transitivePeerDependencies: - supports-color - verdaccio@6.10.1(typanion@3.14.0): + verdaccio@6.10.1(supports-color@7.2.0)(typanion@3.14.0): dependencies: '@cypress/request': 4.0.1 - '@verdaccio/auth': 8.1.3 + '@verdaccio/auth': 8.1.3(supports-color@7.2.0) '@verdaccio/config': 8.3.0 '@verdaccio/core': 8.3.0 - '@verdaccio/hooks': 8.1.4 + '@verdaccio/hooks': 8.1.4(supports-color@7.2.0) '@verdaccio/loaders': 8.1.3 - '@verdaccio/local-storage-legacy': 11.4.3 + '@verdaccio/local-storage-legacy': 11.4.3(supports-color@7.2.0) '@verdaccio/logger': 8.1.3 - '@verdaccio/middleware': 8.1.3 - '@verdaccio/package-filter': 13.2.1 - '@verdaccio/search-indexer': 8.1.0 + '@verdaccio/middleware': 8.1.3(supports-color@7.2.0) + '@verdaccio/package-filter': 13.2.1(supports-color@7.2.0) + '@verdaccio/search-indexer': 8.1.0(supports-color@7.2.0) '@verdaccio/signature': 8.1.3 '@verdaccio/streams': 10.3.0 - '@verdaccio/tarball': 13.1.3 - '@verdaccio/ui-theme': 9.0.0-next-9.28 - '@verdaccio/url': 13.1.3 + '@verdaccio/tarball': 13.1.3(supports-color@7.2.0) + '@verdaccio/ui-theme': 9.0.0-next-9.28(supports-color@7.2.0) + '@verdaccio/url': 13.1.3(supports-color@7.2.0) JSONStream: 1.3.5 async: 3.2.6 clipanion: 4.0.0-rc.4(typanion@3.14.0) @@ -7579,6 +7717,33 @@ snapshots: optionalDependencies: zod: 4.5.4 + vitest@4.1.8(@types/node@25.9.5)(vite@8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0)): + dependencies: + '@vitest/expect': 4.1.8 + '@vitest/mocker': 4.1.8(vite@8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.8 + '@vitest/runner': 4.1.8 + '@vitest/snapshot': 4.1.8 + '@vitest/spy': 4.1.8 + '@vitest/utils': 4.1.8 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.4 + pathe: 2.0.3 + picomatch: 4.0.7 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.3.0 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.1 + vite: 8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.9.5 + transitivePeerDependencies: + - msw + vitest@5.0.0(@types/node@25.9.5)(vite@8.2.2(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0)): dependencies: '@types/chai': 5.2.3 @@ -7600,10 +7765,10 @@ snapshots: transitivePeerDependencies: - msw - vue-eslint-parser@10.4.1(eslint@9.39.5(jiti@2.7.0)): + vue-eslint-parser@10.4.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0): dependencies: debug: 4.4.3(supports-color@7.2.0) - eslint: 9.39.5(jiti@2.7.0) + eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) eslint-scope: 9.1.2 eslint-visitor-keys: 5.0.1 espree: 11.2.0 diff --git a/tsconfig.json b/tsconfig.json index 9134737c..ec0cf58d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,5 +25,5 @@ "noErrorTruncation": true, "types": ["node"] }, - "include": ["scripts", "*.config.*", "vitest.workspace.js"] + "include": ["scripts", "*.config.*"] } diff --git a/vitest.workspace.js b/vitest.config.js similarity index 100% rename from vitest.workspace.js rename to vitest.config.js