@yemi33/minions 0.1.2398 → 0.1.2399
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/slim/js/modals-tiles.js +29 -17
- package/dashboard/slim/styles.css +10 -6
- package/dashboard.js +28 -0
- package/engine/resolve-area.js +200 -0
- package/package.json +1 -1
- package/prompts/cc-system.md +19 -0
|
@@ -12,11 +12,15 @@
|
|
|
12
12
|
if (ev.key === 'Escape' && modal.classList.contains('open')) close();
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
+
function clearTileModalBody() {
|
|
16
|
+
var body = document.getElementById('slim-tile-body');
|
|
17
|
+
if (body) body.textContent = '';
|
|
18
|
+
}
|
|
15
19
|
// Stop the agent-detail "Working for" ticker on every dismiss path so the
|
|
16
20
|
// 1s interval started in openAgentDetail can't leak after the modal closes.
|
|
17
21
|
bindModalClose('slim-agent-modal', 'slim-agent-close', _stopAgentDetailRuntime);
|
|
18
22
|
bindModalClose('slim-tools-modal', 'slim-tools-close');
|
|
19
|
-
bindModalClose('slim-tile-modal', 'slim-tile-close');
|
|
23
|
+
bindModalClose('slim-tile-modal', 'slim-tile-close', clearTileModalBody);
|
|
20
24
|
|
|
21
25
|
function tileEmpty(body, text) {
|
|
22
26
|
var d = document.createElement('div');
|
|
@@ -83,16 +87,24 @@
|
|
|
83
87
|
|
|
84
88
|
// Per-tile body renderers for the detail modal. Each reads the latest status
|
|
85
89
|
// snapshot and fills the modal body; openTileModal looks them up by key.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
// Engine tile body — reuses the LITERAL classic dashboard /engine screen
|
|
91
|
+
// instead of a slim-specific key/value readout, so there is one Engine UI, not
|
|
92
|
+
// two (W-mrtdnknm000med95 / "reuse, don't fork" — mirrors renderQueuedWorkBody
|
|
93
|
+
// for the Work tile, renderPlansBody for the Plans tile, and renderPrsBody for
|
|
94
|
+
// the PRs tile). Slim and classic are two separate IIFE bundles with no shared
|
|
95
|
+
// scope, so we embed the real /engine screen in an iframe with the chrome-off
|
|
96
|
+
// ?embed=1 mode. The iframed page IS the classic screen — the full engine
|
|
97
|
+
// dashboard (quick stats, engine log, managed-process panel + its log SSE
|
|
98
|
+
// stream, memory + diagnostics panels), backed by the same /api endpoints and
|
|
99
|
+
// live streams — with zero duplicated rendering logic. Classic is reachable at
|
|
100
|
+
// /engine even with slim-ux ON (only / is taken over). Built with createElement
|
|
101
|
+
// (no innerHTML) to satisfy the dashboard no-unsanitized lint gate.
|
|
102
|
+
function renderEngineBody(body) {
|
|
103
|
+
var frame = document.createElement('iframe');
|
|
104
|
+
frame.className = 'slim-engine-embed';
|
|
105
|
+
frame.src = '/engine?embed=1';
|
|
106
|
+
frame.title = 'Engine';
|
|
107
|
+
body.appendChild(frame);
|
|
96
108
|
}
|
|
97
109
|
|
|
98
110
|
function renderDispatchTileBody(body, data, isActive) {
|
|
@@ -184,7 +196,7 @@
|
|
|
184
196
|
|
|
185
197
|
// tile key -> { title, render }. Adding a tile is a one-line table edit.
|
|
186
198
|
var TILE_VIEWS = {
|
|
187
|
-
engine: { title: 'Engine', render:
|
|
199
|
+
engine: { title: 'Engine', render: function(body) { renderEngineBody(body); } },
|
|
188
200
|
dispatches: { title: 'Active dispatches', render: function(body, data) { renderDispatchTileBody(body, data, true); } },
|
|
189
201
|
queued: { title: 'Queued work', render: function(body) { renderQueuedWorkBody(body); } },
|
|
190
202
|
plans: { title: 'Plans', render: function(body) { renderPlansBody(body); } },
|
|
@@ -209,10 +221,11 @@
|
|
|
209
221
|
// The "+ Link PR" header chip only applies to the PR list.
|
|
210
222
|
var headerChip = document.getElementById('slim-tile-modal-linkpr');
|
|
211
223
|
if (headerChip) headerChip.style.display = (key === 'prs') ? '' : 'none';
|
|
212
|
-
// The queued, plans + prs tiles embed a full classic screen
|
|
213
|
-
// /prs) in an iframe — widen the modal + drop the
|
|
214
|
-
// embedded screen gets real estate. All
|
|
215
|
-
// treatment.
|
|
224
|
+
// The engine, queued, plans + prs tiles embed a full classic screen
|
|
225
|
+
// (/engine, /work, /plans, /prs) in an iframe — widen the modal + drop the
|
|
226
|
+
// body padding so the embedded screen gets real estate. All four reuse the
|
|
227
|
+
// shared wide-modal treatment.
|
|
228
|
+
modal.classList.toggle('tile-modal--engine', key === 'engine');
|
|
216
229
|
modal.classList.toggle('tile-modal--work', key === 'queued');
|
|
217
230
|
modal.classList.toggle('tile-modal--plans', key === 'plans');
|
|
218
231
|
modal.classList.toggle('tile-modal--prs', key === 'prs');
|
|
@@ -251,4 +264,3 @@
|
|
|
251
264
|
(function bindPlansTileCount() {
|
|
252
265
|
if (typeof loadPlansCounts === 'function') setTimeout(loadPlansCounts, 1500);
|
|
253
266
|
})();
|
|
254
|
-
|
|
@@ -677,16 +677,19 @@
|
|
|
677
677
|
max-width: calc(100vw - 32px);
|
|
678
678
|
}
|
|
679
679
|
|
|
680
|
-
/* Cockpit-tile detail modal: list of dispatches
|
|
681
|
-
|
|
680
|
+
/* Cockpit-tile detail modal: list of dispatches or watches (reuses
|
|
681
|
+
.agent-detail-row / .tile-item). */
|
|
682
682
|
#slim-tile-modal .modal { width: 720px; max-width: calc(100vw - 32px); }
|
|
683
|
-
/* W-mqrdggys000f94a4 / W-mr28ko750010199f / W-mr3l7y0m0007102a
|
|
684
|
-
Queued-work + Plans + PRs tiles embed a
|
|
685
|
-
/plans, /prs) in an iframe; widen the
|
|
686
|
-
embedded screen gets the full
|
|
683
|
+
/* W-mqrdggys000f94a4 / W-mr28ko750010199f / W-mr3l7y0m0007102a /
|
|
684
|
+
W-mrtdnknm000med95 — the Engine + Queued-work + Plans + PRs tiles embed a
|
|
685
|
+
full classic screen (/engine, /work, /plans, /prs) in an iframe; widen the
|
|
686
|
+
modal + remove body padding so the embedded screen gets the full
|
|
687
|
+
width/height. */
|
|
688
|
+
#slim-tile-modal.tile-modal--engine .modal,
|
|
687
689
|
#slim-tile-modal.tile-modal--work .modal,
|
|
688
690
|
#slim-tile-modal.tile-modal--plans .modal,
|
|
689
691
|
#slim-tile-modal.tile-modal--prs .modal { width: 1180px; }
|
|
692
|
+
#slim-tile-modal.tile-modal--engine .modal-body,
|
|
690
693
|
#slim-tile-modal.tile-modal--work .modal-body,
|
|
691
694
|
#slim-tile-modal.tile-modal--plans .modal-body,
|
|
692
695
|
#slim-tile-modal.tile-modal--prs .modal-body { padding: 0; }
|
|
@@ -696,6 +699,7 @@
|
|
|
696
699
|
full width/height. Mirrors the Queued-work/Plans/PRs tile treatment. */
|
|
697
700
|
#slim-settings-modal .modal { width: 1180px; max-width: calc(100vw - 32px); }
|
|
698
701
|
#slim-settings-modal .modal-body { padding: 0; }
|
|
702
|
+
.slim-engine-embed,
|
|
699
703
|
.slim-work-embed,
|
|
700
704
|
.slim-plans-embed,
|
|
701
705
|
.slim-prs-embed,
|
package/dashboard.js
CHANGED
|
@@ -62,6 +62,7 @@ const createPrWorktree = require('./engine/create-pr-worktree');
|
|
|
62
62
|
const prResolve = require('./engine/pr-resolve');
|
|
63
63
|
const prFixTarget = require('./engine/pr-fix-target');
|
|
64
64
|
const prTrack = require('./engine/pr-track');
|
|
65
|
+
const resolveArea = require('./engine/resolve-area');
|
|
65
66
|
const features = require('./engine/features');
|
|
66
67
|
const ccWorkerPool = require('./engine/cc-worker-pool');
|
|
67
68
|
const diagnosticsMemory = require('./engine/diagnostics-memory');
|
|
@@ -13246,6 +13247,33 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
13246
13247
|
builder: () => getDispatchQueue(),
|
|
13247
13248
|
});
|
|
13248
13249
|
}},
|
|
13250
|
+
// GET /api/resolve-area — reverse "component name → subproject path" resolver
|
|
13251
|
+
// (WI W-mrpbsbs7000ce8ac). Given a configured project and a user token
|
|
13252
|
+
// ("OCM", "loop"), resolve it to real subproject path(s) via the filesystem
|
|
13253
|
+
// (engine/resolve-area.js reuses the bounded first-level area enumeration) so
|
|
13254
|
+
// CC threads a resolved PATH into scope instead of guessing from PR titles.
|
|
13255
|
+
{ method: 'GET', path: '/api/resolve-area', desc: 'Resolve a monorepo component/subproject NAME to real subproject path(s) for a configured project, via the filesystem (never PR titles). Query: project (configured project name), token (component name e.g. "OCM"). Returns {ok, project, token, area, paths, absPaths, matched:"exact"|"alias"|"none"}. matched:"none" means the caller should ask the user, not guess.', params: 'project (required), token (required)', handler: (req, res) => {
|
|
13256
|
+
const params = new URL(req.url, 'http://localhost').searchParams;
|
|
13257
|
+
const projectName = (params.get('project') || '').trim();
|
|
13258
|
+
const token = (params.get('token') || '').trim();
|
|
13259
|
+
if (!projectName) return jsonReply(res, 400, { error: 'project query param is required' }, req);
|
|
13260
|
+
if (!token) return jsonReply(res, 400, { error: 'token query param is required' }, req);
|
|
13261
|
+
const descriptor = shared.resolveProjectSource(projectName, PROJECTS, { allowCentral: false });
|
|
13262
|
+
const project = descriptor && descriptor.project;
|
|
13263
|
+
if (!project) return jsonReply(res, 404, { error: `unknown project "${projectName}"` }, req);
|
|
13264
|
+
if (!project.localPath) return jsonReply(res, 400, { error: `project "${project.name}" has no localPath (no checkout to resolve against)` }, req);
|
|
13265
|
+
const resolved = resolveArea.resolveAreaFromToken(project.localPath, token);
|
|
13266
|
+
const absPaths = resolved.paths.map(p => path.join(project.localPath, p).replace(/\\/g, '/'));
|
|
13267
|
+
return jsonReply(res, 200, {
|
|
13268
|
+
ok: true,
|
|
13269
|
+
project: project.name,
|
|
13270
|
+
token,
|
|
13271
|
+
area: resolved.area,
|
|
13272
|
+
paths: resolved.paths,
|
|
13273
|
+
absPaths,
|
|
13274
|
+
matched: resolved.matched,
|
|
13275
|
+
}, req);
|
|
13276
|
+
}},
|
|
13249
13277
|
{ method: 'GET', path: '/api/metrics', desc: 'Per-agent metrics with PR/runtime enrichment (joined against pull-requests + dispatch.completed)', handler: (req, res) => {
|
|
13250
13278
|
return serveFreshJson(req, res, {
|
|
13251
13279
|
tag: 'metrics',
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine/resolve-area.js — Reverse "component name → subproject path" resolver.
|
|
3
|
+
*
|
|
4
|
+
* WI W-mrpbsbs7000ce8ac. Command Center (and other callers) frequently receive
|
|
5
|
+
* a monorepo component name in a natural-language request ("audit OCM changes",
|
|
6
|
+
* "Loop Android"). Without a way to turn that name into a real filesystem path,
|
|
7
|
+
* CC used to guess the subproject's location from PR titles — a documented
|
|
8
|
+
* incident where CC repeatedly asked the operator where OCM lived even though
|
|
9
|
+
* `<repo>/ocm/` exists on disk.
|
|
10
|
+
*
|
|
11
|
+
* This module is the reverse of the (now-removed) dominant-subproject detector:
|
|
12
|
+
* that mapped `changed paths → area`; this maps `user token → area path(s)`.
|
|
13
|
+
*
|
|
14
|
+
* NOTE ON REUSE: the original first-level area enumeration lived in
|
|
15
|
+
* `engine/discover-project-skills.js` as `_listAreas`. That entire module (and
|
|
16
|
+
* its area/skill/harness heuristics) was deliberately removed when repository
|
|
17
|
+
* harness discovery was delegated to the native runtimes (PR #817,
|
|
18
|
+
* "Delegate repository harness discovery to native runtimes"). No `_listAreas`
|
|
19
|
+
* remains to import, so this file carries the single, minimal, bounded
|
|
20
|
+
* first-level enumeration — it is NOT a parallel copy of a live helper.
|
|
21
|
+
*
|
|
22
|
+
* Design constraints (match the removed helper's conventions):
|
|
23
|
+
* - Bounded / deadline-guarded filesystem work (no unbounded recursion).
|
|
24
|
+
* - Deterministic, sorted output across OSes.
|
|
25
|
+
* - Cross-platform: paths are emitted as POSIX forward-slash, dir-suffixed.
|
|
26
|
+
* - Zero runtime deps — Node built-ins only.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
'use strict';
|
|
30
|
+
|
|
31
|
+
const fs = require('fs');
|
|
32
|
+
const path = require('path');
|
|
33
|
+
|
|
34
|
+
const DEFAULTS = {
|
|
35
|
+
// Cap on first-level directories enumerated (monorepo roots are wide but
|
|
36
|
+
// still O(dozens), so this is a generous ceiling that bounds a pathological
|
|
37
|
+
// directory).
|
|
38
|
+
maxAreas: 400,
|
|
39
|
+
// Wall-clock budget for the whole resolve (enumeration + alias existence
|
|
40
|
+
// probes). Bounded like the removed helper so a slow/again-networked FS can
|
|
41
|
+
// never hang a caller.
|
|
42
|
+
walltimeMs: 750,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Documented alias map for known multi-path components — a single user token
|
|
47
|
+
* that resolves to several real subproject paths (a primary top-level area PLUS
|
|
48
|
+
* wrapper / native / telemetry paths scattered elsewhere in the tree).
|
|
49
|
+
*
|
|
50
|
+
* Keys are lowercase tokens. `primary` is the first-level area used for the
|
|
51
|
+
* returned `area`; `paths` is the full candidate set (repo-relative, POSIX,
|
|
52
|
+
* trailing slash). At resolve time each candidate is verified against the
|
|
53
|
+
* on-disk tree so a token used against a repo that is NOT the monorepo cleanly
|
|
54
|
+
* returns no match instead of inventing paths.
|
|
55
|
+
*/
|
|
56
|
+
const AREA_ALIASES = {
|
|
57
|
+
// OCM (Office Copilot Mobile): the top-level `ocm/` area plus its wrapper /
|
|
58
|
+
// native / telemetry paths under officemobile/.
|
|
59
|
+
ocm: {
|
|
60
|
+
primary: 'ocm',
|
|
61
|
+
paths: [
|
|
62
|
+
'ocm/',
|
|
63
|
+
'officemobile/ocmnative/',
|
|
64
|
+
'officemobile/android/ocmciq/',
|
|
65
|
+
'officemobile/android/JavaKotlin/ocmciq/',
|
|
66
|
+
'officemobile/android/hubframework/ocmsdk-wrapper/',
|
|
67
|
+
'officemobile/android/JavaKotlin/hubframework/ocmsdk-wrapper/',
|
|
68
|
+
'officemobile/ios/OCMWrapper/',
|
|
69
|
+
'officemobile/telemetry/android/ocm/',
|
|
70
|
+
'officemobile/telemetry/tml/ocm/',
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function _now() { return Date.now(); }
|
|
76
|
+
|
|
77
|
+
/** Normalize a repo-relative path to POSIX forward-slash with a trailing slash. */
|
|
78
|
+
function _toPosixDir(p) {
|
|
79
|
+
let s = String(p).replace(/\\/g, '/').replace(/\/+$/, '');
|
|
80
|
+
return s.length ? `${s}/` : s;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Bounded existence check: true iff `rel` resolves to a directory under `projectPath`. */
|
|
84
|
+
function _isDir(projectPath, rel) {
|
|
85
|
+
try {
|
|
86
|
+
const abs = path.join(projectPath, rel);
|
|
87
|
+
return fs.statSync(abs).isDirectory();
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Enumerate first-level subproject directories of `projectPath`.
|
|
95
|
+
*
|
|
96
|
+
* Deterministic (locale-sorted), bounded by `opts.maxAreas` and `deadline`,
|
|
97
|
+
* dot-dir and `node_modules` filtered. Returns bare directory names (no slash).
|
|
98
|
+
*
|
|
99
|
+
* @param {string} projectPath — absolute path to the project checkout / worktree
|
|
100
|
+
* @param {object} [opts] — { maxAreas, walltimeMs } overrides
|
|
101
|
+
* @param {number} [deadline] — absolute ms timestamp; enumeration stops past it
|
|
102
|
+
* @returns {string[]} sorted first-level directory names
|
|
103
|
+
*/
|
|
104
|
+
function listAreas(projectPath, opts, deadline) {
|
|
105
|
+
const o = Object.assign({}, DEFAULTS, opts || {});
|
|
106
|
+
const stopAt = typeof deadline === 'number' ? deadline : _now() + Math.max(1, o.walltimeMs);
|
|
107
|
+
const out = [];
|
|
108
|
+
if (!projectPath || typeof projectPath !== 'string') return out;
|
|
109
|
+
let entries;
|
|
110
|
+
try {
|
|
111
|
+
entries = fs.readdirSync(projectPath, { withFileTypes: true });
|
|
112
|
+
} catch {
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
// Sort for deterministic ordering across OSes.
|
|
116
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
117
|
+
for (const ent of entries) {
|
|
118
|
+
if (out.length >= o.maxAreas) break;
|
|
119
|
+
if (_now() > stopAt) break;
|
|
120
|
+
if (!ent.isDirectory()) continue;
|
|
121
|
+
// Skip dot-dirs (.git, .github, .claude, …) and node_modules.
|
|
122
|
+
if (ent.name.startsWith('.')) continue;
|
|
123
|
+
if (ent.name === 'node_modules') continue;
|
|
124
|
+
out.push(ent.name);
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Reverse resolver: map a user-supplied component/subproject token to the real
|
|
131
|
+
* subproject path(s) inside `projectPath`.
|
|
132
|
+
*
|
|
133
|
+
* Resolution order:
|
|
134
|
+
* 1. Alias expansion — if the token is a known multi-path component (see
|
|
135
|
+
* AREA_ALIASES), return every candidate path that actually exists on disk,
|
|
136
|
+
* with `area` set to the primary first-level area. `matched: 'alias'`.
|
|
137
|
+
* 2. Exact match — case-insensitive match of the token against the first-level
|
|
138
|
+
* areas enumerated by `listAreas`. Returns the single `<area>/` path.
|
|
139
|
+
* `matched: 'exact'`.
|
|
140
|
+
* 3. No match — `{ area: null, paths: [], matched: 'none' }`. Callers should
|
|
141
|
+
* then ask the user instead of guessing.
|
|
142
|
+
*
|
|
143
|
+
* All work is bounded by `opts.walltimeMs`. Output paths are deterministic
|
|
144
|
+
* (sorted, de-duped), POSIX forward-slash, trailing-slash directories.
|
|
145
|
+
*
|
|
146
|
+
* @param {string} projectPath — absolute path to the project checkout / worktree
|
|
147
|
+
* @param {string} token — user-supplied component name (e.g. "OCM", "loop")
|
|
148
|
+
* @param {object} [opts] — { maxAreas, walltimeMs } overrides
|
|
149
|
+
* @returns {{area: string|null, paths: string[], matched: 'exact'|'alias'|'none'}}
|
|
150
|
+
*/
|
|
151
|
+
function resolveAreaFromToken(projectPath, token, opts) {
|
|
152
|
+
const result = { area: null, paths: [], matched: 'none' };
|
|
153
|
+
if (!projectPath || typeof projectPath !== 'string') return result;
|
|
154
|
+
if (typeof token !== 'string') return result;
|
|
155
|
+
const t = token.trim().toLowerCase();
|
|
156
|
+
if (!t) return result;
|
|
157
|
+
|
|
158
|
+
const o = Object.assign({}, DEFAULTS, opts || {});
|
|
159
|
+
const deadline = _now() + Math.max(1, o.walltimeMs);
|
|
160
|
+
|
|
161
|
+
const areas = listAreas(projectPath, o, deadline);
|
|
162
|
+
const areasLower = new Map(areas.map(a => [a.toLowerCase(), a]));
|
|
163
|
+
|
|
164
|
+
// 1. Alias expansion (multi-path components like OCM).
|
|
165
|
+
const alias = Object.prototype.hasOwnProperty.call(AREA_ALIASES, t)
|
|
166
|
+
? AREA_ALIASES[t]
|
|
167
|
+
: null;
|
|
168
|
+
if (alias) {
|
|
169
|
+
const verified = [];
|
|
170
|
+
for (const rel of alias.paths) {
|
|
171
|
+
if (_now() > deadline) break;
|
|
172
|
+
if (_isDir(projectPath, rel)) verified.push(_toPosixDir(rel));
|
|
173
|
+
}
|
|
174
|
+
if (verified.length > 0) {
|
|
175
|
+
const primaryDir = areasLower.get(alias.primary.toLowerCase()) || alias.primary;
|
|
176
|
+
return {
|
|
177
|
+
area: primaryDir,
|
|
178
|
+
paths: Array.from(new Set(verified)).sort(),
|
|
179
|
+
matched: 'alias',
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
// Alias known but none of its paths exist here (token used against a repo
|
|
183
|
+
// that is not the monorepo). Fall through to exact / none.
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 2. Exact case-insensitive match against enumerated first-level areas.
|
|
187
|
+
const exact = areasLower.get(t);
|
|
188
|
+
if (exact) {
|
|
189
|
+
return { area: exact, paths: [_toPosixDir(`${exact}/`)], matched: 'exact' };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module.exports = {
|
|
196
|
+
DEFAULTS,
|
|
197
|
+
AREA_ALIASES,
|
|
198
|
+
listAreas,
|
|
199
|
+
resolveAreaFromToken,
|
|
200
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2399",
|
|
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"
|
package/prompts/cc-system.md
CHANGED
|
@@ -204,6 +204,25 @@ curl -s http://localhost:{{dashboard_port}}/api/work-items
|
|
|
204
204
|
curl -s http://localhost:{{dashboard_port}}/api/status
|
|
205
205
|
```
|
|
206
206
|
|
|
207
|
+
## Resolving a monorepo component/subproject by name — GET /api/resolve-area
|
|
208
|
+
|
|
209
|
+
Monorepo projects (e.g. the Office `src` repo) contain many **subprojects** under first-level directories (`ocm/`, `loop/`, `officemobile/`, …). When a request names a component or subproject — "audit OCM changes", "Loop Android", "the LMF area" — that name refers to a **path inside the repo**, not to a PR title.
|
|
210
|
+
|
|
211
|
+
**Rule: resolve the area from the filesystem — NEVER infer a subproject's location from PR titles.** Scanning PR titles/branches to guess where a component lives is wrong (it caused a real incident: CC kept asking the operator where OCM was even though `<repo>/ocm/` exists on disk). Resolve the name to a real path first, then scope your work (`git log -- <path>`, `Glob`, dispatched WI description) to that path.
|
|
212
|
+
|
|
213
|
+
Resolve via `GET /api/resolve-area?project=<name>&token=<component>`:
|
|
214
|
+
```
|
|
215
|
+
curl -s 'http://localhost:{{dashboard_port}}/api/resolve-area?project=src&token=OCM'
|
|
216
|
+
```
|
|
217
|
+
Returns `{ok, project, token, area, paths, absPaths, matched}` where `matched` is:
|
|
218
|
+
- `"exact"` — the token is a first-level area (`paths: ["<area>/"]`).
|
|
219
|
+
- `"alias"` — a known multi-path component; `paths` lists every real subpath that exists on disk.
|
|
220
|
+
- `"none"` — no area matched. **Ask the user where the component lives — do not guess from PR titles.**
|
|
221
|
+
|
|
222
|
+
**Worked example (OCM → `ocm/` + wrapper/native paths).** `token=OCM` resolves to the top-level `ocm/` area PLUS its wrapper/native/telemetry paths (`officemobile/ocmnative/`, `officemobile/android/ocmciq/`, `officemobile/ios/OCMWrapper/`, `officemobile/telemetry/android/ocm/`, …) — only the paths that actually exist in the checkout are returned. Scope OCM audits/fixes to that full path set, not to PRs whose title happens to say "OCM".
|
|
223
|
+
|
|
224
|
+
If the endpoint returns `matched:"none"`, you may also enumerate first-level areas yourself with your `Glob`/`Bash` tools against the project's `localPath` (the reverse resolver just reuses that same first-level directory listing) — but still never fall back to PR titles.
|
|
225
|
+
|
|
207
226
|
## Read-only PR actions on ANY PR — POST /api/pr-action
|
|
208
227
|
|
|
209
228
|
When the user asks you to **review / summarize / comment-on / triage a specific PR by URL or id** — including PRs in repos that are NOT a configured Minions project (e.g. "review this PR https://github.com/some/repo/pull/42", "summarize ado:org/proj/repo#5215493", "triage github:yemi33/minions#2702") — route it through `POST /api/pr-action` instead of dispatching a `review`/`explore` work item. This path is **projectless and read-only**: it resolves the PR, fetches its diff + metadata with NO clone/worktree, and reasons over the (untrusted-fenced) content. Use it for one-off, look-don't-touch asks on an arbitrary PR.
|