@rashidee/co2 1.3.3 → 1.3.4
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 +0 -0
- package/dist/.co2-dat/app.db-shm +0 -0
- package/dist/.co2-dat/app.db-wal +0 -0
- package/dist/.co2-dat/config.json +4 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/plugin/skills/util-gencicdscript/SKILL.md +14 -4
- package/plugin/skills/util-gencicdscript/references/cicd-app-template.md +91 -5
- package/plugin/skills/util-plancicd/SKILL.md +40 -4
|
Binary file
|
|
Binary file
|
|
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.4",
|
|
6813
6813
|
description: "Compound Context Studio \u2014 self-hosted team context authoring for CO2 projects",
|
|
6814
6814
|
type: "module",
|
|
6815
6815
|
license: "MIT",
|
package/package.json
CHANGED
|
@@ -17,7 +17,9 @@ description: >
|
|
|
17
17
|
has never completed a conductor-develop workflow run (checked against the studio's
|
|
18
18
|
/api/cicd-guard endpoint using the spawn-env token). Health/port probing marks apps already
|
|
19
19
|
running — even ones started outside this app. No version selector: only the latest built
|
|
20
|
-
applications are operated.
|
|
20
|
+
applications are operated. When the approved plan declares an `## Infrastructure` list, the
|
|
21
|
+
main page shows an "Infrastructure" link that opens a separate status-only page listing every
|
|
22
|
+
supporting 3rd-party service with a live TCP-reachability indicator and a console link. Writes cicd/CICD_GENERATION.md with `**Status**:` IN PROGRESS →
|
|
21
23
|
COMPLETED so the studio watcher detects completion. Idempotent — re-running regenerates
|
|
22
24
|
cicd/app in place. Trigger on keywords: "generate cicd", "gen cicd script", "generate ci/cd
|
|
23
25
|
app", "cicd app", "generate deployment app". Accepts one MANDATORY argument: the
|
|
@@ -56,9 +58,12 @@ Read `references/cicd-app-template.md` for the complete file templates. Produce:
|
|
|
56
58
|
|
|
57
59
|
1. `cicd/app/cicd.config.json` — from the plan's tables:
|
|
58
60
|
`{ "port": <CI/CD app port>, "apps": [{ "name", "folder", "method", "port", "build",
|
|
59
|
-
"start", "stop", "deployMethod", "deploy", "health" }]
|
|
60
|
-
|
|
61
|
-
(`null` when the method is
|
|
61
|
+
"start", "stop", "deployMethod", "deploy", "health" }], "infra": [{ "id", "name", "host",
|
|
62
|
+
"port", "url", "description" }] }` — `deployMethod` is the plan's Deploy Method column
|
|
63
|
+
(`none|web-server|docker|kubernetes`) and `deploy` its command (`null` when the method is
|
|
64
|
+
`none`). The `infra` array comes from the plan's `## Infrastructure` table (one entry per
|
|
65
|
+
row); `url` is `null` when the Console URL cell is `-`. Emit `"infra": []` when the plan's
|
|
66
|
+
Infrastructure table is empty — never invent services the plan does not list.
|
|
62
67
|
2. `cicd/app/server.js` — the template verbatim (it is generic; all project specifics live in
|
|
63
68
|
cicd.config.json). Zero npm dependencies.
|
|
64
69
|
3. Ensure `cicd/logs/` and `cicd/state.json` are ignored: append `cicd/logs/` and
|
|
@@ -75,6 +80,11 @@ port, application count) and STOP — the studio starts/embeds the app.
|
|
|
75
80
|
template); no install step may be required.
|
|
76
81
|
- The page is bare: no header, sidebar or footer (it is iframed by the studio). Light theme,
|
|
77
82
|
one nav tab per application; the tab body is that application's live log.
|
|
83
|
+
- Infrastructure page: when `infra` is non-empty the main page shows an "Infrastructure" link
|
|
84
|
+
that opens the separate `/infra` page in a new tab — a status-only list of supporting
|
|
85
|
+
3rd-party services, each with a live TCP-reachability dot (polled) and a console link when a
|
|
86
|
+
`url` is set. Infrastructure is never built/started/stopped/deployed (no lifecycle controls,
|
|
87
|
+
no develop gate); the link is hidden when `infra` is empty.
|
|
78
88
|
- Build/Start/Stop/Deploy run the plan's commands verbatim, each with the APPLICATION FOLDER
|
|
79
89
|
(`<project>/<folder>`) as working directory — command paths are relative to the application
|
|
80
90
|
folder (e.g. `java -jar target\app.jar`), never to the project root. Deploy renders only
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// server retains nothing per subscriber beyond the socket itself (bounded initial tail,
|
|
12
12
|
// slow clients are dropped on backpressure), and the client trims its DOM log buffer.
|
|
13
13
|
const http = require('node:http')
|
|
14
|
+
const net = require('node:net')
|
|
14
15
|
const { spawn, execFileSync } = require('node:child_process')
|
|
15
16
|
const { createHash } = require('node:crypto')
|
|
16
17
|
const { readFileSync, writeFileSync, mkdirSync, existsSync, statSync, openSync, readSync, closeSync, createWriteStream } = require('node:fs')
|
|
@@ -141,6 +142,21 @@ async function probe(app) {
|
|
|
141
142
|
} catch { return false }
|
|
142
143
|
}
|
|
143
144
|
|
|
145
|
+
// Infrastructure reachability — a raw TCP connect, so it works for non-HTTP services
|
|
146
|
+
// (PostgreSQL, Redis) as well as web consoles. Status-only: infra is never controlled here.
|
|
147
|
+
function tcpProbe(host, port, timeout = 1000) {
|
|
148
|
+
return new Promise((resolve) => {
|
|
149
|
+
const socket = new net.Socket()
|
|
150
|
+
let done = false
|
|
151
|
+
const finish = (ok) => { if (done) return; done = true; try { socket.destroy() } catch {} ; resolve(ok) }
|
|
152
|
+
socket.setTimeout(timeout)
|
|
153
|
+
socket.once('connect', () => finish(true))
|
|
154
|
+
socket.once('timeout', () => finish(false))
|
|
155
|
+
socket.once('error', () => finish(false))
|
|
156
|
+
try { socket.connect(Number(port), host || '127.0.0.1') } catch { finish(false) }
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
144
160
|
function buildApp(app) {
|
|
145
161
|
if (activity.has(app.name)) return
|
|
146
162
|
activity.set(app.name, 'building'); lastError.delete(app.name)
|
|
@@ -233,7 +249,7 @@ button:disabled{opacity:.4;cursor:default}
|
|
|
233
249
|
.err{padding:4px 12px;font-size:11px;color:#c23b3b;display:none;white-space:pre-wrap}
|
|
234
250
|
#log{flex:1;margin:0;padding:10px 12px;background:#0f1216;color:#d7dde4;font-size:11px;line-height:1.5;overflow:auto;white-space:pre-wrap}
|
|
235
251
|
</style></head><body>
|
|
236
|
-
<div class="top"><strong>CI/CD</strong><span id="branch" class="meta"></span></div>
|
|
252
|
+
<div class="top"><strong>CI/CD</strong><span id="branch" class="meta"></span><span style="flex:1"></span><a id="infralink" class="url" href="/infra" target="_blank" rel="noreferrer" style="display:none">Infrastructure ↗</a></div>
|
|
237
253
|
<div class="tabs" id="tabs"></div>
|
|
238
254
|
<div class="bar" id="bar"></div>
|
|
239
255
|
<div class="notice" id="notice"></div>
|
|
@@ -312,6 +328,7 @@ async function refresh(){
|
|
|
312
328
|
if (!s) return
|
|
313
329
|
state = s
|
|
314
330
|
document.getElementById('branch').textContent = s.currentBranch ? 'branch: '+s.currentBranch : ''
|
|
331
|
+
document.getElementById('infralink').style.display = s.hasInfra ? 'inline' : 'none'
|
|
315
332
|
if (!active && s.apps.length) setActive(s.apps[0].name)
|
|
316
333
|
renderTabs(); renderBar()
|
|
317
334
|
}
|
|
@@ -322,12 +339,61 @@ async function act(op){
|
|
|
322
339
|
refresh(); setInterval(refresh, 3000)
|
|
323
340
|
</script></body></html>`
|
|
324
341
|
|
|
342
|
+
// ---- Infrastructure page ---------------------------------------------------------------
|
|
343
|
+
// Standalone status-only page (opened in a new tab from the main page's Infrastructure link).
|
|
344
|
+
// Lists every supporting 3rd-party service with a live TCP-reachability dot and a console link
|
|
345
|
+
// when the service exposes a web UI. No lifecycle controls — infra is externally managed.
|
|
346
|
+
const INFRA_PAGE = `<!doctype html><html><head><meta charset="utf-8"><title>Infrastructure</title><style>
|
|
347
|
+
body{font-family:system-ui,sans-serif;margin:0;background:#f6f7f9;color:#1c2128;padding:16px}
|
|
348
|
+
h1{font-size:16px;margin:0 0 12px}
|
|
349
|
+
table{border-collapse:collapse;width:100%;max-width:820px;font-size:13px}
|
|
350
|
+
th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #e1e5ea}
|
|
351
|
+
th{font-size:11px;text-transform:uppercase;color:#5b6572}
|
|
352
|
+
.dot{display:inline-block;width:9px;height:9px;border-radius:99px;background:#8a94a0;margin-right:6px;vertical-align:middle}
|
|
353
|
+
.dot.up{background:#1c8a4a}.dot.down{background:#c23b3b}
|
|
354
|
+
.url{color:#1a66d0;text-decoration:underline}
|
|
355
|
+
.muted{color:#5b6572;font-size:12px}
|
|
356
|
+
.legend{margin-top:12px;font-size:11px;color:#5b6572}
|
|
357
|
+
</style></head><body>
|
|
358
|
+
<h1>Infrastructure</h1>
|
|
359
|
+
<table><thead><tr><th>Status</th><th>Service</th><th>Endpoint</th><th>Console</th></tr></thead>
|
|
360
|
+
<tbody id="rows"><tr><td colspan="4" class="muted">Loading…</td></tr></tbody></table>
|
|
361
|
+
<div class="legend">● up ○ down — TCP reachability, polled every 3s</div>
|
|
362
|
+
<script>
|
|
363
|
+
function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g, c => ({'&':'&','<':'<','>':'>','"':'"'}[c])) }
|
|
364
|
+
async function refresh(){
|
|
365
|
+
const s = await fetch('/api/infra').then(r=>r.json()).catch(()=>null)
|
|
366
|
+
if(!s) return
|
|
367
|
+
const rows = (s.infra||[]).map(i => \`
|
|
368
|
+
<tr>
|
|
369
|
+
<td><span class="dot \${i.up?'up':'down'}"></span>\${i.up?'up':'down'}</td>
|
|
370
|
+
<td>\${esc(i.name)}\${i.description?' <span class="muted">— '+esc(i.description)+'</span>':''}</td>
|
|
371
|
+
<td class="muted">\${esc(i.host)}:\${esc(i.port)}</td>
|
|
372
|
+
<td>\${i.url?'<a class="url" href="'+esc(i.url)+'" target="_blank" rel="noreferrer">open ↗</a>':'—'}</td>
|
|
373
|
+
</tr>\`).join('')
|
|
374
|
+
document.getElementById('rows').innerHTML = rows || '<tr><td colspan="4" class="muted">No infrastructure declared.</td></tr>'
|
|
375
|
+
}
|
|
376
|
+
refresh(); setInterval(refresh, 3000)
|
|
377
|
+
</script></body></html>`
|
|
378
|
+
|
|
325
379
|
// ---- HTTP ------------------------------------------------------------------------------
|
|
326
380
|
const server = http.createServer(async (req, res) => {
|
|
327
381
|
if (req.method === 'GET' && req.url === '/') {
|
|
328
382
|
res.writeHead(200, { 'content-type': 'text/html' })
|
|
329
383
|
return res.end(PAGE)
|
|
330
384
|
}
|
|
385
|
+
if (req.method === 'GET' && req.url === '/infra') {
|
|
386
|
+
res.writeHead(200, { 'content-type': 'text/html' })
|
|
387
|
+
return res.end(INFRA_PAGE)
|
|
388
|
+
}
|
|
389
|
+
if (req.method === 'GET' && req.url === '/api/infra') {
|
|
390
|
+
const infra = await Promise.all((CONFIG.infra || []).map(async (i) => ({
|
|
391
|
+
id: i.id, name: i.name, host: i.host || '127.0.0.1', port: i.port,
|
|
392
|
+
url: i.url || null, description: i.description || '',
|
|
393
|
+
up: await tcpProbe(i.host || '127.0.0.1', i.port),
|
|
394
|
+
})))
|
|
395
|
+
return json(res, 200, { infra })
|
|
396
|
+
}
|
|
331
397
|
if (req.method === 'GET' && req.url === '/api/state') {
|
|
332
398
|
const apps = await Promise.all(CONFIG.apps.map(async (a) => {
|
|
333
399
|
const healthy = await probe(a)
|
|
@@ -340,7 +406,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
340
406
|
error: lastError.get(a.name) || null,
|
|
341
407
|
}
|
|
342
408
|
}))
|
|
343
|
-
return json(res, 200, { currentBranch: currentBranch(), apps })
|
|
409
|
+
return json(res, 200, { currentBranch: currentBranch(), hasInfra: (CONFIG.infra || []).length > 0, apps })
|
|
344
410
|
}
|
|
345
411
|
const m = req.url && req.url.match(/^\/api\/apps\/([^/]+)\/(build|start|stop|deploy)$/)
|
|
346
412
|
if (m && req.method === 'POST') {
|
|
@@ -389,9 +455,11 @@ server.on('upgrade', (req, socket) => {
|
|
|
389
455
|
server.listen(PORT, () => console.log(`cicd app on :${PORT}`))
|
|
390
456
|
```
|
|
391
457
|
|
|
392
|
-
`cicd/app/cicd.config.json` shape — one entry per application from the plan's Deployment
|
|
393
|
-
Decisions table
|
|
394
|
-
`
|
|
458
|
+
`cicd/app/cicd.config.json` shape — one `apps` entry per application from the plan's Deployment
|
|
459
|
+
Decisions table, one `infra` entry per row of the plan's `## Infrastructure` table.
|
|
460
|
+
`deployMethod`/`deploy` come from the plan's Deploy columns (`deploy` is `null` when the method
|
|
461
|
+
is `none`). `infra[].url` is `null` for a headless service (Console URL cell `-`); emit
|
|
462
|
+
`"infra": []` when the plan declares no supporting services:
|
|
395
463
|
|
|
396
464
|
```json
|
|
397
465
|
{
|
|
@@ -409,6 +477,24 @@ Decisions table. `deployMethod`/`deploy` come from the plan's Deploy columns (`d
|
|
|
409
477
|
"deploy": "kubectl apply -f my-app/k8s/",
|
|
410
478
|
"health": "http://127.0.0.1:3000/"
|
|
411
479
|
}
|
|
480
|
+
],
|
|
481
|
+
"infra": [
|
|
482
|
+
{
|
|
483
|
+
"id": "postgres-main",
|
|
484
|
+
"name": "PostgreSQL (app db)",
|
|
485
|
+
"host": "127.0.0.1",
|
|
486
|
+
"port": 5432,
|
|
487
|
+
"url": null,
|
|
488
|
+
"description": "Shared application database"
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
"id": "keycloak",
|
|
492
|
+
"name": "Keycloak",
|
|
493
|
+
"host": "127.0.0.1",
|
|
494
|
+
"port": 8180,
|
|
495
|
+
"url": "http://127.0.0.1:8180",
|
|
496
|
+
"description": "Single sign-on"
|
|
497
|
+
}
|
|
412
498
|
]
|
|
413
499
|
}
|
|
414
500
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: util-plancicd
|
|
3
|
-
model: claude-
|
|
3
|
+
model: claude-opus-4-8
|
|
4
4
|
effort: high
|
|
5
5
|
description: >
|
|
6
6
|
Infer a local-deployment plan for every custom application of a CO2 project and write it to
|
|
@@ -10,7 +10,11 @@ description: >
|
|
|
10
10
|
method (container vs bare process), build command (e.g. Spring Boot jar vs Docker image),
|
|
11
11
|
start command, stop mechanism, deploy method + command (none / web-server copy / docker run /
|
|
12
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.
|
|
13
|
+
port for the generated CI/CD app itself. It also enumerates the project's supporting
|
|
14
|
+
infrastructure (3rd-party services from CLAUDE.md's `# Supporting 3rd Party Applications` and
|
|
15
|
+
ENVIRONMENT.md — host, port and optional console URL) into an `## Infrastructure` section so
|
|
16
|
+
the generated CI/CD app can surface a status-only Infrastructure page. The plan carries a
|
|
17
|
+
top-level
|
|
14
18
|
`**Status**: IN PROGRESS` header flipped to `COMPLETED` when done — the studio's watcher reads
|
|
15
19
|
it. This skill NEVER generates the CI/CD app (that is util-gencicdscript, which runs only
|
|
16
20
|
after the studio stamps an **Approved**: line into the plan). Trigger on keywords: "plan
|
|
@@ -43,6 +47,7 @@ If the argument is missing or the file does not exist, STOP and tell the user to
|
|
|
43
47
|
| Dev tools | `DEVTOOL.md` (project root) |
|
|
44
48
|
| Project context | `CLAUDE.md` (project root; `source/CLAUDE.md` fallback) |
|
|
45
49
|
| App specification | `<app_folder>/context/specification/SPECIFICATION.md` |
|
|
50
|
+
| Infrastructure | `CLAUDE.md` `# Supporting 3rd Party Applications` + `ENVIRONMENT.md` |
|
|
46
51
|
| Output plan | `cicd/CICD_PLAN.md` |
|
|
47
52
|
|
|
48
53
|
## Phase 1 — Gather
|
|
@@ -50,11 +55,18 @@ If the argument is missing or the file does not exist, STOP and tell the user to
|
|
|
50
55
|
1. Read the plan-request markdown; its `## Applications` list is the authoritative application set.
|
|
51
56
|
2. Read `ENVIRONMENT.md` (registries, credentials, external services) and `DEVTOOL.md`
|
|
52
57
|
(installed tools + paths: node/pnpm/docker/kubectl/python versions).
|
|
53
|
-
3. Read `CLAUDE.md`: `# Custom Applications` (folder names, Depends on)
|
|
54
|
-
(per-application ports)
|
|
58
|
+
3. Read `CLAUDE.md`: `# Custom Applications` (folder names, Depends on), `# Port Allocation`
|
|
59
|
+
(per-application ports), and `# Supporting 3rd Party Applications` (the infrastructure the
|
|
60
|
+
applications depend on — databases, caches, object storage, SSO, mail, message brokers, etc.).
|
|
55
61
|
4. For each application, read its SPECIFICATION.md tech-stack section if present; otherwise
|
|
56
62
|
infer the stack from build files (`package.json` → node, `pom.xml` → spring-boot,
|
|
57
63
|
`composer.json` → laravel).
|
|
64
|
+
5. Build the **infrastructure list**: one entry per supporting 3rd-party service the project
|
|
65
|
+
declares. For each, resolve `host` (default `127.0.0.1`), `port` and — when it exposes a web
|
|
66
|
+
console/UI — a `url`, cross-referencing ENVIRONMENT.md for the actual host/port/endpoint. A
|
|
67
|
+
service with no host:port to probe (or a purely external SaaS with no local endpoint) is
|
|
68
|
+
omitted. If the project declares no supporting 3rd-party services, the infrastructure list is
|
|
69
|
+
empty and the plan's `## Infrastructure` section is written empty.
|
|
58
70
|
|
|
59
71
|
## Phase 2 — Decide (per application)
|
|
60
72
|
|
|
@@ -87,6 +99,21 @@ If the argument is missing or the file does not exist, STOP and tell the user to
|
|
|
87
99
|
- **Health**: `http://127.0.0.1:<port>/` or a documented health endpoint from the spec.
|
|
88
100
|
- **CI/CD app port**: 4100 unless taken in `# Port Allocation`; record it in the plan.
|
|
89
101
|
|
|
102
|
+
### Infrastructure (status-only)
|
|
103
|
+
|
|
104
|
+
Supporting 3rd-party services are **not** built/started/deployed by the CI/CD app — they are
|
|
105
|
+
surfaced status-only. For each service resolved in Gather step 5, decide:
|
|
106
|
+
|
|
107
|
+
- **id**: a stable kebab-case key unique within the plan (e.g. `postgres-main`, `keycloak`).
|
|
108
|
+
- **name**: display name (e.g. `PostgreSQL (skf_main)`, `Keycloak`).
|
|
109
|
+
- **host** / **port**: the address the CI/CD app TCP-probes for reachability; default host
|
|
110
|
+
`127.0.0.1`. Use the values ENVIRONMENT.md documents.
|
|
111
|
+
- **url**: the web console/UI opened in a new tab, when the service has one (Keycloak admin,
|
|
112
|
+
MinIO console, Mailcatcher inbox); `-` for headless services (PostgreSQL, Redis).
|
|
113
|
+
- **description**: a short blurb (e.g. `Shared app database`, `S3-compatible object storage`).
|
|
114
|
+
|
|
115
|
+
Never emit credentials into the infrastructure entries — status and links only.
|
|
116
|
+
|
|
90
117
|
## Phase 3 — Write the plan
|
|
91
118
|
|
|
92
119
|
Create `cicd/` if missing and write `cicd/CICD_PLAN.md`:
|
|
@@ -108,6 +135,15 @@ Create `cicd/` if missing and write `cicd/CICD_PLAN.md`:
|
|
|
108
135
|
|---|---|---|---|---|---|---|---|---|---|
|
|
109
136
|
| <name> | <folder> | process|container | <port> | `<cmd>` | `<cmd>` | process-kill|docker stop | none|web-server|docker|kubernetes | `<cmd or ->` | <url> |
|
|
110
137
|
|
|
138
|
+
## Infrastructure
|
|
139
|
+
|
|
140
|
+
Supporting 3rd-party services shown status-only (TCP reachability) on the CI/CD app's
|
|
141
|
+
Infrastructure page. Leave the table body empty when the project declares no supporting services.
|
|
142
|
+
|
|
143
|
+
| Id | Service | Host | Port | Console URL | Description |
|
|
144
|
+
|---|---|---|---|---|---|
|
|
145
|
+
| <id> | <name> | <host> | <port> | `<url or ->` | <blurb> |
|
|
146
|
+
|
|
111
147
|
## Rationale
|
|
112
148
|
|
|
113
149
|
- <per-application: why this method/commands, citing ENVIRONMENT.md/DEVTOOL.md evidence>
|