Finishing a Development Branch
obra/superpowers
Walks the last step of a branch: confirm tests pass, detect the git environment, ask how to integrate, carry out your choice and clean up the worktree.
Perform the edits in the sibling sfdx-hardis-training repository after training-impact found an impact, or when a lab has to change for its own reasons.
$ npx skills add hardisgroupcom/sfdx-hardis --skill training-update -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install hardisgroupcom/sfdx-hardis training-update --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/hardisgroupcom/sfdx-hardis.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/training-update .claude/skills/training-update && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "training-update" agent skill from https://github.com/hardisgroupcom/sfdx-hardis/tree/main/.claude/skills/training-update into .claude/skills/training-update/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "training-update", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/hardisgroupcom/sfdx-hardis/tree/main/.claude/skills/training-updateType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add hardisgroupcom/sfdx-hardis --skill training-update -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install hardisgroupcom/sfdx-hardis training-update --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/hardisgroupcom/sfdx-hardis.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/training-update .agents/skills/training-update && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "training-update" agent skill from https://github.com/hardisgroupcom/sfdx-hardis/tree/main/.claude/skills/training-update into .agents/skills/training-update/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "training-update", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add hardisgroupcom/sfdx-hardis --skill training-update -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install hardisgroupcom/sfdx-hardis training-update --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/hardisgroupcom/sfdx-hardis.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/training-update .cursor/skills/training-update && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "training-update" agent skill from https://github.com/hardisgroupcom/sfdx-hardis/tree/main/.claude/skills/training-update into .cursor/skills/training-update/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "training-update", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/hardisgroupcom/sfdx-hardis.git --path .claude/skills/training-update--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add hardisgroupcom/sfdx-hardis --skill training-update -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install hardisgroupcom/sfdx-hardis training-update --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/hardisgroupcom/sfdx-hardis.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/training-update .gemini/skills/training-update && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "training-update" agent skill from https://github.com/hardisgroupcom/sfdx-hardis/tree/main/.claude/skills/training-update into .gemini/skills/training-update/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "training-update", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install hardisgroupcom/sfdx-hardis training-updateInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add hardisgroupcom/sfdx-hardis --skill training-update -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/hardisgroupcom/sfdx-hardis.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/training-update .github/skills/training-update && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "training-update" agent skill from https://github.com/hardisgroupcom/sfdx-hardis/tree/main/.claude/skills/training-update into .github/skills/training-update/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "training-update", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add hardisgroupcom/sfdx-hardis --skill training-update -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install hardisgroupcom/sfdx-hardis training-update --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/hardisgroupcom/sfdx-hardis.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/training-update .opencode/skills/training-update && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "training-update" agent skill from https://github.com/hardisgroupcom/sfdx-hardis/tree/main/.claude/skills/training-update into .opencode/skills/training-update/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "training-update", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
training-updatePerform the edits in the sibling sfdx-hardis-training repository after training-impact found an impact, or when a lab has to change for its own reasons.
Training Update is an agent skill from hardisgroupcom/sfdx-hardis. Perform the edits in the sibling sfdx-hardis-training repository after training-impact found an impact, or when a lab has to change for its own reasons. Covers the lab text in every locale (labs/en/ is the reference, labs/fr/ mirrors it), training-universe.json, the audit rules, the link maps, the Helios screenshot fixtures, and the screenshot rules: capture, web captures, numbered pills and verification. Use it when the user says "update the training", "fix the labs", "translate a lab", "regenerate the training…
Its SKILL.md is about 11k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Development, covering Pull requests. It works with Salesforce. The repository describes itself as: French-army-knife Toolbox for Salesforce. Orchestrates base commands and assist users with interactive wizards to make much more than native Salesforce CLI + Allows you to define…. The licence is AGPL-3.0.
3 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 971ac89. It shows what the files ask for, not the result of running them.
Pre-approves these tools, so the agent can use them without asking each time:
BashReadWriteEditGrepGlobFrom allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
nodegityarnpythonsfgoFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
github.comFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Training Update loads about 11k tokens when it runs. Until then it costs about 156 tokens; SKILL.md has 5,435 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check noted patterns worth knowing about, such as sudo or a known installer.
allowed-tools: Bash, Read, Write, Edit, Grep, GlobAutomated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from hardisgroupcom/sfdx-hardis at commit 971ac89, republished under its AGPL-3.0 licence (© hardisgroupcom). 5,435 words, ~10,657 tokens.
.claude/skills/training-update/SKILL.md (or your agent's skills folder).Load this once [[training-impact]] has said there is an impact, or when a lab has to change for its own reasons. It does the editing; the other skill does the deciding.
[ -d ../sfdx-hardis-training ] || git clone https://github.com/hardisgroupcom/sfdx-hardis-training.git ../sfdx-hardis-training
cd ../sfdx-hardis-training
git checkout main && git pull
git checkout -b fix/<what-changed>The sibling path is fixed and never configurable. Same for ../vscode-sfdx-hardis when screenshots
have to be regenerated.
| What | Where | Generated? |
|---|---|---|
| The lab text | labs/en/level-N/lab-NN-*.md | No, written by hand |
| The translated lab text | labs/<locale>/level-N/lab-NN-*.md, same file names | No, translated by hand from labs/en/ |
| The words of the generated pages | i18n/<locale>.json | No, translated by hand from i18n/en.json |
| The fiction: stories, branches, orgs, cast | training-universe.json | No, the source of truth |
| The backlog, the link maps, the manifest | BACKLOG.md, labs/link-map.<locale>.md, training-manifest.json | Yes, scripts/build/universe.mjs |
| The command links of Under the hood blocks | inside each lab, between <!-- command-links:start/end --> | Yes, scripts/build/lab-command-links.mjs |
| The site pages nobody writes | The backlog, one page per story, the badges index, one page per badge holder | Yes, scripts/build/site.mjs, per locale |
| The audit rules | scripts/verify/rules.mjs | No |
| The seed data | scripts/data/HeliosBaseline/*.csv | Yes, scripts/build/data.mjs |
| The screenshot fixtures | ../vscode-sfdx-hardis/test/fixtures/screenshot/helios/ and training-project/ | Yes, scripts/build/mocks.mjs |
| The lab links in the product documentation | ../sfdx-hardis/docs/*.md, its command descriptions, both READMEs | Yes, scripts/build/doc-links.mjs |
| The course showcase on the doc site | ../sfdx-hardis/docs/salesforce-devops-training.md, README teaser, guide callouts | Yes, scripts/build/doc-links.mjs |
| The raw panel screenshots | labs/_assets/vscode/*.png | Yes, the extension harness |
| The raw web screenshots | labs/_assets/web/*.png | Yes, scripts/build/capture-web.mjs |
| The capture and annotation specs | labs/_assets/web-captures.json, labs/_assets/annotations.json | No, written by hand |
| The annotated screenshots labs link | labs/_assets/annotated/ | Yes, scripts/build/annotate.mjs |
| The raw Salesforce screenshots | labs/_assets/salesforce/*.png | Yes, scripts/build/capture-salesforce.mjs |
| The site sources | site-src/ | Yes, scripts/build/site.mjs, git-ignored |
| The site theme | site-theme/ | No, copied into the site by site.mjs |
| What a learner starts each level from | scripts/start-states/level-N/ | No, written by hand |
| The teammate Pull Requests | scripts/simulate/<story>/ | No, written by hand |
Never edit a generated file. Change its source and re-run the generator. CI fails on drift
(node scripts/build/universe.mjs --check).
The site is Zensical, and it is not mkdocs-material. It reads course-site.yml (named so because
mkdocs.yml belongs to the Helios project documentation of Lab 3.10) and honours most of
it, but it ships none of the plugins: glightbox is declared and Zensical emits the
<a class="glightbox"> wrapper around every picture while shipping no viewer, so the course
carries its own (site-theme/javascripts/lightbox.js, a delegated listener in the capture
phase, because the theme cancels link clicks on the way back up for its instant navigation). Two
more things the theme gets wrong and this stylesheet corrects: the footer text is painted from the
page foreground token rather than --md-footer-fg-color, which in the light scheme is navy on plum
and unreadable; and html .md-footer-meta.md-typeset a:not(:focus,:hover) is specific enough that
a rule has to match it shape for shape to win.
Custom JavaScript runs once per page load, and the theme swaps pages without reloading. Anything
per-page subscribes to document$, the way tables.js does.
Every lab carries front matter that drives the manifest and the checks:
---
id: lab-2-7
title: "Lab 2.7 - Resolve a Git merge conflict with a teammate"
description: "A teammate merged first on the same flow and permission set. Resolve both Git conflicts in VS Code without losing anybody's work."
level: 2
lab: 7
lang: en
source_rev: ""
screenshots:
- vscode/devops-pipeline
depends_on:
commands: [hardis:work:save, hardis:work:refresh]
flags: []
config: [overwriteMode]
panels: [pipeline]
docs: [salesforce-devops-work-on-user-story-profiles]
---Labs are numbered N.M from 1, level then position: Lab 1.1 is the first lab of Level 1, and
text says "Lab 2.5", never "Level 2 lab 4". The folder is labs/en/level-N-<name>/ and the file
N-M-<words>.md, which is also the URL: short, lowercase, hyphenated, words a learner would search
for. title is the page title and must equal the # heading; description is the meta
description, one sentence under 160 characters that names the Salesforce and sfdx-hardis terms the
lab teaches. Both come from training-universe.json (levels[].labs[]), and inserting a lab means
renumbering the ones after it, their files, their rule ids (N.M in scripts/verify/rules.mjs)
and every "Lab N.M" in the text, in every locale: the file names and the numbers are the same in
all of them, so a renumbering that stops at labs/en/ leaves the translations pointing at pages
that moved.
When you change what a lab relies on, change depends_on too. It is what makes the next impact
check work, and it is the one thing easy to forget.
House style for the lab body, in order: The situation, Before you start, Steps,
What you should see, If it goes wrong, Check your work, Go deeper.
If it goes wrong stays an ordinary ## heading in the markdown. scripts/build/site.mjs folds it
into a collapsed block on the site, because it is the one section nobody reads in order. Write it as
a plain section; do not indent it by hand.
Three rules the labs are written under, and they are not negotiable:
src/webviews/lwc-ui/modules/s/ in the extension before writing a command
block. A lab that reaches for a terminal when a panel would do is a defect in the labsf org login web in a lab, at any
level, for any org<details> block naming the
exact command, the files it wrote, and the one decision the tool made that the learner could not
seeThe course ships in English and in French, labs/en/ and labs/fr/, mirrored file for file.
labs/en/ is the reference. Every change starts there. Not a convention to be polite about: it
is what keeps the two from disagreeing about what a button does. So:
labs/en/, then carry the fix into every locale. A French
page that is right while the English one is wrong is a fact nobody else can findid or a slug in one locale. The structure is English everywhere:
same file names, same folders, same id, level, lab, screenshots and depends_on, same
URLs. Only the prose, the title and the description are translatedsource_rev in the front matter names the commit of the
English file it was made from, and node scripts/i18n/check-translations.mjs lists the ones the
source has moved past. That list is what to re-read, and CI reports it without failingnode scripts/i18n/check-structure.mjs compares headings, images, code fences, <details>
blocks, pill references, admonitions and tables, and says nothing about the wordsi18n/<locale>.json, not in markdown. The backlog, the
page of each User Story, the badges index and the badge pages are built from
training-universe.json and the badge records, once per locale, so there is no file to copy for
them. That file holds their words, and under universe the translation of what
training-universe.json writes in English: the pitch, the level names, the roles of the cast,
and the title, story and acceptance criteria of every story. A string it leaves out reads in
English rather than leaving a holeThe order when a change touches a lab:
# 1. English first, and commit it, because the stamp reads git
$EDITOR labs/en/level-2-contributor-advanced/2-3-*.md
git add labs/en && git commit -m "..."
# 2. the same edit in each other locale
$EDITOR labs/fr/level-2-contributor-advanced/2-3-*.md
# 3. the three generators that are locale aware
node scripts/build/lab-crossrefs.mjs # "Lab 2.7, étape 3" becomes a link, per locale
node scripts/build/lab-command-links.mjs # each sf hardis command an Under the hood block names, linked
node scripts/i18n/align-tables.mjs # MD060: a translated cell moves every pipe under it
node scripts/i18n/stamp-source-rev.mjs fr # write source_rev from the commit of step 1
# 4. what always runs
node scripts/build/universe.mjsstamp-source-rev.mjs reads the last commit that touched the English file, so running it before
committing the English change stamps the version before yours and quietly claims the translation
is current. Commit first.
Adding a locale is additive, and TRANSLATION.md in the training repository is the procedure.
Three things worth knowing from here:
TROUBLESHOOTING in scripts/build/site.mjs (the translated "If it goes wrong"
heading, which is what folds that section), LOCALES in scripts/build/lab-crossrefs.mjs (the
word for "step"), and NAV_LABELS plus LOCALE_NAMES in scripts/build/universe.mjs;i18n/<locale>.json is the whole of the generated pages, and the locale also goes in the nav
and in extra.languages of course-site.yml, which carries its home page and its flag. That key
is deliberately not called alternate: under that name the theme reads each entry as the root of
a separate site, asks it for a sitemap.xml it does not have, and takes the language click over;site-overrides/partials/language.html.node scripts/verify/check-nav.mjs and node scripts/verify/check-language-switch.mjs on the
built site are what say the new locale holds together: one language per menu, every picker landing
on the same page in the other language and pointing back, and the choice remembered in a cookie.
Every command and guide a lab teaches carries a Learn by doing block that links that lab, in
../sfdx-hardis and in the extension's README. It is generated, between
<!-- training-links:start --> and <!-- training-links:end -->, from the depends_on front
matter of the labs by way of training-manifest.json:
node scripts/build/universe.mjs # first: the manifest is what the links are read from
node scripts/build/doc-links.mjs # then: writes the blocks into the sibling clones
node scripts/build/doc-links.mjs --check # writes nothing, fails when a page is out of dateSo changing a lab's depends_on changes the product documentation, and the three repositories
are committed together. Never edit a block by hand; move one if it sits in the wrong place, and the
next run rewrites it where you put it.
Edit training-universe.json, never a lab, when the change is about a User Story, a branch, an org
or a character. Then:
node scripts/build/universe.mjsIt regenerates the backlog, the link map and the manifest, and fails when a lab mentions a story id, an org or a branch the universe does not define. That check is what stops the labs and the screenshots telling two different stories.
scripts/verify/rules.mjs holds every rule, used by both Check my work and the badge claim.
Two hard rules when editing one:
Test both paths after any change:
node scripts/verify/check.mjs --level 2
node scripts/verify/audit.mjs --level 2 --dir <a clone> --handle testNever send anybody to a terminal. Not once, in any level. Every action a lab asks for is a click
in a VS Code sfdx-hardis panel, a Training menu entry on the Welcome page, a button in the GitHub web
UI, or typing into a file in the editor. A command may only appear inside a collapsed
<details> block titled "Under the hood", as an explanation of what a button did, never as an
instruction. There is no fenced ```bash block outside such a block anywhere in the course, and adding
one is a regression.
When a step has no button, that is a finding, not a licence: either the product has a click nobody
named, or the product is missing one. Two examples that were fixed rather than documented: connecting
an org could not set an alias, so hardis:org:select gained --alias and a prompt; setting up a
fork, four orgs and their secrets by hand took an afternoon, so Training > Set up my training environment
does it.
# The check. It must print nothing.
grep -rniE "from a terminal|open a terminal|in a terminal" labs/en/Levels 1 and 2 are for admins as much as for developers. The main narrative must not assume
developer knowledge. Raw XML, git internals, Apex interfaces, YAML structure and wildcard patterns
either get a plain-English gloss where they first appear, or they move into a <details> block. Code
a lab contains is there to be copied and read, never written from scratch: say so. Level 3 is for
release managers and may be as technical as it needs to be.
The tells that a Level 1 or 2 paragraph has drifted: a command name used as an explanation
(hardis:work:save generates the package from the git diff), a file path the reader has no reason to
know, an unglossed term (idempotent, working tree, soft reset, grep, @testSetup), or a step that
asks the reader to hand-edit metadata that a panel can edit.
It installs the tools and nothing else: Git, VS Code, Node, the extension pack, and the Setup panel installing the Salesforce CLI. Somebody joining a real project can do that lab and stop, and the lab says so. Everything training-specific, the two free orgs, the clone, the pipeline, the seeded data, belongs to lab 1.
Two lines to keep where they are:
The audience has no Salesforce background and no git background. A step it cannot see is a step it cannot do. The rules below are hard rules, not preferences.
node scripts/build/annotate.mjs from labs/_assets/annotations.json into
labs/_assets/annotated/, and labs reference the annotated copy, never the raw onelabs/_assets/annotated/ is generated. Never retouch an image there by hand: change the
spec and re-run the generator
3bis. A pill reference in the text is painted in the colour of its pill. scripts/build/site.mjs
turns **(2)**, and a (2) inside a bold run such as **Save (3)**, into a coloured span, and
generates the stylesheet from the palette annotate.mjs draws the pills with, so the two cannot
drift. Write the plain markdown and never a colour, an emoji or a styled span by hand; a number
in ordinary, non-bold prose is left alone on purposepx / py keys of the spec are for.
After drawing, look at the result and move any pill that landed on somethingBoth passes mean actually looking at the file, not at the file name:
A screenshot that has not been looked at has not been verified.
VS Code panels come from the extension's own harness, in the sibling clone. It drives a VS Code instance it owns. Regenerate only when a panel actually changed.
# 1. the fixtures, from the training repository
cd ../sfdx-hardis-training
node scripts/build/mocks.mjs
# 2. the capture, from the extension repository
cd ../vscode-sfdx-hardis
yarn dev && yarn compile
SF_MOCK_UNIVERSE=helios \
SFDX_HARDIS_DOC_SCREENSHOTS_DIR=../sfdx-hardis-training/labs/_assets/vscode \
yarn screenshots [names]Use scripts/build/shots.mjs rather than calling the harness by hand. It takes image names or
labs, finds the gate behind each image in labs/_assets/vscode/.shot-gates.json (the harness
writes that map at every capture) or in labs/_assets/vscode-captures.json (images taken under
another pipeline state or copied under another name), runs the harness once per state into a temp
folder, copies back only the images asked for, redraws their pills and builds one sheet to look at:
node scripts/build/shots.mjs vscode/work-new-org web/github-pr-comment
node scripts/build/shots.mjs --lab 2.7 # every image Lab 2.7 shows
node scripts/build/shots.mjs --lab 2.7 --pills # no capture: pills and sheet only
node scripts/build/shots.mjs --all --kind vscode # every VS Code image of the course
node scripts/build/shots.mjs --lab 3.1 --dry-run # what it would takeOn Windows the capture shows no window; elsewhere, ask first. The extension harness drives
VS Code through the Chrome DevTools Protocol (src/test/ui/cdpWindow.ts in vscode-sfdx-hardis),
so it never uses the real mouse, and on Windows it starts VS Code on a desktop of its own
(scripts/hidden-desktop), so no window opens on the user's desktop. There it can run in the
background while they work. On macOS and Linux a window opens at every run.
The Salesforce and web captures of this repository have their own tools: check theirs before
assuming the same.
A workbench menu (the ... of a view, a context menu) could not be captured with the former desktop driver, which activated the window and closed the menu. This has not been tried again with the protocol driver. Webview menus, quick picks and the Command Palette can be captured.
Pass only the names you need: the full batch takes about twenty-five minutes, and with no names at
all it also records the GIFs, which writes recordings/ and *-for-recording.png into the output
folder. Those do not belong in the training assets: delete them, or always pass names.
A name in that list is the name the test is gated on, not the name of the file it writes. Most
tests gate on the shot they take, but the ones that take a group gate on the group:
sidebar-commands writes eight sidebar-commands-*.png, and work-new, work-save,
command-runner, pipeline-modals, pipeline-action-editors, user-activateinvalid and
backpromote behave the same way. Listing the file names of a group silently skips it: the run is
green and the images are the old ones. grep -n "shouldTake(" src/test/ui/docScreenshots.test.ts
lists every gate.
A command panel in a lab shows the command the lab runs, never a stand-in. command-runner
replays hardis:org:mock-showcase, a demo of the panel itself: it is fine for the product docs and
wrong for a lab, where the reader compares the questions with the ones they get. When a lab needs a
command that has no scenario yet, add one to DOCS_SCENARIOS in test/fixtures/sf-shim/sf-mock.js
with the real prompts, log lines and report files (read the command source and src/i18n/en.json
here), take its answers from the universe scenario key written by mocks.mjs, and a gated test in
docScreenshots.test.ts. configure-auth (Lab 3.2) is the example. Every question needs a log
line after it, or the panel shows no answer chip on that row.
The side bar is in every VS Code capture, so a change to what a project declares in
customCommands invalidates all of them, not only the menu shots. The training declares one menu
per level (Training: Level 1, 2, 3), which is three rows instead of one.
SFDX_HARDIS_DOC_SCREENSHOTS_DIR must be an absolute path. A relative one resolves against the
Extension Development Host's own working directory, and the captures land somewhere nobody finds.
Three pipeline states, through SF_MOCK_PIPELINE_STATE. The committed fixture is the pipeline
as Levels 1 and 2 have it, and the other two are built from it at launch:
| Value | What it gives | Used for |
|---|---|---|
| unset | integration and uat, with the feature branches and open Pull Requests | Level 2 and Level 3 labs |
fresh | no feature branches, no Pull Requests, no jobs | Level 1: what a learner's own fork looks like |
fresh-disconnected | the same, with the git provider inactive: grey icon, no toggle, no Pull Requests | Level 1 lab 1, the step that connects GitHub |
level3 | uat, preprod and main configured, each merging into the next | Level 3: the finished four stage pipeline |
They capture under the usual shot names, so take them into a temp folder and copy the file in under
the name the lab uses (devops-pipeline-fresh.png, pipeline-branch-modal-level3.png...).
branchNode in universe.json is where the major branch box sits in the diagram, for the click
that opens its window. Mermaid lays it out from the branches the fixture carries, so it moves
whenever they change, and a stale value clicks empty canvas and captures a pipeline with no window.
scripts/build/mocks.mjs writes it. It is the default state's point: the level3 state lays the diagram out
differently, so pass SFDX_HARDIS_DOC_SCREENSHOTS_BRANCH_NODE=x,y for that run, read off its
devops-pipeline.png. The branch window without a merge target is taken the same way, on uat.
retrieverRows in the same file is the heights of the three rows the Metadata Retriever capture ticks,
and it moves whenever sourceMembers changes.
After any capture, re-pin by looking. A panel that gained a toggle, a Welcome strip or two menu rows
moves every box below it, and annotate.mjs still draws the old spec without complaint. Render the
annotated images four to a sheet with a headless Chrome of your own and check each pill against its
step text. Run one harness batch at a time: two VS Code instances on this machine run out of memory.
Never take the Extensions view. That VS Code has no marketplace access, so it renders "Error
while fetching extensions", and the view stays open for every capture that follows. The training
uses a screenshot taken on a real machine, labs/_assets/vscode/extensions-install.png, which
nothing in the harness may overwrite.
Web pages come from scripts/build/capture-web.mjs, declared in labs/_assets/web-captures.json,
driven by Playwright over CDP against a Chrome started with --remote-debugging-port=9222. A page
that has to be seen signed out is declared "fresh": true and gets its own clean Chrome profile.
cd ../sfdx-hardis-training
node scripts/build/capture-web.mjs [names]
node scripts/build/annotate.mjsNever call browser.close() on a CDP connection. chromium.connectOverCDP attaches to the
user's running browser: closing it closes their browser, with everything they had open. Close only
the pages the script opened. A capture that needs a browser of its own uses
launchPersistentContext with its own --user-data-dir, and closes that context. This has happened
once: the user lost their session and their CDP Chrome, which cannot simply be relaunched because
Chrome refuses --remote-debugging-port on the default profile.
Never automate the desktop to take a screenshot. No SendKeys, no SetForegroundWindow, and
never kill a window matched by its title. Done once, it took over the user's own VS Code window,
typed into it, closed it, and the image it produced contained the user's real org usernames.
SF_MOCK_UNIVERSE unset must keep the product documentation screenshots byte for byte
unchanged. That is the invariant of the whole fixture design. Prove it after any screenshot work:
cd ../vscode-sfdx-hardis
yarn screenshots # writes doc-screenshots/, the MyCompany-CRM universe
python scripts/build-doc-images.py # crops them into ../sfdx-hardis/docs/assets/images
cd ../sfdx-hardis
git status --porcelain docs/assets/imagesEmpty output, or the training work broke the product images.
doc-screenshots/ itself is git-ignored and holds no tracked file, so checking it with git status
proves nothing: a run that changed every capture still reads as clean there. The tracked copies are
the cropped images in the sfdx-hardis documentation, and those are what to look at.
GitHub, the Salesforce signup, an installer page: they change without warning, and the lab still reads fine while the clicks no longer exist. When a lab describes such a screen, re-check the screen against the live page before trusting the text. Two found the hard way:
A screenshot that shows the wrong org, an unrelated project or a stale Pull Request is worse than no screenshot, because it teaches the learner that the picture is decoration.
Training > Reset this level resets a learner to training/start-level-N. Those branches are built
from the deltas in scripts/start-states/level-N/ by scripts/build/start-branches.mjs, each level
applied on top of the one before.
node scripts/build/start-branches.mjs --dry-run # what each branch would carry
node scripts/build/start-branches.mjs # build them locally
node scripts/build/start-branches.mjs --push # publish, from main: what CI does on every push to mainPublishing is CI's job: .github/workflows/start-branches.yml runs --push on every push to
main of the shared repository. Push by hand only when that run failed, and from main, after
checking the run is not still going: two pushes of the same branches race on --force-with-lease.
Two rules that are easy to get wrong:
integration out at training/start-level-N, and run
node scripts/verify/check.mjs --level N-1. That is exactly what the reset produces.scripts/simulate/<story>/ carries them, and a lab
that simulates one an earlier lab already merged gets "Nothing to commit" and opens no Pull
Request. If a start state ships a story pre-merged, the lab that used to simulate it has to review
the merged Pull Request instead. Both capstones had this bug.They are measured against real orgs and a real fork, not estimated, and the home page says so. Two things drive them:
A lab's **Time** line and its row in the level index must agree, and the level totals appear in
four places per locale: each labs/<locale>/level-N/index.md, labs/<locale>/index.md, plus
README.md once. A time changed in English and not in French is the easiest translation drift to
create and the hardest to notice, because nothing compares numbers across locales.
The Metadata Retriever comes before hardis:work:save, always. That is the product's own
workflow (the contribution cards read New User Story, then Commit changes, then Save / Publish) and
it is the only one that works on this course's orgs:
hardis:work:save has exactly three prompts: Have you already committed?, a data export
question that needs ./scripts/data/EmailTemplate and so never fires here, and push?. There
is no screen where a learner picks components. Any lab that describes one is wrongsf project retrieve start, which needs
source tracking. helios-dev is a scratch org and has it, but the course still never uses that
answer: the Metadata Retriever is the one route that also works on a sandbox or a Developer Editionhardis:work:resetselection resets is the commits, not a stored list.
It does a soft reset, restores manifest/, and sets canForcePushSo a lab that changes an org reads: retrieve with the Metadata Retriever, commit from Source Control, then Save / Publish and answer Yes, my commit(s) are ready.
hardis:work:new in this course answers Scratch org, then Reuse scratch org helios-dev. The list
leaves out the scratch orgs config/branches/ names, and never Create new scratch org: the Dev Hub
keeps three alive and all three are taken. With a sandbox, the command only asks whether to initialize
it when the project sets offerSandboxInit: true (this one does not), and that answer does not bring
metadata down. It installs packages, assigns permission sets and runs the init scripts. The command itself
prints that a backpromote is what brings the merged metadata. A lab that says "say yes and you will
have the team's work" is wrong.
Staging is the decision, not the picker. hardis:work:save commits nothing by itself: the
learner stages the retrieved files one at a time in the Source Control panel, with the + on each
row. Level 1 lab 4 teaches that and forbids Stage All Changes, because the panel routinely shows
files nobody asked for.
The target branch question offers availableTargetBranches, and the mock reads that same file.
scripts/build/mocks.mjs builds the choices from config/.sfdx-hardis.yml, so a screenshot can
never offer a branch the project refuses. Levels 1 and 2 pin it to integration alone; Level 3
lab 0 adds preprod, where hotfixes start. A one-item list is correct, and the lab says why.
One signup, then scratch orgs. A learner signs up for one Developer Edition org, helios-prod
(an Org Farm org, orgfarm-<10 hex>-dev-ed.develop.my.salesforce.com), and Set up my training environment (scripts/training/init.mjs) makes it a Dev Hub and creates three scratch orgs from it:
helios-dev to build in, helios-integration and helios-uat for the two stages of Levels 1 and 2.
Level 3 lab 0 adds a second signup, helios-preprod, and makes helios-prod the main org. The
universe carries kind and branchFrom per org, and the fixtures use both host shapes on purpose.
ensureDevHub, ensureScratchOrgs,
seedScratchOrgs from init.mjs), never against the maintainer's own helios-* aliases, and
never create scratch orgs in a loop.DevHubSettings.enableScratchOrgManagementPref, not
enableDevHub, which the Metadata API refuses.Read the command, not its name. This is the single highest-yield rule in this skill: a pass that verified every lab against the sources found six of nine Level 2 labs and eleven of eleven Level 3 labs carrying a wrong claim, six of them impossible to follow.
What that pass kept finding:
hardis:work:resetselection soft-resets every
commit since the branch point; the lab said in bold that it does not.sfdxHardisConfigHelper.ts decides what the settings panel
renders and at which scope, and a branch-scoped key is invisible while the scope reads Global.useDeltaDeployment among them.Green is not proof. Two measured examples worth keeping in the labs: a deployment reported "No
post-deployment actions defined" and went green when git refused the workspace, and SFDMU exits
0 SUCCESS when the target object is missing, having written nothing. Whenever a lab tells a reader
a thing happened, tell them where to look in the org.
cd ../sfdx-hardis-training
node scripts/build/lab-crossrefs.mjs # first: link every mention of another lab, per locale
node scripts/i18n/align-tables.mjs # a translated cell moves every pipe under it
node scripts/i18n/stamp-source-rev.mjs # after the English commit, never before
node scripts/build/universe.mjs # regenerate, because lab front matter feeds the manifest
node scripts/build/universe.mjs --check # generated files up to date, fiction consistent
node scripts/build/annotate.mjs # every annotated image matches its spec
node scripts/verify/check-pills.mjs # the pills an image carries are the ones its step cites
node scripts/i18n/check-translations.mjs # which translations the English source has moved past
node scripts/i18n/check-structure.mjs # which translations lost an image or a block. Reports, never fails
node scripts/verify/check-links.mjs # every link resolves
node scripts/build/site.mjs && python -m zensical build -f course-site.yml
node scripts/verify/check-site.mjs # every page resolves every asset
node scripts/verify/check-mobile.mjs # the shared pages still read at 412px, in every localecheck-pills.mjs and check-mobile.mjs exist because two classes of mistake were invisible to
everything else: a lab citing (3) over a two-pill image, and a five-column table rendering as
one word per column on a phone. Both passed the markdown, the links and the asset checks.
check-pills.mjs reads every locale for the same reason: a translator who drops a (3) breaks
the tie between the sentence and the picture, and nothing else would say so.
Anchors into another lab are per locale. lab-crossrefs.mjs builds them from the step headings
of the locale it is linking within, folding accents to ASCII the way python-markdown does, so
"3. Prendre les vôtres" becomes #3-prendre-les-votres. Change that fold and every French anchor
misses by one letter, with nothing failing: check-site.mjs checks that pages exist, not that
anchors resolve.
Add a CHANGELOG.md entry under the date of the day at the root of the course, in the same
Pull Request: one short line per change a learner or a trainer would notice (a new lab, a lab whose
steps changed, a fixed screenshot, a site feature). Write it for them, not for maintainers: no
script names, no file paths. The course has no versions and no ## Unreleased: the heading is
## YYYY-MM-DD, today's date, added at the top when it does not exist yet.
One Pull Request for the course at a time. When a training Pull Request is already open for the work in progress, add to its branch instead of opening a new branch, and update its description.
State which labs were re-verified, and how. "Re-read" is not re-verified: a lab is re-verified when somebody walked its steps, or when its audit rule was run against a repository in that state.
Cross-link it with the CLI and extension Pull Requests of the same change.
Keep the running Found while training list in the training Pull Request description: anything the exercise turned up in the CLI or the extension, with where it was fixed. A finding that is neither fixed nor written down is the only unacceptable outcome.
vscode-sfdx-hardis skill, for the extension side of the same change© hardisgroupcom, AGPL-3.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .claude/skills/training-update of hardisgroupcom/sfdx-hardis.
Open the folder on GitHubat commit 971ac89
Training Update next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Training Update this skillhardisgroupcom/sfdx-hardis | 400 | — | ~11k | Automated safety check: Notes | AGPL-3.0 | |
| Finishing a Development Branchobra/superpowers | 296k | 5 repos | ~1.9k | Automated safety check: Pass | MIT | |
| PR Babysitteropeninterpreter/openinterpreter | 69k | 3 repos | ~4.2k | Automated safety check: Pass | Apache-2.0 | |
| Check PRonyx-dot-app/onyx | 32k | 2 repos | ~2.3k | Automated safety check: Pass | MIT | |
| Understand Diff AnalysisEgonex-AI/Understand-Anything | 85k | 1 repos | ~1.4k | Automated safety check: Pass | MIT | |
| PR Design DocOpenHands/OpenHands | 90k | — | ~2.4k | Automated safety check: Pass | MIT |
obra/superpowers
Walks the last step of a branch: confirm tests pass, detect the git environment, ask how to integrate, carry out your choice and clean up the worktree.
openinterpreter/openinterpreter
Watches an open GitHub pull request until it merges, handling review comments, diagnosing CI failures and retrying flaky checks along the way.
onyx-dot-app/onyx
Checks a GitHub, GitLab, or Perforce (p4) pull request (or merge request, or shelved changelist) for unresolved review comments, failing status checks, and incomplete PR descriptions.
Egonex-AI/Understand-Anything
Reads your git changes or a pull request against a prebuilt knowledge graph of the project to explain what changed, which components are affected and what is risky.
OpenHands/OpenHands
For a non-trivial pull request, write a self-contained HTML design doc under the temporary .pr/ directory and link a visibility-appropriate preview in the PR description, so maintainers grasp the…
woocommerce/woocommerce
Reviews WooCommerce code changes against the project's standards, flagging backend PHP architecture, naming, documentation, data integrity and testing violations.
hardisgroupcom/sfdx-hardis
Runs a full end-to-end test of sfdx-hardis promotion branches and backpromote against real Salesforce orgs and a throwaway repository, then writes a report.
hardisgroupcom/sfdx-hardis
Walks the sfdx-hardis training course end to end as a learner would, against a real Developer Edition org and fork, fixing broken steps and screenshots that no longer match.
hardisgroupcom/sfdx-hardis
Explains how the sfdx-hardis Salesforce CLI plugin is built: its TypeScript and Oclif stack, command layout, agent-mode flag and provider classes for git, notifications and AI.
hardisgroupcom/sfdx-hardis
Style rules for adding CHANGELOG.md entries: short, user-facing bullets grouped by command under the beta section, each linking the command's docs page.
hardisgroupcom/sfdx-hardis
Documentation standards for sfdx-hardis commands (description format with Command Behavior and Technical explanations sections, MkDocs site, build:doc).
hardisgroupcom/sfdx-hardis
Decision framework for fixing jscpd (copy-paste detector) errors.
Works with
Categories
Perform the edits in the sibling sfdx-hardis-training repository after training-impact found an impact, or when a lab has to change for its own reasons. Training Update is an agent skill from hardisgroupcom/sfdx-hardis. Perform the edits in the sibling sfdx-hardis-training repository after training-impact found an impact, or when a lab has to change for its own reasons.
Training Update fits situations like: the user says update the training; translate a lab; regenerate the training screenshots; annotate a screenshot.
Run `npx skills add hardisgroupcom/sfdx-hardis --skill training-update -a claude-code`. Or copy the skill folder (.claude/skills/training-update in hardisgroupcom/sfdx-hardis) into .claude/skills/training-update in your project. Claude Code loads it when a task matches its description.
Run `npx skills add hardisgroupcom/sfdx-hardis --skill training-update -a codex`. Or copy the skill folder (.claude/skills/training-update in hardisgroupcom/sfdx-hardis) into .agents/skills/training-update in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add hardisgroupcom/sfdx-hardis --skill training-update -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/training-update, .gemini/skills/training-update, .github/skills/training-update and .opencode/skills/training-update in your project.
Going by SKILL.md and its folder, Training Update needs the command-line tools its instructions call (node, git, yarn, python, sf and go). Its frontmatter pre-approves these tools: Bash, Read, Write, Edit, Grep, Glob.
SKILL.md names 1 domain. In commands or code: github.com; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found notes only (pre-approves every shell command (allowed-tools: bash)), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.
Training Update is published under the AGPL-3.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 11k tokens (SKILL.md is roughly 43k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Training Update: Finishing a Development Branch (obra/superpowers, 296k stars), PR Babysitter (openinterpreter/openinterpreter, 69k stars), Check PR (onyx-dot-app/onyx, 32k stars) and Understand Diff Analysis (Egonex-AI/Understand-Anything, 85k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
hardisgroupcom (a GitHub organization) maintains it in hardisgroupcom/sfdx-hardis, which has 400 GitHub stars. The repository holds 21 skills in this directory. The repository was last updated on October 7, 2026.
Source: hardisgroupcom/sfdx-hardis on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.