@mc-and-his-agents/loom-installer 0.1.16 → 0.1.17

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.
Files changed (22) hide show
  1. package/package.json +1 -1
  2. package/payload/manifest.json +42 -42
  3. package/payload/plugin/loom/skills/shared/scripts/loom_check.py +59 -0
  4. package/payload/plugin/loom/skills/shared/scripts/loom_flow.py +149 -2
  5. package/payload/skills/loom-adopt/.loom-runtime/shared/scripts/loom_check.py +59 -0
  6. package/payload/skills/loom-adopt/.loom-runtime/shared/scripts/loom_flow.py +149 -2
  7. package/payload/skills/loom-handoff/.loom-runtime/shared/scripts/loom_check.py +59 -0
  8. package/payload/skills/loom-handoff/.loom-runtime/shared/scripts/loom_flow.py +149 -2
  9. package/payload/skills/loom-init/.loom-runtime/shared/scripts/loom_check.py +59 -0
  10. package/payload/skills/loom-init/.loom-runtime/shared/scripts/loom_flow.py +149 -2
  11. package/payload/skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_check.py +59 -0
  12. package/payload/skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_flow.py +149 -2
  13. package/payload/skills/loom-pre-review/.loom-runtime/shared/scripts/loom_check.py +59 -0
  14. package/payload/skills/loom-pre-review/.loom-runtime/shared/scripts/loom_flow.py +149 -2
  15. package/payload/skills/loom-resume/.loom-runtime/shared/scripts/loom_check.py +59 -0
  16. package/payload/skills/loom-resume/.loom-runtime/shared/scripts/loom_flow.py +149 -2
  17. package/payload/skills/loom-retire/.loom-runtime/shared/scripts/loom_check.py +59 -0
  18. package/payload/skills/loom-retire/.loom-runtime/shared/scripts/loom_flow.py +149 -2
  19. package/payload/skills/loom-review/.loom-runtime/shared/scripts/loom_check.py +59 -0
  20. package/payload/skills/loom-review/.loom-runtime/shared/scripts/loom_flow.py +149 -2
  21. package/payload/skills/loom-spec-review/.loom-runtime/shared/scripts/loom_check.py +59 -0
  22. package/payload/skills/loom-spec-review/.loom-runtime/shared/scripts/loom_flow.py +149 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mc-and-his-agents/loom-installer",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "Node installer for Loom plugin and single-skill installation surfaces.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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": "fbbe9a3659bbaf753f47b38aa230ab122c26227b",
5
+ "source_commit": "c3f5933d41b009b1e0537356221c3bcd7fd910be",
6
6
  "source_ref": "main",
7
- "built_at": "2026-04-25T13:09:15+08:00",
7
+ "built_at": "2026-04-25T13:23:16+08:00",
8
8
  "runtime": {
9
9
  "python_minimum": "3.10",
10
10
  "python_recommended": "3.11+"
@@ -628,13 +628,13 @@
628
628
  },
629
629
  {
630
630
  "path": "plugin/loom/skills/shared/scripts/loom_check.py",
631
- "bytes": 267202,
632
- "sha256": "213e64f5d974ae8b4dfbce97af96b88405b30cce3ab01d3d3ebf6a653aba4f4f"
631
+ "bytes": 269850,
632
+ "sha256": "15eecced57199f79c76774b2a629cae291c610d30b64b29a31ef60f4bad65ec7"
633
633
  },
634
634
  {
635
635
  "path": "plugin/loom/skills/shared/scripts/loom_flow.py",
636
- "bytes": 281098,
637
- "sha256": "85012c8824e33f4e7816e451d3aa8d38eec63f4006ae98c657676f2640923184"
636
+ "bytes": 287204,
637
+ "sha256": "d33bde08ceeafa70ddad41ba1c6f8373e0366da08a9b17484ce0ad3683f83960"
638
638
  },
639
639
  {
640
640
  "path": "plugin/loom/skills/shared/scripts/loom_init.py",
@@ -1213,13 +1213,13 @@
1213
1213
  },
1214
1214
  {
1215
1215
  "path": "skills/loom-adopt/.loom-runtime/shared/scripts/loom_check.py",
1216
- "bytes": 267202,
1217
- "sha256": "213e64f5d974ae8b4dfbce97af96b88405b30cce3ab01d3d3ebf6a653aba4f4f"
1216
+ "bytes": 269850,
1217
+ "sha256": "15eecced57199f79c76774b2a629cae291c610d30b64b29a31ef60f4bad65ec7"
1218
1218
  },
1219
1219
  {
1220
1220
  "path": "skills/loom-adopt/.loom-runtime/shared/scripts/loom_flow.py",
1221
- "bytes": 281098,
1222
- "sha256": "85012c8824e33f4e7816e451d3aa8d38eec63f4006ae98c657676f2640923184"
1221
+ "bytes": 287204,
1222
+ "sha256": "d33bde08ceeafa70ddad41ba1c6f8373e0366da08a9b17484ce0ad3683f83960"
1223
1223
  },
1224
1224
  {
1225
1225
  "path": "skills/loom-adopt/.loom-runtime/shared/scripts/loom_init.py",
@@ -1828,13 +1828,13 @@
1828
1828
  },
1829
1829
  {
1830
1830
  "path": "skills/loom-handoff/.loom-runtime/shared/scripts/loom_check.py",
1831
- "bytes": 267202,
1832
- "sha256": "213e64f5d974ae8b4dfbce97af96b88405b30cce3ab01d3d3ebf6a653aba4f4f"
1831
+ "bytes": 269850,
1832
+ "sha256": "15eecced57199f79c76774b2a629cae291c610d30b64b29a31ef60f4bad65ec7"
1833
1833
  },
1834
1834
  {
1835
1835
  "path": "skills/loom-handoff/.loom-runtime/shared/scripts/loom_flow.py",
1836
- "bytes": 281098,
1837
- "sha256": "85012c8824e33f4e7816e451d3aa8d38eec63f4006ae98c657676f2640923184"
1836
+ "bytes": 287204,
1837
+ "sha256": "d33bde08ceeafa70ddad41ba1c6f8373e0366da08a9b17484ce0ad3683f83960"
1838
1838
  },
1839
1839
  {
1840
1840
  "path": "skills/loom-handoff/.loom-runtime/shared/scripts/loom_init.py",
@@ -2443,13 +2443,13 @@
2443
2443
  },
2444
2444
  {
2445
2445
  "path": "skills/loom-init/.loom-runtime/shared/scripts/loom_check.py",
2446
- "bytes": 267202,
2447
- "sha256": "213e64f5d974ae8b4dfbce97af96b88405b30cce3ab01d3d3ebf6a653aba4f4f"
2446
+ "bytes": 269850,
2447
+ "sha256": "15eecced57199f79c76774b2a629cae291c610d30b64b29a31ef60f4bad65ec7"
2448
2448
  },
2449
2449
  {
2450
2450
  "path": "skills/loom-init/.loom-runtime/shared/scripts/loom_flow.py",
2451
- "bytes": 281098,
2452
- "sha256": "85012c8824e33f4e7816e451d3aa8d38eec63f4006ae98c657676f2640923184"
2451
+ "bytes": 287204,
2452
+ "sha256": "d33bde08ceeafa70ddad41ba1c6f8373e0366da08a9b17484ce0ad3683f83960"
2453
2453
  },
2454
2454
  {
2455
2455
  "path": "skills/loom-init/.loom-runtime/shared/scripts/loom_init.py",
@@ -3063,13 +3063,13 @@
3063
3063
  },
3064
3064
  {
3065
3065
  "path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_check.py",
3066
- "bytes": 267202,
3067
- "sha256": "213e64f5d974ae8b4dfbce97af96b88405b30cce3ab01d3d3ebf6a653aba4f4f"
3066
+ "bytes": 269850,
3067
+ "sha256": "15eecced57199f79c76774b2a629cae291c610d30b64b29a31ef60f4bad65ec7"
3068
3068
  },
3069
3069
  {
3070
3070
  "path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_flow.py",
3071
- "bytes": 281098,
3072
- "sha256": "85012c8824e33f4e7816e451d3aa8d38eec63f4006ae98c657676f2640923184"
3071
+ "bytes": 287204,
3072
+ "sha256": "d33bde08ceeafa70ddad41ba1c6f8373e0366da08a9b17484ce0ad3683f83960"
3073
3073
  },
3074
3074
  {
3075
3075
  "path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_init.py",
@@ -3678,13 +3678,13 @@
3678
3678
  },
3679
3679
  {
3680
3680
  "path": "skills/loom-pre-review/.loom-runtime/shared/scripts/loom_check.py",
3681
- "bytes": 267202,
3682
- "sha256": "213e64f5d974ae8b4dfbce97af96b88405b30cce3ab01d3d3ebf6a653aba4f4f"
3681
+ "bytes": 269850,
3682
+ "sha256": "15eecced57199f79c76774b2a629cae291c610d30b64b29a31ef60f4bad65ec7"
3683
3683
  },
3684
3684
  {
3685
3685
  "path": "skills/loom-pre-review/.loom-runtime/shared/scripts/loom_flow.py",
3686
- "bytes": 281098,
3687
- "sha256": "85012c8824e33f4e7816e451d3aa8d38eec63f4006ae98c657676f2640923184"
3686
+ "bytes": 287204,
3687
+ "sha256": "d33bde08ceeafa70ddad41ba1c6f8373e0366da08a9b17484ce0ad3683f83960"
3688
3688
  },
3689
3689
  {
3690
3690
  "path": "skills/loom-pre-review/.loom-runtime/shared/scripts/loom_init.py",
@@ -4293,13 +4293,13 @@
4293
4293
  },
4294
4294
  {
4295
4295
  "path": "skills/loom-resume/.loom-runtime/shared/scripts/loom_check.py",
4296
- "bytes": 267202,
4297
- "sha256": "213e64f5d974ae8b4dfbce97af96b88405b30cce3ab01d3d3ebf6a653aba4f4f"
4296
+ "bytes": 269850,
4297
+ "sha256": "15eecced57199f79c76774b2a629cae291c610d30b64b29a31ef60f4bad65ec7"
4298
4298
  },
4299
4299
  {
4300
4300
  "path": "skills/loom-resume/.loom-runtime/shared/scripts/loom_flow.py",
4301
- "bytes": 281098,
4302
- "sha256": "85012c8824e33f4e7816e451d3aa8d38eec63f4006ae98c657676f2640923184"
4301
+ "bytes": 287204,
4302
+ "sha256": "d33bde08ceeafa70ddad41ba1c6f8373e0366da08a9b17484ce0ad3683f83960"
4303
4303
  },
4304
4304
  {
4305
4305
  "path": "skills/loom-resume/.loom-runtime/shared/scripts/loom_init.py",
@@ -4908,13 +4908,13 @@
4908
4908
  },
4909
4909
  {
4910
4910
  "path": "skills/loom-retire/.loom-runtime/shared/scripts/loom_check.py",
4911
- "bytes": 267202,
4912
- "sha256": "213e64f5d974ae8b4dfbce97af96b88405b30cce3ab01d3d3ebf6a653aba4f4f"
4911
+ "bytes": 269850,
4912
+ "sha256": "15eecced57199f79c76774b2a629cae291c610d30b64b29a31ef60f4bad65ec7"
4913
4913
  },
4914
4914
  {
4915
4915
  "path": "skills/loom-retire/.loom-runtime/shared/scripts/loom_flow.py",
4916
- "bytes": 281098,
4917
- "sha256": "85012c8824e33f4e7816e451d3aa8d38eec63f4006ae98c657676f2640923184"
4916
+ "bytes": 287204,
4917
+ "sha256": "d33bde08ceeafa70ddad41ba1c6f8373e0366da08a9b17484ce0ad3683f83960"
4918
4918
  },
4919
4919
  {
4920
4920
  "path": "skills/loom-retire/.loom-runtime/shared/scripts/loom_init.py",
@@ -5523,13 +5523,13 @@
5523
5523
  },
5524
5524
  {
5525
5525
  "path": "skills/loom-review/.loom-runtime/shared/scripts/loom_check.py",
5526
- "bytes": 267202,
5527
- "sha256": "213e64f5d974ae8b4dfbce97af96b88405b30cce3ab01d3d3ebf6a653aba4f4f"
5526
+ "bytes": 269850,
5527
+ "sha256": "15eecced57199f79c76774b2a629cae291c610d30b64b29a31ef60f4bad65ec7"
5528
5528
  },
5529
5529
  {
5530
5530
  "path": "skills/loom-review/.loom-runtime/shared/scripts/loom_flow.py",
5531
- "bytes": 281098,
5532
- "sha256": "85012c8824e33f4e7816e451d3aa8d38eec63f4006ae98c657676f2640923184"
5531
+ "bytes": 287204,
5532
+ "sha256": "d33bde08ceeafa70ddad41ba1c6f8373e0366da08a9b17484ce0ad3683f83960"
5533
5533
  },
5534
5534
  {
5535
5535
  "path": "skills/loom-review/.loom-runtime/shared/scripts/loom_init.py",
@@ -6138,13 +6138,13 @@
6138
6138
  },
6139
6139
  {
6140
6140
  "path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_check.py",
6141
- "bytes": 267202,
6142
- "sha256": "213e64f5d974ae8b4dfbce97af96b88405b30cce3ab01d3d3ebf6a653aba4f4f"
6141
+ "bytes": 269850,
6142
+ "sha256": "15eecced57199f79c76774b2a629cae291c610d30b64b29a31ef60f4bad65ec7"
6143
6143
  },
6144
6144
  {
6145
6145
  "path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_flow.py",
6146
- "bytes": 281098,
6147
- "sha256": "85012c8824e33f4e7816e451d3aa8d38eec63f4006ae98c657676f2640923184"
6146
+ "bytes": 287204,
6147
+ "sha256": "d33bde08ceeafa70ddad41ba1c6f8373e0366da08a9b17484ce0ad3683f83960"
6148
6148
  },
6149
6149
  {
6150
6150
  "path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_init.py",
@@ -93,6 +93,7 @@ CORE_DOCS = (
93
93
  "docs/evidence/extraction-ledger.md",
94
94
  "docs/evidence/landing-map.md",
95
95
  "docs/evidence/validations/validation-closeout-reconciliation-blocking-gate.md",
96
+ "docs/evidence/validations/validation-adoption-maturity-upgrade-automation.md",
96
97
  "docs/evidence/validations/validation-github-profile-binding-orchestration.md",
97
98
  "docs/evidence/validations/validation-github-profile-drift-reconciliation.md",
98
99
  "docs/evidence/validations/validation-github-profile-graphql-budget-guard.md",
@@ -1329,6 +1330,42 @@ def require_github_binding_payload(
1329
1330
  failures.append(Failure(category, f"{context}.binding findings must fallback to `github-profile-binding`"))
1330
1331
 
1331
1332
 
1333
+ def require_governance_upgrade_payload(
1334
+ failures: list[Failure],
1335
+ *,
1336
+ category: str,
1337
+ context: str,
1338
+ payload: object,
1339
+ ) -> None:
1340
+ if not isinstance(payload, dict):
1341
+ failures.append(Failure(category, f"{context} must be an object"))
1342
+ return
1343
+ if payload.get("command") != "governance-profile":
1344
+ failures.append(Failure(category, f"{context} must report `command: governance-profile`"))
1345
+ if payload.get("operation") != "upgrade":
1346
+ failures.append(Failure(category, f"{context} must report `operation: upgrade`"))
1347
+ if payload.get("schema_version") != "loom-governance-upgrade/v1":
1348
+ failures.append(Failure(category, f"{context} schema_version must be `loom-governance-upgrade/v1`"))
1349
+ if payload.get("result") not in {"pass", "block"}:
1350
+ failures.append(Failure(category, f"{context} result must be pass/block"))
1351
+ if payload.get("target_maturity") not in {"standard", "strong"}:
1352
+ failures.append(Failure(category, f"{context} target_maturity must be standard/strong"))
1353
+ if not isinstance(payload.get("dry_run"), bool):
1354
+ failures.append(Failure(category, f"{context} dry_run must be boolean"))
1355
+ actions = payload.get("actions")
1356
+ if not isinstance(actions, list) or not actions:
1357
+ failures.append(Failure(category, f"{context} must include non-empty actions"))
1358
+ return
1359
+ for action in actions:
1360
+ if not isinstance(action, dict):
1361
+ failures.append(Failure(category, f"{context} actions must be objects"))
1362
+ continue
1363
+ if action.get("owner") not in {"loom-owned", "repo-owned", "profile"}:
1364
+ failures.append(Failure(category, f"{context} action owner must stay within the stable set"))
1365
+ if action.get("status") not in {"planned", "present"}:
1366
+ failures.append(Failure(category, f"{context} action status must be planned/present"))
1367
+
1368
+
1332
1369
  def require_review_record_contract(
1333
1370
  failures: list[Failure],
1334
1371
  *,
@@ -2315,6 +2352,21 @@ def check_daily_execution_cli(root: Path) -> list[Failure]:
2315
2352
  ["python3", "tools/loom_flow.py", "governance-profile", "upgrade-plan", "--target", "examples/new-project"],
2316
2353
  {"pass", "block"},
2317
2354
  ),
2355
+ (
2356
+ "governance-profile-upgrade",
2357
+ [
2358
+ "python3",
2359
+ "tools/loom_flow.py",
2360
+ "governance-profile",
2361
+ "upgrade",
2362
+ "--target",
2363
+ "examples/new-project",
2364
+ "--to",
2365
+ "standard",
2366
+ "--dry-run",
2367
+ ],
2368
+ {"pass"},
2369
+ ),
2318
2370
  (
2319
2371
  "governance-profile-binding",
2320
2372
  ["python3", "tools/loom_flow.py", "governance-profile", "binding", "--target", "."],
@@ -2574,6 +2626,13 @@ def check_daily_execution_cli(root: Path) -> list[Failure]:
2574
2626
  context="`governance-profile binding`",
2575
2627
  payload=payload,
2576
2628
  )
2629
+ if label == "governance-profile-upgrade":
2630
+ require_governance_upgrade_payload(
2631
+ failures,
2632
+ category="daily-execution-cli",
2633
+ context="`governance-profile upgrade`",
2634
+ payload=payload,
2635
+ )
2577
2636
  if label == "flow-pre-review":
2578
2637
  require_runtime_state_payload(
2579
2638
  failures,
@@ -312,8 +312,12 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
312
312
  "governance-profile",
313
313
  help="Read Loom governance maturity and upgrade requirements",
314
314
  )
315
- governance_profile.add_argument("operation", choices=("status", "upgrade-plan", "binding"))
315
+ governance_profile.add_argument("operation", choices=("status", "upgrade-plan", "upgrade", "binding"))
316
316
  governance_profile.add_argument("--target", required=True, help="Target repository root")
317
+ governance_profile.add_argument("--to", choices=("standard", "strong"), help="Target maturity for governance-profile upgrade")
318
+ governance_profile.add_argument("--dry-run", action="store_true", default=True, help="Preview upgrade actions without writing files; this is the default")
319
+ governance_profile.add_argument("--apply", dest="dry_run", action="store_false", help="Apply Loom-owned scaffold writes")
320
+ governance_profile.add_argument("--force", action="store_true", help="Allow replacement of existing Loom-owned scaffold files during upgrade apply")
317
321
  governance_profile.add_argument("--owner", help="GitHub owner; auto-detected from origin when omitted")
318
322
  governance_profile.add_argument("--repo", dest="repo_name", help="GitHub repository name; auto-detected from origin when omitted")
319
323
  governance_profile.add_argument("--phase", type=int, help="GitHub Phase issue number")
@@ -322,7 +326,6 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
322
326
  governance_profile.add_argument("--pr", type=int, help="GitHub implementation PR number")
323
327
  governance_profile.add_argument("--branch", help="GitHub branch name bound to the work item")
324
328
  governance_profile.add_argument("--sync", action="store_true", help="Preview host binding repairs; writes are intentionally disabled in this phase")
325
- governance_profile.add_argument("--dry-run", action="store_true", help="Preview binding sync actions without changing GitHub state")
326
329
 
327
330
  flow = subparsers.add_parser("flow", help="Run a bundled high-frequency Loom flow")
328
331
  flow.add_argument("operation", choices=("pre-review", "review", "spec-review", "resume", "handoff", "merge-ready"))
@@ -3573,6 +3576,141 @@ def governance_profile_payload(target_root: Path, operation: str) -> dict[str, A
3573
3576
  }
3574
3577
 
3575
3578
 
3579
+ UPGRADE_SCAFFOLD: dict[str, dict[str, str]] = {
3580
+ ".loom/companion/manifest.json": json.dumps(
3581
+ {
3582
+ "schema_version": "loom-repo-companion-manifest/v1",
3583
+ "companion_entry": ".loom/companion/AGENTS.md",
3584
+ "repo_interface": ".loom/companion/repo-interface.json",
3585
+ },
3586
+ ensure_ascii=False,
3587
+ indent=2,
3588
+ )
3589
+ + "\n",
3590
+ ".loom/companion/repo-interface.json": json.dumps(
3591
+ {
3592
+ "schema_version": "loom-repo-interface/v2",
3593
+ "companion_entry": ".loom/companion/AGENTS.md",
3594
+ "repo_specific_requirements": {"review": [], "merge_ready": [], "closeout": []},
3595
+ "specialized_gates": [],
3596
+ "metadata_contract": {"fields": []},
3597
+ "context_schema": {"fields": []},
3598
+ },
3599
+ ensure_ascii=False,
3600
+ indent=2,
3601
+ )
3602
+ + "\n",
3603
+ ".loom/companion/interop.json": json.dumps(
3604
+ {
3605
+ "schema_version": "loom-repo-interop/v1",
3606
+ "host_adapters": [],
3607
+ "repo_native_carriers": [],
3608
+ "shadow_surfaces": {},
3609
+ },
3610
+ ensure_ascii=False,
3611
+ indent=2,
3612
+ )
3613
+ + "\n",
3614
+ ".loom/companion/AGENTS.md": "# Loom Repo Companion\n\n本文件承接 repo-local governance residue;Loom core 与 GitHub profile 规则仍以上游合同为准。\n",
3615
+ }
3616
+
3617
+
3618
+ def governance_upgrade_actions(target_root: Path, target_level: str, maturity: dict[str, Any]) -> list[dict[str, Any]]:
3619
+ missing_by_level = maturity.get("missing_by_level")
3620
+ missing = missing_by_level.get(target_level, []) if isinstance(missing_by_level, dict) else []
3621
+ actions: list[dict[str, Any]] = []
3622
+ for relative, content in UPGRADE_SCAFFOLD.items():
3623
+ path = target_root / relative
3624
+ owner = "loom-owned" if relative.startswith(".loom/") else "repo-owned"
3625
+ actions.append(
3626
+ {
3627
+ "action": "write_scaffold" if not path.exists() else "keep_existing",
3628
+ "path": relative,
3629
+ "owner": owner,
3630
+ "status": "present" if path.exists() else "planned",
3631
+ "reason": "required by governance profile upgrade path",
3632
+ "bytes": len(content.encode("utf-8")),
3633
+ }
3634
+ )
3635
+ for item in missing if isinstance(missing, list) else []:
3636
+ actions.append(
3637
+ {
3638
+ "action": "satisfy_missing_input",
3639
+ "id": item,
3640
+ "owner": "loom-owned" if str(item) in {"repo_interface", "repo_interop"} else "profile",
3641
+ "status": "planned",
3642
+ "reason": f"`{target_level}` maturity currently reports this missing input.",
3643
+ }
3644
+ )
3645
+ return actions
3646
+
3647
+
3648
+ def governance_profile_upgrade_payload(
3649
+ *,
3650
+ target_root: Path,
3651
+ target_level: str | None,
3652
+ dry_run: bool,
3653
+ force: bool,
3654
+ ) -> dict[str, Any]:
3655
+ if target_level is None:
3656
+ return {
3657
+ "command": "governance-profile",
3658
+ "operation": "upgrade",
3659
+ "result": "block",
3660
+ "summary": "governance profile upgrade requires `--to standard` or `--to strong`.",
3661
+ "missing_inputs": ["to"],
3662
+ "fallback_to": "adoption",
3663
+ }
3664
+ base = governance_profile_payload(target_root, "upgrade-plan")
3665
+ maturity = base.get("maturity") if isinstance(base.get("maturity"), dict) else {}
3666
+ actions = governance_upgrade_actions(target_root, target_level, maturity if isinstance(maturity, dict) else {})
3667
+ blockers: list[str] = []
3668
+ written_files: list[str] = []
3669
+ if not dry_run:
3670
+ for action in actions:
3671
+ if action.get("action") != "write_scaffold":
3672
+ continue
3673
+ relative = action.get("path")
3674
+ if not isinstance(relative, str):
3675
+ continue
3676
+ if action.get("owner") != "loom-owned":
3677
+ blockers.append(f"{relative} is repo-owned")
3678
+ continue
3679
+ path = target_root / relative
3680
+ if path.exists() and not force:
3681
+ blockers.append(f"{relative} already exists; use --force to replace Loom-owned scaffold")
3682
+ continue
3683
+ content = UPGRADE_SCAFFOLD.get(relative)
3684
+ if content is None:
3685
+ blockers.append(f"{relative} has no scaffold content")
3686
+ continue
3687
+ path.parent.mkdir(parents=True, exist_ok=True)
3688
+ path.write_text(content, encoding="utf-8")
3689
+ written_files.append(relative)
3690
+ result = "block" if blockers else "pass"
3691
+ return {
3692
+ "command": "governance-profile",
3693
+ "operation": "upgrade",
3694
+ "schema_version": "loom-governance-upgrade/v1",
3695
+ "result": result,
3696
+ "summary": (
3697
+ f"governance profile upgrade toward `{target_level}` produced a dry-run action plan."
3698
+ if dry_run and result == "pass"
3699
+ else f"governance profile upgrade toward `{target_level}` applied Loom-owned scaffold writes."
3700
+ if result == "pass"
3701
+ else f"governance profile upgrade toward `{target_level}` is blocked by unsafe writes."
3702
+ ),
3703
+ "missing_inputs": blockers,
3704
+ "fallback_to": None if result == "pass" else "adoption",
3705
+ "target_maturity": target_level,
3706
+ "dry_run": dry_run,
3707
+ "force": force,
3708
+ "actions": actions,
3709
+ "written_files": written_files,
3710
+ "maturity": maturity,
3711
+ }
3712
+
3713
+
3576
3714
  def issue_binding_entry(role: str, number: int | None, payload: dict[str, Any] | None, errors: list[str]) -> dict[str, Any]:
3577
3715
  status = "present" if payload is not None else "missing"
3578
3716
  if errors:
@@ -3800,6 +3938,15 @@ def github_binding_payload(
3800
3938
 
3801
3939
  def handle_governance_profile(args: argparse.Namespace) -> int:
3802
3940
  target_root = Path(args.target).expanduser().resolve()
3941
+ if args.operation == "upgrade":
3942
+ return emit(
3943
+ governance_profile_upgrade_payload(
3944
+ target_root=target_root,
3945
+ target_level=args.to,
3946
+ dry_run=args.dry_run,
3947
+ force=args.force,
3948
+ )
3949
+ )
3803
3950
  if args.operation == "binding":
3804
3951
  return emit(
3805
3952
  github_binding_payload(
@@ -93,6 +93,7 @@ CORE_DOCS = (
93
93
  "docs/evidence/extraction-ledger.md",
94
94
  "docs/evidence/landing-map.md",
95
95
  "docs/evidence/validations/validation-closeout-reconciliation-blocking-gate.md",
96
+ "docs/evidence/validations/validation-adoption-maturity-upgrade-automation.md",
96
97
  "docs/evidence/validations/validation-github-profile-binding-orchestration.md",
97
98
  "docs/evidence/validations/validation-github-profile-drift-reconciliation.md",
98
99
  "docs/evidence/validations/validation-github-profile-graphql-budget-guard.md",
@@ -1329,6 +1330,42 @@ def require_github_binding_payload(
1329
1330
  failures.append(Failure(category, f"{context}.binding findings must fallback to `github-profile-binding`"))
1330
1331
 
1331
1332
 
1333
+ def require_governance_upgrade_payload(
1334
+ failures: list[Failure],
1335
+ *,
1336
+ category: str,
1337
+ context: str,
1338
+ payload: object,
1339
+ ) -> None:
1340
+ if not isinstance(payload, dict):
1341
+ failures.append(Failure(category, f"{context} must be an object"))
1342
+ return
1343
+ if payload.get("command") != "governance-profile":
1344
+ failures.append(Failure(category, f"{context} must report `command: governance-profile`"))
1345
+ if payload.get("operation") != "upgrade":
1346
+ failures.append(Failure(category, f"{context} must report `operation: upgrade`"))
1347
+ if payload.get("schema_version") != "loom-governance-upgrade/v1":
1348
+ failures.append(Failure(category, f"{context} schema_version must be `loom-governance-upgrade/v1`"))
1349
+ if payload.get("result") not in {"pass", "block"}:
1350
+ failures.append(Failure(category, f"{context} result must be pass/block"))
1351
+ if payload.get("target_maturity") not in {"standard", "strong"}:
1352
+ failures.append(Failure(category, f"{context} target_maturity must be standard/strong"))
1353
+ if not isinstance(payload.get("dry_run"), bool):
1354
+ failures.append(Failure(category, f"{context} dry_run must be boolean"))
1355
+ actions = payload.get("actions")
1356
+ if not isinstance(actions, list) or not actions:
1357
+ failures.append(Failure(category, f"{context} must include non-empty actions"))
1358
+ return
1359
+ for action in actions:
1360
+ if not isinstance(action, dict):
1361
+ failures.append(Failure(category, f"{context} actions must be objects"))
1362
+ continue
1363
+ if action.get("owner") not in {"loom-owned", "repo-owned", "profile"}:
1364
+ failures.append(Failure(category, f"{context} action owner must stay within the stable set"))
1365
+ if action.get("status") not in {"planned", "present"}:
1366
+ failures.append(Failure(category, f"{context} action status must be planned/present"))
1367
+
1368
+
1332
1369
  def require_review_record_contract(
1333
1370
  failures: list[Failure],
1334
1371
  *,
@@ -2315,6 +2352,21 @@ def check_daily_execution_cli(root: Path) -> list[Failure]:
2315
2352
  ["python3", "tools/loom_flow.py", "governance-profile", "upgrade-plan", "--target", "examples/new-project"],
2316
2353
  {"pass", "block"},
2317
2354
  ),
2355
+ (
2356
+ "governance-profile-upgrade",
2357
+ [
2358
+ "python3",
2359
+ "tools/loom_flow.py",
2360
+ "governance-profile",
2361
+ "upgrade",
2362
+ "--target",
2363
+ "examples/new-project",
2364
+ "--to",
2365
+ "standard",
2366
+ "--dry-run",
2367
+ ],
2368
+ {"pass"},
2369
+ ),
2318
2370
  (
2319
2371
  "governance-profile-binding",
2320
2372
  ["python3", "tools/loom_flow.py", "governance-profile", "binding", "--target", "."],
@@ -2574,6 +2626,13 @@ def check_daily_execution_cli(root: Path) -> list[Failure]:
2574
2626
  context="`governance-profile binding`",
2575
2627
  payload=payload,
2576
2628
  )
2629
+ if label == "governance-profile-upgrade":
2630
+ require_governance_upgrade_payload(
2631
+ failures,
2632
+ category="daily-execution-cli",
2633
+ context="`governance-profile upgrade`",
2634
+ payload=payload,
2635
+ )
2577
2636
  if label == "flow-pre-review":
2578
2637
  require_runtime_state_payload(
2579
2638
  failures,