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.
- package/README.md +443 -145
- package/clawdbot-plugin/clawdbot.plugin.json +20 -0
- package/clawdbot-plugin/index.js +555 -0
- package/clawdbot-plugin/index.ts +582 -0
- package/clawdbot-skill/SKILL.md +33 -336
- package/clawdbot-skill/index.ts +491 -321
- package/clawdbot-skill/skill.json +4 -13
- package/dist/commands/clawdbot.d.ts +11 -0
- package/dist/commands/clawdbot.d.ts.map +1 -0
- package/dist/commands/clawdbot.js +215 -0
- package/dist/commands/clawdbot.js.map +1 -0
- package/dist/commands/context-hub.d.ts +5 -0
- package/dist/commands/context-hub.d.ts.map +1 -1
- package/dist/commands/context-hub.js +394 -28
- package/dist/commands/context-hub.js.map +1 -1
- package/dist/commands/gtm-process-update.d.ts +10 -0
- package/dist/commands/gtm-process-update.d.ts.map +1 -0
- package/dist/commands/gtm-process-update.js +101 -0
- package/dist/commands/gtm-process-update.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +278 -4
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +32 -33
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/memory.d.ts +38 -0
- package/dist/commands/memory.d.ts.map +1 -0
- package/dist/commands/memory.js +229 -0
- package/dist/commands/memory.js.map +1 -0
- package/dist/commands/migrate-services.d.ts +8 -0
- package/dist/commands/migrate-services.d.ts.map +1 -0
- package/dist/commands/migrate-services.js +182 -0
- package/dist/commands/migrate-services.js.map +1 -0
- package/dist/commands/onboard.d.ts +24 -0
- package/dist/commands/onboard.d.ts.map +1 -0
- package/dist/commands/onboard.js +663 -0
- package/dist/commands/onboard.js.map +1 -0
- package/dist/commands/openclaw.d.ts +56 -0
- package/dist/commands/openclaw.d.ts.map +1 -0
- package/dist/commands/openclaw.js +700 -0
- package/dist/commands/openclaw.js.map +1 -0
- package/dist/commands/orchestrate.d.ts +14 -0
- package/dist/commands/orchestrate.d.ts.map +1 -0
- package/dist/commands/orchestrate.js +270 -0
- package/dist/commands/orchestrate.js.map +1 -0
- package/dist/commands/profile.d.ts +46 -0
- package/dist/commands/profile.d.ts.map +1 -0
- package/dist/commands/profile.js +498 -0
- package/dist/commands/profile.js.map +1 -0
- package/dist/commands/repair.d.ts.map +1 -1
- package/dist/commands/repair.js +37 -0
- package/dist/commands/repair.js.map +1 -1
- package/dist/commands/service-agent.d.ts +16 -0
- package/dist/commands/service-agent.d.ts.map +1 -0
- package/dist/commands/service-agent.js +375 -0
- package/dist/commands/service-agent.js.map +1 -0
- package/dist/commands/service-manager.d.ts +12 -0
- package/dist/commands/service-manager.d.ts.map +1 -0
- package/dist/commands/service-manager.js +967 -0
- package/dist/commands/service-manager.js.map +1 -0
- package/dist/commands/service-validate.d.ts +12 -0
- package/dist/commands/service-validate.d.ts.map +1 -0
- package/dist/commands/service-validate.js +611 -0
- package/dist/commands/service-validate.js.map +1 -0
- package/dist/commands/services-create.d.ts +15 -0
- package/dist/commands/services-create.d.ts.map +1 -0
- package/dist/commands/services-create.js +1452 -0
- package/dist/commands/services-create.js.map +1 -0
- package/dist/commands/services-scan.d.ts +13 -0
- package/dist/commands/services-scan.d.ts.map +1 -0
- package/dist/commands/services-scan.js +251 -0
- package/dist/commands/services-scan.js.map +1 -0
- package/dist/commands/services-sync-agents.d.ts +23 -0
- package/dist/commands/services-sync-agents.d.ts.map +1 -0
- package/dist/commands/services-sync-agents.js +207 -0
- package/dist/commands/services-sync-agents.js.map +1 -0
- package/dist/commands/services.d.ts +19 -0
- package/dist/commands/services.d.ts.map +1 -0
- package/dist/commands/services.js +742 -0
- package/dist/commands/services.js.map +1 -0
- package/dist/commands/session.d.ts +5 -1
- package/dist/commands/session.d.ts.map +1 -1
- package/dist/commands/session.js +68 -586
- package/dist/commands/session.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +17 -0
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +75 -21
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/validate-settings.d.ts +37 -0
- package/dist/commands/validate-settings.d.ts.map +1 -0
- package/dist/commands/validate-settings.js +197 -0
- package/dist/commands/validate-settings.js.map +1 -0
- package/dist/commands/voice.d.ts +0 -1
- package/dist/commands/voice.d.ts.map +1 -1
- package/dist/commands/voice.js +16 -15
- package/dist/commands/voice.js.map +1 -1
- package/dist/index.js +395 -141
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-generator.d.ts +26 -0
- package/dist/lib/agent-generator.d.ts.map +1 -0
- package/dist/lib/agent-generator.js +331 -0
- package/dist/lib/agent-generator.js.map +1 -0
- package/dist/lib/memory-db.d.ts +102 -0
- package/dist/lib/memory-db.d.ts.map +1 -0
- package/dist/lib/memory-db.js +313 -0
- package/dist/lib/memory-db.js.map +1 -0
- package/dist/lib/memory-indexer.d.ts +47 -0
- package/dist/lib/memory-indexer.d.ts.map +1 -0
- package/dist/lib/memory-indexer.js +215 -0
- package/dist/lib/memory-indexer.js.map +1 -0
- package/dist/lib/memory-search.d.ts +41 -0
- package/dist/lib/memory-search.d.ts.map +1 -0
- package/dist/lib/memory-search.js +246 -0
- package/dist/lib/memory-search.js.map +1 -0
- package/dist/lib/openclaw-registry.d.ts +48 -0
- package/dist/lib/openclaw-registry.d.ts.map +1 -0
- package/dist/lib/openclaw-registry.js +181 -0
- package/dist/lib/openclaw-registry.js.map +1 -0
- package/dist/lib/openclaw-sdk.d.ts +107 -0
- package/dist/lib/openclaw-sdk.d.ts.map +1 -0
- package/dist/lib/openclaw-sdk.js +208 -0
- package/dist/lib/openclaw-sdk.js.map +1 -0
- package/dist/lib/peer-agent-generator.d.ts +44 -0
- package/dist/lib/peer-agent-generator.d.ts.map +1 -0
- package/dist/lib/peer-agent-generator.js +286 -0
- package/dist/lib/peer-agent-generator.js.map +1 -0
- package/dist/lib/service-dependencies.d.ts +44 -0
- package/dist/lib/service-dependencies.d.ts.map +1 -0
- package/dist/lib/service-dependencies.js +314 -0
- package/dist/lib/service-dependencies.js.map +1 -0
- package/dist/lib/service-detector.d.ts +61 -0
- package/dist/lib/service-detector.d.ts.map +1 -0
- package/dist/lib/service-detector.js +521 -0
- package/dist/lib/service-detector.js.map +1 -0
- package/dist/lib/service-gtm.d.ts +157 -0
- package/dist/lib/service-gtm.d.ts.map +1 -0
- package/dist/lib/service-gtm.js +786 -0
- package/dist/lib/service-gtm.js.map +1 -0
- package/dist/lib/service-mcp-base.d.ts +103 -0
- package/dist/lib/service-mcp-base.d.ts.map +1 -0
- package/dist/lib/service-mcp-base.js +263 -0
- package/dist/lib/service-mcp-base.js.map +1 -0
- package/dist/lib/service-utils.d.ts +103 -0
- package/dist/lib/service-utils.d.ts.map +1 -0
- package/dist/lib/service-utils.js +368 -0
- package/dist/lib/service-utils.js.map +1 -0
- package/dist/lib/skill-generator.d.ts +21 -0
- package/dist/lib/skill-generator.d.ts.map +1 -0
- package/dist/lib/skill-generator.js +253 -0
- package/dist/lib/skill-generator.js.map +1 -0
- package/dist/lib/stratus-client.d.ts +100 -0
- package/dist/lib/stratus-client.d.ts.map +1 -0
- package/dist/lib/stratus-client.js +255 -0
- package/dist/lib/stratus-client.js.map +1 -0
- package/dist/mcp/context-hub-mcp.js +135 -53
- package/dist/mcp/context-hub-mcp.js.map +1 -1
- package/dist/mcp/service-mcp-server.d.ts +12 -0
- package/dist/mcp/service-mcp-server.d.ts.map +1 -0
- package/dist/mcp/service-mcp-server.js +434 -0
- package/dist/mcp/service-mcp-server.js.map +1 -0
- package/dist/mcp/service-peer-mcp.d.ts +36 -0
- package/dist/mcp/service-peer-mcp.d.ts.map +1 -0
- package/dist/mcp/service-peer-mcp.js +220 -0
- package/dist/mcp/service-peer-mcp.js.map +1 -0
- package/dist/mcp/service-registry-mcp.d.ts +13 -0
- package/dist/mcp/service-registry-mcp.d.ts.map +1 -0
- package/dist/mcp/service-registry-mcp.js +330 -0
- package/dist/mcp/service-registry-mcp.js.map +1 -0
- package/dist/ui/banner.js +1 -1
- package/dist/ui/banner.js.map +1 -1
- package/dist/ui/context-hub-logs.d.ts +10 -0
- package/dist/ui/context-hub-logs.d.ts.map +1 -0
- package/dist/ui/context-hub-logs.js +175 -0
- package/dist/ui/context-hub-logs.js.map +1 -0
- package/dist/ui/service-dashboard.d.ts +11 -0
- package/dist/ui/service-dashboard.d.ts.map +1 -0
- package/dist/ui/service-dashboard.js +357 -0
- package/dist/ui/service-dashboard.js.map +1 -0
- package/dist/ui/services-manager.d.ts +11 -0
- package/dist/ui/services-manager.d.ts.map +1 -0
- package/dist/ui/services-manager.js +507 -0
- package/dist/ui/services-manager.js.map +1 -0
- package/dist/utils/auth-guard.d.ts.map +1 -1
- package/dist/utils/auth-guard.js +8 -9
- package/dist/utils/auth-guard.js.map +1 -1
- package/dist/utils/claude-md-generator.d.ts +10 -0
- package/dist/utils/claude-md-generator.d.ts.map +1 -0
- package/dist/utils/claude-md-generator.js +215 -0
- package/dist/utils/claude-md-generator.js.map +1 -0
- package/dist/utils/ensure-context-hub.d.ts +20 -0
- package/dist/utils/ensure-context-hub.d.ts.map +1 -0
- package/dist/utils/ensure-context-hub.js +65 -0
- package/dist/utils/ensure-context-hub.js.map +1 -0
- package/dist/utils/ensure-project.d.ts.map +1 -1
- package/dist/utils/ensure-project.js +3 -4
- package/dist/utils/ensure-project.js.map +1 -1
- package/dist/utils/jfl-config.d.ts +19 -0
- package/dist/utils/jfl-config.d.ts.map +1 -0
- package/dist/utils/jfl-config.js +112 -0
- package/dist/utils/jfl-config.js.map +1 -0
- package/dist/utils/jfl-migration.d.ts +29 -0
- package/dist/utils/jfl-migration.d.ts.map +1 -0
- package/dist/utils/jfl-migration.js +142 -0
- package/dist/utils/jfl-migration.js.map +1 -0
- package/dist/utils/jfl-paths.d.ts +55 -0
- package/dist/utils/jfl-paths.d.ts.map +1 -0
- package/dist/utils/jfl-paths.js +120 -0
- package/dist/utils/jfl-paths.js.map +1 -0
- package/dist/utils/settings-validator.d.ts +73 -0
- package/dist/utils/settings-validator.d.ts.map +1 -0
- package/dist/utils/settings-validator.js +222 -0
- package/dist/utils/settings-validator.js.map +1 -0
- package/package.json +19 -3
- package/scripts/commit-gtm.sh +56 -0
- package/scripts/commit-product.sh +68 -0
- package/scripts/context-query.sh +45 -0
- package/scripts/session/auto-commit.sh +297 -0
- package/scripts/session/jfl-doctor.sh +707 -0
- package/scripts/session/session-cleanup.sh +268 -0
- package/scripts/session/session-end.sh +198 -0
- package/scripts/session/session-init.sh +350 -0
- package/scripts/session/session-init.sh.backup +292 -0
- package/scripts/session/session-sync.sh +167 -0
- package/scripts/session/test-context-preservation.sh +160 -0
- package/scripts/session/test-critical-infrastructure.sh +293 -0
- package/scripts/session/test-experience-level.sh +336 -0
- package/scripts/session/test-session-cleanup.sh +268 -0
- package/scripts/session/test-session-sync.sh +320 -0
- package/scripts/voice-start.sh +36 -8
- package/scripts/where-am-i.sh +78 -0
- package/template/.claude/service-settings.json +32 -0
- package/template/.claude/settings.json +14 -1
- package/template/.claude/skills/end/SKILL.md +1780 -0
- package/template/.jfl/config.json +2 -1
- package/template/CLAUDE.md +1039 -134
- package/template/CLAUDE.md.bak +1187 -0
- package/template/scripts/commit-gtm.sh +56 -0
- package/template/scripts/commit-product.sh +68 -0
- package/template/scripts/migrate-to-branch-sessions.sh +201 -0
- package/template/scripts/session/auto-commit.sh +58 -6
- package/template/scripts/session/jfl-doctor.sh +137 -17
- package/template/scripts/session/session-cleanup.sh +268 -0
- package/template/scripts/session/session-end.sh +4 -0
- package/template/scripts/session/session-init.sh +253 -66
- package/template/scripts/session/test-critical-infrastructure.sh +293 -0
- package/template/scripts/session/test-experience-level.sh +336 -0
- package/template/scripts/session/test-session-cleanup.sh +268 -0
- package/template/scripts/session/test-session-sync.sh +320 -0
- package/template/scripts/where-am-i.sh +78 -0
- package/template/templates/service-agent/.claude/settings.json +32 -0
- package/template/templates/service-agent/CLAUDE.md +334 -0
- package/template/templates/service-agent/knowledge/ARCHITECTURE.md +115 -0
- package/template/templates/service-agent/knowledge/DEPLOYMENT.md +199 -0
- package/template/templates/service-agent/knowledge/RUNBOOK.md +412 -0
- package/template/templates/service-agent/knowledge/SERVICE_SPEC.md +77 -0
- package/dist/commands/session-mgmt.d.ts +0 -33
- package/dist/commands/session-mgmt.d.ts.map +0 -1
- package/dist/commands/session-mgmt.js +0 -404
- package/dist/commands/session-mgmt.js.map +0 -1
- 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
|