mancode 0.3.6 → 0.3.7
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 +3 -3
- package/README.zh-CN.md +3 -3
- package/dist/cli.js +625 -599
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
import { program } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/commands/init.ts
|
|
10
|
-
import { promises as
|
|
10
|
+
import { promises as fs4 } from "fs";
|
|
11
11
|
import os from "os";
|
|
12
|
-
import
|
|
12
|
+
import path15 from "path";
|
|
13
13
|
import process5 from "process";
|
|
14
14
|
|
|
15
15
|
// src/installers/claude-code.ts
|
|
@@ -2631,9 +2631,9 @@ function renderManpsRule() {
|
|
|
2631
2631
|
return renderModeSkill("manps", "/");
|
|
2632
2632
|
}
|
|
2633
2633
|
|
|
2634
|
-
// src/installers/
|
|
2635
|
-
import {
|
|
2636
|
-
import
|
|
2634
|
+
// src/installers/platform-status.ts
|
|
2635
|
+
import { promises as fs } from "fs";
|
|
2636
|
+
import path8 from "path";
|
|
2637
2637
|
|
|
2638
2638
|
// src/installers/managed-block.ts
|
|
2639
2639
|
var DEFAULT_MANCODE_START_MARKER = "<!-- mancode:start -->";
|
|
@@ -2725,104 +2725,14 @@ function findMarkerLine(content, marker) {
|
|
|
2725
2725
|
return null;
|
|
2726
2726
|
}
|
|
2727
2727
|
|
|
2728
|
-
// src/installers/codex.ts
|
|
2729
|
-
async function installCodex(projectRoot, options) {
|
|
2730
|
-
await installMancodeCore(projectRoot);
|
|
2731
|
-
const agentsPath = path7.join(projectRoot, "AGENTS.md");
|
|
2732
|
-
const existing = await readTextIfExists(agentsPath);
|
|
2733
|
-
const sharedContent = await generateSharedContent(projectRoot, {
|
|
2734
|
-
platform: "codex",
|
|
2735
|
-
displayName: "Codex (ChatGPT desktop/CLI)",
|
|
2736
|
-
capabilities: {
|
|
2737
|
-
slashCommands: "partial",
|
|
2738
|
-
subagents: false,
|
|
2739
|
-
hooks: false,
|
|
2740
|
-
skills: "agents-skills"
|
|
2741
|
-
},
|
|
2742
|
-
minimal: options.minimal,
|
|
2743
|
-
techStack: options.techStack,
|
|
2744
|
-
uiLibrary: options.uiLibrary,
|
|
2745
|
-
projectProfile: options.projectProfile
|
|
2746
|
-
});
|
|
2747
|
-
const block = [
|
|
2748
|
-
DEFAULT_MANCODE_START_MARKER,
|
|
2749
|
-
"<!-- Managed by mancode. Do not edit this block manually. -->",
|
|
2750
|
-
"",
|
|
2751
|
-
"# mancode Configuration",
|
|
2752
|
-
"",
|
|
2753
|
-
sharedContent.trim(),
|
|
2754
|
-
DEFAULT_MANCODE_END_MARKER
|
|
2755
|
-
].join("\n");
|
|
2756
|
-
await writeFile6(agentsPath, replaceManagedBlock(existing, block), "utf-8");
|
|
2757
|
-
await installCodexSkills(projectRoot, options.minimal ?? false);
|
|
2758
|
-
}
|
|
2759
|
-
|
|
2760
|
-
// src/installers/copilot.ts
|
|
2761
|
-
import { mkdir as mkdir6, writeFile as writeFile7 } from "fs/promises";
|
|
2762
|
-
import path8 from "path";
|
|
2763
|
-
async function installCopilot(projectRoot, options) {
|
|
2764
|
-
await installMancodeCore(projectRoot);
|
|
2765
|
-
const githubDir = path8.join(projectRoot, ".github");
|
|
2766
|
-
await mkdir6(githubDir, { recursive: true });
|
|
2767
|
-
const instructionsPath = path8.join(githubDir, "copilot-instructions.md");
|
|
2768
|
-
const existing = await readTextIfExists(instructionsPath);
|
|
2769
|
-
const sharedContent = await generateSharedContent(projectRoot, {
|
|
2770
|
-
platform: "copilot",
|
|
2771
|
-
displayName: "GitHub Copilot",
|
|
2772
|
-
capabilities: {
|
|
2773
|
-
slashCommands: "none",
|
|
2774
|
-
subagents: false,
|
|
2775
|
-
hooks: false,
|
|
2776
|
-
skills: "instructions"
|
|
2777
|
-
},
|
|
2778
|
-
minimal: true,
|
|
2779
|
-
techStack: options.techStack,
|
|
2780
|
-
uiLibrary: options.uiLibrary,
|
|
2781
|
-
projectProfile: options.projectProfile
|
|
2782
|
-
});
|
|
2783
|
-
const sections = [
|
|
2784
|
-
DEFAULT_MANCODE_START_MARKER,
|
|
2785
|
-
"<!-- Managed by mancode. Do not edit this block manually. -->",
|
|
2786
|
-
"",
|
|
2787
|
-
"# mancode for GitHub Copilot",
|
|
2788
|
-
"",
|
|
2789
|
-
sharedContent.trim()
|
|
2790
|
-
];
|
|
2791
|
-
if (!options.minimal) {
|
|
2792
|
-
sections.push("", renderCopilotPromptConventions());
|
|
2793
|
-
}
|
|
2794
|
-
sections.push(DEFAULT_MANCODE_END_MARKER);
|
|
2795
|
-
await writeFile7(
|
|
2796
|
-
instructionsPath,
|
|
2797
|
-
replaceManagedBlock(existing, sections.join("\n")),
|
|
2798
|
-
"utf-8"
|
|
2799
|
-
);
|
|
2800
|
-
await installCopilotPrompts(projectRoot, options.minimal ?? false);
|
|
2801
|
-
}
|
|
2802
|
-
function renderCopilotPromptConventions() {
|
|
2803
|
-
return [
|
|
2804
|
-
"## mancode Prompt Conventions",
|
|
2805
|
-
"",
|
|
2806
|
-
"GitHub Copilot does not provide native mancode slash commands, hooks, or isolated subagents. Treat these names as user prompt conventions:",
|
|
2807
|
-
"",
|
|
2808
|
-
"- man: use progressive research, planning, implementation, verification, and bounded risk-based review.",
|
|
2809
|
-
"- manba: diagnose bugs and validate real user flows or regressions.",
|
|
2810
|
-
"- manteam: read team memory and write handoff-friendly summaries.",
|
|
2811
|
-
"- manps: prefer `mancode manps [area]` before cleanup.",
|
|
2812
|
-
"- mansolo: exit any active mode and return to default solo behavior.",
|
|
2813
|
-
"",
|
|
2814
|
-
"These correspond to prompt files in `.github/prompts/`. Select the matching prompt to activate a mode."
|
|
2815
|
-
].join("\n");
|
|
2816
|
-
}
|
|
2817
|
-
|
|
2818
2728
|
// src/installers/zcode.ts
|
|
2819
|
-
import { writeFile as
|
|
2820
|
-
import
|
|
2729
|
+
import { writeFile as writeFile6 } from "fs/promises";
|
|
2730
|
+
import path7 from "path";
|
|
2821
2731
|
var ZCODE_MANCODE_START_MARKER = "<!-- mancode:zcode:start -->";
|
|
2822
2732
|
var ZCODE_MANCODE_END_MARKER = "<!-- mancode:zcode:end -->";
|
|
2823
2733
|
async function installZcode(projectRoot, options) {
|
|
2824
2734
|
await installMancodeCore(projectRoot);
|
|
2825
|
-
const agentsPath =
|
|
2735
|
+
const agentsPath = path7.join(projectRoot, "AGENTS.md");
|
|
2826
2736
|
const existing = await readTextIfExists(agentsPath);
|
|
2827
2737
|
const sharedContent = await generateSharedContent(projectRoot, {
|
|
2828
2738
|
platform: "zcode",
|
|
@@ -2847,7 +2757,7 @@ async function installZcode(projectRoot, options) {
|
|
|
2847
2757
|
sharedContent.trim(),
|
|
2848
2758
|
ZCODE_MANCODE_END_MARKER
|
|
2849
2759
|
].join("\n");
|
|
2850
|
-
await
|
|
2760
|
+
await writeFile6(
|
|
2851
2761
|
agentsPath,
|
|
2852
2762
|
replaceManagedBlock(
|
|
2853
2763
|
existing,
|
|
@@ -2860,143 +2770,514 @@ async function installZcode(projectRoot, options) {
|
|
|
2860
2770
|
await installZcodeSkills(projectRoot, options.minimal ?? false);
|
|
2861
2771
|
}
|
|
2862
2772
|
|
|
2863
|
-
// src/installers/
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
skills: "native"
|
|
2873
|
-
},
|
|
2874
|
-
install: installClaudeCode
|
|
2875
|
-
},
|
|
2876
|
-
cursor: {
|
|
2877
|
-
name: "cursor",
|
|
2878
|
-
displayName: "Cursor",
|
|
2879
|
-
capabilities: {
|
|
2880
|
-
slashCommands: "native",
|
|
2881
|
-
subagents: false,
|
|
2882
|
-
hooks: false,
|
|
2883
|
-
skills: "rules"
|
|
2884
|
-
},
|
|
2885
|
-
install: installCursor
|
|
2886
|
-
},
|
|
2887
|
-
codex: {
|
|
2888
|
-
name: "codex",
|
|
2889
|
-
displayName: "Codex (ChatGPT desktop/CLI)",
|
|
2890
|
-
capabilities: {
|
|
2891
|
-
slashCommands: "partial",
|
|
2892
|
-
subagents: false,
|
|
2893
|
-
hooks: false,
|
|
2894
|
-
skills: "agents-skills"
|
|
2895
|
-
},
|
|
2896
|
-
install: installCodex
|
|
2897
|
-
},
|
|
2898
|
-
copilot: {
|
|
2899
|
-
name: "copilot",
|
|
2900
|
-
displayName: "GitHub Copilot",
|
|
2901
|
-
capabilities: {
|
|
2902
|
-
slashCommands: "none",
|
|
2903
|
-
subagents: false,
|
|
2904
|
-
hooks: false,
|
|
2905
|
-
skills: "instructions"
|
|
2906
|
-
},
|
|
2907
|
-
install: installCopilot
|
|
2908
|
-
},
|
|
2909
|
-
zcode: {
|
|
2910
|
-
name: "zcode",
|
|
2911
|
-
displayName: "ZCode",
|
|
2912
|
-
capabilities: {
|
|
2913
|
-
slashCommands: "partial",
|
|
2914
|
-
subagents: false,
|
|
2915
|
-
hooks: false,
|
|
2916
|
-
skills: "agents-skills"
|
|
2917
|
-
},
|
|
2918
|
-
install: installZcode
|
|
2919
|
-
}
|
|
2920
|
-
};
|
|
2921
|
-
function getPlatformInstaller(platform) {
|
|
2922
|
-
if (!isPlatformName(platform)) return null;
|
|
2923
|
-
return PLATFORM_INSTALLERS[platform];
|
|
2924
|
-
}
|
|
2925
|
-
function getPlatformInstallers() {
|
|
2926
|
-
return Object.values(PLATFORM_INSTALLERS);
|
|
2927
|
-
}
|
|
2928
|
-
function formatPlatformName(platform) {
|
|
2929
|
-
return getPlatformInstaller(platform)?.displayName ?? platform;
|
|
2773
|
+
// src/installers/platform-status.ts
|
|
2774
|
+
async function checkPlatformStatus(rootDir, platform, installed) {
|
|
2775
|
+
const readiness = await checkPlatformReadiness(rootDir, platform);
|
|
2776
|
+
return {
|
|
2777
|
+
installed,
|
|
2778
|
+
ready: installed && readiness.ready,
|
|
2779
|
+
target: readiness.target,
|
|
2780
|
+
detail: describeStatus(installed, readiness)
|
|
2781
|
+
};
|
|
2930
2782
|
}
|
|
2931
|
-
function
|
|
2932
|
-
return
|
|
2783
|
+
async function isPlatformPresent(rootDir, platform) {
|
|
2784
|
+
return (await checkPlatformReadiness(rootDir, platform)).present;
|
|
2933
2785
|
}
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
hasRemote: false
|
|
2947
|
-
};
|
|
2948
|
-
var REMOTE_PATTERN = /github\.com|gitlab\.com|bitbucket\.org/;
|
|
2949
|
-
var GIT_ENV = {
|
|
2950
|
-
...process2.env,
|
|
2951
|
-
GIT_PAGER: "cat",
|
|
2952
|
-
PAGER: "cat",
|
|
2953
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
2954
|
-
};
|
|
2955
|
-
async function detectTeamStatus(projectRoot) {
|
|
2956
|
-
try {
|
|
2957
|
-
await access2(path10.join(projectRoot, ".git"));
|
|
2958
|
-
const inside = await runGit(projectRoot, [
|
|
2959
|
-
"rev-parse",
|
|
2960
|
-
"--is-inside-work-tree"
|
|
2961
|
-
]);
|
|
2962
|
-
if (inside.trim() !== "true") return NO_TEAM;
|
|
2963
|
-
const [allEmails, recentEmails, remotes] = await Promise.all([
|
|
2964
|
-
runGit(projectRoot, ["log", "--all", "--format=%ae"]),
|
|
2965
|
-
runGit(projectRoot, ["log", "--since=30 days ago", "--format=%ae"]),
|
|
2966
|
-
runGit(projectRoot, ["remote", "-v"])
|
|
2786
|
+
async function checkPlatformReadiness(rootDir, platform) {
|
|
2787
|
+
if (platform === "claude-code") {
|
|
2788
|
+
const [hasSoloSkill, registered, hasHookFiles] = await Promise.all([
|
|
2789
|
+
fileMatches(
|
|
2790
|
+
path8.join(rootDir, ".claude", "skills", "solo", "SKILL.md"),
|
|
2791
|
+
(content) => isGeneratedClaudeSkill(content, "solo")
|
|
2792
|
+
),
|
|
2793
|
+
claudeHooksRegistered(rootDir),
|
|
2794
|
+
pathsExist([
|
|
2795
|
+
path8.join(rootDir, ".mancode", "hooks", "session-start.mjs"),
|
|
2796
|
+
path8.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
|
|
2797
|
+
])
|
|
2967
2798
|
]);
|
|
2968
|
-
const
|
|
2969
|
-
const
|
|
2970
|
-
const
|
|
2799
|
+
const present = hasSoloSkill && registered && hasHookFiles;
|
|
2800
|
+
const minimal = await isPlatformMinimal(rootDir, "claude-code");
|
|
2801
|
+
const fullContentReady = minimal ? true : await claudeFullContentReady(rootDir);
|
|
2971
2802
|
return {
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2803
|
+
present,
|
|
2804
|
+
ready: present && fullContentReady,
|
|
2805
|
+
target: ".claude/",
|
|
2806
|
+
readyDetail: present && fullContentReady ? minimal ? "solo skill and hooks ready" : "skills, agents, and hooks ready" : "skills, agents, hook files, or hook registration are incomplete"
|
|
2976
2807
|
};
|
|
2977
|
-
} catch {
|
|
2978
|
-
return NO_TEAM;
|
|
2979
2808
|
}
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
}
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
2809
|
+
if (platform === "cursor") {
|
|
2810
|
+
const hasCoreRules = await allManagedSkills(
|
|
2811
|
+
MANCODE_CURSOR_CORE_RULE_FILES.map(
|
|
2812
|
+
(file) => path8.join(rootDir, ".cursor", "rules", file)
|
|
2813
|
+
),
|
|
2814
|
+
[CURSOR_RULE_MANAGED_MARKER]
|
|
2815
|
+
);
|
|
2816
|
+
if (await isPlatformMinimal(rootDir, "cursor")) {
|
|
2817
|
+
return {
|
|
2818
|
+
present: hasCoreRules,
|
|
2819
|
+
ready: hasCoreRules,
|
|
2820
|
+
target: ".cursor/rules/",
|
|
2821
|
+
readyDetail: "mancode core rules present"
|
|
2822
|
+
};
|
|
2823
|
+
}
|
|
2824
|
+
const [hasRules, hasCommands] = await Promise.all([
|
|
2825
|
+
allManagedSkills(
|
|
2826
|
+
MANCODE_CURSOR_RULE_FILES.map(
|
|
2827
|
+
(file) => path8.join(rootDir, ".cursor", "rules", file)
|
|
2828
|
+
),
|
|
2829
|
+
[CURSOR_RULE_MANAGED_MARKER]
|
|
2830
|
+
),
|
|
2831
|
+
allManagedSkills(
|
|
2832
|
+
MODE_NAMES.map(
|
|
2833
|
+
(mode) => path8.join(rootDir, ".cursor", "commands", `${mode}.md`)
|
|
2834
|
+
),
|
|
2835
|
+
[MODE_FILE_MANAGED_MARKER]
|
|
2836
|
+
)
|
|
2837
|
+
]);
|
|
2838
|
+
return {
|
|
2839
|
+
present: hasCoreRules,
|
|
2840
|
+
ready: hasRules && hasCommands,
|
|
2841
|
+
target: ".cursor/rules/ + .cursor/commands/",
|
|
2842
|
+
readyDetail: hasRules && hasCommands ? "mancode rules and commands present" : "mode rules or commands are missing, incomplete, or user-authored"
|
|
2843
|
+
};
|
|
2844
|
+
}
|
|
2845
|
+
if (platform === "codex") {
|
|
2846
|
+
const hasBlock2 = await fileHasManagedBlock(path8.join(rootDir, "AGENTS.md"));
|
|
2847
|
+
if (!hasBlock2) {
|
|
2848
|
+
return {
|
|
2849
|
+
present: false,
|
|
2850
|
+
ready: false,
|
|
2851
|
+
target: "AGENTS.md",
|
|
2852
|
+
readyDetail: "managed block missing"
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
if (await isPlatformMinimal(rootDir, "codex")) {
|
|
2856
|
+
return {
|
|
2857
|
+
present: true,
|
|
2858
|
+
ready: true,
|
|
2859
|
+
target: "AGENTS.md",
|
|
2860
|
+
readyDetail: "managed block present"
|
|
2861
|
+
};
|
|
2862
|
+
}
|
|
2863
|
+
const skillsDir = path8.join(rootDir, ".agents", "skills");
|
|
2864
|
+
const hasSkills = await allManagedSkills(
|
|
2865
|
+
MODE_NAMES.map((mode) => path8.join(skillsDir, mode, "SKILL.md")),
|
|
2866
|
+
MANCODE_AGENT_SKILL_MARKERS
|
|
2867
|
+
);
|
|
2868
|
+
return {
|
|
2869
|
+
present: true,
|
|
2870
|
+
ready: hasSkills,
|
|
2871
|
+
target: "AGENTS.md + .agents/skills/",
|
|
2872
|
+
readyDetail: hasSkills ? "managed block and skills present" : "mode skills are missing, incomplete, or user-authored"
|
|
2873
|
+
};
|
|
2874
|
+
}
|
|
2875
|
+
if (platform === "zcode") {
|
|
2876
|
+
const hasBlock2 = await fileHasManagedBlock(
|
|
2877
|
+
path8.join(rootDir, "AGENTS.md"),
|
|
2878
|
+
ZCODE_MANCODE_START_MARKER,
|
|
2879
|
+
ZCODE_MANCODE_END_MARKER
|
|
2880
|
+
);
|
|
2881
|
+
if (!hasBlock2) {
|
|
2882
|
+
return {
|
|
2883
|
+
present: false,
|
|
2884
|
+
ready: false,
|
|
2885
|
+
target: "AGENTS.md",
|
|
2886
|
+
readyDetail: "managed block missing"
|
|
2887
|
+
};
|
|
2888
|
+
}
|
|
2889
|
+
if (await isPlatformMinimal(rootDir, "zcode")) {
|
|
2890
|
+
return {
|
|
2891
|
+
present: true,
|
|
2892
|
+
ready: true,
|
|
2893
|
+
target: "AGENTS.md",
|
|
2894
|
+
readyDetail: "managed block present"
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2897
|
+
const skillsDir = path8.join(rootDir, ".agents", "skills");
|
|
2898
|
+
const hasSkills = await allManagedSkills(
|
|
2899
|
+
MODE_NAMES.map((mode) => path8.join(skillsDir, mode, "SKILL.md")),
|
|
2900
|
+
MANCODE_AGENT_SKILL_MARKERS
|
|
2901
|
+
);
|
|
2902
|
+
return {
|
|
2903
|
+
present: true,
|
|
2904
|
+
ready: hasSkills,
|
|
2905
|
+
target: "AGENTS.md + .agents/skills/",
|
|
2906
|
+
readyDetail: hasSkills ? "managed block and skills present" : "mode skills are missing, incomplete, or user-authored"
|
|
2907
|
+
};
|
|
2908
|
+
}
|
|
2909
|
+
const hasBlock = await fileHasManagedBlock(
|
|
2910
|
+
path8.join(rootDir, ".github", "copilot-instructions.md")
|
|
2911
|
+
);
|
|
2912
|
+
if (!hasBlock) {
|
|
2913
|
+
return {
|
|
2914
|
+
present: false,
|
|
2915
|
+
ready: false,
|
|
2916
|
+
target: ".github/copilot-instructions.md",
|
|
2917
|
+
readyDetail: "managed block missing"
|
|
2918
|
+
};
|
|
2919
|
+
}
|
|
2920
|
+
if (!await isPlatformMinimal(rootDir, "copilot")) {
|
|
2921
|
+
const promptsDir = path8.join(rootDir, ".github", "prompts");
|
|
2922
|
+
const hasPrompts = await allManagedSkills(
|
|
2923
|
+
MODE_NAMES.map((mode) => path8.join(promptsDir, `${mode}.prompt.md`)),
|
|
2924
|
+
[MODE_FILE_MANAGED_MARKER]
|
|
2925
|
+
);
|
|
2926
|
+
return {
|
|
2927
|
+
present: true,
|
|
2928
|
+
ready: hasPrompts,
|
|
2929
|
+
target: ".github/copilot-instructions.md + .github/prompts/",
|
|
2930
|
+
readyDetail: hasPrompts ? "managed block and prompts present" : "mode prompts are missing, incomplete, or user-authored"
|
|
2931
|
+
};
|
|
2932
|
+
}
|
|
2933
|
+
return {
|
|
2934
|
+
present: true,
|
|
2935
|
+
ready: true,
|
|
2936
|
+
target: ".github/copilot-instructions.md",
|
|
2937
|
+
readyDetail: "managed block present"
|
|
2938
|
+
};
|
|
2939
|
+
}
|
|
2940
|
+
function describeStatus(installed, readiness) {
|
|
2941
|
+
if (installed && readiness.present) return readiness.readyDetail;
|
|
2942
|
+
if (!installed && readiness.present) {
|
|
2943
|
+
return "target files present but platform is not recorded in config";
|
|
2944
|
+
}
|
|
2945
|
+
return "missing generated files or registration";
|
|
2946
|
+
}
|
|
2947
|
+
async function allManagedSkills(paths, markers) {
|
|
2948
|
+
const results = await Promise.all(
|
|
2949
|
+
paths.map((filePath) => fileHasAnyMarker(filePath, markers))
|
|
2950
|
+
);
|
|
2951
|
+
return results.every(Boolean);
|
|
2952
|
+
}
|
|
2953
|
+
async function claudeFullContentReady(rootDir) {
|
|
2954
|
+
const skillChecks = MVP2_SKILLS.map(
|
|
2955
|
+
(skill) => fileMatches(
|
|
2956
|
+
path8.join(rootDir, ".claude", "skills", skill.name, "SKILL.md"),
|
|
2957
|
+
(content) => isGeneratedClaudeSkill(content, skill.name)
|
|
2958
|
+
)
|
|
2959
|
+
);
|
|
2960
|
+
const agentChecks = ALL_AGENTS.map(
|
|
2961
|
+
(agent) => fileMatches(
|
|
2962
|
+
path8.join(rootDir, ".claude", "agents", `${agent.name}.md`),
|
|
2963
|
+
(content) => isGeneratedClaudeAgent(content, agent.name)
|
|
2964
|
+
)
|
|
2965
|
+
);
|
|
2966
|
+
const results = await Promise.all([...skillChecks, ...agentChecks]);
|
|
2967
|
+
return results.every(Boolean);
|
|
2968
|
+
}
|
|
2969
|
+
async function fileMatches(filePath, predicate) {
|
|
2970
|
+
try {
|
|
2971
|
+
return predicate(await fs.readFile(filePath, "utf-8"));
|
|
2972
|
+
} catch {
|
|
2973
|
+
return false;
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
async function pathsExist(paths) {
|
|
2977
|
+
return (await Promise.all(paths.map(pathExists2))).every(Boolean);
|
|
2978
|
+
}
|
|
2979
|
+
async function fileHasAnyMarker(filePath, needles) {
|
|
2980
|
+
try {
|
|
2981
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
2982
|
+
return needles.some((needle) => content.includes(needle));
|
|
2983
|
+
} catch {
|
|
2984
|
+
return false;
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
async function isPlatformMinimal(rootDir, platform) {
|
|
2988
|
+
try {
|
|
2989
|
+
const raw = await fs.readFile(
|
|
2990
|
+
path8.join(rootDir, ".mancode", "config.json"),
|
|
2991
|
+
"utf-8"
|
|
2992
|
+
);
|
|
2993
|
+
const config = JSON.parse(raw);
|
|
2994
|
+
if (!isRecord3(config.platformOptions)) return false;
|
|
2995
|
+
const options = config.platformOptions[platform];
|
|
2996
|
+
return isRecord3(options) && options.minimal === true;
|
|
2997
|
+
} catch {
|
|
2998
|
+
return false;
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
async function fileHasManagedBlock(filePath, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
|
|
3002
|
+
try {
|
|
3003
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
3004
|
+
return hasManagedBlock(content, startMarker, endMarker);
|
|
3005
|
+
} catch {
|
|
3006
|
+
return false;
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
async function claudeHooksRegistered(rootDir) {
|
|
3010
|
+
try {
|
|
3011
|
+
const raw = await fs.readFile(
|
|
3012
|
+
path8.join(rootDir, ".claude", "settings.json"),
|
|
3013
|
+
"utf-8"
|
|
3014
|
+
);
|
|
3015
|
+
const settings = JSON.parse(raw);
|
|
3016
|
+
return hooksRegistered(settings);
|
|
3017
|
+
} catch {
|
|
3018
|
+
return false;
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
function hooksRegistered(settings) {
|
|
3022
|
+
if (!isRecord3(settings)) return false;
|
|
3023
|
+
const hooks = settings.hooks;
|
|
3024
|
+
if (!isRecord3(hooks)) return false;
|
|
3025
|
+
return hasHookCommand(hooks.SessionStart, ".mancode/hooks/session-start.mjs") && hasHookCommand(
|
|
3026
|
+
hooks.UserPromptSubmit,
|
|
3027
|
+
".mancode/hooks/user-prompt-submit.mjs"
|
|
3028
|
+
);
|
|
3029
|
+
}
|
|
3030
|
+
function hasHookCommand(value, needle) {
|
|
3031
|
+
if (!Array.isArray(value)) return false;
|
|
3032
|
+
return value.some((group) => {
|
|
3033
|
+
if (!isRecord3(group) || !Array.isArray(group.hooks)) return false;
|
|
3034
|
+
return group.hooks.some((hook) => {
|
|
3035
|
+
if (!isRecord3(hook) || typeof hook.command !== "string") return false;
|
|
3036
|
+
return hook.command.includes(needle);
|
|
3037
|
+
});
|
|
3038
|
+
});
|
|
3039
|
+
}
|
|
3040
|
+
async function pathExists2(p) {
|
|
3041
|
+
try {
|
|
3042
|
+
await fs.access(p);
|
|
3043
|
+
return true;
|
|
3044
|
+
} catch {
|
|
3045
|
+
return false;
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
function isRecord3(value) {
|
|
3049
|
+
return typeof value === "object" && value !== null;
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
// src/installers/codex.ts
|
|
3053
|
+
import { writeFile as writeFile7 } from "fs/promises";
|
|
3054
|
+
import path9 from "path";
|
|
3055
|
+
async function installCodex(projectRoot, options) {
|
|
3056
|
+
await installMancodeCore(projectRoot);
|
|
3057
|
+
const agentsPath = path9.join(projectRoot, "AGENTS.md");
|
|
3058
|
+
const existing = await readTextIfExists(agentsPath);
|
|
3059
|
+
const sharedContent = await generateSharedContent(projectRoot, {
|
|
3060
|
+
platform: "codex",
|
|
3061
|
+
displayName: "Codex (ChatGPT desktop/CLI)",
|
|
3062
|
+
capabilities: {
|
|
3063
|
+
slashCommands: "partial",
|
|
3064
|
+
subagents: false,
|
|
3065
|
+
hooks: false,
|
|
3066
|
+
skills: "agents-skills"
|
|
3067
|
+
},
|
|
3068
|
+
minimal: options.minimal,
|
|
3069
|
+
techStack: options.techStack,
|
|
3070
|
+
uiLibrary: options.uiLibrary,
|
|
3071
|
+
projectProfile: options.projectProfile
|
|
3072
|
+
});
|
|
3073
|
+
const block = [
|
|
3074
|
+
DEFAULT_MANCODE_START_MARKER,
|
|
3075
|
+
"<!-- Managed by mancode. Do not edit this block manually. -->",
|
|
3076
|
+
"",
|
|
3077
|
+
"# mancode Configuration",
|
|
3078
|
+
"",
|
|
3079
|
+
sharedContent.trim(),
|
|
3080
|
+
DEFAULT_MANCODE_END_MARKER
|
|
3081
|
+
].join("\n");
|
|
3082
|
+
await writeFile7(agentsPath, replaceManagedBlock(existing, block), "utf-8");
|
|
3083
|
+
await installCodexSkills(projectRoot, options.minimal ?? false);
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
// src/installers/copilot.ts
|
|
3087
|
+
import { mkdir as mkdir6, writeFile as writeFile8 } from "fs/promises";
|
|
3088
|
+
import path10 from "path";
|
|
3089
|
+
async function installCopilot(projectRoot, options) {
|
|
3090
|
+
await installMancodeCore(projectRoot);
|
|
3091
|
+
const githubDir = path10.join(projectRoot, ".github");
|
|
3092
|
+
await mkdir6(githubDir, { recursive: true });
|
|
3093
|
+
const instructionsPath = path10.join(githubDir, "copilot-instructions.md");
|
|
3094
|
+
const existing = await readTextIfExists(instructionsPath);
|
|
3095
|
+
const sharedContent = await generateSharedContent(projectRoot, {
|
|
3096
|
+
platform: "copilot",
|
|
3097
|
+
displayName: "GitHub Copilot",
|
|
3098
|
+
capabilities: {
|
|
3099
|
+
slashCommands: "none",
|
|
3100
|
+
subagents: false,
|
|
3101
|
+
hooks: false,
|
|
3102
|
+
skills: "instructions"
|
|
3103
|
+
},
|
|
3104
|
+
minimal: true,
|
|
3105
|
+
techStack: options.techStack,
|
|
3106
|
+
uiLibrary: options.uiLibrary,
|
|
3107
|
+
projectProfile: options.projectProfile
|
|
3108
|
+
});
|
|
3109
|
+
const sections = [
|
|
3110
|
+
DEFAULT_MANCODE_START_MARKER,
|
|
3111
|
+
"<!-- Managed by mancode. Do not edit this block manually. -->",
|
|
3112
|
+
"",
|
|
3113
|
+
"# mancode for GitHub Copilot",
|
|
3114
|
+
"",
|
|
3115
|
+
sharedContent.trim()
|
|
3116
|
+
];
|
|
3117
|
+
if (!options.minimal) {
|
|
3118
|
+
sections.push("", renderCopilotPromptConventions());
|
|
3119
|
+
}
|
|
3120
|
+
sections.push(DEFAULT_MANCODE_END_MARKER);
|
|
3121
|
+
await writeFile8(
|
|
3122
|
+
instructionsPath,
|
|
3123
|
+
replaceManagedBlock(existing, sections.join("\n")),
|
|
3124
|
+
"utf-8"
|
|
3125
|
+
);
|
|
3126
|
+
await installCopilotPrompts(projectRoot, options.minimal ?? false);
|
|
3127
|
+
}
|
|
3128
|
+
function renderCopilotPromptConventions() {
|
|
3129
|
+
return [
|
|
3130
|
+
"## mancode Prompt Conventions",
|
|
3131
|
+
"",
|
|
3132
|
+
"GitHub Copilot does not provide native mancode slash commands, hooks, or isolated subagents. Treat these names as user prompt conventions:",
|
|
3133
|
+
"",
|
|
3134
|
+
"- man: use progressive research, planning, implementation, verification, and bounded risk-based review.",
|
|
3135
|
+
"- manba: diagnose bugs and validate real user flows or regressions.",
|
|
3136
|
+
"- manteam: read team memory and write handoff-friendly summaries.",
|
|
3137
|
+
"- manps: prefer `mancode manps [area]` before cleanup.",
|
|
3138
|
+
"- mansolo: exit any active mode and return to default solo behavior.",
|
|
3139
|
+
"",
|
|
3140
|
+
"These correspond to prompt files in `.github/prompts/`. Select the matching prompt to activate a mode."
|
|
3141
|
+
].join("\n");
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3144
|
+
// src/installers/registry.ts
|
|
3145
|
+
var PLATFORM_INSTALLERS = {
|
|
3146
|
+
"claude-code": {
|
|
3147
|
+
name: "claude-code",
|
|
3148
|
+
displayName: "Claude Code",
|
|
3149
|
+
capabilities: {
|
|
3150
|
+
slashCommands: "native",
|
|
3151
|
+
subagents: true,
|
|
3152
|
+
hooks: true,
|
|
3153
|
+
skills: "native"
|
|
3154
|
+
},
|
|
3155
|
+
install: installClaudeCode
|
|
3156
|
+
},
|
|
3157
|
+
cursor: {
|
|
3158
|
+
name: "cursor",
|
|
3159
|
+
displayName: "Cursor",
|
|
3160
|
+
capabilities: {
|
|
3161
|
+
slashCommands: "native",
|
|
3162
|
+
subagents: false,
|
|
3163
|
+
hooks: false,
|
|
3164
|
+
skills: "rules"
|
|
3165
|
+
},
|
|
3166
|
+
install: installCursor
|
|
3167
|
+
},
|
|
3168
|
+
codex: {
|
|
3169
|
+
name: "codex",
|
|
3170
|
+
displayName: "Codex (ChatGPT desktop/CLI)",
|
|
3171
|
+
capabilities: {
|
|
3172
|
+
slashCommands: "partial",
|
|
3173
|
+
subagents: false,
|
|
3174
|
+
hooks: false,
|
|
3175
|
+
skills: "agents-skills"
|
|
3176
|
+
},
|
|
3177
|
+
install: installCodex
|
|
3178
|
+
},
|
|
3179
|
+
copilot: {
|
|
3180
|
+
name: "copilot",
|
|
3181
|
+
displayName: "GitHub Copilot",
|
|
3182
|
+
capabilities: {
|
|
3183
|
+
slashCommands: "none",
|
|
3184
|
+
subagents: false,
|
|
3185
|
+
hooks: false,
|
|
3186
|
+
skills: "instructions"
|
|
3187
|
+
},
|
|
3188
|
+
install: installCopilot
|
|
3189
|
+
},
|
|
3190
|
+
zcode: {
|
|
3191
|
+
name: "zcode",
|
|
3192
|
+
displayName: "ZCode",
|
|
3193
|
+
capabilities: {
|
|
3194
|
+
slashCommands: "partial",
|
|
3195
|
+
subagents: false,
|
|
3196
|
+
hooks: false,
|
|
3197
|
+
skills: "agents-skills"
|
|
3198
|
+
},
|
|
3199
|
+
install: installZcode
|
|
3200
|
+
}
|
|
3201
|
+
};
|
|
3202
|
+
function getPlatformInstaller(platform) {
|
|
3203
|
+
if (!isPlatformName(platform)) return null;
|
|
3204
|
+
return PLATFORM_INSTALLERS[platform];
|
|
3205
|
+
}
|
|
3206
|
+
function getPlatformInstallers() {
|
|
3207
|
+
return Object.values(PLATFORM_INSTALLERS);
|
|
3208
|
+
}
|
|
3209
|
+
function formatPlatformName(platform) {
|
|
3210
|
+
return getPlatformInstaller(platform)?.displayName ?? platform;
|
|
3211
|
+
}
|
|
3212
|
+
function isPlatformName(platform) {
|
|
3213
|
+
return Object.prototype.hasOwnProperty.call(PLATFORM_INSTALLERS, platform);
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
// src/system/detect-team.ts
|
|
3217
|
+
import { execFile } from "child_process";
|
|
3218
|
+
import { access as access2 } from "fs/promises";
|
|
3219
|
+
import path11 from "path";
|
|
3220
|
+
import process2 from "process";
|
|
3221
|
+
import { promisify } from "util";
|
|
3222
|
+
var execFileAsync = promisify(execFile);
|
|
3223
|
+
var NO_TEAM = {
|
|
3224
|
+
isTeam: false,
|
|
3225
|
+
contributors: 1,
|
|
3226
|
+
recentActive: 1,
|
|
3227
|
+
hasRemote: false
|
|
3228
|
+
};
|
|
3229
|
+
var REMOTE_PATTERN = /github\.com|gitlab\.com|bitbucket\.org/;
|
|
3230
|
+
var GIT_ENV = {
|
|
3231
|
+
...process2.env,
|
|
3232
|
+
GIT_PAGER: "cat",
|
|
3233
|
+
PAGER: "cat",
|
|
3234
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
3235
|
+
};
|
|
3236
|
+
async function detectTeamStatus(projectRoot) {
|
|
3237
|
+
try {
|
|
3238
|
+
await access2(path11.join(projectRoot, ".git"));
|
|
3239
|
+
const inside = await runGit(projectRoot, [
|
|
3240
|
+
"rev-parse",
|
|
3241
|
+
"--is-inside-work-tree"
|
|
3242
|
+
]);
|
|
3243
|
+
if (inside.trim() !== "true") return NO_TEAM;
|
|
3244
|
+
const [allEmails, recentEmails, remotes] = await Promise.all([
|
|
3245
|
+
runGit(projectRoot, ["log", "--all", "--format=%ae"]),
|
|
3246
|
+
runGit(projectRoot, ["log", "--since=30 days ago", "--format=%ae"]),
|
|
3247
|
+
runGit(projectRoot, ["remote", "-v"])
|
|
3248
|
+
]);
|
|
3249
|
+
const contributors = countUniqueLines(allEmails);
|
|
3250
|
+
const recentActive = countUniqueLines(recentEmails);
|
|
3251
|
+
const hasRemote = REMOTE_PATTERN.test(remotes);
|
|
3252
|
+
return {
|
|
3253
|
+
isTeam: contributors > 1 && hasRemote && recentActive > 1,
|
|
3254
|
+
contributors: contributors || 1,
|
|
3255
|
+
recentActive: recentActive || 1,
|
|
3256
|
+
hasRemote
|
|
3257
|
+
};
|
|
3258
|
+
} catch {
|
|
3259
|
+
return NO_TEAM;
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
async function runGit(cwd, args) {
|
|
3263
|
+
const { stdout: stdout2 } = await execFileAsync("git", args, {
|
|
3264
|
+
cwd,
|
|
3265
|
+
encoding: "utf-8",
|
|
3266
|
+
env: GIT_ENV,
|
|
3267
|
+
windowsHide: true
|
|
3268
|
+
});
|
|
3269
|
+
return stdout2;
|
|
3270
|
+
}
|
|
3271
|
+
function countUniqueLines(output) {
|
|
3272
|
+
return new Set(
|
|
3273
|
+
output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
|
|
3274
|
+
).size;
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3277
|
+
// src/system/detect.ts
|
|
3278
|
+
import { execFile as execFile2 } from "child_process";
|
|
3279
|
+
import process3 from "process";
|
|
3280
|
+
import { promisify as promisify2 } from "util";
|
|
3000
3281
|
var execFileAsync2 = promisify2(execFile2);
|
|
3001
3282
|
async function detectSystemDeps(env = process3.env) {
|
|
3002
3283
|
try {
|
|
@@ -3012,8 +3293,8 @@ async function detectSystemDeps(env = process3.env) {
|
|
|
3012
3293
|
|
|
3013
3294
|
// src/system/init-onboarding.ts
|
|
3014
3295
|
import { execFileSync } from "child_process";
|
|
3015
|
-
import { promises as
|
|
3016
|
-
import
|
|
3296
|
+
import { promises as fs2 } from "fs";
|
|
3297
|
+
import path12 from "path";
|
|
3017
3298
|
import process4 from "process";
|
|
3018
3299
|
import { stdin, stdout } from "process";
|
|
3019
3300
|
import { createInterface } from "readline/promises";
|
|
@@ -3118,7 +3399,7 @@ async function detectPlatformHints(rootDir, environment = process4.env) {
|
|
|
3118
3399
|
hints.add("copilot");
|
|
3119
3400
|
const exists = async (relative) => {
|
|
3120
3401
|
try {
|
|
3121
|
-
await
|
|
3402
|
+
await fs2.access(path12.join(rootDir, relative));
|
|
3122
3403
|
return true;
|
|
3123
3404
|
} catch {
|
|
3124
3405
|
return false;
|
|
@@ -3194,7 +3475,7 @@ function createTerminalPrompter() {
|
|
|
3194
3475
|
|
|
3195
3476
|
// src/system/project-profile.ts
|
|
3196
3477
|
import { readFile as readFile7, readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
3197
|
-
import
|
|
3478
|
+
import path13 from "path";
|
|
3198
3479
|
var PROJECT_MANIFESTS = [
|
|
3199
3480
|
"package.json",
|
|
3200
3481
|
"pyproject.toml",
|
|
@@ -3227,12 +3508,12 @@ async function detectProjectProfile(projectRoot) {
|
|
|
3227
3508
|
"data",
|
|
3228
3509
|
"notebooks"
|
|
3229
3510
|
]);
|
|
3230
|
-
const packageJson = await readJson2(
|
|
3511
|
+
const packageJson = await readJson2(path13.join(projectRoot, "package.json"));
|
|
3231
3512
|
const [pubspec, pythonManifests] = await Promise.all([
|
|
3232
|
-
readText(
|
|
3513
|
+
readText(path13.join(projectRoot, "pubspec.yaml")),
|
|
3233
3514
|
Promise.all([
|
|
3234
|
-
readText(
|
|
3235
|
-
readText(
|
|
3515
|
+
readText(path13.join(projectRoot, "pyproject.toml")),
|
|
3516
|
+
readText(path13.join(projectRoot, "requirements.txt"))
|
|
3236
3517
|
]).then((parts) => parts.filter(Boolean).join("\n"))
|
|
3237
3518
|
]);
|
|
3238
3519
|
const deps = Object.keys({
|
|
@@ -3241,7 +3522,7 @@ async function detectProjectProfile(projectRoot) {
|
|
|
3241
3522
|
...packageJson?.peerDependencies
|
|
3242
3523
|
});
|
|
3243
3524
|
const flutter = isFlutterProject(pubspec);
|
|
3244
|
-
const shadcn = entries.includes("components.json") || deps.some((dep) => dep.startsWith("@radix-ui/")) && await isDirectory(
|
|
3525
|
+
const shadcn = entries.includes("components.json") || deps.some((dep) => dep.startsWith("@radix-ui/")) && await isDirectory(path13.join(projectRoot, "src", "components", "ui"));
|
|
3245
3526
|
const languages = inferLanguages(manifests, entries);
|
|
3246
3527
|
const frameworks = inferFrameworks(
|
|
3247
3528
|
deps,
|
|
@@ -3284,7 +3565,7 @@ async function existingDirs(root, candidates) {
|
|
|
3284
3565
|
const found = [];
|
|
3285
3566
|
for (const candidate of candidates) {
|
|
3286
3567
|
try {
|
|
3287
|
-
if ((await stat2(
|
|
3568
|
+
if ((await stat2(path13.join(root, candidate))).isDirectory())
|
|
3288
3569
|
found.push(candidate);
|
|
3289
3570
|
} catch {
|
|
3290
3571
|
}
|
|
@@ -3458,8 +3739,8 @@ function isFlutterProject(pubspec) {
|
|
|
3458
3739
|
}
|
|
3459
3740
|
|
|
3460
3741
|
// src/system/scan-aesthetics.ts
|
|
3461
|
-
import { promises as
|
|
3462
|
-
import
|
|
3742
|
+
import { promises as fs3 } from "fs";
|
|
3743
|
+
import path14 from "path";
|
|
3463
3744
|
var MAX_COMPONENT_SCAN_DEPTH = 12;
|
|
3464
3745
|
var MAX_COMPONENT_FILES = 2e3;
|
|
3465
3746
|
async function scanAesthetics(projectRoot, uiLibrary = null) {
|
|
@@ -3472,7 +3753,7 @@ async function scanAesthetics(projectRoot, uiLibrary = null) {
|
|
|
3472
3753
|
const cssScan = await scanCssVariables(projectRoot);
|
|
3473
3754
|
if (configResult) {
|
|
3474
3755
|
sourceFiles.push(configResult.relPath);
|
|
3475
|
-
const content = await
|
|
3756
|
+
const content = await fs3.readFile(configResult.absPath, "utf-8");
|
|
3476
3757
|
const inspectableContent = stripJsComments(content);
|
|
3477
3758
|
const themeBlock = findKeyBlock(inspectableContent, "theme");
|
|
3478
3759
|
if (themeBlock) {
|
|
@@ -3495,7 +3776,7 @@ async function scanAesthetics(projectRoot, uiLibrary = null) {
|
|
|
3495
3776
|
} else if (await hasTailwindDep(projectRoot) || uiLibrary) {
|
|
3496
3777
|
matchLevel = "low";
|
|
3497
3778
|
}
|
|
3498
|
-
if (uiLibrary && await
|
|
3779
|
+
if (uiLibrary && await pathExists3(path14.join(projectRoot, "package.json"))) {
|
|
3499
3780
|
sourceFiles.push("package.json");
|
|
3500
3781
|
}
|
|
3501
3782
|
sourceFiles.push(...cssScan.sourceFiles);
|
|
@@ -3520,8 +3801,8 @@ async function findTailwindConfig(projectRoot) {
|
|
|
3520
3801
|
"tailwind.config.mjs"
|
|
3521
3802
|
];
|
|
3522
3803
|
for (const name of candidates) {
|
|
3523
|
-
const absPath =
|
|
3524
|
-
if (await
|
|
3804
|
+
const absPath = path14.join(projectRoot, name);
|
|
3805
|
+
if (await pathExists3(absPath)) {
|
|
3525
3806
|
return { absPath, relPath: name };
|
|
3526
3807
|
}
|
|
3527
3808
|
}
|
|
@@ -3747,8 +4028,8 @@ async function scanComponents(projectRoot) {
|
|
|
3747
4028
|
const names = /* @__PURE__ */ new Set();
|
|
3748
4029
|
let visitedFiles = 0;
|
|
3749
4030
|
for (const relRoot of roots) {
|
|
3750
|
-
const absRoot =
|
|
3751
|
-
if (!await
|
|
4031
|
+
const absRoot = path14.join(projectRoot, relRoot);
|
|
4032
|
+
if (!await pathExists3(absRoot)) continue;
|
|
3752
4033
|
visitedFiles = await collectComponentNames(absRoot, names, 0, visitedFiles);
|
|
3753
4034
|
if (visitedFiles >= MAX_COMPONENT_FILES) break;
|
|
3754
4035
|
}
|
|
@@ -3761,16 +4042,16 @@ async function collectComponentNames(dir, names, depth, visitedFiles) {
|
|
|
3761
4042
|
let fileCount = visitedFiles;
|
|
3762
4043
|
let entries;
|
|
3763
4044
|
try {
|
|
3764
|
-
entries = await
|
|
4045
|
+
entries = await fs3.readdir(dir);
|
|
3765
4046
|
} catch {
|
|
3766
4047
|
return fileCount;
|
|
3767
4048
|
}
|
|
3768
4049
|
for (const entry of entries) {
|
|
3769
4050
|
if (fileCount >= MAX_COMPONENT_FILES) return fileCount;
|
|
3770
|
-
const abs =
|
|
4051
|
+
const abs = path14.join(dir, entry);
|
|
3771
4052
|
let info;
|
|
3772
4053
|
try {
|
|
3773
|
-
info = await
|
|
4054
|
+
info = await fs3.lstat(abs);
|
|
3774
4055
|
} catch {
|
|
3775
4056
|
continue;
|
|
3776
4057
|
}
|
|
@@ -3788,7 +4069,7 @@ async function collectComponentNames(dir, names, depth, visitedFiles) {
|
|
|
3788
4069
|
""
|
|
3789
4070
|
);
|
|
3790
4071
|
if (base === "index") {
|
|
3791
|
-
base =
|
|
4072
|
+
base = path14.basename(dir);
|
|
3792
4073
|
}
|
|
3793
4074
|
if (base.startsWith(".")) continue;
|
|
3794
4075
|
const componentName = toPascalCase(base);
|
|
@@ -3810,9 +4091,9 @@ async function scanCssVariables(projectRoot) {
|
|
|
3810
4091
|
const variables = {};
|
|
3811
4092
|
const sourceFiles = [];
|
|
3812
4093
|
for (const relPath of candidates) {
|
|
3813
|
-
const absPath =
|
|
3814
|
-
if (!await
|
|
3815
|
-
const content = await
|
|
4094
|
+
const absPath = path14.join(projectRoot, relPath);
|
|
4095
|
+
if (!await pathExists3(absPath)) continue;
|
|
4096
|
+
const content = await fs3.readFile(absPath, "utf-8");
|
|
3816
4097
|
const found = extractCssVariables(content);
|
|
3817
4098
|
if (Object.keys(found).length === 0) continue;
|
|
3818
4099
|
Object.assign(variables, found);
|
|
@@ -3855,8 +4136,8 @@ function toPascalCase(value) {
|
|
|
3855
4136
|
}
|
|
3856
4137
|
async function hasTailwindDep(projectRoot) {
|
|
3857
4138
|
try {
|
|
3858
|
-
const raw = await
|
|
3859
|
-
|
|
4139
|
+
const raw = await fs3.readFile(
|
|
4140
|
+
path14.join(projectRoot, "package.json"),
|
|
3860
4141
|
"utf-8"
|
|
3861
4142
|
);
|
|
3862
4143
|
const pkg = JSON.parse(raw);
|
|
@@ -3870,9 +4151,9 @@ async function hasTailwindDep(projectRoot) {
|
|
|
3870
4151
|
function escapeRegex(s) {
|
|
3871
4152
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3872
4153
|
}
|
|
3873
|
-
async function
|
|
4154
|
+
async function pathExists3(p) {
|
|
3874
4155
|
try {
|
|
3875
|
-
await
|
|
4156
|
+
await fs3.access(p);
|
|
3876
4157
|
return true;
|
|
3877
4158
|
} catch {
|
|
3878
4159
|
return false;
|
|
@@ -3887,9 +4168,9 @@ var EXIT_USER_CANCEL = 3;
|
|
|
3887
4168
|
var EXIT_INIT_FAILED = 5;
|
|
3888
4169
|
var DEFAULT_INIT_PLATFORM = "claude-code";
|
|
3889
4170
|
async function init(rootDir = process5.cwd(), options = {}) {
|
|
3890
|
-
const mancodeDir =
|
|
3891
|
-
const stateFile =
|
|
3892
|
-
const wasInitialized = await
|
|
4171
|
+
const mancodeDir = path15.join(rootDir, ".mancode");
|
|
4172
|
+
const stateFile = path15.join(mancodeDir, "state.json");
|
|
4173
|
+
const wasInitialized = await pathExists4(stateFile);
|
|
3893
4174
|
let mutationSnapshots = [];
|
|
3894
4175
|
let directorySnapshots = [];
|
|
3895
4176
|
const locale = detectInitLocale(options.lang);
|
|
@@ -3925,7 +4206,7 @@ async function init(rootDir = process5.cwd(), options = {}) {
|
|
|
3925
4206
|
)
|
|
3926
4207
|
);
|
|
3927
4208
|
}
|
|
3928
|
-
if (!await
|
|
4209
|
+
if (!await pathExists4(rootDir)) {
|
|
3929
4210
|
console.error(
|
|
3930
4211
|
localize(
|
|
3931
4212
|
locale,
|
|
@@ -3935,26 +4216,28 @@ async function init(rootDir = process5.cwd(), options = {}) {
|
|
|
3935
4216
|
);
|
|
3936
4217
|
return EXIT_NOT_A_PROJECT_DIR;
|
|
3937
4218
|
}
|
|
3938
|
-
const isGitRepo = await
|
|
4219
|
+
const isGitRepo = await pathExists4(path15.join(rootDir, ".git"));
|
|
3939
4220
|
const hasManifest = await hasProjectManifest(rootDir);
|
|
3940
4221
|
let isGenericProject = false;
|
|
3941
4222
|
if (!isGitRepo && !hasManifest) {
|
|
3942
4223
|
const genericSafety = await canInitializeGenericProject(rootDir);
|
|
3943
|
-
if (!genericSafety.ok) {
|
|
4224
|
+
if (!genericSafety.ok && !(wasInitialized && genericSafety.reason === "nonempty")) {
|
|
3944
4225
|
printNotProjectDirectory(rootDir, locale, genericSafety.reason);
|
|
3945
4226
|
return EXIT_NOT_A_PROJECT_DIR;
|
|
3946
4227
|
}
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
if (
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
4228
|
+
if (!wasInitialized) {
|
|
4229
|
+
const prompter = options.prompter ?? (options.interactive ? createTerminalPrompter() : null);
|
|
4230
|
+
const confirmed = options.empty || options.yes ? true : prompter ? await prompter.confirmGenericProject({ rootDir, locale }) : false;
|
|
4231
|
+
if (!confirmed) {
|
|
4232
|
+
if (options.interactive) {
|
|
4233
|
+
console.log(
|
|
4234
|
+
locale === "zh-CN" ? "\u5DF2\u53D6\u6D88\u521D\u59CB\u5316\u3002" : "Initialization cancelled."
|
|
4235
|
+
);
|
|
4236
|
+
return EXIT_USER_CANCEL;
|
|
4237
|
+
}
|
|
4238
|
+
printNotProjectDirectory(rootDir, locale, "empty");
|
|
4239
|
+
return EXIT_NOT_A_PROJECT_DIR;
|
|
3955
4240
|
}
|
|
3956
|
-
printNotProjectDirectory(rootDir, locale, "empty");
|
|
3957
|
-
return EXIT_NOT_A_PROJECT_DIR;
|
|
3958
4241
|
}
|
|
3959
4242
|
isGenericProject = true;
|
|
3960
4243
|
}
|
|
@@ -4009,9 +4292,12 @@ async function init(rootDir = process5.cwd(), options = {}) {
|
|
|
4009
4292
|
);
|
|
4010
4293
|
}
|
|
4011
4294
|
const existingPlatform = wasInitialized ? await readExistingInitPlatform(stateFile) : null;
|
|
4295
|
+
const existingConfig = wasInitialized ? await readExistingInitPreferences(mancodeDir) : { platforms: [], minimalByPlatform: {} };
|
|
4296
|
+
const recoverablePlatforms = wasInitialized && existingConfig.platforms.length === 0 ? await detectManagedInitPlatforms(rootDir) : [];
|
|
4297
|
+
const forceReinstallPlatforms = options.force && options.platform === void 0 ? existingConfig.platforms.length > 0 ? existingConfig.platforms : recoverablePlatforms : [];
|
|
4012
4298
|
const platformHints = await detectPlatformHints(rootDir);
|
|
4013
4299
|
const selectedPlatforms = await selectInitPlatforms({
|
|
4014
|
-
option: options.platform,
|
|
4300
|
+
option: options.platform ?? (forceReinstallPlatforms.length > 0 ? forceReinstallPlatforms.join(",") : void 0),
|
|
4015
4301
|
existingPlatform,
|
|
4016
4302
|
hints: platformHints,
|
|
4017
4303
|
interactive: options.interactive,
|
|
@@ -4048,13 +4334,17 @@ async function init(rootDir = process5.cwd(), options = {}) {
|
|
|
4048
4334
|
const managedFiles = getInitManagedFilePaths(rootDir, selectedPlatforms);
|
|
4049
4335
|
mutationSnapshots = await snapshotFiles(managedFiles);
|
|
4050
4336
|
directorySnapshots = await snapshotDirectories(managedFiles, [
|
|
4051
|
-
|
|
4052
|
-
|
|
4337
|
+
path15.join(mancodeDir, "workflows"),
|
|
4338
|
+
path15.join(mancodeDir, "preseason-reports")
|
|
4053
4339
|
]);
|
|
4054
4340
|
const team = await detectTeamStatus(rootDir);
|
|
4055
|
-
const
|
|
4056
|
-
|
|
4057
|
-
|
|
4341
|
+
const platformMinimal = Object.fromEntries(
|
|
4342
|
+
selectedPlatforms.map((platform) => [
|
|
4343
|
+
platform,
|
|
4344
|
+
existingConfig.minimalByPlatform[platform] ?? false
|
|
4345
|
+
])
|
|
4346
|
+
);
|
|
4347
|
+
const existingTeamMode = existingConfig.forceTeamMode === true ? "on" : existingConfig.teamMode ?? "auto";
|
|
4058
4348
|
const teamModeEnabled = options.team ?? (existingTeamMode === "on" ? true : existingTeamMode === "off" ? false : team.isTeam);
|
|
4059
4349
|
if (options.team === true) {
|
|
4060
4350
|
console.log(
|
|
@@ -4100,12 +4390,12 @@ async function init(rootDir = process5.cwd(), options = {}) {
|
|
|
4100
4390
|
await validateClaudeCodeSettings(rootDir);
|
|
4101
4391
|
}
|
|
4102
4392
|
await installMancodeCore(rootDir);
|
|
4103
|
-
await
|
|
4393
|
+
await fs4.mkdir(mancodeDir, { recursive: true });
|
|
4104
4394
|
const stateContent = `${JSON.stringify(state, null, 2)}
|
|
4105
4395
|
`;
|
|
4106
|
-
await
|
|
4107
|
-
await
|
|
4108
|
-
|
|
4396
|
+
await fs4.writeFile(stateFile, stateContent, "utf-8");
|
|
4397
|
+
await fs4.writeFile(
|
|
4398
|
+
path15.join(mancodeDir, "project-profile.json"),
|
|
4109
4399
|
`${JSON.stringify(profile, null, 2)}
|
|
4110
4400
|
`,
|
|
4111
4401
|
"utf-8"
|
|
@@ -4121,7 +4411,7 @@ async function init(rootDir = process5.cwd(), options = {}) {
|
|
|
4121
4411
|
...Object.fromEntries(
|
|
4122
4412
|
selectedPlatforms.map((platform) => [
|
|
4123
4413
|
platform,
|
|
4124
|
-
{ minimal:
|
|
4414
|
+
{ minimal: platformMinimal[platform] ?? false }
|
|
4125
4415
|
])
|
|
4126
4416
|
)
|
|
4127
4417
|
}
|
|
@@ -4142,12 +4432,12 @@ async function init(rootDir = process5.cwd(), options = {}) {
|
|
|
4142
4432
|
)
|
|
4143
4433
|
);
|
|
4144
4434
|
const tokens = await scanAesthetics(rootDir, uiLibrary);
|
|
4145
|
-
const tokensPath =
|
|
4435
|
+
const tokensPath = path15.join(
|
|
4146
4436
|
mancodeDir,
|
|
4147
4437
|
"aesthetics",
|
|
4148
4438
|
"style-tokens.json"
|
|
4149
4439
|
);
|
|
4150
|
-
await
|
|
4440
|
+
await fs4.writeFile(
|
|
4151
4441
|
tokensPath,
|
|
4152
4442
|
`${JSON.stringify(tokens, null, 2)}
|
|
4153
4443
|
`,
|
|
@@ -4208,7 +4498,7 @@ async function init(rootDir = process5.cwd(), options = {}) {
|
|
|
4208
4498
|
techStack: profileStack,
|
|
4209
4499
|
uiLibrary,
|
|
4210
4500
|
projectProfile: profile,
|
|
4211
|
-
minimal:
|
|
4501
|
+
minimal: platformMinimal[installer.name] ?? false,
|
|
4212
4502
|
force: options.force
|
|
4213
4503
|
});
|
|
4214
4504
|
}
|
|
@@ -4293,16 +4583,16 @@ async function init(rootDir = process5.cwd(), options = {}) {
|
|
|
4293
4583
|
}
|
|
4294
4584
|
}
|
|
4295
4585
|
async function updateConfigOptions(mancodeDir, patch, preserveInstalledPlatforms) {
|
|
4296
|
-
const configPath =
|
|
4586
|
+
const configPath = path15.join(mancodeDir, "config.json");
|
|
4297
4587
|
let config = {};
|
|
4298
4588
|
try {
|
|
4299
|
-
config = JSON.parse(await
|
|
4589
|
+
config = JSON.parse(await fs4.readFile(configPath, "utf-8"));
|
|
4300
4590
|
} catch {
|
|
4301
4591
|
}
|
|
4302
4592
|
const existingPlatforms = Array.isArray(config.platforms) ? config.platforms.filter(
|
|
4303
4593
|
(platform) => typeof platform === "string"
|
|
4304
4594
|
) : [];
|
|
4305
|
-
const existingPlatformOptions =
|
|
4595
|
+
const existingPlatformOptions = isRecord4(config.platformOptions) ? config.platformOptions : {};
|
|
4306
4596
|
const definedPatch = Object.fromEntries(
|
|
4307
4597
|
Object.entries(patch).filter(([, value]) => value !== void 0)
|
|
4308
4598
|
);
|
|
@@ -4316,44 +4606,60 @@ async function updateConfigOptions(mancodeDir, patch, preserveInstalledPlatforms
|
|
|
4316
4606
|
...patch.platformOptions
|
|
4317
4607
|
}
|
|
4318
4608
|
} : definedPatch;
|
|
4319
|
-
await
|
|
4609
|
+
await fs4.writeFile(
|
|
4320
4610
|
configPath,
|
|
4321
4611
|
`${JSON.stringify({ ...config, ...mergedPatch }, null, 2)}
|
|
4322
4612
|
`,
|
|
4323
4613
|
"utf-8"
|
|
4324
4614
|
);
|
|
4325
4615
|
}
|
|
4326
|
-
async function readExistingInitPreferences(mancodeDir
|
|
4616
|
+
async function readExistingInitPreferences(mancodeDir) {
|
|
4327
4617
|
try {
|
|
4328
4618
|
const config = JSON.parse(
|
|
4329
|
-
await
|
|
4619
|
+
await fs4.readFile(path15.join(mancodeDir, "config.json"), "utf-8")
|
|
4330
4620
|
);
|
|
4331
|
-
const preferences = {
|
|
4621
|
+
const preferences = {
|
|
4622
|
+
platforms: Array.isArray(config.platforms) ? config.platforms.filter(
|
|
4623
|
+
(platform) => typeof platform === "string" && getPlatformInstaller(platform) !== null
|
|
4624
|
+
) : [],
|
|
4625
|
+
minimalByPlatform: {}
|
|
4626
|
+
};
|
|
4332
4627
|
if (typeof config.forceTeamMode === "boolean") {
|
|
4333
4628
|
preferences.forceTeamMode = config.forceTeamMode;
|
|
4334
4629
|
}
|
|
4335
4630
|
if (config.teamMode === "auto" || config.teamMode === "on" || config.teamMode === "off") {
|
|
4336
4631
|
preferences.teamMode = config.teamMode;
|
|
4337
4632
|
}
|
|
4338
|
-
if (
|
|
4339
|
-
const
|
|
4340
|
-
|
|
4341
|
-
|
|
4633
|
+
if (isRecord4(config.platformOptions)) {
|
|
4634
|
+
for (const platform of preferences.platforms) {
|
|
4635
|
+
const platformOptions = config.platformOptions[platform];
|
|
4636
|
+
if (isRecord4(platformOptions) && platformOptions.minimal === true) {
|
|
4637
|
+
preferences.minimalByPlatform[platform] = true;
|
|
4638
|
+
}
|
|
4342
4639
|
}
|
|
4343
4640
|
}
|
|
4344
4641
|
return preferences;
|
|
4345
4642
|
} catch {
|
|
4346
|
-
return {};
|
|
4643
|
+
return { platforms: [], minimalByPlatform: {} };
|
|
4347
4644
|
}
|
|
4348
4645
|
}
|
|
4349
4646
|
async function readExistingInitPlatform(stateFile) {
|
|
4350
4647
|
try {
|
|
4351
|
-
const state = JSON.parse(await
|
|
4648
|
+
const state = JSON.parse(await fs4.readFile(stateFile, "utf-8"));
|
|
4352
4649
|
return typeof state.platform === "string" && getPlatformInstaller(state.platform) ? state.platform : null;
|
|
4353
4650
|
} catch {
|
|
4354
4651
|
return null;
|
|
4355
4652
|
}
|
|
4356
4653
|
}
|
|
4654
|
+
async function detectManagedInitPlatforms(rootDir) {
|
|
4655
|
+
const detected = await Promise.all(
|
|
4656
|
+
getPlatformInstallers().map(async (platform) => ({
|
|
4657
|
+
name: platform.name,
|
|
4658
|
+
present: await isPlatformPresent(rootDir, platform.name)
|
|
4659
|
+
}))
|
|
4660
|
+
);
|
|
4661
|
+
return detected.filter((platform) => platform.present).map(({ name }) => name);
|
|
4662
|
+
}
|
|
4357
4663
|
async function selectInitPlatforms(input) {
|
|
4358
4664
|
if (input.option !== void 0) {
|
|
4359
4665
|
return parsePlatformSelection(input.option);
|
|
@@ -4376,17 +4682,17 @@ async function selectInitPlatforms(input) {
|
|
|
4376
4682
|
}
|
|
4377
4683
|
async function hasProjectManifest(rootDir) {
|
|
4378
4684
|
for (const name of PROJECT_MANIFESTS) {
|
|
4379
|
-
if (await
|
|
4685
|
+
if (await pathExists4(path15.join(rootDir, name))) return true;
|
|
4380
4686
|
}
|
|
4381
4687
|
return false;
|
|
4382
4688
|
}
|
|
4383
4689
|
async function canInitializeGenericProject(rootDir) {
|
|
4384
|
-
const resolved =
|
|
4385
|
-
if (resolved ===
|
|
4690
|
+
const resolved = path15.resolve(rootDir);
|
|
4691
|
+
if (resolved === path15.parse(resolved).root || resolved === path15.resolve(os.homedir())) {
|
|
4386
4692
|
return { ok: false, reason: "unsafe" };
|
|
4387
4693
|
}
|
|
4388
4694
|
try {
|
|
4389
|
-
const entries = await
|
|
4695
|
+
const entries = await fs4.readdir(resolved);
|
|
4390
4696
|
const meaningfulEntries = entries.filter(
|
|
4391
4697
|
(entry) => ![".DS_Store", "Thumbs.db", ".gitkeep"].includes(entry)
|
|
4392
4698
|
);
|
|
@@ -4507,13 +4813,13 @@ function getInitManagedFilePaths(rootDir, platforms) {
|
|
|
4507
4813
|
files.push(`.github/prompts/${mode}.prompt.md`);
|
|
4508
4814
|
}
|
|
4509
4815
|
}
|
|
4510
|
-
return [...new Set(files.map((file) =>
|
|
4816
|
+
return [...new Set(files.map((file) => path15.join(rootDir, file)))];
|
|
4511
4817
|
}
|
|
4512
4818
|
async function snapshotFiles(filePaths) {
|
|
4513
4819
|
return Promise.all(
|
|
4514
4820
|
filePaths.map(async (filePath) => {
|
|
4515
4821
|
try {
|
|
4516
|
-
return { filePath, content: await
|
|
4822
|
+
return { filePath, content: await fs4.readFile(filePath, "utf-8") };
|
|
4517
4823
|
} catch (error) {
|
|
4518
4824
|
if (isNodeError5(error) && error.code === "ENOENT") {
|
|
4519
4825
|
return { filePath, content: null };
|
|
@@ -4526,39 +4832,39 @@ async function snapshotFiles(filePaths) {
|
|
|
4526
4832
|
async function snapshotDirectories(filePaths, additionalDirectories = []) {
|
|
4527
4833
|
const directories = new Set(additionalDirectories);
|
|
4528
4834
|
for (const filePath of filePaths) {
|
|
4529
|
-
let current =
|
|
4530
|
-
while (current !==
|
|
4835
|
+
let current = path15.dirname(filePath);
|
|
4836
|
+
while (current !== path15.dirname(current)) {
|
|
4531
4837
|
directories.add(current);
|
|
4532
|
-
current =
|
|
4838
|
+
current = path15.dirname(current);
|
|
4533
4839
|
}
|
|
4534
4840
|
}
|
|
4535
4841
|
return Promise.all(
|
|
4536
4842
|
[...directories].map(async (dirPath) => ({
|
|
4537
4843
|
dirPath,
|
|
4538
|
-
existed: await
|
|
4844
|
+
existed: await pathExists4(dirPath)
|
|
4539
4845
|
}))
|
|
4540
4846
|
);
|
|
4541
4847
|
}
|
|
4542
4848
|
async function restoreFiles(snapshots) {
|
|
4543
4849
|
for (const snapshot of snapshots) {
|
|
4544
4850
|
if (snapshot.content === null) {
|
|
4545
|
-
await
|
|
4851
|
+
await fs4.rm(snapshot.filePath, { force: true });
|
|
4546
4852
|
continue;
|
|
4547
4853
|
}
|
|
4548
|
-
await
|
|
4549
|
-
await
|
|
4854
|
+
await fs4.mkdir(path15.dirname(snapshot.filePath), { recursive: true });
|
|
4855
|
+
await fs4.writeFile(snapshot.filePath, snapshot.content, "utf-8");
|
|
4550
4856
|
}
|
|
4551
4857
|
}
|
|
4552
4858
|
async function removeNewEmptyDirectories(snapshots) {
|
|
4553
4859
|
const candidates = snapshots.filter((snapshot) => !snapshot.existed).sort((a, b) => b.dirPath.length - a.dirPath.length);
|
|
4554
4860
|
for (const { dirPath } of candidates) {
|
|
4555
4861
|
try {
|
|
4556
|
-
await
|
|
4862
|
+
await fs4.rmdir(dirPath);
|
|
4557
4863
|
} catch {
|
|
4558
4864
|
}
|
|
4559
4865
|
}
|
|
4560
4866
|
}
|
|
4561
|
-
function
|
|
4867
|
+
function isRecord4(value) {
|
|
4562
4868
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4563
4869
|
}
|
|
4564
4870
|
function isNodeError5(error) {
|
|
@@ -4601,9 +4907,9 @@ function printPlatformCreatedFiles(platform, locale) {
|
|
|
4601
4907
|
function localize(locale, chinese, english) {
|
|
4602
4908
|
return locale === "zh-CN" ? chinese : english;
|
|
4603
4909
|
}
|
|
4604
|
-
async function
|
|
4910
|
+
async function pathExists4(p) {
|
|
4605
4911
|
try {
|
|
4606
|
-
await
|
|
4912
|
+
await fs4.access(p);
|
|
4607
4913
|
return true;
|
|
4608
4914
|
} catch {
|
|
4609
4915
|
return false;
|
|
@@ -4614,286 +4920,6 @@ async function pathExists3(p) {
|
|
|
4614
4920
|
import { promises as fs5 } from "fs";
|
|
4615
4921
|
import path16 from "path";
|
|
4616
4922
|
import process6 from "process";
|
|
4617
|
-
|
|
4618
|
-
// src/installers/platform-status.ts
|
|
4619
|
-
import { promises as fs4 } from "fs";
|
|
4620
|
-
import path15 from "path";
|
|
4621
|
-
async function checkPlatformStatus(rootDir, platform, installed) {
|
|
4622
|
-
const readiness = await checkPlatformReadiness(rootDir, platform);
|
|
4623
|
-
return {
|
|
4624
|
-
installed,
|
|
4625
|
-
ready: installed && readiness.ready,
|
|
4626
|
-
target: readiness.target,
|
|
4627
|
-
detail: describeStatus(installed, readiness)
|
|
4628
|
-
};
|
|
4629
|
-
}
|
|
4630
|
-
async function checkPlatformReadiness(rootDir, platform) {
|
|
4631
|
-
if (platform === "claude-code") {
|
|
4632
|
-
const [hasSoloSkill, registered, hasHookFiles] = await Promise.all([
|
|
4633
|
-
fileMatches(
|
|
4634
|
-
path15.join(rootDir, ".claude", "skills", "solo", "SKILL.md"),
|
|
4635
|
-
(content) => isGeneratedClaudeSkill(content, "solo")
|
|
4636
|
-
),
|
|
4637
|
-
claudeHooksRegistered(rootDir),
|
|
4638
|
-
pathsExist([
|
|
4639
|
-
path15.join(rootDir, ".mancode", "hooks", "session-start.mjs"),
|
|
4640
|
-
path15.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
|
|
4641
|
-
])
|
|
4642
|
-
]);
|
|
4643
|
-
const present = hasSoloSkill && registered && hasHookFiles;
|
|
4644
|
-
const minimal = await isPlatformMinimal(rootDir, "claude-code");
|
|
4645
|
-
const fullContentReady = minimal ? true : await claudeFullContentReady(rootDir);
|
|
4646
|
-
return {
|
|
4647
|
-
present,
|
|
4648
|
-
ready: present && fullContentReady,
|
|
4649
|
-
target: ".claude/",
|
|
4650
|
-
readyDetail: present && fullContentReady ? minimal ? "solo skill and hooks ready" : "skills, agents, and hooks ready" : "skills, agents, hook files, or hook registration are incomplete"
|
|
4651
|
-
};
|
|
4652
|
-
}
|
|
4653
|
-
if (platform === "cursor") {
|
|
4654
|
-
const hasCoreRules = await allManagedSkills(
|
|
4655
|
-
MANCODE_CURSOR_CORE_RULE_FILES.map(
|
|
4656
|
-
(file) => path15.join(rootDir, ".cursor", "rules", file)
|
|
4657
|
-
),
|
|
4658
|
-
[CURSOR_RULE_MANAGED_MARKER]
|
|
4659
|
-
);
|
|
4660
|
-
if (await isPlatformMinimal(rootDir, "cursor")) {
|
|
4661
|
-
return {
|
|
4662
|
-
present: hasCoreRules,
|
|
4663
|
-
ready: hasCoreRules,
|
|
4664
|
-
target: ".cursor/rules/",
|
|
4665
|
-
readyDetail: "mancode core rules present"
|
|
4666
|
-
};
|
|
4667
|
-
}
|
|
4668
|
-
const [hasRules, hasCommands] = await Promise.all([
|
|
4669
|
-
allManagedSkills(
|
|
4670
|
-
MANCODE_CURSOR_RULE_FILES.map(
|
|
4671
|
-
(file) => path15.join(rootDir, ".cursor", "rules", file)
|
|
4672
|
-
),
|
|
4673
|
-
[CURSOR_RULE_MANAGED_MARKER]
|
|
4674
|
-
),
|
|
4675
|
-
allManagedSkills(
|
|
4676
|
-
MODE_NAMES.map(
|
|
4677
|
-
(mode) => path15.join(rootDir, ".cursor", "commands", `${mode}.md`)
|
|
4678
|
-
),
|
|
4679
|
-
[MODE_FILE_MANAGED_MARKER]
|
|
4680
|
-
)
|
|
4681
|
-
]);
|
|
4682
|
-
return {
|
|
4683
|
-
present: hasCoreRules,
|
|
4684
|
-
ready: hasRules && hasCommands,
|
|
4685
|
-
target: ".cursor/rules/ + .cursor/commands/",
|
|
4686
|
-
readyDetail: hasRules && hasCommands ? "mancode rules and commands present" : "mode rules or commands are missing, incomplete, or user-authored"
|
|
4687
|
-
};
|
|
4688
|
-
}
|
|
4689
|
-
if (platform === "codex") {
|
|
4690
|
-
const hasBlock2 = await fileHasManagedBlock(path15.join(rootDir, "AGENTS.md"));
|
|
4691
|
-
if (!hasBlock2) {
|
|
4692
|
-
return {
|
|
4693
|
-
present: false,
|
|
4694
|
-
ready: false,
|
|
4695
|
-
target: "AGENTS.md",
|
|
4696
|
-
readyDetail: "managed block missing"
|
|
4697
|
-
};
|
|
4698
|
-
}
|
|
4699
|
-
if (await isPlatformMinimal(rootDir, "codex")) {
|
|
4700
|
-
return {
|
|
4701
|
-
present: true,
|
|
4702
|
-
ready: true,
|
|
4703
|
-
target: "AGENTS.md",
|
|
4704
|
-
readyDetail: "managed block present"
|
|
4705
|
-
};
|
|
4706
|
-
}
|
|
4707
|
-
const skillsDir = path15.join(rootDir, ".agents", "skills");
|
|
4708
|
-
const hasSkills = await allManagedSkills(
|
|
4709
|
-
MODE_NAMES.map((mode) => path15.join(skillsDir, mode, "SKILL.md")),
|
|
4710
|
-
MANCODE_AGENT_SKILL_MARKERS
|
|
4711
|
-
);
|
|
4712
|
-
return {
|
|
4713
|
-
present: true,
|
|
4714
|
-
ready: hasSkills,
|
|
4715
|
-
target: "AGENTS.md + .agents/skills/",
|
|
4716
|
-
readyDetail: hasSkills ? "managed block and skills present" : "mode skills are missing, incomplete, or user-authored"
|
|
4717
|
-
};
|
|
4718
|
-
}
|
|
4719
|
-
if (platform === "zcode") {
|
|
4720
|
-
const hasBlock2 = await fileHasManagedBlock(
|
|
4721
|
-
path15.join(rootDir, "AGENTS.md"),
|
|
4722
|
-
ZCODE_MANCODE_START_MARKER,
|
|
4723
|
-
ZCODE_MANCODE_END_MARKER
|
|
4724
|
-
);
|
|
4725
|
-
if (!hasBlock2) {
|
|
4726
|
-
return {
|
|
4727
|
-
present: false,
|
|
4728
|
-
ready: false,
|
|
4729
|
-
target: "AGENTS.md",
|
|
4730
|
-
readyDetail: "managed block missing"
|
|
4731
|
-
};
|
|
4732
|
-
}
|
|
4733
|
-
if (await isPlatformMinimal(rootDir, "zcode")) {
|
|
4734
|
-
return {
|
|
4735
|
-
present: true,
|
|
4736
|
-
ready: true,
|
|
4737
|
-
target: "AGENTS.md",
|
|
4738
|
-
readyDetail: "managed block present"
|
|
4739
|
-
};
|
|
4740
|
-
}
|
|
4741
|
-
const skillsDir = path15.join(rootDir, ".agents", "skills");
|
|
4742
|
-
const hasSkills = await allManagedSkills(
|
|
4743
|
-
MODE_NAMES.map((mode) => path15.join(skillsDir, mode, "SKILL.md")),
|
|
4744
|
-
MANCODE_AGENT_SKILL_MARKERS
|
|
4745
|
-
);
|
|
4746
|
-
return {
|
|
4747
|
-
present: true,
|
|
4748
|
-
ready: hasSkills,
|
|
4749
|
-
target: "AGENTS.md + .agents/skills/",
|
|
4750
|
-
readyDetail: hasSkills ? "managed block and skills present" : "mode skills are missing, incomplete, or user-authored"
|
|
4751
|
-
};
|
|
4752
|
-
}
|
|
4753
|
-
const hasBlock = await fileHasManagedBlock(
|
|
4754
|
-
path15.join(rootDir, ".github", "copilot-instructions.md")
|
|
4755
|
-
);
|
|
4756
|
-
if (!hasBlock) {
|
|
4757
|
-
return {
|
|
4758
|
-
present: false,
|
|
4759
|
-
ready: false,
|
|
4760
|
-
target: ".github/copilot-instructions.md",
|
|
4761
|
-
readyDetail: "managed block missing"
|
|
4762
|
-
};
|
|
4763
|
-
}
|
|
4764
|
-
if (!await isPlatformMinimal(rootDir, "copilot")) {
|
|
4765
|
-
const promptsDir = path15.join(rootDir, ".github", "prompts");
|
|
4766
|
-
const hasPrompts = await allManagedSkills(
|
|
4767
|
-
MODE_NAMES.map((mode) => path15.join(promptsDir, `${mode}.prompt.md`)),
|
|
4768
|
-
[MODE_FILE_MANAGED_MARKER]
|
|
4769
|
-
);
|
|
4770
|
-
return {
|
|
4771
|
-
present: true,
|
|
4772
|
-
ready: hasPrompts,
|
|
4773
|
-
target: ".github/copilot-instructions.md + .github/prompts/",
|
|
4774
|
-
readyDetail: hasPrompts ? "managed block and prompts present" : "mode prompts are missing, incomplete, or user-authored"
|
|
4775
|
-
};
|
|
4776
|
-
}
|
|
4777
|
-
return {
|
|
4778
|
-
present: true,
|
|
4779
|
-
ready: true,
|
|
4780
|
-
target: ".github/copilot-instructions.md",
|
|
4781
|
-
readyDetail: "managed block present"
|
|
4782
|
-
};
|
|
4783
|
-
}
|
|
4784
|
-
function describeStatus(installed, readiness) {
|
|
4785
|
-
if (installed && readiness.present) return readiness.readyDetail;
|
|
4786
|
-
if (!installed && readiness.present) {
|
|
4787
|
-
return "target files present but platform is not recorded in config";
|
|
4788
|
-
}
|
|
4789
|
-
return "missing generated files or registration";
|
|
4790
|
-
}
|
|
4791
|
-
async function allManagedSkills(paths, markers) {
|
|
4792
|
-
const results = await Promise.all(
|
|
4793
|
-
paths.map((filePath) => fileHasAnyMarker(filePath, markers))
|
|
4794
|
-
);
|
|
4795
|
-
return results.every(Boolean);
|
|
4796
|
-
}
|
|
4797
|
-
async function claudeFullContentReady(rootDir) {
|
|
4798
|
-
const skillChecks = MVP2_SKILLS.map(
|
|
4799
|
-
(skill) => fileMatches(
|
|
4800
|
-
path15.join(rootDir, ".claude", "skills", skill.name, "SKILL.md"),
|
|
4801
|
-
(content) => isGeneratedClaudeSkill(content, skill.name)
|
|
4802
|
-
)
|
|
4803
|
-
);
|
|
4804
|
-
const agentChecks = ALL_AGENTS.map(
|
|
4805
|
-
(agent) => fileMatches(
|
|
4806
|
-
path15.join(rootDir, ".claude", "agents", `${agent.name}.md`),
|
|
4807
|
-
(content) => isGeneratedClaudeAgent(content, agent.name)
|
|
4808
|
-
)
|
|
4809
|
-
);
|
|
4810
|
-
const results = await Promise.all([...skillChecks, ...agentChecks]);
|
|
4811
|
-
return results.every(Boolean);
|
|
4812
|
-
}
|
|
4813
|
-
async function fileMatches(filePath, predicate) {
|
|
4814
|
-
try {
|
|
4815
|
-
return predicate(await fs4.readFile(filePath, "utf-8"));
|
|
4816
|
-
} catch {
|
|
4817
|
-
return false;
|
|
4818
|
-
}
|
|
4819
|
-
}
|
|
4820
|
-
async function pathsExist(paths) {
|
|
4821
|
-
return (await Promise.all(paths.map(pathExists4))).every(Boolean);
|
|
4822
|
-
}
|
|
4823
|
-
async function fileHasAnyMarker(filePath, needles) {
|
|
4824
|
-
try {
|
|
4825
|
-
const content = await fs4.readFile(filePath, "utf-8");
|
|
4826
|
-
return needles.some((needle) => content.includes(needle));
|
|
4827
|
-
} catch {
|
|
4828
|
-
return false;
|
|
4829
|
-
}
|
|
4830
|
-
}
|
|
4831
|
-
async function isPlatformMinimal(rootDir, platform) {
|
|
4832
|
-
try {
|
|
4833
|
-
const raw = await fs4.readFile(
|
|
4834
|
-
path15.join(rootDir, ".mancode", "config.json"),
|
|
4835
|
-
"utf-8"
|
|
4836
|
-
);
|
|
4837
|
-
const config = JSON.parse(raw);
|
|
4838
|
-
if (!isRecord4(config.platformOptions)) return false;
|
|
4839
|
-
const options = config.platformOptions[platform];
|
|
4840
|
-
return isRecord4(options) && options.minimal === true;
|
|
4841
|
-
} catch {
|
|
4842
|
-
return false;
|
|
4843
|
-
}
|
|
4844
|
-
}
|
|
4845
|
-
async function fileHasManagedBlock(filePath, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
|
|
4846
|
-
try {
|
|
4847
|
-
const content = await fs4.readFile(filePath, "utf-8");
|
|
4848
|
-
return hasManagedBlock(content, startMarker, endMarker);
|
|
4849
|
-
} catch {
|
|
4850
|
-
return false;
|
|
4851
|
-
}
|
|
4852
|
-
}
|
|
4853
|
-
async function claudeHooksRegistered(rootDir) {
|
|
4854
|
-
try {
|
|
4855
|
-
const raw = await fs4.readFile(
|
|
4856
|
-
path15.join(rootDir, ".claude", "settings.json"),
|
|
4857
|
-
"utf-8"
|
|
4858
|
-
);
|
|
4859
|
-
const settings = JSON.parse(raw);
|
|
4860
|
-
return hooksRegistered(settings);
|
|
4861
|
-
} catch {
|
|
4862
|
-
return false;
|
|
4863
|
-
}
|
|
4864
|
-
}
|
|
4865
|
-
function hooksRegistered(settings) {
|
|
4866
|
-
if (!isRecord4(settings)) return false;
|
|
4867
|
-
const hooks = settings.hooks;
|
|
4868
|
-
if (!isRecord4(hooks)) return false;
|
|
4869
|
-
return hasHookCommand(hooks.SessionStart, ".mancode/hooks/session-start.mjs") && hasHookCommand(
|
|
4870
|
-
hooks.UserPromptSubmit,
|
|
4871
|
-
".mancode/hooks/user-prompt-submit.mjs"
|
|
4872
|
-
);
|
|
4873
|
-
}
|
|
4874
|
-
function hasHookCommand(value, needle) {
|
|
4875
|
-
if (!Array.isArray(value)) return false;
|
|
4876
|
-
return value.some((group) => {
|
|
4877
|
-
if (!isRecord4(group) || !Array.isArray(group.hooks)) return false;
|
|
4878
|
-
return group.hooks.some((hook) => {
|
|
4879
|
-
if (!isRecord4(hook) || typeof hook.command !== "string") return false;
|
|
4880
|
-
return hook.command.includes(needle);
|
|
4881
|
-
});
|
|
4882
|
-
});
|
|
4883
|
-
}
|
|
4884
|
-
async function pathExists4(p) {
|
|
4885
|
-
try {
|
|
4886
|
-
await fs4.access(p);
|
|
4887
|
-
return true;
|
|
4888
|
-
} catch {
|
|
4889
|
-
return false;
|
|
4890
|
-
}
|
|
4891
|
-
}
|
|
4892
|
-
function isRecord4(value) {
|
|
4893
|
-
return typeof value === "object" && value !== null;
|
|
4894
|
-
}
|
|
4895
|
-
|
|
4896
|
-
// src/commands/install.ts
|
|
4897
4923
|
var EXIT_OK2 = 0;
|
|
4898
4924
|
var EXIT_NOT_INITIALIZED = 1;
|
|
4899
4925
|
var EXIT_UNSUPPORTED_PLATFORM = 2;
|