@event4u/agent-config 2.15.0 → 2.16.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.
@@ -3223,6 +3223,19 @@ def parse_options(argv: list[str]) -> argparse.Namespace:
3223
3223
  "explicit guarantee for air-gapped / CI runs."
3224
3224
  ),
3225
3225
  )
3226
+ parser.add_argument(
3227
+ "--minimal",
3228
+ "--settings-only",
3229
+ dest="minimal",
3230
+ action="store_true",
3231
+ help=(
3232
+ "bootstrap only the project-local override layer: writes "
3233
+ "agents/.gitkeep and a .agent-settings.yml stub. No tool "
3234
+ "payload, no AGENTS.md, no symlinks. Refuses to install "
3235
+ "inside an existing agent-config project (nested-install "
3236
+ "guard). See docs/installation.md → Minimal init."
3237
+ ),
3238
+ )
3226
3239
  opts = parser.parse_args(argv)
3227
3240
  opts.tools = _merge_tools_aliases(opts.tools, opts.ai)
3228
3241
  if opts.scope == "global" and opts.custom_path:
@@ -3282,6 +3295,148 @@ def _is_tool_enabled(tools: set[str], tool_id: str) -> bool:
3282
3295
  return tool_id in tools
3283
3296
 
3284
3297
 
3298
+ # --- Minimal init (Step 7 Phase 2) ---
3299
+
3300
+
3301
+ def _minimal_templates_root() -> Path:
3302
+ """Resolve the bundled ``templates/minimal/`` directory.
3303
+
3304
+ Walks up from this file looking for ``templates/minimal/``; this
3305
+ works both in development mode (running the source tree) and from
3306
+ an ``npm install -g`` install (the script lives under the package
3307
+ root regardless).
3308
+ """
3309
+ for ancestor in (Path(__file__).resolve(), *Path(__file__).resolve().parents):
3310
+ candidate = ancestor / "templates" / "minimal"
3311
+ if candidate.is_dir():
3312
+ return candidate
3313
+ fail("Could not locate templates/minimal/ — package install is corrupt.")
3314
+ return Path() # unreachable
3315
+
3316
+
3317
+ #: Relative path of the install-mode marker file written by both the
3318
+ #: minimal short-circuit and the full install path (Step 8 A5). Read by
3319
+ #: ``doctor --context`` (and any future tooling) instead of inferring
3320
+ #: install state from filesystem heuristics like ``AGENTS.md`` presence.
3321
+ INSTALL_MODE_MARKER_REL = "agents/.agent-state/install-mode.txt"
3322
+
3323
+
3324
+ def _write_install_mode_marker(project_root: Path, mode: str) -> None:
3325
+ """Write ``agents/.agent-state/install-mode.txt`` = ``mode\\n``.
3326
+
3327
+ Idempotent: overwrites unconditionally so re-installs flip the
3328
+ state correctly (e.g. minimal → full upgrade). Failure to write
3329
+ is non-fatal — install proceeds and ``doctor --context`` falls
3330
+ back to the filesystem heuristic. ``mode`` must be ``minimal``
3331
+ or ``full``.
3332
+ """
3333
+ if mode not in ("minimal", "full"):
3334
+ return
3335
+ marker = project_root / INSTALL_MODE_MARKER_REL
3336
+ try:
3337
+ marker.parent.mkdir(parents=True, exist_ok=True)
3338
+ marker.write_text(f"{mode}\n", encoding="utf-8")
3339
+ except OSError:
3340
+ # Marker is advisory; install must not abort because the
3341
+ # state dir is unwritable (e.g. read-only mount in CI).
3342
+ pass
3343
+
3344
+
3345
+ def install_minimal(target_root: Path, force: bool) -> int:
3346
+ """Bootstrap the project-local override layer only (D2-compliant).
3347
+
3348
+ Writes:
3349
+
3350
+ * ``agents/.gitkeep`` so the folder is committable.
3351
+ * ``.agent-settings.yml`` stub (cost_profile=balanced, version pin
3352
+ commented out per D4).
3353
+
3354
+ Refuses (exit 1) when ``target_root`` is **inside** an existing
3355
+ agent-config project (Phase-1 anchor walk above the target). The
3356
+ in-target case is allowed and treated as idempotent — re-running
3357
+ ``--minimal`` in a folder that already has ``.agent-settings.yml``
3358
+ does nothing unless ``--force`` is passed.
3359
+
3360
+ Does **not** touch ``.gitignore`` (D2 — user owns the ignore file).
3361
+ The ``./agent-config`` wrapper is installed by ``scripts/install.sh``
3362
+ in its own minimal short-circuit.
3363
+ """
3364
+ try:
3365
+ from scripts._lib.agent_settings import find_project_root_with_anchor # noqa: PLC0415
3366
+ except ImportError: # pragma: no cover — alt sys.path layout
3367
+ from _lib.agent_settings import find_project_root_with_anchor # type: ignore[no-redef] # noqa: PLC0415
3368
+
3369
+ target_root = target_root.resolve()
3370
+ target_root.mkdir(parents=True, exist_ok=True)
3371
+
3372
+ # Nested-install guard: walk up from the *parent* of target_root.
3373
+ # An anchor at target_root itself is allowed (re-running --minimal
3374
+ # in the same project is idempotent); only a root *above* target
3375
+ # blocks the install.
3376
+ parent = target_root.parent
3377
+ if parent != target_root: # not filesystem root
3378
+ existing = find_project_root_with_anchor(parent)
3379
+ if existing is not None and existing[0] != target_root:
3380
+ root, anchor = existing
3381
+ fail(
3382
+ "Refusing to nest an agent-config layer inside an existing "
3383
+ f"project (anchor: {anchor}). Existing root: {root}. "
3384
+ "Remove the parent layer first or run `--minimal` outside it."
3385
+ )
3386
+ return 1 # unreachable; fail() exits
3387
+
3388
+ templates = _minimal_templates_root()
3389
+ settings_src = templates / SETTINGS_FILE
3390
+ gitkeep_src = templates / "agents-gitkeep"
3391
+
3392
+ if not settings_src.is_file() or not gitkeep_src.is_file():
3393
+ fail(f"Bundled minimal templates missing under {templates}")
3394
+
3395
+ info(f"Minimal init → {target_root}")
3396
+
3397
+ # 1. agents/.gitkeep
3398
+ agents_dir = target_root / "agents"
3399
+ agents_dir.mkdir(exist_ok=True)
3400
+ gitkeep_dst = agents_dir / ".gitkeep"
3401
+ if gitkeep_dst.exists() and not force:
3402
+ skip(f"agents/.gitkeep already exists (use --force to overwrite)")
3403
+ else:
3404
+ gitkeep_dst.write_text(gitkeep_src.read_text(encoding="utf-8"), encoding="utf-8")
3405
+ success("Wrote agents/.gitkeep")
3406
+
3407
+ # 2. .agent-settings.yml stub
3408
+ settings_dst = target_root / SETTINGS_FILE
3409
+ if settings_dst.exists() and not force:
3410
+ skip(f"{SETTINGS_FILE} already exists (use --force to overwrite)")
3411
+ else:
3412
+ settings_dst.write_text(settings_src.read_text(encoding="utf-8"), encoding="utf-8")
3413
+ success(f"Wrote {SETTINGS_FILE}")
3414
+
3415
+ # 3. install-mode marker (Step 8 A5) — authoritative state for
3416
+ # doctor --context and future install-aware tooling. Written even
3417
+ # on idempotent re-runs so the marker is repaired if removed.
3418
+ _write_install_mode_marker(target_root, "minimal")
3419
+
3420
+ # Stderr upgrade hint (Step 8 A5) — minimal installs are intentionally
3421
+ # stripped; surface the upgrade path on stderr so it appears in
3422
+ # human terminals without polluting stdout-parsed output. Suppressed
3423
+ # under --quiet to honor scripted-install contracts.
3424
+ if not QUIET:
3425
+ print(
3426
+ "ℹ️ Minimal install — run `agent-config install --force` "
3427
+ "to add AGENTS.md, bridges, and tool integrations.",
3428
+ file=sys.stderr,
3429
+ )
3430
+
3431
+ if not QUIET:
3432
+ print()
3433
+ info("Next steps:")
3434
+ info(" • Ensure `agent-config` is on $PATH: npm install -g @event4u/agent-config")
3435
+ info(" • Add `.agent-settings.yml` and `agents/` to git (or to .gitignore — your call).")
3436
+ info(" • Run `agent-config doctor` to verify the layer is picked up.")
3437
+ return 0
3438
+
3439
+
3285
3440
  # --- Main ---
3286
3441
 
3287
3442
  def main(argv: list[str]) -> int:
@@ -3302,6 +3457,17 @@ def main(argv: list[str]) -> int:
3302
3457
  if opts.profile not in SUPPORTED_PROFILES:
3303
3458
  fail(f"Unsupported profile: {opts.profile}. Supported: {', '.join(SUPPORTED_PROFILES)}")
3304
3459
 
3460
+ # Minimal-init short-circuit (Step 7 Phase 2): bypass scope
3461
+ # detection, conflict policy, and the full bridge install. Writes
3462
+ # only the project-local override layer (agents/.gitkeep +
3463
+ # .agent-settings.yml stub). The bash wrapper handles the
3464
+ # `./agent-config` script; everything else is intentionally absent.
3465
+ if opts.minimal:
3466
+ target_root = Path(
3467
+ opts.custom_path or opts.project or os.environ.get("PROJECT_ROOT") or os.getcwd()
3468
+ ).resolve()
3469
+ return install_minimal(target_root, opts.force)
3470
+
3305
3471
  # Multi-signal scope detection (Phase 1.3) + scope resolution
3306
3472
  # (Phase 1.4). Order of precedence (highest first):
3307
3473
  # 1. --scope=<x> — explicit user override (CI-friendly; auto = honor detection)
@@ -3376,6 +3542,11 @@ def _main_project_install(
3376
3542
 
3377
3543
  ensure_agent_settings(project_root, package_root, opts.profile, opts.force)
3378
3544
 
3545
+ # Install-mode marker (Step 8 A5) — full path flips any prior
3546
+ # minimal marker to "full" so doctor --context reflects the
3547
+ # upgraded state. Idempotent on re-runs of the same scope.
3548
+ _write_install_mode_marker(project_root, "full")
3549
+
3379
3550
  tools = parsed_tools
3380
3551
 
3381
3552
  # Per-tool merged_keys collected from JSON bridge merges (P1.5).
@@ -37,6 +37,10 @@ DRY_RUN=false
37
37
  VERBOSE=false
38
38
  QUIET=false
39
39
  SKIP_GITIGNORE=false
40
+ # When true, skip payload sync entirely and only install the project-local
41
+ # `./agent-config` wrapper (Step 7 Phase 2). The bridge stage (install.py)
42
+ # handles the .agent-settings.yml stub + nested-install guard.
43
+ MINIMAL=false
40
44
  # Comma-separated tool IDs (default: all). Set by --tools or the
41
45
  # orchestrator (scripts/install). The .augment/ substrate is always
42
46
  # synced because every other tool symlinks back into it.
@@ -75,6 +79,7 @@ parse_args() {
75
79
  --skip-gitignore) SKIP_GITIGNORE=true; shift ;;
76
80
  --tools) TOOLS="$2"; shift 2 ;;
77
81
  --tools=*) TOOLS="${1#*=}"; shift ;;
82
+ --minimal|--settings-only) MINIMAL=true; shift ;;
78
83
  --help|-h) show_help; exit 0 ;;
79
84
  *) log_error "Unknown argument: $1"; show_help; exit 1 ;;
80
85
  esac
@@ -726,6 +731,21 @@ install_cli_wrapper() {
726
731
  main() {
727
732
  parse_args "$@"
728
733
 
734
+ # Minimal-init short-circuit (Step 7 Phase 2): skip every payload-sync
735
+ # stage and only install the project-local `./agent-config` wrapper.
736
+ # The bridge stage (install.py) handles the .agent-settings.yml stub
737
+ # + nested-install guard. No .augment/, no AGENTS.md, no symlinks.
738
+ if $MINIMAL; then
739
+ if ! $QUIET; then
740
+ echo "🔧 Minimal init — installing ./agent-config wrapper only"
741
+ echo " Target: $TARGET_DIR"
742
+ $DRY_RUN && echo " Mode: DRY RUN"
743
+ fi
744
+ install_cli_wrapper "$TARGET_DIR"
745
+ $QUIET || echo "✅ Wrapper installed (payload sync skipped)."
746
+ return 0
747
+ fi
748
+
729
749
  # First-run detection: gate the verbose source/target banner behind the
730
750
  # absence of .agent-settings.yml. Re-runs print a single status line.
731
751
  local is_first_run=false
@@ -21,6 +21,13 @@ set -euo pipefail
21
21
 
22
22
  SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23
23
 
24
+ # Step-7 Phase 3 — pin the project root for the master CLI so subdir
25
+ # invocations of ``./agent-config`` (or symlinks to it) do not re-walk
26
+ # the anchor tree. The wrapper lives at the project root by definition,
27
+ # so SELF_DIR *is* the canonical root. Honor a pre-set value as an
28
+ # escape hatch (test harnesses, supervisors that already pinned it).
29
+ export AGENT_CONFIG_PROJECT_ROOT="${AGENT_CONFIG_PROJECT_ROOT:-$SELF_DIR}"
30
+
24
31
  locate_master() {
25
32
  if [[ -n "${AGENT_CONFIG_MASTER:-}" && -x "$AGENT_CONFIG_MASTER" ]]; then
26
33
  printf '%s' "$AGENT_CONFIG_MASTER"
@@ -0,0 +1,23 @@
1
+ # Agent Settings — minimal stub
2
+ #
3
+ # Created by `agent-config init --minimal`. This file marks the project as
4
+ # an agent-config layer without installing any tool payload. The global
5
+ # `agent-config` binary on $PATH supplies the rules / skills / commands;
6
+ # this file is your per-project override point.
7
+ #
8
+ # Resolution order (deepest wins, see docs/contracts/layered-settings.md):
9
+ # 1. <repo-root>/.agent-settings.yml ← this file
10
+ # 2. ~/.event4u/agent-config/agent-settings.yml (user-global; whitelisted)
11
+ #
12
+ # Add keys here as you need them. Reference template:
13
+ # templates/consumer-settings/ or docs/customization.md
14
+
15
+ # --- Cost profile ---
16
+ # Master switch for which rule tiers load. Options: minimal | balanced | full.
17
+ # `balanced` is the recommended default; flip to `minimal` for kernel-only.
18
+ cost_profile: balanced
19
+
20
+ # --- Version pin (opt-in) ---
21
+ # Leave commented out to follow whatever `agent-config` is on $PATH (per D4).
22
+ # Uncomment and pin to a specific release for reproducible installs.
23
+ # agent_config_version: ""
@@ -0,0 +1,2 @@
1
+ # Placeholder so `agents/` is committable in a fresh `--minimal` install.
2
+ # Rename / remove once you add real content under agents/ (roadmaps, contexts, …).