jfl 0.1.0 → 0.2.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 (262) hide show
  1. package/README.md +443 -145
  2. package/clawdbot-plugin/clawdbot.plugin.json +20 -0
  3. package/clawdbot-plugin/index.js +555 -0
  4. package/clawdbot-plugin/index.ts +582 -0
  5. package/clawdbot-skill/SKILL.md +33 -336
  6. package/clawdbot-skill/index.ts +491 -321
  7. package/clawdbot-skill/skill.json +4 -13
  8. package/dist/commands/clawdbot.d.ts +11 -0
  9. package/dist/commands/clawdbot.d.ts.map +1 -0
  10. package/dist/commands/clawdbot.js +215 -0
  11. package/dist/commands/clawdbot.js.map +1 -0
  12. package/dist/commands/context-hub.d.ts +5 -0
  13. package/dist/commands/context-hub.d.ts.map +1 -1
  14. package/dist/commands/context-hub.js +394 -28
  15. package/dist/commands/context-hub.js.map +1 -1
  16. package/dist/commands/gtm-process-update.d.ts +10 -0
  17. package/dist/commands/gtm-process-update.d.ts.map +1 -0
  18. package/dist/commands/gtm-process-update.js +101 -0
  19. package/dist/commands/gtm-process-update.js.map +1 -0
  20. package/dist/commands/init.d.ts.map +1 -1
  21. package/dist/commands/init.js +278 -4
  22. package/dist/commands/init.js.map +1 -1
  23. package/dist/commands/login.d.ts.map +1 -1
  24. package/dist/commands/login.js +32 -33
  25. package/dist/commands/login.js.map +1 -1
  26. package/dist/commands/memory.d.ts +38 -0
  27. package/dist/commands/memory.d.ts.map +1 -0
  28. package/dist/commands/memory.js +229 -0
  29. package/dist/commands/memory.js.map +1 -0
  30. package/dist/commands/migrate-services.d.ts +8 -0
  31. package/dist/commands/migrate-services.d.ts.map +1 -0
  32. package/dist/commands/migrate-services.js +182 -0
  33. package/dist/commands/migrate-services.js.map +1 -0
  34. package/dist/commands/onboard.d.ts +24 -0
  35. package/dist/commands/onboard.d.ts.map +1 -0
  36. package/dist/commands/onboard.js +663 -0
  37. package/dist/commands/onboard.js.map +1 -0
  38. package/dist/commands/openclaw.d.ts +56 -0
  39. package/dist/commands/openclaw.d.ts.map +1 -0
  40. package/dist/commands/openclaw.js +700 -0
  41. package/dist/commands/openclaw.js.map +1 -0
  42. package/dist/commands/orchestrate.d.ts +14 -0
  43. package/dist/commands/orchestrate.d.ts.map +1 -0
  44. package/dist/commands/orchestrate.js +270 -0
  45. package/dist/commands/orchestrate.js.map +1 -0
  46. package/dist/commands/profile.d.ts +46 -0
  47. package/dist/commands/profile.d.ts.map +1 -0
  48. package/dist/commands/profile.js +498 -0
  49. package/dist/commands/profile.js.map +1 -0
  50. package/dist/commands/repair.d.ts.map +1 -1
  51. package/dist/commands/repair.js +37 -0
  52. package/dist/commands/repair.js.map +1 -1
  53. package/dist/commands/service-agent.d.ts +16 -0
  54. package/dist/commands/service-agent.d.ts.map +1 -0
  55. package/dist/commands/service-agent.js +375 -0
  56. package/dist/commands/service-agent.js.map +1 -0
  57. package/dist/commands/service-manager.d.ts +12 -0
  58. package/dist/commands/service-manager.d.ts.map +1 -0
  59. package/dist/commands/service-manager.js +967 -0
  60. package/dist/commands/service-manager.js.map +1 -0
  61. package/dist/commands/service-validate.d.ts +12 -0
  62. package/dist/commands/service-validate.d.ts.map +1 -0
  63. package/dist/commands/service-validate.js +611 -0
  64. package/dist/commands/service-validate.js.map +1 -0
  65. package/dist/commands/services-create.d.ts +15 -0
  66. package/dist/commands/services-create.d.ts.map +1 -0
  67. package/dist/commands/services-create.js +1452 -0
  68. package/dist/commands/services-create.js.map +1 -0
  69. package/dist/commands/services-scan.d.ts +13 -0
  70. package/dist/commands/services-scan.d.ts.map +1 -0
  71. package/dist/commands/services-scan.js +251 -0
  72. package/dist/commands/services-scan.js.map +1 -0
  73. package/dist/commands/services-sync-agents.d.ts +23 -0
  74. package/dist/commands/services-sync-agents.d.ts.map +1 -0
  75. package/dist/commands/services-sync-agents.js +207 -0
  76. package/dist/commands/services-sync-agents.js.map +1 -0
  77. package/dist/commands/services.d.ts +19 -0
  78. package/dist/commands/services.d.ts.map +1 -0
  79. package/dist/commands/services.js +742 -0
  80. package/dist/commands/services.js.map +1 -0
  81. package/dist/commands/session.d.ts +5 -1
  82. package/dist/commands/session.d.ts.map +1 -1
  83. package/dist/commands/session.js +68 -586
  84. package/dist/commands/session.js.map +1 -1
  85. package/dist/commands/status.d.ts.map +1 -1
  86. package/dist/commands/status.js +17 -0
  87. package/dist/commands/status.js.map +1 -1
  88. package/dist/commands/update.d.ts.map +1 -1
  89. package/dist/commands/update.js +75 -21
  90. package/dist/commands/update.js.map +1 -1
  91. package/dist/commands/validate-settings.d.ts +37 -0
  92. package/dist/commands/validate-settings.d.ts.map +1 -0
  93. package/dist/commands/validate-settings.js +197 -0
  94. package/dist/commands/validate-settings.js.map +1 -0
  95. package/dist/commands/voice.d.ts +0 -1
  96. package/dist/commands/voice.d.ts.map +1 -1
  97. package/dist/commands/voice.js +16 -15
  98. package/dist/commands/voice.js.map +1 -1
  99. package/dist/index.js +395 -141
  100. package/dist/index.js.map +1 -1
  101. package/dist/lib/agent-generator.d.ts +26 -0
  102. package/dist/lib/agent-generator.d.ts.map +1 -0
  103. package/dist/lib/agent-generator.js +331 -0
  104. package/dist/lib/agent-generator.js.map +1 -0
  105. package/dist/lib/memory-db.d.ts +102 -0
  106. package/dist/lib/memory-db.d.ts.map +1 -0
  107. package/dist/lib/memory-db.js +313 -0
  108. package/dist/lib/memory-db.js.map +1 -0
  109. package/dist/lib/memory-indexer.d.ts +47 -0
  110. package/dist/lib/memory-indexer.d.ts.map +1 -0
  111. package/dist/lib/memory-indexer.js +215 -0
  112. package/dist/lib/memory-indexer.js.map +1 -0
  113. package/dist/lib/memory-search.d.ts +41 -0
  114. package/dist/lib/memory-search.d.ts.map +1 -0
  115. package/dist/lib/memory-search.js +246 -0
  116. package/dist/lib/memory-search.js.map +1 -0
  117. package/dist/lib/openclaw-registry.d.ts +48 -0
  118. package/dist/lib/openclaw-registry.d.ts.map +1 -0
  119. package/dist/lib/openclaw-registry.js +181 -0
  120. package/dist/lib/openclaw-registry.js.map +1 -0
  121. package/dist/lib/openclaw-sdk.d.ts +107 -0
  122. package/dist/lib/openclaw-sdk.d.ts.map +1 -0
  123. package/dist/lib/openclaw-sdk.js +208 -0
  124. package/dist/lib/openclaw-sdk.js.map +1 -0
  125. package/dist/lib/peer-agent-generator.d.ts +44 -0
  126. package/dist/lib/peer-agent-generator.d.ts.map +1 -0
  127. package/dist/lib/peer-agent-generator.js +286 -0
  128. package/dist/lib/peer-agent-generator.js.map +1 -0
  129. package/dist/lib/service-dependencies.d.ts +44 -0
  130. package/dist/lib/service-dependencies.d.ts.map +1 -0
  131. package/dist/lib/service-dependencies.js +314 -0
  132. package/dist/lib/service-dependencies.js.map +1 -0
  133. package/dist/lib/service-detector.d.ts +61 -0
  134. package/dist/lib/service-detector.d.ts.map +1 -0
  135. package/dist/lib/service-detector.js +521 -0
  136. package/dist/lib/service-detector.js.map +1 -0
  137. package/dist/lib/service-gtm.d.ts +157 -0
  138. package/dist/lib/service-gtm.d.ts.map +1 -0
  139. package/dist/lib/service-gtm.js +786 -0
  140. package/dist/lib/service-gtm.js.map +1 -0
  141. package/dist/lib/service-mcp-base.d.ts +103 -0
  142. package/dist/lib/service-mcp-base.d.ts.map +1 -0
  143. package/dist/lib/service-mcp-base.js +263 -0
  144. package/dist/lib/service-mcp-base.js.map +1 -0
  145. package/dist/lib/service-utils.d.ts +103 -0
  146. package/dist/lib/service-utils.d.ts.map +1 -0
  147. package/dist/lib/service-utils.js +368 -0
  148. package/dist/lib/service-utils.js.map +1 -0
  149. package/dist/lib/skill-generator.d.ts +21 -0
  150. package/dist/lib/skill-generator.d.ts.map +1 -0
  151. package/dist/lib/skill-generator.js +253 -0
  152. package/dist/lib/skill-generator.js.map +1 -0
  153. package/dist/lib/stratus-client.d.ts +100 -0
  154. package/dist/lib/stratus-client.d.ts.map +1 -0
  155. package/dist/lib/stratus-client.js +255 -0
  156. package/dist/lib/stratus-client.js.map +1 -0
  157. package/dist/mcp/context-hub-mcp.js +135 -53
  158. package/dist/mcp/context-hub-mcp.js.map +1 -1
  159. package/dist/mcp/service-mcp-server.d.ts +12 -0
  160. package/dist/mcp/service-mcp-server.d.ts.map +1 -0
  161. package/dist/mcp/service-mcp-server.js +434 -0
  162. package/dist/mcp/service-mcp-server.js.map +1 -0
  163. package/dist/mcp/service-peer-mcp.d.ts +36 -0
  164. package/dist/mcp/service-peer-mcp.d.ts.map +1 -0
  165. package/dist/mcp/service-peer-mcp.js +220 -0
  166. package/dist/mcp/service-peer-mcp.js.map +1 -0
  167. package/dist/mcp/service-registry-mcp.d.ts +13 -0
  168. package/dist/mcp/service-registry-mcp.d.ts.map +1 -0
  169. package/dist/mcp/service-registry-mcp.js +330 -0
  170. package/dist/mcp/service-registry-mcp.js.map +1 -0
  171. package/dist/ui/banner.js +1 -1
  172. package/dist/ui/banner.js.map +1 -1
  173. package/dist/ui/context-hub-logs.d.ts +10 -0
  174. package/dist/ui/context-hub-logs.d.ts.map +1 -0
  175. package/dist/ui/context-hub-logs.js +175 -0
  176. package/dist/ui/context-hub-logs.js.map +1 -0
  177. package/dist/ui/service-dashboard.d.ts +11 -0
  178. package/dist/ui/service-dashboard.d.ts.map +1 -0
  179. package/dist/ui/service-dashboard.js +357 -0
  180. package/dist/ui/service-dashboard.js.map +1 -0
  181. package/dist/ui/services-manager.d.ts +11 -0
  182. package/dist/ui/services-manager.d.ts.map +1 -0
  183. package/dist/ui/services-manager.js +507 -0
  184. package/dist/ui/services-manager.js.map +1 -0
  185. package/dist/utils/auth-guard.d.ts.map +1 -1
  186. package/dist/utils/auth-guard.js +8 -9
  187. package/dist/utils/auth-guard.js.map +1 -1
  188. package/dist/utils/claude-md-generator.d.ts +10 -0
  189. package/dist/utils/claude-md-generator.d.ts.map +1 -0
  190. package/dist/utils/claude-md-generator.js +215 -0
  191. package/dist/utils/claude-md-generator.js.map +1 -0
  192. package/dist/utils/ensure-context-hub.d.ts +20 -0
  193. package/dist/utils/ensure-context-hub.d.ts.map +1 -0
  194. package/dist/utils/ensure-context-hub.js +65 -0
  195. package/dist/utils/ensure-context-hub.js.map +1 -0
  196. package/dist/utils/ensure-project.d.ts.map +1 -1
  197. package/dist/utils/ensure-project.js +3 -4
  198. package/dist/utils/ensure-project.js.map +1 -1
  199. package/dist/utils/jfl-config.d.ts +19 -0
  200. package/dist/utils/jfl-config.d.ts.map +1 -0
  201. package/dist/utils/jfl-config.js +112 -0
  202. package/dist/utils/jfl-config.js.map +1 -0
  203. package/dist/utils/jfl-migration.d.ts +29 -0
  204. package/dist/utils/jfl-migration.d.ts.map +1 -0
  205. package/dist/utils/jfl-migration.js +142 -0
  206. package/dist/utils/jfl-migration.js.map +1 -0
  207. package/dist/utils/jfl-paths.d.ts +55 -0
  208. package/dist/utils/jfl-paths.d.ts.map +1 -0
  209. package/dist/utils/jfl-paths.js +120 -0
  210. package/dist/utils/jfl-paths.js.map +1 -0
  211. package/dist/utils/settings-validator.d.ts +73 -0
  212. package/dist/utils/settings-validator.d.ts.map +1 -0
  213. package/dist/utils/settings-validator.js +222 -0
  214. package/dist/utils/settings-validator.js.map +1 -0
  215. package/package.json +19 -3
  216. package/scripts/commit-gtm.sh +56 -0
  217. package/scripts/commit-product.sh +68 -0
  218. package/scripts/context-query.sh +45 -0
  219. package/scripts/session/auto-commit.sh +297 -0
  220. package/scripts/session/jfl-doctor.sh +707 -0
  221. package/scripts/session/session-cleanup.sh +268 -0
  222. package/scripts/session/session-end.sh +198 -0
  223. package/scripts/session/session-init.sh +350 -0
  224. package/scripts/session/session-init.sh.backup +292 -0
  225. package/scripts/session/session-sync.sh +167 -0
  226. package/scripts/session/test-context-preservation.sh +160 -0
  227. package/scripts/session/test-critical-infrastructure.sh +293 -0
  228. package/scripts/session/test-experience-level.sh +336 -0
  229. package/scripts/session/test-session-cleanup.sh +268 -0
  230. package/scripts/session/test-session-sync.sh +320 -0
  231. package/scripts/voice-start.sh +36 -8
  232. package/scripts/where-am-i.sh +78 -0
  233. package/template/.claude/service-settings.json +32 -0
  234. package/template/.claude/settings.json +14 -1
  235. package/template/.claude/skills/end/SKILL.md +1780 -0
  236. package/template/.jfl/config.json +2 -1
  237. package/template/CLAUDE.md +1039 -134
  238. package/template/CLAUDE.md.bak +1187 -0
  239. package/template/scripts/commit-gtm.sh +56 -0
  240. package/template/scripts/commit-product.sh +68 -0
  241. package/template/scripts/migrate-to-branch-sessions.sh +201 -0
  242. package/template/scripts/session/auto-commit.sh +58 -6
  243. package/template/scripts/session/jfl-doctor.sh +137 -17
  244. package/template/scripts/session/session-cleanup.sh +268 -0
  245. package/template/scripts/session/session-end.sh +4 -0
  246. package/template/scripts/session/session-init.sh +253 -66
  247. package/template/scripts/session/test-critical-infrastructure.sh +293 -0
  248. package/template/scripts/session/test-experience-level.sh +336 -0
  249. package/template/scripts/session/test-session-cleanup.sh +268 -0
  250. package/template/scripts/session/test-session-sync.sh +320 -0
  251. package/template/scripts/where-am-i.sh +78 -0
  252. package/template/templates/service-agent/.claude/settings.json +32 -0
  253. package/template/templates/service-agent/CLAUDE.md +334 -0
  254. package/template/templates/service-agent/knowledge/ARCHITECTURE.md +115 -0
  255. package/template/templates/service-agent/knowledge/DEPLOYMENT.md +199 -0
  256. package/template/templates/service-agent/knowledge/RUNBOOK.md +412 -0
  257. package/template/templates/service-agent/knowledge/SERVICE_SPEC.md +77 -0
  258. package/dist/commands/session-mgmt.d.ts +0 -33
  259. package/dist/commands/session-mgmt.d.ts.map +0 -1
  260. package/dist/commands/session-mgmt.js +0 -404
  261. package/dist/commands/session-mgmt.js.map +0 -1
  262. package/template/scripts/session/auto-merge.sh +0 -325
@@ -0,0 +1,786 @@
1
+ /**
2
+ * Service-GTM Coordination Library
3
+ *
4
+ * Provides functions for coordinating between service projects and parent GTM workspaces.
5
+ *
6
+ * @purpose Service-GTM coordination helpers for sync and validation
7
+ * @spec Service /end Skill Deployment & GTM Sync Implementation Plan
8
+ */
9
+ import * as fs from "fs";
10
+ import * as path from "path";
11
+ import { execSync } from "child_process";
12
+ import axios from "axios";
13
+ /**
14
+ * Find parent GTM path from service config
15
+ */
16
+ export function findGTMParent(servicePath) {
17
+ const configPath = path.join(servicePath, ".jfl", "config.json");
18
+ if (!fs.existsSync(configPath)) {
19
+ return null;
20
+ }
21
+ try {
22
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
23
+ return config.gtm_parent || null;
24
+ }
25
+ catch (error) {
26
+ return null;
27
+ }
28
+ }
29
+ /**
30
+ * Validate GTM parent exists and is valid GTM workspace
31
+ */
32
+ export function validateGTMParent(gtmPath) {
33
+ // Check directory exists
34
+ if (!fs.existsSync(gtmPath)) {
35
+ return false;
36
+ }
37
+ // Check .jfl/config.json exists
38
+ const configPath = path.join(gtmPath, ".jfl", "config.json");
39
+ if (!fs.existsSync(configPath)) {
40
+ return false;
41
+ }
42
+ // Check it's actually a GTM
43
+ try {
44
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
45
+ return config.type === "gtm";
46
+ }
47
+ catch (error) {
48
+ return false;
49
+ }
50
+ }
51
+ /**
52
+ * Get registered services from GTM config
53
+ */
54
+ export function getRegisteredServices(gtmPath) {
55
+ const configPath = path.join(gtmPath, ".jfl", "config.json");
56
+ if (!fs.existsSync(configPath)) {
57
+ return [];
58
+ }
59
+ try {
60
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
61
+ return config.registered_services || [];
62
+ }
63
+ catch (error) {
64
+ return [];
65
+ }
66
+ }
67
+ /**
68
+ * Add service to GTM's registered_services array
69
+ */
70
+ export function addServiceToGTM(gtmPath, serviceConfig) {
71
+ const configPath = path.join(gtmPath, ".jfl", "config.json");
72
+ if (!fs.existsSync(configPath)) {
73
+ throw new Error(`GTM config not found: ${configPath}`);
74
+ }
75
+ try {
76
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
77
+ // Initialize registered_services if needed
78
+ if (!config.registered_services) {
79
+ config.registered_services = [];
80
+ }
81
+ // Check if already registered
82
+ const existing = config.registered_services.find((s) => s.name === serviceConfig.name);
83
+ if (existing) {
84
+ // Update existing
85
+ existing.status = "active";
86
+ existing.type = serviceConfig.service_type || "unknown";
87
+ }
88
+ else {
89
+ // Add new
90
+ const relativePath = path.relative(gtmPath, serviceConfig.gtm_parent || "");
91
+ config.registered_services.push({
92
+ name: serviceConfig.name,
93
+ path: relativePath || ".",
94
+ type: serviceConfig.service_type || "unknown",
95
+ registered_at: new Date().toISOString(),
96
+ status: "active",
97
+ });
98
+ }
99
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
100
+ }
101
+ catch (error) {
102
+ throw new Error(`Failed to add service to GTM: ${error.message}`);
103
+ }
104
+ }
105
+ /**
106
+ * Update service sync timestamp in GTM config
107
+ */
108
+ export function updateServiceSync(gtmPath, serviceName, timestamp) {
109
+ const configPath = path.join(gtmPath, ".jfl", "config.json");
110
+ if (!fs.existsSync(configPath)) {
111
+ throw new Error(`GTM config not found: ${configPath}`);
112
+ }
113
+ try {
114
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
115
+ if (!config.registered_services) {
116
+ config.registered_services = [];
117
+ }
118
+ const service = config.registered_services.find((s) => s.name === serviceName);
119
+ if (service) {
120
+ service.last_sync = timestamp;
121
+ }
122
+ else {
123
+ // Service not registered, add it
124
+ config.registered_services.push({
125
+ name: serviceName,
126
+ path: ".",
127
+ type: "unknown",
128
+ registered_at: timestamp,
129
+ last_sync: timestamp,
130
+ status: "active",
131
+ });
132
+ }
133
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
134
+ }
135
+ catch (error) {
136
+ throw new Error(`Failed to update service sync: ${error.message}`);
137
+ }
138
+ }
139
+ /**
140
+ * Sync journal entries from service to GTM
141
+ */
142
+ export function syncJournalsToGTM(servicePath, gtmPath, serviceName) {
143
+ const serviceJournalPath = path.join(servicePath, ".jfl", "journal");
144
+ const gtmJournalPath = path.join(gtmPath, ".jfl", "journal");
145
+ // Ensure GTM journal directory exists
146
+ if (!fs.existsSync(gtmJournalPath)) {
147
+ fs.mkdirSync(gtmJournalPath, { recursive: true });
148
+ }
149
+ // Check if service journal directory exists
150
+ if (!fs.existsSync(serviceJournalPath)) {
151
+ return 0;
152
+ }
153
+ let syncedCount = 0;
154
+ // Get all journal files
155
+ const journalFiles = fs
156
+ .readdirSync(serviceJournalPath)
157
+ .filter((f) => f.endsWith(".jsonl"));
158
+ for (const file of journalFiles) {
159
+ const sourcePath = path.join(serviceJournalPath, file);
160
+ const targetName = `service-${serviceName}-${file}`;
161
+ const targetPath = path.join(gtmJournalPath, targetName);
162
+ // Copy journal file
163
+ fs.copyFileSync(sourcePath, targetPath);
164
+ syncedCount++;
165
+ }
166
+ return syncedCount;
167
+ }
168
+ /**
169
+ * Detect files changed in current session using git diff
170
+ */
171
+ async function detectChangedFiles(servicePath, sessionBranch, workingBranch) {
172
+ const result = {
173
+ knowledge: [],
174
+ content: [],
175
+ config: [],
176
+ claude_md: false,
177
+ };
178
+ try {
179
+ // Get all files changed between working branch and session branch
180
+ const changedFiles = execSync(`git diff --name-only ${workingBranch}..HEAD`, { cwd: servicePath, encoding: "utf-8" })
181
+ .trim()
182
+ .split("\n")
183
+ .filter((f) => f.length > 0);
184
+ for (const file of changedFiles) {
185
+ if (file.startsWith("knowledge/")) {
186
+ result.knowledge.push(file);
187
+ }
188
+ else if (file.startsWith("content/")) {
189
+ result.content.push(file);
190
+ }
191
+ else if (file === ".jfl/config.json" ||
192
+ file === "service.json" ||
193
+ file === ".mcp.json") {
194
+ result.config.push(file);
195
+ }
196
+ else if (file === "CLAUDE.md") {
197
+ result.claude_md = true;
198
+ }
199
+ }
200
+ }
201
+ catch (error) {
202
+ // If git diff fails, return empty (session has no commits yet)
203
+ }
204
+ return result;
205
+ }
206
+ /**
207
+ * Ensure GTM service directory exists with all subdirectories
208
+ */
209
+ function ensureGTMServiceDir(gtmPath, serviceName) {
210
+ const serviceDir = path.join(gtmPath, "services", serviceName);
211
+ const dirs = [
212
+ serviceDir,
213
+ path.join(serviceDir, "knowledge"),
214
+ path.join(serviceDir, "content"),
215
+ path.join(serviceDir, "config"),
216
+ ];
217
+ for (const dir of dirs) {
218
+ if (!fs.existsSync(dir)) {
219
+ fs.mkdirSync(dir, { recursive: true });
220
+ }
221
+ }
222
+ }
223
+ /**
224
+ * Sync directory from service to GTM
225
+ */
226
+ async function syncDirectory(sourceDir, targetDir, files) {
227
+ const result = {
228
+ synced: [],
229
+ failed: [],
230
+ bytes: 0,
231
+ };
232
+ // Ensure target directory exists
233
+ if (!fs.existsSync(targetDir)) {
234
+ fs.mkdirSync(targetDir, { recursive: true });
235
+ }
236
+ for (const file of files) {
237
+ try {
238
+ const sourcePath = path.join(sourceDir, "..", file); // file includes "knowledge/" prefix
239
+ const relativePath = file.split("/").slice(1).join("/"); // Remove "knowledge/" prefix
240
+ const targetPath = path.join(targetDir, relativePath);
241
+ // Create subdirectories if needed
242
+ const targetSubdir = path.dirname(targetPath);
243
+ if (!fs.existsSync(targetSubdir)) {
244
+ fs.mkdirSync(targetSubdir, { recursive: true });
245
+ }
246
+ // Copy file
247
+ if (fs.existsSync(sourcePath)) {
248
+ const content = fs.readFileSync(sourcePath);
249
+ fs.writeFileSync(targetPath, content);
250
+ result.synced.push(file);
251
+ result.bytes += content.length;
252
+ }
253
+ }
254
+ catch (error) {
255
+ result.failed.push(file);
256
+ }
257
+ }
258
+ return result;
259
+ }
260
+ /**
261
+ * Sync config files to GTM's services/{name}/config/
262
+ */
263
+ async function syncConfigFiles(servicePath, gtmPath, serviceName, configFiles) {
264
+ const targetDir = path.join(gtmPath, "services", serviceName, "config");
265
+ const synced = [];
266
+ if (!fs.existsSync(targetDir)) {
267
+ fs.mkdirSync(targetDir, { recursive: true });
268
+ }
269
+ for (const file of configFiles) {
270
+ try {
271
+ const sourcePath = path.join(servicePath, file);
272
+ const targetPath = path.join(targetDir, path.basename(file));
273
+ if (fs.existsSync(sourcePath)) {
274
+ fs.copyFileSync(sourcePath, targetPath);
275
+ synced.push(file);
276
+ }
277
+ }
278
+ catch (error) {
279
+ // Skip failed files
280
+ }
281
+ }
282
+ return synced;
283
+ }
284
+ /**
285
+ * Sync service's CLAUDE.md to GTM
286
+ */
287
+ async function syncClaudeMd(servicePath, gtmPath, serviceName) {
288
+ const source = path.join(servicePath, "CLAUDE.md");
289
+ const target = path.join(gtmPath, "services", serviceName, "CLAUDE.md");
290
+ if (!fs.existsSync(source)) {
291
+ return false;
292
+ }
293
+ try {
294
+ fs.copyFileSync(source, target);
295
+ return true;
296
+ }
297
+ catch (error) {
298
+ return false;
299
+ }
300
+ }
301
+ /**
302
+ * Notify GTM agent about service sync
303
+ * Appends event to .jfl/service-events.jsonl for agent to process
304
+ */
305
+ async function notifyGTMAgent(gtmPath, serviceName, payload) {
306
+ try {
307
+ const eventsFile = path.join(gtmPath, ".jfl", "service-events.jsonl");
308
+ // Ensure .jfl directory exists
309
+ const jflDir = path.join(gtmPath, ".jfl");
310
+ if (!fs.existsSync(jflDir)) {
311
+ fs.mkdirSync(jflDir, { recursive: true });
312
+ }
313
+ const event = {
314
+ timestamp: new Date().toISOString(),
315
+ service: serviceName,
316
+ type: "sync-complete",
317
+ payload: {
318
+ session_branch: payload.session_branch,
319
+ duration_minutes: Math.floor(payload.session.duration_seconds / 60),
320
+ commits: payload.git.commits.length,
321
+ files_changed: payload.git.files_changed,
322
+ journal_entries: payload.journal.entry_count,
323
+ content_synced: payload.content_synced,
324
+ health: payload.health,
325
+ message: `Service ${serviceName} completed session - see services/${serviceName}/ for content`,
326
+ },
327
+ };
328
+ fs.appendFileSync(eventsFile, JSON.stringify(event) + "\n");
329
+ return true;
330
+ }
331
+ catch (error) {
332
+ return false;
333
+ }
334
+ }
335
+ /**
336
+ * Phone home to GTM - comprehensive sync with full session metadata
337
+ *
338
+ * Collects everything the GTM needs to understand what happened in the service session:
339
+ * - Session summary (duration, when it started/ended)
340
+ * - Git activity (commits, files changed, lines added/removed)
341
+ * - Journal data (entry count, types of work)
342
+ * - Health status (validation, context hub, uncommitted changes)
343
+ * - Environment metadata (versions, dependencies)
344
+ * - Content sync (knowledge, content, config files)
345
+ *
346
+ * IMPORTANT: Never blocks session end. Collects all errors and returns partial success.
347
+ */
348
+ export async function phoneHomeToGTM(servicePath, gtmPath, sessionBranch) {
349
+ const errors = [];
350
+ // Load service config
351
+ const serviceConfig = loadServiceConfig(servicePath);
352
+ const serviceName = serviceConfig.name;
353
+ const workingBranch = serviceConfig.working_branch || "main";
354
+ const syncTimestamp = new Date().toISOString();
355
+ const endTime = new Date().toISOString();
356
+ // ============================================================
357
+ // Data Collection (parallel execution, never throws)
358
+ // ============================================================
359
+ // 1. Session Summary
360
+ let sessionData = {
361
+ duration_seconds: 0,
362
+ start_time: endTime,
363
+ end_time: endTime,
364
+ };
365
+ try {
366
+ // Get first commit timestamp in session
367
+ const startTimestamp = execSync(`git log --format=%ct --reverse ${workingBranch}..HEAD 2>/dev/null | head -1`, { cwd: servicePath, encoding: "utf-8" }).trim();
368
+ if (startTimestamp) {
369
+ const startTimeUnix = parseInt(startTimestamp);
370
+ const endTimeUnix = Math.floor(Date.now() / 1000);
371
+ sessionData.duration_seconds = endTimeUnix - startTimeUnix;
372
+ sessionData.start_time = new Date(startTimeUnix * 1000).toISOString();
373
+ }
374
+ }
375
+ catch (error) {
376
+ errors.push(`Failed to calculate session duration: ${error.message}`);
377
+ }
378
+ // 2. Git Activity
379
+ const gitData = {
380
+ commits: [],
381
+ files_changed: 0,
382
+ lines_added: 0,
383
+ lines_removed: 0,
384
+ branch_merged_to: workingBranch,
385
+ };
386
+ try {
387
+ // Get commits
388
+ const commitLog = execSync(`git log --format='%H|%s|%an|%ct' ${workingBranch}..HEAD`, { cwd: servicePath, encoding: "utf-8" }).trim();
389
+ if (commitLog) {
390
+ const commitLines = commitLog.split("\n");
391
+ for (const line of commitLines) {
392
+ const [hash, message, author, timestamp] = line.split("|");
393
+ gitData.commits.push({
394
+ hash: hash.substring(0, 8),
395
+ message,
396
+ author,
397
+ timestamp: new Date(parseInt(timestamp) * 1000).toISOString(),
398
+ files_changed: 0, // Will be calculated if needed
399
+ });
400
+ }
401
+ }
402
+ // Get files changed
403
+ const filesChanged = execSync(`git diff --name-only ${workingBranch}..HEAD | wc -l`, { cwd: servicePath, encoding: "utf-8" }).trim();
404
+ gitData.files_changed = parseInt(filesChanged) || 0;
405
+ // Get lines changed
406
+ const diffStat = execSync(`git diff --numstat ${workingBranch}..HEAD`, { cwd: servicePath, encoding: "utf-8" }).trim();
407
+ if (diffStat) {
408
+ const lines = diffStat.split("\n");
409
+ for (const line of lines) {
410
+ const [added, removed] = line.split("\t");
411
+ gitData.lines_added += parseInt(added) || 0;
412
+ gitData.lines_removed += parseInt(removed) || 0;
413
+ }
414
+ }
415
+ }
416
+ catch (error) {
417
+ errors.push(`Failed to collect git activity: ${error.message}`);
418
+ }
419
+ // 3. Journal Data
420
+ const journalData = {
421
+ entry_count: 0,
422
+ types: {
423
+ feature: 0,
424
+ fix: 0,
425
+ decision: 0,
426
+ discovery: 0,
427
+ milestone: 0,
428
+ other: 0,
429
+ },
430
+ };
431
+ try {
432
+ const journalPath = path.join(servicePath, ".jfl", "journal");
433
+ if (fs.existsSync(journalPath)) {
434
+ const journalFiles = fs
435
+ .readdirSync(journalPath)
436
+ .filter((f) => f.endsWith(".jsonl"));
437
+ for (const file of journalFiles) {
438
+ const filePath = path.join(journalPath, file);
439
+ const content = fs.readFileSync(filePath, "utf-8");
440
+ const lines = content.trim().split("\n").filter((l) => l.length > 0);
441
+ journalData.entry_count += lines.length;
442
+ // Count by type
443
+ for (const line of lines) {
444
+ try {
445
+ const entry = JSON.parse(line);
446
+ const type = entry.type || "other";
447
+ if (type in journalData.types) {
448
+ journalData.types[type]++;
449
+ }
450
+ else {
451
+ journalData.types.other++;
452
+ }
453
+ }
454
+ catch {
455
+ // Skip invalid JSON lines
456
+ }
457
+ }
458
+ }
459
+ }
460
+ }
461
+ catch (error) {
462
+ errors.push(`Failed to collect journal data: ${error.message}`);
463
+ }
464
+ // 4. Health Status
465
+ const healthData = {
466
+ context_hub_connected: false,
467
+ uncommitted_changes: false,
468
+ };
469
+ try {
470
+ // Check uncommitted changes
471
+ try {
472
+ execSync("git diff --quiet && git diff --cached --quiet", {
473
+ cwd: servicePath,
474
+ });
475
+ healthData.uncommitted_changes = false;
476
+ }
477
+ catch {
478
+ healthData.uncommitted_changes = true;
479
+ }
480
+ // Check Context Hub connectivity
481
+ try {
482
+ await axios.get("http://localhost:4242/health", { timeout: 1000 });
483
+ healthData.context_hub_connected = true;
484
+ }
485
+ catch {
486
+ healthData.context_hub_connected = false;
487
+ }
488
+ // Run validation if available
489
+ try {
490
+ const validationResult = execSync("jfl services validate --json 2>/dev/null || echo '{}'", { cwd: servicePath, encoding: "utf-8" }).trim();
491
+ const validation = JSON.parse(validationResult);
492
+ if (validation.summary) {
493
+ healthData.validation_results = {
494
+ errors: validation.summary.errors || 0,
495
+ warnings: validation.summary.warnings || 0,
496
+ passed: validation.summary.passed || 0,
497
+ };
498
+ }
499
+ }
500
+ catch {
501
+ // Validation not available or failed
502
+ }
503
+ }
504
+ catch (error) {
505
+ errors.push(`Failed to collect health status: ${error.message}`);
506
+ }
507
+ // 5. Environment Metadata
508
+ const environmentData = {
509
+ node_version: process.version,
510
+ jfl_version: "unknown",
511
+ };
512
+ try {
513
+ // Try to get JFL version
514
+ const jflVersion = execSync("jfl --version 2>/dev/null || echo 'unknown'", {
515
+ encoding: "utf-8",
516
+ }).trim();
517
+ environmentData.jfl_version = jflVersion;
518
+ // Get key dependencies from package.json
519
+ const packageJsonPath = path.join(servicePath, "package.json");
520
+ if (fs.existsSync(packageJsonPath)) {
521
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
522
+ environmentData.dependencies = packageJson.dependencies || {};
523
+ }
524
+ }
525
+ catch (error) {
526
+ errors.push(`Failed to collect environment metadata: ${error.message}`);
527
+ }
528
+ // ============================================================
529
+ // Content Sync (knowledge, content, config files)
530
+ // ============================================================
531
+ // Ensure GTM service directory structure exists
532
+ ensureGTMServiceDir(gtmPath, serviceName);
533
+ // Detect changed files
534
+ const changedFiles = await detectChangedFiles(servicePath, sessionBranch, workingBranch);
535
+ // Initialize content sync tracking
536
+ const contentSyncData = {
537
+ knowledge_files: [],
538
+ content_files: [],
539
+ config_files: [],
540
+ claude_md_synced: false,
541
+ custom_files: [],
542
+ total_bytes: 0,
543
+ };
544
+ // Sync knowledge directory (if enabled)
545
+ if (serviceConfig.sync_to_parent?.knowledge &&
546
+ changedFiles.knowledge.length > 0) {
547
+ try {
548
+ const result = await syncDirectory(path.join(servicePath, "knowledge"), path.join(gtmPath, "services", serviceName, "knowledge"), changedFiles.knowledge);
549
+ contentSyncData.knowledge_files = result.synced;
550
+ contentSyncData.total_bytes += result.bytes;
551
+ if (result.failed.length > 0) {
552
+ errors.push(`Failed to sync ${result.failed.length} knowledge files: ${result.failed.join(", ")}`);
553
+ }
554
+ }
555
+ catch (error) {
556
+ errors.push(`Knowledge sync failed: ${error.message}`);
557
+ }
558
+ }
559
+ // Sync content directory (if enabled)
560
+ if (serviceConfig.sync_to_parent?.content &&
561
+ changedFiles.content.length > 0) {
562
+ try {
563
+ const result = await syncDirectory(path.join(servicePath, "content"), path.join(gtmPath, "services", serviceName, "content"), changedFiles.content);
564
+ contentSyncData.content_files = result.synced;
565
+ contentSyncData.total_bytes += result.bytes;
566
+ if (result.failed.length > 0) {
567
+ errors.push(`Failed to sync ${result.failed.length} content files: ${result.failed.join(", ")}`);
568
+ }
569
+ }
570
+ catch (error) {
571
+ errors.push(`Content sync failed: ${error.message}`);
572
+ }
573
+ }
574
+ // Sync config files (always enabled)
575
+ if (changedFiles.config.length > 0) {
576
+ try {
577
+ const synced = await syncConfigFiles(servicePath, gtmPath, serviceName, changedFiles.config);
578
+ contentSyncData.config_files = synced;
579
+ // Calculate bytes for config files
580
+ for (const file of synced) {
581
+ try {
582
+ const filePath = path.join(servicePath, file);
583
+ const stats = fs.statSync(filePath);
584
+ contentSyncData.total_bytes += stats.size;
585
+ }
586
+ catch {
587
+ // Skip if can't stat
588
+ }
589
+ }
590
+ }
591
+ catch (error) {
592
+ errors.push(`Config sync failed: ${error.message}`);
593
+ }
594
+ }
595
+ // Sync CLAUDE.md (if changed)
596
+ if (changedFiles.claude_md) {
597
+ try {
598
+ const synced = await syncClaudeMd(servicePath, gtmPath, serviceName);
599
+ contentSyncData.claude_md_synced = synced;
600
+ if (synced) {
601
+ try {
602
+ const stats = fs.statSync(path.join(servicePath, "CLAUDE.md"));
603
+ contentSyncData.total_bytes += stats.size;
604
+ }
605
+ catch {
606
+ // Skip if can't stat
607
+ }
608
+ }
609
+ }
610
+ catch (error) {
611
+ errors.push(`CLAUDE.md sync failed: ${error.message}`);
612
+ }
613
+ }
614
+ // ============================================================
615
+ // Build Final Payload (before using it in journal entry)
616
+ // ============================================================
617
+ const payload = {
618
+ service_name: serviceName,
619
+ sync_timestamp: syncTimestamp,
620
+ session_branch: sessionBranch,
621
+ session: sessionData,
622
+ git: gitData,
623
+ journal: journalData,
624
+ health: healthData,
625
+ environment: environmentData,
626
+ content_synced: contentSyncData,
627
+ sync_config: {
628
+ knowledge_enabled: serviceConfig.sync_to_parent?.knowledge || false,
629
+ content_enabled: serviceConfig.sync_to_parent?.content || false,
630
+ config_enabled: true,
631
+ detection_method: "git-diff",
632
+ },
633
+ agent_notified: false,
634
+ errors,
635
+ };
636
+ // ============================================================
637
+ // Sync Operations (never blocks session end)
638
+ // ============================================================
639
+ // 1. Copy journal files (reuse existing function)
640
+ try {
641
+ syncJournalsToGTM(servicePath, gtmPath, serviceName);
642
+ }
643
+ catch (error) {
644
+ errors.push(`Failed to sync journal files: ${error.message}`);
645
+ }
646
+ // 2. Write comprehensive journal entry to GTM
647
+ try {
648
+ const gtmSession = execSync("git branch --show-current 2>/dev/null || echo 'main'", {
649
+ cwd: gtmPath,
650
+ encoding: "utf-8",
651
+ })
652
+ .trim();
653
+ const gtmJournalPath = path.join(gtmPath, ".jfl", "journal", `${gtmSession}.jsonl`);
654
+ // Build detail string with content sync info
655
+ let detailParts = [];
656
+ detailParts.push(`Session Duration: ${Math.floor(sessionData.duration_seconds / 60)}min`);
657
+ detailParts.push(`Git: ${gitData.commits.length} commits, ${gitData.files_changed} files, +${gitData.lines_added}/-${gitData.lines_removed} lines`);
658
+ if (gitData.commits.length > 0) {
659
+ detailParts.push(`Commits:\n ${gitData.commits.map((c) => `${c.hash} ${c.message}`).join("\n ")}`);
660
+ }
661
+ detailParts.push(`Journal: ${journalData.entry_count} entries`);
662
+ // Add content sync details
663
+ const totalContentFiles = contentSyncData.knowledge_files.length +
664
+ contentSyncData.content_files.length +
665
+ contentSyncData.config_files.length;
666
+ if (totalContentFiles > 0 || contentSyncData.claude_md_synced) {
667
+ let contentParts = [];
668
+ if (contentSyncData.knowledge_files.length > 0) {
669
+ contentParts.push(`knowledge (${contentSyncData.knowledge_files.length} files)`);
670
+ }
671
+ if (contentSyncData.content_files.length > 0) {
672
+ contentParts.push(`content (${contentSyncData.content_files.length} files)`);
673
+ }
674
+ if (contentSyncData.config_files.length > 0) {
675
+ contentParts.push(`config (${contentSyncData.config_files.length} files)`);
676
+ }
677
+ if (contentSyncData.claude_md_synced) {
678
+ contentParts.push("CLAUDE.md");
679
+ }
680
+ const bytesStr = contentSyncData.total_bytes > 1024 * 1024
681
+ ? `${(contentSyncData.total_bytes / (1024 * 1024)).toFixed(1)}MB`
682
+ : contentSyncData.total_bytes > 1024
683
+ ? `${(contentSyncData.total_bytes / 1024).toFixed(1)}KB`
684
+ : `${contentSyncData.total_bytes} bytes`;
685
+ detailParts.push(`Content Synced: ${contentParts.join(", ")} (${bytesStr})`);
686
+ }
687
+ if (healthData.validation_results) {
688
+ detailParts.push(`Health: ${healthData.validation_results.passed} passed, ${healthData.validation_results.warnings} warnings, ${healthData.validation_results.errors} errors`);
689
+ }
690
+ detailParts.push(`Environment: Node ${environmentData.node_version}, JFL ${environmentData.jfl_version}`);
691
+ if (errors.length > 0) {
692
+ detailParts.push(`Errors: ${errors.join("; ")}`);
693
+ }
694
+ const comprehensiveEntry = {
695
+ v: 1,
696
+ ts: syncTimestamp,
697
+ session: gtmSession,
698
+ type: "service-sync",
699
+ status: "complete",
700
+ title: `Service sync: ${serviceName}`,
701
+ summary: `${Math.floor(sessionData.duration_seconds / 60)}min session with ${gitData.commits.length} commits, ${journalData.entry_count} journal entries`,
702
+ detail: detailParts.join("\n"),
703
+ service: serviceName,
704
+ session_branch: sessionBranch,
705
+ sync_payload: payload,
706
+ };
707
+ fs.appendFileSync(gtmJournalPath, JSON.stringify(comprehensiveEntry) + "\n");
708
+ }
709
+ catch (error) {
710
+ errors.push(`Failed to write GTM journal entry: ${error.message}`);
711
+ }
712
+ // 3. Update GTM's registered_services with enhanced metadata
713
+ try {
714
+ updateServiceSync(gtmPath, serviceName, syncTimestamp);
715
+ }
716
+ catch (error) {
717
+ errors.push(`Failed to update GTM registered_services: ${error.message}`);
718
+ }
719
+ // 4. Create dedicated sync log
720
+ try {
721
+ const syncLogDir = path.join(gtmPath, ".jfl", "service-syncs");
722
+ if (!fs.existsSync(syncLogDir)) {
723
+ fs.mkdirSync(syncLogDir, { recursive: true });
724
+ }
725
+ const syncLogPath = path.join(syncLogDir, `${serviceName}.jsonl`);
726
+ const syncLogEntry = {
727
+ timestamp: syncTimestamp,
728
+ session_branch: sessionBranch,
729
+ commits: gitData.commits.length,
730
+ files_changed: gitData.files_changed,
731
+ lines_added: gitData.lines_added,
732
+ lines_removed: gitData.lines_removed,
733
+ journal_entries: journalData.entry_count,
734
+ duration_minutes: Math.floor(sessionData.duration_seconds / 60),
735
+ content_synced: contentSyncData,
736
+ errors: errors.length > 0 ? errors : undefined,
737
+ };
738
+ fs.appendFileSync(syncLogPath, JSON.stringify(syncLogEntry) + "\n");
739
+ }
740
+ catch (error) {
741
+ errors.push(`Failed to create sync log: ${error.message}`);
742
+ }
743
+ // 5. Notify GTM agent
744
+ try {
745
+ const notified = await notifyGTMAgent(gtmPath, serviceName, payload);
746
+ payload.agent_notified = notified;
747
+ // Write trigger file for GTM hooks to detect
748
+ if (notified) {
749
+ const inboxDir = path.join(gtmPath, ".jfl", "inbox");
750
+ if (!fs.existsSync(inboxDir)) {
751
+ fs.mkdirSync(inboxDir, { recursive: true });
752
+ }
753
+ const triggerFile = path.join(inboxDir, `service-update-${serviceName}-${Date.now()}.trigger`);
754
+ fs.writeFileSync(triggerFile, JSON.stringify({
755
+ service: serviceName,
756
+ timestamp: syncTimestamp,
757
+ message: `Service ${serviceName} completed session - agent notification ready`
758
+ }));
759
+ }
760
+ }
761
+ catch (error) {
762
+ errors.push(`Agent notification failed: ${error.message}`);
763
+ payload.agent_notified = false;
764
+ }
765
+ // ============================================================
766
+ // Return Acknowledgment
767
+ // ============================================================
768
+ return payload;
769
+ }
770
+ /**
771
+ * Helper: Load service config with error handling
772
+ */
773
+ function loadServiceConfig(servicePath) {
774
+ const configPath = path.join(servicePath, ".jfl", "config.json");
775
+ if (!fs.existsSync(configPath)) {
776
+ throw new Error(`Service config not found: ${configPath}`);
777
+ }
778
+ try {
779
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
780
+ return config;
781
+ }
782
+ catch (error) {
783
+ throw new Error(`Failed to load service config: ${error.message}`);
784
+ }
785
+ }
786
+ //# sourceMappingURL=service-gtm.js.map