mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-24 17:11:20 +08:00
refactor: apply code-review findings to github-native coordination
scripts/github-coordination.js:
- parseArgs: replace 13-entry if/else chain with BOOL_FLAGS/VALUE_FLAGS
lookup maps; shrinks from 119 to ~45 lines
- Extract dispatchCommand(options, ctx) and formatOutput(payload, options)
from main(); main() shrinks to ~20 lines
scripts/lib/github-coordination.js:
- Split 1041-line monolith into 6 focused sub-modules under
scripts/lib/github-coordination/ (policy, parsing, gh-api, state,
actions, store); index becomes a thin re-export (~55 lines)
- Document ECC_GH_SHIM trust boundary in runGh() (gh-api.js)
- Document applyClaim() read→check→write race condition (actions.js)
tests/lib/github-coordination.test.js:
- Refactor runTests() to data-driven DESCRIPTORS array + runGroup()
helper; runTests() shrinks to ~10 lines
- Add 5 new edge-case tests: normalizeRepo('') and normalizeRepo(' ')
throw, desiredLabelsForState for blocked/ready statuses, and
buildIssueStateFromAction for validate action (15 → 20 tests)
tests/scripts/github-coordination.test.js:
- Replace console.log in test runner with process.stdout.write
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
'use strict';
|
||||
|
||||
const { DEFAULT_POLICY, DEFAULT_SCHEMA_VERSION } = require('./policy');
|
||||
const { extractIssueReferences, extractTasks } = require('./parsing');
|
||||
const { normalizeLabels, listIssues, editIssue } = require('./gh-api');
|
||||
|
||||
function slugifySegment(value) {
|
||||
return String(value || '')
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '') || 'unknown';
|
||||
}
|
||||
|
||||
function defaultCoordinationState(issue, policy = DEFAULT_POLICY) {
|
||||
return {
|
||||
schemaVersion: policy.schemaVersion || DEFAULT_SCHEMA_VERSION,
|
||||
kind: 'epic',
|
||||
status: 'available',
|
||||
owner: issue && issue.author && issue.author.login ? issue.author.login : null,
|
||||
branch: null,
|
||||
validation: 'pending',
|
||||
review: 'not-requested',
|
||||
project: {
|
||||
state: 'backlog',
|
||||
fields: {},
|
||||
},
|
||||
dependencies: extractIssueReferences(issue && issue.body ? issue.body : ''),
|
||||
tasks: extractTasks(issue && issue.body ? issue.body : ''),
|
||||
labels: normalizeLabels(issue && issue.labels),
|
||||
lastAction: 'sync',
|
||||
lastActionAt: new Date().toISOString(),
|
||||
lastSyncAt: new Date().toISOString(),
|
||||
notes: null,
|
||||
};
|
||||
}
|
||||
|
||||
function getCoordinationState(issue, policy = DEFAULT_POLICY) {
|
||||
const { extractCoordinationState } = require('./parsing'); // lazy to avoid circular init order
|
||||
const existing = extractCoordinationState(issue && issue.body, policy);
|
||||
if (existing) {
|
||||
return {
|
||||
...defaultCoordinationState(issue, policy),
|
||||
...existing,
|
||||
project: {
|
||||
...defaultCoordinationState(issue, policy).project,
|
||||
...(existing.project || {}),
|
||||
},
|
||||
tasks: Array.isArray(existing.tasks) ? existing.tasks : extractTasks(issue && issue.body ? issue.body : ''),
|
||||
dependencies: Array.isArray(existing.dependencies) ? existing.dependencies : extractIssueReferences(issue && issue.body ? issue.body : ''),
|
||||
labels: Array.isArray(existing.labels) ? existing.labels : normalizeLabels(issue && issue.labels),
|
||||
};
|
||||
}
|
||||
return defaultCoordinationState(issue, policy);
|
||||
}
|
||||
|
||||
function buildIssueStateFromAction(issue, currentState, action, options = {}, policy = DEFAULT_POLICY) {
|
||||
const now = new Date().toISOString();
|
||||
const next = {
|
||||
...currentState,
|
||||
schemaVersion: policy.schemaVersion || DEFAULT_SCHEMA_VERSION,
|
||||
kind: 'epic',
|
||||
lastAction: action,
|
||||
lastActionAt: now,
|
||||
lastSyncAt: now,
|
||||
labels: normalizeLabels(issue.labels),
|
||||
dependencies: Array.isArray(currentState.dependencies) ? currentState.dependencies : extractIssueReferences(issue.body),
|
||||
tasks: Array.isArray(currentState.tasks) ? currentState.tasks : extractTasks(issue.body),
|
||||
};
|
||||
|
||||
if (options.owner !== undefined) next.owner = options.owner;
|
||||
if (options.branch !== undefined) next.branch = options.branch;
|
||||
if (options.validation !== undefined) next.validation = options.validation;
|
||||
if (options.review !== undefined) next.review = options.review;
|
||||
if (options.status !== undefined) next.status = options.status;
|
||||
if (options.projectState !== undefined) {
|
||||
next.project = { ...(next.project || {}), state: options.projectState };
|
||||
}
|
||||
if (options.notes !== undefined) next.notes = options.notes;
|
||||
if (options.tasks !== undefined) next.tasks = options.tasks;
|
||||
if (options.dependencies !== undefined) next.dependencies = options.dependencies;
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
function desiredLabelsForState(state, policy = DEFAULT_POLICY) {
|
||||
const labels = [];
|
||||
const known = policy.labels || DEFAULT_POLICY.labels;
|
||||
|
||||
labels.push(known.epic);
|
||||
labels.push(known.synced);
|
||||
|
||||
if (state.status === 'available') labels.push(known.available);
|
||||
if (state.status === 'claimed') labels.push(known.claimed);
|
||||
if (state.status === 'ready') labels.push(known.ready);
|
||||
if (state.status === 'blocked') labels.push(known.blocked);
|
||||
if (state.validation === 'passed') labels.push(known.validated);
|
||||
if (state.review === 'requested') labels.push(known.reviewRequested);
|
||||
if (state.review === 'approved') labels.push(known.reviewApproved);
|
||||
if (state.review === 'changes-requested') labels.push(known.reviewChangesRequested);
|
||||
if (state.status === 'published') labels.push(known.published);
|
||||
|
||||
return Array.from(new Set(labels.filter(Boolean))).sort();
|
||||
}
|
||||
|
||||
function syncIssueLabels(repo, issue, state, policy = DEFAULT_POLICY, options = {}) {
|
||||
const desired = new Set(desiredLabelsForState(state, policy));
|
||||
const current = new Set(normalizeLabels(issue.labels));
|
||||
const addLabels = Array.from(desired).filter(label => !current.has(label));
|
||||
const removeLabels = Array.from(current).filter(label => {
|
||||
if (!label.startsWith('coordination:') && label !== (policy.labels && policy.labels.epic)) {
|
||||
return false;
|
||||
}
|
||||
return !desired.has(label);
|
||||
});
|
||||
|
||||
if (options.dryRun || (addLabels.length === 0 && removeLabels.length === 0)) {
|
||||
return { addLabels, removeLabels };
|
||||
}
|
||||
|
||||
if (addLabels.length > 0 || removeLabels.length > 0) {
|
||||
editIssue(repo, issue.number, { ...options, addLabels, removeLabels });
|
||||
}
|
||||
|
||||
return { addLabels, removeLabels };
|
||||
}
|
||||
|
||||
function findIssueByNumber(issues, issueNumber) {
|
||||
return issues.find(issue => Number(issue.number) === Number(issueNumber)) || null;
|
||||
}
|
||||
|
||||
function buildIssueComment(action, repo, issueNumber, state, extra = {}) {
|
||||
const summary = [
|
||||
`ECC coordination ${action}`,
|
||||
`Repo: ${repo}`,
|
||||
`Issue: #${issueNumber}`,
|
||||
`Status: ${state.status}`,
|
||||
`Owner: ${state.owner || '(unassigned)'}`,
|
||||
`Branch: ${state.branch || '(none)'}`,
|
||||
`Validation: ${state.validation || 'pending'}`,
|
||||
`Review: ${state.review || 'not-requested'}`,
|
||||
];
|
||||
|
||||
for (const [key, value] of Object.entries(extra)) {
|
||||
summary.push(`${key}: ${value}`);
|
||||
}
|
||||
|
||||
summary.push('', 'This comment is part of the append-only coordination audit trail.');
|
||||
return summary.join('\n');
|
||||
}
|
||||
|
||||
function mapStateToWorkItemStatus(state) {
|
||||
switch (state) {
|
||||
case 'blocked':
|
||||
return 'blocked';
|
||||
case 'published':
|
||||
return 'done';
|
||||
case 'validated':
|
||||
case 'reviewing':
|
||||
case 'claimed':
|
||||
case 'ready':
|
||||
return 'in-progress';
|
||||
case 'changes-requested':
|
||||
return 'needs-review';
|
||||
case 'available':
|
||||
default:
|
||||
return 'open';
|
||||
}
|
||||
}
|
||||
|
||||
function summarizeProjectProjection(state, policy = DEFAULT_POLICY) {
|
||||
return {
|
||||
enabled: Boolean(policy.project && policy.project.enabled),
|
||||
state: state.project && state.project.state ? state.project.state : 'backlog',
|
||||
fields: {
|
||||
...(state.project && state.project.fields ? state.project.fields : {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function summarizeStateForOutput(repo, issue, state, action, policy = DEFAULT_POLICY) {
|
||||
return {
|
||||
schemaVersion: state.schemaVersion || policy.schemaVersion || DEFAULT_SCHEMA_VERSION,
|
||||
repo,
|
||||
issueNumber: issue.number,
|
||||
issueUrl: issue.url || null,
|
||||
issueTitle: issue.title,
|
||||
action,
|
||||
status: state.status,
|
||||
owner: state.owner || null,
|
||||
branch: state.branch || null,
|
||||
validation: state.validation || 'pending',
|
||||
review: state.review || 'not-requested',
|
||||
project: summarizeProjectProjection(state, policy),
|
||||
dependencies: Array.isArray(state.dependencies) ? state.dependencies : [],
|
||||
tasks: Array.isArray(state.tasks) ? state.tasks : [],
|
||||
labels: normalizeLabels(issue.labels),
|
||||
workItemId: `github-${slugifySegment(repo)}-epic-${issue.number}`,
|
||||
lastActionAt: state.lastActionAt || null,
|
||||
lastSyncAt: state.lastSyncAt || null,
|
||||
};
|
||||
}
|
||||
|
||||
function assertIssueClaimable(issue, state) {
|
||||
if (String(issue.state || '').toLowerCase() !== 'open') {
|
||||
throw new Error(`Issue #${issue.number} is not open`);
|
||||
}
|
||||
|
||||
if (state.status === 'claimed' && state.owner) {
|
||||
throw new Error(`Issue #${issue.number} is already claimed by ${state.owner}`);
|
||||
}
|
||||
}
|
||||
|
||||
function verifyDependenciesClosed(repo, dependencyNumbers, options = {}, allIssues = null) {
|
||||
if (!Array.isArray(dependencyNumbers) || dependencyNumbers.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const issueList = allIssues || listIssues(repo, { ...options, state: 'all', limit: options.limit || 200 });
|
||||
const closed = [];
|
||||
for (const dependencyNumber of dependencyNumbers) {
|
||||
const issue = findIssueByNumber(issueList, dependencyNumber);
|
||||
if (!issue) {
|
||||
process.stderr.write(`[github-coordination] Warning: dependency issue #${dependencyNumber} not found in issue list (may be in a different repo or beyond limit)\n`);
|
||||
} else if (String(issue.state || '').toLowerCase() === 'closed') {
|
||||
closed.push(dependencyNumber);
|
||||
}
|
||||
}
|
||||
|
||||
return closed;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
assertIssueClaimable,
|
||||
buildIssueComment,
|
||||
buildIssueStateFromAction,
|
||||
defaultCoordinationState,
|
||||
desiredLabelsForState,
|
||||
findIssueByNumber,
|
||||
getCoordinationState,
|
||||
mapStateToWorkItemStatus,
|
||||
slugifySegment,
|
||||
summarizeProjectProjection,
|
||||
summarizeStateForOutput,
|
||||
syncIssueLabels,
|
||||
verifyDependenciesClosed,
|
||||
};
|
||||
Reference in New Issue
Block a user