@stackmemoryai/stackmemory 0.3.6 → 0.3.7
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/dist/agents/verifiers/base-verifier.js.map +2 -2
- package/dist/agents/verifiers/formatter-verifier.js.map +2 -2
- package/dist/agents/verifiers/llm-judge.js.map +2 -2
- package/dist/cli/claude-sm.js +24 -13
- package/dist/cli/claude-sm.js.map +2 -2
- package/dist/cli/codex-sm.js +24 -13
- package/dist/cli/codex-sm.js.map +2 -2
- package/dist/cli/commands/agent.js.map +2 -2
- package/dist/cli/commands/chromadb.js +217 -32
- package/dist/cli/commands/chromadb.js.map +2 -2
- package/dist/cli/commands/clear.js +12 -1
- package/dist/cli/commands/clear.js.map +2 -2
- package/dist/cli/commands/context.js +13 -2
- package/dist/cli/commands/context.js.map +2 -2
- package/dist/cli/commands/dashboard.js.map +2 -2
- package/dist/cli/commands/gc.js +202 -0
- package/dist/cli/commands/gc.js.map +7 -0
- package/dist/cli/commands/handoff.js +12 -1
- package/dist/cli/commands/handoff.js.map +2 -2
- package/dist/cli/commands/infinite-storage.js +32 -21
- package/dist/cli/commands/infinite-storage.js.map +2 -2
- package/dist/cli/commands/linear-create.js +13 -2
- package/dist/cli/commands/linear-create.js.map +2 -2
- package/dist/cli/commands/linear-list.js +12 -1
- package/dist/cli/commands/linear-list.js.map +2 -2
- package/dist/cli/commands/linear-migrate.js +12 -1
- package/dist/cli/commands/linear-migrate.js.map +2 -2
- package/dist/cli/commands/linear-test.js +12 -1
- package/dist/cli/commands/linear-test.js.map +2 -2
- package/dist/cli/commands/linear-unified.js +262 -0
- package/dist/cli/commands/linear-unified.js.map +7 -0
- package/dist/cli/commands/linear.js +17 -6
- package/dist/cli/commands/linear.js.map +2 -2
- package/dist/cli/commands/monitor.js.map +2 -2
- package/dist/cli/commands/onboard.js.map +2 -2
- package/dist/cli/commands/quality.js.map +2 -2
- package/dist/cli/commands/search.js.map +2 -2
- package/dist/cli/commands/session.js.map +2 -2
- package/dist/cli/commands/skills.js +12 -1
- package/dist/cli/commands/skills.js.map +2 -2
- package/dist/cli/commands/storage.js +18 -7
- package/dist/cli/commands/storage.js.map +2 -2
- package/dist/cli/commands/tasks.js.map +2 -2
- package/dist/cli/commands/tui.js +13 -2
- package/dist/cli/commands/tui.js.map +2 -2
- package/dist/cli/commands/webhook.js +14 -3
- package/dist/cli/commands/webhook.js.map +2 -2
- package/dist/cli/commands/workflow.js +14 -3
- package/dist/cli/commands/workflow.js.map +2 -2
- package/dist/cli/commands/worktree.js.map +2 -2
- package/dist/cli/index.js +18 -5
- package/dist/cli/index.js.map +2 -2
- package/dist/core/config/config-manager.js.map +2 -2
- package/dist/core/context/auto-context.js.map +2 -2
- package/dist/core/context/compaction-handler.js.map +2 -2
- package/dist/core/context/context-bridge.js.map +2 -2
- package/dist/core/context/dual-stack-manager.js.map +2 -2
- package/dist/core/context/frame-database.js.map +2 -2
- package/dist/core/context/frame-digest.js.map +2 -2
- package/dist/core/context/frame-handoff-manager.js.map +2 -2
- package/dist/core/context/frame-manager.js +12 -1
- package/dist/core/context/frame-manager.js.map +2 -2
- package/dist/core/context/frame-stack.js.map +2 -2
- package/dist/core/context/incremental-gc.js +279 -0
- package/dist/core/context/incremental-gc.js.map +7 -0
- package/dist/core/context/permission-manager.js +12 -1
- package/dist/core/context/permission-manager.js.map +2 -2
- package/dist/core/context/refactored-frame-manager.js.map +2 -2
- package/dist/core/context/shared-context-layer.js +12 -1
- package/dist/core/context/shared-context-layer.js.map +2 -2
- package/dist/core/context/stack-merge-resolver.js.map +2 -2
- package/dist/core/context/validation.js.map +2 -2
- package/dist/core/database/batch-operations.js.map +2 -2
- package/dist/core/database/connection-pool.js.map +2 -2
- package/dist/core/database/migration-manager.js.map +2 -2
- package/dist/core/database/paradedb-adapter.js.map +2 -2
- package/dist/core/database/query-cache.js.map +2 -2
- package/dist/core/database/query-router.js.map +2 -2
- package/dist/core/database/sqlite-adapter.js.map +2 -2
- package/dist/core/digest/enhanced-hybrid-digest.js.map +2 -2
- package/dist/core/errors/recovery.js.map +2 -2
- package/dist/core/merge/resolution-engine.js.map +2 -2
- package/dist/core/monitoring/error-handler.js.map +2 -2
- package/dist/core/monitoring/logger.js +14 -3
- package/dist/core/monitoring/logger.js.map +2 -2
- package/dist/core/monitoring/metrics.js +13 -2
- package/dist/core/monitoring/metrics.js.map +2 -2
- package/dist/core/monitoring/progress-tracker.js +12 -1
- package/dist/core/monitoring/progress-tracker.js.map +2 -2
- package/dist/core/monitoring/session-monitor.js.map +2 -2
- package/dist/core/performance/context-cache.js.map +2 -2
- package/dist/core/performance/lazy-context-loader.js.map +2 -2
- package/dist/core/performance/monitor.js.map +2 -2
- package/dist/core/performance/optimized-frame-context.js.map +2 -2
- package/dist/core/performance/performance-benchmark.js.map +2 -2
- package/dist/core/performance/performance-profiler.js +12 -1
- package/dist/core/performance/performance-profiler.js.map +2 -2
- package/dist/core/performance/streaming-jsonl-parser.js.map +2 -2
- package/dist/core/persistence/postgres-adapter.js.map +2 -2
- package/dist/core/projects/project-manager.js.map +2 -2
- package/dist/core/retrieval/context-retriever.js.map +2 -2
- package/dist/core/retrieval/graph-retrieval.js.map +2 -2
- package/dist/core/retrieval/llm-context-retrieval.js.map +2 -2
- package/dist/core/retrieval/retrieval-benchmarks.js.map +2 -2
- package/dist/core/retrieval/summary-generator.js.map +2 -2
- package/dist/core/session/clear-survival.js.map +2 -2
- package/dist/core/session/handoff-generator.js.map +2 -2
- package/dist/core/session/session-manager.js +16 -5
- package/dist/core/session/session-manager.js.map +2 -2
- package/dist/core/skills/skill-storage.js +13 -2
- package/dist/core/skills/skill-storage.js.map +2 -2
- package/dist/core/storage/chromadb-adapter.js.map +2 -2
- package/dist/core/storage/chromadb-simple.js.map +2 -2
- package/dist/core/storage/infinite-storage.js.map +2 -2
- package/dist/core/storage/railway-optimized-storage.js +19 -8
- package/dist/core/storage/railway-optimized-storage.js.map +2 -2
- package/dist/core/storage/remote-storage.js +12 -1
- package/dist/core/storage/remote-storage.js.map +2 -2
- package/dist/core/trace/cli-trace-wrapper.js +16 -5
- package/dist/core/trace/cli-trace-wrapper.js.map +2 -2
- package/dist/core/trace/db-trace-wrapper.js.map +2 -2
- package/dist/core/trace/debug-trace.js +21 -10
- package/dist/core/trace/debug-trace.js.map +2 -2
- package/dist/core/trace/index.js +46 -35
- package/dist/core/trace/index.js.map +2 -2
- package/dist/core/trace/trace-demo.js +12 -1
- package/dist/core/trace/trace-demo.js.map +2 -2
- package/dist/core/trace/trace-detector.js.map +2 -2
- package/dist/core/trace/trace-store.js.map +2 -2
- package/dist/core/utils/update-checker.js.map +2 -2
- package/dist/core/worktree/worktree-manager.js.map +2 -2
- package/dist/features/analytics/api/analytics-api.js.map +2 -2
- package/dist/features/analytics/core/analytics-service.js +12 -1
- package/dist/features/analytics/core/analytics-service.js.map +2 -2
- package/dist/features/analytics/queries/metrics-queries.js.map +2 -2
- package/dist/features/tasks/pebbles-task-store.js.map +2 -2
- package/dist/features/tui/components/analytics-panel.js.map +2 -2
- package/dist/features/tui/components/pr-tracker.js.map +2 -2
- package/dist/features/tui/components/session-monitor.js.map +2 -2
- package/dist/features/tui/components/subagent-fleet.js.map +2 -2
- package/dist/features/tui/components/task-board.js +650 -2
- package/dist/features/tui/components/task-board.js.map +2 -2
- package/dist/features/tui/index.js +16 -5
- package/dist/features/tui/index.js.map +2 -2
- package/dist/features/tui/services/data-service.js +25 -14
- package/dist/features/tui/services/data-service.js.map +2 -2
- package/dist/features/tui/services/linear-task-reader.js.map +2 -2
- package/dist/features/tui/services/websocket-client.js +13 -2
- package/dist/features/tui/services/websocket-client.js.map +2 -2
- package/dist/features/tui/terminal-compat.js +27 -16
- package/dist/features/tui/terminal-compat.js.map +2 -2
- package/dist/features/web/client/stores/task-store.js.map +2 -2
- package/dist/features/web/server/index.js +13 -2
- package/dist/features/web/server/index.js.map +2 -2
- package/dist/integrations/claude-code/enhanced-pre-clear-hooks.js.map +2 -2
- package/dist/integrations/claude-code/lifecycle-hooks.js.map +2 -2
- package/dist/integrations/claude-code/post-task-hooks.js.map +2 -2
- package/dist/integrations/linear/auth.js +17 -6
- package/dist/integrations/linear/auth.js.map +2 -2
- package/dist/integrations/linear/auto-sync.js.map +2 -2
- package/dist/integrations/linear/client.js.map +2 -2
- package/dist/integrations/linear/config.js.map +2 -2
- package/dist/integrations/linear/migration.js.map +2 -2
- package/dist/integrations/linear/oauth-server.js +13 -2
- package/dist/integrations/linear/oauth-server.js.map +2 -2
- package/dist/integrations/linear/rest-client.js.map +2 -2
- package/dist/integrations/linear/sync-enhanced.js +202 -0
- package/dist/integrations/linear/sync-enhanced.js.map +7 -0
- package/dist/integrations/linear/sync-manager.js.map +2 -2
- package/dist/integrations/linear/sync-service.js +12 -1
- package/dist/integrations/linear/sync-service.js.map +2 -2
- package/dist/integrations/linear/sync.js +34 -3
- package/dist/integrations/linear/sync.js.map +2 -2
- package/dist/integrations/linear/unified-sync.js +560 -0
- package/dist/integrations/linear/unified-sync.js.map +7 -0
- package/dist/integrations/linear/webhook-handler.js +12 -1
- package/dist/integrations/linear/webhook-handler.js.map +2 -2
- package/dist/integrations/linear/webhook-server.js +14 -3
- package/dist/integrations/linear/webhook-server.js.map +2 -2
- package/dist/integrations/linear/webhook.js +12 -1
- package/dist/integrations/linear/webhook.js.map +2 -2
- package/dist/integrations/mcp/handlers/context-handlers.js.map +2 -2
- package/dist/integrations/mcp/handlers/linear-handlers.js.map +2 -2
- package/dist/integrations/mcp/handlers/skill-handlers.js +13 -2
- package/dist/integrations/mcp/handlers/skill-handlers.js.map +2 -2
- package/dist/integrations/mcp/handlers/task-handlers.js.map +2 -2
- package/dist/integrations/mcp/handlers/trace-handlers.js.map +2 -2
- package/dist/integrations/mcp/middleware/tool-scoring.js.map +2 -2
- package/dist/integrations/mcp/refactored-server.js +15 -4
- package/dist/integrations/mcp/refactored-server.js.map +2 -2
- package/dist/integrations/mcp/server.js +12 -1
- package/dist/integrations/mcp/server.js.map +2 -2
- package/dist/integrations/mcp/tool-definitions.js.map +2 -2
- package/dist/integrations/pg-aiguide/embedding-provider.js +13 -2
- package/dist/integrations/pg-aiguide/embedding-provider.js.map +2 -2
- package/dist/integrations/pg-aiguide/semantic-search.js.map +2 -2
- package/dist/mcp/stackmemory-mcp-server.js +12 -1
- package/dist/mcp/stackmemory-mcp-server.js.map +2 -2
- package/dist/middleware/exponential-rate-limiter.js.map +2 -2
- package/dist/servers/production/auth-middleware.js +13 -2
- package/dist/servers/production/auth-middleware.js.map +2 -2
- package/dist/servers/railway/index.js +22 -11
- package/dist/servers/railway/index.js.map +2 -2
- package/dist/services/config-service.js.map +2 -2
- package/dist/services/context-service.js.map +2 -2
- package/dist/skills/claude-skills.js +105 -2
- package/dist/skills/claude-skills.js.map +2 -2
- package/dist/skills/dashboard-launcher.js.map +2 -2
- package/dist/skills/repo-ingestion-skill.js +561 -0
- package/dist/skills/repo-ingestion-skill.js.map +7 -0
- package/dist/utils/logger.js +12 -1
- package/dist/utils/logger.js.map +2 -2
- package/package.json +5 -1
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
import { LinearClient } from "./client.js";
|
|
2
|
+
import { LinearDuplicateDetector } from "./sync-enhanced.js";
|
|
3
|
+
import { logger } from "../../core/monitoring/logger.js";
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
5
|
+
import { join, dirname } from "path";
|
|
6
|
+
import { EventEmitter } from "events";
|
|
7
|
+
const DEFAULT_UNIFIED_CONFIG = {
|
|
8
|
+
enabled: true,
|
|
9
|
+
direction: "bidirectional",
|
|
10
|
+
duplicateDetection: true,
|
|
11
|
+
duplicateSimilarityThreshold: 0.85,
|
|
12
|
+
mergeStrategy: "merge_content",
|
|
13
|
+
conflictResolution: "newest_wins",
|
|
14
|
+
taskPlanningEnabled: true,
|
|
15
|
+
taskPlanFile: ".stackmemory/task-plan.md",
|
|
16
|
+
autoCreateTaskPlan: true,
|
|
17
|
+
maxBatchSize: 50,
|
|
18
|
+
rateLimitDelay: 100,
|
|
19
|
+
maxRetries: 3,
|
|
20
|
+
autoSync: false,
|
|
21
|
+
autoSyncInterval: 15
|
|
22
|
+
};
|
|
23
|
+
class UnifiedLinearSync extends EventEmitter {
|
|
24
|
+
config;
|
|
25
|
+
linearClient;
|
|
26
|
+
taskStore;
|
|
27
|
+
authManager;
|
|
28
|
+
duplicateDetector;
|
|
29
|
+
projectRoot;
|
|
30
|
+
mappings = /* @__PURE__ */ new Map();
|
|
31
|
+
// task.id -> linear.id
|
|
32
|
+
lastSyncStats;
|
|
33
|
+
syncInProgress = false;
|
|
34
|
+
constructor(taskStore, authManager, projectRoot, config) {
|
|
35
|
+
super();
|
|
36
|
+
this.taskStore = taskStore;
|
|
37
|
+
this.authManager = authManager;
|
|
38
|
+
this.projectRoot = projectRoot;
|
|
39
|
+
this.config = { ...DEFAULT_UNIFIED_CONFIG, ...config };
|
|
40
|
+
this.linearClient = null;
|
|
41
|
+
this.duplicateDetector = null;
|
|
42
|
+
this.loadMappings();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Initialize the sync system
|
|
46
|
+
*/
|
|
47
|
+
async initialize() {
|
|
48
|
+
try {
|
|
49
|
+
const token = await this.authManager.getValidToken();
|
|
50
|
+
if (!token) {
|
|
51
|
+
throw new Error('Linear authentication required. Run "stackmemory linear auth" first.');
|
|
52
|
+
}
|
|
53
|
+
const isOAuth = this.authManager.isOAuth();
|
|
54
|
+
this.linearClient = new LinearClient({
|
|
55
|
+
apiKey: token,
|
|
56
|
+
useBearer: isOAuth,
|
|
57
|
+
teamId: this.config.defaultTeamId,
|
|
58
|
+
onUnauthorized: isOAuth ? async () => {
|
|
59
|
+
const refreshed = await this.authManager.refreshAccessToken();
|
|
60
|
+
return refreshed.accessToken;
|
|
61
|
+
} : void 0
|
|
62
|
+
});
|
|
63
|
+
if (this.config.duplicateDetection) {
|
|
64
|
+
this.duplicateDetector = new LinearDuplicateDetector(this.linearClient);
|
|
65
|
+
}
|
|
66
|
+
if (this.config.taskPlanningEnabled) {
|
|
67
|
+
await this.initializeTaskPlanning();
|
|
68
|
+
}
|
|
69
|
+
logger.info("Unified Linear sync initialized", {
|
|
70
|
+
direction: this.config.direction,
|
|
71
|
+
duplicateDetection: this.config.duplicateDetection,
|
|
72
|
+
taskPlanning: this.config.taskPlanningEnabled
|
|
73
|
+
});
|
|
74
|
+
} catch (error) {
|
|
75
|
+
logger.error("Failed to initialize Linear sync:", error);
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Main sync method - orchestrates bidirectional sync
|
|
81
|
+
*/
|
|
82
|
+
async sync() {
|
|
83
|
+
if (this.syncInProgress) {
|
|
84
|
+
throw new Error("Sync already in progress");
|
|
85
|
+
}
|
|
86
|
+
this.syncInProgress = true;
|
|
87
|
+
const startTime = Date.now();
|
|
88
|
+
const stats = {
|
|
89
|
+
toLinear: { created: 0, updated: 0, skipped: 0, duplicatesMerged: 0 },
|
|
90
|
+
fromLinear: { created: 0, updated: 0, skipped: 0 },
|
|
91
|
+
conflicts: [],
|
|
92
|
+
errors: [],
|
|
93
|
+
duration: 0,
|
|
94
|
+
timestamp: Date.now()
|
|
95
|
+
};
|
|
96
|
+
try {
|
|
97
|
+
this.emit("sync:started", { config: this.config });
|
|
98
|
+
switch (this.config.direction) {
|
|
99
|
+
case "bidirectional":
|
|
100
|
+
await this.syncFromLinear(stats);
|
|
101
|
+
await this.syncToLinear(stats);
|
|
102
|
+
break;
|
|
103
|
+
case "from_linear":
|
|
104
|
+
await this.syncFromLinear(stats);
|
|
105
|
+
break;
|
|
106
|
+
case "to_linear":
|
|
107
|
+
await this.syncToLinear(stats);
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
if (this.config.taskPlanningEnabled) {
|
|
111
|
+
await this.updateTaskPlan(stats);
|
|
112
|
+
}
|
|
113
|
+
this.saveMappings();
|
|
114
|
+
stats.duration = Date.now() - startTime;
|
|
115
|
+
this.lastSyncStats = stats;
|
|
116
|
+
this.emit("sync:completed", { stats });
|
|
117
|
+
logger.info("Unified sync completed", {
|
|
118
|
+
duration: `${stats.duration}ms`,
|
|
119
|
+
toLinear: stats.toLinear,
|
|
120
|
+
fromLinear: stats.fromLinear,
|
|
121
|
+
conflicts: stats.conflicts.length
|
|
122
|
+
});
|
|
123
|
+
return stats;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
stats.errors.push(error.message);
|
|
126
|
+
stats.duration = Date.now() - startTime;
|
|
127
|
+
this.emit("sync:failed", { stats, error });
|
|
128
|
+
logger.error("Unified sync failed:", error);
|
|
129
|
+
throw error;
|
|
130
|
+
} finally {
|
|
131
|
+
this.syncInProgress = false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Sync from Linear to local tasks
|
|
136
|
+
*/
|
|
137
|
+
async syncFromLinear(stats) {
|
|
138
|
+
try {
|
|
139
|
+
logger.debug("Syncing from Linear...");
|
|
140
|
+
const teamId = this.config.defaultTeamId || await this.getDefaultTeamId();
|
|
141
|
+
const issues = await this.linearClient.getIssues({
|
|
142
|
+
teamId,
|
|
143
|
+
limit: this.config.maxBatchSize
|
|
144
|
+
});
|
|
145
|
+
for (const issue of issues) {
|
|
146
|
+
try {
|
|
147
|
+
await this.delay(this.config.rateLimitDelay);
|
|
148
|
+
const localTaskId = this.findLocalTaskByLinearId(issue.id);
|
|
149
|
+
if (localTaskId) {
|
|
150
|
+
const localTask = await this.taskStore.getTask(localTaskId);
|
|
151
|
+
if (localTask && this.hasChanges(localTask, issue)) {
|
|
152
|
+
await this.updateLocalTask(localTask, issue);
|
|
153
|
+
stats.fromLinear.updated++;
|
|
154
|
+
} else {
|
|
155
|
+
stats.fromLinear.skipped++;
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
await this.createLocalTask(issue);
|
|
159
|
+
stats.fromLinear.created++;
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
stats.errors.push(`Failed to sync issue ${issue.identifier}: ${error.message}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
logger.error("Failed to sync from Linear:", error);
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Sync local tasks to Linear
|
|
172
|
+
*/
|
|
173
|
+
async syncToLinear(stats) {
|
|
174
|
+
try {
|
|
175
|
+
logger.debug("Syncing to Linear...");
|
|
176
|
+
const tasks = await this.taskStore.getAllTasks();
|
|
177
|
+
const teamId = this.config.defaultTeamId || await this.getDefaultTeamId();
|
|
178
|
+
for (const task of tasks) {
|
|
179
|
+
try {
|
|
180
|
+
await this.delay(this.config.rateLimitDelay);
|
|
181
|
+
const linearId = this.mappings.get(task.id);
|
|
182
|
+
if (linearId) {
|
|
183
|
+
const linearIssue = await this.linearClient.getIssue(linearId);
|
|
184
|
+
if (linearIssue && this.taskNeedsUpdate(task, linearIssue)) {
|
|
185
|
+
await this.updateLinearIssue(linearIssue, task);
|
|
186
|
+
stats.toLinear.updated++;
|
|
187
|
+
} else {
|
|
188
|
+
stats.toLinear.skipped++;
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
if (this.config.duplicateDetection) {
|
|
192
|
+
const duplicateCheck = await this.duplicateDetector.checkForDuplicate(
|
|
193
|
+
task.title,
|
|
194
|
+
teamId
|
|
195
|
+
);
|
|
196
|
+
if (duplicateCheck.isDuplicate && duplicateCheck.existingIssue) {
|
|
197
|
+
if (this.config.mergeStrategy === "merge_content") {
|
|
198
|
+
await this.mergeTaskIntoLinear(task, duplicateCheck.existingIssue);
|
|
199
|
+
this.mappings.set(task.id, duplicateCheck.existingIssue.id);
|
|
200
|
+
stats.toLinear.duplicatesMerged++;
|
|
201
|
+
} else if (this.config.mergeStrategy === "skip") {
|
|
202
|
+
stats.toLinear.skipped++;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
await this.createLinearIssue(task, teamId);
|
|
207
|
+
stats.toLinear.created++;
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
await this.createLinearIssue(task, teamId);
|
|
211
|
+
stats.toLinear.created++;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
} catch (error) {
|
|
215
|
+
stats.errors.push(`Failed to sync task ${task.id}: ${error.message}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
logger.error("Failed to sync to Linear:", error);
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Initialize task planning system
|
|
225
|
+
*/
|
|
226
|
+
async initializeTaskPlanning() {
|
|
227
|
+
const planFile = join(this.projectRoot, this.config.taskPlanFile);
|
|
228
|
+
const planDir = dirname(planFile);
|
|
229
|
+
if (!existsSync(planDir)) {
|
|
230
|
+
mkdirSync(planDir, { recursive: true });
|
|
231
|
+
}
|
|
232
|
+
if (!existsSync(planFile) && this.config.autoCreateTaskPlan) {
|
|
233
|
+
const defaultPlan = {
|
|
234
|
+
version: "1.0.0",
|
|
235
|
+
lastUpdated: /* @__PURE__ */ new Date(),
|
|
236
|
+
phases: [
|
|
237
|
+
{
|
|
238
|
+
name: "Backlog",
|
|
239
|
+
description: "Tasks to be prioritized",
|
|
240
|
+
tasks: []
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
name: "Current Sprint",
|
|
244
|
+
description: "Active work items",
|
|
245
|
+
tasks: []
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: "Completed",
|
|
249
|
+
description: "Finished tasks",
|
|
250
|
+
tasks: []
|
|
251
|
+
}
|
|
252
|
+
]
|
|
253
|
+
};
|
|
254
|
+
this.saveTaskPlan(defaultPlan);
|
|
255
|
+
logger.info("Created default task plan", { path: planFile });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Update task plan with sync results
|
|
260
|
+
*/
|
|
261
|
+
async updateTaskPlan(stats) {
|
|
262
|
+
if (!this.config.taskPlanningEnabled) return;
|
|
263
|
+
try {
|
|
264
|
+
const plan = this.loadTaskPlan();
|
|
265
|
+
const tasks = await this.taskStore.getAllTasks();
|
|
266
|
+
plan.phases = [
|
|
267
|
+
{
|
|
268
|
+
name: "Backlog",
|
|
269
|
+
description: "Tasks to be prioritized",
|
|
270
|
+
tasks: tasks.filter((t) => t.status === "todo").map((t) => ({
|
|
271
|
+
id: t.id,
|
|
272
|
+
title: t.title,
|
|
273
|
+
priority: t.priority || "medium",
|
|
274
|
+
status: t.status,
|
|
275
|
+
linearId: this.mappings.get(t.id)
|
|
276
|
+
}))
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: "In Progress",
|
|
280
|
+
description: "Active work items",
|
|
281
|
+
tasks: tasks.filter((t) => t.status === "in_progress").map((t) => ({
|
|
282
|
+
id: t.id,
|
|
283
|
+
title: t.title,
|
|
284
|
+
priority: t.priority || "medium",
|
|
285
|
+
status: t.status,
|
|
286
|
+
linearId: this.mappings.get(t.id)
|
|
287
|
+
}))
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
name: "Completed",
|
|
291
|
+
description: "Finished tasks",
|
|
292
|
+
tasks: tasks.filter((t) => t.status === "done").slice(-20).map((t) => ({
|
|
293
|
+
id: t.id,
|
|
294
|
+
title: t.title,
|
|
295
|
+
priority: t.priority || "medium",
|
|
296
|
+
status: t.status,
|
|
297
|
+
linearId: this.mappings.get(t.id)
|
|
298
|
+
}))
|
|
299
|
+
}
|
|
300
|
+
];
|
|
301
|
+
plan.lastUpdated = /* @__PURE__ */ new Date();
|
|
302
|
+
this.saveTaskPlan(plan);
|
|
303
|
+
this.generateTaskReport(plan, stats);
|
|
304
|
+
} catch (error) {
|
|
305
|
+
logger.error("Failed to update task plan:", error);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Generate markdown task report
|
|
310
|
+
*/
|
|
311
|
+
generateTaskReport(plan, stats) {
|
|
312
|
+
const reportFile = join(this.projectRoot, ".stackmemory", "task-report.md");
|
|
313
|
+
let content = `# Task Sync Report
|
|
314
|
+
|
|
315
|
+
`;
|
|
316
|
+
content += `**Last Updated:** ${plan.lastUpdated.toLocaleString()}
|
|
317
|
+
`;
|
|
318
|
+
content += `**Sync Duration:** ${stats.duration}ms
|
|
319
|
+
|
|
320
|
+
`;
|
|
321
|
+
content += `## Sync Statistics
|
|
322
|
+
|
|
323
|
+
`;
|
|
324
|
+
content += `### To Linear
|
|
325
|
+
`;
|
|
326
|
+
content += `- Created: ${stats.toLinear.created}
|
|
327
|
+
`;
|
|
328
|
+
content += `- Updated: ${stats.toLinear.updated}
|
|
329
|
+
`;
|
|
330
|
+
content += `- Duplicates Merged: ${stats.toLinear.duplicatesMerged}
|
|
331
|
+
`;
|
|
332
|
+
content += `- Skipped: ${stats.toLinear.skipped}
|
|
333
|
+
|
|
334
|
+
`;
|
|
335
|
+
content += `### From Linear
|
|
336
|
+
`;
|
|
337
|
+
content += `- Created: ${stats.fromLinear.created}
|
|
338
|
+
`;
|
|
339
|
+
content += `- Updated: ${stats.fromLinear.updated}
|
|
340
|
+
`;
|
|
341
|
+
content += `- Skipped: ${stats.fromLinear.skipped}
|
|
342
|
+
|
|
343
|
+
`;
|
|
344
|
+
if (stats.conflicts.length > 0) {
|
|
345
|
+
content += `### Conflicts
|
|
346
|
+
`;
|
|
347
|
+
stats.conflicts.forEach((c) => {
|
|
348
|
+
content += `- **${c.taskId}**: ${c.reason} (${c.resolution})
|
|
349
|
+
`;
|
|
350
|
+
});
|
|
351
|
+
content += "\n";
|
|
352
|
+
}
|
|
353
|
+
content += `## Task Overview
|
|
354
|
+
|
|
355
|
+
`;
|
|
356
|
+
plan.phases.forEach((phase) => {
|
|
357
|
+
content += `### ${phase.name} (${phase.tasks.length})
|
|
358
|
+
`;
|
|
359
|
+
content += `> ${phase.description}
|
|
360
|
+
|
|
361
|
+
`;
|
|
362
|
+
if (phase.tasks.length > 0) {
|
|
363
|
+
phase.tasks.slice(0, 10).forEach((task) => {
|
|
364
|
+
const linearLink = task.linearId ? ` [Linear]` : "";
|
|
365
|
+
content += `- **${task.title}**${linearLink}
|
|
366
|
+
`;
|
|
367
|
+
});
|
|
368
|
+
if (phase.tasks.length > 10) {
|
|
369
|
+
content += `- _...and ${phase.tasks.length - 10} more_
|
|
370
|
+
`;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
content += "\n";
|
|
374
|
+
});
|
|
375
|
+
writeFileSync(reportFile, content);
|
|
376
|
+
logger.debug("Task report generated", { path: reportFile });
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Helper methods
|
|
380
|
+
*/
|
|
381
|
+
async getDefaultTeamId() {
|
|
382
|
+
const teams = await this.linearClient.getTeams();
|
|
383
|
+
if (teams.length === 0) {
|
|
384
|
+
throw new Error("No Linear teams found");
|
|
385
|
+
}
|
|
386
|
+
return teams[0].id;
|
|
387
|
+
}
|
|
388
|
+
findLocalTaskByLinearId(linearId) {
|
|
389
|
+
for (const [taskId, linId] of this.mappings) {
|
|
390
|
+
if (linId === linearId) return taskId;
|
|
391
|
+
}
|
|
392
|
+
return void 0;
|
|
393
|
+
}
|
|
394
|
+
hasChanges(localTask, linearIssue) {
|
|
395
|
+
return localTask.title !== linearIssue.title || localTask.description !== (linearIssue.description || "") || this.mapLinearStateToStatus(linearIssue.state.type) !== localTask.status;
|
|
396
|
+
}
|
|
397
|
+
taskNeedsUpdate(task, linearIssue) {
|
|
398
|
+
return task.title !== linearIssue.title || task.description !== (linearIssue.description || "") || task.status !== this.mapLinearStateToStatus(linearIssue.state.type);
|
|
399
|
+
}
|
|
400
|
+
async createLocalTask(issue) {
|
|
401
|
+
const task = await this.taskStore.createTask({
|
|
402
|
+
title: issue.title,
|
|
403
|
+
description: issue.description || "",
|
|
404
|
+
status: this.mapLinearStateToStatus(issue.state.type),
|
|
405
|
+
priority: this.mapLinearPriorityToPriority(issue.priority),
|
|
406
|
+
metadata: {
|
|
407
|
+
linear: {
|
|
408
|
+
id: issue.id,
|
|
409
|
+
identifier: issue.identifier,
|
|
410
|
+
url: issue.url
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
this.mappings.set(task.id, issue.id);
|
|
415
|
+
}
|
|
416
|
+
async updateLocalTask(task, issue) {
|
|
417
|
+
await this.taskStore.updateTask(task.id, {
|
|
418
|
+
title: issue.title,
|
|
419
|
+
description: issue.description || "",
|
|
420
|
+
status: this.mapLinearStateToStatus(issue.state.type),
|
|
421
|
+
priority: this.mapLinearPriorityToPriority(issue.priority)
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
async createLinearIssue(task, teamId) {
|
|
425
|
+
const input = {
|
|
426
|
+
title: task.title,
|
|
427
|
+
description: task.description || "",
|
|
428
|
+
teamId,
|
|
429
|
+
priority: this.mapPriorityToLinearPriority(task.priority)
|
|
430
|
+
};
|
|
431
|
+
const issue = await this.linearClient.createIssue(input);
|
|
432
|
+
this.mappings.set(task.id, issue.id);
|
|
433
|
+
await this.taskStore.updateTask(task.id, {
|
|
434
|
+
metadata: {
|
|
435
|
+
...task.metadata,
|
|
436
|
+
linear: {
|
|
437
|
+
id: issue.id,
|
|
438
|
+
identifier: issue.identifier,
|
|
439
|
+
url: issue.url
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
async updateLinearIssue(issue, task) {
|
|
445
|
+
await this.linearClient.updateIssue(issue.id, {
|
|
446
|
+
title: task.title,
|
|
447
|
+
description: task.description,
|
|
448
|
+
priority: this.mapPriorityToLinearPriority(task.priority)
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
async mergeTaskIntoLinear(task, existingIssue) {
|
|
452
|
+
await this.duplicateDetector.mergeIntoExisting(
|
|
453
|
+
existingIssue,
|
|
454
|
+
task.title,
|
|
455
|
+
task.description,
|
|
456
|
+
`StackMemory Task: ${task.id}
|
|
457
|
+
Merged: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
mapLinearStateToStatus(state) {
|
|
461
|
+
switch (state.toLowerCase()) {
|
|
462
|
+
case "backlog":
|
|
463
|
+
case "unstarted":
|
|
464
|
+
return "todo";
|
|
465
|
+
case "started":
|
|
466
|
+
return "in_progress";
|
|
467
|
+
case "completed":
|
|
468
|
+
return "done";
|
|
469
|
+
case "cancelled":
|
|
470
|
+
return "cancelled";
|
|
471
|
+
default:
|
|
472
|
+
return "todo";
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
mapLinearPriorityToPriority(priority) {
|
|
476
|
+
switch (priority) {
|
|
477
|
+
case 1:
|
|
478
|
+
return "urgent";
|
|
479
|
+
case 2:
|
|
480
|
+
return "high";
|
|
481
|
+
case 3:
|
|
482
|
+
return "medium";
|
|
483
|
+
case 4:
|
|
484
|
+
return "low";
|
|
485
|
+
default:
|
|
486
|
+
return void 0;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
mapPriorityToLinearPriority(priority) {
|
|
490
|
+
switch (priority) {
|
|
491
|
+
case "urgent":
|
|
492
|
+
return 1;
|
|
493
|
+
case "high":
|
|
494
|
+
return 2;
|
|
495
|
+
case "medium":
|
|
496
|
+
return 3;
|
|
497
|
+
case "low":
|
|
498
|
+
return 4;
|
|
499
|
+
default:
|
|
500
|
+
return 0;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
loadMappings() {
|
|
504
|
+
const mappingFile = join(this.projectRoot, ".stackmemory", "linear-mappings.json");
|
|
505
|
+
if (existsSync(mappingFile)) {
|
|
506
|
+
try {
|
|
507
|
+
const data = JSON.parse(readFileSync(mappingFile, "utf8"));
|
|
508
|
+
this.mappings = new Map(Object.entries(data));
|
|
509
|
+
} catch (error) {
|
|
510
|
+
logger.error("Failed to load mappings:", error);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
saveMappings() {
|
|
515
|
+
const mappingFile = join(this.projectRoot, ".stackmemory", "linear-mappings.json");
|
|
516
|
+
const data = Object.fromEntries(this.mappings);
|
|
517
|
+
writeFileSync(mappingFile, JSON.stringify(data, null, 2));
|
|
518
|
+
}
|
|
519
|
+
loadTaskPlan() {
|
|
520
|
+
const planFile = join(this.projectRoot, this.config.taskPlanFile);
|
|
521
|
+
if (existsSync(planFile)) {
|
|
522
|
+
try {
|
|
523
|
+
return JSON.parse(readFileSync(planFile, "utf8"));
|
|
524
|
+
} catch (error) {
|
|
525
|
+
logger.error("Failed to load task plan:", error);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return {
|
|
529
|
+
version: "1.0.0",
|
|
530
|
+
lastUpdated: /* @__PURE__ */ new Date(),
|
|
531
|
+
phases: []
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
saveTaskPlan(plan) {
|
|
535
|
+
const planFile = join(this.projectRoot, this.config.taskPlanFile);
|
|
536
|
+
writeFileSync(planFile, JSON.stringify(plan, null, 2));
|
|
537
|
+
}
|
|
538
|
+
delay(ms) {
|
|
539
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Get last sync statistics
|
|
543
|
+
*/
|
|
544
|
+
getLastSyncStats() {
|
|
545
|
+
return this.lastSyncStats;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Clear duplicate detector cache
|
|
549
|
+
*/
|
|
550
|
+
clearCache() {
|
|
551
|
+
if (this.duplicateDetector) {
|
|
552
|
+
this.duplicateDetector.clearCache();
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
export {
|
|
557
|
+
DEFAULT_UNIFIED_CONFIG,
|
|
558
|
+
UnifiedLinearSync
|
|
559
|
+
};
|
|
560
|
+
//# sourceMappingURL=unified-sync.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/integrations/linear/unified-sync.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Unified Linear Sync System\n * Consolidates all sync functionality with duplicate detection,\n * bidirectional sync, and task planning integration\n */\n\nimport { LinearClient, LinearIssue, LinearCreateIssueInput } from './client.js';\nimport { LinearDuplicateDetector, DuplicateCheckResult } from './sync-enhanced.js';\nimport { PebblesTaskStore } from '../../features/tasks/pebbles-task-store.js';\nimport { LinearAuthManager } from './auth.js';\nimport { logger } from '../../core/monitoring/logger.js';\nimport { Task, TaskStatus, TaskPriority } from '../../types/task.js';\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { EventEmitter } from 'events';\n\n// Unified sync configuration\nexport interface UnifiedSyncConfig {\n // Core settings\n enabled: boolean;\n direction: 'bidirectional' | 'to_linear' | 'from_linear';\n defaultTeamId?: string;\n \n // Duplicate detection\n duplicateDetection: boolean;\n duplicateSimilarityThreshold: number; // 0-1, default 0.85\n mergeStrategy: 'merge_content' | 'skip' | 'create_anyway';\n \n // Conflict resolution\n conflictResolution: 'newest_wins' | 'linear_wins' | 'local_wins' | 'manual';\n \n // Task planning\n taskPlanningEnabled: boolean;\n taskPlanFile?: string; // Default: .stackmemory/task-plan.md\n autoCreateTaskPlan: boolean;\n \n // Performance\n maxBatchSize: number;\n rateLimitDelay: number; // ms between requests\n maxRetries: number;\n \n // Auto-sync\n autoSync: boolean;\n autoSyncInterval?: number; // minutes\n quietHours?: {\n start: number; // hour 0-23\n end: number;\n };\n}\n\nexport const DEFAULT_UNIFIED_CONFIG: UnifiedSyncConfig = {\n enabled: true,\n direction: 'bidirectional',\n duplicateDetection: true,\n duplicateSimilarityThreshold: 0.85,\n mergeStrategy: 'merge_content',\n conflictResolution: 'newest_wins',\n taskPlanningEnabled: true,\n taskPlanFile: '.stackmemory/task-plan.md',\n autoCreateTaskPlan: true,\n maxBatchSize: 50,\n rateLimitDelay: 100,\n maxRetries: 3,\n autoSync: false,\n autoSyncInterval: 15,\n};\n\n// Sync statistics\nexport interface SyncStats {\n toLinear: {\n created: number;\n updated: number;\n skipped: number;\n duplicatesMerged: number;\n };\n fromLinear: {\n created: number;\n updated: number;\n skipped: number;\n };\n conflicts: Array<{\n taskId: string;\n reason: string;\n resolution: string;\n }>;\n errors: string[];\n duration: number;\n timestamp: number;\n}\n\n// Task planning integration\ninterface TaskPlan {\n version: string;\n lastUpdated: Date;\n phases: Array<{\n name: string;\n description: string;\n tasks: Array<{\n id: string;\n title: string;\n priority: TaskPriority;\n status: TaskStatus;\n linearId?: string;\n dependencies?: string[];\n }>;\n }>;\n}\n\nexport class UnifiedLinearSync extends EventEmitter {\n private config: UnifiedSyncConfig;\n private linearClient: LinearClient;\n private taskStore: PebblesTaskStore;\n private authManager: LinearAuthManager;\n private duplicateDetector: LinearDuplicateDetector;\n private projectRoot: string;\n private mappings: Map<string, string> = new Map(); // task.id -> linear.id\n private lastSyncStats?: SyncStats;\n private syncInProgress = false;\n\n constructor(\n taskStore: PebblesTaskStore,\n authManager: LinearAuthManager,\n projectRoot: string,\n config?: Partial<UnifiedSyncConfig>\n ) {\n super();\n this.taskStore = taskStore;\n this.authManager = authManager;\n this.projectRoot = projectRoot;\n this.config = { ...DEFAULT_UNIFIED_CONFIG, ...config };\n \n // Initialize Linear client - will be set up in initialize()\n this.linearClient = null as any;\n this.duplicateDetector = null as any;\n \n // Load existing mappings\n this.loadMappings();\n }\n\n /**\n * Initialize the sync system\n */\n async initialize(): Promise<void> {\n try {\n // Get Linear authentication\n const token = await this.authManager.getValidToken();\n if (!token) {\n throw new Error('Linear authentication required. Run \"stackmemory linear auth\" first.');\n }\n\n // Initialize Linear client with proper auth\n const isOAuth = this.authManager.isOAuth();\n this.linearClient = new LinearClient({\n apiKey: token,\n useBearer: isOAuth,\n teamId: this.config.defaultTeamId,\n onUnauthorized: isOAuth\n ? async () => {\n const refreshed = await this.authManager.refreshAccessToken();\n return refreshed.accessToken;\n }\n : undefined,\n });\n\n // Initialize duplicate detector\n if (this.config.duplicateDetection) {\n this.duplicateDetector = new LinearDuplicateDetector(this.linearClient);\n }\n\n // Initialize task planning if enabled\n if (this.config.taskPlanningEnabled) {\n await this.initializeTaskPlanning();\n }\n\n logger.info('Unified Linear sync initialized', {\n direction: this.config.direction,\n duplicateDetection: this.config.duplicateDetection,\n taskPlanning: this.config.taskPlanningEnabled,\n });\n } catch (error: unknown) {\n logger.error('Failed to initialize Linear sync:', error as Error);\n throw error;\n }\n }\n\n /**\n * Main sync method - orchestrates bidirectional sync\n */\n async sync(): Promise<SyncStats> {\n if (this.syncInProgress) {\n throw new Error('Sync already in progress');\n }\n\n this.syncInProgress = true;\n const startTime = Date.now();\n \n const stats: SyncStats = {\n toLinear: { created: 0, updated: 0, skipped: 0, duplicatesMerged: 0 },\n fromLinear: { created: 0, updated: 0, skipped: 0 },\n conflicts: [],\n errors: [],\n duration: 0,\n timestamp: Date.now(),\n };\n\n try {\n this.emit('sync:started', { config: this.config });\n\n // Determine sync direction and execute\n switch (this.config.direction) {\n case 'bidirectional':\n await this.syncFromLinear(stats);\n await this.syncToLinear(stats);\n break;\n case 'from_linear':\n await this.syncFromLinear(stats);\n break;\n case 'to_linear':\n await this.syncToLinear(stats);\n break;\n }\n\n // Update task plan if enabled\n if (this.config.taskPlanningEnabled) {\n await this.updateTaskPlan(stats);\n }\n\n // Save mappings\n this.saveMappings();\n\n stats.duration = Date.now() - startTime;\n this.lastSyncStats = stats;\n\n this.emit('sync:completed', { stats });\n logger.info('Unified sync completed', {\n duration: `${stats.duration}ms`,\n toLinear: stats.toLinear,\n fromLinear: stats.fromLinear,\n conflicts: stats.conflicts.length,\n });\n\n return stats;\n } catch (error: unknown) {\n stats.errors.push((error as Error).message);\n stats.duration = Date.now() - startTime;\n \n this.emit('sync:failed', { stats, error });\n logger.error('Unified sync failed:', error as Error);\n \n throw error;\n } finally {\n this.syncInProgress = false;\n }\n }\n\n /**\n * Sync from Linear to local tasks\n */\n private async syncFromLinear(stats: SyncStats): Promise<void> {\n try {\n logger.debug('Syncing from Linear...');\n\n // Get team ID\n const teamId = this.config.defaultTeamId || (await this.getDefaultTeamId());\n \n // Fetch Linear issues\n const issues = await this.linearClient.getIssues({\n teamId,\n limit: this.config.maxBatchSize,\n });\n\n for (const issue of issues) {\n try {\n await this.delay(this.config.rateLimitDelay);\n \n // Check if we have this issue mapped\n const localTaskId = this.findLocalTaskByLinearId(issue.id);\n \n if (localTaskId) {\n // Update existing task\n const localTask = await this.taskStore.getTask(localTaskId);\n if (localTask && this.hasChanges(localTask, issue)) {\n await this.updateLocalTask(localTask, issue);\n stats.fromLinear.updated++;\n } else {\n stats.fromLinear.skipped++;\n }\n } else {\n // Create new local task\n await this.createLocalTask(issue);\n stats.fromLinear.created++;\n }\n } catch (error: unknown) {\n stats.errors.push(`Failed to sync issue ${issue.identifier}: ${(error as Error).message}`);\n }\n }\n } catch (error: unknown) {\n logger.error('Failed to sync from Linear:', error as Error);\n throw error;\n }\n }\n\n /**\n * Sync local tasks to Linear\n */\n private async syncToLinear(stats: SyncStats): Promise<void> {\n try {\n logger.debug('Syncing to Linear...');\n\n // Get all local tasks\n const tasks = await this.taskStore.getAllTasks();\n const teamId = this.config.defaultTeamId || (await this.getDefaultTeamId());\n\n for (const task of tasks) {\n try {\n await this.delay(this.config.rateLimitDelay);\n \n // Skip if already mapped to Linear\n const linearId = this.mappings.get(task.id);\n \n if (linearId) {\n // Update existing Linear issue\n const linearIssue = await this.linearClient.getIssue(linearId);\n if (linearIssue && this.taskNeedsUpdate(task, linearIssue)) {\n await this.updateLinearIssue(linearIssue, task);\n stats.toLinear.updated++;\n } else {\n stats.toLinear.skipped++;\n }\n } else {\n // Check for duplicates before creating\n if (this.config.duplicateDetection) {\n const duplicateCheck = await this.duplicateDetector.checkForDuplicate(\n task.title,\n teamId\n );\n \n if (duplicateCheck.isDuplicate && duplicateCheck.existingIssue) {\n if (this.config.mergeStrategy === 'merge_content') {\n // Merge into existing\n await this.mergeTaskIntoLinear(task, duplicateCheck.existingIssue);\n this.mappings.set(task.id, duplicateCheck.existingIssue.id);\n stats.toLinear.duplicatesMerged++;\n } else if (this.config.mergeStrategy === 'skip') {\n stats.toLinear.skipped++;\n continue;\n }\n } else {\n // Create new Linear issue\n await this.createLinearIssue(task, teamId);\n stats.toLinear.created++;\n }\n } else {\n // Create without duplicate check\n await this.createLinearIssue(task, teamId);\n stats.toLinear.created++;\n }\n }\n } catch (error: unknown) {\n stats.errors.push(`Failed to sync task ${task.id}: ${(error as Error).message}`);\n }\n }\n } catch (error: unknown) {\n logger.error('Failed to sync to Linear:', error as Error);\n throw error;\n }\n }\n\n /**\n * Initialize task planning system\n */\n private async initializeTaskPlanning(): Promise<void> {\n const planFile = join(this.projectRoot, this.config.taskPlanFile!);\n const planDir = dirname(planFile);\n\n // Ensure directory exists\n if (!existsSync(planDir)) {\n mkdirSync(planDir, { recursive: true });\n }\n\n // Create default task plan if it doesn't exist\n if (!existsSync(planFile) && this.config.autoCreateTaskPlan) {\n const defaultPlan: TaskPlan = {\n version: '1.0.0',\n lastUpdated: new Date(),\n phases: [\n {\n name: 'Backlog',\n description: 'Tasks to be prioritized',\n tasks: [],\n },\n {\n name: 'Current Sprint',\n description: 'Active work items',\n tasks: [],\n },\n {\n name: 'Completed',\n description: 'Finished tasks',\n tasks: [],\n },\n ],\n };\n\n this.saveTaskPlan(defaultPlan);\n logger.info('Created default task plan', { path: planFile });\n }\n }\n\n /**\n * Update task plan with sync results\n */\n private async updateTaskPlan(stats: SyncStats): Promise<void> {\n if (!this.config.taskPlanningEnabled) return;\n\n try {\n const plan = this.loadTaskPlan();\n const tasks = await this.taskStore.getAllTasks();\n\n // Reorganize tasks by status\n plan.phases = [\n {\n name: 'Backlog',\n description: 'Tasks to be prioritized',\n tasks: tasks\n .filter((t) => t.status === 'todo')\n .map((t) => ({\n id: t.id,\n title: t.title,\n priority: t.priority || 'medium',\n status: t.status,\n linearId: this.mappings.get(t.id),\n })),\n },\n {\n name: 'In Progress',\n description: 'Active work items',\n tasks: tasks\n .filter((t) => t.status === 'in_progress')\n .map((t) => ({\n id: t.id,\n title: t.title,\n priority: t.priority || 'medium',\n status: t.status,\n linearId: this.mappings.get(t.id),\n })),\n },\n {\n name: 'Completed',\n description: 'Finished tasks',\n tasks: tasks\n .filter((t) => t.status === 'done')\n .slice(-20) // Keep last 20 completed\n .map((t) => ({\n id: t.id,\n title: t.title,\n priority: t.priority || 'medium',\n status: t.status,\n linearId: this.mappings.get(t.id),\n })),\n },\n ];\n\n plan.lastUpdated = new Date();\n this.saveTaskPlan(plan);\n\n // Also generate markdown report\n this.generateTaskReport(plan, stats);\n } catch (error: unknown) {\n logger.error('Failed to update task plan:', error as Error);\n }\n }\n\n /**\n * Generate markdown task report\n */\n private generateTaskReport(plan: TaskPlan, stats: SyncStats): void {\n const reportFile = join(this.projectRoot, '.stackmemory', 'task-report.md');\n \n let content = `# Task Sync Report\\n\\n`;\n content += `**Last Updated:** ${plan.lastUpdated.toLocaleString()}\\n`;\n content += `**Sync Duration:** ${stats.duration}ms\\n\\n`;\n\n content += `## Sync Statistics\\n\\n`;\n content += `### To Linear\\n`;\n content += `- Created: ${stats.toLinear.created}\\n`;\n content += `- Updated: ${stats.toLinear.updated}\\n`;\n content += `- Duplicates Merged: ${stats.toLinear.duplicatesMerged}\\n`;\n content += `- Skipped: ${stats.toLinear.skipped}\\n\\n`;\n\n content += `### From Linear\\n`;\n content += `- Created: ${stats.fromLinear.created}\\n`;\n content += `- Updated: ${stats.fromLinear.updated}\\n`;\n content += `- Skipped: ${stats.fromLinear.skipped}\\n\\n`;\n\n if (stats.conflicts.length > 0) {\n content += `### Conflicts\\n`;\n stats.conflicts.forEach((c) => {\n content += `- **${c.taskId}**: ${c.reason} (${c.resolution})\\n`;\n });\n content += '\\n';\n }\n\n content += `## Task Overview\\n\\n`;\n plan.phases.forEach((phase) => {\n content += `### ${phase.name} (${phase.tasks.length})\\n`;\n content += `> ${phase.description}\\n\\n`;\n \n if (phase.tasks.length > 0) {\n phase.tasks.slice(0, 10).forEach((task) => {\n const linearLink = task.linearId ? ` [Linear]` : '';\n content += `- **${task.title}**${linearLink}\\n`;\n });\n \n if (phase.tasks.length > 10) {\n content += `- _...and ${phase.tasks.length - 10} more_\\n`;\n }\n }\n content += '\\n';\n });\n\n writeFileSync(reportFile, content);\n logger.debug('Task report generated', { path: reportFile });\n }\n\n /**\n * Helper methods\n */\n \n private async getDefaultTeamId(): Promise<string> {\n const teams = await this.linearClient.getTeams();\n if (teams.length === 0) {\n throw new Error('No Linear teams found');\n }\n return teams[0]!.id;\n }\n\n private findLocalTaskByLinearId(linearId: string): string | undefined {\n for (const [taskId, linId] of this.mappings) {\n if (linId === linearId) return taskId;\n }\n return undefined;\n }\n\n private hasChanges(localTask: Task, linearIssue: LinearIssue): boolean {\n return (\n localTask.title !== linearIssue.title ||\n localTask.description !== (linearIssue.description || '') ||\n this.mapLinearStateToStatus(linearIssue.state.type) !== localTask.status\n );\n }\n\n private taskNeedsUpdate(task: Task, linearIssue: LinearIssue): boolean {\n return (\n task.title !== linearIssue.title ||\n task.description !== (linearIssue.description || '') ||\n task.status !== this.mapLinearStateToStatus(linearIssue.state.type)\n );\n }\n\n private async createLocalTask(issue: LinearIssue): Promise<void> {\n const task = await this.taskStore.createTask({\n title: issue.title,\n description: issue.description || '',\n status: this.mapLinearStateToStatus(issue.state.type),\n priority: this.mapLinearPriorityToPriority(issue.priority),\n metadata: {\n linear: {\n id: issue.id,\n identifier: issue.identifier,\n url: issue.url,\n },\n },\n });\n\n this.mappings.set(task.id, issue.id);\n }\n\n private async updateLocalTask(task: Task, issue: LinearIssue): Promise<void> {\n await this.taskStore.updateTask(task.id, {\n title: issue.title,\n description: issue.description || '',\n status: this.mapLinearStateToStatus(issue.state.type),\n priority: this.mapLinearPriorityToPriority(issue.priority),\n });\n }\n\n private async createLinearIssue(task: Task, teamId: string): Promise<void> {\n const input: LinearCreateIssueInput = {\n title: task.title,\n description: task.description || '',\n teamId,\n priority: this.mapPriorityToLinearPriority(task.priority),\n };\n\n const issue = await this.linearClient.createIssue(input);\n this.mappings.set(task.id, issue.id);\n\n // Update task with Linear metadata\n await this.taskStore.updateTask(task.id, {\n metadata: {\n ...task.metadata,\n linear: {\n id: issue.id,\n identifier: issue.identifier,\n url: issue.url,\n },\n },\n });\n }\n\n private async updateLinearIssue(issue: LinearIssue, task: Task): Promise<void> {\n await this.linearClient.updateIssue(issue.id, {\n title: task.title,\n description: task.description,\n priority: this.mapPriorityToLinearPriority(task.priority),\n });\n }\n\n private async mergeTaskIntoLinear(task: Task, existingIssue: LinearIssue): Promise<void> {\n await this.duplicateDetector.mergeIntoExisting(\n existingIssue,\n task.title,\n task.description,\n `StackMemory Task: ${task.id}\\nMerged: ${new Date().toISOString()}`\n );\n }\n\n private mapLinearStateToStatus(state: string): TaskStatus {\n switch (state.toLowerCase()) {\n case 'backlog':\n case 'unstarted':\n return 'todo';\n case 'started':\n return 'in_progress';\n case 'completed':\n return 'done';\n case 'cancelled':\n return 'cancelled';\n default:\n return 'todo';\n }\n }\n\n private mapLinearPriorityToPriority(priority?: number): TaskPriority | undefined {\n switch (priority) {\n case 1:\n return 'urgent';\n case 2:\n return 'high';\n case 3:\n return 'medium';\n case 4:\n return 'low';\n default:\n return undefined;\n }\n }\n\n private mapPriorityToLinearPriority(priority?: TaskPriority): number {\n switch (priority) {\n case 'urgent':\n return 1;\n case 'high':\n return 2;\n case 'medium':\n return 3;\n case 'low':\n return 4;\n default:\n return 0;\n }\n }\n\n private loadMappings(): void {\n const mappingFile = join(this.projectRoot, '.stackmemory', 'linear-mappings.json');\n if (existsSync(mappingFile)) {\n try {\n const data = JSON.parse(readFileSync(mappingFile, 'utf8'));\n this.mappings = new Map(Object.entries(data));\n } catch (error: unknown) {\n logger.error('Failed to load mappings:', error as Error);\n }\n }\n }\n\n private saveMappings(): void {\n const mappingFile = join(this.projectRoot, '.stackmemory', 'linear-mappings.json');\n const data = Object.fromEntries(this.mappings);\n writeFileSync(mappingFile, JSON.stringify(data, null, 2));\n }\n\n private loadTaskPlan(): TaskPlan {\n const planFile = join(this.projectRoot, this.config.taskPlanFile!);\n if (existsSync(planFile)) {\n try {\n return JSON.parse(readFileSync(planFile, 'utf8'));\n } catch (error: unknown) {\n logger.error('Failed to load task plan:', error as Error);\n }\n }\n \n return {\n version: '1.0.0',\n lastUpdated: new Date(),\n phases: [],\n };\n }\n\n private saveTaskPlan(plan: TaskPlan): void {\n const planFile = join(this.projectRoot, this.config.taskPlanFile!);\n writeFileSync(planFile, JSON.stringify(plan, null, 2));\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Get last sync statistics\n */\n getLastSyncStats(): SyncStats | undefined {\n return this.lastSyncStats;\n }\n\n /**\n * Clear duplicate detector cache\n */\n clearCache(): void {\n if (this.duplicateDetector) {\n this.duplicateDetector.clearCache();\n }\n }\n}"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -2,6 +2,17 @@ import { createHmac } from "crypto";
|
|
|
2
2
|
import { LinearSyncEngine } from "./sync.js";
|
|
3
3
|
import { LinearAuthManager } from "./auth.js";
|
|
4
4
|
import { logger } from "../../core/monitoring/logger.js";
|
|
5
|
+
function getEnv(key, defaultValue) {
|
|
6
|
+
const value = process.env[key];
|
|
7
|
+
if (value === void 0) {
|
|
8
|
+
if (defaultValue !== void 0) return defaultValue;
|
|
9
|
+
throw new Error(`Environment variable ${key} is required`);
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
function getOptionalEnv(key) {
|
|
14
|
+
return process.env[key];
|
|
15
|
+
}
|
|
5
16
|
class LinearWebhookHandler {
|
|
6
17
|
taskStore;
|
|
7
18
|
syncEngine = null;
|
|
@@ -9,7 +20,7 @@ class LinearWebhookHandler {
|
|
|
9
20
|
constructor(taskStore, webhookSecret) {
|
|
10
21
|
this.taskStore = taskStore;
|
|
11
22
|
this.webhookSecret = webhookSecret;
|
|
12
|
-
if (process.env
|
|
23
|
+
if (process.env["LINEAR_API_KEY"]) {
|
|
13
24
|
const authManager = new LinearAuthManager();
|
|
14
25
|
this.syncEngine = new LinearSyncEngine(taskStore, authManager, {
|
|
15
26
|
enabled: true,
|