@event4u/agent-config 1.39.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 (45) hide show
  1. package/.agent-src/commands/orchestrate.md +123 -0
  2. package/.agent-src/commands/sync-gitignore/fix.md +135 -0
  3. package/.agent-src/commands/sync-gitignore.md +31 -5
  4. package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +30 -2
  5. package/.agent-src/skills/subagent-orchestration/SKILL.md +9 -0
  6. package/.agent-src/skills/using-git-worktrees/SKILL.md +25 -0
  7. package/.agent-src/templates/agent-settings.md +9 -0
  8. package/.agent-src/templates/scripts/work_engine/orchestration.py +168 -0
  9. package/.claude-plugin/marketplace.json +3 -1
  10. package/CHANGELOG.md +42 -0
  11. package/README.md +5 -5
  12. package/bin/install.php +13 -6
  13. package/config/agent-settings.template.yml +21 -0
  14. package/docs/DISTRIBUTION_CHECKLIST.md +169 -0
  15. package/docs/architecture.md +1 -1
  16. package/docs/catalog.md +3 -2
  17. package/docs/contracts/audit-log-v1.md +142 -0
  18. package/docs/contracts/command-clusters.md +2 -0
  19. package/docs/contracts/file-ownership-matrix.json +20 -0
  20. package/docs/contracts/orchestration-dsl-v1.md +152 -0
  21. package/docs/getting-started.md +1 -1
  22. package/docs/installation.md +132 -0
  23. package/docs/setup/per-ide/aider.md +48 -0
  24. package/docs/setup/per-ide/claude-code.md +108 -0
  25. package/docs/setup/per-ide/claude-desktop.md +148 -0
  26. package/docs/setup/per-ide/cline.md +43 -0
  27. package/docs/setup/per-ide/codex.md +46 -0
  28. package/docs/setup/per-ide/copilot.md +80 -0
  29. package/docs/setup/per-ide/cursor.md +125 -0
  30. package/docs/setup/per-ide/gemini-cli.md +45 -0
  31. package/docs/setup/per-ide/windsurf.md +120 -0
  32. package/package.json +1 -1
  33. package/scripts/compress.py +153 -1
  34. package/scripts/extract_audit_patterns.py +202 -0
  35. package/scripts/install +156 -1
  36. package/scripts/install.py +270 -10
  37. package/scripts/install.sh +52 -7
  38. package/scripts/lint_orchestration_dsl.py +214 -0
  39. package/scripts/skill_linter.py +9 -0
  40. package/scripts/sync_gitignore.py +56 -1
  41. package/templates/claude_desktop_config.json.template +21 -0
  42. package/templates/cursor-rule.mdc.j2 +7 -0
  43. package/templates/global-install-manifest.yml +91 -0
  44. package/templates/marketing-copy.yml +64 -0
  45. package/templates/windsurf-rule.md.j2 +7 -0
@@ -0,0 +1,123 @@
1
+ ---
2
+ name: orchestrate
3
+ cluster: orchestrate
4
+ skills: [subagent-orchestration]
5
+ description: Run a YAML pipeline defined under `.agent-config/orchestrations/` — chains personas / skills / commands / sub-agents per the orchestration-dsl-v1 contract
6
+ disable-model-invocation: true
7
+ suggestion:
8
+ eligible: true
9
+ trigger_description: "run a saved orchestration / pipeline / chain"
10
+ trigger_context: "user names a pipeline file or asks to replay a chain"
11
+ ---
12
+
13
+ # orchestrate
14
+
15
+ ## Instructions
16
+
17
+ Execute a YAML pipeline file from `.agent-config/orchestrations/`
18
+ against the current workspace. Pipelines are deterministic chains of
19
+ personas, skills, commands, and sub-agents pinned by the
20
+ [`orchestration-dsl-v1`](../docs/contracts/orchestration-dsl-v1.md)
21
+ contract.
22
+
23
+ This command is the **runtime** side of the contract. The schema and
24
+ the linter (`scripts/lint_orchestration_dsl.py`) live on the authoring
25
+ side; this command reads the same shape and dispatches each step.
26
+
27
+ ### 1. Resolve the pipeline file
28
+
29
+ - The user passes either a pipeline name (`pr-readiness-check`) or a
30
+ path (`.agent-config/orchestrations/pr-readiness-check.yaml`).
31
+ - Resolve to a path under `.agent-config/orchestrations/`. Refuse
32
+ paths outside that directory — pipelines live in one place.
33
+ - If the file does not exist, list the available pipelines and stop.
34
+
35
+ ### 2. Validate before run
36
+
37
+ Run the linter against the resolved file:
38
+
39
+ ```
40
+ python3 scripts/lint_orchestration_dsl.py --file <path>
41
+ ```
42
+
43
+ Exit code ≠ 0 → surface the linter output and stop. **Never** run
44
+ a pipeline that fails its own schema check.
45
+
46
+ ### 3. Collect inputs
47
+
48
+ For each `inputs[]` entry in the pipeline:
49
+
50
+ - If the user supplied a value on invocation (`/orchestrate pr-readiness-check diff_target=feature/x`)
51
+ use it.
52
+ - Else use the `default` field.
53
+ - Else ask **one** question per missing input, in order. Stop after
54
+ the first unanswered required input — pipelines are batch-friendly
55
+ by design, but `ask-when-uncertain` still applies.
56
+
57
+ ### 4. Dispatch the steps
58
+
59
+ Walk `steps[]` in order. For each step:
60
+
61
+ | `kind` | Dispatch path |
62
+ |---|---|
63
+ | `skill` | Invoke the skill identified by `ref` with the resolved `with` block. |
64
+ | `command` | Run the slash-command identified by `ref` as if the user had typed it. |
65
+ | `persona` | Set `roles.active_role` to `ref` for the next dependent step; does not produce its own `output`. |
66
+ | `subagent` | Delegate to [`subagent-orchestration`](../skills/subagent-orchestration/SKILL.md) using `ref` as the mode name. |
67
+
68
+ Capture each step's output in an in-memory `outputs[step.id]` map.
69
+ `${{ inputs.X }}` and `${{ steps.Y.output }}` are substituted via
70
+ string replacement only — no expressions, no shell-out.
71
+
72
+ ### 5. Honour `when`
73
+
74
+ If a step has a `when` field, evaluate it as one of:
75
+
76
+ - `${{ steps.X.output }} == "<literal>"`
77
+ - `steps.X.success` / `steps.X.failure`
78
+
79
+ Anything else → stop the run with a clear error. The DSL is
80
+ deliberately tiny; richer logic belongs in a skill, not in the
81
+ pipeline file.
82
+
83
+ ### 6. Halt on hard failure
84
+
85
+ A step failure ends the pipeline immediately. Surface:
86
+
87
+ - the failing step id and kind/ref
88
+ - the error or non-zero exit
89
+ - the steps that ran cleanly before it
90
+
91
+ Do **not** continue past a failure unless a downstream step has a
92
+ `when: steps.X.failure` guard explicitly authorizing it.
93
+
94
+ ### 7. Produce the delivery report
95
+
96
+ When the pipeline reaches the end of `steps[]`:
97
+
98
+ - Resolve every `outputs[name]` entry by substituting the captured
99
+ step outputs.
100
+ - Print a Markdown delivery report:
101
+ - pipeline name + resolved input values
102
+ - per-step verdict (✅ / ❌, ref, one-line summary)
103
+ - the resolved `outputs:` map at the bottom
104
+
105
+ ### 8. Audit trail
106
+
107
+ Per [`audit-log-v1`](../docs/contracts/audit-log-v1.md), append
108
+ one JSONL entry per step boundary to the current month's audit file
109
+ under `agents/state/audit/`. Counts + ids only — never the step's
110
+ output body.
111
+
112
+ ### 9. What this command does NOT do
113
+
114
+ - Edit the pipeline file. Authoring is human or skill-driven.
115
+ - Commit, push, or open PRs. Those gates live elsewhere.
116
+ - Branch or invent steps. The pipeline file is the source of truth.
117
+
118
+ ## See also
119
+
120
+ - Contract: [`orchestration-dsl-v1.md`](../docs/contracts/orchestration-dsl-v1.md)
121
+ - Linter: `scripts/lint_orchestration_dsl.py`
122
+ - Subagent runtime: [`subagent-orchestration`](../skills/subagent-orchestration/SKILL.md)
123
+ - Audit emission: [`audit-log-v1.md`](../docs/contracts/audit-log-v1.md)
@@ -0,0 +1,135 @@
1
+ ---
2
+ name: sync-gitignore:fix
3
+ cluster: sync-gitignore
4
+ sub: fix
5
+ description: Scrub legacy pre-`/agents/` patterns from the consumer's .gitignore (inside or outside the managed block) and re-sync the canonical entries
6
+ disable-model-invocation: true
7
+ suggestion:
8
+ eligible: true
9
+ trigger_description: "fix .gitignore garbage, clean up legacy agent-config entries, my .gitignore has stale agent entries, /sync-gitignore is not picking up the right paths"
10
+ trigger_context: "consumer project carries pre-/agents/ runtime patterns (.agent-chat-history, .agent-prices.md) at the root from an older install"
11
+ ---
12
+
13
+ # /sync-gitignore:fix
14
+
15
+ Cleanup sibling of [`/sync-gitignore`](../sync-gitignore.md). Strips
16
+ legacy root-level patterns (pre-`/agents/` runtime artefacts —
17
+ `.agent-chat-history`, `.agent-chat-history.bak`,
18
+ `.agent-chat-history.*.bak`, `.agent-prices.md`, `.council-tmp/`) from
19
+ **anywhere** in the consumer's `.gitignore` — inside or outside the
20
+ managed block — then re-runs the regular sync so the current canonical
21
+ `/agents/`-prefixed entries land in the block.
22
+
23
+ Use when:
24
+
25
+ - An older installer (pre-May 2026) dropped root-level `.agent-chat-history`
26
+ / `.agent-prices.md` lines that the current scripts no longer recognise.
27
+ - A hand-edit added one of those legacy paths and it now conflicts with
28
+ the managed `/agents/...` entry.
29
+ - `/sync-gitignore` reports "already in sync" but git is still ignoring
30
+ files at the wrong paths.
31
+
32
+ ## When NOT to use
33
+
34
+ - To remove **user-added** lines from inside the block → that is
35
+ `--replace` on the base command, and it is destructive. This
36
+ sub-command only touches the legacy pattern list — nothing else.
37
+ - To delete the entire managed block → do it by hand.
38
+ - To migrate runtime files themselves (move `.agent-chat-history` →
39
+ `agents/.agent-chat-history`) → the installer's
40
+ `migrate_legacy_root_infra` step handles that. This sub-command only
41
+ fixes `.gitignore`.
42
+
43
+ ## Steps
44
+
45
+ ### 1. Locate script and target
46
+
47
+ Same resolution order as [`/sync-gitignore`](../sync-gitignore.md):
48
+
49
+ 1. `./agent-config/scripts/sync_gitignore.py`
50
+ 2. `vendor/event4u/agent-config/scripts/sync_gitignore.py`
51
+ 3. `node_modules/@event4u/agent-config/scripts/sync_gitignore.py`
52
+
53
+ Target is `<project_root>/.gitignore`. If no `.gitignore` exists,
54
+ stop — there is nothing to fix:
55
+
56
+ ```
57
+ > 📝 No .gitignore found at <project_root>. Nothing to clean up.
58
+ ```
59
+
60
+ ### 2. Dry-run with cleanup
61
+
62
+ Run:
63
+
64
+ ```bash
65
+ python3 <script> --cleanup-legacy --dry-run
66
+ ```
67
+
68
+ Capture stdout (unified diff) and stderr (summary line listing the
69
+ legacy entries that would be removed). Three outcomes:
70
+
71
+ - **Nothing legacy + block in sync** → tell the user and stop:
72
+ ```
73
+ > ✅ .gitignore already clean — no legacy patterns, block in sync.
74
+ ```
75
+ - **Diff produced** → show it and ask:
76
+ ```
77
+ > 🧹 /sync-gitignore:fix would clean up .gitignore:
78
+ >
79
+ > {diff}
80
+ >
81
+ > Summary: would remove {N} legacy entr{y|ies}: {names}
82
+ > would add {M} entr{y|ies} to the managed block
83
+ >
84
+ > 1. Apply — write the changes
85
+ > 2. Skip — leave .gitignore untouched
86
+ ```
87
+ - **Script error** (exit 2) → print the error and stop; do not prompt.
88
+
89
+ ### 3. Act on the choice
90
+
91
+ - `1` (Apply) → re-run **without** `--dry-run`:
92
+ ```bash
93
+ python3 <script> --cleanup-legacy
94
+ ```
95
+ Confirm with the script's own summary lines (removed-legacy count and
96
+ added-entries count both surface there).
97
+ - `2` (Skip) → stop. No changes made.
98
+
99
+ Free-text replies (`"nö"`, `"leave it"`, unrecognized input) count as
100
+ `2`. Never write on ambiguous input.
101
+
102
+ ### 4. Suggest the migration check (informational, do NOT auto-run)
103
+
104
+ If the cleanup removed `.agent-chat-history` or `.agent-prices.md`,
105
+ mention that the **file** at the root (if still present) may need to
106
+ move to `agents/`. The installer does this automatically via
107
+ [`migrate_legacy_root_infra`](../../../scripts/install.sh); the
108
+ agent does not run it from this command. One line of guidance is
109
+ enough:
110
+
111
+ ```
112
+ > ℹ️ If `.agent-chat-history` still sits at the project root, re-run the installer (or move it to `agents/.agent-chat-history` by hand) so the runtime can find it again.
113
+ ```
114
+
115
+ ## Rules
116
+
117
+ - **Append-only by default** — `--cleanup-legacy` removes legacy
118
+ patterns only. User-added non-legacy lines (inside or outside the
119
+ block) survive untouched.
120
+ - **Never combine with `--replace`** — the destructive full-block
121
+ rewrite is a separate concern; mixing the two surprises users.
122
+ - **Dry-run first, always** — the user must see the diff before any
123
+ write.
124
+ - **Do NOT push, commit, or modify other files** — this command writes
125
+ to `.gitignore` only.
126
+
127
+ ## See also
128
+
129
+ - [`/sync-gitignore`](../sync-gitignore.md) — append-only sync of the
130
+ managed block (no legacy cleanup)
131
+ - [`scripts/sync_gitignore.py`](../../../scripts/sync_gitignore.py) —
132
+ the helper (`--cleanup-legacy` flag)
133
+ - [`scripts/install.sh`](../../../scripts/install.sh) —
134
+ `migrate_legacy_root_infra` (moves the **files**, complement to this
135
+ command which fixes the **ignore rules**)
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: sync-gitignore
3
+ cluster: sync-gitignore
3
4
  description: Sync the `event4u/agent-config` block in the consumer project's .gitignore — adds missing entries, preserves user-added lines, shows a diff before writing
4
5
  disable-model-invocation: true
5
6
  suggestion:
@@ -9,6 +10,28 @@ suggestion:
9
10
 
10
11
  # /sync-gitignore
11
12
 
13
+ Top-level entry point for the `/sync-gitignore` family. Bare `/sync-gitignore`
14
+ runs the interactive append-only sync described below. The `:fix`
15
+ sub-command additionally scrubs legacy patterns (pre-`/agents/` layout)
16
+ from anywhere in the consumer's `.gitignore` before re-syncing.
17
+
18
+ ## Sub-commands
19
+
20
+ | Sub-command | Routes to | Purpose |
21
+ |---|---|---|
22
+ | `/sync-gitignore` (bare) | this file (`## Default flow`) | Interactive — append-only sync of the managed block, dry-run preview, confirm before write |
23
+ | `/sync-gitignore:fix` | `commands/sync-gitignore/fix.md` | Cleanup — strip legacy root-level patterns (pre-`/agents/` layout) wherever they appear, then sync |
24
+
25
+ ## Dispatch
26
+
27
+ 1. Parse the user's argument: `/sync-gitignore[:<sub>] [args]`.
28
+ 2. Bare `/sync-gitignore` → run the `## Default flow` below verbatim.
29
+ 3. `/sync-gitignore:fix` → load `commands/sync-gitignore/fix.md` and follow
30
+ its `## Steps` section verbatim.
31
+ 4. Unknown sub-command → print the table above and ask which one.
32
+
33
+ ## Default flow
34
+
12
35
  Ensures the consumer project's `.gitignore` contains every entry the
13
36
  package expects to be ignored (symlinked `.augment/` subdirectories,
14
37
  `/agent-config` CLI wrapper, `.agent-settings*`, `agents/.agent-chat-history*`).
@@ -23,7 +46,7 @@ Use when:
23
46
  setup, or installer ran with `--skip-gitignore`).
24
47
  - You want to audit what the block **should** look like without writing.
25
48
 
26
- ## When NOT to use
49
+ ### When NOT to use
27
50
 
28
51
  - To disable logging or change what is logged → that is
29
52
  `chat_history.enabled` in `.agent-settings.yml`, not `.gitignore`.
@@ -31,8 +54,8 @@ Use when:
31
54
  re-remove its own entries.
32
55
  - To change what the block contains → edit
33
56
  `config/gitignore-block.txt` in the package repo and re-release.
34
-
35
- ## Steps
57
+ - To clean up legacy garbage from older installs → use
58
+ [`/sync-gitignore:fix`](sync-gitignore/fix.md) instead.
36
59
 
37
60
  ### 1. Locate script and target
38
61
 
@@ -88,9 +111,11 @@ Free-text replies (`"nö"`, `"leave it"`, unrecognized input) count as
88
111
  Do **not** suggest `--replace` by default. It rewrites the block in
89
112
  full and drops user-added lines inside the block — destructive.
90
113
  Mention it only if the user explicitly asks to clean up or reset the
91
- block, and confirm once more before running it.
114
+ block, and confirm once more before running it. For removing legacy
115
+ root-level patterns (not user-added lines), prefer
116
+ [`/sync-gitignore:fix`](sync-gitignore/fix.md).
92
117
 
93
- ## Gotchas
118
+ ## Rules
94
119
 
95
120
  - The script honors the explicit `# event4u/agent-config — END` marker.
96
121
  Legacy blocks without it get the marker added automatically on the
@@ -99,6 +124,7 @@ block, and confirm once more before running it.
99
124
  They do not survive `--replace`.
100
125
  - Changes to `config/gitignore-block.txt` require a package update in
101
126
  the consumer project before this command can apply them.
127
+ - **Do NOT chain sub-commands.** One `/sync-gitignore <sub>` per turn.
102
128
 
103
129
  ## See also
104
130
 
@@ -21,6 +21,11 @@ Use this skill when:
21
21
  * Reviewing post-task learnings or retrospectives
22
22
  * Deciding whether a learning belongs in a rule or a skill
23
23
  * After completing a task — reflecting on what worked or caused friction
24
+ * Mining the audit log (`agents/state/audit/<YYYY-MM>.jsonl`,
25
+ [`audit-log-v1`](../../../docs/contracts/audit-log-v1.md)) surfaced
26
+ a repeated phase pattern via
27
+ [`extract_audit_patterns.py`](../../../scripts/extract_audit_patterns.py)
28
+ — the pattern's `count` ≥ 2 already satisfies the repetition gate
24
29
 
25
30
  Do not use this skill when:
26
31
 
@@ -206,8 +211,10 @@ Mandatory fields the draft MUST fill:
206
211
  * `source_learning` — path to the `agents/learnings/<date>-<slug>.md`
207
212
  file this proposal was captured from
208
213
  * `evidence` — **at least two independent** references (PR, issue,
209
- incident, review-comment, test-failure); entries that all resolve
210
- to the same PR are rejected by the gate
214
+ incident, review-comment, test-failure, **or audit-log line ids**
215
+ per [`audit-log-v1`](../../../docs/contracts/audit-log-v1.md));
216
+ entries that all resolve to the same PR or the same audit-log
217
+ `run_id` are rejected by the gate (independence floor)
211
218
  * `Proposed artefact` (§4) — the full draft body, no `TODO` / `TBD`
212
219
  * `Success signal` (§7) — one metric, one baseline, one target, one
213
220
  evaluation date
@@ -295,6 +302,27 @@ audio) goes through the upstream `markitdown-mcp` server first; only
295
302
  write a custom extractor if `markitdown` cannot handle the format and
296
303
  the gap is documented in its skill body.
297
304
 
305
+ ## Audit-derived learnings (optional source)
306
+
307
+ When the input is a pattern surfaced by
308
+ [`extract_audit_patterns.py`](../../../scripts/extract_audit_patterns.py)
309
+ mining `agents/state/audit/<YYYY-MM>.jsonl`
310
+ ([`audit-log-v1`](../../../docs/contracts/audit-log-v1.md)):
311
+
312
+ 1. Treat the script's pattern record as the **State the learning**
313
+ step input (§1) — `pattern.summary` is the one-sentence
314
+ statement, `pattern.line_ids` is the evidence.
315
+ 2. The repetition gate is already satisfied for `count ≥ 2`. Skip
316
+ to §3 (decide the target) — overlap check (§4) and proposal
317
+ draft (§8) remain mandatory.
318
+ 3. Independence floor still applies: two line ids from the same
319
+ `run_id` count as **one** piece of evidence. The mining script
320
+ already de-duplicates by `run_id`; the gate trusts that output.
321
+ 4. Audit-derived proposals MUST set
322
+ `source_learning: agents/state/audit/<YYYY-MM>.jsonl#<line_ids>`
323
+ and link the mining-script run id, so the human reviewer can
324
+ reproduce the pattern from the raw audit log.
325
+
298
326
  ## Environment notes
299
327
 
300
328
  Prefer updating existing rule/skill when possible.
@@ -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). |
@@ -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.39.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",