From 9eb6566bd9a0959aa9f0baaafd7d377179b4e081 Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Fri, 28 Aug 2026 00:32:54 +0200 Subject: [PATCH 1/6] FE-1533: Add a petrinaut-shipping skill to the repo skills --- .agents/skills/petrinaut-shipping/SKILL.md | 65 ++++++++++++++++++++++ .claude/skills/petrinaut-shipping | 1 + .claude/skills/skill-rules.json | 25 +++++++++ 3 files changed, 91 insertions(+) create mode 100644 .agents/skills/petrinaut-shipping/SKILL.md create mode 120000 .claude/skills/petrinaut-shipping diff --git a/.agents/skills/petrinaut-shipping/SKILL.md b/.agents/skills/petrinaut-shipping/SKILL.md new file mode 100644 index 00000000000..06fc740afbe --- /dev/null +++ b/.agents/skills/petrinaut-shipping/SKILL.md @@ -0,0 +1,65 @@ +--- +name: petrinaut-shipping +description: "End-to-end procedure for shipping a change to the Petrinaut packages (libs/@hashintel/petrinaut, petrinaut-core, petrinaut-cli, apps/petrinaut-opt, apps/petrinaut-website): the verification gates per package, the changeset step, docs sync, pre-PR hygiene checks, and CI expectations from draft to ready. Use when implementing any change in these packages, when creating a PR for Petrinaut work, when running tests or lints for them, or when checking CI on a Petrinaut PR." +license: Apache-2.0 +metadata: + triggers: + type: domain + enforcement: suggest + priority: high + keywords: + - petrinaut + - changeset + - SDCPN + - petri net + intent-patterns: + - "\\b(implement|create|fix|add|ship|change)\\b.*?\\bpetrinaut\\b" + - "\\bpetrinaut\\b.*?\\b(PR|tests?|lint|CI|checks?)\\b" + files: + include: + - "libs/@hashintel/petrinaut*/**" + - "apps/petrinaut-opt/**" + - "apps/petrinaut-website/**" +--- + +# Shipping a Petrinaut change + +Standing conventions (changeset policy, docs and diagram placement, CI quirks, the petrinaut-opt boundary) live in `libs/@hashintel/petrinaut/AGENTS.md` and `apps/petrinaut-opt/AGENTS.md`. This skill is the procedure that applies them. + +## Gates + +Run for every touched package, after every increment: + +```sh +yarn fix:format >/dev/null 2>&1 +npx turbo run test:unit lint:tsc lint:eslint --filter @hashintel/petrinaut --filter @hashintel/petrinaut-core --filter @hashintel/petrinaut-cli --force --output-logs errors-only +yarn lint:format +``` + +- Trim the `--filter`s to the touched packages; add `--filter @apps/petrinaut-website` when it consumes the change. +- Structure changed (new folder, moved module): also `yarn workspace @local/petrinaut-arch-docs lint:arch-docs`, and add the layer declaration the AGENTS.md architecture section calls for. +- Arch-docs authored content or `content/diagrams/*.d2` changed: `lint:arch-docs` does not compile MDX or render D2. Run the site build once before pushing: `mise x -- yarn exec turbo run build --filter @apps/petrinaut-docs`. D2 labels containing `:` or `[` must be quoted. +- Python (`apps/petrinaut-opt`, `libs/@local/petrinaut-python`): `uv run pytest` in the package. +- Formatting is oxfmt via the yarn scripts; never run prettier directly. `yarn lint:format` prints its verdict before its final line, so check the exit code rather than the last line of output. + +## Changesets + +One `patch` changeset per PR covering the published packages the PR touches (`@hashintel/petrinaut`, `@hashintel/petrinaut-core`); none for pure refactors. Keep the text to one or two plain sentences. See the AGENTS.md conventions for the full policy. + +## Docs sync + +User-visible behaviour changes update the user guide in the same PR; new pages need registration and a raw import, both test-enforced. The steps are in the "User-facing docs" section of `libs/@hashintel/petrinaut/AGENTS.md`. Doc screenshots cannot be uploaded by an agent: produce candidate captures, list the exact pages and sections to re-capture, and flag "screenshots pending" in the PR body and the summary. + +## Pre-PR hygiene + +- Read `git diff --stat` against the base: no accidental directories, no unstaged leftovers, no generated output, no `mise.lock` churn. +- A `Bin` line in the stat for a text file means escape sequences became literal control bytes; fix it before pushing or the diff is unreviewable. +- A diff too large for one review gets split into stacked PRs, one concern per layer. + +## Draft, CI, ready + +- Open the PR as a draft, body per the repo PR template. +- Watch checks until none are pending: `until [ "$(gh pr checks NNNN 2>/dev/null | grep -c pending)" = "0" ]; do sleep 60; done; gh pr checks NNNN`. +- Judge failures against the CI bullet in the AGENTS.md conventions (Bench-CI non-blocking, the known flaky check, Vercel-side docs failures) before treating them as caused by the diff. +- Flip to ready only when checks are green. AI reviewers run at that point; triage their threads rather than leaving them unresolved. +- End any turn that changed the branch by stating what was committed and pushed, or that nothing was. diff --git a/.claude/skills/petrinaut-shipping b/.claude/skills/petrinaut-shipping new file mode 120000 index 00000000000..a58b8ebc99d --- /dev/null +++ b/.claude/skills/petrinaut-shipping @@ -0,0 +1 @@ +../../.agents/skills/petrinaut-shipping \ No newline at end of file diff --git a/.claude/skills/skill-rules.json b/.claude/skills/skill-rules.json index a0f1e1f71ab..1a927d1bf99 100644 --- a/.claude/skills/skill-rules.json +++ b/.claude/skills/skill-rules.json @@ -193,6 +193,31 @@ "blockMessage": "Skill is required to proceed", "skipConditions": {} }, + "petrinaut-shipping": { + "type": "domain", + "enforcement": "suggest", + "priority": "high", + "description": "End-to-end procedure for shipping a change to the Petrinaut packages (libs/@hashintel/petrinaut, petrinaut-core, petrinaut-cli, apps/petrinaut-opt, apps/petrinaut-website): the verification gates per package, the changeset step, docs sync, pre-PR hygiene checks, and CI expectations from draft to ready. Use when implementing any change in these packages, when creating a PR for Petrinaut work, when running tests or lints for them, or when checking CI on a Petrinaut PR.", + "promptTriggers": { + "keywords": ["petrinaut", "changeset", "SDCPN", "petri net"], + "intentPatterns": [ + "\\b(implement|create|fix|add|ship|change)\\b.*?\\bpetrinaut\\b", + "\\bpetrinaut\\b.*?\\b(PR|tests?|lint|CI|checks?)\\b" + ] + }, + "fileTriggers": { + "include": [ + "libs/@hashintel/petrinaut*/**", + "apps/petrinaut-opt/**", + "apps/petrinaut-website/**" + ], + "exclude": [], + "content": [], + "create-only": false + }, + "blockMessage": "Skill is required to proceed", + "skipConditions": {} + }, "rust-coding-style": { "type": "domain", "enforcement": "suggest", From aa5c1b0d488766a4ded3ffba66adad8a98c9af0c Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Mon, 31 Aug 2026 16:56:45 +0200 Subject: [PATCH 2/6] FE-1533: Narrow the skill's activation triggers --- .agents/skills/petrinaut-shipping/SKILL.md | 8 -------- .claude/skills/skill-rules.json | 8 ++------ 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/.agents/skills/petrinaut-shipping/SKILL.md b/.agents/skills/petrinaut-shipping/SKILL.md index 06fc740afbe..f84f54118e4 100644 --- a/.agents/skills/petrinaut-shipping/SKILL.md +++ b/.agents/skills/petrinaut-shipping/SKILL.md @@ -9,17 +9,9 @@ metadata: priority: high keywords: - petrinaut - - changeset - - SDCPN - - petri net intent-patterns: - "\\b(implement|create|fix|add|ship|change)\\b.*?\\bpetrinaut\\b" - "\\bpetrinaut\\b.*?\\b(PR|tests?|lint|CI|checks?)\\b" - files: - include: - - "libs/@hashintel/petrinaut*/**" - - "apps/petrinaut-opt/**" - - "apps/petrinaut-website/**" --- # Shipping a Petrinaut change diff --git a/.claude/skills/skill-rules.json b/.claude/skills/skill-rules.json index 1a927d1bf99..7b5114a9578 100644 --- a/.claude/skills/skill-rules.json +++ b/.claude/skills/skill-rules.json @@ -199,18 +199,14 @@ "priority": "high", "description": "End-to-end procedure for shipping a change to the Petrinaut packages (libs/@hashintel/petrinaut, petrinaut-core, petrinaut-cli, apps/petrinaut-opt, apps/petrinaut-website): the verification gates per package, the changeset step, docs sync, pre-PR hygiene checks, and CI expectations from draft to ready. Use when implementing any change in these packages, when creating a PR for Petrinaut work, when running tests or lints for them, or when checking CI on a Petrinaut PR.", "promptTriggers": { - "keywords": ["petrinaut", "changeset", "SDCPN", "petri net"], + "keywords": ["petrinaut"], "intentPatterns": [ "\\b(implement|create|fix|add|ship|change)\\b.*?\\bpetrinaut\\b", "\\bpetrinaut\\b.*?\\b(PR|tests?|lint|CI|checks?)\\b" ] }, "fileTriggers": { - "include": [ - "libs/@hashintel/petrinaut*/**", - "apps/petrinaut-opt/**", - "apps/petrinaut-website/**" - ], + "include": [], "exclude": [], "content": [], "create-only": false From 3c38e14819e276e12c251d44d12d2de663f2ff35 Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Mon, 31 Aug 2026 17:19:01 +0200 Subject: [PATCH 3/6] FE-1533: Make the CI wait loop survive a PR with no checks yet --- .agents/skills/petrinaut-shipping/SKILL.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.agents/skills/petrinaut-shipping/SKILL.md b/.agents/skills/petrinaut-shipping/SKILL.md index f84f54118e4..080b0cdd6ee 100644 --- a/.agents/skills/petrinaut-shipping/SKILL.md +++ b/.agents/skills/petrinaut-shipping/SKILL.md @@ -51,7 +51,13 @@ User-visible behaviour changes update the user guide in the same PR; new pages n ## Draft, CI, ready - Open the PR as a draft, body per the repo PR template. -- Watch checks until none are pending: `until [ "$(gh pr checks NNNN 2>/dev/null | grep -c pending)" = "0" ]; do sleep 60; done; gh pr checks NNNN`. +- Watch checks until none are pending. Requiring the command to succeed matters: on a freshly opened PR it fails until GitHub schedules the checks, and treating that as "nothing pending" ends the wait before CI starts. + + ```sh + until gh pr checks NNNN >/dev/null 2>&1 && [ "$(gh pr checks NNNN | grep -c pending)" = "0" ]; do sleep 60; done + gh pr checks NNNN + ``` + - Judge failures against the CI bullet in the AGENTS.md conventions (Bench-CI non-blocking, the known flaky check, Vercel-side docs failures) before treating them as caused by the diff. - Flip to ready only when checks are green. AI reviewers run at that point; triage their threads rather than leaving them unresolved. - End any turn that changed the branch by stating what was committed and pushed, or that nothing was. From c31749f8fd0963222ea7f854a1f0b0395109ab5f Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Mon, 31 Aug 2026 18:23:13 +0200 Subject: [PATCH 4/6] FE-1533: Read check state from JSON so a failed run ends the wait --- .agents/skills/petrinaut-shipping/SKILL.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.agents/skills/petrinaut-shipping/SKILL.md b/.agents/skills/petrinaut-shipping/SKILL.md index 080b0cdd6ee..253c454ee1a 100644 --- a/.agents/skills/petrinaut-shipping/SKILL.md +++ b/.agents/skills/petrinaut-shipping/SKILL.md @@ -51,13 +51,19 @@ User-visible behaviour changes update the user guide in the same PR; new pages n ## Draft, CI, ready - Open the PR as a draft, body per the repo PR template. -- Watch checks until none are pending. Requiring the command to succeed matters: on a freshly opened PR it fails until GitHub schedules the checks, and treating that as "nothing pending" ends the wait before CI starts. +- Watch checks until none are pending, reading the JSON rather than the exit status. `gh pr checks` exits non-zero for three different situations: checks pending, checks failed, and no checks scheduled yet. Gating the loop on that status either ends the wait before CI starts or never ends it once a check fails. ```sh - until gh pr checks NNNN >/dev/null 2>&1 && [ "$(gh pr checks NNNN | grep -c pending)" = "0" ]; do sleep 60; done + while :; do + pending=$(gh pr checks NNNN --json bucket --jq '[.[] | select(.bucket == "pending")] | length' 2>/dev/null) + [ -n "$pending" ] && [ "$pending" -eq 0 ] && break + sleep 60 + done gh pr checks NNNN ``` + An empty result means the checks do not exist yet and the loop keeps waiting. A PR that never settles has not started CI at all. + - Judge failures against the CI bullet in the AGENTS.md conventions (Bench-CI non-blocking, the known flaky check, Vercel-side docs failures) before treating them as caused by the diff. - Flip to ready only when checks are green. AI reviewers run at that point; triage their threads rather than leaving them unresolved. - End any turn that changed the branch by stating what was committed and pushed, or that nothing was. From 3f2565383e34d776593a7ccf645422d2630a4b70 Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Thu, 3 Sep 2026 20:45:23 +0200 Subject: [PATCH 5/6] FE-1533: Require checks to exist before the wait loop ends --- .agents/skills/petrinaut-shipping/SKILL.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.agents/skills/petrinaut-shipping/SKILL.md b/.agents/skills/petrinaut-shipping/SKILL.md index 253c454ee1a..b9d929383f1 100644 --- a/.agents/skills/petrinaut-shipping/SKILL.md +++ b/.agents/skills/petrinaut-shipping/SKILL.md @@ -51,18 +51,15 @@ User-visible behaviour changes update the user guide in the same PR; new pages n ## Draft, CI, ready - Open the PR as a draft, body per the repo PR template. -- Watch checks until none are pending, reading the JSON rather than the exit status. `gh pr checks` exits non-zero for three different situations: checks pending, checks failed, and no checks scheduled yet. Gating the loop on that status either ends the wait before CI starts or never ends it once a check fails. +- Watch checks until the run settles, and decide that in `jq`. Neither the exit status nor a pending count answers it alone: `gh pr checks` exits non-zero when checks are pending, when they failed, and when none are scheduled yet, and with `--json` it prints `[]` before any exist, so a pending count of zero also reads as "finished" on a PR that has not started. ```sh - while :; do - pending=$(gh pr checks NNNN --json bucket --jq '[.[] | select(.bucket == "pending")] | length' 2>/dev/null) - [ -n "$pending" ] && [ "$pending" -eq 0 ] && break - sleep 60 - done + settled='[length, ([.[] | select(.bucket == "pending")] | length)] | .[0] > 0 and .[1] == 0' + until [ "$(gh pr checks NNNN --json bucket --jq "$settled" 2>/dev/null)" = "true" ]; do sleep 60; done gh pr checks NNNN ``` - An empty result means the checks do not exist yet and the loop keeps waiting. A PR that never settles has not started CI at all. + The wait ends once checks exist and none are pending, failures included, so the table below reports them. Anything else, an empty list or a failed call, keeps waiting. - Judge failures against the CI bullet in the AGENTS.md conventions (Bench-CI non-blocking, the known flaky check, Vercel-side docs failures) before treating them as caused by the diff. - Flip to ready only when checks are green. AI reviewers run at that point; triage their threads rather than leaving them unresolved. From 4195c11829c48be8394720c48ac48b756fa4a2c8 Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Thu, 3 Sep 2026 20:52:58 +0200 Subject: [PATCH 6/6] FE-1533: Cover the activation boundary and correct the gate commands --- .agents/skills/petrinaut-shipping/SKILL.md | 12 ++++++++---- .claude/hooks/skill-activation-prompt.test.ts | 12 ++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/.agents/skills/petrinaut-shipping/SKILL.md b/.agents/skills/petrinaut-shipping/SKILL.md index b9d929383f1..c883634ad09 100644 --- a/.agents/skills/petrinaut-shipping/SKILL.md +++ b/.agents/skills/petrinaut-shipping/SKILL.md @@ -24,14 +24,15 @@ Run for every touched package, after every increment: ```sh yarn fix:format >/dev/null 2>&1 -npx turbo run test:unit lint:tsc lint:eslint --filter @hashintel/petrinaut --filter @hashintel/petrinaut-core --filter @hashintel/petrinaut-cli --force --output-logs errors-only +npx turbo run build test:unit lint:tsc lint:eslint --filter @hashintel/petrinaut --filter @hashintel/petrinaut-core --filter @hashintel/petrinaut-cli --force --output-logs errors-only yarn lint:format ``` - Trim the `--filter`s to the touched packages; add `--filter @apps/petrinaut-website` when it consumes the change. +- `build` belongs in that list. `test:unit` depends on `^build`, which builds dependencies and not the selected package, so the React Compiler check, the browser-entry check, and the website's Vite build are otherwise never run. - Structure changed (new folder, moved module): also `yarn workspace @local/petrinaut-arch-docs lint:arch-docs`, and add the layer declaration the AGENTS.md architecture section calls for. - Arch-docs authored content or `content/diagrams/*.d2` changed: `lint:arch-docs` does not compile MDX or render D2. Run the site build once before pushing: `mise x -- yarn exec turbo run build --filter @apps/petrinaut-docs`. D2 labels containing `:` or `[` must be quoted. -- Python (`apps/petrinaut-opt`, `libs/@local/petrinaut-python`): `uv run pytest` in the package. +- Python packages go through Turborepo: `npx turbo run test:unit lint:ruff lint:types --filter @apps/petrinaut-opt --filter @local/petrinaut-python --force`. A plain `uv run pytest` skips the end-to-end tests when the CLI bundle is missing, and skips the comparison of regenerated `openapi.json` that `@apps/petrinaut-opt` runs as part of its own `test:unit`. - Formatting is oxfmt via the yarn scripts; never run prettier directly. `yarn lint:format` prints its verdict before its final line, so check the exit code rather than the last line of output. ## Changesets @@ -55,11 +56,14 @@ User-visible behaviour changes update the user guide in the same PR; new pages n ```sh settled='[length, ([.[] | select(.bucket == "pending")] | length)] | .[0] > 0 and .[1] == 0' - until [ "$(gh pr checks NNNN --json bucket --jq "$settled" 2>/dev/null)" = "true" ]; do sleep 60; done + for _ in $(seq 60); do + [ "$(gh pr checks NNNN --json bucket --jq "$settled" 2>/dev/null)" = "true" ] && break + sleep 60 + done gh pr checks NNNN ``` - The wait ends once checks exist and none are pending, failures included, so the table below reports them. Anything else, an empty list or a failed call, keeps waiting. + The wait ends once checks exist and none are pending, failures included, so the table below reports them. Anything else, an empty list or a failed call, keeps waiting. The loop is bounded and the last call keeps its output, so an expired token or an unreachable API surfaces as an error rather than sleeping for ever. - Judge failures against the CI bullet in the AGENTS.md conventions (Bench-CI non-blocking, the known flaky check, Vercel-side docs failures) before treating them as caused by the diff. - Flip to ready only when checks are green. AI reviewers run at that point; triage their threads rather than leaving them unresolved. diff --git a/.claude/hooks/skill-activation-prompt.test.ts b/.claude/hooks/skill-activation-prompt.test.ts index 36e0f2d22fc..259a1da1d68 100644 --- a/.claude/hooks/skill-activation-prompt.test.ts +++ b/.claude/hooks/skill-activation-prompt.test.ts @@ -68,5 +68,17 @@ describe("Skill Activation Snapshots", () => { ); }); + it("activates Petrinaut shipping guidance for Petrinaut work", () => { + expect( + testSkillActivation("fix the petrinaut simulation engine"), + ).toContain("petrinaut-shipping"); + }); + + it("does not activate Petrinaut shipping guidance for a changeset elsewhere", () => { + expect( + testSkillActivation("add a changeset for the ds-components fix"), + ).not.toContain("petrinaut-shipping"); + }); + it("what is the weather today", ({ task }) => createSnapshot(task.name)); });