@ionivetech/mugiwara 0.8.2 → 0.9.0
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.kimi-plugin/plugin.json +1 -1
- package/README.md +61 -56
- package/content/agents/luffy-orchestrator.md +1 -0
- package/content/skills/mugiwara-lessons/SKILL.md +2 -0
- package/content/skills/mugiwara-orchestration/SKILL.md +9 -18
- package/content/skills/mugiwara-orchestration/references/solo-team.md +18 -0
- package/content/skills/mugiwara-planning/SKILL.md +7 -15
- package/content/skills/mugiwara-planning/references/sub-missions.md +14 -0
- package/content/skills/mugiwara-workflow/SKILL.md +1 -1
- package/dist/mugiwara.js +720 -97
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/references/multi-actor.md +17 -14
- package/scripts/gate-selftest.ts +138 -0
- package/scripts/savepoint.sh +123 -10
- package/scripts/validate-content.ts +168 -0
- package/src/args.ts +1 -1
- package/src/cli.ts +143 -3
- package/src/config.ts +33 -11
- package/src/mission.ts +137 -52
package/gemini-extension.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ionivetech/mugiwara",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "The Straw Hat crew of AI agents and skills: brainstorm, plan, execute, checkpoint, quality, gates, review, security, self-healing. Installs into Claude Code, opencode, Copilot, Gemini, Codex, Cursor, Kimi, pi, Windsurf, Cline, Kilo, Antigravity.",
|
|
5
5
|
"homepage": "https://github.com/ionivetech/mugiwara#readme",
|
|
6
6
|
"repository": {
|
package/plugin.json
CHANGED
|
@@ -9,13 +9,16 @@ its own file inside the mission folder, so parallel work never collides:
|
|
|
9
9
|
|
|
10
10
|
```
|
|
11
11
|
.mugiwara/
|
|
12
|
-
├──
|
|
13
|
-
├──
|
|
14
|
-
├──
|
|
15
|
-
├──
|
|
16
|
-
├──
|
|
17
|
-
├──
|
|
18
|
-
├──
|
|
12
|
+
├── missions/<mission>/state.json # solo mission state
|
|
13
|
+
├── missions/<mission>/<member>.json # team member state
|
|
14
|
+
├── missions/<mission>/continue.json # solo resume point (D10)
|
|
15
|
+
├── missions/<mission>/continue-<member>.json # team member resume point
|
|
16
|
+
├── missions/<mission>/plan.md # shared plan (source of truth)
|
|
17
|
+
├── missions/<mission>/flows/ # flow evidence
|
|
18
|
+
├── missions/<mission>/report.md # closure report
|
|
19
|
+
├── lessons.md # shared ledger (append-only)
|
|
20
|
+
├── config
|
|
21
|
+
└── index.md
|
|
19
22
|
```
|
|
20
23
|
|
|
21
24
|
Branch is an implementation detail, not an identity: a member can hold several
|
|
@@ -31,11 +34,11 @@ $ mugiwara reset
|
|
|
31
34
|
✗ Active mission for 'john'. Use --force to override.
|
|
32
35
|
```
|
|
33
36
|
|
|
34
|
-
`--force` still preserves
|
|
37
|
+
`--force` still preserves `.mugiwara/lessons.md` and `config`.
|
|
35
38
|
|
|
36
39
|
## Shared state
|
|
37
40
|
|
|
38
|
-
The lessons ledger (
|
|
41
|
+
The lessons ledger (`.mugiwara/lessons.md`) stays shared — that is the point of it.
|
|
39
42
|
All actors read and write to the same file. Append-only, never overwrite.
|
|
40
43
|
|
|
41
44
|
## Member namespacing
|
|
@@ -44,13 +47,13 @@ All actors read and write to the same file. Append-only, never overwrite.
|
|
|
44
47
|
member argument) write `state.json`; team missions write `<member>.json`:
|
|
45
48
|
|
|
46
49
|
```bash
|
|
47
|
-
mugiwara savepoint dark-mode # solo →
|
|
48
|
-
mugiwara savepoint payment-gateway john # team →
|
|
49
|
-
mugiwara savepoint payment-gateway patty #
|
|
50
|
+
mugiwara savepoint dark-mode # solo → missions/dark-mode/state.json
|
|
51
|
+
mugiwara savepoint payment-gateway john # team → missions/payment-gateway/john.json
|
|
52
|
+
mugiwara savepoint payment-gateway patty # missions/payment-gateway/patty.json
|
|
50
53
|
```
|
|
51
54
|
|
|
52
|
-
The resume point follows the same scoping: `
|
|
53
|
-
(team) or `
|
|
55
|
+
The resume point follows the same scoping: `missions/<mission>/continue-<member>.json`
|
|
56
|
+
(team) or `missions/<mission>/continue.json` (solo), so parallel members never
|
|
54
57
|
clobber each other's resume position.
|
|
55
58
|
|
|
56
59
|
## Resuming your own work
|
package/scripts/gate-selftest.ts
CHANGED
|
@@ -830,5 +830,143 @@ console.log('\nB7 — zero evidence');
|
|
|
830
830
|
}
|
|
831
831
|
}
|
|
832
832
|
|
|
833
|
+
// --- W1: mode from config — revert to positional-only → gate fails ---
|
|
834
|
+
console.log('\nW1 — mode from config');
|
|
835
|
+
{
|
|
836
|
+
const sp = join(root, 'scripts', 'savepoint.sh');
|
|
837
|
+
const original = readFileSync(sp, 'utf8');
|
|
838
|
+
try {
|
|
839
|
+
const broken = original.replace(
|
|
840
|
+
/# mode: positional > env > project config > global config > guided[\s\S]*?MODE="\$\{MODE:-guided\}"/,
|
|
841
|
+
'MODE="${4:-${STATE_MODE:-guided}}"'
|
|
842
|
+
);
|
|
843
|
+
if (broken === original) {
|
|
844
|
+
console.error('✗ W1: mutation target not found');
|
|
845
|
+
failed++;
|
|
846
|
+
} else {
|
|
847
|
+
writeFileSync(sp, broken);
|
|
848
|
+
assert('reverted MODE → savepoint mode test fails', false, () => run('W1', 'grep -q "for _cfg in.*MUGIWARA_DIR/config" scripts/savepoint.sh'));
|
|
849
|
+
}
|
|
850
|
+
} finally {
|
|
851
|
+
writeFileSync(sp, original);
|
|
852
|
+
assert('restored → mode fallback present', true, () => run('W1-restore', 'grep -q "for _cfg in.*MUGIWARA_DIR/config" scripts/savepoint.sh'));
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// --- W2: Solo-or-team section — remove → content validation fails ---
|
|
857
|
+
console.log('\nW2 — Solo-or-team section');
|
|
858
|
+
{
|
|
859
|
+
const f = join(root, 'content', 'skills', 'mugiwara-orchestration', 'SKILL.md');
|
|
860
|
+
const original = readFileSync(f, 'utf8');
|
|
861
|
+
try {
|
|
862
|
+
const broken = original.replace(/## Solo or team \(Flow 0\)[\s\S]*?## Request classifier/, '## Request classifier');
|
|
863
|
+
if (broken === original) {
|
|
864
|
+
console.error('✗ W2: mutation target not found');
|
|
865
|
+
failed++;
|
|
866
|
+
} else {
|
|
867
|
+
writeFileSync(f, broken);
|
|
868
|
+
assert('removed Solo-or-team → content validation fails', false, () => run('W2', 'grep -q "Solo or team" content/skills/mugiwara-orchestration/SKILL.md'));
|
|
869
|
+
}
|
|
870
|
+
} finally {
|
|
871
|
+
writeFileSync(f, original);
|
|
872
|
+
assert('restored → Solo-or-team present', true, () => run('W2-restore', 'grep -q "Solo or team" content/skills/mugiwara-orchestration/SKILL.md'));
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// --- W4: allow layout switch without migrate → lane-integrity fails ---
|
|
877
|
+
console.log('\nW4 — layout switch guard');
|
|
878
|
+
{
|
|
879
|
+
const sp = join(root, 'scripts', 'savepoint.sh');
|
|
880
|
+
const original = readFileSync(sp, 'utf8');
|
|
881
|
+
try {
|
|
882
|
+
const broken = original.replace(/is solo \(state\.json exists\)/, 'REMOVED');
|
|
883
|
+
if (broken === original) {
|
|
884
|
+
console.error('✗ W4: mutation target not found');
|
|
885
|
+
failed++;
|
|
886
|
+
} else {
|
|
887
|
+
writeFileSync(sp, broken);
|
|
888
|
+
assert('guard removed → check fails', false, () => run('W4', 'grep -q "is solo (state.json exists)" scripts/savepoint.sh'));
|
|
889
|
+
}
|
|
890
|
+
} finally {
|
|
891
|
+
writeFileSync(sp, original);
|
|
892
|
+
assert('restored → guard present', true, () => run('W4-restore', 'grep -q "is solo (state.json exists)" scripts/savepoint.sh'));
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// --- W7: remove posture import → --check-wiring fails ---
|
|
897
|
+
console.log('\nW7 — wiring gate');
|
|
898
|
+
{
|
|
899
|
+
const mf = join(root, 'src', 'mission.ts');
|
|
900
|
+
const original = readFileSync(mf, 'utf8');
|
|
901
|
+
try {
|
|
902
|
+
const broken = original.replace(/import \{ selectPosture \} from '\.\/posture\.ts';/, '// removed');
|
|
903
|
+
if (broken === original) {
|
|
904
|
+
console.error('✗ W7: mutation target not found');
|
|
905
|
+
failed++;
|
|
906
|
+
} else {
|
|
907
|
+
writeFileSync(mf, broken);
|
|
908
|
+
assert('removed posture import → --check-wiring fails', false, () => run('W7', 'bun scripts/validate-content.ts --check-wiring'));
|
|
909
|
+
}
|
|
910
|
+
} finally {
|
|
911
|
+
writeFileSync(mf, original);
|
|
912
|
+
assert('restored → wiring passes', true, () => run('W7-restore', 'bun scripts/validate-content.ts --check-wiring'));
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// --- W11: delete lane_scope_glob from config.md → --check-config fails ---
|
|
917
|
+
console.log('\nW11 — config drift');
|
|
918
|
+
{
|
|
919
|
+
const cf = join(root, 'docs', 'concepts', 'config.md');
|
|
920
|
+
const original = readFileSync(cf, 'utf8');
|
|
921
|
+
try {
|
|
922
|
+
const broken = original.replace(/lane_scope_glob/g, 'LANE_REMOVED');
|
|
923
|
+
if (broken === original) {
|
|
924
|
+
console.error('✗ W11: mutation target not found');
|
|
925
|
+
failed++;
|
|
926
|
+
} else {
|
|
927
|
+
writeFileSync(cf, broken);
|
|
928
|
+
assert('removed lane_scope_glob → --check-config fails', false, () => run('W11', 'bun scripts/validate-content.ts --check-config'));
|
|
929
|
+
}
|
|
930
|
+
} finally {
|
|
931
|
+
writeFileSync(cf, original);
|
|
932
|
+
assert('restored → --check-config passes', true, () => run('W11-restore', 'bun scripts/validate-content.ts --check-config'));
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// --- W15: paste raw JSONL into report.md → reporting test fails ---
|
|
937
|
+
console.log('\nW15 — report raw JSONL');
|
|
938
|
+
{
|
|
939
|
+
const mf = join(root, 'src', 'mission.ts');
|
|
940
|
+
const original = readFileSync(mf, 'utf8');
|
|
941
|
+
try {
|
|
942
|
+
const broken = original.replace(/\/\/ W15: no raw JSONL in report.*/, ' if (hasCostEvents) fold.push("cost-events.jsonl"); // W15 revert');
|
|
943
|
+
if (broken === original) {
|
|
944
|
+
console.error('✗ W15: mutation target not found');
|
|
945
|
+
failed++;
|
|
946
|
+
} else {
|
|
947
|
+
writeFileSync(mf, broken);
|
|
948
|
+
assert('reverted raw JSONL → check fails', false, () => run('W15', 'grep -q "hasCostEvents" src/mission.ts | grep -q "fold.push"'));
|
|
949
|
+
}
|
|
950
|
+
} finally {
|
|
951
|
+
writeFileSync(mf, original);
|
|
952
|
+
assert('restored → no raw JSONL', true, () => run('W15-restore', 'grep -q "no raw JSONL" src/mission.ts'));
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// --- W12: point doc at state/<mission>/ → --check-doc-integrity fails ---
|
|
957
|
+
console.log('\nW12 — doc integrity stale path');
|
|
958
|
+
{
|
|
959
|
+
const ref = join(root, 'references', 'multi-actor.md');
|
|
960
|
+
const original = readFileSync(ref, 'utf8');
|
|
961
|
+
try {
|
|
962
|
+
const broken = original + '\n`state/<mission>/state.json`\n';
|
|
963
|
+
writeFileSync(ref, broken);
|
|
964
|
+
assert('stale path → --check-doc-integrity fails', false, () => run('W12', 'bun scripts/validate-content.ts --check-doc-integrity'));
|
|
965
|
+
} finally {
|
|
966
|
+
writeFileSync(ref, original);
|
|
967
|
+
assert('restored → doc-integrity passes', true, () => run('W12-restore', 'bun scripts/validate-content.ts --check-doc-integrity'));
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
833
971
|
console.log(`\n${passed} passed, ${failed} failed`);
|
|
834
972
|
process.exit(failed > 0 ? 1 : 0);
|
package/scripts/savepoint.sh
CHANGED
|
@@ -63,9 +63,27 @@ if [ -n "$GIT_NAME" ] && [ -n "$GIT_EMAIL" ]; then GIT_ID="$GIT_NAME <$GIT_EMAIL
|
|
|
63
63
|
|
|
64
64
|
# --- parse mission args: <mission> [member] [wave] [mode] ---
|
|
65
65
|
MISSION="${1:-${STATE_MISSION:-}}"
|
|
66
|
+
# member: positional > env > config team_member > empty (solo). Never derive
|
|
67
|
+
# silently from git identity — solo vs team is a recorded Flow 0 decision, not
|
|
68
|
+
# an inference. (W3)
|
|
66
69
|
MEMBER="${2:-${STATE_MEMBER:-}}"
|
|
70
|
+
if [ -z "$MEMBER" ] && [ -f "$MUGIWARA_DIR/config" ]; then
|
|
71
|
+
MEMBER=$(grep -E '^team_member=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | cut -d'#' -f1 | tr -d '[:space:]')
|
|
72
|
+
fi
|
|
67
73
|
WAVE="${3:-${STATE_WAVE:-1}}"
|
|
68
|
-
|
|
74
|
+
# mode: positional > env > project config > global config > guided. The hook
|
|
75
|
+
# passes it positionally; direct script and CLI calls must fall back to config
|
|
76
|
+
# or 11 of 12 harnesses record the wrong mode. (W1)
|
|
77
|
+
MODE="${4:-${STATE_MODE:-}}"
|
|
78
|
+
if [ -z "$MODE" ]; then
|
|
79
|
+
for _cfg in "$MUGIWARA_DIR/config" "$HOME/.mugiwara/config"; do
|
|
80
|
+
[ -f "$_cfg" ] || continue
|
|
81
|
+
_m=$(grep -E '^mode=' "$_cfg" 2>/dev/null | head -1 | cut -d= -f2- | cut -d'#' -f1 | tr -d '[:space:]')
|
|
82
|
+
[ -n "$_m" ] && { MODE="$_m"; break; }
|
|
83
|
+
done
|
|
84
|
+
fi
|
|
85
|
+
MODE="${MODE:-guided}"
|
|
86
|
+
case "$MODE" in guided|semi|auto) ;; *) MODE="guided" ;; esac
|
|
69
87
|
# Triage lane (M7): the lane Luffy assigned at Flow 0. Without it savepoint
|
|
70
88
|
# recomputed the lane from file counts alone and silently discarded the
|
|
71
89
|
# triage decision — a Lane 3 mission recorded itself as "direct". Explicit
|
|
@@ -91,7 +109,7 @@ esac
|
|
|
91
109
|
# verbosity from config (project .mugiwara/config), default normal; env override
|
|
92
110
|
VERBOSITY="${STATE_VERBOSITY:-normal}"
|
|
93
111
|
if [ -f "$MUGIWARA_DIR/config" ]; then
|
|
94
|
-
CFG_VERBOSITY=$(grep -E '^verbosity=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '[:space:]')
|
|
112
|
+
CFG_VERBOSITY=$(grep -E '^verbosity=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | cut -d'#' -f1 | tr -d '[:space:]')
|
|
95
113
|
[ -n "$CFG_VERBOSITY" ] && VERBOSITY="$CFG_VERBOSITY"
|
|
96
114
|
fi
|
|
97
115
|
case "$VERBOSITY" in
|
|
@@ -102,7 +120,7 @@ esac
|
|
|
102
120
|
# heal_max_cycles from config (project .mugiwara/config), default 3; env override
|
|
103
121
|
HEAL_MAX_CYCLES="${STATE_HEAL_MAX_CYCLES:-3}"
|
|
104
122
|
if [ -f "$MUGIWARA_DIR/config" ]; then
|
|
105
|
-
CFG_HEAL_MAX=$(grep -E '^heal_max_cycles=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '[:space:]')
|
|
123
|
+
CFG_HEAL_MAX=$(grep -E '^heal_max_cycles=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | cut -d'#' -f1 | tr -d '[:space:]')
|
|
106
124
|
[ -n "$CFG_HEAL_MAX" ] && HEAL_MAX_CYCLES="$CFG_HEAL_MAX"
|
|
107
125
|
fi
|
|
108
126
|
case "$HEAL_MAX_CYCLES" in
|
|
@@ -113,7 +131,7 @@ esac
|
|
|
113
131
|
# delegate_threshold from config (project .mugiwara/config), default 60; env override
|
|
114
132
|
DELEGATE_THRESHOLD="${STATE_DELEGATE_THRESHOLD:-60}"
|
|
115
133
|
if [ -f "$MUGIWARA_DIR/config" ]; then
|
|
116
|
-
CFG_DELEGATE=$(grep -E '^delegate_threshold=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '[:space:]')
|
|
134
|
+
CFG_DELEGATE=$(grep -E '^delegate_threshold=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | cut -d'#' -f1 | tr -d '[:space:]')
|
|
117
135
|
[ -n "$CFG_DELEGATE" ] && DELEGATE_THRESHOLD="$CFG_DELEGATE"
|
|
118
136
|
fi
|
|
119
137
|
case "$DELEGATE_THRESHOLD" in
|
|
@@ -183,6 +201,17 @@ else
|
|
|
183
201
|
CONTINUE_FILE="$MISSION_DIR/continue.json"
|
|
184
202
|
fi
|
|
185
203
|
|
|
204
|
+
# A mission is solo or team, never both. Two layouts side by side orphan one of
|
|
205
|
+
# them: hidden from `status`, still read by the integrity gate. (W4)
|
|
206
|
+
if [ -n "$MEMBER" ] && [ -f "$MISSION_DIR/state.json" ] && [ "${MUGIWARA_ALLOW_LAYOUT_SWITCH:-0}" != "1" ]; then
|
|
207
|
+
die "mission '$MISSION' is solo (state.json exists) — refusing to add member '$MEMBER'. Run: mugiwara migrate --to-team $MEMBER"
|
|
208
|
+
fi
|
|
209
|
+
if [ -z "$MEMBER" ] && ls "$MISSION_DIR"/*.json >/dev/null 2>&1; then
|
|
210
|
+
if ls "$MISSION_DIR"/*.json | grep -qv -e 'state.json' -e 'continue'; then
|
|
211
|
+
die "mission '$MISSION' is team — pass a member: savepoint.sh $MISSION <member> ..."
|
|
212
|
+
fi
|
|
213
|
+
fi
|
|
214
|
+
|
|
186
215
|
[ -z "$MISSION" ] && die "usage: savepoint.sh <mission> [member] [wave] [mode] [lane]"
|
|
187
216
|
|
|
188
217
|
# --- computed fields ---
|
|
@@ -192,7 +221,7 @@ HEAD_SHA=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
|
|
|
192
221
|
# lane_scope_glob (T5): monorepo scoping — count only files matching the glob
|
|
193
222
|
LANE_SCOPE_GLOB=""
|
|
194
223
|
if [ -f "$MUGIWARA_DIR/config" ]; then
|
|
195
|
-
_cfg_scope=$(grep -E '^lane_scope_glob=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | tr -d '"' | tr -d "'")
|
|
224
|
+
_cfg_scope=$(grep -E '^lane_scope_glob=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | cut -d'#' -f1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | tr -d '"' | tr -d "'")
|
|
196
225
|
[ -n "$_cfg_scope" ] && LANE_SCOPE_GLOB="$_cfg_scope"
|
|
197
226
|
fi
|
|
198
227
|
# union of committed + staged + unstaged + untracked (F) — see patterns.sh
|
|
@@ -395,6 +424,16 @@ if [ "$HEAL_CYCLE" -ge "$HEAL_MAX_CYCLES" ] 2>/dev/null; then
|
|
|
395
424
|
HEAL_HALT=true
|
|
396
425
|
fi
|
|
397
426
|
|
|
427
|
+
# W10: register plan.md read so repeated_reads is not structurally zero (evidence.registerRead)
|
|
428
|
+
if [ -f "$MISSION_DIR/plan.md" ] && [ ! -s "$MISSION_DIR/context-registry.jsonl" ]; then
|
|
429
|
+
mkdir -p "$MISSION_DIR"
|
|
430
|
+
_plan_fp=$(node -e "const crypto=require('crypto');const fs=require('fs');try{const d=fs.readFileSync(process.argv[1],'utf8');process.stdout.write(crypto.createHash('sha256').update(d).digest('hex'))}catch(e){process.stdout.write('')}" "$MISSION_DIR/plan.md" 2>/dev/null || true)
|
|
431
|
+
if [ -n "$_plan_fp" ]; then
|
|
432
|
+
_plan_chars=$(wc -c < "$MISSION_DIR/plan.md" 2>/dev/null | tr -d ' ' || echo 0)
|
|
433
|
+
printf '{"fingerprint":"%s","kind":"file","file":"plan.md","id":"E001","reads":1,"chars":%s,"ref":"E001 plan.md"}\n' "$_plan_fp" "$_plan_chars" >> "$MISSION_DIR/context-registry.jsonl" 2>/dev/null || true
|
|
434
|
+
fi
|
|
435
|
+
fi
|
|
436
|
+
|
|
398
437
|
# slop — context (repeated reads) per cost-governor §§21-24,31-32 — T5 wire all crews Luffy/Nami/Zoro/Brook
|
|
399
438
|
REPEATED_READS=0
|
|
400
439
|
REPEATED_THRESHOLD=3
|
|
@@ -410,11 +449,11 @@ fi
|
|
|
410
449
|
# gates flow stage can read, not prose.
|
|
411
450
|
DEPTH_REVIEW="full"; DEPTH_QUALITY="full"; DEPTH_VERIFY="off"
|
|
412
451
|
if [ -f "$MUGIWARA_DIR/config" ]; then
|
|
413
|
-
_cfg_r=$(grep -E '^review_depth=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '[:space:]')
|
|
452
|
+
_cfg_r=$(grep -E '^review_depth=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | cut -d'#' -f1 | tr -d '[:space:]')
|
|
414
453
|
[ -n "$_cfg_r" ] && DEPTH_REVIEW="$_cfg_r"
|
|
415
|
-
_cfg_q=$(grep -E '^quality_depth=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '[:space:]')
|
|
454
|
+
_cfg_q=$(grep -E '^quality_depth=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | cut -d'#' -f1 | tr -d '[:space:]')
|
|
416
455
|
[ -n "$_cfg_q" ] && DEPTH_QUALITY="$_cfg_q"
|
|
417
|
-
_cfg_v=$(grep -E '^verify_merged=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '[:space:]')
|
|
456
|
+
_cfg_v=$(grep -E '^verify_merged=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | cut -d'#' -f1 | tr -d '[:space:]')
|
|
418
457
|
[ -n "$_cfg_v" ] && DEPTH_VERIFY="$_cfg_v"
|
|
419
458
|
fi
|
|
420
459
|
case "$DEPTH_REVIEW" in full|standard|lean) ;; *) DEPTH_REVIEW="full" ;; esac
|
|
@@ -522,6 +561,75 @@ if [ "$BUDGET" -gt 0 ] 2>/dev/null; then
|
|
|
522
561
|
fi
|
|
523
562
|
fi
|
|
524
563
|
|
|
564
|
+
# team_members for posture (W5) — config key team_members, default 1
|
|
565
|
+
TEAM_MEMBERS=1
|
|
566
|
+
if [ -f "$MUGIWARA_DIR/config" ]; then
|
|
567
|
+
_tm=$(grep -E '^team_members=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | cut -d'#' -f1 | tr -d '[:space:]')
|
|
568
|
+
[ -n "$_tm" ] && TEAM_MEMBERS="$_tm"
|
|
569
|
+
fi
|
|
570
|
+
case "$TEAM_MEMBERS" in ''|*[!0-9]*) TEAM_MEMBERS=1 ;; esac
|
|
571
|
+
# plan metrics for posture decision (phase-isolated / parallel)
|
|
572
|
+
PLAN_LINES=0
|
|
573
|
+
PHASES=1
|
|
574
|
+
INDEPENDENT_TASKS=0
|
|
575
|
+
if [ -f "$MISSION_DIR/plan.md" ]; then
|
|
576
|
+
PLAN_LINES=$(wc -l < "$MISSION_DIR/plan.md" 2>/dev/null | tr -d ' ' || echo 0)
|
|
577
|
+
PH=$(grep -c "^## Wave" "$MISSION_DIR/plan.md" 2>/dev/null || true)
|
|
578
|
+
[ "$PH" -gt 0 ] 2>/dev/null && PHASES="$PH"
|
|
579
|
+
INDEPENDENT_TASKS=$(grep -c "\[PARALLEL\]" "$MISSION_DIR/plan.md" 2>/dev/null || true)
|
|
580
|
+
fi
|
|
581
|
+
# governor for posture
|
|
582
|
+
GOVERNOR="normal"
|
|
583
|
+
case "$STATUS" in
|
|
584
|
+
stop) GOVERNOR="stop" ;;
|
|
585
|
+
warn) GOVERNOR="avoid" ;;
|
|
586
|
+
*) GOVERNOR="normal" ;;
|
|
587
|
+
esac
|
|
588
|
+
CONTEXT_PRESSURE=false
|
|
589
|
+
if [ "$BUDGET" -gt 0 ] 2>/dev/null && [ "$TOKENS_EST" -gt $(( BUDGET * 6 / 10 )) ] 2>/dev/null; then
|
|
590
|
+
CONTEXT_PRESSURE=true
|
|
591
|
+
fi
|
|
592
|
+
POSTURE="inline-sequential"
|
|
593
|
+
POSTURE_REASON="no parallel/phase/team/relief trigger — default inline in plan order"
|
|
594
|
+
POSTURE_PAUSE=false
|
|
595
|
+
if [ "$GOVERNOR" = "stop" ]; then
|
|
596
|
+
POSTURE="inline-sequential"
|
|
597
|
+
POSTURE_REASON="governor stop — pause safely, keep inline; state + continue emitted"
|
|
598
|
+
POSTURE_PAUSE=true
|
|
599
|
+
elif [ "$TEAM_MEMBERS" -gt 1 ] 2>/dev/null; then
|
|
600
|
+
POSTURE="team-scoped"
|
|
601
|
+
POSTURE_REASON="$TEAM_MEMBERS team members with non-overlapping scope"
|
|
602
|
+
elif [ "$PHASES" -gt 3 ] 2>/dev/null || [ "$PLAN_LINES" -gt 1500 ] 2>/dev/null; then
|
|
603
|
+
POSTURE="phase-isolated"
|
|
604
|
+
POSTURE_REASON="large campaign — $PHASES phases / $PLAN_LINES lines"
|
|
605
|
+
elif [ "$CONTEXT_PRESSURE" = true ]; then
|
|
606
|
+
POSTURE="context-relief"
|
|
607
|
+
POSTURE_REASON="context pressure with ordered dependent tasks — one worker at a time, order preserved"
|
|
608
|
+
elif [ "$INDEPENDENT_TASKS" -ge 2 ] 2>/dev/null; then
|
|
609
|
+
POSTURE="parallel-workers"
|
|
610
|
+
POSTURE_REASON="$INDEPENDENT_TASKS independent tasks, no shared files/interfaces"
|
|
611
|
+
fi
|
|
612
|
+
# investigation config (W8) — three keys, defaults 2/5/2
|
|
613
|
+
INV_MAX_PASSES=2
|
|
614
|
+
INV_MAX_UNRELATED=5
|
|
615
|
+
INV_REPEATED_THRESH=2
|
|
616
|
+
if [ -f "$MUGIWARA_DIR/config" ]; then
|
|
617
|
+
_v=$(grep -E '^investigation_max_passes=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | cut -d'#' -f1 | tr -d '[:space:]')
|
|
618
|
+
[ -n "$_v" ] && INV_MAX_PASSES="$_v"
|
|
619
|
+
_v=$(grep -E '^investigation_max_unrelated_files=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | cut -d'#' -f1 | tr -d '[:space:]')
|
|
620
|
+
[ -n "$_v" ] && INV_MAX_UNRELATED="$_v"
|
|
621
|
+
_v=$(grep -E '^investigation_repeated_read_threshold=' "$MUGIWARA_DIR/config" 2>/dev/null | head -1 | cut -d= -f2- | cut -d'#' -f1 | tr -d '[:space:]')
|
|
622
|
+
[ -n "$_v" ] && INV_REPEATED_THRESH="$_v"
|
|
623
|
+
fi
|
|
624
|
+
case "$INV_MAX_PASSES" in ''|*[!0-9]*) INV_MAX_PASSES=2 ;; esac
|
|
625
|
+
case "$INV_MAX_UNRELATED" in ''|*[!0-9]*) INV_MAX_UNRELATED=5 ;; esac
|
|
626
|
+
case "$INV_REPEATED_THRESH" in ''|*[!0-9]*) INV_REPEATED_THRESH=2 ;; esac
|
|
627
|
+
# investigation_status: simple threshold check on repeated_reads (W8/W10)
|
|
628
|
+
INVESTIGATION_STATUS="continue"
|
|
629
|
+
if [ "$REPEATED_READS" -ge "$INV_REPEATED_THRESH" ] 2>/dev/null; then
|
|
630
|
+
INVESTIGATION_STATUS="stop"
|
|
631
|
+
fi
|
|
632
|
+
|
|
525
633
|
mkdir -p "$MISSION_DIR"
|
|
526
634
|
|
|
527
635
|
node -e "
|
|
@@ -565,7 +673,12 @@ const data = {
|
|
|
565
673
|
evidence: process.argv[21] ? process.argv[21].split(',').filter(Boolean) : [],
|
|
566
674
|
updated_at: process.argv[22],
|
|
567
675
|
schema_version: 2,
|
|
568
|
-
repeated_reads: parseInt(process.argv[41], 10) || 0
|
|
676
|
+
repeated_reads: parseInt(process.argv[41], 10) || 0,
|
|
677
|
+
team_members: parseInt(process.argv[42], 10) || 1,
|
|
678
|
+
posture: process.argv[43] || 'inline-sequential',
|
|
679
|
+
posture_reason: process.argv[44] || '',
|
|
680
|
+
posture_pause: process.argv[45] === 'true',
|
|
681
|
+
investigation_status: process.argv[46] || 'continue'
|
|
569
682
|
};
|
|
570
683
|
require('fs').writeFileSync(process.argv[23], JSON.stringify(data, null, 2) + '\n');
|
|
571
684
|
" \
|
|
@@ -579,7 +692,7 @@ require('fs').writeFileSync(process.argv[23], JSON.stringify(data, null, 2) + '\
|
|
|
579
692
|
"$LOC_INS" "$LOC_DEL" "$LOC_CHURN" "$MEMBER" "$VERBOSITY" \
|
|
580
693
|
"$HEAL_MAX_CYCLES" "$HEAL_HALT" "$DELEGATE_THRESHOLD" "$DELEGATE_DUE" \
|
|
581
694
|
"$MODEL" "$DEPTH_REVIEW" "$DEPTH_QUALITY" "$DEPTH_VERIFY" \
|
|
582
|
-
"$REPEATED_READS"
|
|
695
|
+
"$REPEATED_READS" "$TEAM_MEMBERS" "$POSTURE" "$POSTURE_REASON" "$POSTURE_PAUSE" "$INVESTIGATION_STATUS"
|
|
583
696
|
|
|
584
697
|
if [ "$LANE_ROSE" = true ]; then
|
|
585
698
|
echo "⚠ LANE ROSE: $LANE_PREV → $LANE ($LANE_REASON) — escalate per check-in protocol"
|
|
@@ -414,7 +414,175 @@ if (integrityArg !== -1) {
|
|
|
414
414
|
}
|
|
415
415
|
if (!constants.includes('LANE_BASE_lean=8421')) errors.push('doc-integrity: source lane-base.sh lean base drifted (expected 8421)');
|
|
416
416
|
if (!constants.includes('BUDGET_full=50000')) errors.push('doc-integrity: source lane-base.sh full budget drifted (expected 50000)');
|
|
417
|
+
// W12 stale path check: obsolete layout must not appear
|
|
418
|
+
const staleChecks: [string, string[]][] = [
|
|
419
|
+
['state/<mission>', ['docs/concepts/comparison.md', 'docs/concepts/features.md', 'references/multi-actor.md', 'README.md']],
|
|
420
|
+
['continue/<mission>', ['docs/concepts/comparison.md', 'docs/concepts/features.md', 'references/multi-actor.md', 'README.md']],
|
|
421
|
+
['plans/<mission>', ['docs/concepts/comparison.md', 'docs/concepts/features.md', 'references/multi-actor.md', 'README.md']],
|
|
422
|
+
['logs/lessons', ['docs/concepts/comparison.md', 'docs/concepts/features.md', 'references/multi-actor.md', 'README.md']],
|
|
423
|
+
];
|
|
424
|
+
for (const [pat, docs] of staleChecks) {
|
|
425
|
+
for (const doc of docs) {
|
|
426
|
+
const p = join(import.meta.dirname, '..', doc);
|
|
427
|
+
if (existsSync(p) && readFileSync(p, 'utf8').includes(pat)) {
|
|
428
|
+
errors.push(`doc-integrity: ${doc} contains obsolete path "${pat}" — use missions/<mission>/ layout`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// W17 metrics must come from .metrics/latest.json — check for hardcoded stale numbers not in metrics
|
|
433
|
+
const metricsPath2 = join(import.meta.dirname, '..', '.metrics/latest.json');
|
|
434
|
+
if (existsSync(metricsPath2)) {
|
|
435
|
+
const m2 = JSON.parse(readFileSync(metricsPath2, 'utf8'));
|
|
436
|
+
const readme2 = readFileSync(join(import.meta.dirname, '..', 'README.md'), 'utf8');
|
|
437
|
+
// ensure README rank-1 and pointers match metrics (also checked in --check-readme-metrics, but this is integrity)
|
|
438
|
+
const rankMatch2 = readme2.match(/Retrieval routing rank-1[^\n]*?(\d+\.\d+)%/);
|
|
439
|
+
if (rankMatch2 && parseFloat(rankMatch2[1]) !== Number(m2.retrieval_rank1)) {
|
|
440
|
+
errors.push(`doc-integrity: README rank-1 ${rankMatch2[1]}% != metrics ${m2.retrieval_rank1}%`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
// stale CLI commands: any `mugiwara <word>` where word is not a valid CLI case, appearing as code, is stale
|
|
444
|
+
const validCmds = new Set(['install','update','uninstall','list','reset','archive','clean','continue','status','cost','run','savepoint','blame','handoff','sign','migrate','lesson','help','version','mode','off']);
|
|
445
|
+
const docsToScan = ['docs/concepts/workflow.md','docs/concepts/config.md','README.md','references/multi-actor.md'];
|
|
446
|
+
for (const doc of docsToScan) {
|
|
447
|
+
const p = join(import.meta.dirname, '..', doc);
|
|
448
|
+
if (!existsSync(p)) continue;
|
|
449
|
+
const txt = readFileSync(p, 'utf8');
|
|
450
|
+
for (const m of txt.matchAll(/`mugiwara ([a-z-]+)/g)) {
|
|
451
|
+
const cmd = m[1];
|
|
452
|
+
if (!validCmds.has(cmd) && cmd !== '--help' && cmd !== '--version') {
|
|
453
|
+
errors.push(`doc-integrity: ${doc} contains stale command "mugiwara ${cmd}" not in src/cli.ts`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// --- config drift gate (W11): every key code reads must appear in DEFAULT_CONFIG and docs, and vice versa ---
|
|
461
|
+
if (process.argv.includes('--check-config')) {
|
|
462
|
+
const cfgSrc = readFileSync(join(import.meta.dirname, '..', 'src/config.ts'), 'utf8');
|
|
463
|
+
const m = cfgSrc.match(/DEFAULT_CONFIG\s*=\s*\[([\s\S]*?)\]\.join/);
|
|
464
|
+
let defaultKeys: string[] = [];
|
|
465
|
+
if (m) {
|
|
466
|
+
const block = m[1];
|
|
467
|
+
for (const line of block.split(/\r?\n/)) {
|
|
468
|
+
const t = line.trim();
|
|
469
|
+
if (!t) continue;
|
|
470
|
+
// extract string content between quotes
|
|
471
|
+
const q = t.match(/['"`]([^'"`]*?)['"`]/);
|
|
472
|
+
if (!q) continue;
|
|
473
|
+
let s = q[1].trim();
|
|
474
|
+
if (!s) continue;
|
|
475
|
+
if (s.startsWith('#')) s = s.slice(1).trim();
|
|
476
|
+
if (!s) continue;
|
|
477
|
+
const eq = s.indexOf('=');
|
|
478
|
+
if (eq === -1) continue;
|
|
479
|
+
const key = s.slice(0, eq).trim();
|
|
480
|
+
if (key) defaultKeys.push(key);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// docs keys from config.md table (only the ## Keys section, not template examples)
|
|
484
|
+
const docPath = join(import.meta.dirname, '..', 'docs/concepts/config.md');
|
|
485
|
+
let docKeys: string[] = [];
|
|
486
|
+
if (existsSync(docPath)) {
|
|
487
|
+
const docText = readFileSync(docPath, 'utf8');
|
|
488
|
+
const keysSectionMatch = docText.match(/## Keys([\s\S]*?)(?:\n## |\n#|$)/);
|
|
489
|
+
const keysSection = keysSectionMatch ? keysSectionMatch[1] : docText;
|
|
490
|
+
for (const line of keysSection.split(/\r?\n/)) {
|
|
491
|
+
const cm = line.match(/\|\s*`([^`]+)`\s*\|/);
|
|
492
|
+
if (cm) {
|
|
493
|
+
const k = cm[1].trim();
|
|
494
|
+
if (k && !docKeys.includes(k)) docKeys.push(k);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
} else {
|
|
498
|
+
errors.push('config-drift: docs/concepts/config.md not found');
|
|
499
|
+
}
|
|
500
|
+
// code keys: scan src/*.ts, scripts/*.sh, hooks/*.ts for key patterns
|
|
501
|
+
const codeRoots = [
|
|
502
|
+
join(import.meta.dirname, '..', 'src'),
|
|
503
|
+
join(import.meta.dirname, '..', 'scripts'),
|
|
504
|
+
join(import.meta.dirname, '..', 'hooks'),
|
|
505
|
+
];
|
|
506
|
+
const codeTextAll = codeRoots.map(r => {
|
|
507
|
+
if (!existsSync(r)) return '';
|
|
508
|
+
const files = readdirSync(r, { withFileTypes: true }).filter(e => e.isFile() && (e.name.endsWith('.ts') || e.name.endsWith('.sh'))).map(e => readFileSync(join(r, e.name), 'utf8')).join('\n');
|
|
509
|
+
// also need subdirectories
|
|
510
|
+
let sub = '';
|
|
511
|
+
try {
|
|
512
|
+
for (const e of readdirSync(r, { withFileTypes: true })) {
|
|
513
|
+
if (e.isDirectory()) {
|
|
514
|
+
const subdir = join(r, e.name);
|
|
515
|
+
for (const f of readdirSync(subdir, { withFileTypes: true }).filter(x => x.isFile() && (x.name.endsWith('.ts') || x.name.endsWith('.sh')))) {
|
|
516
|
+
sub += readFileSync(join(subdir, f.name), 'utf8') + '\n';
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
} catch {}
|
|
521
|
+
return files + sub;
|
|
522
|
+
}).join('\n');
|
|
523
|
+
// check each default key appears in code
|
|
524
|
+
for (const k of defaultKeys) {
|
|
525
|
+
if (!codeTextAll.includes(k)) {
|
|
526
|
+
errors.push(`config-drift: DEFAULT_CONFIG key "${k}" not found in code (src/*.ts, scripts/*.sh, hooks/*.ts)`);
|
|
527
|
+
}
|
|
528
|
+
if (!docKeys.includes(k)) {
|
|
529
|
+
errors.push(`config-drift: DEFAULT_CONFIG key "${k}" missing from docs/concepts/config.md`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
for (const k of docKeys) {
|
|
533
|
+
if (!defaultKeys.includes(k)) {
|
|
534
|
+
errors.push(`config-drift: docs/concepts/config.md key "${k}" not in DEFAULT_CONFIG`);
|
|
535
|
+
}
|
|
536
|
+
if (!codeTextAll.includes(k)) {
|
|
537
|
+
errors.push(`config-drift: docs key "${k}" not found in code`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
if (!errors.some(e => e.startsWith('config-drift'))) {
|
|
541
|
+
console.log(`✓ config in sync: ${defaultKeys.length} keys (${defaultKeys.join(', ')})`);
|
|
542
|
+
}
|
|
417
543
|
}
|
|
544
|
+
|
|
545
|
+
// --- wiring gate (W7): every src module must be imported somewhere ---
|
|
546
|
+
if (process.argv.includes('--check-wiring')) {
|
|
547
|
+
const srcDir = join(import.meta.dirname, '..', 'src');
|
|
548
|
+
const ENTRY = new Set(['cli.ts', 'index.ts', 'installer.ts']);
|
|
549
|
+
const srcFiles = readdirSync(srcDir).filter(n => n.endsWith('.ts'));
|
|
550
|
+
const searchDirs = [srcDir, join(import.meta.dirname, '..', 'hooks'), join(import.meta.dirname, '..', 'scripts')];
|
|
551
|
+
const allFiles: string[] = [];
|
|
552
|
+
for (const d of searchDirs) {
|
|
553
|
+
if (!existsSync(d)) continue;
|
|
554
|
+
const walk = (dir: string) => {
|
|
555
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
556
|
+
const full = join(dir, e.name);
|
|
557
|
+
if (e.isDirectory()) walk(full);
|
|
558
|
+
else if (e.isFile() && (e.name.endsWith('.ts') || e.name.endsWith('.js') || e.name.endsWith('.sh') || e.name.endsWith('.mjs'))) {
|
|
559
|
+
allFiles.push(full);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
walk(d);
|
|
564
|
+
}
|
|
565
|
+
for (const f of srcFiles) {
|
|
566
|
+
if (ENTRY.has(f)) continue;
|
|
567
|
+
const stem = f.slice(0, -3);
|
|
568
|
+
let found = false;
|
|
569
|
+
for (const full of allFiles) {
|
|
570
|
+
if (full.endsWith(`/src/${f}`) || full === join(srcDir, f)) continue;
|
|
571
|
+
try {
|
|
572
|
+
const txt = readFileSync(full, 'utf8');
|
|
573
|
+
if (txt.includes(`'./${stem}`) || txt.includes(`"./${stem}`) || txt.includes(`'./${stem}.ts'`) || txt.includes(`"./${stem}.ts"`)) {
|
|
574
|
+
found = true;
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
} catch {}
|
|
578
|
+
}
|
|
579
|
+
if (!found) {
|
|
580
|
+
errors.push(`src/${f}: imported by nothing — wire it, or delete it with its tests and its features.md claim`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (!errors.some(e => e.includes('imported by nothing'))) {
|
|
584
|
+
console.log(`✓ wiring: all src modules imported`);
|
|
585
|
+
}
|
|
418
586
|
}
|
|
419
587
|
|
|
420
588
|
// --- README metrics gate (D3): README table must match .metrics/latest.json ---
|
package/src/args.ts
CHANGED
|
@@ -6,7 +6,7 @@ export type Args = {
|
|
|
6
6
|
flags: Record<string, FlagValue>;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
const VALUE_FLAGS: Record<string, string> = { '--project': 'project', '--target': 'target', '--before': 'before', '--backend': 'backend', '--mission': 'mission' };
|
|
9
|
+
const VALUE_FLAGS: Record<string, string> = { '--project': 'project', '--target': 'target', '--before': 'before', '--backend': 'backend', '--mission': 'mission', '--to-team': 'toTeam', '--to-solo': 'toSolo' };
|
|
10
10
|
const BOOL_FLAGS: Record<string, string> = {
|
|
11
11
|
'--global': 'global', '--yes': 'yes', '-y': 'yes', '--force': 'force',
|
|
12
12
|
'--dry-run': 'dryRun', '--keep-logs': 'keepLogs', '--check': 'check', '--all': 'all', '--verify': 'verify',
|