mancode 0.3.2 → 0.3.4

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,10 @@ import {
7
7
  import { program } from "commander";
8
8
 
9
9
  // src/commands/init.ts
10
- import { promises as fs2 } from "fs";
11
- import path13 from "path";
12
- import process3 from "process";
10
+ import { promises as fs3 } from "fs";
11
+ import os from "os";
12
+ import path14 from "path";
13
+ import process5 from "process";
13
14
 
14
15
  // src/installers/claude-code.ts
15
16
  import {
@@ -510,215 +511,229 @@ function yamlString(s) {
510
511
  }
511
512
 
512
513
  // src/templates/inline.ts
513
- var SESSION_START_HOOK = `#!/bin/bash
514
- # .mancode/hooks/session-start.sh
515
- # mancode SessionStart hook - \u52A0\u8F7D\u9879\u76EE\u4E0A\u4E0B\u6587
516
- # \u7CFB\u7EDF\u4F9D\u8D56\uFF1Abash\u3001git\u3001\uFF08\u53EF\u9009\uFF09jq
517
- set -uo pipefail
518
-
519
- PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "$PWD")"
520
- STATE_FILE="$PROJECT_ROOT/.mancode/state.json"
521
- PROFILE_FILE="$PROJECT_ROOT/.mancode/project-profile.json"
522
-
523
- HAS_JQ=0
524
- if [ "\${MANCODE_DISABLE_JQ:-0}" != "1" ]; then
525
- command -v jq >/dev/null 2>&1 && HAS_JQ=1
526
- fi
527
-
528
- json_get() {
529
- local key="$1"
530
- local file="$2"
531
- if [ "$HAS_JQ" = "1" ]; then
532
- jq -r ".$key // empty" "$file" 2>/dev/null || true
533
- else
534
- grep "\\"$key\\"" "$file" 2>/dev/null | sed -E 's/.*: *"([^"]*)".*/\\1/' || true
535
- fi
536
- }
537
-
538
- json_any_get() {
539
- local key="$1"
540
- local file="$2"
541
- if [ "$HAS_JQ" = "1" ]; then
542
- jq -r ".$key // empty" "$file" 2>/dev/null || true
543
- else
544
- grep "\\"$key\\"" "$file" 2>/dev/null | head -n 1 | sed -E 's/.*: *"?([^",}]*)"?[,]?.*/\\1/' || true
545
- fi
546
- }
547
-
548
- if [ ! -f "$STATE_FILE" ]; then
549
- echo "\u2139\uFE0F mancode \u672A\u521D\u59CB\u5316\u3002\u8FD0\u884C \\\`mancode init\\\` \u5F00\u59CB\u3002"
550
- exit 0
551
- fi
552
-
553
- # \u6E05\u6D17\u51FD\u6570\uFF1A\u53BB\u6362\u884C\u3001\u9650\u5236\u957F\u5EA6\uFF08\u9632\u6B62\u810F\u6570\u636E\u6C61\u67D3 prompt\uFF09
554
- sanitize() {
555
- printf '%s' "$1" | tr '\\n\\r' ' ' | head -c 200
556
- }
557
-
558
- MODE=$(json_get "currentMode" "$STATE_FILE")
559
- TECH_STACK=$(json_get "techStack" "$STATE_FILE")
560
- UI_LIBRARY=$(json_get "uiLibrary" "$STATE_FILE")
561
- TEAM_AUTO=$(json_any_get "teamModeAutoDetected" "$STATE_FILE")
562
- CONTRIBUTORS=$(json_any_get "contributors" "$STATE_FILE")
563
-
564
- echo "mancode_mode: \${MODE:-solo}"
565
- echo "project_type: $(sanitize "$TECH_STACK")"
566
- echo "ui_library: $(sanitize "$UI_LIBRARY")"
567
- echo ""
568
-
569
- echo "## mancode \xB7 \${MODE:-solo} mode"
570
- echo ""
571
- echo "\u4F60\u6B63\u5728\u4F7F\u7528 mancode \${MODE:-solo} \u6A21\u5F0F\u3002"
572
- echo ""
573
- echo "### \u6838\u5FC3\u539F\u5219"
574
- echo "1. **\u4F18\u5148\u590D\u7528\u9879\u76EE\u5DF2\u6709\u4EE3\u7801**"
575
- echo " - \u68C0\u67E5\u5DF2\u68C0\u6D4B\u5230\u7684\u6E90\u7801\u76EE\u5F55\u548C\u5DF2\u6709\u7C7B\u4F3C\u5B9E\u73B0"
576
- echo " - \u590D\u7528\u73B0\u6709\u7EC4\u4EF6\u3001\u51FD\u6570\u3001\u6837\u5F0F"
577
- echo ""
578
- if [ "$(json_get "uiAssets" "$PROFILE_FILE")" = "detected" ]; then
579
- echo "2. **\u5E94\u7528\u9879\u76EE\u5BA1\u7F8E token**\uFF08\u4EC5\u5728\u9879\u76EE profile \u786E\u8BA4\u6709 UI \u8D44\u4EA7\u4E14\u4EFB\u52A1\u6D89\u53CA UI \u65F6\uFF09"
580
- echo " - UI library: $(sanitize "$UI_LIBRARY")"
581
- echo " - \u4F7F\u7528\u9879\u76EE\u5DF2\u6709\u7684\u8BBE\u8BA1 token"
582
- else
583
- echo "2. **\u6309\u9879\u76EE\u80FD\u529B\u5DE5\u4F5C**"
584
- echo " - \u4E0D\u5047\u5B9A\u5B58\u5728 UI\u3001\u6D4F\u89C8\u5668\u6216\u7279\u5B9A\u6280\u672F\u6808"
585
- echo " - \u5148\u8BFB\u53D6 project-profile \u4E0E\u9879\u76EE\u73B0\u6709\u9A8C\u8BC1\u65B9\u5F0F"
586
- fi
587
- echo ""
588
- echo "3. **\u6700\u5C0F\u6539\u52A8**"
589
- echo " - \u53EA\u6539\u7528\u6237\u8981\u6C42\u7684\u90E8\u5206"
590
- echo " - \u4E0D\u91CD\u6784\u65E0\u5173\u4EE3\u7801"
591
-
592
- if [ "$TEAM_AUTO" = "true" ] && [ "\${MODE:-solo}" = "solo" ]; then
593
- echo ""
594
- echo "### \u56E2\u961F\u534F\u4F5C\u63D0\u9192"
595
- echo "\u68C0\u6D4B\u5230\u56E2\u961F\u9879\u76EE\uFF08contributors: \${CONTRIBUTORS:-2}\uFF09\u3002"
596
- echo '- \u6D89\u53CA\u591A\u4EBA\u534F\u4F5C\u3001\u4EA4\u63A5\u3001PR\u3001\u5171\u4EAB\u6A21\u5757\u65F6\uFF0C\u4F18\u5148\u4F7F\u7528 /manteam <task>\u3002'
597
- echo '- \u53EA\u505A\u4E2A\u4EBA\u5C0F\u6539\u52A8\u65F6\uFF0C\u53EF\u4EE5\u7EE7\u7EED solo\uFF1B\u9700\u8981\u9000\u51FA\u6D41\u7A0B\u7528 /mansolo\u3002'
598
- fi
514
+ var SESSION_START_HOOK = String.raw`#!/usr/bin/env node
515
+ // .mancode/hooks/session-start.mjs
516
+ // mancode SessionStart hook - cross-platform project context
517
+ import { readFileSync } from 'node:fs';
518
+ import path from 'node:path';
519
+ import { fileURLToPath } from 'node:url';
520
+
521
+ const projectRoot = path.resolve(
522
+ path.dirname(fileURLToPath(import.meta.url)),
523
+ '../..',
524
+ );
525
+ const state = readJson(path.join(projectRoot, '.mancode', 'state.json'));
526
+ const profile =
527
+ readJson(path.join(projectRoot, '.mancode', 'project-profile.json')) || {};
528
+
529
+ if (!state) {
530
+ console.log('ℹ️ mancode 未初始化。运行 mancode init 开始。');
531
+ process.exit(0);
532
+ }
533
+
534
+ const mode = text(state.currentMode) || 'solo';
535
+ const techStack = sanitize(state.techStack);
536
+ const uiLibrary = sanitize(state.uiLibrary);
537
+ const output = [
538
+ 'mancode_mode: ' + mode,
539
+ 'project_type: ' + techStack,
540
+ 'ui_library: ' + uiLibrary,
541
+ '',
542
+ '## mancode · ' + mode + ' mode',
543
+ '',
544
+ '你正在使用 mancode ' + mode + ' 模式。',
545
+ '',
546
+ '### 核心原则',
547
+ '1. **优先复用项目已有代码**',
548
+ ' - 检查已检测到的源码目录和已有类似实现',
549
+ ' - 复用现有组件、函数、样式',
550
+ '',
551
+ ];
552
+
553
+ if (profile.uiAssets === 'detected') {
554
+ output.push(
555
+ '2. **应用项目审美 token**(仅在项目 profile 确认有 UI 资产且任务涉及 UI 时)',
556
+ ' - UI library: ' + uiLibrary,
557
+ ' - 使用项目已有的设计 token',
558
+ );
559
+ } else {
560
+ output.push(
561
+ '2. **按项目能力工作**',
562
+ ' - 不假定存在 UI、浏览器或特定技术栈',
563
+ ' - 先读取 project-profile 与项目现有验证方式',
564
+ );
565
+ }
566
+
567
+ output.push(
568
+ '',
569
+ '3. **最小改动**',
570
+ ' - 只改用户要求的部分',
571
+ ' - 不重构无关代码',
572
+ );
573
+
574
+ if (state.teamModeAutoDetected === true && mode === 'solo') {
575
+ output.push(
576
+ '',
577
+ '### 团队协作提醒',
578
+ '检测到团队项目(contributors: ' +
579
+ (Number.isFinite(state.contributors) ? state.contributors : 2) +
580
+ ')。',
581
+ '- 涉及多人协作、交接、PR、共享模块时,优先使用 /manteam <task>。',
582
+ '- 只做个人小改动时,可以继续 solo;需要退出流程用 /mansolo。',
583
+ );
584
+ }
585
+
586
+ console.log(output.join('\n'));
587
+
588
+ function readJson(filePath) {
589
+ try {
590
+ return JSON.parse(readFileSync(filePath, 'utf8'));
591
+ } catch {
592
+ return null;
593
+ }
594
+ }
595
+
596
+ function text(value) {
597
+ return typeof value === 'string' ? value : '';
598
+ }
599
+
600
+ function sanitize(value) {
601
+ return String(value ?? '').replace(/[\r\n]/g, ' ').slice(0, 200);
602
+ }
599
603
  `;
600
- var USER_PROMPT_SUBMIT_HOOK = `#!/bin/bash
601
- # .mancode/hooks/user-prompt-submit.sh
602
- # mancode UserPromptSubmit hook - \u6CE8\u5165 6 \u95EE\u8FFD\u95EE + \u5BA1\u7F8E token
603
- set -uo pipefail
604
-
605
- PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "$PWD")"
606
- STATE_FILE="$PROJECT_ROOT/.mancode/state.json"
607
- AESTHETICS_FILE="$PROJECT_ROOT/.mancode/aesthetics/style-tokens.json"
608
-
609
- HAS_JQ=0
610
- if [ "\${MANCODE_DISABLE_JQ:-0}" != "1" ]; then
611
- command -v jq >/dev/null 2>&1 && HAS_JQ=1
612
- fi
613
-
614
- json_get() {
615
- local key="$1"
616
- local file="$2"
617
- if [ "$HAS_JQ" = "1" ]; then
618
- jq -r ".$key // empty" "$file" 2>/dev/null || true
619
- else
620
- grep "\\"$key\\"" "$file" 2>/dev/null | sed -E 's/.*: *"([^"]*)".*/\\1/' || true
621
- fi
622
- }
623
-
624
- # \u6E05\u6D17\u6CE8\u5165\u5230 prompt \u7684\u52A8\u6001\u503C\uFF1A\u53BB\u6362\u884C\u3001\u9650\u5236\u957F\u5EA6\uFF0C\u907F\u514D\u810F token \u6C61\u67D3\u4E0A\u4E0B\u6587\u7ED3\u6784
625
- sanitize() {
626
- printf '%s' "$1" | tr '\\n\\r' ' ' | head -c 200
627
- }
628
-
629
- MODE=$(json_get "currentMode" "$STATE_FILE")
630
-
631
- # \u53EA\u5728 solo \u6A21\u5F0F\u4E0B\u8F93\u51FA 6 \u95EE
632
- if [ "$MODE" = "solo" ]; then
633
- echo "## \u52A8\u624B\u524D\uFF0C\u5148\u60F3\u516D\u4E2A\u95EE\u9898\uFF1A"
634
- echo ""
635
- echo "1. **\u4E3A\u4EC0\u4E48\u505A\uFF1F**"
636
- echo " - \u8FD9\u4E2A\u6539\u52A8\u89E3\u51B3\u4EC0\u4E48\u95EE\u9898\uFF1F"
637
- echo ""
638
- echo "2. **\u5DF2\u7ECF\u6709\u4EC0\u4E48\uFF1F**"
639
- echo " - \u9879\u76EE\u91CC\u6709\u6CA1\u6709\u7C7B\u4F3C\u7684\u5B9E\u73B0\u53EF\u4EE5\u590D\u7528\uFF1F"
640
- echo ""
641
- echo "3. **\u6700\u5C11\u6539\u591A\u5C11\uFF1F**"
642
- echo " - \u80FD\u7528\u4E00\u884C\u89E3\u51B3\u5417\uFF1F\u80FD\u590D\u7528\u73B0\u6709\u4EE3\u7801\u5417\uFF1F"
643
- echo ""
644
- echo "4. **\u80FD\u4E0D\u80FD\u4E0D\u62C6\u65B0\u7CFB\u7EDF\uFF1F**"
645
- echo " - \u4E0D\u65B0\u5EFA\u6587\u4EF6\u6216\u6A21\u5757\u80FD\u5B8C\u6210\u5417\uFF1F"
646
- echo ""
647
- echo "5. **\u975E\u5E73\u51E1\u903B\u8F91\u600E\u6837\u6700\u5C0F\u8FD0\u884C\u9A8C\u8BC1\uFF1F**"
648
- echo ""
649
- echo "6. **\u6709\u4EC0\u4E48\u6CA1\u628A\u63E1\u7684\uFF1F**"
650
- echo " - \u5148\u81EA\u884C\u67E5\u4EE3\u7801\u6216\u6587\u6863\uFF0C\u6700\u591A 2 \u6B21\u5DE5\u5177\u8C03\u7528\uFF1B\u4ECD\u4E0D\u786E\u5B9A\u518D\u95EE\u7528\u6237\u3002"
651
- echo ""
652
- fi
653
-
654
- # Claude Code \u901A\u8FC7 stdin \u4F20\u5165 JSON: {"prompt": "...", ...}
655
- # \u5148\u8BFB\u53D6\u5B8C\u6574\u8F93\u5165\uFF0C\u518D\u89E3\u6790
656
- INPUT=$(cat)
657
-
658
- if [ "$HAS_JQ" = "1" ]; then
659
- USER_PROMPT=$(echo "$INPUT" | jq -r '.prompt // ""' 2>/dev/null)
660
- # jq \u5931\u8D25\u6216\u8FD4\u56DE\u7A7A\uFF0Cfallback \u5230\u539F\u59CB\u8F93\u5165
661
- if [ -z "$USER_PROMPT" ]; then
662
- USER_PROMPT="$INPUT"
663
- fi
664
- else
665
- # \u65E0 jq: \u4F7F\u7528 sed \u63D0\u53D6 JSON \u4E2D\u7684 prompt \u5B57\u6BB5
666
- USER_PROMPT=$(echo "$INPUT" | sed -n 's/.*"prompt"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p')
667
- # sed \u5931\u8D25\uFF0Cfallback \u5230\u539F\u59CB\u8F93\u5165
668
- if [ -z "$USER_PROMPT" ]; then
669
- USER_PROMPT="$INPUT"
670
- fi
671
- fi
672
-
673
- if [ "$MODE" = "solo" ] && echo "$USER_PROMPT" | grep -qiE "\u5148(\u522B|\u4E0D\u8981|\u770B|\u770B\u770B|\u8C03\u7814|\u5206\u6790|\u8BC4\u4F30)|\u7ED9.*\u65B9\u6848|\u7ED9.*\u8BA1\u5212|\u600E\u4E48.*\u505A|\u5982\u4F55.*\u505A|\u600E\u4E48.*\u5B9E\u73B0|\u5982\u4F55.*\u5B9E\u73B0|\u5E94\u8BE5\u600E\u4E48|\u600E\u4E48.*\u62C6|\u62C6\u5206|\u53EA\u7ED9.*\u8BA1\u5212|\u4E0D\u8981.*\u6539\u4EE3\u7801|\u522B.*\u6539\u4EE3\u7801|\u4E0D\u8981.*\u52A8\u4EE3\u7801|\u522B.*\u52A8\u4EE3\u7801|\u8BC4\u4F30.*\u98CE\u9669|\u98CE\u9669.*\u8BC4\u4F30|\u8BBE\u8BA1.*\u65B9\u6848|\u67B6\u6784|\u8FC1\u79FB|\u96C6\u6210|\\b(plan|planning|research|investigate|approach|proposal|architecture|risk|migration|integration)\\b|how (should|would|to)|do not (edit|modify|change)|don.t (edit|modify|change)|no code changes|without changing code"; then
674
- echo "## mancode \u81EA\u52A8\u8DEF\u7531"
675
- echo ""
676
- echo "\u8FD9\u4E2A\u8BF7\u6C42\u662F\u89C4\u5212/\u8C03\u7814\u7C7B\u4EFB\u52A1\u3002\u4E0D\u8981\u76F4\u63A5\u8FDB\u5165 solo \u5B9E\u65BD\u3002"
677
- echo "\u5FC5\u987B\u5148\u8C03\u7528 Skill tool\uFF0Cskill='man'\uFF0C\u628A\u7528\u6237\u539F\u59CB\u8BF7\u6C42\u4F5C\u4E3A task\uFF0C\u6267\u884C Scout \u8C03\u7814\u3001\u6F84\u6E05\u548C Plan Coach plan\u3002"
678
- echo "\u7528\u6237\u53EA\u8981\u8BA1\u5212\u65F6\uFF0C\u5728 Step 4 \u9009\u62E9\u201C\u53EA\u8981\u8BA1\u5212\u201D\uFF1B\u4E0D\u8981\u5207\u5230\u53E6\u4E00\u4E2A\u547D\u4EE4\u3002"
679
- echo ""
680
- fi
681
-
682
- # UI token \u4EC5\u5728 profile \u786E\u8BA4\u5B58\u5728 UI \u8D44\u4EA7\u4E14\u4EFB\u52A1\u660E\u786E\u6D89\u53CA UI \u65F6\u6CE8\u5165\u3002
683
- PROFILE_FILE="$PROJECT_ROOT/.mancode/project-profile.json"
684
- UI_ASSETS=""
685
- if [ -f "$PROFILE_FILE" ]; then
686
- UI_ASSETS=$(json_get "uiAssets" "$PROFILE_FILE")
687
- fi
688
- if [ "$UI_ASSETS" = "detected" ] && echo "$USER_PROMPT" | grep -qiE "\\b(button|component|page|style|ui|design|layout|css|color|font|theme|card|input|modal|dialog|header|footer|sidebar|dropdown|tooltip|toast|avatar|badge)\\b|\u754C\u9762|\u9875\u9762|\u6309\u94AE|\u6837\u5F0F|\u989C\u8272|\u5B57\u4F53|\u5E03\u5C40|\u7EC4\u4EF6|\u5F39\u7A97|\u5BFC\u822A|\u5361\u7247|\u8F93\u5165\u6846|\u4E3B\u9898|\u7F8E\u5316|\u4F18\u5316.*\u754C\u9762|\u8C03\u6574.*\u6837\u5F0F"; then
689
- if [ -f "$AESTHETICS_FILE" ]; then
690
- MATCH_LEVEL=$(json_get "matchLevel" "$AESTHETICS_FILE")
691
-
692
- if [ "$MATCH_LEVEL" = "high" ]; then
693
- if [ "$HAS_JQ" = "1" ]; then
694
- # \u63D0\u53D6\u6458\u8981 + cap\uFF08docs/07 \xA74.1\uFF1Acolors \u22648, fonts \u22644, \u603B < 800 tokens\uFF09
695
- UI=$(jq -r '.uiLibrary // empty' "$AESTHETICS_FILE" 2>/dev/null)
696
- DARK=$(jq -r '.darkMode // empty' "$AESTHETICS_FILE" 2>/dev/null)
697
- MATCH=$(jq -r '.matchLevel // empty' "$AESTHETICS_FILE" 2>/dev/null)
698
- COLORS=$(jq -r '.colors | to_entries | map(select(.key | test("^[A-Za-z0-9_-]{1,80}$"))) | .[0:8] | map("\\(.key)=\\(.value)") | join(", ")' "$AESTHETICS_FILE" 2>/dev/null)
699
- FONTS=$(jq -r '.fonts | to_entries | map(select(.key | test("^[A-Za-z0-9_-]{1,80}$"))) | .[0:4] | map("\\(.key)=\\(.value | first)") | join(", ")' "$AESTHETICS_FILE" 2>/dev/null)
700
- COMPONENTS=$(jq -r '(.components // []) | map(select(test("^[A-Z][A-Za-z0-9]{0,79}$"))) | .[0:8] | join(", ")' "$AESTHETICS_FILE" 2>/dev/null)
701
- CSS_VARS=$(jq -r '(.cssVariables // {}) | to_entries | map(select(.key | test("^[A-Za-z0-9_-]{1,80}$"))) | .[0:8] | map("--\\(.key)=\\(.value)") | join(", ")' "$AESTHETICS_FILE" 2>/dev/null)
702
-
703
- echo "## \u5BA1\u7F8E token \u6458\u8981"
704
- [ -n "$UI" ] && echo "UI: $(sanitize "$UI")"
705
- [ -n "$DARK" ] && echo "Dark: $(sanitize "$DARK")"
706
- [ -n "$MATCH" ] && echo "Match: $(sanitize "$MATCH")"
707
- [ -n "$COLORS" ] && echo "Colors (\u524D 8): $(sanitize "$COLORS")"
708
- [ -n "$FONTS" ] && echo "Fonts (\u524D 4): $(sanitize "$FONTS")"
709
- [ -n "$COMPONENTS" ] && echo "Components (\u524D 8): $(sanitize "$COMPONENTS")"
710
- [ -n "$CSS_VARS" ] && echo "CSS variables (\u524D 8): $(sanitize "$CSS_VARS")"
711
- echo "\u5B8C\u6574 token: .mancode/aesthetics/style-tokens.json"
712
- echo ""
713
- else
714
- # \u65E0 jq: \u53EA\u8F93\u51FA\u6307\u9488\uFF08cap \u65E0\u6CD5\u4E25\u683C\u6267\u884C\uFF09
715
- echo "## \u5BA1\u7F8E token"
716
- echo "\u8BFB\u53D6 .mancode/aesthetics/style-tokens.json"
717
- echo ""
718
- fi
719
- fi
720
- fi
721
- fi
604
+ var USER_PROMPT_SUBMIT_HOOK = String.raw`#!/usr/bin/env node
605
+ // .mancode/hooks/user-prompt-submit.mjs
606
+ // mancode UserPromptSubmit hook - cross-platform prompt and style context
607
+ import { readFileSync } from 'node:fs';
608
+ import path from 'node:path';
609
+ import { fileURLToPath } from 'node:url';
610
+
611
+ const projectRoot = path.resolve(
612
+ path.dirname(fileURLToPath(import.meta.url)),
613
+ '../..',
614
+ );
615
+ const mancodeDir = path.join(projectRoot, '.mancode');
616
+ const state = readJson(path.join(mancodeDir, 'state.json')) || {};
617
+ const profile = readJson(path.join(mancodeDir, 'project-profile.json')) || {};
618
+ const mode = typeof state.currentMode === 'string' ? state.currentMode : '';
619
+ const output = [];
620
+
621
+ if (mode === 'solo') {
622
+ output.push(
623
+ '## 动手前,先想六个问题:',
624
+ '',
625
+ '1. **为什么做?**',
626
+ ' - 这个改动解决什么问题?',
627
+ '',
628
+ '2. **已经有什么?**',
629
+ ' - 项目里有没有类似的实现可以复用?',
630
+ '',
631
+ '3. **最少改多少?**',
632
+ ' - 能用一行解决吗?能复用现有代码吗?',
633
+ '',
634
+ '4. **能不能不拆新系统?**',
635
+ ' - 不新建文件或模块能完成吗?',
636
+ '',
637
+ '5. **非平凡逻辑怎样最小运行验证?**',
638
+ '',
639
+ '6. **有什么没把握的?**',
640
+ ' - 先自行查代码或文档,最多 2 次工具调用;仍不确定再问用户。',
641
+ '',
642
+ );
643
+ }
644
+
645
+ const rawInput = readFileSync(0, 'utf8');
646
+ const userPrompt = readPrompt(rawInput);
647
+ const planningPattern = /先(?:别|不要|看|看看|调研|分析|评估)|给.*方案|给.*计划|怎么.*做|如何.*做|怎么.*实现|如何.*实现|应该怎么|怎么.*拆|拆分|只给.*计划|不要.*改代码|别.*改代码|不要.*动代码|别.*动代码|评估.*风险|风险.*评估|设计.*方案|架构|迁移|集成|\b(?:plan|planning|research|investigate|approach|proposal|architecture|risk|migration|integration)\b|how (?:should|would|to)|do not (?:edit|modify|change)|don.t (?:edit|modify|change)|no code changes|without changing code/iu;
648
+
649
+ if (mode === 'solo' && planningPattern.test(userPrompt)) {
650
+ output.push(
651
+ '## mancode 自动路由',
652
+ '',
653
+ '这个请求是规划/调研类任务。不要直接进入 solo 实施。',
654
+ "必须先调用 Skill tool,skill='man',把用户原始请求作为 task,执行 Scout 调研、澄清和 Plan Coach plan。",
655
+ '用户只要计划时,在 Step 4 选择“只要计划”;不要切到另一个命令。',
656
+ '',
657
+ );
658
+ }
659
+
660
+ const uiPattern = /\b(?:button|component|page|style|ui|design|layout|css|color|font|theme|card|input|modal|dialog|header|footer|sidebar|dropdown|tooltip|toast|avatar|badge)\b|界面|页面|按钮|样式|颜色|字体|布局|组件|弹窗|导航|卡片|输入框|主题|美化|优化.*界面|调整.*样式/iu;
661
+
662
+ if (profile.uiAssets === 'detected' && uiPattern.test(userPrompt)) {
663
+ appendAestheticSummary(
664
+ output,
665
+ readJson(path.join(mancodeDir, 'aesthetics', 'style-tokens.json')),
666
+ );
667
+ }
668
+
669
+ if (output.length > 0) console.log(output.join('\n'));
670
+
671
+ function appendAestheticSummary(lines, tokens) {
672
+ if (!tokens || tokens.matchLevel !== 'high') return;
673
+
674
+ const colors = safeEntries(tokens.colors, 8).map(
675
+ ([key, value]) => key + '=' + String(value),
676
+ );
677
+ const fonts = safeEntries(tokens.fonts, 4)
678
+ .filter(([, value]) => Array.isArray(value) && value.length > 0)
679
+ .map(([key, value]) => key + '=' + String(value[0]));
680
+ const components = Array.isArray(tokens.components)
681
+ ? tokens.components
682
+ .filter(
683
+ (value) =>
684
+ typeof value === 'string' && /^[A-Z][A-Za-z0-9]{0,79}$/.test(value),
685
+ )
686
+ .slice(0, 8)
687
+ : [];
688
+ const cssVariables = safeEntries(tokens.cssVariables, 8).map(
689
+ ([key, value]) => '--' + key + '=' + String(value),
690
+ );
691
+
692
+ lines.push('## 审美 token 摘要');
693
+ appendValue(lines, 'UI', tokens.uiLibrary);
694
+ appendValue(lines, 'Dark', tokens.darkMode);
695
+ appendValue(lines, 'Match', tokens.matchLevel);
696
+ appendValue(lines, 'Colors (前 8)', colors.join(', '));
697
+ appendValue(lines, 'Fonts (前 4)', fonts.join(', '));
698
+ appendValue(lines, 'Components (前 8)', components.join(', '));
699
+ appendValue(lines, 'CSS variables (前 8)', cssVariables.join(', '));
700
+ lines.push('完整 token: .mancode/aesthetics/style-tokens.json', '');
701
+ }
702
+
703
+ function safeEntries(value, limit) {
704
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return [];
705
+ return Object.entries(value)
706
+ .filter(([key]) => /^[A-Za-z0-9_-]{1,80}$/.test(key))
707
+ .slice(0, limit);
708
+ }
709
+
710
+ function appendValue(lines, label, value) {
711
+ const clean = sanitize(value);
712
+ if (clean) lines.push(label + ': ' + clean);
713
+ }
714
+
715
+ function readPrompt(input) {
716
+ try {
717
+ const parsed = JSON.parse(input);
718
+ return typeof parsed.prompt === 'string' && parsed.prompt
719
+ ? parsed.prompt
720
+ : input;
721
+ } catch {
722
+ return input;
723
+ }
724
+ }
725
+
726
+ function readJson(filePath) {
727
+ try {
728
+ return JSON.parse(readFileSync(filePath, 'utf8'));
729
+ } catch {
730
+ return null;
731
+ }
732
+ }
733
+
734
+ function sanitize(value) {
735
+ return String(value ?? '').replace(/[\r\n]/g, ' ').slice(0, 200);
736
+ }
722
737
  `;
723
738
  var SOLO_SKILL = `# mancode \xB7 solo mode
724
739
 
@@ -1247,7 +1262,7 @@ ${spec.body}
1247
1262
  }
1248
1263
 
1249
1264
  // src/installers/common.ts
1250
- import { chmod, mkdir as mkdir2, readFile as readFile2, stat, writeFile as writeFile2 } from "fs/promises";
1265
+ import { mkdir as mkdir2, readFile as readFile2, stat, writeFile as writeFile2 } from "fs/promises";
1251
1266
  import path2 from "path";
1252
1267
 
1253
1268
  // src/system/team-memory.ts
@@ -1371,6 +1386,7 @@ var DEFAULT_CONFIG = {
1371
1386
  platforms: ["claude-code"],
1372
1387
  cliCommand: "mancode",
1373
1388
  cliArgs: [],
1389
+ teamMode: "auto",
1374
1390
  forceTeamMode: false,
1375
1391
  defaultStyle: null,
1376
1392
  hooks: {
@@ -1437,12 +1453,10 @@ async function readTextIfExists(filePath) {
1437
1453
  }
1438
1454
  }
1439
1455
  async function installHooks(hooksDir) {
1440
- const sessionStartDst = path2.join(hooksDir, "session-start.sh");
1456
+ const sessionStartDst = path2.join(hooksDir, "session-start.mjs");
1441
1457
  await writeFile2(sessionStartDst, SESSION_START_HOOK, "utf-8");
1442
- await chmod(sessionStartDst, 493);
1443
- const userPromptDst = path2.join(hooksDir, "user-prompt-submit.sh");
1458
+ const userPromptDst = path2.join(hooksDir, "user-prompt-submit.mjs");
1444
1459
  await writeFile2(userPromptDst, USER_PROMPT_SUBMIT_HOOK, "utf-8");
1445
- await chmod(userPromptDst, 493);
1446
1460
  }
1447
1461
  async function pathExists(p) {
1448
1462
  try {
@@ -1460,12 +1474,18 @@ function isNodeError(err) {
1460
1474
  var CLAUDE_SKILL_MANAGED_MARKER = "<!-- Managed by mancode:claude-skill. Do not edit this marker. -->";
1461
1475
  var CLAUDE_AGENT_MANAGED_MARKER = "<!-- Managed by mancode:claude-agent. Do not edit this marker. -->";
1462
1476
  var MANCODE_HOOK_COMMANDS = [
1477
+ 'node ".mancode/hooks/session-start.mjs"',
1478
+ 'node ".mancode/hooks/user-prompt-submit.mjs"'
1479
+ ];
1480
+ var LEGACY_MANCODE_HOOK_COMMANDS = [
1463
1481
  "bash .mancode/hooks/session-start.sh",
1464
1482
  "bash .mancode/hooks/user-prompt-submit.sh"
1465
1483
  ];
1466
1484
  function isGeneratedMancodeHookCommand(command) {
1467
1485
  const normalized = command.trim();
1468
- return MANCODE_HOOK_COMMANDS.some((item) => item === normalized);
1486
+ return [...MANCODE_HOOK_COMMANDS, ...LEGACY_MANCODE_HOOK_COMMANDS].some(
1487
+ (item) => item === normalized
1488
+ );
1469
1489
  }
1470
1490
  var LEGACY_CLAUDE_SKILL_SETTINGS = {
1471
1491
  solo: ".claude/skills/mancode-solo.md",
@@ -1494,6 +1514,14 @@ async function installClaudeCode(projectRoot, options) {
1494
1514
  await installAgents(path3.join(claudeDir, "agents"), force);
1495
1515
  }
1496
1516
  await updateClaudeSettings(claudeDir, settings);
1517
+ await removeLegacyHookFiles(projectRoot);
1518
+ }
1519
+ async function removeLegacyHookFiles(projectRoot) {
1520
+ const hooksDir = path3.join(projectRoot, ".mancode", "hooks");
1521
+ await Promise.all([
1522
+ rm(path3.join(hooksDir, "session-start.sh"), { force: true }),
1523
+ rm(path3.join(hooksDir, "user-prompt-submit.sh"), { force: true })
1524
+ ]);
1497
1525
  }
1498
1526
  async function installSoloSkill(skillsDir, force) {
1499
1527
  await installProjectSkill(
@@ -1747,100 +1775,10 @@ function isNodeError2(err) {
1747
1775
  return err instanceof Error && "code" in err;
1748
1776
  }
1749
1777
 
1750
- // src/installers/codex.ts
1751
- import { writeFile as writeFile5 } from "fs/promises";
1778
+ // src/installers/cursor.ts
1779
+ import { mkdir as mkdir5, readFile as readFile6, rm as rm3, writeFile as writeFile5 } from "fs/promises";
1752
1780
  import path6 from "path";
1753
1781
 
1754
- // src/installers/managed-block.ts
1755
- var DEFAULT_MANCODE_START_MARKER = "<!-- mancode:start -->";
1756
- var DEFAULT_MANCODE_END_MARKER = "<!-- mancode:end -->";
1757
- function removeManagedBlock(existing, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
1758
- const start = findMarkerLine(existing, startMarker);
1759
- const end = findMarkerLine(existing, endMarker);
1760
- if (start === null && end === null) return existing;
1761
- if (start === null || end === null) return existing;
1762
- if (end.start < start.start) return existing;
1763
- const before = existing.slice(0, start.start);
1764
- const after = existing.slice(end.end);
1765
- const merged = `${before}${after}`;
1766
- return cleanUpOrphanedNewlines(merged);
1767
- }
1768
- function hasManagedBlock(existing, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
1769
- const start = findMarkerLine(existing, startMarker);
1770
- const end = findMarkerLine(existing, endMarker);
1771
- return start !== null && end !== null && end.start > start.start;
1772
- }
1773
- function cleanUpOrphanedNewlines(content) {
1774
- const trimmed = content.replace(/\n{3,}/gu, "\n\n").replace(/\n+$/u, "\n");
1775
- return trimmed || "";
1776
- }
1777
- function replaceManagedBlock(existing, block, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
1778
- const normalizedBlock = normalizeManagedBlock(block, startMarker, endMarker);
1779
- const start = findMarkerLine(existing, startMarker);
1780
- const end = findMarkerLine(existing, endMarker);
1781
- if (start === null !== (end === null)) {
1782
- throw new Error("managed block is malformed: missing start or end marker");
1783
- }
1784
- if (start === null && end === null) {
1785
- const trimmedExisting = trimTrailingNewlines(existing);
1786
- if (!trimmedExisting) return `${normalizedBlock}
1787
- `;
1788
- return `${trimmedExisting}
1789
-
1790
- ${normalizedBlock}
1791
- `;
1792
- }
1793
- if (!start || !end) {
1794
- throw new Error("managed block is malformed: missing start or end marker");
1795
- }
1796
- if (end.start < start.start) {
1797
- throw new Error("managed block is malformed: end marker precedes start");
1798
- }
1799
- return `${existing.slice(0, start.start)}${normalizedBlock}${existing.slice(
1800
- end.end
1801
- )}`;
1802
- }
1803
- function normalizeManagedBlock(block, startMarker, endMarker) {
1804
- const trimmedBlock = block.trim();
1805
- const hasStart = trimmedBlock.startsWith(startMarker);
1806
- const hasEnd = trimmedBlock.endsWith(endMarker);
1807
- if (hasStart && hasEnd) return trimmedBlock;
1808
- if (hasStart || hasEnd) {
1809
- throw new Error("managed block content includes only one marker");
1810
- }
1811
- return `${startMarker}
1812
- ${trimmedBlock}
1813
- ${endMarker}`;
1814
- }
1815
- function trimTrailingNewlines(value) {
1816
- return value.replace(/\n+$/u, "");
1817
- }
1818
- function findMarkerLine(content, marker) {
1819
- let offset = 0;
1820
- let inFence = null;
1821
- for (const lineWithBreak of content.matchAll(/[^\n]*(?:\n|$)/gu)) {
1822
- const rawLine = lineWithBreak[0];
1823
- if (!rawLine) break;
1824
- const line = rawLine.replace(/\n$/u, "").replace(/\r$/u, "");
1825
- const fence = line.match(/^(`{3,}|~{3,})/u)?.[1];
1826
- if (fence) {
1827
- const char = fence[0];
1828
- if (!inFence) {
1829
- inFence = { char, length: fence.length };
1830
- } else if (inFence.char === char && fence.length >= inFence.length) {
1831
- inFence = null;
1832
- }
1833
- } else if (!inFence && line === marker) {
1834
- return {
1835
- start: offset,
1836
- end: offset + marker.length
1837
- };
1838
- }
1839
- offset += rawLine.length;
1840
- }
1841
- return null;
1842
- }
1843
-
1844
1782
  // src/installers/mode-skills.ts
1845
1783
  import { mkdir as mkdir4, readFile as readFile4, readdir as readdir2, rm as rm2, writeFile as writeFile4 } from "fs/promises";
1846
1784
  import path4 from "path";
@@ -2426,99 +2364,7 @@ function formatValue(value) {
2426
2364
  return "";
2427
2365
  }
2428
2366
 
2429
- // src/installers/codex.ts
2430
- async function installCodex(projectRoot, options) {
2431
- await installMancodeCore(projectRoot);
2432
- const agentsPath = path6.join(projectRoot, "AGENTS.md");
2433
- const existing = await readTextIfExists(agentsPath);
2434
- const sharedContent = await generateSharedContent(projectRoot, {
2435
- platform: "codex",
2436
- displayName: "Codex (ChatGPT desktop/CLI)",
2437
- capabilities: {
2438
- slashCommands: "partial",
2439
- subagents: false,
2440
- hooks: false,
2441
- skills: "agents-skills"
2442
- },
2443
- minimal: options.minimal,
2444
- techStack: options.techStack,
2445
- uiLibrary: options.uiLibrary,
2446
- projectProfile: options.projectProfile
2447
- });
2448
- const block = [
2449
- DEFAULT_MANCODE_START_MARKER,
2450
- "<!-- Managed by mancode. Do not edit this block manually. -->",
2451
- "",
2452
- "# mancode Configuration",
2453
- "",
2454
- sharedContent.trim(),
2455
- DEFAULT_MANCODE_END_MARKER
2456
- ].join("\n");
2457
- await writeFile5(agentsPath, replaceManagedBlock(existing, block), "utf-8");
2458
- await installCodexSkills(projectRoot, options.minimal ?? false);
2459
- }
2460
-
2461
- // src/installers/copilot.ts
2462
- import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
2463
- import path7 from "path";
2464
- async function installCopilot(projectRoot, options) {
2465
- await installMancodeCore(projectRoot);
2466
- const githubDir = path7.join(projectRoot, ".github");
2467
- await mkdir5(githubDir, { recursive: true });
2468
- const instructionsPath = path7.join(githubDir, "copilot-instructions.md");
2469
- const existing = await readTextIfExists(instructionsPath);
2470
- const sharedContent = await generateSharedContent(projectRoot, {
2471
- platform: "copilot",
2472
- displayName: "GitHub Copilot",
2473
- capabilities: {
2474
- slashCommands: "none",
2475
- subagents: false,
2476
- hooks: false,
2477
- skills: "instructions"
2478
- },
2479
- minimal: true,
2480
- techStack: options.techStack,
2481
- uiLibrary: options.uiLibrary,
2482
- projectProfile: options.projectProfile
2483
- });
2484
- const sections = [
2485
- DEFAULT_MANCODE_START_MARKER,
2486
- "<!-- Managed by mancode. Do not edit this block manually. -->",
2487
- "",
2488
- "# mancode for GitHub Copilot",
2489
- "",
2490
- sharedContent.trim()
2491
- ];
2492
- if (!options.minimal) {
2493
- sections.push("", renderCopilotPromptConventions());
2494
- }
2495
- sections.push(DEFAULT_MANCODE_END_MARKER);
2496
- await writeFile6(
2497
- instructionsPath,
2498
- replaceManagedBlock(existing, sections.join("\n")),
2499
- "utf-8"
2500
- );
2501
- await installCopilotPrompts(projectRoot, options.minimal ?? false);
2502
- }
2503
- function renderCopilotPromptConventions() {
2504
- return [
2505
- "## mancode Prompt Conventions",
2506
- "",
2507
- "GitHub Copilot does not provide native mancode slash commands, hooks, or isolated subagents. Treat these names as user prompt conventions:",
2508
- "",
2509
- "- man: use progressive research, planning, implementation, verification, and bounded risk-based review.",
2510
- "- mamba: diagnose bugs and validate real user flows or regressions.",
2511
- "- manteam: read team memory and write handoff-friendly summaries.",
2512
- "- manps: prefer `mancode manps [area]` before cleanup.",
2513
- "- mansolo: exit any active mode and return to default solo behavior.",
2514
- "",
2515
- "These correspond to prompt files in `.github/prompts/`. Select the matching prompt to activate a mode."
2516
- ].join("\n");
2517
- }
2518
-
2519
2367
  // src/installers/cursor.ts
2520
- import { mkdir as mkdir6, readFile as readFile6, rm as rm3, writeFile as writeFile7 } from "fs/promises";
2521
- import path8 from "path";
2522
2368
  var MANCODE_CURSOR_CORE_RULE_FILES = [
2523
2369
  "mancode-context.mdc",
2524
2370
  "mancode-practice.mdc",
@@ -2537,8 +2383,8 @@ var MANCODE_CURSOR_RULE_FILES = [
2537
2383
  ];
2538
2384
  async function installCursor(projectRoot, options) {
2539
2385
  await installMancodeCore(projectRoot);
2540
- const rulesDir = path8.join(projectRoot, ".cursor", "rules");
2541
- await mkdir6(rulesDir, { recursive: true });
2386
+ const rulesDir = path6.join(projectRoot, ".cursor", "rules");
2387
+ await mkdir5(rulesDir, { recursive: true });
2542
2388
  const sharedContent = await generateSharedContent(projectRoot, {
2543
2389
  platform: "cursor",
2544
2390
  displayName: "Cursor",
@@ -2626,14 +2472,14 @@ async function writeRule(rulesDir, fileName, description, alwaysApply, body) {
2626
2472
 
2627
2473
  ${body.trim()}
2628
2474
  `;
2629
- const rulePath = path8.join(rulesDir, fileName);
2475
+ const rulePath = path6.join(rulesDir, fileName);
2630
2476
  const existing = await readTextIfExists4(rulePath);
2631
2477
  if (existing !== null && !isGeneratedCursorRule(existing, fileName)) {
2632
2478
  throw new Error(
2633
2479
  `refusing to overwrite user-authored Cursor rule: ${rulePath}`
2634
2480
  );
2635
2481
  }
2636
- await writeFile7(
2482
+ await writeFile5(
2637
2483
  rulePath,
2638
2484
  content.replace("---\n\n", `---
2639
2485
 
@@ -2647,17 +2493,17 @@ async function removeAdvancedRules(rulesDir) {
2647
2493
  for (const fileName of MANCODE_CURSOR_ADVANCED_RULE_FILES) {
2648
2494
  await removeGeneratedCursorRule(rulesDir, fileName);
2649
2495
  }
2650
- await removeLegacyCursorRules(path8.dirname(path8.dirname(rulesDir)));
2496
+ await removeLegacyCursorRules(path6.dirname(path6.dirname(rulesDir)));
2651
2497
  }
2652
2498
  async function removeCursorGeneratedRules(projectRoot) {
2653
- const rulesDir = path8.join(projectRoot, ".cursor", "rules");
2499
+ const rulesDir = path6.join(projectRoot, ".cursor", "rules");
2654
2500
  for (const fileName of MANCODE_CURSOR_RULE_FILES) {
2655
2501
  await removeGeneratedCursorRule(rulesDir, fileName);
2656
2502
  }
2657
2503
  await removeLegacyCursorRules(projectRoot);
2658
2504
  }
2659
2505
  async function removeLegacyCursorRules(projectRoot) {
2660
- const legacyPath = path8.join(
2506
+ const legacyPath = path6.join(
2661
2507
  projectRoot,
2662
2508
  ".cursor",
2663
2509
  "rules",
@@ -2672,7 +2518,7 @@ async function removeLegacyCursorRules(projectRoot) {
2672
2518
  }
2673
2519
  }
2674
2520
  async function removeGeneratedCursorRule(rulesDir, fileName) {
2675
- const rulePath = path8.join(rulesDir, fileName);
2521
+ const rulePath = path6.join(rulesDir, fileName);
2676
2522
  const content = await readTextIfExists4(rulePath);
2677
2523
  if (content && isGeneratedCursorRule(content, fileName)) {
2678
2524
  await rm3(rulePath, { force: true });
@@ -2742,16 +2588,200 @@ function renderManpsRule() {
2742
2588
  return renderModeSkill("manps", "/");
2743
2589
  }
2744
2590
 
2745
- // src/installers/zcode.ts
2746
- import { writeFile as writeFile8 } from "fs/promises";
2747
- import path9 from "path";
2748
- var ZCODE_MANCODE_START_MARKER = "<!-- mancode:zcode:start -->";
2749
- var ZCODE_MANCODE_END_MARKER = "<!-- mancode:zcode:end -->";
2750
- async function installZcode(projectRoot, options) {
2751
- await installMancodeCore(projectRoot);
2752
- const agentsPath = path9.join(projectRoot, "AGENTS.md");
2753
- const existing = await readTextIfExists(agentsPath);
2754
- const sharedContent = await generateSharedContent(projectRoot, {
2591
+ // src/installers/codex.ts
2592
+ import { writeFile as writeFile6 } from "fs/promises";
2593
+ import path7 from "path";
2594
+
2595
+ // src/installers/managed-block.ts
2596
+ var DEFAULT_MANCODE_START_MARKER = "<!-- mancode:start -->";
2597
+ var DEFAULT_MANCODE_END_MARKER = "<!-- mancode:end -->";
2598
+ function removeManagedBlock(existing, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
2599
+ const start = findMarkerLine(existing, startMarker);
2600
+ const end = findMarkerLine(existing, endMarker);
2601
+ if (start === null && end === null) return existing;
2602
+ if (start === null || end === null) return existing;
2603
+ if (end.start < start.start) return existing;
2604
+ const before = existing.slice(0, start.start);
2605
+ const after = existing.slice(end.end);
2606
+ const merged = `${before}${after}`;
2607
+ return cleanUpOrphanedNewlines(merged);
2608
+ }
2609
+ function hasManagedBlock(existing, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
2610
+ const start = findMarkerLine(existing, startMarker);
2611
+ const end = findMarkerLine(existing, endMarker);
2612
+ return start !== null && end !== null && end.start > start.start;
2613
+ }
2614
+ function cleanUpOrphanedNewlines(content) {
2615
+ const trimmed = content.replace(/\n{3,}/gu, "\n\n").replace(/\n+$/u, "\n");
2616
+ return trimmed || "";
2617
+ }
2618
+ function replaceManagedBlock(existing, block, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
2619
+ const normalizedBlock = normalizeManagedBlock(block, startMarker, endMarker);
2620
+ const start = findMarkerLine(existing, startMarker);
2621
+ const end = findMarkerLine(existing, endMarker);
2622
+ if (start === null !== (end === null)) {
2623
+ throw new Error("managed block is malformed: missing start or end marker");
2624
+ }
2625
+ if (start === null && end === null) {
2626
+ const trimmedExisting = trimTrailingNewlines(existing);
2627
+ if (!trimmedExisting) return `${normalizedBlock}
2628
+ `;
2629
+ return `${trimmedExisting}
2630
+
2631
+ ${normalizedBlock}
2632
+ `;
2633
+ }
2634
+ if (!start || !end) {
2635
+ throw new Error("managed block is malformed: missing start or end marker");
2636
+ }
2637
+ if (end.start < start.start) {
2638
+ throw new Error("managed block is malformed: end marker precedes start");
2639
+ }
2640
+ return `${existing.slice(0, start.start)}${normalizedBlock}${existing.slice(
2641
+ end.end
2642
+ )}`;
2643
+ }
2644
+ function normalizeManagedBlock(block, startMarker, endMarker) {
2645
+ const trimmedBlock = block.trim();
2646
+ const hasStart = trimmedBlock.startsWith(startMarker);
2647
+ const hasEnd = trimmedBlock.endsWith(endMarker);
2648
+ if (hasStart && hasEnd) return trimmedBlock;
2649
+ if (hasStart || hasEnd) {
2650
+ throw new Error("managed block content includes only one marker");
2651
+ }
2652
+ return `${startMarker}
2653
+ ${trimmedBlock}
2654
+ ${endMarker}`;
2655
+ }
2656
+ function trimTrailingNewlines(value) {
2657
+ return value.replace(/\n+$/u, "");
2658
+ }
2659
+ function findMarkerLine(content, marker) {
2660
+ let offset = 0;
2661
+ let inFence = null;
2662
+ for (const lineWithBreak of content.matchAll(/[^\n]*(?:\n|$)/gu)) {
2663
+ const rawLine = lineWithBreak[0];
2664
+ if (!rawLine) break;
2665
+ const line = rawLine.replace(/\n$/u, "").replace(/\r$/u, "");
2666
+ const fence = line.match(/^(`{3,}|~{3,})/u)?.[1];
2667
+ if (fence) {
2668
+ const char = fence[0];
2669
+ if (!inFence) {
2670
+ inFence = { char, length: fence.length };
2671
+ } else if (inFence.char === char && fence.length >= inFence.length) {
2672
+ inFence = null;
2673
+ }
2674
+ } else if (!inFence && line === marker) {
2675
+ return {
2676
+ start: offset,
2677
+ end: offset + marker.length
2678
+ };
2679
+ }
2680
+ offset += rawLine.length;
2681
+ }
2682
+ return null;
2683
+ }
2684
+
2685
+ // src/installers/codex.ts
2686
+ async function installCodex(projectRoot, options) {
2687
+ await installMancodeCore(projectRoot);
2688
+ const agentsPath = path7.join(projectRoot, "AGENTS.md");
2689
+ const existing = await readTextIfExists(agentsPath);
2690
+ const sharedContent = await generateSharedContent(projectRoot, {
2691
+ platform: "codex",
2692
+ displayName: "Codex (ChatGPT desktop/CLI)",
2693
+ capabilities: {
2694
+ slashCommands: "partial",
2695
+ subagents: false,
2696
+ hooks: false,
2697
+ skills: "agents-skills"
2698
+ },
2699
+ minimal: options.minimal,
2700
+ techStack: options.techStack,
2701
+ uiLibrary: options.uiLibrary,
2702
+ projectProfile: options.projectProfile
2703
+ });
2704
+ const block = [
2705
+ DEFAULT_MANCODE_START_MARKER,
2706
+ "<!-- Managed by mancode. Do not edit this block manually. -->",
2707
+ "",
2708
+ "# mancode Configuration",
2709
+ "",
2710
+ sharedContent.trim(),
2711
+ DEFAULT_MANCODE_END_MARKER
2712
+ ].join("\n");
2713
+ await writeFile6(agentsPath, replaceManagedBlock(existing, block), "utf-8");
2714
+ await installCodexSkills(projectRoot, options.minimal ?? false);
2715
+ }
2716
+
2717
+ // src/installers/copilot.ts
2718
+ import { mkdir as mkdir6, writeFile as writeFile7 } from "fs/promises";
2719
+ import path8 from "path";
2720
+ async function installCopilot(projectRoot, options) {
2721
+ await installMancodeCore(projectRoot);
2722
+ const githubDir = path8.join(projectRoot, ".github");
2723
+ await mkdir6(githubDir, { recursive: true });
2724
+ const instructionsPath = path8.join(githubDir, "copilot-instructions.md");
2725
+ const existing = await readTextIfExists(instructionsPath);
2726
+ const sharedContent = await generateSharedContent(projectRoot, {
2727
+ platform: "copilot",
2728
+ displayName: "GitHub Copilot",
2729
+ capabilities: {
2730
+ slashCommands: "none",
2731
+ subagents: false,
2732
+ hooks: false,
2733
+ skills: "instructions"
2734
+ },
2735
+ minimal: true,
2736
+ techStack: options.techStack,
2737
+ uiLibrary: options.uiLibrary,
2738
+ projectProfile: options.projectProfile
2739
+ });
2740
+ const sections = [
2741
+ DEFAULT_MANCODE_START_MARKER,
2742
+ "<!-- Managed by mancode. Do not edit this block manually. -->",
2743
+ "",
2744
+ "# mancode for GitHub Copilot",
2745
+ "",
2746
+ sharedContent.trim()
2747
+ ];
2748
+ if (!options.minimal) {
2749
+ sections.push("", renderCopilotPromptConventions());
2750
+ }
2751
+ sections.push(DEFAULT_MANCODE_END_MARKER);
2752
+ await writeFile7(
2753
+ instructionsPath,
2754
+ replaceManagedBlock(existing, sections.join("\n")),
2755
+ "utf-8"
2756
+ );
2757
+ await installCopilotPrompts(projectRoot, options.minimal ?? false);
2758
+ }
2759
+ function renderCopilotPromptConventions() {
2760
+ return [
2761
+ "## mancode Prompt Conventions",
2762
+ "",
2763
+ "GitHub Copilot does not provide native mancode slash commands, hooks, or isolated subagents. Treat these names as user prompt conventions:",
2764
+ "",
2765
+ "- man: use progressive research, planning, implementation, verification, and bounded risk-based review.",
2766
+ "- mamba: diagnose bugs and validate real user flows or regressions.",
2767
+ "- manteam: read team memory and write handoff-friendly summaries.",
2768
+ "- manps: prefer `mancode manps [area]` before cleanup.",
2769
+ "- mansolo: exit any active mode and return to default solo behavior.",
2770
+ "",
2771
+ "These correspond to prompt files in `.github/prompts/`. Select the matching prompt to activate a mode."
2772
+ ].join("\n");
2773
+ }
2774
+
2775
+ // src/installers/zcode.ts
2776
+ import { writeFile as writeFile8 } from "fs/promises";
2777
+ import path9 from "path";
2778
+ var ZCODE_MANCODE_START_MARKER = "<!-- mancode:zcode:start -->";
2779
+ var ZCODE_MANCODE_END_MARKER = "<!-- mancode:zcode:end -->";
2780
+ async function installZcode(projectRoot, options) {
2781
+ await installMancodeCore(projectRoot);
2782
+ const agentsPath = path9.join(projectRoot, "AGENTS.md");
2783
+ const existing = await readTextIfExists(agentsPath);
2784
+ const sharedContent = await generateSharedContent(projectRoot, {
2755
2785
  platform: "zcode",
2756
2786
  displayName: "ZCode",
2757
2787
  capabilities: {
@@ -2860,11 +2890,12 @@ function isPlatformName(platform) {
2860
2890
  }
2861
2891
 
2862
2892
  // src/system/detect-team.ts
2863
- import { exec } from "child_process";
2893
+ import { execFile } from "child_process";
2894
+ import { access as access2 } from "fs/promises";
2864
2895
  import path10 from "path";
2865
2896
  import process2 from "process";
2866
2897
  import { promisify } from "util";
2867
- var execAsync = promisify(exec);
2898
+ var execFileAsync = promisify(execFile);
2868
2899
  var NO_TEAM = {
2869
2900
  isTeam: false,
2870
2901
  contributors: 1,
@@ -2872,106 +2903,186 @@ var NO_TEAM = {
2872
2903
  hasRemote: false
2873
2904
  };
2874
2905
  var REMOTE_PATTERN = /github\.com|gitlab\.com|bitbucket\.org/;
2875
- var EXEC_OPTS = {
2876
- shell: "/bin/bash",
2877
- encoding: "utf-8",
2878
- env: {
2879
- ...process2.env,
2880
- GIT_PAGER: "cat",
2881
- PAGER: "cat",
2882
- GIT_TERMINAL_PROMPT: "0"
2883
- }
2906
+ var GIT_ENV = {
2907
+ ...process2.env,
2908
+ GIT_PAGER: "cat",
2909
+ PAGER: "cat",
2910
+ GIT_TERMINAL_PROMPT: "0"
2884
2911
  };
2885
2912
  async function detectTeamStatus(projectRoot) {
2886
- const gitDir = path10.join(projectRoot, ".git");
2887
2913
  try {
2888
- await execAsync("git rev-parse --is-inside-work-tree", {
2889
- cwd: projectRoot,
2890
- ...EXEC_OPTS
2891
- });
2892
- } catch {
2893
- return NO_TEAM;
2894
- }
2895
- try {
2896
- await execAsync(`test -d ${JSON.stringify(gitDir)}`, {
2897
- cwd: projectRoot,
2898
- ...EXEC_OPTS
2899
- });
2914
+ await access2(path10.join(projectRoot, ".git"));
2915
+ const inside = await runGit(projectRoot, [
2916
+ "rev-parse",
2917
+ "--is-inside-work-tree"
2918
+ ]);
2919
+ if (inside.trim() !== "true") return NO_TEAM;
2920
+ const [allEmails, recentEmails, remotes] = await Promise.all([
2921
+ runGit(projectRoot, ["log", "--all", "--format=%ae"]),
2922
+ runGit(projectRoot, ["log", "--since=30 days ago", "--format=%ae"]),
2923
+ runGit(projectRoot, ["remote", "-v"])
2924
+ ]);
2925
+ const contributors = countUniqueLines(allEmails);
2926
+ const recentActive = countUniqueLines(recentEmails);
2927
+ const hasRemote = REMOTE_PATTERN.test(remotes);
2928
+ return {
2929
+ isTeam: contributors > 1 && hasRemote && recentActive > 1,
2930
+ contributors: contributors || 1,
2931
+ recentActive: recentActive || 1,
2932
+ hasRemote
2933
+ };
2900
2934
  } catch {
2901
2935
  return NO_TEAM;
2902
2936
  }
2903
- const contributors = await countUniqueEmails(
2904
- "git log --all --format='%ae'",
2905
- projectRoot
2906
- );
2907
- const recentActive = await countUniqueEmails(
2908
- "git log --since='30 days ago' --format='%ae'",
2909
- projectRoot
2910
- );
2911
- const hasRemote = await checkRemote(projectRoot);
2912
- const safeContributors = Number.isFinite(contributors) ? contributors : 0;
2913
- const safeRecent = Number.isFinite(recentActive) ? recentActive : 0;
2914
- const isTeam = safeContributors > 1 && hasRemote && safeRecent > 1;
2915
- return {
2916
- isTeam,
2917
- contributors: safeContributors || 1,
2918
- recentActive: safeRecent || 1,
2919
- hasRemote
2920
- };
2921
2937
  }
2922
- async function countUniqueEmails(command, cwd) {
2923
- try {
2924
- const { stdout } = await execAsync(
2925
- `${command} 2>/dev/null | sort -u | grep -c . || true`,
2926
- { cwd, ...EXEC_OPTS }
2927
- );
2928
- const n = Number.parseInt(stdout.trim(), 10);
2929
- return Number.isFinite(n) ? n : 0;
2930
- } catch {
2931
- return 0;
2932
- }
2938
+ async function runGit(cwd, args) {
2939
+ const { stdout: stdout2 } = await execFileAsync("git", args, {
2940
+ cwd,
2941
+ encoding: "utf-8",
2942
+ env: GIT_ENV,
2943
+ windowsHide: true
2944
+ });
2945
+ return stdout2;
2933
2946
  }
2934
- async function checkRemote(cwd) {
2947
+ function countUniqueLines(output) {
2948
+ return new Set(
2949
+ output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
2950
+ ).size;
2951
+ }
2952
+
2953
+ // src/system/detect.ts
2954
+ import { execFile as execFile2 } from "child_process";
2955
+ import process3 from "process";
2956
+ import { promisify as promisify2 } from "util";
2957
+ var execFileAsync2 = promisify2(execFile2);
2958
+ async function detectSystemDeps(env = process3.env) {
2935
2959
  try {
2936
- const { stdout } = await execAsync("git remote -v 2>/dev/null || true", {
2937
- cwd,
2938
- ...EXEC_OPTS
2960
+ await execFileAsync2("git", ["--version"], {
2961
+ env,
2962
+ windowsHide: true
2939
2963
  });
2940
- return REMOTE_PATTERN.test(stdout);
2964
+ return { git: true };
2941
2965
  } catch {
2942
- return false;
2966
+ return { git: false };
2943
2967
  }
2944
2968
  }
2945
2969
 
2946
- // src/system/detect.ts
2947
- import { exec as exec2 } from "child_process";
2948
- import { promisify as promisify2 } from "util";
2949
- var execAsync2 = promisify2(exec2);
2950
- async function detectSystemDeps() {
2951
- const ALLOWED_COMMANDS = /* @__PURE__ */ new Set(["bash", "git", "jq", "node"]);
2952
- const check = async (cmd) => {
2953
- if (!ALLOWED_COMMANDS.has(cmd)) {
2954
- throw new Error(`Unsupported dependency check: ${cmd}`);
2955
- }
2970
+ // src/system/init-onboarding.ts
2971
+ import { promises as fs } from "fs";
2972
+ import path11 from "path";
2973
+ import process4 from "process";
2974
+ import { stdin, stdout } from "process";
2975
+ import { createInterface } from "readline/promises";
2976
+ var ALL_PLATFORMS = Object.keys(PLATFORM_INSTALLERS);
2977
+ function detectInitLocale(override, environment = process4.env, systemLocale = Intl.DateTimeFormat().resolvedOptions().locale) {
2978
+ if (override) return parseLocale(override);
2979
+ const environmentLocale = environment.LC_ALL ?? environment.LC_MESSAGES ?? environment.LANG;
2980
+ return parseLocale(environmentLocale) ?? parseLocale(systemLocale) ?? "en";
2981
+ }
2982
+ function parseLocale(value) {
2983
+ if (!value) return null;
2984
+ const normalized = value.toLowerCase().replace("_", "-");
2985
+ if (normalized === "zh" || normalized.startsWith("zh-")) return "zh-CN";
2986
+ if (normalized === "en" || normalized.startsWith("en-")) return "en";
2987
+ return null;
2988
+ }
2989
+ function parsePlatformSelection(value) {
2990
+ const normalized = value.trim().toLowerCase();
2991
+ if (normalized === "all" || normalized === "\u5168\u90E8") return [...ALL_PLATFORMS];
2992
+ const choices = normalized.split(",").map((item) => item.trim()).filter(Boolean);
2993
+ if (choices.length === 0) return null;
2994
+ if (!choices.every((item) => item in PLATFORM_INSTALLERS)) {
2995
+ return null;
2996
+ }
2997
+ return [...new Set(choices)];
2998
+ }
2999
+ async function detectPlatformHints(rootDir, environment = process4.env) {
3000
+ const hints = /* @__PURE__ */ new Set();
3001
+ if (environment.CLAUDECODE || environment.CLAUDE_CODE)
3002
+ hints.add("claude-code");
3003
+ if (environment.CODEX_HOME) hints.add("codex");
3004
+ if (environment.CURSOR_TRACE_ID) hints.add("cursor");
3005
+ if (environment.COPILOT_AGENT || environment.GITHUB_COPILOT)
3006
+ hints.add("copilot");
3007
+ const exists = async (relative) => {
2956
3008
  try {
2957
- await execAsync2(`command -v ${cmd}`, { shell: "/bin/bash" });
3009
+ await fs.access(path11.join(rootDir, relative));
2958
3010
  return true;
2959
3011
  } catch {
2960
3012
  return false;
2961
3013
  }
2962
3014
  };
2963
- const [bash, git, jq, node] = await Promise.all([
2964
- check("bash"),
2965
- check("git"),
2966
- check("jq"),
2967
- check("node")
2968
- ]);
2969
- return { bash, git, jq, node };
3015
+ if (await exists(".claude")) hints.add("claude-code");
3016
+ if (await exists(".cursor")) hints.add("cursor");
3017
+ if (await exists(".github/copilot-instructions.md")) hints.add("copilot");
3018
+ return ALL_PLATFORMS.filter((platform) => hints.has(platform));
3019
+ }
3020
+ function createTerminalPrompter() {
3021
+ return {
3022
+ async confirmGenericProject({ rootDir, locale }) {
3023
+ const rl = createInterface({ input: stdin, output: stdout });
3024
+ try {
3025
+ if (locale === "zh-CN") {
3026
+ console.log("\u5F53\u524D\u76EE\u5F55\u6CA1\u6709\u8BC6\u522B\u5230\u9879\u76EE\u6587\u4EF6\u3002");
3027
+ console.log(`\u76EE\u5F55\uFF1A${rootDir}`);
3028
+ console.log("\u8FD9\u662F\u4E00\u4E2A\u65B0\u9879\u76EE\u5417\uFF1F");
3029
+ console.log("[y] \u521D\u59CB\u5316\u4E3A\u901A\u7528\u9879\u76EE");
3030
+ console.log("[n] \u9000\u51FA");
3031
+ const answer2 = await rl.question("\u8BF7\u9009\u62E9 [y/N]: ");
3032
+ return ["y", "yes", "\u662F"].includes(answer2.trim().toLowerCase());
3033
+ }
3034
+ console.log("No project files were detected in the current directory.");
3035
+ console.log(`Directory: ${rootDir}`);
3036
+ console.log("Is this a new project?");
3037
+ console.log("[y] Initialize as a generic project");
3038
+ console.log("[n] Exit");
3039
+ const answer = await rl.question("Choose [y/N]: ");
3040
+ return ["y", "yes"].includes(answer.trim().toLowerCase());
3041
+ } finally {
3042
+ rl.close();
3043
+ }
3044
+ },
3045
+ async selectPlatforms({ locale, detected }) {
3046
+ const rl = createInterface({ input: stdin, output: stdout });
3047
+ try {
3048
+ const names = ALL_PLATFORMS.map((platform, index) => {
3049
+ const suffix = detected.includes(platform) ? locale === "zh-CN" ? "\uFF08\u5DF2\u68C0\u6D4B\u5230\uFF09" : " (detected)" : "";
3050
+ return `${index + 1}. ${PLATFORM_INSTALLERS[platform].displayName}${suffix}`;
3051
+ });
3052
+ console.log(
3053
+ locale === "zh-CN" ? "\n\u9009\u62E9\u8981\u521D\u59CB\u5316\u7684\u5E73\u53F0\uFF1A" : "\nChoose platforms to initialize:"
3054
+ );
3055
+ console.log(names.join("\n"));
3056
+ console.log(locale === "zh-CN" ? "a. \u5168\u90E8\u5E73\u53F0" : "a. All platforms");
3057
+ console.log(
3058
+ locale === "zh-CN" ? "\u53EF\u8F93\u5165\u7F16\u53F7\uFF08\u4F8B\u5982 1,3\uFF09\u6216 a\u3002" : "Enter numbers (for example 1,3) or a."
3059
+ );
3060
+ const answer = (await rl.question(locale === "zh-CN" ? "\u9009\u62E9: " : "Selection: ")).trim().toLowerCase();
3061
+ if (answer === "a" || answer === "all" || answer === "\u5168\u90E8")
3062
+ return [...ALL_PLATFORMS];
3063
+ const indexes = answer.split(",").map((item) => Number(item.trim()));
3064
+ if (!indexes.length || indexes.some(
3065
+ (index) => !Number.isInteger(index) || index < 1 || index > ALL_PLATFORMS.length
3066
+ )) {
3067
+ return null;
3068
+ }
3069
+ const selected = [];
3070
+ for (const index of indexes) {
3071
+ const platform = ALL_PLATFORMS[index - 1];
3072
+ if (!platform) return null;
3073
+ selected.push(platform);
3074
+ }
3075
+ return [...new Set(selected)];
3076
+ } finally {
3077
+ rl.close();
3078
+ }
3079
+ }
3080
+ };
2970
3081
  }
2971
3082
 
2972
3083
  // src/system/project-profile.ts
2973
3084
  import { readFile as readFile7, readdir as readdir3, stat as stat2 } from "fs/promises";
2974
- import path11 from "path";
3085
+ import path12 from "path";
2975
3086
  var PROJECT_MANIFESTS = [
2976
3087
  "package.json",
2977
3088
  "pyproject.toml",
@@ -3004,12 +3115,12 @@ async function detectProjectProfile(projectRoot) {
3004
3115
  "data",
3005
3116
  "notebooks"
3006
3117
  ]);
3007
- const packageJson = await readJson2(path11.join(projectRoot, "package.json"));
3118
+ const packageJson = await readJson2(path12.join(projectRoot, "package.json"));
3008
3119
  const [pubspec, pythonManifests] = await Promise.all([
3009
- readText(path11.join(projectRoot, "pubspec.yaml")),
3120
+ readText(path12.join(projectRoot, "pubspec.yaml")),
3010
3121
  Promise.all([
3011
- readText(path11.join(projectRoot, "pyproject.toml")),
3012
- readText(path11.join(projectRoot, "requirements.txt"))
3122
+ readText(path12.join(projectRoot, "pyproject.toml")),
3123
+ readText(path12.join(projectRoot, "requirements.txt"))
3013
3124
  ]).then((parts) => parts.filter(Boolean).join("\n"))
3014
3125
  ]);
3015
3126
  const deps = Object.keys({
@@ -3018,7 +3129,7 @@ async function detectProjectProfile(projectRoot) {
3018
3129
  ...packageJson?.peerDependencies
3019
3130
  });
3020
3131
  const flutter = isFlutterProject(pubspec);
3021
- const shadcn = entries.includes("components.json") || deps.some((dep) => dep.startsWith("@radix-ui/")) && await isDirectory(path11.join(projectRoot, "src", "components", "ui"));
3132
+ const shadcn = entries.includes("components.json") || deps.some((dep) => dep.startsWith("@radix-ui/")) && await isDirectory(path12.join(projectRoot, "src", "components", "ui"));
3022
3133
  const languages = inferLanguages(manifests, entries);
3023
3134
  const frameworks = inferFrameworks(
3024
3135
  deps,
@@ -3061,7 +3172,7 @@ async function existingDirs(root, candidates) {
3061
3172
  const found = [];
3062
3173
  for (const candidate of candidates) {
3063
3174
  try {
3064
- if ((await stat2(path11.join(root, candidate))).isDirectory())
3175
+ if ((await stat2(path12.join(root, candidate))).isDirectory())
3065
3176
  found.push(candidate);
3066
3177
  } catch {
3067
3178
  }
@@ -3235,8 +3346,8 @@ function isFlutterProject(pubspec) {
3235
3346
  }
3236
3347
 
3237
3348
  // src/system/scan-aesthetics.ts
3238
- import { promises as fs } from "fs";
3239
- import path12 from "path";
3349
+ import { promises as fs2 } from "fs";
3350
+ import path13 from "path";
3240
3351
  var MAX_COMPONENT_SCAN_DEPTH = 12;
3241
3352
  var MAX_COMPONENT_FILES = 2e3;
3242
3353
  async function scanAesthetics(projectRoot, uiLibrary = null) {
@@ -3249,7 +3360,7 @@ async function scanAesthetics(projectRoot, uiLibrary = null) {
3249
3360
  const cssScan = await scanCssVariables(projectRoot);
3250
3361
  if (configResult) {
3251
3362
  sourceFiles.push(configResult.relPath);
3252
- const content = await fs.readFile(configResult.absPath, "utf-8");
3363
+ const content = await fs2.readFile(configResult.absPath, "utf-8");
3253
3364
  const inspectableContent = stripJsComments(content);
3254
3365
  const themeBlock = findKeyBlock(inspectableContent, "theme");
3255
3366
  if (themeBlock) {
@@ -3272,7 +3383,7 @@ async function scanAesthetics(projectRoot, uiLibrary = null) {
3272
3383
  } else if (await hasTailwindDep(projectRoot) || uiLibrary) {
3273
3384
  matchLevel = "low";
3274
3385
  }
3275
- if (uiLibrary && await pathExists2(path12.join(projectRoot, "package.json"))) {
3386
+ if (uiLibrary && await pathExists2(path13.join(projectRoot, "package.json"))) {
3276
3387
  sourceFiles.push("package.json");
3277
3388
  }
3278
3389
  sourceFiles.push(...cssScan.sourceFiles);
@@ -3297,7 +3408,7 @@ async function findTailwindConfig(projectRoot) {
3297
3408
  "tailwind.config.mjs"
3298
3409
  ];
3299
3410
  for (const name of candidates) {
3300
- const absPath = path12.join(projectRoot, name);
3411
+ const absPath = path13.join(projectRoot, name);
3301
3412
  if (await pathExists2(absPath)) {
3302
3413
  return { absPath, relPath: name };
3303
3414
  }
@@ -3524,7 +3635,7 @@ async function scanComponents(projectRoot) {
3524
3635
  const names = /* @__PURE__ */ new Set();
3525
3636
  let visitedFiles = 0;
3526
3637
  for (const relRoot of roots) {
3527
- const absRoot = path12.join(projectRoot, relRoot);
3638
+ const absRoot = path13.join(projectRoot, relRoot);
3528
3639
  if (!await pathExists2(absRoot)) continue;
3529
3640
  visitedFiles = await collectComponentNames(absRoot, names, 0, visitedFiles);
3530
3641
  if (visitedFiles >= MAX_COMPONENT_FILES) break;
@@ -3538,16 +3649,16 @@ async function collectComponentNames(dir, names, depth, visitedFiles) {
3538
3649
  let fileCount = visitedFiles;
3539
3650
  let entries;
3540
3651
  try {
3541
- entries = await fs.readdir(dir);
3652
+ entries = await fs2.readdir(dir);
3542
3653
  } catch {
3543
3654
  return fileCount;
3544
3655
  }
3545
3656
  for (const entry of entries) {
3546
3657
  if (fileCount >= MAX_COMPONENT_FILES) return fileCount;
3547
- const abs = path12.join(dir, entry);
3658
+ const abs = path13.join(dir, entry);
3548
3659
  let info;
3549
3660
  try {
3550
- info = await fs.lstat(abs);
3661
+ info = await fs2.lstat(abs);
3551
3662
  } catch {
3552
3663
  continue;
3553
3664
  }
@@ -3565,7 +3676,7 @@ async function collectComponentNames(dir, names, depth, visitedFiles) {
3565
3676
  ""
3566
3677
  );
3567
3678
  if (base === "index") {
3568
- base = path12.basename(dir);
3679
+ base = path13.basename(dir);
3569
3680
  }
3570
3681
  if (base.startsWith(".")) continue;
3571
3682
  const componentName = toPascalCase(base);
@@ -3587,9 +3698,9 @@ async function scanCssVariables(projectRoot) {
3587
3698
  const variables = {};
3588
3699
  const sourceFiles = [];
3589
3700
  for (const relPath of candidates) {
3590
- const absPath = path12.join(projectRoot, relPath);
3701
+ const absPath = path13.join(projectRoot, relPath);
3591
3702
  if (!await pathExists2(absPath)) continue;
3592
- const content = await fs.readFile(absPath, "utf-8");
3703
+ const content = await fs2.readFile(absPath, "utf-8");
3593
3704
  const found = extractCssVariables(content);
3594
3705
  if (Object.keys(found).length === 0) continue;
3595
3706
  Object.assign(variables, found);
@@ -3632,8 +3743,8 @@ function toPascalCase(value) {
3632
3743
  }
3633
3744
  async function hasTailwindDep(projectRoot) {
3634
3745
  try {
3635
- const raw = await fs.readFile(
3636
- path12.join(projectRoot, "package.json"),
3746
+ const raw = await fs2.readFile(
3747
+ path13.join(projectRoot, "package.json"),
3637
3748
  "utf-8"
3638
3749
  );
3639
3750
  const pkg = JSON.parse(raw);
@@ -3649,7 +3760,7 @@ function escapeRegex(s) {
3649
3760
  }
3650
3761
  async function pathExists2(p) {
3651
3762
  try {
3652
- await fs.access(p);
3763
+ await fs2.access(p);
3653
3764
  return true;
3654
3765
  } catch {
3655
3766
  return false;
@@ -3660,73 +3771,104 @@ async function pathExists2(p) {
3660
3771
  var EXIT_OK = 0;
3661
3772
  var EXIT_ALREADY_INITIALIZED = 1;
3662
3773
  var EXIT_NOT_A_PROJECT_DIR = 2;
3774
+ var EXIT_USER_CANCEL = 3;
3663
3775
  var EXIT_INIT_FAILED = 5;
3664
3776
  var DEFAULT_INIT_PLATFORM = "claude-code";
3665
- async function init(rootDir = process3.cwd(), options = {}) {
3666
- const mancodeDir = path13.join(rootDir, ".mancode");
3667
- const stateFile = path13.join(mancodeDir, "state.json");
3777
+ async function init(rootDir = process5.cwd(), options = {}) {
3778
+ const mancodeDir = path14.join(rootDir, ".mancode");
3779
+ const stateFile = path14.join(mancodeDir, "state.json");
3668
3780
  const wasInitialized = await pathExists3(stateFile);
3669
- let reinstallSnapshots = [];
3781
+ let mutationSnapshots = [];
3782
+ let directorySnapshots = [];
3783
+ const locale = detectInitLocale(options.lang);
3784
+ if (!locale) {
3785
+ console.error(`\u2717 Unsupported init language: ${options.lang}`);
3786
+ console.error(" Supported values: zh-CN, en");
3787
+ return EXIT_INIT_FAILED;
3788
+ }
3670
3789
  if (wasInitialized) {
3671
3790
  if (!options.force) {
3672
- console.log("\u2139\uFE0F mancode already initialized.");
3791
+ console.log(
3792
+ localize(
3793
+ locale,
3794
+ "\u2139\uFE0F mancode \u5DF2\u7ECF\u521D\u59CB\u5316\u3002",
3795
+ "\u2139\uFE0F mancode already initialized."
3796
+ )
3797
+ );
3673
3798
  console.log(` ${stateFile}`);
3674
- console.log(" Run `mancode init --force` to reinstall.");
3799
+ console.log(
3800
+ localize(
3801
+ locale,
3802
+ " \u8FD0\u884C `mancode init --force` \u91CD\u65B0\u5B89\u88C5\u3002",
3803
+ " Run `mancode init --force` to reinstall."
3804
+ )
3805
+ );
3675
3806
  return EXIT_ALREADY_INITIALIZED;
3676
3807
  }
3677
- try {
3678
- reinstallSnapshots = await snapshotFiles([
3679
- stateFile,
3680
- path13.join(mancodeDir, "config.json"),
3681
- path13.join(mancodeDir, "project-profile.json"),
3682
- path13.join(mancodeDir, "aesthetics", "style-tokens.json")
3683
- ]);
3684
- } catch (error) {
3685
- const message = error instanceof Error ? error.message : String(error);
3686
- console.error(`\u2717 Cannot snapshot existing mancode state: ${message}`);
3687
- return EXIT_INIT_FAILED;
3688
- }
3689
- console.log("\u26A0\uFE0F Reinstalling with --force...");
3808
+ console.log(
3809
+ localize(
3810
+ locale,
3811
+ "\u26A0\uFE0F \u6B63\u5728\u4F7F\u7528 --force \u91CD\u65B0\u5B89\u88C5...",
3812
+ "\u26A0\uFE0F Reinstalling with --force..."
3813
+ )
3814
+ );
3690
3815
  }
3691
3816
  if (!await pathExists3(rootDir)) {
3692
- console.error(`\u2717 Target directory does not exist: ${rootDir}`);
3693
- return EXIT_NOT_A_PROJECT_DIR;
3694
- }
3695
- const isGitRepo = await pathExists3(path13.join(rootDir, ".git"));
3696
- const hasManifest = await Promise.any(
3697
- PROJECT_MANIFESTS.map(async (name) => {
3698
- if (await pathExists3(path13.join(rootDir, name))) return true;
3699
- throw new Error("manifest missing");
3700
- })
3701
- ).catch(() => false);
3702
- if (!isGitRepo && !hasManifest) {
3703
- console.error(`\u2717 Not a project directory: ${rootDir}`);
3704
- console.error(" (No .git or recognized project manifest found)");
3705
- console.error("");
3706
3817
  console.error(
3707
- " Run mancode init in a git repository or a recognized project directory."
3818
+ localize(
3819
+ locale,
3820
+ `\u2717 \u76EE\u6807\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${rootDir}`,
3821
+ `\u2717 Target directory does not exist: ${rootDir}`
3822
+ )
3708
3823
  );
3709
3824
  return EXIT_NOT_A_PROJECT_DIR;
3710
3825
  }
3711
- try {
3712
- console.log("\u2713 \u68C0\u6D4B\u7CFB\u7EDF\u4F9D\u8D56...");
3713
- const deps = await detectSystemDeps();
3714
- if (!deps.bash || !deps.git) {
3715
- console.error("\u2717 Missing required dependencies:");
3716
- if (!deps.bash) console.error(" - bash (required)");
3717
- if (!deps.git) console.error(" - git (required)");
3718
- console.error("");
3719
- console.error(" Install them and try again.");
3826
+ const isGitRepo = await pathExists3(path14.join(rootDir, ".git"));
3827
+ const hasManifest = await hasProjectManifest(rootDir);
3828
+ let isGenericProject = false;
3829
+ if (!isGitRepo && !hasManifest) {
3830
+ const genericSafety = await canInitializeGenericProject(rootDir);
3831
+ if (!genericSafety.ok) {
3832
+ printNotProjectDirectory(rootDir, locale, genericSafety.reason);
3833
+ return EXIT_NOT_A_PROJECT_DIR;
3834
+ }
3835
+ const prompter = options.prompter ?? (options.interactive ? createTerminalPrompter() : null);
3836
+ const confirmed = options.empty || options.yes ? true : prompter ? await prompter.confirmGenericProject({ rootDir, locale }) : false;
3837
+ if (!confirmed) {
3838
+ if (options.interactive) {
3839
+ console.log(
3840
+ locale === "zh-CN" ? "\u5DF2\u53D6\u6D88\u521D\u59CB\u5316\u3002" : "Initialization cancelled."
3841
+ );
3842
+ return EXIT_USER_CANCEL;
3843
+ }
3844
+ printNotProjectDirectory(rootDir, locale, "empty");
3720
3845
  return EXIT_NOT_A_PROJECT_DIR;
3721
3846
  }
3722
- if (!deps.jq) {
3847
+ isGenericProject = true;
3848
+ }
3849
+ try {
3850
+ console.log(
3851
+ localize(
3852
+ locale,
3853
+ "\u2713 \u68C0\u6D4B\u7CFB\u7EDF\u4F9D\u8D56...",
3854
+ "\u2713 Checking system dependencies..."
3855
+ )
3856
+ );
3857
+ const deps = await detectSystemDeps();
3858
+ if (!deps.git) {
3723
3859
  console.log(
3724
- "\u26A0\uFE0F jq not found (optional). Hooks will use grep/sed fallback (slightly slower)."
3860
+ localize(
3861
+ locale,
3862
+ "\u26A0\uFE0F \u672A\u627E\u5230 Git\uFF08\u53EF\u9009\uFF09\u3002\u56E2\u961F\u81EA\u52A8\u68C0\u6D4B\u5C06\u4F7F\u7528 solo \u9ED8\u8BA4\u503C\u3002",
3863
+ "\u26A0\uFE0F Git not found (optional). Team auto-detection will use solo defaults."
3864
+ )
3725
3865
  );
3726
3866
  } else {
3727
- console.log(" bash \u2713 git \u2713 jq \u2713");
3867
+ console.log(" git \u2713");
3728
3868
  }
3729
- console.log("\u2713 \u68C0\u6D4B\u9879\u76EE\u7C7B\u578B...");
3869
+ console.log(
3870
+ localize(locale, "\u2713 \u68C0\u6D4B\u9879\u76EE\u7C7B\u578B...", "\u2713 Detecting project type...")
3871
+ );
3730
3872
  const profile = await detectProjectProfile(rootDir);
3731
3873
  const profileStack = [...profile.languages, ...profile.frameworks];
3732
3874
  const techStackStr = profileStack.join(" + ") || "Unknown";
@@ -3738,37 +3880,100 @@ async function init(rootDir = process3.cwd(), options = {}) {
3738
3880
  console.log(` UI: ${uiLibraryStr}`);
3739
3881
  }
3740
3882
  } else if (hasManifest) {
3741
- console.log(" project manifest found, no known framework dependencies");
3883
+ console.log(
3884
+ localize(
3885
+ locale,
3886
+ " \u5DF2\u53D1\u73B0\u9879\u76EE manifest\uFF0C\u672A\u8BC6\u522B\u5230\u6846\u67B6\u4F9D\u8D56",
3887
+ " Project manifest found; no known framework dependencies"
3888
+ )
3889
+ );
3742
3890
  } else {
3743
3891
  console.log(
3744
- " (No recognized manifest found, profile confidence is low)"
3892
+ localize(
3893
+ locale,
3894
+ " \uFF08\u672A\u53D1\u73B0\u5DF2\u8BC6\u522B\u7684 manifest\uFF0C\u9879\u76EE\u753B\u50CF\u7F6E\u4FE1\u5EA6\u8F83\u4F4E\uFF09",
3895
+ " (No recognized manifest found; profile confidence is low)"
3896
+ )
3745
3897
  );
3746
3898
  }
3747
3899
  const existingPlatform = wasInitialized ? await readExistingInitPlatform(stateFile) : null;
3748
- const initPlatformName = options.platform ?? existingPlatform ?? DEFAULT_INIT_PLATFORM;
3749
- const installer = getPlatformInstaller(initPlatformName);
3750
- if (!installer) {
3751
- console.error(`\u2717 Unsupported platform: ${initPlatformName}`);
3900
+ const platformHints = await detectPlatformHints(rootDir);
3901
+ const selectedPlatforms = await selectInitPlatforms({
3902
+ option: options.platform,
3903
+ existingPlatform,
3904
+ hints: platformHints,
3905
+ interactive: options.interactive,
3906
+ prompter: options.prompter,
3907
+ locale,
3908
+ yes: options.yes
3909
+ });
3910
+ if (!selectedPlatforms) {
3911
+ console.error(
3912
+ locale === "zh-CN" ? "\u2717 \u672A\u9009\u62E9\u5E73\u53F0\u3002\u8BF7\u91CD\u65B0\u8FD0\u884C\u5E76\u9009\u62E9\u5E73\u53F0\uFF0C\u6216\u4F7F\u7528 --platform codex,cursor / --platform all\u3002" : "\u2717 No platform selected. Choose one interactively or pass --platform codex,cursor / --platform all."
3913
+ );
3914
+ return EXIT_INIT_FAILED;
3915
+ }
3916
+ if (selectedPlatforms.length === 0) {
3917
+ console.error("\u2717 No platform selected.");
3918
+ return EXIT_INIT_FAILED;
3919
+ }
3920
+ const firstPlatform = selectedPlatforms[0];
3921
+ if (!firstPlatform) {
3922
+ console.error("\u2717 No platform selected.");
3923
+ return EXIT_INIT_FAILED;
3924
+ }
3925
+ const installers = selectedPlatforms.map(getPlatformInstaller);
3926
+ if (installers.some((installer) => !installer)) {
3927
+ console.error(`\u2717 Unsupported platform: ${options.platform}`);
3752
3928
  return EXIT_INIT_FAILED;
3753
3929
  }
3930
+ const typedInstallers = installers.filter(
3931
+ (installer) => installer !== null
3932
+ );
3933
+ const onlyPlatformHint = platformHints.length === 1 ? platformHints[0] : null;
3934
+ const detectedPrimary = onlyPlatformHint && selectedPlatforms.includes(onlyPlatformHint) ? onlyPlatformHint : null;
3935
+ const primaryPlatform = existingPlatform && selectedPlatforms.includes(existingPlatform) ? existingPlatform : detectedPrimary ?? firstPlatform;
3936
+ const managedFiles = getInitManagedFilePaths(rootDir, selectedPlatforms);
3937
+ mutationSnapshots = await snapshotFiles(managedFiles);
3938
+ directorySnapshots = await snapshotDirectories(managedFiles, [
3939
+ path14.join(mancodeDir, "workflows"),
3940
+ path14.join(mancodeDir, "preseason-reports")
3941
+ ]);
3754
3942
  const team = await detectTeamStatus(rootDir);
3755
- const existingPreferences = wasInitialized ? await readExistingInitPreferences(mancodeDir, installer.name) : {};
3943
+ const existingPreferences = wasInitialized ? await readExistingInitPreferences(mancodeDir, primaryPlatform) : {};
3756
3944
  const initialMinimal = existingPreferences.minimal ?? false;
3757
- const teamModeEnabled = options.team ?? existingPreferences.forceTeamMode ?? team.isTeam;
3945
+ const existingTeamMode = existingPreferences.forceTeamMode === true ? "on" : existingPreferences.teamMode ?? "auto";
3946
+ const teamModeEnabled = options.team ?? (existingTeamMode === "on" ? true : existingTeamMode === "off" ? false : team.isTeam);
3758
3947
  if (options.team === true) {
3759
- console.log(" team: forced on (--team)");
3948
+ console.log(
3949
+ localize(
3950
+ locale,
3951
+ " \u56E2\u961F\u6A21\u5F0F\uFF1A\u5F3A\u5236\u5F00\u542F\uFF08--team\uFF09",
3952
+ " team: forced on (--team)"
3953
+ )
3954
+ );
3760
3955
  } else if (options.team === false) {
3761
- console.log(" team: forced off (--no-team)");
3956
+ console.log(
3957
+ localize(
3958
+ locale,
3959
+ " \u56E2\u961F\u6A21\u5F0F\uFF1A\u5F3A\u5236\u5173\u95ED\uFF08--no-team\uFF09",
3960
+ " team: forced off (--no-team)"
3961
+ )
3962
+ );
3762
3963
  } else if (team.isTeam) {
3763
3964
  console.log(
3764
- ` team: ${team.contributors} contributors (/manteam available)`
3965
+ localize(
3966
+ locale,
3967
+ ` \u56E2\u961F\u6A21\u5F0F\uFF1A${team.contributors} \u4F4D\u8D21\u732E\u8005\uFF08\u53EF\u4F7F\u7528 /manteam\uFF09`,
3968
+ ` team: ${team.contributors} contributors (/manteam available)`
3969
+ )
3765
3970
  );
3766
3971
  }
3767
3972
  const state = {
3768
3973
  version: VERSION,
3769
3974
  currentMode: "solo",
3770
3975
  lastMode: "solo",
3771
- platform: installer.name,
3976
+ platform: primaryPlatform,
3772
3977
  initializedAt: (/* @__PURE__ */ new Date()).toISOString(),
3773
3978
  techStack: techStackStr,
3774
3979
  uiLibrary: uiLibraryStr,
@@ -3776,18 +3981,19 @@ async function init(rootDir = process3.cwd(), options = {}) {
3776
3981
  currentWorkflowMode: null,
3777
3982
  skippedSteps: [],
3778
3983
  teamModeAutoDetected: teamModeEnabled,
3779
- contributors: team.contributors
3984
+ contributors: team.contributors,
3985
+ projectMode: isGenericProject ? "generic" : "detected"
3780
3986
  };
3781
- if (installer.name === "claude-code") {
3987
+ if (selectedPlatforms.includes("claude-code")) {
3782
3988
  await validateClaudeCodeSettings(rootDir);
3783
3989
  }
3784
3990
  await installMancodeCore(rootDir);
3785
- await fs2.mkdir(mancodeDir, { recursive: true });
3991
+ await fs3.mkdir(mancodeDir, { recursive: true });
3786
3992
  const stateContent = `${JSON.stringify(state, null, 2)}
3787
3993
  `;
3788
- await fs2.writeFile(stateFile, stateContent, "utf-8");
3789
- await fs2.writeFile(
3790
- path13.join(mancodeDir, "project-profile.json"),
3994
+ await fs3.writeFile(stateFile, stateContent, "utf-8");
3995
+ await fs3.writeFile(
3996
+ path14.join(mancodeDir, "project-profile.json"),
3791
3997
  `${JSON.stringify(profile, null, 2)}
3792
3998
  `,
3793
3999
  "utf-8"
@@ -3796,24 +4002,40 @@ async function init(rootDir = process3.cwd(), options = {}) {
3796
4002
  mancodeDir,
3797
4003
  {
3798
4004
  forceTeamMode: options.team === void 0 && wasInitialized ? void 0 : options.team === true,
4005
+ teamMode: options.team === void 0 && wasInitialized ? void 0 : options.team === true ? "on" : options.team === false ? "off" : "auto",
3799
4006
  defaultStyle: options.style === void 0 && wasInitialized ? void 0 : options.style ?? null,
3800
- platforms: [installer.name],
4007
+ platforms: selectedPlatforms,
3801
4008
  platformOptions: {
3802
- [installer.name]: { minimal: initialMinimal }
4009
+ ...Object.fromEntries(
4010
+ selectedPlatforms.map((platform) => [
4011
+ platform,
4012
+ { minimal: initialMinimal }
4013
+ ])
4014
+ )
3803
4015
  }
3804
4016
  },
3805
4017
  wasInitialized
3806
4018
  );
3807
- let styleLine = " .mancode/aesthetics/ # style-tokens.json (\u7A7A)";
4019
+ let styleLine = localize(
4020
+ locale,
4021
+ " .mancode/aesthetics/ # style-tokens.json\uFF08\u7A7A\uFF09",
4022
+ " .mancode/aesthetics/ # style-tokens.json (empty)"
4023
+ );
3808
4024
  if (profile.uiAssets === "detected") {
3809
- console.log("\u2713 \u626B\u63CF\u5BA1\u7F8E token...");
4025
+ console.log(
4026
+ localize(
4027
+ locale,
4028
+ "\u2713 \u626B\u63CF\u5BA1\u7F8E token...",
4029
+ "\u2713 Scanning design tokens..."
4030
+ )
4031
+ );
3810
4032
  const tokens = await scanAesthetics(rootDir, uiLibrary);
3811
- const tokensPath = path13.join(
4033
+ const tokensPath = path14.join(
3812
4034
  mancodeDir,
3813
4035
  "aesthetics",
3814
4036
  "style-tokens.json"
3815
4037
  );
3816
- await fs2.writeFile(
4038
+ await fs3.writeFile(
3817
4039
  tokensPath,
3818
4040
  `${JSON.stringify(tokens, null, 2)}
3819
4041
  `,
@@ -3823,70 +4045,146 @@ async function init(rootDir = process3.cwd(), options = {}) {
3823
4045
  const colorCount = Object.keys(tokens.colors).length;
3824
4046
  const fontCount = Object.keys(tokens.fonts).length;
3825
4047
  console.log(
3826
- ` ${colorCount} colors, ${fontCount} fonts (match: high)`
4048
+ localize(
4049
+ locale,
4050
+ ` ${colorCount} \u4E2A\u989C\u8272\uFF0C${fontCount} \u4E2A\u5B57\u4F53\uFF08\u5339\u914D\u5EA6\uFF1A\u9AD8\uFF09`,
4051
+ ` ${colorCount} colors, ${fontCount} fonts (match: high)`
4052
+ )
4053
+ );
4054
+ styleLine = localize(
4055
+ locale,
4056
+ ` .mancode/aesthetics/ # style-tokens.json\uFF08${colorCount} \u4E2A\u989C\u8272\uFF09`,
4057
+ ` .mancode/aesthetics/ # style-tokens.json (${colorCount} colors)`
3827
4058
  );
3828
- styleLine = ` .mancode/aesthetics/ # style-tokens.json (${colorCount} colors)`;
3829
4059
  } else if (tokens.matchLevel === "low") {
3830
4060
  console.log(
3831
- " UI assets detected, no reusable tokens found (match: low)"
4061
+ localize(
4062
+ locale,
4063
+ " \u5DF2\u68C0\u6D4B\u5230 UI \u8D44\u6E90\uFF0C\u672A\u627E\u5230\u53EF\u590D\u7528 token\uFF08\u5339\u914D\u5EA6\uFF1A\u4F4E\uFF09",
4064
+ " UI assets detected; no reusable tokens found (match: low)"
4065
+ )
4066
+ );
4067
+ styleLine = localize(
4068
+ locale,
4069
+ " .mancode/aesthetics/ # style-tokens.json\uFF08\u4F4E\u5339\u914D\uFF09",
4070
+ " .mancode/aesthetics/ # style-tokens.json (low match)"
3832
4071
  );
3833
- styleLine = " .mancode/aesthetics/ # style-tokens.json (low match)";
3834
4072
  } else {
3835
- console.log(" No design tokens found (match: none)");
3836
- styleLine = " .mancode/aesthetics/ # style-tokens.json (no tokens)";
4073
+ console.log(
4074
+ localize(
4075
+ locale,
4076
+ " \u672A\u627E\u5230\u8BBE\u8BA1 token\uFF08\u65E0\u5339\u914D\uFF09",
4077
+ " No design tokens found (match: none)"
4078
+ )
4079
+ );
4080
+ styleLine = localize(
4081
+ locale,
4082
+ " .mancode/aesthetics/ # style-tokens.json\uFF08\u65E0 token\uFF09",
4083
+ " .mancode/aesthetics/ # style-tokens.json (no tokens)"
4084
+ );
3837
4085
  }
3838
4086
  }
3839
- console.log(`\u2713 \u5B89\u88C5 ${installer.displayName} \u9002\u914D...`);
3840
- await installer.install(rootDir, {
3841
- techStack: profileStack,
3842
- uiLibrary,
3843
- projectProfile: profile,
3844
- minimal: initialMinimal,
3845
- force: options.force
3846
- });
4087
+ for (const installer of typedInstallers) {
4088
+ console.log(
4089
+ localize(
4090
+ locale,
4091
+ `\u2713 \u5B89\u88C5 ${installer.displayName} \u9002\u914D\u5668...`,
4092
+ `\u2713 Installing ${installer.displayName} adapter...`
4093
+ )
4094
+ );
4095
+ await installer.install(rootDir, {
4096
+ techStack: profileStack,
4097
+ uiLibrary,
4098
+ projectProfile: profile,
4099
+ minimal: initialMinimal,
4100
+ force: options.force
4101
+ });
4102
+ }
3847
4103
  console.log("");
3848
- console.log("\u2713 mancode initialized.");
4104
+ console.log(
4105
+ localize(locale, "\u2713 mancode \u521D\u59CB\u5316\u5B8C\u6210\u3002", "\u2713 mancode initialized.")
4106
+ );
3849
4107
  console.log("");
3850
- console.log("Created:");
3851
- console.log(" .mancode/state.json # \u9879\u76EE\u72B6\u6001");
3852
- console.log(" .mancode/project-profile.json # \u68C0\u6D4B\u5230\u7684\u9879\u76EE\u4E8B\u5B9E");
3853
- console.log(" .mancode/config.json # \u914D\u7F6E");
4108
+ console.log(localize(locale, "\u5DF2\u521B\u5EFA\uFF1A", "Created:"));
4109
+ console.log(
4110
+ localize(
4111
+ locale,
4112
+ " .mancode/state.json # \u9879\u76EE\u72B6\u6001",
4113
+ " .mancode/state.json # project state"
4114
+ )
4115
+ );
3854
4116
  console.log(
3855
- " .mancode/hooks/ # SessionStart + UserPromptSubmit"
4117
+ localize(
4118
+ locale,
4119
+ " .mancode/project-profile.json # \u68C0\u6D4B\u5230\u7684\u9879\u76EE\u4E8B\u5B9E",
4120
+ " .mancode/project-profile.json # detected project facts"
4121
+ )
4122
+ );
4123
+ console.log(
4124
+ localize(
4125
+ locale,
4126
+ " .mancode/config.json # \u914D\u7F6E",
4127
+ " .mancode/config.json # configuration"
4128
+ )
4129
+ );
4130
+ console.log(
4131
+ localize(
4132
+ locale,
4133
+ " .mancode/hooks/ # SessionStart + UserPromptSubmit",
4134
+ " .mancode/hooks/ # SessionStart + UserPromptSubmit"
4135
+ )
3856
4136
  );
3857
4137
  console.log(styleLine);
3858
4138
  console.log(" .mancode/logs/ # hooks.log");
3859
- printPlatformCreatedFiles(installer.name);
4139
+ for (const platform of selectedPlatforms)
4140
+ printPlatformCreatedFiles(platform, locale);
3860
4141
  console.log("");
3861
- console.log("Next:");
3862
- console.log(" mancode status # Show project state");
3863
- if (installer.name === "claude-code") {
3864
- console.log(" (Restart Claude Code to load hooks)");
3865
- } else if (installer.name === "codex") {
4142
+ console.log(localize(locale, "\u4E0B\u4E00\u6B65\uFF1A", "Next:"));
4143
+ console.log(
4144
+ localize(
4145
+ locale,
4146
+ " mancode status # \u663E\u793A\u9879\u76EE\u72B6\u6001",
4147
+ " mancode status # Show project state"
4148
+ )
4149
+ );
4150
+ if (selectedPlatforms.includes("claude-code")) {
4151
+ console.log(
4152
+ localize(
4153
+ locale,
4154
+ " \uFF08\u91CD\u542F Claude Code \u4EE5\u52A0\u8F7D hooks\uFF09",
4155
+ " (Restart Claude Code to load hooks)"
4156
+ )
4157
+ );
4158
+ }
4159
+ if (selectedPlatforms.includes("codex")) {
3866
4160
  console.log(
3867
- " (If skills do not appear, restart the ChatGPT desktop app or Codex session)"
4161
+ localize(
4162
+ locale,
4163
+ " \uFF08\u5982\u679C skills \u672A\u51FA\u73B0\uFF0C\u8BF7\u91CD\u542F ChatGPT \u684C\u9762\u5E94\u7528\u6216 Codex \u4F1A\u8BDD\uFF09",
4164
+ " (If skills do not appear, restart the ChatGPT desktop app or Codex session)"
4165
+ )
3868
4166
  );
3869
4167
  }
3870
4168
  return EXIT_OK;
3871
4169
  } catch (err) {
3872
- if (wasInitialized) {
3873
- await restoreFiles(reinstallSnapshots);
3874
- } else {
3875
- await fs2.rm(stateFile, { force: true });
3876
- await fs2.rm(path13.join(mancodeDir, "project-profile.json"), {
3877
- force: true
3878
- });
3879
- }
4170
+ await restoreFiles(mutationSnapshots);
4171
+ await removeNewEmptyDirectories(directorySnapshots);
3880
4172
  const msg = err instanceof Error ? err.message : String(err);
3881
- console.error(`\u2717 mancode init failed: ${msg}`);
4173
+ console.error(
4174
+ localize(
4175
+ locale,
4176
+ `\u2717 mancode \u521D\u59CB\u5316\u5931\u8D25\uFF1A${msg}`,
4177
+ `\u2717 mancode init failed: ${msg}`
4178
+ )
4179
+ );
3882
4180
  return EXIT_INIT_FAILED;
3883
4181
  }
3884
4182
  }
3885
4183
  async function updateConfigOptions(mancodeDir, patch, preserveInstalledPlatforms) {
3886
- const configPath = path13.join(mancodeDir, "config.json");
4184
+ const configPath = path14.join(mancodeDir, "config.json");
3887
4185
  let config = {};
3888
4186
  try {
3889
- config = JSON.parse(await fs2.readFile(configPath, "utf-8"));
4187
+ config = JSON.parse(await fs3.readFile(configPath, "utf-8"));
3890
4188
  } catch {
3891
4189
  }
3892
4190
  const existingPlatforms = Array.isArray(config.platforms) ? config.platforms.filter(
@@ -3906,7 +4204,7 @@ async function updateConfigOptions(mancodeDir, patch, preserveInstalledPlatforms
3906
4204
  ...patch.platformOptions
3907
4205
  }
3908
4206
  } : definedPatch;
3909
- await fs2.writeFile(
4207
+ await fs3.writeFile(
3910
4208
  configPath,
3911
4209
  `${JSON.stringify({ ...config, ...mergedPatch }, null, 2)}
3912
4210
  `,
@@ -3916,36 +4214,182 @@ async function updateConfigOptions(mancodeDir, patch, preserveInstalledPlatforms
3916
4214
  async function readExistingInitPreferences(mancodeDir, platform) {
3917
4215
  try {
3918
4216
  const config = JSON.parse(
3919
- await fs2.readFile(path13.join(mancodeDir, "config.json"), "utf-8")
4217
+ await fs3.readFile(path14.join(mancodeDir, "config.json"), "utf-8")
3920
4218
  );
3921
4219
  const preferences = {};
3922
4220
  if (typeof config.forceTeamMode === "boolean") {
3923
4221
  preferences.forceTeamMode = config.forceTeamMode;
3924
4222
  }
3925
- if (Array.isArray(config.platforms) && config.platforms.includes(platform) && isRecord3(config.platformOptions)) {
3926
- const platformOptions = config.platformOptions[platform];
3927
- if (isRecord3(platformOptions) && platformOptions.minimal === true) {
3928
- preferences.minimal = true;
3929
- }
4223
+ if (config.teamMode === "auto" || config.teamMode === "on" || config.teamMode === "off") {
4224
+ preferences.teamMode = config.teamMode;
4225
+ }
4226
+ if (Array.isArray(config.platforms) && config.platforms.includes(platform) && isRecord3(config.platformOptions)) {
4227
+ const platformOptions = config.platformOptions[platform];
4228
+ if (isRecord3(platformOptions) && platformOptions.minimal === true) {
4229
+ preferences.minimal = true;
4230
+ }
4231
+ }
4232
+ return preferences;
4233
+ } catch {
4234
+ return {};
4235
+ }
4236
+ }
4237
+ async function readExistingInitPlatform(stateFile) {
4238
+ try {
4239
+ const state = JSON.parse(await fs3.readFile(stateFile, "utf-8"));
4240
+ return typeof state.platform === "string" && getPlatformInstaller(state.platform) ? state.platform : null;
4241
+ } catch {
4242
+ return null;
4243
+ }
4244
+ }
4245
+ async function selectInitPlatforms(input) {
4246
+ if (input.option !== void 0) {
4247
+ return parsePlatformSelection(input.option);
4248
+ }
4249
+ if (input.existingPlatform) return [input.existingPlatform];
4250
+ if (input.yes && input.interactive !== void 0) {
4251
+ return input.hints.length === 1 ? input.hints : null;
4252
+ }
4253
+ if (input.interactive) {
4254
+ const prompter = input.prompter ?? createTerminalPrompter();
4255
+ return prompter.selectPlatforms({
4256
+ locale: input.locale,
4257
+ detected: input.hints
4258
+ });
4259
+ }
4260
+ if (input.interactive === false) {
4261
+ return input.hints.length === 1 ? input.hints : null;
4262
+ }
4263
+ return [DEFAULT_INIT_PLATFORM];
4264
+ }
4265
+ async function hasProjectManifest(rootDir) {
4266
+ for (const name of PROJECT_MANIFESTS) {
4267
+ if (await pathExists3(path14.join(rootDir, name))) return true;
4268
+ }
4269
+ return false;
4270
+ }
4271
+ async function canInitializeGenericProject(rootDir) {
4272
+ const resolved = path14.resolve(rootDir);
4273
+ if (resolved === path14.parse(resolved).root || resolved === path14.resolve(os.homedir())) {
4274
+ return { ok: false, reason: "unsafe" };
4275
+ }
4276
+ try {
4277
+ const entries = await fs3.readdir(resolved);
4278
+ const meaningfulEntries = entries.filter(
4279
+ (entry) => ![".DS_Store", "Thumbs.db", ".gitkeep"].includes(entry)
4280
+ );
4281
+ return meaningfulEntries.length === 0 ? { ok: true } : { ok: false, reason: "nonempty" };
4282
+ } catch {
4283
+ return { ok: false, reason: "unsafe" };
4284
+ }
4285
+ }
4286
+ function printNotProjectDirectory(rootDir, locale, reason) {
4287
+ if (locale === "zh-CN") {
4288
+ console.error(`\u2717 \u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u53EF\u521D\u59CB\u5316\u7684\u9879\u76EE\u76EE\u5F55\uFF1A${rootDir}`);
4289
+ if (reason === "unsafe") {
4290
+ console.error(" \u4E3A\u907F\u514D\u8BEF\u5199\uFF0C\u4E0D\u80FD\u5728 Home \u76EE\u5F55\u6216\u78C1\u76D8\u6839\u76EE\u5F55\u521D\u59CB\u5316\u3002");
4291
+ } else if (reason === "nonempty") {
4292
+ console.error(
4293
+ " \u672A\u8BC6\u522B\u5230\u9879\u76EE\u6587\u4EF6\uFF0C\u4E14\u76EE\u5F55\u4E2D\u5DF2\u6709\u6587\u4EF6\u3002\u8BF7\u5148\u8FDB\u5165\u9879\u76EE\u76EE\u5F55\u3002"
4294
+ );
4295
+ } else {
4296
+ console.error(
4297
+ " \u672A\u53D1\u73B0 .git \u6216\u9879\u76EE\u6587\u4EF6\u3002\u4EA4\u4E92\u7EC8\u7AEF\u4E2D\u53EF\u786E\u8BA4\u901A\u7528\u9879\u76EE\uFF0C\u6216\u4F7F\u7528 --empty\u3002"
4298
+ );
4299
+ }
4300
+ return;
4301
+ }
4302
+ console.error(`\u2717 Not a project directory: ${rootDir}`);
4303
+ if (reason === "unsafe") {
4304
+ console.error(
4305
+ " Refusing to initialize a home directory or filesystem root."
4306
+ );
4307
+ } else if (reason === "nonempty") {
4308
+ console.error(
4309
+ " No project files were detected and the directory is not empty."
4310
+ );
4311
+ } else {
4312
+ console.error(
4313
+ " No .git or recognized project manifest found. Use an interactive terminal or --empty for a new project."
4314
+ );
4315
+ }
4316
+ }
4317
+ function getInitManagedFilePaths(rootDir, platforms) {
4318
+ const files = [
4319
+ ".mancode/state.json",
4320
+ ".mancode/config.json",
4321
+ ".mancode/project-profile.json",
4322
+ ".mancode/aesthetics/style-tokens.json",
4323
+ ".mancode/hooks/session-start.mjs",
4324
+ ".mancode/hooks/user-prompt-submit.mjs",
4325
+ ".mancode/logs/hooks.log",
4326
+ ".mancode/memory/prd.md",
4327
+ ".mancode/memory/spec.md",
4328
+ ".mancode/memory/decisions.md"
4329
+ ];
4330
+ if (platforms.includes("claude-code")) {
4331
+ files.push(
4332
+ ".mancode/hooks/session-start.sh",
4333
+ ".mancode/hooks/user-prompt-submit.sh",
4334
+ ".claude/settings.json",
4335
+ ".claude/skills/man8/SKILL.md",
4336
+ ".claude/skills/solo/SKILL.md",
4337
+ ".claude/skills/mancode-solo.md",
4338
+ ".claude/skills/mancode-man8.md"
4339
+ );
4340
+ for (const mode of MODE_NAMES) {
4341
+ files.push(
4342
+ `.claude/skills/${mode}/SKILL.md`,
4343
+ `.claude/skills/mancode-${mode}.md`
4344
+ );
4345
+ }
4346
+ for (const agent of ALL_AGENTS) {
4347
+ files.push(`.claude/agents/${agent.name}.md`);
4348
+ }
4349
+ }
4350
+ if (platforms.includes("cursor")) {
4351
+ files.push(".cursor/rules/mancode-man8.mdc", ".cursor/commands/man8.md");
4352
+ for (const fileName of MANCODE_CURSOR_RULE_FILES) {
4353
+ files.push(`.cursor/rules/${fileName}`);
4354
+ }
4355
+ for (const mode of MODE_NAMES) {
4356
+ files.push(`.cursor/commands/${mode}.md`);
3930
4357
  }
3931
- return preferences;
3932
- } catch {
3933
- return {};
3934
4358
  }
3935
- }
3936
- async function readExistingInitPlatform(stateFile) {
3937
- try {
3938
- const state = JSON.parse(await fs2.readFile(stateFile, "utf-8"));
3939
- return typeof state.platform === "string" && getPlatformInstaller(state.platform) ? state.platform : null;
3940
- } catch {
3941
- return null;
4359
+ if (platforms.includes("codex") || platforms.includes("zcode")) {
4360
+ files.push("AGENTS.md", ".agents/skills/man8/SKILL.md");
4361
+ for (const mode of MODE_NAMES) {
4362
+ files.push(`.agents/skills/${mode}/SKILL.md`);
4363
+ }
4364
+ }
4365
+ if (platforms.includes("codex")) {
4366
+ files.push(".codex/skills/man8/SKILL.md");
4367
+ for (const mode of MODE_NAMES) {
4368
+ files.push(`.codex/skills/${mode}/SKILL.md`);
4369
+ }
4370
+ }
4371
+ if (platforms.includes("zcode")) {
4372
+ files.push(".zcode/skills/man8/SKILL.md");
4373
+ for (const mode of MODE_NAMES) {
4374
+ files.push(`.zcode/skills/${mode}/SKILL.md`);
4375
+ }
4376
+ }
4377
+ if (platforms.includes("copilot")) {
4378
+ files.push(
4379
+ ".github/copilot-instructions.md",
4380
+ ".github/prompts/man8.prompt.md"
4381
+ );
4382
+ for (const mode of MODE_NAMES) {
4383
+ files.push(`.github/prompts/${mode}.prompt.md`);
4384
+ }
3942
4385
  }
4386
+ return [...new Set(files.map((file) => path14.join(rootDir, file)))];
3943
4387
  }
3944
4388
  async function snapshotFiles(filePaths) {
3945
4389
  return Promise.all(
3946
4390
  filePaths.map(async (filePath) => {
3947
4391
  try {
3948
- return { filePath, content: await fs2.readFile(filePath, "utf-8") };
4392
+ return { filePath, content: await fs3.readFile(filePath, "utf-8") };
3949
4393
  } catch (error) {
3950
4394
  if (isNodeError3(error) && error.code === "ENOENT") {
3951
4395
  return { filePath, content: null };
@@ -3955,14 +4399,39 @@ async function snapshotFiles(filePaths) {
3955
4399
  })
3956
4400
  );
3957
4401
  }
4402
+ async function snapshotDirectories(filePaths, additionalDirectories = []) {
4403
+ const directories = new Set(additionalDirectories);
4404
+ for (const filePath of filePaths) {
4405
+ let current = path14.dirname(filePath);
4406
+ while (current !== path14.dirname(current)) {
4407
+ directories.add(current);
4408
+ current = path14.dirname(current);
4409
+ }
4410
+ }
4411
+ return Promise.all(
4412
+ [...directories].map(async (dirPath) => ({
4413
+ dirPath,
4414
+ existed: await pathExists3(dirPath)
4415
+ }))
4416
+ );
4417
+ }
3958
4418
  async function restoreFiles(snapshots) {
3959
4419
  for (const snapshot of snapshots) {
3960
4420
  if (snapshot.content === null) {
3961
- await fs2.rm(snapshot.filePath, { force: true });
4421
+ await fs3.rm(snapshot.filePath, { force: true });
3962
4422
  continue;
3963
4423
  }
3964
- await fs2.mkdir(path13.dirname(snapshot.filePath), { recursive: true });
3965
- await fs2.writeFile(snapshot.filePath, snapshot.content, "utf-8");
4424
+ await fs3.mkdir(path14.dirname(snapshot.filePath), { recursive: true });
4425
+ await fs3.writeFile(snapshot.filePath, snapshot.content, "utf-8");
4426
+ }
4427
+ }
4428
+ async function removeNewEmptyDirectories(snapshots) {
4429
+ const candidates = snapshots.filter((snapshot) => !snapshot.existed).sort((a, b) => b.dirPath.length - a.dirPath.length);
4430
+ for (const { dirPath } of candidates) {
4431
+ try {
4432
+ await fs3.rmdir(dirPath);
4433
+ } catch {
4434
+ }
3966
4435
  }
3967
4436
  }
3968
4437
  function isRecord3(value) {
@@ -3971,10 +4440,22 @@ function isRecord3(value) {
3971
4440
  function isNodeError3(error) {
3972
4441
  return error instanceof Error && "code" in error;
3973
4442
  }
3974
- function printPlatformCreatedFiles(platform) {
4443
+ function printPlatformCreatedFiles(platform, locale) {
3975
4444
  if (platform === "claude-code") {
3976
- console.log(" .claude/settings.json # hook \u6CE8\u518C");
3977
- console.log(" .claude/skills/ # solo + MVP-2 slash skills");
4445
+ console.log(
4446
+ localize(
4447
+ locale,
4448
+ " .claude/settings.json # hook \u6CE8\u518C",
4449
+ " .claude/settings.json # hook registration"
4450
+ )
4451
+ );
4452
+ console.log(
4453
+ localize(
4454
+ locale,
4455
+ " .claude/skills/ # solo + MVP-2 slash skills",
4456
+ " .claude/skills/ # solo + MVP-2 slash skills"
4457
+ )
4458
+ );
3978
4459
  return;
3979
4460
  }
3980
4461
  if (platform === "cursor") {
@@ -3993,9 +4474,12 @@ function printPlatformCreatedFiles(platform) {
3993
4474
  }
3994
4475
  console.log(" .github/copilot-instructions.md # Copilot instructions");
3995
4476
  }
4477
+ function localize(locale, chinese, english) {
4478
+ return locale === "zh-CN" ? chinese : english;
4479
+ }
3996
4480
  async function pathExists3(p) {
3997
4481
  try {
3998
- await fs2.access(p);
4482
+ await fs3.access(p);
3999
4483
  return true;
4000
4484
  } catch {
4001
4485
  return false;
@@ -4003,13 +4487,13 @@ async function pathExists3(p) {
4003
4487
  }
4004
4488
 
4005
4489
  // src/commands/install.ts
4006
- import { promises as fs4 } from "fs";
4007
- import path15 from "path";
4008
- import process4 from "process";
4490
+ import { promises as fs5 } from "fs";
4491
+ import path16 from "path";
4492
+ import process6 from "process";
4009
4493
 
4010
4494
  // src/installers/platform-status.ts
4011
- import { promises as fs3 } from "fs";
4012
- import path14 from "path";
4495
+ import { promises as fs4 } from "fs";
4496
+ import path15 from "path";
4013
4497
  async function checkPlatformStatus(rootDir, platform, installed) {
4014
4498
  const readiness = await checkPlatformReadiness(rootDir, platform);
4015
4499
  return {
@@ -4023,13 +4507,13 @@ async function checkPlatformReadiness(rootDir, platform) {
4023
4507
  if (platform === "claude-code") {
4024
4508
  const [hasSoloSkill, registered, hasHookFiles] = await Promise.all([
4025
4509
  fileMatches(
4026
- path14.join(rootDir, ".claude", "skills", "solo", "SKILL.md"),
4510
+ path15.join(rootDir, ".claude", "skills", "solo", "SKILL.md"),
4027
4511
  (content) => isGeneratedClaudeSkill(content, "solo")
4028
4512
  ),
4029
4513
  claudeHooksRegistered(rootDir),
4030
4514
  pathsExist([
4031
- path14.join(rootDir, ".mancode", "hooks", "session-start.sh"),
4032
- path14.join(rootDir, ".mancode", "hooks", "user-prompt-submit.sh")
4515
+ path15.join(rootDir, ".mancode", "hooks", "session-start.mjs"),
4516
+ path15.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
4033
4517
  ])
4034
4518
  ]);
4035
4519
  const present = hasSoloSkill && registered && hasHookFiles;
@@ -4045,7 +4529,7 @@ async function checkPlatformReadiness(rootDir, platform) {
4045
4529
  if (platform === "cursor") {
4046
4530
  const hasCoreRules = await allManagedSkills(
4047
4531
  MANCODE_CURSOR_CORE_RULE_FILES.map(
4048
- (file) => path14.join(rootDir, ".cursor", "rules", file)
4532
+ (file) => path15.join(rootDir, ".cursor", "rules", file)
4049
4533
  ),
4050
4534
  [CURSOR_RULE_MANAGED_MARKER]
4051
4535
  );
@@ -4060,13 +4544,13 @@ async function checkPlatformReadiness(rootDir, platform) {
4060
4544
  const [hasRules, hasCommands] = await Promise.all([
4061
4545
  allManagedSkills(
4062
4546
  MANCODE_CURSOR_RULE_FILES.map(
4063
- (file) => path14.join(rootDir, ".cursor", "rules", file)
4547
+ (file) => path15.join(rootDir, ".cursor", "rules", file)
4064
4548
  ),
4065
4549
  [CURSOR_RULE_MANAGED_MARKER]
4066
4550
  ),
4067
4551
  allManagedSkills(
4068
4552
  MODE_NAMES.map(
4069
- (mode) => path14.join(rootDir, ".cursor", "commands", `${mode}.md`)
4553
+ (mode) => path15.join(rootDir, ".cursor", "commands", `${mode}.md`)
4070
4554
  ),
4071
4555
  [MODE_FILE_MANAGED_MARKER]
4072
4556
  )
@@ -4079,7 +4563,7 @@ async function checkPlatformReadiness(rootDir, platform) {
4079
4563
  };
4080
4564
  }
4081
4565
  if (platform === "codex") {
4082
- const hasBlock2 = await fileHasManagedBlock(path14.join(rootDir, "AGENTS.md"));
4566
+ const hasBlock2 = await fileHasManagedBlock(path15.join(rootDir, "AGENTS.md"));
4083
4567
  if (!hasBlock2) {
4084
4568
  return {
4085
4569
  present: false,
@@ -4096,9 +4580,9 @@ async function checkPlatformReadiness(rootDir, platform) {
4096
4580
  readyDetail: "managed block present"
4097
4581
  };
4098
4582
  }
4099
- const skillsDir = path14.join(rootDir, ".agents", "skills");
4583
+ const skillsDir = path15.join(rootDir, ".agents", "skills");
4100
4584
  const hasSkills = await allManagedSkills(
4101
- MODE_NAMES.map((mode) => path14.join(skillsDir, mode, "SKILL.md")),
4585
+ MODE_NAMES.map((mode) => path15.join(skillsDir, mode, "SKILL.md")),
4102
4586
  MANCODE_AGENT_SKILL_MARKERS
4103
4587
  );
4104
4588
  return {
@@ -4110,7 +4594,7 @@ async function checkPlatformReadiness(rootDir, platform) {
4110
4594
  }
4111
4595
  if (platform === "zcode") {
4112
4596
  const hasBlock2 = await fileHasManagedBlock(
4113
- path14.join(rootDir, "AGENTS.md"),
4597
+ path15.join(rootDir, "AGENTS.md"),
4114
4598
  ZCODE_MANCODE_START_MARKER,
4115
4599
  ZCODE_MANCODE_END_MARKER
4116
4600
  );
@@ -4130,9 +4614,9 @@ async function checkPlatformReadiness(rootDir, platform) {
4130
4614
  readyDetail: "managed block present"
4131
4615
  };
4132
4616
  }
4133
- const skillsDir = path14.join(rootDir, ".agents", "skills");
4617
+ const skillsDir = path15.join(rootDir, ".agents", "skills");
4134
4618
  const hasSkills = await allManagedSkills(
4135
- MODE_NAMES.map((mode) => path14.join(skillsDir, mode, "SKILL.md")),
4619
+ MODE_NAMES.map((mode) => path15.join(skillsDir, mode, "SKILL.md")),
4136
4620
  MANCODE_AGENT_SKILL_MARKERS
4137
4621
  );
4138
4622
  return {
@@ -4143,7 +4627,7 @@ async function checkPlatformReadiness(rootDir, platform) {
4143
4627
  };
4144
4628
  }
4145
4629
  const hasBlock = await fileHasManagedBlock(
4146
- path14.join(rootDir, ".github", "copilot-instructions.md")
4630
+ path15.join(rootDir, ".github", "copilot-instructions.md")
4147
4631
  );
4148
4632
  if (!hasBlock) {
4149
4633
  return {
@@ -4154,9 +4638,9 @@ async function checkPlatformReadiness(rootDir, platform) {
4154
4638
  };
4155
4639
  }
4156
4640
  if (!await isPlatformMinimal(rootDir, "copilot")) {
4157
- const promptsDir = path14.join(rootDir, ".github", "prompts");
4641
+ const promptsDir = path15.join(rootDir, ".github", "prompts");
4158
4642
  const hasPrompts = await allManagedSkills(
4159
- MODE_NAMES.map((mode) => path14.join(promptsDir, `${mode}.prompt.md`)),
4643
+ MODE_NAMES.map((mode) => path15.join(promptsDir, `${mode}.prompt.md`)),
4160
4644
  [MODE_FILE_MANAGED_MARKER]
4161
4645
  );
4162
4646
  return {
@@ -4189,13 +4673,13 @@ async function allManagedSkills(paths, markers) {
4189
4673
  async function claudeFullContentReady(rootDir) {
4190
4674
  const skillChecks = MVP2_SKILLS.map(
4191
4675
  (skill) => fileMatches(
4192
- path14.join(rootDir, ".claude", "skills", skill.name, "SKILL.md"),
4676
+ path15.join(rootDir, ".claude", "skills", skill.name, "SKILL.md"),
4193
4677
  (content) => isGeneratedClaudeSkill(content, skill.name)
4194
4678
  )
4195
4679
  );
4196
4680
  const agentChecks = ALL_AGENTS.map(
4197
4681
  (agent) => fileMatches(
4198
- path14.join(rootDir, ".claude", "agents", `${agent.name}.md`),
4682
+ path15.join(rootDir, ".claude", "agents", `${agent.name}.md`),
4199
4683
  (content) => isGeneratedClaudeAgent(content, agent.name)
4200
4684
  )
4201
4685
  );
@@ -4204,7 +4688,7 @@ async function claudeFullContentReady(rootDir) {
4204
4688
  }
4205
4689
  async function fileMatches(filePath, predicate) {
4206
4690
  try {
4207
- return predicate(await fs3.readFile(filePath, "utf-8"));
4691
+ return predicate(await fs4.readFile(filePath, "utf-8"));
4208
4692
  } catch {
4209
4693
  return false;
4210
4694
  }
@@ -4214,7 +4698,7 @@ async function pathsExist(paths) {
4214
4698
  }
4215
4699
  async function fileHasAnyMarker(filePath, needles) {
4216
4700
  try {
4217
- const content = await fs3.readFile(filePath, "utf-8");
4701
+ const content = await fs4.readFile(filePath, "utf-8");
4218
4702
  return needles.some((needle) => content.includes(needle));
4219
4703
  } catch {
4220
4704
  return false;
@@ -4222,8 +4706,8 @@ async function fileHasAnyMarker(filePath, needles) {
4222
4706
  }
4223
4707
  async function isPlatformMinimal(rootDir, platform) {
4224
4708
  try {
4225
- const raw = await fs3.readFile(
4226
- path14.join(rootDir, ".mancode", "config.json"),
4709
+ const raw = await fs4.readFile(
4710
+ path15.join(rootDir, ".mancode", "config.json"),
4227
4711
  "utf-8"
4228
4712
  );
4229
4713
  const config = JSON.parse(raw);
@@ -4236,7 +4720,7 @@ async function isPlatformMinimal(rootDir, platform) {
4236
4720
  }
4237
4721
  async function fileHasManagedBlock(filePath, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
4238
4722
  try {
4239
- const content = await fs3.readFile(filePath, "utf-8");
4723
+ const content = await fs4.readFile(filePath, "utf-8");
4240
4724
  return hasManagedBlock(content, startMarker, endMarker);
4241
4725
  } catch {
4242
4726
  return false;
@@ -4244,8 +4728,8 @@ async function fileHasManagedBlock(filePath, startMarker = DEFAULT_MANCODE_START
4244
4728
  }
4245
4729
  async function claudeHooksRegistered(rootDir) {
4246
4730
  try {
4247
- const raw = await fs3.readFile(
4248
- path14.join(rootDir, ".claude", "settings.json"),
4731
+ const raw = await fs4.readFile(
4732
+ path15.join(rootDir, ".claude", "settings.json"),
4249
4733
  "utf-8"
4250
4734
  );
4251
4735
  const settings = JSON.parse(raw);
@@ -4258,9 +4742,9 @@ function hooksRegistered(settings) {
4258
4742
  if (!isRecord4(settings)) return false;
4259
4743
  const hooks = settings.hooks;
4260
4744
  if (!isRecord4(hooks)) return false;
4261
- return hasHookCommand(hooks.SessionStart, ".mancode/hooks/session-start.sh") && hasHookCommand(
4745
+ return hasHookCommand(hooks.SessionStart, ".mancode/hooks/session-start.mjs") && hasHookCommand(
4262
4746
  hooks.UserPromptSubmit,
4263
- ".mancode/hooks/user-prompt-submit.sh"
4747
+ ".mancode/hooks/user-prompt-submit.mjs"
4264
4748
  );
4265
4749
  }
4266
4750
  function hasHookCommand(value, needle) {
@@ -4275,7 +4759,7 @@ function hasHookCommand(value, needle) {
4275
4759
  }
4276
4760
  async function pathExists4(p) {
4277
4761
  try {
4278
- await fs3.access(p);
4762
+ await fs4.access(p);
4279
4763
  return true;
4280
4764
  } catch {
4281
4765
  return false;
@@ -4290,8 +4774,8 @@ var EXIT_OK2 = 0;
4290
4774
  var EXIT_NOT_INITIALIZED = 1;
4291
4775
  var EXIT_UNSUPPORTED_PLATFORM = 2;
4292
4776
  var EXIT_INSTALL_FAILED = 3;
4293
- async function install(rootDir = process4.cwd(), platform = "claude-code", options = {}) {
4294
- const stateFile = path15.join(rootDir, ".mancode", "state.json");
4777
+ async function install(rootDir = process6.cwd(), platform = "claude-code", options = {}) {
4778
+ const stateFile = path16.join(rootDir, ".mancode", "state.json");
4295
4779
  if (!await pathExists5(stateFile)) {
4296
4780
  console.error("\u2717 mancode not initialized.");
4297
4781
  console.error(" Run `mancode init` first.");
@@ -4377,9 +4861,9 @@ async function install(rootDir = process4.cwd(), platform = "claude-code", optio
4377
4861
  return EXIT_OK2;
4378
4862
  }
4379
4863
  async function readConfig(rootDir) {
4380
- const configPath = path15.join(rootDir, ".mancode", "config.json");
4864
+ const configPath = path16.join(rootDir, ".mancode", "config.json");
4381
4865
  try {
4382
- const raw = await fs4.readFile(configPath, "utf-8");
4866
+ const raw = await fs5.readFile(configPath, "utf-8");
4383
4867
  return { config: JSON.parse(raw), valid: true };
4384
4868
  } catch (err) {
4385
4869
  if (isNodeError4(err) && err.code === "ENOENT") {
@@ -4392,10 +4876,10 @@ function isNodeError4(err) {
4392
4876
  return err instanceof Error && "code" in err;
4393
4877
  }
4394
4878
  async function updateConfig(rootDir, config) {
4395
- const configPath = path15.join(rootDir, ".mancode", "config.json");
4879
+ const configPath = path16.join(rootDir, ".mancode", "config.json");
4396
4880
  const content = `${JSON.stringify(config, null, 2)}
4397
4881
  `;
4398
- await fs4.writeFile(configPath, content, "utf-8");
4882
+ await fs5.writeFile(configPath, content, "utf-8");
4399
4883
  }
4400
4884
  function withDefaultConfig(config, fallbackPlatform) {
4401
4885
  const fallback = fallbackPlatform ?? DEFAULT_CONFIG.platforms[0];
@@ -4427,8 +4911,8 @@ function readConfiguredMinimal(value, platform) {
4427
4911
  }
4428
4912
  async function readStatePlatform(rootDir) {
4429
4913
  try {
4430
- const raw = await fs4.readFile(
4431
- path15.join(rootDir, ".mancode", "state.json"),
4914
+ const raw = await fs5.readFile(
4915
+ path16.join(rootDir, ".mancode", "state.json"),
4432
4916
  "utf-8"
4433
4917
  );
4434
4918
  const state = JSON.parse(raw);
@@ -4439,7 +4923,7 @@ async function readStatePlatform(rootDir) {
4439
4923
  }
4440
4924
  async function pathExists5(p) {
4441
4925
  try {
4442
- await fs4.access(p);
4926
+ await fs5.access(p);
4443
4927
  return true;
4444
4928
  } catch {
4445
4929
  return false;
@@ -4450,11 +4934,11 @@ function isRecord5(value) {
4450
4934
  }
4451
4935
 
4452
4936
  // src/commands/list-platforms.ts
4453
- import { promises as fs5 } from "fs";
4454
- import path16 from "path";
4455
- import process5 from "process";
4937
+ import { promises as fs6 } from "fs";
4938
+ import path17 from "path";
4939
+ import process7 from "process";
4456
4940
  var EXIT_OK3 = 0;
4457
- async function listPlatforms(rootDir = process5.cwd()) {
4941
+ async function listPlatforms(rootDir = process7.cwd()) {
4458
4942
  const installed = new Set(await readInstalledPlatforms(rootDir));
4459
4943
  const platforms = getPlatformInstallers();
4460
4944
  console.log("");
@@ -4467,8 +4951,8 @@ async function listPlatforms(rootDir = process5.cwd()) {
4467
4951
  }
4468
4952
  async function readInstalledPlatforms(rootDir) {
4469
4953
  try {
4470
- const raw = await fs5.readFile(
4471
- path16.join(rootDir, ".mancode", "config.json"),
4954
+ const raw = await fs6.readFile(
4955
+ path17.join(rootDir, ".mancode", "config.json"),
4472
4956
  "utf-8"
4473
4957
  );
4474
4958
  const config = JSON.parse(raw);
@@ -4492,16 +4976,16 @@ function describePlatform(platform) {
4492
4976
  }
4493
4977
 
4494
4978
  // src/commands/manps.ts
4495
- import { access as access3 } from "fs/promises";
4496
- import path18 from "path";
4497
- import { createInterface } from "readline/promises";
4979
+ import { access as access4 } from "fs/promises";
4980
+ import path19 from "path";
4981
+ import { createInterface as createInterface2 } from "readline/promises";
4498
4982
 
4499
4983
  // src/system/preseason.ts
4500
- import { execFile } from "child_process";
4984
+ import { execFile as execFile3 } from "child_process";
4501
4985
  import { createHash } from "crypto";
4502
4986
  import { existsSync, readFileSync } from "fs";
4503
4987
  import {
4504
- access as access2,
4988
+ access as access3,
4505
4989
  lstat,
4506
4990
  mkdir as mkdir7,
4507
4991
  readFile as readFile8,
@@ -4509,7 +4993,7 @@ import {
4509
4993
  rename,
4510
4994
  writeFile as writeFile9
4511
4995
  } from "fs/promises";
4512
- import path17 from "path";
4996
+ import path18 from "path";
4513
4997
  var PRESEASON_AREAS = [
4514
4998
  "all",
4515
4999
  "deps",
@@ -4589,9 +5073,9 @@ async function runPreseasonScan(projectRoot, area = "all") {
4589
5073
  const needsFiles = normalizedArea === "all" || normalizedArea === "dead-code" || normalizedArea === "config";
4590
5074
  const files = needsFiles ? await listProjectFiles(projectRoot) : [];
4591
5075
  const issues = (await scanArea(projectRoot, normalizedArea, pkg, files)).slice(0, 20);
4592
- const reportDir = path17.join(projectRoot, ".mancode", "preseason-reports");
5076
+ const reportDir = path18.join(projectRoot, ".mancode", "preseason-reports");
4593
5077
  await mkdir7(reportDir, { recursive: true });
4594
- const issueDbPath = path17.join(
5078
+ const issueDbPath = path18.join(
4595
5079
  projectRoot,
4596
5080
  ".mancode",
4597
5081
  "preseason-issues.json"
@@ -4611,7 +5095,7 @@ async function runPreseasonScan(projectRoot, area = "all") {
4611
5095
  const database = await buildIssueDatabase(projectRoot, report);
4612
5096
  await writeFile9(reportPath, renderPreseasonReport(report), "utf-8");
4613
5097
  await writeFile9(
4614
- path17.join(projectRoot, ".mancode", "preseason-report.md"),
5098
+ path18.join(projectRoot, ".mancode", "preseason-report.md"),
4615
5099
  renderPreseasonReport(report),
4616
5100
  "utf-8"
4617
5101
  );
@@ -4619,7 +5103,7 @@ async function runPreseasonScan(projectRoot, area = "all") {
4619
5103
  return report;
4620
5104
  }
4621
5105
  async function runPreseasonRemediation(projectRoot, issues, options = {}) {
4622
- const issueDbPath = path17.join(
5106
+ const issueDbPath = path18.join(
4623
5107
  projectRoot,
4624
5108
  ".mancode",
4625
5109
  "preseason-issues.json"
@@ -4733,7 +5217,7 @@ async function scanArea(projectRoot, area, pkg, files) {
4733
5217
  async function allocateReportPath(reportDir, baseName) {
4734
5218
  for (let attempt = 0; attempt < 1e3; attempt++) {
4735
5219
  const suffix = attempt === 0 ? "" : `-${attempt + 1}`;
4736
- const candidate = path17.join(reportDir, `${baseName}${suffix}.md`);
5220
+ const candidate = path18.join(reportDir, `${baseName}${suffix}.md`);
4737
5221
  if (!existsSync(candidate)) return candidate;
4738
5222
  }
4739
5223
  throw new Error(`unable to allocate preseason report path: ${baseName}`);
@@ -4782,8 +5266,8 @@ async function walk(root, current, results, depth) {
4782
5266
  }
4783
5267
  for (const entry of entries) {
4784
5268
  if (IGNORE_DIRS.has(entry)) continue;
4785
- const abs = path17.join(current, entry);
4786
- const rel = path17.relative(root, abs);
5269
+ const abs = path18.join(current, entry);
5270
+ const rel = path18.relative(root, abs);
4787
5271
  let info;
4788
5272
  try {
4789
5273
  info = await lstat(abs);
@@ -4793,7 +5277,7 @@ async function walk(root, current, results, depth) {
4793
5277
  if (info.isSymbolicLink()) continue;
4794
5278
  if (info.isDirectory()) {
4795
5279
  await walk(root, abs, results, depth + 1);
4796
- } else if (SOURCE_EXTENSIONS.has(path17.extname(entry)) || entry === "package.json") {
5280
+ } else if (SOURCE_EXTENSIONS.has(path18.extname(entry)) || entry === "package.json") {
4797
5281
  results.push(rel);
4798
5282
  if (results.length >= MAX_PROJECT_FILES) return;
4799
5283
  }
@@ -4801,7 +5285,7 @@ async function walk(root, current, results, depth) {
4801
5285
  }
4802
5286
  async function readPackageJson(projectRoot) {
4803
5287
  try {
4804
- const raw = await readFile8(path17.join(projectRoot, "package.json"), "utf-8");
5288
+ const raw = await readFile8(path18.join(projectRoot, "package.json"), "utf-8");
4805
5289
  return JSON.parse(raw);
4806
5290
  } catch {
4807
5291
  return null;
@@ -4894,7 +5378,7 @@ function scanTodos(projectRoot, files) {
4894
5378
  const matches = [];
4895
5379
  for (const file of files) {
4896
5380
  if (matches.length >= 7) break;
4897
- const abs = path17.join(projectRoot, file);
5381
+ const abs = path18.join(projectRoot, file);
4898
5382
  matches.push(...readTodoIssues(abs, file, matches.length));
4899
5383
  }
4900
5384
  return matches.slice(0, 7);
@@ -4931,7 +5415,7 @@ function scanTestGaps(files) {
4931
5415
  if (sourceFiles.length === 0) return [];
4932
5416
  const tests = new Set(files.filter((file) => file.startsWith("tests/")));
4933
5417
  const missing = sourceFiles.filter((file) => {
4934
- const base = path17.basename(file).replace(/\.(ts|tsx|js|jsx)$/, "");
5418
+ const base = path18.basename(file).replace(/\.(ts|tsx|js|jsx)$/, "");
4935
5419
  return !Array.from(tests).some((test) => test.includes(base));
4936
5420
  }).slice(0, 4);
4937
5421
  return missing.map((file, index) => ({
@@ -4940,13 +5424,13 @@ function scanTestGaps(files) {
4940
5424
  type: "tests",
4941
5425
  title: "Core source file has no obvious test",
4942
5426
  file,
4943
- detail: `No matching test file was found for ${path17.basename(file)}.`,
5427
+ detail: `No matching test file was found for ${path18.basename(file)}.`,
4944
5428
  recommendation: "Add focused coverage for the public behavior or document why this module is exercised indirectly."
4945
5429
  }));
4946
5430
  }
4947
5431
  function scanConfig(projectRoot, files) {
4948
5432
  const issues = [];
4949
- if (!files.includes(".gitignore") && !pathExistsSync(path17.join(projectRoot, ".gitignore"))) {
5433
+ if (!files.includes(".gitignore") && !pathExistsSync(path18.join(projectRoot, ".gitignore"))) {
4950
5434
  issues.push({
4951
5435
  id: "config-gitignore",
4952
5436
  severity: "P1",
@@ -4957,7 +5441,7 @@ function scanConfig(projectRoot, files) {
4957
5441
  recommendation: "Add a .gitignore that excludes dependencies, build output, coverage, and local env files."
4958
5442
  });
4959
5443
  }
4960
- if (!files.includes(".editorconfig") && !pathExistsSync(path17.join(projectRoot, ".editorconfig"))) {
5444
+ if (!files.includes(".editorconfig") && !pathExistsSync(path18.join(projectRoot, ".editorconfig"))) {
4961
5445
  issues.push({
4962
5446
  id: "config-editorconfig",
4963
5447
  severity: "P2",
@@ -4976,7 +5460,7 @@ function scanAestheticDrift(projectRoot, files) {
4976
5460
  for (const file of frontendFiles) {
4977
5461
  let content;
4978
5462
  try {
4979
- content = readFileSyncSafe(path17.join(projectRoot, file));
5463
+ content = readFileSyncSafe(path18.join(projectRoot, file));
4980
5464
  } catch {
4981
5465
  continue;
4982
5466
  }
@@ -4996,7 +5480,7 @@ function scanAestheticDrift(projectRoot, files) {
4996
5480
  return issues;
4997
5481
  }
4998
5482
  async function scanArchitecture(projectRoot) {
4999
- const localBinary = path17.join(
5483
+ const localBinary = path18.join(
5000
5484
  projectRoot,
5001
5485
  "node_modules",
5002
5486
  ".bin",
@@ -5044,7 +5528,7 @@ async function hasDependencyCruiserConfig(projectRoot) {
5044
5528
  "dependency-cruiser.config.mjs"
5045
5529
  ];
5046
5530
  const results = await Promise.all(
5047
- files.map((file) => pathExists6(path17.join(projectRoot, file)))
5531
+ files.map((file) => pathExists6(path18.join(projectRoot, file)))
5048
5532
  );
5049
5533
  return results.some(Boolean);
5050
5534
  }
@@ -5061,7 +5545,7 @@ function architectureScannerUnavailableIssue() {
5061
5545
  function runDepcruise(binary, projectRoot) {
5062
5546
  return new Promise((resolve) => {
5063
5547
  const needsShell = process.platform === "win32";
5064
- execFile(
5548
+ execFile3(
5065
5549
  binary,
5066
5550
  ["--output-type", "json", "."],
5067
5551
  {
@@ -5070,11 +5554,11 @@ function runDepcruise(binary, projectRoot) {
5070
5554
  maxBuffer: DEPCRUISE_MAX_BUFFER,
5071
5555
  shell: needsShell
5072
5556
  },
5073
- (error, stdout, stderr) => {
5557
+ (error, stdout2, stderr) => {
5074
5558
  if (!error) {
5075
5559
  resolve({
5076
5560
  status: "ok",
5077
- stdout,
5561
+ stdout: stdout2,
5078
5562
  stderr,
5079
5563
  exitCode: 0,
5080
5564
  timedOut: false
@@ -5094,7 +5578,7 @@ function runDepcruise(binary, projectRoot) {
5094
5578
  }
5095
5579
  resolve({
5096
5580
  status: "failed",
5097
- stdout: nodeError.stdout ?? stdout,
5581
+ stdout: nodeError.stdout ?? stdout2,
5098
5582
  stderr: nodeError.stderr ?? stderr,
5099
5583
  exitCode: typeof nodeError.code === "number" ? nodeError.code : null,
5100
5584
  timedOut: Boolean(nodeError.killed || nodeError.signal === "SIGTERM")
@@ -5150,9 +5634,9 @@ function inferCommands(pkg) {
5150
5634
  return ["lint", "test", "build"].filter((name) => scripts[name]).map((name) => `npm run ${name}`);
5151
5635
  }
5152
5636
  async function buildIssueDatabase(projectRoot, report) {
5153
- const reportRef = path17.relative(projectRoot, report.reportPath);
5637
+ const reportRef = path18.relative(projectRoot, report.reportPath);
5154
5638
  const run = {
5155
- id: path17.basename(report.reportPath, ".md"),
5639
+ id: path18.basename(report.reportPath, ".md"),
5156
5640
  generatedAt: report.generatedAt,
5157
5641
  area: report.area,
5158
5642
  reportPath: reportRef,
@@ -5258,7 +5742,7 @@ function compareIssueRecords(a, b) {
5258
5742
  }
5259
5743
  async function applySafeRemediation(projectRoot, issue) {
5260
5744
  if (issue.id === "config-gitignore" && issue.file === ".gitignore") {
5261
- const gitignorePath = path17.join(projectRoot, ".gitignore");
5745
+ const gitignorePath = path18.join(projectRoot, ".gitignore");
5262
5746
  if (pathExistsSync(gitignorePath)) {
5263
5747
  return { applied: false };
5264
5748
  }
@@ -5267,7 +5751,7 @@ async function applySafeRemediation(projectRoot, issue) {
5267
5751
  return { applied: true, action: "created .gitignore" };
5268
5752
  }
5269
5753
  if (issue.id === "config-editorconfig" && issue.file === ".editorconfig") {
5270
- const editorconfigPath = path17.join(projectRoot, ".editorconfig");
5754
+ const editorconfigPath = path18.join(projectRoot, ".editorconfig");
5271
5755
  if (pathExistsSync(editorconfigPath)) {
5272
5756
  return { applied: false };
5273
5757
  }
@@ -5307,7 +5791,7 @@ async function inferSafePackageScript(projectRoot, scriptName) {
5307
5791
  }
5308
5792
  }
5309
5793
  async function addPackageScript(projectRoot, scriptName, script) {
5310
- const packagePath = path17.join(projectRoot, "package.json");
5794
+ const packagePath = path18.join(projectRoot, "package.json");
5311
5795
  let pkg;
5312
5796
  try {
5313
5797
  pkg = JSON.parse(await readFile8(packagePath, "utf-8"));
@@ -5401,7 +5885,7 @@ function pathExistsSync(file) {
5401
5885
  }
5402
5886
  async function pathExists6(file) {
5403
5887
  try {
5404
- await access2(file);
5888
+ await access3(file);
5405
5889
  return true;
5406
5890
  } catch {
5407
5891
  return false;
@@ -5420,7 +5904,7 @@ var EXIT_NOT_INITIALIZED2 = 1;
5420
5904
  var EXIT_SCAN_FAILED = 2;
5421
5905
  var EXIT_INVALID_ARG = 3;
5422
5906
  async function manps(rootDir, area = "all", options = {}) {
5423
- if (!await pathExists7(path18.join(rootDir, ".mancode", "state.json"))) {
5907
+ if (!await pathExists7(path19.join(rootDir, ".mancode", "state.json"))) {
5424
5908
  if (options.json) {
5425
5909
  console.log(JSON.stringify({ error: "not initialized" }, null, 2));
5426
5910
  } else {
@@ -5499,8 +5983,8 @@ async function manps(rootDir, area = "all", options = {}) {
5499
5983
  console.log(
5500
5984
  `Issues: ${report.issues.length} total (P0 ${p0}, P1 ${p1}, P2 ${p2})`
5501
5985
  );
5502
- console.log(`Report: ${path18.relative(rootDir, report.reportPath)}`);
5503
- console.log(`Issue DB: ${path18.relative(rootDir, report.issueDbPath)}`);
5986
+ console.log(`Report: ${path19.relative(rootDir, report.reportPath)}`);
5987
+ console.log(`Issue DB: ${path19.relative(rootDir, report.issueDbPath)}`);
5504
5988
  if (report.issues.length > 0) {
5505
5989
  console.log("");
5506
5990
  for (const issue of report.issues.slice(0, 7)) {
@@ -5516,7 +6000,7 @@ async function manps(rootDir, area = "all", options = {}) {
5516
6000
  console.log(` Skipped: ${remediation.skipped}`);
5517
6001
  console.log(` Fixed: ${remediation.fixed}`);
5518
6002
  console.log(
5519
- ` Issue DB: ${path18.relative(rootDir, remediation.issueDbPath)}`
6003
+ ` Issue DB: ${path19.relative(rootDir, remediation.issueDbPath)}`
5520
6004
  );
5521
6005
  }
5522
6006
  return EXIT_OK4;
@@ -5542,7 +6026,7 @@ async function runRemediation(rootDir, report, options, silent = false) {
5542
6026
  write
5543
6027
  });
5544
6028
  }
5545
- const rl = createInterface({
6029
+ const rl = createInterface2({
5546
6030
  input: process.stdin,
5547
6031
  output: process.stdout
5548
6032
  });
@@ -5564,30 +6048,253 @@ async function readStdinLines() {
5564
6048
  }
5565
6049
  async function pathExists7(p) {
5566
6050
  try {
5567
- await access3(p);
6051
+ await access4(p);
5568
6052
  return true;
5569
6053
  } catch {
5570
6054
  return false;
5571
6055
  }
5572
6056
  }
5573
6057
 
5574
- // src/commands/refresh-style.ts
5575
- import { promises as fs6 } from "fs";
5576
- import path19 from "path";
5577
- import process6 from "process";
6058
+ // src/commands/refresh-project.ts
6059
+ import { randomUUID } from "crypto";
6060
+ import { promises as fs7 } from "fs";
6061
+ import path20 from "path";
6062
+ import process8 from "process";
5578
6063
  var EXIT_OK5 = 0;
5579
6064
  var EXIT_NOT_INITIALIZED3 = 1;
5580
- async function refreshStyle(rootDir = process6.cwd()) {
5581
- const stateFile = path19.join(rootDir, ".mancode", "state.json");
5582
- if (!await pathExists8(stateFile)) {
6065
+ var EXIT_CORRUPT_STATE = 2;
6066
+ var EXIT_REFRESH_FAILED = 3;
6067
+ async function refreshProject(rootDir = process8.cwd()) {
6068
+ const mancodeDir = path20.join(rootDir, ".mancode");
6069
+ const statePath = path20.join(mancodeDir, "state.json");
6070
+ if (!await pathExists8(statePath)) {
5583
6071
  console.error("\u2717 mancode not initialized.");
5584
6072
  console.error(" Run `mancode init` first.");
5585
6073
  return EXIT_NOT_INITIALIZED3;
5586
6074
  }
6075
+ const state = await readRequiredState(statePath);
6076
+ if (!state) {
6077
+ console.error("\u2717 .mancode/state.json is corrupt or incomplete.");
6078
+ console.error(" Run `mancode init --force` to repair it.");
6079
+ return EXIT_CORRUPT_STATE;
6080
+ }
6081
+ let factsWritten = false;
6082
+ try {
6083
+ const [profile, team, hasGit, hasManifest] = await Promise.all([
6084
+ detectProjectProfile(rootDir),
6085
+ detectTeamStatus(rootDir),
6086
+ pathExists8(path20.join(rootDir, ".git")),
6087
+ hasProjectManifest2(rootDir)
6088
+ ]);
6089
+ const uiLibrary = primaryUiLibrary(profile);
6090
+ const stack = [...profile.languages, ...profile.frameworks];
6091
+ const config = await readJson3(path20.join(mancodeDir, "config.json"));
6092
+ const configuredTeam = config.forceTeamMode === true ? true : config.teamMode === "on" ? true : config.teamMode === "off" ? false : team.isTeam;
6093
+ const nextState = {
6094
+ ...state,
6095
+ techStack: stack.join(" + ") || profile.projectKind,
6096
+ uiLibrary: uiLibrary ?? "None",
6097
+ projectMode: hasGit || hasManifest ? "detected" : "generic",
6098
+ teamModeAutoDetected: configuredTeam,
6099
+ contributors: team.contributors
6100
+ };
6101
+ await writeProjectFacts(
6102
+ statePath,
6103
+ path20.join(mancodeDir, "project-profile.json"),
6104
+ `${JSON.stringify(nextState, null, 2)}
6105
+ `,
6106
+ `${JSON.stringify(profile, null, 2)}
6107
+ `
6108
+ );
6109
+ factsWritten = true;
6110
+ const refreshedPlatforms = await refreshStaticPlatforms(
6111
+ rootDir,
6112
+ config,
6113
+ state.platform,
6114
+ stack,
6115
+ uiLibrary,
6116
+ profile
6117
+ );
6118
+ console.log("\u2713 Project facts refreshed.");
6119
+ console.log(
6120
+ ` ${hasGit ? "Git detected" : "No Git repository"} | ${hasManifest ? "project manifest detected" : "generic project"}`
6121
+ );
6122
+ console.log(
6123
+ ` Stack: ${nextState.techStack} | UI: ${nextState.uiLibrary}`
6124
+ );
6125
+ if (refreshedPlatforms.length > 0) {
6126
+ console.log(` Refreshed adapters: ${refreshedPlatforms.join(", ")}`);
6127
+ }
6128
+ console.log(
6129
+ " Run `mancode refresh-style` if UI files or dependencies changed."
6130
+ );
6131
+ return EXIT_OK5;
6132
+ } catch (error) {
6133
+ const message = error instanceof Error ? error.message : String(error);
6134
+ console.error(`\u2717 Project refresh failed: ${message}`);
6135
+ if (factsWritten) {
6136
+ console.error(
6137
+ " Project facts were saved, but one or more static adapters remain stale."
6138
+ );
6139
+ }
6140
+ return EXIT_REFRESH_FAILED;
6141
+ }
6142
+ }
6143
+ async function hasProjectManifest2(rootDir) {
6144
+ for (const manifest of PROJECT_MANIFESTS) {
6145
+ if (await pathExists8(path20.join(rootDir, manifest))) return true;
6146
+ }
6147
+ return false;
6148
+ }
6149
+ async function writeProjectFacts(statePath, profilePath, stateContent, profileContent) {
6150
+ await Promise.all([
6151
+ ensureReplaceableFile(statePath),
6152
+ ensureReplaceableFile(profilePath)
6153
+ ]);
6154
+ const previousProfile = await readOptionalText(profilePath);
6155
+ const stateTemp = temporaryPath(statePath);
6156
+ const profileTemp = temporaryPath(profilePath);
6157
+ let profileReplaced = false;
6158
+ try {
6159
+ await Promise.all([
6160
+ fs7.writeFile(stateTemp, stateContent, "utf-8"),
6161
+ fs7.writeFile(profileTemp, profileContent, "utf-8")
6162
+ ]);
6163
+ await fs7.rename(profileTemp, profilePath);
6164
+ profileReplaced = true;
6165
+ await fs7.rename(stateTemp, statePath);
6166
+ } catch (error) {
6167
+ if (profileReplaced) {
6168
+ if (previousProfile === null) {
6169
+ await fs7.rm(profilePath, { force: true });
6170
+ } else {
6171
+ await replaceTextFile(profilePath, previousProfile);
6172
+ }
6173
+ }
6174
+ throw error;
6175
+ } finally {
6176
+ await Promise.all([
6177
+ fs7.rm(stateTemp, { force: true }),
6178
+ fs7.rm(profileTemp, { force: true })
6179
+ ]);
6180
+ }
6181
+ }
6182
+ async function ensureReplaceableFile(filePath) {
6183
+ try {
6184
+ const entry = await fs7.lstat(filePath);
6185
+ if (!entry.isFile()) {
6186
+ throw new Error(`cannot replace non-file path: ${filePath}`);
6187
+ }
6188
+ } catch (error) {
6189
+ if (isNodeError6(error) && error.code === "ENOENT") return;
6190
+ throw error;
6191
+ }
6192
+ }
6193
+ async function replaceTextFile(filePath, content) {
6194
+ const tempPath = temporaryPath(filePath);
6195
+ try {
6196
+ await fs7.writeFile(tempPath, content, "utf-8");
6197
+ await fs7.rename(tempPath, filePath);
6198
+ } finally {
6199
+ await fs7.rm(tempPath, { force: true });
6200
+ }
6201
+ }
6202
+ async function readOptionalText(filePath) {
6203
+ try {
6204
+ return await fs7.readFile(filePath, "utf-8");
6205
+ } catch (error) {
6206
+ if (isNodeError6(error) && error.code === "ENOENT") return null;
6207
+ throw error;
6208
+ }
6209
+ }
6210
+ function temporaryPath(filePath) {
6211
+ return path20.join(
6212
+ path20.dirname(filePath),
6213
+ `.${path20.basename(filePath)}.${process8.pid}.${randomUUID()}.tmp`
6214
+ );
6215
+ }
6216
+ async function refreshStaticPlatforms(rootDir, config, fallbackPlatform, stack, uiLibrary, profile) {
6217
+ const platforms = configuredPlatforms(config, fallbackPlatform).filter(
6218
+ (platform) => platform !== "claude-code"
6219
+ );
6220
+ const refreshed = [];
6221
+ for (const platform of platforms) {
6222
+ const installer = getPlatformInstaller(platform);
6223
+ if (!installer) continue;
6224
+ await installer.install(rootDir, {
6225
+ techStack: stack,
6226
+ uiLibrary,
6227
+ projectProfile: profile,
6228
+ minimal: platformIsMinimal(config, platform),
6229
+ force: true
6230
+ });
6231
+ refreshed.push(installer.displayName);
6232
+ }
6233
+ return refreshed;
6234
+ }
6235
+ function configuredPlatforms(config, fallbackPlatform) {
6236
+ const configured = Array.isArray(config.platforms) ? config.platforms : [fallbackPlatform];
6237
+ return configured.filter(
6238
+ (platform) => typeof platform === "string" && getPlatformInstaller(platform) !== null
6239
+ );
6240
+ }
6241
+ function platformIsMinimal(config, platform) {
6242
+ if (!isRecord7(config.platformOptions)) return false;
6243
+ const options = config.platformOptions[platform];
6244
+ return isRecord7(options) && options.minimal === true;
6245
+ }
6246
+ async function readJson3(filePath) {
6247
+ try {
6248
+ const value = JSON.parse(await fs7.readFile(filePath, "utf-8"));
6249
+ return isRecord7(value) ? value : {};
6250
+ } catch {
6251
+ return {};
6252
+ }
6253
+ }
6254
+ async function readRequiredState(filePath) {
6255
+ let state;
6256
+ try {
6257
+ const raw = await fs7.readFile(filePath, "utf-8");
6258
+ const parsed = JSON.parse(raw);
6259
+ if (!isRecord7(parsed)) return null;
6260
+ state = parsed;
6261
+ } catch {
6262
+ return null;
6263
+ }
6264
+ return typeof state.version === "string" && typeof state.currentMode === "string" && typeof state.platform === "string" ? state : null;
6265
+ }
6266
+ function isRecord7(value) {
6267
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6268
+ }
6269
+ function isNodeError6(error) {
6270
+ return error instanceof Error && "code" in error;
6271
+ }
6272
+ async function pathExists8(filePath) {
6273
+ try {
6274
+ await fs7.access(filePath);
6275
+ return true;
6276
+ } catch {
6277
+ return false;
6278
+ }
6279
+ }
6280
+
6281
+ // src/commands/refresh-style.ts
6282
+ import { promises as fs8 } from "fs";
6283
+ import path21 from "path";
6284
+ import process9 from "process";
6285
+ var EXIT_OK6 = 0;
6286
+ var EXIT_NOT_INITIALIZED4 = 1;
6287
+ async function refreshStyle(rootDir = process9.cwd()) {
6288
+ const stateFile = path21.join(rootDir, ".mancode", "state.json");
6289
+ if (!await pathExists9(stateFile)) {
6290
+ console.error("\u2717 mancode not initialized.");
6291
+ console.error(" Run `mancode init` first.");
6292
+ return EXIT_NOT_INITIALIZED4;
6293
+ }
5587
6294
  console.log("\u2713 \u5237\u65B0\u9879\u76EE profile...");
5588
6295
  const profile = await detectProjectProfile(rootDir);
5589
- const profilePath = path19.join(rootDir, ".mancode", "project-profile.json");
5590
- await fs6.writeFile(
6296
+ const profilePath = path21.join(rootDir, ".mancode", "project-profile.json");
6297
+ await fs8.writeFile(
5591
6298
  profilePath,
5592
6299
  `${JSON.stringify(profile, null, 2)}
5593
6300
  `,
@@ -5604,18 +6311,18 @@ async function refreshStyle(rootDir = process6.cwd()) {
5604
6311
  );
5605
6312
  console.log(" Updated .mancode/project-profile.json.");
5606
6313
  await printStaticPlatformRefreshHint(rootDir);
5607
- return EXIT_OK5;
6314
+ return EXIT_OK6;
5608
6315
  }
5609
6316
  console.log("\u2713 \u626B\u63CF\u9879\u76EE\u8BBE\u8BA1 token...");
5610
6317
  const tokens = await scanAesthetics(rootDir, uiLibraryHint);
5611
- const tokensPath = path19.join(
6318
+ const tokensPath = path21.join(
5612
6319
  rootDir,
5613
6320
  ".mancode",
5614
6321
  "aesthetics",
5615
6322
  "style-tokens.json"
5616
6323
  );
5617
- await fs6.mkdir(path19.dirname(tokensPath), { recursive: true });
5618
- await fs6.writeFile(
6324
+ await fs8.mkdir(path21.dirname(tokensPath), { recursive: true });
6325
+ await fs8.writeFile(
5619
6326
  tokensPath,
5620
6327
  `${JSON.stringify(tokens, null, 2)}
5621
6328
  `,
@@ -5663,7 +6370,7 @@ async function refreshStyle(rootDir = process6.cwd()) {
5663
6370
  "\u5DF2\u66F4\u65B0 .mancode/project-profile.json \u548C .mancode/aesthetics/style-tokens.json"
5664
6371
  );
5665
6372
  await printStaticPlatformRefreshHint(rootDir);
5666
- return EXIT_OK5;
6373
+ return EXIT_OK6;
5667
6374
  }
5668
6375
  async function printStaticPlatformRefreshHint(rootDir) {
5669
6376
  const platforms = await readInstalledPlatforms2(rootDir);
@@ -5680,11 +6387,11 @@ async function printStaticPlatformRefreshHint(rootDir) {
5680
6387
  );
5681
6388
  }
5682
6389
  async function refreshLegacyStateContext(rootDir, profile, uiLibrary) {
5683
- const statePath = path19.join(rootDir, ".mancode", "state.json");
6390
+ const statePath = path21.join(rootDir, ".mancode", "state.json");
5684
6391
  try {
5685
- const state = JSON.parse(await fs6.readFile(statePath, "utf-8"));
6392
+ const state = JSON.parse(await fs8.readFile(statePath, "utf-8"));
5686
6393
  const stack = [...profile.languages, ...profile.frameworks];
5687
- await fs6.writeFile(
6394
+ await fs8.writeFile(
5688
6395
  statePath,
5689
6396
  `${JSON.stringify(
5690
6397
  {
@@ -5703,8 +6410,8 @@ async function refreshLegacyStateContext(rootDir, profile, uiLibrary) {
5703
6410
  }
5704
6411
  async function readInstalledPlatforms2(rootDir) {
5705
6412
  try {
5706
- const raw = await fs6.readFile(
5707
- path19.join(rootDir, ".mancode", "config.json"),
6413
+ const raw = await fs8.readFile(
6414
+ path21.join(rootDir, ".mancode", "config.json"),
5708
6415
  "utf-8"
5709
6416
  );
5710
6417
  const config = JSON.parse(raw);
@@ -5715,9 +6422,9 @@ async function readInstalledPlatforms2(rootDir) {
5715
6422
  return [];
5716
6423
  }
5717
6424
  }
5718
- async function pathExists8(p) {
6425
+ async function pathExists9(p) {
5719
6426
  try {
5720
- await fs6.access(p);
6427
+ await fs8.access(p);
5721
6428
  return true;
5722
6429
  } catch {
5723
6430
  return false;
@@ -5726,9 +6433,9 @@ async function pathExists8(p) {
5726
6433
 
5727
6434
  // src/commands/status.ts
5728
6435
  import { spawn } from "child_process";
5729
- import { promises as fs7 } from "fs";
5730
- import path22 from "path";
5731
- import process7 from "process";
6436
+ import { promises as fs9 } from "fs";
6437
+ import path24 from "path";
6438
+ import process10 from "process";
5732
6439
 
5733
6440
  // src/system/workflow.ts
5734
6441
  import {
@@ -5739,11 +6446,11 @@ import {
5739
6446
  stat as stat4,
5740
6447
  writeFile as writeFile11
5741
6448
  } from "fs/promises";
5742
- import path21 from "path";
6449
+ import path23 from "path";
5743
6450
 
5744
6451
  // src/system/review-ledger.ts
5745
6452
  import { mkdir as mkdir8, readFile as readFile9, stat as stat3, writeFile as writeFile10 } from "fs/promises";
5746
- import path20 from "path";
6453
+ import path22 from "path";
5747
6454
  var REVIEW_FILE = "review-ledger.json";
5748
6455
  var TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
5749
6456
  var BLOCKER_ID_PATTERN = /^[A-Z][A-Z0-9-]{0,31}$/;
@@ -5859,8 +6566,8 @@ async function readReviewLedger(projectRoot, taskId) {
5859
6566
  }
5860
6567
  }
5861
6568
  function isReviewLedger(value) {
5862
- if (!isRecord7(value)) return false;
5863
- if (value.version !== "1.0" || !isReviewDepth(value.depth) || !isDomainArray(value.requiredDomains) || !isDomainArray(value.completedDomains) || !isRecord7(value.reports) || !Array.isArray(value.blockers) || value.remediationRounds !== 0 && value.remediationRounds !== 1) {
6569
+ if (!isRecord8(value)) return false;
6570
+ if (value.version !== "1.0" || !isReviewDepth(value.depth) || !isDomainArray(value.requiredDomains) || !isDomainArray(value.completedDomains) || !isRecord8(value.reports) || !Array.isArray(value.blockers) || value.remediationRounds !== 0 && value.remediationRounds !== 1) {
5864
6571
  return false;
5865
6572
  }
5866
6573
  const requiredDomains = value.requiredDomains;
@@ -5876,13 +6583,13 @@ function isReviewLedger(value) {
5876
6583
  }
5877
6584
  const blockerIds = /* @__PURE__ */ new Set();
5878
6585
  return blockers.every(
5879
- (blocker) => isRecord7(blocker) && typeof blocker.id === "string" && BLOCKER_ID_PATTERN.test(blocker.id) && !blockerIds.has(blocker.id) && blockerIds.add(blocker.id) && isReviewDomain(blocker.domain) && completedDomains.includes(blocker.domain) && (blocker.status === "open" || blocker.status === "resolved")
6586
+ (blocker) => isRecord8(blocker) && typeof blocker.id === "string" && BLOCKER_ID_PATTERN.test(blocker.id) && !blockerIds.has(blocker.id) && blockerIds.add(blocker.id) && isReviewDomain(blocker.domain) && completedDomains.includes(blocker.domain) && (blocker.status === "open" || blocker.status === "resolved")
5880
6587
  );
5881
6588
  }
5882
6589
  function isDomainArray(value) {
5883
6590
  return Array.isArray(value) && value.every(isReviewDomain);
5884
6591
  }
5885
- function isRecord7(value) {
6592
+ function isRecord8(value) {
5886
6593
  return typeof value === "object" && value !== null && !Array.isArray(value);
5887
6594
  }
5888
6595
  async function requireReviewLedger(projectRoot, taskId) {
@@ -5892,17 +6599,17 @@ async function requireReviewLedger(projectRoot, taskId) {
5892
6599
  return ledger;
5893
6600
  }
5894
6601
  async function writeReviewLedger(projectRoot, taskId, ledger) {
5895
- const dir = path20.join(projectRoot, ".mancode", "workflows", taskId);
6602
+ const dir = path22.join(projectRoot, ".mancode", "workflows", taskId);
5896
6603
  await mkdir8(dir, { recursive: true });
5897
6604
  await writeFile10(
5898
- path20.join(dir, REVIEW_FILE),
6605
+ path22.join(dir, REVIEW_FILE),
5899
6606
  `${JSON.stringify(ledger, null, 2)}
5900
6607
  `,
5901
6608
  "utf-8"
5902
6609
  );
5903
6610
  }
5904
6611
  function reviewPath(projectRoot, taskId) {
5905
- return path20.join(projectRoot, ".mancode", "workflows", taskId, REVIEW_FILE);
6612
+ return path22.join(projectRoot, ".mancode", "workflows", taskId, REVIEW_FILE);
5906
6613
  }
5907
6614
  function assertValidTaskId(taskId) {
5908
6615
  if (!TASK_ID_PATTERN.test(taskId)) {
@@ -5915,7 +6622,7 @@ function assertSafeReportPath(report) {
5915
6622
  }
5916
6623
  }
5917
6624
  async function assertReportExists(projectRoot, taskId, report) {
5918
- const reportPath = path20.join(
6625
+ const reportPath = path22.join(
5919
6626
  projectRoot,
5920
6627
  ".mancode",
5921
6628
  "workflows",
@@ -5929,9 +6636,9 @@ async function assertReportExists(projectRoot, taskId, report) {
5929
6636
  throw new Error(`review report not found: ${report}`);
5930
6637
  }
5931
6638
  function isSafeReportPath(report) {
5932
- const normalized = path20.posix.normalize(report.replaceAll("\\", "/"));
6639
+ const normalized = path22.posix.normalize(report.replaceAll("\\", "/"));
5933
6640
  return Boolean(
5934
- report.trim() && !path20.isAbsolute(report) && normalized !== ".." && !normalized.startsWith("../") && normalized.endsWith(".md")
6641
+ report.trim() && !path22.isAbsolute(report) && normalized !== ".." && !normalized.startsWith("../") && normalized.endsWith(".md")
5935
6642
  );
5936
6643
  }
5937
6644
 
@@ -5979,7 +6686,7 @@ async function allocateTaskId(projectRoot, baseTaskId) {
5979
6686
  await mkdir9(workflowDir(projectRoot, taskId));
5980
6687
  return taskId;
5981
6688
  } catch (err) {
5982
- if (isNodeError6(err) && err.code === "EEXIST") {
6689
+ if (isNodeError7(err) && err.code === "EEXIST") {
5983
6690
  continue;
5984
6691
  }
5985
6692
  throw err;
@@ -6055,19 +6762,19 @@ async function deleteWorkflow(projectRoot, taskId) {
6055
6762
  return true;
6056
6763
  }
6057
6764
  function workflowsRoot(projectRoot) {
6058
- return path21.join(projectRoot, ".mancode", "workflows");
6765
+ return path23.join(projectRoot, ".mancode", "workflows");
6059
6766
  }
6060
6767
  function workflowDir(projectRoot, taskId) {
6061
6768
  assertValidTaskId2(taskId);
6062
- return path21.join(workflowsRoot(projectRoot), taskId);
6769
+ return path23.join(workflowsRoot(projectRoot), taskId);
6063
6770
  }
6064
6771
  function metadataPath(projectRoot, taskId) {
6065
- return path21.join(workflowDir(projectRoot, taskId), METADATA_FILE);
6772
+ return path23.join(workflowDir(projectRoot, taskId), METADATA_FILE);
6066
6773
  }
6067
6774
  async function writeMetadata(dir, meta) {
6068
6775
  const content = `${JSON.stringify(meta, null, 2)}
6069
6776
  `;
6070
- await writeFile11(path21.join(dir, METADATA_FILE), content, "utf-8");
6777
+ await writeFile11(path23.join(dir, METADATA_FILE), content, "utf-8");
6071
6778
  }
6072
6779
  function parseWorkflowMeta(raw, taskId) {
6073
6780
  try {
@@ -6273,7 +6980,7 @@ async function propagateChildStatus(projectRoot, child) {
6273
6980
  });
6274
6981
  }
6275
6982
  }
6276
- function isNodeError6(err) {
6983
+ function isNodeError7(err) {
6277
6984
  return err instanceof Error && "code" in err;
6278
6985
  }
6279
6986
  function assertValidTaskId2(taskId) {
@@ -6284,19 +6991,19 @@ function assertValidTaskId2(taskId) {
6284
6991
 
6285
6992
  // src/commands/status.ts
6286
6993
  var HOOK_ESTIMATE_TIMEOUT_MS = 2e3;
6287
- var EXIT_OK6 = 0;
6288
- var EXIT_NOT_INITIALIZED4 = 1;
6289
- var EXIT_CORRUPT_STATE = 2;
6290
- async function status(rootDir = process7.cwd(), options = {}) {
6291
- const stateFile = path22.join(rootDir, ".mancode", "state.json");
6292
- if (!await pathExists9(stateFile)) {
6994
+ var EXIT_OK7 = 0;
6995
+ var EXIT_NOT_INITIALIZED5 = 1;
6996
+ var EXIT_CORRUPT_STATE2 = 2;
6997
+ async function status(rootDir = process10.cwd(), options = {}) {
6998
+ const stateFile = path24.join(rootDir, ".mancode", "state.json");
6999
+ if (!await pathExists10(stateFile)) {
6293
7000
  console.error("\u2717 mancode not initialized.");
6294
7001
  console.error(" Run `mancode init` to get started.");
6295
- return EXIT_NOT_INITIALIZED4;
7002
+ return EXIT_NOT_INITIALIZED5;
6296
7003
  }
6297
7004
  let state;
6298
7005
  try {
6299
- const raw = await fs7.readFile(stateFile, "utf-8");
7006
+ const raw = await fs9.readFile(stateFile, "utf-8");
6300
7007
  state = JSON.parse(raw);
6301
7008
  } catch (err) {
6302
7009
  if (err instanceof SyntaxError) {
@@ -6307,7 +7014,7 @@ async function status(rootDir = process7.cwd(), options = {}) {
6307
7014
  const msg = err instanceof Error ? err.message : String(err);
6308
7015
  console.error(`\u2717 Failed to read .mancode/state.json: ${msg}`);
6309
7016
  }
6310
- return EXIT_CORRUPT_STATE;
7017
+ return EXIT_CORRUPT_STATE2;
6311
7018
  }
6312
7019
  const [
6313
7020
  project,
@@ -6315,14 +7022,16 @@ async function status(rootDir = process7.cwd(), options = {}) {
6315
7022
  config,
6316
7023
  hookInjection,
6317
7024
  teamStatus,
6318
- currentWorkflow
7025
+ currentWorkflow,
7026
+ projectRefreshRecommended
6319
7027
  ] = await Promise.all([
6320
7028
  getProjectName(rootDir),
6321
7029
  checkHooks(rootDir),
6322
7030
  readConfig2(rootDir),
6323
7031
  estimateHookInjection(rootDir),
6324
7032
  detectTeamStatus(rootDir),
6325
- getCurrentWorkflow(rootDir, state.currentTask ?? null)
7033
+ getCurrentWorkflow(rootDir, state.currentTask ?? null),
7034
+ shouldRefreshProject(rootDir, state)
6326
7035
  ]);
6327
7036
  const effectiveTeam = getEffectiveTeamStatus(state, config, teamStatus);
6328
7037
  const result = {
@@ -6337,7 +7046,8 @@ async function status(rootDir = process7.cwd(), options = {}) {
6337
7046
  hookInjection,
6338
7047
  team: effectiveTeam,
6339
7048
  currentWorkflow,
6340
- platformStatus: {}
7049
+ platformStatus: {},
7050
+ projectRefreshRecommended
6341
7051
  };
6342
7052
  result.platformStatus = await checkPlatformStatus2(rootDir, result.platforms);
6343
7053
  if (options.json) {
@@ -6345,7 +7055,16 @@ async function status(rootDir = process7.cwd(), options = {}) {
6345
7055
  } else {
6346
7056
  printText(result);
6347
7057
  }
6348
- return EXIT_OK6;
7058
+ return EXIT_OK7;
7059
+ }
7060
+ async function shouldRefreshProject(rootDir, state) {
7061
+ if (state.projectMode !== "generic") return false;
7062
+ const hasGit = await pathExists10(path24.join(rootDir, ".git"));
7063
+ if (hasGit) return true;
7064
+ for (const manifest of PROJECT_MANIFESTS) {
7065
+ if (await pathExists10(path24.join(rootDir, manifest))) return true;
7066
+ }
7067
+ return false;
6349
7068
  }
6350
7069
  async function checkPlatformStatus2(rootDir, installedPlatforms) {
6351
7070
  const installed = new Set(installedPlatforms);
@@ -6387,19 +7106,19 @@ async function getCurrentWorkflow(rootDir, taskId) {
6387
7106
  }
6388
7107
  async function getProjectName(rootDir) {
6389
7108
  try {
6390
- const raw = await fs7.readFile(path22.join(rootDir, "package.json"), "utf-8");
7109
+ const raw = await fs9.readFile(path24.join(rootDir, "package.json"), "utf-8");
6391
7110
  const pkg = JSON.parse(raw);
6392
7111
  if (pkg.name && typeof pkg.name === "string") {
6393
7112
  return pkg.name;
6394
7113
  }
6395
7114
  } catch {
6396
7115
  }
6397
- return path22.basename(rootDir);
7116
+ return path24.basename(rootDir);
6398
7117
  }
6399
7118
  async function readConfig2(rootDir) {
6400
7119
  try {
6401
- const raw = await fs7.readFile(
6402
- path22.join(rootDir, ".mancode", "config.json"),
7120
+ const raw = await fs9.readFile(
7121
+ path24.join(rootDir, ".mancode", "config.json"),
6403
7122
  "utf-8"
6404
7123
  );
6405
7124
  return JSON.parse(raw);
@@ -6414,21 +7133,22 @@ function getInstalledPlatforms(config, fallback) {
6414
7133
  return fallback ? [fallback] : [];
6415
7134
  }
6416
7135
  function getEffectiveTeamStatus(state, config, detected) {
6417
- const configuredTeam = config.forceTeamMode === true ? true : state.teamModeAutoDetected ?? detected.isTeam;
6418
- const forced = config.forceTeamMode === true;
7136
+ const configuredTeam = config.forceTeamMode === true ? true : config.teamMode === "on" ? true : config.teamMode === "off" ? false : state.teamModeAutoDetected ?? detected.isTeam;
7137
+ const forced = config.teamMode === "on" || config.forceTeamMode === true;
7138
+ const autoDetected = config.forceTeamMode === true || config.teamMode === "on" || config.teamMode === "off" ? false : state.teamModeAutoDetected ?? detected.isTeam;
6419
7139
  return {
6420
7140
  ...detected,
6421
7141
  isTeam: configuredTeam,
6422
7142
  contributors: Math.max(detected.contributors, state.contributors ?? 0, 1),
6423
- autoDetected: forced ? false : state.teamModeAutoDetected ?? detected.isTeam,
7143
+ autoDetected,
6424
7144
  forced
6425
7145
  };
6426
7146
  }
6427
7147
  async function checkHooks(rootDir) {
6428
7148
  const [sessionStart, userPromptSubmit, registered] = await Promise.all([
6429
- pathExists9(path22.join(rootDir, ".mancode", "hooks", "session-start.sh")),
6430
- pathExists9(
6431
- path22.join(rootDir, ".mancode", "hooks", "user-prompt-submit.sh")
7149
+ pathExists10(path24.join(rootDir, ".mancode", "hooks", "session-start.mjs")),
7150
+ pathExists10(
7151
+ path24.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
6432
7152
  ),
6433
7153
  isRegistered(rootDir)
6434
7154
  ]);
@@ -6436,8 +7156,8 @@ async function checkHooks(rootDir) {
6436
7156
  }
6437
7157
  async function isRegistered(rootDir) {
6438
7158
  try {
6439
- const raw = await fs7.readFile(
6440
- path22.join(rootDir, ".claude", "settings.json"),
7159
+ const raw = await fs9.readFile(
7160
+ path24.join(rootDir, ".claude", "settings.json"),
6441
7161
  "utf-8"
6442
7162
  );
6443
7163
  const settings = JSON.parse(raw);
@@ -6447,32 +7167,32 @@ async function isRegistered(rootDir) {
6447
7167
  }
6448
7168
  }
6449
7169
  function hooksRegistered2(settings) {
6450
- if (!isRecord8(settings)) return false;
7170
+ if (!isRecord9(settings)) return false;
6451
7171
  const hooks = settings.hooks;
6452
- if (!isRecord8(hooks)) return false;
6453
- return hasHookCommand2(hooks.SessionStart, ".mancode/hooks/session-start.sh") && hasHookCommand2(
7172
+ if (!isRecord9(hooks)) return false;
7173
+ return hasHookCommand2(hooks.SessionStart, ".mancode/hooks/session-start.mjs") && hasHookCommand2(
6454
7174
  hooks.UserPromptSubmit,
6455
- ".mancode/hooks/user-prompt-submit.sh"
7175
+ ".mancode/hooks/user-prompt-submit.mjs"
6456
7176
  );
6457
7177
  }
6458
7178
  function hasHookCommand2(value, needle) {
6459
7179
  if (!Array.isArray(value)) return false;
6460
7180
  return value.some((group) => {
6461
- if (!isRecord8(group) || !Array.isArray(group.hooks)) return false;
7181
+ if (!isRecord9(group) || !Array.isArray(group.hooks)) return false;
6462
7182
  return group.hooks.some((hook) => {
6463
- if (!isRecord8(hook) || typeof hook.command !== "string") return false;
7183
+ if (!isRecord9(hook) || typeof hook.command !== "string") return false;
6464
7184
  return hook.command.includes(needle);
6465
7185
  });
6466
7186
  });
6467
7187
  }
6468
7188
  async function estimateHookInjection(rootDir) {
6469
- const hookPath = path22.join(
7189
+ const hookPath = path24.join(
6470
7190
  rootDir,
6471
7191
  ".mancode",
6472
7192
  "hooks",
6473
- "user-prompt-submit.sh"
7193
+ "user-prompt-submit.mjs"
6474
7194
  );
6475
- if (!await pathExists9(hookPath)) {
7195
+ if (!await pathExists10(hookPath)) {
6476
7196
  return { tokens: 0, cap: 800 };
6477
7197
  }
6478
7198
  try {
@@ -6487,7 +7207,7 @@ async function estimateHookInjection(rootDir) {
6487
7207
  }
6488
7208
  function runHookEstimate(rootDir, hookPath) {
6489
7209
  return new Promise((resolve, reject) => {
6490
- const child = spawn("bash", [hookPath], {
7210
+ const child = spawn(process10.execPath, [hookPath], {
6491
7211
  cwd: rootDir,
6492
7212
  stdio: ["pipe", "pipe", "ignore"]
6493
7213
  });
@@ -6502,15 +7222,15 @@ function runHookEstimate(rootDir, hookPath) {
6502
7222
  child.kill();
6503
7223
  finish(() => reject(new Error("hook estimate timed out")));
6504
7224
  }, HOOK_ESTIMATE_TIMEOUT_MS);
6505
- let stdout = "";
7225
+ let stdout2 = "";
6506
7226
  child.stdout.setEncoding("utf-8");
6507
7227
  child.stdout.on("data", (chunk) => {
6508
- stdout += chunk;
7228
+ stdout2 += chunk;
6509
7229
  });
6510
7230
  child.on("error", (err) => finish(() => reject(err)));
6511
7231
  child.on("close", (code) => {
6512
7232
  finish(() => {
6513
- if (code === 0) resolve(stdout);
7233
+ if (code === 0) resolve(stdout2);
6514
7234
  else reject(new Error(`hook exited with ${code}`));
6515
7235
  });
6516
7236
  });
@@ -6529,6 +7249,11 @@ function printText(r) {
6529
7249
  console.log(`Style: ${r.uiLibrary}`);
6530
7250
  console.log(`Initialized: ${r.initializedAt}`);
6531
7251
  console.log(`Team: ${formatTeamStatus(r.team)}`);
7252
+ if (r.projectRefreshRecommended) {
7253
+ console.log(
7254
+ "Project: new Git or project files detected; run `mancode refresh-project`."
7255
+ );
7256
+ }
6532
7257
  if (r.currentWorkflow) {
6533
7258
  const stepMax = workflowStepMax(r.currentWorkflow.mode);
6534
7259
  console.log(
@@ -6571,9 +7296,9 @@ function printText(r) {
6571
7296
  console.log("");
6572
7297
  if (r.platforms.includes("claude-code")) {
6573
7298
  console.log("Hooks:");
6574
- console.log(` ${r.hooks.sessionStart ? "\u2713" : "\u2717"} session-start.sh`);
7299
+ console.log(` ${r.hooks.sessionStart ? "\u2713" : "\u2717"} session-start.mjs`);
6575
7300
  console.log(
6576
- ` ${r.hooks.userPromptSubmit ? "\u2713" : "\u2717"} user-prompt-submit.sh`
7301
+ ` ${r.hooks.userPromptSubmit ? "\u2713" : "\u2717"} user-prompt-submit.mjs`
6577
7302
  );
6578
7303
  console.log(
6579
7304
  ` ${r.hooks.registered ? "\u2713" : "\u2717"} registered in .claude/settings.json`
@@ -6592,12 +7317,12 @@ function formatTeamStatus(team) {
6592
7317
  function workflowStepMax(mode) {
6593
7318
  return maxWorkflowStep(mode);
6594
7319
  }
6595
- function isRecord8(value) {
7320
+ function isRecord9(value) {
6596
7321
  return typeof value === "object" && value !== null;
6597
7322
  }
6598
- async function pathExists9(p) {
7323
+ async function pathExists10(p) {
6599
7324
  try {
6600
- await fs7.access(p);
7325
+ await fs9.access(p);
6601
7326
  return true;
6602
7327
  } catch {
6603
7328
  return false;
@@ -6605,18 +7330,18 @@ async function pathExists9(p) {
6605
7330
  }
6606
7331
 
6607
7332
  // src/commands/uninstall.ts
6608
- import { access as access4, readFile as readFile11, rm as rm5, writeFile as writeFile12 } from "fs/promises";
6609
- import path23 from "path";
6610
- import process8 from "process";
6611
- var EXIT_OK7 = 0;
6612
- var EXIT_NOT_INITIALIZED5 = 1;
7333
+ import { access as access5, readFile as readFile11, rm as rm5, writeFile as writeFile12 } from "fs/promises";
7334
+ import path25 from "path";
7335
+ import process11 from "process";
7336
+ var EXIT_OK8 = 0;
7337
+ var EXIT_NOT_INITIALIZED6 = 1;
6613
7338
  var EXIT_UNSUPPORTED_PLATFORM2 = 2;
6614
- async function uninstall(rootDir = process8.cwd(), platform, options = {}) {
6615
- const stateFile = path23.join(rootDir, ".mancode", "state.json");
6616
- if (!await pathExists10(stateFile)) {
7339
+ async function uninstall(rootDir = process11.cwd(), platform, options = {}) {
7340
+ const stateFile = path25.join(rootDir, ".mancode", "state.json");
7341
+ if (!await pathExists11(stateFile)) {
6617
7342
  console.error("\u2717 mancode not initialized.");
6618
7343
  console.error(" Run `mancode init` first.");
6619
- return EXIT_NOT_INITIALIZED5;
7344
+ return EXIT_NOT_INITIALIZED6;
6620
7345
  }
6621
7346
  const removeAll = !platform || options.all;
6622
7347
  if (platform) {
@@ -6641,7 +7366,7 @@ async function uninstall(rootDir = process8.cwd(), platform, options = {}) {
6641
7366
  await uninstallPlatform(rootDir, platform ?? "claude-code");
6642
7367
  }
6643
7368
  console.log("\u2713 Uninstall complete.");
6644
- return EXIT_OK7;
7369
+ return EXIT_OK8;
6645
7370
  }
6646
7371
  async function uninstallPlatform(rootDir, platform) {
6647
7372
  console.log(`\u2713 Removing ${formatPlatformName(platform)} adapter...`);
@@ -6664,7 +7389,7 @@ async function uninstallAll(rootDir) {
6664
7389
  await uninstallPlatform(rootDir, p);
6665
7390
  }
6666
7391
  console.log("\u2713 Removing .mancode/ directory...");
6667
- await rm5(path23.join(rootDir, ".mancode"), {
7392
+ await rm5(path25.join(rootDir, ".mancode"), {
6668
7393
  recursive: true,
6669
7394
  force: true
6670
7395
  });
@@ -6674,7 +7399,7 @@ async function uninstallClaudeCode(rootDir) {
6674
7399
  await cleanClaudeSettings(rootDir);
6675
7400
  }
6676
7401
  async function cleanClaudeSettings(rootDir) {
6677
- const settingsPath = path23.join(rootDir, ".claude", "settings.json");
7402
+ const settingsPath = path25.join(rootDir, ".claude", "settings.json");
6678
7403
  let content;
6679
7404
  try {
6680
7405
  content = await readFile11(settingsPath, "utf-8");
@@ -6688,14 +7413,14 @@ async function cleanClaudeSettings(rootDir) {
6688
7413
  return;
6689
7414
  }
6690
7415
  const cleanedHooks = {};
6691
- if (isRecord9(settings.hooks)) {
7416
+ if (isRecord10(settings.hooks)) {
6692
7417
  for (const [event, value] of Object.entries(settings.hooks)) {
6693
7418
  const cleaned = cleanClaudeHookValue(value);
6694
7419
  if (cleaned !== void 0) cleanedHooks[event] = cleaned;
6695
7420
  }
6696
7421
  settings.hooks = Object.keys(cleanedHooks).length > 0 ? cleanedHooks : void 0;
6697
7422
  }
6698
- if (isRecord9(settings.skills)) {
7423
+ if (isRecord10(settings.skills)) {
6699
7424
  const retainedSkills = Object.fromEntries(
6700
7425
  Object.entries(settings.skills).filter(
6701
7426
  ([name, value]) => LEGACY_CLAUDE_SKILL_SETTINGS[name] !== value
@@ -6715,7 +7440,7 @@ function cleanClaudeHookValue(value) {
6715
7440
  const entries2 = value.flatMap(cleanClaudeHookEntry);
6716
7441
  return entries2.length > 0 ? entries2 : void 0;
6717
7442
  }
6718
- if (!isRecord9(value)) return value;
7443
+ if (!isRecord10(value)) return value;
6719
7444
  if (Array.isArray(value.hooks) || typeof value.command === "string") {
6720
7445
  const entries2 = cleanClaudeHookEntry(value);
6721
7446
  return entries2.length > 0 ? entries2 : void 0;
@@ -6729,7 +7454,7 @@ function cleanClaudeHookValue(value) {
6729
7454
  }
6730
7455
  function cleanClaudeHookEntry(entry) {
6731
7456
  if (Array.isArray(entry)) return entry.flatMap(cleanClaudeHookEntry);
6732
- if (!isRecord9(entry)) return [entry];
7457
+ if (!isRecord10(entry)) return [entry];
6733
7458
  if (Array.isArray(entry.hooks)) {
6734
7459
  const hooks = entry.hooks.filter((hook) => !isMancodeHookEntry(hook));
6735
7460
  return hooks.length > 0 ? [{ ...entry, hooks }] : [];
@@ -6737,11 +7462,11 @@ function cleanClaudeHookEntry(entry) {
6737
7462
  return isMancodeHookEntry(entry) ? [] : [entry];
6738
7463
  }
6739
7464
  function isMancodeHookEntry(value) {
6740
- return isRecord9(value) && typeof value.command === "string" && isGeneratedMancodeHookCommand(value.command);
7465
+ return isRecord10(value) && typeof value.command === "string" && isGeneratedMancodeHookCommand(value.command);
6741
7466
  }
6742
7467
  function containsLegacyMancodeHookPath2(value) {
6743
7468
  if (Array.isArray(value)) return value.some(containsLegacyMancodeHookPath2);
6744
- if (!isRecord9(value)) return false;
7469
+ if (!isRecord10(value)) return false;
6745
7470
  if (typeof value.command === "string" && value.command.includes(".mancode/hooks/")) {
6746
7471
  return true;
6747
7472
  }
@@ -6752,7 +7477,7 @@ async function uninstallCursor(rootDir) {
6752
7477
  await removeCursorCommands(rootDir);
6753
7478
  }
6754
7479
  async function uninstallCodex(rootDir) {
6755
- const agentsPath = path23.join(rootDir, "AGENTS.md");
7480
+ const agentsPath = path25.join(rootDir, "AGENTS.md");
6756
7481
  try {
6757
7482
  const content = await readFile11(agentsPath, "utf-8");
6758
7483
  const cleaned = removeManagedBlock(content);
@@ -6767,7 +7492,7 @@ async function uninstallCodex(rootDir) {
6767
7492
  await removeCodexSkills(rootDir);
6768
7493
  }
6769
7494
  async function uninstallCopilot(rootDir) {
6770
- const instructionsPath = path23.join(
7495
+ const instructionsPath = path25.join(
6771
7496
  rootDir,
6772
7497
  ".github",
6773
7498
  "copilot-instructions.md"
@@ -6786,7 +7511,7 @@ async function uninstallCopilot(rootDir) {
6786
7511
  await removeCopilotPrompts(rootDir);
6787
7512
  }
6788
7513
  async function uninstallZcode(rootDir) {
6789
- const agentsPath = path23.join(rootDir, "AGENTS.md");
7514
+ const agentsPath = path25.join(rootDir, "AGENTS.md");
6790
7515
  try {
6791
7516
  const content = await readFile11(agentsPath, "utf-8");
6792
7517
  const cleaned = removeManagedBlock(
@@ -6805,13 +7530,13 @@ async function uninstallZcode(rootDir) {
6805
7530
  await removeZcodeSkills(rootDir);
6806
7531
  }
6807
7532
  async function removeFromConfig(rootDir, platform) {
6808
- const configPath = path23.join(rootDir, ".mancode", "config.json");
7533
+ const configPath = path25.join(rootDir, ".mancode", "config.json");
6809
7534
  try {
6810
7535
  const raw = await readFile11(configPath, "utf-8");
6811
7536
  const config = JSON.parse(raw);
6812
7537
  if (Array.isArray(config.platforms)) {
6813
7538
  config.platforms = config.platforms.filter((p) => p !== platform);
6814
- if (isRecord9(config.platformOptions)) {
7539
+ if (isRecord10(config.platformOptions)) {
6815
7540
  const platformOptions = Object.fromEntries(
6816
7541
  Object.entries(config.platformOptions).filter(
6817
7542
  ([name]) => name !== platform
@@ -6829,12 +7554,12 @@ async function removeFromConfig(rootDir, platform) {
6829
7554
  } catch {
6830
7555
  }
6831
7556
  }
6832
- function isRecord9(value) {
7557
+ function isRecord10(value) {
6833
7558
  return typeof value === "object" && value !== null && !Array.isArray(value);
6834
7559
  }
6835
- async function pathExists10(p) {
7560
+ async function pathExists11(p) {
6836
7561
  try {
6837
- await access4(p);
7562
+ await access5(p);
6838
7563
  return true;
6839
7564
  } catch {
6840
7565
  return false;
@@ -6842,30 +7567,30 @@ async function pathExists10(p) {
6842
7567
  }
6843
7568
 
6844
7569
  // src/commands/version.ts
6845
- import process9 from "process";
7570
+ import process12 from "process";
6846
7571
  function version() {
6847
- const nodeVersion = process9.version;
6848
- const platform = `${process9.platform}/${process9.arch}`;
7572
+ const nodeVersion = process12.version;
7573
+ const platform = `${process12.platform}/${process12.arch}`;
6849
7574
  console.log(`mancode/${VERSION}`);
6850
7575
  console.log(`node/${nodeVersion.replace("v", "")}`);
6851
7576
  console.log(platform);
6852
7577
  }
6853
7578
 
6854
7579
  // src/commands/workflow.ts
6855
- import { access as access5 } from "fs/promises";
6856
- import path24 from "path";
6857
- var EXIT_OK8 = 0;
6858
- var EXIT_NOT_INITIALIZED6 = 1;
7580
+ import { access as access6 } from "fs/promises";
7581
+ import path26 from "path";
7582
+ var EXIT_OK9 = 0;
7583
+ var EXIT_NOT_INITIALIZED7 = 1;
6859
7584
  var EXIT_INVALID_ARG2 = 2;
6860
7585
  async function workflow(rootDir, subcommand, args = [], options = {}) {
6861
- if (!await pathExists11(path24.join(rootDir, ".mancode", "state.json"))) {
7586
+ if (!await pathExists12(path26.join(rootDir, ".mancode", "state.json"))) {
6862
7587
  if (options.json) {
6863
7588
  console.log(JSON.stringify({ error: "not initialized" }, null, 2));
6864
7589
  } else {
6865
7590
  console.error("\u2717 mancode not initialized.");
6866
7591
  console.error(" Run `mancode init` to get started.");
6867
7592
  }
6868
- return EXIT_NOT_INITIALIZED6;
7593
+ return EXIT_NOT_INITIALIZED7;
6869
7594
  }
6870
7595
  switch (subcommand) {
6871
7596
  case "create":
@@ -6914,7 +7639,7 @@ async function workflowReview(rootDir, args, options) {
6914
7639
  if (!ledger)
6915
7640
  return invalidArg(options, `review not initialized: ${taskId}`);
6916
7641
  outputReviewLedger(ledger, options);
6917
- return EXIT_OK8;
7642
+ return EXIT_OK9;
6918
7643
  }
6919
7644
  if (meta.status !== "in_progress" || meta.currentStep < 6) {
6920
7645
  return invalidArg(
@@ -6971,7 +7696,7 @@ async function workflowReview(rootDir, args, options) {
6971
7696
  );
6972
7697
  }
6973
7698
  outputReviewLedger(ledger, options);
6974
- return EXIT_OK8;
7699
+ return EXIT_OK9;
6975
7700
  } catch (error) {
6976
7701
  return invalidArg(
6977
7702
  options,
@@ -7027,7 +7752,7 @@ async function workflowCreate(rootDir, args, options) {
7027
7752
  error instanceof Error ? error.message : "unable to create workflow"
7028
7753
  );
7029
7754
  }
7030
- return EXIT_OK8;
7755
+ return EXIT_OK9;
7031
7756
  }
7032
7757
  async function workflowUpdate(rootDir, taskId, options) {
7033
7758
  if (!taskId) {
@@ -7125,18 +7850,18 @@ async function workflowUpdate(rootDir, taskId, options) {
7125
7850
  } else {
7126
7851
  console.log(`Updated workflow: ${taskId}`);
7127
7852
  }
7128
- return EXIT_OK8;
7853
+ return EXIT_OK9;
7129
7854
  }
7130
7855
  async function workflowList(rootDir, options) {
7131
7856
  const workflows = await listWorkflows(rootDir);
7132
7857
  const views = attachActiveChildren(workflows);
7133
7858
  if (options.json) {
7134
7859
  console.log(JSON.stringify(views, null, 2));
7135
- return EXIT_OK8;
7860
+ return EXIT_OK9;
7136
7861
  }
7137
7862
  if (workflows.length === 0) {
7138
7863
  console.log("No workflows.");
7139
- return EXIT_OK8;
7864
+ return EXIT_OK9;
7140
7865
  }
7141
7866
  const inProgress = workflows.filter((w) => w.status === "in_progress").length;
7142
7867
  console.log(
@@ -7146,7 +7871,7 @@ async function workflowList(rootDir, options) {
7146
7871
  for (const meta of views) {
7147
7872
  console.log(formatWorkflowRow(meta));
7148
7873
  }
7149
- return EXIT_OK8;
7874
+ return EXIT_OK9;
7150
7875
  }
7151
7876
  async function workflowShow(rootDir, taskId, options) {
7152
7877
  if (!taskId) {
@@ -7183,7 +7908,7 @@ async function workflowShow(rootDir, taskId, options) {
7183
7908
  2
7184
7909
  )
7185
7910
  );
7186
- return EXIT_OK8;
7911
+ return EXIT_OK9;
7187
7912
  }
7188
7913
  console.log(`Workflow: ${meta.taskId}`);
7189
7914
  console.log(`Task: ${meta.task}`);
@@ -7210,7 +7935,7 @@ async function workflowShow(rootDir, taskId, options) {
7210
7935
  }
7211
7936
  }
7212
7937
  }
7213
- return EXIT_OK8;
7938
+ return EXIT_OK9;
7214
7939
  }
7215
7940
  async function workflowClean(rootDir, options) {
7216
7941
  const workflows = await listWorkflows(rootDir);
@@ -7278,7 +8003,7 @@ async function workflowClean(rootDir, options) {
7278
8003
  } else {
7279
8004
  console.log(`Removed ${removed.length} workflow(s).`);
7280
8005
  }
7281
- return EXIT_OK8;
8006
+ return EXIT_OK9;
7282
8007
  }
7283
8008
  function formatWorkflowRow(meta) {
7284
8009
  const stepMax = maxWorkflowStep(meta.mode);
@@ -7337,9 +8062,9 @@ function ago(iso) {
7337
8062
  if (hours < 24) return `${hours}h ago`;
7338
8063
  return `${Math.floor(hours / 24)}d ago`;
7339
8064
  }
7340
- async function pathExists11(p) {
8065
+ async function pathExists12(p) {
7341
8066
  try {
7342
- await access5(p);
8067
+ await access6(p);
7343
8068
  return true;
7344
8069
  } catch {
7345
8070
  return false;
@@ -7350,8 +8075,11 @@ async function pathExists11(p) {
7350
8075
  program.name("mancode").description(
7351
8076
  "AI coding agent harness. Modes: solo, man, mamba, manteam, manps."
7352
8077
  ).version(VERSION);
7353
- program.command("init").description("Initialize mancode in the current project").option("--force", "Reinstall even if already initialized").option("--yes", "Skip all confirmations (CI mode)").option("--team", "Force enable team mode (MVP-2)").option("--no-team", "Force disable team mode (MVP-2)").option("--style <name>", "Specify aesthetic style (MVP-2)").option("--platform <platform>", "Initial adapter platform (MVP-3)").action(async (options) => {
7354
- const code = await init(process.cwd(), options);
8078
+ program.command("init").description("Initialize mancode in the current project").option("--force", "Reinstall even if already initialized").option("--yes", "Skip all confirmations (CI mode)").option("--team", "Force enable team mode (MVP-2)").option("--no-team", "Force disable team mode (MVP-2)").option("--style <name>", "Specify aesthetic style (MVP-2)").option("--platform <platforms>", "Adapters: comma-separated names or all").option("--empty", "Initialize a safe empty directory as a generic project").option("--lang <locale>", "Initialization language: zh-CN or en").action(async (options) => {
8079
+ const code = await init(process.cwd(), {
8080
+ ...options,
8081
+ interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY)
8082
+ });
7355
8083
  process.exitCode = code;
7356
8084
  });
7357
8085
  program.command("install [platform]").description(
@@ -7397,6 +8125,12 @@ program.command("refresh-style").description("Refresh project profile and rescan
7397
8125
  const code = await refreshStyle(process.cwd());
7398
8126
  process.exitCode = code;
7399
8127
  });
8128
+ program.command("refresh-project").description(
8129
+ "Refresh detected project facts after adding Git or project files"
8130
+ ).action(async () => {
8131
+ const code = await refreshProject(process.cwd());
8132
+ process.exitCode = code;
8133
+ });
7400
8134
  program.command("version").description("Show version, node version, and platform").action(() => {
7401
8135
  version();
7402
8136
  });