@yemi33/minions 0.1.2424 → 0.1.2425
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/bin/install-internal-minions.js +1209 -0
- package/bin/minions.js +75 -9
- package/dashboard/docs/typography.md +27 -12
- package/dashboard/js/command-center.js +13 -11
- package/dashboard/js/live-stream.js +1 -1
- package/dashboard/js/memory-search.js +388 -43
- package/dashboard/js/modal-qa.js +5 -5
- package/dashboard/js/qa.js +39 -26
- package/dashboard/js/refresh.js +37 -18
- package/dashboard/js/render-agents.js +14 -18
- package/dashboard/js/render-dispatch.js +14 -74
- package/dashboard/js/render-inbox.js +3 -3
- package/dashboard/js/render-meetings.js +8 -8
- package/dashboard/js/render-other.js +45 -20
- package/dashboard/js/render-pipelines.js +6 -6
- package/dashboard/js/render-plans.js +14 -14
- package/dashboard/js/render-prd.js +46 -46
- package/dashboard/js/render-prs.js +9 -65
- package/dashboard/js/render-schedules.js +5 -5
- package/dashboard/js/render-skills.js +6 -6
- package/dashboard/js/render-utils.js +4 -4
- package/dashboard/js/render-watches.js +4 -16
- package/dashboard/js/render-work-items.js +95 -107
- package/dashboard/js/settings.js +175 -74
- package/dashboard/layout.html +3 -2
- package/dashboard/pages/inbox.html +1 -1
- package/dashboard/pages/work.html +1 -1
- package/dashboard/shared/model-display.js +9 -0
- package/dashboard/shared/watches-source.js +43 -0
- package/dashboard/shared/welcome-popup.js +259 -0
- package/dashboard/slim/body.html +6 -6
- package/dashboard/slim/js/chat.js +4 -2
- package/dashboard/slim/js/history.js +10 -3
- package/dashboard/slim/js/modals-tiles.js +85 -16
- package/dashboard/slim/js/status.js +12 -16
- package/dashboard/slim/styles.css +24 -4
- package/dashboard/styles.css +62 -27
- package/dashboard-build.js +8 -2
- package/dashboard.js +3854 -2514
- package/docs/README.md +6 -4
- package/docs/api-errors.md +144 -0
- package/docs/auto-discovery.md +83 -61
- package/docs/capture-demos.js +173 -30
- package/docs/command-center.md +5 -1
- package/docs/completion-reports.md +127 -11
- package/docs/constants.md +15 -3
- package/docs/constellation-style-telemetry.md +2 -2
- package/docs/copilot-cli-schema.md +32 -22
- package/docs/cross-repo-plans.md +30 -30
- package/docs/demo/memory-system.html +1 -1
- package/docs/deprecated.json +132 -18
- package/docs/diagnostics-memory.md +8 -4
- package/docs/human-vs-automated.md +2 -2
- package/docs/internal-install.md +190 -0
- package/docs/kb-pr3223-cascade-archiving.md +16 -0
- package/docs/kb-pr696-merge-conflict-docs.md +23 -0
- package/docs/kb-sweep.md +2 -2
- package/docs/keep-processes.md +8 -1
- package/docs/live-checkout-mode.md +1 -1
- package/docs/managed-spawn.md +21 -3
- package/docs/named-agents.md +3 -2
- package/docs/plan-lifecycle.md +32 -32
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
- package/docs/qa-runbook-lifecycle.md +67 -51
- package/docs/qa-runbooks.md +2 -2
- package/docs/runtime-adapters.md +101 -50
- package/docs/security.md +28 -0
- package/docs/self-improvement.md +47 -13
- package/docs/skills.md +29 -0
- package/docs/slim-ux/concepts.md +2 -2
- package/docs/specs/agent-configurability.md +43 -41
- package/docs/specs/agent-rename.md +13 -13
- package/docs/team-memory.md +283 -14
- package/docs/tutorials/03-plan-driven-feature.md +3 -3
- package/docs/tutorials/04-runtimes-and-harness.md +18 -21
- package/docs/tutorials/08-operations-and-recovery.md +3 -2
- package/docs/tutorials/README.md +4 -3
- package/docs/visual-evidence-ci.md +103 -0
- package/docs/watches.md +35 -30
- package/docs/worktree-lifecycle.md +151 -13
- package/engine/acp-transport.js +1 -0
- package/engine/ado.js +80 -33
- package/engine/agent-api-validation.js +571 -0
- package/engine/agent-worker-pool.js +7 -6
- package/engine/api-contracts/agent-content.js +761 -0
- package/engine/api-contracts/cc-ops.js +219 -0
- package/engine/api-contracts/config-runtime.js +419 -0
- package/engine/api-contracts/core.js +138 -0
- package/engine/api-contracts/index.js +506 -0
- package/engine/api-contracts/orchestration.js +602 -0
- package/engine/api-contracts/pull-requests.js +323 -0
- package/engine/api-contracts/qa-process.js +764 -0
- package/engine/api-contracts/work-plan-prd.js +494 -0
- package/engine/api-validation.js +543 -0
- package/engine/cc-api-validation.js +586 -0
- package/engine/cc-worker-pool.js +9 -1
- package/engine/cleanup.js +393 -271
- package/engine/cli.js +65 -7
- package/engine/comment-classifier.js +70 -5
- package/engine/consolidation.js +123 -5
- package/engine/content-api-validation.js +606 -0
- package/engine/create-pr-worktree.js +22 -1
- package/engine/db/migrations/027-review-learning-lifecycle.js +162 -0
- package/engine/dispatch.js +33 -0
- package/engine/distribution.js +189 -0
- package/engine/execution-model.js +59 -0
- package/engine/features.js +12 -0
- package/engine/github.js +67 -25
- package/engine/harness-context.js +313 -0
- package/engine/inbox-store.js +1 -1
- package/engine/kb-sweep.js +161 -38
- package/engine/keep-process-sweep.js +43 -3
- package/engine/lifecycle.js +1066 -165
- package/engine/llm.js +4 -0
- package/engine/managed-spawn.js +463 -48
- package/engine/meeting.js +197 -34
- package/engine/memory-retrieval.js +280 -0
- package/engine/memory-store.js +821 -38
- package/engine/model-discovery.js +17 -3
- package/engine/pipeline.js +802 -14
- package/engine/plan-prd-validation.js +745 -0
- package/engine/playbook.js +206 -17
- package/engine/pooled-agent-process.js +6 -1
- package/engine/pr-action.js +1 -1
- package/engine/pr-issue-validation.js +653 -0
- package/engine/pr-resolve.js +75 -13
- package/engine/prd-store.js +39 -6
- package/engine/preflight.js +7 -2
- package/engine/process-utils.js +79 -31
- package/engine/projects.js +198 -12
- package/engine/promotion.js +371 -0
- package/engine/qa-from-prd.js +3 -0
- package/engine/qa-process-validation.js +572 -0
- package/engine/qa-runbooks.js +41 -37
- package/engine/qa-runners/maestro.js +11 -7
- package/engine/qa-runners/playwright.js +37 -14
- package/engine/qa-runners.js +9 -10
- package/engine/qa-runs.js +124 -11
- package/engine/qa-sessions.js +139 -40
- package/engine/quarantine-refs.js +81 -11
- package/engine/queries.js +163 -9
- package/engine/review-learning-backfill.js +482 -0
- package/engine/review-learning.js +1236 -0
- package/engine/runtimes/claude.js +60 -2
- package/engine/runtimes/codex.js +57 -16
- package/engine/runtimes/contract.js +21 -0
- package/engine/runtimes/copilot.js +51 -3
- package/engine/runtimes/index.js +1 -0
- package/engine/scheduler.js +201 -32
- package/engine/settings-validation.js +909 -0
- package/engine/shared.js +705 -208
- package/engine/spawn-agent.js +48 -21
- package/engine/steering-constraints.js +31 -0
- package/engine/steering-store.js +10 -2
- package/engine/steering.js +14 -2
- package/engine/timeout.js +404 -111
- package/engine/untrusted-fence.js +24 -10
- package/engine/watch-actions.js +7 -2
- package/engine/watches.js +353 -51
- package/engine/work-item-validation.js +561 -0
- package/engine/work-items-store.js +61 -1
- package/engine/worktree-gc.js +656 -37
- package/engine/worktree-preflight.js +154 -0
- package/engine.js +2068 -342
- package/package.json +1 -1
- package/playbooks/_pr-description-audit.md +88 -41
- package/playbooks/build-fix-complex.md +3 -3
- package/playbooks/docs.md +2 -2
- package/playbooks/fix.md +18 -18
- package/playbooks/implement.md +18 -18
- package/playbooks/qa-session-draft.md +10 -12
- package/playbooks/qa-session-execute.md +16 -12
- package/playbooks/qa-validate.md +8 -7
- package/playbooks/shared-rules.md +45 -0
- package/playbooks/test.md +4 -0
- package/prompts/cc-system.md +1 -1
- package/skills/check-self-authored-review-comment/SKILL.md +2 -2
package/engine/cli.js
CHANGED
|
@@ -23,6 +23,17 @@ function engine() {
|
|
|
23
23
|
let _dispatchModule = null;
|
|
24
24
|
function dispatchModule() { if (!_dispatchModule) _dispatchModule = require('./dispatch'); return _dispatchModule; }
|
|
25
25
|
|
|
26
|
+
function spawnPendingDispatch(e, item, config) {
|
|
27
|
+
return e._withSpawnAttemptLease(
|
|
28
|
+
item.id,
|
|
29
|
+
e.tickGeneration,
|
|
30
|
+
(lease) => e.spawnAgent(item, config, lease),
|
|
31
|
+
).catch((err) => {
|
|
32
|
+
e.log('error', `Immediate spawn failed for ${item.id}: ${err.message}`);
|
|
33
|
+
return null;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
26
37
|
function normalizeSessionBranch(branch) {
|
|
27
38
|
if (!branch) return null;
|
|
28
39
|
return String(branch).replace(/^refs\/heads\//, '');
|
|
@@ -635,7 +646,33 @@ const commands = {
|
|
|
635
646
|
continue;
|
|
636
647
|
}
|
|
637
648
|
|
|
638
|
-
let agentPid =
|
|
649
|
+
let agentPid = null;
|
|
650
|
+
let hadPid = false;
|
|
651
|
+
if (item.runtimePid) {
|
|
652
|
+
let persistedRuntimePid = null;
|
|
653
|
+
try { persistedRuntimePid = shared.validatePid(item.runtimePid); } catch { persistedRuntimePid = null; }
|
|
654
|
+
if (persistedRuntimePid) {
|
|
655
|
+
hadPid = true;
|
|
656
|
+
if (isPidAlive(persistedRuntimePid)) agentPid = persistedRuntimePid;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
if (!agentPid) {
|
|
660
|
+
const filePid = readDispatchPid(item.id);
|
|
661
|
+
if (filePid) {
|
|
662
|
+
agentPid = filePid;
|
|
663
|
+
hadPid = true;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
if (!agentPid && item._spawnPhase === 'launching') {
|
|
667
|
+
e.engineRestartGraceExempt.add(item.id);
|
|
668
|
+
e.log('info', `Restart: ${agentId} (${item.id}) stopped during pre-launch handoff — marking for retry`);
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
if (!agentPid && hadPid) {
|
|
672
|
+
e.engineRestartGraceExempt.add(item.id);
|
|
673
|
+
e.log('info', `Restart: ${agentId} (${item.id}) had no live runtime PID — marking for retry`);
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
639
676
|
|
|
640
677
|
if (!agentPid) {
|
|
641
678
|
const status = getAgentStatus(agentId);
|
|
@@ -651,7 +688,6 @@ const commands = {
|
|
|
651
688
|
}
|
|
652
689
|
}
|
|
653
690
|
|
|
654
|
-
const hadPid = agentPid && agentPid > 0; // track before liveness check
|
|
655
691
|
if (agentPid && agentPid > 0 && !isPidAlive(agentPid)) agentPid = null;
|
|
656
692
|
if (agentPid && !shared.isDispatchExecutionPathSafe(item, config)) {
|
|
657
693
|
e.log('warn', `Reattach: refusing ${item.id} — persisted execution path is not safe under current checkout config`);
|
|
@@ -999,8 +1035,12 @@ const commands = {
|
|
|
999
1035
|
const cfg = require('./queries').getConfig();
|
|
1000
1036
|
const projects = sharedMod.getProjects(cfg);
|
|
1001
1037
|
if (projects.length === 0) return;
|
|
1038
|
+
const worktreeProjects = projects.filter((project) => {
|
|
1039
|
+
try { return !sharedMod.isLiveCheckoutProject(project); }
|
|
1040
|
+
catch { return false; }
|
|
1041
|
+
});
|
|
1002
1042
|
const worktreeRootRel = cfg?.engine?.worktreeRoot || sharedMod.ENGINE_DEFAULTS.worktreeRoot;
|
|
1003
|
-
const
|
|
1043
|
+
const reconcileResult = worktreeGc.reconcileStaleWorktreeDirectories({
|
|
1004
1044
|
projects,
|
|
1005
1045
|
dispatchSnap,
|
|
1006
1046
|
worktreeRootRel,
|
|
@@ -1008,6 +1048,19 @@ const commands = {
|
|
|
1008
1048
|
config: cfg,
|
|
1009
1049
|
writeToInbox: (a, s, c) => { try { return sharedMod.writeToInbox(a, s, c); } catch (_e) { return false; } },
|
|
1010
1050
|
});
|
|
1051
|
+
if (reconcileResult.removed > 0 || reconcileResult.failed > 0) {
|
|
1052
|
+
console.log(` Worktree filesystem reconcile: removed ${reconcileResult.removed} stale director${reconcileResult.removed === 1 ? 'y' : 'ies'}, kept ${reconcileResult.kept}, failed ${reconcileResult.failed} (scanned ${reconcileResult.scanned})`);
|
|
1053
|
+
}
|
|
1054
|
+
const result = worktreeGc.pruneOrphanWorktrees({
|
|
1055
|
+
projects: worktreeProjects,
|
|
1056
|
+
dispatchSnap,
|
|
1057
|
+
worktreeRootRel,
|
|
1058
|
+
log: (lvl, msg) => e.log(lvl, msg),
|
|
1059
|
+
config: cfg,
|
|
1060
|
+
writeToInbox: (a, s, c) => { try { return sharedMod.writeToInbox(a, s, c); } catch (_e) { return false; } },
|
|
1061
|
+
protectedPaths: reconcileResult.protectedPaths,
|
|
1062
|
+
failedRoots: reconcileResult.failedRoots,
|
|
1063
|
+
});
|
|
1011
1064
|
if (result.evicted > 0 || result.failed > 0) {
|
|
1012
1065
|
console.log(` Worktree boot reconcile: evicted ${result.evicted} orphan worktree(s)${result.failed ? `, ${result.failed} failed` : ''} (scanned ${result.scanned}, kept ${result.kept})`);
|
|
1013
1066
|
}
|
|
@@ -1018,7 +1071,7 @@ const commands = {
|
|
|
1018
1071
|
// consistent. Also runs `git worktree prune --expire=now` per project
|
|
1019
1072
|
// so manually-removed dirs drop out of the registry immediately.
|
|
1020
1073
|
const regResult = worktreeGc.pruneOrphanWorktreesFromGitRegistry({
|
|
1021
|
-
projects,
|
|
1074
|
+
projects: worktreeProjects,
|
|
1022
1075
|
dispatchSnap,
|
|
1023
1076
|
worktreeRootRel,
|
|
1024
1077
|
log: (lvl, msg) => e.log(lvl, msg),
|
|
@@ -1400,8 +1453,13 @@ const commands = {
|
|
|
1400
1453
|
const eng = config.engine || {};
|
|
1401
1454
|
const defaultCli = shared.resolveAgentCli(null, eng);
|
|
1402
1455
|
const defaultModelResolved = shared.resolveAgentModel(null, eng);
|
|
1456
|
+
const defaultModelDisplay = queries.resolveRuntimeModelDisplay({
|
|
1457
|
+
runtime: defaultCli,
|
|
1458
|
+
configuredModel: defaultModelResolved,
|
|
1459
|
+
dispatch: getDispatch(),
|
|
1460
|
+
});
|
|
1403
1461
|
console.log(`Default CLI: ${defaultCli}${eng.defaultCli ? '' : ' (default)'}`);
|
|
1404
|
-
console.log(`Default model: ${
|
|
1462
|
+
console.log(`Default model: ${defaultModelDisplay.model || '(runtime model not observed yet)'}`);
|
|
1405
1463
|
const ccCliOverride = (eng.ccCli !== undefined && eng.ccCli !== null && eng.ccCli !== '') ? String(eng.ccCli) : null;
|
|
1406
1464
|
const ccModelOverride = (eng.ccModel !== undefined && eng.ccModel !== null && eng.ccModel !== '') ? String(eng.ccModel) : null;
|
|
1407
1465
|
if (ccCliOverride || ccModelOverride) {
|
|
@@ -1556,7 +1614,7 @@ const commands = {
|
|
|
1556
1614
|
const dispatch = getDispatch();
|
|
1557
1615
|
const item = dispatch.pending.find(d => d.id === id);
|
|
1558
1616
|
if (item) {
|
|
1559
|
-
e
|
|
1617
|
+
spawnPendingDispatch(e, item, config);
|
|
1560
1618
|
}
|
|
1561
1619
|
},
|
|
1562
1620
|
|
|
@@ -1764,7 +1822,7 @@ const commands = {
|
|
|
1764
1822
|
const dispatch = getDispatch();
|
|
1765
1823
|
const item = dispatch.pending.find(d => d.id === id);
|
|
1766
1824
|
if (item) {
|
|
1767
|
-
e
|
|
1825
|
+
spawnPendingDispatch(e, item, config);
|
|
1768
1826
|
console.log('Agent spawned immediately.');
|
|
1769
1827
|
}
|
|
1770
1828
|
} else {
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* signatures + Coverage/Build/Test/Deploy/Pipeline report headings +
|
|
9
9
|
* shields.io-style badge images. Ported from engine/github.js
|
|
10
10
|
* `_isPreviewStatusComment` body checks. ADO has no `user.type === 'bot'`
|
|
11
|
-
* equivalent, so the
|
|
12
|
-
*
|
|
11
|
+
* equivalent, so only the narrower `_isPreviewStatusComment` helper keeps
|
|
12
|
+
* a GH-side bot-author gate; both pollers consume this body match directly.
|
|
13
13
|
*
|
|
14
14
|
* hasMinionsMarker(body) — structural `<!-- minions:agent=… kind=… -->`
|
|
15
15
|
* check using gh-comment.MINIONS_COMMENT_MARKER_RE. Strict: marker must
|
|
@@ -29,9 +29,14 @@
|
|
|
29
29
|
* mid-paragraph mentions are rejected (line-start anchor + only `**`,
|
|
30
30
|
* `__`, `#+` allowed before `VERDICT:`).
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* The reason-returning `classifyNonActionableAutomationComment()` composes
|
|
33
|
+
* `isPreviewStatusBody()` with exact host-specific automation envelopes.
|
|
34
|
+
* Unlike the body predicates, it also receives trusted host author metadata
|
|
35
|
+
* so a copied marker cannot suppress actionable feedback.
|
|
36
|
+
*
|
|
37
|
+
* IMPORTANT — author identity is intentionally NOT consulted by the three
|
|
38
|
+
* body-only predicates above. Callers MUST combine Minions marker matches with
|
|
39
|
+
* their own identity gates before treating a comment as agent-authored:
|
|
35
40
|
*
|
|
36
41
|
* GitHub: `comment.viewerDidAuthor === true`, backfilled via `gh api user`
|
|
37
42
|
* by engine/github.js `_backfillViewerDidAuthor`. The combined
|
|
@@ -80,6 +85,29 @@ const _FIREBASE_HOSTING_LEAD_RE = /^Firebase\s+Hosting\b/im;
|
|
|
80
85
|
const _DEPLOY_READY_RE =
|
|
81
86
|
/\b(?:deploy|deployment|preview)\s+(?:ready|available|succeeded|complete|completed)\b/i;
|
|
82
87
|
|
|
88
|
+
const NON_ACTIONABLE_AUTOMATION_REASONS = Object.freeze({
|
|
89
|
+
PREVIEW_STATUS: 'preview-status',
|
|
90
|
+
GITHUB_VISUAL_EVIDENCE: 'github-visual-evidence',
|
|
91
|
+
ADO_PR_ASSISTANT_STATUS: 'ado-pr-assistant-status',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// GitHub's global Actions bot identity. All fields are API-controlled.
|
|
95
|
+
const _GITHUB_ACTIONS_BOT = Object.freeze({
|
|
96
|
+
login: 'github-actions[bot]',
|
|
97
|
+
type: 'bot',
|
|
98
|
+
id: 41898282,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const _VISUAL_EVIDENCE_ENVELOPE_RE =
|
|
102
|
+
/^<!-- minions:visual-evidence -->\r?\n<!-- visual-evidence-head:[0-9a-f]{40} -->\r?\n<!-- visual-evidence-run:[1-9]\d* -->\r?\n\r?\n### (?:Visual evidence capture failed|Visual evidence is stale|Visual evidence)(?:\r?\n|$)/;
|
|
103
|
+
|
|
104
|
+
const _ADO_GITOPS_AUTHOR = 'gitops (git lowpriv)';
|
|
105
|
+
const _ADO_PR_ASSISTANT_HEADER =
|
|
106
|
+
`<span style="font-family:'Segoe UI',SegoeUI,Segoe,'Helvetica Neue',Arial,sans-serif; font-size:14px; font-weight:600;">PR Assistant</span>`;
|
|
107
|
+
const _ADO_PR_ASSISTANT_STATUS = 'Pull request description has been updated with summary.';
|
|
108
|
+
const _ADO_CONFIG_LEVEL_PREFIX = '<small class="secondary-text">Config Level:';
|
|
109
|
+
const _ADO_GITOPS_USER_AGENT = '<!-- GitOpsUserAgent=GitOps.Apps.Server.pullrequestcopilot -->';
|
|
110
|
+
|
|
83
111
|
function isPreviewStatusBody(body) {
|
|
84
112
|
const text = String(body || '');
|
|
85
113
|
if (!text) return false;
|
|
@@ -94,6 +122,41 @@ function isPreviewStatusBody(body) {
|
|
|
94
122
|
return false;
|
|
95
123
|
}
|
|
96
124
|
|
|
125
|
+
function _isGitHubVisualEvidenceStatus(body, author) {
|
|
126
|
+
if (String(author?.login || '').toLowerCase() !== _GITHUB_ACTIONS_BOT.login) return false;
|
|
127
|
+
if (String(author?.type || '').toLowerCase() !== _GITHUB_ACTIONS_BOT.type) return false;
|
|
128
|
+
if (Number(author?.id) !== _GITHUB_ACTIONS_BOT.id) return false;
|
|
129
|
+
return _VISUAL_EVIDENCE_ENVELOPE_RE.test(String(body || ''));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function _isAdoPrAssistantStatus(body, author) {
|
|
133
|
+
if (String(author?.displayName || '').trim().toLowerCase() !== _ADO_GITOPS_AUTHOR) return false;
|
|
134
|
+
|
|
135
|
+
const text = String(body || '');
|
|
136
|
+
if (!text.startsWith(_ADO_PR_ASSISTANT_HEADER)) return false;
|
|
137
|
+
if (!text.trimEnd().endsWith(_ADO_GITOPS_USER_AGENT)) return false;
|
|
138
|
+
|
|
139
|
+
const afterHeader = text.slice(_ADO_PR_ASSISTANT_HEADER.length);
|
|
140
|
+
const configIndex = afterHeader.indexOf(_ADO_CONFIG_LEVEL_PREFIX);
|
|
141
|
+
if (configIndex === -1) return false;
|
|
142
|
+
return afterHeader.slice(0, configIndex).trim() === _ADO_PR_ASSISTANT_STATUS;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function classifyNonActionableAutomationComment({ host, body, author } = {}) {
|
|
146
|
+
if (isPreviewStatusBody(body)) {
|
|
147
|
+
return NON_ACTIONABLE_AUTOMATION_REASONS.PREVIEW_STATUS;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const normalizedHost = String(host || '').toLowerCase();
|
|
151
|
+
if (normalizedHost === 'github' && _isGitHubVisualEvidenceStatus(body, author)) {
|
|
152
|
+
return NON_ACTIONABLE_AUTOMATION_REASONS.GITHUB_VISUAL_EVIDENCE;
|
|
153
|
+
}
|
|
154
|
+
if (normalizedHost === 'ado' && _isAdoPrAssistantStatus(body, author)) {
|
|
155
|
+
return NON_ACTIONABLE_AUTOMATION_REASONS.ADO_PR_ASSISTANT_STATUS;
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
97
160
|
// ── Structural Minions marker ──────────────────────────────────────────────
|
|
98
161
|
//
|
|
99
162
|
// MINIONS_COMMENT_MARKER_RE uses the /m flag so its `^` anchor matches the
|
|
@@ -151,4 +214,6 @@ module.exports = {
|
|
|
151
214
|
isPreviewStatusBody,
|
|
152
215
|
hasMinionsMarker,
|
|
153
216
|
hasVerdictPrefix,
|
|
217
|
+
classifyNonActionableAutomationComment,
|
|
218
|
+
NON_ACTIONABLE_AUTOMATION_REASONS,
|
|
154
219
|
};
|
package/engine/consolidation.js
CHANGED
|
@@ -15,6 +15,8 @@ const queries = require('./queries');
|
|
|
15
15
|
const { getInboxFiles, getNotes, INBOX_DIR, ENGINE_DIR,
|
|
16
16
|
NOTES_PATH, KNOWLEDGE_DIR, ARCHIVE_DIR } = queries;
|
|
17
17
|
const { wrapUntrusted, buildSource } = require('./untrusted-fence');
|
|
18
|
+
const reviewLearning = require('./review-learning');
|
|
19
|
+
const memoryStore = require('./memory-store');
|
|
18
20
|
|
|
19
21
|
// notes.md size cap. Consolidation appends to notes.md every cycle; without a
|
|
20
22
|
// cap it grows unbounded.
|
|
@@ -836,13 +838,114 @@ let _consolidationStartedAt = 0;
|
|
|
836
838
|
let _forceResetTimeout = null; // force-reset handle; cancelled by _clearProcessingState
|
|
837
839
|
const _processingFiles = new Set(); // files currently being consolidated (race guard)
|
|
838
840
|
|
|
841
|
+
const LEGACY_REVIEW_FEEDBACK_FILE_RE =
|
|
842
|
+
/^feedback-.+-from-.+-\d{4}-\d{2}-\d{2}\.md$/;
|
|
843
|
+
|
|
844
|
+
function _flatFrontmatter(content) {
|
|
845
|
+
const match = String(content || '').match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
846
|
+
if (!match) return {};
|
|
847
|
+
const fields = {};
|
|
848
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
849
|
+
const field = line.match(/^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/);
|
|
850
|
+
if (field) fields[field[1].toLowerCase()] = field[2].trim();
|
|
851
|
+
}
|
|
852
|
+
return fields;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function _legacyReviewProject(config, projectName, prId) {
|
|
856
|
+
const projects = shared.getProjects(config);
|
|
857
|
+
const named = projects.find(project => String(project?.name || '').toLowerCase() === projectName);
|
|
858
|
+
if (named) return named;
|
|
859
|
+
const prScope = String(prId || '').replace(/#[1-9]\d*$/, '').toLowerCase();
|
|
860
|
+
return projects.find(project => String(shared.getProjectPrScope(project) || '').toLowerCase() === prScope) || null;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function _captureLegacyReviewFeedback(item, config) {
|
|
864
|
+
const fields = _flatFrontmatter(item.content);
|
|
865
|
+
const author = String(fields.author || '').toLowerCase();
|
|
866
|
+
const reviewer = String(fields.reviewer || '').toLowerCase();
|
|
867
|
+
if (fields.agent && String(fields.agent).toLowerCase() !== 'feedback') {
|
|
868
|
+
throw new TypeError(`legacy review feedback ${item.name} has non-feedback provenance`);
|
|
869
|
+
}
|
|
870
|
+
if (!/^[a-z][a-z0-9-]{1,63}$/.test(author) || !/^[a-z][a-z0-9-]{1,63}$/.test(reviewer)
|
|
871
|
+
|| !item.name.startsWith(`feedback-${author}-from-${reviewer}-`)) {
|
|
872
|
+
throw new TypeError(`legacy review feedback ${item.name} author/reviewer does not match its filename`);
|
|
873
|
+
}
|
|
874
|
+
const prId = reviewLearning.normalizeCanonicalPrId(fields.sourcepr);
|
|
875
|
+
const project = _legacyReviewProject(config, String(fields.project || '').toLowerCase(), prId);
|
|
876
|
+
const projectName = project?.name || fields.project;
|
|
877
|
+
if (!projectName) throw new TypeError(`legacy review feedback ${item.name} has no project scope`);
|
|
878
|
+
const host = prId.slice(0, prId.indexOf(':'));
|
|
879
|
+
const sourceHash = reviewLearning.hashReviewCommentContent(item.content);
|
|
880
|
+
const sourceIdentity = crypto.createHash('sha256').update(item.name).digest('hex').slice(0, 24);
|
|
881
|
+
const findingKey = reviewLearning.buildFindingKey({
|
|
882
|
+
project: projectName,
|
|
883
|
+
prId,
|
|
884
|
+
sourceKey: `${host}:legacy-feedback:${sourceIdentity}`,
|
|
885
|
+
});
|
|
886
|
+
const candidate = reviewLearning.upsertReviewLearningCandidate({
|
|
887
|
+
findingKey,
|
|
888
|
+
project: projectName,
|
|
889
|
+
prId,
|
|
890
|
+
authorAgent: author,
|
|
891
|
+
reviewerAgent: reviewer,
|
|
892
|
+
sourceHash,
|
|
893
|
+
workItemId: fields.sourceitem || null,
|
|
894
|
+
dispatchId: fields.dispatchid || null,
|
|
895
|
+
metadata: {
|
|
896
|
+
legacyUnverified: true,
|
|
897
|
+
legacySourceRef: item.name,
|
|
898
|
+
backfilledBy: 'inbox-consolidation',
|
|
899
|
+
},
|
|
900
|
+
});
|
|
901
|
+
memoryStore.recordMemoryLifecycleEvent({
|
|
902
|
+
eventType: 'candidate-created',
|
|
903
|
+
memoryId: candidate.id,
|
|
904
|
+
findingKey,
|
|
905
|
+
project: projectName,
|
|
906
|
+
agent: author,
|
|
907
|
+
workItemId: fields.sourceitem || null,
|
|
908
|
+
dispatchId: fields.dispatchid || null,
|
|
909
|
+
metadata: {
|
|
910
|
+
legacyUnverified: true,
|
|
911
|
+
legacySourceRef: item.name,
|
|
912
|
+
sourceHash,
|
|
913
|
+
},
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function divertLegacyReviewFeedback(items, config) {
|
|
918
|
+
const remaining = [];
|
|
919
|
+
const diverted = [];
|
|
920
|
+
for (const item of Array.isArray(items) ? items : []) {
|
|
921
|
+
if (!LEGACY_REVIEW_FEEDBACK_FILE_RE.test(String(item?.name || ''))) {
|
|
922
|
+
remaining.push(item);
|
|
923
|
+
continue;
|
|
924
|
+
}
|
|
925
|
+
try {
|
|
926
|
+
_captureLegacyReviewFeedback(item, config);
|
|
927
|
+
} catch (err) {
|
|
928
|
+
if (!(err instanceof TypeError)) {
|
|
929
|
+
log('warn', `Legacy review feedback backfill deferred for ${item.name}: ${err.message}`);
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
log('warn', `Legacy review feedback has invalid provenance and will be archived (${item.name}): ${err.message}`);
|
|
933
|
+
}
|
|
934
|
+
diverted.push(item.name);
|
|
935
|
+
}
|
|
936
|
+
if (diverted.length > 0) {
|
|
937
|
+
archiveInboxFiles(diverted);
|
|
938
|
+
log('info', `Diverted ${diverted.length} legacy review feedback file(s) from broad memory`);
|
|
939
|
+
}
|
|
940
|
+
return remaining;
|
|
941
|
+
}
|
|
942
|
+
|
|
839
943
|
function consolidateInbox(config) {
|
|
840
944
|
|
|
841
945
|
const { ENGINE_DEFAULTS } = shared;
|
|
842
946
|
const threshold = config.engine?.inboxConsolidateThreshold || ENGINE_DEFAULTS.inboxConsolidateThreshold;
|
|
843
947
|
const pinnedInboxKeys = new Set(shared.getPinnedItems().filter(k => k.startsWith('notes/inbox/')));
|
|
844
|
-
const
|
|
845
|
-
if (files.length < threshold) return;
|
|
948
|
+
const candidateFiles = getInboxFiles().filter(f => !_processingFiles.has(f) && !pinnedInboxKeys.has('notes/inbox/' + f));
|
|
846
949
|
// Auto-reset stale flag if consolidation has been running for >5 minutes (process died without cleanup)
|
|
847
950
|
if (_consolidationInFlight && (Date.now() - _consolidationStartedAt) > 300000) {
|
|
848
951
|
log('warn', 'Consolidation flag was stale (>5m) — resetting');
|
|
@@ -851,12 +954,15 @@ function consolidateInbox(config) {
|
|
|
851
954
|
}
|
|
852
955
|
if (_consolidationInFlight) return;
|
|
853
956
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
const items = files.map(f => ({
|
|
957
|
+
const candidateItems = candidateFiles.map(f => ({
|
|
857
958
|
name: f,
|
|
858
959
|
content: safeRead(path.join(INBOX_DIR, f)) || ''
|
|
859
960
|
}));
|
|
961
|
+
const items = divertLegacyReviewFeedback(candidateItems, config);
|
|
962
|
+
const files = items.map(item => item.name);
|
|
963
|
+
if (files.length < threshold) return;
|
|
964
|
+
|
|
965
|
+
log('info', `Consolidating ${files.length} inbox items into notes.md`);
|
|
860
966
|
|
|
861
967
|
const existingNotes = getNotes() || '';
|
|
862
968
|
consolidateWithLLM(items, existingNotes, files, config);
|
|
@@ -942,6 +1048,11 @@ Use today's date: ${dateStamp()}`;
|
|
|
942
1048
|
|
|
943
1049
|
function consolidateWithLLM(items, existingNotes, files, config) {
|
|
944
1050
|
|
|
1051
|
+
items = divertLegacyReviewFeedback(items, config);
|
|
1052
|
+
const allowedFiles = new Set(items.map(item => item.name));
|
|
1053
|
+
files = files.filter(file => allowedFiles.has(file));
|
|
1054
|
+
if (files.length === 0) return;
|
|
1055
|
+
|
|
945
1056
|
// Cancel any stale force-reset from a prior run before starting fresh
|
|
946
1057
|
clearTimeout(_forceResetTimeout);
|
|
947
1058
|
_forceResetTimeout = null;
|
|
@@ -1103,6 +1214,11 @@ function consolidateWithLLM(items, existingNotes, files, config) {
|
|
|
1103
1214
|
|
|
1104
1215
|
function consolidateWithRegex(items, files, config) {
|
|
1105
1216
|
|
|
1217
|
+
items = divertLegacyReviewFeedback(items, config);
|
|
1218
|
+
const allowedFiles = new Set(items.map(item => item.name));
|
|
1219
|
+
files = files.filter(file => allowedFiles.has(file));
|
|
1220
|
+
if (files.length === 0) return;
|
|
1221
|
+
|
|
1106
1222
|
const allInsights = [];
|
|
1107
1223
|
for (const item of items) {
|
|
1108
1224
|
const content = item.content || '';
|
|
@@ -1327,6 +1443,7 @@ function buildCondensedKbBody(content, titleLine, archiveRelPath) {
|
|
|
1327
1443
|
|
|
1328
1444
|
async function classifyToKnowledgeBase(items, config) {
|
|
1329
1445
|
|
|
1446
|
+
items = divertLegacyReviewFeedback(items, config);
|
|
1330
1447
|
if (!fs.existsSync(KNOWLEDGE_DIR)) fs.mkdirSync(KNOWLEDGE_DIR, { recursive: true });
|
|
1331
1448
|
|
|
1332
1449
|
const knownAgents = config && config.agents
|
|
@@ -1571,6 +1688,7 @@ module.exports = {
|
|
|
1571
1688
|
NOTES_MAX_BYTES,
|
|
1572
1689
|
hasReusableSignal,
|
|
1573
1690
|
buildCondensedKbBody,
|
|
1691
|
+
divertLegacyReviewFeedback, // exported for testing
|
|
1574
1692
|
_alertContentHash,
|
|
1575
1693
|
_alertHashExists,
|
|
1576
1694
|
_writeNotesOrDefer,
|