@zaip-ai/cli 1.4.3 → 2.2.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 (190) hide show
  1. package/README.md +33 -14
  2. package/dist/services/adapter-hook-sources.d.ts +1 -0
  3. package/dist/services/adapter-hook-sources.d.ts.map +1 -1
  4. package/dist/services/adapter-hook-sources.js +1 -0
  5. package/dist/services/adapter-hook-sources.js.map +1 -1
  6. package/dist/services/adapters.d.ts +1 -1
  7. package/dist/services/adapters.d.ts.map +1 -1
  8. package/dist/services/adapters.js +20 -16
  9. package/dist/services/adapters.js.map +1 -1
  10. package/dist/services/agent-adapters.d.ts +97 -34
  11. package/dist/services/agent-adapters.d.ts.map +1 -1
  12. package/dist/services/agent-adapters.js +279 -54
  13. package/dist/services/agent-adapters.js.map +1 -1
  14. package/dist/services/agent-adapters.test.js +13 -7
  15. package/dist/services/agent-adapters.test.js.map +1 -1
  16. package/dist/services/codex-conversation-lifecycle-hook.d.ts +2 -0
  17. package/dist/services/codex-conversation-lifecycle-hook.d.ts.map +1 -0
  18. package/dist/services/codex-conversation-lifecycle-hook.js +29 -0
  19. package/dist/services/codex-conversation-lifecycle-hook.js.map +1 -0
  20. package/dist/services/codex-pre-tool-hook-template.d.ts.map +1 -1
  21. package/dist/services/codex-pre-tool-hook-template.js +173 -0
  22. package/dist/services/codex-pre-tool-hook-template.js.map +1 -1
  23. package/dist/services/codex-subagent-hook-template.d.ts +1 -1
  24. package/dist/services/codex-subagent-hook-template.d.ts.map +1 -1
  25. package/dist/services/codex-subagent-hook-template.js +1 -1
  26. package/dist/services/doctor.js +2 -2
  27. package/dist/services/install-agents-template.d.ts.map +1 -1
  28. package/dist/services/install-agents-template.js +117 -5
  29. package/dist/services/install-agents-template.js.map +1 -1
  30. package/dist/services/logger.js +1 -1
  31. package/dist/services/logger.js.map +1 -1
  32. package/dist/services/logger.test.js +2 -2
  33. package/dist/services/runtime-template.test.js +9 -1
  34. package/dist/services/runtime-template.test.js.map +1 -1
  35. package/dist/services/stack-git.d.ts +3 -0
  36. package/dist/services/stack-git.d.ts.map +1 -1
  37. package/dist/services/stack-git.js +24 -3
  38. package/dist/services/stack-git.js.map +1 -1
  39. package/dist/services/template-manifest.d.ts.map +1 -1
  40. package/dist/services/template-manifest.js +3 -1
  41. package/dist/services/template-manifest.js.map +1 -1
  42. package/dist/services/transactional-files.d.ts +2 -1
  43. package/dist/services/transactional-files.d.ts.map +1 -1
  44. package/dist/services/transactional-files.js +8 -3
  45. package/dist/services/transactional-files.js.map +1 -1
  46. package/package.json +2 -2
  47. package/templates/.agents/README-ENG.md +735 -0
  48. package/templates/.agents/README.md +49 -43
  49. package/templates/.agents/architecture/hostinger-cloudflare-terraform.md +90 -0
  50. package/templates/.agents/config/agents/catalog.yaml +18 -18
  51. package/templates/.agents/config/autonomous-cloud-inputs.json +41 -0
  52. package/templates/.agents/config/clients/codex/agents/zaip-agent-analyst.toml +3 -6
  53. package/templates/.agents/config/clients/codex/agents/zaip-agent-auditor.toml +3 -6
  54. package/templates/.agents/config/clients/codex/agents/zaip-agent-backend.toml +3 -6
  55. package/templates/.agents/config/clients/codex/agents/zaip-agent-brand-strategist.toml +6 -8
  56. package/templates/.agents/config/clients/codex/agents/zaip-agent-builder.toml +3 -6
  57. package/templates/.agents/config/clients/codex/agents/zaip-agent-cloud-engineer.toml +3 -6
  58. package/templates/.agents/config/clients/codex/agents/zaip-agent-data-analyst.toml +5 -7
  59. package/templates/.agents/config/clients/codex/agents/zaip-agent-designer.toml +3 -6
  60. package/templates/.agents/config/clients/codex/agents/zaip-agent-finalizer.toml +3 -6
  61. package/templates/.agents/config/clients/codex/agents/zaip-agent-frontend.toml +3 -6
  62. package/templates/.agents/config/clients/codex/agents/zaip-agent-general-operations.toml +6 -8
  63. package/templates/.agents/config/clients/codex/agents/zaip-agent-governance-compliance.toml +3 -6
  64. package/templates/.agents/config/clients/codex/agents/zaip-agent-growth-strategist.toml +6 -8
  65. package/templates/.agents/config/clients/codex/agents/zaip-agent-integrator.toml +3 -6
  66. package/templates/.agents/config/clients/codex/agents/zaip-agent-marketing-analyst.toml +6 -8
  67. package/templates/.agents/config/clients/codex/agents/zaip-agent-orchestrator.toml +4 -5
  68. package/templates/.agents/config/clients/codex/agents/zaip-agent-refactoring-specialist.toml +3 -6
  69. package/templates/.agents/config/clients/codex/agents/zaip-agent-reviewer.toml +3 -6
  70. package/templates/.agents/config/clients/codex/agents/zaip-agent-sales-specialist.toml +6 -8
  71. package/templates/.agents/config/clients/codex/agents/zaip-agent-security-auditor.toml +3 -6
  72. package/templates/.agents/config/clients/codex/agents/zaip-agent-task-executor.toml +3 -6
  73. package/templates/.agents/config/clients/codex/agents/zaip-agent-website-developer.toml +3 -6
  74. package/templates/.agents/config/clients/codex/agents/zaip-agent-writer.toml +3 -6
  75. package/templates/.agents/config/pricing/models.json +53 -33
  76. package/templates/.agents/dashboard/.zaip-managed.json +64 -44
  77. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-oZVlf-l5.js → DashboardHelpDialog-DaSllaJp.js} +118 -16
  78. package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-BYjVOHI_.js → TraceabilityWorkspace-BCvZFMEf.js} +1 -1
  79. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-_MEktQNM.js +2 -0
  80. package/templates/.agents/dashboard/apps/frontend/dist/index.html +2 -2
  81. package/templates/.agents/dashboard/apps/frontend/index.html +1 -1
  82. package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
  83. package/templates/.agents/dashboard/apps/frontend/src/App.tsx +28 -9
  84. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +27 -0
  85. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +13 -29
  86. package/templates/.agents/dashboard/apps/frontend/src/dashboard-identity.test.ts +43 -0
  87. package/templates/.agents/dashboard/apps/frontend/src/dashboard-identity.ts +32 -0
  88. package/templates/.agents/dashboard/apps/frontend/src/dashboard-project-overview.ts +28 -0
  89. package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +2 -2
  90. package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
  91. package/templates/.agents/dashboard/modules/operations/package.json +1 -1
  92. package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
  93. package/templates/.agents/dashboard/modules/traceability/src/contracts.ts +1 -1
  94. package/templates/.agents/dashboard/packages/branding/package.json +1 -1
  95. package/templates/.agents/dashboard/packages/branding/src/index.ts +1 -1
  96. package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
  97. package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
  98. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +206 -206
  99. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +169 -169
  100. package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
  101. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +206 -206
  102. package/templates/.agents/dashboard/packages/local-runtime/src/server-identity.test.ts +42 -0
  103. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.test.ts +41 -0
  104. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts +1 -0
  105. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot.test.ts +2 -2
  106. package/templates/.agents/dashboard/packages/local-runtime/src/server-sqlite-task-trace.ts +6 -3
  107. package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +1 -1
  108. package/templates/.agents/dashboard/packages/local-runtime/src/server.test-fixtures.ts +1 -1
  109. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +1 -13
  110. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +11 -3
  111. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-refresh.test.ts +55 -0
  112. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical.ts +9 -6
  113. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.test.ts +22 -0
  114. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.ts +10 -8
  115. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +68 -2
  116. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +39 -10
  117. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-seed.test.ts +47 -42
  118. package/templates/.agents/dashboard/packages/ui/package.json +1 -1
  119. package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
  120. package/templates/.agents/design/.zaip-managed.json +41 -41
  121. package/templates/.agents/design/apps/catalog/package.json +1 -1
  122. package/templates/.agents/design/packages/local-runtime/package.json +1 -1
  123. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  124. package/templates/.agents/requirements/cloud-hostinger-autonomy.md +1 -1
  125. package/templates/.agents/requirements/comparative-audit.md +1 -1
  126. package/templates/.agents/requirements/context-engine.md +1 -1
  127. package/templates/.agents/requirements/cost-observability.md +1 -1
  128. package/templates/.agents/requirements/dashboard-launch.md +1 -1
  129. package/templates/.agents/requirements/data-export.md +1 -1
  130. package/templates/.agents/requirements/design-system.md +1 -1
  131. package/templates/.agents/requirements/execution-engine.md +1 -1
  132. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  133. package/templates/.agents/requirements/model-orchestration.md +15 -5
  134. package/templates/.agents/requirements/public-distribution.md +3 -3
  135. package/templates/.agents/requirements/software-architecture.md +1 -1
  136. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  137. package/templates/.agents/requirements/work-lifecycle.md +1 -1
  138. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  139. package/templates/.agents/rules/astra-diagnostic.md +20 -0
  140. package/templates/.agents/rules/core.md +9 -14
  141. package/templates/.agents/rules/meta-mcp.md +1 -0
  142. package/templates/.agents/rules/run-lifecycle-closure.md +1 -1
  143. package/templates/.agents/rules/specialist-routing.md +3 -1
  144. package/templates/.agents/runtime/VERSION +1 -1
  145. package/templates/.agents/runtime/bin/zaip.mjs +95 -61
  146. package/templates/.agents/runtime/checksums.sha256 +6 -6
  147. package/templates/.agents/runtime/manifest.json +1 -1
  148. package/templates/.agents/runtime/pnpm-lock.sha256 +1 -1
  149. package/templates/.agents/runtime/provenance.json +5 -5
  150. package/templates/.agents/runtime/sbom.cdx.json +1 -1
  151. package/templates/.agents/skills/zaip-aios/SKILL.md +4 -2
  152. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +2 -0
  153. package/templates/.agents/skills/zaip-skill-execution/references/runtime-governance.md +17 -0
  154. package/templates/.agents/skills/zaip-skill-general-operations/SKILL.md +6 -7
  155. package/templates/.agents/skills/zaip-skill-general-operations/references/extended-workflow.md +7 -0
  156. package/templates/.agents/skills/zaip-skill-github-setup/SKILL.md +5 -0
  157. package/templates/.agents/skills/zaip-skill-github-setup-ssh-folder/SKILL.md +54 -0
  158. package/templates/.agents/skills/zaip-skill-github-setup-ssh-folder/agents/openai.yaml +4 -0
  159. package/templates/.agents/skills/zaip-skill-github-setup-ssh-folder/assets/.env.example +9 -0
  160. package/templates/.agents/skills/zaip-skill-github-setup-ssh-folder/references/operational-procedure.md +137 -0
  161. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +7 -19
  162. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/references/extended-workflow.md +21 -0
  163. package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +7 -14
  164. package/templates/.agents/skills/zaip-skill-marketing/references/extended-workflow.md +16 -0
  165. package/templates/.agents/skills/zaip-skill-meta-ads-setup/SKILL.md +2 -0
  166. package/templates/.agents/skills/zaip-skill-meta-token-onboarding/SKILL.md +17 -0
  167. package/templates/.agents/skills/zaip-skill-meta-token-onboarding/agents/openai.yaml +4 -0
  168. package/templates/.agents/skills/zaip-skill-meta-token-onboarding/references/onboarding-manual.md +96 -0
  169. package/templates/.agents/skills/zaip-skill-release-qualification/SKILL.md +4 -0
  170. package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +8 -18
  171. package/templates/.agents/skills/zaip-skill-sales-methodology/references/extended-workflow.md +20 -0
  172. package/templates/.agents/skills/zaip-skill-setup/SKILL.md +4 -1
  173. package/templates/.agents/skills/zaip-skill-text-encoding/SKILL.md +1 -1
  174. package/templates/.agents/zaip-aios/INDEX.md +1 -1
  175. package/templates/.agents/zaip-aios/README.md +6 -0
  176. package/templates/.agents/zaip-aios/VERSION +1 -1
  177. package/templates/.agents/zaip-aios/guides/08-contexto-e-tokens.md +19 -3
  178. package/templates/.agents/zaip-aios/guides/12-instalacao-e-upgrade.md +37 -7
  179. package/templates/.agents/zaip-aios/guides/20-instalacao-detalhada.md +14 -1
  180. package/templates/.agents/zaip-aios/guides/23-orquestracao-de-modelos.md +32 -3
  181. package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +21 -0
  182. package/templates/infra/terraform/terraform.tfvars.example +2 -1
  183. package/templates/template-manifest.json +234 -150
  184. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-ZJFXmK7m.js +0 -2
  185. /package/templates/.agents/{data → records}/changes/.gitkeep +0 -0
  186. /package/templates/.agents/{data → records}/executions/.gitkeep +0 -0
  187. /package/templates/.agents/{data → records}/prds/.gitkeep +0 -0
  188. /package/templates/.agents/{data → records}/runs/.gitkeep +0 -0
  189. /package/templates/.agents/{data → records}/specs/.gitkeep +0 -0
  190. /package/templates/.agents/{data → records}/tasks/.gitkeep +0 -0
@@ -0,0 +1,42 @@
1
+ import { mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { WorkspaceFileSystem } from "@zaip-ai/core";
5
+ import { describe, expect, it, vi } from "vitest";
6
+ import { createDashboardApp, githubRepositoryFromRemote } from "./server.js";
7
+
8
+ describe("dashboard identity endpoint", () => {
9
+ it("normalizes GitHub HTTPS and SSH remotes for display", () => {
10
+ expect(githubRepositoryFromRemote("git@github.com:zaip-ai/zaip-aios.git")).toBe(
11
+ "zaip-ai/zaip-aios",
12
+ );
13
+ expect(githubRepositoryFromRemote("https://github.com/zaip-ai/zaip-aios.git")).toBe(
14
+ "zaip-ai/zaip-aios",
15
+ );
16
+ expect(githubRepositoryFromRemote("zaip-ai/zaip-aios")).toBe("zaip-ai/zaip-aios");
17
+ expect(githubRepositoryFromRemote("")).toBeNull();
18
+ expect(githubRepositoryFromRemote("https://gitlab.com/zaip-ai/zaip-aios.git")).toBeNull();
19
+ });
20
+ it("serves authenticated dashboard identity without building or refreshing a projection", async () => {
21
+ const root = await mkdtemp(join(tmpdir(), "zaip-dashboard-identity-"));
22
+ const refreshReadModel = vi.fn(async () => {});
23
+ try {
24
+ const app = createDashboardApp(root, "test-session", undefined, {
25
+ filesystem: new WorkspaceFileSystem(root),
26
+ refreshReadModel,
27
+ });
28
+ const response = await app.request("/api/v2/dashboard-identity", {
29
+ headers: { "x-zaip-session": "test-session" },
30
+ });
31
+
32
+ expect(response.status, await response.clone().text()).toBe(200);
33
+ await expect(response.json()).resolves.toEqual({
34
+ title: "Dashboard Zaip AIOS",
35
+ repository_name: "",
36
+ });
37
+ expect(refreshReadModel).not.toHaveBeenCalled();
38
+ } finally {
39
+ await rm(root, { recursive: true, force: true });
40
+ }
41
+ });
42
+ });
@@ -0,0 +1,41 @@
1
+ import { join } from "node:path";
2
+ import { afterEach, describe, expect, it, vi } from "vitest";
3
+
4
+ const watchers: Array<{ path: string; listener: () => void }> = [];
5
+
6
+ vi.mock("node:fs", () => ({
7
+ watch: vi.fn((path: string, _options: unknown, listener: () => void) => {
8
+ watchers.push({ path, listener });
9
+ return { unref: vi.fn() };
10
+ }),
11
+ }));
12
+
13
+ import { startSnapshotSynchronization } from "./server-snapshot-synchronization.js";
14
+
15
+ afterEach(() => {
16
+ watchers.splice(0);
17
+ vi.useRealTimers();
18
+ vi.unstubAllEnvs();
19
+ });
20
+
21
+ describe("snapshot source synchronization", () => {
22
+ it("watches current data for both creation and alteration refreshes", async () => {
23
+ vi.useFakeTimers();
24
+ const refresh = vi.fn(async () => undefined);
25
+ const workspaceRoot = "C:/workspace";
26
+
27
+ startSnapshotSynchronization({ workspaceRoot, staleSeed: false, watchSources: true, refresh });
28
+
29
+ const currentData = watchers.find(
30
+ ({ path }) => path === join(workspaceRoot, ".agents", "data"),
31
+ );
32
+ expect(currentData).toBeDefined();
33
+ currentData?.listener();
34
+ await vi.advanceTimersByTimeAsync(300);
35
+ expect(refresh).toHaveBeenCalledTimes(1);
36
+
37
+ currentData?.listener();
38
+ await vi.advanceTimersByTimeAsync(300);
39
+ expect(refresh).toHaveBeenCalledTimes(2);
40
+ });
41
+ });
@@ -19,6 +19,7 @@ export function startSnapshotSynchronization(input: {
19
19
  pendingRefresh.unref();
20
20
  };
21
21
  for (const sourceRoot of [
22
+ join(input.workspaceRoot, ".agents", "data"),
22
23
  join(input.workspaceRoot, ".agents", "records"),
23
24
  join(input.workspaceRoot, ".agents", "config"),
24
25
  ]) {
@@ -45,7 +45,7 @@ describe("dashboard backend snapshot", () => {
45
45
  [
46
46
  "lock_version: 1",
47
47
  "framework:",
48
- " version: 1.3.0",
48
+ " version: 2.2.0",
49
49
  " distribution_sha256: null",
50
50
  "layout:",
51
51
  " version: 3",
@@ -510,7 +510,7 @@ describe("dashboard backend snapshot", () => {
510
510
  });
511
511
  await expect(response.json()).resolves.toMatchObject({
512
512
  dashboard: {
513
- title: "Dashboard Zaip AIOS SQLite",
513
+ title: "Dashboard Zaip AIOS",
514
514
  repository_name: "",
515
515
  },
516
516
  operations: { readonly: true, summary: { source_latest_at: "2026-07-14T00:00:05.000Z" } },
@@ -1,11 +1,11 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import type { WorkspaceLayout } from "@zaip-ai/core";
3
+ import type { TaskTrace, TraceDocument } from "@zaip-ai/dashboard-traceability/contracts";
3
4
  import {
4
5
  buildTraceStageRecords,
5
6
  classifyTraceDocument,
6
7
  timestampsFromContent,
7
8
  } from "@zaip-ai/dashboard-traceability/server";
8
- import type { TaskTrace, TraceDocument } from "../../../modules/traceability/src/contracts.js";
9
9
  import type { SqliteReadModelFileSystem } from "./sqlite-read-model.js";
10
10
 
11
11
  export function buildSqliteTaskTrace(
@@ -16,7 +16,10 @@ export function buildSqliteTaskTrace(
16
16
  const initial = filesystem.canonicalTraceFiles([taskId]);
17
17
  const taskFile = initial.find(
18
18
  (row) =>
19
- row.path.includes("/.agents/records/tasks/") || row.path.includes(".agents/records/tasks/"),
19
+ row.path.includes("/.agents/data/tasks/") ||
20
+ row.path.includes(".agents/data/tasks/") ||
21
+ row.path.includes("/.agents/records/tasks/") ||
22
+ row.path.includes(".agents/records/tasks/"),
20
23
  );
21
24
  if (taskFile === undefined) throw new Error(`Canonical Task ${taskId} was not found`);
22
25
  const scalar = (key: string): string | null => {
@@ -59,7 +62,7 @@ export function buildSqliteTaskTrace(
59
62
  schema_version: 2,
60
63
  layout_version: layout.version as 2 | 3,
61
64
  readonly: true,
62
- source: ".agents/records",
65
+ source: filesystem.canonicalSource(),
63
66
  task_id: taskId,
64
67
  task_title: scalar("title") ?? taskFile.content.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? taskId,
65
68
  task_risk: risk === "lite" || risk === "standard" || risk === "critical" ? risk : null,
@@ -11,7 +11,7 @@ export async function dashboardIdentity(
11
11
  filesystem: WorkspaceFileSystem,
12
12
  workspaceRoot: string,
13
13
  ): Promise<{ title: string; repository_name: string }> {
14
- let title = process.env.ZAIP_DASHBOARD_TITLE?.trim() || "Dashboard Zaip AIOS SQLite";
14
+ let title = process.env.ZAIP_DASHBOARD_TITLE?.trim() || "Dashboard Zaip AIOS";
15
15
  let repositorySetting = "auto";
16
16
  try {
17
17
  if (await filesystem.exists(DASHBOARD_CONFIG_PATH)) {
@@ -20,7 +20,7 @@ export async function withSqliteReadModelFixture<T>(
20
20
  );
21
21
  await writeFile(
22
22
  join(root, ".agents", "config", "framework.lock.yaml"),
23
- "lock_version: 1\nframework:\n version: 1.3.0\n distribution_sha256: null\nlayout:\n version: 3\nresolved_profile:\n id: null\n sha256: null\nmanaged_files: {}\n",
23
+ "lock_version: 1\nframework:\n version: 2.2.0\n distribution_sha256: null\nlayout:\n version: 3\nresolved_profile:\n id: null\n sha256: null\nmanaged_files: {}\n",
24
24
  );
25
25
  await rebuildSqliteReadModel(root);
26
26
  filesystem = new SqliteReadModelFileSystem(root);
@@ -6,7 +6,6 @@ import { describe, expect, it, vi } from "vitest";
6
6
  import {
7
7
  createDashboardApp,
8
8
  dashboardWorkspaceKey,
9
- githubRepositoryFromRemote,
10
9
  manifestRun,
11
10
  mergeManifestRuns,
12
11
  openWorkIds,
@@ -107,17 +106,6 @@ describe("dashboard backend", () => {
107
106
  ),
108
107
  ).toEqual(["RUN-IN-PERIOD"]);
109
108
  });
110
- it("normalizes GitHub HTTPS and SSH remotes for display", () => {
111
- expect(githubRepositoryFromRemote("git@github.com:zaip-ai/zaip-aios.git")).toBe(
112
- "zaip-ai/zaip-aios",
113
- );
114
- expect(githubRepositoryFromRemote("https://github.com/zaip-ai/zaip-aios.git")).toBe(
115
- "zaip-ai/zaip-aios",
116
- );
117
- expect(githubRepositoryFromRemote("zaip-ai/zaip-aios")).toBe("zaip-ai/zaip-aios");
118
- expect(githubRepositoryFromRemote("")).toBeNull();
119
- expect(githubRepositoryFromRemote("https://gitlab.com/zaip-ai/zaip-aios.git")).toBeNull();
120
- });
121
109
  it("exposes a modern read-only health endpoint", async () => {
122
110
  const app = createDashboardApp(workspaceRoot, "test-session");
123
111
  const response = await app.request("/api/v2/health");
@@ -294,7 +282,7 @@ describe("dashboard backend", () => {
294
282
  );
295
283
  await writeFile(
296
284
  join(fixtureRoot, ".agents", "config", "framework.lock.yaml"),
297
- "lock_version: 1\nframework:\n version: 1.3.0\n distribution_sha256: null\nlayout:\n version: 3\nresolved_profile:\n id: null\n sha256: null\nmanaged_files: {}\n",
285
+ "lock_version: 1\nframework:\n version: 2.2.0\n distribution_sha256: null\nlayout:\n version: 3\nresolved_profile:\n id: null\n sha256: null\nmanaged_files: {}\n",
298
286
  );
299
287
  const task = (status: "executing" | "completed") => ({
300
288
  schema_version: 1,
@@ -389,6 +389,7 @@ function buildSqliteOperationsProjection(
389
389
  const hasUnknownUsage = Object.values(dailyPeriods).some(
390
390
  (period) => period.usage.total_tokens.unknown > 0 || period.usage.cost.unknown > 0,
391
391
  );
392
+ const canonicalSource = filesystem.canonicalSource();
392
393
  const taskProjection = (row: Record<string, unknown>) => ({
393
394
  schema_version: 2 as const,
394
395
  id: String(row.id),
@@ -411,7 +412,7 @@ function buildSqliteOperationsProjection(
411
412
  started_at: null,
412
413
  finished_at: null,
413
414
  result: null,
414
- artifact_path: ".agents/records",
415
+ artifact_path: canonicalSource,
415
416
  progress: 0,
416
417
  spec_id: typeof row.spec_id === "string" ? row.spec_id : null,
417
418
  origin: typeof row.origin === "string" ? row.origin : null,
@@ -435,7 +436,7 @@ function buildSqliteOperationsProjection(
435
436
  updated_at: String(row.occurred_at),
436
437
  kind,
437
438
  parent_id: null,
438
- artifact_path: ".agents/records",
439
+ artifact_path: canonicalSource,
439
440
  lifecycle_status: String(row.status ?? "draft"),
440
441
  work_status: normalizeMacroStatus(row.status),
441
442
  execution_ids: [],
@@ -472,7 +473,7 @@ function buildSqliteOperationsProjection(
472
473
  schema_version: 2,
473
474
  layout_version: layout.version as 2 | 3,
474
475
  readonly: true,
475
- source: ".agents/records",
476
+ source: canonicalSource,
476
477
  filters: filter,
477
478
  summary: {
478
479
  prd_count: allTime?.aggregate.prd_count ?? prds.length,
@@ -939,6 +940,13 @@ export function createDashboardApp(
939
940
  }
940
941
  });
941
942
 
943
+ // Header identity is intentionally separate from Operations projections.
944
+ // The quick first fold omits it, and resolving this small configuration/git
945
+ // value must not read, refresh, or otherwise materialize a snapshot.
946
+ app.on(["GET", "HEAD"], `${API_PREFIX}/dashboard-identity`, async (context) =>
947
+ headOrJson(context, await dashboardIdentity(filesystem, workspaceRoot)),
948
+ );
949
+
942
950
  app.on(["GET", "HEAD"], `${API_PREFIX}/operations`, async (context) => {
943
951
  try {
944
952
  const requestStartedAt = performance.now();
@@ -68,6 +68,61 @@ afterEach(async () => {
68
68
  });
69
69
 
70
70
  describe("incremental refresh preserves historical facts", () => {
71
+ it("keeps one current Run when a legacy duplicate is added and falls back after current deletion", async () => {
72
+ const root = await fixture();
73
+ await prepareMultiProviderFixture(root);
74
+ const legacy = join(root, ".agents/records/runs/2026/08/RUN-MULTI.jsonl");
75
+ const current = join(root, ".agents/data/runs/2026/08/RUN-MULTI.jsonl");
76
+ await mkdir(join(root, ".agents/data/runs/2026/08"), { recursive: true });
77
+ const content = await readFile(legacy, "utf8");
78
+ await writeFile(current, content);
79
+
80
+ const first = await rebuildSqliteReadModel(root);
81
+ const expected = inspect(
82
+ first.path,
83
+ "SELECT COUNT(*) AS count, SUM(total_tokens) AS tokens, SUM(cost_brl) AS cost FROM zaip_aios_activities WHERE payload_type IS NULL AND run_id='RUN-MULTI'",
84
+ );
85
+ expect(
86
+ inspect(
87
+ first.path,
88
+ "SELECT COUNT(*) AS count FROM zaip_vw_canonical_run_index WHERE run_id='RUN-MULTI'",
89
+ ),
90
+ ).toEqual([{ count: 1 }]);
91
+ // Establish a cached current-only generation before an upgrade leaves a
92
+ // duplicate legacy file behind.
93
+ await rm(legacy);
94
+ await rebuildSqliteReadModel(root);
95
+ await writeFile(legacy, content);
96
+ await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ cache: "rebuilt" });
97
+ expect(
98
+ inspect(
99
+ first.path,
100
+ "SELECT COUNT(*) AS count FROM zaip_vw_canonical_run_index WHERE run_id='RUN-MULTI'",
101
+ ),
102
+ ).toEqual([{ count: 1 }]);
103
+ expect(
104
+ inspect(
105
+ first.path,
106
+ "SELECT COUNT(*) AS count, SUM(total_tokens) AS tokens, SUM(cost_brl) AS cost FROM zaip_aios_activities WHERE payload_type IS NULL AND run_id='RUN-MULTI'",
107
+ ),
108
+ ).toEqual(expected);
109
+ expect(
110
+ inspect(
111
+ first.path,
112
+ "SELECT source_path FROM zaip_aios_activities WHERE payload_type IS NULL AND run_id='RUN-MULTI'",
113
+ ),
114
+ ).toEqual([{ source_path: ".agents/data/runs/2026/08/RUN-MULTI.jsonl" }]);
115
+
116
+ await rm(current);
117
+ await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ cache: "rebuilt" });
118
+ expect(
119
+ inspect(
120
+ first.path,
121
+ "SELECT COUNT(*) AS count, source_path FROM zaip_aios_activities WHERE payload_type IS NULL AND run_id='RUN-MULTI'",
122
+ ),
123
+ ).toEqual([{ count: 1, source_path: ".agents/records/runs/2026/08/RUN-MULTI.jsonl" }]);
124
+ }, 120_000);
125
+
71
126
  it("refreshes developer delivery and shared SPEC allocation from cached Run facts", async () => {
72
127
  const root = await fixture();
73
128
  await prepareMultiProviderFixture(root);
@@ -117,7 +117,7 @@ export function readCanonicalLifecycleRecords(rows: Array<{ path: string; conten
117
117
  const id = stringValue(metadata.id);
118
118
  const createdAt = stringValue(metadata.created_at);
119
119
  if (id === null || createdAt === null || Number.isNaN(Date.parse(createdAt))) continue;
120
- if (/^\.agents\/records\/executions\/.*\/execution\.md$/.test(row.path)) {
120
+ if (/^\.agents\/(?:data|records)\/executions\/.*\/execution\.md$/.test(row.path)) {
121
121
  const origin = recordValue(metadata.origin);
122
122
  const originType = stringValue(origin?.type);
123
123
  const originId =
@@ -142,7 +142,7 @@ export function readCanonicalLifecycleRecords(rows: Array<{ path: string; conten
142
142
  });
143
143
  continue;
144
144
  }
145
- if (/^\.agents\/records\/changes\/.*\/change\.md$/.test(row.path)) {
145
+ if (/^\.agents\/(?:data|records)\/changes\/.*\/change\.md$/.test(row.path)) {
146
146
  const source = recordValue(metadata.source);
147
147
  const sourceType = stringValue(source?.type);
148
148
  const sourceId =
@@ -175,7 +175,9 @@ export function readCanonicalLifecycleRecords(rows: Array<{ path: string; conten
175
175
  });
176
176
  continue;
177
177
  }
178
- if (/^\.agents\/records\/(changes|executions)\/.*\/decisions\/.*\.md$/.test(row.path)) {
178
+ if (
179
+ /^\.agents\/(?:data|records)\/(changes|executions)\/.*\/decisions\/.*\.md$/.test(row.path)
180
+ ) {
179
181
  const origin = metadata.origin;
180
182
  const scopeType = metadata.scope_type;
181
183
  const scopeId = stringValue(metadata.scope_id);
@@ -205,9 +207,10 @@ export function readCanonicalEntities(
205
207
  ): Map<string, CanonicalEntity> {
206
208
  const entities = new Map<string, CanonicalEntity>();
207
209
  for (const row of rows) {
208
- const match = /^\.agents\/records\/(tasks|specs|prds|changes|executions|conversations)\//.exec(
209
- row.path,
210
- );
210
+ const match =
211
+ /^\.agents\/(?:data|records)\/(tasks|specs|prds|changes|executions|conversations)\//.exec(
212
+ row.path,
213
+ );
211
214
  if (match === null) continue;
212
215
  const frontMatter = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(row.content)?.[1];
213
216
  if (frontMatter === undefined) continue;
@@ -25,6 +25,28 @@ afterEach(async () => {
25
25
  });
26
26
 
27
27
  describe("persistent source fingerprint checkpoints", () => {
28
+ it("includes current data records, retains legacy records, and excludes generated projections", async () => {
29
+ const { root } = await fixture();
30
+ await mkdir(join(root, ".agents/data/runs/2026/09"), { recursive: true });
31
+ await mkdir(join(root, ".agents/data/executions/EXEC-CURRENT/decisions"), { recursive: true });
32
+ await mkdir(join(root, ".agents/data/lifecycle/indexes"), { recursive: true });
33
+ await writeFile(join(root, ".agents/data/runs/2026/09/RUN-CURRENT.jsonl"), "{}\n");
34
+ await writeFile(
35
+ join(root, ".agents/data/executions/EXEC-CURRENT/decisions/DEC-CURRENT.md"),
36
+ "---\nid: DEC-CURRENT\ncreated_at: 2026-09-01T00:00:00Z\n---\n",
37
+ );
38
+ await writeFile(join(root, ".agents/data/lifecycle/indexes/run.json"), "{}\n");
39
+ await writeFile(join(root, ".agents/data/lifecycle/active-work.json"), "{}\n");
40
+
41
+ const snapshot = await sourceFingerprint(root);
42
+
43
+ expect(snapshot.candidates.map((candidate) => candidate.source)).toEqual([
44
+ ".agents/data/executions/EXEC-CURRENT/decisions/DEC-CURRENT.md",
45
+ ".agents/data/runs/2026/09/RUN-CURRENT.jsonl",
46
+ ".agents/records/tasks/task.md",
47
+ ]);
48
+ });
49
+
28
50
  it("reuses historical content hashes across serialized checkpoints and detects additions and removals", async () => {
29
51
  const { root, path } = await fixture();
30
52
  const initial = await sourceFingerprint(root);
@@ -5,6 +5,7 @@ import { join, relative, resolve } from "node:path";
5
5
  const textExtensions = new Set([".json", ".jsonl", ".md", ".yaml", ".yml", ".txt"]);
6
6
 
7
7
  function isDashboardCanonicalSource(source: string): boolean {
8
+ const canonicalRoot = "\\.agents\\/(?:data|records)";
8
9
  if (
9
10
  [
10
11
  ".agents/config/project.yaml",
@@ -15,17 +16,18 @@ function isDashboardCanonicalSource(source: string): boolean {
15
16
  )
16
17
  return true;
17
18
  if (source.startsWith(".agents/config/pricing/")) return true;
18
- if (source.startsWith(".agents/records/runs/") && source.endsWith(".jsonl")) return true;
19
- if (/^\.agents\/records\/(tasks|specs|prds)\/.*\.md$/.test(source)) return true;
20
- if (/^\.agents\/records\/executions\/.*\/execution\.md$/.test(source)) return true;
19
+ if (new RegExp(`^${canonicalRoot}/runs/`).test(source) && source.endsWith(".jsonl")) return true;
20
+ if (new RegExp(`^${canonicalRoot}/(tasks|specs|prds)/.*\\.md$`).test(source)) return true;
21
+ if (new RegExp(`^${canonicalRoot}/executions/.*/execution\\.md$`).test(source)) return true;
22
+ if (new RegExp(`^${canonicalRoot}/executions/.*/decisions/.*\\.md$`).test(source)) return true;
21
23
  if (
22
- source.startsWith(".agents/records/lifecycle/historical-developer-attribution/") &&
24
+ new RegExp(`^${canonicalRoot}/lifecycle/historical-developer-attribution/`).test(source) &&
23
25
  source.endsWith(".json")
24
26
  )
25
27
  return true;
26
- return /^\.agents\/records\/changes\/.*\/(change\.md|decisions\/.*\.md|artifacts\/(evidence|tests|validations)\/.*\.md)$/.test(
27
- source,
28
- );
28
+ return new RegExp(
29
+ `^${canonicalRoot}/changes/.*/(change\\.md|decisions/.*\\.md|artifacts/(evidence|tests|validations)/.*\\.md)$`,
30
+ ).test(source);
29
31
  }
30
32
 
31
33
  export type SqliteReadModelSourceCandidate = {
@@ -66,7 +68,7 @@ export async function sourceFingerprint(
66
68
  "executions",
67
69
  "changes",
68
70
  "lifecycle/historical-developer-attribution",
69
- ].map((kind) => `.agents/records/${kind}`),
71
+ ].flatMap((kind) => [`.agents/data/${kind}`, `.agents/records/${kind}`]),
70
72
  ].map((root) => listFiles(resolve(workspaceRoot, root))),
71
73
  )
72
74
  )
@@ -157,6 +157,72 @@ describe("SQLite dashboard read model", () => {
157
157
  await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ files: 2 });
158
158
  });
159
159
 
160
+ it("uses current data entities once when the same ID remains in the legacy layout", async () => {
161
+ const root = await createSqliteTestRoot();
162
+ roots.push(root);
163
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
164
+ await mkdir(join(root, ".agents", "records", "tasks"), { recursive: true });
165
+ await mkdir(join(root, ".agents", "data", "tasks"), { recursive: true });
166
+ await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n");
167
+ const legacy = `---\nid: TASK-DUPLICATE\ntitle: Legacy title\nstatus: completed\ncreated_at: 2026-09-01T10:00:00.000Z\n---\n`;
168
+ const current = `---\nid: TASK-DUPLICATE\ntitle: Current title\nstatus: executing\ncreated_at: 2026-09-02T10:00:00.000Z\n---\n`;
169
+ await writeFile(join(root, ".agents", "records", "tasks", "TASK-DUPLICATE.md"), legacy);
170
+ await writeFile(join(root, ".agents", "data", "tasks", "TASK-DUPLICATE.md"), current);
171
+
172
+ await rebuildSqliteReadModel(root);
173
+ const metadata = new DatabaseSync(
174
+ join(root, ".local", "sqlite", "zaip-aios-dashboard.sqlite"),
175
+ {
176
+ readOnly: true,
177
+ },
178
+ );
179
+ try {
180
+ expect(
181
+ metadata
182
+ .prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key='schema_version'")
183
+ .get(),
184
+ ).toEqual({ value: "74" });
185
+ } finally {
186
+ metadata.close();
187
+ }
188
+ const filesystem = new SqliteReadModelFileSystem(root);
189
+ try {
190
+ expect(filesystem.canonicalWorkPage("task", { from: null, to: null }, 0, 10)).toMatchObject({
191
+ total_count: 1,
192
+ rows: [expect.objectContaining({ id: "TASK-DUPLICATE", title: "Current title" })],
193
+ });
194
+ } finally {
195
+ filesystem.close();
196
+ }
197
+
198
+ await writeFile(
199
+ join(root, ".agents", "records", "tasks", "TASK-DUPLICATE.md"),
200
+ legacy.replace("Legacy title", "Edited legacy title"),
201
+ );
202
+ await rebuildSqliteReadModel(root);
203
+ const currentCached = new SqliteReadModelFileSystem(root);
204
+ try {
205
+ expect(
206
+ currentCached.canonicalWorkPage("task", { from: null, to: null }, 0, 10),
207
+ ).toMatchObject({
208
+ rows: [expect.objectContaining({ title: "Current title" })],
209
+ });
210
+ } finally {
211
+ currentCached.close();
212
+ }
213
+
214
+ await rm(join(root, ".agents", "data", "tasks", "TASK-DUPLICATE.md"));
215
+ await rebuildSqliteReadModel(root);
216
+ const fallback = new SqliteReadModelFileSystem(root);
217
+ try {
218
+ expect(fallback.canonicalWorkPage("task", { from: null, to: null }, 0, 10)).toMatchObject({
219
+ rows: [expect.objectContaining({ title: "Edited legacy title" })],
220
+ });
221
+ } finally {
222
+ fallback.close();
223
+ }
224
+ });
225
+
160
226
  it("accepts only the canonical C3D4 historical assignment among lifecycle files", async () => {
161
227
  const root = await createSqliteTestRoot();
162
228
  roots.push(root);
@@ -1252,7 +1318,7 @@ created_at: 2026-08-10T11:01:00.000Z
1252
1318
  snapshot
1253
1319
  .prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key='schema_version'")
1254
1320
  .get(),
1255
- ).toEqual({ value: "73" });
1321
+ ).toEqual({ value: "74" });
1256
1322
  } finally {
1257
1323
  snapshot.close();
1258
1324
  }
@@ -2464,7 +2530,7 @@ created_at: 2026-08-10T11:01:00.000Z
2464
2530
  );
2465
2531
  await writeFile(
2466
2532
  join(root, ".agents", "config", "framework.lock.yaml"),
2467
- "lock_version: 1\nframework:\n version: 1.3.0\n distribution_sha256: null\nlayout:\n version: 3\nresolved_profile:\n id: null\n sha256: null\nmanaged_files: {}\n",
2533
+ "lock_version: 1\nframework:\n version: 2.2.0\n distribution_sha256: null\nlayout:\n version: 3\nresolved_profile:\n id: null\n sha256: null\nmanaged_files: {}\n",
2468
2534
  "utf8",
2469
2535
  );
2470
2536
  const run = (
@@ -77,9 +77,10 @@ import type { SqliteReadModelSourceCheckpoint } from "./sqlite-read-model-source
77
77
  import { sourceFingerprint } from "./sqlite-read-model-source-fingerprint.js";
78
78
 
79
79
  const dbRelativePath = ".local/sqlite/zaip-aios-dashboard.sqlite";
80
- // Schema 73 persists exact source checkpoints and parsed records for delta refresh.
80
+ // Schema 74 invalidates parser checkpoints produced before .agents/data became
81
+ // canonical input. A schema rebuild is required even when source hashes match.
81
82
  // Older snapshots initialize these inputs once in the same database transaction.
82
- const readModelSchemaVersion = "73";
83
+ const readModelSchemaVersion = "74";
83
84
  const requiredPersistedTables = [
84
85
  "zaip_aios_activities",
85
86
  "zaip_dim_llm_model",
@@ -598,12 +599,12 @@ export async function rebuildSqliteReadModel(
598
599
  source.includes("/historical-developer-attribution/"),
599
600
  );
600
601
  const entityChanged = [...changedSources].some((source) =>
601
- /^\.agents\/records\/(tasks|specs|prds)\//.test(source),
602
+ /^\.agents\/(?:data|records)\/(tasks|specs|prds)\//.test(source),
602
603
  );
603
604
  const changedCreationRunIds = new Set<string>();
604
605
  if (hasPersistentSchema)
605
606
  for (const source of changedSources) {
606
- if (!source.startsWith(".agents/records/specs/")) continue;
607
+ if (!/^\.agents\/(?:data|records)\/specs\//.test(source)) continue;
607
608
  const cached = database
608
609
  .prepare(
609
610
  "SELECT payload_json FROM zaip_aios_activities WHERE payload_type='parsed_source' AND activity_key=?",
@@ -620,6 +621,14 @@ export async function rebuildSqliteReadModel(
620
621
  .filter((row) => configurationChanged || changedSources.has(row.source_path))
621
622
  .map((row) => String(row.run_id)),
622
623
  );
624
+ // A newly-added alternate layout has no prior activity row, but can name
625
+ // the same Run already materialized from the current layout. Mark that
626
+ // Run dirty before retaining canonical facts so the later preferred fold
627
+ // replaces it instead of colliding with a preserved run-index row.
628
+ for (const source of changedSources) {
629
+ const match = /^\.agents\/(?:data|records)\/runs\/.*\/([^/]+)\.jsonl$/.exec(source);
630
+ if (match?.[1]) dirtyRunIds.add(match[1]);
631
+ }
623
632
  // A parent identity change may affect descendants even when their own bytes are unchanged.
624
633
  let expanded = true;
625
634
  while (expanded) {
@@ -837,7 +846,7 @@ export async function rebuildSqliteReadModel(
837
846
  reportLaunchProgress("projection_build", 74, "Organizando fatos e projeções do banco local.");
838
847
  const canonicalRows = database
839
848
  .prepare(
840
- "SELECT path, content FROM files WHERE path LIKE '.agents/records/%' AND path NOT LIKE '.agents/records/runs/%'",
849
+ "SELECT path, content FROM files WHERE (path LIKE '.agents/data/%' OR path LIKE '.agents/records/%') AND path NOT LIKE '.agents/data/runs/%' AND path NOT LIKE '.agents/records/runs/%' ORDER BY CASE WHEN path LIKE '.agents/data/%' THEN 1 ELSE 0 END, path",
841
850
  )
842
851
  .all() as Array<{ path: string; content: string }>;
843
852
  const canonicalEntities = new Map<string, CanonicalEntity>(
@@ -851,7 +860,7 @@ export async function rebuildSqliteReadModel(
851
860
  ),
852
861
  );
853
862
  for (const row of canonicalRows)
854
- if (changedSources.has(row.path) && row.path.startsWith(".agents/records/specs/"))
863
+ if (changedSources.has(row.path) && /^\.agents\/(?:data|records)\/specs\//.test(row.path))
855
864
  for (const [, entity] of parsedSources.read<Array<[string, CanonicalEntity]>>(
856
865
  "entities",
857
866
  row.path,
@@ -897,6 +906,17 @@ export async function rebuildSqliteReadModel(
897
906
  canonicalLifecycle.changes.push(...parsed.changes);
898
907
  canonicalLifecycle.decisions.push(...parsed.decisions);
899
908
  }
909
+ // Both supported layouts can coexist during an upgrade. SQL ordering puts
910
+ // .agents/data last, so the installed current record wins by stable ID.
911
+ canonicalLifecycle.executions = [
912
+ ...new Map(canonicalLifecycle.executions.map((item) => [item.id, item])).values(),
913
+ ];
914
+ canonicalLifecycle.changes = [
915
+ ...new Map(canonicalLifecycle.changes.map((item) => [item.id, item])).values(),
916
+ ];
917
+ canonicalLifecycle.decisions = [
918
+ ...new Map(canonicalLifecycle.decisions.map((item) => [item.id, item])).values(),
919
+ ];
900
920
  const insertCanonicalExecution = database.prepare(
901
921
  "INSERT INTO canonical_executions(id, status, kind, origin_type, origin_id, created_at, occurred_day_sp, finished_at, source_path) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
902
922
  );
@@ -982,7 +1002,7 @@ export async function rebuildSqliteReadModel(
982
1002
  );
983
1003
  const runRows = database
984
1004
  .prepare(
985
- "SELECT path, content, content_sha256 FROM files WHERE path LIKE '.agents/records/runs/%.jsonl' ORDER BY path",
1005
+ "SELECT path, content, content_sha256 FROM files WHERE (path LIKE '.agents/data/runs/%.jsonl' OR path LIKE '.agents/records/runs/%.jsonl') ORDER BY CASE WHEN path LIKE '.agents/data/%' THEN 1 ELSE 0 END, path",
986
1006
  )
987
1007
  .all() as Array<{ path: string; content: string; content_sha256: string }>;
988
1008
 
@@ -992,7 +1012,7 @@ export async function rebuildSqliteReadModel(
992
1012
  const historicalDeveloperManifests = (
993
1013
  database
994
1014
  .prepare(
995
- "SELECT content FROM files WHERE path LIKE '.agents/records/lifecycle/historical-developer-attribution/%.json'",
1015
+ "SELECT content FROM files WHERE path LIKE '.agents/data/lifecycle/historical-developer-attribution/%.json' OR path LIKE '.agents/records/lifecycle/historical-developer-attribution/%.json'",
996
1016
  )
997
1017
  .all() as Array<{ content: string }>
998
1018
  ).flatMap((row) => {
@@ -1047,7 +1067,9 @@ export async function rebuildSqliteReadModel(
1047
1067
  );
1048
1068
  for (const previous of previousActivities) {
1049
1069
  const row = runRows.find((row) => row.path === previous.source_path);
1050
- if (row) runRowsById.set(previous.run_id, row);
1070
+ const selected = runRowsById.get(previous.run_id);
1071
+ if (row && (selected === undefined || !selected.path.startsWith(".agents/data/")))
1072
+ runRowsById.set(previous.run_id, row);
1051
1073
  }
1052
1074
  const folds = new Map<string, { row: (typeof runRows)[number]; fold: RunV2Fold }>();
1053
1075
  const readFold = (row: (typeof runRows)[number]) =>
@@ -3031,6 +3053,13 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
3031
3053
  // exact filter results inside the immutable generation so a tab switch does
3032
3054
  // not repeat its multi-second SQL scan.
3033
3055
  private readonly costMonitorSummaryCache = new Map<string, CostSummarySource>();
3056
+
3057
+ canonicalSource(): ".agents/data" | ".agents/records" {
3058
+ const current = this.database
3059
+ .prepare("SELECT 1 FROM files WHERE path LIKE '.agents/data/%' LIMIT 1")
3060
+ .get();
3061
+ return current === undefined ? ".agents/records" : ".agents/data";
3062
+ }
3034
3063
  private dailyAggregateRows(): Array<Record<string, number | string | null>> {
3035
3064
  if (this.dailyAggregateRowsCache === null) {
3036
3065
  this.dailyAggregateRowsCache = this.database
@@ -3527,7 +3556,7 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
3527
3556
  const clauses = selected.map(() => "content LIKE ?").join(" OR ");
3528
3557
  return this.database
3529
3558
  .prepare(`SELECT path, content FROM files
3530
- WHERE path LIKE '.agents/records/%' AND (${clauses}) ORDER BY path`)
3559
+ WHERE (path LIKE '.agents/data/%' OR path LIKE '.agents/records/%') AND (${clauses}) ORDER BY path`)
3531
3560
  .all(...selected.map((marker) => `%${marker}%`)) as Array<{
3532
3561
  path: string;
3533
3562
  content: string;