mancode 0.3.2 → 0.3.3
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 +32 -10
- package/README.zh-CN.md +30 -10
- package/dist/cli.js +333 -347
- package/dist/cli.js.map +1 -1
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import { program } from "commander";
|
|
|
9
9
|
// src/commands/init.ts
|
|
10
10
|
import { promises as fs2 } from "fs";
|
|
11
11
|
import path13 from "path";
|
|
12
|
-
import
|
|
12
|
+
import process4 from "process";
|
|
13
13
|
|
|
14
14
|
// src/installers/claude-code.ts
|
|
15
15
|
import {
|
|
@@ -510,215 +510,229 @@ function yamlString(s) {
|
|
|
510
510
|
}
|
|
511
511
|
|
|
512
512
|
// src/templates/inline.ts
|
|
513
|
-
var SESSION_START_HOOK = `#!/bin/
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
513
|
+
var SESSION_START_HOOK = String.raw`#!/usr/bin/env node
|
|
514
|
+
// .mancode/hooks/session-start.mjs
|
|
515
|
+
// mancode SessionStart hook - cross-platform project context
|
|
516
|
+
import { readFileSync } from 'node:fs';
|
|
517
|
+
import path from 'node:path';
|
|
518
|
+
import { fileURLToPath } from 'node:url';
|
|
519
|
+
|
|
520
|
+
const projectRoot = path.resolve(
|
|
521
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
522
|
+
'../..',
|
|
523
|
+
);
|
|
524
|
+
const state = readJson(path.join(projectRoot, '.mancode', 'state.json'));
|
|
525
|
+
const profile =
|
|
526
|
+
readJson(path.join(projectRoot, '.mancode', 'project-profile.json')) || {};
|
|
527
|
+
|
|
528
|
+
if (!state) {
|
|
529
|
+
console.log('ℹ️ mancode 未初始化。运行 mancode init 开始。');
|
|
530
|
+
process.exit(0);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const mode = text(state.currentMode) || 'solo';
|
|
534
|
+
const techStack = sanitize(state.techStack);
|
|
535
|
+
const uiLibrary = sanitize(state.uiLibrary);
|
|
536
|
+
const output = [
|
|
537
|
+
'mancode_mode: ' + mode,
|
|
538
|
+
'project_type: ' + techStack,
|
|
539
|
+
'ui_library: ' + uiLibrary,
|
|
540
|
+
'',
|
|
541
|
+
'## mancode · ' + mode + ' mode',
|
|
542
|
+
'',
|
|
543
|
+
'你正在使用 mancode ' + mode + ' 模式。',
|
|
544
|
+
'',
|
|
545
|
+
'### 核心原则',
|
|
546
|
+
'1. **优先复用项目已有代码**',
|
|
547
|
+
' - 检查已检测到的源码目录和已有类似实现',
|
|
548
|
+
' - 复用现有组件、函数、样式',
|
|
549
|
+
'',
|
|
550
|
+
];
|
|
551
|
+
|
|
552
|
+
if (profile.uiAssets === 'detected') {
|
|
553
|
+
output.push(
|
|
554
|
+
'2. **应用项目审美 token**(仅在项目 profile 确认有 UI 资产且任务涉及 UI 时)',
|
|
555
|
+
' - UI library: ' + uiLibrary,
|
|
556
|
+
' - 使用项目已有的设计 token',
|
|
557
|
+
);
|
|
558
|
+
} else {
|
|
559
|
+
output.push(
|
|
560
|
+
'2. **按项目能力工作**',
|
|
561
|
+
' - 不假定存在 UI、浏览器或特定技术栈',
|
|
562
|
+
' - 先读取 project-profile 与项目现有验证方式',
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
output.push(
|
|
567
|
+
'',
|
|
568
|
+
'3. **最小改动**',
|
|
569
|
+
' - 只改用户要求的部分',
|
|
570
|
+
' - 不重构无关代码',
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
if (state.teamModeAutoDetected === true && mode === 'solo') {
|
|
574
|
+
output.push(
|
|
575
|
+
'',
|
|
576
|
+
'### 团队协作提醒',
|
|
577
|
+
'检测到团队项目(contributors: ' +
|
|
578
|
+
(Number.isFinite(state.contributors) ? state.contributors : 2) +
|
|
579
|
+
')。',
|
|
580
|
+
'- 涉及多人协作、交接、PR、共享模块时,优先使用 /manteam <task>。',
|
|
581
|
+
'- 只做个人小改动时,可以继续 solo;需要退出流程用 /mansolo。',
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
console.log(output.join('\n'));
|
|
586
|
+
|
|
587
|
+
function readJson(filePath) {
|
|
588
|
+
try {
|
|
589
|
+
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
590
|
+
} catch {
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function text(value) {
|
|
596
|
+
return typeof value === 'string' ? value : '';
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function sanitize(value) {
|
|
600
|
+
return String(value ?? '').replace(/[\r\n]/g, ' ').slice(0, 200);
|
|
601
|
+
}
|
|
599
602
|
`;
|
|
600
|
-
var USER_PROMPT_SUBMIT_HOOK = `#!/bin/
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
if
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
603
|
+
var USER_PROMPT_SUBMIT_HOOK = String.raw`#!/usr/bin/env node
|
|
604
|
+
// .mancode/hooks/user-prompt-submit.mjs
|
|
605
|
+
// mancode UserPromptSubmit hook - cross-platform prompt and style context
|
|
606
|
+
import { readFileSync } from 'node:fs';
|
|
607
|
+
import path from 'node:path';
|
|
608
|
+
import { fileURLToPath } from 'node:url';
|
|
609
|
+
|
|
610
|
+
const projectRoot = path.resolve(
|
|
611
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
612
|
+
'../..',
|
|
613
|
+
);
|
|
614
|
+
const mancodeDir = path.join(projectRoot, '.mancode');
|
|
615
|
+
const state = readJson(path.join(mancodeDir, 'state.json')) || {};
|
|
616
|
+
const profile = readJson(path.join(mancodeDir, 'project-profile.json')) || {};
|
|
617
|
+
const mode = typeof state.currentMode === 'string' ? state.currentMode : '';
|
|
618
|
+
const output = [];
|
|
619
|
+
|
|
620
|
+
if (mode === 'solo') {
|
|
621
|
+
output.push(
|
|
622
|
+
'## 动手前,先想六个问题:',
|
|
623
|
+
'',
|
|
624
|
+
'1. **为什么做?**',
|
|
625
|
+
' - 这个改动解决什么问题?',
|
|
626
|
+
'',
|
|
627
|
+
'2. **已经有什么?**',
|
|
628
|
+
' - 项目里有没有类似的实现可以复用?',
|
|
629
|
+
'',
|
|
630
|
+
'3. **最少改多少?**',
|
|
631
|
+
' - 能用一行解决吗?能复用现有代码吗?',
|
|
632
|
+
'',
|
|
633
|
+
'4. **能不能不拆新系统?**',
|
|
634
|
+
' - 不新建文件或模块能完成吗?',
|
|
635
|
+
'',
|
|
636
|
+
'5. **非平凡逻辑怎样最小运行验证?**',
|
|
637
|
+
'',
|
|
638
|
+
'6. **有什么没把握的?**',
|
|
639
|
+
' - 先自行查代码或文档,最多 2 次工具调用;仍不确定再问用户。',
|
|
640
|
+
'',
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const rawInput = readFileSync(0, 'utf8');
|
|
645
|
+
const userPrompt = readPrompt(rawInput);
|
|
646
|
+
const planningPattern = /先(?:别|不要|看|看看|调研|分析|评估)|给.*方案|给.*计划|怎么.*做|如何.*做|怎么.*实现|如何.*实现|应该怎么|怎么.*拆|拆分|只给.*计划|不要.*改代码|别.*改代码|不要.*动代码|别.*动代码|评估.*风险|风险.*评估|设计.*方案|架构|迁移|集成|\b(?:plan|planning|research|investigate|approach|proposal|architecture|risk|migration|integration)\b|how (?:should|would|to)|do not (?:edit|modify|change)|don.t (?:edit|modify|change)|no code changes|without changing code/iu;
|
|
647
|
+
|
|
648
|
+
if (mode === 'solo' && planningPattern.test(userPrompt)) {
|
|
649
|
+
output.push(
|
|
650
|
+
'## mancode 自动路由',
|
|
651
|
+
'',
|
|
652
|
+
'这个请求是规划/调研类任务。不要直接进入 solo 实施。',
|
|
653
|
+
"必须先调用 Skill tool,skill='man',把用户原始请求作为 task,执行 Scout 调研、澄清和 Plan Coach plan。",
|
|
654
|
+
'用户只要计划时,在 Step 4 选择“只要计划”;不要切到另一个命令。',
|
|
655
|
+
'',
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const uiPattern = /\b(?:button|component|page|style|ui|design|layout|css|color|font|theme|card|input|modal|dialog|header|footer|sidebar|dropdown|tooltip|toast|avatar|badge)\b|界面|页面|按钮|样式|颜色|字体|布局|组件|弹窗|导航|卡片|输入框|主题|美化|优化.*界面|调整.*样式/iu;
|
|
660
|
+
|
|
661
|
+
if (profile.uiAssets === 'detected' && uiPattern.test(userPrompt)) {
|
|
662
|
+
appendAestheticSummary(
|
|
663
|
+
output,
|
|
664
|
+
readJson(path.join(mancodeDir, 'aesthetics', 'style-tokens.json')),
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (output.length > 0) console.log(output.join('\n'));
|
|
669
|
+
|
|
670
|
+
function appendAestheticSummary(lines, tokens) {
|
|
671
|
+
if (!tokens || tokens.matchLevel !== 'high') return;
|
|
672
|
+
|
|
673
|
+
const colors = safeEntries(tokens.colors, 8).map(
|
|
674
|
+
([key, value]) => key + '=' + String(value),
|
|
675
|
+
);
|
|
676
|
+
const fonts = safeEntries(tokens.fonts, 4)
|
|
677
|
+
.filter(([, value]) => Array.isArray(value) && value.length > 0)
|
|
678
|
+
.map(([key, value]) => key + '=' + String(value[0]));
|
|
679
|
+
const components = Array.isArray(tokens.components)
|
|
680
|
+
? tokens.components
|
|
681
|
+
.filter(
|
|
682
|
+
(value) =>
|
|
683
|
+
typeof value === 'string' && /^[A-Z][A-Za-z0-9]{0,79}$/.test(value),
|
|
684
|
+
)
|
|
685
|
+
.slice(0, 8)
|
|
686
|
+
: [];
|
|
687
|
+
const cssVariables = safeEntries(tokens.cssVariables, 8).map(
|
|
688
|
+
([key, value]) => '--' + key + '=' + String(value),
|
|
689
|
+
);
|
|
690
|
+
|
|
691
|
+
lines.push('## 审美 token 摘要');
|
|
692
|
+
appendValue(lines, 'UI', tokens.uiLibrary);
|
|
693
|
+
appendValue(lines, 'Dark', tokens.darkMode);
|
|
694
|
+
appendValue(lines, 'Match', tokens.matchLevel);
|
|
695
|
+
appendValue(lines, 'Colors (前 8)', colors.join(', '));
|
|
696
|
+
appendValue(lines, 'Fonts (前 4)', fonts.join(', '));
|
|
697
|
+
appendValue(lines, 'Components (前 8)', components.join(', '));
|
|
698
|
+
appendValue(lines, 'CSS variables (前 8)', cssVariables.join(', '));
|
|
699
|
+
lines.push('完整 token: .mancode/aesthetics/style-tokens.json', '');
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function safeEntries(value, limit) {
|
|
703
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return [];
|
|
704
|
+
return Object.entries(value)
|
|
705
|
+
.filter(([key]) => /^[A-Za-z0-9_-]{1,80}$/.test(key))
|
|
706
|
+
.slice(0, limit);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function appendValue(lines, label, value) {
|
|
710
|
+
const clean = sanitize(value);
|
|
711
|
+
if (clean) lines.push(label + ': ' + clean);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function readPrompt(input) {
|
|
715
|
+
try {
|
|
716
|
+
const parsed = JSON.parse(input);
|
|
717
|
+
return typeof parsed.prompt === 'string' && parsed.prompt
|
|
718
|
+
? parsed.prompt
|
|
719
|
+
: input;
|
|
720
|
+
} catch {
|
|
721
|
+
return input;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function readJson(filePath) {
|
|
726
|
+
try {
|
|
727
|
+
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
728
|
+
} catch {
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function sanitize(value) {
|
|
734
|
+
return String(value ?? '').replace(/[\r\n]/g, ' ').slice(0, 200);
|
|
735
|
+
}
|
|
722
736
|
`;
|
|
723
737
|
var SOLO_SKILL = `# mancode \xB7 solo mode
|
|
724
738
|
|
|
@@ -1247,7 +1261,7 @@ ${spec.body}
|
|
|
1247
1261
|
}
|
|
1248
1262
|
|
|
1249
1263
|
// src/installers/common.ts
|
|
1250
|
-
import {
|
|
1264
|
+
import { mkdir as mkdir2, readFile as readFile2, stat, writeFile as writeFile2 } from "fs/promises";
|
|
1251
1265
|
import path2 from "path";
|
|
1252
1266
|
|
|
1253
1267
|
// src/system/team-memory.ts
|
|
@@ -1437,12 +1451,10 @@ async function readTextIfExists(filePath) {
|
|
|
1437
1451
|
}
|
|
1438
1452
|
}
|
|
1439
1453
|
async function installHooks(hooksDir) {
|
|
1440
|
-
const sessionStartDst = path2.join(hooksDir, "session-start.
|
|
1454
|
+
const sessionStartDst = path2.join(hooksDir, "session-start.mjs");
|
|
1441
1455
|
await writeFile2(sessionStartDst, SESSION_START_HOOK, "utf-8");
|
|
1442
|
-
|
|
1443
|
-
const userPromptDst = path2.join(hooksDir, "user-prompt-submit.sh");
|
|
1456
|
+
const userPromptDst = path2.join(hooksDir, "user-prompt-submit.mjs");
|
|
1444
1457
|
await writeFile2(userPromptDst, USER_PROMPT_SUBMIT_HOOK, "utf-8");
|
|
1445
|
-
await chmod(userPromptDst, 493);
|
|
1446
1458
|
}
|
|
1447
1459
|
async function pathExists(p) {
|
|
1448
1460
|
try {
|
|
@@ -1460,12 +1472,18 @@ function isNodeError(err) {
|
|
|
1460
1472
|
var CLAUDE_SKILL_MANAGED_MARKER = "<!-- Managed by mancode:claude-skill. Do not edit this marker. -->";
|
|
1461
1473
|
var CLAUDE_AGENT_MANAGED_MARKER = "<!-- Managed by mancode:claude-agent. Do not edit this marker. -->";
|
|
1462
1474
|
var MANCODE_HOOK_COMMANDS = [
|
|
1475
|
+
'node ".mancode/hooks/session-start.mjs"',
|
|
1476
|
+
'node ".mancode/hooks/user-prompt-submit.mjs"'
|
|
1477
|
+
];
|
|
1478
|
+
var LEGACY_MANCODE_HOOK_COMMANDS = [
|
|
1463
1479
|
"bash .mancode/hooks/session-start.sh",
|
|
1464
1480
|
"bash .mancode/hooks/user-prompt-submit.sh"
|
|
1465
1481
|
];
|
|
1466
1482
|
function isGeneratedMancodeHookCommand(command) {
|
|
1467
1483
|
const normalized = command.trim();
|
|
1468
|
-
return MANCODE_HOOK_COMMANDS.some(
|
|
1484
|
+
return [...MANCODE_HOOK_COMMANDS, ...LEGACY_MANCODE_HOOK_COMMANDS].some(
|
|
1485
|
+
(item) => item === normalized
|
|
1486
|
+
);
|
|
1469
1487
|
}
|
|
1470
1488
|
var LEGACY_CLAUDE_SKILL_SETTINGS = {
|
|
1471
1489
|
solo: ".claude/skills/mancode-solo.md",
|
|
@@ -1494,6 +1512,14 @@ async function installClaudeCode(projectRoot, options) {
|
|
|
1494
1512
|
await installAgents(path3.join(claudeDir, "agents"), force);
|
|
1495
1513
|
}
|
|
1496
1514
|
await updateClaudeSettings(claudeDir, settings);
|
|
1515
|
+
await removeLegacyHookFiles(projectRoot);
|
|
1516
|
+
}
|
|
1517
|
+
async function removeLegacyHookFiles(projectRoot) {
|
|
1518
|
+
const hooksDir = path3.join(projectRoot, ".mancode", "hooks");
|
|
1519
|
+
await Promise.all([
|
|
1520
|
+
rm(path3.join(hooksDir, "session-start.sh"), { force: true }),
|
|
1521
|
+
rm(path3.join(hooksDir, "user-prompt-submit.sh"), { force: true })
|
|
1522
|
+
]);
|
|
1497
1523
|
}
|
|
1498
1524
|
async function installSoloSkill(skillsDir, force) {
|
|
1499
1525
|
await installProjectSkill(
|
|
@@ -2860,11 +2886,12 @@ function isPlatformName(platform) {
|
|
|
2860
2886
|
}
|
|
2861
2887
|
|
|
2862
2888
|
// src/system/detect-team.ts
|
|
2863
|
-
import {
|
|
2889
|
+
import { execFile } from "child_process";
|
|
2890
|
+
import { access as access2 } from "fs/promises";
|
|
2864
2891
|
import path10 from "path";
|
|
2865
2892
|
import process2 from "process";
|
|
2866
2893
|
import { promisify } from "util";
|
|
2867
|
-
var
|
|
2894
|
+
var execFileAsync = promisify(execFile);
|
|
2868
2895
|
var NO_TEAM = {
|
|
2869
2896
|
isTeam: false,
|
|
2870
2897
|
contributors: 1,
|
|
@@ -2872,101 +2899,68 @@ var NO_TEAM = {
|
|
|
2872
2899
|
hasRemote: false
|
|
2873
2900
|
};
|
|
2874
2901
|
var REMOTE_PATTERN = /github\.com|gitlab\.com|bitbucket\.org/;
|
|
2875
|
-
var
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
GIT_PAGER: "cat",
|
|
2881
|
-
PAGER: "cat",
|
|
2882
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
2883
|
-
}
|
|
2902
|
+
var GIT_ENV = {
|
|
2903
|
+
...process2.env,
|
|
2904
|
+
GIT_PAGER: "cat",
|
|
2905
|
+
PAGER: "cat",
|
|
2906
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
2884
2907
|
};
|
|
2885
2908
|
async function detectTeamStatus(projectRoot) {
|
|
2886
|
-
const gitDir = path10.join(projectRoot, ".git");
|
|
2887
2909
|
try {
|
|
2888
|
-
await
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
return NO_TEAM;
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2910
|
+
await access2(path10.join(projectRoot, ".git"));
|
|
2911
|
+
const inside = await runGit(projectRoot, [
|
|
2912
|
+
"rev-parse",
|
|
2913
|
+
"--is-inside-work-tree"
|
|
2914
|
+
]);
|
|
2915
|
+
if (inside.trim() !== "true") return NO_TEAM;
|
|
2916
|
+
const [allEmails, recentEmails, remotes] = await Promise.all([
|
|
2917
|
+
runGit(projectRoot, ["log", "--all", "--format=%ae"]),
|
|
2918
|
+
runGit(projectRoot, ["log", "--since=30 days ago", "--format=%ae"]),
|
|
2919
|
+
runGit(projectRoot, ["remote", "-v"])
|
|
2920
|
+
]);
|
|
2921
|
+
const contributors = countUniqueLines(allEmails);
|
|
2922
|
+
const recentActive = countUniqueLines(recentEmails);
|
|
2923
|
+
const hasRemote = REMOTE_PATTERN.test(remotes);
|
|
2924
|
+
return {
|
|
2925
|
+
isTeam: contributors > 1 && hasRemote && recentActive > 1,
|
|
2926
|
+
contributors: contributors || 1,
|
|
2927
|
+
recentActive: recentActive || 1,
|
|
2928
|
+
hasRemote
|
|
2929
|
+
};
|
|
2900
2930
|
} catch {
|
|
2901
2931
|
return NO_TEAM;
|
|
2902
2932
|
}
|
|
2903
|
-
const contributors = await countUniqueEmails(
|
|
2904
|
-
"git log --all --format='%ae'",
|
|
2905
|
-
projectRoot
|
|
2906
|
-
);
|
|
2907
|
-
const recentActive = await countUniqueEmails(
|
|
2908
|
-
"git log --since='30 days ago' --format='%ae'",
|
|
2909
|
-
projectRoot
|
|
2910
|
-
);
|
|
2911
|
-
const hasRemote = await checkRemote(projectRoot);
|
|
2912
|
-
const safeContributors = Number.isFinite(contributors) ? contributors : 0;
|
|
2913
|
-
const safeRecent = Number.isFinite(recentActive) ? recentActive : 0;
|
|
2914
|
-
const isTeam = safeContributors > 1 && hasRemote && safeRecent > 1;
|
|
2915
|
-
return {
|
|
2916
|
-
isTeam,
|
|
2917
|
-
contributors: safeContributors || 1,
|
|
2918
|
-
recentActive: safeRecent || 1,
|
|
2919
|
-
hasRemote
|
|
2920
|
-
};
|
|
2921
2933
|
}
|
|
2922
|
-
async function
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
} catch {
|
|
2931
|
-
return 0;
|
|
2932
|
-
}
|
|
2934
|
+
async function runGit(cwd, args) {
|
|
2935
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
2936
|
+
cwd,
|
|
2937
|
+
encoding: "utf-8",
|
|
2938
|
+
env: GIT_ENV,
|
|
2939
|
+
windowsHide: true
|
|
2940
|
+
});
|
|
2941
|
+
return stdout;
|
|
2933
2942
|
}
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
...EXEC_OPTS
|
|
2939
|
-
});
|
|
2940
|
-
return REMOTE_PATTERN.test(stdout);
|
|
2941
|
-
} catch {
|
|
2942
|
-
return false;
|
|
2943
|
-
}
|
|
2943
|
+
function countUniqueLines(output) {
|
|
2944
|
+
return new Set(
|
|
2945
|
+
output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
|
|
2946
|
+
).size;
|
|
2944
2947
|
}
|
|
2945
2948
|
|
|
2946
2949
|
// src/system/detect.ts
|
|
2947
|
-
import {
|
|
2950
|
+
import { execFile as execFile2 } from "child_process";
|
|
2951
|
+
import process3 from "process";
|
|
2948
2952
|
import { promisify as promisify2 } from "util";
|
|
2949
|
-
var
|
|
2950
|
-
async function detectSystemDeps() {
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
}
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
return false;
|
|
2961
|
-
}
|
|
2962
|
-
};
|
|
2963
|
-
const [bash, git, jq, node] = await Promise.all([
|
|
2964
|
-
check("bash"),
|
|
2965
|
-
check("git"),
|
|
2966
|
-
check("jq"),
|
|
2967
|
-
check("node")
|
|
2968
|
-
]);
|
|
2969
|
-
return { bash, git, jq, node };
|
|
2953
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
2954
|
+
async function detectSystemDeps(env = process3.env) {
|
|
2955
|
+
try {
|
|
2956
|
+
await execFileAsync2("git", ["--version"], {
|
|
2957
|
+
env,
|
|
2958
|
+
windowsHide: true
|
|
2959
|
+
});
|
|
2960
|
+
return { git: true };
|
|
2961
|
+
} catch {
|
|
2962
|
+
return { git: false };
|
|
2963
|
+
}
|
|
2970
2964
|
}
|
|
2971
2965
|
|
|
2972
2966
|
// src/system/project-profile.ts
|
|
@@ -3662,7 +3656,7 @@ var EXIT_ALREADY_INITIALIZED = 1;
|
|
|
3662
3656
|
var EXIT_NOT_A_PROJECT_DIR = 2;
|
|
3663
3657
|
var EXIT_INIT_FAILED = 5;
|
|
3664
3658
|
var DEFAULT_INIT_PLATFORM = "claude-code";
|
|
3665
|
-
async function init(rootDir =
|
|
3659
|
+
async function init(rootDir = process4.cwd(), options = {}) {
|
|
3666
3660
|
const mancodeDir = path13.join(rootDir, ".mancode");
|
|
3667
3661
|
const stateFile = path13.join(mancodeDir, "state.json");
|
|
3668
3662
|
const wasInitialized = await pathExists3(stateFile);
|
|
@@ -3711,20 +3705,12 @@ async function init(rootDir = process3.cwd(), options = {}) {
|
|
|
3711
3705
|
try {
|
|
3712
3706
|
console.log("\u2713 \u68C0\u6D4B\u7CFB\u7EDF\u4F9D\u8D56...");
|
|
3713
3707
|
const deps = await detectSystemDeps();
|
|
3714
|
-
if (!deps.
|
|
3715
|
-
console.error("\u2717 Missing required dependencies:");
|
|
3716
|
-
if (!deps.bash) console.error(" - bash (required)");
|
|
3717
|
-
if (!deps.git) console.error(" - git (required)");
|
|
3718
|
-
console.error("");
|
|
3719
|
-
console.error(" Install them and try again.");
|
|
3720
|
-
return EXIT_NOT_A_PROJECT_DIR;
|
|
3721
|
-
}
|
|
3722
|
-
if (!deps.jq) {
|
|
3708
|
+
if (!deps.git) {
|
|
3723
3709
|
console.log(
|
|
3724
|
-
"\u26A0\uFE0F
|
|
3710
|
+
"\u26A0\uFE0F Git not found (optional). Team auto-detection will use solo defaults."
|
|
3725
3711
|
);
|
|
3726
3712
|
} else {
|
|
3727
|
-
console.log("
|
|
3713
|
+
console.log(" git \u2713");
|
|
3728
3714
|
}
|
|
3729
3715
|
console.log("\u2713 \u68C0\u6D4B\u9879\u76EE\u7C7B\u578B...");
|
|
3730
3716
|
const profile = await detectProjectProfile(rootDir);
|
|
@@ -4005,7 +3991,7 @@ async function pathExists3(p) {
|
|
|
4005
3991
|
// src/commands/install.ts
|
|
4006
3992
|
import { promises as fs4 } from "fs";
|
|
4007
3993
|
import path15 from "path";
|
|
4008
|
-
import
|
|
3994
|
+
import process5 from "process";
|
|
4009
3995
|
|
|
4010
3996
|
// src/installers/platform-status.ts
|
|
4011
3997
|
import { promises as fs3 } from "fs";
|
|
@@ -4028,8 +4014,8 @@ async function checkPlatformReadiness(rootDir, platform) {
|
|
|
4028
4014
|
),
|
|
4029
4015
|
claudeHooksRegistered(rootDir),
|
|
4030
4016
|
pathsExist([
|
|
4031
|
-
path14.join(rootDir, ".mancode", "hooks", "session-start.
|
|
4032
|
-
path14.join(rootDir, ".mancode", "hooks", "user-prompt-submit.
|
|
4017
|
+
path14.join(rootDir, ".mancode", "hooks", "session-start.mjs"),
|
|
4018
|
+
path14.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
|
|
4033
4019
|
])
|
|
4034
4020
|
]);
|
|
4035
4021
|
const present = hasSoloSkill && registered && hasHookFiles;
|
|
@@ -4258,9 +4244,9 @@ function hooksRegistered(settings) {
|
|
|
4258
4244
|
if (!isRecord4(settings)) return false;
|
|
4259
4245
|
const hooks = settings.hooks;
|
|
4260
4246
|
if (!isRecord4(hooks)) return false;
|
|
4261
|
-
return hasHookCommand(hooks.SessionStart, ".mancode/hooks/session-start.
|
|
4247
|
+
return hasHookCommand(hooks.SessionStart, ".mancode/hooks/session-start.mjs") && hasHookCommand(
|
|
4262
4248
|
hooks.UserPromptSubmit,
|
|
4263
|
-
".mancode/hooks/user-prompt-submit.
|
|
4249
|
+
".mancode/hooks/user-prompt-submit.mjs"
|
|
4264
4250
|
);
|
|
4265
4251
|
}
|
|
4266
4252
|
function hasHookCommand(value, needle) {
|
|
@@ -4290,7 +4276,7 @@ var EXIT_OK2 = 0;
|
|
|
4290
4276
|
var EXIT_NOT_INITIALIZED = 1;
|
|
4291
4277
|
var EXIT_UNSUPPORTED_PLATFORM = 2;
|
|
4292
4278
|
var EXIT_INSTALL_FAILED = 3;
|
|
4293
|
-
async function install(rootDir =
|
|
4279
|
+
async function install(rootDir = process5.cwd(), platform = "claude-code", options = {}) {
|
|
4294
4280
|
const stateFile = path15.join(rootDir, ".mancode", "state.json");
|
|
4295
4281
|
if (!await pathExists5(stateFile)) {
|
|
4296
4282
|
console.error("\u2717 mancode not initialized.");
|
|
@@ -4452,9 +4438,9 @@ function isRecord5(value) {
|
|
|
4452
4438
|
// src/commands/list-platforms.ts
|
|
4453
4439
|
import { promises as fs5 } from "fs";
|
|
4454
4440
|
import path16 from "path";
|
|
4455
|
-
import
|
|
4441
|
+
import process6 from "process";
|
|
4456
4442
|
var EXIT_OK3 = 0;
|
|
4457
|
-
async function listPlatforms(rootDir =
|
|
4443
|
+
async function listPlatforms(rootDir = process6.cwd()) {
|
|
4458
4444
|
const installed = new Set(await readInstalledPlatforms(rootDir));
|
|
4459
4445
|
const platforms = getPlatformInstallers();
|
|
4460
4446
|
console.log("");
|
|
@@ -4492,16 +4478,16 @@ function describePlatform(platform) {
|
|
|
4492
4478
|
}
|
|
4493
4479
|
|
|
4494
4480
|
// src/commands/manps.ts
|
|
4495
|
-
import { access as
|
|
4481
|
+
import { access as access4 } from "fs/promises";
|
|
4496
4482
|
import path18 from "path";
|
|
4497
4483
|
import { createInterface } from "readline/promises";
|
|
4498
4484
|
|
|
4499
4485
|
// src/system/preseason.ts
|
|
4500
|
-
import { execFile } from "child_process";
|
|
4486
|
+
import { execFile as execFile3 } from "child_process";
|
|
4501
4487
|
import { createHash } from "crypto";
|
|
4502
4488
|
import { existsSync, readFileSync } from "fs";
|
|
4503
4489
|
import {
|
|
4504
|
-
access as
|
|
4490
|
+
access as access3,
|
|
4505
4491
|
lstat,
|
|
4506
4492
|
mkdir as mkdir7,
|
|
4507
4493
|
readFile as readFile8,
|
|
@@ -5061,7 +5047,7 @@ function architectureScannerUnavailableIssue() {
|
|
|
5061
5047
|
function runDepcruise(binary, projectRoot) {
|
|
5062
5048
|
return new Promise((resolve) => {
|
|
5063
5049
|
const needsShell = process.platform === "win32";
|
|
5064
|
-
|
|
5050
|
+
execFile3(
|
|
5065
5051
|
binary,
|
|
5066
5052
|
["--output-type", "json", "."],
|
|
5067
5053
|
{
|
|
@@ -5401,7 +5387,7 @@ function pathExistsSync(file) {
|
|
|
5401
5387
|
}
|
|
5402
5388
|
async function pathExists6(file) {
|
|
5403
5389
|
try {
|
|
5404
|
-
await
|
|
5390
|
+
await access3(file);
|
|
5405
5391
|
return true;
|
|
5406
5392
|
} catch {
|
|
5407
5393
|
return false;
|
|
@@ -5564,7 +5550,7 @@ async function readStdinLines() {
|
|
|
5564
5550
|
}
|
|
5565
5551
|
async function pathExists7(p) {
|
|
5566
5552
|
try {
|
|
5567
|
-
await
|
|
5553
|
+
await access4(p);
|
|
5568
5554
|
return true;
|
|
5569
5555
|
} catch {
|
|
5570
5556
|
return false;
|
|
@@ -5574,10 +5560,10 @@ async function pathExists7(p) {
|
|
|
5574
5560
|
// src/commands/refresh-style.ts
|
|
5575
5561
|
import { promises as fs6 } from "fs";
|
|
5576
5562
|
import path19 from "path";
|
|
5577
|
-
import
|
|
5563
|
+
import process7 from "process";
|
|
5578
5564
|
var EXIT_OK5 = 0;
|
|
5579
5565
|
var EXIT_NOT_INITIALIZED3 = 1;
|
|
5580
|
-
async function refreshStyle(rootDir =
|
|
5566
|
+
async function refreshStyle(rootDir = process7.cwd()) {
|
|
5581
5567
|
const stateFile = path19.join(rootDir, ".mancode", "state.json");
|
|
5582
5568
|
if (!await pathExists8(stateFile)) {
|
|
5583
5569
|
console.error("\u2717 mancode not initialized.");
|
|
@@ -5728,7 +5714,7 @@ async function pathExists8(p) {
|
|
|
5728
5714
|
import { spawn } from "child_process";
|
|
5729
5715
|
import { promises as fs7 } from "fs";
|
|
5730
5716
|
import path22 from "path";
|
|
5731
|
-
import
|
|
5717
|
+
import process8 from "process";
|
|
5732
5718
|
|
|
5733
5719
|
// src/system/workflow.ts
|
|
5734
5720
|
import {
|
|
@@ -6287,7 +6273,7 @@ var HOOK_ESTIMATE_TIMEOUT_MS = 2e3;
|
|
|
6287
6273
|
var EXIT_OK6 = 0;
|
|
6288
6274
|
var EXIT_NOT_INITIALIZED4 = 1;
|
|
6289
6275
|
var EXIT_CORRUPT_STATE = 2;
|
|
6290
|
-
async function status(rootDir =
|
|
6276
|
+
async function status(rootDir = process8.cwd(), options = {}) {
|
|
6291
6277
|
const stateFile = path22.join(rootDir, ".mancode", "state.json");
|
|
6292
6278
|
if (!await pathExists9(stateFile)) {
|
|
6293
6279
|
console.error("\u2717 mancode not initialized.");
|
|
@@ -6426,9 +6412,9 @@ function getEffectiveTeamStatus(state, config, detected) {
|
|
|
6426
6412
|
}
|
|
6427
6413
|
async function checkHooks(rootDir) {
|
|
6428
6414
|
const [sessionStart, userPromptSubmit, registered] = await Promise.all([
|
|
6429
|
-
pathExists9(path22.join(rootDir, ".mancode", "hooks", "session-start.
|
|
6415
|
+
pathExists9(path22.join(rootDir, ".mancode", "hooks", "session-start.mjs")),
|
|
6430
6416
|
pathExists9(
|
|
6431
|
-
path22.join(rootDir, ".mancode", "hooks", "user-prompt-submit.
|
|
6417
|
+
path22.join(rootDir, ".mancode", "hooks", "user-prompt-submit.mjs")
|
|
6432
6418
|
),
|
|
6433
6419
|
isRegistered(rootDir)
|
|
6434
6420
|
]);
|
|
@@ -6450,9 +6436,9 @@ function hooksRegistered2(settings) {
|
|
|
6450
6436
|
if (!isRecord8(settings)) return false;
|
|
6451
6437
|
const hooks = settings.hooks;
|
|
6452
6438
|
if (!isRecord8(hooks)) return false;
|
|
6453
|
-
return hasHookCommand2(hooks.SessionStart, ".mancode/hooks/session-start.
|
|
6439
|
+
return hasHookCommand2(hooks.SessionStart, ".mancode/hooks/session-start.mjs") && hasHookCommand2(
|
|
6454
6440
|
hooks.UserPromptSubmit,
|
|
6455
|
-
".mancode/hooks/user-prompt-submit.
|
|
6441
|
+
".mancode/hooks/user-prompt-submit.mjs"
|
|
6456
6442
|
);
|
|
6457
6443
|
}
|
|
6458
6444
|
function hasHookCommand2(value, needle) {
|
|
@@ -6470,7 +6456,7 @@ async function estimateHookInjection(rootDir) {
|
|
|
6470
6456
|
rootDir,
|
|
6471
6457
|
".mancode",
|
|
6472
6458
|
"hooks",
|
|
6473
|
-
"user-prompt-submit.
|
|
6459
|
+
"user-prompt-submit.mjs"
|
|
6474
6460
|
);
|
|
6475
6461
|
if (!await pathExists9(hookPath)) {
|
|
6476
6462
|
return { tokens: 0, cap: 800 };
|
|
@@ -6487,7 +6473,7 @@ async function estimateHookInjection(rootDir) {
|
|
|
6487
6473
|
}
|
|
6488
6474
|
function runHookEstimate(rootDir, hookPath) {
|
|
6489
6475
|
return new Promise((resolve, reject) => {
|
|
6490
|
-
const child = spawn(
|
|
6476
|
+
const child = spawn(process8.execPath, [hookPath], {
|
|
6491
6477
|
cwd: rootDir,
|
|
6492
6478
|
stdio: ["pipe", "pipe", "ignore"]
|
|
6493
6479
|
});
|
|
@@ -6571,9 +6557,9 @@ function printText(r) {
|
|
|
6571
6557
|
console.log("");
|
|
6572
6558
|
if (r.platforms.includes("claude-code")) {
|
|
6573
6559
|
console.log("Hooks:");
|
|
6574
|
-
console.log(` ${r.hooks.sessionStart ? "\u2713" : "\u2717"} session-start.
|
|
6560
|
+
console.log(` ${r.hooks.sessionStart ? "\u2713" : "\u2717"} session-start.mjs`);
|
|
6575
6561
|
console.log(
|
|
6576
|
-
` ${r.hooks.userPromptSubmit ? "\u2713" : "\u2717"} user-prompt-submit.
|
|
6562
|
+
` ${r.hooks.userPromptSubmit ? "\u2713" : "\u2717"} user-prompt-submit.mjs`
|
|
6577
6563
|
);
|
|
6578
6564
|
console.log(
|
|
6579
6565
|
` ${r.hooks.registered ? "\u2713" : "\u2717"} registered in .claude/settings.json`
|
|
@@ -6605,13 +6591,13 @@ async function pathExists9(p) {
|
|
|
6605
6591
|
}
|
|
6606
6592
|
|
|
6607
6593
|
// src/commands/uninstall.ts
|
|
6608
|
-
import { access as
|
|
6594
|
+
import { access as access5, readFile as readFile11, rm as rm5, writeFile as writeFile12 } from "fs/promises";
|
|
6609
6595
|
import path23 from "path";
|
|
6610
|
-
import
|
|
6596
|
+
import process9 from "process";
|
|
6611
6597
|
var EXIT_OK7 = 0;
|
|
6612
6598
|
var EXIT_NOT_INITIALIZED5 = 1;
|
|
6613
6599
|
var EXIT_UNSUPPORTED_PLATFORM2 = 2;
|
|
6614
|
-
async function uninstall(rootDir =
|
|
6600
|
+
async function uninstall(rootDir = process9.cwd(), platform, options = {}) {
|
|
6615
6601
|
const stateFile = path23.join(rootDir, ".mancode", "state.json");
|
|
6616
6602
|
if (!await pathExists10(stateFile)) {
|
|
6617
6603
|
console.error("\u2717 mancode not initialized.");
|
|
@@ -6834,7 +6820,7 @@ function isRecord9(value) {
|
|
|
6834
6820
|
}
|
|
6835
6821
|
async function pathExists10(p) {
|
|
6836
6822
|
try {
|
|
6837
|
-
await
|
|
6823
|
+
await access5(p);
|
|
6838
6824
|
return true;
|
|
6839
6825
|
} catch {
|
|
6840
6826
|
return false;
|
|
@@ -6842,17 +6828,17 @@ async function pathExists10(p) {
|
|
|
6842
6828
|
}
|
|
6843
6829
|
|
|
6844
6830
|
// src/commands/version.ts
|
|
6845
|
-
import
|
|
6831
|
+
import process10 from "process";
|
|
6846
6832
|
function version() {
|
|
6847
|
-
const nodeVersion =
|
|
6848
|
-
const platform = `${
|
|
6833
|
+
const nodeVersion = process10.version;
|
|
6834
|
+
const platform = `${process10.platform}/${process10.arch}`;
|
|
6849
6835
|
console.log(`mancode/${VERSION}`);
|
|
6850
6836
|
console.log(`node/${nodeVersion.replace("v", "")}`);
|
|
6851
6837
|
console.log(platform);
|
|
6852
6838
|
}
|
|
6853
6839
|
|
|
6854
6840
|
// src/commands/workflow.ts
|
|
6855
|
-
import { access as
|
|
6841
|
+
import { access as access6 } from "fs/promises";
|
|
6856
6842
|
import path24 from "path";
|
|
6857
6843
|
var EXIT_OK8 = 0;
|
|
6858
6844
|
var EXIT_NOT_INITIALIZED6 = 1;
|
|
@@ -7339,7 +7325,7 @@ function ago(iso) {
|
|
|
7339
7325
|
}
|
|
7340
7326
|
async function pathExists11(p) {
|
|
7341
7327
|
try {
|
|
7342
|
-
await
|
|
7328
|
+
await access6(p);
|
|
7343
7329
|
return true;
|
|
7344
7330
|
} catch {
|
|
7345
7331
|
return false;
|