main
ps1 126 lines 5.32 KB
Raw
1 #!/usr/bin/env pwsh
2 # scripts/notes/write-note.ps1
3 # ─────────────────────────────────────────────────────────────────────────────
4 # Helper for agents to write notes without wrestling with JSON escaping.
5 # Validates namespace ownership, handles conflicts, pushes automatically.
6 #
7 # Usage:
8 # ./scripts/notes/write-note.ps1 -Agent data -Type decision \
9 # -Content '{"decision":"Use JWT","reasoning":"..."}' \
10 # [-Commit HEAD] [-Promote] [-Archive]
11 # ─────────────────────────────────────────────────────────────────────────────
12
13 [CmdletBinding()]
14 param(
15 [Parameter(Mandatory)][string]$Agent,
16
17 [Parameter(Mandatory)]
18 [ValidateSet("decision","research","review","security-review","progress",
19 "api-contract","risk-assessment","routing-discovery","counter-argument")]
20 [string]$Type,
21
22 [Parameter(Mandatory)]
23 [string]$Content, # JSON object with type-specific fields
24
25 [string]$Commit = "HEAD",
26 [string]$RepoPath = ".",
27 [string]$Remote = "origin",
28 [switch]$Promote, # set promote_to_permanent: true
29 [switch]$Archive, # set archive_on_close: true
30 [switch]$NoPush, # skip auto-push
31 [switch]$Quiet
32 )
33
34 function Log ([string]$msg, [string]$color = "White") {
35 if (-not $Quiet) { Write-Host "[notes/write] $msg" -ForegroundColor $color }
36 }
37
38 $repo = Resolve-Path $RepoPath
39 $namespace = "squad/$($Agent.ToLower())"
40
41 # ── Validate JSON content ────────────────────────────────────────────────────
42 try {
43 $parsed = $Content | ConvertFrom-Json -ErrorAction Stop
44 } catch {
45 Write-Error "Content must be valid JSON. Got: $Content"
46 exit 1
47 }
48
49 # ── Build full note object ────────────────────────────────────────────────────
50 $note = [ordered]@{
51 agent = (Get-Culture).TextInfo.ToTitleCase($Agent.ToLower())
52 timestamp = [System.DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ")
53 type = $Type
54 }
55
56 # Merge content fields into note
57 $parsed.PSObject.Properties | ForEach-Object { $note[$_.Name] = $_.Value }
58
59 # Add flag fields
60 if ($Promote) { $note["promote_to_permanent"] = $true }
61 if ($Archive) { $note["archive_on_close"] = $true }
62
63 $noteJson = $note | ConvertTo-Json -Compress -Depth 10
64
65 # ── Fetch first to avoid conflicts ───────────────────────────────────────────
66 Log "Fetching notes before write..."
67 git -C $repo fetch $Remote "refs/notes/*:refs/notes/*" 2>&1 | Out-Null
68
69 # ── Check if note already exists on this commit ─────────────────────────────
70 $existing = git -C $repo notes --ref=$namespace show $Commit 2>&1
71 $useAppend = ($LASTEXITCODE -eq 0)
72
73 if ($useAppend) {
74 Log "Note exists on $Commit — appending" DarkYellow
75 git -C $repo notes --ref=$namespace append -m $noteJson $Commit
76 } else {
77 git -C $repo notes --ref=$namespace add -m $noteJson $Commit
78 }
79
80 if ($LASTEXITCODE -ne 0) {
81 Write-Error "Failed to write note to refs/notes/$namespace on $Commit"
82 exit 1
83 }
84
85 Log "Note written to refs/notes/$namespace on $($Commit.Substring(0,[Math]::Min(8,$Commit.Length)))" Green
86
87 # ── Push with retry ──────────────────────────────────────────────────────────
88 if (-not $NoPush) {
89 $maxRetries = 5
90 $nsRef = "refs/notes/$namespace"
91
92 for ($i = 0; $i -lt $maxRetries; $i++) {
93 Log "Pushing notes (attempt $($i+1))..."
94 $pushOut = git -C $repo push $Remote "${nsRef}:${nsRef}" 2>&1
95 if ($LASTEXITCODE -eq 0) {
96 Log "Notes pushed successfully." Green
97 break
98 }
99
100 if ($pushOut -match "non-fast-forward|fetch first|rejected") {
101 Log "Push conflict — fetch-first retry..." DarkYellow
102
103 # Force-fetch: overwrite local ref with current remote state
104 git -C $repo fetch $Remote "${nsRef}:${nsRef}" 2>&1 | Out-Null
105
106 # Re-append our note on top of the now-current remote state
107 git -C $repo notes --ref=$namespace append -m $noteJson $Commit 2>&1 | Out-Null
108
109 $jitter = Get-Random -Minimum 0 -Maximum 1000
110 $sleep = [Math]::Pow(2, $i) + $jitter / 1000
111 Start-Sleep -Seconds $sleep
112
113 } else {
114 Log "Push error: $pushOut" Red
115 if ($i -eq $maxRetries - 1) {
116 Write-Warning "Failed after $maxRetries retries. Push manually: git push origin '${nsRef}:${nsRef}'"
117 }
118 }
119 }
120 }
121
122 # ── Show result ───────────────────────────────────────────────────────────────
123 if (-not $Quiet) {
124 Log "Note content:"
125 $note | ConvertTo-Json -Depth 5 | Write-Host -ForegroundColor DarkGray
126 }