@cosmicdrift/kumiko-guards 0.398.0 → 0.399.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.
@@ -9,201 +9,26 @@ import {
9
9
  writeFileSync,
10
10
  } from "node:fs";
11
11
  import { dirname, join } from "node:path";
12
- import { type ExportedDeclarations, type JSDocableNode, Node, Project, ts } from "ts-morph";
12
+ import { type ExportedDeclarations, type JSDocableNode, Node, type Project } from "ts-morph";
13
+ import { renderApiDocs } from "./agent-docs-api";
14
+ import { renderFeatureDocs } from "./agent-docs-features";
15
+ import {
16
+ compareCodepoint,
17
+ createAgentDocsProject,
18
+ finishFile,
19
+ firstSentence,
20
+ isRecord,
21
+ parseJsonFile,
22
+ table,
23
+ } from "./agent-docs-shared";
13
24
 
14
25
  export const AGENT_DOCS_DIR = "packages/framework/agents";
15
26
 
16
- const FEATURE_MANIFEST_REL = "samples/apps/use-all-bundled/feature-manifest.json";
17
27
  const RENDERER_WEB_INDEX_REL = "packages/renderer/src/web/index.ts";
18
28
  const RECIPES_REL = "samples/recipes";
19
29
  const RECIPE_SLUG_RE = /^[a-z0-9-]+$/;
20
30
  const SOURCE_EXTENSIONS = [".ts", ".tsx"];
21
31
 
22
- type ManifestConfigKeyDoc = {
23
- readonly key: string;
24
- readonly type: string;
25
- readonly scope: string;
26
- readonly default: string | number | boolean | null;
27
- };
28
-
29
- type ManifestSecretDoc = {
30
- readonly qualifiedName: string;
31
- readonly scope: string;
32
- readonly label: string | null;
33
- };
34
-
35
- type ManifestFeatureDoc = {
36
- readonly name: string;
37
- readonly description: string | null;
38
- readonly category: string | null;
39
- readonly requires: readonly string[];
40
- readonly optionalRequires: readonly string[];
41
- readonly exposesApis: readonly string[];
42
- readonly usesApis: readonly string[];
43
- readonly configKeys: readonly ManifestConfigKeyDoc[];
44
- readonly secrets: readonly ManifestSecretDoc[];
45
- };
46
-
47
- function isRecord(value: unknown): value is Record<string, unknown> {
48
- return typeof value === "object" && value !== null && !Array.isArray(value);
49
- }
50
-
51
- function isStringArray(value: unknown): value is readonly string[] {
52
- return Array.isArray(value) && value.every((item) => typeof item === "string");
53
- }
54
-
55
- function isNullableString(value: unknown): value is string | null {
56
- return value === null || typeof value === "string";
57
- }
58
-
59
- function isConfigKeyDoc(value: unknown): value is ManifestConfigKeyDoc {
60
- if (!isRecord(value)) return false;
61
- const fallback = value["default"];
62
- return (
63
- typeof value["key"] === "string" &&
64
- typeof value["type"] === "string" &&
65
- typeof value["scope"] === "string" &&
66
- (fallback === null ||
67
- typeof fallback === "string" ||
68
- typeof fallback === "number" ||
69
- typeof fallback === "boolean")
70
- );
71
- }
72
-
73
- function isSecretDoc(value: unknown): value is ManifestSecretDoc {
74
- return (
75
- isRecord(value) &&
76
- typeof value["qualifiedName"] === "string" &&
77
- typeof value["scope"] === "string" &&
78
- isNullableString(value["label"])
79
- );
80
- }
81
-
82
- function parseFeatureDoc(value: unknown): ManifestFeatureDoc | undefined {
83
- if (!isRecord(value) || typeof value["name"] !== "string") return undefined;
84
- const description = value["description"];
85
- const configKeys = value["configKeys"];
86
- const secrets = value["secrets"];
87
- const uiHints = value["uiHints"];
88
- const category = isRecord(uiHints) ? uiHints["category"] : undefined;
89
- if (
90
- !isNullableString(description) ||
91
- !isStringArray(value["requires"]) ||
92
- !isStringArray(value["optionalRequires"]) ||
93
- !isStringArray(value["exposesApis"]) ||
94
- !isStringArray(value["usesApis"]) ||
95
- !Array.isArray(configKeys) ||
96
- !configKeys.every(isConfigKeyDoc) ||
97
- !Array.isArray(secrets) ||
98
- !secrets.every(isSecretDoc)
99
- ) {
100
- return undefined;
101
- }
102
- return {
103
- name: value["name"],
104
- description,
105
- category: typeof category === "string" ? category : null,
106
- requires: value["requires"],
107
- optionalRequires: value["optionalRequires"],
108
- exposesApis: value["exposesApis"],
109
- usesApis: value["usesApis"],
110
- configKeys,
111
- secrets,
112
- };
113
- }
114
-
115
- function parseJsonFile(relPath: string, raw: string): unknown {
116
- try {
117
- return JSON.parse(raw);
118
- } catch (error) {
119
- throw new Error(`${relPath}: invalid JSON`, { cause: error });
120
- }
121
- }
122
-
123
- function parseFeatureManifest(raw: string): readonly ManifestFeatureDoc[] {
124
- const parsed = parseJsonFile(FEATURE_MANIFEST_REL, raw);
125
- const features = isRecord(parsed) ? parsed["features"] : undefined;
126
- if (!Array.isArray(features)) {
127
- throw new Error(`${FEATURE_MANIFEST_REL}: "features" must be an array`);
128
- }
129
- return features.map((entry, index) => {
130
- const feature = parseFeatureDoc(entry);
131
- if (feature === undefined) {
132
- throw new Error(`${FEATURE_MANIFEST_REL}: features[${index}] has an unexpected shape`);
133
- }
134
- return feature;
135
- });
136
- }
137
-
138
- function compareCodepoint(left: string, right: string): number {
139
- if (left < right) return -1;
140
- return left > right ? 1 : 0;
141
- }
142
-
143
- function generatedHeader(source: string): string {
144
- return `<!-- Generated by \`bun run gen:agent-docs\` from ${source}. Do not edit. -->`;
145
- }
146
-
147
- function finishFile(source: string, body: string): string {
148
- const normalized = body.replace(/\r\n/g, "\n").trim();
149
- return `${generatedHeader(source)}\n\n${normalized}\n`;
150
- }
151
-
152
- function escapeCell(text: string): string {
153
- return text.replace(/\s+/g, " ").trim().replace(/\|/g, "\\|");
154
- }
155
-
156
- function table(headers: readonly string[], rows: readonly (readonly string[])[]): string {
157
- const lines = [
158
- `| ${headers.join(" | ")} |`,
159
- `| ${headers.map(() => "---").join(" | ")} |`,
160
- ...rows.map((row) => `| ${row.map(escapeCell).join(" | ")} |`),
161
- ];
162
- return lines.join("\n");
163
- }
164
-
165
- function codeList(values: readonly string[]): string {
166
- return values.map((value) => `\`${value}\``).join(", ");
167
- }
168
-
169
- function renderFeatureSection(feature: ManifestFeatureDoc): string {
170
- const parts = [`## ${feature.name}`];
171
- if (feature.description !== null) parts.push(feature.description.replace(/\s+/g, " ").trim());
172
- if (feature.category !== null) parts.push(`Category: ${feature.category}`);
173
- if (feature.requires.length > 0) parts.push(`Requires: ${codeList(feature.requires)}`);
174
- if (feature.optionalRequires.length > 0) {
175
- parts.push(`Optional requires: ${codeList(feature.optionalRequires)}`);
176
- }
177
- if (feature.configKeys.length > 0) {
178
- const rows = feature.configKeys.map((configKey) => [
179
- `\`${configKey.key}\``,
180
- configKey.type,
181
- configKey.scope,
182
- configKey.default === null ? "" : `\`${String(configKey.default)}\``,
183
- ]);
184
- parts.push(`Config keys:\n\n${table(["Key", "Type", "Scope", "Default"], rows)}`);
185
- }
186
- if (feature.secrets.length > 0) {
187
- const rows = feature.secrets.map((secret) => [
188
- `\`${secret.qualifiedName}\``,
189
- secret.scope,
190
- secret.label ?? "",
191
- ]);
192
- parts.push(`Secrets:\n\n${table(["Secret", "Scope", "Label"], rows)}`);
193
- }
194
- if (feature.exposesApis.length > 0) parts.push(`Exposes APIs: ${codeList(feature.exposesApis)}`);
195
- if (feature.usesApis.length > 0) parts.push(`Uses APIs: ${codeList(feature.usesApis)}`);
196
- return parts.join("\n\n");
197
- }
198
-
199
- function renderFeatures(repoRoot: string): string {
200
- const raw = readFileSync(join(repoRoot, FEATURE_MANIFEST_REL), "utf-8");
201
- const sections = [...parseFeatureManifest(raw)]
202
- .sort((left, right) => compareCodepoint(left.name, right.name))
203
- .map(renderFeatureSection);
204
- return finishFile(FEATURE_MANIFEST_REL, `# Bundled features\n\n${sections.join("\n\n")}`);
205
- }
206
-
207
32
  type WidgetGroup = "Components" | "Hooks" | "Functions" | "Constants" | "Classes";
208
33
  const WIDGET_GROUP_ORDER: readonly WidgetGroup[] = [
209
34
  "Components",
@@ -241,13 +66,6 @@ function jsDocOwnerOf(declaration: ExportedDeclarations): JSDocableNode | undefi
241
66
  return Node.isJSDocable(declaration) ? declaration : undefined;
242
67
  }
243
68
 
244
- const FIRST_SENTENCE_RE = /^(.+?(?<!\be\.g|\bi\.e)[.!?])(?:\s|$)/;
245
-
246
- function firstSentence(text: string): string {
247
- const flattened = text.replace(/\s+/g, " ").trim();
248
- return FIRST_SENTENCE_RE.exec(flattened)?.[1] ?? flattened;
249
- }
250
-
251
69
  function descriptionOf(declarations: readonly ExportedDeclarations[]): string {
252
70
  for (const declaration of declarations) {
253
71
  const docs = jsDocOwnerOf(declaration)?.getJsDocs() ?? [];
@@ -257,15 +75,7 @@ function descriptionOf(declarations: readonly ExportedDeclarations[]): string {
257
75
  return "";
258
76
  }
259
77
 
260
- function renderWidgets(repoRoot: string): string {
261
- const project = new Project({
262
- compilerOptions: {
263
- module: ts.ModuleKind.ESNext,
264
- moduleResolution: ts.ModuleResolutionKind.Bundler,
265
- target: ts.ScriptTarget.ES2022,
266
- jsx: ts.JsxEmit.ReactJSX,
267
- },
268
- });
78
+ function renderWidgets(repoRoot: string, project: Project): string {
269
79
  const indexFile = project.addSourceFileAtPath(join(repoRoot, RENDERER_WEB_INDEX_REL));
270
80
  const grouped = new Map<WidgetGroup, [string, string][]>(
271
81
  WIDGET_GROUP_ORDER.map((group) => [group, []]),
@@ -381,9 +191,13 @@ function renderRecipe(recipe: Recipe): string {
381
191
 
382
192
  export function renderAgentDocs(repoRoot: string): ReadonlyMap<string, string> {
383
193
  const recipes = readRecipes(repoRoot);
194
+ const project = createAgentDocsProject(repoRoot);
195
+ const featureDocs = renderFeatureDocs(repoRoot, project);
384
196
  const files = new Map<string, string>();
385
- files.set("features.md", renderFeatures(repoRoot));
386
- files.set("widgets.md", renderWidgets(repoRoot));
197
+ files.set("features-index.md", featureDocs.featuresIndex);
198
+ files.set("features.md", featureDocs.features);
199
+ files.set("widgets.md", renderWidgets(repoRoot, project));
200
+ for (const [rel, content] of renderApiDocs(repoRoot, project)) files.set(rel, content);
387
201
  files.set("examples.md", renderExamplesIndex(recipes));
388
202
  for (const recipe of recipes) files.set(`examples/${recipe.slug}.md`, renderRecipe(recipe));
389
203
  return files;
@@ -2,7 +2,8 @@
2
2
  /**
3
3
  * Guard: the generated agent docs under `packages/framework/agents/` must
4
4
  * match what `bun run gen:agent-docs` renders from the current sources
5
- * (feature manifest, renderer web exports, sample recipes), and every
5
+ * (feature manifest, public entry-point signatures, renderer web exports,
6
+ * sample recipes), and every
6
7
  * `agents/<file>.md` path the hand-written `packages/framework/AGENTS.md`
7
8
  * points at must exist in that generated set.
8
9
  *