@zaip-ai/cli 3.1.0 → 3.3.0

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 (111) hide show
  1. package/README.md +11 -5
  2. package/dist/commands/init.d.ts.map +1 -1
  3. package/dist/commands/init.js +16 -6
  4. package/dist/commands/init.js.map +1 -1
  5. package/dist/services/adapters.d.ts +1 -1
  6. package/dist/services/adapters.d.ts.map +1 -1
  7. package/dist/services/adapters.js +2 -1
  8. package/dist/services/adapters.js.map +1 -1
  9. package/dist/services/github-setup-status.d.ts +12 -0
  10. package/dist/services/github-setup-status.d.ts.map +1 -0
  11. package/dist/services/github-setup-status.js +84 -0
  12. package/dist/services/github-setup-status.js.map +1 -0
  13. package/dist/services/github-setup-status.test.d.ts +2 -0
  14. package/dist/services/github-setup-status.test.d.ts.map +1 -0
  15. package/dist/services/github-setup-status.test.js +57 -0
  16. package/dist/services/github-setup-status.test.js.map +1 -0
  17. package/dist/services/logger.d.ts +10 -1
  18. package/dist/services/logger.d.ts.map +1 -1
  19. package/dist/services/logger.js +39 -6
  20. package/dist/services/logger.js.map +1 -1
  21. package/dist/services/logger.test.js +50 -5
  22. package/dist/services/logger.test.js.map +1 -1
  23. package/dist/types.d.ts +3 -0
  24. package/dist/types.d.ts.map +1 -1
  25. package/package.json +2 -2
  26. package/templates/.agents/README-ENG.md +2 -1
  27. package/templates/.agents/README.md +2 -1
  28. package/templates/.agents/config/agents/catalog.yaml +1 -0
  29. package/templates/.agents/config/agents/discovery.json +2 -2
  30. package/templates/.agents/config/clients/codex/agents/zaip-agent-builder.toml +2 -2
  31. package/templates/.agents/dashboard/.zaip-managed.json +60 -52
  32. package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-CEDZjl4V.js → CostCalculationGuidePage-BBFDrn64.js} +1 -1
  33. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-DIWDmvAS.js → DashboardHelpDialog-Cd-qo9w7.js} +4 -3
  34. package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-BBRBKaHa.js → PricingSettingsDialog-J7L5m8lk.js} +1 -1
  35. package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-BAvLPAee.js → ProjectSettingsPage-BpsAFy0g.js} +1 -1
  36. package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-C9dDhj34.js → TraceabilityWorkspace-BzVtGLWS.js} +1 -1
  37. package/templates/.agents/dashboard/apps/frontend/dist/assets/{cost-monitor-06__WZco.js → cost-monitor--iPcnZdx.js} +1 -1
  38. package/templates/.agents/dashboard/apps/frontend/dist/assets/{dashboard-localization-CoAVMZN6.js → dashboard-localization-jvdDfNKs.js} +2 -1
  39. package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-DEmo6Tn2.js → icons-vendor-CWzf_RiP.js} +1 -1
  40. package/templates/.agents/dashboard/apps/frontend/dist/assets/{index-BVzw_Wux.js → index-DfdlGa7c.js} +2 -2
  41. package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-CadYsm83.js → react-vendor-CtHdbZk3.js} +1 -1
  42. package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-BzKK8kK3.js → traceability-BKrDH2TR.js} +1 -1
  43. package/templates/.agents/dashboard/apps/frontend/dist/index.html +5 -5
  44. package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
  45. package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
  46. package/templates/.agents/dashboard/modules/operations/package.json +1 -1
  47. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.helpers.tsx +4 -1
  48. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.test.ts +5 -0
  49. package/templates/.agents/dashboard/modules/operations/src/react/export/xlsx-runtime-e2e.test.ts +55 -31
  50. package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
  51. package/templates/.agents/dashboard/packages/branding/package.json +1 -1
  52. package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
  53. package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
  54. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +186 -166
  55. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +135 -115
  56. package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
  57. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +187 -167
  58. package/templates/.agents/dashboard/packages/local-runtime/src/server-period-macro-metric.test.ts +19 -0
  59. package/templates/.agents/dashboard/packages/local-runtime/src/server-read-projections.ts +3 -3
  60. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-first-fold-cache.test.ts +94 -0
  61. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-first-fold-cache.ts +32 -1
  62. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-force-canonical.test.ts +58 -3
  63. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-operations-projection.ts +2 -0
  64. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-counts-every-root-execution-day-returns.test.ts +13 -0
  65. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-keeps-orphan-telemetry-out-kanban-without.test.ts +2 -1
  66. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-schema.ts +15 -2
  67. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +2 -1
  68. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-schema-dashboard-views.ts +20 -2
  69. package/templates/.agents/dashboard/packages/ui/package.json +1 -1
  70. package/templates/.agents/dashboard/scripts/qualify-macro-overview.mjs +50 -0
  71. package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
  72. package/templates/.agents/design/.zaip-managed.json +3 -3
  73. package/templates/.agents/design/apps/catalog/package.json +1 -1
  74. package/templates/.agents/design/packages/local-runtime/package.json +1 -1
  75. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  76. package/templates/.agents/requirements/cloud-hostinger-autonomy.md +1 -1
  77. package/templates/.agents/requirements/comparative-audit.md +1 -1
  78. package/templates/.agents/requirements/context-engine.md +1 -1
  79. package/templates/.agents/requirements/cost-observability.md +1 -1
  80. package/templates/.agents/requirements/dashboard-launch.md +1 -1
  81. package/templates/.agents/requirements/data-export.md +1 -1
  82. package/templates/.agents/requirements/design-system.md +1 -1
  83. package/templates/.agents/requirements/execution-engine.md +1 -1
  84. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  85. package/templates/.agents/requirements/model-orchestration.md +1 -1
  86. package/templates/.agents/requirements/public-distribution.md +17 -2
  87. package/templates/.agents/requirements/software-architecture.md +1 -1
  88. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  89. package/templates/.agents/requirements/work-lifecycle.md +1 -1
  90. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  91. package/templates/.agents/runtime/VERSION +1 -1
  92. package/templates/.agents/runtime/bin/zaip.mjs +13 -12
  93. package/templates/.agents/runtime/checksums.sha256 +5 -5
  94. package/templates/.agents/runtime/manifest.json +1 -1
  95. package/templates/.agents/runtime/provenance.json +4 -4
  96. package/templates/.agents/runtime/sbom.cdx.json +1 -1
  97. package/templates/.agents/skills/zaip-aios/SKILL.md +0 -1
  98. package/templates/.agents/skills/zaip-skill-author-skills/scripts/audit-skill-catalog.mjs +1 -0
  99. package/templates/.agents/skills/zaip-skill-config/SKILL.md +12 -0
  100. package/templates/.agents/skills/zaip-skill-github-setup/SKILL.md +7 -1
  101. package/templates/.agents/skills/zaip-skill-release-qualification/SKILL.md +4 -0
  102. package/templates/.agents/skills/zaip-skill-setup/SKILL.md +36 -2
  103. package/templates/.agents/skills/zaip-skill-setup/agents/openai.yaml +2 -2
  104. package/templates/.agents/skills/zaip-skill-update-version-doc/SKILL.md +39 -0
  105. package/templates/.agents/skills/zaip-skill-update-version-doc/agents/openai.yaml +4 -0
  106. package/templates/.agents/skills/zaip-skill-update-version-doc/scripts/update-version-doc.mjs +21 -0
  107. package/templates/.agents/skills/zaip-skill-update-version-doc/scripts/update-version-doc.test.mjs +32 -0
  108. package/templates/.agents/zaip-aios/README.md +2 -1
  109. package/templates/.agents/zaip-aios/VERSION +1 -1
  110. package/templates/.agents/zaip-aios/guides/20-instalacao-detalhada.md +26 -1
  111. package/templates/template-manifest.json +120 -96
@@ -41,4 +41,23 @@ describe("periodMacroMetric", () => {
41
41
  [expectedStage]: 1,
42
42
  });
43
43
  });
44
+
45
+ it("keeps the canonical Task and rework classification for the filtered macro population", () => {
46
+ const specs = periodMacroMetric(
47
+ [
48
+ { id: "SPEC-STRUCTURED", status: "active", task_count: 2, rework_run_count: 0 },
49
+ { id: "SPEC-EMPTY", status: "blocked", task_count: 0, rework_run_count: 0 },
50
+ ],
51
+ "spec",
52
+ );
53
+ const prds = periodMacroMetric(
54
+ [{ id: "PRD-REWORK", status: "delivered", task_count: 3, rework_run_count: 1 }],
55
+ "prd",
56
+ );
57
+
58
+ expect(specs.status_counts).toEqual({ executing: 1, blocked: 1 });
59
+ expect(specs.classification_counts).toEqual({ empty: 1, structured: 1, rework: 0 });
60
+ expect(prds.status_counts).toEqual({ completed: 1 });
61
+ expect(prds.classification_counts).toEqual({ empty: 0, structured: 0, rework: 1 });
62
+ });
44
63
  });
@@ -308,11 +308,11 @@ export function buildSqliteOperationsProjection(
308
308
  },
309
309
  task_count: taskCount,
310
310
  completed_prd_count:
311
- allTime?.aggregate.completed_prd_count ??
312
- prds.filter((row) => row.status === "completed").length,
311
+ allTime?.aggregate.completed_prd_count ?? macroMetrics.prds.status_counts.completed ?? 0,
313
312
  completed_spec_count:
314
313
  allTime?.aggregate.completed_spec_count ??
315
- specs.filter((row) => row.status === "completed").length,
314
+ macroMetrics.specs.status_counts.completed ??
315
+ 0,
316
316
  completed_task_count: allTime?.aggregate.completed_task_count ?? statuses.completed ?? 0,
317
317
  task_status_counts: statuses,
318
318
  task_risk_counts: risks,
@@ -0,0 +1,94 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import { describe, expect, it } from "vitest";
3
+ import {
4
+ hasReconciledMacroDistributions,
5
+ readFirstFoldAllTime,
6
+ } from "./sqlite-first-fold-cache.js";
7
+ import type { SqliteFirstFoldAllTime } from "./sqlite-read-model-first-fold-types.js";
8
+
9
+ function cachedFirstFold(
10
+ specs: { entityCount: number; statusCount: number; classificationCount: number },
11
+ prds: { entityCount: number; statusCount: number; classificationCount: number },
12
+ ) {
13
+ const macro = (item: {
14
+ entityCount: number;
15
+ statusCount: number;
16
+ classificationCount: number;
17
+ }) => ({
18
+ entity_count: item.entityCount,
19
+ status_counts: { executing: item.statusCount },
20
+ classification_counts: { structured: item.classificationCount },
21
+ });
22
+ return {
23
+ macro_metrics: { specs: macro(specs), prds: macro(prds) },
24
+ } as unknown as SqliteFirstFoldAllTime;
25
+ }
26
+
27
+ describe("first-fold all-time cache", () => {
28
+ it("rejects a legacy macro cache that would render zero panels beside populated cards", () => {
29
+ expect(
30
+ hasReconciledMacroDistributions(
31
+ cachedFirstFold(
32
+ { entityCount: 66, statusCount: 0, classificationCount: 0 },
33
+ { entityCount: 16, statusCount: 0, classificationCount: 0 },
34
+ ),
35
+ ),
36
+ ).toBe(false);
37
+ });
38
+
39
+ it("accepts fully reconciled SPEC and PRD panels", () => {
40
+ expect(
41
+ hasReconciledMacroDistributions(
42
+ cachedFirstFold(
43
+ { entityCount: 66, statusCount: 66, classificationCount: 66 },
44
+ { entityCount: 16, statusCount: 16, classificationCount: 16 },
45
+ ),
46
+ ),
47
+ ).toBe(true);
48
+ });
49
+
50
+ it("rejects malformed legacy count maps before summing them", () => {
51
+ const legacy = cachedFirstFold(
52
+ { entityCount: 1, statusCount: 1, classificationCount: 1 },
53
+ { entityCount: 1, statusCount: 1, classificationCount: 1 },
54
+ ) as unknown as { macro_metrics: { specs: { status_counts: unknown } } };
55
+ legacy.macro_metrics.specs.status_counts = undefined;
56
+
57
+ expect(hasReconciledMacroDistributions(legacy as unknown as SqliteFirstFoldAllTime)).toBe(
58
+ false,
59
+ );
60
+ });
61
+
62
+ it("recomputes a persisted legacy payload whose macro panels are missing", () => {
63
+ const database = new DatabaseSync(":memory:");
64
+ try {
65
+ database.exec(`
66
+ CREATE TABLE zaip_aios_activities (payload_type TEXT, payload_json TEXT, recorded_at TEXT);
67
+ INSERT INTO zaip_aios_activities VALUES ('first_fold_all_time_cache', '{"aggregate":{}}', '2026-09-15T00:00:00.000Z');
68
+ CREATE VIEW zaip_vw_first_fold_overview_all_time AS SELECT 0 AS task_count, 0 AS completed_task_count, 1 AS spec_count, 0 AS completed_spec_count, 1 AS prd_count, 0 AS completed_prd_count, 0 AS total_run_count, 0 AS agent_invocation_count, NULL AS llm_call_count, NULL AS total_duration_ms, NULL AS total_tokens, NULL AS total_cost_usd, NULL AS total_cost_brl, 0 AS execution_day_count, NULL AS latest_task_created_at, NULL AS start_day, NULL AS end_day;
69
+ CREATE VIEW zaip_vw_first_fold_task_status_all_time AS SELECT 'draft' AS status, 0 AS task_count WHERE 0;
70
+ CREATE VIEW zaip_vw_first_fold_task_risk_all_time AS SELECT 'standard' AS risk, 0 AS task_count WHERE 0;
71
+ CREATE VIEW zaip_vw_first_fold_top_tasks_all_time AS SELECT 1 AS rank WHERE 0;
72
+ CREATE VIEW zaip_vw_first_fold_task_metrics_all_time AS SELECT NULL AS average_duration_ms_per_task, NULL AS average_tokens_per_task, NULL AS average_cost_usd_per_task, NULL AS average_cost_brl_per_task, NULL AS latest_task_created_at;
73
+ CREATE VIEW zaip_vw_first_fold_spec_status_all_time AS SELECT 'executing' AS stage, 1 AS entity_count;
74
+ CREATE VIEW zaip_vw_first_fold_prd_status_all_time AS SELECT 'executing' AS stage, 1 AS entity_count;
75
+ CREATE VIEW zaip_vw_first_fold_spec_classification_all_time AS SELECT 'structured' AS classification, 1 AS entity_count;
76
+ CREATE VIEW zaip_vw_first_fold_prd_classification_all_time AS SELECT 'structured' AS classification, 1 AS entity_count;
77
+ CREATE VIEW zaip_vw_first_fold_spec_metrics_all_time AS SELECT 1 AS entity_count, 0 AS total_run_count, NULL AS total_duration_ms, NULL AS total_tokens, NULL AS total_cost_usd, NULL AS total_cost_brl, NULL AS average_duration_ms, NULL AS average_tokens, NULL AS average_cost_usd, NULL AS average_cost_brl, NULL AS latest_created_at;
78
+ CREATE VIEW zaip_vw_first_fold_prd_metrics_all_time AS SELECT 1 AS entity_count, 0 AS total_run_count, NULL AS total_duration_ms, NULL AS total_tokens, NULL AS total_cost_usd, NULL AS total_cost_brl, NULL AS average_duration_ms, NULL AS average_tokens, NULL AS average_cost_usd, NULL AS average_cost_brl, NULL AS latest_created_at;
79
+ CREATE VIEW zaip_vw_first_fold_top_specs_all_time AS SELECT 1 AS rank WHERE 0;
80
+ CREATE VIEW zaip_vw_first_fold_top_prds_all_time AS SELECT 1 AS rank WHERE 0;
81
+ `);
82
+ const rebuilt = readFirstFoldAllTime(database, () => 0);
83
+
84
+ expect(rebuilt.macro_metrics.specs).toMatchObject({
85
+ entity_count: 1,
86
+ status_counts: { executing: 1 },
87
+ classification_counts: { structured: 1 },
88
+ });
89
+ expect(rebuilt.macro_metrics.prds.entity_count).toBe(1);
90
+ } finally {
91
+ database.close();
92
+ }
93
+ });
94
+ });
@@ -24,13 +24,44 @@ function persisted<T>(database: DatabaseSync, payloadType: string): T | null {
24
24
  }
25
25
  }
26
26
 
27
+ /** Reject legacy first-fold payloads that have card totals but no matching macro panels. */
28
+ export function hasReconciledMacroDistributions(value: SqliteFirstFoldAllTime): boolean {
29
+ return (["specs", "prds"] as const).every((kind) => {
30
+ const metric = value.macro_metrics?.[kind];
31
+ if (
32
+ metric === null ||
33
+ metric === undefined ||
34
+ !Number.isSafeInteger(metric.entity_count) ||
35
+ metric.entity_count < 0 ||
36
+ !isCountMap(metric.status_counts) ||
37
+ !isCountMap(metric.classification_counts)
38
+ )
39
+ return false;
40
+ const sum = (counts: Record<string, number>) =>
41
+ Object.values(counts).reduce((total, count) => total + count, 0);
42
+ return (
43
+ sum(metric.status_counts) === metric.entity_count &&
44
+ sum(metric.classification_counts) === metric.entity_count
45
+ );
46
+ });
47
+ }
48
+
49
+ function isCountMap(value: unknown): value is Record<string, number> {
50
+ return (
51
+ value !== null &&
52
+ typeof value === "object" &&
53
+ !Array.isArray(value) &&
54
+ Object.values(value).every((count) => Number.isSafeInteger(count) && count >= 0)
55
+ );
56
+ }
57
+
27
58
  export function readFirstFoldAllTime(
28
59
  database: DatabaseSync,
29
60
  dailyAggregateCount: () => number,
30
61
  viewport?: SqliteFirstFoldViewport,
31
62
  ): SqliteFirstFoldAllTime {
32
63
  const cached = persisted<SqliteFirstFoldAllTime>(database, "first_fold_all_time_cache");
33
- if (cached !== null) return cached;
64
+ if (cached !== null && hasReconciledMacroDistributions(cached)) return cached;
34
65
  const timed = <T>(phase: string, action: () => T) => measureFirstFoldQuery(phase, action);
35
66
  const row =
36
67
  viewport === undefined
@@ -2,7 +2,9 @@ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
2
2
  import { join, resolve } from "node:path";
3
3
  import { DatabaseSync } from "node:sqlite";
4
4
  import { afterEach, describe, expect, it, vi } from "vitest";
5
- import { rebuildSqliteReadModel } from "./sqlite-read-model.js";
5
+ import { buildSqliteOperationsProjection } from "./server-read-projections.js";
6
+ import { rebuildSqliteReadModel, SqliteReadModelFileSystem } from "./sqlite-read-model.js";
7
+ import { readModelSchemaVersion } from "./sqlite-read-model-schema.js";
6
8
 
7
9
  const roots: string[] = [];
8
10
 
@@ -22,6 +24,37 @@ afterEach(async () => {
22
24
  });
23
25
 
24
26
  describe("forceCanonical SQLite refresh", () => {
27
+ it("keeps filtered completed cards consistent with legacy delivered macro statuses", async () => {
28
+ const root = await fixture();
29
+ for (const kind of ["spec", "prd"]) {
30
+ const directory = join(root, `.agents/records/${kind}s/2026/08`);
31
+ await mkdir(directory, { recursive: true });
32
+ await writeFile(
33
+ join(directory, `${kind.toUpperCase()}-DELIVERED.md`),
34
+ `---\nid: ${kind.toUpperCase()}-DELIVERED\ntitle: Delivered macro\nstatus: delivered\ncreated_at: 2026-08-03T12:00:00.000Z\n---\n`,
35
+ );
36
+ }
37
+ await rebuildSqliteReadModel(root);
38
+ const filesystem = new SqliteReadModelFileSystem(root);
39
+ try {
40
+ const projection = buildSqliteOperationsProjection(
41
+ filesystem,
42
+ { version: 3 } as Parameters<typeof buildSqliteOperationsProjection>[1],
43
+ { from: "2026-08-01", to: "2026-08-31" },
44
+ {},
45
+ );
46
+ expect(projection.summary.overview_metrics).toMatchObject({
47
+ completed_spec_count: 1,
48
+ completed_prd_count: 1,
49
+ macro_metrics: {
50
+ specs: { status_counts: { completed: 1 } },
51
+ prds: { status_counts: { completed: 1 } },
52
+ },
53
+ });
54
+ } finally {
55
+ filesystem.close();
56
+ }
57
+ });
25
58
  it("bypasses a healthy generation and verifies canonical sources again", async () => {
26
59
  const root = await fixture();
27
60
  vi.spyOn(Date, "now").mockReturnValue(Date.now() + 5_000);
@@ -37,14 +70,20 @@ describe("forceCanonical SQLite refresh", () => {
37
70
  });
38
71
  });
39
72
 
40
- it("rebuilds a generation whose persisted schema marker is incompatible", async () => {
73
+ it("upgrades an old physical projection when the canonical sources are unchanged", async () => {
41
74
  const root = await fixture();
42
75
  const initial = await rebuildSqliteReadModel(root);
43
76
  const database = new DatabaseSync(initial.path);
44
77
  try {
78
+ // Simulate the pre-80 cache that predates macro classification fields.
79
+ // The source fingerprint remains valid, so only the schema contract can
80
+ // cause the next ordinary refresh to rebuild this disposable projection.
81
+ database.exec(
82
+ "DROP TABLE operations_work_projection; CREATE TABLE operations_work_projection (id TEXT);",
83
+ );
45
84
  database
46
85
  .prepare(
47
- "UPDATE zaip_aios_activities SET payload_json=json_set(payload_json, '$.value', 'invalid') WHERE payload_type='metadata' AND activity_key='metadata:schema_version'",
86
+ "UPDATE zaip_aios_activities SET payload_json=json_set(payload_json, '$.value', '79') WHERE payload_type='metadata' AND activity_key='metadata:schema_version'",
48
87
  )
49
88
  .run();
50
89
  } finally {
@@ -54,5 +93,21 @@ describe("forceCanonical SQLite refresh", () => {
54
93
  cache: "rebuilt",
55
94
  path: initial.path,
56
95
  });
96
+ const upgraded = new DatabaseSync(initial.path, { readOnly: true });
97
+ try {
98
+ expect(upgraded.prepare("PRAGMA table_info(operations_work_projection)").all()).toEqual(
99
+ expect.arrayContaining([
100
+ expect.objectContaining({ name: "task_count" }),
101
+ expect.objectContaining({ name: "rework_run_count" }),
102
+ ]),
103
+ );
104
+ expect(
105
+ upgraded
106
+ .prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key='schema_version'")
107
+ .get(),
108
+ ).toEqual({ value: readModelSchemaVersion });
109
+ } finally {
110
+ upgraded.close();
111
+ }
57
112
  });
58
113
  });
@@ -22,6 +22,8 @@ export function materializeOperationsWorkProjection(database: DatabaseSync): voi
22
22
  JOIN resolved canonical ON canonical.entity_kind=summary.entity_kind AND canonical.id=summary.id
23
23
  LEFT JOIN changes change ON change.id=canonical.resolved_change_id`;
24
24
  database.exec(`
25
+ -- Schema compatibility is checked before incremental refresh. Preserve
26
+ -- this table and its indexes; only replace the projected rows here.
25
27
  CREATE TABLE IF NOT EXISTS operations_work_projection AS ${select} LIMIT 0;
26
28
  DELETE FROM operations_work_projection;
27
29
  INSERT INTO operations_work_projection ${select};
@@ -262,6 +262,19 @@ describe("SQLite dashboard read model", () => {
262
262
  total_cost_brl: null,
263
263
  }),
264
264
  );
265
+ // Period-scoped macro cards are read from operations_work_projection.
266
+ // Keep structural classification fields in that physical projection; a
267
+ // missing column would make every selected SPEC/PRD appear empty.
268
+ expect(
269
+ filesystem.canonicalWorkPage("spec", { from: null, to: null }, 0, 50).rows,
270
+ ).toContainEqual(
271
+ expect.objectContaining({ id: "SPEC-NO-RUN", task_count: 0, rework_run_count: 0 }),
272
+ );
273
+ expect(
274
+ filesystem.canonicalWorkPage("prd", { from: null, to: null }, 0, 50).rows,
275
+ ).toContainEqual(
276
+ expect.objectContaining({ id: "PRD-NO-RUN", task_count: 0, rework_run_count: 0 }),
277
+ );
265
278
  const allTime = filesystem.firstFoldAllTime();
266
279
  expectPersistedFirstFoldCaches(root, allTime, filesystem.firstFoldBoardAllTime());
267
280
  expect(allTime.aggregate).toMatchObject({
@@ -4,6 +4,7 @@ import { join, resolve } from "node:path";
4
4
  import { DatabaseSync } from "node:sqlite";
5
5
  import { afterEach, describe, expect, it, vi } from "vitest";
6
6
  import { rebuildSqliteReadModel, SqliteReadModelFileSystem } from "./sqlite-read-model.js";
7
+ import { readModelSchemaVersion } from "./sqlite-read-model-schema.js";
7
8
 
8
9
  const roots: string[] = [];
9
10
  const sqliteTestTempRoot = join(
@@ -210,7 +211,7 @@ describe("SQLite dashboard read model", () => {
210
211
  snapshot
211
212
  .prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key='schema_version'")
212
213
  .get(),
213
- ).toEqual({ value: "79" });
214
+ ).toEqual({ value: readModelSchemaVersion });
214
215
  } finally {
215
216
  snapshot.close();
216
217
  }
@@ -4,9 +4,9 @@ import { DatabaseSync } from "node:sqlite";
4
4
  import { hasRequiredFirstFoldPayloads } from "./sqlite-first-fold-persistence.js";
5
5
  import type { SqliteReadModelSourceCheckpoint } from "./sqlite-read-model-source-fingerprint.js";
6
6
  export const dbRelativePath = ".local/sqlite/zaip-aios-dashboard.sqlite";
7
- // Schema 79 installs bounded execution/conversation label views and shared daily groups.
7
+ // Schema 80 requires the persisted macro classification fields used by period views.
8
8
  // Old snapshots must rebuild even when their canonical hashes still match.
9
- export const readModelSchemaVersion = "79";
9
+ export const readModelSchemaVersion = "80";
10
10
  export const requiredPersistedTables = [
11
11
  "zaip_aios_activities",
12
12
  "zaip_dim_llm_model",
@@ -151,6 +151,19 @@ export function validateSqliteReadModelSchema(path: string): {
151
151
  throw new Error(
152
152
  `SQLite schema ${readModelSchemaVersion} requires the complete persisted table contract; found ${tables.join(", ") || "none"}`,
153
153
  );
154
+ const projectionColumns = database
155
+ .prepare("PRAGMA table_info(operations_work_projection)")
156
+ .all() as Array<{ name?: unknown }>;
157
+ const projectionColumnNames = new Set(
158
+ projectionColumns.flatMap((column) => (typeof column.name === "string" ? [column.name] : [])),
159
+ );
160
+ const missingProjectionColumns = ["task_count", "rework_run_count"].filter(
161
+ (column) => !projectionColumnNames.has(column),
162
+ );
163
+ if (missingProjectionColumns.length > 0)
164
+ throw new Error(
165
+ `SQLite schema ${readModelSchemaVersion} requires operations_work_projection columns: ${missingProjectionColumns.join(", ")}`,
166
+ );
154
167
  const names = new Set(objects.map((object) => object.name));
155
168
  const missingViews = requiredViews.filter((view) => !names.has(view));
156
169
  if (missingViews.length > 0)
@@ -8,6 +8,7 @@ import {
8
8
  SqliteReadModelFileSystem,
9
9
  sourceFingerprint,
10
10
  } from "./sqlite-read-model.js";
11
+ import { readModelSchemaVersion } from "./sqlite-read-model-schema.js";
11
12
 
12
13
  const roots: string[] = [];
13
14
  const sqliteTestTempRoot = join(
@@ -177,7 +178,7 @@ describe("SQLite dashboard read model", () => {
177
178
  metadata
178
179
  .prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key='schema_version'")
179
180
  .get(),
180
- ).toEqual({ value: "79" });
181
+ ).toEqual({ value: readModelSchemaVersion });
181
182
  } finally {
182
183
  metadata.close();
183
184
  }
@@ -49,13 +49,31 @@ export const dashboardViewSql = ` CREATE VIEW vw_operations_work_list AS
49
49
  ), durations AS (
50
50
  SELECT entity_kind, entity_id, ROUND(SUM(duration_ms)) AS total_duration
51
51
  FROM entity_activity GROUP BY entity_kind, entity_id
52
+ ), macro_classification AS (
53
+ -- These are structural macro facts. The narrowed-period first
54
+ -- fold filters the selected macros by activity, then retains their
55
+ -- canonical Task/rework classification instead of treating the
56
+ -- compact work-list row as an empty delivery.
57
+ SELECT 'spec' AS entity_kind, spec_id AS entity_id,
58
+ COUNT(DISTINCT task_id) AS task_count,
59
+ SUM(CASE WHEN work_type='rework' AND run_role='root' THEN 1 ELSE 0 END) AS rework_run_count
60
+ FROM activities WHERE spec_id IS NOT NULL GROUP BY spec_id
61
+ UNION ALL
62
+ SELECT 'prd' AS entity_kind, prd_id AS entity_id,
63
+ COUNT(DISTINCT task_id) AS task_count,
64
+ SUM(CASE WHEN work_type='rework' AND run_role='root' THEN 1 ELSE 0 END) AS rework_run_count
65
+ FROM activities WHERE prd_id IS NOT NULL GROUP BY prd_id
52
66
  )
53
67
  SELECT w.entity_kind, w.id, w.title, w.status, w.risk, w.occurred_at, w.occurred_day_sp,
54
68
  f.invocation_count, f.llm_call_count, d.total_duration,
55
- f.total_tokens, f.total_cost_usd, f.total_cost_brl
69
+ f.total_tokens, f.total_cost_usd, f.total_cost_brl,
70
+ COALESCE(classification.task_count, 0) AS task_count,
71
+ COALESCE(classification.rework_run_count, 0) AS rework_run_count
56
72
  FROM work_items w
57
73
  LEFT JOIN financial f ON f.entity_kind=w.entity_kind AND f.entity_id=w.id
58
- LEFT JOIN durations d ON d.entity_kind=w.entity_kind AND d.entity_id=w.id;
74
+ LEFT JOIN durations d ON d.entity_kind=w.entity_kind AND d.entity_id=w.id
75
+ LEFT JOIN macro_classification classification
76
+ ON classification.entity_kind=w.entity_kind AND classification.entity_id=w.id;
59
77
  -- Kanban is a lifecycle surface: it lists only canonical records.
60
78
  -- Telemetry-only identifiers remain Cost Monitor facts but cannot
61
79
  -- create a synthetic operational card.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zaip-ai/dashboard-ui",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -0,0 +1,50 @@
1
+ import assert from "node:assert/strict";
2
+ import { spawnSync } from "node:child_process";
3
+ import { existsSync } from "node:fs";
4
+ import { dirname, join, resolve } from "node:path";
5
+
6
+ const root = resolve(import.meta.dirname, "../../..");
7
+ const mode = process.argv[2];
8
+ assert(["test", "check", "integration"].includes(mode), "Expected test|check|integration");
9
+ function run(args, cwd = root) {
10
+ const result = spawnSync(process.execPath, args, {
11
+ cwd,
12
+ windowsHide: true,
13
+ stdio: "inherit",
14
+ timeout: 300_000,
15
+ env: { ...process.env, CI: "true" },
16
+ });
17
+ if (result.error) throw result.error;
18
+ assert.equal(result.status, 0, `Check failed: ${args.join(" ")}`);
19
+ }
20
+
21
+ if (!existsSync(join(root, "node_modules/vitest/vitest.mjs"))) {
22
+ run([
23
+ join(dirname(process.execPath), "node_modules/corepack/dist/pnpm.js"),
24
+ "install",
25
+ "--offline",
26
+ "--frozen-lockfile",
27
+ "--ignore-scripts",
28
+ ]);
29
+ }
30
+ const runtime = ".agents/dashboard/packages/local-runtime";
31
+ const operations = ".agents/dashboard/modules/operations";
32
+ if (mode === "check") {
33
+ for (const directory of [runtime, operations]) {
34
+ run([join(root, "node_modules/typescript/bin/tsc"), "--noEmit"], join(root, directory));
35
+ }
36
+ } else {
37
+ const tests =
38
+ mode === "test"
39
+ ? [
40
+ `${runtime}/src/sqlite-first-fold-cache.test.ts`,
41
+ `${runtime}/src/server-period-macro-metric.test.ts`,
42
+ `${operations}/src/react/OperationsOverview.test.ts`,
43
+ ]
44
+ : [
45
+ `${runtime}/src/sqlite-force-canonical.test.ts`,
46
+ `${runtime}/src/sqlite-read-model-counts-every-root-execution-day-returns.test.ts`,
47
+ `${runtime}/src/server-overview.test.ts`,
48
+ ];
49
+ run([join(root, "node_modules/vitest/vitest.mjs"), "run", ...tests]);
50
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zaip-ai/dashboard-e2e",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "files": [
5
5
  {
6
6
  "path": "DESIGN.md",
@@ -252,7 +252,7 @@
252
252
  },
253
253
  {
254
254
  "path": "apps/catalog/package.json",
255
- "sha256": "a45ba240bcd98a6b1262fbab6751fcf82482b24b9453b02d848b8ebad2842912"
255
+ "sha256": "a5f6cdd1f87cd6476ddfee45685be8daa2769ac1275ae201a725aac53f11739c"
256
256
  },
257
257
  {
258
258
  "path": "apps/catalog/public/logo_novo_azul.png",
@@ -352,7 +352,7 @@
352
352
  },
353
353
  {
354
354
  "path": "packages/local-runtime/package.json",
355
- "sha256": "1204ee6904229503041f1c17bb4bb135b89566ea6431aebd2cc627d647da9656"
355
+ "sha256": "7afa7584db17c5e4e7584da707b0d7cbb38affa1940a1d989ee4d267df80d33d"
356
356
  },
357
357
  {
358
358
  "path": "packages/local-runtime/runtime.mjs",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zaip-ai/design-catalog",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zaip-ai/design-local-runtime",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
@@ -6,7 +6,7 @@ title: Garantia adaptativa e execução autônoma
6
6
  status: active
7
7
  version: 3
8
8
  source_change: null
9
- source_release: "3.1.0"
9
+ source_release: "3.3.0"
10
10
  ---
11
11
 
12
12
  # Garantia adaptativa e execução autônoma
@@ -6,7 +6,7 @@ title: Autonomia delimitada para Cloudflare e Hostinger
6
6
  status: proposed
7
7
  version: 1
8
8
  source_change: null
9
- source_release: "3.1.0"
9
+ source_release: "3.3.0"
10
10
  ---
11
11
 
12
12
  # Cloudflare e Hostinger (proposta de requisito)
@@ -6,7 +6,7 @@ title: Auditoria comparativa e plano de evolução
6
6
  status: active
7
7
  version: 1
8
8
  source_change: null
9
- source_release: "3.1.0"
9
+ source_release: "3.3.0"
10
10
  ---
11
11
 
12
12
  # Auditoria comparativa e plano de evolução
@@ -6,7 +6,7 @@ title: Contexto mínimo, seguro e navegável
6
6
  status: active
7
7
  version: 8
8
8
  source_change: null
9
- source_release: "3.1.0"
9
+ source_release: "3.3.0"
10
10
  ---
11
11
 
12
12
  # Context engine
@@ -6,7 +6,7 @@ title: Tokens, custos e cobertura
6
6
  status: active
7
7
  version: 10
8
8
  source_change: null
9
- source_release: "3.1.0"
9
+ source_release: "3.3.0"
10
10
  ---
11
11
 
12
12
  # Cost observability
@@ -6,7 +6,7 @@ title: Inicialização e hidratação confiável do dashboard
6
6
  status: active
7
7
  version: 1
8
8
  source_change: null
9
- source_release: "3.1.0"
9
+ source_release: "3.3.0"
10
10
  ---
11
11
 
12
12
  # Inicialização e hidratação confiável do dashboard
@@ -6,7 +6,7 @@ title: Exportação contextual de dados para Excel
6
6
  status: proposed
7
7
  version: 2
8
8
  source_change: null
9
- source_release: "3.1.0"
9
+ source_release: "3.3.0"
10
10
  ---
11
11
 
12
12
  # Exportação contextual de dados
@@ -6,7 +6,7 @@ title: Contrato visual e catálogo executável
6
6
  status: active
7
7
  version: 5
8
8
  source_change: null
9
- source_release: "3.1.0"
9
+ source_release: "3.3.0"
10
10
  ---
11
11
 
12
12
  # Design system
@@ -6,7 +6,7 @@ title: Preparação, materialização e DAG
6
6
  status: active
7
7
  version: 11
8
8
  source_change: null
9
- source_release: "3.1.0"
9
+ source_release: "3.3.0"
10
10
  ---
11
11
 
12
12
  # Execution engine
@@ -6,7 +6,7 @@ title: Projeções operacionais e analíticas
6
6
  status: active
7
7
  version: 14
8
8
  source_change: null
9
- source_release: "3.1.0"
9
+ source_release: "3.3.0"
10
10
  ---
11
11
 
12
12
  # Kanban and dashboard
@@ -6,7 +6,7 @@ title: Seleção automática e observável de modelo e effort
6
6
  status: active
7
7
  version: 8
8
8
  source_change: null
9
- source_release: "3.1.0"
9
+ source_release: "3.3.0"
10
10
  ---
11
11
 
12
12
  # Orquestração de modelos