@shrkcrft/cli 0.1.0-alpha.7 → 0.1.0-alpha.8
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/command-catalog.d.ts +7 -3
- package/dist/commands/command-catalog.d.ts.map +1 -1
- package/dist/commands/command-catalog.js +112 -33
- package/dist/commands/commands.command.d.ts.map +1 -1
- package/dist/commands/commands.command.js +4 -4
- package/dist/commands/constructs.command.d.ts.map +1 -1
- package/dist/commands/constructs.command.js +5 -22
- package/dist/commands/diff-check.command.d.ts +30 -0
- package/dist/commands/diff-check.command.d.ts.map +1 -0
- package/dist/commands/diff-check.command.js +210 -0
- package/dist/commands/doctor.command.d.ts.map +1 -1
- package/dist/commands/doctor.command.js +34 -2
- package/dist/commands/export.command.d.ts.map +1 -1
- package/dist/commands/export.command.js +76 -3
- package/dist/commands/help.command.d.ts +4 -3
- package/dist/commands/help.command.d.ts.map +1 -1
- package/dist/commands/help.command.js +74 -16
- package/dist/commands/helper.command.js +1 -1
- package/dist/commands/import.command.d.ts.map +1 -1
- package/dist/commands/import.command.js +121 -5
- package/dist/commands/init.command.d.ts.map +1 -1
- package/dist/commands/init.command.js +151 -7
- package/dist/commands/packs-new.d.ts +1 -1
- package/dist/commands/packs-new.d.ts.map +1 -1
- package/dist/commands/packs-new.js +2 -26
- package/dist/commands/profiles.command.js +4 -4
- package/dist/commands/release.command.js +13 -13
- package/dist/commands/search.command.js +1 -1
- package/dist/commands/task-context.command.js +0 -16
- package/dist/export/claude-commands-export.d.ts +60 -0
- package/dist/export/claude-commands-export.d.ts.map +1 -0
- package/dist/export/claude-commands-export.js +276 -0
- package/dist/export/export-formats.d.ts +1 -1
- package/dist/export/export-formats.d.ts.map +1 -1
- package/dist/export/export-formats.js +139 -12
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +104 -11
- package/package.json +20 -20
- package/dist/commands/plugin.command.d.ts +0 -11
- package/dist/commands/plugin.command.d.ts.map +0 -1
- package/dist/commands/plugin.command.js +0 -394
|
@@ -2,8 +2,9 @@ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { inspectSharkcraft } from '@shrkcrft/inspector';
|
|
4
4
|
import { ALL_EXPORT_FORMATS, isExportFormat, renderExport, } from "../export/export-formats.js";
|
|
5
|
+
import { buildClaudeCommands } from "../export/claude-commands-export.js";
|
|
5
6
|
import { flagBool, flagNumber, flagString, resolveCwd, } from "../command-registry.js";
|
|
6
|
-
import { asJson, header } from "../output/format-output.js";
|
|
7
|
+
import { asJson, bullet, header } from "../output/format-output.js";
|
|
7
8
|
import { exportBundleCommand, exportSessionCommand, exportQualityCommand, exportReviewCommand, } from "./export-bundle.command.js";
|
|
8
9
|
const ARCHIVE_SUBCOMMANDS = {
|
|
9
10
|
bundle: exportBundleCommand,
|
|
@@ -13,7 +14,7 @@ const ARCHIVE_SUBCOMMANDS = {
|
|
|
13
14
|
};
|
|
14
15
|
export const exportCommand = {
|
|
15
16
|
name: 'export',
|
|
16
|
-
description: '
|
|
17
|
+
description: 'Inversion — pull SharkCraft rules into the agent\'s prompt instead of the agent calling back to shrk. Single-file outputs: claude-skill (.claude/skills/<name>/SKILL.md, recommended), agents-md (AGENTS.md), claude-md (CLAUDE.md), cursor-rules (.cursor/rules/*.mdc), copilot-instructions. Multi-file output: claude-commands (.claude/commands/*.md — per-project slash commands like /new-service, /check-changes, /follow-shrk). Dry-run by default; pass --write to save.',
|
|
17
18
|
usage: 'shrk [--cwd <dir>] export <format> [--write] [--output <path>] [--task "<task>"] [--max-rules N] [--max-paths N] [--json]',
|
|
18
19
|
async run(args) {
|
|
19
20
|
const format = args.positional[0];
|
|
@@ -27,8 +28,13 @@ export const exportCommand = {
|
|
|
27
28
|
const sub = { ...args, positional: args.positional.slice(1) };
|
|
28
29
|
return archive.run(sub);
|
|
29
30
|
}
|
|
31
|
+
// Multi-file `claude-commands` dispatches separately — it emits
|
|
32
|
+
// one .md per slash command, not a single rendered file.
|
|
33
|
+
if (format === 'claude-commands') {
|
|
34
|
+
return runClaudeCommandsExport(args);
|
|
35
|
+
}
|
|
30
36
|
if (!isExportFormat(format)) {
|
|
31
|
-
process.stderr.write(`Unknown export format "${format}".\nFormats: ${ALL_EXPORT_FORMATS.join(', ')}, bundle, session, quality, review\n`);
|
|
37
|
+
process.stderr.write(`Unknown export format "${format}".\nFormats: ${ALL_EXPORT_FORMATS.join(', ')}, claude-commands, bundle, session, quality, review\n`);
|
|
32
38
|
return 2;
|
|
33
39
|
}
|
|
34
40
|
const cwd = resolveCwd(args);
|
|
@@ -81,3 +87,70 @@ export const exportCommand = {
|
|
|
81
87
|
return 0;
|
|
82
88
|
},
|
|
83
89
|
};
|
|
90
|
+
/**
|
|
91
|
+
* `shrk export claude-commands` — multi-file generator for Claude
|
|
92
|
+
* Code's native `.claude/commands/` slash-command primitive. Produces
|
|
93
|
+
* one .md per command (static + per-template).
|
|
94
|
+
*
|
|
95
|
+
* Unlike single-file exports (claude-skill / claude-md / etc.) this
|
|
96
|
+
* writes a SET of files. Each file is a complete recipe Claude Code
|
|
97
|
+
* loads when the user types the matching slash command.
|
|
98
|
+
*/
|
|
99
|
+
async function runClaudeCommandsExport(args) {
|
|
100
|
+
const cwd = resolveCwd(args);
|
|
101
|
+
const inspection = await inspectSharkcraft({ cwd });
|
|
102
|
+
const result = buildClaudeCommands(inspection);
|
|
103
|
+
const wantJson = flagBool(args, 'json');
|
|
104
|
+
const doWrite = flagBool(args, 'write');
|
|
105
|
+
const force = flagBool(args, 'force');
|
|
106
|
+
if (wantJson) {
|
|
107
|
+
process.stdout.write(asJson({
|
|
108
|
+
format: 'claude-commands',
|
|
109
|
+
write: doWrite,
|
|
110
|
+
files: result.files.map((f) => ({
|
|
111
|
+
path: f.path,
|
|
112
|
+
slash: f.slash,
|
|
113
|
+
source: f.source,
|
|
114
|
+
})),
|
|
115
|
+
}) + '\n');
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
if (!doWrite) {
|
|
119
|
+
process.stdout.write(header('Export (claude-commands) — dry-run'));
|
|
120
|
+
process.stdout.write(`Would write ${result.files.length} command file(s):\n\n`);
|
|
121
|
+
for (const f of result.files) {
|
|
122
|
+
process.stdout.write(` ${f.path}\n`);
|
|
123
|
+
process.stdout.write(` → users type \`/${f.slash}\` in Claude Code (${f.source})\n`);
|
|
124
|
+
}
|
|
125
|
+
process.stdout.write('\nRe-run with --write to save.\n');
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
const written = [];
|
|
129
|
+
const skipped = [];
|
|
130
|
+
for (const file of result.files) {
|
|
131
|
+
const fullPath = join(cwd, file.path);
|
|
132
|
+
mkdirSync(dirname(fullPath), { recursive: true });
|
|
133
|
+
if (existsSync(fullPath) && !force) {
|
|
134
|
+
skipped.push(file.path);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
writeFileSync(fullPath, file.content, 'utf8');
|
|
138
|
+
written.push(file.path);
|
|
139
|
+
}
|
|
140
|
+
process.stdout.write(header('Claude commands exported'));
|
|
141
|
+
if (written.length) {
|
|
142
|
+
process.stdout.write(`Wrote ${written.length} command file(s):\n`);
|
|
143
|
+
for (const p of written) {
|
|
144
|
+
const f = result.files.find((x) => x.path === p);
|
|
145
|
+
process.stdout.write(bullet(`${p} → \`/${f.slash}\``) + '\n');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (skipped.length) {
|
|
149
|
+
process.stdout.write(`\nSkipped ${skipped.length} (already exist; use --force to overwrite):\n`);
|
|
150
|
+
for (const p of skipped)
|
|
151
|
+
process.stdout.write(bullet(p) + '\n');
|
|
152
|
+
}
|
|
153
|
+
process.stdout.write('\nClaude Code picks up `.claude/commands/*.md` automatically. ' +
|
|
154
|
+
'Open the project in Claude Code, type `/` — the slash commands are in the palette.\n');
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { CommandRegistry } from '../command-registry.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Product start screen for bare `shrk` / `shrk --help`. Shows the
|
|
4
|
+
* curated ~20-command "starter" surface organized by workflow phase.
|
|
5
|
+
* Everything else stays callable; users see the full ~70-verb catalog
|
|
6
|
+
* via `shrk --full-help` or browse it through `shrk surface list`.
|
|
6
7
|
*
|
|
7
8
|
* Returns the lines (without trailing newline). Pulled into a function
|
|
8
9
|
* so tests can assert on the structure without grepping stdout.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"help.command.d.ts","sourceRoot":"","sources":["../../src/commands/help.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"help.command.d.ts","sourceRoot":"","sources":["../../src/commands/help.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAI9D;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAiD1C;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe;;;;cAK3C;QAAE,UAAU,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAA;KAAE,GAAG,MAAM;EAyHpF"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { header } from "../output/format-output.js";
|
|
2
|
+
import { COMMAND_CATALOG, defaultShowInHelp } from "./command-catalog.js";
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Product start screen for bare `shrk` / `shrk --help`. Shows the
|
|
5
|
+
* curated ~20-command "starter" surface organized by workflow phase.
|
|
6
|
+
* Everything else stays callable; users see the full ~70-verb catalog
|
|
7
|
+
* via `shrk --full-help` or browse it through `shrk surface list`.
|
|
6
8
|
*
|
|
7
9
|
* Returns the lines (without trailing newline). Pulled into a function
|
|
8
10
|
* so tests can assert on the structure without grepping stdout.
|
|
@@ -12,20 +14,46 @@ export function renderStartScreen() {
|
|
|
12
14
|
lines.push('SharkCraft CLI — deterministic, local-first project intelligence for AI coding agents.');
|
|
13
15
|
lines.push('Usage: shrk [--cwd <dir>] <command> [...args]');
|
|
14
16
|
lines.push('');
|
|
15
|
-
lines.push('
|
|
16
|
-
lines.push(' $ shrk
|
|
17
|
+
lines.push('Bootstrap:');
|
|
18
|
+
lines.push(' $ shrk init --infer --write — scan the repo + populate sharkcraft/ from real signals (recommended for new repos)');
|
|
19
|
+
lines.push(' $ shrk import claude-md ./CLAUDE.md --populate --write — populate sharkcraft/ from existing CLAUDE.md / AGENTS.md / .cursor/rules');
|
|
20
|
+
lines.push(' $ shrk init --with-claude-skill --write — scaffold sharkcraft/ AND inline rules into .claude/skills/ (one-step)');
|
|
21
|
+
lines.push(' $ shrk init — scaffold sharkcraft/ + config skeleton (preset defaults)');
|
|
17
22
|
lines.push(' $ shrk doctor — is the workspace healthy?');
|
|
18
|
-
lines.push(' $ shrk
|
|
19
|
-
lines.push(' $ shrk
|
|
20
|
-
lines.push('
|
|
21
|
-
lines.push('
|
|
23
|
+
lines.push(' $ shrk inspect — detect frameworks, paths, package manager');
|
|
24
|
+
lines.push(' $ shrk onboard — analyze an existing repo (advisory)');
|
|
25
|
+
lines.push('');
|
|
26
|
+
lines.push('Use it for a task:');
|
|
27
|
+
lines.push(' $ shrk brief — single-page brief Claude reads first (project + rules + paths + verification)');
|
|
28
|
+
lines.push(' $ shrk recommend "<task>" — what should I do?');
|
|
29
|
+
lines.push(' $ shrk context --task "<task>" — token-budgeted relevant context');
|
|
30
|
+
lines.push(' $ shrk task "<task>" — full AI-ready task packet (JSON)');
|
|
31
|
+
lines.push(' $ shrk coverage — what knowledge is missing');
|
|
32
|
+
lines.push('');
|
|
33
|
+
lines.push('Generate code safely:');
|
|
34
|
+
lines.push(' $ shrk gen <template> <name> — generate from template (dry-run by default)');
|
|
35
|
+
lines.push(' $ shrk apply <plan.json> — apply a reviewed plan (CLI is the only write path)');
|
|
36
|
+
lines.push(' $ shrk check boundaries — enforce layer / import boundaries');
|
|
37
|
+
lines.push(' $ shrk quality — pre-PR gate (doctor + boundaries + coverage + drift)');
|
|
38
|
+
lines.push('');
|
|
39
|
+
lines.push('Browse what shrk knows:');
|
|
40
|
+
lines.push(' $ shrk knowledge list — knowledge entries');
|
|
41
|
+
lines.push(' $ shrk rules list — rules + conventions');
|
|
42
|
+
lines.push(' $ shrk templates list — generator templates');
|
|
43
|
+
lines.push(' $ shrk import — parse AGENTS.md / CLAUDE.md / .cursor/rules');
|
|
44
|
+
lines.push(' $ shrk export — render to a flat agent-rule file');
|
|
45
|
+
lines.push('');
|
|
46
|
+
lines.push('Run shrk for an agent:');
|
|
47
|
+
lines.push(' $ shrk export claude-skill --write — generate .claude/skills/<name>/SKILL.md (rules INTO the prompt — no MCP roundtrip)');
|
|
48
|
+
lines.push(' $ shrk export agents-md --write — generate AGENTS.md / CLAUDE.md / .cursor/rules / copilot-instructions');
|
|
49
|
+
lines.push(' $ shrk mcp serve — start the MCP server (stdio) for live queries');
|
|
50
|
+
lines.push(' $ shrk dashboard — start the local read-only dashboard');
|
|
22
51
|
lines.push('');
|
|
23
|
-
lines.push('Discover the rest (
|
|
24
|
-
lines.push(' $ shrk surface list — full
|
|
52
|
+
lines.push('Discover the rest (always callable, hidden from this screen by default):');
|
|
53
|
+
lines.push(' $ shrk surface list — full ~70-verb catalog by tier');
|
|
25
54
|
lines.push(' $ shrk surface profiles — named profiles (small-app / monorepo / ci / agent / pack-author)');
|
|
26
|
-
lines.push(' $ shrk surface explain <cmd> — why a command has its current tier');
|
|
27
55
|
lines.push(' $ shrk help <command> — usage for a specific command');
|
|
28
|
-
lines.push(' $ shrk --full-help —
|
|
56
|
+
lines.push(' $ shrk --full-help — long, exhaustive help');
|
|
29
57
|
lines.push(' $ shrk --about — what shrk is and is not');
|
|
30
58
|
lines.push('');
|
|
31
59
|
lines.push('Free-form input is fine — `shrk "<task>"` routes to `shrk recommend`.');
|
|
@@ -89,13 +117,41 @@ export function makeHelpCommand(registry) {
|
|
|
89
117
|
process.stdout.write(renderStartScreen());
|
|
90
118
|
return 0;
|
|
91
119
|
}
|
|
120
|
+
const wantsAll = args.flags.get('all') === true;
|
|
92
121
|
process.stdout.write(`SharkCraft CLI — structured project intelligence for AI coding agents\n`);
|
|
93
122
|
process.stdout.write(`Usage: shrk [--cwd <dir>] <command> [...args]\n`);
|
|
123
|
+
// The catalog has ~360 entries; only ~30 top-level verbs pay
|
|
124
|
+
// rent (see PRIMARY_VERBS_ALLOWLIST in command-catalog.ts).
|
|
125
|
+
// Default --full-help filters to that set; `--full-help --all`
|
|
126
|
+
// dumps the entire catalog for power users.
|
|
127
|
+
const visibleVerbs = new Set();
|
|
128
|
+
if (!wantsAll) {
|
|
129
|
+
for (const entry of COMMAND_CATALOG) {
|
|
130
|
+
if (defaultShowInHelp(entry)) {
|
|
131
|
+
const verb = entry.command.split(/\s+/)[0] ?? '';
|
|
132
|
+
if (verb)
|
|
133
|
+
visibleVerbs.add(verb);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const visibleTopLevel = registry.list().filter((c) => {
|
|
138
|
+
if (wantsAll)
|
|
139
|
+
return true;
|
|
140
|
+
return visibleVerbs.has(c.name);
|
|
141
|
+
});
|
|
94
142
|
process.stdout.write(header('Top-level commands'));
|
|
95
|
-
for (const c of
|
|
143
|
+
for (const c of visibleTopLevel) {
|
|
96
144
|
process.stdout.write(` ${c.name.padEnd(10)} — ${c.description}\n`);
|
|
97
145
|
}
|
|
98
|
-
|
|
146
|
+
if (!wantsAll) {
|
|
147
|
+
const hiddenCount = registry.list().length - visibleTopLevel.length;
|
|
148
|
+
if (hiddenCount > 0) {
|
|
149
|
+
process.stdout.write(`\n …and ${hiddenCount} more, hidden from default help. Run \`shrk --full-help --all\` to see them, ` +
|
|
150
|
+
`or \`shrk surface list\` to browse by tier.\n`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Show each canonical group once — also filtered by the allowlist
|
|
154
|
+
// unless --all was passed.
|
|
99
155
|
const canonicalGroups = [];
|
|
100
156
|
const aliasMap = registry.listGroupAliases();
|
|
101
157
|
const seen = new Set();
|
|
@@ -103,7 +159,9 @@ export function makeHelpCommand(registry) {
|
|
|
103
159
|
const canonical = aliasMap.get(g) ?? g;
|
|
104
160
|
if (!seen.has(canonical)) {
|
|
105
161
|
seen.add(canonical);
|
|
106
|
-
|
|
162
|
+
if (wantsAll || visibleVerbs.has(canonical)) {
|
|
163
|
+
canonicalGroups.push(canonical);
|
|
164
|
+
}
|
|
107
165
|
}
|
|
108
166
|
}
|
|
109
167
|
const aliasesByCanonical = new Map();
|
|
@@ -85,7 +85,7 @@ export const helperPlanCommand = {
|
|
|
85
85
|
// profile is available. Today the helper-registry detects this inside
|
|
86
86
|
// buildHelperPlan via requireProfile(); we surface a friendly message.
|
|
87
87
|
if ('requiresProfile' in def && def.requiresProfile) {
|
|
88
|
-
process.stderr.write(`Helper "${id}" requires a
|
|
88
|
+
process.stderr.write(`Helper "${id}" requires a registered profile. Available:\n $ shrk profiles list\n`);
|
|
89
89
|
// Still try to build the plan; the registry throws with the same idea.
|
|
90
90
|
}
|
|
91
91
|
const cwd = resolveCwd(args);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import.command.d.ts","sourceRoot":"","sources":["../../src/commands/import.command.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"import.command.d.ts","sourceRoot":"","sources":["../../src/commands/import.command.ts"],"names":[],"mappings":"AAUA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAyBhC,eAAO,MAAM,aAAa,EAAE,eA8H3B,CAAC"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import * as nodePath from 'node:path';
|
|
3
|
-
import { emitKnowledgeTs, importAgentsMd, importClaudeMd, importCursorRules, } from '@shrkcrft/importer';
|
|
3
|
+
import { emitKnowledgeTs, importAgentsMd, importClaudeMd, importCursorRules, synthesizePopulatedFromImport, } from '@shrkcrft/importer';
|
|
4
4
|
import { flagBool, flagString, flagList, resolveCwd, } from "../command-registry.js";
|
|
5
|
-
import { asJson, header, kv } from "../output/format-output.js";
|
|
5
|
+
import { asJson, bullet, header, kv } from "../output/format-output.js";
|
|
6
6
|
function repeatedFlagList(args, name) {
|
|
7
7
|
return flagList(args, name, { dedupe: true });
|
|
8
8
|
}
|
|
@@ -22,8 +22,8 @@ function defaultOutput(format) {
|
|
|
22
22
|
}
|
|
23
23
|
export const importCommand = {
|
|
24
24
|
name: 'import',
|
|
25
|
-
description: 'Parse existing agent rule files (AGENTS.md / CLAUDE.md / .cursor/rules) into a
|
|
26
|
-
usage: 'shrk [--cwd <dir>] import <format> [<path>] [--prefix <id>] [--tag <t>] [--scope <s>] [--output <file>] [--write] [--force] [--json]',
|
|
25
|
+
description: 'Parse existing agent rule files (AGENTS.md / CLAUDE.md / .cursor/rules) into shrk\'s structure. By default (`--write`), saves a single draft TS file under sharkcraft/imports/ for the user to adopt by hand. Pass `--populate` to route entries directly into sharkcraft/rules.ts + paths.ts + knowledge.ts (by type) with a confidence triage report — same shape as `shrk init --infer`. Both modes dry-run by default.',
|
|
26
|
+
usage: 'shrk [--cwd <dir>] import <format> [<path>] [--prefix <id>] [--tag <t>] [--scope <s>] [--output <file>] [--write] [--populate] [--force] [--json]',
|
|
27
27
|
async run(args) {
|
|
28
28
|
const format = args.positional[0];
|
|
29
29
|
if (!format || !KNOWN_FORMATS.includes(format)) {
|
|
@@ -57,9 +57,24 @@ export const importCommand = {
|
|
|
57
57
|
}
|
|
58
58
|
const outPath = nodePath.resolve(cwd, flagString(args, 'output') ?? flagString(args, 'out') ?? defaultOutput(fmt));
|
|
59
59
|
const write = flagBool(args, 'write');
|
|
60
|
+
const populate = flagBool(args, 'populate');
|
|
60
61
|
const tsSource = emitKnowledgeTs(result.entries, {
|
|
61
62
|
sourceLabel: `${fmt} (${result.sourceFiles.join(', ') || path})`,
|
|
62
63
|
});
|
|
64
|
+
// ── `--populate` path: distribute entries into populated
|
|
65
|
+
// sharkcraft/* files (rules.ts / paths.ts / knowledge.ts) with
|
|
66
|
+
// confidence triage. Mirrors `shrk init --infer`'s contract.
|
|
67
|
+
if (populate) {
|
|
68
|
+
return runPopulateImport({
|
|
69
|
+
cwd,
|
|
70
|
+
fmt,
|
|
71
|
+
path,
|
|
72
|
+
result,
|
|
73
|
+
write,
|
|
74
|
+
force: flagBool(args, 'force'),
|
|
75
|
+
wantJson: flagBool(args, 'json'),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
63
78
|
if (flagBool(args, 'json')) {
|
|
64
79
|
process.stdout.write(asJson({
|
|
65
80
|
format: fmt,
|
|
@@ -113,3 +128,104 @@ export const importCommand = {
|
|
|
113
128
|
return 0;
|
|
114
129
|
},
|
|
115
130
|
};
|
|
131
|
+
/**
|
|
132
|
+
* `shrk import <format> --populate` path. Routes parsed entries
|
|
133
|
+
* into populated `sharkcraft/*.ts` files by KnowledgeType, with
|
|
134
|
+
* confidence triage and a companion `.imported-report.md`. Same
|
|
135
|
+
* shape as `shrk init --infer`.
|
|
136
|
+
*/
|
|
137
|
+
function runPopulateImport(args) {
|
|
138
|
+
const { cwd, fmt, path, result, write, force, wantJson } = args;
|
|
139
|
+
const sharkcraftDir = nodePath.join(cwd, 'sharkcraft');
|
|
140
|
+
const projectName = readProjectName(cwd) ?? 'project';
|
|
141
|
+
const description = `Imported from ${fmt} (${path})`;
|
|
142
|
+
const populated = synthesizePopulatedFromImport(result.entries, {
|
|
143
|
+
projectName,
|
|
144
|
+
description,
|
|
145
|
+
sourceLabel: `${fmt} (${result.sourceFiles.join(', ') || path})`,
|
|
146
|
+
});
|
|
147
|
+
if (wantJson) {
|
|
148
|
+
process.stdout.write(asJson({
|
|
149
|
+
format: fmt,
|
|
150
|
+
source: path,
|
|
151
|
+
mode: write ? 'populate' : 'populate-dry-run',
|
|
152
|
+
sharkcraftDir,
|
|
153
|
+
entryCount: result.entries.length,
|
|
154
|
+
warnings: result.warnings,
|
|
155
|
+
files: populated.files.map((f) => ({ path: f.path, kind: f.kind })),
|
|
156
|
+
report: populated.report,
|
|
157
|
+
}) + '\n');
|
|
158
|
+
return 0;
|
|
159
|
+
}
|
|
160
|
+
process.stdout.write(header(`Import (${fmt}) — populate`));
|
|
161
|
+
process.stdout.write(kv('source', path) + '\n');
|
|
162
|
+
process.stdout.write(kv('files read', String(result.sourceFiles.length)) + '\n');
|
|
163
|
+
process.stdout.write(kv('entries parsed', String(result.entries.length)) + '\n');
|
|
164
|
+
process.stdout.write(kv('target', sharkcraftDir) + '\n');
|
|
165
|
+
process.stdout.write(kv('mode', write ? 'write' : 'dry-run (preview only)') + '\n\n');
|
|
166
|
+
process.stdout.write(`Triage: ${populated.report.adoptedHigh.length} adopted directly · ` +
|
|
167
|
+
`${populated.report.adoptedMedium.length} marked for review · ` +
|
|
168
|
+
`${populated.report.dropped.length} dropped (in report).\n\n`);
|
|
169
|
+
if (result.warnings.length > 0) {
|
|
170
|
+
process.stdout.write(header('Parser warnings'));
|
|
171
|
+
for (const w of result.warnings) {
|
|
172
|
+
process.stdout.write(` ${w.origin}: ${w.message}\n`);
|
|
173
|
+
}
|
|
174
|
+
process.stdout.write('\n');
|
|
175
|
+
}
|
|
176
|
+
if (!write) {
|
|
177
|
+
process.stdout.write('Would write:\n');
|
|
178
|
+
for (const f of populated.files)
|
|
179
|
+
process.stdout.write(bullet(f.path) + '\n');
|
|
180
|
+
process.stdout.write('\nRe-run with --write to persist.\n');
|
|
181
|
+
return 0;
|
|
182
|
+
}
|
|
183
|
+
const written = [];
|
|
184
|
+
const skipped = [];
|
|
185
|
+
for (const file of populated.files) {
|
|
186
|
+
const fullPath = nodePath.join(sharkcraftDir, file.path);
|
|
187
|
+
mkdirSync(nodePath.dirname(fullPath), { recursive: true });
|
|
188
|
+
if (existsSync(fullPath) && !force) {
|
|
189
|
+
skipped.push(file.path);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
writeFileSync(fullPath, file.content, 'utf8');
|
|
193
|
+
written.push(file.path);
|
|
194
|
+
}
|
|
195
|
+
if (written.length) {
|
|
196
|
+
process.stdout.write('Wrote:\n');
|
|
197
|
+
for (const p of written)
|
|
198
|
+
process.stdout.write(bullet(p) + '\n');
|
|
199
|
+
}
|
|
200
|
+
if (skipped.length) {
|
|
201
|
+
process.stdout.write('\nSkipped (already exist; use --force to overwrite):\n');
|
|
202
|
+
for (const p of skipped)
|
|
203
|
+
process.stdout.write(bullet(p) + '\n');
|
|
204
|
+
}
|
|
205
|
+
process.stdout.write(`\nRead the import report: \`${nodePath.join('sharkcraft', '.imported-report.md')}\`\n` +
|
|
206
|
+
`It lists what was adopted high-confidence, what's marked for your review, ` +
|
|
207
|
+
`and what \`shrk import\` deliberately doesn't recover from markdown.\n`);
|
|
208
|
+
process.stdout.write('\nNext:\n');
|
|
209
|
+
process.stdout.write(bullet('$ shrk doctor — verify the populated setup') + '\n');
|
|
210
|
+
process.stdout.write(bullet('$ shrk brief — single-page brief Claude reads first') + '\n');
|
|
211
|
+
process.stdout.write(bullet('$ shrk export claude-skill --write — inline the rules into Claude\'s prompt') + '\n');
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Best-effort read of `package.json#name` for use as `projectName`
|
|
216
|
+
* in the generated config. Falls back to undefined; the synthesizer
|
|
217
|
+
* uses 'project' as a default.
|
|
218
|
+
*/
|
|
219
|
+
function readProjectName(cwd) {
|
|
220
|
+
const pkgPath = nodePath.join(cwd, 'package.json');
|
|
221
|
+
try {
|
|
222
|
+
if (!existsSync(pkgPath))
|
|
223
|
+
return undefined;
|
|
224
|
+
const raw = readFileSync(pkgPath, 'utf8');
|
|
225
|
+
const parsed = JSON.parse(raw);
|
|
226
|
+
return parsed.name ?? undefined;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AAuSjH,eAAO,MAAM,WAAW,EAAE,eA2HzB,CAAC"}
|
|
@@ -179,10 +179,10 @@ async function applyPresetInit(cwd, mode) {
|
|
|
179
179
|
renderPathsAdvisory(pathsAdvisory);
|
|
180
180
|
}
|
|
181
181
|
process.stdout.write('\nNext:\n');
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
]) {
|
|
182
|
+
process.stdout.write(bullet('$ shrk doctor — verify the setup (shape-aware verdict)') + '\n');
|
|
183
|
+
process.stdout.write(bullet('$ shrk brief — single-page brief Claude reads first') + '\n');
|
|
184
|
+
process.stdout.write(bullet('$ shrk export claude-skill --write — inline the rules into Claude\'s prompt via .claude/skills/') + '\n');
|
|
185
|
+
for (const cmd of preset.recommendedNextCommands ?? []) {
|
|
186
186
|
process.stdout.write(bullet(`$ ${cmd}`) + '\n');
|
|
187
187
|
}
|
|
188
188
|
process.stdout.write(bullet('Start the MCP server: `shrk mcp serve` (or run it via Claude Code)') + '\n');
|
|
@@ -243,13 +243,23 @@ function applyLegacyInit(cwd, force) {
|
|
|
243
243
|
}
|
|
244
244
|
export const initCommand = {
|
|
245
245
|
name: 'init',
|
|
246
|
-
description: 'Initialize a sharkcraft/ folder in the current repository. Pass --preset <id> to apply a built-in preset (default: generic). Use --zero-config or --preset auto to detect the workspace and pick a preset automatically. Use --legacy for the full pre-preset seed.',
|
|
247
|
-
usage: 'shrk init [--preset <id|auto>] [--zero-config] [--dry-run] [--write] [--legacy] [--force] [--merge] [--suggest-only] [--no-gitignore] [--surface-profile <id>]',
|
|
246
|
+
description: 'Initialize a sharkcraft/ folder in the current repository. Pass --preset <id> to apply a built-in preset (default: generic). Use --zero-config or --preset auto to detect the workspace and pick a preset automatically. Use --legacy for the full pre-preset seed. Pass --infer to scan the repo and populate sharkcraft/* with the real signals it finds (paths from disk, rules from tsconfig/package.json, boundaries from layer structure) — combines with --preset to add inferred entries on top of the preset baseline. Pass --with-claude-skill to also generate .claude/skills/<name>/SKILL.md so Claude Code picks up the rules automatically.',
|
|
247
|
+
usage: 'shrk init [--preset <id|auto>] [--zero-config] [--infer] [--dry-run] [--write] [--with-claude-skill] [--legacy] [--force] [--merge] [--suggest-only] [--no-gitignore] [--surface-profile <id>]',
|
|
248
248
|
async run(args) {
|
|
249
249
|
const force = flagBool(args, 'force');
|
|
250
250
|
const merge = flagBool(args, 'merge');
|
|
251
251
|
const cwd = resolveCwd(args);
|
|
252
|
+
const wantInfer = flagBool(args, 'infer');
|
|
252
253
|
const skipGitignore = flagBool(args, 'no-gitignore');
|
|
254
|
+
// `--infer` mode short-circuits the preset path entirely. The
|
|
255
|
+
// inferred output IS the populated sharkcraft/*, no preset
|
|
256
|
+
// baseline to merge against. This is the cleaner contract: the
|
|
257
|
+
// user opted into "scan my repo and tell me what's there", and
|
|
258
|
+
// adding a preset's static defaults on top would muddy the
|
|
259
|
+
// confidence reporting in .inferred-report.md.
|
|
260
|
+
if (wantInfer) {
|
|
261
|
+
return runInferInit(cwd, args, force);
|
|
262
|
+
}
|
|
253
263
|
if (flagBool(args, 'legacy')) {
|
|
254
264
|
const code = applyLegacyInit(cwd, force);
|
|
255
265
|
if (code === 0 && !skipGitignore) {
|
|
@@ -310,7 +320,7 @@ export const initCommand = {
|
|
|
310
320
|
if (dryRun) {
|
|
311
321
|
process.stdout.write(`Surface profile: ${surfaceDecision.profile} (${surfaceDecision.source}) — ${surfaceDecision.reason}\n`);
|
|
312
322
|
}
|
|
313
|
-
|
|
323
|
+
const presetCode = await applyPresetInit(cwd, {
|
|
314
324
|
presetId,
|
|
315
325
|
dryRun,
|
|
316
326
|
force,
|
|
@@ -320,5 +330,139 @@ export const initCommand = {
|
|
|
320
330
|
surfaceProfile: surfaceDecision.profile,
|
|
321
331
|
surfaceProfileReason: surfaceDecision.reason,
|
|
322
332
|
});
|
|
333
|
+
// `--with-claude-skill` chains the claude-skill export onto the init
|
|
334
|
+
// so a fresh user goes from `npx shrk init --with-claude-skill --write`
|
|
335
|
+
// to a working .claude/skills/<name>/SKILL.md in one step. Skipped in
|
|
336
|
+
// dry-run + when init itself didn't succeed.
|
|
337
|
+
if (presetCode === 0 &&
|
|
338
|
+
!dryRun &&
|
|
339
|
+
(flagBool(args, 'with-claude-skill') || flagBool(args, 'with-skill'))) {
|
|
340
|
+
const skillCode = await runClaudeSkillExportAfterInit(cwd, force);
|
|
341
|
+
// Init success is what we report; skill failure is logged but
|
|
342
|
+
// doesn't fail the init exit code (otherwise users would see a
|
|
343
|
+
// half-applied repo with a non-zero exit and panic).
|
|
344
|
+
if (skillCode !== 0) {
|
|
345
|
+
process.stderr.write('\n(claude-skill export failed — re-run `shrk export claude-skill --write` to retry.)\n');
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return presetCode;
|
|
323
349
|
},
|
|
324
350
|
};
|
|
351
|
+
/**
|
|
352
|
+
* Chain the claude-skill export onto a successful init. Loads the
|
|
353
|
+
* exporter lazily (it depends on inspectSharkcraft which has its own
|
|
354
|
+
* caching) and writes the skill file using the same `--force` semantics
|
|
355
|
+
* as init itself.
|
|
356
|
+
*/
|
|
357
|
+
/**
|
|
358
|
+
* `shrk init --infer` — scan the repo and populate `sharkcraft/*`
|
|
359
|
+
* with the real signals it finds, instead of writing preset defaults.
|
|
360
|
+
*
|
|
361
|
+
* Pipeline:
|
|
362
|
+
* 1. `inspectSharkcraft({ cwd })` builds the full inspection.
|
|
363
|
+
* 2. `buildOnboardingPlan(inspection)` extracts inferred paths,
|
|
364
|
+
* rules, boundaries, pipelines, verification commands.
|
|
365
|
+
* 3. `synthesizeFromOnboarding(plan)` triages each entry by
|
|
366
|
+
* confidence and emits populated `sharkcraft/*.ts` files plus
|
|
367
|
+
* a companion `.inferred-report.md` + `.inferred-report.json`.
|
|
368
|
+
* 4. Writer respects `--dry-run` / `--write` / `--force` /
|
|
369
|
+
* `--with-claude-skill` semantics consistently with the
|
|
370
|
+
* preset-init path.
|
|
371
|
+
*
|
|
372
|
+
* Honest-by-default: every adopted entry is tagged with its source
|
|
373
|
+
* signal; medium-confidence rows get a `// TODO: review` marker; low-
|
|
374
|
+
* confidence rows are dropped from the populated files and listed in
|
|
375
|
+
* the report so the user knows exactly what was inferred vs needs
|
|
376
|
+
* authoring.
|
|
377
|
+
*/
|
|
378
|
+
async function runInferInit(cwd, args, force) {
|
|
379
|
+
const { inspectSharkcraft, buildOnboardingPlan, synthesizeFromOnboarding } = await import('@shrkcrft/inspector');
|
|
380
|
+
const dryRun = !flagBool(args, 'write');
|
|
381
|
+
const sharkcraftDir = nodePath.join(cwd, 'sharkcraft');
|
|
382
|
+
process.stdout.write(header('SharkCraft init — infer'));
|
|
383
|
+
process.stdout.write(`Folder: ${sharkcraftDir}\n`);
|
|
384
|
+
const inspection = await inspectSharkcraft({ cwd });
|
|
385
|
+
const plan = buildOnboardingPlan(inspection);
|
|
386
|
+
const result = synthesizeFromOnboarding(plan);
|
|
387
|
+
// Summary block — print BEFORE writing so the user can ctrl-C if the
|
|
388
|
+
// confidence triage doesn't look right.
|
|
389
|
+
process.stdout.write(`\nDetected profiles: ${plan.projectSummary.profiles.join(', ') || '(none)'}\n`);
|
|
390
|
+
process.stdout.write(`Inference triage: ${result.report.adoptedHigh.length} adopted directly · ` +
|
|
391
|
+
`${result.report.adoptedMedium.length} marked for review · ` +
|
|
392
|
+
`${result.report.dropped.length} dropped (in report).\n\n`);
|
|
393
|
+
const written = [];
|
|
394
|
+
const skipped = [];
|
|
395
|
+
const wouldWrite = [];
|
|
396
|
+
for (const file of result.files) {
|
|
397
|
+
const fullPath = nodePath.join(sharkcraftDir, file.path);
|
|
398
|
+
if (dryRun) {
|
|
399
|
+
wouldWrite.push(file.path);
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
mkdirSync(nodePath.dirname(fullPath), { recursive: true });
|
|
403
|
+
if (existsSync(fullPath) && !force) {
|
|
404
|
+
skipped.push(file.path);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
writeFileSync(fullPath, file.content, 'utf8');
|
|
408
|
+
written.push(file.path);
|
|
409
|
+
}
|
|
410
|
+
if (dryRun) {
|
|
411
|
+
process.stdout.write('Would write:\n');
|
|
412
|
+
for (const p of wouldWrite)
|
|
413
|
+
process.stdout.write(bullet(p) + '\n');
|
|
414
|
+
process.stdout.write('\nRe-run with --write to persist.\n');
|
|
415
|
+
return 0;
|
|
416
|
+
}
|
|
417
|
+
if (written.length) {
|
|
418
|
+
process.stdout.write('Wrote:\n');
|
|
419
|
+
for (const p of written)
|
|
420
|
+
process.stdout.write(bullet(p) + '\n');
|
|
421
|
+
}
|
|
422
|
+
if (skipped.length) {
|
|
423
|
+
process.stdout.write('\nSkipped (already exist; use --force to overwrite):\n');
|
|
424
|
+
for (const p of skipped)
|
|
425
|
+
process.stdout.write(bullet(p) + '\n');
|
|
426
|
+
}
|
|
427
|
+
process.stdout.write(`\nRead the confidence report: \`${nodePath.join('sharkcraft', '.inferred-report.md')}\`\n` +
|
|
428
|
+
`It lists what was adopted high-confidence, what's marked for your review, ` +
|
|
429
|
+
`and what shrk can't infer (project-specific decisions you should author by hand).\n`);
|
|
430
|
+
process.stdout.write('\nNext:\n');
|
|
431
|
+
process.stdout.write(bullet('$ shrk doctor — verify the setup (shape-aware verdict)') + '\n');
|
|
432
|
+
process.stdout.write(bullet('$ shrk brief — single-page brief Claude reads first') + '\n');
|
|
433
|
+
process.stdout.write(bullet('$ shrk export claude-skill --write — inline the rules into Claude\'s prompt') + '\n');
|
|
434
|
+
// Chain claude-skill if requested.
|
|
435
|
+
if (flagBool(args, 'with-claude-skill') || flagBool(args, 'with-skill')) {
|
|
436
|
+
const skillCode = await runClaudeSkillExportAfterInit(cwd, force);
|
|
437
|
+
if (skillCode !== 0) {
|
|
438
|
+
process.stderr.write('\n(claude-skill export failed — re-run `shrk export claude-skill --write` to retry.)\n');
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
// Gitignore + paths advisory same as preset path.
|
|
442
|
+
const skipGitignore = flagBool(args, 'no-gitignore');
|
|
443
|
+
if (!skipGitignore) {
|
|
444
|
+
const patch = ensureSharkcraftGitignore({ cwd, dryRun: false });
|
|
445
|
+
if (patch.added.length > 0) {
|
|
446
|
+
process.stdout.write('\n' + renderGitignorePatch(patch, false));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return 0;
|
|
450
|
+
}
|
|
451
|
+
async function runClaudeSkillExportAfterInit(cwd, force) {
|
|
452
|
+
const { inspectSharkcraft } = await import('@shrkcrft/inspector');
|
|
453
|
+
const { renderExport } = await import("../export/export-formats.js");
|
|
454
|
+
const inspection = await inspectSharkcraft({ cwd });
|
|
455
|
+
const result = renderExport(inspection, { format: 'claude-skill' });
|
|
456
|
+
const outputPath = nodePath.join(cwd, result.suggestedPath);
|
|
457
|
+
mkdirSync(nodePath.dirname(outputPath), { recursive: true });
|
|
458
|
+
if (existsSync(outputPath) && !force) {
|
|
459
|
+
process.stdout.write(`\nSkipped claude-skill export — ${result.suggestedPath} already exists. Pass --force to overwrite, or run \`shrk export claude-skill --write --force\`.\n`);
|
|
460
|
+
return 0;
|
|
461
|
+
}
|
|
462
|
+
writeFileSync(outputPath, result.content, 'utf8');
|
|
463
|
+
process.stdout.write('\n');
|
|
464
|
+
process.stdout.write(header('Claude-skill exported'));
|
|
465
|
+
process.stdout.write(`Wrote ${result.suggestedPath}\n` +
|
|
466
|
+
` → Claude Code will auto-load this skill in any session opened against this repo.\n`);
|
|
467
|
+
return 0;
|
|
468
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ICommandHandler } from '../command-registry.js';
|
|
2
|
-
export type PackKind = 'generic' | 'framework' | 'architecture' | 'enterprise'
|
|
2
|
+
export type PackKind = 'generic' | 'framework' | 'architecture' | 'enterprise';
|
|
3
3
|
interface IScaffoldFile {
|
|
4
4
|
/** Path relative to the new pack root. */
|
|
5
5
|
relativePath: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packs-new.d.ts","sourceRoot":"","sources":["../../src/commands/packs-new.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAIhC,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,cAAc,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"packs-new.d.ts","sourceRoot":"","sources":["../../src/commands/packs-new.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAIhC,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,cAAc,GAAG,YAAY,CAAC;AAS/E,UAAU,aAAa;IACrB,0CAA0C;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,kDAAkD;AAClD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,kBAAkB,GAAG,mBAAmB,CAyG/E;AA0SD,eAAO,MAAM,eAAe,EAAE,eAoG7B,CAAC;AAUF,eAAO,MAAM,gBAAgB,EAAE,eAqI9B,CAAC"}
|