feat(M1): Baseline verification, quality tooling foundation, and current-state report
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
/ai_plan/
|
/ai_plan/
|
||||||
/.pi/
|
/.pi/
|
||||||
/.worktrees/
|
/.worktrees/
|
||||||
|
/node_modules/
|
||||||
/skills/atlassian/shared/scripts/.env
|
/skills/atlassian/shared/scripts/.env
|
||||||
/skills/atlassian/shared/scripts/node_modules/
|
/skills/atlassian/shared/scripts/node_modules/
|
||||||
/skills/atlassian/*/scripts/.env
|
/skills/atlassian/*/scripts/.env
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// markdownlint-cli2 configuration — ai-coding-skills (M1)
|
||||||
|
//
|
||||||
|
// This file controls which files are linted and which are ignored.
|
||||||
|
// The markdownlint *rules* are configured in .markdownlint.jsonc.
|
||||||
|
//
|
||||||
|
// Scope: README.md, docs/, and canonical SKILL.md files.
|
||||||
|
// Excluded: all node_modules, generated agent-variant directories, pi-package.
|
||||||
|
{
|
||||||
|
// Glob patterns for which files to lint
|
||||||
|
"globs": [
|
||||||
|
"README.md",
|
||||||
|
"docs/**/*.md",
|
||||||
|
// Canonical skill files only — agent variants are excluded below
|
||||||
|
"skills/atlassian/shared/**/*.md",
|
||||||
|
"skills/create-plan/**/*.md",
|
||||||
|
"skills/do-task/**/*.md",
|
||||||
|
"skills/implement-plan/**/*.md",
|
||||||
|
"skills/reviewer-runtime/**/*.md",
|
||||||
|
"skills/web-automation/codex/**/*.md",
|
||||||
|
"skills/web-automation/pi/**/*.md",
|
||||||
|
"skills/web-automation/claude-code/SKILL.md",
|
||||||
|
"skills/web-automation/cursor/SKILL.md",
|
||||||
|
"skills/web-automation/opencode/SKILL.md",
|
||||||
|
"skills/atlassian/codex/SKILL.md",
|
||||||
|
"skills/atlassian/claude-code/SKILL.md",
|
||||||
|
"skills/atlassian/cursor/SKILL.md",
|
||||||
|
"skills/atlassian/opencode/SKILL.md",
|
||||||
|
"skills/atlassian/pi/SKILL.md"
|
||||||
|
],
|
||||||
|
|
||||||
|
// Ignore patterns — always exclude node_modules and generated artefacts
|
||||||
|
"ignores": [
|
||||||
|
"**/node_modules/**",
|
||||||
|
"pi-package/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// markdownlint configuration — ai-coding-skills (M1)
|
||||||
|
//
|
||||||
|
// Pre-existing violations in docs/ and skills/ SKILL.md files are recorded
|
||||||
|
// in docs/CLEANUP-BASELINE.md and deferred to a later milestone for fixing.
|
||||||
|
// New markdown files added from M1 onward must satisfy all enabled rules.
|
||||||
|
{
|
||||||
|
// Inherit all default rules, then override below
|
||||||
|
"default": true,
|
||||||
|
|
||||||
|
// MD013 — line length
|
||||||
|
// This project contains long technical strings, code snippets, and URLs
|
||||||
|
// in markdown prose. Enforce a generous limit rather than the strict 80-char
|
||||||
|
// default to avoid noise on otherwise-clean documents.
|
||||||
|
"MD013": {
|
||||||
|
"line_length": 120,
|
||||||
|
"heading_line_length": 120,
|
||||||
|
"code_block_line_length": 160,
|
||||||
|
"code_blocks": false,
|
||||||
|
"tables": false
|
||||||
|
},
|
||||||
|
|
||||||
|
// MD033 — inline HTML
|
||||||
|
// Allow HTML in markdown (used in some SKILL.md files for tables/details).
|
||||||
|
"MD033": false,
|
||||||
|
|
||||||
|
// MD034 — bare URLs
|
||||||
|
// Disabled: existing docs include many plain URLs intentionally.
|
||||||
|
"MD034": false,
|
||||||
|
|
||||||
|
// MD041 — first line should be top-level heading
|
||||||
|
// Disabled: some files intentionally start with front-matter or a preamble.
|
||||||
|
"MD041": false,
|
||||||
|
|
||||||
|
// MD060 — table column style
|
||||||
|
// Disabled: existing tables use compact pipe style throughout.
|
||||||
|
"MD060": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
node_modules/
|
||||||
|
**/node_modules/**
|
||||||
|
pnpm-lock.yaml
|
||||||
|
# Generated agent-variant directories (non-mutating M1 policy)
|
||||||
|
skills/atlassian/codex/
|
||||||
|
skills/atlassian/claude-code/
|
||||||
|
skills/atlassian/cursor/
|
||||||
|
skills/atlassian/opencode/
|
||||||
|
skills/atlassian/pi/
|
||||||
|
skills/web-automation/claude-code/
|
||||||
|
skills/web-automation/cursor/
|
||||||
|
skills/web-automation/opencode/
|
||||||
|
skills/web-automation/pi/
|
||||||
|
pi-package/
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
node_modules/
|
||||||
|
pnpm-lock.yaml
|
||||||
|
# Generated agent-variant directories (non-mutating M1 policy)
|
||||||
|
skills/atlassian/codex/
|
||||||
|
skills/atlassian/claude-code/
|
||||||
|
skills/atlassian/cursor/
|
||||||
|
skills/atlassian/opencode/
|
||||||
|
skills/atlassian/pi/
|
||||||
|
skills/web-automation/claude-code/
|
||||||
|
skills/web-automation/cursor/
|
||||||
|
skills/web-automation/opencode/
|
||||||
|
skills/web-automation/pi/
|
||||||
|
pi-package/
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"printWidth": 100,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"endOfLine": "lf"
|
||||||
|
}
|
||||||
@@ -197,3 +197,6 @@ npm pack --dry-run --json
|
|||||||
```
|
```
|
||||||
|
|
||||||
Additional pi-specific guidance lives in [docs/PI.md](docs/PI.md).
|
Additional pi-specific guidance lives in [docs/PI.md](docs/PI.md).
|
||||||
|
|
||||||
|
For development prerequisites, quality tooling, and the `pnpm run check`
|
||||||
|
contract, see [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md).
|
||||||
|
|||||||
@@ -0,0 +1,292 @@
|
|||||||
|
# Cleanup Baseline — ai-coding-skills (M1)
|
||||||
|
|
||||||
|
Captured: 2026-05-03 · Platform: macOS (arm64, Apple Silicon) · Node 22.14.0 · pnpm 10.18.1
|
||||||
|
|
||||||
|
This document records the as-is state of every quality check at the start of
|
||||||
|
the refactor series. The transitional `pnpm run check` contract allows the
|
||||||
|
aggregate check to exit non-zero **only** on issues listed here. Any failure
|
||||||
|
not covered by this document represents a regression introduced by a later
|
||||||
|
milestone.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Byte-equivalence assertion
|
||||||
|
|
||||||
|
Before any file was modified in M1, `git status` showed a clean working tree.
|
||||||
|
After all S-101 through S-107 changes were applied, `git status` confirms:
|
||||||
|
|
||||||
|
- **Zero** modifications to any nested `package.json` under `skills/` or `pi-package/`.
|
||||||
|
- **Zero** modifications to any file under `skills/*/{codex,claude-code,cursor,opencode,pi}/`
|
||||||
|
(the generated agent-variant directories).
|
||||||
|
- All changes are confined to new root-level config files, new scripts, updated
|
||||||
|
root `package.json` (devDependencies + scripts), and new docs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Pre-existing root scripts
|
||||||
|
|
||||||
|
### `pnpm run sync:pi`
|
||||||
|
|
||||||
|
```text
|
||||||
|
Synced pi package skill mirror into …/pi-package/skills.
|
||||||
|
EXIT: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### `pnpm run verify:pi`
|
||||||
|
|
||||||
|
```text
|
||||||
|
package metadata ok
|
||||||
|
pi resources verified
|
||||||
|
pi workflow skill docs verified
|
||||||
|
EXIT: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### `pnpm run verify:reviewers`
|
||||||
|
|
||||||
|
```text
|
||||||
|
reviewer support verified
|
||||||
|
EXIT: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### `pnpm run test:installer`
|
||||||
|
|
||||||
|
```text
|
||||||
|
TAP version 13
|
||||||
|
# tests 22
|
||||||
|
# pass 22
|
||||||
|
# fail 0
|
||||||
|
EXIT: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Nested package scripts
|
||||||
|
|
||||||
|
### atlassian/shared/scripts — `test`
|
||||||
|
|
||||||
|
```text
|
||||||
|
TAP version 13
|
||||||
|
# tests 23
|
||||||
|
# pass 23
|
||||||
|
# fail 0
|
||||||
|
EXIT: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### atlassian/shared/scripts — `typecheck`
|
||||||
|
|
||||||
|
```text
|
||||||
|
EXIT: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### atlassian/codex/scripts — `typecheck`
|
||||||
|
|
||||||
|
```text
|
||||||
|
EXIT: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### atlassian/claude-code/scripts — `typecheck`
|
||||||
|
|
||||||
|
```text
|
||||||
|
EXIT: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### atlassian/cursor/scripts — `typecheck`
|
||||||
|
|
||||||
|
```text
|
||||||
|
EXIT: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### atlassian/opencode/scripts — `typecheck`
|
||||||
|
|
||||||
|
```text
|
||||||
|
EXIT: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### atlassian/pi/scripts — `typecheck`
|
||||||
|
|
||||||
|
```text
|
||||||
|
EXIT: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### web-automation/codex/scripts — `typecheck`
|
||||||
|
|
||||||
|
```text
|
||||||
|
EXIT: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### web-automation/codex/scripts — `lint`
|
||||||
|
|
||||||
|
```text
|
||||||
|
EXIT: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### web-automation/claude-code/scripts — `typecheck`
|
||||||
|
|
||||||
|
```text
|
||||||
|
EXIT: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### web-automation/cursor/scripts — `typecheck`
|
||||||
|
|
||||||
|
```text
|
||||||
|
EXIT: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### web-automation/opencode/scripts — `typecheck`
|
||||||
|
|
||||||
|
```text
|
||||||
|
EXIT: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### web-automation/pi/scripts — `typecheck`
|
||||||
|
|
||||||
|
```text
|
||||||
|
EXIT: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. reviewer-runtime tests
|
||||||
|
|
||||||
|
All three test scripts under `skills/reviewer-runtime/tests/` passed:
|
||||||
|
|
||||||
|
```text
|
||||||
|
claude-review-template-guard.sh PASS
|
||||||
|
smoke-test.sh PASS
|
||||||
|
telegram-notify-test.sh PASS
|
||||||
|
EXIT: 0 (each)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. M1 quality tooling — baseline violations
|
||||||
|
|
||||||
|
### 4a. ESLint (`pnpm run lint` — ESLint stage)
|
||||||
|
|
||||||
|
Exit code: **1** — 2 pre-existing errors (no new errors from M1 additions).
|
||||||
|
|
||||||
|
Files with violations in the existing codebase:
|
||||||
|
|
||||||
|
| File | Line | Rule | Message |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| `scripts/lib/skill-manager-core.mjs` | 282 | `no-useless-assignment` | Value assigned to `entries` is not used |
|
||||||
|
| `scripts/manage-skills.mjs` | 270 | `no-unused-vars` | `client` is assigned a value but never used |
|
||||||
|
|
||||||
|
Action: Fix in a later milestone. Do not suppress with eslint-disable comments
|
||||||
|
unless the code is intentionally dead.
|
||||||
|
|
||||||
|
### 4b. shellcheck (`pnpm run lint` — shellcheck stage)
|
||||||
|
|
||||||
|
Exit code: **1** — 7 files with pre-existing findings (no new violations from M1).
|
||||||
|
|
||||||
|
| File | Finding |
|
||||||
|
|------|---------|
|
||||||
|
| `scripts/verify-pi-resources.sh` | SC2016 (info, ×3): single-quoted backtick strings; SC2251 (info, ×1): `!` outside condition |
|
||||||
|
| `scripts/verify-pi-workflows.sh` | SC2016 (info, ×2): single-quoted backtick strings; SC2251 (info, ×1): `!` outside condition |
|
||||||
|
| `skills/reviewer-runtime/pi/run-review.sh` | SC2329 (info): unused `handle_signal`; SC2034 (warning, ×2): unused variables |
|
||||||
|
| `skills/reviewer-runtime/run-review.sh` | SC2329 (info): unused `handle_signal`; SC2034 (warning, ×2): unused variables |
|
||||||
|
| `skills/reviewer-runtime/tests/claude-review-template-guard.sh` | SC2016 (info, ×1): single-quoted expansion |
|
||||||
|
| `skills/reviewer-runtime/tests/smoke-test.sh` | SC2064 (warning, ×1): `trap` should use single quotes |
|
||||||
|
| `skills/reviewer-runtime/tests/telegram-notify-test.sh` | SC2064 (warning, ×1): `trap` should use single quotes |
|
||||||
|
|
||||||
|
The following files pass shellcheck with EXIT 0:
|
||||||
|
|
||||||
|
- `scripts/install-pi-package.sh`
|
||||||
|
- `scripts/manage-skills.sh`
|
||||||
|
- `scripts/sync-pi-package-skills.sh`
|
||||||
|
- `scripts/verify-reviewer-support.sh`
|
||||||
|
- `skills/reviewer-runtime/notify-telegram.sh`
|
||||||
|
- `skills/reviewer-runtime/pi/notify-telegram.sh`
|
||||||
|
- `skills/web-automation/scripts/sync-variants.sh`
|
||||||
|
|
||||||
|
Action: Fix `SC2064` trap issues and `SC2034` warnings in a later milestone.
|
||||||
|
SC2016 findings are intentional (single-quoted strings containing backticks are
|
||||||
|
used as literal grep patterns to match markdown text).
|
||||||
|
|
||||||
|
### 4c. markdownlint (`pnpm run verify:docs`)
|
||||||
|
|
||||||
|
Exit code: **1** — 1160 errors across 62 files (no new violations from M1 additions).
|
||||||
|
|
||||||
|
markdownlint-cli2 v0.22.1 scanned `README.md`, `docs/**/*.md`, and canonical
|
||||||
|
`SKILL.md` files (excluding node\_modules and generated agent-variant
|
||||||
|
directories).
|
||||||
|
|
||||||
|
Rule breakdown (selected):
|
||||||
|
|
||||||
|
| Rule | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| MD013 | Line length > 120 chars (majority of errors) |
|
||||||
|
| MD012 | Multiple consecutive blank lines |
|
||||||
|
| MD024 | Duplicate headings |
|
||||||
|
| MD029 | Ordered list item prefix |
|
||||||
|
| MD032 | Blanks around lists |
|
||||||
|
| MD038 | Spaces inside code span |
|
||||||
|
| MD040 | Fenced code without language |
|
||||||
|
| MD058 | Blanks around tables |
|
||||||
|
|
||||||
|
Affected files include (non-exhaustive): `docs/ATLASSIAN.md`,
|
||||||
|
`docs/CREATE-PLAN.md`, `docs/DO-TASK.md`, `docs/IMPLEMENT-PLAN.md`,
|
||||||
|
`docs/INSTALLER.md`, `docs/PI-COMMON-REVIEWER.md`, `docs/PI-RESEARCH.md`,
|
||||||
|
`docs/PI-SUPERPOWERS.md`, `docs/PI.md`, `docs/README.md`,
|
||||||
|
`docs/TELEGRAM-NOTIFICATIONS.md`, `docs/WEB-AUTOMATION.md`, `README.md`,
|
||||||
|
and all `skills/*/*/SKILL.md` variants.
|
||||||
|
|
||||||
|
Action: Bulk-fix in a dedicated doc-cleanup milestone. New docs added in M1
|
||||||
|
(`docs/DEVELOPMENT.md`, `docs/CLEANUP-BASELINE.md`) pass all enabled rules.
|
||||||
|
|
||||||
|
### 4d. markdown-link-check (offline, `pnpm run verify:docs`)
|
||||||
|
|
||||||
|
Exit code: **0** — no broken repo-relative or anchor links found.
|
||||||
|
|
||||||
|
53 markdown files scanned (offline mode: external http/https links ignored).
|
||||||
|
All internal links are valid.
|
||||||
|
|
||||||
|
### 4e. `pnpm run typecheck` (workspace aggregate)
|
||||||
|
|
||||||
|
Exit code: **0**.
|
||||||
|
|
||||||
|
Ran `typecheck` in workspace members:
|
||||||
|
|
||||||
|
- `skills/atlassian/shared/scripts` — PASS
|
||||||
|
- `skills/web-automation/codex/scripts` — PASS
|
||||||
|
|
||||||
|
### 4f. `pnpm run test` (workspace aggregate)
|
||||||
|
|
||||||
|
Exit code: **0**.
|
||||||
|
|
||||||
|
Ran:
|
||||||
|
|
||||||
|
- `pnpm run test:installer` (22/22 tests)
|
||||||
|
- `skills/atlassian/shared/scripts test` (23/23 tests)
|
||||||
|
|
||||||
|
### 4g. `pnpm run check` aggregate (M1 transitional state)
|
||||||
|
|
||||||
|
```text
|
||||||
|
FAIL lint (exit 1) — ESLint + shellcheck pre-existing violations (§4a, §4b)
|
||||||
|
PASS typecheck
|
||||||
|
PASS test
|
||||||
|
PASS verify:pi
|
||||||
|
PASS verify:reviewers
|
||||||
|
FAIL verify:docs (exit 1) — markdownlint pre-existing violations (§4c)
|
||||||
|
PASS verify:generated (stub)
|
||||||
|
```
|
||||||
|
|
||||||
|
Overall exit: **1** — expected per transitional contract; all failures are
|
||||||
|
pre-existing and documented in this file.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Platform notes
|
||||||
|
|
||||||
|
- Tested on macOS 15 (arm64). No GNU/Linux run available at M1 capture time.
|
||||||
|
- shellcheck 0.11.0 installed via `brew install shellcheck`.
|
||||||
|
- `stat` variant: BSD stat (macOS default). The `scripts/` shell scripts do
|
||||||
|
not call `stat`; this affects only M5 CI configuration.
|
||||||
|
- `rg` (ripgrep) is required by `scripts/verify-pi-workflows.sh`. It is
|
||||||
|
present on the test machine; absent systems should install via
|
||||||
|
`brew install ripgrep` / `apt-get install ripgrep`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This file is generated once (M1) and updated only when a subsequent milestone
|
||||||
|
changes the baseline status of a check.*
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
# Development Guide — ai-coding-skills
|
||||||
|
|
||||||
|
This document covers prerequisites, how to run checks locally, the M1 quality
|
||||||
|
tooling, the workspace policy, and the transitional `pnpm run check` contract.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
| Tool | Minimum version | Install |
|
||||||
|
|------|----------------|---------|
|
||||||
|
| Node.js | 20 | `fnm install 22` or `nvm install 22` |
|
||||||
|
| pnpm | 10 | `npm install -g pnpm` |
|
||||||
|
| `rg` (ripgrep) | any | `brew install ripgrep` / `apt-get install ripgrep` |
|
||||||
|
| **shellcheck** | **any** | `brew install shellcheck` / `apt-get install shellcheck` |
|
||||||
|
| `stat` (BSD or GNU) | any | pre-installed on macOS; GNU variant on Linux |
|
||||||
|
| Python 3 | 3.8+ | pre-installed on most systems |
|
||||||
|
|
||||||
|
**`shellcheck` is required.** The `pnpm run lint` script will exit 2 with a
|
||||||
|
clear error message if `shellcheck` is not on `PATH`. Every contributor must
|
||||||
|
install it before running any quality checks.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies (workspace-aware, no nested package.json modifications)
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Run the full quality suite
|
||||||
|
pnpm run check
|
||||||
|
```
|
||||||
|
|
||||||
|
## Individual checks
|
||||||
|
|
||||||
|
| Command | What it does |
|
||||||
|
|---------|-------------|
|
||||||
|
| `pnpm run sync:pi` | Mirror pi skill variants into `pi-package/skills/` |
|
||||||
|
| `pnpm run verify:pi` | Assert pi resource and workflow invariants |
|
||||||
|
| `pnpm run verify:reviewers` | Assert reviewer-runtime skill invariants |
|
||||||
|
| `pnpm run test:installer` | Run root-level Node.js unit tests (22 tests) |
|
||||||
|
| `pnpm run lint` | ESLint on root scripts + shellcheck on all `.sh` files |
|
||||||
|
| `pnpm run lint:fix` | Auto-fix ESLint + Prettier (do not run on pre-existing code until M2) |
|
||||||
|
| `pnpm run typecheck` | TypeScript `tsc --noEmit` in workspace packages |
|
||||||
|
| `pnpm run test` | Run all tests (root + workspace packages) |
|
||||||
|
| `pnpm run verify:docs` | markdownlint + offline link-check |
|
||||||
|
| `pnpm run verify:generated` | Assert generated output freshness (stub; fleshed out in M3) |
|
||||||
|
| `pnpm run check` | Aggregate: run every gate above and report a summary |
|
||||||
|
|
||||||
|
## Quality tooling (added in M1)
|
||||||
|
|
||||||
|
### ESLint
|
||||||
|
|
||||||
|
Root-level flat config in `eslint.config.mjs`. Covers `scripts/**/*.mjs`
|
||||||
|
and `scripts/**/*.js`. Uses `@eslint/js` recommended rules and Node.js
|
||||||
|
globals. Nested workspace packages are responsible for their own ESLint
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
### Prettier
|
||||||
|
|
||||||
|
Config in `.prettierrc.json` (print-width 100, LF line endings). Ignore
|
||||||
|
file at `.prettierignore` excludes generated agent-variant directories and
|
||||||
|
`pnpm-lock.yaml`.
|
||||||
|
|
||||||
|
### markdownlint
|
||||||
|
|
||||||
|
Config in `.markdownlint.jsonc` (rules) and `.markdownlint-cli2.jsonc`
|
||||||
|
(file globs and ignores). Key overrides vs defaults:
|
||||||
|
|
||||||
|
- `MD013` line-length relaxed to 120 chars (code blocks and tables excluded).
|
||||||
|
- `MD033` (inline HTML) disabled.
|
||||||
|
- `MD034` (bare URLs) disabled.
|
||||||
|
- `MD041` (first-line heading) disabled.
|
||||||
|
- `MD060` (table column style) disabled.
|
||||||
|
|
||||||
|
Run `pnpm run verify:docs` to lint all `README.md`, `docs/*.md`, and
|
||||||
|
`skills/**/SKILL.md` files (node\_modules excluded automatically).
|
||||||
|
|
||||||
|
### markdown-link-check
|
||||||
|
|
||||||
|
Two configs:
|
||||||
|
|
||||||
|
- `markdown-link-check.json` — **offline** mode (default): ignores all
|
||||||
|
`http://` and `https://` links. Safe for local dev and CI.
|
||||||
|
- `markdown-link-check.online.json` — **online** mode: checks external links
|
||||||
|
with a 10 s timeout, 2 retries, and retry-on-429. Use `--online` flag on
|
||||||
|
`scripts/lib/run-link-check.mjs`.
|
||||||
|
|
||||||
|
`pnpm run verify:docs` uses the offline config by default.
|
||||||
|
|
||||||
|
### shellcheck
|
||||||
|
|
||||||
|
Wrapper script at `scripts/lib/run-shellcheck.mjs`. Discovers every `*.sh`
|
||||||
|
file under `scripts/` and `skills/` (excluding node\_modules and generated
|
||||||
|
agent-variant directories) and runs shellcheck on each.
|
||||||
|
|
||||||
|
**Installation:**
|
||||||
|
|
||||||
|
- macOS: `brew install shellcheck`
|
||||||
|
- Debian/Ubuntu: `sudo apt-get install shellcheck`
|
||||||
|
- Other: <https://github.com/koalaman/shellcheck#installing>
|
||||||
|
|
||||||
|
The wrapper exits with code **2** (not 1) when shellcheck is missing, so CI
|
||||||
|
can distinguish "shellcheck absent" from "shellcheck found violations".
|
||||||
|
|
||||||
|
## Transitional `check` contract (M1)
|
||||||
|
|
||||||
|
`pnpm run check` may exit non-zero in M1. This is expected. The contract is:
|
||||||
|
|
||||||
|
> `pnpm run check` exits non-zero **only** on issues recorded in
|
||||||
|
> `docs/CLEANUP-BASELINE.md`. Previously-green checks remain green. No
|
||||||
|
> violations are introduced by M1's own changes.
|
||||||
|
|
||||||
|
The current baseline failures are:
|
||||||
|
|
||||||
|
1. `lint` — 2 ESLint violations in existing root scripts; 7 shellcheck
|
||||||
|
findings in existing `.sh` files.
|
||||||
|
2. `verify:docs` — 1160 markdownlint violations in pre-existing docs and
|
||||||
|
`SKILL.md` files.
|
||||||
|
|
||||||
|
All other checks (`typecheck`, `test`, `verify:pi`, `verify:reviewers`,
|
||||||
|
`verify:generated`) pass with EXIT 0.
|
||||||
|
|
||||||
|
Once later milestones fix the baseline violations, `pnpm run check` will
|
||||||
|
reach EXIT 0 and the transitional contract expires.
|
||||||
|
|
||||||
|
## pnpm workspace policy (M1 exclusion-only)
|
||||||
|
|
||||||
|
The `pnpm-workspace.yaml` at the repo root implements the **non-mutating,
|
||||||
|
exclusion-only** policy for M1:
|
||||||
|
|
||||||
|
**Included** (canonical source packages):
|
||||||
|
|
||||||
|
- `skills/atlassian/shared/scripts` — shared Atlassian runtime source
|
||||||
|
- `skills/web-automation/codex/scripts` — provisional canonical copy; M3
|
||||||
|
will rename and/or consolidate
|
||||||
|
|
||||||
|
**Excluded** (generated agent-variant directories):
|
||||||
|
|
||||||
|
- `skills/atlassian/{codex,claude-code,cursor,opencode,pi}/scripts`
|
||||||
|
- `skills/web-automation/{claude-code,cursor,opencode,pi}/scripts`
|
||||||
|
- `pi-package/**`
|
||||||
|
|
||||||
|
**Why exclusion-only in M1?**
|
||||||
|
|
||||||
|
The generated agent-variant directories contain `package.json` files with the
|
||||||
|
same `name` field as the canonical source. Including them in the workspace
|
||||||
|
would cause pnpm to complain about duplicate package names. Renaming them to
|
||||||
|
unique names (e.g. `atlassian-skill-scripts-codex`) requires a generator-driven
|
||||||
|
update that touches every generated file — this is deferred to **M3** to keep
|
||||||
|
M1 byte-identical for those files.
|
||||||
|
|
||||||
|
After `pnpm install`, `git status` should show zero modifications to any file
|
||||||
|
under the excluded directories. If it does not, the workspace config is broken.
|
||||||
|
|
||||||
|
## How to interpret the baseline report
|
||||||
|
|
||||||
|
See `docs/CLEANUP-BASELINE.md` for the full as-is capture. When a CI run
|
||||||
|
fails on a check, compare the output against the baseline:
|
||||||
|
|
||||||
|
- If the failure matches a baseline entry → it is a known pre-existing issue.
|
||||||
|
- If the failure does not appear in the baseline → it is a regression
|
||||||
|
introduced by recent changes and must be fixed before merge.
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
- [CLEANUP-BASELINE.md](./CLEANUP-BASELINE.md) — as-is quality baseline
|
||||||
|
- [README.md](../README.md) — project overview
|
||||||
|
- [docs/README.md](./README.md) — documentation index
|
||||||
@@ -27,6 +27,12 @@ This directory contains user-facing docs for each skill.
|
|||||||
- [TELEGRAM-NOTIFICATIONS.md](./TELEGRAM-NOTIFICATIONS.md) — Shared Telegram notification setup used by reviewer-driven skills.
|
- [TELEGRAM-NOTIFICATIONS.md](./TELEGRAM-NOTIFICATIONS.md) — Shared Telegram notification setup used by reviewer-driven skills.
|
||||||
- [WEB-AUTOMATION.md](./WEB-AUTOMATION.md) — Includes requirements, install, dependency verification, and usage examples for web-automation.
|
- [WEB-AUTOMATION.md](./WEB-AUTOMATION.md) — Includes requirements, install, dependency verification, and usage examples for web-automation.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
- [DEVELOPMENT.md](./DEVELOPMENT.md) — prerequisites, `pnpm run check`, workspace policy,
|
||||||
|
and the transitional quality contract.
|
||||||
|
- [CLEANUP-BASELINE.md](./CLEANUP-BASELINE.md) — as-is quality baseline captured at M1.
|
||||||
|
|
||||||
## Repo Setup
|
## Repo Setup
|
||||||
|
|
||||||
Clone the repo first:
|
Clone the repo first:
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// ESLint flat config — repo root (M1)
|
||||||
|
// Scoped to root-level scripts only. Nested workspace packages manage
|
||||||
|
// their own lint config (or inherit this in a later milestone).
|
||||||
|
import js from "@eslint/js";
|
||||||
|
import globals from "globals";
|
||||||
|
|
||||||
|
export default [
|
||||||
|
// Apply ESLint recommended rules to root-level JS/MJS scripts
|
||||||
|
{
|
||||||
|
files: ["scripts/**/*.mjs", "scripts/**/*.js"],
|
||||||
|
...js.configs.recommended,
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Global ignores — never lint generated directories or node_modules
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"**/node_modules/**",
|
||||||
|
// Generated agent-variant script bundles (excluded per M1 workspace policy)
|
||||||
|
"skills/atlassian/codex/scripts/**",
|
||||||
|
"skills/atlassian/claude-code/scripts/**",
|
||||||
|
"skills/atlassian/cursor/scripts/**",
|
||||||
|
"skills/atlassian/opencode/scripts/**",
|
||||||
|
"skills/atlassian/pi/scripts/**",
|
||||||
|
"skills/web-automation/claude-code/scripts/**",
|
||||||
|
"skills/web-automation/cursor/scripts/**",
|
||||||
|
"skills/web-automation/opencode/scripts/**",
|
||||||
|
"skills/web-automation/pi/scripts/**",
|
||||||
|
"pi-package/**",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"timeout": "10s",
|
||||||
|
"retryCount": 2,
|
||||||
|
"retryOn429": true,
|
||||||
|
"aliveStatusCodes": [200, 206, 429],
|
||||||
|
"ignorePatterns": [
|
||||||
|
{ "pattern": "^https?://" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"timeout": "10s",
|
||||||
|
"retryCount": 2,
|
||||||
|
"retryOn429": true,
|
||||||
|
"aliveStatusCodes": [200, 206, 429],
|
||||||
|
"ignorePatterns": [
|
||||||
|
{ "pattern": "^https://github.com/obra/" },
|
||||||
|
{ "pattern": "^https://github.com/anthropics/" },
|
||||||
|
{ "pattern": "^https://localhost" },
|
||||||
|
{ "pattern": "^http://localhost" }
|
||||||
|
]
|
||||||
|
}
|
||||||
+17
-2
@@ -42,7 +42,14 @@
|
|||||||
"sync:pi": "./scripts/sync-pi-package-skills.sh",
|
"sync:pi": "./scripts/sync-pi-package-skills.sh",
|
||||||
"verify:pi": "./scripts/verify-pi-resources.sh && ./scripts/verify-pi-workflows.sh",
|
"verify:pi": "./scripts/verify-pi-resources.sh && ./scripts/verify-pi-workflows.sh",
|
||||||
"verify:reviewers": "./scripts/verify-reviewer-support.sh",
|
"verify:reviewers": "./scripts/verify-reviewer-support.sh",
|
||||||
"test:installer": "node --test scripts/tests/*.test.mjs"
|
"test:installer": "node --test scripts/tests/*.test.mjs",
|
||||||
|
"lint": "eslint . ; R1=$?; node scripts/lib/run-shellcheck.mjs ; R2=$?; [ $((R1+R2)) -eq 0 ]",
|
||||||
|
"lint:fix": "eslint . --fix && prettier --write .",
|
||||||
|
"typecheck": "pnpm run -r --if-present typecheck",
|
||||||
|
"test": "pnpm run test:installer && pnpm run -r --if-present test",
|
||||||
|
"verify:docs": "markdownlint-cli2 ; R1=$?; node scripts/lib/run-link-check.mjs ; R2=$?; [ $((R1+R2)) -eq 0 ]",
|
||||||
|
"verify:generated": "node -e \"console.log('verify:generated: stub — fleshed out in M3'); process.exit(0)\"",
|
||||||
|
"check": "node scripts/lib/run-check.mjs"
|
||||||
},
|
},
|
||||||
"pi": {
|
"pi": {
|
||||||
"skills": [
|
"skills": [
|
||||||
@@ -53,5 +60,13 @@
|
|||||||
"./pi-package/skills/web-automation"
|
"./pi-package/skills/web-automation"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34"
|
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34",
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "10.0.1",
|
||||||
|
"eslint": "10.3.0",
|
||||||
|
"globals": "17.6.0",
|
||||||
|
"markdown-link-check": "3.14.2",
|
||||||
|
"markdownlint-cli2": "0.22.1",
|
||||||
|
"prettier": "3.8.3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+3014
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
|||||||
|
# pnpm workspace — non-mutating, exclusion-only policy (M1)
|
||||||
|
#
|
||||||
|
# IMPORTANT: This file implements the M1 exclusion-only policy described in
|
||||||
|
# docs/DEVELOPMENT.md. Only canonical *source* packages are included as
|
||||||
|
# workspace members. All generated agent-variant directories are excluded via
|
||||||
|
# negative globs so pnpm never hoists, modifies, or renames their
|
||||||
|
# package.json files.
|
||||||
|
#
|
||||||
|
# Canonical source packages (M1):
|
||||||
|
# - skills/atlassian/shared/scripts → the shared Atlassian runtime
|
||||||
|
# - skills/web-automation/codex/scripts → provisional canonical copy until
|
||||||
|
# M3 finalises the web-automation source-of-truth and renames packages
|
||||||
|
#
|
||||||
|
# Generated agent-variant directories (excluded):
|
||||||
|
# skills/*/codex (except web-automation/codex above)
|
||||||
|
# skills/*/claude-code
|
||||||
|
# skills/*/cursor
|
||||||
|
# skills/*/opencode
|
||||||
|
# skills/*/pi
|
||||||
|
# pi-package/**
|
||||||
|
#
|
||||||
|
# The package-name collision between atlassian variants is sidestepped in M1
|
||||||
|
# by this exclusion. A generator-driven rename to give each variant a unique
|
||||||
|
# name is deferred to M3.
|
||||||
|
|
||||||
|
packages:
|
||||||
|
# ── Canonical source packages ────────────────────────────────────────────
|
||||||
|
- "skills/atlassian/shared/scripts"
|
||||||
|
- "skills/web-automation/codex/scripts"
|
||||||
|
|
||||||
|
# ── Exclude generated agent-variant directories ──────────────────────────
|
||||||
|
- "!skills/atlassian/codex/scripts"
|
||||||
|
- "!skills/atlassian/claude-code/scripts"
|
||||||
|
- "!skills/atlassian/cursor/scripts"
|
||||||
|
- "!skills/atlassian/opencode/scripts"
|
||||||
|
- "!skills/atlassian/pi/scripts"
|
||||||
|
- "!skills/web-automation/claude-code/scripts"
|
||||||
|
- "!skills/web-automation/cursor/scripts"
|
||||||
|
- "!skills/web-automation/opencode/scripts"
|
||||||
|
- "!skills/web-automation/pi/scripts"
|
||||||
|
- "!pi-package/**"
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* run-check.mjs — aggregate quality check runner (M1, S-106)
|
||||||
|
*
|
||||||
|
* Runs every quality gate in sequence and reports a summary.
|
||||||
|
* All steps run even if earlier steps fail, so you get a complete
|
||||||
|
* picture of the repository health in one pass.
|
||||||
|
*
|
||||||
|
* Transitional contract (M1):
|
||||||
|
* This script may exit non-zero. Pre-existing failures are recorded in
|
||||||
|
* docs/CLEANUP-BASELINE.md. Only issues introduced by new changes (not
|
||||||
|
* listed in the baseline) constitute a regression.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node scripts/lib/run-check.mjs # full check
|
||||||
|
* pnpm run check # same, via pnpm
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { spawnSync } from "node:child_process";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const REPO_ROOT = path.resolve(__dirname, "../..");
|
||||||
|
|
||||||
|
// ── Steps ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const STEPS = [
|
||||||
|
{ label: "lint", cmd: "pnpm", args: ["run", "lint"] },
|
||||||
|
{ label: "typecheck", cmd: "pnpm", args: ["run", "typecheck"] },
|
||||||
|
{ label: "test", cmd: "pnpm", args: ["run", "test"] },
|
||||||
|
{ label: "verify:pi", cmd: "pnpm", args: ["run", "verify:pi"] },
|
||||||
|
{ label: "verify:reviewers", cmd: "pnpm", args: ["run", "verify:reviewers"] },
|
||||||
|
{ label: "verify:docs", cmd: "pnpm", args: ["run", "verify:docs"] },
|
||||||
|
{ label: "verify:generated", cmd: "pnpm", args: ["run", "verify:generated"] },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ── Runner ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const RESET = "\x1b[0m";
|
||||||
|
const GREEN = "\x1b[32m";
|
||||||
|
const RED = "\x1b[31m";
|
||||||
|
const BOLD = "\x1b[1m";
|
||||||
|
const DIM = "\x1b[2m";
|
||||||
|
|
||||||
|
function colorize(color, text) {
|
||||||
|
// Respect NO_COLOR env variable
|
||||||
|
if (process.env.NO_COLOR || process.env.CI) return text;
|
||||||
|
return `${color}${text}${RESET}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
for (const step of STEPS) {
|
||||||
|
process.stdout.write(`\n${colorize(BOLD, `=== ${step.label} ===`)}\n`);
|
||||||
|
const result = spawnSync(step.cmd, step.args, {
|
||||||
|
cwd: REPO_ROOT,
|
||||||
|
stdio: "inherit",
|
||||||
|
encoding: "utf8",
|
||||||
|
shell: false,
|
||||||
|
});
|
||||||
|
const ok = result.status === 0 && !result.error;
|
||||||
|
results.push({ label: step.label, ok, status: result.status ?? -1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Summary ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
process.stdout.write(`\n${colorize(BOLD, "=== check summary ===")}\n`);
|
||||||
|
|
||||||
|
const failures = [];
|
||||||
|
for (const r of results) {
|
||||||
|
if (r.ok) {
|
||||||
|
process.stdout.write(
|
||||||
|
` ${colorize(GREEN, "PASS")} ${r.label}\n`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
process.stdout.write(
|
||||||
|
` ${colorize(RED, "FAIL")} ${r.label} ${colorize(DIM, `(exit ${r.status})`)} — see docs/CLEANUP-BASELINE.md if pre-existing\n`
|
||||||
|
);
|
||||||
|
failures.push(r.label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdout.write("\n");
|
||||||
|
|
||||||
|
if (failures.length === 0) {
|
||||||
|
process.stdout.write(colorize(GREEN, "All checks passed.\n"));
|
||||||
|
process.exit(0);
|
||||||
|
} else {
|
||||||
|
process.stdout.write(
|
||||||
|
colorize(
|
||||||
|
RED,
|
||||||
|
`${failures.length} check(s) failed: ${failures.join(", ")}\n`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* run-link-check.mjs — markdown link-check runner (M1, S-104)
|
||||||
|
*
|
||||||
|
* Runs markdown-link-check across README.md, docs/, and every SKILL.md
|
||||||
|
* (excluding node_modules and generated agent-variant directories).
|
||||||
|
*
|
||||||
|
* Modes:
|
||||||
|
* --offline (default) — checks only repo-relative links and #anchor links.
|
||||||
|
* All http/https links are ignored. Safe for CI and local dev
|
||||||
|
* without network access.
|
||||||
|
* --online — checks all links, including external URLs, with timeouts
|
||||||
|
* and retries as configured in markdown-link-check.online.json.
|
||||||
|
*
|
||||||
|
* Exit codes:
|
||||||
|
* 0 — all checked links are alive (or ignored in offline mode)
|
||||||
|
* 1 — one or more broken links found
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { spawnSync } from "node:child_process";
|
||||||
|
import { readdirSync, existsSync } from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const REPO_ROOT = path.resolve(__dirname, "../..");
|
||||||
|
|
||||||
|
// ── CLI arguments ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const online = process.argv.includes("--online");
|
||||||
|
const configFile = online
|
||||||
|
? path.join(REPO_ROOT, "markdown-link-check.online.json")
|
||||||
|
: path.join(REPO_ROOT, "markdown-link-check.json");
|
||||||
|
|
||||||
|
// ── File discovery ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const SKIP_PATHS = new Set([
|
||||||
|
"skills/atlassian/codex",
|
||||||
|
"skills/atlassian/claude-code",
|
||||||
|
"skills/atlassian/cursor",
|
||||||
|
"skills/atlassian/opencode",
|
||||||
|
"skills/atlassian/pi",
|
||||||
|
"skills/web-automation/claude-code",
|
||||||
|
"skills/web-automation/cursor",
|
||||||
|
"skills/web-automation/opencode",
|
||||||
|
"skills/web-automation/pi",
|
||||||
|
"pi-package",
|
||||||
|
]);
|
||||||
|
|
||||||
|
function shouldSkip(absPath) {
|
||||||
|
const rel = path.relative(REPO_ROOT, absPath);
|
||||||
|
for (const skip of SKIP_PATHS) {
|
||||||
|
if (rel === skip || rel.startsWith(skip + path.sep)) return true;
|
||||||
|
}
|
||||||
|
const parts = rel.split(path.sep);
|
||||||
|
return parts.includes("node_modules");
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectMarkdownFiles(dir) {
|
||||||
|
const found = [];
|
||||||
|
let entries;
|
||||||
|
try {
|
||||||
|
entries = readdirSync(dir, { withFileTypes: true });
|
||||||
|
} catch {
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
for (const entry of entries) {
|
||||||
|
const full = path.join(dir, entry.name);
|
||||||
|
if (shouldSkip(full)) continue;
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
found.push(...collectMarkdownFiles(full));
|
||||||
|
} else if (
|
||||||
|
entry.isFile() &&
|
||||||
|
(entry.name.endsWith(".md") || entry.name === "SKILL.md")
|
||||||
|
) {
|
||||||
|
found.push(full);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Collect target files ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const files = [
|
||||||
|
path.join(REPO_ROOT, "README.md"),
|
||||||
|
...collectMarkdownFiles(path.join(REPO_ROOT, "docs")),
|
||||||
|
...collectMarkdownFiles(path.join(REPO_ROOT, "skills")),
|
||||||
|
].filter(existsSync);
|
||||||
|
|
||||||
|
// De-duplicate (README.md could appear twice)
|
||||||
|
const uniqueFiles = [...new Set(files)];
|
||||||
|
|
||||||
|
if (uniqueFiles.length === 0) {
|
||||||
|
console.log("link-check: no markdown files found — nothing to check.");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`link-check: checking ${uniqueFiles.length} file(s) ` +
|
||||||
|
`[mode: ${online ? "online" : "offline"}]…`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── Run markdown-link-check ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const mlcBin = path.join(
|
||||||
|
REPO_ROOT,
|
||||||
|
"node_modules",
|
||||||
|
".bin",
|
||||||
|
"markdown-link-check"
|
||||||
|
);
|
||||||
|
|
||||||
|
let failures = 0;
|
||||||
|
for (const file of uniqueFiles.sort()) {
|
||||||
|
const result = spawnSync(
|
||||||
|
mlcBin,
|
||||||
|
["--config", configFile, "--quiet", file],
|
||||||
|
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
|
||||||
|
);
|
||||||
|
const output = (result.stdout + result.stderr).trim();
|
||||||
|
const rel = path.relative(REPO_ROOT, file);
|
||||||
|
if (result.status !== 0) {
|
||||||
|
failures += 1;
|
||||||
|
console.error(`\n--- ${rel} ---`);
|
||||||
|
if (output) console.error(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failures > 0) {
|
||||||
|
console.error(
|
||||||
|
`\nlink-check: ${failures} file(s) have broken links. ` +
|
||||||
|
`See docs/CLEANUP-BASELINE.md for the as-is baseline.`
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
console.log("link-check: all links OK.");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* run-shellcheck.mjs — shell script quality wrapper (M1, S-105)
|
||||||
|
*
|
||||||
|
* Discovers *.sh files under scripts/ and skills/ (excluding node_modules
|
||||||
|
* and generated agent-variant directories), then runs shellcheck on each.
|
||||||
|
*
|
||||||
|
* shellcheck is a REQUIRED prerequisite. The script fails immediately when
|
||||||
|
* shellcheck is not found on PATH. Install it with:
|
||||||
|
* macOS: brew install shellcheck
|
||||||
|
* Debian: apt-get install shellcheck
|
||||||
|
*
|
||||||
|
* Exit codes:
|
||||||
|
* 0 — all files passed shellcheck
|
||||||
|
* 1 — one or more files have shellcheck findings
|
||||||
|
* 2 — shellcheck is missing from PATH
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { spawnSync } from "node:child_process";
|
||||||
|
import { readdirSync } from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const REPO_ROOT = path.resolve(__dirname, "../..");
|
||||||
|
|
||||||
|
// ── Prerequisites check ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function checkShellcheck() {
|
||||||
|
const result = spawnSync("shellcheck", ["--version"], { encoding: "utf8" });
|
||||||
|
if (result.error || result.status === null || result.status > 1) {
|
||||||
|
console.error(
|
||||||
|
[
|
||||||
|
"ERROR: shellcheck is not available on PATH.",
|
||||||
|
"",
|
||||||
|
"shellcheck is a required prerequisite for this repository.",
|
||||||
|
"Install it before running lint:",
|
||||||
|
"",
|
||||||
|
" macOS: brew install shellcheck",
|
||||||
|
" Debian/Ubuntu: sudo apt-get install shellcheck",
|
||||||
|
" Other: https://github.com/koalaman/shellcheck#installing",
|
||||||
|
].join("\n")
|
||||||
|
);
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── File discovery ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directories to scan for *.sh files.
|
||||||
|
* Relative paths from REPO_ROOT.
|
||||||
|
*/
|
||||||
|
const SCAN_DIRS = ["scripts", "skills"];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path segments that indicate a directory should be skipped entirely.
|
||||||
|
* Matches generated agent-variant bundles and npm/pnpm install artifacts.
|
||||||
|
*/
|
||||||
|
const SKIP_SEGMENTS = new Set([
|
||||||
|
"node_modules",
|
||||||
|
// Generated agent-variant directories (excluded per M1 workspace policy)
|
||||||
|
// skills/<skill>/codex — except web-automation/codex which is canonical
|
||||||
|
// We skip all codex variants to be safe; shellcheck only cares about .sh files
|
||||||
|
// and all variants share the same scripts anyway.
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exact relative paths (from REPO_ROOT) to skip, matched after normalisation.
|
||||||
|
* These are the generated variants that duplicate canonical source.
|
||||||
|
*/
|
||||||
|
const SKIP_PATHS = new Set([
|
||||||
|
"skills/atlassian/codex",
|
||||||
|
"skills/atlassian/claude-code",
|
||||||
|
"skills/atlassian/cursor",
|
||||||
|
"skills/atlassian/opencode",
|
||||||
|
"skills/atlassian/pi",
|
||||||
|
"skills/web-automation/claude-code",
|
||||||
|
"skills/web-automation/cursor",
|
||||||
|
"skills/web-automation/opencode",
|
||||||
|
"skills/web-automation/pi",
|
||||||
|
"pi-package",
|
||||||
|
]);
|
||||||
|
|
||||||
|
function shouldSkip(absPath) {
|
||||||
|
const rel = path.relative(REPO_ROOT, absPath);
|
||||||
|
// Check exact-prefix matches (directory and its children)
|
||||||
|
for (const skip of SKIP_PATHS) {
|
||||||
|
if (rel === skip || rel.startsWith(skip + path.sep)) return true;
|
||||||
|
}
|
||||||
|
// Check path-segment matches (e.g. node_modules anywhere in path)
|
||||||
|
for (const seg of rel.split(path.sep)) {
|
||||||
|
if (SKIP_SEGMENTS.has(seg)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectShellFiles(dir) {
|
||||||
|
const found = [];
|
||||||
|
let entries;
|
||||||
|
try {
|
||||||
|
entries = readdirSync(dir, { withFileTypes: true });
|
||||||
|
} catch {
|
||||||
|
return found; // directory does not exist — skip silently
|
||||||
|
}
|
||||||
|
for (const entry of entries) {
|
||||||
|
const full = path.join(dir, entry.name);
|
||||||
|
if (shouldSkip(full)) continue;
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
found.push(...collectShellFiles(full));
|
||||||
|
} else if (entry.isFile() && entry.name.endsWith(".sh")) {
|
||||||
|
found.push(full);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Main ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
checkShellcheck();
|
||||||
|
|
||||||
|
const files = SCAN_DIRS.flatMap((d) =>
|
||||||
|
collectShellFiles(path.join(REPO_ROOT, d))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (files.length === 0) {
|
||||||
|
console.log("shellcheck: no .sh files found — nothing to check.");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`shellcheck: scanning ${files.length} file(s)…`);
|
||||||
|
|
||||||
|
let failures = 0;
|
||||||
|
for (const file of files.sort()) {
|
||||||
|
const result = spawnSync("shellcheck", [file], {
|
||||||
|
encoding: "utf8",
|
||||||
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
|
});
|
||||||
|
const output = (result.stdout + result.stderr).trim();
|
||||||
|
const rel = path.relative(REPO_ROOT, file);
|
||||||
|
if (result.status !== 0) {
|
||||||
|
failures += 1;
|
||||||
|
// Print findings prefixed with the relative path so output is reproducible
|
||||||
|
// regardless of cwd.
|
||||||
|
console.error(`\n--- ${rel} ---`);
|
||||||
|
if (output) console.error(output);
|
||||||
|
} else {
|
||||||
|
// Quiet on success — only show problems
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failures > 0) {
|
||||||
|
console.error(
|
||||||
|
`\nshellcheck: ${failures} file(s) have findings. ` +
|
||||||
|
`See docs/CLEANUP-BASELINE.md for the as-is baseline.`
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
console.log("shellcheck: all files passed.");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user