@stoneforge/quarry 1.10.2 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/cli/commands/admin.d.ts.map +1 -1
- package/dist/cli/commands/admin.js +313 -3
- package/dist/cli/commands/admin.js.map +1 -1
- package/dist/cli/commands/auto-link-helper.d.ts +33 -0
- package/dist/cli/commands/auto-link-helper.d.ts.map +1 -0
- package/dist/cli/commands/auto-link-helper.js +73 -0
- package/dist/cli/commands/auto-link-helper.js.map +1 -0
- package/dist/cli/commands/crud.d.ts +1 -0
- package/dist/cli/commands/crud.d.ts.map +1 -1
- package/dist/cli/commands/crud.js +44 -5
- package/dist/cli/commands/crud.js.map +1 -1
- package/dist/cli/commands/docs.d.ts +1 -0
- package/dist/cli/commands/docs.d.ts.map +1 -1
- package/dist/cli/commands/docs.js +81 -1
- package/dist/cli/commands/docs.js.map +1 -1
- package/dist/cli/commands/external-sync.d.ts +17 -0
- package/dist/cli/commands/external-sync.d.ts.map +1 -0
- package/dist/cli/commands/external-sync.js +1647 -0
- package/dist/cli/commands/external-sync.js.map +1 -0
- package/dist/cli/commands/log.d.ts +18 -0
- package/dist/cli/commands/log.d.ts.map +1 -0
- package/dist/cli/commands/log.js +282 -0
- package/dist/cli/commands/log.js.map +1 -0
- package/dist/cli/commands/metrics.d.ts +9 -0
- package/dist/cli/commands/metrics.d.ts.map +1 -0
- package/dist/cli/commands/metrics.js +219 -0
- package/dist/cli/commands/metrics.js.map +1 -0
- package/dist/cli/runner.d.ts.map +1 -1
- package/dist/cli/runner.js +8 -0
- package/dist/cli/runner.js.map +1 -1
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +28 -0
- package/dist/config/config.js.map +1 -1
- package/dist/config/defaults.d.ts +13 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +21 -0
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/file.d.ts.map +1 -1
- package/dist/config/file.js +61 -0
- package/dist/config/file.js.map +1 -1
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +2 -2
- package/dist/config/index.js.map +1 -1
- package/dist/config/merge.d.ts.map +1 -1
- package/dist/config/merge.js +46 -1
- package/dist/config/merge.js.map +1 -1
- package/dist/config/types.d.ts +63 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +30 -0
- package/dist/config/types.js.map +1 -1
- package/dist/config/validation.d.ts.map +1 -1
- package/dist/config/validation.js +51 -1
- package/dist/config/validation.js.map +1 -1
- package/dist/external-sync/adapters/task-sync-adapter.d.ts +177 -0
- package/dist/external-sync/adapters/task-sync-adapter.d.ts.map +1 -0
- package/dist/external-sync/adapters/task-sync-adapter.js +353 -0
- package/dist/external-sync/adapters/task-sync-adapter.js.map +1 -0
- package/dist/external-sync/auto-link.d.ts +66 -0
- package/dist/external-sync/auto-link.d.ts.map +1 -0
- package/dist/external-sync/auto-link.js +98 -0
- package/dist/external-sync/auto-link.js.map +1 -0
- package/dist/external-sync/conflict-resolver.d.ts +170 -0
- package/dist/external-sync/conflict-resolver.d.ts.map +1 -0
- package/dist/external-sync/conflict-resolver.js +580 -0
- package/dist/external-sync/conflict-resolver.js.map +1 -0
- package/dist/external-sync/index.d.ts +20 -0
- package/dist/external-sync/index.d.ts.map +1 -0
- package/dist/external-sync/index.js +20 -0
- package/dist/external-sync/index.js.map +1 -0
- package/dist/external-sync/provider-registry.d.ts +109 -0
- package/dist/external-sync/provider-registry.d.ts.map +1 -0
- package/dist/external-sync/provider-registry.js +188 -0
- package/dist/external-sync/provider-registry.js.map +1 -0
- package/dist/external-sync/providers/github/github-api.d.ts +271 -0
- package/dist/external-sync/providers/github/github-api.d.ts.map +1 -0
- package/dist/external-sync/providers/github/github-api.js +366 -0
- package/dist/external-sync/providers/github/github-api.js.map +1 -0
- package/dist/external-sync/providers/github/github-field-map.d.ts +76 -0
- package/dist/external-sync/providers/github/github-field-map.d.ts.map +1 -0
- package/dist/external-sync/providers/github/github-field-map.js +157 -0
- package/dist/external-sync/providers/github/github-field-map.js.map +1 -0
- package/dist/external-sync/providers/github/github-provider.d.ts +36 -0
- package/dist/external-sync/providers/github/github-provider.d.ts.map +1 -0
- package/dist/external-sync/providers/github/github-provider.js +212 -0
- package/dist/external-sync/providers/github/github-provider.js.map +1 -0
- package/dist/external-sync/providers/github/github-task-adapter.d.ts +135 -0
- package/dist/external-sync/providers/github/github-task-adapter.d.ts.map +1 -0
- package/dist/external-sync/providers/github/github-task-adapter.js +374 -0
- package/dist/external-sync/providers/github/github-task-adapter.js.map +1 -0
- package/dist/external-sync/providers/github/index.d.ts +12 -0
- package/dist/external-sync/providers/github/index.d.ts.map +1 -0
- package/dist/external-sync/providers/github/index.js +15 -0
- package/dist/external-sync/providers/github/index.js.map +1 -0
- package/dist/external-sync/providers/index.d.ts +9 -0
- package/dist/external-sync/providers/index.d.ts.map +1 -0
- package/dist/external-sync/providers/index.js +10 -0
- package/dist/external-sync/providers/index.js.map +1 -0
- package/dist/external-sync/providers/linear/index.d.ts +19 -0
- package/dist/external-sync/providers/linear/index.d.ts.map +1 -0
- package/dist/external-sync/providers/linear/index.js +19 -0
- package/dist/external-sync/providers/linear/index.js.map +1 -0
- package/dist/external-sync/providers/linear/linear-api.d.ts +252 -0
- package/dist/external-sync/providers/linear/linear-api.d.ts.map +1 -0
- package/dist/external-sync/providers/linear/linear-api.js +522 -0
- package/dist/external-sync/providers/linear/linear-api.js.map +1 -0
- package/dist/external-sync/providers/linear/linear-field-map.d.ts +135 -0
- package/dist/external-sync/providers/linear/linear-field-map.d.ts.map +1 -0
- package/dist/external-sync/providers/linear/linear-field-map.js +338 -0
- package/dist/external-sync/providers/linear/linear-field-map.js.map +1 -0
- package/dist/external-sync/providers/linear/linear-provider.d.ts +52 -0
- package/dist/external-sync/providers/linear/linear-provider.d.ts.map +1 -0
- package/dist/external-sync/providers/linear/linear-provider.js +169 -0
- package/dist/external-sync/providers/linear/linear-provider.js.map +1 -0
- package/dist/external-sync/providers/linear/linear-task-adapter.d.ts +190 -0
- package/dist/external-sync/providers/linear/linear-task-adapter.d.ts.map +1 -0
- package/dist/external-sync/providers/linear/linear-task-adapter.js +521 -0
- package/dist/external-sync/providers/linear/linear-task-adapter.js.map +1 -0
- package/dist/external-sync/providers/linear/linear-types.d.ts +114 -0
- package/dist/external-sync/providers/linear/linear-types.d.ts.map +1 -0
- package/dist/external-sync/providers/linear/linear-types.js +10 -0
- package/dist/external-sync/providers/linear/linear-types.js.map +1 -0
- package/dist/external-sync/sync-engine.d.ts +298 -0
- package/dist/external-sync/sync-engine.d.ts.map +1 -0
- package/dist/external-sync/sync-engine.js +785 -0
- package/dist/external-sync/sync-engine.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/services/inbox.js +1 -1
- package/dist/sync/hash.d.ts +5 -0
- package/dist/sync/hash.d.ts.map +1 -1
- package/dist/sync/hash.js +21 -2
- package/dist/sync/hash.js.map +1 -1
- package/package.json +11 -5
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Sync Adapter Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared field mapping logic for converting between Stoneforge tasks and
|
|
5
|
+
* external task representations (e.g., GitHub Issues, Linear issues).
|
|
6
|
+
*
|
|
7
|
+
* These utilities are provider-agnostic — each provider supplies its own
|
|
8
|
+
* TaskSyncFieldMapConfig that tells the adapter how to map statuses,
|
|
9
|
+
* priorities, task types, and labels to/from the external system.
|
|
10
|
+
*
|
|
11
|
+
* Key functions:
|
|
12
|
+
* - taskToExternalTask: Convert a Stoneforge Task → ExternalTaskInput for push
|
|
13
|
+
* - externalTaskToTaskUpdates: Convert an ExternalTask → Partial<Task> for pull
|
|
14
|
+
*/
|
|
15
|
+
import { GITHUB_FIELD_MAP_CONFIG } from '../providers/github/github-field-map.js';
|
|
16
|
+
import { createLinearSyncFieldMapConfig, shouldAddBlockedLabel } from '../providers/linear/linear-field-map.js';
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Push: Stoneforge Task → External Task
|
|
19
|
+
// ============================================================================
|
|
20
|
+
/**
|
|
21
|
+
* Converts a Stoneforge Task into an ExternalTaskInput for creating/updating
|
|
22
|
+
* an issue in an external system.
|
|
23
|
+
*
|
|
24
|
+
* Handles:
|
|
25
|
+
* - Title mapping (1:1)
|
|
26
|
+
* - Status → state mapping via config.statusToState
|
|
27
|
+
* - Tags → labels (user tags preserved, sync-managed labels added)
|
|
28
|
+
* - Priority → label via config.priorityLabels
|
|
29
|
+
* - Task type → label via config.taskTypeLabels
|
|
30
|
+
* - Description hydration from descriptionRef document via api.get()
|
|
31
|
+
*
|
|
32
|
+
* Note: Assignees are intentionally NOT written to external systems.
|
|
33
|
+
* Stoneforge assignees are ephemeral agents (e.g., el-xxxx) that don't
|
|
34
|
+
* correspond to valid users on external platforms like GitHub.
|
|
35
|
+
*
|
|
36
|
+
* @param task - The Stoneforge task to convert
|
|
37
|
+
* @param config - Provider-specific field mapping configuration
|
|
38
|
+
* @param api - API with get() for hydrating description documents
|
|
39
|
+
* @returns ExternalTaskInput ready for the provider adapter
|
|
40
|
+
*/
|
|
41
|
+
export async function taskToExternalTask(task, config, api) {
|
|
42
|
+
// Map title (1:1)
|
|
43
|
+
const title = task.title;
|
|
44
|
+
// Map status → state
|
|
45
|
+
const state = config.statusToState(task.status);
|
|
46
|
+
// Build labels from sync-managed fields + user tags
|
|
47
|
+
const labels = buildExternalLabels(task, config);
|
|
48
|
+
// Hydrate description from descriptionRef document
|
|
49
|
+
const body = await hydrateDescription(task.descriptionRef, api);
|
|
50
|
+
return {
|
|
51
|
+
title,
|
|
52
|
+
body,
|
|
53
|
+
state,
|
|
54
|
+
labels,
|
|
55
|
+
// Assignees are intentionally omitted — Stoneforge assignees are ephemeral
|
|
56
|
+
// agents (e.g., el-xxxx) that don't map to valid external system users.
|
|
57
|
+
assignees: undefined,
|
|
58
|
+
priority: task.priority,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// Pull: External Task → Stoneforge Task Updates
|
|
63
|
+
// ============================================================================
|
|
64
|
+
/**
|
|
65
|
+
* Converts an ExternalTask into a partial Task update object for applying
|
|
66
|
+
* external changes to a local Stoneforge task.
|
|
67
|
+
*
|
|
68
|
+
* Handles:
|
|
69
|
+
* - Title mapping (1:1)
|
|
70
|
+
* - State + labels → status mapping via config.stateToStatus
|
|
71
|
+
* - Labels → priority, taskType, tags (separating sync-managed from user labels)
|
|
72
|
+
* - Assignees (first assignee mapped if present)
|
|
73
|
+
*
|
|
74
|
+
* If existingTask is provided, only changed fields are returned (diff mode).
|
|
75
|
+
* If existingTask is undefined, all fields are returned (create mode).
|
|
76
|
+
*
|
|
77
|
+
* Note: Description (body) handling is NOT included in the returned partial.
|
|
78
|
+
* The caller (sync engine) is responsible for creating/updating the description
|
|
79
|
+
* Document and linking via descriptionRef, since that requires document creation
|
|
80
|
+
* which is a separate API operation.
|
|
81
|
+
*
|
|
82
|
+
* @param externalTask - The external task to convert
|
|
83
|
+
* @param existingTask - The existing local task (undefined for new tasks)
|
|
84
|
+
* @param config - Provider-specific field mapping configuration
|
|
85
|
+
* @returns Partial<Task> with only the changed fields (or all fields if no existingTask)
|
|
86
|
+
*/
|
|
87
|
+
export function externalTaskToTaskUpdates(externalTask, existingTask, config) {
|
|
88
|
+
// Parse external labels into structured data
|
|
89
|
+
const parsed = parseExternalLabels(externalTask.labels, config);
|
|
90
|
+
// Map state + labels → status
|
|
91
|
+
const status = config.stateToStatus(externalTask.state, [...externalTask.labels]);
|
|
92
|
+
// Map priority: prefer native priority from the ExternalTask (set by providers
|
|
93
|
+
// with native priority support like Linear), fall back to label-based extraction,
|
|
94
|
+
// then default to medium (3) if neither is available.
|
|
95
|
+
const priority = externalTask.priority ?? parsed.priority ?? 3;
|
|
96
|
+
// Map task type from labels (default to 'task' if not found)
|
|
97
|
+
const taskType = parsed.taskType ?? 'task';
|
|
98
|
+
// User tags = all labels that aren't sync-managed
|
|
99
|
+
const tags = parsed.userTags;
|
|
100
|
+
// Build the full update object
|
|
101
|
+
const fullUpdate = {
|
|
102
|
+
title: externalTask.title,
|
|
103
|
+
status,
|
|
104
|
+
priority: priority,
|
|
105
|
+
taskType: taskType,
|
|
106
|
+
tags,
|
|
107
|
+
};
|
|
108
|
+
// Map first assignee if present
|
|
109
|
+
if (externalTask.assignees.length > 0) {
|
|
110
|
+
// Store assignee name in metadata for resolution by the sync engine
|
|
111
|
+
// The sync engine is responsible for looking up the EntityId
|
|
112
|
+
fullUpdate.metadata = {
|
|
113
|
+
_pendingAssignee: externalTask.assignees[0],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
// Map external ref URL
|
|
117
|
+
fullUpdate.externalRef = externalTask.url;
|
|
118
|
+
// If no existing task, return full create input
|
|
119
|
+
if (!existingTask) {
|
|
120
|
+
return fullUpdate;
|
|
121
|
+
}
|
|
122
|
+
// Diff mode: only return changed fields
|
|
123
|
+
return diffTaskUpdates(existingTask, fullUpdate);
|
|
124
|
+
}
|
|
125
|
+
// ============================================================================
|
|
126
|
+
// Label Building (Push)
|
|
127
|
+
// ============================================================================
|
|
128
|
+
/**
|
|
129
|
+
* Builds the complete set of external labels for a task.
|
|
130
|
+
*
|
|
131
|
+
* Combines:
|
|
132
|
+
* 1. Sync-managed priority label (prefixed)
|
|
133
|
+
* 2. Sync-managed task type label (prefixed)
|
|
134
|
+
* 3. Sync-managed status label (prefixed, when config.statusLabels is present)
|
|
135
|
+
* 4. User tags from the task (not prefixed — these are user-owned)
|
|
136
|
+
*/
|
|
137
|
+
export function buildExternalLabels(task, config) {
|
|
138
|
+
const labels = [];
|
|
139
|
+
const prefix = config.syncLabelPrefix;
|
|
140
|
+
// Add priority label
|
|
141
|
+
const priorityLabel = config.priorityLabels[task.priority];
|
|
142
|
+
if (priorityLabel) {
|
|
143
|
+
labels.push(`${prefix}${priorityLabel}`);
|
|
144
|
+
}
|
|
145
|
+
// Add task type label
|
|
146
|
+
const taskTypeLabel = config.taskTypeLabels[task.taskType];
|
|
147
|
+
if (taskTypeLabel) {
|
|
148
|
+
labels.push(`${prefix}${taskTypeLabel}`);
|
|
149
|
+
}
|
|
150
|
+
// Add status label (when config.statusLabels is present)
|
|
151
|
+
if (config.statusLabels) {
|
|
152
|
+
const statusLabel = config.statusLabels[task.status];
|
|
153
|
+
if (statusLabel) {
|
|
154
|
+
labels.push(`${prefix}${statusLabel}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Add a plain "blocked" label when the task has blocked status.
|
|
158
|
+
// This label is used by provider adapters (e.g., LinearTaskAdapter) to detect
|
|
159
|
+
// blocked tasks and apply provider-specific handling (e.g., adding a native
|
|
160
|
+
// "blocked" label on Linear, mapping to 'started' state type instead of
|
|
161
|
+
// 'unstarted'). The label is always added regardless of statusLabels —
|
|
162
|
+
// adapters need this signal to properly handle blocked tasks.
|
|
163
|
+
if (shouldAddBlockedLabel(task.status)) {
|
|
164
|
+
labels.push('blocked');
|
|
165
|
+
}
|
|
166
|
+
// Add user tags (not prefixed — these are user-managed)
|
|
167
|
+
for (const tag of task.tags) {
|
|
168
|
+
labels.push(tag);
|
|
169
|
+
}
|
|
170
|
+
return labels;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Parses external labels into structured task field values.
|
|
174
|
+
*
|
|
175
|
+
* Separates sync-managed labels (prefixed) from user labels,
|
|
176
|
+
* and extracts priority, task type, and status values from the managed labels.
|
|
177
|
+
*/
|
|
178
|
+
export function parseExternalLabels(labels, config) {
|
|
179
|
+
const prefix = config.syncLabelPrefix;
|
|
180
|
+
let priority;
|
|
181
|
+
let taskType;
|
|
182
|
+
let status;
|
|
183
|
+
const userTags = [];
|
|
184
|
+
// Build reverse lookup maps for priority and task type
|
|
185
|
+
const priorityByLabel = buildReverseLookup(config.priorityLabels);
|
|
186
|
+
const taskTypeByLabel = buildReverseLookup(config.taskTypeLabels);
|
|
187
|
+
// Build reverse lookup for status labels (when present)
|
|
188
|
+
const statusByLabel = config.statusLabels
|
|
189
|
+
? buildReverseLookup(config.statusLabels)
|
|
190
|
+
: undefined;
|
|
191
|
+
for (const label of labels) {
|
|
192
|
+
if (label.startsWith(prefix)) {
|
|
193
|
+
// This is a sync-managed label — extract the value part
|
|
194
|
+
const value = label.slice(prefix.length);
|
|
195
|
+
// Check if it's a priority label
|
|
196
|
+
if (priorityByLabel.has(value)) {
|
|
197
|
+
priority = priorityByLabel.get(value);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
// Check if it's a task type label
|
|
201
|
+
if (taskTypeByLabel.has(value)) {
|
|
202
|
+
taskType = taskTypeByLabel.get(value);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
// Check if it's a status label
|
|
206
|
+
if (statusByLabel && statusByLabel.has(value)) {
|
|
207
|
+
status = statusByLabel.get(value);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
// Sync-managed label we don't recognize — skip it
|
|
211
|
+
// (could be from a newer version or a different adapter)
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
// Not a sync-managed label — treat as user tag
|
|
215
|
+
userTags.push(label);
|
|
216
|
+
}
|
|
217
|
+
return { priority, taskType, status, userTags };
|
|
218
|
+
}
|
|
219
|
+
// ============================================================================
|
|
220
|
+
// Description Handling
|
|
221
|
+
// ============================================================================
|
|
222
|
+
/**
|
|
223
|
+
* Hydrates a task's description from its descriptionRef document.
|
|
224
|
+
*
|
|
225
|
+
* If the task has a descriptionRef, fetches the document via the API
|
|
226
|
+
* and returns its content as the body string. Returns undefined if
|
|
227
|
+
* no descriptionRef is set or the document is not found.
|
|
228
|
+
*/
|
|
229
|
+
export async function hydrateDescription(descriptionRef, api) {
|
|
230
|
+
if (!descriptionRef) {
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
const doc = await api.get(descriptionRef);
|
|
234
|
+
if (!doc || doc.type !== 'document') {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
return doc.content || undefined;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Resolves task assignee to an external-friendly name.
|
|
241
|
+
*
|
|
242
|
+
* @deprecated No longer used — Stoneforge assignees are ephemeral agents
|
|
243
|
+
* that don't map to valid users on external platforms. Assignee writing
|
|
244
|
+
* to external systems has been removed. This function is kept temporarily
|
|
245
|
+
* for reference but will be removed in a future cleanup.
|
|
246
|
+
*/
|
|
247
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
248
|
+
async function resolveAssignees(task, api) {
|
|
249
|
+
if (!task.assignee) {
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
const entity = await api.get(task.assignee);
|
|
253
|
+
if (!entity || entity.type !== 'entity') {
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
// Entity has a 'name' field
|
|
257
|
+
const name = entity.name;
|
|
258
|
+
if (typeof name === 'string') {
|
|
259
|
+
return [name];
|
|
260
|
+
}
|
|
261
|
+
return [];
|
|
262
|
+
}
|
|
263
|
+
// ============================================================================
|
|
264
|
+
// Diff Utilities
|
|
265
|
+
// ============================================================================
|
|
266
|
+
/**
|
|
267
|
+
* Compares a full update against an existing task and returns only
|
|
268
|
+
* the fields that actually changed.
|
|
269
|
+
*
|
|
270
|
+
* This prevents unnecessary updates when pulling changes from external
|
|
271
|
+
* systems where the data hasn't actually changed.
|
|
272
|
+
*/
|
|
273
|
+
export function diffTaskUpdates(existingTask, fullUpdate) {
|
|
274
|
+
const diff = {};
|
|
275
|
+
if (fullUpdate.title !== undefined && fullUpdate.title !== existingTask.title) {
|
|
276
|
+
diff.title = fullUpdate.title;
|
|
277
|
+
}
|
|
278
|
+
if (fullUpdate.status !== undefined && fullUpdate.status !== existingTask.status) {
|
|
279
|
+
diff.status = fullUpdate.status;
|
|
280
|
+
}
|
|
281
|
+
if (fullUpdate.priority !== undefined && fullUpdate.priority !== existingTask.priority) {
|
|
282
|
+
diff.priority = fullUpdate.priority;
|
|
283
|
+
}
|
|
284
|
+
if (fullUpdate.taskType !== undefined && fullUpdate.taskType !== existingTask.taskType) {
|
|
285
|
+
diff.taskType = fullUpdate.taskType;
|
|
286
|
+
}
|
|
287
|
+
if (fullUpdate.tags !== undefined && !arraysEqual(fullUpdate.tags, existingTask.tags)) {
|
|
288
|
+
diff.tags = fullUpdate.tags;
|
|
289
|
+
}
|
|
290
|
+
if (fullUpdate.externalRef !== undefined && fullUpdate.externalRef !== existingTask.externalRef) {
|
|
291
|
+
diff.externalRef = fullUpdate.externalRef;
|
|
292
|
+
}
|
|
293
|
+
// Include metadata if there's a pending assignee
|
|
294
|
+
if (fullUpdate.metadata !== undefined) {
|
|
295
|
+
const pendingAssignee = fullUpdate.metadata?._pendingAssignee;
|
|
296
|
+
if (pendingAssignee !== undefined) {
|
|
297
|
+
diff.metadata = fullUpdate.metadata;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return diff;
|
|
301
|
+
}
|
|
302
|
+
// ============================================================================
|
|
303
|
+
// Internal Helpers
|
|
304
|
+
// ============================================================================
|
|
305
|
+
/**
|
|
306
|
+
* Builds a reverse lookup map from a Record<K, V> to Map<V, K>.
|
|
307
|
+
* Used to look up priority/task type values from their label strings.
|
|
308
|
+
*/
|
|
309
|
+
function buildReverseLookup(record) {
|
|
310
|
+
const map = new Map();
|
|
311
|
+
for (const [key, value] of Object.entries(record)) {
|
|
312
|
+
// Keys from Record<Priority, string> come as strings from Object.entries
|
|
313
|
+
// Need to convert numeric keys back to numbers
|
|
314
|
+
const typedKey = isNaN(Number(key)) ? key : Number(key);
|
|
315
|
+
map.set(value, typedKey);
|
|
316
|
+
}
|
|
317
|
+
return map;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Compares two arrays for shallow equality (order-sensitive).
|
|
321
|
+
*/
|
|
322
|
+
function arraysEqual(a, b) {
|
|
323
|
+
if (a.length !== b.length)
|
|
324
|
+
return false;
|
|
325
|
+
// Sort copies for order-independent comparison
|
|
326
|
+
const sortedA = [...a].sort();
|
|
327
|
+
const sortedB = [...b].sort();
|
|
328
|
+
return sortedA.every((val, idx) => val === sortedB[idx]);
|
|
329
|
+
}
|
|
330
|
+
// ============================================================================
|
|
331
|
+
// Provider Field Map Config Lookup
|
|
332
|
+
// ============================================================================
|
|
333
|
+
/**
|
|
334
|
+
* Returns the TaskSyncFieldMapConfig for a given provider name.
|
|
335
|
+
*
|
|
336
|
+
* Shared utility used by both the CLI link-all command and the sync engine
|
|
337
|
+
* push path to get the correct field mapping for a provider.
|
|
338
|
+
*
|
|
339
|
+
* @param providerName - The provider name (e.g., 'github', 'linear')
|
|
340
|
+
* @returns The provider-specific TaskSyncFieldMapConfig
|
|
341
|
+
*/
|
|
342
|
+
export function getFieldMapConfigForProvider(providerName) {
|
|
343
|
+
switch (providerName) {
|
|
344
|
+
case 'github':
|
|
345
|
+
return GITHUB_FIELD_MAP_CONFIG;
|
|
346
|
+
case 'linear':
|
|
347
|
+
return createLinearSyncFieldMapConfig();
|
|
348
|
+
default:
|
|
349
|
+
// Fallback to GitHub-style config for unknown providers
|
|
350
|
+
return GITHUB_FIELD_MAP_CONFIG;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
//# sourceMappingURL=task-sync-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-sync-adapter.js","sourceRoot":"","sources":["../../../src/external-sync/adapters/task-sync-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-Link Utility
|
|
3
|
+
*
|
|
4
|
+
* Creates an external issue on a configured provider and links it back to
|
|
5
|
+
* a Stoneforge task via `externalRef` and `_externalSync` metadata.
|
|
6
|
+
*
|
|
7
|
+
* Designed to be called after task creation in a fire-and-forget pattern.
|
|
8
|
+
* Never throws — all errors are caught and returned as `{ success: false, error }`.
|
|
9
|
+
*/
|
|
10
|
+
import type { Task, ExternalProvider, ExternalSyncState, SyncDirection } from '@stoneforge/core';
|
|
11
|
+
import type { QuarryAPI } from '../api/types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Parameters for autoLinkTask()
|
|
14
|
+
*/
|
|
15
|
+
export interface AutoLinkTaskParams {
|
|
16
|
+
/** The newly created task to auto-link */
|
|
17
|
+
task: Task;
|
|
18
|
+
/** QuarryAPI instance for updating the task */
|
|
19
|
+
api: QuarryAPI;
|
|
20
|
+
/** Configured external provider instance (e.g., GitHub, Linear) */
|
|
21
|
+
provider: ExternalProvider;
|
|
22
|
+
/** Project identifier (e.g., 'owner/repo' for GitHub, team key for Linear) */
|
|
23
|
+
project: string;
|
|
24
|
+
/** Sync direction to set on the link */
|
|
25
|
+
direction: SyncDirection;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Result of autoLinkTask()
|
|
29
|
+
*/
|
|
30
|
+
export interface AutoLinkTaskResult {
|
|
31
|
+
/** Whether the auto-link succeeded */
|
|
32
|
+
success: boolean;
|
|
33
|
+
/** The ExternalSyncState set on the task (only on success) */
|
|
34
|
+
syncState?: ExternalSyncState;
|
|
35
|
+
/** Error message (only on failure) */
|
|
36
|
+
error?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Auto-link a newly created task to an external issue.
|
|
40
|
+
*
|
|
41
|
+
* Creates an issue on the configured provider and updates the Stoneforge task
|
|
42
|
+
* with `externalRef` and `_externalSync` metadata.
|
|
43
|
+
*
|
|
44
|
+
* **Never throws** — catches all errors and returns `{ success: false, error }`.
|
|
45
|
+
* Auto-link failure must not prevent task creation.
|
|
46
|
+
*
|
|
47
|
+
* @param params - The auto-link parameters
|
|
48
|
+
* @returns Result indicating success or failure with details
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const result = await autoLinkTask({
|
|
53
|
+
* task,
|
|
54
|
+
* api,
|
|
55
|
+
* provider: githubProvider,
|
|
56
|
+
* project: 'owner/repo',
|
|
57
|
+
* direction: 'bidirectional',
|
|
58
|
+
* });
|
|
59
|
+
*
|
|
60
|
+
* if (!result.success) {
|
|
61
|
+
* console.warn('Auto-link failed:', result.error);
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare function autoLinkTask(params: AutoLinkTaskParams): Promise<AutoLinkTaskResult>;
|
|
66
|
+
//# sourceMappingURL=auto-link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-link.d.ts","sourceRoot":"","sources":["../../src/external-sync/auto-link.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,IAAI,EACJ,gBAAgB,EAEhB,iBAAiB,EACjB,aAAa,EAEd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AASjD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,0CAA0C;IAC1C,IAAI,EAAE,IAAI,CAAC;IACX,+CAA+C;IAC/C,GAAG,EAAE,SAAS,CAAC;IACf,mEAAmE;IACnE,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,SAAS,EAAE,aAAa,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAkE1F"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-Link Utility
|
|
3
|
+
*
|
|
4
|
+
* Creates an external issue on a configured provider and links it back to
|
|
5
|
+
* a Stoneforge task via `externalRef` and `_externalSync` metadata.
|
|
6
|
+
*
|
|
7
|
+
* Designed to be called after task creation in a fire-and-forget pattern.
|
|
8
|
+
* Never throws — all errors are caught and returned as `{ success: false, error }`.
|
|
9
|
+
*/
|
|
10
|
+
import { taskToExternalTask, getFieldMapConfigForProvider } from './adapters/task-sync-adapter.js';
|
|
11
|
+
const LOG_PREFIX = '[external-sync]';
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Auto-Link Function
|
|
14
|
+
// ============================================================================
|
|
15
|
+
/**
|
|
16
|
+
* Auto-link a newly created task to an external issue.
|
|
17
|
+
*
|
|
18
|
+
* Creates an issue on the configured provider and updates the Stoneforge task
|
|
19
|
+
* with `externalRef` and `_externalSync` metadata.
|
|
20
|
+
*
|
|
21
|
+
* **Never throws** — catches all errors and returns `{ success: false, error }`.
|
|
22
|
+
* Auto-link failure must not prevent task creation.
|
|
23
|
+
*
|
|
24
|
+
* @param params - The auto-link parameters
|
|
25
|
+
* @returns Result indicating success or failure with details
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const result = await autoLinkTask({
|
|
30
|
+
* task,
|
|
31
|
+
* api,
|
|
32
|
+
* provider: githubProvider,
|
|
33
|
+
* project: 'owner/repo',
|
|
34
|
+
* direction: 'bidirectional',
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* if (!result.success) {
|
|
38
|
+
* console.warn('Auto-link failed:', result.error);
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export async function autoLinkTask(params) {
|
|
43
|
+
const { task, api, provider, project, direction } = params;
|
|
44
|
+
try {
|
|
45
|
+
// 1. Get the provider's task adapter
|
|
46
|
+
const adapter = provider.getTaskAdapter?.();
|
|
47
|
+
if (!adapter) {
|
|
48
|
+
const message = `Provider '${provider.name}' does not support task sync`;
|
|
49
|
+
console.warn(`${LOG_PREFIX} Auto-link failed for task ${task.id}: ${message}`);
|
|
50
|
+
return { success: false, error: message };
|
|
51
|
+
}
|
|
52
|
+
// 2. Build full ExternalTaskInput via taskToExternalTask, with fallback
|
|
53
|
+
let issueInput;
|
|
54
|
+
try {
|
|
55
|
+
const fieldMapConfig = getFieldMapConfigForProvider(provider.name);
|
|
56
|
+
issueInput = await taskToExternalTask(task, fieldMapConfig, api);
|
|
57
|
+
}
|
|
58
|
+
catch (mappingErr) {
|
|
59
|
+
// Fallback to simplified input so auto-link still succeeds
|
|
60
|
+
console.warn(`${LOG_PREFIX} Full field mapping failed for task ${task.id}, using simplified input: ${mappingErr instanceof Error ? mappingErr.message : String(mappingErr)}`);
|
|
61
|
+
issueInput = {
|
|
62
|
+
title: task.title,
|
|
63
|
+
body: task.descriptionRef ? `Stoneforge task: ${task.id}` : undefined,
|
|
64
|
+
labels: task.tags ? [...task.tags] : undefined,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// 3. Create the external issue
|
|
68
|
+
const externalTask = await adapter.createIssue(project, issueInput);
|
|
69
|
+
// 4. Build the ExternalSyncState metadata
|
|
70
|
+
const syncState = {
|
|
71
|
+
provider: provider.name,
|
|
72
|
+
project,
|
|
73
|
+
externalId: externalTask.externalId,
|
|
74
|
+
url: externalTask.url,
|
|
75
|
+
direction,
|
|
76
|
+
adapterType: 'task',
|
|
77
|
+
};
|
|
78
|
+
// 5. Update the task with externalRef and _externalSync metadata
|
|
79
|
+
const existingMetadata = (task.metadata ?? {});
|
|
80
|
+
await api.update(task.id, {
|
|
81
|
+
externalRef: externalTask.url,
|
|
82
|
+
metadata: {
|
|
83
|
+
...existingMetadata,
|
|
84
|
+
_externalSync: syncState,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
console.info(`${LOG_PREFIX} Auto-linked task ${task.id} to ${provider.name} issue ${externalTask.externalId} (${externalTask.url})`);
|
|
88
|
+
// 6. Return success
|
|
89
|
+
return { success: true, syncState };
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
// 7. Never throw — catch all errors and return failure
|
|
93
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
94
|
+
console.warn(`${LOG_PREFIX} Auto-link failed for task ${task.id}: ${message}`);
|
|
95
|
+
return { success: false, error: message };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=auto-link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-link.js","sourceRoot":"","sources":["../../src/external-sync/auto-link.ts"],"names":[],"mappings":"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"}
|