@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.
Files changed (114) hide show
  1. package/LICENSE +21 -0
  2. package/dist/advisor-models.d.ts +34 -0
  3. package/dist/advisor-models.d.ts.map +1 -0
  4. package/dist/advisor-models.js +39 -0
  5. package/dist/advisor-models.js.map +1 -0
  6. package/dist/agent-identity.d.ts +11 -0
  7. package/dist/agent-identity.d.ts.map +1 -0
  8. package/dist/agent-identity.js +29 -0
  9. package/dist/agent-identity.js.map +1 -0
  10. package/dist/alerts.d.ts +44 -0
  11. package/dist/alerts.d.ts.map +1 -0
  12. package/dist/alerts.js +12 -0
  13. package/dist/alerts.js.map +1 -0
  14. package/dist/backend-api-key-config.d.ts +337 -0
  15. package/dist/backend-api-key-config.d.ts.map +1 -0
  16. package/dist/backend-api-key-config.js +682 -0
  17. package/dist/backend-api-key-config.js.map +1 -0
  18. package/dist/backend.d.ts +93 -0
  19. package/dist/backend.d.ts.map +1 -0
  20. package/dist/backend.js +22 -0
  21. package/dist/backend.js.map +1 -0
  22. package/dist/branding.d.ts +96 -0
  23. package/dist/branding.d.ts.map +1 -0
  24. package/dist/branding.js +102 -0
  25. package/dist/branding.js.map +1 -0
  26. package/dist/chat-session-scope.d.ts +14 -0
  27. package/dist/chat-session-scope.d.ts.map +1 -0
  28. package/dist/chat-session-scope.js +18 -0
  29. package/dist/chat-session-scope.js.map +1 -0
  30. package/dist/date-utils.d.ts +80 -0
  31. package/dist/date-utils.d.ts.map +1 -0
  32. package/dist/date-utils.js +187 -0
  33. package/dist/date-utils.js.map +1 -0
  34. package/dist/docs-frontmatter.d.ts +51 -0
  35. package/dist/docs-frontmatter.d.ts.map +1 -0
  36. package/dist/docs-frontmatter.js +184 -0
  37. package/dist/docs-frontmatter.js.map +1 -0
  38. package/dist/docs-schema.d.ts +79 -0
  39. package/dist/docs-schema.d.ts.map +1 -0
  40. package/dist/docs-schema.js +135 -0
  41. package/dist/docs-schema.js.map +1 -0
  42. package/dist/editable-config-keys.d.ts +14 -0
  43. package/dist/editable-config-keys.d.ts.map +1 -0
  44. package/dist/editable-config-keys.js +157 -0
  45. package/dist/editable-config-keys.js.map +1 -0
  46. package/dist/exec-with-stdin.d.ts +14 -0
  47. package/dist/exec-with-stdin.d.ts.map +1 -0
  48. package/dist/exec-with-stdin.js +35 -0
  49. package/dist/exec-with-stdin.js.map +1 -0
  50. package/dist/index.d.ts +37 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +49 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/integrations-snapshot.d.ts +183 -0
  55. package/dist/integrations-snapshot.d.ts.map +1 -0
  56. package/dist/integrations-snapshot.js +757 -0
  57. package/dist/integrations-snapshot.js.map +1 -0
  58. package/dist/integrations.d.ts +675 -0
  59. package/dist/integrations.d.ts.map +1 -0
  60. package/dist/integrations.js +1656 -0
  61. package/dist/integrations.js.map +1 -0
  62. package/dist/keychain-helper-client.d.ts +31 -0
  63. package/dist/keychain-helper-client.d.ts.map +1 -0
  64. package/dist/keychain-helper-client.js +105 -0
  65. package/dist/keychain-helper-client.js.map +1 -0
  66. package/dist/log-entry.d.ts +14 -0
  67. package/dist/log-entry.d.ts.map +1 -0
  68. package/dist/log-entry.js +2 -0
  69. package/dist/log-entry.js.map +1 -0
  70. package/dist/management-domains.d.ts +369 -0
  71. package/dist/management-domains.d.ts.map +1 -0
  72. package/dist/management-domains.js +499 -0
  73. package/dist/management-domains.js.map +1 -0
  74. package/dist/process-key.d.ts +67 -0
  75. package/dist/process-key.d.ts.map +1 -0
  76. package/dist/process-key.js +366 -0
  77. package/dist/process-key.js.map +1 -0
  78. package/dist/schemas.d.ts +267 -0
  79. package/dist/schemas.d.ts.map +1 -0
  80. package/dist/schemas.js +271 -0
  81. package/dist/schemas.js.map +1 -0
  82. package/dist/secret-client-factory.d.ts +16 -0
  83. package/dist/secret-client-factory.d.ts.map +1 -0
  84. package/dist/secret-client-factory.js +111 -0
  85. package/dist/secret-client-factory.js.map +1 -0
  86. package/dist/secret-client-file.d.ts +51 -0
  87. package/dist/secret-client-file.d.ts.map +1 -0
  88. package/dist/secret-client-file.js +160 -0
  89. package/dist/secret-client-file.js.map +1 -0
  90. package/dist/secret-client-linux.d.ts +26 -0
  91. package/dist/secret-client-linux.d.ts.map +1 -0
  92. package/dist/secret-client-linux.js +63 -0
  93. package/dist/secret-client-linux.js.map +1 -0
  94. package/dist/secret-client-windows.d.ts +37 -0
  95. package/dist/secret-client-windows.d.ts.map +1 -0
  96. package/dist/secret-client-windows.js +82 -0
  97. package/dist/secret-client-windows.js.map +1 -0
  98. package/dist/secret-redaction.d.ts +3 -0
  99. package/dist/secret-redaction.d.ts.map +1 -0
  100. package/dist/secret-redaction.js +31 -0
  101. package/dist/secret-redaction.js.map +1 -0
  102. package/dist/skill-curation/decision-language.d.ts +6 -0
  103. package/dist/skill-curation/decision-language.d.ts.map +1 -0
  104. package/dist/skill-curation/decision-language.js +38 -0
  105. package/dist/skill-curation/decision-language.js.map +1 -0
  106. package/dist/skill-curation/schemas.d.ts +461 -0
  107. package/dist/skill-curation/schemas.d.ts.map +1 -0
  108. package/dist/skill-curation/schemas.js +211 -0
  109. package/dist/skill-curation/schemas.js.map +1 -0
  110. package/dist/types.d.ts +204 -0
  111. package/dist/types.d.ts.map +1 -0
  112. package/dist/types.js +54 -0
  113. package/dist/types.js.map +1 -0
  114. 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