@gh-symphony/cli 0.0.20 → 0.0.21
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 +30 -2
- package/dist/chunk-A67CMOYE.js +684 -0
- package/dist/{chunk-TILHWBP6.js → chunk-C67H3OUL.js} +239 -36
- package/dist/{chunk-RN2PACNV.js → chunk-JN3TQVFV.js} +721 -74
- package/dist/{chunk-EKKT5USP.js → chunk-KY6WKH66.js} +437 -101
- package/dist/{chunk-HZVDTAPS.js → chunk-MYVJ6HK4.js} +943 -1182
- package/dist/{chunk-M3IFVLQS.js → chunk-QEONJ5DZ.js} +978 -72
- package/dist/{chunk-3AWF54PI.js → chunk-S6VIK4FF.js} +59 -31
- package/dist/chunk-SXGT7LOF.js +1060 -0
- package/dist/{doctor-IYHCFXOZ.js → doctor-4HBRICHP.js} +102 -37
- package/dist/index.js +38 -17
- package/dist/{init-KZT6YNOH.js → init-HZ3JEDGQ.js} +7 -2
- package/dist/{project-UUVHS3ZR.js → project-25NQ4J4Y.js} +8 -6
- package/dist/{recover-5KQI7WH5.js → recover-L3MJHHDA.js} +4 -2
- package/dist/{repo-HDDE7OUI.js → repo-TDCWQR6P.js} +72 -14
- package/dist/{run-ETC5UTRA.js → run-XJQ6BF7U.js} +4 -2
- package/dist/{setup-VWB7RZUQ.js → setup-B2SVLW2R.js} +46 -8
- package/dist/{start-ENFLZUI6.js → start-I2CC7BLW.js} +6 -4
- package/dist/{upgrade-3YNF3VKY.js → upgrade-OJXPZRYE.js} +2 -2
- package/dist/{version-NUBTTOG7.js → version-TBDCTKDO.js} +1 -1
- package/dist/worker-entry.js +489 -690
- package/dist/{workflow-TBIFY5MO.js → workflow-BLJH2HC3.js} +176 -10
- package/package.json +3 -1
|
@@ -4,12 +4,18 @@ import {
|
|
|
4
4
|
GitHubScopeError,
|
|
5
5
|
checkRequiredScopes,
|
|
6
6
|
createClient,
|
|
7
|
+
discoverUserProjects,
|
|
7
8
|
getGhTokenWithSource,
|
|
8
9
|
getProjectDetail,
|
|
9
10
|
listUserProjects,
|
|
10
11
|
resolveGitHubAuth,
|
|
11
12
|
validateToken
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-C67H3OUL.js";
|
|
14
|
+
import {
|
|
15
|
+
formatClaudePreflightText,
|
|
16
|
+
resolveClaudeCommandBinary,
|
|
17
|
+
runClaudePreflight
|
|
18
|
+
} from "./chunk-QEONJ5DZ.js";
|
|
13
19
|
import {
|
|
14
20
|
loadGlobalConfig,
|
|
15
21
|
saveGlobalConfig,
|
|
@@ -18,8 +24,9 @@ import {
|
|
|
18
24
|
|
|
19
25
|
// src/commands/init.ts
|
|
20
26
|
import * as p from "@clack/prompts";
|
|
27
|
+
import { spawnSync } from "child_process";
|
|
21
28
|
import { createHash } from "crypto";
|
|
22
|
-
import { mkdir as mkdir3, readFile as readFile3, rename as rename2, writeFile as writeFile3 } from "fs/promises";
|
|
29
|
+
import { chmod, mkdir as mkdir3, readFile as readFile3, rename as rename2, writeFile as writeFile3 } from "fs/promises";
|
|
23
30
|
import { basename, dirname as dirname2, join as join3, relative, resolve } from "path";
|
|
24
31
|
|
|
25
32
|
// src/mapping/smart-defaults.ts
|
|
@@ -99,10 +106,243 @@ function validateStateMapping(mappings) {
|
|
|
99
106
|
return { valid: errors.length === 0, errors, warnings };
|
|
100
107
|
}
|
|
101
108
|
|
|
109
|
+
// src/prompts/runtime-claude-constraints.ts
|
|
110
|
+
var CLAUDE_RUNTIME_CONSTRAINTS_SECTION = `## Runtime Constraints
|
|
111
|
+
|
|
112
|
+
1. This run uses \`claude-print\` (Claude Code CLI) in non-interactive mode via \`claude -p\`.
|
|
113
|
+
2. Slash commands such as \`/commit\`, \`/push\`, \`/gh-project\`, \`/gh-pr-writeup\` are NOT available (CLI limitation, independent of isolation settings).
|
|
114
|
+
3. Use \`gh\`, \`git\`, repository scripts, and configured MCP tools directly instead.
|
|
115
|
+
4. If a required permission or tool is unavailable, post a blocker comment on the issue and exit. Do not wait for human input.`;
|
|
116
|
+
var CLAUDE_PERMISSIVE_ISOLATION_NOTE = "<!-- Runtime trade-off note: Permissive preset requires an isolated workspace. Symphony runs each issue in `.runtime/symphony-workspaces/<workspace-id>/`, a throwaway clone. If you disable workspace isolation or mount host paths into worker containers, do not use this runtime in production. -->";
|
|
117
|
+
var CLAUDE_ISOLATION_OFF_NOTE = "<!-- Runtime trade-off note: Isolation is off by default \u2014 the agent will pick up your `CLAUDE.md`, project skills, and personal MCPs from `~/.claude/`. Turn isolation on when running in multi-operator CI, shared infrastructure, or when reproducibility across machines matters. -->";
|
|
118
|
+
var CLAUDE_RUNTIME_PROMPT_PREAMBLE = [
|
|
119
|
+
CLAUDE_RUNTIME_CONSTRAINTS_SECTION,
|
|
120
|
+
CLAUDE_PERMISSIVE_ISOLATION_NOTE,
|
|
121
|
+
CLAUDE_ISOLATION_OFF_NOTE
|
|
122
|
+
].join("\n\n");
|
|
123
|
+
|
|
124
|
+
// src/workflow/default-hooks.ts
|
|
125
|
+
var DEFAULT_AFTER_CREATE_HOOK_PATH = "hooks/after_create.sh";
|
|
126
|
+
var DEFAULT_AFTER_CREATE_HOOK_LABEL = "Workspace hook scaffold";
|
|
127
|
+
var DEFAULT_AFTER_CREATE_HOOK_COMMENT = "scaffolded by workflow init; customize this script for repository setup";
|
|
128
|
+
var DEFAULT_AFTER_CREATE_HOOK_CONTENT = `#!/usr/bin/env bash
|
|
129
|
+
set -euo pipefail
|
|
130
|
+
|
|
131
|
+
# Customize this hook to prepare a freshly created workspace.
|
|
132
|
+
# This scaffold is intentionally a no-op so generated workflows run cleanly.
|
|
133
|
+
exit 0
|
|
134
|
+
`;
|
|
135
|
+
|
|
136
|
+
// src/workflow/repository-guidance.ts
|
|
137
|
+
function normalizeCommand(command) {
|
|
138
|
+
return command.replace(/\s+/g, " ").trim();
|
|
139
|
+
}
|
|
140
|
+
function renderInlineCode(command) {
|
|
141
|
+
const normalized = normalizeCommand(command);
|
|
142
|
+
const longestBacktickRun = Math.max(
|
|
143
|
+
0,
|
|
144
|
+
...Array.from(normalized.matchAll(/`+/g), (match) => match[0].length)
|
|
145
|
+
);
|
|
146
|
+
const fence = "`".repeat(longestBacktickRun + 1);
|
|
147
|
+
const padded = normalized.startsWith("`") || normalized.endsWith("`") ? ` ${normalized} ` : normalized;
|
|
148
|
+
return `${fence}${padded}${fence}`;
|
|
149
|
+
}
|
|
150
|
+
function buildRunnableScriptCommand(packageManager, scriptName) {
|
|
151
|
+
switch (packageManager) {
|
|
152
|
+
case "pnpm":
|
|
153
|
+
return scriptName === "test" ? "pnpm test" : `pnpm ${scriptName}`;
|
|
154
|
+
case "npm":
|
|
155
|
+
return scriptName === "test" ? "npm test" : `npm run ${scriptName}`;
|
|
156
|
+
case "yarn":
|
|
157
|
+
return `yarn ${scriptName}`;
|
|
158
|
+
case "bun":
|
|
159
|
+
return `bun run ${scriptName}`;
|
|
160
|
+
default:
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function formatDetectedCommand(label, rawCommand, packageManager) {
|
|
165
|
+
if (!rawCommand) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const runnableCommand = buildRunnableScriptCommand(packageManager, label);
|
|
169
|
+
const normalizedRawCommand = normalizeCommand(rawCommand);
|
|
170
|
+
if (!runnableCommand) {
|
|
171
|
+
return `${label}: ${renderInlineCode(normalizedRawCommand)}`;
|
|
172
|
+
}
|
|
173
|
+
if (normalizeCommand(runnableCommand) === normalizedRawCommand) {
|
|
174
|
+
return `${label}: ${renderInlineCode(runnableCommand)}`;
|
|
175
|
+
}
|
|
176
|
+
return `${label}: ${renderInlineCode(runnableCommand)} (script: ${renderInlineCode(
|
|
177
|
+
normalizedRawCommand
|
|
178
|
+
)})`;
|
|
179
|
+
}
|
|
180
|
+
function buildRepositoryValidationGuidance(input) {
|
|
181
|
+
const lines = [];
|
|
182
|
+
const commands = [
|
|
183
|
+
formatDetectedCommand("test", input.testCommand, input.packageManager),
|
|
184
|
+
formatDetectedCommand("lint", input.lintCommand, input.packageManager),
|
|
185
|
+
formatDetectedCommand("build", input.buildCommand, input.packageManager)
|
|
186
|
+
].filter((value) => value !== null);
|
|
187
|
+
if (commands.length > 0) {
|
|
188
|
+
lines.push(
|
|
189
|
+
`Detected repository validation commands: ${commands.join(" ; ")}.`
|
|
190
|
+
);
|
|
191
|
+
lines.push(
|
|
192
|
+
"Prefer these repository-defined commands over generic guesses when validating changes."
|
|
193
|
+
);
|
|
194
|
+
lines.push(
|
|
195
|
+
"Use the smallest relevant command during iteration, then run the full available validation sequence before handoff in this order when applicable: test, lint, build."
|
|
196
|
+
);
|
|
197
|
+
} else {
|
|
198
|
+
lines.push(
|
|
199
|
+
"No repository-specific test/lint/build scripts were detected. Keep the generic fallback posture: infer the smallest meaningful validation command from the files you changed, and explicitly report when no automated validation is available."
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
if (input.packageManager) {
|
|
203
|
+
lines.push(
|
|
204
|
+
`Use \`${input.packageManager}\` conventions for ad hoc install/run commands unless the repository clearly requires something else.`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
if (input.monorepo) {
|
|
208
|
+
lines.push(
|
|
209
|
+
"This repository appears to be a monorepo. Infer the affected package or workspace first, prefer workspace-scoped validation when available, and avoid unnecessary full-repo runs unless cross-package changes require them."
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
return lines;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/workflow/workflow-runtime.ts
|
|
216
|
+
var CODEX_RUNTIME_TOKENS = ["codex-app-server", "codex"];
|
|
217
|
+
var CLAUDE_RUNTIME_TOKENS = ["claude-print", "claude-code"];
|
|
218
|
+
var DEFAULT_CODEX_APP_SERVER_ARGS = ["app-server"];
|
|
219
|
+
var DEFAULT_CLAUDE_PRINT_ARGS = [
|
|
220
|
+
"-p",
|
|
221
|
+
"--output-format",
|
|
222
|
+
"stream-json",
|
|
223
|
+
"--input-format",
|
|
224
|
+
"stream-json",
|
|
225
|
+
"--include-partial-messages",
|
|
226
|
+
"--verbose",
|
|
227
|
+
"--permission-mode",
|
|
228
|
+
"bypassPermissions"
|
|
229
|
+
];
|
|
230
|
+
function normalizeInitRuntime(runtime) {
|
|
231
|
+
if (runtime === "codex") {
|
|
232
|
+
return "codex-app-server";
|
|
233
|
+
}
|
|
234
|
+
if (runtime === "claude-code") {
|
|
235
|
+
return "claude-print";
|
|
236
|
+
}
|
|
237
|
+
return runtime;
|
|
238
|
+
}
|
|
239
|
+
function isSupportedInitRuntime(runtime) {
|
|
240
|
+
const normalized = normalizeInitRuntime(runtime);
|
|
241
|
+
return normalized === "codex-app-server" || normalized === "claude-print";
|
|
242
|
+
}
|
|
243
|
+
function containsRuntimeToken(runtime, tokens) {
|
|
244
|
+
return tokens.some((token) => {
|
|
245
|
+
const escaped = token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
246
|
+
return new RegExp(`(^|[\\s"'\\\`])${escaped}(?=$|[\\s"'\\\`])`).test(
|
|
247
|
+
runtime
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
function isCodexRuntime(runtime) {
|
|
252
|
+
return normalizeInitRuntime(runtime) === "codex-app-server" || containsRuntimeToken(runtime, CODEX_RUNTIME_TOKENS);
|
|
253
|
+
}
|
|
254
|
+
function isClaudeRuntime(runtime) {
|
|
255
|
+
return normalizeInitRuntime(runtime) === "claude-print" || containsRuntimeToken(runtime, CLAUDE_RUNTIME_TOKENS);
|
|
256
|
+
}
|
|
257
|
+
function resolveRuntimeCommand(runtime) {
|
|
258
|
+
const normalized = normalizeInitRuntime(runtime);
|
|
259
|
+
if (normalized === "codex-app-server") {
|
|
260
|
+
return "codex";
|
|
261
|
+
}
|
|
262
|
+
if (normalized === "claude-print") {
|
|
263
|
+
return "claude";
|
|
264
|
+
}
|
|
265
|
+
return runtime;
|
|
266
|
+
}
|
|
267
|
+
function resolveRuntimeArgs(runtime) {
|
|
268
|
+
const normalized = normalizeInitRuntime(runtime);
|
|
269
|
+
if (normalized === "codex-app-server") {
|
|
270
|
+
return DEFAULT_CODEX_APP_SERVER_ARGS;
|
|
271
|
+
}
|
|
272
|
+
if (normalized === "claude-print") {
|
|
273
|
+
return DEFAULT_CLAUDE_PRINT_ARGS;
|
|
274
|
+
}
|
|
275
|
+
return [];
|
|
276
|
+
}
|
|
277
|
+
function resolveRuntimeAgentCommand(runtime) {
|
|
278
|
+
const command = resolveRuntimeCommand(runtime);
|
|
279
|
+
const args = resolveRuntimeArgs(runtime);
|
|
280
|
+
return args.length === 0 ? command : [command, ...args].join(" ");
|
|
281
|
+
}
|
|
282
|
+
function resolveShellAgentCommand(runtime) {
|
|
283
|
+
if (/^\s*bash\s+-lc\s+/.test(runtime)) {
|
|
284
|
+
return runtime;
|
|
285
|
+
}
|
|
286
|
+
const normalized = normalizeInitRuntime(runtime);
|
|
287
|
+
if (normalized === "codex-app-server" || normalized === "claude-print") {
|
|
288
|
+
return `bash -lc ${resolveRuntimeAgentCommand(normalized)}`;
|
|
289
|
+
}
|
|
290
|
+
return runtime;
|
|
291
|
+
}
|
|
292
|
+
function buildRuntimeFrontMatter(runtime) {
|
|
293
|
+
const normalized = normalizeInitRuntime(runtime);
|
|
294
|
+
if (normalized === "codex-app-server") {
|
|
295
|
+
return [
|
|
296
|
+
"runtime:",
|
|
297
|
+
" kind: codex-app-server",
|
|
298
|
+
" command: codex",
|
|
299
|
+
" args:",
|
|
300
|
+
" - app-server",
|
|
301
|
+
" isolation:",
|
|
302
|
+
" bare: false",
|
|
303
|
+
" strict_mcp_config: false",
|
|
304
|
+
" timeouts:",
|
|
305
|
+
" read_timeout_ms: 5000",
|
|
306
|
+
" turn_timeout_ms: 3600000",
|
|
307
|
+
" stall_timeout_ms: 300000"
|
|
308
|
+
];
|
|
309
|
+
}
|
|
310
|
+
if (normalized === "claude-print") {
|
|
311
|
+
return [
|
|
312
|
+
"runtime:",
|
|
313
|
+
" kind: claude-print",
|
|
314
|
+
" command: claude",
|
|
315
|
+
" args:",
|
|
316
|
+
...DEFAULT_CLAUDE_PRINT_ARGS.map((arg) => ` - ${arg}`),
|
|
317
|
+
" isolation:",
|
|
318
|
+
" bare: false",
|
|
319
|
+
" strict_mcp_config: false",
|
|
320
|
+
" auth:",
|
|
321
|
+
" env: ANTHROPIC_API_KEY",
|
|
322
|
+
" timeouts:",
|
|
323
|
+
" read_timeout_ms: 5000",
|
|
324
|
+
" turn_timeout_ms: 3600000",
|
|
325
|
+
" stall_timeout_ms: 900000"
|
|
326
|
+
];
|
|
327
|
+
}
|
|
328
|
+
return [
|
|
329
|
+
"runtime:",
|
|
330
|
+
" kind: custom",
|
|
331
|
+
` command: ${runtime}`,
|
|
332
|
+
" isolation:",
|
|
333
|
+
" bare: false",
|
|
334
|
+
" strict_mcp_config: false",
|
|
335
|
+
" timeouts:",
|
|
336
|
+
" read_timeout_ms: 5000",
|
|
337
|
+
" turn_timeout_ms: 3600000",
|
|
338
|
+
" stall_timeout_ms: 300000"
|
|
339
|
+
];
|
|
340
|
+
}
|
|
341
|
+
|
|
102
342
|
// src/workflow/generate-workflow-md.ts
|
|
103
343
|
function generateWorkflowMarkdown(input) {
|
|
104
344
|
const frontMatter = buildFrontMatter(input);
|
|
105
|
-
const promptBody = buildPromptBody(input
|
|
345
|
+
const promptBody = buildPromptBody(input);
|
|
106
346
|
return `---
|
|
107
347
|
${frontMatter}---
|
|
108
348
|
${promptBody}
|
|
@@ -114,6 +354,9 @@ function buildFrontMatter(input) {
|
|
|
114
354
|
lines.push(" kind: github-project");
|
|
115
355
|
lines.push(` project_id: ${input.projectId}`);
|
|
116
356
|
lines.push(` state_field: ${input.stateFieldName}`);
|
|
357
|
+
if (input.priorityFieldName) {
|
|
358
|
+
lines.push(` priority_field: ${input.priorityFieldName}`);
|
|
359
|
+
}
|
|
117
360
|
if (input.lifecycle.activeStates.length > 0) {
|
|
118
361
|
lines.push(" active_states:");
|
|
119
362
|
for (const state of input.lifecycle.activeStates) {
|
|
@@ -132,36 +375,26 @@ function buildFrontMatter(input) {
|
|
|
132
375
|
lines.push(` - ${state}`);
|
|
133
376
|
}
|
|
134
377
|
}
|
|
135
|
-
const agentCommand = resolveAgentCommand(input.runtime);
|
|
136
378
|
lines.push("polling:");
|
|
137
379
|
lines.push(` interval_ms: ${input.pollIntervalMs ?? 3e4}`);
|
|
138
380
|
lines.push("workspace:");
|
|
139
381
|
lines.push(" root: .runtime/symphony-workspaces");
|
|
140
382
|
lines.push("hooks:");
|
|
141
|
-
lines.push(
|
|
383
|
+
lines.push(` after_create: ${DEFAULT_AFTER_CREATE_HOOK_PATH}`);
|
|
142
384
|
lines.push("agent:");
|
|
143
385
|
lines.push(" max_concurrent_agents: 10");
|
|
144
386
|
lines.push(" max_retry_backoff_ms: 30000");
|
|
145
387
|
lines.push(" retry_base_delay_ms: 10000");
|
|
146
|
-
lines.push(
|
|
147
|
-
lines.push(` command: ${agentCommand}`);
|
|
148
|
-
lines.push(" read_timeout_ms: 5000");
|
|
149
|
-
lines.push(" turn_timeout_ms: 3600000");
|
|
388
|
+
lines.push(...buildRuntimeFrontMatter(input.runtime));
|
|
150
389
|
return lines.join("\n") + "\n";
|
|
151
390
|
}
|
|
152
|
-
function
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return runtime;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
function buildPromptBody(mappings) {
|
|
163
|
-
const statusMap = generateStatusMapWithDescriptions(mappings);
|
|
164
|
-
const template = `${statusMap}
|
|
391
|
+
function buildPromptBody(input) {
|
|
392
|
+
const statusMap = generateStatusMapWithDescriptions(input.mappings);
|
|
393
|
+
const validationGuidance = buildRepositoryValidationGuidance(
|
|
394
|
+
input.detectedEnvironment
|
|
395
|
+
);
|
|
396
|
+
const runtimePreamble = buildRuntimePromptPreamble(input.runtime);
|
|
397
|
+
const templateBody = `${statusMap}
|
|
165
398
|
|
|
166
399
|
## Agent Instructions
|
|
167
400
|
|
|
@@ -180,6 +413,10 @@ You are an AI coding agent working on issue {{issue.identifier}}: "{{issue.title
|
|
|
180
413
|
2. Only abort early if there is a genuine blocker (missing required credentials or secrets).
|
|
181
414
|
3. In your final message, report only what was completed and any blockers. Do not include "next steps".
|
|
182
415
|
|
|
416
|
+
### Repository Validation Guidance
|
|
417
|
+
|
|
418
|
+
${validationGuidance.map((line, index) => `${index + 1}. ${line}`).join("\n")}
|
|
419
|
+
|
|
183
420
|
### Workflow
|
|
184
421
|
|
|
185
422
|
1. Read the issue description and understand the requirements.
|
|
@@ -218,7 +455,10 @@ Create a workpad comment on the issue with the following structure to track prog
|
|
|
218
455
|
|
|
219
456
|
- Progress notes
|
|
220
457
|
\`\`\``;
|
|
221
|
-
return
|
|
458
|
+
return [runtimePreamble, templateBody].filter(Boolean).join("\n\n");
|
|
459
|
+
}
|
|
460
|
+
function buildRuntimePromptPreamble(runtime) {
|
|
461
|
+
return isClaudeRuntime(runtime) ? CLAUDE_RUNTIME_PROMPT_PREAMBLE : "";
|
|
222
462
|
}
|
|
223
463
|
function generateStatusMapWithDescriptions(mappings) {
|
|
224
464
|
const roleDescriptions = {
|
|
@@ -237,7 +477,7 @@ function generateStatusMapWithDescriptions(mappings) {
|
|
|
237
477
|
}
|
|
238
478
|
|
|
239
479
|
// src/detection/environment-detector.ts
|
|
240
|
-
import { access, readFile } from "fs/promises";
|
|
480
|
+
import { access, readFile, readdir } from "fs/promises";
|
|
241
481
|
import { join } from "path";
|
|
242
482
|
function isFileMissing(error) {
|
|
243
483
|
return Boolean(
|
|
@@ -269,13 +509,25 @@ async function readJsonFile(path) {
|
|
|
269
509
|
throw error;
|
|
270
510
|
}
|
|
271
511
|
}
|
|
512
|
+
async function readTextFile(path) {
|
|
513
|
+
try {
|
|
514
|
+
return await readFile(path, "utf8");
|
|
515
|
+
} catch (error) {
|
|
516
|
+
if (isFileMissing(error)) {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
throw error;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
272
522
|
async function detectPackageManager(cwd) {
|
|
273
523
|
const lockfiles = [
|
|
274
524
|
{ name: "pnpm-lock.yaml", manager: "pnpm" },
|
|
275
525
|
{ name: "bun.lock", manager: "bun" },
|
|
276
526
|
{ name: "bun.lockb", manager: "bun" },
|
|
277
527
|
{ name: "yarn.lock", manager: "yarn" },
|
|
278
|
-
{ name: "package-lock.json", manager: "npm" }
|
|
528
|
+
{ name: "package-lock.json", manager: "npm" },
|
|
529
|
+
{ name: "uv.lock", manager: "uv" },
|
|
530
|
+
{ name: "poetry.lock", manager: "poetry" }
|
|
279
531
|
];
|
|
280
532
|
for (const { name, manager } of lockfiles) {
|
|
281
533
|
const exists = await fileExists(join(cwd, name));
|
|
@@ -285,7 +537,32 @@ async function detectPackageManager(cwd) {
|
|
|
285
537
|
}
|
|
286
538
|
return { packageManager: null, lockfile: null };
|
|
287
539
|
}
|
|
288
|
-
|
|
540
|
+
function normalizeCommand2(command) {
|
|
541
|
+
return command.replace(/\s+/g, " ").trim();
|
|
542
|
+
}
|
|
543
|
+
function addCandidate(candidates, label, command, priority) {
|
|
544
|
+
if (!command) {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
candidates[label].push({
|
|
548
|
+
command: normalizeCommand2(command),
|
|
549
|
+
priority
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
function resolveCommand(candidates, label) {
|
|
553
|
+
const entries = candidates[label];
|
|
554
|
+
if (entries.length === 0) {
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
const highestPriority = Math.max(...entries.map((entry) => entry.priority));
|
|
558
|
+
const highestPriorityCommands = [
|
|
559
|
+
...new Set(
|
|
560
|
+
entries.filter((entry) => entry.priority === highestPriority).map((entry) => entry.command)
|
|
561
|
+
)
|
|
562
|
+
];
|
|
563
|
+
return highestPriorityCommands.length === 1 ? highestPriorityCommands[0] : null;
|
|
564
|
+
}
|
|
565
|
+
async function detectNodeScripts(cwd) {
|
|
289
566
|
const packageJson = await readJsonFile(join(cwd, "package.json"));
|
|
290
567
|
if (!packageJson?.scripts) {
|
|
291
568
|
return { testCommand: null, buildCommand: null, lintCommand: null };
|
|
@@ -296,6 +573,117 @@ async function detectScripts(cwd) {
|
|
|
296
573
|
lintCommand: packageJson.scripts.lint ?? null
|
|
297
574
|
};
|
|
298
575
|
}
|
|
576
|
+
async function detectMakeCommands(cwd) {
|
|
577
|
+
const makefile = await readTextFile(join(cwd, "Makefile")) ?? await readTextFile(join(cwd, "makefile"));
|
|
578
|
+
if (!makefile) {
|
|
579
|
+
return { testCommand: null, buildCommand: null, lintCommand: null };
|
|
580
|
+
}
|
|
581
|
+
const hasTarget = (target) => new RegExp(`^${target}\\s*::?(?:\\s|$)`, "m").test(makefile);
|
|
582
|
+
return {
|
|
583
|
+
testCommand: hasTarget("test") ? "make test" : null,
|
|
584
|
+
lintCommand: hasTarget("lint") ? "make lint" : null,
|
|
585
|
+
buildCommand: hasTarget("build") ? "make build" : null
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
async function detectJustCommands(cwd) {
|
|
589
|
+
const justfile = await readTextFile(join(cwd, "justfile")) ?? await readTextFile(join(cwd, ".justfile"));
|
|
590
|
+
if (!justfile) {
|
|
591
|
+
return { testCommand: null, buildCommand: null, lintCommand: null };
|
|
592
|
+
}
|
|
593
|
+
const hasRecipe = (name) => new RegExp(`^${name}\\s*:(?!=)`, "m").test(justfile);
|
|
594
|
+
return {
|
|
595
|
+
testCommand: hasRecipe("test") ? "just test" : null,
|
|
596
|
+
lintCommand: hasRecipe("lint") ? "just lint" : null,
|
|
597
|
+
buildCommand: hasRecipe("build") ? "just build" : null
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
async function hasRequirementsFile(cwd) {
|
|
601
|
+
try {
|
|
602
|
+
const entries = await readdir(cwd);
|
|
603
|
+
return entries.some((entry) => /^requirements[^/]*\.txt$/i.test(entry));
|
|
604
|
+
} catch (error) {
|
|
605
|
+
if (isFileMissing(error)) {
|
|
606
|
+
return false;
|
|
607
|
+
}
|
|
608
|
+
throw error;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
async function detectPythonCommands(cwd) {
|
|
612
|
+
const [
|
|
613
|
+
pyproject,
|
|
614
|
+
hasUvLock,
|
|
615
|
+
hasPoetryLock,
|
|
616
|
+
hasPytestIni,
|
|
617
|
+
hasToxIni,
|
|
618
|
+
hasRequirements
|
|
619
|
+
] = await Promise.all([
|
|
620
|
+
readTextFile(join(cwd, "pyproject.toml")),
|
|
621
|
+
fileExists(join(cwd, "uv.lock")),
|
|
622
|
+
fileExists(join(cwd, "poetry.lock")),
|
|
623
|
+
fileExists(join(cwd, "pytest.ini")),
|
|
624
|
+
fileExists(join(cwd, "tox.ini")),
|
|
625
|
+
hasRequirementsFile(cwd)
|
|
626
|
+
]);
|
|
627
|
+
const hasPythonSignals = pyproject !== null || hasUvLock || hasPoetryLock || hasPytestIni || hasToxIni || hasRequirements;
|
|
628
|
+
if (!hasPythonSignals) {
|
|
629
|
+
return { testCommand: null, buildCommand: null, lintCommand: null };
|
|
630
|
+
}
|
|
631
|
+
const hasPytestConfig = hasPytestIni || /\[tool\.pytest(?:\.ini_options)?\]/.test(pyproject ?? "");
|
|
632
|
+
if (!hasPytestConfig) {
|
|
633
|
+
return { testCommand: null, buildCommand: null, lintCommand: null };
|
|
634
|
+
}
|
|
635
|
+
const testCommand = hasUvLock ? "uv run pytest" : hasPoetryLock ? "poetry run pytest" : "pytest";
|
|
636
|
+
return { testCommand, buildCommand: null, lintCommand: null };
|
|
637
|
+
}
|
|
638
|
+
async function detectGoCommands(cwd) {
|
|
639
|
+
const hasGoMod = await fileExists(join(cwd, "go.mod"));
|
|
640
|
+
return {
|
|
641
|
+
testCommand: hasGoMod ? "go test ./..." : null,
|
|
642
|
+
buildCommand: null,
|
|
643
|
+
lintCommand: null
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
async function detectRustCommands(cwd) {
|
|
647
|
+
const hasCargoToml = await fileExists(join(cwd, "Cargo.toml"));
|
|
648
|
+
return {
|
|
649
|
+
testCommand: hasCargoToml ? "cargo test" : null,
|
|
650
|
+
buildCommand: null,
|
|
651
|
+
lintCommand: null
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
async function detectValidationCommands(cwd) {
|
|
655
|
+
const [makeCommands, justCommands, nodeCommands, pythonCommands, goCommands, rustCommands] = await Promise.all([
|
|
656
|
+
detectMakeCommands(cwd),
|
|
657
|
+
detectJustCommands(cwd),
|
|
658
|
+
detectNodeScripts(cwd),
|
|
659
|
+
detectPythonCommands(cwd),
|
|
660
|
+
detectGoCommands(cwd),
|
|
661
|
+
detectRustCommands(cwd)
|
|
662
|
+
]);
|
|
663
|
+
const candidates = {
|
|
664
|
+
testCommand: [],
|
|
665
|
+
lintCommand: [],
|
|
666
|
+
buildCommand: []
|
|
667
|
+
};
|
|
668
|
+
for (const commandSet of [makeCommands, justCommands]) {
|
|
669
|
+
addCandidate(candidates, "testCommand", commandSet.testCommand, 3);
|
|
670
|
+
addCandidate(candidates, "lintCommand", commandSet.lintCommand, 3);
|
|
671
|
+
addCandidate(candidates, "buildCommand", commandSet.buildCommand, 3);
|
|
672
|
+
}
|
|
673
|
+
addCandidate(candidates, "testCommand", nodeCommands.testCommand, 2);
|
|
674
|
+
addCandidate(candidates, "lintCommand", nodeCommands.lintCommand, 2);
|
|
675
|
+
addCandidate(candidates, "buildCommand", nodeCommands.buildCommand, 2);
|
|
676
|
+
for (const commandSet of [pythonCommands, goCommands, rustCommands]) {
|
|
677
|
+
addCandidate(candidates, "testCommand", commandSet.testCommand, 1);
|
|
678
|
+
addCandidate(candidates, "lintCommand", commandSet.lintCommand, 1);
|
|
679
|
+
addCandidate(candidates, "buildCommand", commandSet.buildCommand, 1);
|
|
680
|
+
}
|
|
681
|
+
return {
|
|
682
|
+
testCommand: resolveCommand(candidates, "testCommand"),
|
|
683
|
+
lintCommand: resolveCommand(candidates, "lintCommand"),
|
|
684
|
+
buildCommand: resolveCommand(candidates, "buildCommand")
|
|
685
|
+
};
|
|
686
|
+
}
|
|
299
687
|
async function detectCiPlatform(cwd) {
|
|
300
688
|
const workflowsDir = join(cwd, ".github", "workflows");
|
|
301
689
|
const exists = await fileExists(workflowsDir);
|
|
@@ -310,10 +698,18 @@ async function detectMonorepo(cwd) {
|
|
|
310
698
|
if (hasLerna) {
|
|
311
699
|
return true;
|
|
312
700
|
}
|
|
701
|
+
const hasGoWorkspace = await fileExists(join(cwd, "go.work"));
|
|
702
|
+
if (hasGoWorkspace) {
|
|
703
|
+
return true;
|
|
704
|
+
}
|
|
313
705
|
const packageJson = await readJsonFile(join(cwd, "package.json"));
|
|
314
706
|
if (packageJson?.workspaces) {
|
|
315
707
|
return true;
|
|
316
708
|
}
|
|
709
|
+
const cargoToml = await readTextFile(join(cwd, "Cargo.toml"));
|
|
710
|
+
if (cargoToml?.includes("[workspace]")) {
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
317
713
|
return false;
|
|
318
714
|
}
|
|
319
715
|
async function detectExistingSkills(cwd) {
|
|
@@ -357,7 +753,7 @@ async function detectEnvironment(cwd) {
|
|
|
357
753
|
existingSkills
|
|
358
754
|
] = await Promise.all([
|
|
359
755
|
detectPackageManager(cwd),
|
|
360
|
-
|
|
756
|
+
detectValidationCommands(cwd),
|
|
361
757
|
detectCiPlatform(cwd),
|
|
362
758
|
detectMonorepo(cwd),
|
|
363
759
|
detectExistingSkills(cwd)
|
|
@@ -528,6 +924,11 @@ function generateReferenceWorkflow(input) {
|
|
|
528
924
|
lines.push(" kind: github-project");
|
|
529
925
|
lines.push(` project_id: ${input.projectId}`);
|
|
530
926
|
lines.push(" state_field: Status");
|
|
927
|
+
if (input.priorityFieldName) {
|
|
928
|
+
lines.push(` priority_field: ${input.priorityFieldName}`);
|
|
929
|
+
} else {
|
|
930
|
+
lines.push(" # priority_field: Priority");
|
|
931
|
+
}
|
|
531
932
|
lines.push("");
|
|
532
933
|
const activeColumns = input.statusColumns.filter((c) => c.role === "active");
|
|
533
934
|
const waitColumns = input.statusColumns.filter((c) => c.role === "wait");
|
|
@@ -558,8 +959,6 @@ function generateReferenceWorkflow(input) {
|
|
|
558
959
|
lines.push(" blocker_check_states: [{first active state}]");
|
|
559
960
|
}
|
|
560
961
|
lines.push("");
|
|
561
|
-
const agentCommand = resolveAgentCommand2(input.runtime);
|
|
562
|
-
const hookComment = resolveHookComment(input.runtime);
|
|
563
962
|
lines.push("polling:");
|
|
564
963
|
lines.push(" interval_ms: 30000");
|
|
565
964
|
lines.push("");
|
|
@@ -567,7 +966,9 @@ function generateReferenceWorkflow(input) {
|
|
|
567
966
|
lines.push(" root: .runtime/symphony-workspaces");
|
|
568
967
|
lines.push("");
|
|
569
968
|
lines.push("hooks:");
|
|
570
|
-
lines.push(
|
|
969
|
+
lines.push(
|
|
970
|
+
` after_create: ${DEFAULT_AFTER_CREATE_HOOK_PATH} # ${DEFAULT_AFTER_CREATE_HOOK_COMMENT}`
|
|
971
|
+
);
|
|
571
972
|
lines.push(" before_run: null");
|
|
572
973
|
lines.push(" after_run: null");
|
|
573
974
|
lines.push(" before_remove: null");
|
|
@@ -579,11 +980,7 @@ function generateReferenceWorkflow(input) {
|
|
|
579
980
|
lines.push(" retry_base_delay_ms: 10000");
|
|
580
981
|
lines.push(" max_turns: 20");
|
|
581
982
|
lines.push("");
|
|
582
|
-
lines.push(
|
|
583
|
-
lines.push(` command: ${agentCommand}`);
|
|
584
|
-
lines.push(" read_timeout_ms: 5000");
|
|
585
|
-
lines.push(" turn_timeout_ms: 3600000");
|
|
586
|
-
lines.push(" stall_timeout_ms: 300000");
|
|
983
|
+
lines.push(...buildRuntimeFrontMatter(input.runtime));
|
|
587
984
|
lines.push("");
|
|
588
985
|
lines.push("---");
|
|
589
986
|
lines.push("");
|
|
@@ -611,6 +1008,14 @@ function generateReferenceWorkflow(input) {
|
|
|
611
1008
|
}
|
|
612
1009
|
}
|
|
613
1010
|
lines.push("");
|
|
1011
|
+
lines.push("## Repository Validation Guidance");
|
|
1012
|
+
lines.push("");
|
|
1013
|
+
for (const line of buildRepositoryValidationGuidance(
|
|
1014
|
+
input.detectedEnvironment
|
|
1015
|
+
)) {
|
|
1016
|
+
lines.push(`- ${line}`);
|
|
1017
|
+
}
|
|
1018
|
+
lines.push("");
|
|
614
1019
|
lines.push("## Default Posture");
|
|
615
1020
|
lines.push("");
|
|
616
1021
|
lines.push(
|
|
@@ -812,26 +1217,6 @@ function generateReferenceWorkflow(input) {
|
|
|
812
1217
|
lines.push("");
|
|
813
1218
|
return lines.join("\n");
|
|
814
1219
|
}
|
|
815
|
-
function resolveAgentCommand2(runtime) {
|
|
816
|
-
switch (runtime) {
|
|
817
|
-
case "codex":
|
|
818
|
-
return "codex app-server";
|
|
819
|
-
case "claude-code":
|
|
820
|
-
return "claude-code";
|
|
821
|
-
default:
|
|
822
|
-
return runtime;
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
function resolveHookComment(runtime) {
|
|
826
|
-
switch (runtime) {
|
|
827
|
-
case "codex":
|
|
828
|
-
return "npm/yarn/pnpm install script";
|
|
829
|
-
case "claude-code":
|
|
830
|
-
return "npm/yarn/pnpm install script";
|
|
831
|
-
default:
|
|
832
|
-
return "package-manager-specific script";
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
1220
|
function resolveRoleAction(role) {
|
|
836
1221
|
switch (role) {
|
|
837
1222
|
case "active":
|
|
@@ -849,10 +1234,10 @@ function resolveRoleAction(role) {
|
|
|
849
1234
|
import { mkdir as mkdir2, readFile as readFile2, rename, writeFile as writeFile2 } from "fs/promises";
|
|
850
1235
|
import { join as join2 } from "path";
|
|
851
1236
|
function normalizeRuntimeForSkills(runtime) {
|
|
852
|
-
if (runtime
|
|
1237
|
+
if (isClaudeRuntime(runtime)) {
|
|
853
1238
|
return "claude-code";
|
|
854
1239
|
}
|
|
855
|
-
if (runtime
|
|
1240
|
+
if (isCodexRuntime(runtime)) {
|
|
856
1241
|
return "codex";
|
|
857
1242
|
}
|
|
858
1243
|
return null;
|
|
@@ -921,6 +1306,17 @@ function generateGhSymphonySkill(ctx) {
|
|
|
921
1306
|
);
|
|
922
1307
|
lines.push("- `gh` CLI must be authenticated");
|
|
923
1308
|
lines.push("");
|
|
1309
|
+
lines.push("## Repository Validation Guidance");
|
|
1310
|
+
lines.push("");
|
|
1311
|
+
lines.push(
|
|
1312
|
+
"Carry the detected repository validation posture into any new or refined `WORKFLOW.md` instead of falling back to generic instructions."
|
|
1313
|
+
);
|
|
1314
|
+
for (const line of buildRepositoryValidationGuidance(
|
|
1315
|
+
ctx.detectedEnvironment
|
|
1316
|
+
)) {
|
|
1317
|
+
lines.push(`- ${line}`);
|
|
1318
|
+
}
|
|
1319
|
+
lines.push("");
|
|
924
1320
|
lines.push("## Mode Detection");
|
|
925
1321
|
lines.push("");
|
|
926
1322
|
lines.push("Check if `WORKFLOW.md` exists in the current directory:");
|
|
@@ -1406,6 +1802,15 @@ Then re-run: ${retryCommand}`,
|
|
|
1406
1802
|
"Fix missing scope"
|
|
1407
1803
|
);
|
|
1408
1804
|
}
|
|
1805
|
+
function warnIfProjectDiscoveryPartial(result) {
|
|
1806
|
+
if (!result.partial) {
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
const limitDetail = result.reason === "result_limit" ? "the discovered project count reached the safety cap" : "the GitHub API request budget reached the safety cap";
|
|
1810
|
+
p.log.warn(
|
|
1811
|
+
`Project discovery may be incomplete: ${limitDetail}. Showing ${result.projects.length} discovered project${result.projects.length === 1 ? "" : "s"} after ${result.requests} request${result.requests === 1 ? "" : "s"}.`
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1409
1814
|
async function abortIfCancelled(input) {
|
|
1410
1815
|
const result = await input;
|
|
1411
1816
|
if (p.isCancel(result)) {
|
|
@@ -1418,6 +1823,7 @@ function parseInitFlags(args) {
|
|
|
1418
1823
|
const flags = {
|
|
1419
1824
|
dryRun: false,
|
|
1420
1825
|
nonInteractive: false,
|
|
1826
|
+
runtime: "codex",
|
|
1421
1827
|
skipSkills: false,
|
|
1422
1828
|
skipContext: false
|
|
1423
1829
|
};
|
|
@@ -1439,6 +1845,10 @@ function parseInitFlags(args) {
|
|
|
1439
1845
|
flags.output = next;
|
|
1440
1846
|
i += 1;
|
|
1441
1847
|
break;
|
|
1848
|
+
case "--runtime":
|
|
1849
|
+
flags.runtime = next ?? "";
|
|
1850
|
+
i += 1;
|
|
1851
|
+
break;
|
|
1442
1852
|
case "--skip-skills":
|
|
1443
1853
|
flags.skipSkills = true;
|
|
1444
1854
|
break;
|
|
@@ -1449,6 +1859,25 @@ function parseInitFlags(args) {
|
|
|
1449
1859
|
}
|
|
1450
1860
|
return flags;
|
|
1451
1861
|
}
|
|
1862
|
+
async function runInitRuntimePreflight(runtime) {
|
|
1863
|
+
if (!isClaudeRuntime(runtime)) {
|
|
1864
|
+
return true;
|
|
1865
|
+
}
|
|
1866
|
+
const hasGitHubGraphqlToken = typeof process.env.GITHUB_GRAPHQL_TOKEN === "string" && process.env.GITHUB_GRAPHQL_TOKEN.trim().length > 0;
|
|
1867
|
+
const report = await runClaudePreflight({
|
|
1868
|
+
cwd: process.cwd(),
|
|
1869
|
+
env: process.env,
|
|
1870
|
+
command: resolveClaudeCommandBinary(resolveRuntimeCommand(runtime)) ?? resolveRuntimeCommand(runtime),
|
|
1871
|
+
includeGhAuth: !hasGitHubGraphqlToken
|
|
1872
|
+
});
|
|
1873
|
+
const message = formatClaudePreflightText(report);
|
|
1874
|
+
if (report.ok) {
|
|
1875
|
+
p.log.info(message);
|
|
1876
|
+
return true;
|
|
1877
|
+
}
|
|
1878
|
+
p.log.error(message);
|
|
1879
|
+
return false;
|
|
1880
|
+
}
|
|
1452
1881
|
var handler = async (args, options) => {
|
|
1453
1882
|
const flags = parseInitFlags(args);
|
|
1454
1883
|
if (flags.nonInteractive) {
|
|
@@ -1458,6 +1887,71 @@ var handler = async (args, options) => {
|
|
|
1458
1887
|
await runInteractive(flags, options);
|
|
1459
1888
|
};
|
|
1460
1889
|
var init_default = handler;
|
|
1890
|
+
function resolveInitRuntime(runtime) {
|
|
1891
|
+
return normalizeInitRuntime(runtime ?? "codex-app-server");
|
|
1892
|
+
}
|
|
1893
|
+
function validateInitRuntime(runtime) {
|
|
1894
|
+
if (isSupportedInitRuntime(runtime)) {
|
|
1895
|
+
return null;
|
|
1896
|
+
}
|
|
1897
|
+
return `Unsupported runtime '${runtime}'. Choose one of: codex-app-server, claude-print.`;
|
|
1898
|
+
}
|
|
1899
|
+
async function promptRuntimeSelection() {
|
|
1900
|
+
return abortIfCancelled(
|
|
1901
|
+
p.select({
|
|
1902
|
+
message: "Step 1/3 \u2014 Select the agent runtime:",
|
|
1903
|
+
options: [
|
|
1904
|
+
{
|
|
1905
|
+
value: "codex-app-server",
|
|
1906
|
+
label: "codex-app-server",
|
|
1907
|
+
hint: "Codex app-server JSON-RPC runtime"
|
|
1908
|
+
},
|
|
1909
|
+
{
|
|
1910
|
+
value: "claude-print",
|
|
1911
|
+
label: "claude-print",
|
|
1912
|
+
hint: "Claude Code non-interactive stream-json runtime"
|
|
1913
|
+
}
|
|
1914
|
+
]
|
|
1915
|
+
})
|
|
1916
|
+
);
|
|
1917
|
+
}
|
|
1918
|
+
function commandRuns(binary, args) {
|
|
1919
|
+
const result = spawnSync(binary, args, {
|
|
1920
|
+
encoding: "utf8",
|
|
1921
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1922
|
+
timeout: 3e3
|
|
1923
|
+
});
|
|
1924
|
+
return result.error === void 0 && result.status === 0;
|
|
1925
|
+
}
|
|
1926
|
+
function runRuntimePreflight(runtime) {
|
|
1927
|
+
const command = resolveRuntimeCommand(runtime);
|
|
1928
|
+
const checks = [];
|
|
1929
|
+
const versionOk = commandRuns(command, ["--version"]);
|
|
1930
|
+
checks.push({
|
|
1931
|
+
label: "Runtime binary",
|
|
1932
|
+
ok: versionOk,
|
|
1933
|
+
detail: versionOk ? `${command} is available on PATH.` : `${command} was not found or did not run with --version.`
|
|
1934
|
+
});
|
|
1935
|
+
if (isClaudeRuntime(runtime)) {
|
|
1936
|
+
const hasAnthropicKey = Boolean(process.env.ANTHROPIC_API_KEY);
|
|
1937
|
+
checks.push({
|
|
1938
|
+
label: "Claude auth",
|
|
1939
|
+
ok: hasAnthropicKey,
|
|
1940
|
+
detail: hasAnthropicKey ? "ANTHROPIC_API_KEY is set." : "ANTHROPIC_API_KEY is not set. Set it before running Claude workers."
|
|
1941
|
+
});
|
|
1942
|
+
}
|
|
1943
|
+
const okIcon = "OK";
|
|
1944
|
+
const failIcon = "FAIL";
|
|
1945
|
+
const lines = checks.map(
|
|
1946
|
+
(check) => `${check.ok ? okIcon : failIcon} ${check.label.padEnd(16)} ${check.detail}`
|
|
1947
|
+
);
|
|
1948
|
+
p.note(lines.join("\n"), `Runtime preflight \u2014 ${runtime}`);
|
|
1949
|
+
if (checks.some((check) => !check.ok)) {
|
|
1950
|
+
p.log.warn(
|
|
1951
|
+
"Runtime preflight found missing local prerequisites. Generated files still include the selected runtime defaults."
|
|
1952
|
+
);
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1461
1955
|
async function resolveChangeStatus(path, content, mode) {
|
|
1462
1956
|
try {
|
|
1463
1957
|
const existing = await readFile3(path, "utf8");
|
|
@@ -1487,6 +1981,9 @@ async function writePlannedFile(file) {
|
|
|
1487
1981
|
const temporaryPath = `${file.path}.tmp`;
|
|
1488
1982
|
await writeFile3(temporaryPath, file.content, "utf8");
|
|
1489
1983
|
await rename2(temporaryPath, file.path);
|
|
1984
|
+
if (file.executable) {
|
|
1985
|
+
await chmod(file.path, 493);
|
|
1986
|
+
}
|
|
1490
1987
|
return true;
|
|
1491
1988
|
}
|
|
1492
1989
|
function resolveStatusField(projectDetail) {
|
|
@@ -1494,13 +1991,68 @@ function resolveStatusField(projectDetail) {
|
|
|
1494
1991
|
}
|
|
1495
1992
|
function buildAutomaticStateMappings(statusField) {
|
|
1496
1993
|
const mappings = {};
|
|
1497
|
-
for (const mapping of inferAllStateRoles(
|
|
1994
|
+
for (const mapping of inferAllStateRoles(
|
|
1995
|
+
statusField.options.map((o) => o.name)
|
|
1996
|
+
)) {
|
|
1498
1997
|
if (mapping.role) {
|
|
1499
1998
|
mappings[mapping.columnName] = { role: mapping.role };
|
|
1500
1999
|
}
|
|
1501
2000
|
}
|
|
1502
2001
|
return mappings;
|
|
1503
2002
|
}
|
|
2003
|
+
function isPriorityFieldCandidateName(fieldName) {
|
|
2004
|
+
return /\bpriority\b/i.test(fieldName.trim());
|
|
2005
|
+
}
|
|
2006
|
+
function resolvePriorityField(projectDetail, statusField) {
|
|
2007
|
+
const singleSelectFields = projectDetail.statusFields.filter(
|
|
2008
|
+
(field) => field.id !== statusField.id
|
|
2009
|
+
);
|
|
2010
|
+
const exactMatches = singleSelectFields.filter(
|
|
2011
|
+
(field) => field.name.trim().toLowerCase() === "priority"
|
|
2012
|
+
);
|
|
2013
|
+
if (exactMatches.length === 1) {
|
|
2014
|
+
return { field: exactMatches[0], ambiguous: [] };
|
|
2015
|
+
}
|
|
2016
|
+
if (exactMatches.length > 1) {
|
|
2017
|
+
return { field: null, ambiguous: exactMatches };
|
|
2018
|
+
}
|
|
2019
|
+
const likelyMatches = singleSelectFields.filter(
|
|
2020
|
+
(field) => isPriorityFieldCandidateName(field.name)
|
|
2021
|
+
);
|
|
2022
|
+
if (likelyMatches.length === 1) {
|
|
2023
|
+
return { field: likelyMatches[0], ambiguous: [] };
|
|
2024
|
+
}
|
|
2025
|
+
if (likelyMatches.length > 1) {
|
|
2026
|
+
return { field: null, ambiguous: likelyMatches };
|
|
2027
|
+
}
|
|
2028
|
+
return { field: null, ambiguous: [] };
|
|
2029
|
+
}
|
|
2030
|
+
async function promptPriorityField(priorityCandidates, options) {
|
|
2031
|
+
if (priorityCandidates.length === 0) {
|
|
2032
|
+
return null;
|
|
2033
|
+
}
|
|
2034
|
+
const selectedFieldId = await abortIfCancelled(
|
|
2035
|
+
p.select({
|
|
2036
|
+
message: `${options?.stepLabel ?? "Priority field"} \u2014 Multiple GitHub Project priority fields look plausible. Select the one Symphony should use:`,
|
|
2037
|
+
options: [
|
|
2038
|
+
...priorityCandidates.map((field) => ({
|
|
2039
|
+
value: field.id,
|
|
2040
|
+
label: field.name,
|
|
2041
|
+
hint: `${field.options.length} option${field.options.length === 1 ? "" : "s"}`
|
|
2042
|
+
})),
|
|
2043
|
+
{
|
|
2044
|
+
value: "__skip_priority_field__",
|
|
2045
|
+
label: "Skip priority-aware dispatch",
|
|
2046
|
+
hint: "Leave tracker.priority_field unset"
|
|
2047
|
+
}
|
|
2048
|
+
]
|
|
2049
|
+
})
|
|
2050
|
+
);
|
|
2051
|
+
if (selectedFieldId === "__skip_priority_field__") {
|
|
2052
|
+
return null;
|
|
2053
|
+
}
|
|
2054
|
+
return priorityCandidates.find((field) => field.id === selectedFieldId) ?? null;
|
|
2055
|
+
}
|
|
1504
2056
|
async function promptStateMappings(statusField, options) {
|
|
1505
2057
|
const mappings = {};
|
|
1506
2058
|
const inferred = inferAllStateRoles(statusField.options.map((o) => o.name));
|
|
@@ -1529,12 +2081,15 @@ async function promptStateMappings(statusField, options) {
|
|
|
1529
2081
|
return mappings;
|
|
1530
2082
|
}
|
|
1531
2083
|
async function planWorkflowArtifacts(opts) {
|
|
2084
|
+
const environment = opts.environment ?? await detectEnvironment(opts.cwd);
|
|
1532
2085
|
const workflowMd = generateWorkflowMarkdown({
|
|
1533
2086
|
projectId: opts.projectDetail.id,
|
|
1534
2087
|
stateFieldName: opts.statusField.name,
|
|
2088
|
+
priorityFieldName: opts.priorityField?.name ?? null,
|
|
1535
2089
|
mappings: opts.mappings,
|
|
1536
2090
|
lifecycle: toWorkflowLifecycleConfig(opts.statusField.name, opts.mappings),
|
|
1537
|
-
runtime: opts.runtime
|
|
2091
|
+
runtime: opts.runtime,
|
|
2092
|
+
detectedEnvironment: environment
|
|
1538
2093
|
});
|
|
1539
2094
|
const workflowPlan = await planFileChange({
|
|
1540
2095
|
path: opts.outputPath,
|
|
@@ -1546,9 +2101,11 @@ async function planWorkflowArtifacts(opts) {
|
|
|
1546
2101
|
cwd: opts.cwd,
|
|
1547
2102
|
projectDetail: opts.projectDetail,
|
|
1548
2103
|
statusField: opts.statusField,
|
|
2104
|
+
priorityField: opts.priorityField,
|
|
1549
2105
|
runtime: opts.runtime,
|
|
1550
2106
|
skipSkills: opts.skipSkills,
|
|
1551
|
-
skipContext: opts.skipContext
|
|
2107
|
+
skipContext: opts.skipContext,
|
|
2108
|
+
environment
|
|
1552
2109
|
});
|
|
1553
2110
|
return {
|
|
1554
2111
|
outputPath: opts.outputPath,
|
|
@@ -1570,10 +2127,27 @@ function summarizeEnvironment(env) {
|
|
|
1570
2127
|
];
|
|
1571
2128
|
}
|
|
1572
2129
|
async function planEcosystem(opts) {
|
|
1573
|
-
const {
|
|
2130
|
+
const {
|
|
2131
|
+
cwd,
|
|
2132
|
+
projectDetail,
|
|
2133
|
+
statusField,
|
|
2134
|
+
priorityField,
|
|
2135
|
+
runtime,
|
|
2136
|
+
skipSkills,
|
|
2137
|
+
skipContext
|
|
2138
|
+
} = opts;
|
|
1574
2139
|
const ghSymphonyDir = join3(cwd, ".gh-symphony");
|
|
1575
|
-
const environment = await detectEnvironment(cwd);
|
|
2140
|
+
const environment = opts.environment ?? await detectEnvironment(cwd);
|
|
1576
2141
|
const files = [];
|
|
2142
|
+
files.push(
|
|
2143
|
+
await planFileChange({
|
|
2144
|
+
path: join3(cwd, DEFAULT_AFTER_CREATE_HOOK_PATH),
|
|
2145
|
+
label: DEFAULT_AFTER_CREATE_HOOK_LABEL,
|
|
2146
|
+
content: DEFAULT_AFTER_CREATE_HOOK_CONTENT,
|
|
2147
|
+
mode: "create-only",
|
|
2148
|
+
executable: true
|
|
2149
|
+
})
|
|
2150
|
+
);
|
|
1577
2151
|
if (!skipContext) {
|
|
1578
2152
|
const contextYaml = buildContextYaml({
|
|
1579
2153
|
projectDetail,
|
|
@@ -1581,7 +2155,7 @@ async function planEcosystem(opts) {
|
|
|
1581
2155
|
detectedEnvironment: environment,
|
|
1582
2156
|
runtime: {
|
|
1583
2157
|
agent: runtime,
|
|
1584
|
-
agent_command: runtime
|
|
2158
|
+
agent_command: resolveShellAgentCommand(runtime)
|
|
1585
2159
|
}
|
|
1586
2160
|
});
|
|
1587
2161
|
files.push(
|
|
@@ -1599,7 +2173,9 @@ async function planEcosystem(opts) {
|
|
|
1599
2173
|
name: o.name,
|
|
1600
2174
|
role: null
|
|
1601
2175
|
})),
|
|
1602
|
-
projectId: projectDetail.id
|
|
2176
|
+
projectId: projectDetail.id,
|
|
2177
|
+
priorityFieldName: priorityField?.name ?? null,
|
|
2178
|
+
detectedEnvironment: environment
|
|
1603
2179
|
});
|
|
1604
2180
|
files.push(
|
|
1605
2181
|
await planFileChange({
|
|
@@ -1630,7 +2206,8 @@ async function planEcosystem(opts) {
|
|
|
1630
2206
|
})),
|
|
1631
2207
|
statusFieldId: statusField.id,
|
|
1632
2208
|
contextYamlPath: ".gh-symphony/context.yaml",
|
|
1633
|
-
referenceWorkflowPath: ".gh-symphony/reference-workflow.md"
|
|
2209
|
+
referenceWorkflowPath: ".gh-symphony/reference-workflow.md",
|
|
2210
|
+
detectedEnvironment: environment
|
|
1634
2211
|
}
|
|
1635
2212
|
);
|
|
1636
2213
|
for (const plannedSkill of plannedSkills) {
|
|
@@ -1648,7 +2225,9 @@ async function planEcosystem(opts) {
|
|
|
1648
2225
|
projectId: projectDetail.id,
|
|
1649
2226
|
githubProjectTitle: projectDetail.title,
|
|
1650
2227
|
runtime,
|
|
2228
|
+
priorityFieldName: priorityField?.name ?? null,
|
|
1651
2229
|
skillsDir,
|
|
2230
|
+
skipSkills,
|
|
1652
2231
|
environment,
|
|
1653
2232
|
files
|
|
1654
2233
|
};
|
|
@@ -1656,18 +2235,24 @@ async function planEcosystem(opts) {
|
|
|
1656
2235
|
async function writeEcosystem(opts) {
|
|
1657
2236
|
const plan = await planEcosystem(opts);
|
|
1658
2237
|
await mkdir3(join3(opts.cwd, ".gh-symphony"), { recursive: true });
|
|
2238
|
+
const afterCreateHookPath = join3(opts.cwd, DEFAULT_AFTER_CREATE_HOOK_PATH);
|
|
1659
2239
|
const contextYamlPath = join3(opts.cwd, ".gh-symphony", "context.yaml");
|
|
1660
2240
|
const referenceWorkflowPath = join3(
|
|
1661
2241
|
opts.cwd,
|
|
1662
2242
|
".gh-symphony",
|
|
1663
2243
|
"reference-workflow.md"
|
|
1664
2244
|
);
|
|
2245
|
+
let afterCreateHookWritten = false;
|
|
1665
2246
|
let contextYamlWritten = false;
|
|
1666
2247
|
let referenceWorkflowWritten = false;
|
|
1667
2248
|
const skillsWritten = [];
|
|
1668
2249
|
const skillsSkipped = [];
|
|
1669
2250
|
for (const file of plan.files) {
|
|
1670
2251
|
const written = await writePlannedFile(file);
|
|
2252
|
+
if (file.path === afterCreateHookPath) {
|
|
2253
|
+
afterCreateHookWritten = written;
|
|
2254
|
+
continue;
|
|
2255
|
+
}
|
|
1671
2256
|
if (file.path === contextYamlPath) {
|
|
1672
2257
|
contextYamlWritten = written;
|
|
1673
2258
|
continue;
|
|
@@ -1689,7 +2274,10 @@ async function writeEcosystem(opts) {
|
|
|
1689
2274
|
projectId: plan.projectId,
|
|
1690
2275
|
githubProjectTitle: plan.githubProjectTitle,
|
|
1691
2276
|
runtime: plan.runtime,
|
|
2277
|
+
priorityFieldName: plan.priorityFieldName,
|
|
1692
2278
|
skillsDir: plan.skillsDir,
|
|
2279
|
+
skipSkills: plan.skipSkills,
|
|
2280
|
+
afterCreateHookWritten,
|
|
1693
2281
|
contextYamlWritten,
|
|
1694
2282
|
referenceWorkflowWritten,
|
|
1695
2283
|
skillsWritten: skillsWritten.sort(),
|
|
@@ -1700,11 +2288,21 @@ function printEcosystemSummary(result, workflowPath, opts) {
|
|
|
1700
2288
|
const cwd = process.cwd();
|
|
1701
2289
|
const relWorkflow = relative(cwd, workflowPath) || "WORKFLOW.md";
|
|
1702
2290
|
const lines = [];
|
|
1703
|
-
lines.push(
|
|
2291
|
+
lines.push(
|
|
2292
|
+
`GitHub Project ${result.githubProjectTitle} (${result.projectId})`
|
|
2293
|
+
);
|
|
1704
2294
|
lines.push(`Runtime ${result.runtime}`);
|
|
2295
|
+
if (result.priorityFieldName) {
|
|
2296
|
+
lines.push(`Priority field ${result.priorityFieldName}`);
|
|
2297
|
+
}
|
|
1705
2298
|
lines.push("");
|
|
1706
2299
|
lines.push("Generated files");
|
|
1707
2300
|
lines.push(` \u2713 WORKFLOW.md ${relWorkflow}`);
|
|
2301
|
+
if (result.afterCreateHookWritten) {
|
|
2302
|
+
lines.push(
|
|
2303
|
+
` \u2713 ${DEFAULT_AFTER_CREATE_HOOK_LABEL.padEnd(36)} ${DEFAULT_AFTER_CREATE_HOOK_PATH}`
|
|
2304
|
+
);
|
|
2305
|
+
}
|
|
1708
2306
|
if (result.contextYamlWritten) {
|
|
1709
2307
|
lines.push(
|
|
1710
2308
|
" \u2713 Context metadata .gh-symphony/context.yaml"
|
|
@@ -1725,7 +2323,7 @@ function printEcosystemSummary(result, workflowPath, opts) {
|
|
|
1725
2323
|
for (const name of result.skillsSkipped) {
|
|
1726
2324
|
lines.push(` \u2013 ${name} (already exists, skipped)`);
|
|
1727
2325
|
}
|
|
1728
|
-
} else if (result.
|
|
2326
|
+
} else if (!result.skipSkills) {
|
|
1729
2327
|
lines.push("");
|
|
1730
2328
|
lines.push("Skills \u2192 (skipped \u2014 custom runtime)");
|
|
1731
2329
|
}
|
|
@@ -1750,6 +2348,9 @@ function renderDryRunPreview(workflowPath, workflowPlan, ecosystemPlan) {
|
|
|
1750
2348
|
`GitHub Project ${ecosystemPlan.githubProjectTitle} (${ecosystemPlan.projectId})`
|
|
1751
2349
|
);
|
|
1752
2350
|
lines.push(`Runtime ${ecosystemPlan.runtime}`);
|
|
2351
|
+
if (ecosystemPlan.priorityFieldName) {
|
|
2352
|
+
lines.push(`Priority field ${ecosystemPlan.priorityFieldName}`);
|
|
2353
|
+
}
|
|
1753
2354
|
lines.push("");
|
|
1754
2355
|
lines.push("Planned file changes");
|
|
1755
2356
|
lines.push(
|
|
@@ -1777,6 +2378,7 @@ function buildDryRunJsonResult(workflowPath, workflowPlan, ecosystemPlan) {
|
|
|
1777
2378
|
projectId: ecosystemPlan.projectId,
|
|
1778
2379
|
githubProjectTitle: ecosystemPlan.githubProjectTitle,
|
|
1779
2380
|
runtime: ecosystemPlan.runtime,
|
|
2381
|
+
priorityFieldName: ecosystemPlan.priorityFieldName,
|
|
1780
2382
|
files: [workflowPlan, ...ecosystemPlan.files].map((file) => ({
|
|
1781
2383
|
path: file.path,
|
|
1782
2384
|
label: file.label,
|
|
@@ -1792,6 +2394,18 @@ function printDryRunPreview(workflowPath, workflowPlan, ecosystemPlan) {
|
|
|
1792
2394
|
);
|
|
1793
2395
|
}
|
|
1794
2396
|
async function runNonInteractive(flags, options) {
|
|
2397
|
+
const runtime = resolveInitRuntime(flags.runtime);
|
|
2398
|
+
const runtimeError = validateInitRuntime(runtime);
|
|
2399
|
+
if (runtimeError) {
|
|
2400
|
+
process.stderr.write(`Error: ${runtimeError}
|
|
2401
|
+
`);
|
|
2402
|
+
process.exitCode = 1;
|
|
2403
|
+
return;
|
|
2404
|
+
}
|
|
2405
|
+
if (!await runInitRuntimePreflight(runtime)) {
|
|
2406
|
+
process.exitCode = 1;
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
1795
2409
|
let token;
|
|
1796
2410
|
try {
|
|
1797
2411
|
token = getGhTokenWithSource().token;
|
|
@@ -1849,6 +2463,13 @@ async function runNonInteractive(flags, options) {
|
|
|
1849
2463
|
return;
|
|
1850
2464
|
}
|
|
1851
2465
|
const mappings = buildAutomaticStateMappings(statusField);
|
|
2466
|
+
const { field: autoPriorityField, ambiguous: ambiguousPriorityFields } = resolvePriorityField(githubProject, statusField);
|
|
2467
|
+
if (ambiguousPriorityFields.length > 0) {
|
|
2468
|
+
process.stderr.write(
|
|
2469
|
+
`Warning: Multiple priority-like single-select fields found (${ambiguousPriorityFields.map((field) => `"${field.name}"`).join(", ")}). Skipping tracker.priority_field in non-interactive mode.
|
|
2470
|
+
`
|
|
2471
|
+
);
|
|
2472
|
+
}
|
|
1852
2473
|
const validation = validateStateMapping(mappings);
|
|
1853
2474
|
if (!validation.valid) {
|
|
1854
2475
|
process.stderr.write(
|
|
@@ -1865,8 +2486,9 @@ Run without --non-interactive for manual mapping.
|
|
|
1865
2486
|
outputPath,
|
|
1866
2487
|
projectDetail: githubProject,
|
|
1867
2488
|
statusField,
|
|
2489
|
+
priorityField: autoPriorityField,
|
|
1868
2490
|
mappings,
|
|
1869
|
-
runtime
|
|
2491
|
+
runtime,
|
|
1870
2492
|
skipSkills: flags.skipSkills,
|
|
1871
2493
|
skipContext: flags.skipContext
|
|
1872
2494
|
});
|
|
@@ -1887,7 +2509,8 @@ Run without --non-interactive for manual mapping.
|
|
|
1887
2509
|
cwd: process.cwd(),
|
|
1888
2510
|
projectDetail: githubProject,
|
|
1889
2511
|
statusField,
|
|
1890
|
-
|
|
2512
|
+
priorityField: autoPriorityField,
|
|
2513
|
+
runtime,
|
|
1891
2514
|
skipSkills: flags.skipSkills,
|
|
1892
2515
|
skipContext: flags.skipContext
|
|
1893
2516
|
});
|
|
@@ -1907,6 +2530,15 @@ async function runInteractive(flags, options) {
|
|
|
1907
2530
|
await runInteractiveStandalone(flags, options);
|
|
1908
2531
|
}
|
|
1909
2532
|
async function runInteractiveStandalone(flags, _options) {
|
|
2533
|
+
const runtime = await promptRuntimeSelection();
|
|
2534
|
+
if (isClaudeRuntime(runtime)) {
|
|
2535
|
+
if (!await runInitRuntimePreflight(runtime)) {
|
|
2536
|
+
process.exitCode = 1;
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
} else {
|
|
2540
|
+
runRuntimePreflight(runtime);
|
|
2541
|
+
}
|
|
1910
2542
|
const s1 = p.spinner();
|
|
1911
2543
|
s1.start("Checking GitHub authentication...");
|
|
1912
2544
|
let client;
|
|
@@ -1929,10 +2561,12 @@ async function runInteractiveStandalone(flags, _options) {
|
|
|
1929
2561
|
s2.start("Loading projects...");
|
|
1930
2562
|
let projects;
|
|
1931
2563
|
try {
|
|
1932
|
-
|
|
2564
|
+
const discovery = await discoverUserProjects(client);
|
|
2565
|
+
projects = discovery.projects;
|
|
1933
2566
|
s2.stop(
|
|
1934
2567
|
`Found ${projects.length} project${projects.length === 1 ? "" : "s"}`
|
|
1935
2568
|
);
|
|
2569
|
+
warnIfProjectDiscoveryPartial(discovery);
|
|
1936
2570
|
} catch (error) {
|
|
1937
2571
|
s2.stop("Failed to load projects.");
|
|
1938
2572
|
if (error instanceof GitHubScopeError) {
|
|
@@ -1952,7 +2586,7 @@ async function runInteractiveStandalone(flags, _options) {
|
|
|
1952
2586
|
}
|
|
1953
2587
|
const selectedGithubProjectId = await abortIfCancelled(
|
|
1954
2588
|
p.select({
|
|
1955
|
-
message: "Step
|
|
2589
|
+
message: "Step 2/3 \u2014 Select a GitHub Project board:",
|
|
1956
2590
|
options: projects.map((proj) => ({
|
|
1957
2591
|
value: proj.id,
|
|
1958
2592
|
label: `${proj.owner.login}/${proj.title}`,
|
|
@@ -1981,7 +2615,16 @@ async function runInteractiveStandalone(flags, _options) {
|
|
|
1981
2615
|
process.exitCode = 1;
|
|
1982
2616
|
return;
|
|
1983
2617
|
}
|
|
1984
|
-
const
|
|
2618
|
+
const priorityResolution = resolvePriorityField(projectDetail, statusField);
|
|
2619
|
+
const mappings = await promptStateMappings(statusField, {
|
|
2620
|
+
stepLabel: priorityResolution.ambiguous.length > 0 ? "Step 3/4" : "Step 3/3"
|
|
2621
|
+
});
|
|
2622
|
+
let priorityField = priorityResolution.field;
|
|
2623
|
+
if (priorityResolution.ambiguous.length > 0) {
|
|
2624
|
+
priorityField = await promptPriorityField(priorityResolution.ambiguous, {
|
|
2625
|
+
stepLabel: "Step 4/4"
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
1985
2628
|
const validation = validateStateMapping(mappings);
|
|
1986
2629
|
if (!validation.valid) {
|
|
1987
2630
|
p.log.error("Mapping validation failed:");
|
|
@@ -2000,8 +2643,9 @@ async function runInteractiveStandalone(flags, _options) {
|
|
|
2000
2643
|
outputPath,
|
|
2001
2644
|
projectDetail,
|
|
2002
2645
|
statusField,
|
|
2646
|
+
priorityField,
|
|
2003
2647
|
mappings,
|
|
2004
|
-
runtime
|
|
2648
|
+
runtime,
|
|
2005
2649
|
skipSkills: flags.skipSkills,
|
|
2006
2650
|
skipContext: flags.skipContext
|
|
2007
2651
|
});
|
|
@@ -2014,7 +2658,8 @@ async function runInteractiveStandalone(flags, _options) {
|
|
|
2014
2658
|
cwd: process.cwd(),
|
|
2015
2659
|
projectDetail,
|
|
2016
2660
|
statusField,
|
|
2017
|
-
|
|
2661
|
+
priorityField,
|
|
2662
|
+
runtime,
|
|
2018
2663
|
skipSkills: flags.skipSkills,
|
|
2019
2664
|
skipContext: flags.skipContext
|
|
2020
2665
|
});
|
|
@@ -2061,10 +2706,12 @@ function generateProjectId(githubProjectTitle, uniqueKey) {
|
|
|
2061
2706
|
|
|
2062
2707
|
export {
|
|
2063
2708
|
validateStateMapping,
|
|
2709
|
+
warnIfProjectDiscoveryPartial,
|
|
2064
2710
|
abortIfCancelled,
|
|
2065
2711
|
init_default,
|
|
2066
2712
|
resolveStatusField,
|
|
2067
2713
|
buildAutomaticStateMappings,
|
|
2714
|
+
resolvePriorityField,
|
|
2068
2715
|
promptStateMappings,
|
|
2069
2716
|
planWorkflowArtifacts,
|
|
2070
2717
|
writeWorkflowPlan,
|