@mizyoel/mercury-mesh 0.9.4

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.
Files changed (128) hide show
  1. package/.copilot/mcp-config.json +14 -0
  2. package/.copilot/skills/agent-collaboration/SKILL.md +42 -0
  3. package/.copilot/skills/agent-conduct/SKILL.md +24 -0
  4. package/.copilot/skills/architectural-proposals/SKILL.md +151 -0
  5. package/.copilot/skills/ci-validation-gates/SKILL.md +84 -0
  6. package/.copilot/skills/cli-wiring/SKILL.md +47 -0
  7. package/.copilot/skills/client-compatibility/SKILL.md +89 -0
  8. package/.copilot/skills/cross-mesh/SKILL.md +114 -0
  9. package/.copilot/skills/distributed-mesh/SKILL.md +287 -0
  10. package/.copilot/skills/distributed-mesh/mesh.json.example +30 -0
  11. package/.copilot/skills/distributed-mesh/sync-mesh.ps1 +111 -0
  12. package/.copilot/skills/distributed-mesh/sync-mesh.sh +104 -0
  13. package/.copilot/skills/docs-standards/SKILL.md +71 -0
  14. package/.copilot/skills/economy-mode/SKILL.md +101 -0
  15. package/.copilot/skills/external-comms/SKILL.md +331 -0
  16. package/.copilot/skills/gh-auth-isolation/SKILL.md +183 -0
  17. package/.copilot/skills/git-workflow/SKILL.md +206 -0
  18. package/.copilot/skills/github-multi-account/SKILL.md +95 -0
  19. package/.copilot/skills/history-hygiene/SKILL.md +36 -0
  20. package/.copilot/skills/humanizer/SKILL.md +107 -0
  21. package/.copilot/skills/init-mode/SKILL.md +101 -0
  22. package/.copilot/skills/mesh-conventions/SKILL.md +69 -0
  23. package/.copilot/skills/model-selection/SKILL.md +139 -0
  24. package/.copilot/skills/nap/SKILL.md +24 -0
  25. package/.copilot/skills/personal-mesh/SKILL.md +57 -0
  26. package/.copilot/skills/project-conventions/SKILL.md +56 -0
  27. package/.copilot/skills/release-process/SKILL.md +435 -0
  28. package/.copilot/skills/reskill/SKILL.md +92 -0
  29. package/.copilot/skills/reviewer-protocol/SKILL.md +79 -0
  30. package/.copilot/skills/secret-handling/SKILL.md +200 -0
  31. package/.copilot/skills/session-recovery/SKILL.md +155 -0
  32. package/.copilot/skills/test-discipline/SKILL.md +37 -0
  33. package/.copilot/skills/windows-compatibility/SKILL.md +74 -0
  34. package/.github/agents/mercury-mesh.agent.md +1732 -0
  35. package/.mesh/manifesto.md +66 -0
  36. package/.mesh/templates/casting/Futurama.json +10 -0
  37. package/.mesh/templates/casting-history.json +4 -0
  38. package/.mesh/templates/casting-policy.json +37 -0
  39. package/.mesh/templates/casting-reference.md +104 -0
  40. package/.mesh/templates/casting-registry.json +3 -0
  41. package/.mesh/templates/ceremonies.md +41 -0
  42. package/.mesh/templates/charter.md +56 -0
  43. package/.mesh/templates/constraint-tracking.md +38 -0
  44. package/.mesh/templates/cooperative-rate-limiting.md +229 -0
  45. package/.mesh/templates/copilot-instructions.md +50 -0
  46. package/.mesh/templates/department-backlog.md +15 -0
  47. package/.mesh/templates/department-charter.md +27 -0
  48. package/.mesh/templates/department-state.json +19 -0
  49. package/.mesh/templates/history.md +10 -0
  50. package/.mesh/templates/identity/now.md +9 -0
  51. package/.mesh/templates/identity/wisdom.md +15 -0
  52. package/.mesh/templates/interface-contract.md +26 -0
  53. package/.mesh/templates/issue-lifecycle.md +421 -0
  54. package/.mesh/templates/keda-scaler.md +166 -0
  55. package/.mesh/templates/machine-capabilities.md +77 -0
  56. package/.mesh/templates/mcp-config.md +90 -0
  57. package/.mesh/templates/mercury-mesh.agent.md +1732 -0
  58. package/.mesh/templates/multi-agent-format.md +28 -0
  59. package/.mesh/templates/orchestration-log.md +27 -0
  60. package/.mesh/templates/org-autonomy-spec.md +152 -0
  61. package/.mesh/templates/org-backlog-from-triage.js +199 -0
  62. package/.mesh/templates/org-runtime-reconcile.js +364 -0
  63. package/.mesh/templates/org-seed-runtime.js +238 -0
  64. package/.mesh/templates/org-status.js +193 -0
  65. package/.mesh/templates/org-structure.json +38 -0
  66. package/.mesh/templates/package.json +3 -0
  67. package/.mesh/templates/plugin-marketplace.md +49 -0
  68. package/.mesh/templates/ralph-circuit-breaker.md +313 -0
  69. package/.mesh/templates/ralph-triage.js +844 -0
  70. package/.mesh/templates/raw-agent-output.md +37 -0
  71. package/.mesh/templates/roster.md +60 -0
  72. package/.mesh/templates/routing.md +78 -0
  73. package/.mesh/templates/run-output.md +50 -0
  74. package/.mesh/templates/schedule.json +64 -0
  75. package/.mesh/templates/scribe-charter.md +119 -0
  76. package/.mesh/templates/skill.md +24 -0
  77. package/.mesh/templates/skills/agent-collaboration/SKILL.md +42 -0
  78. package/.mesh/templates/skills/agent-conduct/SKILL.md +24 -0
  79. package/.mesh/templates/skills/architectural-proposals/SKILL.md +151 -0
  80. package/.mesh/templates/skills/ci-validation-gates/SKILL.md +84 -0
  81. package/.mesh/templates/skills/cli-wiring/SKILL.md +47 -0
  82. package/.mesh/templates/skills/client-compatibility/SKILL.md +89 -0
  83. package/.mesh/templates/skills/cross-mesh/SKILL.md +114 -0
  84. package/.mesh/templates/skills/distributed-mesh/SKILL.md +287 -0
  85. package/.mesh/templates/skills/distributed-mesh/mesh.json.example +30 -0
  86. package/.mesh/templates/skills/distributed-mesh/sync-mesh.ps1 +111 -0
  87. package/.mesh/templates/skills/distributed-mesh/sync-mesh.sh +104 -0
  88. package/.mesh/templates/skills/docs-standards/SKILL.md +71 -0
  89. package/.mesh/templates/skills/economy-mode/SKILL.md +101 -0
  90. package/.mesh/templates/skills/external-comms/SKILL.md +331 -0
  91. package/.mesh/templates/skills/gh-auth-isolation/SKILL.md +183 -0
  92. package/.mesh/templates/skills/git-workflow/SKILL.md +204 -0
  93. package/.mesh/templates/skills/github-multi-account/SKILL.md +95 -0
  94. package/.mesh/templates/skills/history-hygiene/SKILL.md +36 -0
  95. package/.mesh/templates/skills/humanizer/SKILL.md +107 -0
  96. package/.mesh/templates/skills/init-mode/SKILL.md +101 -0
  97. package/.mesh/templates/skills/mesh-conventions/SKILL.md +69 -0
  98. package/.mesh/templates/skills/model-selection/SKILL.md +139 -0
  99. package/.mesh/templates/skills/nap/SKILL.md +24 -0
  100. package/.mesh/templates/skills/personal-mesh/SKILL.md +57 -0
  101. package/.mesh/templates/skills/project-conventions/SKILL.md +56 -0
  102. package/.mesh/templates/skills/release-process/SKILL.md +435 -0
  103. package/.mesh/templates/skills/reskill/SKILL.md +92 -0
  104. package/.mesh/templates/skills/reviewer-protocol/SKILL.md +79 -0
  105. package/.mesh/templates/skills/secret-handling/SKILL.md +200 -0
  106. package/.mesh/templates/skills/session-recovery/SKILL.md +155 -0
  107. package/.mesh/templates/skills/test-discipline/SKILL.md +37 -0
  108. package/.mesh/templates/skills/windows-compatibility/SKILL.md +74 -0
  109. package/.mesh/templates/workflows/mesh-ci.yml +24 -0
  110. package/.mesh/templates/workflows/mesh-docs.yml +54 -0
  111. package/.mesh/templates/workflows/mesh-heartbeat.yml +237 -0
  112. package/.mesh/templates/workflows/mesh-insider-release.yml +61 -0
  113. package/.mesh/templates/workflows/mesh-issue-assign.yml +243 -0
  114. package/.mesh/templates/workflows/mesh-label-enforce.yml +181 -0
  115. package/.mesh/templates/workflows/mesh-preview.yml +55 -0
  116. package/.mesh/templates/workflows/mesh-promote.yml +120 -0
  117. package/.mesh/templates/workflows/mesh-release.yml +77 -0
  118. package/.mesh/templates/workflows/mesh-triage.yml +383 -0
  119. package/.mesh/templates/workflows/sync-mesh-labels.yml +204 -0
  120. package/README.md +640 -0
  121. package/bin/mercury-mesh.cjs +317 -0
  122. package/docs/brand-language.md +287 -0
  123. package/docs/commander-onboarding.md +462 -0
  124. package/docs/mercury-mesh-runtime-rename-impact.md +148 -0
  125. package/docs/persona-manifesto.md +114 -0
  126. package/docs/scenarios/client-compatibility.md +59 -0
  127. package/index.cjs +41 -0
  128. package/package.json +43 -0
@@ -0,0 +1,237 @@
1
+ name: Mercury Mesh Heartbeat (Ralph)
2
+ # ⚠️ SYNC: This workflow is maintained in 4 locations. Changes must be applied to all:
3
+ # - templates/workflows/mesh-heartbeat.yml (source template)
4
+ # - packages/mesh-cli/templates/workflows/mesh-heartbeat.yml (CLI package)
5
+ # - .mesh/templates/workflows/mesh-heartbeat.yml (installed template)
6
+ # - .github/workflows/mesh-heartbeat.yml (active workflow)
7
+
8
+ on:
9
+ schedule:
10
+ # Every 30 minutes — adjust via cron expression as needed
11
+ - cron: '*/30 * * * *'
12
+
13
+ # React to completed work or new bridge work
14
+ issues:
15
+ types: [closed, labeled]
16
+ pull_request:
17
+ types: [closed]
18
+
19
+ # Manual trigger
20
+ workflow_dispatch:
21
+
22
+ permissions:
23
+ issues: write
24
+ contents: read
25
+ pull-requests: read
26
+
27
+ jobs:
28
+ heartbeat:
29
+ runs-on: ubuntu-latest
30
+ steps:
31
+ - uses: actions/checkout@v4
32
+
33
+ - name: Resolve runtime
34
+ id: resolve-runtime
35
+ run: |
36
+ runtime_dir=""
37
+ for candidate in .mesh .mercury; do
38
+ if [ -f "$candidate/templates/ralph-triage.js" ]; then
39
+ runtime_dir="$candidate"
40
+ break
41
+ fi
42
+ done
43
+
44
+ if [ -n "$runtime_dir" ]; then
45
+ echo "runtime_dir=$runtime_dir" >> $GITHUB_OUTPUT
46
+ echo "templates_dir=$runtime_dir/templates" >> $GITHUB_OUTPUT
47
+ echo "runtime_arg=--mesh-dir" >> $GITHUB_OUTPUT
48
+ echo "has_script=true" >> $GITHUB_OUTPUT
49
+ if [ -f "$runtime_dir/templates/org-runtime-reconcile.js" ] && [ -f "$runtime_dir/org/structure.json" ]; then
50
+ echo "has_org_runtime=true" >> $GITHUB_OUTPUT
51
+ else
52
+ echo "has_org_runtime=false" >> $GITHUB_OUTPUT
53
+ fi
54
+ if [ -f "$runtime_dir/templates/org-backlog-from-triage.js" ] && [ -f "$runtime_dir/org/structure.json" ]; then
55
+ echo "has_backlog_bridge=true" >> $GITHUB_OUTPUT
56
+ else
57
+ echo "has_backlog_bridge=false" >> $GITHUB_OUTPUT
58
+ fi
59
+ else
60
+ echo "has_script=false" >> $GITHUB_OUTPUT
61
+ echo "has_org_runtime=false" >> $GITHUB_OUTPUT
62
+ echo "has_backlog_bridge=false" >> $GITHUB_OUTPUT
63
+ echo "⚠️ ralph-triage.js not found — ensure .mesh/templates/ is populated"
64
+ fi
65
+
66
+ - name: Ralph — Smart triage
67
+ if: steps.resolve-runtime.outputs.has_script == 'true'
68
+ env:
69
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
70
+ run: |
71
+ node ${{ steps.resolve-runtime.outputs.templates_dir }}/ralph-triage.js \
72
+ ${{ steps.resolve-runtime.outputs.runtime_arg }} ${{ steps.resolve-runtime.outputs.runtime_dir }} \
73
+ --output triage-results.json
74
+
75
+ - name: Ralph — Org runtime reconcile report
76
+ if: steps.resolve-runtime.outputs.has_org_runtime == 'true'
77
+ run: |
78
+ node ${{ steps.resolve-runtime.outputs.templates_dir }}/org-runtime-reconcile.js \
79
+ ${{ steps.resolve-runtime.outputs.runtime_arg }} ${{ steps.resolve-runtime.outputs.runtime_dir }} \
80
+ --output org-runtime-results.json
81
+
82
+ - name: Ralph — Show org runtime reconcile report
83
+ if: hashFiles('org-runtime-results.json') != ''
84
+ run: |
85
+ echo "Org runtime reconcile results:"
86
+ cat org-runtime-results.json
87
+
88
+ - name: Ralph — Org backlog packet preview
89
+ if: steps.resolve-runtime.outputs.has_backlog_bridge == 'true' && hashFiles('triage-results.json') != ''
90
+ run: |
91
+ node ${{ steps.resolve-runtime.outputs.templates_dir }}/org-backlog-from-triage.js \
92
+ ${{ steps.resolve-runtime.outputs.runtime_arg }} ${{ steps.resolve-runtime.outputs.runtime_dir }} \
93
+ --triage-file triage-results.json \
94
+ --output org-backlog-results.json
95
+
96
+ - name: Ralph — Show org backlog packet preview
97
+ if: hashFiles('org-backlog-results.json') != ''
98
+ run: |
99
+ echo "Org backlog packet preview:"
100
+ cat org-backlog-results.json
101
+
102
+ - name: Ralph — Apply triage decisions
103
+ if: steps.resolve-runtime.outputs.has_script == 'true' && hashFiles('triage-results.json') != ''
104
+ uses: actions/github-script@v7
105
+ with:
106
+ script: |
107
+ const fs = require('fs');
108
+ const path = 'triage-results.json';
109
+ if (!fs.existsSync(path)) {
110
+ core.info('No triage results — board is clear');
111
+ return;
112
+ }
113
+
114
+ const results = JSON.parse(fs.readFileSync(path, 'utf8'));
115
+ if (results.length === 0) {
116
+ core.info('📋 Board is clear — Ralph found no untriaged issues');
117
+ return;
118
+ }
119
+
120
+ for (const decision of results) {
121
+ try {
122
+ const labels = Array.isArray(decision.labels) && decision.labels.length > 0
123
+ ? decision.labels
124
+ : [decision.label];
125
+ await github.rest.issues.addLabels({
126
+ owner: context.repo.owner,
127
+ repo: context.repo.repo,
128
+ issue_number: decision.issueNumber,
129
+ labels
130
+ });
131
+
132
+ await github.rest.issues.createComment({
133
+ owner: context.repo.owner,
134
+ repo: context.repo.repo,
135
+ issue_number: decision.issueNumber,
136
+ body: [
137
+ '### 🔄 Ralph — Auto-Triage',
138
+ '',
139
+ `**Assigned to:** ${decision.assignTo}`,
140
+ decision.departmentName ? `**Department:** ${decision.departmentName}` : '',
141
+ `**Reason:** ${decision.reason}`,
142
+ `**Source:** ${decision.source}`,
143
+ '',
144
+ '> Ralph auto-triaged this issue using Mercury Mesh routing rules.',
145
+ '> To reassign, swap the assigned `mesh:*` label. Keep any `dept:*` labels aligned with the new routing.'
146
+ ].join('\n')
147
+ });
148
+
149
+ core.info(`Triaged #${decision.issueNumber} → ${decision.assignTo} (${decision.source}${decision.departmentId ? `, dept:${decision.departmentId}` : ''})`);
150
+ } catch (e) {
151
+ core.warning(`Failed to triage #${decision.issueNumber}: ${e.message}`);
152
+ }
153
+ }
154
+
155
+ core.info(`🔄 Ralph triaged ${results.length} issue(s)`);
156
+
157
+ # Copilot auto-assign step (uses PAT if available)
158
+ - name: Ralph — Assign @copilot issues
159
+ if: success()
160
+ uses: actions/github-script@v7
161
+ with:
162
+ github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN || secrets.GITHUB_TOKEN }}
163
+ script: |
164
+ const fs = require('fs');
165
+ const runtimeCandidates = ['.mesh', '.mercury', '.ai-team'];
166
+ const stateCandidates = ['.mesh', '.mercury'];
167
+ const findFirst = (candidates) => candidates.find((candidate) => fs.existsSync(candidate)) || null;
168
+
169
+ const teamFile = findFirst(runtimeCandidates.map((candidate) => `${candidate}/team.md`));
170
+ if (!teamFile || !fs.existsSync(teamFile)) return;
171
+
172
+ const runtimeDir = teamFile.slice(0, teamFile.lastIndexOf('/'));
173
+ const stateDir = runtimeDir !== '.ai-team'
174
+ ? runtimeDir
175
+ : (findFirst(stateCandidates.map((candidate) => `${candidate}/config.json`)) || '.mesh/config.json').replace(/\/config\.json$/, '');
176
+ const routingFile = findFirst([`${runtimeDir}/routing.md`, '.ai-team/routing.md']) || `${runtimeDir}/routing.md`;
177
+ const structurePath = `${stateDir}/org/structure.json`;
178
+
179
+ const content = fs.readFileSync(teamFile, 'utf8');
180
+
181
+ // Check if @copilot is on the team with auto-assign
182
+ const hasCopilot = content.includes('🤖 Coding Agent') || content.includes('@copilot');
183
+ const autoAssign = content.includes('<!-- copilot-auto-assign: true -->');
184
+ if (!hasCopilot || !autoAssign) return;
185
+
186
+ // Find issues labeled mesh:copilot with no assignee.
187
+ try {
188
+ const { data: openIssues } = await github.rest.issues.listForRepo({
189
+ owner: context.repo.owner,
190
+ repo: context.repo.repo,
191
+ state: 'open',
192
+ per_page: 100
193
+ });
194
+
195
+ const copilotIssues = openIssues.filter((issue) => {
196
+ const labels = Array.isArray(issue.labels)
197
+ ? issue.labels.map((item) => typeof item === 'string' ? item : item && item.name).filter(Boolean)
198
+ : [];
199
+ return labels.includes('mesh:copilot');
200
+ });
201
+
202
+ const unassigned = copilotIssues.filter(i =>
203
+ !i.assignees || i.assignees.length === 0
204
+ );
205
+
206
+ if (unassigned.length === 0) {
207
+ core.info('No unassigned mesh:copilot issues');
208
+ return;
209
+ }
210
+
211
+ // Get repo default branch
212
+ const { data: repoData } = await github.rest.repos.get({
213
+ owner: context.repo.owner,
214
+ repo: context.repo.repo
215
+ });
216
+
217
+ for (const issue of unassigned) {
218
+ try {
219
+ await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/assignees', {
220
+ owner: context.repo.owner,
221
+ repo: context.repo.repo,
222
+ issue_number: issue.number,
223
+ assignees: ['copilot-swe-agent[bot]'],
224
+ agent_assignment: {
225
+ target_repo: `${context.repo.owner}/${context.repo.repo}`,
226
+ base_branch: repoData.default_branch,
227
+ custom_instructions: `Read ${teamFile} for team context, ${routingFile} for routing rules, and if orgMode is enabled read ${structurePath} for department context.`
228
+ }
229
+ });
230
+ core.info(`Assigned copilot-swe-agent[bot] to #${issue.number}`);
231
+ } catch (e) {
232
+ core.warning(`Failed to assign @copilot to #${issue.number}: ${e.message}`);
233
+ }
234
+ }
235
+ } catch (e) {
236
+ core.info(`No compatible copilot label found or error: ${e.message}`);
237
+ }
@@ -0,0 +1,61 @@
1
+ name: Mercury Mesh Insider Release
2
+
3
+ on:
4
+ push:
5
+ branches: [insider]
6
+
7
+ permissions:
8
+ contents: write
9
+
10
+ jobs:
11
+ release:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ with:
16
+ fetch-depth: 0
17
+
18
+ - uses: actions/setup-node@v4
19
+ with:
20
+ node-version: 22
21
+
22
+ - name: Run tests
23
+ run: node --test test/*.test.js
24
+
25
+ - name: Read version from package.json
26
+ id: version
27
+ run: |
28
+ VERSION=$(node -e "console.log(require('./package.json').version)")
29
+ SHORT_SHA=$(git rev-parse --short HEAD)
30
+ INSIDER_VERSION="${VERSION}-insider+${SHORT_SHA}"
31
+ INSIDER_TAG="v${INSIDER_VERSION}"
32
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
33
+ echo "short_sha=$SHORT_SHA" >> "$GITHUB_OUTPUT"
34
+ echo "insider_version=$INSIDER_VERSION" >> "$GITHUB_OUTPUT"
35
+ echo "insider_tag=$INSIDER_TAG" >> "$GITHUB_OUTPUT"
36
+ echo "📦 Base Version: $VERSION (Short SHA: $SHORT_SHA)"
37
+ echo "🏷️ Insider Version: $INSIDER_VERSION"
38
+ echo "🔖 Insider Tag: $INSIDER_TAG"
39
+
40
+ - name: Create git tag
41
+ run: |
42
+ git config user.name "github-actions[bot]"
43
+ git config user.email "github-actions[bot]@users.noreply.github.com"
44
+ git tag -a "${{ steps.version.outputs.insider_tag }}" -m "Insider Release ${{ steps.version.outputs.insider_tag }}"
45
+ git push origin "${{ steps.version.outputs.insider_tag }}"
46
+
47
+ - name: Create GitHub Release
48
+ env:
49
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
50
+ run: |
51
+ gh release create "${{ steps.version.outputs.insider_tag }}" \
52
+ --title "${{ steps.version.outputs.insider_tag }}" \
53
+ --notes "This is an insider/development build of Mercury Mesh. Install or upgrade with:\`\`\`bash\nnpm install -g @f-os/mercury-mesh-cli@${{ steps.version.outputs.insider_tag }}\n\`\`\`\n\n**Note:** Insider builds may be unstable and are intended for early adopters and testing only." \
54
+ --prerelease
55
+
56
+ - name: Verify release
57
+ env:
58
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
59
+ run: |
60
+ gh release view "${{ steps.version.outputs.insider_tag }}"
61
+ echo "✅ Insider Release ${{ steps.version.outputs.insider_tag }} created and verified."
@@ -0,0 +1,243 @@
1
+ name: Mercury Mesh Issue Assign
2
+
3
+ on:
4
+ issues:
5
+ types: [labeled]
6
+
7
+ permissions:
8
+ issues: write
9
+ contents: read
10
+
11
+ jobs:
12
+ assign-work:
13
+ # Trigger on mesh: label family.
14
+ if: startsWith(github.event.label.name, 'mesh:')
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Identify assigned member and trigger work
20
+ uses: actions/github-script@v7
21
+ with:
22
+ script: |
23
+ const fs = require('fs');
24
+ const issue = context.payload.issue;
25
+ const label = context.payload.label.name;
26
+ const runtimeCandidates = ['.mesh', '.mercury', '.ai-team'];
27
+ const stateCandidates = ['.mesh', '.mercury'];
28
+ const findFirst = (candidates) => candidates.find((candidate) => fs.existsSync(candidate)) || null;
29
+
30
+ const labelPrefix = 'mesh';
31
+ const memberName = label.replace(/^mesh:/, '').toLowerCase();
32
+
33
+ const teamFile = findFirst(runtimeCandidates.map((candidate) => `${candidate}/team.md`));
34
+ if (!teamFile || !fs.existsSync(teamFile)) {
35
+ core.warning('No .mesh/team.md, .mercury/team.md, or .ai-team/team.md found — cannot route Mercury Mesh work');
36
+ return;
37
+ }
38
+
39
+ const runtimeDir = teamFile.slice(0, teamFile.lastIndexOf('/'));
40
+ const stateDir = runtimeDir !== '.ai-team'
41
+ ? runtimeDir
42
+ : (findFirst(stateCandidates.map((candidate) => `${candidate}/config.json`)) || '.mesh/config.json').replace(/\/config\.json$/, '');
43
+
44
+ const content = fs.readFileSync(teamFile, 'utf8');
45
+ const lines = content.split('\n');
46
+ const members = [];
47
+ let inMembersTable = false;
48
+ for (const line of lines) {
49
+ if (line.match(/^##\s+(Members|Team Roster)/i)) {
50
+ inMembersTable = true;
51
+ continue;
52
+ }
53
+ if (inMembersTable && line.startsWith('## ')) {
54
+ break;
55
+ }
56
+ if (inMembersTable && line.startsWith('|') && !line.includes('---') && !line.includes('Name')) {
57
+ const cells = line.split('|').map(c => c.trim()).filter(Boolean);
58
+ if (cells.length >= 2) {
59
+ members.push({
60
+ name: cells[0],
61
+ role: cells[1]
62
+ });
63
+ }
64
+ }
65
+ }
66
+ const normalize = (value = '') => String(value).trim().toLowerCase();
67
+ const memberByName = new Map(members.map(member => [normalize(member.name), member]));
68
+
69
+ let orgConfig = null;
70
+ let orgStructure = null;
71
+ try {
72
+ if (fs.existsSync(`${stateDir}/config.json`)) {
73
+ orgConfig = JSON.parse(fs.readFileSync(`${stateDir}/config.json`, 'utf8'));
74
+ }
75
+ } catch (err) {
76
+ core.warning(`Could not parse ${stateDir}/config.json: ${err.message}`);
77
+ }
78
+ try {
79
+ if (fs.existsSync(`${stateDir}/org/structure.json`)) {
80
+ orgStructure = JSON.parse(fs.readFileSync(`${stateDir}/org/structure.json`, 'utf8'));
81
+ }
82
+ } catch (err) {
83
+ core.warning(`Could not parse ${stateDir}/org/structure.json: ${err.message}`);
84
+ }
85
+
86
+ const orgModeEnabled = !!(orgConfig && orgConfig.orgMode && orgStructure && Array.isArray(orgStructure.departments));
87
+ const findDepartmentForMember = (name) => {
88
+ if (!orgModeEnabled) return null;
89
+ const key = normalize(name);
90
+ return orgStructure.departments.find(dept =>
91
+ [dept.lead, ...(dept.members || [])]
92
+ .filter(Boolean)
93
+ .some(candidate => normalize(candidate) === key)
94
+ ) || null;
95
+ };
96
+ const issueLabels = Array.isArray(issue.labels)
97
+ ? issue.labels.map(item => typeof item === 'string' ? item : item && item.name).filter(Boolean)
98
+ : [];
99
+ const deptLabel = issueLabels.find(name => name.startsWith('dept:'));
100
+ let assignedDepartment = null;
101
+ if (deptLabel) {
102
+ const deptId = deptLabel.replace('dept:', '').toLowerCase();
103
+ assignedDepartment = orgModeEnabled
104
+ ? orgStructure.departments.find(dept => normalize(dept.id) === deptId) || null
105
+ : { id: deptId, name: deptId, lead: null };
106
+ }
107
+ const formatMemberName = (name) => memberByName.get(normalize(name))?.name || name;
108
+
109
+ // Check if this is a coding agent assignment
110
+ const isCopilotAssignment = memberName === 'copilot';
111
+
112
+ let assignedMember = null;
113
+ if (isCopilotAssignment) {
114
+ assignedMember = { name: '@copilot', role: 'Coding Agent' };
115
+ } else {
116
+ assignedMember = memberByName.get(memberName) || null;
117
+ if (!assignedDepartment && assignedMember) {
118
+ assignedDepartment = findDepartmentForMember(assignedMember.name);
119
+ }
120
+ }
121
+
122
+ if (!assignedMember) {
123
+ core.warning(`No member found matching label "${label}"`);
124
+ await github.rest.issues.createComment({
125
+ owner: context.repo.owner,
126
+ repo: context.repo.repo,
127
+ issue_number: issue.number,
128
+ body: `⚠️ No Mercury Mesh member found matching label \`${label}\`. Check \`${teamFile}\` for valid member names.`
129
+ });
130
+ return;
131
+ }
132
+
133
+ // Post assignment acknowledgment
134
+ const departmentLine = assignedDepartment
135
+ ? `**Department:** ${assignedDepartment.name}${assignedDepartment.lead ? ` (lead: ${formatMemberName(assignedDepartment.lead)})` : ''}`
136
+ : '';
137
+ let comment;
138
+ if (isCopilotAssignment) {
139
+ comment = [
140
+ `### 🤖 Routed to @copilot (Coding Agent)`,
141
+ '',
142
+ `**Issue:** #${issue.number} — ${issue.title}`,
143
+ departmentLine,
144
+ '',
145
+ `@copilot has been assigned and will pick this up automatically on the bridge.`,
146
+ '',
147
+ `> The coding agent will create a \`copilot/*\` branch and open a draft PR.`,
148
+ `> Review the PR as you would any Mercury Mesh specialist's work.`,
149
+ ].join('\n');
150
+ } else {
151
+ comment = [
152
+ `### 📋 Assigned to ${assignedMember.name} (${assignedMember.role})`,
153
+ '',
154
+ `**Issue:** #${issue.number} — ${issue.title}`,
155
+ departmentLine,
156
+ '',
157
+ `${assignedMember.name} will pick this up in the next Copilot session on the bridge.`,
158
+ '',
159
+ `> **For Copilot coding agent:** If enabled, this issue will be worked automatically.`,
160
+ `> Otherwise, start a Copilot session and say:`,
161
+ `> \`${assignedMember.name}, work on issue #${issue.number}\``,
162
+ ].join('\n');
163
+ }
164
+
165
+ await github.rest.issues.createComment({
166
+ owner: context.repo.owner,
167
+ repo: context.repo.repo,
168
+ issue_number: issue.number,
169
+ body: comment
170
+ });
171
+
172
+ core.info(`Issue #${issue.number} assigned to ${assignedMember.name} (${assignedMember.role})`);
173
+
174
+ # Separate step: assign @copilot using PAT (required for coding agent)
175
+ - name: Assign @copilot coding agent
176
+ if: github.event.label.name == 'mesh:copilot'
177
+ uses: actions/github-script@v7
178
+ with:
179
+ github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN }}
180
+ script: |
181
+ const fs = require('fs');
182
+ const owner = context.repo.owner;
183
+ const repo = context.repo.repo;
184
+ const issue_number = context.payload.issue.number;
185
+ const issue = context.payload.issue;
186
+ const runtimeCandidates = ['.mesh', '.mercury', '.ai-team'];
187
+ const stateCandidates = ['.mesh', '.mercury'];
188
+ const findFirst = (candidates) => candidates.find((candidate) => fs.existsSync(candidate)) || null;
189
+ const teamFile = findFirst(runtimeCandidates.map((candidate) => `${candidate}/team.md`));
190
+ const runtimeDir = teamFile ? teamFile.slice(0, teamFile.lastIndexOf('/')) : '.mesh';
191
+ const stateDir = runtimeDir !== '.ai-team'
192
+ ? runtimeDir
193
+ : (findFirst(stateCandidates.map((candidate) => `${candidate}/config.json`)) || '.mesh/config.json').replace(/\/config\.json$/, '');
194
+ const routingFile = findFirst([`${runtimeDir}/routing.md`, '.ai-team/routing.md']) || `${runtimeDir}/routing.md`;
195
+ const structurePath = `${stateDir}/org/structure.json`;
196
+ const labels = Array.isArray(issue.labels)
197
+ ? issue.labels.map(item => typeof item === 'string' ? item : item && item.name).filter(Boolean)
198
+ : [];
199
+ const deptLabel = labels.find(name => name.startsWith('dept:'));
200
+ let customInstructions = `Read ${teamFile || '.mesh/team.md'} for team context and ${routingFile} for routing rules.`;
201
+ if (deptLabel) {
202
+ const deptId = deptLabel.replace('dept:', '');
203
+ customInstructions += ` This issue is tagged to department "${deptId}".`;
204
+ if (fs.existsSync(structurePath)) {
205
+ customInstructions += ` Read ${structurePath} for hierarchy context before starting.`;
206
+ }
207
+ }
208
+
209
+ // Get the default branch name (main, master, etc.)
210
+ const { data: repoData } = await github.rest.repos.get({ owner, repo });
211
+ const baseBranch = repoData.default_branch;
212
+
213
+ try {
214
+ await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/assignees', {
215
+ owner,
216
+ repo,
217
+ issue_number,
218
+ assignees: ['copilot-swe-agent[bot]'],
219
+ agent_assignment: {
220
+ target_repo: `${owner}/${repo}`,
221
+ base_branch: baseBranch,
222
+ custom_instructions: customInstructions,
223
+ custom_agent: '',
224
+ model: ''
225
+ },
226
+ headers: {
227
+ 'X-GitHub-Api-Version': '2022-11-28'
228
+ }
229
+ });
230
+ core.info(`Assigned copilot-swe-agent to issue #${issue_number} (base: ${baseBranch})`);
231
+ } catch (err) {
232
+ core.warning(`Assignment with agent_assignment failed: ${err.message}`);
233
+ // Fallback: try without agent_assignment
234
+ try {
235
+ await github.rest.issues.addAssignees({
236
+ owner, repo, issue_number,
237
+ assignees: ['copilot-swe-agent']
238
+ });
239
+ core.info(`Fallback assigned copilot-swe-agent to issue #${issue_number}`);
240
+ } catch (err2) {
241
+ core.warning(`Fallback also failed: ${err2.message}`);
242
+ }
243
+ }