@shrkcrft/cli 0.1.0-alpha.2 → 0.1.0-alpha.20
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/audit/knowledge-audit-llm.d.ts +19 -0
- package/dist/audit/knowledge-audit-llm.d.ts.map +1 -0
- package/dist/audit/knowledge-audit-llm.js +164 -0
- package/dist/audit/knowledge-audit.d.ts +61 -0
- package/dist/audit/knowledge-audit.d.ts.map +1 -0
- package/dist/audit/knowledge-audit.js +203 -0
- package/dist/audit/knowledge-fix-plan-llm.d.ts +11 -0
- package/dist/audit/knowledge-fix-plan-llm.d.ts.map +1 -0
- package/dist/audit/knowledge-fix-plan-llm.js +141 -0
- package/dist/audit/knowledge-fix-plan.d.ts +41 -0
- package/dist/audit/knowledge-fix-plan.d.ts.map +1 -0
- package/dist/audit/knowledge-fix-plan.js +125 -0
- package/dist/audit/pipeline-audit-llm.d.ts +11 -0
- package/dist/audit/pipeline-audit-llm.d.ts.map +1 -0
- package/dist/audit/pipeline-audit-llm.js +134 -0
- package/dist/audit/pipeline-audit.d.ts +69 -0
- package/dist/audit/pipeline-audit.d.ts.map +1 -0
- package/dist/audit/pipeline-audit.js +166 -0
- package/dist/audit/templates-audit-llm.d.ts +19 -0
- package/dist/audit/templates-audit-llm.d.ts.map +1 -0
- package/dist/audit/templates-audit-llm.js +207 -0
- package/dist/audit/templates-audit.d.ts +63 -0
- package/dist/audit/templates-audit.d.ts.map +1 -0
- package/dist/audit/templates-audit.js +171 -0
- package/dist/audit/templates-fix-plan-llm.d.ts +19 -0
- package/dist/audit/templates-fix-plan-llm.d.ts.map +1 -0
- package/dist/audit/templates-fix-plan-llm.js +162 -0
- package/dist/audit/templates-fix-plan.d.ts +37 -0
- package/dist/audit/templates-fix-plan.d.ts.map +1 -0
- package/dist/audit/templates-fix-plan.js +174 -0
- package/dist/command-registry.d.ts +28 -0
- package/dist/command-registry.d.ts.map +1 -1
- package/dist/command-registry.js +91 -1
- package/dist/commands/ai-status.command.d.ts +19 -0
- package/dist/commands/ai-status.command.d.ts.map +1 -0
- package/dist/commands/ai-status.command.js +94 -0
- package/dist/commands/api-diff.command.d.ts +11 -0
- package/dist/commands/api-diff.command.d.ts.map +1 -0
- package/dist/commands/api-diff.command.js +144 -0
- package/dist/commands/apply.command.d.ts.map +1 -1
- package/dist/commands/apply.command.js +10 -2
- package/dist/commands/arch.command.d.ts +9 -0
- package/dist/commands/arch.command.d.ts.map +1 -0
- package/dist/commands/arch.command.js +186 -0
- package/dist/commands/ask.command.d.ts.map +1 -1
- package/dist/commands/ask.command.js +10 -9
- package/dist/commands/cache-align.command.d.ts +12 -0
- package/dist/commands/cache-align.command.d.ts.map +1 -0
- package/dist/commands/cache-align.command.js +78 -0
- package/dist/commands/check.command.d.ts.map +1 -1
- package/dist/commands/check.command.js +19 -2
- package/dist/commands/code-intel.command.d.ts +18 -0
- package/dist/commands/code-intel.command.d.ts.map +1 -0
- package/dist/commands/code-intel.command.js +146 -0
- package/dist/commands/codemod.command.d.ts.map +1 -1
- package/dist/commands/codemod.command.js +27 -6
- package/dist/commands/command-catalog.d.ts +15 -3
- package/dist/commands/command-catalog.d.ts.map +1 -1
- package/dist/commands/command-catalog.js +387 -34
- package/dist/commands/commands.command.d.ts.map +1 -1
- package/dist/commands/commands.command.js +4 -4
- package/dist/commands/completion.command.d.ts +10 -0
- package/dist/commands/completion.command.d.ts.map +1 -0
- package/dist/commands/completion.command.js +121 -0
- package/dist/commands/compress.command.d.ts +8 -0
- package/dist/commands/compress.command.d.ts.map +1 -0
- package/dist/commands/compress.command.js +147 -0
- package/dist/commands/constructs.command.d.ts.map +1 -1
- package/dist/commands/constructs.command.js +89 -23
- package/dist/commands/context.command.d.ts.map +1 -1
- package/dist/commands/context.command.js +121 -1
- package/dist/commands/contract-gate.command.d.ts.map +1 -1
- package/dist/commands/contract-gate.command.js +5 -1
- package/dist/commands/delegate.command.d.ts +65 -0
- package/dist/commands/delegate.command.d.ts.map +1 -0
- package/dist/commands/delegate.command.js +657 -0
- package/dist/commands/deps-audit.command.d.ts +23 -0
- package/dist/commands/deps-audit.command.d.ts.map +1 -0
- package/dist/commands/deps-audit.command.js +270 -0
- package/dist/commands/dev.command.d.ts.map +1 -1
- package/dist/commands/dev.command.js +5 -1
- 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 +162 -10
- package/dist/commands/export.command.d.ts.map +1 -1
- package/dist/commands/export.command.js +76 -3
- package/dist/commands/framework.command.d.ts +12 -0
- package/dist/commands/framework.command.d.ts.map +1 -0
- package/dist/commands/framework.command.js +180 -0
- package/dist/commands/gate.command.d.ts +15 -0
- package/dist/commands/gate.command.d.ts.map +1 -0
- package/dist/commands/gate.command.js +300 -0
- package/dist/commands/gen.command.d.ts.map +1 -1
- package/dist/commands/gen.command.js +13 -1
- package/dist/commands/graph-code-subverbs.d.ts +33 -0
- package/dist/commands/graph-code-subverbs.d.ts.map +1 -0
- package/dist/commands/graph-code-subverbs.js +1366 -0
- package/dist/commands/graph.command.d.ts.map +1 -1
- package/dist/commands/graph.command.js +31 -2
- 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 +86 -18
- package/dist/commands/helper.command.js +1 -1
- package/dist/commands/impact.command.d.ts.map +1 -1
- package/dist/commands/impact.command.js +171 -1
- package/dist/commands/import.command.d.ts.map +1 -1
- package/dist/commands/import.command.js +121 -5
- package/dist/commands/ingest.command.d.ts.map +1 -1
- package/dist/commands/ingest.command.js +5 -1
- package/dist/commands/init.command.d.ts.map +1 -1
- package/dist/commands/init.command.js +174 -7
- package/dist/commands/knowledge-author.command.d.ts.map +1 -1
- package/dist/commands/knowledge-author.command.js +9 -0
- package/dist/commands/knowledge-propose.command.d.ts.map +1 -1
- package/dist/commands/knowledge-propose.command.js +4 -2
- package/dist/commands/knowledge.command.d.ts.map +1 -1
- package/dist/commands/knowledge.command.js +26 -3
- package/dist/commands/migrate.command.d.ts +13 -0
- package/dist/commands/migrate.command.d.ts.map +1 -0
- package/dist/commands/migrate.command.js +152 -0
- package/dist/commands/move-plan.command.d.ts +23 -0
- package/dist/commands/move-plan.command.d.ts.map +1 -0
- package/dist/commands/move-plan.command.js +360 -0
- 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 +5 -36
- package/dist/commands/packs.command.d.ts.map +1 -1
- package/dist/commands/packs.command.js +2 -10
- package/dist/commands/plan-context.command.d.ts +11 -0
- package/dist/commands/plan-context.command.d.ts.map +1 -0
- package/dist/commands/plan-context.command.js +85 -0
- package/dist/commands/preflight.command.d.ts.map +1 -1
- package/dist/commands/preflight.command.js +15 -0
- package/dist/commands/profiles.command.js +4 -4
- package/dist/commands/recommend.command.d.ts +6 -0
- package/dist/commands/recommend.command.d.ts.map +1 -1
- package/dist/commands/recommend.command.js +119 -5
- package/dist/commands/release.command.js +13 -13
- package/dist/commands/rule-graph-subverbs.d.ts +3 -0
- package/dist/commands/rule-graph-subverbs.d.ts.map +1 -0
- package/dist/commands/rule-graph-subverbs.js +132 -0
- package/dist/commands/rules.command.d.ts.map +1 -1
- package/dist/commands/rules.command.js +20 -3
- package/dist/commands/scaffold-validate.command.d.ts +22 -0
- package/dist/commands/scaffold-validate.command.d.ts.map +1 -0
- package/dist/commands/scaffold-validate.command.js +215 -0
- package/dist/commands/search-structural.command.d.ts +18 -0
- package/dist/commands/search-structural.command.d.ts.map +1 -0
- package/dist/commands/search-structural.command.js +376 -0
- package/dist/commands/search.command.js +1 -1
- package/dist/commands/smart-context.command.d.ts +67 -0
- package/dist/commands/smart-context.command.d.ts.map +1 -0
- package/dist/commands/smart-context.command.js +4728 -0
- package/dist/commands/spike.command.d.ts +22 -0
- package/dist/commands/spike.command.d.ts.map +1 -0
- package/dist/commands/spike.command.js +235 -0
- package/dist/commands/surface.command.d.ts +1 -0
- package/dist/commands/surface.command.d.ts.map +1 -1
- package/dist/commands/surface.command.js +10 -3
- package/dist/commands/task-context.command.d.ts.map +1 -1
- package/dist/commands/task-context.command.js +5 -17
- package/dist/commands/task.command.d.ts.map +1 -1
- package/dist/commands/task.command.js +8 -2
- package/dist/commands/template-quality.command.d.ts.map +1 -1
- package/dist/commands/template-quality.command.js +39 -3
- package/dist/commands/templates.command.d.ts.map +1 -1
- package/dist/commands/templates.command.js +37 -2
- package/dist/commands/tests.command.d.ts.map +1 -1
- package/dist/commands/tests.command.js +13 -2
- package/dist/commands/watch.command.d.ts +26 -0
- package/dist/commands/watch.command.d.ts.map +1 -0
- package/dist/commands/watch.command.js +456 -0
- package/dist/dashboard/code-intelligence-data.d.ts +33 -0
- package/dist/dashboard/code-intelligence-data.d.ts.map +1 -0
- package/dist/dashboard/code-intelligence-data.js +329 -0
- package/dist/dashboard/dashboard-api-server.d.ts.map +1 -1
- package/dist/dashboard/dashboard-api-server.js +256 -2
- package/dist/dashboard/knowledge-ask.d.ts +4 -0
- package/dist/dashboard/knowledge-ask.d.ts.map +1 -0
- package/dist/dashboard/knowledge-ask.js +112 -0
- package/dist/env/load-dotenv.d.ts +15 -0
- package/dist/env/load-dotenv.d.ts.map +1 -0
- package/dist/env/load-dotenv.js +70 -0
- 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/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/init/init-templates.d.ts.map +1 -1
- package/dist/init/init-templates.js +133 -113
- package/dist/init/paths-advisory.d.ts +20 -0
- package/dist/init/paths-advisory.d.ts.map +1 -0
- package/dist/init/paths-advisory.js +88 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +331 -17
- package/dist/output/ccr-store-config.d.ts +18 -0
- package/dist/output/ccr-store-config.d.ts.map +1 -0
- package/dist/output/ccr-store-config.js +41 -0
- package/dist/output/format-output.d.ts.map +1 -1
- package/dist/output/format-output.js +6 -1
- package/dist/output/output-compression.d.ts +15 -0
- package/dist/output/output-compression.d.ts.map +1 -0
- package/dist/output/output-compression.js +60 -0
- package/dist/output/resolve-compress-type.d.ts +22 -0
- package/dist/output/resolve-compress-type.d.ts.map +1 -0
- package/dist/output/resolve-compress-type.js +21 -0
- package/dist/output/watch-loop.d.ts +9 -1
- package/dist/output/watch-loop.d.ts.map +1 -1
- package/dist/output/watch-loop.js +13 -3
- package/dist/schemas/json-schemas.d.ts +384 -36
- package/dist/schemas/json-schemas.d.ts.map +1 -1
- package/dist/schemas/json-schemas.js +247 -36
- package/dist/surface/profiles.d.ts.map +1 -1
- package/dist/surface/profiles.js +54 -9
- package/dist/surface/surface-config-writer.d.ts.map +1 -1
- package/dist/surface/surface-config-writer.js +23 -11
- package/dist/validation/run-validation-loop.d.ts.map +1 -1
- package/dist/validation/run-validation-loop.js +5 -1
- package/package.json +35 -21
- 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
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { AiMessageRole } from '@shrkcrft/ai';
|
|
2
|
+
export async function enrichAuditWithLlm(report, options) {
|
|
3
|
+
const enrichedEntries = [];
|
|
4
|
+
for (const entry of report.templates) {
|
|
5
|
+
const template = options.inspection.templateRegistry.get(entry.templateId);
|
|
6
|
+
if (!template) {
|
|
7
|
+
enrichedEntries.push(entry);
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
const peers = options.inspection.templateRegistry
|
|
11
|
+
.list()
|
|
12
|
+
.filter((t) => t.id !== template.id)
|
|
13
|
+
.slice(0, 4);
|
|
14
|
+
const messages = buildEnrichmentMessages(template, peers, entry);
|
|
15
|
+
try {
|
|
16
|
+
const res = await options.provider.send({
|
|
17
|
+
messages,
|
|
18
|
+
maxTokens: options.maxTokensPerTemplate ?? 1024,
|
|
19
|
+
});
|
|
20
|
+
if (!res.ok) {
|
|
21
|
+
options.onPerTemplateError?.(entry.templateId, res.error);
|
|
22
|
+
enrichedEntries.push(entry);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const parsed = parseLlmFindings(res.value.content);
|
|
26
|
+
enrichedEntries.push({
|
|
27
|
+
...entry,
|
|
28
|
+
llmFindings: parsed,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
options.onPerTemplateError?.(entry.templateId, e);
|
|
33
|
+
enrichedEntries.push(entry);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
...report,
|
|
38
|
+
llmEnriched: true,
|
|
39
|
+
llmProviderId: options.provider.id,
|
|
40
|
+
templates: enrichedEntries,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function buildEnrichmentMessages(template, peers, entry) {
|
|
44
|
+
const system = {
|
|
45
|
+
role: AiMessageRole.System,
|
|
46
|
+
content: [
|
|
47
|
+
'You are a critic auditing a SharkCraft scaffold template for staleness, content quality, and silent drift from sibling templates.',
|
|
48
|
+
'You will receive: the template body, its declared variables, a sample target path, the deterministic findings already produced by `shrk templates lint` and `shrk templates drift`, and short summaries of up to four sibling templates.',
|
|
49
|
+
'',
|
|
50
|
+
'CRITICAL DISTINCTION — read carefully before flagging anything:',
|
|
51
|
+
'- You are auditing the TEMPLATE itself (the scaffold), NOT the domain it scaffolds. A template that generates a CLI command is fine even if "CLI commands" as a concept were deprecated; you would only flag the template if its OUTPUT is broken.',
|
|
52
|
+
'- "The template imports X" means a literal `import` statement is in the rendered body. Variable names, type-references via {{placeholders}}, and prose mentions in `description` are NOT imports — do not flag them as api-drift.',
|
|
53
|
+
'- "Naming convention drift" is real only when the template diverges from CONCRETE SIBLINGS. Re-describing a variable name (e.g. "uses `camel` instead of camelCase") is restating the schema, not a finding.',
|
|
54
|
+
'',
|
|
55
|
+
'STRICT silence bias: emit a finding only if a senior maintainer would change something on the strength of it. If the deterministic layer already caught it, do not restate. If you are extrapolating from peer summaries without direct evidence, suppress.',
|
|
56
|
+
'',
|
|
57
|
+
'Your job: surface issues the deterministic layer CANNOT see. Look explicitly for each of the following, in order:',
|
|
58
|
+
' 1. **api-drift** — the template imports symbols, paths, or APIs that look outdated (renamed, removed, or replaced). Flag specific symbol/path names.',
|
|
59
|
+
' 2. **deprecated-pattern** — the body uses syntax/idioms a maintainer would call out today (var, == comparisons, removed helpers, old framework APIs).',
|
|
60
|
+
' 3. **doc-content-mismatch**— the `description` and `postGenerationNotes` describe behavior the rendered `content` does not actually produce.',
|
|
61
|
+
' 4. **style-drift** — the body diverges from sibling templates in naming, formatting, error handling, or registration ceremony.',
|
|
62
|
+
' 5. **missing-variable** — sibling templates declare a variable this one omits, and the omission looks unintentional.',
|
|
63
|
+
' 6. **content-bug** — the body contains a subtle bug (off-by-one, wrong return type, broken template literal, missing await) the deterministic layer would not catch.',
|
|
64
|
+
' 7. **stale-phrasing** — `description`, variable descriptions, or `postGenerationNotes` reference a workflow / file / command that no longer exists or has been renamed.',
|
|
65
|
+
' 8. **other** — only when none of the above categories fit.',
|
|
66
|
+
'',
|
|
67
|
+
'A finding is worth flagging only if it would change a maintainer\'s decision. Skip ceremonial nits.',
|
|
68
|
+
'',
|
|
69
|
+
'Return ONLY a JSON object with this exact shape, no preface, no markdown fences:',
|
|
70
|
+
'{',
|
|
71
|
+
' "findings": [',
|
|
72
|
+
' {',
|
|
73
|
+
' "severity": "info" | "warn" | "error",',
|
|
74
|
+
' "category": "api-drift" | "deprecated-pattern" | "doc-content-mismatch" | "style-drift" | "missing-variable" | "content-bug" | "stale-phrasing" | "other",',
|
|
75
|
+
' "message": "<one sentence — name specific symbols, lines, or peers when possible>",',
|
|
76
|
+
' "confidence": 0.0',
|
|
77
|
+
' }',
|
|
78
|
+
' ]',
|
|
79
|
+
'}',
|
|
80
|
+
'If nothing new is worth flagging, return {"findings": []}. Never invent variable names, file paths, or symbol names not present in the supplied template or peer summaries.',
|
|
81
|
+
].join('\n'),
|
|
82
|
+
};
|
|
83
|
+
const body = typeof template.content === 'string'
|
|
84
|
+
? (template.content)
|
|
85
|
+
: '(function body — not introspectable as a string)';
|
|
86
|
+
const sampleTarget = renderSampleTargetPath(template);
|
|
87
|
+
const user = {
|
|
88
|
+
role: AiMessageRole.User,
|
|
89
|
+
content: [
|
|
90
|
+
`# Template under audit`,
|
|
91
|
+
`id: ${template.id}`,
|
|
92
|
+
`name: ${template.name}`,
|
|
93
|
+
`description: ${template.description}`,
|
|
94
|
+
`tags: ${template.tags.join(', ') || '(none)'}`,
|
|
95
|
+
``,
|
|
96
|
+
`## Declared variables`,
|
|
97
|
+
template.variables.length === 0
|
|
98
|
+
? '(none)'
|
|
99
|
+
: template.variables
|
|
100
|
+
.map((v) => `- ${v.name}${v.required ? ' (required)' : ''}${v.description ? ` — ${v.description}` : ''}`)
|
|
101
|
+
.join('\n'),
|
|
102
|
+
``,
|
|
103
|
+
`## Sample target path`,
|
|
104
|
+
sampleTarget ?? '(unable to render)',
|
|
105
|
+
``,
|
|
106
|
+
`## Template body (truncated to ~4 KB)`,
|
|
107
|
+
'```',
|
|
108
|
+
truncate(body, 4096),
|
|
109
|
+
'```',
|
|
110
|
+
``,
|
|
111
|
+
`## Deterministic findings already produced`,
|
|
112
|
+
entry.deterministicFindings.length === 0
|
|
113
|
+
? '(none)'
|
|
114
|
+
: entry.deterministicFindings
|
|
115
|
+
.map((f) => `- [${f.severity}] ${f.category}: ${f.message} (sources: ${f.sources.join(', ')})`)
|
|
116
|
+
.join('\n'),
|
|
117
|
+
``,
|
|
118
|
+
`## Sibling templates`,
|
|
119
|
+
peers.length === 0
|
|
120
|
+
? '(no peers in this workspace)'
|
|
121
|
+
: peers
|
|
122
|
+
.map((p) => {
|
|
123
|
+
const vars = (p.variables ?? [])
|
|
124
|
+
.map((v) => `${v.name}${v.required ? '!' : ''}`)
|
|
125
|
+
.join(', ');
|
|
126
|
+
return `- ${p.id} — ${p.name}\n vars: ${vars || '(none)'}\n tags: ${p.tags.join(', ') || '(none)'}`;
|
|
127
|
+
})
|
|
128
|
+
.join('\n\n'),
|
|
129
|
+
].join('\n'),
|
|
130
|
+
};
|
|
131
|
+
return [system, user];
|
|
132
|
+
}
|
|
133
|
+
function renderSampleTargetPath(template) {
|
|
134
|
+
const fn = template.targetPath;
|
|
135
|
+
if (typeof fn === 'string')
|
|
136
|
+
return fn;
|
|
137
|
+
if (typeof fn !== 'function')
|
|
138
|
+
return null;
|
|
139
|
+
const sample = { name: 'sample-feature' };
|
|
140
|
+
for (const v of template.variables ?? []) {
|
|
141
|
+
sample[v.name] = v.examples?.[0] ?? v.default ?? `sample-${v.name}`;
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
const result = fn(sample);
|
|
145
|
+
return typeof result === 'string' ? result : null;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function truncate(s, max) {
|
|
152
|
+
if (s.length <= max)
|
|
153
|
+
return s;
|
|
154
|
+
return `${s.slice(0, max)}\n… [truncated ${s.length - max} chars]`;
|
|
155
|
+
}
|
|
156
|
+
function parseLlmFindings(raw) {
|
|
157
|
+
const trimmed = raw.trim();
|
|
158
|
+
let jsonText = trimmed;
|
|
159
|
+
const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
160
|
+
if (fenced)
|
|
161
|
+
jsonText = fenced[1].trim();
|
|
162
|
+
let parsed;
|
|
163
|
+
try {
|
|
164
|
+
parsed = JSON.parse(jsonText);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
const firstBrace = jsonText.indexOf('{');
|
|
168
|
+
const lastBrace = jsonText.lastIndexOf('}');
|
|
169
|
+
if (firstBrace < 0 || lastBrace <= firstBrace)
|
|
170
|
+
return [];
|
|
171
|
+
try {
|
|
172
|
+
parsed = JSON.parse(jsonText.slice(firstBrace, lastBrace + 1));
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (!parsed || typeof parsed !== 'object')
|
|
179
|
+
return [];
|
|
180
|
+
const list = parsed.findings;
|
|
181
|
+
if (!Array.isArray(list))
|
|
182
|
+
return [];
|
|
183
|
+
const out = [];
|
|
184
|
+
for (const item of list) {
|
|
185
|
+
if (!item || typeof item !== 'object')
|
|
186
|
+
continue;
|
|
187
|
+
const obj = item;
|
|
188
|
+
const severity = coerceSeverity(obj.severity);
|
|
189
|
+
const category = typeof obj.category === 'string' && obj.category.trim() ? obj.category : 'other';
|
|
190
|
+
const message = typeof obj.message === 'string' ? obj.message.trim() : '';
|
|
191
|
+
const confidence = typeof obj.confidence === 'number' && obj.confidence >= 0 && obj.confidence <= 1
|
|
192
|
+
? obj.confidence
|
|
193
|
+
: 0.5;
|
|
194
|
+
if (!message)
|
|
195
|
+
continue;
|
|
196
|
+
out.push({ severity, category, message, confidence });
|
|
197
|
+
}
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
200
|
+
function coerceSeverity(value) {
|
|
201
|
+
if (value === 'error' || value === 'warn' || value === 'info')
|
|
202
|
+
return value;
|
|
203
|
+
if (value === 'warning')
|
|
204
|
+
return 'warn';
|
|
205
|
+
return 'info';
|
|
206
|
+
}
|
|
207
|
+
export const __internals = { parseLlmFindings, buildEnrichmentMessages };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type ISharkcraftInspection } from '@shrkcrft/inspector';
|
|
2
|
+
import type { IAiBlock } from '@shrkcrft/ai';
|
|
3
|
+
export type AuditFindingSeverity = 'info' | 'warn' | 'error';
|
|
4
|
+
export interface IAuditFinding {
|
|
5
|
+
severity: AuditFindingSeverity;
|
|
6
|
+
category: string;
|
|
7
|
+
message: string;
|
|
8
|
+
location?: string;
|
|
9
|
+
sources: readonly string[];
|
|
10
|
+
suggestion?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ILlmAuditFinding {
|
|
13
|
+
severity: AuditFindingSeverity;
|
|
14
|
+
category: string;
|
|
15
|
+
message: string;
|
|
16
|
+
confidence: number;
|
|
17
|
+
}
|
|
18
|
+
export type AuditVerdict = 'ok' | 'minor' | 'stale' | 'broken';
|
|
19
|
+
export interface IAuditSuggestedAction {
|
|
20
|
+
kind: 'edit' | 'regenerate' | 'retire' | 'investigate';
|
|
21
|
+
target: string;
|
|
22
|
+
note: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ITemplateAuditEntry {
|
|
25
|
+
templateId: string;
|
|
26
|
+
templateName: string;
|
|
27
|
+
verdict: AuditVerdict;
|
|
28
|
+
usage: 'unknown';
|
|
29
|
+
deterministicFindings: readonly IAuditFinding[];
|
|
30
|
+
llmFindings: readonly ILlmAuditFinding[];
|
|
31
|
+
suggestedActions: readonly IAuditSuggestedAction[];
|
|
32
|
+
}
|
|
33
|
+
export interface IAuditSkipped {
|
|
34
|
+
templateId: string;
|
|
35
|
+
reason: string;
|
|
36
|
+
}
|
|
37
|
+
export interface ITemplateAuditReport {
|
|
38
|
+
auditId: string;
|
|
39
|
+
generatedAt: string;
|
|
40
|
+
llmEnriched: boolean;
|
|
41
|
+
llmProviderId: string | null;
|
|
42
|
+
templates: readonly ITemplateAuditEntry[];
|
|
43
|
+
skipped: readonly IAuditSkipped[];
|
|
44
|
+
summary: {
|
|
45
|
+
ok: number;
|
|
46
|
+
minor: number;
|
|
47
|
+
stale: number;
|
|
48
|
+
broken: number;
|
|
49
|
+
total: number;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Configuration-state block populated by the command handler before
|
|
53
|
+
* the report is emitted. Always present — without LLM the block
|
|
54
|
+
* carries setup hints; with LLM it carries upgrade hints. Lets
|
|
55
|
+
* Claude self-configure shrk without external prompting.
|
|
56
|
+
*/
|
|
57
|
+
ai?: IAiBlock;
|
|
58
|
+
}
|
|
59
|
+
export interface IBuildAuditOptions {
|
|
60
|
+
templateId?: string;
|
|
61
|
+
}
|
|
62
|
+
export declare function buildTemplateAudit(inspection: ISharkcraftInspection, options?: IBuildAuditOptions): ITemplateAuditReport;
|
|
63
|
+
//# sourceMappingURL=templates-audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates-audit.d.ts","sourceRoot":"","sources":["../../src/audit/templates-audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE7D,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE/D,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,QAAQ,GAAG,aAAa,CAAC;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,SAAS,CAAC;IACjB,qBAAqB,EAAE,SAAS,aAAa,EAAE,CAAC;IAChD,WAAW,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACzC,gBAAgB,EAAE,SAAS,qBAAqB,EAAE,CAAC;CACpD;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC1C,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;IAClC,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF;;;;;OAKG;IACH,EAAE,CAAC,EAAE,QAAQ,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,qBAAqB,EACjC,OAAO,GAAE,kBAAuB,GAC/B,oBAAoB,CAyFtB"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { buildTemplateDriftReport, lintTemplates, TemplateDriftStatus, } from '@shrkcrft/inspector';
|
|
2
|
+
export function buildTemplateAudit(inspection, options = {}) {
|
|
3
|
+
const all = inspection.templateRegistry.list();
|
|
4
|
+
const userTemplates = all.filter((t) => (inspection.templateSources.get(t.id)?.type ?? 'local') === 'local');
|
|
5
|
+
const skipped = all
|
|
6
|
+
.filter((t) => !userTemplates.includes(t))
|
|
7
|
+
.map((t) => ({
|
|
8
|
+
templateId: t.id,
|
|
9
|
+
reason: 'pack-contributed (out of scope for v1 audit)',
|
|
10
|
+
}));
|
|
11
|
+
const targets = options.templateId
|
|
12
|
+
? userTemplates.filter((t) => t.id === options.templateId)
|
|
13
|
+
: userTemplates;
|
|
14
|
+
const lint = lintTemplates(inspection, targets.map((t) => t.id));
|
|
15
|
+
const drift = buildTemplateDriftReport(inspection, options.templateId ? { templateId: options.templateId } : {});
|
|
16
|
+
const lintByTemplate = new Map();
|
|
17
|
+
for (const r of lint.results)
|
|
18
|
+
lintByTemplate.set(r.templateId, r);
|
|
19
|
+
const driftByTemplate = new Map();
|
|
20
|
+
for (const e of drift.entries)
|
|
21
|
+
driftByTemplate.set(e.templateId, e);
|
|
22
|
+
const entries = [];
|
|
23
|
+
for (const t of targets) {
|
|
24
|
+
const lintEntry = lintByTemplate.get(t.id);
|
|
25
|
+
const driftEntry = driftByTemplate.get(t.id);
|
|
26
|
+
const raw = [];
|
|
27
|
+
for (const i of lintEntry?.issues ?? []) {
|
|
28
|
+
raw.push({
|
|
29
|
+
severity: normaliseSeverity(i.severity),
|
|
30
|
+
category: i.code,
|
|
31
|
+
message: i.message,
|
|
32
|
+
sources: ['templates lint'],
|
|
33
|
+
...(i.suggestion ? { suggestion: i.suggestion } : {}),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
for (const i of driftEntry?.issues ?? []) {
|
|
37
|
+
raw.push({
|
|
38
|
+
severity: normaliseSeverity(i.severity),
|
|
39
|
+
category: i.code,
|
|
40
|
+
message: i.message,
|
|
41
|
+
sources: ['templates drift'],
|
|
42
|
+
...(i.suggestedFix ? { suggestion: i.suggestedFix } : {}),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const deterministicFindings = dedupeFindings(raw);
|
|
46
|
+
entries.push({
|
|
47
|
+
templateId: t.id,
|
|
48
|
+
templateName: t.name,
|
|
49
|
+
verdict: deriveVerdict(deterministicFindings, driftEntry?.status ?? null),
|
|
50
|
+
usage: 'unknown',
|
|
51
|
+
deterministicFindings,
|
|
52
|
+
llmFindings: [],
|
|
53
|
+
suggestedActions: deriveSuggestedActions(t.id, deterministicFindings),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const summary = entries.reduce((acc, e) => {
|
|
57
|
+
acc[e.verdict] += 1;
|
|
58
|
+
acc.total += 1;
|
|
59
|
+
return acc;
|
|
60
|
+
}, { ok: 0, minor: 0, stale: 0, broken: 0, total: 0 });
|
|
61
|
+
const generatedAt = new Date().toISOString();
|
|
62
|
+
return {
|
|
63
|
+
auditId: `audit-${generatedAt.replace(/[:.]/g, '-')}`,
|
|
64
|
+
generatedAt,
|
|
65
|
+
llmEnriched: false,
|
|
66
|
+
llmProviderId: null,
|
|
67
|
+
templates: entries,
|
|
68
|
+
skipped,
|
|
69
|
+
summary,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function normaliseSeverity(s) {
|
|
73
|
+
if (s === 'error')
|
|
74
|
+
return 'error';
|
|
75
|
+
if (s === 'warning')
|
|
76
|
+
return 'warn';
|
|
77
|
+
return 'info';
|
|
78
|
+
}
|
|
79
|
+
function dedupeFindings(raw) {
|
|
80
|
+
const byKey = new Map();
|
|
81
|
+
for (const f of raw) {
|
|
82
|
+
const key = `${f.category}::${f.message}`;
|
|
83
|
+
const existing = byKey.get(key);
|
|
84
|
+
if (!existing) {
|
|
85
|
+
byKey.set(key, { ...f, sources: [...f.sources] });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const mergedSources = Array.from(new Set([...existing.sources, ...f.sources]));
|
|
89
|
+
byKey.set(key, {
|
|
90
|
+
...existing,
|
|
91
|
+
sources: mergedSources,
|
|
92
|
+
...(existing.suggestion || f.suggestion
|
|
93
|
+
? { suggestion: existing.suggestion ?? f.suggestion }
|
|
94
|
+
: {}),
|
|
95
|
+
severity: maxSeverity(existing.severity, f.severity),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return Array.from(byKey.values()).sort((a, b) => {
|
|
99
|
+
const order = { error: 0, warn: 1, info: 2 };
|
|
100
|
+
return order[a.severity] - order[b.severity] || a.category.localeCompare(b.category);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function maxSeverity(a, b) {
|
|
104
|
+
const rank = { info: 0, warn: 1, error: 2 };
|
|
105
|
+
return rank[a] >= rank[b] ? a : b;
|
|
106
|
+
}
|
|
107
|
+
function deriveVerdict(findings, driftStatus) {
|
|
108
|
+
if (findings.some((f) => f.severity === 'error'))
|
|
109
|
+
return 'broken';
|
|
110
|
+
if (driftStatus === TemplateDriftStatus.Fail)
|
|
111
|
+
return 'broken';
|
|
112
|
+
if (findings.some((f) => f.severity === 'warn'))
|
|
113
|
+
return 'stale';
|
|
114
|
+
if (driftStatus === TemplateDriftStatus.Warn)
|
|
115
|
+
return 'stale';
|
|
116
|
+
if (findings.some((f) => f.severity === 'info'))
|
|
117
|
+
return 'minor';
|
|
118
|
+
return 'ok';
|
|
119
|
+
}
|
|
120
|
+
function deriveSuggestedActions(templateId, findings) {
|
|
121
|
+
const actions = [];
|
|
122
|
+
const hasError = findings.some((f) => f.severity === 'error');
|
|
123
|
+
const hasWarn = findings.some((f) => f.severity === 'warn');
|
|
124
|
+
const undeclared = findings.find((f) => f.category === 'undeclared-var');
|
|
125
|
+
const unsafe = findings.find((f) => f.category === 'unsafe-target');
|
|
126
|
+
const noConvention = findings.find((f) => f.category === 'path-no-convention');
|
|
127
|
+
const missingName = findings.find((f) => f.category === 'missing-name');
|
|
128
|
+
if (unsafe) {
|
|
129
|
+
actions.push({
|
|
130
|
+
kind: 'edit',
|
|
131
|
+
target: templateId,
|
|
132
|
+
note: 'targetPath escapes project root — rewrite to a safe relative path under packages/.',
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (missingName) {
|
|
136
|
+
actions.push({
|
|
137
|
+
kind: 'edit',
|
|
138
|
+
target: templateId,
|
|
139
|
+
note: 'Template has no name — add a human-readable `name` field.',
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (undeclared) {
|
|
143
|
+
actions.push({
|
|
144
|
+
kind: 'edit',
|
|
145
|
+
target: templateId,
|
|
146
|
+
note: 'Placeholder is not declared in variables[] — declare it or remove the reference.',
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
if (noConvention) {
|
|
150
|
+
actions.push({
|
|
151
|
+
kind: 'investigate',
|
|
152
|
+
target: templateId,
|
|
153
|
+
note: 'Sample path does not match any registered path convention — confirm the targetPath fn aligns with paths.ts.',
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if (hasError && actions.length === 0) {
|
|
157
|
+
actions.push({
|
|
158
|
+
kind: 'investigate',
|
|
159
|
+
target: templateId,
|
|
160
|
+
note: 'Template has error-level findings; review and fix before next generation.',
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (!hasError && hasWarn && actions.length === 0) {
|
|
164
|
+
actions.push({
|
|
165
|
+
kind: 'edit',
|
|
166
|
+
target: templateId,
|
|
167
|
+
note: 'Address warning-level findings to keep the template aligned with current conventions.',
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return actions;
|
|
171
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type IAiMessage, type IAiProvider } from '@shrkcrft/ai';
|
|
2
|
+
import type { ITemplateDefinition } from '@shrkcrft/templates';
|
|
3
|
+
import type { ISharkcraftInspection } from '@shrkcrft/inspector';
|
|
4
|
+
import type { IFixInstruction, ITemplateFixPlan } from './templates-fix-plan.js';
|
|
5
|
+
export interface IEnrichFixPlanOptions {
|
|
6
|
+
provider: IAiProvider;
|
|
7
|
+
inspection: ISharkcraftInspection;
|
|
8
|
+
maxTokensPerTemplate?: number;
|
|
9
|
+
onPerTemplateError?: (templateId: string, error: Error) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function enrichFixPlanWithLlm(plan: ITemplateFixPlan, options: IEnrichFixPlanOptions): Promise<ITemplateFixPlan>;
|
|
12
|
+
declare function buildFixSuggestionMessages(template: ITemplateDefinition, fixes: readonly IFixInstruction[]): IAiMessage[];
|
|
13
|
+
declare function parseSuggestionMap(raw: string): Map<string, string>;
|
|
14
|
+
export declare const __internals: {
|
|
15
|
+
buildFixSuggestionMessages: typeof buildFixSuggestionMessages;
|
|
16
|
+
parseSuggestionMap: typeof parseSuggestionMap;
|
|
17
|
+
};
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=templates-fix-plan-llm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates-fix-plan-llm.d.ts","sourceRoot":"","sources":["../../src/audit/templates-fix-plan-llm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAChF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEjF,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,WAAW,CAAC;IACtB,UAAU,EAAE,qBAAqB,CAAC;IAClC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACjE;AAED,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,gBAAgB,CAAC,CAuC3B;AAMD,iBAAS,0BAA0B,CACjC,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,SAAS,eAAe,EAAE,GAChC,UAAU,EAAE,CAiFd;AAOD,iBAAS,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAiC5D;AAED,eAAO,MAAM,WAAW;;;CAAqD,CAAC"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { AiMessageRole } from '@shrkcrft/ai';
|
|
2
|
+
export async function enrichFixPlanWithLlm(plan, options) {
|
|
3
|
+
const byTemplate = new Map();
|
|
4
|
+
for (const fix of plan.fixes) {
|
|
5
|
+
const list = byTemplate.get(fix.templateId) ?? [];
|
|
6
|
+
list.push(fix);
|
|
7
|
+
byTemplate.set(fix.templateId, list);
|
|
8
|
+
}
|
|
9
|
+
const enrichedFixes = [];
|
|
10
|
+
for (const [templateId, fixes] of byTemplate.entries()) {
|
|
11
|
+
const template = options.inspection.templateRegistry.get(templateId);
|
|
12
|
+
if (!template) {
|
|
13
|
+
enrichedFixes.push(...fixes);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const messages = buildFixSuggestionMessages(template, fixes);
|
|
18
|
+
const res = await options.provider.send({
|
|
19
|
+
messages,
|
|
20
|
+
maxTokens: options.maxTokensPerTemplate ?? 1024,
|
|
21
|
+
});
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
options.onPerTemplateError?.(templateId, res.error);
|
|
24
|
+
enrichedFixes.push(...fixes);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const suggestions = parseSuggestionMap(res.value.content);
|
|
28
|
+
for (const fix of fixes) {
|
|
29
|
+
const key = suggestionKey(fix.findingCategory, fix.finding);
|
|
30
|
+
const suggestion = suggestions.get(key) ?? suggestions.get(fix.findingCategory) ?? null;
|
|
31
|
+
enrichedFixes.push(suggestion ? { ...fix, llmSuggestion: suggestion } : fix);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
options.onPerTemplateError?.(templateId, e);
|
|
36
|
+
enrichedFixes.push(...fixes);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { ...plan, fixes: enrichedFixes };
|
|
40
|
+
}
|
|
41
|
+
function suggestionKey(category, finding) {
|
|
42
|
+
return `${category}::${finding.slice(0, 80)}`;
|
|
43
|
+
}
|
|
44
|
+
function buildFixSuggestionMessages(template, fixes) {
|
|
45
|
+
const body = typeof template.content === 'string'
|
|
46
|
+
? (template.content)
|
|
47
|
+
: '(function body — not introspectable as a string)';
|
|
48
|
+
const targetPathSource = typeof template.targetPath === 'string'
|
|
49
|
+
? `string: ${template.targetPath}`
|
|
50
|
+
: `function (signature only)`;
|
|
51
|
+
const system = {
|
|
52
|
+
role: AiMessageRole.System,
|
|
53
|
+
content: [
|
|
54
|
+
'You are a senior maintainer sharpening fix recommendations for a SharkCraft scaffold template.',
|
|
55
|
+
'For each finding listed, the deterministic layer has already produced a sound agent prompt with a generic placeholder (e.g. <sample value>, <TODO>).',
|
|
56
|
+
'Your job: replace those placeholders with a CONCRETE recommendation grounded in the supplied template body, variables, and target path.',
|
|
57
|
+
'',
|
|
58
|
+
'Per finding category, prefer to emit:',
|
|
59
|
+
' - required-var-no-example → a literal example value, in single quotes, matching any declared pattern and the variable\'s role.',
|
|
60
|
+
' - undocumented-var → a one-sentence description anchored in how the variable is used in the body.',
|
|
61
|
+
' - undeclared-var → either "ADD: <variable spec>" or "REMOVE: <placeholder>" with a one-line rationale.',
|
|
62
|
+
' - related-id-unresolved → confirm removal; if a near-match exists in supplied context, name it.',
|
|
63
|
+
' - path-no-convention → either "ADD paths.ts entry: <pattern>" or "CHANGE template targetPath: <pattern>", whichever is less intrusive.',
|
|
64
|
+
' - missing-name → a short, descriptive name.',
|
|
65
|
+
' - missing-description → a one-sentence description of what the template scaffolds.',
|
|
66
|
+
' - any other category → a one-sentence concrete suggestion.',
|
|
67
|
+
'',
|
|
68
|
+
'Return ONLY a JSON object with this exact shape, no preface, no fences:',
|
|
69
|
+
'{',
|
|
70
|
+
' "suggestions": [',
|
|
71
|
+
' {',
|
|
72
|
+
' "findingCategory": "<exact category>",',
|
|
73
|
+
' "finding": "<first 80 chars of the original finding message>",',
|
|
74
|
+
' "suggestion": "<one to three sentences, concrete>"',
|
|
75
|
+
' }',
|
|
76
|
+
' ]',
|
|
77
|
+
'}',
|
|
78
|
+
'Omit entries you cannot meaningfully sharpen — better silence than ceremony.',
|
|
79
|
+
].join('\n'),
|
|
80
|
+
};
|
|
81
|
+
const user = {
|
|
82
|
+
role: AiMessageRole.User,
|
|
83
|
+
content: [
|
|
84
|
+
`# Template: ${template.id}`,
|
|
85
|
+
`name: ${template.name}`,
|
|
86
|
+
`description: ${template.description}`,
|
|
87
|
+
`tags: ${template.tags.join(', ') || '(none)'}`,
|
|
88
|
+
``,
|
|
89
|
+
`## Declared variables`,
|
|
90
|
+
template.variables.length === 0
|
|
91
|
+
? '(none)'
|
|
92
|
+
: template.variables
|
|
93
|
+
.map((v) => `- ${v.name}${v.required ? ' (required)' : ''}` +
|
|
94
|
+
(v.pattern ? ` pattern=${v.pattern.source}` : '') +
|
|
95
|
+
(v.examples?.length ? ` examples=[${v.examples.join(', ')}]` : '') +
|
|
96
|
+
(v.description ? `\n ${v.description}` : ''))
|
|
97
|
+
.join('\n'),
|
|
98
|
+
``,
|
|
99
|
+
`## targetPath`,
|
|
100
|
+
targetPathSource,
|
|
101
|
+
``,
|
|
102
|
+
`## Template body (truncated to ~3 KB)`,
|
|
103
|
+
'```',
|
|
104
|
+
truncate(body, 3072),
|
|
105
|
+
'```',
|
|
106
|
+
``,
|
|
107
|
+
`## Fixes needing sharpening`,
|
|
108
|
+
fixes
|
|
109
|
+
.map((f, i) => `### Fix ${i + 1}: ${f.findingCategory}\nfinding: ${f.finding}\ncurrent agent prompt:\n${f.agentPrompt}`)
|
|
110
|
+
.join('\n\n'),
|
|
111
|
+
].join('\n'),
|
|
112
|
+
};
|
|
113
|
+
return [system, user];
|
|
114
|
+
}
|
|
115
|
+
function truncate(s, max) {
|
|
116
|
+
if (s.length <= max)
|
|
117
|
+
return s;
|
|
118
|
+
return `${s.slice(0, max)}\n… [truncated ${s.length - max} chars]`;
|
|
119
|
+
}
|
|
120
|
+
function parseSuggestionMap(raw) {
|
|
121
|
+
const out = new Map();
|
|
122
|
+
const trimmed = raw.trim();
|
|
123
|
+
let jsonText = trimmed;
|
|
124
|
+
const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
125
|
+
if (fenced)
|
|
126
|
+
jsonText = fenced[1].trim();
|
|
127
|
+
let parsed;
|
|
128
|
+
try {
|
|
129
|
+
parsed = JSON.parse(jsonText);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
const firstBrace = jsonText.indexOf('{');
|
|
133
|
+
const lastBrace = jsonText.lastIndexOf('}');
|
|
134
|
+
if (firstBrace < 0 || lastBrace <= firstBrace)
|
|
135
|
+
return out;
|
|
136
|
+
try {
|
|
137
|
+
parsed = JSON.parse(jsonText.slice(firstBrace, lastBrace + 1));
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (!parsed || typeof parsed !== 'object')
|
|
144
|
+
return out;
|
|
145
|
+
const list = parsed.suggestions;
|
|
146
|
+
if (!Array.isArray(list))
|
|
147
|
+
return out;
|
|
148
|
+
for (const item of list) {
|
|
149
|
+
if (!item || typeof item !== 'object')
|
|
150
|
+
continue;
|
|
151
|
+
const obj = item;
|
|
152
|
+
const category = typeof obj.findingCategory === 'string' ? obj.findingCategory.trim() : '';
|
|
153
|
+
const finding = typeof obj.finding === 'string' ? obj.finding.trim() : '';
|
|
154
|
+
const suggestion = typeof obj.suggestion === 'string' ? obj.suggestion.trim() : '';
|
|
155
|
+
if (!category || !suggestion)
|
|
156
|
+
continue;
|
|
157
|
+
const key = finding ? suggestionKey(category, finding) : category;
|
|
158
|
+
out.set(key, suggestion);
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
export const __internals = { buildFixSuggestionMessages, parseSuggestionMap };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { AuditFindingSeverity, ITemplateAuditReport } from './templates-audit.js';
|
|
2
|
+
export type FixConfidence = 'high' | 'medium' | 'low';
|
|
3
|
+
export interface IFixInstruction {
|
|
4
|
+
templateId: string;
|
|
5
|
+
findingCategory: string;
|
|
6
|
+
finding: string;
|
|
7
|
+
severity: AuditFindingSeverity;
|
|
8
|
+
intent: string;
|
|
9
|
+
agentPrompt: string;
|
|
10
|
+
confidence: FixConfidence;
|
|
11
|
+
source: 'deterministic' | 'llm';
|
|
12
|
+
/** Concrete suggestion attached by the LLM-enrichment pass when reachable. Advisory. */
|
|
13
|
+
llmSuggestion?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ISkippedFinding {
|
|
16
|
+
templateId: string;
|
|
17
|
+
findingCategory: string;
|
|
18
|
+
finding: string;
|
|
19
|
+
reason: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ITemplateFixPlan {
|
|
22
|
+
fixPlanId: string;
|
|
23
|
+
generatedAt: string;
|
|
24
|
+
auditId: string;
|
|
25
|
+
sourceFiles: readonly string[];
|
|
26
|
+
fixes: readonly IFixInstruction[];
|
|
27
|
+
skipped: readonly ISkippedFinding[];
|
|
28
|
+
summary: {
|
|
29
|
+
fixCount: number;
|
|
30
|
+
highConfidence: number;
|
|
31
|
+
mediumConfidence: number;
|
|
32
|
+
lowConfidence: number;
|
|
33
|
+
skipped: number;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export declare function buildFixPlan(report: ITemplateAuditReport): ITemplateFixPlan;
|
|
37
|
+
//# sourceMappingURL=templates-fix-plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates-fix-plan.d.ts","sourceRoot":"","sources":["../../src/audit/templates-fix-plan.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EAIpB,oBAAoB,EACrB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,aAAa,CAAC;IAC1B,MAAM,EAAE,eAAe,GAAG,KAAK,CAAC;IAChC,wFAAwF;IACxF,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,KAAK,EAAE,SAAS,eAAe,EAAE,CAAC;IAClC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IACpC,OAAO,EAAE;QACP,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,EAAE,MAAM,CAAC;QACvB,gBAAgB,EAAE,MAAM,CAAC;QACzB,aAAa,EAAE,MAAM,CAAC;QACtB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAID,wBAAgB,YAAY,CAAC,MAAM,EAAE,oBAAoB,GAAG,gBAAgB,CAiC3E"}
|