@abarcenas/ai-workflow-template 1.45.0 → 1.47.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 +128 -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/.prettierignore +26 -0
- package/AGENTS.md +46 -3
- package/README.md +29 -2
- package/opencode.mcp.example.json +1 -0
- package/package.json +3 -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 +1881 -0
- package/scripts/sync.js +816 -239
- 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
|
@@ -9,10 +9,14 @@ This project includes AI-assisted development configuration distributed through
|
|
|
9
9
|
- `.opencode/agents/` — Specialized agent definitions for planning, research, and implementation
|
|
10
10
|
- `.agents/prompts/` — Reusable prompt templates
|
|
11
11
|
|
|
12
|
+
## Project-Specific Rules
|
|
13
|
+
|
|
14
|
+
Put project-specific agent rules in `AGENTS.project.md` at the repository root. That file is project-owned: sync never overwrites or prunes it, and `--force` cannot replace it. New projects load it automatically through the `instructions` entry in `opencode.json`.
|
|
15
|
+
|
|
12
16
|
## Core Workflow
|
|
13
17
|
|
|
14
18
|
1. **Understand before acting** — Read relevant instructions from `.agents/instructions/` before working with specific technologies
|
|
15
|
-
2. **Plan before implementing** —
|
|
19
|
+
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
20
|
3. **Track progress** — Update `memory-bank/activeContext.md` and `memory-bank/progress.md` after each task (see `.agents/instructions/task-implementation.instructions.md`)
|
|
17
21
|
4. **Maintain context** — Keep memory bank files updated (see `.agents/instructions/memory-bank.instructions.md`)
|
|
18
22
|
|
|
@@ -41,7 +45,7 @@ This project includes AI-assisted development configuration distributed through
|
|
|
41
45
|
|
|
42
46
|
## Available Skills
|
|
43
47
|
|
|
44
|
-
|
|
48
|
+
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
49
|
|
|
46
50
|
- `architecture-blueprint-generator` — Generate project architecture blueprints
|
|
47
51
|
- `conventional-commit` — Create conventional commit messages
|
|
@@ -69,6 +73,14 @@ Skills in `.agents/skills/` provide structured workflows for common tasks. Load
|
|
|
69
73
|
- `caveman-stats` — Tracking and statistics for caveman usage
|
|
70
74
|
- `cavecrew` — Multi-agent coordination for caveman skills
|
|
71
75
|
- `compress` — General compression utilities
|
|
76
|
+
- `domain-modeling` — Build and sharpen the project's domain model (`GLOSSARY.md`, ADRs)
|
|
77
|
+
- `graphify-framework-aware` — Exclude framework boilerplate from graphify knowledge graphs
|
|
78
|
+
- `grill-me` — Human-invoked router for a relentless interview (calls `grilling`)
|
|
79
|
+
- `grill-with-docs` — Human-invoked router for the interview plus glossary/ADR writing
|
|
80
|
+
- `grilling` — Stress-test a plan or idea with rounds of numbered questions
|
|
81
|
+
- `lighthouse-cli` — Run Lighthouse performance, accessibility, and SEO audits
|
|
82
|
+
- `memory-search` — Semantic vector search over the memory bank
|
|
83
|
+
- `prototype` — Build a throwaway prototype to answer a design question
|
|
72
84
|
|
|
73
85
|
## Available Prompts
|
|
74
86
|
|
|
@@ -79,13 +91,44 @@ Prompts in `.agents/prompts/` can be invoked directly:
|
|
|
79
91
|
- `playwright-generate-test` — Generate Playwright tests
|
|
80
92
|
- `documentation-writer` — Write documentation (Diataxis framework)
|
|
81
93
|
|
|
94
|
+
## Agent skills
|
|
95
|
+
|
|
96
|
+
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.
|
|
97
|
+
|
|
98
|
+
### Skills
|
|
99
|
+
|
|
100
|
+
| Skill | Path | Use it when |
|
|
101
|
+
|-------|------|-------------|
|
|
102
|
+
| `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. |
|
|
103
|
+
| `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. |
|
|
104
|
+
| `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. |
|
|
105
|
+
| `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. |
|
|
106
|
+
| `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. |
|
|
107
|
+
|
|
108
|
+
`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.
|
|
109
|
+
|
|
110
|
+
### Scope agent
|
|
111
|
+
|
|
112
|
+
`.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.
|
|
113
|
+
|
|
114
|
+
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).
|
|
115
|
+
|
|
116
|
+
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).
|
|
117
|
+
|
|
118
|
+
### Domain docs
|
|
119
|
+
|
|
120
|
+
`.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.
|
|
121
|
+
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.
|
|
122
|
+
|
|
82
123
|
## Available Agent Workflows
|
|
83
124
|
|
|
84
125
|
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
126
|
|
|
86
127
|
### Plan Mode — Strategic Planning & Architecture
|
|
87
128
|
|
|
88
|
-
|
|
129
|
+
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.
|
|
130
|
+
|
|
131
|
+
`.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
132
|
|
|
90
133
|
1. **Understand the Goal** — Ask clarifying questions about requirements and goals
|
|
91
134
|
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)
|
|
@@ -75,8 +75,29 @@ npm run sync # Normal sync
|
|
|
75
75
|
- New files are copied automatically
|
|
76
76
|
- Unmodified files are refreshed from the package
|
|
77
77
|
- Locally edited files are preserved (skipped by default)
|
|
78
|
+
- Project-owned files are never overwritten or pruned after creation, and `--force` cannot replace them
|
|
78
79
|
- GitHub Actions workflows are excluded from consumer sync (`.github/workflows/`)
|
|
79
80
|
|
|
81
|
+
> `--dry-run` previews copies and prunes, but missing scaffold files (memory-bank stubs, `opencode.json`, `AGENTS.project.md`, `.prettierignore`) are still created. This matches the pre-existing scaffold behaviour.
|
|
82
|
+
|
|
83
|
+
### Project-owned files
|
|
84
|
+
|
|
85
|
+
`AGENTS.project.md` is the supported home for project-specific agent rules. It is created if absent, then treated as project-owned: sync never overwrites or prunes it, `--force` cannot replace it, and deleting it recreates it on the next sync. New projects load it through `"instructions": ["AGENTS.project.md"]` in `opencode.json`; a project created before that entry existed must add it once, because sync never rewrites an existing `opencode.json`.
|
|
86
|
+
|
|
87
|
+
The same ownership rule protects `opencode.json`, `.prettierignore`, and `.agents/instructions/learned-knowledge.instructions.md`. `.agents-sync-ignore` is also protected from overwrite and deletion, but sync does **not** read or honour its contents yet — do not rely on it.
|
|
88
|
+
|
|
89
|
+
`__project__/<file>` entries in `.agents-sync-manifest.json` are reporting metadata written only when sync creates the file. Files that already existed before the upgrade are intentionally absent, because sync has no managed baseline for them.
|
|
90
|
+
|
|
91
|
+
The package ships a `.prettierignore` and scaffolds it when absent. New projects get coverage automatically for `.agents/`, `.opencode/agents/`, `.opencode/commands/`, `AGENTS.md`, `skills-lock.json`, and every synced script; the shipped file also ignores `AGENTS.project.md`, though that file is not hash-managed. Existing projects keep their own `.prettierignore` untouched (it is project-owned), so if it does not cover every managed path sync prints one warning:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
[ai-workflow-template] Existing .prettierignore does not protect managed files; merge the patterns from node_modules/@abarcenas/ai-workflow-template/.prettierignore
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Merge those patterns manually — sync never edits the file. Template-side additions to `rootFiles` or `scriptsToSync` also require a matching manual line in an existing project; the shipped file only updates the defaults for new projects. A formatter run over managed files rewrites their hashes, so every later sync skips them and the consumer stops receiving template updates.
|
|
98
|
+
|
|
99
|
+
If sync warns `Skipped locally modified root file: AGENTS.md`, move those local rules into `AGENTS.project.md`, restore the managed file from `node_modules/@abarcenas/ai-workflow-template/AGENTS.md`, then re-run sync.
|
|
100
|
+
|
|
80
101
|
## Core Structure
|
|
81
102
|
|
|
82
103
|
Post-install, your project receives this structure:
|
|
@@ -84,12 +105,13 @@ Post-install, your project receives this structure:
|
|
|
84
105
|
```text
|
|
85
106
|
.agents/
|
|
86
107
|
├── instructions/ # Framework, workflow, and security guidance
|
|
87
|
-
├── skills/ #
|
|
108
|
+
├── skills/ # 34 specialized AI skills
|
|
88
109
|
└── prompts/ # Reusable prompt templates
|
|
89
110
|
.opencode/agents/ # Orchestrator + specialized sub-agents
|
|
90
111
|
.github/workflows/ # CI/CD automation (excluded from sync)
|
|
91
112
|
docs/ # Guides (e.g. playwright-mcp-configuration.md)
|
|
92
113
|
AGENTS.md # Master instruction file with agent guidance
|
|
114
|
+
AGENTS.project.md # Project-owned rules (recreated if deleted, never overwritten)
|
|
93
115
|
```
|
|
94
116
|
|
|
95
117
|
## Multi-Agent Orchestrator
|
|
@@ -99,6 +121,7 @@ AGENTS.md # Master instruction file with agent guidance
|
|
|
99
121
|
| Agent | File | Role |
|
|
100
122
|
| --- | --- | --- |
|
|
101
123
|
| Orchestrator | `.opencode/agents/orchestrator/orchestrator.agent.md` | Master — analyzes requests, builds pipelines, delegates |
|
|
124
|
+
| Scope | `.opencode/agents/scope.agent.md` | Conditional front door for underspecified requests — grilling rounds, then a spike doc |
|
|
102
125
|
| Researcher | `.opencode/agents/researcher.agent.md` | Technical investigation, library evaluation |
|
|
103
126
|
| Architect | `.opencode/agents/architect.agent.md` | System design, component architecture |
|
|
104
127
|
| Implementer | `.opencode/agents/implementer.agent.md` | Translates designs into implementation plans |
|
|
@@ -121,6 +144,7 @@ Invoke the orchestrator with `@orchestrator` followed by your task:
|
|
|
121
144
|
```
|
|
122
145
|
|
|
123
146
|
- **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.
|
|
147
|
+
- **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
148
|
- **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
149
|
|
|
126
150
|
### MCP Tooling
|
|
@@ -222,6 +246,7 @@ All memory-bank files use YAML frontmatter (`id`, `title`, `updated`, `tags`, `e
|
|
|
222
246
|
|
|
223
247
|
## Featured Skills
|
|
224
248
|
|
|
249
|
+
- **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
250
|
- **Workflow automation:** `architecture-blueprint-generator`, `create-implementation-plan`, `create-readme`, `context-map`, `graphify` (global skill), `graphify-framework-aware`
|
|
226
251
|
- **Development tools:** `playwright-explore-website`, `playwright-generate-test`, `github-issues`, `gh-cli`, `polygot-test-agent`, `microsoft-docs`
|
|
227
252
|
- **Token & efficiency:** `caveman`, `caveman-compress`, `compress`, `caveman-commit`, `cavecrew`, `caveman-stats`
|
|
@@ -283,6 +308,8 @@ npm run memory:normalize # Normalize frontmatter (runs automatically on instal
|
|
|
283
308
|
|
|
284
309
|
- Uses `INIT_CWD` to install against the consuming project (not the package dir)
|
|
285
310
|
- Local changes are always preserved unless `--force` is used
|
|
311
|
+
- Project-owned files (`AGENTS.project.md`, `opencode.json`, `.prettierignore`, learned knowledge) are created if absent and never overwritten or pruned after that, even with `--force`
|
|
312
|
+
- Do not add `AGENTS.project.md` or `opencode.json` to `package.json` `files`: both are scaffolded create-if-absent and must never ship from the package
|
|
286
313
|
- `AGENTS.md` is the main instruction file agents should read
|
|
287
314
|
- Package assumes the public npm registry
|
|
288
315
|
- Playwright MCP integration is documented in `docs/`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abarcenas/ai-workflow-template",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.47.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": {
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"url": "https://github.com/abarcenas29/ai-workflow-template/issues"
|
|
39
39
|
},
|
|
40
40
|
"homepage": "https://github.com/abarcenas29/ai-workflow-template#readme",
|
|
41
|
+
"//": "Never add AGENTS.project.md or opencode.json to files: both are scaffolded create-if-absent at the consumer root and must not ship from the package.",
|
|
41
42
|
"files": [
|
|
42
43
|
"bin/",
|
|
43
44
|
".agents/",
|
|
@@ -49,6 +50,7 @@
|
|
|
49
50
|
".husky/",
|
|
50
51
|
"memory-bank/.vocabulary.json",
|
|
51
52
|
"AGENTS.md",
|
|
53
|
+
".prettierignore",
|
|
52
54
|
"opencode.mcp.example.json",
|
|
53
55
|
"skills-lock.json",
|
|
54
56
|
".husky/post-merge",
|
|
@@ -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
|
+
}
|