@execuro-sw-ecosystem/sw-ecosystem-agentic-harness 0.1.0 → 0.1.3

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 (34) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +53 -14
  3. package/SECURITY.md +1 -1
  4. package/agents/sw-php-backend-developer.md +1 -1
  5. package/agents/sw-storefront-developer.md +5 -5
  6. package/agents/sw-tender-editor.md +1 -1
  7. package/bin/cli.mjs +8 -3
  8. package/codex/agents/sw-php-backend-developer.toml +2 -2
  9. package/codex/agents/sw-storefront-developer.toml +6 -6
  10. package/codex/agents/sw-tender-editor.toml +2 -2
  11. package/content/AGENTS.md +2 -2
  12. package/copilot/agents/sw-php-backend-developer.agent.md +2 -2
  13. package/copilot/agents/sw-storefront-developer.agent.md +6 -6
  14. package/copilot/agents/sw-tender-editor.agent.md +2 -2
  15. package/lib/actions.mjs +11 -10
  16. package/lib/commands.mjs +17 -14
  17. package/lib/{companions.mjs → extra-components.mjs} +16 -16
  18. package/lib/guide.mjs +39 -18
  19. package/lib/hosts.mjs +10 -10
  20. package/lib/merge.mjs +10 -0
  21. package/package.json +2 -2
  22. package/plugin.json +1 -1
  23. package/skills/sw-design-requirements/SKILL.md +1 -1
  24. package/skills/sw-design-solution/SKILL.md +1 -1
  25. package/skills/sw-discover-tender/SKILL.md +1 -1
  26. package/skills/sw-document-feature/SKILL.md +1 -1
  27. package/skills/sw-document-feature/reference/setup-wiki.md +7 -2
  28. package/skills/sw-implement-feature/SKILL.md +1 -1
  29. package/skills/sw-setup/SKILL.md +15 -14
  30. package/skills/sw-setup/reference/rows.md +31 -34
  31. package/skills/sw-verify-feature/SKILL.md +1 -1
  32. package/skills/sw-verify-feature-ac-tests/SKILL.md +1 -1
  33. package/skills/sw-verify-feature-architecture/SKILL.md +1 -1
  34. package/skills/sw-verify-feature-code-quality/SKILL.md +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,69 @@ release.
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.3] - 2026-09-17
11
+
12
+ ### Changed
13
+
14
+ - **The `vendor/` row now names the specific missing file when unticked.**
15
+ `sw-setup`'s printed line says which of `vendor/shopware/core`,
16
+ `composer.lock` or `vendor/bin/phpunit` is missing instead of just marking
17
+ the row unticked, so a `--no-dev` vendor tree (which has the first two but
18
+ not `vendor/bin/phpunit`) is diagnosable at a glance. Same fix as before,
19
+ `composer install --no-interaction`, offered the same way.
20
+ - **The update documentation now covers every component.** `README.md`'s
21
+ **Keep up to date** section became **Keeping it up to date** and says how
22
+ each of the three parts updates on its own schedule: this package through
23
+ `install`, the MCP servers by themselves (they are registered as `@latest`,
24
+ so a host fetches the newest build when it next starts one), and the Specs
25
+ Editor and Tender Discovery Tool through their own `npm i -D` line followed
26
+ by a re-run of `install`. The section also moved below Install's own
27
+ subsections, which it had been separating from their heading.
28
+ - **The two extra components are now named individually, with the exact
29
+ pinned `npm i -D` command for each**, in both `README.md`'s new **The
30
+ optional editors** subsection and `guide`'s output — replacing the earlier
31
+ `<package>@<version>` placeholder — because the probe accepts only the
32
+ exact version `EXTRA_COMPONENTS` pins and skips the skill for anything else,
33
+ including `@latest`.
34
+ - **Breaking: the "companion" terminology is renamed to "extra component".**
35
+ The CLI flag `--no-companions` is now `--no-extra-components`, and the
36
+ `status` JSON output key `companions` is now `extra_components`. Anything
37
+ scripting against `0.1.2` or earlier that reads `companions` or passes
38
+ `--no-companions` must update to the new names — there is no backwards-
39
+ compatible alias.
40
+ - **`sw-storefront-developer`'s locate proofs now check for deprecated
41
+ forwarder files and `feature()` branches, and read the active theme's
42
+ `views` chain.** Proof 1 rejects a template whose header names it a removed
43
+ forwarder; proof 2 notes the `feature()` branch a block sits in; proof 3
44
+ also greps the theme's `theme.json` `views` order. The chain order and
45
+ where an override belongs are now deferred to the storefront guideline's
46
+ expert template-inheritance and override-placement sections instead of
47
+ being restated in the agent body.
48
+
49
+ ## [0.1.2] - 2026-09-16
50
+
51
+ ### Changed
52
+
53
+ - **Install/update instructions now anchor to `@latest`.** Every `npx`
54
+ invocation of this package in `README.md`, `sw-setup`'s `SKILL.md` and
55
+ `reference/rows.md` uses `@execuro-sw-ecosystem/sw-ecosystem-agentic-harness@latest`
56
+ instead of a bare package name or a frozen version — a bare invocation can
57
+ resolve to a stale copy already in the `npx` cache or `node_modules`. Added
58
+ a **Keep up to date** section to `README.md` and a **Keeping it current**
59
+ section to `guide`'s output.
60
+
61
+ ## [0.1.1] - 2026-09-16
62
+
63
+ ### Added
64
+
65
+ - **An `install` command** — `apply --yes` under a friendlier one-step name.
66
+ Typing the verb is the consent, so it never prompts and needs no `--yes`; the
67
+ scriptable `apply --yes` is unchanged and stays what skills and CI call.
68
+
69
+ ### Fixed
70
+
71
+ - CI and the GitHub Pages setup for the plugin marketplace.
72
+
10
73
  ## [0.1.0] - 2026-09-16
11
74
 
12
75
  ### Added
package/README.md CHANGED
@@ -49,7 +49,8 @@ declines is fully set up without them, and `--editor` on `sw-design-requirements
49
49
 
50
50
  | Command | What it does |
51
51
  | --- | --- |
52
- | `status` | read-only; what is installed, what drifted, which optional companion packages are present |
52
+ | `install` | installs into every detected host; the same as `apply --yes` |
53
+ | `status` | read-only; what is installed, what drifted, which optional extra components are present |
53
54
  | `plan` | the exact changes, written as a diff; writes nothing |
54
55
  | `apply --yes` | performs them |
55
56
  | `uninstall --yes` | removes what the lock file records |
@@ -58,22 +59,18 @@ declines is fully set up without them, and `--editor` on `sw-design-requirements
58
59
  Flags: `--host claude-code|codex|copilot|cursor` (repeatable; omitting it
59
60
  selects every host that is present), `--scope project|user` (default
60
61
  `project`), `--root <path>` (default the current directory), `--yes` (required
61
- by `apply` and `uninstall`, never prompts), and `--no-companions` (skip
62
- detection of the optional editor packages).
62
+ by `apply` and `uninstall`, never prompts — `install` implies it), and
63
+ `--no-extra-components` (skip detection of the optional editor packages).
63
64
 
64
65
  ## Install
65
66
 
66
67
  ```bash
67
- # see what is there and what is missing — read-only, always safe
68
- npx -y @execuro-sw-ecosystem/sw-ecosystem-agentic-harness status
69
-
70
- # see the exact changes, written as a diff. Writes nothing.
71
- npx -y @execuro-sw-ecosystem/sw-ecosystem-agentic-harness plan
72
-
73
- # perform them
74
- npx -y @execuro-sw-ecosystem/sw-ecosystem-agentic-harness apply --yes
68
+ npx -y @execuro-sw-ecosystem/sw-ecosystem-agentic-harness@latest install
75
69
  ```
76
70
 
71
+ Want to look first? `status` is read-only and `plan` prints the diff; neither
72
+ writes anything.
73
+
77
74
  Then restart your coding agent — every host reads its configuration at startup.
78
75
  The command's output tells you which ones and how.
79
76
 
@@ -111,7 +108,7 @@ Codex never reads `CLAUDE.md`.
111
108
  Undo everything with:
112
109
 
113
110
  ```bash
114
- npx -y @execuro-sw-ecosystem/sw-ecosystem-agentic-harness uninstall --yes
111
+ npx -y @execuro-sw-ecosystem/sw-ecosystem-agentic-harness@latest uninstall --yes
115
112
  ```
116
113
 
117
114
  It removes exactly what the lock file at `.sw-ai-sdk/harness.lock.json` records,
@@ -125,9 +122,51 @@ installed skill file goes stale and the CLI does not. Anything that needs the
125
122
  protocol runs:
126
123
 
127
124
  ```bash
128
- npx -y @execuro-sw-ecosystem/sw-ecosystem-agentic-harness guide
125
+ npx -y @execuro-sw-ecosystem/sw-ecosystem-agentic-harness@latest guide
129
126
  ```
130
127
 
128
+ ## Keeping it up to date
129
+
130
+ Three things update on different schedules.
131
+
132
+ | What | How it updates |
133
+ | --- | --- |
134
+ | This package — skills, sub-agents, permission rules, MCP registrations | `npx -y @execuro-sw-ecosystem/sw-ecosystem-agentic-harness@latest install` |
135
+ | The MCP servers (`ShopwareDevKnowledgeBase`, `playwright`) | By themselves. They are registered as `@latest`, so your agent fetches the newest build the next time it starts one. Nothing to run. |
136
+ | Specs Editor, Tender Discovery Tool | You update them. Each has its own line — see below. |
137
+
138
+ ### The optional editors
139
+
140
+ Install or update each at the exact version this release pins — the CLI
141
+ probes for that version and skips the skill if it finds another:
142
+
143
+ ```bash
144
+ npm i -D @execuro-sw-ecosystem/sw-specs-editor@0.1.0
145
+ npm i -D @execuro-sw-ecosystem/sw-tender-discovery-tool@0.1.0
146
+ ```
147
+
148
+ - `sw-specs-editor` backs the `--editor` flag on `sw-design-requirements` and
149
+ `sw-design-solution`.
150
+ - `sw-tender-discovery-tool` backs the `--editor` flag and the `.xlsx` import
151
+ on `sw-discover-tender`.
152
+
153
+ Re-run `install` afterwards so the new skill lands in each host. `status`'s
154
+ `extra_components[]` array reports the pinned `version` and the exact `install`
155
+ command for whichever one is missing.
156
+
157
+ Re-running `install` is safe at any time: it rewrites only what it owns,
158
+ leaves anything you hand-edited alone (reported as drift), and records the new
159
+ version in the lock file. Restart your coding agent afterwards — every host
160
+ reads its configuration at startup.
161
+
162
+ `@latest` matters. Without it, `npx` can reuse a copy already in its cache or
163
+ in this project's `node_modules`, so you would silently keep running the
164
+ version you first installed.
165
+
166
+ To look before you change anything: `... @latest status` reports
167
+ `installed_version`, any drift, and which extra components are present. It
168
+ writes nothing.
169
+
131
170
  ## Prerequisites
132
171
 
133
172
  | Requirement | Why | Check |
@@ -210,7 +249,7 @@ instead — it then applies across every repo without per-repo commits.
210
249
 
211
250
  ```bash
212
251
  # 1. the install is complete and undrifted
213
- npx -y @execuro-sw-ecosystem/sw-ecosystem-agentic-harness status
252
+ npx -y @execuro-sw-ecosystem/sw-ecosystem-agentic-harness@latest status
214
253
 
215
254
  # 2. the toolchain the agents call actually exists
216
255
  shopware-cli project console --help
package/SECURITY.md CHANGED
@@ -41,7 +41,7 @@ scripts** on install, which removes a large part of the usual npm attack
41
41
  surface.
42
42
 
43
43
  Out of scope: vulnerabilities in the coding agents themselves, in the
44
- separately published companion packages (they have their own repositories
44
+ separately published extra components (they have their own repositories
45
45
  and their own reporting), or in a third-party MCP server this package merely
46
46
  registers, such as Playwright's (report those upstream).
47
47
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: sw-php-backend-developer
3
- description: Shopware 6 PHP backend specialist. Use PROACTIVELY for any Shopware backend work — plugin/app PHP, DAL entities & migrations, Store/Admin API routes, services & decorators, event subscribers, flow actions, message handlers, caching, ACL. Triggers: "add an entity", "create an API endpoint", "extend checkout/order/product logic", "write a migration", "implement backend AC". Not for Administration (Vue) or Storefront (Twig/JS) work.
3
+ description: Shopware 6 PHP backend specialist. Use PROACTIVELY for any Shopware backend work — plugin/app PHP, DAL entities & migrations, Store/Admin API routes, services & decorators, event subscribers, flow actions, message handlers, caching, ACL. Triggers — "add an entity", "create an API endpoint", "extend checkout/order/product logic", "write a migration", "implement backend AC". Not for Administration (Vue) or Storefront (Twig/JS) work.
4
4
  model: inherit
5
5
  tools: Read, Write, Edit, Glob, Grep, Bash, mcp__ShopwareDevKnowledgeBase__list_docs, mcp__ShopwareDevKnowledgeBase__grep_docs, mcp__ShopwareDevKnowledgeBase__read_doc, mcp__ShopwareDevKnowledgeBase__kb_status
6
6
  color: blue
@@ -41,7 +41,7 @@ An edit is not done until `lint:twig` passes on it and the changed markup is obs
41
41
  - Lighthouse on touched pages; perf/a11y/SEO regressions are failures.
42
42
  - Playwright MCP (`mcp__playwright__*`) for click-through and screenshots. Shopware docs MCP (`mcp__ShopwareDevKnowledgeBase__*`) for the exact core template, block, route or plugin before extending; never guess a block name.
43
43
  - Test the targeted state: logged-out, logged-in, guest checkout, plus a non-default currency or language when prices or text change.
44
- - "Does not show up" checklist, in order: template path, block name, theme assigned, theme compiled, cache cleared, browser cache.
44
+ - "Does not show up" checklist, in order: template path (not a deprecated forwarder file), block name in that file (and its `feature()` branch), theme assigned, theme compiled, cache cleared, browser cache.
45
45
 
46
46
  ### Small, reversible
47
47
  - One concern per template, subscriber or plugin; header + footer + checkout is three changes.
@@ -56,14 +56,14 @@ An edit is not done until `lint:twig` passes on it and the changed markup is obs
56
56
 
57
57
  1. **Classify.** Confirm storefront work (see Boundary). If mixed, split and hand off the non-storefront part first.
58
58
  2. **Locate — prove it, never assume.** Docs MCP answers *what an extension point is for*; only the installed source answers *whether it exists here*. Produce all four proofs before writing a single `{% block %}`:
59
- 1. The target template exists at the exact relative path: `ls vendor/shopware/storefront/Resources/views/storefront/<rel>`.
60
- 2. The block name exists verbatim **in that file**: `grep -n "{% block <name>" vendor/shopware/storefront/Resources/views/storefront/<rel>`. Block names are not unique across the storefront (~2200 block tags, ~2035 distinct names — every override re-declares its parent's name), so a repo-wide grep for a name is not proof. If it is not in that file, it lives in an included sub-template: find that file and override it instead.
61
- 3. Every other bundle already providing the same relative path: `grep -rl "<rel>" custom/ vendor/*/*/Resources/views/`. Name who wins under the slot order above, and whether they call `{{ parent() }}`.
59
+ 1. The target template exists at the exact relative path: `ls vendor/shopware/storefront/Resources/views/storefront/<rel>` — and its first lines carry no `@deprecated … File will be removed` header: such a forwarder is never loaded, extend the file it names instead.
60
+ 2. The block name exists verbatim **in that file**: `grep -n "{% block <name>" vendor/shopware/storefront/Resources/views/storefront/<rel>`. Block names are not unique across the storefront (~2200 block tags, ~2035 distinct names — every override re-declares its parent's name), so a repo-wide grep for a name is not proof. If it is not in that file, it lives in an included sub-template: find that file and override it instead. Note the `feature()` branch the block sits in, if any — the guideline's template-inheritance section says which anchor to target.
61
+ 3. Every other bundle already providing the same relative path: `grep -rl "<rel>" custom/ vendor/*/*/Resources/views/`, plus the active theme's chain: `grep -A4 '"views"' custom/*/*/src/Resources/theme.json`. Name who wins under the chain order in the guideline's template-inheritance section, and whether they call `{{ parent() }}`.
62
62
  4. When the dev env is up, `shopware-cli project console debug:twig @Storefront/storefront/<rel>` prints "Matched File" and "Overridden Files" — the real resolution, and it overrides 1–3. Caveat: the theme slot is filled per storefront request, so a CLI run may show only the `@Storefront`/`@Plugins` part of the chain; the browser is the final word on which theme override wins.
63
63
  A block name recalled from memory or from an older Shopware version is a defect, not a starting point.
64
64
  3. **Place.** Theme (look, Bootstrap variables) vs plugin (features).
65
65
  4. **Test first** where tooling supports it: Jest for JS plugins, functional/e2e for rendered HTML. Failing test before the extension.
66
- 5. **Implement** the smallest extension that passes: one block, one subscriber, one plugin.
66
+ 5. **Implement** the smallest extension that passes: one block, one subscriber, one plugin — placed per the guideline's override-placement ladder (edit an existing project override rather than adding a second file for the same path; go later in the chain than a marketplace override).
67
67
  6. **Lint and build.** `lint:twig` on every touched Twig file first, then `extension validate` for SCSS/JS, then the cache/theme/build sequence — all per "Verify: lint, build, render".
68
68
  7. **Verify.** Render assertion first (the changed markup is actually in the page), then browser pass (Playwright MCP), console/network clean, breakpoints, keyboard, Lighthouse.
69
69
  8. **Report** in the format below.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: sw-tender-editor
3
- description: Mechanical file worker for the sw-discover-tender skill — extracts requirement tables from a tender's source export into cluster files, merges PM/architect/QA cluster reports into finished §4 row blocks, splices those blocks into the analysis sidecar, and assembles and checks the final response CSVs. Deterministic, no judgment: every value it writes comes from a named input file, never from memory or estimation. Not for research, feasibility checks, effort estimation or any judgment call — that is `sw-product-manager`, `sw-shopware-architect` and `sw-qa-engineer`'s work; this agent only reshapes what they already produced into the file the skill needs next.
3
+ description: Mechanical file worker for the sw-discover-tender skill — extracts requirement tables from a tender's source export into cluster files, merges PM/architect/QA cluster reports into finished §4 row blocks, splices those blocks into the analysis sidecar, and assembles and checks the final response CSVs. Deterministic, no judgment — every value it writes comes from a named input file, never from memory or estimation. Not for research, feasibility checks, effort estimation or any judgment call — that is `sw-product-manager`, `sw-shopware-architect` and `sw-qa-engineer`'s work; this agent only reshapes what they already produced into the file the skill needs next.
4
4
  tools: Read, Write, Edit, Glob, Grep
5
5
  ---
6
6
 
package/bin/cli.mjs CHANGED
@@ -21,25 +21,30 @@ const COMMANDS = {
21
21
  status: async () => (await import('../lib/commands.mjs')).status,
22
22
  plan: async () => (await import('../lib/commands.mjs')).plan,
23
23
  apply: async () => (await import('../lib/commands.mjs')).apply,
24
+ install: async () => (await import('../lib/commands.mjs')).install,
24
25
  uninstall: async () => (await import('../lib/commands.mjs')).uninstall,
25
26
  guide: async () => (await import('../lib/guide.mjs')).main,
26
27
  };
27
28
 
28
29
  const USAGE = `usage: sw-ecosystem-agentic-harness <command> [options]
29
30
 
31
+ install [--host <h>] [--scope project|user] [--root <path>] [--no-extra-components]
30
32
  status [--host <h>] [--scope project|user] [--root <path>]
31
- plan [--host <h>] [--scope project|user] [--root <path>] [--no-companions]
32
- apply --yes [--host <h>] [--scope project|user] [--root <path>] [--no-companions]
33
+ plan [--host <h>] [--scope project|user] [--root <path>] [--no-extra-components]
34
+ apply --yes [--host <h>] [--scope project|user] [--root <path>] [--no-extra-components]
33
35
  uninstall --yes [--host <h>] [--scope project|user] [--root <path>]
34
36
  guide print the install protocol
35
37
 
38
+ install is apply --yes under a friendlier name: the whole install in one
39
+ command, no --yes needed — typing the verb is the consent.
40
+
36
41
  <h> is one of: claude-code, codex, copilot, cursor. Repeat --host to select
37
42
  several; omitting it selects every host that is present.
38
43
 
39
44
  --scope defaults to project: nothing is written under your home directory
40
45
  unless you ask for it. --root defaults to the current directory.
41
46
 
42
- --no-companions skips the two optional companion-package skills (Specs
47
+ --no-extra-components skips the two optional extra-component skills (Specs
43
48
  Editor, Tender Discovery Tool) even when the package is present locally.
44
49
 
45
50
  Every command prints one JSON object on stdout carrying a next_step. Run
@@ -1,9 +1,9 @@
1
1
  # Generated by scripts/gen-codex-agents.mjs — do not edit.
2
2
  # source: agents/sw-php-backend-developer.md
3
- # source-sha256: 1d248ea38a0118ce2ebcea32f8dc780934bb6228e29c1b034baa1b3c43e102ea
3
+ # source-sha256: ea82533b10d08e276f3c82da3555fd49f66b4141850990bca3e7e878fe07df61
4
4
 
5
5
  name = "sw-php-backend-developer"
6
- description = "Shopware 6 PHP backend specialist. Use PROACTIVELY for any Shopware backend work — plugin/app PHP, DAL entities & migrations, Store/Admin API routes, services & decorators, event subscribers, flow actions, message handlers, caching, ACL. Triggers: \"add an entity\", \"create an API endpoint\", \"extend checkout/order/product logic\", \"write a migration\", \"implement backend AC\". Not for Administration (Vue) or Storefront (Twig/JS) work."
6
+ description = "Shopware 6 PHP backend specialist. Use PROACTIVELY for any Shopware backend work — plugin/app PHP, DAL entities & migrations, Store/Admin API routes, services & decorators, event subscribers, flow actions, message handlers, caching, ACL. Triggers — \"add an entity\", \"create an API endpoint\", \"extend checkout/order/product logic\", \"write a migration\", \"implement backend AC\". Not for Administration (Vue) or Storefront (Twig/JS) work."
7
7
 
8
8
  developer_instructions = '''
9
9
 
@@ -1,6 +1,6 @@
1
1
  # Generated by scripts/gen-codex-agents.mjs — do not edit.
2
2
  # source: agents/sw-storefront-developer.md
3
- # source-sha256: 3d2ee9b1f1691eebedd1abaaf7126b55d5ea74677acda6a3f19765490ed9e6ce
3
+ # source-sha256: 9afbeda4ca95afe1d74bf0268c3551031a519524fd2d1c4d2c4ece874cd6c054
4
4
 
5
5
  name = "sw-storefront-developer"
6
6
  description = "Use PROACTIVELY for any Shopware 6 Storefront work - Twig templates, theme/SCSS, storefront controllers/pages/pagelets, storefront JS plugins, checkout/account/product-page customizations."
@@ -43,7 +43,7 @@ An edit is not done until `lint:twig` passes on it and the changed markup is obs
43
43
  - Lighthouse on touched pages; perf/a11y/SEO regressions are failures.
44
44
  - Playwright MCP (`mcp__playwright__*`) for click-through and screenshots. Shopware docs MCP (`mcp__ShopwareDevKnowledgeBase__*`) for the exact core template, block, route or plugin before extending; never guess a block name.
45
45
  - Test the targeted state: logged-out, logged-in, guest checkout, plus a non-default currency or language when prices or text change.
46
- - "Does not show up" checklist, in order: template path, block name, theme assigned, theme compiled, cache cleared, browser cache.
46
+ - "Does not show up" checklist, in order: template path (not a deprecated forwarder file), block name in that file (and its `feature()` branch), theme assigned, theme compiled, cache cleared, browser cache.
47
47
 
48
48
  ### Small, reversible
49
49
  - One concern per template, subscriber or plugin; header + footer + checkout is three changes.
@@ -58,14 +58,14 @@ An edit is not done until `lint:twig` passes on it and the changed markup is obs
58
58
 
59
59
  1. **Classify.** Confirm storefront work (see Boundary). If mixed, split and hand off the non-storefront part first.
60
60
  2. **Locate — prove it, never assume.** Docs MCP answers *what an extension point is for*; only the installed source answers *whether it exists here*. Produce all four proofs before writing a single `{% block %}`:
61
- 1. The target template exists at the exact relative path: `ls vendor/shopware/storefront/Resources/views/storefront/<rel>`.
62
- 2. The block name exists verbatim **in that file**: `grep -n "{% block <name>" vendor/shopware/storefront/Resources/views/storefront/<rel>`. Block names are not unique across the storefront (~2200 block tags, ~2035 distinct names — every override re-declares its parent's name), so a repo-wide grep for a name is not proof. If it is not in that file, it lives in an included sub-template: find that file and override it instead.
63
- 3. Every other bundle already providing the same relative path: `grep -rl "<rel>" custom/ vendor/*/*/Resources/views/`. Name who wins under the slot order above, and whether they call `{{ parent() }}`.
61
+ 1. The target template exists at the exact relative path: `ls vendor/shopware/storefront/Resources/views/storefront/<rel>` — and its first lines carry no `@deprecated … File will be removed` header: such a forwarder is never loaded, extend the file it names instead.
62
+ 2. The block name exists verbatim **in that file**: `grep -n "{% block <name>" vendor/shopware/storefront/Resources/views/storefront/<rel>`. Block names are not unique across the storefront (~2200 block tags, ~2035 distinct names — every override re-declares its parent's name), so a repo-wide grep for a name is not proof. If it is not in that file, it lives in an included sub-template: find that file and override it instead. Note the `feature()` branch the block sits in, if any — the guideline's template-inheritance section says which anchor to target.
63
+ 3. Every other bundle already providing the same relative path: `grep -rl "<rel>" custom/ vendor/*/*/Resources/views/`, plus the active theme's chain: `grep -A4 '"views"' custom/*/*/src/Resources/theme.json`. Name who wins under the chain order in the guideline's template-inheritance section, and whether they call `{{ parent() }}`.
64
64
  4. When the dev env is up, `shopware-cli project console debug:twig @Storefront/storefront/<rel>` prints "Matched File" and "Overridden Files" — the real resolution, and it overrides 1–3. Caveat: the theme slot is filled per storefront request, so a CLI run may show only the `@Storefront`/`@Plugins` part of the chain; the browser is the final word on which theme override wins.
65
65
  A block name recalled from memory or from an older Shopware version is a defect, not a starting point.
66
66
  3. **Place.** Theme (look, Bootstrap variables) vs plugin (features).
67
67
  4. **Test first** where tooling supports it: Jest for JS plugins, functional/e2e for rendered HTML. Failing test before the extension.
68
- 5. **Implement** the smallest extension that passes: one block, one subscriber, one plugin.
68
+ 5. **Implement** the smallest extension that passes: one block, one subscriber, one plugin — placed per the guideline's override-placement ladder (edit an existing project override rather than adding a second file for the same path; go later in the chain than a marketplace override).
69
69
  6. **Lint and build.** `lint:twig` on every touched Twig file first, then `extension validate` for SCSS/JS, then the cache/theme/build sequence — all per "Verify: lint, build, render".
70
70
  7. **Verify.** Render assertion first (the changed markup is actually in the page), then browser pass (Playwright MCP), console/network clean, breakpoints, keyboard, Lighthouse.
71
71
  8. **Report** in the format below.
@@ -1,9 +1,9 @@
1
1
  # Generated by scripts/gen-codex-agents.mjs — do not edit.
2
2
  # source: agents/sw-tender-editor.md
3
- # source-sha256: 72f167887e7d97164c0c7bb2ed03e23dd041fb868746857744bcd255909a03d5
3
+ # source-sha256: 288b5d684f40ea62aa2b6021cfa3109a7f664fc2365f32109bcd9d1f94eb8a2c
4
4
 
5
5
  name = "sw-tender-editor"
6
- description = "Mechanical file worker for the sw-discover-tender skill — extracts requirement tables from a tender's source export into cluster files, merges PM/architect/QA cluster reports into finished §4 row blocks, splices those blocks into the analysis sidecar, and assembles and checks the final response CSVs. Deterministic, no judgment: every value it writes comes from a named input file, never from memory or estimation. Not for research, feasibility checks, effort estimation or any judgment call — that is `sw-product-manager`, `sw-shopware-architect` and `sw-qa-engineer`'s work; this agent only reshapes what they already produced into the file the skill needs next."
6
+ description = "Mechanical file worker for the sw-discover-tender skill — extracts requirement tables from a tender's source export into cluster files, merges PM/architect/QA cluster reports into finished §4 row blocks, splices those blocks into the analysis sidecar, and assembles and checks the final response CSVs. Deterministic, no judgment — every value it writes comes from a named input file, never from memory or estimation. Not for research, feasibility checks, effort estimation or any judgment call — that is `sw-product-manager`, `sw-shopware-architect` and `sw-qa-engineer`'s work; this agent only reshapes what they already produced into the file the skill needs next."
7
7
 
8
8
  developer_instructions = '''
9
9
 
package/content/AGENTS.md CHANGED
@@ -16,7 +16,7 @@ procedure, and the detail lives in `reference/*.md` files beside its
16
16
  | `sw-discover-tender` | Analyse a client tender (RFP, RFI or RFQ) for a Shopware 6 project into one markdown sidecar, <source basename>-analysis.md. |
17
17
  | `sw-document-feature` | Document one Shopware project feature into the LLM-wiki under docs/project-wiki/ — one feature page per surface (administration/ and/or storefront/), the "why" row in the domain index, ADR pages only promoted from the WIP ADR files sw-design-solution extracted into specs/ once they are accepted and built (with an organised table of contents) or provided by the user (never invented). |
18
18
  | `sw-implement-feature` | Implement a feature from its technical spec (specs/NNNN-slug-spec.md, written by sw-design-solution) following TDD — tests first per acceptance criterion, then the implementation, fixing until every relevant test passes (including that AC's own acceptance/e2e test, run immediately, not deferred), running Shopware's own shopware-cli fixers/static-analysis per AC to conform to Shopware's coding standards as code is written rather than after the fact. |
19
- | `sw-setup` | Print one environment-readiness table for the project's development/test setup — containers, vendor/, Node, the editor CLIs, shopware-cli, the KB MCP, the acceptance-test project, Playwright browsers, its .env, per-plugin test scaffolding, the project wiki and `.gitignore`, plus the installer CLI's own configuration/drift status. |
19
+ | `sw-setup` | Print one environment-readiness table for the project's development/test setup — vendor/, Node, the editor CLIs, shopware-cli, the KB MCP, the acceptance-test project, Playwright browsers, its .env, per-plugin test scaffolding, the project wiki and `.gitignore`, plus the installer CLI's own configuration/drift status. |
20
20
  | `sw-verify-feature` | Verify a feature implementation against its technical spec (specs/NNNN-slug-spec.md) by running three independent verifiers in parallel — AC test coverage/health, architecture/guideline compliance, and code quality/static analysis — then cross-checking their verdicts into one final per-AC pass/not pass/partly report. |
21
21
  | `sw-verify-feature-ac-tests` | Verify that a feature's acceptance criteria are backed by tests that actually exist, actually pass, and actually test what their name/comment/PHPDoc claims. |
22
22
  | `sw-verify-feature-architecture` | Verify that a feature's implementation matches the architectural decisions its spec made, and complies with Shopware's own Core conventions plus any written Project guidelines this repo defines. |
@@ -45,7 +45,7 @@ and installs the harness configuration — permission rules and the MCP
45
45
  server registrations — through the installer CLI:
46
46
 
47
47
  ```
48
- npx -y @execuro-sw-ecosystem/sw-ecosystem-agentic-harness@0.1.0 status
48
+ npx -y @execuro-sw-ecosystem/sw-ecosystem-agentic-harness@latest status
49
49
  ```
50
50
 
51
51
  The Shopware knowledge base is served by the `ShopwareDevKnowledgeBase`
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: "sw-php-backend-developer"
3
- description: "Shopware 6 PHP backend specialist. Use PROACTIVELY for any Shopware backend work — plugin/app PHP, DAL entities & migrations, Store/Admin API routes, services & decorators, event subscribers, flow actions, message handlers, caching, ACL. Triggers: \"add an entity\", \"create an API endpoint\", \"extend checkout/order/product logic\", \"write a migration\", \"implement backend AC\". Not for Administration (Vue) or Storefront (Twig/JS) work."
3
+ description: "Shopware 6 PHP backend specialist. Use PROACTIVELY for any Shopware backend work — plugin/app PHP, DAL entities & migrations, Store/Admin API routes, services & decorators, event subscribers, flow actions, message handlers, caching, ACL. Triggers — \"add an entity\", \"create an API endpoint\", \"extend checkout/order/product logic\", \"write a migration\", \"implement backend AC\". Not for Administration (Vue) or Storefront (Twig/JS) work."
4
4
  tools: [read, edit, search, execute]
5
5
  ---
6
6
 
7
7
  <!-- Generated by scripts/gen-copilot-agents.mjs — do not edit. -->
8
8
  <!-- source: agents/sw-php-backend-developer.md -->
9
- <!-- source-sha256: 1d248ea38a0118ce2ebcea32f8dc780934bb6228e29c1b034baa1b3c43e102ea -->
9
+ <!-- source-sha256: ea82533b10d08e276f3c82da3555fd49f66b4141850990bca3e7e878fe07df61 -->
10
10
  # PHP Backend Developer (Shopware 6)
11
11
 
12
12
  ## Identity
@@ -5,7 +5,7 @@ description: "Use PROACTIVELY for any Shopware 6 Storefront work - Twig template
5
5
 
6
6
  <!-- Generated by scripts/gen-copilot-agents.mjs — do not edit. -->
7
7
  <!-- source: agents/sw-storefront-developer.md -->
8
- <!-- source-sha256: 3d2ee9b1f1691eebedd1abaaf7126b55d5ea74677acda6a3f19765490ed9e6ce -->
8
+ <!-- source-sha256: 9afbeda4ca95afe1d74bf0268c3551031a519524fd2d1c4d2c4ece874cd6c054 -->
9
9
  # Shopware Storefront Developer
10
10
 
11
11
  ## Role
@@ -42,7 +42,7 @@ An edit is not done until `lint:twig` passes on it and the changed markup is obs
42
42
  - Lighthouse on touched pages; perf/a11y/SEO regressions are failures.
43
43
  - Playwright MCP (`mcp__playwright__*`) for click-through and screenshots. Shopware docs MCP (`mcp__ShopwareDevKnowledgeBase__*`) for the exact core template, block, route or plugin before extending; never guess a block name.
44
44
  - Test the targeted state: logged-out, logged-in, guest checkout, plus a non-default currency or language when prices or text change.
45
- - "Does not show up" checklist, in order: template path, block name, theme assigned, theme compiled, cache cleared, browser cache.
45
+ - "Does not show up" checklist, in order: template path (not a deprecated forwarder file), block name in that file (and its `feature()` branch), theme assigned, theme compiled, cache cleared, browser cache.
46
46
 
47
47
  ### Small, reversible
48
48
  - One concern per template, subscriber or plugin; header + footer + checkout is three changes.
@@ -57,14 +57,14 @@ An edit is not done until `lint:twig` passes on it and the changed markup is obs
57
57
 
58
58
  1. **Classify.** Confirm storefront work (see Boundary). If mixed, split and hand off the non-storefront part first.
59
59
  2. **Locate — prove it, never assume.** Docs MCP answers *what an extension point is for*; only the installed source answers *whether it exists here*. Produce all four proofs before writing a single `{% block %}`:
60
- 1. The target template exists at the exact relative path: `ls vendor/shopware/storefront/Resources/views/storefront/<rel>`.
61
- 2. The block name exists verbatim **in that file**: `grep -n "{% block <name>" vendor/shopware/storefront/Resources/views/storefront/<rel>`. Block names are not unique across the storefront (~2200 block tags, ~2035 distinct names — every override re-declares its parent's name), so a repo-wide grep for a name is not proof. If it is not in that file, it lives in an included sub-template: find that file and override it instead.
62
- 3. Every other bundle already providing the same relative path: `grep -rl "<rel>" custom/ vendor/*/*/Resources/views/`. Name who wins under the slot order above, and whether they call `{{ parent() }}`.
60
+ 1. The target template exists at the exact relative path: `ls vendor/shopware/storefront/Resources/views/storefront/<rel>` — and its first lines carry no `@deprecated … File will be removed` header: such a forwarder is never loaded, extend the file it names instead.
61
+ 2. The block name exists verbatim **in that file**: `grep -n "{% block <name>" vendor/shopware/storefront/Resources/views/storefront/<rel>`. Block names are not unique across the storefront (~2200 block tags, ~2035 distinct names — every override re-declares its parent's name), so a repo-wide grep for a name is not proof. If it is not in that file, it lives in an included sub-template: find that file and override it instead. Note the `feature()` branch the block sits in, if any — the guideline's template-inheritance section says which anchor to target.
62
+ 3. Every other bundle already providing the same relative path: `grep -rl "<rel>" custom/ vendor/*/*/Resources/views/`, plus the active theme's chain: `grep -A4 '"views"' custom/*/*/src/Resources/theme.json`. Name who wins under the chain order in the guideline's template-inheritance section, and whether they call `{{ parent() }}`.
63
63
  4. When the dev env is up, `shopware-cli project console debug:twig @Storefront/storefront/<rel>` prints "Matched File" and "Overridden Files" — the real resolution, and it overrides 1–3. Caveat: the theme slot is filled per storefront request, so a CLI run may show only the `@Storefront`/`@Plugins` part of the chain; the browser is the final word on which theme override wins.
64
64
  A block name recalled from memory or from an older Shopware version is a defect, not a starting point.
65
65
  3. **Place.** Theme (look, Bootstrap variables) vs plugin (features).
66
66
  4. **Test first** where tooling supports it: Jest for JS plugins, functional/e2e for rendered HTML. Failing test before the extension.
67
- 5. **Implement** the smallest extension that passes: one block, one subscriber, one plugin.
67
+ 5. **Implement** the smallest extension that passes: one block, one subscriber, one plugin — placed per the guideline's override-placement ladder (edit an existing project override rather than adding a second file for the same path; go later in the chain than a marketplace override).
68
68
  6. **Lint and build.** `lint:twig` on every touched Twig file first, then `extension validate` for SCSS/JS, then the cache/theme/build sequence — all per "Verify: lint, build, render".
69
69
  7. **Verify.** Render assertion first (the changed markup is actually in the page), then browser pass (Playwright MCP), console/network clean, breakpoints, keyboard, Lighthouse.
70
70
  8. **Report** in the format below.
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: "sw-tender-editor"
3
- description: "Mechanical file worker for the sw-discover-tender skill — extracts requirement tables from a tender's source export into cluster files, merges PM/architect/QA cluster reports into finished §4 row blocks, splices those blocks into the analysis sidecar, and assembles and checks the final response CSVs. Deterministic, no judgment: every value it writes comes from a named input file, never from memory or estimation. Not for research, feasibility checks, effort estimation or any judgment call — that is `sw-product-manager`, `sw-shopware-architect` and `sw-qa-engineer`'s work; this agent only reshapes what they already produced into the file the skill needs next."
3
+ description: "Mechanical file worker for the sw-discover-tender skill — extracts requirement tables from a tender's source export into cluster files, merges PM/architect/QA cluster reports into finished §4 row blocks, splices those blocks into the analysis sidecar, and assembles and checks the final response CSVs. Deterministic, no judgment — every value it writes comes from a named input file, never from memory or estimation. Not for research, feasibility checks, effort estimation or any judgment call — that is `sw-product-manager`, `sw-shopware-architect` and `sw-qa-engineer`'s work; this agent only reshapes what they already produced into the file the skill needs next."
4
4
  tools: [read, edit, search]
5
5
  ---
6
6
 
7
7
  <!-- Generated by scripts/gen-copilot-agents.mjs — do not edit. -->
8
8
  <!-- source: agents/sw-tender-editor.md -->
9
- <!-- source-sha256: 72f167887e7d97164c0c7bb2ed03e23dd041fb868746857744bcd255909a03d5 -->
9
+ <!-- source-sha256: 288b5d684f40ea62aa2b6021cfa3109a7f664fc2365f32109bcd9d1f94eb8a2c -->
10
10
  # sw-tender-editor
11
11
 
12
12
  ## Identity
package/lib/actions.mjs CHANGED
@@ -14,13 +14,13 @@ import {
14
14
  abs, ensureDir, readMaybe, readTextMaybe, removeDirIfEmpty, removeFile, rel, sha256, writeAtomic,
15
15
  } from './fsx.mjs';
16
16
  import {
17
- canonical, deletePointer, fenceInner, findFence, getPointer, mergeArrayUnique, parseJsonLoose,
18
- removeArrayValues, removeFence, serializeJson, setPointer, upsertFence,
17
+ canonical, deletePointer, fenceInner, findFence, getPointer, mergeArrayUnique, normalizeEol,
18
+ parseJsonLoose, removeArrayValues, removeFence, serializeJson, setPointer, upsertFence,
19
19
  } from './merge.mjs';
20
20
  import { PKG_ROOT } from './content.mjs';
21
21
  import { isDeclined } from './lock.mjs';
22
22
  import { readFileSync } from 'node:fs';
23
- import { COMPANIONS, probe } from './companions.mjs';
23
+ import { EXTRA_COMPONENTS, probe } from './extra-components.mjs';
24
24
 
25
25
  /** Preview one action against the filesystem. Reads only. */
26
26
  export function preview(action, ctx) {
@@ -83,8 +83,8 @@ function stripProbeBody({ body, ...rest }) {
83
83
  }
84
84
 
85
85
  function previewSkill(action, ctx) {
86
- const companion = COMPANIONS.find((c) => c.id === action.companion);
87
- const probed = probe(companion, { platform: ctx.platform });
86
+ const component = EXTRA_COMPONENTS.find((c) => c.id === action.component);
87
+ const probed = probe(component, { platform: ctx.platform });
88
88
  const probeSummary = stripProbeBody(probed);
89
89
 
90
90
  if (!probed.available) {
@@ -226,8 +226,9 @@ function previewFence(action, ctx) {
226
226
  const found = readTextMaybe(target);
227
227
  const createdFile = found === null;
228
228
  // Hash what will actually sit inside the fence, warning line included, so it
229
- // can be compared with what `findFence` reads back.
230
- const hash = sha256(fenceInner(action.body, action.syntax));
229
+ // can be compared with what `findFence` reads back. Normalised to LF first —
230
+ // a fence's identity does not depend on the target file's line-ending style.
231
+ const hash = sha256(normalizeEol(fenceInner(action.body, action.syntax)));
231
232
  const locked = ctx.lock?.fences?.find(
232
233
  (f) => f.file === action.target && f.block === action.block,
233
234
  );
@@ -243,7 +244,7 @@ function previewFence(action, ctx) {
243
244
  return { ...base, prior: null, state: 'create',
244
245
  detail: createdFile ? 'creates the file with a managed block' : 'appends a managed block' };
245
246
  }
246
- const priorHash = sha256(existing.inner);
247
+ const priorHash = sha256(normalizeEol(existing.inner));
247
248
  if (priorHash === hash) return { ...base, prior: priorHash, state: 'unchanged', created_file: locked?.created_file ?? false, detail: 'up to date' };
248
249
  if (locked && locked.hash !== priorHash) {
249
250
  return { ...base, prior: priorHash, state: 'drift', created_file: locked.created_file,
@@ -276,7 +277,7 @@ export function execute(action, ctx) {
276
277
  }
277
278
  case 'skill-copy': {
278
279
  // Collapses to a `file-copy` lock entry on purpose: `lock.record`,
279
- // `lock.entries`, `revert` and `rebuild` need no companion-specific
280
+ // `lock.entries`, `revert` and `rebuild` need no extra-component-specific
280
281
  // case, and `uninstall` needs no probe. `source` is provenance only —
281
282
  // `revert` never reads it.
282
283
  ensureDir(dirname(target));
@@ -401,7 +402,7 @@ export function revert(entry, ctx) {
401
402
  if (found === null) return { state: 'missing', target: entry.target, reason: 'already gone' };
402
403
  const fence = findFence(found.text, entry.block);
403
404
  if (!fence) return { state: 'missing', target: entry.target, reason: 'our block is already gone' };
404
- if (sha256(fence.inner) !== entry.hash) {
405
+ if (sha256(normalizeEol(fence.inner)) !== entry.hash) {
405
406
  return { state: 'kept', target: entry.target, reason: 'content inside the managed fence was hand-edited' };
406
407
  }
407
408
  const { next } = removeFence(found.text, entry.block);
package/lib/commands.mjs CHANGED
@@ -14,7 +14,7 @@ import { exists, isWritable, abs } from './fsx.mjs';
14
14
  import { HOSTS, buildPlan, detectAll, installedCounts } from './hosts.mjs';
15
15
  import * as lockfile from './lock.mjs';
16
16
  import { dedupe, execute, isWritableState, preview, pruneDirs, revert } from './actions.mjs';
17
- import { probeAll } from './companions.mjs';
17
+ import { probeAll } from './extra-components.mjs';
18
18
 
19
19
  const RUN = 'sw-ecosystem-agentic-harness';
20
20
 
@@ -29,12 +29,12 @@ export function buildContext(argv, command) {
29
29
  let root = process.cwd();
30
30
  let yes = false;
31
31
  let projectWiki;
32
- let companions = true;
32
+ let extraComponents = true;
33
33
 
34
34
  for (let i = 0; i < argv.length; i += 1) {
35
35
  const arg = argv[i];
36
36
  if (arg === '--yes' || arg === '-y') { yes = true; continue; }
37
- if (arg === '--no-companions') { companions = false; continue; }
37
+ if (arg === '--no-extra-components') { extraComponents = false; continue; }
38
38
  if (arg === '--host') {
39
39
  const value = argv[++i];
40
40
  if (!HOSTS.includes(value)) {
@@ -64,7 +64,7 @@ export function buildContext(argv, command) {
64
64
  if (!exists(root)) throw new UsageError(`--root does not exist: ${root}`, [`${RUN} ${command} --root .`]);
65
65
 
66
66
  return {
67
- root, scope, yes, projectWiki, companions,
67
+ root, scope, yes, projectWiki, extraComponents,
68
68
  hosts: hosts.length ? [...new Set(hosts)] : [...HOSTS],
69
69
  platform: process.platform,
70
70
  version: PKG_VERSION,
@@ -168,7 +168,7 @@ export async function status(argv) {
168
168
  const worst = ['conflict', 'drift', 'not-installed', 'ok', 'absent']
169
169
  .find((s) => hosts.some((h) => h.state === s)) ?? 'absent';
170
170
 
171
- const companions = ctx.companions === false ? [] : probeAll({ platform: ctx.platform }).map((p) => ({
171
+ const extraComponents = ctx.extraComponents === false ? [] : probeAll({ platform: ctx.platform }).map((p) => ({
172
172
  id: p.id, package: p.pkg, version: p.version, label: p.label,
173
173
  available: p.available, probe: p.state, reason: p.reason ?? null,
174
174
  installed_for: Object.entries(lock?.files ?? {})
@@ -180,10 +180,10 @@ export async function status(argv) {
180
180
  let next = pending
181
181
  ? `Run \`${RUN} plan\` to see the ${pending} change(s) needed, then \`${RUN} apply --yes\`.`
182
182
  : `Nothing to do — the harness is installed and up to date. Run \`${RUN} plan\` any time to re-check.`;
183
- if (ctx.companions !== false) {
184
- const unavailable = companions.filter((c) => !c.available);
183
+ if (ctx.extraComponents !== false) {
184
+ const unavailable = extraComponents.filter((c) => !c.available);
185
185
  if (unavailable.length) {
186
- next += ` ${unavailable.length} optional companion package(s) are not on this machine — install with ${unavailable.map((c) => `\`${c.install}\``).join(', ')} to enable their skills.`;
186
+ next += ` ${unavailable.length} optional extra component(s) are not on this machine — install with ${unavailable.map((c) => `\`${c.install}\``).join(', ')} to enable their skills.`;
187
187
  }
188
188
  }
189
189
 
@@ -193,7 +193,7 @@ export async function status(argv) {
193
193
  next_step: next,
194
194
  help: [`${RUN} plan`, `${RUN} apply --yes`, `${RUN} guide`],
195
195
  inventory: { skills: skills().length, agents: agents().length },
196
- hosts, companions, drift,
196
+ hosts, extra_components: extraComponents, drift,
197
197
  declined: lock?.declined ?? [],
198
198
  manual: lock?.manual ?? [],
199
199
  errors,
@@ -245,17 +245,17 @@ const RESTART_HELP = {
245
245
  cursor: 'Restart Cursor so it re-reads .cursor/mcp.json',
246
246
  };
247
247
 
248
- export async function apply(argv) {
248
+ export async function apply(argv, label = 'apply') {
249
249
  const ctx = buildContext(argv, 'apply');
250
250
  if (!ctx.yes) {
251
- return out.usage('apply', 'apply refuses to write without --yes',
251
+ return out.usage(label, 'apply refuses to write without --yes',
252
252
  [`${RUN} plan`, `${RUN} apply --yes`]);
253
253
  }
254
254
 
255
255
  let lock;
256
256
  try { lock = loadLock(ctx) ?? lockfile.empty(ctx.scope); } catch (e) {
257
257
  if (e instanceof lockfile.LockVersionError) {
258
- return out.failure('apply', e.message, {
258
+ return out.failure(label, e.message, {
259
259
  next_step: `Upgrade the package: npm i -D ${PKG_NAME}@latest`,
260
260
  help: [`npm i -D ${PKG_NAME}@latest`],
261
261
  });
@@ -266,7 +266,7 @@ export async function apply(argv) {
266
266
  // An apply we cannot record is an apply we cannot undo. Refuse up front
267
267
  // rather than leave the user with files and no way back.
268
268
  if (!isWritable(abs(ctx.root, LOCK_FILE))) {
269
- return out.failure('apply', `cannot write the lock file at ${LOCK_FILE}`, {
269
+ return out.failure(label, `cannot write the lock file at ${LOCK_FILE}`, {
270
270
  next_step: `Make ${ctx.root} writable, then re-run \`${RUN} apply --yes\`.`,
271
271
  help: [`${RUN} status`],
272
272
  });
@@ -345,9 +345,12 @@ export async function apply(argv) {
345
345
  summary, lock_file: LOCK_FILE, next_step: next, help,
346
346
  hosts: [...perHost.values()], actions: results, manual, errors,
347
347
  });
348
- return summary.failed ? out.failure('apply', `${summary.failed} change(s) failed`, body) : out.ok('apply', body);
348
+ return summary.failed ? out.failure(label, `${summary.failed} change(s) failed`, body) : out.ok(label, body);
349
349
  }
350
350
 
351
+ /** `install` is `apply --yes`: typing the verb is the consent. */
352
+ export const install = (argv) => apply(['--yes', ...argv], 'install');
353
+
351
354
  // --------------------------------------------------------------- uninstall
352
355
 
353
356
  export async function uninstall(argv) {