@ionivetech/mugiwara 0.3.0 → 0.5.0
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/.opencode/commands/mugiwara-plan.md +4 -0
- package/README.md +214 -418
- package/content/agents/brook-healing.md +2 -2
- package/content/agents/chopper-checkpoint.md +2 -1
- package/content/agents/eval-runner.md +4 -4
- package/content/agents/franky-gates.md +1 -1
- package/content/agents/jinbe-security.md +2 -1
- package/content/agents/luffy-orchestrator.md +3 -3
- package/content/agents/memory-keeper.md +1 -1
- package/content/agents/nami-planner.md +2 -2
- package/content/agents/resume-coordinator.md +8 -9
- package/content/agents/robin-reviewer.md +3 -2
- package/content/agents/sanji-quality.md +1 -1
- package/content/agents/skeptic-verifier.md +4 -3
- package/content/agents/using-mugiwara.md +3 -3
- package/content/agents/usopp-brainstorm.md +3 -3
- package/content/agents/zoro-execution.md +4 -3
- package/content/skills/mugiwara-agent-security/SKILL.md +19 -2
- package/content/skills/mugiwara-backend/SKILL.md +7 -8
- package/content/skills/mugiwara-backend/references/source-backed-example.md +77 -0
- package/content/skills/mugiwara-brainstorm/SKILL.md +7 -2
- package/content/skills/mugiwara-checkpoint/SKILL.md +7 -2
- package/content/skills/mugiwara-checkpoint/references/ledger-format.md +34 -0
- package/content/skills/{mugiwara-doubt-driven-development → mugiwara-claim-audit}/SKILL.md +7 -2
- package/content/skills/{mugiwara-context-engineering → mugiwara-context-budget}/SKILL.md +7 -2
- package/content/skills/mugiwara-context-budget/references/context-budget.md +37 -0
- package/content/skills/{mugiwara-api-and-interface-design → mugiwara-contract-first}/SKILL.md +9 -2
- package/content/skills/mugiwara-contract-first/references/versioning-playbook.md +36 -0
- package/content/skills/mugiwara-execution/SKILL.md +9 -2
- package/content/skills/mugiwara-execution/references/worked-example.md +88 -0
- package/content/skills/mugiwara-frontend/SKILL.md +21 -22
- package/content/skills/mugiwara-frontend/references/design-tokens.md +25 -0
- package/content/skills/mugiwara-frontend/references/slop-catalog.md +19 -0
- package/content/skills/mugiwara-gates/SKILL.md +9 -4
- package/content/skills/mugiwara-git/SKILL.md +6 -1
- package/content/skills/mugiwara-healing/SKILL.md +39 -7
- package/content/skills/mugiwara-healing/references/failure-taxonomy.md +23 -0
- package/content/skills/mugiwara-lessons/SKILL.md +6 -1
- package/content/skills/mugiwara-orchestration/SKILL.md +18 -5
- package/content/skills/mugiwara-planning/SKILL.md +16 -15
- package/content/skills/mugiwara-planning/references/anti-patterns.md +17 -0
- package/content/skills/mugiwara-planning/references/plan-template.md +46 -0
- package/content/skills/mugiwara-pr/SKILL.md +6 -1
- package/content/skills/{mugiwara-test-driven-development → mugiwara-proof-order}/SKILL.md +7 -2
- package/content/skills/mugiwara-proof-order/references/proof-order-examples.md +62 -0
- package/content/skills/mugiwara-quality/SKILL.md +8 -3
- package/content/skills/mugiwara-resume/SKILL.md +46 -30
- package/content/skills/mugiwara-review/SKILL.md +6 -1
- package/content/skills/mugiwara-review/references/five-axis-worksheet.md +36 -0
- package/content/skills/mugiwara-review/references/severity-rubric.md +20 -0
- package/content/skills/{mugiwara-systematic-debugging → mugiwara-root-cause}/SKILL.md +9 -2
- package/content/skills/mugiwara-root-cause/references/four-phase-worked.md +71 -0
- package/content/skills/mugiwara-security/SKILL.md +18 -18
- package/content/skills/mugiwara-security/references/owasp-mapping.md +30 -0
- package/content/skills/mugiwara-security/references/stride-worksheet.md +37 -0
- package/content/skills/mugiwara-ship/SKILL.md +6 -1
- package/content/skills/{mugiwara-deprecation → mugiwara-sunset}/SKILL.md +7 -2
- package/content/skills/mugiwara-testcases/SKILL.md +6 -1
- package/content/skills/mugiwara-testcases/references/intake-formats.md +42 -0
- package/content/skills/mugiwara-workflow/SKILL.md +26 -24
- package/dist/mugiwara.js +165 -28
- package/docs/adoption-guide.md +2 -2
- package/docs/agents.md +2 -2
- package/docs/audit-trail.md +65 -0
- package/docs/comparison.md +58 -63
- package/docs/compliance-matrix.md +81 -0
- package/docs/config.md +7 -2
- package/docs/cost.md +45 -0
- package/docs/developer-onboarding.md +17 -13
- package/docs/enforcement.md +35 -0
- package/docs/getting-started.md +103 -41
- package/docs/harness-matrix.md +41 -0
- package/docs/index.md +11 -5
- package/docs/lanes.md +82 -0
- package/docs/skill-anatomy.md +42 -49
- package/docs/skills.md +36 -54
- package/docs/troubleshooting.md +91 -0
- package/docs/workflow.md +15 -6
- package/evals/cases/adversarial-pressure-fake-pass.json +12 -0
- package/evals/cases/adversarial-pressure-skip-review.json +13 -0
- package/evals/cases/lane-exploratory-vague.json +12 -0
- package/evals/cases/lane-sensitivity-payment.json +12 -0
- package/evals/cases/negative-secrets-typo.json +12 -0
- package/evals/cases/negative-security-docs-change.json +12 -0
- package/evals/cases/positive-refactor-existing-tests.json +11 -0
- package/evals/cases/positive-resume-mid-mission.json +11 -0
- package/evals/cases/routing-auth-feature.json +13 -0
- package/evals/cases/routing-bug-one-file.json +13 -0
- package/evals/cases/routing-typo.json +13 -0
- package/package.json +6 -4
- package/references/definition-of-done.md +42 -0
- package/references/multi-actor.md +44 -0
- package/references/skill-versioning.md +44 -0
- package/references/source-grounding.md +23 -0
- package/references/token-budget.md +34 -0
- package/scripts/evidence.sh +37 -0
- package/scripts/lane.sh +74 -0
- package/scripts/mission-report.sh +88 -0
- package/scripts/release-notes.ts +65 -0
- package/scripts/retrieval-eval.ts +152 -0
- package/scripts/run-evals.ts +113 -0
- package/scripts/savepoint.sh +165 -0
- package/scripts/sync-version.ts +30 -0
- package/scripts/validate-content.ts +226 -0
- package/src/args.ts +2 -1
- package/src/cli.ts +21 -20
- package/src/installer.ts +31 -3
- package/src/mission.ts +48 -0
- package/src/targets/claude.ts +4 -0
- package/src/targets/copilot.ts +4 -0
- package/src/targets/generic.ts +33 -1
- package/src/targets/kilo.ts +1 -0
- package/src/targets/opencode.ts +4 -0
- package/content/skills/mugiwara-dynamic-workflow/SKILL.md +0 -85
- package/content/skills/mugiwara-eval/SKILL.md +0 -82
- package/content/skills/mugiwara-git-worktrees/SKILL.md +0 -62
- package/content/skills/mugiwara-mode/SKILL.md +0 -72
- package/content/skills/mugiwara-observability/SKILL.md +0 -54
- package/content/skills/mugiwara-writing-skills/SKILL.md +0 -60
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// scripts/run-evals.ts — the mugiwara eval harness.
|
|
3
|
+
// Two modes:
|
|
4
|
+
// bun scripts/run-evals.ts validate the suite (structure + coverage). CI-safe, no model.
|
|
5
|
+
// bun scripts/run-evals.ts --run [cmd] execute each case against a model CLI and score the rubric.
|
|
6
|
+
//
|
|
7
|
+
// --run uses the command to invoke a model that prints only its answer to the
|
|
8
|
+
// task; the harness scores the answer against the rubric with a heuristic
|
|
9
|
+
// keyword match, then reports pass/fail per case and a rank-1 style score.
|
|
10
|
+
// The command defaults to "claude -p" — override with MUGIWARA_EVAL_CMD.
|
|
11
|
+
import { readdirSync, readFileSync, existsSync } from 'node:fs';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
|
|
14
|
+
const root = join(import.meta.dirname, '..');
|
|
15
|
+
const casesDir = join(root, 'evals', 'cases');
|
|
16
|
+
const skillsDir = join(root, 'content', 'skills');
|
|
17
|
+
const errors: string[] = [];
|
|
18
|
+
|
|
19
|
+
type Case = {
|
|
20
|
+
name: string;
|
|
21
|
+
skill: string;
|
|
22
|
+
type?: 'positive' | 'negative' | 'adversarial' | 'lane';
|
|
23
|
+
task: string;
|
|
24
|
+
rubric: string[];
|
|
25
|
+
expect_lane?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function listCases(dir: string, prefix = ''): string[] {
|
|
29
|
+
if (!existsSync(dir)) return [];
|
|
30
|
+
const out: string[] = [];
|
|
31
|
+
for (const ent of readdirSync(dir, { withFileTypes: true })) {
|
|
32
|
+
const p = join(dir, ent.name);
|
|
33
|
+
if (ent.isDirectory()) out.push(...listCases(p, join(prefix, ent.name)));
|
|
34
|
+
else if (ent.name.endsWith('.json')) out.push(join(prefix, ent.name));
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function validateSuite(): Case[] {
|
|
40
|
+
const cases: Case[] = [];
|
|
41
|
+
const files = listCases(casesDir).sort();
|
|
42
|
+
for (const rel of files) {
|
|
43
|
+
let c: Case;
|
|
44
|
+
try { c = JSON.parse(readFileSync(join(casesDir, rel), 'utf8')); }
|
|
45
|
+
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);
|
|
52
|
+
}
|
|
53
|
+
if (!cases.length) { errors.push('no cases in evals/cases/'); }
|
|
54
|
+
|
|
55
|
+
// 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');
|
|
61
|
+
return cases;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function scoreRubric(answer: string, c: Case): { pass: number; total: number; matched: string[] } {
|
|
65
|
+
const a = answer.toLowerCase();
|
|
66
|
+
const matched = c.rubric.filter(r => {
|
|
67
|
+
const terms = r.toLowerCase().split(/[^a-z0-9]+/).filter(w => w.length > 3 && !['does', 'not', 'with', 'into', 'from'].includes(w));
|
|
68
|
+
return terms.some(t => a.includes(t));
|
|
69
|
+
});
|
|
70
|
+
return { pass: matched.length, total: c.rubric.length, matched };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function runCases(env: { execFileSync: typeof import('node:child_process').execFileSync; bin: string; pre: string[] }): Promise<void> {
|
|
74
|
+
const cases = validateSuite();
|
|
75
|
+
if (errors.length) { console.error(errors.map(e => `✗ ${e}`).join('\n')); process.exit(1); }
|
|
76
|
+
const { execFileSync, bin, pre } = env;
|
|
77
|
+
let total = 0, passed = 0;
|
|
78
|
+
for (const c of cases) {
|
|
79
|
+
const prompt = `Task: ${c.task}\n\nWhich mugiwara skill should run and why? Be concrete.`;
|
|
80
|
+
let raw = '';
|
|
81
|
+
try {
|
|
82
|
+
raw = execFileSync(bin, [...pre, prompt], { encoding: 'utf8', timeout: 60000, maxBuffer: 10 * 1024 * 1024 });
|
|
83
|
+
} catch (e: any) {
|
|
84
|
+
raw = e.stdout?.toString?.() ?? String(e.message);
|
|
85
|
+
}
|
|
86
|
+
const { pass, total: t, matched } = scoreRubric(raw, c);
|
|
87
|
+
total += t;
|
|
88
|
+
passed += pass;
|
|
89
|
+
const pct = Math.round((pass / t) * 100);
|
|
90
|
+
console.log(`${pct >= 70 ? '✓' : '✗'} ${c.name} — ${pass}/${t} rubric (${pct}%)`);
|
|
91
|
+
if (pass < t) {
|
|
92
|
+
console.log(` task: ${c.task}`);
|
|
93
|
+
console.log(` matched: ${matched.length ? matched.join('; ') : 'none'}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const overall = Math.round((passed / total) * 100);
|
|
97
|
+
console.log(`\nOVERALL ${passed}/${total} (${overall}%)`);
|
|
98
|
+
if (overall < 70) process.exitCode = 1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const runArg = process.argv.indexOf('--run');
|
|
102
|
+
if (runArg !== -1) {
|
|
103
|
+
const { execFileSync } = require('node:child_process');
|
|
104
|
+
const cmd = process.env.MUGIWARA_EVAL_CMD ?? 'claude -p';
|
|
105
|
+
const [bin, ...pre] = cmd.split(' ');
|
|
106
|
+
runCases({ execFileSync, bin, pre });
|
|
107
|
+
process.exit(0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const cases = validateSuite();
|
|
111
|
+
if (errors.length) { console.error(errors.map(e => `✗ ${e}`).join('\n')); process.exit(1); }
|
|
112
|
+
console.log(`✓ eval suite OK: ${cases.length} cases (${cases.filter(c => c.type === 'positive').length} positive, ${cases.filter(c => c.type === 'negative').length} negative, ${cases.filter(c => c.type === 'adversarial').length} adversarial, ${cases.filter(c => c.type === 'lane').length} lane)`);
|
|
113
|
+
for (const c of cases) console.log(` ${c.name} [${c.type ?? 'default'}]`);
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/savepoint.sh — write .mugiwara/state.json at every wave boundary.
|
|
3
|
+
# Computed from git + file counts; zero model judgement.
|
|
4
|
+
set -u
|
|
5
|
+
|
|
6
|
+
die() { echo "savepoint: $*" >&2; exit 1; }
|
|
7
|
+
|
|
8
|
+
MUGIWARA_DIR="${MUGIWARA_DIR:-.mugiwara}"
|
|
9
|
+
|
|
10
|
+
# --- parse mission args ---
|
|
11
|
+
MISSION="${1:-${STATE_MISSION:-}}"
|
|
12
|
+
ACTOR="${2:-${STATE_ACTOR:-${GIT_AUTHOR_NAME:-${USER:-}}}}"
|
|
13
|
+
BRANCH="${3:-$(git branch --show-current 2>/dev/null || echo 'unknown')}"
|
|
14
|
+
WAVE="${4:-${STATE_WAVE:-1}}"
|
|
15
|
+
MODE="${5:-${STATE_MODE:-guided}}"
|
|
16
|
+
|
|
17
|
+
# --branch flag for per-branch state
|
|
18
|
+
BRANCH_MODE=0
|
|
19
|
+
if [ "${1:-}" = "--branch" ]; then
|
|
20
|
+
BRANCH_MODE=1
|
|
21
|
+
MISSION="${2:-${STATE_MISSION:-}}"
|
|
22
|
+
BRANCH="${3:-$(git branch --show-current 2>/dev/null || echo 'unknown')}"
|
|
23
|
+
shift 2 2>/dev/null || true
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# per-branch state file when --branch used
|
|
27
|
+
BRANCH_SLUG=$(echo "$BRANCH" | tr '/' '-')
|
|
28
|
+
if [ "$BRANCH_MODE" -eq 1 ]; then
|
|
29
|
+
STATE_FILE="$MUGIWARA_DIR/state-${BRANCH_SLUG}.json"
|
|
30
|
+
else
|
|
31
|
+
STATE_FILE="$MUGIWARA_DIR/state.json"
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
[ -z "$MISSION" ] && die "usage: savepoint.sh <mission> [actor] [branch] [wave] [mode]"
|
|
35
|
+
[ -d .git ] || die "not a git repository"
|
|
36
|
+
|
|
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")
|
|
39
|
+
HEAD_SHA=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
|
|
40
|
+
|
|
41
|
+
CHANGED_FILES=$(git diff --name-only "$BASE_SHA"..HEAD 2>/dev/null || git diff --name-only --cached 2>/dev/null || true)
|
|
42
|
+
FILES_TOUCHED=$( [ -n "$CHANGED_FILES" ] && echo "$CHANGED_FILES" | wc -l | tr -d ' ' || echo 0 )
|
|
43
|
+
|
|
44
|
+
LOC_DELTA=0
|
|
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)
|
|
48
|
+
fi
|
|
49
|
+
[ -z "$LOC_DELTA" ] && LOC_DELTA=0
|
|
50
|
+
[ "$LOC_DELTA" = "0" ] || LOC_DELTA=${LOC_DELTA//[^0-9-]/}
|
|
51
|
+
|
|
52
|
+
SENSITIVE_PATTERNS="auth/|payment/|billing/|crypto/|secrets/|\.env|config/|migration/|\.sql$|schema\.|\.prisma$"
|
|
53
|
+
SENSITIVE_PATHS=$(echo "$CHANGED_FILES" | grep -E "$SENSITIVE_PATTERNS" 2>/dev/null | tr '\n' ',' | sed 's/,$//' || true)
|
|
54
|
+
|
|
55
|
+
LANE="direct"
|
|
56
|
+
LANE_REASON=""
|
|
57
|
+
if [ "$FILES_TOUCHED" -ge 9 ] 2>/dev/null || [ -n "$SENSITIVE_PATHS" ]; then
|
|
58
|
+
LANE="full"
|
|
59
|
+
LANE_REASON="$( [ -n "$SENSITIVE_PATHS" ] && echo "sensitive paths: $SENSITIVE_PATHS" || echo "$FILES_TOUCHED files")"
|
|
60
|
+
elif [ "$FILES_TOUCHED" -ge 3 ] 2>/dev/null; then
|
|
61
|
+
LANE="standard"
|
|
62
|
+
LANE_REASON="$FILES_TOUCHED files"
|
|
63
|
+
elif [ "$FILES_TOUCHED" -ge 2 ] 2>/dev/null; then
|
|
64
|
+
LANE="lean"
|
|
65
|
+
LANE_REASON="$FILES_TOUCHED files"
|
|
66
|
+
else
|
|
67
|
+
LANE="direct"
|
|
68
|
+
LANE_REASON="$FILES_TOUCHED file(s) under 20 LOC"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# task counts from plan doc
|
|
72
|
+
PLAN_FILE=$(ls "$MUGIWARA_DIR/plans/${MISSION}.md" 2>/dev/null || true)
|
|
73
|
+
TASKS_DONE=0
|
|
74
|
+
TASKS_TOTAL=0
|
|
75
|
+
if [ -n "$PLAN_FILE" ] && [ -f "$PLAN_FILE" ]; then
|
|
76
|
+
TASKS_TOTAL=$(grep -c '\[ \]' "$PLAN_FILE" 2>/dev/null || echo 0)
|
|
77
|
+
TASKS_DONE=$(grep -c '\[x\]' "$PLAN_FILE" 2>/dev/null || echo 0)
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# blocker count
|
|
81
|
+
BLOCKERS_FILE=$(ls "$MUGIWARA_DIR/issues/${MISSION}-blockers.md" 2>/dev/null || true)
|
|
82
|
+
BLOCKERS_OPEN=0
|
|
83
|
+
if [ -n "$BLOCKERS_FILE" ] && [ -f "$BLOCKERS_FILE" ]; then
|
|
84
|
+
BLOCKERS_OPEN=$(grep -c '|' "$BLOCKERS_FILE" 2>/dev/null || echo 0)
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# heal cycle
|
|
88
|
+
HEAL_CYCLE=1
|
|
89
|
+
TRACE_FILE=$(ls "$MUGIWARA_DIR/results/${MISSION}-trace.md" 2>/dev/null || true)
|
|
90
|
+
if [ -n "$TRACE_FILE" ] && [ -f "$TRACE_FILE" ]; then
|
|
91
|
+
HEAL_COUNT=$(grep -ci 'Wave 8\|wave 8\|heal' "$TRACE_FILE" 2>/dev/null || echo 0)
|
|
92
|
+
HEAL_CYCLE=$((HEAL_COUNT + 1))
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# evidence paths
|
|
96
|
+
EVIDENCE=$(ls "$MUGIWARA_DIR/results/" 2>/dev/null | grep "$MISSION" | sed 's|^|.mugiwara/results/|' | tr '\n' ',' | sed 's/,$//' || true)
|
|
97
|
+
|
|
98
|
+
# skill version from package.json
|
|
99
|
+
SKILL_VERSION="1"
|
|
100
|
+
if [ -f package.json ]; then
|
|
101
|
+
PKG_JSON="$MUGIWARA_DIR/../package.json"
|
|
102
|
+
[ -f "$PKG_JSON" ] && SKILL_VERSION=$(python3 -c "import json; v=json.load(open('$PKG_JSON')).get('version','1'); print(v.split('.')[0])" 2>/dev/null || echo "1")
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
# tokens from env var (harness exports estimated tokens consumed)
|
|
106
|
+
TOKENS_EST=${MUGIWARA_TOKENS:-0}
|
|
107
|
+
|
|
108
|
+
# budget per lane
|
|
109
|
+
BUDGET=0
|
|
110
|
+
case "$LANE" in
|
|
111
|
+
lean) BUDGET=4000 ;;
|
|
112
|
+
standard) BUDGET=10000 ;;
|
|
113
|
+
full) BUDGET=20000 ;;
|
|
114
|
+
spike) BUDGET=3000 ;;
|
|
115
|
+
*) BUDGET=0 ;;
|
|
116
|
+
esac
|
|
117
|
+
|
|
118
|
+
# token budget gate
|
|
119
|
+
WARN_AT=$(( BUDGET * 3 / 2 ))
|
|
120
|
+
STOP_AT=$(( BUDGET * 3 ))
|
|
121
|
+
STATUS="ok"
|
|
122
|
+
if [ "$BUDGET" -gt 0 ] && [ "$TOKENS_EST" -ge "$STOP_AT" ] 2>/dev/null; then
|
|
123
|
+
STATUS="stop"
|
|
124
|
+
elif [ "$BUDGET" -gt 0 ] && [ "$TOKENS_EST" -ge "$WARN_AT" ] 2>/dev/null; then
|
|
125
|
+
STATUS="warn"
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
SENSITIVE_ARR=""
|
|
129
|
+
if [ -n "$SENSITIVE_PATHS" ]; then
|
|
130
|
+
SENSITIVE_ARR=$(echo "$SENSITIVE_PATHS" | tr ',' '\n' | sed 's/^/"/;s/$/"/' | tr '\n' ',' | sed 's/,$//')
|
|
131
|
+
fi
|
|
132
|
+
EVIDENCE_ARR=""
|
|
133
|
+
if [ -n "$EVIDENCE" ]; then
|
|
134
|
+
EVIDENCE_ARR=$(echo "$EVIDENCE" | tr ',' '\n' | sed 's/^/"/;s/$/"/' | tr '\n' ',' | sed 's/,$//')
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
mkdir -p "$MUGIWARA_DIR"
|
|
138
|
+
|
|
139
|
+
cat > "$STATE_FILE" <<JSON
|
|
140
|
+
{
|
|
141
|
+
"mission": "$MISSION",
|
|
142
|
+
"actor": "$ACTOR",
|
|
143
|
+
"branch": "$BRANCH",
|
|
144
|
+
"lane": "$LANE",
|
|
145
|
+
"lane_reason": "$LANE_REASON",
|
|
146
|
+
"wave": $WAVE,
|
|
147
|
+
"mode": "$MODE",
|
|
148
|
+
"base_sha": "$BASE_SHA",
|
|
149
|
+
"head_sha": "$HEAD_SHA",
|
|
150
|
+
"files_touched": $FILES_TOUCHED,
|
|
151
|
+
"loc_delta": $LOC_DELTA,
|
|
152
|
+
"sensitive_paths": [${SENSITIVE_ARR}],
|
|
153
|
+
"tasks": { "done": $TASKS_DONE, "total": $TASKS_TOTAL },
|
|
154
|
+
"blockers_open": $BLOCKERS_OPEN,
|
|
155
|
+
"heal_cycle": $HEAL_CYCLE,
|
|
156
|
+
"tokens_est": $TOKENS_EST,
|
|
157
|
+
"budget": $BUDGET,
|
|
158
|
+
"budget_status": "$STATUS",
|
|
159
|
+
"skill_version": "$SKILL_VERSION",
|
|
160
|
+
"evidence": [${EVIDENCE_ARR}],
|
|
161
|
+
"updated_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
162
|
+
}
|
|
163
|
+
JSON
|
|
164
|
+
|
|
165
|
+
echo "✓ savepoint written: $STATE_FILE (lane=$LANE, wave=$WAVE, files=$FILES_TOUCHED)"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// scripts/sync-version.ts — syncs package.json version into the plugin manifests
|
|
3
|
+
// so Claude/Copilot plugin version always matches the published npm version.
|
|
4
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
const root = join(import.meta.dirname, '..');
|
|
8
|
+
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
|
9
|
+
const v = pkg.version;
|
|
10
|
+
|
|
11
|
+
const targets = [
|
|
12
|
+
join(root, '.claude-plugin', 'plugin.json'),
|
|
13
|
+
join(root, 'plugin.json'),
|
|
14
|
+
join(root, '.claude-plugin', 'marketplace.json'),
|
|
15
|
+
join(root, 'gemini-extension.json'),
|
|
16
|
+
join(root, '.codex-plugin', 'plugin.json'),
|
|
17
|
+
join(root, '.cursor-plugin', 'plugin.json'),
|
|
18
|
+
join(root, '.kimi-plugin', 'plugin.json'),
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
for (const file of targets) {
|
|
22
|
+
const doc = JSON.parse(readFileSync(file, 'utf8'));
|
|
23
|
+
if (doc.version !== undefined) doc.version = v;
|
|
24
|
+
if (doc.metadata?.version !== undefined) doc.metadata.version = v;
|
|
25
|
+
if (Array.isArray(doc.plugins)) {
|
|
26
|
+
for (const p of doc.plugins) if (p.version !== undefined) p.version = v;
|
|
27
|
+
}
|
|
28
|
+
writeFileSync(file, JSON.stringify(doc, null, 2) + '\n');
|
|
29
|
+
console.log(`synced ${file} → ${v}`);
|
|
30
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// scripts/validate-content.ts
|
|
3
|
+
import { readdirSync, readFileSync, existsSync, statSync } from 'node:fs';
|
|
4
|
+
import { join, basename } from 'node:path';
|
|
5
|
+
import { parseFrontmatter } from '../src/frontmatter.ts';
|
|
6
|
+
|
|
7
|
+
const root = join(import.meta.dirname, '..', 'content');
|
|
8
|
+
const errors: string[] = [];
|
|
9
|
+
|
|
10
|
+
function checkFile(file: string, wantName: string, kind: 'skill' | 'agent'): Record<string, string> | null {
|
|
11
|
+
let parsed;
|
|
12
|
+
try { parsed = parseFrontmatter(readFileSync(file, 'utf8')); }
|
|
13
|
+
catch (e) { errors.push(`${kind} ${file}: ${(e as Error).message}`); return null; }
|
|
14
|
+
const { data, body } = parsed;
|
|
15
|
+
if (data.name !== wantName) errors.push(`${kind} ${file}: name "${data.name}" != "${wantName}"`);
|
|
16
|
+
const d = data.description ?? '';
|
|
17
|
+
if (kind === 'skill' && (d.length < 20 || d.length > 500)) errors.push(`skill ${file}: description must be 20-500 chars (got ${d.length})`);
|
|
18
|
+
if (kind === 'agent' && d.length < 20) errors.push(`agent ${file}: description too short`);
|
|
19
|
+
if (kind === 'skill' && body.replace(/\r?\n$/, '').split(/\r?\n/).length > 120) errors.push(`skill ${file}: body exceeds 120 lines`);
|
|
20
|
+
if (kind === 'skill' && !body.includes('## Skip when')) errors.push(`skill ${file}: missing required "## Skip when" block (≤4 lines, numeric threshold)`);
|
|
21
|
+
if (kind === 'skill' && body.includes('## Skip when')) {
|
|
22
|
+
const lines = body.split(/\r?\n/);
|
|
23
|
+
const idx = lines.findIndex(l => l.startsWith('## Skip when'));
|
|
24
|
+
const gateLines = lines.slice(idx + 1).findIndex(l => l.startsWith('## '));
|
|
25
|
+
const end = gateLines === -1 ? lines.length : idx + 1 + gateLines;
|
|
26
|
+
const bullets = lines.slice(idx + 1, end).filter(l => l.trim().startsWith('-'));
|
|
27
|
+
if (bullets.length === 0) errors.push(`skill ${file}: "## Skip when" needs ≥1 bullet`);
|
|
28
|
+
if (bullets.length > 4) errors.push(`skill ${file}: "## Skip when" block exceeds 4 bullets`);
|
|
29
|
+
}
|
|
30
|
+
return data;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function listFiles(dir: string, prefix = ''): string[] {
|
|
34
|
+
if (!existsSync(dir)) return [];
|
|
35
|
+
const out: string[] = [];
|
|
36
|
+
for (const ent of readdirSync(dir, { withFileTypes: true })) {
|
|
37
|
+
const p = join(dir, ent.name);
|
|
38
|
+
if (ent.isDirectory()) out.push(...listFiles(p, join(prefix, ent.name)));
|
|
39
|
+
else out.push(join(prefix, ent.name));
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const syncArg = process.argv.indexOf('--check-sync');
|
|
45
|
+
if (syncArg !== -1) {
|
|
46
|
+
const pairs = [['content/skills', 'skills'], ['content/agents', 'agents']] as const;
|
|
47
|
+
const diffs: string[] = [];
|
|
48
|
+
for (const [from, to] of pairs) {
|
|
49
|
+
const fromRoot = join(import.meta.dirname, '..', from);
|
|
50
|
+
const toRoot = join(import.meta.dirname, '..', to);
|
|
51
|
+
const fromFiles = listFiles(fromRoot).sort();
|
|
52
|
+
const toFiles = listFiles(toRoot).sort();
|
|
53
|
+
for (const rel of fromFiles) {
|
|
54
|
+
const f = join(fromRoot, rel), t = join(toRoot, rel);
|
|
55
|
+
if (!existsSync(t)) diffs.push(`missing copy: ${to}/${rel} (run .claude-plugin/sync.sh)`);
|
|
56
|
+
else if (readFileSync(f, 'utf8') !== readFileSync(t, 'utf8')) diffs.push(`out of sync: ${to}/${rel}`);
|
|
57
|
+
}
|
|
58
|
+
for (const rel of toFiles) {
|
|
59
|
+
if (!fromFiles.includes(rel)) diffs.push(`stale copy: ${to}/${rel} (not in content/, run .claude-plugin/sync.sh)`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (diffs.length) { console.error(diffs.map(d => `✗ ${d}`).join('\n')); process.exit(1); }
|
|
63
|
+
console.log('✓ plugin copies in sync with content/');
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const skillDirs = existsSync(join(root, 'skills'))
|
|
68
|
+
? readdirSync(join(root, 'skills')).filter(d => statSync(join(root, 'skills', d)).isDirectory())
|
|
69
|
+
: [];
|
|
70
|
+
const names = new Map<string, string>();
|
|
71
|
+
const usedSkills = new Set<string>();
|
|
72
|
+
|
|
73
|
+
const checkArg = process.argv.indexOf('--check');
|
|
74
|
+
if (checkArg !== -1) {
|
|
75
|
+
const file = process.argv[checkArg + 1];
|
|
76
|
+
const isSkill = file.includes('skills');
|
|
77
|
+
const want = isSkill ? basename(join(file, '..')) : basename(file).replace(/\.md$/, '');
|
|
78
|
+
checkFile(file, want, isSkill ? 'skill' : 'agent');
|
|
79
|
+
if (errors.length) { console.error(errors.map(e => `✗ ${e}`).join('\n')); process.exit(1); }
|
|
80
|
+
console.log(`✓ ${file}`);
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const dir of skillDirs) {
|
|
85
|
+
const file = join(root, 'skills', dir, 'SKILL.md');
|
|
86
|
+
if (!existsSync(file)) { errors.push(`skill ${dir}: missing SKILL.md`); continue; }
|
|
87
|
+
const data = checkFile(file, dir, 'skill');
|
|
88
|
+
if (data) {
|
|
89
|
+
if (names.has(data.name)) errors.push(`duplicate name: ${data.name}`);
|
|
90
|
+
names.set(data.name, file);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const agentDir = join(root, 'agents');
|
|
95
|
+
const agentFiles = existsSync(agentDir) ? readdirSync(agentDir).filter(f => f.endsWith('.md')) : [];
|
|
96
|
+
for (const f of agentFiles) {
|
|
97
|
+
const data = checkFile(join(agentDir, f), f.replace(/\.md$/, ''), 'agent');
|
|
98
|
+
if (!data) continue;
|
|
99
|
+
if (names.has(data.name)) errors.push(`duplicate name: ${data.name}`);
|
|
100
|
+
names.set(data.name, f);
|
|
101
|
+
const skills = (data.skills ?? '').split(',').map(s => s.trim()).filter(Boolean);
|
|
102
|
+
if (skills.length === 0) errors.push(`agent ${f}: skills field missing/empty`);
|
|
103
|
+
for (const s of skills) {
|
|
104
|
+
usedSkills.add(s);
|
|
105
|
+
if (!skillDirs.includes(s)) errors.push(`agent ${f}: unknown skill "${s}"`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const dir of skillDirs) {
|
|
110
|
+
if (dir !== 'mugiwara-workflow' && !usedSkills.has(dir)) errors.push(`skill ${dir}: not referenced by any agent`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// --- manifest-sync check: .claude-plugin/plugin.json must set-equal content/ ---
|
|
114
|
+
const manifestArg = process.argv.indexOf('--check-manifest');
|
|
115
|
+
if (manifestArg !== -1) {
|
|
116
|
+
const manifests = ['.claude-plugin/plugin.json'];
|
|
117
|
+
let manifestErrors = 0;
|
|
118
|
+
|
|
119
|
+
for (const mp of manifests) {
|
|
120
|
+
const mpath = join(import.meta.dirname, '..', mp);
|
|
121
|
+
if (!existsSync(mpath)) { errors.push(`manifest ${mp}: file not found`); continue; }
|
|
122
|
+
let mdata;
|
|
123
|
+
try { mdata = JSON.parse(readFileSync(mpath, 'utf8')); }
|
|
124
|
+
catch (e) { errors.push(`manifest ${mp}: invalid JSON`); continue; }
|
|
125
|
+
|
|
126
|
+
const mSkills = mdata?.metadata?.skills;
|
|
127
|
+
const mAgents = mdata?.metadata?.agents;
|
|
128
|
+
if (!Array.isArray(mSkills)) { errors.push(`manifest ${mp}: missing metadata.skills array`); }
|
|
129
|
+
if (!Array.isArray(mAgents)) { errors.push(`manifest ${mp}: missing metadata.agents array`); }
|
|
130
|
+
if (!mSkills || !mAgents) continue;
|
|
131
|
+
|
|
132
|
+
const mSkillSet = new Set(mSkills);
|
|
133
|
+
const mAgentSet = new Set(mAgents);
|
|
134
|
+
|
|
135
|
+
// every skill in content/ must be in manifest
|
|
136
|
+
for (const dir of skillDirs) {
|
|
137
|
+
if (!mSkillSet.has(dir)) {
|
|
138
|
+
errors.push(`manifest ${mp}: missing skill "${dir}"`);
|
|
139
|
+
manifestErrors++;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// no stale entries in manifest
|
|
143
|
+
for (const s of mSkills) {
|
|
144
|
+
if (!skillDirs.includes(s)) {
|
|
145
|
+
errors.push(`manifest ${mp}: stale skill "${s}" (not in content/skills/)`);
|
|
146
|
+
manifestErrors++;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// every agent in content/ must be in manifest
|
|
150
|
+
for (const f of agentFiles) {
|
|
151
|
+
const name = f.replace(/\.md$/, '');
|
|
152
|
+
if (!mAgentSet.has(name)) {
|
|
153
|
+
errors.push(`manifest ${mp}: missing agent "${name}"`);
|
|
154
|
+
manifestErrors++;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
for (const a of mAgents) {
|
|
158
|
+
if (!agentFiles.includes(`${a}.md`)) {
|
|
159
|
+
errors.push(`manifest ${mp}: stale agent "${a}" (not in content/agents/)`);
|
|
160
|
+
manifestErrors++;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (manifestErrors === 0) console.log('✓ manifest in sync with content/');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// --- index budget gate ---
|
|
169
|
+
const INDEX_BUDGET = 5500; // chars, all skill + agent descriptions combined
|
|
170
|
+
let totalDescChars = 0;
|
|
171
|
+
for (const dir of skillDirs) {
|
|
172
|
+
const file = join(root, 'skills', dir, 'SKILL.md');
|
|
173
|
+
if (!existsSync(file)) continue;
|
|
174
|
+
const parsed = parseFrontmatter(readFileSync(file, 'utf8'));
|
|
175
|
+
totalDescChars += (parsed.data.description ?? '').length;
|
|
176
|
+
}
|
|
177
|
+
for (const f of agentFiles) {
|
|
178
|
+
const parsed = parseFrontmatter(readFileSync(join(agentDir, f), 'utf8'));
|
|
179
|
+
totalDescChars += (parsed.data.description ?? '').length;
|
|
180
|
+
}
|
|
181
|
+
if (totalDescChars > INDEX_BUDGET) {
|
|
182
|
+
errors.push(`index budget exceeded: ${totalDescChars}/${INDEX_BUDGET} chars (skill + agent descriptions)`);
|
|
183
|
+
} else {
|
|
184
|
+
console.log(`✓ index budget: ${totalDescChars}/${INDEX_BUDGET} chars`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// --- docs-drift check: docs/skills.md and docs/agents.md must reference all content/ entries ---
|
|
188
|
+
const docsArg = process.argv.indexOf('--check-docs');
|
|
189
|
+
if (docsArg !== -1) {
|
|
190
|
+
const docsDir = join(import.meta.dirname, '..', 'docs');
|
|
191
|
+
const skillsDoc = join(docsDir, 'skills.md');
|
|
192
|
+
const agentsDoc = join(docsDir, 'agents.md');
|
|
193
|
+
let docErrors = 0;
|
|
194
|
+
|
|
195
|
+
if (existsSync(skillsDoc)) {
|
|
196
|
+
const content = readFileSync(skillsDoc, 'utf8');
|
|
197
|
+
for (const dir of skillDirs) {
|
|
198
|
+
if (!content.includes(dir)) {
|
|
199
|
+
errors.push(`docs/skills.md: missing skill "${dir}"`);
|
|
200
|
+
docErrors++;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
errors.push('docs/skills.md: file not found');
|
|
205
|
+
docErrors++;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (existsSync(agentsDoc)) {
|
|
209
|
+
const content = readFileSync(agentsDoc, 'utf8');
|
|
210
|
+
for (const f of agentFiles) {
|
|
211
|
+
const name = f.replace(/\.md$/, '');
|
|
212
|
+
if (!content.includes(name)) {
|
|
213
|
+
errors.push(`docs/agents.md: missing agent "${name}"`);
|
|
214
|
+
docErrors++;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
errors.push('docs/agents.md: file not found');
|
|
219
|
+
docErrors++;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (docErrors === 0) console.log('✓ docs in sync with content/');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (errors.length) { console.error(errors.map(e => `✗ ${e}`).join('\n')); process.exit(1); }
|
|
226
|
+
console.log(`✓ content valid: ${skillDirs.length} skills, ${agentFiles.length} agents`);
|
package/src/args.ts
CHANGED
|
@@ -9,7 +9,8 @@ export type Args = {
|
|
|
9
9
|
const VALUE_FLAGS: Record<string, string> = { '--project': 'project', '--target': 'target' };
|
|
10
10
|
const BOOL_FLAGS: Record<string, string> = {
|
|
11
11
|
'--global': 'global', '--yes': 'yes', '-y': 'yes', '--force': 'force',
|
|
12
|
-
'--dry-run': 'dryRun', '--
|
|
12
|
+
'--dry-run': 'dryRun', '--keep-logs': 'keepLogs',
|
|
13
|
+
'--help': 'help', '-h': 'help', '--version': 'version', '-v': 'version',
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
export function parseArgs(argv: string[]): Args {
|
package/src/cli.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// src/cli.ts
|
|
3
|
-
import { existsSync, readFileSync,
|
|
3
|
+
import { existsSync, readFileSync, realpathSync, rmSync } from 'node:fs';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
|
-
import {
|
|
5
|
+
import { resolve } from 'node:path';
|
|
6
6
|
import { pathToFileURL } from 'node:url';
|
|
7
7
|
import { parseArgs, type FlagValue, type Args } from './args.ts';
|
|
8
8
|
import { createRl, choose, multiChoose, confirm } from './prompt.ts';
|
|
9
9
|
import { targets, TARGET_IDS } from './targets/index.ts';
|
|
10
|
-
import { installTo, removeInstalled, VERSION
|
|
10
|
+
import { installTo, removeInstalled, VERSION } from './installer.ts';
|
|
11
11
|
import { manifestPath, readManifest, writeManifest, type Scope } from './manifest.ts';
|
|
12
|
-
import {
|
|
12
|
+
import { resetMission } from './mission.ts';
|
|
13
13
|
|
|
14
14
|
const str = (v: FlagValue): string | undefined => (typeof v === 'string' ? v : undefined);
|
|
15
15
|
const flag = (v: FlagValue): boolean => v === true;
|
|
@@ -23,11 +23,24 @@ export async function run(argv: string[]): Promise<void> {
|
|
|
23
23
|
case 'update': return install({ ...flags, force: true });
|
|
24
24
|
case 'uninstall': return uninstall(flags);
|
|
25
25
|
case 'list': return list(flags);
|
|
26
|
-
case '
|
|
26
|
+
case 'reset': return resetCmd(flags);
|
|
27
27
|
default: throw new Error(`Unknown command: ${command}`);
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function resetCmd(flags: Args['flags']): void {
|
|
32
|
+
const projectDir = resolve(str(flags.project) ?? process.cwd());
|
|
33
|
+
const force = flag(flags.force);
|
|
34
|
+
const result = resetMission(projectDir, flag(flags.keepLogs), force);
|
|
35
|
+
if (result.blocked) {
|
|
36
|
+
console.error(`✗ ${result.blocked}`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
if (result.removed.length) console.log(`removed: ${result.removed.join(', ')}`);
|
|
40
|
+
else console.log('nothing to remove.');
|
|
41
|
+
if (result.kept.length) console.log(`kept: ${result.kept.join(', ')}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
31
44
|
async function resolveOptions(flags: Args['flags']): Promise<{ scope: Scope; projectDir: string; targetIds: string[] }> {
|
|
32
45
|
const interactive = !flag(flags.yes);
|
|
33
46
|
const rl = interactive ? createRl() : null;
|
|
@@ -126,19 +139,6 @@ function list(flags: Args['flags']): void {
|
|
|
126
139
|
if (!found) console.log('No mugiwara installation found.');
|
|
127
140
|
}
|
|
128
141
|
|
|
129
|
-
function skills(): void {
|
|
130
|
-
const dir = join(CONTENT_DIR, 'skills');
|
|
131
|
-
const names = readdirSync(dir, { withFileTypes: true }).filter(e => e.isDirectory()).map(e => e.name).sort();
|
|
132
|
-
const rows = names.map(name => {
|
|
133
|
-
const { data } = parseFrontmatter(readFileSync(join(dir, name, 'SKILL.md'), 'utf8'));
|
|
134
|
-
return [name, data.description ?? ''] as const;
|
|
135
|
-
});
|
|
136
|
-
const w = Math.max(...rows.map(r => r[0].length)) + 2;
|
|
137
|
-
console.log(`mugiwara ${VERSION} — ${rows.length} skills (agentskills.io format):\n`);
|
|
138
|
-
for (const [name, description] of rows) console.log(` ${name.padEnd(w)}${description}`);
|
|
139
|
-
console.log(`\nInstall skills into any agent via skills.sh:\n npx skills add ionivetech/mugiwara`);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
142
|
function help(): void {
|
|
143
143
|
console.log(`mugiwara ${VERSION} — the Straw Hat crew for AI agents
|
|
144
144
|
|
|
@@ -147,7 +147,7 @@ Usage:
|
|
|
147
147
|
mugiwara update replace existing files (backs up differences first)
|
|
148
148
|
mugiwara uninstall remove installed files via manifest
|
|
149
149
|
mugiwara list show installations
|
|
150
|
-
mugiwara
|
|
150
|
+
mugiwara reset wipe mission state (spec/plans/results/review/issues[/logs])
|
|
151
151
|
mugiwara --help this help
|
|
152
152
|
mugiwara --version print version
|
|
153
153
|
|
|
@@ -157,7 +157,8 @@ Flags:
|
|
|
157
157
|
--target <ids|all> comma-separated: ${TARGET_IDS.join(', ')}
|
|
158
158
|
--yes, -y non-interactive (needs --global/--project, --target)
|
|
159
159
|
--force overwrite differing files (with backup)
|
|
160
|
-
--dry-run print actions without writing
|
|
160
|
+
--dry-run print actions without writing
|
|
161
|
+
--keep-logs with reset: keep .mugiwara/logs (lessons ledger survives)`);
|
|
161
162
|
}
|
|
162
163
|
|
|
163
164
|
let entry = process.argv[1] !== undefined ? resolve(process.argv[1]) : undefined;
|