@zaip-ai/cli 1.3.1 → 1.3.4

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.
Files changed (83) hide show
  1. package/LICENSE +6 -3
  2. package/README.md +11 -4
  3. package/dist/services/codex-pre-tool-hook-template.js +1 -1
  4. package/dist/services/runtime-template.test.js +9 -8
  5. package/dist/services/runtime-template.test.js.map +1 -1
  6. package/package.json +2 -2
  7. package/templates/.agents/dashboard/.zaip-managed.json +403 -383
  8. package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-ovxurkIk.js → CostCalculationGuidePage-BZWjXQB-.js} +1 -1
  9. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-BgVgrfhQ.js → DashboardHelpDialog-D8qqeIPE.js} +58 -1
  10. package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-vSvvOHQp.js → PricingSettingsDialog-Cm12_A0v.js} +1 -1
  11. package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-zH758vfs.js → ProjectSettingsPage-DxO9F8B4.js} +1 -1
  12. package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-DLn-Mz2M.js → TraceabilityWorkspace-_x0twOcS.js} +1 -1
  13. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-Cpp8xFij.js +68 -0
  14. package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-B_lDDHn1.js → icons-vendor-CR4Fbf3g.js} +1 -1
  15. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-ncC1rxap.js +2 -0
  16. package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-B3TJrD4O.js → react-vendor-DRJQbKnp.js} +1 -1
  17. package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-7f1j5wx3.js → traceability-CMrDYqjR.js} +1 -1
  18. package/templates/.agents/dashboard/apps/frontend/dist/index.html +5 -5
  19. package/templates/.agents/dashboard/apps/frontend/src/App.tsx +48 -3
  20. package/templates/.agents/dashboard/apps/frontend/src/AppRequestGuards.tsx +44 -0
  21. package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +92 -1
  22. package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +2 -2
  23. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostDeveloperAttributionPanel.test.ts +60 -0
  24. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostDeveloperAttributionPanel.tsx +26 -9
  25. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorNavigation.tsx +189 -0
  26. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +122 -2
  27. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +70 -155
  28. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +59 -0
  29. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +55 -2
  30. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +274 -246
  31. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +255 -227
  32. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +263 -235
  33. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-dimension-facts.test.ts +282 -0
  34. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-dimension-facts.ts +71 -0
  35. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-operations-work-list.test.ts +70 -0
  36. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-operations-work-list.ts +74 -7
  37. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-launch-progress.ts +18 -0
  38. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-stale-seed.ts +50 -0
  39. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +9 -6
  40. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +45 -73
  41. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-seed.test.ts +79 -2
  42. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-work-item-index.test.ts +56 -0
  43. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.mjs +26 -8
  44. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.test.mjs +45 -4
  45. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-lifecycle.mjs +58 -0
  46. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-lifecycle.test.mjs +81 -0
  47. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +171 -71
  48. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +152 -11
  49. package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +4 -9
  50. package/templates/.agents/dashboard/tests/e2e/playwright.config.ts +9 -1
  51. package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-period-transition.spec.ts +232 -0
  52. package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +26 -5
  53. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  54. package/templates/.agents/requirements/comparative-audit.md +1 -1
  55. package/templates/.agents/requirements/context-engine.md +1 -1
  56. package/templates/.agents/requirements/cost-observability.md +1 -1
  57. package/templates/.agents/requirements/dashboard-launch.md +1 -1
  58. package/templates/.agents/requirements/data-export.md +1 -1
  59. package/templates/.agents/requirements/design-system.md +1 -1
  60. package/templates/.agents/requirements/execution-engine.md +1 -1
  61. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  62. package/templates/.agents/requirements/model-orchestration.md +1 -1
  63. package/templates/.agents/requirements/public-distribution.md +1 -1
  64. package/templates/.agents/requirements/software-architecture.md +1 -1
  65. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  66. package/templates/.agents/requirements/work-lifecycle.md +1 -1
  67. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  68. package/templates/.agents/runtime/bin/zaip.mjs +7 -7
  69. package/templates/.agents/runtime/checksums.sha256 +3 -3
  70. package/templates/.agents/runtime/provenance.json +3 -3
  71. package/templates/.agents/runtime/sbom.cdx.json +1595 -3491
  72. package/templates/.agents/skills/zaip-skill-lifecycle-closure/SKILL.md +16 -0
  73. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.mjs +19 -20
  74. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.test.mjs +143 -0
  75. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.mjs +14 -0
  76. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.mjs +3 -1
  77. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.test.mjs +21 -0
  78. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/source-size-telemetry/model-identity-reconciliation.mjs +3 -0
  79. package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +44 -0
  80. package/templates/template-manifest.json +536 -932
  81. package/templates/.agents/dashboard/.tmp-run-list.json +0 -254
  82. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-DKwP1stV.js +0 -68
  83. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-CZQlsLV9.js +0 -2
@@ -11,7 +11,10 @@ import type { CostMonitorProjection } from "../contracts.js";
11
11
  import { normalizeCostProvider } from "../provider.js";
12
12
  import { aggregateTaskCostRows } from "../task-cost-groups.js";
13
13
  import { withTokenBackedCompactDuration } from "./CompactLifecycleCostTable.js";
14
- import { CostDeveloperAttributionPanel } from "./CostDeveloperAttributionPanel.js";
14
+ import {
15
+ CostDeveloperAttributionPanel,
16
+ loadCostDeveloperAttribution,
17
+ } from "./CostDeveloperAttributionPanel.js";
15
18
  import {
16
19
  type CostFilterState,
17
20
  CostFilters,
@@ -25,8 +28,8 @@ import {
25
28
  useCostMonitorPeriod,
26
29
  } from "./CostFilters.js";
27
30
  import { CostMonitorContent } from "./CostMonitorContent.js";
28
- import { ChevronDownIcon, ListFilterIcon, LocaleFlag, SettingsIcon } from "./CostMonitorIcons.js";
29
31
  import { CostMonitorLoadErrors } from "./CostMonitorLoadErrors.js";
32
+ import { CostMonitorNavigation, type CostNavigationPopup } from "./CostMonitorNavigation.js";
30
33
  import { CostSummary } from "./CostSummary.js";
31
34
  import {
32
35
  groupRows,
@@ -64,8 +67,6 @@ import "./cost-monitor-responsive.css";
64
67
  import "./cost-run-origin.css";
65
68
  import "./cost-monitor-embedded.css";
66
69
 
67
- const zaipLogoUrl = new URL("../../../../../config/src/logo_novo_azul.png", import.meta.url).href;
68
-
69
70
  export {
70
71
  findRunDetails,
71
72
  isCurrentCostRunPageGeneration,
@@ -88,6 +89,20 @@ export function isCostViewExportEligible(view: CostView): boolean {
88
89
  return view !== "overview";
89
90
  }
90
91
 
92
+ /** Developers has no local provider/model filter contract. In the embedded
93
+ * all-time surface, do not let those residual UI filters narrow it to month. */
94
+ export function resolveDeveloperAttributionRange({
95
+ embedded,
96
+ globalDateRange,
97
+ runPageRange,
98
+ }: {
99
+ embedded: boolean;
100
+ globalDateRange?: { start: string; end: string };
101
+ runPageRange?: { from: string; to: string };
102
+ }): { from: string; to: string } | undefined {
103
+ return embedded && globalDateRange === undefined ? undefined : runPageRange;
104
+ }
105
+
91
106
  /**
92
107
  * The Cost period request filters Runs, while Operations is the global snapshot
93
108
  * used for shared entity detail. Keep the Executions tab on that same active
@@ -183,9 +198,7 @@ export function CostMonitorPage({
183
198
  onCurrencyChange?.(next);
184
199
  };
185
200
  const [period, setPeriod] = useState<CostPeriod>(defaultCostPeriod);
186
- const [filtersOpen, setFiltersOpen] = useState(false);
187
- const [localeOpen, setLocaleOpen] = useState(false);
188
- const [settingsOpen, setSettingsOpen] = useState(false);
201
+ const [navigationPopup, setNavigationPopup] = useState<CostNavigationPopup>(null);
189
202
  const [selectedRun, setSelectedRun] = useState<RunProjection | null>(null);
190
203
  const [detailData, setDetailData] = useState<CostMonitorProjection | null>(null);
191
204
  const [detailLoading, setDetailLoading] = useState(false);
@@ -216,6 +229,13 @@ export function CostMonitorPage({
216
229
  period,
217
230
  runPageRange,
218
231
  });
232
+ // Developers has no local provider/model filter contract. The embedded
233
+ // surface therefore remains all-time unless the host supplies a date range.
234
+ const developerAttributionRange = resolveDeveloperAttributionRange({
235
+ embedded,
236
+ ...(globalFilters?.dateRange === undefined ? {} : { globalDateRange: globalFilters.dateRange }),
237
+ ...(runPageRange === undefined ? {} : { runPageRange }),
238
+ });
219
239
  const compactPageRange = resolveCompactCostPageRange({
220
240
  embedded,
221
241
  globalDateRange: globalFilters?.dateRange,
@@ -545,6 +565,7 @@ export function CostMonitorPage({
545
565
  const usingCompactPage = compactKind !== null && loadReadModelPage !== undefined;
546
566
  const exportRows = useMemo(() => {
547
567
  if (usingCompactPage) return compactPage?.rows ?? staleCompactPage?.rows ?? [];
568
+ if (view === "developers") return [];
548
569
  if (view === "executions") return visibleExecutions;
549
570
  if (view === "conversations") return groups.conversations;
550
571
  if (view === "tasks") return groups.tasks;
@@ -651,6 +672,25 @@ export function CostMonitorPage({
651
672
  },
652
673
  }
653
674
  : undefined;
675
+ // The developer table is paginated only by the DataTable in the browser.
676
+ // Its API response is the full attributed population, so export from the
677
+ // same endpoint and exact active period instead of the visible ten rows.
678
+ const developerExportSource =
679
+ view === "developers"
680
+ ? {
681
+ prepare: async () => {
682
+ const developerRows = await loadCostDeveloperAttribution({
683
+ ...(developerAttributionRange === undefined ? {} : developerAttributionRange),
684
+ });
685
+ return {
686
+ totalRows: developerRows.length,
687
+ pages: async function* () {
688
+ yield developerRows as readonly Record<string, unknown>[];
689
+ },
690
+ };
691
+ },
692
+ }
693
+ : undefined;
654
694
  // Period views are paginated SQLite projections too. Exporting their
655
695
  // already-rendered group rows truncates the workbook to the visible page
656
696
  // (and can lose the projection's factual duration). Capture the same
@@ -749,151 +789,23 @@ export function CostMonitorPage({
749
789
  retryGroup={groupPageState.retry}
750
790
  />
751
791
  {embedded ? null : (
752
- <>
753
- <nav aria-label="Navegação do Cost Monitor" className="cost-monitor-nav">
754
- <div className="cost-monitor-brand">
755
- <strong className="cost-monitor-title">{text.title}</strong>
756
- <small className="cost-last-updated">{updatedLabel}</small>
757
- </div>
758
- <img alt="ZAIP" className="cost-monitor-logo" src={zaipLogoUrl} />
759
- <div className="cost-nav-actions">
760
- <button
761
- aria-label={text.refresh}
762
- className="cost-nav-icon cost-refresh-action"
763
- disabled={loading}
764
- onClick={onRefresh}
765
- type="button"
766
- >
767
- ↻
768
- </button>
769
- <div className="cost-nav-popup-anchor cost-filter-action">
770
- <button
771
- className={filtersOpen ? "cost-nav-button is-active" : "cost-nav-button"}
772
- onClick={() => {
773
- setFiltersOpen((open) => !open);
774
- setLocaleOpen(false);
775
- setSettingsOpen(false);
776
- }}
777
- type="button"
778
- >
779
- <ListFilterIcon /> {text.filters}
780
- </button>
781
- {filtersOpen ? (
782
- <div className="cost-nav-popup cost-nav-filter-popup">
783
- <CostFilters
784
- available={data.filters_available}
785
- hideQuery={embedded}
786
- locale={locale}
787
- onChange={setFilters}
788
- value={filters}
789
- />
790
- </div>
791
- ) : null}
792
- </div>
793
- <div className="cost-nav-popup-anchor cost-locale-action">
794
- <button
795
- className={localeOpen ? "cost-nav-button is-active" : "cost-nav-button"}
796
- onClick={() => {
797
- setLocaleOpen((open) => !open);
798
- setFiltersOpen(false);
799
- setSettingsOpen(false);
800
- }}
801
- type="button"
802
- >
803
- <LocaleFlag locale={locale} />
804
- {locale === "pt-BR" ? "PT-BR" : "EN-US"}
805
- <ChevronDownIcon />
806
- </button>
807
- {localeOpen ? (
808
- <div className="cost-nav-popup cost-nav-locale-popup">
809
- <button onClick={() => changeLocale("pt-BR")} type="button">
810
- <LocaleFlag locale="pt-BR" /> Português do Brasil{" "}
811
- {locale === "pt-BR" ? "✓" : ""}
812
- </button>
813
- <button onClick={() => changeLocale("en-US")} type="button">
814
- <LocaleFlag locale="en-US" /> English {locale === "en-US" ? "✓" : ""}
815
- </button>
816
- </div>
817
- ) : null}
818
- </div>
819
- <div className="cost-nav-popup-anchor cost-settings-action">
820
- <button
821
- aria-label={text.settings}
822
- className={settingsOpen ? "cost-nav-icon is-active" : "cost-nav-icon"}
823
- onClick={() => {
824
- setSettingsOpen((open) => !open);
825
- setFiltersOpen(false);
826
- setLocaleOpen(false);
827
- }}
828
- type="button"
829
- >
830
- <SettingsIcon />
831
- </button>
832
- {settingsOpen ? (
833
- <div className="cost-nav-popup cost-nav-settings-popup">
834
- <div className="cost-mobile-settings">
835
- <small className="cost-mobile-updated">{updatedLabel}</small>
836
- <button disabled={loading} onClick={onRefresh} type="button">
837
- ↻{" "}
838
- {loading
839
- ? locale === "pt-BR"
840
- ? "Atualizando…"
841
- : "Refreshing…"
842
- : text.refresh}
843
- </button>
844
- <label>
845
- <span>{locale === "pt-BR" ? "Idioma" : "Language"}</span>
846
- <select
847
- onChange={(event) => changeLocale(event.target.value as CostLocale)}
848
- value={locale}
849
- >
850
- <option value="pt-BR">Português do Brasil</option>
851
- <option value="en-US">English</option>
852
- </select>
853
- </label>
854
- <div className="cost-mobile-filters">
855
- <strong>{text.filters}</strong>
856
- <CostFilters
857
- available={data.filters_available}
858
- hideQuery={embedded}
859
- locale={locale}
860
- onChange={setFilters}
861
- value={filters}
862
- />
863
- </div>
864
- </div>
865
- <label>
866
- <span>{text.currency}</span>
867
- <select
868
- onChange={(event) => setCurrency(event.target.value as CostCurrency)}
869
- value={currency}
870
- >
871
- <option value="BRL">BRL · R$</option>
872
- <option value="USD">USD · $</option>
873
- </select>
874
- </label>
875
- </div>
876
- ) : null}
877
- </div>
878
- </div>
879
- </nav>
880
- <header className="cost-hero">
881
- <p className="cost-hero-description">{text.subtitle}</p>
882
- <label className="cost-period">
883
- <span>{text.period}</span>
884
- <select
885
- onChange={(event) => setPeriod(event.target.value as CostPeriod)}
886
- value={period}
887
- >
888
- <option value="all">{text.allTime}</option>
889
- <option value="7">{text.last7}</option>
890
- <option value="30">{text.last30}</option>
891
- <option value="90">{text.last90}</option>
892
- <option value="month">{text.thisMonth}</option>
893
- </select>
894
- </label>
895
- </header>
896
- </>
792
+ <CostMonitorNavigation
793
+ currency={currency}
794
+ filters={filters}
795
+ filtersAvailable={data.filters_available}
796
+ loading={loading}
797
+ locale={locale}
798
+ onCurrencyChange={setCurrency}
799
+ onFiltersChange={setFilters}
800
+ onLocaleChange={changeLocale}
801
+ onPopupChange={setNavigationPopup}
802
+ onPeriodChange={setPeriod}
803
+ onRefresh={onRefresh}
804
+ period={period}
805
+ popup={navigationPopup}
806
+ text={text}
807
+ updatedLabel={updatedLabel}
808
+ />
897
809
  )}
898
810
  {embedded ? null : (
899
811
  <>
@@ -922,7 +834,10 @@ export function CostMonitorPage({
922
834
  <ExcelExportAction
923
835
  filters={{ ...filters, ...globalFilters, period }}
924
836
  pageSource={
925
- compactExportSource ?? agentInvocationExportSource ?? costGroupExportSource
837
+ compactExportSource ??
838
+ agentInvocationExportSource ??
839
+ developerExportSource ??
840
+ costGroupExportSource
926
841
  }
927
842
  rows={exportRows as unknown as readonly Record<string, unknown>[]}
928
843
  view={`cost-${view}`}
@@ -944,9 +859,9 @@ export function CostMonitorPage({
944
859
  view === "developers" ? (
945
860
  <CostDeveloperAttributionPanel
946
861
  locale={locale}
947
- {...(runPageRange === undefined
862
+ {...(developerAttributionRange === undefined
948
863
  ? {}
949
- : { from: runPageRange.from, to: runPageRange.to })}
864
+ : { from: developerAttributionRange.from, to: developerAttributionRange.to })}
950
865
  />
951
866
  ) : null
952
867
  }
@@ -3,11 +3,13 @@ import {
3
3
  AGENT_INVOCATION_EXPORT_COLUMNS,
4
4
  COMPACT_LIFECYCLE_EXPORT_COLUMNS,
5
5
  COST_GROUP_EXPORT_COLUMNS,
6
+ DEVELOPER_EXPORT_COLUMNS,
6
7
  EXECUTION_EXPORT_COLUMNS,
7
8
  EXPORT_SUCCESS_TOAST_MS,
8
9
  exportAgentInvocationRows,
9
10
  exportCompactLifecycleRows,
10
11
  exportCostGroupRows,
12
+ exportDeveloperRows,
11
13
  exportExecutionRows,
12
14
  exportHeadersForView,
13
15
  exportPageSourceRows,
@@ -198,6 +200,63 @@ it("uses the compact work schema for Kanban and Work List", () => {
198
200
  ]);
199
201
  });
200
202
 
203
+ it("exports developer attribution with its factual schema and unavailable metrics intact", () => {
204
+ expect(DEVELOPER_EXPORT_COLUMNS).toEqual([
205
+ "account_id",
206
+ "login_snapshot",
207
+ "prd_count",
208
+ "spec_count",
209
+ "task_count",
210
+ "run_count",
211
+ "test_count",
212
+ "evidence_count",
213
+ "total_duration_hours",
214
+ "total_tokens",
215
+ "total_cost_brl",
216
+ "duration_covered_runs",
217
+ "token_covered_runs",
218
+ "brl_covered_runs",
219
+ ]);
220
+ expect(
221
+ exportDeveloperRows([
222
+ {
223
+ account_id: "42",
224
+ login_snapshot: "dev-42",
225
+ prd_count: 1,
226
+ spec_count: 2,
227
+ task_count: 3,
228
+ run_count: 4,
229
+ test_count: null,
230
+ evidence_count: null,
231
+ total_duration_ms: null,
232
+ total_tokens: null,
233
+ total_cost_brl: 1.5,
234
+ duration_covered_runs: 0,
235
+ token_covered_runs: 0,
236
+ brl_covered_runs: 4,
237
+ },
238
+ ]),
239
+ ).toEqual([
240
+ {
241
+ account_id: "42",
242
+ login_snapshot: "dev-42",
243
+ prd_count: 1,
244
+ spec_count: 2,
245
+ task_count: 3,
246
+ run_count: 4,
247
+ test_count: null,
248
+ evidence_count: null,
249
+ total_duration_hours: null,
250
+ total_tokens: null,
251
+ total_cost_brl: 1.5,
252
+ duration_covered_runs: 0,
253
+ token_covered_runs: 0,
254
+ brl_covered_runs: 4,
255
+ },
256
+ ]);
257
+ expect(exportHeadersForView("cost-developers", [])).toEqual(DEVELOPER_EXPORT_COLUMNS);
258
+ });
259
+
201
260
  it("normalizes Tasks and macros into the same populated work columns", () => {
202
261
  expect(
203
262
  exportWorkRows([
@@ -118,6 +118,26 @@ export const COST_GROUP_EXPORT_COLUMNS = [
118
118
  "duration_hours",
119
119
  ] as const;
120
120
 
121
+ // Cost Monitor developer attribution is a factual aggregate with coverage
122
+ // counters. Keep the workbook schema aligned with that table and retain null
123
+ // facts instead of allowing them to be inferred as zero by a spreadsheet.
124
+ export const DEVELOPER_EXPORT_COLUMNS = [
125
+ "account_id",
126
+ "login_snapshot",
127
+ "prd_count",
128
+ "spec_count",
129
+ "task_count",
130
+ "run_count",
131
+ "test_count",
132
+ "evidence_count",
133
+ "total_duration_hours",
134
+ "total_tokens",
135
+ "total_cost_brl",
136
+ "duration_covered_runs",
137
+ "token_covered_runs",
138
+ "brl_covered_runs",
139
+ ] as const;
140
+
121
141
  export const CONVERSATION_EXPORT_COLUMNS = [
122
142
  "conversation_id",
123
143
  "status",
@@ -334,6 +354,31 @@ export function exportCostGroupRows(
334
354
  });
335
355
  }
336
356
 
357
+ /** Normalize Cost Monitor developer facts into the fixed XLSX schema. */
358
+ export function exportDeveloperRows(
359
+ rows: readonly Record<string, unknown>[],
360
+ ): Record<string, unknown>[] {
361
+ return rows.map((row) => {
362
+ const durationMs = numberValue(row.total_duration_ms);
363
+ return {
364
+ account_id: row.account_id ?? null,
365
+ login_snapshot: row.login_snapshot ?? null,
366
+ prd_count: numberValue(row.prd_count),
367
+ spec_count: numberValue(row.spec_count),
368
+ task_count: numberValue(row.task_count),
369
+ run_count: numberValue(row.run_count),
370
+ test_count: numberValue(row.test_count),
371
+ evidence_count: numberValue(row.evidence_count),
372
+ total_duration_hours: durationMs === null ? null : durationMs / 3_600_000,
373
+ total_tokens: numberValue(row.total_tokens),
374
+ total_cost_brl: numberValue(row.total_cost_brl),
375
+ duration_covered_runs: numberValue(row.duration_covered_runs),
376
+ token_covered_runs: numberValue(row.token_covered_runs),
377
+ brl_covered_runs: numberValue(row.brl_covered_runs),
378
+ };
379
+ });
380
+ }
381
+
337
382
  export function exportConversationRows(
338
383
  rows: readonly Record<string, unknown>[],
339
384
  ): Record<string, unknown>[] {
@@ -480,6 +525,7 @@ export function exportHeadersForView(
480
525
  const isCompactLifecycleView =
481
526
  /-(?:tasks|specs|prds|conversations)$/.test(view) && !isOperationsConversationView;
482
527
  const isCostGroupView = /-(?:agents|models|providers)$/.test(view);
528
+ const isDeveloperView = view === "cost-developers";
483
529
  const isChangeView = view === "operations-changes";
484
530
  const isDecisionView = view === "operations-decisions";
485
531
  if (isPeriodView) headers.push(...PERIOD_EXPORT_COLUMNS);
@@ -489,6 +535,7 @@ export function exportHeadersForView(
489
535
  if (isAgentInvocationView) headers.push(...AGENT_INVOCATION_EXPORT_COLUMNS);
490
536
  if (isCompactLifecycleView) headers.push(...COMPACT_LIFECYCLE_EXPORT_COLUMNS);
491
537
  if (isCostGroupView) headers.push(...COST_GROUP_EXPORT_COLUMNS);
538
+ if (isDeveloperView) headers.push(...DEVELOPER_EXPORT_COLUMNS);
492
539
  if (isOperationsConversationView) headers.push(...CONVERSATION_EXPORT_COLUMNS);
493
540
  if (isChangeView) headers.push(...CHANGE_EXPORT_COLUMNS);
494
541
  if (isDecisionView) headers.push(...DECISION_EXPORT_COLUMNS);
@@ -502,6 +549,7 @@ export function exportHeadersForView(
502
549
  isAgentInvocationView ||
503
550
  isCompactLifecycleView ||
504
551
  isCostGroupView ||
552
+ isDeveloperView ||
505
553
  isOperationsConversationView ||
506
554
  isChangeView ||
507
555
  isDecisionView;
@@ -545,6 +593,7 @@ export function ExcelExportAction({ filters, pageSource, rows, view }: ExcelExpo
545
593
  const isCompactLifecycleView =
546
594
  /-(?:tasks|specs|prds|conversations)$/.test(view) && !isOperationsConversationView;
547
595
  const isCostGroupView = /-(?:agents|models|providers)$/.test(view);
596
+ const isDeveloperView = view === "cost-developers";
548
597
  const isChangeView = view === "operations-changes";
549
598
  const isDecisionView = view === "operations-decisions";
550
599
  const createSnapshot = async () => {
@@ -584,7 +633,9 @@ export function ExcelExportAction({ filters, pageSource, rows, view }: ExcelExpo
584
633
  ? exportCompactLifecycleRows
585
634
  : isCostGroupView
586
635
  ? exportCostGroupRows
587
- : exportRowsWithDurationHours,
636
+ : isDeveloperView
637
+ ? exportDeveloperRows
638
+ : exportRowsWithDurationHours,
588
639
  );
589
640
  return;
590
641
  }
@@ -610,7 +661,9 @@ export function ExcelExportAction({ filters, pageSource, rows, view }: ExcelExpo
610
661
  ? exportCompactLifecycleRows
611
662
  : isCostGroupView
612
663
  ? exportCostGroupRows
613
- : exportRowsWithDurationHours
664
+ : isDeveloperView
665
+ ? exportDeveloperRows
666
+ : exportRowsWithDurationHours
614
667
  )(rows.slice(offset, offset + 500));
615
668
  },
616
669
  });