If you only internalize one, make it #1 — the rest matter but are progressively lower-stakes. Rule 7 isn't ranked here because it isn't a standalone action — it's the discipline that keeps every rule below it from silently going stale. See its own card at the end of "The rules" section.
accountOverrides.ts against the Customer Mapping workbook before committing (Rule 2) — gets customer-facing name-matching wrong if skipped.accountOverrides.ts against the Customer Mapping workbook before you commit — see Rule 2. scripts/create-customer-mapping-workbook.js against an already-provisioned environment — it silently wipes the one live Customer Mapping file dev and prod both share. See Rule 3. CLAUDE.md inaccurate (new integration, new major report, new rule, a moved file), update both in the same commit. Treat this as a living reference, not a one-time snapshot — that's Rule 7, and it applies to every item on this list, not just this one. uat.vue's NEW_BADGE_RELEASE_DATES map, not a hardcoded badge with no expiry. See Rule 6. useLazyFetch(..., { server: false }) — not just useLazyFetch — or it silently blocks first paint on a hard load/refresh anyway. Every pending state renders <ProgressPanel>, never a bare "Loading…" string. See Rule 9 and "How to build a new page" below. app/ is the running Nuxt application — everything user-facing lives here. scripts/ holds standalone, interactive, one-off Node CLIs (initial Azure AD app registration, Customer Mapping workbook provisioning) — not imported by the app, not part of any running process. .env at the repo root (sibling to app/ and scripts/, not inside app/), loaded via a hardcoded ../.env path in nuxt.config.ts. If .env ever ends up in the wrong place, every integration silently returns empty data with no error — check this first if reports come back blank across the board. server/utils/, each with an xConfigured() guard, a thin fetch/query wrapper, and (for OAuth ones) internal token caching. Every call from a report endpoint is individually try/caught to fail closed — one integration being down never takes the whole report offline. accountMatch.ts's namesMatch() does normalize + substring/token-subset matching; accountOverrides.ts layers in confirmed, permanent synonyms; the Customer Mapping SharePoint workbook (customerMapping.ts) layers in live, human-confirmed, per-(account, integration) overrides on top of both. See Rule 2 for how the two override systems relate. server/api/, plus the report-generation endpoints under server/api/reports/ — monthly.get.ts is the big one, fanning out to every integration for one customer/month and streaming progress via SSE. A friendly walkthrough of Rule 1/2/4 below, framed as "what to ask, then what happens" — read this first if you're about to wire up a brand-new external system; read the numbered rules afterward for the exact mechanics.
.env, or an OS-level DSN like Sage's)? /reports/customer-mapping? Default yes — skip only if Cornel has explicitly said a source doesn't need it (precedent: 365 Subscriptions Amendments). server/utils/<name>.ts — the client module: an xConfigured() guard, a thin fetch/query wrapper, read-only calls only (Rule 1). Fail closed — every caller elsewhere try/catches to null/empty rather than letting one integration take a whole report down. SourceMatch field and a SourceHealth tile in report-health.get.ts. This is what actually answers "is it live?" — the tile pings the real credential on every page load and shows Connected/Down, and this page's Integrations table and the home page's integration-down badge both pick it up automatically once it's there. agreements.ts's header comment on an earlier version that guessed and misclassified a real document). Decide read-only vs. elevated write the same way sharePointBackup did — Sites.Selected, scoped to the one site that needs it, never a blanket tenant-wide grant. Fold the connectivity check into checkSharePointConnections()'s sub-connections list rather than inventing a new top-level tile, unless it's genuinely a separate site. IntegrationKey union in customerMapping.ts so a human can confirm/override its match from /reports/customer-mapping. Match using matchableNames(), not raw account names, so existing global synonyms apply for free. ApiSecurityEntry to apiSecurity.ts: what kind of credential it is, that the code usage is read-only (per the Rule 1 default), and the credential's real ceiling if you know it (or "unverified" plus what to check in the vendor's console, if you don't — never guess). This is what makes it show up on the API Security Health page. app/pages, app/server, and CLAUDE.md; check the other hardcoded lists (technical.get.ts's missing-integrations banner, customer-mapping.vue's duplicated type, report-health.vue's matrix columns, this page's CLIENT_FILES map, and any free-text prose that names integrations together). monthly.get.ts the same fail-closed way as every existing source. None of this is optional per source — it's the same checklist every time, regardless of how small the integration seems. Skipping a step is exactly how this app's docs and hardcoded lists have gone stale before.
What happens automatically vs. what you still have to do by hand — read this before creating a new app/pages/**/*.vue file; read Rule 9 below for the loading-feedback mechanics specifically.
app/pages/foo.vue into /foo and app/pages/reports/foo.vue into /reports/foo with zero registration step. In npm run dev the route is live on the next request once the file watcher picks it up — no restart. In production it ships on the next build/deploy like any other file. <NuxtLoadingIndicator> in app.vue) applies automatically, same as every other page — see Rule 9. .page / .toolbar / back-link structure and CSS custom properties (--surface-1, --accent, --good, etc.) rather than inventing the shape from scratch; <ProgressPanel> reads those variables from whichever page includes it. uat.vue (or development.vue for a -dev-suffixed sandbox copy) or it's only reachable by typing the URL directly. NEW_BADGE_RELEASE_DATES entry, not a hand-written badge with no expiry.useLazyFetch(..., { server: false }), and every pending state renders <ProgressPanel>, not a bare "Loading…" string. uat.vue/development.vue themselves) does.useHtmlExport composable, no exceptions.None of this is optional per page — a one-off internal tool page still needs Rule 9's loading discipline. Skipping it is exactly how the home-page slow-open bug happened (see Rule 9 below).
Every new integration is read-only by default — no exceptions without explicit sign-off. Even if the credential/API key/token you're given is capable of more (most vendor keys inherit a full account-wide role, or the role of whichever console user generated them — see the "Not Verified" rows on the API Security Health page for how common this is), the code must never call a create/update/delete/write endpoint just because the credential could. Only sharePointBackup has ever been deliberately elevated past this default (customerMapping.ts writing Customer Mapping rows, scoreCard.ts writing Technical Score Card rows) — and even that is scoped to two named SharePoint sites via Sites.Selected, never a blanket grant. Treat that as the bar any future write access needs to clear: named, scoped, justified, and confirmed with Cornel first — never "the key happened to allow it."
A new server/utils/x.ts client, or a new SharePoint list/library/workbook, is not finished on its own. Same commit:
SourceMatch field to NameHealthRow and a SourceHealth tile in report-health.get.ts (use subConnections instead of a second umbrella tile if it's really more than one Graph resource on an existing site, like SharePoint). IntegrationKey union in customerMapping.ts so a human can manually confirm/override its match from /reports/customer-mapping — unless Cornel has said this source doesn't need manual mapping. matchableNames(accountId, accountName), not raw accountName, so existing global synonyms apply for free. ApiSecurityEntry to server/utils/apiSecurity.ts: credentialType, codeUsage (almost always "read-only"), grantedAccess (default "unverified" plus a scopeNote on what to check in the vendor's own console unless you've actually confirmed the ceiling live — never guess a vendor's permission model), and a warning if the credential is known to carry more than this app needs. This is what makes the new source show up on the API Security Health page. monthly.get.ts (try/catch → null/empty, never take down the whole report). The home page's integration-down badge needs no separate edit — it derives from sourceHealth automatically.
accountOverrides.ts against the Customer Mapping workbook before committingTwo systems correct customer-name mismatches, and they must not silently diverge:
accountOverrides.ts — in-repo, code-level, global synonyms (wrong everywhere that account is looked up). Requires a commit to take effect. customerMapping.ts, live-edited from /reports/customer-mapping) — per-integration overrides. Takes effect immediately, no deploy. Before committing anything that touched name matching this session, check listCustomerMappings() for rows confirmed during the session. A genuinely global synonym gets promoted into accountOverrides.ts (with a comment citing how/when it was confirmed against live data — never from a guess); a genuinely per-integration one stays in the workbook only. If both exist for the same (account, integration), that's a bug — the workbook silently wins for that one integration while the stale code entry still affects every other integration's matching for that account. Doesn't apply to commits that never touched matching code or data.
MAPPING_DRIVE_ID/MAPPING_ITEM_ID in customerMapping.ts are constants committed to git, not .env values — by construction, every environment running current code reads and writes the exact same live "Customer Mapping.xlsx" file. There is no per-environment copy to keep in sync.
scripts/create-customer-mapping-workbook.js to "set up prod's copy" — it re-uploads a blank template over the one real file, wiping every mapping row entered so far. The script now refuses to run if the file already exists; don't pass --force without confirming with Cornel first. .env pointing at a different Azure AD app/tenant) — fix that, don't provision a second workbook. Rule 1's Report Health wiring only updates surfaces auto-derived from sourceHealth at runtime (Report Health's tiles, the home page badge, this page's Integrations table below). Several other places hold their own separately-hardcoded copy of the integration list and stay stale unless checked by hand:
technical.get.ts's integrations object (6 fixed keys) plus the matching TS type and integrationChecks computed in technical.vue — three separate hardcoded copies of the same subset. customer-mapping.vue's locally-redeclared IntegrationKey type and INTEGRATIONS label array — a deliberate duplicate of customerMapping.ts's union (client code can't import server utils), must stay byte-for-byte in step with it. report-health.vue's MatchColumnKey type + columns array + columnFilters — the matrix table's columns, a separate hardcoded list from sourceHealth (only the tile grid above it is auto-derived). apiSecurity.ts's API_SECURITY_REGISTRY — not auto-derived from sourceHealth either; a removed/renamed integration leaves a stale or orphaned entry on the API Security Health page unless removed/renamed here too. uat.vue's Report Health card description, and CLAUDE.md's own "What this is" / Configuration / namesMatch() paragraphs. Easiest to forget because nothing breaks when they go stale. Before calling an integration change done: grep -ri "<integration name>" across app/pages, app/server, and CLAUDE.md, and confirm every hit still reads correctly.
This app has grown several separate scoring systems, and it's easy to lose track of what scores what and why. The section below carries a high-level, always-current write-up of every one of them — any change to a weight, threshold, formula, or the set of components feeding one of these must update that write-up in the same commit:
monthly.vue's computeHealthScore(), client-side.server/utils/healthScore.ts, server-side, deliberately the same shape as the Service Report score with earlier ("aggressive") thresholds. If you change one, check whether the other should change too.executive-summary.get.ts, a structurally different additive points system, not a 0-100 composite.Keep it at the level this rule itself is written at — factors and relative weight, what the number means, score bands — not a line-by-line restatement of the code.
The UAT page (/uat) marks freshly-added reports with a "NEW" badge so people notice them. It's date-driven, never hand-toggled, and follows one rule:
All of it lives in uat.vue's <script setup>: NEW_BADGE_RELEASE_DATES maps each report's path to the date it shipped, and the newBadges computed property does the day-count math on every render — there's no cron job or manual step to turn a badge off, it ages out on its own.
Adding a new report card: add one entry to NEW_BADGE_RELEASE_DATES (the ship date, not the start date) and reuse the same v-if="newBadges['/reports/x']" + new-badge-{green,amber} pattern as the existing cards. Don't hand-add a plain badge with no date behind it — that was the exact bug this rule replaced (four cards stuck permanently "NEW" until this rule was added on 2026-07-07). Removing a report card: delete its map entry too, though a stale one is harmless on its own.
Every /reports/technical run appends one row to a real SharePoint Excel Table ("Technical Score Card.xlsx", server/utils/scoreCard.ts's appendScoreCardRow()). The row carries a full ClustersJson blob (nothing is ever lost there, however many clusters exist) plus one flat, human-skimmable column per cluster for anyone opening the sheet directly. Cornel intends to keep adding scoring clusters/phases over time, and the flat columns do not grow on their own — the live Excel Table was created once with a fixed column set and stays that way until someone deliberately adds to it.
When a cluster is added, renamed, or removed in technicalScoring.ts/technical.get.ts, same commit:
scoreCard.ts's CLUSTER_COLUMN_KEYS to match the cluster's key exactly.scripts/create-technical-scorecard-workbook.js's CLUSTER_COLUMNS (kept only as schema documentation — never re-run this script against an already-provisioned environment, same class of irreversible mistake as Rule 3; it wipes every score row logged so far). ClustersJson, just invisible to anyone skimming the sheet directly. A new cluster is almost always also a scoring-logic change — cross-check Rule 5 too.
Found and fixed 2026-07-08: the report home page (now /uat) and all three Service Report pages (monthly.vue, monthly-2.vue, monthly-dev.vue) fetched the integration-health badge with plain useLazyFetch("/api/reports/report-health") — no server option. That looks non-blocking, but lazy: true only skips blocking client-side route navigation — it does not skip Nuxt's server-side render pass. On a hard load (typing the URL, refreshing, opening a new tab — not clicking a link from an already-open page), Nuxt still awaits every top-level fetch before it can send any HTML, lazy or not. Since report-health.get.ts fans out to ~20 external systems and has a known failure mode that can hang for 10+ minutes with no error (N-central Snowflake's resource monitor quota — see Report Health), those pages could take minutes to open on a fresh load for a reason completely invisible in the component code.
365-recons.vue had already independently solved this correctly for its own second-phase fetch (see its own code comment on matchData) — the same fix is now applied everywhere else this pattern occurred.
When building a new page, or adding a fetch to an existing one:
debug.vue's own useFetch, report-health.vue's SSE stream) — blocking is correct; render <ProgressPanel> while it resolves. (This page's own Integrations table used to be cited here as a blocking example — it was converted to a lazy fetch 2026-07-12, since the rule book prose, not the table, is why this page exists.) useLazyFetch and pass { server: false } together. lazy alone is not enough — re-read the incident above before assuming otherwise. pending, show it with the shared <ProgressPanel :steps="[...]" /> component (app/components/ProgressPanel.vue) instead of a bare <p>Loading…</p> — a single-step spinner (one { message: "..." }) if there's nothing to enumerate, or the real multi-step SSE variant (useProgressStream + ?stream=1) if the endpoint supports streaming and the wait is long enough to be worth showing what's loading. <NuxtLoadingIndicator> in app.vue) is global and automatic — every page gets it on navigation with no per-page code. Don't add a second one. CLAUDE.md and this page are for whoever is coding on this repo — dense, code-level, assume the reader can read TypeScript. IP Dimension's own BookStack wiki (Insights Report chapter) is the opposite audience: non-technical staff, management, and anyone new who wants to understand what this app is and how it works without reading code.
When this applies — after a commit a non-technical reader would care about:
Small fixes, CSS tweaks, and internal refactors with no user-visible or conceptual change don't need a wiki edit.
What's there today (5 pages, established 2026-07-08):
Each integration can additionally have its own page inside the same chapter — how that integration was built, what it's used for, what tools/APIs it needed. Not mandatory for every integration on day one; add one when it's significant enough to warrant its own explanation.
The live app's own bookstack.ts stays read-only per Rule 1 — wiki writes go through scripts/sync-wiki-insights-report.js (page content lives separately in scripts/wiki-content/insights-report.js as a list the script loops over, creating or updating each page by name), never through the running app. BookStack keeps full page revision history, so an edit here isn't the irreversible-mistake class Rules 3/8 warn about — but check what's already there first, and never invent facts (who did what, exact dates) you haven't actually confirmed with Cornel.
Customer-ReportName-Date.html Every page under app/pages/reports/ must have a "Generate HTML File" button that downloads a standalone, self-contained HTML snapshot of what's currently on screen — styled like the real site (real colors/branding), not a washed-out black-and-white printout. Retrofitted onto all 27 report pages on 2026-07-09 after two pages were found exporting a backgroundless version because they cloned an inner content <div> instead of the outer .page/.shell wrapper that actually carries the page's CSS custom properties.
Always go through app/composables/useHtmlExport.ts (exportReportAsHtml() + buildHtmlExportFilename()) rather than hand-rolling the clone/inline/download logic again — copy an existing report page's downloadHtmlReport(). Pick the clone root carefully (the widest ancestor that still carries the page's real background/branding), and use "All Customers" as the customer segment for fleet-wide/non-customer-scoped pages. See CLAUDE.md's Rule 11 for the full mechanics and the exact bug this rule fixed.
/reports/monthly-dev is a sandbox frontend for the exact same /api/reports/monthly payload the live customer-facing Service Report (monthly.vue) consumes — new report content gets tried out there first, in front of nobody but IP Dimension staff, before it's promoted to the real report. Added 2026-07-09 alongside the first real example, a "Mail Relay" section for SMTP2GO.
The rule, for now: any integration classified Customer Mapping under Rule 1 step 0 (a genuine per-Autotask-account dimension) with data worth a customer seeing gets its own card in monthly-dev.vue's "Services" sidebar group — the same heading-plus-dropdown-expansion card style as every other section on the page, not a new visual pattern.
monthly.get.ts endpoint, fail-closed like every other integration there — not a separate endpoint. This is what makes promoting a section from Dev to the real report later a frontend-only change: copy the <details> block and the interface field into monthly.vue, the backend is already there. See mailRelay in monthly.get.ts and the section-mailrelay block in monthly-dev.vue as the reference implementation. matchNames/findMapping(manualMappings, accountId, "<key>") convention every other per-account field on this page already uses — a manual Customer Mapping override (including an explicit "this customer doesn't have this service") always wins over the fuzzy match. v-if must key off there being an actual matched record for this customer, not merely "this integration supports Customer Mapping." No match, or an explicit not-applicable override, means no section at all — not a "no data" placeholder. monthly.vue for real customers to see is a separate, deliberate per-section call. Ask Cornel first. Rules 1, 4, 5, 6 and 8 each end with some version of "update CLAUDE.md / the Dev Guide in the same commit." Easy to follow for whichever rule you're actively applying, easy to forget for everything else nearby — which is exactly how this app's own integration count, and four permanently-stuck "NEW" badges, went stale before anyone noticed (fixed 2026-07-07). This rule exists to make checking for that drift a habit, not an afterthought.
Before considering any non-trivial change finished, ask:
customerMapping.ts's MAPPING_DRIVE_ID/MAPPING_ITEM_ID? → Rule 3 — stop and double-check before committing. monthly.get.ts so promoting it later is frontend-only? useLazyFetch without also passing { server: false }? Does every pending state render <ProgressPanel> instead of a bare "Loading…" string? When in doubt, or after a longer stretch of changes without checking, run /rulebook-sync rather than trusting memory that everything downstream is still accurate — it re-reads CLAUDE.md fresh and reconciles live SharePoint state, not just the code.
High-level only — what feeds each score and what the number means, not every threshold in the code. See Rule 5 above: this section must be updated whenever the underlying logic changes.
A weighted composite computed client-side (monthly.vue's computeHealthScore()). Weights: SLA compliance 25%, First Response met 20%, Backup currency 20%, Server stability 15%, OS support 10%, Tech Stack coverage 10%, DMARC policy 10%. Tech Stack and DMARC are excluded from the average entirely (not scored as 0) when there's no data to measure — e.g. a domain not yet onboarded to EasyDMARC doesn't drag the score down, it just doesn't count that period.
Score bands: 85+ Healthy, 60–84 Needs Attention, below 60 Critical. The findings list underneath (why it isn't 100) is bucketed by severity — critical, then high, then medium.
Computed server-side (server/utils/healthScore.ts) — its own header comment calls it "a server-side port of monthly.vue's computeHealthScore()." Same 7 weighted components, same weights, same 85/60 score bands as the Service Report above. The difference is entirely in what counts as "good" at each component — thresholds trigger earlier ("aggressive"), since a technician should see a problem before a customer would. E.g. backup currency: the Service Report calls 45 days old "good," the Technical Report only allows 30; server instability: the Service Report tolerates up to 25% of servers rebooting before "critical," the Technical Report draws that line at 10%.
Findings also carry a numeric impact (weight × how far below target) and are ranked by impact — "fix this first" — rather than just a severity bucket, and are shown alongside a peer-rank (where this customer sits among others in the same classification bucket). This report reflects the selected month only, not a multi-month trend — it's a live/current-state view for a technician, not a historical comparison.
Separate from the above: the same report also computes a second, independent Technical Score Card system (server/utils/technicalScoring.ts, built in technical.get.ts) — a weighted cluster/sub-metric hierarchy (currently: Device Monitoring & Inventory, Documentation & Process, Agreements & Compliance, Risk & Ticket Management, Microsoft 365 / Identity, Data Quality / Integration Mapping, Patch Management, Backup Protection), each logged to a live SharePoint "Technical Score Card.xlsx" table per CLAUDE.md Rule 8. A sub-metric's score can be null ("couldn't compute this run") rather than 0, and is excluded from its cluster's weighted average instead of silently dragging it down. Device Monitoring & Inventory's "Disabled or No Monitoring" sub-metric (added 2026-07-10) scores nCentral devices with monitoring disabled or never enabled (IS_MANAGED), excluding recognized "…(Billing Profile)" placeholder devices at Probeless sites (nCentral sites with no discovery probe deployed, which get a placeholder device purely so SLA billing still counts them) — those are expected to have none. IS_MANAGED's exact semantics are pending live re-verification once the Snowflake shared warehouse's credit quota resets; treat that one sub-metric's score as provisional until then.
Patch Management and Backup Protection (both added 2026-07-10) are the two newest clusters. Patch Management is a device-weighted average of per-site nCentral patch compliance (Installed ÷ (Installed + Pending), Not Required patches excluded) — the same data already shown in the "Site Patch Status" section. Its SharePoint column existed since the workbook was first provisioned but had sat empty every run until this cluster was actually wired up. Backup Protection auto-detects which provider (Cove, Veeam, or Acronis) a customer is actually matched to and scores whichever one applies: Cove/Acronis start at 100 and lose 20 points per device in critical backup health and 8 per device in warning; Veeam (no per-VM breakdown at this level, only an org-wide restore-point rollup) scores 100/60/20 for OK/WARNING/CRITICAL. Unscored (null, not zero) when no provider matches at all — not a penalty for a customer genuinely without one.
A different shape entirely — not a 0–100 composite, but an additive "priority points" total, no upper bound, scoped to exactly 6 signals (no Upsell Opportunities gaps here, unlike the old "Executive Priority" page this replaced): ticket volume in the last 30 days (2pts/ticket, capped at 20), open RISK Register tickets (15pts each), integrations not mapped (8pts each), a Backup Register untouched >90 days (25pts base + 5pts per additional 30 days overdue), out-of-support/EOL Windows devices (5pts/device, capped at 25), and stale scheduled-check cadences (12pts each, only computed for a shortlist — see below). Customers are first split into Large/Medium/Small tiers by nCentral device count (even thirds by rank), then ranked within their tier — there's no single fleet-wide Top 10 anymore. Higher = worse here, the opposite of the two health scores above where higher = better.
Performance note: check-sheet staleness is the one per-customer signal that needs a live SharePoint call, so it's only computed for the top 15 candidates in a tier (ranked first by the other 5 signals, which are all single bulk queries) before picking the final top 5 — never looped over an entire tier. See executive-summary.get.ts's module header for the full reasoning.
Not part of the score: the page's top "Contracts & Renewals Needing Attention" card (added 2026-07-09) is raw, unscored Autotask/SharePoint data — Autotask contracts still marked active (is_active = 1) whose end date has already passed, and 365 Subscriptions Amendments rows whose effective date has passed while still not marked "Completed". It's surfaced separately from the 6-signal priority score above, not blended into it.
Most other percentages shown in the app are a plain match/coverage ratio, not a weighted score — e.g. Report Health's X/Y "matched" counts per integration. 365 Billing's per-user verdicts (active/stale/disabled/deleted/shared) aren't a score at all, just a classification.
/rulebook-sync.claude/skills/rulebook-sync/SKILL.md is a real Claude Code command, not just a prompt to copy-paste — type /rulebook-sync in the Claude Code chat panel in VS Code and it will: re-read every rule in CLAUDE.md fresh, reconcile the live Customer Mapping SharePoint workbook (Rules 2/3), spot-check the Backup Register SharePoint list that feeds 20% of both health scores, audit every integration-visual surface (Rule 4) and this page's scoring write-up (Rule 5), fix what it can safely fix, write what it learned back into CLAUDE.md and this page (so the next sync starts from an already-updated rule book), then commit — staging only the files it touched, never a blanket git add -A, since another Claude Code session may have unrelated work sitting in the same tree.
This is the intended way to be sure a commit was actually reviewed against the rule book and live SharePoint state, rather than trusting memory. Run it any time you're unsure what's gone stale, and especially after touching an integration, name-matching, or scoring.
If a project skill isn't available in a given session, these free-text prompts do the same thing manually — paste one into the chat panel. When in doubt, start with the first one.
Read every rule in this repo's CLAUDE.md, in priority order, and do a full compliance pass on the current state of the repo: (1) Rule 3 — confirm nothing re-provisioned the Customer Mapping workbook; (2) Rule 2 — check for any live name-mapping confirmations not yet reconciled with accountOverrides.ts; (3) check the Backup Register SharePoint list that feeds 20% of both health scores is still reachable and its fields still resolve; (4) Rule 1 — confirm every integration in customerMapping.ts's IntegrationKey union and report-health.get.ts's sourceHealth is fully wired up; (5) Rule 4 — grep every integration name across app/pages, app/server, and this file for drifted/stale mentions; (6) Rule 5 — check whether monthly.vue's computeHealthScore(), healthScore.ts, or executive-summary.get.ts changed since the Dev Guide's "How scoring works" section was last written, and update it if so. Report back what you changed and what was already consistent — don't just say "done."
I just added/changed the <integration> integration in server/utils/<file>.ts. Re-read this repo's CLAUDE.md process rules and apply them: confirm the code is read-only (the default per Rule 1 — no write call unless explicitly justified and confirmed with Cornel, same bar as the sharePointBackup exception), wire it into Report Health and add an ApiSecurityEntry in apiSecurity.ts per Rule 1, reconcile any name-matching changes per Rule 2, and update every surface listed in Rule 4 that visually lists integrations (including the Dev Guide at /dev and CLAUDE.md's own "What this is" paragraph if the integration count changed).
I just changed <what changed> in <file>. Apply CLAUDE.md Rule 5 — update the Dev Guide's "How scoring works" section to match, at the same high level that section is already written at (factors, relative weight, what the number means, score bands — not a line-by-line restatement of the code).
Before I commit, apply CLAUDE.md Rule 2 — check listCustomerMappings() for anything confirmed this session and reconcile it against accountOverrides.ts, per the rule.
I just added app/pages/<path>.vue. Apply CLAUDE.md's "How to build a new page"
section and Rule 9: confirm any non-essential fetch uses useLazyFetch(..., { server:
false }) (not just useLazyFetch) so it can never block first paint on a hard
load/refresh, confirm every pending state renders <ProgressPanel> rather than a bare
"Loading…" string, add a nav card (with a Rule 6 NEW badge) to uat.vue/
development.vue if nothing links to it yet, and apply Rule 1/2/4/5 if the page touches
a new integration, name-matching, or a new score.Check my current uncommitted changes in this repo against every process rule in CLAUDE.md and tell me what's missing before I commit.
Audit this repo for integration-list drift per CLAUDE.md Rule 4 — grep every integration name across app/pages, app/server, and CLAUDE.md, and tell me if anything is out of sync with customerMapping.ts's IntegrationKey union or report-health.get.ts's sourceHealth array.
I just changed <what changed>. Update both app/pages/dev.vue and CLAUDE.md's relevant rule/section so they still say the same thing.
Pulled live from Report Health — per Rule 1, if you add an integration and it doesn't appear here, it isn't wired in yet.
| Integration | Status | Records | Matched / Total | Client file |
|---|
Not on this list because they aren't matched by customer name (so Report Health's per-account matching doesn't apply): Sage/Pastel Partner (matched via an Autotask UDF account code, see sage.ts) and Westcon Azure usage (still in discovery, not yet built — see the project notes).
Moved to its own page — not "is it reachable," but "what rights does each credential actually have, and does any of them have more than this app needs?" See Rule 1 above: a new integration isn't done until it has a row there too.
All credentials reviewed, no excess access detected →