diff --git a/.gitignore b/.gitignore index 9a625add611..5b9b5fefb7e 100644 --- a/.gitignore +++ b/.gitignore @@ -143,4 +143,5 @@ documentation/ag-grid-docs/test-results/.last-run.json **/WARP.md **/modular-mcp.json !.rulesync/.aiignore +.rulesync/skills/rulesync /.junie/ diff --git a/.rulesync/README.md b/.rulesync/README.md index b041de75cb0..d3bb124bb81 100644 --- a/.rulesync/README.md +++ b/.rulesync/README.md @@ -4,14 +4,15 @@ Quick-reference for all AI agent commands, skills, sub-agents, and rules availab ## How It Works -| Folder | Purpose | Loaded by | -| ------------ | ------------------------------------------------------------------------ | ---------------------- | -| `.rulesync/` | Canonical shared source — works across tools (Cursor, Claude Code, etc.) | All supported AI tools | +| Folder | Purpose | Loaded by | +| ------------ | ------------------------------------------------------------------------------------------------- | ---------------------- | +| `.rulesync/` | Canonical shared source — works across tools (Cursor, Claude Code, etc.) | All supported AI tools | +| `.claude/` | Claude Code extensions — mirrors `.rulesync/` plus Claude Code-specific agents, skills, and rules | Claude Code only | **Loading behaviour:** - **Rules** load automatically based on file-pattern globs (e.g. editing a `.test.ts` file loads the `testing` rule). The root rule (`ag-grid`) loads for all files. -- **Skills** load on-demand when invoked via `/skill-name`. +- **Skills** load on-demand when invoked via `/skill-name`. Skills marked **(user)** are user-invocable only — the LLM should not invoke them autonomously via the Skill tool. - **Sub-agents** are spawned automatically by the AI when a task matches their speciality. - **Commands** are invoked explicitly via `/command-name`. @@ -24,41 +25,40 @@ Quick-reference for all AI agent commands, skills, sub-agents, and rules availab ## Everyday Development -| Type | Name | Invoke | What it does | -| ------- | --------------------- | ----------------------- | -------------------------------------------------- | -| Command | 🔵 `/code-fixup` | `/code-fixup ` | Fix build and lint errors across a package | -| Command | 🔵 `/code-cleanup` | `/code-cleanup` | Remove bloat, duplication; improve clarity | -| Command | 🔵 `/pr-create` | `/pr-create` | Commit, push, and open a PR | -| Command | 🔵 `/pr-review` | `/pr-review ` | Review a PR (Markdown output) | -| Command | 🔵 `/pr-review-json` | `/pr-review-json ` | Review a PR (JSON for inline comments) | -| Skill | 🟢 `dev-server` | `/dev-server` | Start dev server, check build status | -| Skill | 🔵 `git-conventions` | `/git-conventions` | Branch, commit, and PR naming conventions | -| Skill | 🟢 `technology-stack` | `/technology-stack` | Architecture constraints and zero-dependency rules | -| Agent | 🔵 `code-reviewer` | Auto (after edits) | Quality, security, and maintainability review | +| Type | Name | Invoke | What it does | +| ----- | --------------------- | ---------------------------------- | -------------------------------------------------- | +| Skill | 🔵 `code-fixup` | `/code-fixup ` (user) | Fix build and lint errors across a package | +| Skill | 🔵 `code-cleanup` | `/code-cleanup` (user) | Remove bloat, duplication; improve clarity | +| Skill | 🔵 `pr-create` | `/pr-create` (user) | Commit, push, and open a PR | +| Skill | 🔵 `pr-review` | `/pr-review [--json] ` (user) | Review a PR (Markdown default, JSON with `--json`) | +| Skill | 🟢 `dev-server` | `/dev-server` | Start dev server, check build status | +| Skill | 🔵 `git-conventions` | `/git-conventions` | Branch, commit, and PR naming conventions | +| Skill | 🟢 `technology-stack` | `/technology-stack` | Architecture constraints and zero-dependency rules | +| Agent | 🔵 `code-reviewer` | Auto (after edits) | Quality, security, and maintainability review | ## Testing and Quality -| Type | Name | Invoke | What it does | -| ------- | ------------------------ | --------------------- | ---------------------------------------------- | -| Command | 🔵 `/git-bisect` | `/git-bisect` | Find the commit that introduced a regression | -| Command | 🔵 `/batch-lint-cleanup` | `/batch-lint-cleanup` | Auto-fix ESLint violations by rule | -| Command | 🟢 `/docs-e2e-tests` | `/docs-e2e-tests` | Write/update Playwright tests for doc examples | -| Agent | 🔵 `playwright-expert` | Auto | Playwright test architecture and debugging | +| Type | Name | Invoke | What it does | +| ------- | ----------------------- | ---------------------------- | ---------------------------------------------- | +| Skill | 🔵 `git-bisect` | `/git-bisect` (user) | Find the commit that introduced a regression | +| Skill | 🔵 `batch-lint-cleanup` | `/batch-lint-cleanup` (user) | Auto-fix ESLint violations by rule | +| Command | 🟢 `/docs-e2e-tests` | `/docs-e2e-tests` | Write/update Playwright tests for doc examples | +| Agent | 🔵 `playwright-expert` | Auto | Playwright test architecture and debugging | ## Planning and Analysis -| Type | Name | Invoke | What it does | -| ------- | -------------------------------- | ----------------------------- | ------------------------------------------------ | -| Command | 🔵 `/plan-review` | `/plan-review` | Review plans for completeness and correctness | -| Command | 🔵 `/plan-implementation-review` | `/plan-implementation-review` | Review plan execution, identify delivery gaps | -| Agent | 🔵 `nx-expert` | Auto | Nx monorepo configuration and build optimisation | +| Type | Name | Invoke | What it does | +| ----- | ------------------------------- | ------------------------------------ | ------------------------------------------------ | +| Skill | 🔵 `plan-review` | `/plan-review` (user) | Review plans for completeness and correctness | +| Skill | 🔵 `plan-implementation-review` | `/plan-implementation-review` (user) | Review plan execution, identify delivery gaps | +| Agent | 🔵 `nx-expert` | Auto | Nx monorepo configuration and build optimisation | -## Context and Memory +## Memory -| Type | Name | Invoke | What it does | -| ------- | -------------- | ----------- | -------------------------------------------------- | -| Command | 🔵 `/remember` | `/remember` | Save branch context or project learnings as memory | -| Command | 🔵 `/recall` | `/recall` | Load branch context and browse project memory | +| Type | Name | Invoke | What it does | +| ----- | ------------- | ------------------ | -------------------------------------------------- | +| Skill | 🔵 `remember` | `/remember` (user) | Save branch context or project learnings as memory | +| Skill | 🔵 `recall` | `/recall` (user) | Load branch context, browse project memories | ## Documentation Review @@ -69,11 +69,12 @@ Quick-reference for all AI agent commands, skills, sub-agents, and rules availab ## Git and Branch Management -| Type | Name | Invoke | What it does | -| ------- | ------------------------ | --------------------- | ---------------------------------------- | -| Command | 🔵 `/git-worktree-clean` | `/git-worktree-clean` | Hard-reset worktree to `origin/latest` | -| Command | 🔵 `/git-split` | `/git-split` | Split large files preserving git history | -| Command | 🔵 `/pr-split` | `/pr-split` | Split a branch into stacked PRs | +| Type | Name | Invoke | What it does | +| ----- | ----------------------- | ---------------------------- | ---------------------------------------- | +| Skill | 🔵 `sync-ag-shared` | `/sync-ag-shared` (user) | Sync ag-shared subrepo across AG repos | +| Skill | 🔵 `git-worktree-clean` | `/git-worktree-clean` (user) | Hard-reset worktree to `origin/latest` | +| Skill | 🔵 `git-split` | `/git-split` (user) | Split large files preserving git history | +| Skill | 🔵 `pr-split` | `/pr-split` (user) | Split a branch into stacked PRs | --- @@ -113,11 +114,25 @@ Rules load automatically when you edit files matching their glob patterns. Skills load on-demand when invoked. All skills are invoked via `/skill-name`. All skills are shared across AI tools via `.rulesync/skills/`. -| Skill | Description | -| --------------------- | --------------------------------------------------------- | -| 🟢 `dev-server` | Start dev server, check build status | -| 🔵 `git-conventions` | Branch, commit, and PR naming conventions | -| 🟢 `technology-stack` | Architecture constraints and zero-dependency requirements | +| Skill | Description | +| ------------------------------- | --------------------------------------------------------- | +| 🔵 `batch-lint-cleanup` | Auto-fix ESLint violations by rule | +| 🔵 `code-cleanup` | Remove bloat, duplication; improve clarity | +| 🔵 `code-fixup` | Fix build and lint errors across a package | +| 🟢 `dev-server` | Start dev server, check build status | +| 🔵 `git-bisect` | Find the commit that introduced a regression | +| 🔵 `git-conventions` | Branch, commit, and PR naming conventions | +| 🔵 `git-split` | Split large files preserving git history | +| 🔵 `git-worktree-clean` | Hard-reset worktree to `origin/latest` | +| 🔵 `plan-implementation-review` | Review plan execution, identify delivery gaps | +| 🔵 `plan-review` | Review plans for completeness and correctness | +| 🔵 `pr-create` | Commit, push, and open a PR | +| 🔵 `pr-review` | Review a PR (Markdown default, JSON with `--json`) | +| 🔵 `pr-split` | Split a branch into stacked PRs | +| 🔵 `recall` | Load branch context, browse project memories | +| 🔵 `remember` | Save branch context or project learnings as memory | +| 🔵 `sync-ag-shared` | Sync ag-shared subrepo changes across AG repos | +| 🟢 `technology-stack` | Architecture constraints and zero-dependency requirements | --- diff --git a/.rulesync/commands/batch-lint-cleanup.md b/.rulesync/commands/batch-lint-cleanup.md deleted file mode 120000 index 6a0c0c2add0..00000000000 --- a/.rulesync/commands/batch-lint-cleanup.md +++ /dev/null @@ -1 +0,0 @@ -../../external/ag-shared/prompts/commands/batch/lint-cleanup.md \ No newline at end of file diff --git a/.rulesync/commands/code-cleanup.md b/.rulesync/commands/code-cleanup.md deleted file mode 120000 index e117a729f96..00000000000 --- a/.rulesync/commands/code-cleanup.md +++ /dev/null @@ -1 +0,0 @@ -../../external/ag-shared/prompts/commands/code/cleanup.md \ No newline at end of file diff --git a/.rulesync/commands/code-fixup.md b/.rulesync/commands/code-fixup.md deleted file mode 120000 index 33445a6beff..00000000000 --- a/.rulesync/commands/code-fixup.md +++ /dev/null @@ -1 +0,0 @@ -../../external/ag-shared/prompts/commands/code/fixup.md \ No newline at end of file diff --git a/.rulesync/commands/git-bisect.md b/.rulesync/commands/git-bisect.md deleted file mode 120000 index 226ca78bb68..00000000000 --- a/.rulesync/commands/git-bisect.md +++ /dev/null @@ -1 +0,0 @@ -../../external/ag-shared/prompts/commands/git/bisect.md \ No newline at end of file diff --git a/.rulesync/commands/git-split.md b/.rulesync/commands/git-split.md deleted file mode 120000 index c200c1b7ee6..00000000000 --- a/.rulesync/commands/git-split.md +++ /dev/null @@ -1 +0,0 @@ -../../external/ag-shared/prompts/commands/git/split.md \ No newline at end of file diff --git a/.rulesync/commands/git-worktree-clean.md b/.rulesync/commands/git-worktree-clean.md deleted file mode 120000 index 2615bfdbaaa..00000000000 --- a/.rulesync/commands/git-worktree-clean.md +++ /dev/null @@ -1 +0,0 @@ -../../external/ag-shared/prompts/commands/git/worktree-clean.md \ No newline at end of file diff --git a/.rulesync/commands/plan-implementation-review.md b/.rulesync/commands/plan-implementation-review.md deleted file mode 120000 index 050c9c6c063..00000000000 --- a/.rulesync/commands/plan-implementation-review.md +++ /dev/null @@ -1 +0,0 @@ -../../external/ag-shared/prompts/commands/plan/implementation-review.md \ No newline at end of file diff --git a/.rulesync/commands/plan-review.md b/.rulesync/commands/plan-review.md deleted file mode 120000 index af788951e84..00000000000 --- a/.rulesync/commands/plan-review.md +++ /dev/null @@ -1 +0,0 @@ -../../external/ag-shared/prompts/commands/plan/review.md \ No newline at end of file diff --git a/.rulesync/commands/pr-create.md b/.rulesync/commands/pr-create.md deleted file mode 120000 index 8465e6da702..00000000000 --- a/.rulesync/commands/pr-create.md +++ /dev/null @@ -1 +0,0 @@ -../../external/ag-shared/prompts/commands/pr/create.md \ No newline at end of file diff --git a/.rulesync/commands/pr-review-json.md b/.rulesync/commands/pr-review-json.md deleted file mode 120000 index d79a26a0666..00000000000 --- a/.rulesync/commands/pr-review-json.md +++ /dev/null @@ -1 +0,0 @@ -../../external/ag-shared/prompts/commands/pr/review-json.md \ No newline at end of file diff --git a/.rulesync/commands/pr-review.md b/.rulesync/commands/pr-review.md deleted file mode 120000 index 2e0af1a9f53..00000000000 --- a/.rulesync/commands/pr-review.md +++ /dev/null @@ -1 +0,0 @@ -../../external/ag-shared/prompts/commands/pr/review.md \ No newline at end of file diff --git a/.rulesync/commands/pr-split.md b/.rulesync/commands/pr-split.md deleted file mode 120000 index 7680a26bd2e..00000000000 --- a/.rulesync/commands/pr-split.md +++ /dev/null @@ -1 +0,0 @@ -../../external/ag-shared/prompts/commands/pr/split.md \ No newline at end of file diff --git a/.rulesync/commands/recall.md b/.rulesync/commands/recall.md deleted file mode 120000 index 294bb6c1836..00000000000 --- a/.rulesync/commands/recall.md +++ /dev/null @@ -1 +0,0 @@ -../../external/ag-shared/prompts/commands/recall.md \ No newline at end of file diff --git a/.rulesync/commands/remember.md b/.rulesync/commands/remember.md deleted file mode 120000 index 6ed9f690adb..00000000000 --- a/.rulesync/commands/remember.md +++ /dev/null @@ -1 +0,0 @@ -../../external/ag-shared/prompts/commands/remember.md \ No newline at end of file diff --git a/.rulesync/skills/batch-lint-cleanup b/.rulesync/skills/batch-lint-cleanup new file mode 120000 index 00000000000..ea9a54f8a77 --- /dev/null +++ b/.rulesync/skills/batch-lint-cleanup @@ -0,0 +1 @@ +../../external/ag-shared/prompts/skills/batch-lint-cleanup/ \ No newline at end of file diff --git a/.rulesync/skills/code-cleanup b/.rulesync/skills/code-cleanup new file mode 120000 index 00000000000..8676809ce28 --- /dev/null +++ b/.rulesync/skills/code-cleanup @@ -0,0 +1 @@ +../../external/ag-shared/prompts/skills/code-cleanup/ \ No newline at end of file diff --git a/.rulesync/skills/code-fixup b/.rulesync/skills/code-fixup new file mode 120000 index 00000000000..1487e225c81 --- /dev/null +++ b/.rulesync/skills/code-fixup @@ -0,0 +1 @@ +../../external/ag-shared/prompts/skills/code-fixup/ \ No newline at end of file diff --git a/.rulesync/skills/git-bisect b/.rulesync/skills/git-bisect new file mode 120000 index 00000000000..0f40876b1a9 --- /dev/null +++ b/.rulesync/skills/git-bisect @@ -0,0 +1 @@ +../../external/ag-shared/prompts/skills/git-bisect/ \ No newline at end of file diff --git a/.rulesync/skills/git-split b/.rulesync/skills/git-split new file mode 120000 index 00000000000..ecdf201bb6f --- /dev/null +++ b/.rulesync/skills/git-split @@ -0,0 +1 @@ +../../external/ag-shared/prompts/skills/git-split/ \ No newline at end of file diff --git a/.rulesync/skills/git-worktree-clean b/.rulesync/skills/git-worktree-clean new file mode 120000 index 00000000000..f013cb8f3f0 --- /dev/null +++ b/.rulesync/skills/git-worktree-clean @@ -0,0 +1 @@ +../../external/ag-shared/prompts/skills/git-worktree-clean/ \ No newline at end of file diff --git a/.rulesync/skills/plan-implementation-review b/.rulesync/skills/plan-implementation-review new file mode 120000 index 00000000000..070ff631bca --- /dev/null +++ b/.rulesync/skills/plan-implementation-review @@ -0,0 +1 @@ +../../external/ag-shared/prompts/skills/plan-implementation-review/ \ No newline at end of file diff --git a/.rulesync/skills/plan-review b/.rulesync/skills/plan-review new file mode 120000 index 00000000000..43c81e6db38 --- /dev/null +++ b/.rulesync/skills/plan-review @@ -0,0 +1 @@ +../../external/ag-shared/prompts/skills/plan-review/ \ No newline at end of file diff --git a/.rulesync/skills/pr-create b/.rulesync/skills/pr-create new file mode 120000 index 00000000000..e8e45ec3bc9 --- /dev/null +++ b/.rulesync/skills/pr-create @@ -0,0 +1 @@ +../../external/ag-shared/prompts/skills/pr-create/ \ No newline at end of file diff --git a/.rulesync/skills/pr-review b/.rulesync/skills/pr-review new file mode 120000 index 00000000000..cc5e2108a00 --- /dev/null +++ b/.rulesync/skills/pr-review @@ -0,0 +1 @@ +../../external/ag-shared/prompts/skills/pr-review/ \ No newline at end of file diff --git a/.rulesync/skills/pr-split b/.rulesync/skills/pr-split new file mode 120000 index 00000000000..d400325e383 --- /dev/null +++ b/.rulesync/skills/pr-split @@ -0,0 +1 @@ +../../external/ag-shared/prompts/skills/pr-split/ \ No newline at end of file diff --git a/.rulesync/skills/recall b/.rulesync/skills/recall new file mode 120000 index 00000000000..43132bc4aa9 --- /dev/null +++ b/.rulesync/skills/recall @@ -0,0 +1 @@ +../../external/ag-shared/prompts/skills/recall/ \ No newline at end of file diff --git a/.rulesync/skills/remember b/.rulesync/skills/remember new file mode 120000 index 00000000000..c4e83ebcece --- /dev/null +++ b/.rulesync/skills/remember @@ -0,0 +1 @@ +../../external/ag-shared/prompts/skills/remember/ \ No newline at end of file diff --git a/.rulesync/skills/sync-ag-shared b/.rulesync/skills/sync-ag-shared new file mode 120000 index 00000000000..fd03600b519 --- /dev/null +++ b/.rulesync/skills/sync-ag-shared @@ -0,0 +1 @@ +../../external/ag-shared/prompts/skills/sync-ag-shared/ \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 2f81659f9f7..115ad2fd4f5 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -12,8 +12,6 @@ "syler.sass-indented", // shows the cost of each import "wix.vscode-import-cost", - // shows the cost of CSS properties - "kisstkondoros.csstriggers", // es6 string HTML highlight "tobermory.es6-string-html", // prettier diff --git a/external/ag-shared/.gitrepo b/external/ag-shared/.gitrepo index 83d2e9f0948..c0880145a6e 100644 --- a/external/ag-shared/.gitrepo +++ b/external/ag-shared/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://github.com/ag-grid/ag-shared.git branch = latest - commit = 4d82650c622e0433d9b58410adc1c88a00608f9d - parent = e95f2f1e806d05dad1435a1336724ed0b7d9c0e6 + commit = a359ab89a52dee65475a46b6ddd81ef82943f079 + parent = 18f4aceb6c6fc2f7cc446bf315f9b03c24d083de method = rebase cmdver = 0.4.9 diff --git a/external/ag-shared/github/actions/codex-pr-review/action.yml b/external/ag-shared/github/actions/codex-pr-review/action.yml index 82eca0a9781..de26bef2fde 100644 --- a/external/ag-shared/github/actions/codex-pr-review/action.yml +++ b/external/ag-shared/github/actions/codex-pr-review/action.yml @@ -40,7 +40,7 @@ inputs: prompt-file: description: 'Path to the prompt file' required: false - default: 'external/ag-shared/prompts/commands/pr/review.md' + default: 'external/ag-shared/prompts/skills/pr-review/SKILL.md' github-token: description: 'GitHub token for PR access and commenting' required: true @@ -278,12 +278,11 @@ runs: id: prompt shell: bash run: | - # Use JSON prompt if inline comments enabled, otherwise use configured prompt + # Use same prompt file for both modes; JSON mode is controlled via --json in ARGUMENTS + prompt_file="${{ inputs.prompt-file }}" if [[ "${{ inputs.inline-comments }}" == "true" ]]; then - prompt_file="external/ag-shared/prompts/commands/pr/review-json.md" output_ext="json" else - prompt_file="${{ inputs.prompt-file }}" output_ext="md" fi echo "prompt_file=${prompt_file}" >> $GITHUB_OUTPUT @@ -296,7 +295,7 @@ runs: env: OPENAI_API_KEY: ${{ inputs.codex-api-key }} CODEX_API_KEY: ${{ inputs.codex-api-key }} - ARGUMENTS: ${{ inputs.pr-number }} + ARGUMENTS: ${{ inputs.inline-comments == 'true' && format('--json {0}', inputs.pr-number) || inputs.pr-number }} BASE_REF: ${{ inputs.base-ref }} HEAD_REF: ${{ inputs.head-ref }} PR_TITLE: ${{ inputs.pr-title }} diff --git a/external/ag-shared/prompts/commands/pr/review.md b/external/ag-shared/prompts/commands/pr/review.md deleted file mode 100644 index 09af43d3118..00000000000 --- a/external/ag-shared/prompts/commands/pr/review.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -targets: ['*'] -description: 'Review pull requests with Markdown output' ---- - -# PR Review Instructions (Markdown Output) - -You are acting as a reviewer for a proposed code change. Your goal is to identify issues that could impact the quality, correctness, or safety of the codebase. - -**Read and follow all instructions in `external/ag-shared/prompts/commands/pr/_review-core.md` for the review methodology.** - -## Output Format - -Output the review directly to the terminal using this Markdown structure: - -```markdown -# PR Review: #{PR_NUMBER} - {PR_TITLE} - -**PR:** {PR_URL} -**Author:** {AUTHOR} | **Base:** {BASE_BRANCH} ← **Head:** {HEAD_BRANCH} - -## Summary - -{1-2 sentence summary of what this PR does} - -## Findings - -### P0 - Critical - -{List P0 issues, or "None" if empty} - -- **`{filepath}:{start_line}-{end_line}`** - {Issue title} - {Short explanation of the issue and why it's critical} - -### P1 - High - -{List P1 issues, or "None" if empty} - -- **`{filepath}:{line}`** - {Issue title} - {Short explanation} - -### P2 - Medium - -{List P2 issues, or "None" if empty} - -- **`{filepath}:{line}`** - {Issue title} - {Short explanation} - ---- - -_{N} low-priority issues omitted._ - -## Verdict - -**Assessment:** {Patch is correct | Patch is incorrect} -**Confidence:** {0.0-1.0} - -{Concise justification for the verdict - 1-2 sentences} - -**Required Actions:** {Bulleted list of required fixes, or "None - ready to merge"} -``` diff --git a/external/ag-shared/prompts/guides/setup-prompts.md b/external/ag-shared/prompts/guides/setup-prompts.md index 21230659ba9..756d698ba8d 100644 --- a/external/ag-shared/prompts/guides/setup-prompts.md +++ b/external/ag-shared/prompts/guides/setup-prompts.md @@ -51,6 +51,31 @@ case "dir": }).map((item) => join(item.parentPath, item.name)); ``` +## Patching `fromRulesyncSkill()` for New Frontmatter Fields + +Rulesync's `fromRulesyncSkill()` methods explicitly construct new frontmatter objects, picking only known fields (`name`, `description`, tool-specific extras). The `looseObject` schema preserves unknown fields through _parsing_, but the conversion code discards them. To propagate a rulesync-level field to tool-specific output, you must patch each tool's `fromRulesyncSkill()` — there is no passthrough. + +Tool-specific skill classes with `fromRulesyncSkill()`: + +- **ClaudecodeSkill** — extracts `name`, `description`, `allowed-tools` +- **CursorSkill** — extracts `name`, `description` +- **CopilotSkill** — extracts `name`, `description`, `license` +- **SimulatedSkill** (AgentsmdSkill, FactorydroidSkill) — extracts `name`, `description` via `fromRulesyncSkillDefault()` + +## Skill Invocability (`disable-model-invocation`) + +`disable-model-invocation: true` is a de facto standard shared by Claude Code, Cursor, and GitHub Copilot (VS Code agent). It is NOT part of the Agent Skills open standard (agentskills.io). + +| Tool | Supports per-skill invocation control? | Mechanism | +|------|----------------------------------------|-----------| +| Claude Code | Yes | `disable-model-invocation: true` in SKILL.md | +| Cursor | Yes | `disable-model-invocation: true` in SKILL.md | +| GitHub Copilot | Yes (VS Code agent) | `disable-model-invocation: true` in SKILL.md | +| Codex CLI | Yes (different) | `allow_implicit_invocation: false` in agents yaml | +| Gemini CLI / Cline / OpenCode | No | No per-skill control | + +In rulesync source files, use `invocable: user-only` — the patched `fromRulesyncSkill()` methods translate this to `disable-model-invocation: true` for Claude Code, Cursor, and Copilot. + ## AGENTS.md Handling The `--postinstall` flag triggers `stash_agents_md()` and `restore_agents_md()` functions which: @@ -71,6 +96,10 @@ npx patch-package rulesync This updates `patches/rulesync+*.patch` (which symlinks to `external/ag-shared/prompts/patches/`). +## Verify-rulesync Skill File Patterns + +`verify-rulesync.sh` builds an expected file inventory and content-verifies each file. Skill directories can contain arbitrary `.md` resource files (templates, page guides) beyond `SKILL.md`, `_*.md` helpers, and `*.sh` scripts. Both `build_expected_inventory()` and `verify_content()` must glob `*.md` (excluding `SKILL.md`) rather than `_*.md` to capture all of them. + ## Shared Prompt Conventions When using the inverted reference pattern (shared core + thin wrapper), follow these rules to avoid silent breakage. diff --git a/external/ag-shared/prompts/patches/rulesync+7.0.0.patch b/external/ag-shared/prompts/patches/rulesync+7.0.0.patch index 6ae68722c44..39c2baabce2 100644 --- a/external/ag-shared/prompts/patches/rulesync+7.0.0.patch +++ b/external/ag-shared/prompts/patches/rulesync+7.0.0.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/rulesync/dist/index.cjs b/node_modules/rulesync/dist/index.cjs -index b80488f..decff9f 100644 +index b80488f..d2e880b 100644 --- a/node_modules/rulesync/dist/index.cjs +++ b/node_modules/rulesync/dist/index.cjs @@ -1732,9 +1732,10 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand { @@ -14,7 +14,37 @@ index b80488f..decff9f 100644 """`; const paths = this.getSettablePaths({ global }); return new _GeminiCliCommand({ -@@ -13636,15 +13637,23 @@ var RulesProcessor = class extends FeatureProcessor { +@@ -7287,7 +7288,8 @@ var ClaudecodeSkill = class _ClaudecodeSkill extends ToolSkill { + const claudecodeFrontmatter = { + name: rulesyncFrontmatter.name, + description: rulesyncFrontmatter.description, +- "allowed-tools": rulesyncFrontmatter.claudecode?.["allowed-tools"] ++ "allowed-tools": rulesyncFrontmatter.claudecode?.["allowed-tools"], ++ ...rulesyncFrontmatter.invocable === "user-only" && { "disable-model-invocation": true } + }; + const settablePaths = _ClaudecodeSkill.getSettablePaths({ global }); + return new _ClaudecodeSkill({ +@@ -7627,7 +7629,8 @@ var CopilotSkill = class _CopilotSkill extends ToolSkill { + const copilotFrontmatter = { + name: rulesyncFrontmatter.name, + description: rulesyncFrontmatter.description, +- license: rulesyncFrontmatter.copilot?.license ++ license: rulesyncFrontmatter.copilot?.license, ++ ...rulesyncFrontmatter.invocable === "user-only" && { "disable-model-invocation": true } + }; + return new _CopilotSkill({ + baseDir: rulesyncSkill.getBaseDir(), +@@ -7784,7 +7787,8 @@ var CursorSkill = class _CursorSkill extends ToolSkill { + const rulesyncFrontmatter = rulesyncSkill.getFrontmatter(); + const cursorFrontmatter = { + name: rulesyncFrontmatter.name, +- description: rulesyncFrontmatter.description ++ description: rulesyncFrontmatter.description, ++ ...rulesyncFrontmatter.invocable === "user-only" && { "disable-model-invocation": true } + }; + return new _CursorSkill({ + baseDir: rulesyncSkill.getBaseDir(), +@@ -13636,15 +13640,23 @@ var RulesProcessor = class extends FeatureProcessor { const rulesyncBaseDir = (0, import_node_path107.join)(this.baseDir, RULESYNC_RULES_RELATIVE_DIR_PATH); const files = await findFilesByGlobs((0, import_node_path107.join)(rulesyncBaseDir, "**", "*.md")); logger.debug(`Found ${files.length} rulesync files`); @@ -45,7 +75,7 @@ index b80488f..decff9f 100644 if (rootRules.length > 1) { throw new Error("Multiple root rulesync rules found"); diff --git a/node_modules/rulesync/dist/index.js b/node_modules/rulesync/dist/index.js -index 7b7cdc2..8dfcc14 100755 +index 7b7cdc2..a62ed2a 100755 --- a/node_modules/rulesync/dist/index.js +++ b/node_modules/rulesync/dist/index.js @@ -1709,9 +1709,10 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand { @@ -60,7 +90,37 @@ index 7b7cdc2..8dfcc14 100755 """`; const paths = this.getSettablePaths({ global }); return new _GeminiCliCommand({ -@@ -13613,15 +13614,23 @@ var RulesProcessor = class extends FeatureProcessor { +@@ -7264,7 +7265,8 @@ var ClaudecodeSkill = class _ClaudecodeSkill extends ToolSkill { + const claudecodeFrontmatter = { + name: rulesyncFrontmatter.name, + description: rulesyncFrontmatter.description, +- "allowed-tools": rulesyncFrontmatter.claudecode?.["allowed-tools"] ++ "allowed-tools": rulesyncFrontmatter.claudecode?.["allowed-tools"], ++ ...rulesyncFrontmatter.invocable === "user-only" && { "disable-model-invocation": true } + }; + const settablePaths = _ClaudecodeSkill.getSettablePaths({ global }); + return new _ClaudecodeSkill({ +@@ -7604,7 +7606,8 @@ var CopilotSkill = class _CopilotSkill extends ToolSkill { + const copilotFrontmatter = { + name: rulesyncFrontmatter.name, + description: rulesyncFrontmatter.description, +- license: rulesyncFrontmatter.copilot?.license ++ license: rulesyncFrontmatter.copilot?.license, ++ ...rulesyncFrontmatter.invocable === "user-only" && { "disable-model-invocation": true } + }; + return new _CopilotSkill({ + baseDir: rulesyncSkill.getBaseDir(), +@@ -7761,7 +7764,8 @@ var CursorSkill = class _CursorSkill extends ToolSkill { + const rulesyncFrontmatter = rulesyncSkill.getFrontmatter(); + const cursorFrontmatter = { + name: rulesyncFrontmatter.name, +- description: rulesyncFrontmatter.description ++ description: rulesyncFrontmatter.description, ++ ...rulesyncFrontmatter.invocable === "user-only" && { "disable-model-invocation": true } + }; + return new _CursorSkill({ + baseDir: rulesyncSkill.getBaseDir(), +@@ -13613,15 +13617,23 @@ var RulesProcessor = class extends FeatureProcessor { const rulesyncBaseDir = join106(this.baseDir, RULESYNC_RULES_RELATIVE_DIR_PATH); const files = await findFilesByGlobs(join106(rulesyncBaseDir, "**", "*.md")); logger.debug(`Found ${files.length} rulesync files`); diff --git a/external/ag-shared/prompts/commands/batch/lint-cleanup.md b/external/ag-shared/prompts/skills/batch-lint-cleanup/SKILL.md similarity index 99% rename from external/ag-shared/prompts/commands/batch/lint-cleanup.md rename to external/ag-shared/prompts/skills/batch-lint-cleanup/SKILL.md index 0e3dd95d421..965d35db9f0 100644 --- a/external/ag-shared/prompts/commands/batch/lint-cleanup.md +++ b/external/ag-shared/prompts/skills/batch-lint-cleanup/SKILL.md @@ -1,6 +1,8 @@ --- targets: ['*'] +name: batch-lint-cleanup description: 'Analyze ESLint violations and auto-fix specific rules in isolation' +invocable: user-only --- # ESLint Auto-Fix Tool diff --git a/external/ag-shared/prompts/commands/code/cleanup.md b/external/ag-shared/prompts/skills/code-cleanup/SKILL.md similarity index 97% rename from external/ag-shared/prompts/commands/code/cleanup.md rename to external/ag-shared/prompts/skills/code-cleanup/SKILL.md index 71de2cb9ea4..97ab413dcbe 100644 --- a/external/ag-shared/prompts/commands/code/cleanup.md +++ b/external/ag-shared/prompts/skills/code-cleanup/SKILL.md @@ -1,6 +1,8 @@ --- targets: ['*'] +name: code-cleanup description: 'Review and productionize code by removing bloat, duplication, and improving clarity' +invocable: user-only --- # Distil Code Quality - Reduce Bloat and Productionize @@ -27,7 +29,7 @@ If the user provides a command option of `help`: - This project is an Nx monorepo with multiple packages. - Release branches are named `bX.Y.Z` and follow semantic versioning. - The main branch is `latest`. -- Code quality standards are documented in `external/ag-shared/prompts/guides/code-quality.md`. +- Code quality standards are documented in `.rulesync/rules/code-quality.md`. ## 3. Workflow diff --git a/external/ag-shared/prompts/commands/code/fixup.md b/external/ag-shared/prompts/skills/code-fixup/SKILL.md similarity index 97% rename from external/ag-shared/prompts/commands/code/fixup.md rename to external/ag-shared/prompts/skills/code-fixup/SKILL.md index 612d712302e..1d05bc29a5c 100644 --- a/external/ag-shared/prompts/commands/code/fixup.md +++ b/external/ag-shared/prompts/skills/code-fixup/SKILL.md @@ -1,6 +1,8 @@ --- targets: ['*'] +name: code-fixup description: 'Fix build and lint errors by running commands, grouping issues, and orchestrating fixes' +invocable: user-only --- # Fixup build and lint errors diff --git a/external/ag-shared/prompts/commands/git/bisect.md b/external/ag-shared/prompts/skills/git-bisect/SKILL.md similarity index 99% rename from external/ag-shared/prompts/commands/git/bisect.md rename to external/ag-shared/prompts/skills/git-bisect/SKILL.md index d139df1cb69..fc69f51aeea 100644 --- a/external/ag-shared/prompts/commands/git/bisect.md +++ b/external/ag-shared/prompts/skills/git-bisect/SKILL.md @@ -1,6 +1,8 @@ --- targets: ['*'] +name: git-bisect description: 'Find the commit that introduced test failures using git bisect' +invocable: user-only --- # Git Bisect - Find the Commit That Introduced Test Failures diff --git a/external/ag-shared/prompts/commands/git/split.md b/external/ag-shared/prompts/skills/git-split/SKILL.md similarity index 99% rename from external/ag-shared/prompts/commands/git/split.md rename to external/ag-shared/prompts/skills/git-split/SKILL.md index 7c796760f4c..cb4d4c85e10 100644 --- a/external/ag-shared/prompts/commands/git/split.md +++ b/external/ag-shared/prompts/skills/git-split/SKILL.md @@ -1,6 +1,8 @@ --- targets: ['*'] +name: git-split description: 'Split large files into smaller modules while preserving git history (blame, log)' +invocable: user-only --- # Split Large Files with Git History Preservation diff --git a/external/ag-shared/prompts/commands/git/worktree-clean.md b/external/ag-shared/prompts/skills/git-worktree-clean/SKILL.md similarity index 97% rename from external/ag-shared/prompts/commands/git/worktree-clean.md rename to external/ag-shared/prompts/skills/git-worktree-clean/SKILL.md index 45f6469b329..6fabf5b0182 100644 --- a/external/ag-shared/prompts/commands/git/worktree-clean.md +++ b/external/ag-shared/prompts/skills/git-worktree-clean/SKILL.md @@ -1,6 +1,8 @@ --- targets: ['*'] +name: git-worktree-clean description: 'Clean worktree by fetching and hard-resetting to origin/latest (or specified branch)' +invocable: user-only --- # Worktree Clean diff --git a/external/ag-shared/prompts/commands/plan/implementation-review.md b/external/ag-shared/prompts/skills/plan-implementation-review/SKILL.md similarity index 99% rename from external/ag-shared/prompts/commands/plan/implementation-review.md rename to external/ag-shared/prompts/skills/plan-implementation-review/SKILL.md index ee9c3666c61..3c85930b4ff 100644 --- a/external/ag-shared/prompts/commands/plan/implementation-review.md +++ b/external/ag-shared/prompts/skills/plan-implementation-review/SKILL.md @@ -1,6 +1,8 @@ --- targets: ['*'] +name: plan-implementation-review description: 'Review plan execution completeness and identify delivery gaps' +invocable: user-only --- # Plan Implementation Review Prompt diff --git a/external/ag-shared/prompts/commands/plan/review.md b/external/ag-shared/prompts/skills/plan-review/SKILL.md similarity index 99% rename from external/ag-shared/prompts/commands/plan/review.md rename to external/ag-shared/prompts/skills/plan-review/SKILL.md index 8bd1cf2ed67..4f429c1730e 100644 --- a/external/ag-shared/prompts/commands/plan/review.md +++ b/external/ag-shared/prompts/skills/plan-review/SKILL.md @@ -1,6 +1,8 @@ --- targets: ['*'] +name: plan-review description: 'Review plans for completeness, correctness, and verifiability' +invocable: user-only --- # Plan Review Prompt diff --git a/external/ag-shared/prompts/commands/pr/create.md b/external/ag-shared/prompts/skills/pr-create/SKILL.md similarity index 99% rename from external/ag-shared/prompts/commands/pr/create.md rename to external/ag-shared/prompts/skills/pr-create/SKILL.md index fae7728209f..4a329589b59 100644 --- a/external/ag-shared/prompts/commands/pr/create.md +++ b/external/ag-shared/prompts/skills/pr-create/SKILL.md @@ -1,6 +1,8 @@ --- targets: ['*'] +name: pr-create description: 'Create a PR from current commits and/or local changes. Identifies base branch, creates topic branch if needed, commits changes, and opens a PR.' +invocable: user-only --- # Create Pull Request diff --git a/external/ag-shared/prompts/commands/pr/review-json.md b/external/ag-shared/prompts/skills/pr-review/SKILL.md similarity index 69% rename from external/ag-shared/prompts/commands/pr/review-json.md rename to external/ag-shared/prompts/skills/pr-review/SKILL.md index 2d6b0bd943f..fd15c1aba28 100644 --- a/external/ag-shared/prompts/commands/pr/review-json.md +++ b/external/ag-shared/prompts/skills/pr-review/SKILL.md @@ -1,17 +1,81 @@ --- targets: ['*'] -description: 'Review pull requests with structured JSON output for inline commenting' +name: pr-review +description: 'Review pull requests with Markdown or JSON output' +invocable: user-only --- -# PR Review Instructions (JSON Output) +# PR Review Instructions You are acting as a reviewer for a proposed code change. Your goal is to identify issues that could impact the quality, correctness, or safety of the codebase. -**Read and follow all instructions in `external/ag-shared/prompts/commands/pr/_review-core.md` for the review methodology.** +**Read and follow all instructions in the co-located `_review-core.md` file (in this skill's directory) for the review methodology.** + +## Arguments + +Parse the `ARGUMENTS` environment variable (or skill arguments) for flags and the PR number: + +- `--json` — output structured JSON instead of Markdown (used for inline commenting in CI) +- Remaining positional argument — the PR number + +Examples: `123`, `--json 123`, `123 --json` ## Output Format -**CRITICAL**: Output ONLY valid JSON. No markdown code fences, no explanatory text before or after. The output must be parseable by `JSON.parse()`. +### Default: Markdown + +When `--json` is **not** specified, output the review directly to the terminal using this Markdown structure: + +```markdown +# PR Review: #{PR_NUMBER} - {PR_TITLE} + +**PR:** {PR_URL} +**Author:** {AUTHOR} | **Base:** {BASE_BRANCH} ← **Head:** {HEAD_BRANCH} + +## Summary + +{1-2 sentence summary of what this PR does} + +## Findings + +### P0 - Critical + +{List P0 issues, or "None" if empty} + +- **`{filepath}:{start_line}-{end_line}`** - {Issue title} + {Short explanation of the issue and why it's critical} + +### P1 - High + +{List P1 issues, or "None" if empty} + +- **`{filepath}:{line}`** - {Issue title} + {Short explanation} + +### P2 - Medium + +{List P2 issues, or "None" if empty} + +- **`{filepath}:{line}`** - {Issue title} + {Short explanation} + +--- + +_{N} low-priority issues omitted._ + +## Verdict + +**Assessment:** {Patch is correct | Patch is incorrect} +**Confidence:** {0.0-1.0} + +{Concise justification for the verdict - 1-2 sentences} + +**Required Actions:** {Bulleted list of required fixes, or "None - ready to merge"} +``` + +### JSON Mode (`--json`) + +When `--json` is specified, output **ONLY** valid JSON. No markdown code fences, no explanatory text before or after. The output must be parseable by `JSON.parse()`. ```json { @@ -53,7 +117,7 @@ You are acting as a reviewer for a proposed code change. Your goal is to identif } ``` -### Field Definitions +#### Field Definitions | Field | Type | Description | |-------|------|-------------| @@ -82,7 +146,7 @@ You are acting as a reviewer for a proposed code change. Your goal is to identif | `diff_stats.lines_added` | number | Number of lines added (+) | | `diff_stats.lines_removed` | number | Number of lines removed (-) | -## Example Output +#### Example JSON Output ```json { diff --git a/external/ag-shared/prompts/commands/pr/_review-core.md b/external/ag-shared/prompts/skills/pr-review/_review-core.md similarity index 100% rename from external/ag-shared/prompts/commands/pr/_review-core.md rename to external/ag-shared/prompts/skills/pr-review/_review-core.md diff --git a/external/ag-shared/prompts/commands/pr/split.md b/external/ag-shared/prompts/skills/pr-split/SKILL.md similarity index 99% rename from external/ag-shared/prompts/commands/pr/split.md rename to external/ag-shared/prompts/skills/pr-split/SKILL.md index ad190bbb277..34e8ebc392d 100644 --- a/external/ag-shared/prompts/commands/pr/split.md +++ b/external/ag-shared/prompts/skills/pr-split/SKILL.md @@ -1,6 +1,8 @@ --- targets: ['*'] +name: pr-split description: 'Split a branch into a logical sequence of stacked PRs for easier review' +invocable: user-only --- # PR Split Instructions diff --git a/external/ag-shared/prompts/commands/recall.md b/external/ag-shared/prompts/skills/recall/SKILL.md similarity index 57% rename from external/ag-shared/prompts/commands/recall.md rename to external/ag-shared/prompts/skills/recall/SKILL.md index 8150e47f2c1..87f4087f291 100644 --- a/external/ag-shared/prompts/commands/recall.md +++ b/external/ag-shared/prompts/skills/recall/SKILL.md @@ -1,6 +1,9 @@ --- targets: ['*'] -description: 'Load branch context and browse project memory for session resumption' +name: recall +description: Load branch context and browse project memory for session resumption +invocable: user-only +context: fork --- # Recall @@ -11,37 +14,25 @@ Load branch-scoped context from `.context/` and optionally browse project-scoped ## STEP 1: Load Branch Memory -### Determine Context File Path +### Determine Context File Path and Load Content -```bash -# Get current branch name -BRANCH=$(git branch --show-current) - -# Get main repo root (works from worktrees) -MAIN_REPO=$(git rev-parse --path-format=absolute --git-common-dir | sed 's/\.git$//') +Run the co-located script to resolve paths and load any existing context. Use the skill base directory from the header above: -# Derive slug with hash suffix to avoid collisions (e.g. feature/foo vs feature-foo) -SLUG_BASE=$(echo "$BRANCH" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//') -HASH=$(echo -n "$BRANCH" | shasum | cut -c1-6) -SLUG="${SLUG_BASE}-${HASH}" - -# Context file path -CONTEXT_FILE="${MAIN_REPO}.context/${SLUG}.md" +```bash +bash "/context-path.sh" --list-rules ``` -### Load and Present +Parse the structured output: +- `BRANCH=` — current branch name +- `SLUG=` — filename slug +- `CONTEXT_FILE=` — full path to context file +- `STATUS=found|not_found` — whether context exists +- Content after `---CONTENT---` — existing context file contents (if found) +- Content after `---RULES---` — list of project rule files (if present) -Check if the context file exists: - -```bash -if [ -f "$CONTEXT_FILE" ]; then - echo "Found context file: $CONTEXT_FILE" -else - echo "No context file found for branch: $BRANCH" -fi -``` +### Present Branch Context -**If found**, read and present its contents: +**If found**, present its contents: ```markdown ## Branch Context Loaded: {branch} @@ -74,7 +65,7 @@ After presenting branch context, offer to show project memory: If the user says yes: -1. List `.rulesync/rules/` files with a one-line description of each +1. Present the rules listing from the `---RULES---` section of the script output 2. User can request to read specific memory files for details 3. This is informational — project rules auto-load during normal work via globs diff --git a/external/ag-shared/prompts/skills/recall/context-path.sh b/external/ag-shared/prompts/skills/recall/context-path.sh new file mode 120000 index 00000000000..9f73e84c967 --- /dev/null +++ b/external/ag-shared/prompts/skills/recall/context-path.sh @@ -0,0 +1 @@ +../remember/context-path.sh \ No newline at end of file diff --git a/external/ag-shared/prompts/commands/remember.md b/external/ag-shared/prompts/skills/remember/SKILL.md similarity index 87% rename from external/ag-shared/prompts/commands/remember.md rename to external/ag-shared/prompts/skills/remember/SKILL.md index 2c27e71abe4..8e7f07a7af0 100644 --- a/external/ag-shared/prompts/commands/remember.md +++ b/external/ag-shared/prompts/skills/remember/SKILL.md @@ -1,6 +1,9 @@ --- targets: ['*'] -description: 'Save branch context or project learnings as agentic memory' +name: remember +description: Save branch context or project learnings as agentic memory +invocable: user-only +context: fork --- # Remember @@ -29,28 +32,22 @@ Save or update context for the current branch. Keep it concise — only preserve ### STEP B1: Determine Context File Path -```bash -# Get current branch name -BRANCH=$(git branch --show-current) - -# Get main repo root (works from worktrees) -MAIN_REPO=$(git rev-parse --path-format=absolute --git-common-dir | sed 's/\.git$//') - -# Derive slug with hash suffix to avoid collisions (e.g. feature/foo vs feature-foo) -SLUG_BASE=$(echo "$BRANCH" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//') -HASH=$(echo -n "$BRANCH" | shasum | cut -c1-6) -SLUG="${SLUG_BASE}-${HASH}" +Run the co-located script to resolve paths and load any existing context. Use the skill base directory from the header above: -# Context file path -CONTEXT_FILE="${MAIN_REPO}.context/${SLUG}.md" - -# Ensure directory exists -mkdir -p "${MAIN_REPO}.context" +```bash +bash "/context-path.sh" --ensure-dir ``` +Parse the structured output: +- `BRANCH=` — current branch name +- `SLUG=` — filename slug +- `CONTEXT_FILE=` — full path to context file +- `STATUS=found|not_found` — whether context exists +- Content after `---CONTENT---` — existing context file contents (if found) + ### STEP B2: Check for Existing Context -If the context file already exists, read its current contents. When updating, **prune resolved items and transient issues**. +If the context file already exists, read its current contents from the script output. When updating, **prune resolved items and transient issues**. ### STEP B3: Gather Context Information diff --git a/external/ag-shared/prompts/skills/remember/context-path.sh b/external/ag-shared/prompts/skills/remember/context-path.sh new file mode 100755 index 00000000000..4805dbc90f3 --- /dev/null +++ b/external/ag-shared/prompts/skills/remember/context-path.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Usage: context-path.sh [--ensure-dir] [--list-rules] +# Output: key=value pairs, then ---CONTENT--- section, then optional ---RULES--- section +set -euo pipefail + +BRANCH=$(git branch --show-current) +MAIN_REPO=$(git rev-parse --path-format=absolute --git-common-dir | sed 's/\.git$//') +SLUG_BASE=$(echo "$BRANCH" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//') +HASH=$(echo -n "$BRANCH" | shasum | cut -c1-6) +SLUG="${SLUG_BASE}-${HASH}" +CONTEXT_FILE="${MAIN_REPO}.context/${SLUG}.md" + +while [[ $# -gt 0 ]]; do + case "$1" in + --ensure-dir) mkdir -p "${MAIN_REPO}.context"; shift ;; + --list-rules) LIST_RULES=1; shift ;; + *) shift ;; + esac +done + +echo "BRANCH=$BRANCH" +echo "SLUG=$SLUG" +echo "CONTEXT_FILE=$CONTEXT_FILE" + +if [ -f "$CONTEXT_FILE" ]; then + echo "STATUS=found" + echo "---CONTENT---" + cat "$CONTEXT_FILE" +else + echo "STATUS=not_found" +fi + +if [[ "${LIST_RULES:-}" == "1" ]]; then + echo "---RULES---" + RULES_DIR="${MAIN_REPO}.rulesync/rules/" + if [ -d "$RULES_DIR" ]; then + for f in "$RULES_DIR"*.md; do + [ -f "$f" ] || continue + echo "$(basename "$f"): $(grep -m1 '^[^-#[:space:]]' "$f" 2>/dev/null || echo '(no description)')" + done + fi +fi diff --git a/external/ag-shared/prompts/skills/sync-ag-shared/SKILL.md b/external/ag-shared/prompts/skills/sync-ag-shared/SKILL.md new file mode 100644 index 00000000000..28edfa92ef2 --- /dev/null +++ b/external/ag-shared/prompts/skills/sync-ag-shared/SKILL.md @@ -0,0 +1,353 @@ +--- +targets: ['*'] +name: sync-ag-shared +description: 'Sync ag-shared subrepo changes across ag-charts, ag-grid, and ag-studio repos' +invocable: user-only +--- + +# Sync ag-shared Subrepo Across AG Repos + +Orchestrate syncing `external/ag-shared/` changes from the current repo to all other AG repos that consume the subrepo. This handles the full `git subrepo push` / `pull` cycle, companion changes, and cross-linked PRs. + +## Help + +If the user provides a command option of `help`: + +- Explain how to use this skill. +- Explain the prerequisites and what will happen. +- DO NOT proceed, exit the skill immediately after these steps. + +## Prerequisites + +- Git CLI, GitHub CLI (`gh`), and `yarn` must be available. +- `git subrepo` must be installed (`git subrepo --version`). +- Must be on a **feature branch** (not `latest`, `main`, or `master`). +- Working tree must be **clean** (`git status --porcelain` is empty). +- The current repo must have `external/ag-shared/.gitrepo`. + +## STEP 1: Gather State + +Collect all context needed to plan the sync. + +### 1a. Identify Source Repo + +```bash +# The working directory where the skill was invoked — use this for ALL +# git/subrepo commands in the source repo (critical for worktrees). +SOURCE_WD=$(pwd) + +# Resolve the real repo root (worktrees resolve to actual repo location). +# Only used for discovering sibling destination repos, NOT for running commands. +REPO_GIT_DIR=$(git rev-parse --git-common-dir) +SOURCE_ROOT=$(cd "$(dirname "$REPO_GIT_DIR")" && pwd) + +# Current branch +SOURCE_BRANCH=$(git rev-parse --abbrev-ref HEAD) + +# Repo name (for display) +SOURCE_REPO=$(basename "$SOURCE_ROOT") +``` + +**Important — worktree awareness:** When invoked from a git worktree, the feature branch is checked out in the worktree, and the main repo checkout is typically on `latest` (or another branch). You **cannot** `git checkout` the feature branch in the main repo because git prevents a branch from being checked out in two places simultaneously. Always run subrepo and git commands from `SOURCE_WD` (the worktree), never from `SOURCE_ROOT`. + +Validate: + +- `SOURCE_BRANCH` is not `latest`, `main`, or `master`. +- `git status --porcelain` is empty. +- `external/ag-shared/.gitrepo` exists. + +If any validation fails, report the issue and **STOP**. + +### 1b. Discover Destination Repos + +Destination repos are **siblings** of the source repo root. Look for directories at the same level that contain `external/ag-shared/.gitrepo`. + +```bash +PARENT_DIR=$(dirname "$SOURCE_ROOT") +for dir in "$PARENT_DIR"/*/; do + if [ "$dir" != "$SOURCE_ROOT/" ] && [ -f "${dir}external/ag-shared/.gitrepo" ]; then + echo "Found destination: $dir" + fi +done +``` + +Collect the list of destination repos. Typical destinations are two of `ag-charts`, `ag-grid` and `ag-studio`, but discover dynamically. + +### 1c. Validate Destinations + +For each destination repo: + +- Check it has a clean working tree. +- Check it is on `latest` or a feature branch. +- Run `git fetch origin` to ensure it is up to date. + +If any destination has uncommitted changes, default to stashing all changes and continuing - but ask the user to confirm. + +## STEP 2: Analyse Source Changes + +Use a sub-agent (Task tool, `subagent_type: Explore`) to analyse changes on the source branch: + +```bash +# Changes inside ag-shared +git diff latest...HEAD -- external/ag-shared/ + +# Changes outside ag-shared +git diff latest...HEAD -- ':!external/ag-shared/' + +# Commit log +git log --oneline latest...HEAD +``` + +The sub-agent should produce: + +1. **Change summary** — what files changed in `external/ag-shared/` and why. +2. **Companion change predictions** — based on the ag-shared changes, what companion changes are likely needed in each destination repo. For example: + - New/renamed skills may need symlink updates in `.rulesync/`. + - Changed rule globs may need `.claude/settings.json` updates. + - Script changes may need `package.json` or CI updates. + - Setup-prompts changes need `setup-prompts.sh` re-run in each repo. + +## STEP 3: Present Plan and Confirm + +Display to the user: + +``` +## ag-shared Sync Plan + +**Source:** @ +**Destinations:** + +### Changes in ag-shared + + +### Changes outside ag-shared + + +### Predicted Companion Changes + + +### Steps +1. Push ag-shared from +2. Create sync/ branches in each destination +3. Pull ag-shared in each destination +4. Apply companion changes in each destination +5. Verify all repos +6. Push branches and create cross-linked PRs (reuse existing source PR if one exists) +``` + +Use `AskUserQuestion` to confirm before proceeding. The user may want to adjust the plan or skip certain destinations. + +## STEP 4: Push Source ag-shared + +From the source working directory (the worktree or repo where the skill was invoked): + +```bash +cd "$SOURCE_WD" +yarn subrepo push ag-shared +``` + +### Handling "need to pull first" + +If the push fails with _"There are new changes upstream, you need to pull first"_, this means the ag-shared remote has commits not yet in this branch. Handle it: + +```bash +cd "$SOURCE_WD" +yarn subrepo pull ag-shared # Integrates upstream changes +git diff HEAD~1 --stat # Show what the pull changed — verify before continuing +yarn subrepo push ag-shared # Retry the push +``` + +### Stale lock files + +If a subrepo command fails mid-operation, it may leave a stale git lock file. Check for and remove it before retrying: + +```bash +# For worktrees: +LOCK_FILE=$(git rev-parse --git-dir)/index.lock +[ -f "$LOCK_FILE" ] && rm "$LOCK_FILE" + +# Also restore any partially-modified .gitrepo: +git checkout -- external/ag-shared/.gitrepo +``` + +If the push still fails after pulling, report the error and **STOP**. + +## STEP 5: Create Sync Branches and Pull + +For each destination repo: + +```bash +cd "" + +# Fetch latest +git fetch origin + +# Create sync branch from origin/latest +git checkout -b "sync/${SOURCE_BRANCH}" origin/latest + +# Pull ag-shared updates +yarn subrepo pull ag-shared + +# Show what the pull changed — verify files match expected changes from Step 2 +git diff HEAD~1 --stat + +# Verify the pull succeeded +git subrepo status ag-shared +``` + +If `subrepo pull` fails in any repo, report the error and **STOP** — ask the user how to proceed. + +## STEP 6: Apply Companion Changes + +For each destination repo, launch a **sub-agent** (Task tool, `subagent_type: general-purpose`) to apply predicted companion changes. Provide the sub-agent with: + +- The destination repo path. +- The change summary from Step 2. +- The predicted companion changes for this specific repo. +- Instructions to replicate patterns from the source repo. + +Common companion tasks: + +- Run `./external/ag-shared/scripts/setup-prompts/setup-prompts.sh` to regenerate `.claude/` from `.rulesync/`. +- Update `.rulesync/` symlinks if skills/rules were added, renamed, or removed. +- Update product-specific configurations if ag-shared scripts changed. +- Run verification: `./external/ag-shared/scripts/setup-prompts/verify-rulesync.sh`. +- **Run `npx nx format` (or equivalent formatter) before committing** to avoid CI formatting check failures. + +### Iterative Push/Pull (if needed) + +If companion changes modify files inside `external/ag-shared/` (rare but possible): + +1. Commit the changes in the destination repo. +2. `yarn subrepo push ag-shared` from the destination. +3. Go back to the source repo and other destinations: `yarn subrepo pull ag-shared`. +4. Re-verify. + +**Cap iterations at 3.** If changes still bounce after 3 rounds, stop and ask the user. + +## STEP 7: Verify + +For each repo (source + all destinations): + +```bash +# Check subrepo status +git subrepo status ag-shared + +# Verify clean working tree +git status --porcelain + +# Run rulesync verification if available +if [ -f "./external/ag-shared/scripts/setup-prompts/verify-rulesync.sh" ]; then + ./external/ag-shared/scripts/setup-prompts/verify-rulesync.sh +fi +``` + +Report any issues. All repos must have clean working trees and passing verification. + +## STEP 8: Commit, Push, and Create PRs + +### 8a. Push All Branches + +For the source repo (if not already pushed): + +```bash +cd "$SOURCE_WD" +git push -u origin "$SOURCE_BRANCH" +``` + +For each destination repo: + +```bash +cd "" +git push -u origin "sync/${SOURCE_BRANCH}" +``` + +### 8b. Audit PR Diffs for Unrelated Changes + +Before creating PRs, check each destination branch for unrelated changes that may have crept in (e.g. files modified on `origin/latest` after the branch point): + +```bash +cd "" +git diff origin/latest...HEAD --stat +``` + +Review the diff stat. If any files outside `external/ag-shared/` and `.rulesync/` appear that are not companion changes, revert them: + +```bash +git checkout origin/latest -- +git commit -m "Revert unrelated changes to " +``` + +### 8c. Create Cross-Linked PRs + +Create a PR in each repo. All PRs should reference each other. + +**Check for existing PRs first.** The source branch may already have an open PR. Always check before creating: + +```bash +cd "$SOURCE_WD" +SOURCE_PR_URL=$(gh pr view "$SOURCE_BRANCH" --json url -q '.url' 2>/dev/null) +``` + +If an existing PR is found, **reuse it** — update its description to add cross-repo links rather than creating a new PR. Only create a new PR if none exists: + +```bash +if [ -z "$SOURCE_PR_URL" ]; then + SOURCE_PR_URL=$(gh pr create --base latest --title "" --body "...") +fi +``` + +For destination repos, create new PRs (these are always new sync branches): + +```bash +cd "<DEST_ROOT>" +DEST_PR_URL=$(gh pr create --base latest --title "Sync ag-shared from <SOURCE_BRANCH>" --body "$(cat <<'EOF' +## Summary +Sync ag-shared subrepo from <SOURCE_REPO>@<SOURCE_BRANCH>. + +<companion change summary if any> + +## Cross-repo PRs +- Source: <SOURCE_PR_URL> + +## Test plan +- [ ] Verify ag-shared content matches source +- [ ] Run setup-prompts verification +EOF +)") +``` + +Then update all PR descriptions (source and destinations) to cross-link with each other. For existing source PRs, **append** the cross-repo links section rather than replacing the entire body. + +### 8d. Report Results + +Output a summary: + +``` +## Sync Complete + +| Repo | Branch | PR | +| ---- | ------ | -- | +| <source> | <branch> | <url> | +| <dest1> | sync/<branch> | <url> | +| <dest2> | sync/<branch> | <url> | + +All repos verified. Working trees clean. +``` + +## Error Handling + +- **Merge conflicts during subrepo pull:** Stop and ask the user to resolve manually. Provide the conflicting files and repo path. +- **Auth failures:** Check `gh auth status` and `git remote -v`. Ask the user to authenticate. +- **Dirty working tree:** Always stop and report. Never force-clean a destination repo. +- **Subrepo push/pull failures:** Report the full error output. Common causes: diverged history (pull first, then push), missing remote access. +- **Stale git lock files:** A failed subrepo operation may leave `index.lock` in the git dir. Remove it and restore `.gitrepo` before retrying (see Step 4). +- **Worktree branch conflicts:** Never try to `git checkout` the source branch in the main repo — it's already checked out in the worktree. Always `cd` to the worktree working directory for source repo commands. + +## Arguments + +`${ARGUMENTS}` can optionally include: + +- `--skip <repo>` — skip a specific destination repo. +- `--dry-run` — analyse and present plan only, do not execute. +- `--no-pr` — sync branches but do not create PRs. diff --git a/external/ag-shared/scripts/setup-prompts/verify-rulesync.sh b/external/ag-shared/scripts/setup-prompts/verify-rulesync.sh index 476b885184d..1a4154821a9 100755 --- a/external/ag-shared/scripts/setup-prompts/verify-rulesync.sh +++ b/external/ag-shared/scripts/setup-prompts/verify-rulesync.sh @@ -322,8 +322,16 @@ build_expected_inventory() { local dirname dirname=$(basename "$skill_dir") EXPECTED_FILES+=("skills/$dirname/SKILL.md") - # Include _ prefixed helper files (e.g., _dev-server-core.md) - for helper_file in "$skill_dir"_*.md; do + # Include all non-SKILL.md markdown files (helpers, templates, guides) + for helper_file in "$skill_dir"*.md; do + if [[ -f "$helper_file" && "$(basename "$helper_file")" != "SKILL.md" ]]; then + local helper_basename + helper_basename=$(basename "$helper_file") + EXPECTED_FILES+=("skills/$dirname/$helper_basename") + fi + done + # Include co-located shell scripts (e.g., context-path.sh) + for helper_file in "$skill_dir"*.sh; do if [[ -f "$helper_file" ]]; then local helper_basename helper_basename=$(basename "$helper_file") @@ -617,6 +625,34 @@ verify_content() { log_success "Content verified: skills/$dirname/SKILL.md" fi fi + + # Verify non-SKILL.md helper files (templates, guides, etc.) + for helper_file in "$skill_dir"*.md; do + if [[ -f "$helper_file" && "$(basename "$helper_file")" != "SKILL.md" ]]; then + local helper_basename + helper_basename=$(basename "$helper_file") + local helper_source + helper_source=$(resolve_symlink "$helper_file") + local helper_output="$temp_output/skills/$dirname/$helper_basename" + + if [[ -f "$helper_output" ]]; then + local source_content + local output_content + source_content=$(strip_frontmatter "$helper_source" | normalise_content) + output_content=$(strip_frontmatter "$helper_output" | normalise_content) + + if [[ "$source_content" != "$output_content" ]]; then + log_error "Content mismatch: skills/$dirname/$helper_basename" + log_info " Source: $helper_source" + log_info " Output: $helper_output" + diff <(echo "$source_content") <(echo "$output_content") | head -20 || true + ((content_errors++)) || true + else + log_success "Content verified: skills/$dirname/$helper_basename" + fi + fi + fi + done done fi diff --git a/packages/ag-grid-enterprise/src/license/shared/licenseManager.test.ts b/packages/ag-grid-enterprise/src/license/shared/licenseManager.test.ts index 4f8babb0ea9..ba00a84d596 100644 --- a/packages/ag-grid-enterprise/src/license/shared/licenseManager.test.ts +++ b/packages/ag-grid-enterprise/src/license/shared/licenseManager.test.ts @@ -1,6 +1,14 @@ /* eslint no-console: 0 */ import { LicenseManager } from './licenseManager'; +function createMockDocument(hostname: string, pathname = '/'): Document { + return { + defaultView: { + location: { hostname, pathname }, + }, + } as unknown as Document; +} + describe('LicenseManager', () => { const warnLog = console.warn; beforeEach(() => { @@ -28,4 +36,31 @@ describe('LicenseManager', () => { expect(console.warn.mock.calls[0][0]).toContain('AG Grid: warning #291'); }); + + describe('isWebsiteUrl (via isDisplayWatermark)', () => { + function createManagerWithWatermark(hostname: string): LicenseManager { + const manager = new LicenseManager(createMockDocument(hostname)); + // Trigger validateLicense to set a watermark message (no license key set) + manager.validateLicense(); + return manager; + } + + test.each(['ag-grid.com', 'www.ag-grid.com', 'sub.ag-grid.com'])('suppresses watermark on %s', (hostname) => { + const manager = createManagerWithWatermark(hostname); + expect(manager.isDisplayWatermark()).toBe(false); + }); + + test.each(['bryntum.com', 'www.bryntum.com', 'sub.bryntum.com'])('suppresses watermark on %s', (hostname) => { + const manager = createManagerWithWatermark(hostname); + expect(manager.isDisplayWatermark()).toBe(false); + }); + + test.each(['example.com', 'not-ag-grid.com', 'ag-grid.com.evil.com', 'fake-bryntum.com'])( + 'does not suppress watermark on %s', + (hostname) => { + const manager = createManagerWithWatermark(hostname); + expect(manager.isDisplayWatermark()).toBe(true); + } + ); + }); }); diff --git a/packages/ag-grid-enterprise/src/license/shared/licenseManager.ts b/packages/ag-grid-enterprise/src/license/shared/licenseManager.ts index 254d95bddfc..baa5dd88b28 100644 --- a/packages/ag-grid-enterprise/src/license/shared/licenseManager.ts +++ b/packages/ag-grid-enterprise/src/license/shared/licenseManager.ts @@ -198,7 +198,7 @@ export class LicenseManager { private isWebsiteUrl(): boolean { const hostname = this.getHostname(); - return hostname.match(/^((?:[\w-]+\.)?ag-grid\.com)$/) !== null; + return hostname.match(/^(?:[\w-]+\.)?(ag-grid|bryntum)\.com$/) !== null; } private isLocalhost(): boolean { diff --git a/packages/ag-grid-vue3/src/components/utils.ts b/packages/ag-grid-vue3/src/components/utils.ts index 0ab9883425b..06f0f70fdaf 100644 --- a/packages/ag-grid-vue3/src/components/utils.ts +++ b/packages/ag-grid-vue3/src/components/utils.ts @@ -2534,24 +2534,9 @@ export function deepToRaw<T extends Record<string, any>>(sourceObj: T): T { return input; } seen.add(input); - // Check if any value needs unwrapping before allocating a new object + // Always create a shallow copy so the grid's reference equality check + // detects in-place mutations (AG-14654) const keys = Object.keys(input); - let needsCopy = false; - for (let i = 0; i < keys.length; i++) { - const val = input[keys[i]]; - if (val !== null && typeof val === 'object') { - if (isRef(val) || isReactive(val) || isProxy(val)) { - needsCopy = true; - break; - } - // Nested object/array — need to recurse - needsCopy = true; - break; - } - } - if (!needsCopy) { - return input; - } const result: Record<string, any> = {}; for (let i = 0; i < keys.length; i++) { result[keys[i]] = objectIterator(input[keys[i]]); diff --git a/testing/vue3-tests/src/router.ts b/testing/vue3-tests/src/router.ts index 3a10faab738..0b7aab8a06d 100644 --- a/testing/vue3-tests/src/router.ts +++ b/testing/vue3-tests/src/router.ts @@ -90,6 +90,11 @@ const routes = [ name: 'AG-14783 Copy Paste', component: () => import('./test-cases/AG-14783-copy-paste/Page.vue'), }, + { + path: '/ag-14654', + name: 'AG-14654 Change Detection', + component: () => import('./test-cases/AG-14654-change-detection/Page.vue'), + }, ]; export const router = createRouter({ diff --git a/testing/vue3-tests/src/test-cases/AG-13735-class-instances-rowdata/Page.vue b/testing/vue3-tests/src/test-cases/AG-13735-class-instances-rowdata/Page.vue index 40aae25a317..d6654573987 100644 --- a/testing/vue3-tests/src/test-cases/AG-13735-class-instances-rowdata/Page.vue +++ b/testing/vue3-tests/src/test-cases/AG-13735-class-instances-rowdata/Page.vue @@ -1,8 +1,14 @@ <template> - <ag-grid-vue style="width: 100%; height: 500px" :columnDefs="colDefs" :rowData="rowData" /> + <ag-grid-vue + :columnDefs="colDefs" + :rowData="rowData" + class="ag-theme-quartz" + style="width: 100%; height: 500px" + theme="legacy" + /> </template> -<script setup lang="ts"> +<script lang="ts" setup> import { onMounted, ref } from 'vue'; import type { ColDef } from 'ag-grid-community'; diff --git a/testing/vue3-tests/src/test-cases/AG-14083-rowdata-object-functions/Page.vue b/testing/vue3-tests/src/test-cases/AG-14083-rowdata-object-functions/Page.vue index 18ab936e2f0..27391af1fad 100644 --- a/testing/vue3-tests/src/test-cases/AG-14083-rowdata-object-functions/Page.vue +++ b/testing/vue3-tests/src/test-cases/AG-14083-rowdata-object-functions/Page.vue @@ -1,8 +1,14 @@ <template> - <ag-grid-vue style="width: 100%; height: 500px" :columnDefs="colDefs" :rowData="rowData" /> + <ag-grid-vue + :columnDefs="colDefs" + :rowData="rowData" + style="width: 100%; height: 500px" + class="ag-theme-quartz" + theme="legacy" + /> </template> -<script setup lang="ts"> +<script lang="ts" setup> import Decimal from 'decimal.js'; import { onMounted, ref } from 'vue'; diff --git a/testing/vue3-tests/src/test-cases/AG-14654-change-detection/Page.vue b/testing/vue3-tests/src/test-cases/AG-14654-change-detection/Page.vue new file mode 100644 index 00000000000..e36837dfc1e --- /dev/null +++ b/testing/vue3-tests/src/test-cases/AG-14654-change-detection/Page.vue @@ -0,0 +1,42 @@ +<script setup lang="ts"> +import { ref, shallowRef } from 'vue'; + +import type { ColDef, GetRowIdParams, GridReadyEvent } from 'ag-grid-community'; +import { AgGridVue } from 'ag-grid-vue3'; + +interface ICar { + make: string; + model: string; + price: number; +} + +const columnDefs = ref<ColDef[]>([{ field: 'make' }, { field: 'price' }]); + +const rowData = ref<ICar[]>([ + { make: 'Toyota', model: 'Celica', price: 35000 }, + { make: 'Porsche', model: 'Boxster', price: 72000 }, + { make: 'Aston Martin', model: 'DBX', price: 190000 }, +]); + +function getRowId(params: GetRowIdParams<ICar>) { + return `${params.data.make}:${params.data.model}`; +} + +const onGridReady = (params: GridReadyEvent) => { + setInterval(() => { + rowData.value[0].price = Math.round(Math.random() * 100000); + }, 200); +}; +</script> + +<template> + <ag-grid-vue + style="width: 500px; height: 400px" + class="ag-theme-quartz" + theme="legacy" + :columnDefs="columnDefs" + :rowData="rowData" + :getRowId="getRowId" + @grid-ready="onGridReady" + /> +</template> diff --git a/testing/vue3-tests/tests/AG-14654-change-detection.vue3.spec.ts b/testing/vue3-tests/tests/AG-14654-change-detection.vue3.spec.ts new file mode 100644 index 00000000000..8ede70c493d --- /dev/null +++ b/testing/vue3-tests/tests/AG-14654-change-detection.vue3.spec.ts @@ -0,0 +1,21 @@ +import { expect, test } from '@playwright/test'; + +test('row data property mutation updates grid cell', async ({ page }) => { + test.setTimeout(5_000); + + await page.goto('/ag-14654'); + + // Wait for grid to render + await expect(page.getByRole('gridcell')).toHaveCount(6); + + // Capture the initial price value of the first row + const priceCell = page.getByRole('gridcell').nth(1); + const initialPrice = await priceCell.textContent(); + + // Wait for the interval to fire and update the price + await page.waitForTimeout(500); + + // The price should have changed from the initial value + const updatedPrice = await priceCell.textContent(); + expect(updatedPrice).not.toBe(initialPrice); +});