Imported from workoho/spfx-guest-sponsor-info (
AGENTS.md). Install upstream withnpx skills add workoho/spfx-guest-sponsor-info. Copyright stays with the author.
Agent Customization Guide
This file documents tools and best practices for AI coding agents (GitHub Copilot, Claude, etc.) working in this repository.
DevContainer Tools Available
The following CLI tools are pre-installed in .devcontainer/Dockerfile and should be used over their slower alternatives:
File & Search Tools
| Tool | Alternative | Command Example | Best For |
|---|---|---|---|
ripgrep (rg) |
grep |
rg "ComponentName" src/ --type ts |
Fast code/text search across codebase |
| fd | find |
fd "\.tsx$" src/ |
Finding files by pattern (faster, cleaner syntax) |
| bat | cat |
bat src/components/MyComponent.tsx |
Syntax-highlighted file preview in terminal |
| delta | git diff |
git diff HEAD |
Syntax-highlighted git diffs with line numbers |
| shellcheck | — | shellcheck scripts/*.sh |
Static analysis of shell scripts |
| shfmt | — | shfmt -w -i 2 -ci scripts/*.sh |
Format shell scripts (2-space indent, case-indent) |
Data Processing Tools
| Tool | Use Case | Example |
|---|---|---|
| jq | JSON parsing | cat package.json | jq '.scripts' |
| yq | YAML/config parsing | yq eval '.devDependencies' package.json |
| fzf | Interactive fuzzy selection | fd "test.ts" | fzf (pick file interactively) |
Recommended Agent Usage Patterns
1. Searching Code
# ✅ Good: Using rg for fast search
rg "SponsorService" src/ --type ts
# ❌ Avoid: Using grep (slower)
grep -r "SponsorService" src/
2. Finding Files
# ✅ Good: Using fd for clean syntax
fd "GuestSponsorInfo\.tsx"
# ❌ Avoid: Using find (verbose)
find . -name "*GuestSponsorInfo*" -type f
3. Reading Configuration
# ✅ Good: Using yq/jq for structured parsing
yq eval '.engines' package.json
# ❌ Avoid: Manual regex parsing
grep "engines" package.json | cut -d '"'
4. Previewing Files in Terminals
# ✅ Good: Using bat for syntax highlighting
bat src/webparts/guestSponsorInfo/GuestSponsorInfo.tsx
# ❌ Avoid: Using cat (no syntax highlighting)
cat src/webparts/guestSponsorInfo/GuestSponsorInfo.tsx
Git Workflow
Pre-commit Hook
The .husky/pre-commit hook runs lint-staged, which applies targeted fix
and lint to staged files only (not the full suite):
| Staged files | Action |
|---|---|
src/**/*.{ts,tsx} |
eslint --fix |
azure-function/src/**/*.ts |
eslint --fix |
**/*.md |
markdownlint-cli2 --fix |
src/**/loc/*.js |
prettier --write |
.github/**/*.yml, website/**/*.{yml,yaml} |
prettier --write |
**/*.{json,jsonc} |
prettier --write |
**/*.sh |
shfmt |
Note: lint:bicep, lint:ps, lint:actions, lint:sh, the lint:loc
consistency check, and the test suite are not run by the hook — run them
manually before committing when relevant.
Do not skip this hook (--no-verify) except for meta-changes (infrastructure-only commits).
Commit Message Format
Use Conventional Commits (enforced by commitlint via .husky/commit-msg).
See .github/instructions/commit-message.instructions.md for the full ruleset.
Hard limits (violations abort the commit):
- Header ≤ 100 characters
- Every body/footer line ≤ 100 characters — wrap prose manually
- Subject must be lower-case, no trailing period
- Type must be one of:
fixfeatbuildchorecidocsperfrefactorrevertstyletest
chore(i18n): normalize locale files to UTF-8
feat(sponsor): add sponsor card caching
fix(graph): handle missing user profile gracefully
docs: update README configuration section
Final self-check before commit
Before invoking git commit, quickly verify:
- Header length ≤ 100 characters
- Every body/footer line ≤ 100 characters
- Body text is wrapped manually, or omitted if it adds little value
When to Use npm Scripts vs Direct Commands
✅ Use npm Scripts (Managed + Linted)
npm run lint # ESLint + Stylelint + Markdownlint + locale structure
npm run fix # Auto-correct formatting
npm run lint:ts # TypeScript only
npm run lint:loc # Locale .js files (Prettier + syntax + key-order check)
npm test # Jest + coverage
npm start # Dev server with hot-reload
npm run build # Full production build + packaging
⚠️ Avoid Raw Commands Unless Necessary
# ❌ Don't run raw heft commands
npx heft build
# ✅ Use npm scripts instead
npm run build
# ❌ NEVER call the husky binary directly without -- separator.
# Husky v9 treats its first positional argument as a target directory.
# Running 'npx husky --version' (without --) makes husky create a
# '--version/_' directory and overwrite core.hooksPath, breaking all
# git hooks (commits and pushes will fail with "Illegal option --").
npx husky --version # ❌ breaks git hooks!
npx husky list # ❌ breaks git hooks!
# ✅ Use npm scripts to manage hooks
npm run prepare # (re-)install / reset git hooks
# ✅ If you need the husky version, use the -- separator:
node_modules/.bin/husky -- --version # ✅ safe
Code Validation Checklist
During development (after each edit — fast, 2–5 s)
Run only the linter matching the files you touched:
| Changed files | Command |
|---|---|
src/**/*.{ts,tsx} |
npm run lint:ts |
azure-function/src/**/*.ts |
npm run lint:ts:func |
**/*.md |
npm run lint:md |
.github/**/*.yml, azure.yaml, website/**/*.yml |
npm run lint:yml |
azure-function/infra/**/*.bicep |
npm run lint:bicep |
scripts/*.sh |
npm run lint:sh |
src/**/loc/*.js |
npm run lint:loc |
If the change spans multiple file types, run only the matching subset.
Do not run npm run lint (full suite) after every individual edit.
Before finishing or asking the user to commit, run the matching targeted linter(s) for every changed file type in the current diff and fix all reported errors and warnings. Do not rely on pre-commit hooks to discover the first remaining issue.
Before committing (full validation)
- Fix →
npm run fix(auto-correct formatting) - Lint →
npm run lint(full suite — catches anything fix could not resolve) - Test →
npm test(only when logic, components, or services changed — skip for docs, config, locale, or style-only changes)
The pre-commit hook (lint-staged) already runs targeted fix + lint on staged
files, so step 1–2 is a safety net more than a strict requirement.
Never push with lint errors, lint warnings, or test failures.
Portable Agent Policy
Use this file as the shared baseline for any coding agent working in this
repository, especially tools that do not automatically understand
.github/instructions/.
If an agent supports file-scoped instructions, also apply the matching files
under .github/instructions/. If it does not, read the relevant instruction
files manually before editing those paths.
Definition of done for code changes
A change is not complete when the bug is fixed and tests pass. Before finishing, do one local readability pass on the touched code.
- Prefer names that reflect stable domain meaning or UI policy, not incidental implementation details.
- Rename misleading identifiers introduced or exposed by the change when the rename is local and low risk.
- Extract small pure helpers when multi-branch conditionals encode breakpoints, layout policy, or state-derived rendering rules.
- Avoid leaving nested ternaries or dense inline object construction in the touched code when a named helper would make the intent clearer.
- Improve the touched slice and its immediate neighborhood, but do not widen the change into unrelated refactors.
Instruction files to consult manually when needed
src/**/*.{ts,tsx}→.github/instructions/code-quality.instructions.mdazure-function/src/**/*.ts→.github/instructions/azure-function-code-quality.instructions.md**/*.sh→.github/instructions/shell-code-quality.instructions.mdazure-function/infra/**→.github/instructions/infra-code-quality.instructions.mdsrc/**→.github/instructions/fluent-ui.instructions.md- Commit message generation →
.github/instructions/commit-message.instructions.md
Shell Script Conventions
All scripts in scripts/*.sh follow these rules. Before writing a new script, read
scripts/lint.sh for the standard boilerplate (set -euo pipefail, cd, source colors.sh)
and scripts/set-version.sh for the maybe() dry-run pattern.
Colour output
Use the variables from scripts/colors.sh — never copy the detection block inline.
| Variable | Meaning | Typical use |
|---|---|---|
C_GRN |
Green | ✓ success messages |
C_RED |
Red | ✗ errors / fatal messages |
C_YLW |
Yellow bold | ⚠ warnings, menu highlights |
C_CYN |
Cyan | version numbers, file paths |
C_BLD |
Bold | section headers |
C_DIM |
Dim | secondary info, progress lines |
C_RST |
Reset | always close a colour sequence |
Colours are automatically suppressed when $CI is set, stdout is not a TTY,
$NO_COLOR is set, or $TERM is "dumb".
Callout boxes
scripts/colors.sh also provides box functions that draw a coloured frame
around developer-facing messages so they stand out from build output:
| Function | Border colour | Purpose |
|---|---|---|
hint |
Cyan | Developer tips, alternative approaches, good-to-know info |
next_steps |
Green | What to do after the script finishes |
important |
Yellow | Critical action items that must be done |
Pass each line as a separate argument; pass "" for a blank separator line:
hint "Edit ${C_BLD}.env${C_RST} and set SPFX_SERVE_TENANT_DOMAIN" \
"${C_DIM}(or export it on your host OS)${C_RST}"
next_steps "${C_BLD}./scripts/dev-webpart.sh${C_RST} # start the SPFx dev server" \
"${C_BLD}./scripts/dev-function.sh${C_RST} # start Azure Function locally"
important "Edit azure-function/local.settings.json" \
"" \
"Required:" \
" TENANT_ID — your Entra tenant ID"
Prefer these box functions over plain echo for any message a developer should
not miss (setup instructions, next steps, required configuration).
Callout boxes are for interactive scripts only — ones a developer runs in a
terminal. Automated hooks (e.g. azd pre/post-provision hooks in
azure-function/infra/hooks/) must use plain echo — no colors.sh dependency,
no visual formatting. Prioritise efficiency and robustness there.
Comments
Bash is not self-documenting. Comment non-obvious constructs, especially:
- Parameter expansions:
${var%%[-+]*}→ explain what it strips and why - Conditional flags: explain what a
gitornpmflag does if it is not obvious - Decision points: why a fallback exists, what the edge case is
Idempotency
Every script must be idempotent — safe to run multiple times with identical results.
- Guard file/config creation with existence checks:
[[ ! -f ... ]],[[ ! -d ... ]],command -v,git config --get - Use CLI flags that make operations idempotent:
--allow-same-version,az bicep install(always fetches latest),npm install(lockfile-driven) - Never overwrite files unconditionally — always check first or use
cp -n
Network downloads
Never let a failed network download abort a setup script silently or leave corrupt state. Two patterns to follow:
In setup scripts (e.g. post-create.sh): Use the try_net <host> <cmd>
helper defined in .devcontainer/post-create.sh. It probes host with a
3-second HEAD request before attempting the download — if the network is down,
it returns immediately instead of waiting for timeouts. For transient failures
it retries with short backoff (2 s → 4 s).
In all scripts: Never pipe curl directly into tar. Download to a temp
file, clean it up in both success and failure paths. See scripts/release-notes.sh
for the reference implementation using mktemp and trap.
Validation
After every shell script change:
npm run lint:sh # shellcheck -x (SC1091 aware; follows sourced files)
npm run fix # shfmt auto-format (2-space indent, case-indent)
Do not suppress shellcheck warnings with # shellcheck disable unless you add
a comment directly above it explaining the specific reason.
Stack Constraints (Do Not Violate)
- SPFx version — Do not upgrade; use
scripts/upgrade-spfx.shwhen explicitly requested - Node version — Must stay within
enginesrange inpackage.json(currently>=22.14.0 <23.0.0) - React version — Pinned at 17.0.1; do not change
- @microsoft/ packages — Managed as coordinated set; do not individually update
Fluent UI v9
This project uses Fluent UI v9 (@fluentui/react-components) exclusively.
For import patterns, check the existing components — GuestSponsorInfo.tsx and
SponsorCard.tsx are the authoritative reference.
Styles
Use makeStyles from @fluentui/react-components (Griffel) for all component-level
styles, with tokens from @fluentui/react-components for all colour and spacing values.
Use mergeClasses() for conditional class composition.
Do not add CSS/SCSS module files — all styles live in makeStyles hooks.
Forbidden patterns
// ❌ v8 components — project has fully migrated to v9
import { Persona, Callout, Panel, ActionButton, IconButton,
Icon, TooltipHost, MessageBar } from '@fluentui/react';
// ❌ v8 icon font
initializeIcons();
// ❌ v8 styling APIs
import { mergeStyles, mergeStyleSets } from '@fluentui/merge-styles';
// ❌ Hardcoded colour values — use tokens instead
const style = { color: '#0078d4' };
// ❌ CSS/SCSS module files — use makeStyles hooks instead
import styles from './Foo.module.scss';
Key component equivalents
| v8 | v9 |
|---|---|
Persona (avatar display) |
Avatar with color="colorful" |
PersonaPresence enum |
PresenceBadge status string prop |
Callout |
Popover + PopoverTrigger + PopoverSurface |
Panel |
OverlayDrawer + DrawerHeader + DrawerBody |
ActionButton / IconButton |
Button with appearance="subtle" |
Icon iconName="Chat" |
<ChatRegular /> (SVG from @fluentui/react-icons) |
TooltipHost |
Tooltip with relationship="label" |
Link |
Link from @fluentui/react-components |
MessageBar + MessageBarType |
MessageBar + MessageBarBody with intent prop |
IButtonStyles |
makeStyles with tokens (Griffel hook) |
Theme integration
Always wrap the component tree in <FluentProvider> with the site theme:
import { FluentProvider } from '@fluentui/react-components';
import { createV9Theme } from '@fluentui/react-migration-v8-v9';
// `theme` is the IReadonlyTheme from the SPFx ThemeProvider service
const v9Theme = theme ? createV9Theme(theme) : undefined;
return <FluentProvider theme={v9Theme}>{children}</FluentProvider>;
Key Files for Reference
- Lint config →
config/directory +.markdownlint.json+.markdownlint-cli2.jsonc - Locale strings →
src/webparts/guestSponsorInfo/loc/*.js(UTF-8 native characters, no\uXXXXescapes) - Components →
src/webparts/guestSponsorInfo/components/ - Services →
src/webparts/guestSponsorInfo/services/(Graph API calls inSponsorService.ts)
Azure Infrastructure Scripts
PowerShell scripts in azure-function/infra/ assist with one-time setup and
post-deployment verification. They require the
Az.Accounts and Az.Resources PowerShell modules (pre-installed in the dev
container) and an active Connect-AzAccount session.
| Script | When to run |
|---|---|
setup-graph-permissions.ps1 |
Once after deployment — assigns Graph app roles to the Function's Managed Identity |
Verify-DeploymentGuid.ps1 |
After every fresh Bicep/azd deployment — confirms CUA attribution works |
Verify-DeploymentGuid.ps1
Source: bmoore-msft/Verify-DeploymentGuid.ps1 (Microsoft Partner Center team)
Purpose: After deploying via deploy-azure.ps1 / install.ps1, this script follows the
correlationId of the pid-18fb4033-c9f3-41fa-a5db-e3a03b012939 deployment
and lists every Azure resource deployed in the same correlation scope. A
non-empty list confirms that Azure will correctly attribute consumption to the
Workoho Partner Center GUID. An empty list requires investigation (likely the
pid-* deployment was not part of the same Bicep/azd correlation scope).
When to run:
- After every fresh Bicep/azd deployment (via install.ps1 or deploy-azure.ps1)
- Before a Partner Center reporting period to confirm attribution is live
- Not needed for code changes, schema updates, or configuration-only changes
Usage:
.\azure-function\infra\Verify-DeploymentGuid.ps1 `
-deploymentName pid-18fb4033-c9f3-41fa-a5db-e3a03b012939 `
-resourceGroupName <your-resource-group>
Expected output: one or more Azure resource ID strings (Function App, Storage Account, App Service Plan, etc.). See the script header for the full prerequisites list.