@geraldmaron/construct 1.0.0

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 (352) hide show
  1. package/.env.example +69 -0
  2. package/LICENSE +21 -0
  3. package/README.md +344 -0
  4. package/agents/prompts/cx-accessibility.md +29 -0
  5. package/agents/prompts/cx-ai-engineer.md +31 -0
  6. package/agents/prompts/cx-architect.md +40 -0
  7. package/agents/prompts/cx-business-strategist.md +25 -0
  8. package/agents/prompts/cx-data-analyst.md +30 -0
  9. package/agents/prompts/cx-data-engineer.md +32 -0
  10. package/agents/prompts/cx-debugger.md +27 -0
  11. package/agents/prompts/cx-designer.md +33 -0
  12. package/agents/prompts/cx-devil-advocate.md +36 -0
  13. package/agents/prompts/cx-docs-keeper.md +50 -0
  14. package/agents/prompts/cx-engineer.md +36 -0
  15. package/agents/prompts/cx-evaluator.md +26 -0
  16. package/agents/prompts/cx-explorer.md +38 -0
  17. package/agents/prompts/cx-legal-compliance.md +25 -0
  18. package/agents/prompts/cx-operations.md +28 -0
  19. package/agents/prompts/cx-orchestrator.md +30 -0
  20. package/agents/prompts/cx-platform-engineer.md +25 -0
  21. package/agents/prompts/cx-product-manager.md +30 -0
  22. package/agents/prompts/cx-qa.md +42 -0
  23. package/agents/prompts/cx-rd-lead.md +25 -0
  24. package/agents/prompts/cx-release-manager.md +31 -0
  25. package/agents/prompts/cx-researcher.md +31 -0
  26. package/agents/prompts/cx-reviewer.md +36 -0
  27. package/agents/prompts/cx-security.md +37 -0
  28. package/agents/prompts/cx-sre.md +26 -0
  29. package/agents/prompts/cx-test-automation.md +35 -0
  30. package/agents/prompts/cx-trace-reviewer.md +76 -0
  31. package/agents/prompts/cx-ux-researcher.md +30 -0
  32. package/agents/registry.json +879 -0
  33. package/agents/teams.json +94 -0
  34. package/bin/construct +613 -0
  35. package/commands/build/feature.md +21 -0
  36. package/commands/build/fix.md +20 -0
  37. package/commands/design/access.md +19 -0
  38. package/commands/design/flow.md +16 -0
  39. package/commands/design/ui.md +18 -0
  40. package/commands/measure/experiment.md +18 -0
  41. package/commands/measure/metrics.md +18 -0
  42. package/commands/measure/results.md +18 -0
  43. package/commands/plan/api.md +19 -0
  44. package/commands/plan/challenge.md +18 -0
  45. package/commands/plan/decide.md +20 -0
  46. package/commands/plan/feature.md +49 -0
  47. package/commands/plan/requirements.md +24 -0
  48. package/commands/remember/context.md +26 -0
  49. package/commands/remember/handoff.md +19 -0
  50. package/commands/remember/runbook.md +20 -0
  51. package/commands/review/code.md +21 -0
  52. package/commands/review/quality.md +19 -0
  53. package/commands/review/security.md +16 -0
  54. package/commands/ship/ready.md +19 -0
  55. package/commands/ship/release.md +19 -0
  56. package/commands/ship/status.md +18 -0
  57. package/commands/understand/docs.md +22 -0
  58. package/commands/understand/research.md +22 -0
  59. package/commands/understand/this.md +23 -0
  60. package/commands/understand/why.md +18 -0
  61. package/commands/work/clean.md +32 -0
  62. package/commands/work/drive.md +69 -0
  63. package/commands/work/optimize-prompts.md +46 -0
  64. package/commands/work/parallel-review.md +40 -0
  65. package/db/migrations/001_init.sql +38 -0
  66. package/langfuse/docker-compose.yml +82 -0
  67. package/lib/audit-skills.mjs +118 -0
  68. package/lib/auto-docs.mjs +293 -0
  69. package/lib/cli-commands.mjs +409 -0
  70. package/lib/codex-config.mjs +114 -0
  71. package/lib/comment-lint.mjs +191 -0
  72. package/lib/completions.mjs +170 -0
  73. package/lib/context-state.mjs +99 -0
  74. package/lib/cost.mjs +213 -0
  75. package/lib/distill.mjs +403 -0
  76. package/lib/efficiency.mjs +139 -0
  77. package/lib/env-config.mjs +53 -0
  78. package/lib/eval-harness.mjs +59 -0
  79. package/lib/features.mjs +209 -0
  80. package/lib/headhunt.mjs +597 -0
  81. package/lib/hooks/adaptive-lint.mjs +112 -0
  82. package/lib/hooks/agent-tracker.mjs +50 -0
  83. package/lib/hooks/bootstrap-guard.mjs +90 -0
  84. package/lib/hooks/comment-lint.mjs +26 -0
  85. package/lib/hooks/config-protection.mjs +52 -0
  86. package/lib/hooks/console-warn.mjs +43 -0
  87. package/lib/hooks/context-window-recovery.mjs +90 -0
  88. package/lib/hooks/continuation-enforcer.mjs +72 -0
  89. package/lib/hooks/dep-audit.mjs +155 -0
  90. package/lib/hooks/drive-guard.mjs +89 -0
  91. package/lib/hooks/edit-accumulator.mjs +36 -0
  92. package/lib/hooks/edit-error-recovery.mjs +46 -0
  93. package/lib/hooks/edit-guard.mjs +109 -0
  94. package/lib/hooks/env-check.mjs +80 -0
  95. package/lib/hooks/guard-bash.mjs +83 -0
  96. package/lib/hooks/mcp-audit.mjs +57 -0
  97. package/lib/hooks/mcp-health-check.mjs +51 -0
  98. package/lib/hooks/mcp-task-scope.mjs +47 -0
  99. package/lib/hooks/model-fallback.mjs +105 -0
  100. package/lib/hooks/pre-compact.mjs +212 -0
  101. package/lib/hooks/pre-push-gate.mjs +129 -0
  102. package/lib/hooks/read-tracker.mjs +129 -0
  103. package/lib/hooks/registry-sync.mjs +23 -0
  104. package/lib/hooks/scan-secrets.mjs +76 -0
  105. package/lib/hooks/session-start.mjs +95 -0
  106. package/lib/hooks/stop-notify.mjs +191 -0
  107. package/lib/hooks/stop-typecheck.mjs +83 -0
  108. package/lib/hooks/task-completed-guard.mjs +43 -0
  109. package/lib/hooks/teammate-idle-guard.mjs +54 -0
  110. package/lib/hooks/workflow-guard.mjs +62 -0
  111. package/lib/host-capabilities.mjs +123 -0
  112. package/lib/init-docs.mjs +382 -0
  113. package/lib/mcp/server.mjs +1123 -0
  114. package/lib/mcp-catalog.json +243 -0
  115. package/lib/mcp-manager.mjs +433 -0
  116. package/lib/mcp-platform-config.mjs +104 -0
  117. package/lib/model-router.mjs +810 -0
  118. package/lib/opencode-config.mjs +44 -0
  119. package/lib/opencode-runtime-plugin.mjs +909 -0
  120. package/lib/opencode-telemetry.mjs +433 -0
  121. package/lib/orchestration-policy.mjs +258 -0
  122. package/lib/prompt-composer.mjs +196 -0
  123. package/lib/prompt-metadata.mjs +68 -0
  124. package/lib/review.mjs +429 -0
  125. package/lib/role-preload.mjs +52 -0
  126. package/lib/schemas/decision.json +50 -0
  127. package/lib/schemas/implementation.json +51 -0
  128. package/lib/schemas/review-report.json +32 -0
  129. package/lib/schemas/test-report.json +43 -0
  130. package/lib/server/index.mjs +334 -0
  131. package/lib/server/static/app.js +841 -0
  132. package/lib/server/static/index.html +820 -0
  133. package/lib/service-manager.mjs +225 -0
  134. package/lib/setup.mjs +319 -0
  135. package/lib/status.mjs +707 -0
  136. package/lib/storage/backend.mjs +44 -0
  137. package/lib/storage/embeddings.mjs +70 -0
  138. package/lib/storage/hybrid-query.mjs +184 -0
  139. package/lib/storage/sql-store.mjs +55 -0
  140. package/lib/storage/state-source.mjs +101 -0
  141. package/lib/storage/sync.mjs +164 -0
  142. package/lib/storage/vector-store.mjs +59 -0
  143. package/lib/telemetry/backends/langfuse.mjs +58 -0
  144. package/lib/telemetry/backends/noop.mjs +16 -0
  145. package/lib/telemetry/langfuse-ingest.mjs +108 -0
  146. package/lib/telemetry/langfuse-model-sync.mjs +270 -0
  147. package/lib/telemetry/team-rollup.mjs +143 -0
  148. package/lib/toolkit-env.mjs +30 -0
  149. package/lib/validator.mjs +181 -0
  150. package/lib/workflow-state.mjs +794 -0
  151. package/package.json +53 -0
  152. package/personas/construct.md +94 -0
  153. package/platforms/claude/CLAUDE.md +30 -0
  154. package/platforms/claude/settings.template.json +472 -0
  155. package/platforms/opencode/config.template.json +65 -0
  156. package/platforms/opencode/plugins/construct-fallback.js +5 -0
  157. package/platforms/opencode/sync-config.mjs +69 -0
  158. package/rules/common/agents.md +55 -0
  159. package/rules/common/code-review.md +129 -0
  160. package/rules/common/coding-style.md +95 -0
  161. package/rules/common/comments.md +139 -0
  162. package/rules/common/cx-agent-routing.md +61 -0
  163. package/rules/common/cx-skill-routing.md +11 -0
  164. package/rules/common/development-workflow.md +49 -0
  165. package/rules/common/git-workflow.md +29 -0
  166. package/rules/common/hooks.md +35 -0
  167. package/rules/common/patterns.md +36 -0
  168. package/rules/common/performance.md +71 -0
  169. package/rules/common/security.md +34 -0
  170. package/rules/common/testing.md +62 -0
  171. package/rules/golang/coding-style.md +37 -0
  172. package/rules/golang/hooks.md +22 -0
  173. package/rules/golang/patterns.md +50 -0
  174. package/rules/golang/security.md +39 -0
  175. package/rules/golang/testing.md +36 -0
  176. package/rules/python/coding-style.md +47 -0
  177. package/rules/python/hooks.md +24 -0
  178. package/rules/python/patterns.md +44 -0
  179. package/rules/python/security.md +35 -0
  180. package/rules/python/testing.md +43 -0
  181. package/rules/swift/coding-style.md +52 -0
  182. package/rules/swift/hooks.md +25 -0
  183. package/rules/swift/patterns.md +71 -0
  184. package/rules/swift/security.md +38 -0
  185. package/rules/swift/testing.md +50 -0
  186. package/rules/typescript/coding-style.md +204 -0
  187. package/rules/typescript/hooks.md +27 -0
  188. package/rules/typescript/patterns.md +57 -0
  189. package/rules/typescript/security.md +33 -0
  190. package/rules/typescript/testing.md +23 -0
  191. package/rules/web/coding-style.md +101 -0
  192. package/rules/web/design-quality.md +68 -0
  193. package/rules/web/hooks.md +125 -0
  194. package/rules/web/patterns.md +84 -0
  195. package/rules/web/performance.md +69 -0
  196. package/rules/web/security.md +62 -0
  197. package/rules/web/testing.md +60 -0
  198. package/rules/zh/README.md +113 -0
  199. package/rules/zh/agents.md +55 -0
  200. package/rules/zh/code-review.md +129 -0
  201. package/rules/zh/coding-style.md +53 -0
  202. package/rules/zh/development-workflow.md +49 -0
  203. package/rules/zh/git-workflow.md +29 -0
  204. package/rules/zh/hooks.md +35 -0
  205. package/rules/zh/patterns.md +36 -0
  206. package/rules/zh/performance.md +60 -0
  207. package/rules/zh/security.md +34 -0
  208. package/rules/zh/testing.md +34 -0
  209. package/skills/ai/agent-dev.md +102 -0
  210. package/skills/ai/llm-security.md +105 -0
  211. package/skills/ai/ml-ops.md +169 -0
  212. package/skills/ai/orchestration-workflow.md +87 -0
  213. package/skills/ai/prompt-and-eval.md +114 -0
  214. package/skills/ai/prompt-optimizer.md +114 -0
  215. package/skills/ai/rag-system.md +104 -0
  216. package/skills/architecture/api-design.md +100 -0
  217. package/skills/architecture/caching.md +101 -0
  218. package/skills/architecture/cloud-native.md +97 -0
  219. package/skills/architecture/message-queue.md +100 -0
  220. package/skills/architecture/security-arch.md +105 -0
  221. package/skills/development/cpp.md +127 -0
  222. package/skills/development/go.md +129 -0
  223. package/skills/development/java.md +134 -0
  224. package/skills/development/kotlin.md +140 -0
  225. package/skills/development/mobile-crossplatform.md +144 -0
  226. package/skills/development/python.md +109 -0
  227. package/skills/development/rust.md +127 -0
  228. package/skills/development/shell.md +127 -0
  229. package/skills/development/swift.md +129 -0
  230. package/skills/development/typescript.md +129 -0
  231. package/skills/devops/ci-cd.md +108 -0
  232. package/skills/devops/containerization.md +106 -0
  233. package/skills/devops/cost-optimization.md +105 -0
  234. package/skills/devops/data-engineering.md +181 -0
  235. package/skills/devops/database.md +95 -0
  236. package/skills/devops/dependency-management.md +91 -0
  237. package/skills/devops/devsecops.md +96 -0
  238. package/skills/devops/git-workflow.md +100 -0
  239. package/skills/devops/incident-response.md +167 -0
  240. package/skills/devops/monorepo.md +87 -0
  241. package/skills/devops/observability.md +103 -0
  242. package/skills/devops/performance.md +104 -0
  243. package/skills/devops/testing.md +104 -0
  244. package/skills/docs/adr-workflow.md +32 -0
  245. package/skills/docs/backlog-proposal-workflow.md +19 -0
  246. package/skills/docs/customer-profile-workflow.md +22 -0
  247. package/skills/docs/evidence-ingest-workflow.md +23 -0
  248. package/skills/docs/init-docs.md +160 -0
  249. package/skills/docs/init-project.md +50 -0
  250. package/skills/docs/prd-workflow.md +57 -0
  251. package/skills/docs/prfaq-workflow.md +25 -0
  252. package/skills/docs/product-intelligence-review.md +22 -0
  253. package/skills/docs/product-intelligence-workflow.md +62 -0
  254. package/skills/docs/product-signal-workflow.md +27 -0
  255. package/skills/docs/research-workflow.md +28 -0
  256. package/skills/docs/runbook-workflow.md +24 -0
  257. package/skills/exploration/repo-map.md +297 -0
  258. package/skills/frameworks/django.md +159 -0
  259. package/skills/frameworks/nextjs.md +148 -0
  260. package/skills/frameworks/react.md +132 -0
  261. package/skills/frameworks/spring-boot.md +165 -0
  262. package/skills/frontend-design/accessibility.md +153 -0
  263. package/skills/frontend-design/component-patterns.md +111 -0
  264. package/skills/frontend-design/engineering.md +122 -0
  265. package/skills/frontend-design/state-management.md +118 -0
  266. package/skills/frontend-design/ui-aesthetics.md +102 -0
  267. package/skills/frontend-design/ux-principles.md +126 -0
  268. package/skills/quality-gates/review-work.md +90 -0
  269. package/skills/quality-gates/verify-change.md +94 -0
  270. package/skills/quality-gates/verify-module.md +96 -0
  271. package/skills/quality-gates/verify-quality.md +93 -0
  272. package/skills/quality-gates/verify-security.md +95 -0
  273. package/skills/roles/architect.ai-systems.md +37 -0
  274. package/skills/roles/architect.data.md +37 -0
  275. package/skills/roles/architect.enterprise.md +37 -0
  276. package/skills/roles/architect.integration.md +37 -0
  277. package/skills/roles/architect.md +68 -0
  278. package/skills/roles/architect.platform.md +37 -0
  279. package/skills/roles/data-analyst.experiment.md +37 -0
  280. package/skills/roles/data-analyst.md +68 -0
  281. package/skills/roles/data-analyst.product-intelligence.md +37 -0
  282. package/skills/roles/data-analyst.product.md +37 -0
  283. package/skills/roles/data-analyst.telemetry.md +37 -0
  284. package/skills/roles/data-engineer.pipeline.md +37 -0
  285. package/skills/roles/data-engineer.vector-retrieval.md +37 -0
  286. package/skills/roles/data-engineer.warehouse.md +37 -0
  287. package/skills/roles/debugger.md +68 -0
  288. package/skills/roles/designer.accessibility.md +43 -0
  289. package/skills/roles/designer.md +68 -0
  290. package/skills/roles/engineer.ai.md +49 -0
  291. package/skills/roles/engineer.data.md +49 -0
  292. package/skills/roles/engineer.md +85 -0
  293. package/skills/roles/engineer.platform.md +49 -0
  294. package/skills/roles/operator.docs.md +43 -0
  295. package/skills/roles/operator.md +68 -0
  296. package/skills/roles/operator.release.md +43 -0
  297. package/skills/roles/operator.sre.md +43 -0
  298. package/skills/roles/orchestrator.md +66 -0
  299. package/skills/roles/product-manager.ai-product.md +37 -0
  300. package/skills/roles/product-manager.business-strategy.md +43 -0
  301. package/skills/roles/product-manager.enterprise.md +37 -0
  302. package/skills/roles/product-manager.growth.md +37 -0
  303. package/skills/roles/product-manager.md +67 -0
  304. package/skills/roles/product-manager.platform.md +37 -0
  305. package/skills/roles/product-manager.product.md +37 -0
  306. package/skills/roles/qa.ai-eval.md +37 -0
  307. package/skills/roles/qa.api-contract.md +37 -0
  308. package/skills/roles/qa.data-pipeline.md +37 -0
  309. package/skills/roles/qa.md +68 -0
  310. package/skills/roles/qa.test-automation.md +49 -0
  311. package/skills/roles/qa.web-ui.md +37 -0
  312. package/skills/roles/researcher.explorer.md +43 -0
  313. package/skills/roles/researcher.md +68 -0
  314. package/skills/roles/researcher.ux.md +43 -0
  315. package/skills/roles/reviewer.devil-advocate.md +43 -0
  316. package/skills/roles/reviewer.evaluator.md +43 -0
  317. package/skills/roles/reviewer.md +68 -0
  318. package/skills/roles/reviewer.trace.md +43 -0
  319. package/skills/roles/security.ai.md +37 -0
  320. package/skills/roles/security.appsec.md +37 -0
  321. package/skills/roles/security.cloud.md +37 -0
  322. package/skills/roles/security.legal-compliance.md +49 -0
  323. package/skills/roles/security.md +68 -0
  324. package/skills/roles/security.privacy.md +37 -0
  325. package/skills/roles/security.supply-chain.md +37 -0
  326. package/skills/routing.md +139 -0
  327. package/skills/security/blue-team.md +83 -0
  328. package/skills/security/code-audit.md +94 -0
  329. package/skills/security/pentest.md +84 -0
  330. package/skills/security/red-team.md +81 -0
  331. package/skills/security/threat-intel.md +89 -0
  332. package/skills/security/vuln-research.md +87 -0
  333. package/skills/utility/clean-code.md +165 -0
  334. package/sync-agents.mjs +899 -0
  335. package/templates/docs/adr.md +27 -0
  336. package/templates/docs/backlog-proposal.md +26 -0
  337. package/templates/docs/customer-profile.md +34 -0
  338. package/templates/docs/evidence-brief.md +37 -0
  339. package/templates/docs/incident-report.md +46 -0
  340. package/templates/docs/memo.md +27 -0
  341. package/templates/docs/meta-prd.md +56 -0
  342. package/templates/docs/one-pager.md +24 -0
  343. package/templates/docs/prd-business.md +61 -0
  344. package/templates/docs/prd-platform.md +62 -0
  345. package/templates/docs/prd.md +47 -0
  346. package/templates/docs/prfaq.md +31 -0
  347. package/templates/docs/product-intelligence-report.md +31 -0
  348. package/templates/docs/research-brief.md +29 -0
  349. package/templates/docs/rfc-platform.md +60 -0
  350. package/templates/docs/rfc.md +46 -0
  351. package/templates/docs/runbook.md +33 -0
  352. package/templates/docs/signal-brief.md +25 -0
@@ -0,0 +1,52 @@
1
+ /**
2
+ * lib/role-preload.mjs — inline role anti-pattern files into specialist prompts at sync time.
3
+ *
4
+ * Specialist prompts carry a marker: **Role guidance**: call `get_skill("roles/NAME")` before drafting.
5
+ * buildPrompt() in sync-agents.mjs replaces that marker with the role body so the content is always
6
+ * present — no runtime get_skill dependency. Flavor overlays (e.g. engineer.ai) are concatenated
7
+ * under the core role content.
8
+ */
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+
12
+ export const ROLE_DIRECTIVE_RE = /^[ \t]*\*\*Role guidance\*\*:\s*call\s+`get_skill\("roles\/([^"]+)"\)`\s*before\s*drafting\.[ \t]*$/m;
13
+
14
+ export const PROMPT_WORD_CAP = 3600;
15
+
16
+ function stripFrontmatter(body) {
17
+ if (!body.startsWith("---\n")) return body;
18
+ const end = body.indexOf("\n---\n", 4);
19
+ return end === -1 ? body : body.slice(end + 5);
20
+ }
21
+
22
+ function stripTopHeading(body) {
23
+ return body.replace(/^#\s+[^\n]*\n+/, "").replace(/^Load this[^\n]*\n+/, "");
24
+ }
25
+
26
+ export function readRoleFile(root, name) {
27
+ const p = path.join(root, "skills", "roles", `${name}.md`);
28
+ if (!fs.existsSync(p)) return null;
29
+ return stripTopHeading(stripFrontmatter(fs.readFileSync(p, "utf8"))).trim();
30
+ }
31
+
32
+ export function inlineRoleAntiPatterns(prompt, root, agentName = "(unknown)", warn = console.warn) {
33
+ const match = prompt.match(ROLE_DIRECTIVE_RE);
34
+ if (!match) return prompt;
35
+ const ref = match[1];
36
+ const [core, flavor] = ref.split(".");
37
+ const coreBody = readRoleFile(root, core);
38
+ if (!coreBody) {
39
+ warn(`[sync] ${agentName}: role file skills/roles/${core}.md missing; leaving directive in place`);
40
+ return prompt;
41
+ }
42
+ let block = `## Role guidance\n\n${coreBody}`;
43
+ if (flavor) {
44
+ const flavorBody = readRoleFile(root, `${core}.${flavor}`);
45
+ if (flavorBody) {
46
+ block += `\n\n### ${flavor} overlay\n\n${flavorBody}`;
47
+ } else {
48
+ warn(`[sync] ${agentName}: flavor file skills/roles/${core}.${flavor}.md missing`);
49
+ }
50
+ }
51
+ return prompt.replace(ROLE_DIRECTIVE_RE, block);
52
+ }
@@ -0,0 +1,50 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Decision",
4
+ "description": "Output from cx-architect: a documented architectural or design decision",
5
+ "type": "object",
6
+ "required": ["type", "goal", "approach", "tasks", "risks", "acceptanceCriteria"],
7
+ "properties": {
8
+ "type": { "type": "string", "enum": ["decision"] },
9
+ "goal": { "type": "string", "description": "Objective restated in one sentence" },
10
+ "approach": { "type": "string", "description": "Chosen strategy and rationale" },
11
+ "alternativesConsidered": {
12
+ "type": "array",
13
+ "items": { "type": "string" }
14
+ },
15
+ "tasks": {
16
+ "type": "array",
17
+ "items": {
18
+ "type": "object",
19
+ "required": ["description", "owner", "complexity"],
20
+ "properties": {
21
+ "description": { "type": "string" },
22
+ "owner": { "type": "string" },
23
+ "complexity": { "type": "string", "enum": ["S", "M", "L"] }
24
+ }
25
+ }
26
+ },
27
+ "risks": {
28
+ "type": "array",
29
+ "items": {
30
+ "type": "object",
31
+ "required": ["description", "mitigation"],
32
+ "properties": {
33
+ "description": { "type": "string" },
34
+ "mitigation": { "type": "string" }
35
+ }
36
+ }
37
+ },
38
+ "acceptanceCriteria": {
39
+ "type": "array",
40
+ "items": { "type": "string" },
41
+ "minItems": 1
42
+ },
43
+ "doNotTouch": {
44
+ "type": "array",
45
+ "items": { "type": "string" },
46
+ "description": "Files or systems that must stay unchanged"
47
+ }
48
+ },
49
+ "additionalProperties": false
50
+ }
@@ -0,0 +1,51 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Implementation",
4
+ "description": "Output from cx-engineer: summary of changes made",
5
+ "type": "object",
6
+ "required": ["type", "status", "filesChanged", "verificationChecklist"],
7
+ "properties": {
8
+ "type": { "type": "string", "enum": ["implementation"] },
9
+ "status": {
10
+ "type": "string",
11
+ "enum": ["DONE", "BLOCKED", "NEEDS_MAIN_INPUT"]
12
+ },
13
+ "filesChanged": {
14
+ "type": "array",
15
+ "items": {
16
+ "type": "object",
17
+ "required": ["path", "action"],
18
+ "properties": {
19
+ "path": { "type": "string" },
20
+ "action": { "type": "string", "enum": ["created", "modified", "deleted"] },
21
+ "summary": { "type": "string" }
22
+ }
23
+ }
24
+ },
25
+ "verificationChecklist": {
26
+ "type": "object",
27
+ "properties": {
28
+ "compilesWithoutErrors": { "type": "boolean" },
29
+ "existingTestsPass": { "type": "boolean" },
30
+ "newBehaviorHasCoverage": { "type": "boolean" },
31
+ "noHardcodedSecrets": { "type": "boolean" },
32
+ "noFileOver800Lines": { "type": "boolean" }
33
+ },
34
+ "required": ["compilesWithoutErrors", "existingTestsPass", "newBehaviorHasCoverage", "noHardcodedSecrets", "noFileOver800Lines"]
35
+ },
36
+ "blockerDescription": {
37
+ "type": "string",
38
+ "description": "Required when status is BLOCKED or NEEDS_MAIN_INPUT"
39
+ },
40
+ "needsMainInput": {
41
+ "type": "object",
42
+ "description": "Required when status is NEEDS_MAIN_INPUT",
43
+ "properties": {
44
+ "taskKey": { "type": "string" },
45
+ "question": { "type": "string" },
46
+ "safeDefault": { "type": "string" }
47
+ }
48
+ }
49
+ },
50
+ "additionalProperties": false
51
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "ReviewReport",
4
+ "description": "Output from cx-reviewer or cx-security: findings from a code or security review",
5
+ "type": "object",
6
+ "required": ["type", "target", "verdict", "findings"],
7
+ "properties": {
8
+ "type": { "type": "string", "enum": ["review-report"] },
9
+ "target": { "type": "string", "description": "Files or scope reviewed" },
10
+ "verdict": {
11
+ "type": "string",
12
+ "enum": ["APPROVED", "APPROVED_WITH_WARNINGS", "BLOCKED"],
13
+ "description": "APPROVED = no CRITICAL/HIGH. APPROVED_WITH_WARNINGS = only HIGH (merge with caution). BLOCKED = CRITICAL found."
14
+ },
15
+ "findings": {
16
+ "type": "array",
17
+ "items": {
18
+ "type": "object",
19
+ "required": ["severity", "category", "location", "description", "fix"],
20
+ "properties": {
21
+ "severity": { "type": "string", "enum": ["CRITICAL", "HIGH", "MEDIUM", "LOW"] },
22
+ "category": { "type": "string", "description": "e.g. correctness, security, regression, coverage, maintainability" },
23
+ "location": { "type": "string", "description": "file:line" },
24
+ "description": { "type": "string" },
25
+ "fix": { "type": "string", "description": "Concrete remediation" }
26
+ }
27
+ }
28
+ },
29
+ "summary": { "type": "string", "description": "One-paragraph summary of overall quality" }
30
+ },
31
+ "additionalProperties": false
32
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "TestReport",
4
+ "description": "Output from cx-qa: results of test planning and execution",
5
+ "type": "object",
6
+ "required": ["type", "verdict", "passed", "failed", "coverage"],
7
+ "properties": {
8
+ "type": { "type": "string", "enum": ["test-report"] },
9
+ "verdict": {
10
+ "type": "string",
11
+ "enum": ["PASS", "FAIL"],
12
+ "description": "PASS = all tests green and coverage meets threshold. FAIL = failing tests or coverage below threshold."
13
+ },
14
+ "passed": { "type": "integer", "minimum": 0 },
15
+ "failed": { "type": "integer", "minimum": 0 },
16
+ "skipped": { "type": "integer", "minimum": 0 },
17
+ "coverage": {
18
+ "type": "object",
19
+ "properties": {
20
+ "overall": { "type": "number", "description": "0.0–100.0 percentage" },
21
+ "meetsThreshold": { "type": "boolean" },
22
+ "threshold": { "type": "number" }
23
+ }
24
+ },
25
+ "failingTests": {
26
+ "type": "array",
27
+ "items": {
28
+ "type": "object",
29
+ "required": ["name", "error"],
30
+ "properties": {
31
+ "name": { "type": "string" },
32
+ "error": { "type": "string" }
33
+ }
34
+ }
35
+ },
36
+ "gaps": {
37
+ "type": "array",
38
+ "items": { "type": "string" },
39
+ "description": "Untested behaviors that should have tests"
40
+ }
41
+ },
42
+ "additionalProperties": false
43
+ }
@@ -0,0 +1,334 @@
1
+ /**
2
+ * lib/server/index.mjs — <one-line purpose>
3
+ *
4
+ * <2–6 line summary: what it does, who calls it, key side effects.>
5
+ */
6
+ import { createServer } from 'http';
7
+ import { readFileSync, writeFileSync, statSync, watch, existsSync, readdirSync } from 'fs';
8
+ import { join, extname } from 'path';
9
+ import { homedir } from 'os';
10
+ import { fileURLToPath } from 'url';
11
+ import { buildStatus as buildSharedStatus } from '../status.mjs';
12
+
13
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
14
+ const ROOT_DIR = join(__dirname, '..', '..');
15
+ const HOME = homedir();
16
+ const PORT = parseInt(process.env.PORT ?? '4242', 10);
17
+
18
+ const STATIC_DIR = join(__dirname, 'static');
19
+ const REGISTRY_FILE = join(ROOT_DIR, 'agents', 'registry.json');
20
+ const FEATURES_FILE = join(HOME, '.construct', 'features.json');
21
+ const WORKFLOW_FILE = join(process.cwd(), '.cx', 'workflow.json');
22
+ const SKILLS_DIR = join(ROOT_DIR, 'skills');
23
+ const COMMANDS_DIR = join(ROOT_DIR, 'commands');
24
+
25
+ const MIME = {
26
+ '.html': 'text/html; charset=utf-8',
27
+ '.js': 'text/javascript; charset=utf-8',
28
+ '.css': 'text/css; charset=utf-8',
29
+ '.json': 'application/json',
30
+ };
31
+
32
+ function listCommands() {
33
+ if (!existsSync(COMMANDS_DIR)) return [];
34
+ const result = [];
35
+ for (const domain of readdirSync(COMMANDS_DIR).sort()) {
36
+ const domainPath = join(COMMANDS_DIR, domain);
37
+ try {
38
+ if (!statSync(domainPath).isDirectory()) continue;
39
+ const commands = [];
40
+ for (const file of readdirSync(domainPath).sort()) {
41
+ if (!file.endsWith('.md')) continue;
42
+ const content = readFileSync(join(domainPath, file), 'utf8');
43
+ const match = content.match(/^---\r?\n[\s\S]*?description:\s*(.+?)\r?\n[\s\S]*?---/);
44
+ const description = match ? match[1].trim() : file.replace('.md', '');
45
+ commands.push({ name: file.replace('.md', ''), description, slash: `/${domain}:${file.replace('.md', '')}` });
46
+ }
47
+ if (commands.length) result.push({ domain, commands });
48
+ } catch { continue; }
49
+ }
50
+ return result;
51
+ }
52
+
53
+ function listSkills() {
54
+ if (!existsSync(SKILLS_DIR)) return [];
55
+ const result = [];
56
+ for (const cat of readdirSync(SKILLS_DIR)) {
57
+ const catPath = join(SKILLS_DIR, cat);
58
+ try {
59
+ const stat = statSync(catPath);
60
+ if (!stat.isDirectory()) continue;
61
+ const files = readdirSync(catPath)
62
+ .filter(f => f.endsWith('.md') || f.endsWith('.mjs'))
63
+ .filter(f => f !== 'SKILL.md');
64
+ result.push({ category: cat, files });
65
+ } catch { continue; }
66
+ }
67
+ return result;
68
+ }
69
+
70
+ async function buildStatus() {
71
+ const status = await buildSharedStatus({
72
+ rootDir: ROOT_DIR,
73
+ cwd: process.cwd(),
74
+ homeDir: HOME,
75
+ dashboardPort: PORT,
76
+ selfDashboard: true,
77
+ });
78
+
79
+ return {
80
+ ...status,
81
+ skills: listSkills(),
82
+ commands: listCommands(),
83
+ };
84
+ }
85
+
86
+ async function handleSessionUsage(_req, res) {
87
+ try {
88
+ const status = await buildSharedStatus({
89
+ rootDir: ROOT_DIR,
90
+ cwd: process.cwd(),
91
+ homeDir: HOME,
92
+ });
93
+
94
+ const payload = {
95
+ source: status.sessionUsage?.status === 'available' ? 'local-session-log' : 'unavailable',
96
+ status: status.sessionUsage?.status ?? 'unavailable',
97
+ sessionUsage: status.sessionUsage,
98
+ guidance: status.sessionUsage?.status === 'available'
99
+ ? 'Construct can answer token usage questions from locally recorded session usage.'
100
+ : 'No local token usage has been recorded for this session yet.',
101
+ };
102
+
103
+ res.writeHead(200, { 'Content-Type': 'application/json' });
104
+ res.end(JSON.stringify(payload));
105
+ } catch (err) {
106
+ res.writeHead(500, { 'Content-Type': 'application/json' });
107
+ res.end(JSON.stringify({ error: err.message }));
108
+ }
109
+ }
110
+
111
+ const sseClients = new Set();
112
+ const activeWatchers = [];
113
+ let watchRefreshTimer = null;
114
+
115
+ function notifyClients() {
116
+ for (const res of sseClients) {
117
+ try { res.write('data: refresh\n\n'); }
118
+ catch { sseClients.delete(res); }
119
+ }
120
+ }
121
+
122
+ function closeWatchers() {
123
+ while (activeWatchers.length) {
124
+ try { activeWatchers.pop().close(); }
125
+ catch { /* ignore close errors */ }
126
+ }
127
+ }
128
+
129
+ function scheduleWatchRefresh() {
130
+ clearTimeout(watchRefreshTimer);
131
+ watchRefreshTimer = setTimeout(() => {
132
+ watchFiles();
133
+ notifyClients();
134
+ }, 150);
135
+ }
136
+
137
+ function addWatcher(target) {
138
+ if (!existsSync(target)) return;
139
+ try {
140
+ const watcher = watch(target, () => scheduleWatchRefresh());
141
+ activeWatchers.push(watcher);
142
+ } catch {
143
+ /* ignore watch errors */
144
+ }
145
+ }
146
+
147
+ function addDirectoryTreeWatch(root) {
148
+ if (!existsSync(root)) return;
149
+ addWatcher(root);
150
+ try {
151
+ for (const entry of readdirSync(root)) {
152
+ const full = join(root, entry);
153
+ try {
154
+ if (statSync(full).isDirectory()) addWatcher(full);
155
+ } catch {
156
+ /* ignore stat errors */
157
+ }
158
+ }
159
+ } catch {
160
+ /* ignore read errors */
161
+ }
162
+ }
163
+
164
+ function watchFiles() {
165
+ closeWatchers();
166
+ [REGISTRY_FILE, FEATURES_FILE, WORKFLOW_FILE].forEach(addWatcher);
167
+ addDirectoryTreeWatch(SKILLS_DIR);
168
+ addDirectoryTreeWatch(COMMANDS_DIR);
169
+ }
170
+
171
+ const server = createServer(async (req, res) => {
172
+ const url = new URL(req.url, `http://127.0.0.1:${PORT}`);
173
+
174
+ if (url.pathname === '/events') {
175
+ res.writeHead(200, {
176
+ 'Content-Type': 'text/event-stream',
177
+ 'Cache-Control': 'no-cache',
178
+ 'Connection': 'keep-alive',
179
+ 'Access-Control-Allow-Origin': '*',
180
+ });
181
+ res.write(': connected\n\n');
182
+ sseClients.add(res);
183
+ req.on('close', () => sseClients.delete(res));
184
+ return;
185
+ }
186
+
187
+ if (url.pathname === '/api/registry' && req.method === 'GET') {
188
+ try {
189
+ const registry = JSON.parse(readFileSync(REGISTRY_FILE, 'utf8'));
190
+ res.writeHead(200, { 'Content-Type': 'application/json' });
191
+ res.end(JSON.stringify({ mcpServers: registry.mcpServers ?? {}, models: registry.models ?? {} }));
192
+ } catch (err) {
193
+ res.writeHead(500, { 'Content-Type': 'application/json' });
194
+ res.end(JSON.stringify({ error: err.message }));
195
+ }
196
+ return;
197
+ }
198
+
199
+ if (url.pathname === '/api/status') {
200
+ try {
201
+ const status = await buildStatus();
202
+ res.writeHead(200, { 'Content-Type': 'application/json' });
203
+ res.end(JSON.stringify(status));
204
+ } catch (err) {
205
+ res.writeHead(500, { 'Content-Type': 'application/json' });
206
+ res.end(JSON.stringify({ error: err.message }));
207
+ }
208
+ return;
209
+ }
210
+
211
+ if (url.pathname === '/api/session-usage') {
212
+ await handleSessionUsage(req, res);
213
+ return;
214
+ }
215
+
216
+ if (req.method === 'POST') {
217
+ let body = '';
218
+ req.on('data', chunk => { body += chunk; });
219
+ req.on('end', async () => {
220
+ try {
221
+ const data = JSON.parse(body || '{}');
222
+ const { approveWorkflow, approveTask } = await import('../workflow-state.mjs');
223
+ const { promoteHeadhunt, updatePromotionChallenge } = await import('../headhunt.mjs');
224
+
225
+ if (url.pathname === '/api/workflow/approve') {
226
+ const workflow = approveWorkflow(process.cwd(), data.note);
227
+ res.writeHead(200, { 'Content-Type': 'application/json' });
228
+ res.end(JSON.stringify({ success: true, workflow }));
229
+ notifyClients();
230
+ } else if (url.pathname === '/api/workflow/approve-task') {
231
+ if (!data.key) throw new Error('Missing task key');
232
+ const workflow = approveTask(process.cwd(), data.key, data.note);
233
+ res.writeHead(200, { 'Content-Type': 'application/json' });
234
+ res.end(JSON.stringify({ success: true, workflow }));
235
+ notifyClients();
236
+ } else if (url.pathname === '/api/headhunt/promote') {
237
+ if (!data.id) throw new Error('Missing overlay id');
238
+ const request = promoteHeadhunt(data.id, { cwd: process.cwd(), owner: data.owner || null });
239
+ res.writeHead(200, { 'Content-Type': 'application/json' });
240
+ res.end(JSON.stringify({ success: true, request }));
241
+ notifyClients();
242
+ } else if (url.pathname === '/api/headhunt/challenge') {
243
+ if (!data.id) throw new Error('Missing overlay id');
244
+ if (!data.status) throw new Error('Missing challenge status');
245
+ const request = updatePromotionChallenge(data.id, {
246
+ cwd: process.cwd(),
247
+ status: data.status,
248
+ note: data.note || null,
249
+ });
250
+ res.writeHead(200, { 'Content-Type': 'application/json' });
251
+ res.end(JSON.stringify({ success: true, request }));
252
+ notifyClients();
253
+ } else if (url.pathname === '/api/registry/mcp') {
254
+ const registry = JSON.parse(readFileSync(REGISTRY_FILE, 'utf8'));
255
+ if (!registry.mcpServers) registry.mcpServers = {};
256
+ const { action, id, server } = data;
257
+ if (!id || typeof id !== 'string' || !/^[\w-]+$/.test(id)) throw new Error('Invalid MCP server id');
258
+ if (action === 'delete') {
259
+ delete registry.mcpServers[id];
260
+ } else if (action === 'save' && server && typeof server === 'object') {
261
+ const entry = {};
262
+ if (server.type === 'url') {
263
+ if (!server.url) throw new Error('URL required for type=url');
264
+ entry.type = 'url';
265
+ entry.url = String(server.url);
266
+ if (server.headers && typeof server.headers === 'object') entry.headers = server.headers;
267
+ } else {
268
+ if (!server.command) throw new Error('command required');
269
+ entry.command = String(server.command);
270
+ entry.args = Array.isArray(server.args) ? server.args.map(String) : [];
271
+ }
272
+ if (server.description) entry.description = String(server.description);
273
+ registry.mcpServers[id] = entry;
274
+ } else {
275
+ throw new Error('action must be save or delete');
276
+ }
277
+ writeFileSync(REGISTRY_FILE, JSON.stringify(registry, null, 2) + '\n');
278
+ res.writeHead(200, { 'Content-Type': 'application/json' });
279
+ res.end(JSON.stringify({ success: true }));
280
+ notifyClients();
281
+ } else if (url.pathname === '/api/registry/models') {
282
+ const registry = JSON.parse(readFileSync(REGISTRY_FILE, 'utf8'));
283
+ const { tier, primary, fallback } = data;
284
+ const VALID_TIERS = ['reasoning', 'standard', 'fast'];
285
+ if (!VALID_TIERS.includes(tier)) throw new Error('tier must be reasoning, standard, or fast');
286
+ if (!primary || typeof primary !== 'string') throw new Error('primary model required');
287
+ if (!registry.models) registry.models = {};
288
+ registry.models[tier] = {
289
+ primary: String(primary),
290
+ fallback: Array.isArray(fallback) ? fallback.map(String).filter(Boolean) : [],
291
+ };
292
+ writeFileSync(REGISTRY_FILE, JSON.stringify(registry, null, 2) + '\n');
293
+ res.writeHead(200, { 'Content-Type': 'application/json' });
294
+ res.end(JSON.stringify({ success: true }));
295
+ notifyClients();
296
+ } else {
297
+ res.writeHead(404);
298
+ res.end('Not found');
299
+ }
300
+ } catch (err) {
301
+ res.writeHead(400, { 'Content-Type': 'application/json' });
302
+ res.end(JSON.stringify({ error: err.message }));
303
+ }
304
+ });
305
+ return;
306
+ }
307
+
308
+ // Static files
309
+ let filePath = url.pathname === '/' ? '/index.html' : url.pathname;
310
+ const fullPath = join(STATIC_DIR, filePath);
311
+
312
+ if (!fullPath.startsWith(STATIC_DIR)) {
313
+ res.writeHead(403);
314
+ res.end('Forbidden');
315
+ return;
316
+ }
317
+
318
+ if (!existsSync(fullPath)) {
319
+ res.writeHead(404);
320
+ res.end('Not found');
321
+ return;
322
+ }
323
+
324
+ const ext = extname(fullPath);
325
+ const mime = MIME[ext] ?? 'application/octet-stream';
326
+ res.writeHead(200, { 'Content-Type': mime });
327
+ res.end(readFileSync(fullPath));
328
+ });
329
+
330
+ watchFiles();
331
+
332
+ server.listen(PORT, '127.0.0.1', () => {
333
+ console.log(`Construct dashboard running at http://127.0.0.1:${PORT}`);
334
+ });