@momentiq/dark-factory-cli 2.18.0 → 3.0.1
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/README.md +33 -15
- package/dist/adapters/cursor-sdk.d.ts.map +1 -1
- package/dist/adapters/cursor-sdk.js +14 -2
- package/dist/adapters/cursor-sdk.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +6 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/mode-scope.d.ts +86 -0
- package/dist/adapters/mode-scope.d.ts.map +1 -0
- package/dist/adapters/mode-scope.js +456 -0
- package/dist/adapters/mode-scope.js.map +1 -0
- package/dist/branch-protection/audit_branch_protection.py +152 -26
- package/dist/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +17 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/mode.d.ts.map +1 -1
- package/dist/commands/mode.js +135 -10
- package/dist/commands/mode.js.map +1 -1
- package/dist/commands/objectives.d.ts +27 -3
- package/dist/commands/objectives.d.ts.map +1 -1
- package/dist/commands/objectives.js +347 -53
- package/dist/commands/objectives.js.map +1 -1
- package/dist/cycle-doc-validator/validate_cycle_doc.py +128 -11
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +8 -4
- package/dist/doctor.js.map +1 -1
- package/dist/evidence/index.d.ts +1 -0
- package/dist/evidence/index.d.ts.map +1 -1
- package/dist/evidence/index.js +5 -0
- package/dist/evidence/index.js.map +1 -1
- package/dist/evidence/ui-visual/embed.d.ts +65 -0
- package/dist/evidence/ui-visual/embed.d.ts.map +1 -0
- package/dist/evidence/ui-visual/embed.js +176 -0
- package/dist/evidence/ui-visual/embed.js.map +1 -0
- package/dist/evidence/ui-visual/index.d.ts +2 -0
- package/dist/evidence/ui-visual/index.d.ts.map +1 -0
- package/dist/evidence/ui-visual/index.js +6 -0
- package/dist/evidence/ui-visual/index.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/cycle-doc/parser.d.ts +3 -3
- package/dist/mcp/cycle-doc/parser.d.ts.map +1 -1
- package/dist/mcp/cycle-doc/parser.js +2 -2
- package/dist/mcp/cycle-doc/parser.js.map +1 -1
- package/dist/mcp/tools/doctor.d.ts +5 -0
- package/dist/mcp/tools/doctor.d.ts.map +1 -1
- package/dist/mcp/tools/doctor.js +10 -1
- package/dist/mcp/tools/doctor.js.map +1 -1
- package/dist/mcp/tools/review-bypass.d.ts +5 -0
- package/dist/mcp/tools/review-bypass.d.ts.map +1 -1
- package/dist/mcp/tools/review-bypass.js +9 -1
- package/dist/mcp/tools/review-bypass.js.map +1 -1
- package/dist/mode/apply.d.ts +8 -0
- package/dist/mode/apply.d.ts.map +1 -0
- package/dist/mode/apply.js +118 -0
- package/dist/mode/apply.js.map +1 -0
- package/dist/mode/context.d.ts +50 -0
- package/dist/mode/context.d.ts.map +1 -0
- package/dist/mode/context.js +127 -0
- package/dist/mode/context.js.map +1 -0
- package/dist/mode/critic-registration.d.ts +66 -0
- package/dist/mode/critic-registration.d.ts.map +1 -0
- package/dist/mode/critic-registration.js +279 -0
- package/dist/mode/critic-registration.js.map +1 -0
- package/dist/mode/guard.d.ts +15 -0
- package/dist/mode/guard.d.ts.map +1 -0
- package/dist/mode/guard.js +160 -0
- package/dist/mode/guard.js.map +1 -0
- package/dist/mode/init.d.ts.map +1 -1
- package/dist/mode/init.js +14 -5
- package/dist/mode/init.js.map +1 -1
- package/dist/mode/loadConfig.d.ts +34 -0
- package/dist/mode/loadConfig.d.ts.map +1 -0
- package/dist/mode/loadConfig.js +38 -0
- package/dist/mode/loadConfig.js.map +1 -0
- package/dist/mode/prompts/designer-baseline.md +47 -0
- package/dist/mode/resolve.d.ts +1 -1
- package/dist/mode/resolve.d.ts.map +1 -1
- package/dist/mode/resolve.js +19 -3
- package/dist/mode/resolve.js.map +1 -1
- package/dist/mode/scope-policy.d.ts +41 -0
- package/dist/mode/scope-policy.d.ts.map +1 -0
- package/dist/mode/scope-policy.js +97 -0
- package/dist/mode/scope-policy.js.map +1 -0
- package/dist/mode/status.d.ts.map +1 -1
- package/dist/mode/status.js +2 -9
- package/dist/mode/status.js.map +1 -1
- package/dist/onboard/validate.d.ts +24 -6
- package/dist/onboard/validate.d.ts.map +1 -1
- package/dist/onboard/validate.js +87 -14
- package/dist/onboard/validate.js.map +1 -1
- package/dist/skills/config.d.ts +2 -113
- package/dist/skills/config.d.ts.map +1 -1
- package/dist/skills/config.js +19 -62
- package/dist/skills/config.js.map +1 -1
- package/dist/skills/index.d.ts +1 -1
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +1 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/skills/install.d.ts.map +1 -1
- package/dist/skills/install.js +7 -0
- package/dist/skills/install.js.map +1 -1
- package/package.json +2 -2
- package/skills/designer-brief/SKILL.md.tmpl +74 -0
- package/skills/designer-brief/skill.json +17 -0
- package/skills/designer-build/SKILL.md.tmpl +63 -0
- package/skills/designer-build/skill.json +17 -0
- package/skills/designer-handoff/SKILL.md.tmpl +64 -0
- package/skills/designer-handoff/skill.json +17 -0
- package/skills/designer-ship/SKILL.md.tmpl +77 -0
- package/skills/designer-ship/skill.json +17 -0
- package/skills/objectives/SKILL.md.tmpl +16 -1
- package/skills/verify/producer/ui-visual-surfaces.ts +261 -0
- package/skills/verify/producer/ui-visual.config.ts +22 -0
- package/skills/verify/producer/ui-visual.producer.spec.ts +137 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: designer-brief
|
|
3
|
+
description: Use when a non-technical operator (designer, PM, or founder) in Designer Mode asks for a non-trivial UI change — capture their intent as a short brief (What, Why, Done-when, Out-of-bounds) at .darkfactory/briefs/ before building. Tiny, obvious single-shot asks skip the brief and go straight to /designer-build.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /designer-brief — capture intent before building
|
|
7
|
+
|
|
8
|
+
Designer Mode pairs a non-technical operator with an agent that owns every
|
|
9
|
+
technical decision. This skill is the **plan-time** step for {{REPO_NAME}}: it
|
|
10
|
+
turns a product ask into a one-glance brief the operator approves before you
|
|
11
|
+
write a line of code, so "done" has an agreed shape.
|
|
12
|
+
|
|
13
|
+
Everything below is **your** procedure. The operator never sees a brief
|
|
14
|
+
template, a file path, or this doctrine — they see one plain-language summary
|
|
15
|
+
and answer at most a question or two.
|
|
16
|
+
|
|
17
|
+
## When to write a brief (and when to skip)
|
|
18
|
+
|
|
19
|
+
Write a brief when the ask is **non-trivial** — a new section, page, or flow, or
|
|
20
|
+
a change that spans several surfaces or has more than one "done" condition
|
|
21
|
+
("add a testimonials section", "make onboarding feel calmer").
|
|
22
|
+
|
|
23
|
+
**Skip it** — the single-shot path — for tiny, unambiguous polish where the
|
|
24
|
+
change and its "done" are obvious in one look ("the pricing cards feel
|
|
25
|
+
cramped", "this heading is the wrong blue"). Go straight to `/designer-build`,
|
|
26
|
+
screenshot before/after, and confirm.
|
|
27
|
+
|
|
28
|
+
When unsure, prefer a brief — it is three sentences and a checklist, not a spec.
|
|
29
|
+
|
|
30
|
+
## The brief
|
|
31
|
+
|
|
32
|
+
Write it to `.darkfactory/briefs/<date>-<slug>.md`, where `<date>` is today in
|
|
33
|
+
`YYYY-MM-DD` form and `<slug>` is a short kebab-case name for the ask
|
|
34
|
+
(`.darkfactory/briefs/2026-07-03-testimonials-section.md`). Four sections, all
|
|
35
|
+
in the operator's product language — no framework, file, or component names:
|
|
36
|
+
|
|
37
|
+
```markdown
|
|
38
|
+
# <Title of the ask>
|
|
39
|
+
|
|
40
|
+
## What
|
|
41
|
+
One sentence: the change, as the operator would describe it.
|
|
42
|
+
|
|
43
|
+
## Why
|
|
44
|
+
The outcome they want — what a visitor or user feels or can now do.
|
|
45
|
+
|
|
46
|
+
## Done-when
|
|
47
|
+
- [ ] An observable, checkable acceptance item in plain language.
|
|
48
|
+
- [ ] Another one. Each is something you can *show* on screen.
|
|
49
|
+
- [ ] …
|
|
50
|
+
|
|
51
|
+
## Out-of-bounds
|
|
52
|
+
- What this deliberately does NOT touch (keeps scope honest and points at handoffs).
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The **Done-when checkboxes are the acceptance contract.** Write each as
|
|
56
|
+
something you can demonstrate with a screenshot or a visible behavior — they
|
|
57
|
+
become what `/designer-ship` verifies and what closeout proves. Vague items
|
|
58
|
+
("looks nicer") are not acceptance items; make them observable ("the
|
|
59
|
+
testimonials read comfortably on a phone without pinching").
|
|
60
|
+
|
|
61
|
+
## Filling gaps — ask product questions only
|
|
62
|
+
|
|
63
|
+
If the ask is underspecified, ask **only product or visual questions**, one at a
|
|
64
|
+
time, multiple-choice with 2–3 concrete options plus "you decide"
|
|
65
|
+
("Should testimonials show a photo, or just a name and quote?"). Never ask about
|
|
66
|
+
frameworks, data, files, or state — decide those yourself from the repo's
|
|
67
|
+
existing conventions and `DESIGN.md`.
|
|
68
|
+
|
|
69
|
+
## Approve in one glance
|
|
70
|
+
|
|
71
|
+
Show the operator the essence, not the file: *"Here's what I'll build — a
|
|
72
|
+
testimonials section with three quotes and photos, on the home page. It won't
|
|
73
|
+
touch sign-in or your data. Look right?"* On yes, hand off to `/designer-build`.
|
|
74
|
+
Keep the brief file updated if the ask shifts mid-build.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../skill-schema.json",
|
|
3
|
+
"name": "designer-brief",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"summary": "Designer Mode: turn a non-technical operator's intent into a short brief (What/Why/Done-when/Out-of-bounds) at .darkfactory/briefs/ before building; tiny obvious asks skip straight to /designer-build.",
|
|
6
|
+
"originatingRepo": "momentiq-ai/dark-factory",
|
|
7
|
+
"files": [
|
|
8
|
+
{ "template": "SKILL.md.tmpl", "target": "SKILL.md" }
|
|
9
|
+
],
|
|
10
|
+
"variables": {
|
|
11
|
+
"REPO_NAME": {
|
|
12
|
+
"description": "Display name of the consumer repo (e.g. 'Taxpilot').",
|
|
13
|
+
"source": "darkfactory.yaml: repo.displayName",
|
|
14
|
+
"default": "this repo"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: designer-build
|
|
3
|
+
description: Use when implementing a Designer Mode change within the mode's write scopes — reuse the repo's design system, run the dev server, and capture before/after screenshots each visual iteration until the operator says it looks right. Follows /designer-brief (or a tiny single-shot ask), precedes /designer-ship.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /designer-build — implement within scope, verify visually
|
|
7
|
+
|
|
8
|
+
This is the **build loop** of Designer Mode for {{REPO_NAME}}. You implement the
|
|
9
|
+
change, prove it visually, and iterate on the operator's product feedback —
|
|
10
|
+
never on code they have to read.
|
|
11
|
+
|
|
12
|
+
The operator sees screenshots and plain sentences. They never see diffs, file
|
|
13
|
+
trees, terminals, or component names.
|
|
14
|
+
|
|
15
|
+
## Stay in scope
|
|
16
|
+
|
|
17
|
+
Work only inside the mode's `writeScopes`. The PreToolUse guard
|
|
18
|
+
(`df mode guard`) enforces the scopes and `protectedPaths` at the tool level —
|
|
19
|
+
if a call is denied, **do not retry or route around it.** A denial means the ask
|
|
20
|
+
needs an engineer: stop and use `/designer-handoff`. (`df mode status` confirms
|
|
21
|
+
the active mode and its editable areas.)
|
|
22
|
+
|
|
23
|
+
## Reuse the design system — don't invent
|
|
24
|
+
|
|
25
|
+
`DESIGN.md` is the design source of truth (its tokens are injected into your
|
|
26
|
+
session context):
|
|
27
|
+
|
|
28
|
+
- Take every color, font size, and spacing value from `DESIGN.md` tokens. Do
|
|
29
|
+
not hand-pick hex values or one-off spacings.
|
|
30
|
+
- **Reuse existing components before creating anything new** — extend a
|
|
31
|
+
component, don't fork it. Match the patterns already in the codebase.
|
|
32
|
+
- Never remove accessibility affordances (focus rings, labels, alt text,
|
|
33
|
+
contrast). A change that would is out-of-bounds → `/designer-handoff`.
|
|
34
|
+
|
|
35
|
+
## The visual loop (show, don't tell)
|
|
36
|
+
|
|
37
|
+
1. **Baseline.** Start the dev server using the mode catalog's
|
|
38
|
+
`devServer.command` (e.g. `npm run dev`) at its configured URL. With the
|
|
39
|
+
Playwright MCP, screenshot the route(s) and state(s) you're about to change —
|
|
40
|
+
this is the **BEFORE**.
|
|
41
|
+
2. **Change.** Implement one coherent step within scope.
|
|
42
|
+
3. **AFTER.** Re-screenshot the same route(s) and state(s). Include the
|
|
43
|
+
responsive and empty/error states the Done-when items care about (e.g. a
|
|
44
|
+
phone width).
|
|
45
|
+
4. **Show the operator** the before/after and describe the change in one
|
|
46
|
+
sentence: *"Here's the testimonials section — three quotes, easy to read on a
|
|
47
|
+
phone. Want the quotes bigger?"*
|
|
48
|
+
5. **Iterate** on their product feedback ("make it warmer", "tighten the
|
|
49
|
+
spacing") — repeat steps 2–4 until they say it looks right. Keep each
|
|
50
|
+
iteration a small, screenshotted step.
|
|
51
|
+
|
|
52
|
+
Keep the final before/after screenshots — `/designer-ship` embeds them in the PR
|
|
53
|
+
so reviewers and the operator see the change, and the visual-evidence route
|
|
54
|
+
binds them to the shipped commit.
|
|
55
|
+
|
|
56
|
+
## Translate, never dump
|
|
57
|
+
|
|
58
|
+
If something goes wrong (a build error, a failing check), say what it means in
|
|
59
|
+
one sentence and what you're doing about it — never paste the raw error. Decide
|
|
60
|
+
all implementation details yourself, following repo conventions; ask the
|
|
61
|
+
operator only product or visual questions, multiple-choice, one at a time.
|
|
62
|
+
|
|
63
|
+
When the operator says it looks right, hand off to `/designer-ship`.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../skill-schema.json",
|
|
3
|
+
"name": "designer-build",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"summary": "Designer Mode: implement within the mode's write scopes, reuse the design system, and run a dev-server + Playwright before/after screenshot loop until the operator says it looks right.",
|
|
6
|
+
"originatingRepo": "momentiq-ai/dark-factory",
|
|
7
|
+
"files": [
|
|
8
|
+
{ "template": "SKILL.md.tmpl", "target": "SKILL.md" }
|
|
9
|
+
],
|
|
10
|
+
"variables": {
|
|
11
|
+
"REPO_NAME": {
|
|
12
|
+
"description": "Display name of the consumer repo (e.g. 'Taxpilot').",
|
|
13
|
+
"source": "darkfactory.yaml: repo.displayName",
|
|
14
|
+
"default": "this repo"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: designer-handoff
|
|
3
|
+
description: Use when a Designer Mode ask needs an engineer — it is out-of-scope, touches a protected path, requires a dependency change, or a gate will not converge within the N=2 ceiling. Package a scrubbed, dual-register escalation with df handoff and tell the operator in one plain sentence, framed as progress.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /designer-handoff — escalate as progress, never as rejection
|
|
7
|
+
|
|
8
|
+
Some asks legitimately need an engineer. In Designer Mode for {{REPO_NAME}}, that
|
|
9
|
+
is a **first-class outcome**, not a failure — the product answer to a scope
|
|
10
|
+
boundary is a clean handoff that feels like momentum.
|
|
11
|
+
|
|
12
|
+
## When to hand off
|
|
13
|
+
|
|
14
|
+
- A tool call was **denied by the mode guard** (outside `writeScopes`, or a
|
|
15
|
+
`protectedPaths` hit like auth, API, migrations, config, or CI). Do **not**
|
|
16
|
+
retry or work around a denial.
|
|
17
|
+
- The ask needs a **dependency change** under `dependencyPolicy: none`.
|
|
18
|
+
- A gate finding **won't converge within N=2** fix rounds, or the fix requires
|
|
19
|
+
work outside your scope.
|
|
20
|
+
|
|
21
|
+
## Package the artifact with `df handoff`
|
|
22
|
+
|
|
23
|
+
`df handoff` is Issue-anchored: it reads the composed note on **stdin**, upserts
|
|
24
|
+
it as the dedicated handoff Issue's body, adds the `handoff` label, and leaves it
|
|
25
|
+
unassigned; it prints the Issue URL. Link the relevant PR or issue with
|
|
26
|
+
`--link`:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
df handoff --link pr:<PR#> <<'EOF'
|
|
30
|
+
<the composed, scrubbed note — see structure below>
|
|
31
|
+
EOF
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Dual-register: two audiences, one artifact
|
|
35
|
+
|
|
36
|
+
Write the note for the **engineer**, and speak a separate plain sentence to the
|
|
37
|
+
**operator**.
|
|
38
|
+
|
|
39
|
+
Engineer-facing note (structured, precise):
|
|
40
|
+
|
|
41
|
+
```markdown
|
|
42
|
+
## Intent
|
|
43
|
+
What the operator asked for, in their words.
|
|
44
|
+
|
|
45
|
+
## Done so far
|
|
46
|
+
What is already built and where (branch or PR), and what is verified visually.
|
|
47
|
+
|
|
48
|
+
## Blocked — why
|
|
49
|
+
The specific boundary: which scope, protected path, dependency, or gate — and
|
|
50
|
+
what an engineer needs to decide or do next.
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Operator-facing sentence (plain, framed as progress):
|
|
54
|
+
|
|
55
|
+
> *"That touches sign-in security, which needs an engineer's hands. I've written
|
|
56
|
+
> up exactly what you want and everything I built, and handed it to the team —
|
|
57
|
+
> here's the link. Want to keep going on something else meanwhile?"*
|
|
58
|
+
|
|
59
|
+
## Scrub before you hand off
|
|
60
|
+
|
|
61
|
+
The note may be read outside the session — keep it clean. No secrets, no raw
|
|
62
|
+
diffs or stack traces, no internal jargon that leaks implementation the operator
|
|
63
|
+
never saw. State the intent and the blocker; link the PR for the code. Never
|
|
64
|
+
include anything you would not show the operator.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../skill-schema.json",
|
|
3
|
+
"name": "designer-handoff",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"summary": "Designer Mode: package a scrubbed, dual-register df handoff artifact when an ask needs an engineer (out-of-scope, protected path, dependency change, or N=2 gate thrash), framed to the operator as progress.",
|
|
6
|
+
"originatingRepo": "momentiq-ai/dark-factory",
|
|
7
|
+
"files": [
|
|
8
|
+
{ "template": "SKILL.md.tmpl", "target": "SKILL.md" }
|
|
9
|
+
],
|
|
10
|
+
"variables": {
|
|
11
|
+
"REPO_NAME": {
|
|
12
|
+
"description": "Display name of the consumer repo (e.g. 'Taxpilot').",
|
|
13
|
+
"source": "darkfactory.yaml: repo.displayName",
|
|
14
|
+
"default": "this repo"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: designer-ship
|
|
3
|
+
description: Use when a Designer Mode change is visually approved and ready to ship — confirm the brief's Done-when items, run gates, and open plus auto-merge a PR that carries the designer mode trailer and a Closes #N link with before/after screenshots embedded, relaying pipeline status to the operator in plain language.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /designer-ship — verify, ship, and relay in plain language
|
|
7
|
+
|
|
8
|
+
The change is visually approved. This skill takes it through the **unchanged**
|
|
9
|
+
production pipeline for {{REPO_NAME}} — branch, gates, PR, review, merge — and
|
|
10
|
+
narrates it to the operator as outcomes, never as plumbing.
|
|
11
|
+
|
|
12
|
+
The operator sees "it's in review" and "it's live". They never see git, a diff,
|
|
13
|
+
a PR number, or a critic finding.
|
|
14
|
+
|
|
15
|
+
## 1. Confirm the acceptance contract
|
|
16
|
+
|
|
17
|
+
The brief's **Done-when checkboxes are the acceptance criteria.** Before
|
|
18
|
+
shipping, confirm each is satisfied and backed by the before/after screenshots
|
|
19
|
+
from `/designer-build` — a Done-when item with no visible proof is not done.
|
|
20
|
+
|
|
21
|
+
Make "done" proof-bound, not free text. Where the change has a
|
|
22
|
+
`.darkfactory/objectives.yaml`, verify it and read the closeout proof with
|
|
23
|
+
`df prove` (per-objective proven/pending/failed); see the `/objectives` skill
|
|
24
|
+
for authoring and binding mechanics. (Automated derivation of objectives
|
|
25
|
+
directly from a brief is a later Designer-Mode phase; today, treat the Done-when
|
|
26
|
+
list as the contract and verify each item visually.)
|
|
27
|
+
|
|
28
|
+
## 2. Run the gates locally first
|
|
29
|
+
|
|
30
|
+
Run the repo's configured quality gates before pushing (build, tests, and
|
|
31
|
+
type-check as the repo defines them). `df gate-push` runs the local critic on
|
|
32
|
+
HEAD at push time and blocks on unresolved blockers — let it; never bypass a
|
|
33
|
+
gate or ask the operator to.
|
|
34
|
+
|
|
35
|
+
## 3. Branch, commit, PR — with the mode trailers
|
|
36
|
+
|
|
37
|
+
Do the work on a branch (a worktree, per repo doctrine), then commit with the
|
|
38
|
+
Designer Mode trailers:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
Mode: designer
|
|
42
|
+
Closes #<N>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The `Mode: designer` trailer tags the PR as designer-driven. Today, scope is
|
|
46
|
+
enforced live by the PreToolUse guard during the session, and the change still
|
|
47
|
+
faces the full multi-vendor critic quorum + merge queue like any other PR. The
|
|
48
|
+
deterministic mode-scope critic — which reads this trailer to validate the diff
|
|
49
|
+
against the catalog's writeScopes / protectedPaths / diffBudget and block an
|
|
50
|
+
out-of-scope diff at merge time, even from a jailbroken local session — lands in
|
|
51
|
+
a later Designer-Mode phase; the trailer is the label it will key on.
|
|
52
|
+
`Closes #<N>` links the tracking issue.
|
|
53
|
+
|
|
54
|
+
Open the PR and embed the **before/after screenshots** in the body so the change
|
|
55
|
+
is visible to reviewers and the operator:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
gh pr create --base main --title "<plain, conventional-commit title>" \
|
|
59
|
+
--body "<what changed, before/after screenshots, Closes #<N>>"
|
|
60
|
+
gh pr merge <PR#> --auto --squash
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Auto-merge carries the PR into the merge queue; the full critic quorum, required
|
|
64
|
+
checks, and review-thread resolution gate it. You do not admin-merge or bypass.
|
|
65
|
+
|
|
66
|
+
## 4. Relay status in plain language
|
|
67
|
+
|
|
68
|
+
Translate pipeline state into outcomes for the operator. Check the PR's
|
|
69
|
+
automated checks (`gh pr checks`) and the merge-queue state, then say it plainly:
|
|
70
|
+
|
|
71
|
+
- In review: *"It's in review with our automated checks — a couple passed, one
|
|
72
|
+
still running. I'll tell you when it's live."*
|
|
73
|
+
- Merged: *"It's live."*
|
|
74
|
+
- A check found something: fix it within the N=2 iteration ceiling (re-verify
|
|
75
|
+
the visuals after each fix). If it won't converge, or the fix needs work
|
|
76
|
+
outside your scope, switch to `/designer-handoff` — never surface the raw
|
|
77
|
+
finding or critic jargon to the operator.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../skill-schema.json",
|
|
3
|
+
"name": "designer-ship",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"summary": "Designer Mode: confirm the brief's Done-when items, run gates, and open + auto-merge a PR with the Mode: designer and Closes #N trailers and before/after screenshots, relaying pipeline status to the operator in plain language.",
|
|
6
|
+
"originatingRepo": "momentiq-ai/dark-factory",
|
|
7
|
+
"files": [
|
|
8
|
+
{ "template": "SKILL.md.tmpl", "target": "SKILL.md" }
|
|
9
|
+
],
|
|
10
|
+
"variables": {
|
|
11
|
+
"REPO_NAME": {
|
|
12
|
+
"description": "Display name of the consumer repo (e.g. 'Taxpilot').",
|
|
13
|
+
"source": "darkfactory.yaml: repo.displayName",
|
|
14
|
+
"default": "this repo"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: objectives
|
|
3
|
-
description: Use at PLAN TIME for any PR that implements a cycle's acceptance criteria — author the verifiable objectives contract before writing code. Run `df objectives derive --cycle <N> --apply` to generate .darkfactory/objectives.yaml from the linked
|
|
3
|
+
description: Use at PLAN TIME for any PR that implements a cycle's (or designer brief's) acceptance criteria — author the verifiable objectives contract before writing code. Run `df objectives derive --cycle <N> --apply` (or `--brief <path>` for a designer brief's Done-when checkboxes) to generate .darkfactory/objectives.yaml from the linked source, bind each objective's attestedBy to a real route or test, surface the manifest in your plan for agreement, then declare victory at closeout with `df prove` (not free-text "done").
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# /objectives — plan-time verifiable objectives authoring
|
|
@@ -51,6 +51,21 @@ recognized. Accepted item forms:
|
|
|
51
51
|
Each criterion must be **one physical line**. Multi-line criteria are
|
|
52
52
|
rejected because only that single line is hashed into the source binding.
|
|
53
53
|
|
|
54
|
+
**Designer briefs (`--brief`):** for Designer Mode work, derive from a committed
|
|
55
|
+
design brief instead of a cycle doc — `--cycle` and `--brief` are mutually
|
|
56
|
+
exclusive sibling input sources:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# One objective per `## Done-when` checkbox in .darkfactory/briefs/<slug>.md
|
|
60
|
+
df objectives derive --brief .darkfactory/briefs/<slug>.md --apply
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Each `- [ ]`/`- [x]` checkbox under `## Done-when` becomes one objective with id
|
|
64
|
+
`brief-<slug>#ac<k>` (`<slug>` from the brief filename), `source.kind: brief`, and
|
|
65
|
+
the same `text-hash` binding. The checkbox state is stripped before hashing, so
|
|
66
|
+
marking an item done never moves its hash. Bind / check / prove below are
|
|
67
|
+
identical to the cycle path.
|
|
68
|
+
|
|
54
69
|
The emitted manifest looks like:
|
|
55
70
|
|
|
56
71
|
```yaml
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-derived capture surfaces for the Dark Factory **`ui-visual`** verification
|
|
3
|
+
* route (Designer Mode, cycle 27 Phase C).
|
|
4
|
+
*
|
|
5
|
+
* The `playwright` route (`coverage.ts` + `ui-route.producer.spec.ts`) makes a
|
|
6
|
+
* human hand-author `SURFACES[]` — one entry per URL, with a `covers` glob and a
|
|
7
|
+
* copy-stable `requiredHeading`. That is right for an engineer who knows the app.
|
|
8
|
+
* A non-technical Designer-Mode operator cannot. `ui-visual` instead DERIVES the
|
|
9
|
+
* capture surfaces from the changed Next.js **app-router** files: a changed
|
|
10
|
+
* `app/pricing/page.tsx` maps to the URL `/pricing`, which the producer navigates
|
|
11
|
+
* and screenshots. No `SURFACES[]` to edit.
|
|
12
|
+
*
|
|
13
|
+
* This module is PURE (no browser, no fs, no Dark Factory / vendor dependency) and
|
|
14
|
+
* is the load-bearing half of the route — the path→URL mapping and the fail-closed
|
|
15
|
+
* classification — so it is unit-tested without a browser (mirroring `coverage.ts`).
|
|
16
|
+
*
|
|
17
|
+
* ── CONSUMER REFERENCE FILE — ships in the npm tarball next to `coverage.ts`;
|
|
18
|
+
* copy it into your repo alongside `ui-visual.producer.spec.ts` and own it.
|
|
19
|
+
* `df skills install verify` does NOT overwrite it.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** One capture surface derived from a changed app-router file. */
|
|
23
|
+
export interface DerivedSurface {
|
|
24
|
+
/** URL path to navigate (relative to baseURL), e.g. "/", "/pricing". Dynamic
|
|
25
|
+
* routes carry a `:param` placeholder (e.g. "/blog/:slug") and are NOT
|
|
26
|
+
* directly navigable — see `dynamic`. */
|
|
27
|
+
path: string;
|
|
28
|
+
/** Filesystem-safe slug for the evidence subdir, e.g. "home", "pricing". */
|
|
29
|
+
slug: string;
|
|
30
|
+
/** The changed source path this surface is the evidence for. */
|
|
31
|
+
covers: string;
|
|
32
|
+
/** True when the route has a `[param]` / `[...catch]` segment: the producer
|
|
33
|
+
* cannot navigate it without a concrete value, so it is recorded (for human
|
|
34
|
+
* review) rather than auto-captured. */
|
|
35
|
+
dynamic: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface DerivedSurfacePartition {
|
|
39
|
+
/** Static, navigable surfaces — the producer captures each. De-duped by slug. */
|
|
40
|
+
surfaces: DerivedSurface[];
|
|
41
|
+
/** Dynamic routes touched by the change — recorded (need a concrete param),
|
|
42
|
+
* not auto-navigated. De-duped by slug. */
|
|
43
|
+
dynamic: DerivedSurface[];
|
|
44
|
+
/** Changed product-UI paths with NO derivable route (a component, a lib, a
|
|
45
|
+
* non-`app/` `*.tsx`). Auto-derivation cannot know which page renders a
|
|
46
|
+
* component, so these FAIL CLOSED — the change must also touch the page(s)
|
|
47
|
+
* that render them, or the operator adds an explicit surface. Mirrors the
|
|
48
|
+
* `coverage.ts` fail-closed contract. */
|
|
49
|
+
uncovered: string[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// App-router files that render a routable surface. `route.*` (API route
|
|
53
|
+
// handlers) render no UI and are treated as non-visual (exempt). `default.*`
|
|
54
|
+
// (parallel-route fallbacks) render UI at their segment, so they count.
|
|
55
|
+
const ROUTE_FILE_BASENAMES = new Set(["page", "layout", "template", "default"]);
|
|
56
|
+
const ROUTE_FILE_RE = /\.(tsx|jsx|ts|js)$/;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Harness / non-UI files that are EXEMPT from the coverage requirement — the
|
|
60
|
+
* route's own machinery, styles, tests, type decls, config, data. A change to
|
|
61
|
+
* ONLY these does not require a capture (there is no rendered surface). Kept in
|
|
62
|
+
* sync with `coverage.ts`'s `NON_SURFACE_GLOBS` philosophy but self-contained.
|
|
63
|
+
*/
|
|
64
|
+
export const UI_VISUAL_NON_SURFACE_SUFFIXES: readonly string[] = [
|
|
65
|
+
".css",
|
|
66
|
+
".scss",
|
|
67
|
+
".json",
|
|
68
|
+
".md",
|
|
69
|
+
".mdx",
|
|
70
|
+
".d.ts",
|
|
71
|
+
".test.ts",
|
|
72
|
+
".test.tsx",
|
|
73
|
+
".spec.ts",
|
|
74
|
+
".spec.tsx",
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
function isNonSurface(path: string): boolean {
|
|
78
|
+
const lower = path.toLowerCase();
|
|
79
|
+
if (UI_VISUAL_NON_SURFACE_SUFFIXES.some((s) => lower.endsWith(s))) return true;
|
|
80
|
+
// The producer harness itself + Playwright config + test dirs.
|
|
81
|
+
if (/(^|\/)tests?(\/|$)/.test(lower)) return true;
|
|
82
|
+
if (/(^|\/)__tests__(\/|$)/.test(lower)) return true;
|
|
83
|
+
if (/(^|\/)(coverage|ui-visual-surfaces)\.ts$/.test(lower)) return true;
|
|
84
|
+
if (/playwright[^/]*\.config\.(ts|js|mjs|cjs)$/.test(lower)) return true;
|
|
85
|
+
if (/\.producer\.spec\.ts$/.test(lower)) return true;
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Map one changed source path to a route surface, or `null` when it maps to no
|
|
91
|
+
* navigable route (a component, a private `_folder` colocation file, a file not
|
|
92
|
+
* under an `app/` router root, or an API `route.*` handler).
|
|
93
|
+
*/
|
|
94
|
+
export function deriveSurface(changedPath: string): DerivedSurface | null {
|
|
95
|
+
if (ROUTE_FILE_RE.test(changedPath) === false) return null;
|
|
96
|
+
const segments = changedPath.split("/");
|
|
97
|
+
const file = segments[segments.length - 1] ?? "";
|
|
98
|
+
const dotIdx = file.indexOf(".");
|
|
99
|
+
const base = dotIdx >= 0 ? file.slice(0, dotIdx) : file;
|
|
100
|
+
if (!ROUTE_FILE_BASENAMES.has(base)) return null;
|
|
101
|
+
|
|
102
|
+
// Locate the app-router root: the LAST `app` segment (handles `web/app/…`,
|
|
103
|
+
// `apps/web/src/app/…`, `src/app/…`). No `app/` root ⇒ not a routed file.
|
|
104
|
+
const appIdx = segments.lastIndexOf("app");
|
|
105
|
+
if (appIdx < 0) return null;
|
|
106
|
+
|
|
107
|
+
const routeDirSegments = segments.slice(appIdx + 1, segments.length - 1);
|
|
108
|
+
const urlSegments: string[] = [];
|
|
109
|
+
let dynamic = false;
|
|
110
|
+
for (const seg of routeDirSegments) {
|
|
111
|
+
// Route groups `(marketing)` and intercepting markers `(.)`,`(..)` — removed
|
|
112
|
+
// from the URL.
|
|
113
|
+
if (seg.startsWith("(") && seg.endsWith(")")) continue;
|
|
114
|
+
// Parallel-route slots `@modal` — not part of the URL.
|
|
115
|
+
if (seg.startsWith("@")) continue;
|
|
116
|
+
// Private folders `_components` opt OUT of routing — the file is colocation,
|
|
117
|
+
// not a route.
|
|
118
|
+
if (seg.startsWith("_")) return null;
|
|
119
|
+
// Dynamic segments `[slug]`, `[...all]`, `[[...opt]]` → `:slug` placeholder.
|
|
120
|
+
const dyn = seg.match(/^\[+\.{0,3}(.+?)\]+$/);
|
|
121
|
+
if (dyn) {
|
|
122
|
+
dynamic = true;
|
|
123
|
+
urlSegments.push(`:${dyn[1]}`);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
urlSegments.push(seg);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const path = urlSegments.length === 0 ? "/" : `/${urlSegments.join("/")}`;
|
|
130
|
+
const slug = slugForUrl(urlSegments);
|
|
131
|
+
return { path, slug, covers: changedPath, dynamic };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function sanitizeSlug(raw: string): string {
|
|
135
|
+
return raw
|
|
136
|
+
.replace(/:/g, "")
|
|
137
|
+
.replace(/[^a-z0-9-]+/gi, "-")
|
|
138
|
+
.replace(/-+/g, "-")
|
|
139
|
+
.replace(/^-|-$/g, "")
|
|
140
|
+
.toLowerCase();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function slugForUrl(urlSegments: readonly string[]): string {
|
|
144
|
+
if (urlSegments.length === 0) return "home";
|
|
145
|
+
return sanitizeSlug(urlSegments.join("-"));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Partition changed paths (already restricted to the route's trigger surface —
|
|
150
|
+
* `web/**` / `*.tsx` — by `playwright-route.sh`) into the surfaces to capture,
|
|
151
|
+
* the dynamic routes to record, and the uncovered product paths that FAIL CLOSED.
|
|
152
|
+
*
|
|
153
|
+
* An empty `changed` (a manual run with the env unset) yields nothing derivable;
|
|
154
|
+
* the producer falls back to capturing the home surface so evidence is never zero.
|
|
155
|
+
*/
|
|
156
|
+
export function deriveSurfacesFromChangedPaths(
|
|
157
|
+
changed: readonly string[],
|
|
158
|
+
): DerivedSurfacePartition {
|
|
159
|
+
const surfacesBySlug = new Map<string, DerivedSurface>();
|
|
160
|
+
const dynamicBySlug = new Map<string, DerivedSurface>();
|
|
161
|
+
const uncovered: string[] = [];
|
|
162
|
+
|
|
163
|
+
for (const raw of changed) {
|
|
164
|
+
const path = raw.trim();
|
|
165
|
+
if (!path) continue;
|
|
166
|
+
if (isNonSurface(path)) continue; // harness / non-UI — exempt.
|
|
167
|
+
const surface = deriveSurface(path);
|
|
168
|
+
if (surface === null) {
|
|
169
|
+
uncovered.push(path);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const bucket = surface.dynamic ? dynamicBySlug : surfacesBySlug;
|
|
173
|
+
if (!bucket.has(surface.slug)) bucket.set(surface.slug, surface);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
surfaces: [...surfacesBySlug.values()],
|
|
178
|
+
dynamic: [...dynamicBySlug.values()],
|
|
179
|
+
uncovered,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The home surface, smoke-captured when the change armed the route but derived NO
|
|
185
|
+
* navigable surface AND touched no dynamic/uncovered path (a harness-only or
|
|
186
|
+
* signal-less manual run) — so the route never passes with zero evidence.
|
|
187
|
+
*/
|
|
188
|
+
export const UI_VISUAL_HOME_FALLBACK: DerivedSurface = {
|
|
189
|
+
path: "/",
|
|
190
|
+
slug: "home",
|
|
191
|
+
covers: "(smoke)",
|
|
192
|
+
dynamic: false,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/** The producer's capture decision: what to navigate, or why to fail closed. */
|
|
196
|
+
export interface CapturePlan {
|
|
197
|
+
/** Surfaces the producer captures (empty when `failClosed` is set). */
|
|
198
|
+
capture: DerivedSurface[];
|
|
199
|
+
/** Non-null when the route CANNOT produce real evidence and must fail the run
|
|
200
|
+
* (a dynamic page that can't be auto-navigated, or an uncovered product path).
|
|
201
|
+
* `null` means the plan is safe to capture. */
|
|
202
|
+
failClosed: { code: "uncovered" | "dynamic-unmapped"; paths: string[]; message: string } | null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Decide what the `ui-visual` producer captures for a partition of changed paths,
|
|
207
|
+
* or whether it must FAIL CLOSED. The fail-closed contract (extracted here so it is
|
|
208
|
+
* unit-tested without a browser):
|
|
209
|
+
*
|
|
210
|
+
* - `uncovered` path(s) present → fail closed (a bare component can't be evidenced
|
|
211
|
+
* by rendering an unrelated surface).
|
|
212
|
+
* - `dynamic` route(s) present → fail closed. A `[slug]` / `[...all]` page has no
|
|
213
|
+
* auto-navigable URL, so this auto-derive route cannot evidence it; capturing
|
|
214
|
+
* anything else (a static sibling, or the home fallback) would be a false pass
|
|
215
|
+
* for the changed page. Dynamic pages belong to the engineer `playwright` route
|
|
216
|
+
* (hand-authored concrete surfaces). Fail closed even when static surfaces are
|
|
217
|
+
* also present — a run is all-or-nothing.
|
|
218
|
+
* - otherwise capture the derived static surfaces; if there are none, smoke-capture
|
|
219
|
+
* the home surface so evidence is never zero.
|
|
220
|
+
*/
|
|
221
|
+
export function planCapture(partition: DerivedSurfacePartition): CapturePlan {
|
|
222
|
+
const { surfaces, dynamic, uncovered } = partition;
|
|
223
|
+
|
|
224
|
+
if (uncovered.length > 0) {
|
|
225
|
+
return {
|
|
226
|
+
capture: [],
|
|
227
|
+
failClosed: {
|
|
228
|
+
code: "uncovered",
|
|
229
|
+
paths: [...uncovered],
|
|
230
|
+
message:
|
|
231
|
+
"ui-visual route: the following changed UI path(s) map to NO app-router " +
|
|
232
|
+
"surface, so this route cannot produce real evidence for them and FAILS " +
|
|
233
|
+
"CLOSED:\n" +
|
|
234
|
+
uncovered.map((p) => ` - ${p}`).join("\n") +
|
|
235
|
+
"\n\nAuto-derivation captures app-router pages (e.g. app/foo/page.tsx → " +
|
|
236
|
+
"/foo). A bare component has no route: also change (or add a page under " +
|
|
237
|
+
"app/ that renders) the affected UI, so the capture is real.",
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (dynamic.length > 0) {
|
|
243
|
+
return {
|
|
244
|
+
capture: [],
|
|
245
|
+
failClosed: {
|
|
246
|
+
code: "dynamic-unmapped",
|
|
247
|
+
paths: dynamic.map((d) => d.path),
|
|
248
|
+
message:
|
|
249
|
+
"ui-visual route: dynamic app-router page(s) changed. This auto-derive " +
|
|
250
|
+
"route does NOT support dynamic pages (a `[slug]` / `[...all]` route has " +
|
|
251
|
+
"no concrete URL to auto-navigate), so it FAILS CLOSED:\n" +
|
|
252
|
+
dynamic.map((d) => ` - ${d.covers} → ${d.path}`).join("\n") +
|
|
253
|
+
"\n\nCapture dynamic pages with the engineer `playwright` route instead " +
|
|
254
|
+
"(its hand-authored SURFACES[] take real params).",
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (surfaces.length > 0) return { capture: [...surfaces], failClosed: null };
|
|
260
|
+
return { capture: [UI_VISUAL_HOME_FALLBACK], failClosed: null };
|
|
261
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playwright config for the **`ui-visual` (Designer Mode) verification-route
|
|
3
|
+
* producer** (cycle 27 Phase C).
|
|
4
|
+
*
|
|
5
|
+
* DRY over the base UI-route config: it reuses every setting from
|
|
6
|
+
* `playwright.ui-route.config.ts` (one chromium project, no retries, optional
|
|
7
|
+
* storage-state, optional local-preview `webServer`, all the same env knobs) and
|
|
8
|
+
* changes ONLY `testMatch` so this config runs `ui-visual.producer.spec.ts`
|
|
9
|
+
* instead of `ui-route.producer.spec.ts`. Kept a separate file so the shared
|
|
10
|
+
* `playwright-route.sh` can select it via `DF_UI_ROUTE_CONFIG=ui-visual.config.ts`
|
|
11
|
+
* without disturbing the `playwright` route.
|
|
12
|
+
*
|
|
13
|
+
* ── CONSUMER REFERENCE FILE — copy into your repo next to the base config +
|
|
14
|
+
* the producer spec and own it. `df skills install verify` does NOT overwrite it.
|
|
15
|
+
*/
|
|
16
|
+
import { defineConfig } from "@playwright/test";
|
|
17
|
+
import base from "./playwright.ui-route.config";
|
|
18
|
+
|
|
19
|
+
export default defineConfig({
|
|
20
|
+
...base,
|
|
21
|
+
testMatch: /ui-visual\.producer\.spec\.ts$/,
|
|
22
|
+
});
|