@shrkcrft/presets 0.1.0-alpha.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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +15 -0
  3. package/dist/apply/preview-apply.d.ts +52 -0
  4. package/dist/apply/preview-apply.d.ts.map +1 -0
  5. package/dist/apply/preview-apply.js +85 -0
  6. package/dist/builtin/builtin-presets.d.ts +3 -0
  7. package/dist/builtin/builtin-presets.d.ts.map +1 -0
  8. package/dist/builtin/builtin-presets.js +520 -0
  9. package/dist/builtin/r26-presets.d.ts +31 -0
  10. package/dist/builtin/r26-presets.d.ts.map +1 -0
  11. package/dist/builtin/r26-presets.js +458 -0
  12. package/dist/builtin/r26-snippets.d.ts +39 -0
  13. package/dist/builtin/r26-snippets.d.ts.map +1 -0
  14. package/dist/builtin/r26-snippets.js +257 -0
  15. package/dist/builtin/r45-presets.d.ts +7 -0
  16. package/dist/builtin/r45-presets.d.ts.map +1 -0
  17. package/dist/builtin/r45-presets.js +186 -0
  18. package/dist/builtin/r47-presets.d.ts +5 -0
  19. package/dist/builtin/r47-presets.d.ts.map +1 -0
  20. package/dist/builtin/r47-presets.js +65 -0
  21. package/dist/builtin/shared-snippets.d.ts +17 -0
  22. package/dist/builtin/shared-snippets.d.ts.map +1 -0
  23. package/dist/builtin/shared-snippets.js +264 -0
  24. package/dist/define/define-preset.d.ts +4 -0
  25. package/dist/define/define-preset.d.ts.map +1 -0
  26. package/dist/define/define-preset.js +4 -0
  27. package/dist/emit/synthesize-files.d.ts +26 -0
  28. package/dist/emit/synthesize-files.d.ts.map +1 -0
  29. package/dist/emit/synthesize-files.js +172 -0
  30. package/dist/index.d.ts +11 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +10 -0
  33. package/dist/model/preset.d.ts +83 -0
  34. package/dist/model/preset.d.ts.map +1 -0
  35. package/dist/model/preset.js +21 -0
  36. package/dist/registry/load-presets.d.ts +12 -0
  37. package/dist/registry/load-presets.d.ts.map +1 -0
  38. package/dist/registry/load-presets.js +33 -0
  39. package/dist/registry/preset-registry.d.ts +11 -0
  40. package/dist/registry/preset-registry.d.ts.map +1 -0
  41. package/dist/registry/preset-registry.js +22 -0
  42. package/dist/registry/recommend.d.ts +30 -0
  43. package/dist/registry/recommend.d.ts.map +1 -0
  44. package/dist/registry/recommend.js +59 -0
  45. package/dist/registry/resolve-preset.d.ts +75 -0
  46. package/dist/registry/resolve-preset.d.ts.map +1 -0
  47. package/dist/registry/resolve-preset.js +207 -0
  48. package/dist/registry/resolve-references.d.ts +49 -0
  49. package/dist/registry/resolve-references.d.ts.map +1 -0
  50. package/dist/registry/resolve-references.js +38 -0
  51. package/package.json +51 -0
@@ -0,0 +1,257 @@
1
+ // Reusable rule / knowledge expression strings for the Modern Angular preset library.
2
+ // These are embedded verbatim into the synthesized sharkcraft/*.ts files,
3
+ // using `defineKnowledgeEntry` from @shrkcrft/knowledge.
4
+ export function ruleSnippet(opts) {
5
+ const prio = opts.priority.charAt(0).toUpperCase() + opts.priority.slice(1);
6
+ return `defineKnowledgeEntry({
7
+ id: ${JSON.stringify(opts.id)},
8
+ title: ${JSON.stringify(opts.title)},
9
+ type: KnowledgeType.Rule,
10
+ priority: KnowledgePriority.${prio},
11
+ tags: ${JSON.stringify(opts.tags)},
12
+ appliesWhen: ${JSON.stringify(opts.appliesWhen)},
13
+ content: ${JSON.stringify(opts.content)},
14
+ })`;
15
+ }
16
+ // ─── Strict TypeScript rules ───────────────────────────────────────────────
17
+ export const TS_NO_ANY = ruleSnippet({
18
+ id: 'ts.no-any',
19
+ title: 'Avoid `any` in public surfaces',
20
+ priority: 'critical',
21
+ tags: ['typescript', 'safety'],
22
+ appliesWhen: ['generate-code', 'refactor', 'review'],
23
+ content: 'Avoid `any` unless isolated and justified. Prefer `unknown` for external input and narrow it explicitly. Use type assertions only when no other option exists.',
24
+ });
25
+ export const TS_PREFER_SATISFIES = ruleSnippet({
26
+ id: 'ts.prefer-satisfies',
27
+ title: 'Prefer `satisfies` over object casts',
28
+ priority: 'high',
29
+ tags: ['typescript'],
30
+ appliesWhen: ['generate-code', 'refactor'],
31
+ content: 'Use `satisfies` for object-shape validation to preserve narrowed literal types instead of `as` casts.',
32
+ });
33
+ export const TS_DISCRIMINATED_UNIONS = ruleSnippet({
34
+ id: 'ts.discriminated-unions',
35
+ title: 'Use discriminated unions for state machines',
36
+ priority: 'high',
37
+ tags: ['typescript', 'state'],
38
+ appliesWhen: ['generate-code'],
39
+ content: 'Model finite-state shapes as discriminated unions (`type T = { kind: "a"; … } | { kind: "b"; … }`), not booleans + optional fields.',
40
+ });
41
+ export const TS_READONLY_DEFAULT = ruleSnippet({
42
+ id: 'ts.readonly-default',
43
+ title: 'Prefer `readonly` for immutable data',
44
+ priority: 'medium',
45
+ tags: ['typescript'],
46
+ appliesWhen: ['generate-code'],
47
+ content: 'Use `readonly` arrays/properties whenever mutation is not part of the contract — it documents intent and unlocks safer call sites.',
48
+ });
49
+ export const TS_PUBLIC_RETURN_TYPES = ruleSnippet({
50
+ id: 'ts.public-return-types',
51
+ title: 'Annotate public function return types',
52
+ priority: 'high',
53
+ tags: ['typescript', 'api'],
54
+ appliesWhen: ['generate-code', 'review'],
55
+ content: 'Public/exported functions must declare an explicit return type — TS infers internally, but the signature is part of the contract.',
56
+ });
57
+ export const TS_NO_FLOATING_PROMISES = ruleSnippet({
58
+ id: 'ts.no-floating-promises',
59
+ title: 'Never let a Promise float',
60
+ priority: 'critical',
61
+ tags: ['typescript', 'async'],
62
+ appliesWhen: ['generate-code', 'review'],
63
+ content: 'Always `await` or explicitly `.catch()` rejected promises. Floating promises hide errors and produce nondeterministic ordering.',
64
+ });
65
+ export const TS_ERROR_HANDLING = ruleSnippet({
66
+ id: 'ts.error-handling',
67
+ title: 'Model expected failures, do not swallow errors',
68
+ priority: 'critical',
69
+ tags: ['typescript', 'errors'],
70
+ appliesWhen: ['generate-code', 'review'],
71
+ content: 'Throw typed errors (or use Result types) for expected failure paths. Preserve `cause` when wrapping. Never `catch (e) { /* ignore */ }`.',
72
+ });
73
+ export const TS_NO_DEEP_IMPORTS = ruleSnippet({
74
+ id: 'ts.no-deep-imports',
75
+ title: 'No deep imports across package boundaries',
76
+ priority: 'critical',
77
+ tags: ['typescript', 'monorepo'],
78
+ appliesWhen: ['generate-code', 'review'],
79
+ content: 'Import only from a package\'s public entrypoint (`@scope/pkg`), never from internal files (`@scope/pkg/src/internal/...`).',
80
+ });
81
+ export const TS_VALIDATE_BOUNDARY_INPUT = ruleSnippet({
82
+ id: 'ts.validate-boundary-input',
83
+ title: 'Validate untrusted input at boundaries',
84
+ priority: 'high',
85
+ tags: ['typescript', 'safety'],
86
+ appliesWhen: ['generate-code'],
87
+ content: 'External payloads (HTTP, IPC, files) must be parsed/validated (e.g. zod, custom guard) before crossing into the typed domain.',
88
+ });
89
+ export const TS_BRANDED_IDS = ruleSnippet({
90
+ id: 'ts.branded-ids',
91
+ title: 'Brand critical identifiers',
92
+ priority: 'medium',
93
+ tags: ['typescript', 'modeling'],
94
+ appliesWhen: ['generate-code'],
95
+ content: 'For ids that travel widely (UserId, OrderId, etc.) use branded types so they cannot be accidentally swapped with raw strings/numbers.',
96
+ });
97
+ export const TS_NO_CIRCULAR_IMPORTS = ruleSnippet({
98
+ id: 'ts.no-circular-imports',
99
+ title: 'No circular imports',
100
+ priority: 'critical',
101
+ tags: ['typescript', 'monorepo'],
102
+ appliesWhen: ['generate-code', 'review'],
103
+ content: 'Cycles produce unpredictable initialisation order. If you need bidirectional knowledge, split into a shared types module both sides depend on.',
104
+ });
105
+ export const TS_AGENT_SMALL_DIFFS = ruleSnippet({
106
+ id: 'ts.agent.small-diffs',
107
+ title: 'Prefer small incremental changes',
108
+ priority: 'high',
109
+ tags: ['typescript', 'agent'],
110
+ appliesWhen: ['generate-code', 'refactor'],
111
+ content: 'AI agents must inspect existing patterns before generating. Prefer minimal, targeted changes; do not rewrite architecture without an explicit instruction.',
112
+ });
113
+ // ─── Modern Angular rules ──────────────────────────────────────────────────
114
+ export const NG_STANDALONE_COMPONENTS = ruleSnippet({
115
+ id: 'angular.standalone-components',
116
+ title: 'Prefer standalone components',
117
+ priority: 'high',
118
+ tags: ['angular', 'architecture'],
119
+ appliesWhen: ['generate-code'],
120
+ content: 'New components, directives and pipes should be standalone unless an existing NgModule contract requires otherwise.',
121
+ });
122
+ export const NG_ON_PUSH = ruleSnippet({
123
+ id: 'angular.on-push',
124
+ title: 'Use OnPush change detection',
125
+ priority: 'high',
126
+ tags: ['angular', 'performance'],
127
+ appliesWhen: ['generate-code'],
128
+ content: 'Components default to `ChangeDetectionStrategy.OnPush` unless they intentionally rely on default CD. Combine with signals/observables for explicit reactivity.',
129
+ });
130
+ export const NG_SIGNALS_FIRST = ruleSnippet({
131
+ id: 'angular.signals-first',
132
+ title: 'Prefer signals for local reactive state',
133
+ priority: 'high',
134
+ tags: ['angular', 'signals'],
135
+ appliesWhen: ['generate-code'],
136
+ content: 'Use `signal()` for local state and `computed()` for derived values. Use `effect()` only for side effects (DOM/I/O). Do not write to signals inside effects.',
137
+ });
138
+ export const NG_RXJS_NO_NESTED_SUBSCRIBE = ruleSnippet({
139
+ id: 'angular.rxjs.no-nested-subscribe',
140
+ title: 'No nested `subscribe`',
141
+ priority: 'critical',
142
+ tags: ['angular', 'rxjs'],
143
+ appliesWhen: ['generate-code', 'review'],
144
+ content: 'Use `switchMap` / `concatMap` / `mergeMap` / `exhaustMap` deliberately to compose streams. A second `.subscribe()` inside a `.subscribe()` body is almost always a bug.',
145
+ });
146
+ export const NG_LIFECYCLE_SAFE_CLEANUP = ruleSnippet({
147
+ id: 'angular.rxjs.lifecycle-cleanup',
148
+ title: 'Use lifecycle-safe cleanup for subscriptions',
149
+ priority: 'high',
150
+ tags: ['angular', 'rxjs'],
151
+ appliesWhen: ['generate-code'],
152
+ content: 'Wire subscriptions through `takeUntilDestroyed()` (Angular 16+) or a destroy `Subject` so observables tear down with the component.',
153
+ });
154
+ export const NG_TRACK_BY = ruleSnippet({
155
+ id: 'angular.track-by',
156
+ title: 'Use `trackBy` / `@for track`',
157
+ priority: 'high',
158
+ tags: ['angular', 'performance'],
159
+ appliesWhen: ['generate-code'],
160
+ content: 'Always supply a `trackBy` function (or `track` expression in the new control flow) for lists to avoid full DOM re-rendering on every change.',
161
+ });
162
+ export const NG_NO_BUSINESS_LOGIC_IN_TEMPLATE = ruleSnippet({
163
+ id: 'angular.no-business-logic-in-template',
164
+ title: 'Keep business logic out of templates',
165
+ priority: 'medium',
166
+ tags: ['angular', 'components'],
167
+ appliesWhen: ['generate-code', 'review'],
168
+ content: 'Templates should bind to fields, signals, getters and pipes. Complex predicates or transformations belong in the component class or a pure pipe.',
169
+ });
170
+ export const NG_TYPED_REACTIVE_FORMS = ruleSnippet({
171
+ id: 'angular.typed-reactive-forms',
172
+ title: 'Use typed reactive forms',
173
+ priority: 'high',
174
+ tags: ['angular', 'forms'],
175
+ appliesWhen: ['generate-code'],
176
+ content: 'Prefer typed `FormGroup<T>` / `FormControl<T>` so the form value matches the domain model. Avoid `any` form values.',
177
+ });
178
+ export const NG_LAZY_ROUTES = ruleSnippet({
179
+ id: 'angular.lazy-routes',
180
+ title: 'Lazy-load feature routes',
181
+ priority: 'high',
182
+ tags: ['angular', 'routing', 'performance'],
183
+ appliesWhen: ['generate-code'],
184
+ content: 'Use `loadComponent` / `loadChildren` for feature routes so the initial bundle stays small. Heavy components should not be eager-loaded.',
185
+ });
186
+ export const NG_GUARDS_SMALL = ruleSnippet({
187
+ id: 'angular.guards-small',
188
+ title: 'Keep guards / resolvers small',
189
+ priority: 'medium',
190
+ tags: ['angular', 'routing'],
191
+ appliesWhen: ['generate-code'],
192
+ content: 'Guards/resolvers should make a decision quickly. Business logic belongs in a service called by the guard, not inline.',
193
+ });
194
+ export const NG_NO_DEEP_LIB_IMPORTS = ruleSnippet({
195
+ id: 'angular.no-deep-lib-imports',
196
+ title: 'No deep imports across libraries',
197
+ priority: 'critical',
198
+ tags: ['angular', 'monorepo'],
199
+ appliesWhen: ['generate-code', 'review'],
200
+ content: 'Import from a library\'s `index.ts` barrel only. Deep paths break Nx boundaries and the public API contract.',
201
+ });
202
+ export const NG_FEATURE_FOLDERS = ruleSnippet({
203
+ id: 'angular.feature-folders',
204
+ title: 'Prefer feature-oriented folders',
205
+ priority: 'medium',
206
+ tags: ['angular', 'architecture'],
207
+ appliesWhen: ['generate-code'],
208
+ content: 'Group component + service + tests by feature, not by file kind. Avoid `components/`, `services/`, etc. as top-level grab-bags.',
209
+ });
210
+ export const NG_NO_GOD_SERVICES = ruleSnippet({
211
+ id: 'angular.no-god-services',
212
+ title: 'Avoid god services',
213
+ priority: 'high',
214
+ tags: ['angular', 'services'],
215
+ appliesWhen: ['generate-code'],
216
+ content: 'A service should own a focused concern. If a service mixes UI state, API calls, and business rules, split it.',
217
+ });
218
+ export const NG_DOMAIN_NO_UI_IMPORTS = ruleSnippet({
219
+ id: 'angular.domain.no-ui-imports',
220
+ title: 'Domain services must not import UI',
221
+ priority: 'critical',
222
+ tags: ['angular', 'boundaries'],
223
+ appliesWhen: ['generate-code', 'review'],
224
+ content: 'Domain / data services live below the UI layer. They must not import components, templates, or anything from `@angular/animations`/`@angular/router`.',
225
+ });
226
+ export const NG_ACCESSIBLE = ruleSnippet({
227
+ id: 'angular.accessible',
228
+ title: 'Semantic HTML + keyboard support',
229
+ priority: 'high',
230
+ tags: ['angular', 'a11y'],
231
+ appliesWhen: ['generate-code', 'review'],
232
+ content: 'Interactive elements must be reachable by keyboard, have visible focus, and use semantic HTML. Use ARIA only where semantic HTML is insufficient.',
233
+ });
234
+ export const NG_AVOID_BYPASS_SECURITY = ruleSnippet({
235
+ id: 'angular.security.no-bypass',
236
+ title: 'Avoid `bypassSecurityTrust*`',
237
+ priority: 'critical',
238
+ tags: ['angular', 'security'],
239
+ appliesWhen: ['generate-code', 'review'],
240
+ content: '`DomSanitizer.bypassSecurityTrust*` opens an XSS hole. Only use it with reviewed, trusted inputs — sanitize otherwise.',
241
+ });
242
+ export const NG_PLUGIN_STABLE_CONTRACT = ruleSnippet({
243
+ id: 'angular.plugin.stable-contract',
244
+ title: 'Plugin contracts are stable',
245
+ priority: 'critical',
246
+ tags: ['angular', 'plugins'],
247
+ appliesWhen: ['generate-code', 'review'],
248
+ content: 'Plugin manifests, lifecycle hooks, and capability tokens are public API. Breaking changes require a migration note and a major-version bump.',
249
+ });
250
+ export const NG_PLUGIN_NO_DEEP_IMPORTS = ruleSnippet({
251
+ id: 'angular.plugin.no-deep-imports',
252
+ title: 'Plugins cannot deep-import each other',
253
+ priority: 'critical',
254
+ tags: ['angular', 'plugins', 'boundaries'],
255
+ appliesWhen: ['generate-code', 'review'],
256
+ content: 'Plugin-to-plugin communication must use the documented event/token contract. Direct imports between plugin packages are forbidden.',
257
+ });
@@ -0,0 +1,7 @@
1
+ import type { IPreset } from '../model/preset.js';
2
+ export declare const NEXT_APP_PRESET: IPreset;
3
+ export declare const TURBOREPO_PRESET: IPreset;
4
+ export declare const PACKAGE_WORKSPACE_PRESET: IPreset;
5
+ export declare const CLEAN_ARCHITECTURE_TS_PRESET: IPreset;
6
+ export declare const R45_PRESETS: readonly IPreset[];
7
+ //# sourceMappingURL=r45-presets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"r45-presets.d.ts","sourceRoot":"","sources":["../../src/builtin/r45-presets.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAwBlD,eAAO,MAAM,eAAe,EAAE,OA+C5B,CAAC;AAEH,eAAO,MAAM,gBAAgB,EAAE,OA+C7B,CAAC;AAEH,eAAO,MAAM,wBAAwB,EAAE,OAuCrC,CAAC;AAEH,eAAO,MAAM,4BAA4B,EAAE,OAoDzC,CAAC;AAEH,eAAO,MAAM,WAAW,EAAE,SAAS,OAAO,EAKxC,CAAC"}
@@ -0,0 +1,186 @@
1
+ import { WorkspaceProfile } from '@shrkcrft/workspace';
2
+ import { definePreset } from "../define/define-preset.js";
3
+ import { COMMON_AGENT_BRIEFING, COMMON_PATH_SERVICES, COMMON_PATH_TESTS, COMMON_PATH_UTILS, COMMON_PIPELINE_CONTEXT_ONLY, COMMON_PIPELINE_FEATURE_DEV, COMMON_PIPELINE_UNIT_TEST, COMMON_RULE_INTERFACE_PREFIX, COMMON_RULE_NO_LOGIC_CONSTRUCTORS, COMMON_RULE_ONE_EXPORT, COMMON_SAFETY_RULE, COMMON_TEMPLATE_SERVICE, COMMON_TEMPLATE_TEST, COMMON_TEMPLATE_UTILITY, OVERVIEW_DOC, } from "./shared-snippets.js";
4
+ // Universal adoption — fills four Phase-1 preset gaps:
5
+ // next-app, turborepo, package-workspace, clean-architecture-ts.
6
+ // Each preset is generic — no project-specific anchors, no per-stack jargon
7
+ // that an unrelated team cannot use as-is.
8
+ export const NEXT_APP_PRESET = definePreset({
9
+ id: 'next-app',
10
+ title: 'Next.js app',
11
+ description: 'Next.js app baseline (app-router-first): server components by default, route-group conventions, no business logic in pages, typed search params.',
12
+ tags: ['next', 'react', 'frontend'],
13
+ appliesTo: [WorkspaceProfile.HasNext, WorkspaceProfile.HasReact, WorkspaceProfile.IsFrontend],
14
+ weight: 8,
15
+ composes: ['strict-typescript', 'react-app'],
16
+ includes: {
17
+ knowledge: [
18
+ COMMON_AGENT_BRIEFING,
19
+ `defineKnowledgeEntry({
20
+ id: 'next.app-router',
21
+ title: 'Prefer the Next.js App Router for new routes',
22
+ type: KnowledgeType.Rule,
23
+ priority: KnowledgePriority.High,
24
+ tags: ['next', 'routing'],
25
+ appliesWhen: ['generate-code'],
26
+ content: 'New routes live under app/. The pages/ router is legacy; do not introduce new pages/ routes unless explicitly migrating.',
27
+ })`,
28
+ `defineKnowledgeEntry({
29
+ id: 'next.no-business-logic-in-page',
30
+ title: 'Keep business logic out of page.tsx / layout.tsx',
31
+ type: KnowledgeType.Rule,
32
+ priority: KnowledgePriority.High,
33
+ tags: ['next', 'architecture'],
34
+ appliesWhen: ['generate-code', 'refactor'],
35
+ content: 'Page and layout components compose. Domain logic, data fetching, and validation live in colocated modules under app/.../_lib or feature folders.',
36
+ })`,
37
+ ],
38
+ rules: [COMMON_SAFETY_RULE, COMMON_RULE_ONE_EXPORT],
39
+ paths: [COMMON_PATH_TESTS],
40
+ templates: [],
41
+ pipelines: [COMMON_PIPELINE_CONTEXT_ONLY, COMMON_PIPELINE_FEATURE_DEV],
42
+ docs: {
43
+ 'overview.md': OVERVIEW_DOC('Next.js app', 'App Router by default. Server components for data fetching. Client components only at the leaves. Pages are thin composition; business logic stays in colocated modules.'),
44
+ },
45
+ },
46
+ recommendedNextCommands: [
47
+ 'shrk doctor',
48
+ 'shrk task "<task>"',
49
+ 'shrk gen typescript.utility <name> --dry-run',
50
+ ],
51
+ });
52
+ export const TURBOREPO_PRESET = definePreset({
53
+ id: 'turborepo',
54
+ title: 'Turborepo monorepo',
55
+ description: 'Conventions for Turborepo workspaces: package layer order, public entry points, no relative cross-package imports, run via the affected task graph.',
56
+ tags: ['turborepo', 'monorepo'],
57
+ appliesTo: [WorkspaceProfile.HasTurborepo, WorkspaceProfile.IsMonorepo],
58
+ weight: 8,
59
+ composes: ['strict-typescript'],
60
+ includes: {
61
+ knowledge: [
62
+ COMMON_AGENT_BRIEFING,
63
+ `defineKnowledgeEntry({
64
+ id: 'turborepo.affected-tasks',
65
+ title: 'Use turbo run for affected tasks; avoid blanket rebuilds',
66
+ type: KnowledgeType.Rule,
67
+ priority: KnowledgePriority.High,
68
+ tags: ['turborepo', 'ci'],
69
+ appliesWhen: ['generate-code', 'fix-build'],
70
+ content: 'Prefer \`turbo run <task> --filter=...\` over running every package. Wire CI to the affected task graph so unrelated packages stay green.',
71
+ })`,
72
+ `defineKnowledgeEntry({
73
+ id: 'turborepo.public-entrypoints',
74
+ title: 'Cross-package imports go through the public entry point',
75
+ type: KnowledgeType.Rule,
76
+ priority: KnowledgePriority.High,
77
+ tags: ['turborepo', 'imports'],
78
+ appliesWhen: ['generate-code'],
79
+ content: 'Import via the package name (matches package.json exports / main). Avoid reaching into src/.',
80
+ })`,
81
+ ],
82
+ rules: [COMMON_SAFETY_RULE, COMMON_RULE_INTERFACE_PREFIX, COMMON_RULE_ONE_EXPORT],
83
+ paths: [COMMON_PATH_SERVICES, COMMON_PATH_UTILS, COMMON_PATH_TESTS],
84
+ templates: [COMMON_TEMPLATE_SERVICE, COMMON_TEMPLATE_UTILITY, COMMON_TEMPLATE_TEST],
85
+ pipelines: [COMMON_PIPELINE_CONTEXT_ONLY, COMMON_PIPELINE_FEATURE_DEV, COMMON_PIPELINE_UNIT_TEST],
86
+ docs: {
87
+ 'overview.md': OVERVIEW_DOC('Turborepo monorepo', 'apps/ + packages/ layout. turbo.json drives the task graph. CI runs affected tasks only.'),
88
+ },
89
+ },
90
+ recommendedNextCommands: [
91
+ 'shrk doctor',
92
+ 'shrk ci scaffold github-actions --quickstart',
93
+ 'shrk task "<task>"',
94
+ ],
95
+ });
96
+ export const PACKAGE_WORKSPACE_PRESET = definePreset({
97
+ id: 'package-workspace',
98
+ title: 'Package workspace monorepo',
99
+ description: 'Generic npm / pnpm / yarn workspaces monorepo (no Nx, no Turborepo). Layer / boundary conventions + safe-codegen baseline.',
100
+ tags: ['workspaces', 'monorepo'],
101
+ appliesTo: [WorkspaceProfile.HasPackageWorkspaces, WorkspaceProfile.IsMonorepo],
102
+ notAppropriateFor: [WorkspaceProfile.HasNx, WorkspaceProfile.HasTurborepo],
103
+ weight: 6,
104
+ composes: ['strict-typescript'],
105
+ includes: {
106
+ knowledge: [
107
+ COMMON_AGENT_BRIEFING,
108
+ `defineKnowledgeEntry({
109
+ id: 'workspaces.public-entrypoints',
110
+ title: 'Cross-package imports use the package name',
111
+ type: KnowledgeType.Rule,
112
+ priority: KnowledgePriority.High,
113
+ tags: ['workspaces', 'imports'],
114
+ appliesWhen: ['generate-code'],
115
+ content: 'Import other workspace packages by name (package.json exports / main). No relative imports across package boundaries.',
116
+ })`,
117
+ ],
118
+ rules: [COMMON_SAFETY_RULE, COMMON_RULE_INTERFACE_PREFIX, COMMON_RULE_ONE_EXPORT],
119
+ paths: [COMMON_PATH_SERVICES, COMMON_PATH_UTILS, COMMON_PATH_TESTS],
120
+ templates: [COMMON_TEMPLATE_SERVICE, COMMON_TEMPLATE_UTILITY, COMMON_TEMPLATE_TEST],
121
+ pipelines: [COMMON_PIPELINE_CONTEXT_ONLY, COMMON_PIPELINE_FEATURE_DEV, COMMON_PIPELINE_UNIT_TEST],
122
+ docs: {
123
+ 'overview.md': OVERVIEW_DOC('Package workspace', 'Plain workspaces (npm / pnpm / yarn). Each package has a stable public entry. Cross-package boundary checks are enforced by `shrk check boundaries`.'),
124
+ },
125
+ },
126
+ recommendedNextCommands: [
127
+ 'shrk doctor',
128
+ 'shrk check boundaries',
129
+ 'shrk task "<task>"',
130
+ ],
131
+ });
132
+ export const CLEAN_ARCHITECTURE_TS_PRESET = definePreset({
133
+ id: 'clean-architecture-ts',
134
+ title: 'Clean Architecture (TypeScript)',
135
+ description: 'Layered TypeScript convention: domain (no deps), application (depends on domain), infrastructure (depends on application + domain), presentation (depends on application). No reverse imports.',
136
+ tags: ['typescript', 'architecture', 'clean-architecture'],
137
+ appliesTo: [WorkspaceProfile.HasTypeScript],
138
+ weight: 6,
139
+ composes: ['strict-typescript'],
140
+ includes: {
141
+ knowledge: [
142
+ COMMON_AGENT_BRIEFING,
143
+ `defineKnowledgeEntry({
144
+ id: 'clean-arch.layer-order',
145
+ title: 'Clean Architecture layers: domain → application → infrastructure / presentation',
146
+ type: KnowledgeType.Rule,
147
+ priority: KnowledgePriority.Critical,
148
+ tags: ['architecture'],
149
+ appliesWhen: ['generate-code', 'refactor'],
150
+ content: 'Lower layers know nothing about higher ones. Domain has no dependencies on other layers. Application depends only on domain. Infrastructure and presentation are at the edges.',
151
+ })`,
152
+ `defineKnowledgeEntry({
153
+ id: 'clean-arch.boundary-ports',
154
+ title: 'Cross-layer access uses ports / interfaces, not concrete types',
155
+ type: KnowledgeType.Rule,
156
+ priority: KnowledgePriority.High,
157
+ tags: ['architecture', 'imports'],
158
+ appliesWhen: ['generate-code'],
159
+ content: 'Application defines interfaces (ports). Infrastructure implements them (adapters). Presentation depends on application interfaces, never on infrastructure directly.',
160
+ })`,
161
+ ],
162
+ rules: [
163
+ COMMON_SAFETY_RULE,
164
+ COMMON_RULE_INTERFACE_PREFIX,
165
+ COMMON_RULE_ONE_EXPORT,
166
+ COMMON_RULE_NO_LOGIC_CONSTRUCTORS,
167
+ ],
168
+ paths: [COMMON_PATH_SERVICES, COMMON_PATH_UTILS, COMMON_PATH_TESTS],
169
+ templates: [COMMON_TEMPLATE_SERVICE, COMMON_TEMPLATE_UTILITY, COMMON_TEMPLATE_TEST],
170
+ pipelines: [COMMON_PIPELINE_CONTEXT_ONLY, COMMON_PIPELINE_FEATURE_DEV, COMMON_PIPELINE_UNIT_TEST],
171
+ docs: {
172
+ 'overview.md': OVERVIEW_DOC('Clean Architecture (TypeScript)', 'Layers: domain / application / infrastructure / presentation. Lower layers know nothing about higher ones. Wire boundaries through ports + adapters; enforce with `shrk check boundaries`.'),
173
+ },
174
+ },
175
+ recommendedNextCommands: [
176
+ 'shrk doctor',
177
+ 'shrk check boundaries',
178
+ 'shrk task "<task>"',
179
+ ],
180
+ });
181
+ export const R45_PRESETS = Object.freeze([
182
+ NEXT_APP_PRESET,
183
+ TURBOREPO_PRESET,
184
+ PACKAGE_WORKSPACE_PRESET,
185
+ CLEAN_ARCHITECTURE_TS_PRESET,
186
+ ]);
@@ -0,0 +1,5 @@
1
+ import type { IPreset } from '../model/preset.js';
2
+ export declare const NEST_SERVICE_PRESET: IPreset;
3
+ export declare const ANGULAR_APP_PRESET: IPreset;
4
+ export declare const R47_PRESETS: readonly IPreset[];
5
+ //# sourceMappingURL=r47-presets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"r47-presets.d.ts","sourceRoot":"","sources":["../../src/builtin/r47-presets.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAclD,eAAO,MAAM,mBAAmB,EAAE,OA2BhC,CAAC;AAEH,eAAO,MAAM,kBAAkB,EAAE,OA2B/B,CAAC;AAEH,eAAO,MAAM,WAAW,EAAE,SAAS,OAAO,EAGxC,CAAC"}
@@ -0,0 +1,65 @@
1
+ import { WorkspaceProfile } from '@shrkcrft/workspace';
2
+ import { definePreset } from "../define/define-preset.js";
3
+ import { COMMON_AGENT_BRIEFING, COMMON_SAFETY_RULE, OVERVIEW_DOC } from "./shared-snippets.js";
4
+ // Universal adoption (top 5) — canonical-id aliases.
5
+ //
6
+ // Why: canonical preset ids are `nest-service` and `angular-app`. The
7
+ // engine also ships `nestjs-service` and 12 angular variants
8
+ // (`modern-angular`, `angular-signals-first`, …). New users type the
9
+ // canonical id and expect it to work. These aliases compose the
10
+ // existing presets so users get the *same* asset set as before.
11
+ //
12
+ // Weight is bumped by 1 above the composed preset so the recommender
13
+ // picks the canonical alias when both match the same profiles.
14
+ export const NEST_SERVICE_PRESET = definePreset({
15
+ id: 'nest-service',
16
+ title: 'NestJS service',
17
+ description: 'Canonical alias for the NestJS service preset. Composes the `nestjs-service` baseline: typed controllers, providers under a module, DTO validation pipes, e2e tests under `test/`.',
18
+ tags: ['nest', 'nestjs', 'service', 'backend'],
19
+ appliesTo: [WorkspaceProfile.HasNestJS, WorkspaceProfile.IsBackend, WorkspaceProfile.IsService],
20
+ weight: 9,
21
+ composes: ['nestjs-service'],
22
+ includes: {
23
+ knowledge: [COMMON_AGENT_BRIEFING],
24
+ rules: [COMMON_SAFETY_RULE],
25
+ paths: [],
26
+ templates: [],
27
+ pipelines: [],
28
+ docs: {
29
+ 'overview.md': OVERVIEW_DOC('NestJS service', 'Controllers stay thin; business logic lives in providers under a module. DTOs validate at the boundary. e2e tests live under `test/`.'),
30
+ },
31
+ },
32
+ recommendedNextCommands: [
33
+ 'shrk doctor',
34
+ 'shrk ci scaffold github-actions --quickstart',
35
+ 'shrk task "<task>"',
36
+ ],
37
+ });
38
+ export const ANGULAR_APP_PRESET = definePreset({
39
+ id: 'angular-app',
40
+ title: 'Angular app',
41
+ description: 'Canonical alias for a modern Angular app. Composes `modern-angular` (signals-first, OnPush, standalone components, RxJS disciplined). Use the more specific `angular-*` variants only if you want a narrower subset.',
42
+ tags: ['angular', 'app', 'frontend'],
43
+ appliesTo: [WorkspaceProfile.HasAngular, WorkspaceProfile.IsFrontend],
44
+ weight: 10,
45
+ composes: ['modern-angular'],
46
+ includes: {
47
+ knowledge: [COMMON_AGENT_BRIEFING],
48
+ rules: [COMMON_SAFETY_RULE],
49
+ paths: [],
50
+ templates: [],
51
+ pipelines: [],
52
+ docs: {
53
+ 'overview.md': OVERVIEW_DOC('Angular app', 'Modern Angular baseline: signals-first reactivity, OnPush change detection, standalone components, RxJS used deliberately, accessible templates.'),
54
+ },
55
+ },
56
+ recommendedNextCommands: [
57
+ 'shrk doctor',
58
+ 'shrk ci scaffold github-actions --quickstart',
59
+ 'shrk task "<task>"',
60
+ ],
61
+ });
62
+ export const R47_PRESETS = Object.freeze([
63
+ NEST_SERVICE_PRESET,
64
+ ANGULAR_APP_PRESET,
65
+ ]);
@@ -0,0 +1,17 @@
1
+ export declare const COMMON_AGENT_BRIEFING = "defineKnowledgeEntry({\n id: 'agent.briefing',\n title: 'Agent briefing',\n type: KnowledgeType.Rule,\n priority: KnowledgePriority.Critical,\n tags: ['agent', 'safety'],\n appliesWhen: ['generate-code', 'refactor', 'fix-bug'],\n content: `This repo uses SharkCraft. Use shrk CLI or the MCP server to read\ncontext. Do not write files through MCP \u2014 use shrk apply on the CLI.`,\n actionHints: {\n commands: [\n { command: 'shrk doctor' },\n { command: 'shrk context --task \"<task>\"' },\n { command: 'shrk gen <template> <name> --dry-run --save-plan <plan.json>' },\n ],\n mcpTools: ['get_relevant_context', 'get_action_hints', 'create_generation_plan'],\n forbiddenActions: [\n 'Do not write files through MCP.',\n 'Do not skip dry-run when generating new files.',\n ],\n verificationCommands: ['shrk doctor', 'bun x tsc --noEmit'],\n writePolicy: 'cli-only',\n },\n })";
2
+ export declare const COMMON_SAFETY_RULE = "defineKnowledgeEntry({\n id: 'generation.dry-run-by-default',\n title: 'shrk gen is dry-run by default',\n type: KnowledgeType.Rule,\n priority: KnowledgePriority.Critical,\n tags: ['safety', 'generator'],\n appliesWhen: ['generate-code'],\n content: `Always run shrk gen <id> <name> --dry-run first. Apply with\n--write only after the plan is conflict-free. AI agents must call\ncreate_generation_plan through MCP \u2014 they cannot write through MCP.`,\n actionHints: {\n commands: [\n { command: 'shrk gen <id> <name> --dry-run --save-plan <plan.json>' },\n { command: 'shrk apply <plan.json> --verify-signature' },\n ],\n mcpTools: ['create_generation_plan', 'explain_generation_target'],\n forbiddenActions: ['Do not bypass dry-run.', 'Do not write through MCP.'],\n verificationCommands: ['shrk doctor'],\n writePolicy: 'cli-only',\n },\n })";
3
+ export declare const COMMON_PIPELINE_FEATURE_DEV = "definePipeline({\n id: 'feature-dev',\n title: 'Feature development flow',\n description: 'Gather context \u2192 plan \u2192 dry-run gen \u2192 human review \u2192 apply.',\n tags: ['feature', 'generation'],\n inputs: [\n { name: 'task', required: true, description: 'Plain-English task description.' },\n ],\n steps: [\n {\n id: 'context',\n type: 'context',\n description: 'Pull relevant rules, paths, templates for the task.',\n cliCommands: ['shrk context --task \"<task>\" --max-tokens 3500'],\n mcpTools: ['get_relevant_context'],\n },\n {\n id: 'rules',\n type: 'mcp-tool',\n description: 'Confirm the rules that apply.',\n mcpTools: ['get_relevant_rules'],\n },\n {\n id: 'plan',\n type: 'generation-plan',\n description: 'Build a dry-run plan and save it for review.',\n cliCommands: [\n 'shrk gen <template> <name> --dry-run --save-plan <plan.json>',\n ],\n humanReview: true,\n },\n {\n id: 'apply',\n type: 'apply-plan',\n description: 'Human applies the reviewed plan via CLI.',\n cliCommands: ['shrk apply <plan.json> --verify-signature'],\n humanReview: true,\n },\n {\n id: 'verify',\n type: 'command',\n description: 'Run verification commands.',\n cliCommands: ['shrk doctor', 'bun x tsc --noEmit'],\n },\n ],\n })";
4
+ export declare const COMMON_PIPELINE_CONTEXT_ONLY = "definePipeline({\n id: 'context-only',\n title: 'Context-only flow',\n description: 'Retrieve the relevant slice without generating anything.',\n tags: ['safe', 'context'],\n inputs: [\n { name: 'task', required: true, description: 'Plain-English task description.' },\n ],\n steps: [\n {\n id: 'overview',\n type: 'mcp-tool',\n description: 'Project overview + readiness.',\n mcpTools: ['get_project_overview', 'get_ai_readiness_report'],\n },\n {\n id: 'context',\n type: 'context',\n description: 'Token-budgeted context for the task.',\n cliCommands: ['shrk context --task \"<task>\"'],\n mcpTools: ['get_relevant_context'],\n },\n {\n id: 'hints',\n type: 'mcp-tool',\n description: 'Per-task action hints.',\n mcpTools: ['get_action_hints'],\n },\n ],\n })";
5
+ export declare const COMMON_PIPELINE_UNIT_TEST = "definePipeline({\n id: 'unit-test',\n title: 'Unit test verification',\n description: 'Run the project test suite as a verification step.',\n tags: ['test'],\n steps: [\n {\n id: 'test',\n type: 'command',\n description: 'Run unit tests.',\n cliCommands: ['bun test'],\n },\n ],\n })";
6
+ export declare const COMMON_TEMPLATE_SERVICE = "defineTemplate({\n id: 'typescript.service',\n name: 'typescript-service',\n description: 'A minimal TypeScript service class with an init() method.',\n tags: ['typescript', 'service'],\n scope: ['app'],\n appliesWhen: ['generate-service'],\n variables: [\n {\n name: 'className',\n required: true,\n description: 'PascalCase class name (e.g. ProfileService).',\n pattern: /^[A-Z][A-Za-z0-9]+$/,\n },\n ],\n targetPath: (v) => `src/services/${kebab(v.className)}.service.ts`,\n content: (v) => `export class ${v.className} {\\n init(): void {}\\n}\\n`,\n postGenerationNotes: ['Wire the new service into your composition root when ready.'],\n })";
7
+ export declare const COMMON_TEMPLATE_UTILITY = "defineTemplate({\n id: 'typescript.utility',\n name: 'typescript-utility',\n description: 'A pure utility module (one exported function per file).',\n tags: ['typescript', 'utility'],\n scope: ['app'],\n appliesWhen: ['generate-utility'],\n variables: [\n {\n name: 'functionName',\n required: true,\n description: 'camelCase function name (e.g. formatDate).',\n pattern: /^[a-z][A-Za-z0-9]+$/,\n },\n ],\n targetPath: (v) => `src/utils/${v.functionName}.ts`,\n content: (v) => `export function ${v.functionName}(): void {\\n // TODO\\n}\\n`,\n })";
8
+ export declare const COMMON_TEMPLATE_TEST = "defineTemplate({\n id: 'typescript.unit-test',\n name: 'typescript-unit-test',\n description: 'A bun:test unit-test skeleton for an existing module.',\n tags: ['typescript', 'testing'],\n scope: ['app'],\n appliesWhen: ['generate-test'],\n variables: [\n {\n name: 'subject',\n required: true,\n description: 'Subject name (used in describe + filename).',\n pattern: /^[A-Za-z][A-Za-z0-9]+$/,\n },\n ],\n targetPath: (v) => `tests/${v.subject}.spec.ts`,\n content: (v) =>\n `import { describe, expect, test } from 'bun:test';\\n\\ndescribe('${v.subject}', () => {\\n test('placeholder', () => {\\n expect(true).toBe(true);\\n });\\n});\\n`,\n })";
9
+ export declare const TEMPLATE_HELPERS = "function kebab(s: string): string {\n return s\n .replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n .replace(/[^A-Za-z0-9]+/g, '-')\n .toLowerCase();\n}";
10
+ export declare const COMMON_PATH_SERVICES = "defineKnowledgeEntry({\n id: 'paths.services',\n title: 'Services live in src/services/',\n type: KnowledgeType.Path,\n priority: KnowledgePriority.High,\n tags: ['paths', 'services'],\n scope: ['app'],\n appliesWhen: ['generate-service'],\n content: 'Service classes live in src/services/. One service per file.',\n })";
11
+ export declare const COMMON_PATH_UTILS = "defineKnowledgeEntry({\n id: 'paths.utils',\n title: 'Utilities live in src/utils/',\n type: KnowledgeType.Path,\n priority: KnowledgePriority.Medium,\n tags: ['paths', 'utils'],\n appliesWhen: ['generate-utility'],\n content: 'Pure helpers live in src/utils/. One function per file.',\n })";
12
+ export declare const COMMON_PATH_TESTS = "defineKnowledgeEntry({\n id: 'paths.tests',\n title: 'Tests live in tests/',\n type: KnowledgeType.Path,\n priority: KnowledgePriority.Medium,\n tags: ['paths', 'tests'],\n appliesWhen: ['generate-test'],\n content: 'Unit tests live under tests/, mirroring src/. Use *.spec.ts.',\n })";
13
+ export declare const COMMON_RULE_INTERFACE_PREFIX = "defineKnowledgeEntry({\n id: 'typescript.interfaces.i-prefix',\n title: 'Prefix interfaces with I',\n type: KnowledgeType.Rule,\n priority: KnowledgePriority.High,\n tags: ['typescript', 'naming'],\n appliesWhen: ['generate-code', 'refactor'],\n content: 'Interfaces use an I-prefix (IUser, IConfig). Enums are preferred over unions for closed sets.',\n })";
14
+ export declare const COMMON_RULE_ONE_EXPORT = "defineKnowledgeEntry({\n id: 'typescript.files.one-export',\n title: 'One exported construct per file',\n type: KnowledgeType.Rule,\n priority: KnowledgePriority.High,\n tags: ['typescript', 'structure'],\n appliesWhen: ['generate-code'],\n content: 'Each TypeScript file exports exactly one top-level construct. Helpers live in their own file.',\n })";
15
+ export declare const COMMON_RULE_NO_LOGIC_CONSTRUCTORS = "defineKnowledgeEntry({\n id: 'typescript.constructors.no-logic',\n title: 'No business logic in constructors',\n type: KnowledgeType.Rule,\n priority: KnowledgePriority.High,\n tags: ['typescript'],\n appliesWhen: ['generate-code'],\n content: 'Constructors wire dependencies only. Initialization belongs in init().',\n })";
16
+ export declare const OVERVIEW_DOC: (title: string, body: string) => string;
17
+ //# sourceMappingURL=shared-snippets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-snippets.d.ts","sourceRoot":"","sources":["../../src/builtin/shared-snippets.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,qBAAqB,m9BAuB7B,CAAC;AAEN,eAAO,MAAM,kBAAkB,45BAoB1B,CAAC;AAEN,eAAO,MAAM,2BAA2B,s9CA6CnC,CAAC;AAEN,eAAO,MAAM,4BAA4B,s5BA6BpC,CAAC;AAEN,eAAO,MAAM,yBAAyB,yVAajC,CAAC;AAEN,eAAO,MAAM,uBAAuB,mtBAkB/B,CAAC;AAEN,eAAO,MAAM,uBAAuB,8mBAiB/B,CAAC;AAEN,eAAO,MAAM,oBAAoB,ytBAkB5B,CAAC;AAKN,eAAO,MAAM,gBAAgB,8JAK3B,CAAC;AAEH,eAAO,MAAM,oBAAoB,6VAS5B,CAAC;AAEN,eAAO,MAAM,iBAAiB,6TAQzB,CAAC;AAEN,eAAO,MAAM,iBAAiB,uTAQzB,CAAC;AAEN,eAAO,MAAM,4BAA4B,+XAQpC,CAAC;AAEN,eAAO,MAAM,sBAAsB,0XAQ9B,CAAC;AAEN,eAAO,MAAM,iCAAiC,6VAQzC,CAAC;AAEN,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,EAAE,MAAM,MAAM,KAAG,MAK1D,CAAC"}