@smartmemory/compose 0.1.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 (181) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1014 -0
  3. package/bin/compose.js +1515 -0
  4. package/dist/assets/_baseUniq-CQwX6VLz.js +1 -0
  5. package/dist/assets/arc-SxJ2J1sh.js +1 -0
  6. package/dist/assets/architectureDiagram-Q4EWVU46-BykunY1F.js +36 -0
  7. package/dist/assets/blockDiagram-DXYQGD6D-ohAKBOUw.js +132 -0
  8. package/dist/assets/c4Diagram-AHTNJAMY-DBDC3ENB.js +10 -0
  9. package/dist/assets/channel-DGElom1e.js +1 -0
  10. package/dist/assets/chunk-4BX2VUAB-Cv93Z7uM.js +1 -0
  11. package/dist/assets/chunk-4TB4RGXK-DE0WBDkj.js +206 -0
  12. package/dist/assets/chunk-55IACEB6-CE1EXenG.js +1 -0
  13. package/dist/assets/chunk-EDXVE4YY-DA7Ana6H.js +1 -0
  14. package/dist/assets/chunk-FMBD7UC4-CTDIPA3p.js +15 -0
  15. package/dist/assets/chunk-OYMX7WX6-uGBaPaTX.js +231 -0
  16. package/dist/assets/chunk-QZHKN3VN-CYlnXuUO.js +1 -0
  17. package/dist/assets/chunk-YZCP3GAM-ojGkzcZK.js +1 -0
  18. package/dist/assets/classDiagram-6PBFFD2Q-KqWP9wWZ.js +1 -0
  19. package/dist/assets/classDiagram-v2-HSJHXN6E-KqWP9wWZ.js +1 -0
  20. package/dist/assets/clone-DUJKJXd7.js +1 -0
  21. package/dist/assets/cose-bilkent-S5V4N54A-Bktn9hL-.js +1 -0
  22. package/dist/assets/dagre-KV5264BT-DFaSzuRF.js +4 -0
  23. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  24. package/dist/assets/diagram-5BDNPKRD-DnfmDzEm.js +10 -0
  25. package/dist/assets/diagram-G4DWMVQ6-Bm8W9YnG.js +24 -0
  26. package/dist/assets/diagram-MMDJMWI5-B5-TSKvp.js +43 -0
  27. package/dist/assets/diagram-TYMM5635-ls4rqlky.js +24 -0
  28. package/dist/assets/erDiagram-SMLLAGMA-giG6WO-r.js +85 -0
  29. package/dist/assets/flowDiagram-DWJPFMVM-XvlUuz-7.js +162 -0
  30. package/dist/assets/ganttDiagram-T4ZO3ILL-hLBV57oV.js +292 -0
  31. package/dist/assets/gitGraphDiagram-UUTBAWPF-BHu3s_Gn.js +106 -0
  32. package/dist/assets/graph-D0Cfv00Y.js +1 -0
  33. package/dist/assets/index-CUd6pFGF.css +1 -0
  34. package/dist/assets/index-DReRlzZI.js +1144 -0
  35. package/dist/assets/infoDiagram-42DDH7IO-DbqRsOo3.js +2 -0
  36. package/dist/assets/init-Gi6I4Gst.js +1 -0
  37. package/dist/assets/ishikawaDiagram-UXIWVN3A-DnCdx7zb.js +70 -0
  38. package/dist/assets/journeyDiagram-VCZTEJTY-CfD7eNcP.js +139 -0
  39. package/dist/assets/kanban-definition-6JOO6SKY-BYaO9-mK.js +89 -0
  40. package/dist/assets/katex-DkKDou_j.js +257 -0
  41. package/dist/assets/layout-Bj72wOEB.js +1 -0
  42. package/dist/assets/linear-BRFo114D.js +1 -0
  43. package/dist/assets/min-GCHnKlJS.js +1 -0
  44. package/dist/assets/mindmap-definition-QFDTVHPH-n0PMebY4.js +96 -0
  45. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  46. package/dist/assets/pieDiagram-DEJITSTG-pN4CljHF.js +30 -0
  47. package/dist/assets/quadrantDiagram-34T5L4WZ-DNoAy8-D.js +7 -0
  48. package/dist/assets/requirementDiagram-MS252O5E-BhtY05PT.js +84 -0
  49. package/dist/assets/sankeyDiagram-XADWPNL6-B6AD-16A.js +10 -0
  50. package/dist/assets/sequenceDiagram-FGHM5R23-DShHM-uk.js +157 -0
  51. package/dist/assets/stateDiagram-FHFEXIEX-DMxn7HTo.js +1 -0
  52. package/dist/assets/stateDiagram-v2-QKLJ7IA2-o6PnCs4e.js +1 -0
  53. package/dist/assets/timeline-definition-GMOUNBTQ-Cdu6uq52.js +120 -0
  54. package/dist/assets/vennDiagram-DHZGUBPP-CpK29iRe.js +34 -0
  55. package/dist/assets/wardley-RL74JXVD-BQgSkdcO.js +162 -0
  56. package/dist/assets/wardleyDiagram-NUSXRM2D-DJHYev6O.js +20 -0
  57. package/dist/assets/xychartDiagram-5P7HB3ND-1d75pbaO.js +7 -0
  58. package/dist/index.html +30 -0
  59. package/lib/agent-chains.js +65 -0
  60. package/lib/agent-string.js +86 -0
  61. package/lib/budget-ledger.js +86 -0
  62. package/lib/build-all.js +162 -0
  63. package/lib/build-dag.js +120 -0
  64. package/lib/build-stream-writer.js +190 -0
  65. package/lib/build.js +2997 -0
  66. package/lib/capability-checker.js +53 -0
  67. package/lib/cert-inject.js +38 -0
  68. package/lib/cli-progress.js +483 -0
  69. package/lib/constants.js +69 -0
  70. package/lib/cross-layer-audit.js +84 -0
  71. package/lib/debug-discipline.js +173 -0
  72. package/lib/feature-json.js +106 -0
  73. package/lib/gate-prompt.js +291 -0
  74. package/lib/gate-tiers.js +194 -0
  75. package/lib/health-history.js +119 -0
  76. package/lib/health-score.js +227 -0
  77. package/lib/ideabox.js +570 -0
  78. package/lib/import.js +244 -0
  79. package/lib/migrate-roadmap.js +94 -0
  80. package/lib/model-pricing.js +67 -0
  81. package/lib/new.js +413 -0
  82. package/lib/pipeline-cli.js +489 -0
  83. package/lib/plan-parser.js +103 -0
  84. package/lib/qa-scoping.js +474 -0
  85. package/lib/questionnaire.js +200 -0
  86. package/lib/resolve-port.js +7 -0
  87. package/lib/result-normalizer.js +349 -0
  88. package/lib/review-lenses.js +166 -0
  89. package/lib/roadmap-gen.js +210 -0
  90. package/lib/roadmap-parser.js +176 -0
  91. package/lib/server-probe.js +23 -0
  92. package/lib/staleness.js +87 -0
  93. package/lib/step-prompt.js +260 -0
  94. package/lib/step-validator.js +49 -0
  95. package/lib/stratum-mcp-client.js +365 -0
  96. package/lib/team-flag.js +46 -0
  97. package/lib/test-bootstrap.js +401 -0
  98. package/lib/triage.js +274 -0
  99. package/lib/vision-writer.js +391 -0
  100. package/package.json +111 -0
  101. package/pipelines/bug-fix.stratum.yaml +230 -0
  102. package/pipelines/build.stratum.yaml +498 -0
  103. package/pipelines/content.stratum.yaml +112 -0
  104. package/pipelines/coverage-sweep.stratum.yaml +52 -0
  105. package/pipelines/refactor.stratum.yaml +169 -0
  106. package/pipelines/research.stratum.yaml +88 -0
  107. package/pipelines/review-fix.stratum.yaml +109 -0
  108. package/presets/team-feature.stratum.yaml +105 -0
  109. package/presets/team-research.stratum.yaml +108 -0
  110. package/presets/team-review.stratum.yaml +106 -0
  111. package/scripts/agent-activity-hook.sh +31 -0
  112. package/scripts/agent-error-hook.sh +28 -0
  113. package/scripts/analyze-orphans.mjs +50 -0
  114. package/scripts/find-orphans.mjs +26 -0
  115. package/scripts/fix-phases.mjs +49 -0
  116. package/scripts/generate-stratum-spec.mjs +137 -0
  117. package/scripts/import-roadmap.mjs +116 -0
  118. package/scripts/phase-audit.mjs +33 -0
  119. package/scripts/run-pipeline.mjs +314 -0
  120. package/scripts/session-end-hook.sh +18 -0
  121. package/scripts/session-start-hook.sh +38 -0
  122. package/scripts/vision-hook.sh +104 -0
  123. package/scripts/vision-track.mjs +554 -0
  124. package/scripts/wire-all-orphans.mjs +108 -0
  125. package/scripts/wire-orphans.mjs +164 -0
  126. package/server/activity-routes.js +123 -0
  127. package/server/agent-health.js +197 -0
  128. package/server/agent-hooks.js +102 -0
  129. package/server/agent-mcp.js +10 -0
  130. package/server/agent-registry.js +95 -0
  131. package/server/agent-server.js +290 -0
  132. package/server/agent-spawn.js +251 -0
  133. package/server/agent-templates.js +77 -0
  134. package/server/artifact-manager.js +247 -0
  135. package/server/artifact-templates/architecture.md +28 -0
  136. package/server/artifact-templates/blueprint.md +21 -0
  137. package/server/artifact-templates/design.md +36 -0
  138. package/server/artifact-templates/plan.md +25 -0
  139. package/server/artifact-templates/prd.md +43 -0
  140. package/server/artifact-templates/report.md +40 -0
  141. package/server/block-tracker.js +90 -0
  142. package/server/build-stream-bridge.js +502 -0
  143. package/server/coalescing-buffer.js +46 -0
  144. package/server/compose-mcp-tools.js +479 -0
  145. package/server/compose-mcp.js +324 -0
  146. package/server/connectors/agent-connector.js +78 -0
  147. package/server/connectors/claude-sdk-connector.js +198 -0
  148. package/server/connectors/codex-connector.js +240 -0
  149. package/server/connectors/connector-discovery.js +18 -0
  150. package/server/connectors/connector-runtime.js +13 -0
  151. package/server/connectors/opencode-connector.js +200 -0
  152. package/server/design-routes.js +540 -0
  153. package/server/design-session.js +161 -0
  154. package/server/feature-scan.js +593 -0
  155. package/server/file-watcher.js +284 -0
  156. package/server/find-root.js +29 -0
  157. package/server/graph-export.js +343 -0
  158. package/server/ideabox-cache.js +77 -0
  159. package/server/ideabox-routes.js +294 -0
  160. package/server/index.js +156 -0
  161. package/server/model-tiers.js +49 -0
  162. package/server/pipeline-routes.js +288 -0
  163. package/server/policy-evaluator.js +36 -0
  164. package/server/project-root.js +122 -0
  165. package/server/security.js +23 -0
  166. package/server/session-manager.js +403 -0
  167. package/server/session-routes.js +190 -0
  168. package/server/session-store.js +107 -0
  169. package/server/settings-routes.js +35 -0
  170. package/server/settings-store.js +234 -0
  171. package/server/stratum-api.js +102 -0
  172. package/server/stratum-client.js +192 -0
  173. package/server/stratum-sync.js +193 -0
  174. package/server/summarizer.js +139 -0
  175. package/server/supervisor.js +196 -0
  176. package/server/vision-routes.js +668 -0
  177. package/server/vision-server.js +393 -0
  178. package/server/vision-store.js +360 -0
  179. package/server/vision-utils.js +179 -0
  180. package/server/worktree-gc.js +137 -0
  181. package/templates/ROADMAP.md +46 -0
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wire 66 orphaned vision tracker items into the feature/track hierarchy
5
+ * by creating `implements` connections (fromId=item, toId=track).
6
+ */
7
+
8
+ import { readFileSync, writeFileSync } from 'fs';
9
+ import { v4 as uuidv4 } from 'uuid';
10
+ import { resolve, dirname } from 'path';
11
+ import { fileURLToPath } from 'url';
12
+
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ const dataPath = resolve(__dirname, '..', 'data', 'vision-state.json');
15
+
16
+ // The 66 orphaned items mapped to their tracks (item semanticId -> track semanticId)
17
+ const MAPPINGS = [
18
+ ['FORGE-DEC-1', 'FORGE-TRK-1'],
19
+ ['FORGE-DEC-2', 'FORGE-TRK-5'],
20
+ ['FORGE-DEC-5', 'FORGE-TRK-5'],
21
+ ['FORGE-DEC-6', 'FORGE-TRK-5'],
22
+ ['FORGE-DEC-4', 'FORGE-TRK-5'],
23
+ ['FORGE-DEC-3', 'FORGE-TRK-5'],
24
+ ['FORGE-DEC-7', 'FORGE-TRK-3'],
25
+ ['FORGE-DEC-8', 'FORGE-TRK-10'],
26
+ ['FORGE-DEC-9', 'FORGE-TRK-1'],
27
+ ['FORGE-IDEA-8', 'FORGE-TRK-5'],
28
+ ['FORGE-IDEA-9', 'FORGE-TRK-13'],
29
+ ['FORGE-IDEA-2', 'FORGE-TRK-14'],
30
+ ['FORGE-IDEA-3', 'FORGE-TRK-5'],
31
+ ['FORGE-IDEA-1', 'FORGE-TRK-1'],
32
+ ['FORGE-IDEA-4', 'FORGE-TRK-5'],
33
+ ['FORGE-Q-3', 'FORGE-TRK-5'],
34
+ ['FORGE-Q-4', 'FORGE-TRK-1'],
35
+ ['FORGE-Q-5', 'FORGE-TRK-5'],
36
+ ['FORGE-Q-1', 'FORGE-TRK-5'],
37
+ ['FORGE-THR-3', 'FORGE-TRK-1'],
38
+ ['FORGE-THR-1', 'FORGE-TRK-5'],
39
+ ['FORGE-THR-2', 'FORGE-TRK-5'],
40
+ ['FORGE-THR-4', 'FORGE-TRK-5'],
41
+ ['FORGE-ART-1', 'FORGE-TRK-3'],
42
+ ['FORGE-DEC-10', 'FORGE-TRK-1'],
43
+ ['FORGE-DEC-11', 'FORGE-TRK-5'],
44
+ ['FORGE-DEC-12', 'FORGE-TRK-3'],
45
+ ['FORGE-DEC-13', 'FORGE-TRK-3'],
46
+ ['FORGE-IDEA-5', 'FORGE-TRK-8'],
47
+ ['FORGE-IDEA-6', 'FORGE-TRK-9'],
48
+ ['FORGE-IDEA-7', 'FORGE-TRK-11'],
49
+ ['FORGE-IDEA-10', 'FORGE-TRK-14'],
50
+ ['FORGE-THR-5', 'FORGE-TRK-1'],
51
+ ['FORGE-Q-2', 'FORGE-TRK-11'],
52
+ ['FORGE-SPEC-1', 'FORGE-TRK-3'],
53
+ ['FORGE-SPEC-2', 'FORGE-TRK-3'],
54
+ ['FORGE-SPEC-3', 'FORGE-TRK-3'],
55
+ ['FORGE-SPEC-4', 'FORGE-TRK-3'],
56
+ ['FORGE-TASK-1', 'FORGE-TRK-3'],
57
+ ['FORGE-TASK-2', 'FORGE-TRK-3'],
58
+ ['FORGE-TASK-3', 'FORGE-TRK-14'],
59
+ ['FORGE-TASK-4', 'FORGE-TRK-6'],
60
+ ['FORGE-TASK-5', 'FORGE-TRK-14'],
61
+ ['FORGE-TASK-6', 'FORGE-TRK-3'],
62
+ ['FORGE-DEC-14', 'FORGE-TRK-5'],
63
+ ['FORGE-DEC-15', 'FORGE-TRK-5'],
64
+ ['FORGE-DEC-16', 'FORGE-TRK-5'],
65
+ ['FORGE-ART-2', 'FORGE-TRK-5'],
66
+ ['FORGE-ART-3', 'FORGE-TRK-1'],
67
+ ['FORGE-DEC-17', 'FORGE-TRK-12'],
68
+ ['FORGE-DEC-18', 'FORGE-TRK-13'],
69
+ ['FORGE-SPEC-5', 'FORGE-TRK-6'],
70
+ ['FORGE-SPEC-6', 'FORGE-TRK-6'],
71
+ ['FORGE-DEC-19', 'FORGE-TRK-6'],
72
+ ['FORGE-DEC-20', 'FORGE-TRK-6'],
73
+ ['FORGE-DEC-21', 'FORGE-TRK-5'],
74
+ ['FORGE-DEC-22', 'FORGE-TRK-5'],
75
+ ['FORGE-DEC-23', 'FORGE-TRK-13'],
76
+ ['FORGE-DEC-24', 'FORGE-TRK-6'],
77
+ ['FORGE-DEC-25', 'FORGE-TRK-6'],
78
+ ['FORGE-DEC-26', 'FORGE-TRK-6'],
79
+ ['FORGE-DEC-27', 'FORGE-TRK-6'],
80
+ ['FORGE-IDEA-11', 'FORGE-TRK-7'],
81
+ ['FORGE-SPEC-7', 'FORGE-TRK-7'],
82
+ ['FORGE-DEC-28', 'FORGE-TRK-13'],
83
+ ['FORGE-DEC-29', 'FORGE-TRK-13'],
84
+ ];
85
+
86
+ // Read the state file
87
+ const state = JSON.parse(readFileSync(dataPath, 'utf-8'));
88
+
89
+ // Build a lookup: semanticId -> item UUID
90
+ const semanticToId = new Map();
91
+ for (const item of state.items) {
92
+ if (item.semanticId) {
93
+ semanticToId.set(item.semanticId, item.id);
94
+ }
95
+ }
96
+
97
+ // Build a set of existing connections for dedup: "fromId->toId->type"
98
+ const existingConnections = new Set();
99
+ for (const conn of state.connections) {
100
+ existingConnections.add(`${conn.fromId}->${conn.toId}->${conn.type}`);
101
+ }
102
+
103
+ let created = 0;
104
+ let skippedDuplicate = 0;
105
+ let skippedMissing = 0;
106
+ const summary = { byTrack: {} };
107
+
108
+ for (const [itemSemantic, trackSemantic] of MAPPINGS) {
109
+ const fromId = semanticToId.get(itemSemantic);
110
+ const toId = semanticToId.get(trackSemantic);
111
+
112
+ if (!fromId) {
113
+ console.warn(` SKIP: item ${itemSemantic} not found in vision-state.json`);
114
+ skippedMissing++;
115
+ continue;
116
+ }
117
+ if (!toId) {
118
+ console.warn(` SKIP: track ${trackSemantic} not found in vision-state.json`);
119
+ skippedMissing++;
120
+ continue;
121
+ }
122
+
123
+ const key = `${fromId}->${toId}->implements`;
124
+ if (existingConnections.has(key)) {
125
+ skippedDuplicate++;
126
+ continue;
127
+ }
128
+
129
+ const connection = {
130
+ id: uuidv4(),
131
+ fromId,
132
+ toId,
133
+ type: 'implements',
134
+ createdAt: new Date().toISOString(),
135
+ };
136
+
137
+ state.connections.push(connection);
138
+ existingConnections.add(key);
139
+ created++;
140
+
141
+ // Track summary
142
+ if (!summary.byTrack[trackSemantic]) {
143
+ summary.byTrack[trackSemantic] = [];
144
+ }
145
+ summary.byTrack[trackSemantic].push(itemSemantic);
146
+ }
147
+
148
+ // Save
149
+ writeFileSync(dataPath, JSON.stringify(state, null, 2) + '\n', 'utf-8');
150
+
151
+ // Print summary
152
+ console.log(`\n=== Wire Orphans Summary ===`);
153
+ console.log(`Total mappings: ${MAPPINGS.length}`);
154
+ console.log(`Connections created: ${created}`);
155
+ console.log(`Skipped (duplicate): ${skippedDuplicate}`);
156
+ console.log(`Skipped (missing): ${skippedMissing}`);
157
+ console.log(`\nBy track:`);
158
+
159
+ for (const [track, items] of Object.entries(summary.byTrack).sort()) {
160
+ console.log(` ${track} (${items.length} items):`);
161
+ for (const item of items) {
162
+ console.log(` - ${item}`);
163
+ }
164
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * activity-routes.js — POST /api/agent/activity and POST /api/agent/error route handlers.
3
+ *
4
+ * These stay separate from vision-routes because they depend on resolveItems,
5
+ * which lives on VisionServer. Both deps are injected.
6
+ */
7
+
8
+ import { detectError } from './vision-utils.js';
9
+
10
+ // NOTE: Duplicated in vision-server.js and src/components/Terminal.jsx — keep in sync
11
+ const TOOL_CATEGORIES = {
12
+ Read: 'reading', Glob: 'searching', Grep: 'searching',
13
+ Write: 'writing', Edit: 'writing', NotebookEdit: 'writing',
14
+ Bash: 'executing', Task: 'delegating', Skill: 'delegating',
15
+ WebFetch: 'fetching', WebSearch: 'searching',
16
+ TodoRead: 'reading', TodoWrite: 'writing',
17
+ };
18
+
19
+ /**
20
+ * @param {object} app
21
+ * @param {{ store, sessionManager, scheduleBroadcast, broadcastMessage, resolveItems }} deps
22
+ */
23
+ export function attachActivityRoutes(app, { store, sessionManager, scheduleBroadcast, broadcastMessage, resolveItems }) {
24
+ // POST /api/agent/activity — receive tool use events from hooks
25
+ app.post('/api/agent/activity', (req, res) => {
26
+ const { tool, input, response, timestamp } = req.body || {};
27
+ if (!tool) return res.status(400).json({ error: 'tool is required' });
28
+
29
+ let detail = null;
30
+ let filePath = null;
31
+ if (input) {
32
+ filePath = input.file_path || null;
33
+ detail = filePath || input.command || input.pattern || input.query || input.url || input.prompt || null;
34
+ if (detail && detail.length > 120) detail = detail.slice(0, 117) + '...';
35
+ }
36
+
37
+ const items = filePath ? resolveItems(filePath) : [];
38
+
39
+ // Auto-status: Write/Edit on planned items → in_progress
40
+ if (['Write', 'Edit'].includes(tool) && filePath) {
41
+ for (const item of items) {
42
+ if (item.status === 'planned') {
43
+ try {
44
+ store.updateItem(item.id, { status: 'in_progress' });
45
+ scheduleBroadcast();
46
+ } catch { /* ignore */ }
47
+ }
48
+ }
49
+ }
50
+
51
+ const category = TOOL_CATEGORIES[tool] || 'thinking';
52
+
53
+ if (sessionManager) {
54
+ sessionManager.recordActivity(tool, category, filePath, input, items);
55
+ }
56
+
57
+ let error = null;
58
+ if (response && typeof response === 'string') {
59
+ error = detectError(tool, input, response);
60
+ }
61
+
62
+ if (error) {
63
+ if (sessionManager) {
64
+ sessionManager.recordError(tool, filePath, error.type, error.severity, error.message, items);
65
+ }
66
+ broadcastMessage({
67
+ type: 'agentError',
68
+ errorType: error.type,
69
+ severity: error.severity,
70
+ message: error.message,
71
+ tool,
72
+ detail,
73
+ items: items.map(i => ({ id: i.id, title: i.title })),
74
+ timestamp: timestamp || new Date().toISOString(),
75
+ });
76
+ }
77
+
78
+ broadcastMessage({
79
+ type: 'agentActivity',
80
+ tool,
81
+ category,
82
+ detail,
83
+ error: error ? { type: error.type, severity: error.severity } : null,
84
+ items: items.map(i => ({ id: i.id, title: i.title, status: i.status, phase: i.lifecycle?.currentPhase || null })),
85
+ timestamp: timestamp || new Date().toISOString(),
86
+ });
87
+
88
+ res.json({ ok: true });
89
+ });
90
+
91
+ // POST /api/agent/error — receive PostToolUseFailure events from hooks
92
+ app.post('/api/agent/error', (req, res) => {
93
+ const { tool, input, error: errorMsg } = req.body || {};
94
+ if (!tool) return res.status(400).json({ error: 'tool is required' });
95
+
96
+ const filePath = input?.file_path || null;
97
+ const items = filePath ? resolveItems(filePath) : [];
98
+
99
+ const detected = detectError(tool, input, errorMsg || '') || {
100
+ type: 'runtime_error',
101
+ severity: 'error',
102
+ message: errorMsg || 'Tool use failed',
103
+ };
104
+
105
+ if (sessionManager) {
106
+ sessionManager.recordError(tool, filePath, detected.type, detected.severity, detected.message, items);
107
+ }
108
+
109
+ broadcastMessage({
110
+ type: 'agentError',
111
+ errorType: detected.type,
112
+ severity: detected.severity,
113
+ message: detected.message,
114
+ tool,
115
+ detail: filePath || input?.command || null,
116
+ items: items.map(i => ({ id: i.id, title: i.title })),
117
+ timestamp: new Date().toISOString(),
118
+ });
119
+
120
+ console.log(`[vision] Error detected: ${detected.type} (${detected.severity}) from ${tool}: ${detected.message.slice(0, 80)}`);
121
+ res.json({ ok: true, detected });
122
+ });
123
+ }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * agent-health.js — HealthMonitor for spawned subagents.
3
+ *
4
+ * Tracks stdout/stderr activity to detect silent agents.
5
+ * Enforces wall-clock timeout and memory RSS limits.
6
+ * Broadcasts agentSilent / agentKilled events via the vision WS.
7
+ *
8
+ * Terminal reasons: manual_stop | silence_timeout | wall_clock_timeout | memory_exceeded | normal
9
+ */
10
+
11
+ import { execSync } from 'node:child_process';
12
+
13
+ const GRACEFUL_KILL_MS = 5000; // 5s grace before SIGKILL
14
+
15
+ /**
16
+ * Send SIGTERM, then escalate to SIGKILL after a grace period.
17
+ * Cleans up the escalation timer when the process exits on its own.
18
+ */
19
+ export function gracefulKill(proc, graceMs = GRACEFUL_KILL_MS) {
20
+ try { proc.kill('SIGTERM'); } catch { /* already dead */ }
21
+ const killTimer = setTimeout(() => {
22
+ try { proc.kill('SIGKILL'); } catch { /* already dead */ }
23
+ }, graceMs);
24
+ proc.once('close', () => clearTimeout(killTimer));
25
+ }
26
+
27
+ const DEFAULT_SILENCE_WARNING_MS = 60_000; // 60s silence → warning
28
+ const DEFAULT_SILENCE_KILL_MS = 5 * 60_000; // 5min silence → kill
29
+ const DEFAULT_TIMEOUT_MS = 10 * 60_000; // 10min wall-clock
30
+ const DEFAULT_MEMORY_LIMIT_MB = 0; // 0 = disabled
31
+ const MEMORY_POLL_INTERVAL_MS = 30_000; // 30s
32
+
33
+ export class HealthMonitor {
34
+ #broadcastMessage;
35
+ #silenceWarningMs;
36
+ #silenceKillMs;
37
+ #defaultTimeoutMs;
38
+ #memoryLimitMB;
39
+
40
+ /** @type {Map<string, TrackedAgent>} */
41
+ #agents = new Map();
42
+
43
+ /**
44
+ * @param {object} opts
45
+ * @param {function} opts.broadcastMessage
46
+ * @param {number} [opts.silenceWarningMs]
47
+ * @param {number} [opts.silenceKillMs]
48
+ * @param {number} [opts.defaultTimeoutMs]
49
+ * @param {number} [opts.memoryLimitMB]
50
+ */
51
+ constructor({ broadcastMessage, silenceWarningMs, silenceKillMs, defaultTimeoutMs, memoryLimitMB }) {
52
+ this.#broadcastMessage = broadcastMessage;
53
+ this.#silenceWarningMs = silenceWarningMs ?? DEFAULT_SILENCE_WARNING_MS;
54
+ this.#silenceKillMs = silenceKillMs ?? DEFAULT_SILENCE_KILL_MS;
55
+ this.#defaultTimeoutMs = defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
56
+ this.#memoryLimitMB = memoryLimitMB ?? DEFAULT_MEMORY_LIMIT_MB;
57
+ }
58
+
59
+ /**
60
+ * Start monitoring an agent process.
61
+ * @param {string} agentId
62
+ * @param {import('node:child_process').ChildProcess} proc
63
+ */
64
+ track(agentId, proc) {
65
+ // Clean up any prior tracking for same id
66
+ if (this.#agents.has(agentId)) this.untrack(agentId);
67
+
68
+ const entry = {
69
+ proc,
70
+ pid: proc.pid,
71
+ startedAt: Date.now(),
72
+ lastActivity: Date.now(),
73
+ terminalReason: null,
74
+ silenceWarningTimer: null,
75
+ silenceKillTimer: null,
76
+ wallClockTimer: null,
77
+ memoryPollTimer: null,
78
+ stdoutHandler: null,
79
+ stderrHandler: null,
80
+ };
81
+
82
+ // Activity listeners
83
+ const onActivity = () => { entry.lastActivity = Date.now(); this._resetSilenceTimers(agentId); };
84
+ entry.stdoutHandler = onActivity;
85
+ entry.stderrHandler = onActivity;
86
+ if (proc.stdout) proc.stdout.on('data', entry.stdoutHandler);
87
+ if (proc.stderr) proc.stderr.on('data', entry.stderrHandler);
88
+
89
+ this.#agents.set(agentId, entry);
90
+
91
+ // Start silence timers
92
+ this._resetSilenceTimers(agentId);
93
+
94
+ // Wall-clock timeout
95
+ entry.wallClockTimer = setTimeout(() => this._kill(agentId, 'wall_clock_timeout'), this.#defaultTimeoutMs);
96
+
97
+ // Memory polling
98
+ if (this.#memoryLimitMB > 0) {
99
+ entry.memoryPollTimer = setInterval(() => this._checkMemory(agentId), MEMORY_POLL_INTERVAL_MS);
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Stop monitoring an agent (does NOT kill the process).
105
+ */
106
+ untrack(agentId) {
107
+ const entry = this.#agents.get(agentId);
108
+ if (!entry) return;
109
+ this._clearTimers(entry);
110
+ // Remove listeners
111
+ if (entry.proc.stdout && entry.stdoutHandler) entry.proc.stdout.removeListener('data', entry.stdoutHandler);
112
+ if (entry.proc.stderr && entry.stderrHandler) entry.proc.stderr.removeListener('data', entry.stderrHandler);
113
+ this.#agents.delete(agentId);
114
+ }
115
+
116
+ isTracked(agentId) {
117
+ return this.#agents.has(agentId);
118
+ }
119
+
120
+ getTerminalReason(agentId) {
121
+ return this.#agents.get(agentId)?.terminalReason ?? null;
122
+ }
123
+
124
+ setTerminalReason(agentId, reason) {
125
+ const entry = this.#agents.get(agentId);
126
+ if (entry) entry.terminalReason = reason;
127
+ }
128
+
129
+ /** Clean up all tracked agents and timers. */
130
+ destroy() {
131
+ for (const agentId of [...this.#agents.keys()]) {
132
+ this.untrack(agentId);
133
+ }
134
+ }
135
+
136
+ // ── Internal ────────────────────────────────────────────────────────────
137
+
138
+ _resetSilenceTimers(agentId) {
139
+ const entry = this.#agents.get(agentId);
140
+ if (!entry) return;
141
+
142
+ if (entry.silenceWarningTimer) clearTimeout(entry.silenceWarningTimer);
143
+ if (entry.silenceKillTimer) clearTimeout(entry.silenceKillTimer);
144
+
145
+ entry.silenceWarningTimer = setTimeout(() => {
146
+ this.#broadcastMessage({
147
+ type: 'agentSilent',
148
+ agentId,
149
+ silentSinceMs: Date.now() - entry.lastActivity,
150
+ timestamp: new Date().toISOString(),
151
+ });
152
+ }, this.#silenceWarningMs);
153
+
154
+ entry.silenceKillTimer = setTimeout(() => {
155
+ this._kill(agentId, 'silence_timeout');
156
+ }, this.#silenceKillMs);
157
+ }
158
+
159
+ _kill(agentId, reason) {
160
+ const entry = this.#agents.get(agentId);
161
+ if (!entry || entry.terminalReason) return; // already killed
162
+
163
+ entry.terminalReason = reason;
164
+ this._clearTimers(entry);
165
+
166
+ gracefulKill(entry.proc);
167
+
168
+ this.#broadcastMessage({
169
+ type: 'agentKilled',
170
+ agentId,
171
+ reason,
172
+ timestamp: new Date().toISOString(),
173
+ });
174
+ }
175
+
176
+ _clearTimers(entry) {
177
+ if (entry.silenceWarningTimer) { clearTimeout(entry.silenceWarningTimer); entry.silenceWarningTimer = null; }
178
+ if (entry.silenceKillTimer) { clearTimeout(entry.silenceKillTimer); entry.silenceKillTimer = null; }
179
+ if (entry.wallClockTimer) { clearTimeout(entry.wallClockTimer); entry.wallClockTimer = null; }
180
+ if (entry.memoryPollTimer) { clearInterval(entry.memoryPollTimer); entry.memoryPollTimer = null; }
181
+ }
182
+
183
+ _checkMemory(agentId) {
184
+ const entry = this.#agents.get(agentId);
185
+ if (!entry || !entry.pid || entry.terminalReason) return;
186
+
187
+ try {
188
+ const rssKB = parseInt(execSync(`ps -o rss= -p ${entry.pid}`, { encoding: 'utf-8', timeout: 5000 }).trim(), 10);
189
+ const rssMB = rssKB / 1024;
190
+ if (rssMB > this.#memoryLimitMB) {
191
+ this._kill(agentId, 'memory_exceeded');
192
+ }
193
+ } catch {
194
+ // Process may have already exited — ignore
195
+ }
196
+ }
197
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * SDK hook callbacks for the Agent Server.
3
+ *
4
+ * These are in-process JavaScript functions passed to query() — not shell
5
+ * scripts. They POST structured events to the api-server (port 3001), which
6
+ * feeds SessionManager and VisionServer, preserving all Phase 3 monitoring.
7
+ *
8
+ * Single source of truth for TOOL_CATEGORIES (previously duplicated in
9
+ * Terminal.jsx and vision-server.js).
10
+ */
11
+
12
+ const API_SERVER = `http://127.0.0.1:${process.env.PORT || 3001}`;
13
+
14
+ /** Semantic category for each Claude Code tool */
15
+ export const TOOL_CATEGORIES = {
16
+ Read: 'reading', Glob: 'searching', Grep: 'searching',
17
+ Write: 'writing', Edit: 'writing', NotebookEdit: 'writing',
18
+ Bash: 'executing', Task: 'delegating', Skill: 'delegating',
19
+ WebFetch: 'fetching', WebSearch: 'searching',
20
+ TodoRead: 'reading', TodoWrite: 'writing',
21
+ };
22
+
23
+ async function post(path, body) {
24
+ try {
25
+ const res = await fetch(`${API_SERVER}${path}`, {
26
+ method: 'POST',
27
+ headers: { 'Content-Type': 'application/json' },
28
+ body: JSON.stringify(body),
29
+ signal: AbortSignal.timeout(5000),
30
+ });
31
+ return res;
32
+ } catch {
33
+ // api-server might not be ready yet — swallow silently
34
+ }
35
+ }
36
+
37
+ /**
38
+ * PostToolUse — fires after each successful tool execution.
39
+ * Sends tool name, input, and response to /api/agent/activity.
40
+ */
41
+ export async function postToolUseHook(input) {
42
+ const response = input.tool_response;
43
+ const responseStr = typeof response === 'string'
44
+ ? response
45
+ : JSON.stringify(response);
46
+
47
+ await post('/api/agent/activity', {
48
+ tool: input.tool_name,
49
+ input: input.tool_input,
50
+ response: responseStr,
51
+ timestamp: new Date().toISOString(),
52
+ });
53
+
54
+ return { continue: true };
55
+ }
56
+
57
+ /**
58
+ * PostToolUseFailure — fires when a tool execution fails.
59
+ * Sends error details to /api/agent/error for severity classification.
60
+ */
61
+ export async function postToolUseFailureHook(input) {
62
+ await post('/api/agent/error', {
63
+ tool: input.tool_name,
64
+ input: input.tool_input,
65
+ error: input.error,
66
+ });
67
+
68
+ return { continue: true };
69
+ }
70
+
71
+ /**
72
+ * SessionStart — fires at session startup or resume.
73
+ * Triggers SessionManager.startSession() via api-server.
74
+ */
75
+ export async function sessionStartHook(input) {
76
+ await post('/api/session/start', {
77
+ source: input.source || 'startup',
78
+ });
79
+
80
+ return { continue: true };
81
+ }
82
+
83
+ /**
84
+ * SessionEnd — fires when Claude Code exits.
85
+ * Triggers SessionManager.endSession() and potentially journal generation.
86
+ */
87
+ export async function sessionEndHook(input) {
88
+ await post('/api/session/end', {
89
+ reason: input.reason || 'completed',
90
+ transcriptPath: input.transcript_path || null,
91
+ });
92
+
93
+ return { continue: true };
94
+ }
95
+
96
+ /** Hook configuration object for query() options.hooks */
97
+ export const HOOK_OPTIONS = {
98
+ PostToolUse: [{ matcher: '.*', hooks: [postToolUseHook] }],
99
+ PostToolUseFailure: [{ matcher: '.*', hooks: [postToolUseFailureHook] }],
100
+ SessionStart: [{ hooks: [sessionStartHook] }],
101
+ SessionEnd: [{ hooks: [sessionEndHook] }],
102
+ };
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ // T2-F5 retirement shim. The agent_run capability moved to stratum-mcp's
3
+ // stratum_agent_run tool. This file exists only to fail fast with a clear
4
+ // message for users whose .mcp.json still references it. Re-running
5
+ // `compose init` removes the stale entry.
6
+ process.stderr.write(
7
+ 'agent-mcp.js is retired (T2-F5). Use stratum_agent_run on the stratum MCP server.\n'
8
+ + 'Re-run `compose init` to remove the legacy entry from .mcp.json.\n'
9
+ );
10
+ process.exit(1);
@@ -0,0 +1,95 @@
1
+ /**
2
+ * AgentRegistry — persistent tracker for spawned subagents.
3
+ *
4
+ * Tracks parent-child relationships between the main Claude Code session
5
+ * and spawned agents (compose-explorer, compose-architect, etc.).
6
+ * JSON file-backed, same pattern as SettingsStore.
7
+ */
8
+
9
+ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
10
+ import { join, dirname } from 'node:path';
11
+
12
+ export class AgentRegistry {
13
+ #agents;
14
+ #file;
15
+
16
+ constructor(dataDir) {
17
+ this.#file = join(dataDir, 'agents.json');
18
+ this.#agents = new Map();
19
+ this._load();
20
+ }
21
+
22
+ register(agentId, { parentSessionId, agentType, prompt, pid }) {
23
+ const record = {
24
+ agentId,
25
+ parentSessionId: parentSessionId ?? null,
26
+ agentType: agentType ?? 'unknown',
27
+ prompt: (prompt ?? '').slice(0, 200),
28
+ status: 'running',
29
+ pid: pid ?? null,
30
+ startedAt: new Date().toISOString(),
31
+ completedAt: null,
32
+ exitCode: null,
33
+ };
34
+ this.#agents.set(agentId, record);
35
+ this._save();
36
+ return record;
37
+ }
38
+
39
+ complete(agentId, { status, exitCode }) {
40
+ const record = this.#agents.get(agentId);
41
+ if (!record) return null;
42
+ record.status = status;
43
+ record.exitCode = exitCode ?? null;
44
+ record.completedAt = new Date().toISOString();
45
+ this._save();
46
+ return record;
47
+ }
48
+
49
+ getChildren(parentSessionId) {
50
+ return [...this.#agents.values()].filter(a => a.parentSessionId === parentSessionId);
51
+ }
52
+
53
+ getAll() { return [...this.#agents.values()]; }
54
+ get(agentId) { return this.#agents.get(agentId) ?? null; }
55
+
56
+ /** COMP-AGT-1: Return all agents with status 'running'. */
57
+ getRunning() {
58
+ return [...this.#agents.values()].filter(a => a.status === 'running');
59
+ }
60
+
61
+ /** COMP-AGT-1: Update status and optional terminal reason for an agent. */
62
+ updateStatus(agentId, status, terminalReason) {
63
+ const record = this.#agents.get(agentId);
64
+ if (!record) return null;
65
+ record.status = status;
66
+ if (terminalReason) record.terminalReason = terminalReason;
67
+ if (status !== 'running') record.completedAt = new Date().toISOString();
68
+ this._save();
69
+ return record;
70
+ }
71
+
72
+ prune(keep = 50) {
73
+ const all = [...this.#agents.values()]
74
+ .sort((a, b) => new Date(b.startedAt) - new Date(a.startedAt));
75
+ const pruned = all.slice(keep);
76
+ for (const r of pruned) this.#agents.delete(r.agentId);
77
+ if (pruned.length > 0) this._save();
78
+ }
79
+
80
+ _load() {
81
+ try {
82
+ const data = JSON.parse(readFileSync(this.#file, 'utf-8'));
83
+ for (const r of data) this.#agents.set(r.agentId, r);
84
+ } catch { /* fresh start */ }
85
+ }
86
+
87
+ _save() {
88
+ try {
89
+ mkdirSync(dirname(this.#file), { recursive: true });
90
+ writeFileSync(this.#file, JSON.stringify([...this.#agents.values()], null, 2));
91
+ } catch (err) {
92
+ console.error('[agent-registry] Save failed:', err.message);
93
+ }
94
+ }
95
+ }