@shrkcrft/cli 0.1.0-alpha.2 → 0.1.0-alpha.4
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/dist/commands/init.command.d.ts.map +1 -1
- package/dist/commands/init.command.js +23 -0
- package/dist/init/init-templates.d.ts.map +1 -1
- package/dist/init/init-templates.js +133 -113
- package/dist/init/paths-advisory.d.ts +20 -0
- package/dist/init/paths-advisory.d.ts.map +1 -0
- package/dist/init/paths-advisory.js +88 -0
- package/dist/surface/surface-config-writer.d.ts.map +1 -1
- package/dist/surface/surface-config-writer.js +23 -11
- package/package.json +20 -20
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.command.d.ts","sourceRoot":"","sources":["../../src/commands/init.command.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.command.d.ts","sourceRoot":"","sources":["../../src/commands/init.command.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAwB,KAAK,eAAe,EAA+B,MAAM,wBAAwB,CAAC;AAmSjH,eAAO,MAAM,WAAW,EAAE,eA2FzB,CAAC"}
|
|
@@ -6,6 +6,7 @@ import { listBuiltInSurfaceProfiles, suggestSurfaceProfile } from '@shrkcrft/ins
|
|
|
6
6
|
import { INIT_FILES } from "../init/init-templates.js";
|
|
7
7
|
import { buildDetectedBlock, renderDetectedBlockText } from "../init/detected-block.js";
|
|
8
8
|
import { ensureSharkcraftGitignore, renderGitignorePatch } from "../init/gitignore.js";
|
|
9
|
+
import { annotatePathsAgainstDisk } from "../init/paths-advisory.js";
|
|
9
10
|
import { applySurfaceTextEdit } from "../surface/surface-config-writer.js";
|
|
10
11
|
import { flagBool, flagString, resolveCwd } from "../command-registry.js";
|
|
11
12
|
import { bullet, header } from "../output/format-output.js";
|
|
@@ -133,6 +134,11 @@ async function applyPresetInit(cwd, mode) {
|
|
|
133
134
|
source: 'override',
|
|
134
135
|
});
|
|
135
136
|
}
|
|
137
|
+
// Workspace-shape advisory: when the preset emits path conventions
|
|
138
|
+
// that don't exist in this repo (common with generic presets in
|
|
139
|
+
// framework-specific repos), annotate paths.ts so the user knows
|
|
140
|
+
// which defaults to fix.
|
|
141
|
+
const pathsAdvisory = annotatePathsAgainstDisk(cwd, plan.sharkcraftDir);
|
|
136
142
|
process.stdout.write(header('SharkCraft initialized'));
|
|
137
143
|
process.stdout.write(`Preset: ${preset.id} — ${preset.title}\n`);
|
|
138
144
|
process.stdout.write(`Folder: ${plan.sharkcraftDir}\n`);
|
|
@@ -169,6 +175,9 @@ async function applyPresetInit(cwd, mode) {
|
|
|
169
175
|
process.stdout.write('\n' + renderGitignorePatch(patch, false));
|
|
170
176
|
}
|
|
171
177
|
}
|
|
178
|
+
if (pathsAdvisory.annotated) {
|
|
179
|
+
renderPathsAdvisory(pathsAdvisory);
|
|
180
|
+
}
|
|
172
181
|
process.stdout.write('\nNext:\n');
|
|
173
182
|
for (const cmd of preset.recommendedNextCommands ?? [
|
|
174
183
|
'shrk doctor',
|
|
@@ -179,6 +188,16 @@ async function applyPresetInit(cwd, mode) {
|
|
|
179
188
|
process.stdout.write(bullet('Start the MCP server: `shrk mcp serve` (or run it via Claude Code)') + '\n');
|
|
180
189
|
return 0;
|
|
181
190
|
}
|
|
191
|
+
function renderPathsAdvisory(advisory) {
|
|
192
|
+
process.stdout.write('\n');
|
|
193
|
+
process.stdout.write(header('Paths advisory'));
|
|
194
|
+
process.stdout.write('These preset-default paths do not exist in this repo:\n');
|
|
195
|
+
for (const p of advisory.missingPaths) {
|
|
196
|
+
process.stdout.write(bullet(p) + '\n');
|
|
197
|
+
}
|
|
198
|
+
process.stdout.write('\nEdit sharkcraft/paths.ts to match your real layout. ' +
|
|
199
|
+
'Run `shrk onboard --dry-run` to see what the engine infers.\n');
|
|
200
|
+
}
|
|
182
201
|
function applyLegacyInit(cwd, force) {
|
|
183
202
|
const { root } = detectProjectRoot(cwd);
|
|
184
203
|
const sharkcraftDir = nodePath.join(root, 'sharkcraft');
|
|
@@ -212,6 +231,10 @@ function applyLegacyInit(cwd, force) {
|
|
|
212
231
|
for (const s of skipped)
|
|
213
232
|
process.stdout.write(bullet(s) + '\n');
|
|
214
233
|
}
|
|
234
|
+
const legacyAdvisory = annotatePathsAgainstDisk(cwd, sharkcraftDir);
|
|
235
|
+
if (legacyAdvisory.annotated) {
|
|
236
|
+
renderPathsAdvisory(legacyAdvisory);
|
|
237
|
+
}
|
|
215
238
|
process.stdout.write('\nNext:\n');
|
|
216
239
|
process.stdout.write(bullet('Run `shrk inspect` to see your project summary.') + '\n');
|
|
217
240
|
process.stdout.write(bullet('Run `shrk knowledge list` to see what was seeded.') + '\n');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init-templates.d.ts","sourceRoot":"","sources":["../../src/init/init-templates.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"init-templates.d.ts","sourceRoot":"","sources":["../../src/init/init-templates.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAUD,eAAO,MAAM,UAAU,EAAE,SAAS,SAAS,EA2W1C,CAAC"}
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
+
// Self-contained scaffolding for `shrk init --legacy`. Every generated
|
|
2
|
+
// `sharkcraft/*.ts` file must work in a brand-new downstream repo where
|
|
3
|
+
// no `@shrkcrft/*` packages are installed beyond the CLI itself. The
|
|
4
|
+
// loaders (knowledge / templates / pipelines / path-conventions) are
|
|
5
|
+
// shape-agnostic — they accept any object with the required string
|
|
6
|
+
// fields — so each emitted file declares its own minimal helpers +
|
|
7
|
+
// enum-like constants inline.
|
|
1
8
|
export const INIT_FILES = [
|
|
2
9
|
{
|
|
3
10
|
relativePath: 'sharkcraft.config.ts',
|
|
4
|
-
content:
|
|
11
|
+
content: `// Generated by \`shrk init --legacy\`. Plain default export — no
|
|
12
|
+
// @shrkcrft/* import required. The config loader validates this object
|
|
13
|
+
// by shape (zod), so the literal works without a helper call.
|
|
5
14
|
|
|
6
|
-
export default
|
|
15
|
+
export default {
|
|
7
16
|
projectName: 'my-project',
|
|
8
17
|
description: 'A SharkCraft-powered repository.',
|
|
9
18
|
knowledgeFiles: ['knowledge.ts'],
|
|
@@ -13,12 +22,36 @@ export default defineSharkCraftConfig({
|
|
|
13
22
|
docsFiles: ['docs/overview.md', 'docs/architecture.md', 'docs/quick-start.md'],
|
|
14
23
|
defaultMaxTokens: 4000,
|
|
15
24
|
defaultScope: ['typescript'],
|
|
16
|
-
}
|
|
25
|
+
};
|
|
17
26
|
`,
|
|
18
27
|
},
|
|
19
28
|
{
|
|
20
29
|
relativePath: 'knowledge.ts',
|
|
21
|
-
content:
|
|
30
|
+
content: `// Local helpers — keep this file self-contained (no @shrkcrft/* imports).
|
|
31
|
+
// The knowledge loader is shape-agnostic; it accepts any object whose
|
|
32
|
+
// \`id\`, \`title\`, and \`content\` are strings.
|
|
33
|
+
|
|
34
|
+
const KnowledgePriority = {
|
|
35
|
+
Critical: 'critical',
|
|
36
|
+
High: 'high',
|
|
37
|
+
Medium: 'medium',
|
|
38
|
+
Low: 'low',
|
|
39
|
+
} as const;
|
|
40
|
+
|
|
41
|
+
const KnowledgeType = {
|
|
42
|
+
Rule: 'rule',
|
|
43
|
+
Path: 'path',
|
|
44
|
+
Template: 'template',
|
|
45
|
+
Architecture: 'architecture',
|
|
46
|
+
Technical: 'technical',
|
|
47
|
+
Convention: 'convention',
|
|
48
|
+
Workflow: 'workflow',
|
|
49
|
+
Warning: 'warning',
|
|
50
|
+
} as const;
|
|
51
|
+
|
|
52
|
+
function defineKnowledgeEntry<T>(entry: T): T {
|
|
53
|
+
return entry;
|
|
54
|
+
}
|
|
22
55
|
|
|
23
56
|
export const projectOverview = defineKnowledgeEntry({
|
|
24
57
|
id: 'project.overview',
|
|
@@ -67,8 +100,20 @@ export default [projectOverview, aiAgentBriefing, generationSafety];
|
|
|
67
100
|
},
|
|
68
101
|
{
|
|
69
102
|
relativePath: 'rules.ts',
|
|
70
|
-
content:
|
|
71
|
-
|
|
103
|
+
content: `// Local helpers — keep this file self-contained (no @shrkcrft/* imports).
|
|
104
|
+
|
|
105
|
+
const KnowledgePriority = {
|
|
106
|
+
Critical: 'critical',
|
|
107
|
+
High: 'high',
|
|
108
|
+
Medium: 'medium',
|
|
109
|
+
Low: 'low',
|
|
110
|
+
} as const;
|
|
111
|
+
|
|
112
|
+
const KnowledgeType = { Rule: 'rule' } as const;
|
|
113
|
+
|
|
114
|
+
function defineRule<T>(rule: T): T {
|
|
115
|
+
return { ...rule, type: KnowledgeType.Rule } as T;
|
|
116
|
+
}
|
|
72
117
|
|
|
73
118
|
export const tsNamingClasses = defineRule({
|
|
74
119
|
id: 'typescript.naming.classes',
|
|
@@ -142,81 +187,15 @@ export default [
|
|
|
142
187
|
},
|
|
143
188
|
{
|
|
144
189
|
relativePath: 'paths.ts',
|
|
145
|
-
content:
|
|
146
|
-
import { KnowledgePriority } from '@shrkcrft/knowledge';
|
|
147
|
-
|
|
148
|
-
export const appSrc = definePathConvention({
|
|
149
|
-
id: 'app.src',
|
|
150
|
-
title: 'Application source root',
|
|
151
|
-
path: 'src',
|
|
152
|
-
description: 'All application source lives here.',
|
|
153
|
-
priority: KnowledgePriority.Critical,
|
|
154
|
-
scope: ['typescript'],
|
|
155
|
-
tags: ['source-path', 'root'],
|
|
156
|
-
appliesWhen: ['generate-code'],
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
export const services = definePathConvention({
|
|
160
|
-
id: 'app.services',
|
|
161
|
-
title: 'Application services',
|
|
162
|
-
path: 'src/services',
|
|
163
|
-
description: 'Application services live here.',
|
|
164
|
-
priority: KnowledgePriority.High,
|
|
165
|
-
scope: ['typescript', 'backend'],
|
|
166
|
-
tags: ['service', 'source-path'],
|
|
167
|
-
appliesWhen: ['generate-service', 'create-business-logic'],
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
export const utils = definePathConvention({
|
|
171
|
-
id: 'app.utils',
|
|
172
|
-
title: 'Utilities',
|
|
173
|
-
path: 'src/utils',
|
|
174
|
-
description: 'Pure functions, no side effects.',
|
|
175
|
-
priority: KnowledgePriority.Medium,
|
|
176
|
-
scope: ['typescript'],
|
|
177
|
-
tags: ['util', 'source-path'],
|
|
178
|
-
appliesWhen: ['generate-utility'],
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
export const features = definePathConvention({
|
|
182
|
-
id: 'app.features',
|
|
183
|
-
title: 'Feature folders',
|
|
184
|
-
path: 'src/features',
|
|
185
|
-
description: 'Vertical feature slices. Use this for end-to-end features.',
|
|
186
|
-
priority: KnowledgePriority.Medium,
|
|
187
|
-
scope: ['typescript'],
|
|
188
|
-
tags: ['feature', 'source-path'],
|
|
189
|
-
appliesWhen: ['generate-feature'],
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
export const tests = definePathConvention({
|
|
193
|
-
id: 'app.tests',
|
|
194
|
-
title: 'Test files',
|
|
195
|
-
path: 'tests',
|
|
196
|
-
description: 'Test files (or co-located *.spec.ts next to the unit under test).',
|
|
197
|
-
priority: KnowledgePriority.Medium,
|
|
198
|
-
scope: ['typescript', 'testing'],
|
|
199
|
-
tags: ['test', 'source-path'],
|
|
200
|
-
appliesWhen: ['generate-test'],
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
export const docs = definePathConvention({
|
|
204
|
-
id: 'app.docs',
|
|
205
|
-
title: 'Documentation',
|
|
206
|
-
path: 'docs',
|
|
207
|
-
description: 'Long-form human-readable docs (optional).',
|
|
208
|
-
priority: KnowledgePriority.Low,
|
|
209
|
-
scope: ['typescript'],
|
|
210
|
-
tags: ['docs'],
|
|
211
|
-
appliesWhen: ['add-docs'],
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
export default [appSrc, services, utils, features, tests, docs];
|
|
215
|
-
`,
|
|
190
|
+
content: PATHS_PLACEHOLDER_CONTENT(),
|
|
216
191
|
},
|
|
217
192
|
{
|
|
218
193
|
relativePath: 'templates.ts',
|
|
219
|
-
content:
|
|
194
|
+
content: `// Local helpers — keep this file self-contained (no @shrkcrft/* imports).
|
|
195
|
+
|
|
196
|
+
function defineTemplate<T>(template: T): T {
|
|
197
|
+
return template;
|
|
198
|
+
}
|
|
220
199
|
|
|
221
200
|
export const tsService = defineTemplate({
|
|
222
201
|
id: 'typescript.service',
|
|
@@ -229,8 +208,8 @@ export const tsService = defineTemplate({
|
|
|
229
208
|
{ name: 'name', required: true, description: 'kebab-case file name (e.g. user-profile)' },
|
|
230
209
|
{ name: 'className', required: true, description: 'PascalCase class name (e.g. UserProfileService)' },
|
|
231
210
|
],
|
|
232
|
-
targetPath: ({ name }) => \`src/services/\${name}.service.ts\`,
|
|
233
|
-
content: ({ className }) => \`export class \${className} {
|
|
211
|
+
targetPath: ({ name }: { name: string }) => \`src/services/\${name}.service.ts\`,
|
|
212
|
+
content: ({ className }: { className: string }) => \`export class \${className} {
|
|
234
213
|
constructor() {}
|
|
235
214
|
|
|
236
215
|
init(): void {
|
|
@@ -253,42 +232,14 @@ export const tsUtility = defineTemplate({
|
|
|
253
232
|
{ name: 'name', required: true, description: 'kebab-case file name' },
|
|
254
233
|
{ name: 'camel', required: true, description: 'camelCase function name' },
|
|
255
234
|
],
|
|
256
|
-
targetPath: ({ name }) => \`src/utils/\${name}.ts\`,
|
|
257
|
-
content: ({ camel }) => \`export function \${camel}(input: unknown): unknown {
|
|
235
|
+
targetPath: ({ name }: { name: string }) => \`src/utils/\${name}.ts\`,
|
|
236
|
+
content: ({ camel }: { camel: string }) => \`export function \${camel}(input: unknown): unknown {
|
|
258
237
|
return input;
|
|
259
238
|
}
|
|
260
239
|
\`,
|
|
261
240
|
postGenerationNotes: ['Keep utilities pure. No side effects, no shared mutable state.'],
|
|
262
241
|
});
|
|
263
242
|
|
|
264
|
-
export const tsFeatureFolder = defineTemplate({
|
|
265
|
-
id: 'typescript.feature',
|
|
266
|
-
name: 'TypeScript Feature folder',
|
|
267
|
-
description: 'Creates a vertical feature slice (index + service + types).',
|
|
268
|
-
tags: ['typescript', 'feature'],
|
|
269
|
-
scope: ['typescript'],
|
|
270
|
-
appliesWhen: ['generate-feature'],
|
|
271
|
-
variables: [
|
|
272
|
-
{ name: 'name', required: true },
|
|
273
|
-
{ name: 'pascal', required: true },
|
|
274
|
-
],
|
|
275
|
-
files: ({ name, pascal }) => [
|
|
276
|
-
{
|
|
277
|
-
targetPath: \`src/features/\${name}/index.ts\`,
|
|
278
|
-
content: \`export * from './\${name}.service.ts';\nexport * from './\${name}.types.ts';\n\`,
|
|
279
|
-
},
|
|
280
|
-
{
|
|
281
|
-
targetPath: \`src/features/\${name}/\${name}.service.ts\`,
|
|
282
|
-
content: \`import type { I\${pascal}Config } from './\${name}.types.ts';\n\nexport class \${pascal}Service {\n constructor(private readonly config: I\${pascal}Config) {}\n}\n\`,
|
|
283
|
-
},
|
|
284
|
-
{
|
|
285
|
-
targetPath: \`src/features/\${name}/\${name}.types.ts\`,
|
|
286
|
-
content: \`export interface I\${pascal}Config {\n enabled: boolean;\n}\n\`,
|
|
287
|
-
},
|
|
288
|
-
],
|
|
289
|
-
postGenerationNotes: ['Add tests under tests/<feature> or co-located *.spec.ts.'],
|
|
290
|
-
});
|
|
291
|
-
|
|
292
243
|
export const tsTest = defineTemplate({
|
|
293
244
|
id: 'typescript.test',
|
|
294
245
|
name: 'TypeScript Test File',
|
|
@@ -300,11 +251,18 @@ export const tsTest = defineTemplate({
|
|
|
300
251
|
{ name: 'name', required: true },
|
|
301
252
|
{ name: 'pascal', required: true },
|
|
302
253
|
],
|
|
303
|
-
targetPath: ({ name }) => \`tests/\${name}.spec.ts\`,
|
|
304
|
-
content: ({ pascal }) => \`import { describe, expect, test } from 'bun:test'
|
|
254
|
+
targetPath: ({ name }: { name: string }) => \`tests/\${name}.spec.ts\`,
|
|
255
|
+
content: ({ pascal }: { pascal: string }) => \`import { describe, expect, test } from 'bun:test';
|
|
256
|
+
|
|
257
|
+
describe('\${pascal}', () => {
|
|
258
|
+
test('placeholder', () => {
|
|
259
|
+
expect(true).toBe(true);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
\`,
|
|
305
263
|
});
|
|
306
264
|
|
|
307
|
-
export default [tsService, tsUtility,
|
|
265
|
+
export default [tsService, tsUtility, tsTest];
|
|
308
266
|
`,
|
|
309
267
|
},
|
|
310
268
|
{
|
|
@@ -411,3 +369,65 @@ Lower-priority work for SharkCraft-powered knowledge in this repo.
|
|
|
411
369
|
`,
|
|
412
370
|
},
|
|
413
371
|
];
|
|
372
|
+
function PATHS_PLACEHOLDER_CONTENT() {
|
|
373
|
+
// The legacy seed ships generic path examples. The modern preset path
|
|
374
|
+
// (`shrk init --preset <id>`) is workspace-aware. To avoid emitting
|
|
375
|
+
// broken defaults like `src/services/` in repos that don't have them,
|
|
376
|
+
// the legacy seed now ships *commented* examples plus an `// TODO:`
|
|
377
|
+
// marker the human is expected to fill in. The runner-instructions in
|
|
378
|
+
// docs/onboarding.md describe how to derive paths from the live repo.
|
|
379
|
+
return `// Local helpers — keep this file self-contained (no @shrkcrft/* imports).
|
|
380
|
+
|
|
381
|
+
const KnowledgePriority = {
|
|
382
|
+
Critical: 'critical',
|
|
383
|
+
High: 'high',
|
|
384
|
+
Medium: 'medium',
|
|
385
|
+
Low: 'low',
|
|
386
|
+
} as const;
|
|
387
|
+
|
|
388
|
+
const KnowledgeType = { Path: 'path' } as const;
|
|
389
|
+
|
|
390
|
+
function definePathConvention<T>(convention: T): T {
|
|
391
|
+
return { ...convention, type: KnowledgeType.Path } as T;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// TODO: replace the examples below with real path conventions for this
|
|
395
|
+
// repository. Run \`shrk onboard --dry-run\` to see what the inference
|
|
396
|
+
// engine detects from the workspace.
|
|
397
|
+
|
|
398
|
+
export const appSrc = definePathConvention({
|
|
399
|
+
id: 'app.src',
|
|
400
|
+
title: 'Application source root',
|
|
401
|
+
path: 'src',
|
|
402
|
+
description: 'All application source lives here. Adjust to match this repo.',
|
|
403
|
+
priority: KnowledgePriority.Critical,
|
|
404
|
+
scope: ['typescript'],
|
|
405
|
+
tags: ['source-path', 'root'],
|
|
406
|
+
appliesWhen: ['generate-code'],
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
export const tests = definePathConvention({
|
|
410
|
+
id: 'app.tests',
|
|
411
|
+
title: 'Test files',
|
|
412
|
+
path: 'tests',
|
|
413
|
+
description: 'Test files. Many repos co-locate \`*.spec.ts\` next to the unit under test instead — pick one.',
|
|
414
|
+
priority: KnowledgePriority.Medium,
|
|
415
|
+
scope: ['typescript', 'testing'],
|
|
416
|
+
tags: ['test', 'source-path'],
|
|
417
|
+
appliesWhen: ['generate-test'],
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
export const docs = definePathConvention({
|
|
421
|
+
id: 'app.docs',
|
|
422
|
+
title: 'Documentation',
|
|
423
|
+
path: 'docs',
|
|
424
|
+
description: 'Long-form human-readable docs (optional).',
|
|
425
|
+
priority: KnowledgePriority.Low,
|
|
426
|
+
scope: ['typescript'],
|
|
427
|
+
tags: ['docs'],
|
|
428
|
+
appliesWhen: ['add-docs'],
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
export default [appSrc, tests, docs];
|
|
432
|
+
`;
|
|
433
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface IPathsAdvisory {
|
|
2
|
+
missingPaths: readonly string[];
|
|
3
|
+
existingPaths: readonly string[];
|
|
4
|
+
/** True when an advisory comment was prepended to the file. */
|
|
5
|
+
annotated: boolean;
|
|
6
|
+
pathsFile: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Scan the generated `sharkcraft/paths.ts` for `path: '<x>'` references
|
|
10
|
+
* and check each `<x>` against the live workspace. When any path is
|
|
11
|
+
* missing, prepend a clearly-labeled comment block listing the missing
|
|
12
|
+
* paths so the user knows which defaults to adjust.
|
|
13
|
+
*
|
|
14
|
+
* Idempotent — if the file already starts with the advisory marker,
|
|
15
|
+
* it is left untouched. Non-destructive — never edits, comments, or
|
|
16
|
+
* removes the original entries. The user is expected to revise them
|
|
17
|
+
* based on the advisory + `shrk onboard --dry-run` output.
|
|
18
|
+
*/
|
|
19
|
+
export declare function annotatePathsAgainstDisk(cwd: string, sharkcraftDir: string): IPathsAdvisory;
|
|
20
|
+
//# sourceMappingURL=paths-advisory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths-advisory.d.ts","sourceRoot":"","sources":["../../src/init/paths-advisory.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,+DAA+D;IAC/D,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,EACX,aAAa,EAAE,MAAM,GACpB,cAAc,CAwChB"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import * as nodePath from 'node:path';
|
|
3
|
+
const ADVISORY_MARKER = '// ⚠️ Workspace-shape advisory (added by `shrk init`):';
|
|
4
|
+
/**
|
|
5
|
+
* Scan the generated `sharkcraft/paths.ts` for `path: '<x>'` references
|
|
6
|
+
* and check each `<x>` against the live workspace. When any path is
|
|
7
|
+
* missing, prepend a clearly-labeled comment block listing the missing
|
|
8
|
+
* paths so the user knows which defaults to adjust.
|
|
9
|
+
*
|
|
10
|
+
* Idempotent — if the file already starts with the advisory marker,
|
|
11
|
+
* it is left untouched. Non-destructive — never edits, comments, or
|
|
12
|
+
* removes the original entries. The user is expected to revise them
|
|
13
|
+
* based on the advisory + `shrk onboard --dry-run` output.
|
|
14
|
+
*/
|
|
15
|
+
export function annotatePathsAgainstDisk(cwd, sharkcraftDir) {
|
|
16
|
+
const pathsFile = nodePath.join(sharkcraftDir, 'paths.ts');
|
|
17
|
+
if (!existsSync(pathsFile)) {
|
|
18
|
+
return { missingPaths: [], existingPaths: [], annotated: false, pathsFile };
|
|
19
|
+
}
|
|
20
|
+
const original = readFileSync(pathsFile, 'utf8');
|
|
21
|
+
if (original.startsWith(ADVISORY_MARKER)) {
|
|
22
|
+
// Already annotated. Re-derive sets for caller diagnostics.
|
|
23
|
+
return classifyOnly(cwd, pathsFile, original);
|
|
24
|
+
}
|
|
25
|
+
const { existing, missing } = classifyPathReferences(cwd, original);
|
|
26
|
+
if (missing.length === 0) {
|
|
27
|
+
return {
|
|
28
|
+
missingPaths: [],
|
|
29
|
+
existingPaths: existing,
|
|
30
|
+
annotated: false,
|
|
31
|
+
pathsFile,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const lines = [
|
|
35
|
+
ADVISORY_MARKER,
|
|
36
|
+
'//',
|
|
37
|
+
'// The following paths referenced below do NOT exist in this repository:',
|
|
38
|
+
...missing.map((p) => `// - ${p}`),
|
|
39
|
+
'//',
|
|
40
|
+
'// They are conservative defaults from the chosen preset. Adjust them to',
|
|
41
|
+
'// match your actual layout. Run `shrk onboard --dry-run` to see what',
|
|
42
|
+
'// the inference engine detects from your workspace.',
|
|
43
|
+
'//',
|
|
44
|
+
'',
|
|
45
|
+
];
|
|
46
|
+
writeFileSync(pathsFile, lines.join('\n') + original, 'utf8');
|
|
47
|
+
return {
|
|
48
|
+
missingPaths: missing,
|
|
49
|
+
existingPaths: existing,
|
|
50
|
+
annotated: true,
|
|
51
|
+
pathsFile,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function classifyOnly(cwd, pathsFile, original) {
|
|
55
|
+
const { existing, missing } = classifyPathReferences(cwd, original);
|
|
56
|
+
return {
|
|
57
|
+
missingPaths: missing,
|
|
58
|
+
existingPaths: existing,
|
|
59
|
+
annotated: false,
|
|
60
|
+
pathsFile,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function classifyPathReferences(cwd, source) {
|
|
64
|
+
// Match `path: '<x>'` / `path: "<x>"`. Ignore obvious code-context
|
|
65
|
+
// references (e.g. inside template literals); we only consume the
|
|
66
|
+
// first plain-string occurrence per entry.
|
|
67
|
+
const re = /\bpath\s*:\s*['"]([^'"\n]+)['"]/g;
|
|
68
|
+
const seen = new Set();
|
|
69
|
+
let m;
|
|
70
|
+
while ((m = re.exec(source)) !== null) {
|
|
71
|
+
if (m[1])
|
|
72
|
+
seen.add(m[1]);
|
|
73
|
+
}
|
|
74
|
+
const existing = [];
|
|
75
|
+
const missing = [];
|
|
76
|
+
for (const p of [...seen].sort()) {
|
|
77
|
+
if (isReachable(cwd, p))
|
|
78
|
+
existing.push(p);
|
|
79
|
+
else
|
|
80
|
+
missing.push(p);
|
|
81
|
+
}
|
|
82
|
+
return { existing, missing };
|
|
83
|
+
}
|
|
84
|
+
function isReachable(cwd, p) {
|
|
85
|
+
// Absolute or rooted-relative — resolve as-is.
|
|
86
|
+
const full = nodePath.isAbsolute(p) ? p : nodePath.resolve(cwd, p);
|
|
87
|
+
return existsSync(full);
|
|
88
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"surface-config-writer.d.ts","sourceRoot":"","sources":["../../src/surface/surface-config-writer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,MAAM,WAAW,kBAAkB;IACjC,oBAAoB;IACpB,KAAK,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB;IACjB,SAAS,EAAE,KAAK,GAAG,QAAQ,CAAC;CAC7B;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,cAAc,CAAC;IACtB,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACrC,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACrC,iDAAiD;IACjD,WAAW,EAAE,OAAO,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,cAAc,GAAG,SAAS,EAClC,KAAK,EAAE,SAAS,kBAAkB,EAAE,GACnC,kBAAkB,CA0BpB;AAOD;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,kBAAkB,GACvB,yBAAyB,CAe3B;
|
|
1
|
+
{"version":3,"file":"surface-config-writer.d.ts","sourceRoot":"","sources":["../../src/surface/surface-config-writer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,MAAM,WAAW,kBAAkB;IACjC,oBAAoB;IACpB,KAAK,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB;IACjB,SAAS,EAAE,KAAK,GAAG,QAAQ,CAAC;CAC7B;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,cAAc,CAAC;IACtB,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACrC,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACrC,iDAAiD;IACjD,WAAW,EAAE,OAAO,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,cAAc,GAAG,SAAS,EAClC,KAAK,EAAE,SAAS,kBAAkB,EAAE,GACnC,kBAAkB,CA0BpB;AAOD;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,kBAAkB,GACvB,yBAAyB,CAe3B;AAoBD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,SAAO,GAAG,MAAM,CAsBjF;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,MAAM,CA6BtF;AAED,8CAA8C;AAC9C,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAE/D"}
|
|
@@ -72,10 +72,11 @@ function fileExists(p) {
|
|
|
72
72
|
return false;
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
// Plain default export — no @shrkcrft/* import required. The config
|
|
76
|
+
// loader (packages/config/src/config-loader.ts) validates by shape, so a
|
|
77
|
+
// literal object works the same as a `defineSharkCraftConfig()` call.
|
|
78
|
+
const DEFAULT_CONFIG_BODY = `export default {
|
|
79
|
+
};
|
|
79
80
|
`;
|
|
80
81
|
const SURFACE_BLOCK_REGEX = /(^\s*surface\s*:\s*\{[\s\S]*?\}\s*,?\s*\n)/m;
|
|
81
82
|
/**
|
|
@@ -120,14 +121,25 @@ export function applySurfaceTextEdit(original, surface) {
|
|
|
120
121
|
if (SURFACE_BLOCK_REGEX.test(original)) {
|
|
121
122
|
return original.replace(SURFACE_BLOCK_REGEX, block);
|
|
122
123
|
}
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return original.replace(closeRegex, `\n${block}$2`);
|
|
124
|
+
// (A) `defineSharkCraftConfig({ ... })` style — insert before the `})`.
|
|
125
|
+
const closeFn = /(\n)(\}\)\s*;?\s*)$/m;
|
|
126
|
+
if (closeFn.test(original)) {
|
|
127
|
+
return original.replace(closeFn, `\n${block}$2`);
|
|
128
128
|
}
|
|
129
|
-
//
|
|
130
|
-
|
|
129
|
+
// (B) `const config = { ... };\nexport default config;` plain style —
|
|
130
|
+
// insert before the `};` that closes the literal.
|
|
131
|
+
const closePlain = /(\n)(\};?\s*\nexport\s+default\s+\w+\s*;?\s*\n*)$/m;
|
|
132
|
+
if (closePlain.test(original)) {
|
|
133
|
+
return original.replace(closePlain, `\n${block}$2`);
|
|
134
|
+
}
|
|
135
|
+
// (C) `export default { ... };` direct style — insert before the `};`.
|
|
136
|
+
const closeDirect = /(\n)(\};?\s*\n*)$/m;
|
|
137
|
+
if (closeDirect.test(original)) {
|
|
138
|
+
return original.replace(closeDirect, `\n${block}$2`);
|
|
139
|
+
}
|
|
140
|
+
// Fallback: append at end with a plain default-exported config (no
|
|
141
|
+
// `@shrkcrft/*` import required).
|
|
142
|
+
return `${original}\n${DEFAULT_CONFIG_BODY.replace('};', `${block}};`)}`;
|
|
131
143
|
}
|
|
132
144
|
/** Default config file path for a project. */
|
|
133
145
|
export function defaultConfigFile(sharkcraftDir) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shrkcrft/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.4",
|
|
4
4
|
"description": "SharkCraft CLI (`shrk`): structured project intelligence for AI coding agents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "SharkCraft contributors",
|
|
@@ -47,25 +47,25 @@
|
|
|
47
47
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@shrkcrft/core": "^0.1.0-alpha.
|
|
51
|
-
"@shrkcrft/config": "^0.1.0-alpha.
|
|
52
|
-
"@shrkcrft/workspace": "^0.1.0-alpha.
|
|
53
|
-
"@shrkcrft/knowledge": "^0.1.0-alpha.
|
|
54
|
-
"@shrkcrft/context": "^0.1.0-alpha.
|
|
55
|
-
"@shrkcrft/rules": "^0.1.0-alpha.
|
|
56
|
-
"@shrkcrft/paths": "^0.1.0-alpha.
|
|
57
|
-
"@shrkcrft/templates": "^0.1.0-alpha.
|
|
58
|
-
"@shrkcrft/plugin-api": "^0.1.0-alpha.
|
|
59
|
-
"@shrkcrft/dashboard-api": "^0.1.0-alpha.
|
|
60
|
-
"@shrkcrft/pipelines": "^0.1.0-alpha.
|
|
61
|
-
"@shrkcrft/presets": "^0.1.0-alpha.
|
|
62
|
-
"@shrkcrft/boundaries": "^0.1.0-alpha.
|
|
63
|
-
"@shrkcrft/generator": "^0.1.0-alpha.
|
|
64
|
-
"@shrkcrft/importer": "^0.1.0-alpha.
|
|
65
|
-
"@shrkcrft/inspector": "^0.1.0-alpha.
|
|
66
|
-
"@shrkcrft/ai": "^0.1.0-alpha.
|
|
67
|
-
"@shrkcrft/shared": "^0.1.0-alpha.
|
|
68
|
-
"@shrkcrft/mcp-server": "^0.1.0-alpha.
|
|
50
|
+
"@shrkcrft/core": "^0.1.0-alpha.4",
|
|
51
|
+
"@shrkcrft/config": "^0.1.0-alpha.4",
|
|
52
|
+
"@shrkcrft/workspace": "^0.1.0-alpha.4",
|
|
53
|
+
"@shrkcrft/knowledge": "^0.1.0-alpha.4",
|
|
54
|
+
"@shrkcrft/context": "^0.1.0-alpha.4",
|
|
55
|
+
"@shrkcrft/rules": "^0.1.0-alpha.4",
|
|
56
|
+
"@shrkcrft/paths": "^0.1.0-alpha.4",
|
|
57
|
+
"@shrkcrft/templates": "^0.1.0-alpha.4",
|
|
58
|
+
"@shrkcrft/plugin-api": "^0.1.0-alpha.4",
|
|
59
|
+
"@shrkcrft/dashboard-api": "^0.1.0-alpha.4",
|
|
60
|
+
"@shrkcrft/pipelines": "^0.1.0-alpha.4",
|
|
61
|
+
"@shrkcrft/presets": "^0.1.0-alpha.4",
|
|
62
|
+
"@shrkcrft/boundaries": "^0.1.0-alpha.4",
|
|
63
|
+
"@shrkcrft/generator": "^0.1.0-alpha.4",
|
|
64
|
+
"@shrkcrft/importer": "^0.1.0-alpha.4",
|
|
65
|
+
"@shrkcrft/inspector": "^0.1.0-alpha.4",
|
|
66
|
+
"@shrkcrft/ai": "^0.1.0-alpha.4",
|
|
67
|
+
"@shrkcrft/shared": "^0.1.0-alpha.4",
|
|
68
|
+
"@shrkcrft/mcp-server": "^0.1.0-alpha.4"
|
|
69
69
|
},
|
|
70
70
|
"publishConfig": {
|
|
71
71
|
"access": "public"
|