@caddis/cli 0.4.22 → 0.4.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundles/antigravity-plugin/plugin.json +1 -1
- package/bundles/antigravity-plugin/skills/_registry.md +1 -1
- package/bundles/antigravity-plugin/skills/port-to-github/SKILL.md +64 -3
- package/bundles/antigravity-plugin/skills/port-to-github/scripts/Get-PortSource.ps1 +30 -2
- package/bundles/antigravity-plugin/skills/port-to-github/scripts/Invoke-PortHistoryClean.ps1 +123 -0
- package/bundles/antigravity-plugin/skills/port-to-github/scripts/New-PortRuntimeManifest.ps1 +258 -0
- package/bundles/antigravity-plugin/skills/port-to-github/scripts/Publish-PortToGitHub.ps1 +20 -1
- package/bundles/antigravity-plugin/skills/port-to-github/scripts/Test-PortContentPolicy.ps1 +218 -0
- package/bundles/antigravity-plugin-extras/plugin.json +1 -1
- package/bundles/antigravity-plugin-extras/skills/_registry.md +1 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/SKILL.md +174 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/profiles/_template/profile.json +55 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/profiles/batch-job/profile.json +68 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/profiles/dotnet-service/profile.json +67 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/profiles/java-service/profile.json +62 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/profiles/node-service/profile.json +66 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/profiles/php-db/profile.json +75 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/profiles/python-web/profile.json +73 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/profiles/static-site/profile.json +87 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/references/config.example.json +18 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/references/profile-contract.md +207 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/references/standard.md +304 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/scripts/AdoptCommon.ps1 +812 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/scripts/Get-AppInventory.ps1 +236 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/scripts/New-AdoptionPlan.ps1 +311 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/scripts/Resolve-AppProfile.ps1 +246 -0
- package/bundles/antigravity-plugin-extras/skills/adopt-app/scripts/Test-AppConformance.ps1 +343 -0
- package/bundles/codex/.codex/skills/devops/port-to-github/SKILL.md +64 -3
- package/bundles/codex/.codex/skills/devops/port-to-github/scripts/Get-PortSource.ps1 +30 -2
- package/bundles/codex/.codex/skills/devops/port-to-github/scripts/Invoke-PortHistoryClean.ps1 +123 -0
- package/bundles/codex/.codex/skills/devops/port-to-github/scripts/New-PortRuntimeManifest.ps1 +258 -0
- package/bundles/codex/.codex/skills/devops/port-to-github/scripts/Publish-PortToGitHub.ps1 +20 -1
- package/bundles/codex/.codex/skills/devops/port-to-github/scripts/Test-PortContentPolicy.ps1 +218 -0
- package/bundles/manifest.json +3 -3
- package/package.json +1 -1
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Check a staged repository against the source-only content policy, over the tips AND all history.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
The policy: a repository holds application code, configuration and templates, migration
|
|
7
|
+
SCRIPTS, tests, and the scripts/manifests that rebuild the environment. It does NOT hold:
|
|
8
|
+
|
|
9
|
+
runtime a runtime or server distribution (MariaDB, PHP, JDK, Node, Python, Apache...)
|
|
10
|
+
binary compiled or packaged files (.exe .dll .so .jar .msi, archives)
|
|
11
|
+
db-data database data files (ibdata1, *.ibd, *.frm, *.mdf, SQLite files, dumps)
|
|
12
|
+
data-export data files above the size threshold (.csv .xlsx .json .xml .sql ...), or any
|
|
13
|
+
data file under a backups/dumps/exports folder
|
|
14
|
+
dependency installed dependencies (node_modules, vendor, .venv, site-packages)
|
|
15
|
+
backup editor and copy backups (*.bak, *.orig, *.old, *~)
|
|
16
|
+
|
|
17
|
+
Large files that match no rule are reported as 'large-review' (a decision, not a violation).
|
|
18
|
+
|
|
19
|
+
.gitignore does not remove a file that was committed before it: every excluded path is
|
|
20
|
+
checked in ALL history, not only in the current files.
|
|
21
|
+
|
|
22
|
+
Reads <staging>\<Name>\mirror.git. Changes nothing. Writes content-policy.json next to it.
|
|
23
|
+
Exit 0: compliant. Exit 1: at least one excluded path exists somewhere in history.
|
|
24
|
+
|
|
25
|
+
Project overrides (optional) in the config file, key "contentPolicy":
|
|
26
|
+
{ "keep": ["regex", ...], "exclude": ["regex", ...], "dataThresholdMB": 5, "largeMB": 10 }
|
|
27
|
+
Regexes match the repository path (forward slashes).
|
|
28
|
+
#>
|
|
29
|
+
[CmdletBinding()]
|
|
30
|
+
param(
|
|
31
|
+
[Parameter(Mandatory)][string]$Name,
|
|
32
|
+
[string]$StagingRoot,
|
|
33
|
+
[string]$ConfigPath,
|
|
34
|
+
[double]$DataThresholdMB,
|
|
35
|
+
[double]$LargeMB,
|
|
36
|
+
# Print every finding, not only the per-folder summary.
|
|
37
|
+
[switch]$Detail
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
$ErrorActionPreference = 'Stop'
|
|
41
|
+
. (Join-Path $PSScriptRoot 'PortCommon.ps1')
|
|
42
|
+
|
|
43
|
+
$config = Get-PortConfig $ConfigPath
|
|
44
|
+
$stage = Join-Path (Get-PortStagingRoot $config $StagingRoot) $Name
|
|
45
|
+
$mirror = Join-Path $stage 'mirror.git'
|
|
46
|
+
if (-not (Test-Path -LiteralPath $mirror)) { throw "No staged mirror at $mirror. Run Get-PortSource.ps1 first." }
|
|
47
|
+
$manifest = Get-Content -LiteralPath (Join-Path $stage 'manifest.json') -Raw | ConvertFrom-Json
|
|
48
|
+
$branch = $manifest.defaultBranch
|
|
49
|
+
|
|
50
|
+
$policy = Get-PortConfigValue $config 'contentPolicy' $null
|
|
51
|
+
$keepRx = @(); $extraExcludeRx = @()
|
|
52
|
+
if ($policy) {
|
|
53
|
+
if ($policy.PSObject.Properties.Name -contains 'keep') { $keepRx = @($policy.keep) }
|
|
54
|
+
if ($policy.PSObject.Properties.Name -contains 'exclude') { $extraExcludeRx = @($policy.exclude) }
|
|
55
|
+
if (-not $DataThresholdMB -and $policy.PSObject.Properties.Name -contains 'dataThresholdMB') { $DataThresholdMB = [double]$policy.dataThresholdMB }
|
|
56
|
+
if (-not $LargeMB -and $policy.PSObject.Properties.Name -contains 'largeMB') { $LargeMB = [double]$policy.largeMB }
|
|
57
|
+
}
|
|
58
|
+
if (-not $DataThresholdMB) { $DataThresholdMB = 5 }
|
|
59
|
+
if (-not $LargeMB) { $LargeMB = 10 }
|
|
60
|
+
$dataBytes = [int64]($DataThresholdMB * 1MB)
|
|
61
|
+
$largeBytes = [int64]($LargeMB * 1MB)
|
|
62
|
+
|
|
63
|
+
# A runtime folder is the runtime's NAME, optionally followed by a version or platform tag
|
|
64
|
+
# (php-8.3.33, jdk-17.0.2, node-v20.11.0-win-x64, apache24). Not any folder that merely STARTS
|
|
65
|
+
# with one: redis-migrations/ or apache-poi-wrapper/ are source.
|
|
66
|
+
$ver = '([-_ ]?v?\d[\w.]*(-(win|linux|x64|x86|amd64|arm64)[\w.-]*)?)?'
|
|
67
|
+
$runtimeDirRx = "(?i)^(mariadb|mysql|mysql-server|php|jdk|jre|openjdk|java|python|python-embed|nodejs|node|apache|httpd|nginx|tomcat|apache-tomcat|redis|postgres|postgresql|pgsql|erlang|ruby|dotnet|go)$ver$"
|
|
68
|
+
$runtimeBinRx = '(?i)^(mariadbd|mysqld|mariadb|mysql|php|php-cgi|node|python|pythonw|java|javaw|httpd|nginx|redis-server|postgres|pg_ctl|erl|ruby|dotnet)\.exe$'
|
|
69
|
+
$dependencyDirRx = '(?i)^(node_modules|vendor|\.venv|venv|site-packages|bower_components|\.yarn|jspm_packages)$'
|
|
70
|
+
$binaryExtRx = '(?i)\.(exe|dll|so|dylib|msi|msix|bin|iso|img|jar|war|ear|class|pyc|pdb|lib|a|o|obj|zip|7z|tar|gz|tgz|rar|bz2|xz|cab|nupkg|whl|deb|rpm)$'
|
|
71
|
+
$dbDataRx = '(?i)(^|/)(ibdata\d*|ib_logfile\d*|ib_buffer_pool|aria_log[\w.]*|undo\d+)$|\.(ibd|frm|myd|myi|mdf|ldf|ndf|sqlite3?|db3|dmp)$'
|
|
72
|
+
$backupRx = '(?i)(\.(bak|orig|old|swp)|~)$'
|
|
73
|
+
$dataExtRx = '(?i)\.(csv|tsv|xlsx|xlsm|xlsb|xls|ods|parquet|avro|orc|jsonl|ndjson|json|xml|sql|dat|txt|log)$'
|
|
74
|
+
$dataDirRx = '(?i)^(backups?|dumps?|exports?|snapshots?|data-?dumps?)$'
|
|
75
|
+
|
|
76
|
+
function Get-Category([string]$Path, [int64]$Bytes) {
|
|
77
|
+
foreach ($rx in $keepRx) { if ($Path -match $rx) { return $null } }
|
|
78
|
+
foreach ($rx in $extraExcludeRx) { if ($Path -match $rx) { return 'project-exclude' } }
|
|
79
|
+
$parts = $Path -split '/'
|
|
80
|
+
$dirs = if ($parts.Count -gt 1) { $parts[0..($parts.Count - 2)] } else { @() }
|
|
81
|
+
$leaf = $parts[-1]
|
|
82
|
+
foreach ($d in $dirs) { if ($d -match $runtimeDirRx) { return 'runtime' } }
|
|
83
|
+
if ($leaf -match $runtimeBinRx) { return 'runtime' }
|
|
84
|
+
foreach ($d in $dirs) { if ($d -match $dependencyDirRx) { return 'dependency' } }
|
|
85
|
+
if ($Path -match $dbDataRx) { return 'db-data' }
|
|
86
|
+
if ($leaf -match $backupRx) { return 'backup' }
|
|
87
|
+
if ($leaf -match $binaryExtRx) { return 'binary' }
|
|
88
|
+
if ($leaf -match $dataExtRx) {
|
|
89
|
+
# Under a backups/dumps folder any data file is excluded, but a plain note (.txt/.md) is not data.
|
|
90
|
+
if ($leaf -notmatch '(?i)\.(txt|md)$') { foreach ($d in $dirs) { if ($d -match $dataDirRx) { return 'data-export' } } }
|
|
91
|
+
# Migration SCRIPTS stay by policy. A large .sql under migration(s)/ may be a script or a
|
|
92
|
+
# data load, so it is a decision, not an automatic exclusion. Data FILES there (csv, json,
|
|
93
|
+
# xlsx) are still data.
|
|
94
|
+
if ($leaf -match '(?i)\.sql$' -and ($dirs | Where-Object { $_ -match '(?i)^migrations?$' })) {
|
|
95
|
+
if ($Bytes -ge $largeBytes -or $Bytes -ge $dataBytes) { return 'large-review' }
|
|
96
|
+
return $null
|
|
97
|
+
}
|
|
98
|
+
if ($Bytes -ge $dataBytes) { return 'data-export' }
|
|
99
|
+
}
|
|
100
|
+
if ($Bytes -ge $largeBytes) { return 'large-review' }
|
|
101
|
+
return $null
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function MGit([string[]]$a) { Invoke-PortGit (@('-C', $mirror) + $a) }
|
|
105
|
+
|
|
106
|
+
# ---- every blob in history, with every path it ever had ----------------------------------
|
|
107
|
+
Write-PortStep 'Reading every path and blob in history'
|
|
108
|
+
$pathsBySha = @{}
|
|
109
|
+
$submodules = New-Object System.Collections.Generic.HashSet[string]
|
|
110
|
+
function Add-PathForSha([string]$sha, [string]$p) {
|
|
111
|
+
if (-not $pathsBySha.ContainsKey($sha)) { $pathsBySha[$sha] = New-Object System.Collections.Generic.HashSet[string] }
|
|
112
|
+
[void]$pathsBySha[$sha].Add($p)
|
|
113
|
+
}
|
|
114
|
+
# EVERY path a blob was ever written to. rev-list --objects names each blob only ONCE, by the
|
|
115
|
+
# first path it meets, so identical content at a second path was invisible (found by the
|
|
116
|
+
# dogfood: a 3-byte test image at two paths survived a clean). log --raw lists every add or
|
|
117
|
+
# change, per commit, including merges (-m) and root commits.
|
|
118
|
+
foreach ($l in (MGit @('-c', 'core.quotePath=false', 'log', '--all', '--raw', '--no-abbrev', '--no-renames', '-m', '--root', '--format=', '--diff-filter=AMT'))) {
|
|
119
|
+
if (-not $l -or $l[0] -ne ':') { continue }
|
|
120
|
+
$meta, $p = $l -split "`t", 2
|
|
121
|
+
$f = $meta -split ' '
|
|
122
|
+
if ($f.Count -lt 4 -or -not $p -or $f[3] -match '^0+$') { continue }
|
|
123
|
+
# Mode 160000 is a submodule: its "blob" is a commit in ANOTHER repository, invisible to a
|
|
124
|
+
# size scan. Record it, so a runtime vendored as a submodule cannot pass unnoticed.
|
|
125
|
+
if ($f[1] -eq '160000') { [void]$submodules.Add($p); continue }
|
|
126
|
+
Add-PathForSha $f[3] $p
|
|
127
|
+
}
|
|
128
|
+
# Safety net: anything reachable that the log did not show.
|
|
129
|
+
foreach ($l in (MGit @('rev-list', '--objects', '--all'))) {
|
|
130
|
+
$sha, $p = $l -split ' ', 2
|
|
131
|
+
if ($p -and -not $pathsBySha.ContainsKey($sha)) { Add-PathForSha $sha $p }
|
|
132
|
+
}
|
|
133
|
+
$sizeBySha = @{}
|
|
134
|
+
foreach ($l in (MGit @('cat-file', '--batch-all-objects', '--batch-check=%(objecttype) %(objectname) %(objectsize)'))) {
|
|
135
|
+
$t, $o, $s = $l -split ' '
|
|
136
|
+
if ($t -eq 'blob' -and $pathsBySha.ContainsKey($o)) { $sizeBySha[$o] = [int64]$s }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Aggregate per path: largest version, number of versions.
|
|
140
|
+
$byPath = @{}
|
|
141
|
+
foreach ($sha in $sizeBySha.Keys) {
|
|
142
|
+
foreach ($p in $pathsBySha[$sha]) {
|
|
143
|
+
if (-not $byPath.ContainsKey($p)) { $byPath[$p] = [pscustomobject]@{ path = $p; maxBytes = [int64]0; versions = 0 } }
|
|
144
|
+
$e = $byPath[$p]; $e.versions++
|
|
145
|
+
if ($sizeBySha[$sha] -gt $e.maxBytes) { $e.maxBytes = $sizeBySha[$sha] }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
$tipFiles = @{}
|
|
150
|
+
foreach ($l in (MGit @('-c', 'core.quotePath=false', 'ls-tree', '-r', '--name-only', $branch))) { $tipFiles[$l] = $true }
|
|
151
|
+
|
|
152
|
+
# ---- classify ----------------------------------------------------------------------------------
|
|
153
|
+
$findings = New-Object System.Collections.Generic.List[object]
|
|
154
|
+
foreach ($e in $byPath.Values) {
|
|
155
|
+
$cat = Get-Category $e.path $e.maxBytes
|
|
156
|
+
if (-not $cat) { continue }
|
|
157
|
+
$findings.Add([pscustomobject]@{
|
|
158
|
+
path = $e.path; category = $cat; maxBytes = $e.maxBytes; versions = $e.versions
|
|
159
|
+
inDefaultBranch = [bool]$tipFiles[$e.path]
|
|
160
|
+
action = if ($cat -eq 'large-review') { 'review' } else { 'exclude' }
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
foreach ($sm in $submodules) {
|
|
164
|
+
$findings.Add([pscustomobject]@{ path = $sm; category = 'submodule'; maxBytes = [int64]0; versions = 1; inDefaultBranch = [bool]$tipFiles[$sm]; action = 'review' })
|
|
165
|
+
}
|
|
166
|
+
$exclude = @($findings | Where-Object { $_.action -eq 'exclude' })
|
|
167
|
+
$review = @($findings | Where-Object { $_.action -eq 'review' })
|
|
168
|
+
|
|
169
|
+
# Group by top two path segments so a 3,000-file vendored runtime reads as one line.
|
|
170
|
+
# Group by folder, at most 4 levels deep, so one vendored runtime reads as one line and a
|
|
171
|
+
# data folder deep in an app is still named precisely.
|
|
172
|
+
function Get-Group([string]$p) { $s = $p -split '/'; if ($s.Count -le 1) { return $p }; $d = $s[0..([math]::Min(3, $s.Count - 2))]; ($d -join '/') + '/' }
|
|
173
|
+
$groups = @($findings | Group-Object { "$($_.category)|$(Get-Group $_.path)" } | ForEach-Object {
|
|
174
|
+
$cat, $grp = $_.Name -split '\|', 2
|
|
175
|
+
[pscustomobject]@{
|
|
176
|
+
category = $cat; group = $grp; files = $_.Count
|
|
177
|
+
historyMB = [math]::Round((($_.Group | Measure-Object maxBytes -Sum).Sum) / 1MB, 1)
|
|
178
|
+
inDefaultBranch = @($_.Group | Where-Object { $_.inDefaultBranch }).Count
|
|
179
|
+
}
|
|
180
|
+
} | Sort-Object historyMB -Descending)
|
|
181
|
+
|
|
182
|
+
$totalBytes = [int64]0; foreach ($v in $sizeBySha.Values) { $totalBytes += $v }
|
|
183
|
+
# Count each blob once, even when it sits at several excluded paths (a per-path sum can exceed
|
|
184
|
+
# the whole history). Plain loops: Measure-Object on NO input has no .Sum under strict mode.
|
|
185
|
+
$exPaths = New-Object System.Collections.Generic.HashSet[string]
|
|
186
|
+
foreach ($x in $exclude) { [void]$exPaths.Add($x.path) }
|
|
187
|
+
$exBytes = [int64]0
|
|
188
|
+
foreach ($sha in $sizeBySha.Keys) { foreach ($p in $pathsBySha[$sha]) { if ($exPaths.Contains($p)) { $exBytes += $sizeBySha[$sha]; break } } }
|
|
189
|
+
$report = [ordered]@{
|
|
190
|
+
name = $Name; defaultBranch = $branch; checkedAt = (Get-Date).ToString('o')
|
|
191
|
+
thresholds = [ordered]@{ dataThresholdMB = $DataThresholdMB; largeMB = $LargeMB }
|
|
192
|
+
historyBlobMB = [math]::Round($totalBytes / 1MB, 1)
|
|
193
|
+
excludedPaths = $exclude.Count; excludedMB = [math]::Round($exBytes / 1MB, 1)
|
|
194
|
+
reviewPaths = $review.Count
|
|
195
|
+
compliant = ($exclude.Count -eq 0)
|
|
196
|
+
groups = $groups
|
|
197
|
+
findings = @($findings | Sort-Object maxBytes -Descending)
|
|
198
|
+
}
|
|
199
|
+
ConvertTo-PortJsonFile $report (Join-Path $stage 'content-policy.json')
|
|
200
|
+
# Plain path list, one per line, for Invoke-PortHistoryClean.ps1 and for .gitignore review.
|
|
201
|
+
Set-Content -LiteralPath (Join-Path $stage 'content-policy-exclude.txt') -Value @($exclude | Sort-Object path | ForEach-Object { $_.path }) -Encoding UTF8
|
|
202
|
+
|
|
203
|
+
Write-Host ''
|
|
204
|
+
Write-Host ("Content policy for {0}: {1} excluded path(s), {2} MB of history; {3} path(s) to review. History holds {4} MB of blobs in total." -f $Name, $exclude.Count, $report.excludedMB, $review.Count, $report.historyBlobMB)
|
|
205
|
+
if ($groups.Count -gt 0) {
|
|
206
|
+
Write-Host ''
|
|
207
|
+
Write-Host ('{0,-13} {1,-55} {2,7} {3,10} {4}' -f 'category', 'where', 'files', 'MB', 'in default branch')
|
|
208
|
+
foreach ($g in $groups) { Write-Host ('{0,-13} {1,-55} {2,7} {3,10} {4}' -f $g.category, $g.group, $g.files, $g.historyMB, $g.inDefaultBranch) }
|
|
209
|
+
}
|
|
210
|
+
if ($Detail) { $findings | Sort-Object maxBytes -Descending | ForEach-Object { Write-Host (' {0,-13} {1,9:N0} KB {2}' -f $_.category, ($_.maxBytes / 1KB), $_.path) } }
|
|
211
|
+
Write-Host " report: $(Join-Path $stage 'content-policy.json')"
|
|
212
|
+
Write-Host " exclude list: $(Join-Path $stage 'content-policy-exclude.txt')"
|
|
213
|
+
if ($exclude.Count -gt 0) {
|
|
214
|
+
Write-PortWarn 'Not compliant. Decide per group: keep (add a "keep" regex to contentPolicy), or remove from history with Invoke-PortHistoryClean.ps1. Adding a path to .gitignore does NOT remove it from history.'
|
|
215
|
+
exit 1
|
|
216
|
+
}
|
|
217
|
+
Write-PortOk 'Compliant: no excluded content anywhere in history.'
|
|
218
|
+
exit 0
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
|
|
49
49
|
| Skill | Path | When to Use |
|
|
50
50
|
|-------|------|-------------|
|
|
51
|
+
| Adopt App | `adopt-app/` | Bring an app that was never scaffolded - a proof of concept that became real, a folder on a server, a checkout with no remote - up to the standard a generated app starts at. Inventories it without running it (local folder, network share, or a remote Windows box over WinRM), works out what stack each component is, measures it against an adoption standard with three verdicts (pass, gap, and question-only-a-person-can-answer), and writes an ordered plan whose steps cannot be done in the wrong order. Use when the user says "this app was never set up properly", "adopt this POC", "onboard this app", "it just runs from a folder on the server", "bring this up to standard", "what would it take to get this into CI", "nobody knows how this is deployed", or "it started as a prototype and now it is production". |
|
|
51
52
|
| Changelog Generator | `changelog-generator/` | Automatically creates user-facing changelogs from git commits by analyzing commit history, categorizing changes, and transforming technical commits into clear, customer-friendly release notes. Turns hours of manual changelog writing into minutes of automated generation. |
|
|
52
53
|
| Deploy Local | `deploy-local/` | End-to-end local deployment loop for Gitea-hosted projects. Use when the user wants to commit on dev, push to remote, monitor the golden CI/build/deploy workflow, validate prod on the configured prod host, and fix lint/test/pipeline failures until deployment is healthy. |
|
|
53
54
|
| Monorepo | `monorepo/` | Monorepo management with Turborepo and pnpm workspaces. Use for Turborepo setup, turbo.json task dependencies, remote caching, pnpm workspace protocol, shared packages (ui-library, config, types), affected-only CI/CD builds, or monorepo structure (apps/ vs packages/). Covers pitfalls like circular deps, version drift, and hoisting issues. |
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: adopt-app
|
|
3
|
+
description: Bring an app that was never scaffolded - a proof of concept that became real, a folder on a server, a checkout with no remote - up to the standard a generated app starts at. Inventories it without running it (local folder, network share, or a remote Windows box over WinRM), works out what stack each component is, measures it against an adoption standard with three verdicts (pass, gap, and question-only-a-person-can-answer), and writes an ordered plan whose steps cannot be done in the wrong order. Use when the user says "this app was never set up properly", "adopt this POC", "onboard this app", "it just runs from a folder on the server", "bring this up to standard", "what would it take to get this into CI", "nobody knows how this is deployed", or "it started as a prototype and now it is production".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# adopt-app
|
|
7
|
+
|
|
8
|
+
An app that was never scaffolded is missing things a generated app gets for free: a repository
|
|
9
|
+
on the team's host, CI, a declared stack, a health signal, a rollback. This skill finds out
|
|
10
|
+
exactly which of them are missing, what only a person can answer, and in what order to fix it.
|
|
11
|
+
|
|
12
|
+
**It never runs the app.** It reads files, file metadata, git state and what the host has
|
|
13
|
+
registered to start the folder. It does not execute an entry point, an installer, a build or a
|
|
14
|
+
migration. The app is usually the only copy of something already in production.
|
|
15
|
+
|
|
16
|
+
**It changes nothing outside its own output folder.** Every phase is read-only. The last phase
|
|
17
|
+
writes a plan; a different, team-specific skill performs it.
|
|
18
|
+
|
|
19
|
+
| Phase | Script | Writes |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| 1. Inventory | `Get-AppInventory.ps1` | `inventory.json` |
|
|
22
|
+
| 2. Stack | `Resolve-AppProfile.ps1` | `profile.json` |
|
|
23
|
+
| 3. Conformance | `Test-AppConformance.ps1` | `conformance.json`, `answers.json` |
|
|
24
|
+
| 4. Plan | `New-AdoptionPlan.ps1` | `adoption-plan.md`, `adoption.json` |
|
|
25
|
+
|
|
26
|
+
Output goes to `<outDir>/<name>/`, by default `.caddis/adopt/<name>/` under the current repo.
|
|
27
|
+
Scripts are PowerShell 7 (`pwsh`); the code that runs on a remote box is Windows PowerShell 5.1
|
|
28
|
+
compatible.
|
|
29
|
+
|
|
30
|
+
## Before you start
|
|
31
|
+
|
|
32
|
+
1. **Read the project config** if one exists: `.caddis/adopt-app.json`, or `$env:ADOPT_APP_CONFIG`.
|
|
33
|
+
Six keys, every one of them read by a named script: `outDir`, `fileBudget`, `skipFolders`,
|
|
34
|
+
`skipPattern`, `untrackedThresholdMB`, and `expectedRemotePattern` - the regex that says what
|
|
35
|
+
"the team's host" means here. Without that pattern the skill cannot tell a team repository from
|
|
36
|
+
a personal one, and it says so rather than assuming. Shape and per-key detail:
|
|
37
|
+
`references/config.example.json`.
|
|
38
|
+
2. **Find out where the app really is.** A folder on a developer's machine may be a stale copy of
|
|
39
|
+
a server folder, or the only copy in existence. Phase 1 reports which, but ask as well.
|
|
40
|
+
3. **Say up front that nothing will be run or changed.** People are right to be nervous about a
|
|
41
|
+
tool pointed at a production folder.
|
|
42
|
+
|
|
43
|
+
## Phase 1 - inventory
|
|
44
|
+
|
|
45
|
+
```powershell
|
|
46
|
+
$s = '<path-to-this-skill>/scripts'
|
|
47
|
+
& $s/Get-AppInventory.ps1 -Path <folder> [-Name <short-name>] [-ComputerName <box>]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
| Where the app is | How to point at it |
|
|
51
|
+
|---|---|
|
|
52
|
+
| this machine | `-Path E:\legacy-app` |
|
|
53
|
+
| a network share | `-Path \\fileserver\apps\legacy-app` |
|
|
54
|
+
| a remote Windows box | `-Path G:\apps\legacy-app -ComputerName appserver01` |
|
|
55
|
+
|
|
56
|
+
Prefer `-ComputerName` for anything on a server. A share scan works but is slow, and the host
|
|
57
|
+
facts - services, scheduled tasks, web sites - can only be read on the box itself.
|
|
58
|
+
|
|
59
|
+
**Read four things in the output before going on.**
|
|
60
|
+
|
|
61
|
+
- **Is the scan complete?** A budget or an access denial makes it PARTIAL: sizes become MINIMUMS,
|
|
62
|
+
the script exits 1, and `-AllowPartial` is how you accept that on the record. Never treat a
|
|
63
|
+
partial scan's "nothing found" as "nothing there". Raise `-FileBudget`, or use
|
|
64
|
+
`-SkipFolder <name>` to spend the budget where the answer is still open. A skipped folder is
|
|
65
|
+
recorded as NOT SCANNED, never as empty, and it prevents later checks from passing.
|
|
66
|
+
- **Neither committed nor ignored.** Folders in this state are the real hazard: one `git add -A`
|
|
67
|
+
puts them in the history for ever. A filename pattern cannot find them - only git can.
|
|
68
|
+
- **Possible credentials.** The heuristics are tuned to be readable rather than exhaustive: they
|
|
69
|
+
skip comments, type declarations, template references and expressions. **Read every file they
|
|
70
|
+
flag yourself.** A credential that was ever committed must be ROTATED, not only deleted.
|
|
71
|
+
- **What runs it.** If nothing on the host references the folder and the app is live, it runs
|
|
72
|
+
somewhere you have not looked. Find out where before planning anything.
|
|
73
|
+
|
|
74
|
+
Junctions and symlinks are reported and never followed. A junction inside a tree that git manages
|
|
75
|
+
is a data-loss hazard: git's recursive remove follows it and deletes the target's contents.
|
|
76
|
+
|
|
77
|
+
## Phase 2 - what kind of app is it
|
|
78
|
+
|
|
79
|
+
```powershell
|
|
80
|
+
& $s/Resolve-AppProfile.ps1 -Name <short-name> [-ForceProfile <profile>]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
A profile describes one KIND of component - how to detect it, its runtimes, build, services or
|
|
84
|
+
schedule, routing, config, data, CI and deploy. `profiles/` holds the starting set; the contract
|
|
85
|
+
is in `references/profile-contract.md`.
|
|
86
|
+
|
|
87
|
+
An app is a SET of components. The script reports **every** profile that matches each component
|
|
88
|
+
folder, not just the winner, because a folder can honestly be two things and picking one silently
|
|
89
|
+
hides a decision. Anything in `alsoMatches` is a question for the owner - leftover scaffolding
|
|
90
|
+
from an abandoned rewrite matches its own stack perfectly and is not part of the app.
|
|
91
|
+
|
|
92
|
+
- **No profile matched** (exit 2) means the rules do not describe this stack yet, not that the app
|
|
93
|
+
has none. Copy `profiles/_template` into a new folder and fill it in. Set `status` to `draft`;
|
|
94
|
+
only a stack that something has actually automated end to end may be `proven`.
|
|
95
|
+
- **A draft profile** is a description, not an automation. Follow it by hand and write down what
|
|
96
|
+
you did, so it can earn `proven` later.
|
|
97
|
+
|
|
98
|
+
## Phase 3 - conformance
|
|
99
|
+
|
|
100
|
+
```powershell
|
|
101
|
+
& $s/Test-AppConformance.ps1 -Name <short-name>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Three verdicts, and the third is the point:
|
|
105
|
+
|
|
106
|
+
| | means |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `PASS` | observed true |
|
|
109
|
+
| `GAP` | observed false, with the evidence named |
|
|
110
|
+
| `ASK` | **not observable from files.** A person must answer it. |
|
|
111
|
+
|
|
112
|
+
Who owns an app, which of 127 branches is the truth, how it is deployed today, whether the
|
|
113
|
+
rollback has ever been run - none of these are in the files. A tool that forces them into
|
|
114
|
+
pass-or-fail produces a confident wrong answer that nobody rechecks.
|
|
115
|
+
|
|
116
|
+
**An unanswered ASK on a blocking item stops the adoption exactly as a GAP does** (exit 2, versus
|
|
117
|
+
exit 1 for a gap). Record answers as they arrive; they persist in `answers.json`:
|
|
118
|
+
|
|
119
|
+
```powershell
|
|
120
|
+
& $s/Test-AppConformance.ps1 -Name app -Answer owner="Team Payments" -Answer canonical-branch=main
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`-Waive <id>` records an accepted exception. It is stored as a waiver and never rewritten as a
|
|
124
|
+
pass - six months later the difference is the whole value of the record.
|
|
125
|
+
|
|
126
|
+
The standard, item by item and why each one is there: `references/standard.md`.
|
|
127
|
+
|
|
128
|
+
## Phase 4 - the plan
|
|
129
|
+
|
|
130
|
+
```powershell
|
|
131
|
+
& $s/New-AdoptionPlan.ps1 -Name <short-name> [-HandoverSkill <team-skill>]
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The order is the product. Doing these in the wrong order costs real work:
|
|
135
|
+
|
|
136
|
+
- Create the repository before settling what may be in it, and removing it later means rewriting
|
|
137
|
+
history other people have already cloned.
|
|
138
|
+
- Rotate a credential after publishing, and it was public in between.
|
|
139
|
+
- Wire up CI before anyone says which branch is the truth, and CI builds the wrong branch.
|
|
140
|
+
|
|
141
|
+
Each step carries why it is there, what to do, and the check that says it is done. Steps that
|
|
142
|
+
cannot start yet are listed as blocked, with the reason - a plan that omits what it cannot do
|
|
143
|
+
reads like a shorter job.
|
|
144
|
+
|
|
145
|
+
Give `adoption-plan.md` to the owner. Agree it before anything is changed.
|
|
146
|
+
|
|
147
|
+
## Handover
|
|
148
|
+
|
|
149
|
+
This skill says WHAT must be true. It does not know your hosts, ports, routing or CI, so it does
|
|
150
|
+
not perform the adoption. Pass the plan to the team-specific skill that does, and name that skill
|
|
151
|
+
with `-HandoverSkill` so the plan does not end in mid-air.
|
|
152
|
+
|
|
153
|
+
For the repository move itself, use the **port-to-github** skill: it stages the source read-only,
|
|
154
|
+
keeps every branch and tag, verifies by SHA, and never pushes back to the source.
|
|
155
|
+
|
|
156
|
+
Re-run phase 3 after each step. It is the same gate either way, so the work cannot quietly drift
|
|
157
|
+
from the standard.
|
|
158
|
+
|
|
159
|
+
## Rules that apply to every phase
|
|
160
|
+
|
|
161
|
+
- **Never run the app.** Not the installer, not the build, not a migration, not "just the health
|
|
162
|
+
check". Reading is always enough to plan.
|
|
163
|
+
- **A partial answer must not read as a clean one.** Every script says when it did not finish, and
|
|
164
|
+
reports a minimum rather than a wrong total.
|
|
165
|
+
- **File contents never go into an artefact.** A config file holds credentials and a report gets
|
|
166
|
+
copied into tickets and chats. Paths, line numbers and key NAMES only.
|
|
167
|
+
- **A check that cannot fail is worse than no check**, and so is one that cannot pass. Both
|
|
168
|
+
manufacture confidence. Every script here exits non-zero on the failure it exists for; do not
|
|
169
|
+
pipe one to `Out-Null` or read only its last line.
|
|
170
|
+
- **Stage files by name.** The apps this skill is pointed at routinely hold gigabytes that are
|
|
171
|
+
neither committed nor ignored, so `git add -A` in one of them is how they become permanent.
|
|
172
|
+
- Code that runs on a remote box runs under Windows PowerShell 5.1. There, with
|
|
173
|
+
`$ErrorActionPreference='Stop'`, a native command's stderr is a terminating error even with
|
|
174
|
+
`2>$null`. Use `'Continue'` and judge native commands by `$LASTEXITCODE`.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "<stack-name>",
|
|
3
|
+
"status": "placeholder",
|
|
4
|
+
"description": "<one sentence: what kind of component this is>",
|
|
5
|
+
"detect": {
|
|
6
|
+
"allFiles": ["<file every such repo has>"],
|
|
7
|
+
"anyFiles": ["<file any of which indicates it>"],
|
|
8
|
+
"noneFiles": ["<file that rules it out>"],
|
|
9
|
+
"fileContains": [{ "file": "<file or glob>", "pattern": "<regex>" }],
|
|
10
|
+
"noneContain": [{ "file": "<file or glob>", "pattern": "<regex that rules it out>" }]
|
|
11
|
+
},
|
|
12
|
+
"runtimes": [
|
|
13
|
+
{
|
|
14
|
+
"kind": "<runtime>",
|
|
15
|
+
"version": "<exact, or where it is declared>",
|
|
16
|
+
"provision": "<how it gets onto a box, e.g. installed manually, or a provisioning script>"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"build": [{ "what": "<step>", "command": "<command>", "cwd": "<folder, optional>" }],
|
|
20
|
+
"services": [
|
|
21
|
+
{
|
|
22
|
+
"role": "<api|web|worker|database>",
|
|
23
|
+
"port": "<the app's HTTP port>",
|
|
24
|
+
"start": "<start command>",
|
|
25
|
+
"manager": "<how the process is kept running, e.g. a service manager or a container runtime>",
|
|
26
|
+
"bind": "<the interface it binds - loopback-only unless a trusted edge forwards identity>",
|
|
27
|
+
"health": "<health check>"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"schedule": {
|
|
31
|
+
"trigger": "<only for batch components: a scheduler, a file-arrival watcher, or a manual/CI trigger>"
|
|
32
|
+
},
|
|
33
|
+
"routing": {
|
|
34
|
+
"mode": "<reverse-proxy|rewrite-rule|dedicated-site|none>",
|
|
35
|
+
"staticRoot": "<built static files, optional>",
|
|
36
|
+
"apiPrefix": "</api>"
|
|
37
|
+
},
|
|
38
|
+
"config": {
|
|
39
|
+
"files": ["<per-environment config files>"],
|
|
40
|
+
"rule": "<how config is kept out of version control>"
|
|
41
|
+
},
|
|
42
|
+
"data": {
|
|
43
|
+
"migrations": "<tool or none>",
|
|
44
|
+
"state": "<where state lives and how it is restored>"
|
|
45
|
+
},
|
|
46
|
+
"ci": {
|
|
47
|
+
"workflow": "<CI workflow file>",
|
|
48
|
+
"requiredJobs": ["<job>"],
|
|
49
|
+
"deployJob": "<job>"
|
|
50
|
+
},
|
|
51
|
+
"deploy": {
|
|
52
|
+
"script": "<deploy script>",
|
|
53
|
+
"verify": ["<a check that would FAIL on a broken deploy>"]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "batch-job",
|
|
3
|
+
"status": "draft",
|
|
4
|
+
"description": "A scheduled or triggered batch engine with no web API: a CLI run by a scheduler, with inputs, outputs, a schedule and an audit trail rather than a port or a route.",
|
|
5
|
+
"detect": {
|
|
6
|
+
"anyFiles": ["pyproject.toml", "requirements.txt"],
|
|
7
|
+
"fileContains": [
|
|
8
|
+
{
|
|
9
|
+
"file": ["pyproject.toml", "requirements.txt"],
|
|
10
|
+
"pattern": "(?i)\\[project\\.scripts\\]|\\btyper\\b|\\bclick\\b|\\bargparse\\b"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"noneContain": [
|
|
14
|
+
{
|
|
15
|
+
"file": ["pyproject.toml", "requirements.txt"],
|
|
16
|
+
"pattern": "(?i)\\b(fastapi|django|flask|starlette|aiohttp)\\b"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"runtimes": [
|
|
21
|
+
{
|
|
22
|
+
"kind": "python",
|
|
23
|
+
"version": "<from pyproject.toml's requires-python>",
|
|
24
|
+
"provision": "<installed on the host that runs it; a per-app virtual environment>"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"kind": "<other, e.g. a native library or a licensed asset the job depends on>",
|
|
28
|
+
"version": "<exact>",
|
|
29
|
+
"provision": "<provisioned separately; licensed assets come from their licensed source, never from version control>"
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"build": [
|
|
33
|
+
{
|
|
34
|
+
"what": "Dependencies",
|
|
35
|
+
"command": "pip install -e . (or pip install -r requirements.txt)"
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"services": [],
|
|
39
|
+
"schedule": {
|
|
40
|
+
"trigger": "<a task scheduler, a file-arrival watcher, or a manual/CI trigger>",
|
|
41
|
+
"account": "<a service account, never a person's own login>",
|
|
42
|
+
"command": "<the CLI entry point, e.g. python -m <package> run>",
|
|
43
|
+
"overlap": "<what happens if a run is still going when the next one is due>"
|
|
44
|
+
},
|
|
45
|
+
"routing": {
|
|
46
|
+
"mode": "none"
|
|
47
|
+
},
|
|
48
|
+
"config": {
|
|
49
|
+
"files": ["<the config file the CLI reads>"],
|
|
50
|
+
"rule": "kept out of version control; a committed example file"
|
|
51
|
+
},
|
|
52
|
+
"data": {
|
|
53
|
+
"migrations": "none",
|
|
54
|
+
"state": "<input, staging and output locations, kept outside the repo; a retention rule; never committed, especially real customer data>"
|
|
55
|
+
},
|
|
56
|
+
"ci": {
|
|
57
|
+
"workflow": "<CI workflow file: lint, tests, a dry run against sanitised sample data>",
|
|
58
|
+
"requiredJobs": ["<lint_and_test>"],
|
|
59
|
+
"deployJob": "<deploy_prod (updates code and dependencies; does not itself run a batch)>"
|
|
60
|
+
},
|
|
61
|
+
"deploy": {
|
|
62
|
+
"script": "<deploy script: stop the schedule, update, re-enable; never mid-run>",
|
|
63
|
+
"verify": [
|
|
64
|
+
"a dry run against sanitised sample data exits zero and writes the expected output artefact",
|
|
65
|
+
"the next scheduled run completes and writes its expected output artefact"
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
}
|
package/bundles/antigravity-plugin-extras/skills/adopt-app/profiles/dotnet-service/profile.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dotnet-service",
|
|
3
|
+
"status": "draft",
|
|
4
|
+
"description": "An ASP.NET Core web API or web app (.NET 6+), optionally serving a built frontend from the same process.",
|
|
5
|
+
"detect": {
|
|
6
|
+
"anyFiles": ["**/*.csproj", "**/*.sln"],
|
|
7
|
+
"fileContains": [
|
|
8
|
+
{
|
|
9
|
+
"file": "**/*.csproj",
|
|
10
|
+
"pattern": "(?i)Microsoft\\.NET\\.Sdk\\.Web|Microsoft\\.AspNetCore"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"runtimes": [
|
|
15
|
+
{
|
|
16
|
+
"kind": "dotnet",
|
|
17
|
+
"version": "<the target framework moniker in the .csproj, e.g. net8.0>",
|
|
18
|
+
"provision": "<the matching .NET runtime/SDK installed on dev and prod hosts>"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"build": [
|
|
22
|
+
{
|
|
23
|
+
"what": "Restore and publish",
|
|
24
|
+
"command": "dotnet publish -c Release -o <publish folder>"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"what": "Frontend bundle, if the app serves one",
|
|
28
|
+
"command": "npm ci && npm run build",
|
|
29
|
+
"cwd": "<the frontend folder, if any>"
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"services": [
|
|
33
|
+
{
|
|
34
|
+
"role": "api",
|
|
35
|
+
"port": "<the app's HTTP port>",
|
|
36
|
+
"start": "dotnet <published .dll>",
|
|
37
|
+
"manager": "<a service manager or a container runtime>",
|
|
38
|
+
"bind": "<loopback-only, unless a trusted edge forwards identity>",
|
|
39
|
+
"health": "<a real health endpoint, e.g. /health via a health-checks middleware>"
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"routing": {
|
|
43
|
+
"mode": "reverse-proxy",
|
|
44
|
+
"staticRoot": "<built frontend output folder, if any, or wwwroot>",
|
|
45
|
+
"apiPrefix": "/api"
|
|
46
|
+
},
|
|
47
|
+
"config": {
|
|
48
|
+
"files": ["<a per-environment settings file, kept out of version control for real environments>"],
|
|
49
|
+
"rule": "real settings files are gitignored; a committed example settings file shows the shape only"
|
|
50
|
+
},
|
|
51
|
+
"data": {
|
|
52
|
+
"migrations": "<the app's migration tool, e.g. an ORM's migrations feature, or none>",
|
|
53
|
+
"state": "<an external database; nothing durable lives on the app host itself>"
|
|
54
|
+
},
|
|
55
|
+
"ci": {
|
|
56
|
+
"workflow": "<CI workflow file: restore, build, test, publish>",
|
|
57
|
+
"requiredJobs": ["<lint_and_test>"],
|
|
58
|
+
"deployJob": "<deploy_prod>"
|
|
59
|
+
},
|
|
60
|
+
"deploy": {
|
|
61
|
+
"script": "<deploy script: publish, stop, swap, start>",
|
|
62
|
+
"verify": [
|
|
63
|
+
"the health endpoint returns a body with a status field, not just HTTP 200",
|
|
64
|
+
"a rendered page or the frontend bundle's hash changed, so an old bundle behind a new backend cannot pass"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
}
|