ralph-hero-mcp-server 2.5.117 → 2.5.119
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/dist/lib/dashboard.js +14 -13
- package/dist/lib/directions.js +5 -11
- package/dist/lib/hygiene.js +12 -11
- package/dist/lib/metrics.js +4 -3
- package/dist/lib/thresholds.js +44 -0
- package/dist/tools/dashboard-tools.js +9 -9
- package/dist/tools/directions-tools.js +16 -12
- package/dist/tools/hygiene-tools.js +7 -7
- package/dist/tools/trends-tools.js +1 -1
- package/package.json +1 -1
package/dist/lib/dashboard.js
CHANGED
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
* I/O (GraphQL fetching) lives in tools/dashboard-tools.ts.
|
|
6
6
|
*/
|
|
7
7
|
import { STATE_ORDER, LOCK_STATES, TERMINAL_STATES, HUMAN_STATES, } from "./workflow-states.js";
|
|
8
|
+
import { ARCHIVE_AGE_DAYS, CRITICAL_STUCK_HOURS, RECENT_WINDOW_DAYS, STUCK_THRESHOLD_HOURS, } from "./thresholds.js";
|
|
8
9
|
export const DEFAULT_HEALTH_CONFIG = {
|
|
9
|
-
stuckThresholdHours:
|
|
10
|
-
criticalStuckHours:
|
|
10
|
+
stuckThresholdHours: STUCK_THRESHOLD_HOURS,
|
|
11
|
+
criticalStuckHours: CRITICAL_STUCK_HOURS,
|
|
11
12
|
wipLimits: {},
|
|
12
|
-
doneWindowDays:
|
|
13
|
-
|
|
13
|
+
doneWindowDays: RECENT_WINDOW_DAYS,
|
|
14
|
+
archiveAgeDays: ARCHIVE_AGE_DAYS,
|
|
14
15
|
};
|
|
15
16
|
// ---------------------------------------------------------------------------
|
|
16
17
|
// Priority helpers
|
|
@@ -282,13 +283,13 @@ const DAY_MS = 24 * 60 * 60 * 1000;
|
|
|
282
283
|
/**
|
|
283
284
|
* Compute archive eligibility stats from project items.
|
|
284
285
|
*
|
|
285
|
-
* - "Eligible for archive": Done/Canceled items stale beyond
|
|
286
|
+
* - "Eligible for archive": Done/Canceled items stale beyond archiveAgeDays
|
|
286
287
|
* - "Recently completed": Done/Canceled items within doneWindowDays
|
|
287
288
|
* - Staleness computed from closedAt (preferred) or updatedAt (fallback)
|
|
288
289
|
* - Zero additional API calls — works on already-fetched items.
|
|
289
290
|
*/
|
|
290
|
-
export function computeArchiveStats(items, now,
|
|
291
|
-
const thresholdMs =
|
|
291
|
+
export function computeArchiveStats(items, now, archiveAgeDays, doneWindowDays) {
|
|
292
|
+
const thresholdMs = archiveAgeDays * DAY_MS;
|
|
292
293
|
const recentMs = doneWindowDays * DAY_MS;
|
|
293
294
|
const eligible = [];
|
|
294
295
|
let recentlyCompleted = 0;
|
|
@@ -317,7 +318,7 @@ export function computeArchiveStats(items, now, archiveThresholdDays, doneWindow
|
|
|
317
318
|
eligibleForArchive: eligible.length,
|
|
318
319
|
eligibleItems: eligible,
|
|
319
320
|
recentlyCompleted,
|
|
320
|
-
|
|
321
|
+
archiveAgeDays,
|
|
321
322
|
};
|
|
322
323
|
}
|
|
323
324
|
// ---------------------------------------------------------------------------
|
|
@@ -437,7 +438,7 @@ export function buildIterationSection(items) {
|
|
|
437
438
|
export function buildDashboard(items, config = DEFAULT_HEALTH_CONFIG, now = Date.now(), streams) {
|
|
438
439
|
const phases = aggregateByPhase(items, now, config);
|
|
439
440
|
const warnings = detectHealthIssues(phases, config);
|
|
440
|
-
const archive = computeArchiveStats(items, now, config.
|
|
441
|
+
const archive = computeArchiveStats(items, now, config.archiveAgeDays, config.doneWindowDays);
|
|
441
442
|
// Per-project breakdown (only when items span multiple projects)
|
|
442
443
|
const projectGroups = new Map();
|
|
443
444
|
for (const item of items) {
|
|
@@ -512,7 +513,7 @@ export function buildDashboard(items, config = DEFAULT_HEALTH_CONFIG, now = Date
|
|
|
512
513
|
const iterationSection = buildIterationSection(items);
|
|
513
514
|
return {
|
|
514
515
|
generatedAt: new Date(now).toISOString(),
|
|
515
|
-
|
|
516
|
+
boardItems: items.length,
|
|
516
517
|
phases,
|
|
517
518
|
health: {
|
|
518
519
|
ok: warnings.length === 0,
|
|
@@ -536,7 +537,7 @@ export function formatMarkdown(data, issuesPerPhase = 10) {
|
|
|
536
537
|
lines.push(`# Pipeline Status`);
|
|
537
538
|
lines.push(`_Generated: ${data.generatedAt}_`);
|
|
538
539
|
lines.push("");
|
|
539
|
-
lines.push(`**
|
|
540
|
+
lines.push(`**Board items**: ${data.boardItems}`);
|
|
540
541
|
lines.push("");
|
|
541
542
|
// Phase table
|
|
542
543
|
lines.push("| Phase | Count | Points | Issues |");
|
|
@@ -580,7 +581,7 @@ export function formatMarkdown(data, issuesPerPhase = 10) {
|
|
|
580
581
|
lines.push("");
|
|
581
582
|
lines.push("## Archive Eligibility");
|
|
582
583
|
lines.push("");
|
|
583
|
-
lines.push(`**Eligible for archive**: ${data.archive.eligibleForArchive} items (stale > ${data.archive.
|
|
584
|
+
lines.push(`**Eligible for archive**: ${data.archive.eligibleForArchive} items (stale > ${data.archive.archiveAgeDays} days in Done/Canceled)`);
|
|
584
585
|
lines.push(`**Recently completed**: ${data.archive.recentlyCompleted} items`);
|
|
585
586
|
if (data.archive.eligibleItems.length > 0) {
|
|
586
587
|
lines.push("");
|
|
@@ -755,7 +756,7 @@ export function formatAscii(data) {
|
|
|
755
756
|
}
|
|
756
757
|
// Archive summary
|
|
757
758
|
if (data.archive) {
|
|
758
|
-
lines.push(`Archive: ${data.archive.eligibleForArchive} eligible (threshold: ${data.archive.
|
|
759
|
+
lines.push(`Archive: ${data.archive.eligibleForArchive} eligible (threshold: ${data.archive.archiveAgeDays}d), ${data.archive.recentlyCompleted} recent`);
|
|
759
760
|
}
|
|
760
761
|
// Per-project breakdown (only for multi-project)
|
|
761
762
|
if (data.projectBreakdowns &&
|
package/dist/lib/directions.js
CHANGED
|
@@ -28,12 +28,13 @@
|
|
|
28
28
|
* PRs are scored separately and only merged into the final ranking.
|
|
29
29
|
*/
|
|
30
30
|
import { LOCK_STATES, STATE_ORDER } from "./workflow-states.js";
|
|
31
|
+
import { AGENT_BACKLOG_FALLBACK_PENALTY, LOCK_STALE_HOURS, PR_STALE_HOURS, RECENT_WINDOW_DAYS, STUCK_THRESHOLD_HOURS, } from "./thresholds.js";
|
|
31
32
|
export const DEFAULT_RANK_CONFIG = {
|
|
32
33
|
limit: 3,
|
|
33
|
-
stuckThresholdHours:
|
|
34
|
-
lockStaleHours:
|
|
35
|
-
treeRecentDoneDays:
|
|
36
|
-
prStaleHours:
|
|
34
|
+
stuckThresholdHours: STUCK_THRESHOLD_HOURS,
|
|
35
|
+
lockStaleHours: LOCK_STALE_HOURS,
|
|
36
|
+
treeRecentDoneDays: RECENT_WINDOW_DAYS,
|
|
37
|
+
prStaleHours: PR_STALE_HOURS,
|
|
37
38
|
audience: "human",
|
|
38
39
|
};
|
|
39
40
|
// ---------------------------------------------------------------------------
|
|
@@ -68,13 +69,6 @@ const PR_REVIEW_REQUIRED_BOOST = -200;
|
|
|
68
69
|
* waiting for the human's attention.
|
|
69
70
|
*/
|
|
70
71
|
const HUMAN_NEEDED_UNBLOCK_BOOST = -150;
|
|
71
|
-
/**
|
|
72
|
-
* Penalty applied to Backlog / null-state items that surface only via the
|
|
73
|
-
* `audience === "agent"` fallback. Large positive value ensures Backlog
|
|
74
|
-
* fallback items always rank below any actionable-phase item — Backlog
|
|
75
|
-
* surfaces only when the actionable pool is empty.
|
|
76
|
-
*/
|
|
77
|
-
const AGENT_BACKLOG_FALLBACK_PENALTY = 100;
|
|
78
72
|
/**
|
|
79
73
|
* Per-estimate penalty applied when audience === "agent". Larger items
|
|
80
74
|
* cost more (positive score), pushing them down the ranking so agent
|
package/dist/lib/hygiene.js
CHANGED
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { TERMINAL_STATES } from "./workflow-states.js";
|
|
8
8
|
import { groupDashboardItemsByRepo } from "./dashboard.js";
|
|
9
|
+
import { ARCHIVE_AGE_DAYS, ORPHAN_AGE_DAYS, RECENT_WINDOW_DAYS, SIMILARITY_THRESHOLD, } from "./thresholds.js";
|
|
9
10
|
export const DEFAULT_HYGIENE_CONFIG = {
|
|
10
|
-
|
|
11
|
-
staleDays:
|
|
12
|
-
orphanDays:
|
|
11
|
+
archiveAgeDays: ARCHIVE_AGE_DAYS,
|
|
12
|
+
staleDays: RECENT_WINDOW_DAYS,
|
|
13
|
+
orphanDays: ORPHAN_AGE_DAYS,
|
|
13
14
|
wipLimits: {},
|
|
14
|
-
similarityThreshold:
|
|
15
|
+
similarityThreshold: SIMILARITY_THRESHOLD,
|
|
15
16
|
};
|
|
16
17
|
// ---------------------------------------------------------------------------
|
|
17
18
|
// Helpers
|
|
@@ -32,9 +33,9 @@ function toHygieneItem(item, now) {
|
|
|
32
33
|
// Section functions
|
|
33
34
|
// ---------------------------------------------------------------------------
|
|
34
35
|
/**
|
|
35
|
-
* Items in terminal states (Done/Canceled) older than
|
|
36
|
+
* Items in terminal states (Done/Canceled) older than archiveAgeDays.
|
|
36
37
|
*/
|
|
37
|
-
export function findArchiveCandidates(items, now,
|
|
38
|
+
export function findArchiveCandidates(items, now, archiveAgeDays) {
|
|
38
39
|
return items
|
|
39
40
|
.filter((item) => {
|
|
40
41
|
if (item.subIssueCount > 0)
|
|
@@ -43,7 +44,7 @@ export function findArchiveCandidates(items, now, archiveDays) {
|
|
|
43
44
|
if (!ws || !TERMINAL_STATES.includes(ws))
|
|
44
45
|
return false;
|
|
45
46
|
const ts = item.closedAt ?? item.updatedAt;
|
|
46
|
-
return ageDays(ts, now) >
|
|
47
|
+
return ageDays(ts, now) > archiveAgeDays;
|
|
47
48
|
})
|
|
48
49
|
.map((item) => toHygieneItem(item, now));
|
|
49
50
|
}
|
|
@@ -199,7 +200,7 @@ export function findDuplicateCandidates(items, now, threshold) {
|
|
|
199
200
|
* Does NOT recursively emit `repoBreakdowns` — per-repo breakdowns are flat.
|
|
200
201
|
*/
|
|
201
202
|
function buildRepoBreakdown(repoName, items, config, now) {
|
|
202
|
-
const archiveCandidates = findArchiveCandidates(items, now, config.
|
|
203
|
+
const archiveCandidates = findArchiveCandidates(items, now, config.archiveAgeDays);
|
|
203
204
|
const staleItems = findStaleItems(items, now, config.staleDays);
|
|
204
205
|
const orphanedItems = findOrphanedItems(items, now, config.orphanDays);
|
|
205
206
|
const fieldGaps = findFieldGaps(items, now);
|
|
@@ -235,7 +236,7 @@ function buildRepoBreakdown(repoName, items, config, now) {
|
|
|
235
236
|
* Build a complete hygiene report from project items.
|
|
236
237
|
*/
|
|
237
238
|
export function buildHygieneReport(items, config = DEFAULT_HYGIENE_CONFIG, now = Date.now()) {
|
|
238
|
-
const archiveCandidates = findArchiveCandidates(items, now, config.
|
|
239
|
+
const archiveCandidates = findArchiveCandidates(items, now, config.archiveAgeDays);
|
|
239
240
|
const staleItems = findStaleItems(items, now, config.staleDays);
|
|
240
241
|
const orphanedItems = findOrphanedItems(items, now, config.orphanDays);
|
|
241
242
|
const fieldGaps = findFieldGaps(items, now);
|
|
@@ -262,7 +263,7 @@ export function buildHygieneReport(items, config = DEFAULT_HYGIENE_CONFIG, now =
|
|
|
262
263
|
}
|
|
263
264
|
return {
|
|
264
265
|
generatedAt: new Date(now).toISOString(),
|
|
265
|
-
|
|
266
|
+
boardItems: items.length,
|
|
266
267
|
archiveCandidates,
|
|
267
268
|
staleItems,
|
|
268
269
|
orphanedItems,
|
|
@@ -294,7 +295,7 @@ export function formatHygieneMarkdown(report) {
|
|
|
294
295
|
lines.push("# Project Hygiene Report");
|
|
295
296
|
lines.push(`_Generated: ${report.generatedAt}_`);
|
|
296
297
|
lines.push("");
|
|
297
|
-
lines.push(`**
|
|
298
|
+
lines.push(`**Board items**: ${report.boardItems}`);
|
|
298
299
|
lines.push("");
|
|
299
300
|
// Summary
|
|
300
301
|
lines.push("## Summary");
|
package/dist/lib/metrics.js
CHANGED
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
* All functions are side-effect-free: dashboard data in, metrics out.
|
|
5
5
|
* Designed to complement lib/dashboard.ts without modifying it.
|
|
6
6
|
*/
|
|
7
|
+
import { AT_RISK_THRESHOLD, OFF_TRACK_THRESHOLD, RECENT_WINDOW_DAYS, } from "./thresholds.js";
|
|
7
8
|
export const DEFAULT_METRICS_CONFIG = {
|
|
8
|
-
velocityWindowDays:
|
|
9
|
-
atRiskThreshold:
|
|
10
|
-
offTrackThreshold:
|
|
9
|
+
velocityWindowDays: RECENT_WINDOW_DAYS,
|
|
10
|
+
atRiskThreshold: AT_RISK_THRESHOLD,
|
|
11
|
+
offTrackThreshold: OFF_TRACK_THRESHOLD,
|
|
11
12
|
severityWeights: { critical: 3, warning: 1, info: 0 },
|
|
12
13
|
};
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared threshold defaults across discovery tools.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for every "magic number" that gates the
|
|
5
|
+
* discovery surface (next_actions, pipeline_dashboard, project_hygiene,
|
|
6
|
+
* metrics_trends). Per-module CONFIG objects (DEFAULT_RANK_CONFIG,
|
|
7
|
+
* DEFAULT_HEALTH_CONFIG, DEFAULT_HYGIENE_CONFIG, DEFAULT_METRICS_CONFIG)
|
|
8
|
+
* pull from this module so changing a value in one place propagates
|
|
9
|
+
* everywhere it's referenced.
|
|
10
|
+
*
|
|
11
|
+
* Where the same value appears under multiple names (RECENT_WINDOW_DAYS),
|
|
12
|
+
* the names describe distinct concepts but the value is shared so changing
|
|
13
|
+
* one changes all related places.
|
|
14
|
+
*/
|
|
15
|
+
// Lock-state staleness — short, hours-based because lock collisions are urgent.
|
|
16
|
+
export const LOCK_STALE_HOURS = 24;
|
|
17
|
+
// PR staleness — short, hours-based because review timeliness matters.
|
|
18
|
+
export const PR_STALE_HOURS = 24;
|
|
19
|
+
// Non-lock stuck threshold — longer, hours-based for warnings.
|
|
20
|
+
export const STUCK_THRESHOLD_HOURS = 48;
|
|
21
|
+
export const CRITICAL_STUCK_HOURS = STUCK_THRESHOLD_HOURS * 2;
|
|
22
|
+
// Recent activity window — single shared value for "recent enough to be relevant."
|
|
23
|
+
// Used by:
|
|
24
|
+
// - hygiene.staleDays → "non-terminal item hasn't moved in N days"
|
|
25
|
+
// - dashboard.doneWindowDays → "show recent completions"
|
|
26
|
+
// - directions.treeRecentDoneDays → "sibling done within window"
|
|
27
|
+
// - metrics.velocityWindowDays → "completion window for velocity calc"
|
|
28
|
+
export const RECENT_WINDOW_DAYS = 7;
|
|
29
|
+
// Archive age — unified replacement for the old archiveThresholdDays
|
|
30
|
+
// (dashboard) + archiveDays (hygiene) duplication. Both previously
|
|
31
|
+
// defaulted to 14 and described the same concept (Done/Canceled item age
|
|
32
|
+
// before archive eligibility).
|
|
33
|
+
export const ARCHIVE_AGE_DAYS = 14;
|
|
34
|
+
// Backlog assignment-gap — separate concept from archive age.
|
|
35
|
+
export const ORPHAN_AGE_DAYS = 14;
|
|
36
|
+
// Risk score classifications.
|
|
37
|
+
export const AT_RISK_THRESHOLD = 2;
|
|
38
|
+
export const OFF_TRACK_THRESHOLD = 6;
|
|
39
|
+
// Duplicate detection similarity (0-1).
|
|
40
|
+
export const SIMILARITY_THRESHOLD = 0.8;
|
|
41
|
+
// Phase 1 fallback penalty — keeps Backlog items below actionable items
|
|
42
|
+
// when the audience="agent" fallback fires.
|
|
43
|
+
export const AGENT_BACKLOG_FALLBACK_PENALTY = 100;
|
|
44
|
+
//# sourceMappingURL=thresholds.js.map
|
|
@@ -20,7 +20,7 @@ export { DASHBOARD_ITEMS_QUERY, toDashboardItems };
|
|
|
20
20
|
// Register dashboard tools
|
|
21
21
|
// ---------------------------------------------------------------------------
|
|
22
22
|
export function registerDashboardTools(server, client, fieldCache) {
|
|
23
|
-
server.tool("ralph_hero__pipeline_dashboard", "Generate pipeline status dashboard with issue counts per workflow phase, health indicators, and formatted output. Returns structured data with optional markdown or ASCII rendering.", {
|
|
23
|
+
server.tool("ralph_hero__pipeline_dashboard", "Generate pipeline status dashboard with issue counts per workflow phase, health indicators, and formatted output. Returns structured data with optional markdown or ASCII rendering. Top-level `boardItems` is the raw count of all project items including PRs (uniform across discovery tools — next_actions, pipeline_dashboard, project_hygiene). Per-phase `count` values reflect that phase's bucket; for `Done` and `Canceled`, the count is bounded by `doneWindowDays` (default 7) and may be smaller than the actual phase membership. Per-iteration `totalIssues` is a distinct concept (iteration-scoped count).", {
|
|
24
24
|
owner: z
|
|
25
25
|
.string()
|
|
26
26
|
.optional()
|
|
@@ -43,7 +43,7 @@ export function registerDashboardTools(server, client, fieldCache) {
|
|
|
43
43
|
.number()
|
|
44
44
|
.optional()
|
|
45
45
|
.default(48)
|
|
46
|
-
.describe("Hours before flagging stuck issues (default: 48)"),
|
|
46
|
+
.describe("Hours before flagging stuck issues (default: 48, unit: hours). Shared with next_actions.stuckThresholdHours — both pull from STUCK_THRESHOLD_HOURS in src/lib/thresholds.ts."),
|
|
47
47
|
wipLimits: z
|
|
48
48
|
.record(z.coerce.number())
|
|
49
49
|
.optional()
|
|
@@ -52,7 +52,7 @@ export function registerDashboardTools(server, client, fieldCache) {
|
|
|
52
52
|
.number()
|
|
53
53
|
.optional()
|
|
54
54
|
.default(7)
|
|
55
|
-
.describe("Only show Done issues from last N days (default: 7)"),
|
|
55
|
+
.describe("Only show Done issues from last N days (default: 7, unit: days). Shares the RECENT_WINDOW_DAYS value with hygiene.staleDays, next_actions.treeRecentDoneDays, and metrics.velocityWindowDays."),
|
|
56
56
|
issuesPerPhase: z
|
|
57
57
|
.number()
|
|
58
58
|
.optional()
|
|
@@ -67,22 +67,22 @@ export function registerDashboardTools(server, client, fieldCache) {
|
|
|
67
67
|
.number()
|
|
68
68
|
.optional()
|
|
69
69
|
.default(7)
|
|
70
|
-
.describe("Days to look back for velocity calculation (default: 7)"),
|
|
70
|
+
.describe("Days to look back for velocity calculation (default: 7, unit: days). Shares the RECENT_WINDOW_DAYS value with hygiene.staleDays, dashboard.doneWindowDays, and next_actions.treeRecentDoneDays."),
|
|
71
71
|
atRiskThreshold: z
|
|
72
72
|
.number()
|
|
73
73
|
.optional()
|
|
74
74
|
.default(2)
|
|
75
|
-
.describe("Risk score threshold for AT_RISK status (default: 2)"),
|
|
75
|
+
.describe("Risk score threshold for AT_RISK status (default: 2, unit: count). Pulls from AT_RISK_THRESHOLD in src/lib/thresholds.ts."),
|
|
76
76
|
offTrackThreshold: z
|
|
77
77
|
.number()
|
|
78
78
|
.optional()
|
|
79
79
|
.default(6)
|
|
80
|
-
.describe("Risk score threshold for OFF_TRACK status (default: 6)"),
|
|
81
|
-
|
|
80
|
+
.describe("Risk score threshold for OFF_TRACK status (default: 6, unit: count). Pulls from OFF_TRACK_THRESHOLD in src/lib/thresholds.ts."),
|
|
81
|
+
archiveAgeDays: z
|
|
82
82
|
.number()
|
|
83
83
|
.optional()
|
|
84
84
|
.default(14)
|
|
85
|
-
.describe("Days in Done/Canceled before eligible for archive (default: 14)"),
|
|
85
|
+
.describe("Days in Done/Canceled before eligible for archive (default: 14, unit: days). Same concept as project_hygiene.archiveAgeDays — both renamed from the legacy archiveThresholdDays/archiveDays pair so the value is shared across discovery tools."),
|
|
86
86
|
streams: z
|
|
87
87
|
.array(z.object({
|
|
88
88
|
id: z.string(),
|
|
@@ -134,7 +134,7 @@ export function registerDashboardTools(server, client, fieldCache) {
|
|
|
134
134
|
criticalStuckHours: (args.stuckThresholdHours ?? 48) * 2,
|
|
135
135
|
wipLimits: args.wipLimits ?? {},
|
|
136
136
|
doneWindowDays: args.doneWindowDays ?? 7,
|
|
137
|
-
|
|
137
|
+
archiveAgeDays: args.archiveAgeDays ?? 14,
|
|
138
138
|
};
|
|
139
139
|
// Build dashboard from merged items
|
|
140
140
|
const dashboard = buildDashboard(allItems, healthConfig, undefined, args.streams);
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* 5. Build a `RankConfig` from the args + defaults (with injected `now`).
|
|
21
21
|
* 6. Compute each PR's `ageHours` at the boundary and call
|
|
22
22
|
* `rankDirections(allItems, enrichedPRs, config)`.
|
|
23
|
-
* 7. Return `{ directions, fetchedAt,
|
|
23
|
+
* 7. Return `{ directions, fetchedAt, boardItems }`.
|
|
24
24
|
*
|
|
25
25
|
* Determinism: `fetchedAt` is the only time-varying field. Two consecutive
|
|
26
26
|
* calls on the same board state produce byte-identical `directions[]`
|
|
@@ -295,7 +295,11 @@ export function makeRunDirections(client, fieldCache) {
|
|
|
295
295
|
return toolSuccess({
|
|
296
296
|
directions,
|
|
297
297
|
fetchedAt: now.toISOString(),
|
|
298
|
-
|
|
298
|
+
// `boardItems` is the raw count of items on the project board pre-filter
|
|
299
|
+
// (sum across all configured project numbers). Uniform across discovery
|
|
300
|
+
// tools (next_actions, pipeline_dashboard, project_hygiene). The number
|
|
301
|
+
// of returned `directions` is bounded by `limit` and may be much smaller.
|
|
302
|
+
boardItems: allItems.length,
|
|
299
303
|
});
|
|
300
304
|
}
|
|
301
305
|
catch (error) {
|
|
@@ -309,7 +313,7 @@ export function makeRunDirections(client, fieldCache) {
|
|
|
309
313
|
// ---------------------------------------------------------------------------
|
|
310
314
|
export function registerDirectionsTools(server, client, fieldCache) {
|
|
311
315
|
const runDirections = makeRunDirections(client, fieldCache);
|
|
312
|
-
server.tool("ralph_hero__hello_directions", "[DEPRECATED — use ralph_hero__next_actions instead. Removed in 2.7.0.] Compute up to N deterministic 'directions' for the hello skill's session briefing. Open PRs are fetched internally via the configured GitHub token's `repo` scope (one `is:pr is:open repo:owner/name` GraphQL search per unique repo represented in the project items). Each direction includes a structured signals object (staleDays, staleThresholdDays, tiedAtScore, estimateWeight, parentChainNote) for skills to synthesize prose. The legacy 'reason' string is @deprecated and removed in 2.7.0.", {
|
|
316
|
+
server.tool("ralph_hero__hello_directions", "[DEPRECATED — use ralph_hero__next_actions instead. Removed in 2.7.0.] Compute up to N deterministic 'directions' for the hello skill's session briefing. Open PRs are fetched internally via the configured GitHub token's `repo` scope (one `is:pr is:open repo:owner/name` GraphQL search per unique repo represented in the project items). Each direction includes a structured signals object (staleDays, staleThresholdDays, tiedAtScore, estimateWeight, parentChainNote) for skills to synthesize prose. The legacy 'reason' string is @deprecated and removed in 2.7.0. Returns `{ directions, fetchedAt, boardItems }` where `boardItems` is the raw count of items on the project board pre-filter (uniform across discovery tools); the returned `directions` array is bounded by `limit`.", {
|
|
313
317
|
owner: z
|
|
314
318
|
.string()
|
|
315
319
|
.optional()
|
|
@@ -330,29 +334,29 @@ export function registerDirectionsTools(server, client, fieldCache) {
|
|
|
330
334
|
.nonnegative()
|
|
331
335
|
.optional()
|
|
332
336
|
.default(48)
|
|
333
|
-
.describe("Hours before a non-lock issue is considered stale (default: 48)."),
|
|
337
|
+
.describe("Hours before a non-lock issue is considered stale (default: 48, unit: hours). Pulls from STUCK_THRESHOLD_HOURS in src/lib/thresholds.ts."),
|
|
334
338
|
lockStaleHours: z
|
|
335
339
|
.number()
|
|
336
340
|
.nonnegative()
|
|
337
341
|
.optional()
|
|
338
342
|
.default(24)
|
|
339
|
-
.describe("Hours before a lock-state issue is considered stalled (default: 24)."),
|
|
343
|
+
.describe("Hours before a lock-state issue is considered stalled (default: 24, unit: hours). Pulls from LOCK_STALE_HOURS in src/lib/thresholds.ts."),
|
|
340
344
|
treeRecentDoneDays: z
|
|
341
345
|
.number()
|
|
342
346
|
.nonnegative()
|
|
343
347
|
.optional()
|
|
344
348
|
.default(7)
|
|
345
|
-
.describe("Days within which a sibling Done event still pulls a tree forward (default: 7)."),
|
|
349
|
+
.describe("Days within which a sibling Done event still pulls a tree forward (default: 7, unit: days). Pulls from RECENT_WINDOW_DAYS in src/lib/thresholds.ts."),
|
|
346
350
|
prStaleHours: z
|
|
347
351
|
.number()
|
|
348
352
|
.nonnegative()
|
|
349
353
|
.optional()
|
|
350
354
|
.default(24)
|
|
351
|
-
.describe("Hours before an open PR is considered stale (default: 24)."),
|
|
355
|
+
.describe("Hours before an open PR is considered stale (default: 24, unit: hours). Pulls from PR_STALE_HOURS in src/lib/thresholds.ts."),
|
|
352
356
|
}, async (args) => {
|
|
353
357
|
return await runDirections({ ...args, audience: "human" });
|
|
354
358
|
});
|
|
355
|
-
server.tool("ralph_hero__next_actions", "Compute up to N deterministic 'directions' (next actions) with one flagged `recommended: true`. Used by the /hello skill picker (interactive) and by headless orchestrators (auto-select recommended). Open PRs are fetched internally via the configured GitHub token's `repo` scope (one `is:pr is:open repo:owner/name` GraphQL search per unique repo represented in the project items) — callers no longer pass an `openPRs` argument. Each direction includes a structured signals object (staleDays, staleThresholdDays, tiedAtScore, estimateWeight, parentChainNote) for skills to synthesize prose. The legacy 'reason' string is @deprecated and removed in 2.7.0. When `audience='agent'` and no items are in actionable phases (Plan in Review, In Review, Ready for Plan, Research Needed) or otherwise surfacing (lock-stale, unblock-requested), the picker falls back to Backlog and null-state items so autopilot can drive triage. Fallback items receive a fixed score penalty so they never outrank actionable items when those exist; the fallback never fires for `audience='human'`.", {
|
|
359
|
+
server.tool("ralph_hero__next_actions", "Compute up to N deterministic 'directions' (next actions) with one flagged `recommended: true`. Used by the /hello skill picker (interactive) and by headless orchestrators (auto-select recommended). Open PRs are fetched internally via the configured GitHub token's `repo` scope (one `is:pr is:open repo:owner/name` GraphQL search per unique repo represented in the project items) — callers no longer pass an `openPRs` argument. Each direction includes a structured signals object (staleDays, staleThresholdDays, tiedAtScore, estimateWeight, parentChainNote) for skills to synthesize prose. The legacy 'reason' string is @deprecated and removed in 2.7.0. When `audience='agent'` and no items are in actionable phases (Plan in Review, In Review, Ready for Plan, Research Needed) or otherwise surfacing (lock-stale, unblock-requested), the picker falls back to Backlog and null-state items so autopilot can drive triage. Fallback items receive a fixed score penalty so they never outrank actionable items when those exist; the fallback never fires for `audience='human'`. Returns `{ directions, fetchedAt, boardItems }` where `boardItems` is the raw count of items on the project board pre-filter (uniform across discovery tools); the returned `directions` array is bounded by `limit`.", {
|
|
356
360
|
owner: z
|
|
357
361
|
.string()
|
|
358
362
|
.optional()
|
|
@@ -378,25 +382,25 @@ export function registerDirectionsTools(server, client, fieldCache) {
|
|
|
378
382
|
.nonnegative()
|
|
379
383
|
.optional()
|
|
380
384
|
.default(48)
|
|
381
|
-
.describe("Hours before a non-lock issue is considered stale (default: 48)."),
|
|
385
|
+
.describe("Hours before a non-lock issue is considered stale (default: 48, unit: hours). Shared with pipeline_dashboard.stuckThresholdHours — both pull from STUCK_THRESHOLD_HOURS in src/lib/thresholds.ts."),
|
|
382
386
|
lockStaleHours: z
|
|
383
387
|
.number()
|
|
384
388
|
.nonnegative()
|
|
385
389
|
.optional()
|
|
386
390
|
.default(24)
|
|
387
|
-
.describe("Hours before a lock-state issue is considered stalled (default: 24)."),
|
|
391
|
+
.describe("Hours before a lock-state issue is considered stalled (default: 24, unit: hours). Pulls from LOCK_STALE_HOURS in src/lib/thresholds.ts."),
|
|
388
392
|
treeRecentDoneDays: z
|
|
389
393
|
.number()
|
|
390
394
|
.nonnegative()
|
|
391
395
|
.optional()
|
|
392
396
|
.default(7)
|
|
393
|
-
.describe("Days within which a sibling Done event still pulls a tree forward (default: 7)."),
|
|
397
|
+
.describe("Days within which a sibling Done event still pulls a tree forward (default: 7, unit: days). Shares the RECENT_WINDOW_DAYS value with hygiene.staleDays, dashboard.doneWindowDays, and metrics.velocityWindowDays."),
|
|
394
398
|
prStaleHours: z
|
|
395
399
|
.number()
|
|
396
400
|
.nonnegative()
|
|
397
401
|
.optional()
|
|
398
402
|
.default(24)
|
|
399
|
-
.describe("Hours before an open PR is considered stale (default: 24)."),
|
|
403
|
+
.describe("Hours before an open PR is considered stale (default: 24, unit: hours). Pulls from PR_STALE_HOURS in src/lib/thresholds.ts."),
|
|
400
404
|
}, async (args) => {
|
|
401
405
|
return await runDirections({ ...args, audience: args.audience ?? "human" });
|
|
402
406
|
});
|
|
@@ -14,7 +14,7 @@ import { toolSuccess, toolError, resolveProjectOwner, resolveProjectNumbers, } f
|
|
|
14
14
|
// Register hygiene tools
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
16
16
|
export function registerHygieneTools(server, client, fieldCache) {
|
|
17
|
-
server.tool("ralph_hero__project_hygiene", "Generate a project board hygiene report. Identifies archive candidates, stale items, orphaned backlog entries, missing fields, WIP violations, and duplicate candidates. Returns: report with 7 sections + summary stats.", {
|
|
17
|
+
server.tool("ralph_hero__project_hygiene", "Generate a project board hygiene report. Identifies archive candidates, stale items, orphaned backlog entries, missing fields, WIP violations, and duplicate candidates. Returns: report with 7 sections + summary stats. Top-level `boardItems` is the raw count of items on the project board pre-filter (uniform across discovery tools — next_actions, pipeline_dashboard, project_hygiene). Per-category counts in `summary` (archiveCandidateCount, staleCount, orphanCount, fieldCoveragePercent, wipViolationCount, duplicateCandidateCount) are post-filter and represent distinct buckets.", {
|
|
18
18
|
owner: z
|
|
19
19
|
.string()
|
|
20
20
|
.optional()
|
|
@@ -23,21 +23,21 @@ export function registerHygieneTools(server, client, fieldCache) {
|
|
|
23
23
|
.array(z.coerce.number())
|
|
24
24
|
.optional()
|
|
25
25
|
.describe("Project numbers to include. Defaults to RALPH_GH_PROJECT_NUMBERS or single configured project."),
|
|
26
|
-
|
|
26
|
+
archiveAgeDays: z
|
|
27
27
|
.number()
|
|
28
28
|
.optional()
|
|
29
29
|
.default(14)
|
|
30
|
-
.describe("Days before Done/Canceled items become archive candidates (default: 14)"),
|
|
30
|
+
.describe("Days before Done/Canceled items become archive candidates (default: 14, unit: days). Same concept as pipeline_dashboard.archiveAgeDays — both renamed from the legacy archiveDays/archiveThresholdDays pair so the value is shared across discovery tools."),
|
|
31
31
|
staleDays: z
|
|
32
32
|
.number()
|
|
33
33
|
.optional()
|
|
34
34
|
.default(7)
|
|
35
|
-
.describe("Days before non-terminal items are flagged as stale (default: 7)"),
|
|
35
|
+
.describe("Days before non-terminal items are flagged as stale (default: 7, unit: days). Shares the RECENT_WINDOW_DAYS value with dashboard.doneWindowDays, next_actions.treeRecentDoneDays, and metrics.velocityWindowDays."),
|
|
36
36
|
orphanDays: z
|
|
37
37
|
.number()
|
|
38
38
|
.optional()
|
|
39
39
|
.default(14)
|
|
40
|
-
.describe("Days before unassigned Backlog items are flagged as orphaned (default: 14)"),
|
|
40
|
+
.describe("Days before unassigned Backlog items are flagged as orphaned (default: 14, unit: days). Pulls from ORPHAN_AGE_DAYS in src/lib/thresholds.ts (separate concept from archiveAgeDays)."),
|
|
41
41
|
wipLimits: z
|
|
42
42
|
.record(z.coerce.number())
|
|
43
43
|
.optional()
|
|
@@ -46,7 +46,7 @@ export function registerHygieneTools(server, client, fieldCache) {
|
|
|
46
46
|
.number()
|
|
47
47
|
.optional()
|
|
48
48
|
.default(0.8)
|
|
49
|
-
.describe("Similarity threshold for duplicate detection (0.5-1.0, default: 0.8)"),
|
|
49
|
+
.describe("Similarity threshold for duplicate detection (0.5-1.0, default: 0.8, unit: ratio). Pulls from SIMILARITY_THRESHOLD in src/lib/thresholds.ts."),
|
|
50
50
|
format: z
|
|
51
51
|
.enum(["json", "markdown"])
|
|
52
52
|
.optional()
|
|
@@ -89,7 +89,7 @@ export function registerHygieneTools(server, client, fieldCache) {
|
|
|
89
89
|
// Build hygiene config
|
|
90
90
|
const hygieneConfig = {
|
|
91
91
|
...DEFAULT_HYGIENE_CONFIG,
|
|
92
|
-
|
|
92
|
+
archiveAgeDays: args.archiveAgeDays ?? 14,
|
|
93
93
|
staleDays: args.staleDays ?? 7,
|
|
94
94
|
orphanDays: args.orphanDays ?? 14,
|
|
95
95
|
wipLimits: args.wipLimits ?? {},
|
|
@@ -29,7 +29,7 @@ export function registerTrendsTools(server, client, fieldCache) {
|
|
|
29
29
|
.int()
|
|
30
30
|
.positive()
|
|
31
31
|
.default(7)
|
|
32
|
-
.describe("Velocity / highlights window in days (default: 7)."),
|
|
32
|
+
.describe("Velocity / highlights window in days (default: 7, unit: days). Shares the RECENT_WINDOW_DAYS value with hygiene.staleDays, dashboard.doneWindowDays, next_actions.treeRecentDoneDays, and metrics.velocityWindowDays."),
|
|
33
33
|
}, async (args) => {
|
|
34
34
|
try {
|
|
35
35
|
const owner = resolveProjectOwner(client.config);
|