@geraldmaron/construct 1.0.5 → 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 (209) hide show
  1. package/README.md +8 -4
  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 +235 -21
  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/this.md +4 -4
  55. package/commands/understand/why.md +8 -8
  56. package/commands/work/clean.md +14 -14
  57. package/commands/work/drive.md +10 -10
  58. package/commands/work/optimize-prompts.md +9 -9
  59. package/commands/work/parallel-review.md +8 -8
  60. package/db/schema/006_graph.sql +24 -0
  61. package/examples/provider-plugin/README.md +7 -7
  62. package/examples/seed-observations/README.md +6 -6
  63. package/examples/seed-observations/anti-patterns.md +14 -14
  64. package/examples/seed-observations/decisions.md +4 -4
  65. package/examples/seed-observations/patterns.md +14 -14
  66. package/lib/auto-docs.mjs +8 -3
  67. package/lib/cli-commands.mjs +2 -2
  68. package/lib/flavors/loader.mjs +1 -1
  69. package/lib/knowledge/graph.mjs +213 -0
  70. package/lib/mcp/server.mjs +187 -1
  71. package/lib/mcp/tools/profile.mjs +270 -0
  72. package/package.json +1 -1
  73. package/platforms/claude/CLAUDE.md +6 -6
  74. package/rules/common/agents.md +2 -2
  75. package/rules/common/beads-hygiene.md +11 -11
  76. package/rules/common/code-review.md +1 -1
  77. package/rules/common/coding-style.md +1 -1
  78. package/rules/common/comments.md +8 -8
  79. package/rules/common/commit-approval.md +4 -4
  80. package/rules/common/cx-agent-routing.md +2 -2
  81. package/rules/common/cx-skill-routing.md +2 -2
  82. package/rules/common/development-workflow.md +1 -1
  83. package/rules/common/doc-ownership.md +2 -2
  84. package/rules/common/efficiency.md +3 -3
  85. package/rules/common/framing.md +1 -1
  86. package/rules/common/git-workflow.md +1 -1
  87. package/rules/common/patterns.md +1 -1
  88. package/rules/common/performance.md +1 -1
  89. package/rules/common/release-gates.md +7 -7
  90. package/rules/common/research.md +4 -4
  91. package/rules/common/security.md +1 -1
  92. package/rules/common/skill-composition.md +8 -8
  93. package/rules/common/testing.md +1 -1
  94. package/rules/golang/coding-style.md +2 -2
  95. package/rules/golang/hooks.md +1 -1
  96. package/rules/golang/patterns.md +1 -1
  97. package/rules/golang/security.md +1 -1
  98. package/rules/golang/testing.md +1 -1
  99. package/rules/python/coding-style.md +1 -1
  100. package/rules/python/hooks.md +1 -1
  101. package/rules/python/patterns.md +1 -1
  102. package/rules/python/security.md +1 -1
  103. package/rules/python/testing.md +1 -1
  104. package/rules/swift/coding-style.md +3 -3
  105. package/rules/swift/hooks.md +2 -2
  106. package/rules/swift/patterns.md +2 -2
  107. package/rules/swift/security.md +4 -4
  108. package/rules/swift/testing.md +2 -2
  109. package/rules/typescript/coding-style.md +1 -1
  110. package/rules/typescript/hooks.md +1 -1
  111. package/rules/typescript/patterns.md +1 -1
  112. package/rules/typescript/security.md +1 -1
  113. package/rules/typescript/testing.md +1 -1
  114. package/rules/web/coding-style.md +1 -1
  115. package/rules/web/design-quality.md +1 -1
  116. package/rules/web/hooks.md +1 -1
  117. package/rules/web/patterns.md +1 -1
  118. package/rules/web/performance.md +1 -1
  119. package/rules/web/security.md +1 -1
  120. package/rules/web/testing.md +1 -1
  121. package/skills/ai/agent-dev.md +1 -1
  122. package/skills/ai/llm-security.md +1 -1
  123. package/skills/ai/ml-ops.md +6 -6
  124. package/skills/ai/orchestration-workflow.md +1 -1
  125. package/skills/ai/prompt-and-eval.md +1 -1
  126. package/skills/ai/prompt-optimizer.md +13 -13
  127. package/skills/ai/rag-system.md +1 -1
  128. package/skills/architecture/api-design.md +1 -1
  129. package/skills/architecture/caching.md +1 -1
  130. package/skills/architecture/cloud-native.md +1 -1
  131. package/skills/architecture/message-queue.md +1 -1
  132. package/skills/architecture/security-arch.md +1 -1
  133. package/skills/compliance/ai-disclosure.md +1 -1
  134. package/skills/compliance/data-privacy.md +1 -1
  135. package/skills/compliance/license-audit.md +2 -2
  136. package/skills/compliance/regulatory-review.md +1 -1
  137. package/skills/development/cpp.md +1 -1
  138. package/skills/development/go.md +1 -1
  139. package/skills/development/java.md +1 -1
  140. package/skills/development/kotlin.md +9 -9
  141. package/skills/development/mobile-crossplatform.md +13 -13
  142. package/skills/development/python.md +1 -1
  143. package/skills/development/rust.md +1 -1
  144. package/skills/development/shell.md +1 -1
  145. package/skills/development/swift.md +6 -6
  146. package/skills/development/typescript.md +1 -1
  147. package/skills/devops/ci-cd.md +5 -5
  148. package/skills/devops/containerization.md +9 -9
  149. package/skills/devops/cost-optimization.md +1 -1
  150. package/skills/devops/data-engineering.md +2 -2
  151. package/skills/devops/database.md +1 -1
  152. package/skills/devops/dependency-management.md +3 -3
  153. package/skills/devops/devsecops.md +1 -1
  154. package/skills/devops/git-workflow.md +1 -1
  155. package/skills/devops/incident-response.md +18 -18
  156. package/skills/devops/monorepo.md +5 -5
  157. package/skills/devops/observability.md +1 -1
  158. package/skills/devops/performance.md +1 -1
  159. package/skills/devops/testing.md +1 -1
  160. package/skills/docs/adr-workflow.md +2 -2
  161. package/skills/docs/backlog-proposal-workflow.md +1 -1
  162. package/skills/docs/customer-profile-workflow.md +1 -1
  163. package/skills/docs/document-ingest-workflow.md +1 -1
  164. package/skills/docs/evidence-ingest-workflow.md +1 -1
  165. package/skills/docs/init-docs.md +15 -15
  166. package/skills/docs/init-project.md +1 -1
  167. package/skills/docs/prd-workflow.md +3 -3
  168. package/skills/docs/prfaq-workflow.md +1 -1
  169. package/skills/docs/product-intelligence-review.md +1 -1
  170. package/skills/docs/product-intelligence-workflow.md +1 -1
  171. package/skills/docs/product-signal-workflow.md +9 -9
  172. package/skills/docs/research-workflow.md +10 -10
  173. package/skills/docs/runbook-workflow.md +2 -2
  174. package/skills/docs/strategy-workflow.md +3 -3
  175. package/skills/exploration/repo-map.md +11 -11
  176. package/skills/frameworks/django.md +15 -15
  177. package/skills/frameworks/nextjs.md +16 -16
  178. package/skills/frameworks/react.md +12 -12
  179. package/skills/frameworks/spring-boot.md +12 -12
  180. package/skills/frontend-design/accessibility.md +6 -6
  181. package/skills/frontend-design/component-patterns.md +1 -1
  182. package/skills/frontend-design/engineering.md +1 -1
  183. package/skills/frontend-design/state-management.md +1 -1
  184. package/skills/frontend-design/ui-aesthetics.md +1 -1
  185. package/skills/frontend-design/ux-principles.md +1 -1
  186. package/skills/operating/orchestration-reference.md +27 -27
  187. package/skills/quality-gates/review-work.md +3 -3
  188. package/skills/quality-gates/verify-change.md +1 -1
  189. package/skills/quality-gates/verify-module.md +1 -1
  190. package/skills/quality-gates/verify-quality.md +1 -1
  191. package/skills/quality-gates/verify-security.md +1 -1
  192. package/skills/routing.md +14 -14
  193. package/skills/security/blue-team.md +1 -1
  194. package/skills/security/code-audit.md +1 -1
  195. package/skills/security/pentest.md +1 -1
  196. package/skills/security/red-team.md +1 -1
  197. package/skills/security/threat-intel.md +1 -1
  198. package/skills/security/vuln-research.md +1 -1
  199. package/skills/utility/clean-code.md +2 -2
  200. package/templates/docs/changelog-entry.md +1 -1
  201. package/templates/docs/construct_guide.md +13 -13
  202. package/templates/docs/meta-prd.md +16 -16
  203. package/templates/docs/one-pager.md +1 -1
  204. package/templates/docs/prd-business.md +1 -1
  205. package/templates/docs/prd-platform.md +1 -1
  206. package/templates/docs/prd.md +17 -17
  207. package/templates/docs/research-brief.md +8 -8
  208. package/templates/docs/rfc.md +1 -1
  209. package/templates/docs/strategy.md +1 -1
@@ -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
+ }
@@ -3,7 +3,7 @@
3
3
  * lib/mcp/server.mjs — Construct MCP server: tool registry and request dispatcher.
4
4
  *
5
5
  * Thin dispatcher only — all tool implementations live in lib/mcp/tools/*.mjs.
6
- * Registers 40 tools across 7 modules: project, document, storage, skills, workflow, telemetry, memory.
6
+ * Registers 52 tools across 8 modules: project, document, storage, skills, workflow, telemetry, memory, profile.
7
7
  * Consumed by Claude Code, OpenCode, and any MCP-compatible host.
8
8
  */
9
9
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
@@ -48,6 +48,12 @@ import {
48
48
  memorySearch, memoryAddObservations, memoryCreateEntities, memoryRecent,
49
49
  sessionList, sessionLoad, sessionSearch, sessionSave, rovoSearch,
50
50
  } from './tools/memory.mjs';
51
+ import {
52
+ profileShow, profileList, profileDrafts, profileHealthTool,
53
+ outcomesSummary, outcomesRecord, knowledgeAdd,
54
+ profileCreate, profileArchive, sandboxList, learningStatus,
55
+ knowledgeGraphAsk,
56
+ } from './tools/profile.mjs';
51
57
 
52
58
  const DEFAULT_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
53
59
  const ROOT_DIR = resolve(process.env.CX_TOOLKIT_DIR || DEFAULT_ROOT_DIR);
@@ -711,6 +717,174 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
711
717
  },
712
718
  },
713
719
  },
720
+ {
721
+ name: 'profile_show',
722
+ description: 'Return the active Construct org profile (id, displayName, roles, departments, intake taxonomy, doc templates). Use when a specialist needs to know which role set, classification taxonomy, or doc templates apply before drafting work.',
723
+ inputSchema: {
724
+ type: 'object',
725
+ properties: {
726
+ cwd: { type: 'string', description: 'Project root (default: server cwd).' },
727
+ id: { type: 'string', description: 'Force a specific profile id instead of resolving from config.' },
728
+ },
729
+ },
730
+ },
731
+ {
732
+ name: 'profile_list',
733
+ description: 'List the curated org profile catalog (rnd, operations, creative, research) with role/department counts. Use to discover which profiles are available before suggesting `construct profile set`.',
734
+ inputSchema: { type: 'object', properties: {} },
735
+ },
736
+ {
737
+ name: 'profile_drafts',
738
+ description: 'List in-progress draft profiles under `.cx/profiles/draft-*` and any user-defined custom profile at `.cx/profile.json`. Use to see what profile work is pending before scaffolding another draft.',
739
+ inputSchema: {
740
+ type: 'object',
741
+ properties: { cwd: { type: 'string', description: 'Project root (default: server cwd).' } },
742
+ },
743
+ },
744
+ {
745
+ name: 'profile_health',
746
+ description: 'Per-profile health rollup over a window: observation count, per-role outcome runs and success rates. Use to check whether a profile is producing data before recommending changes or archive.',
747
+ inputSchema: {
748
+ type: 'object',
749
+ properties: {
750
+ cwd: { type: 'string', description: 'Project root (default: server cwd).' },
751
+ id: { type: 'string', description: 'Profile id (default: active profile).' },
752
+ window_days: { type: 'number', description: 'Window in days (default 30).' },
753
+ },
754
+ },
755
+ },
756
+ {
757
+ name: 'outcomes_summary',
758
+ description: 'Read `.cx/outcomes/_summary.json` (per-role success rate, 30-day trend). Pass `aggregate=true` to rebuild the summary from JSONL outcome files first. Use to ground tiebreakers and improvement suggestions in real specialist performance.',
759
+ inputSchema: {
760
+ type: 'object',
761
+ properties: {
762
+ cwd: { type: 'string', description: 'Project root (default: server cwd).' },
763
+ aggregate: { type: 'boolean', description: 'Rebuild `_summary.json` before reading (default false).' },
764
+ },
765
+ },
766
+ },
767
+ {
768
+ name: 'outcomes_record',
769
+ description: 'Append a specialist outcome line to `.cx/outcomes/<role>.jsonl` (writes durable state — requires `confirm=true`). Use when a specialist wants to self-report success/failure outside the automatic agent-tracker path.',
770
+ inputSchema: {
771
+ type: 'object',
772
+ required: ['confirm', 'role', 'success'],
773
+ properties: {
774
+ confirm: { type: 'boolean', description: 'Must be true.' },
775
+ cwd: { type: 'string' },
776
+ role: { type: 'string', description: 'Specialist id (e.g. cx-engineer, product-manager).' },
777
+ success: { type: 'boolean' },
778
+ intake_id: { type: 'string' },
779
+ profile: { type: 'string', description: 'Override active profile id stamp.' },
780
+ escalated: { type: 'boolean' },
781
+ duration_ms: { type: 'number' },
782
+ notes: { type: 'string', description: 'Trimmed to 500 chars.' },
783
+ source: { type: 'string', description: 'Origin tag (default: "mcp").' },
784
+ },
785
+ },
786
+ },
787
+ {
788
+ name: 'knowledge_add',
789
+ description: 'Persist a research finding as `.cx/knowledge/external/research/<slug>.md` with research-specific frontmatter (topic, confidence, sources, expiresAt, profile). Writes durable state — requires `confirm=true`. `confidence=confirmed` requires at least one source.',
790
+ inputSchema: {
791
+ type: 'object',
792
+ required: ['confirm', 'slug', 'topic', 'body'],
793
+ properties: {
794
+ confirm: { type: 'boolean', description: 'Must be true.' },
795
+ cwd: { type: 'string' },
796
+ slug: { type: 'string', description: 'Lowercase hyphenated, max 60 chars.' },
797
+ topic: { type: 'string' },
798
+ body: { type: 'string', description: 'Findings / inferences / gaps / recommendation block. Capped at 50KB total file.' },
799
+ confidence: { type: 'string', enum: ['confirmed', 'inferred', 'weak'], description: 'Default: inferred.' },
800
+ sources: {
801
+ type: 'array',
802
+ description: 'Required when confidence=confirmed.',
803
+ items: {
804
+ type: 'object',
805
+ required: ['url'],
806
+ properties: {
807
+ url: { type: 'string' },
808
+ accessedAt: { type: 'string', description: 'ISO timestamp; default now.' },
809
+ span: { type: 'string', description: 'Citation locator; trimmed to 200 chars.' },
810
+ },
811
+ },
812
+ },
813
+ ttl_days: { type: 'number', description: 'Default 90.' },
814
+ },
815
+ },
816
+ },
817
+ {
818
+ name: 'profile_create',
819
+ description: 'Scaffold a draft org profile under `.cx/profiles/draft-<id>/` (requirements.md + profile.json + persona stubs + department charters). Writes durable state — requires `confirm=true`. For curated catalog work, follow `docs/concepts/profile-lifecycle.md` after creation.',
820
+ inputSchema: {
821
+ type: 'object',
822
+ required: ['confirm', 'id'],
823
+ properties: {
824
+ confirm: { type: 'boolean', description: 'Must be true.' },
825
+ cwd: { type: 'string' },
826
+ id: { type: 'string', description: 'Profile id (^[a-z][a-z0-9-]{1,30}$).' },
827
+ display_name: { type: 'string' },
828
+ seed_roles: {
829
+ type: 'array',
830
+ items: { type: 'string' },
831
+ description: 'Role ids to scaffold persona files for (cap 80).',
832
+ },
833
+ seed_departments: {
834
+ type: 'array',
835
+ items: {
836
+ type: 'object',
837
+ required: ['id'],
838
+ properties: {
839
+ id: { type: 'string' },
840
+ displayName: { type: 'string' },
841
+ },
842
+ },
843
+ description: 'Departments to scaffold charters for (cap 12).',
844
+ },
845
+ },
846
+ },
847
+ },
848
+ {
849
+ name: 'profile_archive',
850
+ description: 'Archive a curated profile: moves `profiles/<id>.json` and its intake table into `archive/profiles/<id>/` with an archive note. Destructive — requires `confirm=true` and a substantive `reason` (>=8 chars). Observations and outcomes are preserved.',
851
+ inputSchema: {
852
+ type: 'object',
853
+ required: ['confirm', 'id', 'reason'],
854
+ properties: {
855
+ confirm: { type: 'boolean', description: 'Must be true.' },
856
+ id: { type: 'string' },
857
+ reason: { type: 'string', description: 'Substantive reason (>= 8 chars).' },
858
+ },
859
+ },
860
+ },
861
+ {
862
+ name: 'sandbox_list',
863
+ description: 'List Construct sandboxes under `~/.cx/sandboxes/` (id, path, createdAt). Use to find an isolated environment for QA or dry-runs without polluting the active project.',
864
+ inputSchema: { type: 'object', properties: {} },
865
+ },
866
+ {
867
+ name: 'knowledge_graph_ask',
868
+ description: 'GraphRAG-style global query over the entity graph in `.cx/observations/`. Detects communities via label propagation, ranks them by BM25 against the query, and returns each top community with its central members and extractive summary. Use for "tell me about how X relates across the project" questions that pure semantic retrieval handles poorly.',
869
+ inputSchema: {
870
+ type: 'object',
871
+ required: ['query'],
872
+ properties: {
873
+ query: { type: 'string', description: 'Natural-language question.' },
874
+ cwd: { type: 'string', description: 'Project root (default: server cwd).' },
875
+ top_k: { type: 'number', description: 'Max communities to return (default 5).' },
876
+ min_size: { type: 'number', description: 'Skip communities smaller than this (default 2).' },
877
+ },
878
+ },
879
+ },
880
+ {
881
+ name: 'learning_status',
882
+ description: 'One-shot mirror of `npm run learning:status`: active profile, observation counts (last 24h + total), research finding count, per-role outcome rollup. Use to answer "is Construct learning?" without spawning a shell.',
883
+ inputSchema: {
884
+ type: 'object',
885
+ properties: { cwd: { type: 'string', description: 'Project root (default: server cwd).' } },
886
+ },
887
+ },
714
888
  {
715
889
  name: 'knowledge_search',
716
890
  description: 'Search Construct\'s own documentation, knowledge base, and distilled embed observations. Call this immediately — no approval needed — when the user asks what Construct is, how a feature works, what commands exist, or anything about Construct\'s architecture or configuration. Also searches embed observations from GitHub, Jira, and other configured sources. Returns relevant excerpts with source file and heading.',
@@ -786,6 +960,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
786
960
  const { knowledgeSearch } = await import('../knowledge/search.mjs');
787
961
  result = knowledgeSearch({ query: args.query, topK: args.top_k, repoRoot: args.repo_root, rootDir: args.root_dir });
788
962
  }
963
+ else if (name === 'profile_show') result = profileShow(args);
964
+ else if (name === 'profile_list') result = profileList();
965
+ else if (name === 'profile_drafts') result = profileDrafts(args);
966
+ else if (name === 'profile_health') result = profileHealthTool(args);
967
+ else if (name === 'outcomes_summary') result = outcomesSummary(args);
968
+ else if (name === 'outcomes_record') result = outcomesRecord(args);
969
+ else if (name === 'knowledge_add') result = await knowledgeAdd(args);
970
+ else if (name === 'profile_create') result = profileCreate(args);
971
+ else if (name === 'profile_archive') result = profileArchive(args);
972
+ else if (name === 'sandbox_list') result = sandboxList();
973
+ else if (name === 'learning_status') result = learningStatus(args);
974
+ else if (name === 'knowledge_graph_ask') result = knowledgeGraphAsk(args);
789
975
  else result = { error: `Unknown tool: ${name}` };
790
976
  } catch (err) {
791
977
  result = { error: err.message ?? String(err) };