원클릭으로
ai-docs-autopilot
// End-to-end autopilot: inventory, generate, and submit WDK DDI API reference docs from a CSV file with no user interaction. Use when: running the full doc pipeline unattended, auto-generating and submitting DDI docs.
// End-to-end autopilot: inventory, generate, and submit WDK DDI API reference docs from a CSV file with no user interaction. Use when: running the full doc pipeline unattended, auto-generating and submitting DDI docs.
Generate WDK DDI API reference documentation pages from source code and stubs. Use when: writing API docs, generating reference pages, documenting a header, creating DDI documentation.
Submit generated WDK DDI API reference documentation as a PR to the wdk-ddi repo. Use when: submitting docs, creating a PR for DDI docs, pushing documentation changes.
Inventory and classify APIs listed in a pre-provided CSV for a WDK header file. Use when: inventorying a header, classifying APIs, validating a CSV for doc generation, checking what needs to be documented.
| name | ai-docs-autopilot |
| description | End-to-end autopilot: inventory, generate, and submit WDK DDI API reference docs from a CSV file with no user interaction. Use when: running the full doc pipeline unattended, auto-generating and submitting DDI docs. |
| argument-hint | Specify a header name (e.g. soundwireclass) and the path to the CSV file. |
Run the full DDI documentation pipeline end-to-end with no user interaction. The agent inventories APIs from a CSV, generates documentation pages, and submits them as a PR — stopping only on error.
No local repo clones required. All repo interactions use the Azure DevOps REST API. OS source lookups use the substrate-mcp MCP server.
| Parameter | Value |
|---|---|
| ADO Org | https://dev.azure.com/cpubwin |
| ADO Project | drivers |
| Docs Repo | wdk-ddi (branches: main, stubs/main) |
| Published Docs Repo | wdk-ddi-build (branch: live) |
| Published Docs Repo (staging) | windows-driver-docs-ddi (branch: staging) |
| Content Path | wdk-ddi-src/content/{header}/ |
| Stubs Branch | stubs/main (default; user may specify alternate like stubs/release-amethyst) |
| Header Name | Provided by the user (e.g. soundwireclass) |
| CSV Input | Provided by the user at any local path (e.g. D:\work\soundwireclass.csv) |
| Working Directory | Derived from CSV path (parent folder of the CSV file) |
| Output Path | {working_dir}\output\ |
| Style Guide | Read remotely from wdk-ddi repo: .github/copilot-instructions.md on main |
| User Alias | Auto-detected from CSV Owner column, $env:USERNAME, or az account show |
| Source Branch | Auto-generated as {user-alias}/{header}-update |
az) should be available for auth token acquisition. If not, the agent will prompt for an ADO Personal Access Token (PAT) once per session.substrate-mcp MCP server must be accessible for source code retrieval.microsoft.docs.mcp MCP server should be accessible for supplemental info (non-fatal if unavailable).This skill runs all three phases (inventory, generate, submit) in sequence with no user interaction between them. The process only stops if an error is encountered. Progress is reported to the console after each phase.
Track elapsed time and documents written across all three phases. Because each phase may run in a separate PowerShell process (variables don't survive between invocations), persist the start timestamp in a file so it can be read by later phases.
At the very beginning of the inventory.ps1 script, record the pipeline start time to a file in the working directory:
$pipelineStartTime = Get-Date
$pipelineStartTime.ToString('o') | Out-File -FilePath (Join-Path $workingDir '.pipeline-start') -Encoding utf8 -Force
$docsWrittenCount = 0
Phase 2 is orchestrated by the agent (not a single script). After writing each documentation file, the agent must:
{workingDir}\.pipeline-start.Run this in the terminal after each file is written:
$start = [DateTime]::Parse((Get-Content '{workingDir}\.pipeline-start' -Raw).Trim())
$elapsed = (Get-Date) - $start
Write-Host "[$($elapsed.ToString('hh\:mm\:ss'))] Wrote doc {N}: {filename.md}"
Alternatively, the agent can compute elapsed time from the stored timestamp itself and include it in its console message — the key requirement is that each doc written produces a visible [HH:MM:SS] Wrote doc N: filename progress line.
At the end of the submit.ps1 script, read the start time back and compute the total elapsed time:
$startFile = Join-Path $workingDir '.pipeline-start'
if (Test-Path $startFile) {
$pipelineStartTime = [DateTime]::Parse((Get-Content $startFile -Raw).Trim())
$totalElapsed = (Get-Date) - $pipelineStartTime
} else {
$totalElapsed = [TimeSpan]::Zero
}
Include the elapsed time and document count in the final summary banner (see Phase 3, step 15).
Read a pre-provided CSV of target API filenames, cross-reference each entry against existing docs, stubs, and published content via the ADO REST API, classify their status, and finalize the CSV.
Map each API entity to a filename using these prefixes:
| Prefix | Type | Example |
|---|---|---|
nf | Function | nf-soundwireclass-somefunc.md |
ns | Structure | ns-soundwireclass-some_struct.md |
ne | Enumeration | ne-soundwireclass-some_enum.md |
nc | Callback | nc-soundwireclass-evt_some_callback.md |
ni | IOCTL | ni-soundwireclass-ioctl_some_code.md |
nn | Interface | nn-soundwireclass-isome_interface.md |
nl | Class | nl-soundwireclass-some_class.md |
The filename pattern is: {prefix}-{header}-{api_name_lowercase}.md
Where {header} is the header name without the .h extension.
On the stubs branch, the header landing page is always named na-{header}.md. When it is copied to wdk-ddi-src/content/{header}/ on the main branch, it is renamed to index.md. In the published docs repo, it also appears as index.md. When cross-referencing, check for na-{header}.md on the stubs branch and index.md on main and in the published docs repo.
Some existing files in the published docs repo or on main may have non-standard filenames (e.g. an extra underscore like ns-header-_struct_name.md instead of ns-header-struct_name.md). These are historical naming errors. Do not rename existing files — doing so would break published links. When cross-referencing, also check for these variant filenames. For new APIs, always use the approved naming convention.
Write all PowerShell logic into a single self-contained .ps1 script file, then execute it in one terminal call. Do NOT run ADO REST calls or variable assignments as separate interactive terminal commands — PowerShell variables are lost between terminal invocations and long-running commands may time out and get moved to the background, breaking the workflow.
The pattern is:
{workingDir}\inventory.ps1 containing all the logic from the Inventory Procedure below. Use the create_file tool to write the script — this auto-saves the file to disk immediately, avoiding any unsaved-buffer issues.powershell -ExecutionPolicy Bypass -File "{workingDir}\inventory.ps1"The script should accept no parameters — hardcode the {header}, {csvPath}, {workingDir}, and {userAlias} values directly into the generated script.
Write a single inventory.ps1 script that performs all of the following steps, then execute it.
Strip the .h extension from the user-provided header name to get {header} (e.g. soundwireclass.h → soundwireclass).
Resolve the user alias for branch naming ({user-alias}). The alias identifies who is submitting the PR, not who owns the APIs. Try these sources in order and use the first non-empty value:
a. The Windows username: $env:USERNAME.
b. The Azure CLI identity: az account show --query user.name -o tsv, extracting the alias portion before @.
The resolved alias is used later for the branch name {user-alias}/{header}-update. Hardcode it into the generated scripts.
Resolve paths. The user provides a CSV path. Derive the working directory from it. The script should validate the CSV exists and read it:
$ErrorActionPreference = "Stop"
$header = "{header}"
$csvPath = "{user-provided CSV path}"
$workingDir = Split-Path $csvPath -Parent
# Start the pipeline timer and persist it for later phases
$pipelineStartTime = Get-Date
$pipelineStartTime.ToString('o') | Out-File -FilePath (Join-Path $workingDir '.pipeline-start') -Encoding utf8 -Force
$docsWrittenCount = 0
if (-not (Test-Path $csvPath)) {
Write-Error "CSV not found at $csvPath."
exit 1
}
Read the CSV:
$csvData = Import-Csv $csvPath
The CSV has a header row. Look for a column containing file paths (commonly FilePath or filename) with values like wdk-ddi-src/content/{header}/{filename}.md. Filter to rows with valid .md paths.
Obtain ADO auth token. Try Azure CLI first, then fail with a clear message (PAT prompting cannot work inside a non-interactive script):
$token = (az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv 2>$null)
if (-not $token) {
Write-Error "Failed to get ADO token. Run 'az login' first, or set `$env:ADO_PAT before running."
exit 1
}
$h = @{ Authorization = "Bearer $token" }
$adoBase = "https://dev.azure.com/cpubwin/drivers/_apis/git/repositories"
For each entry in the CSV, extract the target filename from the path (e.g. wdk-ddi-src/content/{header}/nf-soundwireclass-somefunc.md → nf-soundwireclass-somefunc.md).
List all files in the directory on each branch (one API call per branch, not per file). This detects legacy filename variants (see Legacy Filename Exceptions):
# List files on main branch of wdk-ddi
try {
$r1 = Invoke-RestMethod -Uri "$adoBase/wdk-ddi/items?scopePath=wdk-ddi-src/content/{header}/&recursionLevel=OneLevel&versionDescriptor.version=main&versionDescriptor.versionType=branch&api-version=7.0" -Headers $h
$mainFiles = @($r1.value | Where-Object { $_.path -like "*.md" } | ForEach-Object { Split-Path $_.path -Leaf })
} catch { $mainFiles = @() }
# List files on stubs branch of wdk-ddi
try {
$r2 = Invoke-RestMethod -Uri "$adoBase/wdk-ddi/items?scopePath=wdk-ddi-src/content/{header}/&recursionLevel=OneLevel&versionDescriptor.version=stubs/main&versionDescriptor.versionType=branch&api-version=7.0" -Headers $h
$stubFiles = @($r2.value | Where-Object { $_.path -like "*.md" } | ForEach-Object { Split-Path $_.path -Leaf })
} catch { $stubFiles = @() }
# List files on live branch of wdk-ddi-build (published docs)
try {
$r3 = Invoke-RestMethod -Uri "$adoBase/wdk-ddi-build/items?scopePath=wdk-ddi-src/content/{header}/&recursionLevel=OneLevel&versionDescriptor.version=live&versionDescriptor.versionType=branch&api-version=7.0" -Headers $h
$pubFiles = @($r3.value | Where-Object { $_.path -like "*.md" } | ForEach-Object { Split-Path $_.path -Leaf })
} catch { $pubFiles = @() }
Write-Host "Main: $($mainFiles.Count) files, Stubs: $($stubFiles.Count) files, Published: $($pubFiles.Count) files"
Compare each expected filename against the directory listings. If a file exists with a minor variant (e.g. ns-header-_struct_name.md instead of ns-header-struct_name.md), treat it as a match — the doc exists under the legacy name. Use the legacy filename in the CSV (do not rename it).
a. Check if the file exists on main of wdk-ddi (look for the filename in $mainFiles).
b. Check if a stub exists on the stubs branch (look for the filename in $stubFiles).
c. Check if the file exists on live of the published docs repo wdk-ddi-build (look for the filename in $pubFiles).
Classify each API into one of three categories:
stubs/main but no completed doc on mainmain — needs to be compared with the latest stub to identify changesOutput grouped results to the console for the agent to parse and present:
=== New (ready to document) ===
nf-soundwireclass-somefunc.md (function)
ns-soundwireclass-some_struct.md (structure)
=== Update (existing doc, needs diff) ===
nf-soundwireclass-otherfunc.md (function) [+2 params, -1 param, ~1 param]
=== Stub not found ===
ne-soundwireclass-some_enum.md (enumeration)
Diff existing docs against stubs. For each "update" entry, fetch the existing doc from main and the latest stub from stubs/main via the ADO REST API. Compare the parameter/field/value sections to identify:
Output a per-file change summary (e.g. [+2 params, -1 param, ~1 param]) in the console results.
Write the updated CSV at the original CSV path. The format must be compatible with Phase 2:
filename,status
wdk-ddi-src/content/{header}/{filename1}.md,new
wdk-ddi-src/content/{header}/{filename2}.md,update
The script writes both "new" and "update" entries. "Stub not found" entries are excluded.
Output a summary to the console:
{workingDir}{csvPath}After the script finishes, the agent should verify the CSV was written successfully. If the inventory found zero entries for documentation (all "stub not found"), stop with an error. Otherwise, proceed immediately to Phase 2 with no user prompt.
Generate complete API reference documentation pages for WDK DDI entities by combining stub files, OS source code declarations, and supplemental information from published docs.
Strip the .h extension from the user-provided header name to get {header} (e.g. soundwireclass.h → soundwireclass).
Resolve paths. The user provides a CSV path. Derive the working directory from it:
$csvPath = "{user-provided CSV path}"
if (-not (Test-Path $csvPath)) {
Write-Error "CSV not found at $csvPath. Please provide the CSV file first."
return
}
$workingDir = Split-Path $csvPath -Parent
$outputDir = Join-Path $workingDir "output"
Import-Csv $csvPath
If the CSV does not exist, stop with an error.
Obtain ADO auth token. Try Azure CLI first, then fall back to prompting for a PAT:
try {
$token = (az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv 2>$null)
if (-not $token) { throw "No token" }
$headers = @{ Authorization = "Bearer $token" }
} catch {
$pat = Read-Host "Enter ADO PAT (scope: Code Read)"
$base64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$pat"))
$headers = @{ Authorization = "Basic $base64" }
}
$adoBase = "https://dev.azure.com/cpubwin/drivers/_apis/git/repositories"
Read the style guide remotely from the wdk-ddi repo to ensure all formatting rules are followed:
$styleGuide = Invoke-RestMethod -Uri "$adoBase/wdk-ddi/items?path=.github/copilot-instructions.md&versionDescriptor.version=main&versionDescriptor.versionType=branch&api-version=7.0" -Headers $headers
Parse the CSV file to get the list of target filenames. The CSV has a header row and one filename column with paths like wdk-ddi-src/content/{header}/{filename}.md.
Create the output directory if it doesn't exist; if it does, clear it:
if (Test-Path $outputDir) {
Remove-Item "$outputDir\*" -Force -ErrorAction SilentlyContinue
} else {
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
}
For each target file listed in the CSV, skip index.md (the header landing page is handled separately in step 8).
For each remaining target file:
Use the ADO REST API to check if the file already exists on main:
# List files on main branch
$mainFiles = Invoke-RestMethod -Uri "$adoBase/wdk-ddi/items?scopePath=wdk-ddi-src/content/{header}/&recursionLevel=OneLevel&versionDescriptor.version=main&versionDescriptor.versionType=branch&api-version=7.0" -Headers $headers
If the target filename appears in the listing, retrieve it from main and use its content as the starting point — preserve existing text, but always update ms.date to today's date when making any content changes. Only write the file to the output folder if you make changes. Do not copy unchanged files.
$content = Invoke-RestMethod -Uri "$adoBase/wdk-ddi/items?path=wdk-ddi-src/content/{header}/{filename.md}&versionDescriptor.version=main&versionDescriptor.versionType=branch&api-version=7.0" -Headers $headers
$content | Out-File -FilePath (Join-Path $outputDir "{filename.md}") -Encoding utf8
If it does not exist on main, retrieve the stub from the stubs branch:
$content = Invoke-RestMethod -Uri "$adoBase/wdk-ddi/items?path=wdk-ddi-src/content/{header}/{filename.md}&versionDescriptor.version=stubs/main&versionDescriptor.versionType=branch&api-version=7.0" -Headers $headers
$content | Out-File -FilePath (Join-Path $outputDir "{filename.md}") -Encoding utf8
Use the substrate-mcp MCP server to find the API's source declaration:
nf-soundwireclass-somefunc.md → SomeFunc)search_code(query="def:{ApiName}", ext="h")
def: doesn't find it, try keyword search:
search_code(query="{ApiName}", ext="h", path="**/{header}.h")
get_file_content with a line range to retrieve the full declaration including:
_In_, _Out_, _Inout_, _In_opt_, _Out_opt_, _Reserved_)Use the microsoft.docs.mcp server's microsoft_docs_search tool to search for any existing published docs or related conceptual content for the API:
microsoft_docs_search(query="{ApiName} WDK driver")
Find 1-2 completed reference pages in the same header folder to use as formatting models. Use the ADO REST API to list files, then retrieve them:
# List existing completed docs of the same entity type in the {header} folder
$mainFiles = Invoke-RestMethod -Uri "$adoBase/wdk-ddi/items?scopePath=wdk-ddi-src/content/{header}/&recursionLevel=OneLevel&versionDescriptor.version=main&versionDescriptor.versionType=branch&api-version=7.0" -Headers $headers
# Filter for files matching the same prefix (e.g. "nf-") and pick 1-2
Then read the model files:
$modelContent = Invoke-RestMethod -Uri "$adoBase/wdk-ddi/items?path=wdk-ddi-src/content/{header}/{model-filename.md}&versionDescriptor.version=main&versionDescriptor.versionType=branch&api-version=7.0" -Headers $headers
If no completed pages exist in the same folder, look in a related header folder. Read these model files to match their style.
Write the completed file to {outputDir}\{filename.md}. Follow these rules:
ai-usage: ai-assisted if not already presenttech.root to the same value as existing files in the same header folder. If no existing files in the folder (i.e. a brand-new API set), make an educated guess based on the header's source location, API naming patterns, and functionality (e.g. headers in minkernel/ are typically kernel, networking headers are netvista, storage headers are storage, etc.). Log the guessed value and add a note for the user: "NOTE: tech.root guessed as '{value}' — please verify this is correct."ms.date to today's date in MM/DD/YYYY format — both for new files and when updating existing files with changesreq.header, f1_keywords, api_name, topic_type are correctreq.header uses lowercase (e.g. classpnp.h). req.include-header uses sentence capitalization (e.g. Classpnp.h). Note that req.include-header does not always require a value — omit it or leave it empty when an include header is not applicable.req.construct-type for macros: If the source declaration is a preprocessor macro (#define), set req.construct-type: macro, not function. The file prefix remains nf- and the title should say "macro" (e.g. CLEAR_FLAG_NOFENCE macro (classpnp.h)).NE:wdbgexts._POOL_HEADER_FIELD_NAME), preserve that underscore in the UID of the generated document. However, all customer-facing content (title, description, headings, prose) must use the version without the leading underscore (e.g. POOL_HEADER_FIELD_NAME). For api_name: and f1_keywords:, include both versions — the underscored name and the public name — so the page is discoverable by either form.## -description
## -parameters (functions/callbacks/macros)
### -param {Name} [{direction}] subsection per parameter_In_ → [in]_Out_ → [out]_Inout_ → [in, out]_In_opt_ → [in, optional]_Out_opt_ → [out, optional]_Reserved_ → [in] with note "Reserved. Must be zero/NULL."&): If the macro implementation takes the address of a parameter (e.g. &(Flags)), describe the parameter as "An addressable {type} storage location (such as a variable or structure field)" rather than "A {type} value." A macro that takes &(param) requires an lvalue, not an arbitrary expression.## -struct-fields (structures only)
### -field {FieldName} subsection per fieldunion, it is still documented with the ns- file prefix and req.construct-type: structure. All prose (description, field descriptions, remarks, and cross-references from other pages) must call it a "structure", never a "union." The heading should read # {NAME} structure, not # {NAME} union.## -enum-fields (enumerations only)
### -field {ValueName}:{NumericValue} subsection per value (if numeric value is known)## -ioctlparameters (IOCTLs only)
## -returns (functions with return values)
## -remarks
Ex, or a number like 2, 3, or V2), reference the base entity and document only the extensions## -see-also
[**OtherFunc**](nf-{header}-otherfunc.md) for same-header APIs../ paths for cross-header: [**CrossFunc**](../otherheader/nf-otherheader-crossfunc.md)After writing each file, log progress with elapsed time. Run the following in the terminal (or compute the elapsed time from the stored timestamp and include it in the agent's console output):
$start = [DateTime]::Parse((Get-Content '{workingDir}\.pipeline-start' -Raw).Trim())
$elapsed = (Get-Date) - $start
Write-Host "[$($elapsed.ToString('hh\:mm\:ss'))] Wrote doc {N}: {filename.md}"
The agent must track the running document count itself (incrementing after each file) and substitute {N} with the current count and {filename.md} with the actual filename.
**IoCreateFile***DesiredAccess*[title](/windows-hardware/drivers/...)cpp language tagHandle the header landing page (index.md). The landing page is automatically updated during the build to reflect the current API set, so it almost never needs manual editing once published.
index.md already exists on main of wdk-ddi or staging of the published docs repo using the ADO REST API:
# Check wdk-ddi main
$mainItems = Invoke-RestMethod -Uri "$adoBase/wdk-ddi/items?scopePath=wdk-ddi-src/content/{header}/&recursionLevel=OneLevel&versionDescriptor.version=main&versionDescriptor.versionType=branch&api-version=7.0" -Headers $headers
# Check windows-driver-docs-ddi staging
$pubItems = Invoke-RestMethod -Uri "$adoBase/windows-driver-docs-ddi/items?scopePath=wdk-ddi-src/content/{header}/&recursionLevel=OneLevel&versionDescriptor.version=staging&versionDescriptor.versionType=branch&api-version=7.0" -Headers $headers
index.md appears in either listing, skip it — no action needed.index.md exists, retrieve the stub from the stubs branch and save it as index.md:
$stubContent = Invoke-RestMethod -Uri "$adoBase/wdk-ddi/items?path=wdk-ddi-src/content/{header}/na-{header}.md&versionDescriptor.version=stubs/main&versionDescriptor.versionType=branch&api-version=7.0" -Headers $headers
$stubContent | Out-File -FilePath (Join-Path $outputDir "index.md") -Encoding utf8
index.md: After retrieving the stub, ensure the ## -description section follows this strict format:
This header is used by {tech.root display name}. For more information, see: [{tech.root display name}](../{tech.root}/index.md).
Replace {tech.root display name} with the human-readable technology area name and {tech.root} with the tech.root value from the frontmatter (e.g. _netvista → [Networking](/windows-hardware/drivers/ddi/_netvista/)).-description. There must be no -remarks or -see-also sections in an index.md file.Close editor tabs and log progress. After all files have been written to {outputDir}, close all open editor tabs so the workspace is not cluttered with generated files. Run the VS Code command workbench.action.closeAllEditors to close them. Then display:
{outputDir}req.lib, req.dll, req.irql if not determinable from source)Then proceed immediately to Phase 3 with no user prompt.
Submit generated API reference documentation as a pull request to the wdk-ddi Azure DevOps repo using the ADO REST API.
No local repo clone required. Branch creation, file push, and PR creation are all done via the ADO REST API.
Strip the .h extension from the user-provided header name to get {header} (e.g. soundwireclass.h → soundwireclass).
Resolve paths. The user provides the CSV path. Derive the working and output directories:
$csvPath = "{user-provided CSV path}"
if (-not (Test-Path $csvPath)) {
Write-Error "CSV not found at $csvPath."
return
}
$workingDir = Split-Path $csvPath -Parent
$outputDir = Join-Path $workingDir "output"
$entries = Import-Csv $csvPath
If the CSV does not exist, stop with an error. Use the CSV entries to identify the API entities for the commit message and PR description.
Verify the output directory exists and contains files:
$outputFiles = Get-ChildItem -Path $outputDir -Filter "*.md" -ErrorAction SilentlyContinue
if (-not $outputFiles -or $outputFiles.Count -eq 0) {
Write-Error "No generated docs found in $outputDir. Generation phase may have failed."
return
}
Obtain ADO auth token. Try Azure CLI first, then fall back to prompting for a PAT:
try {
$token = (az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv 2>$null)
if (-not $token) { throw "No token" }
$headers = @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" }
} catch {
$pat = Read-Host "Enter ADO PAT (scope: Code Read+Write, PR Contribute)"
$base64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$pat"))
$headers = @{ Authorization = "Basic $base64"; "Content-Type" = "application/json" }
}
$adoBase = "https://dev.azure.com/cpubwin/drivers/_apis/git/repositories"
Get the latest commit SHA on main. This is required as the oldObjectId for the push:
$refs = Invoke-RestMethod -Uri "$adoBase/wdk-ddi/refs?filter=heads/main&api-version=7.0" -Headers $headers
$mainSha = $refs.value[0].objectId
Determine change type for each file. Check which files already exist on main to set the correct changeType (add vs edit):
$mainFiles = Invoke-RestMethod -Uri "$adoBase/wdk-ddi/items?scopePath=wdk-ddi-src/content/{header}/&recursionLevel=OneLevel&versionDescriptor.version=main&versionDescriptor.versionType=branch&api-version=7.0" -Headers $headers
$existingNames = $mainFiles.value | ForEach-Object { Split-Path $_.path -Leaf }
Build the push payload. Read each output file, base64-encode its content, and create the change entries:
$changes = @()
foreach ($file in $outputFiles) {
$contentBytes = [System.IO.File]::ReadAllBytes($file.FullName)
$base64Content = [Convert]::ToBase64String($contentBytes)
$repoPath = "/wdk-ddi-src/content/{header}/$($file.Name)"
$changeType = if ($file.Name -in $existingNames) { "edit" } else { "add" }
$changes += @{
changeType = $changeType
item = @{ path = $repoPath }
newContent = @{
content = $base64Content
contentType = "base64encoded"
}
}
}
Generate the commit message. Use the CSV entries to list the API entity names:
Add/update API reference docs for {header}.h
Documented {N} API entities:
- {ApiName1} ({type})
- {ApiName2} ({type})
...
AI-assisted content generation.
Log the push plan. Display:
add / edit){user-alias}/{header}-updateCreate or locate the branch. This step MUST be completed separately before the push in step 11. The branch must exist and point to a real commit on main before any push is attempted.
CRITICAL — Orphan commit prevention: The
oldObjectIdused in the push (step 11) determines the parent commit of the new commit. IfoldObjectIdis set to0000000000000000000000000000000000000000(all zeros), the ADO pushes API creates an orphan root commit with no parent. The resulting commit tree will contain ONLY the files in thechangesarray — all other repository files will appear as deletions in any PR diff. NEVER use all-zeros asoldObjectIdin the pushes API. Always use$mainShaor the branch's current commit SHA.
First, check if the branch already exists:
$branchRef = Invoke-RestMethod -Uri "$adoBase/wdk-ddi/refs?filter=heads/{user-alias}/{header}-update&api-version=7.0" -Headers $headers
if ($branchRef.value.Count -gt 0) {
# Branch exists — use its current SHA
$branchSha = $branchRef.value[0].objectId
} else {
# Create the branch via the refs API (JSON array body)
$createBranchPayload = "[{`"name`":`"refs/heads/{user-alias}/{header}-update`",`"oldObjectId`":`"0000000000000000000000000000000000000000`",`"newObjectId`":`"$mainSha`"}]"
$refResult = Invoke-RestMethod -Uri "$adoBase/wdk-ddi/refs?api-version=7.0" -Method Post -Headers $headers -Body $createBranchPayload
$branchSha = $refResult.value[0].newObjectId
}
Important: The
POST /refsendpoint requires the body to be a JSON array, not an object. UsingConvertTo-Jsonon a single hashtable wraps it in an{}object, which the API rejects. Either manually construct the JSON string or wrap the hashtable in@(...)and verify the output is[{...}].
Validate that $branchSha is a real commit SHA (40 hex chars, not all zeros) before proceeding:
if (-not $branchSha -or $branchSha -eq "0000000000000000000000000000000000000000") {
Write-Error "Branch SHA is null or all-zeros. Branch creation may have failed. Aborting."
return
}
Write-Host "Branch SHA for push: $branchSha"
Push the commit to the branch. The oldObjectId in refUpdates MUST be $branchSha (which equals $mainSha for a newly created branch). This tells ADO to create a new commit whose parent is $branchSha, inheriting all existing files from that commit's tree:
$pushBody = @{
refUpdates = @(
@{
name = "refs/heads/{user-alias}/{header}-update"
oldObjectId = $branchSha # MUST be a real SHA, never all-zeros
}
)
commits = @(
@{
comment = "<generated commit message>"
changes = $changes
}
)
} | ConvertTo-Json -Depth 10
$pushResult = Invoke-RestMethod -Uri "$adoBase/wdk-ddi/pushes?api-version=7.0" -Method Post -Headers $headers -Body $pushBody
Do NOT combine branch creation and push into a single pushes API call. Always create the branch first (step 10), then push to it (step 11).
Verify the push. Confirm the new commit has a parent (is not orphaned) by checking the commit details:
$newCommitId = $pushResult.commits[0].commitId
$commitDetail = Invoke-RestMethod -Uri "$adoBase/wdk-ddi/commits/$newCommitId?api-version=7.0" -Headers $headers
if (-not $commitDetail.parents -or $commitDetail.parents.Count -eq 0) {
Write-Error "FATAL: Push created an orphan commit (no parent). The branch must be deleted and recreated. This means oldObjectId was wrong."
# Clean up: delete the broken branch
$deletePayload = "[{`"name`":`"refs/heads/{user-alias}/{header}-update`",`"oldObjectId`":`"$($commitDetail.commitId)`",`"newObjectId`":`"0000000000000000000000000000000000000000`"}]"
Invoke-RestMethod -Uri "$adoBase/wdk-ddi/refs?api-version=7.0" -Method Post -Headers $headers -Body $deletePayload
Write-Error "Broken branch deleted. Please retry the submission."
return
}
Write-Host "Commit $newCommitId verified — parent: $($commitDetail.parents[0])"
Create a pull request targeting main:
$prBody = @{
sourceRefName = "refs/heads/{user-alias}/{header}-update"
targetRefName = "refs/heads/main"
title = "{user-alias}/{header}-update: API reference docs for {header}.h"
description = "<PR description with header name, API entity list, AI-assisted note>"
} | ConvertTo-Json
$prResult = Invoke-RestMethod -Uri "$adoBase/wdk-ddi/pullrequests?api-version=7.0" -Method Post -Headers $headers -Body $prBody
The PR description should include:
ai-usage: ai-assisted metadata is set in each file)Display the PR URL to the user:
$prUrl = "https://dev.azure.com/cpubwin/drivers/_git/wdk-ddi/pullrequest/$($prResult.pullRequestId)"
Write-Host "PR created: $prUrl"
Final summary. Read the persisted start time and compute the total elapsed time. Include this in the submit script:
$startFile = Join-Path $workingDir '.pipeline-start'
if (Test-Path $startFile) {
$pipelineStartTime = [DateTime]::Parse((Get-Content $startFile -Raw).Trim())
$totalElapsed = (Get-Date) - $pipelineStartTime
} else {
$totalElapsed = [TimeSpan]::Zero
}
Write-Host ""
Write-Host "========================================"
Write-Host " Autopilot Complete"
Write-Host "========================================"
Write-Host "Total elapsed time : $($totalElapsed.ToString('hh\:mm\:ss'))"
Write-Host "Documents written : $docsWrittenCount"
Write-Host "========================================"
The $docsWrittenCount value must be hardcoded into the generated submit.ps1 script by the agent (since the agent tracks the count during Phase 2).
Then display:
{outputDir}{prUrl}{totalElapsed} | Documents written: {docsWrittenCount}