@execuro-sw-ecosystem/sw-ecosystem-agentic-harness 0.1.2 → 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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,45 @@ 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
+
10
49
  ## [0.1.2] - 2026-09-16
11
50
 
12
51
  ### Changed
package/README.md CHANGED
@@ -50,7 +50,7 @@ declines is fully set up without them, and `--editor` on `sw-design-requirements
50
50
  | Command | What it does |
51
51
  | --- | --- |
52
52
  | `install` | installs into every detected host; the same as `apply --yes` |
53
- | `status` | read-only; what is installed, what drifted, which optional companion packages are present |
53
+ | `status` | read-only; what is installed, what drifted, which optional extra components are present |
54
54
  | `plan` | the exact changes, written as a diff; writes nothing |
55
55
  | `apply --yes` | performs them |
56
56
  | `uninstall --yes` | removes what the lock file records |
@@ -60,7 +60,7 @@ Flags: `--host claude-code|codex|copilot|cursor` (repeatable; omitting it
60
60
  selects every host that is present), `--scope project|user` (default
61
61
  `project`), `--root <path>` (default the current directory), `--yes` (required
62
62
  by `apply` and `uninstall`, never prompts — `install` implies it), and
63
- `--no-companions` (skip detection of the optional editor packages).
63
+ `--no-extra-components` (skip detection of the optional editor packages).
64
64
 
65
65
  ## Install
66
66
 
@@ -80,26 +80,6 @@ not block outside Plan mode, and CI has no terminal. Add `--host claude-code`
80
80
  home directory instead of this repository. The default scope is the project —
81
81
  nothing is written under `~` unless you ask.
82
82
 
83
- ## Keep up to date
84
-
85
- The same command updates:
86
-
87
- ```bash
88
- npx -y @execuro-sw-ecosystem/sw-ecosystem-agentic-harness@latest install
89
- ```
90
-
91
- It rewrites what it owns, leaves anything you hand-edited alone (reported as
92
- drift), and records the new version in the lock file. The `@latest` matters:
93
- without it, `npx` can reuse a copy already in its cache or in this project's
94
- `node_modules`, so you would silently keep running whatever version you first
95
- installed. Restart your coding agent afterwards, same as a first install.
96
-
97
- Want to check what you have first? `... @latest status` reports
98
- `installed_version` and any drift, and writes nothing. The Specs Editor and
99
- Tender Discovery Tool are separate packages and update on their own `npm i -D
100
- <package>@<version>` line — `status`'s `companions` array names the exact
101
- command for each.
102
-
103
83
  ### What it writes
104
84
 
105
85
  | Host | Skills | Sub-agents | MCP | Settings |
@@ -145,6 +125,48 @@ protocol runs:
145
125
  npx -y @execuro-sw-ecosystem/sw-ecosystem-agentic-harness@latest guide
146
126
  ```
147
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
+
148
170
  ## Prerequisites
149
171
 
150
172
  | Requirement | Why | Check |
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
 
@@ -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.
package/bin/cli.mjs CHANGED
@@ -28,10 +28,10 @@ const COMMANDS = {
28
28
 
29
29
  const USAGE = `usage: sw-ecosystem-agentic-harness <command> [options]
30
30
 
31
- install [--host <h>] [--scope project|user] [--root <path>] [--no-companions]
31
+ install [--host <h>] [--scope project|user] [--root <path>] [--no-extra-components]
32
32
  status [--host <h>] [--scope project|user] [--root <path>]
33
- plan [--host <h>] [--scope project|user] [--root <path>] [--no-companions]
34
- 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]
35
35
  uninstall --yes [--host <h>] [--scope project|user] [--root <path>]
36
36
  guide print the install protocol
37
37
 
@@ -44,7 +44,7 @@ const USAGE = `usage: sw-ecosystem-agentic-harness <command> [options]
44
44
  --scope defaults to project: nothing is written under your home directory
45
45
  unless you ask for it. --root defaults to the current directory.
46
46
 
47
- --no-companions skips the two optional companion-package skills (Specs
47
+ --no-extra-components skips the two optional extra-component skills (Specs
48
48
  Editor, Tender Discovery Tool) even when the package is present locally.
49
49
 
50
50
  Every command prints one JSON object on stdout carrying a next_step. Run
@@ -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.
@@ -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.
package/lib/actions.mjs CHANGED
@@ -20,7 +20,7 @@ import {
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) {
@@ -277,7 +277,7 @@ export function execute(action, ctx) {
277
277
  }
278
278
  case 'skill-copy': {
279
279
  // Collapses to a `file-copy` lock entry on purpose: `lock.record`,
280
- // `lock.entries`, `revert` and `rebuild` need no companion-specific
280
+ // `lock.entries`, `revert` and `rebuild` need no extra-component-specific
281
281
  // case, and `uninstall` needs no probe. `source` is provenance only —
282
282
  // `revert` never reads it.
283
283
  ensureDir(dirname(target));
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,
@@ -1,13 +1,13 @@
1
- // The two optional companion packages, probed rather than installed by us.
1
+ // The two optional extra components, probed rather than installed by us.
2
2
  //
3
3
  // Read-then-copy, not `install-skill --target`: the engine's whole safety
4
4
  // story is "hash what we intend to write, compare, record". A foreign binary
5
5
  // that writes on its own cannot be previewed, so `plan` would lie and
6
6
  // `uninstall` would have to guess. Reading the body with `--print` and
7
- // copying it ourselves through `writeAtomic` keeps every companion skill on
8
- // the same adopt/conflict/drift/update ladder as everything else this CLI
9
- // writes, and makes `uninstall` exact even after the user has removed the
10
- // companion package — the common case, since it is optional.
7
+ // copying it ourselves through `writeAtomic` keeps every extra-component
8
+ // skill on the same adopt/conflict/drift/update ladder as everything else
9
+ // this CLI writes, and makes `uninstall` exact even after the user has
10
+ // removed the extra component — the common case, since it is optional.
11
11
  //
12
12
  // `--no-install` plus `npm_config_yes=false` are two independent guards that
13
13
  // this can never reach the network: the first tells npx not to fetch a
@@ -17,7 +17,7 @@ import { spawnSync } from 'node:child_process';
17
17
  import { sha256 } from './fsx.mjs';
18
18
  import { wrapCommand } from './content.mjs';
19
19
 
20
- export const COMPANIONS = [
20
+ export const EXTRA_COMPONENTS = [
21
21
  {
22
22
  id: 'sw-specs-editor', pkg: '@execuro-sw-ecosystem/sw-specs-editor', version: '0.1.0',
23
23
  label: 'Specs Editor',
@@ -29,8 +29,8 @@ export const COMPANIONS = [
29
29
  ];
30
30
 
31
31
  /** The argv `probe` would spawn, without spawning it — for tests. */
32
- export function probeArgs(companion, platform) {
33
- const spec = `${companion.pkg}@${companion.version}`;
32
+ export function probeArgs(component, platform) {
33
+ const spec = `${component.pkg}@${component.version}`;
34
34
  const wrapped = wrapCommand(
35
35
  { command: 'npx', args: ['--no-install', spec, 'install-skill', '--print'] },
36
36
  platform,
@@ -51,9 +51,9 @@ function lastLine(text) {
51
51
  return lines.length ? lines[lines.length - 1] : '';
52
52
  }
53
53
 
54
- function doProbe(companion, platform) {
55
- const spec = `${companion.pkg}@${companion.version}`;
56
- const base = { id: companion.id, pkg: companion.pkg, version: companion.version, spec, label: companion.label };
54
+ function doProbe(component, platform) {
55
+ const spec = `${component.pkg}@${component.version}`;
56
+ const base = { id: component.id, pkg: component.pkg, version: component.version, spec, label: component.label };
57
57
  const wrapped = wrapCommand(
58
58
  { command: 'npx', args: ['--no-install', spec, 'install-skill', '--print'] },
59
59
  platform,
@@ -103,15 +103,15 @@ function doProbe(companion, platform) {
103
103
  return { ...base, available: true, state: 'available', body: stdout, hash: sha256(stdout), bytes: stdout.length };
104
104
  }
105
105
 
106
- /** Probe one companion. Never throws. */
107
- export function probe(companion, { platform = process.platform } = {}) {
108
- const key = `${companion.pkg}@${companion.version}`;
106
+ /** Probe one extra component. Never throws. */
107
+ export function probe(component, { platform = process.platform } = {}) {
108
+ const key = `${component.pkg}@${component.version}`;
109
109
  if (cache.has(key)) return cache.get(key);
110
- const result = doProbe(companion, platform);
110
+ const result = doProbe(component, platform);
111
111
  cache.set(key, result);
112
112
  return result;
113
113
  }
114
114
 
115
115
  export function probeAll(opts) {
116
- return COMPANIONS.map((c) => probe(c, opts));
116
+ return EXTRA_COMPONENTS.map((c) => probe(c, opts));
117
117
  }
package/lib/guide.mjs CHANGED
@@ -6,8 +6,10 @@
6
6
 
7
7
  import * as out from './out.mjs';
8
8
  import { PKG_NAME, PKG_VERSION } from './content.mjs';
9
+ import { EXTRA_COMPONENTS } from './extra-components.mjs';
9
10
 
10
11
  const RUN = `npx -y ${PKG_NAME}@${PKG_VERSION}`;
12
+ const EXTRA_COMPONENT_INSTALLS = EXTRA_COMPONENTS.map((c) => `npm i -D ${c.pkg}@${c.version}`).join('\n');
11
13
 
12
14
  export const GUIDE = `# ${PKG_NAME} — install protocol
13
15
 
@@ -44,17 +46,22 @@ registers the MCP servers and permission rules they depend on.
44
46
  --host claude-code|codex|copilot|cursor restrict to one host, repeatable
45
47
  --scope project|user default: project
46
48
  --root <path> default: the current directory
47
- --no-companions skip the optional companion-package skills
49
+ --no-extra-components skip the optional extra-component skills
48
50
 
49
- ## Optional companion packages
51
+ ## Optional extra components
50
52
 
51
53
  Two skills — Specs Editor and Tender Discovery Tool — ship inside their own
52
54
  npm packages, not this one. \`plan\`/\`apply\` detect them offline
53
- (\`npx --no-install\`) and never fetch them. A \`skipped\` action means the
54
- package is not on this machine yet: install it with
55
- \`npm i -D <package>@<version>\` (the exact command is in the action's
56
- \`remedy\`, and in \`status\`'s \`companions\` array), then re-run
57
- \`${RUN} apply --yes\`.
55
+ (\`npx --no-install\`) and never fetch them, probing for the exact version
56
+ this release pins — any other version, including \`@latest\`, is treated as
57
+ absent and the skill is skipped:
58
+
59
+ \`\`\`bash
60
+ ${EXTRA_COMPONENT_INSTALLS}
61
+ \`\`\`
62
+
63
+ The exact command is also in a \`skipped\` action's \`remedy\`, and in
64
+ \`status\`'s \`extra_components\` array. Install it, then re-run \`${RUN} apply --yes\`.
58
65
 
59
66
  ## What you can rely on
60
67
 
package/lib/hosts.mjs CHANGED
@@ -14,12 +14,12 @@ import {
14
14
  import { abs, isDir, isWritable, walk } from './fsx.mjs';
15
15
  import { renderTomlTable, tomlTableExists, findFence } from './merge.mjs';
16
16
  import { readTextMaybe } from './fsx.mjs';
17
- import { COMPANIONS } from './companions.mjs';
17
+ import { EXTRA_COMPONENTS } from './extra-components.mjs';
18
18
 
19
19
  export const HOSTS = ['claude-code', 'codex', 'copilot', 'cursor'];
20
20
 
21
21
  /** Where each host reads its skills from — the base every skill-copy and
22
- * companion skill-copy action is rooted at. */
22
+ * extra-component skill-copy action is rooted at. */
23
23
  export const SKILL_DIR = {
24
24
  'claude-code': '.claude/skills',
25
25
  codex: '.agents/skills',
@@ -103,16 +103,16 @@ function skillActions(host, targetBase) {
103
103
  }
104
104
 
105
105
  /**
106
- * One `skill-copy` action per optional companion package. The `skill:`
106
+ * One `skill-copy` action per optional extra component. The `skill:`
107
107
  * segment (not `file:`) keeps these ids from colliding, or deduping,
108
108
  * against a shipped `file-copy` action at the same path.
109
109
  */
110
- function companionActions(host, targetBase) {
111
- return COMPANIONS.map((c) => {
110
+ function extraComponentActions(host, targetBase) {
111
+ return EXTRA_COMPONENTS.map((c) => {
112
112
  const target = `${targetBase}/${c.id}/SKILL.md`;
113
113
  return {
114
114
  id: `${host}:skill:${target}`, host, kind: 'skill-copy',
115
- companion: c.id, package: `${c.pkg}@${c.version}`, target,
115
+ component: c.id, package: `${c.pkg}@${c.version}`, target,
116
116
  };
117
117
  });
118
118
  }
@@ -158,7 +158,7 @@ function planClaude(ctx) {
158
158
  const host = 'claude-code';
159
159
  const out = [
160
160
  ...skillActions(host, SKILL_DIR[host]),
161
- ...(ctx.companions === false ? [] : companionActions(host, SKILL_DIR[host])),
161
+ ...(ctx.extraComponents === false ? [] : extraComponentActions(host, SKILL_DIR[host])),
162
162
  ...agentActions(host, '.claude/agents', 'claude', '.md'),
163
163
  ...mcpJsonActions(host, '.mcp.json', 'mcpServers', ctx),
164
164
  ];
@@ -184,7 +184,7 @@ function planCodex(ctx) {
184
184
  const out = [
185
185
  // Codex discovers skills from `.agents/skills`, never `.claude/skills`.
186
186
  ...skillActions(host, SKILL_DIR[host]),
187
- ...(ctx.companions === false ? [] : companionActions(host, SKILL_DIR[host])),
187
+ ...(ctx.extraComponents === false ? [] : extraComponentActions(host, SKILL_DIR[host])),
188
188
  ...agentActions(host, '.codex/agents', 'codex', '.toml'),
189
189
  ];
190
190
 
@@ -229,7 +229,7 @@ function planCopilot(ctx) {
229
229
  const host = 'copilot';
230
230
  const out = [
231
231
  ...skillActions(host, SKILL_DIR[host]),
232
- ...(ctx.companions === false ? [] : companionActions(host, SKILL_DIR[host])),
232
+ ...(ctx.extraComponents === false ? [] : extraComponentActions(host, SKILL_DIR[host])),
233
233
  ...agentActions(host, '.github/agents', 'copilot', '.agent.md'),
234
234
  // Copilot CLI reads `.mcp.json`; VS Code reads `.vscode/mcp.json` under the
235
235
  // incompatible `servers` key. Both, or the two surfaces disagree.
@@ -256,7 +256,7 @@ function planCursor(ctx) {
256
256
  || (isDir(abs(ctx.root, '.claude/skills')) && isDir(abs(ctx.root, '.claude/agents')));
257
257
  if (!claudeTrees) {
258
258
  out.push(...skillActions(host, SKILL_DIR[host]));
259
- if (ctx.companions !== false) out.push(...companionActions(host, SKILL_DIR[host]));
259
+ if (ctx.extraComponents !== false) out.push(...extraComponentActions(host, SKILL_DIR[host]));
260
260
  out.push(...agentActions(host, '.cursor/agents', 'claude', '.md'));
261
261
  }
262
262
  out.push(...mcpJsonActions(host, '.cursor/mcp.json', 'mcpServers', ctx));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@execuro-sw-ecosystem/sw-ecosystem-agentic-harness",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Shopware 6 agentic harness: sw-* skills and sub-agents, plus an installer that writes them into Claude Code, Codex, GitHub Copilot and Cursor.",
5
5
  "license": "MIT",
6
6
  "author": "Execuro UG (haftungsbeschränkt)",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "sw-ecosystem-agentic-harness",
4
- "version": "0.1.2",
4
+ "version": "0.1.3",
5
5
  "description": "Shopware 6 agentic harness: sw-* skills and sub-agents for requirements, specs, implementation, verification and documentation, with an installer CLI that writes them into Claude Code, Codex, GitHub Copilot and Cursor. The knowledge-base MCP and the editor tools are separate npm packages.",
6
6
  "author": {
7
7
  "name": "Execuro UG (haftungsbeschränkt)",
@@ -55,6 +55,7 @@ one line per configuration item the CLI's `hosts[]`/`drift[]` reports:
55
55
 
56
56
  ```
57
57
  - [x] vendor/ — shopware/core 6.7.13.0
58
+ - [ ] vendor/ — vendor/bin/phpunit missing (tree built --no-dev)
58
59
  - [ ] Node — 18.x (need >= 20)
59
60
  - [-] Visual editors (optional) — Specs Editor not installed, Tender tool not installed
60
61
  - [x] shopware-cli — 0.16.10
@@ -15,6 +15,12 @@ reports their state in its own `status`/`plan` output; see `SKILL.md`.
15
15
  (guideline §6.2).
16
16
  - **Ticked:** all three present; report the `shopware/core` version from
17
17
  `composer.lock`.
18
+ - **Unticked:** the printed line names the specific missing path(s), not just
19
+ the row — e.g. a tree built `--no-dev` has `vendor/shopware/core` and
20
+ `composer.lock` but no `vendor/bin/phpunit`, so the line reads
21
+ `vendor/ — vendor/bin/phpunit missing (tree built --no-dev)`. This is the
22
+ same row and the same fix below, offered inside step 3's single question
23
+ like any other row.
18
24
  - **Fix:** `docker compose exec web composer install --no-interaction`
19
25
  (guideline §6.1 — "a vendor tree built with `--no-dev` has no
20
26
  `vendor/bin/phpunit`"). Runs inside the `web` container; if it is not
@@ -39,33 +45,33 @@ reports their state in its own `status`/`plan` output; see `SKILL.md`.
39
45
 
40
46
  The Specs Editor and the Tender Discovery Tool are **optional add-ons**, not
41
47
  part of this plugin. The installer CLI installs them itself, per host — this
42
- row never runs a companion package's own install command.
48
+ row never runs an extra component's own install command.
43
49
 
44
50
  So this row never blocks readiness. It asks.
45
51
 
46
- - **Check:** read `companions[]` from the `status` output the skill already
52
+ - **Check:** read `extra_components[]` from the `status` output the skill already
47
53
  ran in step 1 — each entry's `available`, `installed_for` (the hosts it is
48
54
  placed in), and `install` (the npm command, when missing). Never probe a
49
- path and never run a companion package by hand; the CLI owns both.
50
- - **Ticked:** `[x]` when a companion's `available` is true and
55
+ path and never run an extra component by hand; the CLI owns both.
56
+ - **Ticked:** `[x]` when an extra component's `available` is true and
51
57
  `installed_for` includes this host; `[-]` when the user has declined it.
52
58
  Only an unanswered offer shows `[ ]`, and even then it does not hold the
53
59
  table open — report it as optional and move on.
54
- - **Fix:** offer each missing companion inside step 3's single question,
60
+ - **Fix:** offer each missing extra component inside step 3's single question,
55
61
  naming what it buys, not how it works: "Specs Editor — review a PRD or
56
62
  tech spec on a live page, with notes, question answers and diagrams.
57
63
  Install?" — and "Tender Discovery Tool — read a client tender workbook,
58
64
  confirm its column mapping and review the analysis on a live page.
59
65
  Install?" On yes, for each accepted one: `npm i -D <package>@0.1.0` (the
60
- `package` field from `companions[]`), then re-run
66
+ `package` field from `extra_components[]`), then re-run
61
67
 
62
68
  ```
63
69
  npx -y @execuro-sw-ecosystem/sw-ecosystem-agentic-harness@latest apply --yes
64
70
  ```
65
71
 
66
- which places one `SKILL.md` per companion into each selected host's own
72
+ which places one `SKILL.md` per extra component into each selected host's own
67
73
  skills directory, records it, and removes it again on uninstall. **Never
68
- run the companion package's own `install-skill`** — with no `--target` it
74
+ run the extra component's own `install-skill`** — with no `--target` it
69
75
  defaults to Claude Code's own skills directory, wrong for a Codex, Copilot
70
76
  or Cursor install. Needs the Node row ticked.
71
77
  - **On no:** record the decline and do not re-ask on the next run. `apply`