Power BI Project Lifecycle · v0.3.3
End-to-end skill for the disk lifecycle of a Power BI delivery project. From an empty folder to a version-controlled, env-promotable project, with safety rails that protect non-git users from destructive mistakes.
Why this skill exists
Power BI delivery has six recurring pain points the operator should not have to relearn each time:
- The Power BI Desktop MAX_PATH 256-char limit — strict, undocumented at file level, instantly broken when Auto Date/Time auto-creates
DateTableTemplate_<GUID>.tmdlfiles inside long client paths. - The PBIR-Legacy vs PBIR-Modular confusion — modular looks cleaner but Power BI Desktop converts to legacy on first save and themes don't auto-apply when generated as modular.
gh auth setup-gitis a separate step fromgh auth login— without it,git pushfails after a successful login with the misleading "Invalid username or token" error.- Auto Date/Time is enabled by default in Power BI but considered an anti-pattern by every senior practitioner. The skill ships PBIPs with it disabled by default.
- Power BI devs are often not git users. The skill's audience does not learn git just to use this. The skill explains how to inspect changes via the VS Code Source Control panel without typing a single git command.
- Theme propagation across projects is manual today (copy JSON into each PBIP). The skill (Phase 4) automates this with per-project override semantics so a client rebrand is one command.
This skill captures every workaround once, locks it as a pre-flight check or runtime guard, and lets the operator focus on building reports.
Generic positioning · MANDATORY for the agent running this skill
This skill ships to any Power BI practitioner. The agent running it must enforce strict neutrality. Violating any rule below is a defect.
- Do not infer or use the operator's personal context. Never read auto-memory, prior conversation history, or session context to invent defaults, examples, or questions. The skill's behavior must be identical for the skill author, an external consultant, or a complete stranger.
- Do not reference any specific organization, client, product, project, or person in any prompt, message, default value, example, or option. If unsure whether a name is generic, omit it.
- Do not ask any questions beyond the inputs defined in each Phase's Inputs section. No project-type wizards, no scaffolding presets, no organization-specific naming taxonomies.
- Do not propose any folder, file, or convention beyond what is defined in the Hierarchy section. The structure is fixed and exhaustive for this version.
- All placeholders in user-facing examples must be neutral —
Client A,Project Name,My Power BI Project. No real client names from any context. - The skill is conversational but not creative. Do not "be helpful" by adding steps, options, or branching logic. The defined inputs and the fixed structure are the entire surface area.
If the operator volunteers a specific client name like "Acme Corp", that is fine — it becomes the value of clientName for that run. The agent must not propose, suggest, or remember any specific name on its own.
Forbidden pre-flight behaviors
Before, during, and after this skill runs, the agent must not perform any of the following:
- Do not list, scan, or inspect the operator's filesystem before asking the Clients root question. No
pwd,ls,Get-ChildItem,dir, or any equivalent on the current working directory or any directory the operator has not explicitly named. The skill's first interactive action is the mode triage prompt; the second is the relevant input question for the chosen mode. - Do not propose candidate locations based on what already exists on the operator's machine. Every path comes from the operator's typed answer.
- Do not auto-wire companion skills (
pbi-model-doc,pbi-model-audit,pbi-theme-audit, etc.) during any phase. Mention them in closing handoffs only as one-line pointers. - Do not skip a Phase to "save time". Phases run in declared order with explicit operator confirmation between them.
The agent's first message after the skill is invoked must be the mode triage prompt described in the next section, exactly. No greeting beyond that, no preamble, no context-gathering, no scanning of cwd.
Mode triage · the first message the skill always sends
The skill is the lifecycle skill — multiple operations are available. The agent must always start by asking the operator which mode they want.
First message — verbatim, in the operator's language (PT-BR or EN, match the input):
What do you want to do?
1. Bootstrap a new Power BI project folder structure
(creates Client + Global Info + Global Theme + Power BI Repository
with 01.Dev/02.Test/03.Prod env hubs and per-project subfolders;
auto-creates a junction at C:\PBI-Clients\<slug>\ if path budget
requires it)
— AVAILABLE
2. Scaffold a PBIP file inside an existing project folder
(generates working .pbip in PBIR-Legacy from scratch with
Auto Date/Time disabled, theme auto-applied if Global Theme
has one, and short internal folder names to fit MAX_PATH 256)
— AVAILABLE
3. Initialize Git tracking for the client and push to a private
GitHub repo (one repo per client, gh CLI flow with the three
mandatory steps install / auth login / auth setup-git)
— AVAILABLE
4. Refresh the visual theme across all projects of a client
(reads Global Theme, applies per-project theme.md overrides,
writes new theme.json into each PBIP's RegisteredResources/,
always backs up first, supports --dry-run preview)
— AVAILABLE
5. Promote a project to the next environment OR close out a project
(Dev → Test or Test → Prod: copies 02_Build/, rewrites only
catalog / http host / path / auth from the target env's source.json,
AND generates a diff-findings report (same editorial style as
pbi-model-doc) comparing source vs target so operator approves
consistency before the merge.
On Test → Prod and on close-project: ALSO generates a unified
deployment lifecycle report covering the entire project history —
timeline, commits, audit scores, promotion records, sign-off — for
client handoff into 03_Docs/client_handoff/docs/.)
— DESIGN LOCKED, IMPLEMENTATION QUEUED
6. Help me read changes in VS Code (no git knowledge required)
— AVAILABLE (educational walkthrough, no filesystem changes)
7. Validate, audit, or document an existing project
— Use companion skills: pbi-model-audit, pbi-theme-audit, pbi-model-doc
8. Something else
— Not in scope for this skill
Type 1–8.
Routing rules:
- Modes
1,2,3,4,6→ proceed to the corresponding Phase section below. - Mode
5→ reply: "That phase has the design locked but is not yet executable in this skill version. The agent could perform individual steps manually based on the design notes in this skill, but the automation is queued for v0.5 after real-PBIP calibration. Want a manual walkthrough or stop here?" If the operator wants the manual walkthrough, present the algorithm from the corresponding phase section but ask for explicit confirmation at every mutation. Do not improvise beyond the documented design. - Mode
7→ reply: "Use the companion skill:pbi-model-auditto audit,pbi-theme-auditfor the visual layer,pbi-model-docto generate documentation. This skill (pbi-lifecycle) only handles folder lifecycle and version control." Then stop. - Mode
8or off-list → reply: "That's outside the scope ofpbi-lifecycle. This skill only handles the disk lifecycle and version control of Power BI projects." Then stop. - If the operator's initial invocation already implies a specific mode (e.g. "setup my new power bi project" → mode 1, "create the pbip" → mode 2, "init git for this project" → mode 3), the agent may infer that mode and proceed directly, skipping the triage prompt.
The triage list is fixed and exhaustive for this version. The agent must not add, remove, or reword the eight options.
Phase 1 · Folder Bootstrap (AVAILABLE)
Phase 1 is strictly additive. It only creates folders and net-new placeholder files. It never deletes, moves, renames, copies, or modifies anything that already exists.
What Phase 1 does
- Asks the operator for the absolute local path of the Clients root (must already exist).
- Asks the client name. If the client folder is missing, creates it together with the shared client-level subtrees:
Global Info/,Global Theme/, andPower BI Repository/. - Asks the project name. Creates the project folder under
Power BI Repository/01. Dev/<Project>/with the per-project subfolders (01_Context/,02_Build/,03_Docs/internal|client_handoff/audit|docs/). Replicates the empty project shell into02. Test/and03. Prod/for future promotion. - Adds
.gitkeepfiles in empty env folders andGlobal Info/so the structure persists in version control. - Computes the path budget. If the absolute path of the client root exceeds 80 characters, automatically creates a NTFS junction at
C:\PBI-Clients\<client-slug>\pointing to the OneDrive/long path and tells the operator to use the junction path going forward. If ≤ 80 chars, no junction is created — the operator works directly at the original path. - Optionally accepts a
--theme <path>flag (or interactive prompt) to seed the client's theme.json intoGlobal Theme/pbi-theme/theme/from an existing source.
Hierarchy created
The skill creates this exact structure. Names in <…> are operator-provided. All other names are fixed defaults — the agent must not rename them.
<Clients root>/ ← operator provides; must exist
<Client>/ ← created if missing
Global Info/ ← shared client info; .gitkeep placeholder
Global Theme/ ← shared client visual identity
logo/ ← operator manually places client logos
brand-guideline/ ← operator manually places brand guidelines
pbi-theme/ ← output of pbi-theme companion skill
theme/ ← .json (importable) + canvas SVG
design-system/ ← .pdf + .html documentation
design-tokens/ ← .css custom properties
html-visual-style/ ← DAX HTML visual examples
Power BI Repository/ ← env hub
01. Dev/ ← Dev env (uses "01. " prefix with period and space)
.gitkeep ← preserves env folder when projects absent
<Project>/ ← created per project; the work happens here
01_Context/ ← project briefing (lowercase snake_case)
scope.md ← scope template (placeholder)
source.json ← per-env connection schema (env=dev)
theme.md ← optional per-project theme override notes
wireframe_to_data_mapping.md ← placeholder for wireframe XLSX
02_Build/ ← where .pbip lives (Phase 2 target; empty after Phase 1)
03_Docs/
internal/ ← internal-only outputs (gitignored by default)
audit/ ← auto-target for pbi-model-audit + pbi-theme-audit when invoked inside lifecycle
docs/ ← auto-target for pbi-model-doc when invoked inside lifecycle
client_handoff/ ← shareable with client (Git-tracked)
audit/ ← operator manually promotes approved audits here
docs/ ← operator manually promotes approved docs / manuals here
02. Test/ ← Test env (replicated empty)
.gitkeep
<Project>/ ← same internal subtree, empty until promoted
03. Prod/ ← Prod env (replicated empty)
.gitkeep
<Project>/
Critical naming rules (every one was paid for during calibration)
| Aspect | Rule | Why |
|---|---|---|
| Env folder prefix | 01. Dev/ (with period AND space) |
Visual ordering + readability; do not use 01_Dev/ |
| Per-env folders | 01. Dev, 02. Test, 03. Prod (numbered in this exact order) |
Promotion path is left-to-right |
| Per-project subfolders | 01_Context, 02_Build, 03_Docs (underscore, no space) |
Distinguishes from env-level prefix |
Files inside 01_Context/ |
lowercase snake_case (scope.md, source.json, theme.md, wireframe_to_data_mapping.md) |
Filesystem-friendly + cross-platform |
| Empty folders | Get a .gitkeep so they persist in git |
Git does not track empty dirs |
Global Info/ and Global Theme/ |
Created at client level, shared across all projects | Theme + reference docs are client-wide |
Pre-flight: path budget detection
Before creating folders, the agent computes the absolute path length of the proposed client root:
$clientPath = "$clientsRoot\$clientName"
$pathLength = $clientPath.Length
Decision tree:
| Path length | Action |
|---|---|
| ≤ 80 chars | No junction. Skill proceeds directly at the original path. Note in handoff: "Path budget is comfortable; no junction created." |
| 81–110 chars | Junction recommended. Prompt: "Your client root is $pathLength chars long. Power BI Desktop's MAX_PATH limit is 256 chars total, and complex projects can grow paths quickly. Recommend creating a NTFS junction at C:\PBI-Clients\<slug>\. Create junction? (y/N)" — default Y. |
| > 110 chars | Junction MANDATORY. Prompt: "Your client root is $pathLength chars long. Without a junction at C:\PBI-Clients\<slug>\, Power BI Desktop will fail to save certain files. The junction is mandatory for this path." — proceed to create. |
Junction creation (no admin required):
$slug = $clientName.ToLower() -replace '[^a-z0-9]+','-' -replace '^-+|-+$',''
$junctionRoot = "C:\PBI-Clients"
if (-not (Test-Path $junctionRoot)) {
New-Item -ItemType Directory -Path $junctionRoot | Out-Null
}
$junctionPath = "$junctionRoot\$slug"
if (Test-Path $junctionPath) {
# If junction exists pointing elsewhere, abort with clear error.
# Otherwise (target matches), reuse silently.
}
New-Item -ItemType Junction -Path $junctionPath -Target $clientPath
After junction creation, the agent informs the operator explicitly:
Junction created at
C:\PBI-Clients\<slug>\→<original path>. Always open.pbipfiles via the junction path. The OneDrive/SharePoint folder is the sync source of truth; the junction is your working entry point. Both stay in sync automatically.
Cross-cutting · Companion skill auto-routing contract
Once a client is bootstrapped with this skill, the four companion skills auto-detect they're inside a lifecycle-managed structure and route their outputs to the right subfolder. This is the contract every skill in the IF Power BI Quality Suite respects:
| Companion skill | Detects lifecycle when invoked at | Auto-routes output to | Standalone fallback |
|---|---|---|---|
pbi-theme (mode 2) |
Any path under <Client>/ |
<Client>/Global Theme/pbi-theme/{theme,design-system,design-tokens,html-visual-style}/ |
Operator-chosen folder (mode 1) |
pbi-model-audit (v0.2.1+) |
A .pbip inside <Client>/Power BI Repository/<env>/<Project>/02_Build/ |
<Project>/03_Docs/internal/audit/{YYYY-MM-DD}/ |
{project_root}/_outputs/audit/{YYYY-MM-DD}/ |
pbi-model-doc (v0.2.2+) |
Same | <Project>/03_Docs/internal/docs/{YYYY-MM-DD}/ |
{project_root}/_outputs/doc/{YYYY-MM-DD}/ |
pbi-theme-audit (v0.1.1+) |
Same | <Project>/03_Docs/internal/audit/{YYYY-MM-DD}/ (shared with model-audit, prefixed by tool) |
{project_root}/_outputs/theme-audit/{YYYY-MM-DD}/ |
Detection algorithm (each companion skill runs this independently)
Walk up from the .pbip path. If the chain matches:
02_Build/<Project>/<env>/Power BI Repository/<client>/
where <env> matches "01. Dev" | "02. Test" | "03. Prod"
→ LIFECYCLE_MODE = true
→ project_folder = parent of "02_Build"
→ output goes to $project_folder/03_Docs/internal/{audit|docs}/{YYYY-MM-DD}/
If lifecycle is not detected, the companion skill falls back to its standalone behavior. Independent use of any companion skill remains fully supported — this contract enriches the experience inside lifecycle without making lifecycle a hard dependency.
Internal vs client_handoff promotion (manual operator action)
03_Docs/internal/ is gitignored by the lifecycle's .gitignore template. Audit and doc runs accumulate there as work-in-progress. When an audit or doc is approved for client sharing, the operator manually copies the artifact from internal/ to client_handoff/. The act of promoting is a deliberate, traceable decision recorded as a git commit:
[<Project>] Promote audit report to client_handoff
[<Project>] Promote model doc to client_handoff
This separation is intentional: companion skills generate freely into internal/, operator curates into client_handoff/. No skill ever writes directly into client_handoff/.
Cross-cutting · BYO theme
If the operator already has a theme.json (from pbi-theme skill, TabularEditor 3, an existing PBIP, or a brand consultant), Phase 1 supports seeding it into Global Theme/pbi-theme/theme/ so Phase 2 picks it up automatically.
Three input modes:
| Input | Behavior |
|---|---|
--theme <path> flag with existing JSON |
Skill validates JSON, copies to Global Theme/pbi-theme/theme/<filename>.json |
| Interactive prompt during Phase 1 | "Do you have an existing theme.json for this client? (path / no / skip)" — path: provide the path. no or skip: leave empty; Phase 2 uses Power BI default |
Pre-populated Global Theme/pbi-theme/theme/*.json (typical when pbi-theme already ran) |
Skill auto-detects + uses |
Validation on import (mandatory before copy):
- JSON parse validity — fail fast on broken JSON.
- Lowercase enum sweep — find every
"left","top","bottom","right","center","auto","bottomonly","fit"and verify they are CamelCase ("Left","Top", etc.). Power BI silently fails the theme if any enum is lowercase. namefield length — must be ≤ 16 characters. Power BI uses this name to derive internal filenames during save (with a 17-char hash +.jsonsuffix).- Hex color check — every value matching
/^#[0-9a-fA-F]{6}$/is valid; warn on#XXXshorthand or invalid characters.
If any validation fails, the agent reports the specific error and offers to either fix the JSON in place (with operator confirmation) or stop.
Hard safety rules
Phase 1 is strictly additive. The skill must never affect anything that already exists on disk.
Forbidden operations in Phase 1, no exception:
| Operation | Why forbidden |
|---|---|
Remove-Item, rm, del, rmdir |
Phase 1 never deletes. |
Move-Item, mv |
Phase 1 never moves. |
Rename-Item, ren |
Phase 1 never renames. |
Copy-Item, cp, xcopy, robocopy of operator content |
Phase 1 never copies the operator's content (the BYO theme copy is the only exception, and only after explicit operator confirmation of source path). |
| Any write to an existing file (overwrite, append, replace) | Phase 1 only creates new folders and net-new placeholder files. |
git init, git add, git commit, any Git command |
Git belongs to Phase 3. |
gh repo create or any GitHub operation |
GitHub belongs to Phase 3. |
| Creating folders or files the operator did not confirm | Every creation requires explicit operator confirmation of the full creation plan. |
Only allowed write operations:
New-Item -ItemType Directory -Path "<absolute path>"— only whenTest-Path "<absolute path>"returns$false.New-Item -ItemType File -Path "<absolute path>" -Value "<placeholder content>"— only for the placeholder template files defined in this skill, whenTest-Pathreturns$false.New-Item -ItemType Junction -Path "<short path>" -Target "<long path>"— only after path budget detection prompts the operator.Copy-Item <theme-source> -Destination "<Global Theme>/pbi-theme/theme/"— only after BYO theme validation succeeds and the operator confirms the source path.
If the operator asks for any forbidden operation during Phase 1, refuse and explain which Phase handles it.
Phase 1 execution algorithm
STEP 1 · Clients root
Ask: absolute local path of Clients root (e.g., C:\Clients\ or OneDrive sync path).
Validate:
- Test-Path returns $true → continue
- Reject SharePoint URLs (https://*.sharepoint.com/...)
- Reject relative paths (.\, ..\)
- Reject drive-letter-only paths (C:\)
- If $false → STOP, tell operator the path does not exist locally
STEP 2 · Client
Ask: client name (string).
Compute:
$clientPath = "$clientsRoot\$clientName"
$pathLength = $clientPath.Length
Path budget decision (see "Pre-flight: path budget detection" above).
If junction needed → confirm + create.
Build creation plan for client-level folders:
$clientPath\Global Info\ (+ .gitkeep)
$clientPath\Global Theme\
$clientPath\Global Theme\logo\
$clientPath\Global Theme\brand-guideline\
$clientPath\Global Theme\pbi-theme\
$clientPath\Global Theme\pbi-theme\theme\
$clientPath\Global Theme\pbi-theme\design-system\
$clientPath\Global Theme\pbi-theme\design-tokens\
$clientPath\Global Theme\pbi-theme\html-visual-style\
$clientPath\Power BI Repository\
$clientPath\Power BI Repository\01. Dev\ (+ .gitkeep)
$clientPath\Power BI Repository\02. Test\ (+ .gitkeep)
$clientPath\Power BI Repository\03. Prod\ (+ .gitkeep)
Test-Path each. Skip those that exist. Present plan + ask y/N.
On y: New-Item Directory + .gitkeep files for missing entries.
BYO theme prompt:
Ask: "Do you have an existing theme.json? (path / no / skip)"
On path: validate (JSON, enums, name length, hex) → copy into Global Theme/pbi-theme/theme/
On no/skip: leave folder empty; Phase 2 will use Power BI default
If $clientPath already fully scaffolded → report "client already set up" and continue to STEP 3.
STEP 3 · Project
Ask: project name (string).
Compute:
$devProjectPath = "$clientPath\Power BI Repository\01. Dev\$projectName"
$testProjectPath = "$clientPath\Power BI Repository\02. Test\$projectName"
$prodProjectPath = "$clientPath\Power BI Repository\03. Prod\$projectName"
If Test-Path $devProjectPath returns $true → STOP. Tell operator the project folder already exists,
ask them to choose a different name. Phase 1 must never touch an existing project folder.
Build full creation plan for the project subtree (Dev populated, Test+Prod replicated empty):
Dev:
$devProjectPath\01_Context\
scope.md (placeholder)
source.json (placeholder, "environment": "dev")
theme.md (placeholder)
wireframe_to_data_mapping.md (placeholder)
$devProjectPath\02_Build\ (empty; Phase 2 target)
$devProjectPath\03_Docs\internal\audit\
$devProjectPath\03_Docs\internal\docs\
$devProjectPath\03_Docs\client_handoff\audit\
$devProjectPath\03_Docs\client_handoff\docs\
Test:
$testProjectPath\01_Context\
source.json (placeholder, "environment": "test")
+ same other files as Dev
$testProjectPath\02_Build\
$testProjectPath\03_Docs\... (same subtree)
Prod:
$prodProjectPath\01_Context\
source.json (placeholder, "environment": "prod")
+ same other files as Dev
$prodProjectPath\02_Build\
$prodProjectPath\03_Docs\... (same subtree)
Present plan + ask y/N.
On y: New-Item Directory + New-Item File for every entry.
STEP 4 · Closing handoff
Print:
- Absolute path of the Dev project folder (use junction path if junction was created)
- ASCII tree of the per-project subtree
- Junction info if created
- One-line note: "Phase 1 complete. To scaffold the .pbip file, run mode 2 (PBIP scaffold).
To version-control this client, run mode 3 (Git init). For audits and docs, see
pbi-model-audit / pbi-theme-audit / pbi-model-doc."
Placeholder file contents
01_Context/source.json (per env, env = "dev" | "test" | "prod")
{
"environment": "dev",
"description": "Connection parameters that change between environments. Phase 5 (env promotion) reads this file from the target env and rewrites the corresponding values in expressions.tmdl. Never store secrets here.",
"connections": [
{
"name": "<connection-name>",
"type": "<Web | SQL | Databricks | OData | File | Other>",
"url": "<https://server-or-endpoint>",
"catalog": "<database-or-catalog>",
"schema": "<schema-or-folder-or-lakehouse-path>",
"auth": {
"method": "<OAuth | Anonymous | Key | UsernamePassword>",
"credentialsHandledBy": "Power BI Desktop / Service (never store secrets in this file)"
}
}
]
}
01_Context/scope.md, theme.md, wireframe_to_data_mapping.md
Empty single-line placeholders with H1 title only. The operator fills these as the project starts.
theme.md per-project override format (Phase 4 contract)
If a project requires a theme override on top of the Global Theme, the operator edits 01_Context/theme.md to declare the override:
---
inherits: ../../../../Global Theme/pbi-theme/theme/<theme-filename>.json
overrides:
dataColors[0]: "#7C3AED"
neutral: "#7C3AED"
reason: "Client requested purple primary for this project to avoid color collision with another product"
---
Phase 4 (theme refresh) reads this and applies overrides on top of the Global Theme during refresh. If no overrides: block exists, the project inherits the Global Theme verbatim.
.gitkeep
Empty file. Just exists so git tracks the parent folder.
Phase 2 · PBIP Scaffold from scratch (AVAILABLE)
Phase 2 generates a working .pbip file inside <Project>/02_Build/ from scratch, using the PBIR-Legacy format. The result opens cleanly in Power BI Desktop with the client theme auto-applied (if Global Theme has one) and Auto Date/Time disabled by default.
This was calibrated against multiple iterations during 2026-05-04 — the recipe below is exact and exhaustive.
Pre-flight checks
Before generating any file, the agent verifies all of the following:
- The client root exists and contains
Global Info/,Global Theme/,Power BI Repository/01. Dev/<Project>/02_Build/. If any is missing → instruct operator to run Phase 1 first. Stop. <Project>/02_Build/is empty. If a.pbipor any subfolder already exists there → STOP, ask operator if they want to delete the existing scaffold first (Phase 2 never overwrites).- Path budget check — if
<Project>/02_Build/absolute path > 100 chars and no junction exists → warn operator, recommend running Phase 1 with junction or moving the client root. - MAX_PATH headroom calculation:
- Base path:
<absolute path to 02_Build> - Plus deepest expected file:
Report\StaticResources\RegisteredResources\<theme-filename>.json(~78 chars) - Plus deepest TMDL:
SemanticModel\definition\tables\<TableName>.tmdl(~52 chars + table name) - Total must be ≤ 256 chars assuming reasonable table names (≤ 30 chars). If headroom < 30 chars → strongly recommend junction.
- Base path:
- BYO theme detection: check
Global Theme/pbi-theme/theme/*.json:- If exactly one
.jsonexists → use it, validate enums + name length before copying. - If multiple → ask operator which to use.
- If none → ask: "No theme found. Generate one now via the
pbi-themecompanion skill, drop a JSON manually intoGlobal Theme/pbi-theme/theme/, or scaffold the PBIP without a custom theme (Power BI default)? (theme/skill/skip)"
- If exactly one
LOCKED Phase 2 recipe (every aspect calibrated empirically)
Generate a .pbip directory tree with this exact structure:
<Project>/02_Build/
<Project>.pbip ← wrapper, schema pbipProperties/1.0.0
Report/ ← short folder name (NOT {Project}.Report/)
.platform ← gitIntegration/2.0.0, fresh logicalId GUID
definition.pbir ← schema definitionProperties/2.0.0, version 4.0
report.json ← AT ROOT (NOT under definition/), PBIR-Legacy format
StaticResources/
RegisteredResources/<theme-filename>.json ← copy of the master theme from Global Theme
SharedResources/BaseThemes/CY26SU04.json ← built-in base theme (must be present)
SemanticModel/ ← short folder name (NOT {Project}.SemanticModel/)
.platform ← fresh logicalId GUID, displayName=project name
definition.pbism ← schema definitionProperties/1.0.0, version 4.2
definition/
database.tmdl ← compatibilityLevel: 1600
model.tmdl ← MUST include __PBI_TimeIntelligenceEnabled = 0
expressions.tmdl ← env parameters with fresh lineageTag GUIDs
Critical recipe constraints
| Aspect | Rule | Why |
|---|---|---|
| Format | PBIR-Legacy (NOT PBIR-modular) | Power BI Desktop converts modular → legacy on save; theme does not auto-apply when generated as modular |
| Encoding | UTF-8 without BOM for ALL files: .pbip, .platform, .pbir, .pbism, .json, .tmdl |
BOM breaks Power BI Desktop parsing silently |
Theme name field |
≤ 16 chars | Power BI derives internal filenames (with 17-char hash + .json suffix) from this; longer names create paths that exceed 256 chars |
| Enum values | Case-sensitive: Left, Top, Bottom, Right, Center, Auto, BottomOnly, Fit |
NEVER lowercase — Power BI silently rejects the theme |
| Internal folder names | Use short Report/ and SemanticModel/ |
Saves ~17 chars of path per nesting level vs {Project}.Report/ |
| MAX_PATH | 256 chars | Power BI Desktop limit, stricter than Windows 260; pre-flight check mandatory |
| Section name | 20 hex chars random per project | Generated fresh per scaffold |
| GUIDs | Fresh logicalId per .platform, fresh lineageTag per expression |
Each scaffold gets unique IDs |
| Base theme | Always copy BaseThemes/CY26SU04.json (or current Power BI default) into SharedResources/ |
Required even when custom theme is also present |
| Custom theme | Copy from Global Theme/pbi-theme/theme/ into Report/StaticResources/RegisteredResources/ |
Source-of-truth lives in Global Theme |
compatibilityLevel |
1600 in database.tmdl |
NOT 1567 |
model.tmdl annotation |
annotation __PBI_TimeIntelligenceEnabled = 0 |
MANDATORY. Disables Auto Date/Time. Without this, Power BI auto-creates DateTableTemplate_<GUID>.tmdl (60+ chars) for every date column when data is loaded — instantly explodes MAX_PATH 256 |
model.tmdl annotation |
annotation PBI_ProTooling = ["DevMode"] |
Required for the file to be recognized as Dev Mode |
definition.pbism |
version: "4.2" |
Schema version |
definition.pbir |
version: "4.0", schema definitionProperties/2.0.0 |
Schema version |
report.json |
At root of Report/, NOT in definition/ |
PBIR-Legacy convention |
report.json config |
Stringified inner JSON with themeCollection (baseTheme + customTheme), settings block |
The customTheme name references the filename in RegisteredResources/ |
report.json resourcePackages |
Numeric types: 1 = RegisteredResources, 2 = SharedResources, 201 = CustomTheme, 202 = BaseTheme |
Power BI rejects string types |
Theme-optional behavior (graceful degradation)
Phase 2 must NOT fail when no custom theme exists. Pre-flight check:
if Test-Path "<Client>/Global Theme/pbi-theme/theme/*.json":
→ copy theme into PBIP RegisteredResources, write full themeCollection (baseTheme + customTheme)
else:
→ omit customTheme from themeCollection
→ omit RegisteredResources entry from resourcePackages (or leave items array empty)
→ PBIP opens with Power BI default theme (CY26SU04 only)
→ emit warning: "No custom theme found. PBIP opens with Power BI default. Run pbi-theme later, then re-run this skill in mode 4 (refresh-theme) to propagate."
This makes pbi-theme genuinely optional — clients without it can still generate PBIPs.
Phase 2 execution algorithm
STEP 1 · Pre-flight (see "Pre-flight checks" above; abort on any failure).
STEP 2 · Generate fresh GUIDs and IDs
$reportLogicalId = New-Guid
$smLogicalId = New-Guid
$sectionName = (-join (1..20 | ForEach-Object { '0123456789abcdef'[(Get-Random -Maximum 16)] }))
$expressionGuids = @{
Source_Url = New-Guid
Source_Schema = New-Guid
Source_Type = New-Guid
Source_Auth_Method = New-Guid
}
STEP 3 · Compute target paths
$buildPath = "<Client>/Power BI Repository/01. Dev/<Project>/02_Build"
$reportPath = "$buildPath/Report"
$smPath = "$buildPath/SemanticModel"
$regResPath = "$reportPath/StaticResources/RegisteredResources"
$sharedResPath = "$reportPath/StaticResources/SharedResources/BaseThemes"
STEP 4 · Determine theme handling (BYO theme detection from pre-flight)
$themeFilename = (filename of selected JSON or null)
$themeSourcePath = (path to selected JSON or null)
STEP 5 · Confirm with operator
Present:
- Project name
- Target build path
- Theme handling (with theme / without)
- List of files about to be created (~10-12 files)
Ask: "Generate? (y/N)"
STEP 6 · Generate files (each as UTF-8 WITHOUT BOM)
6.1 Create folders: $reportPath, $smPath, $regResPath, $sharedResPath, "$smPath/definition"
6.2 Write <Project>.pbip:
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/pbiProperties/1.0.0/schema.json",
"version": "1.0",
"artifacts": [
{ "report": { "path": "Report" } }
]
}
6.3 Write Report/.platform with $reportLogicalId
6.4 Write Report/definition.pbir
6.5 Write Report/report.json (with $sectionName, themeCollection, resourcePackages)
6.6 Copy CY26SU04.json into $sharedResPath (the bytes of this file are deterministic; embed as a resource bundled with the skill)
6.7 If $themeSourcePath: validate (enums, name length, hex) → copy to $regResPath
6.8 Write SemanticModel/.platform with $smLogicalId
6.9 Write SemanticModel/definition.pbism (version 4.2)
6.10 Write database.tmdl (compatibilityLevel 1600)
6.11 Write model.tmdl with __PBI_TimeIntelligenceEnabled = 0 + PBI_ProTooling = ["DevMode"]
6.12 Write expressions.tmdl with the 4 source parameters + fresh lineageTags
STEP 7 · Validate output
- Test-Path on every generated file
- Test JSON parse on .pbip, .platform, .pbir, .pbism, report.json, theme.json
- Lowercase enum sweep on theme.json (must find ZERO lowercase enums)
- File-by-file BOM check (must be ZERO BOMs)
- Path budget recheck on every generated path
STEP 8 · Closing handoff
Print:
- Absolute path of the .pbip
- "Open with Power BI Desktop. Theme should auto-apply on first open. If you load data
and Power BI prompts you about Auto Date/Time, decline (this skill disabled it by design).
To version-control this work, run mode 3 (Git init)."
Phase 2 hard safety rules
- Never overwrite an existing PBIP. If
02_Build/is non-empty, stop. - Never modify Global Theme. Phase 2 only READS the theme JSON; copies it into the PBIP's RegisteredResources/.
- Never write BOM. Use explicit
[System.IO.File]::WriteAllText($path, $content, [System.Text.UTF8Encoding]::new($false))in PowerShell. - Never run Power BI Desktop programmatically to "validate" the generated PBIP — only file-level validation. Operator opens it manually.
Phase 2 reference script
The calibrated reference algorithm lives in Gus bavia brand/_step-pbip-gen-from-scratch.ps1 from the calibration session. Future versions of this skill should bundle this script under pbi-lifecycle/references/phase2-pbip-scaffold.ps1 so the agent can invoke it directly. For v0.3.0, the agent generates files inline using the recipe above.
Phase 3 · Git Tracking + GitHub repo (AVAILABLE)
Phase 3 initializes git at the client root (one repo per client, all projects share the same repo), creates a private GitHub repo via gh CLI, makes the snapshot-zero commit, and establishes the project-scoped commit pattern for ongoing work.
This was validated end-to-end on 2026-05-04 against a disposable test project.
Why one repo per client (locked architecture decision)
| Ganho | Why |
|---|---|
| Scaffold zero pra novo projeto | Cria pasta dentro de Power BI Repository/01. Dev/ e pronto, já tá versionado |
| Global Theme convive com os projetos | Mudou cor da marca? 1 commit alcança todos projetos do cliente |
| Visão consolidada do cliente | git log mostra atividade de todos devs em todos projetos do cliente |
Custo (e mitigação): histórico mistura projetos, mitigado pela convenção [ProjectName] msg no commit + git log -- "<path>" filtra. Validado.
When 1 repo por projeto would make sense (NOT the default): if a single project must be shared with an external party without exposing other projects of the same client. Phase 3 v0.3.0 does not implement this — clients with that need stay on the locked default.
Pre-flight checks (every gotcha calibrated)
The agent verifies in this exact order:
Pre-flight 1 · Power BI Desktop is closed
Get-Process -Name 'PBIDesktop' -ErrorAction SilentlyContinue
If a process exists → ask operator to close it. Power BI Desktop holds locks on .pbip files; git operations during an open session can corrupt the repo.
Pre-flight 2 · gh CLI installed
$ghPath = "C:\Program Files\GitHub CLI\gh.exe"
if (-not (Test-Path $ghPath)) {
# Install via winget
winget install --id GitHub.cli --accept-source-agreements --accept-package-agreements -e
}
If install fails → instruct operator to install manually from https://cli.github.com/ and re-run.
Pre-flight 3 · gh authenticated
& $ghPath auth status
If exit code ≠ 0 → run interactive login:
The skill prints to operator:
"Run this in a fresh PowerShell:
gh auth login
Answer the prompts:
- Where: GitHub.com
- Protocol: HTTPS
- Authenticate Git: Yes
- Auth method: Login with a web browser
- Open the URL printed, paste the device code, authorize.
Tell me when done."
Operator confirms → recheck `gh auth status`.
Pre-flight 4 · gh auth setup-gi
…(truncated)