@nusoft/nuos-build-catalogue 0.27.0 → 0.28.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nusoft/nuos-build-catalogue",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "description": "NuOS build-catalogue tooling: semantic search (WU 110) + migration runner that lifts markdown artefacts into JSON-backed workflow records (WU 111, Phase G).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -68,7 +68,8 @@ check_index_drift() {
68
68
  ids_in_tree=$(cd "$REPO_ROOT/$dir" && {
69
69
  find . -maxdepth 2 -type f -name "*.md" \
70
70
  -not -name "_index.md" \
71
- -not -name "*-template.md" 2>/dev/null \
71
+ -not -name "*-template.md" \
72
+ -not -name "*-template-*.md" 2>/dev/null \
72
73
  | sed -nE "$file_regex" \
73
74
  | sort -u
74
75
  })
@@ -34,6 +34,32 @@ nuos-catalogue memory store --value="<what worked and why, or what to avoid>" --
34
34
 
35
35
  If anything in the work unit is ambiguous, **stop and surface the ambiguity to the coordinator** rather than guessing. A guess produces work that may not match the design.
36
36
 
37
+ ## Design system gate (UI work — enforced by hook)
38
+
39
+ **If this work unit touches any UI file (`.css`, `.scss`, `.less`, `.html`, `.tsx`, `.jsx`, `.vue`, `.svelte`, `.astro`), a write-gate hook is active. It will BLOCK the write and force you back here.**
40
+
41
+ Before writing any UI file, complete these steps in order:
42
+
43
+ 1. **Read the design system — all of it:**
44
+ - `docs/build/design-system/tokens-colour.md` — every colour token and its hex value
45
+ - `docs/build/design-system/tokens-typography.md` — font sizes, weights, line heights
46
+ - `docs/build/design-system/tokens-spacing.md` — the spacing scale
47
+ - `docs/build/design-system/tokens-radius-elevation.md` — border radius, shadows
48
+
49
+ 2. **Identify the token reference pattern this project uses** — read two or three existing UI files to confirm one of:
50
+ - CSS custom properties: `color: var(--colour-text-primary);`
51
+ - Theme/token object: `color: theme.colour.text.primary`
52
+ - Utility class config: project-configured Tailwind or similar
53
+
54
+ 3. **Map every value to a token before writing a single line.** If a colour, size, or spacing value you need has no token in the design system, **stop and surface the gap to the coordinator** — do not invent a value or use a hardcode.
55
+
56
+ The hook checks for:
57
+ - Raw hex literals in colour properties: `color: #1a2b3c` — BLOCKED
58
+ - Hex strings in JSX inline styles: `color: '#fff'` — BLOCKED
59
+ - Hex colours in HTML style attributes — BLOCKED
60
+
61
+ CSS custom property definitions (`--colour-x: #hex`) are allowed — that is where the token value lives. Everything else must use the token by name.
62
+
37
63
  ## How you work
38
64
 
39
65
  1. **Plan the change in your head first**, then state it in 1-2 sentences before writing code. Match existing code idioms; don't introduce new patterns the project hasn't adopted.
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # NuOS Build Method — Design System Compliance Hook (PreToolUse)
4
+ #
5
+ # Blocks Write / Edit / MultiEdit when:
6
+ # (a) the target file is a UI file (.css, .scss, .less, .html, .tsx,
7
+ # .jsx, .vue, .svelte, .astro), AND
8
+ # (b) the written content contains hardcoded colour values (hex literals,
9
+ # CSS named colours) in colour-property positions, AND
10
+ # (c) the project has a design system at docs/build/design-system/
11
+ #
12
+ # Rationale
13
+ # ─────────
14
+ # Agents write UI code without first reading the design system, producing
15
+ # raw hex values that bypass the project's token contracts. The reviewer
16
+ # agent catches this after the fact — but by then the coder has already
17
+ # satisfied its acceptance criteria and treats the finding as "cleanup."
18
+ # This hook closes the gap at the moment of writing: the write is blocked
19
+ # and the agent is shown exactly where to look before it can retry.
20
+ #
21
+ # What is checked
22
+ # ───────────────
23
+ # 1. CSS/SCSS/Less: colour property with hardcoded hex literal
24
+ # color: #fff ← BLOCKED
25
+ # --colour-x: #fff ← allowed (token definition line; starts with --)
26
+ # color: var(--x) ← allowed
27
+ # 2. JSX/TSX: inline style object with hex string
28
+ # color: '#1a2b3c' ← BLOCKED
29
+ # 3. HTML: style attribute containing a hex colour
30
+ # style="color: #fff" ← BLOCKED
31
+ #
32
+ # Degrade-safe: if content cannot be reliably parsed (no jq or python3,
33
+ # or parse failure), the hook exits 0. Never block on ambiguous input.
34
+ #
35
+ # Exit codes
36
+ # ──────────
37
+ # 0 — allow (no violations, design system absent, or degrade-safe skip)
38
+ # 2 — block (stderr is surfaced to the model by Claude Code)
39
+
40
+ set -uo pipefail
41
+
42
+ INPUT="$(cat 2>/dev/null || true)"
43
+
44
+ # ── Project root ──────────────────────────────────────────────────────────────
45
+ PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-}"
46
+ if [[ -z "$PROJECT_ROOT" ]]; then
47
+ PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
48
+ fi
49
+ if [[ -z "$PROJECT_ROOT" ]]; then exit 0; fi
50
+
51
+ # ── Extract file path ─────────────────────────────────────────────────────────
52
+ FILE=$(printf '%s' "$INPUT" \
53
+ | grep -oE '"(file_path|notebook_path)"[[:space:]]*:[[:space:]]*"[^"]+"' \
54
+ | head -1 \
55
+ | sed -E 's/"(file_path|notebook_path)"[[:space:]]*:[[:space:]]*"//' \
56
+ | tr -d '"')
57
+
58
+ if [[ -z "${FILE:-}" ]]; then exit 0; fi
59
+
60
+ # Normalise to absolute path
61
+ case "$FILE" in
62
+ /*) ABSOLUTE_FILE="$FILE" ;;
63
+ *) ABSOLUTE_FILE="$(pwd)/$FILE" ;;
64
+ esac
65
+
66
+ # ── Skip the design-system directory itself ───────────────────────────────────
67
+ if [[ "$ABSOLUTE_FILE" == *"/docs/build/design-system/"* ]]; then
68
+ exit 0
69
+ fi
70
+
71
+ # Skip generated output directories
72
+ case "$ABSOLUTE_FILE" in
73
+ */node_modules/*|*/dist/*|*/.next/*|*/build/*|*/.nuxt/*) exit 0 ;;
74
+ esac
75
+
76
+ # ── Only check UI file types ──────────────────────────────────────────────────
77
+ EXTENSION="${FILE##*.}"
78
+ case "$EXTENSION" in
79
+ css|scss|less|html|tsx|jsx|vue|svelte|astro) : ;;
80
+ *) exit 0 ;;
81
+ esac
82
+
83
+ # ── Find the design system ────────────────────────────────────────────────────
84
+ DS_DIR=""
85
+ if [[ -d "$PROJECT_ROOT/docs/build/design-system" ]]; then
86
+ DS_DIR="$PROJECT_ROOT/docs/build/design-system"
87
+ else
88
+ # Split-repo pattern: look for a sibling catalogue that owns the design system
89
+ PARENT="$(dirname "$PROJECT_ROOT")"
90
+ for sibling in "$PARENT"/*/docs/build/design-system; do
91
+ if [[ -d "$sibling" ]]; then
92
+ DS_DIR="$sibling"
93
+ break
94
+ fi
95
+ done
96
+ fi
97
+
98
+ if [[ -z "$DS_DIR" ]]; then exit 0; fi
99
+
100
+ COLOUR_FILE="$DS_DIR/tokens-colour.md"
101
+ if [[ ! -f "$COLOUR_FILE" ]]; then exit 0; fi
102
+
103
+ # ── Extract the content being written ─────────────────────────────────────────
104
+ # Write → tool_input.content
105
+ # Edit → tool_input.new_string
106
+ # MultiEdit → tool_input.edits[].new_string (joined)
107
+ CONTENT=""
108
+ if command -v jq &>/dev/null; then
109
+ CONTENT=$(printf '%s' "$INPUT" | jq -r '
110
+ .tool_input.content //
111
+ .tool_input.new_string //
112
+ (.tool_input.edits // [] | map(.new_string // "") | join("\n")) //
113
+ ""
114
+ ' 2>/dev/null || true)
115
+ elif command -v python3 &>/dev/null; then
116
+ CONTENT=$(printf '%s' "$INPUT" | python3 -c "
117
+ import sys, json
118
+ try:
119
+ d = json.load(sys.stdin)
120
+ ti = d.get('tool_input', {})
121
+ out = ti.get('content') or ti.get('new_string')
122
+ if out is None:
123
+ edits = ti.get('edits', [])
124
+ out = '\n'.join(e.get('new_string', '') for e in edits)
125
+ print(out or '')
126
+ except: pass
127
+ " 2>/dev/null || true)
128
+ fi
129
+
130
+ if [[ -z "${CONTENT:-}" ]]; then exit 0; fi
131
+
132
+ # ── Detect hardcoded colour violations ───────────────────────────────────────
133
+
134
+ # 1. CSS/SCSS/Less: colour property with a hex literal value.
135
+ # Excludes lines that start with optional whitespace followed by '--'
136
+ # (those are CSS custom property definitions — they SET the token value).
137
+ CSS_HEX=$(printf '%s' "$CONTENT" \
138
+ | grep -E '(color|background(-color)?|border(-color)?|fill|stroke|outline(-color)?|accent-color)[[:space:]]*:[[:space:]]*#[0-9a-fA-F]{3,8}' \
139
+ | grep -vE '^\s*--' \
140
+ | grep -oE '(color|background(-color)?|border(-color)?|fill|stroke|outline(-color)?|accent-color)[[:space:]]*:[[:space:]]*#[0-9a-fA-F]{3,8}' \
141
+ | head -3 || true)
142
+
143
+ # 2. JSX/TSX: inline style object with a hex colour string.
144
+ # e.g. color: '#fff' or backgroundColor: "#1a2b3c"
145
+ JSX_HEX=$(printf '%s' "$CONTENT" \
146
+ | grep -oE "(color|backgroundColor|borderColor|fill|stroke|outlineColor)[[:space:]]*:[[:space:]]*['\"]#[0-9a-fA-F]{3,8}" \
147
+ | head -3 || true)
148
+
149
+ # 3. HTML: style attribute that contains a hex colour value.
150
+ HTML_HEX=$(printf '%s' "$CONTENT" \
151
+ | grep -oE 'style=[^>]*#[0-9a-fA-F]{3,8}' \
152
+ | head -3 || true)
153
+
154
+ if [[ -z "${CSS_HEX:-}" && -z "${JSX_HEX:-}" && -z "${HTML_HEX:-}" ]]; then
155
+ exit 0
156
+ fi
157
+
158
+ # ── Build the violation summary ───────────────────────────────────────────────
159
+ VIOLATIONS=""
160
+ [[ -n "${CSS_HEX:-}" ]] && VIOLATIONS="$VIOLATIONS
161
+ CSS: $(printf '%s' "$CSS_HEX" | head -1)"
162
+ [[ -n "${JSX_HEX:-}" ]] && VIOLATIONS="$VIOLATIONS
163
+ JSX: $(printf '%s' "$JSX_HEX" | head -1)"
164
+ [[ -n "${HTML_HEX:-}" ]] && VIOLATIONS="$VIOLATIONS
165
+ HTML: $(printf '%s' "$HTML_HEX" | head -1)"
166
+
167
+ # ── Read token excerpt for the error message ──────────────────────────────────
168
+ TOKEN_HINT=""
169
+ TOKEN_EXCERPT=$(grep -E '`colour\.' "$COLOUR_FILE" 2>/dev/null | head -12 || true)
170
+ if [[ -n "$TOKEN_EXCERPT" ]]; then
171
+ TOKEN_HINT="
172
+ Available colour tokens (from $(basename "$DS_DIR")/tokens-colour.md):
173
+ $TOKEN_EXCERPT
174
+
175
+ → Use the token name. Do NOT use the raw hex value."
176
+ fi
177
+
178
+ # ── Block ─────────────────────────────────────────────────────────────────────
179
+ cat >&2 <<EOF
180
+ ✖ nuos: design-system compliance block — hardcoded colour values detected.
181
+
182
+ File: $FILE
183
+ Violations:$VIOLATIONS
184
+
185
+ ── Required action ──────────────────────────────────────────────────────────
186
+
187
+ Before writing any UI file you MUST:
188
+
189
+ 1. Read the full design system:
190
+ $DS_DIR/tokens-colour.md
191
+ $DS_DIR/tokens-typography.md
192
+ $DS_DIR/tokens-spacing.md
193
+ $DS_DIR/tokens-radius-elevation.md
194
+
195
+ 2. Identify how this project references tokens by reading existing UI
196
+ files in the codebase — look for one of these patterns:
197
+ CSS custom properties: color: var(--colour-text-primary);
198
+ JSX theme object: color: theme.colour.text.primary
199
+ Tailwind config tokens: text-text-primary (if configured)
200
+
201
+ 3. Replace EVERY hardcoded hex or named colour with the correct token
202
+ reference. If no token covers the value you need, STOP and surface
203
+ the gap to the coordinator — do NOT invent a one-off value.
204
+
205
+ This hook will block every write that contains a raw colour value.
206
+ The design system is the contract; the implementation must honour it.
207
+ $TOKEN_HINT
208
+
209
+ EOF
210
+
211
+ exit 2