@rashidee/co2 1.3.15 → 1.3.17
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/dist/.co2-dat/app.db-shm +0 -0
- package/dist/.co2-dat/app.db-wal +0 -0
- package/dist/index.js +52 -1
- package/drizzle/0020_cicd_guard_token.sql +4 -0
- package/drizzle/meta/_journal.json +8 -1
- package/package.json +1 -1
- package/plugin/skills/util-gencicdscript/SKILL.md +56 -23
- package/plugin/skills/util-plancicd/SKILL.md +312 -310
package/dist/.co2-dat/app.db-shm
CHANGED
|
Binary file
|
package/dist/.co2-dat/app.db-wal
CHANGED
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -6809,7 +6809,7 @@ var {
|
|
|
6809
6809
|
// package.json
|
|
6810
6810
|
var package_default = {
|
|
6811
6811
|
name: "@rashidee/co2",
|
|
6812
|
-
version: "1.3.
|
|
6812
|
+
version: "1.3.17",
|
|
6813
6813
|
description: "Compound Context Studio \u2014 self-hosted team context authoring for CO2 projects",
|
|
6814
6814
|
type: "module",
|
|
6815
6815
|
license: "MIT",
|
|
@@ -14735,6 +14735,10 @@ var cicdSetups = sqliteTable(
|
|
|
14735
14735
|
// project_configs.updatedAt at generation
|
|
14736
14736
|
appPort: integer("app_port"),
|
|
14737
14737
|
// last spawned port of the generated app
|
|
14738
|
+
// Guard token issued to the last spawned app (also kept in memory). Persisted so an app
|
|
14739
|
+
// that outlives a studio restart still passes /api/cicd-guard — otherwise its develop
|
|
14740
|
+
// gate reads "studio unreachable" and every control stays disabled until a manual bounce.
|
|
14741
|
+
guardToken: text("guard_token"),
|
|
14738
14742
|
createdBy: integer("created_by").notNull(),
|
|
14739
14743
|
// soft ref → users.id
|
|
14740
14744
|
createdAt: text("created_at").notNull(),
|
|
@@ -22015,6 +22019,46 @@ function stopCicdApp(projectId) {
|
|
|
22015
22019
|
}
|
|
22016
22020
|
|
|
22017
22021
|
// ../server/src/features/cicd/app-proxy.ts
|
|
22022
|
+
var PROXY_BASE_SHIM = `<script data-co2-proxy-base-shim>(function () {
|
|
22023
|
+
var m = location.pathname.match(/^(.*?\\/app-proxy)(?:\\/|$)/)
|
|
22024
|
+
if (!m) return
|
|
22025
|
+
var prefix = m[1]
|
|
22026
|
+
function reroot(p) {
|
|
22027
|
+
return p.charAt(0) === '/' && p.charAt(1) !== '/' && p.indexOf(prefix + '/') !== 0 && p !== prefix ? prefix + p : p
|
|
22028
|
+
}
|
|
22029
|
+
var origFetch = window.fetch.bind(window)
|
|
22030
|
+
window.fetch = function (input, init) {
|
|
22031
|
+
if (typeof input === 'string') input = reroot(input)
|
|
22032
|
+
else if (input instanceof URL && input.host === location.host)
|
|
22033
|
+
input = new URL(reroot(input.pathname) + input.search + input.hash, location.origin)
|
|
22034
|
+
return origFetch(input, init)
|
|
22035
|
+
}
|
|
22036
|
+
var OrigWS = window.WebSocket
|
|
22037
|
+
function PatchedWS(url, protocols) {
|
|
22038
|
+
try {
|
|
22039
|
+
var u = new URL(url, location.href)
|
|
22040
|
+
if (u.host === location.host) {
|
|
22041
|
+
u.pathname = reroot(u.pathname)
|
|
22042
|
+
u.protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
22043
|
+
url = u.toString()
|
|
22044
|
+
}
|
|
22045
|
+
} catch (e) { /* malformed URL \u2014 let the real constructor throw */ }
|
|
22046
|
+
return protocols === undefined ? new OrigWS(url) : new OrigWS(url, protocols)
|
|
22047
|
+
}
|
|
22048
|
+
PatchedWS.prototype = OrigWS.prototype
|
|
22049
|
+
PatchedWS.CONNECTING = OrigWS.CONNECTING
|
|
22050
|
+
PatchedWS.OPEN = OrigWS.OPEN
|
|
22051
|
+
PatchedWS.CLOSING = OrigWS.CLOSING
|
|
22052
|
+
PatchedWS.CLOSED = OrigWS.CLOSED
|
|
22053
|
+
window.WebSocket = PatchedWS
|
|
22054
|
+
document.addEventListener('click', function (e) {
|
|
22055
|
+
var t = e.target
|
|
22056
|
+
var a = t && t.closest ? t.closest('a[href]') : null
|
|
22057
|
+
if (!a) return
|
|
22058
|
+
var href = a.getAttribute('href')
|
|
22059
|
+
if (href) a.setAttribute('href', reroot(href))
|
|
22060
|
+
}, true)
|
|
22061
|
+
})()</script>`;
|
|
22018
22062
|
async function proxyCicdAppRequest(db, workspacePath, projectId, prefix, subPath, req) {
|
|
22019
22063
|
const location = resolveCicdApp(db, workspacePath, projectId);
|
|
22020
22064
|
if (!location.present) throw svcError6("CICD_APP_MISSING", 404);
|
|
@@ -22047,6 +22091,13 @@ async function proxyCicdAppRequest(db, workspacePath, projectId, prefix, subPath
|
|
|
22047
22091
|
if (redirectTo?.startsWith("/") && redirectTo !== prefix && !redirectTo.startsWith(`${prefix}/`)) {
|
|
22048
22092
|
responseHeaders.set("location", `${prefix}${redirectTo}`);
|
|
22049
22093
|
}
|
|
22094
|
+
const contentType = upstream.headers.get("content-type") ?? "";
|
|
22095
|
+
if (contentType.includes("text/html")) {
|
|
22096
|
+
const html = await upstream.text();
|
|
22097
|
+
const headTag = html.match(/<head[^>]*>/i);
|
|
22098
|
+
const patched = headTag ? html.replace(headTag[0], `${headTag[0]}${PROXY_BASE_SHIM}`) : `${PROXY_BASE_SHIM}${html}`;
|
|
22099
|
+
return new Response(patched, { status: upstream.status, headers: responseHeaders });
|
|
22100
|
+
}
|
|
22050
22101
|
return new Response(upstream.body, { status: upstream.status, headers: responseHeaders });
|
|
22051
22102
|
}
|
|
22052
22103
|
var CICD_PROXY_UPGRADE_RE = /^\/api\/cicd\/(\d+)\/app-proxy(\/.*)$/;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
-- v2.3.1: Persist the generated CI/CD app's guard token. The token was in-memory only, so a
|
|
2
|
+
-- studio restart orphaned a still-running app: its spawn-env token no longer validated against
|
|
3
|
+
-- /api/cicd-guard, the develop gate read "studio unreachable" and every control stayed disabled.
|
|
4
|
+
ALTER TABLE `cicd_setups` ADD `guard_token` text;
|
|
@@ -141,6 +141,13 @@
|
|
|
141
141
|
"when": 1785000000000,
|
|
142
142
|
"tag": "0019_git_provider",
|
|
143
143
|
"breakpoints": true
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"idx": 20,
|
|
147
|
+
"version": "6",
|
|
148
|
+
"when": 1785100000000,
|
|
149
|
+
"tag": "0020_cicd_guard_token",
|
|
150
|
+
"breakpoints": true
|
|
144
151
|
}
|
|
145
152
|
]
|
|
146
|
-
}
|
|
153
|
+
}
|
package/package.json
CHANGED
|
@@ -21,12 +21,12 @@ description: >
|
|
|
21
21
|
copy, docker run or kubectl apply, or none). Controls are disabled for any application that
|
|
22
22
|
has never completed a conductor-develop workflow run (checked against the studio's
|
|
23
23
|
/api/cicd-guard endpoint using the spawn-env token). Health/port probing marks apps already
|
|
24
|
-
running — even ones started outside this app.
|
|
25
|
-
the
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
(sequential builds). When the approved plan declares an `## Infrastructure` list, the
|
|
24
|
+
running — even ones started outside this app. There is NO version selector — every control
|
|
25
|
+
operates on the current working tree, and the {VERSION} token in commands auto-resolves to
|
|
26
|
+
the project's current version (studio guard's latest semver, fallback `latest`) — docker
|
|
27
|
+
images are tagged <repo>/<app>:<version> when the plan's Docker section is active (managed
|
|
28
|
+
Dockerfiles materialized before Build when missing; Build never pushes) — and Build All /
|
|
29
|
+
Start All / Stop All buttons operate every eligible application at once (sequential builds). When the approved plan declares an `## Infrastructure` list, the
|
|
30
30
|
main page shows an "Infrastructure" link that opens a separate status-only page listing every
|
|
31
31
|
supporting 3rd-party service with a live TCP-reachability indicator and a console link. Writes cicd/CICD_GENERATION.md with `**Status**:` IN PROGRESS →
|
|
32
32
|
COMPLETED so the studio watcher detects completion. Idempotent — re-running regenerates
|
|
@@ -76,7 +76,8 @@ Read `references/cicd-app-template.md` for the complete file templates. Produce:
|
|
|
76
76
|
`## Dockerfiles` section, or `{ "path": "Dockerfile", "content": null }` for an `existing`
|
|
77
77
|
one (`null` entirely when the app has no image). Copy managed Dockerfile bodies verbatim.
|
|
78
78
|
Commands may carry the literal `{VERSION}` token — copy it verbatim; the generated app
|
|
79
|
-
substitutes it at execution
|
|
79
|
+
substitutes it at execution with the project's CURRENT version (studio guard's latest
|
|
80
|
+
semver, `v` prefix stripped, fallback `latest`) — never via any user-facing selector. `deployMethod` is the plan's Deploy Method column
|
|
80
81
|
(`none|web-server|docker|kubernetes`) and `deploy` its command (`null` when the method is
|
|
81
82
|
`none`). The `infra` array comes from the plan's `## Infrastructure` table (one entry per
|
|
82
83
|
row); `url` is `null` when the Console URL cell is `-`. Emit `"infra": []` when the plan's
|
|
@@ -111,18 +112,49 @@ port, application count) and STOP — the studio starts/embeds the app.
|
|
|
111
112
|
once at boot). Never cache the parsed config in a top-level `const`.
|
|
112
113
|
- The page is bare: no header, sidebar or footer (it is iframed by the studio). Light theme,
|
|
113
114
|
one nav tab per application; the tab body is that application's live log.
|
|
114
|
-
- **Path-prefix proxy awareness (MANDATORY
|
|
115
|
-
same-origin reverse proxy at `/api/cicd/<projectId>/app-proxy/` —
|
|
116
|
-
(`fetch('/api/state')`, `href="/infra"`, `ws://host/ws`) escape the
|
|
117
|
-
instead of this app and render an
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
115
|
+
- **Path-prefix proxy awareness (MANDATORY — copy the template below verbatim)**: the studio
|
|
116
|
+
embeds the page through its same-origin reverse proxy at `/api/cicd/<projectId>/app-proxy/` —
|
|
117
|
+
root-absolute client URLs (`fetch('/api/state')`, `href="/infra"`, `ws://host/ws`) escape the
|
|
118
|
+
prefix, hit the studio instead of this app and render an EMPTY BLACK PAGE in the studio.
|
|
119
|
+
Every generated page's client `<script>` MUST begin with this exact bootstrap block:
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
// Proxy-aware base path — the studio serves this page at /api/cicd/<id>/app-proxy/ (main)
|
|
123
|
+
// and .../app-proxy/infra; direct access serves / and /infra. All client URLs derive from it.
|
|
124
|
+
// Regex-free ON PURPOSE — see the template-literal escaping rule below.
|
|
125
|
+
var _p = location.pathname
|
|
126
|
+
if (_p.endsWith('/infra')) _p = _p.slice(0, -6)
|
|
127
|
+
if (_p.endsWith('/')) _p = _p.slice(0, -1)
|
|
128
|
+
var BASE = _p
|
|
129
|
+
function apiUrl(path) { return BASE + path } // apiUrl('/api/state')
|
|
130
|
+
function wsUrl(path) { // wsUrl('/ws?app=x')
|
|
131
|
+
return (location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host + BASE + path
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
and then NEVER write a root-absolute URL anywhere in client code:
|
|
136
|
+
- every fetch goes through `apiUrl(...)` — `fetch(apiUrl('/api/state'))`, never `fetch('/api/state')`;
|
|
137
|
+
- the log stream socket is `new WebSocket(wsUrl('/ws?app='+encodeURIComponent(name)))` —
|
|
138
|
+
never a hardcoded `ws://` (mixed-content-blocked when the studio is served over HTTPS);
|
|
139
|
+
- the Infrastructure link is set from script (`infralink.href = BASE + '/infra'`), never a
|
|
140
|
+
literal `href="/infra"`.
|
|
124
141
|
Server-side routes stay unprefixed (`/api/state`, `/ws`, `/infra`) — the studio proxy strips
|
|
125
142
|
the prefix before forwarding.
|
|
143
|
+
- **Template-literal escaping in client code (MANDATORY)**: the page HTML/JS is emitted from a
|
|
144
|
+
template literal inside `server.js`, where every backslash is consumed once when the template
|
|
145
|
+
cooks — a client-side regex like `.replace(/\/$/, '')` reaches the browser as
|
|
146
|
+
`.replace(//$/, '')`, the `//` starts a comment, and the WHOLE client script dies with a
|
|
147
|
+
SyntaxError (blank page both direct AND embedded; this exact regression has shipped before).
|
|
148
|
+
Client code inside the template must therefore contain NO backslashes at all: use string
|
|
149
|
+
methods (`endsWith`, `slice`, `split`, `indexOf`) instead of regex literals, and avoid `\n`
|
|
150
|
+
in client strings (concatenate with real newlines or use `String.fromCharCode(10)`).
|
|
151
|
+
- **Syntax self-check gate (MANDATORY before flipping the generation COMPLETED)**:
|
|
152
|
+
1. `node --check server.js` (validates the server code);
|
|
153
|
+
2. start the server briefly, fetch `/` and `/infra`, extract every `<script>…</script>` block
|
|
154
|
+
and run `new Function(script)` on each — any SyntaxError means the page renders blank;
|
|
155
|
+
3. grep the client sections — any remaining `fetch('/`, `href="/`, `'ws://'` or backslash
|
|
156
|
+
inside the page template is a defect; fix via `apiUrl`/`wsUrl`/`BASE` and the escaping
|
|
157
|
+
rule first. Only then flip `CICD_GENERATION.md` to COMPLETED.
|
|
126
158
|
- Infrastructure page: when `infra` is non-empty the main page shows an "Infrastructure" link
|
|
127
159
|
that opens the separate `/infra` page in a new tab — a status-only list of supporting
|
|
128
160
|
3rd-party services, each with a live TCP-reachability dot (polled) and a console link when a
|
|
@@ -132,12 +164,13 @@ port, application count) and STOP — the studio starts/embeds the app.
|
|
|
132
164
|
(`<project>/<folder>`) as working directory — command paths are relative to the application
|
|
133
165
|
folder (e.g. `java -jar target\app.jar`), never to the project root. Deploy renders only
|
|
134
166
|
when the plan assigned a deploy method other than `none`.
|
|
135
|
-
- **
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
167
|
+
- **NO version selector — current codebase only**: the page carries NO version dropdown or
|
|
168
|
+
any other version choice. Build/Start/Stop/Deploy (and the All buttons) always operate on
|
|
169
|
+
the CURRENT working tree — there is nothing else they could build. The literal `{VERSION}`
|
|
170
|
+
token in commands is resolved AUTOMATICALLY at execution time to the project's current
|
|
171
|
+
version: the FIRST entry of the studio guard response's `versions` array (already polled
|
|
172
|
+
every 30 s; latest first), minus its `v` prefix, falling back to `latest` when the studio
|
|
173
|
+
is unreachable. Never render a UI control for it and never check out code.
|
|
141
174
|
- **Dockerfile materialization**: before Build, when an app's `dockerfile.content` is set and
|
|
142
175
|
no file exists at its path in the application folder, the server writes it (never
|
|
143
176
|
overwriting an existing Dockerfile — mirror of the managed-config rule). Build never
|
|
@@ -1,310 +1,312 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: util-plancicd
|
|
3
|
-
model: claude-opus-4-8
|
|
4
|
-
effort: high
|
|
5
|
-
description: >
|
|
6
|
-
Infer a local-deployment plan for every custom application of a CO2 project and write it to
|
|
7
|
-
cicd/CICD_PLAN.md for human review in Compound Context Studio. Reads the project's
|
|
8
|
-
ENVIRONMENT.md and DEVTOOL.md, CLAUDE.md (# Custom Applications, # Port Allocation) and each
|
|
9
|
-
application's context/specification/SPECIFICATION.md, then decides per application: deployment
|
|
10
|
-
method (container vs bare process), build command (e.g. Spring Boot jar vs Docker image),
|
|
11
|
-
start command, stop mechanism, deploy method + command (none / web-server copy / docker run /
|
|
12
|
-
kubectl apply — only when the environment calls for one), port and health-check URL, plus a
|
|
13
|
-
port for the generated CI/CD app itself. When Docker AND a docker repo/registry are both
|
|
14
|
-
configured (DEVTOOL.md + ENVIRONMENT.md), it additionally plans the docker-image build path:
|
|
15
|
-
per-application image names under the repo, Build commands extended with
|
|
16
|
-
`docker build -t <image>:{VERSION}` (the {VERSION} token is resolved by the generated
|
|
17
|
-
app
|
|
18
|
-
`## Dockerfiles` section for any dockerized application lacking one (materialized before
|
|
19
|
-
Build, never overwriting an existing file). It also infers each application's RUNTIME CONFIG — the
|
|
20
|
-
config files its start command reads (e.g. `.env`), deciding per file whether the values come
|
|
21
|
-
from ENVIRONMENT.md or from an existing `.env` / `.env-*` file already in the app folder, listing
|
|
22
|
-
the required keys and, for ENVIRONMENT.md-sourced files, the resolved content — so the generated
|
|
23
|
-
CI/CD app can complete the config before start (guaranteeing the app comes up) and show it in a
|
|
24
|
-
per-application Config modal for troubleshooting. It also enumerates the project's supporting
|
|
25
|
-
infrastructure (3rd-party services from CLAUDE.md's `# Supporting 3rd Party Applications` and
|
|
26
|
-
ENVIRONMENT.md — host, port and optional console URL) into an `## Infrastructure` section so
|
|
27
|
-
the generated CI/CD app can surface a status-only Infrastructure page. The plan carries a
|
|
28
|
-
top-level
|
|
29
|
-
`**Status**: IN PROGRESS` header flipped to `COMPLETED` when done — the studio's watcher reads
|
|
30
|
-
it. This skill NEVER generates the CI/CD app (that is util-gencicdscript, which runs only
|
|
31
|
-
after the studio stamps an **Approved**: line into the plan). Trigger on keywords: "plan
|
|
32
|
-
cicd", "plan deployment", "cicd plan", "infer deployment", "deployment plan", "plan ci/cd".
|
|
33
|
-
Accepts one MANDATORY argument: the project-relative path of the plan-request markdown the
|
|
34
|
-
studio generated (e.g., /util-plancicd execution/cicd-plan-3-1753000000000.md).
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
# Util Plan CI/CD
|
|
38
|
-
|
|
39
|
-
## Inputs
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
/util-plancicd <path-to-plan-request-md>
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
| Argument | Required | Description |
|
|
46
|
-
|---|---|---|
|
|
47
|
-
| path | yes | Project-relative path of the studio-generated plan-request markdown under `execution/` |
|
|
48
|
-
|
|
49
|
-
If the argument is missing or the file does not exist, STOP and tell the user to click
|
|
50
|
-
**Plan deployment** in the studio's CI/CD screen first.
|
|
51
|
-
|
|
52
|
-
### Auto-Resolved Paths
|
|
53
|
-
|
|
54
|
-
| Logical file | Path |
|
|
55
|
-
|---|---|
|
|
56
|
-
| Plan request | `<argument>` |
|
|
57
|
-
| Environment | `ENVIRONMENT.md` (project root) |
|
|
58
|
-
| Dev tools | `DEVTOOL.md` (project root) |
|
|
59
|
-
| Project context | `CLAUDE.md` (project root; `source/CLAUDE.md` fallback) |
|
|
60
|
-
| App specification | `<app_folder>/context/specification/SPECIFICATION.md` |
|
|
61
|
-
| App config files | `<app_folder>/.env`, `<app_folder>/.env-*` (and stack-conventional config files) |
|
|
62
|
-
| Infrastructure | `CLAUDE.md` `# Supporting 3rd Party Applications` + `ENVIRONMENT.md` |
|
|
63
|
-
| Output plan | `cicd/CICD_PLAN.md` |
|
|
64
|
-
|
|
65
|
-
## Phase 1 — Gather
|
|
66
|
-
|
|
67
|
-
1. Read the plan-request markdown; its `## Applications` list is the authoritative application set.
|
|
68
|
-
2. Read `ENVIRONMENT.md` (registries, credentials, external services) and `DEVTOOL.md`
|
|
69
|
-
(installed tools + paths: node/pnpm/docker/kubectl/python versions).
|
|
70
|
-
2a. **Docker facts**: record whether Docker is installed (DEVTOOL.md) AND whether
|
|
71
|
-
ENVIRONMENT.md names a **docker repo/registry** — a registry URL or namespace the images
|
|
72
|
-
are tagged for (e.g. `registry.example.com/team`, a Docker Hub org, an ECR/GCR path).
|
|
73
|
-
The docker-image build path (below) activates ONLY when BOTH are present; Docker without
|
|
74
|
-
a configured repo keeps today's behavior (no image builds).
|
|
75
|
-
3. Read `CLAUDE.md`: `# Custom Applications` (folder names, Depends on), `# Port Allocation`
|
|
76
|
-
(per-application ports), and `# Supporting 3rd Party Applications` (the infrastructure the
|
|
77
|
-
applications depend on — databases, caches, object storage, SSO, mail, message brokers, etc.).
|
|
78
|
-
4. For each application, read its SPECIFICATION.md tech-stack section if present; otherwise
|
|
79
|
-
infer the stack from build files (`package.json` → node, `pom.xml` → spring-boot,
|
|
80
|
-
`composer.json` → laravel).
|
|
81
|
-
4a. For each application, discover its RUNTIME CONFIG files: list any existing `.env` / `.env-*`
|
|
82
|
-
in the application folder AND the stack-conventional config the start command reads (node/
|
|
83
|
-
laravel → `.env`; Spring Boot → `application.properties` / `application-<profile>.yml` or a
|
|
84
|
-
`.env` when the app loads one). Read the content of each existing file (values may be reused).
|
|
85
|
-
Cross-reference ENVIRONMENT.md for the per-application config values (DB/cache/broker hosts and
|
|
86
|
-
ports, external-service URLs, credentials, secrets, the app's own PORT) and the Infrastructure
|
|
87
|
-
list from step 5 for the hosts/ports the app must point at.
|
|
88
|
-
5. Build the **infrastructure list**: one entry per supporting 3rd-party service the project
|
|
89
|
-
declares. For each, resolve `host` (default `127.0.0.1`), `port` and — when it exposes a web
|
|
90
|
-
console/UI — a `url`, cross-referencing ENVIRONMENT.md for the actual host/port/endpoint. A
|
|
91
|
-
service with no host:port to probe (or a purely external SaaS with no local endpoint) is
|
|
92
|
-
omitted. If the project declares no supporting 3rd-party services, the infrastructure list is
|
|
93
|
-
empty and the plan's `## Infrastructure` section is written empty.
|
|
94
|
-
|
|
95
|
-
## Phase 2 — Decide (per application)
|
|
96
|
-
|
|
97
|
-
- **Working directory (CRITICAL)**: the generated CI/CD app executes every command with the
|
|
98
|
-
**application folder** (`<project>/<folder>`) as its working directory — NEVER the project
|
|
99
|
-
root. All paths inside build/start/stop/deploy commands must therefore be relative to the
|
|
100
|
-
application folder (`mvn -q package`, `java -jar target\app.jar`) — never prefixed with the
|
|
101
|
-
application folder itself (`skolafund_middleware\target\...` is WRONG).
|
|
102
|
-
- **Method**: `container` only when Docker appears in DEVTOOL.md AND the stack has a sensible
|
|
103
|
-
container path; otherwise `process` (bare local process — the default for a dev VM).
|
|
104
|
-
- **Build command**: from the stack (e.g. `pnpm install && pnpm build`, `mvn -q package`,
|
|
105
|
-
`composer install`). Use tool paths from DEVTOOL.md when the tool is not on PATH.
|
|
106
|
-
- **Start command**: the stack's production-ish local start (e.g. `node dist/index.js`,
|
|
107
|
-
`java -jar target/<artifact>.jar`, `php artisan serve --port <port>`); `docker build`/
|
|
108
|
-
`docker run` pair when method=container. For jar-based stacks, resolve the EXACT artifact
|
|
109
|
-
filename — read the pom/gradle build config: a Maven `<finalName>` means
|
|
110
|
-
`target/<finalName>.jar` with NO version suffix (Spring Boot repackage keeps that name and
|
|
111
|
-
leaves a `.jar.original` beside it — never reference the `.original`). Never guess
|
|
112
|
-
`<app>-<version>.jar` and never use a `target/*.jar` glob (shell-dependent, breaks on
|
|
113
|
-
Windows cmd).
|
|
114
|
-
- **Stop**: `process-kill` for bare processes; `docker stop <name>` for containers.
|
|
115
|
-
- **Deploy** (method + command): only when ENVIRONMENT.md/DEVTOOL.md show a deployment target —
|
|
116
|
-
`kubernetes` (`kubectl apply -f <app>/k8s/` when kubectl + a cluster/kubeconfig appear),
|
|
117
|
-
`docker` (`docker run`/`docker compose up -d` when the image is the deliverable),
|
|
118
|
-
`web-server` (copy the built artifact into the configured web/app server, e.g. nginx html
|
|
119
|
-
root or Tomcat webapps, when one is configured). Default **`none`** — a bare local process
|
|
120
|
-
needs no deploy step beyond Start; never invent a target the config does not name.
|
|
121
|
-
- **Docker image** (only when Gather 2a found Docker + a docker repo — otherwise skip this
|
|
122
|
-
bullet entirely):
|
|
123
|
-
- **Image name**: `<repo>/<app_folder>` — the application folder name, lowercased, with
|
|
124
|
-
any character docker rejects replaced by `-` (keep a leading `<number>_` prefix as-is;
|
|
125
|
-
e.g. `registry.example.com/team/skolafund_web`).
|
|
126
|
-
- **Build**: append the image build to the app build —
|
|
127
|
-
`<app build> && docker build -t <image>:{VERSION} .` The literal `{VERSION}` token is
|
|
128
|
-
the contract: the generated CI/CD app substitutes the
|
|
129
|
-
(semver without the `v` prefix; `latest` fallback) into commands at execution time
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
`.env
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
- **
|
|
188
|
-
- **
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
**
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
- **
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
>
|
|
220
|
-
> **
|
|
221
|
-
>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
>
|
|
225
|
-
>
|
|
226
|
-
>
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
COPY
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
-
|
|
310
|
-
|
|
1
|
+
---
|
|
2
|
+
name: util-plancicd
|
|
3
|
+
model: claude-opus-4-8
|
|
4
|
+
effort: high
|
|
5
|
+
description: >
|
|
6
|
+
Infer a local-deployment plan for every custom application of a CO2 project and write it to
|
|
7
|
+
cicd/CICD_PLAN.md for human review in Compound Context Studio. Reads the project's
|
|
8
|
+
ENVIRONMENT.md and DEVTOOL.md, CLAUDE.md (# Custom Applications, # Port Allocation) and each
|
|
9
|
+
application's context/specification/SPECIFICATION.md, then decides per application: deployment
|
|
10
|
+
method (container vs bare process), build command (e.g. Spring Boot jar vs Docker image),
|
|
11
|
+
start command, stop mechanism, deploy method + command (none / web-server copy / docker run /
|
|
12
|
+
kubectl apply — only when the environment calls for one), port and health-check URL, plus a
|
|
13
|
+
port for the generated CI/CD app itself. When Docker AND a docker repo/registry are both
|
|
14
|
+
configured (DEVTOOL.md + ENVIRONMENT.md), it additionally plans the docker-image build path:
|
|
15
|
+
per-application image names under the repo, Build commands extended with
|
|
16
|
+
`docker build -t <image>:{VERSION}` (the {VERSION} token is auto-resolved by the generated
|
|
17
|
+
CI/CD app to the project's current version), and a managed Dockerfile authored into the plan's
|
|
18
|
+
`## Dockerfiles` section for any dockerized application lacking one (materialized before
|
|
19
|
+
Build, never overwriting an existing file). It also infers each application's RUNTIME CONFIG — the
|
|
20
|
+
config files its start command reads (e.g. `.env`), deciding per file whether the values come
|
|
21
|
+
from ENVIRONMENT.md or from an existing `.env` / `.env-*` file already in the app folder, listing
|
|
22
|
+
the required keys and, for ENVIRONMENT.md-sourced files, the resolved content — so the generated
|
|
23
|
+
CI/CD app can complete the config before start (guaranteeing the app comes up) and show it in a
|
|
24
|
+
per-application Config modal for troubleshooting. It also enumerates the project's supporting
|
|
25
|
+
infrastructure (3rd-party services from CLAUDE.md's `# Supporting 3rd Party Applications` and
|
|
26
|
+
ENVIRONMENT.md — host, port and optional console URL) into an `## Infrastructure` section so
|
|
27
|
+
the generated CI/CD app can surface a status-only Infrastructure page. The plan carries a
|
|
28
|
+
top-level
|
|
29
|
+
`**Status**: IN PROGRESS` header flipped to `COMPLETED` when done — the studio's watcher reads
|
|
30
|
+
it. This skill NEVER generates the CI/CD app (that is util-gencicdscript, which runs only
|
|
31
|
+
after the studio stamps an **Approved**: line into the plan). Trigger on keywords: "plan
|
|
32
|
+
cicd", "plan deployment", "cicd plan", "infer deployment", "deployment plan", "plan ci/cd".
|
|
33
|
+
Accepts one MANDATORY argument: the project-relative path of the plan-request markdown the
|
|
34
|
+
studio generated (e.g., /util-plancicd execution/cicd-plan-3-1753000000000.md).
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
# Util Plan CI/CD
|
|
38
|
+
|
|
39
|
+
## Inputs
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
/util-plancicd <path-to-plan-request-md>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
| Argument | Required | Description |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| path | yes | Project-relative path of the studio-generated plan-request markdown under `execution/` |
|
|
48
|
+
|
|
49
|
+
If the argument is missing or the file does not exist, STOP and tell the user to click
|
|
50
|
+
**Plan deployment** in the studio's CI/CD screen first.
|
|
51
|
+
|
|
52
|
+
### Auto-Resolved Paths
|
|
53
|
+
|
|
54
|
+
| Logical file | Path |
|
|
55
|
+
|---|---|
|
|
56
|
+
| Plan request | `<argument>` |
|
|
57
|
+
| Environment | `ENVIRONMENT.md` (project root) |
|
|
58
|
+
| Dev tools | `DEVTOOL.md` (project root) |
|
|
59
|
+
| Project context | `CLAUDE.md` (project root; `source/CLAUDE.md` fallback) |
|
|
60
|
+
| App specification | `<app_folder>/context/specification/SPECIFICATION.md` |
|
|
61
|
+
| App config files | `<app_folder>/.env`, `<app_folder>/.env-*` (and stack-conventional config files) |
|
|
62
|
+
| Infrastructure | `CLAUDE.md` `# Supporting 3rd Party Applications` + `ENVIRONMENT.md` |
|
|
63
|
+
| Output plan | `cicd/CICD_PLAN.md` |
|
|
64
|
+
|
|
65
|
+
## Phase 1 — Gather
|
|
66
|
+
|
|
67
|
+
1. Read the plan-request markdown; its `## Applications` list is the authoritative application set.
|
|
68
|
+
2. Read `ENVIRONMENT.md` (registries, credentials, external services) and `DEVTOOL.md`
|
|
69
|
+
(installed tools + paths: node/pnpm/docker/kubectl/python versions).
|
|
70
|
+
2a. **Docker facts**: record whether Docker is installed (DEVTOOL.md) AND whether
|
|
71
|
+
ENVIRONMENT.md names a **docker repo/registry** — a registry URL or namespace the images
|
|
72
|
+
are tagged for (e.g. `registry.example.com/team`, a Docker Hub org, an ECR/GCR path).
|
|
73
|
+
The docker-image build path (below) activates ONLY when BOTH are present; Docker without
|
|
74
|
+
a configured repo keeps today's behavior (no image builds).
|
|
75
|
+
3. Read `CLAUDE.md`: `# Custom Applications` (folder names, Depends on), `# Port Allocation`
|
|
76
|
+
(per-application ports), and `# Supporting 3rd Party Applications` (the infrastructure the
|
|
77
|
+
applications depend on — databases, caches, object storage, SSO, mail, message brokers, etc.).
|
|
78
|
+
4. For each application, read its SPECIFICATION.md tech-stack section if present; otherwise
|
|
79
|
+
infer the stack from build files (`package.json` → node, `pom.xml` → spring-boot,
|
|
80
|
+
`composer.json` → laravel).
|
|
81
|
+
4a. For each application, discover its RUNTIME CONFIG files: list any existing `.env` / `.env-*`
|
|
82
|
+
in the application folder AND the stack-conventional config the start command reads (node/
|
|
83
|
+
laravel → `.env`; Spring Boot → `application.properties` / `application-<profile>.yml` or a
|
|
84
|
+
`.env` when the app loads one). Read the content of each existing file (values may be reused).
|
|
85
|
+
Cross-reference ENVIRONMENT.md for the per-application config values (DB/cache/broker hosts and
|
|
86
|
+
ports, external-service URLs, credentials, secrets, the app's own PORT) and the Infrastructure
|
|
87
|
+
list from step 5 for the hosts/ports the app must point at.
|
|
88
|
+
5. Build the **infrastructure list**: one entry per supporting 3rd-party service the project
|
|
89
|
+
declares. For each, resolve `host` (default `127.0.0.1`), `port` and — when it exposes a web
|
|
90
|
+
console/UI — a `url`, cross-referencing ENVIRONMENT.md for the actual host/port/endpoint. A
|
|
91
|
+
service with no host:port to probe (or a purely external SaaS with no local endpoint) is
|
|
92
|
+
omitted. If the project declares no supporting 3rd-party services, the infrastructure list is
|
|
93
|
+
empty and the plan's `## Infrastructure` section is written empty.
|
|
94
|
+
|
|
95
|
+
## Phase 2 — Decide (per application)
|
|
96
|
+
|
|
97
|
+
- **Working directory (CRITICAL)**: the generated CI/CD app executes every command with the
|
|
98
|
+
**application folder** (`<project>/<folder>`) as its working directory — NEVER the project
|
|
99
|
+
root. All paths inside build/start/stop/deploy commands must therefore be relative to the
|
|
100
|
+
application folder (`mvn -q package`, `java -jar target\app.jar`) — never prefixed with the
|
|
101
|
+
application folder itself (`skolafund_middleware\target\...` is WRONG).
|
|
102
|
+
- **Method**: `container` only when Docker appears in DEVTOOL.md AND the stack has a sensible
|
|
103
|
+
container path; otherwise `process` (bare local process — the default for a dev VM).
|
|
104
|
+
- **Build command**: from the stack (e.g. `pnpm install && pnpm build`, `mvn -q package`,
|
|
105
|
+
`composer install`). Use tool paths from DEVTOOL.md when the tool is not on PATH.
|
|
106
|
+
- **Start command**: the stack's production-ish local start (e.g. `node dist/index.js`,
|
|
107
|
+
`java -jar target/<artifact>.jar`, `php artisan serve --port <port>`); `docker build`/
|
|
108
|
+
`docker run` pair when method=container. For jar-based stacks, resolve the EXACT artifact
|
|
109
|
+
filename — read the pom/gradle build config: a Maven `<finalName>` means
|
|
110
|
+
`target/<finalName>.jar` with NO version suffix (Spring Boot repackage keeps that name and
|
|
111
|
+
leaves a `.jar.original` beside it — never reference the `.original`). Never guess
|
|
112
|
+
`<app>-<version>.jar` and never use a `target/*.jar` glob (shell-dependent, breaks on
|
|
113
|
+
Windows cmd).
|
|
114
|
+
- **Stop**: `process-kill` for bare processes; `docker stop <name>` for containers.
|
|
115
|
+
- **Deploy** (method + command): only when ENVIRONMENT.md/DEVTOOL.md show a deployment target —
|
|
116
|
+
`kubernetes` (`kubectl apply -f <app>/k8s/` when kubectl + a cluster/kubeconfig appear),
|
|
117
|
+
`docker` (`docker run`/`docker compose up -d` when the image is the deliverable),
|
|
118
|
+
`web-server` (copy the built artifact into the configured web/app server, e.g. nginx html
|
|
119
|
+
root or Tomcat webapps, when one is configured). Default **`none`** — a bare local process
|
|
120
|
+
needs no deploy step beyond Start; never invent a target the config does not name.
|
|
121
|
+
- **Docker image** (only when Gather 2a found Docker + a docker repo — otherwise skip this
|
|
122
|
+
bullet entirely):
|
|
123
|
+
- **Image name**: `<repo>/<app_folder>` — the application folder name, lowercased, with
|
|
124
|
+
any character docker rejects replaced by `-` (keep a leading `<number>_` prefix as-is;
|
|
125
|
+
e.g. `registry.example.com/team/skolafund_web`).
|
|
126
|
+
- **Build**: append the image build to the app build —
|
|
127
|
+
`<app build> && docker build -t <image>:{VERSION} .` The literal `{VERSION}` token is
|
|
128
|
+
the contract: the generated CI/CD app substitutes the project's CURRENT version
|
|
129
|
+
(semver without the `v` prefix; `latest` fallback) into commands at execution time —
|
|
130
|
+
there is no user-facing version selector; only the current codebase is ever built.
|
|
131
|
+
Build never pushes — pushing belongs to a `docker` Deploy method.
|
|
132
|
+
- **Container start/deploy**: when method=container, reference `<image>:{VERSION}` in the
|
|
133
|
+
start/deploy commands too, so the current version's tag is what runs.
|
|
134
|
+
- **Dockerfile**: if the application folder already has a `Dockerfile`, use it as-is
|
|
135
|
+
(`existing`). Otherwise author a stack-appropriate Dockerfile (node / spring-boot /
|
|
136
|
+
laravel — same stack detection as depgen-k8s) into the plan's `## Dockerfiles` section
|
|
137
|
+
(`managed`); the generated CI/CD app materializes it before Build only when the file is
|
|
138
|
+
still missing on disk.
|
|
139
|
+
- **Port**: from `# Port Allocation`; if absent, assign sequentially from 3000 avoiding
|
|
140
|
+
collisions with the studio (3001) and the CI/CD app port.
|
|
141
|
+
- **Health / running-detection**: the endpoint the generated CI/CD app uses to tell whether the
|
|
142
|
+
application is already running. Detection is a **raw TCP connect to the app's port on loopback**
|
|
143
|
+
— the generated app probes BOTH IPv4 (`127.0.0.1`) and IPv6 (`::1`) loopback, plus the host
|
|
144
|
+
named in this Health value — so it does not matter which family the framework binds. This
|
|
145
|
+
matters because dev servers differ: Spring Boot binds `127.0.0.1`, but **Vite / Next preview
|
|
146
|
+
servers bind IPv6 `::1` (localhost) only** and are invisible to an IPv4-only probe. Therefore
|
|
147
|
+
set the Health host to **`localhost`** (not `127.0.0.1`) for any Node/Vite/Next dev-server app
|
|
148
|
+
so the detection host set includes the interface it actually binds; use `127.0.0.1` for JVM
|
|
149
|
+
apps. Value form: `http://localhost:<port>/` or a documented health path from the spec (the
|
|
150
|
+
path is informational — detection keys on the port, not the path).
|
|
151
|
+
- **CI/CD app port**: 5000 unless taken in `# Port Allocation`; record it in the plan.
|
|
152
|
+
|
|
153
|
+
### Application config (per application)
|
|
154
|
+
|
|
155
|
+
Decide the runtime config the application needs so it STARTS SUCCESSFULLY, and where each value
|
|
156
|
+
comes from. For every config file the start command reads (`path`, relative to the application
|
|
157
|
+
folder):
|
|
158
|
+
|
|
159
|
+
- **source** — classify each file:
|
|
160
|
+
- `env-file` — an existing `.env` / `.env-*` in the app folder already holds the runtime values;
|
|
161
|
+
use it as-is on disk (do NOT re-materialize it). Set `managed: no`, no resolved content.
|
|
162
|
+
- `environment` — there is no complete runtime file; resolve the required values from
|
|
163
|
+
ENVIRONMENT.md (and the Infrastructure hosts/ports) and record the fully-resolved file content.
|
|
164
|
+
Set `managed: yes` so the CI/CD app writes it before Build/Start when missing.
|
|
165
|
+
- `derived` — the runtime file is chosen/combined from existing material (e.g. copy the target
|
|
166
|
+
env's `.env-<env>` to the canonical `.env` the start command reads, or merge an existing
|
|
167
|
+
`.env-*` with ENVIRONMENT.md values). Set `managed: yes` and record the resolved content.
|
|
168
|
+
- **required keys** — enumerate every key the app must have to boot: its own PORT, datastore/cache/
|
|
169
|
+
broker connection strings, external-service URLs, credentials/secrets. Cross-check them against
|
|
170
|
+
ENVIRONMENT.md and the Infrastructure list.
|
|
171
|
+
- **completeness (the point of this)** — every required key MUST be satisfiable from ENVIRONMENT.md
|
|
172
|
+
or an existing env file. If a required key cannot be resolved, still emit the config entry but
|
|
173
|
+
call out the unresolved key in that application's Rationale as a GAP (so the human fixes
|
|
174
|
+
ENVIRONMENT.md before approving) — never silently drop it.
|
|
175
|
+
- **managed content** — for a `managed: yes` file, write the resolved body verbatim into the plan
|
|
176
|
+
as a fenced block (`path=<file>`). Values (including credentials) are emitted verbatim under the
|
|
177
|
+
dev-environment plaintext rule; they must never appear in the Rationale narrative.
|
|
178
|
+
|
|
179
|
+
Only list files the app actually reads at runtime — do not invent config files. An app that needs
|
|
180
|
+
no config file gets an empty config list.
|
|
181
|
+
|
|
182
|
+
### Infrastructure (status-only)
|
|
183
|
+
|
|
184
|
+
Supporting 3rd-party services are **not** built/started/deployed by the CI/CD app — they are
|
|
185
|
+
surfaced status-only. For each service resolved in Gather step 5, decide:
|
|
186
|
+
|
|
187
|
+
- **id**: a stable kebab-case key unique within the plan (e.g. `postgres-main`, `keycloak`).
|
|
188
|
+
- **name**: display name (e.g. `PostgreSQL (skf_main)`, `Keycloak`).
|
|
189
|
+
- **host** / **port**: the address the CI/CD app TCP-probes for reachability; default host
|
|
190
|
+
`127.0.0.1`. Use the values ENVIRONMENT.md documents.
|
|
191
|
+
- **url**: the web console/UI opened in a new tab, when the service has one (Keycloak admin,
|
|
192
|
+
MinIO console, Mailcatcher inbox); `-` for headless services (PostgreSQL, Redis).
|
|
193
|
+
- **description**: a short blurb (e.g. `Shared app database`, `S3-compatible object storage`).
|
|
194
|
+
|
|
195
|
+
Never emit credentials into the infrastructure entries — status and links only.
|
|
196
|
+
|
|
197
|
+
## Phase 3 — Write the plan
|
|
198
|
+
|
|
199
|
+
Create `cicd/` if missing and write `cicd/CICD_PLAN.md` (shown here in a 4-backtick fence so the
|
|
200
|
+
inner ```env config block renders — the plan file itself uses a normal 3-backtick env fence):
|
|
201
|
+
|
|
202
|
+
````markdown
|
|
203
|
+
# CI/CD Deployment Plan — <Project Name>
|
|
204
|
+
|
|
205
|
+
**Generated**: <ISO date>
|
|
206
|
+
**Status**: IN PROGRESS
|
|
207
|
+
|
|
208
|
+
## CI/CD App
|
|
209
|
+
|
|
210
|
+
- **Port**: <port>
|
|
211
|
+
- **Folder**: cicd/app
|
|
212
|
+
|
|
213
|
+
## Deployment Decisions
|
|
214
|
+
|
|
215
|
+
| Application | Folder | Method | Port | Build | Start | Stop | Deploy Method | Deploy | Health |
|
|
216
|
+
|---|---|---|---|---|---|---|---|---|---|
|
|
217
|
+
| <name> | <folder> | process|container | <port> | `<cmd>` | `<cmd>` | process-kill|docker stop | none|web-server|docker|kubernetes | `<cmd or ->` | <url> |
|
|
218
|
+
|
|
219
|
+
> **Running-detection**: the generated CI/CD app determines "is it running?" by a TCP connect to
|
|
220
|
+
> each application's **Port** on both IPv4 (`127.0.0.1`) and IPv6 (`::1`) loopback plus the
|
|
221
|
+
> **Health** host — so an app is detected whichever family it binds. Use a `localhost` Health host
|
|
222
|
+
> for Node/Vite/Next apps (they bind IPv6 `::1`) and `127.0.0.1` for JVM apps.
|
|
223
|
+
|
|
224
|
+
> **`{VERSION}` token**: Build/Start/Deploy commands may contain the literal `{VERSION}`;
|
|
225
|
+
> the generated CI/CD app replaces it with the project's CURRENT version (semver without
|
|
226
|
+
> the `v` prefix, `latest` when unknown) at execution time — no user-facing selector.
|
|
227
|
+
> Used by the docker-image path to tag images; tag-only — the code built is always the
|
|
228
|
+
> working tree.
|
|
229
|
+
|
|
230
|
+
## Docker
|
|
231
|
+
|
|
232
|
+
Written only from Gather 2a. When Docker or a docker repo is absent, write the section as:
|
|
233
|
+
`_Not dockerized — Docker and/or a docker repo are not configured in DEVTOOL.md/ENVIRONMENT.md._`
|
|
234
|
+
|
|
235
|
+
**Registry**: <repo, e.g. `registry.example.com/team`>
|
|
236
|
+
|
|
237
|
+
| Application | Image | Dockerfile |
|
|
238
|
+
|---|---|---|
|
|
239
|
+
| <name> | `<repo>/<app_folder>` | existing \| managed |
|
|
240
|
+
|
|
241
|
+
## Dockerfiles
|
|
242
|
+
|
|
243
|
+
One fenced block per `managed` Dockerfile (omit the section body — `_All Dockerfiles exist._`
|
|
244
|
+
— when every dockerized app already has one; `_Not dockerized._` when the Docker section is
|
|
245
|
+
inactive). The generated CI/CD app writes each body to `<app_folder>/Dockerfile` before Build
|
|
246
|
+
only when the file is missing on disk — an existing Dockerfile is never overwritten.
|
|
247
|
+
|
|
248
|
+
```dockerfile path=<app_folder>/Dockerfile
|
|
249
|
+
FROM node:22-slim
|
|
250
|
+
WORKDIR /app
|
|
251
|
+
COPY package*.json ./
|
|
252
|
+
RUN npm ci --omit=dev
|
|
253
|
+
COPY . .
|
|
254
|
+
EXPOSE <port>
|
|
255
|
+
CMD ["node", "dist/index.js"]
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Application Configs
|
|
259
|
+
|
|
260
|
+
The runtime config each application needs to START SUCCESSFULLY. The generated CI/CD app writes any
|
|
261
|
+
`managed=yes` file into the application folder before Build/Start when it is missing, and shows
|
|
262
|
+
every file (source, status, required keys, live content) in the per-application Config modal for
|
|
263
|
+
troubleshooting. One table per application; `Path` is relative to the application folder.
|
|
264
|
+
|
|
265
|
+
### <App Name> (<folder>)
|
|
266
|
+
|
|
267
|
+
| Path | Source | Managed | Required Keys |
|
|
268
|
+
|---|---|---|---|
|
|
269
|
+
| .env | environment | yes | DATABASE_URL, REDIS_HOST, PORT |
|
|
270
|
+
| .env-uat | env-file | no | DATABASE_URL, REDIS_HOST |
|
|
271
|
+
|
|
272
|
+
For each `Managed=yes` row, follow it with the resolved file body (verbatim — credentials allowed
|
|
273
|
+
under the dev-plaintext rule):
|
|
274
|
+
|
|
275
|
+
```env path=.env
|
|
276
|
+
DATABASE_URL=postgres://app:app@127.0.0.1:5432/appdb
|
|
277
|
+
REDIS_HOST=127.0.0.1
|
|
278
|
+
PORT=3000
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Omit the fenced block for `env-file` rows (the file is used as-is on disk). If an application needs
|
|
282
|
+
no config file, write `_No runtime config files._` under its heading.
|
|
283
|
+
|
|
284
|
+
## Infrastructure
|
|
285
|
+
|
|
286
|
+
Supporting 3rd-party services shown status-only (TCP reachability) on the CI/CD app's
|
|
287
|
+
Infrastructure page. Leave the table body empty when the project declares no supporting services.
|
|
288
|
+
|
|
289
|
+
| Id | Service | Host | Port | Console URL | Description |
|
|
290
|
+
|---|---|---|---|---|---|
|
|
291
|
+
| <id> | <name> | <host> | <port> | `<url or ->` | <blurb> |
|
|
292
|
+
|
|
293
|
+
## Rationale
|
|
294
|
+
|
|
295
|
+
- <per-application: why this method/commands, citing ENVIRONMENT.md/DEVTOOL.md evidence>
|
|
296
|
+
````
|
|
297
|
+
|
|
298
|
+
Then flip `**Status**: IN PROGRESS` to `**Status**: COMPLETED`. The studio watcher advances
|
|
299
|
+
only on COMPLETED.
|
|
300
|
+
|
|
301
|
+
## STOP — Hand Back to the Human
|
|
302
|
+
|
|
303
|
+
Print a one-paragraph summary and STOP. Never invoke util-gencicdscript or any other skill —
|
|
304
|
+
the human reviews and approves the plan in the studio's CI/CD screen first.
|
|
305
|
+
|
|
306
|
+
## Important Rules
|
|
307
|
+
|
|
308
|
+
- Never write the CI/CD app; never modify PRD.md/CLAUDE.md/BUG.md.
|
|
309
|
+
- Never delete an existing `**Approved**:` line if re-planning over a previous plan — overwrite
|
|
310
|
+
the whole file WITHOUT any Approved line (a re-plan always requires fresh approval).
|
|
311
|
+
- Credentials from ENVIRONMENT.md may be referenced in commands verbatim (dev-environment
|
|
312
|
+
plaintext rule) but never echoed into the Rationale narrative.
|