AI Prompt: Forma3D.Connect — CodeCharta City Visualization (Option C)¶
Purpose: Integrate CodeCharta into the CI/CD pipeline to generate a 3D city map from SonarCloud + git history, served from the existing docs container with shareable URLs
Estimated Effort: 2–4 hours
Prerequisites: Human has completed the manual setup steps listed in Section: Human Setup Required
Output: Pipeline stage, Nginx CORS config, Dockerfile change, shareable visualization URLs
Research: codecharta-city-visualization-research.md
Status: ✅ DONE
🎯 Mission¶
Add a CodeCharta pipeline stage that generates a .cc.json city map by pulling metrics from SonarCloud and parsing git history, then serve the resulting file from the existing docs container so the team can visualize the codebase as a 3D city via the publicly hosted CodeCharta Web Studio.
What this delivers:
- A new CodeCharta generation step in the
BuildAndPackagestage that producesforma3d.cc.json— a merged map combining SonarCloud metrics (complexity, code smells, coverage, tech debt) with git history metrics (authors, commits, churn, coupling) - The
.cc.jsonfile baked into the existing docs Docker image at/codecharta/forma3d.cc.json - A CORS-enabled Nginx location (
/codecharta/) on the docs container so the hosted CodeCharta Web Studio can fetch the file via XHR - Shareable URLs that open the city map with preconfigured views — anyone with the link sees the visualization instantly in their browser
Why this matters:
SonarCloud provides numbers. CodeCharta makes them tangible. The city metaphor immediately surfaces hotspots (large, complex, frequently-changed files), knowledge silos (single-author files), and temporal coupling (files that always change together). This is invaluable for sprint planning, retrospectives, and onboarding.
Critical constraints:
- No new Docker container — reuse the existing
forma3d-connect-docscontainer - No new DNS record — the file is served from
staging-connect-docs.forma3d.be - No changes to the existing SonarCloud analysis stage — CodeCharta reads from the SonarCloud API after analysis completes
- The pipeline checkout must use
fetchDepth: 0(full git history) for the CodeCharta step - The
codecharta/codecharta-analysisDocker image (~1.2 GB) is used only during CI — it is NOT deployed to staging - The SonarCloud token for CodeCharta is a read-only user token, separate from the service connection used for analysis
- The
.cc.jsonfile contains only file paths and numeric metrics — no source code
⚠️ Human Setup Required (Before You Start)¶
The following steps must be completed by a human before the AI begins implementation.
Checklist¶
| # | Action | Where | Output Needed |
|---|---|---|---|
| 1 | Generate a SonarCloud user token (read-only) | sonarcloud.io → My Account → Security → Generate Token (type: User) | Token string (starts with squ_) |
| 2 | Add the token as a secret pipeline variable | Azure DevOps → Pipelines → Library → forma3d-staging variable group → Add SONARCLOUD_CODECHARTA_TOKEN |
Secret variable available to pipeline |
| 3 | Verify SonarCloud project key | SonarCloud → Project → Information | Confirm project key is devgem_forma-3d-connect |
Values the AI Needs¶
Once the human has completed the checklist above, confirm the following values:
| Value | Expected | Where It Goes |
|---|---|---|
SONARCLOUD_CODECHARTA_TOKEN |
Secret variable in forma3d-staging |
Pipeline script (masked) |
| SonarCloud URL | https://sonarcloud.io |
ccsh sonarimport command |
| Project Key | devgem_forma-3d-connect |
ccsh sonarimport command |
| Docs domain | staging-connect-docs.forma3d.be |
Shareable URLs |
📌 Context (Current State)¶
Pipeline Structure¶
The relevant pipeline stages in azure-pipelines.yml:
ValidateAndTest
├── Lint (MS-hosted: ubuntu-latest)
├── TypeCheck (Self-hosted: DO-Build-Agents)
├── UnitTests (Self-hosted: DO-Build-Agents)
└── CodeQuality (MS-hosted: ubuntu-latest, depends on UnitTests)
├── SonarCloudPrepare@4
├── SonarCloudAnalyze@4
└── SonarCloudPublish@4
BuildAndPackage
├── DetectAffected
├── BuildAll (all service Docker images)
├── PackageDocs (depends on DetectAffected, builds docs Docker image)
│ └── docker buildx build --file deployment/docs/Dockerfile ... --push .
└── PackageEventCatalog
Key observations:
- The PackageDocs job uses the repo root (.) as Docker build context
- The PackageDocs job depends on DetectAffected and only runs when docsAffected == true
- The Dockerfile at deployment/docs/Dockerfile is a multi-stage build: Python builder → Nginx runtime
- The current checkout in PackageDocs uses fetchDepth: 1 (shallow clone)
Docs Nginx Configuration¶
Current deployment/docs/nginx.conf serves static HTML from /usr/share/nginx/html. There is no CORS configuration. The server block has locations for / (static files), cached assets, /health, and /health/live.
Docs Dockerfile¶
Current deployment/docs/Dockerfile:
- Builder stage: Python 3.12, Zensical, PlantUML rendering, builds to ./site
- Production stage: nginx:alpine, copies built site to /usr/share/nginx/html, injects build number, generates build-info.json
📋 Step-by-Step Implementation¶
Step 1: Add CodeCharta Generation to the Pipeline¶
Add a new job GenerateCodeCharta in the BuildAndPackage stage. This job:
- Depends on the ValidateAndTest stage completing (SonarCloud analysis must finish first so its API has results)
- Runs on MS-hosted ubuntu-latest (no load on self-hosted agents)
- Uses codecharta/codecharta-analysis Docker image to generate the map
- Publishes the .cc.json as a pipeline artifact AND places it where PackageDocs can consume it
Insert this job in the BuildAndPackage stage, before PackageDocs:
# ----------------------------------------------------------------------
# Job: Generate CodeCharta City Map
# ----------------------------------------------------------------------
# Generates a merged .cc.json from SonarCloud metrics + git history.
# The file is published as an artifact and downloaded by PackageDocs
# to be baked into the docs Docker image.
# Only runs on main branch (SonarCloud main analysis must exist).
# ----------------------------------------------------------------------
- job: GenerateCodeCharta
displayName: 'Generate CodeCharta City Map'
cancelTimeoutInMinutes: 0
condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
pool:
vmImage: 'ubuntu-latest'
steps:
- checkout: self
fetchDepth: 0
clean: true
- script: |
set -e
echo "🏙️ Generating CodeCharta city map..."
docker run --rm \
-v $(Build.SourcesDirectory):/src \
-w /src \
codecharta/codecharta-analysis bash -c '
git config --global --add safe.directory /src &&
echo "📊 Importing SonarCloud metrics..." &&
ccsh sonarimport \
https://sonarcloud.io \
devgem_forma-3d-connect \
--user-token='"'"'$(SONARCLOUD_CODECHARTA_TOKEN)'"'"' \
-o sonar.cc.json -nc &&
echo "📜 Parsing git history..." &&
ccsh gitlogparser repo-scan \
--repo-path /src \
--add-author \
-o git.cc.json -nc &&
echo "🔀 Merging Sonar + Git metrics..." &&
ccsh merge --leaf sonar.cc.json git.cc.json \
-o /src/codecharta/forma3d.cc.json -nc
'
echo "✅ CodeCharta map generated: codecharta/forma3d.cc.json"
ls -la codecharta/
displayName: 'Generate city map from SonarCloud + Git'
env:
SONARCLOUD_CODECHARTA_TOKEN: $(SONARCLOUD_CODECHARTA_TOKEN)
- task: PublishPipelineArtifact@1
displayName: 'Publish CodeCharta Artifact'
inputs:
targetPath: '$(Build.SourcesDirectory)/codecharta'
artifact: 'codecharta-map'
publishLocation: 'pipeline'
Important: The output directory codecharta/ is created inside the repo root. Since the docs Docker build context is . (the repo root), the file will be available to the Dockerfile's COPY instruction.
Step 2: Make PackageDocs Depend on GenerateCodeCharta¶
Update the PackageDocs job to depend on GenerateCodeCharta in addition to DetectAffected. The CodeCharta artifact must be available before the docs image is built.
Current:
- job: PackageDocs
displayName: 'Build & Push Docs Image'
cancelTimeoutInMinutes: 0
dependsOn: DetectAffected
condition: eq(dependencies.DetectAffected.outputs['affected.docsAffected'], 'true')
Change to:
- job: PackageDocs
displayName: 'Build & Push Docs Image'
cancelTimeoutInMinutes: 0
dependsOn:
- DetectAffected
- GenerateCodeCharta
condition: |
and(
not(failed('GenerateCodeCharta')),
eq(dependencies.DetectAffected.outputs['affected.docsAffected'], 'true')
)
Why not(failed(...)) instead of succeeded(...): The GenerateCodeCharta job only runs on main. On PR branches it is skipped. Using not(failed(...)) means the docs build proceeds whether CodeCharta succeeded OR was skipped.
Add a download step at the beginning of the PackageDocs job steps, after checkout:
- task: DownloadPipelineArtifact@2
displayName: 'Download CodeCharta Map'
condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
inputs:
source: 'current'
artifact: 'codecharta-map'
path: '$(Build.SourcesDirectory)/codecharta'
On non-main branches the download is skipped, and the Dockerfile handles the missing file gracefully (see Step 4).
Step 3: Update Nginx Configuration¶
Add a CORS-enabled location block to deployment/docs/nginx.conf for serving the .cc.json file to the CodeCharta Web Studio at codecharta.com.
Add this location block inside the server { } block, after the existing location / { } block:
# CodeCharta city map — served to codecharta.com Web Studio via XHR
location /codecharta/ {
add_header Access-Control-Allow-Origin "https://codecharta.com" always;
add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
add_header Access-Control-Allow-Headers "Range" always;
add_header Cache-Control "no-cache";
# Handle CORS preflight
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin "https://codecharta.com" always;
add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
add_header Access-Control-Allow-Headers "Range" always;
add_header Access-Control-Max-Age 86400;
add_header Content-Length 0;
add_header Content-Type text/plain;
return 204;
}
try_files $uri =404;
}
Why these specific headers:
- Access-Control-Allow-Origin: https://codecharta.com — restricts CORS to only the CodeCharta Web Studio (not a wildcard *)
- Access-Control-Allow-Headers: Range — CodeCharta may use range requests for large files
- Cache-Control: no-cache — ensures users always see the latest map after a pipeline run
- The OPTIONS preflight handler is needed because the browser sends a preflight request for cross-origin XHR
Step 4: Update Docs Dockerfile¶
Add a COPY instruction to the production stage of deployment/docs/Dockerfile to include the CodeCharta map in the docs image.
Add this after the existing COPY --from=builder /repo/site /usr/share/nginx/html line (line 84) and before the RUN find ... sed line (line 89):
# Copy CodeCharta city map (generated by CI pipeline).
# On PR branches the file may not exist — the wildcard glob ensures the
# COPY does not fail when the source is absent.
COPY codecharta/forma3d.cc.jso[n] /usr/share/nginx/html/codecharta/forma3d.cc.json
Why the [n] glob trick: Docker's COPY instruction fails if the source file does not exist. On PR branches, the CodeCharta generation step is skipped, so codecharta/forma3d.cc.json won't be present. Using forma3d.cc.jso[n] is a glob pattern that matches the file if it exists but does not fail if it doesn't (Docker treats glob patterns as optional). This avoids the need for a separate Dockerfile or conditional logic.
Alternative if the glob trick doesn't work with buildx: Create an empty placeholder file in the checkout step of PackageDocs:
- script: |
mkdir -p codecharta
[ -f codecharta/forma3d.cc.json ] || echo '{}' > codecharta/forma3d.cc.json
displayName: 'Ensure CodeCharta placeholder exists'
Step 5: Ensure Docs Rebuild Is Triggered on Main¶
The PackageDocs job currently only runs when docsAffected == true (detected by nx affected). The CodeCharta .cc.json file is generated outside of Nx's affected detection — it changes every build even if docs content hasn't changed.
Option A (simple): The docs image should always rebuild on main when CodeCharta runs. Update the condition:
condition: |
or(
eq(dependencies.DetectAffected.outputs['affected.docsAffected'], 'true'),
and(
eq(variables['Build.SourceBranch'], 'refs/heads/main'),
succeeded('GenerateCodeCharta')
)
)
This means: build docs if either (a) docs content changed, or (b) we're on main and CodeCharta succeeded.
Option B (conservative): Keep the existing condition unchanged. The docs image only updates when docs files change. The CodeCharta map updates at the same time. Downside: the map could be stale if only code changes occur without doc changes. Since docs tend to change frequently in this project, this may be acceptable initially.
Choose Option A for freshness or Option B for minimal pipeline change. Option A is recommended.
📁 Files to Create / Modify¶
| Action | File | What Changes |
|---|---|---|
| Modify | azure-pipelines.yml |
Add GenerateCodeCharta job; update PackageDocs dependency and condition; add artifact download step |
| Modify | deployment/docs/nginx.conf |
Add /codecharta/ location with CORS headers |
| Modify | deployment/docs/Dockerfile |
Add COPY for codecharta/forma3d.cc.json |
| No change | sonar-project.properties |
Already configured — CodeCharta reads via API |
| No change | deployment/staging/docker-compose.yml |
No new container needed |
✅ Verification¶
After First Pipeline Run on Main¶
- Check
GenerateCodeChartajob — should complete in ~2–3 minutes with log output showing sonarimport, gitlogparser, and merge steps - Check pipeline artifacts — a
codecharta-mapartifact should be published containingforma3d.cc.json - Check
PackageDocsjob — should show "Download CodeCharta Map" step completing successfully - Check deployed docs site —
curl -I https://staging-connect-docs.forma3d.be/codecharta/forma3d.cc.jsonshould return 200 withAccess-Control-Allow-Origin: https://codecharta.comheader
Shareable URL Test¶
Open the following URL in a browser:
https://codecharta.com/visualization/app/index.html?file=https://staging-connect-docs.forma3d.be/codecharta/forma3d.cc.json&area=ncloc&height=cognitive_complexity&color=code_smells
Expected result: - The CodeCharta Web Studio loads - A 3D city map of the Forma3D.Connect codebase appears - Buildings represent source files — area shows lines of code, height shows cognitive complexity, color shows code smells
CORS Verification¶
curl -H "Origin: https://codecharta.com" \
-I https://staging-connect-docs.forma3d.be/codecharta/forma3d.cc.json
Expected headers in response:
Access-Control-Allow-Origin: https://codecharta.com
Access-Control-Allow-Methods: GET, OPTIONS
Troubleshooting¶
| Symptom | Likely Cause | Fix |
|---|---|---|
GenerateCodeCharta fails with "401 Unauthorized" |
SonarCloud token is invalid or expired | Regenerate token at sonarcloud.io → My Account → Security; update SONARCLOUD_CODECHARTA_TOKEN in variable group |
GenerateCodeCharta fails with "Project not found" |
Project key mismatch | Verify project key is devgem_forma-3d-connect at sonarcloud.io → Project → Information |
.cc.json shows 0 files from git |
Shallow clone (fetchDepth != 0) | Verify fetchDepth: 0 in the checkout step of GenerateCodeCharta |
| Web Studio shows CORS error | Missing or wrong CORS header | Check deployment/docs/nginx.conf has the /codecharta/ location with Access-Control-Allow-Origin: https://codecharta.com; verify Nginx was reloaded |
| Web Studio loads but city is empty | .cc.json is the empty placeholder {} |
Check that GenerateCodeCharta ran on main and the artifact was downloaded by PackageDocs |
PackageDocs fails with "COPY failed: file not found" |
The glob trick didn't work | Use the placeholder fallback (create empty file in checkout step) |
PackageDocs skipped even though CodeCharta ran |
Condition not updated | Verify the PackageDocs condition includes the or(...) logic from Step 5 Option A |
🔗 Shareable URL Reference¶
Once deployed, share these bookmarkable views with the team:
| View | URL Parameters |
|---|---|
| Complexity hotspots | ?file=https://staging-connect-docs.forma3d.be/codecharta/forma3d.cc.json&area=ncloc&height=cognitive_complexity&color=code_smells |
| Change frequency | ?file=...&area=ncloc&height=number_of_commits&color=number_of_authors |
| Tech debt vs coverage | ?file=...&area=ncloc&height=sqale_index&color=coverage |
| Bug-prone files | ?file=...&area=ncloc&height=fix_commits&color=bugs |
Base URL: https://codecharta.com/visualization/app/index.html
🚫 Out of Scope¶
- Self-hosted CodeCharta visualization container (Option D in research — only needed if codecharta.com dependency is unacceptable)
- Delta maps for sprint-over-sprint comparison (Phase 3 — requires archiving historical maps)
- Custom CodeCharta configuration or metric selection (start with defaults, tune later)
- Per-project city maps (start with full monorepo map; per-service maps can be added later)
- Adding CodeCharta link to the docs site navigation (separate small task)
🎬 Execution Order¶
- Read
azure-pipelines.yml— understand theBuildAndPackagestage structure,PackageDocsjob, andDetectAffectedjob - Read
deployment/docs/nginx.conf— understand current server block structure - Read
deployment/docs/Dockerfile— understand the production stageCOPYsequence - Add
GenerateCodeChartajob toazure-pipelines.yml - Update
PackageDocsdependencies, condition, and add download step - Add
/codecharta/location block todeployment/docs/nginx.conf - Add
COPYinstruction todeployment/docs/Dockerfile - Validate no lint or build errors were introduced
END OF PROMPT
Generate a CodeCharta city map from SonarCloud + git history in CI, serve the .cc.json from the existing docs container with CORS headers, and provide shareable URLs that open the hosted CodeCharta Web Studio with preconfigured views.