@shrkcrft/cli 0.1.0-alpha.12 → 0.1.0-alpha.14
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/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/command-catalog.d.ts.map +1 -1
- package/dist/commands/command-catalog.js +10 -0
- package/dist/commands/doctor.command.d.ts.map +1 -1
- package/dist/commands/doctor.command.js +40 -2
- package/dist/commands/smart-context.command.d.ts +28 -0
- package/dist/commands/smart-context.command.d.ts.map +1 -1
- package/dist/commands/smart-context.command.js +762 -1
- 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/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/main.d.ts.map +1 -1
- package/dist/main.js +40 -18
- package/package.json +32 -32
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import * as nodePath from 'node:path';
|
|
4
4
|
import { AiMessageRole, buildPromptMessages, EnhancementPipeline, EnhancementStageKind, OllamaProvider, buildDefaultEnhancementStages, selectAiProvider, } from '@shrkcrft/ai';
|
|
@@ -10,6 +10,17 @@ import { DeclarationKind, PLAN_CACHE_SCHEMA, PlanCache, SemanticIndex, TaskType,
|
|
|
10
10
|
import { SmartContextDetailedPlanSchema, SmartContextExpansionRequestSchema, } from "../schemas/json-schemas.js";
|
|
11
11
|
import { asJson, header, kv } from "../output/format-output.js";
|
|
12
12
|
import { printError } from "../output/print-error.js";
|
|
13
|
+
import { buildTemplateAudit, } from "../audit/templates-audit.js";
|
|
14
|
+
import { enrichAuditWithLlm } from "../audit/templates-audit-llm.js";
|
|
15
|
+
import { buildFixPlan, } from "../audit/templates-fix-plan.js";
|
|
16
|
+
import { enrichFixPlanWithLlm } from "../audit/templates-fix-plan-llm.js";
|
|
17
|
+
import { buildAiBlock, renderAiBlockMarkdown } from '@shrkcrft/ai';
|
|
18
|
+
import { buildKnowledgeAudit, } from "../audit/knowledge-audit.js";
|
|
19
|
+
import { enrichKnowledgeAuditWithLlm } from "../audit/knowledge-audit-llm.js";
|
|
20
|
+
import { buildKnowledgeFixPlan, } from "../audit/knowledge-fix-plan.js";
|
|
21
|
+
import { enrichKnowledgeFixPlanWithLlm } from "../audit/knowledge-fix-plan-llm.js";
|
|
22
|
+
import { buildPipelineAudit, buildPipelineFixPlan, } from "../audit/pipeline-audit.js";
|
|
23
|
+
import { enrichPipelineAuditWithLlm } from "../audit/pipeline-audit-llm.js";
|
|
13
24
|
const SMART_CONTEXT_DIR = nodePath.join('.sharkcraft', 'smart-context');
|
|
14
25
|
/**
|
|
15
26
|
* Gemini-backed context enrichment.
|
|
@@ -318,12 +329,762 @@ export const smartContextShowCommand = {
|
|
|
318
329
|
return 0;
|
|
319
330
|
},
|
|
320
331
|
};
|
|
332
|
+
/**
|
|
333
|
+
* `shrk smart-context audit-templates` — local-LLM template audit.
|
|
334
|
+
*
|
|
335
|
+
* Orchestrates the existing deterministic template inspectors
|
|
336
|
+
* (`templates lint` + `templates drift`), dedupes their overlap by
|
|
337
|
+
* (category + message), and — when a local provider is reachable —
|
|
338
|
+
* runs an LLM critique pass per template. Always report-only: no edits
|
|
339
|
+
* to template sources, no plan emission. See
|
|
340
|
+
* docs/smart-context-audit-templates.md for the report contract.
|
|
341
|
+
*/
|
|
342
|
+
export const smartContextAuditTemplatesCommand = {
|
|
343
|
+
name: 'audit-templates',
|
|
344
|
+
description: 'Audit user templates with the deterministic inspectors and (when reachable) a local LLM critique pass. Report-only — no edits. `--fix-plan` adds a Claude-targetable fix plan derived from the report.',
|
|
345
|
+
usage: 'shrk smart-context audit-templates [--id <templateId>] [--no-enhance] [--provider auto|ollama|llamacpp] [--model <id>] [--save] [--json] [--fix-plan] [--only-plan]',
|
|
346
|
+
async run(args) {
|
|
347
|
+
const cwd = resolveCwd(args);
|
|
348
|
+
const json = flagBool(args, 'json');
|
|
349
|
+
const save = flagBool(args, 'save');
|
|
350
|
+
const noEnhance = flagBool(args, 'no-enhance');
|
|
351
|
+
const templateId = flagString(args, 'id');
|
|
352
|
+
const providerKind = flagString(args, 'provider');
|
|
353
|
+
const model = flagString(args, 'model');
|
|
354
|
+
const wantFixPlan = flagBool(args, 'fix-plan') || flagBool(args, 'only-plan');
|
|
355
|
+
const onlyPlan = flagBool(args, 'only-plan');
|
|
356
|
+
const inspection = await inspectSharkcraft({ cwd });
|
|
357
|
+
let report = buildTemplateAudit(inspection, templateId ? { templateId } : {});
|
|
358
|
+
if (report.templates.length === 0) {
|
|
359
|
+
if (json) {
|
|
360
|
+
process.stdout.write(asJson(report) + '\n');
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
process.stdout.write(templateId
|
|
364
|
+
? `No user template with id "${templateId}".\n`
|
|
365
|
+
: 'No user templates registered.\n');
|
|
366
|
+
}
|
|
367
|
+
return templateId ? 1 : 0;
|
|
368
|
+
}
|
|
369
|
+
const selection = noEnhance ? null : selectAiProvider(providerKind);
|
|
370
|
+
if (selection?.provider) {
|
|
371
|
+
if (model)
|
|
372
|
+
selection.provider.configure({ model });
|
|
373
|
+
if (!json) {
|
|
374
|
+
process.stderr.write(`[audit-templates] enriching with provider ${selection.provider.id}…\n`);
|
|
375
|
+
}
|
|
376
|
+
report = await enrichAuditWithLlm(report, {
|
|
377
|
+
provider: selection.provider,
|
|
378
|
+
inspection,
|
|
379
|
+
onPerTemplateError: (id, err) => {
|
|
380
|
+
if (!json) {
|
|
381
|
+
process.stderr.write(`[audit-templates] LLM pass failed for ${id}: ${err.message.slice(0, 120)} — keeping deterministic findings only.\n`);
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
else if (!noEnhance && !json) {
|
|
387
|
+
process.stderr.write('[audit-templates] no local LLM reachable — running deterministic-only audit. See `ai.hints` in the output for setup steps.\n');
|
|
388
|
+
}
|
|
389
|
+
report = { ...report, ai: buildAiBlock({ selection, userOptedOut: noEnhance }) };
|
|
390
|
+
let fixPlan = wantFixPlan ? buildFixPlan(report) : null;
|
|
391
|
+
if (fixPlan && selection?.provider) {
|
|
392
|
+
if (!json) {
|
|
393
|
+
process.stderr.write('[audit-templates] sharpening fix plan with LLM suggestions…\n');
|
|
394
|
+
}
|
|
395
|
+
fixPlan = await enrichFixPlanWithLlm(fixPlan, {
|
|
396
|
+
provider: selection.provider,
|
|
397
|
+
inspection,
|
|
398
|
+
onPerTemplateError: (id, err) => {
|
|
399
|
+
if (!json) {
|
|
400
|
+
process.stderr.write(`[audit-templates] LLM fix-plan pass failed for ${id}: ${err.message.slice(0, 120)} — keeping deterministic prompts.\n`);
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
if (save) {
|
|
406
|
+
const saved = saveAuditReport(cwd, report, fixPlan);
|
|
407
|
+
if (json) {
|
|
408
|
+
process.stdout.write(asJson({ saved, report, ...(fixPlan ? { fixPlan } : {}) }) + '\n');
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
process.stdout.write(`Audit saved → ${saved.mdPath}\n`);
|
|
412
|
+
process.stdout.write(` → ${saved.jsonPath}\n`);
|
|
413
|
+
if (saved.planMdPath && saved.planJsonPath) {
|
|
414
|
+
process.stdout.write(`Plan saved → ${saved.planMdPath}\n`);
|
|
415
|
+
process.stdout.write(` → ${saved.planJsonPath}\n`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return exitCodeForAudit(report);
|
|
419
|
+
}
|
|
420
|
+
if (json) {
|
|
421
|
+
if (onlyPlan && fixPlan) {
|
|
422
|
+
process.stdout.write(asJson(fixPlan) + '\n');
|
|
423
|
+
}
|
|
424
|
+
else if (fixPlan) {
|
|
425
|
+
process.stdout.write(asJson({ report, fixPlan }) + '\n');
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
process.stdout.write(asJson(report) + '\n');
|
|
429
|
+
}
|
|
430
|
+
return exitCodeForAudit(report);
|
|
431
|
+
}
|
|
432
|
+
if (!onlyPlan) {
|
|
433
|
+
process.stdout.write(renderAuditMarkdown(report));
|
|
434
|
+
}
|
|
435
|
+
if (fixPlan) {
|
|
436
|
+
if (!onlyPlan)
|
|
437
|
+
process.stdout.write('\n');
|
|
438
|
+
process.stdout.write(renderFixPlanMarkdown(fixPlan));
|
|
439
|
+
}
|
|
440
|
+
return exitCodeForAudit(report);
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
function exitCodeForAudit(report) {
|
|
444
|
+
if (report.summary.broken > 0)
|
|
445
|
+
return 1;
|
|
446
|
+
return 0;
|
|
447
|
+
}
|
|
448
|
+
function saveAuditReport(cwd, report, fixPlan) {
|
|
449
|
+
const dir = nodePath.join(cwd, SMART_CONTEXT_DIR);
|
|
450
|
+
mkdirSync(dir, { recursive: true });
|
|
451
|
+
const slug = report.auditId;
|
|
452
|
+
const mdPath = nodePath.join(dir, `${slug}.md`);
|
|
453
|
+
const jsonPath = nodePath.join(dir, `${slug}.json`);
|
|
454
|
+
writeFileSync(jsonPath, JSON.stringify(report, null, 2), 'utf8');
|
|
455
|
+
writeFileSync(mdPath, renderAuditMarkdown(report), 'utf8');
|
|
456
|
+
if (!fixPlan)
|
|
457
|
+
return { slug, mdPath, jsonPath };
|
|
458
|
+
const planSlug = fixPlan.fixPlanId;
|
|
459
|
+
const planMdPath = nodePath.join(dir, `${planSlug}.md`);
|
|
460
|
+
const planJsonPath = nodePath.join(dir, `${planSlug}.json`);
|
|
461
|
+
writeFileSync(planJsonPath, JSON.stringify(fixPlan, null, 2), 'utf8');
|
|
462
|
+
writeFileSync(planMdPath, renderFixPlanMarkdown(fixPlan), 'utf8');
|
|
463
|
+
return { slug, mdPath, jsonPath, planMdPath, planJsonPath };
|
|
464
|
+
}
|
|
465
|
+
function renderAuditMarkdown(report) {
|
|
466
|
+
const out = [];
|
|
467
|
+
out.push(`# Template audit — ${report.auditId}`);
|
|
468
|
+
out.push('');
|
|
469
|
+
out.push(`- generated: ${report.generatedAt}`);
|
|
470
|
+
out.push(`- llm enriched: ${report.llmEnriched ? `yes (${report.llmProviderId ?? 'unknown'})` : 'no — deterministic only'}`);
|
|
471
|
+
out.push(`- summary: ok=${report.summary.ok}, minor=${report.summary.minor}, stale=${report.summary.stale}, broken=${report.summary.broken} (total ${report.summary.total})`);
|
|
472
|
+
if (report.skipped.length > 0) {
|
|
473
|
+
out.push(`- skipped: ${report.skipped.length} (${report.skipped.map((s) => s.templateId).join(', ')})`);
|
|
474
|
+
}
|
|
475
|
+
out.push('');
|
|
476
|
+
const order = ['broken', 'stale', 'minor', 'ok'];
|
|
477
|
+
for (const verdict of order) {
|
|
478
|
+
const inGroup = report.templates.filter((t) => t.verdict === verdict);
|
|
479
|
+
if (inGroup.length === 0)
|
|
480
|
+
continue;
|
|
481
|
+
out.push(`## ${verdict.toUpperCase()} (${inGroup.length})`);
|
|
482
|
+
out.push('');
|
|
483
|
+
for (const entry of inGroup) {
|
|
484
|
+
out.push(`### \`${entry.templateId}\` — ${entry.templateName}`);
|
|
485
|
+
out.push(`usage: ${entry.usage}`);
|
|
486
|
+
if (entry.deterministicFindings.length === 0 && entry.llmFindings.length === 0) {
|
|
487
|
+
out.push('No findings.');
|
|
488
|
+
out.push('');
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (entry.deterministicFindings.length > 0) {
|
|
492
|
+
out.push('');
|
|
493
|
+
out.push('Findings:');
|
|
494
|
+
for (const f of entry.deterministicFindings) {
|
|
495
|
+
out.push(`- **[deterministic]** ${f.severity} \`${f.category}\` — ${f.message} _(sources: ${f.sources.join(', ')})_`);
|
|
496
|
+
if (f.suggestion)
|
|
497
|
+
out.push(` - ↳ ${f.suggestion}`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (entry.llmFindings.length > 0) {
|
|
501
|
+
out.push('');
|
|
502
|
+
out.push('LLM-flagged (advisory):');
|
|
503
|
+
for (const f of entry.llmFindings) {
|
|
504
|
+
out.push(`- **[llm]** ${f.severity} \`${f.category}\` (confidence ${f.confidence.toFixed(2)}) — ${f.message}`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (entry.suggestedActions.length > 0) {
|
|
508
|
+
out.push('');
|
|
509
|
+
out.push('Suggested actions:');
|
|
510
|
+
for (const a of entry.suggestedActions) {
|
|
511
|
+
out.push(`- \`${a.kind}\` ${a.target} — ${a.note}`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
out.push('');
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (report.ai) {
|
|
518
|
+
out.push(renderAiBlockMarkdown(report.ai));
|
|
519
|
+
}
|
|
520
|
+
return out.join('\n') + '\n';
|
|
521
|
+
}
|
|
522
|
+
function renderFixPlanMarkdown(plan) {
|
|
523
|
+
const out = [];
|
|
524
|
+
out.push(`# Template fix plan — ${plan.fixPlanId}`);
|
|
525
|
+
out.push('');
|
|
526
|
+
out.push(`- generated: ${plan.generatedAt}`);
|
|
527
|
+
out.push(`- derived from audit: ${plan.auditId}`);
|
|
528
|
+
out.push(`- source files Claude will edit: ${plan.sourceFiles.join(', ')}`);
|
|
529
|
+
out.push(`- summary: ${plan.summary.fixCount} fix(es) — high=${plan.summary.highConfidence}, medium=${plan.summary.mediumConfidence}, low=${plan.summary.lowConfidence}; skipped=${plan.summary.skipped}`);
|
|
530
|
+
out.push('');
|
|
531
|
+
if (plan.fixes.length === 0) {
|
|
532
|
+
out.push('No fix instructions emitted.');
|
|
533
|
+
out.push('');
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
const order = ['high', 'medium', 'low'];
|
|
537
|
+
for (const confidence of order) {
|
|
538
|
+
const inGroup = plan.fixes.filter((f) => f.confidence === confidence);
|
|
539
|
+
if (inGroup.length === 0)
|
|
540
|
+
continue;
|
|
541
|
+
out.push(`## Confidence: ${confidence.toUpperCase()} (${inGroup.length})`);
|
|
542
|
+
out.push('');
|
|
543
|
+
for (const fix of inGroup) {
|
|
544
|
+
out.push(`### \`${fix.templateId}\` — \`${fix.findingCategory}\` _(${fix.source}, ${fix.severity})_`);
|
|
545
|
+
out.push(`**Intent.** ${fix.intent}`);
|
|
546
|
+
out.push('');
|
|
547
|
+
out.push(`Original finding: ${fix.finding}`);
|
|
548
|
+
out.push('');
|
|
549
|
+
out.push('Agent prompt:');
|
|
550
|
+
out.push('```');
|
|
551
|
+
out.push(fix.agentPrompt);
|
|
552
|
+
out.push('```');
|
|
553
|
+
if (fix.llmSuggestion) {
|
|
554
|
+
out.push('');
|
|
555
|
+
out.push('LLM suggestion (advisory):');
|
|
556
|
+
out.push('> ' + fix.llmSuggestion.split('\n').join('\n> '));
|
|
557
|
+
}
|
|
558
|
+
out.push('');
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if (plan.skipped.length > 0) {
|
|
563
|
+
out.push(`## Skipped (${plan.skipped.length})`);
|
|
564
|
+
out.push('');
|
|
565
|
+
for (const s of plan.skipped) {
|
|
566
|
+
out.push(`- \`${s.templateId}\` / \`${s.findingCategory}\` — ${s.reason}`);
|
|
567
|
+
out.push(` - finding: ${s.finding}`);
|
|
568
|
+
}
|
|
569
|
+
out.push('');
|
|
570
|
+
}
|
|
571
|
+
return out.join('\n') + '\n';
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* `shrk smart-context audit-knowledge` — local-LLM knowledge audit.
|
|
575
|
+
*
|
|
576
|
+
* Wraps `lintKnowledge` + `buildKnowledgeStaleReport` from `@shrkcrft/inspector`,
|
|
577
|
+
* then layers LLM critique (when a provider is reachable) and emits a
|
|
578
|
+
* Claude-targetable fix plan. Report-only — no writes to knowledge sources.
|
|
579
|
+
* See docs/smart-context-audit-templates.md for the shared report contract.
|
|
580
|
+
*/
|
|
581
|
+
export const smartContextAuditKnowledgeCommand = {
|
|
582
|
+
name: 'audit-knowledge',
|
|
583
|
+
description: 'Audit user knowledge entries with the deterministic inspectors (lint + stale-reference check) and (when reachable) a local LLM critique pass. Report-only — no edits.',
|
|
584
|
+
usage: 'shrk smart-context audit-knowledge [--id <entryId>] [--no-enhance] [--no-stale-check] [--provider auto|ollama|llamacpp] [--model <id>] [--save] [--json] [--fix-plan] [--only-plan]',
|
|
585
|
+
async run(args) {
|
|
586
|
+
const cwd = resolveCwd(args);
|
|
587
|
+
const json = flagBool(args, 'json');
|
|
588
|
+
const save = flagBool(args, 'save');
|
|
589
|
+
const noEnhance = flagBool(args, 'no-enhance');
|
|
590
|
+
const noStaleCheck = flagBool(args, 'no-stale-check');
|
|
591
|
+
const entryId = flagString(args, 'id');
|
|
592
|
+
const providerKind = flagString(args, 'provider');
|
|
593
|
+
const model = flagString(args, 'model');
|
|
594
|
+
const wantFixPlan = flagBool(args, 'fix-plan') || flagBool(args, 'only-plan');
|
|
595
|
+
const onlyPlan = flagBool(args, 'only-plan');
|
|
596
|
+
const inspection = await inspectSharkcraft({ cwd });
|
|
597
|
+
let report = buildKnowledgeAudit(inspection, {
|
|
598
|
+
...(entryId ? { entryId } : {}),
|
|
599
|
+
...(noStaleCheck ? { skipStaleCheck: true } : {}),
|
|
600
|
+
});
|
|
601
|
+
if (report.entries.length === 0) {
|
|
602
|
+
if (json) {
|
|
603
|
+
process.stdout.write(asJson(report) + '\n');
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
process.stdout.write(entryId
|
|
607
|
+
? `No user knowledge entry with id "${entryId}".\n`
|
|
608
|
+
: 'No user knowledge entries registered.\n');
|
|
609
|
+
}
|
|
610
|
+
return entryId ? 1 : 0;
|
|
611
|
+
}
|
|
612
|
+
const selection = noEnhance ? null : selectAiProvider(providerKind);
|
|
613
|
+
if (selection?.provider) {
|
|
614
|
+
if (model)
|
|
615
|
+
selection.provider.configure({ model });
|
|
616
|
+
if (!json) {
|
|
617
|
+
process.stderr.write(`[audit-knowledge] enriching with provider ${selection.provider.id}…\n`);
|
|
618
|
+
}
|
|
619
|
+
report = await enrichKnowledgeAuditWithLlm(report, {
|
|
620
|
+
provider: selection.provider,
|
|
621
|
+
inspection,
|
|
622
|
+
onPerEntryError: (id, err) => {
|
|
623
|
+
if (!json) {
|
|
624
|
+
process.stderr.write(`[audit-knowledge] LLM pass failed for ${id}: ${err.message.slice(0, 120)} — keeping deterministic findings only.\n`);
|
|
625
|
+
}
|
|
626
|
+
},
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
else if (!noEnhance && !json) {
|
|
630
|
+
process.stderr.write('[audit-knowledge] no local LLM reachable — running deterministic-only audit. See `ai.hints` in the output for setup steps.\n');
|
|
631
|
+
}
|
|
632
|
+
report = { ...report, ai: buildAiBlock({ selection, userOptedOut: noEnhance }) };
|
|
633
|
+
let fixPlan = wantFixPlan ? buildKnowledgeFixPlan(report) : null;
|
|
634
|
+
if (fixPlan && selection?.provider) {
|
|
635
|
+
if (!json) {
|
|
636
|
+
process.stderr.write('[audit-knowledge] sharpening fix plan with LLM suggestions…\n');
|
|
637
|
+
}
|
|
638
|
+
fixPlan = await enrichKnowledgeFixPlanWithLlm(fixPlan, {
|
|
639
|
+
provider: selection.provider,
|
|
640
|
+
inspection,
|
|
641
|
+
onPerEntryError: (id, err) => {
|
|
642
|
+
if (!json) {
|
|
643
|
+
process.stderr.write(`[audit-knowledge] LLM fix-plan pass failed for ${id}: ${err.message.slice(0, 120)} — keeping deterministic prompts.\n`);
|
|
644
|
+
}
|
|
645
|
+
},
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
if (save) {
|
|
649
|
+
const saved = saveKnowledgeAuditReport(cwd, report, fixPlan);
|
|
650
|
+
if (json) {
|
|
651
|
+
process.stdout.write(asJson({ saved, report, ...(fixPlan ? { fixPlan } : {}) }) + '\n');
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
process.stdout.write(`Audit saved → ${saved.mdPath}\n`);
|
|
655
|
+
process.stdout.write(` → ${saved.jsonPath}\n`);
|
|
656
|
+
if (saved.planMdPath && saved.planJsonPath) {
|
|
657
|
+
process.stdout.write(`Plan saved → ${saved.planMdPath}\n`);
|
|
658
|
+
process.stdout.write(` → ${saved.planJsonPath}\n`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return exitCodeForKnowledgeAudit(report);
|
|
662
|
+
}
|
|
663
|
+
if (json) {
|
|
664
|
+
if (onlyPlan && fixPlan) {
|
|
665
|
+
process.stdout.write(asJson(fixPlan) + '\n');
|
|
666
|
+
}
|
|
667
|
+
else if (fixPlan) {
|
|
668
|
+
process.stdout.write(asJson({ report, fixPlan }) + '\n');
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
process.stdout.write(asJson(report) + '\n');
|
|
672
|
+
}
|
|
673
|
+
return exitCodeForKnowledgeAudit(report);
|
|
674
|
+
}
|
|
675
|
+
if (!onlyPlan) {
|
|
676
|
+
process.stdout.write(renderKnowledgeAuditMarkdown(report));
|
|
677
|
+
}
|
|
678
|
+
if (fixPlan) {
|
|
679
|
+
if (!onlyPlan)
|
|
680
|
+
process.stdout.write('\n');
|
|
681
|
+
process.stdout.write(renderKnowledgeFixPlanMarkdown(fixPlan));
|
|
682
|
+
}
|
|
683
|
+
return exitCodeForKnowledgeAudit(report);
|
|
684
|
+
},
|
|
685
|
+
};
|
|
686
|
+
function exitCodeForKnowledgeAudit(report) {
|
|
687
|
+
if (report.summary.broken > 0)
|
|
688
|
+
return 1;
|
|
689
|
+
return 0;
|
|
690
|
+
}
|
|
691
|
+
function saveKnowledgeAuditReport(cwd, report, fixPlan) {
|
|
692
|
+
const dir = nodePath.join(cwd, SMART_CONTEXT_DIR);
|
|
693
|
+
mkdirSync(dir, { recursive: true });
|
|
694
|
+
const slug = `knowledge-${report.auditId}`;
|
|
695
|
+
const mdPath = nodePath.join(dir, `${slug}.md`);
|
|
696
|
+
const jsonPath = nodePath.join(dir, `${slug}.json`);
|
|
697
|
+
writeFileSync(jsonPath, JSON.stringify(report, null, 2), 'utf8');
|
|
698
|
+
writeFileSync(mdPath, renderKnowledgeAuditMarkdown(report), 'utf8');
|
|
699
|
+
if (!fixPlan)
|
|
700
|
+
return { slug, mdPath, jsonPath };
|
|
701
|
+
const planSlug = `knowledge-${fixPlan.fixPlanId}`;
|
|
702
|
+
const planMdPath = nodePath.join(dir, `${planSlug}.md`);
|
|
703
|
+
const planJsonPath = nodePath.join(dir, `${planSlug}.json`);
|
|
704
|
+
writeFileSync(planJsonPath, JSON.stringify(fixPlan, null, 2), 'utf8');
|
|
705
|
+
writeFileSync(planMdPath, renderKnowledgeFixPlanMarkdown(fixPlan), 'utf8');
|
|
706
|
+
return { slug, mdPath, jsonPath, planMdPath, planJsonPath };
|
|
707
|
+
}
|
|
708
|
+
function renderKnowledgeAuditMarkdown(report) {
|
|
709
|
+
const out = [];
|
|
710
|
+
out.push(`# Knowledge audit — ${report.auditId}`);
|
|
711
|
+
out.push('');
|
|
712
|
+
out.push(`- generated: ${report.generatedAt}`);
|
|
713
|
+
out.push(`- llm enriched: ${report.llmEnriched ? `yes (${report.llmProviderId ?? 'unknown'})` : 'no — deterministic only'}`);
|
|
714
|
+
out.push(`- summary: ok=${report.summary.ok}, minor=${report.summary.minor}, stale=${report.summary.stale}, broken=${report.summary.broken} (total ${report.summary.total})`);
|
|
715
|
+
if (report.skipped.length > 0) {
|
|
716
|
+
out.push(`- skipped: ${report.skipped.length} (pack-contributed)`);
|
|
717
|
+
}
|
|
718
|
+
out.push('');
|
|
719
|
+
const order = ['broken', 'stale', 'minor', 'ok'];
|
|
720
|
+
for (const verdict of order) {
|
|
721
|
+
const inGroup = report.entries.filter((t) => t.verdict === verdict);
|
|
722
|
+
if (inGroup.length === 0)
|
|
723
|
+
continue;
|
|
724
|
+
out.push(`## ${verdict.toUpperCase()} (${inGroup.length})`);
|
|
725
|
+
out.push('');
|
|
726
|
+
for (const entry of inGroup) {
|
|
727
|
+
out.push(`### \`${entry.entryId}\` (${entry.entryType}) — ${entry.title}`);
|
|
728
|
+
if (entry.deterministicFindings.length === 0 && entry.llmFindings.length === 0) {
|
|
729
|
+
out.push('No findings.');
|
|
730
|
+
out.push('');
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
if (entry.deterministicFindings.length > 0) {
|
|
734
|
+
out.push('');
|
|
735
|
+
out.push('Findings:');
|
|
736
|
+
for (const f of entry.deterministicFindings) {
|
|
737
|
+
out.push(`- **[deterministic]** ${f.severity} \`${f.category}\` (${f.field}) — ${f.message} _(sources: ${f.sources.join(', ')})_`);
|
|
738
|
+
if (f.fixSuggestion)
|
|
739
|
+
out.push(` - ↳ ${f.fixSuggestion}`);
|
|
740
|
+
if (f.stubSuggestion)
|
|
741
|
+
out.push(` - stub: ${f.stubSuggestion}`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
if (entry.llmFindings.length > 0) {
|
|
745
|
+
out.push('');
|
|
746
|
+
out.push('LLM-flagged (advisory):');
|
|
747
|
+
for (const f of entry.llmFindings) {
|
|
748
|
+
out.push(`- **[llm]** ${f.severity} \`${f.category}\` (confidence ${f.confidence.toFixed(2)}) — ${f.message}`);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (entry.suggestedActions.length > 0) {
|
|
752
|
+
out.push('');
|
|
753
|
+
out.push('Suggested actions:');
|
|
754
|
+
for (const a of entry.suggestedActions) {
|
|
755
|
+
out.push(`- \`${a.kind}\` ${a.target} — ${a.note}`);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
out.push('');
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
if (report.ai) {
|
|
762
|
+
out.push(renderAiBlockMarkdown(report.ai));
|
|
763
|
+
}
|
|
764
|
+
return out.join('\n') + '\n';
|
|
765
|
+
}
|
|
766
|
+
function renderKnowledgeFixPlanMarkdown(plan) {
|
|
767
|
+
const out = [];
|
|
768
|
+
out.push(`# Knowledge fix plan — ${plan.fixPlanId}`);
|
|
769
|
+
out.push('');
|
|
770
|
+
out.push(`- generated: ${plan.generatedAt}`);
|
|
771
|
+
out.push(`- derived from audit: ${plan.auditId}`);
|
|
772
|
+
out.push(`- source hint: ${plan.sourceHint}`);
|
|
773
|
+
out.push(`- summary: ${plan.summary.fixCount} fix(es) — high=${plan.summary.highConfidence}, medium=${plan.summary.mediumConfidence}, low=${plan.summary.lowConfidence}; skipped=${plan.summary.skipped}`);
|
|
774
|
+
out.push('');
|
|
775
|
+
if (plan.fixes.length === 0) {
|
|
776
|
+
out.push('No fix instructions emitted.');
|
|
777
|
+
out.push('');
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
const order = ['high', 'medium', 'low'];
|
|
781
|
+
for (const confidence of order) {
|
|
782
|
+
const inGroup = plan.fixes.filter((f) => f.confidence === confidence);
|
|
783
|
+
if (inGroup.length === 0)
|
|
784
|
+
continue;
|
|
785
|
+
out.push(`## Confidence: ${confidence.toUpperCase()} (${inGroup.length})`);
|
|
786
|
+
out.push('');
|
|
787
|
+
for (const fix of inGroup) {
|
|
788
|
+
out.push(`### \`${fix.entryId}\` — \`${fix.findingCategory}\` _(${fix.source}, ${fix.severity})_`);
|
|
789
|
+
out.push(`**Intent.** ${fix.intent}`);
|
|
790
|
+
out.push('');
|
|
791
|
+
out.push(`Original finding: ${fix.finding}`);
|
|
792
|
+
out.push('');
|
|
793
|
+
out.push('Agent prompt:');
|
|
794
|
+
out.push('```');
|
|
795
|
+
out.push(fix.agentPrompt);
|
|
796
|
+
out.push('```');
|
|
797
|
+
if (fix.llmSuggestion) {
|
|
798
|
+
out.push('');
|
|
799
|
+
out.push('LLM suggestion (advisory):');
|
|
800
|
+
out.push('> ' + fix.llmSuggestion.split('\n').join('\n> '));
|
|
801
|
+
}
|
|
802
|
+
out.push('');
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (plan.skipped.length > 0) {
|
|
807
|
+
out.push(`## Skipped (${plan.skipped.length})`);
|
|
808
|
+
out.push('');
|
|
809
|
+
for (const s of plan.skipped) {
|
|
810
|
+
out.push(`- \`${s.entryId}\` / \`${s.findingCategory}\` — ${s.reason}`);
|
|
811
|
+
out.push(` - finding: ${s.finding}`);
|
|
812
|
+
}
|
|
813
|
+
out.push('');
|
|
814
|
+
}
|
|
815
|
+
return out.join('\n') + '\n';
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* `shrk smart-context audit-pipelines` — local-LLM pipeline audit.
|
|
819
|
+
*
|
|
820
|
+
* Wraps `lintPipelines` from `@shrkcrft/inspector`, optionally layers
|
|
821
|
+
* LLM critique, and emits a Claude-targetable fix plan. Same report-only
|
|
822
|
+
* contract as audit-templates / audit-knowledge.
|
|
823
|
+
*/
|
|
824
|
+
export const smartContextAuditPipelinesCommand = {
|
|
825
|
+
name: 'audit-pipelines',
|
|
826
|
+
description: 'Audit registered pipelines with the deterministic inspector and (when reachable) a local LLM critique pass. Report-only — no edits.',
|
|
827
|
+
usage: 'shrk smart-context audit-pipelines [--id <pipelineId>] [--no-enhance] [--provider auto|ollama|llamacpp] [--model <id>] [--save] [--json] [--fix-plan] [--only-plan]',
|
|
828
|
+
async run(args) {
|
|
829
|
+
const cwd = resolveCwd(args);
|
|
830
|
+
const json = flagBool(args, 'json');
|
|
831
|
+
const save = flagBool(args, 'save');
|
|
832
|
+
const noEnhance = flagBool(args, 'no-enhance');
|
|
833
|
+
const pipelineId = flagString(args, 'id');
|
|
834
|
+
const providerKind = flagString(args, 'provider');
|
|
835
|
+
const model = flagString(args, 'model');
|
|
836
|
+
const wantFixPlan = flagBool(args, 'fix-plan') || flagBool(args, 'only-plan');
|
|
837
|
+
const onlyPlan = flagBool(args, 'only-plan');
|
|
838
|
+
const inspection = await inspectSharkcraft({ cwd });
|
|
839
|
+
let report = buildPipelineAudit(inspection, pipelineId ? { pipelineId } : {});
|
|
840
|
+
if (report.pipelines.length === 0) {
|
|
841
|
+
if (json) {
|
|
842
|
+
process.stdout.write(asJson(report) + '\n');
|
|
843
|
+
}
|
|
844
|
+
else {
|
|
845
|
+
process.stdout.write(pipelineId
|
|
846
|
+
? `No pipeline with id "${pipelineId}".\n`
|
|
847
|
+
: 'No pipelines registered.\n');
|
|
848
|
+
}
|
|
849
|
+
return pipelineId ? 1 : 0;
|
|
850
|
+
}
|
|
851
|
+
const selection = noEnhance ? null : selectAiProvider(providerKind);
|
|
852
|
+
if (selection?.provider) {
|
|
853
|
+
if (model)
|
|
854
|
+
selection.provider.configure({ model });
|
|
855
|
+
if (!json) {
|
|
856
|
+
process.stderr.write(`[audit-pipelines] enriching with provider ${selection.provider.id}…\n`);
|
|
857
|
+
}
|
|
858
|
+
report = await enrichPipelineAuditWithLlm(report, {
|
|
859
|
+
provider: selection.provider,
|
|
860
|
+
inspection,
|
|
861
|
+
onPerPipelineError: (id, err) => {
|
|
862
|
+
if (!json) {
|
|
863
|
+
process.stderr.write(`[audit-pipelines] LLM pass failed for ${id}: ${err.message.slice(0, 120)} — keeping deterministic findings only.\n`);
|
|
864
|
+
}
|
|
865
|
+
},
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
else if (!noEnhance && !json) {
|
|
869
|
+
process.stderr.write('[audit-pipelines] no local LLM reachable — running deterministic-only audit. See `ai.hints` in the output for setup steps.\n');
|
|
870
|
+
}
|
|
871
|
+
report = { ...report, ai: buildAiBlock({ selection, userOptedOut: noEnhance }) };
|
|
872
|
+
const fixPlan = wantFixPlan ? buildPipelineFixPlan(report) : null;
|
|
873
|
+
if (save) {
|
|
874
|
+
const saved = savePipelineAuditReport(cwd, report, fixPlan);
|
|
875
|
+
if (json) {
|
|
876
|
+
process.stdout.write(asJson({ saved, report, ...(fixPlan ? { fixPlan } : {}) }) + '\n');
|
|
877
|
+
}
|
|
878
|
+
else {
|
|
879
|
+
process.stdout.write(`Audit saved → ${saved.mdPath}\n`);
|
|
880
|
+
process.stdout.write(` → ${saved.jsonPath}\n`);
|
|
881
|
+
if (saved.planMdPath && saved.planJsonPath) {
|
|
882
|
+
process.stdout.write(`Plan saved → ${saved.planMdPath}\n`);
|
|
883
|
+
process.stdout.write(` → ${saved.planJsonPath}\n`);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
return exitCodeForPipelineAudit(report);
|
|
887
|
+
}
|
|
888
|
+
if (json) {
|
|
889
|
+
if (onlyPlan && fixPlan) {
|
|
890
|
+
process.stdout.write(asJson(fixPlan) + '\n');
|
|
891
|
+
}
|
|
892
|
+
else if (fixPlan) {
|
|
893
|
+
process.stdout.write(asJson({ report, fixPlan }) + '\n');
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
process.stdout.write(asJson(report) + '\n');
|
|
897
|
+
}
|
|
898
|
+
return exitCodeForPipelineAudit(report);
|
|
899
|
+
}
|
|
900
|
+
if (!onlyPlan) {
|
|
901
|
+
process.stdout.write(renderPipelineAuditMarkdown(report));
|
|
902
|
+
}
|
|
903
|
+
if (fixPlan) {
|
|
904
|
+
if (!onlyPlan)
|
|
905
|
+
process.stdout.write('\n');
|
|
906
|
+
process.stdout.write(renderPipelineFixPlanMarkdown(fixPlan));
|
|
907
|
+
}
|
|
908
|
+
return exitCodeForPipelineAudit(report);
|
|
909
|
+
},
|
|
910
|
+
};
|
|
911
|
+
function exitCodeForPipelineAudit(report) {
|
|
912
|
+
if (report.summary.broken > 0)
|
|
913
|
+
return 1;
|
|
914
|
+
return 0;
|
|
915
|
+
}
|
|
916
|
+
function savePipelineAuditReport(cwd, report, fixPlan) {
|
|
917
|
+
const dir = nodePath.join(cwd, SMART_CONTEXT_DIR);
|
|
918
|
+
mkdirSync(dir, { recursive: true });
|
|
919
|
+
const slug = `pipelines-${report.auditId}`;
|
|
920
|
+
const mdPath = nodePath.join(dir, `${slug}.md`);
|
|
921
|
+
const jsonPath = nodePath.join(dir, `${slug}.json`);
|
|
922
|
+
writeFileSync(jsonPath, JSON.stringify(report, null, 2), 'utf8');
|
|
923
|
+
writeFileSync(mdPath, renderPipelineAuditMarkdown(report), 'utf8');
|
|
924
|
+
if (!fixPlan)
|
|
925
|
+
return { slug, mdPath, jsonPath };
|
|
926
|
+
const planSlug = `pipelines-${fixPlan.fixPlanId}`;
|
|
927
|
+
const planMdPath = nodePath.join(dir, `${planSlug}.md`);
|
|
928
|
+
const planJsonPath = nodePath.join(dir, `${planSlug}.json`);
|
|
929
|
+
writeFileSync(planJsonPath, JSON.stringify(fixPlan, null, 2), 'utf8');
|
|
930
|
+
writeFileSync(planMdPath, renderPipelineFixPlanMarkdown(fixPlan), 'utf8');
|
|
931
|
+
return { slug, mdPath, jsonPath, planMdPath, planJsonPath };
|
|
932
|
+
}
|
|
933
|
+
function renderPipelineAuditMarkdown(report) {
|
|
934
|
+
const out = [];
|
|
935
|
+
out.push(`# Pipeline audit — ${report.auditId}`);
|
|
936
|
+
out.push('');
|
|
937
|
+
out.push(`- generated: ${report.generatedAt}`);
|
|
938
|
+
out.push(`- llm enriched: ${report.llmEnriched ? `yes (${report.llmProviderId ?? 'unknown'})` : 'no — deterministic only'}`);
|
|
939
|
+
out.push(`- summary: ok=${report.summary.ok}, minor=${report.summary.minor}, stale=${report.summary.stale}, broken=${report.summary.broken} (total ${report.summary.total})`);
|
|
940
|
+
out.push('');
|
|
941
|
+
const order = ['broken', 'stale', 'minor', 'ok'];
|
|
942
|
+
for (const verdict of order) {
|
|
943
|
+
const inGroup = report.pipelines.filter((p) => p.verdict === verdict);
|
|
944
|
+
if (inGroup.length === 0)
|
|
945
|
+
continue;
|
|
946
|
+
out.push(`## ${verdict.toUpperCase()} (${inGroup.length})`);
|
|
947
|
+
out.push('');
|
|
948
|
+
for (const entry of inGroup) {
|
|
949
|
+
out.push(`### \`${entry.pipelineId}\``);
|
|
950
|
+
if (entry.deterministicFindings.length === 0 && entry.llmFindings.length === 0) {
|
|
951
|
+
out.push('No findings.');
|
|
952
|
+
out.push('');
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
if (entry.deterministicFindings.length > 0) {
|
|
956
|
+
out.push('');
|
|
957
|
+
out.push('Findings:');
|
|
958
|
+
for (const f of entry.deterministicFindings) {
|
|
959
|
+
out.push(`- **[deterministic]** ${f.severity} \`${f.category}\`${f.stepId ? ` (step "${f.stepId}")` : ''} — ${f.message} _(sources: ${f.sources.join(', ')})_`);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (entry.llmFindings.length > 0) {
|
|
963
|
+
out.push('');
|
|
964
|
+
out.push('LLM-flagged (advisory):');
|
|
965
|
+
for (const f of entry.llmFindings) {
|
|
966
|
+
out.push(`- **[llm]** ${f.severity} \`${f.category}\` (confidence ${f.confidence.toFixed(2)}) — ${f.message}`);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
out.push('');
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
if (report.ai) {
|
|
973
|
+
out.push(renderAiBlockMarkdown(report.ai));
|
|
974
|
+
}
|
|
975
|
+
return out.join('\n') + '\n';
|
|
976
|
+
}
|
|
977
|
+
function renderPipelineFixPlanMarkdown(plan) {
|
|
978
|
+
const out = [];
|
|
979
|
+
out.push(`# Pipeline fix plan — ${plan.fixPlanId}`);
|
|
980
|
+
out.push('');
|
|
981
|
+
out.push(`- generated: ${plan.generatedAt}`);
|
|
982
|
+
out.push(`- derived from audit: ${plan.auditId}`);
|
|
983
|
+
out.push(`- source hint: ${plan.sourceHint}`);
|
|
984
|
+
out.push(`- summary: ${plan.summary.fixCount} fix(es) — high=${plan.summary.highConfidence}, medium=${plan.summary.mediumConfidence}, low=${plan.summary.lowConfidence}`);
|
|
985
|
+
out.push('');
|
|
986
|
+
if (plan.fixes.length === 0) {
|
|
987
|
+
out.push('No fix instructions emitted.');
|
|
988
|
+
out.push('');
|
|
989
|
+
return out.join('\n') + '\n';
|
|
990
|
+
}
|
|
991
|
+
const order = ['high', 'medium', 'low'];
|
|
992
|
+
for (const confidence of order) {
|
|
993
|
+
const inGroup = plan.fixes.filter((f) => f.confidence === confidence);
|
|
994
|
+
if (inGroup.length === 0)
|
|
995
|
+
continue;
|
|
996
|
+
out.push(`## Confidence: ${confidence.toUpperCase()} (${inGroup.length})`);
|
|
997
|
+
out.push('');
|
|
998
|
+
for (const fix of inGroup) {
|
|
999
|
+
out.push(`### \`${fix.pipelineId}\` — \`${fix.findingCategory}\` _(${fix.source}, ${fix.severity})_`);
|
|
1000
|
+
out.push(`**Intent.** ${fix.intent}`);
|
|
1001
|
+
out.push('');
|
|
1002
|
+
out.push(`Original finding: ${fix.finding}`);
|
|
1003
|
+
out.push('');
|
|
1004
|
+
out.push('Agent prompt:');
|
|
1005
|
+
out.push('```');
|
|
1006
|
+
out.push(fix.agentPrompt);
|
|
1007
|
+
out.push('```');
|
|
1008
|
+
out.push('');
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return out.join('\n') + '\n';
|
|
1012
|
+
}
|
|
1013
|
+
// Patterns matching ONNX worker-thread teardown noise that surfaces
|
|
1014
|
+
// AFTER a successful embeddings-build. `pipeline.dispose()` returns
|
|
1015
|
+
// cleanly but `onnxruntime-node`'s worker pool isn't actually joined;
|
|
1016
|
+
// when the main thread exits, the workers briefly outlive it and hit a
|
|
1017
|
+
// pthread mutex teardown race. The libc++abi message is the user-visible
|
|
1018
|
+
// symptom. Filtered here so the child's exit doesn't pollute the user's
|
|
1019
|
+
// terminal — exit code is preserved.
|
|
1020
|
+
const EMBEDDINGS_NOISE_PATTERNS = [
|
|
1021
|
+
/^libc\+\+abi: terminating due to uncaught exception of type std::__1::system_error: mutex lock failed/,
|
|
1022
|
+
];
|
|
1023
|
+
function isEmbeddingsCleanupNoise(line) {
|
|
1024
|
+
return EMBEDDINGS_NOISE_PATTERNS.some((p) => p.test(line));
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Run `embeddings-build` in an isolated child process and filter
|
|
1028
|
+
* known cleanup noise from its stderr. See EMBEDDINGS_NOISE_PATTERNS
|
|
1029
|
+
* for the rationale.
|
|
1030
|
+
*
|
|
1031
|
+
* Implementation: re-exec the same CLI binary (`process.execPath` +
|
|
1032
|
+
* `process.argv.slice(1)`) with `SHRK_EMBEDDINGS_WORKER=1` in the env.
|
|
1033
|
+
* The child sees the env flag, skips this wrapper, and runs the
|
|
1034
|
+
* indexing inline. The parent pipes child stdout through unchanged
|
|
1035
|
+
* (so JSON output + result line flow as-is) and filters child stderr
|
|
1036
|
+
* line-by-line before forwarding.
|
|
1037
|
+
*
|
|
1038
|
+
* Trust model: the child's exit code is the source of truth. Even if
|
|
1039
|
+
* the child aborts during cleanup, `reallyExit(code)` in main.ts has
|
|
1040
|
+
* already set the kernel-visible exit code before the abort. We
|
|
1041
|
+
* surface that code verbatim.
|
|
1042
|
+
*/
|
|
1043
|
+
function runEmbeddingsBuildInChild() {
|
|
1044
|
+
return new Promise((resolve) => {
|
|
1045
|
+
const child = spawn(process.execPath, process.argv.slice(1), {
|
|
1046
|
+
env: { ...process.env, SHRK_EMBEDDINGS_WORKER: '1' },
|
|
1047
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
1048
|
+
});
|
|
1049
|
+
child.stdout.pipe(process.stdout);
|
|
1050
|
+
let stderrBuf = '';
|
|
1051
|
+
const flushLine = (line) => {
|
|
1052
|
+
if (isEmbeddingsCleanupNoise(line))
|
|
1053
|
+
return;
|
|
1054
|
+
process.stderr.write(line + '\n');
|
|
1055
|
+
};
|
|
1056
|
+
child.stderr.on('data', (chunk) => {
|
|
1057
|
+
stderrBuf += chunk.toString('utf8');
|
|
1058
|
+
let idx;
|
|
1059
|
+
while ((idx = stderrBuf.indexOf('\n')) !== -1) {
|
|
1060
|
+
flushLine(stderrBuf.slice(0, idx));
|
|
1061
|
+
stderrBuf = stderrBuf.slice(idx + 1);
|
|
1062
|
+
}
|
|
1063
|
+
});
|
|
1064
|
+
child.on('error', (err) => {
|
|
1065
|
+
process.stderr.write(`Failed to spawn embeddings worker: ${err.message}\n`);
|
|
1066
|
+
resolve(1);
|
|
1067
|
+
});
|
|
1068
|
+
child.on('close', (code) => {
|
|
1069
|
+
if (stderrBuf.length > 0 && !isEmbeddingsCleanupNoise(stderrBuf)) {
|
|
1070
|
+
process.stderr.write(stderrBuf);
|
|
1071
|
+
}
|
|
1072
|
+
resolve(typeof code === 'number' ? code : 1);
|
|
1073
|
+
});
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
321
1076
|
/** `shrk smart-context embeddings build` — (re)build the semantic index. */
|
|
322
1077
|
export const smartContextEmbeddingsBuildCommand = {
|
|
323
1078
|
name: 'embeddings-build',
|
|
324
1079
|
description: 'Build or incrementally refresh the semantic index. Defaults to incremental updates when an index already exists; pass --rebuild for a full rebuild.',
|
|
325
1080
|
usage: 'shrk smart-context embeddings-build [--model <hf-id>] [--root <dir>]... [--max-files N] [--rebuild] [--json]',
|
|
326
1081
|
async run(args) {
|
|
1082
|
+
// Top-level: if we're the parent, re-exec ourselves in worker mode
|
|
1083
|
+
// and filter the resulting cleanup noise. The child path (env flag
|
|
1084
|
+
// set) runs the original code below inline.
|
|
1085
|
+
if (process.env.SHRK_EMBEDDINGS_WORKER !== '1') {
|
|
1086
|
+
return runEmbeddingsBuildInChild();
|
|
1087
|
+
}
|
|
327
1088
|
const cwd = resolveCwd(args);
|
|
328
1089
|
const model = flagString(args, 'model');
|
|
329
1090
|
const maxFiles = flagNumber(args, 'max-files') ?? 5000;
|