@yemi33/minions 0.1.2168 → 0.1.2170
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/dashboard.js +9 -1
- package/engine/ado.js +126 -0
- package/package.json +1 -1
package/dashboard.js
CHANGED
|
@@ -5805,8 +5805,16 @@ const server = http.createServer(async (req, res) => {
|
|
|
5805
5805
|
fs.mkdirSync(inboxDir, { recursive: true });
|
|
5806
5806
|
const today = new Date().toISOString().slice(0, 10);
|
|
5807
5807
|
const author = body.author || os.userInfo().username;
|
|
5808
|
+
// BUG-H15 / P-h15-traversal: sanitize `author` before composing the
|
|
5809
|
+
// filename. Without slugify, a body like `{ author: "../../escape" }`
|
|
5810
|
+
// collapsed via path.join and wrote outside the inbox directory.
|
|
5811
|
+
// The Origin gate (dashboard.js handleCors above) explicitly allows
|
|
5812
|
+
// missing-Origin requests (curl / CLI tooling), so this sink is
|
|
5813
|
+
// reachable from any local process — including a malicious npm
|
|
5814
|
+
// postinstall or an agent processing an <UNTRUSTED-INPUT> block.
|
|
5815
|
+
const safeAuthor = shared.slugify(author, 40) || 'unknown';
|
|
5808
5816
|
const slug = shared.slugify(body.title || 'note', 40);
|
|
5809
|
-
const filename = `${
|
|
5817
|
+
const filename = `${safeAuthor}-${slug}-${today}-${shared.uid().slice(-4)}.md`;
|
|
5810
5818
|
const content = `# ${body.title}\n\n**By:** ${author}\n**Date:** ${today}\n\n${body.what}\n${body.why ? '\n**Why:** ' + body.why + '\n' : ''}`;
|
|
5811
5819
|
safeWrite(shared.uniquePath(path.join(inboxDir, filename)), content);
|
|
5812
5820
|
invalidateStatusCache();
|
package/engine/ado.js
CHANGED
|
@@ -1057,6 +1057,131 @@ async function forEachActivePr(config, token, callback) {
|
|
|
1057
1057
|
}
|
|
1058
1058
|
}
|
|
1059
1059
|
|
|
1060
|
+
// BUG-C2 — Also poll manually-linked ADO PRs from central pull-requests.json.
|
|
1061
|
+
// Mirror of engine/github.js:540-633. Without this block, any ADO PR enrolled
|
|
1062
|
+
// into central pull-requests.json via the dashboard attach UI or
|
|
1063
|
+
// lifecycle.enrollPrFromCanonicalId is never polled (status, build, review,
|
|
1064
|
+
// comments, abandoned-reconciliation all freeze permanently).
|
|
1065
|
+
//
|
|
1066
|
+
// MIRROR-SYMMETRY CONTRACT: any future divergence between
|
|
1067
|
+
// engine/github.js:540-633 and this branch is itself a bug class. The
|
|
1068
|
+
// canonical 'mirror-ado-github-poll-fix' skill applies. Keep filter, gating,
|
|
1069
|
+
// per-PR iteration, and merge-back semantics symmetric.
|
|
1070
|
+
//
|
|
1071
|
+
// Notable host-specific differences vs github.js:
|
|
1072
|
+
// 1. Tick-scoped probe gating uses per-org ADO throttle state (isAdoThrottled
|
|
1073
|
+
// keyed by orgBase) rather than ghApi('', slug). Rationale: adoFetch throws
|
|
1074
|
+
// on 404/auth failure (no GH_NOT_FOUND-equivalent sentinel), and an extra
|
|
1075
|
+
// per-org probe would only burn throttle budget. Throttle skip mirrors the
|
|
1076
|
+
// same gate used by the project-loop above (lines 991-994).
|
|
1077
|
+
// 2. The project arg is synthesized from parseCanonicalAdoPrId per AC
|
|
1078
|
+
// option (b) — the existing callbacks at lines 1076 and 1618 dereference
|
|
1079
|
+
// project.adoProject; tolerate-null would require touching both call sites
|
|
1080
|
+
// whereas synthesizing keeps the callback contract identical.
|
|
1081
|
+
// 3. The repo-host filter is explicit (repoHost === 'ado') so a future
|
|
1082
|
+
// central poll that loops over both forks doesn't accidentally treat
|
|
1083
|
+
// github PRs as ADO records.
|
|
1084
|
+
// 4. Already-configured PRs are skipped (isPrCompatibleWithProject against
|
|
1085
|
+
// any configured ADO project) so we never double-poll PRs that the
|
|
1086
|
+
// project loop above already handled.
|
|
1087
|
+
const centralPath = path.join(shared.MINIONS_DIR, 'pull-requests.json');
|
|
1088
|
+
const centralPrs = shared.safeJsonArr(centralPath);
|
|
1089
|
+
const configuredAdoProjects = projects.filter(p => !isGitHubProject(p) && p.adoOrg && p.adoProject);
|
|
1090
|
+
const isConfiguredAdoCanonical = (canonicalId) => {
|
|
1091
|
+
if (!canonicalId) return false;
|
|
1092
|
+
return configuredAdoProjects.some(p => shared.isPrCompatibleWithProject(p, { id: canonicalId }, ''));
|
|
1093
|
+
};
|
|
1094
|
+
|
|
1095
|
+
const activeCentral = centralPrs.filter(pr => {
|
|
1096
|
+
if (!shared.PR_POLLABLE_STATUSES.has(pr.status)) return false;
|
|
1097
|
+
if (String(pr.repoHost || 'ado').toLowerCase() !== 'ado') return false;
|
|
1098
|
+
if (!parseCanonicalAdoPrId(pr.id, 'central ADO PR poll')) return false;
|
|
1099
|
+
if (isConfiguredAdoCanonical(pr.id)) return false;
|
|
1100
|
+
return true;
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
// Per-org throttle probe (mirrors github.js:563-581 slug-probe gating).
|
|
1104
|
+
// Cached per tick (Map cleared each forEachActivePr invocation) so multiple
|
|
1105
|
+
// central PRs sharing one orgBase do a single throttle check.
|
|
1106
|
+
const centralOrgProbes = new Map(); // orgBase → 'ok' | 'throttled'
|
|
1107
|
+
for (const pr of activeCentral) {
|
|
1108
|
+
const parsed = parseCanonicalAdoPrId(pr.id, 'central ADO PR poll');
|
|
1109
|
+
if (!parsed) continue;
|
|
1110
|
+
const orgBase = getAdoOrgBase(parsed);
|
|
1111
|
+
if (centralOrgProbes.has(orgBase)) continue;
|
|
1112
|
+
if (isAdoThrottled(orgBase)) {
|
|
1113
|
+
log('info', `[ado] central PR poll skipped for ${orgBase} — throttled`);
|
|
1114
|
+
centralOrgProbes.set(orgBase, 'throttled');
|
|
1115
|
+
} else {
|
|
1116
|
+
centralOrgProbes.set(orgBase, 'ok');
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
let centralUpdated = 0;
|
|
1121
|
+
const updatedCentralRecords = [];
|
|
1122
|
+
for (const pr of activeCentral) {
|
|
1123
|
+
const parsed = parseCanonicalAdoPrId(pr.id, 'central ADO PR poll');
|
|
1124
|
+
if (!parsed) continue;
|
|
1125
|
+
const orgBase = getAdoOrgBase(parsed);
|
|
1126
|
+
if (centralOrgProbes.get(orgBase) !== 'ok') continue;
|
|
1127
|
+
const prNum = shared.getPrNumber(pr);
|
|
1128
|
+
if (!prNum) continue;
|
|
1129
|
+
|
|
1130
|
+
// Option (b) per BUG-C2 AC: synthesize a minimal project object from the
|
|
1131
|
+
// parsed canonical id. Mirrors the shape the existing callbacks expect
|
|
1132
|
+
// (project.adoOrg / project.adoProject / project.repoName / project.repositoryId)
|
|
1133
|
+
// so neither pollPrStatus nor pollPrHumanComments needs a tolerate-null branch.
|
|
1134
|
+
const synthesizedProject = {
|
|
1135
|
+
adoOrg: parsed.adoOrg,
|
|
1136
|
+
adoProject: parsed.adoProject,
|
|
1137
|
+
repoName: parsed.repoName,
|
|
1138
|
+
repositoryId: parsed.repositoryId,
|
|
1139
|
+
repoHost: 'ado',
|
|
1140
|
+
prUrlBase: parsed.prUrlBase || '',
|
|
1141
|
+
name: `central:${parsed.adoOrg}/${parsed.adoProject}/${parsed.repoName || parsed.repositoryId || 'unknown'}`,
|
|
1142
|
+
_synthesizedFromCentralPr: true,
|
|
1143
|
+
};
|
|
1144
|
+
const adoRepositoryId = getAdoRepositoryId(synthesizedProject);
|
|
1145
|
+
if (!adoRepositoryId) {
|
|
1146
|
+
log('warn', `[ado] central PR ${pr.id}: cannot resolve adoRepositoryId from canonical id — skipping`);
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
try {
|
|
1151
|
+
const before = shared.snapshotPrRecord(pr);
|
|
1152
|
+
const updated = await callback(synthesizedProject, pr, prNum, orgBase, adoRepositoryId);
|
|
1153
|
+
if (updated) {
|
|
1154
|
+
centralUpdated++;
|
|
1155
|
+
updatedCentralRecords.push({ before, after: shared.snapshotPrRecord(pr) });
|
|
1156
|
+
}
|
|
1157
|
+
} catch (err) {
|
|
1158
|
+
log('warn', `ADO: failed to poll central PR ${pr.id}: ${err.message}`);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
if (centralUpdated > 0) {
|
|
1163
|
+
mutateJsonFileLocked(centralPath, (currentPrs) => {
|
|
1164
|
+
for (const { before, after } of updatedCentralRecords) {
|
|
1165
|
+
const idx = currentPrs.findIndex(p => p.id === after.id);
|
|
1166
|
+
if (idx >= 0) {
|
|
1167
|
+
// Never downgrade reviewStatus from 'approved' — permanent terminal state
|
|
1168
|
+
// (mirrors project-loop guard at lines 1038-1040).
|
|
1169
|
+
if (currentPrs[idx].reviewStatus === REVIEW_STATUS.APPROVED && after.reviewStatus !== REVIEW_STATUS.APPROVED) {
|
|
1170
|
+
after.reviewStatus = REVIEW_STATUS.APPROVED;
|
|
1171
|
+
}
|
|
1172
|
+
// Never downgrade status from 'merged' — permanent terminal state
|
|
1173
|
+
// (mirrors github.js:634-636 central merged-status guard).
|
|
1174
|
+
if (currentPrs[idx].status === PR_STATUS.MERGED && after.status !== PR_STATUS.MERGED) {
|
|
1175
|
+
after.status = PR_STATUS.MERGED;
|
|
1176
|
+
}
|
|
1177
|
+
shared.applyPrFieldDelta(currentPrs[idx], before, after);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
return currentPrs;
|
|
1181
|
+
}, { defaultValue: [] });
|
|
1182
|
+
totalUpdated += centralUpdated;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1060
1185
|
return totalUpdated;
|
|
1061
1186
|
}
|
|
1062
1187
|
|
|
@@ -2603,6 +2728,7 @@ async function reconcileAbandonedPrs(config) {
|
|
|
2603
2728
|
module.exports = {
|
|
2604
2729
|
getAdoToken,
|
|
2605
2730
|
adoFetch,
|
|
2731
|
+
forEachActivePr, // BUG-C2 — exported for central-PR mirror-symmetry tests
|
|
2606
2732
|
pollPrStatus,
|
|
2607
2733
|
pollPrHumanComments,
|
|
2608
2734
|
reconcilePrs,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2170",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|