@gh-symphony/cli 0.1.4 → 0.2.2
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 +87 -0
- package/dist/{chunk-HT3FAJAO.js → chunk-27UZ6KX2.js} +273 -10
- package/dist/{chunk-EWTMSDCE.js → chunk-3SKN5L3I.js} +260 -18
- package/dist/{chunk-WOVNN5NW.js → chunk-4ICDSQCJ.js} +1 -0
- package/dist/{chunk-DW63WPRE.js → chunk-6PFFGP7S.js} +18 -3
- package/dist/{chunk-Z3NZOPLZ.js → chunk-BOM2BYZQ.js} +43 -0
- package/dist/{chunk-E7OCBNB2.js → chunk-FAU72YC2.js} +1 -1
- package/dist/{chunk-RHLUIMBN.js → chunk-PLBG7TZA.js} +306 -30
- package/dist/{chunk-6I753NYO.js → chunk-RZ3WO7OV.js} +1 -1
- package/dist/{repo-IH6UWE4H.js → chunk-X4QSP3AX.js} +5443 -6207
- package/dist/{config-cmd-2ADPUYWA.js → config-cmd-AOZVS6GU.js} +1 -1
- package/dist/{doctor-I32MANQ4.js → doctor-GDZSGJIT.js} +602 -26
- package/dist/index.js +12 -8
- package/dist/repo-SWEUWY4H.js +2693 -0
- package/dist/{setup-UJC2WYHQ.js → setup-XNOSJ3RX.js} +35 -32
- package/dist/{upgrade-XYHCUGHT.js → upgrade-ZWUAJLHK.js} +2 -2
- package/dist/{version-B2AYYGLM.js → version-PLQK6X2P.js} +1 -1
- package/dist/worker-entry.js +77 -9
- package/dist/{workflow-WSXHMO5B.js → workflow-2ERPNGRB.js} +7 -6
- package/package.json +3 -3
- package/dist/chunk-YIARPBOR.js +0 -1648
package/README.md
CHANGED
|
@@ -37,6 +37,7 @@ gh-symphony doctor
|
|
|
37
37
|
gh-symphony doctor --fix
|
|
38
38
|
gh-symphony doctor --json
|
|
39
39
|
gh-symphony doctor --smoke
|
|
40
|
+
gh-symphony doctor --bundle
|
|
40
41
|
GITHUB_GRAPHQL_TOKEN=ghp_your_classic_token gh-symphony doctor --json
|
|
41
42
|
```
|
|
42
43
|
|
|
@@ -107,6 +108,75 @@ You can further customize the agent's behavior by editing `WORKFLOW.md` — this
|
|
|
107
108
|
|
|
108
109
|
> Currently supported runtimes: **Codex**, **Claude Code**
|
|
109
110
|
|
|
111
|
+
### Explicit Priority Mapping
|
|
112
|
+
|
|
113
|
+
GitHub Project V2 does not have a native issue priority. For GitHub Project workflows, dispatch priority is controlled only by the explicit `tracker.priority` policy in `WORKFLOW.md`; there is no fallback from Project fields to labels and no guessed label naming convention. Unmapped values resolve to `priority = null`, so dispatch falls back to created time and identifier.
|
|
114
|
+
|
|
115
|
+
Project field source:
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
tracker:
|
|
119
|
+
kind: github-project
|
|
120
|
+
project_id: PVT_kwDOxxxxxx
|
|
121
|
+
state_field: Status
|
|
122
|
+
priority:
|
|
123
|
+
source: project-field
|
|
124
|
+
field: Priority
|
|
125
|
+
values:
|
|
126
|
+
Urgent: 0
|
|
127
|
+
High: 1
|
|
128
|
+
Medium: 2
|
|
129
|
+
Low: 3
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Label source:
|
|
133
|
+
|
|
134
|
+
```yaml
|
|
135
|
+
tracker:
|
|
136
|
+
kind: github-project
|
|
137
|
+
project_id: PVT_kwDOxxxxxx
|
|
138
|
+
state_field: Status
|
|
139
|
+
priority:
|
|
140
|
+
source: labels
|
|
141
|
+
labels:
|
|
142
|
+
P0: 0
|
|
143
|
+
P1: 1
|
|
144
|
+
P2: 2
|
|
145
|
+
P3: 3
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Disabled:
|
|
149
|
+
|
|
150
|
+
```yaml
|
|
151
|
+
tracker:
|
|
152
|
+
kind: github-project
|
|
153
|
+
priority:
|
|
154
|
+
source: disabled
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Legacy `tracker.priority_field: Priority` still works, but it is deprecated because it derives numeric priority from the live Project option order. Migrate by copying the field name into `tracker.priority.field` and writing each option display name under `values` with the intended number. If both keys are present, `tracker.priority` wins and `gh-symphony doctor` reports a warning.
|
|
158
|
+
|
|
159
|
+
Run `gh-symphony workflow validate` for local schema errors and `gh-symphony doctor` for live drift warnings such as missing Project fields, missing labels, unmapped live options, stale mappings, and active issues whose priority-like value resolves to `priority = null`.
|
|
160
|
+
|
|
161
|
+
### Linear Tracker Repositories
|
|
162
|
+
|
|
163
|
+
For Linear, configure the tracker in `WORKFLOW.md` and initialize the repository runtime from the target GitHub repository:
|
|
164
|
+
|
|
165
|
+
```yaml
|
|
166
|
+
tracker:
|
|
167
|
+
kind: linear
|
|
168
|
+
api_key: $LINEAR_API_KEY
|
|
169
|
+
project_slug: symphony-0c79b11b75ea
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`gh-symphony repo init` validates `tracker.project_slug` and resolves `tracker.api_key`, so `LINEAR_API_KEY` must be set before initialization. Linear aliases such as `tracker.project_id`, `projectId`, `project_id`, and `teamId` are rejected, and `.gh-symphony/config.json` is not a Linear source of truth.
|
|
173
|
+
|
|
174
|
+
Linear runs are polling-only. There is no webhook setup command. Put state transition, workpad comment, and PR handoff policy in `WORKFLOW.md`; see `docs/examples/linear-WORKFLOW.md` in the repository for a complete example. Preview a Linear issue prompt with:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
gh-symphony workflow preview ENG-123
|
|
178
|
+
```
|
|
179
|
+
|
|
110
180
|
### Repository `.env` Mapping
|
|
111
181
|
|
|
112
182
|
If your hooks or worker runs need staging hosts, database URLs, Playwright base URLs, or other runtime-only values, store them in the repository runtime directory instead of hardcoding them in `WORKFLOW.md`.
|
|
@@ -284,6 +354,22 @@ Without `--issue`, doctor auto-selects one active live issue from the managed pr
|
|
|
284
354
|
- launches `gh-symphony setup` when repository runtime setup or GitHub Project binding must be repaired
|
|
285
355
|
- prints concrete runtime install guidance when the configured command is missing on `PATH`
|
|
286
356
|
|
|
357
|
+
`gh-symphony doctor --bundle` creates a redacted support bundle for bug reports:
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
gh-symphony doctor --bundle
|
|
361
|
+
gh-symphony doctor --bundle ./tmp/support-bundle
|
|
362
|
+
gh-symphony doctor --bundle --project-id your-project-id
|
|
363
|
+
gh-symphony doctor --bundle --json
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
The bundle writes a deterministic directory containing `manifest.json`,
|
|
367
|
+
`doctor.json`, redacted CLI/project config, `WORKFLOW.md`, runtime
|
|
368
|
+
`status.json`/`issues.json` when available, and bounded recent run
|
|
369
|
+
`events.ndjson`, `worker.log`, and `orchestrator.log` tails. Missing optional
|
|
370
|
+
artifacts are listed in `manifest.missing`; redaction and truncation counts are
|
|
371
|
+
reported in the command summary.
|
|
372
|
+
|
|
287
373
|
The diagnostic checks cover:
|
|
288
374
|
|
|
289
375
|
- the active GitHub auth source (`GITHUB_GRAPHQL_TOKEN` first, otherwise `gh`) and required scopes
|
|
@@ -302,6 +388,7 @@ Use JSON output for scripts and CI smoke checks. `--fix --json` includes a remed
|
|
|
302
388
|
gh-symphony doctor --json
|
|
303
389
|
gh-symphony doctor --fix --json
|
|
304
390
|
gh-symphony doctor --smoke --json
|
|
391
|
+
gh-symphony doctor --bundle --json
|
|
305
392
|
gh-symphony repo start --once
|
|
306
393
|
```
|
|
307
394
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
workflow_init_default
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-PLBG7TZA.js";
|
|
5
5
|
import {
|
|
6
6
|
fetchGithubProjectIssueByRepositoryAndNumber,
|
|
7
|
-
inspectManagedProjectSelection
|
|
8
|
-
|
|
7
|
+
inspectManagedProjectSelection,
|
|
8
|
+
resolveTrackerAdapter
|
|
9
|
+
} from "./chunk-X4QSP3AX.js";
|
|
9
10
|
import {
|
|
10
11
|
GitHubApiError,
|
|
11
12
|
createClient,
|
|
@@ -13,16 +14,198 @@ import {
|
|
|
13
14
|
getGhTokenWithSource,
|
|
14
15
|
getProjectDetail,
|
|
15
16
|
validateGitHubToken
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-BOM2BYZQ.js";
|
|
17
18
|
import {
|
|
18
19
|
buildPromptVariables,
|
|
19
20
|
parseWorkflowMarkdown,
|
|
20
21
|
renderPrompt
|
|
21
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-3SKN5L3I.js";
|
|
23
|
+
import {
|
|
24
|
+
loadActiveProjectConfig
|
|
25
|
+
} from "./chunk-4ICDSQCJ.js";
|
|
22
26
|
|
|
23
27
|
// src/commands/workflow.ts
|
|
24
28
|
import { readFile } from "fs/promises";
|
|
25
29
|
import { resolve } from "path";
|
|
30
|
+
|
|
31
|
+
// src/priority-diagnostics.ts
|
|
32
|
+
function buildPriorityConfigDiagnostics(workflow) {
|
|
33
|
+
const diagnostics = [];
|
|
34
|
+
if (workflow.tracker.priorityFieldName) {
|
|
35
|
+
if (workflow.tracker.priority) {
|
|
36
|
+
diagnostics.push({
|
|
37
|
+
title: "Priority mapping precedence",
|
|
38
|
+
summary: "Both legacy tracker.priority_field and explicit tracker.priority are configured; explicit tracker.priority wins.",
|
|
39
|
+
remediation: "Remove tracker.priority_field after confirming the explicit tracker.priority mapping is correct.",
|
|
40
|
+
details: {
|
|
41
|
+
priorityFieldName: workflow.tracker.priorityFieldName,
|
|
42
|
+
explicitSource: workflow.tracker.priority.source
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
} else {
|
|
46
|
+
diagnostics.push({
|
|
47
|
+
title: "Legacy priority mapping",
|
|
48
|
+
summary: "tracker.priority_field is deprecated and still supported with legacy Project option-order semantics.",
|
|
49
|
+
remediation: "Migrate to tracker.priority with an explicit project-field, labels, or disabled source.",
|
|
50
|
+
details: {
|
|
51
|
+
priorityFieldName: workflow.tracker.priorityFieldName
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return diagnostics;
|
|
57
|
+
}
|
|
58
|
+
function buildPriorityDriftDiagnostics(input) {
|
|
59
|
+
const priority = input.workflow.tracker.priority;
|
|
60
|
+
if (!priority || priority.source === "disabled") {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
if (priority.source === "project-field") {
|
|
64
|
+
return buildProjectFieldDriftDiagnostics({
|
|
65
|
+
priority,
|
|
66
|
+
projectDetail: input.projectDetail,
|
|
67
|
+
activeIssues: input.activeIssues
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return buildLabelDriftDiagnostics({
|
|
71
|
+
priority,
|
|
72
|
+
repositoryLabels: input.repositoryLabels,
|
|
73
|
+
activeIssues: input.activeIssues
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function buildProjectFieldDriftDiagnostics(input) {
|
|
77
|
+
const diagnostics = [];
|
|
78
|
+
const field = input.projectDetail.statusFields.find(
|
|
79
|
+
(candidate) => candidate.name === input.priority.field
|
|
80
|
+
);
|
|
81
|
+
if (!field) {
|
|
82
|
+
diagnostics.push({
|
|
83
|
+
title: "Priority Project field drift",
|
|
84
|
+
summary: `Configured priority field "${input.priority.field}" was not found in the GitHub Project schema.`,
|
|
85
|
+
remediation: "Update tracker.priority.field to the exact live Project V2 single-select field name, or disable priority mapping.",
|
|
86
|
+
details: {
|
|
87
|
+
field: input.priority.field,
|
|
88
|
+
availableFields: input.projectDetail.statusFields.map(
|
|
89
|
+
(candidate) => candidate.name
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
return diagnostics;
|
|
94
|
+
}
|
|
95
|
+
const liveOptions = new Set(field.options.map((option) => option.name));
|
|
96
|
+
const mappedOptions = new Set(Object.keys(input.priority.values));
|
|
97
|
+
const unmappedLiveOptions = [...liveOptions].filter(
|
|
98
|
+
(option) => !mappedOptions.has(option)
|
|
99
|
+
);
|
|
100
|
+
const missingConfiguredOptions = [...mappedOptions].filter(
|
|
101
|
+
(option) => !liveOptions.has(option)
|
|
102
|
+
);
|
|
103
|
+
if (unmappedLiveOptions.length > 0) {
|
|
104
|
+
diagnostics.push({
|
|
105
|
+
title: "Unmapped priority Project options",
|
|
106
|
+
summary: `Priority field "${field.name}" has live option(s) not mapped in tracker.priority.values: ${unmappedLiveOptions.join(", ")}.`,
|
|
107
|
+
remediation: "Add explicit numeric mappings for these options or accept that issues holding them resolve to priority = null.",
|
|
108
|
+
details: { field: field.name, unmappedLiveOptions }
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (missingConfiguredOptions.length > 0) {
|
|
112
|
+
diagnostics.push({
|
|
113
|
+
title: "Missing priority Project options",
|
|
114
|
+
summary: `tracker.priority.values references option(s) that do not exist in priority field "${field.name}": ${missingConfiguredOptions.join(", ")}.`,
|
|
115
|
+
remediation: "Rename the mapping keys to match live Project option display names or remove stale mappings.",
|
|
116
|
+
details: { field: field.name, missingConfiguredOptions }
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const activeUnmapped = input.activeIssues.flatMap((issue) => {
|
|
120
|
+
const rawValue = issue.metadata?.[field.name];
|
|
121
|
+
return typeof rawValue === "string" && rawValue.length > 0 && !mappedOptions.has(rawValue) ? [{ issue: issue.identifier, value: rawValue }] : [];
|
|
122
|
+
});
|
|
123
|
+
if (activeUnmapped.length > 0) {
|
|
124
|
+
diagnostics.push({
|
|
125
|
+
title: "Active issues with unmapped priority values",
|
|
126
|
+
summary: `Active issue(s) currently hold unmapped "${field.name}" value(s); they resolve to priority = null.`,
|
|
127
|
+
remediation: "Map the live value in tracker.priority.values, change the issue value, or leave it unmapped intentionally.",
|
|
128
|
+
details: { field: field.name, activeUnmapped }
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return diagnostics;
|
|
132
|
+
}
|
|
133
|
+
function buildLabelDriftDiagnostics(input) {
|
|
134
|
+
const diagnostics = [];
|
|
135
|
+
const configuredLabels = Object.keys(input.priority.labels);
|
|
136
|
+
if (input.repositoryLabels) {
|
|
137
|
+
const missingByRepository = input.repositoryLabels.flatMap((snapshot) => {
|
|
138
|
+
const live = new Set(snapshot.labels);
|
|
139
|
+
const missing = configuredLabels.filter((label) => !live.has(label));
|
|
140
|
+
return missing.length > 0 ? [{ repository: snapshot.repository, missing }] : [];
|
|
141
|
+
});
|
|
142
|
+
if (missingByRepository.length > 0) {
|
|
143
|
+
diagnostics.push({
|
|
144
|
+
title: "Missing configured priority labels",
|
|
145
|
+
summary: "One or more configured tracker.priority.labels entries are absent from linked repositories.",
|
|
146
|
+
remediation: "Create the labels in each linked repository, rename the mapping keys to exact live labels, or remove stale mappings.",
|
|
147
|
+
details: { missingByRepository }
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
const liveLabels = new Set(
|
|
151
|
+
input.repositoryLabels.flatMap((snapshot) => snapshot.labels)
|
|
152
|
+
);
|
|
153
|
+
const missingEverywhere = configuredLabels.filter(
|
|
154
|
+
(label) => !liveLabels.has(label)
|
|
155
|
+
);
|
|
156
|
+
if (missingEverywhere.length > 0) {
|
|
157
|
+
diagnostics.push({
|
|
158
|
+
title: "Stale priority label mappings",
|
|
159
|
+
summary: `tracker.priority.labels references label(s) that do not exist in any linked repository: ${missingEverywhere.join(", ")}.`,
|
|
160
|
+
remediation: "Rename the mapping keys to exact live labels, create those labels, or remove stale mappings.",
|
|
161
|
+
details: { missingEverywhere }
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const configuredLabelByNormalized = new Map(
|
|
166
|
+
configuredLabels.map((label) => [normalizeLabelForComparison(label), label])
|
|
167
|
+
);
|
|
168
|
+
const activeConflicts = input.activeIssues.flatMap((issue) => {
|
|
169
|
+
const matches = issue.labels.flatMap((label) => {
|
|
170
|
+
const configuredLabel = configuredLabelByNormalized.get(
|
|
171
|
+
normalizeLabelForComparison(label)
|
|
172
|
+
);
|
|
173
|
+
return configuredLabel ? [configuredLabel] : [];
|
|
174
|
+
});
|
|
175
|
+
return matches.length > 1 ? [{ issue: issue.identifier, labels: matches }] : [];
|
|
176
|
+
});
|
|
177
|
+
if (activeConflicts.length > 0) {
|
|
178
|
+
diagnostics.push({
|
|
179
|
+
title: "Active issues with multiple priority labels",
|
|
180
|
+
summary: "Active issue(s) have multiple configured priority labels; runtime chooses the lowest numeric value and emits priority.label_conflict_resolved.",
|
|
181
|
+
remediation: "Remove extra priority labels from the issue if only one priority label should apply.",
|
|
182
|
+
details: { activeConflicts }
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
const activeUnmapped = input.activeIssues.flatMap((issue) => {
|
|
186
|
+
const labels = issue.labels.filter(
|
|
187
|
+
(label) => isPriorityLikeLabel(label) && !configuredLabelByNormalized.has(normalizeLabelForComparison(label))
|
|
188
|
+
);
|
|
189
|
+
return labels.length > 0 ? [{ issue: issue.identifier, labels }] : [];
|
|
190
|
+
});
|
|
191
|
+
if (activeUnmapped.length > 0) {
|
|
192
|
+
diagnostics.push({
|
|
193
|
+
title: "Active issues with unmapped priority labels",
|
|
194
|
+
summary: "Active issue(s) have priority-like labels not mapped by tracker.priority.labels; those labels do not affect dispatch priority.",
|
|
195
|
+
remediation: "Add explicit mappings for these labels, rename the issue labels, or leave them unmapped intentionally.",
|
|
196
|
+
details: { activeUnmapped }
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return diagnostics;
|
|
200
|
+
}
|
|
201
|
+
function isPriorityLikeLabel(label) {
|
|
202
|
+
return /^(p\d+|priority[:/\s_-].+|prio[:/\s_-].+)$/i.test(label.trim());
|
|
203
|
+
}
|
|
204
|
+
function normalizeLabelForComparison(label) {
|
|
205
|
+
return label.trim().toLowerCase();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/commands/workflow.ts
|
|
26
209
|
var SAMPLE_ISSUE = {
|
|
27
210
|
id: "issue-157-sample",
|
|
28
211
|
identifier: "octo/hello-world#157",
|
|
@@ -65,7 +248,9 @@ var workflowCommandDependencies = {
|
|
|
65
248
|
fetchLiveIssue: fetchGithubProjectIssueByRepositoryAndNumber,
|
|
66
249
|
getGitHubProjectDetail: getProjectDetail,
|
|
67
250
|
getGitHubTokenWithSource: getGhTokenWithSource,
|
|
251
|
+
loadActiveProjectConfig,
|
|
68
252
|
resolveManagedProjectSelection: inspectManagedProjectSelection,
|
|
253
|
+
resolveTrackerAdapter,
|
|
69
254
|
validateGitHubToken
|
|
70
255
|
};
|
|
71
256
|
function setWorkflowCommandDependenciesForTest(overrides) {
|
|
@@ -76,7 +261,9 @@ function resetWorkflowCommandDependenciesForTest() {
|
|
|
76
261
|
workflowCommandDependencies.fetchLiveIssue = fetchGithubProjectIssueByRepositoryAndNumber;
|
|
77
262
|
workflowCommandDependencies.getGitHubProjectDetail = getProjectDetail;
|
|
78
263
|
workflowCommandDependencies.getGitHubTokenWithSource = getGhTokenWithSource;
|
|
264
|
+
workflowCommandDependencies.loadActiveProjectConfig = loadActiveProjectConfig;
|
|
79
265
|
workflowCommandDependencies.resolveManagedProjectSelection = inspectManagedProjectSelection;
|
|
266
|
+
workflowCommandDependencies.resolveTrackerAdapter = resolveTrackerAdapter;
|
|
80
267
|
workflowCommandDependencies.validateGitHubToken = validateGitHubToken;
|
|
81
268
|
}
|
|
82
269
|
function parseWorkflowArgs(args) {
|
|
@@ -140,6 +327,9 @@ function parsePreviewFlags(args) {
|
|
|
140
327
|
if (!value || value.startsWith("-")) {
|
|
141
328
|
throw new Error("Option '--issue' argument missing");
|
|
142
329
|
}
|
|
330
|
+
if (flags.issue) {
|
|
331
|
+
throw new Error("Only one preview issue identifier can be provided.");
|
|
332
|
+
}
|
|
143
333
|
flags.issue = value;
|
|
144
334
|
i += 1;
|
|
145
335
|
break;
|
|
@@ -162,6 +352,10 @@ function parsePreviewFlags(args) {
|
|
|
162
352
|
if (arg?.startsWith("-")) {
|
|
163
353
|
throw new Error(`Unknown option '${arg}'`);
|
|
164
354
|
}
|
|
355
|
+
if (flags.issue) {
|
|
356
|
+
throw new Error("Only one preview issue identifier can be provided.");
|
|
357
|
+
}
|
|
358
|
+
flags.issue = arg;
|
|
165
359
|
break;
|
|
166
360
|
}
|
|
167
361
|
}
|
|
@@ -185,7 +379,9 @@ Commands:
|
|
|
185
379
|
Options:
|
|
186
380
|
workflow init [--non-interactive] [--project <id>] [--output <path>] [--skip-skills] [--skip-context] [--dry-run]
|
|
187
381
|
workflow validate [--file <path>]
|
|
188
|
-
workflow preview [--file <path>] [--issue <owner/repo#number>] [--project-id <projectId>] [--sample <json>] [--attempt <n>]
|
|
382
|
+
workflow preview [issue] [--file <path>] [--issue <owner/repo#number|ENG-123>] [--project-id <projectId>] [--sample <json>] [--attempt <n>]
|
|
383
|
+
|
|
384
|
+
Linear workflows use polling through the configured tracker adapter. No Linear webhook setup command is provided.
|
|
189
385
|
`);
|
|
190
386
|
}
|
|
191
387
|
async function loadWorkflowMarkdown(workflowPath) {
|
|
@@ -399,7 +595,7 @@ function renderIssueWorkflowPreview(input) {
|
|
|
399
595
|
function formatAuthError(error) {
|
|
400
596
|
return `GitHub authentication is required for live issue preview. ${error.message}`;
|
|
401
597
|
}
|
|
402
|
-
async function loadLiveIssue(issueReference, projectId, options) {
|
|
598
|
+
async function loadLiveIssue(issueReference, projectId, workflow, options) {
|
|
403
599
|
const issue = parseIssueReference(issueReference);
|
|
404
600
|
const selection = await workflowCommandDependencies.resolveManagedProjectSelection({
|
|
405
601
|
configDir: options.configDir,
|
|
@@ -453,6 +649,7 @@ async function loadLiveIssue(issueReference, projectId, options) {
|
|
|
453
649
|
token: auth.token,
|
|
454
650
|
apiUrl: selection.projectConfig.tracker.apiUrl,
|
|
455
651
|
assignedOnly: selection.projectConfig.tracker.settings?.assignedOnly === true,
|
|
652
|
+
priority: workflow.tracker.priority,
|
|
456
653
|
priorityFieldName: typeof selection.projectConfig.tracker.settings?.priorityFieldName === "string" ? selection.projectConfig.tracker.settings.priorityFieldName : void 0,
|
|
457
654
|
timeoutMs: typeof selection.projectConfig.tracker.settings?.timeoutMs === "number" ? selection.projectConfig.tracker.settings.timeoutMs : void 0
|
|
458
655
|
},
|
|
@@ -472,6 +669,58 @@ async function loadLiveIssue(issueReference, projectId, options) {
|
|
|
472
669
|
sampleSource: `live:${trackedIssue.identifier}`
|
|
473
670
|
};
|
|
474
671
|
}
|
|
672
|
+
var LINEAR_IDENTIFIER_PATTERN = /^[A-Z][A-Z0-9]*-\d+$/;
|
|
673
|
+
function isLinearIssueIdentifier(value) {
|
|
674
|
+
return LINEAR_IDENTIFIER_PATTERN.test(value.trim().toUpperCase());
|
|
675
|
+
}
|
|
676
|
+
async function loadLinearIssue(issueIdentifier, workflow, options) {
|
|
677
|
+
const projectConfig = await workflowCommandDependencies.loadActiveProjectConfig(
|
|
678
|
+
options.configDir
|
|
679
|
+
);
|
|
680
|
+
if (!projectConfig?.repository) {
|
|
681
|
+
throw new Error(
|
|
682
|
+
"Linear live issue preview requires a repository runtime initialized with 'gh-symphony repo init'."
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
const workflowProjectSlug = workflow.tracker.projectSlug?.trim();
|
|
686
|
+
if (!workflowProjectSlug) {
|
|
687
|
+
throw new Error(
|
|
688
|
+
'Linear live issue preview requires WORKFLOW.md field "tracker.project_slug".'
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
if (!workflow.tracker.apiKey?.trim()) {
|
|
692
|
+
throw new Error(
|
|
693
|
+
'Linear live issue preview requires WORKFLOW.md field "tracker.api_key" to resolve, for example "$LINEAR_API_KEY".'
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
if (projectConfig.tracker.adapter !== "linear" || projectConfig.tracker.bindingId !== workflowProjectSlug) {
|
|
697
|
+
throw new Error(
|
|
698
|
+
`Linear live issue preview requires an active repository runtime initialized for project "${workflowProjectSlug}". Run 'gh-symphony repo init' from this repository, then re-run the preview.`
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
const orchestratorProject = {
|
|
702
|
+
projectId: projectConfig.projectId,
|
|
703
|
+
slug: projectConfig.slug,
|
|
704
|
+
workspaceDir: projectConfig.workspaceDir,
|
|
705
|
+
repository: projectConfig.repository,
|
|
706
|
+
tracker: projectConfig.tracker
|
|
707
|
+
};
|
|
708
|
+
const trackerAdapter = workflowCommandDependencies.resolveTrackerAdapter(projectConfig.tracker);
|
|
709
|
+
const [issue] = await trackerAdapter.fetchIssueStatesByIds(
|
|
710
|
+
orchestratorProject,
|
|
711
|
+
[issueIdentifier.trim().toUpperCase()],
|
|
712
|
+
{ token: workflow.tracker.apiKey }
|
|
713
|
+
);
|
|
714
|
+
if (!issue) {
|
|
715
|
+
throw new Error(
|
|
716
|
+
`Linear issue ${issueIdentifier} was not found in project "${workflow.tracker.projectSlug}".`
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
return {
|
|
720
|
+
issue,
|
|
721
|
+
sampleSource: `live:${issue.identifier}`
|
|
722
|
+
};
|
|
723
|
+
}
|
|
475
724
|
function validateWorkflow(workflowPath, markdown) {
|
|
476
725
|
const workflow = parseWorkflowMarkdown(markdown);
|
|
477
726
|
const promptFreshVariables = buildPromptVariables(SAMPLE_ISSUE, {
|
|
@@ -495,6 +744,7 @@ function validateWorkflow(workflowPath, markdown) {
|
|
|
495
744
|
promptRetry: "pass",
|
|
496
745
|
continuationGuidance: continuationGuidanceStatus
|
|
497
746
|
},
|
|
747
|
+
warnings: buildPriorityConfigDiagnostics(workflow),
|
|
498
748
|
summary: {
|
|
499
749
|
trackerKind: workflow.tracker.kind,
|
|
500
750
|
githubProjectId: workflow.githubProjectId,
|
|
@@ -565,6 +815,17 @@ Hooks
|
|
|
565
815
|
before_remove=${report.summary.hooks.beforeRemove ?? "unset"}
|
|
566
816
|
hooks.timeout_ms=${report.summary.hooks.timeoutMs}
|
|
567
817
|
`);
|
|
818
|
+
if (report.warnings.length > 0) {
|
|
819
|
+
process.stdout.write("\nWarnings\n");
|
|
820
|
+
for (const warning of report.warnings) {
|
|
821
|
+
process.stdout.write(` ${warning.title}: ${warning.summary}
|
|
822
|
+
`);
|
|
823
|
+
if (warning.remediation) {
|
|
824
|
+
process.stdout.write(` Fix: ${warning.remediation}
|
|
825
|
+
`);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
568
829
|
}
|
|
569
830
|
async function runValidate(args, options) {
|
|
570
831
|
const flags = parseValidateFlags(args);
|
|
@@ -586,12 +847,12 @@ async function runPreview(args, options) {
|
|
|
586
847
|
}
|
|
587
848
|
const { workflowPath, markdown } = await loadWorkflowMarkdown(flags.file);
|
|
588
849
|
const workflow = parseWorkflowMarkdown(markdown);
|
|
589
|
-
if (flags.issue && workflow.tracker.kind !== "github-project") {
|
|
850
|
+
if (flags.issue && workflow.tracker.kind !== "github-project" && !(workflow.tracker.kind === "linear" && isLinearIssueIdentifier(flags.issue))) {
|
|
590
851
|
throw new Error(
|
|
591
|
-
"Live issue preview requires 'tracker.kind: github-project'
|
|
852
|
+
"Live issue preview requires 'tracker.kind: github-project' with owner/repo#number or 'tracker.kind: linear' with a Linear identifier such as ENG-123."
|
|
592
853
|
);
|
|
593
854
|
}
|
|
594
|
-
const { issue, sampleSource } = flags.issue ? await loadLiveIssue(flags.issue, flags.projectId, options) : await loadSampleIssue(flags.sample);
|
|
855
|
+
const { issue, sampleSource } = flags.issue ? workflow.tracker.kind === "linear" ? await loadLinearIssue(flags.issue, workflow, options) : await loadLiveIssue(flags.issue, flags.projectId, workflow, options) : await loadSampleIssue(flags.sample);
|
|
595
856
|
const renderedPrompt = renderIssueWorkflowPreview({
|
|
596
857
|
workflow,
|
|
597
858
|
issue,
|
|
@@ -662,6 +923,8 @@ var handler = async (args, options) => {
|
|
|
662
923
|
var workflow_default = handler;
|
|
663
924
|
|
|
664
925
|
export {
|
|
926
|
+
buildPriorityConfigDiagnostics,
|
|
927
|
+
buildPriorityDriftDiagnostics,
|
|
665
928
|
setWorkflowCommandDependenciesForTest,
|
|
666
929
|
resetWorkflowCommandDependenciesForTest,
|
|
667
930
|
parseIssueReference,
|