@yemi33/minions 0.1.2048 → 0.1.2050
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/command-center.js +72 -0
- package/dashboard/js/command-history.js +2 -1
- package/dashboard/js/command-input.js +7 -3
- package/dashboard/js/command-parser.js +1 -0
- package/dashboard/js/detail-panel.js +7 -0
- package/dashboard/js/fre.js +1 -0
- package/dashboard/js/live-stream.js +2 -0
- package/dashboard/js/modal-qa.js +9 -0
- package/dashboard/js/modal.js +5 -0
- package/dashboard/js/qa.js +6 -0
- package/dashboard/js/refresh.js +8 -1
- package/dashboard/js/render-agents.js +30 -3
- package/dashboard/js/render-dispatch.js +12 -0
- package/dashboard/js/render-inbox.js +6 -0
- package/dashboard/js/render-kb.js +4 -0
- package/dashboard/js/render-managed.js +1 -0
- package/dashboard/js/render-meetings.js +6 -0
- package/dashboard/js/render-other.js +11 -0
- package/dashboard/js/render-pinned.js +2 -0
- package/dashboard/js/render-pipelines.js +5 -0
- package/dashboard/js/render-plans.js +9 -0
- package/dashboard/js/render-prd.js +8 -0
- package/dashboard/js/render-prs.js +4 -0
- package/dashboard/js/render-schedules.js +5 -0
- package/dashboard/js/render-skills.js +2 -0
- package/dashboard/js/render-watches.js +10 -0
- package/dashboard/js/render-work-items.js +9 -0
- package/dashboard/js/settings.js +387 -188
- package/dashboard/slim.html +8 -8
- package/dashboard/styles.css +35 -0
- package/docs/deprecated.json +57 -8
- package/engine/lifecycle.js +0 -1
- package/engine/queries.js +37 -5
- package/package.json +6 -3
- package/playbooks/fix.md +15 -5
- package/playbooks/shared-rules.md +29 -0
package/dashboard/slim.html
CHANGED
|
@@ -919,10 +919,10 @@
|
|
|
919
919
|
var flags = (data && data.features) || [];
|
|
920
920
|
|
|
921
921
|
// Build the body as a document fragment so we avoid .innerHTML for
|
|
922
|
-
// the dynamic parts
|
|
923
|
-
//
|
|
924
|
-
//
|
|
925
|
-
// (
|
|
922
|
+
// the dynamic parts. Static intro paragraph uses innerHTML because
|
|
923
|
+
// it carries a <code> tag and is a pure string literal (exempted
|
|
924
|
+
// by the no-unsanitized rule).
|
|
925
|
+
// (Comment retained for legibility; ratchet test is being retired.)
|
|
926
926
|
var intro = document.createElement('p');
|
|
927
927
|
intro.innerHTML = 'Toggle <code>slim-ux</code> off to return to the original dashboard. Advanced settings (agents, projects, runtime) live there for now.';
|
|
928
928
|
var frag = document.createDocumentFragment();
|
|
@@ -1342,8 +1342,8 @@
|
|
|
1342
1342
|
// Zero-project: a hint to add one. Selection persists per-browser in
|
|
1343
1343
|
// localStorage and is included in every /api/command-center/stream call.
|
|
1344
1344
|
// Build the "+ Add" button as a real DOM node (was an HTML string + innerHTML
|
|
1345
|
-
// injection —
|
|
1346
|
-
//
|
|
1345
|
+
// injection — the no-unsanitized/property rule prefers we avoid that pattern
|
|
1346
|
+
// entirely for dynamic data).
|
|
1347
1347
|
function makeContextAddBtn() {
|
|
1348
1348
|
var btn = document.createElement('button');
|
|
1349
1349
|
btn.type = 'button';
|
|
@@ -1911,8 +1911,8 @@
|
|
|
1911
1911
|
listEl.innerHTML = '<div class="history-empty">Nothing has happened yet — Command Center will fill this in.</div>';
|
|
1912
1912
|
return;
|
|
1913
1913
|
}
|
|
1914
|
-
// Build the feed as DOM nodes
|
|
1915
|
-
// (
|
|
1914
|
+
// Build the feed as DOM nodes to avoid dynamic .innerHTML
|
|
1915
|
+
// (no-unsanitized/property rule). Each event becomes
|
|
1916
1916
|
// .history-item > .history-head + .history-title + .history-meta, with
|
|
1917
1917
|
// the optional PR "open" link constructed as a real <a> node.
|
|
1918
1918
|
var frag = document.createDocumentFragment();
|
package/dashboard/styles.css
CHANGED
|
@@ -710,6 +710,41 @@
|
|
|
710
710
|
|
|
711
711
|
.modal-body { padding: var(--space-7) var(--space-8); overflow-y: auto; overflow-x: auto; white-space: pre-wrap; font-size: var(--text-md); line-height: 1.7; color: var(--muted); font-family: Consolas, monospace; }
|
|
712
712
|
|
|
713
|
+
/* ── Settings: left-rail tabbed layout (W-mpmwxkcn000646cc) ─────────────
|
|
714
|
+
Replaces the prior flat scroll of every engine.* knob with a Slack/VS-Code-style
|
|
715
|
+
vertical nav + per-tab pane. Search input filters control rows across all tabs
|
|
716
|
+
by data-search attribute. .modal.modal-wide is added by openSettings() so the
|
|
717
|
+
rail + content fit comfortably side-by-side. */
|
|
718
|
+
.modal-body.settings-body { padding: 0; white-space: normal; font-size: var(--text-md); line-height: 1.45; color: var(--text); font-family: 'Segoe UI', system-ui, sans-serif; }
|
|
719
|
+
.settings-layout { display: flex; min-height: 480px; max-height: calc(80vh - 64px); }
|
|
720
|
+
.settings-rail { width: 220px; min-width: 220px; background: var(--surface2); border-right: 1px solid var(--border); display: flex; flex-direction: column; overflow: hidden; }
|
|
721
|
+
.settings-search-wrap { padding: var(--space-5) var(--space-5) var(--space-4); border-bottom: 1px solid var(--border); }
|
|
722
|
+
.settings-search { width: 100%; padding: var(--space-3) var(--space-4); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text); font-size: var(--text-md); font-family: inherit; }
|
|
723
|
+
.settings-search:focus { outline: none; border-color: var(--blue); }
|
|
724
|
+
.settings-rail-nav { flex: 1; overflow-y: auto; padding: var(--space-3) 0; }
|
|
725
|
+
.settings-rail-btn { display: flex; align-items: center; justify-content: space-between; width: 100%; padding: var(--space-3) var(--space-6); background: transparent; border: none; border-left: 3px solid transparent; color: var(--muted); font-size: var(--text-md); font-family: inherit; cursor: pointer; text-align: left; transition: all var(--transition-fast); }
|
|
726
|
+
.settings-rail-btn:hover { color: var(--text); background: var(--surface); }
|
|
727
|
+
.settings-rail-btn.active { color: var(--blue); border-left-color: var(--blue); background: var(--surface); font-weight: 600; }
|
|
728
|
+
.settings-rail-btn.featured { color: var(--text); }
|
|
729
|
+
.settings-rail-btn.featured.active { color: var(--blue); }
|
|
730
|
+
.settings-rail-btn.no-match { opacity: 0.3; }
|
|
731
|
+
.settings-rail-btn .match-count { font-size: var(--text-xs); color: var(--blue); background: var(--bg); border-radius: var(--radius-xl); padding: 1px 6px; min-width: 18px; text-align: center; }
|
|
732
|
+
.settings-content { flex: 1; overflow-y: auto; padding: var(--space-7) var(--space-8); min-width: 0; }
|
|
733
|
+
.settings-pane { display: none; }
|
|
734
|
+
.settings-pane.active { display: block; }
|
|
735
|
+
.settings-pane h3 { font-size: var(--text-xl); color: var(--blue); margin-bottom: var(--space-3); font-weight: 600; }
|
|
736
|
+
.settings-pane .settings-pane-sub { font-size: var(--text-base); color: var(--muted); margin-bottom: var(--space-7); }
|
|
737
|
+
.settings-pane h4 { font-size: var(--text-lg); color: var(--text); margin: var(--space-7) 0 var(--space-4); font-weight: 600; }
|
|
738
|
+
.settings-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); }
|
|
739
|
+
.settings-grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: var(--space-4); }
|
|
740
|
+
.settings-stack { display: flex; flex-direction: column; gap: var(--space-3); }
|
|
741
|
+
.settings-row { padding: 2px 0; }
|
|
742
|
+
.settings-row.no-match { display: none; }
|
|
743
|
+
.settings-row.match-highlight { background: rgba(88,166,255,0.08); border-left: 2px solid var(--blue); padding-left: var(--space-3); border-radius: var(--radius-sm); }
|
|
744
|
+
.settings-body.searching .settings-pane { display: block; }
|
|
745
|
+
.settings-body.searching .settings-pane.no-match { display: none; }
|
|
746
|
+
.settings-pane-empty { font-size: var(--text-base); color: var(--muted); padding: var(--space-6) 0; font-style: italic; }
|
|
747
|
+
|
|
713
748
|
/* Responsive: tablet / narrow window */
|
|
714
749
|
@media (max-width: 1100px) {
|
|
715
750
|
.layout { grid-template-columns: 1fr; }
|
package/docs/deprecated.json
CHANGED
|
@@ -1,28 +1,77 @@
|
|
|
1
1
|
[
|
|
2
2
|
{
|
|
3
3
|
"id": "config-poll-key-migration",
|
|
4
|
-
"location": "engine/queries.js:
|
|
4
|
+
"location": "engine/queries.js:126-163",
|
|
5
5
|
"function": "migrateDeprecatedConfigPollKeysOnce",
|
|
6
6
|
"reason": "One-time boot migration that renames legacy engine config keys (adoPollStatusEvery → prPollStatusEvery, adoPollCommentsEvery → prPollCommentsEvery). Keys were renamed on 2026-04-16; this shim ensures any persisted config.json on a long-lived host gets transparently rewritten on next boot.",
|
|
7
7
|
"targetRemovalDate": "2026-08-09",
|
|
8
|
-
"notes": "Safe to delete on or after 2026-08-09 (3 months after the rename) once we are confident every engine has rebooted at least once and the closure-flagged migration (_configPollKeyMigrationChecked at line
|
|
8
|
+
"notes": "Safe to delete on or after 2026-08-09 (3 months after the rename) once we are confident every engine has rebooted at least once and the closure-flagged migration (_configPollKeyMigrationChecked at line 124) is no longer reachable on any persisted config. Removal is one function + the call site in getConfig() at engine/queries.js:166."
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
"id": "legacy-done-aliases",
|
|
12
|
-
"location": "engine/cleanup.js:
|
|
12
|
+
"location": "engine/cleanup.js:1052-1054",
|
|
13
13
|
"constants": ["LEGACY_DONE_ALIASES", "LEGACY_NEEDS_REVIEW_STATUS"],
|
|
14
14
|
"reason": "Read-side tolerance: cleanup sweep auto-migrates four obsolete work-item / PRD status strings ('in-pr', 'implemented', 'complete', 'needs-human-review') to the canonical 'done' / 'failed' values. The aliases are no longer written anywhere in the engine; the constants exist only to repair stale on-disk values from old engine versions.",
|
|
15
15
|
"targetRemovalDate": null,
|
|
16
|
-
"notes": "Keep indefinitely until telemetry / a sweep log shows zero migrations performed for 30 consecutive days across all known projects (work-items.json + prd/*.json). At that point the constants and both _migrateLegacyItem branches in engine/cleanup.js (definitions at :
|
|
16
|
+
"notes": "Keep indefinitely until telemetry / a sweep log shows zero migrations performed for 30 consecutive days across all known projects (work-items.json + prd/*.json). At that point the constants and both _migrateLegacyItem branches in engine/cleanup.js (definitions at :1052-1054; usage at :1055-1071 for work items and :1156-1162 for PRD missing_features) can be deleted. Total cost on disk today: 4 strings."
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"id": "completion-fallback-parsers",
|
|
20
20
|
"description": "parseStructuredCompletion and parseCompletionFieldSummary in engine/lifecycle.js",
|
|
21
21
|
"file": "engine/lifecycle.js",
|
|
22
|
-
"lines": "
|
|
23
|
-
"telemetryGate": "_engine.completionFallbacks must read 0 across
|
|
22
|
+
"lines": "2856, 3054",
|
|
23
|
+
"telemetryGate": "_engine.completionFallbacks must read 0 (both fenced and summary counters) across sweepWindowDays starting from sweepStartDate",
|
|
24
|
+
"sweepWindowDays": 14,
|
|
25
|
+
"sweepStartDate": "2026-05-27",
|
|
26
|
+
"sweepRationale": "14 days matches the dead-code audit cadence (one full weekly audit cycle plus a buffer week). Policy decision pending confirmation in open-question #1 of the 2026-05-27 Bug Audit Review meeting conclusion — if the human teammate prefers a different cadence, repin this field and the matching enforcingSweepWindowTest fixture.",
|
|
24
27
|
"enforcingTest": "test/unit/completion-fallback-telemetry.test.js:217-234",
|
|
25
|
-
"
|
|
28
|
+
"enforcingSweepWindowTest": "test/unit/completion-fallback-sweep-window.test.js",
|
|
29
|
+
"notes": "Do NOT set removedAt until telemetry confirms zero usage across the sweepWindowDays from sweepStartDate. The follow-up code-removal PR (dropping parseStructuredCompletion at engine/lifecycle.js:2856, parseCompletionFieldSummary at :3054, and the gated fallback at :3852-3877) is dispatched separately once the window is observed clean."
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "config-claude-binary-override",
|
|
33
|
+
"description": "Legacy `config.claude.binary` runtime-resolution override. Older `minions init` versions persisted a `config.claude.binary` field that pointed the Claude runtime at a specific binary path. The runtime adapter still honors this override on every Claude spawn; the engine emits a `deprecated-config-claude` warning at config-load time but does NOT delete the override, so the override branch in claude.js is load-bearing for any install that still carries a non-default value.",
|
|
34
|
+
"code": [
|
|
35
|
+
{ "file": "engine/runtimes/claude.js", "lines": "82-93", "note": "resolveBinary() respects config.claude.binary on every Claude spawn (probes npm package dir or direct binary path)" },
|
|
36
|
+
{ "file": "engine/shared.js", "lines": "2311-2326", "note": "warnings.push({ id: 'deprecated-config-claude' }) — surface-only; never deletes the override" },
|
|
37
|
+
{ "file": "engine/shared.js", "lines": "2770-2774", "note": "DEFAULT_CLAUDE.binary baseline that the warning + prune logic compares against" }
|
|
38
|
+
],
|
|
39
|
+
"removalGate": "Telemetry: the `deprecated-config-claude` warning emitted at engine/shared.js:2321-2324 must report zero hits across all known engines for >=30 consecutive days, AND a sweep of every persisted config.json must show no `config.claude.binary` value that diverges from DEFAULT_CLAUDE.binary. Only then is the override branch in resolveBinary() (engine/runtimes/claude.js:82-93) removable, along with the `_deprecatedConfigClaudeFields` membership for `binary` and the warning emitter at engine/shared.js:2311-2326.",
|
|
40
|
+
"targetRemovalDate": null,
|
|
41
|
+
"notes": "Do NOT set targetRemovalDate — removal must be signal-gated, not calendar-gated. This entry is paired with `prune-default-claude-config`: the prune strips DEFAULT-matching values but intentionally preserves user overrides, which is precisely why the override branch in claude.js stays reachable. Removing the override before the prune entry's gate clears would silently break installs that still rely on a custom binary path."
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "legacy-cc-model-migration",
|
|
45
|
+
"description": "applyLegacyCcModelMigration: in-memory shim that promotes legacy `engine.ccModel` to `engine.defaultModel` when defaultModel is unset, so single-model installs keep working after the runtime fleet refactor (P-3b8e5f1d). No on-disk rewrite — the persisted config.json continues to carry the legacy `ccModel` field. Called unconditionally on every engine boot from cli.start().",
|
|
46
|
+
"code": [
|
|
47
|
+
{ "file": "engine/shared.js", "lines": "2236", "note": "applyLegacyCcModelMigration definition (function signature + once-per-process flag via _resetLegacyCcModelMigrationFlag)" },
|
|
48
|
+
{ "file": "engine/cli.js", "lines": "471-472", "note": "Boot call site inside start(); wrapped in try/catch so a migration failure cannot block startup" },
|
|
49
|
+
{ "file": "CLAUDE.md", "lines": "316", "note": "Architectural documentation calling out the in-memory promotion contract" },
|
|
50
|
+
{ "file": "docs/slim-ux/concepts.md", "lines": "671", "note": "Surface-level concepts doc cross-reference" },
|
|
51
|
+
{ "file": "test/unit.test.js", "lines": "19402", "note": "Source-inspection test pinning the CLAUDE.md description against the function name" },
|
|
52
|
+
{ "file": "test/unit/runtime-fleet-helpers.test.js", "lines": "209-254", "note": "Behavioural unit tests (promotion, no-op when defaultModel set, no-op when ccModel unset, empty-string handling, once-only logging, null-safety)" },
|
|
53
|
+
{ "file": "test/unit/runtime-fleet-helpers.test.js", "lines": "500-505", "note": "Source-inspection test pinning the cli.js boot call site" }
|
|
54
|
+
],
|
|
55
|
+
"removalGate": "Telemetry: the once-per-boot deprecation log line emitted by applyLegacyCcModelMigration (via the injected logger at engine/shared.js:2236) must show zero promotion events across all known engines for >=30 consecutive days, AND a sweep of every persisted config.json must confirm no `engine.ccModel` field remains. Once both conditions hold, removal deletes the function + _resetLegacyCcModelMigrationFlag export at engine/shared.js:4977, the boot call at engine/cli.js:471-472, the CLAUDE.md:316 paragraph and docs/slim-ux/concepts.md:671 reference, and the tests at runtime-fleet-helpers.test.js:209-254 + :500-505 + unit.test.js:19402.",
|
|
56
|
+
"targetRemovalDate": null,
|
|
57
|
+
"notes": "Do NOT set targetRemovalDate — gating is signal-based. The function is silent on no-op (returns false without logging), so the meaningful telemetry signal is the absence of the promotion log line over the sweep window, NOT the absence of function invocations (cli.js calls it every boot regardless)."
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "prune-default-claude-config",
|
|
61
|
+
"description": "pruneDefaultClaudeConfig: active sanitizer that strips generated `config.claude.{binary,outputFormat,allowedTools,permissionMode}` defaults from persisted config.json so the `deprecated-config-claude` warning stops tripping on stale defaults left by older `minions init` versions. Sub-cluster of `config-claude-binary-override` — the prune deliberately preserves non-default user overrides (binary/allowedTools), which is what keeps the override branch in engine/runtimes/claude.js load-bearing.",
|
|
62
|
+
"code": [
|
|
63
|
+
{ "file": "engine/shared.js", "lines": "2776-2809", "note": "pruneDefaultClaudeConfig definition: preserves non-default binary/allowedTools, always strips permissionMode + outputFormat" },
|
|
64
|
+
{ "file": "engine/shared.js", "lines": "4989", "note": "Module export entry" },
|
|
65
|
+
{ "file": "dashboard.js", "lines": "196", "note": "Called when loading config for the dashboard UI" },
|
|
66
|
+
{ "file": "dashboard.js", "lines": "8753", "note": "Called during first config save handler" },
|
|
67
|
+
{ "file": "dashboard.js", "lines": "8983", "note": "Called during second config save path" },
|
|
68
|
+
{ "file": "dashboard.js", "lines": "9102", "note": "Called during third config save path" },
|
|
69
|
+
{ "file": "minions.js", "lines": "385", "note": "Called during CLI init/update flow" },
|
|
70
|
+
{ "file": "test/unit.test.js", "lines": "2153-2196", "note": "Behavioural unit tests (default strip, override preservation, outputFormat unconditional strip) + dashboard call-site source pin" },
|
|
71
|
+
{ "file": "test/unit/runtime-fleet-helpers.test.js", "lines": "546", "note": "Source-inspection test pinning the dashboard handler call site" }
|
|
72
|
+
],
|
|
73
|
+
"removalGate": "Telemetry: pruneDefaultClaudeConfig must return false (no mutation) for every call across all known engines for >=30 consecutive days (add an `_engine.pruneDefaultClaudeConfigStrips` counter if needed to observe this), AND the parent `config-claude-binary-override` entry must have already cleared its own gate. The dependency is strict: removing the prune while users still rely on the override branch would surface the `deprecated-config-claude` warning on every stale generated default. Once both conditions hold, removal is the function definition (engine/shared.js:2776-2809), the export at :4989, all 5 call sites (dashboard.js:196, :8753, :8983, :9102; minions.js:385), and the tests at unit.test.js:2153-2196 + runtime-fleet-helpers.test.js:546.",
|
|
74
|
+
"targetRemovalDate": null,
|
|
75
|
+
"notes": "Do NOT set targetRemovalDate — gating is signal-based AND ordered. This entry MUST NOT be removed before `config-claude-binary-override` clears its gate, otherwise installs with stale defaults will flood the deprecation channel until their next config save. The 5 call sites form a complete coverage net: load (dashboard.js:196 + minions.js:385) + save (dashboard.js:8753/8983/9102), so any code path that touches config.json runs the sanitizer."
|
|
26
76
|
}
|
|
27
77
|
]
|
|
28
|
-
|
package/engine/lifecycle.js
CHANGED
package/engine/queries.js
CHANGED
|
@@ -531,6 +531,11 @@ function getAgents(config) {
|
|
|
531
531
|
// → engine.defaultCli → 'copilot'. Surfaced so the dashboard can show a
|
|
532
532
|
// runtime tag next to the agent name.
|
|
533
533
|
const runtime = shared.resolveAgentCli(a, config.engine || {});
|
|
534
|
+
// Resolve the model alongside the runtime so the dashboard can render a
|
|
535
|
+
// sibling chip (W-mpmwxk4y00053271). `resolveAgentModel` returns
|
|
536
|
+
// `undefined` when neither the per-agent override nor `engine.defaultModel`
|
|
537
|
+
// is set, which the renderer treats as "runtime default" and omits.
|
|
538
|
+
const model = shared.resolveAgentModel(a, config.engine || {}) || null;
|
|
534
539
|
const inboxFiles = allInboxFiles.filter(f => f.includes(a.id));
|
|
535
540
|
let steeringInboxFiles = [];
|
|
536
541
|
try { steeringInboxFiles = steering.listUnreadSteeringMessages(a.id); } catch { steeringInboxFiles = []; }
|
|
@@ -552,7 +557,7 @@ function getAgents(config) {
|
|
|
552
557
|
const chartered = fs.existsSync(path.join(AGENTS_DIR, a.id, 'charter.md'));
|
|
553
558
|
if (lastAction.length > 120) lastAction = lastAction.slice(0, 120) + '...';
|
|
554
559
|
return {
|
|
555
|
-
...a, runtime, status: s.status, lastAction,
|
|
560
|
+
...a, runtime, model, status: s.status, lastAction,
|
|
556
561
|
currentTask: (s.task || '').slice(0, 200),
|
|
557
562
|
// P-w2c8d1e7 — Phase 1.2: surface the active dispatch id so watch
|
|
558
563
|
// captureState can snapshot it for currentDispatchId-aware predicates.
|
|
@@ -1236,8 +1241,16 @@ function getWorkItems(config) {
|
|
|
1236
1241
|
}
|
|
1237
1242
|
}
|
|
1238
1243
|
if (!item._pr) {
|
|
1239
|
-
// Derive from PR.prdItems (single source of truth)
|
|
1240
|
-
|
|
1244
|
+
// Derive from PR.prdItems (single source of truth), then fall back to
|
|
1245
|
+
// branch identity. The branch fallback heals decomposed-child WIs whose
|
|
1246
|
+
// PR-opening agent only stamped the parent id into prdItems — branch
|
|
1247
|
+
// names (`work/<wi-id>`) are unique per WI within a project, so a
|
|
1248
|
+
// branch hit is as authoritative as a prdItems hit. Scoped to the same
|
|
1249
|
+
// project so cross-repo branch collisions don't false-link.
|
|
1250
|
+
// (W-mpn0b76200044eed)
|
|
1251
|
+
const itemProject = item.project || item._source;
|
|
1252
|
+
const linkedPr = allPrs.find(p => (p.prdItems || []).includes(item.id))
|
|
1253
|
+
|| (item.branch && allPrs.find(p => p.branch === item.branch && p._project === itemProject));
|
|
1241
1254
|
if (linkedPr) {
|
|
1242
1255
|
item._pr = linkedPr.id;
|
|
1243
1256
|
item._prUrl = linkedPr.url;
|
|
@@ -1925,6 +1938,10 @@ function getProjectGitStatus(localPath, configuredMainBranch = null) {
|
|
|
1925
1938
|
const key = _projectGitStatusCacheKey(localPath, configuredMainBranch);
|
|
1926
1939
|
const now = Date.now();
|
|
1927
1940
|
const cached = _projectGitStatusCache.get(key);
|
|
1941
|
+
// Compute ref-mtime freshness once. Used twice below: to gate the TTL
|
|
1942
|
+
// fast-path AND to decide whether the stale-while-revalidate return
|
|
1943
|
+
// should null out the known-stale ahead/behind counters (#2848).
|
|
1944
|
+
const refsAdvanced = !!(cached && _projectGitRefsAdvancedSince(localPath, configuredMainBranch, cached.refMtimes));
|
|
1928
1945
|
// Within TTL: short-circuit ONLY when no tracked git ref has advanced
|
|
1929
1946
|
// past cached.ts. Without the mtime gate, a freshly-pulled repo serves
|
|
1930
1947
|
// the pre-pull ahead/behind counts for up to 15s + one SPA poll (~19s
|
|
@@ -1939,7 +1956,7 @@ function getProjectGitStatus(localPath, configuredMainBranch = null) {
|
|
|
1939
1956
|
if (cachedIsMissing && fs.existsSync(localPath)) {
|
|
1940
1957
|
// Path came back — fall through to schedule a fresh probe.
|
|
1941
1958
|
} else if (cached && cached.ts && (now - cached.ts) < PROJECT_GIT_STATUS_TTL
|
|
1942
|
-
&& !
|
|
1959
|
+
&& !refsAdvanced) {
|
|
1943
1960
|
return cached.value;
|
|
1944
1961
|
}
|
|
1945
1962
|
// Cheap synchronous existsSync — short-circuits a path that just disappeared
|
|
@@ -1955,7 +1972,22 @@ function getProjectGitStatus(localPath, configuredMainBranch = null) {
|
|
|
1955
1972
|
// previous value (or pending placeholder on the very first call). The next
|
|
1956
1973
|
// /api/status response after the refresh settles will have fresh data.
|
|
1957
1974
|
_scheduleProjectGitStatusRefresh(localPath, key, configuredMainBranch);
|
|
1958
|
-
|
|
1975
|
+
if (!cached) return PROJECT_GIT_STATUS_PENDING;
|
|
1976
|
+
// When tracked git refs have advanced past the cached snapshot, the
|
|
1977
|
+
// cached ahead/behind counts are known to be stale (e.g. user just ran
|
|
1978
|
+
// `git pull` / `git fetch` and the local checkout is now current).
|
|
1979
|
+
// Returning the cached value as-is would briefly display a misleading
|
|
1980
|
+
// "behind: 8" after the user has already pulled to current — see
|
|
1981
|
+
// yemi33/minions#2848. Null those specific counters so the dashboard
|
|
1982
|
+
// shows a pending state for ahead/behind until the background probe
|
|
1983
|
+
// settles; branch and dirty flags stay valid enough on the cached
|
|
1984
|
+
// value (and update on the next poll once the probe lands). TTL-expiry
|
|
1985
|
+
// fall-throughs (refsAdvanced=false) still return the prior counters
|
|
1986
|
+
// because nothing local has actually changed there.
|
|
1987
|
+
if (refsAdvanced) {
|
|
1988
|
+
return { ...cached.value, ahead: null, behind: null };
|
|
1989
|
+
}
|
|
1990
|
+
return cached.value;
|
|
1959
1991
|
}
|
|
1960
1992
|
|
|
1961
1993
|
// Force a refresh now and wait for completion. Called from dashboard boot
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2050",
|
|
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"
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"test:perf": "node test/perf/managed-spawn-load.test.js",
|
|
24
24
|
"test:e2e:accept": "node test/playwright/accept-baseline.js",
|
|
25
25
|
"test:e2e:accept-force": "node test/playwright/accept-baseline.js --force",
|
|
26
|
-
"test:setup": "npx playwright install chromium"
|
|
26
|
+
"test:setup": "npx playwright install chromium",
|
|
27
|
+
"lint": "eslint dashboard/"
|
|
27
28
|
},
|
|
28
29
|
"keywords": [
|
|
29
30
|
"ai",
|
|
@@ -65,7 +66,9 @@
|
|
|
65
66
|
"@azure-devops/mcp": "2.7.0"
|
|
66
67
|
},
|
|
67
68
|
"devDependencies": {
|
|
68
|
-
"@playwright/test": "^1.58.2"
|
|
69
|
+
"@playwright/test": "^1.58.2",
|
|
70
|
+
"eslint": "^9.39.4",
|
|
71
|
+
"eslint-plugin-no-unsanitized": "^4.1.5"
|
|
69
72
|
},
|
|
70
73
|
"publishConfig": {
|
|
71
74
|
"access": "public"
|
package/playbooks/fix.md
CHANGED
|
@@ -93,11 +93,21 @@ Do NOT remove the worktree — the engine handles cleanup automatically.
|
|
|
93
93
|
|
|
94
94
|
## Resolve Review Comments
|
|
95
95
|
|
|
96
|
-
After pushing, respond to each review comment/thread
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
- **
|
|
96
|
+
After pushing, respond to each review comment/thread. **Every disposition requires a reply before you resolve, close, or mark-fixed the thread — no exceptions.** Resolving a thread silently is a process violation (see "Resolving Review Threads — No Silent Closures" in `shared-rules.md`).
|
|
97
|
+
|
|
98
|
+
Pick the disposition that matches the finding and post the matching reply, then act on the thread:
|
|
99
|
+
|
|
100
|
+
- **Fixed** → Reply confirming the fix with commit SHA or `file:line` of the change, then resolve the thread.
|
|
101
|
+
- **Satisfied elsewhere** (addressed by another chunk / sibling PR / existing wiring the reviewer missed) → Reply citing the exact PR number, commit SHA, or `file:line` that satisfies it, then resolve. Example: `Satisfied by chunk 6 (PR 5216079) — ArtifactsMainScreen.kt:305 re-fires start() via LaunchedEffect on every screen enter.`
|
|
102
|
+
- **Deferred** (non-blocking nit you accept but choose not to act on now) → Reply starting with `Deferred:` and a one-line reason, then resolve. Example: `Deferred: default-off rollout switch makes this acceptable for v1; revisit if telemetry shows otherwise.`
|
|
103
|
+
- **Tracked as follow-up** → Reply with `Tracked as follow-up: W-…` and the WI link, then resolve (see `## Follow-up Dispatch` below).
|
|
104
|
+
- **Won't fix / disagree** (comment is invalid, stale, already addressed, out of scope, or harmful) → Reply with the evidence-backed rationale explaining why no code change was made. Leave the thread **active** for the reviewer to decide; do NOT resolve unilaterally.
|
|
105
|
+
|
|
106
|
+
Host-specific mechanics:
|
|
107
|
+
- **GitHub**: Reply to each review comment, then resolve the conversation for the four resolve-eligible dispositions above. Leave Won't-fix threads unresolved.
|
|
108
|
+
- **ADO**: Use `az` CLI first to reply to each thread and update status when supported; use ADO MCP only as a fallback when `az` is unavailable or insufficient. Set status to `fixed` for Fixed/Satisfied-elsewhere/Tracked-as-follow-up and `closed` for Deferred; leave `active` for Won't-fix rationale replies.
|
|
109
|
+
|
|
110
|
+
Hard rule: never resolve, close, or mark-fixed a thread without first posting a reply that explains the disposition. The chunk-5 / Caleb-Tseng / MaiLibraryViewModel thread (ADO 5215549 thread 65692221) is the canonical bad example — closed silently with no audit trail.
|
|
101
111
|
|
|
102
112
|
## Follow-up Dispatch
|
|
103
113
|
|
|
@@ -140,6 +140,35 @@ Concretely:
|
|
|
140
140
|
- If you skipped local validation, say so in the completion JSON (e.g. `tests: skipped — relying on PR pipeline`) and still exit.
|
|
141
141
|
- Holding a slot to watch a pipeline is wasted capacity; the engine has its own pipeline-monitoring path.
|
|
142
142
|
|
|
143
|
+
## Resolving Review Threads — No Silent Closures
|
|
144
|
+
|
|
145
|
+
Whenever you resolve, close, or mark-fixed any PR review thread (GitHub
|
|
146
|
+
conversation or ADO thread), you MUST first post a reply on that thread
|
|
147
|
+
explaining the disposition. This is non-negotiable across every playbook
|
|
148
|
+
(`fix`, `review`, `implement`, follow-up dispatch, anything that touches
|
|
149
|
+
a review thread).
|
|
150
|
+
|
|
151
|
+
Allowed dispositions and what the reply MUST contain:
|
|
152
|
+
- **Fixed** → reply with commit SHA or file:line of the change, then resolve.
|
|
153
|
+
- **Satisfied elsewhere** (addressed by another chunk / sibling PR / existing
|
|
154
|
+
wiring the reviewer missed) → reply citing the exact PR number, commit
|
|
155
|
+
SHA, or `file:line` that satisfies it, then resolve. Example:
|
|
156
|
+
`Satisfied by chunk 6 (PR 5216079) — ArtifactsMainScreen.kt:305
|
|
157
|
+
re-fires start() via LaunchedEffect on every screen enter.`
|
|
158
|
+
- **Deferred** (non-blocking nit you accept but choose not to act on now)
|
|
159
|
+
→ reply starting with `Deferred:` and a one-line reason, then resolve.
|
|
160
|
+
Example: `Deferred: default-off rollout switch makes this acceptable
|
|
161
|
+
for v1; revisit if telemetry shows otherwise.`
|
|
162
|
+
- **Tracked as follow-up** → reply with `Tracked as follow-up: W-…` and
|
|
163
|
+
the WI link, then resolve (see follow-up dispatch template).
|
|
164
|
+
- **Won't fix / disagree** → reply with the evidence-backed rationale;
|
|
165
|
+
leave the thread **active** for the reviewer to decide. Do NOT resolve
|
|
166
|
+
unilaterally.
|
|
167
|
+
|
|
168
|
+
Hard rule: resolving a thread with no reply comment is a process violation.
|
|
169
|
+
The chunk-5 / Caleb-Tseng / MaiLibraryViewModel thread (ADO 5215549 thread
|
|
170
|
+
65692221) is the canonical bad example — closed silently with no audit trail.
|
|
171
|
+
|
|
143
172
|
{{#github_shared_rules}}
|
|
144
173
|
## Checking PR and Build Status
|
|
145
174
|
|