@yemi33/minions 0.1.2197 → 0.1.2199

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.
@@ -1447,11 +1447,41 @@ function _ccActionResultLine(action, result) {
1447
1447
  }
1448
1448
  if (result && result.ok) {
1449
1449
  var duplicate = result.duplicate ? ' <span style="color:var(--orange)">already exists</span>' : '';
1450
- return '<div class="cc-action-feedback-chip" style="' + CC_ACTION_CHIP_STYLE + ';color:var(--green)">&#10003; ' + label + duplicate + '</div>';
1450
+ var chip = '<div class="cc-action-feedback-chip" style="' + CC_ACTION_CHIP_STYLE + ';color:var(--green)">&#10003; ' + label + duplicate + '</div>';
1451
+ // P-ppa10004: a successful pr-action carries follow-up affordances
1452
+ // ([Comment] [Fix once] [Track for auto-fix]); render them as clickable
1453
+ // chips that send a templated CC turn.
1454
+ var followups = (result && result.followups) || (action && action.followups);
1455
+ return chip + _ccFollowupChips(followups);
1451
1456
  }
1452
1457
  return '';
1453
1458
  }
1454
1459
 
1460
+ // Render follow-up action chips (P-ppa10004). Each chip click sends its
1461
+ // templated CC message as a fresh turn via ccPrActionFollowup.
1462
+ function _ccFollowupChips(followups) {
1463
+ if (!Array.isArray(followups) || followups.length === 0) return '';
1464
+ var btns = followups.map(function(f) {
1465
+ if (!f || !f.label || !f.message) return '';
1466
+ return '<button type="button" class="cc-followup-chip" onclick="ccPrActionFollowup(' + _ccJsArg(f.message) + ')" ' +
1467
+ 'style="padding:3px 10px;border-radius:4px;font-size:var(--text-sm);border:1px solid var(--border);background:var(--surface2);color:var(--blue);cursor:pointer">' +
1468
+ escHtml(f.label) + '</button>';
1469
+ }).filter(Boolean).join('');
1470
+ if (!btns) return '';
1471
+ return '<div class="cc-followup-chips" style="margin:4px 0 0 0;display:flex;flex-wrap:wrap;gap:6px">' + btns + '</div>';
1472
+ }
1473
+
1474
+ // Click handler for a follow-up action chip: drop the templated message into
1475
+ // the input and send it as a normal CC turn (so the user sees their intent and
1476
+ // CC routes it — forward-compatible with the Phase 2/3 flows).
1477
+ function ccPrActionFollowup(message) {
1478
+ if (!message) return;
1479
+ var input = document.getElementById('cc-input');
1480
+ if (!input) return;
1481
+ input.value = String(message);
1482
+ ccSend();
1483
+ }
1484
+
1455
1485
 
1456
1486
  function _ccAppendHtmlToMessage(tabOrId, messageId, html) {
1457
1487
  if (!messageId || !html) return false;
@@ -2163,4 +2193,4 @@ if (document.readyState === 'loading') {
2163
2193
  ccInitResize();
2164
2194
  }
2165
2195
 
2166
- window.MinionsCC = { toggleCommandCenter, ccNewSession, ccNewTab, ccSwitchTab, ccCloseTab, ccRenderTabBar, ccRestoreMessages, ccSaveState, ccUpdateSessionIndicator, ccAddMessage, ccSend, ccAbort, ccExecuteAction };
2196
+ window.MinionsCC = { toggleCommandCenter, ccNewSession, ccNewTab, ccSwitchTab, ccCloseTab, ccRenderTabBar, ccRestoreMessages, ccSaveState, ccUpdateSessionIndicator, ccAddMessage, ccSend, ccAbort, ccExecuteAction, ccPrActionFollowup };
package/dashboard.js CHANGED
@@ -42,6 +42,10 @@ const { wrapUntrusted, buildSource } = require('./engine/untrusted-fence');
42
42
  const steering = require('./engine/steering');
43
43
  const steeringStore = require('./engine/steering-store');
44
44
  const projectDiscovery = require('./engine/project-discovery');
45
+ const prAction = require('./engine/pr-action');
46
+ const prResolve = require('./engine/pr-resolve');
47
+ const prFixTarget = require('./engine/pr-fix-target');
48
+ const prTrack = require('./engine/pr-track');
45
49
  const features = require('./engine/features');
46
50
  const ccWorkerPool = require('./engine/cc-worker-pool');
47
51
  const diagnosticsMemory = require('./engine/diagnostics-memory');
@@ -3404,6 +3408,9 @@ function _buildSyntheticActionResultsForTurn(turnId, message, requestedAt) {
3404
3408
  };
3405
3409
  if (entry.project) action.project = entry.project;
3406
3410
  if (entry.path) action.path = entry.path;
3411
+ // P-ppa10004: pr-action chips carry follow-up affordances ([Comment]
3412
+ // [Fix once] [Track for auto-fix]) so the renderer can offer the next step.
3413
+ if (Array.isArray(entry.followups) && entry.followups.length) action.followups = entry.followups;
3407
3414
  actions.push(action);
3408
3415
  const result = {
3409
3416
  ok: true,
@@ -3419,6 +3426,7 @@ function _buildSyntheticActionResultsForTurn(turnId, message, requestedAt) {
3419
3426
  if (entry.id) result.id = entry.id;
3420
3427
  if (entry.project) result.project = entry.project;
3421
3428
  if (entry.path) result.path = entry.path;
3429
+ if (Array.isArray(entry.followups) && entry.followups.length) result.followups = entry.followups;
3422
3430
  results.push(result);
3423
3431
  }
3424
3432
  return { actions, results };
@@ -3435,6 +3443,7 @@ function _ccTurnEntryToActionType(kind) {
3435
3443
  case 'pipeline-run': return 'trigger-pipeline';
3436
3444
  case 'watch': return 'create-watch';
3437
3445
  case 'meeting': return 'create-meeting';
3446
+ case 'pr-action': return 'pr-action';
3438
3447
  default: return kind;
3439
3448
  }
3440
3449
  }
@@ -3949,14 +3958,6 @@ function getWorkItemPrRef(input) {
3949
3958
  return shared.extractWorkItemPrRef(input);
3950
3959
  }
3951
3960
 
3952
- function isPrTargetedWorkType(workType) {
3953
- return ['fix', 'review', 'test'].includes(String(workType || '').toLowerCase());
3954
- }
3955
-
3956
- function trimTrailingPrRefPunctuation(value) {
3957
- return String(value || '').replace(/[),.;:]+$/g, '');
3958
- }
3959
-
3960
3961
  // ── Shared LLM call core — used by CC panel and doc modals ──────────────────
3961
3962
 
3962
3963
  // Session store for doc modals — keyed by filePath or title, persisted to disk.
@@ -12606,6 +12607,92 @@ What would you like to discuss or change? When you're happy, say "approve" and I
12606
12607
  })();
12607
12608
  }},
12608
12609
 
12610
+ { method: 'POST', path: '/api/pr-action', desc: 'User-initiated read-only action on any GitHub/ADO PR URL (no project/clone required). Resolves the PR via the projectless resolver. Default: returns an observable dispatch handle. With `execute:true`: runs the read-only action now (fetch → fence → DIRECT LLM) and returns its output plus follow-up action chips ([Comment] [Fix once] [Track for auto-fix]). Read-only only — no auto-polling/discovery.', params: 'url (PR URL or canonical id), action (review|summarize|comment|triage), execute (optional bool — run now and return output + follow-up chips)', handler: async (req, res) => {
12611
+ const body = await readBody(req);
12612
+ try {
12613
+ if (body && body.execute) {
12614
+ reloadConfig();
12615
+ const record = await prAction.runPrAction({ url: body?.url, action: body?.action }, { engineConfig: CONFIG.engine });
12616
+ const followups = record.status === 'done' ? prAction.buildPrActionFollowups(record, { prUrl: body?.url }) : [];
12617
+ recordCcTurnIfPresent(req, { kind: 'pr-action', id: record.id, title: `${record.action} ${record.prId || ''}`.trim(), followups });
12618
+ return jsonReply(res, 200, { ok: record.status !== 'failed', ...record, followups }, req);
12619
+ }
12620
+ const handle = await prAction.createPrAction({ url: body?.url, action: body?.action });
12621
+ recordCcTurnIfPresent(req, { kind: 'pr-action', id: handle.id, title: `${handle.action} ${handle.prId || ''}`.trim() });
12622
+ return jsonReply(res, 200, { ok: true, ...handle }, req);
12623
+ } catch (e) {
12624
+ return jsonReply(res, e.statusCode || 400, { error: e.message }, req);
12625
+ }
12626
+ }},
12627
+
12628
+ { method: 'POST', path: '/api/pr-action/fix', desc: 'Plan a one-off fix on any GitHub/ADO PR URL (Phase 2, P-ppa10005 — decision/routing surface only). Without `target`: returns the execution-target choice. If the PR’s repo is already a configured project, returns {status:"project-ready"} (skip the prompt, use the existing project fix path); otherwise {status:"awaiting-target-choice"} with options [clone-keep, temp-clone (default), remote-patch, devbox] plus a CC prompt + dashboard modal — NOTHING is cloned. With `target`: validates the choice and returns the routing intent {status:"routed", executor} to the matching executor. temp-clone (default) is implemented in engine/pr-temp-clone.js (executor.module:"pr-temp-clone"), clone-keep in engine/pr-clone-keep.js (executor.module:"pr-clone-keep", auto-clone + persistent project registration), remote-patch in engine/pr-remote-patch.js (executor.module:"pr-remote-patch", GitHub Contents API single-file edit, NO clone and NO validation — GitHub-only), and devbox in engine/pr-devbox.js (executor.module:"pr-devbox", P-ppa10011 stretch — ephemeral clone+fix+push ON A DEVBOX, zero local footprint, never auto-selected). Never clones silently. P-ppa10009: pass `remember:true` alongside `target` to remember the choice per repo so subsequent fixes on that repo skip the prompt; a remembered choice makes the no-`target` call return {status:"routed", fromRemembered:true} instead of prompting. Pass `reprompt:true` to force the prompt despite a remembered choice (override). Clear a remembered choice via POST /api/pr-action/fix-target/forget.', params: 'url (PR URL or canonical id), target (optional — clone-keep|temp-clone|remote-patch|devbox), remember (optional bool — persist target per repo), reprompt (optional bool — force the choice despite a remembered one)', handler: async (req, res) => {
12629
+ const body = await readBody(req);
12630
+ try {
12631
+ reloadConfig();
12632
+ if (body && body.target != null && body.target !== '') {
12633
+ // `remember:true` persists this choice per repo so future fixes skip
12634
+ // the prompt (P-ppa10009).
12635
+ const routed = prFixTarget.resolvePrFixTarget({ url: body?.url }, body.target, { config: CONFIG, remember: !!body.remember });
12636
+ recordCcTurnIfPresent(req, { kind: 'pr-action', id: routed.prId || '', title: `fix ${routed.prId || ''} → ${routed.target}`.trim() });
12637
+ return jsonReply(res, 200, { ok: true, ...routed }, req);
12638
+ }
12639
+ // `reprompt:true` forces the execution-target prompt even when a per-repo
12640
+ // choice is remembered (the override path for P-ppa10009).
12641
+ const plan = prFixTarget.planPrFix({ url: body?.url }, { config: CONFIG, prUrl: body?.url, ignoreRemembered: !!body?.reprompt });
12642
+ recordCcTurnIfPresent(req, { kind: 'pr-action', id: plan.prId || '', title: `fix ${plan.prId || ''} (${plan.status})`.trim() });
12643
+ return jsonReply(res, 200, { ok: true, ...plan }, req);
12644
+ } catch (e) {
12645
+ return jsonReply(res, e.statusCode || 400, { error: e.message }, req);
12646
+ }
12647
+ }},
12648
+
12649
+ { method: 'POST', path: '/api/pr-action/fix-target/forget', desc: 'Clear/override the remembered per-repo execution-target choice (Phase 3, P-ppa10009). Given a PR `url` (or canonical id), forgets the remembered clone-keep|temp-clone|remote-patch choice for that repo scope so the next fix re-prompts. Returns {cleared:true} if a remembered choice existed, {cleared:false} otherwise.', params: 'url (PR URL or canonical id)', handler: async (req, res) => {
12650
+ const body = await readBody(req);
12651
+ try {
12652
+ const ref = prResolve.normalizePrRef(typeof body?.url === 'string' ? body.url.trim() : '');
12653
+ if (!ref) return jsonReply(res, 400, { error: `unrecognized PR reference: ${JSON.stringify(String(body?.url || '').slice(0, 120))}` }, req);
12654
+ const cleared = prFixTarget.clearRememberedTargetForRef(ref);
12655
+ recordCcTurnIfPresent(req, { kind: 'pr-action', id: ref.id || '', title: `forget fix target ${ref.id || ''}`.trim() });
12656
+ return jsonReply(res, 200, { ok: true, cleared, prId: ref.id || null }, req);
12657
+ } catch (e) {
12658
+ return jsonReply(res, e.statusCode || 400, { error: e.message }, req);
12659
+ }
12660
+ }},
12661
+
12662
+ { method: 'POST', path: '/api/pr-action/track', desc: 'Promote a one-off PR to TRACKED auto-fix (Phase 3, P-ppa10010 — enrollment wiring). If the PR’s repo is NOT a configured project, returns {status:"awaiting-target-choice"} with options [clone-keep (recommended for ongoing tracking), temp-clone, remote-patch] + a CC prompt and dashboard modal — NOTHING is cloned (pick "clone-keep" via POST /api/pr-action/fix to register a persistent project, then call this again to finish enrollment). If the repo IS a configured project, enrolls the PR via the manual-link path with contextOnly:false (the canonical shared.isAutoManagedPrRecord signal) and returns {status:"enrolled", autoManaged:true}. From enrollment on the EXISTING pollers/discoverFromPrs → review/fix/re-review → auto-merge pipeline runs unchanged — no new fix engine.', params: 'url (PR URL or canonical id)', handler: async (req, res) => {
12663
+ const body = await readBody(req);
12664
+ try {
12665
+ reloadConfig();
12666
+ const plan = prTrack.planPrTrack({ url: body?.url }, { config: CONFIG, prUrl: body?.url });
12667
+ if (plan.status === 'awaiting-target-choice') {
12668
+ // Unconfigured repo — ongoing tracking needs a persistent checkout, so
12669
+ // pause for the Clone & keep choice. NOTHING is cloned/enrolled yet.
12670
+ recordCcTurnIfPresent(req, { kind: 'pr-action', id: plan.prId || '', title: `track ${plan.prId || ''} (${plan.status})`.trim() });
12671
+ return jsonReply(res, 200, { ok: true, ...plan }, req);
12672
+ }
12673
+ // project-ready: enroll via the existing manual-link path. Linking with
12674
+ // contextOnly:false creates the record if missing and PROMOTES an existing
12675
+ // context-only record to auto-managed (upsert refuses to demote a managed
12676
+ // PR). No new engine — the existing pipeline discovers it next tick.
12677
+ const enrollment = prTrack.buildTrackEnrollment(plan, { prUrl: body?.url });
12678
+ const linkResult = linkPullRequestForTracking({ url: enrollment.url, contextOnly: enrollment.contextOnly }, CONFIG);
12679
+ invalidateStatusCache();
12680
+ const autoManaged = shared.isAutoManagedPrRecord(linkResult.record);
12681
+ recordCcTurnIfPresent(req, { kind: 'pr-action', id: linkResult.id, title: `track ${linkResult.id} → auto-fix`.trim() });
12682
+ return jsonReply(res, 200, {
12683
+ ok: true,
12684
+ status: 'enrolled',
12685
+ prId: linkResult.id,
12686
+ autoManaged,
12687
+ contextOnly: false,
12688
+ project: linkResult.targetProject?.name || 'central',
12689
+ projectName: plan.projectName || linkResult.targetProject?.name || null,
12690
+ }, req);
12691
+ } catch (e) {
12692
+ return jsonReply(res, e.statusCode || 400, { error: e.message }, req);
12693
+ }
12694
+ }},
12695
+
12609
12696
  { method: 'POST', path: '/api/pull-requests/observe', desc: 'Toggle canonical contextOnly flag on a tracked PR (public `observe` body param is preserved as the inverse for backward compat)', params: 'host (github|ado), slug, number, observe (boolean)', handler: async (req, res) => {
12610
12697
  const body = await readBody(req);
12611
12698
  reloadConfig();
package/docs/README.md CHANGED
@@ -21,15 +21,18 @@ Architecture, design proposals, and lifecycle references for people working on t
21
21
  - [constellation-style-telemetry.md](constellation-style-telemetry.md) — Feasibility study (design proposal, not implemented) for a local-first usage/analytics layer modelled on Constellation's telemetry stack — typed append-only event log + retention/rollup discipline + dashboard Usage page. Explains why a 1:1 PostgreSQL/multi-tenant port is the wrong goal for Minions.
22
22
  - [cooldown-merge-semantics.md](cooldown-merge-semantics.md) — Scoping deliverable defining merge semantics for `saveCooldowns` (longer-of TTL merge, key-level upserts, gitignored on-disk format).
23
23
  - [copilot-cli-schema.md](copilot-cli-schema.md) — Behavior and schema reference for the GitHub Copilot CLI adapter (capability flags, stdin vs `-p`, model discovery, effort levels).
24
+ - [cross-repo-plans.md](cross-repo-plans.md) — Cross-repo plans: a single plan whose work items ship into two or more configured projects — per-item `project` field, per-project work-item fan-out, and one verify work item per touched repo.
24
25
  - [dead-code-audit-retractions.md](dead-code-audit-retractions.md) — Retracted dead-code-audit findings (false positives) that future audits MUST read before re-citing.
25
26
  - [deprecated-process.md](deprecated-process.md) — Schema for `docs/deprecated.json` and the weekly `cleanup-deprecated` audit walk that retires entries past their removal signal.
26
27
  - [design-state-storage.md](design-state-storage.md) — Design proposal evaluating five database options for replacing Minions' file-based JSON state; recommends `node:sqlite` as the medium-term target (accepted; implementation tracked in CHANGELOG.md Phases 0–9).
27
28
  - [harness-mode.md](harness-mode.md) — Tri-Agent Harness Mode (`harness_mode: "tri_agent"` on scheduled tasks): Planner → Generator → Evaluator loop that iterates a shared on-disk artifact until a rubric passes or the iteration cap fires.
29
+ - [harness-propagation.md](harness-propagation.md) — How user-level and project-local harness assets (skills, slash-commands, MCP config, `CLAUDE.md` / `AGENTS.md`) propagate into an agent's worktree via `--add-dir`, the `harnessPropagateProjectLocal` flag, and the project-local-on-main worktree-visibility footgun.
28
30
  - [kb-sweep.md](kb-sweep.md) — Knowledge-base consolidation sweep (hash dedup → LLM batch dedup/reclassify → per-entry compress) and the detached runner that keeps it alive across `minions restart`.
29
31
  - [keep-processes.md](keep-processes.md) — `meta.keep_processes` sidecar contract: when to use it vs managed-spawn, sidecar schema, caps, and the [`engine/keep-process-sweep.js`](../engine/keep-process-sweep.js) lifecycle.
30
32
  - [live-checkout-mode.md](live-checkout-mode.md) — Per-project opt-in `worktreeMode: 'live'`: skips `git worktree add` and dispatches in-place inside `project.localPath` for `repo`-managed trees, submodule-heavy repos, deep Windows paths, and native build state. Includes the refuse-on-dirty contract and the per-project mutating-concurrency cap of 1.
31
33
  - [managed-spawn.md](managed-spawn.md) — Engine-owned long-running services (managed-spawn primitive): sidecar schema, healthcheck examples, lifecycle, dashboard API, and the WI 1 (build) → WI 2 (test) chained-validation pattern.
32
34
  - [plan-lifecycle.md](plan-lifecycle.md) — Full plan pipeline from `/plan` through PRD materialization, dispatch with dependency gating, verify task, and human archive.
35
+ - [pr-auto-fix-dispatch.md](pr-auto-fix-dispatch.md) — Short reference table mapping each PR auto-fix / review dispatch site in `engine.js#discoverFromPrs` to its gate flag, plus the `pollingPaused` / `autoFixPaused` master kill-switches and the per-provider polling gates.
33
36
  - [pr-comment-followup.md](pr-comment-followup.md) — PR-comment follow-up dispatch contract: fix/review agents may spin off a new WI via `POST /api/work-items` with `meta.pr_followup` instead of broadening the current PR or rebutting the comment.
34
37
  - [pr-review-fix-loop.md](pr-review-fix-loop.md) — How the engine moves a PR from creation through review, fix dispatch, and re-review, including stale-status guards.
35
38
  - [project-skills.md](project-skills.md) — Project-local skill discovery (`.claude/skills/`, `.claude/commands/`, `CLAUDE.md` / `.github/copilot-instructions.md` slash-command mentions): how dispatched agents see and steer toward purpose-built tooling the project ships, plus the intent-vocabulary contract.
@@ -0,0 +1,29 @@
1
+ // engine/db/migrations/014-pr-fix-target-prefs.js
2
+ //
3
+ // Plan: projectless-pr-actions, Phase 3, item P-ppa10009 — "Remember
4
+ // execution-target choice per repo (optional)".
5
+ //
6
+ // Adds the `pr_fix_target_prefs` table — one row per repo scope — so the
7
+ // execution-target choice surface (engine/pr-fix-target.js) can OPTIONALLY
8
+ // remember the user's pick (clone-keep | temp-clone | remote-patch) keyed by
9
+ // canonical PR scope (github:owner/repo / ado:org/proj/repo). When a remembered
10
+ // choice exists, a repeat fix on that repo routes straight to the executor
11
+ // instead of re-prompting. Absence of a row still prompts (the safe default —
12
+ // never clone silently). The choice is clearable/overridable.
13
+ //
14
+ // SQL-first per CLAUDE.md "New state goes into SQL first" — no JSON sidecar;
15
+ // reads/writes go through engine/pr-fix-target-store.js.
16
+
17
+ module.exports = {
18
+ version: 14,
19
+ description: 'pr_fix_target_prefs: remembered per-repo execution-target choices',
20
+ up(db) {
21
+ db.exec(`
22
+ CREATE TABLE pr_fix_target_prefs (
23
+ repo_scope TEXT PRIMARY KEY,
24
+ target TEXT NOT NULL,
25
+ updated_at INTEGER NOT NULL
26
+ );
27
+ `);
28
+ },
29
+ };
@@ -109,6 +109,35 @@ function resolveTokenForSlug(slug, opts = {}) {
109
109
  return _fetchTokenForAccount(account, opts);
110
110
  }
111
111
 
112
+ /**
113
+ * Resolve a GitHub PAT for a named account directly (bypassing slug→account
114
+ * mapping). Used by the projectless PR resolver's unknown-slug fallback
115
+ * (engine/pr-resolve.js) to try each authed account in turn WITHOUT touching
116
+ * the global active `gh auth` profile. Returns the token string or null.
117
+ */
118
+ function tokenForAccount(account, opts = {}) {
119
+ return _fetchTokenForAccount(account, opts);
120
+ }
121
+
122
+ /**
123
+ * List the distinct gh account names configured in `engine.ghAccounts`. This is
124
+ * the candidate set the unknown-slug fallback iterates over (per the plan:
125
+ * "fall back across the available authed accounts" rather than relying on the
126
+ * active `gh auth` profile). Order follows the config object's own key order.
127
+ */
128
+ function listConfiguredAccounts(opts = {}) {
129
+ const config = _readConfig(opts);
130
+ const accounts = (config && config.engine && config.engine.ghAccounts) || {};
131
+ if (!accounts || typeof accounts !== 'object') return [];
132
+ const seen = new Set();
133
+ const out = [];
134
+ for (const value of Object.values(accounts)) {
135
+ const name = value == null ? '' : String(value);
136
+ if (name && !seen.has(name)) { seen.add(name); out.push(name); }
137
+ }
138
+ return out;
139
+ }
140
+
112
141
  /** Test-only: prime a slug→token override that bypasses config + shell-out. */
113
142
  function _setTokenForTest(slug, token) {
114
143
  if (!slug) return;
@@ -127,6 +156,8 @@ function _clearTokenCache() {
127
156
  module.exports = {
128
157
  resolveTokenForSlug,
129
158
  resolveAccountForSlug,
159
+ tokenForAccount,
160
+ listConfiguredAccounts,
130
161
  _setTokenForTest,
131
162
  _clearTokenCache,
132
163
  // Constants exposed for tests + diagnostics.