@zaip-ai/cli 3.0.0 → 3.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.
- package/README.md +11 -5
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +16 -6
- package/dist/commands/init.js.map +1 -1
- package/dist/services/adapters.d.ts +1 -1
- package/dist/services/adapters.js +1 -1
- package/dist/services/github-setup-status.d.ts +12 -0
- package/dist/services/github-setup-status.d.ts.map +1 -0
- package/dist/services/github-setup-status.js +84 -0
- package/dist/services/github-setup-status.js.map +1 -0
- package/dist/services/github-setup-status.test.d.ts +2 -0
- package/dist/services/github-setup-status.test.d.ts.map +1 -0
- package/dist/services/github-setup-status.test.js +57 -0
- package/dist/services/github-setup-status.test.js.map +1 -0
- package/dist/services/logger.d.ts +10 -1
- package/dist/services/logger.d.ts.map +1 -1
- package/dist/services/logger.js +23 -6
- package/dist/services/logger.js.map +1 -1
- package/dist/services/logger.test.js +33 -5
- package/dist/services/logger.test.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/templates/.agents/README-ENG.md +5 -1
- package/templates/.agents/README.md +5 -1
- package/templates/.agents/config/agents/discovery.json +1 -1
- package/templates/.agents/dashboard/.zaip-managed.json +65 -41
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-TnrL1ZyZ.js → DashboardHelpDialog-BzaUTZDx.js} +27 -12
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-DjQpBqGU.js → PricingSettingsDialog-21cCJik-.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-BQZXUnQu.js → TraceabilityWorkspace-wqW52uCx.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{dashboard-localization-Basaw1NR.js → dashboard-localization-BkAm8-h4.js} +5 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{index-BQe3zTvL.js → index-DqZKJS8y.js} +2 -2
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +1 -1
- package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
- package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
- package/templates/.agents/dashboard/modules/operations/package.json +1 -1
- package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
- package/templates/.agents/dashboard/packages/branding/package.json +1 -1
- package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
- package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +182 -160
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +174 -152
- package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/server.mjs +182 -160
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-calendar.ts +17 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cold-parse.test.ts +78 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-projection.test.ts +94 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-projection.ts +5 -5
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-duration-lookup.test.ts +59 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-entity-label-views.test.ts +72 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-materialize-runs.ts +3 -4
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-entity-group.test.ts +171 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-entity-group.ts +36 -16
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical-parsing.ts +7 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical.ts +5 -15
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-keeps-orphan-telemetry-out-kanban-without.test.ts +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-rebuild.ts +2 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-schema.ts +3 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-schema-base-views.ts +22 -14
- package/templates/.agents/dashboard/packages/ui/package.json +1 -1
- package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
- package/templates/.agents/design/.zaip-managed.json +3 -3
- package/templates/.agents/design/apps/catalog/package.json +1 -1
- package/templates/.agents/design/packages/local-runtime/package.json +1 -1
- package/templates/.agents/requirements/autonomous-assurance.md +1 -1
- package/templates/.agents/requirements/cloud-hostinger-autonomy.md +1 -1
- package/templates/.agents/requirements/comparative-audit.md +1 -1
- package/templates/.agents/requirements/context-engine.md +1 -1
- package/templates/.agents/requirements/cost-observability.md +1 -1
- package/templates/.agents/requirements/dashboard-launch.md +1 -1
- package/templates/.agents/requirements/data-export.md +1 -1
- package/templates/.agents/requirements/design-system.md +1 -1
- package/templates/.agents/requirements/execution-engine.md +1 -1
- package/templates/.agents/requirements/kanban-dashboard.md +1 -1
- package/templates/.agents/requirements/model-orchestration.md +1 -1
- package/templates/.agents/requirements/public-distribution.md +9 -2
- package/templates/.agents/requirements/software-architecture.md +1 -1
- package/templates/.agents/requirements/strategic-improvements.md +1 -1
- package/templates/.agents/requirements/work-lifecycle.md +1 -1
- package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
- package/templates/.agents/runtime/VERSION +1 -1
- package/templates/.agents/runtime/bin/zaip.mjs +5 -5
- package/templates/.agents/runtime/checksums.sha256 +5 -5
- package/templates/.agents/runtime/manifest.json +1 -1
- package/templates/.agents/runtime/provenance.json +4 -4
- package/templates/.agents/runtime/sbom.cdx.json +1 -1
- package/templates/.agents/skills/zaip-skill-config/SKILL.md +12 -0
- package/templates/.agents/skills/zaip-skill-github-deploy/references/release-identity.md +17 -12
- package/templates/.agents/skills/zaip-skill-github-setup/SKILL.md +7 -1
- package/templates/.agents/skills/zaip-skill-setup/SKILL.md +12 -0
- package/templates/.agents/zaip-aios/README.md +4 -3
- package/templates/.agents/zaip-aios/VERSION +1 -1
- package/templates/.agents/zaip-aios/guides/20-instalacao-detalhada.md +22 -1
- package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +20 -9
- package/templates/template-manifest.json +105 -81
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Intl's immutable formatter is reusable; timestamps and results are never cached.
|
|
2
|
+
const dateFormatter = new Intl.DateTimeFormat("en", {
|
|
3
|
+
timeZone: "America/Sao_Paulo",
|
|
4
|
+
year: "numeric",
|
|
5
|
+
month: "2-digit",
|
|
6
|
+
day: "2-digit",
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export function saoPauloDay(timestamp: string): string {
|
|
10
|
+
const values = Object.fromEntries(
|
|
11
|
+
dateFormatter
|
|
12
|
+
.formatToParts(new Date(timestamp))
|
|
13
|
+
.filter((part) => part.type !== "literal")
|
|
14
|
+
.map((part) => [part.type, part.value]),
|
|
15
|
+
) as { year: string; month: string; day: string };
|
|
16
|
+
return `${values.year}-${values.month}-${values.day}`;
|
|
17
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { expect, it, vi } from "vitest";
|
|
2
|
+
import { saoPauloDay } from "./sqlite-calendar.js";
|
|
3
|
+
import { readCanonicalLifecycleRecords } from "./sqlite-read-model-canonical.js";
|
|
4
|
+
import { isCanonicalLifecyclePath } from "./sqlite-read-model-canonical-parsing.js";
|
|
5
|
+
|
|
6
|
+
it("reuses only the immutable formatter and preserves calendar, DST and invalid input behavior", () => {
|
|
7
|
+
const cases = [
|
|
8
|
+
["2024-03-01T02:59:59Z", "2024-02-29"],
|
|
9
|
+
["2026-01-01T02:59:59Z", "2025-12-31"],
|
|
10
|
+
["2026-01-01T03:00:00Z", "2026-01-01"],
|
|
11
|
+
["2018-11-04T02:59:59Z", "2018-11-03"],
|
|
12
|
+
["2018-11-04T03:00:00Z", "2018-11-04"],
|
|
13
|
+
["2019-02-17T02:00:00Z", "2019-02-16"],
|
|
14
|
+
] as const;
|
|
15
|
+
const formatterConstructor = vi.spyOn(Intl, "DateTimeFormat");
|
|
16
|
+
try {
|
|
17
|
+
for (let index = 0; index < 100; index++)
|
|
18
|
+
for (const [timestamp, expected] of cases) expect(saoPauloDay(timestamp)).toBe(expected);
|
|
19
|
+
expect(() => saoPauloDay("invalid")).toThrow(RangeError);
|
|
20
|
+
expect(formatterConstructor).not.toHaveBeenCalled();
|
|
21
|
+
} finally {
|
|
22
|
+
formatterConstructor.mockRestore();
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("ignores irrelevant source content before YAML parsing and keeps both lifecycle layouts", () => {
|
|
27
|
+
const ignored = [
|
|
28
|
+
".agents/records/tasks/2026/09/task.md",
|
|
29
|
+
".agents/records/changes/2026/09/C/artifacts/tests/test.md",
|
|
30
|
+
".agents/records/runs/run.jsonl",
|
|
31
|
+
".agents/records/executions/E/other.md",
|
|
32
|
+
".agents/records/changes/C/decisions/other.txt",
|
|
33
|
+
"elsewhere/executions/E/execution.md",
|
|
34
|
+
].map((path) => ({
|
|
35
|
+
path,
|
|
36
|
+
get content(): string {
|
|
37
|
+
throw new Error("Irrelevant content must never be inspected");
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
40
|
+
expect(readCanonicalLifecycleRecords(ignored)).toEqual({
|
|
41
|
+
executions: [],
|
|
42
|
+
changes: [],
|
|
43
|
+
decisions: [],
|
|
44
|
+
});
|
|
45
|
+
for (const layout of ["data", "records"]) {
|
|
46
|
+
const rows = [
|
|
47
|
+
{
|
|
48
|
+
path: `.agents/${layout}/executions/2026/09/E/execution.md`,
|
|
49
|
+
content:
|
|
50
|
+
"---\nid: E\ncreated_at: 2026-09-15T00:00:00Z\nstatus: blocked\norigin:\n type: interaction\n conversation_id: C\n---\n",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
path: `.agents/${layout}/changes/2026/09/H/change.md`,
|
|
54
|
+
content:
|
|
55
|
+
"---\nid: H\ncreated_at: 2026-09-15T00:00:00Z\nsource:\n type: task\n task_id: T\n---\n",
|
|
56
|
+
},
|
|
57
|
+
...["changes", "executions"].map((kind) => ({
|
|
58
|
+
path: `.agents/${layout}/${kind}/E/artifacts/decisions/D.md`,
|
|
59
|
+
content: `---\nid: D-${kind}\ncreated_at: 2026-09-15T00:00:00Z\norigin: human\nscope_type: execution\nscope_id: E\n---\n`,
|
|
60
|
+
})),
|
|
61
|
+
];
|
|
62
|
+
expect(rows.every((row) => isCanonicalLifecyclePath(row.path))).toBe(true);
|
|
63
|
+
const result = readCanonicalLifecycleRecords([...ignored, ...rows]);
|
|
64
|
+
expect(result.executions).toHaveLength(1);
|
|
65
|
+
expect(result.executions[0]).toMatchObject({ id: "E", originId: "C", status: "blocked" });
|
|
66
|
+
expect(result.changes).toHaveLength(1);
|
|
67
|
+
expect(result.changes[0]).toMatchObject({ id: "H", sourceId: "T" });
|
|
68
|
+
expect(result.decisions).toHaveLength(2);
|
|
69
|
+
expect(
|
|
70
|
+
readCanonicalLifecycleRecords([
|
|
71
|
+
{
|
|
72
|
+
path: `.agents/${layout}/executions/E/execution.md`,
|
|
73
|
+
content: "---\nid: [invalid\n---\n",
|
|
74
|
+
},
|
|
75
|
+
]).executions,
|
|
76
|
+
).toEqual([]);
|
|
77
|
+
}
|
|
78
|
+
});
|
package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-projection.test.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import { expect, it } from "vitest";
|
|
3
|
+
import { materializeCostGroupDailyProjection } from "./sqlite-cost-group-projection.js";
|
|
4
|
+
import { baseViewSql } from "./sqlite-schema-base-views.js";
|
|
5
|
+
|
|
6
|
+
it("evaluates the invocation population once across all eight daily groups with legacy parity", () => {
|
|
7
|
+
const database = new DatabaseSync(":memory:");
|
|
8
|
+
let costReads = 0;
|
|
9
|
+
database.function("observed_cost", (id) => {
|
|
10
|
+
costReads++;
|
|
11
|
+
return id === 1 ? 0 : null;
|
|
12
|
+
});
|
|
13
|
+
try {
|
|
14
|
+
database.exec(`CREATE TABLE inputs(id INTEGER); INSERT INTO inputs VALUES (1),(2);
|
|
15
|
+
CREATE VIEW invocations AS SELECT '2026-09-15' AS occurred_day_sp,
|
|
16
|
+
'e' AS execution_id, 'a' AS zaip_agent, 'p' AS provider, 'm' AS model,
|
|
17
|
+
't' AS task_id, 's' AS spec_id, 'r' AS prd_id, 'c' AS conversation_id,
|
|
18
|
+
1 AS llm_call_count, NULL AS duration_ms, 2 AS input_tokens,
|
|
19
|
+
NULL AS cached_input_tokens, 3 AS output_tokens, NULL AS reasoning_tokens,
|
|
20
|
+
5 AS total_tokens, observed_cost(id) AS cost_usd, NULL AS cost_brl FROM inputs;`);
|
|
21
|
+
const definition = /CREATE VIEW cost_monitor_group_daily_cache AS ([^;]+);/.exec(
|
|
22
|
+
baseViewSql,
|
|
23
|
+
)?.[1];
|
|
24
|
+
if (!definition) throw new Error("Canonical daily group SQL absent");
|
|
25
|
+
database.exec(`CREATE VIEW measured AS ${definition};`);
|
|
26
|
+
const actual = database.prepare("SELECT * FROM measured ORDER BY group_kind").all();
|
|
27
|
+
expect(actual).toHaveLength(8);
|
|
28
|
+
expect(costReads).toBe(2);
|
|
29
|
+
const reference = definition
|
|
30
|
+
.replace(/WITH group_invocations AS MATERIALIZED \([^)]*\) /, "")
|
|
31
|
+
.replaceAll("FROM group_invocations", "FROM invocations");
|
|
32
|
+
costReads = 0;
|
|
33
|
+
const expected = database.prepare(`SELECT * FROM (${reference}) ORDER BY group_kind`).all();
|
|
34
|
+
expect(costReads).toBe(16);
|
|
35
|
+
expect(actual).toEqual(expected);
|
|
36
|
+
for (const row of actual) {
|
|
37
|
+
expect(row.invocation_count).toBe(2);
|
|
38
|
+
expect(row.total_cost_usd).toBe(0);
|
|
39
|
+
expect(row.total_cost_brl).toBeNull();
|
|
40
|
+
expect(row.total_duration).toBeNull();
|
|
41
|
+
}
|
|
42
|
+
} finally {
|
|
43
|
+
database.close();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("materializes exact labels without evaluating unrelated entity metrics", () => {
|
|
48
|
+
const database = new DatabaseSync(":memory:");
|
|
49
|
+
let irrelevantReads = 0;
|
|
50
|
+
database.function("unrelated_metric", () => {
|
|
51
|
+
irrelevantReads++;
|
|
52
|
+
throw new Error("Label projection must not evaluate unrelated metrics");
|
|
53
|
+
});
|
|
54
|
+
try {
|
|
55
|
+
database.exec(
|
|
56
|
+
`CREATE TABLE zaip_vw_cost_monitor_group_daily_cache(group_kind TEXT, day TEXT, group_value TEXT, cost REAL);`,
|
|
57
|
+
);
|
|
58
|
+
const insert = database.prepare(
|
|
59
|
+
"INSERT INTO zaip_vw_cost_monitor_group_daily_cache VALUES (?, '2026-09-15', ?, 2)",
|
|
60
|
+
);
|
|
61
|
+
for (const kind of ["task", "spec", "prd", "execution", "conversation"]) {
|
|
62
|
+
insert.run(kind, kind);
|
|
63
|
+
insert.run(kind, "missing");
|
|
64
|
+
insert.run(kind, null);
|
|
65
|
+
database.exec(
|
|
66
|
+
`CREATE VIEW zaip_vw_${kind}s AS SELECT '${kind}' AS id, 'Title' AS title, 'blocked' AS status, 'standard' AS risk, unrelated_metric() AS expensive_unused;`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
materializeCostGroupDailyProjection(database);
|
|
70
|
+
const rows = database
|
|
71
|
+
.prepare("SELECT * FROM cost_group_entity_projection ORDER BY group_kind, group_value")
|
|
72
|
+
.all();
|
|
73
|
+
expect(rows).toHaveLength(10);
|
|
74
|
+
for (const row of rows) {
|
|
75
|
+
expect(row.status).toBe(row.group_value === "missing" ? null : "blocked");
|
|
76
|
+
const titled =
|
|
77
|
+
row.group_value !== "missing" && ["task", "spec", "prd"].includes(String(row.group_kind));
|
|
78
|
+
expect(row.title).toBe(titled ? "Title" : null);
|
|
79
|
+
expect(row.risk).toBe(titled ? "standard" : null);
|
|
80
|
+
}
|
|
81
|
+
expect(irrelevantReads).toBe(0);
|
|
82
|
+
materializeCostGroupDailyProjection(database);
|
|
83
|
+
expect(
|
|
84
|
+
database
|
|
85
|
+
.prepare("SELECT * FROM cost_group_entity_projection ORDER BY group_kind, group_value")
|
|
86
|
+
.all(),
|
|
87
|
+
).toEqual(rows);
|
|
88
|
+
expect(database.prepare("SELECT COUNT(*) AS n FROM cost_group_daily_projection").get()?.n).toBe(
|
|
89
|
+
15,
|
|
90
|
+
);
|
|
91
|
+
} finally {
|
|
92
|
+
database.close();
|
|
93
|
+
}
|
|
94
|
+
});
|
package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-projection.ts
CHANGED
|
@@ -15,11 +15,11 @@ export function materializeCostGroupDailyProjection(database: DatabaseSync): voi
|
|
|
15
15
|
CREATE TABLE IF NOT EXISTS cost_group_entity_projection(group_kind TEXT, group_value TEXT, title TEXT, status TEXT, risk TEXT);
|
|
16
16
|
DELETE FROM cost_group_entity_projection;
|
|
17
17
|
INSERT INTO cost_group_entity_projection
|
|
18
|
-
WITH tasks AS MATERIALIZED (SELECT
|
|
19
|
-
specs AS MATERIALIZED (SELECT
|
|
20
|
-
prds AS MATERIALIZED (SELECT
|
|
21
|
-
executions AS MATERIALIZED (SELECT
|
|
22
|
-
conversations AS MATERIALIZED (SELECT
|
|
18
|
+
WITH tasks AS MATERIALIZED (SELECT id, title, status, risk FROM zaip_vw_tasks),
|
|
19
|
+
specs AS MATERIALIZED (SELECT id, title, status, risk FROM zaip_vw_specs),
|
|
20
|
+
prds AS MATERIALIZED (SELECT id, title, status, risk FROM zaip_vw_prds),
|
|
21
|
+
executions AS MATERIALIZED (SELECT id, status FROM zaip_vw_executions),
|
|
22
|
+
conversations AS MATERIALIZED (SELECT id, status FROM zaip_vw_conversations)
|
|
23
23
|
SELECT 'task' AS group_kind, groups.group_value, entity.title, entity.status, entity.risk
|
|
24
24
|
FROM (SELECT DISTINCT group_value FROM cost_group_daily_projection
|
|
25
25
|
WHERE group_kind = 'task' AND group_value IS NOT NULL) groups
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import { expect, it } from "vitest";
|
|
3
|
+
import { baseViewSql } from "./sqlite-schema-base-views.js";
|
|
4
|
+
|
|
5
|
+
it("uses indexed duration lookup with exact legacy sums and nulls and no duplicate Run rows", () => {
|
|
6
|
+
const db = new DatabaseSync(":memory:");
|
|
7
|
+
try {
|
|
8
|
+
db.exec(
|
|
9
|
+
"CREATE TABLE zaip_aios_activities(activity_row_id TEXT, payload_type TEXT, payload_json TEXT);",
|
|
10
|
+
);
|
|
11
|
+
const insert = db.prepare("INSERT INTO zaip_aios_activities VALUES(?,?,?)");
|
|
12
|
+
for (const id of ["known", "zero", "unknown", "missing"]) insert.run(id, null, null);
|
|
13
|
+
for (const [id, values] of [
|
|
14
|
+
["known", [3, 5, null]],
|
|
15
|
+
["zero", [0]],
|
|
16
|
+
["unknown", [null]],
|
|
17
|
+
["unrelated", [900]],
|
|
18
|
+
] as const)
|
|
19
|
+
for (const duration of values)
|
|
20
|
+
insert.run(
|
|
21
|
+
`fact-${id}`,
|
|
22
|
+
"duration_allocation",
|
|
23
|
+
JSON.stringify({ activity_row_id: id, attributed_token_active_ms: duration }),
|
|
24
|
+
);
|
|
25
|
+
insert.run(
|
|
26
|
+
"technical",
|
|
27
|
+
"other",
|
|
28
|
+
JSON.stringify({ activity_row_id: "known", attributed_token_active_ms: 999 }),
|
|
29
|
+
);
|
|
30
|
+
const index = /CREATE INDEX zaip_activities_duration_activity[^;]+;/.exec(baseViewSql)?.[0];
|
|
31
|
+
const view = /CREATE VIEW activities AS\s+([^;]+);/.exec(baseViewSql)?.[1];
|
|
32
|
+
if (!index || !view) throw new Error("Canonical indexed duration SQL missing");
|
|
33
|
+
db.exec(`${index} CREATE VIEW activities AS ${view};`);
|
|
34
|
+
const query =
|
|
35
|
+
"SELECT activity_row_id,attributed_token_active_ms FROM activities ORDER BY activity_row_id";
|
|
36
|
+
const actual = db.prepare(query).all();
|
|
37
|
+
const expected = db
|
|
38
|
+
.prepare(`SELECT a.activity_row_id,f.duration AS attributed_token_active_ms
|
|
39
|
+
FROM zaip_aios_activities a LEFT JOIN (
|
|
40
|
+
SELECT json_extract(payload_json,'$.activity_row_id') AS activity_row_id,
|
|
41
|
+
SUM(json_extract(payload_json,'$.attributed_token_active_ms')) AS duration
|
|
42
|
+
FROM zaip_aios_activities WHERE payload_type='duration_allocation' GROUP BY activity_row_id
|
|
43
|
+
) f USING(activity_row_id) WHERE a.payload_type IS NULL ORDER BY a.activity_row_id`)
|
|
44
|
+
.all();
|
|
45
|
+
expect(actual).toEqual(expected);
|
|
46
|
+
expect(actual).toHaveLength(4);
|
|
47
|
+
expect(actual.map((row) => row.attributed_token_active_ms)).toEqual([8, null, null, 0]);
|
|
48
|
+
const plan = db.prepare(`EXPLAIN QUERY PLAN ${query}`).all();
|
|
49
|
+
expect(
|
|
50
|
+
plan.some((row) =>
|
|
51
|
+
String(row.detail).includes("zaip_activities_duration_activity (<expr>=?)"),
|
|
52
|
+
),
|
|
53
|
+
).toBe(true);
|
|
54
|
+
const labelPlan = db.prepare("EXPLAIN QUERY PLAN SELECT activity_row_id FROM activities").all();
|
|
55
|
+
expect(labelPlan.some((row) => String(row.detail).includes("CORRELATED"))).toBe(false);
|
|
56
|
+
} finally {
|
|
57
|
+
db.close();
|
|
58
|
+
}
|
|
59
|
+
});
|
package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-entity-label-views.test.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import { expect, it } from "vitest";
|
|
3
|
+
import { baseViewSql } from "./sqlite-schema-base-views.js";
|
|
4
|
+
|
|
5
|
+
it("preserves canonical precedence and legacy labels without repeated payload or metric expansion", () => {
|
|
6
|
+
const db = new DatabaseSync(":memory:");
|
|
7
|
+
let payloadReads = 0;
|
|
8
|
+
let metricReads = 0;
|
|
9
|
+
db.function("payload_origin", (value) => {
|
|
10
|
+
payloadReads++;
|
|
11
|
+
return value;
|
|
12
|
+
});
|
|
13
|
+
db.function("unused_metric", () => {
|
|
14
|
+
metricReads++;
|
|
15
|
+
return 42;
|
|
16
|
+
});
|
|
17
|
+
try {
|
|
18
|
+
db.exec(`
|
|
19
|
+
CREATE TABLE inputs(id TEXT, status TEXT, created_at TEXT, occurred_day_sp TEXT, origin_type TEXT, origin_id TEXT);
|
|
20
|
+
INSERT INTO inputs VALUES
|
|
21
|
+
('e1','blocked','2026-09-01','2026-09-01','interaction','c1'),
|
|
22
|
+
('e2','completed','2026-09-02','2026-09-02','interaction','c1'),
|
|
23
|
+
('e3',NULL,'2026-09-03','2026-09-03','task','not-conversation'),
|
|
24
|
+
('e4','failed','2026-09-04','2026-09-04','interaction',NULL);
|
|
25
|
+
CREATE VIEW canonical_executions AS SELECT id,status,created_at,occurred_day_sp,origin_type,
|
|
26
|
+
payload_origin(origin_id) AS origin_id FROM inputs;
|
|
27
|
+
CREATE TABLE zaip_aios_activities(payload_type TEXT, execution_id TEXT, conversation_id TEXT,
|
|
28
|
+
status TEXT, occurred_at TEXT, occurred_day_sp TEXT);
|
|
29
|
+
INSERT INTO zaip_aios_activities VALUES
|
|
30
|
+
(NULL,'e1','c1','failed','2026-08-01','2026-08-01'),
|
|
31
|
+
(NULL,'legacy','legacy-c','blocked','2026-09-05','2026-09-05'),
|
|
32
|
+
(NULL,'legacy','legacy-c','completed','2026-09-06','2026-09-06'),
|
|
33
|
+
(NULL,'unknown','unknown-c',NULL,NULL,NULL),
|
|
34
|
+
('technical','fake','fake-c','completed','2026-01-01','2026-01-01'),
|
|
35
|
+
(NULL,NULL,NULL,'completed','2026-01-01','2026-01-01');
|
|
36
|
+
CREATE VIEW activities AS SELECT a.*, f.metric FROM zaip_aios_activities a
|
|
37
|
+
LEFT JOIN (SELECT execution_id, SUM(unused_metric()) AS metric FROM zaip_aios_activities GROUP BY execution_id) f
|
|
38
|
+
USING(execution_id) WHERE a.payload_type IS NULL;
|
|
39
|
+
`);
|
|
40
|
+
const reference = {
|
|
41
|
+
executions: `SELECT id,status,created_at AS occurred_at,occurred_day_sp FROM canonical_executions
|
|
42
|
+
UNION ALL SELECT execution_id,MAX(status),MIN(occurred_at),MIN(occurred_day_sp)
|
|
43
|
+
FROM activities WHERE execution_id IS NOT NULL AND NOT EXISTS
|
|
44
|
+
(SELECT 1 FROM canonical_executions c WHERE c.id=activities.execution_id) GROUP BY execution_id`,
|
|
45
|
+
conversations: `SELECT origin_id AS id,MAX(status) AS status,MIN(created_at) AS occurred_at,MIN(occurred_day_sp) AS occurred_day_sp
|
|
46
|
+
FROM canonical_executions WHERE origin_type='interaction' AND origin_id IS NOT NULL GROUP BY origin_id
|
|
47
|
+
UNION ALL SELECT conversation_id,MAX(status),MIN(occurred_at),MIN(occurred_day_sp)
|
|
48
|
+
FROM activities WHERE conversation_id IS NOT NULL AND NOT EXISTS
|
|
49
|
+
(SELECT 1 FROM canonical_executions c WHERE c.origin_type='interaction' AND c.origin_id=activities.conversation_id) GROUP BY conversation_id`,
|
|
50
|
+
};
|
|
51
|
+
for (const name of ["executions", "conversations"] as const) {
|
|
52
|
+
const expected = db.prepare(`SELECT * FROM (${reference[name]}) ORDER BY id`).all();
|
|
53
|
+
const repeatedReads = payloadReads;
|
|
54
|
+
payloadReads = 0;
|
|
55
|
+
metricReads = 0;
|
|
56
|
+
const definition = new RegExp(`CREATE VIEW ${name} AS\\s+([^;]+);`).exec(baseViewSql)?.[1];
|
|
57
|
+
if (!definition) throw new Error(`Missing canonical view ${name}`);
|
|
58
|
+
const actual = db.prepare(`SELECT * FROM (${definition}) ORDER BY id`).all();
|
|
59
|
+
expect(actual).toEqual(expected);
|
|
60
|
+
expect(actual.some((row) => String(row.id).startsWith("fake"))).toBe(false);
|
|
61
|
+
expect(metricReads).toBe(0);
|
|
62
|
+
if (name === "conversations") {
|
|
63
|
+
expect(payloadReads).toBe(4);
|
|
64
|
+
expect(repeatedReads).toBeGreaterThan(payloadReads);
|
|
65
|
+
expect(actual.find((row) => row.id === "c1")?.status).toBe("completed");
|
|
66
|
+
expect(actual.find((row) => row.id === "unknown-c")?.status).toBeNull();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} finally {
|
|
70
|
+
db.close();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import type { DatabaseSync } from "node:sqlite";
|
|
3
|
-
import {
|
|
3
|
+
import { prepareHistoricalDeveloperAttribution, pricingCatalogSchema } from "@zaip-ai/costs";
|
|
4
4
|
import {
|
|
5
5
|
foldRunV2Records,
|
|
6
6
|
parseRunV2JsonLines,
|
|
@@ -97,7 +97,7 @@ export function materializeCanonicalRuns(input: RunMaterializationInput) {
|
|
|
97
97
|
.all() as Array<{ content: string }>
|
|
98
98
|
).flatMap((row) => {
|
|
99
99
|
try {
|
|
100
|
-
return [JSON.parse(row.content)];
|
|
100
|
+
return [prepareHistoricalDeveloperAttribution(JSON.parse(row.content))];
|
|
101
101
|
} catch {
|
|
102
102
|
return [];
|
|
103
103
|
}
|
|
@@ -117,8 +117,7 @@ export function materializeCanonicalRuns(input: RunMaterializationInput) {
|
|
|
117
117
|
};
|
|
118
118
|
for (const candidate of historicalDeveloperManifests) {
|
|
119
119
|
try {
|
|
120
|
-
const resolved =
|
|
121
|
-
manifest: candidate,
|
|
120
|
+
const resolved = candidate({
|
|
122
121
|
run_id: String(JSON.parse(row.content.split(/\r?\n/u)[0] ?? "{}").run_id ?? ""),
|
|
123
122
|
path: row.path,
|
|
124
123
|
content: row.content,
|
package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-entity-group.test.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import type { SqliteViewInput } from "./sqlite-query-context.js";
|
|
4
|
+
import { readEntityGroup } from "./sqlite-query-entity-group.js";
|
|
5
|
+
|
|
6
|
+
const databases: DatabaseSync[] = [];
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
vi.restoreAllMocks();
|
|
9
|
+
for (const database of databases.splice(0)) database.close();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
function fixture(kind: "spec" | "prd") {
|
|
13
|
+
const database = new DatabaseSync(":memory:");
|
|
14
|
+
databases.push(database);
|
|
15
|
+
database.exec(`
|
|
16
|
+
CREATE TABLE ${kind}s (id TEXT PRIMARY KEY, title TEXT, status TEXT, risk TEXT,
|
|
17
|
+
occurred_at TEXT, occurred_day_sp TEXT);
|
|
18
|
+
CREATE TABLE invocations (agent_invocation_id TEXT PRIMARY KEY, ${kind}_id TEXT,
|
|
19
|
+
occurred_at TEXT, occurred_day_sp TEXT, llm_call_count INTEGER, duration_ms REAL,
|
|
20
|
+
input_tokens REAL, cached_input_tokens REAL, output_tokens REAL, reasoning_tokens REAL,
|
|
21
|
+
total_tokens REAL, cost_usd REAL, cost_brl REAL);
|
|
22
|
+
`);
|
|
23
|
+
const entity = (id: string, day = "2026-09-10", status = "completed", risk = "standard") =>
|
|
24
|
+
database
|
|
25
|
+
.prepare(`INSERT INTO ${kind}s VALUES (?, ?, ?, ?, ?, ?)`)
|
|
26
|
+
.run(id, `Title ${id}`, status, risk, `${day}T12:00:00Z`, day);
|
|
27
|
+
const invocation = (id: string, parent: string, day: string, cost: number | null) =>
|
|
28
|
+
database
|
|
29
|
+
.prepare("INSERT INTO invocations VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
|
30
|
+
.run(
|
|
31
|
+
id,
|
|
32
|
+
parent,
|
|
33
|
+
`${day}T13:00:00Z`,
|
|
34
|
+
day,
|
|
35
|
+
cost === null ? null : 1,
|
|
36
|
+
cost,
|
|
37
|
+
cost,
|
|
38
|
+
cost,
|
|
39
|
+
cost,
|
|
40
|
+
cost,
|
|
41
|
+
cost,
|
|
42
|
+
cost,
|
|
43
|
+
cost,
|
|
44
|
+
);
|
|
45
|
+
const read = (input: SqliteViewInput = {}) =>
|
|
46
|
+
readEntityGroup(
|
|
47
|
+
{ database },
|
|
48
|
+
kind === "spec" ? "cost_monitor_specs" : "cost_monitor_prds",
|
|
49
|
+
input,
|
|
50
|
+
kind,
|
|
51
|
+
);
|
|
52
|
+
return { database, entity, invocation, read };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe.each(["spec", "prd"] as const)("canonical %s single grouped query", (kind) => {
|
|
56
|
+
it("computes full totals and deterministic pages with one SQL statement", () => {
|
|
57
|
+
const { database, entity, invocation, read } = fixture(kind);
|
|
58
|
+
for (let i = 0; i < 12; i++) {
|
|
59
|
+
const id = `E${String(i).padStart(2, "0")}`;
|
|
60
|
+
entity(id);
|
|
61
|
+
invocation(`I${i}`, id, "2026-09-10", 0.12345678901234566);
|
|
62
|
+
}
|
|
63
|
+
const prepare = vi.spyOn(database, "prepare");
|
|
64
|
+
const first = read({ limit: 10 });
|
|
65
|
+
expect(prepare).toHaveBeenCalledTimes(1);
|
|
66
|
+
prepare.mockClear();
|
|
67
|
+
const second = read({ limit: 10, offset: 10 });
|
|
68
|
+
expect(prepare).toHaveBeenCalledTimes(1);
|
|
69
|
+
expect(first.total_count).toBe(12);
|
|
70
|
+
expect(first.has_more).toBe(true);
|
|
71
|
+
expect(second.has_more).toBe(false);
|
|
72
|
+
expect(second.rows.map((row) => row.id)).toEqual(["E10", "E11"]);
|
|
73
|
+
expect(first.totals).toEqual(second.totals);
|
|
74
|
+
expect(first.totals).toMatchObject({
|
|
75
|
+
entity_count: 12,
|
|
76
|
+
invocation_count: 12,
|
|
77
|
+
total_duration: 1,
|
|
78
|
+
});
|
|
79
|
+
expect(first.rows[0]).toMatchObject({
|
|
80
|
+
id: "E00",
|
|
81
|
+
key: "E00",
|
|
82
|
+
total_cost_brl: 0.12345678901234566,
|
|
83
|
+
});
|
|
84
|
+
expect(Object.keys(first.rows[0] ?? {}).some((key) => key.startsWith("__"))).toBe(false);
|
|
85
|
+
const beyond = read({ limit: 10, offset: 99 });
|
|
86
|
+
expect(beyond).toMatchObject({
|
|
87
|
+
total_count: 12,
|
|
88
|
+
rows: [],
|
|
89
|
+
totals: first.totals,
|
|
90
|
+
has_more: false,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("retains canonical entity dates, no-run unknowns and observed zero with filters", () => {
|
|
95
|
+
const { entity, invocation, read } = fixture(kind);
|
|
96
|
+
entity("CURRENT");
|
|
97
|
+
invocation("old-run", "CURRENT", "2026-08-01", 100);
|
|
98
|
+
entity("ZERO");
|
|
99
|
+
invocation("zero", "ZERO", "2026-09-10", 0);
|
|
100
|
+
entity("UNKNOWN");
|
|
101
|
+
invocation("unknown", "UNKNOWN", "2026-09-10", null);
|
|
102
|
+
entity("NO-RUN");
|
|
103
|
+
entity("OLD", "2026-08-01");
|
|
104
|
+
invocation("new-run", "OLD", "2026-09-10", 999);
|
|
105
|
+
entity("BLOCKED", "2026-09-10", "blocked", "critical");
|
|
106
|
+
const page = read({
|
|
107
|
+
from: "2026-09-01",
|
|
108
|
+
to: "2026-09-30",
|
|
109
|
+
status: "completed",
|
|
110
|
+
risk: "standard",
|
|
111
|
+
});
|
|
112
|
+
expect(page.total_count).toBe(4);
|
|
113
|
+
expect(page.totals).toEqual({
|
|
114
|
+
entity_count: 4,
|
|
115
|
+
invocation_count: 2,
|
|
116
|
+
total_duration: 0,
|
|
117
|
+
total_tokens: 0,
|
|
118
|
+
total_cost_usd: 0,
|
|
119
|
+
total_cost_brl: 0,
|
|
120
|
+
});
|
|
121
|
+
expect(page.rows.find((row) => row.id === "CURRENT")).toMatchObject({
|
|
122
|
+
invocation_count: 0,
|
|
123
|
+
total_cost_brl: null,
|
|
124
|
+
first_day: "2026-09-10",
|
|
125
|
+
occurred_at: "2026-09-10T12:00:00Z",
|
|
126
|
+
});
|
|
127
|
+
expect(page.rows.find((row) => row.id === "ZERO")).toMatchObject({
|
|
128
|
+
total_cost_brl: 0,
|
|
129
|
+
invocation_count: 1,
|
|
130
|
+
});
|
|
131
|
+
expect(page.rows.find((row) => row.id === "UNKNOWN")).toMatchObject({
|
|
132
|
+
total_cost_brl: null,
|
|
133
|
+
invocation_count: 1,
|
|
134
|
+
});
|
|
135
|
+
expect(page.rows.find((row) => row.id === "NO-RUN")).toMatchObject({
|
|
136
|
+
total_cost_brl: null,
|
|
137
|
+
invocation_count: 0,
|
|
138
|
+
});
|
|
139
|
+
expect(read({ query: "Title BLOCKED", risk: "critical" }).rows.map((row) => row.id)).toEqual([
|
|
140
|
+
"BLOCKED",
|
|
141
|
+
]);
|
|
142
|
+
expect(read({ query: "CURRENT" }).totals.total_cost_brl).toBe(100);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("returns nullable empty totals and clamps page boundaries without losing counts", () => {
|
|
146
|
+
const { entity, read } = fixture(kind);
|
|
147
|
+
expect(read({ limit: 0, offset: -2 })).toMatchObject({
|
|
148
|
+
rows: [],
|
|
149
|
+
total_count: 0,
|
|
150
|
+
limit: 1,
|
|
151
|
+
offset: 0,
|
|
152
|
+
has_more: false,
|
|
153
|
+
totals: {
|
|
154
|
+
entity_count: 0,
|
|
155
|
+
invocation_count: 0,
|
|
156
|
+
total_duration: null,
|
|
157
|
+
total_tokens: null,
|
|
158
|
+
total_cost_usd: null,
|
|
159
|
+
total_cost_brl: null,
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
entity("ONLY");
|
|
163
|
+
expect(read({ limit: 1000, offset: 1 })).toMatchObject({
|
|
164
|
+
rows: [],
|
|
165
|
+
total_count: 1,
|
|
166
|
+
limit: 200,
|
|
167
|
+
has_more: false,
|
|
168
|
+
totals: { entity_count: 1, invocation_count: 0, total_cost_brl: null },
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -2,7 +2,7 @@ import { type CostGroupKind, readCostGroupPage } from "./sqlite-cost-group-page.
|
|
|
2
2
|
import type { SqliteQueryContext, SqliteViewInput } from "./sqlite-query-context.js";
|
|
3
3
|
import type { SqliteViewName } from "./sqlite-read-model-types.js";
|
|
4
4
|
export function readEntityGroup(
|
|
5
|
-
context: SqliteQueryContext,
|
|
5
|
+
context: Pick<SqliteQueryContext, "database">,
|
|
6
6
|
view: SqliteViewName,
|
|
7
7
|
input: SqliteViewInput,
|
|
8
8
|
groupKind: string,
|
|
@@ -55,7 +55,7 @@ export function readEntityGroup(
|
|
|
55
55
|
const entityWhere = entityClauses.length === 0 ? "1" : entityClauses.join(" AND ");
|
|
56
56
|
const invocationJoin =
|
|
57
57
|
invocationClauses.length === 0 ? "" : ` AND ${invocationClauses.join(" AND ")}`;
|
|
58
|
-
const ctes = `WITH grouped AS (
|
|
58
|
+
const ctes = `WITH grouped AS MATERIALIZED (
|
|
59
59
|
SELECT entity.id AS group_value, entity.title, entity.status, entity.risk,
|
|
60
60
|
COALESCE(MIN(i.occurred_day_sp), entity.occurred_day_sp) AS first_day,
|
|
61
61
|
COALESCE(MAX(i.occurred_day_sp), entity.occurred_day_sp) AS last_day,
|
|
@@ -76,23 +76,43 @@ export function readEntityGroup(
|
|
|
76
76
|
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
77
77
|
const offset = Math.max(0, input.offset ?? 0);
|
|
78
78
|
const values = [...invocationValues, ...entityValues];
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
.prepare(`${ctes} SELECT COUNT(*) AS count FROM grouped`)
|
|
83
|
-
.get(...values) as { count: number }
|
|
84
|
-
).count,
|
|
85
|
-
);
|
|
86
|
-
const totals = context.database
|
|
79
|
+
// Materialize this population once for count, totals and the requested page.
|
|
80
|
+
// A totals-first LEFT JOIN retains totals even for an empty/out-of-range page.
|
|
81
|
+
const result = context.database
|
|
87
82
|
.prepare(
|
|
88
|
-
`${ctes}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
83
|
+
`${ctes}, totals AS (
|
|
84
|
+
SELECT COUNT(*) AS __entity_count,
|
|
85
|
+
COALESCE(SUM(invocation_count), 0) AS __invocation_count,
|
|
86
|
+
ROUND(SUM(total_duration)) AS __total_duration,
|
|
87
|
+
SUM(total_tokens) AS __total_tokens,
|
|
88
|
+
SUM(total_cost_usd) AS __total_cost_usd,
|
|
89
|
+
SUM(total_cost_brl) AS __total_cost_brl FROM grouped
|
|
90
|
+
), paged AS (
|
|
91
|
+
SELECT 1 AS __row_present, group_value AS id, group_value AS key,
|
|
92
|
+
title, status, risk, first_day, last_day, occurred_at, invocation_count,
|
|
93
|
+
llm_call_count, total_duration, total_input_tokens, total_cached_input_tokens,
|
|
94
|
+
total_output_tokens, total_reasoning_tokens, total_tokens, total_cost_usd,
|
|
95
|
+
total_cost_brl FROM grouped ORDER BY total_cost_brl DESC, group_value LIMIT ? OFFSET ?
|
|
96
|
+
) SELECT totals.*, paged.* FROM totals LEFT JOIN paged ON 1
|
|
97
|
+
ORDER BY paged.total_cost_brl DESC, paged.id`,
|
|
94
98
|
)
|
|
95
99
|
.all(...values, limit, offset) as Array<Record<string, unknown>>;
|
|
100
|
+
const aggregate = result[0];
|
|
101
|
+
if (!aggregate) throw new Error("Canonical macro query did not return its aggregate row.");
|
|
102
|
+
const total_count = Number(aggregate.__entity_count);
|
|
103
|
+
const totals = {
|
|
104
|
+
entity_count: aggregate.__entity_count,
|
|
105
|
+
invocation_count: aggregate.__invocation_count,
|
|
106
|
+
total_duration: aggregate.__total_duration,
|
|
107
|
+
total_tokens: aggregate.__total_tokens,
|
|
108
|
+
total_cost_usd: aggregate.__total_cost_usd,
|
|
109
|
+
total_cost_brl: aggregate.__total_cost_brl,
|
|
110
|
+
};
|
|
111
|
+
const rows = result
|
|
112
|
+
.filter((row) => row.__row_present === 1)
|
|
113
|
+
.map((row) =>
|
|
114
|
+
Object.fromEntries(Object.entries(row).filter(([key]) => !key.startsWith("__"))),
|
|
115
|
+
);
|
|
96
116
|
return {
|
|
97
117
|
data_source: "sqlite_read_model" as const,
|
|
98
118
|
view,
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { parseYaml } from "@zaip-ai/core";
|
|
2
2
|
|
|
3
|
+
/** Same accepted lifecycle paths as the parser, before any YAML or cache work. */
|
|
4
|
+
export function isCanonicalLifecyclePath(path: string): boolean {
|
|
5
|
+
return /^\.agents\/(?:data|records)\/(?:executions\/.*\/execution\.md|changes\/.*\/change\.md|(?:changes|executions)\/.*\/decisions\/.*\.md)$/.test(
|
|
6
|
+
path,
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
|
|
3
10
|
export function frontMatterRecord(content: string): Record<string, unknown> | null {
|
|
4
11
|
const source = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content)?.[1];
|
|
5
12
|
if (source === undefined) return null;
|