@thinkai/tai-api-contract 2.34.0-pr.810.e54f7dcd → 2.35.0-pr.811.d57af044

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/src/index.ts CHANGED
@@ -302,10 +302,6 @@ export interface DashboardProductivityMetricsDto {
302
302
  quality?: {
303
303
  codeReviewHoursPerWeek?: number;
304
304
  buildFailuresPerWeek?: number;
305
- /** Share of completed CI runs that failed (GitHub Actions / Checks). */
306
- ciBuildFailRatePercent?: number;
307
- /** Count of build.failed events in the bucket window. */
308
- ciBuildFailuresCount?: number;
309
305
  avgTimeToFixBuildHours?: number;
310
306
  testCoveragePercent?: number;
311
307
  escapedBugsCount?: number;
@@ -464,10 +460,6 @@ export interface ProductivityInsightsAggregatedTeamWeekDto {
464
460
  weekLabel?: string;
465
461
  mergedPrCount: number;
466
462
  failRatePercent: number;
467
- /** GitHub Actions CI build failure rate (failed / (failed + passed) * 100). */
468
- ciFailRatePercent?: number;
469
- /** Count of build.failed events in the bucket when CI ingest is enabled. */
470
- ciBuildFailuresCount?: number;
471
463
  avgReviewWaitHours?: number;
472
464
  avgApprovalHours?: number;
473
465
  avgTimeToMergeHours?: number;
@@ -492,8 +484,6 @@ export interface ProductivityInsightsAggregatedTeamsDto {
492
484
  range: "4w" | "8w" | "q" | "12w" | "16w";
493
485
  scope: ProductivityInsightsAggregatedTeamsScopeDto;
494
486
  generatedAt: string;
495
- /** True when the workspace has opted in to GitHub Actions CI ingest. */
496
- githubActionsCiEnabled?: boolean;
497
487
  teams: ProductivityInsightsAggregatedTeamRowDto[];
498
488
  }
499
489
 
@@ -535,7 +525,6 @@ export interface ProductivityInsightsTeamLeaderItemDto {
535
525
  subtitle?: string;
536
526
  periodAvgMergedPrs: number;
537
527
  avgFailRatePercent?: number;
538
- avgCiFailRatePercent?: number;
539
528
  }
540
529
 
541
530
  /** Period summary for a single-team drilldown. */
@@ -543,8 +532,6 @@ export interface ProductivityInsightsTeamSummaryDto {
543
532
  totalMergedPrs: number;
544
533
  avgMergedPrsPerWeek: number;
545
534
  avgFailRatePercent: number;
546
- /** Average GitHub Actions CI build failure rate over the range when CI ingest is enabled. */
547
- avgCiFailRatePercent?: number;
548
535
  headcount: number;
549
536
  }
550
537
 
@@ -573,8 +560,6 @@ export interface ProductivityInsightsTeamsResponseDto {
573
560
  year?: number;
574
561
  scope: ProductivityInsightsAggregatedTeamsScopeDto;
575
562
  generatedAt: string;
576
- /** True when the workspace has opted in to GitHub Actions CI ingest. */
577
- githubActionsCiEnabled?: boolean;
578
563
  /** Top-ranked teams when `teamId` query is omitted. */
579
564
  items?: ProductivityInsightsTeamLeaderItemDto[];
580
565
  /** Single-team drilldown when `teamId` query is set. */
@@ -628,14 +613,10 @@ export interface ProductivityInsightsContributorActivitySummaryDto {
628
613
  avgMergedPrsPerWeek: number;
629
614
  totalAbsenceDays: number;
630
615
  avgFailRate: number;
631
- /** Average GitHub Actions CI build failure rate over the range when CI ingest is enabled. */
632
- avgCiFailRate?: number;
633
616
  mergedPrDelta: number;
634
617
  avgMergedPrsPerWeekDelta: number;
635
618
  totalAbsenceDaysDelta: number;
636
619
  avgFailRateDelta: number;
637
- /** Change in average CI build failure rate vs the prior comparison window. */
638
- avgCiFailRateDelta?: number;
639
620
  }
640
621
 
641
622
  /** Response for GET /workspaces/:workspaceId/insights/productivity-insights/contributors/:contributorId */
@@ -647,8 +628,6 @@ export interface ProductivityInsightsContributorDetailDto {
647
628
  year?: number;
648
629
  scope: ProductivityInsightsAggregatedTeamsScopeDto;
649
630
  generatedAt: string;
650
- /** True when the workspace has opted in to GitHub Actions CI ingest. */
651
- githubActionsCiEnabled?: boolean;
652
631
  contributor: ProductivityInsightsContributorProfileDto;
653
632
  weeks: ProductivityInsightsContributorActivityWeekDto[];
654
633
  summary: ProductivityInsightsContributorActivitySummaryDto;
@@ -663,7 +642,6 @@ export interface ProductivityInsightsNeedsAttentionItemDto {
663
642
  subtitle?: string;
664
643
  periodAvgMergedPrs: number;
665
644
  avgFailRatePercent?: number;
666
- avgCiFailRatePercent?: number;
667
645
  }
668
646
 
669
647
  /** Response for GET /workspaces/:workspaceId/insights/productivity-insights/needs-attention */
@@ -673,7 +651,6 @@ export interface ProductivityInsightsNeedsAttentionDto {
673
651
  scope: ProductivityInsightsAggregatedTeamsScopeDto;
674
652
  entityType: "teams" | "contributors";
675
653
  generatedAt: string;
676
- githubActionsCiEnabled?: boolean;
677
654
  items: ProductivityInsightsNeedsAttentionItemDto[];
678
655
  }
679
656
 
@@ -1107,3 +1084,13 @@ export interface HrisTriggerSyncResultDto {
1107
1084
  /** Per-provider results when one or more HRIS sources are configured. */
1108
1085
  providers?: HrisProviderSyncResultDto[];
1109
1086
  }
1087
+
1088
+ export {
1089
+ JOURNEY_STEP_IDS,
1090
+ JOURNEY_STEP_REGISTRY,
1091
+ normalizeStepIdList,
1092
+ normalizeToStepId,
1093
+ parseJourneyStepInputs,
1094
+ stepIdToRoute,
1095
+ type JourneyStepId,
1096
+ } from "./journeySteps.js";
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Unit tests for journey step ID registry and normalization.
3
+ */
4
+ import {
5
+ JOURNEY_STEP_IDS,
6
+ normalizeStepIdList,
7
+ normalizeToStepId,
8
+ parseJourneyStepInputs,
9
+ stepIdToRoute,
10
+ } from "./journeySteps.js";
11
+
12
+ if (normalizeToStepId("/phase-a/p1") !== "onboarding.workspace") {
13
+ throw new Error("expected /phase-a/p1 → onboarding.workspace");
14
+ }
15
+ if (normalizeToStepId("/onboarding/workspace") !== "onboarding.workspace") {
16
+ throw new Error("expected semantic route alias");
17
+ }
18
+ if (normalizeToStepId("readiness.dashboard") !== "readiness.dashboard") {
19
+ throw new Error("expected canonical step id passthrough");
20
+ }
21
+ if (normalizeToStepId("/unknown/route") !== null) {
22
+ throw new Error("unknown route should normalize to null");
23
+ }
24
+
25
+ const deduped = normalizeStepIdList(["/phase-a/p1", "/onboarding/workspace", "/phase-a/p1"]);
26
+ if (deduped.length !== 1 || deduped[0] !== "onboarding.workspace") {
27
+ throw new Error(`dedupe failed: ${JSON.stringify(deduped)}`);
28
+ }
29
+
30
+ const droppedUnknown = parseJourneyStepInputs(["/phase-a/p1", "/stale/route"]);
31
+ if (droppedUnknown.error || droppedUnknown.value?.length !== 1) {
32
+ throw new Error(`unknown should drop silently by default: ${JSON.stringify(droppedUnknown)}`);
33
+ }
34
+ if (droppedUnknown.unknown?.length !== 1 || droppedUnknown.unknown[0] !== "/stale/route") {
35
+ throw new Error("unknown list should report dropped entries");
36
+ }
37
+
38
+ const rejectUnknown = parseJourneyStepInputs(["/stale/route"], { rejectUnknown: true });
39
+ if (rejectUnknown.error !== "Unknown journey step" || rejectUnknown.unknown?.[0] !== "/stale/route") {
40
+ throw new Error(`rejectUnknown failed: ${JSON.stringify(rejectUnknown)}`);
41
+ }
42
+
43
+ const blankStep = parseJourneyStepInputs([" "], { rejectUnknown: true });
44
+ if (blankStep.error !== "Each completed step must be a non-empty string") {
45
+ throw new Error(`blank step guard failed: ${JSON.stringify(blankStep)}`);
46
+ }
47
+
48
+ const atCap = parseJourneyStepInputs(JOURNEY_STEP_IDS, { rejectUnknown: true });
49
+ if (atCap.error || atCap.value?.length !== JOURNEY_STEP_IDS.length) {
50
+ throw new Error(`full registry parse failed: ${JSON.stringify(atCap)}`);
51
+ }
52
+
53
+ if (stepIdToRoute("onboarding.workspace") !== "/onboarding/workspace") {
54
+ throw new Error("stepIdToRoute failed for onboarding.workspace");
55
+ }
56
+ if (stepIdToRoute("not.a.step") !== null) {
57
+ throw new Error("stepIdToRoute should return null for unknown id");
58
+ }
59
+
60
+ console.log("journeySteps.test: OK");
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Canonical journey step IDs — stable keys for journey progress storage.
3
+ * Semantic routes are the SPA URL surface; legacy /phase-* paths normalize here.
4
+ */
5
+
6
+ export const JOURNEY_STEP_IDS = [
7
+ "onboarding.workspace",
8
+ "onboarding.ai_readiness",
9
+ "onboarding.integrations",
10
+ "readiness.dashboard",
11
+ "readiness.fix_queue",
12
+ "readiness.activity",
13
+ "insights.productivity",
14
+ "insights.productivity.by_user",
15
+ "insights.productivity.by_team",
16
+ "insights.ai_usage",
17
+ "insights.ai_usage.by_user",
18
+ "insights.ai_usage.by_team",
19
+ "insights.ai_usage.by_model",
20
+ ] as const;
21
+
22
+ export type JourneyStepId = (typeof JOURNEY_STEP_IDS)[number];
23
+
24
+ type StepDef = {
25
+ id: JourneyStepId;
26
+ route: string;
27
+ aliases: readonly string[];
28
+ };
29
+
30
+ /** Registry order matches product journey A → R → C. */
31
+ export const JOURNEY_STEP_REGISTRY: readonly StepDef[] = [
32
+ {
33
+ id: "onboarding.workspace",
34
+ route: "/onboarding/workspace",
35
+ aliases: ["/phase-a/p1"],
36
+ },
37
+ {
38
+ id: "onboarding.ai_readiness",
39
+ route: "/onboarding/ai-readiness",
40
+ aliases: ["/phase-a/p2"],
41
+ },
42
+ {
43
+ id: "onboarding.integrations",
44
+ route: "/onboarding/integrations",
45
+ aliases: [
46
+ "/onboarding/platform",
47
+ "/phase-a/p3",
48
+ "/phase-a/p4",
49
+ "/phase-a/p5",
50
+ "/phase-a/p6",
51
+ "/phase-a/p7",
52
+ ],
53
+ },
54
+ {
55
+ id: "readiness.dashboard",
56
+ route: "/agentic-foundation",
57
+ aliases: ["/phase-r/r1"],
58
+ },
59
+ {
60
+ id: "readiness.fix_queue",
61
+ route: "/agentic-foundation/fix-queue",
62
+ aliases: ["/phase-r/r3"],
63
+ },
64
+ {
65
+ id: "readiness.activity",
66
+ route: "/agentic-foundation/activity",
67
+ aliases: ["/phase-r/r4"],
68
+ },
69
+ {
70
+ id: "insights.productivity",
71
+ route: "/insights/productivity",
72
+ aliases: ["/insights/productivity/overview", "/phase-c/c4"],
73
+ },
74
+ {
75
+ id: "insights.productivity.by_user",
76
+ route: "/insights/productivity/by-user",
77
+ aliases: ["/phase-c/c4/by-user"],
78
+ },
79
+ {
80
+ id: "insights.productivity.by_team",
81
+ route: "/insights/productivity/by-team",
82
+ aliases: ["/phase-c/c4/by-team"],
83
+ },
84
+ {
85
+ id: "insights.ai_usage",
86
+ route: "/insights/ai-usage",
87
+ aliases: ["/insights/ai-usage/overview", "/phase-c/c7"],
88
+ },
89
+ {
90
+ id: "insights.ai_usage.by_user",
91
+ route: "/insights/ai-usage/by-user",
92
+ aliases: ["/phase-c/c7/ai-usage-by-user"],
93
+ },
94
+ {
95
+ id: "insights.ai_usage.by_team",
96
+ route: "/insights/ai-usage/by-team",
97
+ aliases: ["/phase-c/c7/ai-usage-by-team"],
98
+ },
99
+ {
100
+ id: "insights.ai_usage.by_model",
101
+ route: "/insights/ai-usage/by-model",
102
+ aliases: ["/phase-c/c7/costs-by-llm-model"],
103
+ },
104
+ ] as const;
105
+
106
+ const STEP_ID_SET = new Set<string>(JOURNEY_STEP_IDS);
107
+
108
+ const ALIAS_TO_STEP_ID = new Map<string, JourneyStepId>();
109
+
110
+ for (const def of JOURNEY_STEP_REGISTRY) {
111
+ ALIAS_TO_STEP_ID.set(def.id, def.id);
112
+ ALIAS_TO_STEP_ID.set(def.route, def.id);
113
+ for (const alias of def.aliases) {
114
+ ALIAS_TO_STEP_ID.set(alias, def.id);
115
+ }
116
+ }
117
+
118
+ const STEP_ID_TO_ROUTE = new Map<JourneyStepId, string>(
119
+ JOURNEY_STEP_REGISTRY.map((d) => [d.id, d.route]),
120
+ );
121
+
122
+ function normalizePathKey(input: string): string {
123
+ const trimmed = input.trim();
124
+ if (!trimmed) return "";
125
+ const noQuery = trimmed.split("?")[0]?.split("#")[0] ?? trimmed;
126
+ const collapsed = noQuery.replace(/\/+$/, "") || "/";
127
+ return collapsed;
128
+ }
129
+
130
+ /** Map step ID, semantic route, or legacy path to a canonical step ID. */
131
+ export function normalizeToStepId(input: string): JourneyStepId | null {
132
+ const key = normalizePathKey(input);
133
+ if (!key) return null;
134
+ return ALIAS_TO_STEP_ID.get(key) ?? null;
135
+ }
136
+
137
+ /** Resolve a canonical step ID to its primary semantic SPA route. */
138
+ export function stepIdToRoute(stepId: string): string | null {
139
+ if (!STEP_ID_SET.has(stepId)) return null;
140
+ return STEP_ID_TO_ROUTE.get(stepId as JourneyStepId) ?? null;
141
+ }
142
+
143
+ /** Normalize an array of mixed inputs to deduped canonical step IDs (drops unknowns). */
144
+ export function normalizeStepIdList(inputs: readonly string[]): JourneyStepId[] {
145
+ const out = new Set<JourneyStepId>();
146
+ for (const raw of inputs) {
147
+ const id = normalizeToStepId(raw);
148
+ if (id) out.add(id);
149
+ }
150
+ return [...out];
151
+ }
152
+
153
+ /** Accept step IDs and legacy/route strings; returns normalized IDs or an error message. */
154
+ export function parseJourneyStepInputs(
155
+ inputs: readonly string[],
156
+ options?: { rejectUnknown?: boolean },
157
+ ): { value?: JourneyStepId[]; error?: string; unknown?: string[] } {
158
+ const rejectUnknown = options?.rejectUnknown === true;
159
+ const unknown: string[] = [];
160
+ const out = new Set<JourneyStepId>();
161
+ for (const raw of inputs) {
162
+ if (typeof raw !== "string") {
163
+ return { error: "Each completed step must be a string" };
164
+ }
165
+ const trimmed = raw.trim();
166
+ if (!trimmed) {
167
+ return { error: "Each completed step must be a non-empty string" };
168
+ }
169
+ if (trimmed.length > 128) {
170
+ return { error: "Each completed step must be at most 128 characters" };
171
+ }
172
+ const id = normalizeToStepId(trimmed);
173
+ if (!id) {
174
+ unknown.push(trimmed);
175
+ continue;
176
+ }
177
+ out.add(id);
178
+ }
179
+ if (rejectUnknown && unknown.length > 0) {
180
+ return { error: "Unknown journey step", unknown };
181
+ }
182
+ const value = [...out];
183
+ if (value.length > 256) {
184
+ return { error: "completedStepIds must have at most 256 entries" };
185
+ }
186
+ if (unknown.length > 0) {
187
+ return { value, unknown };
188
+ }
189
+ return { value };
190
+ }