@caddis/cli 0.4.18 → 0.4.20
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/scripts/caddis_adhoc.py +530 -0
- package/bundles/antigravity-plugin/scripts/caddis_lanes.py +8 -0
- package/bundles/antigravity-plugin/skills/_registry.md +5 -0
- package/bundles/antigravity-plugin/skills/data-lineage/SKILL.md +32 -0
- package/bundles/antigravity-plugin/skills/hld/SKILL.md +39 -0
- package/bundles/antigravity-plugin/skills/lld/SKILL.md +45 -0
- package/bundles/antigravity-plugin/skills/mmr/SKILL.md +87 -0
- package/bundles/antigravity-plugin/skills/port-to-github/SKILL.md +231 -0
- package/bundles/antigravity-plugin/skills/port-to-github/references/ci-and-credentials.md +66 -0
- package/bundles/antigravity-plugin/skills/port-to-github/references/config.example.json +18 -0
- package/bundles/antigravity-plugin/skills/port-to-github/references/old-host.md +61 -0
- package/bundles/antigravity-plugin/skills/port-to-github/scripts/Compare-PortRefs.ps1 +68 -0
- package/bundles/antigravity-plugin/skills/port-to-github/scripts/Convert-PortWorkflow.ps1 +92 -0
- package/bundles/antigravity-plugin/skills/port-to-github/scripts/Get-PortSource.ps1 +459 -0
- package/bundles/antigravity-plugin/skills/port-to-github/scripts/PortCommon.ps1 +130 -0
- package/bundles/antigravity-plugin/skills/port-to-github/scripts/Publish-PortToGitHub.ps1 +156 -0
- package/bundles/antigravity-plugin/skills/port-to-github/scripts/Switch-CheckoutOrigin.ps1 +182 -0
- package/bundles/antigravity-plugin/skills/port-to-github/scripts/Test-PortReadiness.ps1 +218 -0
- package/bundles/antigravity-plugin/skills/port-to-github/scripts/Test-PortWorkflowParses.ps1 +62 -0
- package/bundles/antigravity-plugin-extras/plugin.json +1 -1
- package/bundles/antigravity-plugin-extras/skills/_registry.md +3 -0
- package/bundles/antigravity-plugin-extras/skills/data-lineage/SKILL.md +158 -0
- package/bundles/antigravity-plugin-extras/skills/data-lineage/scripts/convert_doc_to_confluence.py +1430 -0
- package/bundles/antigravity-plugin-extras/skills/hld/SKILL.md +220 -0
- package/bundles/antigravity-plugin-extras/skills/hld/references/gold-standard-hld.md +428 -0
- package/bundles/antigravity-plugin-extras/skills/hld/scripts/convert_doc_to_confluence.py +1430 -0
- package/bundles/antigravity-plugin-extras/skills/lld/SKILL.md +198 -0
- package/bundles/antigravity-plugin-extras/skills/lld/scripts/convert_doc_to_confluence.py +1430 -0
- package/bundles/codex/.codex/skills/devops/port-to-github/SKILL.md +231 -0
- package/bundles/codex/.codex/skills/devops/port-to-github/references/ci-and-credentials.md +66 -0
- package/bundles/codex/.codex/skills/devops/port-to-github/references/config.example.json +18 -0
- package/bundles/codex/.codex/skills/devops/port-to-github/references/old-host.md +61 -0
- package/bundles/codex/.codex/skills/devops/port-to-github/scripts/Compare-PortRefs.ps1 +68 -0
- package/bundles/codex/.codex/skills/devops/port-to-github/scripts/Convert-PortWorkflow.ps1 +92 -0
- package/bundles/codex/.codex/skills/devops/port-to-github/scripts/Get-PortSource.ps1 +459 -0
- package/bundles/codex/.codex/skills/devops/port-to-github/scripts/PortCommon.ps1 +130 -0
- package/bundles/codex/.codex/skills/devops/port-to-github/scripts/Publish-PortToGitHub.ps1 +156 -0
- package/bundles/codex/.codex/skills/devops/port-to-github/scripts/Switch-CheckoutOrigin.ps1 +182 -0
- package/bundles/codex/.codex/skills/devops/port-to-github/scripts/Test-PortReadiness.ps1 +218 -0
- package/bundles/codex/.codex/skills/devops/port-to-github/scripts/Test-PortWorkflowParses.ps1 +62 -0
- package/bundles/manifest.json +3 -3
- package/package.json +1 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lld
|
|
3
|
+
description: Generate Lower-Level Design (LLD) Markdown documentation and Confluence XHTML with native PlantUML diagrams.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /lld Command
|
|
7
|
+
|
|
8
|
+
Run the Lower-Level Design (LLD) generation procedure:
|
|
9
|
+
|
|
10
|
+
1. **Detailed Technical Discovery:**
|
|
11
|
+
- Scan physical hosts, service managers (NSSM, systemd), environment configurations, and ports.
|
|
12
|
+
- Extract internal API routes, controllers, middleware, and dependency injection patterns.
|
|
13
|
+
- Inspect database schemas, tables, views, primary keys, and index configurations.
|
|
14
|
+
- Catalog stored procedures (`usp_*`), parameter lists, analytical logic flows, and discrepancy output structures.
|
|
15
|
+
- Map ETL scripts, daily/monthly cron schedules, record volumes, idempotency logic, and shared audit tables (e.g. `ETL_AUDIT_LOG`).
|
|
16
|
+
- Map background refresher loops, polling intervals, watermark sync, and caching tiers.
|
|
17
|
+
- Map UI screens to stable query IDs (`QRY-*`), API endpoints, repository methods, and source tables.
|
|
18
|
+
|
|
19
|
+
2. **Component & Interface Classification Interview (Grill-Me):**
|
|
20
|
+
- **MANDATORY GATE:** Present discovered internal components (`SYS_Lxx`) and internal interfaces (`INT_Lxx`) to the user.
|
|
21
|
+
- Ask the user to confirm/clarify:
|
|
22
|
+
- Which internal components are New / Impacted (`Impacted = Yes`, Red `#DC2626`) vs Existing fleet databases/services (`Impacted = No`, Blue `#1D4ED8`)?
|
|
23
|
+
- Which interfaces are Existing Connectivity (Green `#16A34A`) vs New Connectivity (Red `#DC2626`)?
|
|
24
|
+
- Seek user sign-off in a single interactive turn before drafting diagrams.
|
|
25
|
+
|
|
26
|
+
3. **Author Canonical Markdown Document:**
|
|
27
|
+
- Create document at `docs/<app>/<app>-lld.md`.
|
|
28
|
+
- Adhere strictly to the standard LLD structure including the 5 mandatory technical sections:
|
|
29
|
+
1. Operational Process Flow (multi-stage batch, validation, refresh, and serving pipeline)
|
|
30
|
+
2. End-to-End Operational Sequence Diagram (technical interaction sequence)
|
|
31
|
+
3. Detailed Solution Architecture Landscape (`SYS_Lxx` components, `INT_Lxx` interfaces, Applications and Interfaces tables)
|
|
32
|
+
4. End-to-End Database and Data Processing Pipeline (staging, validation, mart rollup, API extraction)
|
|
33
|
+
5. End-to-End Database Lineage Specification (complete domain -> source -> staging -> SP -> mart -> API -> UI matrix)
|
|
34
|
+
- Dissect every ETL pipeline with schedules, row volumes, and idempotency.
|
|
35
|
+
- Detail stored procedures catalog with parameters, join logic, and thresholds.
|
|
36
|
+
- Use colorful, professional Mermaid diagrams with explicit `classDef` styling palettes (no raw yellow boxes).
|
|
37
|
+
- Use ASD-STE100 Simplified Technical English: active voice, short sentences, zero em-dashes.
|
|
38
|
+
|
|
39
|
+
4. **Convert to Confluence Storage Format XHTML:**
|
|
40
|
+
- Run the PlantUML-enabled converter:
|
|
41
|
+
```powershell
|
|
42
|
+
python scripts/convert_doc_to_confluence.py docs/<app>/<app>-lld.md --output docs/<app>/<app>-lld.html --diagram-type plantuml
|
|
43
|
+
```
|
|
44
|
+
- Verify that all diagrams are converted to native `<ac:structured-macro ac:name="plantuml">` blocks with scale 1.2, natural splines, auto-wrapped edge labels, crimson red borders on impacted components, and embedded Creole legends.
|
|
45
|
+
- Verify that output validates as well-formed XML.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mmr
|
|
3
|
+
description: Multi-Model Review & Runner (MMR) — run an ad-hoc task in an isolated multi-lane sandbox with rate-limit failover, test gates, and second-vendor review, or run a fast cross-model review on a diff
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /caddis:mmr — Multi-Model Review & Runner
|
|
7
|
+
|
|
8
|
+
Execute an ad-hoc task across model lanes (GLM-5.3 → Codex Sol → Claude) inside an isolated
|
|
9
|
+
git worktree with automated test gates and second-vendor review (DeepSeek). Destroys the worktree
|
|
10
|
+
immediately on completion (zero worktree leaks) and leaves the output on a clean branch with a
|
|
11
|
+
simple Verdict Card.
|
|
12
|
+
|
|
13
|
+
When invoked without a task prompt (or with a git range), `/mmr` runs a direct cross-model diff review.
|
|
14
|
+
|
|
15
|
+
## Natural Language Triggers
|
|
16
|
+
You can trigger MMR naturally without typing the full slash command:
|
|
17
|
+
- *"Run this on another lane: <task>"* → `/mmr "<task>"`
|
|
18
|
+
- *"Try this with multilane: <task>"* → `/mmr "<task>"`
|
|
19
|
+
- *"Keep it"* / *"Keep the changes"* → `/mmr keep`
|
|
20
|
+
- *"Drop it"* / *"Discard the attempt"* → `/mmr drop`
|
|
21
|
+
- *"Check mmr status"* / *"Which lanes are ready?"* → `/mmr status`
|
|
22
|
+
- *"Review this diff with mmr"* → `/mmr`
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Commands
|
|
27
|
+
|
|
28
|
+
### 1. Run an Ad-Hoc Task
|
|
29
|
+
```bash
|
|
30
|
+
python scripts/caddis_adhoc.py run "<task description>" [--type backend|tests|docs|ui|hard]
|
|
31
|
+
# Shorthand:
|
|
32
|
+
python scripts/caddis_adhoc.py "<task description>"
|
|
33
|
+
```
|
|
34
|
+
- **Sandbox Isolation:** Creates an ephemeral 1-phase plan at `.caddis/orchestrator/adhoc/<slug>.md`.
|
|
35
|
+
- **Pre-flight Checks:** Verifies dirty tree and API keys. Warns if local edits exist.
|
|
36
|
+
- **Failover Cascade:** Automatically cascades from GLM to Codex to Claude if a lane is out of budget.
|
|
37
|
+
- **Local Test Gates:** Runs project test gates before review.
|
|
38
|
+
- **Cross-Vendor Review:** DeepSeek or GLM reviews the diff.
|
|
39
|
+
- **Immediate Worktree Cleanup:** Stashes/commits results to `lane/adhoc-<slug>` and immediately removes the worktree. Zero open worktrees remain.
|
|
40
|
+
- **Verdict Card:** Prints PASS/FAIL summary with diff stats and next actions.
|
|
41
|
+
|
|
42
|
+
### 2. Keep the Result
|
|
43
|
+
```bash
|
|
44
|
+
python scripts/caddis_adhoc.py keep
|
|
45
|
+
```
|
|
46
|
+
- Keeps the output branch `lane/adhoc-<slug>`.
|
|
47
|
+
- **Main Branch Protection:** If you are on `main` or `master`, direct merge is disabled to protect trunk. Recommends running `/ship-pr`.
|
|
48
|
+
- If on a feature branch and the working tree is clean, provides squash-merge instructions.
|
|
49
|
+
|
|
50
|
+
### 3. Drop the Result
|
|
51
|
+
```bash
|
|
52
|
+
python scripts/caddis_adhoc.py drop
|
|
53
|
+
```
|
|
54
|
+
- Deletes the branch `lane/adhoc-<slug>`.
|
|
55
|
+
- Deletes the ephemeral plan file and run logs.
|
|
56
|
+
- Workspace remains clean and untouched.
|
|
57
|
+
|
|
58
|
+
### 4. Check Status
|
|
59
|
+
```bash
|
|
60
|
+
python scripts/caddis_adhoc.py status
|
|
61
|
+
```
|
|
62
|
+
- Displays API key readiness for all lanes (`Anthropic`, `DeepSeek`, `GLM`, `OpenAI`).
|
|
63
|
+
- Shows the latest task status, model used, and gates/review verdicts.
|
|
64
|
+
- Lists any unmerged `lane/adhoc-*` branches.
|
|
65
|
+
|
|
66
|
+
### 5. Review Diff Only
|
|
67
|
+
```bash
|
|
68
|
+
python scripts/caddis_adhoc.py review [--range <range>]
|
|
69
|
+
# Shorthand (no args):
|
|
70
|
+
python scripts/caddis_adhoc.py
|
|
71
|
+
```
|
|
72
|
+
- Fast cross-model review of working tree or git range (e.g., `origin/main..HEAD`).
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Tool Resolution
|
|
77
|
+
Resolve `caddis_adhoc.py` deterministically:
|
|
78
|
+
```bash
|
|
79
|
+
if [ -f "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_adhoc.py" ]; then
|
|
80
|
+
TOOL="${CLAUDE_PLUGIN_ROOT}/scripts/caddis_adhoc.py"
|
|
81
|
+
elif [ -f "scripts/caddis_adhoc.py" ]; then
|
|
82
|
+
TOOL="scripts/caddis_adhoc.py"
|
|
83
|
+
else
|
|
84
|
+
echo "caddis_adhoc.py not found — report this and stop."
|
|
85
|
+
exit 3
|
|
86
|
+
fi
|
|
87
|
+
```
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: port-to-github
|
|
3
|
+
description: Move (port) an app or repository to GitHub from ANY source - a Gitea/GitLab/any git server URL, a local folder, a network (UNC) share, or a folder on a remote Windows box reached over WinRM - with or without existing git history. Keeps every branch and tag, verifies by SHA, translates Gitea/Forgejo Actions CI, switches deploy checkouts to GitHub with automatic rollback, and retires the old host safely. Use when the user says "move this app to GitHub", "port X to GitHub", "migrate from Gitea", "put this folder on GitHub", "this app only exists on the server", "import the repo from the prod box", or "cut the deploy over to GitHub".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# port-to-github
|
|
7
|
+
|
|
8
|
+
Move one app to GitHub without losing history, leaking a secret, or breaking its deploy.
|
|
9
|
+
|
|
10
|
+
The work is in **six phases**. Each ends in a check that can FAIL. Do not start a phase until
|
|
11
|
+
the one before it passed. Scripts are in `scripts/` next to this file. They are PowerShell 7
|
|
12
|
+
(`pwsh`); the parts that run on a remote box are Windows PowerShell 5.1 compatible.
|
|
13
|
+
|
|
14
|
+
| Phase | Script | Changes anything outside this machine? |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| 1. Stage the source | `Get-PortSource.ps1` | No. The source is only read. |
|
|
17
|
+
| 2. Readiness | `Test-PortReadiness.ps1` | No. |
|
|
18
|
+
| 3. Publish | `Publish-PortToGitHub.ps1` | **Yes: creates a GitHub repo and pushes.** |
|
|
19
|
+
| 4. CI | `Convert-PortWorkflow.ps1`, `Test-PortWorkflowParses.ps1` | Yes: a feature branch and a PR. |
|
|
20
|
+
| 5. Cutover | `Switch-CheckoutOrigin.ps1` | **Yes: changes where a deploy host pulls from.** |
|
|
21
|
+
| 6. Retire the old host | manual, see `references/old-host.md` | **Yes, and can be destructive.** |
|
|
22
|
+
|
|
23
|
+
**Stop and ask the user before phases 3, 5 and 6.** Each one is outward-facing. Say exactly
|
|
24
|
+
what will be created or changed, on which host, and how to undo it.
|
|
25
|
+
|
|
26
|
+
Not every port needs every phase. A folder that never had CI or a deploy host ends after
|
|
27
|
+
phase 3. Say which phases apply and why, before you start.
|
|
28
|
+
|
|
29
|
+
## Before you start
|
|
30
|
+
|
|
31
|
+
1. **Read the project config**, if one exists: `.caddis/port-to-github.json` in the repo you are
|
|
32
|
+
working from (or `$env:PORT_TO_GITHUB_CONFIG`). It sets the GitHub `owner`, `visibility`,
|
|
33
|
+
staging folder, the name for the old remote, dependency hosts and their secrets, and
|
|
34
|
+
`projectNotes` - pointers to the project's own rules. **Read every file `projectNotes` names
|
|
35
|
+
before phase 4.** Project rules beat this skill. The shape is in `references/config.example.json`.
|
|
36
|
+
2. `gh auth status` must show an account that can create repos in the owner. `git` must be able
|
|
37
|
+
to push to github.com (run `gh auth setup-git` once if not).
|
|
38
|
+
3. **Find the authoritative copy.** If a folder is a clone of a server, port from the SERVER
|
|
39
|
+
URL, not the folder: the folder may lack branches the server has. Port from a folder only
|
|
40
|
+
when it is the only copy, or when it holds commits the server lacks. Phase 1 reports both
|
|
41
|
+
cases.
|
|
42
|
+
|
|
43
|
+
## Phase 1 - stage the source
|
|
44
|
+
|
|
45
|
+
```powershell
|
|
46
|
+
$s = '<path-to-this-skill>/scripts'
|
|
47
|
+
& $s/Get-PortSource.ps1 -Source <source> [-Name <repo-name>] [-IncludeUncommitted]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
| Source | Example `-Source` |
|
|
51
|
+
|---|---|
|
|
52
|
+
| any git server | `https://gitea.example/org/app.git`, `git@host:org/app.git` |
|
|
53
|
+
| local folder | `D:\work\app` |
|
|
54
|
+
| network share | `\\fileserver\projects\app` |
|
|
55
|
+
| remote box (WinRM) | `winrm://appserver01/D:/apps/app` (or `-ComputerName appserver01 -Source D:\apps\app`) |
|
|
56
|
+
|
|
57
|
+
What it does, for every kind: builds `<staging>/<Name>/mirror.git` holding ONLY branches and
|
|
58
|
+
tags, removes the remote so nothing can be pushed back to the source, and writes
|
|
59
|
+
`manifest.json`. A folder without history becomes one import commit; `node_modules`, virtual
|
|
60
|
+
envs and caches are skipped, and a `.gitignore` is written if there was none.
|
|
61
|
+
|
|
62
|
+
Read the output. These need a decision, not a shrug:
|
|
63
|
+
|
|
64
|
+
- **Uncommitted changes** in a source checkout are left out unless you pass
|
|
65
|
+
`-IncludeUncommitted`, which puts them on a separate branch `port/uncommitted-snapshot-*`.
|
|
66
|
+
The real branches stay exactly as committed. Ask the user which they want.
|
|
67
|
+
- **Remote-only branches**: branches the checkout only knows as `origin/x`. If the checkout is
|
|
68
|
+
the only copy, re-run with `-PromoteRemoteBranches origin`. If a server has them, port from it.
|
|
69
|
+
- **Likely secrets** in a plain folder or a snapshot stop the script (exit 2). Nothing has left
|
|
70
|
+
the machine. Show the user the list. Re-run with `-OnSecret Exclude` only after they agree
|
|
71
|
+
those files must not be published.
|
|
72
|
+
- **Dropped refs** (`refs/pull/*`, `refs/remotes/*`, notes) are expected. GitHub refuses
|
|
73
|
+
`refs/pull/*`, and pull-request discussion does not move with git anyway. If the old host's
|
|
74
|
+
PR and issue history matters, export it before phase 6.
|
|
75
|
+
|
|
76
|
+
## Phase 2 - readiness
|
|
77
|
+
|
|
78
|
+
```powershell
|
|
79
|
+
& $s/Test-PortReadiness.ps1 -Name <repo-name>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Blockers (exit 1) must be fixed before phase 3:** a file over 100 MB anywhere in history,
|
|
83
|
+
Git LFS content, a likely live credential in the current tree, an empty `${{ }}` in a file
|
|
84
|
+
under `.github/workflows`. A credential found in the tree must be **rotated**, not only
|
|
85
|
+
deleted: history keeps it. (An empty `${{ }}` in a `.gitea/` workflow is only a warning here:
|
|
86
|
+
GitHub does not read that folder, so it matters in phase 4.)
|
|
87
|
+
|
|
88
|
+
**Branches that already contain `.github/workflows` run them the moment they are pushed** -
|
|
89
|
+
deploy jobs included. Readiness lists those branches; publish refuses them unless you pass
|
|
90
|
+
`-AllowWorkflowRuns`, which you do only after reading every such workflow with the user.
|
|
91
|
+
|
|
92
|
+
**Warnings are decisions.** Go through each with the user: secret-type files in past history
|
|
93
|
+
(the new repo will expose them), CI credentials, dependency hosts, runner labels, files that
|
|
94
|
+
still name the old host. The report is in `readiness.json`.
|
|
95
|
+
|
|
96
|
+
## Phase 3 - publish (ask first)
|
|
97
|
+
|
|
98
|
+
Tell the user: the repo name, owner, visibility, branch and tag counts. Then:
|
|
99
|
+
|
|
100
|
+
```powershell
|
|
101
|
+
& $s/Publish-PortToGitHub.ps1 -Name <repo-name> [-Owner <org>] [-Visibility private]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
It first checks the stage still holds exactly the refs phase 1 recorded (anything else would be
|
|
105
|
+
pushed and then "verified" against itself), creates the repo EMPTY, pushes `refs/heads/*` and
|
|
106
|
+
`refs/tags/*` with an explicit refspec, sets the default branch, and compares every ref by SHA
|
|
107
|
+
(exit 1 on any difference). Do not run other git commands inside the stage; if you did,
|
|
108
|
+
rebuild it with `Get-PortSource.ps1 -Force`.
|
|
109
|
+
|
|
110
|
+
- **Never** `git push --mirror`: it tries to push hidden refs, fails AFTER pushing the rest (so
|
|
111
|
+
a populated repo reports failure), and deletes anything on GitHub the stage lacks.
|
|
112
|
+
- **An existing repo.** If one exists with history, the script refuses. Find out whether it
|
|
113
|
+
shares history with the source: for a few of its commit SHAs, `git -C mirror.git cat-file -e
|
|
114
|
+
<sha>^{commit}`. If it shares nothing (for example a snapshot someone uploaded with a fresh
|
|
115
|
+
"Initial commit"), pushing on top welds a fake ancestry in forever: save anything of value
|
|
116
|
+
from it, delete it, and re-run. Use `-AllowExisting` only when it is a genuine older copy; the
|
|
117
|
+
script then requires every GitHub branch to be an ancestor of the staged one.
|
|
118
|
+
- Verify with the SHA comparison, never by counting refs or by "the push said OK".
|
|
119
|
+
|
|
120
|
+
Stop here if the app has no CI and no deploy host. Otherwise continue.
|
|
121
|
+
|
|
122
|
+
## Phase 4 - CI
|
|
123
|
+
|
|
124
|
+
Work in a normal clone of the NEW repo, on a feature branch.
|
|
125
|
+
|
|
126
|
+
```powershell
|
|
127
|
+
git clone https://github.com/<owner>/<repo>.git; cd <repo>; git switch -c port/github-actions
|
|
128
|
+
& $s/Convert-PortWorkflow.ps1 -AddPermissions [-SourceRef <sha>]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
It copies `.gitea/workflows` (or `.forgejo/workflows`) to `.github/workflows`, rewrites the
|
|
132
|
+
`gitea.` context to `github.`, and prints every credential line. **Leave the scripts the
|
|
133
|
+
workflows call where they are** - GitHub only needs the workflow file under `.github/`.
|
|
134
|
+
|
|
135
|
+
Then do by hand, one line at a time - this is the whole job, and a blanket swap breaks it:
|
|
136
|
+
|
|
137
|
+
1. **Classify every credential line.** It faces ORIGIN (checkout, fetch, reset, pushing a tag
|
|
138
|
+
back to this repo) or it names a DEPENDENCY host (a package registry, a library pinned from
|
|
139
|
+
another repo or server). There is no third category.
|
|
140
|
+
- ORIGIN -> `${{ github.token }}`, username `x-access-token`. This includes an
|
|
141
|
+
`actions/checkout` that pushes tags - it is easy to miss.
|
|
142
|
+
- DEPENDENCY -> a secret valid for THAT host. `github.token` reads only the repo whose
|
|
143
|
+
workflow is running, so a library in ANOTHER GitHub repo needs its own read token.
|
|
144
|
+
One job can hold two credential pairs that serve different hosts. Change only the pair
|
|
145
|
+
that faces origin.
|
|
146
|
+
2. **One Authorization header per host.** `http.<url>.extraheader` values ACCUMULATE; two for the
|
|
147
|
+
same host is an HTTP 400. `actions/checkout` persists its own header, so a step that adds
|
|
148
|
+
another for github.com must unset the checkout's for that command and restore it after.
|
|
149
|
+
3. **npm and GitHub dependencies:** npm fetches a GitHub dependency over SSH even when
|
|
150
|
+
`package.json` says `https`. Set `url.https://github.com/.insteadOf=ssh://git@github.com/`
|
|
151
|
+
and `GIT_TERMINAL_PROMPT=0` so a missing credential fails in seconds instead of hanging.
|
|
152
|
+
4. **Runner labels** must exist on GitHub (`runs-on:`). Self-hosted runners must be registered
|
|
153
|
+
to the org or repo, not only to the old host.
|
|
154
|
+
5. **Never write `${{ }}` in any text in a workflow**, including comments inside `run:` blocks.
|
|
155
|
+
6. **If a workflow was frozen** on the old host (triggers reduced to `workflow_dispatch`), pass
|
|
156
|
+
`-SourceRef` with the last commit before the freeze. A workflow built from a frozen copy
|
|
157
|
+
fires on nothing, which looks like "still queued", not like a fault.
|
|
158
|
+
|
|
159
|
+
Details and the credential patterns: `references/ci-and-credentials.md`.
|
|
160
|
+
|
|
161
|
+
**Validate before anything reaches `main`.** Commit the workflow files BY NAME, push the branch,
|
|
162
|
+
then:
|
|
163
|
+
|
|
164
|
+
```powershell
|
|
165
|
+
& $s/Test-PortWorkflowParses.ps1 -Owner <owner> -Repo <repo> -Branch port/github-actions
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
An unparseable workflow produces a failed run with zero jobs even on a branch its triggers
|
|
169
|
+
ignore; a valid one produces no run (or a normal run). A broken workflow on `main` leaves the
|
|
170
|
+
app with no deploy path, so this check is not optional. Then open a PR and let the real jobs
|
|
171
|
+
run; merging follows the repo's normal rules.
|
|
172
|
+
|
|
173
|
+
## Phase 5 - cutover (ask first)
|
|
174
|
+
|
|
175
|
+
Only for apps with a deploy host whose checkout pulls from the old origin. Order matters:
|
|
176
|
+
|
|
177
|
+
1. `Switch-CheckoutOrigin.ps1 -Mode Inspect` on each host. Record what it prints.
|
|
178
|
+
2. **Tell the user** the host, path, old and new URL, and the rollback command.
|
|
179
|
+
3. Switch:
|
|
180
|
+
```powershell
|
|
181
|
+
& $s/Switch-CheckoutOrigin.ps1 -Path D:\apps\app [-ComputerName appserver01] -Mode Switch `
|
|
182
|
+
-NewUrl https://github.com/<owner>/<repo>.git -VerifyWithGhToken
|
|
183
|
+
```
|
|
184
|
+
It records the old URL first, keeps it as a second remote, fetches, and requires the
|
|
185
|
+
checkout's HEAD to be on a GitHub branch. On failure it restores the old origin itself.
|
|
186
|
+
`-VerifyWithGhToken` lends your gh token to that one fetch, in memory only, because a deploy
|
|
187
|
+
host normally has no GitHub credential of its own (CI supplies one per run).
|
|
188
|
+
4. **Stop the old host from deploying** (disable its deploy job) only AFTER the first GitHub
|
|
189
|
+
deploy is green. Two schedulers on one checkout have no lock between them.
|
|
190
|
+
5. **Verify the deploy by an artefact changing** - a built bundle's hash or file time - never by
|
|
191
|
+
an HTTP 200 or a version endpoint alone. Both pass when a new backend serves an old frontend.
|
|
192
|
+
|
|
193
|
+
Rollback, at any point before phase 6:
|
|
194
|
+
|
|
195
|
+
```powershell
|
|
196
|
+
& $s/Switch-CheckoutOrigin.ps1 -Path D:\apps\app [-ComputerName appserver01] -Mode Rollback
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Also repoint developer checkouts the same way (`-Mode Switch` without `-VerifyWithGhToken`), so
|
|
200
|
+
`git push` goes to GitHub. Keep the old remote; it is the local rollback.
|
|
201
|
+
|
|
202
|
+
## Phase 6 - retire the old host (ask first)
|
|
203
|
+
|
|
204
|
+
Read `references/old-host.md`. The short version:
|
|
205
|
+
|
|
206
|
+
- Prove nothing is lost first:
|
|
207
|
+
`& $s/Compare-PortRefs.ps1 -Left <old-url> -Right <github-url> -Mode LeftInRight` must exit 0.
|
|
208
|
+
Push any ref that exists only on the old host before you continue.
|
|
209
|
+
- Prefer a **read-only pull mirror** of GitHub on the old host (Actions off) over deleting it:
|
|
210
|
+
anything still pinned to the old URL keeps working. Never a push mirror from CI.
|
|
211
|
+
- Anything that still pins a dependency from the old host keeps the old host alive. Find those
|
|
212
|
+
pins before retiring it.
|
|
213
|
+
|
|
214
|
+
## Report
|
|
215
|
+
|
|
216
|
+
End with a short table per app: source kind, branches and tags ported (SHA-verified yes/no),
|
|
217
|
+
warnings accepted and by whom, CI state, cutover state and rollback command, what is left.
|
|
218
|
+
Say which phases were skipped and why.
|
|
219
|
+
|
|
220
|
+
## Rules that apply to every phase
|
|
221
|
+
|
|
222
|
+
- Stage files BY NAME. The only `git add -A` is inside the scripts, on fresh throwaway trees.
|
|
223
|
+
- A check that cannot fail is not a check. Every script here exits non-zero on the failure it
|
|
224
|
+
exists for; do not pipe it to `Out-Null` or read only its last line.
|
|
225
|
+
- Audit the default branch on the SERVER (`origin/main`), never a working tree that may sit on
|
|
226
|
+
an old feature branch.
|
|
227
|
+
- Code that runs on a remote box runs under Windows PowerShell 5.1. There, with
|
|
228
|
+
`$ErrorActionPreference='Stop'`, a native command's stderr is a terminating error even with
|
|
229
|
+
`2>$null`. Use `'Continue'` and judge native commands by `$LASTEXITCODE`.
|
|
230
|
+
- One variable at a time. When a whole batch fails the same way, change one thing and retry
|
|
231
|
+
one item before re-reading the request.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# CI and credentials after a port
|
|
2
|
+
|
|
3
|
+
Every item here broke a real deploy at least once.
|
|
4
|
+
|
|
5
|
+
## The two kinds of credential
|
|
6
|
+
|
|
7
|
+
| Faces | Examples | On GitHub |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| ORIGIN - this repo | `actions/checkout`, a deploy script's `git fetch`/`reset`, pushing a version tag back | `${{ github.token }}`, username `x-access-token`. Needs `permissions: contents: write` to push. Nothing to store or rotate. |
|
|
10
|
+
| DEPENDENCY - another host or repo | a library pinned `git+https://host/org/lib.git@v1`, a private npm/PyPI registry, a Git server that stays behind | A secret that can read THAT host. `github.token` cannot read a different repo, even in the same org. |
|
|
11
|
+
|
|
12
|
+
Choose the credential **per host**, from the host named in the pin, never from where the app is
|
|
13
|
+
hosted. When a pin moves from the old server to GitHub, its credential must move with it in the
|
|
14
|
+
same commit; when an app moves but its pins do not, the old credential must stay.
|
|
15
|
+
|
|
16
|
+
A deploy script that builds its auth header from `git remote get-url origin` needs NO change
|
|
17
|
+
for the origin half: repointing origin (phase 5) is the whole cutover. It DOES need a change if
|
|
18
|
+
it also uses that origin-derived credential for dependency clones - the moment origin is
|
|
19
|
+
GitHub and the dependency is not, that pairing is wrong.
|
|
20
|
+
|
|
21
|
+
## Injecting a credential for a child process (pip, npm)
|
|
22
|
+
|
|
23
|
+
`GIT_CONFIG_*` environment entries (git 2.31+) are inherited by every git a tool spawns:
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
env:
|
|
27
|
+
GIT_CONFIG_COUNT: '2'
|
|
28
|
+
GIT_CONFIG_KEY_0: http.https://github.com/.extraheader
|
|
29
|
+
GIT_CONFIG_VALUE_0: AUTHORIZATION: basic <base64 of x-access-token:TOKEN>
|
|
30
|
+
GIT_CONFIG_KEY_1: http.https://old-host.example/.extraheader
|
|
31
|
+
GIT_CONFIG_VALUE_1: AUTHORIZATION: basic <base64 of user:OLD_HOST_TOKEN>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Add a new entry and increment the count. Never replace entry 0 when another host still needs
|
|
35
|
+
it. Build the base64 in a script step from `env:`; never put a token in a URL - it lands in
|
|
36
|
+
lockfiles and logs.
|
|
37
|
+
|
|
38
|
+
## Traps
|
|
39
|
+
|
|
40
|
+
- **Header accumulation.** `http.<url>.extraheader` never overrides; git sends every matching
|
|
41
|
+
one. `actions/checkout` persists one in `.git/config`. A second for the same host -> HTTP 400
|
|
42
|
+
`Duplicate header: "Authorization"`. Unset the checkout's entry for the one command that
|
|
43
|
+
needs a different credential, then restore it; otherwise every later step loses its auth.
|
|
44
|
+
- **`git config --local` outside a repo fails.** A runner's temp working directory is not a
|
|
45
|
+
repository. Check `git rev-parse --is-inside-work-tree` first.
|
|
46
|
+
- **npm uses SSH for GitHub deps** despite `https` in `package.json` and the lockfile. Add
|
|
47
|
+
`url.https://github.com/.insteadOf ssh://git@github.com/`. Also make sure the lockfile's
|
|
48
|
+
`resolved` URLs say `git+https`, not `git+ssh`.
|
|
49
|
+
- **A cache hit is not proof of credentials.** A warm npm or pip cache skips the authenticated
|
|
50
|
+
fetch entirely. Prove a new credential with a fresh install (cleared cache, empty `HOME`, no
|
|
51
|
+
credential helper).
|
|
52
|
+
- **Windows PowerShell 5.1 runners.** With `$ErrorActionPreference='Stop'`, anything a native
|
|
53
|
+
exe writes to stderr becomes a terminating error, and `2>$null` does not prevent it. Use
|
|
54
|
+
`'Continue'` in the step, `2>&1 | Out-Null` to silence, and judge by `$LASTEXITCODE`.
|
|
55
|
+
- **A step whose last native command is EXPECTED to fail** (a negative test) must end with
|
|
56
|
+
`exit 0`, or the step is red after printing its pass.
|
|
57
|
+
- **Required checks.** A job skipped by a path filter at the TRIGGER never reports, so making it
|
|
58
|
+
a required check blocks every PR that does not touch those paths. A job skipped by `if:` counts
|
|
59
|
+
as passed. Renaming a job renames its check context and silently breaks a branch rule.
|
|
60
|
+
- **`concurrency: cancel-in-progress`** cancels a PR's running jobs when you push again. A
|
|
61
|
+
`cancelled` job or a PR with no runs is often this, not a fault. Re-check against the new
|
|
62
|
+
head SHA.
|
|
63
|
+
- **Deploy scripts may run from the deploy checkout**, not from the commit being deployed. If the
|
|
64
|
+
workflow syncs that checkout to the target commit before calling the script, a script change
|
|
65
|
+
takes effect on the first deploy; if it does not, it takes effect on the NEXT deploy. Check
|
|
66
|
+
which before predicting.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Copy to .caddis/port-to-github.json in the repo you run the port from. Every field is optional.",
|
|
3
|
+
"owner": "example-org",
|
|
4
|
+
"visibility": "private",
|
|
5
|
+
"stagingRoot": "%TEMP%\\port-to-github",
|
|
6
|
+
"oldRemoteName": "gitea",
|
|
7
|
+
"dependencyHosts": [
|
|
8
|
+
{ "host": "github.com", "username": "x-access-token", "secret": "ORG_READ_TOKEN", "note": "libraries in other repos of the org" },
|
|
9
|
+
{ "host": "git.example.internal", "username": "ci-bot", "secret": "OLD_HOST_READ_TOKEN", "note": "stays until nothing pins from it" }
|
|
10
|
+
],
|
|
11
|
+
"deployHosts": [
|
|
12
|
+
{ "computerName": "appserver01", "checkoutRoot": "D:\\apps" }
|
|
13
|
+
],
|
|
14
|
+
"projectNotes": [
|
|
15
|
+
"AGENTS.md - the project's own rules; they override this skill",
|
|
16
|
+
"docs/runbooks/<your-migration-runbook>.md"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Retiring the old host
|
|
2
|
+
|
|
3
|
+
Only after: the app is on GitHub, SHA-verified, and its first GitHub deploy was green and
|
|
4
|
+
verified by an artefact changing.
|
|
5
|
+
|
|
6
|
+
## 1. Prove nothing is lost
|
|
7
|
+
|
|
8
|
+
```powershell
|
|
9
|
+
& <skill>/scripts/Compare-PortRefs.ps1 -Left <old-url> -Right https://github.com/<owner>/<repo>.git `
|
|
10
|
+
-Mode LeftInRight -LeftLabel old -RightLabel github
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Exit 0 means every branch and tag on the old host exists on GitHub with the same SHA. If not,
|
|
14
|
+
push the missing refs to GitHub first (from a fresh `git clone --mirror` of the old host, with
|
|
15
|
+
an explicit refspec for just those refs) and run it again. Release tags pushed by the old CI
|
|
16
|
+
after the port are the usual stragglers.
|
|
17
|
+
|
|
18
|
+
## 2. Find what still depends on the old host
|
|
19
|
+
|
|
20
|
+
Search every consumer's default branch (not working trees) for the old host name: dependency
|
|
21
|
+
pins, registry config, CI steps, deploy scripts. Anything found keeps the old host alive, and
|
|
22
|
+
keeps a credential for it alive.
|
|
23
|
+
|
|
24
|
+
## 3. Choose: read-only mirror, archive, or delete
|
|
25
|
+
|
|
26
|
+
| Option | When | Cost |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| **Read-only pull mirror of GitHub** (Actions OFF) | Something still pins from the old URL, or you want a live fallback | The old host keeps running; a mirror that stops pulling goes stale silently |
|
|
29
|
+
| Archive (read-only, no sync) | Nothing depends on it; you want the old PR/issue history browsable | A frozen snapshot that drifts from day one |
|
|
30
|
+
| Delete | Nothing depends on it and its history is exported | Irreversible |
|
|
31
|
+
|
|
32
|
+
**Never a push mirror driven by CI.** A job pushing into a host that then runs CI on what
|
|
33
|
+
arrives has caused a full outage: tests failed, deploy was skipped (not failed), the push guard
|
|
34
|
+
only checked `failure`, and it pushed a change disabling the old deploy - leaving no deploy path
|
|
35
|
+
anywhere. A pull mirror with Actions disabled cannot do that.
|
|
36
|
+
|
|
37
|
+
## 4. Converting a Gitea repo into a pull mirror
|
|
38
|
+
|
|
39
|
+
An existing Gitea repo cannot be converted in place. It must be deleted and recreated as a
|
|
40
|
+
mirror under the same path, which **destroys its pull requests and issues** - export them first
|
|
41
|
+
(API: `/repos/{owner}/{repo}/pulls?state=all`, `/issues?state=all`, comments per item).
|
|
42
|
+
|
|
43
|
+
1. Run step 1 again immediately before, and export PRs/issues.
|
|
44
|
+
2. `DELETE /api/v1/repos/{owner}/{repo}`.
|
|
45
|
+
3. **Verify the delete on BOTH halves: the API returns 404 AND the repository directory is gone
|
|
46
|
+
on disk.** Gitea can remove the database row and fail on disk (a locked pack file). The next
|
|
47
|
+
create then returns 500 while still making an EMPTY mirror row, on which `mirror-sync`
|
|
48
|
+
returns 400. The Gitea log names the locked file; nothing else does.
|
|
49
|
+
4. `POST /api/v1/repos/migrate` with `mirror: true`, the GitHub clone URL, and a GitHub read
|
|
50
|
+
token (`auth_token`). Then disable Actions on it (`has_actions: false`).
|
|
51
|
+
5. Verify by refs, not by existence: `Compare-PortRefs.ps1 -Left <github> -Right <gitea> -Mode Equal`.
|
|
52
|
+
6. If something pins from the mirror, prove it: clone the exact pinned tag from the mirror.
|
|
53
|
+
|
|
54
|
+
## 5. Watching a mirror
|
|
55
|
+
|
|
56
|
+
A pull mirror syncs on an interval (Gitea default 8h). A mirror behind GitHub is either LAGGING
|
|
57
|
+
(the GitHub commit is newer than the mirror's last pull - normal) or STALE (the commit was there
|
|
58
|
+
at the last pull and still did not arrive - the stored credential probably expired). Decide
|
|
59
|
+
which by comparing the GitHub commit time with the mirror's last-update time before you touch
|
|
60
|
+
any credential. `POST /repos/{owner}/{repo}/mirror-sync` forces a pull: an immediate match means
|
|
61
|
+
it was lag.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Compare branches and tags between two git locations by SHA. The only honest "did it copy" check.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
-Left and -Right can each be a URL, a local path, a UNC path or a bare repository.
|
|
7
|
+
Uses git ls-remote, so nothing is fetched or changed.
|
|
8
|
+
|
|
9
|
+
Modes:
|
|
10
|
+
Equal every head and tag matches on both sides (after a push)
|
|
11
|
+
LeftInRight every Left ref exists in Right with the same SHA; Right may have more
|
|
12
|
+
(before destroying an old host: nothing on the old host may be lost)
|
|
13
|
+
|
|
14
|
+
Exit 0 when the mode holds, 1 when it does not, and the differing refs are printed.
|
|
15
|
+
#>
|
|
16
|
+
[CmdletBinding()]
|
|
17
|
+
param(
|
|
18
|
+
[Parameter(Mandatory)][string]$Left,
|
|
19
|
+
[Parameter(Mandatory)][string]$Right,
|
|
20
|
+
[ValidateSet('Equal', 'LeftInRight')][string]$Mode = 'Equal',
|
|
21
|
+
[string]$LeftLabel = 'left',
|
|
22
|
+
[string]$RightLabel = 'right',
|
|
23
|
+
[switch]$PassThru
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
$ErrorActionPreference = 'Stop'
|
|
27
|
+
. (Join-Path $PSScriptRoot 'PortCommon.ps1')
|
|
28
|
+
|
|
29
|
+
function Get-Refs([string]$Where) {
|
|
30
|
+
$lines = Invoke-PortGit @('ls-remote', '--heads', '--tags', $Where)
|
|
31
|
+
$map = @{}
|
|
32
|
+
foreach ($l in $lines) {
|
|
33
|
+
if (-not $l) { continue }
|
|
34
|
+
$sha, $ref = $l -split "`t", 2
|
|
35
|
+
# An annotated tag shows twice: the tag object and the peeled commit (^{}). Compare the
|
|
36
|
+
# tag object; the peeled line is derived from it.
|
|
37
|
+
if ($ref -like '*^{}') { continue }
|
|
38
|
+
$map[$ref] = $sha
|
|
39
|
+
}
|
|
40
|
+
return $map
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
$l = Get-Refs $Left
|
|
44
|
+
$r = Get-Refs $Right
|
|
45
|
+
|
|
46
|
+
$onlyLeft = @($l.Keys | Where-Object { -not $r.ContainsKey($_) } | Sort-Object)
|
|
47
|
+
$onlyRight = @($r.Keys | Where-Object { -not $l.ContainsKey($_) } | Sort-Object)
|
|
48
|
+
$differ = @($l.Keys | Where-Object { $r.ContainsKey($_) -and $r[$_] -ne $l[$_] } | Sort-Object)
|
|
49
|
+
|
|
50
|
+
$ok = ($onlyLeft.Count -eq 0 -and $differ.Count -eq 0)
|
|
51
|
+
if ($Mode -eq 'Equal') { $ok = $ok -and ($onlyRight.Count -eq 0) }
|
|
52
|
+
|
|
53
|
+
Write-Host ("{0}: {1} refs {2}: {3} refs only-{0}: {4} only-{2}: {5} differ: {6}" -f $LeftLabel, $l.Count, $RightLabel, $r.Count, $onlyLeft.Count, $onlyRight.Count, $differ.Count)
|
|
54
|
+
foreach ($x in $onlyLeft) { Write-Host " only in ${LeftLabel}: $x" }
|
|
55
|
+
if ($Mode -eq 'Equal') { foreach ($x in $onlyRight) { Write-Host " only in ${RightLabel}: $x" } }
|
|
56
|
+
foreach ($x in $differ) { Write-Host " differs: $x $($l[$x].Substring(0,12)) vs $($r[$x].Substring(0,12))" }
|
|
57
|
+
|
|
58
|
+
if ($l.Count -eq 0) {
|
|
59
|
+
Write-PortFail "$LeftLabel has no refs at all. An empty side cannot prove anything."
|
|
60
|
+
$ok = $false
|
|
61
|
+
}
|
|
62
|
+
if ($ok) { Write-PortOk "Refs agree ($Mode)." } else { Write-PortFail "Refs do NOT agree ($Mode)." }
|
|
63
|
+
|
|
64
|
+
if ($PassThru) {
|
|
65
|
+
[pscustomobject]@{ Ok = $ok; OnlyLeft = $onlyLeft; OnlyRight = $onlyRight; Differ = $differ; LeftCount = $l.Count; RightCount = $r.Count }
|
|
66
|
+
}
|
|
67
|
+
if (-not $ok) { exit 1 }
|
|
68
|
+
exit 0
|