@rashidee/co2 1.3.29 → 1.3.32

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.
Files changed (71) hide show
  1. package/dist/.co2-dat/app.db-shm +0 -0
  2. package/dist/.co2-dat/app.db-wal +0 -0
  3. package/dist/index.js +48 -9
  4. package/package.json +2 -2
  5. package/plugin/.claude-plugin/marketplace.json +4 -4
  6. package/plugin/.claude-plugin/plugin.json +2 -2
  7. package/plugin/skills/util-gencicdscript/SKILL.md +32 -17
  8. package/plugin/skills/util-gencicdscript/references/cicd-app-template.md +59 -32
  9. package/plugin/skills/util-plancicd/SKILL.md +65 -24
  10. package/static/assets/{abnfDiagram-VRR7QNED-B1zOhOO1.js → abnfDiagram-VRR7QNED-Bz30rK4w.js} +1 -1
  11. package/static/assets/{arc-B8GD8vjH.js → arc-K9O7tO8Y.js} +1 -1
  12. package/static/assets/{architectureDiagram-ZJ3FMSHR-Dg86MCTB.js → architectureDiagram-ZJ3FMSHR-BdrVmNvN.js} +1 -1
  13. package/static/assets/{blockDiagram-677ZJIJ3-CK3Tug1R.js → blockDiagram-677ZJIJ3-DImeRXuQ.js} +1 -1
  14. package/static/assets/{c4Diagram-LMCZKHZV-qM1w9tnY.js → c4Diagram-LMCZKHZV-Bj4yyPGH.js} +1 -1
  15. package/static/assets/channel-Cam27IrS.js +1 -0
  16. package/static/assets/{chunk-2Q5K7J3B-isC3spex.js → chunk-2Q5K7J3B-BHYOpztr.js} +1 -1
  17. package/static/assets/{chunk-32BRIVSS-BlKMW18Z.js → chunk-32BRIVSS-C2Wy22tQ.js} +1 -1
  18. package/static/assets/{chunk-5VM5RSS4-Dvcwsclh.js → chunk-5VM5RSS4-V-_fRucz.js} +1 -1
  19. package/static/assets/{chunk-EX3LRPZG-CxyFSCcZ.js → chunk-EX3LRPZG-DxT6dnNF.js} +1 -1
  20. package/static/assets/{chunk-JWPE2WC7-C1Gudu3M.js → chunk-JWPE2WC7-BZ9dVylx.js} +1 -1
  21. package/static/assets/{chunk-MOJQB5TN-CpZ6vY3L.js → chunk-MOJQB5TN-B3bjiMAY.js} +1 -1
  22. package/static/assets/{chunk-RYQCIY6F-Bi7Lt98n.js → chunk-RYQCIY6F-42f9_hh5.js} +1 -1
  23. package/static/assets/{chunk-V7JOEXUC-ypRmRGpD.js → chunk-V7JOEXUC-DU7kGh4W.js} +1 -1
  24. package/static/assets/{chunk-VR4S4FIN-CUmgHQ4J.js → chunk-VR4S4FIN-glWsMWYs.js} +1 -1
  25. package/static/assets/{chunk-XXDRQBXY-YKI0BxVa.js → chunk-XXDRQBXY-En74ppE-.js} +1 -1
  26. package/static/assets/classDiagram-OUVF2IWQ-C8kRWfsa.js +1 -0
  27. package/static/assets/classDiagram-v2-EOCWNBFH-C8kRWfsa.js +1 -0
  28. package/static/assets/{cose-bilkent-JH36ORCC-Bvy5w26V.js → cose-bilkent-JH36ORCC-q5Mo8ofj.js} +1 -1
  29. package/static/assets/{cynefin-VYW2F7L2-DmQsQ2TU.js → cynefin-VYW2F7L2-Dzy0NABg.js} +1 -1
  30. package/static/assets/{cynefinDiagram-TSTJHNR4-D0-WC7kz.js → cynefinDiagram-TSTJHNR4-PbQZZaRi.js} +1 -1
  31. package/static/assets/{dagre-VKFMJZFB-C1BlJBGv.js → dagre-VKFMJZFB-BZU8w1_Z.js} +1 -1
  32. package/static/assets/{diagram-FQU43EPY-Cg2hPqzw.js → diagram-FQU43EPY-Bp-CZLzx.js} +1 -1
  33. package/static/assets/{diagram-G47NLZAW-BP823BgP.js → diagram-G47NLZAW-CVmpgFTR.js} +1 -1
  34. package/static/assets/{diagram-NH7WQ7WH-C_Zqnx2Q.js → diagram-NH7WQ7WH-CohligHl.js} +1 -1
  35. package/static/assets/{diagram-OA4YK3LP-V607Kbpj.js → diagram-OA4YK3LP-DN1i4dDH.js} +1 -1
  36. package/static/assets/{diagram-WEI45ONY-DxUG6r0f.js → diagram-WEI45ONY-HoqeBQvE.js} +1 -1
  37. package/static/assets/{ebnfDiagram-CCIWWBDH-Ba30Z15f.js → ebnfDiagram-CCIWWBDH-BBKpOoys.js} +1 -1
  38. package/static/assets/{erDiagram-Q63AITRT-D6FLaEED.js → erDiagram-Q63AITRT-CGdT5vjr.js} +1 -1
  39. package/static/assets/{flowDiagram-23GEKE2U-C-d7PYEe.js → flowDiagram-23GEKE2U-DEsLeisJ.js} +1 -1
  40. package/static/assets/{ganttDiagram-NO4QXBWP-Ba6Uwkik.js → ganttDiagram-NO4QXBWP-Dmik8C0l.js} +1 -1
  41. package/static/assets/{gitGraphDiagram-IHSO6WYX-ZAJbW3RO.js → gitGraphDiagram-IHSO6WYX-eaEegSaF.js} +1 -1
  42. package/static/assets/{index-DU7Xwr-g.js → index-BWq7zKXB.js} +135 -134
  43. package/static/assets/{index-DPxMV4aU.css → index-D6Kc-3Ei.css} +1 -1
  44. package/static/assets/{infoDiagram-FWYZ7A6U-BFfhZFvq.js → infoDiagram-FWYZ7A6U-D_ysDB_l.js} +1 -1
  45. package/static/assets/{ishikawaDiagram-FXEZZL3T-IIK9GJIX.js → ishikawaDiagram-FXEZZL3T-ClxYOJnV.js} +1 -1
  46. package/static/assets/{journeyDiagram-5HDEW3XC-DREaqROi.js → journeyDiagram-5HDEW3XC-CnFPmn-M.js} +1 -1
  47. package/static/assets/{kanban-definition-HUTT4EX6-XQ87DJka.js → kanban-definition-HUTT4EX6-DJsX2G5l.js} +1 -1
  48. package/static/assets/{linear-BRptxfMg.js → linear-BWkxWbFl.js} +1 -1
  49. package/static/assets/{mindmap-definition-LN4V7U3C-zx0hP0Si.js → mindmap-definition-LN4V7U3C-D5ENc9VY.js} +1 -1
  50. package/static/assets/{pegDiagram-2B236MQR-BkKjcF1V.js → pegDiagram-2B236MQR-DOJqp1by.js} +1 -1
  51. package/static/assets/{pieDiagram-ENE6RG2P-BlrcgJsf.js → pieDiagram-ENE6RG2P-g8RqyZcx.js} +1 -1
  52. package/static/assets/{quadrantDiagram-ABIIQ3AL-BY6Q-v3j.js → quadrantDiagram-ABIIQ3AL-BTqvdqkC.js} +1 -1
  53. package/static/assets/{railroadDiagram-RFXS5EU6-Dd0Y_o2f.js → railroadDiagram-RFXS5EU6-iq_hwy4b.js} +1 -1
  54. package/static/assets/{requirementDiagram-TGXJPOKE-DnkNRD4q.js → requirementDiagram-TGXJPOKE-46avlw83.js} +1 -1
  55. package/static/assets/{sankeyDiagram-HTMAVEWB-DIEbMauU.js → sankeyDiagram-HTMAVEWB-CDpxKea5.js} +1 -1
  56. package/static/assets/{sequenceDiagram-DBY2YBRQ-DrSyx6Hq.js → sequenceDiagram-DBY2YBRQ-DqUd93_A.js} +1 -1
  57. package/static/assets/{sizeCapture-X5ZJPWSS-BHRK6fVR.js → sizeCapture-X5ZJPWSS-Dg-DnkUa.js} +1 -1
  58. package/static/assets/{stateDiagram-2N3HPSRC-p36VYpET.js → stateDiagram-2N3HPSRC-D6BbVr9A.js} +1 -1
  59. package/static/assets/stateDiagram-v2-6OUMAXLB-Dq2xOcdU.js +1 -0
  60. package/static/assets/{swimlanes-5IMT3BWC-B27NzwNn.js → swimlanes-5IMT3BWC-uMMz-BEY.js} +2 -2
  61. package/static/assets/swimlanesDiagram-G3AALYLV-BsnXjA98.js +8 -0
  62. package/static/assets/{timeline-definition-FHXFAJF6-BBkuwSpt.js → timeline-definition-FHXFAJF6-xtE5xErE.js} +1 -1
  63. package/static/assets/{vennDiagram-L72KCM5P-BkqY7uCt.js → vennDiagram-L72KCM5P-Dy_JaPOh.js} +1 -1
  64. package/static/assets/{wardleyDiagram-EHGQE667-BhekO6RT.js → wardleyDiagram-EHGQE667-D08p63LT.js} +1 -1
  65. package/static/assets/{xychartDiagram-FW5EYKEG-Dsyggs2D.js → xychartDiagram-FW5EYKEG-CWXfhK2E.js} +1 -1
  66. package/static/index.html +2 -2
  67. package/static/assets/channel-Dnlq5moS.js +0 -1
  68. package/static/assets/classDiagram-OUVF2IWQ-CKr0oG9V.js +0 -1
  69. package/static/assets/classDiagram-v2-EOCWNBFH-CKr0oG9V.js +0 -1
  70. package/static/assets/stateDiagram-v2-6OUMAXLB-CHODjKhh.js +0 -1
  71. package/static/assets/swimlanesDiagram-G3AALYLV-DZEtOcuy.js +0 -8
Binary file
Binary file
package/dist/index.js CHANGED
@@ -6809,8 +6809,8 @@ var {
6809
6809
  // package.json
6810
6810
  var package_default = {
6811
6811
  name: "@rashidee/co2",
6812
- version: "1.3.28",
6813
- description: "Compound Context Studio \xC3\u0192\xC2\xA2\xC3\xA2\xE2\u20AC\u0161\xC2\xAC\xC3\xA2\xE2\u201A\xAC\xC2\x9D self-hosted team context authoring for CO2 projects",
6812
+ version: "1.3.32",
6813
+ description: "Compound Context Studio \xC3\u0192\xC6\u2019\xC3\u2020\xE2\u20AC\u2122\xC3\u0192\xE2\u20AC\xA0\xC3\xA2\xE2\u201A\xAC\xE2\u201E\xA2\xC3\u0192\xC6\u2019\xC3\xA2\xE2\u201A\xAC\xC2\xA0\xC3\u0192\xC2\xA2\xC3\xA2\xE2\u20AC\u0161\xC2\xAC\xC3\xA2\xE2\u20AC\u017E\xC2\xA2\xC3\u0192\xC6\u2019\xC3\u2020\xE2\u20AC\u2122\xC3\u0192\xC2\xA2\xC3\xA2\xE2\u20AC\u0161\xC2\xAC\xC3\u2026\xC2\xA1\xC3\u0192\xC6\u2019\xC3\xA2\xE2\u201A\xAC\xC5\xA1\xC3\u0192\xE2\u20AC\u0161\xC3\u201A\xC2\xA2\xC3\u0192\xC6\u2019\xC3\u2020\xE2\u20AC\u2122\xC3\u0192\xE2\u20AC\xA0\xC3\xA2\xE2\u201A\xAC\xE2\u201E\xA2\xC3\u0192\xC6\u2019\xC3\xA2\xE2\u201A\xAC\xC5\xA1\xC3\u0192\xE2\u20AC\u0161\xC3\u201A\xC2\xA2\xC3\u0192\xC6\u2019\xC3\u2020\xE2\u20AC\u2122\xC3\u0192\xE2\u20AC\u0161\xC3\u201A\xC2\xA2\xC3\u0192\xC6\u2019\xC3\u201A\xC2\xA2\xC3\u0192\xC2\xA2\xC3\xA2\xE2\u20AC\u0161\xC2\xAC\xC3\u2026\xC2\xA1\xC3\u0192\xE2\u20AC\u0161\xC3\u201A\xC2\xAC\xC3\u0192\xC6\u2019\xC3\xA2\xE2\u201A\xAC\xC2\xA6\xC3\u0192\xE2\u20AC\u0161\xC3\u201A\xC2\xA1\xC3\u0192\xC6\u2019\xC3\u2020\xE2\u20AC\u2122\xC3\u0192\xC2\xA2\xC3\xA2\xE2\u20AC\u0161\xC2\xAC\xC3\u2026\xC2\xA1\xC3\u0192\xC6\u2019\xC3\xA2\xE2\u201A\xAC\xC5\xA1\xC3\u0192\xE2\u20AC\u0161\xC3\u201A\xC2\xAC\xC3\u0192\xC6\u2019\xC3\u2020\xE2\u20AC\u2122\xC3\u0192\xE2\u20AC\xA0\xC3\xA2\xE2\u201A\xAC\xE2\u201E\xA2\xC3\u0192\xC6\u2019\xC3\xA2\xE2\u201A\xAC\xC5\xA1\xC3\u0192\xE2\u20AC\u0161\xC3\u201A\xC2\xA2\xC3\u0192\xC6\u2019\xC3\u2020\xE2\u20AC\u2122\xC3\u0192\xE2\u20AC\u0161\xC3\u201A\xC2\xA2\xC3\u0192\xC6\u2019\xC3\u201A\xC2\xA2\xC3\u0192\xC2\xA2\xC3\xA2\xE2\u201A\xAC\xC5\xA1\xC3\u201A\xC2\xAC\xC3\u0192\xE2\u20AC\xA6\xC3\u201A\xC2\xA1\xC3\u0192\xC6\u2019\xC3\xA2\xE2\u201A\xAC\xC5\xA1\xC3\u0192\xE2\u20AC\u0161\xC3\u201A\xC2\xAC\xC3\u0192\xC6\u2019\xC3\u2020\xE2\u20AC\u2122\xC3\u0192\xC2\xA2\xC3\xA2\xE2\u20AC\u0161\xC2\xAC\xC3\u2026\xC2\xA1\xC3\u0192\xC6\u2019\xC3\xA2\xE2\u201A\xAC\xC5\xA1\xC3\u0192\xE2\u20AC\u0161\xC3\u201A\xC2\x9D self-hosted team context authoring for CO2 projects",
6814
6814
  type: "module",
6815
6815
  license: "MIT",
6816
6816
  publishConfig: {
@@ -23211,19 +23211,30 @@ function getOrCreateSetup(db, projectId, userId) {
23211
23211
  const now = (/* @__PURE__ */ new Date()).toISOString();
23212
23212
  return db.insert(cicdSetups).values({ projectId, status: "draft", createdBy: userId, createdAt: now, updatedAt: now }).returning().get();
23213
23213
  }
23214
- function buildPlanRequestMarkdown(projectName, appNames, relativePath) {
23214
+ function buildPlanRequestMarkdown(projectName, appNames, relativePath, guidance) {
23215
+ const trimmedGuidance = guidance?.trim() ?? "";
23216
+ const guidanceSection = trimmedGuidance ? `## User Guidance
23217
+
23218
+ The following guidance was authored by the user and is AUTHORITATIVE \u2014 honor it over inferred
23219
+ defaults wherever they conflict, and note in the plan's Rationale where guidance overrode
23220
+ inference. \`shared_context/...\` links reference files under the project's \`shared_context/\`
23221
+ folder \u2014 read them for additional context.
23222
+
23223
+ ${trimmedGuidance}
23224
+
23225
+ ` : "";
23215
23226
  return `# CI/CD Deployment Plan Request \u2014 ${projectName}
23216
23227
 
23217
23228
  ## Instructions
23218
23229
 
23219
23230
  Infer a deployment plan for every application listed below. Read the project's
23220
23231
  \`ENVIRONMENT.md\`, \`DEVTOOL.md\`, \`CLAUDE.md\` (# Custom Applications, # Port Allocation)
23221
- and each application's \`context/specification/SPECIFICATION.md\`. Decide per application:
23232
+ and each application's \`context/specification/SPECIFICATION.md\`.${trimmedGuidance ? " Honor the `## User Guidance` section below." : ""} Decide per application:
23222
23233
  deployment method (container vs bare process), build command, start command, stop mechanism,
23223
23234
  port and health-check URL. Write the plan to \`${CICD_PLAN_RELATIVE}\` with a top-level
23224
23235
  \`**Status**: IN PROGRESS\` header, flipped to \`COMPLETED\` when the plan is finished.
23225
23236
 
23226
- ## Applications
23237
+ ${guidanceSection}## Applications
23227
23238
 
23228
23239
  ${appNames.map((n) => `- ${n}`).join("\n")}
23229
23240
 
@@ -23234,7 +23245,13 @@ ${appNames.map((n) => `- ${n}`).join("\n")}
23234
23245
  \`\`\`
23235
23246
  `;
23236
23247
  }
23237
- function requestPlan(db, workspacePath, projectId, userId) {
23248
+ function resetToDraft(db, projectId, userId) {
23249
+ const setup = getOrCreateSetup(db, projectId, userId);
23250
+ stopCicdWatcher(setup.id, "plan");
23251
+ stopCicdWatcher(setup.id, "generation");
23252
+ db.update(cicdSetups).set({ status: "draft", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq(cicdSetups.id, setup.id)).run();
23253
+ }
23254
+ function requestPlan(db, workspacePath, projectId, userId, guidance) {
23238
23255
  const setup = getOrCreateSetup(db, projectId, userId);
23239
23256
  stopCicdWatcher(setup.id, "generation");
23240
23257
  const project = requireProject(db, projectId);
@@ -23250,7 +23267,8 @@ function requestPlan(db, workspacePath, projectId, userId) {
23250
23267
  buildPlanRequestMarkdown(
23251
23268
  project.name,
23252
23269
  apps.map((a) => a.name),
23253
- relative3
23270
+ relative3,
23271
+ guidance
23254
23272
  ),
23255
23273
  "utf8"
23256
23274
  );
@@ -23475,12 +23493,33 @@ function cicdRoutes(db, workspacePath, studioPort) {
23475
23493
  return errorResponse3(c, e);
23476
23494
  }
23477
23495
  });
23478
- app.post("/:projectId/plan", (c) => {
23496
+ app.post("/:projectId/reset", (c) => {
23497
+ const projectId = parseProjectId(c);
23498
+ if (projectId === null) return c.json({ code: "INVALID_ID" }, 400);
23499
+ const user = c.get("user");
23500
+ if (!user) return c.json({ code: "UNAUTHORIZED" }, 401);
23501
+ try {
23502
+ resetToDraft(db, projectId, user.id);
23503
+ stopCicdApp(db, projectId);
23504
+ return c.json({ ok: true });
23505
+ } catch (e) {
23506
+ return errorResponse3(c, e);
23507
+ }
23508
+ });
23509
+ app.post("/:projectId/plan", async (c) => {
23479
23510
  const projectId = parseProjectId(c);
23480
23511
  if (projectId === null) return c.json({ code: "INVALID_ID" }, 400);
23481
23512
  const user = c.get("user");
23513
+ let guidance;
23514
+ try {
23515
+ const body = await c.req.json();
23516
+ if (typeof body.guidance === "string" && body.guidance.trim() !== "") {
23517
+ guidance = body.guidance.slice(0, 2e4);
23518
+ }
23519
+ } catch {
23520
+ }
23482
23521
  try {
23483
- const result = requestPlan(db, workspacePath, projectId, user.id);
23522
+ const result = requestPlan(db, workspacePath, projectId, user.id, guidance);
23484
23523
  stopCicdApp(db, projectId);
23485
23524
  return c.json(result, 201);
23486
23525
  } catch (e) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rashidee/co2",
3
- "version": "1.3.29",
4
- "description": "Compound Context Studio — self-hosted team context authoring for CO2 projects",
3
+ "version": "1.3.32",
4
+ "description": "Compound Context Studio — self-hosted team context authoring for CO2 projects",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "publishConfig": {
@@ -4,15 +4,15 @@
4
4
  "name": "Rashidee M.Rashid"
5
5
  },
6
6
  "metadata": {
7
- "description": "Compound Context (CO2) Workflow an opinionated context engineering methodology for AI-driven software development",
8
- "version": "1.0.2"
7
+ "description": "Compound Context (CO2) Workflow — an opinionated context engineering methodology for AI-driven software development",
8
+ "version": "1.0.3"
9
9
  },
10
10
  "plugins": [
11
11
  {
12
12
  "name": "co2-skills",
13
13
  "source": "./",
14
- "description": "Compound Context Skills generates data models, HTML mockups, technical specifications, test specifications, and orchestrates full-stack development from Agile user stories.",
15
- "version": "1.8.4",
14
+ "description": "Compound Context Skills — generates data models, HTML mockups, technical specifications, test specifications, and orchestrates full-stack development from Agile user stories.",
15
+ "version": "1.8.7",
16
16
  "author": {
17
17
  "name": "Rashidee M.Rashid"
18
18
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "co2-skills",
3
- "version": "1.8.5",
4
- "description": "Compound Context Skills — an opinionated context engineering methodology for AI-driven software development. Generates data models, HTML mockups, technical specifications, test specifications, and orchestrates full-stack development from Agile user stories.",
3
+ "version": "1.8.7",
4
+ "description": "Compound Context Skills — an opinionated context engineering methodology for AI-driven software development. Generates data models, HTML mockups, technical specifications, test specifications, and orchestrates full-stack development from Agile user stories.",
5
5
  "author": {
6
6
  "name": "Rashidee M.Rashid"
7
7
  },
@@ -9,16 +9,19 @@ description: >
9
9
  cicd/app/ — a zero-npm-dependency node:http app (server.js + cicd.config.json) rendering a
10
10
  bare light-themed page (no header/sidebar/footer, iframe-embeddable) with one nav tab per
11
11
  application: a controls bar (status badge, clickable app URL opening a new tab, a Config
12
- button, and Build / Start / Stop / Deploy buttons) above that application's log streamed live
12
+ button, and MODE-DEPENDENT lifecycle buttons from the plan's per-application execution mode:
13
+ `process` → Build / Start / Stop (+ Push to Docker when planned); `docker` → Build / Push to
14
+ Docker / Redeploy; `k8s` → Build / Update Manifest / Deploy to K8s) above that application's
15
+ log streamed live
13
16
  over a built-in zero-dependency WebSocket (bounded tail + backpressure drop server-side, trimmed
14
17
  DOM buffer client-side — no leaks/OOM). The Config button opens a modal listing the application's
15
18
  runtime config files (from the plan's `## Application Configs`) — each with its source
16
19
  (ENVIRONMENT.md-derived vs an existing .env/.env-* file), present/MISSING status, required keys
17
20
  and full live content — for troubleshooting; and before Build/Start the app materializes any
18
21
  missing plan-managed config file into the application folder so it always starts with complete
19
- configuration. Build and Deploy commands come from the approved plan (inferred
20
- from ENVIRONMENT.md/DEVTOOL.md e.g. Spring Boot jar vs Docker image; deploy via web-server
21
- copy, docker run or kubectl apply, or none). Controls are disabled for any application that
22
+ configuration. All lifecycle commands come verbatim from the approved plan (inferred
23
+ from ENVIRONMENT.md/DEVTOOL.md and the plan's execution modes; k8s manifests were generated by
24
+ depgen-k8s at planning time). Controls are disabled for any application that
22
25
  has never completed a conductor-develop workflow run (checked against the studio's
23
26
  /api/cicd-guard endpoint using the spawn-env token). Health/port probing marks apps already
24
27
  running — even ones started outside this app. There is NO version selector and there are NO
@@ -67,8 +70,8 @@ Read `references/cicd-app-template.md` for the complete file templates. Produce:
67
70
 
68
71
  1. `cicd/app/cicd.config.json` — from the plan's tables:
69
72
  `{ "port": <CI/CD app port>, "generatedAt": "<current ISO-8601 UTC>", "docker": { "registry" } | null, "apps": [{ "name", "folder",
70
- "method", "port", "build", "start", "stop", "deployMethod", "deploy", "health", "image",
71
- "dockerfile", "configs" }], "infra": [{ "id", "name",
73
+ "mode", "port", "build", "start", "stop", "push", "redeploy", "updateManifest", "deploy",
74
+ "health", "image", "dockerfile", "configs" }], "infra": [{ "id", "name",
72
75
  "host", "port", "url", "description" }] }` — `generatedAt` MUST be the actual time you generate
73
76
  this file (a fresh value on EVERY run — never copy a previous one): the running server pins the
74
77
  value it booted with and shows a "restart required" banner when a later regeneration moves it
@@ -79,9 +82,12 @@ Read `references/cicd-app-template.md` for the complete file templates. Produce:
79
82
  `## Dockerfiles` section, or `{ "path": "Dockerfile", "content": null }` for an `existing`
80
83
  one (`null` entirely when the app has no image). Copy managed Dockerfile bodies verbatim.
81
84
  Commands are copied verbatim from the plan and run as-is — there is NO `{VERSION}` token
82
- or any other placeholder substitution. `deployMethod` is the plan's Deploy Method column
83
- (`none|web-server|docker|kubernetes`) and `deploy` its command (`null` when the method is
84
- `none`). The `infra` array comes from the plan's `## Infrastructure` table (one entry per
85
+ or any other placeholder substitution. `mode` is the plan's Mode column
86
+ (`process|docker|k8s`); `start`/`stop`/`push`/`redeploy`/`updateManifest`/`deploy` come from
87
+ their plan columns, `null` wherever the cell is `-` the generated page renders per
88
+ application ONLY the buttons whose command exists, per mode (`process`: Build/Start/Stop
89
+ + Push; `docker`: Build/Push/Redeploy; `k8s`: Build/Update Manifest/Deploy to K8s).
90
+ The `infra` array comes from the plan's `## Infrastructure` table (one entry per
85
91
  row); `url` is `null` when the Console URL cell is `-`. Emit `"infra": []` when the plan's
86
92
  Infrastructure table is empty — never invent services the plan does not list.
87
93
  - `configs` — the runtime config files each application reads at start, from the plan's
@@ -162,19 +168,28 @@ port, application count) and STOP — the studio starts/embeds the app.
162
168
  3rd-party services, each with a live TCP-reachability dot (polled) and a console link when a
163
169
  `url` is set. Infrastructure is never built/started/stopped/deployed (no lifecycle controls,
164
170
  no develop gate); the link is hidden when `infra` is empty.
165
- - Build/Start/Stop/Deploy run the plan's commands, each with the APPLICATION FOLDER
171
+ - Every lifecycle button runs the plan's command, each with the APPLICATION FOLDER
166
172
  (`<project>/<folder>`) as working directory — command paths are relative to the application
167
- folder (e.g. `java -jar target\app.jar`), never to the project root. Deploy renders only
168
- when the plan assigned a deploy method other than `none`.
173
+ folder (e.g. `java -jar target\app.jar`), never to the project root.
174
+ - **Mode-dependent buttons (MANDATORY)**: the controls bar renders per application EXACTLY the
175
+ button set of its `mode` — `process`: Build / Start / Stop, plus Push to Docker when `push`
176
+ is set; `docker`: Build / Push to Docker / Redeploy (NO Start/Stop — Redeploy owns the
177
+ container lifecycle); `k8s`: Build / Update Manifest / Deploy to K8s (NO Start/Stop — pods
178
+ run in the cluster, and the running badge shows a neutral `k8s` phase instead of the local
179
+ port probe). A button whose command is `null` never renders. Rendering a Start/Stop pair for
180
+ a docker/k8s application is a defect — it misleads the user into thinking the app executes
181
+ directly on this machine.
169
182
  - **NO version UI, NO All-apps buttons, NO command substitution**: the page carries NO
170
- version dropdown or any other version choice, and NO Build All / Start All / Stop All
171
- buttons Build/Start/Stop/Deploy are per-application only and always operate on the
172
- CURRENT working tree, running the plan's commands verbatim (no `{VERSION}` or other token
173
- substitution). Never render a version control and never check out code.
183
+ version dropdown or any other version choice, and NO all-application bulk buttons every
184
+ lifecycle button is per-application only and always operates on the CURRENT working tree,
185
+ running the plan's commands verbatim (no `{VERSION}` or other token substitution). Never
186
+ render a version control and never check out code.
174
187
  - **Dockerfile materialization**: before Build, when an app's `dockerfile.content` is set and
175
188
  no file exists at its path in the application folder, the server writes it (never
176
189
  overwriting an existing Dockerfile — mirror of the managed-config rule). Build never
177
- pushes an image; pushing belongs to a `docker` deploy command.
190
+ pushes an image in `process`/`docker` mode — pushing is the Push button's command; the
191
+ `k8s` Build is the one exception (its plan command appends the push so the cluster pulls
192
+ the fresh image).
178
193
  - Develop gate: controls stay disabled for an application that never completed a
179
194
  conductor-develop run — checked via `${CO2_STUDIO_URL}/api/cicd-guard/${CO2_PROJECT_ID}`
180
195
  with the CO2_GUARD_TOKEN from the spawn env (refreshed every 30 s, last answer cached).
@@ -293,24 +293,29 @@ function buildApp(app) {
293
293
  })
294
294
  }
295
295
 
296
- function deployApp(app) {
297
- if (activity.has(app.name) || !app.deploy) return
298
- activity.set(app.name, 'deploying'); lastError.delete(app.name)
299
- runLogged(app, app.deploy, (code) => {
296
+ // One-shot lifecycle commands (deploy / push / redeploy / update-manifest) share one runner:
297
+ // one activity slot per app, exit code surfaced as the app's error. The activity label doubles
298
+ // as the client badge phase while the command runs.
299
+ function runOnce(app, label, cmd) {
300
+ if (activity.has(app.name) || !cmd) return
301
+ activity.set(app.name, label); lastError.delete(app.name)
302
+ runLogged(app, cmd, (code) => {
300
303
  activity.delete(app.name)
301
- if (code !== 0) lastError.set(app.name, `deploy exited ${code} — see log`)
302
- else logLine(app, `deploy OK (${app.deployMethod})`)
304
+ if (code !== 0) lastError.set(app.name, `${label} exited ${code} — see log`)
305
+ else logLine(app, `${label} OK`)
303
306
  })
304
307
  }
308
+ function deployApp(app) { runOnce(app, 'deploying', app.deploy) }
309
+ function pushApp(app) { runOnce(app, 'pushing', app.push) }
310
+ function redeployApp(app) { runOnce(app, 'redeploying', app.redeploy) }
311
+ function updateManifestApp(app) { runOnce(app, 'updating', app.updateManifest) }
305
312
 
313
+ // Start exists only for mode=process apps (docker mode redeploys the container, k8s mode
314
+ // rolls the cluster workload) — the plan carries no start command for the other modes.
306
315
  function startApp(app) {
307
- if (running.get(app.name) || activity.has(app.name)) return
316
+ if (running.get(app.name) || activity.has(app.name) || !app.start) return
308
317
  failed.delete(app.name); lastError.delete(app.name)
309
318
  ensureConfigs(app)
310
- if (app.method === 'container') {
311
- runLogged(app, app.start, () => {})
312
- saveState(); return
313
- }
314
319
  const child = runLogged(app, app.start, (code) => {
315
320
  running.delete(app.name)
316
321
  // A user-requested Stop exits nonzero (killed) — that is not a failure.
@@ -427,7 +432,8 @@ body{font-family:system-ui,sans-serif;margin:0;background:#f6f7f9;color:#1c2128;
427
432
  .dot.running{background:#1c8a4a}.dot.failed{background:#c23b3b}.dot.building,.dot.deploying{background:#c9922a}
428
433
  .bar{display:flex;gap:8px;align-items:center;padding:10px 12px;background:#fff;border-bottom:1px solid #e1e5ea;flex-wrap:wrap}
429
434
  .badge{font-size:10px;text-transform:uppercase;border-radius:99px;padding:2px 8px;color:#fff;background:#8a94a0}
430
- .badge.running{background:#1c8a4a}.badge.failed{background:#c23b3b}.badge.building,.badge.deploying{background:#c9922a}
435
+ .badge.running{background:#1c8a4a}.badge.failed{background:#c23b3b}.badge.k8s{background:#3a6fc4}
436
+ .badge.building,.badge.deploying,.badge.pushing,.badge.redeploying,.badge.updating{background:#c9922a}
431
437
  .url{font-size:12px;color:#1a66d0;text-decoration:underline;word-break:break-all}
432
438
  button{background:#fff;color:#1c2128;border:1px solid #c6cdd6;border-radius:6px;padding:4px 12px;font-size:12px;cursor:pointer}
433
439
  button:hover:enabled{background:#eef1f5}
@@ -491,17 +497,25 @@ function renderBar(){
491
497
  // Fail OPEN — only a definitive "never developed" (false) disables controls; unknown (null)
492
498
  // leaves them enabled so an unverifiable studio never locks the user out (Defect 2c/4).
493
499
  const gate = a.developedOnce !== false
494
- const busy = a.phase==='building'||a.phase==='deploying'
500
+ const mode = a.mode || 'process'
501
+ const busy = a.phase==='building'||a.phase==='deploying'||a.phase==='pushing'||a.phase==='redeploying'||a.phase==='updating'
502
+ // Mode-dependent controls: process → Build/Start/Stop (+ Push when planned);
503
+ // docker → Build/Push/Redeploy; k8s → Build/Update Manifest/Deploy to K8s. Rendering a
504
+ // Start/Stop pair for a docker/k8s app is a defect — it misleads the user into thinking
505
+ // the application executes directly on this machine.
495
506
  document.getElementById('bar').innerHTML = \`
496
507
  <span class="badge \${a.phase}">\${a.phase}\${a.external?' (external)':''}</span>
497
- <span class="meta">\${a.method} · port \${a.port}</span>
498
- <a class="url" href="\${url}" target="_blank" rel="noreferrer">\${url}</a>
508
+ <span class="meta">\${mode} · port \${a.port}</span>
509
+ \${mode!=='k8s'?\`<a class="url" href="\${url}" target="_blank" rel="noreferrer">\${url}</a>\`:''}
499
510
  <span style="flex:1"></span>
500
511
  <button onclick="openConfig()" title="Show this application's runtime configuration files">Config</button>
501
- <button onclick="act('build')" \${!gate||busy||a.phase==='running'?'disabled':''} title="\${a.image?'Builds the app AND the docker image '+a.image+':latest':'Runs the plan build command'}">Build</button>
502
- <button onclick="act('start')" \${!gate||busy||a.phase==='running'?'disabled':''}>Start</button>
503
- <button onclick="act('stop')" \${a.phase!=='running'?'disabled':''}>Stop</button>
504
- \${a.deploy?\`<button onclick="act('deploy')" \${!gate||busy?'disabled':''}>Deploy (\${a.deployMethod})</button>\`:''}\`
512
+ <button onclick="act('build')" \${!gate||busy||(mode==='process'&&a.phase==='running')?'disabled':''} title="\${a.image?'Builds the app AND the docker image '+a.image+':latest':'Runs the plan build command'}">Build</button>
513
+ \${mode==='process'?\`<button onclick="act('start')" \${!gate||busy||a.phase==='running'?'disabled':''}>Start</button>
514
+ <button onclick="act('stop')" \${a.phase!=='running'?'disabled':''}>Stop</button>\`:''}
515
+ \${(mode==='process'&&a.push)||mode==='docker'?\`<button onclick="act('push')" \${!gate||busy||!a.push?'disabled':''}>Push to Docker</button>\`:''}
516
+ \${mode==='docker'?\`<button onclick="act('redeploy')" \${!gate||busy||!a.redeploy?'disabled':''}>Redeploy</button>\`:''}
517
+ \${mode==='k8s'?\`<button onclick="act('update-manifest')" \${!gate||busy||!a.updateManifest?'disabled':''}>Update Manifest</button>
518
+ <button onclick="act('deploy')" \${!gate||busy||!a.deploy?'disabled':''}>Deploy to K8s</button>\`:''}\`
505
519
  const notice = document.getElementById('notice')
506
520
  if (a.developedOnce === false) {
507
521
  notice.textContent = 'This application has not been developed through the workflow yet — run conductor-feature-develop first.'
@@ -659,12 +673,17 @@ const server = http.createServer(async (req, res) => {
659
673
  }
660
674
  if (req.method === 'GET' && req.url === '/api/state') {
661
675
  const apps = await Promise.all(loadConfig().apps.map(async (a) => {
676
+ const mode = a.mode || 'process'
662
677
  const healthy = await probe(a)
663
- const phase = activity.get(a.name) || (healthy ? 'running' : failed.has(a.name) ? 'failed' : 'stopped')
678
+ // k8s apps run in the cluster, not on this machine the local port probe is
679
+ // meaningless, so their idle phase is a neutral 'k8s' badge.
680
+ const phase = activity.get(a.name)
681
+ || (mode === 'k8s' ? 'k8s' : healthy ? 'running' : failed.has(a.name) ? 'failed' : 'stopped')
664
682
  const dev = developInfo(a)
665
683
  return {
666
- name: a.name, method: a.method, port: a.port, phase,
667
- deploy: !!a.deploy, deployMethod: a.deployMethod || 'none',
684
+ name: a.name, mode, port: a.port, phase,
685
+ deploy: !!a.deploy, push: !!a.push, redeploy: !!a.redeploy,
686
+ updateManifest: !!a.updateManifest,
668
687
  image: a.image || null,
669
688
  external: healthy && !running.has(a.name),
670
689
  developedOnce: dev.val, developSource: dev.src,
@@ -689,7 +708,7 @@ const server = http.createServer(async (req, res) => {
689
708
  return json(res, 200, { configs: (app.configs || []).map((c) => readConfig(app, c)) })
690
709
  }
691
710
  const path = (req.url || '').split('?')[0]
692
- const m = path.match(/^\/api\/apps\/([^/]+)\/(build|start|stop|deploy)$/)
711
+ const m = path.match(/^\/api\/apps\/([^/]+)\/(build|start|stop|deploy|push|redeploy|update-manifest)$/)
693
712
  if (m && req.method === 'POST') {
694
713
  const app = loadConfig().apps.find((a) => a.name === decodeURIComponent(m[1]))
695
714
  if (!app) return json(res, 404, { error: 'unknown app' })
@@ -700,6 +719,9 @@ const server = http.createServer(async (req, res) => {
700
719
  if (m[2] === 'build') buildApp(app)
701
720
  else if (m[2] === 'start') startApp(app)
702
721
  else if (m[2] === 'deploy') deployApp(app)
722
+ else if (m[2] === 'push') pushApp(app)
723
+ else if (m[2] === 'redeploy') redeployApp(app)
724
+ else if (m[2] === 'update-manifest') updateManifestApp(app)
703
725
  else await stopApp(app)
704
726
  return json(res, 200, { ok: true })
705
727
  }
@@ -743,9 +765,12 @@ Top-level `"generatedAt"` is an ISO-8601 UTC timestamp **stamped fresh on every
743
765
  (Defect 1): the running server pins the value it booted with and flags a "restart required" banner
744
766
  once a regeneration moves it forward, so a stale in-place-replaced process is self-diagnosing. It
745
767
  MUST change on each run — use the actual generation time (e.g. `new Date().toISOString()`).
746
- `deployMethod`/`deploy` come from the plan's Deploy columns (`deploy` is `null` when the method
747
- is `none`). `infra[].url` is `null` for a headless service (Console URL cell `-`); emit
748
- `"infra": []` when the plan declares no supporting services.
768
+ `mode` is the plan's Mode column (`process|docker|k8s`) and decides the rendered button set;
769
+ `start`/`stop`/`push`/`redeploy`/`updateManifest`/`deploy` come from their plan columns and are
770
+ `null` wherever the plan cell is `-` (a `null` command's button never renders — `process` apps
771
+ carry start/stop and optionally push; `docker` apps carry push/redeploy; `k8s` apps carry
772
+ updateManifest/deploy). `infra[].url` is `null` for a headless service (Console URL cell `-`);
773
+ emit `"infra": []` when the plan declares no supporting services.
749
774
 
750
775
  Docker fields (from the plan's `## Docker` / `## Dockerfiles` sections): top-level
751
776
  `"docker": { "registry": "<repo>" }` or `null` when the plan is not dockerized. Per app,
@@ -776,13 +801,15 @@ verbatim (this is a local dev CI/CD app).
776
801
  {
777
802
  "name": "my-app",
778
803
  "folder": "my-app",
779
- "method": "process",
804
+ "mode": "k8s",
780
805
  "port": 3000,
781
- "build": "rm -rf dist && pnpm install && pnpm build && docker build --no-cache -t registry.example.com/team/my-app:latest .",
782
- "start": "node dist/index.js",
783
- "stop": "process-kill",
784
- "deployMethod": "kubernetes",
785
- "deploy": "kubectl apply -f my-app/k8s/",
806
+ "build": "rm -rf dist && pnpm install && pnpm build && docker build --no-cache -t registry.example.com/team/my-app:latest . && docker push registry.example.com/team/my-app:latest",
807
+ "start": null,
808
+ "stop": null,
809
+ "push": null,
810
+ "redeploy": null,
811
+ "updateManifest": "kubectl apply -f k8s/",
812
+ "deploy": "kubectl rollout restart deployment/my-app",
786
813
  "health": "http://127.0.0.1:3000/",
787
814
  "image": "registry.example.com/team/my-app",
788
815
  "dockerfile": {
@@ -6,11 +6,14 @@ description: >
6
6
  Infer a local-deployment plan for every custom application of a CO2 project and write it to
7
7
  cicd/CICD_PLAN.md for human review in Compound Context Studio. Reads the project's
8
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
9
+ application's context/specification/SPECIFICATION.md, then decides per application its
10
+ EXECUTION MODE `process` (runs directly on this machine: Build / Start / Stop buttons, plus
11
+ Push-to-Docker when a registry is configured), `docker` (deployed as a docker container on
12
+ this machine: Build / Push-to-Docker / Redeploy buttons), or `k8s` (deployed to Kubernetes:
13
+ Build / Update-Manifest / Deploy-to-K8s buttons) with the mode-appropriate commands, port
14
+ and health-check URL, plus a port for the generated CI/CD app itself. For every k8s-mode
15
+ application it TRIGGERS the depgen-k8s skill to generate the required Dockerfile + manifests
16
+ into `<app_folder>/k8s/` before the plan is written. When Docker AND a docker repo/registry are both
14
17
  configured (DEVTOOL.md + ENVIRONMENT.md), it additionally plans the docker-image build path:
15
18
  per-application image names under the repo, Build commands extended with
16
19
  `docker build --no-cache -t <image>:latest` (clean image build, no version token — the generated
@@ -53,7 +56,7 @@ If the argument is missing or the file does not exist, STOP and tell the user to
53
56
 
54
57
  | Logical file | Path |
55
58
  |---|---|
56
- | Plan request | `<argument>` |
59
+ | Plan request | `<argument>` (may carry a `## User Guidance` section — authoritative human input) |
57
60
  | Environment | `ENVIRONMENT.md` (project root) |
58
61
  | Dev tools | `DEVTOOL.md` (project root) |
59
62
  | Project context | `CLAUDE.md` (project root; `source/CLAUDE.md` fallback) |
@@ -65,6 +68,13 @@ If the argument is missing or the file does not exist, STOP and tell the user to
65
68
  ## Phase 1 — Gather
66
69
 
67
70
  1. Read the plan-request markdown; its `## Applications` list is the authoritative application set.
71
+ 1a. **User guidance**: when the plan-request markdown carries a `## User Guidance` section, treat
72
+ its content as AUTHORITATIVE human input — honor it over inferred defaults wherever the two
73
+ conflict (e.g. a forced deployment method, specific build/start/deploy commands, target hosts,
74
+ ports). If the guidance contains `shared_context/...` markdown links, read those files (paths
75
+ relative to the project root) as additional context. Record every place guidance overrode
76
+ inference in the plan's `## Rationale` section (e.g. "container deployment per user guidance").
77
+ No `## User Guidance` section → plan exactly as before.
68
78
  2. Read `ENVIRONMENT.md` (registries, credentials, external services) and `DEVTOOL.md`
69
79
  (installed tools + paths: node/pnpm/docker/kubectl/python versions).
70
80
  2a. **Docker facts**: record whether Docker is installed (DEVTOOL.md) AND whether
@@ -99,8 +109,23 @@ If the argument is missing or the file does not exist, STOP and tell the user to
99
109
  root. All paths inside build/start/stop/deploy commands must therefore be relative to the
100
110
  application folder (`mvn -q package`, `java -jar target\app.jar`) — never prefixed with the
101
111
  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).
112
+ - **Execution mode (drives the generated app's BUTTON SET)** decide per application HOW this
113
+ machine runs/ships it: `process` | `docker` | `k8s`. Honor `## User Guidance` first; else
114
+ infer from ENVIRONMENT.md/DEVTOOL.md:
115
+ - `k8s` — the deployment target is Kubernetes (kubectl + a cluster/kubeconfig configured, or
116
+ guidance says so). Requires Docker + a registry (the cluster pulls the pushed image).
117
+ Buttons: **Build / Update Manifest / Deploy to K8s** — no Start/Stop (pods run in the
118
+ cluster, not on this machine).
119
+ - `docker` — the application runs on this machine AS a docker container (Docker installed
120
+ and the container is the run mode). Buttons: **Build / Push to Docker / Redeploy** — no
121
+ bare Start/Stop (Redeploy owns the container lifecycle).
122
+ - `process` — bare local process, the dev-VM default. Buttons: **Build / Start / Stop**,
123
+ plus **Push to Docker** only when the docker-image path (below) is active.
124
+ - **depgen-k8s trigger (k8s mode only, MANDATORY)**: for EVERY k8s-mode application, invoke the
125
+ depgen-k8s skill BEFORE writing the plan —
126
+ `Skill(skill: "co2-skills:depgen-k8s", args: "<application name>")` — so the required
127
+ Dockerfile and Kubernetes manifests exist in `<app_folder>/k8s/`; the plan's Update-Manifest/
128
+ Deploy commands reference those generated files. Never hand-author k8s manifests here.
104
129
  - **Build command (ALWAYS a CLEAN build)**: the Build button must produce a from-scratch build so
105
130
  no stale artifact from a previous run can ever be started or shipped — use the stack's CLEAN
106
131
  lifecycle, never an incremental one:
@@ -113,21 +138,32 @@ If the argument is missing or the file does not exist, STOP and tell the user to
113
138
  - Laravel/PHP with a front-end build → `rm -rf public/build && composer install && npm ci && npm run build`;
114
139
  plain PHP with no build step → `composer install`.
115
140
  Use tool paths from DEVTOOL.md when the tool is not on PATH.
116
- - **Start command**: the stack's production-ish local start (e.g. `node dist/index.js`,
117
- `java -jar target/<artifact>.jar`, `php artisan serve --port <port>`); `docker build`/
118
- `docker run` pair when method=container. For jar-based stacks, resolve the EXACT artifact
141
+ - **Start command (`process` mode only — `-` for docker/k8s)**: the stack's production-ish
142
+ local start (e.g. `node dist/index.js`, `java -jar target/<artifact>.jar`,
143
+ `php artisan serve --port <port>`). For jar-based stacks, resolve the EXACT artifact
119
144
  filename — read the pom/gradle build config: a Maven `<finalName>` means
120
145
  `target/<finalName>.jar` with NO version suffix (Spring Boot repackage keeps that name and
121
146
  leaves a `.jar.original` beside it — never reference the `.original`). Never guess
122
147
  `<app>-<version>.jar` and never use a `target/*.jar` glob (shell-dependent, breaks on
123
148
  Windows cmd).
124
- - **Stop**: `process-kill` for bare processes; `docker stop <name>` for containers.
125
- - **Deploy** (method + command): only when ENVIRONMENT.md/DEVTOOL.md show a deployment target
126
- `kubernetes` (`kubectl apply -f <app>/k8s/` when kubectl + a cluster/kubeconfig appear),
127
- `docker` (`docker run`/`docker compose up -d` when the image is the deliverable),
128
- `web-server` (copy the built artifact into the configured web/app server, e.g. nginx html
129
- root or Tomcat webapps, when one is configured). Default **`none`** a bare local process
130
- needs no deploy step beyond Start; never invent a target the config does not name.
149
+ - **Stop (`process` mode only `-` for docker/k8s)**: `process-kill`.
150
+ - **Per-mode lifecycle commands** (each recorded in its own plan column; `-` when the mode does
151
+ not use it the generated app renders ONLY the buttons whose command exists):
152
+ - **Push** (`docker push <image>:latest`) `docker` mode always; `process` mode only when
153
+ the docker-image path is active (the Build already produced the image). `-` for k8s
154
+ (pushing is folded into the k8s Build so the cluster can pull the fresh image).
155
+ - **Redeploy** (`docker` mode) replace the running container with the freshly built
156
+ `latest` image: `docker rm -f <container> ; docker run -d --name <container>
157
+ -p <port>:<port> <env/volume flags> <image>:latest` (or `docker compose up -d` when a
158
+ compose file is the run mode). The container name defaults to the app folder name.
159
+ - **Update Manifest** (`k8s` mode) — apply the depgen-k8s-generated manifests to the
160
+ cluster: `kubectl apply -f k8s/` (namespace flag when ENVIRONMENT.md names one).
161
+ - **Deploy** (`k8s` mode) — roll the workload onto the freshly pushed image:
162
+ `kubectl rollout restart deployment/<app> -n <ns>` (match the deployment name in the
163
+ generated manifests). For `process`/`docker` the Deploy column is `-` — those modes ship
164
+ via Push/Redeploy, and a bare local process needs no deploy step beyond Start.
165
+ - **k8s Build** additionally appends the push — `<app build> && docker build --no-cache
166
+ -t <image>:latest . && docker push <image>:latest` — the cluster pulls from the registry.
131
167
  - **Docker image** (only when Gather 2a found Docker + a docker repo — otherwise skip this
132
168
  bullet entirely):
133
169
  - **Image name**: `<repo>/<app_folder>` — the application folder name, lowercased, with
@@ -138,9 +174,10 @@ If the argument is missing or the file does not exist, STOP and tell the user to
138
174
  clean build too, so no stale layer survives. Commands carry NO `{VERSION}` token or
139
175
  any other placeholder — the generated CI/CD app runs them verbatim; only the current
140
176
  working tree is ever built and images are always tagged `latest`.
141
- Build never pushes — pushing belongs to a `docker` Deploy method.
142
- - **Container start/deploy**: when method=container, reference `<image>:latest` in the
143
- start/deploy commands too, so the freshly built tag is what runs.
177
+ Build never pushes in `process`/`docker` mode — pushing is the Push button's job
178
+ (k8s Build is the one exception: it appends the push so the cluster can pull).
179
+ - **Container redeploy**: in `docker` mode, reference `<image>:latest` in the Redeploy
180
+ command so the freshly built tag is what runs.
144
181
  - **Dockerfile**: if the application folder already has a `Dockerfile`, use it as-is
145
182
  (`existing`). Otherwise author a stack-appropriate Dockerfile (node / spring-boot /
146
183
  laravel — same stack detection as depgen-k8s) into the plan's `## Dockerfiles` section
@@ -222,9 +259,13 @@ inner ```env config block renders — the plan file itself uses a normal 3-backt
222
259
 
223
260
  ## Deployment Decisions
224
261
 
225
- | Application | Folder | Method | Port | Build | Start | Stop | Deploy Method | Deploy | Health |
226
- |---|---|---|---|---|---|---|---|---|---|
227
- | <name> | <folder> | process|container | <port> | `<cmd>` | `<cmd>` | process-kill|docker stop | none|web-server|docker|kubernetes | `<cmd or ->` | <url> |
262
+ | Application | Folder | Mode | Port | Build | Start | Stop | Push | Redeploy | Update Manifest | Deploy | Health |
263
+ |---|---|---|---|---|---|---|---|---|---|---|---|
264
+ | <name> | <folder> | process|docker|k8s | <port> | `<cmd>` | `<cmd or ->` | process-kill|- | `<cmd or ->` | `<cmd or ->` | `<cmd or ->` | `<cmd or ->` | <url> |
265
+
266
+ > **Mode → buttons**: the generated CI/CD app renders per application exactly the buttons whose
267
+ > command exists — `process`: Build / Start / Stop (+ Push to Docker when Push is set);
268
+ > `docker`: Build / Push to Docker / Redeploy; `k8s`: Build / Update Manifest / Deploy to K8s.
228
269
 
229
270
  > **Running-detection**: the generated CI/CD app determines "is it running?" by a TCP connect to
230
271
  > each application's **Port** on both IPv4 (`127.0.0.1`) and IPv6 (`::1`) loopback plus the
@@ -1 +1 @@
1
- import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-CpZ6vY3L.js";import{p as f}from"./chunk-JWPE2WC7-C1Gudu3M.js";import{_ as n,l as o}from"./index-DU7Xwr-g.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-DmQsQ2TU.js";var v=d().RailroadAbnf.parser.LangiumParser,l=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),i=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:i,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return l(e.element);case"AbnfOptionalGroup":return{type:"optional",element:l(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:l(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},w={parser:R,db:a,renderer:u,styles:p};export{w as diagram};
1
+ import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-B3bjiMAY.js";import{p as f}from"./chunk-JWPE2WC7-BZ9dVylx.js";import{_ as n,l as o}from"./index-BWq7zKXB.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-Dzy0NABg.js";var v=d().RailroadAbnf.parser.LangiumParser,l=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),i=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:i,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return l(e.element);case"AbnfOptionalGroup":return{type:"optional",element:l(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:l(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},w={parser:R,db:a,renderer:u,styles:p};export{w as diagram};
@@ -1 +1 @@
1
- import{Y as ln,$ as an,a0 as y,a1 as tn,a2 as F,a3 as q,a4 as _,a5 as un,a6 as rn,a7 as J,a8 as o,a9 as j,aa as sn,ab as on,ac as fn}from"./index-DU7Xwr-g.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(fn(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-an,c=A.apply(this,arguments)-an,B=un(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(un(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<rn)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(on((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-rn/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
1
+ import{Y as ln,$ as an,a0 as y,a1 as tn,a2 as F,a3 as q,a4 as _,a5 as un,a6 as rn,a7 as J,a8 as o,a9 as j,aa as sn,ab as on,ac as fn}from"./index-BWq7zKXB.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(fn(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-an,c=A.apply(this,arguments)-an,B=un(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(un(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<rn)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(on((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-rn/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};