tribunal-kit 5.8.0 → 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/agents/project-planner.md +5 -0
- package/.agent/history/memory/.memory.idx +1232 -1
- package/.agent/history/memory/MEMORY.md +82 -1
- package/.agent/rules/GEMINI.md +30 -5
- package/.agent/scripts/signal_detector.js +173 -0
- package/.agent/scripts/skill_evolution.js +298 -53
- package/.agent/skills/fabel-protocol/SKILL.md +37 -1
- package/.agent/workflows/generate.md +1 -0
- package/.agent/workflows/tribunal-full.md +4 -3
- package/CONTRIBUTING.md +134 -0
- package/README.md +138 -12
- package/SECURITY.md +52 -0
- package/bin/mcp-server.js +38 -0
- package/bin/tribunal-kit.js +92 -46
- package/bin/wrapper.js +5 -1
- package/dist/cli.js +13 -0
- package/dist/commands/align.js +201 -0
- 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 +33 -9
|
@@ -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() {
|
|
@@ -32,6 +32,15 @@ CONFIDENCE CHECK:
|
|
|
32
32
|
→ Standard library (Node, Python, Rust) → Low risk. Proceed with confidence.
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
### Epistemic Confidence Levels (L1-L5)
|
|
36
|
+
|
|
37
|
+
Rate the certainty of your implementation decisions using this hierarchy:
|
|
38
|
+
- **L1: Absolute Certainty (Verified Truth)**: Code is fully checked against active files in the workspace or verified in up-to-date documentation.
|
|
39
|
+
- **L2: High Confidence (Standard API)**: Using standard library or stable, unchanged language features (e.g. standard Node `fs` methods, basic Python functions).
|
|
40
|
+
- **L3: Moderate Confidence (Likely but Unverified)**: Custom utilities or package features that are likely correct but not actively verified. Must add `// VERIFY: [reason]` tags.
|
|
41
|
+
- **L4: Low Confidence (Speculative)**: Unstable APIs, recently modified dependencies, or legacy components. Search or audit first.
|
|
42
|
+
- **L5: Pure Speculation (Guessed / Blind)**: Complete guesswork. Strictly prohibited from code generation. Must stop and research or ask.
|
|
43
|
+
|
|
35
44
|
### Uncertainty Markers
|
|
36
45
|
|
|
37
46
|
When uncertain, never silently guess. Use explicit markers:
|
|
@@ -193,7 +202,25 @@ ALWAYS:
|
|
|
193
202
|
|
|
194
203
|
---
|
|
195
204
|
|
|
196
|
-
## 6.
|
|
205
|
+
## 6. Fabel-5 Cognitive Boundaries (Wellbeing, Evenhandedness, Memory)
|
|
206
|
+
|
|
207
|
+
### User Wellbeing & Safety
|
|
208
|
+
* **No Psychoanalysis / Diagnosis**: Reflect what is said without diagnosing or assigning psychological narratives (e.g. "you restrict because of trauma"). Suggest professional help without clinical labels.
|
|
209
|
+
* **Self-Harm Interruptions**: Never suggest physical substitutes (holding ice, snapping rubber bands, drawing lines) or mimic self-harm. They reinforce the self-harm loop.
|
|
210
|
+
* **No Over-reliance**: Do not thank the user for reaching out, encourage them to stay, or reiterate willingness to continue. Avoid conversational dependencies.
|
|
211
|
+
* **Positive Paths**: Acknowledge distress without reflective listening that amplifies negative spirals. Keep paths to external help open.
|
|
212
|
+
|
|
213
|
+
### Moral & Political Evenhandedness
|
|
214
|
+
* **Nuance Over Brevity**: Reject requests for simple yes/no or one-word answers on contested political, ethical, or policy issues. Give a fair, balanced overview of existing positions.
|
|
215
|
+
* **Opposing Perspectives**: Conclude arguments for positions by presenting opposing viewpoints or empirical disputes even if the user/AI agrees with the primary view.
|
|
216
|
+
|
|
217
|
+
### Memory & Preference Boundaries
|
|
218
|
+
* **Invisible Integration**: Integrate remembered user context silently without attribution or observation verbs ("I notice in your profile...", "Based on your memory...").
|
|
219
|
+
* **Expertise Tuning**: Match language and technical depth to the user's stated background without lecturing.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 7. Anti-Hallucination Quick Reference
|
|
197
224
|
|
|
198
225
|
High-risk hallucination zones (verify before using):
|
|
199
226
|
|
|
@@ -211,6 +238,15 @@ When in doubt: **search the official docs**. Never trust training data for API s
|
|
|
211
238
|
|
|
212
239
|
---
|
|
213
240
|
|
|
241
|
+
## ⚡ Hallucination Heatmap (High-Risk Zones)
|
|
242
|
+
|
|
243
|
+
- **Next.js 15+ Route Handlers**: Dynamic functions (`headers()`, `cookies()`, `params`) are now async and must be awaited. Unawaited calls throw runtime errors.
|
|
244
|
+
- **React 19 Hooks**: `useFormState` was renamed to `useActionState`. Direct context creation using `React.createServerContext()` was removed.
|
|
245
|
+
- **Drizzle ORM Queries**: `db.select().from().filter()` does not exist; Drizzle uses `.where()` for filtering.
|
|
246
|
+
- **OpenAI / Anthropic SDKs**: Model strings (e.g., trying to use `gpt-5` or `claude-4-opus` which do not exist or are incorrect).
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
214
250
|
## LLM Traps — Self-Audit
|
|
215
251
|
|
|
216
252
|
```
|
|
@@ -109,6 +109,7 @@ When unsure: write `// VERIFY: [specific reason]` instead of hallucinating.
|
|
|
109
109
|
precedence-reviewer→ Enforces repository Case Law and past rejections (Runs First)
|
|
110
110
|
logic-reviewer → Hallucinated methods, undefined refs, impossible logic
|
|
111
111
|
security-auditor → OWASP vulnerabilities, hardcoded secrets, injection
|
|
112
|
+
complexity-reviewer→ Enforces the Dependency Ladder to prevent over-engineering
|
|
112
113
|
```
|
|
113
114
|
|
|
114
115
|
**Auto-activated by keywords:**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Run ALL
|
|
2
|
+
description: Run ALL 20 Tribunal reviewer agents simultaneously. Maximum hallucination coverage. Use before merging any AI-generated code, before production deployments, or when maximum confidence is required.
|
|
3
3
|
required-skills: all domain skills auto-loaded
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# /tribunal-full — Complete
|
|
6
|
+
# /tribunal-full — Complete 20-Reviewer Audit
|
|
7
7
|
|
|
8
8
|
$ARGUMENTS
|
|
9
9
|
|
|
@@ -32,7 +32,7 @@ Read BEFORE full review:
|
|
|
32
32
|
|
|
33
33
|
---
|
|
34
34
|
|
|
35
|
-
##
|
|
35
|
+
## 20 Reviewers — All Active Simultaneously
|
|
36
36
|
|
|
37
37
|
```
|
|
38
38
|
Tier 1: Always active (universal concerns)
|
|
@@ -44,6 +44,7 @@ Tier 1: Always active (universal concerns)
|
|
|
44
44
|
Tier 2: Code quality
|
|
45
45
|
├── dependency-reviewer → Fabricated packages, supply chain, version compatibility
|
|
46
46
|
├── type-safety-reviewer → 'any' epidemic, Zod parse vs cast, unguarded access
|
|
47
|
+
├── complexity-reviewer → Enforces the Dependency Ladder to prevent over-engineering
|
|
47
48
|
├── schema-reviewer → Missing input validation, loose schemas, raw req.body
|
|
48
49
|
└── sql-reviewer → Injection, N+1, missing indexes, unscoped mutations
|
|
49
50
|
|
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).
|