@mc-and-his-agents/loom-installer 0.1.52 → 0.1.53
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/package.json +1 -1
- package/payload/manifest.json +22 -22
- package/payload/plugin/loom/skills/shared/scripts/loom_check.py +141 -1
- package/payload/skills/loom-adopt/.loom-runtime/shared/scripts/loom_check.py +141 -1
- package/payload/skills/loom-handoff/.loom-runtime/shared/scripts/loom_check.py +141 -1
- package/payload/skills/loom-init/.loom-runtime/shared/scripts/loom_check.py +141 -1
- package/payload/skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_check.py +141 -1
- package/payload/skills/loom-pre-review/.loom-runtime/shared/scripts/loom_check.py +141 -1
- package/payload/skills/loom-resume/.loom-runtime/shared/scripts/loom_check.py +141 -1
- package/payload/skills/loom-retire/.loom-runtime/shared/scripts/loom_check.py +141 -1
- package/payload/skills/loom-review/.loom-runtime/shared/scripts/loom_check.py +141 -1
- package/payload/skills/loom-spec-review/.loom-runtime/shared/scripts/loom_check.py +141 -1
package/package.json
CHANGED
package/payload/manifest.json
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"schema_version": "loom-installer-payload/v1",
|
|
3
3
|
"loom_version": "0.4.0",
|
|
4
4
|
"source_repository": "https://github.com/MC-and-his-Agents/Loom",
|
|
5
|
-
"source_commit": "
|
|
5
|
+
"source_commit": "544fa797c5cb4ddc5754050239ef3fb22e136726",
|
|
6
6
|
"source_ref": "main",
|
|
7
|
-
"built_at": "2026-04-
|
|
7
|
+
"built_at": "2026-04-28T16:53:46+08:00",
|
|
8
8
|
"runtime": {
|
|
9
9
|
"python_minimum": "3.10",
|
|
10
10
|
"python_recommended": "3.11+"
|
|
@@ -633,8 +633,8 @@
|
|
|
633
633
|
},
|
|
634
634
|
{
|
|
635
635
|
"path": "plugin/loom/skills/shared/scripts/loom_check.py",
|
|
636
|
-
"bytes":
|
|
637
|
-
"sha256": "
|
|
636
|
+
"bytes": 378826,
|
|
637
|
+
"sha256": "89158e15ce752ae1c4a3dc9edeaa2452954346ef8573a75f6e6b56dc5478e924"
|
|
638
638
|
},
|
|
639
639
|
{
|
|
640
640
|
"path": "plugin/loom/skills/shared/scripts/loom_flow.py",
|
|
@@ -1223,8 +1223,8 @@
|
|
|
1223
1223
|
},
|
|
1224
1224
|
{
|
|
1225
1225
|
"path": "skills/loom-adopt/.loom-runtime/shared/scripts/loom_check.py",
|
|
1226
|
-
"bytes":
|
|
1227
|
-
"sha256": "
|
|
1226
|
+
"bytes": 378826,
|
|
1227
|
+
"sha256": "89158e15ce752ae1c4a3dc9edeaa2452954346ef8573a75f6e6b56dc5478e924"
|
|
1228
1228
|
},
|
|
1229
1229
|
{
|
|
1230
1230
|
"path": "skills/loom-adopt/.loom-runtime/shared/scripts/loom_flow.py",
|
|
@@ -1843,8 +1843,8 @@
|
|
|
1843
1843
|
},
|
|
1844
1844
|
{
|
|
1845
1845
|
"path": "skills/loom-handoff/.loom-runtime/shared/scripts/loom_check.py",
|
|
1846
|
-
"bytes":
|
|
1847
|
-
"sha256": "
|
|
1846
|
+
"bytes": 378826,
|
|
1847
|
+
"sha256": "89158e15ce752ae1c4a3dc9edeaa2452954346ef8573a75f6e6b56dc5478e924"
|
|
1848
1848
|
},
|
|
1849
1849
|
{
|
|
1850
1850
|
"path": "skills/loom-handoff/.loom-runtime/shared/scripts/loom_flow.py",
|
|
@@ -2463,8 +2463,8 @@
|
|
|
2463
2463
|
},
|
|
2464
2464
|
{
|
|
2465
2465
|
"path": "skills/loom-init/.loom-runtime/shared/scripts/loom_check.py",
|
|
2466
|
-
"bytes":
|
|
2467
|
-
"sha256": "
|
|
2466
|
+
"bytes": 378826,
|
|
2467
|
+
"sha256": "89158e15ce752ae1c4a3dc9edeaa2452954346ef8573a75f6e6b56dc5478e924"
|
|
2468
2468
|
},
|
|
2469
2469
|
{
|
|
2470
2470
|
"path": "skills/loom-init/.loom-runtime/shared/scripts/loom_flow.py",
|
|
@@ -3088,8 +3088,8 @@
|
|
|
3088
3088
|
},
|
|
3089
3089
|
{
|
|
3090
3090
|
"path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_check.py",
|
|
3091
|
-
"bytes":
|
|
3092
|
-
"sha256": "
|
|
3091
|
+
"bytes": 378826,
|
|
3092
|
+
"sha256": "89158e15ce752ae1c4a3dc9edeaa2452954346ef8573a75f6e6b56dc5478e924"
|
|
3093
3093
|
},
|
|
3094
3094
|
{
|
|
3095
3095
|
"path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_flow.py",
|
|
@@ -3708,8 +3708,8 @@
|
|
|
3708
3708
|
},
|
|
3709
3709
|
{
|
|
3710
3710
|
"path": "skills/loom-pre-review/.loom-runtime/shared/scripts/loom_check.py",
|
|
3711
|
-
"bytes":
|
|
3712
|
-
"sha256": "
|
|
3711
|
+
"bytes": 378826,
|
|
3712
|
+
"sha256": "89158e15ce752ae1c4a3dc9edeaa2452954346ef8573a75f6e6b56dc5478e924"
|
|
3713
3713
|
},
|
|
3714
3714
|
{
|
|
3715
3715
|
"path": "skills/loom-pre-review/.loom-runtime/shared/scripts/loom_flow.py",
|
|
@@ -4328,8 +4328,8 @@
|
|
|
4328
4328
|
},
|
|
4329
4329
|
{
|
|
4330
4330
|
"path": "skills/loom-resume/.loom-runtime/shared/scripts/loom_check.py",
|
|
4331
|
-
"bytes":
|
|
4332
|
-
"sha256": "
|
|
4331
|
+
"bytes": 378826,
|
|
4332
|
+
"sha256": "89158e15ce752ae1c4a3dc9edeaa2452954346ef8573a75f6e6b56dc5478e924"
|
|
4333
4333
|
},
|
|
4334
4334
|
{
|
|
4335
4335
|
"path": "skills/loom-resume/.loom-runtime/shared/scripts/loom_flow.py",
|
|
@@ -4948,8 +4948,8 @@
|
|
|
4948
4948
|
},
|
|
4949
4949
|
{
|
|
4950
4950
|
"path": "skills/loom-retire/.loom-runtime/shared/scripts/loom_check.py",
|
|
4951
|
-
"bytes":
|
|
4952
|
-
"sha256": "
|
|
4951
|
+
"bytes": 378826,
|
|
4952
|
+
"sha256": "89158e15ce752ae1c4a3dc9edeaa2452954346ef8573a75f6e6b56dc5478e924"
|
|
4953
4953
|
},
|
|
4954
4954
|
{
|
|
4955
4955
|
"path": "skills/loom-retire/.loom-runtime/shared/scripts/loom_flow.py",
|
|
@@ -5568,8 +5568,8 @@
|
|
|
5568
5568
|
},
|
|
5569
5569
|
{
|
|
5570
5570
|
"path": "skills/loom-review/.loom-runtime/shared/scripts/loom_check.py",
|
|
5571
|
-
"bytes":
|
|
5572
|
-
"sha256": "
|
|
5571
|
+
"bytes": 378826,
|
|
5572
|
+
"sha256": "89158e15ce752ae1c4a3dc9edeaa2452954346ef8573a75f6e6b56dc5478e924"
|
|
5573
5573
|
},
|
|
5574
5574
|
{
|
|
5575
5575
|
"path": "skills/loom-review/.loom-runtime/shared/scripts/loom_flow.py",
|
|
@@ -6188,8 +6188,8 @@
|
|
|
6188
6188
|
},
|
|
6189
6189
|
{
|
|
6190
6190
|
"path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_check.py",
|
|
6191
|
-
"bytes":
|
|
6192
|
-
"sha256": "
|
|
6191
|
+
"bytes": 378826,
|
|
6192
|
+
"sha256": "89158e15ce752ae1c4a3dc9edeaa2452954346ef8573a75f6e6b56dc5478e924"
|
|
6193
6193
|
},
|
|
6194
6194
|
{
|
|
6195
6195
|
"path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_flow.py",
|
|
@@ -2417,6 +2417,7 @@ def check_root_self_adoption_carrier(root: Path) -> list[Failure]:
|
|
|
2417
2417
|
return failures
|
|
2418
2418
|
|
|
2419
2419
|
required_paths = (
|
|
2420
|
+
".agents/plugins/marketplace.json",
|
|
2420
2421
|
".loom/bootstrap/manifest.json",
|
|
2421
2422
|
".loom/bootstrap/init-result.json",
|
|
2422
2423
|
".loom/bin/loom_init.py",
|
|
@@ -2532,6 +2533,145 @@ def check_root_self_adoption_carrier(root: Path) -> list[Failure]:
|
|
|
2532
2533
|
failures.append(Failure("root-self-adoption", "`root shadow parity` must consume the root repo companion interface"))
|
|
2533
2534
|
if not isinstance(repo_interop, dict) or repo_interop.get("availability") != "present":
|
|
2534
2535
|
failures.append(Failure("root-self-adoption", "`root shadow parity` must consume the root repo interop contract"))
|
|
2536
|
+
failures.extend(check_root_self_plugin_install(root))
|
|
2537
|
+
return failures
|
|
2538
|
+
|
|
2539
|
+
|
|
2540
|
+
def check_root_self_plugin_install(root: Path) -> list[Failure]:
|
|
2541
|
+
failures: list[Failure] = []
|
|
2542
|
+
marketplace_path = root / ".agents/plugins/marketplace.json"
|
|
2543
|
+
if not marketplace_path.exists():
|
|
2544
|
+
return failures
|
|
2545
|
+
try:
|
|
2546
|
+
marketplace = load_json_file(marketplace_path)
|
|
2547
|
+
except json.JSONDecodeError as exc:
|
|
2548
|
+
return [Failure("root-self-plugin", f"`.agents/plugins/marketplace.json` is invalid JSON: {exc.msg}")]
|
|
2549
|
+
if not isinstance(marketplace, dict):
|
|
2550
|
+
return [Failure("root-self-plugin", "`.agents/plugins/marketplace.json` must contain a JSON object")]
|
|
2551
|
+
plugins = marketplace.get("plugins")
|
|
2552
|
+
if not isinstance(plugins, list):
|
|
2553
|
+
failures.append(Failure("root-self-plugin", "`marketplace.plugins` must be a list"))
|
|
2554
|
+
plugins = []
|
|
2555
|
+
loom_entry = next(
|
|
2556
|
+
(
|
|
2557
|
+
entry
|
|
2558
|
+
for entry in plugins
|
|
2559
|
+
if isinstance(entry, dict) and entry.get("name") == "loom"
|
|
2560
|
+
),
|
|
2561
|
+
None,
|
|
2562
|
+
)
|
|
2563
|
+
if not isinstance(loom_entry, dict):
|
|
2564
|
+
failures.append(Failure("root-self-plugin", "Codex marketplace must declare the `loom` plugin"))
|
|
2565
|
+
else:
|
|
2566
|
+
source = loom_entry.get("source")
|
|
2567
|
+
if not isinstance(source, dict) or source.get("source") != "local":
|
|
2568
|
+
failures.append(Failure("root-self-plugin", "Codex marketplace `loom` entry must use a local source"))
|
|
2569
|
+
if not isinstance(source, dict) or source.get("path") != "./plugins/loom":
|
|
2570
|
+
failures.append(Failure("root-self-plugin", "Codex marketplace `loom` entry must point to `./plugins/loom`"))
|
|
2571
|
+
|
|
2572
|
+
package_root = root / "packages/loom-installer"
|
|
2573
|
+
cli_entry = package_root / "dist/src/cli.js"
|
|
2574
|
+
package_json = package_root / "package.json"
|
|
2575
|
+
if not package_json.exists():
|
|
2576
|
+
failures.append(Failure("root-self-plugin", "installer package must exist for self-plugin verification"))
|
|
2577
|
+
return failures
|
|
2578
|
+
|
|
2579
|
+
with tempfile.TemporaryDirectory(prefix="loom-root-self-plugin-") as tmp:
|
|
2580
|
+
tmp_root = Path(tmp)
|
|
2581
|
+
target = tmp_root / "target"
|
|
2582
|
+
home = tmp_root / "home"
|
|
2583
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
2584
|
+
home.mkdir(parents=True, exist_ok=True)
|
|
2585
|
+
env = {
|
|
2586
|
+
"HOME": str(home),
|
|
2587
|
+
"CODEX_HOME": str(home / ".codex"),
|
|
2588
|
+
"LOOM_INSTALLER_BUILD_TIMESTAMP": "2026-01-01T00:00:00.000Z",
|
|
2589
|
+
}
|
|
2590
|
+
commands: list[tuple[str, list[str], Path]] = []
|
|
2591
|
+
if not (package_root / "node_modules/.bin/tsc").exists():
|
|
2592
|
+
commands.append(
|
|
2593
|
+
(
|
|
2594
|
+
"install self-plugin build dependencies",
|
|
2595
|
+
["npm", "ci", "--prefix", str(package_root)],
|
|
2596
|
+
root,
|
|
2597
|
+
)
|
|
2598
|
+
)
|
|
2599
|
+
commands.extend(
|
|
2600
|
+
(
|
|
2601
|
+
(
|
|
2602
|
+
"build self-plugin installer",
|
|
2603
|
+
["npm", "--prefix", str(package_root), "run", "build"],
|
|
2604
|
+
root,
|
|
2605
|
+
),
|
|
2606
|
+
(
|
|
2607
|
+
"install self-plugin payload",
|
|
2608
|
+
[
|
|
2609
|
+
"node",
|
|
2610
|
+
str(cli_entry),
|
|
2611
|
+
"add",
|
|
2612
|
+
"plugin",
|
|
2613
|
+
"--host",
|
|
2614
|
+
"codex",
|
|
2615
|
+
"--target",
|
|
2616
|
+
str(target),
|
|
2617
|
+
"--force",
|
|
2618
|
+
"--json",
|
|
2619
|
+
],
|
|
2620
|
+
root,
|
|
2621
|
+
),
|
|
2622
|
+
)
|
|
2623
|
+
)
|
|
2624
|
+
for label, args, cwd in commands:
|
|
2625
|
+
try:
|
|
2626
|
+
result = run_command(root, args, cwd=cwd, env=env, timeout_seconds=300)
|
|
2627
|
+
except subprocess.TimeoutExpired:
|
|
2628
|
+
failures.append(Failure("root-self-plugin", f"`{label}` timed out"))
|
|
2629
|
+
return failures
|
|
2630
|
+
if result.returncode != 0:
|
|
2631
|
+
detail = result.stderr.strip() or result.stdout.strip() or "command failed without output"
|
|
2632
|
+
failures.append(Failure("root-self-plugin", f"`{label}` failed: {detail}"))
|
|
2633
|
+
return failures
|
|
2634
|
+
|
|
2635
|
+
installed_marketplace = target / ".agents/plugins/marketplace.json"
|
|
2636
|
+
plugin_root = target / "plugins/loom"
|
|
2637
|
+
expected_paths = (
|
|
2638
|
+
plugin_root / ".codex-plugin/plugin.json",
|
|
2639
|
+
plugin_root / "skills/registry.json",
|
|
2640
|
+
plugin_root / "skills/install-layout.json",
|
|
2641
|
+
plugin_root / "skills/shared/scripts/loom_init.py",
|
|
2642
|
+
plugin_root / "skills/loom-init/SKILL.md",
|
|
2643
|
+
)
|
|
2644
|
+
for path in expected_paths:
|
|
2645
|
+
if not path.exists():
|
|
2646
|
+
failures.append(Failure("root-self-plugin", f"self-plugin install is missing `{path.relative_to(target).as_posix()}`"))
|
|
2647
|
+
try:
|
|
2648
|
+
installed = load_json_file(installed_marketplace)
|
|
2649
|
+
except (FileNotFoundError, json.JSONDecodeError) as exc:
|
|
2650
|
+
failures.append(Failure("root-self-plugin", f"installed marketplace is unreadable: {exc}"))
|
|
2651
|
+
else:
|
|
2652
|
+
installed_plugins = installed.get("plugins") if isinstance(installed, dict) else None
|
|
2653
|
+
installed_entry = None
|
|
2654
|
+
if isinstance(installed_plugins, list):
|
|
2655
|
+
installed_entry = next(
|
|
2656
|
+
(
|
|
2657
|
+
entry
|
|
2658
|
+
for entry in installed_plugins
|
|
2659
|
+
if isinstance(entry, dict) and entry.get("name") == "loom"
|
|
2660
|
+
),
|
|
2661
|
+
None,
|
|
2662
|
+
)
|
|
2663
|
+
installed_source = installed_entry.get("source") if isinstance(installed_entry, dict) else None
|
|
2664
|
+
if not isinstance(installed_source, dict) or installed_source.get("path") != "./plugins/loom":
|
|
2665
|
+
failures.append(Failure("root-self-plugin", "installed marketplace must point `loom` to `./plugins/loom`"))
|
|
2666
|
+
if plugin_root.exists():
|
|
2667
|
+
generated_cache = [
|
|
2668
|
+
path.relative_to(target).as_posix()
|
|
2669
|
+
for path in plugin_root.rglob("*")
|
|
2670
|
+
if "__pycache__" in path.parts or path.suffix == ".pyc"
|
|
2671
|
+
]
|
|
2672
|
+
if generated_cache:
|
|
2673
|
+
preview = ", ".join(generated_cache[:5])
|
|
2674
|
+
failures.append(Failure("root-self-plugin", f"self-plugin payload must exclude Python cache artifacts: {preview}"))
|
|
2535
2675
|
return failures
|
|
2536
2676
|
|
|
2537
2677
|
|
|
@@ -7593,7 +7733,7 @@ def collect_failures(root: Path) -> list[Failure]:
|
|
|
7593
7733
|
|
|
7594
7734
|
|
|
7595
7735
|
def print_report(root: Path, failures: list[Failure]) -> None:
|
|
7596
|
-
categories_checked =
|
|
7736
|
+
categories_checked = 20
|
|
7597
7737
|
if not failures:
|
|
7598
7738
|
print(f"loom_check: OK ({root})")
|
|
7599
7739
|
print(f"checked {categories_checked} surfaces")
|
|
@@ -2417,6 +2417,7 @@ def check_root_self_adoption_carrier(root: Path) -> list[Failure]:
|
|
|
2417
2417
|
return failures
|
|
2418
2418
|
|
|
2419
2419
|
required_paths = (
|
|
2420
|
+
".agents/plugins/marketplace.json",
|
|
2420
2421
|
".loom/bootstrap/manifest.json",
|
|
2421
2422
|
".loom/bootstrap/init-result.json",
|
|
2422
2423
|
".loom/bin/loom_init.py",
|
|
@@ -2532,6 +2533,145 @@ def check_root_self_adoption_carrier(root: Path) -> list[Failure]:
|
|
|
2532
2533
|
failures.append(Failure("root-self-adoption", "`root shadow parity` must consume the root repo companion interface"))
|
|
2533
2534
|
if not isinstance(repo_interop, dict) or repo_interop.get("availability") != "present":
|
|
2534
2535
|
failures.append(Failure("root-self-adoption", "`root shadow parity` must consume the root repo interop contract"))
|
|
2536
|
+
failures.extend(check_root_self_plugin_install(root))
|
|
2537
|
+
return failures
|
|
2538
|
+
|
|
2539
|
+
|
|
2540
|
+
def check_root_self_plugin_install(root: Path) -> list[Failure]:
|
|
2541
|
+
failures: list[Failure] = []
|
|
2542
|
+
marketplace_path = root / ".agents/plugins/marketplace.json"
|
|
2543
|
+
if not marketplace_path.exists():
|
|
2544
|
+
return failures
|
|
2545
|
+
try:
|
|
2546
|
+
marketplace = load_json_file(marketplace_path)
|
|
2547
|
+
except json.JSONDecodeError as exc:
|
|
2548
|
+
return [Failure("root-self-plugin", f"`.agents/plugins/marketplace.json` is invalid JSON: {exc.msg}")]
|
|
2549
|
+
if not isinstance(marketplace, dict):
|
|
2550
|
+
return [Failure("root-self-plugin", "`.agents/plugins/marketplace.json` must contain a JSON object")]
|
|
2551
|
+
plugins = marketplace.get("plugins")
|
|
2552
|
+
if not isinstance(plugins, list):
|
|
2553
|
+
failures.append(Failure("root-self-plugin", "`marketplace.plugins` must be a list"))
|
|
2554
|
+
plugins = []
|
|
2555
|
+
loom_entry = next(
|
|
2556
|
+
(
|
|
2557
|
+
entry
|
|
2558
|
+
for entry in plugins
|
|
2559
|
+
if isinstance(entry, dict) and entry.get("name") == "loom"
|
|
2560
|
+
),
|
|
2561
|
+
None,
|
|
2562
|
+
)
|
|
2563
|
+
if not isinstance(loom_entry, dict):
|
|
2564
|
+
failures.append(Failure("root-self-plugin", "Codex marketplace must declare the `loom` plugin"))
|
|
2565
|
+
else:
|
|
2566
|
+
source = loom_entry.get("source")
|
|
2567
|
+
if not isinstance(source, dict) or source.get("source") != "local":
|
|
2568
|
+
failures.append(Failure("root-self-plugin", "Codex marketplace `loom` entry must use a local source"))
|
|
2569
|
+
if not isinstance(source, dict) or source.get("path") != "./plugins/loom":
|
|
2570
|
+
failures.append(Failure("root-self-plugin", "Codex marketplace `loom` entry must point to `./plugins/loom`"))
|
|
2571
|
+
|
|
2572
|
+
package_root = root / "packages/loom-installer"
|
|
2573
|
+
cli_entry = package_root / "dist/src/cli.js"
|
|
2574
|
+
package_json = package_root / "package.json"
|
|
2575
|
+
if not package_json.exists():
|
|
2576
|
+
failures.append(Failure("root-self-plugin", "installer package must exist for self-plugin verification"))
|
|
2577
|
+
return failures
|
|
2578
|
+
|
|
2579
|
+
with tempfile.TemporaryDirectory(prefix="loom-root-self-plugin-") as tmp:
|
|
2580
|
+
tmp_root = Path(tmp)
|
|
2581
|
+
target = tmp_root / "target"
|
|
2582
|
+
home = tmp_root / "home"
|
|
2583
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
2584
|
+
home.mkdir(parents=True, exist_ok=True)
|
|
2585
|
+
env = {
|
|
2586
|
+
"HOME": str(home),
|
|
2587
|
+
"CODEX_HOME": str(home / ".codex"),
|
|
2588
|
+
"LOOM_INSTALLER_BUILD_TIMESTAMP": "2026-01-01T00:00:00.000Z",
|
|
2589
|
+
}
|
|
2590
|
+
commands: list[tuple[str, list[str], Path]] = []
|
|
2591
|
+
if not (package_root / "node_modules/.bin/tsc").exists():
|
|
2592
|
+
commands.append(
|
|
2593
|
+
(
|
|
2594
|
+
"install self-plugin build dependencies",
|
|
2595
|
+
["npm", "ci", "--prefix", str(package_root)],
|
|
2596
|
+
root,
|
|
2597
|
+
)
|
|
2598
|
+
)
|
|
2599
|
+
commands.extend(
|
|
2600
|
+
(
|
|
2601
|
+
(
|
|
2602
|
+
"build self-plugin installer",
|
|
2603
|
+
["npm", "--prefix", str(package_root), "run", "build"],
|
|
2604
|
+
root,
|
|
2605
|
+
),
|
|
2606
|
+
(
|
|
2607
|
+
"install self-plugin payload",
|
|
2608
|
+
[
|
|
2609
|
+
"node",
|
|
2610
|
+
str(cli_entry),
|
|
2611
|
+
"add",
|
|
2612
|
+
"plugin",
|
|
2613
|
+
"--host",
|
|
2614
|
+
"codex",
|
|
2615
|
+
"--target",
|
|
2616
|
+
str(target),
|
|
2617
|
+
"--force",
|
|
2618
|
+
"--json",
|
|
2619
|
+
],
|
|
2620
|
+
root,
|
|
2621
|
+
),
|
|
2622
|
+
)
|
|
2623
|
+
)
|
|
2624
|
+
for label, args, cwd in commands:
|
|
2625
|
+
try:
|
|
2626
|
+
result = run_command(root, args, cwd=cwd, env=env, timeout_seconds=300)
|
|
2627
|
+
except subprocess.TimeoutExpired:
|
|
2628
|
+
failures.append(Failure("root-self-plugin", f"`{label}` timed out"))
|
|
2629
|
+
return failures
|
|
2630
|
+
if result.returncode != 0:
|
|
2631
|
+
detail = result.stderr.strip() or result.stdout.strip() or "command failed without output"
|
|
2632
|
+
failures.append(Failure("root-self-plugin", f"`{label}` failed: {detail}"))
|
|
2633
|
+
return failures
|
|
2634
|
+
|
|
2635
|
+
installed_marketplace = target / ".agents/plugins/marketplace.json"
|
|
2636
|
+
plugin_root = target / "plugins/loom"
|
|
2637
|
+
expected_paths = (
|
|
2638
|
+
plugin_root / ".codex-plugin/plugin.json",
|
|
2639
|
+
plugin_root / "skills/registry.json",
|
|
2640
|
+
plugin_root / "skills/install-layout.json",
|
|
2641
|
+
plugin_root / "skills/shared/scripts/loom_init.py",
|
|
2642
|
+
plugin_root / "skills/loom-init/SKILL.md",
|
|
2643
|
+
)
|
|
2644
|
+
for path in expected_paths:
|
|
2645
|
+
if not path.exists():
|
|
2646
|
+
failures.append(Failure("root-self-plugin", f"self-plugin install is missing `{path.relative_to(target).as_posix()}`"))
|
|
2647
|
+
try:
|
|
2648
|
+
installed = load_json_file(installed_marketplace)
|
|
2649
|
+
except (FileNotFoundError, json.JSONDecodeError) as exc:
|
|
2650
|
+
failures.append(Failure("root-self-plugin", f"installed marketplace is unreadable: {exc}"))
|
|
2651
|
+
else:
|
|
2652
|
+
installed_plugins = installed.get("plugins") if isinstance(installed, dict) else None
|
|
2653
|
+
installed_entry = None
|
|
2654
|
+
if isinstance(installed_plugins, list):
|
|
2655
|
+
installed_entry = next(
|
|
2656
|
+
(
|
|
2657
|
+
entry
|
|
2658
|
+
for entry in installed_plugins
|
|
2659
|
+
if isinstance(entry, dict) and entry.get("name") == "loom"
|
|
2660
|
+
),
|
|
2661
|
+
None,
|
|
2662
|
+
)
|
|
2663
|
+
installed_source = installed_entry.get("source") if isinstance(installed_entry, dict) else None
|
|
2664
|
+
if not isinstance(installed_source, dict) or installed_source.get("path") != "./plugins/loom":
|
|
2665
|
+
failures.append(Failure("root-self-plugin", "installed marketplace must point `loom` to `./plugins/loom`"))
|
|
2666
|
+
if plugin_root.exists():
|
|
2667
|
+
generated_cache = [
|
|
2668
|
+
path.relative_to(target).as_posix()
|
|
2669
|
+
for path in plugin_root.rglob("*")
|
|
2670
|
+
if "__pycache__" in path.parts or path.suffix == ".pyc"
|
|
2671
|
+
]
|
|
2672
|
+
if generated_cache:
|
|
2673
|
+
preview = ", ".join(generated_cache[:5])
|
|
2674
|
+
failures.append(Failure("root-self-plugin", f"self-plugin payload must exclude Python cache artifacts: {preview}"))
|
|
2535
2675
|
return failures
|
|
2536
2676
|
|
|
2537
2677
|
|
|
@@ -7593,7 +7733,7 @@ def collect_failures(root: Path) -> list[Failure]:
|
|
|
7593
7733
|
|
|
7594
7734
|
|
|
7595
7735
|
def print_report(root: Path, failures: list[Failure]) -> None:
|
|
7596
|
-
categories_checked =
|
|
7736
|
+
categories_checked = 20
|
|
7597
7737
|
if not failures:
|
|
7598
7738
|
print(f"loom_check: OK ({root})")
|
|
7599
7739
|
print(f"checked {categories_checked} surfaces")
|
|
@@ -2417,6 +2417,7 @@ def check_root_self_adoption_carrier(root: Path) -> list[Failure]:
|
|
|
2417
2417
|
return failures
|
|
2418
2418
|
|
|
2419
2419
|
required_paths = (
|
|
2420
|
+
".agents/plugins/marketplace.json",
|
|
2420
2421
|
".loom/bootstrap/manifest.json",
|
|
2421
2422
|
".loom/bootstrap/init-result.json",
|
|
2422
2423
|
".loom/bin/loom_init.py",
|
|
@@ -2532,6 +2533,145 @@ def check_root_self_adoption_carrier(root: Path) -> list[Failure]:
|
|
|
2532
2533
|
failures.append(Failure("root-self-adoption", "`root shadow parity` must consume the root repo companion interface"))
|
|
2533
2534
|
if not isinstance(repo_interop, dict) or repo_interop.get("availability") != "present":
|
|
2534
2535
|
failures.append(Failure("root-self-adoption", "`root shadow parity` must consume the root repo interop contract"))
|
|
2536
|
+
failures.extend(check_root_self_plugin_install(root))
|
|
2537
|
+
return failures
|
|
2538
|
+
|
|
2539
|
+
|
|
2540
|
+
def check_root_self_plugin_install(root: Path) -> list[Failure]:
|
|
2541
|
+
failures: list[Failure] = []
|
|
2542
|
+
marketplace_path = root / ".agents/plugins/marketplace.json"
|
|
2543
|
+
if not marketplace_path.exists():
|
|
2544
|
+
return failures
|
|
2545
|
+
try:
|
|
2546
|
+
marketplace = load_json_file(marketplace_path)
|
|
2547
|
+
except json.JSONDecodeError as exc:
|
|
2548
|
+
return [Failure("root-self-plugin", f"`.agents/plugins/marketplace.json` is invalid JSON: {exc.msg}")]
|
|
2549
|
+
if not isinstance(marketplace, dict):
|
|
2550
|
+
return [Failure("root-self-plugin", "`.agents/plugins/marketplace.json` must contain a JSON object")]
|
|
2551
|
+
plugins = marketplace.get("plugins")
|
|
2552
|
+
if not isinstance(plugins, list):
|
|
2553
|
+
failures.append(Failure("root-self-plugin", "`marketplace.plugins` must be a list"))
|
|
2554
|
+
plugins = []
|
|
2555
|
+
loom_entry = next(
|
|
2556
|
+
(
|
|
2557
|
+
entry
|
|
2558
|
+
for entry in plugins
|
|
2559
|
+
if isinstance(entry, dict) and entry.get("name") == "loom"
|
|
2560
|
+
),
|
|
2561
|
+
None,
|
|
2562
|
+
)
|
|
2563
|
+
if not isinstance(loom_entry, dict):
|
|
2564
|
+
failures.append(Failure("root-self-plugin", "Codex marketplace must declare the `loom` plugin"))
|
|
2565
|
+
else:
|
|
2566
|
+
source = loom_entry.get("source")
|
|
2567
|
+
if not isinstance(source, dict) or source.get("source") != "local":
|
|
2568
|
+
failures.append(Failure("root-self-plugin", "Codex marketplace `loom` entry must use a local source"))
|
|
2569
|
+
if not isinstance(source, dict) or source.get("path") != "./plugins/loom":
|
|
2570
|
+
failures.append(Failure("root-self-plugin", "Codex marketplace `loom` entry must point to `./plugins/loom`"))
|
|
2571
|
+
|
|
2572
|
+
package_root = root / "packages/loom-installer"
|
|
2573
|
+
cli_entry = package_root / "dist/src/cli.js"
|
|
2574
|
+
package_json = package_root / "package.json"
|
|
2575
|
+
if not package_json.exists():
|
|
2576
|
+
failures.append(Failure("root-self-plugin", "installer package must exist for self-plugin verification"))
|
|
2577
|
+
return failures
|
|
2578
|
+
|
|
2579
|
+
with tempfile.TemporaryDirectory(prefix="loom-root-self-plugin-") as tmp:
|
|
2580
|
+
tmp_root = Path(tmp)
|
|
2581
|
+
target = tmp_root / "target"
|
|
2582
|
+
home = tmp_root / "home"
|
|
2583
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
2584
|
+
home.mkdir(parents=True, exist_ok=True)
|
|
2585
|
+
env = {
|
|
2586
|
+
"HOME": str(home),
|
|
2587
|
+
"CODEX_HOME": str(home / ".codex"),
|
|
2588
|
+
"LOOM_INSTALLER_BUILD_TIMESTAMP": "2026-01-01T00:00:00.000Z",
|
|
2589
|
+
}
|
|
2590
|
+
commands: list[tuple[str, list[str], Path]] = []
|
|
2591
|
+
if not (package_root / "node_modules/.bin/tsc").exists():
|
|
2592
|
+
commands.append(
|
|
2593
|
+
(
|
|
2594
|
+
"install self-plugin build dependencies",
|
|
2595
|
+
["npm", "ci", "--prefix", str(package_root)],
|
|
2596
|
+
root,
|
|
2597
|
+
)
|
|
2598
|
+
)
|
|
2599
|
+
commands.extend(
|
|
2600
|
+
(
|
|
2601
|
+
(
|
|
2602
|
+
"build self-plugin installer",
|
|
2603
|
+
["npm", "--prefix", str(package_root), "run", "build"],
|
|
2604
|
+
root,
|
|
2605
|
+
),
|
|
2606
|
+
(
|
|
2607
|
+
"install self-plugin payload",
|
|
2608
|
+
[
|
|
2609
|
+
"node",
|
|
2610
|
+
str(cli_entry),
|
|
2611
|
+
"add",
|
|
2612
|
+
"plugin",
|
|
2613
|
+
"--host",
|
|
2614
|
+
"codex",
|
|
2615
|
+
"--target",
|
|
2616
|
+
str(target),
|
|
2617
|
+
"--force",
|
|
2618
|
+
"--json",
|
|
2619
|
+
],
|
|
2620
|
+
root,
|
|
2621
|
+
),
|
|
2622
|
+
)
|
|
2623
|
+
)
|
|
2624
|
+
for label, args, cwd in commands:
|
|
2625
|
+
try:
|
|
2626
|
+
result = run_command(root, args, cwd=cwd, env=env, timeout_seconds=300)
|
|
2627
|
+
except subprocess.TimeoutExpired:
|
|
2628
|
+
failures.append(Failure("root-self-plugin", f"`{label}` timed out"))
|
|
2629
|
+
return failures
|
|
2630
|
+
if result.returncode != 0:
|
|
2631
|
+
detail = result.stderr.strip() or result.stdout.strip() or "command failed without output"
|
|
2632
|
+
failures.append(Failure("root-self-plugin", f"`{label}` failed: {detail}"))
|
|
2633
|
+
return failures
|
|
2634
|
+
|
|
2635
|
+
installed_marketplace = target / ".agents/plugins/marketplace.json"
|
|
2636
|
+
plugin_root = target / "plugins/loom"
|
|
2637
|
+
expected_paths = (
|
|
2638
|
+
plugin_root / ".codex-plugin/plugin.json",
|
|
2639
|
+
plugin_root / "skills/registry.json",
|
|
2640
|
+
plugin_root / "skills/install-layout.json",
|
|
2641
|
+
plugin_root / "skills/shared/scripts/loom_init.py",
|
|
2642
|
+
plugin_root / "skills/loom-init/SKILL.md",
|
|
2643
|
+
)
|
|
2644
|
+
for path in expected_paths:
|
|
2645
|
+
if not path.exists():
|
|
2646
|
+
failures.append(Failure("root-self-plugin", f"self-plugin install is missing `{path.relative_to(target).as_posix()}`"))
|
|
2647
|
+
try:
|
|
2648
|
+
installed = load_json_file(installed_marketplace)
|
|
2649
|
+
except (FileNotFoundError, json.JSONDecodeError) as exc:
|
|
2650
|
+
failures.append(Failure("root-self-plugin", f"installed marketplace is unreadable: {exc}"))
|
|
2651
|
+
else:
|
|
2652
|
+
installed_plugins = installed.get("plugins") if isinstance(installed, dict) else None
|
|
2653
|
+
installed_entry = None
|
|
2654
|
+
if isinstance(installed_plugins, list):
|
|
2655
|
+
installed_entry = next(
|
|
2656
|
+
(
|
|
2657
|
+
entry
|
|
2658
|
+
for entry in installed_plugins
|
|
2659
|
+
if isinstance(entry, dict) and entry.get("name") == "loom"
|
|
2660
|
+
),
|
|
2661
|
+
None,
|
|
2662
|
+
)
|
|
2663
|
+
installed_source = installed_entry.get("source") if isinstance(installed_entry, dict) else None
|
|
2664
|
+
if not isinstance(installed_source, dict) or installed_source.get("path") != "./plugins/loom":
|
|
2665
|
+
failures.append(Failure("root-self-plugin", "installed marketplace must point `loom` to `./plugins/loom`"))
|
|
2666
|
+
if plugin_root.exists():
|
|
2667
|
+
generated_cache = [
|
|
2668
|
+
path.relative_to(target).as_posix()
|
|
2669
|
+
for path in plugin_root.rglob("*")
|
|
2670
|
+
if "__pycache__" in path.parts or path.suffix == ".pyc"
|
|
2671
|
+
]
|
|
2672
|
+
if generated_cache:
|
|
2673
|
+
preview = ", ".join(generated_cache[:5])
|
|
2674
|
+
failures.append(Failure("root-self-plugin", f"self-plugin payload must exclude Python cache artifacts: {preview}"))
|
|
2535
2675
|
return failures
|
|
2536
2676
|
|
|
2537
2677
|
|
|
@@ -7593,7 +7733,7 @@ def collect_failures(root: Path) -> list[Failure]:
|
|
|
7593
7733
|
|
|
7594
7734
|
|
|
7595
7735
|
def print_report(root: Path, failures: list[Failure]) -> None:
|
|
7596
|
-
categories_checked =
|
|
7736
|
+
categories_checked = 20
|
|
7597
7737
|
if not failures:
|
|
7598
7738
|
print(f"loom_check: OK ({root})")
|
|
7599
7739
|
print(f"checked {categories_checked} surfaces")
|