tribunal-kit 5.8.1 → 5.8.2
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/.agent/history/memory/.memory.idx +305 -1
- package/.agent/history/memory/MEMORY.md +21 -1
- package/.agent/scripts/signal_detector.js +173 -0
- package/.agent/scripts/skill_evolution.js +298 -53
- package/CONTRIBUTING.md +134 -0
- package/SECURITY.md +52 -0
- package/bin/tribunal-kit.js +92 -46
- package/dist/commands/init.js +68 -30
- package/dist/esm/index.mjs +116 -0
- package/dist/index.d.ts +288 -0
- package/dist/utils/helpers.js +21 -9
- package/package.json +20 -8
|
@@ -487,50 +487,209 @@ async function callLlmApi(prompt, provider, apiKey) {
|
|
|
487
487
|
return null;
|
|
488
488
|
}
|
|
489
489
|
|
|
490
|
+
// ── Commands ──────────────────────────────────────────────────────────────────
|
|
491
|
+
// ── Strategy Filtering ────────────────────────────────────────────────────────
|
|
492
|
+
function applyStrategyFilter(signals, strategy) {
|
|
493
|
+
if (strategy === "repair-only") {
|
|
494
|
+
return signals.filter((s) => s.type === "log_error");
|
|
495
|
+
}
|
|
496
|
+
return signals;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// ── Log Reflection Prompt ─────────────────────────────────────────────────────
|
|
500
|
+
function generateLogReflectionPrompt(delta) {
|
|
501
|
+
return `You are analyzing runtime log signals (errors, tracebacks, and warnings) from an AI agent's execution.
|
|
502
|
+
Your job is to identify systemic coding issues and extract evolution assets:
|
|
503
|
+
1. **Idioms (Genes)**: Coding rules/conventions to prevent the AI from generating this mistake again.
|
|
504
|
+
2. **Cases (Capsules)**: Rejection precedents (offending code pattern and why it failed) to block reviewers from approving it.
|
|
505
|
+
|
|
506
|
+
Rules:
|
|
507
|
+
- Return ONLY a YAML structure containing 'idioms:' and 'cases:'. No markdown boxes, no conversational prose.
|
|
508
|
+
- Maximum 3 idioms and 3 cases.
|
|
509
|
+
- If an asset category is not applicable, leave it empty (e.g., 'idioms: []').
|
|
510
|
+
|
|
511
|
+
Log Signals:
|
|
512
|
+
\`\`\`
|
|
513
|
+
${delta.slice(0, 1800)}
|
|
514
|
+
\`\`\`
|
|
515
|
+
|
|
516
|
+
Output format (YAML only):
|
|
517
|
+
idioms:
|
|
518
|
+
- pattern: "<code convention or rule to avoid this error>"
|
|
519
|
+
reason: "<why it is needed based on the error>"
|
|
520
|
+
domain: "<backend|frontend|database|security|performance|general>"
|
|
521
|
+
cases:
|
|
522
|
+
- pattern: "<offending code snippet or error-triggering pattern>"
|
|
523
|
+
reason: "<specific runtime error / traceback reason>"
|
|
524
|
+
domain: "<backend|frontend|database|security|performance|general>"
|
|
525
|
+
verdict: "REJECTED"
|
|
526
|
+
`;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// ── Combined YAML Parser ──────────────────────────────────────────────────────
|
|
530
|
+
function parseEvolutionYaml(response) {
|
|
531
|
+
const idioms = [];
|
|
532
|
+
const cases = [];
|
|
533
|
+
let currentSection = null; // 'idioms' | 'cases'
|
|
534
|
+
let current = null;
|
|
535
|
+
|
|
536
|
+
const lines = response.split("\n");
|
|
537
|
+
for (const line of lines) {
|
|
538
|
+
const trimmed = line.trim();
|
|
539
|
+
if (trimmed === "idioms:") {
|
|
540
|
+
if (current) {
|
|
541
|
+
if (currentSection === "idioms") idioms.push(current);
|
|
542
|
+
if (currentSection === "cases") cases.push(current);
|
|
543
|
+
}
|
|
544
|
+
currentSection = "idioms";
|
|
545
|
+
current = null;
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (trimmed === "cases:") {
|
|
549
|
+
if (current) {
|
|
550
|
+
if (currentSection === "idioms") idioms.push(current);
|
|
551
|
+
if (currentSection === "cases") cases.push(current);
|
|
552
|
+
}
|
|
553
|
+
currentSection = "cases";
|
|
554
|
+
current = null;
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (trimmed.startsWith("- pattern:") || (trimmed.startsWith("- ") && trimmed.includes("pattern:"))) {
|
|
559
|
+
if (current) {
|
|
560
|
+
if (currentSection === "idioms") idioms.push(current);
|
|
561
|
+
if (currentSection === "cases") cases.push(current);
|
|
562
|
+
}
|
|
563
|
+
let pat = "";
|
|
564
|
+
if (trimmed.startsWith("- pattern:")) {
|
|
565
|
+
pat = trimmed.substring("- pattern:".length).trim();
|
|
566
|
+
} else {
|
|
567
|
+
pat = trimmed.split("pattern:", 2)[1].trim();
|
|
568
|
+
}
|
|
569
|
+
current = { pattern: pat.replace(/^['"]|['"]$/g, "") };
|
|
570
|
+
} else if (trimmed.startsWith("reason:") && current) {
|
|
571
|
+
current.reason = trimmed.substring("reason:".length).trim().replace(/^['"]|['"]$/g, "");
|
|
572
|
+
} else if (trimmed.startsWith("domain:") && current) {
|
|
573
|
+
current.domain = trimmed.substring("domain:".length).trim().replace(/^['"]|['"]$/g, "");
|
|
574
|
+
} else if (trimmed.startsWith("verdict:") && current) {
|
|
575
|
+
current.verdict = trimmed.substring("verdict:".length).trim().replace(/^['"]|['"]$/g, "");
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (current) {
|
|
580
|
+
if (currentSection === "idioms") idioms.push(current);
|
|
581
|
+
if (currentSection === "cases") cases.push(current);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
return { idioms, cases };
|
|
585
|
+
}
|
|
586
|
+
|
|
490
587
|
// ── Commands ──────────────────────────────────────────────────────────────────
|
|
491
588
|
async function cmdDigest(args) {
|
|
492
589
|
const dryRun = args.includes("--dry-run");
|
|
493
590
|
const diffMode = args.includes("--head") ? "head" : "staged";
|
|
494
591
|
|
|
592
|
+
const logArg = args.find((a) => a.startsWith("--log="));
|
|
593
|
+
const logFile = logArg ? logArg.split("=").slice(1).join("=") : null;
|
|
594
|
+
|
|
595
|
+
const strategyArg = args.find((a) => a.startsWith("--strategy="));
|
|
596
|
+
const strategy = strategyArg ? strategyArg.split("=").slice(1).join("=") : "balanced";
|
|
597
|
+
|
|
495
598
|
console.log(
|
|
496
599
|
`\n${BOLD}${CYAN}━━━ Skill Evolution — Digest Cycle ━━━━━━━━━━━━━━━━${RESET}`,
|
|
497
600
|
);
|
|
498
601
|
if (dryRun)
|
|
499
602
|
console.log(` ${YELLOW}DRY RUN — no files will be written${RESET}\n`);
|
|
500
603
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
604
|
+
let rawDiff = "";
|
|
605
|
+
let delta = "";
|
|
606
|
+
let rawTokens = 0;
|
|
607
|
+
let deltaTokens = 0;
|
|
608
|
+
let logSignals = [];
|
|
609
|
+
const isLogMode = !!logFile;
|
|
610
|
+
|
|
611
|
+
if (isLogMode) {
|
|
612
|
+
console.log(` ${DIM}[1/5] Reading log file: ${logFile}...${RESET}`);
|
|
613
|
+
if (!fs.existsSync(logFile)) {
|
|
614
|
+
console.log(` ${RED}✖ Log file not found: ${logFile}${RESET}\n`);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
const logText = fs.readFileSync(logFile, "utf8");
|
|
618
|
+
rawTokens = countTokensEstimate(logText);
|
|
619
|
+
|
|
504
620
|
console.log(
|
|
505
|
-
` ${
|
|
621
|
+
` ${DIM}[2/5] Extracting signals from log (Signal Detector)...${RESET}`,
|
|
506
622
|
);
|
|
623
|
+
const { detectSignals } = require("./signal_detector");
|
|
624
|
+
const signals = detectSignals(logText);
|
|
625
|
+
|
|
626
|
+
if (signals.length === 0) {
|
|
627
|
+
console.log(
|
|
628
|
+
` ${GREEN}✔ No errors or performance signals found in logs.${RESET}`,
|
|
629
|
+
);
|
|
630
|
+
console.log(` ${DIM} No self-evolution reflection needed.${RESET}\n`);
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
logSignals = applyStrategyFilter(signals, strategy);
|
|
635
|
+
if (logSignals.length === 0) {
|
|
636
|
+
console.log(
|
|
637
|
+
` ${YELLOW}⚠ Strategy filter [${strategy}] filtered out all signals.${RESET}\n`,
|
|
638
|
+
);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
|
|
507
642
|
console.log(
|
|
508
|
-
` ${
|
|
643
|
+
` ${GREEN}✔ Filtered to ${logSignals.length} signal(s) using [${strategy}] strategy.${RESET}`,
|
|
509
644
|
);
|
|
510
|
-
return;
|
|
511
|
-
}
|
|
512
645
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
646
|
+
delta = logSignals
|
|
647
|
+
.map((s) => {
|
|
648
|
+
let chunk = `--- SIGNAL: ${s.type} ---\n`;
|
|
649
|
+
if (s.file) chunk += `File: ${s.file}${s.line ? `:${s.line}` : ""}\n`;
|
|
650
|
+
chunk += `Message: ${s.message}\n`;
|
|
651
|
+
chunk += `Context:\n${s.context}\n`;
|
|
652
|
+
return chunk;
|
|
653
|
+
})
|
|
654
|
+
.join("\n\n");
|
|
655
|
+
|
|
656
|
+
deltaTokens = countTokensEstimate(delta);
|
|
657
|
+
} else {
|
|
658
|
+
console.log(` ${DIM}[1/5] Fetching git diff (${diffMode})...${RESET}`);
|
|
659
|
+
rawDiff = getGitDiff(diffMode);
|
|
660
|
+
if (!rawDiff.trim()) {
|
|
661
|
+
console.log(
|
|
662
|
+
` ${YELLOW}⚠ No diff found. Commit or stage changes first.${RESET}`,
|
|
663
|
+
);
|
|
664
|
+
console.log(
|
|
665
|
+
` ${DIM}Tip: Use --head to diff against the last commit.${RESET}\n`,
|
|
666
|
+
);
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
517
669
|
|
|
518
|
-
|
|
519
|
-
` ${DIM}[2/5] Extracting architectural delta (Semantic Filter)...${RESET}`,
|
|
520
|
-
);
|
|
521
|
-
const delta = semanticDelta(rawDiff, 2);
|
|
522
|
-
if (!delta.trim()) {
|
|
670
|
+
rawTokens = countTokensEstimate(rawDiff);
|
|
523
671
|
console.log(
|
|
524
|
-
` ${
|
|
672
|
+
` ${DIM} Raw diff: ~${rawTokens} tokens (${rawDiff.length} chars)${RESET}`,
|
|
525
673
|
);
|
|
674
|
+
|
|
526
675
|
console.log(
|
|
527
|
-
` ${DIM}
|
|
676
|
+
` ${DIM}[2/5] Extracting architectural delta (Semantic Filter)...${RESET}`,
|
|
528
677
|
);
|
|
529
|
-
|
|
678
|
+
delta = semanticDelta(rawDiff, 2);
|
|
679
|
+
if (!delta.trim()) {
|
|
680
|
+
console.log(
|
|
681
|
+
` ${GREEN}✔ Delta is 100% trivial (whitespace/comments/imports only).${RESET}`,
|
|
682
|
+
);
|
|
683
|
+
console.log(
|
|
684
|
+
` ${DIM} No LLM call needed. Zero tokens consumed.${RESET}\n`,
|
|
685
|
+
);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
deltaTokens = countTokensEstimate(delta);
|
|
530
690
|
}
|
|
531
691
|
|
|
532
|
-
const
|
|
533
|
-
const savedTokens = rawTokens - deltaTokens;
|
|
692
|
+
const savedTokens = Math.max(0, rawTokens - deltaTokens);
|
|
534
693
|
const savedPct = Math.floor((savedTokens / Math.max(rawTokens, 1)) * 100);
|
|
535
694
|
console.log(
|
|
536
695
|
` ${GREEN}✔ Filtered to ~${deltaTokens} tokens (${savedPct}% reduction, saved ~${savedTokens} tokens)${RESET}`,
|
|
@@ -546,7 +705,7 @@ async function cmdDigest(args) {
|
|
|
546
705
|
}
|
|
547
706
|
if (delta.split("\n").length > 20)
|
|
548
707
|
console.log(
|
|
549
|
-
`
|
|
708
|
+
` ... (${delta.split("\n").length - 20} more lines)`,
|
|
550
709
|
);
|
|
551
710
|
|
|
552
711
|
if (dryRun) {
|
|
@@ -559,8 +718,10 @@ async function cmdDigest(args) {
|
|
|
559
718
|
return;
|
|
560
719
|
}
|
|
561
720
|
|
|
562
|
-
// GENERATE: Auto-LLM call.
|
|
563
|
-
const reflectionPrompt =
|
|
721
|
+
// GENERATE: Auto-LLM call.
|
|
722
|
+
const reflectionPrompt = isLogMode
|
|
723
|
+
? generateLogReflectionPrompt(delta)
|
|
724
|
+
: generateReflectionPrompt(delta);
|
|
564
725
|
let llmResponse = "";
|
|
565
726
|
|
|
566
727
|
let llmCreds = detectLlmProvider();
|
|
@@ -582,12 +743,12 @@ async function cmdDigest(args) {
|
|
|
582
743
|
console.log(
|
|
583
744
|
` ${YELLOW}⚠ API call failed — falling back to manual mode${RESET}`,
|
|
584
745
|
);
|
|
585
|
-
llmCreds = null;
|
|
746
|
+
llmCreds = null;
|
|
586
747
|
}
|
|
587
748
|
}
|
|
588
749
|
|
|
589
750
|
if (!llmCreds || !llmResponse) {
|
|
590
|
-
// Manual fallback: copy-paste mode
|
|
751
|
+
// Manual fallback: copy-paste mode
|
|
591
752
|
console.log(
|
|
592
753
|
`\n ${DIM}[3/5] LLM Reflection — copy the prompt below and paste the response${RESET}`,
|
|
593
754
|
);
|
|
@@ -619,21 +780,42 @@ async function cmdDigest(args) {
|
|
|
619
780
|
llmResponse = responseLines.join("\n");
|
|
620
781
|
}
|
|
621
782
|
|
|
622
|
-
console.log(`\n ${DIM}[4/5] Parsing idioms...${RESET}`);
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
783
|
+
console.log(`\n ${DIM}[4/5] Parsing idioms/cases...${RESET}`);
|
|
784
|
+
let newIdioms = [];
|
|
785
|
+
let newCases = [];
|
|
786
|
+
|
|
787
|
+
if (isLogMode) {
|
|
788
|
+
const parsed = parseEvolutionYaml(llmResponse);
|
|
789
|
+
newIdioms = parsed.idioms;
|
|
790
|
+
newCases = parsed.cases;
|
|
791
|
+
} else {
|
|
792
|
+
newIdioms = parseLlmYamlResponse(llmResponse);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (!newIdioms.length && !newCases.length) {
|
|
626
796
|
console.log(
|
|
627
|
-
` ${
|
|
797
|
+
` ${YELLOW}⚠ No idioms or cases extracted from LLM response.${RESET}`,
|
|
628
798
|
);
|
|
799
|
+
console.log(`\n`);
|
|
629
800
|
return;
|
|
630
801
|
}
|
|
631
802
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
803
|
+
if (newIdioms.length > 0) {
|
|
804
|
+
console.log(` ${GREEN}✔ Extracted ${newIdioms.length} idiom(s)${RESET}`);
|
|
805
|
+
for (const idiom of newIdioms) {
|
|
806
|
+
console.log(
|
|
807
|
+
` ${CYAN}• ${idiom.pattern || "?"}${RESET} — ${idiom.reason || ""}`,
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (newCases.length > 0) {
|
|
813
|
+
console.log(` ${GREEN}✔ Extracted ${newCases.length} case precedent(s)${RESET}`);
|
|
814
|
+
for (const c of newCases) {
|
|
815
|
+
console.log(
|
|
816
|
+
` ${RED}• ${c.pattern || "?"}${RESET} — ${c.reason || ""}`,
|
|
817
|
+
);
|
|
818
|
+
}
|
|
637
819
|
}
|
|
638
820
|
|
|
639
821
|
console.log(
|
|
@@ -647,10 +829,8 @@ async function cmdDigest(args) {
|
|
|
647
829
|
let added = 0;
|
|
648
830
|
|
|
649
831
|
for (const idiom of newIdioms) {
|
|
650
|
-
// FIX: Use Levenshtein normalised similarity (threshold 0.80) instead of
|
|
651
|
-
// substring .includes() which was over-aggressive and blocked valid idioms.
|
|
652
832
|
if (isDuplicateIdiom(idiom.pattern || "", existing)) {
|
|
653
|
-
console.log(` ${DIM} Skipped near-duplicate: ${idiom.pattern}${RESET}`);
|
|
833
|
+
console.log(` ${DIM} Skipped near-duplicate idiom: ${idiom.pattern}${RESET}`);
|
|
654
834
|
continue;
|
|
655
835
|
}
|
|
656
836
|
merged.push({
|
|
@@ -664,17 +844,86 @@ async function cmdDigest(args) {
|
|
|
664
844
|
added++;
|
|
665
845
|
}
|
|
666
846
|
|
|
667
|
-
if (added
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
);
|
|
671
|
-
|
|
847
|
+
if (added > 0) {
|
|
848
|
+
log.total_idioms = merged.length;
|
|
849
|
+
const skillMd = renderSkillMd(merged, (log.cycles || []).length + 1);
|
|
850
|
+
fs.mkdirSync(SKILL_DIR, { recursive: true });
|
|
851
|
+
fs.writeFileSync(SKILL_FILE, skillMd, "utf8");
|
|
852
|
+
console.log(` ${GREEN}✔ ${added} new idiom(s) added to SKILL.md${RESET}`);
|
|
853
|
+
} else {
|
|
854
|
+
console.log(` ${DIM} No new unique idioms added to SKILL.md.${RESET}`);
|
|
672
855
|
}
|
|
673
856
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
857
|
+
// Record Case Precedents if present
|
|
858
|
+
if (newCases && newCases.length > 0) {
|
|
859
|
+
console.log(`\n ${DIM}Merging into Case Law precedents...${RESET}`);
|
|
860
|
+
const caseLawManager = require("./case_law_manager");
|
|
861
|
+
const clIndex = caseLawManager.loadIndex();
|
|
862
|
+
let clNextId = clIndex.next_id;
|
|
863
|
+
let clAdded = 0;
|
|
864
|
+
|
|
865
|
+
for (const c of newCases) {
|
|
866
|
+
const diffText = c.pattern || "";
|
|
867
|
+
const reason = c.reason || "";
|
|
868
|
+
const domain = c.domain || "general";
|
|
869
|
+
const verdict = c.verdict || "REJECTED";
|
|
870
|
+
const fingerprint = caseLawManager.contentHash(diffText);
|
|
871
|
+
|
|
872
|
+
let isDup = false;
|
|
873
|
+
for (const existing of clIndex.cases) {
|
|
874
|
+
if (existing.fingerprint === fingerprint) {
|
|
875
|
+
isDup = true;
|
|
876
|
+
break;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
if (isDup) {
|
|
880
|
+
console.log(
|
|
881
|
+
` ${DIM} Skipped duplicate case: ${reason.slice(0, 50)}...${RESET}`,
|
|
882
|
+
);
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
const deltaCase = caseLawManager.semanticDelta(diffText);
|
|
887
|
+
const tags = caseLawManager.extractTags(diffText + " " + reason);
|
|
888
|
+
const caseRecord = {
|
|
889
|
+
id: clNextId,
|
|
890
|
+
fingerprint,
|
|
891
|
+
timestamp: new Date().toISOString().slice(0, 19),
|
|
892
|
+
domain,
|
|
893
|
+
verdict,
|
|
894
|
+
reason: reason.trim(),
|
|
895
|
+
pr_ref: "auto-evolution",
|
|
896
|
+
reviewer: "skill-evolution",
|
|
897
|
+
tags,
|
|
898
|
+
stack_version: null,
|
|
899
|
+
diff_raw: diffText.trim(),
|
|
900
|
+
diff_delta: deltaCase,
|
|
901
|
+
auto_recorded: true,
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
caseLawManager.saveCase(caseRecord);
|
|
905
|
+
clIndex.cases.push({
|
|
906
|
+
id: clNextId,
|
|
907
|
+
fingerprint,
|
|
908
|
+
domain,
|
|
909
|
+
verdict,
|
|
910
|
+
tags,
|
|
911
|
+
timestamp: caseRecord.timestamp,
|
|
912
|
+
reason_summary: reason.trim().slice(0, 120),
|
|
913
|
+
stack_version: null,
|
|
914
|
+
});
|
|
915
|
+
clNextId++;
|
|
916
|
+
clAdded++;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
if (clAdded > 0) {
|
|
920
|
+
clIndex.next_id = clNextId;
|
|
921
|
+
caseLawManager.saveIndex(clIndex);
|
|
922
|
+
console.log(` ${GREEN}✔ ${clAdded} new Case Law precedent(s) recorded.${RESET}`);
|
|
923
|
+
} else {
|
|
924
|
+
console.log(` ${DIM} No new unique Case Law precedents recorded.${RESET}`);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
678
927
|
|
|
679
928
|
log.cycles = log.cycles || [];
|
|
680
929
|
log.cycles.push({
|
|
@@ -687,15 +936,11 @@ async function cmdDigest(args) {
|
|
|
687
936
|
log.total_tokens_saved = (log.total_tokens_saved || 0) + savedTokens;
|
|
688
937
|
saveLog(log);
|
|
689
938
|
|
|
690
|
-
console.log(`\n ${GREEN}✔
|
|
691
|
-
console.log(` ${DIM} File: ${SKILL_FILE}${RESET}`);
|
|
939
|
+
console.log(`\n ${GREEN}✔ Learn cycle complete.${RESET}`);
|
|
692
940
|
console.log(` ${DIM} Total idioms: ${merged.length}${RESET}`);
|
|
693
941
|
console.log(
|
|
694
942
|
` ${DIM} Lifetime tokens saved: ${log.total_tokens_saved}${RESET}\n`,
|
|
695
943
|
);
|
|
696
|
-
console.log(
|
|
697
|
-
` ${CYAN}Commit SKILL.md to share your Engineering Culture with the team.${RESET}\n`,
|
|
698
|
-
);
|
|
699
944
|
}
|
|
700
945
|
|
|
701
946
|
function cmdShow() {
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Contributing to Tribunal Kit
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing to Tribunal Kit! This guide will help you get started.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Clone the repository
|
|
9
|
+
git clone https://github.com/Harmitx7/tribunal-kit.git
|
|
10
|
+
cd tribunal-kit
|
|
11
|
+
|
|
12
|
+
# Install dependencies
|
|
13
|
+
npm install
|
|
14
|
+
|
|
15
|
+
# Run tests
|
|
16
|
+
npm run test:unit
|
|
17
|
+
|
|
18
|
+
# Run the CLI locally
|
|
19
|
+
node bin/wrapper.js --help
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Development Setup
|
|
23
|
+
|
|
24
|
+
### Prerequisites
|
|
25
|
+
|
|
26
|
+
- **Node.js** >= 18.0.0 (LTS recommended)
|
|
27
|
+
- **npm** >= 9
|
|
28
|
+
- **Rust** (optional) — Only needed if modifying the Rust core engine in `crates/`
|
|
29
|
+
|
|
30
|
+
### Project Structure
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
tribunal-kit/
|
|
34
|
+
├── bin/ # CLI entry points (JS monolith + MCP server)
|
|
35
|
+
│ ├── wrapper.js # Main entry: routes to Rust binary or JS fallback
|
|
36
|
+
│ ├── tribunal-kit.js # Legacy JS CLI (1,500+ lines)
|
|
37
|
+
│ └── mcp-server.js # MCP server over JSON-RPC 2.0 / stdio
|
|
38
|
+
├── dist/ # Modular CLI (lazy-loaded commands)
|
|
39
|
+
│ ├── cli.js # CLI core with command routing
|
|
40
|
+
│ ├── commands/ # Individual command modules
|
|
41
|
+
│ ├── utils/ # Logger, helpers, version checker, hasher
|
|
42
|
+
│ └── index.d.ts # TypeScript declarations
|
|
43
|
+
├── crates/core/ # Rust core engine (Tokio-based)
|
|
44
|
+
├── .agent/ # The intelligence payload (agents, skills, workflows)
|
|
45
|
+
│ ├── agents/ # 43 specialist agent definitions
|
|
46
|
+
│ ├── skills/ # Reusable skill packs
|
|
47
|
+
│ ├── workflows/ # 34 workflow definitions
|
|
48
|
+
│ └── scripts/ # Automation scripts
|
|
49
|
+
├── test/
|
|
50
|
+
│ ├── unit/ # Unit tests (Jest)
|
|
51
|
+
│ └── integration/ # Integration tests
|
|
52
|
+
└── scripts/ # Build & release scripts
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Running Tests
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Unit tests with coverage
|
|
59
|
+
npm run test:unit
|
|
60
|
+
|
|
61
|
+
# All tests
|
|
62
|
+
npm test
|
|
63
|
+
|
|
64
|
+
# Run a specific test file
|
|
65
|
+
npx jest test/unit/memory.test.js
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Building the Rust Core (Optional)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Build in release mode
|
|
72
|
+
cargo build --release
|
|
73
|
+
|
|
74
|
+
# Run benchmarks comparing Rust vs JS
|
|
75
|
+
npm run benchmark:rust
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## How to Contribute
|
|
79
|
+
|
|
80
|
+
### Reporting Bugs
|
|
81
|
+
|
|
82
|
+
1. Check [existing issues](https://github.com/Harmitx7/tribunal-kit/issues) first
|
|
83
|
+
2. Use the bug report template
|
|
84
|
+
3. Include: Node.js version, OS, steps to reproduce, expected vs actual behavior
|
|
85
|
+
|
|
86
|
+
### Suggesting Features
|
|
87
|
+
|
|
88
|
+
1. Open a [GitHub Discussion](https://github.com/Harmitx7/tribunal-kit/discussions) first
|
|
89
|
+
2. Describe the use case, not just the solution
|
|
90
|
+
3. Be specific about which command or workflow this affects
|
|
91
|
+
|
|
92
|
+
### Submitting Pull Requests
|
|
93
|
+
|
|
94
|
+
1. **Fork** the repository and create a branch from `main`
|
|
95
|
+
2. **Write tests** for any new functionality
|
|
96
|
+
3. **Run the test suite** — all tests must pass: `npm run test:unit`
|
|
97
|
+
4. **Follow existing code style** — the project uses ESLint
|
|
98
|
+
5. **Write clear commit messages** following [Conventional Commits](https://www.conventionalcommits.org/):
|
|
99
|
+
```
|
|
100
|
+
feat: add --json flag to status command
|
|
101
|
+
fix: handle symlinks in init --force
|
|
102
|
+
docs: update CLI reference for memory command
|
|
103
|
+
test: add coverage for marathon harness
|
|
104
|
+
```
|
|
105
|
+
6. **Open a PR** with a clear description of what changed and why
|
|
106
|
+
|
|
107
|
+
### Contributing Agents or Skills
|
|
108
|
+
|
|
109
|
+
Tribunal Kit's value comes from its agent and skill library. To contribute:
|
|
110
|
+
|
|
111
|
+
1. **Agents** go in `.agent/agents/` as markdown files
|
|
112
|
+
2. **Skills** go in `.agent/skills/<skill-name>/SKILL.md`
|
|
113
|
+
3. Follow the existing format — check any existing agent/skill for the structure
|
|
114
|
+
4. Include the YAML frontmatter (`name`, `description`)
|
|
115
|
+
5. Add practical, non-obvious guidance — not generic advice
|
|
116
|
+
|
|
117
|
+
### Code Style
|
|
118
|
+
|
|
119
|
+
- **JavaScript**: CommonJS (`require`/`module.exports`), no transpilation needed
|
|
120
|
+
- **Naming**: `camelCase` for functions/variables, `UPPER_SNAKE` for constants
|
|
121
|
+
- **Error handling**: Always handle errors in async functions
|
|
122
|
+
- **Comments**: Explain *why*, not *what*
|
|
123
|
+
- **No new dependencies**: Zero production dependencies is a feature, not a limitation
|
|
124
|
+
|
|
125
|
+
## Review Process
|
|
126
|
+
|
|
127
|
+
1. All PRs are reviewed by a maintainer
|
|
128
|
+
2. CI must pass (tests + lint)
|
|
129
|
+
3. Changes to `.agent/` content are reviewed for accuracy and usefulness
|
|
130
|
+
4. Breaking changes require a discussion before implementation
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE).
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
| Version | Supported |
|
|
6
|
+
| ------- | ------------------ |
|
|
7
|
+
| 5.x | ✅ Active support |
|
|
8
|
+
| 4.x | ⚠️ Critical fixes only |
|
|
9
|
+
| < 4.0 | ❌ End of life |
|
|
10
|
+
|
|
11
|
+
## Reporting a Vulnerability
|
|
12
|
+
|
|
13
|
+
**Do not open a public GitHub issue for security vulnerabilities.**
|
|
14
|
+
|
|
15
|
+
Instead, please report vulnerabilities privately using one of these methods:
|
|
16
|
+
|
|
17
|
+
1. **GitHub Security Advisory** (preferred): Use [GitHub's private vulnerability reporting](https://github.com/Harmitx7/tribunal-kit/security/advisories/new) to submit a confidential report.
|
|
18
|
+
|
|
19
|
+
2. **Email**: Send details to the maintainer via the email listed in the npm package.
|
|
20
|
+
|
|
21
|
+
### What to include
|
|
22
|
+
|
|
23
|
+
- Description of the vulnerability
|
|
24
|
+
- Steps to reproduce
|
|
25
|
+
- Affected versions
|
|
26
|
+
- Potential impact
|
|
27
|
+
- Suggested fix (if any)
|
|
28
|
+
|
|
29
|
+
### Response timeline
|
|
30
|
+
|
|
31
|
+
| Action | Timeline |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| Acknowledgment | Within 48 hours |
|
|
34
|
+
| Initial assessment | Within 5 business days |
|
|
35
|
+
| Fix release (critical) | Within 7 days |
|
|
36
|
+
| Fix release (moderate) | Within 30 days |
|
|
37
|
+
|
|
38
|
+
## Security Design
|
|
39
|
+
|
|
40
|
+
Tribunal Kit follows these security principles:
|
|
41
|
+
|
|
42
|
+
- **No network requests at runtime** — The CLI operates entirely offline. The only network call is an optional npm registry version check during `init`/`update`, which can be skipped with `--skip-update-check`.
|
|
43
|
+
- **No code execution from user input** — CLI arguments are parsed without `eval()` or shell interpolation. All subprocess spawning uses array-based arguments (never string concatenation).
|
|
44
|
+
- **No secrets stored** — Tribunal Kit does not store, read, or transmit API keys, tokens, or credentials.
|
|
45
|
+
- **Minimal dependencies** — Zero production dependencies. Only `jest`, `eslint`, and `typescript` as devDependencies.
|
|
46
|
+
- **Platform binaries are optional** — The Rust core binary is distributed as optional dependencies. The CLI falls back to pure JavaScript if binaries are unavailable.
|
|
47
|
+
|
|
48
|
+
## Supply Chain
|
|
49
|
+
|
|
50
|
+
- All releases are published from CI via GitHub Actions
|
|
51
|
+
- Platform binaries are built in GitHub-hosted runners with pinned action versions
|
|
52
|
+
- The package uses `npm provenance` for verifiable supply chain attestation
|