@skillit/client 0.2.0

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/CHANGELOG.md +32 -0
  2. package/LICENSE +21 -0
  3. package/dist/bin.d.ts +3 -0
  4. package/dist/bin.d.ts.map +1 -0
  5. package/dist/bin.js +16 -0
  6. package/dist/bin.js.map +1 -0
  7. package/dist/commands/init.d.ts +33 -0
  8. package/dist/commands/init.d.ts.map +1 -0
  9. package/dist/commands/init.js +152 -0
  10. package/dist/commands/init.js.map +1 -0
  11. package/dist/commands/refine.d.ts +49 -0
  12. package/dist/commands/refine.d.ts.map +1 -0
  13. package/dist/commands/refine.js +201 -0
  14. package/dist/commands/refine.js.map +1 -0
  15. package/dist/detect-mode.d.ts +10 -0
  16. package/dist/detect-mode.d.ts.map +1 -0
  17. package/dist/detect-mode.js +88 -0
  18. package/dist/detect-mode.js.map +1 -0
  19. package/dist/detect-source.d.ts +46 -0
  20. package/dist/detect-source.d.ts.map +1 -0
  21. package/dist/detect-source.js +124 -0
  22. package/dist/detect-source.js.map +1 -0
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +4 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/model/anthropic.d.ts +10 -0
  28. package/dist/model/anthropic.d.ts.map +1 -0
  29. package/dist/model/anthropic.js +110 -0
  30. package/dist/model/anthropic.js.map +1 -0
  31. package/dist/model/cli/adapters.d.ts +26 -0
  32. package/dist/model/cli/adapters.d.ts.map +1 -0
  33. package/dist/model/cli/adapters.js +133 -0
  34. package/dist/model/cli/adapters.js.map +1 -0
  35. package/dist/model/cli/cli-client.d.ts +24 -0
  36. package/dist/model/cli/cli-client.d.ts.map +1 -0
  37. package/dist/model/cli/cli-client.js +36 -0
  38. package/dist/model/cli/cli-client.js.map +1 -0
  39. package/dist/model/cli/run.d.ts +17 -0
  40. package/dist/model/cli/run.d.ts.map +1 -0
  41. package/dist/model/cli/run.js +72 -0
  42. package/dist/model/cli/run.js.map +1 -0
  43. package/dist/model/model-client-factory.d.ts +16 -0
  44. package/dist/model/model-client-factory.d.ts.map +1 -0
  45. package/dist/model/model-client-factory.js +44 -0
  46. package/dist/model/model-client-factory.js.map +1 -0
  47. package/dist/model/models.d.ts +4 -0
  48. package/dist/model/models.d.ts.map +1 -0
  49. package/dist/model/models.js +8 -0
  50. package/dist/model/models.js.map +1 -0
  51. package/package.json +31 -0
  52. package/scripts/gen-refine-cli-skill.mjs +48 -0
  53. package/src/__tests__/anthropic-model.test.ts +45 -0
  54. package/src/__tests__/anthropic-prompt.test.ts +102 -0
  55. package/src/__tests__/cli-adapters.test.ts +119 -0
  56. package/src/__tests__/cli-client.test.ts +58 -0
  57. package/src/__tests__/cli-run.test.ts +52 -0
  58. package/src/__tests__/detect-mode.test.ts +114 -0
  59. package/src/__tests__/detect-source.test.ts +158 -0
  60. package/src/__tests__/fixtures/bin-with-program.mjs +6 -0
  61. package/src/__tests__/init.test.ts +218 -0
  62. package/src/__tests__/model-client-factory.test.ts +28 -0
  63. package/src/__tests__/refine-resolve.test.ts +72 -0
  64. package/src/bin.ts +18 -0
  65. package/src/commands/init.ts +207 -0
  66. package/src/commands/refine.ts +261 -0
  67. package/src/detect-mode.ts +94 -0
  68. package/src/detect-source.ts +123 -0
  69. package/src/index.ts +3 -0
  70. package/src/model/anthropic.ts +116 -0
  71. package/src/model/cli/adapters.ts +178 -0
  72. package/src/model/cli/cli-client.ts +52 -0
  73. package/src/model/cli/run.ts +83 -0
  74. package/src/model/model-client-factory.ts +62 -0
  75. package/src/model/models.ts +7 -0
  76. package/tsconfig.build.json +9 -0
  77. package/tsconfig.json +4 -0
@@ -0,0 +1,88 @@
1
+ import { access, readFile } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { basename, dirname, join } from 'node:path';
4
+ function isMcpServerDep(dep) {
5
+ // Match server-implementation packages only. Excludes purely consumer-side
6
+ // packages such as @modelcontextprotocol/inspector or @modelcontextprotocol/client-*
7
+ // which do not imply the project has editable server source files.
8
+ return (dep === '@modelcontextprotocol/sdk' ||
9
+ dep.startsWith('@modelcontextprotocol/server-') ||
10
+ dep === 'fastmcp');
11
+ }
12
+ async function hasMcpSdkDep(cwd) {
13
+ const home = homedir();
14
+ let dir = cwd;
15
+ while (dir !== home && dir !== dirname(dir)) {
16
+ try {
17
+ const raw = await readFile(join(dir, 'package.json'), 'utf8');
18
+ const pkg = JSON.parse(raw);
19
+ const deps = {
20
+ ...pkg['dependencies'],
21
+ ...pkg['devDependencies']
22
+ };
23
+ if (Object.keys(deps).some(isMcpServerDep))
24
+ return true;
25
+ }
26
+ catch {
27
+ // no package.json or parse error in this dir; keep walking
28
+ }
29
+ dir = dirname(dir);
30
+ }
31
+ return false;
32
+ }
33
+ async function fileExists(path) {
34
+ try {
35
+ await access(path);
36
+ return true;
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ }
42
+ async function hasMcpConfig(cwd) {
43
+ const home = homedir();
44
+ let dir = cwd;
45
+ while (dir !== home && dir !== dirname(dir)) {
46
+ if ((await fileExists(join(dir, 'mcp.json'))) ||
47
+ (await fileExists(join(dir, 'claude_desktop_config.json')))) {
48
+ return true;
49
+ }
50
+ dir = dirname(dir);
51
+ }
52
+ return false;
53
+ }
54
+ const KNOWN_RUNTIME_BASENAMES = new Set(['mcp.json', 'claude_desktop_config.json']);
55
+ async function isMcpRuntimeConfigFile(path) {
56
+ if (KNOWN_RUNTIME_BASENAMES.has(basename(path)))
57
+ return true;
58
+ try {
59
+ const raw = await readFile(path, 'utf8');
60
+ const parsed = JSON.parse(raw);
61
+ return 'mcpServers' in parsed;
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ }
67
+ /**
68
+ * Detect refine mode from project context.
69
+ *
70
+ * @param cwd - directory to inspect for build/runtime signals
71
+ * @param mcpConfigPath - optional path to the --mcp config file; if its
72
+ * basename is a known runtime config filename, or its contents contain a
73
+ * top-level `mcpServers` key, it is treated as an additional runtime signal.
74
+ */
75
+ export async function detectRefineMode(cwd, mcpConfigPath) {
76
+ const [hasBuild, hasRuntimeFromCwd, mcpFileIsRuntime] = await Promise.all([
77
+ hasMcpSdkDep(cwd),
78
+ hasMcpConfig(cwd),
79
+ mcpConfigPath !== undefined ? isMcpRuntimeConfigFile(mcpConfigPath) : Promise.resolve(false)
80
+ ]);
81
+ const hasRuntime = hasRuntimeFromCwd || mcpFileIsRuntime;
82
+ if (hasBuild && !hasRuntime)
83
+ return 'build';
84
+ if (hasRuntime && !hasBuild)
85
+ return 'runtime';
86
+ return 'ambiguous';
87
+ }
88
+ //# sourceMappingURL=detect-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-mode.js","sourceRoot":"","sources":["../src/detect-mode.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ export type RefineSourceKind = 'cli' | 'mcp' | 'typedoc';
2
+ export type DetectedRefineSource = RefineSourceKind | 'ambiguous' | 'none';
3
+ /**
4
+ * Detect the raw, deduped list of refine source kinds installed in `cwd`'s
5
+ * `package.json` (union of `dependencies` + `devDependencies`), in stable
6
+ * order (`cli`, `mcp`, `typedoc`).
7
+ *
8
+ * Missing or unreadable `package.json` → `[]` (never throws). This is the
9
+ * single place the package→source mapping lives; {@link detectRefineSource}
10
+ * is derived from it.
11
+ */
12
+ export declare function detectInstalledSources(cwd: string): Promise<RefineSourceKind[]>;
13
+ /**
14
+ * Collapse an installed-source list into the {@link DetectedRefineSource}
15
+ * verdict: empty → `'none'`, single → that source, many → `'ambiguous'`.
16
+ */
17
+ export declare function classifyRefineSources(sources: readonly RefineSourceKind[]): DetectedRefineSource;
18
+ /**
19
+ * Detect the refine source from `@skillit/*` packages installed in `cwd`'s
20
+ * `package.json` (union of `dependencies` + `devDependencies`).
21
+ *
22
+ * - 0 matching packages → `'none'`
23
+ * - exactly 1 distinct source → that source
24
+ * - more than 1 distinct source → `'ambiguous'`
25
+ * - missing or unreadable `package.json` → `'none'` (never throws)
26
+ */
27
+ export declare function detectRefineSource(cwd: string): Promise<DetectedRefineSource>;
28
+ /**
29
+ * Detect the nature of the project at `cwd` from its `package.json`:
30
+ * - has `commander` or `yargs` dep, OR a `bin` that loads to a commander
31
+ * {@link Command} → `'cli'`
32
+ * - else has `@modelcontextprotocol/sdk` → `'mcp'`
33
+ * - else → `'typedoc'` (safe default for a plain TS library)
34
+ *
35
+ * Missing or unreadable `package.json` → `'typedoc'` (never throws). The `cli`
36
+ * check is evaluated first (deps as a fast path, no import; the loadable-bin
37
+ * probe runs only when those deps are absent), then `mcp`, then the `typedoc`
38
+ * default.
39
+ */
40
+ export declare function detectProjectNature(cwd: string): Promise<RefineSourceKind>;
41
+ /**
42
+ * Detect the package manager for `cwd` by lockfile presence:
43
+ * `pnpm-lock.yaml` → `'pnpm'`, `yarn.lock` → `'yarn'`, else `'npm'`.
44
+ */
45
+ export declare function detectPackageManager(cwd: string): 'pnpm' | 'yarn' | 'npm';
46
+ //# sourceMappingURL=detect-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-source.d.ts","sourceRoot":"","sources":["../src/detect-source.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,KAAK,GAAG,SAAS,CAAC;AACzD,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG,WAAW,GAAG,MAAM,CAAC;AA+B3E;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CASrF;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,GAAG,oBAAoB,CAIhG;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAEnF;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAMhF;AAgBD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAIzE"}
@@ -0,0 +1,124 @@
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
+ * Read the union of `dependencies` + `devDependencies` from `<cwd>/package.json`.
7
+ * Missing or unreadable `package.json` → `{}` (never throws). Single source of
8
+ * truth for dependency reads across detection helpers.
9
+ */
10
+ async function readDeps(cwd) {
11
+ try {
12
+ const raw = await readFile(join(cwd, 'package.json'), 'utf8');
13
+ const pkg = JSON.parse(raw);
14
+ return {
15
+ ...pkg['dependencies'],
16
+ ...pkg['devDependencies']
17
+ };
18
+ }
19
+ catch {
20
+ return {};
21
+ }
22
+ }
23
+ /** Map an installed package name to its refine source kind, if any. */
24
+ function packageToSource(dep) {
25
+ if (dep === '@skillit/cli')
26
+ return 'cli';
27
+ if (dep === '@skillit/mcp')
28
+ return 'mcp';
29
+ if (dep === 'typedoc-plugin-skillit' || dep === '@skillit/typedoc')
30
+ return 'typedoc';
31
+ return undefined;
32
+ }
33
+ /** Stable, canonical ordering for reported candidate lists. */
34
+ const SOURCE_ORDER = ['cli', 'mcp', 'typedoc'];
35
+ /**
36
+ * Detect the raw, deduped list of refine source kinds installed in `cwd`'s
37
+ * `package.json` (union of `dependencies` + `devDependencies`), in stable
38
+ * order (`cli`, `mcp`, `typedoc`).
39
+ *
40
+ * Missing or unreadable `package.json` → `[]` (never throws). This is the
41
+ * single place the package→source mapping lives; {@link detectRefineSource}
42
+ * is derived from it.
43
+ */
44
+ export async function detectInstalledSources(cwd) {
45
+ const deps = await readDeps(cwd);
46
+ const sources = new Set();
47
+ for (const dep of Object.keys(deps)) {
48
+ const source = packageToSource(dep);
49
+ if (source)
50
+ sources.add(source);
51
+ }
52
+ return SOURCE_ORDER.filter((s) => sources.has(s));
53
+ }
54
+ /**
55
+ * Collapse an installed-source list into the {@link DetectedRefineSource}
56
+ * verdict: empty → `'none'`, single → that source, many → `'ambiguous'`.
57
+ */
58
+ export function classifyRefineSources(sources) {
59
+ if (sources.length === 0)
60
+ return 'none';
61
+ if (sources.length === 1)
62
+ return sources[0];
63
+ return 'ambiguous';
64
+ }
65
+ /**
66
+ * Detect the refine source from `@skillit/*` packages installed in `cwd`'s
67
+ * `package.json` (union of `dependencies` + `devDependencies`).
68
+ *
69
+ * - 0 matching packages → `'none'`
70
+ * - exactly 1 distinct source → that source
71
+ * - more than 1 distinct source → `'ambiguous'`
72
+ * - missing or unreadable `package.json` → `'none'` (never throws)
73
+ */
74
+ export async function detectRefineSource(cwd) {
75
+ return classifyRefineSources(await detectInstalledSources(cwd));
76
+ }
77
+ /**
78
+ * Detect the nature of the project at `cwd` from its `package.json`:
79
+ * - has `commander` or `yargs` dep, OR a `bin` that loads to a commander
80
+ * {@link Command} → `'cli'`
81
+ * - else has `@modelcontextprotocol/sdk` → `'mcp'`
82
+ * - else → `'typedoc'` (safe default for a plain TS library)
83
+ *
84
+ * Missing or unreadable `package.json` → `'typedoc'` (never throws). The `cli`
85
+ * check is evaluated first (deps as a fast path, no import; the loadable-bin
86
+ * probe runs only when those deps are absent), then `mcp`, then the `typedoc`
87
+ * default.
88
+ */
89
+ export async function detectProjectNature(cwd) {
90
+ const deps = await readDeps(cwd);
91
+ if ('commander' in deps || 'yargs' in deps)
92
+ return 'cli';
93
+ if (await hasLoadableProgram(cwd))
94
+ return 'cli';
95
+ if ('@modelcontextprotocol/sdk' in deps)
96
+ return 'mcp';
97
+ return 'typedoc';
98
+ }
99
+ /**
100
+ * Attempt to load the consumer's `package.json` `bin` as a commander program
101
+ * (via {@link loadProgram}). Returns `true` only if it resolves to a `Command`;
102
+ * any failure (no bin, import error, non-Command export) → `false`.
103
+ */
104
+ async function hasLoadableProgram(cwd) {
105
+ try {
106
+ await loadProgram({ cwd });
107
+ return true;
108
+ }
109
+ catch {
110
+ return false;
111
+ }
112
+ }
113
+ /**
114
+ * Detect the package manager for `cwd` by lockfile presence:
115
+ * `pnpm-lock.yaml` → `'pnpm'`, `yarn.lock` → `'yarn'`, else `'npm'`.
116
+ */
117
+ export function detectPackageManager(cwd) {
118
+ if (existsSync(join(cwd, 'pnpm-lock.yaml')))
119
+ return 'pnpm';
120
+ if (existsSync(join(cwd, 'yarn.lock')))
121
+ return 'yarn';
122
+ return 'npm';
123
+ }
124
+ //# sourceMappingURL=detect-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-source.js","sourceRoot":"","sources":["../src/detect-source.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { AnthropicModelClient } from './model/anthropic.js';
2
+ export { parseReviewVerdict } from './model/anthropic.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ // packages/client/src/index.ts
2
+ export { AnthropicModelClient } from './model/anthropic.js';
3
+ export { parseReviewVerdict } from './model/anthropic.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { DraftRequest, ReviewRequest, ReviewResult, ModelClient } from '@skillit/core';
2
+ export declare function parseReviewVerdict(text: string): ReviewResult;
3
+ export declare function buildDraftPrompt(req: DraftRequest): string;
4
+ export declare function buildReviewPrompt(req: ReviewRequest): string;
5
+ export declare class AnthropicModelClient implements ModelClient {
6
+ private client;
7
+ draft(req: DraftRequest): Promise<string>;
8
+ review(req: ReviewRequest): Promise<ReviewResult>;
9
+ }
10
+ //# sourceMappingURL=anthropic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/model/anthropic.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5F,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CA4C7D;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAa1D;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAe5D;AAED,qBAAa,oBAAqB,YAAW,WAAW;IACtD,OAAO,CAAC,MAAM,CAAmB;IAE3B,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAa9C;IAEK,MAAM,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAatD;CACF"}
@@ -0,0 +1,110 @@
1
+ // packages/client/src/model/anthropic.ts
2
+ import Anthropic from '@anthropic-ai/sdk';
3
+ import { DRAFTER, REVIEWER, MAX_TOKENS } from './models.js';
4
+ export function parseReviewVerdict(text) {
5
+ // Prefer {"verdict" anchor to skip stray {braces} in prose.
6
+ // Fall back to the first { if the model omits the verdict key.
7
+ // Depth-scan for the matching }, skipping { and } inside JSON string values.
8
+ const verdictAnchor = text.indexOf('{"verdict"');
9
+ const start = verdictAnchor !== -1 ? verdictAnchor : text.indexOf('{');
10
+ if (start === -1)
11
+ return { verdict: 'accepted', feedback: '' };
12
+ let depth = 0;
13
+ let end = -1;
14
+ let inString = false;
15
+ let escaped = false;
16
+ for (let i = start; i < text.length; i++) {
17
+ const ch = text[i];
18
+ if (escaped) {
19
+ escaped = false;
20
+ continue;
21
+ }
22
+ if (ch === '\\' && inString) {
23
+ escaped = true;
24
+ continue;
25
+ }
26
+ if (ch === '"') {
27
+ inString = !inString;
28
+ continue;
29
+ }
30
+ if (inString)
31
+ continue;
32
+ if (ch === '{')
33
+ depth++;
34
+ else if (ch === '}') {
35
+ if (--depth === 0) {
36
+ end = i;
37
+ break;
38
+ }
39
+ }
40
+ }
41
+ if (end === -1)
42
+ return { verdict: 'accepted', feedback: '' };
43
+ try {
44
+ const parsed = JSON.parse(text.slice(start, end + 1));
45
+ return {
46
+ verdict: parsed.verdict === 'revise' ? 'revise' : 'accepted',
47
+ feedback: parsed.feedback ?? ''
48
+ };
49
+ }
50
+ catch {
51
+ return { verdict: 'accepted', feedback: '' };
52
+ }
53
+ }
54
+ export function buildDraftPrompt(req) {
55
+ const parts = [
56
+ `You are improving skill annotations for "${req.skill.name}".`,
57
+ `Tool: ${req.toolName}`,
58
+ `Tag to fill: @${req.tag}`,
59
+ `Guidance: ${req.suggestion}`,
60
+ req.currentValue ? `Current value:\n${req.currentValue}` : 'No current value.',
61
+ 'Write only the annotation content — no code fences, no extra commentary.'
62
+ ];
63
+ if (req.guidance) {
64
+ parts.push(`Conventions (follow these):\n${req.guidance}`);
65
+ }
66
+ return parts.join('\n\n');
67
+ }
68
+ export function buildReviewPrompt(req) {
69
+ const parts = [
70
+ `You are reviewing a skill annotation draft for "${req.skill.name}".`,
71
+ `Tool: ${req.toolName}, Tag: @${req.tag}`,
72
+ `Guidance the drafter was given: ${req.suggestion}`,
73
+ `Draft:\n${req.draft}`
74
+ ];
75
+ if (req.guidance) {
76
+ parts.push(`Conventions (follow these):\n${req.guidance}`);
77
+ }
78
+ parts.push('Respond with JSON only: {"verdict":"accepted"|"revise","feedback":"..."}.', 'Accept if the draft meaningfully addresses the guidance. Revise if it is vague or incorrect.');
79
+ return parts.join('\n\n');
80
+ }
81
+ export class AnthropicModelClient {
82
+ client = new Anthropic();
83
+ async draft(req) {
84
+ const prompt = buildDraftPrompt(req);
85
+ const msg = await this.client.messages.create({
86
+ model: DRAFTER,
87
+ max_tokens: MAX_TOKENS,
88
+ messages: [{ role: 'user', content: prompt }]
89
+ });
90
+ const block = msg.content[0];
91
+ if (!block || block.type !== 'text') {
92
+ throw new Error(`Unexpected response from ${DRAFTER}: no text block`);
93
+ }
94
+ return block.text.trim();
95
+ }
96
+ async review(req) {
97
+ const prompt = buildReviewPrompt(req);
98
+ const msg = await this.client.messages.create({
99
+ model: REVIEWER,
100
+ max_tokens: MAX_TOKENS,
101
+ messages: [{ role: 'user', content: prompt }]
102
+ });
103
+ const block = msg.content[0];
104
+ if (!block || block.type !== 'text') {
105
+ throw new Error(`Unexpected response from ${REVIEWER}: no text block`);
106
+ }
107
+ return parseReviewVerdict(block.text);
108
+ }
109
+ }
110
+ //# sourceMappingURL=anthropic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../src/model/anthropic.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAE1C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE5D,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,4DAA4D;IAC5D,+DAA+D;IAC/D,6EAA6E;IAC7E,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,aAAa,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACvE,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC/D,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC;IACb,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACpB,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,GAAG,KAAK,CAAC;YAChB,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC5B,OAAO,GAAG,IAAI,CAAC;YACf,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,QAAQ,GAAG,CAAC,QAAQ,CAAC;YACrB,SAAS;QACX,CAAC;QACD,IAAI,QAAQ;YAAE,SAAS;QACvB,IAAI,EAAE,KAAK,GAAG;YAAE,KAAK,EAAE,CAAC;aACnB,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACpB,IAAI,EAAE,KAAK,KAAK,CAAC,EAAE,CAAC;gBAClB,GAAG,GAAG,CAAC,CAAC;gBACR,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,GAAG,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC7D,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC,CAAC,CAA0B,CAAC;QAC/E,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU;YAC5D,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,EAAE;SAChC,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC/C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAiB;IAChD,MAAM,KAAK,GAAG;QACZ,4CAA4C,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI;QAC9D,SAAS,GAAG,CAAC,QAAQ,EAAE;QACvB,iBAAiB,GAAG,CAAC,GAAG,EAAE;QAC1B,aAAa,GAAG,CAAC,UAAU,EAAE;QAC7B,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,mBAAmB,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,mBAAmB;QAC9E,0EAA0E;KAC3E,CAAC;IACF,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;QACjB,KAAK,CAAC,IAAI,CAAC,gCAAgC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,GAAkB;IAClD,MAAM,KAAK,GAAG;QACZ,mDAAmD,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI;QACrE,SAAS,GAAG,CAAC,QAAQ,WAAW,GAAG,CAAC,GAAG,EAAE;QACzC,mCAAmC,GAAG,CAAC,UAAU,EAAE;QACnD,WAAW,GAAG,CAAC,KAAK,EAAE;KACvB,CAAC;IACF,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;QACjB,KAAK,CAAC,IAAI,CAAC,gCAAgC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC7D,CAAC;IACD,KAAK,CAAC,IAAI,CACR,2EAA2E,EAC3E,8FAA8F,CAC/F,CAAC;IACF,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,OAAO,oBAAoB;IACvB,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;IAEjC,KAAK,CAAC,KAAK,CAAC,GAAiB;QAC3B,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;QAErC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC5C,KAAK,EAAE,OAAO;YACd,UAAU,EAAE,UAAU;YACtB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;SAC9C,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,4BAA4B,OAAO,iBAAiB,CAAC,CAAC;QACxE,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAkB;QAC7B,MAAM,MAAM,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;QAEtC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC5C,KAAK,EAAE,QAAQ;YACf,UAAU,EAAE,UAAU;YACtB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;SAC9C,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,iBAAiB,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC;CACF"}
@@ -0,0 +1,26 @@
1
+ export type CliModelClientKind = 'claude' | 'codex' | 'copilot';
2
+ export type ModelRole = 'draft' | 'review';
3
+ export interface CliInvocation {
4
+ /** Executable name. */
5
+ cmd: string;
6
+ /** Arguments (no shell). */
7
+ args: string[];
8
+ /** Prompt to pipe to stdin, if this CLI takes the prompt that way. */
9
+ input?: string;
10
+ }
11
+ export interface CliAdapter {
12
+ readonly name: CliModelClientKind;
13
+ /** How to invoke the CLI for a given role with a given prompt. */
14
+ invocation(role: ModelRole, prompt: string): CliInvocation;
15
+ /**
16
+ * Extract the model's answer text from the CLI's stdout. An empty-string
17
+ * answer is valid and returned as-is; throws only when no answer message is
18
+ * present or the CLI reported an in-band error.
19
+ */
20
+ extractResult(stdout: string): string;
21
+ }
22
+ export declare const claudeAdapter: CliAdapter;
23
+ export declare const codexAdapter: CliAdapter;
24
+ export declare const copilotAdapter: CliAdapter;
25
+ export declare function adapterFor(kind: CliModelClientKind): CliAdapter;
26
+ //# sourceMappingURL=adapters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../../../src/model/cli/adapters.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAChE,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE3C,MAAM,WAAW,aAAa;IAC5B,uBAAuB;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,4BAA4B;IAC5B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,kEAAkE;IAClE,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC;IAC3D;;;;OAIG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACvC;AA0DD,eAAO,MAAM,aAAa,EAAE,UAyB3B,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,UA2B1B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,UA4B5B,CAAC;AAQF,wBAAgB,UAAU,CAAC,IAAI,EAAE,kBAAkB,GAAG,UAAU,CAE/D"}
@@ -0,0 +1,133 @@
1
+ // packages/client/src/model/cli/adapters.ts
2
+ import { DRAFTER, REVIEWER } from '../models.js';
3
+ /**
4
+ * Walk a JSONL stream (one JSON object per line), skip non-`{` log/noise lines
5
+ * and unparseable lines, and return the LAST value `extract` accepts. Returning
6
+ * `''` from `extract` is a valid match (the empty answer wins last) — only
7
+ * `undefined` means "not a match", so the adapter never editorializes empty
8
+ * model output.
9
+ */
10
+ function lastJsonlMatch(stdout, extract) {
11
+ let last;
12
+ for (const line of stdout.split('\n')) {
13
+ const trimmed = line.trim();
14
+ if (!trimmed.startsWith('{'))
15
+ continue;
16
+ let evt;
17
+ try {
18
+ evt = JSON.parse(trimmed);
19
+ }
20
+ catch {
21
+ continue;
22
+ }
23
+ const got = extract(evt);
24
+ if (got !== undefined)
25
+ last = got;
26
+ }
27
+ return last;
28
+ }
29
+ /** Best-effort scan for a JSONL failure signal, used to enrich no-match errors. */
30
+ function jsonlFailureSignal(stdout, detect) {
31
+ let signal;
32
+ for (const line of stdout.split('\n')) {
33
+ const trimmed = line.trim();
34
+ if (!trimmed.startsWith('{'))
35
+ continue;
36
+ let evt;
37
+ try {
38
+ evt = JSON.parse(trimmed);
39
+ }
40
+ catch {
41
+ continue;
42
+ }
43
+ const got = detect(evt);
44
+ if (got !== undefined)
45
+ signal = got;
46
+ }
47
+ return signal;
48
+ }
49
+ export const claudeAdapter = {
50
+ name: 'claude',
51
+ invocation(role, prompt) {
52
+ const model = role === 'review' ? REVIEWER : DRAFTER;
53
+ return {
54
+ cmd: 'claude',
55
+ args: ['-p', '--output-format', 'json', '--model', model],
56
+ input: prompt
57
+ };
58
+ },
59
+ extractResult(stdout) {
60
+ let parsed;
61
+ try {
62
+ parsed = JSON.parse(stdout);
63
+ }
64
+ catch {
65
+ throw new Error(`claude: could not parse JSON output: ${stdout.slice(0, 200)}`);
66
+ }
67
+ if (parsed.is_error) {
68
+ throw new Error(`claude reported an error: ${parsed.result ?? '(no result)'}`);
69
+ }
70
+ if (typeof parsed.result !== 'string') {
71
+ throw new Error(`claude: no result field in output: ${stdout.slice(0, 200)}`);
72
+ }
73
+ return parsed.result;
74
+ }
75
+ };
76
+ export const codexAdapter = {
77
+ name: 'codex',
78
+ invocation(_role, prompt) {
79
+ // codex uses its default model (no per-role mapping); prompt on stdin.
80
+ return { cmd: 'codex', args: ['exec', '--json'], input: prompt };
81
+ },
82
+ extractResult(stdout) {
83
+ const last = lastJsonlMatch(stdout, (evt) => evt.type === 'item.completed' &&
84
+ evt.item?.type === 'agent_message' &&
85
+ typeof evt.item.text === 'string'
86
+ ? evt.item.text
87
+ : undefined);
88
+ if (last === undefined) {
89
+ const failure = jsonlFailureSignal(stdout, (evt) => evt.type === 'turn.failed' || (evt.type === 'item.completed' && evt.item?.type === 'error')
90
+ ? evt.type === 'turn.failed'
91
+ ? 'turn.failed'
92
+ : 'item.error'
93
+ : undefined);
94
+ const detail = failure !== undefined ? ` (failure signal: ${failure})` : '';
95
+ throw new Error(`codex: no agent_message in output${detail}: ${stdout.slice(0, 200)}`);
96
+ }
97
+ return last;
98
+ }
99
+ };
100
+ export const copilotAdapter = {
101
+ name: 'copilot',
102
+ invocation(_role, prompt) {
103
+ // copilot uses its default model; prompt piped via stdin (not `-p <text>`)
104
+ // so untrusted prompt content never reaches argv — keeps the Windows
105
+ // shell-launch path injection-safe (only static flags are args).
106
+ return { cmd: 'copilot', args: ['--output-format', 'json', '--no-color'], input: prompt };
107
+ },
108
+ extractResult(stdout) {
109
+ // copilot emits JSONL; the answer is the last `assistant.message` event's
110
+ // data.content (skip `assistant.message_delta` streaming events + the final
111
+ // `result` event). Verified live 2026-06-03.
112
+ const last = lastJsonlMatch(stdout, (evt) => evt.type === 'assistant.message' && typeof evt.data?.content === 'string'
113
+ ? evt.data.content
114
+ : undefined);
115
+ if (last === undefined) {
116
+ const failure = jsonlFailureSignal(stdout, (evt) => evt.type === 'result' && typeof evt.exitCode === 'number' && evt.exitCode !== 0
117
+ ? `exitCode ${evt.exitCode}`
118
+ : undefined);
119
+ const detail = failure !== undefined ? ` (failure signal: ${failure})` : '';
120
+ throw new Error(`copilot: no assistant.message in output${detail}: ${stdout.slice(0, 200)}`);
121
+ }
122
+ return last;
123
+ }
124
+ };
125
+ const ADAPTERS = {
126
+ claude: claudeAdapter,
127
+ codex: codexAdapter,
128
+ copilot: copilotAdapter
129
+ };
130
+ export function adapterFor(kind) {
131
+ return ADAPTERS[kind];
132
+ }
133
+ //# sourceMappingURL=adapters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapters.js","sourceRoot":"","sources":["../../../src/model/cli/adapters.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { DraftRequest, ReviewRequest, ReviewResult, ModelClient } from '@skillit/core';
2
+ import { type RunCliOptions } from './run.js';
3
+ import type { CliAdapter } from './adapters.js';
4
+ /** Injectable process runner (defaults to the real `runCli`). */
5
+ export type CliRunner = (opts: RunCliOptions) => Promise<string>;
6
+ export interface CliModelClientOptions {
7
+ runner?: CliRunner;
8
+ timeoutMs?: number;
9
+ }
10
+ /**
11
+ * A {@link ModelClient} that drives an agent CLI (claude/codex/copilot) instead
12
+ * of the Anthropic API. Reuses the shared prompt builders and verdict parser;
13
+ * only the transport differs.
14
+ */
15
+ export declare class CliModelClient implements ModelClient {
16
+ private readonly adapter;
17
+ private readonly runner;
18
+ private readonly timeoutMs?;
19
+ constructor(adapter: CliAdapter, options?: CliModelClientOptions);
20
+ private run;
21
+ draft(req: DraftRequest): Promise<string>;
22
+ review(req: ReviewRequest): Promise<ReviewResult>;
23
+ }
24
+ //# sourceMappingURL=cli-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-client.d.ts","sourceRoot":"","sources":["../../../src/model/cli/cli-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5F,OAAO,EAAU,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,iEAAiE;AACjE,MAAM,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAEjE,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,qBAAa,cAAe,YAAW,WAAW;IAK9C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJ1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAS;IAEpC,YACmB,OAAO,EAAE,UAAU,EACpC,OAAO,GAAE,qBAA0B,EAIpC;YAEa,GAAG;IAWX,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAG9C;IAEK,MAAM,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAGtD;CACF"}