@skillit/client 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +40 -0
  2. package/dist/bin.d.ts +2 -1
  3. package/dist/bin.d.ts.map +1 -1
  4. package/dist/bin.js +21 -13
  5. package/dist/bin.js.map +1 -1
  6. package/dist/commands/audit.d.ts +34 -0
  7. package/dist/commands/audit.d.ts.map +1 -0
  8. package/dist/commands/audit.js +139 -0
  9. package/dist/commands/audit.js.map +1 -0
  10. package/dist/commands/gen.d.ts +20 -0
  11. package/dist/commands/gen.d.ts.map +1 -0
  12. package/dist/commands/gen.js +108 -0
  13. package/dist/commands/gen.js.map +1 -0
  14. package/dist/commands/init.d.ts +3 -22
  15. package/dist/commands/init.d.ts.map +1 -1
  16. package/dist/commands/init.js +23 -84
  17. package/dist/commands/init.js.map +1 -1
  18. package/dist/commands/refine.d.ts +34 -2
  19. package/dist/commands/refine.d.ts.map +1 -1
  20. package/dist/commands/refine.js +61 -29
  21. package/dist/commands/refine.js.map +1 -1
  22. package/dist/generate.d.ts +67 -0
  23. package/dist/generate.d.ts.map +1 -0
  24. package/dist/generate.js +55 -0
  25. package/dist/generate.js.map +1 -0
  26. package/dist/mcp-mode.d.ts +22 -0
  27. package/dist/mcp-mode.d.ts.map +1 -0
  28. package/dist/mcp-mode.js +27 -0
  29. package/dist/mcp-mode.js.map +1 -0
  30. package/dist/model/anthropic.d.ts +11 -0
  31. package/dist/model/anthropic.d.ts.map +1 -1
  32. package/dist/model/anthropic.js +16 -2
  33. package/dist/model/anthropic.js.map +1 -1
  34. package/dist/model/cli/adapters.d.ts.map +1 -1
  35. package/dist/model/cli/adapters.js +35 -2
  36. package/dist/model/cli/adapters.js.map +1 -1
  37. package/dist/model/cli/cli-client.d.ts.map +1 -1
  38. package/dist/model/cli/cli-client.js +4 -2
  39. package/dist/model/cli/cli-client.js.map +1 -1
  40. package/dist/program.d.ts +12 -0
  41. package/dist/program.d.ts.map +1 -0
  42. package/dist/program.js +42 -0
  43. package/dist/program.js.map +1 -0
  44. package/dist/typedoc-entry.d.ts +6 -0
  45. package/dist/typedoc-entry.d.ts.map +1 -0
  46. package/dist/typedoc-entry.js +14 -0
  47. package/dist/typedoc-entry.js.map +1 -0
  48. package/package.json +18 -7
  49. package/skills/skillit-bootstrap/SKILL.md +115 -0
  50. package/skills/skillit-bootstrap/references/surface-routing.md +97 -0
  51. package/CHANGELOG.md +0 -32
  52. package/scripts/gen-refine-cli-skill.mjs +0 -48
  53. package/src/__tests__/anthropic-model.test.ts +0 -45
  54. package/src/__tests__/anthropic-prompt.test.ts +0 -102
  55. package/src/__tests__/cli-adapters.test.ts +0 -119
  56. package/src/__tests__/cli-client.test.ts +0 -58
  57. package/src/__tests__/cli-run.test.ts +0 -52
  58. package/src/__tests__/detect-mode.test.ts +0 -114
  59. package/src/__tests__/detect-source.test.ts +0 -158
  60. package/src/__tests__/fixtures/bin-with-program.mjs +0 -6
  61. package/src/__tests__/init.test.ts +0 -218
  62. package/src/__tests__/model-client-factory.test.ts +0 -28
  63. package/src/__tests__/refine-resolve.test.ts +0 -72
  64. package/src/bin.ts +0 -18
  65. package/src/commands/init.ts +0 -207
  66. package/src/commands/refine.ts +0 -261
  67. package/src/detect-mode.ts +0 -94
  68. package/src/detect-source.ts +0 -123
  69. package/src/index.ts +0 -3
  70. package/src/model/anthropic.ts +0 -116
  71. package/src/model/cli/adapters.ts +0 -178
  72. package/src/model/cli/cli-client.ts +0 -52
  73. package/src/model/cli/run.ts +0 -83
  74. package/src/model/model-client-factory.ts +0 -62
  75. package/src/model/models.ts +0 -7
  76. package/tsconfig.build.json +0 -9
  77. package/tsconfig.json +0 -4
@@ -1,261 +0,0 @@
1
- // packages/client/src/commands/refine.ts
2
- import { Command } from 'commander';
3
- import {
4
- McpRefineSource,
5
- TypeScriptMcpRefineSource,
6
- extractMcpSkill,
7
- readMcpConfigFile
8
- } from '@skillit/mcp';
9
- import { CliRefineSource, loadProgram } from '@skillit/cli';
10
- import { refineSkill, type ModelClient, type RefineSource } from '@skillit/core';
11
- import { createModelClient } from '../model/model-client-factory.js';
12
- import { detectRefineMode } from '../detect-mode.js';
13
- import {
14
- classifyRefineSources,
15
- detectInstalledSources,
16
- type DetectedRefineSource,
17
- type RefineSourceKind
18
- } from '../detect-source.js';
19
- import { join } from 'node:path';
20
-
21
- function parsePositiveInt(raw: string, flag: string): number {
22
- const n = parseInt(raw, 10);
23
- if (!Number.isFinite(n) || n < 1) {
24
- throw new Error(`${flag} must be a positive integer, got: ${raw}`);
25
- }
26
- return n;
27
- }
28
-
29
- /** Options consulted by {@link resolveRefineSource}. */
30
- export interface RefineSourceResolveOpts {
31
- source?: string;
32
- mcp?: string;
33
- }
34
-
35
- /** Result of resolving the refine source: a concrete kind or an actionable error. */
36
- export type RefineSourceResolution = { kind: 'cli' | 'mcp' } | { error: string };
37
-
38
- const VALID_SOURCES = ['cli', 'mcp', 'typedoc'] as const;
39
- const SOURCE_FORM = '--source <cli|mcp|typedoc>';
40
-
41
- /**
42
- * Resolve the refine source from explicit `--source` (wins) or detection,
43
- * then validate per-source flag requirements. Pure and unit-testable: it does
44
- * not read the filesystem or invoke the model.
45
- *
46
- * @param opts - parsed `--source` / `--mcp` flags
47
- * @param detected - result of {@link detectRefineSource} for the cwd
48
- * @param candidates - the raw installed source kinds (from
49
- * {@link detectInstalledSources}); named in the ambiguous error
50
- */
51
- export function resolveRefineSource(
52
- opts: RefineSourceResolveOpts,
53
- detected: DetectedRefineSource,
54
- candidates: readonly RefineSourceKind[] = []
55
- ): RefineSourceResolution {
56
- let kind: RefineSourceKind;
57
- if (opts.source !== undefined) {
58
- if (!VALID_SOURCES.includes(opts.source as RefineSourceKind)) {
59
- return { error: `Invalid --source value: ${opts.source}. Use ${SOURCE_FORM}.` };
60
- }
61
- kind = opts.source as RefineSourceKind;
62
- } else if (detected === 'ambiguous') {
63
- return {
64
- error: `Cannot determine refine source: multiple @skillit sources installed (found: ${candidates.join(', ')}).
65
- Pass ${SOURCE_FORM} to choose one.`
66
- };
67
- } else if (detected === 'none') {
68
- return {
69
- error: `Cannot determine refine source: no @skillit source package detected.
70
- Pass ${SOURCE_FORM} to choose one.`
71
- };
72
- } else {
73
- kind = detected;
74
- }
75
-
76
- if (kind === 'typedoc') {
77
- return { error: 'typedoc refine not yet supported; use --source cli|mcp.' };
78
- }
79
- if (kind === 'mcp' && opts.mcp === undefined) {
80
- return { error: 'The mcp source requires --mcp <path> (path to mcp.json or MCP config file).' };
81
- }
82
- return { kind };
83
- }
84
-
85
- /** Parsed options for the `refine` action / {@link runRefineCommand}. */
86
- export interface RefineCommandOpts {
87
- source?: string;
88
- program?: string;
89
- mcp?: string;
90
- server?: string;
91
- overlay?: string;
92
- mode?: string;
93
- sourceGlob?: string;
94
- maxIterations: string;
95
- items: string;
96
- modelClient?: string;
97
- modelCliTimeout?: string;
98
- }
99
-
100
- /** The model backend to use; defaults to the API client. */
101
- export function resolveModelClientKind(raw: string | undefined): string {
102
- return raw ?? 'api';
103
- }
104
-
105
- /**
106
- * The body of the `refine` command action, extracted so it can be reused
107
- * programmatically (e.g. by `skillit init`) without argv gymnastics. Sets
108
- * `process.exitCode` and writes progress/errors to the console exactly as the
109
- * CLI does.
110
- */
111
- export async function runRefineCommand(opts: RefineCommandOpts): Promise<void> {
112
- const cwd = process.cwd();
113
- const maxIterations = parsePositiveInt(opts.maxIterations, '--max-iterations');
114
- const itemsPerIteration = parsePositiveInt(opts.items, '--items');
115
-
116
- const timeoutMs =
117
- opts.modelCliTimeout !== undefined
118
- ? parsePositiveInt(opts.modelCliTimeout, '--model-cli-timeout')
119
- : undefined;
120
- let model: ModelClient;
121
- try {
122
- model = createModelClient(
123
- resolveModelClientKind(opts.modelClient),
124
- timeoutMs !== undefined ? { timeoutMs } : {}
125
- );
126
- } catch (error) {
127
- console.error(error instanceof Error ? error.message : String(error));
128
- process.exitCode = 1;
129
- return;
130
- }
131
-
132
- const candidates = await detectInstalledSources(cwd);
133
- const detected = classifyRefineSources(candidates);
134
- const resolution = resolveRefineSource(opts, detected, candidates);
135
- if ('error' in resolution) {
136
- console.error(resolution.error);
137
- process.exitCode = 1;
138
- return;
139
- }
140
-
141
- let source: RefineSource;
142
- let reportInPlace = false;
143
-
144
- if (resolution.kind === 'cli') {
145
- const program = await loadProgram({ program: opts.program, cwd });
146
- const sourceGlob = opts.sourceGlob ?? join(cwd, '**', '*.ts');
147
- source = new CliRefineSource({ program, sourceGlob, cwd });
148
- reportInPlace = true;
149
- } else {
150
- // mcp source: --mcp guaranteed present by resolveRefineSource.
151
- const mcpPath = opts.mcp!;
152
- const overlayPath = opts.overlay ?? join(cwd, '.skillit-overlay.json');
153
-
154
- let mode: 'build' | 'runtime';
155
- if (opts.mode === 'build' || opts.mode === 'runtime') {
156
- mode = opts.mode;
157
- } else if (opts.mode !== undefined) {
158
- console.error(`Invalid --mode value: ${opts.mode}. Use 'build' or 'runtime'.`);
159
- process.exitCode = 1;
160
- return;
161
- } else {
162
- const detectedMode = await detectRefineMode(cwd, mcpPath);
163
- if (detectedMode === 'ambiguous') {
164
- console.error(`Cannot determine refine mode.
165
- Use --mode build (TypeScript MCP server you own)
166
- --mode runtime (consuming project, any MCP server)`);
167
- process.exitCode = 1;
168
- return;
169
- }
170
- mode = detectedMode;
171
- }
172
-
173
- if (mode === 'build') {
174
- console.log('Refining in build mode (TypeScript MCP)');
175
- } else {
176
- console.log('Refining in runtime mode (overlay)');
177
- }
178
-
179
- const entries = await readMcpConfigFile(mcpPath);
180
- const entry = opts.server
181
- ? entries.find((e) => e.name === opts.server)
182
- : entries.find((e) => !e.disabled);
183
- if (!entry) {
184
- const name = opts.server ? `"${opts.server}"` : 'any enabled server';
185
- throw new Error(`Could not find ${name} in ${mcpPath}`);
186
- }
187
-
188
- if (mode === 'build') {
189
- const sourceGlob = opts.sourceGlob ?? join(cwd, '**', '*.ts');
190
- source = new TypeScriptMcpRefineSource({
191
- transport: entry.transport,
192
- sourceGlob
193
- });
194
- reportInPlace = true;
195
- } else {
196
- source = new McpRefineSource({
197
- overlayPath,
198
- extract: () => extractMcpSkill({ transport: entry.transport })
199
- });
200
- }
201
-
202
- const result = await runRefine(source, model, maxIterations, itemsPerIteration);
203
- reportResult(result, { reportInPlace, overlayPath });
204
- process.exitCode = result.passed ? 0 : 1;
205
- return;
206
- }
207
-
208
- const result = await runRefine(source, model, maxIterations, itemsPerIteration);
209
- reportResult(result, { reportInPlace });
210
- process.exitCode = result.passed ? 0 : 1;
211
- }
212
-
213
- export function buildRefineCommand(): Command {
214
- return new Command('refine')
215
- .description('Autonomously improve a skill via the audit→draft→review loop')
216
- .option('--source <kind>', 'cli | mcp | typedoc (auto-detected if omitted)')
217
- .option('--program <file#export>', 'commander program entry (cli source)')
218
- .option('--mcp <path>', 'path to mcp.json or MCP config file')
219
- .option('--server <name>', 'server name within the config (defaults to first enabled)')
220
- .option('--overlay <path>', 'path to overlay JSON file (runtime mode only)')
221
- .option('--mode <mode>', 'refine mode: build or runtime (auto-detected if omitted)')
222
- .option('--source-glob <glob>', 'glob pattern for TypeScript source files')
223
- .option('--max-iterations <n>', 'iteration cap (default 5)', '5')
224
- .option('--items <n>', 'work items per iteration (default 5)', '5')
225
- .option('--model-client <kind>', 'model backend: api | claude | codex | copilot', 'api')
226
- .option('--model-cli-timeout <ms>', 'per-call timeout for cli model backends (ms)')
227
- .action((opts: RefineCommandOpts) => runRefineCommand(opts));
228
- }
229
-
230
- function runRefine(
231
- source: RefineSource,
232
- model: ModelClient,
233
- maxIterations: number,
234
- itemsPerIteration: number
235
- ): ReturnType<typeof refineSkill> {
236
- return refineSkill({
237
- source,
238
- model,
239
- maxIterations,
240
- itemsPerIteration,
241
- onIteration: (iter) => {
242
- const { grade, total } = iter.estimate;
243
- console.log(
244
- ` Iteration ${iter.iteration}: grade ${grade} (${total}/120), ${iter.fixes.length} fix(es) applied`
245
- );
246
- }
247
- });
248
- }
249
-
250
- function reportResult(
251
- result: Awaited<ReturnType<typeof refineSkill>>,
252
- opts: { reportInPlace: boolean; overlayPath?: string }
253
- ): void {
254
- console.log(`\nDone. Reason: ${result.stoppedReason}`);
255
- console.log(`Final grade: ${result.finalEstimate.grade} (${result.finalEstimate.total}/120)`);
256
- if (opts.reportInPlace) {
257
- console.log(`Source files updated in place.`);
258
- } else if (opts.overlayPath && result.iterations.length > 0) {
259
- console.log(`Overlay: ${opts.overlayPath}`);
260
- }
261
- }
@@ -1,94 +0,0 @@
1
- import { access, readFile } from 'node:fs/promises';
2
- import { homedir } from 'node:os';
3
- import { basename, dirname, join } from 'node:path';
4
-
5
- function isMcpServerDep(dep: string): boolean {
6
- // Match server-implementation packages only. Excludes purely consumer-side
7
- // packages such as @modelcontextprotocol/inspector or @modelcontextprotocol/client-*
8
- // which do not imply the project has editable server source files.
9
- return (
10
- dep === '@modelcontextprotocol/sdk' ||
11
- dep.startsWith('@modelcontextprotocol/server-') ||
12
- dep === 'fastmcp'
13
- );
14
- }
15
-
16
- async function hasMcpSdkDep(cwd: string): Promise<boolean> {
17
- const home = homedir();
18
- let dir = cwd;
19
- while (dir !== home && dir !== dirname(dir)) {
20
- try {
21
- const raw = await readFile(join(dir, 'package.json'), 'utf8');
22
- const pkg = JSON.parse(raw) as Record<string, unknown>;
23
- const deps = {
24
- ...(pkg['dependencies'] as Record<string, string> | undefined),
25
- ...(pkg['devDependencies'] as Record<string, string> | undefined)
26
- };
27
- if (Object.keys(deps).some(isMcpServerDep)) return true;
28
- } catch {
29
- // no package.json or parse error in this dir; keep walking
30
- }
31
- dir = dirname(dir);
32
- }
33
- return false;
34
- }
35
-
36
- async function fileExists(path: string): Promise<boolean> {
37
- try {
38
- await access(path);
39
- return true;
40
- } catch {
41
- return false;
42
- }
43
- }
44
-
45
- async function hasMcpConfig(cwd: string): Promise<boolean> {
46
- const home = homedir();
47
- let dir = cwd;
48
- while (dir !== home && dir !== dirname(dir)) {
49
- if (
50
- (await fileExists(join(dir, 'mcp.json'))) ||
51
- (await fileExists(join(dir, 'claude_desktop_config.json')))
52
- ) {
53
- return true;
54
- }
55
- dir = dirname(dir);
56
- }
57
- return false;
58
- }
59
-
60
- const KNOWN_RUNTIME_BASENAMES = new Set(['mcp.json', 'claude_desktop_config.json']);
61
-
62
- async function isMcpRuntimeConfigFile(path: string): Promise<boolean> {
63
- if (KNOWN_RUNTIME_BASENAMES.has(basename(path))) return true;
64
- try {
65
- const raw = await readFile(path, 'utf8');
66
- const parsed = JSON.parse(raw) as Record<string, unknown>;
67
- return 'mcpServers' in parsed;
68
- } catch {
69
- return false;
70
- }
71
- }
72
-
73
- /**
74
- * Detect refine mode from project context.
75
- *
76
- * @param cwd - directory to inspect for build/runtime signals
77
- * @param mcpConfigPath - optional path to the --mcp config file; if its
78
- * basename is a known runtime config filename, or its contents contain a
79
- * top-level `mcpServers` key, it is treated as an additional runtime signal.
80
- */
81
- export async function detectRefineMode(
82
- cwd: string,
83
- mcpConfigPath?: string
84
- ): Promise<'build' | 'runtime' | 'ambiguous'> {
85
- const [hasBuild, hasRuntimeFromCwd, mcpFileIsRuntime] = await Promise.all([
86
- hasMcpSdkDep(cwd),
87
- hasMcpConfig(cwd),
88
- mcpConfigPath !== undefined ? isMcpRuntimeConfigFile(mcpConfigPath) : Promise.resolve(false)
89
- ]);
90
- const hasRuntime = hasRuntimeFromCwd || mcpFileIsRuntime;
91
- if (hasBuild && !hasRuntime) return 'build';
92
- if (hasRuntime && !hasBuild) return 'runtime';
93
- return 'ambiguous';
94
- }
@@ -1,123 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { readFile } from 'node:fs/promises';
3
- import { join } from 'node:path';
4
- import { loadProgram } from '@skillit/cli';
5
-
6
- export type RefineSourceKind = 'cli' | 'mcp' | 'typedoc';
7
- export type DetectedRefineSource = RefineSourceKind | 'ambiguous' | 'none';
8
-
9
- /**
10
- * Read the union of `dependencies` + `devDependencies` from `<cwd>/package.json`.
11
- * Missing or unreadable `package.json` → `{}` (never throws). Single source of
12
- * truth for dependency reads across detection helpers.
13
- */
14
- async function readDeps(cwd: string): Promise<Record<string, string>> {
15
- try {
16
- const raw = await readFile(join(cwd, 'package.json'), 'utf8');
17
- const pkg = JSON.parse(raw) as Record<string, unknown>;
18
- return {
19
- ...(pkg['dependencies'] as Record<string, string> | undefined),
20
- ...(pkg['devDependencies'] as Record<string, string> | undefined)
21
- };
22
- } catch {
23
- return {};
24
- }
25
- }
26
-
27
- /** Map an installed package name to its refine source kind, if any. */
28
- function packageToSource(dep: string): RefineSourceKind | undefined {
29
- if (dep === '@skillit/cli') return 'cli';
30
- if (dep === '@skillit/mcp') return 'mcp';
31
- if (dep === 'typedoc-plugin-skillit' || dep === '@skillit/typedoc') return 'typedoc';
32
- return undefined;
33
- }
34
-
35
- /** Stable, canonical ordering for reported candidate lists. */
36
- const SOURCE_ORDER: readonly RefineSourceKind[] = ['cli', 'mcp', 'typedoc'];
37
-
38
- /**
39
- * Detect the raw, deduped list of refine source kinds installed in `cwd`'s
40
- * `package.json` (union of `dependencies` + `devDependencies`), in stable
41
- * order (`cli`, `mcp`, `typedoc`).
42
- *
43
- * Missing or unreadable `package.json` → `[]` (never throws). This is the
44
- * single place the package→source mapping lives; {@link detectRefineSource}
45
- * is derived from it.
46
- */
47
- export async function detectInstalledSources(cwd: string): Promise<RefineSourceKind[]> {
48
- const deps = await readDeps(cwd);
49
-
50
- const sources = new Set<RefineSourceKind>();
51
- for (const dep of Object.keys(deps)) {
52
- const source = packageToSource(dep);
53
- if (source) sources.add(source);
54
- }
55
- return SOURCE_ORDER.filter((s) => sources.has(s));
56
- }
57
-
58
- /**
59
- * Collapse an installed-source list into the {@link DetectedRefineSource}
60
- * verdict: empty → `'none'`, single → that source, many → `'ambiguous'`.
61
- */
62
- export function classifyRefineSources(sources: readonly RefineSourceKind[]): DetectedRefineSource {
63
- if (sources.length === 0) return 'none';
64
- if (sources.length === 1) return sources[0]!;
65
- return 'ambiguous';
66
- }
67
-
68
- /**
69
- * Detect the refine source from `@skillit/*` packages installed in `cwd`'s
70
- * `package.json` (union of `dependencies` + `devDependencies`).
71
- *
72
- * - 0 matching packages → `'none'`
73
- * - exactly 1 distinct source → that source
74
- * - more than 1 distinct source → `'ambiguous'`
75
- * - missing or unreadable `package.json` → `'none'` (never throws)
76
- */
77
- export async function detectRefineSource(cwd: string): Promise<DetectedRefineSource> {
78
- return classifyRefineSources(await detectInstalledSources(cwd));
79
- }
80
-
81
- /**
82
- * Detect the nature of the project at `cwd` from its `package.json`:
83
- * - has `commander` or `yargs` dep, OR a `bin` that loads to a commander
84
- * {@link Command} → `'cli'`
85
- * - else has `@modelcontextprotocol/sdk` → `'mcp'`
86
- * - else → `'typedoc'` (safe default for a plain TS library)
87
- *
88
- * Missing or unreadable `package.json` → `'typedoc'` (never throws). The `cli`
89
- * check is evaluated first (deps as a fast path, no import; the loadable-bin
90
- * probe runs only when those deps are absent), then `mcp`, then the `typedoc`
91
- * default.
92
- */
93
- export async function detectProjectNature(cwd: string): Promise<RefineSourceKind> {
94
- const deps = await readDeps(cwd);
95
- if ('commander' in deps || 'yargs' in deps) return 'cli';
96
- if (await hasLoadableProgram(cwd)) return 'cli';
97
- if ('@modelcontextprotocol/sdk' in deps) return 'mcp';
98
- return 'typedoc';
99
- }
100
-
101
- /**
102
- * Attempt to load the consumer's `package.json` `bin` as a commander program
103
- * (via {@link loadProgram}). Returns `true` only if it resolves to a `Command`;
104
- * any failure (no bin, import error, non-Command export) → `false`.
105
- */
106
- async function hasLoadableProgram(cwd: string): Promise<boolean> {
107
- try {
108
- await loadProgram({ cwd });
109
- return true;
110
- } catch {
111
- return false;
112
- }
113
- }
114
-
115
- /**
116
- * Detect the package manager for `cwd` by lockfile presence:
117
- * `pnpm-lock.yaml` → `'pnpm'`, `yarn.lock` → `'yarn'`, else `'npm'`.
118
- */
119
- export function detectPackageManager(cwd: string): 'pnpm' | 'yarn' | 'npm' {
120
- if (existsSync(join(cwd, 'pnpm-lock.yaml'))) return 'pnpm';
121
- if (existsSync(join(cwd, 'yarn.lock'))) return 'yarn';
122
- return 'npm';
123
- }
package/src/index.ts DELETED
@@ -1,3 +0,0 @@
1
- // packages/client/src/index.ts
2
- export { AnthropicModelClient } from './model/anthropic.js';
3
- export { parseReviewVerdict } from './model/anthropic.js';
@@ -1,116 +0,0 @@
1
- // packages/client/src/model/anthropic.ts
2
- import Anthropic from '@anthropic-ai/sdk';
3
- import type { DraftRequest, ReviewRequest, ReviewResult, ModelClient } from '@skillit/core';
4
- import { DRAFTER, REVIEWER, MAX_TOKENS } from './models.js';
5
-
6
- export function parseReviewVerdict(text: string): ReviewResult {
7
- // Prefer {"verdict" anchor to skip stray {braces} in prose.
8
- // Fall back to the first { if the model omits the verdict key.
9
- // Depth-scan for the matching }, skipping { and } inside JSON string values.
10
- const verdictAnchor = text.indexOf('{"verdict"');
11
- const start = verdictAnchor !== -1 ? verdictAnchor : text.indexOf('{');
12
- if (start === -1) return { verdict: 'accepted', feedback: '' };
13
- let depth = 0;
14
- let end = -1;
15
- let inString = false;
16
- let escaped = false;
17
- for (let i = start; i < text.length; i++) {
18
- const ch = text[i]!;
19
- if (escaped) {
20
- escaped = false;
21
- continue;
22
- }
23
- if (ch === '\\' && inString) {
24
- escaped = true;
25
- continue;
26
- }
27
- if (ch === '"') {
28
- inString = !inString;
29
- continue;
30
- }
31
- if (inString) continue;
32
- if (ch === '{') depth++;
33
- else if (ch === '}') {
34
- if (--depth === 0) {
35
- end = i;
36
- break;
37
- }
38
- }
39
- }
40
- if (end === -1) return { verdict: 'accepted', feedback: '' };
41
- try {
42
- const parsed = JSON.parse(text.slice(start, end + 1)) as Partial<ReviewResult>;
43
- return {
44
- verdict: parsed.verdict === 'revise' ? 'revise' : 'accepted',
45
- feedback: parsed.feedback ?? ''
46
- };
47
- } catch {
48
- return { verdict: 'accepted', feedback: '' };
49
- }
50
- }
51
-
52
- export function buildDraftPrompt(req: DraftRequest): string {
53
- const parts = [
54
- `You are improving skill annotations for "${req.skill.name}".`,
55
- `Tool: ${req.toolName}`,
56
- `Tag to fill: @${req.tag}`,
57
- `Guidance: ${req.suggestion}`,
58
- req.currentValue ? `Current value:\n${req.currentValue}` : 'No current value.',
59
- 'Write only the annotation content — no code fences, no extra commentary.'
60
- ];
61
- if (req.guidance) {
62
- parts.push(`Conventions (follow these):\n${req.guidance}`);
63
- }
64
- return parts.join('\n\n');
65
- }
66
-
67
- export function buildReviewPrompt(req: ReviewRequest): string {
68
- const parts = [
69
- `You are reviewing a skill annotation draft for "${req.skill.name}".`,
70
- `Tool: ${req.toolName}, Tag: @${req.tag}`,
71
- `Guidance the drafter was given: ${req.suggestion}`,
72
- `Draft:\n${req.draft}`
73
- ];
74
- if (req.guidance) {
75
- parts.push(`Conventions (follow these):\n${req.guidance}`);
76
- }
77
- parts.push(
78
- 'Respond with JSON only: {"verdict":"accepted"|"revise","feedback":"..."}.',
79
- 'Accept if the draft meaningfully addresses the guidance. Revise if it is vague or incorrect.'
80
- );
81
- return parts.join('\n\n');
82
- }
83
-
84
- export class AnthropicModelClient implements ModelClient {
85
- private client = new Anthropic();
86
-
87
- async draft(req: DraftRequest): Promise<string> {
88
- const prompt = buildDraftPrompt(req);
89
-
90
- const msg = await this.client.messages.create({
91
- model: DRAFTER,
92
- max_tokens: MAX_TOKENS,
93
- messages: [{ role: 'user', content: prompt }]
94
- });
95
- const block = msg.content[0];
96
- if (!block || block.type !== 'text') {
97
- throw new Error(`Unexpected response from ${DRAFTER}: no text block`);
98
- }
99
- return block.text.trim();
100
- }
101
-
102
- async review(req: ReviewRequest): Promise<ReviewResult> {
103
- const prompt = buildReviewPrompt(req);
104
-
105
- const msg = await this.client.messages.create({
106
- model: REVIEWER,
107
- max_tokens: MAX_TOKENS,
108
- messages: [{ role: 'user', content: prompt }]
109
- });
110
- const block = msg.content[0];
111
- if (!block || block.type !== 'text') {
112
- throw new Error(`Unexpected response from ${REVIEWER}: no text block`);
113
- }
114
- return parseReviewVerdict(block.text);
115
- }
116
- }