LLM時代はテキスト形式でドキュメントを書く 4. コーティングエージェントで引き出す、Markdownファイルの真価

に公開

Markdownファイルを書きなれてきたところで、Markdownファイルの真価を説明します。

優先度が低いMarkdownファイルだが…

繰り返しますが、Markdownファイルは万能ではありません。ただ、適切な場所で使用する & AIコーディングエージェントと組み合わせると、非常に強力な効果を発揮します。それは、PowerPointで表を作るよりも、Excelで表を作る方が2倍便利、というレベルではなく、Wordで小説を書けるが、Excelでは不可能というレベルの断絶があります。

AIエージェントによる、docs ⇔ システムの横断

Markdown以前、つまりOfficeファイルによるドキュメント管理を行う場合、プロジェクトの各フェーズで利用されるドキュメントフォーマットは以下のようなものとなります。

普通の話ですが、各ドキュメントは作業者が手作業で作成し、更新をします。要件定義書に変更があった場合、内容を確認して基本設計書以下に落とします。

これをmarkdownファイルにすると、どうなるか?

特に何も変わりません。フォーマットが違うだけですからね。

けど、markdownファイルはAIコーディングエージェントが使えます。これを利用すれば、各ドキュメントの変更をAIに依頼する事ができます。

具体的には

  • 要件定義が更新されたから、差分の内容を基本設計書や詳細設計書に反映する
  • お客様との定例会で指摘された内容の確認を、ドキュメントを横断して回答する

こういった事が可能となります。

デモ

簡単なデモをします。

要件

Microsoft Teamsにおいて、ユーザーが自由にチームを作成できないよう制限を設ける設定を実装する。

こんな要件があるとします。これを元に、基本設計書や詳細設計書、パラメーターシートの作成を依頼します。

基本設計書とパラメーターシートを掲載しておきます。

実装(IaCライク)

それでは、実際に設定できるようにPowerShellスクリプトの作成を依頼します。べきとうせい(冪等性)を考慮して、と依頼したので、ちょっと長めになっています。

PowerShellスクリプト
#Requires -Modules MicrosoftTeams
<#
.SYNOPSIS
    Microsoft Teamsのチーム作成制限を設定するべき等性スクリプト

.DESCRIPTION
    このスクリプトは以下の処理を行います:
    - チーム作成制限ポリシーの作成/更新
    - 既存設定の確認と必要な場合のみ変更
    - 実行ログの出力
    - ロールバック用の設定バックアップ

.PARAMETER TenantId
    対象テナントのID(オプション)

.PARAMETER DryRun
    実際の変更を行わず、変更予定内容のみ表示

.EXAMPLE
    .\Configure-TeamsCreationRestriction.ps1
    
.EXAMPLE
    .\Configure-TeamsCreationRestriction.ps1 -DryRun
#>

[CmdletBinding()]
param(
    [Parameter(Mandatory = $false)]
    [string]$TenantId,
    
    [Parameter(Mandatory = $false)]
    [switch]$DryRun
)

# エラー時の動作設定
$ErrorActionPreference = "Stop"

# ログ関数
function Write-Log {
    param(
        [string]$Message,
        [string]$Level = "INFO"
    )
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $logMessage = "[$timestamp] [$Level] $Message"
    Write-Host $logMessage
    
    # ログファイルへの出力
    $logFile = "TeamsConfig_$(Get-Date -Format 'yyyyMMdd').log"
    Add-Content -Path $logFile -Value $logMessage
}

# 設定確認関数
function Test-PolicyExists {
    param([string]$PolicyName)
    
    try {
        $policy = Get-CsTeamsChannelsPolicy -Identity $PolicyName -ErrorAction SilentlyContinue
        return $null -ne $policy
    }
    catch {
        return $false
    }
}

# 現在設定の取得とバックアップ
function Backup-CurrentSettings {
    Write-Log "現在の設定をバックアップ中..."
    
    $backupData = @{
        Timestamp = Get-Date
        Policies = @()
    }
    
    try {
        $allPolicies = Get-CsTeamsChannelsPolicy
        foreach ($policy in $allPolicies) {
            $backupData.Policies += @{
                Identity = $policy.Identity
                AllowOrgWideTeamCreation = $policy.AllowOrgWideTeamCreation
                AllowPrivateTeamDiscovery = $policy.AllowPrivateTeamDiscovery
            }
        }
        
        $backupFile = "TeamsPolicy_Backup_$(Get-Date -Format 'yyyyMMddHHmmss').json"
        $backupData | ConvertTo-Json -Depth 3 | Out-File -FilePath $backupFile
        Write-Log "設定をバックアップしました: $backupFile"
        return $backupFile
    }
    catch {
        Write-Log "バックアップに失敗しました: $_" -Level "ERROR"
        throw
    }
}

# メイン処理
function Main {
    Write-Log "=== Teams チーム作成制限設定スクリプト開始 ==="
    
    try {
        # 1. Teams PowerShell接続確認
        Write-Log "Microsoft Teams接続確認中..."
        
        if ($TenantId) {
            Connect-MicrosoftTeams -TenantId $TenantId
        } else {
            # 既に接続されているかチェック
            try {
                $context = Get-CsTeamsChannelsPolicy -Identity Global -ErrorAction Stop
                Write-Log "Teams PowerShellに接続済みです"
            }
            catch {
                Write-Log "Teams PowerShellに接続します..."
                Connect-MicrosoftTeams
            }
        }
        
        # 2. 現在設定のバックアップ
        $backupFile = Backup-CurrentSettings
        
        # 3. 制限ポリシーの設定定義
        $restrictPolicyName = "RestrictTeamCreation"
        $targetSettings = @{
            AllowOrgWideTeamCreation = $false
            AllowPrivateTeamDiscovery = $false
        }
        
        # 4. ポリシー存在確認とべき等性チェック
        Write-Log "ポリシー '$restrictPolicyName' の確認中..."
        
        if (Test-PolicyExists -PolicyName $restrictPolicyName) {
            # 既存ポリシーの設定確認
            $currentPolicy = Get-CsTeamsChannelsPolicy -Identity $restrictPolicyName
            $needsUpdate = $false
            
            foreach ($setting in $targetSettings.GetEnumerator()) {
                $currentValue = $currentPolicy.($setting.Key)
                if ($currentValue -ne $setting.Value) {
                    Write-Log "設定変更が必要: $($setting.Key) = $currentValue -> $($setting.Value)"
                    $needsUpdate = $true
                }
            }
            
            if (-not $needsUpdate) {
                Write-Log "ポリシー '$restrictPolicyName' は既に正しく設定されています(べき等性により変更不要)"
            } else {
                if ($DryRun) {
                    Write-Log "[DRY RUN] ポリシー '$restrictPolicyName' を更新予定"
                } else {
                    Write-Log "ポリシー '$restrictPolicyName' を更新中..."
                    Set-CsTeamsChannelsPolicy -Identity $restrictPolicyName @targetSettings
                    Write-Log "ポリシーを正常に更新しました"
                }
            }
        } else {
            # 新規ポリシー作成
            if ($DryRun) {
                Write-Log "[DRY RUN] 新しいポリシー '$restrictPolicyName' を作成予定"
            } else {
                Write-Log "新しいポリシー '$restrictPolicyName' を作成中..."
                New-CsTeamsChannelsPolicy -Identity $restrictPolicyName @targetSettings
                Write-Log "ポリシーを正常に作成しました"
            }
        }
        
        # 5. 設定結果の確認
        if (-not $DryRun) {
            Write-Log "設定結果を確認中..."
            $finalPolicy = Get-CsTeamsChannelsPolicy -Identity $restrictPolicyName
            
            Write-Log "=== 最終設定内容 ==="
            foreach ($setting in $targetSettings.GetEnumerator()) {
                $finalValue = $finalPolicy.($setting.Key)
                $status = if ($finalValue -eq $setting.Value) { "✓" } else { "✗" }
                Write-Log "$status $($setting.Key): $finalValue"
            }
        }
        
        # 6. 適用手順の案内
        Write-Log ""
        Write-Log "=== 次のステップ ==="
        Write-Log "1. 対象ユーザーまたはグループにポリシーを適用:"
        Write-Log "   Grant-CsTeamsChannelsPolicy -Identity 'user@domain.com' -PolicyName '$restrictPolicyName'"
        Write-Log ""
        Write-Log "2. 全ユーザーに適用する場合:"
        Write-Log "   Grant-CsTeamsChannelsPolicy -PolicyName '$restrictPolicyName' -Global"
        Write-Log ""
        Write-Log "3. 設定確認:"
        Write-Log "   Get-CsUserPolicyAssignment -Identity 'user@domain.com' -PolicyType TeamsChannelsPolicy"
        
        Write-Log "=== スクリプト完了 ==="
        
    }
    catch {
        Write-Log "エラーが発生しました: $_" -Level "ERROR"
        Write-Log "バックアップファイル: $backupFile" -Level "ERROR"
        throw
    }
}

# スクリプト実行
Main

要件の変更~各種ドキュメントの変更

さて、お客様とミーティングをしたところ「システム管理者は1人とは限らない。とあるグループを作成して、そのグループ内に所属する人がチームを作成できるようにしてほしい」と依頼を受けました。

この要件をAIコーディングエージェントに依頼し、ドキュメント~設定スクリプトまでを全て修正してもらいます。ついでにMermaidで図まで書いてもらいました。

※vscodeがダークモードで図が見づらい

テストについて

ここまできたら、テストもコード化して自動テストしたいですよね。これもAIコーディングエージェントに依頼しました。

※ 最初に依頼すると、単純にユーザーがグループにいるかどうかの確認を実施するコードを吐いてきたので「それは単体テストだよ」と指摘し、結合テストを作ってもらいました。

CLIの手順を作ってもらいましたが、内容見ると動かなさそうだったので追加質問すると謝られました。

念のためGUI手順書も作ってもらったので、そちらでやるしかないですね。

要件~テスト実施まで、mdファイルで管理してAIエージェントを理由する事で、矛盾なく一気通貫にドキュメントやスクリプトを管理できる事がお分かりいただけたと思います。

残課題

環境固有の認証情報(ID/Password)管理

これを1回限りの構築とするのではなく、別のテナントでも同じことを実施したいと思っています。よって都度都度認証情報を入れるのではなく、作業者がアクセスできるKeyVaultに保存するとか、認証情報の管理戦略は検討する必要があります。実際にIaCを機能させるポイントだと思っています。

結合テストがGUIでしか実施できない・GUIで実施する必要がある

今回はGUIでの結合テストとしましたが、これをCUIにすると効率が上がります。しかし、現場によってはエビデンスを画面ショットで残したい、という場合もあり、そのような時はGUIを使う必要があります。

ここは自動テストソリューションを導入するなどの検討で解決できますし、Computer UseやBrowser Useの技術も進んできます。これらがテキストファイルで管理できれば、IaCのプロセスに組み込め、より管理しやすくなります。

Discussion