mandrel 2.38.0 → 2.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.agents/README.md +51 -11
  2. package/.agents/agents/auditor.md +5 -0
  3. package/.agents/docs/SDLC.md +21 -12
  4. package/.agents/docs/agentrc-reference.json +1 -4
  5. package/.agents/docs/configuration.md +2 -2
  6. package/.agents/instructions.md +17 -16
  7. package/.agents/schemas/agentrc.schema.json +6 -7
  8. package/.agents/scripts/audit-to-stories.js +510 -66
  9. package/.agents/scripts/generate-skills-index.js +158 -75
  10. package/.agents/scripts/lib/audit-to-stories/epic-grouping-directive.js +39 -0
  11. package/.agents/scripts/lib/audit-to-stories/ledger-commit.js +290 -0
  12. package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +94 -3
  13. package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +10 -0
  14. package/.agents/scripts/lib/changed-files.js +100 -9
  15. package/.agents/scripts/lib/config-settings-schema.js +25 -7
  16. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  17. package/.agents/scripts/lib/label-constants.js +18 -0
  18. package/.agents/scripts/lib/label-taxonomy.js +18 -5
  19. package/.agents/scripts/lib/orchestration/epic-container.js +186 -0
  20. package/.agents/scripts/lib/orchestration/epic-expansion.js +148 -0
  21. package/.agents/scripts/lib/orchestration/plan-persist/epic-ops.js +320 -0
  22. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +18 -0
  23. package/.agents/scripts/lib/orchestration/run-epilogue.js +130 -1
  24. package/.agents/scripts/lib/qa/resolve-qa-contract.js +58 -6
  25. package/.agents/scripts/lib/skills/skills-index.js +168 -0
  26. package/.agents/scripts/lib/skills/walk-skill-files.js +133 -9
  27. package/.agents/scripts/plan-persist.js +39 -1
  28. package/.agents/scripts/providers/github/sub-issue-add.js +218 -0
  29. package/.agents/scripts/quality-preview.js +50 -9
  30. package/.agents/scripts/resolve-stories.js +42 -2
  31. package/.agents/scripts/validate-skills.js +53 -66
  32. package/.agents/templates/docs/audit-sweep-runbook.md +169 -0
  33. package/.agents/workflows/audit-to-stories.md +85 -7
  34. package/.agents/workflows/helpers/audit-lens-core.md +24 -4
  35. package/.agents/workflows/helpers/deliver-reference.md +8 -0
  36. package/.agents/workflows/helpers/plan-reference.md +28 -0
  37. package/.agents/workflows/mandrel-deliver.md +47 -43
  38. package/.agents/workflows/mandrel-plan.md +44 -38
  39. package/.agents/workflows/qa-run.md +13 -5
  40. package/docs/CHANGELOG.md +28 -0
  41. package/package.json +1 -1
package/.agents/README.md CHANGED
@@ -322,9 +322,12 @@ in `runtime-deps.json`.
322
322
  Orchestration and planning are **Story-only** (`type::story`) — `/mandrel-plan`
323
323
  persists Stories with inline `acceptance[]` / `verify[]` and a folded
324
324
  `## Spec`; `/mandrel-deliver` runs `helpers/deliver-story` on `story-<id>` → PR →
325
- `main`. There is no `type::epic` / `type::task` label, Epic issue form, or
326
- `epic/<id>` integration branch; a ticket carrying an `Epic: #N` footer is
327
- refused by `/mandrel-deliver`. The execution-model contract is owned by
325
+ `main`. There is no `type::task` label, Epic issue form, or `epic/<id>`
326
+ integration branch; a ticket carrying an `Epic: #N` footer is refused by
327
+ `/mandrel-deliver`. `type::epic` exists as a **pure container** — a goal plus a
328
+ child checklist, never delivered itself, expanded to its open Stories by
329
+ `/mandrel-deliver <epicId>` — and its linkage is parent→child only, which is
330
+ what keeps that footer refused. The execution-model contract is owned by
328
331
  [`instructions.md` § 5.B](instructions.md) and [`docs/SDLC.md`](docs/SDLC.md).
329
332
 
330
333
  ---
@@ -775,7 +778,16 @@ time by `resolveQaContract`. Copy the reference shape from
775
778
  "qa": {
776
779
  "featureRoot": "tests/features", // root the selector resolves .feature files against
777
780
  "fixturesManifest": "tests/fixtures/personas.json", // persona → seed-data manifest
778
- "signInSeam": { "urlTemplate": "/dev/sign-in-as/{persona}" }, // dev seam (see step 3)
781
+ "environments": { // one entry per deployment target
782
+ "local": {
783
+ "baseUrl": "http://localhost:3000",
784
+ "signInSeam": { "urlTemplate": "/dev/sign-in-as/{persona}" } // dev seam (see step 3)
785
+ },
786
+ "staging": {
787
+ "baseUrl": "https://staging.example.test",
788
+ "allowWrites": false // no signInSeam — an honestly seamless target
789
+ }
790
+ },
779
791
  "personas": ["admin", "member"], // name-only array — the honest shape for a url-template seam
780
792
  "consoleAllowlist": ["[HMR]"], // optional benign-noise filter (default [])
781
793
  "designTokens": "src/styles/tokens.css" // optional visual-check pointer (default null)
@@ -783,8 +795,9 @@ time by `resolveQaContract`. Copy the reference shape from
783
795
  }
784
796
  ```
785
797
 
786
- `featureRoot`, `fixturesManifest`, `signInSeam`, and `personas` are mandatory;
787
- omitting any one makes the resolver throw a field-named error.
798
+ `featureRoot`, `fixturesManifest`, `environments`, and `personas` are mandatory;
799
+ omitting any one makes the resolver throw a field-named error. Within an
800
+ environment only `baseUrl` is required — `signInSeam` is optional (see step 3).
788
801
  `consoleAllowlist` and `designTokens` default to `[]` and `null`.
789
802
 
790
803
  `personas` accepts **two shapes** (the resolver normalizes both to one
@@ -800,13 +813,16 @@ canonical internal map keyed by persona name):
800
813
  (or credential) seam where that material is genuinely consulted:
801
814
 
802
815
  ```jsonc
803
- "signInSeam": { "skill": "stack/qa/sign-in" },
816
+ "signInSeam": { "skill": "stack/qa/acme-sso" }, // a skill YOU author (see step 3)
804
817
  "personas": {
805
818
  "admin": { "credentialRef": "QA_ADMIN_CREDENTIAL" }, // stored-credential reference, never an inline secret
806
- "member": { "signInSkill": "stack/qa/sign-in-member" } // or a per-persona sign-in skill
819
+ "member": { "signInSkill": "stack/qa/acme-sso-member" } // or a per-persona sign-in skill
807
820
  }
808
821
  ```
809
822
 
823
+ Both skill ids above are **illustrative names for skills you write** — the
824
+ framework ships no sign-in skill. Step 3 says where they go.
825
+
810
826
  ### 2. Author the fixtures manifest
811
827
 
812
828
  Create the file referenced by `fixturesManifest`. It binds each persona to the
@@ -824,9 +840,33 @@ credentials are never entered. Expose one of two shapes:
824
840
  - **`{ urlTemplate }`** — a dev sign-in route where `{persona}` is substituted
825
841
  (e.g. `/dev/sign-in-as/{persona}` → `/dev/sign-in-as/admin`); gate it to
826
842
  non-production builds. Pair with the name-only `personas` array.
827
- - **`{ skill }`** — when sign-in is multi-step or non-URL, point at a consumer
828
- skill whose `SKILL.md` the harness reads. Pair with the object-map
829
- `personas` form (per step 1).
843
+ - **`{ skill }`** — when sign-in is multi-step or non-URL, name a skill by its
844
+ tier-relative id (e.g. `stack/qa/acme-sso`) whose `SKILL.md` the harness
845
+ reads. Pair with the object-map `personas` form (per step 1).
846
+ - **Omit it entirely** — the honest shape for a target with no sign-in seam at
847
+ all, such as a deployed build whose dev bypass is tree-shaken out. The
848
+ workflows then drive the unauthenticated surface and record the gap rather
849
+ than fabricating a session.
850
+
851
+ **Where a `{ skill }` seam resolves.** The id is looked up under
852
+ `.agents/skills/` (the package payload) and then `.agents/local/skills/` —
853
+ the consumer-writable zone. Author your own sign-in skill in the local zone:
854
+
855
+ ```text
856
+ .agents/local/skills/stack/qa/acme-sso/SKILL.md → id: stack/qa/acme-sso
857
+ ```
858
+
859
+ `.agents/local/` is never copied into by `mandrel sync`, never pruned, and
860
+ never reported as payload drift by `mandrel doctor`, so a skill you write
861
+ there survives every upgrade. It is held to the same bar as a shipped skill —
862
+ `validate-skills.js` checks its frontmatter and Policy Capsule, and
863
+ `generate-skills-index.js` writes it into its own
864
+ `.agents/local/skills/skills.index.json` (never into the shipped manifest,
865
+ which must stay byte-identical to the package payload).
866
+
867
+ A seam naming an id that resolves under neither root is rejected by
868
+ `resolveQaEnvironment` when the contract is resolved — not silently carried
869
+ until a sweep reaches its sign-in step.
830
870
 
831
871
  Once these three `qa.*` keys are in place, `/qa-explore <surface>`, `/qa-assist`,
832
872
  and `/qa-run <selector>` all resolve the contract and operate against the bound
@@ -130,6 +130,11 @@ Record the outcome in the Executive Summary as a single line —
130
130
  `Self-cross-check: kept <k> / dropped <d>.` — and, when `d > 0`, name the
131
131
  dropped findings with their reason. The line's absence is itself a defect.
132
132
 
133
+ Beside it, carry one machine-readable tally of the findings you kept —
134
+ `Severity tally: Critical <n> / High <n> / Medium <n> / Low <n>`, zeros
135
+ included, `Info` never counted. `audit-to-stories` cross-checks that line
136
+ against its parse and refuses a report whose tally is missing or wrong.
137
+
133
138
  ## Fan-out (heavyweight lenses)
134
139
 
135
140
  When your caller dispatches you for a single dimension of a heavyweight lens
@@ -7,12 +7,13 @@ each Story is delivered on its own `story-<id>` branch and reaches `main`
7
7
  through its own PR.
8
8
 
9
9
  An Epic may still exist as an **optional untyped human umbrella issue**
10
- (no `type::epic` label and no shipped Epic issue form only
11
- `.github/ISSUE_TEMPLATE/story.yml`), but **delivery and planning
12
- orchestration are Story-only**: there is no Epic wave loop, no
13
- `epic/<id>` integration branch, no `epic.yaml` reconciler, and any ticket
14
- that still carries an `Epic: #N` footer is **refused** by `/mandrel-deliver`
15
- (close it or re-plan it as a v2 Story).
10
+ (the only shipped issue form is `.github/ISSUE_TEMPLATE/story.yml`), and
11
+ **delivery and planning orchestration are Story-only**: there is no Epic
12
+ wave loop, no `epic/<id>` integration branch, no `epic.yaml` reconciler,
13
+ and any ticket that still carries an `Epic: #N` footer is **refused** by
14
+ `/mandrel-deliver` (close it or re-plan it as a v2 Story). `type::epic`
15
+ exists as a **container only** a grouping ticket with no execution
16
+ payload, expanded to its children at delivery (ADR `20260905-5139`).
16
17
 
17
18
  The framework is **Claude Code-first**: `.claude/`, hooks, skills, and
18
19
  the slash-command surface lean in on Claude Code as the reference
@@ -247,9 +248,10 @@ self-eval, ceremony, close, CI watch, confirm-merge, cleanup) lives in the
247
248
 
248
249
  The single operator-facing entry point is `/mandrel-deliver`. It performs no
249
250
  git/label mutations itself — `deliver-story` owns every script invocation
250
- per Story. Any ticket that is not `type::story`, or that still carries an
251
- `Epic: #N` reference, is a hard error naming the ID and the fix (close or
252
- re-plan as a v2 Story).
251
+ per Story. A `type::epic` id expands to its open child Stories before
252
+ resolution. Any ticket that is neither of those two types, or that still
253
+ carries an `Epic: #N` reference, is a hard error naming the ID and the fix
254
+ (close or re-plan as a v2 Story).
253
255
 
254
256
  ### Branch model (authoritative)
255
257
 
@@ -560,9 +562,15 @@ Editing the main checkout's `.agentrc.json` only affects **the next**
560
562
 
561
563
  ### `Epic: #N` refusal
562
564
 
563
- `/mandrel-deliver` refuses any ticket that still carries an `Epic: #N` footer or is
564
- not `type::story`. This is expected — v2 has no Epic delivery path. Close
565
- the ticket or re-plan the work as a v2 Story via `/mandrel-plan --tickets <id>`.
565
+ `/mandrel-deliver` refuses any ticket that still carries an `Epic: #N` footer,
566
+ or that is neither `type::story` nor `type::epic`. This is expected — v2 has
567
+ no Epic *delivery* path. Close the ticket or re-plan the work as a v2 Story
568
+ via `/mandrel-plan --tickets <id>`.
569
+
570
+ The container Epic (ADR `20260905-5139`) does **not** soften this. Its linkage
571
+ runs parent→child only — the Epic body lists its children, and no Story body
572
+ ever gains a footer pointing back — so a ticket carrying `Epic: #N` is still a
573
+ v1 ticket and still refused.
566
574
 
567
575
  ---
568
576
 
@@ -573,6 +581,7 @@ the ticket or re-plan the work as a v2 Story via `/mandrel-plan --tickets <id>`.
573
581
  | `npx mandrel init` | Cold-start — install `mandrel` (if absent), `mandrel sync`, `bootstrap.js` (provisions repo + Projects V2 board, labels, branch protection), then the onboarding tail (stack detection, docs scaffolding, doctor gate, `/mandrel-plan` handoff). |
574
582
  | `/mandrel-plan --seed "<text>"` | Plan from chat text — interrogate → author **one Story by default** → persist `type::story`. |
575
583
  | `/mandrel-plan --seed-file <path>` | Plan from on-disk notes / a plan seed (the `/audit-to-stories` handoff). |
584
+ | `/mandrel-deliver <epicId>` | Deliver every open Story under a container Epic — the id expands before resolution. |
576
585
  | `/mandrel-plan --tickets <ids>` | Analyze existing issue(s) into proper Stories (prefer an N=1 rewrite). |
577
586
  | `/mandrel-deliver <storyId>` | Deliver one Story via `helpers/deliver-story` — `story-<id>` → PR → `main`. |
578
587
  | `/mandrel-deliver <storyId> [<storyId>…]` | Deliver multiple Stories in `depends_on` order (resolved from live state), then run the per-run epilogue. |
@@ -363,9 +363,6 @@
363
363
  },
364
364
  "staging": {
365
365
  "baseUrl": "https://staging.example.test",
366
- "signInSeam": {
367
- "skill": "stack/qa/sign-in"
368
- },
369
366
  "allowWrites": false
370
367
  }
371
368
  },
@@ -374,7 +371,7 @@
374
371
  "credentialRef": "QA_ADMIN_CREDENTIAL"
375
372
  },
376
373
  "member": {
377
- "signInSkill": "stack/qa/sign-in-member"
374
+ "credentialRef": "QA_MEMBER_CREDENTIAL"
378
375
  }
379
376
  },
380
377
  "gherkinLint": {
@@ -336,8 +336,8 @@ Agent-driven QA harness contract (Epic #3214; environment-keyed by Epic #4326).
336
336
  | --- | --- | --- | --- | --- |
337
337
  | `featureRoot` | No | `string` | `"tests/features"` | Directory holding the Gherkin feature files the QA sweep drives. |
338
338
  | `fixturesManifest` | No | `string` | `"tests/fixtures/personas.json"` | Path to the persona/fixture manifest the harness seeds from. |
339
- | `environments` | No | `object<map>` | `{"local":{"baseUrl":"http://localhost:3000","signInSeam":{"urlTemplate":"/dev/sign-in-as/{persona}"}},"staging":{"baseUrl":"https://staging.example.test","signInSeam":{"skill":"stack/qa/sign-in"},"allowWrites":false}}` | Deployment targets the QA harness can run against (Epic #4326). A map keyed by environment name (e.g. `local`, `staging`), each carrying its own `baseUrl`, its own per-environment sign-in seam (the same url-template/skill union as the top-level seam), and an optional `allowWrites` gate. resolveQaEnvironment selects one environment per invocation by name or by raw-URL origin match against `baseUrl`; `allowWrites` defaults to true only for the `local` environment. Replaces the retired top-level single `signInSeam`. |
340
- | `personas` | No | one of: `array`, `object` | `{"admin":{"credentialRef":"QA_ADMIN_CREDENTIAL"},"member":{"signInSkill":"stack/qa/sign-in-member"}}` | Personas the QA-harness sign-in seam accepts. Two accepted shapes: (1) a plain array of persona names — the honest shape for a `urlTemplate` dev-impersonation seam, where the persona name is the sole input the workflow consumes; (2) the object-map form keyed by persona name, where each entry carries per-persona auth material (`credentialRef` or `signInSkill`) consulted only under a skill-based or credential-based seam. |
339
+ | `environments` | No | `object<map>` | `{"local":{"baseUrl":"http://localhost:3000","signInSeam":{"urlTemplate":"/dev/sign-in-as/{persona}"}},"staging":{"baseUrl":"https://staging.example.test","allowWrites":false}}` | Deployment targets the QA harness can run against (Epic #4326). A map keyed by environment name (e.g. `local`, `staging`), each carrying its own `baseUrl`, an optional per-environment sign-in seam, and an optional `allowWrites` gate. `signInSeam` is the union `{ urlTemplate }` (a dev impersonation route) or `{ skill }` (a skill id such as `stack/qa/acme-sso`, resolved against `.agents/skills/` then the consumer-writable `.agents/local/skills/` zone, and rejected loudly by resolveQaEnvironment when it resolves under neither); omit it entirely for a target with no sign-in seam. resolveQaEnvironment selects one environment per invocation by name or by raw-URL origin match against `baseUrl`; `allowWrites` defaults to true only for the `local` environment. Replaces the retired top-level single `signInSeam`. |
340
+ | `personas` | No | one of: `array`, `object` | `{"admin":{"credentialRef":"QA_ADMIN_CREDENTIAL"},"member":{"credentialRef":"QA_MEMBER_CREDENTIAL"}}` | Personas the QA-harness sign-in seam accepts. Two accepted shapes: (1) a plain array of persona names — the honest shape for a `urlTemplate` dev-impersonation seam, where the persona name is the sole input the workflow consumes; (2) the object-map form keyed by persona name, where each entry carries per-persona auth material (`credentialRef` or `signInSkill`) consulted only under a skill-based or credential-based seam. |
341
341
  | `gherkinLint` | No | `object` | `{"scopes":{"web":{"featureRoots":["apps/web/tests/features"],"stepRoots":["apps/web/tests/steps"]}},"exemptionTags":["@skip"],"stepWaivers":[]}` | Static Gherkin corpus gate (Story #5013). Optional; the gate runs only when this block is present, so an upgrade never reddens the lint of a consumer that never asked the framework to police its `.feature` files. Inside the opt-in it fails closed: an unresolvable `@cucumber/gherkin` parser, or a scope resolving zero step definitions, exits 1 rather than reporting a clean run. |
342
342
  | `gherkinLint.scopes` | Yes | `object<map>` | — | Binding scopes, keyed by name. Each scope resolves its own features against its own step definitions only — pooling every step root into one matcher list is what makes a cross-app false bind possible, where a step defined solely in app B silently vouches for app A. The scope name appears verbatim in every unbound finding. |
343
343
  | `gherkinLint.exemptionTags` | No | `array<string>` | `["@skip"]` | Tags marking a scenario as intentionally non-binding, so must-bind skips it. Never an escape from must-compile: a parse error in the file still fails the run. Default: ["@skip"]. |
@@ -147,9 +147,9 @@ sizing) **fail closed** naming what to trim:
147
147
  spawn only when the work justifies replicating context. One objective
148
148
  per subagent; depth compounds the cost (every nested level re-pays).
149
149
  - **Anti-Laziness / No Dead Code.** NEVER use placeholder comments like
150
- `// ... existing code ...`; every edit must leave complete, runnable
151
- code. Remove unused imports, commented-out code, and dead branches
152
- before finalizing.
150
+ `// ... existing code ...`; every edit must leave complete, runnable code.
151
+ Remove unused imports, commented-out code, and dead branches before
152
+ finalizing.
153
153
  - **Verification.** Include explicit verification steps in every plan.
154
154
 
155
155
  ---
@@ -157,28 +157,29 @@ sizing) **fail closed** naming what to trim:
157
157
  ## 5. Git & Story Protocol (Strict Standards)
158
158
 
159
159
  [`rules/git-conventions.md`](rules/git-conventions.md) is the canonical
160
- reference: `story-<storyId>` branches seeded by `single-story-init.js`,
161
- every Story reaching `main` via its own PR
162
- (`helpers/deliver-story` / `single-story-close.js`).
160
+ reference for branch shapes and commit subjects, and is always loaded —
161
+ this section does not restate it.
163
162
 
164
163
  ### A. Status Tracking & Commit Standards
165
164
 
166
165
  State mutations are GitHub labels (`agent::ready`, `agent::executing`,
167
166
  `agent::done`) via
168
167
  `node .agents/scripts/update-ticket-state.js --ticket [ID] --state [STATUS]`.
169
- Do NOT manually update issue descriptions or status fields unless
170
- prompted.
168
+ Do NOT manually update issue descriptions or status fields unless prompted.
171
169
 
172
- ### B. Ticket hierarchy (Story-only)
170
+ ### B. Ticket hierarchy
173
171
 
174
- The v2 ticket model is Story-only: `acceptance[]` / `verify[]` live
172
+ The Story is the only executable ticket: `acceptance[]` / `verify[]`
175
173
  inline plus the folded Tech Spec in `## Spec` (over-budget Specs fail
176
- closed — split or tighten; never write Specs under `docs/`). Optional
177
- `depends_on` edges order rare multi-Story runs, resolved by `/mandrel-deliver`
178
- from live state; the `plan-run::<id>` label is filter metadata only.
179
- Commit subjects reference the Story via `(refs #<storyId>)`. There is no
180
- `type::epic` / `type::task` label; `/mandrel-deliver` refuses tickets carrying an
181
- `Epic: #N` footer.
174
+ closed — split or tighten; never under `docs/`). Optional `depends_on`
175
+ edges order rare multi-Story runs, resolved by `/mandrel-deliver` from
176
+ live state; `plan-run::<id>` is filter metadata. Commit subjects
177
+ reference the Story via `(refs #<storyId>)`. There is no `type::task`.
178
+
179
+ `type::epic` is a container only (goal + child checklist, no `agent::*`,
180
+ never delivered): `/mandrel-plan` offers one above 2 Stories and
181
+ `/mandrel-deliver <epicId>` expands it. Linkage is parent→child only, so
182
+ an `Epic: #N` footer is still refused.
182
183
 
183
184
  ---
184
185
 
@@ -1921,7 +1921,9 @@
1921
1921
  },
1922
1922
  "advisoryAllowlist": {
1923
1923
  "type": "array",
1924
- "items": { "type": "string" },
1924
+ "items": {
1925
+ "type": "string"
1926
+ },
1925
1927
  "description": "Story #5096. Check-run names exempt from blockOnAdvisoryFailure — a red run whose name matches exactly never blocks arming. Matching is exact; an unnamed run can never match and always blocks.",
1926
1928
  "default": []
1927
1929
  }
@@ -1985,7 +1987,7 @@
1985
1987
  },
1986
1988
  "environments": {
1987
1989
  "type": "object",
1988
- "description": "Deployment targets the QA harness can run against (Epic #4326). A map keyed by environment name (e.g. `local`, `staging`), each carrying its own `baseUrl`, its own per-environment sign-in seam (the same url-template/skill union as the top-level seam), and an optional `allowWrites` gate. resolveQaEnvironment selects one environment per invocation by name or by raw-URL origin match against `baseUrl`; `allowWrites` defaults to true only for the `local` environment. Replaces the retired top-level single `signInSeam`.",
1990
+ "description": "Deployment targets the QA harness can run against (Epic #4326). A map keyed by environment name (e.g. `local`, `staging`), each carrying its own `baseUrl`, an optional per-environment sign-in seam, and an optional `allowWrites` gate. `signInSeam` is the union `{ urlTemplate }` (a dev impersonation route) or `{ skill }` (a skill id such as `stack/qa/acme-sso`, resolved against `.agents/skills/` then the consumer-writable `.agents/local/skills/` zone, and rejected loudly by resolveQaEnvironment when it resolves under neither); omit it entirely for a target with no sign-in seam. resolveQaEnvironment selects one environment per invocation by name or by raw-URL origin match against `baseUrl`; `allowWrites` defaults to true only for the `local` environment. Replaces the retired top-level single `signInSeam`.",
1989
1991
  "default": {
1990
1992
  "local": {
1991
1993
  "baseUrl": "http://localhost:3000",
@@ -1995,9 +1997,6 @@
1995
1997
  },
1996
1998
  "staging": {
1997
1999
  "baseUrl": "https://staging.example.test",
1998
- "signInSeam": {
1999
- "skill": "stack/qa/sign-in"
2000
- },
2001
2000
  "allowWrites": false
2002
2001
  }
2003
2002
  },
@@ -2048,7 +2047,7 @@
2048
2047
  "type": "boolean"
2049
2048
  }
2050
2049
  },
2051
- "required": ["baseUrl", "signInSeam"],
2050
+ "required": ["baseUrl"],
2052
2051
  "additionalProperties": false
2053
2052
  }
2054
2053
  },
@@ -2059,7 +2058,7 @@
2059
2058
  "credentialRef": "QA_ADMIN_CREDENTIAL"
2060
2059
  },
2061
2060
  "member": {
2062
- "signInSkill": "stack/qa/sign-in-member"
2061
+ "credentialRef": "QA_MEMBER_CREDENTIAL"
2063
2062
  }
2064
2063
  },
2065
2064
  "oneOf": [