@event4u/agent-config 1.38.0 → 1.40.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 (56) hide show
  1. package/.agent-src/commands/onboard.md +131 -50
  2. package/.agent-src/commands/orchestrate.md +123 -0
  3. package/.agent-src/commands/sync-gitignore/fix.md +135 -0
  4. package/.agent-src/commands/sync-gitignore.md +31 -5
  5. package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +30 -2
  6. package/.agent-src/skills/subagent-orchestration/SKILL.md +9 -0
  7. package/.agent-src/skills/using-git-worktrees/SKILL.md +25 -0
  8. package/.agent-src/templates/agent-settings.md +9 -0
  9. package/.agent-src/templates/agents/agent-project-settings.example.yml +9 -2
  10. package/.agent-src/templates/scripts/work_engine/_lib/__init__.py +7 -0
  11. package/.agent-src/templates/scripts/work_engine/_lib/agent_settings.py +168 -0
  12. package/.agent-src/templates/scripts/work_engine/hooks/settings.py +18 -19
  13. package/.agent-src/templates/scripts/work_engine/orchestration.py +168 -0
  14. package/.claude-plugin/marketplace.json +3 -1
  15. package/AGENTS.md +4 -4
  16. package/CHANGELOG.md +76 -0
  17. package/README.md +17 -6
  18. package/bin/install.php +13 -6
  19. package/config/agent-settings.template.yml +21 -0
  20. package/docs/DISTRIBUTION_CHECKLIST.md +169 -0
  21. package/docs/architecture.md +1 -1
  22. package/docs/catalog.md +3 -2
  23. package/docs/contracts/audit-log-v1.md +142 -0
  24. package/docs/contracts/command-clusters.md +2 -0
  25. package/docs/contracts/file-ownership-matrix.json +20 -0
  26. package/docs/contracts/orchestration-dsl-v1.md +152 -0
  27. package/docs/customization.md +45 -0
  28. package/docs/getting-started.md +1 -1
  29. package/docs/guidelines/agent-infra/layered-settings.md +54 -17
  30. package/docs/installation.md +132 -0
  31. package/docs/setup/mcp-client-config.md +152 -0
  32. package/docs/setup/mcp-cloud-endpoints.md +16 -0
  33. package/docs/setup/per-ide/aider.md +48 -0
  34. package/docs/setup/per-ide/claude-code.md +108 -0
  35. package/docs/setup/per-ide/claude-desktop.md +148 -0
  36. package/docs/setup/per-ide/cline.md +43 -0
  37. package/docs/setup/per-ide/codex.md +46 -0
  38. package/docs/setup/per-ide/copilot.md +80 -0
  39. package/docs/setup/per-ide/cursor.md +125 -0
  40. package/docs/setup/per-ide/gemini-cli.md +45 -0
  41. package/docs/setup/per-ide/windsurf.md +120 -0
  42. package/package.json +1 -1
  43. package/scripts/_lib/agent_settings.py +168 -0
  44. package/scripts/compress.py +153 -1
  45. package/scripts/extract_audit_patterns.py +202 -0
  46. package/scripts/install +156 -1
  47. package/scripts/install.py +270 -10
  48. package/scripts/install.sh +52 -7
  49. package/scripts/lint_orchestration_dsl.py +214 -0
  50. package/scripts/skill_linter.py +9 -0
  51. package/scripts/sync_gitignore.py +56 -1
  52. package/templates/claude_desktop_config.json.template +21 -0
  53. package/templates/cursor-rule.mdc.j2 +7 -0
  54. package/templates/global-install-manifest.yml +91 -0
  55. package/templates/marketing-copy.yml +64 -0
  56. package/templates/windsurf-rule.md.j2 +7 -0
@@ -240,6 +240,15 @@ prefer the cheaper one (`do-and-judge` < `do-and-judge-two-stage` <
240
240
  `do-in-steps` < `do-in-parallel` < `do-competitively` <
241
241
  `judge-with-debate` < `do-in-worktrees`).
242
242
 
243
+ **Mode 6 (`do-in-worktrees`) is gated by `worktrees.mode`** from
244
+ `.agent-settings.yml` (default: `ask`). Resolve before picking:
245
+
246
+ | `worktrees.mode` | Mode 6 |
247
+ |---|---|
248
+ | `ask` | Eligible. `using-git-worktrees` will run the per-creation permission ask. |
249
+ | `on` | Eligible. Per-creation ask suppressed. |
250
+ | `off` | **Not eligible.** Fall back to mode 3 (`do-in-steps`) — same step-by-step chain, in-place on the current branch. Unless the user **explicitly asked this turn** for a worktree chain, in which case proceed with mode 6 and acknowledge the override per [`using-git-worktrees § Pre-flight`](../using-git-worktrees/SKILL.md). |
251
+
243
252
  ### 4. Dispatch
244
253
 
245
254
  Hand off to the matching command:
@@ -44,6 +44,31 @@ work and makes it impossible to tell what you broke.
44
44
 
45
45
  ## Procedure
46
46
 
47
+ ### 0. Pre-flight — read `worktrees.mode`
48
+
49
+ Before anything else, read `worktrees.mode` from `.agent-settings.yml`
50
+ (default: `ask`). The setting is a **mechanical layer on top of**
51
+ `scope-control`'s permission gate — it narrows, never widens.
52
+
53
+ | `worktrees.mode` | Behaviour |
54
+ |---|---|
55
+ | `ask` | Status quo. Continue to step 1; `scope-control` permission gate applies for every worktree creation. |
56
+ | `on` | Standing permission. Skip the per-creation permission ask; continue to step 1. Iron-Law gates (ignore-check, clean baseline) still apply. |
57
+ | `off` | No autonomous worktree creation. **Refuse** unless the user explicitly asked **this turn** for a worktree ("do this in a worktree", "use mode 6", "spawn a worktree for X"). |
58
+
59
+ **Off, no explicit request** → stop. Tell the user the setting is `off`,
60
+ suggest the in-place alternative (`subagent-orchestration` mode 3
61
+ `do-in-steps`, or just stay on the current branch). Do not re-ask on
62
+ the same task.
63
+
64
+ **Off, with explicit request this turn** → acknowledge once
65
+ ("`worktrees.mode` is `off`; running this on your explicit request
66
+ for this task") and continue to step 1. The override is for this one
67
+ task — it does not flip the setting.
68
+
69
+ The setting only suppresses **unprompted** usage. The tool stays
70
+ available when the user wants it.
71
+
47
72
  ### 1. Inspect current state
48
73
 
49
74
  Before creating anything, check existing conventions — do not assume:
@@ -230,6 +230,14 @@ subagents:
230
230
  # Set to 1 to serialize. Hard cap enforced by runtime.
231
231
  max_parallel: 3
232
232
 
233
+ # --- Git worktrees ---
234
+ worktrees:
235
+ # off | on | ask (default: ask)
236
+ # off = no autonomous worktree creation (explicit user request overrides)
237
+ # on = standing permission (skill skips the per-creation ask)
238
+ # ask = status quo — skill asks before creating
239
+ mode: ask
240
+
233
241
  # --- Role modes (see guidelines/agent-infra/role-contracts.md) ---
234
242
  roles:
235
243
  # Role the agent defaults to at the start of a session.
@@ -442,6 +450,7 @@ the canonical narrative lives in
442
450
  | `subagents.implementer_model` | model alias or empty | _(empty)_ | Model for implementer subagents. Empty = same tier as session model. See [subagent-configuration](../contexts/subagent-configuration.md). |
443
451
  | `subagents.judge_model` | model alias or empty | _(empty)_ | Model for judge subagents. Empty = one tier above implementer (opus if sonnet, sonnet if haiku). |
444
452
  | `subagents.max_parallel` | integer | `3` | Maximum parallel subagent invocations. `1` serializes. |
453
+ | `worktrees.mode` | `off`, `on`, `ask` | `ask` | Controls autonomous `git worktree` usage. `off` = skill refuses unless the user explicitly asks for a worktree that turn (then it runs); `subagent-orchestration` mode 6 falls back to mode 3. `on` = standing permission (skill skips the per-creation ask; ignore-check and clean-baseline gates still apply). `ask` = status quo — `scope-control` permission gate runs every time. |
445
454
  | `roles.default_role` | `""`, `developer`, `reviewer`, `tester`, `po`, `incident`, `planner` | _(empty)_ | Role the agent defaults to at the start of a session. See [`role-contracts`](../docs/guidelines/agent-infra/role-contracts.md). |
446
455
  | `roles.active_role` | same as `default_role` | _(empty)_ | Role currently active; set by `/mode <name>`, cleared by `/mode none`. Enables the `role-mode-adherence` rule. |
447
456
  | `personas.override` | list of persona ids | `[]` | Developer-local override of the team default lens cast. Empty = inherit `personas.default` from `.agent-project-settings.yml`. See [`layered-settings`](../docs/guidelines/agent-infra/layered-settings.md). |
@@ -6,14 +6,21 @@
6
6
  #
7
7
  # Precedence (lowest → highest):
8
8
  # 1. Package defaults (shipped by event4u/agent-config)
9
- # 2. This file (.agent-project-settings.yml)team defaults
10
- # 3. .agent-settings.yml developer overrides (gitignored)
9
+ # 2. ~/.config/agent-config/agent-settings.yml — user-global DX-comfort
10
+ # defaults (whitelist: name, ide, cost_profile, personal.bot_icon,
11
+ # personal.autonomy, caveman.speak_scope). Created on opt-in via
12
+ # /onboard; project-local files always win.
13
+ # 3. This file (.agent-project-settings.yml) — team defaults
14
+ # 4. .agent-settings.yml — developer overrides (gitignored)
11
15
  #
12
16
  # Any key marked `locked: true` in this file CANNOT be overridden by
13
17
  # .agent-settings.yml. Use sparingly — locked keys reduce developer
14
18
  # autonomy. Reserve for compliance or correctness concerns (e.g.
15
19
  # forcing a test framework, pinning a coding style).
16
20
  #
21
+ # Full precedence model + user-global whitelist contract:
22
+ # docs/guidelines/agent-infra/layered-settings.md
23
+ #
17
24
  # Copy this file to `.agent-project-settings.yml` (drop the `.example`)
18
25
  # and commit it.
19
26
 
@@ -0,0 +1,7 @@
1
+ """Internal helpers shared across work_engine submodules.
2
+
3
+ Currently houses :mod:`agent_settings` — the byte-identical mirror of
4
+ ``scripts/_lib/agent_settings.py`` from the agent-config package. The
5
+ parity test in ``tests/test_template_agent_settings_parity.py`` guards
6
+ the two files against drift.
7
+ """
@@ -0,0 +1,168 @@
1
+ """Centralized loader for ``.agent-settings.yml`` with user-global fallback.
2
+
3
+ Phase 1 of road-to-portable-dev-preferences. Single source of truth for
4
+ how scripts read agent settings — replaces ~15 ad-hoc loaders in P3.
5
+
6
+ Resolution order (project wins, user-global fills gaps for whitelisted
7
+ keys only):
8
+
9
+ 1. Project ``./.agent-settings.yml`` (full file, all keys)
10
+ 2. ``~/.config/agent-config/agent-settings.yml`` (whitelist only)
11
+ 3. Built-in defaults (currently empty)
12
+
13
+ Whitelisted keys (``MERGEABLE_KEYS``) are exact dotted paths. A
14
+ non-whitelisted key in the user-global file is silently ignored — the
15
+ ``verbose=True`` flag surfaces ignored paths via ``logging.info`` for
16
+ debugging.
17
+
18
+ Contract — pure, read-only, tolerant:
19
+
20
+ * Lazy PyYAML import; no yaml installed → defaults returned.
21
+ * Missing project file → user-global + defaults.
22
+ * Missing user-global file → project + defaults.
23
+ * Both missing → defaults.
24
+ * Malformed YAML / unreadable file → defaults, logged at WARNING.
25
+ * No file is ever created or written by this module.
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import logging
30
+ from pathlib import Path
31
+ from typing import Any
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+ DEFAULT_PROJECT_FILE = ".agent-settings.yml"
36
+ DEFAULT_USER_GLOBAL_FILE = (
37
+ Path.home() / ".config" / "agent-config" / "agent-settings.yml"
38
+ )
39
+
40
+ #: Exact dotted paths allowed to cascade from user-global into the merged
41
+ #: settings. Anything not listed here is silently ignored when present in
42
+ #: the user-global file. Adding a key requires an ADR — see
43
+ #: ``agents/roadmaps/road-to-portable-dev-preferences.md``.
44
+ MERGEABLE_KEYS: tuple[str, ...] = (
45
+ "name",
46
+ "ide",
47
+ "cost_profile",
48
+ "personal.bot_icon",
49
+ "personal.autonomy",
50
+ "caveman.speak_scope",
51
+ )
52
+
53
+ _DEFAULTS: dict[str, Any] = {}
54
+
55
+
56
+ def load_agent_settings(
57
+ project_path: Path | str | None = None,
58
+ user_global_path: Path | str | None = None,
59
+ verbose: bool = False,
60
+ ) -> dict[str, Any]:
61
+ """Return the merged settings dict.
62
+
63
+ ``project_path`` defaults to ``./.agent-settings.yml`` (CWD-relative).
64
+ ``user_global_path`` defaults to
65
+ ``~/.config/agent-config/agent-settings.yml``. Both arguments accept
66
+ ``Path`` or ``str``. Pass ``verbose=True`` to log keys present in
67
+ user-global that are not on the whitelist.
68
+ """
69
+ project = _read_yaml(
70
+ Path(project_path) if project_path else Path(DEFAULT_PROJECT_FILE),
71
+ ) or {}
72
+ user_global_raw = _read_yaml(
73
+ Path(user_global_path) if user_global_path else DEFAULT_USER_GLOBAL_FILE,
74
+ ) or {}
75
+
76
+ user_global_filtered, ignored = _filter_whitelist(
77
+ user_global_raw, MERGEABLE_KEYS,
78
+ )
79
+ if verbose and ignored:
80
+ logger.info(
81
+ "agent_settings: ignored non-whitelisted user-global keys: %s",
82
+ sorted(ignored),
83
+ )
84
+
85
+ merged: dict[str, Any] = _deep_copy_defaults(_DEFAULTS)
86
+ _deep_merge(merged, user_global_filtered)
87
+ _deep_merge(merged, project)
88
+ return merged
89
+
90
+
91
+ def _read_yaml(path: Path) -> dict[str, Any] | None:
92
+ """Best-effort YAML read; never raises. Returns ``None`` on any failure."""
93
+ if not path.is_file():
94
+ return None
95
+ try:
96
+ import yaml # type: ignore[import-untyped]
97
+ except ImportError:
98
+ return None
99
+ try:
100
+ with path.open(encoding="utf-8") as fh:
101
+ data = yaml.safe_load(fh) or {}
102
+ except (OSError, yaml.YAMLError):
103
+ logger.warning("agent_settings: unreadable or malformed YAML at %s", path)
104
+ return None
105
+ return data if isinstance(data, dict) else None
106
+
107
+
108
+ def _filter_whitelist(
109
+ raw: dict[str, Any], allowed: tuple[str, ...],
110
+ ) -> tuple[dict[str, Any], list[str]]:
111
+ """Return ``(filtered_dict, ignored_paths)`` from a user-global blob."""
112
+ filtered: dict[str, Any] = {}
113
+ for dotted in allowed:
114
+ value = _get_dotted(raw, dotted)
115
+ if value is not None:
116
+ _set_dotted(filtered, dotted, value)
117
+ ignored = [p for p in _leaf_paths(raw) if p not in allowed]
118
+ return filtered, ignored
119
+
120
+
121
+ def _get_dotted(data: dict[str, Any], dotted: str) -> Any:
122
+ cursor: Any = data
123
+ for part in dotted.split("."):
124
+ if not isinstance(cursor, dict) or part not in cursor:
125
+ return None
126
+ cursor = cursor[part]
127
+ return cursor
128
+
129
+
130
+ def _set_dotted(target: dict[str, Any], dotted: str, value: Any) -> None:
131
+ parts = dotted.split(".")
132
+ cursor = target
133
+ for part in parts[:-1]:
134
+ nxt = cursor.setdefault(part, {})
135
+ if not isinstance(nxt, dict):
136
+ nxt = {}
137
+ cursor[part] = nxt
138
+ cursor = nxt
139
+ cursor[parts[-1]] = value
140
+
141
+
142
+ def _leaf_paths(data: dict[str, Any], prefix: str = "") -> list[str]:
143
+ paths: list[str] = []
144
+ for key, value in data.items():
145
+ path = f"{prefix}.{key}" if prefix else key
146
+ if isinstance(value, dict) and value:
147
+ paths.extend(_leaf_paths(value, path))
148
+ else:
149
+ paths.append(path)
150
+ return paths
151
+
152
+
153
+ def _deep_merge(dst: dict[str, Any], src: dict[str, Any]) -> None:
154
+ """Merge ``src`` into ``dst`` in-place; nested dicts are merged recursively."""
155
+ for key, value in src.items():
156
+ if (
157
+ isinstance(value, dict)
158
+ and isinstance(dst.get(key), dict)
159
+ ):
160
+ _deep_merge(dst[key], value)
161
+ else:
162
+ dst[key] = value
163
+
164
+
165
+ def _deep_copy_defaults(src: dict[str, Any]) -> dict[str, Any]:
166
+ out: dict[str, Any] = {}
167
+ _deep_merge(out, src)
168
+ return out
@@ -13,6 +13,12 @@ settings.py``):
13
13
  * Chat-history hooks gate on **two** switches: ``hooks.chat_history.
14
14
  enabled`` AND the global ``chat_history.enabled``. Either off → no
15
15
  chat-history hook registers.
16
+
17
+ Per road-to-portable-dev-preferences P3, the YAML read goes through
18
+ :func:`work_engine._lib.agent_settings.load_agent_settings`, which
19
+ cascades the whitelisted ``cost_profile`` (and other DX-comfort keys)
20
+ from ``~/.config/agent-config/agent-settings.yml`` when the project
21
+ file omits them. Project values always win.
16
22
  """
17
23
  from __future__ import annotations
18
24
 
@@ -20,6 +26,8 @@ from dataclasses import dataclass
20
26
  from pathlib import Path
21
27
  from typing import Any
22
28
 
29
+ from work_engine._lib.agent_settings import load_agent_settings
30
+
23
31
  DEFAULT_SETTINGS_FILE = ".agent-settings.yml"
24
32
  DEFAULT_CHAT_HISTORY_SCRIPT = "scripts/chat_history.py"
25
33
 
@@ -52,36 +60,27 @@ _DEFAULT = HookSettings()
52
60
 
53
61
  def load_hook_settings(
54
62
  settings_path: Path | str | None = None,
63
+ user_global_path: Path | str | None = None,
55
64
  ) -> HookSettings:
56
65
  """Return :class:`HookSettings` hydrated from ``.agent-settings.yml``.
57
66
 
58
67
  ``settings_path`` defaults to ``./.agent-settings.yml`` relative to
59
68
  the current working directory — same convention as chat-history.
69
+ ``user_global_path`` defaults to
70
+ ``~/.config/agent-config/agent-settings.yml`` and only cascades the
71
+ whitelisted DX-comfort keys (currently ``cost_profile``) when the
72
+ project file omits them. See road-to-portable-dev-preferences P3.
60
73
  """
61
74
  path = Path(settings_path) if settings_path else Path(DEFAULT_SETTINGS_FILE)
62
- raw = _read_yaml(path)
63
- if raw is None:
75
+ raw = load_agent_settings(
76
+ project_path=path,
77
+ user_global_path=user_global_path,
78
+ )
79
+ if not raw:
64
80
  return _DEFAULT
65
81
  return _settings_from_raw(raw)
66
82
 
67
83
 
68
- def _read_yaml(path: Path) -> dict[str, Any] | None:
69
- if not path.is_file():
70
- return None
71
- try:
72
- import yaml # type: ignore[import-untyped]
73
- except ImportError:
74
- return None
75
- try:
76
- with path.open(encoding="utf-8") as fh:
77
- data = yaml.safe_load(fh) or {}
78
- except (OSError, yaml.YAMLError):
79
- return None
80
- if not isinstance(data, dict):
81
- return None
82
- return data
83
-
84
-
85
84
  def _settings_from_raw(data: dict[str, Any]) -> HookSettings:
86
85
  hooks = data.get("hooks")
87
86
  if not isinstance(hooks, dict):
@@ -0,0 +1,168 @@
1
+ """State machine stub for the ``/orchestrate`` command.
2
+
3
+ Reads a pipeline file conforming to
4
+ ``docs/contracts/orchestration-dsl-v1.md`` and produces an ordered
5
+ sequence of step descriptors the agent dispatches one at a time.
6
+ The runtime itself is **not** in Python — each step is executed by the
7
+ agent via skill / command / persona / subagent dispatch. This module
8
+ holds the deterministic bookkeeping:
9
+
10
+ - load + interpolate
11
+ - step iteration with success / failure / when-guard tracking
12
+ - output-map resolution at the end
13
+
14
+ Design constraints (R1 carve-outs from
15
+ ``road-to-distribution-and-adoption.md``):
16
+
17
+ - No external dependencies. YAML loading reuses the dispatcher's
18
+ loader so the runtime sees what the linter sees.
19
+ - No side effects. The state machine never edits files, runs commands,
20
+ or emits hooks of its own. Audit emission is the caller's job.
21
+ - Forward-ref free. ``steps[].with`` references can only reach
22
+ earlier steps; this is enforced both by the linter and at runtime.
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import re
27
+ from dataclasses import dataclass, field
28
+ from pathlib import Path
29
+ from typing import Any, Iterator
30
+
31
+ _INTERP_RE = re.compile(
32
+ r"\$\{\{\s*(inputs|steps)\.([a-z0-9_-]+)(?:\.output)?\s*\}\}"
33
+ )
34
+
35
+
36
+ @dataclass
37
+ class StepResult:
38
+ """One step's record after dispatch."""
39
+ step_id: str
40
+ kind: str
41
+ ref: str
42
+ success: bool = False
43
+ output: str = ""
44
+ error: str | None = None
45
+
46
+
47
+ @dataclass
48
+ class PipelineState:
49
+ """Bookkeeping for a single ``/orchestrate`` run."""
50
+ name: str
51
+ inputs: dict[str, str]
52
+ results: dict[str, StepResult] = field(default_factory=dict)
53
+ halted: bool = False
54
+ halt_reason: str | None = None
55
+
56
+
57
+ def _load_pipeline(path: Path) -> dict[str, Any]:
58
+ """Reuse the linter's loader so the runtime accepts the same shape.
59
+
60
+ Walks parents to find a directory containing ``scripts/hooks/``
61
+ so the loader is reachable both when this module runs from the
62
+ consumer projection (``.agent-src/templates/scripts/work_engine/``)
63
+ and from the source-of-truth tree
64
+ (``.agent-src.uncompressed/templates/scripts/work_engine/``).
65
+ """
66
+ import sys
67
+ here = Path(__file__).resolve()
68
+ for parent in here.parents:
69
+ candidate = parent / "scripts" / "hooks" / "dispatch_hook.py"
70
+ if candidate.is_file():
71
+ sys.path.insert(0, str(parent / "scripts"))
72
+ break
73
+ from hooks.dispatch_hook import _load_yaml # noqa: E402
74
+ doc = _load_yaml(path)
75
+ if not isinstance(doc, dict):
76
+ raise ValueError(f"{path}: top-level must be a mapping")
77
+ return doc
78
+
79
+
80
+ def _interpolate(value: Any, state: PipelineState) -> Any:
81
+ """Substitute ``${{ inputs.X }}`` / ``${{ steps.Y.output }}`` in a
82
+ nested value. Unknown references raise — the linter should have
83
+ caught them, but the runtime double-checks."""
84
+ if isinstance(value, str):
85
+ def replace(match: re.Match[str]) -> str:
86
+ ns, ident = match.group(1), match.group(2)
87
+ if ns == "inputs":
88
+ if ident not in state.inputs:
89
+ raise KeyError(f"unknown input '{ident}'")
90
+ return state.inputs[ident]
91
+ if ident not in state.results:
92
+ raise KeyError(f"unknown step '{ident}'")
93
+ return state.results[ident].output
94
+ return _INTERP_RE.sub(replace, value)
95
+ if isinstance(value, dict):
96
+ return {k: _interpolate(v, state) for k, v in value.items()}
97
+ if isinstance(value, list):
98
+ return [_interpolate(v, state) for v in value]
99
+ return value
100
+
101
+
102
+ def _when_passes(when: str | None, state: PipelineState) -> bool:
103
+ """Evaluate the limited ``when`` mini-language. Supports
104
+ ``steps.X.success`` / ``steps.X.failure`` and equality on a
105
+ single ``${{ steps.X.output }}`` template against a literal."""
106
+ if not when:
107
+ return True
108
+ when = when.strip()
109
+ m = re.fullmatch(r"steps\.([a-z0-9_-]+)\.(success|failure)", when)
110
+ if m:
111
+ sid, kind = m.group(1), m.group(2)
112
+ if sid not in state.results:
113
+ return False
114
+ return state.results[sid].success if kind == "success" else not state.results[sid].success
115
+ m = re.fullmatch(r'\$\{\{\s*steps\.([a-z0-9_-]+)\.output\s*\}\}\s*==\s*"([^"]*)"', when)
116
+ if m:
117
+ sid, literal = m.group(1), m.group(2)
118
+ return state.results.get(sid, StepResult(sid, "", "")).output == literal
119
+ raise ValueError(f"unsupported when expression: {when!r}")
120
+
121
+
122
+ def iter_steps(path: Path, inputs: dict[str, str]) -> Iterator[dict[str, Any]]:
123
+ """Yield interpolated step descriptors in order.
124
+
125
+ Caller dispatches each descriptor via skill / command / persona /
126
+ subagent and feeds the result back via :func:`record_result`.
127
+ """
128
+ doc = _load_pipeline(path)
129
+ merged_inputs = {
130
+ inp["id"]: inputs.get(inp["id"], inp.get("default", ""))
131
+ for inp in (doc.get("inputs") or [])
132
+ if isinstance(inp, dict) and isinstance(inp.get("id"), str)
133
+ }
134
+ state = PipelineState(name=doc.get("name", ""), inputs=merged_inputs)
135
+ for step in doc.get("steps") or []:
136
+ if state.halted:
137
+ break
138
+ if not _when_passes(step.get("when"), state):
139
+ continue
140
+ yield {
141
+ "id": step["id"],
142
+ "kind": step["kind"],
143
+ "ref": step["ref"],
144
+ "with": _interpolate(step.get("with") or {}, state),
145
+ "_state": state,
146
+ }
147
+
148
+
149
+ def record_result(descriptor: dict[str, Any], *, success: bool, output: str = "", error: str | None = None) -> None:
150
+ """Caller hands the descriptor + outcome back so subsequent steps
151
+ can see ``${{ steps.<id>.output }}``."""
152
+ state: PipelineState = descriptor["_state"]
153
+ state.results[descriptor["id"]] = StepResult(
154
+ step_id=descriptor["id"], kind=descriptor["kind"], ref=descriptor["ref"],
155
+ success=success, output=output, error=error,
156
+ )
157
+ if not success:
158
+ state.halted = True
159
+ state.halt_reason = f"step {descriptor['id']} failed"
160
+
161
+
162
+ def resolve_outputs(path: Path, state: PipelineState) -> dict[str, str]:
163
+ """Resolve the pipeline's ``outputs:`` map against the captured
164
+ step outputs. Returns an empty map if the pipeline declares no
165
+ outputs."""
166
+ doc = _load_pipeline(path)
167
+ raw = doc.get("outputs") or {}
168
+ return {k: _interpolate(v, state) for k, v in raw.items()}
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Shared agent configuration \u2014 skills for AI coding tools (Claude Code, Augment, Cursor, Cline, Windsurf, Gemini CLI).",
9
- "version": "1.38.0"
9
+ "version": "1.40.0"
10
10
  },
11
11
  "plugins": [
12
12
  {
@@ -184,6 +184,7 @@
184
184
  "./.claude/skills/optimize-prompt",
185
185
  "./.claude/skills/optimize-rtk",
186
186
  "./.claude/skills/optimize-skills",
187
+ "./.claude/skills/orchestrate",
187
188
  "./.claude/skills/override",
188
189
  "./.claude/skills/override-create",
189
190
  "./.claude/skills/override-manage",
@@ -262,6 +263,7 @@
262
263
  "./.claude/skills/subagent-orchestration",
263
264
  "./.claude/skills/sync-agent-settings",
264
265
  "./.claude/skills/sync-gitignore",
266
+ "./.claude/skills/sync-gitignore-fix",
265
267
  "./.claude/skills/systematic-debugging",
266
268
  "./.claude/skills/tailwind-engineer",
267
269
  "./.claude/skills/tech-debt-tracker",
package/AGENTS.md CHANGED
@@ -16,12 +16,12 @@ task ci # full pipeline — green before PR
16
16
 
17
17
  ## Pointers
18
18
 
19
- - **Package self-orientation** — identity, four-wing cognition map, repo layout, tech stack, key-rules table, telemetry, command-suggester: [`docs/contracts/package-self-orientation.md`](docs/contracts/package-self-orientation.md).
20
- - **Kernel + Router** — 9 always-loaded Iron-Law rules, tier-1 / tier-2 routing, cost profiles, per-rule char caps enforced by `task lint-rule-budget`: [`kernel-membership`](docs/contracts/kernel-membership.md) + [`rule-router`](docs/contracts/rule-router.md).
19
+ - **Package self-orientation** (beta) — identity, four-wing cognition map, repo layout, tech stack, key-rules table, telemetry, command-suggester: [`docs/contracts/package-self-orientation.md`](docs/contracts/package-self-orientation.md).
20
+ - **Kernel + Router** (beta) — 9 always-loaded Iron-Law rules, tier-1 / tier-2 routing, cost profiles, per-rule char caps enforced by `task lint-rule-budget`: [`kernel-membership`](docs/contracts/kernel-membership.md) + [`rule-router`](docs/contracts/rule-router.md).
21
21
  - **Multi-tool projection** — Augment, Claude Code, Cursor, Cline, Windsurf, Gemini CLI, Claude.ai bundle pipeline that ships from `.agent-src/` to consumer surfaces: [`docs/architecture.md`](docs/architecture.md#cloud-bundle-pipeline).
22
22
  - **Editing this repo** — Iron-Law rules (portability, source-of-truth, skill-quality) and the Thin-Root contract (caps · pointer-ratio · triage block) governing AGENTS.md: [`augment-portability`](.agent-src/rules/augment-portability.md), [`augment-source-of-truth`](.agent-src/rules/augment-source-of-truth.md), [`skill-quality`](.agent-src/rules/skill-quality.md), [`agents-md-thin-root`](.agent-src/skills/agents-md-thin-root/SKILL.md).
23
- - **Consumer story + architecture deep-dive** — what the package does for installers and how it ships: [`README.md`](README.md), [`docs/architecture.md`](docs/architecture.md).
24
- - **Personas** — 11 review-lens cast (6 core · 5 specialist), `personas:` vs `/mode` axes, citation map, override pattern: [`docs/personas.md`](docs/personas.md), schema [`docs/contracts/persona-schema.md`](docs/contracts/persona-schema.md).
23
+ - **Consumer story + architecture deep-dive** — install story + ship pipeline: [`README.md`](README.md), [`docs/architecture.md`](docs/architecture.md).
24
+ - **Personas** — 11 review-lens cast (6 core · 5 specialist), `personas:` vs `/mode` axes, citation map, override pattern: [`docs/personas.md`](docs/personas.md), schema [`docs/contracts/persona-schema.md`](docs/contracts/persona-schema.md) (beta).
25
25
 
26
26
  ## Emergency triage — read this when nothing else is reachable
27
27
 
package/CHANGELOG.md CHANGED
@@ -318,6 +318,82 @@ our recommendation order, not its support status.
318
318
  users" tension without removing any path that an existing user
319
319
  might rely on.
320
320
 
321
+ ## [1.40.0](https://github.com/event4u-app/agent-config/compare/1.39.0...1.40.0) (2026-05-11)
322
+
323
+ ### Features
324
+
325
+ * **settings:** add worktrees.mode (off/on/ask) to gate autonomous worktree usage ([a07080f](https://github.com/event4u-app/agent-config/commit/a07080f28113dfa2452830b660deb4cb467256ec))
326
+ * **commands:** add /sync-gitignore:fix subcommand for legacy cleanup ([5b058b0](https://github.com/event4u-app/agent-config/commit/5b058b0a8a5b451eac89c4905076e4cc9773e299))
327
+ * **audit:** add pattern-extraction script for audit-log-v1 (roadmap Q2) ([966e472](https://github.com/event4u-app/agent-config/commit/966e47215eb80cfff6e2229b90c6e28b806c7dff))
328
+ * **learning-skill:** wire audit-log-v1 into learning-to-rule-or-skill (roadmap Q3) ([f53d129](https://github.com/event4u-app/agent-config/commit/f53d12947b41334c58952b3a6017962507abbe45))
329
+ * **orchestrate:** add /orchestrate command + state machine (roadmap G2) ([4b39691](https://github.com/event4u-app/agent-config/commit/4b396910c0c7078430aa11958f121d49ddda766c))
330
+ * **contracts:** add orchestration-dsl-v1 + linter (roadmap G1) ([5cb2801](https://github.com/event4u-app/agent-config/commit/5cb2801f14a06d2dabe9f2dc57b7ca424d7e4d4d))
331
+ * **contracts:** add audit-log-v1 schema (roadmap Q1) ([e0849b2](https://github.com/event4u-app/agent-config/commit/e0849b2305949ad5409ff9981970922ec702c68e))
332
+ * **install:** one-liner setup.sh + npm wrapper refinements ([aee688f](https://github.com/event4u-app/agent-config/commit/aee688fcbf9bb18b0253bc7ce96192cc7e5afe2e))
333
+ * **compress:** project Cursor .mdc + Windsurf .md rules + flatten commands ([486bafd](https://github.com/event4u-app/agent-config/commit/486bafd83c1f5224ee61b29b46cc827b21440623))
334
+ * **install:** multi-tool installer + global user-level install ([6113872](https://github.com/event4u-app/agent-config/commit/6113872b146321b784b01a428c5c2abcbad9e08d))
335
+
336
+ ### Bug Fixes
337
+
338
+ * **ci:** bump README command count and exclude projection dirs from skill-lint ([c5a1493](https://github.com/event4u-app/agent-config/commit/c5a1493d4ef31b2e9af16e0945330cccaa02576c))
339
+ * **clusters:** register /orchestrate as new cluster head ([fb73e58](https://github.com/event4u-app/agent-config/commit/fb73e58c7eb72d53e6ee111a18fe8f5a1f1e053b))
340
+ * **roadmap:** annotate council reference with allowed-marker ([e2997d0](https://github.com/event4u-app/agent-config/commit/e2997d00b5e7dc2625367f87dab3bc611b9cf8e5))
341
+ * **readme:** unlink experimental mcp-cloud-scope contract ([c7ac883](https://github.com/event4u-app/agent-config/commit/c7ac883be116e6f92f2125d5334f38f17b97da27))
342
+
343
+ ### Documentation
344
+
345
+ * bump command count to 105 for /orchestrate ([df49c6c](https://github.com/event4u-app/agent-config/commit/df49c6c268e08df7567128c97cc267fed9e38694))
346
+
347
+ ### Refactoring
348
+
349
+ * **roadmap:** apply council shape-fix to distribution roadmap ([4c7f770](https://github.com/event4u-app/agent-config/commit/4c7f770d66b07107164c2eafcc886ddf6bf29aa6))
350
+ * **distribution:** extract marketplace listings to docs/DISTRIBUTION_CHECKLIST.md ([5fc6075](https://github.com/event4u-app/agent-config/commit/5fc607567a96bc5e3d9a38d276ce85f88dae7015))
351
+ * **roadmap:** move S33 (screencasts) to distribution roadmap H5 ([95d27f6](https://github.com/event4u-app/agent-config/commit/95d27f65b577824d9c330478af5c7c7acddfe9f3))
352
+
353
+ ### Chores
354
+
355
+ * **ownership:** regenerate ownership matrix for new files ([f728343](https://github.com/event4u-app/agent-config/commit/f728343cd4bc9a7340bb19a218d1ccdc6b5bfb9e))
356
+ * **index:** tag orchestrate command with cluster in catalogs ([c4ed9ee](https://github.com/event4u-app/agent-config/commit/c4ed9ee9eaca972bc35549330131c98dd6b05966))
357
+ * **index:** regenerate catalog for /orchestrate command ([c6fe13a](https://github.com/event4u-app/agent-config/commit/c6fe13ae9d83e142154226235bf65d1d2f72cacd))
358
+ * **roadmaps:** regen dashboard after distribution-roadmap shape-fix ([e0ad5b3](https://github.com/event4u-app/agent-config/commit/e0ad5b3554d34a4eaaf747b0955cc16e32f5c8e2))
359
+ * **roadmaps:** archive road-to-simplicity-and-everywhere + regen dashboard ([d2d5c31](https://github.com/event4u-app/agent-config/commit/d2d5c310db1ec8fd3cd44111400b098c4d99c856))
360
+
361
+ Tests: 3141 (+442 since 1.39.0)
362
+
363
+ ## [1.39.0](https://github.com/event4u-app/agent-config/compare/1.38.0...1.39.0) (2026-05-11)
364
+
365
+ ### Features
366
+
367
+ * **onboard,docs:** wire user-global DX defaults into onboarding + docs ([761a969](https://github.com/event4u-app/agent-config/commit/761a96979c42b880d83f72ccd2ac2d752ae47f0b))
368
+ * **settings:** add centralized agent-settings loader ([d5699be](https://github.com/event4u-app/agent-config/commit/d5699bee9ebb47dd2f1d78716e1ff84a7139b5da))
369
+
370
+ ### Bug Fixes
371
+
372
+ * **ci:** grant contents:write to deploy-mcp-worker for release comment ([fb5c895](https://github.com/event4u-app/agent-config/commit/fb5c89537daef55e5dde12bfe85cc703bfa181ed))
373
+
374
+ ### Documentation
375
+
376
+ * **roadmap:** add road-to-simplicity-and-everywhere (highest prio) ([417a8fa](https://github.com/event4u-app/agent-config/commit/417a8fa9b7059dac783d3bc9423c64a994a9421a))
377
+ * **roadmap:** add road-to-mcp-full-coverage (Discovery-First) ([fabf897](https://github.com/event4u-app/agent-config/commit/fabf89761322a437b39178366e45f68efbdd7924))
378
+ * **mcp-cloud:** clarify Lite-vs-Full scope at endpoint surface ([97c4684](https://github.com/event4u-app/agent-config/commit/97c4684d07be4fe49c248a1ee0a60cd36b82e071))
379
+ * **stability:** align public surface with stability markers ([d9afe4a](https://github.com/event4u-app/agent-config/commit/d9afe4ad67aad80e2bb12b1c707189f3ee8f47c2))
380
+ * **mcp:** clarify .agent-settings.yml vs. MCP client config ([27a77b2](https://github.com/event4u-app/agent-config/commit/27a77b2459cad1faff12cd8a237e59091ec687de))
381
+ * **mcp:** add per-client setup guide for hosted Remote MCP ([179da38](https://github.com/event4u-app/agent-config/commit/179da38631ea9693a94f259fb1edd2746b89425d))
382
+
383
+ ### Refactoring
384
+
385
+ * **work-engine:** centralize agent-settings loading in shared _lib ([3c548bb](https://github.com/event4u-app/agent-config/commit/3c548bb72d3c2022362050a4c88ebe412bf7a897))
386
+
387
+ ### Chores
388
+
389
+ * **compress:** regenerate commands/onboard.md after onboard,docs source edit ([e676915](https://github.com/event4u-app/agent-config/commit/e67691564405f05fba033f29b0df696e25788dd0))
390
+
391
+ ### Other
392
+
393
+ * add portable-dev-preferences (3 phases, refined via AI council) ([7468a4c](https://github.com/event4u-app/agent-config/commit/7468a4cd2d352d6bfcd7797a8dd8b6ed8ec8f27a))
394
+
395
+ Tests: 2699 (+20 since 1.38.0)
396
+
321
397
  ## [1.38.0](https://github.com/event4u-app/agent-config/compare/1.37.0...1.38.0) (2026-05-11)
322
398
 
323
399
  ### Features