Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 190 additions & 0 deletions .github/workflows/generate-aidocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
name: Generate AI Docs

# ──────────────────────────────────────────────────────────────────────────────
# Triggers
# ──────────────────────────────────────────────────────────────────────────────
on:
workflow_dispatch:
inputs:
base_branch:
description: 'Base branch for the AI docs review PR (default: main)'
required: false
default: main
type: string
release_name:
description: 'Optional release label used in the review branch name (example: 2026.09.R1.PublicAPIService.docs)'
required: false
type: string
force_regenerate:
description: 'Force a full regeneration of the AI docs'
required: false
type: boolean
default: false

# ──────────────────────────────────────────────────────────────────────────────
# Jobs
# ──────────────────────────────────────────────────────────────────────────────
jobs:
generate-aidocs:
name: Generate AI Docs review branch
runs-on: windows-latest
permissions:
contents: write # push the review branch only; this workflow never creates PRs or merges

steps:
# ── 1. Checkout the release/base branch ─────────────────────────────────
- name: Checkout target branch
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ inputs.base_branch || 'main' }}
token: ${{ secrets.GITHUB_TOKEN }}

# ── 2. Resolve branch metadata ───────────────────────────────────────────
- name: Resolve review metadata
id: meta
shell: pwsh
env:
INPUT_BASE: ${{ inputs.base_branch }}
INPUT_LABEL: ${{ inputs.release_name }}
run: |
$base = $env:INPUT_BASE
if ([string]::IsNullOrWhiteSpace($base)) { $base = 'main' }

$label = $env:INPUT_LABEL
if ([string]::IsNullOrWhiteSpace($label)) {
$label = "auto-$([DateTime]::UtcNow.ToString('yyyyMMdd-HHmmss'))"
}

# Must start alphanumeric so the value can't be parsed as a git option.
if ($base -notmatch '^[A-Za-z0-9][A-Za-z0-9._/-]*$') { throw "Invalid base_branch: '$base'" }
if ($label -notmatch '^[A-Za-z0-9][A-Za-z0-9._-]*$') { throw "Invalid release_name: '$label'" }

$branch = "ai-docs/$label"

"base=$base" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"branch=$branch" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"label=$label" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8

Write-Host "Base branch : $base"
Write-Host "Review branch: $branch"

# ── 3. Create the AI docs review branch from the chosen base ────────────
- name: Create AI docs review branch
shell: pwsh
env:
BASE: ${{ steps.meta.outputs.base }}
BRANCH: ${{ steps.meta.outputs.branch }}
run: |
git checkout $env:BASE
git checkout -B $env:BRANCH

# ── 4. Download required tooling and SDK artifacts ───────────────────────
- name: Download C2M4AI
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path tools | Out-Null
.\scripts\ai-docs-scripts\Get-C2M4AI.ps1 -OutputPath tools\C2M4AI.exe

- name: Download .NET SDK XML documentation
shell: pwsh
run: .\scripts\ai-docs-scripts\Get-DotNetSdkXml.ps1 -OutputPath tools\Rws.LanguageCloud.Sdk.xml

- name: Download Java SDK sources
shell: pwsh
run: .\scripts\ai-docs-scripts\Get-JavaSdkSources.ps1 -OutputDir tools\java-api

# ── 5. Run the AI Docs Pipeline on the review branch ────────────────────
- name: Run AI Docs Pipeline
shell: pwsh
env:
FORCE: ${{ inputs.force_regenerate }}
run: |
$params = @{ RootDir = '.'; All = $true }
if ($env:FORCE -eq 'true') { $params.Force = $true }

.\scripts\ai-docs-scripts\Invoke-AiDocsPipeline.ps1 @params

# ── 6. Commit generated AI docs to the review branch ────────────────────
- name: Commit AI docs changes
id: commit
shell: pwsh
env:
LABEL: ${{ steps.meta.outputs.label }}
run: |
git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"

git add articles/LCPublicAPI/aidocs/
$staged = git diff --cached --stat

if ($staged) {
git commit -m "docs: regenerate AI docs $($env:LABEL) [skip ci]"
"changed=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
Write-Host "AI docs branch updated with generated changes." -ForegroundColor Green
} else {
"changed=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
Write-Host "No AI docs changes were generated. Nothing to push." -ForegroundColor DarkGray
}

# ── 7. Push the review branch (no PR, no merge — a human does both) ────────
- name: Push review branch
if: steps.commit.outputs.changed == 'true'
shell: pwsh
env:
BRANCH: ${{ steps.meta.outputs.branch }}
run: git push origin $env:BRANCH

# ── 8. Tell the human what to do next ──────────────────────────────────────
- name: Write next-steps summary
shell: pwsh
env:
CHANGED: ${{ steps.commit.outputs.changed }}
BRANCH: ${{ steps.meta.outputs.branch }}
BASE: ${{ steps.meta.outputs.base }}
SERVER: ${{ github.server_url }}
REPO: ${{ github.repository }}
run: |
if ($env:CHANGED -ne 'true') {
"## AI docs: no changes`nNothing was generated, so no branch was pushed." | Out-File $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
return
}

$compare = "$($env:SERVER)/$($env:REPO)/compare/$($env:BASE)...$($env:BRANCH)?expand=1"
$doc = "$($env:SERVER)/$($env:REPO)/blob/$($env:BRANCH)/scripts/ai-docs-scripts/README.md"

# Single-quoted here-string: backticks and code fences stay literal.
# __PLACEHOLDERS__ are replaced below.
$text = @'
## AI docs branch is ready: `__BRANCH__`

The reference docs are done. The guides still need a person. **Do the steps in order and do not skip any.**

### Steps

1. Open this branch in **Open VS Code**.
2. **Open the powershell terminal.** and run this command:
```powershell
.\scripts\ai-docs-scripts\New-GuidesPrompt.ps1 -All
```
You should see `Prompt written to: ...\_update-prompt.md` in green.

3. **Run the AI agent.**
1. Open Copilot Chat in Agent mode.
2. Type `#_update-prompt.md` and press Enter.
3. Wait until Copilot says it is finished (several minutes), then click **Keep**.

4. **Rebuild the indexes.** Paste these two lines into the terminal, one at a time, pressing Enter after each:
```powershell
.\scripts\ai-docs-scripts\Update-GuidesIndex.ps1
.\scripts\ai-docs-scripts\Update-AiDocsIndex.ps1
```
Green `Updated` or grey `Unchanged` is fine. **Red means stop and ask the docs team.**

5. **Final** - Review the changes, commit, push and open a pull request when ready.

More detail: [Reviewer guide in the README](__DOC__)
'@

$text = $text.Replace('__BRANCH__', $env:BRANCH).Replace('__BASE__', $env:BASE).Replace('__COMPARE__', $compare).Replace('__DOC__', $doc)
$text | Out-File $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ _site
*.testlog
.vscode
.vs
tools/
_update-prompt.md
.api-spec-hash
15 changes: 15 additions & 0 deletions articles/LCPublicAPI/docs/AI-Development.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# AI Agent Development

The Trados Cloud Platform API provides a machine-readable documentation set designed specifically for AI-assisted integrations.This section gives you and your tooling everything needed to understand and call the API correctly.

## What are AI Docs?

AI Docs are a structured documentation set derived from the live OpenAPI specification. They are designed to be consumed by AI agents.

## Starting point for AI agents

If you are wiring up an AI agent or a tool that needs to discover and call Trados Cloud Platform API endpoints, point it at the master index:

**<a href="../aidocs/index.md" target="_blank">AI Docs - Master Index</a>**

The index lists every available operation and links directly to its detail page. From there, your agent can navigate to any endpoint it needs.
11 changes: 10 additions & 1 deletion docfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"*.md"
],
"exclude": [
"_site/**"
"_site/**",
"articles/LCPublicAPI/aidocs/**"
]
}
],
Expand All @@ -47,6 +48,14 @@
"exclude": [
"_site/**"
]
},
{
"files": [
"articles/LCPublicAPI/aidocs/**"
],
"exclude": [
"_site/**"
]
}
],
"overwrite": [
Expand Down
104 changes: 104 additions & 0 deletions scripts/ai-docs-scripts/Get-ApiSpecDiff.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<#
.SYNOPSIS
Diffs two OpenAPI spec files and returns which operations were added,
removed, or changed (by content hash).

.DESCRIPTION
Used by the pipeline orchestrator to decide which reference .md files
need to be regenerated or have their SDK sections re-injected.

Each operation is keyed by its operationId. The "changed" set contains
operations present in both specs whose full operation object differs.

Output is a JSON object with three arrays:
{ "Added": [...], "Removed": [...], "Changed": [...] }

.PARAMETER OldSpec
Path to the previous version of the OpenAPI JSON file.

.PARAMETER NewSpec
Path to the current version of the OpenAPI JSON file.

.PARAMETER OutputJson
Optional path to write the JSON diff result. If omitted, result is
written to stdout only.

.EXAMPLE
.\scripts\ai-docs-scripts\Get-ApiSpecDiff.ps1 -OldSpec .\prev\api.json -NewSpec .\LCPublicAPI\api.json
.\scripts\ai-docs-scripts\Get-ApiSpecDiff.ps1 -OldSpec old.json -NewSpec new.json -OutputJson diff.json
#>
param(
[Parameter(Mandatory)][string] $OldSpec,
[Parameter(Mandatory)][string] $NewSpec,
[string] $OutputJson
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

function Get-OperationMap ([string]$specPath) {
$spec = Get-Content $specPath -Raw | ConvertFrom-Json
$ops = @{}

foreach ($pathProp in $spec.paths.PSObject.Properties) {
$pathStr = $pathProp.Name
foreach ($methodProp in $pathProp.Value.PSObject.Properties) {
# Skip non-HTTP fields (e.g. "parameters" at path level)
$httpMethods = 'get','post','put','patch','delete','head','options','trace'
if ($methodProp.Name -notin $httpMethods) { continue }

$operation = $methodProp.Value
$operationId = $operation.operationId
if (-not $operationId) { continue }

# Hash the full operation object for change detection
$json = $operation | ConvertTo-Json -Depth 20 -Compress
$bytes = [System.Text.Encoding]::UTF8.GetBytes($json)
$hash = [System.Security.Cryptography.SHA256]::Create().ComputeHash($bytes)
$hashStr = [System.BitConverter]::ToString($hash) -replace '-', ''

$ops[$operationId] = [PSCustomObject]@{
Path = $pathStr
Method = $methodProp.Name.ToUpper()
Hash = $hashStr
}
}
}
return $ops
}

Write-Host "=== Get-ApiSpecDiff ===" -ForegroundColor Cyan
Write-Host "Old : $OldSpec"
Write-Host "New : $NewSpec"
Write-Host ""

$old = Get-OperationMap $OldSpec
$new = Get-OperationMap $NewSpec

$added = @($new.Keys | Where-Object { -not $old.ContainsKey($_) } | Sort-Object)
$removed = @($old.Keys | Where-Object { -not $new.ContainsKey($_) } | Sort-Object)
$changed = @($new.Keys | Where-Object {
$old.ContainsKey($_) -and $old[$_].Hash -ne $new[$_].Hash
} | Sort-Object)

$result = [PSCustomObject]@{
Added = $added
Removed = $removed
Changed = $changed
}

# Report
Write-Host "Added ($($added.Count)) : $($added -join ', ')"
Write-Host "Removed ($($removed.Count)) : $($removed -join ', ')"
Write-Host "Changed ($($changed.Count)) : $($changed -join ', ')"

$json = $result | ConvertTo-Json -Depth 3

if ($OutputJson) {
$json | Set-Content -Path $OutputJson -Encoding UTF8
Write-Host ""
Write-Host "Diff written to: $OutputJson" -ForegroundColor Cyan
}

# Always write to stdout so callers can capture via pipe or $( )
Write-Output $json
Loading
Loading