wogiflow 1.0.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/.workflow/agents/reviewer.md +81 -0
- package/.workflow/agents/security.md +94 -0
- package/.workflow/agents/story-writer.md +58 -0
- package/.workflow/bridges/base-bridge.js +395 -0
- package/.workflow/bridges/claude-bridge.js +434 -0
- package/.workflow/bridges/index.js +130 -0
- package/.workflow/lib/assumption-detector.js +481 -0
- package/.workflow/lib/config-substitution.js +371 -0
- package/.workflow/lib/failure-categories.js +478 -0
- package/.workflow/state/app-map.md.template +15 -0
- package/.workflow/state/architecture.md.template +24 -0
- package/.workflow/state/component-index.json.template +5 -0
- package/.workflow/state/decisions.md.template +15 -0
- package/.workflow/state/feedback-patterns.md.template +9 -0
- package/.workflow/state/knowledge-sync.json.template +6 -0
- package/.workflow/state/progress.md.template +14 -0
- package/.workflow/state/ready.json.template +7 -0
- package/.workflow/state/request-log.md.template +14 -0
- package/.workflow/state/session-state.json.template +11 -0
- package/.workflow/state/stack.md.template +33 -0
- package/.workflow/state/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +257 -0
- package/.workflow/templates/correction-report.md +67 -0
- package/.workflow/templates/gemini-md.hbs +52 -0
- package/README.md +1802 -0
- package/bin/flow +205 -0
- package/lib/index.js +33 -0
- package/lib/installer.js +467 -0
- package/lib/release-channel.js +269 -0
- package/lib/skill-registry.js +526 -0
- package/lib/upgrader.js +401 -0
- package/lib/utils.js +305 -0
- package/package.json +64 -0
- package/scripts/flow +985 -0
- package/scripts/flow-adaptive-learning.js +1259 -0
- package/scripts/flow-aggregate.js +488 -0
- package/scripts/flow-archive +133 -0
- package/scripts/flow-auto-context.js +1015 -0
- package/scripts/flow-auto-learn.js +615 -0
- package/scripts/flow-bridge.js +223 -0
- package/scripts/flow-browser-suggest.js +316 -0
- package/scripts/flow-bug.js +247 -0
- package/scripts/flow-cascade.js +711 -0
- package/scripts/flow-changelog +85 -0
- package/scripts/flow-checkpoint.js +483 -0
- package/scripts/flow-cli.js +403 -0
- package/scripts/flow-code-intelligence.js +760 -0
- package/scripts/flow-complexity.js +502 -0
- package/scripts/flow-config-set.js +152 -0
- package/scripts/flow-constants.js +157 -0
- package/scripts/flow-context +152 -0
- package/scripts/flow-context-init.js +482 -0
- package/scripts/flow-context-monitor.js +384 -0
- package/scripts/flow-context-scoring.js +886 -0
- package/scripts/flow-correct.js +458 -0
- package/scripts/flow-damage-control.js +985 -0
- package/scripts/flow-deps +101 -0
- package/scripts/flow-diff.js +700 -0
- package/scripts/flow-done +151 -0
- package/scripts/flow-done.js +489 -0
- package/scripts/flow-durable-session.js +1541 -0
- package/scripts/flow-entropy-monitor.js +345 -0
- package/scripts/flow-export-profile +349 -0
- package/scripts/flow-export-scanner.js +1046 -0
- package/scripts/flow-figma-confirm.js +400 -0
- package/scripts/flow-figma-extract.js +496 -0
- package/scripts/flow-figma-generate.js +683 -0
- package/scripts/flow-figma-index.js +909 -0
- package/scripts/flow-figma-match.js +617 -0
- package/scripts/flow-figma-mcp-server.js +518 -0
- package/scripts/flow-figma-pipeline.js +414 -0
- package/scripts/flow-file-ops.js +301 -0
- package/scripts/flow-gate-confidence.js +825 -0
- package/scripts/flow-guided-edit.js +659 -0
- package/scripts/flow-health +185 -0
- package/scripts/flow-health.js +413 -0
- package/scripts/flow-hooks.js +556 -0
- package/scripts/flow-http-client.js +249 -0
- package/scripts/flow-hybrid-detect.js +167 -0
- package/scripts/flow-hybrid-interactive.js +591 -0
- package/scripts/flow-hybrid-test.js +152 -0
- package/scripts/flow-import-profile +439 -0
- package/scripts/flow-init +253 -0
- package/scripts/flow-instruction-richness.js +827 -0
- package/scripts/flow-jira-integration.js +579 -0
- package/scripts/flow-knowledge-router.js +522 -0
- package/scripts/flow-knowledge-sync.js +589 -0
- package/scripts/flow-linear-integration.js +631 -0
- package/scripts/flow-links.js +774 -0
- package/scripts/flow-log-manager.js +559 -0
- package/scripts/flow-loop-enforcer.js +1246 -0
- package/scripts/flow-loop-retry-learning.js +630 -0
- package/scripts/flow-lsp.js +923 -0
- package/scripts/flow-map-index +348 -0
- package/scripts/flow-map-sync +201 -0
- package/scripts/flow-memory-blocks.js +668 -0
- package/scripts/flow-memory-compactor.js +350 -0
- package/scripts/flow-memory-db.js +1110 -0
- package/scripts/flow-memory-sync.js +484 -0
- package/scripts/flow-metrics.js +353 -0
- package/scripts/flow-migrate-ids.js +370 -0
- package/scripts/flow-model-adapter.js +802 -0
- package/scripts/flow-model-router.js +884 -0
- package/scripts/flow-models.js +1231 -0
- package/scripts/flow-morning.js +517 -0
- package/scripts/flow-multi-approach.js +660 -0
- package/scripts/flow-new-feature +86 -0
- package/scripts/flow-onboard +1042 -0
- package/scripts/flow-orchestrate-llm.js +459 -0
- package/scripts/flow-orchestrate.js +3592 -0
- package/scripts/flow-output.js +123 -0
- package/scripts/flow-parallel-detector.js +399 -0
- package/scripts/flow-parallel-dispatch.js +987 -0
- package/scripts/flow-parallel.js +428 -0
- package/scripts/flow-pattern-enforcer.js +600 -0
- package/scripts/flow-prd-manager.js +282 -0
- package/scripts/flow-progress.js +323 -0
- package/scripts/flow-project-analyzer.js +975 -0
- package/scripts/flow-prompt-composer.js +487 -0
- package/scripts/flow-providers.js +1381 -0
- package/scripts/flow-queue.js +308 -0
- package/scripts/flow-ready +82 -0
- package/scripts/flow-ready.js +189 -0
- package/scripts/flow-regression.js +396 -0
- package/scripts/flow-response-parser.js +450 -0
- package/scripts/flow-resume.js +284 -0
- package/scripts/flow-rules-sync.js +439 -0
- package/scripts/flow-run-trace.js +718 -0
- package/scripts/flow-safety.js +587 -0
- package/scripts/flow-search +104 -0
- package/scripts/flow-security.js +481 -0
- package/scripts/flow-session-end +106 -0
- package/scripts/flow-session-end.js +437 -0
- package/scripts/flow-session-state.js +671 -0
- package/scripts/flow-setup-hooks +216 -0
- package/scripts/flow-setup-hooks.js +377 -0
- package/scripts/flow-skill-create.js +329 -0
- package/scripts/flow-skill-creator.js +572 -0
- package/scripts/flow-skill-generator.js +1046 -0
- package/scripts/flow-skill-learn.js +880 -0
- package/scripts/flow-skill-matcher.js +578 -0
- package/scripts/flow-spec-generator.js +820 -0
- package/scripts/flow-stack-wizard.js +895 -0
- package/scripts/flow-standup +162 -0
- package/scripts/flow-start +74 -0
- package/scripts/flow-start.js +235 -0
- package/scripts/flow-status +110 -0
- package/scripts/flow-status.js +301 -0
- package/scripts/flow-step-browser.js +83 -0
- package/scripts/flow-step-changelog.js +217 -0
- package/scripts/flow-step-comments.js +306 -0
- package/scripts/flow-step-complexity.js +234 -0
- package/scripts/flow-step-coverage.js +218 -0
- package/scripts/flow-step-knowledge.js +193 -0
- package/scripts/flow-step-pr-tests.js +364 -0
- package/scripts/flow-step-regression.js +89 -0
- package/scripts/flow-step-review.js +516 -0
- package/scripts/flow-step-security.js +162 -0
- package/scripts/flow-step-silent-failures.js +290 -0
- package/scripts/flow-step-simplifier.js +346 -0
- package/scripts/flow-story +105 -0
- package/scripts/flow-story.js +500 -0
- package/scripts/flow-suspend.js +252 -0
- package/scripts/flow-sync-daemon.js +654 -0
- package/scripts/flow-task-analyzer.js +606 -0
- package/scripts/flow-team-dashboard.js +748 -0
- package/scripts/flow-team-sync.js +752 -0
- package/scripts/flow-team.js +977 -0
- package/scripts/flow-tech-options.js +528 -0
- package/scripts/flow-templates.js +812 -0
- package/scripts/flow-tiered-learning.js +728 -0
- package/scripts/flow-trace +204 -0
- package/scripts/flow-transcript-chunking.js +1106 -0
- package/scripts/flow-transcript-digest.js +7918 -0
- package/scripts/flow-transcript-language.js +465 -0
- package/scripts/flow-transcript-parsing.js +1085 -0
- package/scripts/flow-transcript-stories.js +2194 -0
- package/scripts/flow-update-map +224 -0
- package/scripts/flow-utils.js +2242 -0
- package/scripts/flow-verification.js +644 -0
- package/scripts/flow-verify.js +1177 -0
- package/scripts/flow-voice-input.js +638 -0
- package/scripts/flow-watch +168 -0
- package/scripts/flow-workflow-steps.js +521 -0
- package/scripts/flow-workflow.js +1029 -0
- package/scripts/flow-worktree.js +489 -0
- package/scripts/hooks/adapters/base-adapter.js +102 -0
- package/scripts/hooks/adapters/claude-code.js +359 -0
- package/scripts/hooks/adapters/index.js +79 -0
- package/scripts/hooks/core/component-check.js +341 -0
- package/scripts/hooks/core/index.js +35 -0
- package/scripts/hooks/core/loop-check.js +241 -0
- package/scripts/hooks/core/session-context.js +294 -0
- package/scripts/hooks/core/task-gate.js +177 -0
- package/scripts/hooks/core/validation.js +230 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
- package/scripts/hooks/entry/claude-code/session-end.js +87 -0
- package/scripts/hooks/entry/claude-code/session-start.js +46 -0
- package/scripts/hooks/entry/claude-code/stop.js +43 -0
- package/scripts/postinstall.js +139 -0
- package/templates/browser-test-flow.json +56 -0
- package/templates/bug-report.md +43 -0
- package/templates/component-detail.md +42 -0
- package/templates/component.stories.tsx +49 -0
- package/templates/context/constraints.md +83 -0
- package/templates/context/conventions.md +177 -0
- package/templates/context/stack.md +60 -0
- package/templates/correction-report.md +90 -0
- package/templates/feature-proposal.md +35 -0
- package/templates/hybrid/_base.md +254 -0
- package/templates/hybrid/_patterns.md +45 -0
- package/templates/hybrid/create-component.md +127 -0
- package/templates/hybrid/create-file.md +56 -0
- package/templates/hybrid/create-hook.md +145 -0
- package/templates/hybrid/create-service.md +70 -0
- package/templates/hybrid/fix-bug.md +33 -0
- package/templates/hybrid/modify-file.md +55 -0
- package/templates/story.md +68 -0
- package/templates/task.json +56 -0
- package/templates/trace.md +69 -0
|
@@ -0,0 +1,752 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Team Sync (Project-Based)
|
|
5
|
+
*
|
|
6
|
+
* Synchronizes workflow knowledge between team members at the PROJECT level.
|
|
7
|
+
* Unlike user-based sync, this ensures the entire team shares:
|
|
8
|
+
* - decisions.md (project patterns and conventions)
|
|
9
|
+
* - app-map.md (component registry)
|
|
10
|
+
* - skill learnings (accumulated knowledge)
|
|
11
|
+
*
|
|
12
|
+
* Architecture:
|
|
13
|
+
* - Each project has a unique projectId
|
|
14
|
+
* - All sync happens at project scope, not user scope
|
|
15
|
+
* - Conflict resolution via timestamps (newest-wins) or manual merge
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const crypto = require('crypto');
|
|
21
|
+
const { getConfig, getProjectRoot } = require('./flow-utils');
|
|
22
|
+
|
|
23
|
+
// Lazy-load to avoid circular dependency
|
|
24
|
+
let _syncDecisionsToRules = null;
|
|
25
|
+
function syncDecisionsToRules() {
|
|
26
|
+
if (!_syncDecisionsToRules) {
|
|
27
|
+
_syncDecisionsToRules = require('./flow-rules-sync').syncDecisionsToRules;
|
|
28
|
+
}
|
|
29
|
+
return _syncDecisionsToRules();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get team sync configuration
|
|
34
|
+
*/
|
|
35
|
+
function getTeamConfig() {
|
|
36
|
+
const config = getConfig();
|
|
37
|
+
return config.team || {};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Generate a project ID from the project root path
|
|
42
|
+
*/
|
|
43
|
+
function generateProjectId() {
|
|
44
|
+
const projectRoot = getProjectRoot();
|
|
45
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
46
|
+
|
|
47
|
+
let projectName = path.basename(projectRoot);
|
|
48
|
+
|
|
49
|
+
// Try to get name from package.json
|
|
50
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
51
|
+
try {
|
|
52
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
53
|
+
if (pkg.name) {
|
|
54
|
+
projectName = pkg.name;
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
// Use directory name
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Create a hash from the project name and git remote
|
|
62
|
+
const gitConfigPath = path.join(projectRoot, '.git', 'config');
|
|
63
|
+
let gitRemote = '';
|
|
64
|
+
|
|
65
|
+
if (fs.existsSync(gitConfigPath)) {
|
|
66
|
+
try {
|
|
67
|
+
const gitConfig = fs.readFileSync(gitConfigPath, 'utf-8');
|
|
68
|
+
const match = gitConfig.match(/url = (.+)/);
|
|
69
|
+
if (match) {
|
|
70
|
+
gitRemote = match[1];
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
// No git remote
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const hash = crypto.createHash('sha256')
|
|
78
|
+
.update(projectName + gitRemote)
|
|
79
|
+
.digest('hex')
|
|
80
|
+
.substring(0, 12);
|
|
81
|
+
|
|
82
|
+
return `proj_${projectName.replace(/[^a-z0-9]/gi, '_').toLowerCase()}_${hash}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get sync-able files and their content
|
|
87
|
+
*/
|
|
88
|
+
function getSyncableFiles() {
|
|
89
|
+
const projectRoot = getProjectRoot();
|
|
90
|
+
const teamConfig = getTeamConfig();
|
|
91
|
+
const files = {};
|
|
92
|
+
|
|
93
|
+
// Always sync decisions.md if enabled
|
|
94
|
+
if (teamConfig.syncDecisions !== false) {
|
|
95
|
+
const decisionsPath = path.join(projectRoot, '.workflow', 'state', 'decisions.md');
|
|
96
|
+
if (fs.existsSync(decisionsPath)) {
|
|
97
|
+
files.decisions = {
|
|
98
|
+
path: decisionsPath,
|
|
99
|
+
content: fs.readFileSync(decisionsPath, 'utf-8'),
|
|
100
|
+
hash: hashContent(fs.readFileSync(decisionsPath, 'utf-8')),
|
|
101
|
+
lastModified: fs.statSync(decisionsPath).mtime.toISOString()
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Sync app-map.md if enabled
|
|
107
|
+
if (teamConfig.syncAppMap !== false) {
|
|
108
|
+
const appMapPath = path.join(projectRoot, '.workflow', 'state', 'app-map.md');
|
|
109
|
+
if (fs.existsSync(appMapPath)) {
|
|
110
|
+
files.appMap = {
|
|
111
|
+
path: appMapPath,
|
|
112
|
+
content: fs.readFileSync(appMapPath, 'utf-8'),
|
|
113
|
+
hash: hashContent(fs.readFileSync(appMapPath, 'utf-8')),
|
|
114
|
+
lastModified: fs.statSync(appMapPath).mtime.toISOString()
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Sync skill learnings if enabled
|
|
120
|
+
if (teamConfig.syncSkillLearnings !== false) {
|
|
121
|
+
const skillsDir = path.join(projectRoot, '.claude', 'skills');
|
|
122
|
+
if (fs.existsSync(skillsDir)) {
|
|
123
|
+
const skillDirs = fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
124
|
+
.filter(d => d.isDirectory() && !d.name.startsWith('_'))
|
|
125
|
+
.map(d => d.name);
|
|
126
|
+
|
|
127
|
+
files.skillLearnings = {};
|
|
128
|
+
|
|
129
|
+
for (const skill of skillDirs) {
|
|
130
|
+
const learningsPath = path.join(skillsDir, skill, 'knowledge', 'learnings.md');
|
|
131
|
+
if (fs.existsSync(learningsPath)) {
|
|
132
|
+
files.skillLearnings[skill] = {
|
|
133
|
+
path: learningsPath,
|
|
134
|
+
content: fs.readFileSync(learningsPath, 'utf-8'),
|
|
135
|
+
hash: hashContent(fs.readFileSync(learningsPath, 'utf-8')),
|
|
136
|
+
lastModified: fs.statSync(learningsPath).mtime.toISOString()
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Sync component-index.json if enabled
|
|
144
|
+
if (teamConfig.syncComponentIndex !== false) {
|
|
145
|
+
const indexPath = path.join(projectRoot, '.workflow', 'state', 'component-index.json');
|
|
146
|
+
if (fs.existsSync(indexPath)) {
|
|
147
|
+
files.componentIndex = {
|
|
148
|
+
path: indexPath,
|
|
149
|
+
content: fs.readFileSync(indexPath, 'utf-8'),
|
|
150
|
+
hash: hashContent(fs.readFileSync(indexPath, 'utf-8')),
|
|
151
|
+
lastModified: fs.statSync(indexPath).mtime.toISOString()
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Sync request-log.md if enabled (recent entries only if configured)
|
|
157
|
+
const syncRequestLog = teamConfig.syncRequestLog || teamConfig.sync?.requestLog;
|
|
158
|
+
if (syncRequestLog && syncRequestLog !== false) {
|
|
159
|
+
const logPath = path.join(projectRoot, '.workflow', 'state', 'request-log.md');
|
|
160
|
+
if (fs.existsSync(logPath)) {
|
|
161
|
+
let content = fs.readFileSync(logPath, 'utf-8');
|
|
162
|
+
|
|
163
|
+
// If "recent" mode, only sync last 20 entries
|
|
164
|
+
if (syncRequestLog === 'recent') {
|
|
165
|
+
const entries = content.split(/(?=### R-\d+)/);
|
|
166
|
+
const recentEntries = entries.slice(-20);
|
|
167
|
+
content = recentEntries.join('');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
files.requestLog = {
|
|
171
|
+
path: logPath,
|
|
172
|
+
content,
|
|
173
|
+
hash: hashContent(content),
|
|
174
|
+
lastModified: fs.statSync(logPath).mtime.toISOString(),
|
|
175
|
+
mode: syncRequestLog
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Sync ready.json (tasks) if enabled
|
|
181
|
+
if (teamConfig.syncTasks !== false && teamConfig.sync?.tasks !== false) {
|
|
182
|
+
const tasksPath = path.join(projectRoot, '.workflow', 'state', 'ready.json');
|
|
183
|
+
if (fs.existsSync(tasksPath)) {
|
|
184
|
+
files.tasks = {
|
|
185
|
+
path: tasksPath,
|
|
186
|
+
content: fs.readFileSync(tasksPath, 'utf-8'),
|
|
187
|
+
hash: hashContent(fs.readFileSync(tasksPath, 'utf-8')),
|
|
188
|
+
lastModified: fs.statSync(tasksPath).mtime.toISOString()
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Sync memory database if enabled
|
|
194
|
+
// Note: We export facts as JSON rather than syncing the raw SQLite file
|
|
195
|
+
const syncMemory = teamConfig.syncMemory || teamConfig.sync?.memory;
|
|
196
|
+
if (syncMemory) {
|
|
197
|
+
const memoryDbPath = path.join(projectRoot, '.workflow', 'memory', 'local.db');
|
|
198
|
+
if (fs.existsSync(memoryDbPath)) {
|
|
199
|
+
try {
|
|
200
|
+
// Try to export facts as JSON for safer sync
|
|
201
|
+
const facts = exportMemoryFacts(memoryDbPath);
|
|
202
|
+
if (facts && facts.length > 0) {
|
|
203
|
+
const factsJson = JSON.stringify(facts, null, 2);
|
|
204
|
+
files.memory = {
|
|
205
|
+
path: memoryDbPath,
|
|
206
|
+
content: factsJson,
|
|
207
|
+
hash: hashContent(factsJson),
|
|
208
|
+
lastModified: fs.statSync(memoryDbPath).mtime.toISOString(),
|
|
209
|
+
factCount: facts.length,
|
|
210
|
+
format: 'json-export'
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
} catch (err) {
|
|
214
|
+
// If export fails, note it but don't block sync
|
|
215
|
+
console.warn('Memory export failed:', err.message);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return files;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Export facts from memory database as JSON
|
|
225
|
+
*/
|
|
226
|
+
function exportMemoryFacts(dbPath) {
|
|
227
|
+
try {
|
|
228
|
+
// Use sqlite3 CLI to export (avoids native module dependency)
|
|
229
|
+
const { execSync } = require('child_process');
|
|
230
|
+
|
|
231
|
+
// First check what columns exist
|
|
232
|
+
const schema = execSync(
|
|
233
|
+
`sqlite3 "${dbPath}" "PRAGMA table_info(facts);"`,
|
|
234
|
+
{ encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
// Parse available columns
|
|
238
|
+
const columns = schema.split('\n')
|
|
239
|
+
.filter(Boolean)
|
|
240
|
+
.map(line => line.split('|')[1])
|
|
241
|
+
.filter(Boolean);
|
|
242
|
+
|
|
243
|
+
if (columns.length === 0) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Build query with available columns
|
|
248
|
+
const selectCols = columns.map(c => `'${c}', ${c}`).join(', ');
|
|
249
|
+
const result = execSync(
|
|
250
|
+
`sqlite3 "${dbPath}" "SELECT json_group_array(json_object(${selectCols})) FROM facts LIMIT 100"`,
|
|
251
|
+
{ encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }
|
|
252
|
+
);
|
|
253
|
+
return JSON.parse(result.trim() || '[]');
|
|
254
|
+
} catch {
|
|
255
|
+
// sqlite3 CLI not available or query failed - fail silently
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Hash content for change detection
|
|
262
|
+
*/
|
|
263
|
+
function hashContent(content) {
|
|
264
|
+
return crypto.createHash('md5').update(content).digest('hex');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Create a sync payload for upload
|
|
269
|
+
*/
|
|
270
|
+
function createSyncPayload() {
|
|
271
|
+
const teamConfig = getTeamConfig();
|
|
272
|
+
const files = getSyncableFiles();
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
projectId: teamConfig.projectId || generateProjectId(),
|
|
276
|
+
teamId: teamConfig.teamId,
|
|
277
|
+
timestamp: new Date().toISOString(),
|
|
278
|
+
files,
|
|
279
|
+
metadata: {
|
|
280
|
+
version: getConfig().version || '1.0.0',
|
|
281
|
+
syncedBy: teamConfig.userId || 'anonymous'
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Apply remote changes to local files
|
|
288
|
+
*/
|
|
289
|
+
function applyRemoteChanges(remotePayload, strategy = 'newest-wins') {
|
|
290
|
+
const projectRoot = getProjectRoot();
|
|
291
|
+
const localFiles = getSyncableFiles();
|
|
292
|
+
const changes = [];
|
|
293
|
+
|
|
294
|
+
// Apply decisions.md
|
|
295
|
+
if (remotePayload.files.decisions) {
|
|
296
|
+
const result = mergeFile(
|
|
297
|
+
localFiles.decisions,
|
|
298
|
+
remotePayload.files.decisions,
|
|
299
|
+
strategy
|
|
300
|
+
);
|
|
301
|
+
if (result.changed) {
|
|
302
|
+
fs.writeFileSync(result.path, result.content);
|
|
303
|
+
// Sync to .claude/rules/ for Claude Code integration
|
|
304
|
+
syncDecisionsToRules();
|
|
305
|
+
changes.push({ file: 'decisions.md', action: result.action });
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Apply app-map.md
|
|
310
|
+
if (remotePayload.files.appMap) {
|
|
311
|
+
const result = mergeFile(
|
|
312
|
+
localFiles.appMap,
|
|
313
|
+
remotePayload.files.appMap,
|
|
314
|
+
strategy
|
|
315
|
+
);
|
|
316
|
+
if (result.changed) {
|
|
317
|
+
fs.writeFileSync(result.path, result.content);
|
|
318
|
+
changes.push({ file: 'app-map.md', action: result.action });
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Apply skill learnings
|
|
323
|
+
if (remotePayload.files.skillLearnings) {
|
|
324
|
+
for (const [skill, remoteFile] of Object.entries(remotePayload.files.skillLearnings)) {
|
|
325
|
+
const localFile = localFiles.skillLearnings?.[skill];
|
|
326
|
+
const result = mergeFile(localFile, remoteFile, strategy);
|
|
327
|
+
|
|
328
|
+
if (result.changed) {
|
|
329
|
+
// Create skill directory if needed
|
|
330
|
+
const skillDir = path.join(projectRoot, '.claude', 'skills', skill, 'knowledge');
|
|
331
|
+
if (!fs.existsSync(skillDir)) {
|
|
332
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
333
|
+
}
|
|
334
|
+
fs.writeFileSync(result.path, result.content);
|
|
335
|
+
changes.push({ file: `.claude/skills/${skill}/learnings.md`, action: result.action });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return changes;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Merge a single file based on strategy
|
|
345
|
+
*/
|
|
346
|
+
function mergeFile(localFile, remoteFile, strategy) {
|
|
347
|
+
const projectRoot = getProjectRoot();
|
|
348
|
+
|
|
349
|
+
// Remote file path may be absolute from remote, need to localize
|
|
350
|
+
const localPath = localFile?.path || remoteFile.path.replace(/.*\.workflow/, path.join(projectRoot, '.workflow'));
|
|
351
|
+
|
|
352
|
+
// No local file - use remote
|
|
353
|
+
if (!localFile) {
|
|
354
|
+
return {
|
|
355
|
+
changed: true,
|
|
356
|
+
action: 'created',
|
|
357
|
+
path: localPath,
|
|
358
|
+
content: remoteFile.content
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Same content - no change
|
|
363
|
+
if (localFile.hash === remoteFile.hash) {
|
|
364
|
+
return { changed: false };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Apply strategy
|
|
368
|
+
switch (strategy) {
|
|
369
|
+
case 'newest-wins': {
|
|
370
|
+
const localTime = new Date(localFile.lastModified).getTime();
|
|
371
|
+
const remoteTime = new Date(remoteFile.lastModified).getTime();
|
|
372
|
+
|
|
373
|
+
if (remoteTime > localTime) {
|
|
374
|
+
return {
|
|
375
|
+
changed: true,
|
|
376
|
+
action: 'updated',
|
|
377
|
+
path: localPath,
|
|
378
|
+
content: remoteFile.content
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
return { changed: false };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
case 'remote-wins':
|
|
385
|
+
return {
|
|
386
|
+
changed: true,
|
|
387
|
+
action: 'updated',
|
|
388
|
+
path: localPath,
|
|
389
|
+
content: remoteFile.content
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
case 'local-wins':
|
|
393
|
+
return { changed: false };
|
|
394
|
+
|
|
395
|
+
case 'merge':
|
|
396
|
+
// For markdown files, we can do a simple append of unique sections
|
|
397
|
+
const merged = mergeMarkdownContent(localFile.content, remoteFile.content);
|
|
398
|
+
return {
|
|
399
|
+
changed: merged !== localFile.content,
|
|
400
|
+
action: 'merged',
|
|
401
|
+
path: localPath,
|
|
402
|
+
content: merged
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
default:
|
|
406
|
+
return { changed: false };
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Simple markdown merge - append unique sections
|
|
412
|
+
*/
|
|
413
|
+
function mergeMarkdownContent(localContent, remoteContent) {
|
|
414
|
+
const localSections = parseSections(localContent);
|
|
415
|
+
const remoteSections = parseSections(remoteContent);
|
|
416
|
+
|
|
417
|
+
// Find sections in remote that don't exist in local
|
|
418
|
+
const localHeaders = new Set(localSections.map(s => s.header.toLowerCase()));
|
|
419
|
+
const newSections = remoteSections.filter(s =>
|
|
420
|
+
!localHeaders.has(s.header.toLowerCase())
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
if (newSections.length === 0) {
|
|
424
|
+
return localContent;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Append new sections
|
|
428
|
+
let merged = localContent.trimEnd();
|
|
429
|
+
|
|
430
|
+
for (const section of newSections) {
|
|
431
|
+
merged += `\n\n${section.raw}`;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return merged;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Parse markdown into sections by headers
|
|
439
|
+
*/
|
|
440
|
+
function parseSections(content) {
|
|
441
|
+
const sections = [];
|
|
442
|
+
const lines = content.split('\n');
|
|
443
|
+
let currentSection = null;
|
|
444
|
+
|
|
445
|
+
for (const line of lines) {
|
|
446
|
+
const headerMatch = line.match(/^(#{1,3})\s+(.+)/);
|
|
447
|
+
|
|
448
|
+
if (headerMatch) {
|
|
449
|
+
if (currentSection) {
|
|
450
|
+
sections.push(currentSection);
|
|
451
|
+
}
|
|
452
|
+
currentSection = {
|
|
453
|
+
level: headerMatch[1].length,
|
|
454
|
+
header: headerMatch[2],
|
|
455
|
+
raw: line,
|
|
456
|
+
content: []
|
|
457
|
+
};
|
|
458
|
+
} else if (currentSection) {
|
|
459
|
+
currentSection.raw += '\n' + line;
|
|
460
|
+
currentSection.content.push(line);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (currentSection) {
|
|
465
|
+
sections.push(currentSection);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return sections;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Get sync status
|
|
473
|
+
*/
|
|
474
|
+
function getSyncStatus() {
|
|
475
|
+
const teamConfig = getTeamConfig();
|
|
476
|
+
const files = getSyncableFiles();
|
|
477
|
+
|
|
478
|
+
const status = {
|
|
479
|
+
enabled: teamConfig.enabled === true,
|
|
480
|
+
projectId: teamConfig.projectId || generateProjectId(),
|
|
481
|
+
teamId: teamConfig.teamId,
|
|
482
|
+
projectScope: teamConfig.projectScope !== false,
|
|
483
|
+
lastSync: teamConfig.lastSync || null,
|
|
484
|
+
files: {
|
|
485
|
+
decisions: files.decisions ? {
|
|
486
|
+
exists: true,
|
|
487
|
+
hash: files.decisions.hash,
|
|
488
|
+
lastModified: files.decisions.lastModified
|
|
489
|
+
} : { exists: false },
|
|
490
|
+
appMap: files.appMap ? {
|
|
491
|
+
exists: true,
|
|
492
|
+
hash: files.appMap.hash,
|
|
493
|
+
lastModified: files.appMap.lastModified
|
|
494
|
+
} : { exists: false },
|
|
495
|
+
componentIndex: files.componentIndex ? {
|
|
496
|
+
exists: true,
|
|
497
|
+
hash: files.componentIndex.hash,
|
|
498
|
+
lastModified: files.componentIndex.lastModified
|
|
499
|
+
} : { exists: false },
|
|
500
|
+
requestLog: files.requestLog ? {
|
|
501
|
+
exists: true,
|
|
502
|
+
mode: files.requestLog.mode,
|
|
503
|
+
lastModified: files.requestLog.lastModified
|
|
504
|
+
} : { exists: false },
|
|
505
|
+
tasks: files.tasks ? {
|
|
506
|
+
exists: true,
|
|
507
|
+
hash: files.tasks.hash,
|
|
508
|
+
lastModified: files.tasks.lastModified
|
|
509
|
+
} : { exists: false },
|
|
510
|
+
memory: files.memory ? {
|
|
511
|
+
exists: true,
|
|
512
|
+
factCount: files.memory.factCount,
|
|
513
|
+
lastModified: files.memory.lastModified
|
|
514
|
+
} : { exists: false },
|
|
515
|
+
skillLearnings: files.skillLearnings ? Object.keys(files.skillLearnings) : []
|
|
516
|
+
},
|
|
517
|
+
syncConfig: {
|
|
518
|
+
syncDecisions: teamConfig.syncDecisions !== false,
|
|
519
|
+
syncAppMap: teamConfig.syncAppMap !== false,
|
|
520
|
+
syncComponentIndex: teamConfig.syncComponentIndex !== false,
|
|
521
|
+
syncSkillLearnings: teamConfig.syncSkillLearnings !== false,
|
|
522
|
+
syncRequestLog: teamConfig.syncRequestLog || 'recent',
|
|
523
|
+
syncTasks: teamConfig.syncTasks || false,
|
|
524
|
+
syncMemory: teamConfig.syncMemory || false,
|
|
525
|
+
conflictResolution: teamConfig.conflictResolution || 'newest-wins'
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
return status;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Initialize team sync for a project
|
|
534
|
+
*/
|
|
535
|
+
function initializeTeamSync(teamId, options = {}) {
|
|
536
|
+
const projectRoot = getProjectRoot();
|
|
537
|
+
const configPath = path.join(projectRoot, '.workflow', 'config.json');
|
|
538
|
+
|
|
539
|
+
const config = getConfig();
|
|
540
|
+
|
|
541
|
+
config.team = {
|
|
542
|
+
...config.team,
|
|
543
|
+
enabled: true,
|
|
544
|
+
teamId,
|
|
545
|
+
projectId: options.projectId || generateProjectId(),
|
|
546
|
+
projectScope: true,
|
|
547
|
+
syncDecisions: options.syncDecisions !== false,
|
|
548
|
+
syncAppMap: options.syncAppMap !== false,
|
|
549
|
+
syncSkillLearnings: options.syncSkillLearnings !== false,
|
|
550
|
+
conflictResolution: options.conflictResolution || 'newest-wins',
|
|
551
|
+
lastSync: null
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
555
|
+
|
|
556
|
+
return {
|
|
557
|
+
success: true,
|
|
558
|
+
projectId: config.team.projectId,
|
|
559
|
+
message: `Team sync initialized for project ${config.team.projectId}`
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Sync with backend (placeholder for actual API call)
|
|
565
|
+
*/
|
|
566
|
+
async function syncWithBackend() {
|
|
567
|
+
const teamConfig = getTeamConfig();
|
|
568
|
+
|
|
569
|
+
if (!teamConfig.enabled) {
|
|
570
|
+
return { success: false, message: 'Team sync not enabled' };
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const payload = createSyncPayload();
|
|
574
|
+
|
|
575
|
+
// In a real implementation, this would call the API
|
|
576
|
+
// For now, save to a local sync file for testing
|
|
577
|
+
const projectRoot = getProjectRoot();
|
|
578
|
+
const syncDir = path.join(projectRoot, '.workflow', 'sync');
|
|
579
|
+
|
|
580
|
+
if (!fs.existsSync(syncDir)) {
|
|
581
|
+
fs.mkdirSync(syncDir, { recursive: true });
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const syncFile = path.join(syncDir, `sync-${Date.now()}.json`);
|
|
585
|
+
fs.writeFileSync(syncFile, JSON.stringify(payload, null, 2));
|
|
586
|
+
|
|
587
|
+
// Update last sync time
|
|
588
|
+
const configPath = path.join(projectRoot, '.workflow', 'config.json');
|
|
589
|
+
const config = getConfig();
|
|
590
|
+
config.team.lastSync = new Date().toISOString();
|
|
591
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
592
|
+
|
|
593
|
+
return {
|
|
594
|
+
success: true,
|
|
595
|
+
message: 'Sync payload created',
|
|
596
|
+
syncFile,
|
|
597
|
+
filesIncluded: Object.keys(payload.files)
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Generate sync status report
|
|
603
|
+
*/
|
|
604
|
+
function generateStatusReport() {
|
|
605
|
+
const status = getSyncStatus();
|
|
606
|
+
const lines = [
|
|
607
|
+
'',
|
|
608
|
+
'╔══════════════════════════════════════════════════════╗',
|
|
609
|
+
'║ 🔄 TEAM SYNC STATUS ║',
|
|
610
|
+
'╠══════════════════════════════════════════════════════╣'
|
|
611
|
+
];
|
|
612
|
+
|
|
613
|
+
const enabledIcon = status.enabled ? '✅' : '❌';
|
|
614
|
+
lines.push(`║ Status: ${enabledIcon} ${status.enabled ? 'Enabled' : 'Disabled'}`.padEnd(55) + '║');
|
|
615
|
+
lines.push(`║ Project ID: ${(status.projectId || 'Not set').substring(0, 35)}`.padEnd(55) + '║');
|
|
616
|
+
lines.push(`║ Team ID: ${status.teamId || 'Not set'}`.padEnd(55) + '║');
|
|
617
|
+
lines.push(`║ Scope: ${status.projectScope ? 'Project-based' : 'User-based'}`.padEnd(55) + '║');
|
|
618
|
+
|
|
619
|
+
lines.push('╠══════════════════════════════════════════════════════╣');
|
|
620
|
+
lines.push('║ Syncable Files:'.padEnd(55) + '║');
|
|
621
|
+
|
|
622
|
+
const dIcon = status.files.decisions?.exists ? '✅' : '❌';
|
|
623
|
+
const dEnabled = status.syncConfig.syncDecisions ? '' : ' (disabled)';
|
|
624
|
+
lines.push(`║ ${dIcon} decisions.md${dEnabled}`.padEnd(55) + '║');
|
|
625
|
+
|
|
626
|
+
const aIcon = status.files.appMap?.exists ? '✅' : '❌';
|
|
627
|
+
const aEnabled = status.syncConfig.syncAppMap ? '' : ' (disabled)';
|
|
628
|
+
lines.push(`║ ${aIcon} app-map.md${aEnabled}`.padEnd(55) + '║');
|
|
629
|
+
|
|
630
|
+
const cIcon = status.files.componentIndex?.exists ? '✅' : '❌';
|
|
631
|
+
const cEnabled = status.syncConfig.syncComponentIndex ? '' : ' (disabled)';
|
|
632
|
+
lines.push(`║ ${cIcon} component-index.json${cEnabled}`.padEnd(55) + '║');
|
|
633
|
+
|
|
634
|
+
const rIcon = status.files.requestLog?.exists ? '✅' : '❌';
|
|
635
|
+
const rMode = status.syncConfig.syncRequestLog === 'recent' ? ' (recent)' : '';
|
|
636
|
+
const rEnabled = status.syncConfig.syncRequestLog ? rMode : ' (disabled)';
|
|
637
|
+
lines.push(`║ ${rIcon} request-log.md${rEnabled}`.padEnd(55) + '║');
|
|
638
|
+
|
|
639
|
+
const tIcon = status.files.tasks?.exists ? '✅' : '❌';
|
|
640
|
+
const tEnabled = status.syncConfig.syncTasks ? '' : ' (disabled)';
|
|
641
|
+
lines.push(`║ ${tIcon} ready.json (tasks)${tEnabled}`.padEnd(55) + '║');
|
|
642
|
+
|
|
643
|
+
const mIcon = status.files.memory?.exists ? '✅' : '❌';
|
|
644
|
+
const mCount = status.files.memory?.factCount ? ` (${status.files.memory.factCount} facts)` : '';
|
|
645
|
+
const mEnabled = status.syncConfig.syncMemory ? mCount : ' (disabled)';
|
|
646
|
+
lines.push(`║ ${mIcon} memory facts${mEnabled}`.padEnd(55) + '║');
|
|
647
|
+
|
|
648
|
+
const skillCount = status.files.skillLearnings?.length || 0;
|
|
649
|
+
const sEnabled = status.syncConfig.syncSkillLearnings ? '' : ' (disabled)';
|
|
650
|
+
lines.push(`║ 📚 ${skillCount} skill learning files${sEnabled}`.padEnd(55) + '║');
|
|
651
|
+
|
|
652
|
+
lines.push('╠══════════════════════════════════════════════════════╣');
|
|
653
|
+
lines.push(`║ Conflict Resolution: ${status.syncConfig.conflictResolution}`.padEnd(55) + '║');
|
|
654
|
+
lines.push(`║ Last Sync: ${status.lastSync || 'Never'}`.padEnd(55) + '║');
|
|
655
|
+
lines.push('╚══════════════════════════════════════════════════════╝');
|
|
656
|
+
|
|
657
|
+
return lines.join('\n');
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// ============================================================
|
|
661
|
+
// Exports
|
|
662
|
+
// ============================================================
|
|
663
|
+
|
|
664
|
+
module.exports = {
|
|
665
|
+
getTeamConfig,
|
|
666
|
+
generateProjectId,
|
|
667
|
+
getSyncableFiles,
|
|
668
|
+
createSyncPayload,
|
|
669
|
+
applyRemoteChanges,
|
|
670
|
+
getSyncStatus,
|
|
671
|
+
initializeTeamSync,
|
|
672
|
+
syncWithBackend,
|
|
673
|
+
generateStatusReport,
|
|
674
|
+
hashContent,
|
|
675
|
+
mergeMarkdownContent
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
// ============================================================
|
|
679
|
+
// CLI
|
|
680
|
+
// ============================================================
|
|
681
|
+
|
|
682
|
+
if (require.main === module) {
|
|
683
|
+
const args = process.argv.slice(2);
|
|
684
|
+
const command = args[0];
|
|
685
|
+
|
|
686
|
+
switch (command) {
|
|
687
|
+
case 'status': {
|
|
688
|
+
console.log(generateStatusReport());
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
case 'init': {
|
|
693
|
+
const teamId = args[1];
|
|
694
|
+
if (!teamId) {
|
|
695
|
+
console.error('Usage: node flow-team-sync.js init <team-id>');
|
|
696
|
+
process.exit(1);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const result = initializeTeamSync(teamId);
|
|
700
|
+
console.log(`\n✅ ${result.message}`);
|
|
701
|
+
console.log(` Project ID: ${result.projectId}`);
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
case 'sync': {
|
|
706
|
+
syncWithBackend().then(result => {
|
|
707
|
+
if (result.success) {
|
|
708
|
+
console.log(`\n✅ ${result.message}`);
|
|
709
|
+
console.log(` Files: ${result.filesIncluded.join(', ')}`);
|
|
710
|
+
} else {
|
|
711
|
+
console.error(`\n❌ ${result.message}`);
|
|
712
|
+
process.exit(1);
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
break;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
case 'payload': {
|
|
719
|
+
const payload = createSyncPayload();
|
|
720
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
case 'project-id': {
|
|
725
|
+
console.log(generateProjectId());
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
default:
|
|
730
|
+
console.log(`
|
|
731
|
+
Wogi Flow - Team Sync (Project-Based)
|
|
732
|
+
|
|
733
|
+
Usage:
|
|
734
|
+
node flow-team-sync.js <command> [args]
|
|
735
|
+
|
|
736
|
+
Commands:
|
|
737
|
+
status Show sync status
|
|
738
|
+
init <team-id> Initialize team sync
|
|
739
|
+
sync Sync with backend
|
|
740
|
+
payload Show sync payload (debug)
|
|
741
|
+
project-id Generate/show project ID
|
|
742
|
+
|
|
743
|
+
Configuration (config.json):
|
|
744
|
+
team.enabled: true
|
|
745
|
+
team.projectScope: true (project-based, not user-based)
|
|
746
|
+
team.syncDecisions: true Sync decisions.md
|
|
747
|
+
team.syncAppMap: true Sync app-map.md
|
|
748
|
+
team.syncSkillLearnings: true Sync skill learnings
|
|
749
|
+
team.conflictResolution: "newest-wins" | "remote-wins" | "local-wins" | "merge"
|
|
750
|
+
`);
|
|
751
|
+
}
|
|
752
|
+
}
|