@shrkcrft/ai 0.1.0-alpha.12 → 0.1.0-alpha.13
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/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/llm-hints.d.ts +36 -0
- package/dist/llm-hints.d.ts.map +1 -0
- package/dist/llm-hints.js +92 -0
- package/dist/llm-recommendations.d.ts +72 -0
- package/dist/llm-recommendations.d.ts.map +1 -0
- package/dist/llm-recommendations.js +188 -0
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -8,4 +8,6 @@ export * from './ollama/ollama-provider.js';
|
|
|
8
8
|
export * from './llamacpp/llama-cpp-provider.js';
|
|
9
9
|
export * from './provider-resolver.js';
|
|
10
10
|
export * from './pipeline/enhancement-pipeline.js';
|
|
11
|
+
export * from './llm-hints.js';
|
|
12
|
+
export * from './llm-recommendations.js';
|
|
11
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kCAAkC,CAAC;AACjD,cAAc,wBAAwB,CAAC;AACvC,cAAc,oCAAoC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kCAAkC,CAAC;AACjD,cAAc,wBAAwB,CAAC;AACvC,cAAc,oCAAoC,CAAC;AACnD,cAAc,gBAAgB,CAAC;AAC/B,cAAc,0BAA0B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -8,3 +8,5 @@ export * from "./ollama/ollama-provider.js";
|
|
|
8
8
|
export * from "./llamacpp/llama-cpp-provider.js";
|
|
9
9
|
export * from "./provider-resolver.js";
|
|
10
10
|
export * from "./pipeline/enhancement-pipeline.js";
|
|
11
|
+
export * from "./llm-hints.js";
|
|
12
|
+
export * from "./llm-recommendations.js";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { AiProviderKind } from './provider-resolver.js';
|
|
2
|
+
import type { IAiProvider } from './ai-provider.js';
|
|
3
|
+
export type AiHintLevel = 'setup' | 'upgrade' | 'info';
|
|
4
|
+
export interface IAiHint {
|
|
5
|
+
level: AiHintLevel;
|
|
6
|
+
title: string;
|
|
7
|
+
steps: readonly string[];
|
|
8
|
+
}
|
|
9
|
+
export interface IAiBlock {
|
|
10
|
+
reachable: boolean;
|
|
11
|
+
requestedProvider: AiProviderKind;
|
|
12
|
+
providerId: string | null;
|
|
13
|
+
enhancementSkipped: boolean;
|
|
14
|
+
hints: readonly IAiHint[];
|
|
15
|
+
}
|
|
16
|
+
export interface IBuildAiBlockInput {
|
|
17
|
+
/** What `selectAiProvider` returned, or null if the caller didn't try. */
|
|
18
|
+
selection?: {
|
|
19
|
+
requested: AiProviderKind;
|
|
20
|
+
provider: IAiProvider | null;
|
|
21
|
+
} | null;
|
|
22
|
+
/** True when --no-enhance was passed (user opted out — don't nag). */
|
|
23
|
+
userOptedOut?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Produces the structured `ai` block that lives on every audit report
|
|
27
|
+
* and any command using `enrichWithLlmRecommendations`. Without the
|
|
28
|
+
* AI block, `--no-enhance` and "no provider reachable" look the same
|
|
29
|
+
* to a downstream agent. The block disambiguates.
|
|
30
|
+
*
|
|
31
|
+
* Lives in `@shrkcrft/ai` so any package (CLI, packs, MCP server's
|
|
32
|
+
* read-only surfaces) can construct the same shape.
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildAiBlock(input?: IBuildAiBlockInput): IAiBlock;
|
|
35
|
+
export declare function renderAiBlockMarkdown(block: IAiBlock): string;
|
|
36
|
+
//# sourceMappingURL=llm-hints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-hints.d.ts","sourceRoot":"","sources":["../src/llm-hints.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEvD,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,cAAc,CAAC;IAClC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,KAAK,EAAE,SAAS,OAAO,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,0EAA0E;IAC1E,SAAS,CAAC,EAAE;QAAE,SAAS,EAAE,cAAc,CAAC;QAAC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/E,sEAAsE;IACtE,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,KAAK,GAAE,kBAAuB,GAAG,QAAQ,CAiErE;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAiB7D"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { selectAiProvider } from "./provider-resolver.js";
|
|
2
|
+
/**
|
|
3
|
+
* Produces the structured `ai` block that lives on every audit report
|
|
4
|
+
* and any command using `enrichWithLlmRecommendations`. Without the
|
|
5
|
+
* AI block, `--no-enhance` and "no provider reachable" look the same
|
|
6
|
+
* to a downstream agent. The block disambiguates.
|
|
7
|
+
*
|
|
8
|
+
* Lives in `@shrkcrft/ai` so any package (CLI, packs, MCP server's
|
|
9
|
+
* read-only surfaces) can construct the same shape.
|
|
10
|
+
*/
|
|
11
|
+
export function buildAiBlock(input = {}) {
|
|
12
|
+
// Honour an explicitly-passed selection (including {provider: null} when
|
|
13
|
+
// --no-enhance is in play) without re-probing the auto chain. Only fall
|
|
14
|
+
// back to live probing when the caller didn't supply a selection at all.
|
|
15
|
+
const selection = input.selection !== undefined && input.selection !== null
|
|
16
|
+
? input.selection
|
|
17
|
+
: input.userOptedOut
|
|
18
|
+
? { requested: 'auto', provider: null }
|
|
19
|
+
: selectAiProvider(undefined);
|
|
20
|
+
const reachable = selection.provider !== null;
|
|
21
|
+
const providerId = selection.provider?.id ?? null;
|
|
22
|
+
const requested = selection.requested;
|
|
23
|
+
const userOptedOut = Boolean(input.userOptedOut);
|
|
24
|
+
const hints = [];
|
|
25
|
+
if (!reachable && !userOptedOut) {
|
|
26
|
+
hints.push({
|
|
27
|
+
level: 'setup',
|
|
28
|
+
title: 'Enable LLM enrichment for deeper analysis',
|
|
29
|
+
steps: [
|
|
30
|
+
'Local-first: install Ollama (https://ollama.com/download) or set LLAMACPP_MODEL_PATH for in-process inference.',
|
|
31
|
+
'Pull a model that fits your machine — e.g. `ollama pull llama3.2` (good general-purpose) or `ollama pull qwen2.5-coder:7b` (code-aware).',
|
|
32
|
+
'Optional: export OLLAMA_HOST=http://localhost:11434 (default) or point at a remote daemon.',
|
|
33
|
+
'Optional: export OLLAMA_MODEL=<id> to pin the model used by shrk.',
|
|
34
|
+
'Re-run without --no-enhance. The deterministic findings are unchanged; LLM critique appears under `llmFindings`.',
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
else if (!reachable && userOptedOut) {
|
|
39
|
+
hints.push({
|
|
40
|
+
level: 'info',
|
|
41
|
+
title: 'LLM enrichment disabled by --no-enhance',
|
|
42
|
+
steps: [
|
|
43
|
+
'Deterministic findings are first-class; LLM is purely additive.',
|
|
44
|
+
'Drop --no-enhance to layer LLM critique on top when a provider is available.',
|
|
45
|
+
],
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
hints.push({
|
|
50
|
+
level: 'info',
|
|
51
|
+
title: `LLM enrichment active via ${providerId}`,
|
|
52
|
+
steps: [
|
|
53
|
+
'LLM-derived findings appear with `[llm]` tags and a confidence score.',
|
|
54
|
+
'Tune behavior: --provider ollama|llamacpp, --model <id>, AI_PROVIDER env var (overrides --provider when unset).',
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
hints.push({
|
|
58
|
+
level: 'upgrade',
|
|
59
|
+
title: 'Sharpen LLM output if findings feel thin',
|
|
60
|
+
steps: [
|
|
61
|
+
'Prefer a code-aware model for technical staleness checks (e.g. qwen2.5-coder:7b, deepseek-coder-v2).',
|
|
62
|
+
'Larger models notice more drift but cost latency — try 7B for code, 14B+ for nuanced doc-content review.',
|
|
63
|
+
'For fix-plan enrichment, the same provider is reused; no separate config needed.',
|
|
64
|
+
],
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
reachable,
|
|
69
|
+
requestedProvider: requested,
|
|
70
|
+
providerId,
|
|
71
|
+
enhancementSkipped: userOptedOut,
|
|
72
|
+
hints,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function renderAiBlockMarkdown(block) {
|
|
76
|
+
const out = [];
|
|
77
|
+
const status = block.reachable
|
|
78
|
+
? `active via \`${block.providerId}\``
|
|
79
|
+
: block.enhancementSkipped
|
|
80
|
+
? 'disabled by `--no-enhance`'
|
|
81
|
+
: 'unavailable (no local LLM detected)';
|
|
82
|
+
out.push(`## AI configuration — ${status}`);
|
|
83
|
+
out.push('');
|
|
84
|
+
for (const hint of block.hints) {
|
|
85
|
+
out.push(`### [${hint.level}] ${hint.title}`);
|
|
86
|
+
for (const step of hint.steps) {
|
|
87
|
+
out.push(`- ${step}`);
|
|
88
|
+
}
|
|
89
|
+
out.push('');
|
|
90
|
+
}
|
|
91
|
+
return out.join('\n');
|
|
92
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { IAiProvider } from './ai-provider.js';
|
|
2
|
+
import { type IAiBlock } from './llm-hints.js';
|
|
3
|
+
export type RecommendationSeverity = 'info' | 'warn' | 'error';
|
|
4
|
+
export interface ILlmRecommendation {
|
|
5
|
+
severity: RecommendationSeverity;
|
|
6
|
+
category: string;
|
|
7
|
+
/** Short, one-sentence description of what's recommended. */
|
|
8
|
+
title: string;
|
|
9
|
+
/** Detailed prose; typically 1-3 sentences with concrete next-steps. */
|
|
10
|
+
detail: string;
|
|
11
|
+
/** Optional target identifier (rule id, template id, file path) the recommendation applies to. */
|
|
12
|
+
target?: string;
|
|
13
|
+
/** Confidence in [0, 1]; lower for fuzzier judgments. */
|
|
14
|
+
confidence: number;
|
|
15
|
+
}
|
|
16
|
+
export interface IRecommendationEnvelope {
|
|
17
|
+
/** Always present, even when LLM is unavailable. */
|
|
18
|
+
ai: IAiBlock;
|
|
19
|
+
recommendations: readonly ILlmRecommendation[];
|
|
20
|
+
}
|
|
21
|
+
export interface IEnrichWithLlmRecommendationsInput {
|
|
22
|
+
/**
|
|
23
|
+
* The shape of the deterministic surface (e.g., 'doctor', 'templates-drift').
|
|
24
|
+
* Used in the LLM prompt so the model knows what it's looking at.
|
|
25
|
+
*/
|
|
26
|
+
surface: string;
|
|
27
|
+
/**
|
|
28
|
+
* Human-readable description of the deterministic findings (what's already
|
|
29
|
+
* known). Should be tight — the prompt fits into one LLM call.
|
|
30
|
+
*/
|
|
31
|
+
deterministicSummary: string;
|
|
32
|
+
/**
|
|
33
|
+
* Provider kind to request. Defaults to 'auto' (local-first walk).
|
|
34
|
+
*/
|
|
35
|
+
providerKind?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Override the auto-selection by passing an already-resolved provider
|
|
38
|
+
* (useful for tests).
|
|
39
|
+
*/
|
|
40
|
+
providerOverride?: IAiProvider | null;
|
|
41
|
+
/**
|
|
42
|
+
* True when the caller's --no-enhance equivalent was passed.
|
|
43
|
+
* When true, no LLM call is made and the AI block records the opt-out.
|
|
44
|
+
*/
|
|
45
|
+
userOptedOut?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Per-surface ask: what should the LLM produce on top of the
|
|
48
|
+
* deterministic summary? E.g. "for each warning, produce one concrete
|
|
49
|
+
* next-step the user can run from the CLI."
|
|
50
|
+
*/
|
|
51
|
+
ask: string;
|
|
52
|
+
/**
|
|
53
|
+
* Optional override for the model used by the provider.
|
|
54
|
+
*/
|
|
55
|
+
model?: string;
|
|
56
|
+
maxTokens?: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Shared utility for layering LLM recommendations onto any deterministic
|
|
60
|
+
* surface. The deterministic portion is the caller's responsibility; this
|
|
61
|
+
* helper only adds the `ai` block and a structured `recommendations` array.
|
|
62
|
+
*
|
|
63
|
+
* Hard guarantee: if no LLM is reachable (or `userOptedOut` is true), the
|
|
64
|
+
* call is a no-op apart from emitting the `ai` block with setup hints.
|
|
65
|
+
*
|
|
66
|
+
* Lives in `@shrkcrft/ai` so any callable surface (CLI commands, packs,
|
|
67
|
+
* read-only MCP tools that want recommendations alongside their data)
|
|
68
|
+
* can reuse the same envelope shape.
|
|
69
|
+
*/
|
|
70
|
+
export declare function enrichWithLlmRecommendations(input: IEnrichWithLlmRecommendationsInput): Promise<IRecommendationEnvelope>;
|
|
71
|
+
export declare function renderRecommendationsMarkdown(envelope: IRecommendationEnvelope): string;
|
|
72
|
+
//# sourceMappingURL=llm-recommendations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-recommendations.d.ts","sourceRoot":"","sources":["../src/llm-recommendations.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE7D,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE/D,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,sBAAsB,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,kGAAkG;IAClG,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IACtC,oDAAoD;IACpD,EAAE,EAAE,QAAQ,CAAC;IACb,eAAe,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAChD;AAED,MAAM,WAAW,kCAAkC;IACjD;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAC7B;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACtC;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,kCAAkC,GACxC,OAAO,CAAC,uBAAuB,CAAC,CAoClC;AA2FD,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,uBAAuB,GAAG,MAAM,CA+BvF"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { AiMessageRole } from "./ai-request.js";
|
|
2
|
+
import { selectAiProvider } from "./provider-resolver.js";
|
|
3
|
+
import { buildAiBlock } from "./llm-hints.js";
|
|
4
|
+
/**
|
|
5
|
+
* Shared utility for layering LLM recommendations onto any deterministic
|
|
6
|
+
* surface. The deterministic portion is the caller's responsibility; this
|
|
7
|
+
* helper only adds the `ai` block and a structured `recommendations` array.
|
|
8
|
+
*
|
|
9
|
+
* Hard guarantee: if no LLM is reachable (or `userOptedOut` is true), the
|
|
10
|
+
* call is a no-op apart from emitting the `ai` block with setup hints.
|
|
11
|
+
*
|
|
12
|
+
* Lives in `@shrkcrft/ai` so any callable surface (CLI commands, packs,
|
|
13
|
+
* read-only MCP tools that want recommendations alongside their data)
|
|
14
|
+
* can reuse the same envelope shape.
|
|
15
|
+
*/
|
|
16
|
+
export async function enrichWithLlmRecommendations(input) {
|
|
17
|
+
if (input.userOptedOut) {
|
|
18
|
+
const aiBlock = buildAiBlock({
|
|
19
|
+
selection: { requested: normaliseKind(input.providerKind), provider: null },
|
|
20
|
+
userOptedOut: true,
|
|
21
|
+
});
|
|
22
|
+
return { ai: aiBlock, recommendations: [] };
|
|
23
|
+
}
|
|
24
|
+
const selection = input.providerOverride !== undefined
|
|
25
|
+
? { requested: normaliseKind(input.providerKind), provider: input.providerOverride }
|
|
26
|
+
: selectAiProvider(input.providerKind);
|
|
27
|
+
if (!selection.provider) {
|
|
28
|
+
const aiBlock = buildAiBlock({ selection, userOptedOut: false });
|
|
29
|
+
return { ai: aiBlock, recommendations: [] };
|
|
30
|
+
}
|
|
31
|
+
if (input.model)
|
|
32
|
+
selection.provider.configure({ model: input.model });
|
|
33
|
+
const messages = buildRecommendationMessages(input);
|
|
34
|
+
let recommendations = [];
|
|
35
|
+
try {
|
|
36
|
+
const res = await selection.provider.send({
|
|
37
|
+
messages,
|
|
38
|
+
maxTokens: input.maxTokens ?? 1024,
|
|
39
|
+
});
|
|
40
|
+
if (res.ok) {
|
|
41
|
+
recommendations = parseRecommendations(res.value.content);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Swallow — recommendations stay empty; ai block still carries provider info.
|
|
46
|
+
}
|
|
47
|
+
const aiBlock = buildAiBlock({ selection, userOptedOut: false });
|
|
48
|
+
return { ai: aiBlock, recommendations };
|
|
49
|
+
}
|
|
50
|
+
function buildRecommendationMessages(input) {
|
|
51
|
+
const system = {
|
|
52
|
+
role: AiMessageRole.System,
|
|
53
|
+
content: [
|
|
54
|
+
`You are a critic layering concrete next-step recommendations on top of a deterministic SharkCraft "${input.surface}" report.`,
|
|
55
|
+
'The deterministic report is supplied verbatim — treat its findings as facts. Your job is to translate them into actions a developer (or an AI coding agent) can take immediately.',
|
|
56
|
+
'',
|
|
57
|
+
'The user-specified ask is:',
|
|
58
|
+
input.ask,
|
|
59
|
+
'',
|
|
60
|
+
'Return ONLY a JSON object with this exact shape, no preface, no fences:',
|
|
61
|
+
'{',
|
|
62
|
+
' "recommendations": [',
|
|
63
|
+
' {',
|
|
64
|
+
' "severity": "info" | "warn" | "error",',
|
|
65
|
+
' "category": "<short kebab-case category>",',
|
|
66
|
+
' "title": "<one-sentence summary>",',
|
|
67
|
+
' "detail": "<one to three sentences with concrete next-steps; name files, commands, or symbols when possible>",',
|
|
68
|
+
' "target": "<optional id or path>",',
|
|
69
|
+
' "confidence": 0.0',
|
|
70
|
+
' }',
|
|
71
|
+
' ]',
|
|
72
|
+
'}',
|
|
73
|
+
'Skip the bullet entirely if you cannot say anything specific. Better silence than ceremony.',
|
|
74
|
+
].join('\n'),
|
|
75
|
+
};
|
|
76
|
+
const user = {
|
|
77
|
+
role: AiMessageRole.User,
|
|
78
|
+
content: [`# Deterministic ${input.surface} summary`, '', input.deterministicSummary].join('\n'),
|
|
79
|
+
};
|
|
80
|
+
return [system, user];
|
|
81
|
+
}
|
|
82
|
+
function parseRecommendations(raw) {
|
|
83
|
+
const trimmed = raw.trim();
|
|
84
|
+
let jsonText = trimmed;
|
|
85
|
+
const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
86
|
+
if (fenced)
|
|
87
|
+
jsonText = fenced[1].trim();
|
|
88
|
+
let parsed;
|
|
89
|
+
try {
|
|
90
|
+
parsed = JSON.parse(jsonText);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
const first = jsonText.indexOf('{');
|
|
94
|
+
const last = jsonText.lastIndexOf('}');
|
|
95
|
+
if (first < 0 || last <= first)
|
|
96
|
+
return [];
|
|
97
|
+
try {
|
|
98
|
+
parsed = JSON.parse(jsonText.slice(first, last + 1));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (!parsed || typeof parsed !== 'object')
|
|
105
|
+
return [];
|
|
106
|
+
const list = parsed.recommendations;
|
|
107
|
+
if (!Array.isArray(list))
|
|
108
|
+
return [];
|
|
109
|
+
const out = [];
|
|
110
|
+
for (const item of list) {
|
|
111
|
+
if (!item || typeof item !== 'object')
|
|
112
|
+
continue;
|
|
113
|
+
const obj = item;
|
|
114
|
+
const severity = coerceSeverity(obj.severity);
|
|
115
|
+
const category = typeof obj.category === 'string' && obj.category.trim()
|
|
116
|
+
? obj.category.trim()
|
|
117
|
+
: 'other';
|
|
118
|
+
const title = typeof obj.title === 'string' ? obj.title.trim() : '';
|
|
119
|
+
const detail = typeof obj.detail === 'string' ? obj.detail.trim() : '';
|
|
120
|
+
if (!title || !detail)
|
|
121
|
+
continue;
|
|
122
|
+
const confidence = typeof obj.confidence === 'number' && obj.confidence >= 0 && obj.confidence <= 1
|
|
123
|
+
? obj.confidence
|
|
124
|
+
: 0.5;
|
|
125
|
+
const target = typeof obj.target === 'string' && obj.target.trim() ? obj.target.trim() : undefined;
|
|
126
|
+
out.push({ severity, category, title, detail, confidence, ...(target ? { target } : {}) });
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
function coerceSeverity(value) {
|
|
131
|
+
if (value === 'error' || value === 'warn' || value === 'info')
|
|
132
|
+
return value;
|
|
133
|
+
if (value === 'warning')
|
|
134
|
+
return 'warn';
|
|
135
|
+
return 'info';
|
|
136
|
+
}
|
|
137
|
+
function normaliseKind(kind) {
|
|
138
|
+
const known = new Set(['claude', 'gemini', 'ollama', 'llamacpp']);
|
|
139
|
+
if (kind && known.has(kind.toLowerCase()))
|
|
140
|
+
return kind.toLowerCase();
|
|
141
|
+
return 'auto';
|
|
142
|
+
}
|
|
143
|
+
export function renderRecommendationsMarkdown(envelope) {
|
|
144
|
+
const out = [];
|
|
145
|
+
if (envelope.recommendations.length === 0) {
|
|
146
|
+
out.push('## LLM recommendations');
|
|
147
|
+
out.push('');
|
|
148
|
+
out.push(envelope.ai.reachable
|
|
149
|
+
? '(LLM returned no actionable recommendations — the deterministic output already covers the surface.)'
|
|
150
|
+
: '(LLM unavailable — see the AI configuration block below to enable.)');
|
|
151
|
+
out.push('');
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
out.push(`## LLM recommendations (${envelope.recommendations.length})`);
|
|
155
|
+
out.push('');
|
|
156
|
+
const order = ['error', 'warn', 'info'];
|
|
157
|
+
for (const sev of order) {
|
|
158
|
+
const group = envelope.recommendations.filter((r) => r.severity === sev);
|
|
159
|
+
if (group.length === 0)
|
|
160
|
+
continue;
|
|
161
|
+
for (const rec of group) {
|
|
162
|
+
out.push(`- **[${sev}]** \`${rec.category}\`${rec.target ? ` (${rec.target})` : ''} — ${rec.title} _(confidence ${rec.confidence.toFixed(2)})_`);
|
|
163
|
+
out.push(` - ${rec.detail}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
out.push('');
|
|
167
|
+
}
|
|
168
|
+
out.push('---');
|
|
169
|
+
out.push('');
|
|
170
|
+
out.push(renderAiHintsCompact(envelope.ai));
|
|
171
|
+
return out.join('\n');
|
|
172
|
+
}
|
|
173
|
+
function renderAiHintsCompact(ai) {
|
|
174
|
+
const out = [];
|
|
175
|
+
const status = ai.reachable
|
|
176
|
+
? `active via \`${ai.providerId}\``
|
|
177
|
+
: ai.enhancementSkipped
|
|
178
|
+
? 'disabled by user'
|
|
179
|
+
: 'unavailable';
|
|
180
|
+
out.push(`### AI configuration — ${status}`);
|
|
181
|
+
for (const hint of ai.hints) {
|
|
182
|
+
out.push(`- [${hint.level}] **${hint.title}**`);
|
|
183
|
+
for (const step of hint.steps) {
|
|
184
|
+
out.push(` - ${step}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return out.join('\n');
|
|
188
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shrkcrft/ai",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.13",
|
|
4
4
|
"description": "SharkCraft local LLM provider abstraction: Ollama (HTTP) + llama.cpp (in-process) + multi-pass enhancement pipeline.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "SharkCraft contributors",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@shrkcrft/core": "^0.1.0-alpha.
|
|
47
|
-
"@shrkcrft/context": "^0.1.0-alpha.
|
|
46
|
+
"@shrkcrft/core": "^0.1.0-alpha.13",
|
|
47
|
+
"@shrkcrft/context": "^0.1.0-alpha.13",
|
|
48
48
|
"node-llama-cpp": "^3.16.0"
|
|
49
49
|
},
|
|
50
50
|
"publishConfig": {
|