@hegemonart/get-design-done 1.43.0 → 1.45.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.
@@ -0,0 +1,38 @@
1
+ # Spatial - Composition, Grid, and Perceptual Organization
2
+
3
+ Spatial design governs how elements are placed, sized, and related in two-dimensional
4
+ space: composition principles (rule of thirds, focal point, eye-flow), proportion
5
+ derivation (baseline grid, spacing ladder), CSS implementation (Grid, container queries,
6
+ clamp), visual hierarchy, Gestalt grouping, and data presentation. Read this file first,
7
+ then load the specific fragment for the task at hand. Does not cover 3D depth signaling
8
+ via color/shadow as a color decision (see `color.md`) or responsive layout shifts driven
9
+ by locale/platform (see `responsive.md`).
10
+
11
+ ## Fragment Index
12
+
13
+ | Fragment | When to load |
14
+ |---|---|
15
+ | [`./composition.md`](./composition.md) | choosing a compositional grid (rule of thirds, root rectangles, Fibonacci), placing a focal point, deciding eye-flow archetype (Z / F / Gutenberg) |
16
+ | [`./proportion-systems.md`](./proportion-systems.md) | deriving the baseline grid unit, spacing ladder, icon sizing, corner radius from a single multiplier (4pt / 8pt / sqrt(2)) |
17
+ | [`./css-grid-layout.md`](./css-grid-layout.md) | implementing CSS Grid templates, subgrid, container queries, `clamp()` fluid type, logical props, anchor positioning |
18
+ | [`./visual-hierarchy-layout.md`](./visual-hierarchy-layout.md) | ordering elements by importance: Z-order, whitespace rules, shadow depth, 24 landing archetypes |
19
+ | [`./gestalt.md`](./gestalt.md) | auditing perceptual grouping: proximity, similarity, continuity, closure, figure/ground - 8 principles with scoring rubrics |
20
+ | [`./image-optimization.md`](./image-optimization.md) | images in layout: format matrix, srcset/sizes math, LQIP/BlurHash, CDN transforms, image budget |
21
+ | [`./data-visualization.md`](./data-visualization.md) | charts/dashboards: 25-type chart-choice matrix, color-blind palettes, axis conventions, UUPM dashboard patterns |
22
+ | [`./surfaces.md`](./surfaces.md) | surface treatments: concentric radius formula, optical alignment, 3-layer shadow system, hit area sizing |
23
+
24
+ ## Rules of Thumb
25
+
26
+ 1. Place primary CTAs on the rule-of-thirds power points (the 33%/67% intersections), not dead-center. Centered heroes are the intentional exception, not the default.
27
+ 2. Choose one baseline unit for the whole product (4pt, 8pt, or sqrt(2)) and author every spacing, sizing, and radius token as a multiple of it. No one-off values.
28
+ 3. CSS Grid container queries (`@container`) should replace media queries for component-level layout. Reserve media queries for page-level breakpoints only.
29
+ 4. Gestalt proximity rule: related controls 8px apart or less; unrelated groups 32px apart or more. The layout should communicate relationships before the user reads labels.
30
+ 5. Choose the chart type before the color palette. The wrong chart type cannot be rescued by a better palette. Use `data-visualization.md` chart-choice matrix first.
31
+
32
+ ## See Also
33
+
34
+ - Shadow and elevation as color/depth signal: [`./color.md`](./color.md)
35
+ - Type scale ties into spatial proportion grid: [`./typography.md`](./typography.md)
36
+ - Responsive layout shifts at breakpoints: [`./responsive.md`](./responsive.md)
37
+ - Gestalt grouping informs information architecture: [`./interaction.md`](./interaction.md)
38
+ - Gaming: TV-safe area constraints: [`./domains/gaming-patterns.md`](./domains/gaming-patterns.md)
@@ -1,5 +1,34 @@
1
1
  # Typography - Scale, Pairing, and Hierarchy
2
2
 
3
+ This file is the domain index for typography. It covers type-scale construction,
4
+ weight hierarchy, font pairing, and micro-typography. Read `variable-fonts-loading.md`
5
+ when web fonts are in scope. Read `proportion-systems.md` when the full UI grid is
6
+ in scope. Does not cover text-contrast (see `color.md`) or locale text expansion
7
+ (see `responsive.md`, which indexes [`./i18n.md`](./i18n.md) for the per-locale expansion budgets).
8
+
9
+ ## Fragment Index
10
+
11
+ | Fragment | When to load |
12
+ |---|---|
13
+ | This file (below) | type scale ratios, sizing tokens, weight hierarchy, font pairings, micro-typography |
14
+ | [`./variable-fonts-loading.md`](./variable-fonts-loading.md) | web fonts, `@font-face`, `font-display`, FOIT/FOUT, variable font axes, subsetting, fallback metrics |
15
+ | [`./proportion-systems.md`](./proportion-systems.md) | 4pt/8pt/sqrt(2) baseline grid, spacing ladder, icon sizing, corner radius derivation |
16
+
17
+ ## Rules of Thumb
18
+
19
+ 1. A 1.25 (Major Third) or 1.333 (Perfect Fourth) ratio fits 95% of SaaS products; only reach for 1.618 when visual drama is the explicit brief.
20
+ 2. Never author `line-height` as a pixel value - always use a unitless multiplier (1.4-1.6 for body, 1.1-1.2 for headings); unitless values scale with user font-size overrides.
21
+ 3. Font weight on the web has only 7 meaningful stops (100-700 in 100-steps); any weight not in the font file rounds to the nearest available - check the variable font `wght` range before specifying 450 or 600.
22
+ 4. Baseline-grid lock: every spacing and sizing token should be a multiple of the chosen baseline unit; `padding: 12px` next to body at `16/24` on an 8pt grid is a silent proportion break.
23
+ 5. For localized UIs, size type containers for +40% expansion (Russian/Finnish worst-case); top-aligned labels are the only label position that absorbs expansion without layout breakage.
24
+
25
+ ## See Also
26
+
27
+ - Text contrast and color-blindness checks: [`./color.md`](./color.md)
28
+ - Text expansion in localized UIs: [`./responsive.md`](./responsive.md)
29
+ - Proportion systems tie type to spatial grid: [`./spatial.md`](./spatial.md)
30
+ - Finance number formatting (tabular-nums): [`./domains/finance-patterns.md`](./domains/finance-patterns.md)
31
+
3
32
  ---
4
33
 
5
34
  ## Type Scale Systems
@@ -35,7 +64,7 @@ Choose a ratio and base size. Common ratios:
35
64
 
36
65
  Never create a scale ad-hoc. Pick one ratio, generate the scale, use only values in the scale.
37
66
 
38
- **See:** [`./proportion-systems.md`](./proportion-systems.md) §Modular Relationships for how the type scale ties to spacing / sizing / radius scales - when one ratio drives all four scales the whole UI gains a single rhythm rather than four independently-tuned progressions.
67
+ **See:** [`./proportion-systems.md`](./proportion-systems.md) for how the type scale ties to spacing / sizing / radius scales.
39
68
 
40
69
  ---
41
70
 
@@ -43,11 +72,11 @@ Never create a scale ad-hoc. Pick one ratio, generate the scale, use only values
43
72
 
44
73
  | Context | Line height | Notes |
45
74
  |---|---|---|
46
- | Body text | **1.5 1.75** | More generous = more readable |
47
- | Headings | **1.1 1.3** | Tight heading stacks look intentional |
75
+ | Body text | **1.5 - 1.75** | More generous = more readable |
76
+ | Headings | **1.1 - 1.3** | Tight heading stacks look intentional |
48
77
  | Captions / small text | **1.4** | Smaller text needs more breathing room |
49
- | Code blocks | **1.6 1.8** | Line scanning for code |
50
- | Display / hero | **0.9 1.1** | Can go very tight for dramatic effect |
78
+ | Code blocks | **1.6 - 1.8** | Line scanning for code |
79
+ | Display / hero | **0.9 - 1.1** | Can go very tight for dramatic effect |
51
80
 
52
81
  ---
53
82
 
@@ -55,9 +84,9 @@ Never create a scale ad-hoc. Pick one ratio, generate the scale, use only values
55
84
 
56
85
  | Context | Characters per line | Notes |
57
86
  |---|---|---|
58
- | Desktop body | **65 75 chars** | Optimal reading comfort |
59
- | Mobile body | **35 55 chars** | Narrower viewport forces shorter |
60
- | Hero/display | **35 55 chars** | Headings should never wrap awkwardly |
87
+ | Desktop body | **65 - 75 chars** | Optimal reading comfort |
88
+ | Mobile body | **35 - 55 chars** | Narrower viewport forces shorter |
89
+ | Hero/display | **35 - 55 chars** | Headings should never wrap awkwardly |
61
90
  | Data/tables | No limit | Tables have own structure |
62
91
 
63
92
  Enforce with `max-width`: `65ch` for body containers works with any font size.
@@ -68,13 +97,13 @@ Enforce with `max-width`: `65ch` for body containers works with any font size.
68
97
 
69
98
  | Role | Weight | Notes |
70
99
  |---|---|---|
71
- | Display headings | **700 900** | Bold commands attention |
72
- | Page headings | **600 700** | Strong but not display-level |
73
- | Section headings | **500 600** | Distinguish from body |
100
+ | Display headings | **700 - 900** | Bold commands attention |
101
+ | Page headings | **600 - 700** | Strong but not display-level |
102
+ | Section headings | **500 - 600** | Distinguish from body |
74
103
  | Body text | **400** | Regular - no emphasis weight |
75
104
  | UI labels | **500** | Slightly heavier than body |
76
105
  | Captions | **400** | Regular - size reduces emphasis |
77
- | Monospace code | **400 500** | |
106
+ | Monospace code | **400 - 500** | |
78
107
 
79
108
  **Rule**: Never use `font-weight: 300` (light) on small text. It becomes illegible below 16px.
80
109
 
@@ -127,7 +156,7 @@ Enforce with `max-width`: `65ch` for body containers works with any font size.
127
156
 
128
157
  **All caps body text** - reserved for: labels, badges, category markers, short UI labels only. Never for sentences or paragraphs.
129
158
 
130
- **Inconsistent tracking** - only use `letter-spacing` intentionally. Positive tracking on uppercase labels is fine. Negative tracking on small body text reduces readability. Random tracking changes across components signal lack of system.
159
+ **Inconsistent tracking** - only use `letter-spacing` intentionally. Positive tracking on uppercase labels is fine. Negative tracking on small body text reduces readability.
131
160
 
132
161
  ---
133
162
 
@@ -136,8 +165,8 @@ Enforce with `max-width`: `65ch` for body containers works with any font size.
136
165
  | Use case | letter-spacing |
137
166
  |---|---|
138
167
  | Body text | `0` (default) |
139
- | Uppercase labels / badges | `0.05em 0.1em` |
140
- | Display headings | `−0.02em 0.01em` |
168
+ | Uppercase labels / badges | `0.05em - 0.1em` |
169
+ | Display headings | `-0.02em - 0.01em` |
141
170
  | Monospace code | `0` or slight positive |
142
171
 
143
172
  ---
@@ -174,68 +203,19 @@ Test: Can you tell which element is a heading just from the weight/family, witho
174
203
 
175
204
  ## Brand Archetype Quick Guide
176
205
 
177
- Pick the archetype closest to the project brief; use the recommended pairing
178
- as a starting point (adjust for specific constraints).
179
-
180
206
  | Archetype | Character | Recommended Pairing |
181
207
  |-----------|-----------|---------------------|
182
208
  | SaaS / productivity | clear, neutral, utilitarian | Inter (UI) + Inter (body) - single family |
183
209
  | Consumer / editorial | warm, opinionated, expressive | Fraunces or GT Sectra (display) + Inter (body) |
184
210
  | Enterprise / finance | authoritative, conservative | IBM Plex Sans (UI) + IBM Plex Serif (body) |
185
211
  | Developer tools | technical, efficient | Geist (UI) + Geist Mono (code) |
186
- | Bold / expressive | high-energy, distinctive | Söhne or Mona Sans (display) + Inter (body) |
187
-
188
- **Selection heuristic:** If the brief uses words like "professional", "trustworthy", "clean" → SaaS or Enterprise. If "warm", "editorial", "narrative" → Consumer. If "bold", "energetic", "distinctive" → Bold. If "technical", "efficient", "fast" → Dev tools.
189
-
190
- ---
191
-
192
- ## Variable Fonts
193
-
194
- Variable fonts expose typographic axes that can be animated or set per-context
195
- via `font-variation-settings`. Prefer variable fonts over static family fallbacks
196
- when available - one file covers all weights and widths.
197
-
198
- ### Common axes
199
-
200
- | Axis | Range | Purpose |
201
- |------|-------|---------|
202
- | wght | 100–900 | Weight (Thin → Black) |
203
- | wdth | 50%–150% | Width (Condensed → Extended) |
204
- | ital | 0 / 1 | Italic toggle (discrete in most) |
205
- | opsz | font-size value | Optical size (auto-applies when `font-optical-sizing: auto`) |
206
-
207
- ### @font-face format
208
-
209
- ```css
210
- @font-face {
211
- font-family: 'InterVariable';
212
- src: url('/fonts/InterVariable.woff2') format('woff2-variations');
213
- font-weight: 100 900;
214
- font-style: normal;
215
- }
216
- ```
217
-
218
- ### Usage via font-variation-settings
219
-
220
- ```css
221
- .heading { font-variation-settings: "wght" 700, "opsz" 32; }
222
- .body { font-variation-settings: "wght" 400; }
223
- ```
224
-
225
- ### Fallback strategy
226
-
227
- Always include a non-variable fallback of the same family in the font stack:
228
-
229
- ```css
230
- font-family: 'InterVariable', 'Inter', -apple-system, system-ui, sans-serif;
231
- ```
212
+ | Bold / expressive | high-energy, distinctive | Sohne or Mona Sans (display) + Inter (body) |
232
213
 
233
- **See:** [`./i18n.md`](./i18n.md) §Multi-Script Font Stacks for when a multi-script project should ship weighted-static fonts rather than a single variable font (some scripts ship as static-only families; mixing scripts in a single variable file is rarely viable).
214
+ **Selection heuristic:** If the brief uses words like "professional", "trustworthy", "clean" use SaaS or Enterprise. If "warm", "editorial", "narrative" use Consumer. If "bold", "energetic", "distinctive" use Bold. If "technical", "efficient", "fast" use Dev tools.
234
215
 
235
216
  ---
236
217
 
237
218
  ## Micro-Typography
238
- Source: jakubkrehel/make-interfaces-feel-better (MIT) - typography.md
239
219
 
240
220
  ### text-wrap
241
221
 
@@ -254,7 +234,7 @@ Apply antialiasing at root level only:
254
234
  -moz-osx-font-smoothing: grayscale;
255
235
  }
256
236
  ```
257
- Never apply per-element - this creates inconsistency within a single text block and is one of the most common micro-typography mistakes. The antialiased value makes text appear slightly thinner/lighter, which is generally preferred for UI type at modern screen densities.
237
+ Never apply per-element - this creates inconsistency within a single text block.
258
238
 
259
239
  ### Tabular numerals
260
240
 
@@ -262,52 +242,4 @@ Use tabular-nums on any surface where numbers change dynamically or need to alig
262
242
  ```css
263
243
  .counter, .price, .timer, .table-cell { font-variant-numeric: tabular-nums; }
264
244
  ```
265
- Proportional numerals (the default) cause text to shift width when numbers change, creating a distracting jitter in timers and prices. Exception: Inter's `1` character widens slightly with tabular-nums - test at your numeric composition before committing.
266
-
267
- ## Font Pairings Catalog
268
- Source: nextlevelbuilder/ui-ux-pro-max-skill (MIT) - data/typography.csv
269
-
270
- 57 professionally curated pairings grouped by use-case vertical. For font loading, see `reference/data/google-fonts.csv` for the full 1923-font reference.
271
-
272
- ### SaaS / Productivity
273
- - **Inter + Inter** (mono weight hierarchy) - The "safe default" for app UIs. Use regular (400) for body, medium (500) for labels, semibold (600) for headings. Consistent x-height, excellent at small sizes.
274
- - **Inter + JetBrains Mono** - For dev tools and dashboards with code display. JetBrains Mono has excellent legibility at 12-14px.
275
- - **Geist Sans + Geist Mono** - Vercel's pair; clean, modern, designed together.
276
- - **Outfit + DM Mono** - Friendly SaaS feel with clear code fallback.
277
-
278
- ### Consumer / Marketing
279
- - **Satoshi + Cabinet Grotesk** - High-energy, modern consumer feel.
280
- - **Plus Jakarta Sans + Syne** - Playful but legible; works for creative consumer apps.
281
- - **DM Sans + DM Serif Display** - Classic pairing; editorial headers, clean body.
282
- - **Nunito + Source Code Pro** - Approachable and friendly.
283
-
284
- ### Finance / Enterprise
285
- - **IBM Plex Sans + IBM Plex Mono** - Authoritative, systematic, designed for data-heavy interfaces.
286
- - **Source Sans 3 + Source Code Pro** - Adobe's workhorse pair; widely trusted.
287
- - **Lato + Roboto Mono** - Clean, neutral enterprise pair.
288
-
289
- ### Editorial / Publishing
290
- - **Playfair Display + Source Serif 4** - High contrast serif headers with readable body serif.
291
- - **Cormorant Garamond + Proza Libre** - Elegant luxury/editorial tone.
292
- - **Libre Baskerville + Libre Franklin** - Free-license editorial pair.
293
- - **EB Garamond + Lato** - Classic print feel with modern body.
294
-
295
- ### Wellness / Health
296
- - **Nunito + Nunito Sans** - Soft, approachable, consistent x-height.
297
- - **Quicksand + Work Sans** - Rounded, friendly, healthcare-appropriate.
298
- - **Raleway + Open Sans** - Clean and welcoming.
299
-
300
- ### Dev Tools
301
- - **JetBrains Mono + Inter** - Code-first, UI-second; natural for developer tools.
302
- - **Fira Code + Fira Sans** - Cohesive family; ligatures available.
303
- - **Cascadia Code + Segoe UI** - Microsoft's modern dev pair.
304
-
305
- ### Luxury / Fashion
306
- - **Cormorant + Montserrat** - High contrast serif + geometric sans; classic luxury.
307
- - **Bodoni Moda + Jost** - Fashion editorial feel.
308
- - **Playfair Display + Raleway** - Elegant header + clean body.
309
-
310
- ### Gaming / Entertainment
311
- - **Syne + DM Sans** - Bold, energetic headers; clean readable body.
312
- - **Bebas Neue + Open Sans** - Impact headlines; neutral body.
313
- - **Exo 2 + Roboto** - Futuristic but readable.
245
+ Proportional numerals (the default) cause text to shift width when numbers change, creating jitter in timers and prices. Exception: Inter's `1` character widens slightly with tabular-nums - test at your numeric composition before committing.
@@ -0,0 +1,60 @@
1
+ # UX Writing - Domain Index
2
+
3
+ This is the domain entry-point for UX writing. Load this file when the task
4
+ involves voice, tone, or the copy standards that govern text a user reads in
5
+ the product: button labels, error messages, empty states, onboarding copy, and
6
+ microcopy. It does not cover the engineering side of localization (see
7
+ `reference/responsive.md` for string expansion budgets and `Intl.*` APIs); it
8
+ does not cover label position in forms or information architecture (see
9
+ `reference/interaction.md`).
10
+
11
+ ---
12
+
13
+ ## Fragment Index
14
+
15
+ → **`reference/brand-voice.md`** (Phase 24) - use when establishing or
16
+ auditing tone: 6 voice axes (Formal-Casual, Serious-Playful, etc.), 12
17
+ archetypes, tone-by-context matrix, industry-context palettes
18
+
19
+ → **`reference/style-vocabulary.md`** (Phase 24) - use when confirming an
20
+ aesthetic direction has a compatible voice register: the `Avoid For` column
21
+ cross-checks brand fit; `Best For` confirms product-type alignment
22
+
23
+ → **`reference/anti-patterns.md`** - use when auditing existing copy: the
24
+ BAN/SLOP grep patterns surface copy violations (AI-tells, filler phrases)
25
+ alongside visual ones
26
+
27
+ ---
28
+
29
+ ## Rules of Thumb
30
+
31
+ **1. Voice axes are independent from archetypes.**
32
+ Pick a position on each of the 6 axes first (Formal-Casual, Serious-Playful,
33
+ Authoritative-Supportive, and so on), then layer an archetype on top. The
34
+ archetype does not determine the axis position - they are orthogonal dimensions
35
+ in `reference/brand-voice.md`.
36
+
37
+ **2. Never use placeholder text as the sole label for a form field.**
38
+ It disappears on focus, fails WCAG 1.3.5, and collapses under translation.
39
+ Top-aligned labels are the only position that absorbs localization expansion
40
+ without breaking layout.
41
+
42
+ **3. Error messages follow a three-clause structure: what happened, why, what
43
+ to do.**
44
+ State the event in past tense, explain the cause in one clause, give a present
45
+ tense imperative for recovery. No tech jargon. Never blame the user.
46
+
47
+ **4. Shift toward supportive tone during error, empty-state, and onboarding
48
+ moments.**
49
+ Even a formally-voiced product should move on the axis toward supportive when
50
+ the user is at risk of abandonment. The per-context tone matrix in
51
+ `reference/brand-voice.md` provides the exact adjustment.
52
+
53
+ ---
54
+
55
+ ## Cross-Domain See Also
56
+
57
+ - String expansion budgets constrain copy length: `reference/responsive.md`
58
+ - Error copy paired with form patterns: `reference/interaction.md`
59
+ - Empty-state copy pairs with onboarding pattern: `reference/interaction.md`
60
+ - Style direction voice register cross-check: `reference/style-vocabulary.md`
@@ -0,0 +1,59 @@
1
+ // Phase 44 - harness freshness (shippable pure module). Read by health-mirror + check-harness-freshness.cjs.
2
+ 'use strict';
3
+
4
+ const { readHarnesses } = require('./manifest/index.cjs');
5
+
6
+ const WARN_DAYS = 60;
7
+ const FAIL_DAYS = 180;
8
+ const MS_PER_DAY = 86400000;
9
+
10
+ /**
11
+ * Returns the age of a last_verified timestamp in fractional days.
12
+ * Returns Infinity when the timestamp is absent or unparseable.
13
+ * @param {string|null|undefined} last_verified
14
+ * @param {number} nowMs
15
+ * @returns {number}
16
+ */
17
+ function ageInDays(last_verified, nowMs) {
18
+ if (!last_verified) return Infinity;
19
+ const t = Date.parse(last_verified);
20
+ if (!Number.isFinite(t)) return Infinity;
21
+ return (nowMs - t) / MS_PER_DAY;
22
+ }
23
+
24
+ /**
25
+ * Evaluate freshness for every harness in the manifest (or a supplied list).
26
+ *
27
+ * STATUS-AWARE (D-04): only `tested` harnesses can warn/fail on a stale
28
+ * last_verified date. experimental / untested / known-broken harnesses
29
+ * make no freshness promise — their freshness is always 'n/a'.
30
+ *
31
+ * @param {{ nowMs?: number, harnesses?: object[] }} [opts]
32
+ * @returns {{ id: string, status: string, last_verified: string|null, age_days: number|null, freshness: string }[]}
33
+ */
34
+ function checkFreshness({ nowMs, harnesses } = {}) {
35
+ const list = harnesses || readHarnesses().harnesses || [];
36
+ const now = typeof nowMs === 'number' ? nowMs : Date.now();
37
+
38
+ return list.map((h) => {
39
+ const status = (h.capability_matrix && h.capability_matrix.status) || 'untested';
40
+ const age = ageInDays(h.last_verified, now);
41
+
42
+ // Only `tested` harnesses carry a freshness obligation.
43
+ // All other statuses → 'n/a' (never a build failure).
44
+ let freshness = 'n/a';
45
+ if (status === 'tested') {
46
+ freshness = age >= FAIL_DAYS ? 'fail' : age >= WARN_DAYS ? 'warn' : 'ok';
47
+ }
48
+
49
+ return {
50
+ id: h.id,
51
+ status,
52
+ last_verified: h.last_verified || null,
53
+ age_days: Number.isFinite(age) ? Math.floor(age) : null,
54
+ freshness,
55
+ };
56
+ });
57
+ }
58
+
59
+ module.exports = { checkFreshness, ageInDays, WARN_DAYS, FAIL_DAYS };
@@ -51,6 +51,7 @@ const fs = require('node:fs');
51
51
  const path = require('node:path');
52
52
 
53
53
  const { getDisableReason } = require('../issue-reporter/kill-switch.cjs');
54
+ const { checkFreshness, WARN_DAYS, FAIL_DAYS } = require('../harness-freshness.cjs');
54
55
 
55
56
  function fileExists(p) {
56
57
  try {
@@ -211,6 +212,32 @@ async function getHealthChecks(rootDir) {
211
212
  checks.push({ name: 'skill_discipline', status, detail });
212
213
  }
213
214
 
215
+ // 8. harness_freshness — per-harness last_verified age (Phase 44). PURE: reads the manifest SoT via the
216
+ // shippable harness-freshness module; status-aware (only `tested` harnesses can warn/fail). NEVER throws.
217
+ {
218
+ let status = 'ok';
219
+ let detail;
220
+ try {
221
+ const results = checkFreshness();
222
+ const failing = results.filter((r) => r.freshness === 'fail');
223
+ const warning = results.filter((r) => r.freshness === 'warn');
224
+ const tested = results.filter((r) => r.status === 'tested');
225
+ if (failing.length) {
226
+ status = 'fail';
227
+ detail = `harness freshness: ${failing.length} stale (>${FAIL_DAYS}d): ${failing.map((r) => r.id).join(', ')}`;
228
+ } else if (warning.length) {
229
+ status = 'warn';
230
+ detail = `harness freshness: ${warning.length} aging (>${WARN_DAYS}d): ${warning.map((r) => r.id).join(', ')}`;
231
+ } else {
232
+ detail = `harness freshness: ${tested.length} tested harness(es) current`;
233
+ }
234
+ } catch {
235
+ status = 'warn';
236
+ detail = 'harness freshness: unavailable';
237
+ }
238
+ checks.push({ name: 'harness_freshness', status, detail });
239
+ }
240
+
214
241
  return { checks };
215
242
  }
216
243