@hacksmith/doraval 0.2.20 → 0.2.23
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/README.md +9 -5
- package/bin/doraval.js +1356 -168
- package/package.json +1 -1
package/bin/doraval.js
CHANGED
|
@@ -2588,9 +2588,683 @@ var init_sync = __esm(() => {
|
|
|
2588
2588
|
});
|
|
2589
2589
|
});
|
|
2590
2590
|
|
|
2591
|
+
// src/cli/commands/claude/context.ts
|
|
2592
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
|
|
2593
|
+
import { join as join7 } from "path";
|
|
2594
|
+
function detectContext(cwd = process.cwd()) {
|
|
2595
|
+
const hasClaudeDir = existsSync8(join7(cwd, ".claude"));
|
|
2596
|
+
const hasPluginManifest = existsSync8(join7(cwd, ".claude-plugin", "plugin.json"));
|
|
2597
|
+
let looseSkillFiles = [];
|
|
2598
|
+
try {
|
|
2599
|
+
const files = readdirSync3(cwd);
|
|
2600
|
+
looseSkillFiles = files.filter((f) => {
|
|
2601
|
+
if (!f.endsWith(".md") || f.startsWith("."))
|
|
2602
|
+
return false;
|
|
2603
|
+
const lower = f.toLowerCase();
|
|
2604
|
+
if (lower === "readme.md" || lower === "changelog.md" || lower === "license.md" || lower.includes("contributing"))
|
|
2605
|
+
return false;
|
|
2606
|
+
return lower.includes("skill") || lower === "skill.md";
|
|
2607
|
+
});
|
|
2608
|
+
} catch {}
|
|
2609
|
+
const isEmpty = !hasClaudeDir && !hasPluginManifest && looseSkillFiles.length === 0;
|
|
2610
|
+
return {
|
|
2611
|
+
cwd,
|
|
2612
|
+
hasClaudeDir,
|
|
2613
|
+
hasPluginManifest,
|
|
2614
|
+
looseSkillFiles,
|
|
2615
|
+
isEmpty
|
|
2616
|
+
};
|
|
2617
|
+
}
|
|
2618
|
+
var init_context = () => {};
|
|
2619
|
+
|
|
2620
|
+
// src/cli/commands/claude/new.ts
|
|
2621
|
+
var exports_new = {};
|
|
2622
|
+
__export(exports_new, {
|
|
2623
|
+
scaffold: () => scaffold,
|
|
2624
|
+
default: () => new_default,
|
|
2625
|
+
decidePath: () => decidePath
|
|
2626
|
+
});
|
|
2627
|
+
import { join as join8, basename as basename2 } from "path";
|
|
2628
|
+
import { mkdirSync as mkdirSync2, writeFileSync, existsSync as existsSync9 } from "fs";
|
|
2629
|
+
function decidePath(ctx, intent, providedName) {
|
|
2630
|
+
const rawName = providedName || "";
|
|
2631
|
+
let decisionPath = "standalone";
|
|
2632
|
+
let targetDir = ctx.cwd;
|
|
2633
|
+
let shouldCreateDir = false;
|
|
2634
|
+
let migrateExisting = false;
|
|
2635
|
+
const useCurrentDirAsRoot = rawName === "." || rawName === basename2(ctx.cwd) || !rawName;
|
|
2636
|
+
if (intent === "distribute" || intent === "self-later" && ctx.looseSkillFiles.length > 0 && !ctx.hasClaudeDir) {
|
|
2637
|
+
decisionPath = "plugin";
|
|
2638
|
+
if (useCurrentDirAsRoot) {
|
|
2639
|
+
targetDir = ctx.cwd;
|
|
2640
|
+
shouldCreateDir = false;
|
|
2641
|
+
} else {
|
|
2642
|
+
targetDir = join8(ctx.cwd, rawName);
|
|
2643
|
+
shouldCreateDir = true;
|
|
2644
|
+
}
|
|
2645
|
+
migrateExisting = ctx.looseSkillFiles.length > 0;
|
|
2646
|
+
} else if (intent === "self-later" && !ctx.hasClaudeDir) {
|
|
2647
|
+
decisionPath = "plugin";
|
|
2648
|
+
if (useCurrentDirAsRoot) {
|
|
2649
|
+
targetDir = ctx.cwd;
|
|
2650
|
+
shouldCreateDir = false;
|
|
2651
|
+
} else {
|
|
2652
|
+
targetDir = join8(ctx.cwd, rawName);
|
|
2653
|
+
shouldCreateDir = true;
|
|
2654
|
+
}
|
|
2655
|
+
} else if (decisionPath === "standalone") {
|
|
2656
|
+
if (useCurrentDirAsRoot) {
|
|
2657
|
+
targetDir = ctx.cwd;
|
|
2658
|
+
shouldCreateDir = false;
|
|
2659
|
+
} else {
|
|
2660
|
+
targetDir = join8(ctx.cwd, rawName);
|
|
2661
|
+
shouldCreateDir = true;
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
return { path: decisionPath, targetDir, shouldCreateDir, migrateExisting };
|
|
2665
|
+
}
|
|
2666
|
+
function scaffold(decision, ctx, migrateContent) {
|
|
2667
|
+
const { targetDir, path, shouldCreateDir } = decision;
|
|
2668
|
+
if (existsSync9(targetDir) && shouldCreateDir) {
|
|
2669
|
+
ui.fail("Target already exists");
|
|
2670
|
+
process.exit(1);
|
|
2671
|
+
}
|
|
2672
|
+
if (shouldCreateDir) {
|
|
2673
|
+
mkdirSync2(targetDir, { recursive: true });
|
|
2674
|
+
}
|
|
2675
|
+
if (path === "plugin") {
|
|
2676
|
+
const pluginName = basename2(targetDir);
|
|
2677
|
+
const pluginJson = {
|
|
2678
|
+
name: pluginName,
|
|
2679
|
+
description: "Scaffolded by doraval claude new",
|
|
2680
|
+
version: "0.1.0"
|
|
2681
|
+
};
|
|
2682
|
+
mkdirSync2(join8(targetDir, ".claude-plugin"), { recursive: true });
|
|
2683
|
+
writeFileSync(join8(targetDir, ".claude-plugin", "plugin.json"), JSON.stringify(pluginJson, null, 2));
|
|
2684
|
+
const marketplaceJson = {
|
|
2685
|
+
name: pluginName,
|
|
2686
|
+
version: "0.1.0",
|
|
2687
|
+
description: "Scaffolded by doraval claude new",
|
|
2688
|
+
author: { name: "" },
|
|
2689
|
+
homepage: "",
|
|
2690
|
+
repository: "",
|
|
2691
|
+
license: "MIT",
|
|
2692
|
+
keywords: ["claude-code", "skills", "plugin"]
|
|
2693
|
+
};
|
|
2694
|
+
writeFileSync(join8(targetDir, "marketplace.json"), JSON.stringify(marketplaceJson, null, 2));
|
|
2695
|
+
const demoSkillName = "doraval";
|
|
2696
|
+
mkdirSync2(join8(targetDir, "skills", demoSkillName), { recursive: true });
|
|
2697
|
+
let skillContent;
|
|
2698
|
+
if (migrateContent) {
|
|
2699
|
+
skillContent = migrateContent;
|
|
2700
|
+
} else {
|
|
2701
|
+
skillContent = `---
|
|
2702
|
+
name: ${demoSkillName}
|
|
2703
|
+
description: Use doraval to validate, measure drift, and judge skills and plugins. Use when authoring or reviewing context engineering artifacts for AI coding agents.
|
|
2704
|
+
---
|
|
2705
|
+
|
|
2706
|
+
# Use Doraval
|
|
2707
|
+
|
|
2708
|
+
Doraval is the context engineering toolkit.
|
|
2709
|
+
|
|
2710
|
+
When you need to check a skill or plugin:
|
|
2711
|
+
|
|
2712
|
+
- Validate the current directory: \`doraval validate .\`
|
|
2713
|
+
- Validate a specific plugin: \`doraval validate . --for claude:plugin\`
|
|
2714
|
+
- Validate one skill: \`doraval skill validate ./skills/${demoSkillName}/\`
|
|
2715
|
+
- Check for rubric drift: \`doraval skill drift ./skills/${demoSkillName}/\`
|
|
2716
|
+
- Get an AI quality judgment: \`doraval skill judge ./skills/${demoSkillName}/\`
|
|
2717
|
+
|
|
2718
|
+
Always run \`doraval validate\` before sharing or publishing a plugin. This skill demonstrates a complete, self-referential example of using doraval inside a generated plugin.`;
|
|
2719
|
+
}
|
|
2720
|
+
writeFileSync(join8(targetDir, "skills", demoSkillName, "SKILL.md"), skillContent);
|
|
2721
|
+
const readmePath = join8(targetDir, "README.md");
|
|
2722
|
+
if (!existsSync9(readmePath)) {
|
|
2723
|
+
writeFileSync(readmePath, "# " + pluginName + `
|
|
2724
|
+
|
|
2725
|
+
Claude Code plugin scaffolded by doraval.`);
|
|
2726
|
+
}
|
|
2727
|
+
} else {
|
|
2728
|
+
mkdirSync2(join8(targetDir, ".claude", "skills", "my-skill"), { recursive: true });
|
|
2729
|
+
const skillBody = migrateContent || `# My Skill
|
|
2730
|
+
|
|
2731
|
+
Basic starter.`;
|
|
2732
|
+
writeFileSync(join8(targetDir, ".claude", "skills", "my-skill", "SKILL.md"), `---
|
|
2733
|
+
name: my-skill
|
|
2734
|
+
description: Starter
|
|
2735
|
+
---
|
|
2736
|
+
|
|
2737
|
+
${skillBody}`);
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
var import_picocolors10, new_default;
|
|
2741
|
+
var init_new = __esm(() => {
|
|
2742
|
+
init_dist();
|
|
2743
|
+
init_out();
|
|
2744
|
+
init_context();
|
|
2745
|
+
init_prompt();
|
|
2746
|
+
import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
2747
|
+
new_default = defineCommand({
|
|
2748
|
+
meta: {
|
|
2749
|
+
name: "new",
|
|
2750
|
+
description: "Create a new skill or plugin following Claude Code packaging rules"
|
|
2751
|
+
},
|
|
2752
|
+
args: {
|
|
2753
|
+
name: {
|
|
2754
|
+
type: "positional",
|
|
2755
|
+
description: "Optional name for the skill or plugin",
|
|
2756
|
+
required: false
|
|
2757
|
+
},
|
|
2758
|
+
yes: {
|
|
2759
|
+
type: "boolean",
|
|
2760
|
+
description: "Skip interactive prompts (use defaults and flags)",
|
|
2761
|
+
default: false
|
|
2762
|
+
},
|
|
2763
|
+
intent: {
|
|
2764
|
+
type: "string",
|
|
2765
|
+
description: 'Intent: "self" | "self-later" | "distribute"',
|
|
2766
|
+
required: false
|
|
2767
|
+
}
|
|
2768
|
+
},
|
|
2769
|
+
run({ args }) {
|
|
2770
|
+
ui.heading("doraval claude new \u2014 Context-aware scaffolding");
|
|
2771
|
+
const ctx = detectContext();
|
|
2772
|
+
let intent = args.intent || "self-later";
|
|
2773
|
+
if (!args.yes) {
|
|
2774
|
+
const ans = prompt(" Intent (self | self-later | distribute)", intent);
|
|
2775
|
+
intent = ans || intent;
|
|
2776
|
+
}
|
|
2777
|
+
const decision = decidePath(ctx, intent, args.name);
|
|
2778
|
+
ui.info(` Decision: path=${decision.path}, target=${decision.targetDir}`);
|
|
2779
|
+
let migrateContent;
|
|
2780
|
+
if (decision.migrateExisting && !args.yes) {
|
|
2781
|
+
migrateContent = "Content from your existing SKILL.md (user-confirmed).";
|
|
2782
|
+
}
|
|
2783
|
+
scaffold(decision, ctx, migrateContent);
|
|
2784
|
+
ui.write(`
|
|
2785
|
+
${import_picocolors10.default.green("\u2713")} Created ${decision.path} at ${import_picocolors10.default.bold(decision.targetDir)}`);
|
|
2786
|
+
const cmdName = decision.path === "plugin" ? `/${basename2(decision.targetDir)}:doraval` : "/my-skill";
|
|
2787
|
+
ui.info(` Command: ${cmdName}`);
|
|
2788
|
+
if (decision.path === "plugin") {
|
|
2789
|
+
ui.info(` Claude: .claude-plugin/plugin.json`);
|
|
2790
|
+
ui.info(` Marketplace: marketplace.json (unified / cross-provider listings)`);
|
|
2791
|
+
}
|
|
2792
|
+
ui.info(` Test: claude --plugin-dir ${decision.targetDir} (or use normally for standalone)`);
|
|
2793
|
+
ui.info(` Validate: doraval validate ${decision.targetDir}`);
|
|
2794
|
+
if (decision.path === "plugin" && decision.migrateExisting) {
|
|
2795
|
+
ui.info(" (Existing content migrated where confirmed.)");
|
|
2796
|
+
}
|
|
2797
|
+
process.exit(0);
|
|
2798
|
+
}
|
|
2799
|
+
});
|
|
2800
|
+
});
|
|
2801
|
+
|
|
2802
|
+
// src/cli/commands/bump.ts
|
|
2803
|
+
var exports_bump = {};
|
|
2804
|
+
__export(exports_bump, {
|
|
2805
|
+
default: () => bump_default
|
|
2806
|
+
});
|
|
2807
|
+
import { resolve as resolve3, join as join9, dirname, relative } from "path";
|
|
2808
|
+
import { existsSync as existsSync10, readFileSync, writeFileSync as writeFileSync2, readdirSync as readdirSync4, statSync } from "fs";
|
|
2809
|
+
function bumpVersion(current, type) {
|
|
2810
|
+
if (/^\d+\.\d+\.\d+$/.test(type))
|
|
2811
|
+
return type;
|
|
2812
|
+
const curr = current || "0.0.0";
|
|
2813
|
+
const parts = curr.split(".").map((n) => parseInt(n, 10) || 0);
|
|
2814
|
+
const [major = 0, minor = 0, patch = 0] = parts;
|
|
2815
|
+
switch (type) {
|
|
2816
|
+
case "patch":
|
|
2817
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
2818
|
+
case "minor":
|
|
2819
|
+
return `${major}.${minor + 1}.0`;
|
|
2820
|
+
case "major":
|
|
2821
|
+
return `${major + 1}.0.0`;
|
|
2822
|
+
default:
|
|
2823
|
+
throw new Error(`Invalid bump type "${type}". Use patch, minor, major, or an exact version like 1.2.3`);
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
function readJson(p) {
|
|
2827
|
+
try {
|
|
2828
|
+
const content = readFileSync(p, "utf8");
|
|
2829
|
+
return JSON.parse(content);
|
|
2830
|
+
} catch {
|
|
2831
|
+
return null;
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
function writeJson(p, data) {
|
|
2835
|
+
writeFileSync2(p, JSON.stringify(data, null, 2) + `
|
|
2836
|
+
`, "utf8");
|
|
2837
|
+
}
|
|
2838
|
+
function getVersion(obj) {
|
|
2839
|
+
if (!obj || typeof obj !== "object")
|
|
2840
|
+
return;
|
|
2841
|
+
if (typeof obj.version === "string")
|
|
2842
|
+
return obj.version;
|
|
2843
|
+
if (obj.metadata && typeof obj.metadata.version === "string")
|
|
2844
|
+
return obj.metadata.version;
|
|
2845
|
+
return;
|
|
2846
|
+
}
|
|
2847
|
+
function setVersion(obj, newVersion) {
|
|
2848
|
+
if (!obj || typeof obj !== "object")
|
|
2849
|
+
return false;
|
|
2850
|
+
if (typeof obj.version === "string") {
|
|
2851
|
+
obj.version = newVersion;
|
|
2852
|
+
return true;
|
|
2853
|
+
}
|
|
2854
|
+
if (obj.metadata && typeof obj.metadata.version === "string") {
|
|
2855
|
+
obj.metadata.version = newVersion;
|
|
2856
|
+
return true;
|
|
2857
|
+
}
|
|
2858
|
+
return false;
|
|
2859
|
+
}
|
|
2860
|
+
function walkForTargets(dir, maxDepth = 6, currentDepth = 0) {
|
|
2861
|
+
const results = [];
|
|
2862
|
+
if (currentDepth > maxDepth)
|
|
2863
|
+
return results;
|
|
2864
|
+
let entries;
|
|
2865
|
+
try {
|
|
2866
|
+
entries = readdirSync4(dir);
|
|
2867
|
+
} catch {
|
|
2868
|
+
return results;
|
|
2869
|
+
}
|
|
2870
|
+
for (const entry of entries) {
|
|
2871
|
+
const full = join9(dir, entry);
|
|
2872
|
+
let st;
|
|
2873
|
+
try {
|
|
2874
|
+
st = statSync(full);
|
|
2875
|
+
} catch {
|
|
2876
|
+
continue;
|
|
2877
|
+
}
|
|
2878
|
+
if (st.isDirectory()) {
|
|
2879
|
+
const sub = walkForTargets(full, maxDepth, currentDepth + 1);
|
|
2880
|
+
results.push(...sub);
|
|
2881
|
+
} else if (st.isFile()) {
|
|
2882
|
+
if (entry === "plugin.json") {
|
|
2883
|
+
const parentDir = dirname(full);
|
|
2884
|
+
const parentName = parentDir.split(/[/\\]/).pop();
|
|
2885
|
+
if (parentName === ".claude-plugin" || parentName === ".codex-plugin" || parentName === ".cursor-plugin") {
|
|
2886
|
+
results.push({
|
|
2887
|
+
file: full,
|
|
2888
|
+
kind: "plugin",
|
|
2889
|
+
label: `plugin manifest (${parentName.replace(".", "")})`
|
|
2890
|
+
});
|
|
2891
|
+
}
|
|
2892
|
+
} else if (entry === "marketplace.json") {
|
|
2893
|
+
const json = readJson(full);
|
|
2894
|
+
if (json && getVersion(json)) {
|
|
2895
|
+
results.push({
|
|
2896
|
+
file: full,
|
|
2897
|
+
kind: "marketplace",
|
|
2898
|
+
label: "marketplace.json"
|
|
2899
|
+
});
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
return results;
|
|
2905
|
+
}
|
|
2906
|
+
var import_picocolors11, bump_default;
|
|
2907
|
+
var init_bump = __esm(() => {
|
|
2908
|
+
init_dist();
|
|
2909
|
+
init_out();
|
|
2910
|
+
import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
2911
|
+
bump_default = defineCommand({
|
|
2912
|
+
meta: {
|
|
2913
|
+
name: "bump",
|
|
2914
|
+
description: "Bump semver versions in plugin.json (manifests) and marketplace.json files (supports Claude, Codex, Cursor)"
|
|
2915
|
+
},
|
|
2916
|
+
args: {
|
|
2917
|
+
type: {
|
|
2918
|
+
type: "positional",
|
|
2919
|
+
description: "patch | minor | major | x.y.z (exact version)",
|
|
2920
|
+
required: false
|
|
2921
|
+
},
|
|
2922
|
+
path: {
|
|
2923
|
+
type: "positional",
|
|
2924
|
+
description: "Directory to scan from (defaults to current dir). Supports single plugin or marketplace root with many plugins/",
|
|
2925
|
+
required: false
|
|
2926
|
+
},
|
|
2927
|
+
only: {
|
|
2928
|
+
type: "string",
|
|
2929
|
+
description: 'Scope to "all" (default), "plugin" (only plugin.json manifests), or "marketplace" (only marketplace.json files that carry a top-level version)',
|
|
2930
|
+
default: "all"
|
|
2931
|
+
}
|
|
2932
|
+
},
|
|
2933
|
+
run({ args }) {
|
|
2934
|
+
let rawType = args.type || "patch";
|
|
2935
|
+
let targetPath = args.path || ".";
|
|
2936
|
+
const scopeInput = (args.only || "all").toLowerCase();
|
|
2937
|
+
const scope = scopeInput === "plugin" || scopeInput === "marketplace" ? scopeInput : "all";
|
|
2938
|
+
if (!["all", "plugin", "marketplace"].includes(scopeInput)) {
|
|
2939
|
+
ui.fail(`Invalid --only "${args.only}". Allowed: all, plugin, marketplace.`);
|
|
2940
|
+
process.exit(1);
|
|
2941
|
+
}
|
|
2942
|
+
const isKnownType = ["patch", "minor", "major"].includes(rawType) || /^\d+\.\d+\.\d+$/.test(rawType);
|
|
2943
|
+
const maybePath = resolve3(rawType);
|
|
2944
|
+
const looksLikeDir = existsSync10(maybePath) || rawType === "." || rawType.startsWith("./") || rawType.startsWith("../");
|
|
2945
|
+
if (!isKnownType && looksLikeDir) {
|
|
2946
|
+
targetPath = rawType;
|
|
2947
|
+
rawType = "patch";
|
|
2948
|
+
} else if (!isKnownType) {
|
|
2949
|
+
ui.fail(`Unknown bump type "${rawType}". Use patch | minor | major | 1.2.3`);
|
|
2950
|
+
process.exit(1);
|
|
2951
|
+
}
|
|
2952
|
+
const root = resolve3(targetPath);
|
|
2953
|
+
if (!existsSync10(root)) {
|
|
2954
|
+
ui.fail(`Path does not exist: ${root}`);
|
|
2955
|
+
process.exit(1);
|
|
2956
|
+
}
|
|
2957
|
+
ui.heading("doraval bump");
|
|
2958
|
+
ui.info(` scanning: ${root}`);
|
|
2959
|
+
ui.info(` scope: ${scope} (use --only plugin or --only marketplace to narrow; Cursor metadata.version supported)`);
|
|
2960
|
+
const discovered = walkForTargets(root);
|
|
2961
|
+
let targets = discovered;
|
|
2962
|
+
if (scope === "plugin") {
|
|
2963
|
+
targets = discovered.filter((t) => t.kind === "plugin");
|
|
2964
|
+
} else if (scope === "marketplace") {
|
|
2965
|
+
targets = discovered.filter((t) => t.kind === "marketplace");
|
|
2966
|
+
}
|
|
2967
|
+
if (targets.length === 0) {
|
|
2968
|
+
ui.fail("No matching files found under the scope.");
|
|
2969
|
+
ui.info("");
|
|
2970
|
+
ui.info(" Looked for (recursively):");
|
|
2971
|
+
ui.info(" \u2022 **/.claude-plugin/plugin.json");
|
|
2972
|
+
ui.info(" \u2022 **/.codex-plugin/plugin.json");
|
|
2973
|
+
ui.info(" \u2022 **/.cursor-plugin/plugin.json (or marketplace.json)");
|
|
2974
|
+
ui.info(" \u2022 **/marketplace.json (top-level version or metadata.version for Cursor)");
|
|
2975
|
+
ui.info("");
|
|
2976
|
+
ui.info(" Tip: run from inside a plugin directory, or pass a path that contains plugins/.");
|
|
2977
|
+
ui.info(" Examples:");
|
|
2978
|
+
ui.info(" dora bump minor");
|
|
2979
|
+
ui.info(" dora bump minor ./my-claude-plugin");
|
|
2980
|
+
ui.info(" dora bump --only plugin . # only the manifests");
|
|
2981
|
+
ui.info(" dora bump --only marketplace ./marketplaces-root # includes Cursor metadata.version");
|
|
2982
|
+
process.exit(1);
|
|
2983
|
+
}
|
|
2984
|
+
ui.info(` matched ${targets.length} file(s)`);
|
|
2985
|
+
let bumpedCount = 0;
|
|
2986
|
+
for (const t of targets) {
|
|
2987
|
+
const json = readJson(t.file);
|
|
2988
|
+
if (!json || typeof json !== "object") {
|
|
2989
|
+
ui.warnItem(`skipped (invalid JSON): ${relative(root, t.file)}`);
|
|
2990
|
+
continue;
|
|
2991
|
+
}
|
|
2992
|
+
const current = getVersion(json);
|
|
2993
|
+
let next;
|
|
2994
|
+
try {
|
|
2995
|
+
next = bumpVersion(current, rawType);
|
|
2996
|
+
} catch (err) {
|
|
2997
|
+
ui.fail(err.message || String(err));
|
|
2998
|
+
process.exit(1);
|
|
2999
|
+
}
|
|
3000
|
+
const relPath = relative(root, t.file);
|
|
3001
|
+
if (current === next) {
|
|
3002
|
+
ui.dim(` \u2022 ${t.label} ${current || "(no version)"} (no change) [${relPath}]`);
|
|
3003
|
+
continue;
|
|
3004
|
+
}
|
|
3005
|
+
const didUpdate = setVersion(json, next);
|
|
3006
|
+
if (!didUpdate) {
|
|
3007
|
+
ui.warnItem(`skipped (could not locate version field to update): ${relPath}`);
|
|
3008
|
+
continue;
|
|
3009
|
+
}
|
|
3010
|
+
writeJson(t.file, json);
|
|
3011
|
+
ui.success(`${t.label}: ${import_picocolors11.default.dim(current || "(none)")} \u2192 ${import_picocolors11.default.green(next)}`);
|
|
3012
|
+
ui.info(` ${relPath}`);
|
|
3013
|
+
bumpedCount++;
|
|
3014
|
+
}
|
|
3015
|
+
ui.blank();
|
|
3016
|
+
if (bumpedCount === 0) {
|
|
3017
|
+
ui.info("All matched files were already at the target version.");
|
|
3018
|
+
} else {
|
|
3019
|
+
ui.info(`Done. Bumped ${bumpedCount} file(s).`);
|
|
3020
|
+
ui.dim(" Next: doraval validate " + (targetPath === "." ? "." : targetPath));
|
|
3021
|
+
}
|
|
3022
|
+
process.exit(0);
|
|
3023
|
+
}
|
|
3024
|
+
});
|
|
3025
|
+
});
|
|
3026
|
+
|
|
3027
|
+
// src/cli/commands/codex/context.ts
|
|
3028
|
+
import { existsSync as existsSync11, readdirSync as readdirSync5 } from "fs";
|
|
3029
|
+
import { join as join10 } from "path";
|
|
3030
|
+
function detectContext2(cwd = process.cwd()) {
|
|
3031
|
+
const hasCodexDir = existsSync11(join10(cwd, ".codex"));
|
|
3032
|
+
const hasPluginManifest = existsSync11(join10(cwd, ".codex-plugin", "plugin.json"));
|
|
3033
|
+
const hasMarketplace = existsSync11(join10(cwd, ".agents", "plugins", "marketplace.json")) || existsSync11(join10(cwd, ".codex-plugin", "marketplace.json"));
|
|
3034
|
+
let looseSkillFiles = [];
|
|
3035
|
+
try {
|
|
3036
|
+
const files = readdirSync5(cwd);
|
|
3037
|
+
looseSkillFiles = files.filter((f) => {
|
|
3038
|
+
if (!f.endsWith(".md") || f.startsWith("."))
|
|
3039
|
+
return false;
|
|
3040
|
+
const lower = f.toLowerCase();
|
|
3041
|
+
if (lower === "readme.md" || lower === "changelog.md" || lower === "license.md" || lower.includes("contributing"))
|
|
3042
|
+
return false;
|
|
3043
|
+
return lower.includes("skill") || lower === "skill.md";
|
|
3044
|
+
});
|
|
3045
|
+
} catch {}
|
|
3046
|
+
const isEmpty = !hasPluginManifest && looseSkillFiles.length === 0;
|
|
3047
|
+
return {
|
|
3048
|
+
cwd,
|
|
3049
|
+
hasCodexDir,
|
|
3050
|
+
hasPluginManifest,
|
|
3051
|
+
hasMarketplace,
|
|
3052
|
+
looseSkillFiles,
|
|
3053
|
+
isEmpty
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
var init_context2 = () => {};
|
|
3057
|
+
|
|
3058
|
+
// src/cli/commands/codex/new.ts
|
|
3059
|
+
var exports_new2 = {};
|
|
3060
|
+
__export(exports_new2, {
|
|
3061
|
+
scaffold: () => scaffold2,
|
|
3062
|
+
default: () => new_default2,
|
|
3063
|
+
decidePath: () => decidePath2
|
|
3064
|
+
});
|
|
3065
|
+
import { join as join11, basename as basename3 } from "path";
|
|
3066
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, existsSync as existsSync12 } from "fs";
|
|
3067
|
+
function decidePath2(ctx, intent, providedName) {
|
|
3068
|
+
const rawName = providedName || "";
|
|
3069
|
+
let decisionPath = "standalone";
|
|
3070
|
+
let targetDir = ctx.cwd;
|
|
3071
|
+
let shouldCreateDir = false;
|
|
3072
|
+
let migrateExisting = false;
|
|
3073
|
+
const useCurrentDirAsRoot = rawName === "." || rawName === basename3(ctx.cwd) || !rawName;
|
|
3074
|
+
if (intent === "distribute" || intent === "self-later" && ctx.looseSkillFiles.length > 0 && !ctx.hasPluginManifest) {
|
|
3075
|
+
decisionPath = "plugin";
|
|
3076
|
+
if (useCurrentDirAsRoot) {
|
|
3077
|
+
targetDir = ctx.cwd;
|
|
3078
|
+
shouldCreateDir = false;
|
|
3079
|
+
} else {
|
|
3080
|
+
targetDir = join11(ctx.cwd, rawName);
|
|
3081
|
+
shouldCreateDir = true;
|
|
3082
|
+
}
|
|
3083
|
+
migrateExisting = ctx.looseSkillFiles.length > 0;
|
|
3084
|
+
} else if (intent === "self-later" && !ctx.hasPluginManifest) {
|
|
3085
|
+
decisionPath = "plugin";
|
|
3086
|
+
if (useCurrentDirAsRoot) {
|
|
3087
|
+
targetDir = ctx.cwd;
|
|
3088
|
+
shouldCreateDir = false;
|
|
3089
|
+
} else {
|
|
3090
|
+
targetDir = join11(ctx.cwd, rawName);
|
|
3091
|
+
shouldCreateDir = true;
|
|
3092
|
+
}
|
|
3093
|
+
} else if (decisionPath === "standalone") {
|
|
3094
|
+
if (useCurrentDirAsRoot) {
|
|
3095
|
+
targetDir = ctx.cwd;
|
|
3096
|
+
shouldCreateDir = false;
|
|
3097
|
+
} else {
|
|
3098
|
+
targetDir = join11(ctx.cwd, rawName);
|
|
3099
|
+
shouldCreateDir = true;
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
return { path: decisionPath, targetDir, shouldCreateDir, migrateExisting };
|
|
3103
|
+
}
|
|
3104
|
+
function scaffold2(decision, ctx, migrateContent) {
|
|
3105
|
+
const { targetDir, path, shouldCreateDir } = decision;
|
|
3106
|
+
if (existsSync12(targetDir) && shouldCreateDir) {
|
|
3107
|
+
ui.fail("Target already exists");
|
|
3108
|
+
process.exit(1);
|
|
3109
|
+
}
|
|
3110
|
+
if (shouldCreateDir) {
|
|
3111
|
+
mkdirSync3(targetDir, { recursive: true });
|
|
3112
|
+
}
|
|
3113
|
+
if (path === "plugin") {
|
|
3114
|
+
const pluginName = basename3(targetDir);
|
|
3115
|
+
const pluginJson = {
|
|
3116
|
+
name: pluginName,
|
|
3117
|
+
version: "0.1.0",
|
|
3118
|
+
description: "Scaffolded by doraval codex new",
|
|
3119
|
+
skills: "./skills/",
|
|
3120
|
+
interface: {
|
|
3121
|
+
displayName: pluginName,
|
|
3122
|
+
shortDescription: "Scaffolded starter plugin",
|
|
3123
|
+
category: "Productivity"
|
|
3124
|
+
}
|
|
3125
|
+
};
|
|
3126
|
+
mkdirSync3(join11(targetDir, ".codex-plugin"), { recursive: true });
|
|
3127
|
+
writeFileSync3(join11(targetDir, ".codex-plugin", "plugin.json"), JSON.stringify(pluginJson, null, 2));
|
|
3128
|
+
mkdirSync3(join11(targetDir, ".agents", "plugins"), { recursive: true });
|
|
3129
|
+
const marketplaceJson = {
|
|
3130
|
+
name: "local",
|
|
3131
|
+
interface: {
|
|
3132
|
+
displayName: "Local (doraval scaffold)"
|
|
3133
|
+
},
|
|
3134
|
+
plugins: [
|
|
3135
|
+
{
|
|
3136
|
+
name: pluginName,
|
|
3137
|
+
source: {
|
|
3138
|
+
source: "local",
|
|
3139
|
+
path: "../.."
|
|
3140
|
+
},
|
|
3141
|
+
policy: {
|
|
3142
|
+
installation: "AVAILABLE",
|
|
3143
|
+
authentication: "ON_INSTALL"
|
|
3144
|
+
},
|
|
3145
|
+
category: "Productivity"
|
|
3146
|
+
}
|
|
3147
|
+
]
|
|
3148
|
+
};
|
|
3149
|
+
writeFileSync3(join11(targetDir, ".agents", "plugins", "marketplace.json"), JSON.stringify(marketplaceJson, null, 2));
|
|
3150
|
+
const demoSkillName = "doraval";
|
|
3151
|
+
mkdirSync3(join11(targetDir, "skills", demoSkillName), { recursive: true });
|
|
3152
|
+
let skillContent;
|
|
3153
|
+
if (migrateContent) {
|
|
3154
|
+
skillContent = migrateContent;
|
|
3155
|
+
} else {
|
|
3156
|
+
skillContent = `---
|
|
3157
|
+
name: ${demoSkillName}
|
|
3158
|
+
description: Use doraval to validate, measure drift, and judge skills and plugins. Use when authoring or reviewing context engineering artifacts for AI coding agents (works for Codex too).
|
|
3159
|
+
---
|
|
3160
|
+
|
|
3161
|
+
# Use Doraval (Codex edition)
|
|
3162
|
+
|
|
3163
|
+
Doraval is the context engineering toolkit.
|
|
3164
|
+
|
|
3165
|
+
When you need to check a skill or Codex plugin:
|
|
3166
|
+
|
|
3167
|
+
- Validate the current directory: \`doraval validate .\`
|
|
3168
|
+
- Validate one skill: \`doraval skill validate ./skills/${demoSkillName}/\`
|
|
3169
|
+
- Check for rubric drift: \`doraval skill drift ./skills/${demoSkillName}/\`
|
|
3170
|
+
- Get an AI quality judgment: \`doraval skill judge ./skills/${demoSkillName}/\`
|
|
3171
|
+
|
|
3172
|
+
Always run \`doraval validate\` before sharing or publishing a plugin.
|
|
3173
|
+
|
|
3174
|
+
This skill demonstrates a complete, self-referential example of using doraval inside a generated Codex plugin.
|
|
3175
|
+
|
|
3176
|
+
To test in Codex:
|
|
3177
|
+
1. Make sure this plugin is listed in a marketplace (we created .agents/plugins/marketplace.json for you).
|
|
3178
|
+
2. Restart Codex.
|
|
3179
|
+
3. Open the plugin directory, select your local marketplace, and enable the plugin.
|
|
3180
|
+
4. Invoke the demo with /${pluginName}:doraval`;
|
|
3181
|
+
}
|
|
3182
|
+
writeFileSync3(join11(targetDir, "skills", demoSkillName, "SKILL.md"), skillContent);
|
|
3183
|
+
const readmePath = join11(targetDir, "README.md");
|
|
3184
|
+
if (!existsSync12(readmePath)) {
|
|
3185
|
+
writeFileSync3(readmePath, "# " + pluginName + `
|
|
3186
|
+
|
|
3187
|
+
Codex plugin scaffolded by doraval.`);
|
|
3188
|
+
}
|
|
3189
|
+
} else {
|
|
3190
|
+
mkdirSync3(join11(targetDir, "skills", "doraval"), { recursive: true });
|
|
3191
|
+
const skillBody = migrateContent || `# My Skill
|
|
3192
|
+
|
|
3193
|
+
Basic starter for Codex.`;
|
|
3194
|
+
writeFileSync3(join11(targetDir, "skills", "doraval", "SKILL.md"), `---
|
|
3195
|
+
name: doraval
|
|
3196
|
+
description: Starter (local skill)
|
|
3197
|
+
---
|
|
3198
|
+
|
|
3199
|
+
${skillBody}`);
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
var import_picocolors12, new_default2;
|
|
3203
|
+
var init_new2 = __esm(() => {
|
|
3204
|
+
init_dist();
|
|
3205
|
+
init_out();
|
|
3206
|
+
init_context2();
|
|
3207
|
+
init_prompt();
|
|
3208
|
+
import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
3209
|
+
new_default2 = defineCommand({
|
|
3210
|
+
meta: {
|
|
3211
|
+
name: "new",
|
|
3212
|
+
description: "Create a new skill or plugin following Codex packaging rules"
|
|
3213
|
+
},
|
|
3214
|
+
args: {
|
|
3215
|
+
name: {
|
|
3216
|
+
type: "positional",
|
|
3217
|
+
description: "Optional name for the skill or plugin",
|
|
3218
|
+
required: false
|
|
3219
|
+
},
|
|
3220
|
+
yes: {
|
|
3221
|
+
type: "boolean",
|
|
3222
|
+
description: "Skip interactive prompts (use defaults and flags)",
|
|
3223
|
+
default: false
|
|
3224
|
+
},
|
|
3225
|
+
intent: {
|
|
3226
|
+
type: "string",
|
|
3227
|
+
description: 'Intent: "self" | "self-later" | "distribute"',
|
|
3228
|
+
required: false
|
|
3229
|
+
}
|
|
3230
|
+
},
|
|
3231
|
+
run({ args }) {
|
|
3232
|
+
ui.heading("doraval codex new \u2014 Context-aware scaffolding");
|
|
3233
|
+
const ctx = detectContext2();
|
|
3234
|
+
let intent = args.intent || "self-later";
|
|
3235
|
+
if (!args.yes) {
|
|
3236
|
+
const ans = prompt(" Intent (self | self-later | distribute)", intent);
|
|
3237
|
+
intent = ans || intent;
|
|
3238
|
+
}
|
|
3239
|
+
const decision = decidePath2(ctx, intent, args.name);
|
|
3240
|
+
ui.info(` Decision: path=${decision.path}, target=${decision.targetDir}`);
|
|
3241
|
+
let migrateContent;
|
|
3242
|
+
if (decision.migrateExisting && !args.yes) {
|
|
3243
|
+
migrateContent = "Content from your existing SKILL.md (user-confirmed).";
|
|
3244
|
+
}
|
|
3245
|
+
scaffold2(decision, ctx, migrateContent);
|
|
3246
|
+
ui.write(`
|
|
3247
|
+
${import_picocolors12.default.green("\u2713")} Created ${decision.path} at ${import_picocolors12.default.bold(decision.targetDir)}`);
|
|
3248
|
+
const cmdName = decision.path === "plugin" ? `/${basename3(decision.targetDir)}:doraval` : "/doraval (local skill)";
|
|
3249
|
+
ui.info(` Command: ${cmdName}`);
|
|
3250
|
+
if (decision.path === "plugin") {
|
|
3251
|
+
ui.info(` Codex manifest: .codex-plugin/plugin.json`);
|
|
3252
|
+
ui.info(` Marketplace catalog: .agents/plugins/marketplace.json (starter for local testing)`);
|
|
3253
|
+
ui.info(` (Move/expand the marketplace.json to $REPO_ROOT/.agents/plugins/ or ~/.agents/plugins/ as needed)`);
|
|
3254
|
+
}
|
|
3255
|
+
ui.info(` Test (local): restart Codex, select your marketplace in the plugin directory`);
|
|
3256
|
+
ui.info(` Validate: doraval validate ${decision.targetDir}`);
|
|
3257
|
+
if (decision.path === "plugin" && decision.migrateExisting) {
|
|
3258
|
+
ui.info(" (Existing content migrated where confirmed.)");
|
|
3259
|
+
}
|
|
3260
|
+
process.exit(0);
|
|
3261
|
+
}
|
|
3262
|
+
});
|
|
3263
|
+
});
|
|
3264
|
+
|
|
2591
3265
|
// src/validators/claude/skill.ts
|
|
2592
|
-
import { existsSync as
|
|
2593
|
-
import { resolve as
|
|
3266
|
+
import { existsSync as existsSync13 } from "fs";
|
|
3267
|
+
import { resolve as resolve4 } from "path";
|
|
2594
3268
|
var OPTIONAL_DIRS2, claudeSkillValidator;
|
|
2595
3269
|
var init_skill = __esm(() => {
|
|
2596
3270
|
init_frontmatter();
|
|
@@ -2602,10 +3276,10 @@ var init_skill = __esm(() => {
|
|
|
2602
3276
|
name: "Claude Skill",
|
|
2603
3277
|
description: "Validates SKILL.md per current Claude Code spec: frontmatter (name/description relaxed to recommended; directory name usually provides the /command), body, supporting files, dynamic injection (!`cmd`), substitutions ($ARGUMENTS, ${CLAUDE_*}), and advanced fields (allowed-tools, context, disable-model-invocation, when_to_use, etc.)",
|
|
2604
3278
|
detect(dir) {
|
|
2605
|
-
return
|
|
3279
|
+
return existsSync13(resolve4(dir, "SKILL.md"));
|
|
2606
3280
|
},
|
|
2607
3281
|
async validate(dir, _opts) {
|
|
2608
|
-
const skillMd =
|
|
3282
|
+
const skillMd = resolve4(dir, "SKILL.md");
|
|
2609
3283
|
const raw = await Bun.file(skillMd).text();
|
|
2610
3284
|
let parsed;
|
|
2611
3285
|
try {
|
|
@@ -2617,32 +3291,108 @@ var init_skill = __esm(() => {
|
|
|
2617
3291
|
passes: []
|
|
2618
3292
|
};
|
|
2619
3293
|
}
|
|
2620
|
-
const existingDirs = OPTIONAL_DIRS2.filter((d) =>
|
|
3294
|
+
const existingDirs = OPTIONAL_DIRS2.filter((d) => existsSync13(resolve4(dir, d)));
|
|
2621
3295
|
return validateSkillModel(parsed, { existingDirs: [...existingDirs] });
|
|
2622
3296
|
}
|
|
2623
3297
|
};
|
|
2624
3298
|
});
|
|
2625
3299
|
|
|
2626
3300
|
// src/validators/claude/plugin.ts
|
|
2627
|
-
import { existsSync as
|
|
2628
|
-
import { resolve as
|
|
2629
|
-
|
|
3301
|
+
import { existsSync as existsSync14, readdirSync as readdirSync6 } from "fs";
|
|
3302
|
+
import { resolve as resolve5, join as join12 } from "path";
|
|
3303
|
+
function levenshtein(a, b) {
|
|
3304
|
+
if (a === b)
|
|
3305
|
+
return 0;
|
|
3306
|
+
const m = a.length, n = b.length;
|
|
3307
|
+
if (m === 0)
|
|
3308
|
+
return n;
|
|
3309
|
+
if (n === 0)
|
|
3310
|
+
return m;
|
|
3311
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
3312
|
+
for (let i = 0;i <= m; i++)
|
|
3313
|
+
dp[i][0] = i;
|
|
3314
|
+
for (let j = 0;j <= n; j++)
|
|
3315
|
+
dp[0][j] = j;
|
|
3316
|
+
for (let i = 1;i <= m; i++) {
|
|
3317
|
+
for (let j = 1;j <= n; j++) {
|
|
3318
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
3319
|
+
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
return dp[m][n];
|
|
3323
|
+
}
|
|
3324
|
+
function suggestField(unknown) {
|
|
3325
|
+
const lower = unknown.toLowerCase();
|
|
3326
|
+
for (const k of KNOWN_FIELDS2) {
|
|
3327
|
+
if (k.toLowerCase() === lower)
|
|
3328
|
+
return k;
|
|
3329
|
+
if (levenshtein(k.toLowerCase(), lower) <= 1)
|
|
3330
|
+
return k;
|
|
3331
|
+
if (k.toLowerCase().startsWith(lower.slice(0, 3)) && lower.length > 3)
|
|
3332
|
+
return k;
|
|
3333
|
+
}
|
|
3334
|
+
if (lower === "licence")
|
|
3335
|
+
return "license";
|
|
3336
|
+
if (lower === "dependancies" || lower === "deps")
|
|
3337
|
+
return "dependencies";
|
|
3338
|
+
if (lower === "mcp" || lower === "mcpservers")
|
|
3339
|
+
return "mcpServers";
|
|
3340
|
+
if (lower === "lsp")
|
|
3341
|
+
return "lspServers";
|
|
3342
|
+
if (lower === "outputstyles" || lower === "styles")
|
|
3343
|
+
return "outputStyles";
|
|
3344
|
+
if (lower === "userconfig")
|
|
3345
|
+
return "userConfig";
|
|
3346
|
+
return null;
|
|
3347
|
+
}
|
|
3348
|
+
function isRelativePathLike(v) {
|
|
3349
|
+
if (typeof v !== "string")
|
|
3350
|
+
return false;
|
|
3351
|
+
return RELATIVE_PATH_REGEX.test(v) && !v.includes("..");
|
|
3352
|
+
}
|
|
3353
|
+
var NAME_REGEX2, RELATIVE_PATH_REGEX, KNOWN_FIELDS2, REPLACES_DEFAULT, claudePluginValidator;
|
|
2630
3354
|
var init_plugin = __esm(() => {
|
|
2631
3355
|
NAME_REGEX2 = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
2632
3356
|
RELATIVE_PATH_REGEX = /^\.\//;
|
|
3357
|
+
KNOWN_FIELDS2 = new Set([
|
|
3358
|
+
"$schema",
|
|
3359
|
+
"name",
|
|
3360
|
+
"displayName",
|
|
3361
|
+
"version",
|
|
3362
|
+
"description",
|
|
3363
|
+
"author",
|
|
3364
|
+
"homepage",
|
|
3365
|
+
"repository",
|
|
3366
|
+
"license",
|
|
3367
|
+
"keywords",
|
|
3368
|
+
"defaultEnabled",
|
|
3369
|
+
"skills",
|
|
3370
|
+
"commands",
|
|
3371
|
+
"agents",
|
|
3372
|
+
"hooks",
|
|
3373
|
+
"mcpServers",
|
|
3374
|
+
"outputStyles",
|
|
3375
|
+
"lspServers",
|
|
3376
|
+
"experimental",
|
|
3377
|
+
"userConfig",
|
|
3378
|
+
"channels",
|
|
3379
|
+
"dependencies"
|
|
3380
|
+
]);
|
|
3381
|
+
REPLACES_DEFAULT = new Set(["commands", "agents", "outputStyles", "lspServers"]);
|
|
2633
3382
|
claudePluginValidator = {
|
|
2634
3383
|
id: "claude:plugin",
|
|
2635
3384
|
provider: "claude",
|
|
2636
3385
|
name: "Claude Plugin",
|
|
2637
|
-
description: "Validates .claude-plugin/plugin.json manifest, component
|
|
3386
|
+
description: "Validates .claude-plugin/plugin.json manifest (complete schema per Plugins reference), component path rules (replace vs augment), .claude-plugin/ purity, default dirs, single-root-skill layout, unrecognized fields + suggestions, and structure",
|
|
2638
3387
|
detect(dir) {
|
|
2639
|
-
return
|
|
3388
|
+
return existsSync14(resolve5(dir, ".claude-plugin", "plugin.json"));
|
|
2640
3389
|
},
|
|
2641
3390
|
async validate(dir, _opts) {
|
|
2642
3391
|
const errors = [];
|
|
2643
3392
|
const warnings = [];
|
|
2644
3393
|
const passes = [];
|
|
2645
|
-
const manifestPath =
|
|
3394
|
+
const manifestPath = resolve5(dir, ".claude-plugin", "plugin.json");
|
|
3395
|
+
const dotClaudePluginDir = resolve5(dir, ".claude-plugin");
|
|
2646
3396
|
let manifest;
|
|
2647
3397
|
try {
|
|
2648
3398
|
const raw = await Bun.file(manifestPath).text();
|
|
@@ -2652,6 +3402,17 @@ var init_plugin = __esm(() => {
|
|
|
2652
3402
|
errors.push(".claude-plugin/plugin.json is missing or invalid JSON");
|
|
2653
3403
|
return { errors, warnings, passes };
|
|
2654
3404
|
}
|
|
3405
|
+
try {
|
|
3406
|
+
const entries = readdirSync6(dotClaudePluginDir);
|
|
3407
|
+
const unexpected = entries.filter((e) => e !== "plugin.json");
|
|
3408
|
+
if (unexpected.length > 0) {
|
|
3409
|
+
for (const e of unexpected) {
|
|
3410
|
+
warnings.push(`Unexpected item "${e}" inside .claude-plugin/ \u2014 only plugin.json belongs here. Move component directories and files (skills/, commands/, agents/, hooks/, .mcp.json etc.) to the plugin root.`);
|
|
3411
|
+
}
|
|
3412
|
+
} else if (entries.length === 1) {
|
|
3413
|
+
passes.push(".claude-plugin/ contains only plugin.json (correct layout)");
|
|
3414
|
+
}
|
|
3415
|
+
} catch {}
|
|
2655
3416
|
if (!manifest.name) {
|
|
2656
3417
|
errors.push('Missing required field: "name"');
|
|
2657
3418
|
} else {
|
|
@@ -2665,10 +3426,12 @@ var init_plugin = __esm(() => {
|
|
|
2665
3426
|
if (manifest.version !== undefined) {
|
|
2666
3427
|
const v = String(manifest.version);
|
|
2667
3428
|
if (!/^\d+\.\d+\.\d+/.test(v)) {
|
|
2668
|
-
errors.push(`Invalid version format: "${v}" \u2014 must
|
|
3429
|
+
errors.push(`Invalid version format: "${v}" \u2014 must look like semver (MAJOR.MINOR.PATCH) when using explicit versioning`);
|
|
2669
3430
|
} else {
|
|
2670
|
-
passes.push(`version: "${v}"`);
|
|
3431
|
+
passes.push(`version: "${v}" (explicit \u2014 bump on every release to publish updates)`);
|
|
2671
3432
|
}
|
|
3433
|
+
} else {
|
|
3434
|
+
passes.push("version omitted (git commit SHA used as version key \u2014 every commit becomes an available update)");
|
|
2672
3435
|
}
|
|
2673
3436
|
if (manifest.description !== undefined) {
|
|
2674
3437
|
const desc = String(manifest.description);
|
|
@@ -2677,65 +3440,172 @@ var init_plugin = __esm(() => {
|
|
|
2677
3440
|
} else {
|
|
2678
3441
|
passes.push("description field present");
|
|
2679
3442
|
}
|
|
3443
|
+
} else {
|
|
3444
|
+
warnings.push('Missing "description" (recommended for UI, marketplace listings, and auto-discovery)');
|
|
3445
|
+
}
|
|
3446
|
+
if (manifest.displayName !== undefined) {
|
|
3447
|
+
passes.push(`displayName: "${manifest.displayName}" (human UI label; falls back to name)`);
|
|
3448
|
+
}
|
|
3449
|
+
if (manifest.author !== undefined) {
|
|
3450
|
+
const a = manifest.author;
|
|
3451
|
+
if (a && typeof a === "object" && a.name) {
|
|
3452
|
+
passes.push("author present");
|
|
3453
|
+
} else {
|
|
3454
|
+
warnings.push('author should be an object like {"name": "...", "email?": "..."}');
|
|
3455
|
+
}
|
|
2680
3456
|
}
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
3457
|
+
if (manifest.license !== undefined) {
|
|
3458
|
+
passes.push(`license: "${manifest.license}"`);
|
|
3459
|
+
}
|
|
3460
|
+
if (manifest.keywords !== undefined) {
|
|
3461
|
+
if (Array.isArray(manifest.keywords)) {
|
|
3462
|
+
passes.push(`keywords: [${manifest.keywords.join(", ")}]`);
|
|
3463
|
+
} else {
|
|
3464
|
+
errors.push("keywords must be an array of strings");
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
if (manifest.defaultEnabled !== undefined) {
|
|
3468
|
+
passes.push(`defaultEnabled: ${manifest.defaultEnabled}`);
|
|
3469
|
+
}
|
|
3470
|
+
if (manifest.homepage)
|
|
3471
|
+
passes.push("homepage present");
|
|
3472
|
+
if (manifest.repository)
|
|
3473
|
+
passes.push("repository present");
|
|
3474
|
+
const unknown = Object.keys(manifest).filter((k) => !KNOWN_FIELDS2.has(k));
|
|
3475
|
+
for (const k of unknown) {
|
|
3476
|
+
const sug = suggestField(k);
|
|
3477
|
+
const hint = sug ? ` (did you mean "${sug}"?)` : "";
|
|
3478
|
+
warnings.push(`Unrecognized top-level field "${k}"${hint} \u2014 will be ignored at runtime (allowed for cross-tool manifest compatibility).`);
|
|
3479
|
+
}
|
|
3480
|
+
const handleField = (field, val) => {
|
|
3481
|
+
if (val === undefined || val === null)
|
|
3482
|
+
return;
|
|
3483
|
+
if (isRelativePathLike(val) || Array.isArray(val) && val.every(isRelativePathLike)) {
|
|
3484
|
+
const arr = Array.isArray(val) ? val : [val];
|
|
3485
|
+
for (const p of arr) {
|
|
3486
|
+
const s = String(p);
|
|
3487
|
+
if (!RELATIVE_PATH_REGEX.test(s)) {
|
|
3488
|
+
errors.push(`${field}: path "${s}" must start with "./"`);
|
|
3489
|
+
} else if (s.includes("..")) {
|
|
3490
|
+
errors.push(`${field}: path "${s}" must not use ".." (paths are confined to the plugin tree after cache copy)`);
|
|
3491
|
+
} else if (existsSync14(resolve5(dir, s))) {
|
|
3492
|
+
passes.push(`${field}: path "${s}" exists`);
|
|
3493
|
+
} else {
|
|
3494
|
+
warnings.push(`${field}: path "${s}" does not exist on disk`);
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
if (field === "skills") {
|
|
3498
|
+
passes.push(`${field}: augments the default skills/ (both are scanned)`);
|
|
3499
|
+
} else if (REPLACES_DEFAULT.has(field)) {
|
|
3500
|
+
passes.push(`${field}: custom path replaces default ${field}/ scan`);
|
|
2691
3501
|
} else {
|
|
2692
|
-
|
|
3502
|
+
passes.push(`${field}: custom path or config (merge rules apply)`);
|
|
2693
3503
|
}
|
|
3504
|
+
} else if (typeof val === "object") {
|
|
3505
|
+
passes.push(`${field}: inline ${field} config present`);
|
|
2694
3506
|
}
|
|
2695
3507
|
};
|
|
2696
|
-
|
|
2697
|
-
if (manifest[
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
3508
|
+
["skills", "commands", "agents", "hooks", "mcpServers", "outputStyles", "lspServers"].forEach((f) => {
|
|
3509
|
+
if (manifest[f] !== undefined)
|
|
3510
|
+
handleField(f, manifest[f]);
|
|
3511
|
+
});
|
|
3512
|
+
if (manifest.experimental && typeof manifest.experimental === "object") {
|
|
3513
|
+
const exp = manifest.experimental;
|
|
3514
|
+
if (exp.themes !== undefined)
|
|
3515
|
+
handleField("experimental.themes", exp.themes);
|
|
3516
|
+
if (exp.monitors !== undefined)
|
|
3517
|
+
handleField("experimental.monitors", exp.monitors);
|
|
3518
|
+
passes.push("experimental section present (themes and monitors are experimental components)");
|
|
3519
|
+
}
|
|
3520
|
+
if (manifest.userConfig && typeof manifest.userConfig === "object") {
|
|
3521
|
+
const keys = Object.keys(manifest.userConfig);
|
|
3522
|
+
passes.push(`userConfig: ${keys.length} user-configurable value(s) declared`);
|
|
3523
|
+
for (const k of keys) {
|
|
3524
|
+
const opt = manifest.userConfig[k];
|
|
3525
|
+
if (!opt || !opt.type || !opt.title) {
|
|
3526
|
+
warnings.push(`userConfig.${k} is missing required "type" and/or "title"`);
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
if (Array.isArray(manifest.channels)) {
|
|
3531
|
+
passes.push(`channels: ${manifest.channels.length} channel(s) (each binds to an mcpServer)`);
|
|
3532
|
+
manifest.channels.forEach((ch, i) => {
|
|
3533
|
+
if (!ch?.server)
|
|
3534
|
+
warnings.push(`channels[${i}]: "server" is required and must match an mcpServers key`);
|
|
3535
|
+
});
|
|
3536
|
+
}
|
|
3537
|
+
if (Array.isArray(manifest.dependencies)) {
|
|
3538
|
+
passes.push(`dependencies: declares ${manifest.dependencies.length} plugin dependency/ies`);
|
|
3539
|
+
}
|
|
3540
|
+
const skillsDir = resolve5(dir, "skills");
|
|
3541
|
+
if (existsSync14(skillsDir)) {
|
|
3542
|
+
const entries = readdirSync6(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
3543
|
+
for (const e of entries) {
|
|
3544
|
+
const md = join12(skillsDir, e.name, "SKILL.md");
|
|
3545
|
+
if (existsSync14(md)) {
|
|
3546
|
+
passes.push(`skills/${e.name}/SKILL.md exists`);
|
|
2708
3547
|
} else {
|
|
2709
|
-
errors.push(`skills/${
|
|
3548
|
+
errors.push(`skills/${e.name}/ is missing SKILL.md`);
|
|
2710
3549
|
}
|
|
2711
3550
|
}
|
|
3551
|
+
if (manifest.skills !== undefined) {
|
|
3552
|
+
warnings.push('Default skills/ dir co-exists with manifest "skills" \u2014 manifest path is authoritative; default folder ignored for loading');
|
|
3553
|
+
}
|
|
2712
3554
|
}
|
|
2713
|
-
const commandsDir =
|
|
2714
|
-
if (
|
|
2715
|
-
const
|
|
2716
|
-
if (
|
|
2717
|
-
passes.push(`commands/ has ${
|
|
2718
|
-
}
|
|
2719
|
-
|
|
3555
|
+
const commandsDir = resolve5(dir, "commands");
|
|
3556
|
+
if (existsSync14(commandsDir)) {
|
|
3557
|
+
const mds = readdirSync6(commandsDir).filter((f) => f.endsWith(".md"));
|
|
3558
|
+
if (mds.length) {
|
|
3559
|
+
passes.push(`commands/ has ${mds.length} .md file(s)`);
|
|
3560
|
+
}
|
|
3561
|
+
if (manifest.commands !== undefined) {
|
|
3562
|
+
warnings.push('commands/ co-exists with manifest "commands" \u2014 manifest replaces default (dir ignored)');
|
|
2720
3563
|
}
|
|
2721
3564
|
}
|
|
2722
|
-
const agentsDir =
|
|
2723
|
-
if (
|
|
2724
|
-
const
|
|
2725
|
-
if (
|
|
2726
|
-
passes.push(`agents/ has ${
|
|
2727
|
-
}
|
|
2728
|
-
|
|
3565
|
+
const agentsDir = resolve5(dir, "agents");
|
|
3566
|
+
if (existsSync14(agentsDir)) {
|
|
3567
|
+
const mds = readdirSync6(agentsDir).filter((f) => f.endsWith(".md"));
|
|
3568
|
+
if (mds.length) {
|
|
3569
|
+
passes.push(`agents/ has ${mds.length} .md file(s)`);
|
|
3570
|
+
}
|
|
3571
|
+
if (manifest.agents !== undefined) {
|
|
3572
|
+
warnings.push('agents/ co-exists with manifest "agents" \u2014 manifest replaces default (dir ignored)');
|
|
2729
3573
|
}
|
|
2730
3574
|
}
|
|
3575
|
+
if (existsSync14(resolve5(dir, "output-styles"))) {
|
|
3576
|
+
passes.push("output-styles/ directory present");
|
|
3577
|
+
if (manifest.outputStyles)
|
|
3578
|
+
warnings.push("output-styles/ co-exists with manifest outputStyles \u2014 manifest wins");
|
|
3579
|
+
}
|
|
3580
|
+
if (existsSync14(resolve5(dir, "themes")))
|
|
3581
|
+
passes.push("themes/ present (experimental)");
|
|
3582
|
+
if (existsSync14(resolve5(dir, "monitors")) || manifest.experimental?.monitors) {
|
|
3583
|
+
passes.push("monitors config present (experimental)");
|
|
3584
|
+
}
|
|
3585
|
+
if (existsSync14(resolve5(dir, "bin")))
|
|
3586
|
+
passes.push("bin/ present (adds executables to Bash tool $PATH)");
|
|
3587
|
+
if (existsSync14(resolve5(dir, "settings.json")))
|
|
3588
|
+
passes.push("settings.json present (plugin defaults for agent/statusline)");
|
|
3589
|
+
if (existsSync14(resolve5(dir, "README.md")))
|
|
3590
|
+
passes.push("README.md present");
|
|
3591
|
+
if (existsSync14(resolve5(dir, ".mcp.json")))
|
|
3592
|
+
passes.push(".mcp.json present (validated by claude:mcp)");
|
|
3593
|
+
if (existsSync14(resolve5(dir, ".lsp.json")))
|
|
3594
|
+
passes.push(".lsp.json present (validated by claude:lsp when registered)");
|
|
3595
|
+
if (existsSync14(resolve5(dir, "hooks/hooks.json")) || existsSync14(resolve5(dir, "hooks.json"))) {
|
|
3596
|
+
passes.push("hooks config present (validated by claude:hooks)");
|
|
3597
|
+
}
|
|
3598
|
+
if (existsSync14(resolve5(dir, "SKILL.md")) && !existsSync14(skillsDir) && manifest.skills === undefined) {
|
|
3599
|
+
passes.push('Root SKILL.md detected \u2014 plugin will be treated as a single-skill plugin (prefer frontmatter "name" for stable /command)');
|
|
3600
|
+
}
|
|
2731
3601
|
return { errors, warnings, passes };
|
|
2732
3602
|
}
|
|
2733
3603
|
};
|
|
2734
3604
|
});
|
|
2735
3605
|
|
|
2736
3606
|
// src/validators/claude/marketplace.ts
|
|
2737
|
-
import { existsSync as
|
|
2738
|
-
import { resolve as
|
|
3607
|
+
import { existsSync as existsSync15, readdirSync as readdirSync7 } from "fs";
|
|
3608
|
+
import { resolve as resolve6, join as join13 } from "path";
|
|
2739
3609
|
var claudeMarketplaceValidator;
|
|
2740
3610
|
var init_marketplace = __esm(() => {
|
|
2741
3611
|
claudeMarketplaceValidator = {
|
|
@@ -2744,16 +3614,16 @@ var init_marketplace = __esm(() => {
|
|
|
2744
3614
|
name: "Claude Plugin Marketplace",
|
|
2745
3615
|
description: "Validates marketplace structure: plugins/ directory with valid plugin subdirectories",
|
|
2746
3616
|
detect(dir) {
|
|
2747
|
-
const pluginsDir =
|
|
2748
|
-
if (!
|
|
3617
|
+
const pluginsDir = resolve6(dir, "plugins");
|
|
3618
|
+
if (!existsSync15(pluginsDir))
|
|
2749
3619
|
return false;
|
|
2750
3620
|
try {
|
|
2751
|
-
const entries =
|
|
3621
|
+
const entries = readdirSync7(pluginsDir, { withFileTypes: true });
|
|
2752
3622
|
for (const entry of entries) {
|
|
2753
3623
|
if (!entry.isDirectory())
|
|
2754
3624
|
continue;
|
|
2755
|
-
const hasSkills =
|
|
2756
|
-
const hasManifest =
|
|
3625
|
+
const hasSkills = existsSync15(join13(pluginsDir, entry.name, "skills"));
|
|
3626
|
+
const hasManifest = existsSync15(join13(pluginsDir, entry.name, ".claude-plugin", "plugin.json"));
|
|
2757
3627
|
if (hasSkills || hasManifest)
|
|
2758
3628
|
return true;
|
|
2759
3629
|
}
|
|
@@ -2764,33 +3634,33 @@ var init_marketplace = __esm(() => {
|
|
|
2764
3634
|
const errors = [];
|
|
2765
3635
|
const warnings = [];
|
|
2766
3636
|
const passes = [];
|
|
2767
|
-
const pluginsDir =
|
|
2768
|
-
if (!
|
|
3637
|
+
const pluginsDir = resolve6(dir, "plugins");
|
|
3638
|
+
if (!existsSync15(pluginsDir)) {
|
|
2769
3639
|
errors.push("Missing plugins/ directory");
|
|
2770
3640
|
return { errors, warnings, passes };
|
|
2771
3641
|
}
|
|
2772
3642
|
passes.push("plugins/ directory exists");
|
|
2773
|
-
const pluginEntries =
|
|
3643
|
+
const pluginEntries = readdirSync7(pluginsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
2774
3644
|
if (pluginEntries.length === 0) {
|
|
2775
3645
|
errors.push("plugins/ directory is empty \u2014 expected at least one plugin");
|
|
2776
3646
|
return { errors, warnings, passes };
|
|
2777
3647
|
}
|
|
2778
3648
|
passes.push(`${pluginEntries.length} plugin(s) found`);
|
|
2779
|
-
if (
|
|
3649
|
+
if (existsSync15(resolve6(dir, "README.md"))) {
|
|
2780
3650
|
passes.push("README.md exists at marketplace root");
|
|
2781
3651
|
} else {
|
|
2782
3652
|
warnings.push("No README.md at marketplace root \u2014 recommended for discoverability");
|
|
2783
3653
|
}
|
|
2784
|
-
if (
|
|
3654
|
+
if (existsSync15(resolve6(dir, "LICENSE"))) {
|
|
2785
3655
|
passes.push("LICENSE exists at marketplace root");
|
|
2786
3656
|
} else {
|
|
2787
3657
|
warnings.push("No LICENSE at marketplace root \u2014 recommended");
|
|
2788
3658
|
}
|
|
2789
3659
|
for (const plugin of pluginEntries) {
|
|
2790
|
-
const pluginPath =
|
|
2791
|
-
const hasSkills =
|
|
2792
|
-
const hasManifest =
|
|
2793
|
-
const hasReadme =
|
|
3660
|
+
const pluginPath = join13(pluginsDir, plugin.name);
|
|
3661
|
+
const hasSkills = existsSync15(join13(pluginPath, "skills"));
|
|
3662
|
+
const hasManifest = existsSync15(join13(pluginPath, ".claude-plugin", "plugin.json"));
|
|
3663
|
+
const hasReadme = existsSync15(join13(pluginPath, "README.md"));
|
|
2794
3664
|
if (hasManifest || hasSkills) {
|
|
2795
3665
|
passes.push(`Plugin "${plugin.name}" has ${hasManifest ? "manifest" : "skills/"}`);
|
|
2796
3666
|
} else {
|
|
@@ -2806,35 +3676,55 @@ var init_marketplace = __esm(() => {
|
|
|
2806
3676
|
});
|
|
2807
3677
|
|
|
2808
3678
|
// src/validators/claude/hooks.ts
|
|
2809
|
-
import { existsSync as
|
|
2810
|
-
import { resolve as
|
|
3679
|
+
import { existsSync as existsSync16 } from "fs";
|
|
3680
|
+
import { resolve as resolve7 } from "path";
|
|
2811
3681
|
var KNOWN_EVENTS, claudeHooksValidator;
|
|
2812
3682
|
var init_hooks = __esm(() => {
|
|
2813
3683
|
KNOWN_EVENTS = [
|
|
3684
|
+
"SessionStart",
|
|
3685
|
+
"Setup",
|
|
3686
|
+
"UserPromptSubmit",
|
|
3687
|
+
"UserPromptExpansion",
|
|
2814
3688
|
"PreToolUse",
|
|
3689
|
+
"PermissionRequest",
|
|
3690
|
+
"PermissionDenied",
|
|
2815
3691
|
"PostToolUse",
|
|
2816
|
-
"
|
|
3692
|
+
"PostToolUseFailure",
|
|
3693
|
+
"PostToolBatch",
|
|
3694
|
+
"Notification",
|
|
3695
|
+
"MessageDisplay",
|
|
3696
|
+
"SubagentStart",
|
|
2817
3697
|
"SubagentStop",
|
|
2818
|
-
"
|
|
2819
|
-
"
|
|
2820
|
-
"
|
|
3698
|
+
"TaskCreated",
|
|
3699
|
+
"TaskCompleted",
|
|
3700
|
+
"Stop",
|
|
3701
|
+
"StopFailure",
|
|
3702
|
+
"TeammateIdle",
|
|
3703
|
+
"InstructionsLoaded",
|
|
3704
|
+
"ConfigChange",
|
|
3705
|
+
"CwdChanged",
|
|
3706
|
+
"FileChanged",
|
|
3707
|
+
"WorktreeCreate",
|
|
3708
|
+
"WorktreeRemove",
|
|
2821
3709
|
"PreCompact",
|
|
2822
|
-
"
|
|
2823
|
-
"
|
|
3710
|
+
"PostCompact",
|
|
3711
|
+
"Elicitation",
|
|
3712
|
+
"ElicitationResult",
|
|
3713
|
+
"SessionEnd"
|
|
2824
3714
|
];
|
|
2825
3715
|
claudeHooksValidator = {
|
|
2826
3716
|
id: "claude:hooks",
|
|
2827
3717
|
provider: "claude",
|
|
2828
3718
|
name: "Claude Hooks",
|
|
2829
|
-
description: "Validates hooks/hooks.json:
|
|
3719
|
+
description: "Validates hooks/hooks.json (or root hooks.json): all lifecycle events per Plugins reference, hook group structure (matcher + hooks[]), supported hook types (command, http, mcp_tool, prompt, agent)",
|
|
2830
3720
|
detect(dir) {
|
|
2831
|
-
return
|
|
3721
|
+
return existsSync16(resolve7(dir, "hooks", "hooks.json")) || existsSync16(resolve7(dir, "hooks.json"));
|
|
2832
3722
|
},
|
|
2833
3723
|
async validate(dir, _opts) {
|
|
2834
3724
|
const errors = [];
|
|
2835
3725
|
const warnings = [];
|
|
2836
3726
|
const passes = [];
|
|
2837
|
-
const hooksPath =
|
|
3727
|
+
const hooksPath = existsSync16(resolve7(dir, "hooks", "hooks.json")) ? resolve7(dir, "hooks", "hooks.json") : resolve7(dir, "hooks.json");
|
|
2838
3728
|
let config;
|
|
2839
3729
|
try {
|
|
2840
3730
|
const raw = await Bun.file(hooksPath).text();
|
|
@@ -2849,32 +3739,74 @@ var init_hooks = __esm(() => {
|
|
|
2849
3739
|
if (KNOWN_EVENTS.includes(name)) {
|
|
2850
3740
|
passes.push(`Event "${name}" is a known lifecycle event`);
|
|
2851
3741
|
} else {
|
|
2852
|
-
warnings.push(`Unknown event name: "${name}" \u2014
|
|
3742
|
+
warnings.push(`Unknown event name: "${name}" \u2014 see full list in Plugins reference (SessionStart, PreToolUse, PostToolUse, Stop, ...)`);
|
|
2853
3743
|
}
|
|
2854
3744
|
}
|
|
3745
|
+
for (const [event, groups] of Object.entries(config)) {
|
|
3746
|
+
if (!Array.isArray(groups)) {
|
|
3747
|
+
errors.push(`Event "${event}": value must be an array of hook groups`);
|
|
3748
|
+
continue;
|
|
3749
|
+
}
|
|
3750
|
+
groups.forEach((group, gi) => {
|
|
3751
|
+
if (!group || typeof group !== "object") {
|
|
3752
|
+
errors.push(`${event}[${gi}]: hook group must be an object`);
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
3755
|
+
if (group.matcher !== undefined && typeof group.matcher !== "string") {
|
|
3756
|
+
warnings.push(`${event}[${gi}]: "matcher" should be a string (e.g. "Write|Edit" or glob)`);
|
|
3757
|
+
}
|
|
3758
|
+
const hooksArr = group.hooks;
|
|
3759
|
+
if (!Array.isArray(hooksArr)) {
|
|
3760
|
+
errors.push(`${event}[${gi}]: missing or invalid "hooks" array`);
|
|
3761
|
+
return;
|
|
3762
|
+
}
|
|
3763
|
+
hooksArr.forEach((h, hi) => {
|
|
3764
|
+
if (!h || typeof h !== "object" || !h.type) {
|
|
3765
|
+
errors.push(`${event}[${gi}].hooks[${hi}]: must have "type"`);
|
|
3766
|
+
return;
|
|
3767
|
+
}
|
|
3768
|
+
const t = String(h.type);
|
|
3769
|
+
if (!["command", "http", "mcp_tool", "prompt", "agent"].includes(t)) {
|
|
3770
|
+
warnings.push(`${event}[${gi}].hooks[${hi}]: unknown type "${t}" (valid: command, http, mcp_tool, prompt, agent)`);
|
|
3771
|
+
}
|
|
3772
|
+
if (t === "command" && !h.command) {
|
|
3773
|
+
errors.push(`${event}[${gi}].hooks[${hi}]: type=command requires "command"`);
|
|
3774
|
+
}
|
|
3775
|
+
if (t === "http" && !h.url) {
|
|
3776
|
+
errors.push(`${event}[${gi}].hooks[${hi}]: type=http requires "url"`);
|
|
3777
|
+
}
|
|
3778
|
+
if (h.command && typeof h.command === "string" && /\$\{CLAUDE_/.test(h.command)) {
|
|
3779
|
+
passes.push(`${event}[${gi}].hooks[${hi}]: uses plugin env substitution`);
|
|
3780
|
+
}
|
|
3781
|
+
});
|
|
3782
|
+
if (hooksArr.length > 0) {
|
|
3783
|
+
passes.push(`Event "${event}" has ${hooksArr.length} hook action(s)`);
|
|
3784
|
+
}
|
|
3785
|
+
});
|
|
3786
|
+
}
|
|
2855
3787
|
return { errors, warnings, passes };
|
|
2856
3788
|
}
|
|
2857
3789
|
};
|
|
2858
3790
|
});
|
|
2859
3791
|
|
|
2860
3792
|
// src/validators/claude/mcp.ts
|
|
2861
|
-
import { existsSync as
|
|
2862
|
-
import { resolve as
|
|
3793
|
+
import { existsSync as existsSync17 } from "fs";
|
|
3794
|
+
import { resolve as resolve8 } from "path";
|
|
2863
3795
|
var claudeMcpValidator;
|
|
2864
3796
|
var init_mcp = __esm(() => {
|
|
2865
3797
|
claudeMcpValidator = {
|
|
2866
3798
|
id: "claude:mcp",
|
|
2867
3799
|
provider: "claude",
|
|
2868
3800
|
name: "Claude MCP Config",
|
|
2869
|
-
description: "Validates .mcp.json: server
|
|
3801
|
+
description: "Validates .mcp.json (or inline via plugin.json mcpServers): server entries (stdio: command+args, or url), env, cwd, ${CLAUDE_PLUGIN_ROOT} etc. substitutions per Plugins reference",
|
|
2870
3802
|
detect(dir) {
|
|
2871
|
-
return
|
|
3803
|
+
return existsSync17(resolve8(dir, ".mcp.json"));
|
|
2872
3804
|
},
|
|
2873
3805
|
async validate(dir, _opts) {
|
|
2874
3806
|
const errors = [];
|
|
2875
3807
|
const warnings = [];
|
|
2876
3808
|
const passes = [];
|
|
2877
|
-
const mcpPath =
|
|
3809
|
+
const mcpPath = resolve8(dir, ".mcp.json");
|
|
2878
3810
|
let config;
|
|
2879
3811
|
try {
|
|
2880
3812
|
const raw = await Bun.file(mcpPath).text();
|
|
@@ -2894,14 +3826,42 @@ var init_mcp = __esm(() => {
|
|
|
2894
3826
|
return { errors, warnings, passes };
|
|
2895
3827
|
}
|
|
2896
3828
|
passes.push(`${serverNames.length} server(s) defined`);
|
|
3829
|
+
for (const [name, entry] of Object.entries(config)) {
|
|
3830
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
3831
|
+
errors.push(`mcp server "${name}": definition must be an object`);
|
|
3832
|
+
continue;
|
|
3833
|
+
}
|
|
3834
|
+
const e = entry;
|
|
3835
|
+
const hasCommand = typeof e.command === "string";
|
|
3836
|
+
const hasUrl = typeof e.url === "string";
|
|
3837
|
+
if (!hasCommand && !hasUrl) {
|
|
3838
|
+
errors.push(`mcp server "${name}": must have either "command" (for stdio) or "url" (for SSE/HTTP)`);
|
|
3839
|
+
}
|
|
3840
|
+
if (hasCommand && !Array.isArray(e.args)) {
|
|
3841
|
+
warnings.push(`mcp server "${name}": "command" present but no "args" array (ok for some servers)`);
|
|
3842
|
+
}
|
|
3843
|
+
if (hasUrl && hasCommand) {
|
|
3844
|
+
warnings.push(`mcp server "${name}": both "command" and "url" present \u2014 usually one or the other`);
|
|
3845
|
+
}
|
|
3846
|
+
if (e.env && typeof e.env === "object") {
|
|
3847
|
+
passes.push(`mcp server "${name}": has env`);
|
|
3848
|
+
}
|
|
3849
|
+
if (typeof e.cwd === "string") {
|
|
3850
|
+
passes.push(`mcp server "${name}": has cwd`);
|
|
3851
|
+
}
|
|
3852
|
+
const hasSubs = JSON.stringify(e).match(/\$\{CLAUDE_PLUGIN_(ROOT|DATA)|CLAUDE_PROJECT_DIR|user_config\.|ENV_VAR\}/);
|
|
3853
|
+
if (hasSubs) {
|
|
3854
|
+
passes.push(`mcp server "${name}": uses \${CLAUDE_PLUGIN_*} / user_config / env substitution`);
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
2897
3857
|
return { errors, warnings, passes };
|
|
2898
3858
|
}
|
|
2899
3859
|
};
|
|
2900
3860
|
});
|
|
2901
3861
|
|
|
2902
3862
|
// src/validators/claude/subagent.ts
|
|
2903
|
-
import { existsSync as
|
|
2904
|
-
import { resolve as
|
|
3863
|
+
import { existsSync as existsSync18, readdirSync as readdirSync8 } from "fs";
|
|
3864
|
+
import { resolve as resolve9, join as join14 } from "path";
|
|
2905
3865
|
var claudeSubagentValidator;
|
|
2906
3866
|
var init_subagent = __esm(() => {
|
|
2907
3867
|
init_frontmatter();
|
|
@@ -2909,13 +3869,13 @@ var init_subagent = __esm(() => {
|
|
|
2909
3869
|
id: "claude:subagent",
|
|
2910
3870
|
provider: "claude",
|
|
2911
3871
|
name: "Claude Subagents",
|
|
2912
|
-
description: "Validates agents
|
|
3872
|
+
description: "Validates agents/*.md (plugin subagents): frontmatter per spec (name, description, model, effort, maxTurns, tools, disallowedTools, skills, memory, background, isolation=worktree), body; warns on disallowed fields (hooks, mcpServers, permissionMode) for security",
|
|
2913
3873
|
detect(dir) {
|
|
2914
|
-
const agentsDir =
|
|
2915
|
-
if (!
|
|
3874
|
+
const agentsDir = resolve9(dir, "agents");
|
|
3875
|
+
if (!existsSync18(agentsDir))
|
|
2916
3876
|
return false;
|
|
2917
3877
|
try {
|
|
2918
|
-
return
|
|
3878
|
+
return readdirSync8(agentsDir).some((f) => f.endsWith(".md"));
|
|
2919
3879
|
} catch {
|
|
2920
3880
|
return false;
|
|
2921
3881
|
}
|
|
@@ -2924,27 +3884,63 @@ var init_subagent = __esm(() => {
|
|
|
2924
3884
|
const errors = [];
|
|
2925
3885
|
const warnings = [];
|
|
2926
3886
|
const passes = [];
|
|
2927
|
-
const agentsDir =
|
|
2928
|
-
const mdFiles =
|
|
3887
|
+
const agentsDir = resolve9(dir, "agents");
|
|
3888
|
+
const mdFiles = readdirSync8(agentsDir).filter((f) => f.endsWith(".md"));
|
|
2929
3889
|
if (mdFiles.length === 0) {
|
|
2930
3890
|
errors.push("agents/ directory has no .md files");
|
|
2931
3891
|
return { errors, warnings, passes };
|
|
2932
3892
|
}
|
|
2933
3893
|
passes.push(`${mdFiles.length} agent definition(s) found`);
|
|
3894
|
+
const SUPPORTED = new Set([
|
|
3895
|
+
"name",
|
|
3896
|
+
"description",
|
|
3897
|
+
"model",
|
|
3898
|
+
"effort",
|
|
3899
|
+
"maxTurns",
|
|
3900
|
+
"tools",
|
|
3901
|
+
"disallowedTools",
|
|
3902
|
+
"skills",
|
|
3903
|
+
"memory",
|
|
3904
|
+
"background",
|
|
3905
|
+
"isolation"
|
|
3906
|
+
]);
|
|
3907
|
+
const DISALLOWED = new Set(["hooks", "mcpServers", "permissionMode"]);
|
|
2934
3908
|
for (const file of mdFiles) {
|
|
2935
|
-
const filePath =
|
|
3909
|
+
const filePath = join14(agentsDir, file);
|
|
2936
3910
|
const raw = await Bun.file(filePath).text();
|
|
2937
3911
|
try {
|
|
2938
3912
|
const parsed = parseFrontmatter(raw);
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
warnings.push(`${file}: missing "description" in frontmatter`);
|
|
3913
|
+
const fm = parsed.data;
|
|
3914
|
+
if (Object.keys(fm).length === 0) {
|
|
3915
|
+
warnings.push(`${file}: no YAML frontmatter (description recommended so Claude knows when to invoke)`);
|
|
2943
3916
|
} else {
|
|
2944
|
-
|
|
3917
|
+
if (fm.description) {
|
|
3918
|
+
passes.push(`${file}: has frontmatter with description`);
|
|
3919
|
+
} else {
|
|
3920
|
+
warnings.push(`${file}: missing "description" in frontmatter`);
|
|
3921
|
+
}
|
|
3922
|
+
const usedSupported = [];
|
|
3923
|
+
Object.keys(fm).forEach((k) => {
|
|
3924
|
+
if (SUPPORTED.has(k))
|
|
3925
|
+
usedSupported.push(k);
|
|
3926
|
+
if (DISALLOWED.has(k)) {
|
|
3927
|
+
errors.push(`${file}: frontmatter "${k}" is not supported for plugin-shipped agents (security restriction)`);
|
|
3928
|
+
}
|
|
3929
|
+
});
|
|
3930
|
+
if (usedSupported.length) {
|
|
3931
|
+
passes.push(`${file}: frontmatter fields: ${usedSupported.join(", ")}`);
|
|
3932
|
+
}
|
|
3933
|
+
if (fm.isolation !== undefined && fm.isolation !== "worktree") {
|
|
3934
|
+
errors.push(`${file}: "isolation" must be "worktree" if present (only supported value for plugin agents)`);
|
|
3935
|
+
}
|
|
3936
|
+
if (fm.name && typeof fm.name === "string") {
|
|
3937
|
+
passes.push(`${file}: name: "${fm.name}"`);
|
|
3938
|
+
}
|
|
2945
3939
|
}
|
|
2946
3940
|
if (!parsed.content.trim()) {
|
|
2947
3941
|
errors.push(`${file}: body is empty`);
|
|
3942
|
+
} else {
|
|
3943
|
+
passes.push(`${file}: has agent system prompt body`);
|
|
2948
3944
|
}
|
|
2949
3945
|
} catch {
|
|
2950
3946
|
errors.push(`${file}: failed to parse`);
|
|
@@ -2956,8 +3952,8 @@ var init_subagent = __esm(() => {
|
|
|
2956
3952
|
});
|
|
2957
3953
|
|
|
2958
3954
|
// src/validators/claude/command.ts
|
|
2959
|
-
import { existsSync as
|
|
2960
|
-
import { resolve as
|
|
3955
|
+
import { existsSync as existsSync19, readdirSync as readdirSync9 } from "fs";
|
|
3956
|
+
import { resolve as resolve10, join as join15 } from "path";
|
|
2961
3957
|
var claudeCommandValidator;
|
|
2962
3958
|
var init_command = __esm(() => {
|
|
2963
3959
|
init_frontmatter();
|
|
@@ -2967,11 +3963,11 @@ var init_command = __esm(() => {
|
|
|
2967
3963
|
name: "Claude Commands",
|
|
2968
3964
|
description: "Validates commands/ (or legacy .claude/commands/) .md files: frontmatter (including rich skill fields), description, body",
|
|
2969
3965
|
detect(dir) {
|
|
2970
|
-
const commandsDir =
|
|
2971
|
-
if (!
|
|
3966
|
+
const commandsDir = resolve10(dir, "commands");
|
|
3967
|
+
if (!existsSync19(commandsDir))
|
|
2972
3968
|
return false;
|
|
2973
3969
|
try {
|
|
2974
|
-
return
|
|
3970
|
+
return readdirSync9(commandsDir).some((f) => f.endsWith(".md"));
|
|
2975
3971
|
} catch {
|
|
2976
3972
|
return false;
|
|
2977
3973
|
}
|
|
@@ -2980,15 +3976,15 @@ var init_command = __esm(() => {
|
|
|
2980
3976
|
const errors = [];
|
|
2981
3977
|
const warnings = [];
|
|
2982
3978
|
const passes = [];
|
|
2983
|
-
const commandsDir =
|
|
2984
|
-
const mdFiles =
|
|
3979
|
+
const commandsDir = resolve10(dir, "commands");
|
|
3980
|
+
const mdFiles = readdirSync9(commandsDir).filter((f) => f.endsWith(".md"));
|
|
2985
3981
|
if (mdFiles.length === 0) {
|
|
2986
3982
|
errors.push("commands/ directory has no .md files");
|
|
2987
3983
|
return { errors, warnings, passes };
|
|
2988
3984
|
}
|
|
2989
3985
|
passes.push(`${mdFiles.length} command definition(s) found`);
|
|
2990
3986
|
for (const file of mdFiles) {
|
|
2991
|
-
const filePath =
|
|
3987
|
+
const filePath = join15(commandsDir, file);
|
|
2992
3988
|
const raw = await Bun.file(filePath).text();
|
|
2993
3989
|
try {
|
|
2994
3990
|
const parsed = parseFrontmatter(raw);
|
|
@@ -3017,8 +4013,8 @@ var init_command = __esm(() => {
|
|
|
3017
4013
|
});
|
|
3018
4014
|
|
|
3019
4015
|
// src/validators/claude/memory.ts
|
|
3020
|
-
import { existsSync as
|
|
3021
|
-
import { resolve as
|
|
4016
|
+
import { existsSync as existsSync20 } from "fs";
|
|
4017
|
+
import { resolve as resolve11 } from "path";
|
|
3022
4018
|
var claudeMemoryValidator;
|
|
3023
4019
|
var init_memory = __esm(() => {
|
|
3024
4020
|
claudeMemoryValidator = {
|
|
@@ -3027,13 +4023,13 @@ var init_memory = __esm(() => {
|
|
|
3027
4023
|
name: "Claude CLAUDE.md",
|
|
3028
4024
|
description: "Validates CLAUDE.md: non-empty, length recommendations, @path imports",
|
|
3029
4025
|
detect(dir) {
|
|
3030
|
-
return
|
|
4026
|
+
return existsSync20(resolve11(dir, "CLAUDE.md"));
|
|
3031
4027
|
},
|
|
3032
4028
|
async validate(dir, _opts) {
|
|
3033
4029
|
const errors = [];
|
|
3034
4030
|
const warnings = [];
|
|
3035
4031
|
const passes = [];
|
|
3036
|
-
const filePath =
|
|
4032
|
+
const filePath = resolve11(dir, "CLAUDE.md");
|
|
3037
4033
|
const raw = await Bun.file(filePath).text();
|
|
3038
4034
|
if (!raw.trim()) {
|
|
3039
4035
|
errors.push("CLAUDE.md is empty");
|
|
@@ -3051,8 +4047,8 @@ var init_memory = __esm(() => {
|
|
|
3051
4047
|
let match;
|
|
3052
4048
|
while ((match = importRegex.exec(raw)) !== null) {
|
|
3053
4049
|
const importPath = match[1];
|
|
3054
|
-
const resolvedImport =
|
|
3055
|
-
if (
|
|
4050
|
+
const resolvedImport = resolve11(dir, importPath);
|
|
4051
|
+
if (existsSync20(resolvedImport)) {
|
|
3056
4052
|
passes.push(`@import "${importPath}" exists`);
|
|
3057
4053
|
} else {
|
|
3058
4054
|
warnings.push(`@import "${importPath}" \u2014 file not found at ${resolvedImport}`);
|
|
@@ -3063,6 +4059,165 @@ var init_memory = __esm(() => {
|
|
|
3063
4059
|
};
|
|
3064
4060
|
});
|
|
3065
4061
|
|
|
4062
|
+
// src/validators/claude/lsp.ts
|
|
4063
|
+
import { existsSync as existsSync21 } from "fs";
|
|
4064
|
+
import { resolve as resolve12 } from "path";
|
|
4065
|
+
var claudeLspValidator;
|
|
4066
|
+
var init_lsp = __esm(() => {
|
|
4067
|
+
claudeLspValidator = {
|
|
4068
|
+
id: "claude:lsp",
|
|
4069
|
+
provider: "claude",
|
|
4070
|
+
name: "Claude LSP Servers",
|
|
4071
|
+
description: "Validates .lsp.json (or plugin.json lspServers): language server configs with required command + extensionToLanguage; optional transport, env, settings, diagnostics etc. (binaries installed separately)",
|
|
4072
|
+
detect(dir) {
|
|
4073
|
+
return existsSync21(resolve12(dir, ".lsp.json")) || existsSync21(resolve12(dir, ".claude-plugin", "plugin.json"));
|
|
4074
|
+
},
|
|
4075
|
+
async validate(dir, _opts) {
|
|
4076
|
+
const errors = [];
|
|
4077
|
+
const warnings = [];
|
|
4078
|
+
const passes = [];
|
|
4079
|
+
let cfg = null;
|
|
4080
|
+
const lspPath = resolve12(dir, ".lsp.json");
|
|
4081
|
+
if (existsSync21(lspPath)) {
|
|
4082
|
+
try {
|
|
4083
|
+
cfg = JSON.parse(await Bun.file(lspPath).text());
|
|
4084
|
+
passes.push(".lsp.json is valid JSON");
|
|
4085
|
+
} catch {
|
|
4086
|
+
errors.push(".lsp.json is invalid JSON");
|
|
4087
|
+
return { errors, warnings, passes };
|
|
4088
|
+
}
|
|
4089
|
+
} else {
|
|
4090
|
+
const manifestPath = resolve12(dir, ".claude-plugin", "plugin.json");
|
|
4091
|
+
if (existsSync21(manifestPath)) {
|
|
4092
|
+
try {
|
|
4093
|
+
const m = JSON.parse(await Bun.file(manifestPath).text());
|
|
4094
|
+
if (m && m.lspServers && typeof m.lspServers === "object") {
|
|
4095
|
+
cfg = m.lspServers;
|
|
4096
|
+
passes.push("lspServers present inline in plugin.json");
|
|
4097
|
+
}
|
|
4098
|
+
} catch {}
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
if (!cfg) {
|
|
4102
|
+
if (!existsSync21(lspPath)) {
|
|
4103
|
+
return { errors, warnings, passes };
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4106
|
+
if (cfg && typeof cfg === "object") {
|
|
4107
|
+
const langs = Object.keys(cfg);
|
|
4108
|
+
passes.push(`${langs.length} language server(s) configured`);
|
|
4109
|
+
for (const lang of langs) {
|
|
4110
|
+
const entry = cfg[lang];
|
|
4111
|
+
if (!entry || !entry.command) {
|
|
4112
|
+
errors.push(`lsp "${lang}": "command" (the LSP binary) is required`);
|
|
4113
|
+
}
|
|
4114
|
+
if (!entry.extensionToLanguage || typeof entry.extensionToLanguage !== "object") {
|
|
4115
|
+
errors.push(`lsp "${lang}": "extensionToLanguage" map is required (e.g. { ".ts": "typescript" })`);
|
|
4116
|
+
} else {
|
|
4117
|
+
passes.push(`lsp "${lang}": has extensionToLanguage mapping`);
|
|
4118
|
+
}
|
|
4119
|
+
if (entry.diagnostics === false) {
|
|
4120
|
+
passes.push(`lsp "${lang}": diagnostics disabled (navigation only)`);
|
|
4121
|
+
}
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
4124
|
+
warnings.push('Reminder: the actual language server binary (gopls, pyright, etc.) must be installed separately on PATH. See /plugin errors tab if "Executable not found".');
|
|
4125
|
+
return { errors, warnings, passes };
|
|
4126
|
+
}
|
|
4127
|
+
};
|
|
4128
|
+
});
|
|
4129
|
+
|
|
4130
|
+
// src/validators/claude/monitors.ts
|
|
4131
|
+
import { existsSync as existsSync22 } from "fs";
|
|
4132
|
+
import { resolve as resolve13 } from "path";
|
|
4133
|
+
var claudeMonitorsValidator;
|
|
4134
|
+
var init_monitors = __esm(() => {
|
|
4135
|
+
claudeMonitorsValidator = {
|
|
4136
|
+
id: "claude:monitors",
|
|
4137
|
+
provider: "claude",
|
|
4138
|
+
name: "Claude Monitors (experimental)",
|
|
4139
|
+
description: "Validates monitors/monitors.json (or experimental.monitors): array of {name, command, description, when?}; commands support ${CLAUDE_PLUGIN_*} subs. Monitors run only in interactive CLI sessions.",
|
|
4140
|
+
detect(dir) {
|
|
4141
|
+
return existsSync22(resolve13(dir, "monitors", "monitors.json")) || existsSync22(resolve13(dir, "monitors.json")) || existsSync22(resolve13(dir, ".claude-plugin", "plugin.json"));
|
|
4142
|
+
},
|
|
4143
|
+
async validate(dir, _opts) {
|
|
4144
|
+
const errors = [];
|
|
4145
|
+
const warnings = [];
|
|
4146
|
+
const passes = [];
|
|
4147
|
+
let arr = null;
|
|
4148
|
+
const candidates = [
|
|
4149
|
+
resolve13(dir, "monitors", "monitors.json"),
|
|
4150
|
+
resolve13(dir, "monitors.json")
|
|
4151
|
+
];
|
|
4152
|
+
for (const p of candidates) {
|
|
4153
|
+
if (existsSync22(p)) {
|
|
4154
|
+
try {
|
|
4155
|
+
const parsed = JSON.parse(await Bun.file(p).text());
|
|
4156
|
+
if (Array.isArray(parsed)) {
|
|
4157
|
+
arr = parsed;
|
|
4158
|
+
passes.push("monitors config is valid JSON array");
|
|
4159
|
+
}
|
|
4160
|
+
break;
|
|
4161
|
+
} catch {
|
|
4162
|
+
errors.push("monitors config is invalid JSON");
|
|
4163
|
+
return { errors, warnings, passes };
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
4167
|
+
if (!arr) {
|
|
4168
|
+
const mp = resolve13(dir, ".claude-plugin", "plugin.json");
|
|
4169
|
+
if (existsSync22(mp)) {
|
|
4170
|
+
try {
|
|
4171
|
+
const m = JSON.parse(await Bun.file(mp).text());
|
|
4172
|
+
const exp = m?.experimental;
|
|
4173
|
+
const inline = typeof exp === "string" ? null : exp?.monitors;
|
|
4174
|
+
if (Array.isArray(inline))
|
|
4175
|
+
arr = inline;
|
|
4176
|
+
else if (typeof inline === "string") {
|
|
4177
|
+
passes.push("experimental.monitors declared as path in manifest (content not validated here)");
|
|
4178
|
+
}
|
|
4179
|
+
} catch {}
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
if (!arr) {
|
|
4183
|
+
return { errors, warnings, passes };
|
|
4184
|
+
}
|
|
4185
|
+
if (!Array.isArray(arr)) {
|
|
4186
|
+
errors.push("monitors config must be a JSON array");
|
|
4187
|
+
return { errors, warnings, passes };
|
|
4188
|
+
}
|
|
4189
|
+
const seen = new Set;
|
|
4190
|
+
arr.forEach((mon, i) => {
|
|
4191
|
+
if (!mon || typeof mon !== "object") {
|
|
4192
|
+
errors.push(`monitors[${i}]: entry must be an object`);
|
|
4193
|
+
return;
|
|
4194
|
+
}
|
|
4195
|
+
if (!mon.name || typeof mon.name !== "string") {
|
|
4196
|
+
errors.push(`monitors[${i}]: "name" (unique id) is required`);
|
|
4197
|
+
} else {
|
|
4198
|
+
if (seen.has(mon.name))
|
|
4199
|
+
errors.push(`monitors: duplicate name "${mon.name}"`);
|
|
4200
|
+
seen.add(mon.name);
|
|
4201
|
+
}
|
|
4202
|
+
if (!mon.command || typeof mon.command !== "string") {
|
|
4203
|
+
errors.push(`monitors[${i}]: "command" (shell command) is required`);
|
|
4204
|
+
} else if (/\$\{CLAUDE_/.test(mon.command)) {
|
|
4205
|
+
passes.push(`monitors[${i}] "${mon.name || i}": uses CLAUDE_PLUGIN_* substitution`);
|
|
4206
|
+
}
|
|
4207
|
+
if (!mon.description) {
|
|
4208
|
+
warnings.push(`monitors[${i}]: "description" recommended (shown in task panel)`);
|
|
4209
|
+
}
|
|
4210
|
+
if (mon.when && !/^always$|^on-skill-invoke:/.test(String(mon.when))) {
|
|
4211
|
+
warnings.push(`monitors[${i}]: "when" should be "always" (default) or "on-skill-invoke:<skill>"`);
|
|
4212
|
+
}
|
|
4213
|
+
});
|
|
4214
|
+
passes.push(`${arr.length} monitor(s) declared`);
|
|
4215
|
+
warnings.push("Note: monitors are experimental, run only for interactive CLI sessions, and are skipped on some hosts. They do not stop automatically if the plugin is disabled mid-session.");
|
|
4216
|
+
return { errors, warnings, passes };
|
|
4217
|
+
}
|
|
4218
|
+
};
|
|
4219
|
+
});
|
|
4220
|
+
|
|
3066
4221
|
// src/validators/index.ts
|
|
3067
4222
|
function resolveFor(forFlag, allValidators = validators) {
|
|
3068
4223
|
if (!forFlag) {
|
|
@@ -3097,6 +4252,8 @@ var init_validators = __esm(() => {
|
|
|
3097
4252
|
init_subagent();
|
|
3098
4253
|
init_command();
|
|
3099
4254
|
init_memory();
|
|
4255
|
+
init_lsp();
|
|
4256
|
+
init_monitors();
|
|
3100
4257
|
validators = [
|
|
3101
4258
|
claudeSkillValidator,
|
|
3102
4259
|
claudePluginValidator,
|
|
@@ -3105,14 +4262,16 @@ var init_validators = __esm(() => {
|
|
|
3105
4262
|
claudeMcpValidator,
|
|
3106
4263
|
claudeSubagentValidator,
|
|
3107
4264
|
claudeCommandValidator,
|
|
3108
|
-
claudeMemoryValidator
|
|
4265
|
+
claudeMemoryValidator,
|
|
4266
|
+
claudeLspValidator,
|
|
4267
|
+
claudeMonitorsValidator
|
|
3109
4268
|
];
|
|
3110
4269
|
});
|
|
3111
4270
|
|
|
3112
4271
|
// src/core/remote.ts
|
|
3113
4272
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
3114
4273
|
import { mkdtempSync, rmSync } from "fs";
|
|
3115
|
-
import { join as
|
|
4274
|
+
import { join as join16 } from "path";
|
|
3116
4275
|
import { tmpdir } from "os";
|
|
3117
4276
|
function parseRemoteUrl(input) {
|
|
3118
4277
|
if (input.startsWith(".") || input.startsWith("/") || input.startsWith("~")) {
|
|
@@ -3149,7 +4308,7 @@ function isGhAvailable() {
|
|
|
3149
4308
|
return ghAvailable;
|
|
3150
4309
|
}
|
|
3151
4310
|
async function cloneToTemp(parsed) {
|
|
3152
|
-
const tmpDir = mkdtempSync(
|
|
4311
|
+
const tmpDir = mkdtempSync(join16(tmpdir(), "dora-"));
|
|
3153
4312
|
const cleanup = () => {
|
|
3154
4313
|
try {
|
|
3155
4314
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
@@ -3197,15 +4356,15 @@ var exports_validate_top = {};
|
|
|
3197
4356
|
__export(exports_validate_top, {
|
|
3198
4357
|
default: () => validate_top_default
|
|
3199
4358
|
});
|
|
3200
|
-
import { existsSync as
|
|
3201
|
-
import { resolve as
|
|
3202
|
-
var
|
|
4359
|
+
import { existsSync as existsSync24 } from "fs";
|
|
4360
|
+
import { resolve as resolve14 } from "path";
|
|
4361
|
+
var import_picocolors13, validate_top_default;
|
|
3203
4362
|
var init_validate_top = __esm(() => {
|
|
3204
4363
|
init_dist();
|
|
3205
4364
|
init_out();
|
|
3206
4365
|
init_validators();
|
|
3207
4366
|
init_remote();
|
|
3208
|
-
|
|
4367
|
+
import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
3209
4368
|
validate_top_default = defineCommand({
|
|
3210
4369
|
meta: {
|
|
3211
4370
|
name: "validate",
|
|
@@ -3245,24 +4404,24 @@ var init_validate_top = __esm(() => {
|
|
|
3245
4404
|
let cleanup;
|
|
3246
4405
|
if (remote) {
|
|
3247
4406
|
ui.info(`
|
|
3248
|
-
Cloning ${
|
|
4407
|
+
Cloning ${import_picocolors13.default.dim(args.path)}...`);
|
|
3249
4408
|
try {
|
|
3250
4409
|
const result = await cloneToTemp(remote);
|
|
3251
|
-
fullPath = remote.subpath ?
|
|
4410
|
+
fullPath = remote.subpath ? resolve14(result.dir, remote.subpath) : result.dir;
|
|
3252
4411
|
cleanup = result.cleanup;
|
|
3253
4412
|
} catch (err) {
|
|
3254
4413
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3255
4414
|
ui.fail(msg);
|
|
3256
4415
|
process.exit(1);
|
|
3257
4416
|
}
|
|
3258
|
-
if (!
|
|
4417
|
+
if (!existsSync24(fullPath)) {
|
|
3259
4418
|
cleanup();
|
|
3260
4419
|
ui.fail(`Subdirectory not found in repo: ${remote.subpath}`);
|
|
3261
4420
|
process.exit(1);
|
|
3262
4421
|
}
|
|
3263
4422
|
} else {
|
|
3264
|
-
fullPath =
|
|
3265
|
-
if (!
|
|
4423
|
+
fullPath = resolve14(args.path);
|
|
4424
|
+
if (!existsSync24(fullPath)) {
|
|
3266
4425
|
ui.fail(`Path not found: ${args.path}
|
|
3267
4426
|
|
|
3268
4427
|
Check that the path is correct and the directory exists.`);
|
|
@@ -3293,13 +4452,13 @@ Check that the path is correct and the directory exists.`);
|
|
|
3293
4452
|
` + `Available providers:
|
|
3294
4453
|
` + providers.map((p) => {
|
|
3295
4454
|
const pvs = validators.filter((v) => v.provider === p);
|
|
3296
|
-
return ` ${
|
|
3297
|
-
` + pvs.map((v) => ` \u2022 ${
|
|
4455
|
+
return ` ${import_picocolors13.default.bold(p)}
|
|
4456
|
+
` + pvs.map((v) => ` \u2022 ${import_picocolors13.default.dim(v.id)} \u2014 ${v.description}`).join(`
|
|
3298
4457
|
`);
|
|
3299
4458
|
}).join(`
|
|
3300
4459
|
`) + `
|
|
3301
4460
|
|
|
3302
|
-
Use ${
|
|
4461
|
+
Use ${import_picocolors13.default.dim("--for <provider>")} or ${import_picocolors13.default.dim("--for <provider:type>")} to target explicitly.`);
|
|
3303
4462
|
process.exit(1);
|
|
3304
4463
|
}
|
|
3305
4464
|
const allResults = [];
|
|
@@ -3320,7 +4479,7 @@ Use ${import_picocolors10.default.dim("--for <provider>")} or ${import_picocolor
|
|
|
3320
4479
|
} else {
|
|
3321
4480
|
for (const { id, name, result } of allResults) {
|
|
3322
4481
|
ui.write(`
|
|
3323
|
-
${
|
|
4482
|
+
${import_picocolors13.default.bold("dora validate")} \u2014 ${import_picocolors13.default.white(name)} ${import_picocolors13.default.dim(`(${id})`)}
|
|
3324
4483
|
`);
|
|
3325
4484
|
ui.info(` Path: ${args.path}
|
|
3326
4485
|
`);
|
|
@@ -3335,7 +4494,7 @@ Use ${import_picocolors10.default.dim("--for <provider>")} or ${import_picocolor
|
|
|
3335
4494
|
}
|
|
3336
4495
|
if (result.errors.length === 0 && result.warnings.length === 0) {
|
|
3337
4496
|
ui.write(`
|
|
3338
|
-
${
|
|
4497
|
+
${import_picocolors13.default.green("\u2713")} ${import_picocolors13.default.white("All checks passed.")}
|
|
3339
4498
|
`);
|
|
3340
4499
|
} else {
|
|
3341
4500
|
ui.info(`
|
|
@@ -3357,16 +4516,16 @@ var exports_init2 = {};
|
|
|
3357
4516
|
__export(exports_init2, {
|
|
3358
4517
|
default: () => init_default2
|
|
3359
4518
|
});
|
|
3360
|
-
import { basename as
|
|
4519
|
+
import { basename as basename4, join as join17 } from "path";
|
|
3361
4520
|
var {spawnSync: spawnSync5 } = globalThis.Bun;
|
|
3362
|
-
var
|
|
4521
|
+
var import_picocolors14, init_default2;
|
|
3363
4522
|
var init_init2 = __esm(() => {
|
|
3364
4523
|
init_dist();
|
|
3365
4524
|
init_out();
|
|
3366
4525
|
init_journal_config();
|
|
3367
4526
|
init_journal_remote();
|
|
3368
4527
|
init_prompt();
|
|
3369
|
-
|
|
4528
|
+
import_picocolors14 = __toESM(require_picocolors(), 1);
|
|
3370
4529
|
init_default2 = defineCommand({
|
|
3371
4530
|
meta: {
|
|
3372
4531
|
name: "init",
|
|
@@ -3393,17 +4552,17 @@ var init_init2 = __esm(() => {
|
|
|
3393
4552
|
ui.heading("dora init \u2014 Set up doraval, your journal, and the coding agent dora should use on the fly");
|
|
3394
4553
|
const ghCheck = ensureGhCli();
|
|
3395
4554
|
if (!ghCheck.ok) {
|
|
3396
|
-
ui.write(` ${
|
|
4555
|
+
ui.write(` ${import_picocolors14.default.red("\u2717")} ${import_picocolors14.default.white("The GitHub CLI (")}${import_picocolors14.default.bold("gh")}${import_picocolors14.default.white(") is not installed.")}
|
|
3397
4556
|
`);
|
|
3398
|
-
ui.info(` doraval uses ${
|
|
4557
|
+
ui.info(` doraval uses ${import_picocolors14.default.bold("gh")} to fetch and sync journal files with GitHub.
|
|
3399
4558
|
`);
|
|
3400
4559
|
ui.info(` Install it:
|
|
3401
4560
|
`);
|
|
3402
|
-
ui.info(` macOS: ${
|
|
3403
|
-
ui.info(` Linux: ${
|
|
3404
|
-
ui.info(` Windows: ${
|
|
4561
|
+
ui.info(` macOS: ${import_picocolors14.default.dim("brew install gh")}`);
|
|
4562
|
+
ui.info(` Linux: ${import_picocolors14.default.dim("https://github.com/cli/cli/blob/trunk/docs/install_linux.md")}`);
|
|
4563
|
+
ui.info(` Windows: ${import_picocolors14.default.dim("winget install --id GitHub.cli")}
|
|
3405
4564
|
`);
|
|
3406
|
-
ui.info(` Then authenticate: ${
|
|
4565
|
+
ui.info(` Then authenticate: ${import_picocolors14.default.dim("gh auth login")}
|
|
3407
4566
|
`);
|
|
3408
4567
|
process.exit(1);
|
|
3409
4568
|
}
|
|
@@ -3416,44 +4575,44 @@ var init_init2 = __esm(() => {
|
|
|
3416
4575
|
if (gitOwner) {
|
|
3417
4576
|
defaultRepo = `${gitOwner}/${gitOwner}.md`;
|
|
3418
4577
|
if (ghLogin && ghLogin !== gitOwner) {
|
|
3419
|
-
sourceNote = ` ${
|
|
4578
|
+
sourceNote = ` ${import_picocolors14.default.dim("(from git remote; your active gh account is " + ghLogin + ")")}
|
|
3420
4579
|
`;
|
|
3421
4580
|
} else {
|
|
3422
|
-
sourceNote = ` ${
|
|
4581
|
+
sourceNote = ` ${import_picocolors14.default.dim("(from git remote)")}
|
|
3423
4582
|
`;
|
|
3424
4583
|
}
|
|
3425
4584
|
} else if (ghLogin) {
|
|
3426
4585
|
defaultRepo = `${ghLogin}/${ghLogin}.md`;
|
|
3427
|
-
sourceNote = ` ${
|
|
4586
|
+
sourceNote = ` ${import_picocolors14.default.dim("(from your active gh account)")}
|
|
3428
4587
|
`;
|
|
3429
4588
|
} else {
|
|
3430
|
-
ui.warn(`Not logged in to GitHub. Run ${
|
|
4589
|
+
ui.warn(`Not logged in to GitHub. Run ${import_picocolors14.default.dim("gh auth login")} first.
|
|
3431
4590
|
`);
|
|
3432
4591
|
process.exit(1);
|
|
3433
4592
|
}
|
|
3434
4593
|
const existingConfig = await readConfig();
|
|
3435
4594
|
if (existingConfig?.journal.repo) {
|
|
3436
4595
|
defaultRepo = existingConfig.journal.repo;
|
|
3437
|
-
sourceNote = ` ${
|
|
4596
|
+
sourceNote = ` ${import_picocolors14.default.dim("(from your previous journal setup)")}
|
|
3438
4597
|
`;
|
|
3439
4598
|
}
|
|
3440
|
-
ui.info(` Journal repo ${
|
|
4599
|
+
ui.info(` Journal repo ${import_picocolors14.default.dim("(owner/name)")}`);
|
|
3441
4600
|
if (sourceNote)
|
|
3442
4601
|
ui.write(sourceNote);
|
|
3443
4602
|
repo = prompt(" >", defaultRepo);
|
|
3444
4603
|
}
|
|
3445
4604
|
let project = args.project || process.env.DORAVAL_PROJECT;
|
|
3446
4605
|
if (!project) {
|
|
3447
|
-
const defaultProject =
|
|
4606
|
+
const defaultProject = basename4(process.cwd());
|
|
3448
4607
|
project = prompt(" Project name", defaultProject);
|
|
3449
4608
|
}
|
|
3450
4609
|
project = sanitizeProjectName(project);
|
|
3451
4610
|
if (!repoExists(repo)) {
|
|
3452
|
-
ui.write(` ${
|
|
4611
|
+
ui.write(` ${import_picocolors14.default.red("\u2717")} ${import_picocolors14.default.white("Repository")} ${import_picocolors14.default.bold(repo)} ${import_picocolors14.default.white("not found on GitHub.")}
|
|
3453
4612
|
`);
|
|
3454
4613
|
ui.info(` Create it first:
|
|
3455
4614
|
`);
|
|
3456
|
-
ui.info(` ${
|
|
4615
|
+
ui.info(` ${import_picocolors14.default.dim(`gh repo create ${repo} --private --description "Personal journal for agent decisions"`)}
|
|
3457
4616
|
`);
|
|
3458
4617
|
process.exit(1);
|
|
3459
4618
|
}
|
|
@@ -3461,16 +4620,16 @@ var init_init2 = __esm(() => {
|
|
|
3461
4620
|
const alreadyRegistered = existing?.journal.projects[project];
|
|
3462
4621
|
const isRefresh = alreadyRegistered && args.refresh;
|
|
3463
4622
|
if (alreadyRegistered && !isRefresh) {
|
|
3464
|
-
ui.write(` ${
|
|
4623
|
+
ui.write(` ${import_picocolors14.default.yellow("\u26A0")} ${import_picocolors14.default.white("Project")} ${import_picocolors14.default.bold(project)} ${import_picocolors14.default.white("is already registered.")}
|
|
3465
4624
|
`);
|
|
3466
4625
|
ui.info(` Repo: ${existing.journal.repo}
|
|
3467
4626
|
`);
|
|
3468
|
-
ui.info(` To refresh journal files, use ${
|
|
4627
|
+
ui.info(` To refresh journal files, use ${import_picocolors14.default.dim("dora journal update")} (or ${import_picocolors14.default.dim("dora init --refresh")}).
|
|
3469
4628
|
`);
|
|
3470
4629
|
}
|
|
3471
4630
|
const journalsDir = getJournalsDir();
|
|
3472
4631
|
const remotePath = `projects/${project}.md`;
|
|
3473
|
-
const localPath =
|
|
4632
|
+
const localPath = join17(journalsDir, `${project}.md`);
|
|
3474
4633
|
const effectiveRepo = isRefresh && !args.repo ? existing.journal.repo : repo;
|
|
3475
4634
|
const config = existing ?? {
|
|
3476
4635
|
journal: { repo: effectiveRepo, projects: {} }
|
|
@@ -3481,9 +4640,9 @@ var init_init2 = __esm(() => {
|
|
|
3481
4640
|
local_path: localPath
|
|
3482
4641
|
};
|
|
3483
4642
|
ensureDoravalDirs();
|
|
3484
|
-
ui.write(` ${
|
|
4643
|
+
ui.write(` ${import_picocolors14.default.dim(import_picocolors14.default.gray("Fetching journal files from"))} ${import_picocolors14.default.gray(effectiveRepo)}${import_picocolors14.default.dim(import_picocolors14.default.gray("..."))}
|
|
3485
4644
|
`);
|
|
3486
|
-
const globalDest =
|
|
4645
|
+
const globalDest = join17(journalsDir, "global.md");
|
|
3487
4646
|
const refreshGlobalRes = await refreshLocalJournalFile(effectiveRepo, "global.md", globalDest);
|
|
3488
4647
|
let wroteGlobal;
|
|
3489
4648
|
if (!refreshGlobalRes.ok) {
|
|
@@ -3500,7 +4659,7 @@ var init_init2 = __esm(() => {
|
|
|
3500
4659
|
if (wroteGlobal) {
|
|
3501
4660
|
ui.success("global.md");
|
|
3502
4661
|
} else {
|
|
3503
|
-
ui.write(` ${
|
|
4662
|
+
ui.write(` ${import_picocolors14.default.dim("\xB7")} global.md ${import_picocolors14.default.dim("(not found \u2014 will be created on first sync)")}`);
|
|
3504
4663
|
await Bun.write(globalDest, `# Global Journal
|
|
3505
4664
|
|
|
3506
4665
|
Cross-project principles.
|
|
@@ -3522,7 +4681,7 @@ Cross-project principles.
|
|
|
3522
4681
|
if (wroteProject) {
|
|
3523
4682
|
ui.success(remotePath);
|
|
3524
4683
|
} else {
|
|
3525
|
-
ui.write(` ${
|
|
4684
|
+
ui.write(` ${import_picocolors14.default.dim("\xB7")} ${remotePath} ${import_picocolors14.default.dim("(not found \u2014 will be created on first sync)")}`);
|
|
3526
4685
|
await Bun.write(localPath, `# ${project} Journal
|
|
3527
4686
|
|
|
3528
4687
|
Project-specific decisions.
|
|
@@ -3530,13 +4689,13 @@ Project-specific decisions.
|
|
|
3530
4689
|
}
|
|
3531
4690
|
await writeConfig(config);
|
|
3532
4691
|
ui.write(`
|
|
3533
|
-
${
|
|
4692
|
+
${import_picocolors14.default.green("\u2713")} ${import_picocolors14.default.white("Journal ready for project")} ${import_picocolors14.default.bold(import_picocolors14.default.white(project))}.
|
|
3534
4693
|
`);
|
|
3535
4694
|
const existingAgent = (await readConfig())?.agent;
|
|
3536
4695
|
if (existingAgent?.command) {
|
|
3537
|
-
ui.write(` ${
|
|
4696
|
+
ui.write(` ${import_picocolors14.default.bold(import_picocolors14.default.white("Coding agent (already configured)"))}
|
|
3538
4697
|
`);
|
|
3539
|
-
ui.write(` Current: ${
|
|
4698
|
+
ui.write(` Current: ${import_picocolors14.default.dim(import_picocolors14.default.gray(existingAgent.command))} template: ${import_picocolors14.default.dim(import_picocolors14.default.gray(existingAgent.prompt_template || "(default)"))}
|
|
3540
4699
|
`);
|
|
3541
4700
|
const change = prompt(" Reconfigure / change the coding agent for on-the-fly enrichment? (y/N)", "n");
|
|
3542
4701
|
if (!/^y/i.test(String(change))) {
|
|
@@ -3546,16 +4705,16 @@ Project-specific decisions.
|
|
|
3546
4705
|
if (existingAgent)
|
|
3547
4706
|
cfg.agent = existingAgent;
|
|
3548
4707
|
await writeConfig(cfg);
|
|
3549
|
-
ui.write(` ${
|
|
4708
|
+
ui.write(` ${import_picocolors14.default.green("\u2713")} ${import_picocolors14.default.white("Try:")} ${import_picocolors14.default.dim(import_picocolors14.default.gray('dora journal add "short decision"'))}
|
|
3550
4709
|
`);
|
|
3551
4710
|
process.exit(0);
|
|
3552
4711
|
return;
|
|
3553
4712
|
}
|
|
3554
4713
|
ui.blank();
|
|
3555
4714
|
} else {
|
|
3556
|
-
ui.write(` ${
|
|
4715
|
+
ui.write(` ${import_picocolors14.default.bold(import_picocolors14.default.white("Coding agent for journal add"))}
|
|
3557
4716
|
`);
|
|
3558
|
-
ui.info(` When configured, ${
|
|
4717
|
+
ui.info(` When configured, ${import_picocolors14.default.dim(import_picocolors14.default.gray('dora journal add ".."'))} will use your agent to enrich entries with tags and rationale automatically.
|
|
3559
4718
|
`);
|
|
3560
4719
|
}
|
|
3561
4720
|
const common = [
|
|
@@ -3574,7 +4733,7 @@ Project-specific decisions.
|
|
|
3574
4733
|
}
|
|
3575
4734
|
}
|
|
3576
4735
|
let agentCmd = detected || "claude";
|
|
3577
|
-
ui.write(` Detected / default agent command: ${
|
|
4736
|
+
ui.write(` Detected / default agent command: ${import_picocolors14.default.dim(import_picocolors14.default.gray(agentCmd))}`);
|
|
3578
4737
|
agentCmd = prompt(" Agent command (the binary you run for prompts)", agentCmd);
|
|
3579
4738
|
let template = detected ? common.find((c) => c.name === detected)?.template || '-p "{{prompt}}" --output-format json' : '-p "{{prompt}}" --output-format json';
|
|
3580
4739
|
ui.info(` Prompt template (use {{prompt}} placeholder):`);
|
|
@@ -3586,11 +4745,11 @@ Project-specific decisions.
|
|
|
3586
4745
|
};
|
|
3587
4746
|
await writeConfig(finalConfig);
|
|
3588
4747
|
ui.write(`
|
|
3589
|
-
${
|
|
4748
|
+
${import_picocolors14.default.green("\u2713")} ${import_picocolors14.default.white("Agent configured.")}
|
|
3590
4749
|
`);
|
|
3591
|
-
ui.info(` Re-run ${
|
|
4750
|
+
ui.info(` Re-run ${import_picocolors14.default.dim(import_picocolors14.default.gray("dora init"))} anytime to change it.
|
|
3592
4751
|
`);
|
|
3593
|
-
ui.info(` Next: ${
|
|
4752
|
+
ui.info(` Next: ${import_picocolors14.default.dim(import_picocolors14.default.gray('dora journal add ".."'))}, ${import_picocolors14.default.dim(import_picocolors14.default.gray("dora journal list"))}, or ${import_picocolors14.default.dim(import_picocolors14.default.gray("dora journal update"))}.
|
|
3594
4753
|
`);
|
|
3595
4754
|
process.exit(0);
|
|
3596
4755
|
}
|
|
@@ -3602,7 +4761,7 @@ init_dist();
|
|
|
3602
4761
|
// package.json
|
|
3603
4762
|
var package_default = {
|
|
3604
4763
|
name: "@hacksmith/doraval",
|
|
3605
|
-
version: "0.2.
|
|
4764
|
+
version: "0.2.23",
|
|
3606
4765
|
author: "Saif",
|
|
3607
4766
|
repository: {
|
|
3608
4767
|
type: "git",
|
|
@@ -3663,7 +4822,7 @@ var package_default = {
|
|
|
3663
4822
|
};
|
|
3664
4823
|
|
|
3665
4824
|
// src/cli/index.ts
|
|
3666
|
-
var
|
|
4825
|
+
var import_picocolors15 = __toESM(require_picocolors(), 1);
|
|
3667
4826
|
var skill = defineCommand({
|
|
3668
4827
|
meta: {
|
|
3669
4828
|
name: "skill",
|
|
@@ -3694,6 +4853,32 @@ var journal = defineCommand({
|
|
|
3694
4853
|
showUsage(journal);
|
|
3695
4854
|
}
|
|
3696
4855
|
});
|
|
4856
|
+
var claude = defineCommand({
|
|
4857
|
+
meta: {
|
|
4858
|
+
name: "claude",
|
|
4859
|
+
description: "Claude Code-specific commands (packaging, scaffolding, distribution)"
|
|
4860
|
+
},
|
|
4861
|
+
subCommands: {
|
|
4862
|
+
new: () => Promise.resolve().then(() => (init_new(), exports_new)).then((m) => m.default),
|
|
4863
|
+
bump: () => Promise.resolve().then(() => (init_bump(), exports_bump)).then((m) => m.default)
|
|
4864
|
+
},
|
|
4865
|
+
run() {
|
|
4866
|
+
showUsage(claude);
|
|
4867
|
+
}
|
|
4868
|
+
});
|
|
4869
|
+
var codex = defineCommand({
|
|
4870
|
+
meta: {
|
|
4871
|
+
name: "codex",
|
|
4872
|
+
description: "Codex (OpenAI)-specific commands (packaging, scaffolding, distribution)"
|
|
4873
|
+
},
|
|
4874
|
+
subCommands: {
|
|
4875
|
+
new: () => Promise.resolve().then(() => (init_new2(), exports_new2)).then((m) => m.default),
|
|
4876
|
+
bump: () => Promise.resolve().then(() => (init_bump(), exports_bump)).then((m) => m.default)
|
|
4877
|
+
},
|
|
4878
|
+
run() {
|
|
4879
|
+
showUsage(codex);
|
|
4880
|
+
}
|
|
4881
|
+
});
|
|
3697
4882
|
var doraemonArt = `
|
|
3698
4883
|
\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2880\u28E0\u28E4\u28F4\u28F6\u28F6\u28F6\u28F6\u28F6\u2836\u28F6\u28E4\u28E4\u28C0\u2800\u2800\u2800\u2800\u2800\u2800
|
|
3699
4884
|
\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2880\u28E4\u28FE\u28FF\u28FF\u28FF\u2801\u2800\u2880\u2808\u28BF\u2880\u28C0\u2800\u2839\u28FF\u28FF\u28FF\u28E6\u28C4\u2800\u2800\u2800
|
|
@@ -3715,12 +4900,15 @@ var main = defineCommand({
|
|
|
3715
4900
|
subCommands: {
|
|
3716
4901
|
validate: () => Promise.resolve().then(() => (init_validate_top(), exports_validate_top)).then((m) => m.default),
|
|
3717
4902
|
init: () => Promise.resolve().then(() => (init_init2(), exports_init2)).then((m) => m.default),
|
|
4903
|
+
bump: () => Promise.resolve().then(() => (init_bump(), exports_bump)).then((m) => m.default),
|
|
3718
4904
|
skill: () => Promise.resolve(skill),
|
|
3719
|
-
journal: () => Promise.resolve(journal)
|
|
4905
|
+
journal: () => Promise.resolve(journal),
|
|
4906
|
+
claude: () => Promise.resolve(claude),
|
|
4907
|
+
codex: () => Promise.resolve(codex)
|
|
3720
4908
|
},
|
|
3721
4909
|
run() {
|
|
3722
4910
|
console.log(`
|
|
3723
|
-
` +
|
|
4911
|
+
` + import_picocolors15.default.blue(doraemonArt) + `
|
|
3724
4912
|
`);
|
|
3725
4913
|
showUsage(main);
|
|
3726
4914
|
}
|