@gh-symphony/cli 0.0.14 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-5NV3LSAJ.js +11 -0
- package/dist/chunk-6HBZC3BE.js +468 -0
- package/dist/chunk-76QPITKI.js +109 -0
- package/dist/chunk-IWR4UQEJ.js +2250 -0
- package/dist/chunk-JO3AXHQI.js +130 -0
- package/dist/chunk-M7OSMUTN.js +874 -0
- package/dist/chunk-MVRF7BES.js +68 -0
- package/dist/chunk-RNWX7DQU.js +4617 -0
- package/dist/chunk-ROGRTUFI.js +108 -0
- package/dist/chunk-TH5QPO3Y.js +67 -0
- package/dist/config-cmd-AZ7POMAA.js +110 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +568 -356
- package/dist/init-EZXQAXZM.js +17 -0
- package/dist/logs-6LNGT2GF.js +188 -0
- package/dist/project-3ELXQ35D.js +678 -0
- package/dist/recover-T6ME6C56.js +130 -0
- package/dist/repo-R3XBIVAX.js +121 -0
- package/dist/run-DYINRZHK.js +107 -0
- package/dist/start-PIFQMIC2.js +15 -0
- package/dist/status-3WK5BWRZ.js +11 -0
- package/dist/stop-AA3AP5M6.js +9 -0
- package/dist/version-VBB62JWI.js +30 -0
- package/package.json +9 -4
- package/dist/ansi.d.ts +0 -15
- package/dist/ansi.js +0 -53
- package/dist/commands/config-cmd.d.ts +0 -3
- package/dist/commands/config-cmd.js +0 -90
- package/dist/commands/help.d.ts +0 -3
- package/dist/commands/help.js +0 -55
- package/dist/commands/init.d.ts +0 -34
- package/dist/commands/init.js +0 -477
- package/dist/commands/logs.d.ts +0 -3
- package/dist/commands/logs.js +0 -184
- package/dist/commands/project.d.ts +0 -3
- package/dist/commands/project.js +0 -649
- package/dist/commands/recover.d.ts +0 -3
- package/dist/commands/recover.js +0 -119
- package/dist/commands/repo.d.ts +0 -3
- package/dist/commands/repo.js +0 -103
- package/dist/commands/run.d.ts +0 -3
- package/dist/commands/run.js +0 -95
- package/dist/commands/start.d.ts +0 -20
- package/dist/commands/start.js +0 -344
- package/dist/commands/status-refresh.d.ts +0 -9
- package/dist/commands/status-refresh.js +0 -27
- package/dist/commands/status.d.ts +0 -3
- package/dist/commands/status.js +0 -237
- package/dist/commands/stop.d.ts +0 -3
- package/dist/commands/stop.js +0 -92
- package/dist/commands/version.d.ts +0 -3
- package/dist/commands/version.js +0 -21
- package/dist/completion.d.ts +0 -1
- package/dist/completion.js +0 -204
- package/dist/config.d.ts +0 -38
- package/dist/config.js +0 -82
- package/dist/context/context-types.d.ts +0 -36
- package/dist/context/context-types.js +0 -1
- package/dist/context/generate-context-yaml.d.ts +0 -15
- package/dist/context/generate-context-yaml.js +0 -129
- package/dist/dashboard/renderer.d.ts +0 -9
- package/dist/dashboard/renderer.js +0 -220
- package/dist/detection/environment-detector.d.ts +0 -11
- package/dist/detection/environment-detector.js +0 -140
- package/dist/github/client.d.ts +0 -71
- package/dist/github/client.js +0 -348
- package/dist/github/gh-auth.d.ts +0 -34
- package/dist/github/gh-auth.js +0 -110
- package/dist/mapping/smart-defaults.d.ts +0 -17
- package/dist/mapping/smart-defaults.js +0 -86
- package/dist/orchestrator-runtime.d.ts +0 -1
- package/dist/orchestrator-runtime.js +0 -4
- package/dist/orchestrator-status-endpoint.d.ts +0 -5
- package/dist/orchestrator-status-endpoint.js +0 -27
- package/dist/project-selection.d.ts +0 -8
- package/dist/project-selection.js +0 -56
- package/dist/skills/skill-writer.d.ts +0 -14
- package/dist/skills/skill-writer.js +0 -62
- package/dist/skills/templates/commit.d.ts +0 -2
- package/dist/skills/templates/commit.js +0 -45
- package/dist/skills/templates/document.d.ts +0 -7
- package/dist/skills/templates/document.js +0 -16
- package/dist/skills/templates/gh-project.d.ts +0 -2
- package/dist/skills/templates/gh-project.js +0 -88
- package/dist/skills/templates/gh-symphony.d.ts +0 -2
- package/dist/skills/templates/gh-symphony.js +0 -125
- package/dist/skills/templates/index.d.ts +0 -8
- package/dist/skills/templates/index.js +0 -28
- package/dist/skills/templates/land.d.ts +0 -2
- package/dist/skills/templates/land.js +0 -59
- package/dist/skills/templates/pull.d.ts +0 -2
- package/dist/skills/templates/pull.js +0 -41
- package/dist/skills/templates/push.d.ts +0 -2
- package/dist/skills/templates/push.js +0 -36
- package/dist/skills/types.d.ts +0 -23
- package/dist/skills/types.js +0 -1
- package/dist/workflow/generate-reference-workflow.d.ts +0 -9
- package/dist/workflow/generate-reference-workflow.js +0 -261
- package/dist/workflow/generate-workflow-md.d.ts +0 -12
- package/dist/workflow/generate-workflow-md.js +0 -134
package/dist/commands/init.d.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { GlobalOptions } from "../index.js";
|
|
2
|
-
import { type ProjectDetail, type ProjectStatusField, type LinkedRepository } from "../github/client.js";
|
|
3
|
-
export declare function abortIfCancelled<T>(input: T | Promise<T>): Promise<Exclude<T, symbol>>;
|
|
4
|
-
declare const handler: (args: string[], options: GlobalOptions) => Promise<void>;
|
|
5
|
-
export default handler;
|
|
6
|
-
type EcosystemOptions = {
|
|
7
|
-
cwd: string;
|
|
8
|
-
projectDetail: ProjectDetail;
|
|
9
|
-
statusField: ProjectStatusField;
|
|
10
|
-
runtime: string;
|
|
11
|
-
skipSkills: boolean;
|
|
12
|
-
skipContext: boolean;
|
|
13
|
-
};
|
|
14
|
-
export type EcosystemResult = {
|
|
15
|
-
projectId: string;
|
|
16
|
-
githubProjectTitle: string;
|
|
17
|
-
runtime: string;
|
|
18
|
-
skillsDir: string | null;
|
|
19
|
-
contextYamlWritten: boolean;
|
|
20
|
-
referenceWorkflowWritten: boolean;
|
|
21
|
-
skillsWritten: string[];
|
|
22
|
-
skillsSkipped: string[];
|
|
23
|
-
};
|
|
24
|
-
export declare function writeEcosystem(opts: EcosystemOptions): Promise<EcosystemResult>;
|
|
25
|
-
type WriteConfigInput = {
|
|
26
|
-
projectId: string;
|
|
27
|
-
project: ProjectDetail;
|
|
28
|
-
repos: LinkedRepository[];
|
|
29
|
-
workspaceDir: string;
|
|
30
|
-
maxAttempts?: number;
|
|
31
|
-
assignedOnly?: boolean;
|
|
32
|
-
};
|
|
33
|
-
export declare function writeConfig(configDir: string, input: WriteConfigInput): Promise<void>;
|
|
34
|
-
export declare function generateProjectId(githubProjectTitle: string, uniqueKey: string): string;
|
package/dist/commands/init.js
DELETED
|
@@ -1,477 +0,0 @@
|
|
|
1
|
-
import * as p from "@clack/prompts";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import { mkdir, rename, writeFile } from "node:fs/promises";
|
|
4
|
-
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
5
|
-
import { createClient, validateToken, checkRequiredScopes, listUserProjects, getProjectDetail, GitHubScopeError, } from "../github/client.js";
|
|
6
|
-
import { inferAllStateRoles, toWorkflowLifecycleConfig, validateStateMapping, } from "../mapping/smart-defaults.js";
|
|
7
|
-
import { generateWorkflowMarkdown } from "../workflow/generate-workflow-md.js";
|
|
8
|
-
import { loadGlobalConfig, saveGlobalConfig, saveProjectConfig, } from "../config.js";
|
|
9
|
-
import { getGhToken, ensureGhAuth, GhAuthError } from "../github/gh-auth.js";
|
|
10
|
-
import { detectEnvironment } from "../detection/environment-detector.js";
|
|
11
|
-
import { buildContextYaml, writeContextYaml, } from "../context/generate-context-yaml.js";
|
|
12
|
-
import { generateReferenceWorkflow } from "../workflow/generate-reference-workflow.js";
|
|
13
|
-
import { resolveSkillsDir, writeAllSkills } from "../skills/skill-writer.js";
|
|
14
|
-
import { ALL_SKILL_TEMPLATES } from "../skills/templates/index.js";
|
|
15
|
-
// ── Scope error display ───────────────────────────────────────────────────────
|
|
16
|
-
const KNOWN_REQUIRED_SCOPES = ["repo", "read:org", "project"];
|
|
17
|
-
function displayScopeError(error, retryCommand) {
|
|
18
|
-
const plural = error.requiredScopes.length === 1 ? "" : "s";
|
|
19
|
-
p.log.error(`Token is missing required scope${plural}: ${error.requiredScopes.join(", ")}`);
|
|
20
|
-
const currentSet = new Set(error.currentScopes.map((s) => s.toLowerCase()));
|
|
21
|
-
const scopesToAdd = KNOWN_REQUIRED_SCOPES.filter((s) => !currentSet.has(s));
|
|
22
|
-
const scopeArg = scopesToAdd.length > 0
|
|
23
|
-
? scopesToAdd.join(",")
|
|
24
|
-
: error.requiredScopes.join(",");
|
|
25
|
-
p.note(`gh auth refresh --scopes ${scopeArg}\n\nThen re-run: ${retryCommand}`, "Fix missing scope");
|
|
26
|
-
}
|
|
27
|
-
// ── Cancellation utility ─────────────────────────────────────────────────────
|
|
28
|
-
export async function abortIfCancelled(input) {
|
|
29
|
-
const result = await input;
|
|
30
|
-
if (p.isCancel(result)) {
|
|
31
|
-
p.cancel("Setup cancelled.");
|
|
32
|
-
process.exit(130);
|
|
33
|
-
}
|
|
34
|
-
return result;
|
|
35
|
-
}
|
|
36
|
-
function parseInitFlags(args) {
|
|
37
|
-
const flags = {
|
|
38
|
-
nonInteractive: false,
|
|
39
|
-
skipSkills: false,
|
|
40
|
-
skipContext: false,
|
|
41
|
-
};
|
|
42
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
43
|
-
const arg = args[i];
|
|
44
|
-
const next = args[i + 1];
|
|
45
|
-
switch (arg) {
|
|
46
|
-
case "--non-interactive":
|
|
47
|
-
flags.nonInteractive = true;
|
|
48
|
-
break;
|
|
49
|
-
case "--project":
|
|
50
|
-
flags.project = next;
|
|
51
|
-
i += 1;
|
|
52
|
-
break;
|
|
53
|
-
case "--output":
|
|
54
|
-
flags.output = next;
|
|
55
|
-
i += 1;
|
|
56
|
-
break;
|
|
57
|
-
case "--skip-skills":
|
|
58
|
-
flags.skipSkills = true;
|
|
59
|
-
break;
|
|
60
|
-
case "--skip-context":
|
|
61
|
-
flags.skipContext = true;
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return flags;
|
|
66
|
-
}
|
|
67
|
-
// ── Init command handler ─────────────────────────────────────────────────────
|
|
68
|
-
const handler = async (args, options) => {
|
|
69
|
-
const flags = parseInitFlags(args);
|
|
70
|
-
if (flags.nonInteractive) {
|
|
71
|
-
await runNonInteractive(flags, options);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
await runInteractive(options);
|
|
75
|
-
};
|
|
76
|
-
export default handler;
|
|
77
|
-
export async function writeEcosystem(opts) {
|
|
78
|
-
const { cwd, projectDetail, statusField, runtime, skipSkills, skipContext } = opts;
|
|
79
|
-
const ghSymphonyDir = join(cwd, ".gh-symphony");
|
|
80
|
-
await mkdir(ghSymphonyDir, { recursive: true });
|
|
81
|
-
// 1. Detect environment
|
|
82
|
-
const env = await detectEnvironment(cwd);
|
|
83
|
-
// 2. Write context.yaml (unless --skip-context)
|
|
84
|
-
let contextYamlWritten = false;
|
|
85
|
-
if (!skipContext) {
|
|
86
|
-
const contextYaml = buildContextYaml({
|
|
87
|
-
projectDetail,
|
|
88
|
-
statusField,
|
|
89
|
-
detectedEnvironment: env,
|
|
90
|
-
runtime: {
|
|
91
|
-
agent: runtime,
|
|
92
|
-
agent_command: runtime === "codex"
|
|
93
|
-
? "bash -lc codex app-server"
|
|
94
|
-
: runtime === "claude-code"
|
|
95
|
-
? "bash -lc claude-code"
|
|
96
|
-
: runtime,
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
await writeContextYaml(cwd, contextYaml);
|
|
100
|
-
contextYamlWritten = true;
|
|
101
|
-
}
|
|
102
|
-
// 3. Write reference-workflow.md
|
|
103
|
-
const refWorkflow = generateReferenceWorkflow({
|
|
104
|
-
runtime,
|
|
105
|
-
statusColumns: statusField.options.map((o) => ({
|
|
106
|
-
name: o.name,
|
|
107
|
-
role: null,
|
|
108
|
-
})),
|
|
109
|
-
projectId: projectDetail.id,
|
|
110
|
-
});
|
|
111
|
-
const refPath = join(ghSymphonyDir, "reference-workflow.md");
|
|
112
|
-
const tmpRef = refPath + ".tmp";
|
|
113
|
-
await writeFile(tmpRef, refWorkflow, "utf8");
|
|
114
|
-
await rename(tmpRef, refPath);
|
|
115
|
-
// 4. Write skills (unless --skip-skills)
|
|
116
|
-
const skillsDir = resolveSkillsDir(cwd, runtime);
|
|
117
|
-
let skillsWritten = [];
|
|
118
|
-
let skillsSkipped = [];
|
|
119
|
-
if (!skipSkills && skillsDir) {
|
|
120
|
-
const result = await writeAllSkills(cwd, runtime, ALL_SKILL_TEMPLATES, {
|
|
121
|
-
runtime,
|
|
122
|
-
projectId: projectDetail.id,
|
|
123
|
-
githubProjectTitle: projectDetail.title,
|
|
124
|
-
repositories: projectDetail.linkedRepositories.map((r) => ({
|
|
125
|
-
owner: r.owner,
|
|
126
|
-
name: r.name,
|
|
127
|
-
})),
|
|
128
|
-
statusColumns: statusField.options.map((o) => ({
|
|
129
|
-
id: o.id,
|
|
130
|
-
name: o.name,
|
|
131
|
-
role: null,
|
|
132
|
-
})),
|
|
133
|
-
statusFieldId: statusField.id,
|
|
134
|
-
contextYamlPath: ".gh-symphony/context.yaml",
|
|
135
|
-
referenceWorkflowPath: ".gh-symphony/reference-workflow.md",
|
|
136
|
-
});
|
|
137
|
-
skillsWritten = result.written.map((p) => basename(dirname(p)));
|
|
138
|
-
skillsSkipped = result.skipped.map((p) => basename(dirname(p)));
|
|
139
|
-
}
|
|
140
|
-
return {
|
|
141
|
-
projectId: projectDetail.id,
|
|
142
|
-
githubProjectTitle: projectDetail.title,
|
|
143
|
-
runtime,
|
|
144
|
-
skillsDir,
|
|
145
|
-
contextYamlWritten,
|
|
146
|
-
referenceWorkflowWritten: true,
|
|
147
|
-
skillsWritten,
|
|
148
|
-
skillsSkipped,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
// ── Ecosystem summary output ─────────────────────────────────────────────────
|
|
152
|
-
function printEcosystemSummary(result, workflowPath, opts) {
|
|
153
|
-
const cwd = process.cwd();
|
|
154
|
-
const relWorkflow = relative(cwd, workflowPath) || "WORKFLOW.md";
|
|
155
|
-
const lines = [];
|
|
156
|
-
lines.push(`GitHub Project ${result.githubProjectTitle} (${result.projectId})`);
|
|
157
|
-
lines.push(`Runtime ${result.runtime}`);
|
|
158
|
-
lines.push("");
|
|
159
|
-
lines.push("Generated files");
|
|
160
|
-
lines.push(` ✓ WORKFLOW.md ${relWorkflow}`);
|
|
161
|
-
if (result.contextYamlWritten) {
|
|
162
|
-
lines.push(" ✓ Context metadata .gh-symphony/context.yaml");
|
|
163
|
-
}
|
|
164
|
-
if (result.referenceWorkflowWritten) {
|
|
165
|
-
lines.push(" ✓ Reference workflow .gh-symphony/reference-workflow.md");
|
|
166
|
-
}
|
|
167
|
-
if (result.skillsDir) {
|
|
168
|
-
const relSkillsDir = relative(cwd, result.skillsDir);
|
|
169
|
-
lines.push("");
|
|
170
|
-
lines.push(`Skills → ${relSkillsDir}/`);
|
|
171
|
-
for (const name of result.skillsWritten) {
|
|
172
|
-
lines.push(` ✓ ${name}`);
|
|
173
|
-
}
|
|
174
|
-
for (const name of result.skillsSkipped) {
|
|
175
|
-
lines.push(` – ${name} (already exists, skipped)`);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
else if (result.runtime !== "codex" && result.runtime !== "claude-code") {
|
|
179
|
-
lines.push("");
|
|
180
|
-
lines.push("Skills → (skipped — custom runtime)");
|
|
181
|
-
}
|
|
182
|
-
if (opts.interactive) {
|
|
183
|
-
p.note(lines.join("\n"), "Setup complete");
|
|
184
|
-
p.outro(opts.nextSteps ?? "Ready.");
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
process.stdout.write(lines.map((l) => ` ${l}`).join("\n") + "\n");
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
// ── Non-interactive mode: WORKFLOW.md only ───────────────────────────────────
|
|
191
|
-
async function runNonInteractive(flags, options) {
|
|
192
|
-
let token;
|
|
193
|
-
try {
|
|
194
|
-
token = getGhToken();
|
|
195
|
-
}
|
|
196
|
-
catch {
|
|
197
|
-
process.stderr.write("Error: GitHub token not found. Run 'gh auth login --scopes repo,read:org,project' or set GITHUB_GRAPHQL_TOKEN.\n");
|
|
198
|
-
process.exitCode = 1;
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
const client = createClient(token);
|
|
202
|
-
// Validate token
|
|
203
|
-
let viewer;
|
|
204
|
-
try {
|
|
205
|
-
viewer = await validateToken(client);
|
|
206
|
-
}
|
|
207
|
-
catch {
|
|
208
|
-
process.stderr.write("Error: Invalid GitHub token.\n");
|
|
209
|
-
process.exitCode = 1;
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
const scopeCheck = checkRequiredScopes(viewer.scopes);
|
|
213
|
-
if (!scopeCheck.valid) {
|
|
214
|
-
process.stderr.write(`Error: Missing required PAT scopes: ${scopeCheck.missing.join(", ")}\n`);
|
|
215
|
-
process.exitCode = 1;
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
// Find project
|
|
219
|
-
const projects = await listUserProjects(client);
|
|
220
|
-
let githubProject;
|
|
221
|
-
if (flags.project) {
|
|
222
|
-
const match = projects.find((proj) => proj.id === flags.project || proj.url === flags.project);
|
|
223
|
-
if (!match) {
|
|
224
|
-
process.stderr.write(`Error: Project not found: ${flags.project}\n`);
|
|
225
|
-
process.exitCode = 1;
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
githubProject = await getProjectDetail(client, match.id);
|
|
229
|
-
}
|
|
230
|
-
else if (projects.length === 1) {
|
|
231
|
-
githubProject = await getProjectDetail(client, projects[0].id);
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
process.stderr.write("Error: --project is required when multiple projects exist.\n");
|
|
235
|
-
process.exitCode = 1;
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
// Auto-map with smart defaults
|
|
239
|
-
const statusField = githubProject.statusFields.find((f) => f.name.toLowerCase() === "status") ??
|
|
240
|
-
githubProject.statusFields[0];
|
|
241
|
-
if (!statusField) {
|
|
242
|
-
process.stderr.write("Error: No status field found on the project.\n");
|
|
243
|
-
process.exitCode = 1;
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
const columnNames = statusField.options.map((o) => o.name);
|
|
247
|
-
const inferred = inferAllStateRoles(columnNames);
|
|
248
|
-
const mappings = {};
|
|
249
|
-
for (const mapping of inferred) {
|
|
250
|
-
if (mapping.role) {
|
|
251
|
-
mappings[mapping.columnName] = { role: mapping.role };
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
const validation = validateStateMapping(mappings);
|
|
255
|
-
if (!validation.valid) {
|
|
256
|
-
process.stderr.write(`Error: Cannot auto-map columns. ${validation.errors.join("; ")}\nRun without --non-interactive for manual mapping.\n`);
|
|
257
|
-
process.exitCode = 1;
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
const lifecycleConfig = toWorkflowLifecycleConfig(statusField.name, mappings);
|
|
261
|
-
const outputPath = resolve(flags.output ?? "WORKFLOW.md");
|
|
262
|
-
const workflowMd = generateWorkflowMarkdown({
|
|
263
|
-
projectId: githubProject.id,
|
|
264
|
-
stateFieldName: statusField.name,
|
|
265
|
-
mappings,
|
|
266
|
-
lifecycle: lifecycleConfig,
|
|
267
|
-
runtime: "codex",
|
|
268
|
-
});
|
|
269
|
-
await writeFile(outputPath, workflowMd, "utf8");
|
|
270
|
-
const ecosystemResult = await writeEcosystem({
|
|
271
|
-
cwd: process.cwd(),
|
|
272
|
-
projectDetail: githubProject,
|
|
273
|
-
statusField,
|
|
274
|
-
runtime: "codex",
|
|
275
|
-
skipSkills: flags.skipSkills,
|
|
276
|
-
skipContext: flags.skipContext,
|
|
277
|
-
});
|
|
278
|
-
if (options.json) {
|
|
279
|
-
process.stdout.write(JSON.stringify({ output: outputPath, status: "created" }) + "\n");
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
printEcosystemSummary(ecosystemResult, outputPath, {
|
|
283
|
-
interactive: false,
|
|
284
|
-
nextSteps: "Run 'gh-symphony project add' to register a project.",
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
// ── Interactive mode: WORKFLOW.md generation ─────────────────────────────────
|
|
289
|
-
async function runInteractive(options) {
|
|
290
|
-
p.intro("gh-symphony — WORKFLOW.md Setup");
|
|
291
|
-
await runInteractiveStandalone(options);
|
|
292
|
-
}
|
|
293
|
-
// ── Interactive WORKFLOW.md generation ────────────────────────────────────────
|
|
294
|
-
async function runInteractiveStandalone(_options) {
|
|
295
|
-
const s1 = p.spinner();
|
|
296
|
-
s1.start("Checking gh CLI authentication...");
|
|
297
|
-
let client;
|
|
298
|
-
try {
|
|
299
|
-
const { token } = ensureGhAuth();
|
|
300
|
-
client = createClient(token);
|
|
301
|
-
s1.stop("Authenticated via gh CLI");
|
|
302
|
-
}
|
|
303
|
-
catch (error) {
|
|
304
|
-
s1.stop("Authentication failed.");
|
|
305
|
-
if (error instanceof GhAuthError) {
|
|
306
|
-
if (error.code === "not_installed") {
|
|
307
|
-
p.log.error("gh CLI가 설치되어 있지 않습니다. https://cli.github.com 에서 설치하세요.");
|
|
308
|
-
}
|
|
309
|
-
else if (error.code === "not_authenticated") {
|
|
310
|
-
p.log.error("gh auth login --scopes repo,read:org,project 를 실행하세요.");
|
|
311
|
-
}
|
|
312
|
-
else if (error.code === "missing_scopes") {
|
|
313
|
-
p.log.error("gh auth refresh --scopes repo,read:org,project 를 실행하세요.");
|
|
314
|
-
}
|
|
315
|
-
else {
|
|
316
|
-
p.log.error(error.message);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
else {
|
|
320
|
-
p.log.error(error instanceof Error ? error.message : "Unknown error");
|
|
321
|
-
}
|
|
322
|
-
process.exitCode = 1;
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
// Step 1/2: Project selection
|
|
326
|
-
const s2 = p.spinner();
|
|
327
|
-
s2.start("Loading projects...");
|
|
328
|
-
let projects;
|
|
329
|
-
try {
|
|
330
|
-
projects = await listUserProjects(client);
|
|
331
|
-
s2.stop(`Found ${projects.length} project${projects.length === 1 ? "" : "s"}`);
|
|
332
|
-
}
|
|
333
|
-
catch (error) {
|
|
334
|
-
s2.stop("Failed to load projects.");
|
|
335
|
-
if (error instanceof GitHubScopeError) {
|
|
336
|
-
displayScopeError(error, "gh-symphony init");
|
|
337
|
-
}
|
|
338
|
-
else {
|
|
339
|
-
p.log.error(error instanceof Error ? error.message : "Unknown error");
|
|
340
|
-
}
|
|
341
|
-
process.exitCode = 1;
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
if (projects.length === 0) {
|
|
345
|
-
p.log.error("No GitHub Projects found. Create a project at https://github.com/orgs/YOUR_ORG/projects and re-run.");
|
|
346
|
-
process.exitCode = 1;
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
const selectedGithubProjectId = await abortIfCancelled(p.select({
|
|
350
|
-
message: "Step 1/2 — Select a GitHub Project board:",
|
|
351
|
-
options: projects.map((proj) => ({
|
|
352
|
-
value: proj.id,
|
|
353
|
-
label: `${proj.owner.login}/${proj.title}`,
|
|
354
|
-
hint: `${proj.openItemCount} items`,
|
|
355
|
-
})),
|
|
356
|
-
maxItems: 15,
|
|
357
|
-
}));
|
|
358
|
-
const s2d = p.spinner();
|
|
359
|
-
s2d.start("Loading project details...");
|
|
360
|
-
let projectDetail;
|
|
361
|
-
try {
|
|
362
|
-
projectDetail = await getProjectDetail(client, selectedGithubProjectId);
|
|
363
|
-
s2d.stop(`Loaded: ${projectDetail.title}`);
|
|
364
|
-
}
|
|
365
|
-
catch (error) {
|
|
366
|
-
s2d.stop("Failed to load project details.");
|
|
367
|
-
p.log.error(error instanceof Error ? error.message : "Unknown error");
|
|
368
|
-
process.exitCode = 1;
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
// Step 3: Status column mapping
|
|
372
|
-
const statusField = projectDetail.statusFields.find((f) => f.name.toLowerCase() === "status") ??
|
|
373
|
-
projectDetail.statusFields[0];
|
|
374
|
-
if (!statusField) {
|
|
375
|
-
p.log.error("No status field found on the project. The project needs a single-select 'Status' field.");
|
|
376
|
-
process.exitCode = 1;
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
const columnNames = statusField.options.map((o) => o.name);
|
|
380
|
-
const inferred = inferAllStateRoles(columnNames);
|
|
381
|
-
p.log.info(`Found ${columnNames.length} status columns on field "${statusField.name}".`);
|
|
382
|
-
const mappings = {};
|
|
383
|
-
for (const mapping of inferred) {
|
|
384
|
-
const roleOptions = [
|
|
385
|
-
{ value: "active", label: "Active (agent works on this)" },
|
|
386
|
-
{ value: "wait", label: "Wait (human review / hold)" },
|
|
387
|
-
{ value: "terminal", label: "Terminal (completed)" },
|
|
388
|
-
];
|
|
389
|
-
const defaultRole = mapping.role ?? "wait";
|
|
390
|
-
const sortedOptions = [
|
|
391
|
-
roleOptions.find((o) => o.value === defaultRole),
|
|
392
|
-
...roleOptions.filter((o) => o.value !== defaultRole),
|
|
393
|
-
];
|
|
394
|
-
const selectedRole = await abortIfCancelled(p.select({
|
|
395
|
-
message: `Step 2/2 — Map column "${mapping.columnName}":${mapping.confidence === "high" ? " (auto-detected)" : ""}`,
|
|
396
|
-
options: sortedOptions,
|
|
397
|
-
}));
|
|
398
|
-
if (selectedRole !== "skip") {
|
|
399
|
-
mappings[mapping.columnName] = { role: selectedRole };
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
const validation = validateStateMapping(mappings);
|
|
403
|
-
if (!validation.valid) {
|
|
404
|
-
p.log.error("Mapping validation failed:");
|
|
405
|
-
for (const err of validation.errors) {
|
|
406
|
-
p.log.error(` • ${err}`);
|
|
407
|
-
}
|
|
408
|
-
process.exitCode = 1;
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
for (const warn of validation.warnings) {
|
|
412
|
-
p.log.warn(` ⚠ ${warn}`);
|
|
413
|
-
}
|
|
414
|
-
const lifecycleConfig = toWorkflowLifecycleConfig(statusField.name, mappings);
|
|
415
|
-
// Generate WORKFLOW.md only — no config files written
|
|
416
|
-
const workflowMd = generateWorkflowMarkdown({
|
|
417
|
-
projectId: projectDetail.id,
|
|
418
|
-
stateFieldName: statusField.name,
|
|
419
|
-
mappings,
|
|
420
|
-
lifecycle: lifecycleConfig,
|
|
421
|
-
runtime: "codex",
|
|
422
|
-
});
|
|
423
|
-
const outputPath = resolve("WORKFLOW.md");
|
|
424
|
-
await writeFile(outputPath, workflowMd, "utf8");
|
|
425
|
-
const ecosystemResult = await writeEcosystem({
|
|
426
|
-
cwd: process.cwd(),
|
|
427
|
-
projectDetail,
|
|
428
|
-
statusField,
|
|
429
|
-
runtime: "codex",
|
|
430
|
-
skipSkills: false,
|
|
431
|
-
skipContext: false,
|
|
432
|
-
});
|
|
433
|
-
printEcosystemSummary(ecosystemResult, outputPath, {
|
|
434
|
-
interactive: true,
|
|
435
|
-
nextSteps: "Run 'gh-symphony project add' to register a project.",
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
export async function writeConfig(configDir, input) {
|
|
439
|
-
await saveProjectConfig(configDir, input.projectId, {
|
|
440
|
-
projectId: input.projectId,
|
|
441
|
-
slug: input.projectId,
|
|
442
|
-
displayName: input.project.title,
|
|
443
|
-
workspaceDir: input.workspaceDir,
|
|
444
|
-
repositories: input.repos.map((r) => ({
|
|
445
|
-
owner: r.owner,
|
|
446
|
-
name: r.name,
|
|
447
|
-
cloneUrl: r.cloneUrl,
|
|
448
|
-
})),
|
|
449
|
-
tracker: {
|
|
450
|
-
adapter: "github-project",
|
|
451
|
-
bindingId: input.project.id,
|
|
452
|
-
settings: {
|
|
453
|
-
projectId: input.project.id,
|
|
454
|
-
...(input.assignedOnly ? { assignedOnly: true } : {}),
|
|
455
|
-
},
|
|
456
|
-
},
|
|
457
|
-
});
|
|
458
|
-
// Save/update global config
|
|
459
|
-
const existing = await loadGlobalConfig(configDir);
|
|
460
|
-
const globalConfig = {
|
|
461
|
-
activeProject: input.projectId,
|
|
462
|
-
projects: [
|
|
463
|
-
...(existing?.projects ?? []).filter((t) => t !== input.projectId),
|
|
464
|
-
input.projectId,
|
|
465
|
-
],
|
|
466
|
-
};
|
|
467
|
-
await saveGlobalConfig(configDir, globalConfig);
|
|
468
|
-
}
|
|
469
|
-
export function generateProjectId(githubProjectTitle, uniqueKey) {
|
|
470
|
-
const slug = githubProjectTitle
|
|
471
|
-
.toLowerCase()
|
|
472
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
473
|
-
.replace(/^-|-$/g, "")
|
|
474
|
-
.slice(0, 32);
|
|
475
|
-
const suffix = createHash("sha1").update(uniqueKey).digest("hex").slice(0, 8);
|
|
476
|
-
return [slug || "project", suffix].join("-");
|
|
477
|
-
}
|
package/dist/commands/logs.d.ts
DELETED