@commandgarden/cli 2.11.0 → 2.12.1

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.
@@ -10,8 +10,8 @@
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
11
11
  <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
12
12
  <title>commandGarden</title>
13
- <script type="module" crossorigin src="/assets/index-BD8reQ8T.js"></script>
14
- <link rel="stylesheet" crossorigin href="/assets/index-wgb9-PIF.css">
13
+ <script type="module" crossorigin src="/assets/index-DR1TjI6k.js"></script>
14
+ <link rel="stylesheet" crossorigin href="/assets/index-CDD2bVHm.css">
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
@@ -122,22 +122,79 @@ export class JournalService {
122
122
  generateBasicInsights(tt, mtg, jira, git, crossRef) {
123
123
  const insights = [];
124
124
  if (crossRef?.forgottenDays.length) {
125
- insights.push(`You have activity on ${crossRef.forgottenDays.join(', ')} but 0 hours logged in TimeTracking — you may have forgotten to fill it.`);
125
+ insights.push({
126
+ id: 'forgotten-days',
127
+ severity: 'action',
128
+ category: 'time',
129
+ title: 'Unreleased hours',
130
+ detail: `Activity on ${crossRef.forgottenDays.join(', ')} but 0 hours logged — you may have forgotten to fill TimeTracking.`,
131
+ });
132
+ }
133
+ if (tt && tt.totalHours < tt.targetHours * 0.8) {
134
+ const gap = Math.round((tt.targetHours - tt.totalHours) * 100) / 100;
135
+ insights.push({
136
+ id: 'below-target',
137
+ severity: 'warning',
138
+ category: 'time',
139
+ title: 'Below weekly target',
140
+ detail: `${gap}h short of your ${tt.targetHours}h target.`,
141
+ });
126
142
  }
127
143
  if (crossRef?.heavyMeetingDays.length) {
128
144
  const days = crossRef.heavyMeetingDays.map((d) => `${d.date} (${d.hours}h)`).join(', ');
129
- insights.push(`Heavy meeting days: ${days}. Consider blocking focus time.`);
145
+ insights.push({
146
+ id: 'heavy-meetings',
147
+ severity: 'warning',
148
+ category: 'meetings',
149
+ title: 'Heavy meeting days',
150
+ detail: `${days}. Consider blocking focus time.`,
151
+ });
130
152
  }
131
- if (tt && tt.totalHours < tt.targetHours * 0.8) {
132
- const gap = tt.targetHours - tt.totalHours;
133
- insights.push(`You're ${gap}h below your ${tt.targetHours}h weekly target.`);
153
+ if (crossRef && crossRef.meetingRatio > 0.5 && mtg) {
154
+ insights.push({
155
+ id: 'high-meeting-ratio',
156
+ severity: 'info',
157
+ category: 'meetings',
158
+ title: 'High meeting ratio',
159
+ detail: `Meetings consumed ${Math.round(crossRef.meetingRatio * 100)}% of your logged hours this week.`,
160
+ });
134
161
  }
135
162
  if (jira?.blockers.length) {
136
163
  const items = jira.blockers.map((b) => `${b.key} (${b.staleDays}d)`).join(', ');
137
- insights.push(`Stale tickets: ${items}. Consider unblocking or reassigning.`);
164
+ insights.push({
165
+ id: 'stale-tickets',
166
+ severity: 'warning',
167
+ category: 'tickets',
168
+ title: 'Stale tickets',
169
+ detail: `${items}. Consider unblocking or reassigning.`,
170
+ });
138
171
  }
139
- if (crossRef && crossRef.meetingRatio > 0.5 && mtg) {
140
- insights.push(`Meetings consumed ${Math.round(crossRef.meetingRatio * 100)}% of your logged hours this week.`);
172
+ if (crossRef && crossRef.zeroCodingDays.length > 0 && git && git.totalCommits > 0) {
173
+ insights.push({
174
+ id: 'zero-coding-days',
175
+ severity: 'info',
176
+ category: 'code',
177
+ title: 'Zero-commit days',
178
+ detail: `No commits on ${crossRef.zeroCodingDays.join(', ')}.`,
179
+ });
180
+ }
181
+ if (git && git.totalCommits > 0 && git.totalPRsReviewed === 0) {
182
+ insights.push({
183
+ id: 'low-pr-reviews',
184
+ severity: 'info',
185
+ category: 'code',
186
+ title: 'No PR reviews',
187
+ detail: 'You made commits this week but reviewed 0 PRs.',
188
+ });
189
+ }
190
+ if (insights.length === 0) {
191
+ insights.push({
192
+ id: 'all-clear',
193
+ severity: 'positive',
194
+ category: 'time',
195
+ title: 'All clear',
196
+ detail: 'No issues detected this week.',
197
+ });
141
198
  }
142
199
  return insights;
143
200
  }
@@ -35,6 +35,11 @@ export interface MonthlyTimetrackingData {
35
35
  projectId: string;
36
36
  hours: number;
37
37
  }[];
38
+ hoursByProjectActivity: {
39
+ projectId: string;
40
+ activity: string;
41
+ hours: number;
42
+ }[];
38
43
  }
39
44
  export interface MeetingEntry {
40
45
  date: string;
@@ -85,6 +90,15 @@ export interface GitData {
85
90
  daily: GitDaily[];
86
91
  repos: GitRepoActivity[];
87
92
  }
93
+ export type InsightSeverity = 'action' | 'warning' | 'info' | 'positive';
94
+ export type InsightCategory = 'time' | 'meetings' | 'tickets' | 'code';
95
+ export interface Insight {
96
+ id: string;
97
+ severity: InsightSeverity;
98
+ category: InsightCategory;
99
+ title: string;
100
+ detail: string;
101
+ }
88
102
  export interface CrossRefData {
89
103
  forgottenDays: string[];
90
104
  heavyMeetingDays: {
@@ -110,7 +124,7 @@ export interface JournalResponse {
110
124
  jira: JiraData | null;
111
125
  git: GitData | null;
112
126
  crossRef: CrossRefData | null;
113
- insights: string[];
127
+ insights: Insight[];
114
128
  errors: string[];
115
129
  monthlyTimetracking: MonthlyTimetrackingData | null;
116
130
  cache?: JournalCacheInfo;
@@ -115,16 +115,31 @@ export class TimetrackingSource {
115
115
  const releasedDates = workingDatesElapsed.filter((d) => releasedDateSet.has(d));
116
116
  const unreleasedDates = workingDatesElapsed.filter((d) => !releasedDateSet.has(d));
117
117
  const hoursMap = new Map();
118
+ const hoursByPAMap = new Map();
118
119
  for (const row of raw) {
119
120
  if (!row.projectId)
120
121
  continue;
121
122
  const hrs = typeof row.hours === 'number' ? row.hours : 0;
122
123
  hoursMap.set(row.projectId, (hoursMap.get(row.projectId) ?? 0) + hrs);
124
+ if (row.activity) {
125
+ const paKey = `${row.projectId}\0${row.activity}`;
126
+ const existing = hoursByPAMap.get(paKey);
127
+ if (existing) {
128
+ existing.hours += hrs;
129
+ }
130
+ else {
131
+ hoursByPAMap.set(paKey, { projectId: row.projectId, activity: row.activity, hours: hrs });
132
+ }
133
+ }
123
134
  }
124
135
  const hoursByProject = [...hoursMap.entries()].map(([projectId, hours]) => ({
125
136
  projectId,
126
137
  hours: Math.round(hours * 100) / 100,
127
138
  }));
139
+ const hoursByProjectActivity = [...hoursByPAMap.values()].map((e) => ({
140
+ ...e,
141
+ hours: Math.round(e.hours * 100) / 100,
142
+ }));
128
143
  return {
129
144
  month,
130
145
  workingDaysTotal,
@@ -132,6 +147,7 @@ export class TimetrackingSource {
132
147
  releasedDates,
133
148
  unreleasedDates,
134
149
  hoursByProject,
150
+ hoursByProjectActivity,
135
151
  };
136
152
  }
137
153
  }
@@ -3,6 +3,7 @@ name: git-commits
3
3
  version: "1.0"
4
4
  description: "Azure DevOps Git commits and PRs across all branches for a date range"
5
5
  access: read
6
+ cdp: true
6
7
 
7
8
  domains:
8
9
  - "dev.azure.com"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commandgarden/cli",
3
- "version": "2.11.0",
3
+ "version": "2.12.1",
4
4
  "description": "Enterprise browser automation CLI",
5
5
  "type": "module",
6
6
  "license": "MIT",