@abarcenas/ai-workflow-template 1.45.0 → 1.46.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.
- package/.agents/config/README.md +43 -0
- package/.agents/config/domain.md +37 -0
- package/.agents/instructions/learned-knowledge.instructions.md +110 -0
- package/.agents/instructions/memory-bank.instructions.md +11 -0
- package/.agents/skills/domain-modeling/ADR-FORMAT.md +47 -0
- package/.agents/skills/domain-modeling/GLOSSARY-FORMAT.md +60 -0
- package/.agents/skills/domain-modeling/SKILL.md +74 -0
- package/.agents/skills/grill-me/SKILL.md +7 -0
- package/.agents/skills/grill-with-docs/SKILL.md +7 -0
- package/.agents/skills/grilling/SKILL.md +28 -0
- package/.agents/skills/prototype/LOGIC.md +67 -0
- package/.agents/skills/prototype/SKILL.md +26 -0
- package/.agents/skills/prototype/UI.md +112 -0
- package/.github/workflows/npm-publish.yml +10 -0
- package/.github/workflows/pr-test.yml +31 -0
- package/.opencode/agents/orchestrator/orchestrator.agent.md +68 -3
- package/.opencode/agents/scope.agent.md +299 -0
- package/AGENTS.md +42 -3
- package/README.md +5 -2
- package/package.json +1 -1
- package/scripts/bump-version.test.js +238 -0
- package/scripts/mcp/playwright-mcp-launcher.js +55 -29
- package/scripts/mcp/playwright-mcp-launcher.test.js +217 -0
- package/scripts/mcp-memory-server.js +93 -76
- package/scripts/mcp-memory-server.test.js +398 -0
- package/scripts/memory-cli.test.js +297 -0
- package/scripts/memory-index.js +119 -31
- package/scripts/memory-index.test.js +1173 -0
- package/scripts/normalize-memory.js +26 -1
- package/scripts/normalize-memory.test.js +753 -0
- package/scripts/setup/discover.test.js +124 -2
- package/scripts/setup/hooks.test.js +188 -1
- package/scripts/setup/husky-init.test.js +311 -0
- package/scripts/setup/index.js +35 -5
- package/scripts/setup/index.test.js +279 -0
- package/scripts/setup/knowledgebase.test.js +100 -0
- package/scripts/setup/skills-phase.js +568 -0
- package/scripts/setup/skills-phase.test.js +1274 -0
- package/scripts/setup/sync-phase.js +6 -1
- package/scripts/setup/sync-phase.test.js +180 -2
- package/scripts/setup/ui.test.js +579 -0
- package/scripts/setup/utils.test.js +586 -0
- package/scripts/sync-unit.test.js +1342 -0
- package/scripts/sync.js +540 -246
- package/scripts/sync.test.js +283 -0
- package/scripts/vocab-sync.test.js +88 -0
- package/skills-lock.json +90 -0
package/AGENTS.md
CHANGED
|
@@ -12,7 +12,7 @@ This project includes AI-assisted development configuration distributed through
|
|
|
12
12
|
## Core Workflow
|
|
13
13
|
|
|
14
14
|
1. **Understand before acting** — Read relevant instructions from `.agents/instructions/` before working with specific technologies
|
|
15
|
-
2. **Plan before implementing** —
|
|
15
|
+
2. **Plan before implementing** — Narrow underspecified work with the `scope` agent first, then use the `.opencode/agents/plan.agent.md` (optional deep planning) or `.opencode/agents/implementer.agent.md` patterns for structured work
|
|
16
16
|
3. **Track progress** — Update `memory-bank/activeContext.md` and `memory-bank/progress.md` after each task (see `.agents/instructions/task-implementation.instructions.md`)
|
|
17
17
|
4. **Maintain context** — Keep memory bank files updated (see `.agents/instructions/memory-bank.instructions.md`)
|
|
18
18
|
|
|
@@ -41,7 +41,7 @@ This project includes AI-assisted development configuration distributed through
|
|
|
41
41
|
|
|
42
42
|
## Available Skills
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
All 34 skills installed in `.agents/skills/` are listed below. Load one when the task matches its purpose. The five scoping skills (`domain-modeling`, `grill-me`, `grill-with-docs`, `grilling`, `prototype`) are described in detail in [`## Agent skills`](#agent-skills).
|
|
45
45
|
|
|
46
46
|
- `architecture-blueprint-generator` — Generate project architecture blueprints
|
|
47
47
|
- `conventional-commit` — Create conventional commit messages
|
|
@@ -69,6 +69,14 @@ Skills in `.agents/skills/` provide structured workflows for common tasks. Load
|
|
|
69
69
|
- `caveman-stats` — Tracking and statistics for caveman usage
|
|
70
70
|
- `cavecrew` — Multi-agent coordination for caveman skills
|
|
71
71
|
- `compress` — General compression utilities
|
|
72
|
+
- `domain-modeling` — Build and sharpen the project's domain model (`GLOSSARY.md`, ADRs)
|
|
73
|
+
- `graphify-framework-aware` — Exclude framework boilerplate from graphify knowledge graphs
|
|
74
|
+
- `grill-me` — Human-invoked router for a relentless interview (calls `grilling`)
|
|
75
|
+
- `grill-with-docs` — Human-invoked router for the interview plus glossary/ADR writing
|
|
76
|
+
- `grilling` — Stress-test a plan or idea with rounds of numbered questions
|
|
77
|
+
- `lighthouse-cli` — Run Lighthouse performance, accessibility, and SEO audits
|
|
78
|
+
- `memory-search` — Semantic vector search over the memory bank
|
|
79
|
+
- `prototype` — Build a throwaway prototype to answer a design question
|
|
72
80
|
|
|
73
81
|
## Available Prompts
|
|
74
82
|
|
|
@@ -79,13 +87,44 @@ Prompts in `.agents/prompts/` can be invoked directly:
|
|
|
79
87
|
- `playwright-generate-test` — Generate Playwright tests
|
|
80
88
|
- `documentation-writer` — Write documentation (Diataxis framework)
|
|
81
89
|
|
|
90
|
+
## Agent skills
|
|
91
|
+
|
|
92
|
+
Five skills installed from `mattpocock/skills` (pinned at `v1.3.1`) drive scoping and design work. They sit beside the other skills in `.agents/skills/`; this section explains when to reach for them and what they pair with.
|
|
93
|
+
|
|
94
|
+
### Skills
|
|
95
|
+
|
|
96
|
+
| Skill | Path | Use it when |
|
|
97
|
+
|-------|------|-------------|
|
|
98
|
+
| `grilling` | `.agents/skills/grilling/SKILL.md` | A plan, decision, or idea needs stress-testing, or a request is too vague to plan. Works a design tree in rounds: the current frontier is asked as numbered questions, each with a recommended answer. |
|
|
99
|
+
| `grill-me` | `.agents/skills/grill-me/SKILL.md` | You want the same interview with no repository context and no document output — a stateless, one-shot entry point. |
|
|
100
|
+
| `grill-with-docs` | `.agents/skills/grill-with-docs/SKILL.md` | You want the interview plus the written record — the glossary and ADRs are created as decisions settle. |
|
|
101
|
+
| `domain-modeling` | `.agents/skills/domain-modeling/SKILL.md` | Project terminology or an architectural decision is being introduced or changed and `GLOSSARY.md` or an ADR needs to capture it. |
|
|
102
|
+
| `prototype` | `.agents/skills/prototype/SKILL.md` | A design question can only be settled by running throwaway code — the logic branch for state models and data flow, the UI branch for look and feel. |
|
|
103
|
+
|
|
104
|
+
`grill-me` and `grill-with-docs` are **human-invoked routers**: both set `disable-model-invocation: true`, so an agent cannot load them. Agents invoke `grilling` directly, plus `domain-modeling` when a term or decision actually settles.
|
|
105
|
+
|
|
106
|
+
### Scope agent
|
|
107
|
+
|
|
108
|
+
`.opencode/agents/scope.agent.md` is the front door for underspecified work. It runs `grilling` in rounds against a single draft file, emits each round's frontier as numbered questions with a recommended answer per question, and stops when the frontier is empty or the remaining unknowns are explicitly deferred.
|
|
109
|
+
|
|
110
|
+
It terminates by writing `docs/spike-<feature>.md` — the spike document `.opencode/agents/researcher.agent.md` already expects as its input. From there the work continues to `researcher` (open questions need evidence), `implementer` (scope settled, planning remains), or `architect` (the shape of the system is the open problem).
|
|
111
|
+
|
|
112
|
+
The agent has no command-execution permission: it never builds, runs, or tests code. When a question can only be settled empirically it emits `prototype needed: <question>` with a branch, and that work is handed off to `coder` (logic) or `designer` (UI).
|
|
113
|
+
|
|
114
|
+
### Domain docs
|
|
115
|
+
|
|
116
|
+
`.agents/config/domain.md` declares where domain documentation lives: `GLOSSARY.md` at the repository root and ADRs in `docs/adr/`, numbered `0001-slug.md` onwards — the single-context layout.
|
|
117
|
+
The `domain-modeling` skill reads it before exploring the codebase; when the file or the docs it names are absent, the skill proceeds silently and creates them lazily, only once a term or decision has actually been settled.
|
|
118
|
+
|
|
82
119
|
## Available Agent Workflows
|
|
83
120
|
|
|
84
121
|
These agent workflows (from `.opencode/agents/`) define specialized modes of operation. Invoke the relevant workflow at the start of a task by stating which mode you want to activate.
|
|
85
122
|
|
|
86
123
|
### Plan Mode — Strategic Planning & Architecture
|
|
87
124
|
|
|
88
|
-
|
|
125
|
+
Underspecified work does not start here. The default front door is the **`scope` agent** (see [`## Agent skills`](#agent-skills)): it narrows the request through grilling rounds and writes `docs/spike-<feature>.md`, which is handed to `researcher` when open questions need evidence, or to `implementer` when only planning remains.
|
|
126
|
+
|
|
127
|
+
`.opencode/agents/plan.agent.md` remains available as an **optional deep-planning mode** for work that is already scoped but needs a step-by-step plan. It is not deleted and not deprecated — it is demoted in the default flow only. When you use it, follow this sequence:
|
|
89
128
|
|
|
90
129
|
1. **Understand the Goal** — Ask clarifying questions about requirements and goals
|
|
91
130
|
2. **Explore Context** — Examine relevant files, components, and systems that will be affected
|
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ npx ai-workflow-setup --help # Show all options
|
|
|
52
52
|
|
|
53
53
|
## What This Package Does
|
|
54
54
|
|
|
55
|
-
- **
|
|
55
|
+
- **34 specialized AI skills**, framework instructions, and prompt templates (`.agents/`)
|
|
56
56
|
- **Smart agent frameworks** — Plan Mode, Implementation Plans, Research Spikes
|
|
57
57
|
- **Multi-agent orchestrator** — dynamic pipelines with 10+ specialized sub-agents
|
|
58
58
|
- **TDD orchestrator** — strict RED→GREEN→REFACTOR/VERIFY pipeline (90% coverage gate)
|
|
@@ -84,7 +84,7 @@ Post-install, your project receives this structure:
|
|
|
84
84
|
```text
|
|
85
85
|
.agents/
|
|
86
86
|
├── instructions/ # Framework, workflow, and security guidance
|
|
87
|
-
├── skills/ #
|
|
87
|
+
├── skills/ # 34 specialized AI skills
|
|
88
88
|
└── prompts/ # Reusable prompt templates
|
|
89
89
|
.opencode/agents/ # Orchestrator + specialized sub-agents
|
|
90
90
|
.github/workflows/ # CI/CD automation (excluded from sync)
|
|
@@ -99,6 +99,7 @@ AGENTS.md # Master instruction file with agent guidance
|
|
|
99
99
|
| Agent | File | Role |
|
|
100
100
|
| --- | --- | --- |
|
|
101
101
|
| Orchestrator | `.opencode/agents/orchestrator/orchestrator.agent.md` | Master — analyzes requests, builds pipelines, delegates |
|
|
102
|
+
| Scope | `.opencode/agents/scope.agent.md` | Conditional front door for underspecified requests — grilling rounds, then a spike doc |
|
|
102
103
|
| Researcher | `.opencode/agents/researcher.agent.md` | Technical investigation, library evaluation |
|
|
103
104
|
| Architect | `.opencode/agents/architect.agent.md` | System design, component architecture |
|
|
104
105
|
| Implementer | `.opencode/agents/implementer.agent.md` | Translates designs into implementation plans |
|
|
@@ -121,6 +122,7 @@ Invoke the orchestrator with `@orchestrator` followed by your task:
|
|
|
121
122
|
```
|
|
122
123
|
|
|
123
124
|
- **Run flow:** auto-bootstraps architecture on first run → builds a pipeline → executes agents sequentially (confirm/skip each step, or enable auto-confirm) → tracker documents to `docs/tracker-log.md` → persists new knowledge.
|
|
125
|
+
- **Scope step (conditional, runs first):** inserted at the front of the pipeline only when the request is underspecified — `scope` runs grilling rounds against a draft file, writes `docs/spike-<feature>.md`, then hands off to `researcher` or `implementer`. Always interactive (auto-confirm cannot skip it) and has **no command-execution permission** — prototypes are delegated to `coder` (logic) or `designer` (UI).
|
|
124
126
|
- **First run only:** runs the architecture-blueprint-generator skill to detect tech stack/patterns, writing context to `docs/.architecture-context.md` and `.agents/instructions/learned-knowledge.instructions.md`; later runs skip this.
|
|
125
127
|
|
|
126
128
|
### MCP Tooling
|
|
@@ -222,6 +224,7 @@ All memory-bank files use YAML frontmatter (`id`, `title`, `updated`, `tags`, `e
|
|
|
222
224
|
|
|
223
225
|
## Featured Skills
|
|
224
226
|
|
|
227
|
+
- **Feature scoping:** `grilling`, `grill-me`, `grill-with-docs`, `domain-modeling`, `prototype` — narrow underspecified requests into a spike; `GLOSSARY.md` and `docs/adr/` locations come from `.agents/config/domain.md`
|
|
225
228
|
- **Workflow automation:** `architecture-blueprint-generator`, `create-implementation-plan`, `create-readme`, `context-map`, `graphify` (global skill), `graphify-framework-aware`
|
|
226
229
|
- **Development tools:** `playwright-explore-website`, `playwright-generate-test`, `github-issues`, `gh-cli`, `polygot-test-agent`, `microsoft-docs`
|
|
227
230
|
- **Token & efficiency:** `caveman`, `caveman-compress`, `compress`, `caveman-commit`, `cavecrew`, `caveman-stats`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abarcenas/ai-workflow-template",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.46.0",
|
|
4
4
|
"description": "A comprehensive template repository for AI-assisted development workflows using GitHub Copilot. This template provides a structured approach to leveraging AI assistance through pre-configured skills, prompts, agents, and coding standards.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// bump-version.test.js — Unit tests for the pre-commit version bumper
|
|
3
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// bump-version.js is a top-level script: it reads package.json on import,
|
|
6
|
+
// derives the previous major version from `git show HEAD:package.json`, writes
|
|
7
|
+
// the new version, and stages the file. Tests therefore mock `fs` and
|
|
8
|
+
// `child_process` so the repository's real package.json is never touched, then
|
|
9
|
+
// re-import the module for each scenario with `vi.resetModules()`.
|
|
10
|
+
//
|
|
11
|
+
// Covered scenarios:
|
|
12
|
+
// 1. SKIP_BUMP=1 → logs skip, exits 0, no fs/git calls
|
|
13
|
+
// 2. SKIP_BUMP=yes → same (any non-empty value is truthy)
|
|
14
|
+
// 3. SKIP_BUMP=0 → same (string "0" is truthy in Node)
|
|
15
|
+
// 4. SKIP_BUMP="" → falsy, bump proceeds
|
|
16
|
+
// 5. Minor bump (same major) → 1.4.7 → 1.5.0
|
|
17
|
+
// 6. Minor bump with multi-digit minor → 1.10.3 → 1.11.0
|
|
18
|
+
// 7. No HEAD (git show fails) → falls back to current major, bumps minor
|
|
19
|
+
// 8. Major changed → 3.2.1 → 3.0.0
|
|
20
|
+
// 9. Exact serialisation → 2-space JSON + trailing newline, fields preserved
|
|
21
|
+
// 10. Git commands → `git show HEAD:package.json` then `git add package.json`
|
|
22
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
23
|
+
|
|
24
|
+
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
25
|
+
|
|
26
|
+
vi.mock('fs', () => ({
|
|
27
|
+
readFileSync: vi.fn(),
|
|
28
|
+
writeFileSync: vi.fn(),
|
|
29
|
+
}))
|
|
30
|
+
|
|
31
|
+
vi.mock('child_process', () => ({
|
|
32
|
+
execSync: vi.fn(),
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
import { readFileSync, writeFileSync } from 'fs'
|
|
36
|
+
import { execSync } from 'child_process'
|
|
37
|
+
|
|
38
|
+
const EXIT_SENTINEL = new Error('__process_exit__')
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Re-imports bump-version.js with the current mock state.
|
|
42
|
+
*
|
|
43
|
+
* @returns {Promise<void>}
|
|
44
|
+
*/
|
|
45
|
+
async function loadModule() {
|
|
46
|
+
vi.resetModules()
|
|
47
|
+
await import('./bump-version.js')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Wires the fs/git mocks for a normal (non-skip) run.
|
|
52
|
+
*
|
|
53
|
+
* @param {object} opts
|
|
54
|
+
* @param {object} opts.pkg - Contents returned for package.json.
|
|
55
|
+
* @param {object} [opts.prevPkg] - Contents returned for HEAD:package.json.
|
|
56
|
+
* @param {boolean} [opts.noHead] - When true, `git show` throws.
|
|
57
|
+
*/
|
|
58
|
+
function mockNormalRun({ pkg, prevPkg, noHead = false }) {
|
|
59
|
+
readFileSync.mockReturnValue(JSON.stringify(pkg))
|
|
60
|
+
execSync.mockImplementation((cmd) => {
|
|
61
|
+
if (cmd === 'git show HEAD:package.json') {
|
|
62
|
+
if (noHead) throw new Error('fatal: invalid object name HEAD')
|
|
63
|
+
return JSON.stringify(prevPkg)
|
|
64
|
+
}
|
|
65
|
+
return ''
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
describe('bump-version.js', () => {
|
|
70
|
+
let logSpy
|
|
71
|
+
let exitSpy
|
|
72
|
+
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
vi.clearAllMocks()
|
|
75
|
+
delete process.env.SKIP_BUMP
|
|
76
|
+
logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
77
|
+
exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
afterEach(() => {
|
|
81
|
+
delete process.env.SKIP_BUMP
|
|
82
|
+
logSpy.mockRestore()
|
|
83
|
+
exitSpy.mockRestore()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// ── SKIP_BUMP guard ──────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
it('SKIP_BUMP=1: logs skip message, exits 0, touches nothing', async () => {
|
|
89
|
+
process.env.SKIP_BUMP = '1'
|
|
90
|
+
exitSpy.mockImplementation(() => {
|
|
91
|
+
throw EXIT_SENTINEL
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
await expect(loadModule()).rejects.toBe(EXIT_SENTINEL)
|
|
95
|
+
|
|
96
|
+
expect(logSpy).toHaveBeenCalledWith('SKIP_BUMP set — version bump skipped')
|
|
97
|
+
expect(exitSpy).toHaveBeenCalledWith(0)
|
|
98
|
+
expect(readFileSync).not.toHaveBeenCalled()
|
|
99
|
+
expect(writeFileSync).not.toHaveBeenCalled()
|
|
100
|
+
expect(execSync).not.toHaveBeenCalled()
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('SKIP_BUMP=yes: any non-empty value skips the bump', async () => {
|
|
104
|
+
process.env.SKIP_BUMP = 'yes'
|
|
105
|
+
exitSpy.mockImplementation(() => {
|
|
106
|
+
throw EXIT_SENTINEL
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
await expect(loadModule()).rejects.toBe(EXIT_SENTINEL)
|
|
110
|
+
|
|
111
|
+
expect(exitSpy).toHaveBeenCalledWith(0)
|
|
112
|
+
expect(writeFileSync).not.toHaveBeenCalled()
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('SKIP_BUMP=0: the string "0" is truthy, so the bump is skipped', async () => {
|
|
116
|
+
process.env.SKIP_BUMP = '0'
|
|
117
|
+
exitSpy.mockImplementation(() => {
|
|
118
|
+
throw EXIT_SENTINEL
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
await expect(loadModule()).rejects.toBe(EXIT_SENTINEL)
|
|
122
|
+
|
|
123
|
+
expect(exitSpy).toHaveBeenCalledWith(0)
|
|
124
|
+
expect(execSync).not.toHaveBeenCalled()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('SKIP_BUMP="": empty string is falsy, so the bump proceeds', async () => {
|
|
128
|
+
process.env.SKIP_BUMP = ''
|
|
129
|
+
mockNormalRun({
|
|
130
|
+
pkg: { name: 'fixture', version: '1.4.7' },
|
|
131
|
+
prevPkg: { version: '1.4.0' },
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
await loadModule()
|
|
135
|
+
|
|
136
|
+
expect(exitSpy).not.toHaveBeenCalled()
|
|
137
|
+
expect(logSpy).toHaveBeenCalledWith('Version bumped: 1.4.7 → 1.5.0')
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// ── Version arithmetic ───────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
it('same major: bumps the minor and resets the patch', async () => {
|
|
143
|
+
mockNormalRun({
|
|
144
|
+
pkg: { name: 'fixture', version: '1.4.7' },
|
|
145
|
+
prevPkg: { version: '1.4.0' },
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
await loadModule()
|
|
149
|
+
|
|
150
|
+
const written = JSON.parse(writeFileSync.mock.calls[0][1])
|
|
151
|
+
expect(written.version).toBe('1.5.0')
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('multi-digit minor: increments numerically (1.10.3 → 1.11.0)', async () => {
|
|
155
|
+
mockNormalRun({
|
|
156
|
+
pkg: { name: 'fixture', version: '1.10.3' },
|
|
157
|
+
prevPkg: { version: '1.9.9' },
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
await loadModule()
|
|
161
|
+
|
|
162
|
+
expect(logSpy).toHaveBeenCalledWith('Version bumped: 1.10.3 → 1.11.0')
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('no HEAD yet: catch path keeps the current major and bumps the minor', async () => {
|
|
166
|
+
mockNormalRun({
|
|
167
|
+
pkg: { name: 'fixture', version: '2.3.1' },
|
|
168
|
+
noHead: true,
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
await loadModule()
|
|
172
|
+
|
|
173
|
+
expect(logSpy).toHaveBeenCalledWith('Version bumped: 2.3.1 → 2.4.0')
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('major changed: normalises to N.0.0', async () => {
|
|
177
|
+
mockNormalRun({
|
|
178
|
+
pkg: { name: 'fixture', version: '3.2.1' },
|
|
179
|
+
prevPkg: { version: '2.9.9' },
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
await loadModule()
|
|
183
|
+
|
|
184
|
+
expect(logSpy).toHaveBeenCalledWith('Version bumped: 3.2.1 → 3.0.0')
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
// ── Serialisation and git side-effects ───────────────────────────────────
|
|
188
|
+
|
|
189
|
+
it('writes 2-space JSON with a trailing newline and preserves other fields', async () => {
|
|
190
|
+
const pkg = {
|
|
191
|
+
name: 'fixture',
|
|
192
|
+
version: '1.4.7',
|
|
193
|
+
scripts: { test: 'vitest' },
|
|
194
|
+
keywords: ['a', 'b'],
|
|
195
|
+
}
|
|
196
|
+
mockNormalRun({ pkg, prevPkg: { version: '1.0.0' } })
|
|
197
|
+
|
|
198
|
+
await loadModule()
|
|
199
|
+
|
|
200
|
+
const [path, contents] = writeFileSync.mock.calls[0]
|
|
201
|
+
expect(path).toMatch(/package\.json$/)
|
|
202
|
+
expect(contents.endsWith('\n')).toBe(true)
|
|
203
|
+
expect(contents).toBe(
|
|
204
|
+
JSON.stringify({ ...pkg, version: '1.5.0' }, null, 2) + '\n',
|
|
205
|
+
)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('reads HEAD:package.json then stages package.json', async () => {
|
|
209
|
+
mockNormalRun({
|
|
210
|
+
pkg: { name: 'fixture', version: '0.9.2' },
|
|
211
|
+
prevPkg: { version: '0.9.0' },
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
await loadModule()
|
|
215
|
+
|
|
216
|
+
expect(execSync).toHaveBeenNthCalledWith(
|
|
217
|
+
1,
|
|
218
|
+
'git show HEAD:package.json',
|
|
219
|
+
{ encoding: 'utf8' },
|
|
220
|
+
)
|
|
221
|
+
expect(execSync).toHaveBeenNthCalledWith(2, 'git add package.json')
|
|
222
|
+
expect(execSync).toHaveBeenCalledTimes(2)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('reads package.json from the package root', async () => {
|
|
226
|
+
mockNormalRun({
|
|
227
|
+
pkg: { name: 'fixture', version: '1.0.0' },
|
|
228
|
+
prevPkg: { version: '1.0.0' },
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
await loadModule()
|
|
232
|
+
|
|
233
|
+
expect(readFileSync).toHaveBeenCalledWith(
|
|
234
|
+
expect.stringMatching(/package\.json$/),
|
|
235
|
+
'utf8',
|
|
236
|
+
)
|
|
237
|
+
})
|
|
238
|
+
})
|
|
@@ -6,41 +6,67 @@
|
|
|
6
6
|
* based on environment variables and project settings.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
import { spawn } from 'node:child_process'
|
|
10
|
+
import { resolve } from 'node:path'
|
|
11
|
+
import { fileURLToPath } from 'node:url'
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Builds the `npx @playwright/mcp@latest` argument list from the environment.
|
|
15
|
+
*
|
|
16
|
+
* @param {NodeJS.ProcessEnv} [env=process.env] - Environment to read.
|
|
17
|
+
* @returns {string[]} Arguments passed to `npx`.
|
|
18
|
+
*/
|
|
19
|
+
export function buildArgs(env = process.env) {
|
|
20
|
+
const args = ['@playwright/mcp@latest']
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
if (env.HEADLESS === 'true') {
|
|
23
|
+
args.push('--headless')
|
|
24
|
+
}
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
26
|
+
if (env.VIEWPORT) {
|
|
27
|
+
args.push('--viewport', env.VIEWPORT)
|
|
28
|
+
}
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
if (process.env.VIEWPORT) {
|
|
26
|
-
args.push('--viewport', process.env.VIEWPORT);
|
|
30
|
+
return args
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
33
|
+
/**
|
|
34
|
+
* Spawns the Playwright MCP server and forwards signals / exit codes.
|
|
35
|
+
*
|
|
36
|
+
* @param {object} [options]
|
|
37
|
+
* @param {object} [options.env=process.env] - Environment to read.
|
|
38
|
+
* @param {object} [options.proc=process] - Process-like emitter for signals.
|
|
39
|
+
* @param {Function} [options.exit=process.exit] - Exit function.
|
|
40
|
+
* @returns {import('node:child_process').ChildProcess} The spawned child.
|
|
41
|
+
*/
|
|
42
|
+
export function launch({
|
|
43
|
+
env = process.env,
|
|
44
|
+
proc = process,
|
|
45
|
+
exit = process.exit,
|
|
46
|
+
} = {}) {
|
|
47
|
+
const child = spawn('npx', buildArgs(env), {
|
|
48
|
+
stdio: 'inherit',
|
|
49
|
+
shell: true,
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
proc.on('SIGINT', () => {
|
|
53
|
+
child.kill('SIGINT')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
proc.on('SIGTERM', () => {
|
|
57
|
+
child.kill('SIGTERM')
|
|
58
|
+
})
|
|
34
59
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
});
|
|
60
|
+
child.on('exit', (code) => {
|
|
61
|
+
exit(code || 0)
|
|
62
|
+
})
|
|
39
63
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
64
|
+
return child
|
|
65
|
+
}
|
|
43
66
|
|
|
44
|
-
|
|
45
|
-
process.
|
|
46
|
-
|
|
67
|
+
const isDirectRun =
|
|
68
|
+
process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
|
69
|
+
|
|
70
|
+
if (isDirectRun) {
|
|
71
|
+
launch()
|
|
72
|
+
}
|