@shrkcrft/presets 0.1.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +15 -0
- package/dist/apply/preview-apply.d.ts +52 -0
- package/dist/apply/preview-apply.d.ts.map +1 -0
- package/dist/apply/preview-apply.js +85 -0
- package/dist/builtin/builtin-presets.d.ts +3 -0
- package/dist/builtin/builtin-presets.d.ts.map +1 -0
- package/dist/builtin/builtin-presets.js +520 -0
- package/dist/builtin/r26-presets.d.ts +31 -0
- package/dist/builtin/r26-presets.d.ts.map +1 -0
- package/dist/builtin/r26-presets.js +458 -0
- package/dist/builtin/r26-snippets.d.ts +39 -0
- package/dist/builtin/r26-snippets.d.ts.map +1 -0
- package/dist/builtin/r26-snippets.js +257 -0
- package/dist/builtin/r45-presets.d.ts +7 -0
- package/dist/builtin/r45-presets.d.ts.map +1 -0
- package/dist/builtin/r45-presets.js +186 -0
- package/dist/builtin/r47-presets.d.ts +5 -0
- package/dist/builtin/r47-presets.d.ts.map +1 -0
- package/dist/builtin/r47-presets.js +65 -0
- package/dist/builtin/shared-snippets.d.ts +17 -0
- package/dist/builtin/shared-snippets.d.ts.map +1 -0
- package/dist/builtin/shared-snippets.js +264 -0
- package/dist/define/define-preset.d.ts +4 -0
- package/dist/define/define-preset.d.ts.map +1 -0
- package/dist/define/define-preset.js +4 -0
- package/dist/emit/synthesize-files.d.ts +26 -0
- package/dist/emit/synthesize-files.d.ts.map +1 -0
- package/dist/emit/synthesize-files.js +172 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/model/preset.d.ts +83 -0
- package/dist/model/preset.d.ts.map +1 -0
- package/dist/model/preset.js +21 -0
- package/dist/registry/load-presets.d.ts +12 -0
- package/dist/registry/load-presets.d.ts.map +1 -0
- package/dist/registry/load-presets.js +33 -0
- package/dist/registry/preset-registry.d.ts +11 -0
- package/dist/registry/preset-registry.d.ts.map +1 -0
- package/dist/registry/preset-registry.js +22 -0
- package/dist/registry/recommend.d.ts +30 -0
- package/dist/registry/recommend.d.ts.map +1 -0
- package/dist/registry/recommend.js +59 -0
- package/dist/registry/resolve-preset.d.ts +75 -0
- package/dist/registry/resolve-preset.d.ts.map +1 -0
- package/dist/registry/resolve-preset.js +207 -0
- package/dist/registry/resolve-references.d.ts +49 -0
- package/dist/registry/resolve-references.d.ts.map +1 -0
- package/dist/registry/resolve-references.js +38 -0
- package/package.json +52 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// Reusable knowledge / rule / template / pipeline snippets shared across the
|
|
2
|
+
// built-in presets. Stored as raw TS source strings — they are injected into
|
|
3
|
+
// the synthesized sharkcraft/*.ts files verbatim.
|
|
4
|
+
export const COMMON_AGENT_BRIEFING = `defineKnowledgeEntry({
|
|
5
|
+
id: 'agent.briefing',
|
|
6
|
+
title: 'Agent briefing',
|
|
7
|
+
type: KnowledgeType.Rule,
|
|
8
|
+
priority: KnowledgePriority.Critical,
|
|
9
|
+
tags: ['agent', 'safety'],
|
|
10
|
+
appliesWhen: ['generate-code', 'refactor', 'fix-bug'],
|
|
11
|
+
content: \`This repo uses SharkCraft. Use shrk CLI or the MCP server to read
|
|
12
|
+
context. Do not write files through MCP — use shrk apply on the CLI.\`,
|
|
13
|
+
actionHints: {
|
|
14
|
+
commands: [
|
|
15
|
+
{ command: 'shrk doctor' },
|
|
16
|
+
{ command: 'shrk context --task "<task>"' },
|
|
17
|
+
{ command: 'shrk gen <template> <name> --dry-run --save-plan <plan.json>' },
|
|
18
|
+
],
|
|
19
|
+
mcpTools: ['get_relevant_context', 'get_action_hints', 'create_generation_plan'],
|
|
20
|
+
forbiddenActions: [
|
|
21
|
+
'Do not write files through MCP.',
|
|
22
|
+
'Do not skip dry-run when generating new files.',
|
|
23
|
+
],
|
|
24
|
+
verificationCommands: ['shrk doctor', 'bun x tsc --noEmit'],
|
|
25
|
+
writePolicy: 'cli-only',
|
|
26
|
+
},
|
|
27
|
+
})`;
|
|
28
|
+
export const COMMON_SAFETY_RULE = `defineKnowledgeEntry({
|
|
29
|
+
id: 'generation.dry-run-by-default',
|
|
30
|
+
title: 'shrk gen is dry-run by default',
|
|
31
|
+
type: KnowledgeType.Rule,
|
|
32
|
+
priority: KnowledgePriority.Critical,
|
|
33
|
+
tags: ['safety', 'generator'],
|
|
34
|
+
appliesWhen: ['generate-code'],
|
|
35
|
+
content: \`Always run shrk gen <id> <name> --dry-run first. Apply with
|
|
36
|
+
--write only after the plan is conflict-free. AI agents must call
|
|
37
|
+
create_generation_plan through MCP — they cannot write through MCP.\`,
|
|
38
|
+
actionHints: {
|
|
39
|
+
commands: [
|
|
40
|
+
{ command: 'shrk gen <id> <name> --dry-run --save-plan <plan.json>' },
|
|
41
|
+
{ command: 'shrk apply <plan.json> --verify-signature' },
|
|
42
|
+
],
|
|
43
|
+
mcpTools: ['create_generation_plan', 'explain_generation_target'],
|
|
44
|
+
forbiddenActions: ['Do not bypass dry-run.', 'Do not write through MCP.'],
|
|
45
|
+
verificationCommands: ['shrk doctor'],
|
|
46
|
+
writePolicy: 'cli-only',
|
|
47
|
+
},
|
|
48
|
+
})`;
|
|
49
|
+
export const COMMON_PIPELINE_FEATURE_DEV = `definePipeline({
|
|
50
|
+
id: 'feature-dev',
|
|
51
|
+
title: 'Feature development flow',
|
|
52
|
+
description: 'Gather context → plan → dry-run gen → human review → apply.',
|
|
53
|
+
tags: ['feature', 'generation'],
|
|
54
|
+
inputs: [
|
|
55
|
+
{ name: 'task', required: true, description: 'Plain-English task description.' },
|
|
56
|
+
],
|
|
57
|
+
steps: [
|
|
58
|
+
{
|
|
59
|
+
id: 'context',
|
|
60
|
+
type: 'context',
|
|
61
|
+
description: 'Pull relevant rules, paths, templates for the task.',
|
|
62
|
+
cliCommands: ['shrk context --task "<task>" --max-tokens 3500'],
|
|
63
|
+
mcpTools: ['get_relevant_context'],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'rules',
|
|
67
|
+
type: 'mcp-tool',
|
|
68
|
+
description: 'Confirm the rules that apply.',
|
|
69
|
+
mcpTools: ['get_relevant_rules'],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'plan',
|
|
73
|
+
type: 'generation-plan',
|
|
74
|
+
description: 'Build a dry-run plan and save it for review.',
|
|
75
|
+
cliCommands: [
|
|
76
|
+
'shrk gen <template> <name> --dry-run --save-plan <plan.json>',
|
|
77
|
+
],
|
|
78
|
+
humanReview: true,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'apply',
|
|
82
|
+
type: 'apply-plan',
|
|
83
|
+
description: 'Human applies the reviewed plan via CLI.',
|
|
84
|
+
cliCommands: ['shrk apply <plan.json> --verify-signature'],
|
|
85
|
+
humanReview: true,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'verify',
|
|
89
|
+
type: 'command',
|
|
90
|
+
description: 'Run verification commands.',
|
|
91
|
+
cliCommands: ['shrk doctor', 'bun x tsc --noEmit'],
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
})`;
|
|
95
|
+
export const COMMON_PIPELINE_CONTEXT_ONLY = `definePipeline({
|
|
96
|
+
id: 'context-only',
|
|
97
|
+
title: 'Context-only flow',
|
|
98
|
+
description: 'Retrieve the relevant slice without generating anything.',
|
|
99
|
+
tags: ['safe', 'context'],
|
|
100
|
+
inputs: [
|
|
101
|
+
{ name: 'task', required: true, description: 'Plain-English task description.' },
|
|
102
|
+
],
|
|
103
|
+
steps: [
|
|
104
|
+
{
|
|
105
|
+
id: 'overview',
|
|
106
|
+
type: 'mcp-tool',
|
|
107
|
+
description: 'Project overview + readiness.',
|
|
108
|
+
mcpTools: ['get_project_overview', 'get_ai_readiness_report'],
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: 'context',
|
|
112
|
+
type: 'context',
|
|
113
|
+
description: 'Token-budgeted context for the task.',
|
|
114
|
+
cliCommands: ['shrk context --task "<task>"'],
|
|
115
|
+
mcpTools: ['get_relevant_context'],
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: 'hints',
|
|
119
|
+
type: 'mcp-tool',
|
|
120
|
+
description: 'Per-task action hints.',
|
|
121
|
+
mcpTools: ['get_action_hints'],
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
})`;
|
|
125
|
+
export const COMMON_PIPELINE_UNIT_TEST = `definePipeline({
|
|
126
|
+
id: 'unit-test',
|
|
127
|
+
title: 'Unit test verification',
|
|
128
|
+
description: 'Run the project test suite as a verification step.',
|
|
129
|
+
tags: ['test'],
|
|
130
|
+
steps: [
|
|
131
|
+
{
|
|
132
|
+
id: 'test',
|
|
133
|
+
type: 'command',
|
|
134
|
+
description: 'Run unit tests.',
|
|
135
|
+
cliCommands: ['bun test'],
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
})`;
|
|
139
|
+
export const COMMON_TEMPLATE_SERVICE = `defineTemplate({
|
|
140
|
+
id: 'typescript.service',
|
|
141
|
+
name: 'typescript-service',
|
|
142
|
+
description: 'A minimal TypeScript service class with an init() method.',
|
|
143
|
+
tags: ['typescript', 'service'],
|
|
144
|
+
scope: ['app'],
|
|
145
|
+
appliesWhen: ['generate-service'],
|
|
146
|
+
variables: [
|
|
147
|
+
{
|
|
148
|
+
name: 'className',
|
|
149
|
+
required: true,
|
|
150
|
+
description: 'PascalCase class name (e.g. ProfileService).',
|
|
151
|
+
pattern: /^[A-Z][A-Za-z0-9]+$/,
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
targetPath: (v) => \`src/services/\${kebab(v.className)}.service.ts\`,
|
|
155
|
+
content: (v) => \`export class \${v.className} {\\n init(): void {}\\n}\\n\`,
|
|
156
|
+
postGenerationNotes: ['Wire the new service into your composition root when ready.'],
|
|
157
|
+
})`;
|
|
158
|
+
export const COMMON_TEMPLATE_UTILITY = `defineTemplate({
|
|
159
|
+
id: 'typescript.utility',
|
|
160
|
+
name: 'typescript-utility',
|
|
161
|
+
description: 'A pure utility module (one exported function per file).',
|
|
162
|
+
tags: ['typescript', 'utility'],
|
|
163
|
+
scope: ['app'],
|
|
164
|
+
appliesWhen: ['generate-utility'],
|
|
165
|
+
variables: [
|
|
166
|
+
{
|
|
167
|
+
name: 'functionName',
|
|
168
|
+
required: true,
|
|
169
|
+
description: 'camelCase function name (e.g. formatDate).',
|
|
170
|
+
pattern: /^[a-z][A-Za-z0-9]+$/,
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
targetPath: (v) => \`src/utils/\${v.functionName}.ts\`,
|
|
174
|
+
content: (v) => \`export function \${v.functionName}(): void {\\n // TODO\\n}\\n\`,
|
|
175
|
+
})`;
|
|
176
|
+
export const COMMON_TEMPLATE_TEST = `defineTemplate({
|
|
177
|
+
id: 'typescript.unit-test',
|
|
178
|
+
name: 'typescript-unit-test',
|
|
179
|
+
description: 'A bun:test unit-test skeleton for an existing module.',
|
|
180
|
+
tags: ['typescript', 'testing'],
|
|
181
|
+
scope: ['app'],
|
|
182
|
+
appliesWhen: ['generate-test'],
|
|
183
|
+
variables: [
|
|
184
|
+
{
|
|
185
|
+
name: 'subject',
|
|
186
|
+
required: true,
|
|
187
|
+
description: 'Subject name (used in describe + filename).',
|
|
188
|
+
pattern: /^[A-Za-z][A-Za-z0-9]+$/,
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
targetPath: (v) => \`tests/\${v.subject}.spec.ts\`,
|
|
192
|
+
content: (v) =>
|
|
193
|
+
\`import { describe, expect, test } from 'bun:test';\\n\\ndescribe('\${v.subject}', () => {\\n test('placeholder', () => {\\n expect(true).toBe(true);\\n });\\n});\\n\`,
|
|
194
|
+
})`;
|
|
195
|
+
// Helper available to template content. Defined at the top of templates.ts so
|
|
196
|
+
// each template can reference it. Adding it inline keeps generated files
|
|
197
|
+
// self-contained.
|
|
198
|
+
export const TEMPLATE_HELPERS = `function kebab(s: string): string {
|
|
199
|
+
return s
|
|
200
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
201
|
+
.replace(/[^A-Za-z0-9]+/g, '-')
|
|
202
|
+
.toLowerCase();
|
|
203
|
+
}`;
|
|
204
|
+
export const COMMON_PATH_SERVICES = `defineKnowledgeEntry({
|
|
205
|
+
id: 'paths.services',
|
|
206
|
+
title: 'Services live in src/services/',
|
|
207
|
+
type: KnowledgeType.Path,
|
|
208
|
+
priority: KnowledgePriority.High,
|
|
209
|
+
tags: ['paths', 'services'],
|
|
210
|
+
scope: ['app'],
|
|
211
|
+
appliesWhen: ['generate-service'],
|
|
212
|
+
content: 'Service classes live in src/services/. One service per file.',
|
|
213
|
+
})`;
|
|
214
|
+
export const COMMON_PATH_UTILS = `defineKnowledgeEntry({
|
|
215
|
+
id: 'paths.utils',
|
|
216
|
+
title: 'Utilities live in src/utils/',
|
|
217
|
+
type: KnowledgeType.Path,
|
|
218
|
+
priority: KnowledgePriority.Medium,
|
|
219
|
+
tags: ['paths', 'utils'],
|
|
220
|
+
appliesWhen: ['generate-utility'],
|
|
221
|
+
content: 'Pure helpers live in src/utils/. One function per file.',
|
|
222
|
+
})`;
|
|
223
|
+
export const COMMON_PATH_TESTS = `defineKnowledgeEntry({
|
|
224
|
+
id: 'paths.tests',
|
|
225
|
+
title: 'Tests live in tests/',
|
|
226
|
+
type: KnowledgeType.Path,
|
|
227
|
+
priority: KnowledgePriority.Medium,
|
|
228
|
+
tags: ['paths', 'tests'],
|
|
229
|
+
appliesWhen: ['generate-test'],
|
|
230
|
+
content: 'Unit tests live under tests/, mirroring src/. Use *.spec.ts.',
|
|
231
|
+
})`;
|
|
232
|
+
export const COMMON_RULE_INTERFACE_PREFIX = `defineKnowledgeEntry({
|
|
233
|
+
id: 'typescript.interfaces.i-prefix',
|
|
234
|
+
title: 'Prefix interfaces with I',
|
|
235
|
+
type: KnowledgeType.Rule,
|
|
236
|
+
priority: KnowledgePriority.High,
|
|
237
|
+
tags: ['typescript', 'naming'],
|
|
238
|
+
appliesWhen: ['generate-code', 'refactor'],
|
|
239
|
+
content: 'Interfaces use an I-prefix (IUser, IConfig). Enums are preferred over unions for closed sets.',
|
|
240
|
+
})`;
|
|
241
|
+
export const COMMON_RULE_ONE_EXPORT = `defineKnowledgeEntry({
|
|
242
|
+
id: 'typescript.files.one-export',
|
|
243
|
+
title: 'One exported construct per file',
|
|
244
|
+
type: KnowledgeType.Rule,
|
|
245
|
+
priority: KnowledgePriority.High,
|
|
246
|
+
tags: ['typescript', 'structure'],
|
|
247
|
+
appliesWhen: ['generate-code'],
|
|
248
|
+
content: 'Each TypeScript file exports exactly one top-level construct. Helpers live in their own file.',
|
|
249
|
+
})`;
|
|
250
|
+
export const COMMON_RULE_NO_LOGIC_CONSTRUCTORS = `defineKnowledgeEntry({
|
|
251
|
+
id: 'typescript.constructors.no-logic',
|
|
252
|
+
title: 'No business logic in constructors',
|
|
253
|
+
type: KnowledgeType.Rule,
|
|
254
|
+
priority: KnowledgePriority.High,
|
|
255
|
+
tags: ['typescript'],
|
|
256
|
+
appliesWhen: ['generate-code'],
|
|
257
|
+
content: 'Constructors wire dependencies only. Initialization belongs in init().',
|
|
258
|
+
})`;
|
|
259
|
+
export const OVERVIEW_DOC = (title, body) => `# ${title}
|
|
260
|
+
|
|
261
|
+
${body}
|
|
262
|
+
|
|
263
|
+
> Generated by \`shrk presets apply\`. SharkCraft (CLI + MCP) is the source of truth — this file is a human-readable companion.
|
|
264
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-preset.d.ts","sourceRoot":"","sources":["../../src/define/define-preset.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,iEAAiE;AACjE,wBAAgB,YAAY,CAAC,CAAC,SAAS,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAE5D"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { IPreset, IPresetIncludes } from '../model/preset.js';
|
|
2
|
+
export interface ISynthesizedFile {
|
|
3
|
+
/** Path relative to sharkcraft/. */
|
|
4
|
+
path: string;
|
|
5
|
+
content: string;
|
|
6
|
+
/** Why this file was produced. */
|
|
7
|
+
kind: 'config' | 'knowledge' | 'rules' | 'paths' | 'templates' | 'pipelines' | 'docs' | 'tasks' | 'import-draft' | 'literal';
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Pure synthesizer: return the list of files this preset would create under
|
|
11
|
+
* the target's sharkcraft/ folder. Does not touch disk.
|
|
12
|
+
*/
|
|
13
|
+
export declare function synthesizePresetFiles(preset: IPreset): ISynthesizedFile[];
|
|
14
|
+
/**
|
|
15
|
+
* Synthesize files from a flattened {@link IResolvedPreset}. Uses the merged
|
|
16
|
+
* `includes` from composition rather than the root preset's local includes.
|
|
17
|
+
*/
|
|
18
|
+
export declare function synthesizeResolvedPresetFiles(resolved: {
|
|
19
|
+
preset: IPreset;
|
|
20
|
+
includes: IPresetIncludes;
|
|
21
|
+
filesToCreate: readonly {
|
|
22
|
+
path: string;
|
|
23
|
+
content: string;
|
|
24
|
+
}[];
|
|
25
|
+
}): ISynthesizedFile[];
|
|
26
|
+
//# sourceMappingURL=synthesize-files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synthesize-files.d.ts","sourceRoot":"","sources":["../../src/emit/synthesize-files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAEnE,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,IAAI,EACA,QAAQ,GACR,WAAW,GACX,OAAO,GACP,OAAO,GACP,WAAW,GACX,WAAW,GACX,MAAM,GACN,OAAO,GACP,cAAc,GACd,SAAS,CAAC;CACf;AAsID;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,gBAAgB,EAAE,CAEzE;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE;IACtD,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,eAAe,CAAC;IAC1B,aAAa,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC7D,GAAG,gBAAgB,EAAE,CAErB"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
function recordToMap(v) {
|
|
2
|
+
if (!v)
|
|
3
|
+
return new Map();
|
|
4
|
+
if (v instanceof Map)
|
|
5
|
+
return v;
|
|
6
|
+
return new Map(Object.entries(v));
|
|
7
|
+
}
|
|
8
|
+
function listBlock(items, indent = ' ') {
|
|
9
|
+
if (!items || items.length === 0)
|
|
10
|
+
return '';
|
|
11
|
+
return items.map((expr) => indent + expr.replace(/\n/g, `\n${indent}`)).join(',\n') + ',\n';
|
|
12
|
+
}
|
|
13
|
+
function knowledgeFile(items) {
|
|
14
|
+
return ("import { defineKnowledgeEntry, KnowledgePriority, KnowledgeType } from '@shrkcrft/knowledge';\n\n" +
|
|
15
|
+
'const knowledge = [\n' +
|
|
16
|
+
listBlock(items) +
|
|
17
|
+
'];\n\n' +
|
|
18
|
+
'export default knowledge;\n');
|
|
19
|
+
}
|
|
20
|
+
function rulesFile(items) {
|
|
21
|
+
return ("import { defineKnowledgeEntry, KnowledgePriority, KnowledgeType } from '@shrkcrft/knowledge';\n\n" +
|
|
22
|
+
'const rules = [\n' +
|
|
23
|
+
listBlock(items) +
|
|
24
|
+
'];\n\n' +
|
|
25
|
+
'export default rules;\n');
|
|
26
|
+
}
|
|
27
|
+
function pathsFile(items) {
|
|
28
|
+
return ("import { defineKnowledgeEntry, KnowledgePriority, KnowledgeType } from '@shrkcrft/knowledge';\n\n" +
|
|
29
|
+
'const paths = [\n' +
|
|
30
|
+
listBlock(items) +
|
|
31
|
+
'];\n\n' +
|
|
32
|
+
'export default paths;\n');
|
|
33
|
+
}
|
|
34
|
+
function templatesFile(items) {
|
|
35
|
+
// Inject a small kebab helper so template content() closures can use it.
|
|
36
|
+
// Generated code is self-contained — no extra runtime dependency required.
|
|
37
|
+
return ("import { defineTemplate } from '@shrkcrft/templates';\n\n" +
|
|
38
|
+
'function kebab(s: string): string {\n' +
|
|
39
|
+
" return s.replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/[^A-Za-z0-9]+/g, '-').toLowerCase();\n" +
|
|
40
|
+
'}\n\n' +
|
|
41
|
+
'const templates = [\n' +
|
|
42
|
+
listBlock(items) +
|
|
43
|
+
'];\n\n' +
|
|
44
|
+
'export default templates;\n');
|
|
45
|
+
}
|
|
46
|
+
function pipelinesFile(items) {
|
|
47
|
+
return ("import { definePipeline } from '@shrkcrft/pipelines';\n\n" +
|
|
48
|
+
'const pipelines = [\n' +
|
|
49
|
+
listBlock(items) +
|
|
50
|
+
'];\n\n' +
|
|
51
|
+
'export default pipelines;\n');
|
|
52
|
+
}
|
|
53
|
+
function configFile(preset) {
|
|
54
|
+
// Generic config that points at the standard knowledge/rules/paths/etc.
|
|
55
|
+
// files. Consumers can tailor it later.
|
|
56
|
+
const filesByKind = inferFiles(preset);
|
|
57
|
+
const knowledgeFiles = filesByKind.has('knowledge.ts') ? ['knowledge.ts'] : [];
|
|
58
|
+
const ruleFiles = filesByKind.has('rules.ts') ? ['rules.ts'] : [];
|
|
59
|
+
const pathFiles = filesByKind.has('paths.ts') ? ['paths.ts'] : [];
|
|
60
|
+
const templateFiles = filesByKind.has('templates.ts') ? ['templates.ts'] : [];
|
|
61
|
+
const pipelineFiles = filesByKind.has('pipelines.ts') ? ['pipelines.ts'] : [];
|
|
62
|
+
const docsList = [...filesByKind.keys()].filter((p) => p.startsWith('docs/'));
|
|
63
|
+
// When the preset declares a `surfaceProfile`, emit a `surface.profile`
|
|
64
|
+
// block so the project starts with the right visible slice without
|
|
65
|
+
// needing a separate `shrk surface` step.
|
|
66
|
+
const surfaceBlock = preset.surfaceProfile
|
|
67
|
+
? ` surface: { profile: ${JSON.stringify(preset.surfaceProfile)} },\n`
|
|
68
|
+
: '';
|
|
69
|
+
return (`// Generated by \`shrk presets apply ${preset.id}\`.\n` +
|
|
70
|
+
'// Edit freely. SharkCraft does not regenerate this file.\n\n' +
|
|
71
|
+
'const config = {\n' +
|
|
72
|
+
` projectName: 'shrk-project',\n` +
|
|
73
|
+
` description: ${JSON.stringify(preset.description)},\n` +
|
|
74
|
+
` knowledgeFiles: ${JSON.stringify(knowledgeFiles)},\n` +
|
|
75
|
+
` ruleFiles: ${JSON.stringify(ruleFiles)},\n` +
|
|
76
|
+
` pathFiles: ${JSON.stringify(pathFiles)},\n` +
|
|
77
|
+
` templateFiles: ${JSON.stringify(templateFiles)},\n` +
|
|
78
|
+
` pipelineFiles: ${JSON.stringify(pipelineFiles)},\n` +
|
|
79
|
+
` docsFiles: ${JSON.stringify(docsList)},\n` +
|
|
80
|
+
` defaultMaxTokens: 3500,\n` +
|
|
81
|
+
surfaceBlock +
|
|
82
|
+
'};\n\n' +
|
|
83
|
+
'export default config;\n');
|
|
84
|
+
}
|
|
85
|
+
function inferFiles(preset) {
|
|
86
|
+
const files = new Map();
|
|
87
|
+
const inc = preset.includes;
|
|
88
|
+
if (inc.knowledge && inc.knowledge.length > 0) {
|
|
89
|
+
files.set('knowledge.ts', knowledgeFile(inc.knowledge));
|
|
90
|
+
}
|
|
91
|
+
if (inc.rules && inc.rules.length > 0) {
|
|
92
|
+
files.set('rules.ts', rulesFile(inc.rules));
|
|
93
|
+
}
|
|
94
|
+
if (inc.paths && inc.paths.length > 0) {
|
|
95
|
+
files.set('paths.ts', pathsFile(inc.paths));
|
|
96
|
+
}
|
|
97
|
+
if (inc.templates && inc.templates.length > 0) {
|
|
98
|
+
files.set('templates.ts', templatesFile(inc.templates));
|
|
99
|
+
}
|
|
100
|
+
if (inc.pipelines && inc.pipelines.length > 0) {
|
|
101
|
+
files.set('pipelines.ts', pipelinesFile(inc.pipelines));
|
|
102
|
+
}
|
|
103
|
+
const docs = recordToMap(inc.docs);
|
|
104
|
+
for (const [name, content] of docs) {
|
|
105
|
+
files.set(`docs/${name}`, content);
|
|
106
|
+
}
|
|
107
|
+
const tasks = recordToMap(inc.tasks);
|
|
108
|
+
for (const [name, content] of tasks) {
|
|
109
|
+
files.set(`tasks/${name}`, content);
|
|
110
|
+
}
|
|
111
|
+
return files;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Pure synthesizer: return the list of files this preset would create under
|
|
115
|
+
* the target's sharkcraft/ folder. Does not touch disk.
|
|
116
|
+
*/
|
|
117
|
+
export function synthesizePresetFiles(preset) {
|
|
118
|
+
return synthesizeFromIncludes(preset, preset.includes, preset.filesToCreate ?? []);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Synthesize files from a flattened {@link IResolvedPreset}. Uses the merged
|
|
122
|
+
* `includes` from composition rather than the root preset's local includes.
|
|
123
|
+
*/
|
|
124
|
+
export function synthesizeResolvedPresetFiles(resolved) {
|
|
125
|
+
return synthesizeFromIncludes(resolved.preset, resolved.includes, resolved.filesToCreate);
|
|
126
|
+
}
|
|
127
|
+
function synthesizeFromIncludes(preset, includes, filesToCreate) {
|
|
128
|
+
const out = [];
|
|
129
|
+
out.push({ path: 'sharkcraft.config.ts', content: configFile({ ...preset, includes }), kind: 'config' });
|
|
130
|
+
const files = inferFilesFromIncludes(includes);
|
|
131
|
+
for (const [path, content] of files) {
|
|
132
|
+
const kind = path.endsWith('.ts')
|
|
133
|
+
? path.replace('.ts', '')
|
|
134
|
+
: path.startsWith('docs/')
|
|
135
|
+
? 'docs'
|
|
136
|
+
: path.startsWith('tasks/')
|
|
137
|
+
? 'tasks'
|
|
138
|
+
: 'literal';
|
|
139
|
+
out.push({ path, content, kind });
|
|
140
|
+
}
|
|
141
|
+
for (const f of filesToCreate) {
|
|
142
|
+
out.push({ path: f.path, content: f.content, kind: 'literal' });
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
function inferFilesFromIncludes(inc) {
|
|
147
|
+
const files = new Map();
|
|
148
|
+
if (inc.knowledge && inc.knowledge.length > 0) {
|
|
149
|
+
files.set('knowledge.ts', knowledgeFile(inc.knowledge));
|
|
150
|
+
}
|
|
151
|
+
if (inc.rules && inc.rules.length > 0) {
|
|
152
|
+
files.set('rules.ts', rulesFile(inc.rules));
|
|
153
|
+
}
|
|
154
|
+
if (inc.paths && inc.paths.length > 0) {
|
|
155
|
+
files.set('paths.ts', pathsFile(inc.paths));
|
|
156
|
+
}
|
|
157
|
+
if (inc.templates && inc.templates.length > 0) {
|
|
158
|
+
files.set('templates.ts', templatesFile(inc.templates));
|
|
159
|
+
}
|
|
160
|
+
if (inc.pipelines && inc.pipelines.length > 0) {
|
|
161
|
+
files.set('pipelines.ts', pipelinesFile(inc.pipelines));
|
|
162
|
+
}
|
|
163
|
+
const docs = recordToMap(inc.docs);
|
|
164
|
+
for (const [name, content] of docs) {
|
|
165
|
+
files.set(`docs/${name}`, content);
|
|
166
|
+
}
|
|
167
|
+
const tasks = recordToMap(inc.tasks);
|
|
168
|
+
for (const [name, content] of tasks) {
|
|
169
|
+
files.set(`tasks/${name}`, content);
|
|
170
|
+
}
|
|
171
|
+
return files;
|
|
172
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './model/preset.js';
|
|
2
|
+
export * from './define/define-preset.js';
|
|
3
|
+
export * from './registry/preset-registry.js';
|
|
4
|
+
export * from './registry/load-presets.js';
|
|
5
|
+
export * from './registry/recommend.js';
|
|
6
|
+
export * from './emit/synthesize-files.js';
|
|
7
|
+
export * from './apply/preview-apply.js';
|
|
8
|
+
export * from './registry/resolve-preset.js';
|
|
9
|
+
export * from './registry/resolve-references.js';
|
|
10
|
+
export * from './builtin/builtin-presets.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC;AACxC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./model/preset.js";
|
|
2
|
+
export * from "./define/define-preset.js";
|
|
3
|
+
export * from "./registry/preset-registry.js";
|
|
4
|
+
export * from "./registry/load-presets.js";
|
|
5
|
+
export * from "./registry/recommend.js";
|
|
6
|
+
export * from "./emit/synthesize-files.js";
|
|
7
|
+
export * from "./apply/preview-apply.js";
|
|
8
|
+
export * from "./registry/resolve-preset.js";
|
|
9
|
+
export * from "./registry/resolve-references.js";
|
|
10
|
+
export * from "./builtin/builtin-presets.js";
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { WorkspaceProfile } from '@shrkcrft/workspace';
|
|
2
|
+
/**
|
|
3
|
+
* A preset is a reusable, additive SharkCraft project setup. Presets are
|
|
4
|
+
* applied to a target repo through the CLI — never through MCP.
|
|
5
|
+
*
|
|
6
|
+
* Presets ship **content** (knowledge / rules / paths / templates / pipelines /
|
|
7
|
+
* docs / tasks) plus metadata describing where they fit and what should
|
|
8
|
+
* happen next. They are NOT executable code; they are typed data the
|
|
9
|
+
* apply step renders into source files in the consumer's sharkcraft/ folder.
|
|
10
|
+
*/
|
|
11
|
+
export interface IPresetFile {
|
|
12
|
+
/** Relative path inside the target's sharkcraft/ folder. */
|
|
13
|
+
path: string;
|
|
14
|
+
/** Literal content to write. */
|
|
15
|
+
content: string;
|
|
16
|
+
}
|
|
17
|
+
export interface IPresetIncludes {
|
|
18
|
+
/** TS expressions for KnowledgeEntry objects (rendered into knowledge.ts). */
|
|
19
|
+
knowledge?: readonly string[];
|
|
20
|
+
/** TS expressions for KnowledgeEntry-shaped rule objects. */
|
|
21
|
+
rules?: readonly string[];
|
|
22
|
+
/** TS expressions for PathConvention KnowledgeEntry objects. */
|
|
23
|
+
paths?: readonly string[];
|
|
24
|
+
/** TS expressions for ITemplateDefinition objects. */
|
|
25
|
+
templates?: readonly string[];
|
|
26
|
+
/** TS expressions for IPipelineDefinition objects. */
|
|
27
|
+
pipelines?: readonly string[];
|
|
28
|
+
/** Filename → markdown content for sharkcraft/docs/<filename>. */
|
|
29
|
+
docs?: ReadonlyMap<string, string> | Readonly<Record<string, string>>;
|
|
30
|
+
/** Filename → markdown content for sharkcraft/tasks/<filename>. */
|
|
31
|
+
tasks?: ReadonlyMap<string, string> | Readonly<Record<string, string>>;
|
|
32
|
+
knowledgeIds?: readonly string[];
|
|
33
|
+
ruleIds?: readonly string[];
|
|
34
|
+
pathConventionIds?: readonly string[];
|
|
35
|
+
templateIds?: readonly string[];
|
|
36
|
+
pipelineIds?: readonly string[];
|
|
37
|
+
}
|
|
38
|
+
export interface IPreset {
|
|
39
|
+
/** Stable id (slug-style). */
|
|
40
|
+
id: string;
|
|
41
|
+
/** Short human title. */
|
|
42
|
+
title: string;
|
|
43
|
+
/** Free-form description. */
|
|
44
|
+
description: string;
|
|
45
|
+
/** Tags for grouping/filtering. */
|
|
46
|
+
tags?: readonly string[];
|
|
47
|
+
/** Profile tags this preset is appropriate for (used by recommendation). */
|
|
48
|
+
appliesTo?: readonly WorkspaceProfile[];
|
|
49
|
+
/** Profile tags this preset is INappropriate for (used by recommendation). */
|
|
50
|
+
notAppropriateFor?: readonly WorkspaceProfile[];
|
|
51
|
+
/** Higher = stronger recommendation when applicable. Default 5 (1..10). */
|
|
52
|
+
weight?: number;
|
|
53
|
+
/** Bundled content to merge into the target's sharkcraft/ folder. */
|
|
54
|
+
includes: IPresetIncludes;
|
|
55
|
+
/** Verbatim file writes (non-knowledge files). */
|
|
56
|
+
filesToCreate?: readonly IPresetFile[];
|
|
57
|
+
/** Notes printed after `shrk presets apply --write` finishes. */
|
|
58
|
+
postInstallNotes?: readonly string[];
|
|
59
|
+
/** Commands the human should run next. */
|
|
60
|
+
recommendedNextCommands?: readonly string[];
|
|
61
|
+
/** Free-form safety notes printed before any write. */
|
|
62
|
+
safetyNotes?: readonly string[];
|
|
63
|
+
/** Other preset ids this one composes; applied first, current preset wins. */
|
|
64
|
+
composes?: readonly string[];
|
|
65
|
+
/**
|
|
66
|
+
* Surface profile id this preset selects when
|
|
67
|
+
* applied via `shrk init`. The init flow writes `surface.profile:
|
|
68
|
+
* '<id>'` into the generated `sharkcraft.config.ts`. Set to one of
|
|
69
|
+
* the built-in profile ids (`developer`, `small-app`, `monorepo`,
|
|
70
|
+
* `pack-author`, `ci`, `agent`) or a pack-contributed profile id.
|
|
71
|
+
*/
|
|
72
|
+
surfaceProfile?: string;
|
|
73
|
+
}
|
|
74
|
+
export interface IPresetValidationIssue {
|
|
75
|
+
field: string;
|
|
76
|
+
message: string;
|
|
77
|
+
}
|
|
78
|
+
export interface IPresetValidationResult {
|
|
79
|
+
valid: boolean;
|
|
80
|
+
issues: IPresetValidationIssue[];
|
|
81
|
+
}
|
|
82
|
+
export declare function validatePreset(value: unknown): IPresetValidationResult;
|
|
83
|
+
//# sourceMappingURL=preset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preset.d.ts","sourceRoot":"","sources":["../../src/model/preset.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,6DAA6D;IAC7D,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,gEAAgE;IAChE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,sDAAsD;IACtD,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,sDAAsD;IACtD,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,kEAAkE;IAClE,IAAI,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtE,mEAAmE;IACnE,KAAK,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAKvE,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,OAAO;IACtB,8BAA8B;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACxC,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAChD,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,QAAQ,EAAE,eAAe,CAAC;IAC1B,kDAAkD;IAClD,aAAa,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IACvC,iEAAiE;IACjE,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,0CAA0C;IAC1C,uBAAuB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,uDAAuD;IACvD,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,sBAAsB,EAAE,CAAC;CAClC;AAID,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,uBAAuB,CAmBtE"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
|
|
2
|
+
export function validatePreset(value) {
|
|
3
|
+
const issues = [];
|
|
4
|
+
if (!value || typeof value !== 'object') {
|
|
5
|
+
return { valid: false, issues: [{ field: '<root>', message: 'preset must be an object' }] };
|
|
6
|
+
}
|
|
7
|
+
const p = value;
|
|
8
|
+
if (typeof p.id !== 'string' || !ID_PATTERN.test(p.id)) {
|
|
9
|
+
issues.push({ field: 'id', message: 'id must be a slug-style string' });
|
|
10
|
+
}
|
|
11
|
+
if (typeof p.title !== 'string' || p.title.length === 0) {
|
|
12
|
+
issues.push({ field: 'title', message: 'title required' });
|
|
13
|
+
}
|
|
14
|
+
if (typeof p.description !== 'string' || p.description.length === 0) {
|
|
15
|
+
issues.push({ field: 'description', message: 'description required' });
|
|
16
|
+
}
|
|
17
|
+
if (!p.includes || typeof p.includes !== 'object') {
|
|
18
|
+
issues.push({ field: 'includes', message: 'includes object required' });
|
|
19
|
+
}
|
|
20
|
+
return { valid: issues.length === 0, issues };
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type IImportContext } from '@shrkcrft/core';
|
|
2
|
+
import type { IPreset } from '../model/preset.js';
|
|
3
|
+
export interface ILoadedPresetFile {
|
|
4
|
+
source: string;
|
|
5
|
+
presets: IPreset[];
|
|
6
|
+
warnings: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface ILoadPresetsOptions {
|
|
9
|
+
importContext?: IImportContext;
|
|
10
|
+
}
|
|
11
|
+
export declare function loadPresetsFromFile(absPath: string, options?: ILoadPresetsOptions): Promise<ILoadedPresetFile>;
|
|
12
|
+
//# sourceMappingURL=load-presets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-presets.d.ts","sourceRoot":"","sources":["../../src/registry/load-presets.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,cAAc,EAAc,MAAM,gBAAgB,CAAC;AACjE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAGlD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,aAAa,CAAC,EAAE,cAAc,CAAC;CAChC;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,iBAAiB,CAAC,CA2B5B"}
|