@devory/core 0.1.1 → 0.3.1

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/src/standards.ts DELETED
@@ -1,283 +0,0 @@
1
- /**
2
- * packages/core/src/standards.ts
3
- *
4
- * Loads and types the devory.standards.yml configuration file.
5
- *
6
- * devory.standards.yml is the user-facing doctrine source — the structured
7
- * definition of what "good" means for a given codebase. When present it takes
8
- * precedence over the freeform brain/ markdown files.
9
- *
10
- * Entry points:
11
- * loadStandards(factoryRoot) — load and parse the YAML file
12
- * serializeStandardsAsDoctrine(s) — render as a string for AI context injection
13
- */
14
-
15
- import * as fs from "fs";
16
- import * as path from "path";
17
- import { fileURLToPath } from "url";
18
- import { load as parseYaml } from "js-yaml";
19
-
20
- const __filename = fileURLToPath(import.meta.url);
21
- const __dirname = path.dirname(__filename);
22
-
23
- // ---------------------------------------------------------------------------
24
- // Schema types
25
- // ---------------------------------------------------------------------------
26
-
27
- export interface StandardsStack {
28
- language?: string;
29
- framework?: string;
30
- database?: string;
31
- }
32
-
33
- export interface StandardsTesting {
34
- require_unit?: boolean;
35
- require_integration?: boolean;
36
- coverage_threshold?: number;
37
- avoid_mocking?: string[];
38
- }
39
-
40
- export interface StandardsArchitecture {
41
- pattern?: string;
42
- max_file_lines?: number;
43
- no_circular_deps?: boolean;
44
- }
45
-
46
- export interface StandardsCodeStyle {
47
- no_any?: boolean;
48
- prefer_explicit_over_clever?: boolean;
49
- }
50
-
51
- export interface StandardsDoctrine {
52
- extends?: string;
53
- testing?: StandardsTesting;
54
- architecture?: StandardsArchitecture;
55
- code_style?: StandardsCodeStyle;
56
- /** Pro/Teams tier only — ignored on Core */
57
- custom_rules?: string[];
58
- }
59
-
60
- export interface Standards {
61
- version?: string;
62
- stack?: StandardsStack;
63
- doctrine?: StandardsDoctrine;
64
- }
65
-
66
- // ---------------------------------------------------------------------------
67
- // Source descriptor
68
- // ---------------------------------------------------------------------------
69
-
70
- export type StandardsSourceType = "yaml" | "brain" | "none";
71
-
72
- export interface StandardsSource {
73
- type: StandardsSourceType;
74
- /** Absolute path to devory.standards.yml, if found */
75
- path?: string;
76
- }
77
-
78
- export interface LoadedStandards {
79
- standards: Standards;
80
- source: StandardsSource;
81
- }
82
-
83
- // ---------------------------------------------------------------------------
84
- // Loader
85
- // ---------------------------------------------------------------------------
86
-
87
- export const STANDARDS_FILENAME = "devory.standards.yml";
88
-
89
- /**
90
- * Load devory.standards.yml from the factory root.
91
- * Returns null standards and source type "none" when the file does not exist.
92
- * Throws a descriptive error if the file exists but cannot be parsed.
93
- */
94
- export function loadStandards(factoryRoot: string): LoadedStandards {
95
- const filePath = path.join(factoryRoot, STANDARDS_FILENAME);
96
-
97
- if (!fs.existsSync(filePath)) {
98
- return { standards: {}, source: { type: "none" } };
99
- }
100
-
101
- const raw = fs.readFileSync(filePath, "utf-8");
102
-
103
- let parsed: unknown;
104
- try {
105
- parsed = parseYaml(raw);
106
- } catch (err) {
107
- throw new Error(
108
- `devory: failed to parse ${STANDARDS_FILENAME}: ${err instanceof Error ? err.message : String(err)}`
109
- );
110
- }
111
-
112
- if (parsed === null || typeof parsed !== "object") {
113
- throw new Error(`devory: ${STANDARDS_FILENAME} must be a YAML object, got: ${typeof parsed}`);
114
- }
115
-
116
- const user = parsed as Standards;
117
- const extendsValue = (user as Standards & { extends?: string }).extends
118
- ?? user.doctrine?.extends;
119
-
120
- // Resolve extends chain if a bundled baseline is referenced
121
- const standards = extendsValue
122
- ? mergeStandards(loadBaseline(extendsValue), user)
123
- : user;
124
-
125
- return {
126
- standards,
127
- source: { type: "yaml", path: filePath },
128
- };
129
- }
130
-
131
- // ---------------------------------------------------------------------------
132
- // Baseline resolver
133
- //
134
- // Maps "@devory/defaults/<name>" to the bundled YAML file shipped with
135
- // @devory/core. Users reference baselines via the `extends` field in
136
- // devory.standards.yml or within baseline files themselves.
137
- // ---------------------------------------------------------------------------
138
-
139
- const DEVORY_DEFAULTS_PREFIX = "@devory/defaults/";
140
- const DEFAULTS_DIR = path.join(__dirname, "defaults");
141
-
142
- const KNOWN_BASELINES: Record<string, string> = {
143
- generic: "generic.yml",
144
- "typescript-nextjs": "typescript-nextjs.yml",
145
- "typescript-node": "typescript-node.yml",
146
- };
147
-
148
- /**
149
- * Resolve an extends string to an absolute path.
150
- * Returns null if the baseline is not a bundled Devory baseline or does not exist.
151
- */
152
- export function resolveBaselinePath(extendsValue: string): string | null {
153
- if (!extendsValue.startsWith(DEVORY_DEFAULTS_PREFIX)) return null;
154
- const key = extendsValue.slice(DEVORY_DEFAULTS_PREFIX.length);
155
- const filename = KNOWN_BASELINES[key];
156
- if (!filename) return null;
157
- return path.join(DEFAULTS_DIR, filename);
158
- }
159
-
160
- /**
161
- * Load a bundled Devory baseline by its extends identifier.
162
- * Recursively resolves the baseline's own extends chain (e.g. typescript-nextjs → generic).
163
- * Returns an empty Standards object if the baseline cannot be found.
164
- */
165
- export function loadBaseline(extendsValue: string): Standards {
166
- const filePath = resolveBaselinePath(extendsValue);
167
- if (!filePath || !fs.existsSync(filePath)) return {};
168
-
169
- const raw = fs.readFileSync(filePath, "utf-8");
170
- const parsed = parseYaml(raw);
171
- if (!parsed || typeof parsed !== "object") return {};
172
-
173
- const baseline = parsed as Standards & { extends?: string };
174
- const parentExtends = baseline.extends;
175
-
176
- if (parentExtends) {
177
- const parent = loadBaseline(parentExtends);
178
- return mergeStandards(parent, baseline);
179
- }
180
-
181
- return baseline as Standards;
182
- }
183
-
184
- /**
185
- * Merge two Standards objects — base values are overridden by overrides.
186
- * Only one level of nesting is merged (stack, doctrine sub-objects).
187
- */
188
- export function mergeStandards(base: Standards, overrides: Standards): Standards {
189
- return {
190
- version: overrides.version ?? base.version,
191
- stack: overrides.stack || base.stack
192
- ? { ...base.stack, ...overrides.stack }
193
- : undefined,
194
- doctrine: overrides.doctrine || base.doctrine
195
- ? {
196
- ...base.doctrine,
197
- ...overrides.doctrine,
198
- testing: overrides.doctrine?.testing || base.doctrine?.testing
199
- ? { ...base.doctrine?.testing, ...overrides.doctrine?.testing }
200
- : undefined,
201
- architecture: overrides.doctrine?.architecture || base.doctrine?.architecture
202
- ? { ...base.doctrine?.architecture, ...overrides.doctrine?.architecture }
203
- : undefined,
204
- code_style: overrides.doctrine?.code_style || base.doctrine?.code_style
205
- ? { ...base.doctrine?.code_style, ...overrides.doctrine?.code_style }
206
- : undefined,
207
- }
208
- : undefined,
209
- };
210
- }
211
-
212
- // ---------------------------------------------------------------------------
213
- // Doctrine serializer
214
- //
215
- // Renders a Standards object as a human-readable markdown-ish string
216
- // suitable for injection into an AI worker's context alongside other doctrine.
217
- // ---------------------------------------------------------------------------
218
-
219
- export function serializeStandardsAsDoctrine(standards: Standards): string {
220
- const lines: string[] = ["# Engineering Standards (devory.standards.yml)", ""];
221
-
222
- const { stack, doctrine } = standards;
223
-
224
- if (stack) {
225
- lines.push("## Stack");
226
- if (stack.language) lines.push(`- Language: ${stack.language}`);
227
- if (stack.framework) lines.push(`- Framework: ${stack.framework}`);
228
- if (stack.database) lines.push(`- Database: ${stack.database}`);
229
- lines.push("");
230
- }
231
-
232
- if (doctrine) {
233
- if (doctrine.extends) {
234
- lines.push(`## Baseline`);
235
- lines.push(`Extends: ${doctrine.extends}`);
236
- lines.push("");
237
- }
238
-
239
- if (doctrine.testing) {
240
- lines.push("## Testing Standards");
241
- const t = doctrine.testing;
242
- if (t.require_unit !== undefined)
243
- lines.push(`- Unit tests required: ${t.require_unit}`);
244
- if (t.require_integration !== undefined)
245
- lines.push(`- Integration tests required: ${t.require_integration}`);
246
- if (t.coverage_threshold !== undefined)
247
- lines.push(`- Coverage threshold: ${t.coverage_threshold}%`);
248
- if (t.avoid_mocking?.length)
249
- lines.push(`- Avoid mocking: ${t.avoid_mocking.join(", ")}`);
250
- lines.push("");
251
- }
252
-
253
- if (doctrine.architecture) {
254
- lines.push("## Architecture Standards");
255
- const a = doctrine.architecture;
256
- if (a.pattern) lines.push(`- Pattern: ${a.pattern}`);
257
- if (a.max_file_lines !== undefined)
258
- lines.push(`- Max file lines: ${a.max_file_lines}`);
259
- if (a.no_circular_deps !== undefined)
260
- lines.push(`- No circular dependencies: ${a.no_circular_deps}`);
261
- lines.push("");
262
- }
263
-
264
- if (doctrine.code_style) {
265
- lines.push("## Code Style Standards");
266
- const cs = doctrine.code_style;
267
- if (cs.no_any !== undefined) lines.push(`- No \`any\` type: ${cs.no_any}`);
268
- if (cs.prefer_explicit_over_clever !== undefined)
269
- lines.push(`- Prefer explicit over clever: ${cs.prefer_explicit_over_clever}`);
270
- lines.push("");
271
- }
272
-
273
- if (doctrine.custom_rules?.length) {
274
- lines.push("## Custom Rules");
275
- for (const rule of doctrine.custom_rules) {
276
- lines.push(`- ${rule}`);
277
- }
278
- lines.push("");
279
- }
280
- }
281
-
282
- return lines.join("\n").trimEnd();
283
- }