@gh-symphony/cli 0.3.0 → 0.4.1

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.
@@ -16,7 +16,7 @@ import {
16
16
  formatClaudePreflightText,
17
17
  resolveClaudeCommandBinary,
18
18
  runClaudePreflight
19
- } from "./chunk-5HQLPZR5.js";
19
+ } from "./chunk-YKC6CGD6.js";
20
20
 
21
21
  // src/mapping/smart-defaults.ts
22
22
  var ROLE_PATTERNS = [
@@ -97,8 +97,17 @@ function validateStateMapping(mappings) {
97
97
  import * as p from "@clack/prompts";
98
98
  import { spawnSync } from "child_process";
99
99
  import { createHash } from "crypto";
100
- import { chmod, mkdir as mkdir3, readFile as readFile3, rename as rename2, writeFile as writeFile3 } from "fs/promises";
101
- import { basename, dirname as dirname2, join as join3, relative, resolve } from "path";
100
+ import {
101
+ chmod,
102
+ mkdir as mkdir2,
103
+ readFile as readFile3,
104
+ readdir as readdir2,
105
+ rename as rename2,
106
+ rm,
107
+ rmdir,
108
+ writeFile as writeFile2
109
+ } from "fs/promises";
110
+ import { dirname as dirname2, join as join3, relative, resolve } from "path";
102
111
 
103
112
  // src/prompts/runtime-claude-constraints.ts
104
113
  var CLAUDE_RUNTIME_CONSTRAINTS_SECTION = `## Runtime Constraints
@@ -209,7 +218,6 @@ function buildRepositoryValidationGuidance(input) {
209
218
  // src/workflow/workflow-runtime.ts
210
219
  var CODEX_RUNTIME_TOKENS = ["codex-app-server", "codex"];
211
220
  var CLAUDE_RUNTIME_TOKENS = ["claude-print", "claude-code"];
212
- var DEFAULT_CODEX_APP_SERVER_ARGS = ["app-server"];
213
221
  var DEFAULT_CLAUDE_PRINT_ARGS = [
214
222
  "-p",
215
223
  "--output-format",
@@ -258,31 +266,6 @@ function resolveRuntimeCommand(runtime) {
258
266
  }
259
267
  return runtime;
260
268
  }
261
- function resolveRuntimeArgs(runtime) {
262
- const normalized = normalizeInitRuntime(runtime);
263
- if (normalized === "codex-app-server") {
264
- return DEFAULT_CODEX_APP_SERVER_ARGS;
265
- }
266
- if (normalized === "claude-print") {
267
- return DEFAULT_CLAUDE_PRINT_ARGS;
268
- }
269
- return [];
270
- }
271
- function resolveRuntimeAgentCommand(runtime) {
272
- const command = resolveRuntimeCommand(runtime);
273
- const args = resolveRuntimeArgs(runtime);
274
- return args.length === 0 ? command : [command, ...args].join(" ");
275
- }
276
- function resolveShellAgentCommand(runtime) {
277
- if (/^\s*bash\s+-lc\s+/.test(runtime)) {
278
- return runtime;
279
- }
280
- const normalized = normalizeInitRuntime(runtime);
281
- if (normalized === "codex-app-server" || normalized === "claude-print") {
282
- return `bash -lc ${resolveRuntimeAgentCommand(normalized)}`;
283
- }
284
- return runtime;
285
- }
286
269
  function buildRuntimeFrontMatter(runtime) {
287
270
  const normalized = normalizeInitRuntime(runtime);
288
271
  if (normalized === "codex-app-server") {
@@ -833,1090 +816,1273 @@ async function detectEnvironment(cwd) {
833
816
  };
834
817
  }
835
818
 
836
- // src/context/generate-context-yaml.ts
837
- import { mkdir, writeFile } from "fs/promises";
838
- import { dirname } from "path";
839
- function yamlQuote(value) {
840
- const specialChars = /[:#{'"\]{}()\\[]|\n/;
841
- if (specialChars.test(value)) {
842
- return `"${value.replace(/"/g, '\\"')}"`;
819
+ // src/skills/skill-writer.ts
820
+ import { mkdir, readFile as readFile2, rename, writeFile } from "fs/promises";
821
+ import { dirname, join as join2 } from "path";
822
+ function normalizeRuntimeForSkills(runtime) {
823
+ if (isClaudeRuntime(runtime)) {
824
+ return "claude-code";
825
+ }
826
+ if (isCodexRuntime(runtime)) {
827
+ return "codex";
843
828
  }
844
- return value;
829
+ return null;
845
830
  }
846
- function generateContextYamlString(context) {
847
- const lines = [];
848
- lines.push("schema_version: 1");
849
- lines.push(`collected_at: ${context.collected_at}`);
850
- lines.push("");
851
- lines.push("project:");
852
- lines.push(` id: ${context.project.id}`);
853
- lines.push(` title: ${yamlQuote(context.project.title)}`);
854
- lines.push(` url: ${context.project.url}`);
855
- lines.push("");
856
- lines.push("status_field:");
857
- lines.push(` id: ${context.status_field.id}`);
858
- lines.push(` name: ${yamlQuote(context.status_field.name)}`);
859
- lines.push(" columns:");
860
- for (const column of context.status_field.columns) {
861
- lines.push(` - id: ${column.id}`);
862
- lines.push(` name: ${yamlQuote(column.name)}`);
863
- lines.push(
864
- ` color: ${column.color === null ? "null" : yamlQuote(column.color)}`
865
- );
866
- lines.push(
867
- ` inferred_role: ${column.inferred_role === null ? "null" : column.inferred_role}`
868
- );
869
- lines.push(` confidence: ${column.confidence}`);
831
+ function resolveSkillsDir(repoRoot, runtime) {
832
+ const normalizedRuntime = normalizeRuntimeForSkills(runtime);
833
+ if (normalizedRuntime === "claude-code") {
834
+ return join2(repoRoot, ".claude", "skills");
870
835
  }
871
- lines.push("");
872
- lines.push("text_fields:");
873
- if (context.text_fields.length === 0) {
874
- lines.push(" []");
875
- } else {
876
- for (const field of context.text_fields) {
877
- lines.push(` - id: ${field.id}`);
878
- lines.push(` name: ${yamlQuote(field.name)}`);
879
- lines.push(` data_type: ${field.data_type}`);
880
- }
836
+ if (normalizedRuntime === "codex") {
837
+ return join2(repoRoot, ".codex", "skills");
881
838
  }
882
- lines.push("");
883
- lines.push("repositories:");
884
- if (context.repositories.length === 0) {
885
- lines.push(" []");
886
- } else {
887
- for (const repo of context.repositories) {
888
- lines.push(` - owner: ${repo.owner}`);
889
- lines.push(` name: ${repo.name}`);
890
- lines.push(` clone_url: ${repo.clone_url}`);
891
- }
839
+ return null;
840
+ }
841
+ function buildSkillFilePlans(repoRoot, runtime, templates, context) {
842
+ const skillsDir = resolveSkillsDir(repoRoot, runtime);
843
+ if (!skillsDir) {
844
+ return { skillsDir: null, files: [] };
892
845
  }
846
+ return {
847
+ skillsDir,
848
+ files: templates.flatMap(
849
+ (template) => template.files.map((file) => ({
850
+ path: join2(skillsDir, template.name, file.relativePath),
851
+ content: file.generate(context)
852
+ }))
853
+ )
854
+ };
855
+ }
856
+
857
+ // src/skills/templates/document.ts
858
+ function renderSkillDocument(options) {
859
+ const { name, description, bodyLines } = options;
860
+ return [
861
+ "---",
862
+ `name: ${name}`,
863
+ `description: ${description}`,
864
+ "license: MIT",
865
+ "metadata:",
866
+ " author: gh-symphony",
867
+ ' version: "1.0"',
868
+ ' generatedBy: "gh-symphony"',
869
+ "---",
870
+ "",
871
+ ...bodyLines
872
+ ].join("\n");
873
+ }
874
+
875
+ // src/skills/templates/gh-symphony.ts
876
+ function generateGhSymphonySkill(ctx) {
877
+ const lines = [];
878
+ lines.push("# /gh-symphony \u2014 WORKFLOW.md Design & Refinement");
879
+ lines.push("");
880
+ lines.push("## Trigger");
881
+ lines.push("");
882
+ lines.push("Use this skill when you want to:");
883
+ lines.push("- Create a new WORKFLOW.md for a GitHub Symphony project");
884
+ lines.push("- Refine or improve an existing WORKFLOW.md");
885
+ lines.push("- Validate that a WORKFLOW.md is correctly structured");
886
+ lines.push("");
887
+ lines.push("## Prerequisites");
888
+ lines.push("");
889
+ lines.push("- `WORKFLOW.md` is the repository policy and config source");
890
+ lines.push("- `references/README.md` must exist beside this skill");
891
+ lines.push("- `gh` CLI must be authenticated");
892
+ lines.push("");
893
+ lines.push("## Repository Validation Guidance");
893
894
  lines.push("");
894
- lines.push("detected_environment:");
895
895
  lines.push(
896
- ` packageManager: ${context.detected_environment.packageManager === null ? "null" : yamlQuote(context.detected_environment.packageManager)}`
896
+ "Carry the detected repository validation posture into any new or refined `WORKFLOW.md` instead of falling back to generic instructions."
897
897
  );
898
+ for (const line of buildRepositoryValidationGuidance(
899
+ ctx.detectedEnvironment
900
+ )) {
901
+ lines.push(`- ${line}`);
902
+ }
903
+ lines.push("");
904
+ lines.push("## Mode Detection");
905
+ lines.push("");
906
+ lines.push("Check if `WORKFLOW.md` exists in the current directory:");
907
+ lines.push("- **Not found** \u2192 enter **Design Mode** (create from scratch)");
908
+ lines.push("- **Found** \u2192 ask user: refine existing or validate only?");
909
+ lines.push(" - Refine \u2192 enter **Refine Mode**");
910
+ lines.push(" - Validate \u2192 enter **Validate Mode**");
911
+ lines.push("");
912
+ lines.push("## Design Mode");
913
+ lines.push("");
898
914
  lines.push(
899
- ` lockfile: ${context.detected_environment.lockfile === null ? "null" : yamlQuote(context.detected_environment.lockfile)}`
915
+ "1. Read `WORKFLOW.md` if it exists and extract repository conventions:"
900
916
  );
917
+ lines.push(" - test / lint / build commands from the prompt body");
918
+ lines.push(" - lifecycle states from front matter");
901
919
  lines.push(
902
- ` testCommand: ${context.detected_environment.testCommand === null ? "null" : yamlQuote(context.detected_environment.testCommand)}`
920
+ "2. Read `references/README.md` for the available reference files."
903
921
  );
922
+ lines.push('3. Ask the user: "What should this orchestration accomplish?"');
923
+ lines.push(" - implement (default) \u2014 write features / fix bugs");
924
+ lines.push(" - review \u2014 review PRs and leave comments");
925
+ lines.push(" - maintain \u2014 dependency bumps, chores, hygiene");
926
+ lines.push(" - custom \u2014 describe in their own words");
904
927
  lines.push(
905
- ` buildCommand: ${context.detected_environment.buildCommand === null ? "null" : yamlQuote(context.detected_environment.buildCommand)}`
928
+ "4. Pick the matching `references/workflow-posture-*.md` file(s); compose multiple files when the intent spans categories."
906
929
  );
930
+ lines.push("5. Ask these setup questions:");
931
+ lines.push(" - Which status columns should be **active** (agent works)?");
932
+ lines.push(" - Which should be **wait** (agent pauses for human)?");
933
+ lines.push(" - Which should be **terminal** (agent stops)?");
934
+ lines.push(" - What runtime is being used? (codex / claude-code / custom)");
935
+ lines.push(" - Any custom hooks needed? (after_create, before_run, etc.)");
936
+ lines.push("6. Generate WORKFLOW.md:");
937
+ lines.push(" - front matter from `references/workflow-schema.md`");
907
938
  lines.push(
908
- ` lintCommand: ${context.detected_environment.lintCommand === null ? "null" : yamlQuote(context.detected_environment.lintCommand)}`
939
+ " - prompt body from the selected posture file(s), adapted to actual repository commands"
909
940
  );
941
+ lines.push("7. Show a diff or preview and confirm with the user.");
942
+ lines.push("8. Validate via `gh-symphony workflow validate`.");
943
+ lines.push("");
944
+ lines.push("## Refine Mode");
945
+ lines.push("");
946
+ lines.push("1. Read the current `WORKFLOW.md`");
910
947
  lines.push(
911
- ` ciPlatform: ${context.detected_environment.ciPlatform === null ? "null" : yamlQuote(context.detected_environment.ciPlatform)}`
948
+ "2. Read `references/README.md` and select the relevant posture file(s)"
912
949
  );
913
- lines.push(` monorepo: ${context.detected_environment.monorepo}`);
914
- lines.push(" existingSkills:");
915
- if (context.detected_environment.existingSkills.length === 0) {
916
- lines.push(" []");
917
- } else {
918
- for (const skill of context.detected_environment.existingSkills) {
919
- lines.push(` - ${yamlQuote(skill)}`);
920
- }
921
- }
922
- lines.push("");
923
- lines.push("runtime:");
924
- lines.push(` agent: ${yamlQuote(context.runtime.agent)}`);
925
- lines.push(` agent_command: ${yamlQuote(context.runtime.agent_command)}`);
926
- return lines.join("\n") + "\n";
927
- }
928
- function buildContextYaml(params) {
929
- const columns = params.statusField.options.map((option) => {
930
- const roleMapping = inferStateRole(option.name);
931
- return {
932
- id: option.id,
933
- name: option.name,
934
- color: option.color,
935
- inferred_role: roleMapping.role,
936
- confidence: roleMapping.confidence
937
- };
938
- });
939
- const textFields = params.projectDetail.textFields.map((field) => ({
940
- id: field.id,
941
- name: field.name,
942
- data_type: field.dataType
943
- }));
944
- const repositories = params.projectDetail.linkedRepositories.map((repo) => ({
945
- owner: repo.owner,
946
- name: repo.name,
947
- clone_url: repo.cloneUrl
948
- }));
949
- return {
950
- schema_version: 1,
951
- collected_at: (/* @__PURE__ */ new Date()).toISOString(),
952
- project: {
953
- id: params.projectDetail.id,
954
- title: params.projectDetail.title,
955
- url: params.projectDetail.url
956
- },
957
- status_field: {
958
- id: params.statusField.id,
959
- name: params.statusField.name,
960
- columns
961
- },
962
- text_fields: textFields,
963
- repositories,
964
- detected_environment: params.detectedEnvironment,
965
- runtime: params.runtime
966
- };
967
- }
968
-
969
- // src/workflow/generate-reference-workflow.ts
970
- function generateReferenceWorkflow(input) {
971
- const lines = [];
972
- lines.push("# Reference WORKFLOW.md \u2014 gh-symphony");
973
- lines.push("# This file is a reference template for authoring WORKFLOW.md.");
974
950
  lines.push(
975
- "# AI agents reference this file (via the /gh-symphony skill) when designing WORKFLOW.md."
951
+ "3. Compare the current prompt body against the selected posture references"
976
952
  );
977
- lines.push("# Do not edit this file directly.");
953
+ lines.push("4. Identify missing or incomplete sections:");
954
+ lines.push(" - Status Map with role annotations");
955
+ lines.push(" - Default Posture / Agent Instructions");
956
+ lines.push(" - Guardrails section");
957
+ lines.push(" - Workpad Template");
958
+ lines.push(" - Step 0 routing logic");
959
+ lines.push("5. Propose improvements and apply with user confirmation");
960
+ lines.push("6. Validate the refined file");
978
961
  lines.push("");
979
- lines.push("---");
962
+ lines.push("## Validate Mode");
980
963
  lines.push("");
981
- lines.push("# \u2550\u2550\u2550 FRONT MATTER FIELD REFERENCE \u2550\u2550\u2550");
964
+ lines.push("Check the WORKFLOW.md for:");
965
+ lines.push("- Front matter is valid YAML");
982
966
  lines.push(
983
- "# All front matter fields supported by the gh-symphony parser are listed below."
967
+ "- Required fields are present (see `references/workflow-schema.md`)"
984
968
  );
985
- lines.push("");
986
- lines.push("tracker:");
987
- lines.push(" kind: github-project");
988
- lines.push(` project_id: ${input.projectId}`);
989
- lines.push(" state_field: Status");
990
- lines.push(...buildReferencePriorityLines(input.priority));
991
- lines.push("");
992
- const activeColumns = input.statusColumns.filter((c) => c.role === "active");
993
- const waitColumns = input.statusColumns.filter((c) => c.role === "wait");
994
- const terminalColumns = input.statusColumns.filter(
995
- (c) => c.role === "terminal"
969
+ lines.push(
970
+ "- Template variables use only supported names (see `references/workflow-schema.md`)"
996
971
  );
997
- const blockerCheckStates = input.lifecycle?.blockerCheckStates ?? [];
998
- const planningStates = input.lifecycle?.planningStates ?? blockerCheckStates;
999
- if (activeColumns.length > 0) {
1000
- lines.push(" active_states:");
1001
- for (const col of activeColumns) {
1002
- lines.push(` - ${col.name}`);
1003
- }
1004
- } else {
1005
- lines.push(" active_states: [{active column names}]");
1006
- }
1007
- if (terminalColumns.length > 0) {
1008
- lines.push(" terminal_states:");
1009
- for (const col of terminalColumns) {
1010
- lines.push(` - ${col.name}`);
1011
- }
1012
- } else {
1013
- lines.push(" terminal_states: [{terminal column names}]");
1014
- }
972
+ lines.push("- Status Map matches the lifecycle configuration");
1015
973
  lines.push(
1016
- ...buildReferenceStringList("blocker_check_states", blockerCheckStates)
974
+ "- No unsupported double-brace variable patterns (only the 8 listed below are valid)"
1017
975
  );
1018
- lines.push(...buildReferenceStringList("planning_states", planningStates));
1019
- lines.push("");
1020
- lines.push("# Linear tracker example:");
1021
- lines.push("# tracker:");
1022
- lines.push("# kind: linear");
1023
- lines.push("# endpoint: https://api.linear.app/graphql");
1024
- lines.push("# api_key: $LINEAR_API_KEY");
1025
- lines.push("# project_slug: symphony-0c79b11b75ea");
1026
- lines.push("# active_states:");
1027
- lines.push("# - Todo");
1028
- lines.push("# - In Progress");
1029
- lines.push("# terminal_states:");
1030
- lines.push("# - Done");
1031
- lines.push("# - Canceled");
1032
- lines.push("# - Duplicate");
1033
976
  lines.push(
1034
- "# Linear uses repository-local polling; gh-symphony does not provide"
977
+ "- Prompt body posture is consistent with the selected `references/workflow-posture-*.md` file(s)"
1035
978
  );
1036
- lines.push("# a Linear webhook setup command.");
1037
979
  lines.push("");
980
+ lines.push("## Supported Front Matter Fields");
981
+ lines.push("");
982
+ lines.push("```yaml");
983
+ lines.push("tracker:");
984
+ lines.push(" kind: github-project");
985
+ lines.push(" project_id: PVT_xxx");
986
+ lines.push(" state_field: Status");
987
+ lines.push(" active_states: [Todo, In Progress]");
988
+ lines.push(" terminal_states: [Done, Cancelled]");
989
+ lines.push(" blocker_check_states: [Blocked]");
1038
990
  lines.push("polling:");
1039
991
  lines.push(" interval_ms: 30000");
1040
- lines.push("");
1041
992
  lines.push("workspace:");
1042
993
  lines.push(" root: .runtime/symphony-workspaces");
1043
- lines.push("");
1044
994
  lines.push("hooks:");
1045
- lines.push(
1046
- ` after_create: ${DEFAULT_AFTER_CREATE_HOOK_PATH} # ${DEFAULT_AFTER_CREATE_HOOK_COMMENT}`
1047
- );
995
+ lines.push(" after_create: |");
996
+ lines.push(" git clone --depth 1 https://github.com/owner/repo .");
1048
997
  lines.push(" before_run: null");
1049
998
  lines.push(" after_run: null");
1050
999
  lines.push(" before_remove: null");
1051
1000
  lines.push(" timeout_ms: 60000");
1052
- lines.push("");
1053
1001
  lines.push("agent:");
1054
1002
  lines.push(" max_concurrent_agents: 10");
1055
1003
  lines.push(" max_retry_backoff_ms: 30000");
1056
1004
  lines.push(" retry_base_delay_ms: 10000");
1057
1005
  lines.push(" max_turns: 20");
1006
+ lines.push("codex:");
1007
+ lines.push(" command: codex app-server");
1008
+ lines.push(" read_timeout_ms: 5000");
1009
+ lines.push(" turn_timeout_ms: 3600000");
1010
+ lines.push(" stall_timeout_ms: 300000");
1011
+ lines.push("```");
1058
1012
  lines.push("");
1059
- lines.push(...buildRuntimeFrontMatter(input.runtime));
1013
+ lines.push("## Supported Template Variables");
1060
1014
  lines.push("");
1061
- lines.push("---");
1015
+ lines.push("Use these in the WORKFLOW.md prompt body (double-brace syntax):");
1016
+ lines.push("");
1017
+ lines.push("| Variable | Description |");
1018
+ lines.push("|----------|-------------|");
1019
+ lines.push("| `issue.identifier` | e.g. `acme/platform#42` |");
1020
+ lines.push("| `issue.title` | Issue title |");
1021
+ lines.push("| `issue.state` | Current tracker state |");
1022
+ lines.push("| `issue.description` | Issue body |");
1023
+ lines.push("| `issue.url` | Issue URL |");
1024
+ lines.push("| `issue.repository` | `owner/name` |");
1025
+ lines.push("| `issue.number` | Issue number |");
1026
+ lines.push("| `attempt` | Retry attempt number (null on first run) |");
1062
1027
  lines.push("");
1063
- lines.push("# \u2550\u2550\u2550 PROMPT BODY REFERENCE \u2550\u2550\u2550");
1064
1028
  lines.push(
1065
- "# GitHub Project adaptation of the Elixir Symphony reference prompt."
1029
+ "**Important**: Only these 8 variables are supported. Using any other variable"
1066
1030
  );
1031
+ lines.push("will cause a runtime error (strict mode validation).");
1067
1032
  lines.push("");
1068
- lines.push("## Status Map");
1069
- lines.push("");
1070
- lines.push("| Status | Role | Agent Action |");
1071
- lines.push("| ------ | ---- | ------------ |");
1072
- for (const col of input.statusColumns) {
1073
- const roleLabel = col.role ?? "unset";
1074
- const action = resolveRoleAction(col.role);
1075
- lines.push(`| ${col.name} | ${roleLabel} | ${action} |`);
1076
- }
1077
- if (waitColumns.length > 0) {
1078
- lines.push("");
1079
- lines.push("**Wait States (awaiting PR review):**");
1080
- for (const col of waitColumns) {
1081
- lines.push(
1082
- `- **${col.name}**: PR created. Awaiting human review. Agent is idle.`
1083
- );
1084
- }
1085
- }
1033
+ lines.push("## Related Skills");
1086
1034
  lines.push("");
1087
- lines.push("## Repository Validation Guidance");
1088
- lines.push("");
1089
- for (const line of buildRepositoryValidationGuidance(
1090
- input.detectedEnvironment
1091
- )) {
1092
- lines.push(`- ${line}`);
1093
- }
1094
- lines.push("");
1095
- lines.push("## Default Posture");
1096
- lines.push("");
1097
- lines.push(
1098
- "1. This is an unattended orchestration session. Do not ask humans for follow-up tasks."
1099
- );
1100
- lines.push(
1101
- "2. Exit early only for genuine blockers (missing required credentials or secrets)."
1102
- );
1103
- lines.push(
1104
- '3. In your final message, report only completed work and blockers. Do not include "next steps".'
1105
- );
1106
- lines.push(
1107
- "4. Do not modify the issue body for planning or progress-tracking purposes."
1108
- );
1109
- lines.push(
1110
- "5. If the issue is in a terminal state, do nothing and exit immediately."
1111
- );
1112
- lines.push(
1113
- "6. If you discover out-of-scope improvements, open a separate issue rather than expanding the current scope."
1114
- );
1115
- lines.push(
1116
- "7. Keep all commits as logical units and follow conventional commit format."
1117
- );
1118
- lines.push("8. Do not make commits that break existing tests.");
1119
- lines.push("9. Verify all existing tests pass before creating a PR.");
1120
- lines.push("10. Create a workpad as an issue comment to track progress.");
1121
- lines.push("11. Use the gh-project skill to manage issue status.");
1122
1035
  lines.push(
1123
- "12. When a blocker is found, record it in an issue comment and transition the status appropriately."
1036
+ "- `/gh-project` \u2014 interact with GitHub Project v2 board (status transitions, workpad comments)"
1124
1037
  );
1125
1038
  lines.push(
1126
- "13. Once your work is complete and the PR is merged, transition the issue to the Done state."
1039
+ "- `/commit` \u2014 produce clean, logical commits during implementation"
1127
1040
  );
1041
+ lines.push("- `/push` \u2014 keep remote branch current and publish updates");
1042
+ lines.push("- `/pull` \u2014 sync branch with latest origin/main before handoff");
1043
+ lines.push("- `/land` \u2014 merge approved PR and transition issue to Done");
1044
+ return renderSkillDocument({
1045
+ name: "gh-symphony",
1046
+ description: "Design, refine, and validate repository WORKFLOW.md files for GitHub Symphony projects.",
1047
+ bodyLines: lines
1048
+ });
1049
+ }
1050
+
1051
+ // src/skills/templates/gh-project.ts
1052
+ function generateGhProjectSkill(ctx) {
1053
+ const lines = [];
1054
+ lines.push("# /gh-project \u2014 GitHub Project v2 Status Management");
1128
1055
  lines.push("");
1129
- lines.push("## Related Skills");
1056
+ lines.push("## Purpose");
1130
1057
  lines.push("");
1131
1058
  lines.push(
1132
- "- **gh-project**: Manage GitHub Project v2 issue status and update fields"
1133
- );
1134
- lines.push(
1135
- "- **commit**: Create logical-unit commits (conventional commit format)"
1059
+ "Interact with the GitHub Project v2 board to manage issue status,"
1136
1060
  );
1137
- lines.push("- **push**: Push branch and sync with the remote repository");
1138
- lines.push("- **pull**: Fetch latest changes and resolve conflicts");
1139
- lines.push("- **land**: Create PR, request review, and handle merge");
1140
- lines.push("");
1141
- lines.push("## Step 0: Determine current state and route");
1061
+ lines.push("create workpad comments, and handle follow-up issues.");
1142
1062
  lines.push("");
1143
- lines.push(
1144
- "Check the current issue state and route to the appropriate step:"
1145
- );
1063
+ lines.push("## Prerequisites");
1146
1064
  lines.push("");
1147
- if (terminalColumns.length > 0) {
1148
- const terminalNames = terminalColumns.map((c) => c.name).join(", ");
1149
- lines.push(`- **${terminalNames}** \u2192 Exit immediately. Do nothing.`);
1150
- }
1151
- if (waitColumns.length > 0) {
1152
- const waitNames = waitColumns.map((c) => c.name).join(", ");
1153
- lines.push(`- **${waitNames}** \u2192 Go to Step 3 (handle awaiting review).`);
1154
- }
1155
- if (activeColumns.length > 0) {
1156
- const activeNames = activeColumns.map((c) => c.name).join(", ");
1157
- lines.push(
1158
- `- **${activeNames}** \u2192 Go to Step 1 (start or continue execution).`
1159
- );
1160
- }
1065
+ lines.push("- `gh` CLI is authenticated (`gh auth status`)");
1161
1066
  lines.push(
1162
- "- **Other states** \u2192 Log the unclear state in an issue comment and exit."
1067
+ "- This generated skill contains the status field and option IDs below"
1163
1068
  );
1164
1069
  lines.push("");
1165
- lines.push("## Step 1: Start/continue execution");
1070
+ lines.push("## Column ID Quick Reference");
1166
1071
  lines.push("");
1167
- lines.push(
1168
- "1. Read the issue body and comments to understand current progress."
1169
- );
1170
- lines.push(
1171
- "2. If an existing workpad comment is found, continue from it; otherwise create a new workpad."
1172
- );
1173
- lines.push(
1174
- "3. See the 'Workpad Template' section below for the workpad format."
1175
- );
1176
- lines.push(
1177
- "4. If no branch exists, create a feature branch based on `{issue.repository}`."
1178
- );
1179
- lines.push("5. Proceed to Step 2.");
1072
+ lines.push(`Status Field ID: \`${ctx.statusFieldId}\``);
1180
1073
  lines.push("");
1181
- lines.push("## Step 2: Execution phase");
1074
+ lines.push("| Column Name | Role | Option ID |");
1075
+ lines.push("|-------------|------|-----------|");
1076
+ for (const col of ctx.statusColumns) {
1077
+ const role = col.role ?? "unknown";
1078
+ lines.push(`| ${col.name} | ${role} | \`${col.id}\` |`);
1079
+ }
1182
1080
  lines.push("");
1183
- lines.push("1. Implement according to the issue description.");
1184
- lines.push(
1185
- "2. Commit changes in logical units (conventional commit format)."
1186
- );
1187
- lines.push("3. Verify all existing tests pass.");
1188
- lines.push("4. Write tests for new functionality.");
1189
- lines.push("5. Once all Completion Bar criteria are met, create a PR.");
1190
- lines.push(
1191
- "6. After creating the PR, transition the issue status to the Human Review state."
1192
- );
1193
- lines.push("7. Proceed to Step 3.");
1081
+ lines.push("## Operations");
1194
1082
  lines.push("");
1195
- lines.push("## Step 3: Human Review and merge handling");
1083
+ lines.push("### Change Issue Status");
1196
1084
  lines.push("");
1197
- lines.push("1. If a PR already exists, check for review comments.");
1198
- lines.push(
1199
- "2. If no review comments are present, remain in the waiting state."
1200
- );
1201
1085
  lines.push(
1202
- "3. If the PR is merged, transition the issue to a terminal state."
1086
+ "Use `gh project item-edit` with the field ID and option ID from the table above:"
1203
1087
  );
1204
- lines.push("4. If review changes are requested, proceed to Step 4.");
1205
- lines.push("");
1206
- lines.push("## Step 4: Rework handling");
1207
1088
  lines.push("");
1089
+ lines.push("```bash");
1090
+ lines.push("# Get the project item ID for an issue");
1208
1091
  lines.push(
1209
- "1. Read all PR review comments and identify the requested changes."
1092
+ "gh project item-list <project-number> --owner <owner> --format json \\"
1210
1093
  );
1211
1094
  lines.push(
1212
- "2. Process the changes following the PR Feedback Sweep Protocol."
1095
+ " | jq '.items[] | select(.content.number == <issue-number>) | .id'"
1213
1096
  );
1214
- lines.push("3. After implementing changes, commit and update the PR.");
1215
- lines.push("4. Transition the issue status back to the Human Review state.");
1216
- lines.push("5. Return to Step 3.");
1217
1097
  lines.push("");
1218
- lines.push("## PR Feedback Sweep Protocol");
1098
+ lines.push("# Update the status field");
1099
+ lines.push(`gh project item-edit \\`);
1100
+ lines.push(` --project-id ${ctx.projectId} \\`);
1101
+ lines.push(` --id <item-id> \\`);
1102
+ lines.push(` --field-id ${ctx.statusFieldId} \\`);
1103
+ lines.push(` --single-select-option-id <option-id-from-table-above>`);
1104
+ lines.push("```");
1219
1105
  lines.push("");
1220
- lines.push("Order for processing PR review feedback:");
1106
+ lines.push("### Create Workpad Comment");
1221
1107
  lines.push("");
1108
+ lines.push("```bash");
1222
1109
  lines.push(
1223
- "1. **Collect all comments**: List all unresolved review comments."
1224
- );
1225
- lines.push(
1226
- "2. **Triage by priority**: Handle blocking comments before non-blocking ones."
1227
- );
1228
- lines.push(
1229
- "3. **Implement changes**: Make the code changes corresponding to each comment."
1230
- );
1231
- lines.push(
1232
- "4. **Reply to comments**: Respond to each review comment with a description of what was done."
1110
+ 'gh issue comment <issue-number> --repo <owner>/<repo> --body "## Workpad\\n\\n### Plan\\n- [ ] Task 1"'
1233
1111
  );
1112
+ lines.push("```");
1113
+ lines.push("");
1114
+ lines.push("### Update Existing Comment");
1115
+ lines.push("");
1116
+ lines.push("```bash");
1234
1117
  lines.push(
1235
- "5. **Commit**: Commit changes in `fix: address PR review feedback` format."
1118
+ "gh api -X PATCH /repos/<owner>/<repo>/issues/comments/<comment-id> \\"
1236
1119
  );
1237
- lines.push("6. **Request re-review**: Ask the reviewer for a re-review.");
1120
+ lines.push(' -f body="## Workpad\\n\\n### Plan\\n- [x] Task 1 (done)"');
1121
+ lines.push("```");
1238
1122
  lines.push("");
1239
- lines.push("## Completion Bar");
1123
+ lines.push("### Create Follow-up Issue");
1240
1124
  lines.push("");
1241
- lines.push("All of the following must be satisfied before creating a PR:");
1125
+ lines.push("```bash");
1126
+ lines.push("gh issue create --repo <owner>/<repo> \\");
1127
+ lines.push(' --title "Follow-up: <title>" \\');
1128
+ lines.push(' --body "<description>" \\');
1129
+ lines.push(' --label "backlog"');
1130
+ lines.push("```");
1131
+ lines.push("");
1132
+ lines.push("### Add Label");
1242
1133
  lines.push("");
1134
+ lines.push("```bash");
1243
1135
  lines.push(
1244
- "- [ ] All requirements from the issue description are implemented."
1136
+ 'gh issue edit <issue-number> --repo <owner>/<repo> --add-label "<label>"'
1245
1137
  );
1246
- lines.push("- [ ] All existing tests pass.");
1247
- lines.push("- [ ] Tests are written for new functionality.");
1248
- lines.push("- [ ] Code style follows project conventions.");
1249
- lines.push("- [ ] The PR description clearly explains the changes.");
1250
- lines.push("- [ ] Related documentation is updated (if applicable).");
1138
+ lines.push("```");
1251
1139
  lines.push("");
1252
- lines.push("## Guardrails");
1140
+ lines.push("## Rules");
1253
1141
  lines.push("");
1254
- lines.push("- **Scope**: Never make changes outside the scope of the issue.");
1255
1142
  lines.push(
1256
- "- **Secrets**: Never hardcode tokens, passwords, or API keys in code."
1143
+ "- Always follow the WORKFLOW.md status map flow for state transitions"
1257
1144
  );
1258
1145
  lines.push(
1259
- "- **Breaking changes**: Do not modify existing APIs or interfaces without explicit authorization."
1146
+ "- Before transitioning to a terminal state, verify the Completion Bar is satisfied:"
1260
1147
  );
1261
- lines.push("- **Force push**: Do not force-push to the main/master branch.");
1148
+ lines.push(" - All acceptance criteria checked");
1149
+ lines.push(" - All tests passing");
1150
+ lines.push(" - PR merged (if applicable)");
1262
1151
  lines.push(
1263
- "- **Issue body**: Do not modify the issue body for progress tracking."
1152
+ "- Use the Column ID Quick Reference table above for all status transitions"
1264
1153
  );
1265
1154
  lines.push(
1266
- "- **Infinite loops**: If the same task fails 3 or more consecutive times, log it as a blocker and exit."
1155
+ "- Do not transition issues to terminal states without explicit completion verification"
1267
1156
  );
1157
+ return renderSkillDocument({
1158
+ name: "gh-project",
1159
+ description: "Manage GitHub Project v2 issue states, workpad comments, and related follow-up actions.",
1160
+ bodyLines: lines
1161
+ });
1162
+ }
1163
+
1164
+ // src/skills/templates/commit.ts
1165
+ function generateCommitSkill(_ctx) {
1166
+ const lines = [];
1167
+ lines.push("# /commit \u2014 Clean Commit Workflow");
1268
1168
  lines.push("");
1269
- lines.push("## Workpad Template");
1270
- lines.push("");
1271
- lines.push("Workpad format to create as an issue comment:");
1169
+ lines.push("## Trigger");
1272
1170
  lines.push("");
1273
- lines.push("```markdown");
1274
- lines.push("## Workpad \u2014 {issue.identifier}");
1171
+ lines.push("Use this skill when creating commits during implementation.");
1275
1172
  lines.push("");
1276
- lines.push("**Status**: {current phase}");
1277
- lines.push("**Branch**: {branch name}");
1278
- lines.push("**PR**: {PR URL or not created}");
1173
+ lines.push("## Rules");
1279
1174
  lines.push("");
1280
- lines.push("### Plan");
1175
+ lines.push("- Commit in logical units \u2014 one concern per commit");
1176
+ lines.push("- Never commit a broken intermediate state (tests must pass)");
1177
+ lines.push("- Never commit temporary debug code or commented-out blocks");
1178
+ lines.push("- Run tests before every commit");
1281
1179
  lines.push("");
1282
- lines.push("- [ ] {task 1}");
1283
- lines.push("- [ ] {task 2}");
1180
+ lines.push("## Format");
1284
1181
  lines.push("");
1285
- lines.push("### Progress Log");
1182
+ lines.push("Use Conventional Commit format:");
1286
1183
  lines.push("");
1287
- lines.push("- {timestamp}: {action taken}");
1184
+ lines.push("```");
1185
+ lines.push("<type>(<scope>): <description>");
1288
1186
  lines.push("");
1289
- lines.push("### Blockers");
1187
+ lines.push("[optional body \u2014 explain WHY, not WHAT, 72 chars/line]");
1290
1188
  lines.push("");
1291
- lines.push("None");
1189
+ lines.push("[optional footer: Closes #N]");
1292
1190
  lines.push("```");
1293
1191
  lines.push("");
1294
- return lines.join("\n");
1295
- }
1296
- function buildReferenceStringList(key, values) {
1297
- if (values.length === 0) {
1298
- return [` ${key}: []`];
1299
- }
1300
- return [` ${key}:`, ...values.map((value) => ` - ${value}`)];
1301
- }
1302
- function buildReferencePriorityLines(priority) {
1303
- const lines = [];
1304
- if (priority?.source === "project-field" || priority?.source === "labels") {
1305
- lines.push(
1306
- " # Priority is explicit. Numbers below are editable policy (lower = higher priority)."
1307
- );
1308
- } else {
1309
- lines.push(
1310
- " # Priority dispatch is disabled until an operator chooses one explicit source."
1311
- );
1312
- }
1192
+ lines.push("**Types**: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`");
1193
+ lines.push("");
1313
1194
  lines.push(
1314
- " # See docs/adr/2026-05-18_explicit-dispatch-priority-mappings.md"
1195
+ "**Description**: imperative mood, 50 chars max, no period at end"
1315
1196
  );
1316
- if (priority?.source === "project-field") {
1317
- lines.push(" priority:");
1318
- lines.push(" source: project-field");
1319
- lines.push(` field: ${JSON.stringify(priority.field)}`);
1320
- lines.push(" values:");
1321
- for (const [name, value] of Object.entries(priority.values)) {
1322
- lines.push(` ${JSON.stringify(name)}: ${value}`);
1323
- }
1324
- return lines;
1325
- }
1326
- if (priority?.source === "labels") {
1327
- lines.push(" priority:");
1328
- lines.push(" source: labels");
1329
- lines.push(" labels:");
1330
- for (const [name, value] of Object.entries(priority.labels)) {
1331
- lines.push(` ${JSON.stringify(name)}: ${value}`);
1332
- }
1333
- return lines;
1334
- }
1335
- lines.push(" priority:");
1336
- lines.push(" source: disabled");
1337
1197
  lines.push("");
1338
- lines.push(" # Optional template: project-field priority source.");
1339
- lines.push(" # priority:");
1340
- lines.push(" # source: project-field");
1341
- lines.push(" # field: Priority");
1342
- lines.push(" # values:");
1343
- lines.push(" # Urgent: 0");
1344
- lines.push(" # High: 1");
1198
+ lines.push("## Examples");
1345
1199
  lines.push("");
1346
- lines.push(" # Optional template: labels priority source.");
1347
- lines.push(" # priority:");
1348
- lines.push(" # source: labels");
1349
- lines.push(" # labels:");
1350
- lines.push(" # P0: 0");
1351
- lines.push(" # P1: 1");
1352
- return lines;
1353
- }
1354
- function resolveRoleAction(role) {
1355
- switch (role) {
1356
- case "active":
1357
- return "Agent starts work immediately. Creates workpad and proceeds with implementation.";
1358
- case "wait":
1359
- return "PR created. Awaiting human review. Agent is idle.";
1360
- case "terminal":
1361
- return "Completed state. Agent exits.";
1362
- case null:
1363
- return "Role unset. Must be explicitly configured in WORKFLOW.md.";
1364
- }
1365
- }
1366
-
1367
- // src/skills/skill-writer.ts
1368
- import { mkdir as mkdir2, readFile as readFile2, rename, writeFile as writeFile2 } from "fs/promises";
1369
- import { join as join2 } from "path";
1370
- function normalizeRuntimeForSkills(runtime) {
1371
- if (isClaudeRuntime(runtime)) {
1372
- return "claude-code";
1373
- }
1374
- if (isCodexRuntime(runtime)) {
1375
- return "codex";
1376
- }
1377
- return null;
1378
- }
1379
- function resolveSkillsDir(repoRoot, runtime) {
1380
- const normalizedRuntime = normalizeRuntimeForSkills(runtime);
1381
- if (normalizedRuntime === "claude-code") {
1382
- return join2(repoRoot, ".claude", "skills");
1383
- }
1384
- if (normalizedRuntime === "codex") {
1385
- return join2(repoRoot, ".codex", "skills");
1386
- }
1387
- return null;
1388
- }
1389
- function buildSkillFilePlans(repoRoot, runtime, templates, context) {
1390
- const skillsDir = resolveSkillsDir(repoRoot, runtime);
1391
- if (!skillsDir) {
1392
- return { skillsDir: null, files: [] };
1393
- }
1394
- return {
1395
- skillsDir,
1396
- files: templates.map((template) => ({
1397
- path: join2(skillsDir, template.name, template.fileName),
1398
- content: template.generate(context)
1399
- }))
1400
- };
1200
+ lines.push("```");
1201
+ lines.push("feat(auth): add OAuth2 token refresh");
1202
+ lines.push("fix(api): handle null response from upstream");
1203
+ lines.push("test(worker): add retry exhaustion coverage");
1204
+ lines.push("```");
1205
+ return renderSkillDocument({
1206
+ name: "commit",
1207
+ description: "Create clean, logically scoped commits that keep the repository in a shippable state.",
1208
+ bodyLines: lines
1209
+ });
1401
1210
  }
1402
1211
 
1403
- // src/skills/templates/document.ts
1404
- function renderSkillDocument(options) {
1405
- const { name, description, bodyLines } = options;
1406
- return [
1407
- "---",
1408
- `name: ${name}`,
1409
- `description: ${description}`,
1410
- "license: MIT",
1411
- "metadata:",
1412
- " author: gh-symphony",
1413
- ' version: "1.0"',
1414
- ' generatedBy: "gh-symphony"',
1415
- "---",
1416
- "",
1417
- ...bodyLines
1418
- ].join("\n");
1212
+ // src/skills/templates/push.ts
1213
+ function generatePushSkill(_ctx) {
1214
+ const lines = [];
1215
+ lines.push("# /push \u2014 Git Push Workflow");
1216
+ lines.push("");
1217
+ lines.push("## Trigger");
1218
+ lines.push("");
1219
+ lines.push(
1220
+ "Use this skill when publishing local commits to the remote branch."
1221
+ );
1222
+ lines.push("");
1223
+ lines.push("## Flow");
1224
+ lines.push("");
1225
+ lines.push("1. Run local tests and lint \u2014 ensure they pass before pushing");
1226
+ lines.push("2. Push to remote:");
1227
+ lines.push(" ```bash");
1228
+ lines.push(" git push origin <branch> # subsequent pushes");
1229
+ lines.push(" git push -u origin <branch> # first push (sets upstream)");
1230
+ lines.push(" ```");
1231
+ lines.push("3. If push is rejected (non-fast-forward):");
1232
+ lines.push(" - Run `git fetch origin && git merge origin/main`");
1233
+ lines.push(" - Resolve any conflicts");
1234
+ lines.push(" - Re-run tests");
1235
+ lines.push(" - Push again");
1236
+ lines.push("4. Record push result in workpad Notes");
1237
+ lines.push("");
1238
+ lines.push("## Rules");
1239
+ lines.push("");
1240
+ lines.push("- Never use `--force` (destructive)");
1241
+ lines.push(
1242
+ "- Only use `--force-with-lease` if absolutely necessary \u2014 record the reason in workpad"
1243
+ );
1244
+ lines.push("- Verify CI starts after push (check GitHub Actions tab)");
1245
+ lines.push("- Do not push directly to `main` or `master`");
1246
+ return renderSkillDocument({
1247
+ name: "push",
1248
+ description: "Publish verified local commits to the remote branch without unsafe force pushes.",
1249
+ bodyLines: lines
1250
+ });
1419
1251
  }
1420
1252
 
1421
- // src/skills/templates/gh-symphony.ts
1422
- function generateGhSymphonySkill(ctx) {
1253
+ // src/skills/templates/pull.ts
1254
+ function generatePullSkill(_ctx) {
1423
1255
  const lines = [];
1424
- lines.push("# /gh-symphony \u2014 WORKFLOW.md Design & Refinement");
1256
+ lines.push("# /pull \u2014 Git Pull / Sync Workflow");
1425
1257
  lines.push("");
1426
1258
  lines.push("## Trigger");
1427
1259
  lines.push("");
1428
- lines.push("Use this skill when you want to:");
1429
- lines.push("- Create a new WORKFLOW.md for a GitHub Symphony project");
1430
- lines.push("- Refine or improve an existing WORKFLOW.md");
1431
- lines.push("- Validate that a WORKFLOW.md is correctly structured");
1260
+ lines.push(
1261
+ "Use this skill to sync the current branch with the latest `origin/main`"
1262
+ );
1263
+ lines.push("before starting work or before creating a PR.");
1432
1264
  lines.push("");
1433
- lines.push("## Prerequisites");
1265
+ lines.push("## Flow");
1434
1266
  lines.push("");
1267
+ lines.push("1. Fetch latest from remote:");
1268
+ lines.push(" ```bash");
1269
+ lines.push(" git fetch origin");
1270
+ lines.push(" ```");
1271
+ lines.push("2. Merge into current branch:");
1272
+ lines.push(" ```bash");
1273
+ lines.push(" git merge origin/main");
1274
+ lines.push(" ```");
1275
+ lines.push("3. If conflicts arise:");
1276
+ lines.push(" - Resolve each conflict file");
1277
+ lines.push(" - Run tests to confirm nothing broke");
1435
1278
  lines.push(
1436
- `- \`${ctx.contextYamlPath}\` must exist (contains GitHub Project metadata)`
1279
+ " - Commit the merge: `git commit` (merge commit message is auto-generated)"
1437
1280
  );
1438
1281
  lines.push(
1439
- `- \`${ctx.referenceWorkflowPath}\` must exist (annotated reference template)`
1282
+ "4. Re-run tests after merge to confirm the integrated state is clean"
1440
1283
  );
1441
- lines.push("- `gh` CLI must be authenticated");
1284
+ lines.push("5. Record pull skill evidence in workpad Notes:");
1285
+ lines.push(" - merge source (e.g. `origin/main`)");
1286
+ lines.push(" - result: `clean` or `conflicts resolved`");
1287
+ lines.push(" - resulting HEAD short SHA: `git rev-parse --short HEAD`");
1442
1288
  lines.push("");
1443
- lines.push("## Repository Validation Guidance");
1289
+ lines.push("## Rules");
1290
+ lines.push("");
1291
+ lines.push("- Always pull before creating a PR");
1292
+ lines.push("- Always pull at the start of a new work session");
1293
+ lines.push("- Record the pull evidence in the workpad before proceeding");
1294
+ return renderSkillDocument({
1295
+ name: "pull",
1296
+ description: "Sync the current branch with the latest remote base before implementation or review handoff.",
1297
+ bodyLines: lines
1298
+ });
1299
+ }
1300
+
1301
+ // src/skills/templates/land.ts
1302
+ function generateLandSkill(_ctx) {
1303
+ const lines = [];
1304
+ lines.push("# /land \u2014 PR Merge Workflow");
1305
+ lines.push("");
1306
+ lines.push("## Trigger");
1444
1307
  lines.push("");
1445
1308
  lines.push(
1446
- "Carry the detected repository validation posture into any new or refined `WORKFLOW.md` instead of falling back to generic instructions."
1309
+ "Use this skill when the issue is in the Merging state (PR approved by human)."
1310
+ );
1311
+ lines.push(
1312
+ "Do NOT call `gh pr merge` directly \u2014 always go through this flow."
1447
1313
  );
1448
- for (const line of buildRepositoryValidationGuidance(
1449
- ctx.detectedEnvironment
1450
- )) {
1451
- lines.push(`- ${line}`);
1452
- }
1453
1314
  lines.push("");
1454
- lines.push("## Mode Detection");
1315
+ lines.push("## Pre-flight Checks");
1455
1316
  lines.push("");
1456
- lines.push("Check if `WORKFLOW.md` exists in the current directory:");
1457
- lines.push("- **Not found** \u2192 enter **Design Mode** (create from scratch)");
1458
- lines.push("- **Found** \u2192 ask user: refine existing or validate only?");
1459
- lines.push(" - Refine \u2192 enter **Refine Mode**");
1460
- lines.push(" - Validate \u2192 enter **Validate Mode**");
1317
+ lines.push("Before merging, verify ALL of the following:");
1461
1318
  lines.push("");
1462
- lines.push("## Design Mode");
1319
+ lines.push("1. **PR is approved**:");
1320
+ lines.push(" ```bash");
1321
+ lines.push(
1322
+ ` gh pr view --json reviews --jq '.reviews[] | select(.state == "APPROVED")'`
1323
+ );
1324
+ lines.push(" ```");
1325
+ lines.push("2. **All CI checks are green**:");
1326
+ lines.push(" ```bash");
1327
+ lines.push(" gh pr checks");
1328
+ lines.push(" ```");
1329
+ lines.push("3. **Branch is up-to-date with base**:");
1330
+ lines.push(" ```bash");
1331
+ lines.push(
1332
+ " git fetch origin && git merge-base --is-ancestor origin/main HEAD"
1333
+ );
1334
+ lines.push(" ```");
1335
+ lines.push(" If not up-to-date, run the `/pull` skill first.");
1463
1336
  lines.push("");
1337
+ lines.push("## Flow");
1338
+ lines.push("");
1339
+ lines.push("1. Run all pre-flight checks above");
1340
+ lines.push("2. If all checks pass, merge the PR:");
1341
+ lines.push(" ```bash");
1342
+ lines.push(" gh pr merge --squash # squash merge (default)");
1343
+ lines.push(" # or: gh pr merge --merge # merge commit");
1344
+ lines.push(" # or: gh pr merge --rebase # rebase merge");
1345
+ lines.push(" ```");
1346
+ lines.push(" Choose the merge strategy per project policy.");
1347
+ lines.push("3. On merge success:");
1464
1348
  lines.push(
1465
- `1. Read \`${ctx.contextYamlPath}\` to understand the project structure`
1349
+ " - Use the **gh-project skill** to transition the issue status to Done"
1466
1350
  );
1351
+ lines.push(" - Do NOT call status APIs directly \u2014 delegate to gh-project");
1352
+ lines.push("4. On merge failure:");
1353
+ lines.push(" - Record the failure reason in workpad Notes");
1354
+ lines.push(" - Resolve the blocking issue (re-run pre-flight checks)");
1355
+ lines.push(" - Retry the merge");
1356
+ lines.push("5. Loop until merged or blocked by an unresolvable issue");
1357
+ lines.push("");
1358
+ lines.push("## Rules");
1359
+ lines.push("");
1360
+ lines.push("- Never call `gh pr merge` without completing pre-flight checks");
1467
1361
  lines.push(
1468
- `2. Read \`${ctx.referenceWorkflowPath}\` as the annotated reference`
1362
+ "- Status transition to Done MUST go through the gh-project skill"
1469
1363
  );
1470
- lines.push("3. Ask the user these key questions:");
1471
- lines.push(" - Which status columns should be **active** (agent works)?");
1472
- lines.push(" - Which should be **wait** (agent pauses for human)?");
1473
- lines.push(" - Which should be **terminal** (agent stops)?");
1474
- lines.push(" - What runtime is being used? (codex / claude-code / custom)");
1475
- lines.push(" - Any custom hooks needed? (after_create, before_run, etc.)");
1476
1364
  lines.push(
1477
- "4. Generate WORKFLOW.md using the reference as a structural guide"
1365
+ "- If any pre-flight check fails, do not merge \u2014 fix the issue first"
1366
+ );
1367
+ lines.push("- Record all merge attempts and outcomes in the workpad");
1368
+ return renderSkillDocument({
1369
+ name: "land",
1370
+ description: "Merge approved pull requests safely after verifying approvals, CI, and branch freshness.",
1371
+ bodyLines: lines
1372
+ });
1373
+ }
1374
+
1375
+ // src/skills/templates/gh-symphony-references/readme.ts
1376
+ function generateGhSymphonyReferencesReadme(_ctx) {
1377
+ return [
1378
+ "# /gh-symphony references",
1379
+ "",
1380
+ "The /gh-symphony skill consults these files when designing or refining",
1381
+ "WORKFLOW.md.",
1382
+ "",
1383
+ "## Schema",
1384
+ "",
1385
+ "| File | What it is |",
1386
+ "| ---- | ---------- |",
1387
+ "| `workflow-schema.md` | All supported front matter fields and their types. |",
1388
+ "",
1389
+ "## Workflow prompt body postures",
1390
+ "",
1391
+ "When the user describes what the orchestration should do, pick the matching",
1392
+ "posture file(s) and use its prompt-body sections as the seed. Postures can be",
1393
+ "combined when the user's intent spans multiple categories.",
1394
+ "",
1395
+ "| File | Use when the user wants... |",
1396
+ "| ---- | -------------------------- |",
1397
+ "| `workflow-posture-implement.md` | Coding agent writes features / bug fixes (default). |",
1398
+ "| `workflow-posture-review.md` | Agent reviews PRs and leaves comments. No code writes. |",
1399
+ "| `workflow-posture-maintain.md` | Minimal-change maintenance: deps, lint sweeps, hygiene. |",
1400
+ "",
1401
+ "## Adding your own reference",
1402
+ "",
1403
+ "Drop a markdown file here with a descriptive name. The skill discovers files",
1404
+ "on each invocation; no code changes needed."
1405
+ ].join("\n");
1406
+ }
1407
+
1408
+ // src/workflow/generate-reference-workflow.ts
1409
+ function generateReferenceWorkflow(input) {
1410
+ const lines = [];
1411
+ lines.push("# Reference WORKFLOW.md \u2014 gh-symphony");
1412
+ lines.push("# This file is a reference template for authoring WORKFLOW.md.");
1413
+ lines.push(
1414
+ "# AI agents reference this file (via the /gh-symphony skill) when designing WORKFLOW.md."
1478
1415
  );
1479
- lines.push("5. Validate the generated file (see Validate Mode)");
1416
+ lines.push("# Do not edit this file directly.");
1480
1417
  lines.push("");
1481
- lines.push("## Refine Mode");
1418
+ lines.push("---");
1482
1419
  lines.push("");
1483
- lines.push("1. Read the current `WORKFLOW.md`");
1484
- lines.push(`2. Read \`${ctx.referenceWorkflowPath}\` for comparison`);
1485
- lines.push("3. Identify missing or incomplete sections:");
1486
- lines.push(" - Status Map with role annotations");
1487
- lines.push(" - Default Posture / Agent Instructions");
1488
- lines.push(" - Guardrails section");
1489
- lines.push(" - Workpad Template");
1490
- lines.push(" - Step 0 routing logic");
1491
- lines.push("4. Propose improvements and apply with user confirmation");
1492
- lines.push("5. Validate the refined file");
1420
+ lines.push("# \u2550\u2550\u2550 FRONT MATTER FIELD REFERENCE \u2550\u2550\u2550");
1421
+ lines.push(
1422
+ "# All front matter fields supported by the gh-symphony parser are listed below."
1423
+ );
1493
1424
  lines.push("");
1494
- lines.push("## Validate Mode");
1425
+ lines.push("tracker:");
1426
+ lines.push(" kind: github-project");
1427
+ lines.push(` project_id: ${input.projectId}`);
1428
+ lines.push(" state_field: Status");
1429
+ lines.push(...buildReferencePriorityLines(input.priority));
1495
1430
  lines.push("");
1496
- lines.push("Check the WORKFLOW.md for:");
1497
- lines.push("- Front matter is valid YAML");
1498
- lines.push(
1499
- "- Required fields are present (see Supported Front Matter Fields)"
1431
+ const activeColumns = input.statusColumns.filter((c) => c.role === "active");
1432
+ const waitColumns = input.statusColumns.filter((c) => c.role === "wait");
1433
+ const terminalColumns = input.statusColumns.filter(
1434
+ (c) => c.role === "terminal"
1500
1435
  );
1436
+ const blockerCheckStates = input.lifecycle?.blockerCheckStates ?? [];
1437
+ const planningStates = input.lifecycle?.planningStates ?? blockerCheckStates;
1438
+ if (activeColumns.length > 0) {
1439
+ lines.push(" active_states:");
1440
+ for (const col of activeColumns) {
1441
+ lines.push(` - ${col.name}`);
1442
+ }
1443
+ } else {
1444
+ lines.push(" active_states: [{active column names}]");
1445
+ }
1446
+ if (terminalColumns.length > 0) {
1447
+ lines.push(" terminal_states:");
1448
+ for (const col of terminalColumns) {
1449
+ lines.push(` - ${col.name}`);
1450
+ }
1451
+ } else {
1452
+ lines.push(" terminal_states: [{terminal column names}]");
1453
+ }
1501
1454
  lines.push(
1502
- "- Template variables use only supported names (see Supported Template Variables)"
1455
+ ...buildReferenceStringList("blocker_check_states", blockerCheckStates)
1503
1456
  );
1504
- lines.push("- Status Map matches the lifecycle configuration");
1457
+ lines.push(...buildReferenceStringList("planning_states", planningStates));
1458
+ lines.push("");
1459
+ lines.push("# Linear tracker example:");
1460
+ lines.push("# tracker:");
1461
+ lines.push("# kind: linear");
1462
+ lines.push("# endpoint: https://api.linear.app/graphql");
1463
+ lines.push("# api_key: $LINEAR_API_KEY");
1464
+ lines.push("# project_slug: symphony-0c79b11b75ea");
1465
+ lines.push("# active_states:");
1466
+ lines.push("# - Todo");
1467
+ lines.push("# - In Progress");
1468
+ lines.push("# terminal_states:");
1469
+ lines.push("# - Done");
1470
+ lines.push("# - Canceled");
1471
+ lines.push("# - Duplicate");
1505
1472
  lines.push(
1506
- "- No unsupported double-brace variable patterns (only the 8 listed below are valid)"
1473
+ "# Linear uses repository-local polling; gh-symphony does not provide"
1507
1474
  );
1475
+ lines.push("# a Linear webhook setup command.");
1508
1476
  lines.push("");
1509
- lines.push("## Supported Front Matter Fields");
1510
- lines.push("");
1511
- lines.push("```yaml");
1512
- lines.push("tracker:");
1513
- lines.push(" kind: github-project");
1514
- lines.push(" project_id: PVT_xxx");
1515
- lines.push(" state_field: Status");
1516
- lines.push(" active_states: [Todo, In Progress]");
1517
- lines.push(" terminal_states: [Done, Cancelled]");
1518
- lines.push(" blocker_check_states: [Blocked]");
1519
1477
  lines.push("polling:");
1520
1478
  lines.push(" interval_ms: 30000");
1479
+ lines.push("");
1521
1480
  lines.push("workspace:");
1522
1481
  lines.push(" root: .runtime/symphony-workspaces");
1482
+ lines.push("");
1523
1483
  lines.push("hooks:");
1524
- lines.push(" after_create: |");
1525
- lines.push(" git clone --depth 1 https://github.com/owner/repo .");
1484
+ lines.push(
1485
+ ` after_create: ${DEFAULT_AFTER_CREATE_HOOK_PATH} # ${DEFAULT_AFTER_CREATE_HOOK_COMMENT}`
1486
+ );
1526
1487
  lines.push(" before_run: null");
1527
1488
  lines.push(" after_run: null");
1528
1489
  lines.push(" before_remove: null");
1529
1490
  lines.push(" timeout_ms: 60000");
1491
+ lines.push("");
1530
1492
  lines.push("agent:");
1531
1493
  lines.push(" max_concurrent_agents: 10");
1532
1494
  lines.push(" max_retry_backoff_ms: 30000");
1533
1495
  lines.push(" retry_base_delay_ms: 10000");
1534
1496
  lines.push(" max_turns: 20");
1535
- lines.push("codex:");
1536
- lines.push(" command: codex app-server");
1537
- lines.push(" read_timeout_ms: 5000");
1538
- lines.push(" turn_timeout_ms: 3600000");
1539
- lines.push(" stall_timeout_ms: 300000");
1540
- lines.push("```");
1541
- lines.push("");
1542
- lines.push("## Supported Template Variables");
1543
1497
  lines.push("");
1544
- lines.push("Use these in the WORKFLOW.md prompt body (double-brace syntax):");
1498
+ lines.push(...buildRuntimeFrontMatter(input.runtime));
1545
1499
  lines.push("");
1546
- lines.push("| Variable | Description |");
1547
- lines.push("|----------|-------------|");
1548
- lines.push("| `issue.identifier` | e.g. `acme/platform#42` |");
1549
- lines.push("| `issue.title` | Issue title |");
1550
- lines.push("| `issue.state` | Current tracker state |");
1551
- lines.push("| `issue.description` | Issue body |");
1552
- lines.push("| `issue.url` | Issue URL |");
1553
- lines.push("| `issue.repository` | `owner/name` |");
1554
- lines.push("| `issue.number` | Issue number |");
1555
- lines.push("| `attempt` | Retry attempt number (null on first run) |");
1500
+ lines.push("---");
1556
1501
  lines.push("");
1502
+ lines.push("# \u2550\u2550\u2550 PROMPT BODY REFERENCE \u2550\u2550\u2550");
1557
1503
  lines.push(
1558
- "**Important**: Only these 8 variables are supported. Using any other variable"
1504
+ "# GitHub Project adaptation of the Elixir Symphony reference prompt."
1559
1505
  );
1560
- lines.push("will cause a runtime error (strict mode validation).");
1561
1506
  lines.push("");
1562
- lines.push("## Related Skills");
1507
+ lines.push("## Status Map");
1508
+ lines.push("");
1509
+ lines.push("| Status | Role | Agent Action |");
1510
+ lines.push("| ------ | ---- | ------------ |");
1511
+ for (const col of input.statusColumns) {
1512
+ const roleLabel = col.role ?? "unset";
1513
+ const action = resolveRoleAction(col.role);
1514
+ lines.push(`| ${col.name} | ${roleLabel} | ${action} |`);
1515
+ }
1516
+ if (waitColumns.length > 0) {
1517
+ lines.push("");
1518
+ lines.push("**Wait States (awaiting PR review):**");
1519
+ for (const col of waitColumns) {
1520
+ lines.push(
1521
+ `- **${col.name}**: PR created. Awaiting human review. Agent is idle.`
1522
+ );
1523
+ }
1524
+ }
1525
+ lines.push("");
1526
+ lines.push("## Repository Validation Guidance");
1527
+ lines.push("");
1528
+ for (const line of buildRepositoryValidationGuidance(
1529
+ input.detectedEnvironment
1530
+ )) {
1531
+ lines.push(`- ${line}`);
1532
+ }
1533
+ lines.push("");
1534
+ lines.push("## Default Posture");
1563
1535
  lines.push("");
1564
1536
  lines.push(
1565
- "- `/gh-project` \u2014 interact with GitHub Project v2 board (status transitions, workpad comments)"
1537
+ "1. This is an unattended orchestration session. Do not ask humans for follow-up tasks."
1566
1538
  );
1567
1539
  lines.push(
1568
- "- `/commit` \u2014 produce clean, logical commits during implementation"
1540
+ "2. Exit early only for genuine blockers (missing required credentials or secrets)."
1569
1541
  );
1570
- lines.push("- `/push` \u2014 keep remote branch current and publish updates");
1571
- lines.push("- `/pull` \u2014 sync branch with latest origin/main before handoff");
1572
- lines.push("- `/land` \u2014 merge approved PR and transition issue to Done");
1573
- return renderSkillDocument({
1574
- name: "gh-symphony",
1575
- description: "Design, refine, and validate repository WORKFLOW.md files for GitHub Symphony projects.",
1576
- bodyLines: lines
1577
- });
1578
- }
1579
-
1580
- // src/skills/templates/gh-project.ts
1581
- function generateGhProjectSkill(ctx) {
1582
- const lines = [];
1583
- lines.push("# /gh-project \u2014 GitHub Project v2 Status Management");
1584
- lines.push("");
1585
- lines.push("## Purpose");
1586
- lines.push("");
1587
1542
  lines.push(
1588
- "Interact with the GitHub Project v2 board to manage issue status,"
1543
+ '3. In your final message, report only completed work and blockers. Do not include "next steps".'
1589
1544
  );
1590
- lines.push("create workpad comments, and handle follow-up issues.");
1591
- lines.push("");
1592
- lines.push("## Prerequisites");
1593
- lines.push("");
1594
- lines.push("- `gh` CLI is authenticated (`gh auth status`)");
1595
1545
  lines.push(
1596
- `- \`${ctx.contextYamlPath}\` exists with field IDs and option IDs`
1546
+ "4. Do not modify the issue body for planning or progress-tracking purposes."
1597
1547
  );
1598
- lines.push("");
1599
- lines.push("## Column ID Quick Reference");
1600
- lines.push("");
1601
- lines.push(`Status Field ID: \`${ctx.statusFieldId}\``);
1602
- lines.push("");
1603
- lines.push("| Column Name | Role | Option ID |");
1604
- lines.push("|-------------|------|-----------|");
1605
- for (const col of ctx.statusColumns) {
1606
- const role = col.role ?? "unknown";
1607
- lines.push(`| ${col.name} | ${role} | \`${col.id}\` |`);
1608
- }
1609
- lines.push("");
1610
- lines.push("## Operations");
1611
- lines.push("");
1612
- lines.push("### Change Issue Status");
1613
- lines.push("");
1614
1548
  lines.push(
1615
- "Use `gh project item-edit` with the field ID and option ID from the table above:"
1549
+ "5. If the issue is in a terminal state, do nothing and exit immediately."
1616
1550
  );
1617
- lines.push("");
1618
- lines.push("```bash");
1619
- lines.push("# Get the project item ID for an issue");
1620
1551
  lines.push(
1621
- "gh project item-list <project-number> --owner <owner> --format json \\"
1552
+ "6. If you discover out-of-scope improvements, open a separate issue rather than expanding the current scope."
1622
1553
  );
1623
1554
  lines.push(
1624
- " | jq '.items[] | select(.content.number == <issue-number>) | .id'"
1555
+ "7. Keep all commits as logical units and follow conventional commit format."
1625
1556
  );
1626
- lines.push("");
1627
- lines.push("# Update the status field");
1628
- lines.push(`gh project item-edit \\`);
1629
- lines.push(` --project-id ${ctx.projectId} \\`);
1630
- lines.push(` --id <item-id> \\`);
1631
- lines.push(` --field-id ${ctx.statusFieldId} \\`);
1632
- lines.push(` --single-select-option-id <option-id-from-table-above>`);
1633
- lines.push("```");
1634
- lines.push("");
1635
- lines.push("### Create Workpad Comment");
1636
- lines.push("");
1637
- lines.push("```bash");
1557
+ lines.push("8. Do not make commits that break existing tests.");
1558
+ lines.push("9. Verify all existing tests pass before creating a PR.");
1559
+ lines.push("10. Create a workpad as an issue comment to track progress.");
1560
+ lines.push("11. Use the gh-project skill to manage issue status.");
1638
1561
  lines.push(
1639
- 'gh issue comment <issue-number> --repo <owner>/<repo> --body "## Workpad\\n\\n### Plan\\n- [ ] Task 1"'
1562
+ "12. When a blocker is found, record it in an issue comment and transition the status appropriately."
1563
+ );
1564
+ lines.push(
1565
+ "13. Once your work is complete and the PR is merged, transition the issue to the Done state."
1640
1566
  );
1641
- lines.push("```");
1642
1567
  lines.push("");
1643
- lines.push("### Update Existing Comment");
1568
+ lines.push("## Related Skills");
1644
1569
  lines.push("");
1645
- lines.push("```bash");
1646
1570
  lines.push(
1647
- "gh api -X PATCH /repos/<owner>/<repo>/issues/comments/<comment-id> \\"
1571
+ "- **gh-project**: Manage GitHub Project v2 issue status and update fields"
1648
1572
  );
1649
- lines.push(' -f body="## Workpad\\n\\n### Plan\\n- [x] Task 1 (done)"');
1650
- lines.push("```");
1651
- lines.push("");
1652
- lines.push("### Create Follow-up Issue");
1573
+ lines.push(
1574
+ "- **commit**: Create logical-unit commits (conventional commit format)"
1575
+ );
1576
+ lines.push("- **push**: Push branch and sync with the remote repository");
1577
+ lines.push("- **pull**: Fetch latest changes and resolve conflicts");
1578
+ lines.push("- **land**: Create PR, request review, and handle merge");
1653
1579
  lines.push("");
1654
- lines.push("```bash");
1655
- lines.push("gh issue create --repo <owner>/<repo> \\");
1656
- lines.push(' --title "Follow-up: <title>" \\');
1657
- lines.push(' --body "<description>" \\');
1658
- lines.push(' --label "backlog"');
1659
- lines.push("```");
1580
+ lines.push("## Step 0: Determine current state and route");
1660
1581
  lines.push("");
1661
- lines.push("### Add Label");
1582
+ lines.push(
1583
+ "Check the current issue state and route to the appropriate step:"
1584
+ );
1662
1585
  lines.push("");
1663
- lines.push("```bash");
1586
+ if (terminalColumns.length > 0) {
1587
+ const terminalNames = terminalColumns.map((c) => c.name).join(", ");
1588
+ lines.push(`- **${terminalNames}** \u2192 Exit immediately. Do nothing.`);
1589
+ }
1590
+ if (waitColumns.length > 0) {
1591
+ const waitNames = waitColumns.map((c) => c.name).join(", ");
1592
+ lines.push(`- **${waitNames}** \u2192 Go to Step 3 (handle awaiting review).`);
1593
+ }
1594
+ if (activeColumns.length > 0) {
1595
+ const activeNames = activeColumns.map((c) => c.name).join(", ");
1596
+ lines.push(
1597
+ `- **${activeNames}** \u2192 Go to Step 1 (start or continue execution).`
1598
+ );
1599
+ }
1664
1600
  lines.push(
1665
- 'gh issue edit <issue-number> --repo <owner>/<repo> --add-label "<label>"'
1601
+ "- **Other states** \u2192 Log the unclear state in an issue comment and exit."
1666
1602
  );
1667
- lines.push("```");
1668
1603
  lines.push("");
1669
- lines.push("## Rules");
1604
+ lines.push("## Step 1: Start/continue execution");
1670
1605
  lines.push("");
1671
1606
  lines.push(
1672
- "- Always follow the WORKFLOW.md status map flow for state transitions"
1607
+ "1. Read the issue body and comments to understand current progress."
1673
1608
  );
1674
1609
  lines.push(
1675
- "- Before transitioning to a terminal state, verify the Completion Bar is satisfied:"
1610
+ "2. If an existing workpad comment is found, continue from it; otherwise create a new workpad."
1676
1611
  );
1677
- lines.push(" - All acceptance criteria checked");
1678
- lines.push(" - All tests passing");
1679
- lines.push(" - PR merged (if applicable)");
1680
1612
  lines.push(
1681
- "- Use the Column ID Quick Reference table above for all status transitions"
1613
+ "3. See the 'Workpad Template' section below for the workpad format."
1682
1614
  );
1683
1615
  lines.push(
1684
- "- Do not transition issues to terminal states without explicit completion verification"
1616
+ "4. If no branch exists, create a feature branch based on `{issue.repository}`."
1685
1617
  );
1686
- return renderSkillDocument({
1687
- name: "gh-project",
1688
- description: "Manage GitHub Project v2 issue states, workpad comments, and related follow-up actions.",
1689
- bodyLines: lines
1690
- });
1691
- }
1692
-
1693
- // src/skills/templates/commit.ts
1694
- function generateCommitSkill(_ctx) {
1695
- const lines = [];
1696
- lines.push("# /commit \u2014 Clean Commit Workflow");
1697
- lines.push("");
1698
- lines.push("## Trigger");
1699
- lines.push("");
1700
- lines.push("Use this skill when creating commits during implementation.");
1701
- lines.push("");
1702
- lines.push("## Rules");
1703
- lines.push("");
1704
- lines.push("- Commit in logical units \u2014 one concern per commit");
1705
- lines.push("- Never commit a broken intermediate state (tests must pass)");
1706
- lines.push("- Never commit temporary debug code or commented-out blocks");
1707
- lines.push("- Run tests before every commit");
1708
- lines.push("");
1709
- lines.push("## Format");
1618
+ lines.push("5. Proceed to Step 2.");
1710
1619
  lines.push("");
1711
- lines.push("Use Conventional Commit format:");
1620
+ lines.push("## Step 2: Execution phase");
1712
1621
  lines.push("");
1713
- lines.push("```");
1714
- lines.push("<type>(<scope>): <description>");
1622
+ lines.push("1. Implement according to the issue description.");
1623
+ lines.push(
1624
+ "2. Commit changes in logical units (conventional commit format)."
1625
+ );
1626
+ lines.push("3. Verify all existing tests pass.");
1627
+ lines.push("4. Write tests for new functionality.");
1628
+ lines.push("5. Once all Completion Bar criteria are met, create a PR.");
1629
+ lines.push(
1630
+ "6. After creating the PR, transition the issue status to the Human Review state."
1631
+ );
1632
+ lines.push("7. Proceed to Step 3.");
1715
1633
  lines.push("");
1716
- lines.push("[optional body \u2014 explain WHY, not WHAT, 72 chars/line]");
1634
+ lines.push("## Step 3: Human Review and merge handling");
1717
1635
  lines.push("");
1718
- lines.push("[optional footer: Closes #N]");
1719
- lines.push("```");
1636
+ lines.push("1. If a PR already exists, check for review comments.");
1637
+ lines.push(
1638
+ "2. If no review comments are present, remain in the waiting state."
1639
+ );
1640
+ lines.push(
1641
+ "3. If the PR is merged, transition the issue to a terminal state."
1642
+ );
1643
+ lines.push("4. If review changes are requested, proceed to Step 4.");
1720
1644
  lines.push("");
1721
- lines.push("**Types**: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`");
1645
+ lines.push("## Step 4: Rework handling");
1722
1646
  lines.push("");
1723
1647
  lines.push(
1724
- "**Description**: imperative mood, 50 chars max, no period at end"
1648
+ "1. Read all PR review comments and identify the requested changes."
1725
1649
  );
1650
+ lines.push(
1651
+ "2. Process the changes following the PR Feedback Sweep Protocol."
1652
+ );
1653
+ lines.push("3. After implementing changes, commit and update the PR.");
1654
+ lines.push("4. Transition the issue status back to the Human Review state.");
1655
+ lines.push("5. Return to Step 3.");
1726
1656
  lines.push("");
1727
- lines.push("## Examples");
1728
- lines.push("");
1729
- lines.push("```");
1730
- lines.push("feat(auth): add OAuth2 token refresh");
1731
- lines.push("fix(api): handle null response from upstream");
1732
- lines.push("test(worker): add retry exhaustion coverage");
1733
- lines.push("```");
1734
- return renderSkillDocument({
1735
- name: "commit",
1736
- description: "Create clean, logically scoped commits that keep the repository in a shippable state.",
1737
- bodyLines: lines
1738
- });
1739
- }
1740
-
1741
- // src/skills/templates/push.ts
1742
- function generatePushSkill(_ctx) {
1743
- const lines = [];
1744
- lines.push("# /push \u2014 Git Push Workflow");
1657
+ lines.push("## PR Feedback Sweep Protocol");
1745
1658
  lines.push("");
1746
- lines.push("## Trigger");
1659
+ lines.push("Order for processing PR review feedback:");
1747
1660
  lines.push("");
1748
1661
  lines.push(
1749
- "Use this skill when publishing local commits to the remote branch."
1662
+ "1. **Collect all comments**: List all unresolved review comments."
1750
1663
  );
1664
+ lines.push(
1665
+ "2. **Triage by priority**: Handle blocking comments before non-blocking ones."
1666
+ );
1667
+ lines.push(
1668
+ "3. **Implement changes**: Make the code changes corresponding to each comment."
1669
+ );
1670
+ lines.push(
1671
+ "4. **Reply to comments**: Respond to each review comment with a description of what was done."
1672
+ );
1673
+ lines.push(
1674
+ "5. **Commit**: Commit changes in `fix: address PR review feedback` format."
1675
+ );
1676
+ lines.push("6. **Request re-review**: Ask the reviewer for a re-review.");
1751
1677
  lines.push("");
1752
- lines.push("## Flow");
1753
- lines.push("");
1754
- lines.push("1. Run local tests and lint \u2014 ensure they pass before pushing");
1755
- lines.push("2. Push to remote:");
1756
- lines.push(" ```bash");
1757
- lines.push(" git push origin <branch> # subsequent pushes");
1758
- lines.push(" git push -u origin <branch> # first push (sets upstream)");
1759
- lines.push(" ```");
1760
- lines.push("3. If push is rejected (non-fast-forward):");
1761
- lines.push(" - Run `git fetch origin && git merge origin/main`");
1762
- lines.push(" - Resolve any conflicts");
1763
- lines.push(" - Re-run tests");
1764
- lines.push(" - Push again");
1765
- lines.push("4. Record push result in workpad Notes");
1678
+ lines.push("## Completion Bar");
1766
1679
  lines.push("");
1767
- lines.push("## Rules");
1680
+ lines.push("All of the following must be satisfied before creating a PR:");
1768
1681
  lines.push("");
1769
- lines.push("- Never use `--force` (destructive)");
1770
1682
  lines.push(
1771
- "- Only use `--force-with-lease` if absolutely necessary \u2014 record the reason in workpad"
1683
+ "- [ ] All requirements from the issue description are implemented."
1772
1684
  );
1773
- lines.push("- Verify CI starts after push (check GitHub Actions tab)");
1774
- lines.push("- Do not push directly to `main` or `master`");
1775
- return renderSkillDocument({
1776
- name: "push",
1777
- description: "Publish verified local commits to the remote branch without unsafe force pushes.",
1778
- bodyLines: lines
1779
- });
1780
- }
1781
-
1782
- // src/skills/templates/pull.ts
1783
- function generatePullSkill(_ctx) {
1784
- const lines = [];
1785
- lines.push("# /pull \u2014 Git Pull / Sync Workflow");
1685
+ lines.push("- [ ] All existing tests pass.");
1686
+ lines.push("- [ ] Tests are written for new functionality.");
1687
+ lines.push("- [ ] Code style follows project conventions.");
1688
+ lines.push("- [ ] The PR description clearly explains the changes.");
1689
+ lines.push("- [ ] Related documentation is updated (if applicable).");
1786
1690
  lines.push("");
1787
- lines.push("## Trigger");
1691
+ lines.push("## Guardrails");
1788
1692
  lines.push("");
1693
+ lines.push("- **Scope**: Never make changes outside the scope of the issue.");
1789
1694
  lines.push(
1790
- "Use this skill to sync the current branch with the latest `origin/main`"
1695
+ "- **Secrets**: Never hardcode tokens, passwords, or API keys in code."
1791
1696
  );
1792
- lines.push("before starting work or before creating a PR.");
1793
- lines.push("");
1794
- lines.push("## Flow");
1795
- lines.push("");
1796
- lines.push("1. Fetch latest from remote:");
1797
- lines.push(" ```bash");
1798
- lines.push(" git fetch origin");
1799
- lines.push(" ```");
1800
- lines.push("2. Merge into current branch:");
1801
- lines.push(" ```bash");
1802
- lines.push(" git merge origin/main");
1803
- lines.push(" ```");
1804
- lines.push("3. If conflicts arise:");
1805
- lines.push(" - Resolve each conflict file");
1806
- lines.push(" - Run tests to confirm nothing broke");
1807
1697
  lines.push(
1808
- " - Commit the merge: `git commit` (merge commit message is auto-generated)"
1698
+ "- **Breaking changes**: Do not modify existing APIs or interfaces without explicit authorization."
1809
1699
  );
1700
+ lines.push("- **Force push**: Do not force-push to the main/master branch.");
1810
1701
  lines.push(
1811
- "4. Re-run tests after merge to confirm the integrated state is clean"
1702
+ "- **Issue body**: Do not modify the issue body for progress tracking."
1703
+ );
1704
+ lines.push(
1705
+ "- **Infinite loops**: If the same task fails 3 or more consecutive times, log it as a blocker and exit."
1812
1706
  );
1813
- lines.push("5. Record pull skill evidence in workpad Notes:");
1814
- lines.push(" - merge source (e.g. `origin/main`)");
1815
- lines.push(" - result: `clean` or `conflicts resolved`");
1816
- lines.push(" - resulting HEAD short SHA: `git rev-parse --short HEAD`");
1817
1707
  lines.push("");
1818
- lines.push("## Rules");
1708
+ lines.push("## Workpad Template");
1819
1709
  lines.push("");
1820
- lines.push("- Always pull before creating a PR");
1821
- lines.push("- Always pull at the start of a new work session");
1822
- lines.push("- Record the pull evidence in the workpad before proceeding");
1823
- return renderSkillDocument({
1824
- name: "pull",
1825
- description: "Sync the current branch with the latest remote base before implementation or review handoff.",
1826
- bodyLines: lines
1827
- });
1828
- }
1829
-
1830
- // src/skills/templates/land.ts
1831
- function generateLandSkill(_ctx) {
1832
- const lines = [];
1833
- lines.push("# /land \u2014 PR Merge Workflow");
1710
+ lines.push("Workpad format to create as an issue comment:");
1834
1711
  lines.push("");
1835
- lines.push("## Trigger");
1712
+ lines.push("```markdown");
1713
+ lines.push("## Workpad \u2014 {issue.identifier}");
1836
1714
  lines.push("");
1837
- lines.push(
1838
- "Use this skill when the issue is in the Merging state (PR approved by human)."
1839
- );
1840
- lines.push(
1841
- "Do NOT call `gh pr merge` directly \u2014 always go through this flow."
1842
- );
1715
+ lines.push("**Status**: {current phase}");
1716
+ lines.push("**Branch**: {branch name}");
1717
+ lines.push("**PR**: {PR URL or not created}");
1843
1718
  lines.push("");
1844
- lines.push("## Pre-flight Checks");
1719
+ lines.push("### Plan");
1845
1720
  lines.push("");
1846
- lines.push("Before merging, verify ALL of the following:");
1721
+ lines.push("- [ ] {task 1}");
1722
+ lines.push("- [ ] {task 2}");
1847
1723
  lines.push("");
1848
- lines.push("1. **PR is approved**:");
1849
- lines.push(" ```bash");
1850
- lines.push(
1851
- ` gh pr view --json reviews --jq '.reviews[] | select(.state == "APPROVED")'`
1852
- );
1853
- lines.push(" ```");
1854
- lines.push("2. **All CI checks are green**:");
1855
- lines.push(" ```bash");
1856
- lines.push(" gh pr checks");
1857
- lines.push(" ```");
1858
- lines.push("3. **Branch is up-to-date with base**:");
1859
- lines.push(" ```bash");
1724
+ lines.push("### Progress Log");
1725
+ lines.push("");
1726
+ lines.push("- {timestamp}: {action taken}");
1727
+ lines.push("");
1728
+ lines.push("### Blockers");
1729
+ lines.push("");
1730
+ lines.push("None");
1731
+ lines.push("```");
1732
+ lines.push("");
1733
+ return lines.join("\n");
1734
+ }
1735
+ function buildReferenceStringList(key, values) {
1736
+ if (values.length === 0) {
1737
+ return [` ${key}: []`];
1738
+ }
1739
+ return [` ${key}:`, ...values.map((value) => ` - ${value}`)];
1740
+ }
1741
+ function buildReferencePriorityLines(priority) {
1742
+ const lines = [];
1743
+ if (priority?.source === "project-field" || priority?.source === "labels") {
1744
+ lines.push(
1745
+ " # Priority is explicit. Numbers below are editable policy (lower = higher priority)."
1746
+ );
1747
+ } else {
1748
+ lines.push(
1749
+ " # Priority dispatch is disabled until an operator chooses one explicit source."
1750
+ );
1751
+ }
1860
1752
  lines.push(
1861
- " git fetch origin && git merge-base --is-ancestor origin/main HEAD"
1753
+ " # See docs/adr/2026-05-18_explicit-dispatch-priority-mappings.md"
1862
1754
  );
1863
- lines.push(" ```");
1864
- lines.push(" If not up-to-date, run the `/pull` skill first.");
1755
+ if (priority?.source === "project-field") {
1756
+ lines.push(" priority:");
1757
+ lines.push(" source: project-field");
1758
+ lines.push(` field: ${JSON.stringify(priority.field)}`);
1759
+ lines.push(" values:");
1760
+ for (const [name, value] of Object.entries(priority.values)) {
1761
+ lines.push(` ${JSON.stringify(name)}: ${value}`);
1762
+ }
1763
+ return lines;
1764
+ }
1765
+ if (priority?.source === "labels") {
1766
+ lines.push(" priority:");
1767
+ lines.push(" source: labels");
1768
+ lines.push(" labels:");
1769
+ for (const [name, value] of Object.entries(priority.labels)) {
1770
+ lines.push(` ${JSON.stringify(name)}: ${value}`);
1771
+ }
1772
+ return lines;
1773
+ }
1774
+ lines.push(" priority:");
1775
+ lines.push(" source: disabled");
1865
1776
  lines.push("");
1866
- lines.push("## Flow");
1777
+ lines.push(" # Optional template: project-field priority source.");
1778
+ lines.push(" # priority:");
1779
+ lines.push(" # source: project-field");
1780
+ lines.push(" # field: Priority");
1781
+ lines.push(" # values:");
1782
+ lines.push(" # Urgent: 0");
1783
+ lines.push(" # High: 1");
1867
1784
  lines.push("");
1868
- lines.push("1. Run all pre-flight checks above");
1869
- lines.push("2. If all checks pass, merge the PR:");
1870
- lines.push(" ```bash");
1871
- lines.push(" gh pr merge --squash # squash merge (default)");
1872
- lines.push(" # or: gh pr merge --merge # merge commit");
1873
- lines.push(" # or: gh pr merge --rebase # rebase merge");
1874
- lines.push(" ```");
1875
- lines.push(" Choose the merge strategy per project policy.");
1876
- lines.push("3. On merge success:");
1877
- lines.push(
1878
- " - Use the **gh-project skill** to transition the issue status to Done"
1785
+ lines.push(" # Optional template: labels priority source.");
1786
+ lines.push(" # priority:");
1787
+ lines.push(" # source: labels");
1788
+ lines.push(" # labels:");
1789
+ lines.push(" # P0: 0");
1790
+ lines.push(" # P1: 1");
1791
+ return lines;
1792
+ }
1793
+ function resolveRoleAction(role) {
1794
+ switch (role) {
1795
+ case "active":
1796
+ return "Agent starts work immediately. Creates workpad and proceeds with implementation.";
1797
+ case "wait":
1798
+ return "PR created. Awaiting human review. Agent is idle.";
1799
+ case "terminal":
1800
+ return "Completed state. Agent exits.";
1801
+ case null:
1802
+ return "Role unset. Must be explicitly configured in WORKFLOW.md.";
1803
+ }
1804
+ }
1805
+
1806
+ // src/skills/templates/gh-symphony-references/workflow-schema.ts
1807
+ function generateWorkflowSchemaReference(ctx) {
1808
+ const reference = generateReferenceWorkflow({
1809
+ runtime: ctx.runtime,
1810
+ statusColumns: ctx.statusColumns.map((column) => ({
1811
+ name: column.name,
1812
+ role: column.role
1813
+ })),
1814
+ projectId: ctx.projectId,
1815
+ priority: null,
1816
+ detectedEnvironment: ctx.detectedEnvironment
1817
+ });
1818
+ return [
1819
+ reference,
1820
+ "",
1821
+ "## Supported Template Variables",
1822
+ "",
1823
+ "Use these in the WORKFLOW.md prompt body with double-brace syntax.",
1824
+ "",
1825
+ "| Variable | Description |",
1826
+ "| -------- | ----------- |",
1827
+ "| `issue.identifier` | Issue identifier, for example `acme/platform#42`. |",
1828
+ "| `issue.title` | Issue title. |",
1829
+ "| `issue.state` | Current tracker state. |",
1830
+ "| `issue.description` | Issue body. |",
1831
+ "| `issue.url` | Issue URL. |",
1832
+ "| `issue.repository` | Repository in `owner/name` form. |",
1833
+ "| `issue.number` | Issue number. |",
1834
+ "| `attempt` | Retry attempt number, or null on the first run. |",
1835
+ "",
1836
+ "Only these variables are supported by strict-mode prompt rendering."
1837
+ ].join("\n");
1838
+ }
1839
+
1840
+ // src/skills/templates/gh-symphony-references/workflow-posture-implement.ts
1841
+ function generateWorkflowPostureImplementReference(ctx) {
1842
+ const validationGuidance = buildRepositoryValidationGuidance(
1843
+ ctx.detectedEnvironment
1879
1844
  );
1880
- lines.push(" - Do NOT call status APIs directly \u2014 delegate to gh-project");
1881
- lines.push("4. On merge failure:");
1882
- lines.push(" - Record the failure reason in workpad Notes");
1883
- lines.push(" - Resolve the blocking issue (re-run pre-flight checks)");
1884
- lines.push(" - Retry the merge");
1885
- lines.push("5. Loop until merged or blocked by an unresolvable issue");
1886
- lines.push("");
1887
- lines.push("## Rules");
1888
- lines.push("");
1889
- lines.push("- Never call `gh pr merge` without completing pre-flight checks");
1890
- lines.push(
1891
- "- Status transition to Done MUST go through the gh-project skill"
1845
+ return [
1846
+ "# Workflow posture: implement",
1847
+ "",
1848
+ "Use this prompt-body posture when the agent should write features or fix bugs.",
1849
+ "This is the default posture and preserves the current generated WORKFLOW.md",
1850
+ "prompt-body behavior.",
1851
+ "",
1852
+ "## Agent Instructions",
1853
+ "",
1854
+ 'You are an AI coding agent working on issue `{issue.identifier}`: "`{issue.title}`".',
1855
+ "",
1856
+ "**Repository:** `{issue.repository}`",
1857
+ "**Current state:** `{issue.state}`",
1858
+ "",
1859
+ "### Task",
1860
+ "",
1861
+ "`{issue.description}`",
1862
+ "",
1863
+ "### Default Posture",
1864
+ "",
1865
+ "1. This is an unattended orchestration session. Do not ask humans for follow-up actions.",
1866
+ "2. Only abort early if there is a genuine blocker (missing required credentials or secrets).",
1867
+ '3. In your final message, report only what was completed and any blockers. Do not include "next steps".',
1868
+ "",
1869
+ "### Repository Validation Guidance",
1870
+ "",
1871
+ ...validationGuidance.map((line, index) => `${index + 1}. ${line}`),
1872
+ "",
1873
+ "### Workflow",
1874
+ "",
1875
+ "1. Read the issue description and understand the requirements.",
1876
+ "2. Explore the codebase to understand the relevant code structure.",
1877
+ "3. Implement the changes following the project's coding conventions.",
1878
+ "4. Write or update tests to cover the changes.",
1879
+ "5. Verify that all existing tests pass.",
1880
+ "6. Create a PR with a clear description of the changes.",
1881
+ "",
1882
+ "### Guardrails",
1883
+ "",
1884
+ "- Do not edit the issue body for planning or progress tracking.",
1885
+ "- If the issue is in a terminal state, do nothing and exit.",
1886
+ "- If you find out-of-scope improvements, open a separate issue rather than expanding the current scope.",
1887
+ "",
1888
+ "### Workpad Template",
1889
+ "",
1890
+ "Create a workpad comment on the issue with the following structure to track progress:",
1891
+ "",
1892
+ "```md",
1893
+ "## Workpad",
1894
+ "",
1895
+ "### Plan",
1896
+ "",
1897
+ "- [ ] 1. Task item",
1898
+ "",
1899
+ "### Acceptance Criteria",
1900
+ "",
1901
+ "- [ ] Criterion 1",
1902
+ "",
1903
+ "### Validation",
1904
+ "",
1905
+ "- [ ] Test: `command`",
1906
+ "",
1907
+ "### Notes",
1908
+ "",
1909
+ "- Progress notes",
1910
+ "```"
1911
+ ].join("\n");
1912
+ }
1913
+
1914
+ // src/skills/templates/gh-symphony-references/workflow-posture-review.ts
1915
+ function generateWorkflowPostureReviewReference(ctx) {
1916
+ const validationGuidance = buildRepositoryValidationGuidance(
1917
+ ctx.detectedEnvironment
1892
1918
  );
1893
- lines.push(
1894
- "- If any pre-flight check fails, do not merge \u2014 fix the issue first"
1919
+ return [
1920
+ "# Workflow posture: review",
1921
+ "",
1922
+ "Use this prompt-body posture when the agent should review PRs and leave",
1923
+ "comments. This posture is read-only for repository code.",
1924
+ "",
1925
+ "## Agent Instructions",
1926
+ "",
1927
+ 'You are an AI code-review agent working on issue `{issue.identifier}`: "`{issue.title}`".',
1928
+ "",
1929
+ "**Repository:** `{issue.repository}`",
1930
+ "**Current state:** `{issue.state}`",
1931
+ "",
1932
+ "### Task",
1933
+ "",
1934
+ "`{issue.description}`",
1935
+ "",
1936
+ "### Default Posture",
1937
+ "",
1938
+ "1. Review linked pull requests. Do NOT write code, push commits, or open new PRs.",
1939
+ "2. Treat failing required tests as grounds to request changes unless the failure is clearly unrelated and documented.",
1940
+ "3. In your final message, report only the review outcome and any blockers. Do not include follow-up work for the human unless it is required to unblock review.",
1941
+ "",
1942
+ "### Repository Validation Guidance",
1943
+ "",
1944
+ ...validationGuidance.map((line, index) => `${index + 1}. ${line}`),
1945
+ "",
1946
+ "### Workflow",
1947
+ "",
1948
+ "1. Find the PR linked from the issue, project item, or issue timeline.",
1949
+ "2. Read the PR title, body, diff, linked issue, existing reviews, inline comments, and check status.",
1950
+ "3. Run the repository's relevant tests, lint, typecheck, or build commands when available and practical.",
1951
+ "4. Leave inline review comments for concrete, actionable findings.",
1952
+ "5. Submit a summary review: approve only when the change is correct and validation is acceptable; otherwise request changes.",
1953
+ "",
1954
+ "### Guardrails",
1955
+ "",
1956
+ "- Never push code from this posture.",
1957
+ "- Never approve PRs that introduce new dependencies without explicitly noting the dependency risk and why it is acceptable.",
1958
+ "- If relevant tests fail and the failure is not proven unrelated, request changes.",
1959
+ "- Keep comments specific to correctness, maintainability, tests, security, and issue fit.",
1960
+ "- Do not create a workpad; the review threads on the PR are the audit trail."
1961
+ ].join("\n");
1962
+ }
1963
+
1964
+ // src/skills/templates/gh-symphony-references/workflow-posture-maintain.ts
1965
+ function generateWorkflowPostureMaintainReference(ctx) {
1966
+ const validationGuidance = buildRepositoryValidationGuidance(
1967
+ ctx.detectedEnvironment
1895
1968
  );
1896
- lines.push("- Record all merge attempts and outcomes in the workpad");
1897
- return renderSkillDocument({
1898
- name: "land",
1899
- description: "Merge approved pull requests safely after verifying approvals, CI, and branch freshness.",
1900
- bodyLines: lines
1901
- });
1969
+ return [
1970
+ "# Workflow posture: maintain",
1971
+ "",
1972
+ "Use this prompt-body posture for low-risk maintenance such as dependency",
1973
+ "bumps, lint sweeps, small chores, and repository hygiene.",
1974
+ "",
1975
+ "## Agent Instructions",
1976
+ "",
1977
+ 'You are a maintenance coding agent working on issue `{issue.identifier}`: "`{issue.title}`".',
1978
+ "",
1979
+ "**Repository:** `{issue.repository}`",
1980
+ "**Current state:** `{issue.state}`",
1981
+ "",
1982
+ "### Task",
1983
+ "",
1984
+ "`{issue.description}`",
1985
+ "",
1986
+ "### Default Posture",
1987
+ "",
1988
+ "1. Make the smallest possible change that satisfies the maintenance request.",
1989
+ "2. Defer human-judgment calls instead of broadening scope.",
1990
+ "3. In your final message, report only what was completed and any blockers. Do not include optional next steps.",
1991
+ "",
1992
+ "### Repository Validation Guidance",
1993
+ "",
1994
+ ...validationGuidance.map((line, index) => `${index + 1}. ${line}`),
1995
+ "",
1996
+ "### Workflow",
1997
+ "",
1998
+ "1. Identify the exact maintenance task and affected files.",
1999
+ "2. Make the minimal change needed; avoid drive-by refactors.",
2000
+ "3. Run the relevant tests, lint, typecheck, or build commands for the affected area.",
2001
+ "4. Create a PR when the change is complete, or exit with a blocker note if approval is required.",
2002
+ "",
2003
+ "### Guardrails",
2004
+ "",
2005
+ "- Do not perform major dependency bumps without explicit approval.",
2006
+ "- Do not delete files without confirmation unless the issue explicitly requests it.",
2007
+ "- If the implementation exceeds 50 lines of non-generated code, stop and ask for human confirmation before continuing.",
2008
+ "- Do not mix unrelated cleanup into the maintenance change.",
2009
+ "",
2010
+ "### Workpad Template",
2011
+ "",
2012
+ "Create a compact workpad comment on the issue with the following structure:",
2013
+ "",
2014
+ "```md",
2015
+ "## Workpad",
2016
+ "",
2017
+ "### Plan",
2018
+ "",
2019
+ "- [ ] Minimal maintenance change",
2020
+ "- [ ] Validation and PR handoff",
2021
+ "",
2022
+ "### Validation",
2023
+ "",
2024
+ "- [ ] Test/lint/typecheck/build command",
2025
+ "",
2026
+ "### Blockers",
2027
+ "",
2028
+ "None",
2029
+ "```"
2030
+ ].join("\n");
1902
2031
  }
1903
2032
 
2033
+ // src/skills/templates/gh-symphony-references/index.ts
2034
+ var GH_SYMPHONY_REFERENCE_FILES = [
2035
+ {
2036
+ relativePath: "references/README.md",
2037
+ generate: generateGhSymphonyReferencesReadme
2038
+ },
2039
+ {
2040
+ relativePath: "references/workflow-schema.md",
2041
+ generate: generateWorkflowSchemaReference
2042
+ },
2043
+ {
2044
+ relativePath: "references/workflow-posture-implement.md",
2045
+ generate: generateWorkflowPostureImplementReference
2046
+ },
2047
+ {
2048
+ relativePath: "references/workflow-posture-review.md",
2049
+ generate: generateWorkflowPostureReviewReference
2050
+ },
2051
+ {
2052
+ relativePath: "references/workflow-posture-maintain.md",
2053
+ generate: generateWorkflowPostureMaintainReference
2054
+ }
2055
+ ];
2056
+
1904
2057
  // src/skills/templates/index.ts
1905
2058
  var ALL_SKILL_TEMPLATES = [
1906
2059
  {
1907
2060
  name: "gh-symphony",
1908
- fileName: "SKILL.md",
1909
- generate: generateGhSymphonySkill
2061
+ files: [
2062
+ { relativePath: "SKILL.md", generate: generateGhSymphonySkill },
2063
+ ...GH_SYMPHONY_REFERENCE_FILES
2064
+ ]
1910
2065
  },
1911
2066
  {
1912
2067
  name: "gh-project",
1913
- fileName: "SKILL.md",
1914
- generate: generateGhProjectSkill
2068
+ files: [{ relativePath: "SKILL.md", generate: generateGhProjectSkill }]
2069
+ },
2070
+ {
2071
+ name: "commit",
2072
+ files: [{ relativePath: "SKILL.md", generate: generateCommitSkill }]
2073
+ },
2074
+ {
2075
+ name: "push",
2076
+ files: [{ relativePath: "SKILL.md", generate: generatePushSkill }]
2077
+ },
2078
+ {
2079
+ name: "pull",
2080
+ files: [{ relativePath: "SKILL.md", generate: generatePullSkill }]
1915
2081
  },
1916
- { name: "commit", fileName: "SKILL.md", generate: generateCommitSkill },
1917
- { name: "push", fileName: "SKILL.md", generate: generatePushSkill },
1918
- { name: "pull", fileName: "SKILL.md", generate: generatePullSkill },
1919
- { name: "land", fileName: "SKILL.md", generate: generateLandSkill }
2082
+ {
2083
+ name: "land",
2084
+ files: [{ relativePath: "SKILL.md", generate: generateLandSkill }]
2085
+ }
1920
2086
  ];
1921
2087
 
1922
2088
  // src/commands/workflow-init.ts
@@ -1953,6 +2119,17 @@ async function abortIfCancelled(input) {
1953
2119
  }
1954
2120
  return result;
1955
2121
  }
2122
+ var SKIP_CONTEXT_DEPRECATION = "--skip-context is deprecated and is now a no-op. Repo-local .gh-symphony/context.yaml is no longer generated.";
2123
+ var LEGACY_GH_SYMPHONY_FILES = [
2124
+ {
2125
+ relativePath: ".gh-symphony/context.yaml",
2126
+ reason: "replaced by skill-local references/"
2127
+ },
2128
+ {
2129
+ relativePath: ".gh-symphony/reference-workflow.md",
2130
+ reason: null
2131
+ }
2132
+ ];
1956
2133
  function parseInitFlags(args) {
1957
2134
  const flags = {
1958
2135
  dryRun: false,
@@ -1993,6 +2170,9 @@ function parseInitFlags(args) {
1993
2170
  }
1994
2171
  return flags;
1995
2172
  }
2173
+ function warnDeprecatedSkipContext() {
2174
+ p.log.warn(SKIP_CONTEXT_DEPRECATION);
2175
+ }
1996
2176
  async function runInitRuntimePreflight(runtime) {
1997
2177
  if (!isClaudeRuntime(runtime)) {
1998
2178
  return true;
@@ -2015,6 +2195,9 @@ async function runInitRuntimePreflight(runtime) {
2015
2195
  }
2016
2196
  var handler = async (args, options) => {
2017
2197
  const flags = parseInitFlags(args);
2198
+ if (flags.skipContext) {
2199
+ warnDeprecatedSkipContext();
2200
+ }
2018
2201
  if (flags.nonInteractive) {
2019
2202
  await runNonInteractive(flags, options);
2020
2203
  return;
@@ -2087,6 +2270,62 @@ function runRuntimePreflight(runtime) {
2087
2270
  );
2088
2271
  }
2089
2272
  }
2273
+ async function findLegacyGhSymphonyFiles(cwd) {
2274
+ const found = [];
2275
+ for (const legacyFile of LEGACY_GH_SYMPHONY_FILES) {
2276
+ try {
2277
+ await readFile3(join3(cwd, legacyFile.relativePath), "utf8");
2278
+ found.push(legacyFile.relativePath);
2279
+ } catch {
2280
+ }
2281
+ }
2282
+ return found;
2283
+ }
2284
+ async function removeLegacyGhSymphonyFiles(cwd, legacyFiles) {
2285
+ const removed = [];
2286
+ for (const relativePath of legacyFiles) {
2287
+ await rm(join3(cwd, relativePath), { force: true });
2288
+ removed.push(relativePath);
2289
+ }
2290
+ const legacyDir = join3(cwd, ".gh-symphony");
2291
+ try {
2292
+ const remaining = await readdir2(legacyDir);
2293
+ if (remaining.length === 0) {
2294
+ await rmdir(legacyDir);
2295
+ }
2296
+ } catch {
2297
+ }
2298
+ return removed;
2299
+ }
2300
+ async function promptLegacyGhSymphonyCleanup(cwd) {
2301
+ const legacyFiles = await findLegacyGhSymphonyFiles(cwd);
2302
+ if (legacyFiles.length === 0) {
2303
+ return [];
2304
+ }
2305
+ const lines = [
2306
+ "Found legacy .gh-symphony/ directory.",
2307
+ "These files are no longer used:"
2308
+ ];
2309
+ for (const legacyFile of LEGACY_GH_SYMPHONY_FILES) {
2310
+ if (!legacyFiles.includes(legacyFile.relativePath)) {
2311
+ continue;
2312
+ }
2313
+ const suffix = legacyFile.reason ? ` (${legacyFile.reason})` : "";
2314
+ lines.push(` \u2022 ${legacyFile.relativePath}${suffix}`);
2315
+ }
2316
+ lines.push("Safe to delete.");
2317
+ p.log.info(lines.join("\n"));
2318
+ const removeFiles = await abortIfCancelled(
2319
+ p.confirm({
2320
+ message: "Remove legacy .gh-symphony/ files?",
2321
+ initialValue: false
2322
+ })
2323
+ );
2324
+ if (!removeFiles) {
2325
+ return [];
2326
+ }
2327
+ return removeLegacyGhSymphonyFiles(cwd, legacyFiles);
2328
+ }
2090
2329
  async function resolveChangeStatus(path, content, mode) {
2091
2330
  try {
2092
2331
  const existing = await readFile3(path, "utf8");
@@ -2112,15 +2351,18 @@ async function writePlannedFile(file) {
2112
2351
  if (file.status === "unchanged") {
2113
2352
  return false;
2114
2353
  }
2115
- await mkdir3(dirname2(file.path), { recursive: true });
2354
+ await mkdir2(dirname2(file.path), { recursive: true });
2116
2355
  const temporaryPath = `${file.path}.tmp`;
2117
- await writeFile3(temporaryPath, file.content, "utf8");
2356
+ await writeFile2(temporaryPath, file.content, "utf8");
2118
2357
  await rename2(temporaryPath, file.path);
2119
2358
  if (file.executable) {
2120
2359
  await chmod(file.path, 493);
2121
2360
  }
2122
2361
  return true;
2123
2362
  }
2363
+ function skillNameForPath(skillsDir, filePath) {
2364
+ return relative(skillsDir, filePath).split(/[\\/]/)[0] ?? "";
2365
+ }
2124
2366
  function resolveStatusField(projectDetail) {
2125
2367
  return projectDetail.statusFields.find((f) => f.name.toLowerCase() === "status") ?? projectDetail.statusFields[0] ?? null;
2126
2368
  }
@@ -2450,8 +2692,7 @@ async function planEcosystem(opts) {
2450
2692
  statusField,
2451
2693
  priorityField,
2452
2694
  runtime,
2453
- skipSkills,
2454
- skipContext
2695
+ skipSkills
2455
2696
  } = opts;
2456
2697
  const priority = opts.priority ?? (priorityField ? buildProjectFieldPriority(priorityField) : buildDisabledPriority());
2457
2698
  const automaticLifecycle = toWorkflowLifecycleConfig(
@@ -2467,7 +2708,6 @@ async function planEcosystem(opts) {
2467
2708
  planningStates: defaultBlockerCheckStates
2468
2709
  }
2469
2710
  );
2470
- const ghSymphonyDir = join3(cwd, ".gh-symphony");
2471
2711
  const environment = opts.environment ?? await detectEnvironment(cwd);
2472
2712
  const files = [];
2473
2713
  files.push(
@@ -2479,44 +2719,6 @@ async function planEcosystem(opts) {
2479
2719
  executable: true
2480
2720
  })
2481
2721
  );
2482
- if (!skipContext) {
2483
- const contextYaml = buildContextYaml({
2484
- projectDetail,
2485
- statusField,
2486
- detectedEnvironment: environment,
2487
- runtime: {
2488
- agent: runtime,
2489
- agent_command: resolveShellAgentCommand(runtime)
2490
- }
2491
- });
2492
- files.push(
2493
- await planFileChange({
2494
- path: join3(ghSymphonyDir, "context.yaml"),
2495
- label: "Context metadata",
2496
- content: generateContextYamlString(contextYaml),
2497
- mode: "overwrite"
2498
- })
2499
- );
2500
- }
2501
- const referenceWorkflow = generateReferenceWorkflow({
2502
- runtime,
2503
- statusColumns: statusField.options.map((o) => ({
2504
- name: o.name,
2505
- role: null
2506
- })),
2507
- projectId: projectDetail.id,
2508
- priority,
2509
- lifecycle,
2510
- detectedEnvironment: environment
2511
- });
2512
- files.push(
2513
- await planFileChange({
2514
- path: join3(ghSymphonyDir, "reference-workflow.md"),
2515
- label: "Reference workflow",
2516
- content: referenceWorkflow,
2517
- mode: "overwrite"
2518
- })
2519
- );
2520
2722
  const skillsDir = skipSkills ? null : resolveSkillsDir(cwd, runtime);
2521
2723
  if (!skipSkills && skillsDir) {
2522
2724
  const { files: plannedSkills } = buildSkillFilePlans(
@@ -2537,16 +2739,15 @@ async function planEcosystem(opts) {
2537
2739
  role: null
2538
2740
  })),
2539
2741
  statusFieldId: statusField.id,
2540
- contextYamlPath: ".gh-symphony/context.yaml",
2541
- referenceWorkflowPath: ".gh-symphony/reference-workflow.md",
2542
2742
  detectedEnvironment: environment
2543
2743
  }
2544
2744
  );
2545
2745
  for (const plannedSkill of plannedSkills) {
2746
+ const skillName = skillNameForPath(skillsDir, plannedSkill.path);
2546
2747
  files.push(
2547
2748
  await planFileChange({
2548
2749
  path: plannedSkill.path,
2549
- label: `Skill ${basename(dirname2(plannedSkill.path))}`,
2750
+ label: `Skill ${skillName}`,
2550
2751
  content: plannedSkill.content,
2551
2752
  mode: "create-only"
2552
2753
  })
@@ -2568,17 +2769,8 @@ async function planEcosystem(opts) {
2568
2769
  }
2569
2770
  async function writeEcosystem(opts) {
2570
2771
  const plan = await planEcosystem(opts);
2571
- await mkdir3(join3(opts.cwd, ".gh-symphony"), { recursive: true });
2572
2772
  const afterCreateHookPath = join3(opts.cwd, DEFAULT_AFTER_CREATE_HOOK_PATH);
2573
- const contextYamlPath = join3(opts.cwd, ".gh-symphony", "context.yaml");
2574
- const referenceWorkflowPath = join3(
2575
- opts.cwd,
2576
- ".gh-symphony",
2577
- "reference-workflow.md"
2578
- );
2579
2773
  let afterCreateHookWritten = false;
2580
- let contextYamlWritten = false;
2581
- let referenceWorkflowWritten = false;
2582
2774
  const skillsWritten = [];
2583
2775
  const skillsSkipped = [];
2584
2776
  for (const file of plan.files) {
@@ -2587,16 +2779,8 @@ async function writeEcosystem(opts) {
2587
2779
  afterCreateHookWritten = written;
2588
2780
  continue;
2589
2781
  }
2590
- if (file.path === contextYamlPath) {
2591
- contextYamlWritten = written;
2592
- continue;
2593
- }
2594
- if (file.path === referenceWorkflowPath) {
2595
- referenceWorkflowWritten = written;
2596
- continue;
2597
- }
2598
2782
  if (file.label.startsWith("Skill ")) {
2599
- const skillName = basename(dirname2(file.path));
2783
+ const skillName = file.label.slice("Skill ".length);
2600
2784
  if (written) {
2601
2785
  skillsWritten.push(skillName);
2602
2786
  } else {
@@ -2614,10 +2798,11 @@ async function writeEcosystem(opts) {
2614
2798
  skillsDir: plan.skillsDir,
2615
2799
  skipSkills: plan.skipSkills,
2616
2800
  afterCreateHookWritten,
2617
- contextYamlWritten,
2618
- referenceWorkflowWritten,
2619
- skillsWritten: skillsWritten.sort(),
2620
- skillsSkipped: skillsSkipped.sort()
2801
+ contextYamlWritten: false,
2802
+ referenceWorkflowWritten: false,
2803
+ skillsWritten: [...new Set(skillsWritten)].sort(),
2804
+ skillsSkipped: [...new Set(skillsSkipped)].sort(),
2805
+ legacyFilesRemoved: []
2621
2806
  };
2622
2807
  }
2623
2808
  function formatPrioritySummaryLines(priority) {
@@ -2669,16 +2854,6 @@ function printEcosystemSummary(result, workflowPath, opts) {
2669
2854
  ` \u2713 ${DEFAULT_AFTER_CREATE_HOOK_LABEL.padEnd(36)} ${DEFAULT_AFTER_CREATE_HOOK_PATH}`
2670
2855
  );
2671
2856
  }
2672
- if (result.contextYamlWritten) {
2673
- lines.push(
2674
- " \u2713 Context metadata .gh-symphony/context.yaml"
2675
- );
2676
- }
2677
- if (result.referenceWorkflowWritten) {
2678
- lines.push(
2679
- " \u2713 Reference workflow .gh-symphony/reference-workflow.md"
2680
- );
2681
- }
2682
2857
  if (result.skillsDir) {
2683
2858
  const relSkillsDir = relative(cwd, result.skillsDir);
2684
2859
  lines.push("");
@@ -3052,6 +3227,7 @@ async function runInteractiveStandalone(flags, _options) {
3052
3227
  printDryRunPreview(outputPath, workflowPlan, ecosystemPlan);
3053
3228
  return;
3054
3229
  }
3230
+ await promptLegacyGhSymphonyCleanup(process.cwd());
3055
3231
  await writeWorkflowPlan(workflowPlan);
3056
3232
  const ecosystemResult = await writeEcosystem({
3057
3233
  cwd: process.cwd(),
@@ -3075,7 +3251,9 @@ export {
3075
3251
  toWorkflowLifecycleConfig,
3076
3252
  validateStateMapping,
3077
3253
  abortIfCancelled,
3254
+ warnDeprecatedSkipContext,
3078
3255
  workflow_init_default,
3256
+ promptLegacyGhSymphonyCleanup,
3079
3257
  resolveStatusField,
3080
3258
  buildAutomaticStateMappings,
3081
3259
  resolvePriorityField,