kc-beta 0.1.1 → 0.1.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.
@@ -0,0 +1,139 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const BUNDLED_SKILLS_DIR = path.resolve(__dirname, "../../template/skills");
7
+
8
+ /**
9
+ * Discover and index meta skills from template/skills/.
10
+ * Follows Claude Code's pattern: skills are NOT dumped into the system prompt.
11
+ * Instead, a brief index (name + description) is injected into context.
12
+ * The agent reads full SKILL.md content on demand via workspace_file or sandbox_exec.
13
+ *
14
+ * Skills are organized as:
15
+ * template/skills/{lang}/meta-meta/ — System architecture methodology
16
+ * template/skills/{lang}/meta/ — Verification domain methodology
17
+ * template/skills/{lang}/skill-creator/ — Anthropic's official skill creation toolkit
18
+ */
19
+ export class SkillLoader {
20
+ /**
21
+ * @param {string} [language] - "en" or "zh"
22
+ * @param {string} [skillsDir] - Override skills directory (default: bundled template)
23
+ */
24
+ constructor(language = "en", skillsDir) {
25
+ this._lang = language;
26
+ this._skillsDir = skillsDir || BUNDLED_SKILLS_DIR;
27
+ this._index = null;
28
+ }
29
+
30
+ /**
31
+ * Build the skill index by scanning SKILL.md frontmatter.
32
+ * Cached after first call.
33
+ * @returns {Array<{name: string, description: string, category: string, path: string}>}
34
+ */
35
+ getIndex() {
36
+ if (this._index) return this._index;
37
+
38
+ this._index = [];
39
+ const langDir = path.join(this._skillsDir, this._lang);
40
+ if (!fs.existsSync(langDir)) return this._index;
41
+
42
+ for (const category of ["meta-meta", "meta", "skill-creator"]) {
43
+ const catDir = path.join(langDir, category);
44
+ if (!fs.existsSync(catDir)) continue;
45
+
46
+ // skill-creator is a single skill, not a directory of skills
47
+ const skillMd = path.join(catDir, "SKILL.md");
48
+ if (fs.existsSync(skillMd)) {
49
+ const { name, description } = this._parseFrontmatter(skillMd);
50
+ if (name) {
51
+ this._index.push({
52
+ name: name || category,
53
+ description: description || "",
54
+ category,
55
+ path: path.relative(this._skillsDir, catDir),
56
+ });
57
+ }
58
+ }
59
+
60
+ // Check subdirectories (meta-meta/bootstrap-workspace/, etc.)
61
+ for (const entry of fs.readdirSync(catDir, { withFileTypes: true })) {
62
+ if (!entry.isDirectory()) continue;
63
+ const subSkillMd = path.join(catDir, entry.name, "SKILL.md");
64
+ if (!fs.existsSync(subSkillMd)) continue;
65
+
66
+ const { name, description } = this._parseFrontmatter(subSkillMd);
67
+ this._index.push({
68
+ name: name || entry.name,
69
+ description: description || "",
70
+ category,
71
+ path: path.relative(this._skillsDir, path.join(catDir, entry.name)),
72
+ });
73
+ }
74
+ }
75
+
76
+ return this._index;
77
+ }
78
+
79
+ /**
80
+ * Format the skill index for injection into agent context.
81
+ * Brief listing — agent reads full content on demand.
82
+ * @returns {string}
83
+ */
84
+ formatForContext() {
85
+ const index = this.getIndex();
86
+ if (index.length === 0) return "";
87
+
88
+ const metaMeta = index.filter((s) => s.category === "meta-meta");
89
+ const meta = index.filter((s) => s.category === "meta");
90
+ const other = index.filter((s) => s.category !== "meta-meta" && s.category !== "meta");
91
+
92
+ const lines = ["## Available Methodology Skills",
93
+ "Read full skill content from the skills/ directory when needed.\n"];
94
+
95
+ if (metaMeta.length) {
96
+ lines.push("**System Architecture (meta-meta):**");
97
+ for (const s of metaMeta) {
98
+ lines.push(`- **${s.name}**: ${s.description.slice(0, 120)}`);
99
+ }
100
+ lines.push("");
101
+ }
102
+
103
+ if (meta.length) {
104
+ lines.push("**Verification Methodology (meta):**");
105
+ for (const s of meta) {
106
+ lines.push(`- **${s.name}**: ${s.description.slice(0, 120)}`);
107
+ }
108
+ lines.push("");
109
+ }
110
+
111
+ if (other.length) {
112
+ lines.push("**Toolkits:**");
113
+ for (const s of other) {
114
+ lines.push(`- **${s.name}**: ${s.description.slice(0, 120)}`);
115
+ }
116
+ }
117
+
118
+ return lines.join("\n");
119
+ }
120
+
121
+ /**
122
+ * Parse YAML frontmatter from a SKILL.md file.
123
+ * Only extracts name and description — lightweight.
124
+ */
125
+ _parseFrontmatter(filePath) {
126
+ try {
127
+ const content = fs.readFileSync(filePath, "utf-8");
128
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
129
+ if (!match) return {};
130
+
131
+ const frontmatter = match[1];
132
+ const name = frontmatter.match(/^name:\s*(.+)$/m)?.[1]?.trim() || "";
133
+ const description = frontmatter.match(/^description:\s*(.+)$/m)?.[1]?.trim() || "";
134
+ return { name, description };
135
+ } catch {
136
+ return {};
137
+ }
138
+ }
139
+ }
@@ -72,14 +72,23 @@ export class TierDowngradeTool extends BaseTool {
72
72
  }
73
73
  }
74
74
 
75
- const recommend = targetAcc >= threshold && delta <= 0.05 ? "downgrade" : "keep_current";
75
+ // Read tier tolerance from .env (default from onboarding config)
76
+ let tolerance = 0.05;
77
+ if (fs.existsSync(envPath)) {
78
+ for (const line of fs.readFileSync(envPath, "utf-8").split("\n")) {
79
+ if (line.startsWith("TIER_TOLERANCE=")) {
80
+ try { tolerance = parseFloat(line.split("=")[1].trim()); }
81
+ catch { /* ignore */ }
82
+ }
83
+ }
84
+ }
76
85
 
77
86
  const report = {
78
87
  rule_id: ruleId, current_tier: currentTier, target_tier: targetTier,
79
88
  current_accuracy: Math.round(currentAcc * 1000) / 1000,
80
89
  target_accuracy: Math.round(targetAcc * 1000) / 1000,
81
90
  accuracy_delta: Math.round(delta * 1000) / 1000,
82
- threshold, recommendation: recommend, test_count: testInputs.length,
91
+ threshold, tolerance, test_count: testInputs.length,
83
92
  };
84
93
  return new ToolResult(JSON.stringify(report, null, 2));
85
94
  }
@@ -160,6 +160,15 @@ export async function onboard() {
160
160
  const accuracy = parseFloat(await ask(rl, ` ${CYAN}${t.accuracy}${RESET}`, defaultAcc));
161
161
  console.log();
162
162
 
163
+ // Advanced thresholds (Enter to keep defaults)
164
+ const advLabel = lang === "zh" ? "高级阈值" : "Advanced Thresholds";
165
+ const skipHint = lang === "zh" ? "回车使用默认值" : "Enter to keep defaults";
166
+ console.log(` ${CYAN}${advLabel}${RESET} ${DIM}(${skipHint})${RESET}`);
167
+ const systemicThreshold = parseFloat(await ask(rl, ` ${lang === "zh" ? "系统性问题阈值" : "Systemic threshold"}`, existing.systemic_threshold?.toString() || "0.10"));
168
+ const spotCheckRate = parseFloat(await ask(rl, ` ${lang === "zh" ? "抽查比率" : "Spot-check rate"}`, existing.spot_check_rate?.toString() || "0.10"));
169
+ const tierTolerance = parseFloat(await ask(rl, ` ${lang === "zh" ? "降级容差" : "Tier downgrade tolerance"}`, existing.tier_tolerance?.toString() || "0.05"));
170
+ console.log();
171
+
163
172
  rl.close();
164
173
 
165
174
  const config = {
@@ -170,6 +179,9 @@ export async function onboard() {
170
179
  conductor_model: model,
171
180
  tiers,
172
181
  accuracy_threshold: accuracy,
182
+ systemic_threshold: systemicThreshold,
183
+ spot_check_rate: spotCheckRate,
184
+ tier_tolerance: tierTolerance,
173
185
  };
174
186
 
175
187
  fs.mkdirSync(CONFIG_DIR, { recursive: true });
package/src/config.js CHANGED
@@ -81,6 +81,11 @@ export function loadSettings(workspacePath) {
81
81
  skillAccuracy: parseFloat(env.SKILL_ACCURACY || gc.accuracy_threshold?.toString() || "0.9"),
82
82
  workflowAccuracy: parseFloat(env.WORKFLOW_ACCURACY || "0.9"),
83
83
 
84
+ // Advanced thresholds (from onboarding or .env)
85
+ systemicThreshold: parseFloat(env.SYSTEMIC_THRESHOLD || gc.systemic_threshold?.toString() || "0.10"),
86
+ spotCheckRate: parseFloat(env.SPOT_CHECK_RATE || gc.spot_check_rate?.toString() || "0.10"),
87
+ tierTolerance: parseFloat(env.TIER_TOLERANCE || gc.tier_tolerance?.toString() || "0.05"),
88
+
84
89
  // Evolution
85
90
  maxIterations: parseInt(env.MAX_ITERATIONS || "20", 10),
86
91
  monitorFrequency: env.MONITOR_FREQUENCY || "mid",