@event4u/agent-config 4.1.0 → 4.3.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.
Files changed (66) hide show
  1. package/.agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/CHANGELOG.md +51 -0
  4. package/README.md +3 -2
  5. package/dist/cli/agent-config.js +9 -0
  6. package/dist/cli/agent-config.js.map +1 -1
  7. package/dist/cli/commands/uiServe.js +1 -1
  8. package/dist/cli/commands/uiServe.js.map +1 -1
  9. package/dist/cli/initRouting.js +101 -0
  10. package/dist/cli/initRouting.js.map +1 -0
  11. package/dist/discovery/deprecation-report.md +1 -1
  12. package/dist/discovery/discovery-manifest.json +1 -1
  13. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  14. package/dist/discovery/discovery-manifest.summary.md +1 -1
  15. package/dist/discovery/orphan-report.md +1 -1
  16. package/dist/discovery/packs.json +1 -1
  17. package/dist/discovery/trust-report.md +1 -1
  18. package/dist/discovery/workspaces.json +1 -1
  19. package/dist/mcp/registry-manifest.json +1 -1
  20. package/dist/server/routes/install.js +11 -200
  21. package/dist/server/routes/install.js.map +1 -1
  22. package/dist/server/routes/wizard.js +167 -26
  23. package/dist/server/routes/wizard.js.map +1 -1
  24. package/dist/server/schemas/settings.js +1 -0
  25. package/dist/server/schemas/settings.js.map +1 -1
  26. package/dist/ui/assets/index-BDAhhpDV.js +40 -0
  27. package/dist/ui/assets/index-BDAhhpDV.js.map +1 -0
  28. package/dist/ui/index.html +1 -1
  29. package/docs/contracts/gui-wizard.md +116 -96
  30. package/docs/decisions/ADR-021-deployment-shape.md +2 -2
  31. package/docs/deploy/connector-setup.md +2 -2
  32. package/docs/deploy/policy-cookbook.md +2 -2
  33. package/package.json +1 -1
  34. package/scripts/__pycache__/validate_frontmatter.cpython-312.pyc +0 -0
  35. package/scripts/_lib/__pycache__/__init__.cpython-312.pyc +0 -0
  36. package/scripts/_lib/__pycache__/agent_src.cpython-312.pyc +0 -0
  37. package/scripts/install.py +197 -34
  38. package/scripts/lint_framework_leakage_allowlist.json +8 -0
  39. package/dist/install/apply.js +0 -238
  40. package/dist/install/apply.js.map +0 -1
  41. package/dist/install/bridges/augment.js +0 -20
  42. package/dist/install/bridges/augment.js.map +0 -1
  43. package/dist/install/bridges/claude.js +0 -44
  44. package/dist/install/bridges/claude.js.map +0 -1
  45. package/dist/install/bridges/cline.js +0 -69
  46. package/dist/install/bridges/cline.js.map +0 -1
  47. package/dist/install/bridges/copilot.js +0 -28
  48. package/dist/install/bridges/copilot.js.map +0 -1
  49. package/dist/install/bridges/cursor.js +0 -34
  50. package/dist/install/bridges/cursor.js.map +0 -1
  51. package/dist/install/bridges/gemini.js +0 -39
  52. package/dist/install/bridges/gemini.js.map +0 -1
  53. package/dist/install/bridges/index.js +0 -88
  54. package/dist/install/bridges/index.js.map +0 -1
  55. package/dist/install/bridges/marker-content.js +0 -153
  56. package/dist/install/bridges/marker-content.js.map +0 -1
  57. package/dist/install/bridges/markers.js +0 -42
  58. package/dist/install/bridges/markers.js.map +0 -1
  59. package/dist/install/bridges/types.js +0 -31
  60. package/dist/install/bridges/types.js.map +0 -1
  61. package/dist/install/bridges/vscode.js +0 -26
  62. package/dist/install/bridges/vscode.js.map +0 -1
  63. package/dist/install/bridges/windsurf.js +0 -35
  64. package/dist/install/bridges/windsurf.js.map +0 -1
  65. package/dist/ui/assets/index-DLEuEW1V.js +0 -35
  66. package/dist/ui/assets/index-DLEuEW1V.js.map +0 -1
@@ -88,6 +88,43 @@ LEGACY_RENAME_MAP = {
88
88
 
89
89
  QUIET = False
90
90
 
91
+ # Machine-readable progress stream for the wizard `--apply-payload` real-apply
92
+ # bridge (road-to-single-install-source-of-truth § Phase 1). When True,
93
+ # `_emit_progress` writes NDJSON lines to stdout so the GUI can stream install
94
+ # progress; human-readable `info`/`success` output is suppressed via QUIET so
95
+ # the two never interleave (`warn`/`fail` still surface on stderr). Off for
96
+ # every normal CLI install.
97
+ PROGRESS_NDJSON = False
98
+
99
+
100
+ def _emit_progress(obj: "dict[str, Any]") -> None:
101
+ """Write one NDJSON progress line to stdout when PROGRESS_NDJSON is on.
102
+
103
+ No-op for normal CLI installs. The line shapes mirror the wizard SSE
104
+ frames the GUI already consumes: per-unit
105
+ ``{"type":"file","file":...,"status":...,"written":N,"total":M}`` and the
106
+ terminal ``{"type":"done"|"error",...}``.
107
+ """
108
+ if not PROGRESS_NDJSON:
109
+ return
110
+ sys.stdout.write(json.dumps(obj, separators=(",", ":")) + "\n")
111
+ sys.stdout.flush()
112
+
113
+
114
+ def _emit_progress_terminal(rc: int) -> None:
115
+ """Emit the terminal NDJSON frame for a real-apply run.
116
+
117
+ rc == 0 → ``{"type":"done"}``; otherwise ``{"type":"error",...}``. No-op
118
+ unless PROGRESS_NDJSON is on, so it is safe to call from every install
119
+ return path.
120
+ """
121
+ if not PROGRESS_NDJSON:
122
+ return
123
+ if rc == 0:
124
+ _emit_progress({"type": "done"})
125
+ else:
126
+ _emit_progress({"type": "error", "code": "E_INSTALL", "exitCode": rc})
127
+
91
128
 
92
129
  def info(msg: str) -> None:
93
130
  if not QUIET:
@@ -817,12 +854,42 @@ def _validate_user_type(package_root: Path, value: str) -> str:
817
854
  return cleaned
818
855
 
819
856
 
857
+ def _inject_packs(body: str, packs: "list[str]") -> str:
858
+ """Insert a top-level ``packs:`` block into a rendered settings body.
859
+
860
+ Inserted directly after the ``cost_profile:`` line so the active pack
861
+ selection sits beside the other install-time knobs. No-op when ``packs``
862
+ is empty — non-pack installs stay byte-identical to the template render.
863
+ """
864
+ if not packs:
865
+ return body
866
+ block = "packs:\n" + "".join(f" - {p}\n" for p in packs)
867
+ lines = body.splitlines(keepends=True)
868
+ out: list[str] = []
869
+ inserted = False
870
+ for line in lines:
871
+ out.append(line)
872
+ if not inserted and line.startswith("cost_profile:"):
873
+ if not line.endswith("\n"):
874
+ out[-1] = line + "\n"
875
+ out.append(block)
876
+ inserted = True
877
+ if not inserted:
878
+ # No cost_profile anchor (unexpected) — append at the end so the
879
+ # selection is still recorded rather than silently dropped.
880
+ if out and not out[-1].endswith("\n"):
881
+ out[-1] = out[-1] + "\n"
882
+ out.append(block)
883
+ return "".join(out)
884
+
885
+
820
886
  def ensure_agent_settings(
821
887
  project_root: Path,
822
888
  package_root: Path,
823
889
  profile: str,
824
890
  force: bool,
825
891
  user_type: str = "",
892
+ packs: "list[str] | None" = None,
826
893
  ) -> None:
827
894
  target = project_root / SETTINGS_FILE
828
895
  profile_source = package_root / "config" / "profiles" / f"{profile}.ini"
@@ -847,6 +914,7 @@ def ensure_agent_settings(
847
914
  # Inject runtime-only values (not part of the .ini profile presets).
848
915
  profile_values["user_type"] = _validate_user_type(package_root, user_type)
849
916
  template_body = _render_template(template, profile_values)
917
+ template_body = _inject_packs(template_body, packs or [])
850
918
 
851
919
  legacy_target = project_root / LEGACY_SETTINGS_FILE
852
920
  if legacy_target.is_file() and target.exists():
@@ -3524,6 +3592,24 @@ def install_global(
3524
3592
  package_root = _resolve_package_root_for_global()
3525
3593
  deploy_results = _deploy_global_content(tools, force, package_root, written)
3526
3594
 
3595
+ # NDJSON progress for the wizard --apply-payload real-apply bridge. One
3596
+ # `file` frame per deployed tool unit (coarse, per AI-council 2026-05-27);
3597
+ # the GUI maps these to its SSE progress frames. No-op under normal CLI
3598
+ # installs (PROGRESS_NDJSON off). Emitted independent of QUIET because the
3599
+ # real-apply path sets QUIET=True to silence the human stream.
3600
+ if PROGRESS_NDJSON:
3601
+ ordered = sorted(deploy_results)
3602
+ total = len(ordered)
3603
+ for idx, tool_id in enumerate(ordered, start=1):
3604
+ w, _s, status, _ = deploy_results[tool_id]
3605
+ _emit_progress({
3606
+ "type": "file",
3607
+ "file": tool_id,
3608
+ "status": status,
3609
+ "written": idx,
3610
+ "total": total,
3611
+ })
3612
+
3527
3613
  if not QUIET:
3528
3614
  print()
3529
3615
  info("Deployed per-tool content:")
@@ -3697,6 +3783,17 @@ def parse_options(argv: list[str]) -> argparse.Namespace:
3697
3783
  "the comma-separated values are unioned. Default: all."
3698
3784
  ),
3699
3785
  )
3786
+ parser.add_argument(
3787
+ "--packs",
3788
+ default=None,
3789
+ help=(
3790
+ "comma-separated pack IDs to record as the active selection in "
3791
+ ".agent-settings.yml (project scope). Packs are a "
3792
+ "frontmatter/condense-time concept — recording the selection "
3793
+ "lets downstream condense/runtime honor it; install.py does not "
3794
+ "materialize packs. Default: none (base package only)."
3795
+ ),
3796
+ )
3700
3797
  parser.add_argument(
3701
3798
  "--no-smoke",
3702
3799
  action="store_true",
@@ -3805,6 +3902,13 @@ def parse_options(argv: list[str]) -> argparse.Namespace:
3805
3902
  )
3806
3903
  opts = parser.parse_args(argv)
3807
3904
  opts.tools = _merge_tools_aliases(opts.tools, opts.ai)
3905
+ # Normalize --packs (comma-separated string | None) to a list so the CLI
3906
+ # and the --apply-payload bridge agree on opts.packs being list[str].
3907
+ opts.packs = (
3908
+ [p.strip() for p in opts.packs.split(",") if p.strip()]
3909
+ if isinstance(opts.packs, str)
3910
+ else []
3911
+ )
3808
3912
  if opts.scope == "global" and opts.custom_path:
3809
3913
  fail("--custom-path is incompatible with --scope=global")
3810
3914
  if opts.global_install and opts.custom_path:
@@ -4167,16 +4271,17 @@ def run_interactive_init(project_root: Path, force: bool) -> int:
4167
4271
  # --- Wizard auto-launch (Phase 6 follow-up) ---
4168
4272
  #
4169
4273
  # Auto-launches the browser configuration wizard at the tail of a
4170
- # successful install. The TS side ships a `gui` subcommand on the
4171
- # installer CLI; this Python parent acts as a supervisor that:
4274
+ # successful install. The unified CLI ships an `install` subcommand
4275
+ # (`dist/cli/agent-config.js`); this Python parent acts as a supervisor that:
4172
4276
  #
4173
4277
  # 1. evaluates gate conditions (TTY, CI, --no-ui, env override),
4174
4278
  # 2. validates the dist exists,
4175
- # 3. spawns `node <cli> gui --project-root <root>` via subprocess.Popen,
4279
+ # 3. spawns `node <cli> install --no-open --project-root <root>` via subprocess.Popen,
4176
4280
  # 4. captures stderr on a background thread (for failure surfacing),
4177
4281
  # 5. reads stdout line-by-line with a progressive timeout
4178
4282
  # (10s → 20s → 40s → 80s) and matches the strict readiness regex
4179
- # `^WIZARD_READY url=(http://(?:127.0.0.1|localhost):\d+/)\r?$`,
4283
+ # `^WIZARD_READY (http://(?:127.0.0.1|localhost):\d+/\S*)\r?$`
4284
+ # (the CLI prints `WIZARD_READY <url>` where url carries `?token=…`),
4180
4285
  # 6. on success: prints the URL banner and waits for the child to
4181
4286
  # exit (Ctrl-C in the parent terminal propagates to the child),
4182
4287
  # 7. on timeout: kills the child, prints captured stderr tail, falls
@@ -4186,7 +4291,7 @@ def run_interactive_init(project_root: Path, force: bool) -> int:
4186
4291
  # Roadmap: agents/roadmaps/wizard-install-py-wiring.md Step 3.
4187
4292
 
4188
4293
  _WIZARD_READY_RE = re.compile(
4189
- r"^WIZARD_READY url=(http://(?:127\.0\.0\.1|localhost):\d+/)\r?$"
4294
+ r"^WIZARD_READY (http://(?:127\.0\.0\.1|localhost):\d+/\S*)\r?$"
4190
4295
  )
4191
4296
  _WIZARD_TIMEOUTS = (10.0, 20.0, 40.0, 80.0) # cumulative budget 150s.
4192
4297
 
@@ -4195,8 +4300,8 @@ def _wizard_should_launch(opts: argparse.Namespace) -> tuple[bool, str]:
4195
4300
  """Evaluate gate conditions for the post-install wizard auto-launch.
4196
4301
 
4197
4302
  Returns (decision, reason). When decision is False the reason
4198
- string explains why (CI / no-tty / --no-ui / env override) and is
4199
- suitable for the pre-install banner Council Tier 2 § 8.
4303
+ string explains why (CI / no-tty / --no-ui / env override / explicit
4304
+ --tools) and is suitable for the pre-install banner Council Tier 2 § 8.
4200
4305
  """
4201
4306
  if getattr(opts, "no_ui", False):
4202
4307
  return (False, "--no-ui flag set")
@@ -4207,18 +4312,26 @@ def _wizard_should_launch(opts: argparse.Namespace) -> tuple[bool, str]:
4207
4312
  return (False, "CI environment detected")
4208
4313
  if not sys.stdout.isatty():
4209
4314
  return (False, "stdout is not a TTY")
4315
+ # Explicit `--tools=<list>` means the caller already knows what to
4316
+ # install — run the non-interactive CLI install, don't open the GUI
4317
+ # (road-to-single-install-source-of-truth § Phase 4). The implicit/
4318
+ # explicit `all` default does NOT suppress the wizard.
4319
+ tools_raw = getattr(opts, "tools", None)
4320
+ if tools_raw and not _tools_was_all(tools_raw):
4321
+ return (False, "explicit --tools= selection (headless install)")
4210
4322
  return (True, "")
4211
4323
 
4212
4324
 
4213
4325
  def _wizard_cli_dist(project_root: Path) -> Path | None:
4214
- """Resolve the installer dist path. Returns None if not built.
4326
+ """Resolve the unified CLI dist path. Returns None if not built.
4215
4327
 
4216
- Walks up from this file (scripts/install.py is at <pkg>/scripts/)
4217
- to <pkg>/packages/core/installer/dist/cli.js. That's the layout
4218
- the monorepo ships; consumer installs run `node <pkg>/.../cli.js`.
4328
+ Walks up from this file (scripts/install.py is at <pkg>/scripts/) to
4329
+ <pkg>/dist/cli/agent-config.js the published bin entry (package.json
4330
+ `bin`). The dead `packages/core/installer/dist/cli.js` layout was
4331
+ retired in road-to-single-install-source-of-truth § Phase 4.
4219
4332
  """
4220
4333
  package_root = Path(__file__).resolve().parent.parent
4221
- cli = package_root / "packages" / "core" / "installer" / "dist" / "cli.js"
4334
+ cli = package_root / "dist" / "cli" / "agent-config.js"
4222
4335
  return cli if cli.exists() else None
4223
4336
 
4224
4337
 
@@ -4233,17 +4346,18 @@ def _wizard_spawn(project_root: Path) -> int:
4233
4346
  cli = _wizard_cli_dist(project_root)
4234
4347
  if cli is None:
4235
4348
  print(
4236
- "(Wizard not available — installer package not built. "
4237
- "Run 'npm run build' inside packages/core/installer/.)"
4349
+ "(Wizard not available — CLI bundle not built. "
4350
+ "Run 'npm run build' at the package root to produce dist/cli/.)"
4238
4351
  )
4239
4352
  return 0
4240
4353
 
4241
- cmd = ["node", str(cli), "gui", "--project-root", str(project_root)]
4354
+ # Spawn the unified CLI's `install` subcommand (boots the UI server,
4355
+ # lands on Step 1 / AI tools). `--no-open` keeps the Python parent in
4356
+ # charge of the user-facing URL print (Tier 2 § 8 ordering) — the dead
4357
+ # `gui` subcommand + AGENT_CONFIG_GUI_NO_OPEN env were retired in
4358
+ # road-to-single-install-source-of-truth § Phase 4.
4359
+ cmd = ["node", str(cli), "install", "--no-open", "--project-root", str(project_root)]
4242
4360
  env = os.environ.copy()
4243
- # The Node child writes its own readiness banner; suppress the
4244
- # browser-open inside the child so the Python parent stays in
4245
- # charge of the user-facing URL print (Tier 2 § 8 ordering).
4246
- env.setdefault("AGENT_CONFIG_GUI_NO_OPEN", "1")
4247
4361
 
4248
4362
  try:
4249
4363
  child = subprocess.Popen( # noqa: S603 - cmd is locally-built, not user input
@@ -4255,7 +4369,7 @@ def _wizard_spawn(project_root: Path) -> int:
4255
4369
  bufsize=1, # line-buffered
4256
4370
  )
4257
4371
  except OSError as exc:
4258
- print(f"(Wizard failed to start: {exc}; run 'node {cli} gui' manually.)")
4372
+ print(f"(Wizard failed to start: {exc}; run 'node {cli} install --no-open' manually.)")
4259
4373
  return 0
4260
4374
 
4261
4375
  # Drain stderr on a background thread so a chatty child can't
@@ -4327,7 +4441,7 @@ def _wizard_await_ready(
4327
4441
  tail = "\n ".join(stderr_tail[-20:]) if stderr_tail else "(no stderr captured)"
4328
4442
  print(
4329
4443
  f"(Wizard server boot timed out after {int(sum(_WIZARD_TIMEOUTS))}s; "
4330
- f"run 'node {cli} gui' manually.)\n"
4444
+ f"run 'node {cli} install --no-open' manually.)\n"
4331
4445
  f" Last stderr:\n {tail}"
4332
4446
  )
4333
4447
  return 0
@@ -4445,10 +4559,9 @@ def main(argv: list[str]) -> int:
4445
4559
  f"--apply-payload schema_version must be 'wizard-v2' or "
4446
4560
  f"'installer-v1', got {schema_version!r}"
4447
4561
  )
4448
- # Translate payload → opts so the dry-run / real-install path
4449
- # downstream sees the same shape it would from CLI flags. Only
4450
- # dry-run preview is wired in Phase 1.5; real apply lands in a
4451
- # follow-up minor (kill-switch via package version per D7).
4562
+ # Translate payload → opts so the SAME canonical install path
4563
+ # downstream sees the shape it would from CLI flags — no second
4564
+ # code path (road-to-single-install-source-of-truth § Phase 1).
4452
4565
  if schema_version == "wizard-v2":
4453
4566
  tools = payload.get("tools") or []
4454
4567
  if isinstance(tools, list) and tools:
@@ -4457,6 +4570,36 @@ def main(argv: list[str]) -> int:
4457
4570
  opts.scope = "project"
4458
4571
  else:
4459
4572
  opts.scope = "global"
4573
+ # settings{} → --profile / --user-type. `settings` is the
4574
+ # merged .agent-settings.yml body; pull the two install-time
4575
+ # knobs the canonical installer consumes. Everything else in
4576
+ # the settings body is written by the wizard `/finish` 2PC
4577
+ # commit, not by install.py.
4578
+ settings = payload.get("settings") or {}
4579
+ if isinstance(settings, dict):
4580
+ cost_profile = settings.get("cost_profile")
4581
+ if isinstance(cost_profile, str) and cost_profile:
4582
+ opts.profile = cost_profile
4583
+ personal = settings.get("personal")
4584
+ if isinstance(personal, dict):
4585
+ user_type = personal.get("user_type")
4586
+ if isinstance(user_type, str) and user_type:
4587
+ opts.user_type = user_type
4588
+ # packs[] → declarative selection persisted into
4589
+ # .agent-settings.yml on the project path (ensure_agent_settings).
4590
+ # Packs are a frontmatter/condense-time concept; there is no
4591
+ # install-time materialization — the selection is recorded so
4592
+ # downstream condense/runtime honors it (AI-council 2026-05-27).
4593
+ packs = payload.get("packs") or []
4594
+ if isinstance(packs, list):
4595
+ opts.packs = [p for p in packs if isinstance(p, str)]
4596
+ # Per-tool user-hook opts → ensure_*_hook flags: deliberately
4597
+ # NOT wired. The wizard-v2 payload carries no hook fields, and
4598
+ # auto-enabling user-scope hooks (which write to the global
4599
+ # user config) on tool selection would silently widen install
4600
+ # scope — a non-destructive-by-default violation. Maps only
4601
+ # once the schema grows an explicit hooks field (AI-council
4602
+ # 2026-05-27, Gemini + Codex converged).
4460
4603
  elif schema_version == "installer-v1":
4461
4604
  ai_tools = payload.get("ai_tools") or []
4462
4605
  if isinstance(ai_tools, list) and ai_tools:
@@ -4466,13 +4609,13 @@ def main(argv: list[str]) -> int:
4466
4609
  opts.dry_run = True
4467
4610
  if opts.dry_run:
4468
4611
  return _apply_payload_preview(payload, opts)
4469
- # Real-apply path through the payload bridge is gated by the
4470
- # follow-up minor (Phase 1.9). Until then, fail loudly so the
4471
- # caller knows they need --dry-run.
4472
- fail(
4473
- "--apply-payload without --dry-run is not yet wired "
4474
- "(road-to-global-only-install § Phase 1.5 ships dry-run only)."
4475
- )
4612
+ # Real apply: stream machine-readable NDJSON progress on stdout and
4613
+ # silence human output so the GUI gets a clean stream. Then fall
4614
+ # through to the canonical install path below — no separate apply
4615
+ # implementation.
4616
+ global PROGRESS_NDJSON
4617
+ PROGRESS_NDJSON = True
4618
+ QUIET = True
4476
4619
 
4477
4620
  # --offline: propagate via env so child subprocesses (versions /
4478
4621
  # update / check_update_banner) honor the air-gap guarantee
@@ -4561,7 +4704,9 @@ def main(argv: list[str]) -> int:
4561
4704
  return rc
4562
4705
  # Pass detect_root so the manifest refresh runs when --global is
4563
4706
  # invoked from within a project tree (ADR-008 Phase 3.2).
4564
- return install_global(parsed_tools, opts.force, project_root=detect_root)
4707
+ rc = install_global(parsed_tools, opts.force, project_root=detect_root)
4708
+ _emit_progress_terminal(rc)
4709
+ return rc
4565
4710
 
4566
4711
  project_root = custom_path or Path(opts.project or os.environ.get("PROJECT_ROOT") or os.getcwd()).resolve()
4567
4712
  is_first_run = not (project_root / SETTINGS_FILE).exists()
@@ -4571,9 +4716,11 @@ def main(argv: list[str]) -> int:
4571
4716
  # never ships ahead of the bridge files it parameterizes.
4572
4717
  if rc == 0 and getattr(opts, "interactive", False):
4573
4718
  run_interactive_init(project_root, opts.force)
4719
+ _emit_progress_terminal(rc)
4574
4720
  return rc
4575
4721
  except ConflictAbort as exc:
4576
4722
  warn(exc.message)
4723
+ _emit_progress({"type": "error", "code": "E_CONFLICT_UNRESOLVED", "message": exc.message})
4577
4724
  return 1
4578
4725
  finally:
4579
4726
  _set_conflict_policy(None)
@@ -4670,7 +4817,8 @@ def _main_project_install(
4670
4817
  print()
4671
4818
 
4672
4819
  ensure_agent_settings(
4673
- project_root, package_root, opts.profile, opts.force, opts.user_type
4820
+ project_root, package_root, opts.profile, opts.force, opts.user_type,
4821
+ packs=getattr(opts, "packs", None),
4674
4822
  )
4675
4823
 
4676
4824
  # Install-mode marker (Step 8 A5) — full path flips any prior
@@ -4745,6 +4893,21 @@ def _main_project_install(
4745
4893
  ensure_gemini_user_hooks(package_root, opts.force),
4746
4894
  )
4747
4895
 
4896
+ # NDJSON progress for the wizard --apply-payload real-apply bridge on the
4897
+ # project scope (scope_to_project_only=true). One `file` frame per enabled
4898
+ # tool unit, coarse per AI-council 2026-05-27. No-op under normal CLI.
4899
+ if PROGRESS_NDJSON and not opts.skip_bridges:
4900
+ ordered = sorted(tools)
4901
+ total = len(ordered)
4902
+ for idx, tool_id in enumerate(ordered, start=1):
4903
+ _emit_progress({
4904
+ "type": "file",
4905
+ "file": tool_id,
4906
+ "status": "deployed",
4907
+ "written": idx,
4908
+ "total": total,
4909
+ })
4910
+
4748
4911
  if not opts.skip_bridges and not opts.no_smoke:
4749
4912
  if not QUIET:
4750
4913
  print()
@@ -2,6 +2,14 @@
2
2
  "version": 1,
3
3
  "_doc": "Each entry: { file: relative path from repo root, lines: [int,...] | \"*\" for whole file, reason: short justification }. Use sparingly — first ask whether the file should be neutralized instead.",
4
4
  "entries": [
5
+ {
6
+ "file": ".agent-src.uncondensed/skills/module-detect-on-the-fly/SKILL.md",
7
+ "lines": [
8
+ 42,
9
+ 104
10
+ ],
11
+ "reason": "Cross-stack noise-segment enumeration (vendor + node_modules + dist/Modules) documenting the module-detection path filter"
12
+ },
5
13
  {
6
14
  "file": ".agent-src.uncondensed/skills/using-git-worktrees/SKILL.md",
7
15
  "lines": [
@@ -1,238 +0,0 @@
1
- /**
2
- * Apply orchestrator \u2014 Phase A4 (atomic writes + transaction log).
3
- *
4
- * Consumes an {@link InstallPlan} produced by `plan.ts:buildInstallPlan`
5
- * and walks every {@link FileEntry}, copying source bytes into the
6
- * declared target with {@link atomicWriteFile}. Each successful write
7
- * appends one {@link TxLogEntry} (kind=`write`) to the transaction log
8
- * so a recovery pass on next boot can reverse-apply on crash.
9
- *
10
- * Pure orchestration:
11
- * - No path recomputation (the plan is the source of truth).
12
- * - No re-walking of the source tree.
13
- * - SHA-256 idempotency: skip an entry whose target already exists
14
- * with matching bytes, unless `force=true` flips the policy.
15
- *
16
- * Progress is surfaced via the optional `onProgress` callback so the
17
- * Fastify SSE adapter (Phase B1) can stream per-file events to the
18
- * Preact wizard.
19
- */
20
- import { existsSync, readFileSync } from 'node:fs';
21
- import { atomicWriteFile } from './atomic.js';
22
- import { isJsonTarget, mergeJsonContent, parseJsonLenient, resolveFileConflict, } from './conflict.js';
23
- import { sha256File } from './plan.js';
24
- import { appendTxLog } from './txlog.js';
25
- /**
26
- * Execute an {@link InstallPlan}.
27
- *
28
- * One pass through every entry. Writes are atomic (write-to-temp +
29
- * rename); the txlog records each success so recovery can reverse-apply.
30
- *
31
- * Returns {@link ApplyResult} aggregating per-file outcomes. Never
32
- * throws on individual file errors \u2014 errors land in
33
- * {@link ApplyResult.errors} so the wizard can render a partial-success
34
- * screen rather than crashing the install.
35
- */
36
- export function applyPlan(inputs) {
37
- const { plan, sourceByTarget, logPath, onProgress, resolutions } = inputs;
38
- const all = flattenEntries(plan);
39
- const acc = newAccumulator();
40
- let index = 0;
41
- for (const entry of all) {
42
- index += 1;
43
- processEntry(entry, plan, sourceByTarget, logPath, acc, index, all.length, onProgress, resolutions);
44
- }
45
- return { target: plan.target, ...acc };
46
- }
47
- /**
48
- * Async streaming variant — yields the event loop between entries so an
49
- * {@link AbortSignal} fired by a disconnecting SSE client can stop the
50
- * loop. Each entry still executes synchronously (atomic write + txlog
51
- * append) so a mid-write abort is impossible; aborts land between
52
- * entries and append a single `abort` marker before resolving.
53
- *
54
- * Council Finding #24: SSE handlers must wire `req.on("close")` to an
55
- * `AbortController` and pass the signal here so half-applied installs
56
- * surface a clean partial result instead of a zombie loop.
57
- */
58
- export async function applyPlanStreaming(inputs) {
59
- const { plan, sourceByTarget, logPath, onProgress, signal, resolutions } = inputs;
60
- const all = flattenEntries(plan);
61
- const acc = newAccumulator();
62
- let index = 0;
63
- for (const entry of all) {
64
- if (signal?.aborted === true) {
65
- appendTxLog(logPath, {
66
- ts: new Date().toISOString(),
67
- kind: 'abort',
68
- path: entry.path,
69
- sha256: null,
70
- note: 'client disconnect',
71
- });
72
- break;
73
- }
74
- index += 1;
75
- processEntry(entry, plan, sourceByTarget, logPath, acc, index, all.length, onProgress, resolutions);
76
- // Yield to the event loop so the abort signal can fire and any
77
- // SSE write buffers can drain before the next entry.
78
- await new Promise((resolve) => setImmediate(resolve));
79
- }
80
- return { target: plan.target, ...acc };
81
- }
82
- function newAccumulator() {
83
- return { written: [], skipped: [], conflicts: [], errors: [] };
84
- }
85
- function flattenEntries(plan) {
86
- const all = [];
87
- for (const entries of Object.values(plan.filesByTool)) {
88
- for (const e of entries)
89
- all.push(e);
90
- }
91
- return all;
92
- }
93
- /**
94
- * Process one {@link FileEntry} — resolve conflict, write or skip, append
95
- * txlog, push to accumulator, emit progress.
96
- *
97
- * Shared by sync {@link applyPlan} and async {@link applyPlanStreaming}
98
- * so the conflict matrix lives in one place.
99
- */
100
- function processEntry(entry, plan, sourceByTarget, logPath, acc, index, total, onProgress, resolutions) {
101
- try {
102
- if (entry.kind === 'bridge') {
103
- // Bridges are pointers we don't own bytes-for-bytes — skip
104
- // them at the apply layer; bridge generators (A6) produce
105
- // them via their own writer pass.
106
- acc.skipped.push(entry);
107
- onProgress?.({ file: entry, written: index, total, status: 'skipped' });
108
- return;
109
- }
110
- const source = sourceByTarget.get(entry.path);
111
- if (source === undefined) {
112
- acc.errors.push({
113
- path: entry.path,
114
- code: 'E_PLAN_MISSING_SOURCE',
115
- message: 'no source mapping for target path',
116
- });
117
- onProgress?.({
118
- file: entry,
119
- written: index,
120
- total,
121
- status: 'error',
122
- error: { code: 'E_PLAN_MISSING_SOURCE', message: 'no source mapping' },
123
- });
124
- return;
125
- }
126
- const idempotent = isIdempotent(entry, plan);
127
- const exists = existsSync(entry.path);
128
- const outcome = resolveFileConflict({
129
- targetPath: entry.path,
130
- idempotent,
131
- exists,
132
- policy: plan.policy,
133
- });
134
- if (outcome === 'skip') {
135
- acc.skipped.push(entry);
136
- onProgress?.({ file: entry, written: index, total, status: 'skipped' });
137
- return;
138
- }
139
- if (outcome === 'surface') {
140
- // Phase B3 — the wizard may have pre-resolved this conflict
141
- // before the apply call. The `resolutions` map is keyed by
142
- // absolute target path. Missing keys → unresolved conflict
143
- // (acc.conflicts) so the wizard surfaces them again. An
144
- // explicit `skip` resolution → acc.skipped: the user decided
145
- // to leave the file alone, that is a settled outcome.
146
- const resolution = resolutions?.get(entry.path);
147
- if (resolution === undefined) {
148
- acc.conflicts.push(entry);
149
- onProgress?.({ file: entry, written: index, total, status: 'conflict' });
150
- return;
151
- }
152
- if (resolution === 'skip') {
153
- acc.skipped.push(entry);
154
- onProgress?.({ file: entry, written: index, total, status: 'skipped' });
155
- return;
156
- }
157
- const data = readFileSync(source);
158
- const payload = resolution === 'merge' && isJsonTarget(entry) && exists
159
- ? mergeJsonPayload(entry.path, data)
160
- : data;
161
- atomicWriteFile(entry.path, payload);
162
- appendTxLog(logPath, {
163
- ts: new Date().toISOString(),
164
- kind: 'write',
165
- path: entry.path,
166
- sha256: entry.sha256,
167
- });
168
- acc.written.push(entry);
169
- onProgress?.({ file: entry, written: index, total, status: 'written' });
170
- return;
171
- }
172
- const data = readFileSync(source);
173
- const payload = isJsonTarget(entry) && exists ? mergeJsonPayload(entry.path, data) : data;
174
- atomicWriteFile(entry.path, payload);
175
- appendTxLog(logPath, {
176
- ts: new Date().toISOString(),
177
- kind: 'write',
178
- path: entry.path,
179
- sha256: entry.sha256,
180
- });
181
- acc.written.push(entry);
182
- onProgress?.({ file: entry, written: index, total, status: 'written' });
183
- }
184
- catch (err) {
185
- const message = err instanceof Error ? err.message : String(err);
186
- const code = errorCode(err);
187
- acc.errors.push({ path: entry.path, code, message });
188
- onProgress?.({
189
- file: entry,
190
- written: index,
191
- total,
192
- status: 'error',
193
- error: { code, message },
194
- });
195
- }
196
- }
197
- /**
198
- * True when the target already matches the planned bytes (no write needed).
199
- *
200
- * `policy.force` flips this off \u2014 `--force-overwrite` writes regardless.
201
- */
202
- function isIdempotent(entry, plan) {
203
- if (plan.policy.force)
204
- return false;
205
- if (entry.sha256 === null)
206
- return false;
207
- if (!existsSync(entry.path))
208
- return false;
209
- return sha256File(entry.path) === entry.sha256;
210
- }
211
- /**
212
- * Read the existing JSON target, deep-merge with the planned payload,
213
- * and return the canonical 4-space-indented bytes.
214
- *
215
- * Falls back to the raw planned payload when the existing file is
216
- * corrupt or non-object — matches the Python `read_json_file` lenient
217
- * contract so a truncated upstream config does not abort the install.
218
- */
219
- function mergeJsonPayload(targetPath, planned) {
220
- const existingText = readFileSync(targetPath, 'utf8');
221
- const existing = parseJsonLenient(existingText);
222
- const overlay = parseJsonLenient(planned.toString('utf8'));
223
- const merged = mergeJsonContent(existing, overlay);
224
- return Buffer.from(merged, 'utf8');
225
- }
226
- function errorCode(err) {
227
- if (err !== null && typeof err === 'object' && 'code' in err) {
228
- const c = err.code;
229
- if (typeof c === 'string') {
230
- if (c === 'ENOSPC')
231
- return 'E_DISK_FULL';
232
- if (c === 'EACCES' || c === 'EPERM')
233
- return 'E_PERM';
234
- }
235
- }
236
- return 'E_WRITE';
237
- }
238
- //# sourceMappingURL=apply.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"apply.js","sourceRoot":"","sources":["../../src/install/apply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEnD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACH,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,GACtB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAyDzC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,SAAS,CAAC,MAAmB;IACzC,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;IAC1E,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAE7B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;QACtB,KAAK,IAAI,CAAC,CAAC;QACX,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;IACxG,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,EAAE,CAAC;AAC3C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAA4B;IACjE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;IAClF,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAE7B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;QACtB,IAAI,MAAM,EAAE,OAAO,KAAK,IAAI,EAAE,CAAC;YAC3B,WAAW,CAAC,OAAO,EAAE;gBACjB,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBAC5B,IAAI,EAAE,OAAO;gBACb,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,MAAM,EAAE,IAAI;gBACZ,IAAI,EAAE,mBAAmB;aAC5B,CAAC,CAAC;YACH,MAAM;QACV,CAAC;QACD,KAAK,IAAI,CAAC,CAAC;QACX,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;QACpG,+DAA+D;QAC/D,qDAAqD;QACrD,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IAChE,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,EAAE,CAAC;AAC3C,CAAC;AAUD,SAAS,cAAc;IACnB,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;AACnE,CAAC;AAED,SAAS,cAAc,CAAC,IAAiB;IACrC,MAAM,GAAG,GAAgB,EAAE,CAAC;IAC5B,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QACpD,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,SAAS,YAAY,CACjB,KAAgB,EAChB,IAAiB,EACjB,cAA2C,EAC3C,OAAe,EACf,GAAqB,EACrB,KAAa,EACb,KAAa,EACb,UAA2D,EAC3D,WAAgE;IAEhE,IAAI,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC1B,2DAA2D;YAC3D,0DAA0D;YAC1D,kCAAkC;YAClC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACxB,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;YACxE,OAAO;QACX,CAAC;QAED,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACvB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,IAAI,EAAE,uBAAuB;gBAC7B,OAAO,EAAE,mCAAmC;aAC/C,CAAC,CAAC;YACH,UAAU,EAAE,CAAC;gBACT,IAAI,EAAE,KAAK;gBACX,OAAO,EAAE,KAAK;gBACd,KAAK;gBACL,MAAM,EAAE,OAAO;gBACf,KAAK,EAAE,EAAE,IAAI,EAAE,uBAAuB,EAAE,OAAO,EAAE,mBAAmB,EAAE;aACzE,CAAC,CAAC;YACH,OAAO;QACX,CAAC;QAED,MAAM,UAAU,GAAG,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,OAAO,GAAG,mBAAmB,CAAC;YAChC,UAAU,EAAE,KAAK,CAAC,IAAI;YACtB,UAAU;YACV,MAAM;YACN,MAAM,EAAE,IAAI,CAAC,MAAM;SACtB,CAAC,CAAC;QAEH,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;YACrB,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACxB,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;YACxE,OAAO;QACX,CAAC;QACD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YACxB,4DAA4D;YAC5D,2DAA2D;YAC3D,2DAA2D;YAC3D,wDAAwD;YACxD,6DAA6D;YAC7D,sDAAsD;YACtD,MAAM,UAAU,GAAG,WAAW,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAChD,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;gBAC3B,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAC1B,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;gBACzE,OAAO;YACX,CAAC;YACD,IAAI,UAAU,KAAK,MAAM,EAAE,CAAC;gBACxB,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACxB,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;gBACxE,OAAO;YACX,CAAC;YACD,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;YAClC,MAAM,OAAO,GAAG,UAAU,KAAK,OAAO,IAAI,YAAY,CAAC,KAAK,CAAC,IAAI,MAAM;gBACnE,CAAC,CAAC,gBAAgB,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC;gBACpC,CAAC,CAAC,IAAI,CAAC;YACX,eAAe,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACrC,WAAW,CAAC,OAAO,EAAE;gBACjB,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBAC5B,IAAI,EAAE,OAAO;gBACb,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,MAAM,EAAE,KAAK,CAAC,MAAM;aACvB,CAAC,CAAC;YACH,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACxB,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;YACxE,OAAO;QACX,CAAC;QAED,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;QAClC,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC1F,eAAe,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACrC,WAAW,CAAC,OAAO,EAAE;YACjB,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC5B,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,MAAM,EAAE,KAAK,CAAC,MAAM;SACvB,CAAC,CAAC;QACH,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IAC5E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACX,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC5B,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QACrD,UAAU,EAAE,CAAC;YACT,IAAI,EAAE,KAAK;YACX,OAAO,EAAE,KAAK;YACd,KAAK;YACL,MAAM,EAAE,OAAO;YACf,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;SAC3B,CAAC,CAAC;IACP,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,SAAS,YAAY,CAAC,KAAgB,EAAE,IAAiB;IACrD,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACpC,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACxC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1C,OAAO,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,MAAM,CAAC;AACnD,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,gBAAgB,CAAC,UAAkB,EAAE,OAAe;IACzD,MAAM,YAAY,GAAG,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,YAAY,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACnD,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,SAAS,CAAC,GAAY;IAC3B,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;QAC3D,MAAM,CAAC,GAAI,GAAyB,CAAC,IAAI,CAAC;QAC1C,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;YACxB,IAAI,CAAC,KAAK,QAAQ;gBAAE,OAAO,aAAa,CAAC;YACzC,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,OAAO;gBAAE,OAAO,QAAQ,CAAC;QACzD,CAAC;IACL,CAAC;IACD,OAAO,SAAS,CAAC;AACrB,CAAC"}