@ionivetech/mugiwara 0.8.1 → 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 +11 -20
- 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 +20 -20
- package/dist/mugiwara.js +863 -133
- 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 +294 -1
- package/scripts/lane-base.ts +16 -0
- package/scripts/lane.sh +5 -1
- package/scripts/lib/lane-base.sh +1 -1
- package/scripts/savepoint.sh +151 -15
- package/scripts/validate-content.ts +168 -0
- package/src/args.ts +1 -1
- package/src/cli.ts +203 -21
- package/src/config.ts +33 -11
- package/src/continue.ts +7 -1
- package/src/cost.ts +1 -1
- package/src/installer.ts +27 -4
- package/src/integrity.ts +41 -10
- package/src/mission.ts +137 -52
- package/src/policy.ts +17 -2
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
|
@@ -649,7 +649,7 @@ console.log('\nT3 — lane-aware gates');
|
|
|
649
649
|
return s.length === 12 && s.includes('run-evals') && s.includes('retrieval-eval') && s.includes('conformance');
|
|
650
650
|
});
|
|
651
651
|
assert('budget direct → 0, full → 50000', true, () => budgetForLane('direct') === 0 && budgetForLane('full') === 50000);
|
|
652
|
-
assert('budget spike →
|
|
652
|
+
assert('budget spike → 9000 (direct fixture 9k)', true, () => budgetForLane('spike') === 9000);
|
|
653
653
|
// mutation: break direct step count → should fail (file content shows not 3)
|
|
654
654
|
const broken = originalPolicy.replace(
|
|
655
655
|
"direct: ['build-hooks:check', 'typecheck', 'build']",
|
|
@@ -675,5 +675,298 @@ console.log('\nT3 — lane-aware gates');
|
|
|
675
675
|
});
|
|
676
676
|
}
|
|
677
677
|
|
|
678
|
+
// --- B1: CLI availability — remove section → content validation fails ---
|
|
679
|
+
console.log('\nB1 — CLI availability');
|
|
680
|
+
{
|
|
681
|
+
const wf = join(root, 'content', 'skills', 'mugiwara-workflow', 'SKILL.md');
|
|
682
|
+
const original = readFileSync(wf, 'utf8');
|
|
683
|
+
try {
|
|
684
|
+
const b1Pattern = /## CLI availability[\s\S]*?## Artifact trust/;
|
|
685
|
+
const b1Broken = original.replace(b1Pattern, '## Artifact trust');
|
|
686
|
+
if (!b1Pattern.test(original) || b1Broken === original) {
|
|
687
|
+
console.error('✗ B1: mutation target not found — the gate it guards may be dead.');
|
|
688
|
+
failed++;
|
|
689
|
+
} else {
|
|
690
|
+
writeFileSync(wf, b1Broken);
|
|
691
|
+
assert('missing CLI availability → grep fails', false, () => run('B1-grep', 'grep -q "CLI availability" content/skills/mugiwara-workflow/SKILL.md'));
|
|
692
|
+
}
|
|
693
|
+
} finally {
|
|
694
|
+
writeFileSync(wf, original);
|
|
695
|
+
assert('restored → content validation passes', true, () => run('B1-restore', 'grep -q "CLI availability" content/skills/mugiwara-workflow/SKILL.md'));
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// --- B2: team evidence gate — revert to single state.json → integrity gate fails on team ---
|
|
700
|
+
console.log('\nB2 — team evidence gate');
|
|
701
|
+
{
|
|
702
|
+
const integ = join(root, 'src', 'integrity.ts');
|
|
703
|
+
const original = readFileSync(integ, 'utf8');
|
|
704
|
+
try {
|
|
705
|
+
const fixedPattern = /const stateFiles = existsSync\(missionDir\)/;
|
|
706
|
+
const broken = original.replace(fixedPattern, "const evidenceFile = join(missionDir, 'state.json'); // B2 revert");
|
|
707
|
+
const fullPattern = / \/\/ Solo layout writes state\.json; team layout writes <member>\.json per member\./;
|
|
708
|
+
let b2Broken = original;
|
|
709
|
+
if (fullPattern.test(original)) {
|
|
710
|
+
// remove the team-aware block header to make grep for stateFiles fail for the specific definition
|
|
711
|
+
b2Broken = original.replace(fixedPattern, "const evidenceFile = join(missionDir, 'state.json'); // B2 revert");
|
|
712
|
+
// also need to remove remaining stateFiles references to make grep fail - replace all stateFiles with evidenceFile
|
|
713
|
+
b2Broken = b2Broken.replace(/stateFiles/g, 'evidenceFile');
|
|
714
|
+
}
|
|
715
|
+
if (!fixedPattern.test(original) || broken === original) {
|
|
716
|
+
console.error('✗ B2: mutation target not found — the gate it guards may be dead.');
|
|
717
|
+
failed++;
|
|
718
|
+
} else {
|
|
719
|
+
writeFileSync(integ, b2Broken);
|
|
720
|
+
assert('single state.json → team evidence gate dead', false, () => run('B2', 'grep -q "const stateFiles = existsSync" src/integrity.ts'));
|
|
721
|
+
}
|
|
722
|
+
} finally {
|
|
723
|
+
writeFileSync(integ, original);
|
|
724
|
+
assert('restored → team gate present', true, () => run('B2-restore', 'grep -q "const stateFiles = existsSync" src/integrity.ts'));
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// --- B3: task counter — restore unanchored grep → savepoint task test fails ---
|
|
729
|
+
console.log('\nB3 — task counter');
|
|
730
|
+
{
|
|
731
|
+
const sp = join(root, 'scripts', 'savepoint.sh');
|
|
732
|
+
const original = readFileSync(sp, 'utf8');
|
|
733
|
+
try {
|
|
734
|
+
const broken = original.replace('TASKS_TOTAL=$(count_boxes "$PLAN_FILE" \'[ xX]\')', 'TASKS_TOTAL=$(grep -cE \'^\\s*-\\s*\\[[ xX]\\]\' "$PLAN_FILE" 2>/dev/null || true)')
|
|
735
|
+
.replace('TASKS_DONE=$(count_boxes "$PLAN_FILE" \'[xX]\')', 'TASKS_DONE=$(grep -c \'\\[x\\]\' "$PLAN_FILE" 2>/dev/null || true)');
|
|
736
|
+
if (broken === original) {
|
|
737
|
+
console.error('✗ B3: mutation target not found — the gate it guards may be dead.');
|
|
738
|
+
failed++;
|
|
739
|
+
} else {
|
|
740
|
+
writeFileSync(sp, broken);
|
|
741
|
+
assert('unanchored grep → savepoint task test fails', false, () => run('B3', 'bun run test -- savepoint -t "B3: task counting"'));
|
|
742
|
+
}
|
|
743
|
+
} finally {
|
|
744
|
+
writeFileSync(sp, original);
|
|
745
|
+
assert('restored → savepoint task test passes', true, () => run('B3-restore', 'bun run test -- savepoint -t "B3: task counting"'));
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// --- B4: repo root — restore [ -d .git ] → lane subdirectory test fails ---
|
|
750
|
+
console.log('\nB4 — repo root');
|
|
751
|
+
{
|
|
752
|
+
const lane = join(root, 'scripts', 'lane.sh');
|
|
753
|
+
const original = readFileSync(lane, 'utf8');
|
|
754
|
+
try {
|
|
755
|
+
const broken = original.replace(
|
|
756
|
+
/# Resolve the repo root: handles subdirectories and git worktrees[\s\S]*?cd "\$REPO_ROOT" \|\| \{ echo "lane: cannot enter repo root" >&2; exit 1; \}/,
|
|
757
|
+
'[ -d .git ] || { echo "lane: not a git repository" >&2; exit 1; }'
|
|
758
|
+
);
|
|
759
|
+
if (broken === original) {
|
|
760
|
+
console.error('✗ B4: mutation target not found — the gate it guards may be dead.');
|
|
761
|
+
failed++;
|
|
762
|
+
} else {
|
|
763
|
+
writeFileSync(lane, broken);
|
|
764
|
+
assert('[ -d .git ] → lane subdirectory gate dead', false, () => run('B4', 'grep -q "git rev-parse --show-toplevel" scripts/lane.sh'));
|
|
765
|
+
}
|
|
766
|
+
} finally {
|
|
767
|
+
writeFileSync(lane, original);
|
|
768
|
+
assert('restored → lane uses git rev-parse', true, () => run('B4-restore', 'grep -q "git rev-parse --show-toplevel" scripts/lane.sh'));
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// --- B5: spike budget — set below base → lane-base fails ---
|
|
773
|
+
console.log('\nB5 — spike budget');
|
|
774
|
+
{
|
|
775
|
+
const baseFile = join(root, 'scripts', 'lib', 'lane-base.sh');
|
|
776
|
+
const original = readFileSync(baseFile, 'utf8');
|
|
777
|
+
try {
|
|
778
|
+
const broken = original.replace('BUDGET_spike=9000', 'BUDGET_spike=3000');
|
|
779
|
+
if (broken === original) {
|
|
780
|
+
console.error('✗ B5: mutation target not found — the gate it guards may be dead.');
|
|
781
|
+
failed++;
|
|
782
|
+
} else {
|
|
783
|
+
writeFileSync(baseFile, broken);
|
|
784
|
+
assert('BUDGET_spike 3000 < LANE_BASE 5411 → lane-base fails', false, () => run('B5', 'bun scripts/lane-base.ts'));
|
|
785
|
+
}
|
|
786
|
+
} finally {
|
|
787
|
+
writeFileSync(baseFile, original);
|
|
788
|
+
assert('restored → lane-base passes', true, () => run('B5-restore', 'bun scripts/lane-base.ts'));
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// --- B6: corrupt state — swallow parse errors again → status test fails ---
|
|
793
|
+
console.log('\nB6 — corrupt state');
|
|
794
|
+
{
|
|
795
|
+
const cont = join(root, 'src', 'continue.ts');
|
|
796
|
+
const original = readFileSync(cont, 'utf8');
|
|
797
|
+
try {
|
|
798
|
+
const broken = original.replace('unreadable.push(join(mission, f));', '// corrupt savepoint — skip, never crash the listing');
|
|
799
|
+
if (broken === original) {
|
|
800
|
+
console.error('✗ B6: mutation target not found — the gate it guards may be dead.');
|
|
801
|
+
failed++;
|
|
802
|
+
} else {
|
|
803
|
+
writeFileSync(cont, broken);
|
|
804
|
+
assert('swallow parse errors → unreadable gate dead', false, () => run('B6', 'grep -q "unreadableStateFiles" src/continue.ts && grep -q "unreadable.push" src/continue.ts'));
|
|
805
|
+
}
|
|
806
|
+
} finally {
|
|
807
|
+
writeFileSync(cont, original);
|
|
808
|
+
assert('restored → corrupt state surfaced', true, () => run('B6-restore', 'grep -q "unreadable.push" src/continue.ts'));
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// --- B7: zero evidence — remove check → integrity team test fails ---
|
|
813
|
+
console.log('\nB7 — zero evidence');
|
|
814
|
+
{
|
|
815
|
+
const integ = join(root, 'src', 'integrity.ts');
|
|
816
|
+
const original = readFileSync(integ, 'utf8');
|
|
817
|
+
try {
|
|
818
|
+
const b7Pattern = /mission declares no evidence/;
|
|
819
|
+
const broken = original.replace(b7Pattern, 'ZERO_EVIDENCE_REMOVED');
|
|
820
|
+
if (!b7Pattern.test(original) || broken === original) {
|
|
821
|
+
console.error('✗ B7: mutation target not found — the gate it guards may be dead.');
|
|
822
|
+
failed++;
|
|
823
|
+
} else {
|
|
824
|
+
writeFileSync(integ, broken);
|
|
825
|
+
assert('no zero-evidence check → integrity gate dead', false, () => run('B7', 'grep -q "mission declares no evidence" src/integrity.ts'));
|
|
826
|
+
}
|
|
827
|
+
} finally {
|
|
828
|
+
writeFileSync(integ, original);
|
|
829
|
+
assert('restored → zero-evidence warned', true, () => run('B7-restore', 'grep -q "mission declares no evidence" src/integrity.ts'));
|
|
830
|
+
}
|
|
831
|
+
}
|
|
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
|
+
|
|
678
971
|
console.log(`\n${passed} passed, ${failed} failed`);
|
|
679
972
|
process.exit(failed > 0 ? 1 : 0);
|
package/scripts/lane-base.ts
CHANGED
|
@@ -107,6 +107,22 @@ for (const lane of lanes) {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
// A lane whose base exceeds its budget is born in `warn` — the budget is then
|
|
111
|
+
// noise rather than a signal. (B5)
|
|
112
|
+
for (const lane of ['lean', 'standard', 'full', 'spike']) {
|
|
113
|
+
const base = constants[lane]?.base ?? 0;
|
|
114
|
+
const budget = constants[lane]?.budget ?? 0;
|
|
115
|
+
if (base >= budget) {
|
|
116
|
+
console.log(` ✗ LANE_BASE_${lane} (${base}) >= BUDGET_${lane} (${budget}) — every mission starts over budget`);
|
|
117
|
+
failures++;
|
|
118
|
+
}
|
|
119
|
+
const pct = budget ? Math.round((base / budget) * 100) : 100;
|
|
120
|
+
if (pct > 80) {
|
|
121
|
+
console.log(` ✗ LANE_BASE_${lane} is ${pct}% of BUDGET_${lane} — leaves no headroom (target ≤70%)`);
|
|
122
|
+
failures++;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
110
126
|
if (failures > 0) {
|
|
111
127
|
console.log(`\nlane-base: ${failures} constant(s) drifted from content load`);
|
|
112
128
|
process.exit(1);
|
package/scripts/lane.sh
CHANGED
|
@@ -11,7 +11,11 @@ BASE="${1:-main}"
|
|
|
11
11
|
JSON_OUT=0
|
|
12
12
|
[ "${2:-}" = "--json" ] && JSON_OUT=1
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
# Resolve the repo root: handles subdirectories and git worktrees, where .git
|
|
15
|
+
# is a file rather than a directory. (B4)
|
|
16
|
+
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || {
|
|
17
|
+
echo "lane: not a git repository" >&2; exit 1; }
|
|
18
|
+
cd "$REPO_ROOT" || { echo "lane: cannot enter repo root" >&2; exit 1; }
|
|
15
19
|
|
|
16
20
|
# resolve base
|
|
17
21
|
if ! git rev-parse "$BASE" >/dev/null 2>&1; then
|
package/scripts/lib/lane-base.sh
CHANGED