@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.
- package/.copilot/mcp-config.json +14 -0
- package/.copilot/skills/agent-collaboration/SKILL.md +42 -0
- package/.copilot/skills/agent-conduct/SKILL.md +24 -0
- package/.copilot/skills/architectural-proposals/SKILL.md +151 -0
- package/.copilot/skills/ci-validation-gates/SKILL.md +84 -0
- package/.copilot/skills/cli-wiring/SKILL.md +47 -0
- package/.copilot/skills/client-compatibility/SKILL.md +89 -0
- package/.copilot/skills/cross-mesh/SKILL.md +114 -0
- package/.copilot/skills/distributed-mesh/SKILL.md +287 -0
- package/.copilot/skills/distributed-mesh/mesh.json.example +30 -0
- package/.copilot/skills/distributed-mesh/sync-mesh.ps1 +111 -0
- package/.copilot/skills/distributed-mesh/sync-mesh.sh +104 -0
- package/.copilot/skills/docs-standards/SKILL.md +71 -0
- package/.copilot/skills/economy-mode/SKILL.md +101 -0
- package/.copilot/skills/external-comms/SKILL.md +331 -0
- package/.copilot/skills/gh-auth-isolation/SKILL.md +183 -0
- package/.copilot/skills/git-workflow/SKILL.md +206 -0
- package/.copilot/skills/github-multi-account/SKILL.md +95 -0
- package/.copilot/skills/history-hygiene/SKILL.md +36 -0
- package/.copilot/skills/humanizer/SKILL.md +107 -0
- package/.copilot/skills/init-mode/SKILL.md +101 -0
- package/.copilot/skills/mesh-conventions/SKILL.md +69 -0
- package/.copilot/skills/model-selection/SKILL.md +139 -0
- package/.copilot/skills/nap/SKILL.md +24 -0
- package/.copilot/skills/personal-mesh/SKILL.md +57 -0
- package/.copilot/skills/project-conventions/SKILL.md +56 -0
- package/.copilot/skills/release-process/SKILL.md +435 -0
- package/.copilot/skills/reskill/SKILL.md +92 -0
- package/.copilot/skills/reviewer-protocol/SKILL.md +79 -0
- package/.copilot/skills/secret-handling/SKILL.md +200 -0
- package/.copilot/skills/session-recovery/SKILL.md +155 -0
- package/.copilot/skills/test-discipline/SKILL.md +37 -0
- package/.copilot/skills/windows-compatibility/SKILL.md +74 -0
- package/.github/agents/mercury-mesh.agent.md +1732 -0
- package/.mesh/manifesto.md +66 -0
- package/.mesh/templates/casting/Futurama.json +10 -0
- package/.mesh/templates/casting-history.json +4 -0
- package/.mesh/templates/casting-policy.json +37 -0
- package/.mesh/templates/casting-reference.md +104 -0
- package/.mesh/templates/casting-registry.json +3 -0
- package/.mesh/templates/ceremonies.md +41 -0
- package/.mesh/templates/charter.md +56 -0
- package/.mesh/templates/constraint-tracking.md +38 -0
- package/.mesh/templates/cooperative-rate-limiting.md +229 -0
- package/.mesh/templates/copilot-instructions.md +50 -0
- package/.mesh/templates/department-backlog.md +15 -0
- package/.mesh/templates/department-charter.md +27 -0
- package/.mesh/templates/department-state.json +19 -0
- package/.mesh/templates/history.md +10 -0
- package/.mesh/templates/identity/now.md +9 -0
- package/.mesh/templates/identity/wisdom.md +15 -0
- package/.mesh/templates/interface-contract.md +26 -0
- package/.mesh/templates/issue-lifecycle.md +421 -0
- package/.mesh/templates/keda-scaler.md +166 -0
- package/.mesh/templates/machine-capabilities.md +77 -0
- package/.mesh/templates/mcp-config.md +90 -0
- package/.mesh/templates/mercury-mesh.agent.md +1732 -0
- package/.mesh/templates/multi-agent-format.md +28 -0
- package/.mesh/templates/orchestration-log.md +27 -0
- package/.mesh/templates/org-autonomy-spec.md +152 -0
- package/.mesh/templates/org-backlog-from-triage.js +199 -0
- package/.mesh/templates/org-runtime-reconcile.js +364 -0
- package/.mesh/templates/org-seed-runtime.js +238 -0
- package/.mesh/templates/org-status.js +193 -0
- package/.mesh/templates/org-structure.json +38 -0
- package/.mesh/templates/package.json +3 -0
- package/.mesh/templates/plugin-marketplace.md +49 -0
- package/.mesh/templates/ralph-circuit-breaker.md +313 -0
- package/.mesh/templates/ralph-triage.js +844 -0
- package/.mesh/templates/raw-agent-output.md +37 -0
- package/.mesh/templates/roster.md +60 -0
- package/.mesh/templates/routing.md +78 -0
- package/.mesh/templates/run-output.md +50 -0
- package/.mesh/templates/schedule.json +64 -0
- package/.mesh/templates/scribe-charter.md +119 -0
- package/.mesh/templates/skill.md +24 -0
- package/.mesh/templates/skills/agent-collaboration/SKILL.md +42 -0
- package/.mesh/templates/skills/agent-conduct/SKILL.md +24 -0
- package/.mesh/templates/skills/architectural-proposals/SKILL.md +151 -0
- package/.mesh/templates/skills/ci-validation-gates/SKILL.md +84 -0
- package/.mesh/templates/skills/cli-wiring/SKILL.md +47 -0
- package/.mesh/templates/skills/client-compatibility/SKILL.md +89 -0
- package/.mesh/templates/skills/cross-mesh/SKILL.md +114 -0
- package/.mesh/templates/skills/distributed-mesh/SKILL.md +287 -0
- package/.mesh/templates/skills/distributed-mesh/mesh.json.example +30 -0
- package/.mesh/templates/skills/distributed-mesh/sync-mesh.ps1 +111 -0
- package/.mesh/templates/skills/distributed-mesh/sync-mesh.sh +104 -0
- package/.mesh/templates/skills/docs-standards/SKILL.md +71 -0
- package/.mesh/templates/skills/economy-mode/SKILL.md +101 -0
- package/.mesh/templates/skills/external-comms/SKILL.md +331 -0
- package/.mesh/templates/skills/gh-auth-isolation/SKILL.md +183 -0
- package/.mesh/templates/skills/git-workflow/SKILL.md +204 -0
- package/.mesh/templates/skills/github-multi-account/SKILL.md +95 -0
- package/.mesh/templates/skills/history-hygiene/SKILL.md +36 -0
- package/.mesh/templates/skills/humanizer/SKILL.md +107 -0
- package/.mesh/templates/skills/init-mode/SKILL.md +101 -0
- package/.mesh/templates/skills/mesh-conventions/SKILL.md +69 -0
- package/.mesh/templates/skills/model-selection/SKILL.md +139 -0
- package/.mesh/templates/skills/nap/SKILL.md +24 -0
- package/.mesh/templates/skills/personal-mesh/SKILL.md +57 -0
- package/.mesh/templates/skills/project-conventions/SKILL.md +56 -0
- package/.mesh/templates/skills/release-process/SKILL.md +435 -0
- package/.mesh/templates/skills/reskill/SKILL.md +92 -0
- package/.mesh/templates/skills/reviewer-protocol/SKILL.md +79 -0
- package/.mesh/templates/skills/secret-handling/SKILL.md +200 -0
- package/.mesh/templates/skills/session-recovery/SKILL.md +155 -0
- package/.mesh/templates/skills/test-discipline/SKILL.md +37 -0
- package/.mesh/templates/skills/windows-compatibility/SKILL.md +74 -0
- package/.mesh/templates/workflows/mesh-ci.yml +24 -0
- package/.mesh/templates/workflows/mesh-docs.yml +54 -0
- package/.mesh/templates/workflows/mesh-heartbeat.yml +237 -0
- package/.mesh/templates/workflows/mesh-insider-release.yml +61 -0
- package/.mesh/templates/workflows/mesh-issue-assign.yml +243 -0
- package/.mesh/templates/workflows/mesh-label-enforce.yml +181 -0
- package/.mesh/templates/workflows/mesh-preview.yml +55 -0
- package/.mesh/templates/workflows/mesh-promote.yml +120 -0
- package/.mesh/templates/workflows/mesh-release.yml +77 -0
- package/.mesh/templates/workflows/mesh-triage.yml +383 -0
- package/.mesh/templates/workflows/sync-mesh-labels.yml +204 -0
- package/README.md +640 -0
- package/bin/mercury-mesh.cjs +317 -0
- package/docs/brand-language.md +287 -0
- package/docs/commander-onboarding.md +462 -0
- package/docs/mercury-mesh-runtime-rename-impact.md +148 -0
- package/docs/persona-manifesto.md +114 -0
- package/docs/scenarios/client-compatibility.md +59 -0
- package/index.cjs +41 -0
- 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
|
+
}
|