@cratis/pi 7.2.0 → 7.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -34,8 +34,10 @@ managed corpus.
34
34
  The packaged `cratis-path-guidance` extension attaches a path-scoped rule to the
35
35
  tool result the first time a matching file is touched, and after a successful
36
36
  `write` or `edit` adds one advisory line naming every skill whose `SKILL.md`
37
- `cratis-hint-paths` frontmatter matches the file. (The key is Cratis-specific
38
- because Claude Code gives a plain `paths` key its own meaning.) A skill is hinted at
37
+ `metadata.cratis-hint-paths` frontmatter (one string of whitespace-separated globs)
38
+ matches the file. (It sits under `metadata` because Agent Skills allows only a fixed set
39
+ of top-level keys, and the key is Cratis-specific because Claude Code gives a plain
40
+ `paths` key its own meaning.) A skill is hinted at
39
41
  most once per session and not when it is already in context: read (a shell command
40
42
  counts only when `cat`, `sed`, `head`, `tail`, `less`, `bat`, `rg` or `grep` opens
41
43
  its `SKILL.md` or a file under `references/`), preloaded by pi-subagents, or
@@ -128,7 +128,7 @@ For implementation, the applicable changed-lane gates must pass. Mark unrelated
128
128
  - [ ] `Documentation/verify-markdown.sh` passes when documentation is added or changed
129
129
  - [ ] `code-reviewer` finds no blocking issues
130
130
  - [ ] `security-reviewer` finds no vulnerabilities
131
- - [ ] PR description follows the pull request template
131
+ - [ ] PR description follows the pull request template and the release-note contract in `pull-requests.md`; test and review notes are in a PR comment
132
132
 
133
133
  ---
134
134
 
@@ -152,7 +152,7 @@ For implementation, the applicable changed-lane gates must pass. Mark unrelated
152
152
  - [ ] `code-reviewer` finds no blocking issues
153
153
  - [ ] `security-reviewer` finds no vulnerabilities
154
154
  - [ ] All documentation is complete and accurate (if required)
155
- - [ ] PR description follows the pull request template
155
+ - [ ] PR description follows the pull request template and the release-note contract in `pull-requests.md`; test and review notes are in a PR comment
156
156
 
157
157
  ---
158
158
 
@@ -115,7 +115,7 @@ For an implemented application slice, require the applicable changed-lane gates
115
115
  - [ ] `Documentation/verify-markdown.sh` passes when documentation is added or changed
116
116
  - [ ] Code review by `code-reviewer` finds no blocking issues
117
117
  - [ ] Security review by `security-reviewer` finds no vulnerabilities
118
- - [ ] PR description follows the pull request template
118
+ - [ ] PR description follows the pull request template and the release-note contract in `pull-requests.md`; test and review notes are in a PR comment
119
119
 
120
120
  ---
121
121
 
@@ -8,6 +8,6 @@ export interface SkillTrigger {
8
8
  filePath: string;
9
9
  /** Directory holding `SKILL.md` and the skill's references. */
10
10
  baseDir: string;
11
- /** The `cratis-hint-paths` frontmatter of the skill. Empty when the skill declares no trigger. */
11
+ /** The `metadata.cratis-hint-paths` frontmatter of the skill. Empty when the skill declares no trigger. */
12
12
  globs: string[];
13
13
  }
@@ -66,7 +66,7 @@ function expandedSkillNames(prompt: unknown): string[] {
66
66
  *
67
67
  * Path-scoped rules are attached to the tool result the first time a matching file is touched in the session,
68
68
  * exactly as they were when `cratis-rules` delivered them. On a successful `write` or `edit`, one advisory line
69
- * names every skill whose `cratis-hint-paths` frontmatter matches the file and where its `SKILL.md` is. A skill
69
+ * names every skill whose `metadata.cratis-hint-paths` frontmatter matches the file and where its `SKILL.md` is. A skill
70
70
  * is not hinted when it is already in context: read in the session, preloaded by pi-subagents
71
71
  * (`# Preloaded Skill: <name>` followed by its text in the system prompt), or expanded by `/skill:<name>`. Hints are advisory only:
72
72
  * nothing is blocked and the system prompt is never touched.
@@ -4,9 +4,8 @@
4
4
  import { existsSync, readFileSync, readdirSync } from 'node:fs';
5
5
  import { dirname, join } from 'node:path';
6
6
  import { corpusRoot } from '../shared/corpusRoot.ts';
7
- import { frontmatter } from '../shared/frontmatter.ts';
8
7
  import { globToRegExp } from '../shared/globs.ts';
9
- import { skillTriggerKey } from '../shared/skillFrontmatter.ts';
8
+ import { skillTriggerGlobs } from '../shared/skillFrontmatter.ts';
10
9
  import { selectedSkillNames } from '../shared/skillSelection.ts';
11
10
  import type { LoadedSkill } from './LoadedSkill.ts';
12
11
  import type { SkillMatch } from './SkillMatch.ts';
@@ -35,7 +34,7 @@ function repositorySkills(cwd: string): LoadedSkill[] {
35
34
 
36
35
  function triggerGlobs(filePath: string): string[] {
37
36
  try {
38
- return frontmatter(readFileSync(filePath, 'utf8')).get(skillTriggerKey) ?? [];
37
+ return skillTriggerGlobs(readFileSync(filePath, 'utf8'));
39
38
  } catch {
40
39
  return [];
41
40
  }
@@ -48,7 +47,7 @@ function triggerGlobs(filePath: string): string[] {
48
47
  * it can be empty (a pi-subagents agent with `skills: false`, the usual setup for cheap workers), or non-empty
49
48
  * with only personal skills (an agent with `skills: true` whose `extensions:` allowlist leaves `@cratis/pi`, and
50
49
  * so its skill paths, out). The selected skills' `SKILL.md` can still be read by path, which is what the hint asks
51
- * for; a corpus skill the repository did not select is never added. Skills without a `cratis-hint-paths` trigger
50
+ * for; a corpus skill the repository did not select is never added. Skills without a `metadata.cratis-hint-paths` trigger
52
51
  * are left out.
53
52
  */
54
53
  export function skillTriggers(loaded: LoadedSkill[] | undefined, cwd: string): SkillTrigger[] {
@@ -5,17 +5,23 @@ export function unquote(value: string): string {
5
5
  return value.trim().replace(/^["']|["']$/g, '');
6
6
  }
7
7
 
8
+ /** The text between the opening and closing `---` markers, or `undefined` when there is no terminated frontmatter. */
9
+ export function frontmatterText(content: string): string | undefined {
10
+ if (!content.startsWith('---\n')) return undefined;
11
+ const end = content.indexOf('\n---\n', 4);
12
+ return end < 0 ? undefined : content.slice(4, end);
13
+ }
14
+
8
15
  /**
9
16
  * Reads the YAML-ish frontmatter the corpus uses: scalar `key: value` lines and `key:` followed by
10
17
  * ` - item` lines. Anything richer is not used by the rules and skills and is deliberately not supported.
11
18
  */
12
19
  export function frontmatter(content: string): Map<string, string[]> {
13
20
  const fields = new Map<string, string[]>();
14
- if (!content.startsWith('---\n')) return fields;
15
- const end = content.indexOf('\n---\n', 4);
16
- if (end < 0) return fields;
21
+ const text = frontmatterText(content);
22
+ if (text === undefined) return fields;
17
23
  let current: string | undefined;
18
- for (const line of content.slice(4, end).split('\n')) {
24
+ for (const line of text.split('\n')) {
19
25
  const item = /^\s+-\s+(.*)$/.exec(line);
20
26
  if (item && current) {
21
27
  fields.get(current)!.push(unquote(item[1]));
@@ -29,3 +35,93 @@ export function frontmatter(content: string): Map<string, string[]> {
29
35
  }
30
36
  return fields;
31
37
  }
38
+
39
+ /** One `key: value` line under a top-level block map such as `metadata`, as written, not interpreted. */
40
+ export interface FrontmatterEntry {
41
+ /** The number of spaces the key is indented by, or -1 when the indentation contains a tab. */
42
+ indent: number;
43
+ /** The text after the colon, trimmed and otherwise untouched: quotes, a trailing comment and block scalar indicators included. */
44
+ raw: string;
45
+ /** True when more-indented lines follow the entry, so its value is a block scalar, a multi-line scalar or a nested map. */
46
+ continues: boolean;
47
+ }
48
+
49
+ /** The result of reading the block map under a top-level key. */
50
+ export interface FrontmatterBlock {
51
+ /** The text after `name:` on the key's own line, trimmed. Non-empty for an inline value such as a flow map. */
52
+ inline: string;
53
+ /** The entries the block holds, in order. A more-indented line belongs to the entry above it and is not an entry itself. */
54
+ entries: Map<string, FrontmatterEntry>;
55
+ }
56
+
57
+ /**
58
+ * Reads the block under a top-level `name:` key without interpreting the values. Blank and comment lines are skipped.
59
+ * Returns `undefined` when the key is absent or the frontmatter is unterminated.
60
+ */
61
+ export function frontmatterBlock(content: string, name: string): FrontmatterBlock | undefined {
62
+ const text = frontmatterText(content);
63
+ if (text === undefined) return undefined;
64
+ let block: FrontmatterBlock | undefined;
65
+ let inside = false;
66
+ let last: FrontmatterEntry | undefined;
67
+ let skipAbove: number | undefined;
68
+ for (const line of text.split('\n')) {
69
+ // Any line that starts in column 0 with something other than a comment ends the current block, whatever its
70
+ // spelling (a quoted key, a space before the colon). Only a real `name:` key starts this block.
71
+ if (/^[^\s#]/.test(line)) {
72
+ const top = /^([A-Za-z][\w-]*):(.*)$/.exec(line);
73
+ inside = top?.[1] === name;
74
+ last = undefined;
75
+ skipAbove = undefined;
76
+ // A comment after the key (`metadata: # note`) is not a value, so the block below it is still the block form.
77
+ if (inside) block ??= { inline: /^\s+#/.test(top![2]) ? '' : top![2].trim(), entries: new Map() };
78
+ continue;
79
+ }
80
+ if (!inside || !block || line.trim() === '' || /^\s*#/.test(line)) continue;
81
+ const indent = /^ *\t/.test(line) ? -1 : /^( *)/.exec(line)![1].length;
82
+ // Lines under a sibling key the entry pattern does not read (a quoted key, `? complex`) are not entries.
83
+ if (skipAbove !== undefined && indent > skipAbove) continue;
84
+ skipAbove = undefined;
85
+ const entry = /^\s+([A-Za-z][\w-]*):(.*)$/.exec(line);
86
+ if (entry && (!last || indent <= last.indent)) {
87
+ last = { indent, raw: entry[2].trim(), continues: false };
88
+ block.entries.set(entry[1], last);
89
+ } else if (last && (indent < 0 || indent > last.indent)) {
90
+ last.continues = true;
91
+ } else {
92
+ // At the entry's indentation or less, so a sibling key ends the entry even when its spelling is not read here.
93
+ last = undefined;
94
+ skipAbove = indent;
95
+ }
96
+ }
97
+ return block;
98
+ }
99
+
100
+ /**
101
+ * The string a raw scalar spells when it is written as one complete double-quoted or single-quoted string on one
102
+ * line, with nothing after the closing quote. Anything else, including a string with an escape or an embedded
103
+ * quote, a trailing `# comment`, an unquoted value and a block scalar, yields `undefined`: it is not guessed at.
104
+ */
105
+ export function quotedScalar(raw: string): string | undefined {
106
+ return /^"([^"\\]*)"$/.exec(raw)?.[1] ?? /^'([^']*)'$/.exec(raw)?.[1];
107
+ }
108
+
109
+ /**
110
+ * Reads the nested string map under a top-level `name:` key, as Agent Skills `metadata` uses. Only the one form the
111
+ * corpus supports is read: an entry indented two spaces whose value is a complete one-line quoted string (see
112
+ * `quotedScalar`). Values stay whole strings, without comma splitting. An entry in any other form is left out of the
113
+ * map, so a caller never sees a guess such as `>-`; `frontmatterBlock` gives the raw entries for reporting them.
114
+ * Returns `undefined` when the key is absent or the frontmatter is unterminated.
115
+ */
116
+ export function frontmatterMap(content: string, name: string): Map<string, string> | undefined {
117
+ const block = frontmatterBlock(content, name);
118
+ if (!block) return undefined;
119
+ const map = new Map<string, string>();
120
+ // A value on the key's own line (a flow map, even one that continues below) is not the supported block form.
121
+ if (block.inline !== '') return map;
122
+ for (const [key, entry] of block.entries) {
123
+ const value = entry.indent === 2 && !entry.continues ? quotedScalar(entry.raw) : undefined;
124
+ if (value !== undefined) map.set(key, value);
125
+ }
126
+ return map;
127
+ }
@@ -1,9 +1,31 @@
1
1
  // Copyright (c) Cratis. All rights reserved.
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
- /**
5
- * The `SKILL.md` frontmatter key that lists the globs a skill is hinted for. It is deliberately Cratis-specific:
6
- * Claude Code gives a plain `paths` key in `SKILL.md` a meaning of its own (a "conditional skill" that stays out
7
- * of the skill list until a matching file is touched), and `.claude/skills` exposes this corpus to it.
8
- */
4
+ import { frontmatterMap } from './frontmatter.ts';
5
+
6
+ // The key, under the `metadata` map of `SKILL.md` frontmatter, whose value lists the globs a skill is hinted for.
7
+ // Agent Skills allows only `name`, `description`, `license`, `compatibility`, `metadata` and `allowed-tools` at the
8
+ // top level, and `metadata` maps strings to strings, so the value is one string of whitespace-separated globs:
9
+ //
10
+ // metadata:
11
+ // cratis-hint-paths: "**/for_*/**/*.cs **/Documentation/**/*.{md,mdx}"
12
+ //
13
+ // It is deliberately Cratis-specific: Claude Code gives a plain `paths` key in `SKILL.md` a meaning of its own (a
14
+ // "conditional skill" that stays out of the skill list until a matching file is touched), and `.claude/skills`
15
+ // exposes this corpus to it.
9
16
  export const skillTriggerKey = 'cratis-hint-paths';
17
+
18
+ /** The raw `metadata.cratis-hint-paths` string of a `SKILL.md`, or `undefined` when the skill declares none. */
19
+ export function skillTriggerValue(content: string): string | undefined {
20
+ return frontmatterMap(content, 'metadata')?.get(skillTriggerKey);
21
+ }
22
+
23
+ /** Splits a trigger value into its globs. Globs contain no whitespace, and brace sets keep their commas. */
24
+ export function splitSkillTriggerGlobs(value: string): string[] {
25
+ return value.split(/\s+/).filter(Boolean);
26
+ }
27
+
28
+ /** The trigger globs a `SKILL.md` declares under `metadata.cratis-hint-paths`; empty when it declares none. */
29
+ export function skillTriggerGlobs(content: string): string[] {
30
+ return splitSkillTriggerGlobs(skillTriggerValue(content) ?? '');
31
+ }
@@ -2,20 +2,20 @@
2
2
  agent: agent
3
3
  description: >
4
4
  Ship local changes: create a branch, make logical commits, push, open and
5
- label a PR with a proper description, merge it, prepare no-effect related
6
- issue dispositions, and delete the branch.
5
+ label a PR with a proper description, merge it, prepare related issue
6
+ dispositions the request does not authorize, and delete the branch.
7
7
  ---
8
8
 
9
9
  # Ship Changes
10
10
 
11
11
  Ship the current local modifications to `main` through the standard
12
- branch → commits → PR → merge → no-effect issue disposition → cleanup workflow.
12
+ branch → commits → PR → merge → issue disposition → cleanup workflow.
13
13
 
14
14
  ## Inputs
15
15
 
16
16
  - **What changed** — brief description of the work (used for branch name and PR title)
17
17
  - **Label** — `no-release`, `patch`, `minor`, or `major`, or omit entirely if no label should be applied
18
- - **Related issue** — optional exact repository and issue number; if unknown, search read-only first. Comment on or close it only when the user's request includes that effect.
18
+ - **Related issue** — optional exact repository and issue number; if unknown, search read-only first. Comment on or close it only when the user's request includes that effect; otherwise prepare the disposition as a proposal. In repositories released by `cratis/release-action`, a delivered issue is closed by release-action through `(#n)` in the description, but release-action does not run for a `no-release` PR, so `(#n)` closes nothing there; elsewhere follow the repository's own release and issue-closing process.
19
19
 
20
20
  Invoking this prompt is direct authority for the standard branch, commit, push, pull-request,
21
21
  requested-label, merge, and branch-cleanup effects. Do not pause to ask for separate approval at
@@ -32,3 +32,18 @@ Every other label proceeds through merge under this prompt's ordinary authority.
32
32
  This prompt is for an explicit request to ship or land. If the user asked only to commit, only to
33
33
  push, or only to open a PR, stop after that step: the narrower request does not become authority
34
34
  for the rest of the chain because this prompt happens to be loaded.
35
+
36
+ ## Pull request description
37
+
38
+ The description is published verbatim as the release notes, so it follows the contract in
39
+ [`pull-requests.md`](../rules/pull-requests.md#the-description-is-the-release-note).
40
+
41
+ - Before `gh pr create` or `gh pr edit`, write the body from `.github/pull_request_template.md` and
42
+ check it against the contract. The rules agents most often break:
43
+ - No development write-up: no Overview/Verification/Test plan headings (only a first `## Summary`
44
+ is allowed), no review, testing or provenance notes. Those go in a PR comment.
45
+ - `(#n)` ends the bullet that delivers an issue and `(part of #n)` marks anything that stays open;
46
+ never `Closes`/`Fixes`/`Refs` before a number, and never an issue reference inside an HTML comment,
47
+ which still closes the issue.
48
+ - Links are absolute `https://` URLs, never relative paths.
49
+ - After pushing, if `verify-release-notes` fails, fix it by editing the description; it re-runs on edit.
@@ -4,19 +4,81 @@ applyTo: "**/*"
4
4
 
5
5
  # How to Do Pull Requests
6
6
 
7
- PR descriptions serve two purposes: they help reviewers understand the change *now*, and they become the release notes that users read *later*. Write them with both audiences in mind.
7
+ ## The description is the release note
8
8
 
9
- **The description is the release note — it is published verbatim.** Write it as the note you want the person upgrading to read, in the repository template's sections. A generic development write-up (`## Summary`, `## Verification`, `## Testing`, a list of the files you touched, a description of how you arrived at the change) is not a release note, and shipping one makes the release history unreadable. The same applies wherever a release is produced by hand: release-notes text typed into a manual workflow run, or written straight into a published release, carries exactly the same shape and the same audience as a PR description. There is no path to a release whose notes are allowed to describe the work instead of the change.
9
+ **The PR description is published verbatim as the GitHub release. In repositories released by `cratis/release-action`, it also closes every issue written as `(#n)`; elsewhere, follow the repository's own release and issue-closing process.** Write the note the person upgrading should read, not a development write-up. The same contract applies to release notes typed into a manual workflow run or written straight into a release. Where it is installed, the `verify-release-notes` check enforces it on every PR into the default branch that carries exactly one of `major`/`minor`/`patch` (it re-runs when the label is added); it skips `no-release` and unlabelled PRs, which should still be written this way because any of them may become releasable. `release-action` does not run for a `no-release` PR, so `(#n)` closes nothing there. Editing the description re-runs the check. HTML comments are not shown on the release page and the check ignores them for headings, keywords and placeholders, but a `(#n)` inside a comment still closes the issue (`release-action` skips only fenced and inline code) and the check fails it, so never put an issue reference in a comment. The template's own comment can stay: its `(#123)` examples are inline code.
10
10
 
11
- ## Description
11
+ ### Allowed shape
12
12
 
13
- - Follow the repository's pull request template (`.github/pull_request_template.md`).
14
- - Focus on the **Added**, **Changed**, **Fixed**, **Removed**, **Security**, and **Deprecated** sections. Remove sections that are empty — don't leave blank headings.
15
- - Each bullet should be short, self-contained, and release-note ready.
16
- - **Write for users of the framework, not for internal developers.** Only include changes that have an impact on anyone using what we build — new APIs, changed behavior, fixed bugs, removed features. Do not list internal implementation details like storage changes, converter updates, gRPC contract internals, or spec additions. If a change is purely internal plumbing, it does not belong in the PR description. For a user-visible fix, a brief root cause and what now prevents a regression, stated as observable behavior rather than a list of specs, are user-facing and may be included: they tell the upgrader whether to trust the fix. Credit an external contributor by name or handle, unless they asked not to be named.
17
- - Add the associated issue reference at the end of a bullet when there is a real GitHub issue for the change (e.g. `(#351)`). Keep it a bare reference — **no closing keywords** (`Closes #351`, `Fixes #351`) anywhere in the body, because the published release notes are the PR description verbatim. If there is no associated issue, omit the reference entirely. Never use a placeholder like `(#issue)` or leave the example number `(#123)` literally, and never invent a random issue number. **Always verify the issue number read-only using the repository source — never guess or invent a number.** Comment on or close an issue when the user's request includes that effect; otherwise prepare a bounded post-merge disposition without performing it.
18
- - Include a summary only if there is a cohesive theme across the changes. If you find yourself restating individual bullets in slightly different words, the summary adds no value — remove it.
19
- - Never include Copilot prompt content in the PR description. Remove any "Original prompt" / coding agent transcript blocks before publishing.
13
+ - Follow the repository's pull request template (`.github/pull_request_template.md`) within this contract.
14
+ - An optional summary, first, only when one cohesive theme spans the bullets: **either** a `## Summary` section (short, user-facing prose) **or** one unheaded lead paragraph (1–3 sentences), never both. `Summary` is a level-2 heading; `# Summary` is the wrong level. No bullets before the first section.
15
+ - Then only these `##` sections, each at most once, in this order after any `## Summary`: `## Added`, `## Changed`, `## Fixed`, `## Removed`, `## Security`, `## Deprecated`. Keep only the sections that have bullets: a section holding only prose, a code example or `None.` fails the check, so delete it. `###` sub-headings inside a section are fine unless they use a forbidden name; inside a section even `### Added` is only a sub-heading, and a section itself is always level 2.
16
+ - A `major`/`minor`/`patch` PR needs at least one bullet under an allowed section.
17
+ - Bullets are short, self-contained and user-facing: what a consumer compiles against, runs or observes. A user-visible fix may add one sentence of root cause or regression guard, stated as observable behavior. Credit an external contributor by name or handle, unless they asked not to be named.
18
+ - Breaking changes and upgrade actions are bullets in `## Changed` or `## Removed` that state the action. A longer migration story goes in the docs, linked by absolute URL.
19
+ - Do not list internal plumbing (storage, converters, gRPC internals, specs, file lists, refactor narration).
20
+
21
+ ### Issue references
22
+
23
+ | Write | Meaning |
24
+ | --- | --- |
25
+ | `(#351)` at the **end of the bullet that delivers it** | Delivered: in a repository released by `cratis/release-action`, it closes it at release |
26
+ | `(part of #351)` or prose `see #351` | Related, partial or follow-up: never closes |
27
+ | `Cratis/Repo#351` | Other repository: never closed |
28
+ | one `(#351)` per issue, never `(#351, #352)` | `(#351, #352)` closes nothing: write `(#351) (#352)` |
29
+
30
+ - Nothing but more issue references (also `(part of #n)`, `(see #n)` and `(Cratis/Repo#n)`), closing emphasis, `<br>` and sentence punctuation (a `:` may introduce nested bullets) may follow the delivering `(#n)`. Write one `(#n)` per issue: release-action only matches `(#n)`, so `(#56, #57)` closes nothing; write `(#56) (#57)`.
31
+ - Never use a linking or closing keyword before a number, anywhere: `Close`, `Closes`, `Closed`, `Fix`, `Fixes`, `Fixed`, `Resolve`, `Resolves`, `Resolved`, `Refs`, `Ref`, `References`, also as `Keyword: #n`, with `owner/repo#n`, followed by an issue URL, and wrapped in bold, italics or a link (`**Closes** #n`, `Closes [#n](url)`, `[Closes #n](url)`, and `[Closes](url)` where the link starts a clause). A `Refs #93` line closes nothing and is not a delivery marker: write `(#93)` at the end of the delivering bullet, or `(part of #93)` if it must stay open.
32
+ - Comment on or close an issue when the user's request includes that effect; otherwise prepare a bounded post-merge disposition without performing it. In repositories released by `cratis/release-action`, a delivered `(#n)` is closed by release-action at release, so do not close it by hand; for a `no-release` PR release-action does not run, so any disposition follows this same rule. Elsewhere, follow the repository's own release and issue-closing process.
33
+ - Use a bare `(#n)` only for an issue this PR fully delivers. No issue means no reference. Never use a placeholder such as `(#issue)` or the template's `(#123)`, and verify every number exists in the right repository; never guess.
34
+
35
+ ### Forbidden anywhere outside code
36
+
37
+ - **Headings** (any level, also written as HTML `<h2>`, `<b>`, `<i>` or a possibly multi-line `<summary>`, or as an `*italic*` line; a heading line is checked like any other, so a keyword, relative link or `(#n)` in one fails too) other than a first `## Summary`, such as Overview, Description, What, Why, How, Context, Changes, What changed, Test plan, Testing, Tests, Verification, Verified, Validation, Quality, Review, Notes, Notes for reviewers, Limitations, Known follow-up, Acceptance, Details, and any `#`/`##` heading not in the allowed list.
38
+ - **Review, verification, testing and provenance notes**: `Review:`, `Reviewed:`, a stand-alone `Reviewed by` line, `Verification:`, `Tested:`, `Testing:`, `Validation:` lines that stand alone or report a result (`Tests: 400 passed`, `Review: approved`); same-provider, cross-provider, Opus-only or Anthropic-only review remarks; the review workflow having passed, returned or run; CI or gate results (`CI green`, `all tests passed`); and a line stating which agent or model wrote the description (`Generated with Claude Code`, `Co-Authored-By:`). A bullet that describes a product change and only mentions review, validation, tests or an AI model (`- Validation: rules now apply to commands (#3)`, `- Reviewed by status is now shown on the dashboard (#4)`) is fine: inside a bullet, a summary or the lead paragraph the check reads a note only when it ends the clause (`- Cross-provider review pending`).
39
+ - **Internal state** that is not a consumer change (for example "npm publication remains disabled").
40
+ - **Relative links** (`](Documentation/x.md)`, `](./x)`, `](Source/...)`): they 404 on the release page. Use `https://github.com/Cratis/<Repo>/blob/main/<path>`, a `#anchor` or `mailto:`.
41
+ - **Placeholders and transcripts**: template text, empty sections, Copilot "Original prompt" blocks, agent transcripts.
42
+
43
+ Reviewer-facing information (test plan, verification, review provenance, notes for reviewers) goes in a **PR comment**, never the description.
44
+
45
+ ### Bad to good
46
+
47
+ Bad (Cratis/Arc.TypeScript v0.48.0 as first published, bullets abridged):
48
+
49
+ ```markdown
50
+ Arc can now construct Chronicle reactors and reducers through its own dependency injection, one scope per delivered batch, as an opt-in preview. npm publication remains disabled.
51
+
52
+ ## Added
53
+
54
+ - Preview option `withChronicle({ ..., activateArtifactsInScopes: true })` ... (#93)
55
+
56
+ See [Activate reactors and reducers in Arc scopes](Documentation/chronicle/reactors/scoped-activation.md).
57
+
58
+ Review: Opus-only (same-provider) review.
59
+
60
+ Refs #93
61
+ ```
62
+
63
+ Good (as corrected):
64
+
65
+ ```markdown
66
+ Arc can now construct Chronicle reactors and reducers through its own dependency injection, one scope per delivered batch, as an opt-in preview.
67
+
68
+ ## Added
69
+
70
+ - Preview option `withChronicle({ ..., activateArtifactsInScopes: true })` ... See [Activate reactors and reducers in Arc scopes](https://github.com/Cratis/Arc.TypeScript/blob/main/Documentation/chronicle/reactors/scoped-activation.md). (part of #93)
71
+ ```
72
+
73
+ "npm publication remains disabled" is internal status, the link was relative (it 404s on the release page), the review line is provenance for reviewers, and `Refs #93` is a keyword line. #93 was only partly delivered by this release, so its bullets say `(part of #93)`; `(#93)` would have closed it.
74
+
75
+ ### Before you create or edit a PR
76
+
77
+ 1. Read the body against the forbidden list and the issue table above; delete every hit and verify every issue number.
78
+ 2. Sections in order, none empty, at most one summary form; every bullet user-facing.
79
+ 3. Test plan, verification and review notes are in a PR comment.
80
+
81
+ If `verify-release-notes` fails, fix it by editing the description, not by pushing code.
20
82
 
21
83
  ## Commits
22
84
 
@@ -2,9 +2,8 @@
2
2
  name: cratis-application-react-specifications
3
3
  description: Write specifications for the React and TypeScript surface of a Cratis application slice — view models, helpers, command orchestration, and narrow component behavior — using Vitest with Mocha-style describe/it, Sinon, and the Chai should interface. Use when adding or changing frontend behavior in an application that consumes Cratis. Do not use for backend scenarios or for specifications inside a Cratis framework package.
4
4
  license: MIT
5
- cratis-hint-paths:
6
- - "**/for_*/**/*.ts"
7
- - "**/for_*/**/*.tsx"
5
+ metadata:
6
+ cratis-hint-paths: "**/for_*/**/*.ts **/for_*/**/*.tsx"
8
7
  ---
9
8
 
10
9
  # Cratis application React specifications
@@ -2,9 +2,8 @@
2
2
  name: cratis-application-slice-specifications
3
3
  description: Specify the backend behavior of an event-sourced Cratis application slice with the in-process scenario family — CommandScenario, EventScenario, ReadModelScenario and ReactorScenario — including what to cover for each slice type and where the specification files live. Use when adding or changing backend behavior in an application built on Cratis. Do not use for framework library specifications and do not use for frontend behavior.
4
4
  license: MIT
5
- cratis-hint-paths:
6
- - "**/when_*/**/*.cs"
7
- - "**/for_*/**/*.cs"
5
+ metadata:
6
+ cratis-hint-paths: "**/when_*/**/*.cs **/for_*/**/*.cs"
8
7
  ---
9
8
 
10
9
  # Cratis application slice specifications
@@ -2,8 +2,8 @@
2
2
  name: cratis-documentation-writing
3
3
  description: Plan, write, and improve user-centered Cratis documentation with a clear reader journey and one primary Diátaxis purpose per page. Use for product docs, tutorials, how-to guides, reference, explanations, and documentation reviews. For executable examples use cratis-technical-examples; for release notes use cratis-release-notes. Do not invent APIs or publish content.
4
4
  license: MIT
5
- cratis-hint-paths:
6
- - "**/Documentation/**/*.{md,mdx}"
5
+ metadata:
6
+ cratis-hint-paths: "**/Documentation/**/*.{md,mdx}"
7
7
  ---
8
8
 
9
9
  # Documentation writing
@@ -136,9 +136,9 @@ An AI-drafted narrative is a first draft, not a finished page. Give the model
136
136
  the reader, the scenario, the terminology and the source evidence up front;
137
137
  then revise the result against that evidence and the
138
138
  **cratis-writing-voice-and-cadence** constructions before it ships. Tell the
139
- reviewer the narrative was AI-drafted (in the review request or commit
140
- message, not in a PR description's release-note sections) so they read it as
141
- prose, not only as a diff.
139
+ reviewer the narrative was AI-drafted (in the review request, a PR comment or
140
+ the commit message, never in the PR description, which is published as the
141
+ release note) so they read it as prose, not only as a diff.
142
142
 
143
143
  For machine-readable delivery and retrieval checks, use
144
144
  **cratis-llm-friendly-documentation**; publishing `llms-full.txt` alone does
@@ -2,8 +2,8 @@
2
2
  name: cratis-engineering-docs-authoring
3
3
  description: Draft accurate Cratis product or engineering documentation once the reader's goal, owning source, and product evidence are known. Use for tutorials, how-to guides, explanations, and references; use the owning repository's navigation and visual QA workflows for placement and rendering.
4
4
  license: LICENSE
5
- cratis-hint-paths:
6
- - "**/Documentation/**/*.{md,mdx}"
5
+ metadata:
6
+ cratis-hint-paths: "**/Documentation/**/*.{md,mdx}"
7
7
  ---
8
8
 
9
9
  # Cratis documentation authoring
@@ -20,10 +20,10 @@ required to draft notes.
20
20
  Before writing, identify product, tag or candidate version, previous version,
21
21
  artifact/package names, supported platforms, and the audience. Inventory
22
22
  observable changes from the diff, specs, linked issues/PRs, and authoritative
23
- product source at that revision. A PR description may be published verbatim as
24
- release notes in a Cratis repository; follow its current template and confirm
25
- that behavior before drafting. Do not turn an unreleased draft into a claim
26
- about shipped behavior.
23
+ product source at that revision. A PR description is published verbatim as
24
+ release notes in a Cratis repository; follow its current template and the
25
+ pull-request contract, and confirm that behavior before drafting. Do not turn
26
+ an unreleased draft into a claim about shipped behavior.
27
27
 
28
28
  For each change, ask: who uses this path, what happened before, what happens
29
29
  now, is action required, and how would they notice? Check changed defaults,
@@ -42,15 +42,29 @@ one to add weight.
42
42
 
43
43
  ## Write for the right channel
44
44
 
45
- - **Exact-version release note (GitHub/PR):** start with changes requiring
46
- action and affected workflows, then new capabilities and fixes. State impact
47
- and the user's next action in plain language. Include an issue reference only
48
- when verified, and follow the owning repository's closing-keyword policy.
49
- Do not list internal refactors or specs that change nothing users observe.
50
- For a user-visible fix, a sentence of root cause and of what now guards
51
- against a regression, stated as observable behavior rather than a list of
52
- specs, is user-facing: it tells the reader whether to trust the fix. Credit an external contributor by name or handle and say what
53
- they did, unless they asked not to be named.
45
+ - **Exact-version release note (GitHub/PR):** in a Cratis repository this is
46
+ the merged PR description, published verbatim, so it follows the contract in
47
+ [pull-requests.md](../../rules/pull-requests.md), which holds the full
48
+ rules. The ones most often broken: no development write-up (no Overview,
49
+ Verification or Test plan headings, no review or provenance notes; those go
50
+ in a PR comment); `(#n)` at the end of the bullet that delivers an issue and
51
+ `(part of #n)` for anything that stays open (in repositories released by
52
+ `cratis/release-action`, `(#n)` is what closes the issue), never `Closes`, `Fixes` or
53
+ `Refs` before a number (also in bold or a link), and no issue reference
54
+ inside an HTML comment, which still closes the issue; and absolute
55
+ `https://` links only. Sections are in
56
+ a fixed order (Added, Changed, Fixed, Removed, Security, Deprecated); within
57
+ a section, put changes requiring action first, then new capabilities and
58
+ fixes. State impact and the user's next action in plain language: state an
59
+ upgrade action in its `## Changed` or `## Removed` bullet (a first
60
+ `## Summary` section or an unheaded lead paragraph, not both, may mention it
61
+ too). Do not list internal
62
+ refactors or specs that change nothing users observe. For a
63
+ user-visible fix, a sentence of root cause and of what now guards against a
64
+ regression, stated as observable behavior rather than a list of specs, is
65
+ user-facing: it tells the reader whether to trust the fix. Credit an
66
+ external contributor by name or handle and say what they did, unless they
67
+ asked not to be named.
54
68
  - **Migration guide (durable product docs):** a compact *old behavior → new
55
69
  behavior → required action* table for each affected upgrade path, followed
56
70
  by source-verified before/after code or commands. Distinguish required
@@ -75,6 +89,23 @@ boundary that makes a change safe to adopt. One item can follow this shape:
75
89
  That is a checklist for facts, not a template to repeat word-for-word across
76
90
  items. Use descriptive headings, meaningful links, and natural sentence rhythm.
77
91
 
92
+ ## Repair an already-published release
93
+
94
+ Only when asked to fix a release that violates the contract:
95
+
96
+ 1. Read the published body (`gh release view TAG --json body`) and the merged
97
+ PR. Keep the facts; remove headings, review and verification lines,
98
+ internal status and closing keywords, and turn relative links into
99
+ `https://` URLs. Do not invent changes, versions or issue numbers.
100
+ 2. Publish the corrected text with `gh release edit TAG --notes-file FILE`,
101
+ only when the request authorizes editing that release.
102
+ 3. In a repository released by `cratis/release-action`, editing a release
103
+ does not re-run release-action, so an issue that a `(#n)` would have
104
+ closed stays open. For each one, confirm the release
105
+ actually delivered it. Close it by hand with a comment naming the release
106
+ only when the request covers closing issues; otherwise list the issues as a
107
+ proposal. Leave an issue open when the release only partly delivered it.
108
+
78
109
  ## Verify before handing over
79
110
 
80
111
  1. Reconcile every version, affected range, capability and API against the
@@ -6,6 +6,9 @@
6
6
  { "kind": "skill-contains", "value": "Exact-version release note" },
7
7
  { "kind": "skill-contains", "value": "old behavior → new" },
8
8
  { "kind": "skill-contains", "value": "migration from optional cleanup" },
9
- { "kind": "skill-contains", "value": "drafting never authorizes publishing" }
9
+ { "kind": "skill-contains", "value": "drafting never authorizes publishing" },
10
+ { "kind": "skill-contains", "value": "`(part of #n)`" },
11
+ { "kind": "skill-contains", "value": "gh release edit TAG --notes-file" },
12
+ { "kind": "skill-contains", "value": "does not re-run release-action" }
10
13
  ]
11
14
  }
@@ -2,8 +2,8 @@
2
2
  name: cratis-specifications-csharp
3
3
  description: Write C# specifications with Cratis.Specifications using the Establish/Because/should_ pattern and the for_/when_/and_ folder hierarchy. Use when adding or restructuring C# specs in any Cratis repository, choosing between an isolated unit spec and an in-process scenario spec, or building reusable given/ contexts. Do not use for TypeScript or React specs, and do not use it to decide what a command, projection, or reactor should do.
4
4
  license: MIT
5
- cratis-hint-paths:
6
- - "**/for_*/**/*.cs"
5
+ metadata:
6
+ cratis-hint-paths: "**/for_*/**/*.cs"
7
7
  ---
8
8
 
9
9
  # Cratis C# specifications
@@ -2,9 +2,8 @@
2
2
  name: cratis-specifications-typescript
3
3
  description: Write TypeScript specifications in the Cratis BDD style using the given() helper, reusable context classes, Sinon stubbing, and the Chai .should fluent interface. Use when adding or restructuring TypeScript specifications, building a given/ context class, or laying out a for_/when_ specification folder. Do not use for C# specifications, and do not use it to decide what the code under specification should do.
4
4
  license: MIT
5
- cratis-hint-paths:
6
- - "**/for_*/**/*.ts"
7
- - "**/for_*/**/*.tsx"
5
+ metadata:
6
+ cratis-hint-paths: "**/for_*/**/*.ts **/for_*/**/*.tsx"
8
7
  ---
9
8
 
10
9
  # Cratis TypeScript specifications
@@ -2,8 +2,8 @@
2
2
  name: cratis-technical-examples
3
3
  description: Design and verify developer-facing code samples, tutorial projects, and documentation snippets against real Cratis APIs. Use when adding or reviewing a runnable example, multi-client snippet, sample app, command/output pair, or migration before/after code. Do not invent API shapes or treat rendering as a compilation check.
4
4
  license: MIT
5
- cratis-hint-paths:
6
- - "**/Samples/**/*.{cs,ts,tsx}"
5
+ metadata:
6
+ cratis-hint-paths: "**/Samples/**/*.{cs,ts,tsx}"
7
7
  ---
8
8
 
9
9
  # Technical examples readers can trust
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/pi",
3
- "version": "7.2.0",
3
+ "version": "7.2.2",
4
4
  "description": "Configuration-aware Cratis AI integration for Pi",
5
5
  "type": "module",
6
6
  "files": [