@geraldmaron/construct 1.0.4 → 1.0.6

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 (293) hide show
  1. package/README.md +57 -44
  2. package/agents/prompts/cx-accessibility.md +3 -3
  3. package/agents/prompts/cx-ai-engineer.md +7 -7
  4. package/agents/prompts/cx-architect.md +6 -6
  5. package/agents/prompts/cx-business-strategist.md +6 -6
  6. package/agents/prompts/cx-data-analyst.md +7 -7
  7. package/agents/prompts/cx-data-engineer.md +3 -3
  8. package/agents/prompts/cx-debugger.md +6 -6
  9. package/agents/prompts/cx-designer.md +5 -5
  10. package/agents/prompts/cx-devil-advocate.md +4 -4
  11. package/agents/prompts/cx-docs-keeper.md +5 -5
  12. package/agents/prompts/cx-engineer.md +5 -5
  13. package/agents/prompts/cx-evaluator.md +2 -2
  14. package/agents/prompts/cx-explorer.md +8 -8
  15. package/agents/prompts/cx-legal-compliance.md +2 -2
  16. package/agents/prompts/cx-operations.md +5 -5
  17. package/agents/prompts/cx-orchestrator.md +10 -10
  18. package/agents/prompts/cx-platform-engineer.md +5 -5
  19. package/agents/prompts/cx-product-manager.md +4 -4
  20. package/agents/prompts/cx-qa.md +8 -8
  21. package/agents/prompts/cx-rd-lead.md +9 -9
  22. package/agents/prompts/cx-release-manager.md +5 -5
  23. package/agents/prompts/cx-researcher.md +22 -22
  24. package/agents/prompts/cx-reviewer.md +7 -7
  25. package/agents/prompts/cx-security.md +10 -10
  26. package/agents/prompts/cx-sre.md +7 -7
  27. package/agents/prompts/cx-test-automation.md +3 -3
  28. package/agents/prompts/cx-trace-reviewer.md +8 -8
  29. package/agents/prompts/cx-ux-researcher.md +3 -3
  30. package/bin/construct +470 -4
  31. package/commands/build/feature.md +4 -4
  32. package/commands/build/fix.md +8 -8
  33. package/commands/design/access.md +3 -3
  34. package/commands/design/flow.md +3 -3
  35. package/commands/design/ui.md +4 -4
  36. package/commands/measure/experiment.md +5 -5
  37. package/commands/measure/metrics.md +3 -3
  38. package/commands/measure/results.md +4 -4
  39. package/commands/plan/api.md +3 -3
  40. package/commands/plan/challenge.md +3 -3
  41. package/commands/plan/decide.md +3 -3
  42. package/commands/plan/feature.md +7 -7
  43. package/commands/plan/requirements.md +3 -3
  44. package/commands/remember/context.md +5 -5
  45. package/commands/remember/handoff.md +3 -3
  46. package/commands/remember/runbook.md +3 -3
  47. package/commands/review/code.md +8 -8
  48. package/commands/review/quality.md +4 -4
  49. package/commands/review/security.md +3 -3
  50. package/commands/ship/ready.md +3 -3
  51. package/commands/ship/release.md +3 -3
  52. package/commands/ship/status.md +4 -4
  53. package/commands/understand/docs.md +3 -3
  54. package/commands/understand/research.md +5 -3
  55. package/commands/understand/this.md +4 -4
  56. package/commands/understand/why.md +8 -8
  57. package/commands/work/clean.md +14 -14
  58. package/commands/work/drive.md +10 -10
  59. package/commands/work/optimize-prompts.md +9 -9
  60. package/commands/work/parallel-review.md +8 -8
  61. package/db/schema/006_graph.sql +24 -0
  62. package/examples/provider-plugin/README.md +7 -7
  63. package/examples/seed-observations/README.md +6 -6
  64. package/examples/seed-observations/anti-patterns.md +14 -14
  65. package/examples/seed-observations/decisions.md +4 -4
  66. package/examples/seed-observations/patterns.md +14 -14
  67. package/lib/auto-docs.mjs +10 -5
  68. package/lib/cli-commands.mjs +45 -1
  69. package/lib/comment-lint.mjs +7 -1
  70. package/lib/config/schema.mjs +3 -0
  71. package/lib/flavors/loader.mjs +136 -0
  72. package/lib/hooks/agent-tracker.mjs +22 -3
  73. package/lib/hooks/pre-push-gate.mjs +14 -1
  74. package/lib/hooks/session-optimize.mjs +3 -2
  75. package/lib/hooks/session-reflect.mjs +68 -0
  76. package/lib/init-unified.mjs +25 -2
  77. package/lib/intake/classify.mjs +61 -183
  78. package/lib/intake/prepare.mjs +7 -0
  79. package/lib/intake/tables/creative.mjs +94 -0
  80. package/lib/intake/tables/operations.mjs +85 -0
  81. package/lib/intake/tables/research.mjs +85 -0
  82. package/lib/intake/tables/rnd.mjs +175 -0
  83. package/lib/knowledge/graph.mjs +213 -0
  84. package/lib/knowledge/research-store.mjs +109 -0
  85. package/lib/mcp/server.mjs +187 -1
  86. package/lib/mcp/tools/profile.mjs +270 -0
  87. package/lib/observation-store.mjs +19 -0
  88. package/lib/outcomes/aggregate.mjs +104 -0
  89. package/lib/outcomes/record.mjs +115 -0
  90. package/lib/parity.mjs +6 -9
  91. package/lib/profiles/lifecycle.mjs +388 -0
  92. package/lib/profiles/loader.mjs +123 -0
  93. package/lib/profiles/validate-custom.mjs +114 -0
  94. package/lib/reflect/extractor.mjs +193 -0
  95. package/lib/reflect.mjs +89 -2
  96. package/lib/sandbox.mjs +102 -0
  97. package/package.json +6 -1
  98. package/personas/construct.md +20 -20
  99. package/platforms/claude/CLAUDE.md +6 -6
  100. package/platforms/claude/settings.template.json +13 -0
  101. package/rules/common/agents.md +2 -2
  102. package/rules/common/beads-hygiene.md +11 -11
  103. package/rules/common/code-review.md +1 -1
  104. package/rules/common/coding-style.md +1 -1
  105. package/rules/common/comments.md +8 -8
  106. package/rules/common/commit-approval.md +4 -4
  107. package/rules/common/cx-agent-routing.md +2 -2
  108. package/rules/common/cx-skill-routing.md +2 -2
  109. package/rules/common/development-workflow.md +1 -1
  110. package/rules/common/doc-ownership.md +2 -2
  111. package/rules/common/efficiency.md +3 -3
  112. package/rules/common/framing.md +1 -1
  113. package/rules/common/git-workflow.md +1 -1
  114. package/rules/common/patterns.md +1 -1
  115. package/rules/common/performance.md +1 -1
  116. package/rules/common/release-gates.md +7 -7
  117. package/rules/common/research.md +4 -4
  118. package/rules/common/security.md +1 -1
  119. package/rules/common/skill-composition.md +8 -8
  120. package/rules/common/testing.md +1 -1
  121. package/rules/golang/coding-style.md +2 -2
  122. package/rules/golang/hooks.md +1 -1
  123. package/rules/golang/patterns.md +1 -1
  124. package/rules/golang/security.md +1 -1
  125. package/rules/golang/testing.md +1 -1
  126. package/rules/python/coding-style.md +1 -1
  127. package/rules/python/hooks.md +1 -1
  128. package/rules/python/patterns.md +1 -1
  129. package/rules/python/security.md +1 -1
  130. package/rules/python/testing.md +1 -1
  131. package/rules/swift/coding-style.md +3 -3
  132. package/rules/swift/hooks.md +2 -2
  133. package/rules/swift/patterns.md +2 -2
  134. package/rules/swift/security.md +4 -4
  135. package/rules/swift/testing.md +2 -2
  136. package/rules/typescript/coding-style.md +1 -1
  137. package/rules/typescript/hooks.md +1 -1
  138. package/rules/typescript/patterns.md +1 -1
  139. package/rules/typescript/security.md +1 -1
  140. package/rules/typescript/testing.md +1 -1
  141. package/rules/web/coding-style.md +1 -1
  142. package/rules/web/design-quality.md +1 -1
  143. package/rules/web/hooks.md +1 -1
  144. package/rules/web/patterns.md +1 -1
  145. package/rules/web/performance.md +1 -1
  146. package/rules/web/security.md +1 -1
  147. package/rules/web/testing.md +1 -1
  148. package/scripts/sync-agents.mjs +11 -0
  149. package/skills/ai/agent-dev.md +1 -1
  150. package/skills/ai/llm-security.md +1 -1
  151. package/skills/ai/ml-ops.md +6 -6
  152. package/skills/ai/orchestration-workflow.md +1 -1
  153. package/skills/ai/prompt-and-eval.md +1 -1
  154. package/skills/ai/prompt-optimizer.md +13 -13
  155. package/skills/ai/rag-system.md +1 -1
  156. package/skills/architecture/api-design.md +1 -1
  157. package/skills/architecture/caching.md +1 -1
  158. package/skills/architecture/cloud-native.md +1 -1
  159. package/skills/architecture/message-queue.md +1 -1
  160. package/skills/architecture/security-arch.md +1 -1
  161. package/skills/compliance/ai-disclosure.md +1 -1
  162. package/skills/compliance/data-privacy.md +1 -1
  163. package/skills/compliance/license-audit.md +2 -2
  164. package/skills/compliance/regulatory-review.md +1 -1
  165. package/skills/development/cpp.md +1 -1
  166. package/skills/development/go.md +1 -1
  167. package/skills/development/java.md +1 -1
  168. package/skills/development/kotlin.md +9 -9
  169. package/skills/development/mobile-crossplatform.md +13 -13
  170. package/skills/development/python.md +1 -1
  171. package/skills/development/rust.md +1 -1
  172. package/skills/development/shell.md +1 -1
  173. package/skills/development/swift.md +6 -6
  174. package/skills/development/typescript.md +1 -1
  175. package/skills/devops/ci-cd.md +5 -5
  176. package/skills/devops/containerization.md +9 -9
  177. package/skills/devops/cost-optimization.md +1 -1
  178. package/skills/devops/data-engineering.md +2 -2
  179. package/skills/devops/database.md +1 -1
  180. package/skills/devops/dependency-management.md +3 -3
  181. package/skills/devops/devsecops.md +1 -1
  182. package/skills/devops/git-workflow.md +1 -1
  183. package/skills/devops/incident-response.md +18 -18
  184. package/skills/devops/monorepo.md +5 -5
  185. package/skills/devops/observability.md +1 -1
  186. package/skills/devops/performance.md +1 -1
  187. package/skills/devops/testing.md +1 -1
  188. package/skills/docs/adr-workflow.md +2 -2
  189. package/skills/docs/backlog-proposal-workflow.md +1 -1
  190. package/skills/docs/customer-profile-workflow.md +1 -1
  191. package/skills/docs/document-ingest-workflow.md +1 -1
  192. package/skills/docs/evidence-ingest-workflow.md +1 -1
  193. package/skills/docs/init-docs.md +15 -15
  194. package/skills/docs/init-project.md +1 -1
  195. package/skills/docs/prd-workflow.md +3 -3
  196. package/skills/docs/prfaq-workflow.md +1 -1
  197. package/skills/docs/product-intelligence-review.md +1 -1
  198. package/skills/docs/product-intelligence-workflow.md +1 -1
  199. package/skills/docs/product-signal-workflow.md +9 -9
  200. package/skills/docs/research-workflow.md +10 -10
  201. package/skills/docs/runbook-workflow.md +2 -2
  202. package/skills/docs/strategy-workflow.md +3 -3
  203. package/skills/exploration/repo-map.md +11 -11
  204. package/skills/frameworks/django.md +15 -15
  205. package/skills/frameworks/nextjs.md +16 -16
  206. package/skills/frameworks/react.md +12 -12
  207. package/skills/frameworks/spring-boot.md +12 -12
  208. package/skills/frontend-design/accessibility.md +6 -6
  209. package/skills/frontend-design/component-patterns.md +1 -1
  210. package/skills/frontend-design/engineering.md +1 -1
  211. package/skills/frontend-design/state-management.md +1 -1
  212. package/skills/frontend-design/ui-aesthetics.md +1 -1
  213. package/skills/frontend-design/ux-principles.md +1 -1
  214. package/skills/operating/orchestration-reference.md +27 -27
  215. package/skills/quality-gates/review-work.md +3 -3
  216. package/skills/quality-gates/verify-change.md +1 -1
  217. package/skills/quality-gates/verify-module.md +1 -1
  218. package/skills/quality-gates/verify-quality.md +1 -1
  219. package/skills/quality-gates/verify-security.md +1 -1
  220. package/skills/roles/architect.ai-systems.md +4 -2
  221. package/skills/roles/architect.data.md +4 -2
  222. package/skills/roles/architect.enterprise.md +4 -2
  223. package/skills/roles/architect.integration.md +4 -2
  224. package/skills/roles/architect.md +7 -5
  225. package/skills/roles/architect.platform.md +4 -2
  226. package/skills/roles/data-analyst.experiment.md +4 -2
  227. package/skills/roles/data-analyst.md +9 -7
  228. package/skills/roles/data-analyst.product-intelligence.md +4 -2
  229. package/skills/roles/data-analyst.product.md +4 -2
  230. package/skills/roles/data-analyst.telemetry.md +4 -2
  231. package/skills/roles/data-engineer.pipeline.md +4 -2
  232. package/skills/roles/data-engineer.vector-retrieval.md +4 -2
  233. package/skills/roles/data-engineer.warehouse.md +4 -2
  234. package/skills/roles/debugger.md +7 -5
  235. package/skills/roles/designer.accessibility.md +4 -2
  236. package/skills/roles/designer.md +10 -8
  237. package/skills/roles/engineer.ai.md +4 -2
  238. package/skills/roles/engineer.data.md +5 -3
  239. package/skills/roles/engineer.md +14 -12
  240. package/skills/roles/engineer.platform.md +5 -3
  241. package/skills/roles/operator.docs.md +6 -4
  242. package/skills/roles/operator.md +6 -4
  243. package/skills/roles/operator.release.md +4 -2
  244. package/skills/roles/operator.sre.md +5 -3
  245. package/skills/roles/orchestrator.md +5 -3
  246. package/skills/roles/product-manager.ai-product.md +4 -2
  247. package/skills/roles/product-manager.business-strategy.md +4 -2
  248. package/skills/roles/product-manager.enterprise.md +4 -2
  249. package/skills/roles/product-manager.growth.md +4 -2
  250. package/skills/roles/product-manager.md +6 -4
  251. package/skills/roles/product-manager.platform.md +4 -2
  252. package/skills/roles/product-manager.product.md +4 -2
  253. package/skills/roles/qa.ai-eval.md +4 -2
  254. package/skills/roles/qa.api-contract.md +4 -2
  255. package/skills/roles/qa.data-pipeline.md +4 -2
  256. package/skills/roles/qa.md +7 -5
  257. package/skills/roles/qa.test-automation.md +5 -3
  258. package/skills/roles/qa.web-ui.md +4 -2
  259. package/skills/roles/researcher.explorer.md +4 -2
  260. package/skills/roles/researcher.md +11 -9
  261. package/skills/roles/researcher.ux.md +4 -2
  262. package/skills/roles/reviewer.devil-advocate.md +4 -2
  263. package/skills/roles/reviewer.evaluator.md +4 -2
  264. package/skills/roles/reviewer.md +14 -12
  265. package/skills/roles/reviewer.trace.md +4 -2
  266. package/skills/roles/security.ai.md +4 -2
  267. package/skills/roles/security.appsec.md +4 -2
  268. package/skills/roles/security.cloud.md +4 -2
  269. package/skills/roles/security.legal-compliance.md +4 -2
  270. package/skills/roles/security.md +7 -5
  271. package/skills/roles/security.privacy.md +4 -2
  272. package/skills/roles/security.supply-chain.md +4 -2
  273. package/skills/routing.md +14 -14
  274. package/skills/security/blue-team.md +1 -1
  275. package/skills/security/code-audit.md +1 -1
  276. package/skills/security/pentest.md +1 -1
  277. package/skills/security/red-team.md +1 -1
  278. package/skills/security/threat-intel.md +1 -1
  279. package/skills/security/vuln-research.md +1 -1
  280. package/skills/utility/clean-code.md +2 -2
  281. package/templates/docs/changelog-entry.md +1 -1
  282. package/templates/docs/construct_guide.md +13 -13
  283. package/templates/docs/meta-prd.md +16 -16
  284. package/templates/docs/one-pager.md +1 -1
  285. package/templates/docs/persona-artifact.md +36 -0
  286. package/templates/docs/prd-business.md +1 -1
  287. package/templates/docs/prd-platform.md +1 -1
  288. package/templates/docs/prd.md +17 -17
  289. package/templates/docs/research-brief.md +8 -8
  290. package/templates/docs/research-finding.md +26 -0
  291. package/templates/docs/rfc.md +1 -1
  292. package/templates/docs/skill-artifact.md +27 -0
  293. package/templates/docs/strategy.md +1 -1
@@ -0,0 +1,85 @@
1
+ /**
2
+ * lib/intake/tables/research.mjs — Research intake classification.
3
+ *
4
+ * Loop: question → gather → analyze → synthesize → recommend. Covers analysis,
5
+ * market intelligence, evaluation, advisory work. Output is a written
6
+ * recommendation, not a shipped product.
7
+ */
8
+
9
+ export const INTAKE_TYPES = ['question', 'study', 'synthesis', 'report', 'experiment', 'ops', 'unknown'];
10
+ export const STAGES = ['question', 'gather', 'analyze', 'synthesize', 'recommend'];
11
+
12
+ export const UNKNOWN_TRIAGE = {
13
+ intakeType: 'unknown',
14
+ rdStage: 'unknown',
15
+ primaryOwner: 'researcher',
16
+ recommendedChain: ['researcher'],
17
+ recommendedAction: 'summarize',
18
+ risk: 'low',
19
+ requiresApproval: false,
20
+ };
21
+
22
+ export const CLASSIFICATION_TABLE = [
23
+ {
24
+ intakeType: 'study',
25
+ keywords: ['interview', 'user study', 'survey', 'focus group', 'usability test', 'diary study', 'ethnography'],
26
+ rdStage: 'gather',
27
+ primaryOwner: 'ux-researcher',
28
+ recommendedChain: ['ux-researcher', 'researcher'],
29
+ recommendedAction: 'create-experiment',
30
+ risk: 'low',
31
+ requiresApproval: false,
32
+ },
33
+ {
34
+ intakeType: 'experiment',
35
+ keywords: ['hypothesis', 'experiment', 'controlled trial', 'a/b test', 'pilot', 'falsifiable'],
36
+ rdStage: 'analyze',
37
+ primaryOwner: 'evaluator',
38
+ recommendedChain: ['evaluator', 'researcher', 'data-analyst'],
39
+ recommendedAction: 'evaluate',
40
+ risk: 'low',
41
+ requiresApproval: false,
42
+ },
43
+ {
44
+ intakeType: 'report',
45
+ keywords: ['report', 'finding', 'dashboard', 'metric', 'benchmark', 'baseline', 'comparison'],
46
+ rdStage: 'recommend',
47
+ primaryOwner: 'data-analyst',
48
+ recommendedChain: ['data-analyst', 'researcher', 'reviewer'],
49
+ recommendedAction: 'evaluate',
50
+ risk: 'low',
51
+ requiresApproval: false,
52
+ },
53
+ {
54
+ intakeType: 'synthesis',
55
+ keywords: ['synthesis', 'literature review', 'meta', 'state of the art', 'roundup', 'survey paper', 'taxonomy'],
56
+ rdStage: 'synthesize',
57
+ primaryOwner: 'researcher',
58
+ recommendedChain: ['researcher', 'reviewer'],
59
+ recommendedAction: 'research',
60
+ risk: 'low',
61
+ requiresApproval: false,
62
+ },
63
+ {
64
+ intakeType: 'question',
65
+ keywords: ['question', 'research question', 'what is', 'why does', 'how do', 'investigate', 'figure out', 'understand'],
66
+ rdStage: 'question',
67
+ primaryOwner: 'researcher',
68
+ recommendedChain: ['researcher'],
69
+ recommendedAction: 'clarify',
70
+ risk: 'low',
71
+ requiresApproval: false,
72
+ },
73
+ {
74
+ intakeType: 'ops',
75
+ keywords: ['process', 'methodology', 'template', 'protocol', 'consent form', 'data handling'],
76
+ rdStage: 'gather',
77
+ primaryOwner: 'operator',
78
+ recommendedChain: ['operator', 'researcher'],
79
+ recommendedAction: 'create-runbook',
80
+ risk: 'low',
81
+ requiresApproval: false,
82
+ },
83
+ ];
84
+
85
+ export default { INTAKE_TYPES, STAGES, CLASSIFICATION_TABLE, UNKNOWN_TRIAGE };
@@ -0,0 +1,175 @@
1
+ /**
2
+ * lib/intake/tables/rnd.mjs — RND intake classification table.
3
+ *
4
+ * Extracted from lib/intake/classify.mjs as part of B2. Behavior must remain
5
+ * byte-identical for RND inputs; tests/intake/golden-rnd.test.mjs is the
6
+ * regression guard.
7
+ *
8
+ * Schema: { INTAKE_TYPES, STAGES, CLASSIFICATION_TABLE, UNKNOWN_TRIAGE }.
9
+ * UNKNOWN_TRIAGE handles the no-match fallback for this profile.
10
+ */
11
+
12
+ export const INTAKE_TYPES = [
13
+ 'user-signal',
14
+ 'bug',
15
+ 'requirement',
16
+ 'research',
17
+ 'experiment',
18
+ 'eval-finding',
19
+ 'architecture',
20
+ 'incident',
21
+ 'launch-asset',
22
+ 'ops',
23
+ 'security',
24
+ 'legal-compliance',
25
+ 'unknown',
26
+ ];
27
+
28
+ export const STAGES = [
29
+ 'signal',
30
+ 'framing',
31
+ 'hypothesis',
32
+ 'research',
33
+ 'artifact',
34
+ 'design',
35
+ 'implementation',
36
+ 'evaluation',
37
+ 'release',
38
+ 'operations',
39
+ 'unknown',
40
+ ];
41
+
42
+ export const UNKNOWN_TRIAGE = {
43
+ intakeType: 'unknown',
44
+ rdStage: 'unknown',
45
+ primaryOwner: 'orchestrator',
46
+ recommendedChain: ['orchestrator'],
47
+ recommendedAction: 'summarize',
48
+ risk: 'low',
49
+ requiresApproval: false,
50
+ };
51
+
52
+ export const CLASSIFICATION_TABLE = [
53
+ {
54
+ intakeType: 'security',
55
+ keywords: ['security', 'secret', 'cve', 'vulnerability', 'vuln', 'exploit', 'leak', 'auth bypass', 'privilege escalation', 'sqli', 'xss', 'csrf', 'rce'],
56
+ rdStage: 'operations',
57
+ primaryOwner: 'security',
58
+ recommendedChain: ['security', 'engineer', 'reviewer'],
59
+ recommendedAction: 'diagnose',
60
+ risk: 'high',
61
+ requiresApproval: true,
62
+ },
63
+ {
64
+ intakeType: 'incident',
65
+ keywords: ['incident', 'outage', 'slo breach', 'sla breach', 'latency spike', 'availability', 'down', 'p0 ', 'p1 ', 'pagerduty', '5xx', 'oncall'],
66
+ rdStage: 'operations',
67
+ primaryOwner: 'sre',
68
+ recommendedChain: ['sre', 'debugger', 'platform-engineer'],
69
+ recommendedAction: 'create-runbook',
70
+ risk: 'high',
71
+ requiresApproval: true,
72
+ },
73
+ {
74
+ intakeType: 'legal-compliance',
75
+ keywords: ['gdpr', 'ccpa', 'hipaa', 'sox', 'soc2', 'license', 'lawsuit', 'dpa', 'data retention', 'pii', 'subpoena', 'compliance audit'],
76
+ rdStage: 'operations',
77
+ primaryOwner: 'legal-compliance',
78
+ recommendedChain: ['legal-compliance', 'security', 'product-manager'],
79
+ recommendedAction: 'clarify',
80
+ risk: 'high',
81
+ requiresApproval: true,
82
+ },
83
+ {
84
+ intakeType: 'architecture',
85
+ keywords: ['architecture', 'adr', 'rfc', 'interface', 'tradeoff', 'boundary', 'system design', 'data model', 'api contract', 'migration plan'],
86
+ rdStage: 'design',
87
+ primaryOwner: 'architect',
88
+ recommendedChain: ['architect', 'devil-advocate', 'engineer'],
89
+ recommendedAction: 'draft-rfc',
90
+ risk: 'medium',
91
+ requiresApproval: false,
92
+ },
93
+ {
94
+ intakeType: 'eval-finding',
95
+ keywords: ['eval', 'evaluation', 'hallucination', 'judge', 'trace', 'score regression', 'recall@', 'precision@', 'mrr', 'ndcg', 'failure case', 'rubric'],
96
+ rdStage: 'evaluation',
97
+ primaryOwner: 'evaluator',
98
+ recommendedChain: ['evaluator', 'ai-engineer', 'trace-reviewer'],
99
+ recommendedAction: 'evaluate',
100
+ risk: 'medium',
101
+ requiresApproval: false,
102
+ },
103
+ {
104
+ intakeType: 'bug',
105
+ keywords: ['bug', 'broken', 'error', 'stack trace', 'regression', 'crash', 'exception', 'fails', 'failing', 'throws', 'not working', 'reproduce', 'repro:'],
106
+ rdStage: 'implementation',
107
+ primaryOwner: 'debugger',
108
+ recommendedChain: ['debugger', 'engineer', 'qa', 'reviewer'],
109
+ recommendedAction: 'diagnose',
110
+ risk: 'medium',
111
+ requiresApproval: false,
112
+ },
113
+ {
114
+ intakeType: 'experiment',
115
+ keywords: ['hypothesis', 'experiment', 'spike', 'prototype', 'falsifiable', 'research question', 'a/b test', 'pilot'],
116
+ rdStage: 'hypothesis',
117
+ primaryOwner: 'rd-lead',
118
+ recommendedChain: ['rd-lead', 'researcher', 'evaluator'],
119
+ recommendedAction: 'create-experiment',
120
+ risk: 'low',
121
+ requiresApproval: false,
122
+ },
123
+ {
124
+ intakeType: 'launch-asset',
125
+ keywords: ['release', 'changelog', 'version bump', 'ship', 'launch', 'rollout', 'cut a release', 'rc1', 'rc2', 'release candidate'],
126
+ rdStage: 'release',
127
+ primaryOwner: 'release-manager',
128
+ recommendedChain: ['release-manager', 'qa', 'docs-keeper'],
129
+ recommendedAction: 'release-review',
130
+ risk: 'medium',
131
+ requiresApproval: false,
132
+ },
133
+ {
134
+ intakeType: 'research',
135
+ keywords: ['competitor', 'market', 'pricing', 'positioning', 'industry', 'state of the art', 'literature', 'benchmark study', 'desk research'],
136
+ rdStage: 'research',
137
+ primaryOwner: 'business-strategist',
138
+ recommendedChain: ['business-strategist', 'researcher', 'product-manager'],
139
+ recommendedAction: 'research',
140
+ risk: 'low',
141
+ requiresApproval: false,
142
+ },
143
+ {
144
+ intakeType: 'user-signal',
145
+ keywords: ['customer', 'feedback', 'pain point', 'user says', 'user feedback', 'support ticket', 'churn', 'nps', 'usability', 'frustrated'],
146
+ rdStage: 'signal',
147
+ primaryOwner: 'product-manager',
148
+ recommendedChain: ['product-manager', 'ux-researcher', 'researcher'],
149
+ recommendedAction: 'clarify',
150
+ risk: 'low',
151
+ requiresApproval: false,
152
+ },
153
+ {
154
+ intakeType: 'requirement',
155
+ keywords: ['acceptance criteria', 'requirement', 'must have', 'should have', 'feature request', 'prd', 'use case', 'success metric'],
156
+ rdStage: 'framing',
157
+ primaryOwner: 'product-manager',
158
+ recommendedChain: ['product-manager', 'architect', 'engineer'],
159
+ recommendedAction: 'draft-prd',
160
+ risk: 'low',
161
+ requiresApproval: false,
162
+ },
163
+ {
164
+ intakeType: 'ops',
165
+ keywords: ['runbook', 'cron', 'scheduled job', 'maintenance', 'backup', 'restore', 'capacity plan', 'cost optimization', 'dependency upgrade'],
166
+ rdStage: 'operations',
167
+ primaryOwner: 'operations',
168
+ recommendedChain: ['operations', 'sre', 'engineer'],
169
+ recommendedAction: 'create-runbook',
170
+ risk: 'low',
171
+ requiresApproval: false,
172
+ },
173
+ ];
174
+
175
+ export default { INTAKE_TYPES, STAGES, CLASSIFICATION_TABLE, UNKNOWN_TRIAGE };
@@ -0,0 +1,213 @@
1
+ /**
2
+ * lib/knowledge/graph.mjs — GraphRAG-style entity index + global query.
3
+ *
4
+ * Phase C9 foundations:
5
+ * - buildGraph(rootDir): derive nodes + edges from `.cx/observations/entities.json`.
6
+ * - detectCommunities(graph): pure-JS label propagation. Deterministic
7
+ * (sorted neighbor scan, lowest-id tiebreak). O(V+E) per iteration.
8
+ * - summarizeCommunity(community, entities): extractive summary over the
9
+ * member entities, ranked by degree centrality. Requires no LLM call,
10
+ * so the primitive works in solo mode without a provider configured.
11
+ * - askGlobal({ query, rootDir }): rank communities by overlap with the
12
+ * query terms + their members' search text, return the top communities
13
+ * with member entities and the question they best answer.
14
+ *
15
+ * Full Leiden (modularity optimization + refinement) would need an external
16
+ * graph library and is blocked by ADR-0001 (zero-npm-core). Label propagation
17
+ * is the deterministic alternative. Produces useful coarse communities on
18
+ * the small graphs Construct sessions accumulate (typically <500 entities).
19
+ * Swap implementations later by replacing `detectCommunities` only.
20
+ */
21
+ import { existsSync, readFileSync } from 'node:fs';
22
+ import path from 'node:path';
23
+ import { rankByBm25 } from '../storage/embeddings.mjs';
24
+
25
+ const OBS_DIR = '.cx/observations';
26
+ const ENTITIES_FILE = 'entities.json';
27
+
28
+ const MAX_LP_ITERATIONS = 30;
29
+ const MAX_COMMUNITY_TOP_TERMS = 8;
30
+ const MIN_COMMUNITY_SIZE = 2;
31
+
32
+ function readEntities(rootDir) {
33
+ const p = path.join(rootDir, OBS_DIR, ENTITIES_FILE);
34
+ if (!existsSync(p)) return [];
35
+ try { return JSON.parse(readFileSync(p, 'utf8')); } catch { return []; }
36
+ }
37
+
38
+ /**
39
+ * Build an undirected graph from the entity store. Nodes are entity names;
40
+ * edges come from `relatedEntities[]`. Returns:
41
+ * { nodes: Map<name, entity>, adj: Map<name, Set<name>> }
42
+ */
43
+ export function buildGraph(rootDir) {
44
+ const entities = readEntities(rootDir);
45
+ const nodes = new Map();
46
+ const adj = new Map();
47
+ for (const e of entities) {
48
+ if (!e?.name) continue;
49
+ nodes.set(e.name, e);
50
+ if (!adj.has(e.name)) adj.set(e.name, new Set());
51
+ }
52
+ for (const e of entities) {
53
+ if (!e?.name) continue;
54
+ for (const other of e.relatedEntities || []) {
55
+ if (!nodes.has(other)) continue;
56
+ adj.get(e.name).add(other);
57
+ adj.get(other).add(e.name);
58
+ }
59
+ }
60
+ return { nodes, adj };
61
+ }
62
+
63
+ /**
64
+ * Run synchronous label propagation. Each node starts with its own label;
65
+ * iteratively adopt the most frequent label among neighbors, with a sorted
66
+ * neighbor scan and lowest-id tiebreak so the result is deterministic.
67
+ *
68
+ * Returns: Map<name, communityId> plus a stable grouping.
69
+ */
70
+ export function detectCommunities(graph) {
71
+ const labels = new Map();
72
+ const sortedNodes = [...graph.nodes.keys()].sort();
73
+ for (const n of sortedNodes) labels.set(n, n);
74
+
75
+ for (let iter = 0; iter < MAX_LP_ITERATIONS; iter++) {
76
+ let changed = false;
77
+ for (const node of sortedNodes) {
78
+ const neighbors = [...(graph.adj.get(node) || [])].sort();
79
+ if (neighbors.length === 0) continue;
80
+
81
+ const counts = new Map();
82
+ for (const nb of neighbors) {
83
+ const lbl = labels.get(nb);
84
+ counts.set(lbl, (counts.get(lbl) || 0) + 1);
85
+ }
86
+
87
+ // highest-count label, lowest-id tiebreak
88
+ let bestLabel = labels.get(node);
89
+ let bestCount = -1;
90
+ const sortedLabels = [...counts.entries()].sort((a, b) => {
91
+ if (b[1] !== a[1]) return b[1] - a[1];
92
+ return a[0] < b[0] ? -1 : 1;
93
+ });
94
+ if (sortedLabels.length > 0) {
95
+ [bestLabel, bestCount] = sortedLabels[0];
96
+ }
97
+
98
+ if (bestCount > 0 && bestLabel !== labels.get(node)) {
99
+ labels.set(node, bestLabel);
100
+ changed = true;
101
+ }
102
+ }
103
+ if (!changed) break;
104
+ }
105
+
106
+ const communities = new Map();
107
+ for (const [node, label] of labels) {
108
+ if (!communities.has(label)) communities.set(label, []);
109
+ communities.get(label).push(node);
110
+ }
111
+ // Stable order: by size desc, then alphabetical on canonical label
112
+ const grouped = [...communities.entries()]
113
+ .map(([id, members]) => ({ id, members: members.sort() }))
114
+ .sort((a, b) => {
115
+ if (b.members.length !== a.members.length) return b.members.length - a.members.length;
116
+ return a.id < b.id ? -1 : 1;
117
+ });
118
+ return { labels, communities: grouped };
119
+ }
120
+
121
+ /**
122
+ * Extractive community summary. Picks the top-N members by intra-community
123
+ * degree (the "central" nodes of the cluster) and harvests their summaries
124
+ * into a short text block. Deterministic and dependency-free.
125
+ */
126
+ export function summarizeCommunity(group, graph) {
127
+ if (!group?.members?.length) return { id: group?.id, size: 0, topMembers: [], summary: '' };
128
+ const memberSet = new Set(group.members);
129
+ const scored = group.members.map((name) => {
130
+ const neighbors = graph.adj.get(name) || new Set();
131
+ let degree = 0;
132
+ for (const nb of neighbors) if (memberSet.has(nb)) degree++;
133
+ return { name, degree };
134
+ }).sort((a, b) => {
135
+ if (b.degree !== a.degree) return b.degree - a.degree;
136
+ return a.name < b.name ? -1 : 1;
137
+ });
138
+
139
+ const topMembers = scored.slice(0, MAX_COMMUNITY_TOP_TERMS);
140
+ const lines = [];
141
+ for (const { name } of topMembers) {
142
+ const entity = graph.nodes.get(name);
143
+ if (!entity) continue;
144
+ const summary = (entity.summary || '').trim();
145
+ if (summary) lines.push(`- ${entity.name}: ${summary}`);
146
+ else lines.push(`- ${entity.name}`);
147
+ }
148
+ return {
149
+ id: group.id,
150
+ size: group.members.length,
151
+ topMembers: topMembers.map((m) => m.name),
152
+ summary: lines.join('\n'),
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Global query over the entity graph. Builds the graph, detects communities,
158
+ * scores each community by BM25 over its concatenated member summaries, and
159
+ * returns the top communities with members.
160
+ *
161
+ * Intentionally returns structured data instead of a synthesized prose
162
+ * answer: the LM-based response synthesis is the next layer up and belongs
163
+ * in the persona prompt, not in this retrieval primitive. Solo mode can
164
+ * consume the raw result without calling an external model.
165
+ *
166
+ * @param {object} args
167
+ * @param {string} args.query - natural-language question
168
+ * @param {string} args.rootDir - project root containing `.cx/observations/`
169
+ * @param {number} [args.topK=5] - max communities to return
170
+ * @param {number} [args.minSize=MIN_COMMUNITY_SIZE] - skip singleton communities
171
+ * @returns {{ query, communities: Array<{ id, size, topMembers, summary, score }>, totalEntities, totalCommunities }}
172
+ */
173
+ export function askGlobal({ query, rootDir, topK = 5, minSize = MIN_COMMUNITY_SIZE } = {}) {
174
+ if (!query || typeof query !== 'string') {
175
+ return { query: query || '', communities: [], totalEntities: 0, totalCommunities: 0 };
176
+ }
177
+ const graph = buildGraph(rootDir);
178
+ if (graph.nodes.size === 0) {
179
+ return { query, communities: [], totalEntities: 0, totalCommunities: 0 };
180
+ }
181
+
182
+ const { communities } = detectCommunities(graph);
183
+ const eligible = communities.filter((g) => g.members.length >= minSize);
184
+ if (eligible.length === 0) {
185
+ return {
186
+ query,
187
+ communities: [],
188
+ totalEntities: graph.nodes.size,
189
+ totalCommunities: communities.length,
190
+ };
191
+ }
192
+
193
+ const summaries = eligible.map((group) => summarizeCommunity(group, graph));
194
+ const docs = summaries.map((s, i) => ({
195
+ id: String(i),
196
+ text: `${s.topMembers.join(' ')}\n${s.summary}`,
197
+ }));
198
+ const ranked = rankByBm25(docs, query, { limit: topK });
199
+
200
+ const out = [];
201
+ for (const r of ranked) {
202
+ const summary = summaries[Number(r.id)];
203
+ if (!summary) continue;
204
+ out.push({ ...summary, score: r.score });
205
+ }
206
+
207
+ return {
208
+ query,
209
+ communities: out,
210
+ totalEntities: graph.nodes.size,
211
+ totalCommunities: communities.length,
212
+ };
213
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * lib/knowledge/research-store.mjs — Persist research findings into the knowledge base.
3
+ *
4
+ * Wraps the existing document-ingest pipeline so research output written via
5
+ * `construct knowledge add --source=research` lands in
6
+ * `.cx/knowledge/external/research/<slug>.md` with research-specific frontmatter
7
+ * (topic, confidence, sources, expiresAt, profile). The file is then synced into
8
+ * the SQL/vector index via the standard `syncFileStateToSql` path.
9
+ *
10
+ * Schema (frontmatter):
11
+ * kind: research-finding
12
+ * topic: string
13
+ * confidence: confirmed | inferred | weak
14
+ * sources: [{ url, accessedAt, span? }]
15
+ * expiresAt: ISO date (default +90d)
16
+ * profile: <profile-id>
17
+ */
18
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
19
+ import { dirname, join } from 'node:path';
20
+ import { syncFileStateToSql } from '../storage/sync.mjs';
21
+ import { resolveActiveProfile } from '../profiles/loader.mjs';
22
+
23
+ const ROOT = '.cx/knowledge/external/research';
24
+ const MAX_BYTES = 50 * 1024;
25
+ const DEFAULT_TTL_DAYS = 90;
26
+
27
+ const VALID_CONFIDENCE = new Set(['confirmed', 'inferred', 'weak']);
28
+
29
+ /**
30
+ * @param {object} args
31
+ * @param {string} args.cwd - project root
32
+ * @param {string} args.slug - filename slug (lowercase, hyphenated)
33
+ * @param {string} args.topic - human-readable topic line
34
+ * @param {string} args.body - the FINDINGS / INFERENCES / GAPS / RECOMMENDATION block
35
+ * @param {string} [args.confidence] - confirmed | inferred | weak
36
+ * @param {Array<{url:string,accessedAt?:string,span?:string}>} [args.sources]
37
+ * @param {number} [args.ttlDays] - override default expiry
38
+ * @returns {Promise<{path:string,bytes:number}>}
39
+ */
40
+ export async function addResearchFinding({
41
+ cwd,
42
+ slug,
43
+ topic,
44
+ body,
45
+ confidence = 'inferred',
46
+ sources = [],
47
+ ttlDays = DEFAULT_TTL_DAYS,
48
+ }) {
49
+ if (!cwd) throw new Error('addResearchFinding: cwd is required');
50
+ if (!slug || !/^[a-z0-9][a-z0-9-]{0,60}$/.test(slug)) {
51
+ throw new Error('addResearchFinding: slug must be lowercase, hyphenated, max 60 chars');
52
+ }
53
+ if (!topic || typeof topic !== 'string') {
54
+ throw new Error('addResearchFinding: topic is required');
55
+ }
56
+ if (!body || typeof body !== 'string') {
57
+ throw new Error('addResearchFinding: body is required');
58
+ }
59
+ if (!VALID_CONFIDENCE.has(confidence)) {
60
+ throw new Error(`addResearchFinding: confidence must be one of ${Array.from(VALID_CONFIDENCE).join(', ')}`);
61
+ }
62
+ if (confidence === 'confirmed' && (!Array.isArray(sources) || sources.length === 0)) {
63
+ throw new Error('addResearchFinding: confidence=confirmed requires at least one source');
64
+ }
65
+
66
+ const profile = resolveActiveProfile(cwd);
67
+ const now = new Date();
68
+ const expiresAt = new Date(now.getTime() + ttlDays * 24 * 60 * 60 * 1000).toISOString();
69
+
70
+ const outDir = join(cwd, ROOT);
71
+ mkdirSync(outDir, { recursive: true });
72
+ const outPath = join(outDir, `${slug}.md`);
73
+
74
+ const fmSources = (Array.isArray(sources) ? sources : [])
75
+ .filter((s) => s && typeof s.url === 'string')
76
+ .map((s) => ({
77
+ url: s.url,
78
+ accessedAt: s.accessedAt || now.toISOString(),
79
+ ...(s.span ? { span: String(s.span).slice(0, 200) } : {}),
80
+ }));
81
+
82
+ const frontmatter = [
83
+ '---',
84
+ 'kind: research-finding',
85
+ `topic: ${JSON.stringify(topic)}`,
86
+ `confidence: ${confidence}`,
87
+ `sources: ${JSON.stringify(fmSources)}`,
88
+ `created: ${now.toISOString()}`,
89
+ `expiresAt: ${expiresAt}`,
90
+ `profile: ${profile?.id ?? 'rnd'}`,
91
+ '---',
92
+ '',
93
+ ].join('\n');
94
+
95
+ const fullText = frontmatter + body.trim() + '\n';
96
+ if (Buffer.byteLength(fullText, 'utf8') > MAX_BYTES) {
97
+ throw new Error(`addResearchFinding: file exceeds ${MAX_BYTES} bytes`);
98
+ }
99
+
100
+ writeFileSync(outPath, fullText);
101
+
102
+ // Best-effort sync into SQL/vector index. The file is the source of truth;
103
+ // index lag never blocks the operator.
104
+ try {
105
+ await syncFileStateToSql(cwd, { project: profile?.id ?? 'rnd' });
106
+ } catch { /* best effort */ }
107
+
108
+ return { path: outPath, bytes: Buffer.byteLength(fullText, 'utf8') };
109
+ }