How to change anything in the Strategic Plan, and what to run afterwards.
Written for someone editing the files rather than maintaining the LaTeX. You do not need to understand the macros — only where each kind of content lives and what shape the line takes.
1. Numbers come from Excel; everything else comes from the
.tex files. The document never opens the workbook.
export_metrics reads the workbook and writes
data/metrics.tex, and the document reads that. If you
change a number and do not run the export, the PDF keeps the old one and
nothing warns you at the time.
2. Never edit a generated file.
data/metrics.tex is rewritten on every export, and columns
A–C of the workbook are rewritten by sync_metrics --fix.
Anything you type there is lost.
| To change | Edit |
|---|---|
| A metric’s number | the workbook on I: |
| A metric’s wording | content/departments/<dept>.tex |
| A milestone’s wording or date | content/departments/<dept>.tex |
| A strategic detail line | content/departments/<dept>.tex |
| A strategy’s wording or owners | content/strategies.tex |
| A goal’s title or narrative | content/pillars-and-goals.tex |
| A pillar’s wording | content/pillars-and-goals.tex |
| Board priorities | config/board-priorities.tex |
| The plan years | config/plan-window.tex |
| Director, portfolio counts | config/agency.tex |
| Moving content between departments | see section P |
| Adding or renaming a department | config/departments.tex |
| Hiding a metric | config/suppressed-metrics.tex |
| Mission, vision, core values | content/about.tex |
| “The Strategic Plan” narrative | content/the-plan.tex |
| Appendix B | content/appendix-b.tex |
| The cover page — photo, panel, spacing | lib/mhplanstyle.sty, “Cover page” |
| The cover photograph itself | assets/cover.jpg |
| Colours, fonts, margins | lib/mhbrand.sty |
Department file names are listed in
config/departments.tex, fourth argument of each
\newdepartment line.
I:\PnR\Special Projects\MaineHousing Strategic Plan\Data Collection - Metrics.xlsxAM.G3.1.reinvest.D Line 1 Label, E Line 1 Value —
e.g. 3yr Avg / 2.72%F/G and H/I for
a second and third lineK Owner, L Last UpdatedJ Preview shows how the cell will print. It is a
formula; do not type over it.$33,085,573, sub-5%,
25 days or less. Do not add backslashes;
the export escapes LaTeX characters for you.A blank value prints a red TBD and raises a build warning, so gaps stay visible rather than silently becoming zero.
The description is in the department file, not the workbook.
Open content/departments/<dept>.tex.
Find the line:
\metric{Subsequent loan proceeds reinvested annually in maintaining
and improving the physical condition of properties.}{AM.G3.1.reinvest}Edit the first braces (the wording). Leave the second (the key) alone — the key is what ties the row to the workbook.
Rebuild. make check will notice that the workbook’s
copy of the wording is now out of date and report it as
STALE; run
python scripts/sync_metrics.py --fix to refresh column
C.
In content/departments/<dept>.tex, add a line
inside the right \rstrategy block:
\metric{Number of new thing counted.}{AM.G3.2.newthing}
Keys follow DEPT.Gn.m.slug — department, goal, strategy,
short name. Metrics appear in the order the lines appear.
Rebuild. The metric prints TBD because the workbook has no row yet.
Add the row to the workbook, either by hand or with
python scripts/sync_metrics.py --fix, which appends any key
the document asks for and the workbook lacks.
Fill in the value, export, rebuild.
Use this when a figure is not ready to publish but you still want the data gathered.
Add the key to config/suppressed-metrics.tex:
\suppressmetric{AM.G3.2.cashflow} % benchmark still being validatedRebuild. No row prints; the \metric line and the
workbook row both stay put.
Do this rather than commenting out the \metric line. A
commented-out metric leaves its workbook row looking like an orphan on
every sync.
Every build reports what is hidden, so nothing stays suppressed by accident:
Metrics suppressed (in the workbook, not printed): 2
-- DEV.G3.1.age15
-- AM.G3.2.cashflow
To bring it back, delete the line.
Two places, in this order:
Delete the \metric{...}{KEY} line from
content/departments/<dept>.tex. Rebuild — this
refreshes the key inventory.
Remove the workbook row:
python scripts/sync_metrics.py --prune # dry run, lists them
python scripts/sync_metrics.py --prune --yes # actually delete
Or delete the rows by hand in Excel; make sync names
them for you.
Doing step 2 first is the mistake to avoid: the row stays in the PDF
and prints TBD, because the \metric line is what puts it on
the page. The build summary, make sync and
make check will all flag it.
Open content/departments/<dept>.tex and find
the line:
\milestone{Refine and implement a portfolio risk rating tool.}{Dec}{2026}
Three parts: text, month, year.
Edit whichever you need. Month is free text — Dec,
Sept, Mar.
Two variants exist:
\milestonetarget{Establish a cohort of Hub Coordinators.}{Jun}{2026}
\milestoneundated{Report results of audits to the Board of Commissioners.}
\milestonetarget prints “Target:” instead of
“Expected:”. \milestoneundated prints no date at all.
Milestone dates do not roll forward with the plan window — by design. When you advance the years, a milestone dated before the new window start is flagged in the build summary so you can retire it, re-date it, or report it complete. See N.
Add or delete the whole \milestone{...}{...}{...} line
inside the right \rstrategy block. Nothing else to update —
milestones have no workbook rows. make milestones will show
the new inventory.
Detail lines are the prose bullets under each strategy.
\detail{Increase inspection frequency for projects deemed high risk due
to physical plant concerns.}
Edit, add or delete whole \detail{...} lines. They print
in order.
Two variants:
\detaillist{Host regular meetings with partner organizations to:}{%
\item Maintain open dialogue about program requirements.
\item Address relevant current events and policy concerns.}
\detailseealso{DEV,PNR}
\detaillist is a detail line with a bulleted list
beneath it, both in the same cell so no rule separates the bullets from
the line that introduces them. Use it wherever a detail ends in a colon.
(An older \detailitems, which put the list in a row of its
own, still builds but is no longer used.) \detailseealso
prints “Additional department details in Development and Planning &
Research.” with both linked — give it department codes, and the commas
and “and” are handled for any number.
To point at a strategy elsewhere in the plan, inside prose:
\detail{Access, and support advocacy (see strategy
\backlink{strat:7.5}{7.5}), for sufficient funding.}
Strategy wording is declared once and reused by the pillar table and by every roadmap that carries it. Change it here and it changes everywhere.
Open content/strategies.tex.
Find the line:
\newstrategy{3.1}{Maintain and improve the condition, accessibility,
and energy efficiency of multi-family projects.}{AM,DEV}
Three parts: number, wording, owning departments.
Edit the wording, or the department list. The Departments column of the pillar table regenerates itself.
Department codes come from config/departments.tex.
ALL and MULT print “All” and “Multiple” and
link nowhere.
Add. Put a new \newstrategy line in
content/strategies.tex in the position you want it to
appear in the pillar table:
\newstrategy{3.5}{Wording of the new strategy.}{AM}
It appears in the table immediately. To give it departmental detail,
add an \rstrategy{3.5} block to each owning department’s
file.
Retire. Delete the \newstrategy line,
then delete any \rstrategy{3.5} blocks that reference it.
If you miss one the build stops with a named error rather than printing
a blank:
! Package mhplan Error: Strategy 3.5 belongs to goal 3, which is not declared.
Renumbering an existing strategy is the risky edit —
the number is also the link target used by roadmaps and prose
cross-references. If you renumber, search the whole
content/ folder for the old number.
Open content/pillars-and-goals.tex.
Find the goal:
\newgoal{3}{2}
{Ensure the long-term sustainability of MaineHousing's multifamily
portfolio.}
{MaineHousing oversees more than \mfpropertycount{} multifamily
properties that house \mfhouseholdcount{} Maine households. ...}
Four parts: goal number, pillar number, title, narrative paragraph.
Edit the title or the narrative. The title appears in the pillar section, in every roadmap that carries the goal, and in the PDF bookmarks — all from this one place.
To add a goal, add a \newgoal block
after the pillar it belongs to. The section, heading, narrative,
strategy table and bookmark all follow; main.tex needs no
change. Declare the pillar before its goals or the build stops with a
named error.
Same file, above its goals:
\newpillar{2}{To preserve and improve the quality of housing in Maine.}
{Preserve and Improve the Quality of Housing in Maine}
Three parts: number, the mission statement used in the Four Pillars summary, and the section heading that follows “Pillar Two —”.
Set annually, so this is revised on the same cycle as the plan window.
Open config/board-priorities.tex.
Replace the priorities:
\newboardpriority{Advocacy}{Be proactive in our advocacy for
legislation and public policies that create efficiencies.}
They print in the order declared.
Move the previous year’s set into the archive at the foot of the file, commented out, so the sequence of Board direction stays readable.
The heading year comes from \setboardyear in
config/plan-window.tex — no change needed here.
Optionally name the strategies a priority bears on:
\newboardpriority[8.3,8.4]{Creativity}{Focus both externally and ...}
which appends “(see strategies 8.3 and 8.4)”, hyperlinked. Leave the bracket off and nothing is added.
In config/plan-window.tex, change one line:
\setplanwindow{2027}
That moves the cover date, the Board priorities heading, and Appendix B’s reference to the next edition.
Update config/board-priorities.tex — see
M.
Rebuild and read the summary:
Milestones dated before 2027: 101
Then run make milestones for the list by department. For
each: retire it, re-date it, or report it complete.
Update the numbers in the workbook and export.
Review strategies and departmental detail.
Run make check, then make sync, then
rebuild.
When publishing, set \reviewmodeoff in
config/plan-window.tex so review markers
disappear.
config/agency.tex:
\newcommand{\directorname}{Daniel Brennan}
\newcommand{\mfpropertycount}{850}
\newcommand{\mfhouseholdcount}{24,000}
These are used in the front matter and in Goal 3’s narrative, so a changed figure updates everywhere it appears.
When a strategy and its detail, metrics and milestones transfer between departments, four things need attention. Strategy numbers never change — 9.1 is 9.1 whoever runs it — so only the department changes.
Cut the \rstrategy block and everything under it from
the old department’s file in content/departments/ and paste
it into the new one, in the right \deptgoal section. Detail
lines, \detaillist, \detailseealso and
milestones all move with it unchanged and need no further edit.
The easiest step to forget.
content/strategies.tex lists the owning departments:
\newstrategy{1.1}{Offer a mortgage product that is highly competitive.}{HO,TRE}
Remove the old department, add the new one. Miss this and the Pillar
table credits one department while the roadmap shows another — so
check.R now tests for it:
[FAIL] roadmap blocks match strategy ownership HO has a block for 1.1, owned by TRE
Metric keys carry the department: FIN.G1.6.delinquency
becomes HO.G1.6.delinquency. Rename the key in
place; do not add and delete.
FIN. with HO. within
the selection. The row never moves, so values, labels, Owner,
Last Updated and Notes all travel with it.--fix only fills that in on rows it appends, so a renamed
row keeps the old department until you change it.source("scripts/sync_metrics.R") — missing, orphaned
and stale should all report none.Why not --fix then
--prune: that treats a move as a deletion plus an
unrelated addition. The new rows arrive blank and the populated ones are
deleted, so every value, owner, date and note is lost:
HO.G1.3.setuptime L1=None: None <- new, blank
FIN.G1.3.setuptime deleted, with its values
Search content/departments/ for
\detailseealso{FIN} and for
\backlink{strat:...} mentioning the moved strategies. These
still compile if left stale — they just point somewhere misleading, and
no check will tell you.
source("scripts/export_metrics.R")
source("build.R"); build_plan()
source("scripts/check.R")
Milestones need nothing beyond moving the lines: they live only in
the .tex files and have no workbook rows. The department’s
roadmap colour also needs nothing — it follows registry position, not
content.
config/departments.tex:
\newdepartment{AM} {Asset Mgmt} {Asset Management} {asset-management}
Four parts: code, short label for the Departments column, full name
for the roadmap heading, and the file name in
content/departments/.
content/departments/<file>.tex. Appendix A is
generated from this list, so nothing else changes.| Mission, vision, core values, the Director quote | content/about.tex |
| “The Strategic Plan” and “The Four Pillars” narrative | content/the-plan.tex |
| Appendix B | content/appendix-b.tex |
Ordinary prose — edit the words.
Labels like Mission, Vision,
Core Values and 2026 Board of Commissioners’
Priorities are bold paragraphs, not sectioning commands.
Nothing in LaTeX protects them: one can be left alone at the bottom of a
page with its content overleaf. A real \section is safe,
because LaTeX pins a heading to the first line that follows it — an
ordinary bold paragraph gets no such treatment.
Two commands in lib/mhbrand.sty supply it by hand:
\mhminorheading{2026 Board of Commissioners' Priorities} % own paragraph
\mhminorheadingrunon{Mission} % text on the next line
Each refuses to start unless enough of the page remains, and keeps
the label attached to whatever follows. Use them for any new bold label
rather than a bare \textbf{...}.
A goal heading, its narrative and the opening of its strategy table are bound into one unit, so the whole thing moves to the next page rather than leaving a heading stranded. Three things make that hold, and all three are needed:
\nopagebreak after the heading, binding it to the
narrative;\interlinepenalty inside the narrative, so TeX cannot
break within it — without this the break simply moves one line up;\mhstrategykeeprows (default 2) rows held with it, so the
table cannot open with a bare header.This replaced a fixed \needspace, which could only ever
be right for one combination of narrative length and first-row height.
Stress-tested by inflating every narrative until the page count rose
from 8 to 12: no heading was separated from its table at any length.
Strategic Goal n: in the pillar sections and
Goal #n: in the department roadmaps carry the same
protection, with a larger reserve — each is followed by a narrative
paragraph and the head of a table:
\renewcommand{\mhgoalheadinglines}{8} % default 8
These stay \subsections rather than becoming bold
paragraphs, because that is what generates their PDF bookmarks and the
goal:n targets the roadmaps link back to. Converting them
to \mhminorheading would look identical and silently lose
both.
It is followed by something taller than the guard allows for. Raise the relevant reserve:
\renewcommand{\mhminorheadinglines}{8} % default 5
\renewcommand{\mhgoalheadinglines}{10} % default 8
\mhminorheadinglines at 5 is enough for a
label plus the first few lines of a paragraph or list. Raising either
too far just pushes headings onto the next page unnecessarily, so go up
in ones and twos and rebuild.
The Mission / Vision / Core Values block is in
content/about.tex:
\begin{corevalues}
\corevalue{integrity}{Integrity and Accountability}
\end{corevalues}
The first argument names the icon file,
assets/value-integrity.png; the second is the label. With
no icon file present a plain outlined square stands in, so the page
still builds.
The icon sits in a fixed-width box, which is what
keeps every label starting at the same place no matter how wide the
individual icons are. Four lengths in lib/mhbrand.sty
control the row:
\setlength{\mhcorevalueindent}{28pt} % left indent of the whole row
\setlength{\mhcorevalueiconbox}{28pt} % width reserved for the icon
\setlength{\mhcorevalueiconsize}{22pt} % icon height
\setlength{\mhcorevaluegap}{12pt} % icon to label
If the labels look ragged down the left, the icon
box is doing its job but something else moved; if an icon looks
squashed, it is wider than the box and has been scaled down to fit.
Raise \mhcorevalueiconbox — the labels stay aligned, they
just move right together. Icons narrower than the box are untouched and
keep their full height.
A strategy block splits across a page break freely, provided enough
of its rows land on each side. Two numbers in
lib/mhtables.sty define “enough”, near the top of the
section headed Pagination of strategy blocks:
\newcommand{\mhblockminfirst}{2} % rows required on the page it starts on
\newcommand{\mhblockminlast}{2} % rows required on the page it continues onto
If neither side can be satisfied, the whole block moves to the next page.
Measured over the 124 blocks in the current plan:
| Setting | Rows stranded alone | Appendix pages with >150pt unused |
|---|---|---|
2 / 2 |
0 | 18 |
2 / 1 |
7 | 17 |
1 / 1 |
11 | 16 |
Stricter settings cost whitespace; looser ones pack pages tighter and
leave single rows adrift. 2/2 is set.
Two things to know if you change them:
.aux file, written on one
pass and read on the next, because TeX cannot know how many rows a block
has until it has set them. A first run from clean therefore applies only
the minfirst half; the second pass settles it.
make runs enough passes, but a single bare
latexmk on a cleaned tree may not — use
make force if a break looks wrong.2 will start pushing
whole blocks onto fresh pages and cost pages rather than tidying
them.Not every large gap in the appendix is caused by this rule. Some blocks sit on a page with room to spare and still move over whole for reasons outside these two numbers.
Everything on the cover is a named length set at the
top of the cover section in lib/mhbrand.sty, under the
comment “Cover page”. Change the numbers there; do not go hunting inside
the picture itself.
assets/cover.jpg.make force — see the
warning below.If the file is absent the cover still builds, with a plain MH Blue block where the photo would be. Nothing breaks; it just looks unfinished.
The photo is never distorted. It is scaled to cover the photo band and the surplus is cropped, the way a web banner behaves — so any proportions work. A wide panorama is scaled to the band’s height and cropped left and right; a tall portrait is scaled to the page width and cropped top and bottom.
For a photo that fills the band with no cropping at all, supply it at the band’s proportions: 8.5 × 5.45 inches, i.e. 2550 × 1635 px at 300 dpi. Anything at least that large is fine; smaller will look soft in print.
When a photo is cropped, by default you see its top-left. Two lengths pan the crop:
\setlength{\mhcoverphotoshiftx}{2in} % pan right
\setlength{\mhcoverphotoshifty}{1.2in} % pan down
Positive values move right and down through the
picture, towards its far corner. 0pt on both is the
top-left. Work in small steps and rebuild; a tenth of an inch is
visible.
Only the axis that is actually cropped can be panned, and which one that is depends on the photo:
shiftx pans it and shifty has nothing to
move;shifty pans
it and shiftx has nothing to move.Panning an axis with no surplus slides the photo off its band and
leaves a white gap down one edge. If you see one, you are using the
wrong axis for that photo — set it back to 0pt.
Three lengths interact down the page: the photo is clipped at
\mhcoverphotoheight, the slate band starts at
\mhcoverbandtop, and anything between them shows as a white
stripe.
The band is painted over the photo, so it always
wins. The shipped composition uses that deliberately: the photo is
clipped at 6.0in but the band starts at 5.5in,
so the photo runs behind the band and there is no white
stripe — the two meet flush at 5.5in. Measured on the built
cover, photo 0–5.50in, slate 5.50–11.00in.
photoheight below bandtop
leaves a white stripe between them.photoheight at or above
bandtop closes it. The photo stops where the band
starts.photoheight further changes nothing visible; it
only adds headroom for panning (below).So to make the photo cover more of the page, move
\mhcoverbandtop down, not
\mhcoverphotoheight up.
\mhcoverphotoshifty slides the image up behind the clip.
For the photo still to reach the band, the image — scaled to the full
page width — must be at least bandtop + shifty tall. At the
shipped 5.5in and 55pt that is
6.26in, i.e. about 1880px at 300dpi for an 8.5in-wide
photo.
If it is shorter, its bottom edge rises above the band and a white
stripe appears across the page. That is also what happens when
assets/cover.jpg is missing entirely: the stand-in block is
exactly photoheight tall, so any downward pan exposes a gap
under it. A white stripe on a cover with no photo in it is this, not a
misconfiguration.
| Length | Default | Controls |
|---|---|---|
\mhcoverphotoheight |
6.0in |
how far down the page the photo reaches, up to
\mhcoverbandtop |
\mhcoverphotoshiftx |
0pt |
pans a cropped photo right |
\mhcoverphotoshifty |
55pt |
pans a cropped photo down |
\mhcoverbandtop |
5.5in |
where the slate band begins, measured from the top of the page; also the hard ceiling for the photo |
\mhcoverpanelx |
0.52in |
white panel, distance from the left edge |
\mhcoverpanely |
2.7in |
white panel, distance from the top edge |
\mhcoverpanelwidth |
5.15in |
white panel width |
\mhcoverpanelheight |
6.50in |
white panel height |
Inside the panel, top to bottom:
| Length | Default | Controls |
|---|---|---|
\mhcovertextwidth |
4.55in |
text column width; the difference from the panel width is the left inset |
\mhcovergaptop |
0.35in |
space above the logo |
\mhcoverlogoheight |
0.42in |
logo height |
\mhcovergaptitle |
0.42in |
logo to title |
\mhcovergaprule |
0.85in |
title to green rule |
\mhcoverrulewidth |
4.2in |
length of the green rule |
\mhcoverrulethick |
3pt |
thickness of the green rule |
\mhcovergapdate |
0.30in |
rule to the date line |
Two more, not lengths:
\newcommand{\mhcovertitlesize}{\fontsize{40}{48}\selectfont}
\newcommand{\mhcovertitle}{MaineHousing\\ Strategic Plan}
\mhcovertitlesize takes a size and a baseline separation
— keep the second roughly 1.25 times the first.
\mhcovertitle is the wording; \\ is the line
break. The date line underneath comes from \planwindow and
follows \setplanwindow, so it needs no editing.
The logo is assets/logo.png, sized by
\mhcoverlogoheight. Absent, the cover falls back to a
typeset wordmark.
Rebuilding all 57 pages to look at one page is slow. Comment out
everything after the cover in main.tex temporarily, or
build the external edition (make external, 8 pages) — the
cover is identical in both.
Always use make force while adjusting the
cover. Adding or replacing a file in assets/ does
not change any file latexmk tracks, so an ordinary make
will report everything up to date and leave you looking at the old
cover. This will waste your time at least once otherwise.
Whatever you changed, this is the loop.
With make:
make export the workbook, then build -> build/main.pdf
make check verify everything
RStudio:
source("build.R")
build_plan() # export metrics, then build
source("scripts/check.R") # verify -- the same fourteen checks as `make check`
Only changed .tex files, no workbook
edits? make pdf skips the export and is quicker.
When in doubt run make — the export is cheap.
Other commands:
make sync |
workbook vs document: missing, orphaned, stale, unrefreshed |
make milestones |
milestone inventory: overdue, due this year, beyond |
make extracts |
a review PDF per department, into build/extracts/ (see
below) |
make external |
the external edition, into build/external.pdf |
make force |
rebuild ignoring the cache |
make clean |
remove build artefacts |
R equivalents, all runnable from the RStudio console:
source("scripts/sync_metrics.R"),
source("scripts/milestones.R"),
source("scripts/check.R"), build_extracts(),
build_plan_external(). Nothing routine requires leaving
RStudio.
=== MaineHousing Strategic Plan: review summary ===
Plan window ....... January 2026 to December 2028
Metric values as of 31 July 2026
Metrics with no workbook value: 1
Metrics suppressed (in the workbook, not printed): 0
Milestones dated before 2026: 0
“Metric values as of” is the date the export last ran. If it is older than your workbook edit, the export did not run and the PDF has stale numbers.
A review packet for one department: the same source, the same numbers, the same styling, restricted to that department’s roadmap. Four to six pages instead of fifty-seven, for sending a director their own section.
make extracts all fourteen
python scripts/extracts.py AM DEV just these
python scripts/extracts.py --list show the department codes
From RStudio, needing no Python at all:
source("build.R")
build_extracts() # or build_extracts(c("AM", "DEV"))
Output lands in build/extracts/, named for the
department — Asset Management roadmap 2026.pdf. The year
follows \setplanwindow.
make external builds an outward-facing copy — no
appendices, no Departments column — from the same source. You do not
maintain it separately: edit the plan as usual and rebuild both.
The only thing to keep in mind is that anything you put in a department roadmap (strategic detail, metrics, milestones) appears only in the internal edition. Content that must reach an external reader belongs in a strategy, a goal narrative, or the front matter.
In .tex files these need a backslash:
| Type | Write |
|---|---|
% |
\% |
& |
\& |
$ |
\$ |
# |
\# |
_ |
\_ |
So: Maintain costs at a maximum of 5\%. and
Energy \& Housing Services.
In the Excel workbook, do not escape anything. Type
2.72% and $33,085,573 as you would normally;
the export handles it.
Quotation marks: use `single' and
``double'' — LaTeX turns them into proper curly quotes. A
straight " gives the wrong glyph.
| Message | Meaning |
|---|---|
Strategy 3.5 belongs to goal 3, which is not declared |
Strategy number points at a goal that does not exist — check
content/pillars-and-goals.tex |
Goal 11 is assigned to pillar 7, which has not been declared |
Same, one level up |
No workbook value for metric key 'X' |
The document asks for a metric the workbook lacks — add the row, or you forgot to export |
\suppressmetric{X} refers to a key no \metric{} uses |
A suppression outlived its metric; delete the line |
Undefined control sequence |
Usually a mistyped command, or an unescaped
%/&/$ |
Misplaced \omit / Misplaced \noalign |
A table row macro was used outside a roadmap
environment, or a new one was written incorrectly — see the warning at
the head of the row-macro section in lib/mhtables.sty |
metrics export up to date FAIL |
The workbook was saved after the last export; run the export |
no missing characters FAIL |
The font lacks a character being used and is dropping it silently; the check names the character and the face |
roadmap blocks match strategy ownership
FAIL |
A department has a roadmap block for a strategy it is not listed as
owning in content/strategies.tex — usually a move with step
2 forgotten |
make check runs fourteen tests and exits non-zero if any
fail, so it can gate a commit. Run it before you publish anything.