@ionivetech/mugiwara 0.5.0 → 0.5.1

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.
Files changed (50) hide show
  1. package/content/skills/mugiwara-context-budget/SKILL.md +1 -1
  2. package/content/skills/mugiwara-frontend/SKILL.md +1 -1
  3. package/content/skills/mugiwara-healing/SKILL.md +1 -1
  4. package/content/skills/mugiwara-orchestration/SKILL.md +1 -1
  5. package/content/skills/mugiwara-planning/SKILL.md +1 -1
  6. package/content/skills/mugiwara-proof-order/SKILL.md +1 -1
  7. package/content/skills/mugiwara-quality/SKILL.md +1 -1
  8. package/content/skills/mugiwara-review/SKILL.md +1 -1
  9. package/content/skills/mugiwara-root-cause/SKILL.md +1 -1
  10. package/content/skills/mugiwara-security/SKILL.md +1 -1
  11. package/content/skills/mugiwara-sunset/SKILL.md +1 -1
  12. package/content/skills/mugiwara-testcases/SKILL.md +1 -1
  13. package/content/skills/mugiwara-workflow/SKILL.md +1 -1
  14. package/evals/cases/_no-skill.json +16 -0
  15. package/evals/cases/adversarial-pressure-fake-pass.json +21 -8
  16. package/evals/cases/adversarial-pressure-skip-review.json +19 -7
  17. package/evals/cases/lane-exploratory-vague.json +18 -6
  18. package/evals/cases/lane-sensitivity-payment.json +18 -6
  19. package/evals/cases/positive-refactor-existing-tests.json +21 -7
  20. package/evals/cases/positive-resume-mid-mission.json +20 -7
  21. package/evals/cases/routing-agent-security.json +25 -0
  22. package/evals/cases/routing-auth-feature.json +20 -7
  23. package/evals/cases/routing-backend.json +25 -0
  24. package/evals/cases/routing-bug-one-file.json +20 -7
  25. package/evals/cases/routing-claim-audit.json +25 -0
  26. package/evals/cases/routing-context-budget.json +25 -0
  27. package/evals/cases/routing-contract-first.json +25 -0
  28. package/evals/cases/routing-execution.json +25 -0
  29. package/evals/cases/routing-frontend.json +26 -0
  30. package/evals/cases/routing-gates.json +25 -0
  31. package/evals/cases/routing-git.json +25 -0
  32. package/evals/cases/routing-healing.json +25 -0
  33. package/evals/cases/routing-lessons.json +25 -0
  34. package/evals/cases/routing-orchestration.json +25 -0
  35. package/evals/cases/routing-planning.json +26 -0
  36. package/evals/cases/routing-pr.json +25 -0
  37. package/evals/cases/routing-proof-order.json +25 -0
  38. package/evals/cases/routing-quality.json +25 -0
  39. package/evals/cases/routing-ship.json +26 -0
  40. package/evals/cases/routing-sunset.json +25 -0
  41. package/evals/cases/routing-workflow.json +25 -0
  42. package/evals/floor.json +6 -0
  43. package/package.json +2 -1
  44. package/scripts/probe.ts +40 -0
  45. package/scripts/retrieval-eval.ts +178 -69
  46. package/scripts/run-evals.ts +56 -20
  47. package/scripts/savepoint.sh +5 -4
  48. package/evals/cases/negative-secrets-typo.json +0 -12
  49. package/evals/cases/negative-security-docs-change.json +0 -12
  50. package/evals/cases/routing-typo.json +0 -13
@@ -16,12 +16,16 @@ const casesDir = join(root, 'evals', 'cases');
16
16
  const skillsDir = join(root, 'content', 'skills');
17
17
  const errors: string[] = [];
18
18
 
19
+ type BehavioralTest = {
20
+ task: string;
21
+ rubric: string[];
22
+ };
23
+
19
24
  type Case = {
20
25
  name: string;
21
26
  skill: string;
22
27
  type?: 'positive' | 'negative' | 'adversarial' | 'lane';
23
- task: string;
24
- rubric: string[];
28
+ behavioral?: BehavioralTest[];
25
29
  expect_lane?: string;
26
30
  };
27
31
 
@@ -40,34 +44,65 @@ function validateSuite(): Case[] {
40
44
  const cases: Case[] = [];
41
45
  const files = listCases(casesDir).sort();
42
46
  for (const rel of files) {
43
- let c: Case;
44
- try { c = JSON.parse(readFileSync(join(casesDir, rel), 'utf8')); }
47
+ let raw: any;
48
+ try { raw = JSON.parse(readFileSync(join(casesDir, rel), 'utf8')); }
45
49
  catch (e) { errors.push(`${rel}: invalid JSON (${(e as Error).message})`); continue; }
46
- if (!c.name || !c.skill || !c.task || !Array.isArray(c.rubric) || !c.rubric.length)
47
- errors.push(`${rel}: missing name/skill/task/nonempty rubric`);
48
- else if (!existsSync(join(skillsDir, c.skill))) errors.push(`${rel}: unknown skill "${c.skill}"`);
49
- if (c.type && !['positive', 'negative', 'adversarial', 'lane'].includes(c.type))
50
- errors.push(`${rel}: bad type "${c.type}"`);
51
- cases.push(c);
50
+ if (!raw.name || !raw.skill) { errors.push(`${rel}: missing name/skill`); continue; }
51
+
52
+ // skip files with no behavioral section (e.g. _no-skill.json)
53
+ if (!raw.behavioral || !Array.isArray(raw.behavioral) || raw.behavioral.length === 0) continue;
54
+ if (raw.skill === '_no-skill') continue;
55
+
56
+ // infer type from filename if not declared
57
+ const type = raw.type ?? (
58
+ rel.includes('positive') ? 'positive' :
59
+ rel.includes('negative') ? 'negative' :
60
+ rel.includes('adversarial') ? 'adversarial' :
61
+ rel.includes('lane') ? 'lane' :
62
+ undefined
63
+ );
64
+
65
+ // validate skill exists
66
+ if (!existsSync(join(skillsDir, raw.skill)))
67
+ errors.push(`${rel}: unknown skill "${raw.skill}"`);
68
+
69
+ if (type && !['positive', 'negative', 'adversarial', 'lane'].includes(type))
70
+ errors.push(`${rel}: bad type "${type}"`);
71
+
72
+ // register one case per behavioral test
73
+ for (const b of raw.behavioral) {
74
+ if (!b.task || !Array.isArray(b.rubric) || !b.rubric.length) {
75
+ errors.push(`${rel}: behavioral entry missing task/nonempty rubric`);
76
+ continue;
77
+ }
78
+ cases.push({
79
+ name: raw.name,
80
+ skill: raw.skill,
81
+ type,
82
+ behavioral: [b],
83
+ expect_lane: raw.expect_lane,
84
+ });
85
+ }
52
86
  }
53
- if (!cases.length) { errors.push('no cases in evals/cases/'); }
87
+
88
+ if (!cases.length) { errors.push('no behavioral cases in evals/cases/'); }
54
89
 
55
90
  // coverage gates: the suite must exercise routing in all directions.
56
- const types = new Set(cases.map(c => c.type).filter(Boolean));
57
- if (cases.filter(c => c.type === 'positive').length < 2) errors.push('need ≥2 positive cases');
58
- if (cases.filter(c => c.type === 'negative').length < 2) errors.push('need 2 negative cases');
59
- if (cases.filter(c => c.type === 'adversarial').length < 2) errors.push('need ≥2 adversarial cases');
60
- if (!types.has('lane')) errors.push('need ≥1 lane case');
91
+ const explicitTypes = cases.filter(c => c.type);
92
+ const allTypes = new Set(explicitTypes.map(c => c.type).filter(Boolean));
93
+ if (explicitTypes.filter(c => c.type === 'adversarial').length < 2) errors.push('need >=2 adversarial cases');
94
+ if (!allTypes.has('lane')) errors.push('need >=1 lane case');
61
95
  return cases;
62
96
  }
63
97
 
64
98
  function scoreRubric(answer: string, c: Case): { pass: number; total: number; matched: string[] } {
65
99
  const a = answer.toLowerCase();
66
- const matched = c.rubric.filter(r => {
100
+ const rubric = c.behavioral?.[0]?.rubric ?? [];
101
+ const matched = rubric.filter(r => {
67
102
  const terms = r.toLowerCase().split(/[^a-z0-9]+/).filter(w => w.length > 3 && !['does', 'not', 'with', 'into', 'from'].includes(w));
68
103
  return terms.some(t => a.includes(t));
69
104
  });
70
- return { pass: matched.length, total: c.rubric.length, matched };
105
+ return { pass: matched.length, total: rubric.length, matched };
71
106
  }
72
107
 
73
108
  async function runCases(env: { execFileSync: typeof import('node:child_process').execFileSync; bin: string; pre: string[] }): Promise<void> {
@@ -76,7 +111,8 @@ async function runCases(env: { execFileSync: typeof import('node:child_process')
76
111
  const { execFileSync, bin, pre } = env;
77
112
  let total = 0, passed = 0;
78
113
  for (const c of cases) {
79
- const prompt = `Task: ${c.task}\n\nWhich mugiwara skill should run and why? Be concrete.`;
114
+ const task = c.behavioral?.[0]?.task ?? '';
115
+ const prompt = `Task: ${task}\n\nWhich mugiwara skill should run and why? Be concrete.`;
80
116
  let raw = '';
81
117
  try {
82
118
  raw = execFileSync(bin, [...pre, prompt], { encoding: 'utf8', timeout: 60000, maxBuffer: 10 * 1024 * 1024 });
@@ -89,7 +125,7 @@ async function runCases(env: { execFileSync: typeof import('node:child_process')
89
125
  const pct = Math.round((pass / t) * 100);
90
126
  console.log(`${pct >= 70 ? '✓' : '✗'} ${c.name} — ${pass}/${t} rubric (${pct}%)`);
91
127
  if (pass < t) {
92
- console.log(` task: ${c.task}`);
128
+ console.log(` task: ${task}`);
93
129
  console.log(` matched: ${matched.length ? matched.join('; ') : 'none'}`);
94
130
  }
95
131
  }
@@ -35,7 +35,7 @@ fi
35
35
  [ -d .git ] || die "not a git repository"
36
36
 
37
37
  # --- computed fields ---
38
- BASE_SHA=$(git merge-base HEAD main 2>/dev/null || git merge-base HEAD master 2>/dev/null || git rev-parse HEAD~1 2>/dev/null || echo "unknown")
38
+ BASE_SHA=$(git merge-base HEAD main 2>/dev/null || git merge-base HEAD master 2>/dev/null || git merge-base HEAD "$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||')" 2>/dev/null || git rev-parse HEAD~1 2>/dev/null || echo "unknown")
39
39
  HEAD_SHA=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
40
40
 
41
41
  CHANGED_FILES=$(git diff --name-only "$BASE_SHA"..HEAD 2>/dev/null || git diff --name-only --cached 2>/dev/null || true)
@@ -43,11 +43,12 @@ FILES_TOUCHED=$( [ -n "$CHANGED_FILES" ] && echo "$CHANGED_FILES" | wc -l | tr -
43
43
 
44
44
  LOC_DELTA=0
45
45
  if [ "$BASE_SHA" != "unknown" ]; then
46
- LOC_DELTA=$(git diff --shortstat "$BASE_SHA"..HEAD 2>/dev/null | \
47
- awk '{ins=0; del=0; for(i=1;i<=NF;i++){if($i=="insertion") ins=$(i-1); if($i=="deletion") del=$(i-1)} print ins-del}' || echo 0)
46
+ STAT=$(git diff --shortstat "$BASE_SHA"..HEAD 2>/dev/null || echo "")
47
+ INS=$(echo "$STAT" | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+' || echo 0)
48
+ DEL=$(echo "$STAT" | grep -oE '[0-9]+ deletion' | grep -oE '[0-9]+' || echo 0)
49
+ LOC_DELTA=$(( ${INS:-0} - ${DEL:-0} ))
48
50
  fi
49
51
  [ -z "$LOC_DELTA" ] && LOC_DELTA=0
50
- [ "$LOC_DELTA" = "0" ] || LOC_DELTA=${LOC_DELTA//[^0-9-]/}
51
52
 
52
53
  SENSITIVE_PATTERNS="auth/|payment/|billing/|crypto/|secrets/|\.env|config/|migration/|\.sql$|schema\.|\.prisma$"
53
54
  SENSITIVE_PATHS=$(echo "$CHANGED_FILES" | grep -E "$SENSITIVE_PATTERNS" 2>/dev/null | tr '\n' ',' | sed 's/,$//' || true)
@@ -1,12 +0,0 @@
1
- {
2
- "name": "negative-secrets-typo",
3
- "skill": "mugiwara-git",
4
- "type": "negative",
5
- "task": "Fix a typo in a comment in src/index.ts (one word). This is a trivial change.",
6
- "rubric": [
7
- "does NOT rank mugiwara-security first (no trust boundary crossed)",
8
- "does NOT rank mugiwara-systematic-debugging first (cause known)",
9
- "treats it as lane 0 direct work"
10
- ],
11
- "expect_lane": "direct"
12
- }
@@ -1,12 +0,0 @@
1
- {
2
- "name": "negative-security-docs-change",
3
- "skill": "mugiwara-git",
4
- "type": "negative",
5
- "task": "Update the README to fix a broken link. No code, no config, no data flow changes.",
6
- "rubric": [
7
- "does NOT rank mugiwara-security first (docs-only, no trust boundary)",
8
- "does NOT rank mugiwara-test-driven-development first (no production code)",
9
- "does NOT rank mugiwara-gates first (no code changed)"
10
- ],
11
- "expect_lane": "direct"
12
- }
@@ -1,13 +0,0 @@
1
- {
2
- "name": "routing-typo",
3
- "skill": "mugiwara-git",
4
- "task": "Fix a typo in a comment in src/index.ts (one word).",
5
- "rubric": [
6
- "treats it as lane 0 direct work",
7
- "makes the one-line change without brainstorm or plan",
8
- "does not invoke the crew pipeline",
9
- "commits with a conventional commit message"
10
- ],
11
- "lane": "0",
12
- "expect_lane": "direct"
13
- }