mancode 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -19
- package/README.zh-CN.md +19 -18
- package/dist/cli.js +852 -761
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
import { program } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/commands/init.ts
|
|
10
|
-
import { promises as
|
|
10
|
+
import { promises as fs4 } from "fs";
|
|
11
11
|
import os from "os";
|
|
12
|
-
import
|
|
12
|
+
import path15 from "path";
|
|
13
13
|
import process5 from "process";
|
|
14
14
|
|
|
15
15
|
// src/installers/claude-code.ts
|
|
@@ -308,8 +308,8 @@ var HEAD_COACH_AGENT = {
|
|
|
308
308
|
- \u5C06\u5173\u952E\u51B3\u7B56\u4EA4\u7ED9\u8C03\u7528\u65B9 appendTeamDecision\uFF0C\u5E76\u66F4\u65B0 Active Plans\u3002
|
|
309
309
|
- \u5408\u5E76 worktree \u524D\u53D6\u5F97\u7528\u6237\u786E\u8BA4\uFF1B\u6E05\u7406\u4E34\u65F6\u6587\u4EF6\u3002
|
|
310
310
|
|
|
311
|
-
**/
|
|
312
|
-
- \u5F53\u771F\u5B9E\u6D4F\u89C8\u5668\u9A8C\u8BC1\u3001\u590D\u6742\u590D\u73B0\u6216\u56DE\u5F52\u9700\u8981\u4E13\u95E8\u8BCA\u65AD\u65F6\uFF0C\u901A\u8FC7 \`mancode workflow create
|
|
311
|
+
**/manba \u4EA4\u63A5\u9636\u6BB5**\uFF1A
|
|
312
|
+
- \u5F53\u771F\u5B9E\u6D4F\u89C8\u5668\u9A8C\u8BC1\u3001\u590D\u6742\u590D\u73B0\u6216\u56DE\u5F52\u9700\u8981\u4E13\u95E8\u8BCA\u65AD\u65F6\uFF0C\u901A\u8FC7 \`mancode workflow create manba ... --parent-task <taskId>\` \u521B\u5EFA\u5B50 workflow\uFF1B\u6240\u6709 metadata \u53D8\u5316\u90FD\u8D70 workflow CLI\u3002
|
|
313
313
|
- \u5B50\u4EFB\u52A1 fixed/verified/no_repro \u540E\u56DE\u5230\u7236 /man Step 6\uFF1B\u82E5\u7236\u66FE\u56E0\u8BE5\u5B50\u4EFB\u52A1 blocked\uFF0C\u5148\u7528 workflow CLI \u6062\u590D\u4E3A in_progress\u3002blocked \u6216 manual_test_required \u65F6\u8BA9\u7236 workflow \u540C\u6B65 blocked\uFF0C\u540E\u8005\u4E0D\u5F97\u81EA\u52A8\u6062\u590D\u3002
|
|
314
314
|
|
|
315
315
|
\u4E3B\u89C1\u5F3A\uFF0C\u5FC5\u8981\u65F6\u624D\u95EE\u7528\u6237\u3002\u5F00\u5E72\u3002`
|
|
@@ -531,7 +531,8 @@ if (!state) {
|
|
|
531
531
|
process.exit(0);
|
|
532
532
|
}
|
|
533
533
|
|
|
534
|
-
const
|
|
534
|
+
const storedMode = text(state.currentMode) || 'solo';
|
|
535
|
+
const mode = storedMode === 'mamba' ? 'manba' : storedMode;
|
|
535
536
|
const techStack = sanitize(state.techStack);
|
|
536
537
|
const uiLibrary = sanitize(state.uiLibrary);
|
|
537
538
|
const output = [
|
|
@@ -848,7 +849,7 @@ var MAN_SKILL = {
|
|
|
848
849
|
|
|
849
850
|
### Step 6: \u81EA\u6D4B\u3001\u8BCA\u65AD\u4E0E\u56DE\u5F52
|
|
850
851
|
|
|
851
|
-
\u8FD0\u884C\u5B9E\u9645 build/lint/typecheck/test \u548C smoke test\u3002\u76F8\u540C\u4EE3\u7801\u3001\u73AF\u5883\u3001\u547D\u4EE4\u4E0B\u76F8\u540C\u9519\u8BEF\u7B7E\u540D\u5931\u8D25\u4E24\u6B21\uFF0C\u505C\u6B62\u76F2\u8BD5\u5E76\u8BCA\u65AD\u6839\u56E0\u3002\u9700\u8981\u771F\u5B9E\u6D4F\u89C8\u5668\u3001\u590D\u6742\u590D\u73B0\u6216\u56DE\u5F52\u65F6\uFF0C\u7528 \`mancode workflow create
|
|
852
|
+
\u8FD0\u884C\u5B9E\u9645 build/lint/typecheck/test \u548C smoke test\u3002\u76F8\u540C\u4EE3\u7801\u3001\u73AF\u5883\u3001\u547D\u4EE4\u4E0B\u76F8\u540C\u9519\u8BEF\u7B7E\u540D\u5931\u8D25\u4E24\u6B21\uFF0C\u505C\u6B62\u76F2\u8BD5\u5E76\u8BCA\u65AD\u6839\u56E0\u3002\u9700\u8981\u771F\u5B9E\u6D4F\u89C8\u5668\u3001\u590D\u6742\u590D\u73B0\u6216\u56DE\u5F52\u65F6\uFF0C\u7528 \`mancode workflow create manba "<\u95EE\u9898>" --parent-task <taskId> --json\` \u521B\u5EFA\u5B50 workflow\uFF1B\u7236\u4EFB\u52A1\u4FDD\u6301 Step 6\u3002\u5B50\u4EFB\u52A1 fixed/verified/no_repro \u540E\u6062\u590D\u672C\u4EFB\u52A1\uFF1B\u82E5\u7236\u66FE\u56E0\u8BE5\u5B50\u4EFB\u52A1 blocked\uFF0C\u5148\u901A\u8FC7 \`workflow update --status in_progress\` \u6062\u590D\uFF0C\u518D\u66F4\u65B0\u81F3 Step 7\u3002blocked \u6216 manual_test_required \u4F1A\u7531 CLI \u81EA\u52A8\u963B\u585E\u7236\u4EFB\u52A1\uFF0C\u4E0D\u5F97\u624B\u6539\u7236 metadata\uFF0C\u4E5F\u4E0D\u5F97\u81EA\u52A8\u8D8A\u8FC7\u4EBA\u5DE5\u9A8C\u8BC1\u8981\u6C42\u3002
|
|
852
853
|
|
|
853
854
|
\u9A8C\u8BC1\u540E\u57FA\u4E8E**\u5B9E\u9645 diff**\u5199 \`review-scope.md\`\uFF1Abase\u3001\u6539\u52A8\u6587\u4EF6\u3001\u9700\u6C42\u3001\u5DF2\u8DD1\u9A8C\u8BC1\u3001\u786C\u98CE\u9669\u548C\u5BA1\u67E5\u6DF1\u5EA6\u3002\u9274\u6743\u3001\u652F\u4ED8\u3001\u654F\u611F\u6570\u636E\u3001\u8FC1\u79FB/\u5220\u9664\u3001\u516C\u5F00 API\u3001\u672A\u53EF\u4FE1\u8F93\u5165\u3001\u5E76\u53D1\u3001\u8DE8\u670D\u52A1\u6216\u57FA\u7840\u8BBE\u65BD\u547D\u4E2D\u4EFB\u4E00\u9879\u65F6\u7528\u5B8C\u6574\u5BA1\u67E5 \`full\`\uFF1B\u5426\u5219\u7528\u5B9A\u5411\u5BA1\u67E5 \`targeted\`\u3002\u8FD0\u884C \`mancode workflow review <taskId> init --review-depth targeted --review-domain quality\` \u6216 \`--review-depth full\`\u3002\u7528\u6237\u660E\u786E\u8DF3\u8FC7\u5BA1\u67E5\u65F6\u624D\u628A \`review\` \u5199\u5165\u7D2F\u8BA1 skippedSteps\uFF0C\u5E76\u8BB0\u5F55\u6B8B\u4F59\u98CE\u9669\u3002
|
|
854
855
|
|
|
@@ -877,15 +878,15 @@ ${CORE_CODING_PRINCIPLES}
|
|
|
877
878
|
|
|
878
879
|
// src/templates/skills/mamba.ts
|
|
879
880
|
var MAMBA_SKILL = {
|
|
880
|
-
name: "
|
|
881
|
+
name: "manba",
|
|
881
882
|
description: "Diagnose bugs and validate real user flows. Reproduce, find the root cause, run targeted regression checks, and use browser automation when the platform supports it.",
|
|
882
|
-
body: `# mancode \xB7 /
|
|
883
|
+
body: `# mancode \xB7 /manba (Diagnosis & Real Validation)
|
|
883
884
|
|
|
884
|
-
\u7528\u6237\u7528 \`/
|
|
885
|
+
\u7528\u6237\u7528 \`/manba <\u95EE\u9898\u6216\u6A21\u5757>\` \u89E6\u53D1\u4F60\u3002\u76EE\u6807\u662F\u7528\u8BC1\u636E\u56DE\u7B54\uFF1A\u80FD\u5426\u590D\u73B0\u3001\u6839\u56E0\u5728\u54EA\u91CC\u3001\u4FEE\u590D\u662F\u5426\u6709\u6548\u3001\u5173\u952E\u8DEF\u5F84\u662F\u5426\u56DE\u5F52\u3002\u5BF9\u5916\u6A21\u5F0F\u540D\u662F manba\uFF1B\u6301\u4E45\u5316 workflow/state \u4E3A\u517C\u5BB9\u5386\u53F2\u6570\u636E\u7EE7\u7EED\u4F7F\u7528\u5185\u90E8\u503C mamba\u3002
|
|
885
886
|
|
|
886
887
|
## 1. \u5EFA\u7ACB\u4EFB\u52A1
|
|
887
888
|
|
|
888
|
-
\u5148\u8BFB state\u3002\u82E5 \`currentTask\` \u6307\u5411 unrelated active workflow\uFF0C\u505C\u6B62\u5E76\u8BA9\u7528\u6237\u9009\u62E9\u6062\u590D\u8BE5\u6D41\u7A0B\u6216\u5148\u7528 /mansolo \u653E\u5F03\uFF1B\u4E0D\u5F97\u8986\u76D6\u65E7\u6307\u9488\u3002\u72EC\u7ACB\u4EFB\u52A1\u8FD0\u884C \`mancode workflow create
|
|
889
|
+
\u5148\u8BFB state\u3002\u82E5 \`currentTask\` \u6307\u5411 unrelated active workflow\uFF0C\u505C\u6B62\u5E76\u8BA9\u7528\u6237\u9009\u62E9\u6062\u590D\u8BE5\u6D41\u7A0B\u6216\u5148\u7528 /mansolo \u653E\u5F03\uFF1B\u4E0D\u5F97\u8986\u76D6\u65E7\u6307\u9488\u3002\u72EC\u7ACB\u4EFB\u52A1\u8FD0\u884C \`mancode workflow create manba "<\u95EE\u9898\u6216\u6A21\u5757>" --json\`\uFF1B\u7531 /man \u6216 /manteam Step 6 \u8F6C\u5165\u65F6\u8FD0\u884C \`mancode workflow create manba "<\u95EE\u9898\u6216\u6A21\u5757>" --parent-task <\u7236taskId> --json\`\u3002\u8BFB\u53D6\u8FD4\u56DE taskId \u540E\uFF0C\u5C06 state \u7684 currentMode/currentTask/currentWorkflowMode \u6307\u5411\u5185\u90E8\u517C\u5BB9\u503C mamba\u3002\u4E0D\u5F97\u76F4\u63A5\u521B\u5EFA\u6216\u6539\u5199 metadata.json\u3002
|
|
889
890
|
|
|
890
891
|
\u8BB0\u5F55\u73B0\u8C61\u3001\u73AF\u5883\u3001\u590D\u73B0\u6761\u4EF6\u3001\u9884\u671F\u4E0E\u5B9E\u9645\u7ED3\u679C\u3001\u4E0D\u53EF\u89E6\u78B0\u7684\u6570\u636E\u3002\u5148\u68C0\u67E5\u5DF2\u6709\u6D4B\u8BD5\u3001\u8DEF\u7531\u3001\u6A21\u5757\u5165\u53E3\u548C README\uFF1B\u4ECD\u65E0\u6CD5\u786E\u5B9A\u6700\u5C0F\u8DEF\u5F84\u65F6\u53EA\u95EE\u5173\u952E\u8DEF\u5F84\u95EE\u9898\u3002\u5B8C\u6210\u540E\u7528 CLI \u66F4\u65B0\u81F3 Step 2\u3002
|
|
891
892
|
|
|
@@ -900,7 +901,7 @@ var MAMBA_SKILL = {
|
|
|
900
901
|
|
|
901
902
|
\u4EC5\u5F53 profile \u786E\u8BA4 Web UI \u4E14\u6D4F\u89C8\u5668\u80FD\u529B\u53EF\u7528\u65F6\uFF0C\u68C0\u67E5 \`npx\` \u5E76\u4F7F\u7528\u9879\u76EE\u7684 Playwright CLI wrapper\u3002\u5176\u4ED6\u9879\u76EE\u7C7B\u578B\u4F7F\u7528\u5DF2\u68C0\u6D4B\u5230\u7684 API\u3001\u6A21\u62DF\u5668\u3001\u8BBE\u5907\u3001\u547D\u4EE4\u6216\u5951\u7EA6\u9A8C\u8BC1\u65B9\u5F0F\u3002\u6D4F\u89C8\u5668\u9A8C\u8BC1\u65F6\u5148 snapshot \u518D\u4F7F\u7528\u5143\u7D20\u5F15\u7528\uFF1B\u5BFC\u822A\u3001\u5F39\u7A97\u6216 UI \u5927\u53D8\u5316\u540E\u91CD\u65B0 snapshot\u3002\u622A\u56FE\u3001trace \u548C\u6D4F\u89C8\u5668\u65E5\u5FD7\u5199\u5165 \`.mancode/workflows/<taskId>/artifacts/playwright/\`\uFF0C\u62A5\u544A\u53EA\u5199\u76F8\u5BF9\u8DEF\u5F84\uFF0C\u5E76\u8131\u654F Token\u3001Cookie\u3001\u4E2A\u4EBA\u6216\u4E1A\u52A1\u654F\u611F\u6570\u636E\u3002
|
|
902
903
|
|
|
903
|
-
\u73AF\u5883\u5C31\u7EEA\u540E\u7528 CLI \u66F4\u65B0\u81F3 Step 3\uFF1B\u7F3A\u5C11\u73AF\u5883\u3001\u6743\u9650\u6216\u6570\u636E\u5219\u76F4\u63A5\u5199 blocked + blockingReason\uFF0C\u5E76\u4FDD\u7559 state \u6307\u5411\u5F53\u524D
|
|
904
|
+
\u73AF\u5883\u5C31\u7EEA\u540E\u7528 CLI \u66F4\u65B0\u81F3 Step 3\uFF1B\u7F3A\u5C11\u73AF\u5883\u3001\u6743\u9650\u6216\u6570\u636E\u5219\u76F4\u63A5\u5199 blocked + blockingReason\uFF0C\u5E76\u4FDD\u7559 state \u6307\u5411\u5F53\u524D manba \u4EE5\u4FBF\u6062\u590D\u3002
|
|
904
905
|
|
|
905
906
|
## 3. \u590D\u73B0\u4E0E\u8BCA\u65AD
|
|
906
907
|
|
|
@@ -920,7 +921,7 @@ var MAMBA_SKILL = {
|
|
|
920
921
|
- \u7F3A\u5C11\u73AF\u5883\u3001\u6743\u9650\u6216\u6570\u636E\uFF0C\u6216\u4FEE\u590D\u4ECD\u5931\u8D25\uFF1A\`status: "blocked"\` + \`blockingReason\`
|
|
921
922
|
- \u53D7\u9650\u5E73\u53F0\u53EA\u80FD\u751F\u6210\u624B\u6D4B\u65B9\u6848\uFF1A\`status: "completed", outcome: "manual_test_required"\`\uFF1B\u5FC5\u987B\u660E\u786E\u6CA1\u6709\u6267\u884C\u771F\u5B9E\u6D4B\u8BD5
|
|
922
923
|
|
|
923
|
-
\u7528 \`mancode workflow update <taskId> --status <status> [--outcome <outcome>] [--blocking-reason <\u539F\u56E0>]\` \u5199\u7ED3\u8BBA\u3002\u7236\u5B50\u4EFB\u52A1\u7684 blocked/manual_test_required \u4F20\u64AD\u7531 CLI \u8D1F\u8D23\u3002\u5173\u8054\u5B50\u4EFB\u52A1\u4EE5 fixed/verified/no_repro \u5B8C\u6210\u540E\uFF0C\u5148\u8BFB\u53D6\u7236 workflow\uFF1B\u82E5\u7236\u4ECD\u56E0\u672C\u5B50\u4EFB\u52A1 blocked\uFF0C\u8FD0\u884C \`mancode workflow update <parentTaskId> --status in_progress\` \u6E05\u9664\u963B\u585E\uFF0C\u518D\u628A state \u6062\u590D\u5230\u7236 workflow \u7684 Step 6\u3002manual_test_required \u4E0D\u5F97\u81EA\u52A8\u6062\u590D\u7236\u4EFB\u52A1\u3002\u72EC\u7ACB\u4EFB\u52A1\u5B8C\u6210\u540E\u56DE solo\uFF1Bblocked \u7684\u72EC\u7ACB\u4EFB\u52A1\u7EE7\u7EED\u4FDD\u6301
|
|
924
|
+
\u7528 \`mancode workflow update <taskId> --status <status> [--outcome <outcome>] [--blocking-reason <\u539F\u56E0>]\` \u5199\u7ED3\u8BBA\u3002\u7236\u5B50\u4EFB\u52A1\u7684 blocked/manual_test_required \u4F20\u64AD\u7531 CLI \u8D1F\u8D23\u3002\u5173\u8054\u5B50\u4EFB\u52A1\u4EE5 fixed/verified/no_repro \u5B8C\u6210\u540E\uFF0C\u5148\u8BFB\u53D6\u7236 workflow\uFF1B\u82E5\u7236\u4ECD\u56E0\u672C\u5B50\u4EFB\u52A1 blocked\uFF0C\u8FD0\u884C \`mancode workflow update <parentTaskId> --status in_progress\` \u6E05\u9664\u963B\u585E\uFF0C\u518D\u628A state \u6062\u590D\u5230\u7236 workflow \u7684 Step 6\u3002manual_test_required \u4E0D\u5F97\u81EA\u52A8\u6062\u590D\u7236\u4EFB\u52A1\u3002\u72EC\u7ACB\u4EFB\u52A1\u5B8C\u6210\u540E\u56DE solo\uFF1Bblocked \u7684\u72EC\u7ACB\u4EFB\u52A1\u7EE7\u7EED\u4FDD\u6301 manba \u6A21\u5F0F\uFF08state \u5185\u90E8\u503C\u4E3A mamba\uFF09\u3002\u4EFB\u4F55\u60C5\u51B5\u4E0B\u90FD\u4E0D\u5F97\u76F4\u63A5\u7F16\u8F91 metadata \u7ED5\u8FC7 CLI\u3002
|
|
924
925
|
|
|
925
926
|
${CORE_CODING_PRINCIPLES}
|
|
926
927
|
`
|
|
@@ -1121,8 +1122,8 @@ var MANSOLO_SKILL = {
|
|
|
1121
1122
|
|
|
1122
1123
|
- **\u6CA1\u6709 .mancode/state.json**\uFF1A\u544A\u8BC9\u7528\u6237\uFF1A"\u9879\u76EE\u672A\u521D\u59CB\u5316\uFF0C\u8FD0\u884C \`mancode init\`\u3002"
|
|
1123
1124
|
- **state.json \u5DF2\u662F solo**\uFF1A\u4ECD\u6B63\u5E38\u6267\u884C\uFF0C\u544A\u77E5\u7528\u6237\u5F53\u524D\u5DF2\u662F solo\u3002
|
|
1124
|
-
- **\u5F53\u524D task \u5DF2\u662F completed/abandoned**\uFF1A\u4E0D\u8981\u5C1D\u8BD5\u628A\u7EC8\u6001\u6539\u6210 abandoned\uFF1B\u76F4\u63A5\u6E05\u7406 state\u3002\u82E5\u5B83\u662F\u5173\u8054
|
|
1125
|
-
- **\u5F53\u524D\u5728 active /man /
|
|
1125
|
+
- **\u5F53\u524D task \u5DF2\u662F completed/abandoned**\uFF1A\u4E0D\u8981\u5C1D\u8BD5\u628A\u7EC8\u6001\u6539\u6210 abandoned\uFF1B\u76F4\u63A5\u6E05\u7406 state\u3002\u82E5\u5B83\u662F\u5173\u8054 manba \u4E14\u7236\u4EFB\u52A1\u4ECD active\uFF0C\u5148\u5C55\u793A\u7236\u4EFB\u52A1\u5E76\u6309\u4E0B\u4E00\u6761\u786E\u8BA4\u662F\u5426\u4E00\u5E76\u653E\u5F03\u3002
|
|
1126
|
+
- **\u5F53\u524D\u5728 active /man /manba /manteam \u6D41\u7A0B\u4E2D**\uFF1A\u7528 AskUserQuestion \u95EE\u7528\u6237\uFF1A
|
|
1126
1127
|
\`\`\`
|
|
1127
1128
|
AskUserQuestion({
|
|
1128
1129
|
questions: [{
|
|
@@ -1138,7 +1139,7 @@ var MANSOLO_SKILL = {
|
|
|
1138
1139
|
\`\`\`
|
|
1139
1140
|
\u7528\u6237\u786E\u8BA4\u540E\uFF1A
|
|
1140
1141
|
- \u5148\u7528 \`mancode workflow show <taskId> --json\` \u68C0\u67E5\u7236\u5B50\u5173\u7CFB\u548C activeChildren\u3002
|
|
1141
|
-
- \u82E5\u5B58\u5728\u6D3B\u8DC3\u5B50\u4EFB\u52A1\u6216\u5F53\u524D\u662F\u5E26 parentTaskId \u7684
|
|
1142
|
+
- \u82E5\u5B58\u5728\u6D3B\u8DC3\u5B50\u4EFB\u52A1\u6216\u5F53\u524D\u662F\u5E26 parentTaskId \u7684 manba\uFF0C\u660E\u786E\u8BF4\u660E\u5C06\u653E\u5F03\u7684 workflow \u94FE\uFF1B\u5148\u9010\u4E2A\u7528 CLI \u6807\u8BB0\u5B50\u4EFB\u52A1 abandoned\uFF0C\u518D\u6807\u8BB0\u7236/\u5F53\u524D\u4EFB\u52A1 abandoned\u3002
|
|
1142
1143
|
- \u6240\u6709 metadata \u72B6\u6001\u90FD\u5FC5\u987B\u901A\u8FC7 \`mancode workflow update <taskId> --status abandoned\` \u66F4\u65B0\uFF1BCLI \u62D2\u7EDD\u65F6\u505C\u6B62\uFF0C\u4E0D\u5F97\u76F4\u63A5 Edit \u7ED5\u8FC7\u3002
|
|
1143
1144
|
- \u6210\u529F\u540E\u518D\u66F4\u65B0 state.json \u5207\u56DE solo\uFF1BCLI \u4F1A\u540C\u6B65\u6E05\u7406 Active Plans\u3002
|
|
1144
1145
|
|
|
@@ -1193,12 +1194,12 @@ var MANTEAM_SKILL = {
|
|
|
1193
1194
|
3. Game Plan\uFF1A\u8BA1\u5212\u91CC\u5FC5\u987B\u5305\u542B\u53D8\u66F4\u8FB9\u754C\u3001\u517C\u5BB9\u6027\u98CE\u9669\u3001\u56DE\u6EDA\u65B9\u5F0F\uFF1BPlan Coach \u53EA\u8FD4\u56DE\u8BA1\u5212\u6587\u672C\uFF0C\u7981\u6B62\u63D0\u524D\u4FEE\u6539\u4E1A\u52A1\u6587\u4EF6\u6216\u56E2\u961F memory\u3002
|
|
1194
1195
|
4. \u8BA1\u5212\u5173\u5361\uFF1A\u7528\u6237\u53EF\u53EA\u4FDD\u7559\u8BA1\u5212\u3001\u7EE7\u7EED\u6267\u884C\u6216\u91CD\u5199\u8BA1\u5212\uFF1BActive Plans \u6309 taskId \u66F4\u65B0\u3002
|
|
1195
1196
|
5. \u5B9E\u65BD\uFF1A\u6539\u52A8\u524D\u518D\u6B21\u68C0\u67E5 \`git status --short\`\uFF0C\u907F\u514D\u8E29\u7528\u6237\u6216\u961F\u53CB\u6539\u52A8\u3002
|
|
1196
|
-
6. Self-test\uFF1A\u4F18\u5148\u8DD1\u9879\u76EE\u5DF2\u6709\u9A8C\u8BC1\u547D\u4EE4\uFF1B\u5931\u8D25\u4E24\u6B21\u505C\u4E0B\u8BCA\u65AD\u6839\u56E0\u3002\u9700\u8981\u771F\u5B9E\u9A8C\u8BC1\u65F6\u521B\u5EFA\u5E26 parentTaskId \u7684 /
|
|
1197
|
+
6. Self-test\uFF1A\u4F18\u5148\u8DD1\u9879\u76EE\u5DF2\u6709\u9A8C\u8BC1\u547D\u4EE4\uFF1B\u5931\u8D25\u4E24\u6B21\u505C\u4E0B\u8BCA\u65AD\u6839\u56E0\u3002\u9700\u8981\u771F\u5B9E\u9A8C\u8BC1\u65F6\u521B\u5EFA\u5E26 parentTaskId \u7684 /manba \u5B50\u4EFB\u52A1\u3002\u5B50\u4EFB\u52A1\u4FEE\u590D\u540E\u82E5\u7236\u4EFB\u52A1\u66FE\u56E0\u6B64 blocked\uFF0C\u5148\u7528 workflow CLI \u6062\u590D\u7236\u4EFB\u52A1\u4E3A in_progress\uFF1Bmanual_test_required \u4E0D\u5F97\u81EA\u52A8\u6062\u590D\u3002
|
|
1197
1198
|
7. Review scope + Film #1\uFF1A\u57FA\u4E8E\u5B9E\u9645 diff \u5199 \`review-scope.md\`\uFF0C\u7528 \`workflow review ... init\` \u9009\u62E9 targeted \u6216 full\uFF1B\u91CD\u70B9\u5BA1\u67E5\u884C\u4E3A\u3001\u53EF\u7EF4\u62A4\u6027\u3001\u56E2\u961F\u98CE\u683C\u4E0E\u6D4B\u8BD5\uFF0Cfinding \u5FC5\u987B\u6709\u8BC1\u636E\u548C\u7A33\u5B9A ID\u3002
|
|
1198
1199
|
8. Film #2\uFF1A\u4EC5 full \u6267\u884C\uFF0C\u5148\u8BFB Film #1 \u62A5\u544A\u5E76\u53BB\u91CD\uFF0C\u53EA\u5BA1\u67E5\u8FB9\u754C\u3001\u5B89\u5168\u3001\u6027\u80FD\u3001\u5E76\u53D1\u548C\u517C\u5BB9\u6027\u3002\u7528\u6237\u660E\u786E\u8981\u6C42\u624D\u53EF\u8DF3\u8FC7\u5168\u90E8 review\uFF0C\u5E76\u5199\u5165 \`skippedSteps\` \u548C\u6B8B\u4F59\u98CE\u9669\u3002
|
|
1199
1200
|
9. Post-game\uFF1A\u6C47\u603B blocker\uFF0C\u53EA\u505A\u4E00\u8F6E\u4FEE\u590D\u5E76\u7528 \`workflow review ... remediate\` \u8BB0\u5F55\uFF1B\u590D\u9A8C\u540E\u5199 summary \u4E0E hand-off\u3002\u9A8C\u8BC1\u5931\u8D25\u3001\u5BA1\u67E5\u4E0D\u5B8C\u6574\u6216\u4ECD\u6709 blocker \u65F6\u6807\u8BB0 blocked\uFF0C\u4E0D\u5F97\u6807 completed\u3002
|
|
1200
1201
|
|
|
1201
|
-
\u5982\u679C\u7528\u6237\u5728\u786E\u8BA4\u9636\u6BB5\u9009\u62E9"\u9000\u51FA"\u6216\u653E\u5F03\u56E2\u961F workflow\uFF1A\u7528 \`mancode workflow update <taskId> --status abandoned\` \u66F4\u65B0\u5E76\u6E05\u7406 Active Plans\uFF1B\u82E5\u6709\u6D3B\u8DC3
|
|
1202
|
+
\u5982\u679C\u7528\u6237\u5728\u786E\u8BA4\u9636\u6BB5\u9009\u62E9"\u9000\u51FA"\u6216\u653E\u5F03\u56E2\u961F workflow\uFF1A\u7528 \`mancode workflow update <taskId> --status abandoned\` \u66F4\u65B0\u5E76\u6E05\u7406 Active Plans\uFF1B\u82E5\u6709\u6D3B\u8DC3 manba \u5B50\u4EFB\u52A1\uFF0C\u5148\u53D6\u5F97\u786E\u8BA4\u5E76\u9010\u4E2A abandoned\u3002\u6210\u529F\u540E\u518D\u7528 Edit \u66F4\u65B0 state \u56DE solo\u3002\u4E0D\u8981\u76F4\u63A5\u6539 metadata\uFF0C\u4E5F\u4E0D\u8981\u628A abandoned workflow \u7559\u5728 active state\u3002
|
|
1202
1203
|
|
|
1203
1204
|
## Team Hand-off Summary
|
|
1204
1205
|
|
|
@@ -1493,6 +1494,7 @@ var LEGACY_CLAUDE_SKILL_SETTINGS = {
|
|
|
1493
1494
|
man: ".claude/skills/mancode-man.md",
|
|
1494
1495
|
mansolo: ".claude/skills/mancode-mansolo.md"
|
|
1495
1496
|
};
|
|
1497
|
+
var LEGACY_MVP2_SKILL_NAMES = ["mamba", "man8"];
|
|
1496
1498
|
async function validateClaudeCodeSettings(projectRoot) {
|
|
1497
1499
|
await readClaudeSettings(path3.join(projectRoot, ".claude"));
|
|
1498
1500
|
}
|
|
@@ -1500,6 +1502,7 @@ async function installClaudeCode(projectRoot, options) {
|
|
|
1500
1502
|
const claudeDir = path3.join(projectRoot, ".claude");
|
|
1501
1503
|
const settings = await readClaudeSettings(claudeDir);
|
|
1502
1504
|
const force = options.force ?? false;
|
|
1505
|
+
await validateClaudeWriteTargets(claudeDir, options.minimal ?? false, force);
|
|
1503
1506
|
await installMancodeCore(projectRoot);
|
|
1504
1507
|
await mkdir3(path3.join(claudeDir, "skills"), { recursive: true });
|
|
1505
1508
|
await installSoloSkill(path3.join(claudeDir, "skills"), force);
|
|
@@ -1516,6 +1519,33 @@ async function installClaudeCode(projectRoot, options) {
|
|
|
1516
1519
|
await updateClaudeSettings(claudeDir, settings);
|
|
1517
1520
|
await removeLegacyHookFiles(projectRoot);
|
|
1518
1521
|
}
|
|
1522
|
+
async function validateClaudeWriteTargets(claudeDir, minimal, force) {
|
|
1523
|
+
if (!force) return;
|
|
1524
|
+
const skillsDir = path3.join(claudeDir, "skills");
|
|
1525
|
+
await assertWritableClaudeSkill(skillsDir, "solo");
|
|
1526
|
+
if (minimal) return;
|
|
1527
|
+
for (const skill of MVP2_SKILLS) {
|
|
1528
|
+
await assertWritableClaudeSkill(skillsDir, skill.name);
|
|
1529
|
+
}
|
|
1530
|
+
for (const agent of ALL_AGENTS) {
|
|
1531
|
+
const agentPath = path3.join(claudeDir, "agents", `${agent.name}.md`);
|
|
1532
|
+
const existing = await readTextIfExists2(agentPath);
|
|
1533
|
+
if (existing !== null && !isGeneratedClaudeAgent(existing, agent.name)) {
|
|
1534
|
+
throw new Error(
|
|
1535
|
+
`refusing to overwrite user-authored Claude Code agent: ${agentPath}`
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
async function assertWritableClaudeSkill(skillsDir, modeName) {
|
|
1541
|
+
const skillPath = path3.join(skillsDir, modeName, "SKILL.md");
|
|
1542
|
+
const existing = await readTextIfExists2(skillPath);
|
|
1543
|
+
if (existing !== null && !isGeneratedClaudeSkill(existing, modeName)) {
|
|
1544
|
+
throw new Error(
|
|
1545
|
+
`refusing to overwrite user-authored Claude Code skill: ${skillPath}`
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1519
1549
|
async function removeLegacyHookFiles(projectRoot) {
|
|
1520
1550
|
const hooksDir = path3.join(projectRoot, ".mancode", "hooks");
|
|
1521
1551
|
await Promise.all([
|
|
@@ -1536,20 +1566,24 @@ async function installSoloSkill(skillsDir, force) {
|
|
|
1536
1566
|
await removeLegacyFlatSkill(skillsDir, "mancode-solo", "solo");
|
|
1537
1567
|
}
|
|
1538
1568
|
async function installMvp2Skills(skillsDir, force) {
|
|
1539
|
-
await removeManagedLegacyMan8Skill(skillsDir);
|
|
1540
1569
|
for (const skill of MVP2_SKILLS) {
|
|
1541
1570
|
await installProjectSkill(skillsDir, skill, force);
|
|
1542
1571
|
await removeLegacyFlatSkill(skillsDir, `mancode-${skill.name}`, skill.name);
|
|
1543
1572
|
}
|
|
1573
|
+
await removeManagedLegacyMvp2Skills(skillsDir);
|
|
1544
1574
|
}
|
|
1545
|
-
async function
|
|
1546
|
-
|
|
1575
|
+
async function removeManagedLegacyMvp2Skills(skillsDir) {
|
|
1576
|
+
for (const modeName of LEGACY_MVP2_SKILL_NAMES) {
|
|
1577
|
+
await removeGeneratedSkillDir(skillsDir, modeName);
|
|
1578
|
+
await removeLegacyFlatSkill(skillsDir, `mancode-${modeName}`, modeName);
|
|
1579
|
+
}
|
|
1547
1580
|
}
|
|
1548
1581
|
async function uninstallMvp2Skills(skillsDir) {
|
|
1549
1582
|
for (const skill of MVP2_SKILLS) {
|
|
1550
1583
|
await removeGeneratedSkillDir(skillsDir, skill.name);
|
|
1551
1584
|
await removeLegacyFlatSkill(skillsDir, `mancode-${skill.name}`, skill.name);
|
|
1552
1585
|
}
|
|
1586
|
+
await removeManagedLegacyMvp2Skills(skillsDir);
|
|
1553
1587
|
}
|
|
1554
1588
|
async function installProjectSkill(skillsDir, skill, force = true) {
|
|
1555
1589
|
const skillDir = path3.join(skillsDir, skill.name);
|
|
@@ -1615,7 +1649,7 @@ async function removeClaudeGeneratedContent(projectRoot) {
|
|
|
1615
1649
|
const skillsDir = path3.join(claudeDir, "skills");
|
|
1616
1650
|
for (const modeName of [
|
|
1617
1651
|
"solo",
|
|
1618
|
-
|
|
1652
|
+
...LEGACY_MVP2_SKILL_NAMES,
|
|
1619
1653
|
...MVP2_SKILLS.map((skill) => skill.name)
|
|
1620
1654
|
]) {
|
|
1621
1655
|
await removeGeneratedSkillDir(skillsDir, modeName);
|
|
@@ -1650,8 +1684,9 @@ ${marker}
|
|
|
1650
1684
|
async function readTextIfExists2(filePath) {
|
|
1651
1685
|
try {
|
|
1652
1686
|
return await readFile3(filePath, "utf-8");
|
|
1653
|
-
} catch {
|
|
1654
|
-
return null;
|
|
1687
|
+
} catch (error) {
|
|
1688
|
+
if (isNodeError2(error) && error.code === "ENOENT") return null;
|
|
1689
|
+
throw error;
|
|
1655
1690
|
}
|
|
1656
1691
|
}
|
|
1657
1692
|
async function removeIfEmpty(dir) {
|
|
@@ -1783,13 +1818,13 @@ import path6 from "path";
|
|
|
1783
1818
|
import { mkdir as mkdir4, readFile as readFile4, readdir as readdir2, rm as rm2, writeFile as writeFile4 } from "fs/promises";
|
|
1784
1819
|
import path4 from "path";
|
|
1785
1820
|
var MODE_NAMES = [
|
|
1786
|
-
"
|
|
1821
|
+
"manba",
|
|
1787
1822
|
"man",
|
|
1788
1823
|
"manteam",
|
|
1789
1824
|
"manps",
|
|
1790
1825
|
"mansolo"
|
|
1791
1826
|
];
|
|
1792
|
-
var LEGACY_MODE_NAMES = ["man8"];
|
|
1827
|
+
var LEGACY_MODE_NAMES = ["mamba", "man8"];
|
|
1793
1828
|
var CODEX_SKILL_MANAGED_MARKER = "<!-- Managed by mancode:codex-skill. Do not edit this file manually. -->";
|
|
1794
1829
|
var ZCODE_SKILL_MANAGED_MARKER = "<!-- Managed by mancode:zcode-skill. Do not edit this file manually. -->";
|
|
1795
1830
|
var MODE_FILE_MANAGED_MARKER = "<!-- Managed by mancode:mode-file. Do not edit this file manually. -->";
|
|
@@ -1799,7 +1834,7 @@ var MANCODE_AGENT_SKILL_MARKERS = [
|
|
|
1799
1834
|
];
|
|
1800
1835
|
function renderModeSkill(mode, commandPrefix = "$") {
|
|
1801
1836
|
const meta = MODE_META[mode];
|
|
1802
|
-
const stateMode = mode === "mansolo" ? "solo" : mode;
|
|
1837
|
+
const stateMode = mode === "mansolo" ? "solo" : mode === "manba" ? "mamba" : mode;
|
|
1803
1838
|
const modesList = MODE_NAMES.map((m) => `${commandPrefix}${m}`).join(", ");
|
|
1804
1839
|
return [
|
|
1805
1840
|
meta.intro,
|
|
@@ -1840,7 +1875,6 @@ async function installCodexSkills(projectRoot, minimal) {
|
|
|
1840
1875
|
return;
|
|
1841
1876
|
}
|
|
1842
1877
|
const skillsDir = path4.join(projectRoot, ".agents", "skills");
|
|
1843
|
-
await removeLegacyManagedSkills(skillsDir, MANCODE_AGENT_SKILL_MARKERS);
|
|
1844
1878
|
for (const mode of MODE_NAMES) {
|
|
1845
1879
|
const modeDir = path4.join(skillsDir, mode);
|
|
1846
1880
|
await mkdir4(modeDir, { recursive: true });
|
|
@@ -1864,6 +1898,7 @@ async function installCodexSkills(projectRoot, minimal) {
|
|
|
1864
1898
|
MANCODE_AGENT_SKILL_MARKERS
|
|
1865
1899
|
);
|
|
1866
1900
|
}
|
|
1901
|
+
await removeLegacyManagedSkills(skillsDir, MANCODE_AGENT_SKILL_MARKERS);
|
|
1867
1902
|
}
|
|
1868
1903
|
async function installZcodeSkills(projectRoot, minimal) {
|
|
1869
1904
|
if (minimal) {
|
|
@@ -1871,7 +1906,6 @@ async function installZcodeSkills(projectRoot, minimal) {
|
|
|
1871
1906
|
return;
|
|
1872
1907
|
}
|
|
1873
1908
|
const skillsDir = path4.join(projectRoot, ".agents", "skills");
|
|
1874
|
-
await removeLegacyManagedSkills(skillsDir, MANCODE_AGENT_SKILL_MARKERS);
|
|
1875
1909
|
for (const mode of MODE_NAMES) {
|
|
1876
1910
|
const modeDir = path4.join(skillsDir, mode);
|
|
1877
1911
|
await mkdir4(modeDir, { recursive: true });
|
|
@@ -1895,6 +1929,7 @@ async function installZcodeSkills(projectRoot, minimal) {
|
|
|
1895
1929
|
MANCODE_AGENT_SKILL_MARKERS
|
|
1896
1930
|
);
|
|
1897
1931
|
}
|
|
1932
|
+
await removeLegacyManagedSkills(skillsDir, MANCODE_AGENT_SKILL_MARKERS);
|
|
1898
1933
|
}
|
|
1899
1934
|
async function installCursorCommands(projectRoot, minimal) {
|
|
1900
1935
|
if (minimal) {
|
|
@@ -1903,7 +1938,6 @@ async function installCursorCommands(projectRoot, minimal) {
|
|
|
1903
1938
|
}
|
|
1904
1939
|
const commandsDir = path4.join(projectRoot, ".cursor", "commands");
|
|
1905
1940
|
await mkdir4(commandsDir, { recursive: true });
|
|
1906
|
-
await removeLegacyGeneratedModeFile(path4.join(commandsDir, "man8.md"));
|
|
1907
1941
|
for (const mode of MODE_NAMES) {
|
|
1908
1942
|
const meta = MODE_META[mode];
|
|
1909
1943
|
const content = [
|
|
@@ -1923,6 +1957,8 @@ async function installCursorCommands(projectRoot, minimal) {
|
|
|
1923
1957
|
"Cursor"
|
|
1924
1958
|
);
|
|
1925
1959
|
}
|
|
1960
|
+
await removeManagedModeFile(path4.join(commandsDir, "mamba.md"), "mamba");
|
|
1961
|
+
await removeLegacyGeneratedModeFile(path4.join(commandsDir, "man8.md"));
|
|
1926
1962
|
}
|
|
1927
1963
|
async function installCopilotPrompts(projectRoot, minimal) {
|
|
1928
1964
|
if (minimal) {
|
|
@@ -1931,7 +1967,6 @@ async function installCopilotPrompts(projectRoot, minimal) {
|
|
|
1931
1967
|
}
|
|
1932
1968
|
const promptsDir = path4.join(projectRoot, ".github", "prompts");
|
|
1933
1969
|
await mkdir4(promptsDir, { recursive: true });
|
|
1934
|
-
await removeLegacyGeneratedModeFile(path4.join(promptsDir, "man8.prompt.md"));
|
|
1935
1970
|
for (const mode of MODE_NAMES) {
|
|
1936
1971
|
const meta = MODE_META[mode];
|
|
1937
1972
|
const content = [
|
|
@@ -1952,6 +1987,11 @@ async function installCopilotPrompts(projectRoot, minimal) {
|
|
|
1952
1987
|
"GitHub Copilot"
|
|
1953
1988
|
);
|
|
1954
1989
|
}
|
|
1990
|
+
await removeManagedModeFile(
|
|
1991
|
+
path4.join(promptsDir, "mamba.prompt.md"),
|
|
1992
|
+
"mamba"
|
|
1993
|
+
);
|
|
1994
|
+
await removeLegacyGeneratedModeFile(path4.join(promptsDir, "man8.prompt.md"));
|
|
1955
1995
|
}
|
|
1956
1996
|
async function removeCodexSkills(projectRoot) {
|
|
1957
1997
|
if (!await platformNeedsSharedSkills(projectRoot, "zcode")) {
|
|
@@ -2090,18 +2130,22 @@ function isRecord2(value) {
|
|
|
2090
2130
|
async function readTextIfExists3(filePath) {
|
|
2091
2131
|
try {
|
|
2092
2132
|
return await readFile4(filePath, "utf-8");
|
|
2093
|
-
} catch {
|
|
2094
|
-
return null;
|
|
2133
|
+
} catch (error) {
|
|
2134
|
+
if (isNodeError3(error) && error.code === "ENOENT") return null;
|
|
2135
|
+
throw error;
|
|
2095
2136
|
}
|
|
2096
2137
|
}
|
|
2138
|
+
function isNodeError3(error) {
|
|
2139
|
+
return error instanceof Error && "code" in error;
|
|
2140
|
+
}
|
|
2097
2141
|
var MODE_META = {
|
|
2098
|
-
|
|
2142
|
+
manba: {
|
|
2099
2143
|
description: "Diagnose bugs and validate real user flows with targeted regression checks.",
|
|
2100
|
-
intro: "# mancode
|
|
2144
|
+
intro: "# mancode manba \u2014 Diagnosis and Real Validation",
|
|
2101
2145
|
workflow: [
|
|
2102
2146
|
"## Five-step workflow (simulate roles in one conversation)",
|
|
2103
2147
|
"",
|
|
2104
|
-
'Use `mancode workflow create
|
|
2148
|
+
'Use `mancode workflow create manba "<task>" --json`; when called from',
|
|
2105
2149
|
"a /man or /manteam Step 6 workflow, add `--parent-task <taskId>`. Persist the returned",
|
|
2106
2150
|
"taskId in `.mancode/state.json` as currentTask; set currentMode and",
|
|
2107
2151
|
"currentWorkflowMode to mamba. Never edit metadata.json directly; use",
|
|
@@ -2137,7 +2181,7 @@ var MODE_META = {
|
|
|
2137
2181
|
"Step 3 \u2014 Plan: write `plan.md` with scope, reuse, exclusions, risks, completion criteria, and real validation. First plan is v1; revisions use `--plan-version <current+1>` and Active Plans is updated by the CLI.",
|
|
2138
2182
|
"Step 4 \u2014 Plan gate: the user chooses plan-only (`status planned`), continue to execution, or revise the plan. Do not implement before this choice.",
|
|
2139
2183
|
"Step 5 \u2014 Implement the confirmed minimum change.",
|
|
2140
|
-
"Step 6 \u2014 Run detected build/lint/typecheck/test and smoke checks. A real or complex diagnosis creates a linked
|
|
2184
|
+
"Step 6 \u2014 Run detected build/lint/typecheck/test and smoke checks. A real or complex diagnosis creates a linked manba child while the parent remains at Step 6. After a resolved child, restore a parent blocked by that child with `workflow update --status in_progress` before Step 7; never auto-resume manual_test_required. Then write `review-scope.md` from the actual diff, verification evidence, and hard-risk triggers. Initialize a targeted review for routine governed changes or a full review for auth, payment, sensitive data, migrations/deletion, public APIs, untrusted input, concurrency, cross-service, or infrastructure changes using `mancode workflow review <taskId> init ...`.",
|
|
2141
2185
|
"Step 7 \u2014 Run one quality review limited to the changed diff and direct impact. Findings require changed-line evidence and user impact, with at most three new findings. Record stable blocker IDs through `workflow review ... complete`; do not fix yet.",
|
|
2142
2186
|
"Step 8 \u2014 Only full review runs the security/boundary reviewer. It must read Film #1, mark the same root cause duplicate, and stay within security, permissions, recovery, resources, and boundaries. A targeted review ends after Film #1.",
|
|
2143
2187
|
"Step 9 \u2014 If open blockers exist, fix them in one remediation round and record resolved IDs through `workflow review ... remediate`; with no blockers, do not create a remediation round. Re-run affected verification without re-running completed reviewers. Write `summary.md` and set completed only when required domains are complete and blockers are zero; otherwise set blocked+blockingReason.",
|
|
@@ -2158,9 +2202,9 @@ var MODE_META = {
|
|
|
2158
2202
|
"",
|
|
2159
2203
|
"Then execute every /man Step 1 through Step 9, including clarification,",
|
|
2160
2204
|
"plan gate, validation, bounded risk-based review, conditional completion, planVersion,",
|
|
2161
|
-
"Active Plans, and linked
|
|
2205
|
+
"Active Plans, and linked manba child behavior. Add compatibility, rollback,",
|
|
2162
2206
|
"ownership, conflict, and handoff notes at each relevant step.",
|
|
2163
|
-
"When a resolved
|
|
2207
|
+
"When a resolved manba child had blocked the parent, restore the parent through",
|
|
2164
2208
|
"`workflow update --status in_progress`; never auto-resume manual_test_required.",
|
|
2165
2209
|
"",
|
|
2166
2210
|
"Read `.mancode/memory/prd.md`, `spec.md`, and `decisions.md`; append durable",
|
|
@@ -2234,7 +2278,7 @@ function renderProjectContext(state, tokens, profile, options) {
|
|
|
2234
2278
|
const currentProfile = options.projectProfile ?? profile;
|
|
2235
2279
|
const techStack = options.projectProfile ? options.techStack.join(" + ") || "Unknown" : state?.techStack || options.techStack.join(" + ") || "Unknown";
|
|
2236
2280
|
const uiLibrary = options.projectProfile ? options.uiLibrary || "None" : state?.uiLibrary || tokens?.uiLibrary || options.uiLibrary || "None";
|
|
2237
|
-
const mode = state?.currentMode || "solo";
|
|
2281
|
+
const mode = state?.currentMode === "mamba" ? "manba" : state?.currentMode || "solo";
|
|
2238
2282
|
return [
|
|
2239
2283
|
"## mancode Project Context",
|
|
2240
2284
|
"",
|
|
@@ -2298,7 +2342,7 @@ function renderAesthetics(tokens) {
|
|
|
2298
2342
|
function renderModes(options) {
|
|
2299
2343
|
let commandLabel;
|
|
2300
2344
|
if (options.capabilities.skills === "agents-skills") {
|
|
2301
|
-
commandLabel = "Invoke mode skills with `$man`, `$
|
|
2345
|
+
commandLabel = "Invoke mode skills with `$man`, `$manba`, `$manteam`, `$manps`, `$mansolo`.";
|
|
2302
2346
|
} else if (options.capabilities.slashCommands === "native") {
|
|
2303
2347
|
commandLabel = "Use the named commands directly when available.";
|
|
2304
2348
|
} else {
|
|
@@ -2309,7 +2353,7 @@ function renderModes(options) {
|
|
|
2309
2353
|
"",
|
|
2310
2354
|
`- solo: default lightweight mode. ${commandLabel}`,
|
|
2311
2355
|
"- man: progressive governance workflow with planning and optional full execution.",
|
|
2312
|
-
"-
|
|
2356
|
+
"- manba: diagnose bugs and validate real user flows or regressions.",
|
|
2313
2357
|
"- manteam: use team memory and leave handoff-friendly summaries.",
|
|
2314
2358
|
"- manps: run project health and cleanup scans before remediation.",
|
|
2315
2359
|
"- mansolo: return to solo mode."
|
|
@@ -2371,11 +2415,15 @@ var MANCODE_CURSOR_CORE_RULE_FILES = [
|
|
|
2371
2415
|
"mancode-solo.mdc"
|
|
2372
2416
|
];
|
|
2373
2417
|
var MANCODE_CURSOR_ADVANCED_RULE_FILES = [
|
|
2374
|
-
"mancode-
|
|
2418
|
+
"mancode-manba.mdc",
|
|
2375
2419
|
"mancode-man.mdc",
|
|
2376
2420
|
"mancode-manteam.mdc",
|
|
2377
2421
|
"mancode-manps.mdc"
|
|
2378
2422
|
];
|
|
2423
|
+
var MANCODE_CURSOR_LEGACY_RULE_FILES = [
|
|
2424
|
+
"mancode-mamba.mdc",
|
|
2425
|
+
"mancode-man8.mdc"
|
|
2426
|
+
];
|
|
2379
2427
|
var CURSOR_RULE_MANAGED_MARKER = "<!-- Managed by mancode:cursor-rule. Do not edit this marker. -->";
|
|
2380
2428
|
var MANCODE_CURSOR_RULE_FILES = [
|
|
2381
2429
|
...MANCODE_CURSOR_CORE_RULE_FILES,
|
|
@@ -2427,13 +2475,12 @@ async function installCursor(projectRoot, options) {
|
|
|
2427
2475
|
await installCursorCommands(projectRoot, true);
|
|
2428
2476
|
return;
|
|
2429
2477
|
}
|
|
2430
|
-
await removeLegacyCursorRules(projectRoot);
|
|
2431
2478
|
await writeRule(
|
|
2432
2479
|
rulesDir,
|
|
2433
|
-
"mancode-
|
|
2480
|
+
"mancode-manba.mdc",
|
|
2434
2481
|
"Use for bug diagnosis and real regression testing",
|
|
2435
2482
|
false,
|
|
2436
|
-
|
|
2483
|
+
renderManbaRule()
|
|
2437
2484
|
);
|
|
2438
2485
|
await writeRule(
|
|
2439
2486
|
rulesDir,
|
|
@@ -2456,6 +2503,7 @@ async function installCursor(projectRoot, options) {
|
|
|
2456
2503
|
false,
|
|
2457
2504
|
renderManpsRule()
|
|
2458
2505
|
);
|
|
2506
|
+
await removeLegacyCursorRules(projectRoot);
|
|
2459
2507
|
await installCursorCommands(projectRoot, options.minimal ?? false);
|
|
2460
2508
|
}
|
|
2461
2509
|
async function writeRule(rulesDir, fileName, description, alwaysApply, body) {
|
|
@@ -2503,18 +2551,9 @@ async function removeCursorGeneratedRules(projectRoot) {
|
|
|
2503
2551
|
await removeLegacyCursorRules(projectRoot);
|
|
2504
2552
|
}
|
|
2505
2553
|
async function removeLegacyCursorRules(projectRoot) {
|
|
2506
|
-
const
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
"rules",
|
|
2510
|
-
"mancode-man8.mdc"
|
|
2511
|
-
);
|
|
2512
|
-
try {
|
|
2513
|
-
const content = await readFile6(legacyPath, "utf-8");
|
|
2514
|
-
if (content.includes("# mancode man8 \u2014 Investigate and Plan") && content.includes("## Mode Persistence")) {
|
|
2515
|
-
await rm3(legacyPath, { force: true });
|
|
2516
|
-
}
|
|
2517
|
-
} catch {
|
|
2554
|
+
const rulesDir = path6.join(projectRoot, ".cursor", "rules");
|
|
2555
|
+
for (const fileName of MANCODE_CURSOR_LEGACY_RULE_FILES) {
|
|
2556
|
+
await removeGeneratedCursorRule(rulesDir, fileName);
|
|
2518
2557
|
}
|
|
2519
2558
|
}
|
|
2520
2559
|
async function removeGeneratedCursorRule(rulesDir, fileName) {
|
|
@@ -2541,10 +2580,14 @@ function isGeneratedCursorRule(content, fileName) {
|
|
|
2541
2580
|
async function readTextIfExists4(filePath) {
|
|
2542
2581
|
try {
|
|
2543
2582
|
return await readFile6(filePath, "utf-8");
|
|
2544
|
-
} catch {
|
|
2545
|
-
return null;
|
|
2583
|
+
} catch (error) {
|
|
2584
|
+
if (isNodeError4(error) && error.code === "ENOENT") return null;
|
|
2585
|
+
throw error;
|
|
2546
2586
|
}
|
|
2547
2587
|
}
|
|
2588
|
+
function isNodeError4(error) {
|
|
2589
|
+
return error instanceof Error && "code" in error;
|
|
2590
|
+
}
|
|
2548
2591
|
function renderPracticeRule() {
|
|
2549
2592
|
return [
|
|
2550
2593
|
"# mancode Practice",
|
|
@@ -2575,8 +2618,8 @@ function renderSoloRule() {
|
|
|
2575
2618
|
"- Recommend /man only for auth, payment, sensitive data, migrations/deletion, public APIs, untrusted input, concurrency, or infrastructure risk."
|
|
2576
2619
|
].join("\n");
|
|
2577
2620
|
}
|
|
2578
|
-
function
|
|
2579
|
-
return renderModeSkill("
|
|
2621
|
+
function renderManbaRule() {
|
|
2622
|
+
return renderModeSkill("manba", "/");
|
|
2580
2623
|
}
|
|
2581
2624
|
function renderManRule() {
|
|
2582
2625
|
return renderModeSkill("man", "/");
|
|
@@ -2588,9 +2631,9 @@ function renderManpsRule() {
|
|
|
2588
2631
|
return renderModeSkill("manps", "/");
|
|
2589
2632
|
}
|
|
2590
2633
|
|
|
2591
|
-
// src/installers/
|
|
2592
|
-
import {
|
|
2593
|
-
import
|
|
2634
|
+
// src/installers/platform-status.ts
|
|
2635
|
+
import { promises as fs } from "fs";
|
|
2636
|
+
import path8 from "path";
|
|
2594
2637
|
|
|
2595
2638
|
// src/installers/managed-block.ts
|
|
2596
2639
|
var DEFAULT_MANCODE_START_MARKER = "<!-- mancode:start -->";
|
|
@@ -2682,104 +2725,14 @@ function findMarkerLine(content, marker) {
|
|
|
2682
2725
|
return null;
|
|
2683
2726
|
}
|
|
2684
2727
|
|
|
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
2728
|
// src/installers/zcode.ts
|
|
2776
|
-
import { writeFile as
|
|
2777
|
-
import
|
|
2729
|
+
import { writeFile as writeFile6 } from "fs/promises";
|
|
2730
|
+
import path7 from "path";
|
|
2778
2731
|
var ZCODE_MANCODE_START_MARKER = "<!-- mancode:zcode:start -->";
|
|
2779
2732
|
var ZCODE_MANCODE_END_MARKER = "<!-- mancode:zcode:end -->";
|
|
2780
2733
|
async function installZcode(projectRoot, options) {
|
|
2781
2734
|
await installMancodeCore(projectRoot);
|
|
2782
|
-
const agentsPath =
|
|
2735
|
+
const agentsPath = path7.join(projectRoot, "AGENTS.md");
|
|
2783
2736
|
const existing = await readTextIfExists(agentsPath);
|
|
2784
2737
|
const sharedContent = await generateSharedContent(projectRoot, {
|
|
2785
2738
|
platform: "zcode",
|
|
@@ -2804,7 +2757,7 @@ async function installZcode(projectRoot, options) {
|
|
|
2804
2757
|
sharedContent.trim(),
|
|
2805
2758
|
ZCODE_MANCODE_END_MARKER
|
|
2806
2759
|
].join("\n");
|
|
2807
|
-
await
|
|
2760
|
+
await writeFile6(
|
|
2808
2761
|
agentsPath,
|
|
2809
2762
|
replaceManagedBlock(
|
|
2810
2763
|
existing,
|
|
@@ -2817,101 +2770,472 @@ async function installZcode(projectRoot, options) {
|
|
|
2817
2770
|
await installZcodeSkills(projectRoot, options.minimal ?? false);
|
|
2818
2771
|
}
|
|
2819
2772
|
|
|
2820
|
-
// src/installers/
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
skills: "native"
|
|
2830
|
-
},
|
|
2831
|
-
install: installClaudeCode
|
|
2832
|
-
},
|
|
2833
|
-
cursor: {
|
|
2834
|
-
name: "cursor",
|
|
2835
|
-
displayName: "Cursor",
|
|
2836
|
-
capabilities: {
|
|
2837
|
-
slashCommands: "native",
|
|
2838
|
-
subagents: false,
|
|
2839
|
-
hooks: false,
|
|
2840
|
-
skills: "rules"
|
|
2841
|
-
},
|
|
2842
|
-
install: installCursor
|
|
2843
|
-
},
|
|
2844
|
-
codex: {
|
|
2845
|
-
name: "codex",
|
|
2846
|
-
displayName: "Codex (ChatGPT desktop/CLI)",
|
|
2847
|
-
capabilities: {
|
|
2848
|
-
slashCommands: "partial",
|
|
2849
|
-
subagents: false,
|
|
2850
|
-
hooks: false,
|
|
2851
|
-
skills: "agents-skills"
|
|
2852
|
-
},
|
|
2853
|
-
install: installCodex
|
|
2854
|
-
},
|
|
2855
|
-
copilot: {
|
|
2856
|
-
name: "copilot",
|
|
2857
|
-
displayName: "GitHub Copilot",
|
|
2858
|
-
capabilities: {
|
|
2859
|
-
slashCommands: "none",
|
|
2860
|
-
subagents: false,
|
|
2861
|
-
hooks: false,
|
|
2862
|
-
skills: "instructions"
|
|
2863
|
-
},
|
|
2864
|
-
install: installCopilot
|
|
2865
|
-
},
|
|
2866
|
-
zcode: {
|
|
2867
|
-
name: "zcode",
|
|
2868
|
-
displayName: "ZCode",
|
|
2869
|
-
capabilities: {
|
|
2870
|
-
slashCommands: "partial",
|
|
2871
|
-
subagents: false,
|
|
2872
|
-
hooks: false,
|
|
2873
|
-
skills: "agents-skills"
|
|
2874
|
-
},
|
|
2875
|
-
install: installZcode
|
|
2876
|
-
}
|
|
2877
|
-
};
|
|
2878
|
-
function getPlatformInstaller(platform) {
|
|
2879
|
-
if (!isPlatformName(platform)) return null;
|
|
2880
|
-
return PLATFORM_INSTALLERS[platform];
|
|
2881
|
-
}
|
|
2882
|
-
function getPlatformInstallers() {
|
|
2883
|
-
return Object.values(PLATFORM_INSTALLERS);
|
|
2884
|
-
}
|
|
2885
|
-
function formatPlatformName(platform) {
|
|
2886
|
-
return getPlatformInstaller(platform)?.displayName ?? platform;
|
|
2773
|
+
// src/installers/platform-status.ts
|
|
2774
|
+
async function checkPlatformStatus(rootDir, platform, installed) {
|
|
2775
|
+
const readiness = await checkPlatformReadiness(rootDir, platform);
|
|
2776
|
+
return {
|
|
2777
|
+
installed,
|
|
2778
|
+
ready: installed && readiness.ready,
|
|
2779
|
+
target: readiness.target,
|
|
2780
|
+
detail: describeStatus(installed, readiness)
|
|
2781
|
+
};
|
|
2887
2782
|
}
|
|
2888
|
-
function
|
|
2889
|
-
return
|
|
2783
|
+
async function isPlatformPresent(rootDir, platform) {
|
|
2784
|
+
return (await checkPlatformReadiness(rootDir, platform)).present;
|
|
2890
2785
|
}
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2786
|
+
async function checkPlatformReadiness(rootDir, platform) {
|
|
2787
|
+
if (platform === "claude-code") {
|
|
2788
|
+
const [hasSoloSkill, registered, hasHookFiles] = await Promise.all([
|
|
2789
|
+
fileMatches(
|
|
2790
|
+
path8.join(rootDir, ".claude", "skills", "solo", "SKILL.md"),
|
|
2791
|
+
(content) => isGeneratedClaudeSkill(content, "solo")
|
|
2792
|
+
),
|
|
2793
|
+
claudeHooksRegistered(rootDir),
|
|
2794
|
+
pathsExist([
|
|
2795
|
+
path8.join(rootDir, ".mancode", "hooks", "session-start.mjs"),
|
|
2796
|
+
path8.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
|
|
2797
|
+
])
|
|
2798
|
+
]);
|
|
2799
|
+
const present = hasSoloSkill && registered && hasHookFiles;
|
|
2800
|
+
const minimal = await isPlatformMinimal(rootDir, "claude-code");
|
|
2801
|
+
const fullContentReady = minimal ? true : await claudeFullContentReady(rootDir);
|
|
2802
|
+
return {
|
|
2803
|
+
present,
|
|
2804
|
+
ready: present && fullContentReady,
|
|
2805
|
+
target: ".claude/",
|
|
2806
|
+
readyDetail: present && fullContentReady ? minimal ? "solo skill and hooks ready" : "skills, agents, and hooks ready" : "skills, agents, hook files, or hook registration are incomplete"
|
|
2807
|
+
};
|
|
2808
|
+
}
|
|
2809
|
+
if (platform === "cursor") {
|
|
2810
|
+
const hasCoreRules = await allManagedSkills(
|
|
2811
|
+
MANCODE_CURSOR_CORE_RULE_FILES.map(
|
|
2812
|
+
(file) => path8.join(rootDir, ".cursor", "rules", file)
|
|
2813
|
+
),
|
|
2814
|
+
[CURSOR_RULE_MANAGED_MARKER]
|
|
2815
|
+
);
|
|
2816
|
+
if (await isPlatformMinimal(rootDir, "cursor")) {
|
|
2817
|
+
return {
|
|
2818
|
+
present: hasCoreRules,
|
|
2819
|
+
ready: hasCoreRules,
|
|
2820
|
+
target: ".cursor/rules/",
|
|
2821
|
+
readyDetail: "mancode core rules present"
|
|
2822
|
+
};
|
|
2823
|
+
}
|
|
2824
|
+
const [hasRules, hasCommands] = await Promise.all([
|
|
2825
|
+
allManagedSkills(
|
|
2826
|
+
MANCODE_CURSOR_RULE_FILES.map(
|
|
2827
|
+
(file) => path8.join(rootDir, ".cursor", "rules", file)
|
|
2828
|
+
),
|
|
2829
|
+
[CURSOR_RULE_MANAGED_MARKER]
|
|
2830
|
+
),
|
|
2831
|
+
allManagedSkills(
|
|
2832
|
+
MODE_NAMES.map(
|
|
2833
|
+
(mode) => path8.join(rootDir, ".cursor", "commands", `${mode}.md`)
|
|
2834
|
+
),
|
|
2835
|
+
[MODE_FILE_MANAGED_MARKER]
|
|
2836
|
+
)
|
|
2837
|
+
]);
|
|
2838
|
+
return {
|
|
2839
|
+
present: hasCoreRules,
|
|
2840
|
+
ready: hasRules && hasCommands,
|
|
2841
|
+
target: ".cursor/rules/ + .cursor/commands/",
|
|
2842
|
+
readyDetail: hasRules && hasCommands ? "mancode rules and commands present" : "mode rules or commands are missing, incomplete, or user-authored"
|
|
2843
|
+
};
|
|
2844
|
+
}
|
|
2845
|
+
if (platform === "codex") {
|
|
2846
|
+
const hasBlock2 = await fileHasManagedBlock(path8.join(rootDir, "AGENTS.md"));
|
|
2847
|
+
if (!hasBlock2) {
|
|
2848
|
+
return {
|
|
2849
|
+
present: false,
|
|
2850
|
+
ready: false,
|
|
2851
|
+
target: "AGENTS.md",
|
|
2852
|
+
readyDetail: "managed block missing"
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
if (await isPlatformMinimal(rootDir, "codex")) {
|
|
2856
|
+
return {
|
|
2857
|
+
present: true,
|
|
2858
|
+
ready: true,
|
|
2859
|
+
target: "AGENTS.md",
|
|
2860
|
+
readyDetail: "managed block present"
|
|
2861
|
+
};
|
|
2862
|
+
}
|
|
2863
|
+
const skillsDir = path8.join(rootDir, ".agents", "skills");
|
|
2864
|
+
const hasSkills = await allManagedSkills(
|
|
2865
|
+
MODE_NAMES.map((mode) => path8.join(skillsDir, mode, "SKILL.md")),
|
|
2866
|
+
MANCODE_AGENT_SKILL_MARKERS
|
|
2867
|
+
);
|
|
2868
|
+
return {
|
|
2869
|
+
present: true,
|
|
2870
|
+
ready: hasSkills,
|
|
2871
|
+
target: "AGENTS.md + .agents/skills/",
|
|
2872
|
+
readyDetail: hasSkills ? "managed block and skills present" : "mode skills are missing, incomplete, or user-authored"
|
|
2873
|
+
};
|
|
2874
|
+
}
|
|
2875
|
+
if (platform === "zcode") {
|
|
2876
|
+
const hasBlock2 = await fileHasManagedBlock(
|
|
2877
|
+
path8.join(rootDir, "AGENTS.md"),
|
|
2878
|
+
ZCODE_MANCODE_START_MARKER,
|
|
2879
|
+
ZCODE_MANCODE_END_MARKER
|
|
2880
|
+
);
|
|
2881
|
+
if (!hasBlock2) {
|
|
2882
|
+
return {
|
|
2883
|
+
present: false,
|
|
2884
|
+
ready: false,
|
|
2885
|
+
target: "AGENTS.md",
|
|
2886
|
+
readyDetail: "managed block missing"
|
|
2887
|
+
};
|
|
2888
|
+
}
|
|
2889
|
+
if (await isPlatformMinimal(rootDir, "zcode")) {
|
|
2890
|
+
return {
|
|
2891
|
+
present: true,
|
|
2892
|
+
ready: true,
|
|
2893
|
+
target: "AGENTS.md",
|
|
2894
|
+
readyDetail: "managed block present"
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2897
|
+
const skillsDir = path8.join(rootDir, ".agents", "skills");
|
|
2898
|
+
const hasSkills = await allManagedSkills(
|
|
2899
|
+
MODE_NAMES.map((mode) => path8.join(skillsDir, mode, "SKILL.md")),
|
|
2900
|
+
MANCODE_AGENT_SKILL_MARKERS
|
|
2901
|
+
);
|
|
2902
|
+
return {
|
|
2903
|
+
present: true,
|
|
2904
|
+
ready: hasSkills,
|
|
2905
|
+
target: "AGENTS.md + .agents/skills/",
|
|
2906
|
+
readyDetail: hasSkills ? "managed block and skills present" : "mode skills are missing, incomplete, or user-authored"
|
|
2907
|
+
};
|
|
2908
|
+
}
|
|
2909
|
+
const hasBlock = await fileHasManagedBlock(
|
|
2910
|
+
path8.join(rootDir, ".github", "copilot-instructions.md")
|
|
2911
|
+
);
|
|
2912
|
+
if (!hasBlock) {
|
|
2913
|
+
return {
|
|
2914
|
+
present: false,
|
|
2915
|
+
ready: false,
|
|
2916
|
+
target: ".github/copilot-instructions.md",
|
|
2917
|
+
readyDetail: "managed block missing"
|
|
2918
|
+
};
|
|
2919
|
+
}
|
|
2920
|
+
if (!await isPlatformMinimal(rootDir, "copilot")) {
|
|
2921
|
+
const promptsDir = path8.join(rootDir, ".github", "prompts");
|
|
2922
|
+
const hasPrompts = await allManagedSkills(
|
|
2923
|
+
MODE_NAMES.map((mode) => path8.join(promptsDir, `${mode}.prompt.md`)),
|
|
2924
|
+
[MODE_FILE_MANAGED_MARKER]
|
|
2925
|
+
);
|
|
2926
|
+
return {
|
|
2927
|
+
present: true,
|
|
2928
|
+
ready: hasPrompts,
|
|
2929
|
+
target: ".github/copilot-instructions.md + .github/prompts/",
|
|
2930
|
+
readyDetail: hasPrompts ? "managed block and prompts present" : "mode prompts are missing, incomplete, or user-authored"
|
|
2931
|
+
};
|
|
2932
|
+
}
|
|
2933
|
+
return {
|
|
2934
|
+
present: true,
|
|
2935
|
+
ready: true,
|
|
2936
|
+
target: ".github/copilot-instructions.md",
|
|
2937
|
+
readyDetail: "managed block present"
|
|
2938
|
+
};
|
|
2939
|
+
}
|
|
2940
|
+
function describeStatus(installed, readiness) {
|
|
2941
|
+
if (installed && readiness.present) return readiness.readyDetail;
|
|
2942
|
+
if (!installed && readiness.present) {
|
|
2943
|
+
return "target files present but platform is not recorded in config";
|
|
2944
|
+
}
|
|
2945
|
+
return "missing generated files or registration";
|
|
2946
|
+
}
|
|
2947
|
+
async function allManagedSkills(paths, markers) {
|
|
2948
|
+
const results = await Promise.all(
|
|
2949
|
+
paths.map((filePath) => fileHasAnyMarker(filePath, markers))
|
|
2950
|
+
);
|
|
2951
|
+
return results.every(Boolean);
|
|
2952
|
+
}
|
|
2953
|
+
async function claudeFullContentReady(rootDir) {
|
|
2954
|
+
const skillChecks = MVP2_SKILLS.map(
|
|
2955
|
+
(skill) => fileMatches(
|
|
2956
|
+
path8.join(rootDir, ".claude", "skills", skill.name, "SKILL.md"),
|
|
2957
|
+
(content) => isGeneratedClaudeSkill(content, skill.name)
|
|
2958
|
+
)
|
|
2959
|
+
);
|
|
2960
|
+
const agentChecks = ALL_AGENTS.map(
|
|
2961
|
+
(agent) => fileMatches(
|
|
2962
|
+
path8.join(rootDir, ".claude", "agents", `${agent.name}.md`),
|
|
2963
|
+
(content) => isGeneratedClaudeAgent(content, agent.name)
|
|
2964
|
+
)
|
|
2965
|
+
);
|
|
2966
|
+
const results = await Promise.all([...skillChecks, ...agentChecks]);
|
|
2967
|
+
return results.every(Boolean);
|
|
2968
|
+
}
|
|
2969
|
+
async function fileMatches(filePath, predicate) {
|
|
2970
|
+
try {
|
|
2971
|
+
return predicate(await fs.readFile(filePath, "utf-8"));
|
|
2972
|
+
} catch {
|
|
2973
|
+
return false;
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
async function pathsExist(paths) {
|
|
2977
|
+
return (await Promise.all(paths.map(pathExists2))).every(Boolean);
|
|
2978
|
+
}
|
|
2979
|
+
async function fileHasAnyMarker(filePath, needles) {
|
|
2980
|
+
try {
|
|
2981
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
2982
|
+
return needles.some((needle) => content.includes(needle));
|
|
2983
|
+
} catch {
|
|
2984
|
+
return false;
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
async function isPlatformMinimal(rootDir, platform) {
|
|
2988
|
+
try {
|
|
2989
|
+
const raw = await fs.readFile(
|
|
2990
|
+
path8.join(rootDir, ".mancode", "config.json"),
|
|
2991
|
+
"utf-8"
|
|
2992
|
+
);
|
|
2993
|
+
const config = JSON.parse(raw);
|
|
2994
|
+
if (!isRecord3(config.platformOptions)) return false;
|
|
2995
|
+
const options = config.platformOptions[platform];
|
|
2996
|
+
return isRecord3(options) && options.minimal === true;
|
|
2997
|
+
} catch {
|
|
2998
|
+
return false;
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
async function fileHasManagedBlock(filePath, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
|
|
3002
|
+
try {
|
|
3003
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
3004
|
+
return hasManagedBlock(content, startMarker, endMarker);
|
|
3005
|
+
} catch {
|
|
3006
|
+
return false;
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
async function claudeHooksRegistered(rootDir) {
|
|
3010
|
+
try {
|
|
3011
|
+
const raw = await fs.readFile(
|
|
3012
|
+
path8.join(rootDir, ".claude", "settings.json"),
|
|
3013
|
+
"utf-8"
|
|
3014
|
+
);
|
|
3015
|
+
const settings = JSON.parse(raw);
|
|
3016
|
+
return hooksRegistered(settings);
|
|
3017
|
+
} catch {
|
|
3018
|
+
return false;
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
function hooksRegistered(settings) {
|
|
3022
|
+
if (!isRecord3(settings)) return false;
|
|
3023
|
+
const hooks = settings.hooks;
|
|
3024
|
+
if (!isRecord3(hooks)) return false;
|
|
3025
|
+
return hasHookCommand(hooks.SessionStart, ".mancode/hooks/session-start.mjs") && hasHookCommand(
|
|
3026
|
+
hooks.UserPromptSubmit,
|
|
3027
|
+
".mancode/hooks/user-prompt-submit.mjs"
|
|
3028
|
+
);
|
|
3029
|
+
}
|
|
3030
|
+
function hasHookCommand(value, needle) {
|
|
3031
|
+
if (!Array.isArray(value)) return false;
|
|
3032
|
+
return value.some((group) => {
|
|
3033
|
+
if (!isRecord3(group) || !Array.isArray(group.hooks)) return false;
|
|
3034
|
+
return group.hooks.some((hook) => {
|
|
3035
|
+
if (!isRecord3(hook) || typeof hook.command !== "string") return false;
|
|
3036
|
+
return hook.command.includes(needle);
|
|
3037
|
+
});
|
|
3038
|
+
});
|
|
3039
|
+
}
|
|
3040
|
+
async function pathExists2(p) {
|
|
3041
|
+
try {
|
|
3042
|
+
await fs.access(p);
|
|
3043
|
+
return true;
|
|
3044
|
+
} catch {
|
|
3045
|
+
return false;
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
function isRecord3(value) {
|
|
3049
|
+
return typeof value === "object" && value !== null;
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
// src/installers/codex.ts
|
|
3053
|
+
import { writeFile as writeFile7 } from "fs/promises";
|
|
3054
|
+
import path9 from "path";
|
|
3055
|
+
async function installCodex(projectRoot, options) {
|
|
3056
|
+
await installMancodeCore(projectRoot);
|
|
3057
|
+
const agentsPath = path9.join(projectRoot, "AGENTS.md");
|
|
3058
|
+
const existing = await readTextIfExists(agentsPath);
|
|
3059
|
+
const sharedContent = await generateSharedContent(projectRoot, {
|
|
3060
|
+
platform: "codex",
|
|
3061
|
+
displayName: "Codex (ChatGPT desktop/CLI)",
|
|
3062
|
+
capabilities: {
|
|
3063
|
+
slashCommands: "partial",
|
|
3064
|
+
subagents: false,
|
|
3065
|
+
hooks: false,
|
|
3066
|
+
skills: "agents-skills"
|
|
3067
|
+
},
|
|
3068
|
+
minimal: options.minimal,
|
|
3069
|
+
techStack: options.techStack,
|
|
3070
|
+
uiLibrary: options.uiLibrary,
|
|
3071
|
+
projectProfile: options.projectProfile
|
|
3072
|
+
});
|
|
3073
|
+
const block = [
|
|
3074
|
+
DEFAULT_MANCODE_START_MARKER,
|
|
3075
|
+
"<!-- Managed by mancode. Do not edit this block manually. -->",
|
|
3076
|
+
"",
|
|
3077
|
+
"# mancode Configuration",
|
|
3078
|
+
"",
|
|
3079
|
+
sharedContent.trim(),
|
|
3080
|
+
DEFAULT_MANCODE_END_MARKER
|
|
3081
|
+
].join("\n");
|
|
3082
|
+
await writeFile7(agentsPath, replaceManagedBlock(existing, block), "utf-8");
|
|
3083
|
+
await installCodexSkills(projectRoot, options.minimal ?? false);
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
// src/installers/copilot.ts
|
|
3087
|
+
import { mkdir as mkdir6, writeFile as writeFile8 } from "fs/promises";
|
|
3088
|
+
import path10 from "path";
|
|
3089
|
+
async function installCopilot(projectRoot, options) {
|
|
3090
|
+
await installMancodeCore(projectRoot);
|
|
3091
|
+
const githubDir = path10.join(projectRoot, ".github");
|
|
3092
|
+
await mkdir6(githubDir, { recursive: true });
|
|
3093
|
+
const instructionsPath = path10.join(githubDir, "copilot-instructions.md");
|
|
3094
|
+
const existing = await readTextIfExists(instructionsPath);
|
|
3095
|
+
const sharedContent = await generateSharedContent(projectRoot, {
|
|
3096
|
+
platform: "copilot",
|
|
3097
|
+
displayName: "GitHub Copilot",
|
|
3098
|
+
capabilities: {
|
|
3099
|
+
slashCommands: "none",
|
|
3100
|
+
subagents: false,
|
|
3101
|
+
hooks: false,
|
|
3102
|
+
skills: "instructions"
|
|
3103
|
+
},
|
|
3104
|
+
minimal: true,
|
|
3105
|
+
techStack: options.techStack,
|
|
3106
|
+
uiLibrary: options.uiLibrary,
|
|
3107
|
+
projectProfile: options.projectProfile
|
|
3108
|
+
});
|
|
3109
|
+
const sections = [
|
|
3110
|
+
DEFAULT_MANCODE_START_MARKER,
|
|
3111
|
+
"<!-- Managed by mancode. Do not edit this block manually. -->",
|
|
3112
|
+
"",
|
|
3113
|
+
"# mancode for GitHub Copilot",
|
|
3114
|
+
"",
|
|
3115
|
+
sharedContent.trim()
|
|
3116
|
+
];
|
|
3117
|
+
if (!options.minimal) {
|
|
3118
|
+
sections.push("", renderCopilotPromptConventions());
|
|
3119
|
+
}
|
|
3120
|
+
sections.push(DEFAULT_MANCODE_END_MARKER);
|
|
3121
|
+
await writeFile8(
|
|
3122
|
+
instructionsPath,
|
|
3123
|
+
replaceManagedBlock(existing, sections.join("\n")),
|
|
3124
|
+
"utf-8"
|
|
3125
|
+
);
|
|
3126
|
+
await installCopilotPrompts(projectRoot, options.minimal ?? false);
|
|
3127
|
+
}
|
|
3128
|
+
function renderCopilotPromptConventions() {
|
|
3129
|
+
return [
|
|
3130
|
+
"## mancode Prompt Conventions",
|
|
3131
|
+
"",
|
|
3132
|
+
"GitHub Copilot does not provide native mancode slash commands, hooks, or isolated subagents. Treat these names as user prompt conventions:",
|
|
3133
|
+
"",
|
|
3134
|
+
"- man: use progressive research, planning, implementation, verification, and bounded risk-based review.",
|
|
3135
|
+
"- manba: diagnose bugs and validate real user flows or regressions.",
|
|
3136
|
+
"- manteam: read team memory and write handoff-friendly summaries.",
|
|
3137
|
+
"- manps: prefer `mancode manps [area]` before cleanup.",
|
|
3138
|
+
"- mansolo: exit any active mode and return to default solo behavior.",
|
|
3139
|
+
"",
|
|
3140
|
+
"These correspond to prompt files in `.github/prompts/`. Select the matching prompt to activate a mode."
|
|
3141
|
+
].join("\n");
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3144
|
+
// src/installers/registry.ts
|
|
3145
|
+
var PLATFORM_INSTALLERS = {
|
|
3146
|
+
"claude-code": {
|
|
3147
|
+
name: "claude-code",
|
|
3148
|
+
displayName: "Claude Code",
|
|
3149
|
+
capabilities: {
|
|
3150
|
+
slashCommands: "native",
|
|
3151
|
+
subagents: true,
|
|
3152
|
+
hooks: true,
|
|
3153
|
+
skills: "native"
|
|
3154
|
+
},
|
|
3155
|
+
install: installClaudeCode
|
|
3156
|
+
},
|
|
3157
|
+
cursor: {
|
|
3158
|
+
name: "cursor",
|
|
3159
|
+
displayName: "Cursor",
|
|
3160
|
+
capabilities: {
|
|
3161
|
+
slashCommands: "native",
|
|
3162
|
+
subagents: false,
|
|
3163
|
+
hooks: false,
|
|
3164
|
+
skills: "rules"
|
|
3165
|
+
},
|
|
3166
|
+
install: installCursor
|
|
3167
|
+
},
|
|
3168
|
+
codex: {
|
|
3169
|
+
name: "codex",
|
|
3170
|
+
displayName: "Codex (ChatGPT desktop/CLI)",
|
|
3171
|
+
capabilities: {
|
|
3172
|
+
slashCommands: "partial",
|
|
3173
|
+
subagents: false,
|
|
3174
|
+
hooks: false,
|
|
3175
|
+
skills: "agents-skills"
|
|
3176
|
+
},
|
|
3177
|
+
install: installCodex
|
|
3178
|
+
},
|
|
3179
|
+
copilot: {
|
|
3180
|
+
name: "copilot",
|
|
3181
|
+
displayName: "GitHub Copilot",
|
|
3182
|
+
capabilities: {
|
|
3183
|
+
slashCommands: "none",
|
|
3184
|
+
subagents: false,
|
|
3185
|
+
hooks: false,
|
|
3186
|
+
skills: "instructions"
|
|
3187
|
+
},
|
|
3188
|
+
install: installCopilot
|
|
3189
|
+
},
|
|
3190
|
+
zcode: {
|
|
3191
|
+
name: "zcode",
|
|
3192
|
+
displayName: "ZCode",
|
|
3193
|
+
capabilities: {
|
|
3194
|
+
slashCommands: "partial",
|
|
3195
|
+
subagents: false,
|
|
3196
|
+
hooks: false,
|
|
3197
|
+
skills: "agents-skills"
|
|
3198
|
+
},
|
|
3199
|
+
install: installZcode
|
|
3200
|
+
}
|
|
3201
|
+
};
|
|
3202
|
+
function getPlatformInstaller(platform) {
|
|
3203
|
+
if (!isPlatformName(platform)) return null;
|
|
3204
|
+
return PLATFORM_INSTALLERS[platform];
|
|
3205
|
+
}
|
|
3206
|
+
function getPlatformInstallers() {
|
|
3207
|
+
return Object.values(PLATFORM_INSTALLERS);
|
|
3208
|
+
}
|
|
3209
|
+
function formatPlatformName(platform) {
|
|
3210
|
+
return getPlatformInstaller(platform)?.displayName ?? platform;
|
|
3211
|
+
}
|
|
3212
|
+
function isPlatformName(platform) {
|
|
3213
|
+
return Object.prototype.hasOwnProperty.call(PLATFORM_INSTALLERS, platform);
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
// src/system/detect-team.ts
|
|
3217
|
+
import { execFile } from "child_process";
|
|
3218
|
+
import { access as access2 } from "fs/promises";
|
|
3219
|
+
import path11 from "path";
|
|
3220
|
+
import process2 from "process";
|
|
3221
|
+
import { promisify } from "util";
|
|
3222
|
+
var execFileAsync = promisify(execFile);
|
|
3223
|
+
var NO_TEAM = {
|
|
3224
|
+
isTeam: false,
|
|
3225
|
+
contributors: 1,
|
|
3226
|
+
recentActive: 1,
|
|
3227
|
+
hasRemote: false
|
|
3228
|
+
};
|
|
3229
|
+
var REMOTE_PATTERN = /github\.com|gitlab\.com|bitbucket\.org/;
|
|
3230
|
+
var GIT_ENV = {
|
|
3231
|
+
...process2.env,
|
|
3232
|
+
GIT_PAGER: "cat",
|
|
3233
|
+
PAGER: "cat",
|
|
3234
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
2911
3235
|
};
|
|
2912
3236
|
async function detectTeamStatus(projectRoot) {
|
|
2913
3237
|
try {
|
|
2914
|
-
await access2(
|
|
3238
|
+
await access2(path11.join(projectRoot, ".git"));
|
|
2915
3239
|
const inside = await runGit(projectRoot, [
|
|
2916
3240
|
"rev-parse",
|
|
2917
3241
|
"--is-inside-work-tree"
|
|
@@ -2969,8 +3293,8 @@ async function detectSystemDeps(env = process3.env) {
|
|
|
2969
3293
|
|
|
2970
3294
|
// src/system/init-onboarding.ts
|
|
2971
3295
|
import { execFileSync } from "child_process";
|
|
2972
|
-
import { promises as
|
|
2973
|
-
import
|
|
3296
|
+
import { promises as fs2 } from "fs";
|
|
3297
|
+
import path12 from "path";
|
|
2974
3298
|
import process4 from "process";
|
|
2975
3299
|
import { stdin, stdout } from "process";
|
|
2976
3300
|
import { createInterface } from "readline/promises";
|
|
@@ -3075,7 +3399,7 @@ async function detectPlatformHints(rootDir, environment = process4.env) {
|
|
|
3075
3399
|
hints.add("copilot");
|
|
3076
3400
|
const exists = async (relative) => {
|
|
3077
3401
|
try {
|
|
3078
|
-
await
|
|
3402
|
+
await fs2.access(path12.join(rootDir, relative));
|
|
3079
3403
|
return true;
|
|
3080
3404
|
} catch {
|
|
3081
3405
|
return false;
|
|
@@ -3151,7 +3475,7 @@ function createTerminalPrompter() {
|
|
|
3151
3475
|
|
|
3152
3476
|
// src/system/project-profile.ts
|
|
3153
3477
|
import { readFile as readFile7, readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
3154
|
-
import
|
|
3478
|
+
import path13 from "path";
|
|
3155
3479
|
var PROJECT_MANIFESTS = [
|
|
3156
3480
|
"package.json",
|
|
3157
3481
|
"pyproject.toml",
|
|
@@ -3184,12 +3508,12 @@ async function detectProjectProfile(projectRoot) {
|
|
|
3184
3508
|
"data",
|
|
3185
3509
|
"notebooks"
|
|
3186
3510
|
]);
|
|
3187
|
-
const packageJson = await readJson2(
|
|
3511
|
+
const packageJson = await readJson2(path13.join(projectRoot, "package.json"));
|
|
3188
3512
|
const [pubspec, pythonManifests] = await Promise.all([
|
|
3189
|
-
readText(
|
|
3513
|
+
readText(path13.join(projectRoot, "pubspec.yaml")),
|
|
3190
3514
|
Promise.all([
|
|
3191
|
-
readText(
|
|
3192
|
-
readText(
|
|
3515
|
+
readText(path13.join(projectRoot, "pyproject.toml")),
|
|
3516
|
+
readText(path13.join(projectRoot, "requirements.txt"))
|
|
3193
3517
|
]).then((parts) => parts.filter(Boolean).join("\n"))
|
|
3194
3518
|
]);
|
|
3195
3519
|
const deps = Object.keys({
|
|
@@ -3198,7 +3522,7 @@ async function detectProjectProfile(projectRoot) {
|
|
|
3198
3522
|
...packageJson?.peerDependencies
|
|
3199
3523
|
});
|
|
3200
3524
|
const flutter = isFlutterProject(pubspec);
|
|
3201
|
-
const shadcn = entries.includes("components.json") || deps.some((dep) => dep.startsWith("@radix-ui/")) && await isDirectory(
|
|
3525
|
+
const shadcn = entries.includes("components.json") || deps.some((dep) => dep.startsWith("@radix-ui/")) && await isDirectory(path13.join(projectRoot, "src", "components", "ui"));
|
|
3202
3526
|
const languages = inferLanguages(manifests, entries);
|
|
3203
3527
|
const frameworks = inferFrameworks(
|
|
3204
3528
|
deps,
|
|
@@ -3241,7 +3565,7 @@ async function existingDirs(root, candidates) {
|
|
|
3241
3565
|
const found = [];
|
|
3242
3566
|
for (const candidate of candidates) {
|
|
3243
3567
|
try {
|
|
3244
|
-
if ((await stat2(
|
|
3568
|
+
if ((await stat2(path13.join(root, candidate))).isDirectory())
|
|
3245
3569
|
found.push(candidate);
|
|
3246
3570
|
} catch {
|
|
3247
3571
|
}
|
|
@@ -3415,8 +3739,8 @@ function isFlutterProject(pubspec) {
|
|
|
3415
3739
|
}
|
|
3416
3740
|
|
|
3417
3741
|
// src/system/scan-aesthetics.ts
|
|
3418
|
-
import { promises as
|
|
3419
|
-
import
|
|
3742
|
+
import { promises as fs3 } from "fs";
|
|
3743
|
+
import path14 from "path";
|
|
3420
3744
|
var MAX_COMPONENT_SCAN_DEPTH = 12;
|
|
3421
3745
|
var MAX_COMPONENT_FILES = 2e3;
|
|
3422
3746
|
async function scanAesthetics(projectRoot, uiLibrary = null) {
|
|
@@ -3429,7 +3753,7 @@ async function scanAesthetics(projectRoot, uiLibrary = null) {
|
|
|
3429
3753
|
const cssScan = await scanCssVariables(projectRoot);
|
|
3430
3754
|
if (configResult) {
|
|
3431
3755
|
sourceFiles.push(configResult.relPath);
|
|
3432
|
-
const content = await
|
|
3756
|
+
const content = await fs3.readFile(configResult.absPath, "utf-8");
|
|
3433
3757
|
const inspectableContent = stripJsComments(content);
|
|
3434
3758
|
const themeBlock = findKeyBlock(inspectableContent, "theme");
|
|
3435
3759
|
if (themeBlock) {
|
|
@@ -3452,7 +3776,7 @@ async function scanAesthetics(projectRoot, uiLibrary = null) {
|
|
|
3452
3776
|
} else if (await hasTailwindDep(projectRoot) || uiLibrary) {
|
|
3453
3777
|
matchLevel = "low";
|
|
3454
3778
|
}
|
|
3455
|
-
if (uiLibrary && await
|
|
3779
|
+
if (uiLibrary && await pathExists3(path14.join(projectRoot, "package.json"))) {
|
|
3456
3780
|
sourceFiles.push("package.json");
|
|
3457
3781
|
}
|
|
3458
3782
|
sourceFiles.push(...cssScan.sourceFiles);
|
|
@@ -3477,8 +3801,8 @@ async function findTailwindConfig(projectRoot) {
|
|
|
3477
3801
|
"tailwind.config.mjs"
|
|
3478
3802
|
];
|
|
3479
3803
|
for (const name of candidates) {
|
|
3480
|
-
const absPath =
|
|
3481
|
-
if (await
|
|
3804
|
+
const absPath = path14.join(projectRoot, name);
|
|
3805
|
+
if (await pathExists3(absPath)) {
|
|
3482
3806
|
return { absPath, relPath: name };
|
|
3483
3807
|
}
|
|
3484
3808
|
}
|
|
@@ -3704,8 +4028,8 @@ async function scanComponents(projectRoot) {
|
|
|
3704
4028
|
const names = /* @__PURE__ */ new Set();
|
|
3705
4029
|
let visitedFiles = 0;
|
|
3706
4030
|
for (const relRoot of roots) {
|
|
3707
|
-
const absRoot =
|
|
3708
|
-
if (!await
|
|
4031
|
+
const absRoot = path14.join(projectRoot, relRoot);
|
|
4032
|
+
if (!await pathExists3(absRoot)) continue;
|
|
3709
4033
|
visitedFiles = await collectComponentNames(absRoot, names, 0, visitedFiles);
|
|
3710
4034
|
if (visitedFiles >= MAX_COMPONENT_FILES) break;
|
|
3711
4035
|
}
|
|
@@ -3718,16 +4042,16 @@ async function collectComponentNames(dir, names, depth, visitedFiles) {
|
|
|
3718
4042
|
let fileCount = visitedFiles;
|
|
3719
4043
|
let entries;
|
|
3720
4044
|
try {
|
|
3721
|
-
entries = await
|
|
4045
|
+
entries = await fs3.readdir(dir);
|
|
3722
4046
|
} catch {
|
|
3723
4047
|
return fileCount;
|
|
3724
4048
|
}
|
|
3725
4049
|
for (const entry of entries) {
|
|
3726
4050
|
if (fileCount >= MAX_COMPONENT_FILES) return fileCount;
|
|
3727
|
-
const abs =
|
|
4051
|
+
const abs = path14.join(dir, entry);
|
|
3728
4052
|
let info;
|
|
3729
4053
|
try {
|
|
3730
|
-
info = await
|
|
4054
|
+
info = await fs3.lstat(abs);
|
|
3731
4055
|
} catch {
|
|
3732
4056
|
continue;
|
|
3733
4057
|
}
|
|
@@ -3745,7 +4069,7 @@ async function collectComponentNames(dir, names, depth, visitedFiles) {
|
|
|
3745
4069
|
""
|
|
3746
4070
|
);
|
|
3747
4071
|
if (base === "index") {
|
|
3748
|
-
base =
|
|
4072
|
+
base = path14.basename(dir);
|
|
3749
4073
|
}
|
|
3750
4074
|
if (base.startsWith(".")) continue;
|
|
3751
4075
|
const componentName = toPascalCase(base);
|
|
@@ -3767,9 +4091,9 @@ async function scanCssVariables(projectRoot) {
|
|
|
3767
4091
|
const variables = {};
|
|
3768
4092
|
const sourceFiles = [];
|
|
3769
4093
|
for (const relPath of candidates) {
|
|
3770
|
-
const absPath =
|
|
3771
|
-
if (!await
|
|
3772
|
-
const content = await
|
|
4094
|
+
const absPath = path14.join(projectRoot, relPath);
|
|
4095
|
+
if (!await pathExists3(absPath)) continue;
|
|
4096
|
+
const content = await fs3.readFile(absPath, "utf-8");
|
|
3773
4097
|
const found = extractCssVariables(content);
|
|
3774
4098
|
if (Object.keys(found).length === 0) continue;
|
|
3775
4099
|
Object.assign(variables, found);
|
|
@@ -3812,8 +4136,8 @@ function toPascalCase(value) {
|
|
|
3812
4136
|
}
|
|
3813
4137
|
async function hasTailwindDep(projectRoot) {
|
|
3814
4138
|
try {
|
|
3815
|
-
const raw = await
|
|
3816
|
-
|
|
4139
|
+
const raw = await fs3.readFile(
|
|
4140
|
+
path14.join(projectRoot, "package.json"),
|
|
3817
4141
|
"utf-8"
|
|
3818
4142
|
);
|
|
3819
4143
|
const pkg = JSON.parse(raw);
|
|
@@ -3827,9 +4151,9 @@ async function hasTailwindDep(projectRoot) {
|
|
|
3827
4151
|
function escapeRegex(s) {
|
|
3828
4152
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3829
4153
|
}
|
|
3830
|
-
async function
|
|
4154
|
+
async function pathExists3(p) {
|
|
3831
4155
|
try {
|
|
3832
|
-
await
|
|
4156
|
+
await fs3.access(p);
|
|
3833
4157
|
return true;
|
|
3834
4158
|
} catch {
|
|
3835
4159
|
return false;
|
|
@@ -3844,9 +4168,9 @@ var EXIT_USER_CANCEL = 3;
|
|
|
3844
4168
|
var EXIT_INIT_FAILED = 5;
|
|
3845
4169
|
var DEFAULT_INIT_PLATFORM = "claude-code";
|
|
3846
4170
|
async function init(rootDir = process5.cwd(), options = {}) {
|
|
3847
|
-
const mancodeDir =
|
|
3848
|
-
const stateFile =
|
|
3849
|
-
const wasInitialized = await
|
|
4171
|
+
const mancodeDir = path15.join(rootDir, ".mancode");
|
|
4172
|
+
const stateFile = path15.join(mancodeDir, "state.json");
|
|
4173
|
+
const wasInitialized = await pathExists4(stateFile);
|
|
3850
4174
|
let mutationSnapshots = [];
|
|
3851
4175
|
let directorySnapshots = [];
|
|
3852
4176
|
const locale = detectInitLocale(options.lang);
|
|
@@ -3882,7 +4206,7 @@ async function init(rootDir = process5.cwd(), options = {}) {
|
|
|
3882
4206
|
)
|
|
3883
4207
|
);
|
|
3884
4208
|
}
|
|
3885
|
-
if (!await
|
|
4209
|
+
if (!await pathExists4(rootDir)) {
|
|
3886
4210
|
console.error(
|
|
3887
4211
|
localize(
|
|
3888
4212
|
locale,
|
|
@@ -3892,26 +4216,28 @@ async function init(rootDir = process5.cwd(), options = {}) {
|
|
|
3892
4216
|
);
|
|
3893
4217
|
return EXIT_NOT_A_PROJECT_DIR;
|
|
3894
4218
|
}
|
|
3895
|
-
const isGitRepo = await
|
|
4219
|
+
const isGitRepo = await pathExists4(path15.join(rootDir, ".git"));
|
|
3896
4220
|
const hasManifest = await hasProjectManifest(rootDir);
|
|
3897
4221
|
let isGenericProject = false;
|
|
3898
4222
|
if (!isGitRepo && !hasManifest) {
|
|
3899
4223
|
const genericSafety = await canInitializeGenericProject(rootDir);
|
|
3900
|
-
if (!genericSafety.ok) {
|
|
4224
|
+
if (!genericSafety.ok && !(wasInitialized && genericSafety.reason === "nonempty")) {
|
|
3901
4225
|
printNotProjectDirectory(rootDir, locale, genericSafety.reason);
|
|
3902
4226
|
return EXIT_NOT_A_PROJECT_DIR;
|
|
3903
4227
|
}
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
if (
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
4228
|
+
if (!wasInitialized) {
|
|
4229
|
+
const prompter = options.prompter ?? (options.interactive ? createTerminalPrompter() : null);
|
|
4230
|
+
const confirmed = options.empty || options.yes ? true : prompter ? await prompter.confirmGenericProject({ rootDir, locale }) : false;
|
|
4231
|
+
if (!confirmed) {
|
|
4232
|
+
if (options.interactive) {
|
|
4233
|
+
console.log(
|
|
4234
|
+
locale === "zh-CN" ? "\u5DF2\u53D6\u6D88\u521D\u59CB\u5316\u3002" : "Initialization cancelled."
|
|
4235
|
+
);
|
|
4236
|
+
return EXIT_USER_CANCEL;
|
|
4237
|
+
}
|
|
4238
|
+
printNotProjectDirectory(rootDir, locale, "empty");
|
|
4239
|
+
return EXIT_NOT_A_PROJECT_DIR;
|
|
3912
4240
|
}
|
|
3913
|
-
printNotProjectDirectory(rootDir, locale, "empty");
|
|
3914
|
-
return EXIT_NOT_A_PROJECT_DIR;
|
|
3915
4241
|
}
|
|
3916
4242
|
isGenericProject = true;
|
|
3917
4243
|
}
|
|
@@ -3966,9 +4292,12 @@ async function init(rootDir = process5.cwd(), options = {}) {
|
|
|
3966
4292
|
);
|
|
3967
4293
|
}
|
|
3968
4294
|
const existingPlatform = wasInitialized ? await readExistingInitPlatform(stateFile) : null;
|
|
4295
|
+
const existingConfig = wasInitialized ? await readExistingInitPreferences(mancodeDir) : { platforms: [], minimalByPlatform: {} };
|
|
4296
|
+
const recoverablePlatforms = wasInitialized && existingConfig.platforms.length === 0 ? await detectManagedInitPlatforms(rootDir) : [];
|
|
4297
|
+
const forceReinstallPlatforms = options.force && options.platform === void 0 ? existingConfig.platforms.length > 0 ? existingConfig.platforms : recoverablePlatforms : [];
|
|
3969
4298
|
const platformHints = await detectPlatformHints(rootDir);
|
|
3970
4299
|
const selectedPlatforms = await selectInitPlatforms({
|
|
3971
|
-
option: options.platform,
|
|
4300
|
+
option: options.platform ?? (forceReinstallPlatforms.length > 0 ? forceReinstallPlatforms.join(",") : void 0),
|
|
3972
4301
|
existingPlatform,
|
|
3973
4302
|
hints: platformHints,
|
|
3974
4303
|
interactive: options.interactive,
|
|
@@ -4005,13 +4334,17 @@ async function init(rootDir = process5.cwd(), options = {}) {
|
|
|
4005
4334
|
const managedFiles = getInitManagedFilePaths(rootDir, selectedPlatforms);
|
|
4006
4335
|
mutationSnapshots = await snapshotFiles(managedFiles);
|
|
4007
4336
|
directorySnapshots = await snapshotDirectories(managedFiles, [
|
|
4008
|
-
|
|
4009
|
-
|
|
4337
|
+
path15.join(mancodeDir, "workflows"),
|
|
4338
|
+
path15.join(mancodeDir, "preseason-reports")
|
|
4010
4339
|
]);
|
|
4011
4340
|
const team = await detectTeamStatus(rootDir);
|
|
4012
|
-
const
|
|
4013
|
-
|
|
4014
|
-
|
|
4341
|
+
const platformMinimal = Object.fromEntries(
|
|
4342
|
+
selectedPlatforms.map((platform) => [
|
|
4343
|
+
platform,
|
|
4344
|
+
existingConfig.minimalByPlatform[platform] ?? false
|
|
4345
|
+
])
|
|
4346
|
+
);
|
|
4347
|
+
const existingTeamMode = existingConfig.forceTeamMode === true ? "on" : existingConfig.teamMode ?? "auto";
|
|
4015
4348
|
const teamModeEnabled = options.team ?? (existingTeamMode === "on" ? true : existingTeamMode === "off" ? false : team.isTeam);
|
|
4016
4349
|
if (options.team === true) {
|
|
4017
4350
|
console.log(
|
|
@@ -4057,12 +4390,12 @@ async function init(rootDir = process5.cwd(), options = {}) {
|
|
|
4057
4390
|
await validateClaudeCodeSettings(rootDir);
|
|
4058
4391
|
}
|
|
4059
4392
|
await installMancodeCore(rootDir);
|
|
4060
|
-
await
|
|
4393
|
+
await fs4.mkdir(mancodeDir, { recursive: true });
|
|
4061
4394
|
const stateContent = `${JSON.stringify(state, null, 2)}
|
|
4062
4395
|
`;
|
|
4063
|
-
await
|
|
4064
|
-
await
|
|
4065
|
-
|
|
4396
|
+
await fs4.writeFile(stateFile, stateContent, "utf-8");
|
|
4397
|
+
await fs4.writeFile(
|
|
4398
|
+
path15.join(mancodeDir, "project-profile.json"),
|
|
4066
4399
|
`${JSON.stringify(profile, null, 2)}
|
|
4067
4400
|
`,
|
|
4068
4401
|
"utf-8"
|
|
@@ -4078,7 +4411,7 @@ async function init(rootDir = process5.cwd(), options = {}) {
|
|
|
4078
4411
|
...Object.fromEntries(
|
|
4079
4412
|
selectedPlatforms.map((platform) => [
|
|
4080
4413
|
platform,
|
|
4081
|
-
{ minimal:
|
|
4414
|
+
{ minimal: platformMinimal[platform] ?? false }
|
|
4082
4415
|
])
|
|
4083
4416
|
)
|
|
4084
4417
|
}
|
|
@@ -4099,12 +4432,12 @@ async function init(rootDir = process5.cwd(), options = {}) {
|
|
|
4099
4432
|
)
|
|
4100
4433
|
);
|
|
4101
4434
|
const tokens = await scanAesthetics(rootDir, uiLibrary);
|
|
4102
|
-
const tokensPath =
|
|
4435
|
+
const tokensPath = path15.join(
|
|
4103
4436
|
mancodeDir,
|
|
4104
4437
|
"aesthetics",
|
|
4105
4438
|
"style-tokens.json"
|
|
4106
4439
|
);
|
|
4107
|
-
await
|
|
4440
|
+
await fs4.writeFile(
|
|
4108
4441
|
tokensPath,
|
|
4109
4442
|
`${JSON.stringify(tokens, null, 2)}
|
|
4110
4443
|
`,
|
|
@@ -4165,7 +4498,7 @@ async function init(rootDir = process5.cwd(), options = {}) {
|
|
|
4165
4498
|
techStack: profileStack,
|
|
4166
4499
|
uiLibrary,
|
|
4167
4500
|
projectProfile: profile,
|
|
4168
|
-
minimal:
|
|
4501
|
+
minimal: platformMinimal[installer.name] ?? false,
|
|
4169
4502
|
force: options.force
|
|
4170
4503
|
});
|
|
4171
4504
|
}
|
|
@@ -4250,16 +4583,16 @@ async function init(rootDir = process5.cwd(), options = {}) {
|
|
|
4250
4583
|
}
|
|
4251
4584
|
}
|
|
4252
4585
|
async function updateConfigOptions(mancodeDir, patch, preserveInstalledPlatforms) {
|
|
4253
|
-
const configPath =
|
|
4586
|
+
const configPath = path15.join(mancodeDir, "config.json");
|
|
4254
4587
|
let config = {};
|
|
4255
4588
|
try {
|
|
4256
|
-
config = JSON.parse(await
|
|
4589
|
+
config = JSON.parse(await fs4.readFile(configPath, "utf-8"));
|
|
4257
4590
|
} catch {
|
|
4258
4591
|
}
|
|
4259
4592
|
const existingPlatforms = Array.isArray(config.platforms) ? config.platforms.filter(
|
|
4260
4593
|
(platform) => typeof platform === "string"
|
|
4261
4594
|
) : [];
|
|
4262
|
-
const existingPlatformOptions =
|
|
4595
|
+
const existingPlatformOptions = isRecord4(config.platformOptions) ? config.platformOptions : {};
|
|
4263
4596
|
const definedPatch = Object.fromEntries(
|
|
4264
4597
|
Object.entries(patch).filter(([, value]) => value !== void 0)
|
|
4265
4598
|
);
|
|
@@ -4273,44 +4606,60 @@ async function updateConfigOptions(mancodeDir, patch, preserveInstalledPlatforms
|
|
|
4273
4606
|
...patch.platformOptions
|
|
4274
4607
|
}
|
|
4275
4608
|
} : definedPatch;
|
|
4276
|
-
await
|
|
4609
|
+
await fs4.writeFile(
|
|
4277
4610
|
configPath,
|
|
4278
4611
|
`${JSON.stringify({ ...config, ...mergedPatch }, null, 2)}
|
|
4279
4612
|
`,
|
|
4280
4613
|
"utf-8"
|
|
4281
4614
|
);
|
|
4282
4615
|
}
|
|
4283
|
-
async function readExistingInitPreferences(mancodeDir
|
|
4616
|
+
async function readExistingInitPreferences(mancodeDir) {
|
|
4284
4617
|
try {
|
|
4285
4618
|
const config = JSON.parse(
|
|
4286
|
-
await
|
|
4619
|
+
await fs4.readFile(path15.join(mancodeDir, "config.json"), "utf-8")
|
|
4287
4620
|
);
|
|
4288
|
-
const preferences = {
|
|
4621
|
+
const preferences = {
|
|
4622
|
+
platforms: Array.isArray(config.platforms) ? config.platforms.filter(
|
|
4623
|
+
(platform) => typeof platform === "string" && getPlatformInstaller(platform) !== null
|
|
4624
|
+
) : [],
|
|
4625
|
+
minimalByPlatform: {}
|
|
4626
|
+
};
|
|
4289
4627
|
if (typeof config.forceTeamMode === "boolean") {
|
|
4290
4628
|
preferences.forceTeamMode = config.forceTeamMode;
|
|
4291
4629
|
}
|
|
4292
4630
|
if (config.teamMode === "auto" || config.teamMode === "on" || config.teamMode === "off") {
|
|
4293
4631
|
preferences.teamMode = config.teamMode;
|
|
4294
4632
|
}
|
|
4295
|
-
if (
|
|
4296
|
-
const
|
|
4297
|
-
|
|
4298
|
-
|
|
4633
|
+
if (isRecord4(config.platformOptions)) {
|
|
4634
|
+
for (const platform of preferences.platforms) {
|
|
4635
|
+
const platformOptions = config.platformOptions[platform];
|
|
4636
|
+
if (isRecord4(platformOptions) && platformOptions.minimal === true) {
|
|
4637
|
+
preferences.minimalByPlatform[platform] = true;
|
|
4638
|
+
}
|
|
4299
4639
|
}
|
|
4300
4640
|
}
|
|
4301
4641
|
return preferences;
|
|
4302
4642
|
} catch {
|
|
4303
|
-
return {};
|
|
4643
|
+
return { platforms: [], minimalByPlatform: {} };
|
|
4304
4644
|
}
|
|
4305
4645
|
}
|
|
4306
4646
|
async function readExistingInitPlatform(stateFile) {
|
|
4307
4647
|
try {
|
|
4308
|
-
const state = JSON.parse(await
|
|
4648
|
+
const state = JSON.parse(await fs4.readFile(stateFile, "utf-8"));
|
|
4309
4649
|
return typeof state.platform === "string" && getPlatformInstaller(state.platform) ? state.platform : null;
|
|
4310
4650
|
} catch {
|
|
4311
4651
|
return null;
|
|
4312
4652
|
}
|
|
4313
4653
|
}
|
|
4654
|
+
async function detectManagedInitPlatforms(rootDir) {
|
|
4655
|
+
const detected = await Promise.all(
|
|
4656
|
+
getPlatformInstallers().map(async (platform) => ({
|
|
4657
|
+
name: platform.name,
|
|
4658
|
+
present: await isPlatformPresent(rootDir, platform.name)
|
|
4659
|
+
}))
|
|
4660
|
+
);
|
|
4661
|
+
return detected.filter((platform) => platform.present).map(({ name }) => name);
|
|
4662
|
+
}
|
|
4314
4663
|
async function selectInitPlatforms(input) {
|
|
4315
4664
|
if (input.option !== void 0) {
|
|
4316
4665
|
return parsePlatformSelection(input.option);
|
|
@@ -4333,17 +4682,17 @@ async function selectInitPlatforms(input) {
|
|
|
4333
4682
|
}
|
|
4334
4683
|
async function hasProjectManifest(rootDir) {
|
|
4335
4684
|
for (const name of PROJECT_MANIFESTS) {
|
|
4336
|
-
if (await
|
|
4685
|
+
if (await pathExists4(path15.join(rootDir, name))) return true;
|
|
4337
4686
|
}
|
|
4338
4687
|
return false;
|
|
4339
4688
|
}
|
|
4340
4689
|
async function canInitializeGenericProject(rootDir) {
|
|
4341
|
-
const resolved =
|
|
4342
|
-
if (resolved ===
|
|
4690
|
+
const resolved = path15.resolve(rootDir);
|
|
4691
|
+
if (resolved === path15.parse(resolved).root || resolved === path15.resolve(os.homedir())) {
|
|
4343
4692
|
return { ok: false, reason: "unsafe" };
|
|
4344
4693
|
}
|
|
4345
4694
|
try {
|
|
4346
|
-
const entries = await
|
|
4695
|
+
const entries = await fs4.readdir(resolved);
|
|
4347
4696
|
const meaningfulEntries = entries.filter(
|
|
4348
4697
|
(entry) => ![".DS_Store", "Thumbs.db", ".gitkeep"].includes(entry)
|
|
4349
4698
|
);
|
|
@@ -4399,432 +4748,164 @@ function getInitManagedFilePaths(rootDir, platforms) {
|
|
|
4399
4748
|
if (platforms.includes("claude-code")) {
|
|
4400
4749
|
files.push(
|
|
4401
4750
|
".mancode/hooks/session-start.sh",
|
|
4402
|
-
".mancode/hooks/user-prompt-submit.sh",
|
|
4403
|
-
".claude/settings.json",
|
|
4404
|
-
".claude/skills/man8/SKILL.md",
|
|
4405
|
-
".claude/skills/
|
|
4406
|
-
".claude/skills/
|
|
4407
|
-
".claude/skills/mancode-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
files.push(`.claude/agents/${agent.name}.md`);
|
|
4417
|
-
}
|
|
4418
|
-
}
|
|
4419
|
-
if (platforms.includes("cursor")) {
|
|
4420
|
-
files.push(".cursor/rules/mancode-man8.mdc", ".cursor/commands/man8.md");
|
|
4421
|
-
for (const fileName of MANCODE_CURSOR_RULE_FILES) {
|
|
4422
|
-
files.push(`.cursor/rules/${fileName}`);
|
|
4423
|
-
}
|
|
4424
|
-
for (const mode of MODE_NAMES) {
|
|
4425
|
-
files.push(`.cursor/commands/${mode}.md`);
|
|
4426
|
-
}
|
|
4427
|
-
}
|
|
4428
|
-
if (platforms.includes("codex") || platforms.includes("zcode")) {
|
|
4429
|
-
files.push("AGENTS.md", ".agents/skills/man8/SKILL.md");
|
|
4430
|
-
for (const mode of MODE_NAMES) {
|
|
4431
|
-
files.push(`.agents/skills/${mode}/SKILL.md`);
|
|
4432
|
-
}
|
|
4433
|
-
}
|
|
4434
|
-
if (platforms.includes("codex")) {
|
|
4435
|
-
files.push(".codex/skills/man8/SKILL.md");
|
|
4436
|
-
for (const mode of MODE_NAMES) {
|
|
4437
|
-
files.push(`.codex/skills/${mode}/SKILL.md`);
|
|
4438
|
-
}
|
|
4439
|
-
}
|
|
4440
|
-
if (platforms.includes("zcode")) {
|
|
4441
|
-
files.push(".zcode/skills/man8/SKILL.md");
|
|
4442
|
-
for (const mode of MODE_NAMES) {
|
|
4443
|
-
files.push(`.zcode/skills/${mode}/SKILL.md`);
|
|
4444
|
-
}
|
|
4445
|
-
}
|
|
4446
|
-
if (platforms.includes("copilot")) {
|
|
4447
|
-
files.push(
|
|
4448
|
-
".github/copilot-instructions.md",
|
|
4449
|
-
".github/prompts/man8.prompt.md"
|
|
4450
|
-
);
|
|
4451
|
-
for (const mode of MODE_NAMES) {
|
|
4452
|
-
files.push(`.github/prompts/${mode}.prompt.md`);
|
|
4453
|
-
}
|
|
4454
|
-
}
|
|
4455
|
-
return [...new Set(files.map((file) => path14.join(rootDir, file)))];
|
|
4456
|
-
}
|
|
4457
|
-
async function snapshotFiles(filePaths) {
|
|
4458
|
-
return Promise.all(
|
|
4459
|
-
filePaths.map(async (filePath) => {
|
|
4460
|
-
try {
|
|
4461
|
-
return { filePath, content: await fs3.readFile(filePath, "utf-8") };
|
|
4462
|
-
} catch (error) {
|
|
4463
|
-
if (isNodeError3(error) && error.code === "ENOENT") {
|
|
4464
|
-
return { filePath, content: null };
|
|
4465
|
-
}
|
|
4466
|
-
throw error;
|
|
4467
|
-
}
|
|
4468
|
-
})
|
|
4469
|
-
);
|
|
4470
|
-
}
|
|
4471
|
-
async function snapshotDirectories(filePaths, additionalDirectories = []) {
|
|
4472
|
-
const directories = new Set(additionalDirectories);
|
|
4473
|
-
for (const filePath of filePaths) {
|
|
4474
|
-
let current = path14.dirname(filePath);
|
|
4475
|
-
while (current !== path14.dirname(current)) {
|
|
4476
|
-
directories.add(current);
|
|
4477
|
-
current = path14.dirname(current);
|
|
4478
|
-
}
|
|
4479
|
-
}
|
|
4480
|
-
return Promise.all(
|
|
4481
|
-
[...directories].map(async (dirPath) => ({
|
|
4482
|
-
dirPath,
|
|
4483
|
-
existed: await pathExists3(dirPath)
|
|
4484
|
-
}))
|
|
4485
|
-
);
|
|
4486
|
-
}
|
|
4487
|
-
async function restoreFiles(snapshots) {
|
|
4488
|
-
for (const snapshot of snapshots) {
|
|
4489
|
-
if (snapshot.content === null) {
|
|
4490
|
-
await fs3.rm(snapshot.filePath, { force: true });
|
|
4491
|
-
continue;
|
|
4492
|
-
}
|
|
4493
|
-
await fs3.mkdir(path14.dirname(snapshot.filePath), { recursive: true });
|
|
4494
|
-
await fs3.writeFile(snapshot.filePath, snapshot.content, "utf-8");
|
|
4495
|
-
}
|
|
4496
|
-
}
|
|
4497
|
-
async function removeNewEmptyDirectories(snapshots) {
|
|
4498
|
-
const candidates = snapshots.filter((snapshot) => !snapshot.existed).sort((a, b) => b.dirPath.length - a.dirPath.length);
|
|
4499
|
-
for (const { dirPath } of candidates) {
|
|
4500
|
-
try {
|
|
4501
|
-
await fs3.rmdir(dirPath);
|
|
4502
|
-
} catch {
|
|
4503
|
-
}
|
|
4504
|
-
}
|
|
4505
|
-
}
|
|
4506
|
-
function isRecord3(value) {
|
|
4507
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4508
|
-
}
|
|
4509
|
-
function isNodeError3(error) {
|
|
4510
|
-
return error instanceof Error && "code" in error;
|
|
4511
|
-
}
|
|
4512
|
-
function printPlatformCreatedFiles(platform, locale) {
|
|
4513
|
-
if (platform === "claude-code") {
|
|
4514
|
-
console.log(
|
|
4515
|
-
localize(
|
|
4516
|
-
locale,
|
|
4517
|
-
" .claude/settings.json # hook \u6CE8\u518C",
|
|
4518
|
-
" .claude/settings.json # hook registration"
|
|
4519
|
-
)
|
|
4520
|
-
);
|
|
4521
|
-
console.log(
|
|
4522
|
-
localize(
|
|
4523
|
-
locale,
|
|
4524
|
-
" .claude/skills/ # solo + MVP-2 slash skills",
|
|
4525
|
-
" .claude/skills/ # solo + MVP-2 slash skills"
|
|
4526
|
-
)
|
|
4527
|
-
);
|
|
4528
|
-
return;
|
|
4529
|
-
}
|
|
4530
|
-
if (platform === "cursor") {
|
|
4531
|
-
console.log(" .cursor/rules/ # Cursor project rules");
|
|
4532
|
-
return;
|
|
4533
|
-
}
|
|
4534
|
-
if (platform === "codex") {
|
|
4535
|
-
console.log(" AGENTS.md # Codex managed block");
|
|
4536
|
-
console.log(" .agents/skills/ # Codex mode skills");
|
|
4537
|
-
return;
|
|
4538
|
-
}
|
|
4539
|
-
if (platform === "zcode") {
|
|
4540
|
-
console.log(" AGENTS.md # ZCode managed block");
|
|
4541
|
-
console.log(" .agents/skills/ # ZCode mode skills");
|
|
4542
|
-
return;
|
|
4543
|
-
}
|
|
4544
|
-
console.log(" .github/copilot-instructions.md # Copilot instructions");
|
|
4545
|
-
}
|
|
4546
|
-
function localize(locale, chinese, english) {
|
|
4547
|
-
return locale === "zh-CN" ? chinese : english;
|
|
4548
|
-
}
|
|
4549
|
-
async function pathExists3(p) {
|
|
4550
|
-
try {
|
|
4551
|
-
await fs3.access(p);
|
|
4552
|
-
return true;
|
|
4553
|
-
} catch {
|
|
4554
|
-
return false;
|
|
4555
|
-
}
|
|
4556
|
-
}
|
|
4557
|
-
|
|
4558
|
-
// src/commands/install.ts
|
|
4559
|
-
import { promises as fs5 } from "fs";
|
|
4560
|
-
import path16 from "path";
|
|
4561
|
-
import process6 from "process";
|
|
4562
|
-
|
|
4563
|
-
// src/installers/platform-status.ts
|
|
4564
|
-
import { promises as fs4 } from "fs";
|
|
4565
|
-
import path15 from "path";
|
|
4566
|
-
async function checkPlatformStatus(rootDir, platform, installed) {
|
|
4567
|
-
const readiness = await checkPlatformReadiness(rootDir, platform);
|
|
4568
|
-
return {
|
|
4569
|
-
installed,
|
|
4570
|
-
ready: installed && readiness.ready,
|
|
4571
|
-
target: readiness.target,
|
|
4572
|
-
detail: describeStatus(installed, readiness)
|
|
4573
|
-
};
|
|
4574
|
-
}
|
|
4575
|
-
async function checkPlatformReadiness(rootDir, platform) {
|
|
4576
|
-
if (platform === "claude-code") {
|
|
4577
|
-
const [hasSoloSkill, registered, hasHookFiles] = await Promise.all([
|
|
4578
|
-
fileMatches(
|
|
4579
|
-
path15.join(rootDir, ".claude", "skills", "solo", "SKILL.md"),
|
|
4580
|
-
(content) => isGeneratedClaudeSkill(content, "solo")
|
|
4581
|
-
),
|
|
4582
|
-
claudeHooksRegistered(rootDir),
|
|
4583
|
-
pathsExist([
|
|
4584
|
-
path15.join(rootDir, ".mancode", "hooks", "session-start.mjs"),
|
|
4585
|
-
path15.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
|
|
4586
|
-
])
|
|
4587
|
-
]);
|
|
4588
|
-
const present = hasSoloSkill && registered && hasHookFiles;
|
|
4589
|
-
const minimal = await isPlatformMinimal(rootDir, "claude-code");
|
|
4590
|
-
const fullContentReady = minimal ? true : await claudeFullContentReady(rootDir);
|
|
4591
|
-
return {
|
|
4592
|
-
present,
|
|
4593
|
-
ready: present && fullContentReady,
|
|
4594
|
-
target: ".claude/",
|
|
4595
|
-
readyDetail: present && fullContentReady ? minimal ? "solo skill and hooks ready" : "skills, agents, and hooks ready" : "skills, agents, hook files, or hook registration are incomplete"
|
|
4596
|
-
};
|
|
4597
|
-
}
|
|
4598
|
-
if (platform === "cursor") {
|
|
4599
|
-
const hasCoreRules = await allManagedSkills(
|
|
4600
|
-
MANCODE_CURSOR_CORE_RULE_FILES.map(
|
|
4601
|
-
(file) => path15.join(rootDir, ".cursor", "rules", file)
|
|
4602
|
-
),
|
|
4603
|
-
[CURSOR_RULE_MANAGED_MARKER]
|
|
4604
|
-
);
|
|
4605
|
-
if (await isPlatformMinimal(rootDir, "cursor")) {
|
|
4606
|
-
return {
|
|
4607
|
-
present: hasCoreRules,
|
|
4608
|
-
ready: hasCoreRules,
|
|
4609
|
-
target: ".cursor/rules/",
|
|
4610
|
-
readyDetail: "mancode core rules present"
|
|
4611
|
-
};
|
|
4612
|
-
}
|
|
4613
|
-
const [hasRules, hasCommands] = await Promise.all([
|
|
4614
|
-
allManagedSkills(
|
|
4615
|
-
MANCODE_CURSOR_RULE_FILES.map(
|
|
4616
|
-
(file) => path15.join(rootDir, ".cursor", "rules", file)
|
|
4617
|
-
),
|
|
4618
|
-
[CURSOR_RULE_MANAGED_MARKER]
|
|
4619
|
-
),
|
|
4620
|
-
allManagedSkills(
|
|
4621
|
-
MODE_NAMES.map(
|
|
4622
|
-
(mode) => path15.join(rootDir, ".cursor", "commands", `${mode}.md`)
|
|
4623
|
-
),
|
|
4624
|
-
[MODE_FILE_MANAGED_MARKER]
|
|
4625
|
-
)
|
|
4626
|
-
]);
|
|
4627
|
-
return {
|
|
4628
|
-
present: hasCoreRules,
|
|
4629
|
-
ready: hasRules && hasCommands,
|
|
4630
|
-
target: ".cursor/rules/ + .cursor/commands/",
|
|
4631
|
-
readyDetail: hasRules && hasCommands ? "mancode rules and commands present" : "mode rules or commands are missing, incomplete, or user-authored"
|
|
4632
|
-
};
|
|
4633
|
-
}
|
|
4634
|
-
if (platform === "codex") {
|
|
4635
|
-
const hasBlock2 = await fileHasManagedBlock(path15.join(rootDir, "AGENTS.md"));
|
|
4636
|
-
if (!hasBlock2) {
|
|
4637
|
-
return {
|
|
4638
|
-
present: false,
|
|
4639
|
-
ready: false,
|
|
4640
|
-
target: "AGENTS.md",
|
|
4641
|
-
readyDetail: "managed block missing"
|
|
4642
|
-
};
|
|
4751
|
+
".mancode/hooks/user-prompt-submit.sh",
|
|
4752
|
+
".claude/settings.json",
|
|
4753
|
+
".claude/skills/man8/SKILL.md",
|
|
4754
|
+
".claude/skills/mamba/SKILL.md",
|
|
4755
|
+
".claude/skills/solo/SKILL.md",
|
|
4756
|
+
".claude/skills/mancode-mamba.md",
|
|
4757
|
+
".claude/skills/mancode-solo.md",
|
|
4758
|
+
".claude/skills/mancode-man8.md"
|
|
4759
|
+
);
|
|
4760
|
+
for (const mode of MODE_NAMES) {
|
|
4761
|
+
files.push(
|
|
4762
|
+
`.claude/skills/${mode}/SKILL.md`,
|
|
4763
|
+
`.claude/skills/mancode-${mode}.md`
|
|
4764
|
+
);
|
|
4643
4765
|
}
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
present: true,
|
|
4647
|
-
ready: true,
|
|
4648
|
-
target: "AGENTS.md",
|
|
4649
|
-
readyDetail: "managed block present"
|
|
4650
|
-
};
|
|
4766
|
+
for (const agent of ALL_AGENTS) {
|
|
4767
|
+
files.push(`.claude/agents/${agent.name}.md`);
|
|
4651
4768
|
}
|
|
4652
|
-
const skillsDir = path15.join(rootDir, ".agents", "skills");
|
|
4653
|
-
const hasSkills = await allManagedSkills(
|
|
4654
|
-
MODE_NAMES.map((mode) => path15.join(skillsDir, mode, "SKILL.md")),
|
|
4655
|
-
MANCODE_AGENT_SKILL_MARKERS
|
|
4656
|
-
);
|
|
4657
|
-
return {
|
|
4658
|
-
present: true,
|
|
4659
|
-
ready: hasSkills,
|
|
4660
|
-
target: "AGENTS.md + .agents/skills/",
|
|
4661
|
-
readyDetail: hasSkills ? "managed block and skills present" : "mode skills are missing, incomplete, or user-authored"
|
|
4662
|
-
};
|
|
4663
4769
|
}
|
|
4664
|
-
if (
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4770
|
+
if (platforms.includes("cursor")) {
|
|
4771
|
+
files.push(
|
|
4772
|
+
".cursor/rules/mancode-mamba.mdc",
|
|
4773
|
+
".cursor/rules/mancode-man8.mdc",
|
|
4774
|
+
".cursor/commands/mamba.md",
|
|
4775
|
+
".cursor/commands/man8.md"
|
|
4669
4776
|
);
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
present: false,
|
|
4673
|
-
ready: false,
|
|
4674
|
-
target: "AGENTS.md",
|
|
4675
|
-
readyDetail: "managed block missing"
|
|
4676
|
-
};
|
|
4777
|
+
for (const fileName of MANCODE_CURSOR_RULE_FILES) {
|
|
4778
|
+
files.push(`.cursor/rules/${fileName}`);
|
|
4677
4779
|
}
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
present: true,
|
|
4681
|
-
ready: true,
|
|
4682
|
-
target: "AGENTS.md",
|
|
4683
|
-
readyDetail: "managed block present"
|
|
4684
|
-
};
|
|
4780
|
+
for (const mode of MODE_NAMES) {
|
|
4781
|
+
files.push(`.cursor/commands/${mode}.md`);
|
|
4685
4782
|
}
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4783
|
+
}
|
|
4784
|
+
if (platforms.includes("codex") || platforms.includes("zcode")) {
|
|
4785
|
+
files.push(
|
|
4786
|
+
"AGENTS.md",
|
|
4787
|
+
".agents/skills/mamba/SKILL.md",
|
|
4788
|
+
".agents/skills/man8/SKILL.md"
|
|
4690
4789
|
);
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
target: "AGENTS.md + .agents/skills/",
|
|
4695
|
-
readyDetail: hasSkills ? "managed block and skills present" : "mode skills are missing, incomplete, or user-authored"
|
|
4696
|
-
};
|
|
4790
|
+
for (const mode of MODE_NAMES) {
|
|
4791
|
+
files.push(`.agents/skills/${mode}/SKILL.md`);
|
|
4792
|
+
}
|
|
4697
4793
|
}
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
present: false,
|
|
4704
|
-
ready: false,
|
|
4705
|
-
target: ".github/copilot-instructions.md",
|
|
4706
|
-
readyDetail: "managed block missing"
|
|
4707
|
-
};
|
|
4794
|
+
if (platforms.includes("codex")) {
|
|
4795
|
+
files.push(".codex/skills/mamba/SKILL.md", ".codex/skills/man8/SKILL.md");
|
|
4796
|
+
for (const mode of MODE_NAMES) {
|
|
4797
|
+
files.push(`.codex/skills/${mode}/SKILL.md`);
|
|
4798
|
+
}
|
|
4708
4799
|
}
|
|
4709
|
-
if (
|
|
4710
|
-
|
|
4711
|
-
const
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
);
|
|
4715
|
-
return {
|
|
4716
|
-
present: true,
|
|
4717
|
-
ready: hasPrompts,
|
|
4718
|
-
target: ".github/copilot-instructions.md + .github/prompts/",
|
|
4719
|
-
readyDetail: hasPrompts ? "managed block and prompts present" : "mode prompts are missing, incomplete, or user-authored"
|
|
4720
|
-
};
|
|
4800
|
+
if (platforms.includes("zcode")) {
|
|
4801
|
+
files.push(".zcode/skills/mamba/SKILL.md", ".zcode/skills/man8/SKILL.md");
|
|
4802
|
+
for (const mode of MODE_NAMES) {
|
|
4803
|
+
files.push(`.zcode/skills/${mode}/SKILL.md`);
|
|
4804
|
+
}
|
|
4721
4805
|
}
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
if (!installed && readiness.present) {
|
|
4732
|
-
return "target files present but platform is not recorded in config";
|
|
4806
|
+
if (platforms.includes("copilot")) {
|
|
4807
|
+
files.push(
|
|
4808
|
+
".github/copilot-instructions.md",
|
|
4809
|
+
".github/prompts/mamba.prompt.md",
|
|
4810
|
+
".github/prompts/man8.prompt.md"
|
|
4811
|
+
);
|
|
4812
|
+
for (const mode of MODE_NAMES) {
|
|
4813
|
+
files.push(`.github/prompts/${mode}.prompt.md`);
|
|
4814
|
+
}
|
|
4733
4815
|
}
|
|
4734
|
-
return
|
|
4816
|
+
return [...new Set(files.map((file) => path15.join(rootDir, file)))];
|
|
4735
4817
|
}
|
|
4736
|
-
async function
|
|
4737
|
-
|
|
4738
|
-
|
|
4818
|
+
async function snapshotFiles(filePaths) {
|
|
4819
|
+
return Promise.all(
|
|
4820
|
+
filePaths.map(async (filePath) => {
|
|
4821
|
+
try {
|
|
4822
|
+
return { filePath, content: await fs4.readFile(filePath, "utf-8") };
|
|
4823
|
+
} catch (error) {
|
|
4824
|
+
if (isNodeError5(error) && error.code === "ENOENT") {
|
|
4825
|
+
return { filePath, content: null };
|
|
4826
|
+
}
|
|
4827
|
+
throw error;
|
|
4828
|
+
}
|
|
4829
|
+
})
|
|
4739
4830
|
);
|
|
4740
|
-
return results.every(Boolean);
|
|
4741
4831
|
}
|
|
4742
|
-
async function
|
|
4743
|
-
const
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4832
|
+
async function snapshotDirectories(filePaths, additionalDirectories = []) {
|
|
4833
|
+
const directories = new Set(additionalDirectories);
|
|
4834
|
+
for (const filePath of filePaths) {
|
|
4835
|
+
let current = path15.dirname(filePath);
|
|
4836
|
+
while (current !== path15.dirname(current)) {
|
|
4837
|
+
directories.add(current);
|
|
4838
|
+
current = path15.dirname(current);
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
return Promise.all(
|
|
4842
|
+
[...directories].map(async (dirPath) => ({
|
|
4843
|
+
dirPath,
|
|
4844
|
+
existed: await pathExists4(dirPath)
|
|
4845
|
+
}))
|
|
4754
4846
|
);
|
|
4755
|
-
const results = await Promise.all([...skillChecks, ...agentChecks]);
|
|
4756
|
-
return results.every(Boolean);
|
|
4757
4847
|
}
|
|
4758
|
-
async function
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4848
|
+
async function restoreFiles(snapshots) {
|
|
4849
|
+
for (const snapshot of snapshots) {
|
|
4850
|
+
if (snapshot.content === null) {
|
|
4851
|
+
await fs4.rm(snapshot.filePath, { force: true });
|
|
4852
|
+
continue;
|
|
4853
|
+
}
|
|
4854
|
+
await fs4.mkdir(path15.dirname(snapshot.filePath), { recursive: true });
|
|
4855
|
+
await fs4.writeFile(snapshot.filePath, snapshot.content, "utf-8");
|
|
4763
4856
|
}
|
|
4764
4857
|
}
|
|
4765
|
-
async function
|
|
4766
|
-
|
|
4767
|
-
}
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
} catch {
|
|
4773
|
-
return false;
|
|
4858
|
+
async function removeNewEmptyDirectories(snapshots) {
|
|
4859
|
+
const candidates = snapshots.filter((snapshot) => !snapshot.existed).sort((a, b) => b.dirPath.length - a.dirPath.length);
|
|
4860
|
+
for (const { dirPath } of candidates) {
|
|
4861
|
+
try {
|
|
4862
|
+
await fs4.rmdir(dirPath);
|
|
4863
|
+
} catch {
|
|
4864
|
+
}
|
|
4774
4865
|
}
|
|
4775
4866
|
}
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
const raw = await fs4.readFile(
|
|
4779
|
-
path15.join(rootDir, ".mancode", "config.json"),
|
|
4780
|
-
"utf-8"
|
|
4781
|
-
);
|
|
4782
|
-
const config = JSON.parse(raw);
|
|
4783
|
-
if (!isRecord4(config.platformOptions)) return false;
|
|
4784
|
-
const options = config.platformOptions[platform];
|
|
4785
|
-
return isRecord4(options) && options.minimal === true;
|
|
4786
|
-
} catch {
|
|
4787
|
-
return false;
|
|
4788
|
-
}
|
|
4867
|
+
function isRecord4(value) {
|
|
4868
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4789
4869
|
}
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
const content = await fs4.readFile(filePath, "utf-8");
|
|
4793
|
-
return hasManagedBlock(content, startMarker, endMarker);
|
|
4794
|
-
} catch {
|
|
4795
|
-
return false;
|
|
4796
|
-
}
|
|
4870
|
+
function isNodeError5(error) {
|
|
4871
|
+
return error instanceof Error && "code" in error;
|
|
4797
4872
|
}
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4873
|
+
function printPlatformCreatedFiles(platform, locale) {
|
|
4874
|
+
if (platform === "claude-code") {
|
|
4875
|
+
console.log(
|
|
4876
|
+
localize(
|
|
4877
|
+
locale,
|
|
4878
|
+
" .claude/settings.json # hook \u6CE8\u518C",
|
|
4879
|
+
" .claude/settings.json # hook registration"
|
|
4880
|
+
)
|
|
4803
4881
|
);
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4882
|
+
console.log(
|
|
4883
|
+
localize(
|
|
4884
|
+
locale,
|
|
4885
|
+
" .claude/skills/ # solo + MVP-2 slash skills",
|
|
4886
|
+
" .claude/skills/ # solo + MVP-2 slash skills"
|
|
4887
|
+
)
|
|
4888
|
+
);
|
|
4889
|
+
return;
|
|
4808
4890
|
}
|
|
4891
|
+
if (platform === "cursor") {
|
|
4892
|
+
console.log(" .cursor/rules/ # Cursor project rules");
|
|
4893
|
+
return;
|
|
4894
|
+
}
|
|
4895
|
+
if (platform === "codex") {
|
|
4896
|
+
console.log(" AGENTS.md # Codex managed block");
|
|
4897
|
+
console.log(" .agents/skills/ # Codex mode skills");
|
|
4898
|
+
return;
|
|
4899
|
+
}
|
|
4900
|
+
if (platform === "zcode") {
|
|
4901
|
+
console.log(" AGENTS.md # ZCode managed block");
|
|
4902
|
+
console.log(" .agents/skills/ # ZCode mode skills");
|
|
4903
|
+
return;
|
|
4904
|
+
}
|
|
4905
|
+
console.log(" .github/copilot-instructions.md # Copilot instructions");
|
|
4809
4906
|
}
|
|
4810
|
-
function
|
|
4811
|
-
|
|
4812
|
-
const hooks = settings.hooks;
|
|
4813
|
-
if (!isRecord4(hooks)) return false;
|
|
4814
|
-
return hasHookCommand(hooks.SessionStart, ".mancode/hooks/session-start.mjs") && hasHookCommand(
|
|
4815
|
-
hooks.UserPromptSubmit,
|
|
4816
|
-
".mancode/hooks/user-prompt-submit.mjs"
|
|
4817
|
-
);
|
|
4818
|
-
}
|
|
4819
|
-
function hasHookCommand(value, needle) {
|
|
4820
|
-
if (!Array.isArray(value)) return false;
|
|
4821
|
-
return value.some((group) => {
|
|
4822
|
-
if (!isRecord4(group) || !Array.isArray(group.hooks)) return false;
|
|
4823
|
-
return group.hooks.some((hook) => {
|
|
4824
|
-
if (!isRecord4(hook) || typeof hook.command !== "string") return false;
|
|
4825
|
-
return hook.command.includes(needle);
|
|
4826
|
-
});
|
|
4827
|
-
});
|
|
4907
|
+
function localize(locale, chinese, english) {
|
|
4908
|
+
return locale === "zh-CN" ? chinese : english;
|
|
4828
4909
|
}
|
|
4829
4910
|
async function pathExists4(p) {
|
|
4830
4911
|
try {
|
|
@@ -4834,11 +4915,11 @@ async function pathExists4(p) {
|
|
|
4834
4915
|
return false;
|
|
4835
4916
|
}
|
|
4836
4917
|
}
|
|
4837
|
-
function isRecord4(value) {
|
|
4838
|
-
return typeof value === "object" && value !== null;
|
|
4839
|
-
}
|
|
4840
4918
|
|
|
4841
4919
|
// src/commands/install.ts
|
|
4920
|
+
import { promises as fs5 } from "fs";
|
|
4921
|
+
import path16 from "path";
|
|
4922
|
+
import process6 from "process";
|
|
4842
4923
|
var EXIT_OK2 = 0;
|
|
4843
4924
|
var EXIT_NOT_INITIALIZED = 1;
|
|
4844
4925
|
var EXIT_UNSUPPORTED_PLATFORM = 2;
|
|
@@ -4935,13 +5016,13 @@ async function readConfig(rootDir) {
|
|
|
4935
5016
|
const raw = await fs5.readFile(configPath, "utf-8");
|
|
4936
5017
|
return { config: JSON.parse(raw), valid: true };
|
|
4937
5018
|
} catch (err) {
|
|
4938
|
-
if (
|
|
5019
|
+
if (isNodeError6(err) && err.code === "ENOENT") {
|
|
4939
5020
|
return { config: {}, valid: false };
|
|
4940
5021
|
}
|
|
4941
5022
|
throw err;
|
|
4942
5023
|
}
|
|
4943
5024
|
}
|
|
4944
|
-
function
|
|
5025
|
+
function isNodeError6(err) {
|
|
4945
5026
|
return err instanceof Error && "code" in err;
|
|
4946
5027
|
}
|
|
4947
5028
|
async function updateConfig(rootDir, config) {
|
|
@@ -5749,7 +5830,7 @@ async function readIssueDatabase(issueDbPath) {
|
|
|
5749
5830
|
try {
|
|
5750
5831
|
raw = await readFile8(issueDbPath, "utf-8");
|
|
5751
5832
|
} catch (err) {
|
|
5752
|
-
if (
|
|
5833
|
+
if (isNodeError7(err) && err.code === "ENOENT") {
|
|
5753
5834
|
return emptyIssueDatabase();
|
|
5754
5835
|
}
|
|
5755
5836
|
throw err;
|
|
@@ -5960,7 +6041,7 @@ async function pathExists6(file) {
|
|
|
5960
6041
|
return false;
|
|
5961
6042
|
}
|
|
5962
6043
|
}
|
|
5963
|
-
function
|
|
6044
|
+
function isNodeError7(err) {
|
|
5964
6045
|
return err instanceof Error && "code" in err;
|
|
5965
6046
|
}
|
|
5966
6047
|
function isRecord6(value) {
|
|
@@ -6255,7 +6336,7 @@ async function ensureReplaceableFile(filePath) {
|
|
|
6255
6336
|
throw new Error(`cannot replace non-file path: ${filePath}`);
|
|
6256
6337
|
}
|
|
6257
6338
|
} catch (error) {
|
|
6258
|
-
if (
|
|
6339
|
+
if (isNodeError8(error) && error.code === "ENOENT") return;
|
|
6259
6340
|
throw error;
|
|
6260
6341
|
}
|
|
6261
6342
|
}
|
|
@@ -6272,7 +6353,7 @@ async function readOptionalText(filePath) {
|
|
|
6272
6353
|
try {
|
|
6273
6354
|
return await fs7.readFile(filePath, "utf-8");
|
|
6274
6355
|
} catch (error) {
|
|
6275
|
-
if (
|
|
6356
|
+
if (isNodeError8(error) && error.code === "ENOENT") return null;
|
|
6276
6357
|
throw error;
|
|
6277
6358
|
}
|
|
6278
6359
|
}
|
|
@@ -6335,7 +6416,7 @@ async function readRequiredState(filePath) {
|
|
|
6335
6416
|
function isRecord7(value) {
|
|
6336
6417
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6337
6418
|
}
|
|
6338
|
-
function
|
|
6419
|
+
function isNodeError8(error) {
|
|
6339
6420
|
return error instanceof Error && "code" in error;
|
|
6340
6421
|
}
|
|
6341
6422
|
async function pathExists8(filePath) {
|
|
@@ -6755,7 +6836,7 @@ async function allocateTaskId(projectRoot, baseTaskId) {
|
|
|
6755
6836
|
await mkdir9(workflowDir(projectRoot, taskId));
|
|
6756
6837
|
return taskId;
|
|
6757
6838
|
} catch (err) {
|
|
6758
|
-
if (
|
|
6839
|
+
if (isNodeError9(err) && err.code === "EEXIST") {
|
|
6759
6840
|
continue;
|
|
6760
6841
|
}
|
|
6761
6842
|
throw err;
|
|
@@ -6871,7 +6952,7 @@ function isWorkflowOutcome(value) {
|
|
|
6871
6952
|
async function validateParentTask(projectRoot, mode, parentTaskId, requireStepSix = false) {
|
|
6872
6953
|
if (!parentTaskId) return;
|
|
6873
6954
|
if (mode !== "mamba") {
|
|
6874
|
-
throw new Error("only
|
|
6955
|
+
throw new Error("only manba workflows can have a parent task");
|
|
6875
6956
|
}
|
|
6876
6957
|
const parent = await readWorkflow(projectRoot, parentTaskId);
|
|
6877
6958
|
if (!parent || parent.mode !== "man" && parent.mode !== "manteam") {
|
|
@@ -6911,14 +6992,14 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
|
|
|
6911
6992
|
);
|
|
6912
6993
|
}
|
|
6913
6994
|
if (updated.status === "planned" && updated.mode === "mamba") {
|
|
6914
|
-
throw new Error("
|
|
6995
|
+
throw new Error("manba workflows cannot be planned");
|
|
6915
6996
|
}
|
|
6916
6997
|
if (updated.status === "planned" && updated.currentStep !== 4) {
|
|
6917
6998
|
throw new Error("planned workflows must be at step 4");
|
|
6918
6999
|
}
|
|
6919
7000
|
if (updated.status === "completed" && updated.currentStep !== maxWorkflowStep(updated.mode)) {
|
|
6920
7001
|
throw new Error(
|
|
6921
|
-
`completed ${updated.mode} workflows must be at step ${maxWorkflowStep(updated.mode)}`
|
|
7002
|
+
`completed ${updated.mode === "mamba" ? "manba" : updated.mode} workflows must be at step ${maxWorkflowStep(updated.mode)}`
|
|
6922
7003
|
);
|
|
6923
7004
|
}
|
|
6924
7005
|
if (updated.blockingReason !== void 0 && typeof updated.blockingReason !== "string") {
|
|
@@ -6932,25 +7013,25 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
|
|
|
6932
7013
|
throw new Error("only blocked workflows can have a blocking reason");
|
|
6933
7014
|
}
|
|
6934
7015
|
if (updated.mode !== "mamba" && updated.outcome !== void 0) {
|
|
6935
|
-
throw new Error("only
|
|
7016
|
+
throw new Error("only manba workflows can have an outcome");
|
|
6936
7017
|
}
|
|
6937
7018
|
if (updated.outcome !== void 0 && !isWorkflowOutcome(updated.outcome)) {
|
|
6938
7019
|
throw new Error(`invalid workflow outcome: ${updated.outcome}`);
|
|
6939
7020
|
}
|
|
6940
7021
|
if (updated.outcome !== void 0 && updated.status !== "completed") {
|
|
6941
|
-
throw new Error("
|
|
7022
|
+
throw new Error("manba outcomes can only be set on completed workflows");
|
|
6942
7023
|
}
|
|
6943
7024
|
if (updated.mode === "mamba" && updated.status === "completed" && !updated.outcome) {
|
|
6944
|
-
throw new Error("completed
|
|
7025
|
+
throw new Error("completed manba workflows require an outcome");
|
|
6945
7026
|
}
|
|
6946
7027
|
if (updated.mode === "mamba" && updated.planVersion !== void 0) {
|
|
6947
|
-
throw new Error("
|
|
7028
|
+
throw new Error("manba workflows cannot have a plan version");
|
|
6948
7029
|
}
|
|
6949
7030
|
if (updated.reviewPolicyVersion !== void 0 && updated.reviewPolicyVersion !== 1) {
|
|
6950
7031
|
throw new Error("invalid workflow review policy version");
|
|
6951
7032
|
}
|
|
6952
7033
|
if (updated.mode === "mamba" && updated.reviewPolicyVersion !== void 0) {
|
|
6953
|
-
throw new Error("
|
|
7034
|
+
throw new Error("manba workflows cannot have a review policy version");
|
|
6954
7035
|
}
|
|
6955
7036
|
if (updated.planVersion !== void 0 && (!Number.isInteger(updated.planVersion) || updated.planVersion < 1)) {
|
|
6956
7037
|
throw new Error("workflow plan version must be a positive integer");
|
|
@@ -6963,7 +7044,7 @@ async function validateWorkflowMeta(projectRoot, updated, existing) {
|
|
|
6963
7044
|
}
|
|
6964
7045
|
await validateParentTask(projectRoot, updated.mode, updated.parentTaskId);
|
|
6965
7046
|
if ((isTerminalWorkflowStatus(updated.status) || updated.status === "planned") && (await listActiveMambaChildren(projectRoot, updated.taskId)).length > 0) {
|
|
6966
|
-
throw new Error("cannot finish workflow with an active
|
|
7047
|
+
throw new Error("cannot finish workflow with an active manba child");
|
|
6967
7048
|
}
|
|
6968
7049
|
if (updated.status === "completed" && updated.reviewPolicyVersion === 1 && !reviewWasExplicitlySkipped(updated) && !await reviewCanComplete(projectRoot, updated.taskId)) {
|
|
6969
7050
|
throw new Error("workflow review is incomplete or still has open blockers");
|
|
@@ -7034,7 +7115,7 @@ async function propagateChildStatus(projectRoot, child) {
|
|
|
7034
7115
|
if (!parentTaskId) return;
|
|
7035
7116
|
const parent = await readWorkflow(projectRoot, parentTaskId);
|
|
7036
7117
|
if (!parent || parent.status !== "in_progress") return;
|
|
7037
|
-
const reason = child.status === "blocked" ? `
|
|
7118
|
+
const reason = child.status === "blocked" ? `manba child ${child.taskId} is blocked: ${child.blockingReason ?? "unknown reason"}` : `manba child ${child.taskId} requires manual testing`;
|
|
7038
7119
|
await writeMetadata(workflowDir(projectRoot, parent.taskId), {
|
|
7039
7120
|
...parent,
|
|
7040
7121
|
status: "blocked",
|
|
@@ -7049,7 +7130,7 @@ async function propagateChildStatus(projectRoot, child) {
|
|
|
7049
7130
|
});
|
|
7050
7131
|
}
|
|
7051
7132
|
}
|
|
7052
|
-
function
|
|
7133
|
+
function isNodeError9(err) {
|
|
7053
7134
|
return err instanceof Error && "code" in err;
|
|
7054
7135
|
}
|
|
7055
7136
|
function assertValidTaskId2(taskId) {
|
|
@@ -7309,7 +7390,8 @@ function runHookEstimate(rootDir, hookPath) {
|
|
|
7309
7390
|
});
|
|
7310
7391
|
}
|
|
7311
7392
|
function printText(r) {
|
|
7312
|
-
const
|
|
7393
|
+
const publicMode = r.mode === "mamba" ? "manba" : r.mode;
|
|
7394
|
+
const modeLabel = publicMode === "solo" ? `${publicMode} (default)` : publicMode;
|
|
7313
7395
|
console.log("");
|
|
7314
7396
|
console.log(`mancode v${r.version}`);
|
|
7315
7397
|
console.log("");
|
|
@@ -7686,7 +7768,7 @@ async function workflow(rootDir, subcommand, args = [], options = {}) {
|
|
|
7686
7768
|
} else {
|
|
7687
7769
|
console.error(`\u2717 Invalid workflow subcommand: ${subcommand}`);
|
|
7688
7770
|
console.error(
|
|
7689
|
-
" Use: create <man|
|
|
7771
|
+
" Use: create <man|manba|manteam> <task> | update <taskId> | review <taskId> <init|complete|remediate|show> | list | show <taskId> | clean"
|
|
7690
7772
|
);
|
|
7691
7773
|
}
|
|
7692
7774
|
return EXIT_INVALID_ARG2;
|
|
@@ -7790,20 +7872,21 @@ function parseCsv(value) {
|
|
|
7790
7872
|
return (value ?? "").split(",").map((item) => item.trim()).filter(Boolean);
|
|
7791
7873
|
}
|
|
7792
7874
|
async function workflowCreate(rootDir, args, options) {
|
|
7793
|
-
const
|
|
7875
|
+
const requestedMode = args[0];
|
|
7876
|
+
const mode = parsePublicWorkflowMode(requestedMode);
|
|
7794
7877
|
const task = args.slice(1).join(" ").trim();
|
|
7795
|
-
if (mode
|
|
7878
|
+
if (!mode) {
|
|
7796
7879
|
return invalidArg(
|
|
7797
7880
|
options,
|
|
7798
|
-
`invalid workflow mode: ${
|
|
7799
|
-
"Use: mancode workflow create <man|
|
|
7881
|
+
`invalid workflow mode: ${requestedMode ?? ""}`,
|
|
7882
|
+
"Use: mancode workflow create <man|manba|manteam> <task> [--parent-task <taskId>]"
|
|
7800
7883
|
);
|
|
7801
7884
|
}
|
|
7802
7885
|
if (!task) {
|
|
7803
7886
|
return invalidArg(
|
|
7804
7887
|
options,
|
|
7805
7888
|
"missing workflow task",
|
|
7806
|
-
"Use: mancode workflow create <man|
|
|
7889
|
+
"Use: mancode workflow create <man|manba|manteam> <task> [--parent-task <taskId>]"
|
|
7807
7890
|
);
|
|
7808
7891
|
}
|
|
7809
7892
|
try {
|
|
@@ -7873,7 +7956,7 @@ async function workflowUpdate(rootDir, taskId, options) {
|
|
|
7873
7956
|
if (existing.mode !== "mamba" || nextStatus !== "completed") {
|
|
7874
7957
|
return invalidArg(
|
|
7875
7958
|
options,
|
|
7876
|
-
"--outcome is only valid when completing a
|
|
7959
|
+
"--outcome is only valid when completing a manba workflow"
|
|
7877
7960
|
);
|
|
7878
7961
|
}
|
|
7879
7962
|
patch.outcome = options.outcome;
|
|
@@ -7981,7 +8064,7 @@ async function workflowShow(rootDir, taskId, options) {
|
|
|
7981
8064
|
}
|
|
7982
8065
|
console.log(`Workflow: ${meta.taskId}`);
|
|
7983
8066
|
console.log(`Task: ${meta.task}`);
|
|
7984
|
-
console.log(`Mode: ${meta.mode}`);
|
|
8067
|
+
console.log(`Mode: ${formatPublicWorkflowMode(meta.mode)}`);
|
|
7985
8068
|
console.log(`Status: ${meta.status}`);
|
|
7986
8069
|
console.log(`Current step:${meta.currentStep}/${maxWorkflowStep(meta.mode)}`);
|
|
7987
8070
|
console.log(
|
|
@@ -7996,7 +8079,7 @@ async function workflowShow(rootDir, taskId, options) {
|
|
|
7996
8079
|
if (meta.blockingReason) console.log(`Blocked: ${meta.blockingReason}`);
|
|
7997
8080
|
if (children !== void 0) {
|
|
7998
8081
|
if (children.length > 0) {
|
|
7999
|
-
console.log(`Children: ${children.length} active
|
|
8082
|
+
console.log(`Children: ${children.length} active manba workflow(s)`);
|
|
8000
8083
|
for (const child of children) {
|
|
8001
8084
|
console.log(
|
|
8002
8085
|
` - ${child.taskId} (Step ${child.currentStep}/5, ${child.status})`
|
|
@@ -8084,7 +8167,15 @@ function formatWorkflowRow(meta) {
|
|
|
8084
8167
|
meta.blockingReason ? `blocked=${meta.blockingReason}` : "",
|
|
8085
8168
|
meta.activeChildren && meta.activeChildren.length > 0 ? `children=${meta.activeChildren.map((child) => child.taskId).join(",")}` : ""
|
|
8086
8169
|
].filter(Boolean);
|
|
8087
|
-
return `${meta.taskId.padEnd(42)} ${meta.mode.padEnd(7)} ${meta.status.padEnd(11)} ${step}${details.length > 0 ? ` | ${details.join(" | ")}` : ""}`;
|
|
8170
|
+
return `${meta.taskId.padEnd(42)} ${formatPublicWorkflowMode(meta.mode).padEnd(7)} ${meta.status.padEnd(11)} ${step}${details.length > 0 ? ` | ${details.join(" | ")}` : ""}`;
|
|
8171
|
+
}
|
|
8172
|
+
function parsePublicWorkflowMode(value) {
|
|
8173
|
+
if (value === "manba" || value === "mamba") return "mamba";
|
|
8174
|
+
if (value === "man" || value === "manteam") return value;
|
|
8175
|
+
return null;
|
|
8176
|
+
}
|
|
8177
|
+
function formatPublicWorkflowMode(mode) {
|
|
8178
|
+
return mode === "mamba" ? "manba" : mode;
|
|
8088
8179
|
}
|
|
8089
8180
|
function attachActiveChildren(workflows) {
|
|
8090
8181
|
return workflows.map((meta) => {
|
|
@@ -8142,7 +8233,7 @@ async function pathExists12(p) {
|
|
|
8142
8233
|
|
|
8143
8234
|
// src/cli.ts
|
|
8144
8235
|
program.name("mancode").description(
|
|
8145
|
-
"AI coding agent harness. Modes: solo, man,
|
|
8236
|
+
"AI coding agent harness. Modes: solo, man, manba, manteam, manps."
|
|
8146
8237
|
).version(VERSION);
|
|
8147
8238
|
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) => {
|
|
8148
8239
|
const code = await init(process.cwd(), {
|
|
@@ -8175,8 +8266,8 @@ program.command("uninstall [platform]").description("Remove platform adapter or
|
|
|
8175
8266
|
});
|
|
8176
8267
|
program.command("workflow <subcommand> [args...]").description("Manage mancode workflows").option("--dry-run", "Preview clean without deleting").option("--older-than <duration>", "Clean workflows older than (e.g. 30d)").option("--step <n>", "Update workflow current step").option("--status <status>", "Update workflow status").option(
|
|
8177
8268
|
"--parent-task <taskId>",
|
|
8178
|
-
"Parent /man or /manteam workflow for
|
|
8179
|
-
).option("--blocking-reason <reason>", "Explain why a workflow is blocked").option("--outcome <outcome>", "Set
|
|
8269
|
+
"Parent /man or /manteam workflow for manba"
|
|
8270
|
+
).option("--blocking-reason <reason>", "Explain why a workflow is blocked").option("--outcome <outcome>", "Set manba outcome").option("--plan-version <n>", "Set the next man/manteam plan revision").option("--skipped <steps>", "Update skipped steps as comma-separated values").option("--review-depth <depth>", "Review depth: targeted or full").option("--review-domain <domain>", "Review domain: quality or security").option(
|
|
8180
8271
|
"--report <path>",
|
|
8181
8272
|
"Relative Markdown report path for a review domain"
|
|
8182
8273
|
).option("--blockers <ids>", "Comma-separated blocker ids found by a review").option(
|