@rashidee/co2 1.3.12 → 1.3.14

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 (76) hide show
  1. package/dist/.co2-dat/app.db +0 -0
  2. package/dist/.co2-dat/app.db-shm +0 -0
  3. package/dist/.co2-dat/app.db-wal +0 -0
  4. package/dist/index.js +145 -9
  5. package/package.json +1 -1
  6. package/plugin/.claude-plugin/marketplace.json +1 -1
  7. package/plugin/.claude-plugin/plugin.json +1 -1
  8. package/plugin/README.md +3 -1
  9. package/plugin/SKILLS.md +5 -2
  10. package/plugin/skills/conductor-feature-develop/SKILL.md +13 -0
  11. package/plugin/skills/conductor-feature-prepare/SKILL.md +39 -11
  12. package/plugin/skills/specgen-custom/SKILL.md +442 -0
  13. package/plugin/skills/specgen-custom/references/spec-template.md +271 -0
  14. package/plugin/skills/specgen-custom/references/stack-doc-template.md +154 -0
  15. package/static/assets/{abnfDiagram-VRR7QNED-Qm0Wk_H_.js → abnfDiagram-VRR7QNED-D8RafkEP.js} +1 -1
  16. package/static/assets/{arc-Cfk6Kt0D.js → arc-Coo8djJL.js} +1 -1
  17. package/static/assets/{architectureDiagram-ZJ3FMSHR-DQTlZaUK.js → architectureDiagram-ZJ3FMSHR-D7fGTL3b.js} +1 -1
  18. package/static/assets/{blockDiagram-677ZJIJ3-D-4ibdoX.js → blockDiagram-677ZJIJ3-CjI7s86S.js} +1 -1
  19. package/static/assets/{c4Diagram-LMCZKHZV-CHuqcdJm.js → c4Diagram-LMCZKHZV-Ao3qCcFi.js} +1 -1
  20. package/static/assets/channel-Ci11n54j.js +1 -0
  21. package/static/assets/{chunk-2Q5K7J3B-MB9pWQe1.js → chunk-2Q5K7J3B-lUK_s3cG.js} +1 -1
  22. package/static/assets/{chunk-32BRIVSS-BttYlwU1.js → chunk-32BRIVSS-PA889NNs.js} +1 -1
  23. package/static/assets/{chunk-5VM5RSS4-oyt0Tagd.js → chunk-5VM5RSS4-CxACsgKF.js} +1 -1
  24. package/static/assets/{chunk-EX3LRPZG-DaTxdTWl.js → chunk-EX3LRPZG-BuSTKl3j.js} +1 -1
  25. package/static/assets/{chunk-JWPE2WC7-CNgDxPAD.js → chunk-JWPE2WC7-CuKFDdbM.js} +1 -1
  26. package/static/assets/{chunk-MOJQB5TN-CoAudO3B.js → chunk-MOJQB5TN-CVXUJZuG.js} +1 -1
  27. package/static/assets/{chunk-RYQCIY6F-BrkDEEha.js → chunk-RYQCIY6F-DrMs0YPQ.js} +1 -1
  28. package/static/assets/{chunk-V7JOEXUC-D14rGvBO.js → chunk-V7JOEXUC-DJopV6W1.js} +1 -1
  29. package/static/assets/{chunk-VR4S4FIN-CAtas2vF.js → chunk-VR4S4FIN-CcNBYiui.js} +1 -1
  30. package/static/assets/{chunk-XXDRQBXY-DcVAnNUQ.js → chunk-XXDRQBXY-CCFhxbN4.js} +1 -1
  31. package/static/assets/classDiagram-OUVF2IWQ-D8JGj9_e.js +1 -0
  32. package/static/assets/classDiagram-v2-EOCWNBFH-D8JGj9_e.js +1 -0
  33. package/static/assets/{cose-bilkent-JH36ORCC-B7y4MI6Z.js → cose-bilkent-JH36ORCC-DXoH0ywa.js} +1 -1
  34. package/static/assets/{cynefin-VYW2F7L2-D2mnk-iJ.js → cynefin-VYW2F7L2-DH8W0rE6.js} +1 -1
  35. package/static/assets/{cynefinDiagram-TSTJHNR4-DNzQJhG7.js → cynefinDiagram-TSTJHNR4-BW_IHnv8.js} +1 -1
  36. package/static/assets/{dagre-VKFMJZFB-NMSyTo8N.js → dagre-VKFMJZFB-Biw6PsWK.js} +1 -1
  37. package/static/assets/{diagram-FQU43EPY-CdqS5Ike.js → diagram-FQU43EPY-TtNOYp5j.js} +1 -1
  38. package/static/assets/{diagram-G47NLZAW-Bsb4T5JI.js → diagram-G47NLZAW-CQk6Jt_6.js} +1 -1
  39. package/static/assets/{diagram-NH7WQ7WH-Z-PyzBSC.js → diagram-NH7WQ7WH-DTY1BO9B.js} +1 -1
  40. package/static/assets/{diagram-OA4YK3LP-BZBTYbxg.js → diagram-OA4YK3LP-DEnO6Eyl.js} +1 -1
  41. package/static/assets/{diagram-WEI45ONY-BL2LA5H_.js → diagram-WEI45ONY-WKF4avWj.js} +1 -1
  42. package/static/assets/{ebnfDiagram-CCIWWBDH-BGtukMeo.js → ebnfDiagram-CCIWWBDH-K4uJ1i6v.js} +1 -1
  43. package/static/assets/{erDiagram-Q63AITRT-B6OJ4YCl.js → erDiagram-Q63AITRT-B4wRa3fY.js} +1 -1
  44. package/static/assets/{flowDiagram-23GEKE2U-BBkMgJ1i.js → flowDiagram-23GEKE2U-SnSUCnHl.js} +1 -1
  45. package/static/assets/{ganttDiagram-NO4QXBWP-Cv8KHFBD.js → ganttDiagram-NO4QXBWP-CsdfAP2-.js} +1 -1
  46. package/static/assets/{gitGraphDiagram-IHSO6WYX-hYxDNjWx.js → gitGraphDiagram-IHSO6WYX-C8xab2KC.js} +1 -1
  47. package/static/assets/{index-DKI3BgkF.css → index-D58A6Kf1.css} +1 -1
  48. package/static/assets/{index-DnYGyYET.js → index-D74-2ULy.js} +93 -93
  49. package/static/assets/{infoDiagram-FWYZ7A6U-WuQIiedv.js → infoDiagram-FWYZ7A6U-CjUSYyJV.js} +1 -1
  50. package/static/assets/{ishikawaDiagram-FXEZZL3T-BJGOD-IY.js → ishikawaDiagram-FXEZZL3T-DGEbhYY4.js} +1 -1
  51. package/static/assets/{journeyDiagram-5HDEW3XC-Ba_NXseS.js → journeyDiagram-5HDEW3XC-Bn8WZeF-.js} +1 -1
  52. package/static/assets/{kanban-definition-HUTT4EX6-C5RMm9uF.js → kanban-definition-HUTT4EX6-DD9QNPz9.js} +1 -1
  53. package/static/assets/{linear-CLKppNoj.js → linear-CREoNC-Z.js} +1 -1
  54. package/static/assets/{mindmap-definition-LN4V7U3C-ClSG_qmQ.js → mindmap-definition-LN4V7U3C-BLCKbp6n.js} +1 -1
  55. package/static/assets/{pegDiagram-2B236MQR-C_JEZqk3.js → pegDiagram-2B236MQR-DLyLfiFi.js} +1 -1
  56. package/static/assets/{pieDiagram-ENE6RG2P-D0lx7wDi.js → pieDiagram-ENE6RG2P-B7v1-Mow.js} +1 -1
  57. package/static/assets/{quadrantDiagram-ABIIQ3AL-BBKy0PVs.js → quadrantDiagram-ABIIQ3AL-Dnt728yI.js} +1 -1
  58. package/static/assets/{railroadDiagram-RFXS5EU6-C1Badu5q.js → railroadDiagram-RFXS5EU6-BZ0m-G9m.js} +1 -1
  59. package/static/assets/{requirementDiagram-TGXJPOKE-1j59jBQH.js → requirementDiagram-TGXJPOKE-3SCMjIUR.js} +1 -1
  60. package/static/assets/{sankeyDiagram-HTMAVEWB-BLR5S8aZ.js → sankeyDiagram-HTMAVEWB-BoK8Wh3S.js} +1 -1
  61. package/static/assets/{sequenceDiagram-DBY2YBRQ-BlqdK4qm.js → sequenceDiagram-DBY2YBRQ-B89bqSYK.js} +1 -1
  62. package/static/assets/{sizeCapture-X5ZJPWSS-C27ndGIT.js → sizeCapture-X5ZJPWSS-BVTk4iE2.js} +1 -1
  63. package/static/assets/{stateDiagram-2N3HPSRC-DOu4536_.js → stateDiagram-2N3HPSRC-Q5DLz7JU.js} +1 -1
  64. package/static/assets/stateDiagram-v2-6OUMAXLB-CU_q9WoS.js +1 -0
  65. package/static/assets/{swimlanes-5IMT3BWC-BpGitR9A.js → swimlanes-5IMT3BWC-1LqBZGZu.js} +2 -2
  66. package/static/assets/swimlanesDiagram-G3AALYLV-CilhyDV0.js +8 -0
  67. package/static/assets/{timeline-definition-FHXFAJF6-JIvdjH7z.js → timeline-definition-FHXFAJF6-Bk8mm9PQ.js} +1 -1
  68. package/static/assets/{vennDiagram-L72KCM5P-DulnqLWm.js → vennDiagram-L72KCM5P-qS4AIIXn.js} +1 -1
  69. package/static/assets/{wardleyDiagram-EHGQE667--MhZNL8M.js → wardleyDiagram-EHGQE667-BjnQ0zER.js} +1 -1
  70. package/static/assets/{xychartDiagram-FW5EYKEG-CjnNgnMQ.js → xychartDiagram-FW5EYKEG-Xtl5bYjl.js} +1 -1
  71. package/static/index.html +2 -2
  72. package/static/assets/channel-H9DRrduO.js +0 -1
  73. package/static/assets/classDiagram-OUVF2IWQ-DRNPM383.js +0 -1
  74. package/static/assets/classDiagram-v2-EOCWNBFH-DRNPM383.js +0 -1
  75. package/static/assets/stateDiagram-v2-6OUMAXLB-DvP1ZCp8.js +0 -1
  76. package/static/assets/swimlanesDiagram-G3AALYLV-B5cf18Ey.js +0 -8
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.12",
6812
+ version: "1.3.14",
6813
6813
  description: "Compound Context Studio \u2014 self-hosted team context authoring for CO2 projects",
6814
6814
  type: "module",
6815
6815
  license: "MIT",
@@ -25398,7 +25398,7 @@ function scanProject(db, workspacePath, folderName) {
25398
25398
  import { join as join22, relative as relative2 } from "path";
25399
25399
 
25400
25400
  // ../server/src/features/workflow/service.ts
25401
- import { existsSync as existsSync20, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync13 } from "fs";
25401
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, readFileSync as readFileSync15, statSync as statSync6, writeFileSync as writeFileSync13 } from "fs";
25402
25402
  import { join as join21, relative, resolve as resolve2, sep } from "path";
25403
25403
  var ACTIVE_RUN_STATUSES = ["pending", "in_progress"];
25404
25404
  function svcError14(code, status) {
@@ -25855,6 +25855,125 @@ function rescanWorkflowRun(db, workspacePath, workflowMarkdownId, watcherInterva
25855
25855
  }
25856
25856
  return getWorkflowRun(db, workflowMarkdownId, workspacePath);
25857
25857
  }
25858
+ var DISCOVERABLE_CONDUCTOR_TYPES = ["prepare", "develop", "quality"];
25859
+ function rescanProjectWorkflows(db, workspacePath, projectId, user, watcherIntervalMs) {
25860
+ const project = db.select().from(projects).where(eq(projects.id, projectId)).get();
25861
+ if (!project) throw svcError14("PROJECT_NOT_FOUND", 404);
25862
+ const projectDir2 = join21(workspacePath, project.workspaceFolderName);
25863
+ const nonTerminal = db.select({ id: conductorRuns.id, wfmId: conductorRuns.workflowMarkdownId }).from(conductorRuns).where(
25864
+ and(
25865
+ eq(conductorRuns.projectId, projectId),
25866
+ inArray(conductorRuns.status, [...ACTIVE_RUN_STATUSES])
25867
+ )
25868
+ ).all();
25869
+ const rescannedRuns = nonTerminal.length;
25870
+ const wfmIds = new Set(nonTerminal.map((r) => r.wfmId).filter((x) => x !== null));
25871
+ for (const wfmId of wfmIds) rescanWorkflowRun(db, workspacePath, wfmId, watcherIntervalMs);
25872
+ const apps = db.select({ id: applications.id, name: applications.name }).from(applications).where(and(eq(applications.projectId, projectId), eq(applications.isDeleted, 0))).all();
25873
+ const currentVersionId = ensureBaselineVersion(db, projectId, user.id);
25874
+ const semver = db.select({ semver: prdVersions.semver }).from(prdVersions).where(eq(prdVersions.id, currentVersionId)).get()?.semver ?? null;
25875
+ let discoveredRuns = 0;
25876
+ const now = (/* @__PURE__ */ new Date()).toISOString();
25877
+ for (const app of apps) {
25878
+ for (const conductorType of DISCOVERABLE_CONDUCTOR_TYPES) {
25879
+ const trackingFilePath = resolveRunTrackingFilePath(projectDir2, app.name, conductorType);
25880
+ const fileStatus = parseTrackingStatus(trackingFilePath);
25881
+ if (fileStatus === "unknown") continue;
25882
+ const aliases = conductorType === "quality" ? ["quality", "defect"] : [conductorType];
25883
+ const known = db.select({
25884
+ status: conductorRuns.status,
25885
+ prdVersionId: conductorRuns.prdVersionId,
25886
+ createdAt: conductorRuns.createdAt,
25887
+ completedAt: conductorRuns.completedAt
25888
+ }).from(conductorRuns).where(
25889
+ and(
25890
+ eq(conductorRuns.applicationId, app.id),
25891
+ inArray(conductorRuns.conductorType, aliases)
25892
+ )
25893
+ ).all();
25894
+ const knownForVersion = known.filter(
25895
+ (r) => r.prdVersionId === null || r.prdVersionId === currentVersionId
25896
+ );
25897
+ if (fileStatus === "COMPLETED" && knownForVersion.some((r) => r.status === "completed"))
25898
+ continue;
25899
+ if (fileStatus === "IN PROGRESS" && known.some((r) => r.status === "pending" || r.status === "in_progress"))
25900
+ continue;
25901
+ if (fileStatus === "FAILED" && knownForVersion.some((r) => r.status === "failed")) continue;
25902
+ let mtimeMs;
25903
+ try {
25904
+ mtimeMs = statSync6(trackingFilePath).mtimeMs;
25905
+ } catch {
25906
+ continue;
25907
+ }
25908
+ const latestKnownMs = known.reduce(
25909
+ (acc, r) => Math.max(
25910
+ acc,
25911
+ Date.parse(r.createdAt) || 0,
25912
+ r.completedAt ? Date.parse(r.completedAt) || 0 : 0
25913
+ ),
25914
+ 0
25915
+ );
25916
+ if (latestKnownMs > 0 && mtimeMs <= latestKnownMs) continue;
25917
+ const mtimeIso = new Date(mtimeMs).toISOString();
25918
+ const wfm = db.insert(workflowMarkdowns).values({
25919
+ projectId,
25920
+ conductorType,
25921
+ // The master the discovery was read from — Run History's path link previews it.
25922
+ filePath: relative(projectDir2, trackingFilePath).replace(/\\/g, "/"),
25923
+ versionLabel: semver,
25924
+ createdBy: user.id,
25925
+ createdByName: user.username,
25926
+ createdAt: now,
25927
+ updatedAt: now
25928
+ }).returning({ id: workflowMarkdowns.id }).get();
25929
+ const run = db.insert(conductorRuns).values({
25930
+ projectId,
25931
+ applicationId: app.id,
25932
+ conductorType,
25933
+ prdVersionId: currentVersionId,
25934
+ trackingFilePath,
25935
+ status: fileStatus === "COMPLETED" ? "completed" : fileStatus === "FAILED" ? "failed" : "in_progress",
25936
+ startedBy: user.id,
25937
+ startedAt: mtimeIso,
25938
+ completedAt: fileStatus === "IN PROGRESS" ? null : mtimeIso,
25939
+ workflowMarkdownId: wfm.id,
25940
+ priorPhaseSatisfied: 1,
25941
+ gitOperationStatus: "skipped",
25942
+ // terminal execution — the user owns its git flow
25943
+ createdAt: now
25944
+ }).returning({ id: conductorRuns.id }).get();
25945
+ discoveredRuns++;
25946
+ if (fileStatus === "COMPLETED") {
25947
+ try {
25948
+ registerConductorArtifacts(db, workspacePath, run.id);
25949
+ } catch {
25950
+ }
25951
+ syncDefectRunStatuses(db, run.id);
25952
+ } else if (fileStatus === "IN PROGRESS") {
25953
+ const appName = app.name;
25954
+ startWatcher(
25955
+ db,
25956
+ run.id,
25957
+ () => resolveRunTrackingFilePath(projectDir2, appName, conductorType),
25958
+ user.id,
25959
+ watcherIntervalMs,
25960
+ (id, terminalStatus) => {
25961
+ lastArtifactSweepAt.delete(id);
25962
+ if (terminalStatus === "COMPLETED") {
25963
+ try {
25964
+ registerConductorArtifacts(db, workspacePath, id);
25965
+ } catch {
25966
+ }
25967
+ syncDefectRunStatuses(db, id);
25968
+ }
25969
+ },
25970
+ (id) => sweepRunArtifactsThrottled(db, workspacePath, id)
25971
+ );
25972
+ }
25973
+ }
25974
+ }
25975
+ return { rescannedRuns, discoveredRuns };
25976
+ }
25858
25977
  function getWorkflowRun(db, workflowMarkdownId, workspacePath) {
25859
25978
  const wfm = db.select().from(workflowMarkdowns).where(eq(workflowMarkdowns.id, workflowMarkdownId)).get();
25860
25979
  if (!wfm) throw svcError14("RUN_NOT_FOUND", 404);
@@ -25952,7 +26071,9 @@ function toDetail(db, projectFolder, wfm, workspacePath) {
25952
26071
  status: rollupStatus(runs.map((r) => r.status)),
25953
26072
  applications: appBreakdown,
25954
26073
  filePath: wfm.filePath,
25955
- executeCommand: buildExecuteCommand(projectFolder, wfm.filePath),
26074
+ // Only generated markdowns (workflow/<user>/<ts>.md) carry an Execute prompt — a row
26075
+ // discovered from a tracking master (rescanProjectWorkflows) has nothing to re-run.
26076
+ executeCommand: wfm.filePath.startsWith("workflow/") ? buildExecuteCommand(projectFolder, wfm.filePath) : "",
25956
26077
  versionLabel: wfm.versionLabel,
25957
26078
  gitOperationStatus: git4?.gitOperationStatus ?? "not_started",
25958
26079
  gitBranchCreated: git4?.gitBranchCreated ?? null,
@@ -26469,7 +26590,7 @@ function projectRoutes(db, workspacePath) {
26469
26590
  }
26470
26591
 
26471
26592
  // ../server/src/features/shared-context/service.ts
26472
- import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync16, readdirSync as readdirSync4, statSync as statSync6, writeFileSync as writeFileSync14 } from "fs";
26593
+ import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync16, readdirSync as readdirSync4, statSync as statSync7, writeFileSync as writeFileSync14 } from "fs";
26473
26594
  import { join as join23, resolve as resolve3, sep as sep2 } from "path";
26474
26595
  function svcError16(code, status) {
26475
26596
  return Object.assign(new Error(code), { code, status });
@@ -26527,7 +26648,7 @@ function walk(dir, relBase) {
26527
26648
  children: walk(join23(dir, entry.name), rel)
26528
26649
  });
26529
26650
  } else if (entry.isFile()) {
26530
- const stat = statSync6(join23(dir, entry.name));
26651
+ const stat = statSync7(join23(dir, entry.name));
26531
26652
  nodes.push({
26532
26653
  name: entry.name,
26533
26654
  path: rel,
@@ -26566,7 +26687,7 @@ function uploadFile(db, workspacePath, projectId, folder, file) {
26566
26687
  function getFileContent(db, workspacePath, projectId, relPath) {
26567
26688
  const root = resolveSharedRoot(db, workspacePath, projectId);
26568
26689
  const full = safeResolve(root, relPath);
26569
- if (!existsSync21(full) || !statSync6(full).isFile()) throw svcError16("NOT_FOUND", 404);
26690
+ if (!existsSync21(full) || !statSync7(full).isFile()) throw svcError16("NOT_FOUND", 404);
26570
26691
  const name = relPath.split("/").pop() ?? relPath;
26571
26692
  return { mimeType: mimeForName(name), bytes: readFileSync16(full), name };
26572
26693
  }
@@ -26574,7 +26695,7 @@ function saveMarkdown(db, workspacePath, projectId, relPath, content) {
26574
26695
  if (mimeForName(relPath) !== "text/markdown") throw svcError16("NOT_MARKDOWN", 400);
26575
26696
  const root = resolveSharedRoot(db, workspacePath, projectId);
26576
26697
  const full = safeResolve(root, relPath);
26577
- if (!existsSync21(full) || !statSync6(full).isFile()) throw svcError16("NOT_FOUND", 404);
26698
+ if (!existsSync21(full) || !statSync7(full).isFile()) throw svcError16("NOT_FOUND", 404);
26578
26699
  writeFileSync14(full, content, "utf8");
26579
26700
  }
26580
26701
 
@@ -27149,6 +27270,21 @@ function workflowRoutes(db, workspacePath) {
27149
27270
  return errorResponse16(c, e);
27150
27271
  }
27151
27272
  });
27273
+ app.post("/rescan-all", (c) => {
27274
+ const projectId = Number(c.req.query("projectId"));
27275
+ if (Number.isNaN(projectId)) return c.json({ code: "INVALID_ID" }, 400);
27276
+ const user = c.get("user");
27277
+ try {
27278
+ return c.json(
27279
+ rescanProjectWorkflows(db, workspacePath, projectId, {
27280
+ id: user.id,
27281
+ username: user.username
27282
+ })
27283
+ );
27284
+ } catch (e) {
27285
+ return errorResponse16(c, e);
27286
+ }
27287
+ });
27152
27288
  app.post("/runs/:id/rescan", (c) => {
27153
27289
  const id = Number(c.req.param("id"));
27154
27290
  if (Number.isNaN(id)) return c.json({ code: "INVALID_ID" }, 400);
@@ -27663,7 +27799,7 @@ function loggerMiddleware() {
27663
27799
  }
27664
27800
 
27665
27801
  // ../server/src/lib/static.ts
27666
- import { existsSync as existsSync24, readFileSync as readFileSync19, statSync as statSync7 } from "fs";
27802
+ import { existsSync as existsSync24, readFileSync as readFileSync19, statSync as statSync8 } from "fs";
27667
27803
  import { extname, join as join26, normalize, resolve as resolve4, sep as sep3 } from "path";
27668
27804
  var MIME = {
27669
27805
  ".html": "text/html; charset=utf-8",
@@ -27701,7 +27837,7 @@ function serveSpa(staticDir) {
27701
27837
  }
27702
27838
  const rel = path === "/" ? "/index.html" : decodeURIComponent(path);
27703
27839
  const filePath = safeResolve2(staticDir, rel);
27704
- if (filePath && existsSync24(filePath) && statSync7(filePath).isFile()) {
27840
+ if (filePath && existsSync24(filePath) && statSync8(filePath).isFile()) {
27705
27841
  const body = readFileSync19(filePath);
27706
27842
  return c.body(body, 200, {
27707
27843
  "Content-Type": MIME[extname(filePath)] ?? "application/octet-stream"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rashidee/co2",
3
- "version": "1.3.12",
3
+ "version": "1.3.14",
4
4
  "description": "Compound Context Studio — self-hosted team context authoring for CO2 projects",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -12,7 +12,7 @@
12
12
  "name": "co2-skills",
13
13
  "source": "./",
14
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.6.0",
15
+ "version": "1.7.0",
16
16
  "author": {
17
17
  "name": "Rashidee M.Rashid"
18
18
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "co2-skills",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
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"
package/plugin/README.md CHANGED
@@ -229,6 +229,7 @@ Example skill invocation for technical specification generation:
229
229
  /specgen-flutter-riverpod <app_name> ## For generating Flutter 3 mobile application technical specification with Riverpod, Hive, Dio, go_router and Firebase Messaging.
230
230
  /specgen-ts-cli <app_name> ## For generating Node.js CLI application technical specification with TypeScript, Commander.js and tsup.
231
231
  /specgen-node-cli-web <app_name> ## For generating self-hosted Node.js web application technical specification distributed as a global npm CLI — Hono, embedded React SPA, Drizzle + SQLite, hand-rolled sessions and tsup.
232
+ /specgen-custom <app_name> ## For generating a technical specification for an arbitrary user-defined stack from a custom stack spec markdown (also the automatic fallback when no specgen matches).
232
233
  ~~~
233
234
 
234
235
  - Input:
@@ -556,7 +557,7 @@ We welcome contributions from the community! Here are some ways you can contribu
556
557
 
557
558
  ## Specification Generator Skills (`specgen-*`)
558
559
 
559
- This is the **highest impact area** for contribution. The CO2 workflow is designed to be technology-agnostic, but each `specgen-*` skill targets a specific technology stack. We currently support:
560
+ This is the **highest impact area** for contribution. The CO2 workflow is designed to be technology-agnostic, but each `specgen-*` skill targets a specific technology stack. Stacks not covered by a dedicated variant are served by `specgen-custom` (driven by a user-authored stack spec markdown), but a contributed stack-specific specgen always produces a deeper specification and remains preferred. We currently support:
560
561
 
561
562
  | Skill | Technology Stack |
562
563
  |-------|-----------------|
@@ -570,6 +571,7 @@ This is the **highest impact area** for contribution. The CO2 workflow is design
570
571
  | `specgen-ts-cli` | Node.js CLI + TypeScript + Commander.js + tsup + pkg |
571
572
  | `specgen-node-cli-web` | Self-hosted Node.js web app as a global npm CLI — Node 22 + commander 12 + Hono 4 + embedded React 19/Vite 6 SPA + Tailwind CSS v4 + shadcn/ui + Drizzle ORM + SQLite + hand-rolled scrypt sessions + tsup + Biome + Vitest + Playwright |
572
573
  | `specgen-sdk-java` | Java SDK library — Maven Multi-Release fat JAR (JDK 8 baseline + JDK 11+ overlay) + OkHttp |
574
+ | `specgen-custom` | Any stack described by a user-authored custom stack spec markdown (authoring template: `skills/specgen-custom/references/stack-doc-template.md`) — an explicit declaration overrides the built-in variants; also the automatic fallback when no variant matches |
573
575
 
574
576
  We are looking for contributors to create new `specgen-*` skills for other technology stacks, such as:
575
577
 
package/plugin/SKILLS.md CHANGED
@@ -237,7 +237,9 @@ All specgens share the same contract: **Invoke** `/specgen-<variant> <applicatio
237
237
  UI-bearing stacks); **Output** `context/specification/` — root `SPECIFICATION.md` + one
238
238
  self-contained `SPEC.md` per module (or per command for CLI stacks), with complete code
239
239
  samples and traceability tables. **Pick exactly ONE variant, matching the application's
240
- technology stack** (declared in `CLAUDE.md` / PRD Architecture Principle):
240
+ technology stack** (declared in `CLAUDE.md` / PRD Architecture Principle). A custom stack
241
+ spec declared as ``Stack per custom stack spec at `<path>` `` always wins over the built-in
242
+ variants, and `specgen-custom` is the fallback when nothing matches:
241
243
 
242
244
  | Skill | Pick when the application is… |
243
245
  |-------|-------------------------------|
@@ -250,6 +252,7 @@ technology stack** (declared in `CLAUDE.md` / PRD Architecture Principle):
250
252
  | `specgen-ts-cli` | Distributable Node.js CLI **tool** (TypeScript 5, Commander 12, tsup, Zod, chalk/ora; optional prompts, conf/cosmiconfig, SQLite, execa, got, @yao-pkg/pkg binaries) — terminal-only, no web UI |
251
253
  | `specgen-node-cli-web` | Self-hosted **web application distributed as a global npm CLI** — installed `npm i -g`, started `<app> start`, opened at `http://IP:PORT`. Single Node 22 process: Hono 4 API + embedded pre-built React 19/Vite 6 SPA (Tailwind v4, shadcn/ui), Drizzle + SQLite, hand-rolled scrypt sessions with first-run admin + forced password change, tsup, Biome, Vitest + Playwright with built-binary smoke test |
252
254
  | `specgen-sdk-java` | Java SDK **library** wrapping a remote REST API — Maven Multi-Release fat JAR, JDK 8 baseline + JDK 11+ overlay, OkHttp as sole runtime dependency |
255
+ | `specgen-custom` | ANY other stack — driven by a user-authored custom stack spec markdown (declared in PRD.md/CLAUDE.md as ``Stack per custom stack spec at `<path>` ``, authoring template in `references/stack-doc-template.md`). An explicit declaration OVERRIDES the built-in variants; also the automatic fallback (with `[TODO]`-annotated defaults) when no variant matches |
253
256
 
254
257
  > **`specgen-ts-cli` vs `specgen-node-cli-web`:** if the deliverable is a terminal tool with
255
258
  > commands and flags → `specgen-ts-cli`. If the deliverable is a browser-accessed web app that
@@ -406,7 +409,7 @@ implemented** (empty stub — no SKILL.md). Do not invoke; fall back to `specgen
406
409
  | "prepare everything for development" | `conductor-feature-prepare` |
407
410
  | "design the data model / ERD" | `modelgen-relational` or `modelgen-nosql` (by datastore) |
408
411
  | "generate mockups / UI screens" | `mockgen-tailwind` or `mockgen-shadcn` (by stack) |
409
- | "write the technical spec" | the ONE `specgen-*` matching the stack (see table) |
412
+ | "write the technical spec" | the ONE `specgen-*` matching the stack (see table); a declared custom stack spec → `specgen-custom`, which is also the fallback when nothing matches |
410
413
  | "generate the test plan" | `testgen-functional` |
411
414
  | "set up databases/queues in k8s" | `util-preparek8senv` |
412
415
  | "plan deployment / plan cicd" | `util-plancicd` |
@@ -389,6 +389,11 @@ files to understand what has already been completed.
389
389
  version (e.g., `${APP_VERSION:1.0.4}`)
390
390
  - **config/app.php** (Laravel): Update the default in `env('APP_VERSION', '1.0.0')`
391
391
  to the new version
392
+ - **Custom / other stacks** (SPECIFICATION.md generated by `specgen-custom`): Update
393
+ the manifest file/field and environment variable named in SPECIFICATION.md's
394
+ **Application Version Configuration** subsection. If the spec names none, update
395
+ the project's primary manifest and add a `[TODO]` in IMPLEMENTATION_MASTER.md
396
+ noting the gap.
392
397
  The version displayed in the application footer (or API info endpoint) MUST reflect
393
398
  the new version after this update.
394
399
  3. Proceed to Phase 3 (Implementation) for the affected modules.
@@ -496,6 +501,8 @@ subdirectory. Instead:
496
501
  then move ALL files (including dotfiles) from that temp directory up to `<source-code-path>/`,
497
502
  then remove the empty temp directory. This avoids overwriting the existing `context/` folder.
498
503
  - For Spring Boot: Same approach — scaffold into a temp dir, then move files up.
504
+ - For any other stack whose scaffolding tool creates a nested project directory: same
505
+ temp-dir-then-move approach.
499
506
  - NEVER use the project slug/name as the target directory if it would create a nested folder.
500
507
 
501
508
  **Scaffolding Checklist** (adapt to the technology stack from SPECIFICATION.md):
@@ -511,6 +518,9 @@ subdirectory. Instead:
511
518
  - **Laravel**: Set `version` in `composer.json` and `APP_VERSION` in `.env`
512
519
  - **React / Node.js**: Set `version` in `package.json` and `VITE_APP_VERSION` in
513
520
  `.env.development` (or `APP_VERSION` in `.env` for Node.js backends)
521
+ - **Custom / other stacks**: Use the manifest file/field and environment variable named in
522
+ SPECIFICATION.md's **Application Version Configuration** subsection (a spec generated by
523
+ `specgen-custom` always names them)
514
524
  - The version in the manifest MUST match the version in the environment variable
515
525
  - For multi-version processing, this version will be updated during each version increment
516
526
  in the Phase 0 resume check
@@ -1057,6 +1067,9 @@ Extracted from IMPLEMENTATION_MASTER.md execution order and module details.>
1057
1067
  - For **Spring Boot** apps: use Maven/Gradle commands, `src/main/java` paths, `application.yml` config
1058
1068
  - For **Laravel** apps: use Composer/Artisan commands, `app/` paths, `.env` config
1059
1069
  - For **REST API** apps: omit view/template sections, focus on API endpoints and Swagger/OpenAPI docs
1070
+ - For **custom stacks** (SPECIFICATION.md generated by `specgen-custom`): every command, path
1071
+ and config reference comes from SPECIFICATION.md's Build, Run & Packaging and Project
1072
+ Structure sections — never from stack assumptions
1060
1073
  - Include only sections that are relevant to the actual specification — do NOT add sections for
1061
1074
  features that are not part of the spec (e.g., skip messaging section if no messaging is configured)
1062
1075
 
@@ -319,8 +319,10 @@ record the result for use throughout the version loop:
319
319
  decisions and resolved-file-path propagation to mockgen-* and specgen-*. No additional action
320
320
  needed; Step 1.5 is the canonical handler.
321
321
  - **`# Architecture Principle`** — Already consumed by Step 1.3 (Infer Database Type) and
322
- Step 1.7 (Infer Technology Stack) as the **primary signal** for routing decisions. Sub-skills
323
- (modelgen-*, specgen-*) read it directly from PRD.md.
322
+ Step 1.7 (Infer Technology Stack) as the **primary signal** for routing decisions. It may
323
+ also declare a custom stack spec file (``Stack per custom stack spec at `<path>` `` or a
324
+ markdown link to the stack file), which Step 1.7 treats as the **overriding** signal and
325
+ routes to `specgen-custom`. Sub-skills (modelgen-*, specgen-*) read it directly from PRD.md.
324
326
  - **`# High Level Process Flow`** — **Detect presence here** so all downstream skills are aware:
325
327
  1. Check PRD.md for a `# High Level Process Flow` section. If present, record:
326
328
  - The list of named flows (e.g., "Country submission flow", "Worker registration flow")
@@ -384,6 +386,10 @@ tracker that this skill — and any resumed Ralph Loop iteration — reads to kn
384
386
  | Spec Generator | TBD | - |
385
387
 
386
388
  > Filled in by the inference steps (1.3, 1.5, 1.7) as the pipeline runs.
389
+ > When a custom stack spec is used, record Technology Stack = "Custom (per `<path>`)" and
390
+ > Spec Generator = "specgen-custom (explicit)" or "specgen-custom (fallback)"; the Source
391
+ > column names the declaration (`PRD.md # Architecture Principle (custom stack spec)`,
392
+ > `CLAUDE.md entry (custom stack spec)`) or `fallback (no specgen matched)`.
387
393
 
388
394
  ---
389
395
 
@@ -514,31 +520,53 @@ applies to all items regardless of version.
514
520
 
515
521
  #### Step 1.7: Infer Technology Stack
516
522
 
517
- 1. **Check PRD.md `# Architecture Principle` section first** (primary signal):
523
+ 1. **Check for an explicit custom stack spec declaration first** (overrides all other signals):
524
+ - In PRD.md `# Architecture Principle`: a statement matching ``Stack per custom stack spec at `<path>` ``
525
+ or a markdown link to a custom stack spec markdown file (e.g., `[STACK.md](reference/STACK.md)`)
526
+ - Else in the application's CLAUDE.md entry (under `# Custom Applications`): the same
527
+ ``Stack per custom stack spec at `<path>` `` convention
528
+ - If found: resolve the path (relative to PRD.md first, then to the project root) and verify
529
+ the file exists — if it does NOT exist, **STOP** and report the dangling declaration to the
530
+ user. If it exists, record **Technology Stack = Custom (per `<path>`)** with the resolved
531
+ path and **skip items 2–3** — the stack doc is authoritative.
532
+ 2. **Check PRD.md `# Architecture Principle` section** (primary signal):
518
533
  - If Architecture Principle explicitly mentions a framework (e.g., "Spring Boot", "Laravel"), use it
519
534
  - If it mentions a view engine (e.g., "JTE", "Blade"), use it
520
535
  - If it mentions frontend tooling (e.g., "HTMX", "Alpine.js", "Vite"), use it
521
536
  - If it mentions architectural style (e.g., "monolithic", "REST API"), use it to disambiguate specgen variant
522
- 2. **Fallback to CLAUDE.md** (if Architecture Principle is absent or incomplete):
537
+ 3. **Fallback to CLAUDE.md** (if Architecture Principle is absent or incomplete):
523
538
  - Read application details from CLAUDE.md (already in context)
524
539
  - Identify the technology stack:
525
540
  - Framework (Laravel, Spring Boot, etc.)
526
541
  - ORM (Eloquent, JPA/Hibernate, etc.)
527
542
  - View engine (Blade, JTE, etc.)
528
543
  - Frontend enhancement (HTMX, Alpine.js, etc.)
529
- 3. Record the inferred technology stack for the next step
544
+ 4. Record the inferred technology stack (or the resolved custom stack doc path) for the next step
530
545
 
531
546
  #### Step 1.8: Generate Technical Specification
532
547
 
533
548
  1. **Check if artifacts exist**: List files in `<app_folder>/context/specification/`
534
549
  - If `.md` files already exist AND this is NOT a version increment → **SKIP** this step
535
550
  - If this IS a version increment, **proceed** to re-invoke the skill with version/module args
536
- 2. Determine the appropriate specification generator:
537
- - Check available skills matching `specgen-*` pattern
538
- - For Laravel + Eloquent + Blade + HTMX `Skill(skill: "co2-skills:specgen-laravel-eloquent-bladehtmx", args: "<app_folder> [version:<version>] [module:<module>]")`
539
- - For Spring Boot + JPA + JTE + HTMX → `Skill(skill: "co2-skills:specgen-spring-jpa-jtehtmx", args: "<app_folder> [version:<version>] [module:<module>]")`
540
- - For a self-hosted Node.js web app distributed as a global npm CLI (Hono + embedded React SPA + SQLite) → `Skill(skill: "co2-skills:specgen-node-cli-web", args: "<app_folder> [version:<version>] [module:<module>]")`
541
- - Match the skill name against the inferred technology stack components
551
+ 2. Determine the appropriate specification generator, **in this priority order**:
552
+ - **a. Explicit custom stack spec (from Step 1.7)** — if Step 1.7 resolved a custom stack doc,
553
+ use `specgen-custom` regardless of whether a built-in specgen would also match the stack:
554
+ `Skill(skill: "co2-skills:specgen-custom", args: "<app_folder> [version:<version>] [module:<module>] stack:<resolved-stack-doc-path>")`
555
+ - **b. Built-in specgen match** otherwise, check available skills matching the `specgen-*`
556
+ pattern (excluding `specgen-custom`) and match against the inferred technology stack.
557
+ Only route to a variant that actually exists as an invokable skill (has a SKILL.md):
558
+ - For Laravel + Eloquent + Blade + HTMX → `Skill(skill: "co2-skills:specgen-laravel-eloquent-bladehtmx", args: "<app_folder> [version:<version>] [module:<module>]")`
559
+ - For Spring Boot + JPA + JTE + HTMX → `Skill(skill: "co2-skills:specgen-spring-jpa-jtehtmx", args: "<app_folder> [version:<version>] [module:<module>]")`
560
+ - For a self-hosted Node.js web app distributed as a global npm CLI (Hono + embedded React SPA + SQLite) → `Skill(skill: "co2-skills:specgen-node-cli-web", args: "<app_folder> [version:<version>] [module:<module>]")`
561
+ - Match the skill name against the inferred technology stack components
562
+ - **c. No match — fallback to specgen-custom** — if NO built-in specgen matches the inferred
563
+ stack, do NOT stop and do NOT force-fit the closest variant. Invoke:
564
+ `Skill(skill: "co2-skills:specgen-custom", args: "<app_folder> [version:<version>] [module:<module>] stack-desc:\"<inferred stack summary from Step 1.7>\"")`
565
+ specgen-custom will first attempt to auto-discover a stack doc at its well-known paths
566
+ (`<app_folder>/context/reference/STACK.md`, `<app_folder>/context/STACK.md`,
567
+ `<root>/shared_context/STACK.md`); if none exists it generates from the inferred stack
568
+ description, annotating every assumption with `[TODO]`. Surface the applied defaults to
569
+ the user in the run summary.
542
570
  3. Wait for the skill to complete. The output will be specification files in `<app_folder>/context/specification/`
543
571
 
544
572
  #### Step 1.9: Generate Test Specification