@aitne/shared 0.1.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/LICENSE +21 -0
- package/dist/advisor-models.d.ts +34 -0
- package/dist/advisor-models.d.ts.map +1 -0
- package/dist/advisor-models.js +39 -0
- package/dist/advisor-models.js.map +1 -0
- package/dist/agent-identity.d.ts +11 -0
- package/dist/agent-identity.d.ts.map +1 -0
- package/dist/agent-identity.js +29 -0
- package/dist/agent-identity.js.map +1 -0
- package/dist/alerts.d.ts +44 -0
- package/dist/alerts.d.ts.map +1 -0
- package/dist/alerts.js +12 -0
- package/dist/alerts.js.map +1 -0
- package/dist/backend-api-key-config.d.ts +337 -0
- package/dist/backend-api-key-config.d.ts.map +1 -0
- package/dist/backend-api-key-config.js +682 -0
- package/dist/backend-api-key-config.js.map +1 -0
- package/dist/backend.d.ts +93 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +22 -0
- package/dist/backend.js.map +1 -0
- package/dist/branding.d.ts +96 -0
- package/dist/branding.d.ts.map +1 -0
- package/dist/branding.js +102 -0
- package/dist/branding.js.map +1 -0
- package/dist/chat-session-scope.d.ts +14 -0
- package/dist/chat-session-scope.d.ts.map +1 -0
- package/dist/chat-session-scope.js +18 -0
- package/dist/chat-session-scope.js.map +1 -0
- package/dist/date-utils.d.ts +80 -0
- package/dist/date-utils.d.ts.map +1 -0
- package/dist/date-utils.js +187 -0
- package/dist/date-utils.js.map +1 -0
- package/dist/docs-frontmatter.d.ts +51 -0
- package/dist/docs-frontmatter.d.ts.map +1 -0
- package/dist/docs-frontmatter.js +184 -0
- package/dist/docs-frontmatter.js.map +1 -0
- package/dist/docs-schema.d.ts +79 -0
- package/dist/docs-schema.d.ts.map +1 -0
- package/dist/docs-schema.js +135 -0
- package/dist/docs-schema.js.map +1 -0
- package/dist/editable-config-keys.d.ts +14 -0
- package/dist/editable-config-keys.d.ts.map +1 -0
- package/dist/editable-config-keys.js +157 -0
- package/dist/editable-config-keys.js.map +1 -0
- package/dist/exec-with-stdin.d.ts +14 -0
- package/dist/exec-with-stdin.d.ts.map +1 -0
- package/dist/exec-with-stdin.js +35 -0
- package/dist/exec-with-stdin.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations-snapshot.d.ts +183 -0
- package/dist/integrations-snapshot.d.ts.map +1 -0
- package/dist/integrations-snapshot.js +757 -0
- package/dist/integrations-snapshot.js.map +1 -0
- package/dist/integrations.d.ts +675 -0
- package/dist/integrations.d.ts.map +1 -0
- package/dist/integrations.js +1656 -0
- package/dist/integrations.js.map +1 -0
- package/dist/keychain-helper-client.d.ts +31 -0
- package/dist/keychain-helper-client.d.ts.map +1 -0
- package/dist/keychain-helper-client.js +105 -0
- package/dist/keychain-helper-client.js.map +1 -0
- package/dist/log-entry.d.ts +14 -0
- package/dist/log-entry.d.ts.map +1 -0
- package/dist/log-entry.js +2 -0
- package/dist/log-entry.js.map +1 -0
- package/dist/management-domains.d.ts +369 -0
- package/dist/management-domains.d.ts.map +1 -0
- package/dist/management-domains.js +499 -0
- package/dist/management-domains.js.map +1 -0
- package/dist/process-key.d.ts +67 -0
- package/dist/process-key.d.ts.map +1 -0
- package/dist/process-key.js +366 -0
- package/dist/process-key.js.map +1 -0
- package/dist/schemas.d.ts +267 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +271 -0
- package/dist/schemas.js.map +1 -0
- package/dist/secret-client-factory.d.ts +16 -0
- package/dist/secret-client-factory.d.ts.map +1 -0
- package/dist/secret-client-factory.js +111 -0
- package/dist/secret-client-factory.js.map +1 -0
- package/dist/secret-client-file.d.ts +51 -0
- package/dist/secret-client-file.d.ts.map +1 -0
- package/dist/secret-client-file.js +160 -0
- package/dist/secret-client-file.js.map +1 -0
- package/dist/secret-client-linux.d.ts +26 -0
- package/dist/secret-client-linux.d.ts.map +1 -0
- package/dist/secret-client-linux.js +63 -0
- package/dist/secret-client-linux.js.map +1 -0
- package/dist/secret-client-windows.d.ts +37 -0
- package/dist/secret-client-windows.d.ts.map +1 -0
- package/dist/secret-client-windows.js +82 -0
- package/dist/secret-client-windows.js.map +1 -0
- package/dist/secret-redaction.d.ts +3 -0
- package/dist/secret-redaction.d.ts.map +1 -0
- package/dist/secret-redaction.js +31 -0
- package/dist/secret-redaction.js.map +1 -0
- package/dist/skill-curation/decision-language.d.ts +6 -0
- package/dist/skill-curation/decision-language.d.ts.map +1 -0
- package/dist/skill-curation/decision-language.js +38 -0
- package/dist/skill-curation/decision-language.js.map +1 -0
- package/dist/skill-curation/schemas.d.ts +461 -0
- package/dist/skill-curation/schemas.d.ts.map +1 -0
- package/dist/skill-curation/schemas.js +211 -0
- package/dist/skill-curation/schemas.js.map +1 -0
- package/dist/types.d.ts +204 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +54 -0
- package/dist/types.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timezone-aware date utilities for the Aitne system.
|
|
3
|
+
*
|
|
4
|
+
* The "agent day" starts at `dayBoundaryHour` (default 04:00) in the
|
|
5
|
+
* configured timezone, NOT at UTC midnight. All SQL queries that reference
|
|
6
|
+
* "today" must use getAgentDayBoundsUtc() to get correct UTC boundaries.
|
|
7
|
+
*/
|
|
8
|
+
// ── Local date string ──────────────────────────────────────────────
|
|
9
|
+
/**
|
|
10
|
+
* Get YYYY-MM-DD string in the specified (or system-local) timezone.
|
|
11
|
+
*
|
|
12
|
+
* Unlike `date.toISOString().slice(0, 10)` which returns the UTC date,
|
|
13
|
+
* this respects the given timezone (or falls back to system local).
|
|
14
|
+
*/
|
|
15
|
+
export function localDateStr(date, timezone) {
|
|
16
|
+
if (timezone) {
|
|
17
|
+
const t = nowInTimezone(timezone, date);
|
|
18
|
+
return `${t.year}-${String(t.month).padStart(2, "0")}-${String(t.day).padStart(2, "0")}`;
|
|
19
|
+
}
|
|
20
|
+
const y = date.getFullYear();
|
|
21
|
+
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
22
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
23
|
+
return `${y}-${m}-${d}`;
|
|
24
|
+
}
|
|
25
|
+
// ── Timezone-aware "now" ───────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* Get time components in the specified timezone.
|
|
28
|
+
* Falls back to system timezone if timezone is empty/undefined.
|
|
29
|
+
*/
|
|
30
|
+
export function nowInTimezone(timezone, now) {
|
|
31
|
+
const d = now ?? new Date();
|
|
32
|
+
if (!timezone) {
|
|
33
|
+
return {
|
|
34
|
+
hours: d.getHours(),
|
|
35
|
+
minutes: d.getMinutes(),
|
|
36
|
+
year: d.getFullYear(),
|
|
37
|
+
month: d.getMonth() + 1,
|
|
38
|
+
day: d.getDate(),
|
|
39
|
+
dayOfWeek: d.getDay(),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const offsetMs = getTimezoneOffsetMs(timezone, d);
|
|
43
|
+
const local = new Date(d.getTime() + offsetMs);
|
|
44
|
+
return {
|
|
45
|
+
hours: local.getUTCHours(),
|
|
46
|
+
minutes: local.getUTCMinutes(),
|
|
47
|
+
year: local.getUTCFullYear(),
|
|
48
|
+
month: local.getUTCMonth() + 1,
|
|
49
|
+
day: local.getUTCDate(),
|
|
50
|
+
dayOfWeek: local.getUTCDay(),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// ── Agent day boundaries ───────────────────────────────────────────
|
|
54
|
+
/**
|
|
55
|
+
* Compute the UTC boundaries of the current "agent day".
|
|
56
|
+
*
|
|
57
|
+
* An agent day starts at `dayBoundaryHour` in the configured timezone.
|
|
58
|
+
* For example, with timezone "America/New_York" and dayBoundaryHour 4:
|
|
59
|
+
* - Agent day starts at 04:00 ET = 09:00 UTC (current day, EST)
|
|
60
|
+
* - Agent day ends at 04:00 ET = 09:00 UTC (next day, EST)
|
|
61
|
+
*
|
|
62
|
+
* @returns start (inclusive) and end (exclusive) as 'YYYY-MM-DD HH:MM:SS'
|
|
63
|
+
* UTC strings suitable for SQLite WHERE clauses.
|
|
64
|
+
*/
|
|
65
|
+
export function getAgentDayBoundsUtc(timezone, dayBoundaryHour, now) {
|
|
66
|
+
const d = now ?? new Date();
|
|
67
|
+
const offsetMs = timezone
|
|
68
|
+
? getTimezoneOffsetMs(timezone, d)
|
|
69
|
+
: -d.getTimezoneOffset() * 60 * 1000;
|
|
70
|
+
// Shift UTC time into "local" space (UTC-shifted Date where UTC methods
|
|
71
|
+
// give local time components)
|
|
72
|
+
const localMs = d.getTime() + offsetMs;
|
|
73
|
+
const localDate = new Date(localMs);
|
|
74
|
+
const localHour = localDate.getUTCHours();
|
|
75
|
+
// Start of the agent day in local-shifted space
|
|
76
|
+
const dayStartLocal = new Date(localDate);
|
|
77
|
+
dayStartLocal.setUTCHours(dayBoundaryHour, 0, 0, 0);
|
|
78
|
+
if (localHour < dayBoundaryHour) {
|
|
79
|
+
// Before the boundary: still in previous agent day
|
|
80
|
+
dayStartLocal.setUTCDate(dayStartLocal.getUTCDate() - 1);
|
|
81
|
+
}
|
|
82
|
+
// Convert back to real UTC. We recompute the timezone offset *at the
|
|
83
|
+
// boundary instant* — using the offset captured at `d` would skew the
|
|
84
|
+
// result by an hour around DST transitions when `d` and the boundary
|
|
85
|
+
// straddle the change-over (e.g. inputs around 02:00–04:00 local on a
|
|
86
|
+
// spring-forward day in ET/CET). For non-DST zones (Asia/Tokyo, UTC)
|
|
87
|
+
// this is a no-op.
|
|
88
|
+
const provisionalUtcMs = dayStartLocal.getTime() - offsetMs;
|
|
89
|
+
const boundaryOffsetMs = timezone
|
|
90
|
+
? getTimezoneOffsetMs(timezone, new Date(provisionalUtcMs))
|
|
91
|
+
: offsetMs;
|
|
92
|
+
const dayStartUtcMs = dayStartLocal.getTime() - boundaryOffsetMs;
|
|
93
|
+
const dayEndUtcMs = dayStartUtcMs + 24 * 60 * 60 * 1000;
|
|
94
|
+
return {
|
|
95
|
+
start: formatSqliteDatetime(new Date(dayStartUtcMs)),
|
|
96
|
+
end: formatSqliteDatetime(new Date(dayEndUtcMs)),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Return the local YYYY-MM-DD label of the current agent day.
|
|
101
|
+
*
|
|
102
|
+
* With a 04:00 boundary, times between 00:00 and 03:59 belong to the
|
|
103
|
+
* previous agent day and therefore return the previous local date.
|
|
104
|
+
*/
|
|
105
|
+
export function getAgentDayDateStr(timezone, dayBoundaryHour, now) {
|
|
106
|
+
const { start } = getAgentDayBoundsUtc(timezone, dayBoundaryHour, now);
|
|
107
|
+
return localDateStr(new Date(start.replace(" ", "T") + "Z"), timezone);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Return minutes elapsed since the start of the current agent day.
|
|
111
|
+
*
|
|
112
|
+
* Example: with a 04:00 boundary, 05:30 returns 90 and 02:15 returns 1335.
|
|
113
|
+
*/
|
|
114
|
+
export function getAgentDayProgressMinutes(timezone, dayBoundaryHour, now) {
|
|
115
|
+
const local = nowInTimezone(timezone, now);
|
|
116
|
+
const currentMinutes = local.hours * 60 + local.minutes;
|
|
117
|
+
const boundaryMinutes = dayBoundaryHour * 60;
|
|
118
|
+
return currentMinutes >= boundaryMinutes
|
|
119
|
+
? currentMinutes - boundaryMinutes
|
|
120
|
+
: (24 * 60 - boundaryMinutes) + currentMinutes;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Project a candidate Date forward to the next start of an active-hours
|
|
124
|
+
* window in the configured timezone. The window is `[startHour, endHour)`
|
|
125
|
+
* in local time; `endHour=24` is treated as exclusive (no shift for
|
|
126
|
+
* candidate hour 23).
|
|
127
|
+
*
|
|
128
|
+
* - Candidate's local hour already inside the window → returns candidate
|
|
129
|
+
* unchanged.
|
|
130
|
+
* - Local hour `< startHour` → returns today's `startHour:00:00.000` local.
|
|
131
|
+
* - Local hour `>= endHour` → returns tomorrow's `startHour:00:00.000`
|
|
132
|
+
* local.
|
|
133
|
+
*
|
|
134
|
+
* Mirrors the timezone-shift / DST-recompute pattern used in
|
|
135
|
+
* `getAgentDayBoundsUtc` so cross-DST transitions land on the correct
|
|
136
|
+
* wall-clock hour rather than an hour offset.
|
|
137
|
+
*/
|
|
138
|
+
export function nextActiveHoursStart(candidate, timezone, startHour, endHour) {
|
|
139
|
+
const local = nowInTimezone(timezone, candidate);
|
|
140
|
+
if (local.hours >= startHour && local.hours < endHour) {
|
|
141
|
+
return candidate;
|
|
142
|
+
}
|
|
143
|
+
const offsetMs = timezone
|
|
144
|
+
? getTimezoneOffsetMs(timezone, candidate)
|
|
145
|
+
: -candidate.getTimezoneOffset() * 60 * 1000;
|
|
146
|
+
// Local-shifted Date — UTC accessors return the local-time components.
|
|
147
|
+
const localShifted = new Date(candidate.getTime() + offsetMs);
|
|
148
|
+
const target = new Date(localShifted);
|
|
149
|
+
target.setUTCHours(startHour, 0, 0, 0);
|
|
150
|
+
if (local.hours >= endHour) {
|
|
151
|
+
target.setUTCDate(target.getUTCDate() + 1);
|
|
152
|
+
}
|
|
153
|
+
// local.hours < startHour → today's startHour (no day shift needed).
|
|
154
|
+
// Convert back to UTC, recomputing TZ offset at the target instant so a
|
|
155
|
+
// candidate that straddles a DST change lands at the intended local hour.
|
|
156
|
+
const provisionalUtcMs = target.getTime() - offsetMs;
|
|
157
|
+
const targetOffsetMs = timezone
|
|
158
|
+
? getTimezoneOffsetMs(timezone, new Date(provisionalUtcMs))
|
|
159
|
+
: offsetMs;
|
|
160
|
+
return new Date(target.getTime() - targetOffsetMs);
|
|
161
|
+
}
|
|
162
|
+
// ── Helpers ────────────────────────────────────────────────────────
|
|
163
|
+
/** Format a Date as 'YYYY-MM-DD HH:MM:SS' UTC string for SQLite. */
|
|
164
|
+
export function formatSqliteDatetime(date) {
|
|
165
|
+
return date.toISOString().replace("T", " ").slice(0, 19);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Parse a SQLite UTC datetime string ('YYYY-MM-DD HH:MM:SS') into epoch ms.
|
|
169
|
+
* More robust than ad-hoc `new Date(s + "Z")` — explicitly normalizes the
|
|
170
|
+
* format before parsing.
|
|
171
|
+
*/
|
|
172
|
+
export function parseSqliteUtcMs(s) {
|
|
173
|
+
// SQLite datetime('now') produces 'YYYY-MM-DD HH:MM:SS' (space-separated, no Z)
|
|
174
|
+
// Replace space with T and append Z to create a valid ISO 8601 UTC string
|
|
175
|
+
return new Date(s.replace(" ", "T") + "Z").getTime();
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Get the timezone offset in milliseconds (positive = east of UTC).
|
|
179
|
+
* Uses toLocaleString comparison which handles DST correctly for
|
|
180
|
+
* the given instant.
|
|
181
|
+
*/
|
|
182
|
+
function getTimezoneOffsetMs(timezone, date) {
|
|
183
|
+
const utcStr = date.toLocaleString("en-US", { timeZone: "UTC" });
|
|
184
|
+
const localStr = date.toLocaleString("en-US", { timeZone: timezone });
|
|
185
|
+
return new Date(localStr).getTime() - new Date(utcStr).getTime();
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=date-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-utils.js","sourceRoot":"","sources":["../src/date-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,sEAAsE;AAEtE;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,IAAU,EAAE,QAAiB;IACxD,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,CAAC,GAAG,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACxC,OAAO,GAAG,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IAC3F,CAAC;IACD,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAC7B,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACvD,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAClD,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAC1B,CAAC;AAED,sEAAsE;AAEtE;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,QAA4B,EAAE,GAAU;IAQpE,MAAM,CAAC,GAAG,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;IAC5B,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO;YACL,KAAK,EAAE,CAAC,CAAC,QAAQ,EAAE;YACnB,OAAO,EAAE,CAAC,CAAC,UAAU,EAAE;YACvB,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE;YACrB,KAAK,EAAE,CAAC,CAAC,QAAQ,EAAE,GAAG,CAAC;YACvB,GAAG,EAAE,CAAC,CAAC,OAAO,EAAE;YAChB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;SACtB,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,mBAAmB,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,QAAQ,CAAC,CAAC;IAE/C,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE;QAC1B,OAAO,EAAE,KAAK,CAAC,aAAa,EAAE;QAC9B,IAAI,EAAE,KAAK,CAAC,cAAc,EAAE;QAC5B,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,GAAG,CAAC;QAC9B,GAAG,EAAE,KAAK,CAAC,UAAU,EAAE;QACvB,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE;KAC7B,CAAC;AACJ,CAAC;AAED,sEAAsE;AAEtE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAClC,QAA4B,EAC5B,eAAuB,EACvB,GAAU;IAEV,MAAM,CAAC,GAAG,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;IAC5B,MAAM,QAAQ,GAAG,QAAQ;QACvB,CAAC,CAAC,mBAAmB,CAAC,QAAQ,EAAE,CAAC,CAAC;QAClC,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAEvC,wEAAwE;IACxE,8BAA8B;IAC9B,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,EAAE,GAAG,QAAQ,CAAC;IACvC,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;IAEpC,MAAM,SAAS,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;IAE1C,gDAAgD;IAChD,MAAM,aAAa,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;IAC1C,aAAa,CAAC,WAAW,CAAC,eAAe,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAEpD,IAAI,SAAS,GAAG,eAAe,EAAE,CAAC;QAChC,mDAAmD;QACnD,aAAa,CAAC,UAAU,CAAC,aAAa,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3D,CAAC;IAED,qEAAqE;IACrE,sEAAsE;IACtE,qEAAqE;IACrE,sEAAsE;IACtE,qEAAqE;IACrE,mBAAmB;IACnB,MAAM,gBAAgB,GAAG,aAAa,CAAC,OAAO,EAAE,GAAG,QAAQ,CAAC;IAC5D,MAAM,gBAAgB,GAAG,QAAQ;QAC/B,CAAC,CAAC,mBAAmB,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC3D,CAAC,CAAC,QAAQ,CAAC;IACb,MAAM,aAAa,GAAG,aAAa,CAAC,OAAO,EAAE,GAAG,gBAAgB,CAAC;IACjE,MAAM,WAAW,GAAG,aAAa,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAExD,OAAO;QACL,KAAK,EAAE,oBAAoB,CAAC,IAAI,IAAI,CAAC,aAAa,CAAC,CAAC;QACpD,GAAG,EAAE,oBAAoB,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC,CAAC;KACjD,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAA4B,EAC5B,eAAuB,EACvB,GAAU;IAEV,MAAM,EAAE,KAAK,EAAE,GAAG,oBAAoB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,CAAC,CAAC;IACvE,OAAO,YAAY,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;AACzE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CACxC,QAA4B,EAC5B,eAAuB,EACvB,GAAU;IAEV,MAAM,KAAK,GAAG,aAAa,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC3C,MAAM,cAAc,GAAG,KAAK,CAAC,KAAK,GAAG,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC;IACxD,MAAM,eAAe,GAAG,eAAe,GAAG,EAAE,CAAC;IAE7C,OAAO,cAAc,IAAI,eAAe;QACtC,CAAC,CAAC,cAAc,GAAG,eAAe;QAClC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,eAAe,CAAC,GAAG,cAAc,CAAC;AACnD,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,oBAAoB,CAClC,SAAe,EACf,QAA4B,EAC5B,SAAiB,EACjB,OAAe;IAEf,MAAM,KAAK,GAAG,aAAa,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACjD,IAAI,KAAK,CAAC,KAAK,IAAI,SAAS,IAAI,KAAK,CAAC,KAAK,GAAG,OAAO,EAAE,CAAC;QACtD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ;QACvB,CAAC,CAAC,mBAAmB,CAAC,QAAQ,EAAE,SAAS,CAAC;QAC1C,CAAC,CAAC,CAAC,SAAS,CAAC,iBAAiB,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAE/C,uEAAuE;IACvE,MAAM,YAAY,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,QAAQ,CAAC,CAAC;IAC9D,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC;IACtC,MAAM,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAEvC,IAAI,KAAK,CAAC,KAAK,IAAI,OAAO,EAAE,CAAC;QAC3B,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC;IAC7C,CAAC;IACD,qEAAqE;IAErE,wEAAwE;IACxE,0EAA0E;IAC1E,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,EAAE,GAAG,QAAQ,CAAC;IACrD,MAAM,cAAc,GAAG,QAAQ;QAC7B,CAAC,CAAC,mBAAmB,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC3D,CAAC,CAAC,QAAQ,CAAC;IACb,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,cAAc,CAAC,CAAC;AACrD,CAAC;AAED,sEAAsE;AAEtE,oEAAoE;AACpE,MAAM,UAAU,oBAAoB,CAAC,IAAU;IAC7C,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC3D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAS;IACxC,gFAAgF;IAChF,0EAA0E;IAC1E,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC;AACvD,CAAC;AAED;;;;GAIG;AACH,SAAS,mBAAmB,CAAC,QAAgB,EAAE,IAAU;IACvD,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;IACtE,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal YAML frontmatter parser scoped to the docs corpus shape
|
|
3
|
+
* (DOCS_QA_DESIGN.md §7).
|
|
4
|
+
*
|
|
5
|
+
* Lives in `packages/shared` so the daemon (indexer) and the dashboard
|
|
6
|
+
* (page-doc-map drift guard) can both validate fixture frontmatter
|
|
7
|
+
* against the canonical Zod schema without two parsers drifting.
|
|
8
|
+
*
|
|
9
|
+
* Why hand-rolled. The codebase already carries two hand-rolled
|
|
10
|
+
* frontmatter readers (`context-frontmatter.ts`, `custom-routine-scheduler.ts`)
|
|
11
|
+
* but they are flat-scalar-only. Docs frontmatter needs arrays
|
|
12
|
+
* (`aliases:`, `tags:`, `related:`, ...) and a block-scalar `summary:`.
|
|
13
|
+
* Pulling in a full YAML library for this tightly-bounded use case is
|
|
14
|
+
* unnecessary; the tighter parser here
|
|
15
|
+
* fails loudly on shapes we don't expect (nested mappings, flow-style
|
|
16
|
+
* arrays) so authors stay inside the supported subset.
|
|
17
|
+
*
|
|
18
|
+
* Supported shapes:
|
|
19
|
+
* - `key: scalar` (string, possibly quoted with `"` or `'`)
|
|
20
|
+
* - `key: [number]` parsed as number when the value is bare-numeric
|
|
21
|
+
* - `key: true` / `key: false` parsed as boolean
|
|
22
|
+
* - `key:\n - item\n - item` parsed as `string[]` (or empty array
|
|
23
|
+
* when followed by another key with no `-` lines in between)
|
|
24
|
+
* - `key: |` (or `key: |-`) followed by indented block, joined with
|
|
25
|
+
* `\n`. Trailing newline is dropped for `|-` and preserved for `|`.
|
|
26
|
+
* - `key: {}` parsed as `Record<string, never>` (used by `extra:`)
|
|
27
|
+
* - `# comment` and inline trailing `# comment` stripped
|
|
28
|
+
*
|
|
29
|
+
* Unsupported (rejected): nested mappings, flow-style arrays/objects
|
|
30
|
+
* other than `{}`, anchor / alias references.
|
|
31
|
+
*
|
|
32
|
+
* Validation against the Zod schema is the caller's job — this parser
|
|
33
|
+
* just produces a `Record<string, unknown>`.
|
|
34
|
+
*/
|
|
35
|
+
export interface ParsedFrontmatter {
|
|
36
|
+
/** Parsed YAML mapping. */
|
|
37
|
+
values: Record<string, unknown>;
|
|
38
|
+
/** Body content after the closing `---`. */
|
|
39
|
+
body: string;
|
|
40
|
+
}
|
|
41
|
+
export declare class FrontmatterParseError extends Error {
|
|
42
|
+
readonly line: number;
|
|
43
|
+
readonly raw: string;
|
|
44
|
+
constructor(line: number, raw: string, reason: string);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Split a Markdown source into `{ frontmatter, body }`. Returns `null`
|
|
48
|
+
* when the file does not start with `---` (no frontmatter).
|
|
49
|
+
*/
|
|
50
|
+
export declare function parseFrontmatter(source: string): ParsedFrontmatter | null;
|
|
51
|
+
//# sourceMappingURL=docs-frontmatter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docs-frontmatter.d.ts","sourceRoot":"","sources":["../src/docs-frontmatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,MAAM,WAAW,iBAAiB;IAChC,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,qBAAsB,SAAQ,KAAK;aAE5B,IAAI,EAAE,MAAM;aACZ,GAAG,EAAE,MAAM;gBADX,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EAC3B,MAAM,EAAE,MAAM;CAKjB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAWzE"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal YAML frontmatter parser scoped to the docs corpus shape
|
|
3
|
+
* (DOCS_QA_DESIGN.md §7).
|
|
4
|
+
*
|
|
5
|
+
* Lives in `packages/shared` so the daemon (indexer) and the dashboard
|
|
6
|
+
* (page-doc-map drift guard) can both validate fixture frontmatter
|
|
7
|
+
* against the canonical Zod schema without two parsers drifting.
|
|
8
|
+
*
|
|
9
|
+
* Why hand-rolled. The codebase already carries two hand-rolled
|
|
10
|
+
* frontmatter readers (`context-frontmatter.ts`, `custom-routine-scheduler.ts`)
|
|
11
|
+
* but they are flat-scalar-only. Docs frontmatter needs arrays
|
|
12
|
+
* (`aliases:`, `tags:`, `related:`, ...) and a block-scalar `summary:`.
|
|
13
|
+
* Pulling in a full YAML library for this tightly-bounded use case is
|
|
14
|
+
* unnecessary; the tighter parser here
|
|
15
|
+
* fails loudly on shapes we don't expect (nested mappings, flow-style
|
|
16
|
+
* arrays) so authors stay inside the supported subset.
|
|
17
|
+
*
|
|
18
|
+
* Supported shapes:
|
|
19
|
+
* - `key: scalar` (string, possibly quoted with `"` or `'`)
|
|
20
|
+
* - `key: [number]` parsed as number when the value is bare-numeric
|
|
21
|
+
* - `key: true` / `key: false` parsed as boolean
|
|
22
|
+
* - `key:\n - item\n - item` parsed as `string[]` (or empty array
|
|
23
|
+
* when followed by another key with no `-` lines in between)
|
|
24
|
+
* - `key: |` (or `key: |-`) followed by indented block, joined with
|
|
25
|
+
* `\n`. Trailing newline is dropped for `|-` and preserved for `|`.
|
|
26
|
+
* - `key: {}` parsed as `Record<string, never>` (used by `extra:`)
|
|
27
|
+
* - `# comment` and inline trailing `# comment` stripped
|
|
28
|
+
*
|
|
29
|
+
* Unsupported (rejected): nested mappings, flow-style arrays/objects
|
|
30
|
+
* other than `{}`, anchor / alias references.
|
|
31
|
+
*
|
|
32
|
+
* Validation against the Zod schema is the caller's job — this parser
|
|
33
|
+
* just produces a `Record<string, unknown>`.
|
|
34
|
+
*/
|
|
35
|
+
export class FrontmatterParseError extends Error {
|
|
36
|
+
line;
|
|
37
|
+
raw;
|
|
38
|
+
constructor(line, raw, reason) {
|
|
39
|
+
super(`Frontmatter parse error at line ${line}: ${reason} (${JSON.stringify(raw)})`);
|
|
40
|
+
this.line = line;
|
|
41
|
+
this.raw = raw;
|
|
42
|
+
this.name = "FrontmatterParseError";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Split a Markdown source into `{ frontmatter, body }`. Returns `null`
|
|
47
|
+
* when the file does not start with `---` (no frontmatter).
|
|
48
|
+
*/
|
|
49
|
+
export function parseFrontmatter(source) {
|
|
50
|
+
const lines = source.split(/\r?\n/);
|
|
51
|
+
if ((lines[0] ?? "").trim() !== "---")
|
|
52
|
+
return null;
|
|
53
|
+
const closeIdx = lines.findIndex((l, i) => i > 0 && l.trim() === "---");
|
|
54
|
+
if (closeIdx < 0) {
|
|
55
|
+
throw new FrontmatterParseError(1, "---", "missing closing '---'");
|
|
56
|
+
}
|
|
57
|
+
const fmLines = lines.slice(1, closeIdx);
|
|
58
|
+
const body = lines.slice(closeIdx + 1).join("\n");
|
|
59
|
+
const values = parseMapping(fmLines, /*lineOffset*/ 1);
|
|
60
|
+
return { values, body };
|
|
61
|
+
}
|
|
62
|
+
function stripInlineComment(value) {
|
|
63
|
+
// Inline comments only count when preceded by whitespace; `#` inside a
|
|
64
|
+
// quoted scalar is literal. Treat unquoted values: the first ` #`
|
|
65
|
+
// sequence ends the value.
|
|
66
|
+
if (value.startsWith('"') || value.startsWith("'"))
|
|
67
|
+
return value;
|
|
68
|
+
const idx = value.indexOf(" #");
|
|
69
|
+
if (idx === -1)
|
|
70
|
+
return value;
|
|
71
|
+
return value.slice(0, idx).trimEnd();
|
|
72
|
+
}
|
|
73
|
+
function parseScalar(rawValue) {
|
|
74
|
+
const v = stripInlineComment(rawValue.trim());
|
|
75
|
+
if (v === "" || v === "null" || v === "~")
|
|
76
|
+
return null;
|
|
77
|
+
if (v === "true")
|
|
78
|
+
return true;
|
|
79
|
+
if (v === "false")
|
|
80
|
+
return false;
|
|
81
|
+
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
|
|
82
|
+
return v.slice(1, -1);
|
|
83
|
+
}
|
|
84
|
+
if (/^-?\d+(\.\d+)?$/.test(v)) {
|
|
85
|
+
const n = Number(v);
|
|
86
|
+
return Number.isFinite(n) ? n : v;
|
|
87
|
+
}
|
|
88
|
+
return v;
|
|
89
|
+
}
|
|
90
|
+
function parseMapping(lines, lineOffset) {
|
|
91
|
+
const out = {};
|
|
92
|
+
let i = 0;
|
|
93
|
+
while (i < lines.length) {
|
|
94
|
+
const raw = lines[i];
|
|
95
|
+
const trimmed = raw.trim();
|
|
96
|
+
// Skip blank + comment lines.
|
|
97
|
+
if (trimmed === "" || trimmed.startsWith("#")) {
|
|
98
|
+
i += 1;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
// Top-level keys must start at column 0.
|
|
102
|
+
if (raw.startsWith(" ") || raw.startsWith("\t")) {
|
|
103
|
+
throw new FrontmatterParseError(lineOffset + i, raw, "unexpected indentation at top level (nested mappings not supported)");
|
|
104
|
+
}
|
|
105
|
+
const colon = raw.indexOf(":");
|
|
106
|
+
if (colon < 0) {
|
|
107
|
+
throw new FrontmatterParseError(lineOffset + i, raw, "expected 'key: value' at top level");
|
|
108
|
+
}
|
|
109
|
+
const key = raw.slice(0, colon).trim();
|
|
110
|
+
const valuePart = raw.slice(colon + 1);
|
|
111
|
+
// Detect block-scalar marker (`|` or `|-`).
|
|
112
|
+
const blockMatch = stripInlineComment(valuePart.trim());
|
|
113
|
+
if (blockMatch === "|" || blockMatch === "|-") {
|
|
114
|
+
const stripTrailingNewline = blockMatch === "|-";
|
|
115
|
+
const collected = [];
|
|
116
|
+
let j = i + 1;
|
|
117
|
+
while (j < lines.length) {
|
|
118
|
+
const next = lines[j];
|
|
119
|
+
if (next === "" || next.startsWith(" ") || next.startsWith("\t")) {
|
|
120
|
+
// Strip the 2-space indent (tab counted as one indent level).
|
|
121
|
+
const stripped = next.startsWith("\t") ? next.slice(1) : next.slice(2);
|
|
122
|
+
collected.push(stripped);
|
|
123
|
+
j += 1;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
let joined = collected.join("\n");
|
|
129
|
+
if (stripTrailingNewline) {
|
|
130
|
+
joined = joined.replace(/\n+$/, "");
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// `|` preserves a single trailing newline; multiple are folded
|
|
134
|
+
// to one, no trailing newline → add one.
|
|
135
|
+
joined = joined.replace(/\n+$/, "\n");
|
|
136
|
+
if (!joined.endsWith("\n"))
|
|
137
|
+
joined += "\n";
|
|
138
|
+
}
|
|
139
|
+
out[key] = joined;
|
|
140
|
+
i = j;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
// Detect inline value vs list-form value.
|
|
144
|
+
const inlineValue = stripInlineComment(valuePart).trim();
|
|
145
|
+
if (inlineValue === "") {
|
|
146
|
+
// List form: subsequent indented lines starting with `- ` are items.
|
|
147
|
+
const items = [];
|
|
148
|
+
let j = i + 1;
|
|
149
|
+
while (j < lines.length) {
|
|
150
|
+
const next = lines[j];
|
|
151
|
+
const t = next.trim();
|
|
152
|
+
if (t === "" || t.startsWith("#")) {
|
|
153
|
+
j += 1;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (!next.startsWith(" ") && !next.startsWith("\t")) {
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
const itemMatch = next.match(/^\s*-\s*(.*)$/);
|
|
160
|
+
if (!itemMatch) {
|
|
161
|
+
throw new FrontmatterParseError(lineOffset + j, next, "expected list item starting with '- ' under multi-line key");
|
|
162
|
+
}
|
|
163
|
+
items.push(parseScalar(itemMatch[1]));
|
|
164
|
+
j += 1;
|
|
165
|
+
}
|
|
166
|
+
out[key] = items;
|
|
167
|
+
i = j;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (inlineValue === "{}") {
|
|
171
|
+
out[key] = {};
|
|
172
|
+
i += 1;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
// Reject inline flow-style arrays / objects we don't support.
|
|
176
|
+
if (inlineValue.startsWith("[") || inlineValue.startsWith("{")) {
|
|
177
|
+
throw new FrontmatterParseError(lineOffset + i, raw, "flow-style array/object literals not supported (use list form)");
|
|
178
|
+
}
|
|
179
|
+
out[key] = parseScalar(inlineValue);
|
|
180
|
+
i += 1;
|
|
181
|
+
}
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=docs-frontmatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docs-frontmatter.js","sourceRoot":"","sources":["../src/docs-frontmatter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Docs & QA frontmatter schema (DOCS_QA_DESIGN.md §7).
|
|
4
|
+
*
|
|
5
|
+
* Shared between the daemon (indexer, FTS5 row builder) and dashboard
|
|
6
|
+
* (renderer, ?-button map). The schema is `.strict()` — unknown top-level
|
|
7
|
+
* keys must move under `extra:`. This is deliberate: loose passthrough lets
|
|
8
|
+
* typos like `tag:` (instead of `tags:`) silently bypass validation.
|
|
9
|
+
*
|
|
10
|
+
* Forward-compat is provided via `schema_version` + the `extra` bag.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DOCS_SCHEMA_VERSION: 1;
|
|
13
|
+
declare const DOC_CATEGORIES: readonly ["getting-started", "concepts", "features", "guides", "troubleshooting", "reference", "glossary"];
|
|
14
|
+
export type DocCategory = (typeof DOC_CATEGORIES)[number];
|
|
15
|
+
declare const DOC_STATUS: readonly ["stable", "beta", "draft", "deprecated"];
|
|
16
|
+
export type DocStatus = (typeof DOC_STATUS)[number];
|
|
17
|
+
export declare const docsFrontmatterSchema: z.ZodObject<{
|
|
18
|
+
schema_version: z.ZodLiteral<1>;
|
|
19
|
+
slug: z.ZodString;
|
|
20
|
+
title: z.ZodString;
|
|
21
|
+
id: z.ZodOptional<z.ZodString>;
|
|
22
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
23
|
+
category: z.ZodEnum<{
|
|
24
|
+
"getting-started": "getting-started";
|
|
25
|
+
concepts: "concepts";
|
|
26
|
+
features: "features";
|
|
27
|
+
guides: "guides";
|
|
28
|
+
troubleshooting: "troubleshooting";
|
|
29
|
+
reference: "reference";
|
|
30
|
+
glossary: "glossary";
|
|
31
|
+
}>;
|
|
32
|
+
summary: z.ZodString;
|
|
33
|
+
section: z.ZodOptional<z.ZodString>;
|
|
34
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
35
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
36
|
+
stable: "stable";
|
|
37
|
+
beta: "beta";
|
|
38
|
+
draft: "draft";
|
|
39
|
+
deprecated: "deprecated";
|
|
40
|
+
}>>;
|
|
41
|
+
ask_examples: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
42
|
+
locale: z.ZodOptional<z.ZodString>;
|
|
43
|
+
created: z.ZodOptional<z.ZodString>;
|
|
44
|
+
updated: z.ZodOptional<z.ZodString>;
|
|
45
|
+
review_due: z.ZodOptional<z.ZodString>;
|
|
46
|
+
supersedes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
47
|
+
keywords: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
48
|
+
related: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
49
|
+
prerequisites: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
50
|
+
ui_anchors: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
51
|
+
process_keys: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
52
|
+
config_keys: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
53
|
+
api_endpoints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
54
|
+
context_files: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
55
|
+
extra: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
56
|
+
}, z.core.$strict>;
|
|
57
|
+
export type DocsFrontmatter = z.infer<typeof docsFrontmatterSchema>;
|
|
58
|
+
/**
|
|
59
|
+
* Slugify an H1/H2/H3 heading text into the anchor id used inside
|
|
60
|
+
* `[doc:slug#anchor]` citations. Mirrors the rule the dashboard renderer
|
|
61
|
+
* is expected to use (lower, replace whitespace with `-`, drop punctuation).
|
|
62
|
+
*
|
|
63
|
+
* Kept in `shared` so the citation post-processor (daemon) and the
|
|
64
|
+
* citation pill renderer (dashboard) cannot drift.
|
|
65
|
+
*/
|
|
66
|
+
export declare function slugifyAnchor(heading: string): string;
|
|
67
|
+
export interface ParsedCitationToken {
|
|
68
|
+
/** Index in the source string where `[doc:` starts. */
|
|
69
|
+
start: number;
|
|
70
|
+
/** Index in the source string just past the closing `]`. */
|
|
71
|
+
end: number;
|
|
72
|
+
slug: string;
|
|
73
|
+
anchor: string | null;
|
|
74
|
+
raw: string;
|
|
75
|
+
}
|
|
76
|
+
/** Find every `[doc:slug#anchor]` token in `text`. */
|
|
77
|
+
export declare function parseCitationTokens(text: string): ParsedCitationToken[];
|
|
78
|
+
export {};
|
|
79
|
+
//# sourceMappingURL=docs-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docs-schema.d.ts","sourceRoot":"","sources":["../src/docs-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;GASG;AAEH,eAAO,MAAM,mBAAmB,EAAG,CAAU,CAAC;AAE9C,QAAA,MAAM,cAAc,4GAQV,CAAC;AACX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D,QAAA,MAAM,UAAU,oDAAqD,CAAC;AACtE,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AA+CpD,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAiDvB,CAAC;AAEZ,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAOrD;AAKD,MAAM,WAAW,mBAAmB;IAClC,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,sDAAsD;AACtD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAcvE"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Docs & QA frontmatter schema (DOCS_QA_DESIGN.md §7).
|
|
4
|
+
*
|
|
5
|
+
* Shared between the daemon (indexer, FTS5 row builder) and dashboard
|
|
6
|
+
* (renderer, ?-button map). The schema is `.strict()` — unknown top-level
|
|
7
|
+
* keys must move under `extra:`. This is deliberate: loose passthrough lets
|
|
8
|
+
* typos like `tag:` (instead of `tags:`) silently bypass validation.
|
|
9
|
+
*
|
|
10
|
+
* Forward-compat is provided via `schema_version` + the `extra` bag.
|
|
11
|
+
*/
|
|
12
|
+
export const DOCS_SCHEMA_VERSION = 1;
|
|
13
|
+
const DOC_CATEGORIES = [
|
|
14
|
+
"getting-started",
|
|
15
|
+
"concepts",
|
|
16
|
+
"features",
|
|
17
|
+
"guides",
|
|
18
|
+
"troubleshooting",
|
|
19
|
+
"reference",
|
|
20
|
+
"glossary",
|
|
21
|
+
];
|
|
22
|
+
const DOC_STATUS = ["stable", "beta", "draft", "deprecated"];
|
|
23
|
+
/**
|
|
24
|
+
* Slug grammar: lowercase ASCII letters/digits with `/` separators and
|
|
25
|
+
* `-` inside a segment. The first segment must be one of the known
|
|
26
|
+
* categories, but the schema enforces only the shape — the cross-check
|
|
27
|
+
* `slug.split('/')[0] === category` lives in the indexer because it
|
|
28
|
+
* needs the runtime category value.
|
|
29
|
+
*
|
|
30
|
+
* Example: `features/routines/morning-routine`
|
|
31
|
+
*/
|
|
32
|
+
const slugSchema = z
|
|
33
|
+
.string()
|
|
34
|
+
.min(1)
|
|
35
|
+
.max(160)
|
|
36
|
+
.regex(/^[a-z0-9]+(?:[-_][a-z0-9]+)*(?:\/[a-z0-9]+(?:[-_][a-z0-9]+)*)*$/, "slug must be lowercase kebab-case path segments separated by '/'");
|
|
37
|
+
/**
|
|
38
|
+
* Cross-link target: same shape as `slug`, but used in `related:` /
|
|
39
|
+
* `prerequisites:` / `supersedes:` lists. Kept as a separate alias so a
|
|
40
|
+
* future grammar change to one side doesn't silently widen the other.
|
|
41
|
+
*/
|
|
42
|
+
const slugRefSchema = slugSchema;
|
|
43
|
+
/** Short id used in `[[id]]` cross-references. ASCII kebab-case only. */
|
|
44
|
+
const docIdSchema = z
|
|
45
|
+
.string()
|
|
46
|
+
.min(1)
|
|
47
|
+
.max(64)
|
|
48
|
+
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "id must be lowercase kebab-case");
|
|
49
|
+
/** YAML date — ISO-8601 calendar date (`YYYY-MM-DD`). */
|
|
50
|
+
const isoDateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "must be YYYY-MM-DD");
|
|
51
|
+
/**
|
|
52
|
+
* Dashboard route used as a `ui_anchors` entry. Must start with `/` and
|
|
53
|
+
* may carry a query string. The drift-guard test cross-checks each
|
|
54
|
+
* value against `dashboard/src/app/**\/page.tsx` (DOCS_QA_DESIGN.md §8.3).
|
|
55
|
+
*/
|
|
56
|
+
const uiAnchorSchema = z
|
|
57
|
+
.string()
|
|
58
|
+
.min(1)
|
|
59
|
+
.regex(/^\/[A-Za-z0-9\-_/?=&%.]*$/, "ui_anchors entries must look like a dashboard path");
|
|
60
|
+
export const docsFrontmatterSchema = z
|
|
61
|
+
.object({
|
|
62
|
+
// === Identity (required) ===
|
|
63
|
+
schema_version: z.literal(DOCS_SCHEMA_VERSION),
|
|
64
|
+
slug: slugSchema,
|
|
65
|
+
title: z.string().min(1).max(200),
|
|
66
|
+
// === Identity (recommended) ===
|
|
67
|
+
id: docIdSchema.optional(),
|
|
68
|
+
aliases: z.array(z.string().min(1).max(120)).max(32).optional(),
|
|
69
|
+
// === Classification (required) ===
|
|
70
|
+
category: z.enum(DOC_CATEGORIES),
|
|
71
|
+
summary: z.string().min(1).max(2000),
|
|
72
|
+
// === Classification (recommended) ===
|
|
73
|
+
section: z.string().min(1).max(64).optional(),
|
|
74
|
+
tags: z.array(z.string().min(1).max(64)).max(32).optional(),
|
|
75
|
+
status: z.enum(DOC_STATUS).optional(),
|
|
76
|
+
ask_examples: z.array(z.string().min(1).max(240)).max(16).optional(),
|
|
77
|
+
// === Classification (optional) ===
|
|
78
|
+
locale: z.string().min(2).max(16).optional(),
|
|
79
|
+
// === Lifecycle (optional) ===
|
|
80
|
+
created: isoDateSchema.optional(),
|
|
81
|
+
updated: isoDateSchema.optional(),
|
|
82
|
+
review_due: isoDateSchema.optional(),
|
|
83
|
+
supersedes: z.array(slugRefSchema).max(16).optional(),
|
|
84
|
+
// === Search (optional but valuable) ===
|
|
85
|
+
keywords: z.array(z.string().min(1).max(120)).max(32).optional(),
|
|
86
|
+
// === Cross-links (optional) ===
|
|
87
|
+
related: z.array(slugRefSchema).max(32).optional(),
|
|
88
|
+
prerequisites: z.array(slugRefSchema).max(16).optional(),
|
|
89
|
+
// === System bindings (optional — drives ?-button + filtering) ===
|
|
90
|
+
ui_anchors: z.array(uiAnchorSchema).max(32).optional(),
|
|
91
|
+
process_keys: z.array(z.string().min(1).max(120)).max(32).optional(),
|
|
92
|
+
config_keys: z.array(z.string().min(1).max(120)).max(32).optional(),
|
|
93
|
+
api_endpoints: z.array(z.string().min(1).max(160)).max(32).optional(),
|
|
94
|
+
context_files: z.array(z.string().min(1).max(160)).max(32).optional(),
|
|
95
|
+
// === Forward-compat escape hatch ===
|
|
96
|
+
// Zod 4.x: z.record(keySchema, valueSchema). The previous one-arg
|
|
97
|
+
// form (`z.record(z.unknown())`) compiled but threw at parse time.
|
|
98
|
+
extra: z.record(z.string(), z.unknown()).optional(),
|
|
99
|
+
})
|
|
100
|
+
.strict();
|
|
101
|
+
/**
|
|
102
|
+
* Slugify an H1/H2/H3 heading text into the anchor id used inside
|
|
103
|
+
* `[doc:slug#anchor]` citations. Mirrors the rule the dashboard renderer
|
|
104
|
+
* is expected to use (lower, replace whitespace with `-`, drop punctuation).
|
|
105
|
+
*
|
|
106
|
+
* Kept in `shared` so the citation post-processor (daemon) and the
|
|
107
|
+
* citation pill renderer (dashboard) cannot drift.
|
|
108
|
+
*/
|
|
109
|
+
export function slugifyAnchor(heading) {
|
|
110
|
+
return heading
|
|
111
|
+
.toLowerCase()
|
|
112
|
+
.replace(/[^a-z0-9\s-]/g, "")
|
|
113
|
+
.trim()
|
|
114
|
+
.replace(/\s+/g, "-")
|
|
115
|
+
.replace(/-+/g, "-");
|
|
116
|
+
}
|
|
117
|
+
/** Citation token shape: `[doc:slug#anchor]`. Anchor is optional. */
|
|
118
|
+
const CITATION_RE = /\[doc:([a-z0-9][a-z0-9\-_/]*)(?:#([a-z0-9][a-z0-9-]*))?\]/g;
|
|
119
|
+
/** Find every `[doc:slug#anchor]` token in `text`. */
|
|
120
|
+
export function parseCitationTokens(text) {
|
|
121
|
+
const out = [];
|
|
122
|
+
CITATION_RE.lastIndex = 0;
|
|
123
|
+
let m;
|
|
124
|
+
while ((m = CITATION_RE.exec(text)) !== null) {
|
|
125
|
+
out.push({
|
|
126
|
+
start: m.index,
|
|
127
|
+
end: m.index + m[0].length,
|
|
128
|
+
slug: m[1],
|
|
129
|
+
anchor: m[2] ?? null,
|
|
130
|
+
raw: m[0],
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=docs-schema.js.map
|