@cratis/pi 2.0.8 → 2.0.10

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.
@@ -62,6 +62,23 @@ The two `within_type_attribute` patterns are not line greps — the scanner trac
62
62
  blocks and type scope (positional record, multi-line declaration, or braced body), so a nullable
63
63
  property is only reported when it really sits inside an `[EventType]`.
64
64
 
65
+ ### Project-specific gate configuration
66
+
67
+ The shipped gates discover the repository's own solution and package, so most repositories need no
68
+ configuration at all. A repository whose project is not where discovery lands — several packages, a
69
+ frontend under `Source/<App>` — states only what differs in its own
70
+ `.cratis/ai/quality-gates.project.json`, which the gate merges over the managed file by gate id:
71
+
72
+ ```json
73
+ { "gates": [ { "id": "frontend-lint", "workingDirectory": "Source/App" } ] }
74
+ ```
75
+
76
+ That file is project-owned and outside the managed manifest. **Do not put project facts into
77
+ `scripts/quality-gates.json`**: it is Cratis-managed, so the next managed update either reports it as
78
+ drift or replaces it, and the repository silently loses its own configuration. An override naming a
79
+ gate that does not exist is reported on stderr rather than ignored, and an unreadable override leaves
80
+ the managed gates running unchanged.
81
+
65
82
  **Gated** (`Stop`, exit 2): the app-pinned commands from the Quality Gates table in
66
83
  `general.md` and the steps in [`agent-stop.md`](./agent-stop.md) — Debug build, specs, Release
67
84
  build (with `-p:CratisProxiesOutputPath=` per `general.md`, so the proxy generator does not
@@ -304,7 +321,7 @@ Each is an explicit, auditable opt-out — none of them is a default.
304
321
  | `CRATIS_HOOKS_SKIP_GATE=1` | disables the quality gate |
305
322
  | `CRATIS_HOOKS_GATE_DRYRUN=1` | prints which gates would run, and why, then exits 0 |
306
323
  | `CRATIS_HOOKS_PATTERNS=<path>` | replaces the pattern file |
307
- | `CRATIS_HOOKS_GATES=<path>` | replaces the gate file |
324
+ | `CRATIS_HOOKS_GATES=<path>` | replaces the gate file (the project override still merges over it) |
308
325
  | `CRATIS_HOOKS_SUBPATH_REPORT=1` | prints every `@cratis/*` subpath reference and how it resolved, not only the failures |
309
326
  | `CRATIS_HOOKS_IMPORT_REPORT=1` | prints every `@cratis/*` named import binding and how it resolved, not only the failures |
310
327
  | `CRATIS_HOOKS_TYPE_REPORT=1` | prints every .NET type/attribute name the corpus mentions and how it resolved, not only the failures |
@@ -40,6 +40,41 @@ jq -e . "$config" >/dev/null 2>&1 || {
40
40
  printf 'cratis-quality-gate: %s is not valid JSON — gate skipped.\n' "$config" >&2
41
41
  exit 0
42
42
  }
43
+
44
+ # ── Project-owned overrides ──────────────────────────────────────────────────
45
+ # Where a repository's own answer to "which directory does this gate build in" lives. It is
46
+ # outside the managed tree on purpose: a repository that instead edits the managed
47
+ # quality-gates.json mixes project facts into Cratis-owned content, so the next managed update
48
+ # either reports drift or silently discards the repository's own configuration. The override
49
+ # states only what differs, keyed by gate id, and nothing here needs a script fork.
50
+ overrides="$root/.cratis/ai/quality-gates.project.json"
51
+ if [ -f "$overrides" ]; then
52
+ if jq -e . "$overrides" >/dev/null 2>&1; then
53
+ merged="$(mktemp "${TMPDIR:-/tmp}/cratis-quality-gates.XXXXXX")"
54
+ if jq -s '
55
+ .[0] as $base | .[1] as $over
56
+ | ($over.gates // []) as $gates
57
+ | $base
58
+ + ($over | del(.gates))
59
+ + { gates: [ $base.gates[] as $gate
60
+ | ($gates | map(select(.id == $gate.id)) | first) as $patch
61
+ | if $patch == null then $gate else $gate + ($patch | del(.id)) end ] }
62
+ ' "$config" "$overrides" >"$merged" 2>/dev/null; then
63
+ unknown="$(jq -r --slurpfile base "$config" '[.gates // [] | .[].id] - [$base[0].gates[].id] | .[]' "$overrides" 2>/dev/null || true)"
64
+ [ -n "$unknown" ] && printf 'cratis-quality-gate: %s overrides unknown gate(s): %s\n' \
65
+ "${overrides#"$root"/}" "$(printf '%s' "$unknown" | tr '\n' ' ')" >&2
66
+ config="$merged"
67
+ else
68
+ rm -f "$merged"
69
+ printf 'cratis-quality-gate: %s could not be merged — managed gates used unchanged.\n' \
70
+ "${overrides#"$root"/}" >&2
71
+ fi
72
+ else
73
+ printf 'cratis-quality-gate: %s is not valid JSON — managed gates used unchanged.\n' \
74
+ "${overrides#"$root"/}" >&2
75
+ fi
76
+ fi
77
+
43
78
  [ "$(jq -r '.enabled // true' "$config")" = "true" ] || exit 0
44
79
 
45
80
  # ── What changed in the working tree ─────────────────────────────────────────
@@ -9,13 +9,18 @@ set -euo pipefail
9
9
  # ── Environment ───────────────────────────────────────────────────────────────
10
10
 
11
11
  # Root of the repository the hook is running for.
12
+ #
13
+ # The fallback walks up from this script, which is installed at
14
+ # <root>/.cratis/ai/hooks/scripts/hook-lib.sh - four levels, not three. Three landed on
15
+ # <root>/.cratis, so without CLAUDE_PROJECT_DIR every hook read a repository whose git
16
+ # directory, tracked files and project files were all missing, and silently did nothing.
12
17
  hook_repo_root() {
13
18
  local d="${CLAUDE_PROJECT_DIR:-}"
14
19
  if [ -n "$d" ] && [ -d "$d" ]; then
15
20
  (cd "$d" && pwd)
16
21
  return 0
17
22
  fi
18
- (cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)
23
+ (cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)
19
24
  }
20
25
 
21
26
  # True when the named command is on PATH.
@@ -29,9 +29,14 @@
29
29
  "an application repository, a framework repository, and (as nothing to run) in a corpus-only",
30
30
  "repository such as this one, which carries no .NET or Node project at all.",
31
31
  "",
32
- "The override model, in order of increasing force: a repository sets workingDirectory to pin",
33
- "one of several candidate projects; or it drops its own quality-gates.json in place of this",
34
- "file; or it points CRATIS_HOOKS_GATES at a file anywhere. None of them requires a script fork.",
32
+ "The override model, in order of increasing force: a repository states only what differs in its",
33
+ "own .cratis/ai/quality-gates.project.json, which the runner merges over this file by gate id",
34
+ "(a project fact belongs there, never edited into this managed file, or the next managed update",
35
+ "reports drift or discards it); or it points CRATIS_HOOKS_GATES at a file anywhere. Neither",
36
+ "requires a script fork.",
37
+ "",
38
+ "Example .cratis/ai/quality-gates.project.json, for a repository whose frontend is not at the root:",
39
+ " { \"gates\": [ { \"id\": \"frontend-lint\", \"workingDirectory\": \"Source/App\" } ] }",
35
40
  "",
36
41
  "Commands are pinned to .cratis/ai/rules/general.md 'Quality Gates' and .cratis/ai/hooks/agent-stop.md.",
37
42
  "Note the Release build passes -p:CratisProxiesOutputPath= per general.md so the proxy",
@@ -6,6 +6,8 @@ applyTo: "**/*"
6
6
 
7
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.
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.
10
+
9
11
  ## Description
10
12
 
11
13
  - Follow the repository's pull request template (`.github/pull_request_template.md`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/pi",
3
- "version": "2.0.8",
3
+ "version": "2.0.10",
4
4
  "description": "Configuration-aware Cratis AI integration for Pi",
5
5
  "type": "module",
6
6
  "files": [