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/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 fs3 } from "fs";
10
+ import { promises as fs4 } from "fs";
11
11
  import os from "os";
12
- import path14 from "path";
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/codex.ts
2635
- import { writeFile as writeFile6 } from "fs/promises";
2636
- import path7 from "path";
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 writeFile8 } from "fs/promises";
2820
- import path9 from "path";
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 = path9.join(projectRoot, "AGENTS.md");
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 writeFile8(
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/registry.ts
2864
- var PLATFORM_INSTALLERS = {
2865
- "claude-code": {
2866
- name: "claude-code",
2867
- displayName: "Claude Code",
2868
- capabilities: {
2869
- slashCommands: "native",
2870
- subagents: true,
2871
- hooks: true,
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 isPlatformName(platform) {
2932
- return Object.prototype.hasOwnProperty.call(PLATFORM_INSTALLERS, platform);
2783
+ async function isPlatformPresent(rootDir, platform) {
2784
+ return (await checkPlatformReadiness(rootDir, platform)).present;
2933
2785
  }
2934
-
2935
- // src/system/detect-team.ts
2936
- import { execFile } from "child_process";
2937
- import { access as access2 } from "fs/promises";
2938
- import path10 from "path";
2939
- import process2 from "process";
2940
- import { promisify } from "util";
2941
- var execFileAsync = promisify(execFile);
2942
- var NO_TEAM = {
2943
- isTeam: false,
2944
- contributors: 1,
2945
- recentActive: 1,
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 contributors = countUniqueLines(allEmails);
2969
- const recentActive = countUniqueLines(recentEmails);
2970
- const hasRemote = REMOTE_PATTERN.test(remotes);
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
- isTeam: contributors > 1 && hasRemote && recentActive > 1,
2973
- contributors: contributors || 1,
2974
- recentActive: recentActive || 1,
2975
- hasRemote
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
- async function runGit(cwd, args) {
2982
- const { stdout: stdout2 } = await execFileAsync("git", args, {
2983
- cwd,
2984
- encoding: "utf-8",
2985
- env: GIT_ENV,
2986
- windowsHide: true
2987
- });
2988
- return stdout2;
2989
- }
2990
- function countUniqueLines(output) {
2991
- return new Set(
2992
- output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
2993
- ).size;
2994
- }
2995
-
2996
- // src/system/detect.ts
2997
- import { execFile as execFile2 } from "child_process";
2998
- import process3 from "process";
2999
- import { promisify as promisify2 } from "util";
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 fs } from "fs";
3016
- import path11 from "path";
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 fs.access(path11.join(rootDir, relative));
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 path12 from "path";
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(path12.join(projectRoot, "package.json"));
3511
+ const packageJson = await readJson2(path13.join(projectRoot, "package.json"));
3231
3512
  const [pubspec, pythonManifests] = await Promise.all([
3232
- readText(path12.join(projectRoot, "pubspec.yaml")),
3513
+ readText(path13.join(projectRoot, "pubspec.yaml")),
3233
3514
  Promise.all([
3234
- readText(path12.join(projectRoot, "pyproject.toml")),
3235
- readText(path12.join(projectRoot, "requirements.txt"))
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(path12.join(projectRoot, "src", "components", "ui"));
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(path12.join(root, candidate))).isDirectory())
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 fs2 } from "fs";
3462
- import path13 from "path";
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 fs2.readFile(configResult.absPath, "utf-8");
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 pathExists2(path13.join(projectRoot, "package.json"))) {
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 = path13.join(projectRoot, name);
3524
- if (await pathExists2(absPath)) {
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 = path13.join(projectRoot, relRoot);
3751
- if (!await pathExists2(absRoot)) continue;
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 fs2.readdir(dir);
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 = path13.join(dir, entry);
4051
+ const abs = path14.join(dir, entry);
3771
4052
  let info;
3772
4053
  try {
3773
- info = await fs2.lstat(abs);
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 = path13.basename(dir);
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 = path13.join(projectRoot, relPath);
3814
- if (!await pathExists2(absPath)) continue;
3815
- const content = await fs2.readFile(absPath, "utf-8");
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 fs2.readFile(
3859
- path13.join(projectRoot, "package.json"),
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 pathExists2(p) {
4154
+ async function pathExists3(p) {
3874
4155
  try {
3875
- await fs2.access(p);
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 = path14.join(rootDir, ".mancode");
3891
- const stateFile = path14.join(mancodeDir, "state.json");
3892
- const wasInitialized = await pathExists3(stateFile);
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 pathExists3(rootDir)) {
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 pathExists3(path14.join(rootDir, ".git"));
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
- const prompter = options.prompter ?? (options.interactive ? createTerminalPrompter() : null);
3948
- const confirmed = options.empty || options.yes ? true : prompter ? await prompter.confirmGenericProject({ rootDir, locale }) : false;
3949
- if (!confirmed) {
3950
- if (options.interactive) {
3951
- console.log(
3952
- locale === "zh-CN" ? "\u5DF2\u53D6\u6D88\u521D\u59CB\u5316\u3002" : "Initialization cancelled."
3953
- );
3954
- return EXIT_USER_CANCEL;
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
- path14.join(mancodeDir, "workflows"),
4052
- path14.join(mancodeDir, "preseason-reports")
4337
+ path15.join(mancodeDir, "workflows"),
4338
+ path15.join(mancodeDir, "preseason-reports")
4053
4339
  ]);
4054
4340
  const team = await detectTeamStatus(rootDir);
4055
- const existingPreferences = wasInitialized ? await readExistingInitPreferences(mancodeDir, primaryPlatform) : {};
4056
- const initialMinimal = existingPreferences.minimal ?? false;
4057
- const existingTeamMode = existingPreferences.forceTeamMode === true ? "on" : existingPreferences.teamMode ?? "auto";
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 fs3.mkdir(mancodeDir, { recursive: true });
4393
+ await fs4.mkdir(mancodeDir, { recursive: true });
4104
4394
  const stateContent = `${JSON.stringify(state, null, 2)}
4105
4395
  `;
4106
- await fs3.writeFile(stateFile, stateContent, "utf-8");
4107
- await fs3.writeFile(
4108
- path14.join(mancodeDir, "project-profile.json"),
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: initialMinimal }
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 = path14.join(
4435
+ const tokensPath = path15.join(
4146
4436
  mancodeDir,
4147
4437
  "aesthetics",
4148
4438
  "style-tokens.json"
4149
4439
  );
4150
- await fs3.writeFile(
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: initialMinimal,
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 = path14.join(mancodeDir, "config.json");
4586
+ const configPath = path15.join(mancodeDir, "config.json");
4297
4587
  let config = {};
4298
4588
  try {
4299
- config = JSON.parse(await fs3.readFile(configPath, "utf-8"));
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 = isRecord3(config.platformOptions) ? config.platformOptions : {};
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 fs3.writeFile(
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, platform) {
4616
+ async function readExistingInitPreferences(mancodeDir) {
4327
4617
  try {
4328
4618
  const config = JSON.parse(
4329
- await fs3.readFile(path14.join(mancodeDir, "config.json"), "utf-8")
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 (Array.isArray(config.platforms) && config.platforms.includes(platform) && isRecord3(config.platformOptions)) {
4339
- const platformOptions = config.platformOptions[platform];
4340
- if (isRecord3(platformOptions) && platformOptions.minimal === true) {
4341
- preferences.minimal = true;
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 fs3.readFile(stateFile, "utf-8"));
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 pathExists3(path14.join(rootDir, name))) return true;
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 = path14.resolve(rootDir);
4385
- if (resolved === path14.parse(resolved).root || resolved === path14.resolve(os.homedir())) {
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 fs3.readdir(resolved);
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) => path14.join(rootDir, 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 fs3.readFile(filePath, "utf-8") };
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 = path14.dirname(filePath);
4530
- while (current !== path14.dirname(current)) {
4835
+ let current = path15.dirname(filePath);
4836
+ while (current !== path15.dirname(current)) {
4531
4837
  directories.add(current);
4532
- current = path14.dirname(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 pathExists3(dirPath)
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 fs3.rm(snapshot.filePath, { force: true });
4851
+ await fs4.rm(snapshot.filePath, { force: true });
4546
4852
  continue;
4547
4853
  }
4548
- await fs3.mkdir(path14.dirname(snapshot.filePath), { recursive: true });
4549
- await fs3.writeFile(snapshot.filePath, snapshot.content, "utf-8");
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 fs3.rmdir(dirPath);
4862
+ await fs4.rmdir(dirPath);
4557
4863
  } catch {
4558
4864
  }
4559
4865
  }
4560
4866
  }
4561
- function isRecord3(value) {
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 pathExists3(p) {
4910
+ async function pathExists4(p) {
4605
4911
  try {
4606
- await fs3.access(p);
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;