diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc index 72067d2..1240b71 100644 --- a/.markdownlint-cli2.jsonc +++ b/.markdownlint-cli2.jsonc @@ -29,9 +29,12 @@ "skills/atlassian/pi/SKILL.md" ], - // Ignore patterns — always exclude node_modules and generated artefacts + // Ignore patterns — always exclude node_modules, generated artefacts, and canonical _source dirs "ignores": [ "**/node_modules/**", - "pi-package/**" + "pi-package/**", + // Canonical source directories — contain per-agent source with relative links + // calibrated to their generated location (one level up); exclude from linting. + "skills/**/_source/**" ] } diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..331dad9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,127 @@ +# Changelog — ai-coding-skills + +All notable changes to the ai-coding-skills repository are recorded here. +Entries are milestone-scoped; stories within each milestone are listed for traceability. + +--- + +## M3 — Shared-source generator for agent variants + +### Package metadata change ⚠️ + +**All generated agent-variant `package.json` files have been renamed to unique +private scoped names.** This is an intentional breaking rename in the artifact +structure, but all packages carry `"private": true` and are **never published** +to any registry. End-user manual-install workflows are unaffected: copying a +`skills///` directory continues to work unchanged. + +| Old `name` | New `name` | Path | +|-----------|-----------|------| +| `atlassian-skill-scripts` | `@ai-coding-skills/atlassian-claude-code` | `skills/atlassian/claude-code/scripts/package.json` | +| `atlassian-skill-scripts` | `@ai-coding-skills/atlassian-codex` | `skills/atlassian/codex/scripts/package.json` | +| `atlassian-skill-scripts` | `@ai-coding-skills/atlassian-cursor` | `skills/atlassian/cursor/scripts/package.json` | +| `atlassian-skill-scripts` | `@ai-coding-skills/atlassian-opencode` | `skills/atlassian/opencode/scripts/package.json` | +| `atlassian-skill-scripts` | `@ai-coding-skills/atlassian-pi` | `skills/atlassian/pi/scripts/package.json` | +| `atlassian-skill-scripts` | `@ai-coding-skills/atlassian-pi-mirror` | `pi-package/skills/atlassian/scripts/package.json` | +| `web-automation-scripts` | `@ai-coding-skills/web-automation-claude-code` | `skills/web-automation/claude-code/scripts/package.json` | +| `web-automation-scripts` | `@ai-coding-skills/web-automation-codex` | `skills/web-automation/codex/scripts/package.json` | +| `web-automation-scripts` | `@ai-coding-skills/web-automation-cursor` | `skills/web-automation/cursor/scripts/package.json` | +| `web-automation-scripts` | `@ai-coding-skills/web-automation-opencode` | `skills/web-automation/opencode/scripts/package.json` | +| `web-automation-scripts` | `@ai-coding-skills/web-automation-pi` | `skills/web-automation/pi/scripts/package.json` | +| `web-automation-scripts` | `@ai-coding-skills/web-automation-pi-mirror` | `pi-package/skills/web-automation/scripts/package.json` | + +All renamed packages also gained `"private": true`. + +### What else changed in M3 + +**S-301 — Canonical sources documented** + +Canonical source directories introduced (all outside every generated root): + +- `skills/atlassian/_source//SKILL.md` — per-agent Atlassian skill descriptions +- `skills/web-automation/_source//SKILL.md` — per-agent Web Automation skill descriptions +- `skills/web-automation/shared/` — shared web-automation TypeScript runtime scripts +- `skills/create-plan/_source//` — per-agent create-plan SKILL.md + templates +- `skills/do-task/_source//` — per-agent do-task SKILL.md + templates +- `skills/implement-plan/_source//` — per-agent implement-plan SKILL.md +- `skills/reviewer-runtime/` (base) — canonical `run-review.sh` and `notify-telegram.sh` + +**S-302 — Generator built** + +`scripts/generate-skills.mjs` regenerates all 31 generated roots from canonical +sources in one pass. Exports `detectFileType`, `applyHeader`, +`makePackageJsonContent`, `getGeneratedRoots`, `buildManifest`, and +`generateSkills`. 35 unit tests. + +**S-303 — Manual-workflow skills migrated** + +`create-plan`, `do-task`, `implement-plan` agent variants now generated from +`_source/` canonical sources. Only diffs vs pre-migration: file-type-aware +headers and new `.generated-manifest.json` files. + +**S-304 — Web-automation runtime scripts migrated** + +`skills/web-automation/shared/` created from the former codex canonical source. +All five web-automation agent variants and the pi-package mirror now generated +from this shared location. + +**S-305 — Reviewer-runtime Pi variants migrated** + +`skills/reviewer-runtime/pi/run-review.sh` and `notify-telegram.sh` are now +generated from the canonical base scripts. The "keep this file in sync" +comments are replaced by generated-file headers. + +**S-306 — `sync:pi` and `verify:generated` implemented** + +- `pnpm run sync:pi` now calls `node scripts/generate-skills.mjs`. +- `scripts/verify-generated.mjs` implements the full comparison contract: + walks declared roots only, uses `.generated-manifest.json` as oracle, reports + structured diffs, exits non-zero on any mismatch. 5 unit tests including the + required `_source/` stray-file boundary test. +- `pnpm run verify:generated` wired to the real implementation. + +**S-307 — Workspace updated** + +`pnpm-workspace.yaml` updated to **include** all generated agent-variant +packages (now uniquely named) alongside the canonical sources. The M1 +negative-glob exclusions are replaced by positive includes. + +**S-308 — Documentation updated** + +- `docs/DEVELOPMENT.md`: new "How variants are generated" section with + canonical source table, generated-root list, contributor workflow, header + policy table, and manifest contract. +- `CHANGELOG.md` (this file): full rename table and story summaries. + +### Byte-equivalence diff allow-list (M3 vs pre-M3 generated roots) + +The only permitted diffs between the M3-generated output and the pre-M3 +manually-maintained variants are: + +1. **File-type-aware generated-file headers** — added per the policy table in + `docs/DEVELOPMENT.md`. +2. **New `.generated-manifest.json` files** — one per generated root. +3. **`package.json` `name` field** — renamed to `@ai-coding-skills/-`. +4. **`"private": true` added** — to each generated `package.json`. + +No other content diffs are permitted; `verify:generated` and the generator +itself reject any other change. + +### `pnpm run check` status after M3 + +```text +PASS lint (was FAIL; all pre-existing violations fixed) +PASS typecheck +PASS test +PASS verify:pi +PASS verify:reviewers +PASS verify:docs +PASS verify:generated (was stub; now real implementation) +``` + +All checks green for the first time. + +--- + +*Previous milestones (M1, M2) did not have a CHANGELOG.md entry. See +`docs/CLEANUP-BASELINE.md` for the M1 baseline and M2 delta.* diff --git a/README.md b/README.md index 492ed01..0851b5a 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ The repo root now includes a pi package manifest that ships only the pi-specific - `skills/reviewer-runtime/pi/` - `docs/PI*.md` - `scripts/manage-skills.mjs` and `scripts/manage-skills.sh` -- `scripts/sync-pi-package-skills.sh` +- `scripts/generate-skills.mjs` - `scripts/verify-pi-resources.sh` - `scripts/verify-pi-workflows.sh` - `scripts/verify-reviewer-support.sh` @@ -159,7 +159,7 @@ The repo pins its pnpm version in `package.json` so Corepack-backed installs res Before publishing or sharing a tarball, run: ```bash -./scripts/sync-pi-package-skills.sh +pnpm run sync:pi npm run verify:pi npm run verify:reviewers npm pack --dry-run --json diff --git a/docs/ATLASSIAN.md b/docs/ATLASSIAN.md index de4a073..29a1161 100644 --- a/docs/ATLASSIAN.md +++ b/docs/ATLASSIAN.md @@ -138,7 +138,7 @@ Recommended full Pi package install: Manual single-skill Pi install from the package mirror: ```bash -./scripts/sync-pi-package-skills.sh +pnpm run sync:pi mkdir -p .pi/skills/atlassian cp -R pi-package/skills/atlassian/* .pi/skills/atlassian/ cd .pi/skills/atlassian/scripts diff --git a/docs/CLEANUP-BASELINE.md b/docs/CLEANUP-BASELINE.md index 97038e4..639f80a 100644 --- a/docs/CLEANUP-BASELINE.md +++ b/docs/CLEANUP-BASELINE.md @@ -293,6 +293,59 @@ changes the baseline status of a check.* --- +## Post-M3 state + +Captured: 2026-05-03 · Platform: macOS 15 (arm64) · Node 22.14.0 · pnpm 10.18.1 + +M3 resolved all remaining pre-existing `lint` violations (2 ESLint errors and +7 shellcheck findings). `verify:generated` is now a real implementation +(was a stub in M2). + +### `pnpm run check` aggregate (post-M3) + +```text +PASS lint (was FAIL in M1/M2; pre-existing violations fixed in M3) +PASS typecheck +PASS test +PASS verify:pi +PASS verify:reviewers +PASS verify:docs +PASS verify:generated (was stub in M2; real implementation in M3) +``` + +Overall exit: **0** — all checks green for the first time. + +### What changed in M3 + +- `scripts/generate-skills.mjs` added (generator for all 31 agent-variant roots). +- `scripts/verify-generated.mjs` added (drift detector). +- Canonical source directories created: `skills//_source/`, + `skills/web-automation/shared/`. +- All 31 generated roots now carry `.generated-manifest.json` and file-type-aware + headers. +- `package.json` renames: all generated agent-variant packages renamed to + `@ai-coding-skills/-` with `"private": true`. +- `pnpm-workspace.yaml` updated: M1 negative-glob exclusions replaced by + positive includes for all uniquely-named generated variants. +- Pre-existing ESLint violations fixed: `skill-manager-core.mjs:282` + (`no-useless-assignment`) and `manage-skills.mjs:270` (`no-unused-vars`). +- Pre-existing shellcheck findings fixed: SC2034 and SC2329 in + `reviewer-runtime/run-review.sh`; SC2064 in test trap statements; + SC2016 / SC2251 in verify scripts (suppress intentional patterns). +- `pnpm run sync:pi` now calls the generator instead of + `sync-pi-package-skills.sh`. +- `docs/DEVELOPMENT.md` extended with generation workflow and metadata docs. +- `CHANGELOG.md` created with package-metadata change callout. + +### Full byte-equivalence diff (M3 vs pre-M3) + +See `CHANGELOG.md` § "Byte-equivalence diff allow-list" for the complete +documented diff between M3-generated output and the pre-M3 variants. The +only permitted diffs are headers, `.generated-manifest.json`, `name` rename, +and `"private": true`. + +--- + ## Post-M2 state Captured: 2026-05-03 · Platform: macOS 15 (arm64) · Node 22.14.0 · pnpm 10.18.1 diff --git a/docs/CREATE-PLAN.md b/docs/CREATE-PLAN.md index ed60f12..eafad01 100644 --- a/docs/CREATE-PLAN.md +++ b/docs/CREATE-PLAN.md @@ -116,7 +116,7 @@ Recommended full Pi package install: Manual single-skill Pi install from the package mirror: ```bash -./scripts/sync-pi-package-skills.sh +pnpm run sync:pi mkdir -p .pi/skills/create-plan cp -R pi-package/skills/create-plan/* .pi/skills/create-plan/ mkdir -p .pi/skills/reviewer-runtime/pi diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 904acc1..70119b7 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -167,34 +167,46 @@ container with Node 20. documented in `docs/CLEANUP-BASELINE.md`. Those will be fixed in a dedicated cleanup pass. No violations were introduced by M2. -## pnpm workspace policy (M1 exclusion-only) +**M3 update:** `pnpm run check` is now **fully green**. All pre-existing lint +violations have been fixed (2 ESLint errors, 7 shellcheck findings). The +`verify:generated` check is now a real implementation (was a stub in M2). -The `pnpm-workspace.yaml` at the repo root implements the **non-mutating, -exclusion-only** policy for M1: +## pnpm workspace policy (updated in M3) -**Included** (canonical source packages): +The `pnpm-workspace.yaml` at the repo root uses a **positive-include** policy +introduced in M3. There are no negative-glob exclusions. -- `skills/atlassian/shared/scripts` — shared Atlassian runtime source -- `skills/web-automation/codex/scripts` — provisional canonical copy; M3 - will rename and/or consolidate +**Canonical source packages** (never generated): -**Excluded** (generated agent-variant directories): +- `skills/atlassian/shared/scripts` — shared Atlassian TypeScript runtime +- `skills/web-automation/shared` — shared web-automation runtime template -- `skills/atlassian/{codex,claude-code,cursor,opencode,pi}/scripts` -- `skills/web-automation/{claude-code,cursor,opencode,pi}/scripts` -- `pi-package/**` +**Generated agent-variant packages** (uniquely named, positively included): -**Why exclusion-only in M1?** +- `skills/atlassian/{claude-code,codex,cursor,opencode,pi}/scripts` + → names `@ai-coding-skills/atlassian-{claude-code,codex,cursor,opencode,pi}` +- `skills/web-automation/{claude-code,codex,cursor,opencode,pi}/scripts` + → names `@ai-coding-skills/web-automation-{claude-code,codex,cursor,opencode,pi}` +- `pi-package/skills/atlassian/scripts` + → name `@ai-coding-skills/atlassian-pi-mirror` +- `pi-package/skills/web-automation/scripts` + → name `@ai-coding-skills/web-automation-pi-mirror` -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. +**Why unique names matter:** -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. +Each package in a pnpm workspace must have a distinct `name` field. In M1 all +generated agent-variant packages shared the same non-unique name as their +canonical source package. In M3 every package receives a scoped unique name +of the form `@ai-coding-skills/-`, enabling pnpm to include them +alongside canonical source packages without conflicts. The pi-package mirrors +use the `-mirror` suffix to distinguish them from the `pi` agent variants. + +All generated packages are `"private": true` and are never published to any +registry. + +After `pnpm install`, `git status` should show zero modifications to any +package.json file under any generated directory. If it does not, the workspace +config or generator is broken. ## How to interpret the baseline report @@ -205,8 +217,113 @@ fails on a check, compare the output against the baseline: - If the failure does not appear in the baseline → it is a regression introduced by recent changes and must be fixed before merge. +## How variants are generated (M3) + +Every agent-variant directory under `skills///` and every +`pi-package/skills//` mirror is **generator-owned**. Do not edit +files inside these directories directly — edit the canonical source instead +and run `pnpm run sync:pi` to regenerate. + +### Canonical sources (non-generated) + +These directories are **never** generated and are **never** inside a generated +root. They are the single source of truth for all content. + +| Skill | Canonical source | +|-------|------------------| +| `atlassian` | SKILL.md: `skills/atlassian/_source//SKILL.md`; scripts: `skills/atlassian/shared/scripts/` | +| `web-automation` | SKILL.md: `skills/web-automation/_source//SKILL.md`; scripts: `skills/web-automation/shared/` | +| `create-plan` | `skills/create-plan/_source//` (SKILL.md + templates) | +| `do-task` | `skills/do-task/_source//` (SKILL.md + templates) | +| `implement-plan` | `skills/implement-plan/_source//SKILL.md` | +| `reviewer-runtime` (base) | `skills/reviewer-runtime/run-review.sh`, `notify-telegram.sh` | + +The `_source/` directories and `shared/` directories sit under the skill root +**outside** every generated root. Stale-file detection (`verify:generated`) +never traverses them. + +### Generated roots (authoritative list) + +All paths are relative to the repo root. + +```text +skills/atlassian/{claude-code,codex,cursor,opencode,pi}/ +skills/web-automation/{claude-code,codex,cursor,opencode,pi}/ +skills/create-plan/{claude-code,codex,cursor,opencode,pi}/ +skills/do-task/{claude-code,codex,cursor,opencode,pi}/ +skills/implement-plan/{claude-code,codex,cursor,opencode,pi}/ +skills/reviewer-runtime/pi/ +pi-package/skills/{atlassian,create-plan,do-task,implement-plan,web-automation}/ +``` + +### Contributor workflow + +1. **Edit the canonical source** in the appropriate `_source//`, `shared/`, or + base `skills/reviewer-runtime/` directory. +2. **Run the generator:** + + ```bash + pnpm run sync:pi # regenerates all 31 generated roots + ``` + +3. **Verify no drift:** + + ```bash + pnpm run verify:generated # must exit 0 + ``` + +4. **Stage both the canonical source AND the generated output** in the same commit. + Never commit a canonical change without regenerating. + +### File-type-aware header policy + +Every generated file (except JSON and `pnpm-lock.yaml`) receives a header +identifying it as generated and pointing to the canonical source. Headers are +inserted so they do not break parsers or tools: + +| File type | Header form | Placement | +|-----------|-------------|----------| +| Markdown (with YAML front matter) | `` | After closing `---` | +| Markdown (no front matter) | `` | Top of file | +| Shell script | `# ⚠️ GENERATED FILE ...` | After `#!` shebang | +| TypeScript / JavaScript | `// ⚠️ GENERATED FILE ...` | After `#!` shebang if present, else top | +| YAML (non-lockfile) | `# ⚠️ GENERATED FILE ...` | Top of file | +| JSON | *(no header — recorded in `.generated-manifest.json`)* | — | +| `pnpm-lock.yaml` | *(no header — managed by pnpm)* | — | +| JSONC | `// ⚠️ GENERATED FILE ...` | Top of file | + +### `.generated-manifest.json` contract + +Each generated root contains a `.generated-manifest.json` that: + +- Has a `$schema` marker and a `generator` field for identification. +- Lists every **other** generator-owned path in that root (relative path, + file mode, SHA-256 hash, kind). +- **Does NOT list itself** (non-self-referential). +- Is scoped to exactly **one** generated root — there is no manifest at + `skills//`. +- Is verified structurally (not byte-for-byte) by `verify:generated`. + +### Package metadata (M3 change) + +Each generated agent-variant `package.json` carries: + +- A **unique private name** in the form `@ai-coding-skills/-` (e.g. + `@ai-coding-skills/atlassian-claude-code`). +- `"private": true` to prevent accidental npm publication. + +The pi-package mirrors (`pi-package/skills/atlassian/scripts` and +`pi-package/skills/web-automation/scripts`) use the `-mirror` agent suffix +(`@ai-coding-skills/atlassian-pi-mirror`, `@ai-coding-skills/web-automation-pi-mirror`) +to distinguish them from the `pi` agent variants in the workspace. + +This replaces the previous non-unique `name` fields (`atlassian-skill-scripts`, +`web-automation-scripts`). These packages are private and are never published. +See `CHANGELOG.md` for the full rename list. + ## Links - [CLEANUP-BASELINE.md](./CLEANUP-BASELINE.md) — as-is quality baseline - [README.md](../README.md) — project overview - [docs/README.md](./README.md) — documentation index +- [CHANGELOG.md](../CHANGELOG.md) — milestone-by-milestone change record diff --git a/docs/DO-TASK.md b/docs/DO-TASK.md index 208e662..d721e2e 100644 --- a/docs/DO-TASK.md +++ b/docs/DO-TASK.md @@ -151,7 +151,7 @@ Recommended full Pi package install: Manual single-skill Pi install from the package mirror: ```bash -./scripts/sync-pi-package-skills.sh +pnpm run sync:pi mkdir -p .pi/skills/do-task cp -R pi-package/skills/do-task/* .pi/skills/do-task/ mkdir -p .pi/skills/reviewer-runtime/pi diff --git a/docs/IMPLEMENT-PLAN.md b/docs/IMPLEMENT-PLAN.md index be24e0c..1b1ef7b 100644 --- a/docs/IMPLEMENT-PLAN.md +++ b/docs/IMPLEMENT-PLAN.md @@ -124,7 +124,7 @@ Recommended full Pi package install: Manual single-skill Pi install from the package mirror: ```bash -./scripts/sync-pi-package-skills.sh +pnpm run sync:pi mkdir -p .pi/skills/implement-plan cp -R pi-package/skills/implement-plan/* .pi/skills/implement-plan/ mkdir -p .pi/skills/reviewer-runtime/pi diff --git a/docs/PI.md b/docs/PI.md index a582405..63fcdd7 100644 --- a/docs/PI.md +++ b/docs/PI.md @@ -22,15 +22,18 @@ Related docs: ### Source Of Truth -Author Pi variants under: +Edit the **canonical sources** under: -- `skills/atlassian/pi/` -- `skills/create-plan/pi/` -- `skills/do-task/pi/` -- `skills/implement-plan/pi/` -- `skills/web-automation/pi/` +- `skills/atlassian/_source/pi/SKILL.md` +- `skills/create-plan/_source/pi/` (SKILL.md + templates) +- `skills/do-task/_source/pi/` (SKILL.md + templates) +- `skills/implement-plan/_source/pi/SKILL.md` +- `skills/web-automation/_source/pi/SKILL.md` -These are the directories to edit by hand. +After editing a canonical source, run `pnpm run sync:pi` to regenerate all +agent-variant directories including `skills//pi/` and the package mirrors. +Do not edit the generated `skills//pi/` directories directly — they are +overwritten by the generator. ### Package Mirror @@ -42,7 +45,9 @@ The package exposes: - `pi-package/skills/implement-plan/` - `pi-package/skills/web-automation/` -Those directories are generated from the source variants by [`scripts/sync-pi-package-skills.sh`](../scripts/sync-pi-package-skills.sh). +Those directories are generated from the canonical sources by +[`scripts/generate-skills.mjs`](../scripts/generate-skills.mjs) +(run via `pnpm run sync:pi`). ### Shared Setup Docs @@ -106,7 +111,7 @@ The package surface intentionally ships: - `docs/PI*.md` - `scripts/install-pi-package.sh` - `scripts/manage-skills.mjs` and `scripts/manage-skills.sh` -- `scripts/sync-pi-package-skills.sh` +- `scripts/generate-skills.mjs` - `scripts/verify-pi-resources.sh` - `scripts/verify-pi-workflows.sh` - `scripts/verify-reviewer-support.sh` @@ -130,7 +135,7 @@ Global installs use `~/.pi/agent/skills//` instead of `.pi/skills/ When a source Pi variant changes: ```bash -./scripts/sync-pi-package-skills.sh +pnpm run sync:pi npm run verify:pi npm run verify:reviewers npm pack --dry-run --json diff --git a/docs/README.md b/docs/README.md index f0efe49..f76c90e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -61,10 +61,12 @@ Reference docs for each skill family: ### 7. Development - [DEVELOPMENT.md](./DEVELOPMENT.md) — Prerequisites, `pnpm run check`, - workspace policy, cross-platform shell support, and the transitional quality - contract. + workspace policy, generation workflow, cross-platform shell support, and the + quality contract. - [CLEANUP-BASELINE.md](./CLEANUP-BASELINE.md) — As-is quality baseline captured at M1, updated through subsequent milestones. +- [../CHANGELOG.md](../CHANGELOG.md) — Milestone-by-milestone change record + (includes M3 package-metadata rename table). ## Repo Setup diff --git a/docs/WEB-AUTOMATION.md b/docs/WEB-AUTOMATION.md index edb4f67..969a69b 100644 --- a/docs/WEB-AUTOMATION.md +++ b/docs/WEB-AUTOMATION.md @@ -89,7 +89,7 @@ Recommended full Pi package install: Manual single-skill Pi install from the package mirror: ```bash -./scripts/sync-pi-package-skills.sh +pnpm run sync:pi mkdir -p .pi/skills/web-automation cp -R pi-package/skills/web-automation/* .pi/skills/web-automation/ cd .pi/skills/web-automation/scripts diff --git a/eslint.config.mjs b/eslint.config.mjs index 9dbbc35..efd636f 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -20,16 +20,34 @@ export default [ { 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/**", + // Generated agent-variant directories (M3: now uniquely named but still + // not linted — the canonical source in shared/ or _source/ is the linting target) + "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/**", + // skill-only generated variants (no scripts to lint) + "skills/create-plan/claude-code/**", + "skills/create-plan/codex/**", + "skills/create-plan/cursor/**", + "skills/create-plan/opencode/**", + "skills/create-plan/pi/**", + "skills/do-task/claude-code/**", + "skills/do-task/codex/**", + "skills/do-task/cursor/**", + "skills/do-task/opencode/**", + "skills/do-task/pi/**", + "skills/implement-plan/claude-code/**", + "skills/implement-plan/codex/**", + "skills/implement-plan/cursor/**", + "skills/implement-plan/opencode/**", + "skills/implement-plan/pi/**", + "skills/reviewer-runtime/pi/**", "pi-package/**", ], }, diff --git a/package.json b/package.json index f688e63..6389ef0 100644 --- a/package.json +++ b/package.json @@ -33,13 +33,14 @@ "scripts/lib/skill-manager-core.mjs", "scripts/manage-skills.mjs", "scripts/manage-skills.sh", - "scripts/sync-pi-package-skills.sh", + "CHANGELOG.md", + "scripts/generate-skills.mjs", "scripts/verify-pi-resources.sh", "scripts/verify-pi-workflows.sh", "scripts/verify-reviewer-support.sh" ], "scripts": { - "sync:pi": "./scripts/sync-pi-package-skills.sh", + "sync:pi": "node scripts/generate-skills.mjs", "verify:pi": "./scripts/verify-pi-resources.sh && ./scripts/verify-pi-workflows.sh", "verify:reviewers": "./scripts/verify-reviewer-support.sh", "test:installer": "node --test scripts/tests/*.test.mjs", @@ -49,7 +50,7 @@ "test": "pnpm run test:installer && pnpm run -r --if-present test", "verify:docs": "markdownlint-cli2 ; R1=$?; node scripts/lib/run-link-check.mjs ; R2=$?; node scripts/verify-docs-flow.mjs ; R3=$?; [ $((R1+R2+R3)) -eq 0 ]", "verify:docs:online": "markdownlint-cli2 ; R1=$?; node scripts/lib/run-link-check.mjs --online ; R2=$?; node scripts/verify-docs-flow.mjs ; R3=$?; [ $((R1+R2+R3)) -eq 0 ]", - "verify:generated": "node -e \"console.log('verify:generated: stub — fleshed out in M3'); process.exit(0)\"", + "verify:generated": "node scripts/verify-generated.mjs", "check": "node scripts/lib/run-check.mjs" }, "pi": { diff --git a/pi-package/skills/atlassian/.generated-manifest.json b/pi-package/skills/atlassian/.generated-manifest.json new file mode 100644 index 0000000..9bfbf01 --- /dev/null +++ b/pi-package/skills/atlassian/.generated-manifest.json @@ -0,0 +1,97 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "pi-package/skills/atlassian", + "files": [ + { + "path": "scripts/package.json", + "kind": "file", + "mode": "644", + "sha256": "97dd269f922ea83f6abfd29497ca532b94540acd1461b3a012f92853db55e196" + }, + { + "path": "scripts/pnpm-lock.yaml", + "kind": "file", + "mode": "644", + "sha256": "15556a6f53e68bb8d92d2710aae0836bc80af7f29be9d63aa1b87fcbd33732c6" + }, + { + "path": "scripts/src/adf.ts", + "kind": "file", + "mode": "644", + "sha256": "c7c3b4a78ccd8fb5a8ab99c82e0eab67a0a0d656b3985c1f56817bda199ad20f" + }, + { + "path": "scripts/src/cli.ts", + "kind": "file", + "mode": "644", + "sha256": "5c4f4db76817fa9dbdae0fd0c75be302248d4b87fc0a53f6bd3c90407a75ae98" + }, + { + "path": "scripts/src/config.ts", + "kind": "file", + "mode": "644", + "sha256": "700dcdce96afab5294426e09f539135ae5432632370260190d6292071422eb3f" + }, + { + "path": "scripts/src/confluence.ts", + "kind": "file", + "mode": "644", + "sha256": "709d5d61fdb14e37aa4eaa7175eb7f17f0ec661376c96071020fbc9574ddbb73" + }, + { + "path": "scripts/src/files.ts", + "kind": "file", + "mode": "644", + "sha256": "16296eaa3ae41a4d7c694773036f9bb4bd2baa2db6a9c318078532b713678dba" + }, + { + "path": "scripts/src/health.ts", + "kind": "file", + "mode": "644", + "sha256": "1db4b49e05b16a095b7e7ca31cdc4e22ebda19e20e05c40baaaac648eaec0d08" + }, + { + "path": "scripts/src/http.ts", + "kind": "file", + "mode": "644", + "sha256": "66444b777d4d9b14d9793eb051c586eb811d2b36815b1018dd9d7517666c7eb2" + }, + { + "path": "scripts/src/jira.ts", + "kind": "file", + "mode": "644", + "sha256": "485d8d618fe04eb1ce546c1694eadf15d867bc83c2a6f7df994688ab0335ea4f" + }, + { + "path": "scripts/src/output.ts", + "kind": "file", + "mode": "644", + "sha256": "38e99818582a4962c09a83175634cba2bfead6acf33bd5f43cdca5caed7100a0" + }, + { + "path": "scripts/src/raw.ts", + "kind": "file", + "mode": "644", + "sha256": "2309c96dd45a03509df204803de9ecf0b5ff82fd488730f55ac5dd6a23b81dd8" + }, + { + "path": "scripts/src/types.ts", + "kind": "file", + "mode": "644", + "sha256": "9f92d27ab68604d5abfd0f5dc9552b96fed6d1f9fc7dc6eb30190d8b617628bf" + }, + { + "path": "scripts/tsconfig.json", + "kind": "file", + "mode": "644", + "sha256": "3c2eb7ba5c95a16cada153de4787ca7a4bf179609bf3848e12ff15b1b7927a68" + }, + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "69d83441799f3feada7fbf85691bda16fc30718b724871d7e37cfac574db2253" + } + ] +} diff --git a/pi-package/skills/atlassian/SKILL.md b/pi-package/skills/atlassian/SKILL.md index 10d3507..406e702 100644 --- a/pi-package/skills/atlassian/SKILL.md +++ b/pi-package/skills/atlassian/SKILL.md @@ -3,6 +3,8 @@ name: atlassian description: Interact with Atlassian Cloud Jira and Confluence through a portable task-oriented CLI for search, issue/page edits, comments, transitions, and bounded raw requests. --- + + # Atlassian (Pi) Portable Atlassian workflows for pi using the shared TypeScript CLI in `scripts/`. diff --git a/pi-package/skills/atlassian/scripts/package.json b/pi-package/skills/atlassian/scripts/package.json index a9d8adc..4e18a43 100644 --- a/pi-package/skills/atlassian/scripts/package.json +++ b/pi-package/skills/atlassian/scripts/package.json @@ -1,5 +1,5 @@ { - "name": "atlassian-skill-scripts", + "name": "@ai-coding-skills/atlassian-pi-mirror", "version": "1.0.0", "description": "Shared runtime for the Atlassian skill", "type": "module", @@ -16,5 +16,6 @@ "tsx": "^4.20.5", "typescript": "^5.9.2" }, - "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34" + "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34", + "private": true } diff --git a/pi-package/skills/atlassian/scripts/src/adf.ts b/pi-package/skills/atlassian/scripts/src/adf.ts index 638914c..8774bd1 100644 --- a/pi-package/skills/atlassian/scripts/src/adf.ts +++ b/pi-package/skills/atlassian/scripts/src/adf.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. const TEXT_NODE = "text"; function textNode(text: string) { diff --git a/pi-package/skills/atlassian/scripts/src/cli.ts b/pi-package/skills/atlassian/scripts/src/cli.ts index 6012b99..68c1f87 100644 --- a/pi-package/skills/atlassian/scripts/src/cli.ts +++ b/pi-package/skills/atlassian/scripts/src/cli.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import process from "node:process"; import { pathToFileURL } from "node:url"; diff --git a/pi-package/skills/atlassian/scripts/src/config.ts b/pi-package/skills/atlassian/scripts/src/config.ts index eb34a39..58d37cc 100644 --- a/pi-package/skills/atlassian/scripts/src/config.ts +++ b/pi-package/skills/atlassian/scripts/src/config.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import path from "node:path"; import { config as loadDotEnv } from "dotenv"; diff --git a/pi-package/skills/atlassian/scripts/src/confluence.ts b/pi-package/skills/atlassian/scripts/src/confluence.ts index f22d66d..25e027d 100644 --- a/pi-package/skills/atlassian/scripts/src/confluence.ts +++ b/pi-package/skills/atlassian/scripts/src/confluence.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { sendJsonRequest } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js"; diff --git a/pi-package/skills/atlassian/scripts/src/files.ts b/pi-package/skills/atlassian/scripts/src/files.ts index 8339109..cd7ed39 100644 --- a/pi-package/skills/atlassian/scripts/src/files.ts +++ b/pi-package/skills/atlassian/scripts/src/files.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { readFile } from "node:fs/promises"; import path from "node:path"; diff --git a/pi-package/skills/atlassian/scripts/src/health.ts b/pi-package/skills/atlassian/scripts/src/health.ts index b2d4d24..5946887 100644 --- a/pi-package/skills/atlassian/scripts/src/health.ts +++ b/pi-package/skills/atlassian/scripts/src/health.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { createJsonHeaders, createStatusError } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js"; diff --git a/pi-package/skills/atlassian/scripts/src/http.ts b/pi-package/skills/atlassian/scripts/src/http.ts index 5791886..1184cd1 100644 --- a/pi-package/skills/atlassian/scripts/src/http.ts +++ b/pi-package/skills/atlassian/scripts/src/http.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { createBasicAuthHeader } from "./config.js"; import type { AtlassianConfig, FetchLike } from "./types.js"; diff --git a/pi-package/skills/atlassian/scripts/src/jira.ts b/pi-package/skills/atlassian/scripts/src/jira.ts index 5cf3a6e..af19e03 100644 --- a/pi-package/skills/atlassian/scripts/src/jira.ts +++ b/pi-package/skills/atlassian/scripts/src/jira.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { markdownToAdf } from "./adf.js"; import { sendJsonRequest } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike, JiraIssueSummary } from "./types.js"; diff --git a/pi-package/skills/atlassian/scripts/src/output.ts b/pi-package/skills/atlassian/scripts/src/output.ts index 06b8a6e..afb43d9 100644 --- a/pi-package/skills/atlassian/scripts/src/output.ts +++ b/pi-package/skills/atlassian/scripts/src/output.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import type { CommandOutput, OutputFormat, Writer } from "./types.js"; function renderText(payload: CommandOutput) { diff --git a/pi-package/skills/atlassian/scripts/src/raw.ts b/pi-package/skills/atlassian/scripts/src/raw.ts index 8e11793..620a259 100644 --- a/pi-package/skills/atlassian/scripts/src/raw.ts +++ b/pi-package/skills/atlassian/scripts/src/raw.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { readWorkspaceFile } from "./files.js"; import { sendJsonRequest } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js"; diff --git a/pi-package/skills/atlassian/scripts/src/types.ts b/pi-package/skills/atlassian/scripts/src/types.ts index 7f48f56..c365ff5 100644 --- a/pi-package/skills/atlassian/scripts/src/types.ts +++ b/pi-package/skills/atlassian/scripts/src/types.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. export type AtlassianConfig = { baseUrl: string; jiraBaseUrl: string; diff --git a/pi-package/skills/create-plan/.generated-manifest.json b/pi-package/skills/create-plan/.generated-manifest.json new file mode 100644 index 0000000..a35116e --- /dev/null +++ b/pi-package/skills/create-plan/.generated-manifest.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "pi-package/skills/create-plan", + "files": [ + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "8767db25ce6f03e141ce4c48f37e9d7c4958c3cf3d70729f3bd7214b84f6d065" + }, + { + "path": "templates/continuation-runbook.md", + "kind": "file", + "mode": "644", + "sha256": "1685cded3d4abaf03122a490175ff03b7da593ce60cbca97ae15fadcb706617f" + }, + { + "path": "templates/milestone-plan.md", + "kind": "file", + "mode": "644", + "sha256": "364c08bf0a5ee3738195a0770db72c5a4c9ad7f7fb89eaa064eb8c67f47ad69a" + }, + { + "path": "templates/story-tracker.md", + "kind": "file", + "mode": "644", + "sha256": "ecb550ea9dcd9dde6c813e90af9f538bf5a247fc249e8e323f2b7bf583e52196" + } + ] +} diff --git a/pi-package/skills/create-plan/SKILL.md b/pi-package/skills/create-plan/SKILL.md index 3af2db2..6d91443 100644 --- a/pi-package/skills/create-plan/SKILL.md +++ b/pi-package/skills/create-plan/SKILL.md @@ -3,6 +3,8 @@ name: create-plan description: Use when a user asks to create or maintain a structured implementation plan in pi, including milestones, bite-sized stories, and resumable local planning artifacts under ai_plan. --- + + # Create Plan (Pi) Create and maintain a local plan workspace under `ai_plan/` at project root. diff --git a/pi-package/skills/create-plan/templates/continuation-runbook.md b/pi-package/skills/create-plan/templates/continuation-runbook.md index 4c33f9a..1d0deae 100644 --- a/pi-package/skills/create-plan/templates/continuation-runbook.md +++ b/pi-package/skills/create-plan/templates/continuation-runbook.md @@ -1,3 +1,4 @@ + # Continuation Runbook: [Plan Title] ## Reference Files (START HERE) diff --git a/pi-package/skills/create-plan/templates/milestone-plan.md b/pi-package/skills/create-plan/templates/milestone-plan.md index f646b48..1e428c6 100644 --- a/pi-package/skills/create-plan/templates/milestone-plan.md +++ b/pi-package/skills/create-plan/templates/milestone-plan.md @@ -1,3 +1,4 @@ + # [Plan Title] ## Overview diff --git a/pi-package/skills/create-plan/templates/story-tracker.md b/pi-package/skills/create-plan/templates/story-tracker.md index 08dbd3a..64a0d1f 100644 --- a/pi-package/skills/create-plan/templates/story-tracker.md +++ b/pi-package/skills/create-plan/templates/story-tracker.md @@ -1,3 +1,4 @@ + # Story Tracker: [Plan Title] ## Progress Summary diff --git a/pi-package/skills/do-task/.generated-manifest.json b/pi-package/skills/do-task/.generated-manifest.json new file mode 100644 index 0000000..a158855 --- /dev/null +++ b/pi-package/skills/do-task/.generated-manifest.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "pi-package/skills/do-task", + "files": [ + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "4920ad0cdeda546b37432c2268159724de54ddb01922308f2df88fcca4db8d31" + }, + { + "path": "templates/task-plan.md", + "kind": "file", + "mode": "644", + "sha256": "fd38213fabf350e14b48c5209910d00c16ff74c455101618063835fa8c19e73e" + } + ] +} diff --git a/pi-package/skills/do-task/SKILL.md b/pi-package/skills/do-task/SKILL.md index 16afac2..3e4bb5f 100644 --- a/pi-package/skills/do-task/SKILL.md +++ b/pi-package/skills/do-task/SKILL.md @@ -3,6 +3,8 @@ name: do-task description: Execute a single user-supplied prompt end-to-end in pi with plan review, implementation review, verification, and one persistent task-plan artifact. --- + + # Do Task (Pi) Execute an ad-hoc user prompt end-to-end: parse, clarify, plan, implement, verify, review, commit, and optionally push. diff --git a/pi-package/skills/do-task/templates/task-plan.md b/pi-package/skills/do-task/templates/task-plan.md index 4165ce1..3ddfee7 100644 --- a/pi-package/skills/do-task/templates/task-plan.md +++ b/pi-package/skills/do-task/templates/task-plan.md @@ -1,3 +1,4 @@ + # Task Plan: [Short Title] > **Variant guardrail (pi):** Required workflow skills (`brainstorming`, `test-driven-development`, `verification-before-completion`, `finishing-a-development-branch`, `using-git-worktrees`) must be available to pi as documented in `docs/PI-SUPERPOWERS.md`. Load the relevant workflow skill before entering its matching phase. diff --git a/pi-package/skills/implement-plan/.generated-manifest.json b/pi-package/skills/implement-plan/.generated-manifest.json new file mode 100644 index 0000000..329cb71 --- /dev/null +++ b/pi-package/skills/implement-plan/.generated-manifest.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "pi-package/skills/implement-plan", + "files": [ + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "578e5b4fbc443ade486e4aa034b0875ebfa7eefd3a1268852ac1c5cbf71f28bf" + } + ] +} diff --git a/pi-package/skills/implement-plan/SKILL.md b/pi-package/skills/implement-plan/SKILL.md index eb4e77b..f458dff 100644 --- a/pi-package/skills/implement-plan/SKILL.md +++ b/pi-package/skills/implement-plan/SKILL.md @@ -3,6 +3,8 @@ name: implement-plan description: Use when a plan folder created by create-plan must be executed in pi with milestone verification, reviewer gates, local commits, and resumable tracker updates. --- + + # Implement Plan (Pi) Execute an existing plan under `ai_plan/` milestone by milestone, using verification gates, reviewer approval, and local commits after each approved milestone. diff --git a/pi-package/skills/web-automation/.generated-manifest.json b/pi-package/skills/web-automation/.generated-manifest.json new file mode 100644 index 0000000..3c0bd0b --- /dev/null +++ b/pi-package/skills/web-automation/.generated-manifest.json @@ -0,0 +1,97 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "pi-package/skills/web-automation", + "files": [ + { + "path": "scripts/auth.ts", + "kind": "file", + "mode": "644", + "sha256": "ce0a8aae0bc41b86e11aab51cc0e0cfa484a1934807f147c05c9bd38d416c066" + }, + { + "path": "scripts/browse.ts", + "kind": "file", + "mode": "644", + "sha256": "42da9cdc6806b8d7d8d814952ad9540033b6c6a4cbe9844ada328b2ceace67c9" + }, + { + "path": "scripts/check-install.js", + "kind": "file", + "mode": "644", + "sha256": "e46ee8cbe103794bf1e9c3466bb0fbd21079ceddc60ad9521299e8bc0150e48f" + }, + { + "path": "scripts/extract.js", + "kind": "file", + "mode": "644", + "sha256": "6fa2a0589de8afd6501e332e5fa263e1344187ea43a33590b431cdee59d04217" + }, + { + "path": "scripts/flow.ts", + "kind": "file", + "mode": "644", + "sha256": "b1c256bf6a206473512a4c0555c891893a48025529da282fa6cd07e68ad3d051" + }, + { + "path": "scripts/package.json", + "kind": "file", + "mode": "644", + "sha256": "1d9226da585c65106dacd874e5e6c7951f5a5b2b0f0cf5902f305a951ca4b44d" + }, + { + "path": "scripts/pnpm-lock.yaml", + "kind": "file", + "mode": "644", + "sha256": "17017e15e8b04311f5d53bdd37065b2f5a514a3119f40a0403148440ed181437" + }, + { + "path": "scripts/scan-local-app.ts", + "kind": "file", + "mode": "644", + "sha256": "3f42f9bb2d355fefc8645d2b2acfa3107bd87f9c2579b2631c94132bed0abea4" + }, + { + "path": "scripts/scrape.ts", + "kind": "file", + "mode": "644", + "sha256": "a1a3d81d57d9e8ab1854ce3cb230bdd39ae1087ec50c9fe82cc58f5f2663ebeb" + }, + { + "path": "scripts/test-full.ts", + "kind": "file", + "mode": "644", + "sha256": "76a647e840753621445c36894bff62e163f6a2e4d0860fa8e64d8df45fe21e08" + }, + { + "path": "scripts/test-minimal.ts", + "kind": "file", + "mode": "644", + "sha256": "59e0b2319d3f7521b2a8a4fca2d779afaa157bf2d160160fdec8cb56bea30b4f" + }, + { + "path": "scripts/test-profile.ts", + "kind": "file", + "mode": "644", + "sha256": "6cf0141581a9275bfa8a070a36212cef5f6417d64df3df3e614ec682008376b9" + }, + { + "path": "scripts/tsconfig.json", + "kind": "file", + "mode": "644", + "sha256": "5f9a83c8caab167eb20defbb5afde58f2bb573a300af99654997dcb3372408e0" + }, + { + "path": "scripts/turndown-plugin-gfm.d.ts", + "kind": "file", + "mode": "644", + "sha256": "c5001c059b160eff18a4097a8a0a7b96689b4ebc374543c7d5bf6e40b0d8a5ac" + }, + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "7ff56c1c50697439875f4dd0a7f7697962c8ba2105a4f66ab7b170f5dcc762bd" + } + ] +} diff --git a/pi-package/skills/web-automation/SKILL.md b/pi-package/skills/web-automation/SKILL.md index 5a021e7..53b403a 100644 --- a/pi-package/skills/web-automation/SKILL.md +++ b/pi-package/skills/web-automation/SKILL.md @@ -3,6 +3,8 @@ name: web-automation description: Browse and scrape web pages using Playwright-compatible CloakBrowser. Use when automating web workflows, extracting rendered page content, handling authenticated sessions, or running multi-step browser flows. --- + + # Web Automation with CloakBrowser (Pi) Automated web browsing and scraping for pi using the shared runtime bundle in `scripts/`. diff --git a/pi-package/skills/web-automation/scripts/auth.ts b/pi-package/skills/web-automation/scripts/auth.ts index e79f23d..272ee6d 100644 --- a/pi-package/skills/web-automation/scripts/auth.ts +++ b/pi-package/skills/web-automation/scripts/auth.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. /** * Authentication handler for web automation diff --git a/pi-package/skills/web-automation/scripts/browse.ts b/pi-package/skills/web-automation/scripts/browse.ts index 01cf098..3d27c9e 100644 --- a/pi-package/skills/web-automation/scripts/browse.ts +++ b/pi-package/skills/web-automation/scripts/browse.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. /** * Browser launcher using CloakBrowser with persistent profile diff --git a/pi-package/skills/web-automation/scripts/check-install.js b/pi-package/skills/web-automation/scripts/check-install.js index 2a60c8a..cb7197d 100644 --- a/pi-package/skills/web-automation/scripts/check-install.js +++ b/pi-package/skills/web-automation/scripts/check-install.js @@ -1,4 +1,5 @@ #!/usr/bin/env node +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import fs from "node:fs"; import path from "node:path"; @@ -18,11 +19,9 @@ async function main() { try { await import("cloakbrowser"); await import("playwright-core"); - await import("better-sqlite3"); - await import("esbuild"); } catch (error) { fail( - "Missing dependency/config: web-automation requires cloakbrowser, playwright-core, better-sqlite3, and esbuild.", + "Missing dependency/config: web-automation requires cloakbrowser and playwright-core.", error instanceof Error ? error.message : String(error) ); } @@ -34,7 +33,6 @@ async function main() { } process.stdout.write("OK: cloakbrowser + playwright-core installed\n"); - process.stdout.write("OK: better-sqlite3 + esbuild installed\n"); process.stdout.write("OK: CloakBrowser integration detected in browse.ts\n"); } diff --git a/pi-package/skills/web-automation/scripts/extract.js b/pi-package/skills/web-automation/scripts/extract.js old mode 100755 new mode 100644 index 5e3908a..f7b5995 --- a/pi-package/skills/web-automation/scripts/extract.js +++ b/pi-package/skills/web-automation/scripts/extract.js @@ -1,4 +1,5 @@ #!/usr/bin/env node +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import fs from "node:fs"; import path from "node:path"; diff --git a/pi-package/skills/web-automation/scripts/flow.ts b/pi-package/skills/web-automation/scripts/flow.ts index 5d01e55..dfe2d8c 100644 --- a/pi-package/skills/web-automation/scripts/flow.ts +++ b/pi-package/skills/web-automation/scripts/flow.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import parseArgs from 'minimist'; import type { Page } from 'playwright-core'; diff --git a/pi-package/skills/web-automation/scripts/package.json b/pi-package/skills/web-automation/scripts/package.json index a2221e8..6bc3972 100644 --- a/pi-package/skills/web-automation/scripts/package.json +++ b/pi-package/skills/web-automation/scripts/package.json @@ -1,5 +1,5 @@ { - "name": "web-automation-scripts", + "name": "@ai-coding-skills/web-automation-pi-mirror", "version": "1.0.0", "description": "Web browsing and scraping scripts using CloakBrowser", "type": "module", @@ -32,5 +32,6 @@ "tsx": "^4.7.0", "typescript": "^5.3.0" }, - "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34" + "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34", + "private": true } diff --git a/pi-package/skills/web-automation/scripts/scan-local-app.ts b/pi-package/skills/web-automation/scripts/scan-local-app.ts index 6a05b35..00e213e 100644 --- a/pi-package/skills/web-automation/scripts/scan-local-app.ts +++ b/pi-package/skills/web-automation/scripts/scan-local-app.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { mkdirSync, writeFileSync } from 'fs'; import { dirname, resolve } from 'path'; diff --git a/pi-package/skills/web-automation/scripts/scrape.ts b/pi-package/skills/web-automation/scripts/scrape.ts index 0820de0..2a4b75d 100644 --- a/pi-package/skills/web-automation/scripts/scrape.ts +++ b/pi-package/skills/web-automation/scripts/scrape.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. /** * Web scraper that extracts content to markdown diff --git a/pi-package/skills/web-automation/scripts/test-full.ts b/pi-package/skills/web-automation/scripts/test-full.ts index 356bbab..16037ee 100644 --- a/pi-package/skills/web-automation/scripts/test-full.ts +++ b/pi-package/skills/web-automation/scripts/test-full.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { launchPersistentContext } from 'cloakbrowser'; import { homedir } from 'os'; import { join } from 'path'; diff --git a/pi-package/skills/web-automation/scripts/test-minimal.ts b/pi-package/skills/web-automation/scripts/test-minimal.ts index a5412e7..7cef2ce 100644 --- a/pi-package/skills/web-automation/scripts/test-minimal.ts +++ b/pi-package/skills/web-automation/scripts/test-minimal.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { launch } from 'cloakbrowser'; async function test() { diff --git a/pi-package/skills/web-automation/scripts/test-profile.ts b/pi-package/skills/web-automation/scripts/test-profile.ts index ec59ddd..604ec55 100644 --- a/pi-package/skills/web-automation/scripts/test-profile.ts +++ b/pi-package/skills/web-automation/scripts/test-profile.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { launchPersistentContext } from 'cloakbrowser'; import { homedir } from 'os'; import { join } from 'path'; diff --git a/pi-package/skills/web-automation/scripts/turndown-plugin-gfm.d.ts b/pi-package/skills/web-automation/scripts/turndown-plugin-gfm.d.ts index 316bed1..113765e 100644 --- a/pi-package/skills/web-automation/scripts/turndown-plugin-gfm.d.ts +++ b/pi-package/skills/web-automation/scripts/turndown-plugin-gfm.d.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. declare module 'turndown-plugin-gfm' { import TurndownService from 'turndown'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 00a4946..50c406d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,6 +27,166 @@ importers: specifier: 3.8.3 version: 3.8.3 + pi-package/skills/atlassian/scripts: + dependencies: + commander: + specifier: ^13.1.0 + version: 13.1.0 + dotenv: + specifier: ^16.4.7 + version: 16.6.1 + devDependencies: + '@types/node': + specifier: ^24.3.0 + version: 24.12.2 + tsx: + specifier: ^4.20.5 + version: 4.21.0 + typescript: + specifier: ^5.9.2 + version: 5.9.3 + + pi-package/skills/web-automation/scripts: + dependencies: + '@mozilla/readability': + specifier: ^0.5.0 + version: 0.5.0 + better-sqlite3: + specifier: ^12.6.2 + version: 12.9.0 + cloakbrowser: + specifier: ^0.3.22 + version: 0.3.26(playwright-core@1.59.1) + jsdom: + specifier: ^24.0.0 + version: 24.1.3 + minimist: + specifier: ^1.2.8 + version: 1.2.8 + playwright-core: + specifier: ^1.59.1 + version: 1.59.1 + turndown: + specifier: ^7.1.2 + version: 7.2.4 + turndown-plugin-gfm: + specifier: ^1.0.2 + version: 1.0.2 + devDependencies: + '@types/jsdom': + specifier: ^21.1.6 + version: 21.1.7 + '@types/minimist': + specifier: ^1.2.5 + version: 1.2.5 + '@types/turndown': + specifier: ^5.0.4 + version: 5.0.6 + esbuild: + specifier: 0.27.0 + version: 0.27.0 + tsx: + specifier: ^4.7.0 + version: 4.21.0 + typescript: + specifier: ^5.3.0 + version: 5.9.3 + + skills/atlassian/claude-code/scripts: + dependencies: + commander: + specifier: ^13.1.0 + version: 13.1.0 + dotenv: + specifier: ^16.4.7 + version: 16.6.1 + devDependencies: + '@types/node': + specifier: ^24.3.0 + version: 24.12.2 + tsx: + specifier: ^4.20.5 + version: 4.21.0 + typescript: + specifier: ^5.9.2 + version: 5.9.3 + + skills/atlassian/codex/scripts: + dependencies: + commander: + specifier: ^13.1.0 + version: 13.1.0 + dotenv: + specifier: ^16.4.7 + version: 16.6.1 + devDependencies: + '@types/node': + specifier: ^24.3.0 + version: 24.12.2 + tsx: + specifier: ^4.20.5 + version: 4.21.0 + typescript: + specifier: ^5.9.2 + version: 5.9.3 + + skills/atlassian/cursor/scripts: + dependencies: + commander: + specifier: ^13.1.0 + version: 13.1.0 + dotenv: + specifier: ^16.4.7 + version: 16.6.1 + devDependencies: + '@types/node': + specifier: ^24.3.0 + version: 24.12.2 + tsx: + specifier: ^4.20.5 + version: 4.21.0 + typescript: + specifier: ^5.9.2 + version: 5.9.3 + + skills/atlassian/opencode/scripts: + dependencies: + commander: + specifier: ^13.1.0 + version: 13.1.0 + dotenv: + specifier: ^16.4.7 + version: 16.6.1 + devDependencies: + '@types/node': + specifier: ^24.3.0 + version: 24.12.2 + tsx: + specifier: ^4.20.5 + version: 4.21.0 + typescript: + specifier: ^5.9.2 + version: 5.9.3 + + skills/atlassian/pi/scripts: + dependencies: + commander: + specifier: ^13.1.0 + version: 13.1.0 + dotenv: + specifier: ^16.4.7 + version: 16.6.1 + devDependencies: + '@types/node': + specifier: ^24.3.0 + version: 24.12.2 + tsx: + specifier: ^4.20.5 + version: 4.21.0 + typescript: + specifier: ^5.9.2 + version: 5.9.3 + skills/atlassian/shared/scripts: dependencies: commander: @@ -46,6 +206,52 @@ importers: specifier: ^5.9.2 version: 5.9.3 + skills/web-automation/claude-code/scripts: + dependencies: + '@mozilla/readability': + specifier: ^0.5.0 + version: 0.5.0 + better-sqlite3: + specifier: ^12.6.2 + version: 12.9.0 + cloakbrowser: + specifier: ^0.3.22 + version: 0.3.26(playwright-core@1.59.1) + jsdom: + specifier: ^24.0.0 + version: 24.1.3 + minimist: + specifier: ^1.2.8 + version: 1.2.8 + playwright-core: + specifier: ^1.59.1 + version: 1.59.1 + turndown: + specifier: ^7.1.2 + version: 7.2.4 + turndown-plugin-gfm: + specifier: ^1.0.2 + version: 1.0.2 + devDependencies: + '@types/jsdom': + specifier: ^21.1.6 + version: 21.1.7 + '@types/minimist': + specifier: ^1.2.5 + version: 1.2.5 + '@types/turndown': + specifier: ^5.0.4 + version: 5.0.6 + esbuild: + specifier: 0.27.0 + version: 0.27.0 + tsx: + specifier: ^4.7.0 + version: 4.21.0 + typescript: + specifier: ^5.3.0 + version: 5.9.3 + skills/web-automation/codex/scripts: dependencies: '@mozilla/readability': @@ -92,6 +298,190 @@ importers: specifier: ^5.3.0 version: 5.9.3 + skills/web-automation/cursor/scripts: + dependencies: + '@mozilla/readability': + specifier: ^0.5.0 + version: 0.5.0 + better-sqlite3: + specifier: ^12.6.2 + version: 12.9.0 + cloakbrowser: + specifier: ^0.3.22 + version: 0.3.26(playwright-core@1.59.1) + jsdom: + specifier: ^24.0.0 + version: 24.1.3 + minimist: + specifier: ^1.2.8 + version: 1.2.8 + playwright-core: + specifier: ^1.59.1 + version: 1.59.1 + turndown: + specifier: ^7.1.2 + version: 7.2.4 + turndown-plugin-gfm: + specifier: ^1.0.2 + version: 1.0.2 + devDependencies: + '@types/jsdom': + specifier: ^21.1.6 + version: 21.1.7 + '@types/minimist': + specifier: ^1.2.5 + version: 1.2.5 + '@types/turndown': + specifier: ^5.0.4 + version: 5.0.6 + esbuild: + specifier: 0.27.0 + version: 0.27.0 + tsx: + specifier: ^4.7.0 + version: 4.21.0 + typescript: + specifier: ^5.3.0 + version: 5.9.3 + + skills/web-automation/opencode/scripts: + dependencies: + '@mozilla/readability': + specifier: ^0.5.0 + version: 0.5.0 + better-sqlite3: + specifier: ^12.6.2 + version: 12.9.0 + cloakbrowser: + specifier: ^0.3.22 + version: 0.3.26(playwright-core@1.59.1) + jsdom: + specifier: ^24.0.0 + version: 24.1.3 + minimist: + specifier: ^1.2.8 + version: 1.2.8 + playwright-core: + specifier: ^1.59.1 + version: 1.59.1 + turndown: + specifier: ^7.1.2 + version: 7.2.4 + turndown-plugin-gfm: + specifier: ^1.0.2 + version: 1.0.2 + devDependencies: + '@types/jsdom': + specifier: ^21.1.6 + version: 21.1.7 + '@types/minimist': + specifier: ^1.2.5 + version: 1.2.5 + '@types/turndown': + specifier: ^5.0.4 + version: 5.0.6 + esbuild: + specifier: 0.27.0 + version: 0.27.0 + tsx: + specifier: ^4.7.0 + version: 4.21.0 + typescript: + specifier: ^5.3.0 + version: 5.9.3 + + skills/web-automation/pi/scripts: + dependencies: + '@mozilla/readability': + specifier: ^0.5.0 + version: 0.5.0 + better-sqlite3: + specifier: ^12.6.2 + version: 12.9.0 + cloakbrowser: + specifier: ^0.3.22 + version: 0.3.26(playwright-core@1.59.1) + jsdom: + specifier: ^24.0.0 + version: 24.1.3 + minimist: + specifier: ^1.2.8 + version: 1.2.8 + playwright-core: + specifier: ^1.59.1 + version: 1.59.1 + turndown: + specifier: ^7.1.2 + version: 7.2.4 + turndown-plugin-gfm: + specifier: ^1.0.2 + version: 1.0.2 + devDependencies: + '@types/jsdom': + specifier: ^21.1.6 + version: 21.1.7 + '@types/minimist': + specifier: ^1.2.5 + version: 1.2.5 + '@types/turndown': + specifier: ^5.0.4 + version: 5.0.6 + esbuild: + specifier: 0.27.0 + version: 0.27.0 + tsx: + specifier: ^4.7.0 + version: 4.21.0 + typescript: + specifier: ^5.3.0 + version: 5.9.3 + + skills/web-automation/shared: + dependencies: + '@mozilla/readability': + specifier: ^0.5.0 + version: 0.5.0 + better-sqlite3: + specifier: ^12.6.2 + version: 12.9.0 + cloakbrowser: + specifier: ^0.3.22 + version: 0.3.26(playwright-core@1.59.1) + jsdom: + specifier: ^24.0.0 + version: 24.1.3 + minimist: + specifier: ^1.2.8 + version: 1.2.8 + playwright-core: + specifier: ^1.59.1 + version: 1.59.1 + turndown: + specifier: ^7.1.2 + version: 7.2.4 + turndown-plugin-gfm: + specifier: ^1.0.2 + version: 1.0.2 + devDependencies: + '@types/jsdom': + specifier: ^21.1.6 + version: 21.1.7 + '@types/minimist': + specifier: ^1.2.5 + version: 1.2.5 + '@types/turndown': + specifier: ^5.0.4 + version: 5.0.6 + esbuild: + specifier: 0.27.0 + version: 0.27.0 + tsx: + specifier: ^4.7.0 + version: 4.21.0 + typescript: + specifier: ^5.3.0 + version: 5.9.3 + packages: '@asamuzakjp/css-color@3.2.0': diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 82435d9..f7f6657 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,41 +1,39 @@ -# pnpm workspace — non-mutating, exclusion-only policy (M1) +# pnpm workspace — includes canonical sources + uniquely-named generated variants (M3) # -# 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. +# M3 update: every generated agent-variant package.json now carries a unique +# private name (@ai-coding-skills/-), which allows pnpm to include +# ALL generated roots alongside the canonical sources. The M1 negative-glob +# exclusions are replaced by explicit positive includes. # -# 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 +# Canonical source packages (never generated): +# - skills/atlassian/shared/scripts → atlassian-skill-scripts (source + tests) +# - skills/web-automation/shared → web-automation-scripts (source template) # -# 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. +# Generated agent-variant directories (now uniquely named, included): +# - skills/atlassian/{claude-code,codex,cursor,opencode,pi}/scripts +# - skills/web-automation/{claude-code,codex,cursor,opencode,pi}/scripts +# - pi-package/skills/atlassian/scripts (@ai-coding-skills/atlassian-pi-mirror) +# - pi-package/skills/web-automation/scripts (@ai-coding-skills/web-automation-pi-mirror) packages: # ── Canonical source packages ──────────────────────────────────────────── - "skills/atlassian/shared/scripts" - - "skills/web-automation/codex/scripts" + - "skills/web-automation/shared" - # ── 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/**" + # ── Generated atlassian agent-variant packages ─────────────────────────── + - "skills/atlassian/claude-code/scripts" + - "skills/atlassian/codex/scripts" + - "skills/atlassian/cursor/scripts" + - "skills/atlassian/opencode/scripts" + - "skills/atlassian/pi/scripts" + + # ── Generated web-automation agent-variant packages ────────────────────── + - "skills/web-automation/claude-code/scripts" + - "skills/web-automation/codex/scripts" + - "skills/web-automation/cursor/scripts" + - "skills/web-automation/opencode/scripts" + - "skills/web-automation/pi/scripts" + + # ── Generated pi-package mirrors ───────────────────────────────────────── + - "pi-package/skills/atlassian/scripts" + - "pi-package/skills/web-automation/scripts" diff --git a/scripts/generate-skills.mjs b/scripts/generate-skills.mjs new file mode 100644 index 0000000..75e66b0 --- /dev/null +++ b/scripts/generate-skills.mjs @@ -0,0 +1,600 @@ +#!/usr/bin/env node +/** + * generate-skills.mjs — shared-source generator for agent variants (M3, S-302) + * + * Generates every agent-variant directory (`skills///`) and + * `pi-package/skills//` mirror from canonical sources. Generated files + * carry file-type-aware headers; each generated root gets a non-self-referential + * `.generated-manifest.json`. + * + * Usage: + * node scripts/generate-skills.mjs # regenerate everything + * pnpm run sync:pi # same via pnpm alias + * + * Exported helpers (used by verify-generated.mjs and tests): + * detectFileType(filePath) → string + * applyHeader(content, fileType, canonicalHint) → string + * makePackageJsonContent(sourcePkg, skillName, agentName) → object + * getGeneratedRoots(repoRoot?) → string[] + * buildManifest(generatedRootAbs, generatedRootRel) → Promise + * generateSkills(repoRoot, options?) → Promise<{generatedRoots: string[]}> + */ + +import { + lstat, + mkdir, + readdir, + readFile, + rm, + writeFile, +} from "node:fs/promises"; +import crypto from "node:crypto"; +import path from "node:path"; +import { pathToFileURL } from "node:url"; + +const REPO_ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), ".."); + +// ── Constants ────────────────────────────────────────────────────────────── + +const MANIFEST_SCHEMA = + "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json"; +const MANIFEST_GENERATOR = "scripts/generate-skills.mjs"; +const MANIFEST_FILENAME = ".generated-manifest.json"; + +const AGENTS = ["claude-code", "codex", "cursor", "opencode", "pi"]; + +/** + * Canonical list of all generated roots, relative to repo root. + * Verifier uses this to know which directories to walk. + */ +const GENERATED_ROOTS = [ + // atlassian agent variants + "skills/atlassian/claude-code", + "skills/atlassian/codex", + "skills/atlassian/cursor", + "skills/atlassian/opencode", + "skills/atlassian/pi", + // web-automation agent variants + "skills/web-automation/claude-code", + "skills/web-automation/codex", + "skills/web-automation/cursor", + "skills/web-automation/opencode", + "skills/web-automation/pi", + // create-plan agent variants + "skills/create-plan/claude-code", + "skills/create-plan/codex", + "skills/create-plan/cursor", + "skills/create-plan/opencode", + "skills/create-plan/pi", + // do-task agent variants + "skills/do-task/claude-code", + "skills/do-task/codex", + "skills/do-task/cursor", + "skills/do-task/opencode", + "skills/do-task/pi", + // implement-plan agent variants + "skills/implement-plan/claude-code", + "skills/implement-plan/codex", + "skills/implement-plan/cursor", + "skills/implement-plan/opencode", + "skills/implement-plan/pi", + // reviewer-runtime pi variant + "skills/reviewer-runtime/pi", + // pi-package mirrors + "pi-package/skills/atlassian", + "pi-package/skills/create-plan", + "pi-package/skills/do-task", + "pi-package/skills/implement-plan", + "pi-package/skills/web-automation", +]; + +// ── File-type detection ──────────────────────────────────────────────────── + +/** + * Classify a file path into a header policy category. + * @param {string} filePath - Relative or absolute path to the file. + * @returns {'markdown'|'shell'|'ts'|'js'|'json'|'yaml'|'jsonc'|'unknown'} + */ +export function detectFileType(filePath) { + const base = path.basename(filePath); + const ext = path.extname(base).toLowerCase(); + + if (ext === ".md") return "markdown"; + if (ext === ".sh") return "shell"; + if (ext === ".ts") return "ts"; + if (ext === ".js") return "js"; + if (ext === ".jsonc") return "jsonc"; + if (ext === ".json") return "json"; + if (ext === ".yaml" || ext === ".yml") return "yaml"; + return "unknown"; +} + +// ── Header insertion ────────────────────────────────────────────────────── + +const HEADER_MSG = (hint) => + `⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in ${hint} and run \`pnpm run sync:pi\`.`; + +/** + * Insert a file-type-aware generated-file header into content. + * + * Policy: + * markdown → HTML comment after YAML front matter (or at top if no front matter) + * shell → # comment after shebang (never before it) + * ts/js → // comment at top + * jsonc → // comment at top + * yaml → # comment at top (pnpm-lock.yaml skipped by caller) + * json → no header (recorded in .generated-manifest.json) + * unknown → no header + * + * @param {string} content - Original file content. + * @param {string} fileType - Output of detectFileType(). + * @param {string} canonicalHint - Human-readable hint to canonical source location. + * @returns {string} Content with header inserted, or original if no header applies. + */ +export function applyHeader(content, fileType, canonicalHint) { + const msg = HEADER_MSG(canonicalHint); + + switch (fileType) { + case "markdown": { + // Insert HTML comment after YAML front matter closing ---, or at top + if (content.startsWith("---\n")) { + const closingIdx = content.indexOf("\n---\n", 4); + if (closingIdx !== -1) { + const after = closingIdx + 5; // length of "\n---\n" + const before = content.slice(0, after); + const rest = content.slice(after); + // Ensure the comment is on its own line, with a blank line before/after + return `${before}\n\n${rest}`; + } + } + return `\n${content}`; + } + + case "shell": { + // Insert # comment AFTER shebang line (never before it) + const lines = content.split("\n"); + if (lines[0].startsWith("#!")) { + return [lines[0], `# ${msg}`, ...lines.slice(1)].join("\n"); + } + return `# ${msg}\n${content}`; + } + + case "ts": + case "js": + case "jsonc": { + // Insert after shebang if present (TypeScript requires #! on line 1) + const lines = content.split("\n"); + if (lines[0].startsWith("#!")) { + return [lines[0], `// ${msg}`, ...lines.slice(1)].join("\n"); + } + return `// ${msg}\n${content}`; + } + + case "yaml": { + return `# ${msg}\n${content}`; + } + + case "json": + case "unknown": + default: + return content; + } +} + +// ── package.json transformation ─────────────────────────────────────────── + +/** + * Produce a modified package.json object with a unique scoped name and + * `"private": true` for an agent-variant generated root. + * + * @param {object} sourcePkg - Parsed source package.json object. + * @param {string} skillName - Skill identifier (e.g. "atlassian"). + * @param {string} agentName - Agent identifier (e.g. "claude-code"). + * @returns {object} New object (source is not mutated). + */ +export function makePackageJsonContent(sourcePkg, skillName, agentName) { + return { + ...sourcePkg, + name: `@ai-coding-skills/${skillName}-${agentName}`, + private: true, + }; +} + +// ── Generated-root list ─────────────────────────────────────────────────── + +/** + * Return the authoritative list of generated roots as repo-relative paths. + * Callers (verify-generated.mjs) use this to know which directories to walk. + * + * @returns {string[]} Sorted array of relative paths. + */ +export function getGeneratedRoots() { + return [...GENERATED_ROOTS]; +} + +// ── Manifest construction ───────────────────────────────────────────────── + +/** + * Walk a directory recursively and return all file paths (abs). + * Skips node_modules. + */ +async function walkDir(dir) { + const results = []; + let entries; + try { + entries = await readdir(dir, { withFileTypes: true }); + } catch { + return results; + } + for (const entry of entries) { + if (entry.name === "node_modules") continue; + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + const sub = await walkDir(full); + results.push(...sub); + } else if (entry.isFile()) { + results.push(full); + } + } + return results; +} + +/** + * Build a .generated-manifest.json object for a generated root. + * + * The manifest lists every file in the generated root EXCEPT itself. + * Files are sorted by relative path for stable canonical serialization. + * + * @param {string} generatedRootAbs - Absolute path to the generated root directory. + * @param {string} generatedRootRel - Repo-relative path (e.g. "skills/create-plan/pi"). + * @returns {Promise} Manifest object (not yet serialized to disk). + */ +export async function buildManifest(generatedRootAbs, generatedRootRel) { + const allFiles = await walkDir(generatedRootAbs); + const entries = []; + + for (const absPath of allFiles) { + const relPath = path.relative(generatedRootAbs, absPath).replace(/\\/g, "/"); + + // Non-self-referential: the manifest never lists itself + if (relPath === MANIFEST_FILENAME) continue; + + const contentBuf = await readFile(absPath); + const sha256 = crypto.createHash("sha256").update(contentBuf).digest("hex"); + + const st = await lstat(absPath); + const mode = (st.mode & 0o777).toString(8).padStart(3, "0"); + + entries.push({ + path: relPath, + kind: "file", + mode, + sha256, + }); + } + + entries.sort((a, b) => a.path.localeCompare(b.path)); + + return { + $schema: MANIFEST_SCHEMA, + generator: MANIFEST_GENERATOR, + generatedRoot: generatedRootRel, + files: entries, + }; +} + +// ── Core generation helpers ─────────────────────────────────────────────── + +/** + * Copy a single file from source to destination, inserting a header. + * Skips header for: + * - pnpm-lock.yaml (managed by pnpm, would be stripped on next install) + * - JSON files (per header policy, no in-file header) + * - node_modules (never copied) + */ +async function copyWithHeader(srcAbs, dstAbs, canonicalHint) { + const basename = path.basename(srcAbs); + // Skip pnpm-lock.yaml header (pnpm regenerates without comment) + const skipHeader = basename === "pnpm-lock.yaml"; + + const raw = await readFile(srcAbs, "utf8"); + const fileType = skipHeader ? "unknown" : detectFileType(srcAbs); + const content = applyHeader(raw, fileType, canonicalHint); + + await mkdir(path.dirname(dstAbs), { recursive: true }); + await writeFile(dstAbs, content, "utf8"); +} + +/** + * Recursively copy a directory tree, adding headers to text files. + */ +async function copyDirWithHeaders(srcDir, dstDir, canonicalHint) { + const entries = await readdir(srcDir, { withFileTypes: true }); + await mkdir(dstDir, { recursive: true }); + + for (const entry of entries) { + if (entry.name === "node_modules") continue; + const src = path.join(srcDir, entry.name); + const dst = path.join(dstDir, entry.name); + + if (entry.isDirectory()) { + await copyDirWithHeaders(src, dst, canonicalHint); + } else if (entry.isFile()) { + await copyWithHeader(src, dst, canonicalHint); + } + } +} + +/** + * Write a .generated-manifest.json file into a generated root. + */ +async function writeManifest(generatedRootAbs, generatedRootRel) { + const manifest = await buildManifest(generatedRootAbs, generatedRootRel); + const dstPath = path.join(generatedRootAbs, MANIFEST_FILENAME); + await writeFile(dstPath, JSON.stringify(manifest, null, 2) + "\n", "utf8"); +} + +// ── Skill-family generators ─────────────────────────────────────────────── + +/** + * Generate one agent variant for a "skills-only" skill + * (create-plan, do-task, implement-plan). + * + * Canonical source: skills//_source// + * Generated root: skills/// + * + * @param {string} writeRoot - Root directory to write output into (defaults to repoRoot). + */ +async function generateSkillOnlyVariant(repoRoot, writeRoot, skillName, agentName, generatedRootRel) { + const sourceDir = path.join(repoRoot, "skills", skillName, "_source", agentName); + const targetDir = path.join(writeRoot, generatedRootRel); + const canonicalHint = `skills/${skillName}/_source/${agentName}/`; + + // Clear previous generated content (preserve node_modules if any) + await clearGeneratedRoot(targetDir); + + // Copy all files from source with headers + await copyDirWithHeaders(sourceDir, targetDir, canonicalHint); + + // Write manifest + await writeManifest(targetDir, generatedRootRel); +} + +/** + * Generate one agent variant for a "scripts+skill" skill + * (atlassian, web-automation). + * + * For atlassian: + * - SKILL.md from skills/atlassian/_source//SKILL.md + * - scripts/* from skills/atlassian/shared/scripts/ + * (only src/, tsconfig.json, pnpm-lock.yaml — not tests/ or scripts/sync-*) + * + * For web-automation: + * - SKILL.md from skills/web-automation/_source//SKILL.md + * - scripts/* from skills/web-automation/shared/ + * + * @param {string} [packageAgentName] - Override the agent name used only for the + * package.json `name` field. Defaults to `agentName`. Use this to give + * pi-package mirrors a distinct name (e.g. "pi-mirror") so workspace package + * names are unique even when two roots share the same source agent. + */ +async function generateScriptsSkillVariant( + repoRoot, + writeRoot, + skillName, + agentName, + generatedRootRel, + config, + packageAgentName, +) { + const targetDir = path.join(writeRoot, generatedRootRel); + await clearGeneratedRoot(targetDir); + + // 1. Copy SKILL.md from per-agent canonical source + const skillMdSrc = path.join(repoRoot, "skills", skillName, "_source", agentName, "SKILL.md"); + const skillMdDst = path.join(targetDir, "SKILL.md"); + const skillMdHint = `skills/${skillName}/_source/${agentName}/SKILL.md`; + await copyWithHeader(skillMdSrc, skillMdDst, skillMdHint); + + // 2. Copy scripts + const scriptsTargetDir = path.join(targetDir, "scripts"); + await mkdir(scriptsTargetDir, { recursive: true }); + + const canonicalScripts = path.join(repoRoot, config.canonicalScripts); + const scriptsHint = `${config.canonicalScripts}/`; + + for (const entry of config.scriptFiles) { + const srcPath = path.join(canonicalScripts, entry); + const dstPath = path.join(scriptsTargetDir, entry); // scriptsTargetDir already uses writeRoot + + // Check if source exists + let st; + try { + st = await lstat(srcPath); + } catch { + continue; // skip if file doesn't exist in canonical + } + + if (st.isDirectory()) { + await copyDirWithHeaders(srcPath, dstPath, scriptsHint); + } else { + await copyWithHeader(srcPath, dstPath, scriptsHint); + } + } + + // 3. Generate modified package.json + const srcPkgPath = path.join(canonicalScripts, "package.json"); + const srcPkg = JSON.parse(await readFile(srcPkgPath, "utf8")); + + let targetPkg = makePackageJsonContent(srcPkg, skillName, packageAgentName ?? agentName); + + // For atlassian variants, strip test/sync scripts (not in agent variants) + if (skillName === "atlassian") { + const agentScripts = {}; + for (const [k, v] of Object.entries(targetPkg.scripts ?? {})) { + if (k !== "test" && k !== "sync:agents") agentScripts[k] = v; + } + targetPkg = { ...targetPkg, scripts: agentScripts }; + } + + const dstPkgPath = path.join(scriptsTargetDir, "package.json"); + await writeFile(dstPkgPath, JSON.stringify(targetPkg, null, 2) + "\n", "utf8"); + + // 4. Write manifest + await writeManifest(targetDir, generatedRootRel); +} + +/** + * Generate the reviewer-runtime pi variant from the non-Pi canonical scripts. + * + * Canonical source: skills/reviewer-runtime/{run-review.sh,notify-telegram.sh} + * Generated root: skills/reviewer-runtime/pi/ + * + * The pi variant is byte-identical to the canonical except for: + * - A generated # comment after the shebang (replaces old "keep in sync" comment) + * + * @param {string} writeRoot - Root directory to write output into (defaults to repoRoot). + */ +async function generateReviewerRuntimePi(repoRoot, writeRoot) { + const srcDir = path.join(repoRoot, "skills", "reviewer-runtime"); + const dstDir = path.join(writeRoot, "skills", "reviewer-runtime", "pi"); + const canonicalHint = "skills/reviewer-runtime/"; + + // Clear old generated content (preserve tests/ which is canonical) + await clearGeneratedRoot(dstDir); + + for (const fname of ["run-review.sh", "notify-telegram.sh"]) { + const srcPath = path.join(srcDir, fname); + const dstPath = path.join(dstDir, fname); + + const raw = await readFile(srcPath, "utf8"); + let content = applyHeader(raw, "shell", `${canonicalHint}${fname}`); + + await mkdir(path.dirname(dstPath), { recursive: true }); + await writeFile(dstPath, content, "utf8"); + + // Preserve executable bit + const st = await lstat(srcPath); + if (st.mode & 0o100) { + const { chmod } = await import("node:fs/promises"); + await chmod(dstPath, 0o755); + } + } + + // Write manifest (generatedRootRel is always relative to repo root, not writeRoot) + await writeManifest(dstDir, "skills/reviewer-runtime/pi"); +} + +/** + * Clear generated content in a root, preserving: + * - node_modules (installed by pnpm) + * - .generated-manifest.json (will be rewritten after generation) + */ +async function clearGeneratedRoot(rootDir) { + let entries; + try { + entries = await readdir(rootDir, { withFileTypes: true }); + } catch { + return; // dir doesn't exist yet — nothing to clear + } + + for (const entry of entries) { + if (entry.name === "node_modules") continue; + if (entry.name === MANIFEST_FILENAME) continue; + await rm(path.join(rootDir, entry.name), { recursive: true, force: true }); + } +} + +// ── Skill configurations ────────────────────────────────────────────────── + +const SCRIPTS_SKILL_CONFIGS = { + atlassian: { + canonicalScripts: "skills/atlassian/shared/scripts", + // Files to copy from canonicalScripts into each agent's scripts/ dir + scriptFiles: ["src", "tsconfig.json", "pnpm-lock.yaml"], + }, + "web-automation": { + canonicalScripts: "skills/web-automation/shared", + scriptFiles: [ + "auth.ts", + "browse.ts", + "check-install.js", + "extract.js", + "flow.ts", + "scan-local-app.ts", + "scrape.ts", + "test-full.ts", + "test-minimal.ts", + "test-profile.ts", + "tsconfig.json", + "turndown-plugin-gfm.d.ts", + "pnpm-lock.yaml", + ], + }, +}; + +// ── Main generator ──────────────────────────────────────────────────────── + +/** + * Regenerate all agent variants from canonical sources. + * + * @param {string} repoRoot - Absolute path to repo root (canonical sources are read from here). + * @param {object} [options] + * @param {boolean} [options.dryRun] - If true, don't write files (future use). + * @param {string} [options.targetRoot] - Write generated output here instead of `repoRoot`. + * Canonical sources are always read from `repoRoot`. Use this to generate into a + * temp directory for drift detection without modifying on-disk files. + * @returns {Promise<{generatedRoots: string[]}>} + */ +export async function generateSkills(repoRoot = REPO_ROOT, options = {}) { + const { dryRun = false, targetRoot } = options; + const writeRoot = targetRoot ?? repoRoot; + if (dryRun) { + return { generatedRoots: GENERATED_ROOTS }; + } + + const skillOnlySkills = ["create-plan", "do-task", "implement-plan"]; + + // 1. Generate skill-only skills (create-plan, do-task, implement-plan) + for (const skillName of skillOnlySkills) { + for (const agentName of AGENTS) { + const rootRel = `skills/${skillName}/${agentName}`; + await generateSkillOnlyVariant(repoRoot, writeRoot, skillName, agentName, rootRel); + } + // pi-package mirror (same source as pi variant) + const piPackageRel = `pi-package/skills/${skillName}`; + await generateSkillOnlyVariant(repoRoot, writeRoot, skillName, "pi", piPackageRel); + } + + // 2. Generate scripts skills (atlassian, web-automation) + for (const [skillName, config] of Object.entries(SCRIPTS_SKILL_CONFIGS)) { + for (const agentName of AGENTS) { + const rootRel = `skills/${skillName}/${agentName}`; + await generateScriptsSkillVariant(repoRoot, writeRoot, skillName, agentName, rootRel, config); + } + // pi-package mirror: same source as pi variant but a distinct package name + // ("pi-mirror" suffix) so the workspace has no duplicate package names. + const piPackageRel = `pi-package/skills/${skillName}`; + await generateScriptsSkillVariant(repoRoot, writeRoot, skillName, "pi", piPackageRel, config, "pi-mirror"); + } + + // 3. Generate reviewer-runtime pi variant + await generateReviewerRuntimePi(repoRoot, writeRoot); + + return { generatedRoots: GENERATED_ROOTS }; +} + +// ── CLI entry point ──────────────────────────────────────────────────────── + +if (import.meta.url === pathToFileURL(process.argv[1]).href) { + try { + const result = await generateSkills(REPO_ROOT); + console.log( + `Generated ${result.generatedRoots.length} roots from canonical sources.`, + ); + process.exit(0); + } catch (err) { + console.error("generate-skills: fatal error:", err.message ?? err); + process.exit(1); + } +} diff --git a/scripts/lib/run-link-check.mjs b/scripts/lib/run-link-check.mjs index 58b91f0..6840323 100644 --- a/scripts/lib/run-link-check.mjs +++ b/scripts/lib/run-link-check.mjs @@ -47,13 +47,20 @@ const SKIP_PATHS = new Set([ "pi-package", ]); +// Also skip any _source/ subdirectory within skills — canonical source files +// use relative paths calibrated to the generated location (one level shallower). +const SKIP_SEGMENT = "_source"; + 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"); + if (parts.includes("node_modules")) return true; + // Skip canonical _source/ directories (links calibrated to generated location) + if (parts.includes(SKIP_SEGMENT)) return true; + return false; } function collectMarkdownFiles(dir) { diff --git a/scripts/lib/skill-manager-core.mjs b/scripts/lib/skill-manager-core.mjs index 27f88c9..8afd84b 100644 --- a/scripts/lib/skill-manager-core.mjs +++ b/scripts/lib/skill-manager-core.mjs @@ -279,7 +279,7 @@ async function findClaudeCodeSuperpowersPluginRoots(homeDir) { async function findCursorSuperpowersPluginRoots(homeDir) { const pluginRoot = path.join(homeDir, ".cursor", "plugins", "cache", "cursor-public", "superpowers"); - let entries = []; + let entries; try { entries = await readdir(pluginRoot, { withFileTypes: true }); } catch (error) { @@ -598,7 +598,10 @@ export async function executeOperation(op) { if (op.action === "unsupported" || op.status === "skipped") return { ...op, status: "skipped" }; if (op.kind === "package-skill") return { ...op, status: "included" }; if (op.kind === "sync-pi-package") { - runCommand(path.join(op.repoRoot, "scripts", "sync-pi-package-skills.sh"), [], { cwd: op.repoRoot }); + // Use the canonical generator (pnpm run sync:pi / node scripts/generate-skills.mjs). + // The legacy sync-pi-package-skills.sh is retired in M3; it bypassed the + // generator and copied skills/*/pi into pi-package directly, corrupting manifests. + runCommand(process.execPath, [path.join(op.repoRoot, "scripts", "generate-skills.mjs")], { cwd: op.repoRoot }); return { ...op, status: "ok" }; } if (op.kind === "pi-package") { diff --git a/scripts/manage-skills.mjs b/scripts/manage-skills.mjs index 5050fcf..c63bcf2 100755 --- a/scripts/manage-skills.mjs +++ b/scripts/manage-skills.mjs @@ -267,7 +267,6 @@ async function main() { const removeAnswer = await rl.question(`Remove Superpowers for ${prompt.clientId}/${prompt.scope} too? (yes/no) [no]: `); if (removeAnswer.trim().toLowerCase() === "yes") { const scope = resolveClientScope(prompt.clientId, prompt.scope, process.cwd()); - const client = CLIENTS[prompt.clientId]; const target = `${scope.skillsRoot}/superpowers`; plan.operations.push({ kind: "superpowers", clientId: prompt.clientId, scope: prompt.scope, action: "remove", target, skillsRoot: scope.skillsRoot }); } diff --git a/scripts/tests/generate-skills.test.mjs b/scripts/tests/generate-skills.test.mjs new file mode 100644 index 0000000..90aa27a --- /dev/null +++ b/scripts/tests/generate-skills.test.mjs @@ -0,0 +1,350 @@ +/** + * Unit tests for generate-skills.mjs — RED phase of TDD. + * + * Tests cover: + * - detectFileType: classification of files by extension + * - applyHeader: insertion per file-type-aware policy + * - makePackageJsonContent: unique name + private:true + * - getGeneratedRoots: returns the canonical generated-root list + */ + +import assert from "node:assert/strict"; +import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises"; +import crypto from "node:crypto"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import test from "node:test"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const SCRIPTS_DIR = path.resolve(__dirname, ".."); + +const { + detectFileType, + applyHeader, + makePackageJsonContent, + getGeneratedRoots, + buildManifest, +} = await import(`${SCRIPTS_DIR}/generate-skills.mjs`); + +// ── detectFileType ──────────────────────────────────────────────────────── + +test("detectFileType: .md files → markdown", () => { + assert.equal(detectFileType("SKILL.md"), "markdown"); + assert.equal(detectFileType("templates/milestone-plan.md"), "markdown"); + assert.equal(detectFileType("README.md"), "markdown"); +}); + +test("detectFileType: .sh files → shell", () => { + assert.equal(detectFileType("run-review.sh"), "shell"); + assert.equal(detectFileType("scripts/install.sh"), "shell"); +}); + +test("detectFileType: .ts and .d.ts files → ts", () => { + assert.equal(detectFileType("src/cli.ts"), "ts"); + assert.equal(detectFileType("turndown-plugin-gfm.d.ts"), "ts"); + assert.equal(detectFileType("auth.ts"), "ts"); +}); + +test("detectFileType: .js files → js", () => { + assert.equal(detectFileType("check-install.js"), "js"); + assert.equal(detectFileType("extract.js"), "js"); +}); + +test("detectFileType: .json files → json", () => { + assert.equal(detectFileType("package.json"), "json"); + assert.equal(detectFileType("tsconfig.json"), "json"); +}); + +test("detectFileType: .yaml and .yml files → yaml", () => { + assert.equal(detectFileType("pnpm-lock.yaml"), "yaml"); + assert.equal(detectFileType("other.yml"), "yaml"); +}); + +test("detectFileType: .jsonc files → jsonc", () => { + assert.equal(detectFileType(".markdownlint.jsonc"), "jsonc"); +}); + +test("detectFileType: unknown extension → unknown", () => { + assert.equal(detectFileType("Makefile"), "unknown"); + assert.equal(detectFileType("somefile"), "unknown"); +}); + +// ── applyHeader ─────────────────────────────────────────────────────────── + +test("applyHeader: markdown with YAML front matter inserts HTML comment after closing ---", () => { + const content = "---\nname: create-plan\n---\n\n# Create Plan\n\nBody.\n"; + const result = applyHeader(content, "markdown", "skills/create-plan/_source/claude-code/SKILL.md"); + + // Front matter block preserved verbatim at start + assert.ok(result.startsWith("---\nname: create-plan\n---\n"), "front matter at start"); + + // HTML comment present + assert.ok(result.includes("`; + const generatedContent = `---\nname: test-skill\n---\n\n${headerLine}\n\n# Test Skill\n`; + await writeFile(path.join(agentDir, "SKILL.md"), generatedContent); + + // Add a STRAY file in the agent dir — this SHOULD be flagged + await writeFile(path.join(agentDir, "STRAY.md"), "stray content"); + + // Write a manifest that does NOT include STRAY.md + const { buildManifest } = await import(`${SCRIPTS_DIR}/generate-skills.mjs`); + const manifest = await buildManifest(agentDir, `skills/${skillName}/${agentName}`); + // Remove STRAY.md from manifest (simulate pre-stray-add manifest) + manifest.files = manifest.files.filter((f) => f.path !== "STRAY.md"); + await writeFile( + path.join(agentDir, ".generated-manifest.json"), + JSON.stringify(manifest, null, 2) + "\n", + ); + + const result = await verifyGenerated(dir, { + generatedRootsOverride: [`skills/${skillName}/${agentName}`], + }); + + assert.equal(result.ok, false, "should fail when stray file present"); + const strayError = result.errors.some((e) => e.includes("STRAY.md")); + assert.ok(strayError, `STRAY.md should appear in errors: ${JSON.stringify(result.errors)}`); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); + +test("verifyGenerated: .generated-manifest.json is excluded from stale-file detection", async () => { + // Even though .generated-manifest.json is in the generated root, it should + // not be considered a "stale file" just because it's not in the files list + const dir = await mkdtemp(path.join(tmpdir(), "vg-manifest-self-")); + try { + const skillName = "test-skill"; + const agentName = "pi"; + + const sourceDir = path.join(dir, "skills", skillName, "_source", agentName); + const agentDir = path.join(dir, "skills", skillName, agentName); + + await mkdir(sourceDir, { recursive: true }); + await mkdir(agentDir, { recursive: true }); + + const skillContent = "---\nname: test-skill\n---\n\n# Test Skill\n"; + await writeFile(path.join(sourceDir, "SKILL.md"), skillContent); + + const headerLine = + ``; + const generatedContent = `---\nname: test-skill\n---\n\n${headerLine}\n\n# Test Skill\n`; + await writeFile(path.join(agentDir, "SKILL.md"), generatedContent); + + // Write manifest (will include SKILL.md, not itself) + const { buildManifest } = await import(`${SCRIPTS_DIR}/generate-skills.mjs`); + const manifest = await buildManifest(agentDir, `skills/${skillName}/${agentName}`); + await writeFile( + path.join(agentDir, ".generated-manifest.json"), + JSON.stringify(manifest, null, 2) + "\n", + ); + + const result = await verifyGenerated(dir, { + generatedRootsOverride: [`skills/${skillName}/${agentName}`], + }); + + // Should pass — .generated-manifest.json is excluded from stale detection + const manifestErrors = result.errors.filter((e) => e.includes(".generated-manifest.json")); + assert.equal(manifestErrors.length, 0, `manifest file should not appear as stale: ${JSON.stringify(manifestErrors)}`); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); + +test("verifyGenerated: missing file from manifest is flagged as deleted", async () => { + const dir = await mkdtemp(path.join(tmpdir(), "vg-missing-file-")); + try { + const skillName = "test-skill"; + const agentName = "cursor"; + + const sourceDir = path.join(dir, "skills", skillName, "_source", agentName); + const agentDir = path.join(dir, "skills", skillName, agentName); + + await mkdir(sourceDir, { recursive: true }); + await mkdir(agentDir, { recursive: true }); + + const skillContent = "---\nname: test-skill\n---\n\n# Test Skill\n"; + await writeFile(path.join(sourceDir, "SKILL.md"), skillContent); + await writeFile(path.join(agentDir, "SKILL.md"), "generated content\n"); + + // Manifest claims templates/plan.md exists, but the file doesn't + const manifest = { + $schema: "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + generator: "scripts/generate-skills.mjs", + generatedRoot: `skills/${skillName}/${agentName}`, + files: [ + { path: "SKILL.md", kind: "file", mode: "644", sha256: "aaa" }, + { path: "templates/plan.md", kind: "file", mode: "644", sha256: "bbb" }, + ], + }; + await writeFile( + path.join(agentDir, ".generated-manifest.json"), + JSON.stringify(manifest, null, 2) + "\n", + ); + + const result = await verifyGenerated(dir, { + generatedRootsOverride: [`skills/${skillName}/${agentName}`], + }); + + assert.equal(result.ok, false, "should fail on missing file"); + const missingError = result.errors.some((e) => e.includes("templates/plan.md")); + assert.ok(missingError, `missing file should appear in errors: ${JSON.stringify(result.errors)}`); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); + +test("verifyGenerated: content mismatch is flagged", async () => { + const dir = await mkdtemp(path.join(tmpdir(), "vg-content-mismatch-")); + try { + const skillName = "test-skill"; + const agentName = "opencode"; + + const sourceDir = path.join(dir, "skills", skillName, "_source", agentName); + const agentDir = path.join(dir, "skills", skillName, agentName); + + await mkdir(sourceDir, { recursive: true }); + await mkdir(agentDir, { recursive: true }); + + await writeFile(path.join(sourceDir, "SKILL.md"), "---\nname: test-skill\n---\n\n# Original\n"); + // Agent dir has DIFFERENT content than what manifest says + await writeFile(path.join(agentDir, "SKILL.md"), "---\nname: test-skill\n---\n\n# Modified!\n"); + + const manifest = { + $schema: "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + generator: "scripts/generate-skills.mjs", + generatedRoot: `skills/${skillName}/${agentName}`, + files: [ + { + path: "SKILL.md", + kind: "file", + mode: "644", + // SHA of the ORIGINAL content (not what's on disk) + sha256: "0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }; + await writeFile( + path.join(agentDir, ".generated-manifest.json"), + JSON.stringify(manifest, null, 2) + "\n", + ); + + const result = await verifyGenerated(dir, { + generatedRootsOverride: [`skills/${skillName}/${agentName}`], + }); + + assert.equal(result.ok, false, "should fail on content mismatch"); + const mismatchError = result.errors.some((e) => e.includes("SKILL.md")); + assert.ok(mismatchError, `content mismatch should appear in errors: ${JSON.stringify(result.errors)}`); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); + +test("verifyGenerated: manifest entry with wrong sha256 is flagged even when paths match", async () => { + const dir = await mkdtemp(path.join(tmpdir(), "vg-manifest-sha-")); + try { + const skillName = "test-skill"; + const agentName = "codex"; + + const sourceDir = path.join(dir, "skills", skillName, "_source", agentName); + const agentDir = path.join(dir, "skills", skillName, agentName); + + await mkdir(sourceDir, { recursive: true }); + await mkdir(agentDir, { recursive: true }); + + const content = "---\nname: test-skill\n---\n\n# Test Skill\n"; + await writeFile(path.join(sourceDir, "SKILL.md"), content); + await writeFile(path.join(agentDir, "SKILL.md"), content); + + // Manifest has correct path but deliberately wrong sha256 (simulates corrupted metadata) + const manifest = { + $schema: "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + generator: "scripts/generate-skills.mjs", + generatedRoot: `skills/${skillName}/${agentName}`, + files: [ + { + path: "SKILL.md", + kind: "file", + mode: "644", + sha256: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + }, + ], + }; + await writeFile( + path.join(agentDir, ".generated-manifest.json"), + JSON.stringify(manifest, null, 2) + "\n", + ); + + const result = await verifyGenerated(dir, { + generatedRootsOverride: [`skills/${skillName}/${agentName}`], + }); + + // Even though file paths match, the sha256 mismatch in the manifest should be detected + assert.equal(result.ok, false, "should fail on sha256 mismatch in manifest"); + const sha256Error = result.errors.some( + (e) => e.includes("sha256") || e.includes("SKILL.md"), + ); + assert.ok(sha256Error, `sha256 mismatch should appear in errors: ${JSON.stringify(result.errors)}`); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); + +test("verifyGenerated: manifest entry with wrong mode is flagged even when paths match", async () => { + const dir = await mkdtemp(path.join(tmpdir(), "vg-manifest-mode-")); + try { + const skillName = "test-skill"; + const agentName = "cursor"; + + const agentDir = path.join(dir, "skills", skillName, agentName); + await mkdir(agentDir, { recursive: true }); + + const content = "# Test Skill\n"; + await writeFile(path.join(agentDir, "SKILL.md"), content); + + // Build a correct manifest first (with real sha256) + const { buildManifest } = await import(`${SCRIPTS_DIR}/generate-skills.mjs`); + const correctManifest = await buildManifest(agentDir, `skills/${skillName}/${agentName}`); + + // Tamper: change the mode field for the SKILL.md entry + const tamperedManifest = { + ...correctManifest, + files: correctManifest.files.map((f) => + f.path === "SKILL.md" ? { ...f, mode: "777" } : f, + ), + }; + await writeFile( + path.join(agentDir, ".generated-manifest.json"), + JSON.stringify(tamperedManifest, null, 2) + "\n", + ); + + const result = await verifyGenerated(dir, { + generatedRootsOverride: [`skills/${skillName}/${agentName}`], + }); + + // The mode mismatch in the manifest should be detected by diffManifests + assert.equal(result.ok, false, "should fail on mode mismatch in manifest"); + const modeError = result.errors.some( + (e) => e.includes("mode") || e.includes("SKILL.md"), + ); + assert.ok(modeError, `mode mismatch should appear in errors: ${JSON.stringify(result.errors)}`); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); diff --git a/scripts/verify-generated.mjs b/scripts/verify-generated.mjs new file mode 100644 index 0000000..b4a227a --- /dev/null +++ b/scripts/verify-generated.mjs @@ -0,0 +1,421 @@ +#!/usr/bin/env node +/** + * verify-generated.mjs — drift detector for generator-owned files (M3, S-306) + * + * Verifies that every declared generated root on disk matches the content that + * the generator would produce from the current canonical sources. + * + * Two verification modes: + * + * Production mode (default, no generatedRootsOverride): + * Calls generateSkills() into a temp directory and compares the freshly + * generated output file-by-file against the on-disk generated roots. + * Detects ALL forms of drift: + * (a) Direct edits to generated files + * (b) Canonical source changes without running `pnpm run sync:pi` + * (c) Stale files added to a generated root + * (d) Files deleted from a generated root + * + * Test mode (generatedRootsOverride set): + * Uses `.generated-manifest.json` as the oracle (manifest-based checks). + * Suitable for unit tests that use artificial generated-root fixtures + * without a full canonical source tree. + * + * Walk scope: only the declared generated roots returned by generate-skills.mjs. + * - `skills//_source/` and `skills//shared/` are NEVER walked. + * - `.generated-manifest.json` is excluded from content comparison. + * - `node_modules/` is always excluded. + * + * Exit codes: + * 0 — all generated roots match + * 1 — one or more mismatches detected + * + * Usage: + * node scripts/verify-generated.mjs + * pnpm run verify:generated + * + * Exported: + * verifyGenerated(repoRoot, options?) → Promise<{ok: boolean, errors: string[]}> + */ + +import crypto from "node:crypto"; +import { lstat, mkdtemp, readdir, readFile, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { pathToFileURL } from "node:url"; + +import { buildManifest, generateSkills, getGeneratedRoots } from "./generate-skills.mjs"; + +const REPO_ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), ".."); +const MANIFEST_FILENAME = ".generated-manifest.json"; + +// ── Helpers ─────────────────────────────────────────────────────────────── + +async function sha256File(absPath) { + const buf = await readFile(absPath); + return crypto.createHash("sha256").update(buf).digest("hex"); +} + +async function pathExists(p) { + try { + await lstat(p); + return true; + } catch { + return false; + } +} + +/** + * Walk a directory and return all file relative paths (excluding node_modules + * and the manifest file itself, which is handled separately). + */ +async function walkRootFiles(rootDir) { + const results = []; + let entries; + try { + entries = await readdir(rootDir, { withFileTypes: true }); + } catch { + return results; + } + + for (const entry of entries) { + if (entry.name === "node_modules") continue; + if (entry.name === MANIFEST_FILENAME) continue; // manifest handled separately + + const full = path.join(rootDir, entry.name); + if (entry.isDirectory()) { + const sub = await walkSubDir(full, entry.name); + results.push(...sub); + } else if (entry.isFile()) { + results.push(entry.name); + } + } + return results; +} + +async function walkSubDir(dir, prefix) { + const results = []; + let entries; + try { + entries = await readdir(dir, { withFileTypes: true }); + } catch { + return results; + } + for (const entry of entries) { + if (entry.name === "node_modules") continue; + const relPath = `${prefix}/${entry.name}`; + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + const sub = await walkSubDir(full, relPath); + results.push(...sub); + } else if (entry.isFile()) { + results.push(relPath); + } + } + return results; +} + +/** + * Load and parse the .generated-manifest.json from a generated root. + * Returns null if missing or malformed. + */ +async function loadManifest(rootDir) { + const manifestPath = path.join(rootDir, MANIFEST_FILENAME); + try { + const raw = await readFile(manifestPath, "utf8"); + const obj = JSON.parse(raw); + // Structural validation: must have $schema and generator markers + if (!obj.$schema || !obj.generator || !Array.isArray(obj.files)) { + return null; + } + return obj; + } catch { + return null; + } +} + +/** + * Verify structural equality of two manifests (ignoring ephemeral fields). + * Returns array of difference descriptions, or empty array if equal. + */ +function diffManifests(expected, actual, rootRel) { + const diffs = []; + + if (expected.$schema !== actual.$schema) { + diffs.push(`${rootRel}/.generated-manifest.json: $schema mismatch`); + } + if (expected.generator !== actual.generator) { + diffs.push(`${rootRel}/.generated-manifest.json: generator mismatch`); + } + if (expected.generatedRoot !== actual.generatedRoot) { + diffs.push( + `${rootRel}/.generated-manifest.json: generatedRoot mismatch ` + + `(expected ${expected.generatedRoot}, got ${actual.generatedRoot})`, + ); + } + + // Compare files arrays structurally — paths, kind, mode, and sha256 + const expByPath = new Map(expected.files.map((f) => [f.path, f])); + const actByPath = new Map(actual.files.map((f) => [f.path, f])); + + for (const p of expByPath.keys()) { + if (!actByPath.has(p)) { + diffs.push(`${rootRel}/.generated-manifest.json: expected file entry missing: ${p}`); + } + } + for (const p of actByPath.keys()) { + if (!expByPath.has(p)) { + diffs.push(`${rootRel}/.generated-manifest.json: unexpected file entry: ${p}`); + } + } + + // For entries present in both, compare kind, mode, and sha256 + for (const [p, expEntry] of expByPath) { + const actEntry = actByPath.get(p); + if (!actEntry) continue; // missing already reported above + + if (expEntry.kind !== actEntry.kind) { + diffs.push( + `${rootRel}/.generated-manifest.json: ${p}: kind mismatch ` + + `(expected ${expEntry.kind}, got ${actEntry.kind})`, + ); + } + if (expEntry.mode !== actEntry.mode) { + diffs.push( + `${rootRel}/.generated-manifest.json: ${p}: mode mismatch ` + + `(expected ${expEntry.mode}, got ${actEntry.mode})`, + ); + } + if (expEntry.sha256 !== actEntry.sha256) { + diffs.push( + `${rootRel}/.generated-manifest.json: ${p}: sha256 mismatch ` + + `(expected ${expEntry.sha256.slice(0, 8)}…, got ${actEntry.sha256.slice(0, 8)}…)`, + ); + } + } + + return diffs; +} + +// ── Core verifier ───────────────────────────────────────────────────────── + +/** + * Verify all declared generated roots against freshly generated output. + * + * Calls generateSkills() into a temp directory and compares the resulting + * files to the on-disk roots. Detects all drift types: + * - Canonical source changed without running `pnpm run sync:pi` + * - Generated file edited directly + * - Stale file added to generated root + * - Generated file deleted from root + * + * Does NOT walk _source/, shared/, or node_modules/. + * + * @param {string} repoRoot - Absolute path to repo root. + * @param {string[]} rootsRelative - Generated root paths to verify. + * @param {string[]} errors - Error array to append to. + */ +async function verifyWithFreshGeneration(repoRoot, rootsRelative, errors) { + const tmpDir = await mkdtemp(path.join(tmpdir(), "verify-gen-")); + try { + await generateSkills(repoRoot, { targetRoot: tmpDir }); + + for (const rootRel of rootsRelative) { + const freshRootAbs = path.join(tmpDir, rootRel); + const onDiskRootAbs = path.join(repoRoot, rootRel); + + if (!(await pathExists(onDiskRootAbs))) { + errors.push(`generated root missing: ${rootRel}`); + continue; + } + + const freshFiles = await walkRootFiles(freshRootAbs); + const onDiskFiles = await walkRootFiles(onDiskRootAbs); + + const freshSet = new Set(freshFiles); + const diskSet = new Set(onDiskFiles); + + // Files the generator produces but are absent on disk + for (const f of freshFiles) { + if (!diskSet.has(f)) { + errors.push(`${rootRel}/${f}: missing (expected per canonical sources — run \`pnpm run sync:pi\`)`); + } + } + + // On-disk files the generator would NOT produce (stale) + for (const f of onDiskFiles) { + if (!freshSet.has(f)) { + errors.push(`${rootRel}/${f}: stale (not produced from canonical sources)`); + } + } + + // Content comparison for files present in both sets + for (const f of freshFiles) { + if (!diskSet.has(f)) continue; // missing already reported + const freshHash = await sha256File(path.join(freshRootAbs, f)); + const diskHash = await sha256File(path.join(onDiskRootAbs, f)); + if (freshHash !== diskHash) { + errors.push( + `${rootRel}/${f}: content drift ` + + `(on-disk sha256=${diskHash.slice(0, 8)}…, expected=${freshHash.slice(0, 8)}… — run \`pnpm run sync:pi\`)`, + ); + } + } + + // Validate .generated-manifest.json on disk matches what the generator produced + const freshManifestPath = path.join(freshRootAbs, MANIFEST_FILENAME); + const diskManifestPath = path.join(onDiskRootAbs, MANIFEST_FILENAME); + if (await pathExists(freshManifestPath)) { + if (!(await pathExists(diskManifestPath))) { + errors.push(`${rootRel}/${MANIFEST_FILENAME}: missing`); + } else { + const freshManifest = await loadManifest(freshRootAbs); + const diskManifest = await loadManifest(onDiskRootAbs); + if (!diskManifest) { + errors.push(`${rootRel}/${MANIFEST_FILENAME}: missing or malformed`); + } else if (freshManifest) { + const manifestDiffs = diffManifests(freshManifest, diskManifest, rootRel); + errors.push(...manifestDiffs); + } + } + } + } + } finally { + await rm(tmpDir, { recursive: true, force: true }); + } +} + +/** + * Verify using .generated-manifest.json as the oracle (test / no-canonical-sources mode). + * + * Used when generatedRootsOverride is set: the test fixture creates generated + * roots with manifests but does not provide a full canonical source tree. + * + * For each root: + * 1. Load the on-disk .generated-manifest.json (report missing manifest). + * 2. For every file listed in the manifest: verify existence, mode, sha256. + * 3. Walk the root for any files NOT listed in the manifest (stale files). + * 4. Verify the manifest's own structural fields match expected schema. + * + * @param {string} repoRoot - Absolute path to repo root. + * @param {string[]} rootsRelative - Generated root paths to verify. + * @param {string[]} errors - Error array to append to. + */ +async function verifyWithManifest(repoRoot, rootsRelative, errors) { + for (const rootRel of rootsRelative) { + const rootAbs = path.join(repoRoot, rootRel); + + // Check the root directory exists + if (!(await pathExists(rootAbs))) { + errors.push(`generated root missing: ${rootRel}`); + continue; + } + + // Load on-disk manifest + const manifest = await loadManifest(rootAbs); + if (!manifest) { + errors.push(`${rootRel}/.generated-manifest.json: missing or malformed`); + // Can't verify files without a manifest; report all on-disk files as stale + const onDisk = await walkRootFiles(rootAbs); + for (const f of onDisk) { + errors.push(`${rootRel}/${f}: stale (no valid manifest)`); + } + continue; + } + + // Build expected manifest from current disk state (structural check) + const expectedManifest = await buildManifest(rootAbs, rootRel); + const manifestDiffs = diffManifests(expectedManifest, manifest, rootRel); + errors.push(...manifestDiffs); + + // Build maps for file checks + const manifestByPath = new Map(manifest.files.map((f) => [f.path, f])); + + // Check each file listed in manifest + for (const entry of manifest.files) { + const fileAbs = path.join(rootAbs, entry.path); + + if (!(await pathExists(fileAbs))) { + errors.push(`${rootRel}/${entry.path}: missing (listed in manifest)`); + continue; + } + + // Check content hash + const actualHash = await sha256File(fileAbs); + if (actualHash !== entry.sha256) { + errors.push( + `${rootRel}/${entry.path}: content mismatch ` + + `(manifest sha256=${entry.sha256.slice(0, 8)}…, actual=${actualHash.slice(0, 8)}…)`, + ); + } + + // Check mode + const st = await lstat(fileAbs); + const actualMode = (st.mode & 0o777).toString(8).padStart(3, "0"); + if (actualMode !== entry.mode) { + errors.push( + `${rootRel}/${entry.path}: mode mismatch ` + + `(manifest=${entry.mode}, actual=${actualMode})`, + ); + } + } + + // Detect stale files: on-disk files not listed in manifest + const onDiskFiles = await walkRootFiles(rootAbs); + for (const relPath of onDiskFiles) { + if (!manifestByPath.has(relPath)) { + errors.push(`${rootRel}/${relPath}: stale (not in manifest)`); + } + } + } +} + +/** + * Verify all declared generated roots in a repo. + * + * In production mode (no generatedRootsOverride): generates into a temp + * directory and compares file-by-file against on-disk roots. This detects + * both direct edits to generated files AND changes to canonical sources that + * were not followed by `pnpm run sync:pi`. + * + * In test mode (generatedRootsOverride set): uses manifest-based checks. + * Suitable for unit tests that provide artificial generated-root fixtures + * without a full canonical source tree. + * + * @param {string} [repoRoot] - Absolute path to repo root. + * @param {object} [options] + * @param {string[]} [options.generatedRootsOverride] - Override root list (test mode only). + * @returns {Promise<{ok: boolean, errors: string[]}>} + */ +export async function verifyGenerated(repoRoot = REPO_ROOT, options = {}) { + const rootsRelative = options.generatedRootsOverride ?? getGeneratedRoots(); + const errors = []; + + if (options.generatedRootsOverride) { + // Test mode: no canonical source tree available — use manifest oracle + await verifyWithManifest(repoRoot, rootsRelative, errors); + } else { + // Production mode: generate fresh from canonical sources and compare + await verifyWithFreshGeneration(repoRoot, rootsRelative, errors); + } + + return { ok: errors.length === 0, errors }; +} + +// ── CLI entry point ──────────────────────────────────────────────────────── + +if (import.meta.url === pathToFileURL(process.argv[1]).href) { + const result = await verifyGenerated(REPO_ROOT); + + if (result.ok) { + console.log("verify:generated: all generated roots are up to date."); + process.exit(0); + } else { + console.error("verify:generated: drift detected:\n"); + for (const err of result.errors) { + console.error(` ✗ ${err}`); + } + console.error(`\n${result.errors.length} error(s). Run \`pnpm run sync:pi\` to regenerate.`); + process.exit(1); + } +} diff --git a/scripts/verify-pi-resources.sh b/scripts/verify-pi-resources.sh index b2dd387..28b6f63 100755 --- a/scripts/verify-pi-resources.sh +++ b/scripts/verify-pi-resources.sh @@ -21,7 +21,6 @@ REQUIRED_FILES=( "skills/reviewer-runtime/pi/run-review.sh" "skills/reviewer-runtime/pi/notify-telegram.sh" "scripts/install-pi-package.sh" - "scripts/sync-pi-package-skills.sh" "pi-package/skills/atlassian/SKILL.md" "pi-package/skills/create-plan/SKILL.md" "pi-package/skills/do-task/SKILL.md" @@ -39,13 +38,13 @@ done test -x skills/reviewer-runtime/pi/run-review.sh test -x skills/reviewer-runtime/pi/notify-telegram.sh test -x scripts/install-pi-package.sh -test -x scripts/sync-pi-package-skills.sh find skills/web-automation/pi/scripts -type f -print -quit | grep -q . find skills/atlassian/pi/scripts -type f -print -quit | grep -q . for file in skills/create-plan/pi/SKILL.md skills/do-task/pi/SKILL.md skills/implement-plan/pi/SKILL.md; do grep -q 'docs/PI-SUPERPOWERS.md' "$file" grep -q 'docs/PI-COMMON-REVIEWER.md' "$file" + # shellcheck disable=SC2016 grep -q 'Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, `pi`, or `skip`' "$file" grep -q 'pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files' "$file" grep -q -- '--tools read,grep,find,ls -p' "$file" @@ -54,11 +53,13 @@ done grep -q 'reviewer model is configured independently' docs/PI-COMMON-REVIEWER.md grep -q 'provider-qualified model IDs' docs/PI-COMMON-REVIEWER.md +# shellcheck disable=SC2016 grep -q 'MUST NOT include `write`, `edit`, or `bash`' docs/PI-COMMON-REVIEWER.md grep -q 'Reviewer CLI | codex \\| claude \\| cursor \\| opencode \\| pi' skills/do-task/pi/templates/task-plan.md grep -q 'pi-package/skills/atlassian/scripts' skills/atlassian/pi/SKILL.md grep -q 'pi-package/skills/web-automation/scripts' skills/web-automation/pi/SKILL.md +# shellcheck disable=SC2016 grep -q 'local checkout package install keeps the runtime in `pi-package/skills//scripts`' docs/PI.md grep -q 'install-pi-package.sh --global' docs/PI.md @@ -81,6 +82,8 @@ for family in atlassian create-plan do-task implement-plan web-automation; do diff -qr \ --exclude '.DS_Store' \ --exclude 'node_modules' \ + --exclude '.generated-manifest.json' \ + --exclude 'package.json' \ "$source_dir" "$mirror_dir" >/dev/null while IFS= read -r -d '' source_path; do @@ -93,10 +96,14 @@ for family in atlassian create-plan do-task implement-plan web-automation; do -mindepth 1 -print0) done -! grep -nE 'update_plan|plan mode|sub-agent|subagents' \ +# SC2251: restructured to avoid ! outside condition +if grep -nE 'update_plan|plan mode|sub-agent|subagents' \ skills/create-plan/pi/SKILL.md \ skills/do-task/pi/SKILL.md \ - skills/implement-plan/pi/SKILL.md + skills/implement-plan/pi/SKILL.md; then + echo "Error: pi SKILL.md files must not contain Codex-specific terms" >&2 + exit 1 +fi node <<'EOF' const fs = require("fs"); @@ -129,7 +136,7 @@ for (const requiredFile of [ "pi-package/skills", "docs/PI-COMMON-REVIEWER.md", "scripts/install-pi-package.sh", - "scripts/sync-pi-package-skills.sh", + "scripts/generate-skills.mjs", ]) { if (!pkg.files.includes(requiredFile)) { console.error(`package.json files must include ${requiredFile}`); diff --git a/scripts/verify-pi-workflows.sh b/scripts/verify-pi-workflows.sh index 5e90488..5274db9 100755 --- a/scripts/verify-pi-workflows.sh +++ b/scripts/verify-pi-workflows.sh @@ -13,6 +13,7 @@ WORKFLOW_FILES=( for file in "${WORKFLOW_FILES[@]}"; do test -f "$file" grep -q 'docs/PI-SUPERPOWERS.md' "$file" + # shellcheck disable=SC2016 grep -q 'Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, `pi`, or `skip`' "$file" grep -q 'pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files' "$file" grep -q -- '--tools read,grep,find,ls -p' "$file" @@ -21,6 +22,7 @@ done grep -q 'reviewer model is configured independently' docs/PI-COMMON-REVIEWER.md grep -q 'provider-qualified model IDs' docs/PI-COMMON-REVIEWER.md +# shellcheck disable=SC2016 grep -q 'MUST NOT include `write`, `edit`, or `bash`' docs/PI-COMMON-REVIEWER.md if command -v pi >/dev/null 2>&1; then @@ -43,6 +45,10 @@ grep -q 'Reviewer CLI | codex \\| claude \\| cursor \\| opencode \\| pi' skills/ test -x skills/reviewer-runtime/pi/run-review.sh test -x skills/reviewer-runtime/pi/notify-telegram.sh -! rg -n 'update_plan|plan mode|sub-agent|subagents' "${WORKFLOW_FILES[@]}" +# SC2251: restructured to avoid ! outside condition +if rg -n 'update_plan|plan mode|sub-agent|subagents' "${WORKFLOW_FILES[@]}"; then + echo "Error: pi workflow SKILL.md files must not contain Codex-specific terms" >&2 + exit 1 +fi echo "pi workflow skill docs verified" diff --git a/skills/atlassian/_source/claude-code/SKILL.md b/skills/atlassian/_source/claude-code/SKILL.md new file mode 100644 index 0000000..c7708d8 --- /dev/null +++ b/skills/atlassian/_source/claude-code/SKILL.md @@ -0,0 +1,78 @@ +--- +name: atlassian +description: Interact with Atlassian Cloud Jira and Confluence through a portable task-oriented CLI for search, issue/page edits, comments, transitions, and bounded raw requests. +--- + +# Atlassian (Claude Code) + +Portable Atlassian workflows for Claude Code using a shared TypeScript CLI. + +## Requirements + +- Node.js 20+ +- `pnpm` +- Atlassian Cloud account access +- `ATLASSIAN_BASE_URL` +- `ATLASSIAN_EMAIL` +- `ATLASSIAN_API_TOKEN` + +The `ATLASSIAN_*` values may come from the shell environment or a `.env` file in `~/.claude/skills/atlassian/scripts`. + +## First-Time Setup + +```bash +mkdir -p ~/.claude/skills/atlassian +cp -R skills/atlassian/claude-code/* ~/.claude/skills/atlassian/ +cd ~/.claude/skills/atlassian/scripts +pnpm install +``` + +## Prerequisite Check (MANDATORY) + +```bash +cd ~/.claude/skills/atlassian/scripts +node -e "require.resolve('commander');require.resolve('dotenv');console.log('OK: runtime dependencies installed')" +node -e 'require("dotenv").config({ path: ".env" }); const required = ["ATLASSIAN_BASE_URL", "ATLASSIAN_EMAIL", "ATLASSIAN_API_TOKEN"]; const missing = required.filter((key) => !(process.env[key] || "").trim()); if (missing.length) { console.error("Missing required Atlassian config: " + missing.join(", ")); process.exit(1); } console.log("OK: Atlassian config present")' +pnpm atlassian health +``` + +If any check fails, stop and return: + +`Missing dependency/config: atlassian requires installed CLI dependencies and valid Atlassian Cloud credentials. Configure ATLASSIAN_* in the shell environment or scripts/.env, then retry.` + +## Supported Commands + +- `pnpm atlassian health` +- `pnpm atlassian jira-search --jql "..."` +- `pnpm atlassian jira-get --issue ABC-123` +- `pnpm atlassian jira-create ... [--dry-run]` +- `pnpm atlassian jira-update ... [--dry-run]` +- `pnpm atlassian jira-comment ... [--dry-run]` +- `pnpm atlassian jira-transitions --issue ABC-123` +- `pnpm atlassian jira-transition ... [--dry-run]` +- `pnpm atlassian conf-search --query "..."` +- `pnpm atlassian conf-get --page 12345` +- `pnpm atlassian conf-create ... [--dry-run]` +- `pnpm atlassian conf-update ... [--dry-run]` +- `pnpm atlassian conf-comment ... [--dry-run]` +- `pnpm atlassian conf-children --page 12345` +- `pnpm atlassian raw --product jira|confluence --method GET|POST|PUT --path ...` + +## Usage Examples + +- `pnpm atlassian jira-search --jql "project = ENG ORDER BY updated DESC" --max-results 10` +- `pnpm atlassian conf-comment --page 12345 --body-file comment.storage.html --dry-run` +- `pnpm atlassian raw --product jira --method GET --path "/rest/api/3/issue/ENG-123"` + +## Safety Rules + +- Default output is JSON; only switch to text output when the user needs a human-readable summary. +- Use `--dry-run` before any write unless the user clearly asked for the mutation. +- Treat `raw` as an escape hatch, not the default API surface. +- `--body-file` must stay inside the current workspace. +- Confluence write bodies should be storage-format inputs in v1. + +## Notes + +- Atlassian Cloud is the primary supported platform in v1. +- The portable CLI exists so the same skill works consistently across multiple agent environments. diff --git a/skills/atlassian/_source/codex/SKILL.md b/skills/atlassian/_source/codex/SKILL.md new file mode 100644 index 0000000..e955654 --- /dev/null +++ b/skills/atlassian/_source/codex/SKILL.md @@ -0,0 +1,81 @@ +--- +name: atlassian +description: Interact with Atlassian Cloud Jira and Confluence through a portable task-oriented CLI for search, issue/page edits, comments, transitions, and bounded raw requests. +--- + +# Atlassian (Codex) + +Portable Atlassian workflows for Codex using a shared TypeScript CLI. + +## Requirements + +- Node.js 20+ +- `pnpm` +- Atlassian Cloud account access +- `ATLASSIAN_BASE_URL` +- `ATLASSIAN_EMAIL` +- `ATLASSIAN_API_TOKEN` + +The `ATLASSIAN_*` values may come from the shell environment or a `.env` file in `~/.codex/skills/atlassian/scripts`. + +## First-Time Setup + +```bash +mkdir -p ~/.codex/skills/atlassian +cp -R skills/atlassian/codex/* ~/.codex/skills/atlassian/ +cd ~/.codex/skills/atlassian/scripts +pnpm install +``` + +## Prerequisite Check (MANDATORY) + +Run before using the skill: + +```bash +cd ~/.codex/skills/atlassian/scripts +node -e "require.resolve('commander');require.resolve('dotenv');console.log('OK: runtime dependencies installed')" +node -e 'require("dotenv").config({ path: ".env" }); const required = ["ATLASSIAN_BASE_URL", "ATLASSIAN_EMAIL", "ATLASSIAN_API_TOKEN"]; const missing = required.filter((key) => !(process.env[key] || "").trim()); if (missing.length) { console.error("Missing required Atlassian config: " + missing.join(", ")); process.exit(1); } console.log("OK: Atlassian config present")' +pnpm atlassian health +``` + +If any check fails, stop and return: + +`Missing dependency/config: atlassian requires installed CLI dependencies and valid Atlassian Cloud credentials. Configure ATLASSIAN_* in the shell environment or scripts/.env, then retry.` + +## Supported Commands + +- `pnpm atlassian health` +- `pnpm atlassian jira-search --jql "..."` +- `pnpm atlassian jira-get --issue ABC-123` +- `pnpm atlassian jira-create ... [--dry-run]` +- `pnpm atlassian jira-update ... [--dry-run]` +- `pnpm atlassian jira-comment ... [--dry-run]` +- `pnpm atlassian jira-transitions --issue ABC-123` +- `pnpm atlassian jira-transition ... [--dry-run]` +- `pnpm atlassian conf-search --query "..."` +- `pnpm atlassian conf-get --page 12345` +- `pnpm atlassian conf-create ... [--dry-run]` +- `pnpm atlassian conf-update ... [--dry-run]` +- `pnpm atlassian conf-comment ... [--dry-run]` +- `pnpm atlassian conf-children --page 12345` +- `pnpm atlassian raw --product jira|confluence --method GET|POST|PUT --path ...` + +## Usage Examples + +- `pnpm atlassian jira-search --jql "project = ENG ORDER BY updated DESC" --max-results 10` +- `pnpm atlassian conf-update --page 12345 --title "Runbook" --body-file page.storage.html --dry-run` +- `pnpm atlassian raw --product confluence --method POST --path "/wiki/api/v2/pages" --body-file page.json --dry-run` + +## Safety Rules + +- Default output is JSON; prefer that for agent workflows. +- Use `--dry-run` before any mutating command unless the user clearly wants the write to happen immediately. +- Jira long-text fields are converted to ADF locally. +- Confluence page bodies are storage-first in v1. +- `--body-file` must point to workspace-scoped files only; do not use arbitrary system paths. +- `raw` is for explicit edge cases only and does not allow `DELETE`. + +## Notes + +- Atlassian Cloud is the only first-class target in v1. +- This skill exists so Codex, Claude Code, Cursor Agent, and OpenCode can share the same command surface even when MCP access differs. diff --git a/skills/atlassian/_source/cursor/SKILL.md b/skills/atlassian/_source/cursor/SKILL.md new file mode 100644 index 0000000..91cfb1c --- /dev/null +++ b/skills/atlassian/_source/cursor/SKILL.md @@ -0,0 +1,93 @@ +--- +name: atlassian +description: Interact with Atlassian Cloud Jira and Confluence through a portable task-oriented CLI for search, issue/page edits, comments, transitions, and bounded raw requests. +--- + +# Atlassian (Cursor Agent CLI) + +Portable Atlassian workflows for Cursor Agent CLI using a shared TypeScript CLI. + +## Requirements + +- Cursor Agent CLI skill discovery via `.cursor/skills/` or `~/.cursor/skills/` +- Node.js 20+ +- `pnpm` +- Atlassian Cloud account access +- `ATLASSIAN_BASE_URL` +- `ATLASSIAN_EMAIL` +- `ATLASSIAN_API_TOKEN` + +The `ATLASSIAN_*` values may come from the shell environment or a `.env` file in the installed `scripts/` folder. + +## First-Time Setup + +Repo-local install: + +```bash +mkdir -p .cursor/skills/atlassian +cp -R skills/atlassian/cursor/* .cursor/skills/atlassian/ +cd .cursor/skills/atlassian/scripts +pnpm install +``` + +Global install: + +```bash +mkdir -p ~/.cursor/skills/atlassian +cp -R skills/atlassian/cursor/* ~/.cursor/skills/atlassian/ +cd ~/.cursor/skills/atlassian/scripts +pnpm install +``` + +## Prerequisite Check (MANDATORY) + +Repo-local form: + +```bash +cursor-agent --version +cd .cursor/skills/atlassian/scripts +node -e "require.resolve('commander');require.resolve('dotenv');console.log('OK: runtime dependencies installed')" +node -e 'require("dotenv").config({ path: ".env" }); const required = ["ATLASSIAN_BASE_URL", "ATLASSIAN_EMAIL", "ATLASSIAN_API_TOKEN"]; const missing = required.filter((key) => !(process.env[key] || "").trim()); if (missing.length) { console.error("Missing required Atlassian config: " + missing.join(", ")); process.exit(1); } console.log("OK: Atlassian config present")' +pnpm atlassian health +``` + +If any check fails, stop and return: + +`Missing dependency/config: atlassian requires installed CLI dependencies and valid Atlassian Cloud credentials. Configure ATLASSIAN_* in the shell environment or scripts/.env, then retry.` + +## Supported Commands + +- `pnpm atlassian health` +- `pnpm atlassian jira-search --jql "..."` +- `pnpm atlassian jira-get --issue ABC-123` +- `pnpm atlassian jira-create ... [--dry-run]` +- `pnpm atlassian jira-update ... [--dry-run]` +- `pnpm atlassian jira-comment ... [--dry-run]` +- `pnpm atlassian jira-transitions --issue ABC-123` +- `pnpm atlassian jira-transition ... [--dry-run]` +- `pnpm atlassian conf-search --query "..."` +- `pnpm atlassian conf-get --page 12345` +- `pnpm atlassian conf-create ... [--dry-run]` +- `pnpm atlassian conf-update ... [--dry-run]` +- `pnpm atlassian conf-comment ... [--dry-run]` +- `pnpm atlassian conf-children --page 12345` +- `pnpm atlassian raw --product jira|confluence --method GET|POST|PUT --path ...` + +## Usage Examples + +- `pnpm atlassian jira-get --issue ENG-123` +- `pnpm atlassian conf-search --query "title ~ \\\"Runbook\\\"" --max-results 10 --start-at 0` +- `pnpm atlassian raw --product confluence --method POST --path "/wiki/api/v2/pages" --body-file page.json --dry-run` + +## Safety Rules + +- Prefer JSON output for agent use. +- Use `--dry-run` before writes unless the user explicitly wants the change applied. +- Keep `--body-file` inputs within the current workspace. +- Use `raw` only for user-requested unsupported endpoints. +- `raw` does not allow `DELETE`. + +## Notes + +- Cursor discovers this skill from `.cursor/skills/` or `~/.cursor/skills/`. +- Atlassian Cloud is the supported platform in v1. diff --git a/skills/atlassian/_source/opencode/SKILL.md b/skills/atlassian/_source/opencode/SKILL.md new file mode 100644 index 0000000..5fb17ab --- /dev/null +++ b/skills/atlassian/_source/opencode/SKILL.md @@ -0,0 +1,78 @@ +--- +name: atlassian +description: Interact with Atlassian Cloud Jira and Confluence through a portable task-oriented CLI for search, issue/page edits, comments, transitions, and bounded raw requests. +--- + +# Atlassian (OpenCode) + +Portable Atlassian workflows for OpenCode using a shared TypeScript CLI. + +## Requirements + +- Node.js 20+ +- `pnpm` +- Atlassian Cloud account access +- `ATLASSIAN_BASE_URL` +- `ATLASSIAN_EMAIL` +- `ATLASSIAN_API_TOKEN` + +The `ATLASSIAN_*` values may come from the shell environment or a `.env` file in `~/.config/opencode/skills/atlassian/scripts`. + +## First-Time Setup + +```bash +mkdir -p ~/.config/opencode/skills/atlassian +cp -R skills/atlassian/opencode/* ~/.config/opencode/skills/atlassian/ +cd ~/.config/opencode/skills/atlassian/scripts +pnpm install +``` + +## Prerequisite Check (MANDATORY) + +```bash +cd ~/.config/opencode/skills/atlassian/scripts +node -e "require.resolve('commander');require.resolve('dotenv');console.log('OK: runtime dependencies installed')" +node -e 'require("dotenv").config({ path: ".env" }); const required = ["ATLASSIAN_BASE_URL", "ATLASSIAN_EMAIL", "ATLASSIAN_API_TOKEN"]; const missing = required.filter((key) => !(process.env[key] || "").trim()); if (missing.length) { console.error("Missing required Atlassian config: " + missing.join(", ")); process.exit(1); } console.log("OK: Atlassian config present")' +pnpm atlassian health +``` + +If any check fails, stop and return: + +`Missing dependency/config: atlassian requires installed CLI dependencies and valid Atlassian Cloud credentials. Configure ATLASSIAN_* in the shell environment or scripts/.env, then retry.` + +## Supported Commands + +- `pnpm atlassian health` +- `pnpm atlassian jira-search --jql "..."` +- `pnpm atlassian jira-get --issue ABC-123` +- `pnpm atlassian jira-create ... [--dry-run]` +- `pnpm atlassian jira-update ... [--dry-run]` +- `pnpm atlassian jira-comment ... [--dry-run]` +- `pnpm atlassian jira-transitions --issue ABC-123` +- `pnpm atlassian jira-transition ... [--dry-run]` +- `pnpm atlassian conf-search --query "..."` +- `pnpm atlassian conf-get --page 12345` +- `pnpm atlassian conf-create ... [--dry-run]` +- `pnpm atlassian conf-update ... [--dry-run]` +- `pnpm atlassian conf-comment ... [--dry-run]` +- `pnpm atlassian conf-children --page 12345` +- `pnpm atlassian raw --product jira|confluence --method GET|POST|PUT --path ...` + +## Usage Examples + +- `pnpm atlassian jira-transition --issue ENG-123 --transition 31 --dry-run` +- `pnpm atlassian conf-create --space OPS --title "Runbook" --body-file page.storage.html --dry-run` +- `pnpm atlassian raw --product jira --method GET --path "/rest/api/3/issue/ENG-123"` + +## Safety Rules + +- Prefer JSON output for machine consumption. +- Use `--dry-run` on writes unless the user explicitly asks to commit the remote mutation. +- Restrict `--body-file` to project files. +- Use `raw` only for unsupported edge cases. +- `DELETE` is intentionally unsupported in raw mode. + +## Notes + +- Atlassian Cloud is first-class in v1; Data Center support is future work. +- The CLI contract is shared across all agent variants so the same usage pattern works everywhere. diff --git a/skills/atlassian/_source/pi/SKILL.md b/skills/atlassian/_source/pi/SKILL.md new file mode 100644 index 0000000..10d3507 --- /dev/null +++ b/skills/atlassian/_source/pi/SKILL.md @@ -0,0 +1,99 @@ +--- +name: atlassian +description: Interact with Atlassian Cloud Jira and Confluence through a portable task-oriented CLI for search, issue/page edits, comments, transitions, and bounded raw requests. +--- + +# Atlassian (Pi) + +Portable Atlassian workflows for pi using the shared TypeScript CLI in `scripts/`. + +## Requirements + +- Node.js 20+ +- `pnpm` +- Atlassian Cloud account access +- `ATLASSIAN_BASE_URL` +- `ATLASSIAN_EMAIL` +- `ATLASSIAN_API_TOKEN` + +The `ATLASSIAN_*` values may come from the shell environment or a `.env` file in the installed skill's `scripts/` directory. + +## First-Time Setup + +Global install: + +```bash +mkdir -p ~/.pi/agent/skills/atlassian +cp -R skills/atlassian/pi/* ~/.pi/agent/skills/atlassian/ +cd ~/.pi/agent/skills/atlassian/scripts +pnpm install +``` + +Project-local install: + +```bash +mkdir -p .pi/skills/atlassian +cp -R skills/atlassian/pi/* .pi/skills/atlassian/ +cd .pi/skills/atlassian/scripts +pnpm install +``` + +Pi can also load this repo through settings or package installs as documented in [docs/PI.md](../../../docs/PI.md). + +If you installed this repo from a local checkout with `./scripts/install-pi-package.sh`, the runtime stays in the checkout mirror at `pi-package/skills/atlassian/scripts`. + +## Prerequisite Check (MANDATORY) + +Run inside the skill runtime directory that matches your install style: + +- local checkout package install: `pi-package/skills/atlassian/scripts` +- project-local copied install: `.pi/skills/atlassian/scripts` +- global copied install: `~/.pi/agent/skills/atlassian/scripts` + +```bash +cd pi-package/skills/atlassian/scripts +node -e "require.resolve('commander');require.resolve('dotenv');console.log('OK: runtime dependencies installed')" +node -e 'require("dotenv").config({ path: ".env" }); const required = ["ATLASSIAN_BASE_URL", "ATLASSIAN_EMAIL", "ATLASSIAN_API_TOKEN"]; const missing = required.filter((key) => !(process.env[key] || "").trim()); if (missing.length) { console.error("Missing required Atlassian config: " + missing.join(", ")); process.exit(1); } console.log("OK: Atlassian config present")' +pnpm atlassian health +``` + +If any check fails, stop and return: + +`Missing dependency/config: atlassian requires installed CLI dependencies and valid Atlassian Cloud credentials. Configure ATLASSIAN_* in the shell environment or scripts/.env, then retry.` + +## Supported Commands + +- `pnpm atlassian health` +- `pnpm atlassian jira-search --jql "..."` +- `pnpm atlassian jira-get --issue ABC-123` +- `pnpm atlassian jira-create ... [--dry-run]` +- `pnpm atlassian jira-update ... [--dry-run]` +- `pnpm atlassian jira-comment ... [--dry-run]` +- `pnpm atlassian jira-transitions --issue ABC-123` +- `pnpm atlassian jira-transition ... [--dry-run]` +- `pnpm atlassian conf-search --query "..."` +- `pnpm atlassian conf-get --page 12345` +- `pnpm atlassian conf-create ... [--dry-run]` +- `pnpm atlassian conf-update ... [--dry-run]` +- `pnpm atlassian conf-comment ... [--dry-run]` +- `pnpm atlassian conf-children --page 12345` +- `pnpm atlassian raw --product jira|confluence --method GET|POST|PUT --path ...` + +## Usage Examples + +- `pnpm atlassian jira-search --jql "project = ENG ORDER BY updated DESC" --max-results 10` +- `pnpm atlassian conf-comment --page 12345 --body-file comment.storage.html --dry-run` +- `pnpm atlassian raw --product jira --method GET --path "/rest/api/3/issue/ENG-123"` + +## Safety Rules + +- Default output is JSON; prefer that for agent workflows. +- Use `--dry-run` before any mutating command unless the user clearly wants the write to happen immediately. +- `raw` is for explicit edge cases only and does not allow `DELETE`. +- `--body-file` must stay inside the current workspace. +- Confluence write bodies should be storage-format inputs in v1. + +## Notes + +- Atlassian Cloud is the primary supported platform in v1. +- Package installs use the repo's `pi-package/skills/atlassian/` mirror so the installed skill directory name matches `atlassian`. diff --git a/skills/atlassian/claude-code/.generated-manifest.json b/skills/atlassian/claude-code/.generated-manifest.json new file mode 100644 index 0000000..1cfc506 --- /dev/null +++ b/skills/atlassian/claude-code/.generated-manifest.json @@ -0,0 +1,97 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/atlassian/claude-code", + "files": [ + { + "path": "scripts/package.json", + "kind": "file", + "mode": "644", + "sha256": "4030a6965cd5b29674beedd2b5b1bce08b9bd8a7304dfc22d68ca9c0cdd0f6a3" + }, + { + "path": "scripts/pnpm-lock.yaml", + "kind": "file", + "mode": "644", + "sha256": "15556a6f53e68bb8d92d2710aae0836bc80af7f29be9d63aa1b87fcbd33732c6" + }, + { + "path": "scripts/src/adf.ts", + "kind": "file", + "mode": "644", + "sha256": "c7c3b4a78ccd8fb5a8ab99c82e0eab67a0a0d656b3985c1f56817bda199ad20f" + }, + { + "path": "scripts/src/cli.ts", + "kind": "file", + "mode": "644", + "sha256": "5c4f4db76817fa9dbdae0fd0c75be302248d4b87fc0a53f6bd3c90407a75ae98" + }, + { + "path": "scripts/src/config.ts", + "kind": "file", + "mode": "644", + "sha256": "700dcdce96afab5294426e09f539135ae5432632370260190d6292071422eb3f" + }, + { + "path": "scripts/src/confluence.ts", + "kind": "file", + "mode": "644", + "sha256": "709d5d61fdb14e37aa4eaa7175eb7f17f0ec661376c96071020fbc9574ddbb73" + }, + { + "path": "scripts/src/files.ts", + "kind": "file", + "mode": "644", + "sha256": "16296eaa3ae41a4d7c694773036f9bb4bd2baa2db6a9c318078532b713678dba" + }, + { + "path": "scripts/src/health.ts", + "kind": "file", + "mode": "644", + "sha256": "1db4b49e05b16a095b7e7ca31cdc4e22ebda19e20e05c40baaaac648eaec0d08" + }, + { + "path": "scripts/src/http.ts", + "kind": "file", + "mode": "644", + "sha256": "66444b777d4d9b14d9793eb051c586eb811d2b36815b1018dd9d7517666c7eb2" + }, + { + "path": "scripts/src/jira.ts", + "kind": "file", + "mode": "644", + "sha256": "485d8d618fe04eb1ce546c1694eadf15d867bc83c2a6f7df994688ab0335ea4f" + }, + { + "path": "scripts/src/output.ts", + "kind": "file", + "mode": "644", + "sha256": "38e99818582a4962c09a83175634cba2bfead6acf33bd5f43cdca5caed7100a0" + }, + { + "path": "scripts/src/raw.ts", + "kind": "file", + "mode": "644", + "sha256": "2309c96dd45a03509df204803de9ecf0b5ff82fd488730f55ac5dd6a23b81dd8" + }, + { + "path": "scripts/src/types.ts", + "kind": "file", + "mode": "644", + "sha256": "9f92d27ab68604d5abfd0f5dc9552b96fed6d1f9fc7dc6eb30190d8b617628bf" + }, + { + "path": "scripts/tsconfig.json", + "kind": "file", + "mode": "644", + "sha256": "3c2eb7ba5c95a16cada153de4787ca7a4bf179609bf3848e12ff15b1b7927a68" + }, + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "96b660983b82a4060d5e3d91f916aa683f584a7b26f74c3145b3e23994030b71" + } + ] +} diff --git a/skills/atlassian/claude-code/SKILL.md b/skills/atlassian/claude-code/SKILL.md index c7708d8..aad2a68 100644 --- a/skills/atlassian/claude-code/SKILL.md +++ b/skills/atlassian/claude-code/SKILL.md @@ -3,6 +3,8 @@ name: atlassian description: Interact with Atlassian Cloud Jira and Confluence through a portable task-oriented CLI for search, issue/page edits, comments, transitions, and bounded raw requests. --- + + # Atlassian (Claude Code) Portable Atlassian workflows for Claude Code using a shared TypeScript CLI. diff --git a/skills/atlassian/claude-code/scripts/package.json b/skills/atlassian/claude-code/scripts/package.json index a9d8adc..97d4dba 100644 --- a/skills/atlassian/claude-code/scripts/package.json +++ b/skills/atlassian/claude-code/scripts/package.json @@ -1,5 +1,5 @@ { - "name": "atlassian-skill-scripts", + "name": "@ai-coding-skills/atlassian-claude-code", "version": "1.0.0", "description": "Shared runtime for the Atlassian skill", "type": "module", @@ -16,5 +16,6 @@ "tsx": "^4.20.5", "typescript": "^5.9.2" }, - "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34" + "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34", + "private": true } diff --git a/skills/atlassian/claude-code/scripts/src/adf.ts b/skills/atlassian/claude-code/scripts/src/adf.ts index 638914c..8774bd1 100644 --- a/skills/atlassian/claude-code/scripts/src/adf.ts +++ b/skills/atlassian/claude-code/scripts/src/adf.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. const TEXT_NODE = "text"; function textNode(text: string) { diff --git a/skills/atlassian/claude-code/scripts/src/cli.ts b/skills/atlassian/claude-code/scripts/src/cli.ts index 6012b99..68c1f87 100644 --- a/skills/atlassian/claude-code/scripts/src/cli.ts +++ b/skills/atlassian/claude-code/scripts/src/cli.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import process from "node:process"; import { pathToFileURL } from "node:url"; diff --git a/skills/atlassian/claude-code/scripts/src/config.ts b/skills/atlassian/claude-code/scripts/src/config.ts index eb34a39..58d37cc 100644 --- a/skills/atlassian/claude-code/scripts/src/config.ts +++ b/skills/atlassian/claude-code/scripts/src/config.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import path from "node:path"; import { config as loadDotEnv } from "dotenv"; diff --git a/skills/atlassian/claude-code/scripts/src/confluence.ts b/skills/atlassian/claude-code/scripts/src/confluence.ts index f22d66d..25e027d 100644 --- a/skills/atlassian/claude-code/scripts/src/confluence.ts +++ b/skills/atlassian/claude-code/scripts/src/confluence.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { sendJsonRequest } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js"; diff --git a/skills/atlassian/claude-code/scripts/src/files.ts b/skills/atlassian/claude-code/scripts/src/files.ts index 8339109..cd7ed39 100644 --- a/skills/atlassian/claude-code/scripts/src/files.ts +++ b/skills/atlassian/claude-code/scripts/src/files.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { readFile } from "node:fs/promises"; import path from "node:path"; diff --git a/skills/atlassian/claude-code/scripts/src/health.ts b/skills/atlassian/claude-code/scripts/src/health.ts index b2d4d24..5946887 100644 --- a/skills/atlassian/claude-code/scripts/src/health.ts +++ b/skills/atlassian/claude-code/scripts/src/health.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { createJsonHeaders, createStatusError } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js"; diff --git a/skills/atlassian/claude-code/scripts/src/http.ts b/skills/atlassian/claude-code/scripts/src/http.ts index 5791886..1184cd1 100644 --- a/skills/atlassian/claude-code/scripts/src/http.ts +++ b/skills/atlassian/claude-code/scripts/src/http.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { createBasicAuthHeader } from "./config.js"; import type { AtlassianConfig, FetchLike } from "./types.js"; diff --git a/skills/atlassian/claude-code/scripts/src/jira.ts b/skills/atlassian/claude-code/scripts/src/jira.ts index 5cf3a6e..af19e03 100644 --- a/skills/atlassian/claude-code/scripts/src/jira.ts +++ b/skills/atlassian/claude-code/scripts/src/jira.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { markdownToAdf } from "./adf.js"; import { sendJsonRequest } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike, JiraIssueSummary } from "./types.js"; diff --git a/skills/atlassian/claude-code/scripts/src/output.ts b/skills/atlassian/claude-code/scripts/src/output.ts index 06b8a6e..afb43d9 100644 --- a/skills/atlassian/claude-code/scripts/src/output.ts +++ b/skills/atlassian/claude-code/scripts/src/output.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import type { CommandOutput, OutputFormat, Writer } from "./types.js"; function renderText(payload: CommandOutput) { diff --git a/skills/atlassian/claude-code/scripts/src/raw.ts b/skills/atlassian/claude-code/scripts/src/raw.ts index 8e11793..620a259 100644 --- a/skills/atlassian/claude-code/scripts/src/raw.ts +++ b/skills/atlassian/claude-code/scripts/src/raw.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { readWorkspaceFile } from "./files.js"; import { sendJsonRequest } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js"; diff --git a/skills/atlassian/claude-code/scripts/src/types.ts b/skills/atlassian/claude-code/scripts/src/types.ts index 7f48f56..c365ff5 100644 --- a/skills/atlassian/claude-code/scripts/src/types.ts +++ b/skills/atlassian/claude-code/scripts/src/types.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. export type AtlassianConfig = { baseUrl: string; jiraBaseUrl: string; diff --git a/skills/atlassian/codex/.generated-manifest.json b/skills/atlassian/codex/.generated-manifest.json new file mode 100644 index 0000000..d09eaa9 --- /dev/null +++ b/skills/atlassian/codex/.generated-manifest.json @@ -0,0 +1,97 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/atlassian/codex", + "files": [ + { + "path": "scripts/package.json", + "kind": "file", + "mode": "644", + "sha256": "2462c2f552c99460071fc60231c5d2d9eca5283bd045e6c3f845e60f351e7e73" + }, + { + "path": "scripts/pnpm-lock.yaml", + "kind": "file", + "mode": "644", + "sha256": "15556a6f53e68bb8d92d2710aae0836bc80af7f29be9d63aa1b87fcbd33732c6" + }, + { + "path": "scripts/src/adf.ts", + "kind": "file", + "mode": "644", + "sha256": "c7c3b4a78ccd8fb5a8ab99c82e0eab67a0a0d656b3985c1f56817bda199ad20f" + }, + { + "path": "scripts/src/cli.ts", + "kind": "file", + "mode": "644", + "sha256": "5c4f4db76817fa9dbdae0fd0c75be302248d4b87fc0a53f6bd3c90407a75ae98" + }, + { + "path": "scripts/src/config.ts", + "kind": "file", + "mode": "644", + "sha256": "700dcdce96afab5294426e09f539135ae5432632370260190d6292071422eb3f" + }, + { + "path": "scripts/src/confluence.ts", + "kind": "file", + "mode": "644", + "sha256": "709d5d61fdb14e37aa4eaa7175eb7f17f0ec661376c96071020fbc9574ddbb73" + }, + { + "path": "scripts/src/files.ts", + "kind": "file", + "mode": "644", + "sha256": "16296eaa3ae41a4d7c694773036f9bb4bd2baa2db6a9c318078532b713678dba" + }, + { + "path": "scripts/src/health.ts", + "kind": "file", + "mode": "644", + "sha256": "1db4b49e05b16a095b7e7ca31cdc4e22ebda19e20e05c40baaaac648eaec0d08" + }, + { + "path": "scripts/src/http.ts", + "kind": "file", + "mode": "644", + "sha256": "66444b777d4d9b14d9793eb051c586eb811d2b36815b1018dd9d7517666c7eb2" + }, + { + "path": "scripts/src/jira.ts", + "kind": "file", + "mode": "644", + "sha256": "485d8d618fe04eb1ce546c1694eadf15d867bc83c2a6f7df994688ab0335ea4f" + }, + { + "path": "scripts/src/output.ts", + "kind": "file", + "mode": "644", + "sha256": "38e99818582a4962c09a83175634cba2bfead6acf33bd5f43cdca5caed7100a0" + }, + { + "path": "scripts/src/raw.ts", + "kind": "file", + "mode": "644", + "sha256": "2309c96dd45a03509df204803de9ecf0b5ff82fd488730f55ac5dd6a23b81dd8" + }, + { + "path": "scripts/src/types.ts", + "kind": "file", + "mode": "644", + "sha256": "9f92d27ab68604d5abfd0f5dc9552b96fed6d1f9fc7dc6eb30190d8b617628bf" + }, + { + "path": "scripts/tsconfig.json", + "kind": "file", + "mode": "644", + "sha256": "3c2eb7ba5c95a16cada153de4787ca7a4bf179609bf3848e12ff15b1b7927a68" + }, + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "e951cc63a6a3abf6b050b15f7316ea3675dd6aa3bc54677faa76ef5e6f1f7c16" + } + ] +} diff --git a/skills/atlassian/codex/SKILL.md b/skills/atlassian/codex/SKILL.md index e955654..fdb8193 100644 --- a/skills/atlassian/codex/SKILL.md +++ b/skills/atlassian/codex/SKILL.md @@ -3,6 +3,8 @@ name: atlassian description: Interact with Atlassian Cloud Jira and Confluence through a portable task-oriented CLI for search, issue/page edits, comments, transitions, and bounded raw requests. --- + + # Atlassian (Codex) Portable Atlassian workflows for Codex using a shared TypeScript CLI. diff --git a/skills/atlassian/codex/scripts/package.json b/skills/atlassian/codex/scripts/package.json index a9d8adc..603c142 100644 --- a/skills/atlassian/codex/scripts/package.json +++ b/skills/atlassian/codex/scripts/package.json @@ -1,5 +1,5 @@ { - "name": "atlassian-skill-scripts", + "name": "@ai-coding-skills/atlassian-codex", "version": "1.0.0", "description": "Shared runtime for the Atlassian skill", "type": "module", @@ -16,5 +16,6 @@ "tsx": "^4.20.5", "typescript": "^5.9.2" }, - "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34" + "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34", + "private": true } diff --git a/skills/atlassian/codex/scripts/src/adf.ts b/skills/atlassian/codex/scripts/src/adf.ts index 638914c..8774bd1 100644 --- a/skills/atlassian/codex/scripts/src/adf.ts +++ b/skills/atlassian/codex/scripts/src/adf.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. const TEXT_NODE = "text"; function textNode(text: string) { diff --git a/skills/atlassian/codex/scripts/src/cli.ts b/skills/atlassian/codex/scripts/src/cli.ts index 6012b99..68c1f87 100644 --- a/skills/atlassian/codex/scripts/src/cli.ts +++ b/skills/atlassian/codex/scripts/src/cli.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import process from "node:process"; import { pathToFileURL } from "node:url"; diff --git a/skills/atlassian/codex/scripts/src/config.ts b/skills/atlassian/codex/scripts/src/config.ts index eb34a39..58d37cc 100644 --- a/skills/atlassian/codex/scripts/src/config.ts +++ b/skills/atlassian/codex/scripts/src/config.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import path from "node:path"; import { config as loadDotEnv } from "dotenv"; diff --git a/skills/atlassian/codex/scripts/src/confluence.ts b/skills/atlassian/codex/scripts/src/confluence.ts index f22d66d..25e027d 100644 --- a/skills/atlassian/codex/scripts/src/confluence.ts +++ b/skills/atlassian/codex/scripts/src/confluence.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { sendJsonRequest } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js"; diff --git a/skills/atlassian/codex/scripts/src/files.ts b/skills/atlassian/codex/scripts/src/files.ts index 8339109..cd7ed39 100644 --- a/skills/atlassian/codex/scripts/src/files.ts +++ b/skills/atlassian/codex/scripts/src/files.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { readFile } from "node:fs/promises"; import path from "node:path"; diff --git a/skills/atlassian/codex/scripts/src/health.ts b/skills/atlassian/codex/scripts/src/health.ts index b2d4d24..5946887 100644 --- a/skills/atlassian/codex/scripts/src/health.ts +++ b/skills/atlassian/codex/scripts/src/health.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { createJsonHeaders, createStatusError } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js"; diff --git a/skills/atlassian/codex/scripts/src/http.ts b/skills/atlassian/codex/scripts/src/http.ts index 5791886..1184cd1 100644 --- a/skills/atlassian/codex/scripts/src/http.ts +++ b/skills/atlassian/codex/scripts/src/http.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { createBasicAuthHeader } from "./config.js"; import type { AtlassianConfig, FetchLike } from "./types.js"; diff --git a/skills/atlassian/codex/scripts/src/jira.ts b/skills/atlassian/codex/scripts/src/jira.ts index 5cf3a6e..af19e03 100644 --- a/skills/atlassian/codex/scripts/src/jira.ts +++ b/skills/atlassian/codex/scripts/src/jira.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { markdownToAdf } from "./adf.js"; import { sendJsonRequest } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike, JiraIssueSummary } from "./types.js"; diff --git a/skills/atlassian/codex/scripts/src/output.ts b/skills/atlassian/codex/scripts/src/output.ts index 06b8a6e..afb43d9 100644 --- a/skills/atlassian/codex/scripts/src/output.ts +++ b/skills/atlassian/codex/scripts/src/output.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import type { CommandOutput, OutputFormat, Writer } from "./types.js"; function renderText(payload: CommandOutput) { diff --git a/skills/atlassian/codex/scripts/src/raw.ts b/skills/atlassian/codex/scripts/src/raw.ts index 8e11793..620a259 100644 --- a/skills/atlassian/codex/scripts/src/raw.ts +++ b/skills/atlassian/codex/scripts/src/raw.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { readWorkspaceFile } from "./files.js"; import { sendJsonRequest } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js"; diff --git a/skills/atlassian/codex/scripts/src/types.ts b/skills/atlassian/codex/scripts/src/types.ts index 7f48f56..c365ff5 100644 --- a/skills/atlassian/codex/scripts/src/types.ts +++ b/skills/atlassian/codex/scripts/src/types.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. export type AtlassianConfig = { baseUrl: string; jiraBaseUrl: string; diff --git a/skills/atlassian/cursor/.generated-manifest.json b/skills/atlassian/cursor/.generated-manifest.json new file mode 100644 index 0000000..7aa3f67 --- /dev/null +++ b/skills/atlassian/cursor/.generated-manifest.json @@ -0,0 +1,97 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/atlassian/cursor", + "files": [ + { + "path": "scripts/package.json", + "kind": "file", + "mode": "644", + "sha256": "a48e86c0cd54ac3c9972b1d2818ee62a086ec34f1f7e88adcfc66eccc0adf04c" + }, + { + "path": "scripts/pnpm-lock.yaml", + "kind": "file", + "mode": "644", + "sha256": "15556a6f53e68bb8d92d2710aae0836bc80af7f29be9d63aa1b87fcbd33732c6" + }, + { + "path": "scripts/src/adf.ts", + "kind": "file", + "mode": "644", + "sha256": "c7c3b4a78ccd8fb5a8ab99c82e0eab67a0a0d656b3985c1f56817bda199ad20f" + }, + { + "path": "scripts/src/cli.ts", + "kind": "file", + "mode": "644", + "sha256": "5c4f4db76817fa9dbdae0fd0c75be302248d4b87fc0a53f6bd3c90407a75ae98" + }, + { + "path": "scripts/src/config.ts", + "kind": "file", + "mode": "644", + "sha256": "700dcdce96afab5294426e09f539135ae5432632370260190d6292071422eb3f" + }, + { + "path": "scripts/src/confluence.ts", + "kind": "file", + "mode": "644", + "sha256": "709d5d61fdb14e37aa4eaa7175eb7f17f0ec661376c96071020fbc9574ddbb73" + }, + { + "path": "scripts/src/files.ts", + "kind": "file", + "mode": "644", + "sha256": "16296eaa3ae41a4d7c694773036f9bb4bd2baa2db6a9c318078532b713678dba" + }, + { + "path": "scripts/src/health.ts", + "kind": "file", + "mode": "644", + "sha256": "1db4b49e05b16a095b7e7ca31cdc4e22ebda19e20e05c40baaaac648eaec0d08" + }, + { + "path": "scripts/src/http.ts", + "kind": "file", + "mode": "644", + "sha256": "66444b777d4d9b14d9793eb051c586eb811d2b36815b1018dd9d7517666c7eb2" + }, + { + "path": "scripts/src/jira.ts", + "kind": "file", + "mode": "644", + "sha256": "485d8d618fe04eb1ce546c1694eadf15d867bc83c2a6f7df994688ab0335ea4f" + }, + { + "path": "scripts/src/output.ts", + "kind": "file", + "mode": "644", + "sha256": "38e99818582a4962c09a83175634cba2bfead6acf33bd5f43cdca5caed7100a0" + }, + { + "path": "scripts/src/raw.ts", + "kind": "file", + "mode": "644", + "sha256": "2309c96dd45a03509df204803de9ecf0b5ff82fd488730f55ac5dd6a23b81dd8" + }, + { + "path": "scripts/src/types.ts", + "kind": "file", + "mode": "644", + "sha256": "9f92d27ab68604d5abfd0f5dc9552b96fed6d1f9fc7dc6eb30190d8b617628bf" + }, + { + "path": "scripts/tsconfig.json", + "kind": "file", + "mode": "644", + "sha256": "3c2eb7ba5c95a16cada153de4787ca7a4bf179609bf3848e12ff15b1b7927a68" + }, + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "39cb974a328958272e0fefa21969b41fca010326dbff121be2062af3138f9a9b" + } + ] +} diff --git a/skills/atlassian/cursor/SKILL.md b/skills/atlassian/cursor/SKILL.md index 91cfb1c..b9e9994 100644 --- a/skills/atlassian/cursor/SKILL.md +++ b/skills/atlassian/cursor/SKILL.md @@ -3,6 +3,8 @@ name: atlassian description: Interact with Atlassian Cloud Jira and Confluence through a portable task-oriented CLI for search, issue/page edits, comments, transitions, and bounded raw requests. --- + + # Atlassian (Cursor Agent CLI) Portable Atlassian workflows for Cursor Agent CLI using a shared TypeScript CLI. diff --git a/skills/atlassian/cursor/scripts/package.json b/skills/atlassian/cursor/scripts/package.json index a9d8adc..d6aa0c8 100644 --- a/skills/atlassian/cursor/scripts/package.json +++ b/skills/atlassian/cursor/scripts/package.json @@ -1,5 +1,5 @@ { - "name": "atlassian-skill-scripts", + "name": "@ai-coding-skills/atlassian-cursor", "version": "1.0.0", "description": "Shared runtime for the Atlassian skill", "type": "module", @@ -16,5 +16,6 @@ "tsx": "^4.20.5", "typescript": "^5.9.2" }, - "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34" + "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34", + "private": true } diff --git a/skills/atlassian/cursor/scripts/src/adf.ts b/skills/atlassian/cursor/scripts/src/adf.ts index 638914c..8774bd1 100644 --- a/skills/atlassian/cursor/scripts/src/adf.ts +++ b/skills/atlassian/cursor/scripts/src/adf.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. const TEXT_NODE = "text"; function textNode(text: string) { diff --git a/skills/atlassian/cursor/scripts/src/cli.ts b/skills/atlassian/cursor/scripts/src/cli.ts index 6012b99..68c1f87 100644 --- a/skills/atlassian/cursor/scripts/src/cli.ts +++ b/skills/atlassian/cursor/scripts/src/cli.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import process from "node:process"; import { pathToFileURL } from "node:url"; diff --git a/skills/atlassian/cursor/scripts/src/config.ts b/skills/atlassian/cursor/scripts/src/config.ts index eb34a39..58d37cc 100644 --- a/skills/atlassian/cursor/scripts/src/config.ts +++ b/skills/atlassian/cursor/scripts/src/config.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import path from "node:path"; import { config as loadDotEnv } from "dotenv"; diff --git a/skills/atlassian/cursor/scripts/src/confluence.ts b/skills/atlassian/cursor/scripts/src/confluence.ts index f22d66d..25e027d 100644 --- a/skills/atlassian/cursor/scripts/src/confluence.ts +++ b/skills/atlassian/cursor/scripts/src/confluence.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { sendJsonRequest } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js"; diff --git a/skills/atlassian/cursor/scripts/src/files.ts b/skills/atlassian/cursor/scripts/src/files.ts index 8339109..cd7ed39 100644 --- a/skills/atlassian/cursor/scripts/src/files.ts +++ b/skills/atlassian/cursor/scripts/src/files.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { readFile } from "node:fs/promises"; import path from "node:path"; diff --git a/skills/atlassian/cursor/scripts/src/health.ts b/skills/atlassian/cursor/scripts/src/health.ts index b2d4d24..5946887 100644 --- a/skills/atlassian/cursor/scripts/src/health.ts +++ b/skills/atlassian/cursor/scripts/src/health.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { createJsonHeaders, createStatusError } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js"; diff --git a/skills/atlassian/cursor/scripts/src/http.ts b/skills/atlassian/cursor/scripts/src/http.ts index 5791886..1184cd1 100644 --- a/skills/atlassian/cursor/scripts/src/http.ts +++ b/skills/atlassian/cursor/scripts/src/http.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { createBasicAuthHeader } from "./config.js"; import type { AtlassianConfig, FetchLike } from "./types.js"; diff --git a/skills/atlassian/cursor/scripts/src/jira.ts b/skills/atlassian/cursor/scripts/src/jira.ts index 5cf3a6e..af19e03 100644 --- a/skills/atlassian/cursor/scripts/src/jira.ts +++ b/skills/atlassian/cursor/scripts/src/jira.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { markdownToAdf } from "./adf.js"; import { sendJsonRequest } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike, JiraIssueSummary } from "./types.js"; diff --git a/skills/atlassian/cursor/scripts/src/output.ts b/skills/atlassian/cursor/scripts/src/output.ts index 06b8a6e..afb43d9 100644 --- a/skills/atlassian/cursor/scripts/src/output.ts +++ b/skills/atlassian/cursor/scripts/src/output.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import type { CommandOutput, OutputFormat, Writer } from "./types.js"; function renderText(payload: CommandOutput) { diff --git a/skills/atlassian/cursor/scripts/src/raw.ts b/skills/atlassian/cursor/scripts/src/raw.ts index 8e11793..620a259 100644 --- a/skills/atlassian/cursor/scripts/src/raw.ts +++ b/skills/atlassian/cursor/scripts/src/raw.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { readWorkspaceFile } from "./files.js"; import { sendJsonRequest } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js"; diff --git a/skills/atlassian/cursor/scripts/src/types.ts b/skills/atlassian/cursor/scripts/src/types.ts index 7f48f56..c365ff5 100644 --- a/skills/atlassian/cursor/scripts/src/types.ts +++ b/skills/atlassian/cursor/scripts/src/types.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. export type AtlassianConfig = { baseUrl: string; jiraBaseUrl: string; diff --git a/skills/atlassian/opencode/.generated-manifest.json b/skills/atlassian/opencode/.generated-manifest.json new file mode 100644 index 0000000..3888a55 --- /dev/null +++ b/skills/atlassian/opencode/.generated-manifest.json @@ -0,0 +1,97 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/atlassian/opencode", + "files": [ + { + "path": "scripts/package.json", + "kind": "file", + "mode": "644", + "sha256": "780e807a8e4c611103273ce93ed029fdad62d87bc999bd022f8cebeefd61619b" + }, + { + "path": "scripts/pnpm-lock.yaml", + "kind": "file", + "mode": "644", + "sha256": "15556a6f53e68bb8d92d2710aae0836bc80af7f29be9d63aa1b87fcbd33732c6" + }, + { + "path": "scripts/src/adf.ts", + "kind": "file", + "mode": "644", + "sha256": "c7c3b4a78ccd8fb5a8ab99c82e0eab67a0a0d656b3985c1f56817bda199ad20f" + }, + { + "path": "scripts/src/cli.ts", + "kind": "file", + "mode": "644", + "sha256": "5c4f4db76817fa9dbdae0fd0c75be302248d4b87fc0a53f6bd3c90407a75ae98" + }, + { + "path": "scripts/src/config.ts", + "kind": "file", + "mode": "644", + "sha256": "700dcdce96afab5294426e09f539135ae5432632370260190d6292071422eb3f" + }, + { + "path": "scripts/src/confluence.ts", + "kind": "file", + "mode": "644", + "sha256": "709d5d61fdb14e37aa4eaa7175eb7f17f0ec661376c96071020fbc9574ddbb73" + }, + { + "path": "scripts/src/files.ts", + "kind": "file", + "mode": "644", + "sha256": "16296eaa3ae41a4d7c694773036f9bb4bd2baa2db6a9c318078532b713678dba" + }, + { + "path": "scripts/src/health.ts", + "kind": "file", + "mode": "644", + "sha256": "1db4b49e05b16a095b7e7ca31cdc4e22ebda19e20e05c40baaaac648eaec0d08" + }, + { + "path": "scripts/src/http.ts", + "kind": "file", + "mode": "644", + "sha256": "66444b777d4d9b14d9793eb051c586eb811d2b36815b1018dd9d7517666c7eb2" + }, + { + "path": "scripts/src/jira.ts", + "kind": "file", + "mode": "644", + "sha256": "485d8d618fe04eb1ce546c1694eadf15d867bc83c2a6f7df994688ab0335ea4f" + }, + { + "path": "scripts/src/output.ts", + "kind": "file", + "mode": "644", + "sha256": "38e99818582a4962c09a83175634cba2bfead6acf33bd5f43cdca5caed7100a0" + }, + { + "path": "scripts/src/raw.ts", + "kind": "file", + "mode": "644", + "sha256": "2309c96dd45a03509df204803de9ecf0b5ff82fd488730f55ac5dd6a23b81dd8" + }, + { + "path": "scripts/src/types.ts", + "kind": "file", + "mode": "644", + "sha256": "9f92d27ab68604d5abfd0f5dc9552b96fed6d1f9fc7dc6eb30190d8b617628bf" + }, + { + "path": "scripts/tsconfig.json", + "kind": "file", + "mode": "644", + "sha256": "3c2eb7ba5c95a16cada153de4787ca7a4bf179609bf3848e12ff15b1b7927a68" + }, + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "b7137d7c3ad97161cdb4603eebff6165a50baa247b61f3b8462744faa8d206d3" + } + ] +} diff --git a/skills/atlassian/opencode/SKILL.md b/skills/atlassian/opencode/SKILL.md index 5fb17ab..c5b6fda 100644 --- a/skills/atlassian/opencode/SKILL.md +++ b/skills/atlassian/opencode/SKILL.md @@ -3,6 +3,8 @@ name: atlassian description: Interact with Atlassian Cloud Jira and Confluence through a portable task-oriented CLI for search, issue/page edits, comments, transitions, and bounded raw requests. --- + + # Atlassian (OpenCode) Portable Atlassian workflows for OpenCode using a shared TypeScript CLI. diff --git a/skills/atlassian/opencode/scripts/package.json b/skills/atlassian/opencode/scripts/package.json index a9d8adc..0728989 100644 --- a/skills/atlassian/opencode/scripts/package.json +++ b/skills/atlassian/opencode/scripts/package.json @@ -1,5 +1,5 @@ { - "name": "atlassian-skill-scripts", + "name": "@ai-coding-skills/atlassian-opencode", "version": "1.0.0", "description": "Shared runtime for the Atlassian skill", "type": "module", @@ -16,5 +16,6 @@ "tsx": "^4.20.5", "typescript": "^5.9.2" }, - "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34" + "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34", + "private": true } diff --git a/skills/atlassian/opencode/scripts/src/adf.ts b/skills/atlassian/opencode/scripts/src/adf.ts index 638914c..8774bd1 100644 --- a/skills/atlassian/opencode/scripts/src/adf.ts +++ b/skills/atlassian/opencode/scripts/src/adf.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. const TEXT_NODE = "text"; function textNode(text: string) { diff --git a/skills/atlassian/opencode/scripts/src/cli.ts b/skills/atlassian/opencode/scripts/src/cli.ts index 6012b99..68c1f87 100644 --- a/skills/atlassian/opencode/scripts/src/cli.ts +++ b/skills/atlassian/opencode/scripts/src/cli.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import process from "node:process"; import { pathToFileURL } from "node:url"; diff --git a/skills/atlassian/opencode/scripts/src/config.ts b/skills/atlassian/opencode/scripts/src/config.ts index eb34a39..58d37cc 100644 --- a/skills/atlassian/opencode/scripts/src/config.ts +++ b/skills/atlassian/opencode/scripts/src/config.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import path from "node:path"; import { config as loadDotEnv } from "dotenv"; diff --git a/skills/atlassian/opencode/scripts/src/confluence.ts b/skills/atlassian/opencode/scripts/src/confluence.ts index f22d66d..25e027d 100644 --- a/skills/atlassian/opencode/scripts/src/confluence.ts +++ b/skills/atlassian/opencode/scripts/src/confluence.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { sendJsonRequest } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js"; diff --git a/skills/atlassian/opencode/scripts/src/files.ts b/skills/atlassian/opencode/scripts/src/files.ts index 8339109..cd7ed39 100644 --- a/skills/atlassian/opencode/scripts/src/files.ts +++ b/skills/atlassian/opencode/scripts/src/files.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { readFile } from "node:fs/promises"; import path from "node:path"; diff --git a/skills/atlassian/opencode/scripts/src/health.ts b/skills/atlassian/opencode/scripts/src/health.ts index b2d4d24..5946887 100644 --- a/skills/atlassian/opencode/scripts/src/health.ts +++ b/skills/atlassian/opencode/scripts/src/health.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { createJsonHeaders, createStatusError } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js"; diff --git a/skills/atlassian/opencode/scripts/src/http.ts b/skills/atlassian/opencode/scripts/src/http.ts index 5791886..1184cd1 100644 --- a/skills/atlassian/opencode/scripts/src/http.ts +++ b/skills/atlassian/opencode/scripts/src/http.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { createBasicAuthHeader } from "./config.js"; import type { AtlassianConfig, FetchLike } from "./types.js"; diff --git a/skills/atlassian/opencode/scripts/src/jira.ts b/skills/atlassian/opencode/scripts/src/jira.ts index 5cf3a6e..af19e03 100644 --- a/skills/atlassian/opencode/scripts/src/jira.ts +++ b/skills/atlassian/opencode/scripts/src/jira.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { markdownToAdf } from "./adf.js"; import { sendJsonRequest } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike, JiraIssueSummary } from "./types.js"; diff --git a/skills/atlassian/opencode/scripts/src/output.ts b/skills/atlassian/opencode/scripts/src/output.ts index 06b8a6e..afb43d9 100644 --- a/skills/atlassian/opencode/scripts/src/output.ts +++ b/skills/atlassian/opencode/scripts/src/output.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import type { CommandOutput, OutputFormat, Writer } from "./types.js"; function renderText(payload: CommandOutput) { diff --git a/skills/atlassian/opencode/scripts/src/raw.ts b/skills/atlassian/opencode/scripts/src/raw.ts index 8e11793..620a259 100644 --- a/skills/atlassian/opencode/scripts/src/raw.ts +++ b/skills/atlassian/opencode/scripts/src/raw.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { readWorkspaceFile } from "./files.js"; import { sendJsonRequest } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js"; diff --git a/skills/atlassian/opencode/scripts/src/types.ts b/skills/atlassian/opencode/scripts/src/types.ts index 7f48f56..c365ff5 100644 --- a/skills/atlassian/opencode/scripts/src/types.ts +++ b/skills/atlassian/opencode/scripts/src/types.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. export type AtlassianConfig = { baseUrl: string; jiraBaseUrl: string; diff --git a/skills/atlassian/pi/.generated-manifest.json b/skills/atlassian/pi/.generated-manifest.json new file mode 100644 index 0000000..4fdd605 --- /dev/null +++ b/skills/atlassian/pi/.generated-manifest.json @@ -0,0 +1,97 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/atlassian/pi", + "files": [ + { + "path": "scripts/package.json", + "kind": "file", + "mode": "644", + "sha256": "454e09224d66eb976fa27a06e9998497f628baff1bc7a8f44d70e6eb107d7106" + }, + { + "path": "scripts/pnpm-lock.yaml", + "kind": "file", + "mode": "644", + "sha256": "15556a6f53e68bb8d92d2710aae0836bc80af7f29be9d63aa1b87fcbd33732c6" + }, + { + "path": "scripts/src/adf.ts", + "kind": "file", + "mode": "644", + "sha256": "c7c3b4a78ccd8fb5a8ab99c82e0eab67a0a0d656b3985c1f56817bda199ad20f" + }, + { + "path": "scripts/src/cli.ts", + "kind": "file", + "mode": "644", + "sha256": "5c4f4db76817fa9dbdae0fd0c75be302248d4b87fc0a53f6bd3c90407a75ae98" + }, + { + "path": "scripts/src/config.ts", + "kind": "file", + "mode": "644", + "sha256": "700dcdce96afab5294426e09f539135ae5432632370260190d6292071422eb3f" + }, + { + "path": "scripts/src/confluence.ts", + "kind": "file", + "mode": "644", + "sha256": "709d5d61fdb14e37aa4eaa7175eb7f17f0ec661376c96071020fbc9574ddbb73" + }, + { + "path": "scripts/src/files.ts", + "kind": "file", + "mode": "644", + "sha256": "16296eaa3ae41a4d7c694773036f9bb4bd2baa2db6a9c318078532b713678dba" + }, + { + "path": "scripts/src/health.ts", + "kind": "file", + "mode": "644", + "sha256": "1db4b49e05b16a095b7e7ca31cdc4e22ebda19e20e05c40baaaac648eaec0d08" + }, + { + "path": "scripts/src/http.ts", + "kind": "file", + "mode": "644", + "sha256": "66444b777d4d9b14d9793eb051c586eb811d2b36815b1018dd9d7517666c7eb2" + }, + { + "path": "scripts/src/jira.ts", + "kind": "file", + "mode": "644", + "sha256": "485d8d618fe04eb1ce546c1694eadf15d867bc83c2a6f7df994688ab0335ea4f" + }, + { + "path": "scripts/src/output.ts", + "kind": "file", + "mode": "644", + "sha256": "38e99818582a4962c09a83175634cba2bfead6acf33bd5f43cdca5caed7100a0" + }, + { + "path": "scripts/src/raw.ts", + "kind": "file", + "mode": "644", + "sha256": "2309c96dd45a03509df204803de9ecf0b5ff82fd488730f55ac5dd6a23b81dd8" + }, + { + "path": "scripts/src/types.ts", + "kind": "file", + "mode": "644", + "sha256": "9f92d27ab68604d5abfd0f5dc9552b96fed6d1f9fc7dc6eb30190d8b617628bf" + }, + { + "path": "scripts/tsconfig.json", + "kind": "file", + "mode": "644", + "sha256": "3c2eb7ba5c95a16cada153de4787ca7a4bf179609bf3848e12ff15b1b7927a68" + }, + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "69d83441799f3feada7fbf85691bda16fc30718b724871d7e37cfac574db2253" + } + ] +} diff --git a/skills/atlassian/pi/SKILL.md b/skills/atlassian/pi/SKILL.md index 10d3507..406e702 100644 --- a/skills/atlassian/pi/SKILL.md +++ b/skills/atlassian/pi/SKILL.md @@ -3,6 +3,8 @@ name: atlassian description: Interact with Atlassian Cloud Jira and Confluence through a portable task-oriented CLI for search, issue/page edits, comments, transitions, and bounded raw requests. --- + + # Atlassian (Pi) Portable Atlassian workflows for pi using the shared TypeScript CLI in `scripts/`. diff --git a/skills/atlassian/pi/scripts/package.json b/skills/atlassian/pi/scripts/package.json index a9d8adc..f1b233b 100644 --- a/skills/atlassian/pi/scripts/package.json +++ b/skills/atlassian/pi/scripts/package.json @@ -1,5 +1,5 @@ { - "name": "atlassian-skill-scripts", + "name": "@ai-coding-skills/atlassian-pi", "version": "1.0.0", "description": "Shared runtime for the Atlassian skill", "type": "module", @@ -16,5 +16,6 @@ "tsx": "^4.20.5", "typescript": "^5.9.2" }, - "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34" + "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34", + "private": true } diff --git a/skills/atlassian/pi/scripts/src/adf.ts b/skills/atlassian/pi/scripts/src/adf.ts index 638914c..8774bd1 100644 --- a/skills/atlassian/pi/scripts/src/adf.ts +++ b/skills/atlassian/pi/scripts/src/adf.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. const TEXT_NODE = "text"; function textNode(text: string) { diff --git a/skills/atlassian/pi/scripts/src/cli.ts b/skills/atlassian/pi/scripts/src/cli.ts index 6012b99..68c1f87 100644 --- a/skills/atlassian/pi/scripts/src/cli.ts +++ b/skills/atlassian/pi/scripts/src/cli.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import process from "node:process"; import { pathToFileURL } from "node:url"; diff --git a/skills/atlassian/pi/scripts/src/config.ts b/skills/atlassian/pi/scripts/src/config.ts index eb34a39..58d37cc 100644 --- a/skills/atlassian/pi/scripts/src/config.ts +++ b/skills/atlassian/pi/scripts/src/config.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import path from "node:path"; import { config as loadDotEnv } from "dotenv"; diff --git a/skills/atlassian/pi/scripts/src/confluence.ts b/skills/atlassian/pi/scripts/src/confluence.ts index f22d66d..25e027d 100644 --- a/skills/atlassian/pi/scripts/src/confluence.ts +++ b/skills/atlassian/pi/scripts/src/confluence.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { sendJsonRequest } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js"; diff --git a/skills/atlassian/pi/scripts/src/files.ts b/skills/atlassian/pi/scripts/src/files.ts index 8339109..cd7ed39 100644 --- a/skills/atlassian/pi/scripts/src/files.ts +++ b/skills/atlassian/pi/scripts/src/files.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { readFile } from "node:fs/promises"; import path from "node:path"; diff --git a/skills/atlassian/pi/scripts/src/health.ts b/skills/atlassian/pi/scripts/src/health.ts index b2d4d24..5946887 100644 --- a/skills/atlassian/pi/scripts/src/health.ts +++ b/skills/atlassian/pi/scripts/src/health.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { createJsonHeaders, createStatusError } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js"; diff --git a/skills/atlassian/pi/scripts/src/http.ts b/skills/atlassian/pi/scripts/src/http.ts index 5791886..1184cd1 100644 --- a/skills/atlassian/pi/scripts/src/http.ts +++ b/skills/atlassian/pi/scripts/src/http.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { createBasicAuthHeader } from "./config.js"; import type { AtlassianConfig, FetchLike } from "./types.js"; diff --git a/skills/atlassian/pi/scripts/src/jira.ts b/skills/atlassian/pi/scripts/src/jira.ts index 5cf3a6e..af19e03 100644 --- a/skills/atlassian/pi/scripts/src/jira.ts +++ b/skills/atlassian/pi/scripts/src/jira.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { markdownToAdf } from "./adf.js"; import { sendJsonRequest } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike, JiraIssueSummary } from "./types.js"; diff --git a/skills/atlassian/pi/scripts/src/output.ts b/skills/atlassian/pi/scripts/src/output.ts index 06b8a6e..afb43d9 100644 --- a/skills/atlassian/pi/scripts/src/output.ts +++ b/skills/atlassian/pi/scripts/src/output.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import type { CommandOutput, OutputFormat, Writer } from "./types.js"; function renderText(payload: CommandOutput) { diff --git a/skills/atlassian/pi/scripts/src/raw.ts b/skills/atlassian/pi/scripts/src/raw.ts index 8e11793..620a259 100644 --- a/skills/atlassian/pi/scripts/src/raw.ts +++ b/skills/atlassian/pi/scripts/src/raw.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. import { readWorkspaceFile } from "./files.js"; import { sendJsonRequest } from "./http.js"; import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js"; diff --git a/skills/atlassian/pi/scripts/src/types.ts b/skills/atlassian/pi/scripts/src/types.ts index 7f48f56..c365ff5 100644 --- a/skills/atlassian/pi/scripts/src/types.ts +++ b/skills/atlassian/pi/scripts/src/types.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`. export type AtlassianConfig = { baseUrl: string; jiraBaseUrl: string; diff --git a/skills/create-plan/_source/claude-code/SKILL.md b/skills/create-plan/_source/claude-code/SKILL.md new file mode 100644 index 0000000..90cdd43 --- /dev/null +++ b/skills/create-plan/_source/claude-code/SKILL.md @@ -0,0 +1,584 @@ +--- +name: create-plan +description: Use when starting a new feature, project, or complex task that needs structured planning with milestones, bite-sized stories, and resumable execution context. ALWAYS invoke when user says "create a plan", "make a plan", "plan this", "start planning", or similar planning requests. +--- + +# Create Plan (Claude Code) + +Create and maintain a local plan folder under `ai_plan/` at project root. + +## Prerequisite Check (MANDATORY) + +This Claude Code variant depends on Superpowers planning skills and explicit sub-skill invocation. + +Required: + +- Superpowers repo: `https://github.com/obra/superpowers` +- `brainstorming` skill +- `writing-plans` skill + +If any dependency is missing, stop immediately and return: + +"Missing dependency: Superpowers planning skills are required (`brainstorming`, `writing-plans`). Install from https://github.com/obra/superpowers, then retry." + +## Process + +### Phase 1: Analyze + +- Explore the codebase with exploration agents. +- Understand existing patterns and context. + +### Phase 2: Gather Requirements + +- Ask questions ONE AT A TIME until user says ready. +- Cover scope, constraints, success criteria, dependencies. +- Summarize before proceeding. + +### Phase 3: Configure Reviewer + +Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, `pi`, or `skip`. + +If `REVIEWER_CLI=pi`, verify the Pi reviewer binary before entering the review loop: + +```bash +pi --version +``` + +For shorthand `pi/`, split only on the first slash when the prefix is exactly `pi`; store the complete remainder in `REVIEWER_MODEL`. Examples: `pi/claude-opus-4-7` -> `claude-opus-4-7`, `pi/anthropic/claude-opus-4-7` -> `anthropic/claude-opus-4-7`, and `pi/openrouter/anthropic/claude-opus-4-7` -> `openrouter/anthropic/claude-opus-4-7`. + +When `REVIEWER_CLI=pi`, the reviewer model is configured independently from the model running this workflow. If the model/provider is unavailable, surface helper stderr/status and use `pi --list-models [search]` to inspect configured models. + +If the user has already specified a reviewer CLI and model (e.g., "create a plan, review with codex o4-mini"), use those values. Otherwise, ask: + +1. **Which CLI should review the plan?** + - `codex` — OpenAI Codex CLI (`codex exec`) + - `claude` — Claude Code CLI (`claude -p`) + - `cursor` — Cursor Agent CLI (`cursor-agent -p`) + - `skip` — No external review, proceed directly to file generation + +2. **Which model?** (only if a CLI was chosen) + - For `codex`: default `o4-mini`, alternatives: `gpt-5.3-codex`, `o3` + - For `claude`: default `sonnet`, alternatives: `opus`, `haiku` + - For `cursor`: **run `cursor-agent models` first** to see your account's available models (availability varies by subscription) + - Accept any model string the user provides + +3. **Max review rounds for the plan?** (default: 10) + - If the user does not provide a value, set `MAX_ROUNDS=10`. + +Store the chosen `REVIEWER_CLI`, `REVIEWER_MODEL`, and `MAX_ROUNDS` for Phase 6 (Iterative Plan Review). + +### Phase 4: Design (REQUIRED SUB-SKILL) + +- Invoke `superpowers:brainstorming` explicitly. +- Present 2-3 approaches and recommend one. +- Validate design in sections. + +### Phase 5: Plan (REQUIRED SUB-SKILL) + +- Invoke `superpowers:writing-plans` explicitly. +- Break into milestones and bite-sized stories (2-5 min each). +- Story IDs: `S-{milestone}{sequence}`. + +### Phase 6: Iterative Plan Review + +Send the plan to the configured reviewer CLI for feedback. Revise and re-submit until approved (default max 10 rounds). + +**Skip this phase entirely if reviewer was set to `skip`.** + +#### Step 1: Generate Session ID + +```bash +REVIEW_ID=$(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) +``` + +Use for temp artifacts: + +- `/tmp/plan-${REVIEW_ID}.md` +- `/tmp/plan-review-${REVIEW_ID}.md` +- `/tmp/plan-review-${REVIEW_ID}.json` (Cursor only) +- `/tmp/plan-review-${REVIEW_ID}.stderr` +- `/tmp/plan-review-${REVIEW_ID}.status` +- `/tmp/plan-review-${REVIEW_ID}.runner.out` +- `/tmp/plan-review-${REVIEW_ID}.sh` + +Resolve the shared reviewer helper from the installed Claude Code skills directory: + +```bash +REVIEWER_RUNTIME=~/.claude/skills/reviewer-runtime/run-review.sh +``` + +Set helper success-artifact args before writing the command script: + +```bash +HELPER_SUCCESS_FILE_ARGS=() +case "$REVIEWER_CLI" in + codex) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/plan-review-${REVIEW_ID}.md) + ;; + cursor) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/plan-review-${REVIEW_ID}.json) + ;; +esac +``` + +#### Step 2: Write Plan to Temp File + +Write the complete plan (milestones, stories, design decisions, specs) to `/tmp/plan-${REVIEW_ID}.md`. + +#### Review Contract (Applies to Every Round) + +The reviewer response must use this structure: + +```text +## Summary +... + +## Findings +### P0 +- ... +### P1 +- ... +### P2 +- ... +### P3 +- ... + +## Verdict +VERDICT: APPROVED +``` + +Rules: + +- Order findings from `P0` to `P3`. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- Use `- None.` when a severity has no findings. +- `VERDICT: APPROVED` is allowed only when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking. +- The calling agent should still try to fix `P3` findings when they are cheap and safe. + +#### Liveness Contract (Applies While Review Is Running) + +- The shared reviewer runtime emits `state=in-progress note="In progress N"` heartbeats every 60 seconds while the reviewer child is alive. +- The calling agent must keep waiting as long as a fresh `In progress N` heartbeat keeps arriving roughly once per minute. +- Do not abort just because the review is slow, a soft timeout fired, or a `stall-warning` line appears, as long as the `In progress N` heartbeat continues. +- Treat missing heartbeats, `state=failed`, `state=completed-empty-output`, and `state=needs-operator-decision` as escalation signals. + +#### Step 3: Submit to Reviewer (Round 1) + +Write the reviewer invocation to `/tmp/plan-review-${REVIEW_ID}.sh` as a bash script: + +```bash +#!/usr/bin/env bash +set -euo pipefail +``` + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call every round (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "Read the file /tmp/plan-${REVIEW_ID}.md and review. Return exactly the required ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +```bash +codex exec \ + -m ${REVIEWER_MODEL} \ + -s read-only \ + -o /tmp/plan-review-${REVIEW_ID}.md \ + "Review the implementation plan in /tmp/plan-${REVIEW_ID}.md. Focus on: +1. Correctness — Will this plan achieve the stated goals? +2. Risks — What could go wrong? Edge cases? Data loss? +3. Missing steps — Is anything forgotten? +4. Alternatives — Is there a simpler or better approach? +5. Security — Any security concerns? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." +``` + +Do not try to capture the Codex session ID yet. When using the helper, extract it from `/tmp/plan-review-${REVIEW_ID}.runner.out` after the command completes (look for `session id: `), then store it as `CODEX_SESSION_ID` for resume in subsequent rounds. + +**If `REVIEWER_CLI` is `claude`:** + +```bash +claude -p \ + "Review the implementation plan below. Focus on: + +$(cat /tmp/plan-${REVIEW_ID}.md) + +1. Correctness — Will this plan achieve the stated goals? +2. Risks — What could go wrong? Edge cases? Data loss? +3. Missing steps — Is anything forgotten? +4. Alternatives — Is there a simpler or better approach? +5. Security — Any security concerns? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +**If `REVIEWER_CLI` is `cursor`:** + +```bash +cursor-agent -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "Read the file /tmp/plan-${REVIEW_ID}.md and review the implementation plan. Focus on: +1. Correctness — Will this plan achieve the stated goals? +2. Risks — What could go wrong? Edge cases? Data loss? +3. Missing steps — Is anything forgotten? +4. Alternatives — Is there a simpler or better approach? +5. Security — Any security concerns? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." \ + > /tmp/plan-review-${REVIEW_ID}.json +``` + +For `cursor`, the command script writes raw JSON to `/tmp/plan-review-${REVIEW_ID}.json`. Do not run `jq` extraction until after the helper or fallback execution completes. If `jq` is not installed, inform the user: `brew install jq` (macOS) or equivalent. + +Run the command script through the shared helper when available: + +```bash +if [ -x "$REVIEWER_RUNTIME" ]; then + "$REVIEWER_RUNTIME" \ + --command-file /tmp/plan-review-${REVIEW_ID}.sh \ + --stdout-file /tmp/plan-review-${REVIEW_ID}.runner.out \ + --stderr-file /tmp/plan-review-${REVIEW_ID}.stderr \ + --status-file /tmp/plan-review-${REVIEW_ID}.status \ + "${HELPER_SUCCESS_FILE_ARGS[@]}" +else + echo "Warning: reviewer runtime helper not found at $REVIEWER_RUNTIME; falling back to direct synchronous review." >&2 + bash /tmp/plan-review-${REVIEW_ID}.sh >/tmp/plan-review-${REVIEW_ID}.runner.out 2>/tmp/plan-review-${REVIEW_ID}.stderr +fi +``` + +Run the helper in the foreground and watch its live stdout for `state=in-progress` heartbeats. If your agent environment buffers command output until exit, start the helper in the background and poll `/tmp/plan-review-${REVIEW_ID}.status` separately instead of treating heartbeats as post-hoc-only data. + +After the command completes: + +- If `REVIEWER_CLI=cursor`, extract the final review text: + +```bash +CURSOR_SESSION_ID=$(jq -r '.session_id' /tmp/plan-review-${REVIEW_ID}.json) +jq -r '.result' /tmp/plan-review-${REVIEW_ID}.json > /tmp/plan-review-${REVIEW_ID}.md +``` + +- If `REVIEWER_CLI=codex`, extract `CODEX_SESSION_ID` from `/tmp/plan-review-${REVIEW_ID}.runner.out` after the helper or fallback run. If the review text is only in `.runner.out`, move or copy the actual review body into `/tmp/plan-review-${REVIEW_ID}.md` before verdict parsing. +- If `REVIEWER_CLI=claude` or `REVIEWER_CLI=pi`, promote stdout captured by the helper or fallback runner into the markdown review file: + +```bash +cp /tmp/plan-review-${REVIEW_ID}.runner.out /tmp/plan-review-${REVIEW_ID}.md +``` + +Fallback is allowed only when the helper is missing or not executable. + +#### Step 4: Read Review & Check Verdict + +1. Read `/tmp/plan-review-${REVIEW_ID}.md` +2. If the review failed, produced empty output, or reached helper timeout, also read: + - `/tmp/plan-review-${REVIEW_ID}.stderr` + - `/tmp/plan-review-${REVIEW_ID}.status` + - `/tmp/plan-review-${REVIEW_ID}.runner.out` +3. Present review to the user: + +```markdown +## Plan Review — Round N (reviewer: ${REVIEWER_CLI} / ${REVIEWER_MODEL}) + +[Reviewer feedback] +``` + +1. While the reviewer is still running, keep waiting as long as fresh `state=in-progress note="In progress N"` heartbeats continue to appear roughly once per minute. +2. Check verdict: + - **VERDICT: APPROVED** with no `P0`, `P1`, or `P2` findings → proceed to Phase 7 (Initialize workspace) + - **VERDICT: APPROVED** with only `P3` findings → optionally fix the `P3` items if they are cheap and safe, then proceed + - **VERDICT: REVISE** or any `P0`, `P1`, or `P2` finding → go to Step 5 + - No clear verdict but `P0`, `P1`, and `P2` are all `- None.` → treat as approved + - Helper state `completed-empty-output` → treat as failed review attempt, surface stderr/status, fix invocation or prompt handling, then retry + - Helper state `needs-operator-decision` → surface status log and decide whether to extend the timeout, abort, or retry with different helper parameters + - Max rounds (`MAX_ROUNDS`) reached → present the outcome to the user for a manual decision (proceed or stop) + +#### Step 5: Revise the Plan + +Address the reviewer findings in priority order (`P0` → `P1` → `P2`, then `P3` when practical). Update the plan in conversation context and rewrite `/tmp/plan-${REVIEW_ID}.md`. + +Summarize revisions for the user: + +```markdown +### Revisions (Round N) +- [Change and reason, one bullet per issue addressed] +``` + +If a revision contradicts the user's explicit requirements, skip it and note it for the user. + +#### Step 6: Re-submit to Reviewer (Rounds 2-N) + +Rewrite `/tmp/plan-review-${REVIEW_ID}.sh` for the next round. The script should contain the reviewer invocation only; do not run it directly. + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call with prior-round context (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "You previously reviewed this plan and requested revisions. Read the updated payload at /tmp/plan-${REVIEW_ID}.md and re-review using the same ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +Resume the existing session: + +```bash +codex exec resume ${CODEX_SESSION_ID} \ + -o /tmp/plan-review-${REVIEW_ID}.md \ + "I've revised the plan based on your feedback. Updated plan is in /tmp/plan-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." +``` + +If resume fails (session expired), fall back to fresh `codex exec` with context about prior rounds. + +**If `REVIEWER_CLI` is `claude`:** + +Fresh call with accumulated context (Claude CLI has no session resume): + +```bash +claude -p \ + "You previously reviewed an implementation plan and requested revisions. + +Previous feedback summary: [key points from last review] + +I've revised the plan. Updated version is below. + +$(cat /tmp/plan-${REVIEW_ID}.md) + +Changes made: +[List specific changes] + +Re-review the full plan using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +**If `REVIEWER_CLI` is `cursor`:** + +Resume the existing session: + +```bash +cursor-agent --resume ${CURSOR_SESSION_ID} -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "I've revised the plan based on your feedback. Updated plan is in /tmp/plan-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." \ + > /tmp/plan-review-${REVIEW_ID}.json + +jq -r '.result' /tmp/plan-review-${REVIEW_ID}.json > /tmp/plan-review-${REVIEW_ID}.md +``` + +If resume fails, fall back to fresh `cursor-agent -p` with context about prior rounds. + +After updating `/tmp/plan-review-${REVIEW_ID}.sh`, run the same helper/fallback flow from Round 1. + +Return to Step 4. + +#### Step 7: Present Final Result + +```markdown +## Plan Review — Final (reviewer: ${REVIEWER_CLI} / ${REVIEWER_MODEL}) + +**Status:** Approved after N round(s) +[or] +**Status:** Max rounds (`MAX_ROUNDS`) reached — not fully approved + +[Final feedback / remaining concerns] +``` + +#### Step 8: Cleanup + +```bash +rm -f /tmp/plan-${REVIEW_ID}.md \ + /tmp/plan-review-${REVIEW_ID}.md \ + /tmp/plan-review-${REVIEW_ID}.json \ + /tmp/plan-review-${REVIEW_ID}.stderr \ + /tmp/plan-review-${REVIEW_ID}.status \ + /tmp/plan-review-${REVIEW_ID}.runner.out \ + /tmp/plan-review-${REVIEW_ID}.sh +``` + +If the round failed, produced empty output, or reached operator-decision timeout, keep `.stderr`, `.status`, and `.runner.out` until the issue is diagnosed instead of deleting them immediately. + +### Phase 7: Initialize Local Plan Workspace (MANDATORY) + +At project root: + +1. Ensure `ai_plan/` exists. Create it if missing. +2. Ensure `.gitignore` contains `/ai_plan/`. +3. If `.gitignore` was changed, commit that change immediately (local commit only). + +Recommended commit message: + +- `chore(gitignore): ignore ai_plan local planning artifacts` + +### Phase 8: Generate Plan Files (MANDATORY - DO NOT SKIP) + +**PLAN MODE CHECK:** If currently in plan mode: + +1. Inform user that plan files cannot be written while in plan mode. +2. Instruct user to exit plan mode (approve plan or use ExitPlanMode). +3. Proceed with file generation only after exiting plan mode. + +Create `ai_plan/YYYY-MM-DD-/` with ALL files: + +1. `original-plan.md` - Copy the plan file from `~/.claude/plans/` as-is. +2. `final-transcript.md` - Copy of final planning transcript for reference. +3. `milestone-plan.md` - Full specification (template-based). +4. `story-tracker.md` - Status tracking (template-based, all stories start as `pending`). +5. `continuation-runbook.md` - Resume context and execution protocol (template-based). + +Use templates from this skill's `templates/` folder (or `~/.claude/skills/create-plan/templates/` when installed directly in Claude Code). + +### Phase 9: Handoff Instructions + +When handing off to execution, instruct: +> Read `ai_plan/YYYY-MM-DD-/continuation-runbook.md` first, then execute from `ai_plan` files only. + +Private plan files under `~/.claude/plans/` are planning artifacts and must not be used as execution source of truth. + +### Phase 10: Telegram Notification (MANDATORY) + +Resolve the Telegram notifier helper from the installed Claude Code skills directory: + +```bash +TELEGRAM_NOTIFY_RUNTIME=~/.claude/skills/reviewer-runtime/notify-telegram.sh +``` + +On every terminal outcome for the create-plan run (approved, max rounds reached, skipped reviewer, or failure), send a Telegram summary if the helper exists and both `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` are configured: + +```bash +if [ -x "$TELEGRAM_NOTIFY_RUNTIME" ] && [ -n "${TELEGRAM_BOT_TOKEN:-}" ] && [ -n "${TELEGRAM_CHAT_ID:-}" ]; then + "$TELEGRAM_NOTIFY_RUNTIME" --message "create-plan completed for : " +fi +``` + +Rules: + +- Telegram is the only supported notification path. Do not use desktop notifications, `say`, email, or any other notifier. +- Notification failures are non-blocking, but they must be surfaced to the user. +- Before stopping for any user interaction, approval, or manual decision, send a Telegram summary first if configured. +- If Telegram is not configured, state that no Telegram notification was sent. + +## Tracker Discipline (MANDATORY) + +**ALWAYS update `story-tracker.md` before/after each story. NEVER proceed with stale tracker state.** + +Before starting any story: + +1. Open `story-tracker.md` +2. Mark story `in-dev` +3. Add notes if relevant +4. Then begin implementation + +After completing any story: + +1. Mark story `completed` +2. Add commit hash in Notes +3. Review pending stories +4. Update Last Updated and Stories Complete counts + +## Execution Rules to Include in Plan (MANDATORY) + +- Run lint/typecheck/tests after each milestone. +- Prefer linting changed files only for speed. +- Commit locally after each completed milestone (**do not push**). +- Stop and ask user for feedback. +- Apply feedback, rerun checks, and commit again. +- Move to next milestone only after user approval. +- After all milestones are completed and approved, ask permission to push. +- Only after approved push: mark plan as completed. + +## Gitignore Note + +`ai_plan/` is intentionally local and must stay gitignored. Do not treat inability to commit plan-file updates in `ai_plan/` as a problem. + +## Verification Checklist + +- [ ] `ai_plan/` exists at project root +- [ ] `.gitignore` includes `/ai_plan/` +- [ ] `.gitignore` ignore-rule commit was created if needed +- [ ] Plan directory created under `ai_plan/YYYY-MM-DD-/` +- [ ] Reviewer configured or explicitly skipped +- [ ] Max review rounds confirmed (default: 10) +- [ ] Plan review completed (approved or max rounds) — or skipped +- [ ] `original-plan.md` copied from `~/.claude/plans/` plan file +- [ ] `final-transcript.md` present +- [ ] `milestone-plan.md` present +- [ ] `story-tracker.md` created with all stories as `pending` +- [ ] `continuation-runbook.md` present +- [ ] Handoff explicitly says to read runbook first and execute from plan folder +- [ ] Telegram notification attempted if configured + +## Exit Triggers for Question Phase + +User says: "ready", "done", "let's plan", "proceed", "enough questions" diff --git a/skills/create-plan/_source/claude-code/templates/continuation-runbook.md b/skills/create-plan/_source/claude-code/templates/continuation-runbook.md new file mode 100644 index 0000000..11ca6e7 --- /dev/null +++ b/skills/create-plan/_source/claude-code/templates/continuation-runbook.md @@ -0,0 +1,138 @@ +# Continuation Runbook: [Plan Title] + +## Reference Files (START HERE) + +Upon resumption, these files in this folder are the ONLY source of truth: + +| File | Purpose | When to Use | +|------|---------|-------------| +| `continuation-runbook.md` | Full context reproduction + execution workflow | Read FIRST | +| `story-tracker.md` | Current progress and status | Check/update BEFORE and AFTER every story | +| `milestone-plan.md` | Complete plan with specifications | Reference implementation details | +| `original-plan.md` | Original approved plan | Reference original intent | +| `final-transcript.md` | Final planning transcript | Reference reasoning/context | + +Do NOT reference planner-private files during implementation. + +--- + +## Quick Resume Instructions + +1. Read this runbook completely. +2. Check `story-tracker.md`. +3. Find next `pending` story and mark as `in-dev` before starting. +4. Implement the story. +5. Update tracker immediately after each change. + +--- + +## Mandatory Execution Workflow + +Work from this folder (`ai_plan/YYYY-MM-DD-/`) and always follow this order: + +1. Read `continuation-runbook.md` first. +2. Execute stories milestone by milestone. +3. After completing a milestone: + - Run lint/typecheck/tests, prioritizing changed files for speed. + - Commit locally (**DO NOT PUSH**). + - Stop and ask user for feedback. +4. If feedback is provided: + - Apply feedback changes. + - Re-run checks for changed files. + - Commit locally again. + - Ask for milestone approval. +5. Only move to next milestone after explicit approval. +6. After all milestones are completed and approved: + - Ask permission to push. + - If approved, push. + - Mark plan status as `completed`. + +--- + +## Git Note + +`ai_plan/` is intentionally local and must stay gitignored. Do not treat inability to commit plan-file updates inside `ai_plan/` as an error. + +--- + +## Full Context Reproduction + +### Project Overview + +[What this project/feature is about] + +### User Requirements + +[All gathered requirements] + +### Scope + +[In scope / out of scope] + +### Dependencies + +[External dependencies, prerequisites, related systems] + +--- + +## Key Specifications + +### Type Definitions + +```typescript +// Copy-paste ready type definitions +``` + +### Enums & Constants + +```typescript +// All enums/constants needed +``` + +### API Endpoints + +```typescript +// Request/response shapes +``` + +--- + +## Critical Design Decisions + +| Decision | Chosen Approach | Alternatives Rejected | Rationale | +|----------|-----------------|----------------------|-----------| +| [Topic] | [What we chose] | [Other options] | [Why] | + +--- + +## Verification Commands + +### Lint (changed files first) + +```bash +# example: pnpm eslint +``` + +### Typecheck + +```bash +# example: pnpm tsc --noEmit +``` + +### Tests (target changed scope first) + +```bash +# example: pnpm test -- +``` + +--- + +## File Quick Reference + +| File | Purpose | +|------|---------| +| `original-plan.md` | Original approved plan | +| `final-transcript.md` | Final planning transcript | +| `milestone-plan.md` | Full specification | +| `story-tracker.md` | Current progress tracker | +| `continuation-runbook.md` | This runbook | diff --git a/skills/create-plan/_source/claude-code/templates/milestone-plan.md b/skills/create-plan/_source/claude-code/templates/milestone-plan.md new file mode 100644 index 0000000..f646b48 --- /dev/null +++ b/skills/create-plan/_source/claude-code/templates/milestone-plan.md @@ -0,0 +1,118 @@ +# [Plan Title] + +## Overview + +- **Goal:** [One sentence describing the end state] +- **Created:** YYYY-MM-DD +- **Status:** In Progress | Complete + +## Context + +### Requirements + +[Gathered requirements from user questions] + +### Constraints + +[Technical, business, or timeline constraints] + +### Success Criteria + +[How we know this is complete] + +## Architecture + +### Design Decisions + +[Key architectural choices and rationale] + +### Component Relationships + +[How pieces fit together] + +### Data Flow + +[How data moves through the system] + +## Milestones + +### M1: [Name] + +**Description:** [What this milestone achieves] + +**Acceptance Criteria:** + +- [ ] [Criterion 1] +- [ ] [Criterion 2] + +**Stories:** S-101, S-102, S-103... + +**Milestone Completion Rule (MANDATORY):** + +- Run lint/typecheck/tests for changed files. +- Commit locally (DO NOT push). +- Stop and ask user for feedback. +- Apply feedback, re-check changed files, commit again. +- Move to next milestone only after user approval. + +--- + +### M2: [Name] + +**Description:** [What this milestone achieves] + +**Acceptance Criteria:** + +- [ ] [Criterion 1] +- [ ] [Criterion 2] + +**Stories:** S-201, S-202, S-203... + +**Milestone Completion Rule (MANDATORY):** + +- Run lint/typecheck/tests for changed files. +- Commit locally (DO NOT push). +- Stop and ask user for feedback. +- Apply feedback, re-check changed files, commit again. +- Move to next milestone only after user approval. + +--- + +## Technical Specifications + +### Types & Interfaces + +```typescript +// Key type definitions +``` + +### API Contracts + +```typescript +// Endpoint signatures, request/response shapes +``` + +### Constants & Enums + +```typescript +// Shared constants +``` + +## Files Inventory + +| File | Purpose | Milestone | +|------|---------|-----------| +| `path/to/file.ts` | [What it does] | M1 | +| `path/to/other.ts` | [What it does] | M2 | + +--- + +## Related Plan Files + +This file is part of the plan folder under `ai_plan/`: + +- `original-plan.md` - Original approved plan (reference for original intent) +- `final-transcript.md` - Final planning transcript (reference for rationale/context) +- `milestone-plan.md` - This file (full specification) +- `story-tracker.md` - Status tracking (must be kept up to date) +- `continuation-runbook.md` - Resume/execution context (read first) diff --git a/skills/create-plan/_source/claude-code/templates/story-tracker.md b/skills/create-plan/_source/claude-code/templates/story-tracker.md new file mode 100644 index 0000000..08dbd3a --- /dev/null +++ b/skills/create-plan/_source/claude-code/templates/story-tracker.md @@ -0,0 +1,71 @@ +# Story Tracker: [Plan Title] + +## Progress Summary + +- **Current Milestone:** M1 +- **Stories Complete:** 0/N +- **Milestones Approved:** 0/M +- **Last Updated:** YYYY-MM-DD + +--- + +## Milestones + +### M1: [Name] + +| Story | Description | Status | Notes | +|-------|-------------|--------|-------| +| S-101 | [Brief description] | pending | | +| S-102 | [Brief description] | pending | | +| S-103 | [Brief description] | pending | | + +**Approval Status:** pending + +--- + +### M2: [Name] + +| Story | Description | Status | Notes | +|-------|-------------|--------|-------| +| S-201 | [Brief description] | pending | | +| S-202 | [Brief description] | pending | | +| S-203 | [Brief description] | pending | | + +**Approval Status:** pending + +--- + +## Status Legend + +| Status | Meaning | +|--------|---------| +| `pending` | Not started | +| `in-dev` | Currently being worked on | +| `completed` | Done - include commit hash in Notes | +| `deferred` | Postponed - include reason in Notes | + +## Update Instructions (MANDATORY) + +Before starting any story: + +1. Mark story as `in-dev` +2. Update "Last Updated" + +After completing any story: + +1. Mark story as `completed` +2. Add local commit hash to Notes +3. Update "Stories Complete" and "Last Updated" + +At milestone boundary: + +1. Run lint/typecheck/tests for changed files +2. Commit (no push) +3. Request feedback +4. Apply feedback, re-check changed files, commit again +5. Mark milestone **Approval Status: approved** only after user confirms +6. Continue only after approval + +After all milestones approved: + +- Ask permission to push and then mark plan completed. diff --git a/skills/create-plan/_source/codex/SKILL.md b/skills/create-plan/_source/codex/SKILL.md new file mode 100644 index 0000000..4fe088c --- /dev/null +++ b/skills/create-plan/_source/codex/SKILL.md @@ -0,0 +1,624 @@ +--- +name: create-plan +description: Use when a user asks to create or maintain a structured implementation plan in Codex, including milestones, bite-sized stories, and resumable local planning artifacts under ai_plan. +--- + +# Create Plan (Codex Native Superpowers) + +Create and maintain a local plan workspace under `ai_plan/` at project root. + +## Overview + +This skill wraps the current Superpowers flow for Codex: + +1. Design first with `superpowers:brainstorming` +2. Then build an implementation plan with `superpowers:writing-plans` +3. Review the plan iteratively with a second model/provider +4. Persist a local execution package in `ai_plan/YYYY-MM-DD-/` + +**Core principle:** Codex uses native skill discovery from `~/.agents/skills/`. Do not use deprecated `superpowers-codex bootstrap` or `use-skill` CLI commands. + +## Prerequisite Check (MANDATORY) + +Required: + +- Superpowers skills symlink: `~/.agents/skills/superpowers -> ~/.codex/superpowers/skills` +- `superpowers:brainstorming` +- `superpowers:writing-plans` + +Verify before proceeding: + +```bash +test -L ~/.agents/skills/superpowers +test -f ~/.agents/skills/superpowers/brainstorming/SKILL.md +test -f ~/.agents/skills/superpowers/writing-plans/SKILL.md +``` + +If any dependency is missing, stop and return: + +`Missing dependency: native Superpowers skills are required (superpowers:brainstorming, superpowers:writing-plans). Ensure ~/.agents/skills/superpowers is configured, then retry.` + +## Required Skill Invocation Rules + +- Invoke relevant skills through native discovery (no CLI wrapper). +- Announce skill usage explicitly: + - `I've read the [Skill Name] skill and I'm using it to [purpose].` +- For skills with checklists, track checklist items with `update_plan` todos. +- Tool mapping for Codex: + - `TodoWrite` -> `update_plan` + - `Task` subagents -> unavailable in Codex; do the work directly and state the limitation + - `Skill` -> use native skill discovery from `~/.agents/skills/` + +## Process + +### Phase 1: Analyze + +- Explore the codebase and existing patterns. + +### Phase 2: Gather Requirements + +- Ask questions one at a time until user says ready. +- Confirm scope, constraints, success criteria, dependencies. + +### Phase 3: Configure Reviewer + +Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, `pi`, or `skip`. + +If `REVIEWER_CLI=pi`, verify the Pi reviewer binary before entering the review loop: + +```bash +pi --version +``` + +For shorthand `pi/`, split only on the first slash when the prefix is exactly `pi`; store the complete remainder in `REVIEWER_MODEL`. Examples: `pi/claude-opus-4-7` -> `claude-opus-4-7`, `pi/anthropic/claude-opus-4-7` -> `anthropic/claude-opus-4-7`, and `pi/openrouter/anthropic/claude-opus-4-7` -> `openrouter/anthropic/claude-opus-4-7`. + +When `REVIEWER_CLI=pi`, the reviewer model is configured independently from the model running this workflow. If the model/provider is unavailable, surface helper stderr/status and use `pi --list-models [search]` to inspect configured models. + +If the user has already specified a reviewer CLI and model (e.g., "create a plan, review with claude sonnet"), use those values. Otherwise, ask: + +1. **Which CLI should review the plan?** + - `codex` — OpenAI Codex CLI (`codex exec`) + - `claude` — Claude Code CLI (`claude -p`) + - `cursor` — Cursor Agent CLI (`cursor-agent -p`) + - `skip` — No external review, proceed directly to file generation + +2. **Which model?** (only if a CLI was chosen) + - For `codex`: default `o4-mini`, alternatives: `gpt-5.3-codex`, `o3` + - For `claude`: default `sonnet`, alternatives: `opus`, `haiku` + - For `cursor`: **run `cursor-agent models` first** to see your account's available models (availability varies by subscription) + - Accept any model string the user provides + +3. **Max review rounds for the plan?** (default: 10) + - If the user does not provide a value, set `MAX_ROUNDS=10`. + +Store the chosen `REVIEWER_CLI`, `REVIEWER_MODEL`, and `MAX_ROUNDS` for Phase 6 (Iterative Plan Review). + +### Phase 4: Design (REQUIRED SUB-SKILL) + +Invoke `superpowers:brainstorming`, then propose 2-3 approaches and recommend one. + +### Phase 5: Plan (REQUIRED SUB-SKILL) + +Invoke `superpowers:writing-plans`, then break work into milestones and bite-sized stories. + +### Phase 6: Iterative Plan Review + +Send the plan to the configured reviewer CLI for feedback. Revise and re-submit until approved (default max 10 rounds). + +**Skip this phase entirely if reviewer was set to `skip`.** + +#### Step 1: Generate Session ID + +```bash +REVIEW_ID=$(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) +``` + +Use for temp artifacts: + +- `/tmp/plan-${REVIEW_ID}.md` - plan payload +- `/tmp/plan-review-${REVIEW_ID}.md` - normalized review text presented to the user +- `/tmp/plan-review-${REVIEW_ID}.json` - raw Cursor JSON (only for `cursor`) +- `/tmp/plan-review-${REVIEW_ID}.stderr` - reviewer stderr +- `/tmp/plan-review-${REVIEW_ID}.status` - helper heartbeat/status log +- `/tmp/plan-review-${REVIEW_ID}.runner.out` - helper-managed stdout from the reviewer command process +- `/tmp/plan-review-${REVIEW_ID}.sh` - reviewer command script + +Resolve the shared reviewer helper from the installed Codex skills directory: + +```bash +REVIEWER_RUNTIME=~/.codex/skills/reviewer-runtime/run-review.sh +``` + +Set helper success-artifact args before writing the command script: + +```bash +HELPER_SUCCESS_FILE_ARGS=() +case "$REVIEWER_CLI" in + codex) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/plan-review-${REVIEW_ID}.md) + ;; + cursor) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/plan-review-${REVIEW_ID}.json) + ;; +esac +``` + +#### Step 2: Write Plan to Temp File + +Write the complete plan (milestones, stories, design decisions, specs) to `/tmp/plan-${REVIEW_ID}.md`. + +#### Review Contract (Applies to Every Round) + +The reviewer response must use this structure: + +```text +## Summary +... + +## Findings +### P0 +- ... +### P1 +- ... +### P2 +- ... +### P3 +- ... + +## Verdict +VERDICT: APPROVED +``` + +Rules: + +- Order findings from `P0` to `P3`. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- Use `- None.` when a severity has no findings. +- `VERDICT: APPROVED` is allowed only when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking. +- The calling agent should still try to fix `P3` findings when they are cheap and safe. + +#### Liveness Contract (Applies While Review Is Running) + +- The shared reviewer runtime emits `state=in-progress note="In progress N"` heartbeats every 60 seconds while the reviewer child is alive. +- The calling agent must keep waiting as long as a fresh `In progress N` heartbeat keeps arriving roughly once per minute. +- Do not abort just because the review is slow, a soft timeout fired, or a `stall-warning` line appears, as long as the `In progress N` heartbeat continues. +- Treat missing heartbeats, `state=failed`, `state=completed-empty-output`, and `state=needs-operator-decision` as escalation signals. + +#### Step 3: Submit to Reviewer (Round 1) + +Write the reviewer invocation to `/tmp/plan-review-${REVIEW_ID}.sh` as a bash script: + +```bash +#!/usr/bin/env bash +set -euo pipefail +``` + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call every round (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "Read the file /tmp/plan-${REVIEW_ID}.md and review. Return exactly the required ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +```bash +codex exec \ + -m ${REVIEWER_MODEL} \ + -s read-only \ + -o /tmp/plan-review-${REVIEW_ID}.md \ + "Review the implementation plan in /tmp/plan-${REVIEW_ID}.md. Focus on: +1. Correctness — Will this plan achieve the stated goals? +2. Risks — What could go wrong? Edge cases? Data loss? +3. Missing steps — Is anything forgotten? +4. Alternatives — Is there a simpler or better approach? +5. Security — Any security concerns? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." +``` + +Do not try to capture the Codex session ID yet. When using the helper, extract it from `/tmp/plan-review-${REVIEW_ID}.runner.out` after the command completes (look for `session id: `), then store it as `CODEX_SESSION_ID` for resume in subsequent rounds. + +**If `REVIEWER_CLI` is `claude`:** + +```bash +claude -p \ + "Review the implementation plan below. Focus on: + +$(cat /tmp/plan-${REVIEW_ID}.md) + +1. Correctness — Will this plan achieve the stated goals? +2. Risks — What could go wrong? Edge cases? Data loss? +3. Missing steps — Is anything forgotten? +4. Alternatives — Is there a simpler or better approach? +5. Security — Any security concerns? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +**If `REVIEWER_CLI` is `cursor`:** + +```bash +cursor-agent -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "Read the file /tmp/plan-${REVIEW_ID}.md and review the implementation plan. Focus on: +1. Correctness — Will this plan achieve the stated goals? +2. Risks — What could go wrong? Edge cases? Data loss? +3. Missing steps — Is anything forgotten? +4. Alternatives — Is there a simpler or better approach? +5. Security — Any security concerns? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." \ + > /tmp/plan-review-${REVIEW_ID}.json +``` + +For `cursor`, the command script writes raw JSON to `/tmp/plan-review-${REVIEW_ID}.json`. Do not run `jq` extraction until after the helper or fallback execution completes. If `jq` is not installed, inform the user: `brew install jq` (macOS) or equivalent. + +Run the command script through the shared helper when available: + +```bash +if [ -x "$REVIEWER_RUNTIME" ]; then + "$REVIEWER_RUNTIME" \ + --command-file /tmp/plan-review-${REVIEW_ID}.sh \ + --stdout-file /tmp/plan-review-${REVIEW_ID}.runner.out \ + --stderr-file /tmp/plan-review-${REVIEW_ID}.stderr \ + --status-file /tmp/plan-review-${REVIEW_ID}.status \ + "${HELPER_SUCCESS_FILE_ARGS[@]}" +else + echo "Warning: reviewer runtime helper not found at $REVIEWER_RUNTIME; falling back to direct synchronous review." >&2 + bash /tmp/plan-review-${REVIEW_ID}.sh >/tmp/plan-review-${REVIEW_ID}.runner.out 2>/tmp/plan-review-${REVIEW_ID}.stderr +fi +``` + +Run the helper in the foreground and watch its live stdout for `state=in-progress` heartbeats. If your agent environment buffers command output until exit, start the helper in the background and poll `/tmp/plan-review-${REVIEW_ID}.status` separately instead of treating heartbeats as post-hoc-only data. + +After the command completes: + +- If `REVIEWER_CLI=cursor`, extract the final review text: + +```bash +CURSOR_SESSION_ID=$(jq -r '.session_id' /tmp/plan-review-${REVIEW_ID}.json) +jq -r '.result' /tmp/plan-review-${REVIEW_ID}.json > /tmp/plan-review-${REVIEW_ID}.md +``` + +- If `REVIEWER_CLI=codex`, extract `CODEX_SESSION_ID` from `/tmp/plan-review-${REVIEW_ID}.runner.out` after the helper or fallback run. If the review text is only in `.runner.out`, move or copy the actual review body into `/tmp/plan-review-${REVIEW_ID}.md` before verdict parsing. +- If `REVIEWER_CLI=claude` or `REVIEWER_CLI=pi`, promote stdout captured by the helper or fallback runner into the markdown review file: + +```bash +cp /tmp/plan-review-${REVIEW_ID}.runner.out /tmp/plan-review-${REVIEW_ID}.md +``` + +Fallback is allowed only when the helper is missing or not executable. + +#### Step 4: Read Review & Check Verdict + +1. Read `/tmp/plan-review-${REVIEW_ID}.md` +2. If the review failed, produced empty output, or reached helper timeout, also read: + - `/tmp/plan-review-${REVIEW_ID}.stderr` + - `/tmp/plan-review-${REVIEW_ID}.status` + - `/tmp/plan-review-${REVIEW_ID}.runner.out` +3. Present review to the user: + +```markdown +## Plan Review — Round N (reviewer: ${REVIEWER_CLI} / ${REVIEWER_MODEL}) + +[Reviewer feedback] +``` + +1. While the reviewer is still running, keep waiting as long as fresh `state=in-progress note="In progress N"` heartbeats continue to appear roughly once per minute. +2. Check verdict: + - **VERDICT: APPROVED** with no `P0`, `P1`, or `P2` findings → proceed to Phase 7 (Initialize workspace) + - **VERDICT: APPROVED** with only `P3` findings → optionally fix the `P3` items if they are cheap and safe, then proceed + - **VERDICT: REVISE** or any `P0`, `P1`, or `P2` finding → go to Step 5 + - No clear verdict but `P0`, `P1`, and `P2` are all `- None.` → treat as approved + - Helper state `completed-empty-output` → treat as failed review attempt, surface stderr/status, fix invocation or prompt handling, then retry + - Helper state `needs-operator-decision` → surface status log and decide whether to extend the timeout, abort, or retry with different helper parameters + - Max rounds (`MAX_ROUNDS`) reached → present the outcome to the user for a manual decision (proceed or stop) + +#### Step 5: Revise the Plan + +Address the reviewer findings in priority order (`P0` → `P1` → `P2`, then `P3` when practical). Update the plan in conversation context and rewrite `/tmp/plan-${REVIEW_ID}.md`. + +Summarize revisions for the user: + +```markdown +### Revisions (Round N) +- [Change and reason, one bullet per issue addressed] +``` + +If a revision contradicts the user's explicit requirements, skip it and note it for the user. + +#### Step 6: Re-submit to Reviewer (Rounds 2-N) + +Rewrite `/tmp/plan-review-${REVIEW_ID}.sh` for the next round. The script should contain the reviewer invocation only; do not run it directly. + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call with prior-round context (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "You previously reviewed this plan and requested revisions. Read the updated payload at /tmp/plan-${REVIEW_ID}.md and re-review using the same ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +Resume the existing session: + +```bash +codex exec resume ${CODEX_SESSION_ID} \ + -o /tmp/plan-review-${REVIEW_ID}.md \ + "I've revised the plan based on your feedback. Updated plan is in /tmp/plan-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." +``` + +If resume fails (session expired), fall back to fresh `codex exec` with context about prior rounds. + +**If `REVIEWER_CLI` is `claude`:** + +Fresh call with accumulated context (Claude CLI has no session resume): + +```bash +claude -p \ + "You previously reviewed an implementation plan and requested revisions. + +Previous feedback summary: [key points from last review] + +I've revised the plan. Updated version is below. + +$(cat /tmp/plan-${REVIEW_ID}.md) + +Changes made: +[List specific changes] + +Re-review the full plan using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +**If `REVIEWER_CLI` is `cursor`:** + +Resume the existing session: + +```bash +cursor-agent --resume ${CURSOR_SESSION_ID} -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "I've revised the plan based on your feedback. Updated plan is in /tmp/plan-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." \ + > /tmp/plan-review-${REVIEW_ID}.json + +jq -r '.result' /tmp/plan-review-${REVIEW_ID}.json > /tmp/plan-review-${REVIEW_ID}.md +``` + +If resume fails, fall back to fresh `cursor-agent -p` with context about prior rounds. + +After updating `/tmp/plan-review-${REVIEW_ID}.sh`, run the same helper/fallback flow from Round 1. + +Return to Step 4. + +#### Step 7: Present Final Result + +```markdown +## Plan Review — Final (reviewer: ${REVIEWER_CLI} / ${REVIEWER_MODEL}) + +**Status:** Approved after N round(s) +[or] +**Status:** Max rounds (`MAX_ROUNDS`) reached — not fully approved + +[Final feedback / remaining concerns] +``` + +#### Step 8: Cleanup + +```bash +rm -f /tmp/plan-${REVIEW_ID}.md \ + /tmp/plan-review-${REVIEW_ID}.md \ + /tmp/plan-review-${REVIEW_ID}.json \ + /tmp/plan-review-${REVIEW_ID}.stderr \ + /tmp/plan-review-${REVIEW_ID}.status \ + /tmp/plan-review-${REVIEW_ID}.runner.out \ + /tmp/plan-review-${REVIEW_ID}.sh +``` + +If the round failed, produced empty output, or reached operator-decision timeout, keep `.stderr`, `.status`, and `.runner.out` until the issue is diagnosed instead of deleting them immediately. + +### Phase 7: Initialize Local Plan Workspace (MANDATORY) + +At project root: + +1. Ensure `ai_plan/` exists. Create it if missing. +2. Ensure `.gitignore` contains `/ai_plan/`. +3. If `.gitignore` was changed, commit that change immediately (local commit only). + +Recommended commit message: + +- `chore(gitignore): ignore ai_plan local planning artifacts` + +### Phase 8: Generate Plan Files (MANDATORY) + +Create `ai_plan/YYYY-MM-DD-/` with all files below: + +1. `original-plan.md` - copy of original planner-generated plan. +2. `final-transcript.md` - copy of final planning transcript used to reach approved plan. +3. `milestone-plan.md` - full implementation spec (from template). +4. `story-tracker.md` - story/milestone status tracker (from template). +5. `continuation-runbook.md` - execution instructions and context (from template). + +Use templates from this skill's `templates/` folder. + +### Phase 9: Handoff + +Always instruct the executing agent: +> Read `ai_plan/YYYY-MM-DD-/continuation-runbook.md` first, then execute from that folder. + +Do not rely on planner-private files during implementation. + +### Phase 10: Telegram Notification (MANDATORY) + +Resolve the Telegram notifier helper from the installed Codex skills directory: + +```bash +TELEGRAM_NOTIFY_RUNTIME=~/.codex/skills/reviewer-runtime/notify-telegram.sh +``` + +On every terminal outcome for the create-plan run (approved, max rounds reached, skipped reviewer, or failure), send a Telegram summary if the helper exists and both `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` are configured: + +```bash +if [ -x "$TELEGRAM_NOTIFY_RUNTIME" ] && [ -n "${TELEGRAM_BOT_TOKEN:-}" ] && [ -n "${TELEGRAM_CHAT_ID:-}" ]; then + "$TELEGRAM_NOTIFY_RUNTIME" --message "create-plan completed for : " +fi +``` + +Rules: + +- Telegram is the only supported notification path. Do not use desktop notifications, `say`, email, or any other notifier. +- Notification failures are non-blocking, but they must be surfaced to the user. +- Before stopping for any user interaction, approval, or manual decision, send a Telegram summary first if configured. +- If Telegram is not configured, state that no Telegram notification was sent. + +## Quick Reference + +| Phase | Action | Required Output | +|---|---|---| +| 1 | Analyze codebase/context | Constraints and known patterns | +| 2 | Gather requirements (one question at a time) | Confirmed scope and success criteria | +| 3 | Configure reviewer CLI and model | `REVIEWER_CLI`, `REVIEWER_MODEL`, `MAX_ROUNDS` (or `skip`) | +| 4 | Invoke `superpowers:brainstorming` | Chosen design approach | +| 5 | Invoke `superpowers:writing-plans` | Milestones and bite-sized stories | +| 6 | Iterative plan review (max `MAX_ROUNDS` rounds) | Reviewer approval or max-rounds warning | +| 7 | Initialize `ai_plan/` + `.gitignore` | Local planning workspace ready | +| 8 | Build plan package from templates | Full plan folder with required files | +| 9 | Handoff with runbook-first instruction | Resumable execution context | +| 10 | Send Telegram notification | User notified or notification status reported | + +## Execution Rules to Include in Plan (MANDATORY) + +- Run lint/typecheck/tests after each milestone. +- Prefer linting changed files only for speed. +- Commit locally after each completed milestone (**do not push**). +- Stop and ask user for feedback. +- Apply feedback, rerun checks, and commit again. +- Move to next milestone only after user approval. +- After all milestones are completed and approved, ask permission to push. +- Only after approved push: mark plan as completed. + +## Gitignore Note + +`ai_plan/` is intentionally local and must stay gitignored. Do not treat inability to commit plan-file updates in `ai_plan/` as a problem. + +## Common Mistakes + +- Using deprecated commands like `superpowers-codex bootstrap` or `superpowers-codex use-skill`. +- Jumping to implementation planning without running `superpowers:brainstorming` first. +- Asking multiple requirement questions in one message. +- Forgetting to create/update `.gitignore` for `/ai_plan/`. +- Omitting one or more required files in the plan package. +- Handoff without explicit "read runbook first" direction. +- Skipping the reviewer phase without explicit user opt-out. +- Not capturing the Codex session ID for resume in subsequent review rounds. +- Using any notification path other than Telegram. + +## Rationalizations and Counters + +| Rationalization | Counter | +|---|---| +| "Bootstrap CLI is faster" | Deprecated for Codex; native discovery is the supported path. | +| "I can skip brainstorming for small tasks" | Creative/planning work still requires design validation first. | +| "I don't need `update_plan` for checklist skills" | Checklist tracking is mandatory for execution reliability. | +| "I can keep plan files outside `ai_plan/`" | This skill standardizes local resumable planning under `ai_plan/`. | +| "The reviewer approved, I can skip my own validation" | Reviewer feedback supplements but does not replace your own verification. | + +## Red Flags - Stop and Correct + +- You are about to run any `superpowers-codex` command. +- You started writing milestones before design validation. +- You did not announce which skill you invoked and why. +- You are marking planning complete without all required files. +- Handoff does not explicitly point to `continuation-runbook.md`. +- You are applying a reviewer suggestion that contradicts user requirements. + +## Verification Checklist + +- [ ] `ai_plan/` exists at project root +- [ ] `.gitignore` includes `/ai_plan/` +- [ ] `.gitignore` ignore-rule commit was created if needed +- [ ] Plan directory created under `ai_plan/YYYY-MM-DD-/` +- [ ] Reviewer configured or explicitly skipped +- [ ] Max review rounds confirmed (default: 10) +- [ ] Plan review completed (approved or max rounds) — or skipped +- [ ] `original-plan.md` present +- [ ] `final-transcript.md` present +- [ ] `milestone-plan.md` present +- [ ] `story-tracker.md` present +- [ ] `continuation-runbook.md` present +- [ ] Handoff explicitly says to read runbook first and execute from plan folder +- [ ] Telegram notification attempted if configured diff --git a/skills/create-plan/_source/codex/templates/continuation-runbook.md b/skills/create-plan/_source/codex/templates/continuation-runbook.md new file mode 100644 index 0000000..dcaaf28 --- /dev/null +++ b/skills/create-plan/_source/codex/templates/continuation-runbook.md @@ -0,0 +1,145 @@ +# Continuation Runbook: [Plan Title] + +## Reference Files (START HERE) + +Upon resumption, these files in this folder are the ONLY source of truth: + +| File | Purpose | When to Use | +|------|---------|-------------| +| `continuation-runbook.md` | Full context reproduction + execution workflow | Read FIRST | +| `story-tracker.md` | Current progress and status | Check/update BEFORE and AFTER every story | +| `milestone-plan.md` | Complete plan with specifications | Reference implementation details | +| `original-plan.md` | Original approved plan | Reference original intent | +| `final-transcript.md` | Final planning transcript | Reference reasoning/context | + +Do NOT reference planner-private files during implementation. + +## Skill Workflow Guardrails + +- Invoke relevant skills before action using native skill discovery. +- Announce which skill is being used and why. +- If a checklist-driven skill applies, track checklist execution explicitly. +- Do not use deprecated CLI wrappers for skill invocation. + +--- + +## Quick Resume Instructions + +1. Read this runbook completely. +2. Check `story-tracker.md`. +3. Find next `pending` story and mark as `in-dev` before starting. +4. Implement the story. +5. Update tracker immediately after each change. + +--- + +## Mandatory Execution Workflow + +Work from this folder (`ai_plan/YYYY-MM-DD-/`) and always follow this order: + +1. Read `continuation-runbook.md` first. +2. Execute stories milestone by milestone. +3. After completing a milestone: + - Run lint/typecheck/tests, prioritizing changed files for speed. + - Commit locally (**DO NOT PUSH**). + - Stop and ask user for feedback. +4. If feedback is provided: + - Apply feedback changes. + - Re-run checks for changed files. + - Commit locally again. + - Ask for milestone approval. +5. Only move to next milestone after explicit approval. +6. After all milestones are completed and approved: + - Ask permission to push. + - If approved, push. + - Mark plan status as `completed`. + +--- + +## Git Note + +`ai_plan/` is intentionally local and must stay gitignored. Do not treat inability to commit plan-file updates inside `ai_plan/` as an error. + +--- + +## Full Context Reproduction + +### Project Overview + +[What this project/feature is about] + +### User Requirements + +[All gathered requirements] + +### Scope + +[In scope / out of scope] + +### Dependencies + +[External dependencies, prerequisites, related systems] + +--- + +## Key Specifications + +### Type Definitions + +```typescript +// Copy-paste ready type definitions +``` + +### Enums & Constants + +```typescript +// All enums/constants needed +``` + +### API Endpoints + +```typescript +// Request/response shapes +``` + +--- + +## Critical Design Decisions + +| Decision | Chosen Approach | Alternatives Rejected | Rationale | +|----------|-----------------|----------------------|-----------| +| [Topic] | [What we chose] | [Other options] | [Why] | + +--- + +## Verification Commands + +### Lint (changed files first) + +```bash +# example: +``` + +### Typecheck + +```bash +# example: +``` + +### Tests (target changed scope first) + +```bash +# example: +``` + +--- + +## File Quick Reference + +| File | Purpose | +|------|---------| +| `original-plan.md` | Original approved plan | +| `final-transcript.md` | Final planning transcript | +| `milestone-plan.md` | Full specification | +| `story-tracker.md` | Current progress tracker | +| `continuation-runbook.md` | This runbook | diff --git a/skills/create-plan/_source/codex/templates/milestone-plan.md b/skills/create-plan/_source/codex/templates/milestone-plan.md new file mode 100644 index 0000000..36ce980 --- /dev/null +++ b/skills/create-plan/_source/codex/templates/milestone-plan.md @@ -0,0 +1,125 @@ +# [Plan Title] + +## Overview + +- **Goal:** [One sentence describing the end state] +- **Created:** YYYY-MM-DD +- **Status:** In Progress | Complete + +## Planning Guardrails + +- This plan assumes design was validated before implementation planning. +- Skills are invoked via native discovery (Codex: `~/.agents/skills/`). +- Deprecated CLI wrappers (for example, `superpowers-codex bootstrap` / `use-skill`) are not part of this workflow. +- Milestones require verification + local commits + explicit approval before proceeding. + +## Context + +### Requirements + +[Gathered requirements from user questions] + +### Constraints + +[Technical, business, or timeline constraints] + +### Success Criteria + +[How we know this is complete] + +## Architecture + +### Design Decisions + +[Key architectural choices and rationale] + +### Component Relationships + +[How pieces fit together] + +### Data Flow + +[How data moves through the system] + +## Milestones + +### M1: [Name] + +**Description:** [What this milestone achieves] + +**Acceptance Criteria:** + +- [ ] [Criterion 1] +- [ ] [Criterion 2] + +**Stories:** S-101, S-102, S-103... + +**Milestone Completion Rule (MANDATORY):** + +- Run lint/typecheck/tests for changed files. +- Commit locally (DO NOT push). +- Stop and ask user for feedback. +- Apply feedback, re-check changed files, commit again. +- Move to next milestone only after user approval. + +--- + +### M2: [Name] + +**Description:** [What this milestone achieves] + +**Acceptance Criteria:** + +- [ ] [Criterion 1] +- [ ] [Criterion 2] + +**Stories:** S-201, S-202, S-203... + +**Milestone Completion Rule (MANDATORY):** + +- Run lint/typecheck/tests for changed files. +- Commit locally (DO NOT push). +- Stop and ask user for feedback. +- Apply feedback, re-check changed files, commit again. +- Move to next milestone only after user approval. + +--- + +## Technical Specifications + +### Types & Interfaces + +```typescript +// Key type definitions (or equivalent language constructs) +``` + +### API Contracts + +```typescript +// Endpoint signatures, request/response shapes (if applicable) +``` + +### Constants & Enums + +```typescript +// Shared constants (if applicable) +``` + +## Files Inventory + +| File | Purpose | Milestone | +|------|---------|-----------| +| `path/to/file.ts` | [What it does] | M1 | +| `path/to/other.ts` | [What it does] | M2 | + +--- + +## Related Plan Files + +This file is part of the plan folder under `ai_plan/`: + +- `original-plan.md` - Original approved plan (reference for original intent) +- `final-transcript.md` - Final planning transcript (reference for rationale/context) +- `milestone-plan.md` - This file (full specification) +- `story-tracker.md` - Status tracking (must be kept up to date) +- `continuation-runbook.md` - Resume/execution context (read first) diff --git a/skills/create-plan/_source/codex/templates/story-tracker.md b/skills/create-plan/_source/codex/templates/story-tracker.md new file mode 100644 index 0000000..b08681d --- /dev/null +++ b/skills/create-plan/_source/codex/templates/story-tracker.md @@ -0,0 +1,78 @@ +# Story Tracker: [Plan Title] + +## Progress Summary + +- **Current Milestone:** M1 +- **Stories Complete:** 0/N +- **Milestones Approved:** 0/M +- **Last Updated:** YYYY-MM-DD + +## Tracking Guardrails + +- Update status immediately when work starts (`in-dev`) and when work completes (`completed`). +- Require explicit user approval at each milestone boundary before continuing. +- Do not push until all milestones are approved and permission is explicitly granted. +- Keep this file and `milestone-plan.md` synchronized. + +--- + +## Milestones + +### M1: [Name] + +| Story | Description | Status | Notes | +|-------|-------------|--------|-------| +| S-101 | [Brief description] | pending | | +| S-102 | [Brief description] | pending | | +| S-103 | [Brief description] | pending | | + +**Approval Status:** pending + +--- + +### M2: [Name] + +| Story | Description | Status | Notes | +|-------|-------------|--------|-------| +| S-201 | [Brief description] | pending | | +| S-202 | [Brief description] | pending | | +| S-203 | [Brief description] | pending | | + +**Approval Status:** pending + +--- + +## Status Legend + +| Status | Meaning | +|--------|---------| +| `pending` | Not started | +| `in-dev` | Currently being worked on | +| `completed` | Done - include commit hash in Notes | +| `deferred` | Postponed - include reason in Notes | + +## Update Instructions (MANDATORY) + +Before starting any story: + +1. Mark story as `in-dev` +2. Update "Last Updated" + +After completing any story: + +1. Mark story as `completed` +2. Add local commit hash to Notes +3. Update "Stories Complete" and "Last Updated" + +At milestone boundary: + +1. Run lint/typecheck/tests for changed files +2. Commit (no push) +3. Request feedback +4. Apply feedback, re-check changed files, commit again +5. Mark milestone **Approval Status: approved** only after user confirms +6. Continue only after approval + +After all milestones approved: + +- Ask permission to push and then mark plan completed. diff --git a/skills/create-plan/_source/cursor/SKILL.md b/skills/create-plan/_source/cursor/SKILL.md new file mode 100644 index 0000000..24016af --- /dev/null +++ b/skills/create-plan/_source/cursor/SKILL.md @@ -0,0 +1,649 @@ +--- +name: create-plan +description: Use when starting a new feature, project, or complex task that needs structured planning with milestones, bite-sized stories, and resumable execution context in Cursor Agent CLI workflows. ALWAYS invoke when user says "create a plan", "make a plan", "plan this", "start planning", or similar planning requests. +--- + +# Create Plan (Cursor Agent CLI) + +Create and maintain a local plan folder under `ai_plan/` at project root. + +## Overview + +This skill wraps the current Superpowers flow for the Cursor Agent CLI (`cursor-agent`): + +1. Design first with `superpowers:brainstorming` +2. Then build an implementation plan with `superpowers:writing-plans` +3. Review the plan iteratively with a second model/provider +4. Persist a local execution package in `ai_plan/YYYY-MM-DD-/` + +**Core principle:** Cursor Agent CLI discovers skills from `.cursor/skills/` (repo-local), `~/.cursor/skills/` (global), and installed Cursor plugin cache entries. It also reads `AGENTS.md` at the repo root for additional instructions. + +## Prerequisite Check (MANDATORY) + +Required: + +- Cursor Agent CLI: `cursor-agent --version` (install via `curl https://cursor.com/install -fsS | bash`). The binary is `cursor-agent` (installed to `~/.local/bin/`). Some environments alias it as `cursor agent` (subcommand of the Cursor IDE CLI) — both forms work, but this skill uses `cursor-agent` throughout. +- `jq` (required only if using `cursor` as the reviewer CLI): `jq --version` (install via `brew install jq` or your package manager) +- Superpowers repo: `https://github.com/obra/superpowers` +- Superpowers skills available from the Cursor plugin cache, `.cursor/skills/` (repo-local), or `~/.cursor/skills/` (global). Do not install both the plugin and a manual Superpowers copy, or Cursor may show duplicate skill entries. +- `superpowers:brainstorming` +- `superpowers:writing-plans` + +Verify before proceeding: + +```bash +cursor-agent --version +test -f .cursor/skills/superpowers/skills/brainstorming/SKILL.md || test -f ~/.cursor/skills/superpowers/skills/brainstorming/SKILL.md || find ~/.cursor/plugins/cache/cursor-public/superpowers -path '*/skills/brainstorming/SKILL.md' -print -quit 2>/dev/null | grep -q . +test -f .cursor/skills/superpowers/skills/writing-plans/SKILL.md || test -f ~/.cursor/skills/superpowers/skills/writing-plans/SKILL.md || find ~/.cursor/plugins/cache/cursor-public/superpowers -path '*/skills/writing-plans/SKILL.md' -print -quit 2>/dev/null | grep -q . +# Only if using cursor as reviewer CLI: +# jq --version +``` + +If any dependency is missing, stop and return: + +`Missing dependency: Superpowers planning skills are required (superpowers:brainstorming, superpowers:writing-plans). Install the Cursor Superpowers plugin or install Superpowers under .cursor/skills/ or ~/.cursor/skills/, then retry.` + +## Required Skill Invocation Rules + +- Invoke relevant skills through Cursor-native discovery (`.cursor/skills/`, `~/.cursor/skills/`, or installed Cursor plugin cache entries). +- Announce skill usage explicitly: + - `I've read the [Skill Name] skill and I'm using it to [purpose].` +- For skills with checklists, track checklist items explicitly in conversation. + +## Process + +### Phase 1: Analyze + +- Explore the codebase and existing patterns. + +### Phase 2: Gather Requirements + +- Ask questions one at a time until user says ready. +- Confirm scope, constraints, success criteria, dependencies. + +### Phase 3: Configure Reviewer + +Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, `pi`, or `skip`. + +If `REVIEWER_CLI=pi`, verify the Pi reviewer binary before entering the review loop: + +```bash +pi --version +``` + +For shorthand `pi/`, split only on the first slash when the prefix is exactly `pi`; store the complete remainder in `REVIEWER_MODEL`. Examples: `pi/claude-opus-4-7` -> `claude-opus-4-7`, `pi/anthropic/claude-opus-4-7` -> `anthropic/claude-opus-4-7`, and `pi/openrouter/anthropic/claude-opus-4-7` -> `openrouter/anthropic/claude-opus-4-7`. + +When `REVIEWER_CLI=pi`, the reviewer model is configured independently from the model running this workflow. If the model/provider is unavailable, surface helper stderr/status and use `pi --list-models [search]` to inspect configured models. + +If the user has already specified a reviewer CLI and model (e.g., "create a plan, review with codex o4-mini"), use those values. Otherwise, ask: + +1. **Which CLI should review the plan?** + - `codex` — OpenAI Codex CLI (`codex exec`) + - `claude` — Claude Code CLI (`claude -p`) + - `cursor` — Cursor Agent CLI (`cursor-agent -p`) + - `skip` — No external review, proceed directly to file generation + +2. **Which model?** (only if a CLI was chosen) + - For `codex`: default `o4-mini`, alternatives: `gpt-5.3-codex`, `o3` + - For `claude`: default `sonnet`, alternatives: `opus`, `haiku` + - For `cursor`: **run `cursor-agent models` first** to see your account's available models (availability varies by subscription) + - Accept any model string the user provides + +3. **Max review rounds for the plan?** (default: 10) + - If the user does not provide a value, set `MAX_ROUNDS=10`. + +Store the chosen `REVIEWER_CLI`, `REVIEWER_MODEL`, and `MAX_ROUNDS` for Phase 6 (Iterative Plan Review). + +### Phase 4: Design (REQUIRED SUB-SKILL) + +Invoke `superpowers:brainstorming`, then propose 2-3 approaches and recommend one. + +### Phase 5: Plan (REQUIRED SUB-SKILL) + +Invoke `superpowers:writing-plans`, then break work into milestones and bite-sized stories. +Story IDs: `S-{milestone}{sequence}`. + +### Phase 6: Iterative Plan Review + +Send the plan to the configured reviewer CLI for feedback. Revise and re-submit until approved (default max 10 rounds). + +**Skip this phase entirely if reviewer was set to `skip`.** + +#### Step 1: Generate Session ID + +```bash +REVIEW_ID=$(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) +``` + +Use for temp artifacts: + +- `/tmp/plan-${REVIEW_ID}.md` +- `/tmp/plan-review-${REVIEW_ID}.md` +- `/tmp/plan-review-${REVIEW_ID}.json` (Cursor only) +- `/tmp/plan-review-${REVIEW_ID}.stderr` +- `/tmp/plan-review-${REVIEW_ID}.status` +- `/tmp/plan-review-${REVIEW_ID}.runner.out` +- `/tmp/plan-review-${REVIEW_ID}.sh` + +Resolve the shared reviewer helper from Cursor's installed skills directory: + +```bash +if [ -x .cursor/skills/reviewer-runtime/run-review.sh ]; then + REVIEWER_RUNTIME=.cursor/skills/reviewer-runtime/run-review.sh +else + REVIEWER_RUNTIME=~/.cursor/skills/reviewer-runtime/run-review.sh +fi +``` + +Set helper success-artifact args before writing the command script: + +```bash +HELPER_SUCCESS_FILE_ARGS=() +case "$REVIEWER_CLI" in + codex) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/plan-review-${REVIEW_ID}.md) + ;; + cursor) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/plan-review-${REVIEW_ID}.json) + ;; +esac +``` + +#### Step 2: Write Plan to Temp File + +Write the complete plan (milestones, stories, design decisions, specs) to `/tmp/plan-${REVIEW_ID}.md`. + +#### Review Contract (Applies to Every Round) + +The reviewer response must use this structure: + +```text +## Summary +... + +## Findings +### P0 +- ... +### P1 +- ... +### P2 +- ... +### P3 +- ... + +## Verdict +VERDICT: APPROVED +``` + +Rules: + +- Order findings from `P0` to `P3`. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- Use `- None.` when a severity has no findings. +- `VERDICT: APPROVED` is allowed only when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking. +- The calling agent should still try to fix `P3` findings when they are cheap and safe. + +#### Liveness Contract (Applies While Review Is Running) + +- The shared reviewer runtime emits `state=in-progress note="In progress N"` heartbeats every 60 seconds while the reviewer child is alive. +- The calling agent must keep waiting as long as a fresh `In progress N` heartbeat keeps arriving roughly once per minute. +- Do not abort just because the review is slow, a soft timeout fired, or a `stall-warning` line appears, as long as the `In progress N` heartbeat continues. +- Treat missing heartbeats, `state=failed`, `state=completed-empty-output`, and `state=needs-operator-decision` as escalation signals. + +#### Step 3: Submit to Reviewer (Round 1) + +Write the reviewer invocation to `/tmp/plan-review-${REVIEW_ID}.sh` as a bash script: + +```bash +#!/usr/bin/env bash +set -euo pipefail +``` + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call every round (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "Read the file /tmp/plan-${REVIEW_ID}.md and review. Return exactly the required ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +```bash +codex exec \ + -m ${REVIEWER_MODEL} \ + -s read-only \ + -o /tmp/plan-review-${REVIEW_ID}.md \ + "Review the implementation plan in /tmp/plan-${REVIEW_ID}.md. Focus on: +1. Correctness — Will this plan achieve the stated goals? +2. Risks — What could go wrong? Edge cases? Data loss? +3. Missing steps — Is anything forgotten? +4. Alternatives — Is there a simpler or better approach? +5. Security — Any security concerns? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." +``` + +Do not try to capture the Codex session ID yet. When using the helper, extract it from `/tmp/plan-review-${REVIEW_ID}.runner.out` after the command completes (look for `session id: `), then store it as `CODEX_SESSION_ID` for resume in subsequent rounds. + +**If `REVIEWER_CLI` is `claude`:** + +```bash +claude -p \ + "Review the implementation plan below. Focus on: + +$(cat /tmp/plan-${REVIEW_ID}.md) + +1. Correctness — Will this plan achieve the stated goals? +2. Risks — What could go wrong? Edge cases? Data loss? +3. Missing steps — Is anything forgotten? +4. Alternatives — Is there a simpler or better approach? +5. Security — Any security concerns? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +**If `REVIEWER_CLI` is `cursor`:** + +```bash +cursor-agent -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "Read the file /tmp/plan-${REVIEW_ID}.md and review the implementation plan. Focus on: +1. Correctness — Will this plan achieve the stated goals? +2. Risks — What could go wrong? Edge cases? Data loss? +3. Missing steps — Is anything forgotten? +4. Alternatives — Is there a simpler or better approach? +5. Security — Any security concerns? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." \ + > /tmp/plan-review-${REVIEW_ID}.json +``` + +For `cursor`, the command script writes raw JSON to `/tmp/plan-review-${REVIEW_ID}.json`. Do not run `jq` extraction until after the helper or fallback execution completes. + +Notes on Cursor flags: + +- `--mode=ask` — read-only mode, no file modifications +- `--trust` — trust workspace without prompting (required for non-interactive use) +- `-p` / `--print` — non-interactive mode, output to stdout +- `--output-format json` — structured output with `session_id` and `result` fields + +Run the command script through the shared helper when available: + +```bash +if [ -x "$REVIEWER_RUNTIME" ]; then + "$REVIEWER_RUNTIME" \ + --command-file /tmp/plan-review-${REVIEW_ID}.sh \ + --stdout-file /tmp/plan-review-${REVIEW_ID}.runner.out \ + --stderr-file /tmp/plan-review-${REVIEW_ID}.stderr \ + --status-file /tmp/plan-review-${REVIEW_ID}.status \ + "${HELPER_SUCCESS_FILE_ARGS[@]}" +else + echo "Warning: reviewer runtime helper not found at $REVIEWER_RUNTIME; falling back to direct synchronous review." >&2 + bash /tmp/plan-review-${REVIEW_ID}.sh >/tmp/plan-review-${REVIEW_ID}.runner.out 2>/tmp/plan-review-${REVIEW_ID}.stderr +fi +``` + +Run the helper in the foreground and watch its live stdout for `state=in-progress` heartbeats. If your agent environment buffers command output until exit, start the helper in the background and poll `/tmp/plan-review-${REVIEW_ID}.status` separately instead of treating heartbeats as post-hoc-only data. + +After the command completes: + +- If `REVIEWER_CLI=cursor`, extract the final review text: + +```bash +CURSOR_SESSION_ID=$(jq -r '.session_id' /tmp/plan-review-${REVIEW_ID}.json) +jq -r '.result' /tmp/plan-review-${REVIEW_ID}.json > /tmp/plan-review-${REVIEW_ID}.md +``` + +- If `REVIEWER_CLI=codex`, extract `CODEX_SESSION_ID` from `/tmp/plan-review-${REVIEW_ID}.runner.out` after the helper or fallback run. If the review text is only in `.runner.out`, move or copy the actual review body into `/tmp/plan-review-${REVIEW_ID}.md` before verdict parsing. +- If `REVIEWER_CLI=claude` or `REVIEWER_CLI=pi`, promote stdout captured by the helper or fallback runner into the markdown review file: + +```bash +cp /tmp/plan-review-${REVIEW_ID}.runner.out /tmp/plan-review-${REVIEW_ID}.md +``` + +#### Step 4: Read Review & Check Verdict + +1. Read `/tmp/plan-review-${REVIEW_ID}.md` +2. If the review failed, produced empty output, or reached helper timeout, also read: + - `/tmp/plan-review-${REVIEW_ID}.stderr` + - `/tmp/plan-review-${REVIEW_ID}.status` + - `/tmp/plan-review-${REVIEW_ID}.runner.out` +3. Present review to the user: + +```markdown +## Plan Review — Round N (reviewer: ${REVIEWER_CLI} / ${REVIEWER_MODEL}) + +[Reviewer feedback] +``` + +1. While the reviewer is still running, keep waiting as long as fresh `state=in-progress note="In progress N"` heartbeats continue to appear roughly once per minute. +2. Check verdict: + - **VERDICT: APPROVED** with no `P0`, `P1`, or `P2` findings → proceed to Phase 7 (Initialize workspace) + - **VERDICT: APPROVED** with only `P3` findings → optionally fix the `P3` items if they are cheap and safe, then proceed + - **VERDICT: REVISE** or any `P0`, `P1`, or `P2` finding → go to Step 5 + - No clear verdict but `P0`, `P1`, and `P2` are all `- None.` → treat as approved + - Helper state `completed-empty-output` → treat as failed review attempt, surface stderr/status, fix invocation or prompt handling, then retry + - Helper state `needs-operator-decision` → surface status log and decide whether to extend the timeout, abort, or retry with different helper parameters + - Max rounds (`MAX_ROUNDS`) reached → present the outcome to the user for a manual decision (proceed or stop) + +#### Step 5: Revise the Plan + +Address the reviewer findings in priority order (`P0` → `P1` → `P2`, then `P3` when practical). Update the plan in conversation context and rewrite `/tmp/plan-${REVIEW_ID}.md`. + +Summarize revisions for the user: + +```markdown +### Revisions (Round N) +- [Change and reason, one bullet per issue addressed] +``` + +If a revision contradicts the user's explicit requirements, skip it and note it for the user. + +#### Step 6: Re-submit to Reviewer (Rounds 2-N) + +Rewrite `/tmp/plan-review-${REVIEW_ID}.sh` for the next round. The script should contain the reviewer invocation only; do not run it directly. + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call with prior-round context (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "You previously reviewed this plan and requested revisions. Read the updated payload at /tmp/plan-${REVIEW_ID}.md and re-review using the same ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +Resume the existing session: + +```bash +codex exec resume ${CODEX_SESSION_ID} \ + -o /tmp/plan-review-${REVIEW_ID}.md \ + "I've revised the plan based on your feedback. Updated plan is in /tmp/plan-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." +``` + +If resume fails (session expired), fall back to fresh `codex exec` with context about prior rounds. + +**If `REVIEWER_CLI` is `claude`:** + +Fresh call with accumulated context (Claude CLI has no session resume): + +```bash +claude -p \ + "You previously reviewed an implementation plan and requested revisions. + +Previous feedback summary: [key points from last review] + +I've revised the plan. Updated version is below. + +$(cat /tmp/plan-${REVIEW_ID}.md) + +Changes made: +[List specific changes] + +Re-review the full plan using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +**If `REVIEWER_CLI` is `cursor`:** + +Resume the existing session: + +```bash +cursor-agent --resume ${CURSOR_SESSION_ID} -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "I've revised the plan based on your feedback. Updated plan is in /tmp/plan-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." \ + > /tmp/plan-review-${REVIEW_ID}.json + +jq -r '.result' /tmp/plan-review-${REVIEW_ID}.json > /tmp/plan-review-${REVIEW_ID}.md +``` + +If resume fails, fall back to fresh `cursor-agent -p` with context about prior rounds. + +After updating `/tmp/plan-review-${REVIEW_ID}.sh`, run the same helper/fallback flow from Round 1. + +Return to Step 4. + +#### Step 7: Present Final Result + +```markdown +## Plan Review — Final (reviewer: ${REVIEWER_CLI} / ${REVIEWER_MODEL}) + +**Status:** Approved after N round(s) +[or] +**Status:** Max rounds (`MAX_ROUNDS`) reached — not fully approved + +[Final feedback / remaining concerns] +``` + +#### Step 8: Cleanup + +```bash +rm -f /tmp/plan-${REVIEW_ID}.md \ + /tmp/plan-review-${REVIEW_ID}.md \ + /tmp/plan-review-${REVIEW_ID}.json \ + /tmp/plan-review-${REVIEW_ID}.stderr \ + /tmp/plan-review-${REVIEW_ID}.status \ + /tmp/plan-review-${REVIEW_ID}.runner.out \ + /tmp/plan-review-${REVIEW_ID}.sh +``` + +If the round failed, produced empty output, or reached operator-decision timeout, keep `.stderr`, `.status`, and `.runner.out` until the issue is diagnosed instead of deleting them immediately. + +### Phase 7: Initialize Local Plan Workspace (MANDATORY) + +At project root: + +1. Ensure `ai_plan/` exists. Create it if missing. +2. Ensure `.gitignore` contains `/ai_plan/`. +3. If `.gitignore` was changed, commit that change immediately (local commit only). + +Recommended commit message: + +- `chore(gitignore): ignore ai_plan local planning artifacts` + +### Phase 8: Generate Plan Files (MANDATORY) + +Create `ai_plan/YYYY-MM-DD-/` with all files below: + +1. `original-plan.md` - copy of original planner-generated plan. +2. `final-transcript.md` - copy of final planning transcript used to reach approved plan. +3. `milestone-plan.md` - full implementation spec (from template). +4. `story-tracker.md` - story/milestone status tracker (from template). +5. `continuation-runbook.md` - execution instructions and context (from template). + +Use templates from this skill's `templates/` folder. + +### Phase 9: Handoff + +Always instruct the executing agent: +> Read `ai_plan/YYYY-MM-DD-/continuation-runbook.md` first, then execute from that folder. + +Do not rely on planner-private files during implementation. + +### Phase 10: Telegram Notification (MANDATORY) + +Resolve the Telegram notifier helper from Cursor's installed skills directory: + +```bash +if [ -x .cursor/skills/reviewer-runtime/notify-telegram.sh ]; then + TELEGRAM_NOTIFY_RUNTIME=.cursor/skills/reviewer-runtime/notify-telegram.sh +else + TELEGRAM_NOTIFY_RUNTIME=~/.cursor/skills/reviewer-runtime/notify-telegram.sh +fi +``` + +On every terminal outcome for the create-plan run (approved, max rounds reached, skipped reviewer, or failure), send a Telegram summary if the helper exists and both `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` are configured: + +```bash +if [ -x "$TELEGRAM_NOTIFY_RUNTIME" ] && [ -n "${TELEGRAM_BOT_TOKEN:-}" ] && [ -n "${TELEGRAM_CHAT_ID:-}" ]; then + "$TELEGRAM_NOTIFY_RUNTIME" --message "create-plan completed for : " +fi +``` + +Rules: + +- Telegram is the only supported notification path. Do not use desktop notifications, `say`, email, or any other notifier. +- Notification failures are non-blocking, but they must be surfaced to the user. +- Before stopping for any user interaction, approval, or manual decision, send a Telegram summary first if configured. +- If Telegram is not configured, state that no Telegram notification was sent. + +## Quick Reference + +| Phase | Action | Required Output | +|---|---|---| +| 1 | Analyze codebase/context | Constraints and known patterns | +| 2 | Gather requirements (one question at a time) | Confirmed scope and success criteria | +| 3 | Configure reviewer CLI and model | `REVIEWER_CLI`, `REVIEWER_MODEL`, `MAX_ROUNDS` (or `skip`) | +| 4 | Invoke `superpowers:brainstorming` | Chosen design approach | +| 5 | Invoke `superpowers:writing-plans` | Milestones and bite-sized stories | +| 6 | Iterative plan review (max `MAX_ROUNDS` rounds) | Reviewer approval or max-rounds warning | +| 7 | Initialize `ai_plan/` + `.gitignore` | Local planning workspace ready | +| 8 | Build plan package from templates | Full plan folder with required files | +| 9 | Handoff with runbook-first instruction | Resumable execution context | +| 10 | Send Telegram notification | User notified or notification status reported | + +## Tracker Discipline (MANDATORY) + +Before starting any story: + +1. Open `story-tracker.md` +2. Mark story `in-dev` +3. Add notes if relevant +4. Then begin implementation + +After completing any story: + +1. Mark story `completed` +2. Add commit hash in Notes +3. Review pending stories +4. Update Last Updated and Stories Complete counts + +## Execution Rules to Include in Plan (MANDATORY) + +- Run lint/typecheck/tests after each milestone. +- Prefer linting changed files only for speed. +- Commit locally after each completed milestone (**do not push**). +- Stop and ask user for feedback. +- Apply feedback, rerun checks, and commit again. +- Move to next milestone only after user approval. +- After all milestones are completed and approved, ask permission to push. +- Only after approved push: mark plan as completed. + +## Gitignore Note + +`ai_plan/` is intentionally local and must stay gitignored. Do not treat inability to commit plan-file updates in `ai_plan/` as a problem. + +## Common Mistakes + +- Forgetting `--trust` flag when running `cursor-agent` non-interactively (causes interactive prompt). +- Using `--mode=agent` or `--force` for reviews (reviewer should be read-only, use `--mode=ask`). +- Jumping to implementation planning without running `superpowers:brainstorming` first. +- Asking multiple requirement questions in one message. +- Forgetting to create/update `.gitignore` for `/ai_plan/`. +- Omitting one or more required files in the plan package. +- Handoff without explicit "read runbook first" direction. +- Skipping the reviewer phase without explicit user opt-out. +- Using any notification path other than Telegram. + +## Red Flags - Stop and Correct + +- You started writing milestones before design validation. +- You did not announce which skill you invoked and why. +- You are marking planning complete without all required files. +- Handoff does not explicitly point to `continuation-runbook.md`. +- You are applying a reviewer suggestion that contradicts user requirements. +- Reviewer CLI is running with write permissions (must be read-only). + +## Verification Checklist + +- [ ] `ai_plan/` exists at project root +- [ ] `.gitignore` includes `/ai_plan/` +- [ ] `.gitignore` ignore-rule commit was created if needed +- [ ] Plan directory created under `ai_plan/YYYY-MM-DD-/` +- [ ] Reviewer configured or explicitly skipped +- [ ] Max review rounds confirmed (default: 10) +- [ ] Plan review completed (approved or max rounds) — or skipped +- [ ] `original-plan.md` present +- [ ] `final-transcript.md` present +- [ ] `milestone-plan.md` present +- [ ] `story-tracker.md` present +- [ ] `continuation-runbook.md` present +- [ ] Handoff explicitly says to read runbook first and execute from plan folder +- [ ] Telegram notification attempted if configured + +## Exit Triggers for Question Phase + +User says: "ready", "done", "let's plan", "proceed", "enough questions" diff --git a/skills/create-plan/_source/cursor/templates/continuation-runbook.md b/skills/create-plan/_source/cursor/templates/continuation-runbook.md new file mode 100644 index 0000000..1fac6bf --- /dev/null +++ b/skills/create-plan/_source/cursor/templates/continuation-runbook.md @@ -0,0 +1,144 @@ +# Continuation Runbook: [Plan Title] + +## Reference Files (START HERE) + +Upon resumption, these files in this folder are the ONLY source of truth: + +| File | Purpose | When to Use | +|------|---------|-------------| +| `continuation-runbook.md` | Full context reproduction + execution workflow | Read FIRST | +| `story-tracker.md` | Current progress and status | Check/update BEFORE and AFTER every story | +| `milestone-plan.md` | Complete plan with specifications | Reference implementation details | +| `original-plan.md` | Original approved plan | Reference original intent | +| `final-transcript.md` | Final planning transcript | Reference reasoning/context | + +Do NOT reference planner-private files during implementation. + +## Skill Workflow Guardrails + +- Invoke relevant skills before action using workspace discovery (`.cursor/skills/`). +- Announce which skill is being used and why. +- If a checklist-driven skill applies, track checklist execution explicitly. + +--- + +## Quick Resume Instructions + +1. Read this runbook completely. +2. Check `story-tracker.md`. +3. Find next `pending` story and mark as `in-dev` before starting. +4. Implement the story. +5. Update tracker immediately after each change. + +--- + +## Mandatory Execution Workflow + +Work from this folder (`ai_plan/YYYY-MM-DD-/`) and always follow this order: + +1. Read `continuation-runbook.md` first. +2. Execute stories milestone by milestone. +3. After completing a milestone: + - Run lint/typecheck/tests, prioritizing changed files for speed. + - Commit locally (**DO NOT PUSH**). + - Stop and ask user for feedback. +4. If feedback is provided: + - Apply feedback changes. + - Re-run checks for changed files. + - Commit locally again. + - Ask for milestone approval. +5. Only move to next milestone after explicit approval. +6. After all milestones are completed and approved: + - Ask permission to push. + - If approved, push. + - Mark plan status as `completed`. + +--- + +## Git Note + +`ai_plan/` is intentionally local and must stay gitignored. Do not treat inability to commit plan-file updates inside `ai_plan/` as an error. + +--- + +## Full Context Reproduction + +### Project Overview + +[What this project/feature is about] + +### User Requirements + +[All gathered requirements] + +### Scope + +[In scope / out of scope] + +### Dependencies + +[External dependencies, prerequisites, related systems] + +--- + +## Key Specifications + +### Type Definitions + +```typescript +// Copy-paste ready type definitions +``` + +### Enums & Constants + +```typescript +// All enums/constants needed +``` + +### API Endpoints + +```typescript +// Request/response shapes +``` + +--- + +## Critical Design Decisions + +| Decision | Chosen Approach | Alternatives Rejected | Rationale | +|----------|-----------------|----------------------|-----------| +| [Topic] | [What we chose] | [Other options] | [Why] | + +--- + +## Verification Commands + +### Lint (changed files first) + +```bash +# example: +``` + +### Typecheck + +```bash +# example: +``` + +### Tests (target changed scope first) + +```bash +# example: +``` + +--- + +## File Quick Reference + +| File | Purpose | +|------|---------| +| `original-plan.md` | Original approved plan | +| `final-transcript.md` | Final planning transcript | +| `milestone-plan.md` | Full specification | +| `story-tracker.md` | Current progress tracker | +| `continuation-runbook.md` | This runbook | diff --git a/skills/create-plan/_source/cursor/templates/milestone-plan.md b/skills/create-plan/_source/cursor/templates/milestone-plan.md new file mode 100644 index 0000000..2a04aaa --- /dev/null +++ b/skills/create-plan/_source/cursor/templates/milestone-plan.md @@ -0,0 +1,124 @@ +# [Plan Title] + +## Overview + +- **Goal:** [One sentence describing the end state] +- **Created:** YYYY-MM-DD +- **Status:** In Progress | Complete + +## Planning Guardrails + +- This plan assumes design was validated before implementation planning. +- Skills are invoked via workspace discovery (`.cursor/skills/`). +- Milestones require verification + local commits + explicit approval before proceeding. + +## Context + +### Requirements + +[Gathered requirements from user questions] + +### Constraints + +[Technical, business, or timeline constraints] + +### Success Criteria + +[How we know this is complete] + +## Architecture + +### Design Decisions + +[Key architectural choices and rationale] + +### Component Relationships + +[How pieces fit together] + +### Data Flow + +[How data moves through the system] + +## Milestones + +### M1: [Name] + +**Description:** [What this milestone achieves] + +**Acceptance Criteria:** + +- [ ] [Criterion 1] +- [ ] [Criterion 2] + +**Stories:** S-101, S-102, S-103... + +**Milestone Completion Rule (MANDATORY):** + +- Run lint/typecheck/tests for changed files. +- Commit locally (DO NOT push). +- Stop and ask user for feedback. +- Apply feedback, re-check changed files, commit again. +- Move to next milestone only after user approval. + +--- + +### M2: [Name] + +**Description:** [What this milestone achieves] + +**Acceptance Criteria:** + +- [ ] [Criterion 1] +- [ ] [Criterion 2] + +**Stories:** S-201, S-202, S-203... + +**Milestone Completion Rule (MANDATORY):** + +- Run lint/typecheck/tests for changed files. +- Commit locally (DO NOT push). +- Stop and ask user for feedback. +- Apply feedback, re-check changed files, commit again. +- Move to next milestone only after user approval. + +--- + +## Technical Specifications + +### Types & Interfaces + +```typescript +// Key type definitions (or equivalent language constructs) +``` + +### API Contracts + +```typescript +// Endpoint signatures, request/response shapes (if applicable) +``` + +### Constants & Enums + +```typescript +// Shared constants (if applicable) +``` + +## Files Inventory + +| File | Purpose | Milestone | +|------|---------|-----------| +| `path/to/file.ts` | [What it does] | M1 | +| `path/to/other.ts` | [What it does] | M2 | + +--- + +## Related Plan Files + +This file is part of the plan folder under `ai_plan/`: + +- `original-plan.md` - Original approved plan (reference for original intent) +- `final-transcript.md` - Final planning transcript (reference for rationale/context) +- `milestone-plan.md` - This file (full specification) +- `story-tracker.md` - Status tracking (must be kept up to date) +- `continuation-runbook.md` - Resume/execution context (read first) diff --git a/skills/create-plan/_source/cursor/templates/story-tracker.md b/skills/create-plan/_source/cursor/templates/story-tracker.md new file mode 100644 index 0000000..b08681d --- /dev/null +++ b/skills/create-plan/_source/cursor/templates/story-tracker.md @@ -0,0 +1,78 @@ +# Story Tracker: [Plan Title] + +## Progress Summary + +- **Current Milestone:** M1 +- **Stories Complete:** 0/N +- **Milestones Approved:** 0/M +- **Last Updated:** YYYY-MM-DD + +## Tracking Guardrails + +- Update status immediately when work starts (`in-dev`) and when work completes (`completed`). +- Require explicit user approval at each milestone boundary before continuing. +- Do not push until all milestones are approved and permission is explicitly granted. +- Keep this file and `milestone-plan.md` synchronized. + +--- + +## Milestones + +### M1: [Name] + +| Story | Description | Status | Notes | +|-------|-------------|--------|-------| +| S-101 | [Brief description] | pending | | +| S-102 | [Brief description] | pending | | +| S-103 | [Brief description] | pending | | + +**Approval Status:** pending + +--- + +### M2: [Name] + +| Story | Description | Status | Notes | +|-------|-------------|--------|-------| +| S-201 | [Brief description] | pending | | +| S-202 | [Brief description] | pending | | +| S-203 | [Brief description] | pending | | + +**Approval Status:** pending + +--- + +## Status Legend + +| Status | Meaning | +|--------|---------| +| `pending` | Not started | +| `in-dev` | Currently being worked on | +| `completed` | Done - include commit hash in Notes | +| `deferred` | Postponed - include reason in Notes | + +## Update Instructions (MANDATORY) + +Before starting any story: + +1. Mark story as `in-dev` +2. Update "Last Updated" + +After completing any story: + +1. Mark story as `completed` +2. Add local commit hash to Notes +3. Update "Stories Complete" and "Last Updated" + +At milestone boundary: + +1. Run lint/typecheck/tests for changed files +2. Commit (no push) +3. Request feedback +4. Apply feedback, re-check changed files, commit again +5. Mark milestone **Approval Status: approved** only after user confirms +6. Continue only after approval + +After all milestones approved: + +- Ask permission to push and then mark plan completed. diff --git a/skills/create-plan/_source/opencode/SKILL.md b/skills/create-plan/_source/opencode/SKILL.md new file mode 100644 index 0000000..86eecd8 --- /dev/null +++ b/skills/create-plan/_source/opencode/SKILL.md @@ -0,0 +1,717 @@ +--- +name: create-plan +description: Use when starting a new feature, project, or complex task that needs structured planning with milestones, bite-sized stories, and resumable execution context in Opencode workflows. ALWAYS invoke when user says "create a plan", "make a plan", "plan this", "start planning", or similar planning requests. +--- + +# Create Plan (OpenCode) + +Create and maintain a local plan folder under `ai_plan/` at project root. + +## Prerequisite Check (MANDATORY) + +This OpenCode variant depends on Superpowers skills being available through OpenCode's native skill system. + +Required: + +- Superpowers repo: `https://github.com/obra/superpowers` +- OpenCode Superpowers skills available at `~/.agents/skills/superpowers` or `~/.config/opencode/skills/superpowers` +- `superpowers/brainstorming` +- `superpowers/writing-plans` + +Verify before proceeding: + +```bash +test -f ~/.agents/skills/superpowers/brainstorming/SKILL.md || test -f ~/.config/opencode/skills/superpowers/brainstorming/SKILL.md +test -f ~/.agents/skills/superpowers/writing-plans/SKILL.md || test -f ~/.config/opencode/skills/superpowers/writing-plans/SKILL.md +``` + +If dependencies are missing, stop immediately and return: + +"Missing dependency: OpenCode Superpowers skills are required (`superpowers/brainstorming`, `superpowers/writing-plans`). Install from https://github.com/obra/superpowers (OpenCode setup), then retry." + +## Process + +### Phase 1: Bootstrap Superpowers Context (REQUIRED) + +Use OpenCode's native skill tool: + +- list skills +- verify `superpowers/brainstorming` and `superpowers/writing-plans` are discoverable + +### Phase 2: Analyze + +- Explore the codebase and existing patterns. + +### Phase 3: Gather Requirements + +- Ask questions ONE AT A TIME until user says ready. +- Cover scope, constraints, success criteria, dependencies. +- Summarize before proceeding. + +### Phase 4: Configure Reviewer + +If the user has already specified a reviewer CLI and model (e.g., "create a plan, review with codex o4-mini"), use those values. Otherwise, ask: + +1. **Which CLI should review the plan?** + - `codex` — OpenAI Codex CLI (`codex exec`) + - `claude` — Claude Code CLI (`claude -p`) + - `cursor` — Cursor Agent CLI (`cursor-agent -p`) + - `skip` — No external review, proceed directly to file generation + +2. **Which model?** (only if a CLI was chosen) + - For `codex`: default `o4-mini`, alternatives: `gpt-5.3-codex`, `o3` + - For `claude`: default `sonnet`, alternatives: `opus`, `haiku` + - For `cursor`: **run `cursor-agent models` first** to see your account's available models (availability varies by subscription) + - Accept any model string the user provides + +3. **Max review rounds for the plan?** (default: 10) + - If the user does not provide a value, set `MAX_ROUNDS=10`. + +Store the chosen `REVIEWER_CLI`, `REVIEWER_MODEL`, and `MAX_ROUNDS` for Phase 7 (Iterative Plan Review). + +Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, `pi`, or `skip`. + +If `REVIEWER_CLI=pi`, verify the Pi reviewer binary before entering the review loop: + +```bash +pi --version +``` + +For shorthand `pi/`, split only on the first slash when the prefix is exactly `pi`; store the complete remainder in `REVIEWER_MODEL`. Examples: `pi/claude-opus-4-7` -> `claude-opus-4-7`, `pi/anthropic/claude-opus-4-7` -> `anthropic/claude-opus-4-7`, and `pi/openrouter/anthropic/claude-opus-4-7` -> `openrouter/anthropic/claude-opus-4-7`. + +When `REVIEWER_CLI=pi`, the reviewer model is configured independently from the model running this workflow. If the model/provider is unavailable, surface helper stderr/status and use `pi --list-models [search]` to inspect configured models. + +### Phase 5: Design (REQUIRED SUB-SKILL) + +Use OpenCode's native skill tool to load: + +- `superpowers/brainstorming` + +Then present 2-3 approaches and recommend one. + +### Phase 6: Plan (REQUIRED SUB-SKILL) + +Use OpenCode's native skill tool to load: + +- `superpowers/writing-plans` + +Then break into milestones and bite-sized stories (2-5 min each). +Story IDs: `S-{milestone}{sequence}`. + +### Phase 7: Iterative Plan Review + +Send the plan to the configured reviewer CLI for feedback. Revise and re-submit until approved (default max 10 rounds). + +**Skip this phase entirely if reviewer was set to `skip`.** + +#### Step 1: Generate Session ID + +```bash +REVIEW_ID=$(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) +``` + +Use for temp artifacts: + +- `/tmp/plan-${REVIEW_ID}.md` +- `/tmp/plan-review-${REVIEW_ID}.md` +- `/tmp/plan-review-${REVIEW_ID}.json` (Cursor only) +- `/tmp/plan-review-${REVIEW_ID}.stderr` +- `/tmp/plan-review-${REVIEW_ID}.status` +- `/tmp/plan-review-${REVIEW_ID}.runner.out` +- `/tmp/plan-review-${REVIEW_ID}.sh` + +Resolve the shared reviewer helper from the installed OpenCode skills directory: + +```bash +REVIEWER_RUNTIME=~/.config/opencode/skills/reviewer-runtime/run-review.sh +``` + +Set helper success-artifact args before writing the command script: + +```bash +HELPER_SUCCESS_FILE_ARGS=() +case "$REVIEWER_CLI" in + codex) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/plan-review-${REVIEW_ID}.md) + ;; + cursor) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/plan-review-${REVIEW_ID}.json) + ;; + opencode) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/plan-review-${REVIEW_ID}.md) + ;; +esac +``` + +#### Step 2: Write Plan to Temp File + +Write the complete plan (milestones, stories, design decisions, specs) to `/tmp/plan-${REVIEW_ID}.md`. + +#### Review Contract (Applies to Every Round) + +The reviewer response must use this structure: + +```text +## Summary +... + +## Findings +### P0 +- ... +### P1 +- ... +### P2 +- ... +### P3 +- ... + +## Verdict +VERDICT: APPROVED +``` + +Rules: + +- Order findings from `P0` to `P3`. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- Use `- None.` when a severity has no findings. +- `VERDICT: APPROVED` is allowed only when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking. +- The calling agent should still try to fix `P3` findings when they are cheap and safe. + +#### Liveness Contract (Applies While Review Is Running) + +- The shared reviewer runtime emits `state=in-progress note="In progress N"` heartbeats every 60 seconds while the reviewer child is alive. +- The calling agent must keep waiting as long as a fresh `In progress N` heartbeat keeps arriving roughly once per minute. +- Do not abort just because the review is slow, a soft timeout fired, or a `stall-warning` line appears, as long as the `In progress N` heartbeat continues. +- Treat missing heartbeats, `state=failed`, `state=completed-empty-output`, and `state=needs-operator-decision` as escalation signals. + +#### Step 3: Submit to Reviewer (Round 1) + +Write the reviewer invocation to `/tmp/plan-review-${REVIEW_ID}.sh` as a bash script: + +```bash +#!/usr/bin/env bash +set -euo pipefail +``` + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call every round (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "Read the file /tmp/plan-${REVIEW_ID}.md and review. Return exactly the required ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +```bash +codex exec \ + -m ${REVIEWER_MODEL} \ + -s read-only \ + -o /tmp/plan-review-${REVIEW_ID}.md \ + "Review the implementation plan in /tmp/plan-${REVIEW_ID}.md. Focus on: +1. Correctness — Will this plan achieve the stated goals? +2. Risks — What could go wrong? Edge cases? Data loss? +3. Missing steps — Is anything forgotten? +4. Alternatives — Is there a simpler or better approach? +5. Security — Any security concerns? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." +``` + +Do not try to capture the Codex session ID yet. When using the helper, extract it from `/tmp/plan-review-${REVIEW_ID}.runner.out` after the command completes (look for `session id: `), then store it as `CODEX_SESSION_ID` for resume in subsequent rounds. + +**If `REVIEWER_CLI` is `claude`:** + +```bash +claude -p \ + "Review the implementation plan below. Focus on: + +$(cat /tmp/plan-${REVIEW_ID}.md) + +1. Correctness — Will this plan achieve the stated goals? +2. Risks — What could go wrong? Edge cases? Data loss? +3. Missing steps — Is anything forgotten? +4. Alternatives — Is there a simpler or better approach? +5. Security — Any security concerns? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +**If `REVIEWER_CLI` is `cursor`:** + +```bash +cursor-agent -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "Read the file /tmp/plan-${REVIEW_ID}.md and review the implementation plan. Focus on: +1. Correctness — Will this plan achieve the stated goals? +2. Risks — What could go wrong? Edge cases? Data loss? +3. Missing steps — Is anything forgotten? +4. Alternatives — Is there a simpler or better approach? +5. Security — Any security concerns? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." \ + > /tmp/plan-review-${REVIEW_ID}.json +``` + +For `cursor`, the command script writes raw JSON to `/tmp/plan-review-${REVIEW_ID}.json`. Do not run `jq` extraction until after the helper or fallback execution completes. If `jq` is not installed, inform the user: `brew install jq` (macOS) or equivalent. + +**If `REVIEWER_CLI` is `opencode`:** + +OpenCode uses `--agent plan` for read-oriented review. Fresh call is the recommended default. + +Round 1: + +```bash +opencode run \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "Read the file /tmp/plan-${REVIEW_ID}.md and review the implementation plan. Focus on: +1. Correctness — Will this plan achieve the stated goals? +2. Risks — What could go wrong? Edge cases? Data loss? +3. Missing steps — Is anything forgotten? +4. Alternatives — Is there a simpler or better approach? +5. Security — Any security concerns? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use \`- None.\` when a severity has no findings. +- \`P0\` = total blocker, \`P1\` = major risk, \`P2\` = must-fix before approval, \`P3\` = cosmetic / nice to have. +- End with exactly one verdict line: \`VERDICT: APPROVED\` or \`VERDICT: REVISE\` +- \`VERDICT: APPROVED\` is allowed only when there are no \`P0\`, \`P1\`, or \`P2\` findings. \`P3\` findings are non-blocking." \ + > /tmp/plan-review-${REVIEW_ID}.json +``` + +Round 2 and later (fresh-call, recommended default): + +```bash +opencode run \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "You previously reviewed this plan and requested revisions. + +Previous feedback summary: [key points from last review] + +I've revised. Updated payload is in /tmp/plan-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same ## Summary, ## Findings, and ## Verdict structure as before." \ + > /tmp/plan-review-${REVIEW_ID}.json +``` + +Extract the review body: + +```bash +jq -r '.[] | select(.type == "message" and .role == "assistant") | .content' \ + /tmp/plan-review-${REVIEW_ID}.json \ + > /tmp/plan-review-${REVIEW_ID}.md \ + || cp /tmp/plan-review-${REVIEW_ID}.json /tmp/plan-review-${REVIEW_ID}.md +``` + +If the JSON parse falls through, promote the raw JSON file as the review output. On any opencode +CLI or JSON parsing failure, treat this loop round as `completed-empty-output` and follow the +helper-failure escalation in Step 4. + +Run the command script through the shared helper when available: + +```bash +if [ -x "$REVIEWER_RUNTIME" ]; then + "$REVIEWER_RUNTIME" \ + --command-file /tmp/plan-review-${REVIEW_ID}.sh \ + --stdout-file /tmp/plan-review-${REVIEW_ID}.runner.out \ + --stderr-file /tmp/plan-review-${REVIEW_ID}.stderr \ + --status-file /tmp/plan-review-${REVIEW_ID}.status \ + "${HELPER_SUCCESS_FILE_ARGS[@]}" +else + echo "Warning: reviewer runtime helper not found at $REVIEWER_RUNTIME; falling back to direct synchronous review." >&2 + bash /tmp/plan-review-${REVIEW_ID}.sh >/tmp/plan-review-${REVIEW_ID}.runner.out 2>/tmp/plan-review-${REVIEW_ID}.stderr +fi +``` + +Run the helper in the foreground and watch its live stdout for `state=in-progress` heartbeats. If your agent environment buffers command output until exit, start the helper in the background and poll `/tmp/plan-review-${REVIEW_ID}.status` separately instead of treating heartbeats as post-hoc-only data. + +After the command completes: + +- If `REVIEWER_CLI=cursor`, extract the final review text: + +```bash +CURSOR_SESSION_ID=$(jq -r '.session_id' /tmp/plan-review-${REVIEW_ID}.json) +jq -r '.result' /tmp/plan-review-${REVIEW_ID}.json > /tmp/plan-review-${REVIEW_ID}.md +``` + +- If `REVIEWER_CLI=codex`, extract `CODEX_SESSION_ID` from `/tmp/plan-review-${REVIEW_ID}.runner.out` after the helper or fallback run. If the review text is only in `.runner.out`, move or copy the actual review body into `/tmp/plan-review-${REVIEW_ID}.md` before verdict parsing. +- If `REVIEWER_CLI=opencode`, the `jq` extraction above covers output capture. If it falls through, copy runner output: `cp /tmp/plan-review-${REVIEW_ID}.runner.out /tmp/plan-review-${REVIEW_ID}.md`. On Round 1, also attempt to capture the session id for optional use in subsequent rounds: `OPENCODE_SESSION_ID=$(jq -r 'if type == "array" then (.[0] | (.id? // .session_id?)) else (.id? // .session_id?) end // empty' /tmp/plan-review-${REVIEW_ID}.json 2>/dev/null || true)` +- If `REVIEWER_CLI=claude` or `REVIEWER_CLI=pi`, promote stdout captured by the helper or fallback runner into the markdown review file: + +```bash +cp /tmp/plan-review-${REVIEW_ID}.runner.out /tmp/plan-review-${REVIEW_ID}.md +``` + +#### Step 4: Read Review & Check Verdict + +1. Read `/tmp/plan-review-${REVIEW_ID}.md` +2. If the review failed, produced empty output, or reached helper timeout, also read: + - `/tmp/plan-review-${REVIEW_ID}.stderr` + - `/tmp/plan-review-${REVIEW_ID}.status` + - `/tmp/plan-review-${REVIEW_ID}.runner.out` +3. Present review to the user: + +```markdown +## Plan Review — Round N (reviewer: ${REVIEWER_CLI} / ${REVIEWER_MODEL}) + +[Reviewer feedback] +``` + +1. While the reviewer is still running, keep waiting as long as fresh `state=in-progress note="In progress N"` heartbeats continue to appear roughly once per minute. +2. Check verdict: + - **VERDICT: APPROVED** with no `P0`, `P1`, or `P2` findings → proceed to Phase 8 (Initialize workspace) + - **VERDICT: APPROVED** with only `P3` findings → optionally fix the `P3` items if they are cheap and safe, then proceed + - **VERDICT: REVISE** or any `P0`, `P1`, or `P2` finding → go to Step 5 + - No clear verdict but `P0`, `P1`, and `P2` are all `- None.` → treat as approved + - Helper state `completed-empty-output` → treat as failed review attempt, surface stderr/status, fix invocation or prompt handling, then retry + - Helper state `needs-operator-decision` → surface status log and decide whether to extend the timeout, abort, or retry with different helper parameters + - Max rounds (`MAX_ROUNDS`) reached → present the outcome to the user for a manual decision (proceed or stop) + +#### Step 5: Revise the Plan + +Address the reviewer findings in priority order (`P0` → `P1` → `P2`, then `P3` when practical). Update the plan in conversation context and rewrite `/tmp/plan-${REVIEW_ID}.md`. + +Summarize revisions for the user: + +```markdown +### Revisions (Round N) +- [Change and reason, one bullet per issue addressed] +``` + +If a revision contradicts the user's explicit requirements, skip it and note it for the user. + +#### Step 6: Re-submit to Reviewer (Rounds 2-N) + +Rewrite `/tmp/plan-review-${REVIEW_ID}.sh` for the next round. The script should contain the reviewer invocation only; do not run it directly. + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call with prior-round context (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "You previously reviewed this plan and requested revisions. Read the updated payload at /tmp/plan-${REVIEW_ID}.md and re-review using the same ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +Resume the existing session: + +```bash +codex exec resume ${CODEX_SESSION_ID} \ + -o /tmp/plan-review-${REVIEW_ID}.md \ + "I've revised the plan based on your feedback. Updated plan is in /tmp/plan-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." +``` + +If resume fails (session expired), fall back to fresh `codex exec` with context about prior rounds. + +**If `REVIEWER_CLI` is `claude`:** + +Fresh call with accumulated context (Claude CLI has no session resume): + +```bash +claude -p \ + "You previously reviewed an implementation plan and requested revisions. + +Previous feedback summary: [key points from last review] + +I've revised the plan. Updated version is below. + +$(cat /tmp/plan-${REVIEW_ID}.md) + +Changes made: +[List specific changes] + +Re-review the full plan using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +**If `REVIEWER_CLI` is `cursor`:** + +Resume the existing session: + +```bash +cursor-agent --resume ${CURSOR_SESSION_ID} -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "I've revised the plan based on your feedback. Updated plan is in /tmp/plan-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." \ + > /tmp/plan-review-${REVIEW_ID}.json + +jq -r '.result' /tmp/plan-review-${REVIEW_ID}.json > /tmp/plan-review-${REVIEW_ID}.md +``` + +If resume fails, fall back to fresh `cursor-agent -p` with context about prior rounds. + +**If `REVIEWER_CLI` is `opencode`:** + +Fresh call (recommended default — opencode has no guaranteed stable session ID in headless mode): + +```bash +opencode run \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "You previously reviewed this plan and requested revisions. + +Previous feedback summary: [key points from last review] + +I've revised. Updated payload is in /tmp/plan-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same \`## Summary\`, \`## Findings\`, and \`## Verdict\` structure as before. +Keep findings ordered \`P0\` to \`P3\`, use \`- None.\` when a severity has no findings, and only use \`VERDICT: APPROVED\` when no \`P0\`, \`P1\`, or \`P2\` findings remain. \`P3\` findings are non-blocking." \ + > /tmp/plan-review-${REVIEW_ID}.json + +jq -r '.[] | select(.type == "message" and .role == "assistant") | .content' \ + /tmp/plan-review-${REVIEW_ID}.json \ + > /tmp/plan-review-${REVIEW_ID}.md \ + || cp /tmp/plan-review-${REVIEW_ID}.json /tmp/plan-review-${REVIEW_ID}.md +``` + +Optional session-resume path (only if `OPENCODE_SESSION_ID` was captured on Round 1 and your installed opencode accepts `-s ` reliably in headless mode): + +```bash +opencode run \ + -s ${OPENCODE_SESSION_ID} \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "I've revised the plan based on your feedback. Updated plan is in /tmp/plan-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same \`## Summary\`, \`## Findings\`, and \`## Verdict\` structure as before. +Keep findings ordered \`P0\` to \`P3\`, use \`- None.\` when a severity has no findings, and only use \`VERDICT: APPROVED\` when no \`P0\`, \`P1\`, or \`P2\` findings remain. \`P3\` findings are non-blocking." \ + > /tmp/plan-review-${REVIEW_ID}.json + +jq -r '.[] | select(.type == "message" and .role == "assistant") | .content' \ + /tmp/plan-review-${REVIEW_ID}.json \ + > /tmp/plan-review-${REVIEW_ID}.md \ + || cp /tmp/plan-review-${REVIEW_ID}.json /tmp/plan-review-${REVIEW_ID}.md +``` + +If session resume fails (session expired or not supported), fall back to the fresh-call path above. + +After updating `/tmp/plan-review-${REVIEW_ID}.sh`, run the same helper/fallback flow from Round 1. + +Return to Step 4. + +#### Step 7: Present Final Result + +```markdown +## Plan Review — Final (reviewer: ${REVIEWER_CLI} / ${REVIEWER_MODEL}) + +**Status:** Approved after N round(s) +[or] +**Status:** Max rounds (`MAX_ROUNDS`) reached — not fully approved + +[Final feedback / remaining concerns] +``` + +#### Step 8: Cleanup + +```bash +rm -f /tmp/plan-${REVIEW_ID}.md \ + /tmp/plan-review-${REVIEW_ID}.md \ + /tmp/plan-review-${REVIEW_ID}.json \ + /tmp/plan-review-${REVIEW_ID}.stderr \ + /tmp/plan-review-${REVIEW_ID}.status \ + /tmp/plan-review-${REVIEW_ID}.runner.out \ + /tmp/plan-review-${REVIEW_ID}.sh +``` + +If the round failed, produced empty output, or reached operator-decision timeout, keep `.stderr`, `.status`, and `.runner.out` until the issue is diagnosed instead of deleting them immediately. + +### Phase 8: Initialize Local Plan Workspace (MANDATORY) + +At project root: + +1. Ensure `ai_plan/` exists. Create it if missing. +2. Ensure `.gitignore` contains `/ai_plan/`. +3. If `.gitignore` was changed, commit that change immediately (local commit only). + +Recommended commit message: + +- `chore(gitignore): ignore ai_plan local planning artifacts` + +### Phase 9: Generate Plan Files (MANDATORY) + +Create `ai_plan/YYYY-MM-DD-/` with ALL files: + +1. `original-plan.md` - copy of original planner-generated plan. +2. `final-transcript.md` - copy of final planning transcript used to reach approved plan. +3. `milestone-plan.md` - full implementation spec (template-based). +4. `story-tracker.md` - status tracking (template-based, all stories start as `pending`). +5. `continuation-runbook.md` - resume context and execution protocol (template-based). + +Use templates from this skill's `templates/` folder. + +### Phase 10: Handoff + +Always instruct the executing agent: +> Read `ai_plan/YYYY-MM-DD-/continuation-runbook.md` first, then execute from `ai_plan` files only. + +### Phase 11: Telegram Notification (MANDATORY) + +Resolve the Telegram notifier helper from the installed OpenCode skills directory: + +```bash +TELEGRAM_NOTIFY_RUNTIME=~/.config/opencode/skills/reviewer-runtime/notify-telegram.sh +``` + +On every terminal outcome for the create-plan run (approved, max rounds reached, skipped reviewer, or failure), send a Telegram summary if the helper exists and both `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` are configured: + +```bash +if [ -x "$TELEGRAM_NOTIFY_RUNTIME" ] && [ -n "${TELEGRAM_BOT_TOKEN:-}" ] && [ -n "${TELEGRAM_CHAT_ID:-}" ]; then + "$TELEGRAM_NOTIFY_RUNTIME" --message "create-plan completed for : " +fi +``` + +Rules: + +- Telegram is the only supported notification path. Do not use desktop notifications, `say`, email, or any other notifier. +- Notification failures are non-blocking, but they must be surfaced to the user. +- Before stopping for any user interaction, approval, or manual decision, send a Telegram summary first if configured. +- If Telegram is not configured, state that no Telegram notification was sent. + +## Tracker Discipline (MANDATORY) + +Before starting any story: + +1. Open `story-tracker.md` +2. Mark story `in-dev` +3. Add notes if relevant +4. Then begin implementation + +After completing any story: + +1. Mark story `completed` +2. Add commit hash in Notes +3. Review pending stories +4. Update Last Updated and Stories Complete counts + +## Execution Rules to Include in Plan (MANDATORY) + +- Run lint/typecheck/tests after each milestone. +- Prefer linting changed files only for speed. +- Commit locally after each completed milestone (**do not push**). +- Stop and ask user for feedback. +- Apply feedback, rerun checks, and commit again. +- Move to next milestone only after user approval. +- After all milestones are completed and approved, ask permission to push. +- Only after approved push: mark plan as completed. + +## Gitignore Note + +`ai_plan/` is intentionally local and must stay gitignored. Do not treat inability to commit plan-file updates in `ai_plan/` as a problem. + +## Verification Checklist + +- [ ] `ai_plan/` exists at project root +- [ ] `.gitignore` includes `/ai_plan/` +- [ ] `.gitignore` ignore-rule commit was created if needed +- [ ] Plan directory created under `ai_plan/YYYY-MM-DD-/` +- [ ] Reviewer configured or explicitly skipped +- [ ] Max review rounds confirmed (default: 10) +- [ ] Plan review completed (approved or max rounds) — or skipped +- [ ] `original-plan.md` present +- [ ] `final-transcript.md` present +- [ ] `milestone-plan.md` present +- [ ] `story-tracker.md` created with all stories as `pending` +- [ ] `continuation-runbook.md` present +- [ ] Handoff explicitly says to read runbook first and execute from plan folder +- [ ] Telegram notification attempted if configured + +## Exit Triggers for Question Phase + +User says: "ready", "done", "let's plan", "proceed", "enough questions" diff --git a/skills/create-plan/_source/opencode/templates/continuation-runbook.md b/skills/create-plan/_source/opencode/templates/continuation-runbook.md new file mode 100644 index 0000000..11ca6e7 --- /dev/null +++ b/skills/create-plan/_source/opencode/templates/continuation-runbook.md @@ -0,0 +1,138 @@ +# Continuation Runbook: [Plan Title] + +## Reference Files (START HERE) + +Upon resumption, these files in this folder are the ONLY source of truth: + +| File | Purpose | When to Use | +|------|---------|-------------| +| `continuation-runbook.md` | Full context reproduction + execution workflow | Read FIRST | +| `story-tracker.md` | Current progress and status | Check/update BEFORE and AFTER every story | +| `milestone-plan.md` | Complete plan with specifications | Reference implementation details | +| `original-plan.md` | Original approved plan | Reference original intent | +| `final-transcript.md` | Final planning transcript | Reference reasoning/context | + +Do NOT reference planner-private files during implementation. + +--- + +## Quick Resume Instructions + +1. Read this runbook completely. +2. Check `story-tracker.md`. +3. Find next `pending` story and mark as `in-dev` before starting. +4. Implement the story. +5. Update tracker immediately after each change. + +--- + +## Mandatory Execution Workflow + +Work from this folder (`ai_plan/YYYY-MM-DD-/`) and always follow this order: + +1. Read `continuation-runbook.md` first. +2. Execute stories milestone by milestone. +3. After completing a milestone: + - Run lint/typecheck/tests, prioritizing changed files for speed. + - Commit locally (**DO NOT PUSH**). + - Stop and ask user for feedback. +4. If feedback is provided: + - Apply feedback changes. + - Re-run checks for changed files. + - Commit locally again. + - Ask for milestone approval. +5. Only move to next milestone after explicit approval. +6. After all milestones are completed and approved: + - Ask permission to push. + - If approved, push. + - Mark plan status as `completed`. + +--- + +## Git Note + +`ai_plan/` is intentionally local and must stay gitignored. Do not treat inability to commit plan-file updates inside `ai_plan/` as an error. + +--- + +## Full Context Reproduction + +### Project Overview + +[What this project/feature is about] + +### User Requirements + +[All gathered requirements] + +### Scope + +[In scope / out of scope] + +### Dependencies + +[External dependencies, prerequisites, related systems] + +--- + +## Key Specifications + +### Type Definitions + +```typescript +// Copy-paste ready type definitions +``` + +### Enums & Constants + +```typescript +// All enums/constants needed +``` + +### API Endpoints + +```typescript +// Request/response shapes +``` + +--- + +## Critical Design Decisions + +| Decision | Chosen Approach | Alternatives Rejected | Rationale | +|----------|-----------------|----------------------|-----------| +| [Topic] | [What we chose] | [Other options] | [Why] | + +--- + +## Verification Commands + +### Lint (changed files first) + +```bash +# example: pnpm eslint +``` + +### Typecheck + +```bash +# example: pnpm tsc --noEmit +``` + +### Tests (target changed scope first) + +```bash +# example: pnpm test -- +``` + +--- + +## File Quick Reference + +| File | Purpose | +|------|---------| +| `original-plan.md` | Original approved plan | +| `final-transcript.md` | Final planning transcript | +| `milestone-plan.md` | Full specification | +| `story-tracker.md` | Current progress tracker | +| `continuation-runbook.md` | This runbook | diff --git a/skills/create-plan/_source/opencode/templates/milestone-plan.md b/skills/create-plan/_source/opencode/templates/milestone-plan.md new file mode 100644 index 0000000..f646b48 --- /dev/null +++ b/skills/create-plan/_source/opencode/templates/milestone-plan.md @@ -0,0 +1,118 @@ +# [Plan Title] + +## Overview + +- **Goal:** [One sentence describing the end state] +- **Created:** YYYY-MM-DD +- **Status:** In Progress | Complete + +## Context + +### Requirements + +[Gathered requirements from user questions] + +### Constraints + +[Technical, business, or timeline constraints] + +### Success Criteria + +[How we know this is complete] + +## Architecture + +### Design Decisions + +[Key architectural choices and rationale] + +### Component Relationships + +[How pieces fit together] + +### Data Flow + +[How data moves through the system] + +## Milestones + +### M1: [Name] + +**Description:** [What this milestone achieves] + +**Acceptance Criteria:** + +- [ ] [Criterion 1] +- [ ] [Criterion 2] + +**Stories:** S-101, S-102, S-103... + +**Milestone Completion Rule (MANDATORY):** + +- Run lint/typecheck/tests for changed files. +- Commit locally (DO NOT push). +- Stop and ask user for feedback. +- Apply feedback, re-check changed files, commit again. +- Move to next milestone only after user approval. + +--- + +### M2: [Name] + +**Description:** [What this milestone achieves] + +**Acceptance Criteria:** + +- [ ] [Criterion 1] +- [ ] [Criterion 2] + +**Stories:** S-201, S-202, S-203... + +**Milestone Completion Rule (MANDATORY):** + +- Run lint/typecheck/tests for changed files. +- Commit locally (DO NOT push). +- Stop and ask user for feedback. +- Apply feedback, re-check changed files, commit again. +- Move to next milestone only after user approval. + +--- + +## Technical Specifications + +### Types & Interfaces + +```typescript +// Key type definitions +``` + +### API Contracts + +```typescript +// Endpoint signatures, request/response shapes +``` + +### Constants & Enums + +```typescript +// Shared constants +``` + +## Files Inventory + +| File | Purpose | Milestone | +|------|---------|-----------| +| `path/to/file.ts` | [What it does] | M1 | +| `path/to/other.ts` | [What it does] | M2 | + +--- + +## Related Plan Files + +This file is part of the plan folder under `ai_plan/`: + +- `original-plan.md` - Original approved plan (reference for original intent) +- `final-transcript.md` - Final planning transcript (reference for rationale/context) +- `milestone-plan.md` - This file (full specification) +- `story-tracker.md` - Status tracking (must be kept up to date) +- `continuation-runbook.md` - Resume/execution context (read first) diff --git a/skills/create-plan/_source/opencode/templates/story-tracker.md b/skills/create-plan/_source/opencode/templates/story-tracker.md new file mode 100644 index 0000000..08dbd3a --- /dev/null +++ b/skills/create-plan/_source/opencode/templates/story-tracker.md @@ -0,0 +1,71 @@ +# Story Tracker: [Plan Title] + +## Progress Summary + +- **Current Milestone:** M1 +- **Stories Complete:** 0/N +- **Milestones Approved:** 0/M +- **Last Updated:** YYYY-MM-DD + +--- + +## Milestones + +### M1: [Name] + +| Story | Description | Status | Notes | +|-------|-------------|--------|-------| +| S-101 | [Brief description] | pending | | +| S-102 | [Brief description] | pending | | +| S-103 | [Brief description] | pending | | + +**Approval Status:** pending + +--- + +### M2: [Name] + +| Story | Description | Status | Notes | +|-------|-------------|--------|-------| +| S-201 | [Brief description] | pending | | +| S-202 | [Brief description] | pending | | +| S-203 | [Brief description] | pending | | + +**Approval Status:** pending + +--- + +## Status Legend + +| Status | Meaning | +|--------|---------| +| `pending` | Not started | +| `in-dev` | Currently being worked on | +| `completed` | Done - include commit hash in Notes | +| `deferred` | Postponed - include reason in Notes | + +## Update Instructions (MANDATORY) + +Before starting any story: + +1. Mark story as `in-dev` +2. Update "Last Updated" + +After completing any story: + +1. Mark story as `completed` +2. Add local commit hash to Notes +3. Update "Stories Complete" and "Last Updated" + +At milestone boundary: + +1. Run lint/typecheck/tests for changed files +2. Commit (no push) +3. Request feedback +4. Apply feedback, re-check changed files, commit again +5. Mark milestone **Approval Status: approved** only after user confirms +6. Continue only after approval + +After all milestones approved: + +- Ask permission to push and then mark plan completed. diff --git a/skills/create-plan/_source/pi/SKILL.md b/skills/create-plan/_source/pi/SKILL.md new file mode 100644 index 0000000..3af2db2 --- /dev/null +++ b/skills/create-plan/_source/pi/SKILL.md @@ -0,0 +1,234 @@ +--- +name: create-plan +description: Use when a user asks to create or maintain a structured implementation plan in pi, including milestones, bite-sized stories, and resumable local planning artifacts under ai_plan. +--- + +# Create Plan (Pi) + +Create and maintain a local plan workspace under `ai_plan/` at project root. + +## Shared Setup + +Before using this skill, read: + +- [docs/PI-SUPERPOWERS.md](../../../docs/PI-SUPERPOWERS.md) +- [docs/PI-COMMON-REVIEWER.md](../../../docs/PI-COMMON-REVIEWER.md) + +The workflow depends on: + +- Obra Superpowers skills being visible to pi +- the pi reviewer-runtime helper being installed in a supported location + +## Prerequisite Check (MANDATORY) + +Required: + +- `pi --version` +- Superpowers `brainstorming` +- Superpowers `writing-plans` +- pi reviewer runtime helper: + - `.pi/skills/reviewer-runtime/pi/run-review.sh`, or + - `~/.pi/agent/skills/reviewer-runtime/pi/run-review.sh` + +Quick checks for common installs: + +```bash +pi --version +test -f ~/.agents/skills/superpowers/brainstorming/SKILL.md || test -f ~/.pi/agent/skills/superpowers/brainstorming/SKILL.md +test -f ~/.agents/skills/superpowers/writing-plans/SKILL.md || test -f ~/.pi/agent/skills/superpowers/writing-plans/SKILL.md +test -x .pi/skills/reviewer-runtime/pi/run-review.sh || test -x ~/.pi/agent/skills/reviewer-runtime/pi/run-review.sh +``` + +If you use a settings-defined skill path for Superpowers, verify it matches [docs/PI-SUPERPOWERS.md](../../../docs/PI-SUPERPOWERS.md) before continuing. + +If you install the reviewer helper in a nonstandard location, verify it matches [docs/PI-COMMON-REVIEWER.md](../../../docs/PI-COMMON-REVIEWER.md) before continuing. + +If any dependency is missing, stop and return: + +`Missing dependency: pi planning requires Superpowers brainstorming/writing-plans skills plus the reviewer setup documented in docs/PI-SUPERPOWERS.md and docs/PI-COMMON-REVIEWER.md.` + +## Required Workflow Rules + +- Load the relevant workflow skill before entering its phase. If pi did not auto-load it, use `/skill:brainstorming` or `/skill:writing-plans`. +- Announce skill usage explicitly: + - `I've read the [Skill Name] skill and I'm using it to [purpose].` +- Track checklist-style progress inside the plan artifacts that this skill generates. +- Do not use deprecated wrapper CLIs. + +## Process + +### Phase 1: Analyze + +- Explore the codebase and existing patterns. +- Review any current docs, scripts, or variant layouts that affect the plan. + +### Phase 2: Gather Requirements + +- Ask questions one at a time until the scope is clear. +- Confirm constraints, success criteria, dependencies, and what is out of scope. + +### Phase 3: Configure Reviewer + +If the user already specified a reviewer CLI and model, use those values. Otherwise ask: + +Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, `pi`, or `skip` + +1. Which CLI should review the plan? + - `codex` + - `claude` + - `cursor` + - `opencode` + - `pi` + - `skip` +2. Which model? +3. Max review rounds? Default: `10` + +Store `REVIEWER_CLI`, `REVIEWER_MODEL`, and `MAX_ROUNDS` for the review loop. + +If `REVIEWER_CLI=pi`, verify the Pi reviewer binary before entering the review loop: + +```bash +pi --version +``` + +For shorthand `pi/`, split only on the first slash when the prefix is exactly `pi`; store the complete remainder in `REVIEWER_MODEL`. Examples: `pi/claude-opus-4-7` -> `claude-opus-4-7`, `pi/anthropic/claude-opus-4-7` -> `anthropic/claude-opus-4-7`, and `pi/openrouter/anthropic/claude-opus-4-7` -> `openrouter/anthropic/claude-opus-4-7`. + +When `REVIEWER_CLI=pi`, the reviewer model is configured independently from the pi model running this workflow. Use any configured pi model string, including provider-qualified model IDs. If the reviewer model or provider is unavailable, surface the review helper stderr/status and ask for a configured model; use `pi --list-models [search]` to inspect configured models. + +The pi reviewer command rendered into `/tmp/plan-review-${REVIEW_ID}.sh` must be isolated and read-only: + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files --model "$REVIEWER_MODEL" --tools read,grep,find,ls -p "Read the file /tmp/plan-${REVIEW_ID}.md and review." +``` + +The pi reviewer invocation must not load workflow skills and must not include `write`, `edit`, or `bash` tools. + +### Phase 4: Design + +- Load `brainstorming`. +- Present 2-3 approaches and recommend one. +- Resolve open design questions before the milestone breakdown. + +### Phase 5: Plan + +- Load `writing-plans`. +- Break the work into milestones and bite-sized stories. +- Story IDs should use the `S-101`, `S-102` style. + +### Phase 6: Iterative Plan Review + +Skip this phase if `REVIEWER_CLI=skip`. + +#### Step 1: Generate Session ID + +```bash +REVIEW_ID=$(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) +``` + +Use these temp artifacts: + +- `/tmp/plan-${REVIEW_ID}.md` +- `/tmp/plan-review-${REVIEW_ID}.md` +- `/tmp/plan-review-${REVIEW_ID}.json` +- `/tmp/plan-review-${REVIEW_ID}.stderr` +- `/tmp/plan-review-${REVIEW_ID}.status` +- `/tmp/plan-review-${REVIEW_ID}.runner.out` +- `/tmp/plan-review-${REVIEW_ID}.sh` + +Resolve the pi reviewer runtime helper in this order: + +```bash +REVIEWER_RUNTIME="" +for candidate in ".pi/skills/reviewer-runtime/pi/run-review.sh" "$HOME/.pi/agent/skills/reviewer-runtime/pi/run-review.sh"; do + if [ -x "$candidate" ]; then + REVIEWER_RUNTIME="$candidate" + break + fi +done +``` + +#### Step 2: Write The Plan Payload + +Write the full plan to `/tmp/plan-${REVIEW_ID}.md`. + +Reviewer responses must use this structure: + +```text +## Summary +... + +## Findings +### P0 +- ... +### P1 +- ... +### P2 +- ... +### P3 +- ... + +## Verdict +VERDICT: APPROVED +``` + +Rules: + +- Order findings from `P0` to `P3` +- Use `- None.` when a severity has no findings +- `VERDICT: APPROVED` is valid only when no `P0`, `P1`, or `P2` findings remain + +#### Step 3: Submit To Reviewer + +Build a bash command script in `/tmp/plan-review-${REVIEW_ID}.sh` and execute it through the shared helper when present: + +```bash +"$REVIEWER_RUNTIME" \ + --command-file /tmp/plan-review-${REVIEW_ID}.sh \ + --stdout-file /tmp/plan-review-${REVIEW_ID}.runner.out \ + --stderr-file /tmp/plan-review-${REVIEW_ID}.stderr \ + --status-file /tmp/plan-review-${REVIEW_ID}.status +``` + +Fallback to direct execution only if the helper is missing. + +#### Step 4: Wait And Parse Verdict + +- Keep waiting while fresh `state=in-progress note="In progress N"` heartbeats continue to appear. +- Treat `P0`, `P1`, or `P2` as must-fix findings. +- `P3` findings are non-blocking, but fix them when cheap and safe. + +#### Step 5: Revise And Re-Submit + +- Address findings in priority order. +- Rebuild the plan payload. +- Re-submit until approved or `MAX_ROUNDS` is reached. + +### Phase 7: Generate Plan Files + +Once the plan is approved: + +1. Ensure `/ai_plan/` exists in `.gitignore` +2. Create `ai_plan/YYYY-MM-DD-/` +3. Write: + - `original-plan.md` + - `final-transcript.md` + - `milestone-plan.md` + - `story-tracker.md` + - `continuation-runbook.md` +4. Use the template files from this skill's `templates/` directory + +### Phase 8: Telegram Completion Notification + +Resolve the notification helper in this order: + +```bash +TELEGRAM_NOTIFY_RUNTIME="" +for candidate in ".pi/skills/reviewer-runtime/pi/notify-telegram.sh" "$HOME/.pi/agent/skills/reviewer-runtime/pi/notify-telegram.sh"; do + if [ -x "$candidate" ]; then + TELEGRAM_NOTIFY_RUNTIME="$candidate" + break + fi +done +``` + +If the helper exists and both `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` are configured, send a short completion summary. If not, state that no Telegram completion notification was sent. diff --git a/skills/create-plan/_source/pi/templates/continuation-runbook.md b/skills/create-plan/_source/pi/templates/continuation-runbook.md new file mode 100644 index 0000000..4c33f9a --- /dev/null +++ b/skills/create-plan/_source/pi/templates/continuation-runbook.md @@ -0,0 +1,145 @@ +# Continuation Runbook: [Plan Title] + +## Reference Files (START HERE) + +Upon resumption, these files in this folder are the ONLY source of truth: + +| File | Purpose | When to Use | +|------|---------|-------------| +| `continuation-runbook.md` | Full context reproduction + execution workflow | Read FIRST | +| `story-tracker.md` | Current progress and status | Check/update BEFORE and AFTER every story | +| `milestone-plan.md` | Complete plan with specifications | Reference implementation details | +| `original-plan.md` | Original approved plan | Reference original intent | +| `final-transcript.md` | Final planning transcript | Reference reasoning/context | + +Do NOT reference planner-private files during implementation. + +## Skill Workflow Guardrails + +- Load relevant skills before action. If pi did not auto-load them, use `/skill:`. +- Announce which skill is being used and why. +- If a checklist-driven workflow applies, keep its state current in the plan artifacts. +- Do not use deprecated wrapper CLIs. + +--- + +## Quick Resume Instructions + +1. Read this runbook completely. +2. Check `story-tracker.md`. +3. Find next `pending` story and mark as `in-dev` before starting. +4. Implement the story. +5. Update tracker immediately after each change. + +--- + +## Mandatory Execution Workflow + +Work from this folder (`ai_plan/YYYY-MM-DD-/`) and always follow this order: + +1. Read `continuation-runbook.md` first. +2. Execute stories milestone by milestone. +3. After completing a milestone: + - Run lint/typecheck/tests, prioritizing changed files for speed. + - Commit locally (**DO NOT PUSH**). + - Stop and ask user for feedback. +4. If feedback is provided: + - Apply feedback changes. + - Re-run checks for changed files. + - Commit locally again. + - Ask for milestone approval. +5. Only move to next milestone after explicit approval. +6. After all milestones are completed and approved: + - Ask permission to push. + - If approved, push. + - Mark plan status as `completed`. + +--- + +## Git Note + +`ai_plan/` is intentionally local and must stay gitignored. Do not treat inability to commit plan-file updates inside `ai_plan/` as an error. + +--- + +## Full Context Reproduction + +### Project Overview + +[What this project/feature is about] + +### User Requirements + +[All gathered requirements] + +### Scope + +[In scope / out of scope] + +### Dependencies + +[External dependencies, prerequisites, related systems] + +--- + +## Key Specifications + +### Type Definitions + +```typescript +// Copy-paste ready type definitions +``` + +### Enums & Constants + +```typescript +// All enums/constants needed +``` + +### API Endpoints + +```typescript +// Request/response shapes +``` + +--- + +## Critical Design Decisions + +| Decision | Chosen Approach | Alternatives Rejected | Rationale | +|----------|-----------------|----------------------|-----------| +| [Topic] | [What we chose] | [Other options] | [Why] | + +--- + +## Verification Commands + +### Lint (changed files first) + +```bash +# example: pnpm eslint +``` + +### Typecheck + +```bash +# example: pnpm tsc --noEmit +``` + +### Tests (target changed scope first) + +```bash +# example: pnpm test -- +``` + +--- + +## File Quick Reference + +| File | Purpose | +|------|---------| +| `original-plan.md` | Original approved plan | +| `final-transcript.md` | Final planning transcript | +| `milestone-plan.md` | Full specification | +| `story-tracker.md` | Current progress tracker | +| `continuation-runbook.md` | This runbook | diff --git a/skills/create-plan/_source/pi/templates/milestone-plan.md b/skills/create-plan/_source/pi/templates/milestone-plan.md new file mode 100644 index 0000000..f646b48 --- /dev/null +++ b/skills/create-plan/_source/pi/templates/milestone-plan.md @@ -0,0 +1,118 @@ +# [Plan Title] + +## Overview + +- **Goal:** [One sentence describing the end state] +- **Created:** YYYY-MM-DD +- **Status:** In Progress | Complete + +## Context + +### Requirements + +[Gathered requirements from user questions] + +### Constraints + +[Technical, business, or timeline constraints] + +### Success Criteria + +[How we know this is complete] + +## Architecture + +### Design Decisions + +[Key architectural choices and rationale] + +### Component Relationships + +[How pieces fit together] + +### Data Flow + +[How data moves through the system] + +## Milestones + +### M1: [Name] + +**Description:** [What this milestone achieves] + +**Acceptance Criteria:** + +- [ ] [Criterion 1] +- [ ] [Criterion 2] + +**Stories:** S-101, S-102, S-103... + +**Milestone Completion Rule (MANDATORY):** + +- Run lint/typecheck/tests for changed files. +- Commit locally (DO NOT push). +- Stop and ask user for feedback. +- Apply feedback, re-check changed files, commit again. +- Move to next milestone only after user approval. + +--- + +### M2: [Name] + +**Description:** [What this milestone achieves] + +**Acceptance Criteria:** + +- [ ] [Criterion 1] +- [ ] [Criterion 2] + +**Stories:** S-201, S-202, S-203... + +**Milestone Completion Rule (MANDATORY):** + +- Run lint/typecheck/tests for changed files. +- Commit locally (DO NOT push). +- Stop and ask user for feedback. +- Apply feedback, re-check changed files, commit again. +- Move to next milestone only after user approval. + +--- + +## Technical Specifications + +### Types & Interfaces + +```typescript +// Key type definitions +``` + +### API Contracts + +```typescript +// Endpoint signatures, request/response shapes +``` + +### Constants & Enums + +```typescript +// Shared constants +``` + +## Files Inventory + +| File | Purpose | Milestone | +|------|---------|-----------| +| `path/to/file.ts` | [What it does] | M1 | +| `path/to/other.ts` | [What it does] | M2 | + +--- + +## Related Plan Files + +This file is part of the plan folder under `ai_plan/`: + +- `original-plan.md` - Original approved plan (reference for original intent) +- `final-transcript.md` - Final planning transcript (reference for rationale/context) +- `milestone-plan.md` - This file (full specification) +- `story-tracker.md` - Status tracking (must be kept up to date) +- `continuation-runbook.md` - Resume/execution context (read first) diff --git a/skills/create-plan/_source/pi/templates/story-tracker.md b/skills/create-plan/_source/pi/templates/story-tracker.md new file mode 100644 index 0000000..08dbd3a --- /dev/null +++ b/skills/create-plan/_source/pi/templates/story-tracker.md @@ -0,0 +1,71 @@ +# Story Tracker: [Plan Title] + +## Progress Summary + +- **Current Milestone:** M1 +- **Stories Complete:** 0/N +- **Milestones Approved:** 0/M +- **Last Updated:** YYYY-MM-DD + +--- + +## Milestones + +### M1: [Name] + +| Story | Description | Status | Notes | +|-------|-------------|--------|-------| +| S-101 | [Brief description] | pending | | +| S-102 | [Brief description] | pending | | +| S-103 | [Brief description] | pending | | + +**Approval Status:** pending + +--- + +### M2: [Name] + +| Story | Description | Status | Notes | +|-------|-------------|--------|-------| +| S-201 | [Brief description] | pending | | +| S-202 | [Brief description] | pending | | +| S-203 | [Brief description] | pending | | + +**Approval Status:** pending + +--- + +## Status Legend + +| Status | Meaning | +|--------|---------| +| `pending` | Not started | +| `in-dev` | Currently being worked on | +| `completed` | Done - include commit hash in Notes | +| `deferred` | Postponed - include reason in Notes | + +## Update Instructions (MANDATORY) + +Before starting any story: + +1. Mark story as `in-dev` +2. Update "Last Updated" + +After completing any story: + +1. Mark story as `completed` +2. Add local commit hash to Notes +3. Update "Stories Complete" and "Last Updated" + +At milestone boundary: + +1. Run lint/typecheck/tests for changed files +2. Commit (no push) +3. Request feedback +4. Apply feedback, re-check changed files, commit again +5. Mark milestone **Approval Status: approved** only after user confirms +6. Continue only after approval + +After all milestones approved: + +- Ask permission to push and then mark plan completed. diff --git a/skills/create-plan/claude-code/.generated-manifest.json b/skills/create-plan/claude-code/.generated-manifest.json new file mode 100644 index 0000000..3c98940 --- /dev/null +++ b/skills/create-plan/claude-code/.generated-manifest.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/create-plan/claude-code", + "files": [ + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "21635cb0342626e5adc568d7cf6d10eb95cdce6fad665b6553718640fd5d51ce" + }, + { + "path": "templates/continuation-runbook.md", + "kind": "file", + "mode": "644", + "sha256": "16d72a6d302d22712b1b4cd919bf9d899d6d31775c7183f32c78fe6e28f330d0" + }, + { + "path": "templates/milestone-plan.md", + "kind": "file", + "mode": "644", + "sha256": "76700ac603bc13c585cf9de25861be99dc0988df5925f06609ca60a71bee1b6d" + }, + { + "path": "templates/story-tracker.md", + "kind": "file", + "mode": "644", + "sha256": "6838b4242765e407d1498402b802b04b59c18b5ca3344aff8c33665bad00f28a" + } + ] +} diff --git a/skills/create-plan/claude-code/SKILL.md b/skills/create-plan/claude-code/SKILL.md index 90cdd43..3645172 100644 --- a/skills/create-plan/claude-code/SKILL.md +++ b/skills/create-plan/claude-code/SKILL.md @@ -3,6 +3,8 @@ name: create-plan description: Use when starting a new feature, project, or complex task that needs structured planning with milestones, bite-sized stories, and resumable execution context. ALWAYS invoke when user says "create a plan", "make a plan", "plan this", "start planning", or similar planning requests. --- + + # Create Plan (Claude Code) Create and maintain a local plan folder under `ai_plan/` at project root. diff --git a/skills/create-plan/claude-code/templates/continuation-runbook.md b/skills/create-plan/claude-code/templates/continuation-runbook.md index 11ca6e7..622f5fa 100644 --- a/skills/create-plan/claude-code/templates/continuation-runbook.md +++ b/skills/create-plan/claude-code/templates/continuation-runbook.md @@ -1,3 +1,4 @@ + # Continuation Runbook: [Plan Title] ## Reference Files (START HERE) diff --git a/skills/create-plan/claude-code/templates/milestone-plan.md b/skills/create-plan/claude-code/templates/milestone-plan.md index f646b48..4314392 100644 --- a/skills/create-plan/claude-code/templates/milestone-plan.md +++ b/skills/create-plan/claude-code/templates/milestone-plan.md @@ -1,3 +1,4 @@ + # [Plan Title] ## Overview diff --git a/skills/create-plan/claude-code/templates/story-tracker.md b/skills/create-plan/claude-code/templates/story-tracker.md index 08dbd3a..b7a8627 100644 --- a/skills/create-plan/claude-code/templates/story-tracker.md +++ b/skills/create-plan/claude-code/templates/story-tracker.md @@ -1,3 +1,4 @@ + # Story Tracker: [Plan Title] ## Progress Summary diff --git a/skills/create-plan/codex/.generated-manifest.json b/skills/create-plan/codex/.generated-manifest.json new file mode 100644 index 0000000..0f1d59b --- /dev/null +++ b/skills/create-plan/codex/.generated-manifest.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/create-plan/codex", + "files": [ + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "c30b3e8060c0df498b80bc0190b2993c3aae609ca3e137a71de0ef58615aed9c" + }, + { + "path": "templates/continuation-runbook.md", + "kind": "file", + "mode": "644", + "sha256": "b446958c1c49471dd7844093f94ba8f01439fa8285782127ae002513bbe83086" + }, + { + "path": "templates/milestone-plan.md", + "kind": "file", + "mode": "644", + "sha256": "a1eb885af21821f644d9fa8625cd8a6df018f1650f5269a0499ba86c26867101" + }, + { + "path": "templates/story-tracker.md", + "kind": "file", + "mode": "644", + "sha256": "2ad604c863724df3d189e489f41c578634085e5d9238f9e11f23002b0471aad9" + } + ] +} diff --git a/skills/create-plan/codex/SKILL.md b/skills/create-plan/codex/SKILL.md index 4fe088c..07fccba 100644 --- a/skills/create-plan/codex/SKILL.md +++ b/skills/create-plan/codex/SKILL.md @@ -3,6 +3,8 @@ name: create-plan description: Use when a user asks to create or maintain a structured implementation plan in Codex, including milestones, bite-sized stories, and resumable local planning artifacts under ai_plan. --- + + # Create Plan (Codex Native Superpowers) Create and maintain a local plan workspace under `ai_plan/` at project root. diff --git a/skills/create-plan/codex/templates/continuation-runbook.md b/skills/create-plan/codex/templates/continuation-runbook.md index dcaaf28..b2ee50b 100644 --- a/skills/create-plan/codex/templates/continuation-runbook.md +++ b/skills/create-plan/codex/templates/continuation-runbook.md @@ -1,3 +1,4 @@ + # Continuation Runbook: [Plan Title] ## Reference Files (START HERE) diff --git a/skills/create-plan/codex/templates/milestone-plan.md b/skills/create-plan/codex/templates/milestone-plan.md index 36ce980..fe55abe 100644 --- a/skills/create-plan/codex/templates/milestone-plan.md +++ b/skills/create-plan/codex/templates/milestone-plan.md @@ -1,3 +1,4 @@ + # [Plan Title] ## Overview diff --git a/skills/create-plan/codex/templates/story-tracker.md b/skills/create-plan/codex/templates/story-tracker.md index b08681d..9c54912 100644 --- a/skills/create-plan/codex/templates/story-tracker.md +++ b/skills/create-plan/codex/templates/story-tracker.md @@ -1,3 +1,4 @@ + # Story Tracker: [Plan Title] ## Progress Summary diff --git a/skills/create-plan/cursor/.generated-manifest.json b/skills/create-plan/cursor/.generated-manifest.json new file mode 100644 index 0000000..9625b3a --- /dev/null +++ b/skills/create-plan/cursor/.generated-manifest.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/create-plan/cursor", + "files": [ + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "f1478478780244adbca57e1723c2055961ff5f1949a185e23d23d406ce4c76ce" + }, + { + "path": "templates/continuation-runbook.md", + "kind": "file", + "mode": "644", + "sha256": "372189876f2d55edcbae8c106c1b0992a48a7061f9033ea5d3f2d329d57baecb" + }, + { + "path": "templates/milestone-plan.md", + "kind": "file", + "mode": "644", + "sha256": "01c7a810c73d9890d29255788815411c6b1605b236e1cf336652f15e444841de" + }, + { + "path": "templates/story-tracker.md", + "kind": "file", + "mode": "644", + "sha256": "969525389a148d5eb3e8a8e5c85647e9273e8309a96a37af194adcb5f89a5630" + } + ] +} diff --git a/skills/create-plan/cursor/SKILL.md b/skills/create-plan/cursor/SKILL.md index 24016af..24058b1 100644 --- a/skills/create-plan/cursor/SKILL.md +++ b/skills/create-plan/cursor/SKILL.md @@ -3,6 +3,8 @@ name: create-plan description: Use when starting a new feature, project, or complex task that needs structured planning with milestones, bite-sized stories, and resumable execution context in Cursor Agent CLI workflows. ALWAYS invoke when user says "create a plan", "make a plan", "plan this", "start planning", or similar planning requests. --- + + # Create Plan (Cursor Agent CLI) Create and maintain a local plan folder under `ai_plan/` at project root. diff --git a/skills/create-plan/cursor/templates/continuation-runbook.md b/skills/create-plan/cursor/templates/continuation-runbook.md index 1fac6bf..a2721f0 100644 --- a/skills/create-plan/cursor/templates/continuation-runbook.md +++ b/skills/create-plan/cursor/templates/continuation-runbook.md @@ -1,3 +1,4 @@ + # Continuation Runbook: [Plan Title] ## Reference Files (START HERE) diff --git a/skills/create-plan/cursor/templates/milestone-plan.md b/skills/create-plan/cursor/templates/milestone-plan.md index 2a04aaa..1a39473 100644 --- a/skills/create-plan/cursor/templates/milestone-plan.md +++ b/skills/create-plan/cursor/templates/milestone-plan.md @@ -1,3 +1,4 @@ + # [Plan Title] ## Overview diff --git a/skills/create-plan/cursor/templates/story-tracker.md b/skills/create-plan/cursor/templates/story-tracker.md index b08681d..ade654e 100644 --- a/skills/create-plan/cursor/templates/story-tracker.md +++ b/skills/create-plan/cursor/templates/story-tracker.md @@ -1,3 +1,4 @@ + # Story Tracker: [Plan Title] ## Progress Summary diff --git a/skills/create-plan/opencode/.generated-manifest.json b/skills/create-plan/opencode/.generated-manifest.json new file mode 100644 index 0000000..0d4f22d --- /dev/null +++ b/skills/create-plan/opencode/.generated-manifest.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/create-plan/opencode", + "files": [ + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "881c5fb4bb1c75535018d8eb1d557ac6779da8267607d2cb5ae52481bf961b77" + }, + { + "path": "templates/continuation-runbook.md", + "kind": "file", + "mode": "644", + "sha256": "15efb6e1fcfbdcc9c0d5fa1b357dea600afe42a20b761863bece6792f53dbd84" + }, + { + "path": "templates/milestone-plan.md", + "kind": "file", + "mode": "644", + "sha256": "36231230edd506e22a42d82293adefff6b5434fe972fde6767ca0b7ddf6b5dd3" + }, + { + "path": "templates/story-tracker.md", + "kind": "file", + "mode": "644", + "sha256": "2756a718f5de9709347085f6e0a525ec8c0e52dc3b9f67ddc4c226b60df1c967" + } + ] +} diff --git a/skills/create-plan/opencode/SKILL.md b/skills/create-plan/opencode/SKILL.md index 86eecd8..0e27da2 100644 --- a/skills/create-plan/opencode/SKILL.md +++ b/skills/create-plan/opencode/SKILL.md @@ -3,6 +3,8 @@ name: create-plan description: Use when starting a new feature, project, or complex task that needs structured planning with milestones, bite-sized stories, and resumable execution context in Opencode workflows. ALWAYS invoke when user says "create a plan", "make a plan", "plan this", "start planning", or similar planning requests. --- + + # Create Plan (OpenCode) Create and maintain a local plan folder under `ai_plan/` at project root. diff --git a/skills/create-plan/opencode/templates/continuation-runbook.md b/skills/create-plan/opencode/templates/continuation-runbook.md index 11ca6e7..94f45ba 100644 --- a/skills/create-plan/opencode/templates/continuation-runbook.md +++ b/skills/create-plan/opencode/templates/continuation-runbook.md @@ -1,3 +1,4 @@ + # Continuation Runbook: [Plan Title] ## Reference Files (START HERE) diff --git a/skills/create-plan/opencode/templates/milestone-plan.md b/skills/create-plan/opencode/templates/milestone-plan.md index f646b48..b7adb4d 100644 --- a/skills/create-plan/opencode/templates/milestone-plan.md +++ b/skills/create-plan/opencode/templates/milestone-plan.md @@ -1,3 +1,4 @@ + # [Plan Title] ## Overview diff --git a/skills/create-plan/opencode/templates/story-tracker.md b/skills/create-plan/opencode/templates/story-tracker.md index 08dbd3a..ab415d8 100644 --- a/skills/create-plan/opencode/templates/story-tracker.md +++ b/skills/create-plan/opencode/templates/story-tracker.md @@ -1,3 +1,4 @@ + # Story Tracker: [Plan Title] ## Progress Summary diff --git a/skills/create-plan/pi/.generated-manifest.json b/skills/create-plan/pi/.generated-manifest.json new file mode 100644 index 0000000..acd9cd9 --- /dev/null +++ b/skills/create-plan/pi/.generated-manifest.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/create-plan/pi", + "files": [ + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "8767db25ce6f03e141ce4c48f37e9d7c4958c3cf3d70729f3bd7214b84f6d065" + }, + { + "path": "templates/continuation-runbook.md", + "kind": "file", + "mode": "644", + "sha256": "1685cded3d4abaf03122a490175ff03b7da593ce60cbca97ae15fadcb706617f" + }, + { + "path": "templates/milestone-plan.md", + "kind": "file", + "mode": "644", + "sha256": "364c08bf0a5ee3738195a0770db72c5a4c9ad7f7fb89eaa064eb8c67f47ad69a" + }, + { + "path": "templates/story-tracker.md", + "kind": "file", + "mode": "644", + "sha256": "ecb550ea9dcd9dde6c813e90af9f538bf5a247fc249e8e323f2b7bf583e52196" + } + ] +} diff --git a/skills/create-plan/pi/SKILL.md b/skills/create-plan/pi/SKILL.md index 3af2db2..6d91443 100644 --- a/skills/create-plan/pi/SKILL.md +++ b/skills/create-plan/pi/SKILL.md @@ -3,6 +3,8 @@ name: create-plan description: Use when a user asks to create or maintain a structured implementation plan in pi, including milestones, bite-sized stories, and resumable local planning artifacts under ai_plan. --- + + # Create Plan (Pi) Create and maintain a local plan workspace under `ai_plan/` at project root. diff --git a/skills/create-plan/pi/templates/continuation-runbook.md b/skills/create-plan/pi/templates/continuation-runbook.md index 4c33f9a..1d0deae 100644 --- a/skills/create-plan/pi/templates/continuation-runbook.md +++ b/skills/create-plan/pi/templates/continuation-runbook.md @@ -1,3 +1,4 @@ + # Continuation Runbook: [Plan Title] ## Reference Files (START HERE) diff --git a/skills/create-plan/pi/templates/milestone-plan.md b/skills/create-plan/pi/templates/milestone-plan.md index f646b48..1e428c6 100644 --- a/skills/create-plan/pi/templates/milestone-plan.md +++ b/skills/create-plan/pi/templates/milestone-plan.md @@ -1,3 +1,4 @@ + # [Plan Title] ## Overview diff --git a/skills/create-plan/pi/templates/story-tracker.md b/skills/create-plan/pi/templates/story-tracker.md index 08dbd3a..64a0d1f 100644 --- a/skills/create-plan/pi/templates/story-tracker.md +++ b/skills/create-plan/pi/templates/story-tracker.md @@ -1,3 +1,4 @@ + # Story Tracker: [Plan Title] ## Progress Summary diff --git a/skills/do-task/_source/claude-code/SKILL.md b/skills/do-task/_source/claude-code/SKILL.md new file mode 100644 index 0000000..1c61bb9 --- /dev/null +++ b/skills/do-task/_source/claude-code/SKILL.md @@ -0,0 +1,798 @@ +--- +name: do-task +description: Execute a single user-supplied prompt end-to-end with two reviewer loops (plan review + implementation review). ALWAYS invoke when the user says `/do-task`, "do this task", "do task ...", "execute this task", or "make it so". Also invoke on the hint phrase "just do ...". Do NOT invoke on "implement this" (that phrase is reserved for implement-plan). +--- + +# Do Task (Claude Code) + +Execute an ad-hoc user prompt end-to-end: parse → clarify → plan (with reviewer loop) → implement (TDD-first where applicable) → verify → implementation review loop → commit → optional push → notify. + +This is a single-artifact sibling of `create-plan` + `implement-plan`. Unlike `implement-plan`, `do-task` operates on one persistent `task-plan.md` (not a full milestone plan) and defaults to the **current branch** (not a worktree). + +## Prerequisite Check (MANDATORY) + +Required: + +- Claude Code CLI: `claude --version` +- Superpowers repo: `https://github.com/obra/superpowers` +- `superpowers:brainstorming` +- `superpowers:test-driven-development` +- `superpowers:verification-before-completion` +- `superpowers:finishing-a-development-branch` +- `superpowers:using-git-worktrees` (only when the prompt opts in to a worktree) +- Shared reviewer runtime: `~/.claude/skills/reviewer-runtime/run-review.sh` +- Telegram notifier helper: `~/.claude/skills/reviewer-runtime/notify-telegram.sh` + +If any required dependency is missing, stop immediately and return: + +`Missing dependency: [specific missing item]. Install required Superpowers skills (https://github.com/obra/superpowers) and the reviewer-runtime helper, then retry.` + +This variant depends on explicit sub-skill invocation via the `Skill` tool. Do NOT use shell wrappers. + +## Trigger Phrase Detection + +**Binding triggers** (always invoke this skill): + +- `/do-task` +- "do this task" +- "do task ..." +- "execute this task" +- "make it so" + +**Hint trigger** (invoke unless context clearly maps to another skill): + +- "just do ..." + +**Escape phrases** (skip the Phase 2 clarifying-question loop): + +- `--no-questions` +- `"just do it:"` +- `"just do this:"` +- `"no questions:"` + +**Excluded** (do NOT trigger `do-task`): + +- "implement this" — reserved for `implement-plan`. + +**Dropped defaults** (explicitly NOT binding triggers): + +- "work on ..." +- "handle this" +- "take care of ..." +- "get this done" + +**Worktree opt-in phrases** (Phase 4 takes the worktree branch): + +- "in a worktree" +- "use a worktree" +- "on an isolated branch" +- "on a new branch called X" + +## Process + +### Phase 1: Preflight + +1. Verify git repo: `git rev-parse --is-inside-work-tree`. +2. Verify `/ai_plan/` is present in `.gitignore`. If missing: + - Append `/ai_plan/` to `.gitignore`. + - Commit that infra change immediately with message `chore(gitignore): ignore ai_plan local planning artifacts`. + - This infra commit is EXPLICITLY separate from the task commit in Phase 9. It may occur even when the final task ends up `aborted` or `failed`. +3. Verify required sub-skills are discoverable through the `Skill` tool. Announce each sub-skill before invocation using: + `I've read the [Skill Name] skill and I'm using it to [purpose].` + +### Phase 2: Parse Prompt and Question + +1. Capture the exact user prompt verbatim. +2. Detect trigger phrase (see above) and record which one matched. +3. Detect escape phrase. If set, skip clarifying questions entirely. +4. Apply the ask-first heuristic: + - Skip clarifying questions ONLY if ALL are true: + - Prompt names a concrete target (file, feature, or function). + - Prompt names a concrete outcome (what success looks like). + - Prompt has no ambiguous scope (no "and maybe also ..."). + - All identifiers in the prompt are resolvable against the codebase. + - Otherwise, ask 1-3 clarifying questions, ONE AT A TIME, multiple-choice preferred. + - Empty prompt → ask exactly once: "what task?". +5. Invoke `superpowers:brainstorming` via the `Skill` tool for any **behavior-changing** task — feature creation, bug fix with multiple plausible approaches, refactor, design decision. Present 2-3 approaches and recommend one before finalizing the plan. The ONLY skip conditions are the same ones that allow TDD auto-skip: `pure-documentation` and `pure-comment-whitespace-rename`. When skipping, record the skip reason in the Interpretation section of `task-plan.md`. + +### Phase 3: Configure Reviewer + +If the user has already specified a reviewer CLI and model (e.g., "do task X, review with codex gpt-5.4"), use those values. If the user says "use defaults" or otherwise opts out of explicit configuration, proceed with `REVIEWER_CLI=codex`, `REVIEWER_MODEL=gpt-5.4`, and `MAX_ROUNDS=10`. Otherwise, ask: + +1. **Which CLI should review both the plan and the implementation?** + - `codex` — OpenAI Codex CLI (`codex exec`) + - `claude` — Claude Code CLI (`claude -p`) + - `cursor` — Cursor Agent CLI (`cursor-agent -p`) + - `opencode` — OpenCode CLI (`opencode run`) + - `skip` — No external review, proceed with user approval only at each loop. + +2. **Which model?** (only if a CLI was chosen) + - For `codex`: default `gpt-5.4`, alternatives: `gpt-5.3-codex`, `o4-mini`, `o3`. + - For `claude`: default `sonnet`, alternatives: `opus`, `haiku`. + - For `cursor`: **run `cursor-agent models` first** to see available models. + - For `opencode`: provider-qualified form `/` (e.g., `anthropic/claude-sonnet-4-5`, `openai/gpt-5.4`). Run `opencode models` to list available models. + - Accept any model string the user provides. + +3. **Max review rounds shared across both loops?** (default: 10) + - If the user does not provide a value, set `MAX_ROUNDS=10`. + +Store `REVIEWER_CLI`, `REVIEWER_MODEL`, and `MAX_ROUNDS` for Phases 5 and 8. + +Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, `pi`, or `skip`. + +If `REVIEWER_CLI=pi`, verify the Pi reviewer binary before entering the review loop: + +```bash +pi --version +``` + +For shorthand `pi/`, split only on the first slash when the prefix is exactly `pi`; store the complete remainder in `REVIEWER_MODEL`. Examples: `pi/claude-opus-4-7` -> `claude-opus-4-7`, `pi/anthropic/claude-opus-4-7` -> `anthropic/claude-opus-4-7`, and `pi/openrouter/anthropic/claude-opus-4-7` -> `openrouter/anthropic/claude-opus-4-7`. + +When `REVIEWER_CLI=pi`, the reviewer model is configured independently from the model running this workflow. If the model/provider is unavailable, surface helper stderr/status and use `pi --list-models [search]` to inspect configured models. + +### Phase 4: Initialize Plan Workspace + +**PLAN MODE CHECK:** If currently in plan mode: + +1. Inform user that `task-plan.md` cannot be written while in plan mode. +2. Instruct user to exit plan mode (approve plan or use `ExitPlanMode`). +3. Proceed with file generation only after exiting plan mode. + +Steps: + +1. Compute slug: `YYYY-MM-DD-` where `` is a kebab-case hash of the task goal (lowercase, alphanumeric + hyphens only). +2. Compute plan folder: `ai_plan//`. +3. **Resume detection:** If the folder already exists, read `task-plan.md`: + - If `Status` is `draft` or `plan-approved` or `implementation-in-progress`: offer to resume, pick a new suffix (`-v2`), or abort. Default is resume. + - If `Status` is any terminal value (`pushed`, `local-only`, `aborted-*`, `failed`): offer a new suffix or abort. Default is new suffix. +4. If not resuming, create the folder and write `task-plan.md` from the template at `templates/task-plan.md` (this skill's template folder; falls back to `~/.claude/skills/do-task/templates/task-plan.md` when installed directly). +5. Fill in: + - `Metadata` block. + - `Prompt` (verbatim). + - `Interpretation`, `Assumptions`, `Files`, `Approach`, `TDD Approach`, `Acceptance Criteria`, `Verification`, `Rollback`. + - Leave `Runtime State`, `Review History`, `Final Status` empty (skill updates these). +6. Set `Status: draft`. + +**Worktree branch:** If the prompt opts in to a worktree (see Trigger Phrase Detection), invoke `superpowers:using-git-worktrees` via the `Skill` tool before proceeding. Otherwise continue on the current branch. + +### Phase 5: Plan Review Loop + +If `REVIEWER_CLI=skip`, present `task-plan.md` to the user and proceed only after explicit user approval. + +Otherwise, invoke the Review Loop (Shared Subroutine) with: + +```text +REVIEW_KIND = plan +REVIEW_ID = $(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) +PAYLOAD_PATH = /tmp/do-task-plan-${REVIEW_ID}.md +PROMPT_TEMPLATE = PLAN_REVIEW_PROMPT (see below) +SESSION_ID_VAR = CODEX_PLAN_SESSION_ID | CURSOR_PLAN_SESSION_ID | OPENCODE_PLAN_SESSION_ID +``` + +Payload is the current `task-plan.md` **with the `Runtime State` and `Review History` blocks stripped** before writing to `PAYLOAD_PATH`. Those two blocks contain reviewer session IDs and scan outcomes that must never be sent back to any reviewer CLI. Reviewers only need the Prompt, Interpretation, Assumptions, Files, Approach, TDD Approach, Acceptance Criteria, Verification, Rollback, and Metadata sections. + +`PLAN_REVIEW_PROMPT`: + +```text +Review this task plan for completeness, correctness, and risk. Focus on: +1. Does the plan match the user's prompt? +2. Are all assumptions surfaced? +3. Are acceptance criteria testable? +4. Is the TDD approach appropriate per the TDD Approach section? +5. Are there missing files, risks, or security concerns? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE`. +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking. +``` + +On APPROVED: + +- Set `Status: plan-approved`. +- Append APPROVED row to Review History. +- Proceed to Phase 6. + +On MAX_ROUNDS: + +- Set `Status: aborted-plan-review`. +- Send Telegram summary before stopping. +- Ask the user whether to override and proceed, restart, or abort. + +### Phase 6: Execute (TDD-first where applicable) + +Native orchestration — do not invoke `superpowers:executing-plans`. + +1. Set `Status: implementation-in-progress`. +2. For every behavior-changing file edit: + - Invoke `superpowers:test-driven-development` via the `Skill` tool. + - Write the failing test first. Run it. Confirm it fails. + - Implement the minimal code to make it pass. Run the test. Confirm green. + - Do NOT commit yet — a single task commit happens in Phase 9. +3. Auto-skip of TDD is permitted ONLY for tasks classified in `task-plan.md` TDD Approach as: + - `pure-documentation` + - `pure-comment-whitespace-rename` +4. Any other skip (including `pure-config-addition`) requires explicit user approval recorded in `task-plan.md` with an ISO-8601 timestamp. +5. Update `task-plan.md` after each logical step: add notes to `Approach`, check off `Acceptance Criteria` items as they complete. + +### Phase 7: Verification Gate + +Invoke `superpowers:verification-before-completion` via the `Skill` tool. + +Run the commands listed in the `Verification` section of `task-plan.md`: + +- Lint (changed files first). +- Typecheck. +- Tests (targeted first, then broader suite if quick). + +All must pass. If a command fails: + +- Fix the issue. +- Re-run that command. +- Increment `verification_attempts` in Runtime State. + +If `verification_attempts` exceeds 3 without green: + +- Set `Status: aborted-verification`. +- Send Telegram summary. +- Ask the user whether to retry, override, or abort. + +### Phase 8: Implementation Review Loop + +If `REVIEWER_CLI=skip`, present a diff + verification summary to the user and proceed only after explicit user approval. + +Otherwise, invoke the Review Loop (Shared Subroutine) with: + +```text +REVIEW_KIND = implementation +REVIEW_ID = $(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) # distinct from plan-review ID +PAYLOAD_PATH = /tmp/do-task-implementation-${REVIEW_ID}.md +PROMPT_TEMPLATE = IMPL_REVIEW_PROMPT (see below) +SESSION_ID_VAR = CODEX_IMPL_SESSION_ID | CURSOR_IMPL_SESSION_ID | OPENCODE_IMPL_SESSION_ID +``` + +Payload contents (assembled by the skill): + +```markdown +# Implementation Review: [Short Title] + +## Task Plan (the plan that was approved) + + +## Changes Made (git diff) + + +## Verification Output +### Lint + +### Typecheck + +### Tests + +``` + +`IMPL_REVIEW_PROMPT`: + +```text +Review this implementation against the task plan. Focus on: +1. Correctness — Does the diff satisfy the Acceptance Criteria? +2. Code quality — Clean, maintainable, no obvious issues? +3. Test coverage — Are behavior changes adequately tested (per the plan's TDD Approach)? +4. Security — Any security concerns introduced? +5. Regressions — Does the diff risk breaking unrelated code? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE`. +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking. +``` + +On APPROVED: + +- Set `Status: implementation-approved`. +- Append APPROVED row to Review History. +- Proceed to Phase 9. + +On MAX_ROUNDS: + +- Set `Status: aborted-impl-review`. +- Send Telegram summary. +- Ask the user whether to override and commit anyway, restart, or abort. + +### Phase 9: Commit + Push Ask + +Invoke `superpowers:finishing-a-development-branch` via the `Skill` tool. + +1. Stage all changed files explicitly (avoid `git add -A`). +2. Single commit with message derived from the task goal: + - Format: `(): ` + - Example: `feat(auth): add session token rotation` +3. Do NOT push. Update `Status: local-only`. +4. Ask the user: "Push to remote? (yes / no)" + - On explicit `yes` → push, then set `Status: pushed`. + - Any other response → leave `Status: local-only`. + +### Phase 10: Telegram Notification + Finalize + +Resolve the notifier helper: + +```bash +TELEGRAM_NOTIFY_RUNTIME=~/.claude/skills/reviewer-runtime/notify-telegram.sh +``` + +On every terminal outcome (`pushed`, `local-only`, `aborted-*`, `failed`), send a Telegram summary if both `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` are set: + +```bash +if [ -x "$TELEGRAM_NOTIFY_RUNTIME" ] && [ -n "${TELEGRAM_BOT_TOKEN:-}" ] && [ -n "${TELEGRAM_CHAT_ID:-}" ]; then + "$TELEGRAM_NOTIFY_RUNTIME" --message "do-task : " +fi +``` + +Rules: + +- Telegram is the only supported notification path. +- Notification failures are non-blocking but must be surfaced to the user. +- Before stopping for any user interaction, approval, or manual decision, send a Telegram summary first if configured. +- If Telegram is not configured, state that no Telegram notification was sent. + +Fill in `Final Status` in `task-plan.md` (include commit hash if any). Do NOT delete the plan folder — it stays as a record. + +--- + +## Review Loop (Shared Subroutine) + +This subroutine is invoked twice per `do-task` run: once in Phase 5 (`REVIEW_KIND=plan`) and once in Phase 8 (`REVIEW_KIND=implementation`). Separate session IDs are used for each loop so reviewer context never leaks across loops. + +### Subroutine Inputs + +| Variable | Purpose | +|----------|---------| +| `REVIEW_KIND` | `plan` or `implementation` | +| `REVIEW_ID` | 8-char hex (from `uuidgen`); reused across rounds of the same loop | +| `PAYLOAD_PATH` | `/tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md` | +| `PROMPT_TEMPLATE` | `PLAN_REVIEW_PROMPT` or `IMPL_REVIEW_PROMPT` | +| `REVIEWER_CLI` | `codex` \| `claude` \| `cursor` \| `opencode` \| `pi` | +| `REVIEWER_MODEL` | Model name | +| `MAX_ROUNDS` | Default 10 | +| `SESSION_ID_VAR` | `CODEX_PLAN_SESSION_ID` \| `CODEX_IMPL_SESSION_ID` \| `CURSOR_PLAN_SESSION_ID` \| `CURSOR_IMPL_SESSION_ID` \| `OPENCODE_PLAN_SESSION_ID` \| `OPENCODE_IMPL_SESSION_ID` | + +Temp artifact paths (per loop): + +- `/tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md` — payload +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md` — normalized review text +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json` — raw Cursor/OpenCode JSON (cursor only, plus opencode when `--format json` is used) +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.stderr` +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.status` +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out` +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh` + +Resolve the shared helper: + +```bash +REVIEWER_RUNTIME=~/.claude/skills/reviewer-runtime/run-review.sh +``` + +Set helper success-artifact args before writing the command script: + +```bash +HELPER_SUCCESS_FILE_ARGS=() +case "$REVIEWER_CLI" in + codex) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md) + ;; + cursor) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json) + ;; +esac +``` + +### Step 1: Write Payload + +Write the full payload for this round to `PAYLOAD_PATH`. + +### Step 1a: Secret Scan (per-payload, no caching) + +**BEFORE** sending the payload to any reviewer CLI, scan it for secrets. This scan runs EVERY round — no results are cached. Rationale: Phase 8 payloads include newly-introduced diff content that earlier rounds never saw. + +Run the secret scan with all of these anchored regexes. Use `grep -En` on the payload file: + +```bash +SECRET_REGEX_FILE=$(mktemp) +cat >"$SECRET_REGEX_FILE" <<'EOF' +AKIA[0-9A-Z]{16} +"type"\s*:\s*"service_account" +(ghp|gho|ghs|ghu|ghr)_[A-Za-z0-9]{36,} +xox[abpsr]-[0-9]+-[0-9]+-[0-9]+-[A-Za-z0-9]{24,} +xox[abpsr]-[A-Za-z0-9]{10,48} +sk-(proj-)?[A-Za-z0-9_-]{20,} +sk-ant-(api|admin)[0-9]+-[A-Za-z0-9_-]{20,} +-----BEGIN [A-Z ]+ PRIVATE KEY----- +(TOKEN|SECRET|PASSWORD|API_?KEY|ACCESS_?KEY)\s*=\s*["']?[A-Za-z0-9+/=_-]{8,} +eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+ +EOF + +SCAN_MATCHES=$(grep -Ensf "$SECRET_REGEX_FILE" "$PAYLOAD_PATH" || true) +rm -f "$SECRET_REGEX_FILE" +``` + +If `SCAN_MATCHES` is non-empty: + +1. **Redact the matched text before surfacing** — never echo the raw secret to the user, chat log, terminal scrollback, or any persistent file. Replace each matched substring with a fixed token that preserves only the fact of a match: `[REDACTED::-chars]`. Example: a matched AWS key becomes `[REDACTED:aws-access-key:20-chars]`. Keep the file path and line number; they are useful for the user and not secret. +2. Present the redacted match summary to the user using this exact wording: + + ```text + SECRET-SCAN MATCH in outbound reviewer payload (loop: ${REVIEW_KIND}, round: N): + :: [REDACTED::-chars] + ... + Proceed with sending this payload to ${REVIEWER_CLI}? (yes / no / redact) + ``` + + Pattern labels: `aws-access-key`, `gcp-service-account`, `github-token`, `slack-token`, `openai-key`, `anthropic-key`, `pem-private-key`, `dotenv-style`, `jwt`. + +3. Wait for user response. +4. On `yes`: record `last_scan_outcome_${REVIEW_KIND}=user-approved-with-matches` in Runtime State, and proceed. +5. On `redact`: ask the user to supply redactions, apply them to `PAYLOAD_PATH`, re-scan (this step), record `last_scan_outcome_${REVIEW_KIND}=redacted-and-approved`. +6. On `no`: stop the loop, set `Status: failed`, send Telegram, return to the user. + +If `SCAN_MATCHES` is empty, record `last_scan_outcome_${REVIEW_KIND}=clean` and proceed. + +### Step 2: Generate Reviewer Command Script + +Write the reviewer invocation to `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh` as a bash script starting with: + +```bash +#!/usr/bin/env bash +set -euo pipefail +``` + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call every round (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "Read the file /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md and review. Return exactly the required ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +Round 1 — fresh `codex exec`: + +```bash +codex exec \ + -m ${REVIEWER_MODEL} \ + -s read-only \ + -o /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md \ + "Review the ${REVIEW_KIND} payload in /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md. + +${PROMPT_TEMPLATE}" +``` + +Do not capture the Codex session ID yet. After Round 1 completes, extract it from `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out` (look for `session id: `) and persist it to Runtime State under `${SESSION_ID_VAR}`. + +Round 2 and later — resume session: + +```bash +codex exec resume ${SESSION_ID_VAR_VALUE} \ + -o /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md \ + "I've revised based on your feedback. Updated payload is in /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same ## Summary, ## Findings, and ## Verdict structure as before. +Keep findings ordered P0 to P3, use '- None.' when a severity has no findings, and only use VERDICT: APPROVED when no P0, P1, or P2 findings remain." +``` + +If resume fails, fall back to fresh `codex exec` with prior-round context. + +**If `REVIEWER_CLI` is `claude`:** + +Fresh call every round (Claude CLI has no session resume): + +```bash +claude -p \ + "${ROUND_PREFIX}Review the following ${REVIEW_KIND} payload. + +$(cat /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md) + +${PROMPT_TEMPLATE}" \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +Where `${ROUND_PREFIX}` is empty for Round 1 and `"You previously reviewed this ${REVIEW_KIND} and requested revisions. Previous feedback summary: [key points]. "` for subsequent rounds. + +**If `REVIEWER_CLI` is `cursor`:** + +Round 1: + +```bash +cursor-agent -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "Read the file /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md and review. + +${PROMPT_TEMPLATE}" \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +Round 2 and later — resume: + +```bash +cursor-agent --resume ${SESSION_ID_VAR_VALUE} -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "I've revised based on your feedback. Updated payload is in /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same ## Summary, ## Findings, and ## Verdict structure as before." \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +If resume fails, fall back to fresh `cursor-agent -p`. + +After the command completes, extract the session id and review text: + +```bash +CURSOR_SID=$(jq -r '.session_id' /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json) +jq -r '.result' /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md +``` + +Persist `CURSOR_SID` to Runtime State under `${SESSION_ID_VAR}` on Round 1. + +**If `REVIEWER_CLI` is `opencode`:** + +OpenCode does not expose a dedicated read-only flag at the CLI level; use the built-in `plan` primary agent (`--agent plan`) for review, which is read-oriented and does not modify files. Session resume is supported via `-s `, but the most reliable pattern for non-interactive review is **fresh call each round** (like `claude`) because opencode's session lifecycle and ID capture are less standardized than codex/cursor for headless runs. Skills MAY opt-in to session resume when they have verified the installed opencode version exposes a stable session id in `--format json` output. + +Round 1 (preferred, fresh call): + +```bash +opencode run \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "Read the file /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md and review. + +${PROMPT_TEMPLATE}" \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +Round 2 and later (fresh-call fallback path — recommended default): + +```bash +opencode run \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "You previously reviewed this ${REVIEW_KIND} and requested revisions. + +Previous feedback summary: [key points from last review] + +I've revised. Updated payload is below. + +$(cat /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md) + +Changes made: +[List specific changes] + +Re-review using the same ## Summary, ## Findings, and ## Verdict structure as before." \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +Optional session-resume path (only if the installed opencode reliably emits a session id in `--format json` output and accepts it back via `-s`): + +```bash +# Round 2+ with resume +opencode run \ + -s ${SESSION_ID_VAR_VALUE} \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "I've revised. Updated payload is in /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same ## Summary, ## Findings, and ## Verdict structure as before." \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +Extract the review body (the JSON stream emits events; the final assistant message contains the review text): + +```bash +jq -r '.[] | select(.type == "message" and .role == "assistant") | .content' \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md \ + || cp /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md +``` + +If the JSON parse falls through, promote the raw JSON file as the review output and surface a warning to the user. On any opencode CLI or JSON parsing failure, treat this loop round as `completed-empty-output` and follow the helper-failure escalation in Step 6. + +### Step 3: Run via `run-review.sh` + +Run the command script through the shared helper when available: + +```bash +if [ -x "$REVIEWER_RUNTIME" ]; then + "$REVIEWER_RUNTIME" \ + --command-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh \ + --stdout-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + --stderr-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.stderr \ + --status-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.status \ + "${HELPER_SUCCESS_FILE_ARGS[@]}" +else + echo "Warning: reviewer runtime helper not found at $REVIEWER_RUNTIME; falling back to direct synchronous review." >&2 + bash /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh \ + >/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + 2>/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.stderr +fi +``` + +Run the helper in the foreground and watch live stdout for `state=in-progress` heartbeats. If your agent environment buffers command output until exit, start the helper in the background and poll the `.status` file instead of treating heartbeats as post-hoc-only data. + +### Step 4: Promote Reviewer Output + Capture Session ID + +After the command completes: + +- `cursor`: already promoted in Step 2 via `jq -r '.result' ...`. Also capture `session_id` if first round. +- `codex`: extract `CODEX_SESSION_ID` from `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out` after the helper or fallback run. If the review text lives only in `.runner.out`, `cp` it into the `.md` file: + + ```bash + cp /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md + ``` + +- `claude` or `pi`: promote `.runner.out` into the `.md` file: + + ```bash + cp /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md + ``` + +- `opencode`: already promoted in Step 2 via `jq` on the JSON stream. If opt-in session-resume is active and the JSON includes a stable session id, capture it and persist to `${SESSION_ID_VAR}`. + +On Round 1, persist the captured session ID (if any) into `task-plan.md`'s Runtime State under `${SESSION_ID_VAR}`. + +### Step 5: Parse Verdict + Update Review History + +1. Read `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md`. +2. Append one row to `task-plan.md` Review History: + - Timestamp (ISO-8601 UTC). + - Loop (`plan` or `implementation`). + - Round number. + - Verdict (`APPROVED` or `REVISE`). + - Summary (first line of the `## Summary` section). +3. Increment `plan_review_round` or `implementation_review_round` in Runtime State. + +### Step 6: Branch APPROVED / REVISE / MAX_ROUNDS + +Verdict rules: + +- **VERDICT: APPROVED** with no `P0`, `P1`, or `P2` findings → exit the subroutine with `APPROVED`. +- **VERDICT: APPROVED** with only `P3` findings → optionally fix the `P3` items if cheap and safe, then exit with `APPROVED`. +- **VERDICT: REVISE** or any `P0`, `P1`, or `P2` finding → go to revision (see below), then return to Step 1 for the next round. +- No clear verdict but `P0`, `P1`, and `P2` are all `- None.` → treat as APPROVED. +- Helper state `completed-empty-output` → treat as failed review attempt, surface `.stderr`/`.status`, fix invocation or prompt handling, then retry. +- Helper state `needs-operator-decision` → surface status log and decide whether to extend the timeout, abort, or retry with different helper parameters. +- Round counter ≥ `MAX_ROUNDS` → exit the subroutine with `MAX_ROUNDS`. Caller decides next action per Phase 5 or Phase 8. + +**Revision:** The caller (Phase 5 for plan, Phase 6/7 for implementation) applies findings in priority order (`P0` → `P1` → `P2` → `P3`). For implementation review revisions, Phase 7 verification must be re-run after every revision before returning to Step 1. + +### Step 7: Liveness Contract (during Step 3) + +- The shared reviewer runtime emits `state=in-progress note="In progress N"` heartbeats every 60 seconds while the reviewer child is alive. +- Keep waiting as long as a fresh `In progress N` heartbeat keeps arriving roughly once per minute. +- Do not abort just because the review is slow, a soft timeout fired, or a `stall-warning` line appears, as long as the `In progress N` heartbeat continues. +- Treat missing heartbeats, `state=failed`, `state=completed-empty-output`, and `state=needs-operator-decision` as escalation signals. + +### Step 8: Cleanup (on successful round exit) + +```bash +rm -f /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.stderr \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.status \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh +``` + +If the round failed, produced empty output, or reached operator-decision timeout, KEEP `.stderr`, `.status`, and `.runner.out` until the issue is diagnosed instead of deleting them. + +--- + +## Resume Semantics + +1. Detect existing plan folder by slug at Phase 4. +2. Read `task-plan.md` → `Status`. +3. Decide next action: + + | Status | Action | + |--------|--------| + | `draft` | Resume at Phase 5 (plan review) | + | `plan-approved` | Resume at Phase 6 (execute) | + | `implementation-in-progress` | Resume at Phase 6 (continue execute) | + | `implementation-approved` | Resume at Phase 9 (commit + push ask) | + | `pushed` \| `local-only` | Ask user: new suffix, abort, or replay for reference only | + | `aborted-*` \| `failed` | Offer new suffix or full restart | + +4. When resuming, read Runtime State for `CODEX_PLAN_SESSION_ID`, `CODEX_IMPL_SESSION_ID`, `CURSOR_PLAN_SESSION_ID`, `CURSOR_IMPL_SESSION_ID`, `OPENCODE_PLAN_SESSION_ID`, `OPENCODE_IMPL_SESSION_ID`, and the round counters. If a session ID is populated, use it for the first revision round in that loop (Round 2) via `codex exec resume`, `cursor-agent --resume`, or `opencode run -s ` as applicable. + +--- + +## Tracker Discipline (MANDATORY) + +**ALWAYS update `task-plan.md` before/after each phase transition. NEVER proceed with stale state.** + +Before starting any phase: + +1. Update `Status` if it transitions. +2. Update `last_phase_entered` in Runtime State. + +After completing any phase: + +1. Update `Status` if it transitions. +2. Append notes to the relevant section of `task-plan.md`. + +Review History is append-only. + +--- + +## Execution Workflow Rules + +- Current branch is the default; worktree is opt-in only. +- Do NOT push without explicit "yes". +- Secret scan runs **per-payload, no caching** — every round, including revisions. +- Review loops use `MAX_ROUNDS=10` by default, shared across both loops. +- The task commit is a single commit created in Phase 9; interim WIP commits are NOT created. +- The `.gitignore` infra commit in Phase 1 is explicitly separate from the task commit and is allowed even on abort. + +--- + +## Verification Checklist + +- [ ] `ai_plan/` exists and `/ai_plan/` is in `.gitignore` +- [ ] `task-plan.md` created under `ai_plan/YYYY-MM-DD-/` +- [ ] Reviewer CLI + model + `MAX_ROUNDS` configured (or `skip`) +- [ ] Secret scan ran on every outbound reviewer payload +- [ ] Plan review completed (APPROVED, MAX_ROUNDS handled, or skipped) +- [ ] Phase 6 executed TDD-first for all behavior-changing steps (or documented skip) +- [ ] Phase 7 verification green before Phase 8 +- [ ] Implementation review completed (APPROVED, MAX_ROUNDS handled, or skipped) +- [ ] Single task commit created locally, no push without explicit yes +- [ ] Telegram notification attempted if configured +- [ ] `task-plan.md` Final Status filled in diff --git a/skills/do-task/_source/claude-code/templates/task-plan.md b/skills/do-task/_source/claude-code/templates/task-plan.md new file mode 100644 index 0000000..f637ecc --- /dev/null +++ b/skills/do-task/_source/claude-code/templates/task-plan.md @@ -0,0 +1,143 @@ +# Task Plan: [Short Title] + +> **Variant guardrail (Claude Code):** When generating or updating this file, the agent MUST be out of plan mode. Sub-skills (`brainstorming`, `test-driven-development`, `verification-before-completion`, `finishing-a-development-branch`, `using-git-worktrees`) MUST be invoked through the `Skill` tool explicitly — no shell wrappers. + +## Metadata + +| Field | Value | +|-------|-------| +| Created | YYYY-MM-DD | +| Slug | YYYY-MM-DD- | +| Runtime | claude-code | +| Reviewer CLI | codex \| claude \| cursor \| opencode \| pi | +| Reviewer Model | | +| MAX_ROUNDS | 10 | +| Branch Strategy | current-branch \| worktree | +| Branch Name | | +| Worktree Path | | +| Status | draft | + +### Status Enum (authoritative) + +| Value | Meaning | +|-------|---------| +| `draft` | Newly created; plan review not yet started | +| `plan-approved` | Plan review loop returned APPROVED | +| `implementation-in-progress` | Phase 6 executing | +| `implementation-approved` | Phase 8 review loop returned APPROVED; awaiting commit | +| `pushed` | Committed + pushed to remote | +| `local-only` | Committed locally; user declined push | +| `aborted-plan-review` | MAX_ROUNDS reached in Phase 5; user aborted | +| `aborted-impl-review` | MAX_ROUNDS reached in Phase 8; user aborted | +| `aborted-verification` | Phase 7 retries exhausted; user aborted | +| `failed` | Hard tooling failure | + +--- + +## Prompt + + + +## Interpretation + + + +## Assumptions + + + +## Files + + + +| Action | Path | Why | +|--------|------|-----| +| | | | + +## Approach + + + +## TDD Approach + + + +## Acceptance Criteria + +- [ ] +- [ ] + +## Verification + + + +## Rollback + + + +--- + +## Runtime State + + + +```yaml +plan_review_round: 0 +implementation_review_round: 0 +CODEX_PLAN_SESSION_ID: +CODEX_IMPL_SESSION_ID: +CURSOR_PLAN_SESSION_ID: +CURSOR_IMPL_SESSION_ID: +OPENCODE_PLAN_SESSION_ID: +OPENCODE_IMPL_SESSION_ID: +last_phase_entered: +last_round_ts: +last_scan_outcome_plan: +last_scan_outcome_impl: +verification_attempts: 0 +tests_added_count: 0 +tdd_used: false +``` + +## Review History + + + +| Timestamp (ISO-8601) | Loop | Round | Verdict | Summary | +|----------------------|------|-------|---------|---------| +| | | | | | + +## Final Status + + + +--- + +## Guardrails (do NOT remove) + +- This file is the single persistent artifact for `do-task`. Do not split it or delete it on success. +- `Status` must always match one of the 10 enum values. +- `Runtime State` is updated by the skill, not by the user. +- Review History is append-only. +- `last_scan_outcome_plan` and `last_scan_outcome_impl` record the most recent secret-scan result for each loop. They are informational; the scan itself runs per-payload with no caching. diff --git a/skills/do-task/_source/codex/SKILL.md b/skills/do-task/_source/codex/SKILL.md new file mode 100644 index 0000000..1a9a172 --- /dev/null +++ b/skills/do-task/_source/codex/SKILL.md @@ -0,0 +1,848 @@ +--- +name: do-task +description: Execute a single user-supplied prompt end-to-end with two reviewer loops (plan review + implementation review) in Codex. ALWAYS invoke when the user says `/do-task`, "do this task", "do task ...", "execute this task", or "make it so". Also invoke on the hint phrase "just do ...". Do NOT invoke on "implement this" (that phrase is reserved for implement-plan). +--- + +# Do Task (Codex Native Superpowers) + +Execute an ad-hoc user prompt end-to-end: parse → clarify → plan (with reviewer loop) → implement (TDD-first where applicable) → verify → implementation review loop → commit → optional push → notify. + +This is a single-artifact sibling of `create-plan` + `implement-plan`. Unlike `implement-plan`, `do-task` operates on one persistent `task-plan.md` (not a full milestone plan) and defaults to the **current branch** (not a worktree). + +**Core principle:** Codex uses native skill discovery from `~/.agents/skills/`. Do not use deprecated `superpowers-codex bootstrap` or `use-skill` CLI commands. + +## Prerequisite Check (MANDATORY) + +Required: + +- Codex CLI: `codex --version` +- Superpowers repo: `https://github.com/obra/superpowers` +- Superpowers skills symlink: `~/.agents/skills/superpowers -> ~/.codex/superpowers/skills` +- `superpowers:brainstorming` +- `superpowers:test-driven-development` +- `superpowers:verification-before-completion` +- `superpowers:finishing-a-development-branch` +- `superpowers:using-git-worktrees` (only when the prompt opts in to a worktree) +- Shared reviewer runtime: `~/.codex/skills/reviewer-runtime/run-review.sh` +- Telegram notifier helper: `~/.codex/skills/reviewer-runtime/notify-telegram.sh` + +Verify before proceeding: + +```bash +test -L ~/.agents/skills/superpowers +test -f ~/.agents/skills/superpowers/brainstorming/SKILL.md +test -f ~/.agents/skills/superpowers/test-driven-development/SKILL.md +test -f ~/.agents/skills/superpowers/verification-before-completion/SKILL.md +test -f ~/.agents/skills/superpowers/finishing-a-development-branch/SKILL.md +``` + +If any required dependency is missing, stop immediately and return: + +`Missing dependency: [specific missing item]. Install required Superpowers skills (https://github.com/obra/superpowers) and the reviewer-runtime helper, then retry.` + +## Required Skill Invocation Rules + +- Invoke relevant skills through native discovery (no CLI wrapper). +- Announce skill usage explicitly: + - `I've read the [Skill Name] skill and I'm using it to [purpose].` +- For skills with checklists, track checklist items with `update_plan` todos. +- Tool mapping for Codex: + - `TodoWrite` -> `update_plan` + - `Task` subagents -> unavailable in Codex; do the work directly and state the limitation + - `Skill` -> use native skill discovery from `~/.agents/skills/` + +## Trigger Phrase Detection + +**Binding triggers** (always invoke this skill): + +- `/do-task` +- "do this task" +- "do task ..." +- "execute this task" +- "make it so" + +**Hint trigger** (invoke unless context clearly maps to another skill): + +- "just do ..." + +**Escape phrases** (skip the Phase 2 clarifying-question loop): + +- `--no-questions` +- `"just do it:"` +- `"just do this:"` +- `"no questions:"` + +**Excluded** (do NOT trigger `do-task`): + +- "implement this" — reserved for `implement-plan`. + +**Dropped defaults** (explicitly NOT binding triggers): + +- "work on ..." +- "handle this" +- "take care of ..." +- "get this done" + +**Worktree opt-in phrases** (Phase 4 takes the worktree branch): + +- "in a worktree" +- "use a worktree" +- "on an isolated branch" +- "on a new branch called X" + +## Process + +### Phase 1: Preflight + +1. Verify git repo: `git rev-parse --is-inside-work-tree`. +2. Verify `/ai_plan/` is present in `.gitignore`. If missing: + - Append `/ai_plan/` to `.gitignore`. + - Commit that infra change immediately with message `chore(gitignore): ignore ai_plan local planning artifacts`. + - This infra commit is EXPLICITLY separate from the task commit in Phase 9. It may occur even when the final task ends up `aborted` or `failed`. +3. Verify required sub-skills are discoverable under `~/.agents/skills/superpowers/`. Announce each sub-skill before invocation using: + `I've read the [Skill Name] skill and I'm using it to [purpose].` + +### Phase 2: Parse Prompt and Question + +1. Capture the exact user prompt verbatim. +2. Detect trigger phrase (see above) and record which one matched. +3. Detect escape phrase. If set, skip clarifying questions entirely. +4. Apply the ask-first heuristic: + - Skip clarifying questions ONLY if ALL are true: + - Prompt names a concrete target (file, feature, or function). + - Prompt names a concrete outcome (what success looks like). + - Prompt has no ambiguous scope (no "and maybe also ..."). + - All identifiers in the prompt are resolvable against the codebase. + - Otherwise, ask 1-3 clarifying questions, ONE AT A TIME, multiple-choice preferred. + - Empty prompt → ask exactly once: "what task?". +5. Invoke `superpowers:brainstorming` via native discovery (`~/.agents/skills/superpowers/brainstorming/SKILL.md`) for any **behavior-changing** task — feature creation, bug fix with multiple plausible approaches, refactor, design decision. Present 2-3 approaches and recommend one before finalizing the plan. The ONLY skip conditions are the same ones that allow TDD auto-skip: `pure-documentation` and `pure-comment-whitespace-rename`. When skipping, record the skip reason in the Interpretation section of `task-plan.md`. + +### Phase 3: Configure Reviewer + +If the user has already specified a reviewer CLI and model (e.g., "do task X, review with codex gpt-5.4"), use those values. If the user says "use defaults" or otherwise opts out of explicit configuration, proceed with `REVIEWER_CLI=codex`, `REVIEWER_MODEL=gpt-5.4`, and `MAX_ROUNDS=10`. Otherwise, ask: + +1. **Which CLI should review both the plan and the implementation?** + - `codex` — OpenAI Codex CLI (`codex exec`) + - `claude` — Claude Code CLI (`claude -p`) + - `cursor` — Cursor Agent CLI (`cursor-agent -p`) + - `opencode` — OpenCode CLI (`opencode run`) + - `skip` — No external review, proceed with user approval only at each loop. + +2. **Which model?** (only if a CLI was chosen) + - For `codex`: default `gpt-5.4`, alternatives: `gpt-5.3-codex`, `o4-mini`, `o3`. + - For `claude`: default `sonnet`, alternatives: `opus`, `haiku`. + - For `cursor`: **run `cursor-agent models` first** to see available models. + - For `opencode`: provider-qualified form `/` (e.g., `anthropic/claude-sonnet-4-5`, `openai/gpt-5.4`). Run `opencode models` to list available models. + - Accept any model string the user provides. + +3. **Max review rounds shared across both loops?** (default: 10) + - If the user does not provide a value, set `MAX_ROUNDS=10`. + +Store `REVIEWER_CLI`, `REVIEWER_MODEL`, and `MAX_ROUNDS` for Phases 5 and 8. + +Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, `pi`, or `skip`. + +If `REVIEWER_CLI=pi`, verify the Pi reviewer binary before entering the review loop: + +```bash +pi --version +``` + +For shorthand `pi/`, split only on the first slash when the prefix is exactly `pi`; store the complete remainder in `REVIEWER_MODEL`. Examples: `pi/claude-opus-4-7` -> `claude-opus-4-7`, `pi/anthropic/claude-opus-4-7` -> `anthropic/claude-opus-4-7`, and `pi/openrouter/anthropic/claude-opus-4-7` -> `openrouter/anthropic/claude-opus-4-7`. + +When `REVIEWER_CLI=pi`, the reviewer model is configured independently from the model running this workflow. If the model/provider is unavailable, surface helper stderr/status and use `pi --list-models [search]` to inspect configured models. + +### Phase 4: Initialize Plan Workspace + +Codex has no plan-mode concept; there is no plan-mode guard here. + +Steps: + +1. Compute slug: `YYYY-MM-DD-` where `` is a kebab-case hash of the task goal (lowercase, alphanumeric + hyphens only). +2. Compute plan folder: `ai_plan//`. +3. **Resume detection:** If the folder already exists, read `task-plan.md`: + - If `Status` is `draft` or `plan-approved` or `implementation-in-progress`: offer to resume, pick a new suffix (`-v2`), or abort. Default is resume. + - If `Status` is any terminal value (`pushed`, `local-only`, `aborted-*`, `failed`): offer a new suffix or abort. Default is new suffix. +4. If not resuming, create the folder and write `task-plan.md` from the template at `templates/task-plan.md` (this skill's template folder; falls back to `~/.codex/skills/do-task/templates/task-plan.md` when installed directly). +5. Fill in: + - `Metadata` block. + - `Prompt` (verbatim). + - `Interpretation`, `Assumptions`, `Files`, `Approach`, `TDD Approach`, `Acceptance Criteria`, `Verification`, `Rollback`. + - Leave `Runtime State`, `Review History`, `Final Status` empty (skill updates these). +6. Set `Status: draft`. + +**Worktree branch:** If the prompt opts in to a worktree (see Trigger Phrase Detection), invoke `superpowers:using-git-worktrees` via native discovery before proceeding. Otherwise continue on the current branch. + +### Phase 5: Plan Review Loop + +If `REVIEWER_CLI=skip`, present `task-plan.md` to the user and proceed only after explicit user approval. + +Otherwise, invoke the Review Loop (Shared Subroutine) with: + +```text +REVIEW_KIND = plan +REVIEW_ID = $(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) +PAYLOAD_PATH = /tmp/do-task-plan-${REVIEW_ID}.md +PROMPT_TEMPLATE = PLAN_REVIEW_PROMPT (see below) +SESSION_ID_VAR = CODEX_PLAN_SESSION_ID | CURSOR_PLAN_SESSION_ID | OPENCODE_PLAN_SESSION_ID +``` + +Payload is the current `task-plan.md` **with the `Runtime State` and `Review History` blocks stripped** before writing to `PAYLOAD_PATH`. Those two blocks contain reviewer session IDs and scan outcomes that must never be sent back to any reviewer CLI. Reviewers only need the Prompt, Interpretation, Assumptions, Files, Approach, TDD Approach, Acceptance Criteria, Verification, Rollback, and Metadata sections. + +`PLAN_REVIEW_PROMPT`: + +```text +Review this task plan for completeness, correctness, and risk. Focus on: +1. Does the plan match the user's prompt? +2. Are all assumptions surfaced? +3. Are acceptance criteria testable? +4. Is the TDD approach appropriate per the TDD Approach section? +5. Are there missing files, risks, or security concerns? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE`. +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking. +``` + +On APPROVED: + +- Set `Status: plan-approved`. +- Append APPROVED row to Review History. +- Proceed to Phase 6. + +On MAX_ROUNDS: + +- Set `Status: aborted-plan-review`. +- Send Telegram summary before stopping. +- Ask the user whether to override and proceed, restart, or abort. + +### Phase 6: Execute (TDD-first where applicable) + +Native orchestration — do not invoke `superpowers:executing-plans`. + +1. Set `Status: implementation-in-progress`. +2. For every behavior-changing file edit: + - Invoke `superpowers:test-driven-development` via native discovery. + - Write the failing test first. Run it. Confirm it fails. + - Implement the minimal code to make it pass. Run the test. Confirm green. + - Do NOT commit yet — a single task commit happens in Phase 9. +3. Auto-skip of TDD is permitted ONLY for tasks classified in `task-plan.md` TDD Approach as: + - `pure-documentation` + - `pure-comment-whitespace-rename` +4. Any other skip (including `pure-config-addition`) requires explicit user approval recorded in `task-plan.md` with an ISO-8601 timestamp. +5. Update `task-plan.md` after each logical step: add notes to `Approach`, check off `Acceptance Criteria` items as they complete. + +### Phase 7: Verification Gate + +Invoke `superpowers:verification-before-completion` via native discovery. + +Run the commands listed in the `Verification` section of `task-plan.md`: + +- Lint (changed files first). +- Typecheck. +- Tests (targeted first, then broader suite if quick). + +All must pass. If a command fails: + +- Fix the issue. +- Re-run that command. +- Increment `verification_attempts` in Runtime State. + +If `verification_attempts` exceeds 3 without green: + +- Set `Status: aborted-verification`. +- Send Telegram summary. +- Ask the user whether to retry, override, or abort. + +### Phase 8: Implementation Review Loop + +If `REVIEWER_CLI=skip`, present a diff + verification summary to the user and proceed only after explicit user approval. + +Otherwise, invoke the Review Loop (Shared Subroutine) with: + +```text +REVIEW_KIND = implementation +REVIEW_ID = $(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) # distinct from plan-review ID +PAYLOAD_PATH = /tmp/do-task-implementation-${REVIEW_ID}.md +PROMPT_TEMPLATE = IMPL_REVIEW_PROMPT (see below) +SESSION_ID_VAR = CODEX_IMPL_SESSION_ID | CURSOR_IMPL_SESSION_ID | OPENCODE_IMPL_SESSION_ID +``` + +Payload contents (assembled by the skill): + +```markdown +# Implementation Review: [Short Title] + +## Task Plan (the plan that was approved) + + +## Changes Made (git diff) + + +## Verification Output +### Lint + +### Typecheck + +### Tests + +``` + +`IMPL_REVIEW_PROMPT`: + +```text +Review this implementation against the task plan. Focus on: +1. Correctness — Does the diff satisfy the Acceptance Criteria? +2. Code quality — Clean, maintainable, no obvious issues? +3. Test coverage — Are behavior changes adequately tested (per the plan's TDD Approach)? +4. Security — Any security concerns introduced? +5. Regressions — Does the diff risk breaking unrelated code? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE`. +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking. +``` + +On APPROVED: + +- Set `Status: implementation-approved`. +- Append APPROVED row to Review History. +- Proceed to Phase 9. + +On MAX_ROUNDS: + +- Set `Status: aborted-impl-review`. +- Send Telegram summary. +- Ask the user whether to override and commit anyway, restart, or abort. + +### Phase 9: Commit + Push Ask + +Invoke `superpowers:finishing-a-development-branch` via native discovery. + +1. Stage all changed files explicitly (avoid `git add -A`). +2. Single commit with message derived from the task goal: + - Format: `(): ` + - Example: `feat(auth): add session token rotation` +3. Do NOT push. Update `Status: local-only`. +4. Ask the user: "Push to remote? (yes / no)" + - On explicit `yes` → push, then set `Status: pushed`. + - Any other response → leave `Status: local-only`. + +### Phase 10: Telegram Notification + Finalize + +Resolve the notifier helper: + +```bash +TELEGRAM_NOTIFY_RUNTIME=~/.codex/skills/reviewer-runtime/notify-telegram.sh +``` + +On every terminal outcome (`pushed`, `local-only`, `aborted-*`, `failed`), send a Telegram summary if both `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` are set: + +```bash +if [ -x "$TELEGRAM_NOTIFY_RUNTIME" ] && [ -n "${TELEGRAM_BOT_TOKEN:-}" ] && [ -n "${TELEGRAM_CHAT_ID:-}" ]; then + "$TELEGRAM_NOTIFY_RUNTIME" --message "do-task : " +fi +``` + +Rules: + +- Telegram is the only supported notification path. +- Notification failures are non-blocking but must be surfaced to the user. +- Before stopping for any user interaction, approval, or manual decision, send a Telegram summary first if configured. +- If Telegram is not configured, state that no Telegram notification was sent. + +Fill in `Final Status` in `task-plan.md` (include commit hash if any). Do NOT delete the plan folder — it stays as a record. + +--- + +## Review Loop (Shared Subroutine) + +This subroutine is invoked twice per `do-task` run: once in Phase 5 (`REVIEW_KIND=plan`) and once in Phase 8 (`REVIEW_KIND=implementation`). Separate session IDs are used for each loop so reviewer context never leaks across loops. + +### Subroutine Inputs + +| Variable | Purpose | +|----------|---------| +| `REVIEW_KIND` | `plan` or `implementation` | +| `REVIEW_ID` | 8-char hex (from `uuidgen`); reused across rounds of the same loop | +| `PAYLOAD_PATH` | `/tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md` | +| `PROMPT_TEMPLATE` | `PLAN_REVIEW_PROMPT` or `IMPL_REVIEW_PROMPT` | +| `REVIEWER_CLI` | `codex` \| `claude` \| `cursor` \| `opencode` \| `pi` | +| `REVIEWER_MODEL` | Model name | +| `MAX_ROUNDS` | Default 10 | +| `SESSION_ID_VAR` | `CODEX_PLAN_SESSION_ID` \| `CODEX_IMPL_SESSION_ID` \| `CURSOR_PLAN_SESSION_ID` \| `CURSOR_IMPL_SESSION_ID` \| `OPENCODE_PLAN_SESSION_ID` \| `OPENCODE_IMPL_SESSION_ID` | + +Temp artifact paths (per loop): + +- `/tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md` — payload +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md` — normalized review text +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json` — raw Cursor/OpenCode JSON (cursor only, plus opencode when `--format json` is used) +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.stderr` +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.status` +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out` +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh` + +Resolve the shared helper: + +```bash +REVIEWER_RUNTIME=~/.codex/skills/reviewer-runtime/run-review.sh +``` + +Set helper success-artifact args before writing the command script: + +```bash +HELPER_SUCCESS_FILE_ARGS=() +case "$REVIEWER_CLI" in + codex) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md) + ;; + cursor) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json) + ;; +esac +``` + +### Step 1: Write Payload + +Write the full payload for this round to `PAYLOAD_PATH`. + +### Step 1a: Secret Scan (per-payload, no caching) + +**BEFORE** sending the payload to any reviewer CLI, scan it for secrets. This scan runs EVERY round — no results are cached. Rationale: Phase 8 payloads include newly-introduced diff content that earlier rounds never saw. + +Run the secret scan with all of these anchored regexes. Use `grep -En` on the payload file: + +```bash +SECRET_REGEX_FILE=$(mktemp) +cat >"$SECRET_REGEX_FILE" <<'EOF' +AKIA[0-9A-Z]{16} +"type"\s*:\s*"service_account" +(ghp|gho|ghs|ghu|ghr)_[A-Za-z0-9]{36,} +xox[abpsr]-[0-9]+-[0-9]+-[0-9]+-[A-Za-z0-9]{24,} +xox[abpsr]-[A-Za-z0-9]{10,48} +sk-(proj-)?[A-Za-z0-9_-]{20,} +sk-ant-(api|admin)[0-9]+-[A-Za-z0-9_-]{20,} +-----BEGIN [A-Z ]+ PRIVATE KEY----- +(TOKEN|SECRET|PASSWORD|API_?KEY|ACCESS_?KEY)\s*=\s*["']?[A-Za-z0-9+/=_-]{8,} +eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+ +EOF + +SCAN_MATCHES=$(grep -Ensf "$SECRET_REGEX_FILE" "$PAYLOAD_PATH" || true) +rm -f "$SECRET_REGEX_FILE" +``` + +If `SCAN_MATCHES` is non-empty: + +1. **Redact the matched text before surfacing** — never echo the raw secret to the user, chat log, terminal scrollback, or any persistent file. Replace each matched substring with a fixed token that preserves only the fact of a match: `[REDACTED::-chars]`. Example: a matched AWS key becomes `[REDACTED:aws-access-key:20-chars]`. Keep the file path and line number; they are useful for the user and not secret. +2. Present the redacted match summary to the user using this exact wording: + + ```text + SECRET-SCAN MATCH in outbound reviewer payload (loop: ${REVIEW_KIND}, round: N): + :: [REDACTED::-chars] + ... + Proceed with sending this payload to ${REVIEWER_CLI}? (yes / no / redact) + ``` + + Pattern labels: `aws-access-key`, `gcp-service-account`, `github-token`, `slack-token`, `openai-key`, `anthropic-key`, `pem-private-key`, `dotenv-style`, `jwt`. + +3. Wait for user response. +4. On `yes`: record `last_scan_outcome_${REVIEW_KIND}=user-approved-with-matches` in Runtime State, and proceed. +5. On `redact`: ask the user to supply redactions, apply them to `PAYLOAD_PATH`, re-scan (this step), record `last_scan_outcome_${REVIEW_KIND}=redacted-and-approved`. +6. On `no`: stop the loop, set `Status: failed`, send Telegram, return to the user. + +If `SCAN_MATCHES` is empty, record `last_scan_outcome_${REVIEW_KIND}=clean` and proceed. + +### Step 2: Generate Reviewer Command Script + +Write the reviewer invocation to `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh` as a bash script starting with: + +```bash +#!/usr/bin/env bash +set -euo pipefail +``` + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call every round (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "Read the file /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md and review. Return exactly the required ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +Round 1 — fresh `codex exec`: + +```bash +codex exec \ + -m ${REVIEWER_MODEL} \ + -s read-only \ + -o /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md \ + "Review the ${REVIEW_KIND} payload in /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md. + +${PROMPT_TEMPLATE}" +``` + +Do not capture the Codex session ID yet. After Round 1 completes, extract it from `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out` (look for `session id: `) and persist it to Runtime State under `${SESSION_ID_VAR}`. + +Round 2 and later — resume session: + +```bash +codex exec resume ${SESSION_ID_VAR_VALUE} \ + -o /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md \ + "I've revised based on your feedback. Updated payload is in /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same ## Summary, ## Findings, and ## Verdict structure as before. +Keep findings ordered P0 to P3, use '- None.' when a severity has no findings, and only use VERDICT: APPROVED when no P0, P1, or P2 findings remain." +``` + +If resume fails, fall back to fresh `codex exec` with prior-round context. + +**If `REVIEWER_CLI` is `claude`:** + +Fresh call every round (Claude CLI has no session resume): + +```bash +claude -p \ + "${ROUND_PREFIX}Review the following ${REVIEW_KIND} payload. + +$(cat /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md) + +${PROMPT_TEMPLATE}" \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +Where `${ROUND_PREFIX}` is empty for Round 1 and `"You previously reviewed this ${REVIEW_KIND} and requested revisions. Previous feedback summary: [key points]. "` for subsequent rounds. + +**If `REVIEWER_CLI` is `cursor`:** + +Round 1: + +```bash +cursor-agent -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "Read the file /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md and review. + +${PROMPT_TEMPLATE}" \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +Round 2 and later — resume: + +```bash +cursor-agent --resume ${SESSION_ID_VAR_VALUE} -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "I've revised based on your feedback. Updated payload is in /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same ## Summary, ## Findings, and ## Verdict structure as before." \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +If resume fails, fall back to fresh `cursor-agent -p`. + +After the command completes, extract the session id and review text: + +```bash +CURSOR_SID=$(jq -r '.session_id' /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json) +jq -r '.result' /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md +``` + +Persist `CURSOR_SID` to Runtime State under `${SESSION_ID_VAR}` on Round 1. + +**If `REVIEWER_CLI` is `opencode`:** + +OpenCode does not expose a dedicated read-only flag at the CLI level; use the built-in `plan` primary agent (`--agent plan`) for review, which is read-oriented and does not modify files. Session resume is supported via `-s `, but the most reliable pattern for non-interactive review is **fresh call each round** (like `claude`) because opencode's session lifecycle and ID capture are less standardized than codex/cursor for headless runs. Skills MAY opt-in to session resume when they have verified the installed opencode version exposes a stable session id in `--format json` output. + +Round 1 (preferred, fresh call): + +```bash +opencode run \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "Read the file /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md and review. + +${PROMPT_TEMPLATE}" \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +Round 2 and later (fresh-call fallback path — recommended default): + +```bash +opencode run \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "You previously reviewed this ${REVIEW_KIND} and requested revisions. + +Previous feedback summary: [key points from last review] + +I've revised. Updated payload is below. + +$(cat /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md) + +Changes made: +[List specific changes] + +Re-review using the same ## Summary, ## Findings, and ## Verdict structure as before." \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +Optional session-resume path (only if the installed opencode reliably emits a session id in `--format json` output and accepts it back via `-s`): + +```bash +# Round 2+ with resume +opencode run \ + -s ${SESSION_ID_VAR_VALUE} \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "I've revised. Updated payload is in /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same ## Summary, ## Findings, and ## Verdict structure as before." \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +Extract the review body (the JSON stream emits events; the final assistant message contains the review text): + +```bash +jq -r '.[] | select(.type == "message" and .role == "assistant") | .content' \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md \ + || cp /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md +``` + +If the JSON parse falls through, promote the raw JSON file as the review output and surface a warning to the user. On any opencode CLI or JSON parsing failure, treat this loop round as `completed-empty-output` and follow the helper-failure escalation in Step 6. + +### Step 3: Run via `run-review.sh` + +Run the command script through the shared helper when available: + +```bash +if [ -x "$REVIEWER_RUNTIME" ]; then + "$REVIEWER_RUNTIME" \ + --command-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh \ + --stdout-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + --stderr-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.stderr \ + --status-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.status \ + "${HELPER_SUCCESS_FILE_ARGS[@]}" +else + echo "Warning: reviewer runtime helper not found at $REVIEWER_RUNTIME; falling back to direct synchronous review." >&2 + bash /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh \ + >/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + 2>/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.stderr +fi +``` + +Run the helper in the foreground and watch live stdout for `state=in-progress` heartbeats. If your agent environment buffers command output until exit, start the helper in the background and poll the `.status` file instead of treating heartbeats as post-hoc-only data. + +### Step 4: Promote Reviewer Output + Capture Session ID + +After the command completes: + +- `cursor`: already promoted in Step 2 via `jq -r '.result' ...`. Also capture `session_id` if first round. +- `codex`: extract `CODEX_SESSION_ID` from `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out` after the helper or fallback run. If the review text lives only in `.runner.out`, `cp` it into the `.md` file: + + ```bash + cp /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md + ``` + +- `claude` or `pi`: promote `.runner.out` into the `.md` file: + + ```bash + cp /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md + ``` + +- `opencode`: already promoted in Step 2 via `jq` on the JSON stream. If opt-in session-resume is active and the JSON includes a stable session id, capture it and persist to `${SESSION_ID_VAR}`. + +On Round 1, persist the captured session ID (if any) into `task-plan.md`'s Runtime State under `${SESSION_ID_VAR}`. + +### Step 5: Parse Verdict + Update Review History + +1. Read `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md`. +2. Append one row to `task-plan.md` Review History: + - Timestamp (ISO-8601 UTC). + - Loop (`plan` or `implementation`). + - Round number. + - Verdict (`APPROVED` or `REVISE`). + - Summary (first line of the `## Summary` section). +3. Increment `plan_review_round` or `implementation_review_round` in Runtime State. + +### Step 6: Branch APPROVED / REVISE / MAX_ROUNDS + +Verdict rules: + +- **VERDICT: APPROVED** with no `P0`, `P1`, or `P2` findings → exit the subroutine with `APPROVED`. +- **VERDICT: APPROVED** with only `P3` findings → optionally fix the `P3` items if cheap and safe, then exit with `APPROVED`. +- **VERDICT: REVISE** or any `P0`, `P1`, or `P2` finding → go to revision (see below), then return to Step 1 for the next round. +- No clear verdict but `P0`, `P1`, and `P2` are all `- None.` → treat as APPROVED. +- Helper state `completed-empty-output` → treat as failed review attempt, surface `.stderr`/`.status`, fix invocation or prompt handling, then retry. +- Helper state `needs-operator-decision` → surface status log and decide whether to extend the timeout, abort, or retry with different helper parameters. +- Round counter ≥ `MAX_ROUNDS` → exit the subroutine with `MAX_ROUNDS`. Caller decides next action per Phase 5 or Phase 8. + +**Revision:** The caller (Phase 5 for plan, Phase 6/7 for implementation) applies findings in priority order (`P0` → `P1` → `P2` → `P3`). For implementation review revisions, Phase 7 verification must be re-run after every revision before returning to Step 1. + +### Step 7: Liveness Contract (during Step 3) + +- The shared reviewer runtime emits `state=in-progress note="In progress N"` heartbeats every 60 seconds while the reviewer child is alive. +- Keep waiting as long as a fresh `In progress N` heartbeat keeps arriving roughly once per minute. +- Do not abort just because the review is slow, a soft timeout fired, or a `stall-warning` line appears, as long as the `In progress N` heartbeat continues. +- Treat missing heartbeats, `state=failed`, `state=completed-empty-output`, and `state=needs-operator-decision` as escalation signals. + +### Step 8: Cleanup (on successful round exit) + +```bash +rm -f /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.stderr \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.status \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh +``` + +If the round failed, produced empty output, or reached operator-decision timeout, KEEP `.stderr`, `.status`, and `.runner.out` until the issue is diagnosed instead of deleting them. + +--- + +## Resume Semantics + +1. Detect existing plan folder by slug at Phase 4. +2. Read `task-plan.md` → `Status`. +3. Decide next action: + + | Status | Action | + |--------|--------| + | `draft` | Resume at Phase 5 (plan review) | + | `plan-approved` | Resume at Phase 6 (execute) | + | `implementation-in-progress` | Resume at Phase 6 (continue execute) | + | `implementation-approved` | Resume at Phase 9 (commit + push ask) | + | `pushed` \| `local-only` | Ask user: new suffix, abort, or replay for reference only | + | `aborted-*` \| `failed` | Offer new suffix or full restart | + +4. When resuming, read Runtime State for `CODEX_PLAN_SESSION_ID`, `CODEX_IMPL_SESSION_ID`, `CURSOR_PLAN_SESSION_ID`, `CURSOR_IMPL_SESSION_ID`, `OPENCODE_PLAN_SESSION_ID`, `OPENCODE_IMPL_SESSION_ID`, and the round counters. If a session ID is populated, use it for the first revision round in that loop (Round 2) via `codex exec resume`, `cursor-agent --resume`, or `opencode run -s ` as applicable. + +--- + +## Tracker Discipline (MANDATORY) + +**ALWAYS update `task-plan.md` before/after each phase transition. NEVER proceed with stale state.** + +Before starting any phase: + +1. Update `Status` if it transitions. +2. Update `last_phase_entered` in Runtime State. + +After completing any phase: + +1. Update `Status` if it transitions. +2. Append notes to the relevant section of `task-plan.md`. + +Review History is append-only. + +--- + +## Execution Workflow Rules + +- Current branch is the default; worktree is opt-in only. +- Do NOT push without explicit "yes". +- Secret scan runs **per-payload, no caching** — every round, including revisions. +- Review loops use `MAX_ROUNDS=10` by default, shared across both loops. +- The task commit is a single commit created in Phase 9; interim WIP commits are NOT created. +- The `.gitignore` infra commit in Phase 1 is explicitly separate from the task commit and is allowed even on abort. + +--- + +## Verification Checklist + +- [ ] `ai_plan/` exists and `/ai_plan/` is in `.gitignore` +- [ ] `task-plan.md` created under `ai_plan/YYYY-MM-DD-/` +- [ ] Reviewer CLI + model + `MAX_ROUNDS` configured (or `skip`) +- [ ] Secret scan ran on every outbound reviewer payload +- [ ] Plan review completed (APPROVED, MAX_ROUNDS handled, or skipped) +- [ ] Phase 6 executed TDD-first for all behavior-changing steps (or documented skip) +- [ ] Phase 7 verification green before Phase 8 +- [ ] Implementation review completed (APPROVED, MAX_ROUNDS handled, or skipped) +- [ ] Single task commit created locally, no push without explicit yes +- [ ] Telegram notification attempted if configured +- [ ] `task-plan.md` Final Status filled in + +--- + +## Variant Hardening Notes — Codex + +- Must use native skill discovery from `~/.agents/skills/` (no CLI wrappers). +- Must verify Superpowers skills symlink: `~/.agents/skills/superpowers -> ~/.codex/superpowers/skills` +- Must invoke required sub-skills with explicit announcements before any action. +- Must track checklist-driven sub-skills with `update_plan` todos (the Codex equivalent of `TodoWrite`). +- `Task` subagents are unavailable in Codex — do the work directly and state the limitation in the plan if a subagent was expected. +- Deprecated CLI commands (`superpowers-codex bootstrap`, `use-skill`) must NOT be used. +- Helper paths are `~/.codex/skills/reviewer-runtime/{run-review.sh,notify-telegram.sh}`. +- No plan-mode guard (Codex has no plan-mode concept). + +## Common Mistakes + +- Using deprecated commands like `superpowers-codex bootstrap` or `superpowers-codex use-skill`. +- Jumping to Phase 6 execution without running `superpowers:brainstorming` on behavior-changing work. +- Asking multiple clarifying questions in a single message. +- Forgetting `update_plan` tracking for sub-skill checklists. +- Forgetting to create/update `.gitignore` for `/ai_plan/`. +- Skipping the per-payload secret scan because "the previous round was clean". +- Pushing the task commit without explicit user approval. + +## Red Flags — Stop and Correct + +- You are about to run any `superpowers-codex` command. +- You are writing `task-plan.md` before the brainstorming step (when required) completes. +- You are pushing commits without user approval. +- You did not announce which skill you invoked and why. +- You are proceeding to implementation review with failing lint/typecheck/tests. +- You are echoing raw secret-scan matches to the user or logs. diff --git a/skills/do-task/_source/codex/templates/task-plan.md b/skills/do-task/_source/codex/templates/task-plan.md new file mode 100644 index 0000000..fb31e06 --- /dev/null +++ b/skills/do-task/_source/codex/templates/task-plan.md @@ -0,0 +1,143 @@ +# Task Plan: [Short Title] + +> **Variant guardrail (Codex):** Sub-skills (`brainstorming`, `test-driven-development`, `verification-before-completion`, `finishing-a-development-branch`, `using-git-worktrees`) MUST be invoked through native skill discovery from `~/.agents/skills/superpowers//SKILL.md` — no `superpowers-codex` CLI wrappers. Checklist-driven sub-skills MUST track items with `update_plan` todos. + +## Metadata + +| Field | Value | +|-------|-------| +| Created | YYYY-MM-DD | +| Slug | YYYY-MM-DD- | +| Runtime | codex | +| Reviewer CLI | codex \| claude \| cursor \| opencode \| pi | +| Reviewer Model | | +| MAX_ROUNDS | 10 | +| Branch Strategy | current-branch \| worktree | +| Branch Name | | +| Worktree Path | | +| Status | draft | + +### Status Enum (authoritative) + +| Value | Meaning | +|-------|---------| +| `draft` | Newly created; plan review not yet started | +| `plan-approved` | Plan review loop returned APPROVED | +| `implementation-in-progress` | Phase 6 executing | +| `implementation-approved` | Phase 8 review loop returned APPROVED; awaiting commit | +| `pushed` | Committed + pushed to remote | +| `local-only` | Committed locally; user declined push | +| `aborted-plan-review` | MAX_ROUNDS reached in Phase 5; user aborted | +| `aborted-impl-review` | MAX_ROUNDS reached in Phase 8; user aborted | +| `aborted-verification` | Phase 7 retries exhausted; user aborted | +| `failed` | Hard tooling failure | + +--- + +## Prompt + + + +## Interpretation + + + +## Assumptions + + + +## Files + + + +| Action | Path | Why | +|--------|------|-----| +| | | | + +## Approach + + + +## TDD Approach + + + +## Acceptance Criteria + +- [ ] +- [ ] + +## Verification + + + +## Rollback + + + +--- + +## Runtime State + + + +```yaml +plan_review_round: 0 +implementation_review_round: 0 +CODEX_PLAN_SESSION_ID: +CODEX_IMPL_SESSION_ID: +CURSOR_PLAN_SESSION_ID: +CURSOR_IMPL_SESSION_ID: +OPENCODE_PLAN_SESSION_ID: +OPENCODE_IMPL_SESSION_ID: +last_phase_entered: +last_round_ts: +last_scan_outcome_plan: +last_scan_outcome_impl: +verification_attempts: 0 +tests_added_count: 0 +tdd_used: false +``` + +## Review History + + + +| Timestamp (ISO-8601) | Loop | Round | Verdict | Summary | +|----------------------|------|-------|---------|---------| +| | | | | | + +## Final Status + + + +--- + +## Guardrails (do NOT remove) + +- This file is the single persistent artifact for `do-task`. Do not split it or delete it on success. +- `Status` must always match one of the 10 enum values. +- `Runtime State` is updated by the skill, not by the user. +- Review History is append-only. +- `last_scan_outcome_plan` and `last_scan_outcome_impl` record the most recent secret-scan result for each loop. They are informational; the scan itself runs per-payload with no caching. diff --git a/skills/do-task/_source/cursor/SKILL.md b/skills/do-task/_source/cursor/SKILL.md new file mode 100644 index 0000000..8f4f6f8 --- /dev/null +++ b/skills/do-task/_source/cursor/SKILL.md @@ -0,0 +1,853 @@ +--- +name: do-task +description: Execute a single user-supplied prompt end-to-end with two reviewer loops (plan review + implementation review) in Cursor Agent CLI. ALWAYS invoke when the user says `/do-task`, "do this task", "do task ...", "execute this task", or "make it so". Also invoke on the hint phrase "just do ...". Do NOT invoke on "implement this" (that phrase is reserved for implement-plan). +--- + +# Do Task (Cursor Agent CLI) + +Execute an ad-hoc user prompt end-to-end: parse → clarify → plan (with reviewer loop) → implement (TDD-first where applicable) → verify → implementation review loop → commit → optional push → notify. + +This is a single-artifact sibling of `create-plan` + `implement-plan`. Unlike `implement-plan`, `do-task` operates on one persistent `task-plan.md` (not a full milestone plan) and defaults to the **current branch** (not a worktree). + +**Core principle:** Cursor Agent CLI discovers skills from `.cursor/skills/` (repo-local), `~/.cursor/skills/` (global), and installed Cursor plugin cache entries. It also reads `AGENTS.md` at the repo root for additional instructions. + +## Prerequisite Check (MANDATORY) + +Required: + +- Cursor Agent CLI: `cursor-agent --version` (install via `curl https://cursor.com/install -fsS | bash`). Binary is `cursor-agent`; the alias `cursor agent` also works. +- `jq` (**required** — `do-task` always parses JSON output from at least the cursor reviewer branch, and other reviewers may produce JSON). Install via `brew install jq` (macOS) or your package manager. Verify: `jq --version`. +- Superpowers repo: `https://github.com/obra/superpowers` +- Superpowers skills available from the Cursor plugin cache, `.cursor/skills/` (repo-local), or `~/.cursor/skills/` (global). Do not install both the plugin and a manual Superpowers copy, or Cursor may show duplicate skill entries. +- `superpowers:brainstorming` +- `superpowers:test-driven-development` +- `superpowers:verification-before-completion` +- `superpowers:finishing-a-development-branch` +- `superpowers:using-git-worktrees` (only when the prompt opts in to a worktree) +- Shared reviewer runtime: `.cursor/skills/reviewer-runtime/run-review.sh` (repo-local, preferred) OR `~/.cursor/skills/reviewer-runtime/run-review.sh` (global fallback) +- Telegram notifier helper: `.cursor/skills/reviewer-runtime/notify-telegram.sh` OR `~/.cursor/skills/reviewer-runtime/notify-telegram.sh` + +Verify before proceeding: + +```bash +cursor-agent --version +jq --version +test -f .cursor/skills/superpowers/skills/brainstorming/SKILL.md || test -f ~/.cursor/skills/superpowers/skills/brainstorming/SKILL.md || find ~/.cursor/plugins/cache/cursor-public/superpowers -path '*/skills/brainstorming/SKILL.md' -print -quit 2>/dev/null | grep -q . +test -f .cursor/skills/superpowers/skills/test-driven-development/SKILL.md || test -f ~/.cursor/skills/superpowers/skills/test-driven-development/SKILL.md || find ~/.cursor/plugins/cache/cursor-public/superpowers -path '*/skills/test-driven-development/SKILL.md' -print -quit 2>/dev/null | grep -q . +test -f .cursor/skills/superpowers/skills/verification-before-completion/SKILL.md || test -f ~/.cursor/skills/superpowers/skills/verification-before-completion/SKILL.md || find ~/.cursor/plugins/cache/cursor-public/superpowers -path '*/skills/verification-before-completion/SKILL.md' -print -quit 2>/dev/null | grep -q . +test -f .cursor/skills/superpowers/skills/finishing-a-development-branch/SKILL.md || test -f ~/.cursor/skills/superpowers/skills/finishing-a-development-branch/SKILL.md || find ~/.cursor/plugins/cache/cursor-public/superpowers -path '*/skills/finishing-a-development-branch/SKILL.md' -print -quit 2>/dev/null | grep -q . +``` + +If any required dependency is missing, stop immediately and return: + +`Missing dependency: [specific missing item]. Install Cursor Agent CLI, jq, and the Cursor Superpowers plugin or Superpowers skills under .cursor/skills/ or ~/.cursor/skills/, then retry.` + +## Required Skill Invocation Rules + +- Invoke relevant skills through Cursor-native discovery (`.cursor/skills/`, `~/.cursor/skills/`, or installed Cursor plugin cache entries). +- Announce skill usage explicitly: + - `I've read the [Skill Name] skill and I'm using it to [purpose].` +- For skills with checklists, track checklist items explicitly in conversation. +- The reviewer CLI branch for `cursor` MUST use `--mode=ask --trust --output-format json`. Never use `--mode=agent` or `--force` for reviewer calls. + +## Trigger Phrase Detection + +**Binding triggers** (always invoke this skill): + +- `/do-task` +- "do this task" +- "do task ..." +- "execute this task" +- "make it so" + +**Hint trigger** (invoke unless context clearly maps to another skill): + +- "just do ..." + +**Escape phrases** (skip the Phase 2 clarifying-question loop): + +- `--no-questions` +- `"just do it:"` +- `"just do this:"` +- `"no questions:"` + +**Excluded** (do NOT trigger `do-task`): + +- "implement this" — reserved for `implement-plan`. + +**Dropped defaults** (explicitly NOT binding triggers): + +- "work on ..." +- "handle this" +- "take care of ..." +- "get this done" + +**Worktree opt-in phrases** (Phase 4 takes the worktree branch): + +- "in a worktree" +- "use a worktree" +- "on an isolated branch" +- "on a new branch called X" + +## Process + +### Phase 1: Preflight + +1. Verify git repo: `git rev-parse --is-inside-work-tree`. +2. Verify `/ai_plan/` is present in `.gitignore`. If missing: + - Append `/ai_plan/` to `.gitignore`. + - Commit that infra change immediately with message `chore(gitignore): ignore ai_plan local planning artifacts`. + - This infra commit is EXPLICITLY separate from the task commit in Phase 9. It may occur even when the final task ends up `aborted` or `failed`. +3. Verify required sub-skills are discoverable through Cursor-native skill discovery: the Cursor Superpowers plugin cache, `.cursor/skills/superpowers/skills/`, or `~/.cursor/skills/superpowers/skills/`. Announce each sub-skill before invocation using: + `I've read the [Skill Name] skill and I'm using it to [purpose].` + +### Phase 2: Parse Prompt and Question + +1. Capture the exact user prompt verbatim. +2. Detect trigger phrase (see above) and record which one matched. +3. Detect escape phrase. If set, skip clarifying questions entirely. +4. Apply the ask-first heuristic: + - Skip clarifying questions ONLY if ALL are true: + - Prompt names a concrete target (file, feature, or function). + - Prompt names a concrete outcome (what success looks like). + - Prompt has no ambiguous scope (no "and maybe also ..."). + - All identifiers in the prompt are resolvable against the codebase. + - Otherwise, ask 1-3 clarifying questions, ONE AT A TIME, multiple-choice preferred. + - Empty prompt → ask exactly once: "what task?". +5. Invoke `superpowers:brainstorming` through Cursor-native skill discovery for any **behavior-changing** task — feature creation, bug fix with multiple plausible approaches, refactor, design decision. Present 2-3 approaches and recommend one before finalizing the plan. The ONLY skip conditions are the same ones that allow TDD auto-skip: `pure-documentation` and `pure-comment-whitespace-rename`. When skipping, record the skip reason in the Interpretation section of `task-plan.md`. + +### Phase 3: Configure Reviewer + +If the user has already specified a reviewer CLI and model (e.g., "do task X, review with codex gpt-5.4"), use those values. If the user says "use defaults" or otherwise opts out of explicit configuration, proceed with `REVIEWER_CLI=codex`, `REVIEWER_MODEL=gpt-5.4`, and `MAX_ROUNDS=10`. Otherwise, ask: + +1. **Which CLI should review both the plan and the implementation?** + - `codex` — OpenAI Codex CLI (`codex exec`) + - `claude` — Claude Code CLI (`claude -p`) + - `cursor` — Cursor Agent CLI (`cursor-agent -p`) + - `opencode` — OpenCode CLI (`opencode run`) + - `skip` — No external review, proceed with user approval only at each loop. + +2. **Which model?** (only if a CLI was chosen) + - For `codex`: default `gpt-5.4`, alternatives: `gpt-5.3-codex`, `o4-mini`, `o3`. + - For `claude`: default `sonnet`, alternatives: `opus`, `haiku`. + - For `cursor`: **run `cursor-agent models` first** to see available models. + - For `opencode`: provider-qualified form `/` (e.g., `anthropic/claude-sonnet-4-5`, `openai/gpt-5.4`). Run `opencode models` to list available models. + - Accept any model string the user provides. + +3. **Max review rounds shared across both loops?** (default: 10) + - If the user does not provide a value, set `MAX_ROUNDS=10`. + +Store `REVIEWER_CLI`, `REVIEWER_MODEL`, and `MAX_ROUNDS` for Phases 5 and 8. + +Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, `pi`, or `skip`. + +If `REVIEWER_CLI=pi`, verify the Pi reviewer binary before entering the review loop: + +```bash +pi --version +``` + +For shorthand `pi/`, split only on the first slash when the prefix is exactly `pi`; store the complete remainder in `REVIEWER_MODEL`. Examples: `pi/claude-opus-4-7` -> `claude-opus-4-7`, `pi/anthropic/claude-opus-4-7` -> `anthropic/claude-opus-4-7`, and `pi/openrouter/anthropic/claude-opus-4-7` -> `openrouter/anthropic/claude-opus-4-7`. + +When `REVIEWER_CLI=pi`, the reviewer model is configured independently from the model running this workflow. If the model/provider is unavailable, surface helper stderr/status and use `pi --list-models [search]` to inspect configured models. + +### Phase 4: Initialize Plan Workspace + +Cursor Agent CLI has no plan-mode concept; there is no plan-mode guard here. + +Steps: + +1. Compute slug: `YYYY-MM-DD-` where `` is a kebab-case hash of the task goal (lowercase, alphanumeric + hyphens only). +2. Compute plan folder: `ai_plan//`. +3. **Resume detection:** If the folder already exists, read `task-plan.md`: + - If `Status` is `draft` or `plan-approved` or `implementation-in-progress`: offer to resume, pick a new suffix (`-v2`), or abort. Default is resume. + - If `Status` is any terminal value (`pushed`, `local-only`, `aborted-*`, `failed`): offer a new suffix or abort. Default is new suffix. +4. If not resuming, create the folder and write `task-plan.md` from the template at `templates/task-plan.md` (this skill's template folder; resolves to `.cursor/skills/do-task/templates/task-plan.md` repo-local or `~/.cursor/skills/do-task/templates/task-plan.md` global when installed directly). +5. Fill in: + - `Metadata` block. + - `Prompt` (verbatim). + - `Interpretation`, `Assumptions`, `Files`, `Approach`, `TDD Approach`, `Acceptance Criteria`, `Verification`, `Rollback`. + - Leave `Runtime State`, `Review History`, `Final Status` empty (skill updates these). +6. Set `Status: draft`. + +**Worktree branch:** If the prompt opts in to a worktree (see Trigger Phrase Detection), invoke `superpowers:using-git-worktrees` via Cursor-native discovery before proceeding. Otherwise continue on the current branch. + +### Phase 5: Plan Review Loop + +If `REVIEWER_CLI=skip`, present `task-plan.md` to the user and proceed only after explicit user approval. + +Otherwise, invoke the Review Loop (Shared Subroutine) with: + +```text +REVIEW_KIND = plan +REVIEW_ID = $(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) +PAYLOAD_PATH = /tmp/do-task-plan-${REVIEW_ID}.md +PROMPT_TEMPLATE = PLAN_REVIEW_PROMPT (see below) +SESSION_ID_VAR = CODEX_PLAN_SESSION_ID | CURSOR_PLAN_SESSION_ID | OPENCODE_PLAN_SESSION_ID +``` + +Payload is the current `task-plan.md` **with the `Runtime State` and `Review History` blocks stripped** before writing to `PAYLOAD_PATH`. Those two blocks contain reviewer session IDs and scan outcomes that must never be sent back to any reviewer CLI. Reviewers only need the Prompt, Interpretation, Assumptions, Files, Approach, TDD Approach, Acceptance Criteria, Verification, Rollback, and Metadata sections. + +`PLAN_REVIEW_PROMPT`: + +```text +Review this task plan for completeness, correctness, and risk. Focus on: +1. Does the plan match the user's prompt? +2. Are all assumptions surfaced? +3. Are acceptance criteria testable? +4. Is the TDD approach appropriate per the TDD Approach section? +5. Are there missing files, risks, or security concerns? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE`. +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking. +``` + +On APPROVED: + +- Set `Status: plan-approved`. +- Append APPROVED row to Review History. +- Proceed to Phase 6. + +On MAX_ROUNDS: + +- Set `Status: aborted-plan-review`. +- Send Telegram summary before stopping. +- Ask the user whether to override and proceed, restart, or abort. + +### Phase 6: Execute (TDD-first where applicable) + +Native orchestration — do not invoke `superpowers:executing-plans`. + +1. Set `Status: implementation-in-progress`. +2. For every behavior-changing file edit: + - Invoke `superpowers:test-driven-development` via Cursor-native discovery. + - Write the failing test first. Run it. Confirm it fails. + - Implement the minimal code to make it pass. Run the test. Confirm green. + - Do NOT commit yet — a single task commit happens in Phase 9. +3. Auto-skip of TDD is permitted ONLY for tasks classified in `task-plan.md` TDD Approach as: + - `pure-documentation` + - `pure-comment-whitespace-rename` +4. Any other skip (including `pure-config-addition`) requires explicit user approval recorded in `task-plan.md` with an ISO-8601 timestamp. +5. Update `task-plan.md` after each logical step: add notes to `Approach`, check off `Acceptance Criteria` items as they complete. + +### Phase 7: Verification Gate + +Invoke `superpowers:verification-before-completion` via Cursor-native discovery. + +Run the commands listed in the `Verification` section of `task-plan.md`: + +- Lint (changed files first). +- Typecheck. +- Tests (targeted first, then broader suite if quick). + +All must pass. If a command fails: + +- Fix the issue. +- Re-run that command. +- Increment `verification_attempts` in Runtime State. + +If `verification_attempts` exceeds 3 without green: + +- Set `Status: aborted-verification`. +- Send Telegram summary. +- Ask the user whether to retry, override, or abort. + +### Phase 8: Implementation Review Loop + +If `REVIEWER_CLI=skip`, present a diff + verification summary to the user and proceed only after explicit user approval. + +Otherwise, invoke the Review Loop (Shared Subroutine) with: + +```text +REVIEW_KIND = implementation +REVIEW_ID = $(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) # distinct from plan-review ID +PAYLOAD_PATH = /tmp/do-task-implementation-${REVIEW_ID}.md +PROMPT_TEMPLATE = IMPL_REVIEW_PROMPT (see below) +SESSION_ID_VAR = CODEX_IMPL_SESSION_ID | CURSOR_IMPL_SESSION_ID | OPENCODE_IMPL_SESSION_ID +``` + +Payload contents (assembled by the skill): + +```markdown +# Implementation Review: [Short Title] + +## Task Plan (the plan that was approved) + + +## Changes Made (git diff) + + +## Verification Output +### Lint + +### Typecheck + +### Tests + +``` + +`IMPL_REVIEW_PROMPT`: + +```text +Review this implementation against the task plan. Focus on: +1. Correctness — Does the diff satisfy the Acceptance Criteria? +2. Code quality — Clean, maintainable, no obvious issues? +3. Test coverage — Are behavior changes adequately tested (per the plan's TDD Approach)? +4. Security — Any security concerns introduced? +5. Regressions — Does the diff risk breaking unrelated code? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE`. +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking. +``` + +On APPROVED: + +- Set `Status: implementation-approved`. +- Append APPROVED row to Review History. +- Proceed to Phase 9. + +On MAX_ROUNDS: + +- Set `Status: aborted-impl-review`. +- Send Telegram summary. +- Ask the user whether to override and commit anyway, restart, or abort. + +### Phase 9: Commit + Push Ask + +Invoke `superpowers:finishing-a-development-branch` via Cursor-native discovery. + +1. Stage all changed files explicitly (avoid `git add -A`). +2. Single commit with message derived from the task goal: + - Format: `(): ` + - Example: `feat(auth): add session token rotation` +3. Do NOT push. Update `Status: local-only`. +4. Ask the user: "Push to remote? (yes / no)" + - On explicit `yes` → push, then set `Status: pushed`. + - Any other response → leave `Status: local-only`. + +### Phase 10: Telegram Notification + Finalize + +Resolve the notifier helper (prefer repo-local, fall back to global): + +```bash +if [ -x .cursor/skills/reviewer-runtime/notify-telegram.sh ]; then + TELEGRAM_NOTIFY_RUNTIME=.cursor/skills/reviewer-runtime/notify-telegram.sh +else + TELEGRAM_NOTIFY_RUNTIME=~/.cursor/skills/reviewer-runtime/notify-telegram.sh +fi +``` + +On every terminal outcome (`pushed`, `local-only`, `aborted-*`, `failed`), send a Telegram summary if both `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` are set: + +```bash +if [ -x "$TELEGRAM_NOTIFY_RUNTIME" ] && [ -n "${TELEGRAM_BOT_TOKEN:-}" ] && [ -n "${TELEGRAM_CHAT_ID:-}" ]; then + "$TELEGRAM_NOTIFY_RUNTIME" --message "do-task : " +fi +``` + +Rules: + +- Telegram is the only supported notification path. +- Notification failures are non-blocking but must be surfaced to the user. +- Before stopping for any user interaction, approval, or manual decision, send a Telegram summary first if configured. +- If Telegram is not configured, state that no Telegram notification was sent. + +Fill in `Final Status` in `task-plan.md` (include commit hash if any). Do NOT delete the plan folder — it stays as a record. + +--- + +## Review Loop (Shared Subroutine) + +This subroutine is invoked twice per `do-task` run: once in Phase 5 (`REVIEW_KIND=plan`) and once in Phase 8 (`REVIEW_KIND=implementation`). Separate session IDs are used for each loop so reviewer context never leaks across loops. + +### Subroutine Inputs + +| Variable | Purpose | +|----------|---------| +| `REVIEW_KIND` | `plan` or `implementation` | +| `REVIEW_ID` | 8-char hex (from `uuidgen`); reused across rounds of the same loop | +| `PAYLOAD_PATH` | `/tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md` | +| `PROMPT_TEMPLATE` | `PLAN_REVIEW_PROMPT` or `IMPL_REVIEW_PROMPT` | +| `REVIEWER_CLI` | `codex` \| `claude` \| `cursor` \| `opencode` \| `pi` | +| `REVIEWER_MODEL` | Model name | +| `MAX_ROUNDS` | Default 10 | +| `SESSION_ID_VAR` | `CODEX_PLAN_SESSION_ID` \| `CODEX_IMPL_SESSION_ID` \| `CURSOR_PLAN_SESSION_ID` \| `CURSOR_IMPL_SESSION_ID` \| `OPENCODE_PLAN_SESSION_ID` \| `OPENCODE_IMPL_SESSION_ID` | + +Temp artifact paths (per loop): + +- `/tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md` — payload +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md` — normalized review text +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json` — raw Cursor/OpenCode JSON (cursor only, plus opencode when `--format json` is used) +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.stderr` +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.status` +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out` +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh` + +Resolve the shared helper (prefer repo-local, fall back to global): + +```bash +if [ -x .cursor/skills/reviewer-runtime/run-review.sh ]; then + REVIEWER_RUNTIME=.cursor/skills/reviewer-runtime/run-review.sh +else + REVIEWER_RUNTIME=~/.cursor/skills/reviewer-runtime/run-review.sh +fi +``` + +Set helper success-artifact args before writing the command script: + +```bash +HELPER_SUCCESS_FILE_ARGS=() +case "$REVIEWER_CLI" in + codex) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md) + ;; + cursor) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json) + ;; +esac +``` + +### Step 1: Write Payload + +Write the full payload for this round to `PAYLOAD_PATH`. + +### Step 1a: Secret Scan (per-payload, no caching) + +**BEFORE** sending the payload to any reviewer CLI, scan it for secrets. This scan runs EVERY round — no results are cached. Rationale: Phase 8 payloads include newly-introduced diff content that earlier rounds never saw. + +Run the secret scan with all of these anchored regexes. Use `grep -En` on the payload file: + +```bash +SECRET_REGEX_FILE=$(mktemp) +cat >"$SECRET_REGEX_FILE" <<'EOF' +AKIA[0-9A-Z]{16} +"type"\s*:\s*"service_account" +(ghp|gho|ghs|ghu|ghr)_[A-Za-z0-9]{36,} +xox[abpsr]-[0-9]+-[0-9]+-[0-9]+-[A-Za-z0-9]{24,} +xox[abpsr]-[A-Za-z0-9]{10,48} +sk-(proj-)?[A-Za-z0-9_-]{20,} +sk-ant-(api|admin)[0-9]+-[A-Za-z0-9_-]{20,} +-----BEGIN [A-Z ]+ PRIVATE KEY----- +(TOKEN|SECRET|PASSWORD|API_?KEY|ACCESS_?KEY)\s*=\s*["']?[A-Za-z0-9+/=_-]{8,} +eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+ +EOF + +SCAN_MATCHES=$(grep -Ensf "$SECRET_REGEX_FILE" "$PAYLOAD_PATH" || true) +rm -f "$SECRET_REGEX_FILE" +``` + +If `SCAN_MATCHES` is non-empty: + +1. **Redact the matched text before surfacing** — never echo the raw secret to the user, chat log, terminal scrollback, or any persistent file. Replace each matched substring with a fixed token that preserves only the fact of a match: `[REDACTED::-chars]`. Example: a matched AWS key becomes `[REDACTED:aws-access-key:20-chars]`. Keep the file path and line number; they are useful for the user and not secret. +2. Present the redacted match summary to the user using this exact wording: + + ```text + SECRET-SCAN MATCH in outbound reviewer payload (loop: ${REVIEW_KIND}, round: N): + :: [REDACTED::-chars] + ... + Proceed with sending this payload to ${REVIEWER_CLI}? (yes / no / redact) + ``` + + Pattern labels: `aws-access-key`, `gcp-service-account`, `github-token`, `slack-token`, `openai-key`, `anthropic-key`, `pem-private-key`, `dotenv-style`, `jwt`. + +3. Wait for user response. +4. On `yes`: record `last_scan_outcome_${REVIEW_KIND}=user-approved-with-matches` in Runtime State, and proceed. +5. On `redact`: ask the user to supply redactions, apply them to `PAYLOAD_PATH`, re-scan (this step), record `last_scan_outcome_${REVIEW_KIND}=redacted-and-approved`. +6. On `no`: stop the loop, set `Status: failed`, send Telegram, return to the user. + +If `SCAN_MATCHES` is empty, record `last_scan_outcome_${REVIEW_KIND}=clean` and proceed. + +### Step 2: Generate Reviewer Command Script + +Write the reviewer invocation to `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh` as a bash script starting with: + +```bash +#!/usr/bin/env bash +set -euo pipefail +``` + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call every round (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "Read the file /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md and review. Return exactly the required ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +Round 1 — fresh `codex exec`: + +```bash +codex exec \ + -m ${REVIEWER_MODEL} \ + -s read-only \ + -o /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md \ + "Review the ${REVIEW_KIND} payload in /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md. + +${PROMPT_TEMPLATE}" +``` + +Do not capture the Codex session ID yet. After Round 1 completes, extract it from `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out` (look for `session id: `) and persist it to Runtime State under `${SESSION_ID_VAR}`. + +Round 2 and later — resume session: + +```bash +codex exec resume ${SESSION_ID_VAR_VALUE} \ + -o /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md \ + "I've revised based on your feedback. Updated payload is in /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same ## Summary, ## Findings, and ## Verdict structure as before. +Keep findings ordered P0 to P3, use '- None.' when a severity has no findings, and only use VERDICT: APPROVED when no P0, P1, or P2 findings remain." +``` + +If resume fails, fall back to fresh `codex exec` with prior-round context. + +**If `REVIEWER_CLI` is `claude`:** + +Fresh call every round (Claude CLI has no session resume): + +```bash +claude -p \ + "${ROUND_PREFIX}Review the following ${REVIEW_KIND} payload. + +$(cat /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md) + +${PROMPT_TEMPLATE}" \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +Where `${ROUND_PREFIX}` is empty for Round 1 and `"You previously reviewed this ${REVIEW_KIND} and requested revisions. Previous feedback summary: [key points]. "` for subsequent rounds. + +**If `REVIEWER_CLI` is `cursor`:** + +Round 1: + +```bash +cursor-agent -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "Read the file /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md and review. + +${PROMPT_TEMPLATE}" \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +Round 2 and later — resume: + +```bash +cursor-agent --resume ${SESSION_ID_VAR_VALUE} -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "I've revised based on your feedback. Updated payload is in /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same ## Summary, ## Findings, and ## Verdict structure as before." \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +If resume fails, fall back to fresh `cursor-agent -p`. + +After the command completes, extract the session id and review text: + +```bash +CURSOR_SID=$(jq -r '.session_id' /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json) +jq -r '.result' /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md +``` + +Persist `CURSOR_SID` to Runtime State under `${SESSION_ID_VAR}` on Round 1. + +**If `REVIEWER_CLI` is `opencode`:** + +OpenCode does not expose a dedicated read-only flag at the CLI level; use the built-in `plan` primary agent (`--agent plan`) for review, which is read-oriented and does not modify files. Session resume is supported via `-s `, but the most reliable pattern for non-interactive review is **fresh call each round** (like `claude`) because opencode's session lifecycle and ID capture are less standardized than codex/cursor for headless runs. Skills MAY opt-in to session resume when they have verified the installed opencode version exposes a stable session id in `--format json` output. + +Round 1 (preferred, fresh call): + +```bash +opencode run \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "Read the file /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md and review. + +${PROMPT_TEMPLATE}" \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +Round 2 and later (fresh-call fallback path — recommended default): + +```bash +opencode run \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "You previously reviewed this ${REVIEW_KIND} and requested revisions. + +Previous feedback summary: [key points from last review] + +I've revised. Updated payload is below. + +$(cat /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md) + +Changes made: +[List specific changes] + +Re-review using the same ## Summary, ## Findings, and ## Verdict structure as before." \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +Optional session-resume path (only if the installed opencode reliably emits a session id in `--format json` output and accepts it back via `-s`): + +```bash +# Round 2+ with resume +opencode run \ + -s ${SESSION_ID_VAR_VALUE} \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "I've revised. Updated payload is in /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same ## Summary, ## Findings, and ## Verdict structure as before." \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +Extract the review body (the JSON stream emits events; the final assistant message contains the review text): + +```bash +jq -r '.[] | select(.type == "message" and .role == "assistant") | .content' \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md \ + || cp /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md +``` + +If the JSON parse falls through, promote the raw JSON file as the review output and surface a warning to the user. On any opencode CLI or JSON parsing failure, treat this loop round as `completed-empty-output` and follow the helper-failure escalation in Step 6. + +### Step 3: Run via `run-review.sh` + +Run the command script through the shared helper when available: + +```bash +if [ -x "$REVIEWER_RUNTIME" ]; then + "$REVIEWER_RUNTIME" \ + --command-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh \ + --stdout-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + --stderr-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.stderr \ + --status-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.status \ + "${HELPER_SUCCESS_FILE_ARGS[@]}" +else + echo "Warning: reviewer runtime helper not found at $REVIEWER_RUNTIME; falling back to direct synchronous review." >&2 + bash /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh \ + >/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + 2>/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.stderr +fi +``` + +Run the helper in the foreground and watch live stdout for `state=in-progress` heartbeats. If your agent environment buffers command output until exit, start the helper in the background and poll the `.status` file instead of treating heartbeats as post-hoc-only data. + +### Step 4: Promote Reviewer Output + Capture Session ID + +After the command completes: + +- `cursor`: already promoted in Step 2 via `jq -r '.result' ...`. Also capture `session_id` if first round. +- `codex`: extract `CODEX_SESSION_ID` from `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out` after the helper or fallback run. If the review text lives only in `.runner.out`, `cp` it into the `.md` file: + + ```bash + cp /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md + ``` + +- `claude` or `pi`: promote `.runner.out` into the `.md` file: + + ```bash + cp /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md + ``` + +- `opencode`: already promoted in Step 2 via `jq` on the JSON stream. If opt-in session-resume is active and the JSON includes a stable session id, capture it and persist to `${SESSION_ID_VAR}`. + +On Round 1, persist the captured session ID (if any) into `task-plan.md`'s Runtime State under `${SESSION_ID_VAR}`. + +### Step 5: Parse Verdict + Update Review History + +1. Read `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md`. +2. Append one row to `task-plan.md` Review History: + - Timestamp (ISO-8601 UTC). + - Loop (`plan` or `implementation`). + - Round number. + - Verdict (`APPROVED` or `REVISE`). + - Summary (first line of the `## Summary` section). +3. Increment `plan_review_round` or `implementation_review_round` in Runtime State. + +### Step 6: Branch APPROVED / REVISE / MAX_ROUNDS + +Verdict rules: + +- **VERDICT: APPROVED** with no `P0`, `P1`, or `P2` findings → exit the subroutine with `APPROVED`. +- **VERDICT: APPROVED** with only `P3` findings → optionally fix the `P3` items if cheap and safe, then exit with `APPROVED`. +- **VERDICT: REVISE** or any `P0`, `P1`, or `P2` finding → go to revision (see below), then return to Step 1 for the next round. +- No clear verdict but `P0`, `P1`, and `P2` are all `- None.` → treat as APPROVED. +- Helper state `completed-empty-output` → treat as failed review attempt, surface `.stderr`/`.status`, fix invocation or prompt handling, then retry. +- Helper state `needs-operator-decision` → surface status log and decide whether to extend the timeout, abort, or retry with different helper parameters. +- Round counter ≥ `MAX_ROUNDS` → exit the subroutine with `MAX_ROUNDS`. Caller decides next action per Phase 5 or Phase 8. + +**Revision:** The caller (Phase 5 for plan, Phase 6/7 for implementation) applies findings in priority order (`P0` → `P1` → `P2` → `P3`). For implementation review revisions, Phase 7 verification must be re-run after every revision before returning to Step 1. + +### Step 7: Liveness Contract (during Step 3) + +- The shared reviewer runtime emits `state=in-progress note="In progress N"` heartbeats every 60 seconds while the reviewer child is alive. +- Keep waiting as long as a fresh `In progress N` heartbeat keeps arriving roughly once per minute. +- Do not abort just because the review is slow, a soft timeout fired, or a `stall-warning` line appears, as long as the `In progress N` heartbeat continues. +- Treat missing heartbeats, `state=failed`, `state=completed-empty-output`, and `state=needs-operator-decision` as escalation signals. + +### Step 8: Cleanup (on successful round exit) + +```bash +rm -f /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.stderr \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.status \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh +``` + +If the round failed, produced empty output, or reached operator-decision timeout, KEEP `.stderr`, `.status`, and `.runner.out` until the issue is diagnosed instead of deleting them. + +--- + +## Resume Semantics + +1. Detect existing plan folder by slug at Phase 4. +2. Read `task-plan.md` → `Status`. +3. Decide next action: + + | Status | Action | + |--------|--------| + | `draft` | Resume at Phase 5 (plan review) | + | `plan-approved` | Resume at Phase 6 (execute) | + | `implementation-in-progress` | Resume at Phase 6 (continue execute) | + | `implementation-approved` | Resume at Phase 9 (commit + push ask) | + | `pushed` \| `local-only` | Ask user: new suffix, abort, or replay for reference only | + | `aborted-*` \| `failed` | Offer new suffix or full restart | + +4. When resuming, read Runtime State for `CODEX_PLAN_SESSION_ID`, `CODEX_IMPL_SESSION_ID`, `CURSOR_PLAN_SESSION_ID`, `CURSOR_IMPL_SESSION_ID`, `OPENCODE_PLAN_SESSION_ID`, `OPENCODE_IMPL_SESSION_ID`, and the round counters. If a session ID is populated, use it for the first revision round in that loop (Round 2) via `codex exec resume`, `cursor-agent --resume`, or `opencode run -s ` as applicable. + +--- + +## Tracker Discipline (MANDATORY) + +**ALWAYS update `task-plan.md` before/after each phase transition. NEVER proceed with stale state.** + +Before starting any phase: + +1. Update `Status` if it transitions. +2. Update `last_phase_entered` in Runtime State. + +After completing any phase: + +1. Update `Status` if it transitions. +2. Append notes to the relevant section of `task-plan.md`. + +Review History is append-only. + +--- + +## Execution Workflow Rules + +- Current branch is the default; worktree is opt-in only. +- Do NOT push without explicit "yes". +- Secret scan runs **per-payload, no caching** — every round, including revisions. +- Review loops use `MAX_ROUNDS=10` by default, shared across both loops. +- The task commit is a single commit created in Phase 9; interim WIP commits are NOT created. +- The `.gitignore` infra commit in Phase 1 is explicitly separate from the task commit and is allowed even on abort. + +--- + +## Verification Checklist + +- [ ] `ai_plan/` exists and `/ai_plan/` is in `.gitignore` +- [ ] `task-plan.md` created under `ai_plan/YYYY-MM-DD-/` +- [ ] Reviewer CLI + model + `MAX_ROUNDS` configured (or `skip`) +- [ ] Secret scan ran on every outbound reviewer payload +- [ ] Plan review completed (APPROVED, MAX_ROUNDS handled, or skipped) +- [ ] Phase 6 executed TDD-first for all behavior-changing steps (or documented skip) +- [ ] Phase 7 verification green before Phase 8 +- [ ] Implementation review completed (APPROVED, MAX_ROUNDS handled, or skipped) +- [ ] Single task commit created locally, no push without explicit yes +- [ ] Telegram notification attempted if configured +- [ ] `task-plan.md` Final Status filled in + +--- + +## Variant Hardening Notes — Cursor + +- Must use Cursor-native discovery from `.cursor/skills/`, `~/.cursor/skills/`, or installed Cursor plugin cache entries. +- Reviewer invocation MUST use `--mode=ask --trust --output-format json`. Never `--mode=agent`, never `--force`, never write-capable modes for reviewer calls. +- `jq` is a hard prerequisite because the cursor reviewer branch always returns JSON that must be parsed for `session_id` and `result`. +- Helper paths are `.cursor/skills/reviewer-runtime/...` preferred, with `~/.cursor/skills/reviewer-runtime/...` as the fallback path. +- Sub-skills are invoked through Cursor-native discovery with explicit announcement — Cursor does not expose a `Skill` tool. +- `cursor-agent --version` must succeed as part of the prerequisite check. The binary is `cursor-agent`; the alias `cursor agent` is equivalent. +- No plan-mode guard (Cursor Agent CLI has no plan-mode concept). + +## Common Mistakes + +- Forgetting `--trust` flag when running `cursor-agent` non-interactively (causes interactive prompt). +- Using `--mode=agent` or `--force` for reviews (reviewer must be read-only, use `--mode=ask`). +- Forgetting to install `jq` (cursor reviewer branch silently breaks). +- Skipping workspace-discovery skill verification in Prerequisite Check. +- Asking multiple clarifying questions in a single message. +- Skipping the per-payload secret scan because "the previous round was clean". +- Pushing the task commit without explicit user approval. + +## Red Flags — Stop and Correct + +- Reviewer CLI is running with `--mode=agent`, `--force`, or any write-capable flag. +- You did not announce which skill you invoked and why. +- You are proceeding to implementation review with failing lint/typecheck/tests. +- You are echoing raw secret-scan matches to the user or logs. +- You are pushing without explicit user approval. diff --git a/skills/do-task/_source/cursor/templates/task-plan.md b/skills/do-task/_source/cursor/templates/task-plan.md new file mode 100644 index 0000000..6691020 --- /dev/null +++ b/skills/do-task/_source/cursor/templates/task-plan.md @@ -0,0 +1,143 @@ +# Task Plan: [Short Title] + +> **Variant guardrail (Cursor):** Sub-skills (`brainstorming`, `test-driven-development`, `verification-before-completion`, `finishing-a-development-branch`, `using-git-worktrees`) MUST be invoked through workspace discovery from `.cursor/skills/superpowers/skills//SKILL.md` or `~/.cursor/skills/superpowers/skills//SKILL.md`. Reviewer invocations MUST use `--mode=ask --trust --output-format json`. `jq` is a hard prerequisite. + +## Metadata + +| Field | Value | +|-------|-------| +| Created | YYYY-MM-DD | +| Slug | YYYY-MM-DD- | +| Runtime | cursor | +| Reviewer CLI | codex \| claude \| cursor \| opencode \| pi | +| Reviewer Model | | +| MAX_ROUNDS | 10 | +| Branch Strategy | current-branch \| worktree | +| Branch Name | | +| Worktree Path | | +| Status | draft | + +### Status Enum (authoritative) + +| Value | Meaning | +|-------|---------| +| `draft` | Newly created; plan review not yet started | +| `plan-approved` | Plan review loop returned APPROVED | +| `implementation-in-progress` | Phase 6 executing | +| `implementation-approved` | Phase 8 review loop returned APPROVED; awaiting commit | +| `pushed` | Committed + pushed to remote | +| `local-only` | Committed locally; user declined push | +| `aborted-plan-review` | MAX_ROUNDS reached in Phase 5; user aborted | +| `aborted-impl-review` | MAX_ROUNDS reached in Phase 8; user aborted | +| `aborted-verification` | Phase 7 retries exhausted; user aborted | +| `failed` | Hard tooling failure | + +--- + +## Prompt + + + +## Interpretation + + + +## Assumptions + + + +## Files + + + +| Action | Path | Why | +|--------|------|-----| +| | | | + +## Approach + + + +## TDD Approach + + + +## Acceptance Criteria + +- [ ] +- [ ] + +## Verification + + + +## Rollback + + + +--- + +## Runtime State + + + +```yaml +plan_review_round: 0 +implementation_review_round: 0 +CODEX_PLAN_SESSION_ID: +CODEX_IMPL_SESSION_ID: +CURSOR_PLAN_SESSION_ID: +CURSOR_IMPL_SESSION_ID: +OPENCODE_PLAN_SESSION_ID: +OPENCODE_IMPL_SESSION_ID: +last_phase_entered: +last_round_ts: +last_scan_outcome_plan: +last_scan_outcome_impl: +verification_attempts: 0 +tests_added_count: 0 +tdd_used: false +``` + +## Review History + + + +| Timestamp (ISO-8601) | Loop | Round | Verdict | Summary | +|----------------------|------|-------|---------|---------| +| | | | | | + +## Final Status + + + +--- + +## Guardrails (do NOT remove) + +- This file is the single persistent artifact for `do-task`. Do not split it or delete it on success. +- `Status` must always match one of the 10 enum values. +- `Runtime State` is updated by the skill, not by the user. +- Review History is append-only. +- `last_scan_outcome_plan` and `last_scan_outcome_impl` record the most recent secret-scan result for each loop. They are informational; the scan itself runs per-payload with no caching. diff --git a/skills/do-task/_source/opencode/SKILL.md b/skills/do-task/_source/opencode/SKILL.md new file mode 100644 index 0000000..759fac9 --- /dev/null +++ b/skills/do-task/_source/opencode/SKILL.md @@ -0,0 +1,848 @@ +--- +name: do-task +description: Execute a single user-supplied prompt end-to-end with two reviewer loops (plan review + implementation review) in OpenCode. ALWAYS invoke when the user says `/do-task`, "do this task", "do task ...", "execute this task", or "make it so". Also invoke on the hint phrase "just do ...". Do NOT invoke on "implement this" (that phrase is reserved for implement-plan). +--- + +# Do Task (OpenCode) + +Execute an ad-hoc user prompt end-to-end: parse → clarify → plan (with reviewer loop) → implement (TDD-first where applicable) → verify → implementation review loop → commit → optional push → notify. + +This is a single-artifact sibling of `create-plan` + `implement-plan`. Unlike `implement-plan`, `do-task` operates on one persistent `task-plan.md` (not a full milestone plan) and defaults to the **current branch** (not a worktree). + +**Core principle:** OpenCode loads skills through its native skill tool. Local skills live under `~/.config/opencode/skills/`, and OpenCode can also expose shared agent skills from `~/.agents/skills/`. Sub-skill invocations use OpenCode's native mechanism — not Claude's `Skill` tool, not Cursor's discovery mechanism. + +## Prerequisite Check (MANDATORY) + +Required: + +- OpenCode CLI: `opencode --version` (install via your package manager or `brew install opencode`). +- Superpowers repo: `https://github.com/obra/superpowers` +- OpenCode Superpowers skills available at `~/.agents/skills/superpowers` or `~/.config/opencode/skills/superpowers` +- `superpowers/brainstorming` +- `superpowers/test-driven-development` +- `superpowers/verification-before-completion` +- `superpowers/finishing-a-development-branch` +- `superpowers/using-git-worktrees` (only when the prompt opts in to a worktree) +- Shared reviewer runtime: `~/.config/opencode/skills/reviewer-runtime/run-review.sh` +- Telegram notifier helper: `~/.config/opencode/skills/reviewer-runtime/notify-telegram.sh` + +Verify before proceeding: + +```bash +opencode --version +test -f ~/.agents/skills/superpowers/brainstorming/SKILL.md || test -f ~/.config/opencode/skills/superpowers/brainstorming/SKILL.md +test -f ~/.agents/skills/superpowers/test-driven-development/SKILL.md || test -f ~/.config/opencode/skills/superpowers/test-driven-development/SKILL.md +test -f ~/.agents/skills/superpowers/verification-before-completion/SKILL.md || test -f ~/.config/opencode/skills/superpowers/verification-before-completion/SKILL.md +test -f ~/.agents/skills/superpowers/finishing-a-development-branch/SKILL.md || test -f ~/.config/opencode/skills/superpowers/finishing-a-development-branch/SKILL.md +``` + +If any required dependency is missing, stop immediately and return: + +`Missing dependency: [specific missing item]. Install required OpenCode Superpowers skills (https://github.com/obra/superpowers, OpenCode setup) and the reviewer-runtime helper, then retry.` + +## Required Skill Invocation Rules + +- Invoke relevant skills through OpenCode's native skill tool. +- Announce skill usage explicitly: + - `I've read the [Skill Name] skill and I'm using it to [purpose].` +- For skills with checklists, track checklist items explicitly in conversation. +- Do NOT use Claude's `Skill` tool syntax or Cursor's discovery mechanism. OpenCode's skill system may expose shared files from `~/.agents/skills/`, but invocation still goes through OpenCode's native skill mechanism. + +## Trigger Phrase Detection + +**Binding triggers** (always invoke this skill): + +- `/do-task` +- "do this task" +- "do task ..." +- "execute this task" +- "make it so" + +**Hint trigger** (invoke unless context clearly maps to another skill): + +- "just do ..." + +**Escape phrases** (skip the Phase 2 clarifying-question loop): + +- `--no-questions` +- `"just do it:"` +- `"just do this:"` +- `"no questions:"` + +**Excluded** (do NOT trigger `do-task`): + +- "implement this" — reserved for `implement-plan`. + +**Dropped defaults** (explicitly NOT binding triggers): + +- "work on ..." +- "handle this" +- "take care of ..." +- "get this done" + +**Worktree opt-in phrases** (Phase 4 takes the worktree branch): + +- "in a worktree" +- "use a worktree" +- "on an isolated branch" +- "on a new branch called X" + +## Process + +### Phase 1: Preflight (includes Bootstrap Superpowers Context) + +1. **Bootstrap Superpowers context** — use OpenCode's native skill tool to list installed skills and confirm `superpowers/brainstorming`, `superpowers/test-driven-development`, `superpowers/verification-before-completion`, and `superpowers/finishing-a-development-branch` are discoverable. If any is missing, stop with the Prerequisite Check error message. +2. Verify git repo: `git rev-parse --is-inside-work-tree`. +3. Verify `/ai_plan/` is present in `.gitignore`. If missing: + - Append `/ai_plan/` to `.gitignore`. + - Commit that infra change immediately with message `chore(gitignore): ignore ai_plan local planning artifacts`. + - This infra commit is EXPLICITLY separate from the task commit in Phase 9. It may occur even when the final task ends up `aborted` or `failed`. +4. Announce each sub-skill before invocation using: + `I've read the [Skill Name] skill and I'm using it to [purpose].` + +### Phase 2: Parse Prompt and Question + +1. Capture the exact user prompt verbatim. +2. Detect trigger phrase (see above) and record which one matched. +3. Detect escape phrase. If set, skip clarifying questions entirely. +4. Apply the ask-first heuristic: + - Skip clarifying questions ONLY if ALL are true: + - Prompt names a concrete target (file, feature, or function). + - Prompt names a concrete outcome (what success looks like). + - Prompt has no ambiguous scope (no "and maybe also ..."). + - All identifiers in the prompt are resolvable against the codebase. + - Otherwise, ask 1-3 clarifying questions, ONE AT A TIME, multiple-choice preferred. + - Empty prompt → ask exactly once: "what task?". +5. Invoke `superpowers/brainstorming` via OpenCode's native skill tool for any **behavior-changing** task — feature creation, bug fix with multiple plausible approaches, refactor, design decision. Present 2-3 approaches and recommend one before finalizing the plan. The ONLY skip conditions are the same ones that allow TDD auto-skip: `pure-documentation` and `pure-comment-whitespace-rename`. When skipping, record the skip reason in the Interpretation section of `task-plan.md`. + +### Phase 3: Configure Reviewer + +If the user has already specified a reviewer CLI and model (e.g., "do task X, review with codex gpt-5.4"), use those values. If the user says "use defaults" or otherwise opts out of explicit configuration, proceed with `REVIEWER_CLI=codex`, `REVIEWER_MODEL=gpt-5.4`, and `MAX_ROUNDS=10`. Otherwise, ask: + +1. **Which CLI should review both the plan and the implementation?** + - `codex` — OpenAI Codex CLI (`codex exec`) + - `claude` — Claude Code CLI (`claude -p`) + - `cursor` — Cursor Agent CLI (`cursor-agent -p`) + - `opencode` — OpenCode CLI (`opencode run`) + - `skip` — No external review, proceed with user approval only at each loop. + +2. **Which model?** (only if a CLI was chosen) + - For `codex`: default `gpt-5.4`, alternatives: `gpt-5.3-codex`, `o4-mini`, `o3`. + - For `claude`: default `sonnet`, alternatives: `opus`, `haiku`. + - For `cursor`: **run `cursor-agent models` first** to see available models. + - For `opencode`: provider-qualified form `/` (e.g., `anthropic/claude-sonnet-4-5`, `openai/gpt-5.4`). Run `opencode models` to list available models. + - Accept any model string the user provides. + +3. **Max review rounds shared across both loops?** (default: 10) + - If the user does not provide a value, set `MAX_ROUNDS=10`. + +Store `REVIEWER_CLI`, `REVIEWER_MODEL`, and `MAX_ROUNDS` for Phases 5 and 8. + +Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, `pi`, or `skip`. + +If `REVIEWER_CLI=pi`, verify the Pi reviewer binary before entering the review loop: + +```bash +pi --version +``` + +For shorthand `pi/`, split only on the first slash when the prefix is exactly `pi`; store the complete remainder in `REVIEWER_MODEL`. Examples: `pi/claude-opus-4-7` -> `claude-opus-4-7`, `pi/anthropic/claude-opus-4-7` -> `anthropic/claude-opus-4-7`, and `pi/openrouter/anthropic/claude-opus-4-7` -> `openrouter/anthropic/claude-opus-4-7`. + +When `REVIEWER_CLI=pi`, the reviewer model is configured independently from the model running this workflow. If the model/provider is unavailable, surface helper stderr/status and use `pi --list-models [search]` to inspect configured models. + +### Phase 4: Initialize Plan Workspace + +OpenCode has no plan-mode concept; there is no plan-mode guard here. + +Steps: + +1. Compute slug: `YYYY-MM-DD-` where `` is a kebab-case hash of the task goal (lowercase, alphanumeric + hyphens only). +2. Compute plan folder: `ai_plan//`. +3. **Resume detection:** If the folder already exists, read `task-plan.md`: + - If `Status` is `draft` or `plan-approved` or `implementation-in-progress`: offer to resume, pick a new suffix (`-v2`), or abort. Default is resume. + - If `Status` is any terminal value (`pushed`, `local-only`, `aborted-*`, `failed`): offer a new suffix or abort. Default is new suffix. +4. If not resuming, create the folder and write `task-plan.md` from the template at `templates/task-plan.md` (this skill's template folder; falls back to `~/.config/opencode/skills/do-task/templates/task-plan.md` when installed directly). +5. Fill in: + - `Metadata` block. + - `Prompt` (verbatim). + - `Interpretation`, `Assumptions`, `Files`, `Approach`, `TDD Approach`, `Acceptance Criteria`, `Verification`, `Rollback`. + - Leave `Runtime State`, `Review History`, `Final Status` empty (skill updates these). +6. Set `Status: draft`. + +**Worktree branch:** If the prompt opts in to a worktree (see Trigger Phrase Detection), invoke `superpowers/using-git-worktrees` via OpenCode's native skill tool before proceeding. Otherwise continue on the current branch. + +### Phase 5: Plan Review Loop + +If `REVIEWER_CLI=skip`, present `task-plan.md` to the user and proceed only after explicit user approval. + +Otherwise, invoke the Review Loop (Shared Subroutine) with: + +```text +REVIEW_KIND = plan +REVIEW_ID = $(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) +PAYLOAD_PATH = /tmp/do-task-plan-${REVIEW_ID}.md +PROMPT_TEMPLATE = PLAN_REVIEW_PROMPT (see below) +SESSION_ID_VAR = CODEX_PLAN_SESSION_ID | CURSOR_PLAN_SESSION_ID | OPENCODE_PLAN_SESSION_ID +``` + +Payload is the current `task-plan.md` **with the `Runtime State` and `Review History` blocks stripped** before writing to `PAYLOAD_PATH`. Those two blocks contain reviewer session IDs and scan outcomes that must never be sent back to any reviewer CLI. Reviewers only need the Prompt, Interpretation, Assumptions, Files, Approach, TDD Approach, Acceptance Criteria, Verification, Rollback, and Metadata sections. + +`PLAN_REVIEW_PROMPT`: + +```text +Review this task plan for completeness, correctness, and risk. Focus on: +1. Does the plan match the user's prompt? +2. Are all assumptions surfaced? +3. Are acceptance criteria testable? +4. Is the TDD approach appropriate per the TDD Approach section? +5. Are there missing files, risks, or security concerns? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE`. +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking. +``` + +On APPROVED: + +- Set `Status: plan-approved`. +- Append APPROVED row to Review History. +- Proceed to Phase 6. + +On MAX_ROUNDS: + +- Set `Status: aborted-plan-review`. +- Send Telegram summary before stopping. +- Ask the user whether to override and proceed, restart, or abort. + +### Phase 6: Execute (TDD-first where applicable) + +Native orchestration — do not invoke `superpowers:executing-plans`. + +1. Set `Status: implementation-in-progress`. +2. For every behavior-changing file edit: + - Invoke `superpowers/test-driven-development` via OpenCode's native skill tool. + - Write the failing test first. Run it. Confirm it fails. + - Implement the minimal code to make it pass. Run the test. Confirm green. + - Do NOT commit yet — a single task commit happens in Phase 9. +3. Auto-skip of TDD is permitted ONLY for tasks classified in `task-plan.md` TDD Approach as: + - `pure-documentation` + - `pure-comment-whitespace-rename` +4. Any other skip (including `pure-config-addition`) requires explicit user approval recorded in `task-plan.md` with an ISO-8601 timestamp. +5. Update `task-plan.md` after each logical step: add notes to `Approach`, check off `Acceptance Criteria` items as they complete. + +### Phase 7: Verification Gate + +Invoke `superpowers/verification-before-completion` via OpenCode's native skill tool. + +Run the commands listed in the `Verification` section of `task-plan.md`: + +- Lint (changed files first). +- Typecheck. +- Tests (targeted first, then broader suite if quick). + +All must pass. If a command fails: + +- Fix the issue. +- Re-run that command. +- Increment `verification_attempts` in Runtime State. + +If `verification_attempts` exceeds 3 without green: + +- Set `Status: aborted-verification`. +- Send Telegram summary. +- Ask the user whether to retry, override, or abort. + +### Phase 8: Implementation Review Loop + +If `REVIEWER_CLI=skip`, present a diff + verification summary to the user and proceed only after explicit user approval. + +Otherwise, invoke the Review Loop (Shared Subroutine) with: + +```text +REVIEW_KIND = implementation +REVIEW_ID = $(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) # distinct from plan-review ID +PAYLOAD_PATH = /tmp/do-task-implementation-${REVIEW_ID}.md +PROMPT_TEMPLATE = IMPL_REVIEW_PROMPT (see below) +SESSION_ID_VAR = CODEX_IMPL_SESSION_ID | CURSOR_IMPL_SESSION_ID | OPENCODE_IMPL_SESSION_ID +``` + +Payload contents (assembled by the skill): + +```markdown +# Implementation Review: [Short Title] + +## Task Plan (the plan that was approved) + + +## Changes Made (git diff) + + +## Verification Output +### Lint + +### Typecheck + +### Tests + +``` + +`IMPL_REVIEW_PROMPT`: + +```text +Review this implementation against the task plan. Focus on: +1. Correctness — Does the diff satisfy the Acceptance Criteria? +2. Code quality — Clean, maintainable, no obvious issues? +3. Test coverage — Are behavior changes adequately tested (per the plan's TDD Approach)? +4. Security — Any security concerns introduced? +5. Regressions — Does the diff risk breaking unrelated code? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE`. +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking. +``` + +On APPROVED: + +- Set `Status: implementation-approved`. +- Append APPROVED row to Review History. +- Proceed to Phase 9. + +On MAX_ROUNDS: + +- Set `Status: aborted-impl-review`. +- Send Telegram summary. +- Ask the user whether to override and commit anyway, restart, or abort. + +### Phase 9: Commit + Push Ask + +Invoke `superpowers/finishing-a-development-branch` via OpenCode's native skill tool. + +1. Stage all changed files explicitly (avoid `git add -A`). +2. Single commit with message derived from the task goal: + - Format: `(): ` + - Example: `feat(auth): add session token rotation` +3. Do NOT push. Update `Status: local-only`. +4. Ask the user: "Push to remote? (yes / no)" + - On explicit `yes` → push, then set `Status: pushed`. + - Any other response → leave `Status: local-only`. + +### Phase 10: Telegram Notification + Finalize + +Resolve the notifier helper: + +```bash +TELEGRAM_NOTIFY_RUNTIME=~/.config/opencode/skills/reviewer-runtime/notify-telegram.sh +``` + +On every terminal outcome (`pushed`, `local-only`, `aborted-*`, `failed`), send a Telegram summary if both `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` are set: + +```bash +if [ -x "$TELEGRAM_NOTIFY_RUNTIME" ] && [ -n "${TELEGRAM_BOT_TOKEN:-}" ] && [ -n "${TELEGRAM_CHAT_ID:-}" ]; then + "$TELEGRAM_NOTIFY_RUNTIME" --message "do-task : " +fi +``` + +Rules: + +- Telegram is the only supported notification path. +- Notification failures are non-blocking but must be surfaced to the user. +- Before stopping for any user interaction, approval, or manual decision, send a Telegram summary first if configured. +- If Telegram is not configured, state that no Telegram notification was sent. + +Fill in `Final Status` in `task-plan.md` (include commit hash if any). Do NOT delete the plan folder — it stays as a record. + +--- + +## Review Loop (Shared Subroutine) + +This subroutine is invoked twice per `do-task` run: once in Phase 5 (`REVIEW_KIND=plan`) and once in Phase 8 (`REVIEW_KIND=implementation`). Separate session IDs are used for each loop so reviewer context never leaks across loops. + +### Subroutine Inputs + +| Variable | Purpose | +|----------|---------| +| `REVIEW_KIND` | `plan` or `implementation` | +| `REVIEW_ID` | 8-char hex (from `uuidgen`); reused across rounds of the same loop | +| `PAYLOAD_PATH` | `/tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md` | +| `PROMPT_TEMPLATE` | `PLAN_REVIEW_PROMPT` or `IMPL_REVIEW_PROMPT` | +| `REVIEWER_CLI` | `codex` \| `claude` \| `cursor` \| `opencode` \| `pi` | +| `REVIEWER_MODEL` | Model name | +| `MAX_ROUNDS` | Default 10 | +| `SESSION_ID_VAR` | `CODEX_PLAN_SESSION_ID` \| `CODEX_IMPL_SESSION_ID` \| `CURSOR_PLAN_SESSION_ID` \| `CURSOR_IMPL_SESSION_ID` \| `OPENCODE_PLAN_SESSION_ID` \| `OPENCODE_IMPL_SESSION_ID` | + +Temp artifact paths (per loop): + +- `/tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md` — payload +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md` — normalized review text +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json` — raw Cursor/OpenCode JSON (cursor only, plus opencode when `--format json` is used) +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.stderr` +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.status` +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out` +- `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh` + +Resolve the shared helper: + +```bash +REVIEWER_RUNTIME=~/.config/opencode/skills/reviewer-runtime/run-review.sh +``` + +Set helper success-artifact args before writing the command script: + +```bash +HELPER_SUCCESS_FILE_ARGS=() +case "$REVIEWER_CLI" in + codex) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md) + ;; + cursor) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json) + ;; +esac +``` + +### Step 1: Write Payload + +Write the full payload for this round to `PAYLOAD_PATH`. + +### Step 1a: Secret Scan (per-payload, no caching) + +**BEFORE** sending the payload to any reviewer CLI, scan it for secrets. This scan runs EVERY round — no results are cached. Rationale: Phase 8 payloads include newly-introduced diff content that earlier rounds never saw. + +Run the secret scan with all of these anchored regexes. Use `grep -En` on the payload file: + +```bash +SECRET_REGEX_FILE=$(mktemp) +cat >"$SECRET_REGEX_FILE" <<'EOF' +AKIA[0-9A-Z]{16} +"type"\s*:\s*"service_account" +(ghp|gho|ghs|ghu|ghr)_[A-Za-z0-9]{36,} +xox[abpsr]-[0-9]+-[0-9]+-[0-9]+-[A-Za-z0-9]{24,} +xox[abpsr]-[A-Za-z0-9]{10,48} +sk-(proj-)?[A-Za-z0-9_-]{20,} +sk-ant-(api|admin)[0-9]+-[A-Za-z0-9_-]{20,} +-----BEGIN [A-Z ]+ PRIVATE KEY----- +(TOKEN|SECRET|PASSWORD|API_?KEY|ACCESS_?KEY)\s*=\s*["']?[A-Za-z0-9+/=_-]{8,} +eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+ +EOF + +SCAN_MATCHES=$(grep -Ensf "$SECRET_REGEX_FILE" "$PAYLOAD_PATH" || true) +rm -f "$SECRET_REGEX_FILE" +``` + +If `SCAN_MATCHES` is non-empty: + +1. **Redact the matched text before surfacing** — never echo the raw secret to the user, chat log, terminal scrollback, or any persistent file. Replace each matched substring with a fixed token that preserves only the fact of a match: `[REDACTED::-chars]`. Example: a matched AWS key becomes `[REDACTED:aws-access-key:20-chars]`. Keep the file path and line number; they are useful for the user and not secret. +2. Present the redacted match summary to the user using this exact wording: + + ```text + SECRET-SCAN MATCH in outbound reviewer payload (loop: ${REVIEW_KIND}, round: N): + :: [REDACTED::-chars] + ... + Proceed with sending this payload to ${REVIEWER_CLI}? (yes / no / redact) + ``` + + Pattern labels: `aws-access-key`, `gcp-service-account`, `github-token`, `slack-token`, `openai-key`, `anthropic-key`, `pem-private-key`, `dotenv-style`, `jwt`. + +3. Wait for user response. +4. On `yes`: record `last_scan_outcome_${REVIEW_KIND}=user-approved-with-matches` in Runtime State, and proceed. +5. On `redact`: ask the user to supply redactions, apply them to `PAYLOAD_PATH`, re-scan (this step), record `last_scan_outcome_${REVIEW_KIND}=redacted-and-approved`. +6. On `no`: stop the loop, set `Status: failed`, send Telegram, return to the user. + +If `SCAN_MATCHES` is empty, record `last_scan_outcome_${REVIEW_KIND}=clean` and proceed. + +### Step 2: Generate Reviewer Command Script + +Write the reviewer invocation to `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh` as a bash script starting with: + +```bash +#!/usr/bin/env bash +set -euo pipefail +``` + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call every round (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "Read the file /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md and review. Return exactly the required ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +Round 1 — fresh `codex exec`: + +```bash +codex exec \ + -m ${REVIEWER_MODEL} \ + -s read-only \ + -o /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md \ + "Review the ${REVIEW_KIND} payload in /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md. + +${PROMPT_TEMPLATE}" +``` + +Do not capture the Codex session ID yet. After Round 1 completes, extract it from `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out` (look for `session id: `) and persist it to Runtime State under `${SESSION_ID_VAR}`. + +Round 2 and later — resume session: + +```bash +codex exec resume ${SESSION_ID_VAR_VALUE} \ + -o /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md \ + "I've revised based on your feedback. Updated payload is in /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same ## Summary, ## Findings, and ## Verdict structure as before. +Keep findings ordered P0 to P3, use '- None.' when a severity has no findings, and only use VERDICT: APPROVED when no P0, P1, or P2 findings remain." +``` + +If resume fails, fall back to fresh `codex exec` with prior-round context. + +**If `REVIEWER_CLI` is `claude`:** + +Fresh call every round (Claude CLI has no session resume): + +```bash +claude -p \ + "${ROUND_PREFIX}Review the following ${REVIEW_KIND} payload. + +$(cat /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md) + +${PROMPT_TEMPLATE}" \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +Where `${ROUND_PREFIX}` is empty for Round 1 and `"You previously reviewed this ${REVIEW_KIND} and requested revisions. Previous feedback summary: [key points]. "` for subsequent rounds. + +**If `REVIEWER_CLI` is `cursor`:** + +Round 1: + +```bash +cursor-agent -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "Read the file /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md and review. + +${PROMPT_TEMPLATE}" \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +Round 2 and later — resume: + +```bash +cursor-agent --resume ${SESSION_ID_VAR_VALUE} -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "I've revised based on your feedback. Updated payload is in /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same ## Summary, ## Findings, and ## Verdict structure as before." \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +If resume fails, fall back to fresh `cursor-agent -p`. + +After the command completes, extract the session id and review text: + +```bash +CURSOR_SID=$(jq -r '.session_id' /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json) +jq -r '.result' /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md +``` + +Persist `CURSOR_SID` to Runtime State under `${SESSION_ID_VAR}` on Round 1. + +**If `REVIEWER_CLI` is `opencode`:** + +OpenCode does not expose a dedicated read-only flag at the CLI level; use the built-in `plan` primary agent (`--agent plan`) for review, which is read-oriented and does not modify files. Session resume is supported via `-s `, but the most reliable pattern for non-interactive review is **fresh call each round** (like `claude`) because opencode's session lifecycle and ID capture are less standardized than codex/cursor for headless runs. Skills MAY opt-in to session resume when they have verified the installed opencode version exposes a stable session id in `--format json` output. + +Round 1 (preferred, fresh call): + +```bash +opencode run \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "Read the file /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md and review. + +${PROMPT_TEMPLATE}" \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +Round 2 and later (fresh-call fallback path — recommended default): + +```bash +opencode run \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "You previously reviewed this ${REVIEW_KIND} and requested revisions. + +Previous feedback summary: [key points from last review] + +I've revised. Updated payload is below. + +$(cat /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md) + +Changes made: +[List specific changes] + +Re-review using the same ## Summary, ## Findings, and ## Verdict structure as before." \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +Optional session-resume path (only if the installed opencode reliably emits a session id in `--format json` output and accepts it back via `-s`): + +```bash +# Round 2+ with resume +opencode run \ + -s ${SESSION_ID_VAR_VALUE} \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "I've revised. Updated payload is in /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same ## Summary, ## Findings, and ## Verdict structure as before." \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json +``` + +Extract the review body (the JSON stream emits events; the final assistant message contains the review text): + +```bash +jq -r '.[] | select(.type == "message" and .role == "assistant") | .content' \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json \ + > /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md \ + || cp /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md +``` + +If the JSON parse falls through, promote the raw JSON file as the review output and surface a warning to the user. On any opencode CLI or JSON parsing failure, treat this loop round as `completed-empty-output` and follow the helper-failure escalation in Step 6. + +### Step 3: Run via `run-review.sh` + +Run the command script through the shared helper when available: + +```bash +if [ -x "$REVIEWER_RUNTIME" ]; then + "$REVIEWER_RUNTIME" \ + --command-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh \ + --stdout-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + --stderr-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.stderr \ + --status-file /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.status \ + "${HELPER_SUCCESS_FILE_ARGS[@]}" +else + echo "Warning: reviewer runtime helper not found at $REVIEWER_RUNTIME; falling back to direct synchronous review." >&2 + bash /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh \ + >/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + 2>/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.stderr +fi +``` + +Run the helper in the foreground and watch live stdout for `state=in-progress` heartbeats. If your agent environment buffers command output until exit, start the helper in the background and poll the `.status` file instead of treating heartbeats as post-hoc-only data. + +### Step 4: Promote Reviewer Output + Capture Session ID + +After the command completes: + +- `cursor`: already promoted in Step 2 via `jq -r '.result' ...`. Also capture `session_id` if first round. +- `codex`: extract `CODEX_SESSION_ID` from `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out` after the helper or fallback run. If the review text lives only in `.runner.out`, `cp` it into the `.md` file: + + ```bash + cp /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md + ``` + +- `claude` or `pi`: promote `.runner.out` into the `.md` file: + + ```bash + cp /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md + ``` + +- `opencode`: already promoted in Step 2 via `jq` on the JSON stream. If opt-in session-resume is active and the JSON includes a stable session id, capture it and persist to `${SESSION_ID_VAR}`. + +On Round 1, persist the captured session ID (if any) into `task-plan.md`'s Runtime State under `${SESSION_ID_VAR}`. + +### Step 5: Parse Verdict + Update Review History + +1. Read `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md`. +2. Append one row to `task-plan.md` Review History: + - Timestamp (ISO-8601 UTC). + - Loop (`plan` or `implementation`). + - Round number. + - Verdict (`APPROVED` or `REVISE`). + - Summary (first line of the `## Summary` section). +3. Increment `plan_review_round` or `implementation_review_round` in Runtime State. + +### Step 6: Branch APPROVED / REVISE / MAX_ROUNDS + +Verdict rules: + +- **VERDICT: APPROVED** with no `P0`, `P1`, or `P2` findings → exit the subroutine with `APPROVED`. +- **VERDICT: APPROVED** with only `P3` findings → optionally fix the `P3` items if cheap and safe, then exit with `APPROVED`. +- **VERDICT: REVISE** or any `P0`, `P1`, or `P2` finding → go to revision (see below), then return to Step 1 for the next round. +- No clear verdict but `P0`, `P1`, and `P2` are all `- None.` → treat as APPROVED. +- Helper state `completed-empty-output` → treat as failed review attempt, surface `.stderr`/`.status`, fix invocation or prompt handling, then retry. +- Helper state `needs-operator-decision` → surface status log and decide whether to extend the timeout, abort, or retry with different helper parameters. +- Round counter ≥ `MAX_ROUNDS` → exit the subroutine with `MAX_ROUNDS`. Caller decides next action per Phase 5 or Phase 8. + +**Revision:** The caller (Phase 5 for plan, Phase 6/7 for implementation) applies findings in priority order (`P0` → `P1` → `P2` → `P3`). For implementation review revisions, Phase 7 verification must be re-run after every revision before returning to Step 1. + +### Step 7: Liveness Contract (during Step 3) + +- The shared reviewer runtime emits `state=in-progress note="In progress N"` heartbeats every 60 seconds while the reviewer child is alive. +- Keep waiting as long as a fresh `In progress N` heartbeat keeps arriving roughly once per minute. +- Do not abort just because the review is slow, a soft timeout fired, or a `stall-warning` line appears, as long as the `In progress N` heartbeat continues. +- Treat missing heartbeats, `state=failed`, `state=completed-empty-output`, and `state=needs-operator-decision` as escalation signals. + +### Step 8: Cleanup (on successful round exit) + +```bash +rm -f /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.md \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.json \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.stderr \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.status \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.runner.out \ + /tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh +``` + +If the round failed, produced empty output, or reached operator-decision timeout, KEEP `.stderr`, `.status`, and `.runner.out` until the issue is diagnosed instead of deleting them. + +--- + +## Resume Semantics + +1. Detect existing plan folder by slug at Phase 4. +2. Read `task-plan.md` → `Status`. +3. Decide next action: + + | Status | Action | + |--------|--------| + | `draft` | Resume at Phase 5 (plan review) | + | `plan-approved` | Resume at Phase 6 (execute) | + | `implementation-in-progress` | Resume at Phase 6 (continue execute) | + | `implementation-approved` | Resume at Phase 9 (commit + push ask) | + | `pushed` \| `local-only` | Ask user: new suffix, abort, or replay for reference only | + | `aborted-*` \| `failed` | Offer new suffix or full restart | + +4. When resuming, read Runtime State for `CODEX_PLAN_SESSION_ID`, `CODEX_IMPL_SESSION_ID`, `CURSOR_PLAN_SESSION_ID`, `CURSOR_IMPL_SESSION_ID`, `OPENCODE_PLAN_SESSION_ID`, `OPENCODE_IMPL_SESSION_ID`, and the round counters. If a session ID is populated, use it for the first revision round in that loop (Round 2) via `codex exec resume`, `cursor-agent --resume`, or `opencode run -s ` as applicable. + +--- + +## Tracker Discipline (MANDATORY) + +**ALWAYS update `task-plan.md` before/after each phase transition. NEVER proceed with stale state.** + +Before starting any phase: + +1. Update `Status` if it transitions. +2. Update `last_phase_entered` in Runtime State. + +After completing any phase: + +1. Update `Status` if it transitions. +2. Append notes to the relevant section of `task-plan.md`. + +Review History is append-only. + +--- + +## Execution Workflow Rules + +- Current branch is the default; worktree is opt-in only. +- Do NOT push without explicit "yes". +- Secret scan runs **per-payload, no caching** — every round, including revisions. +- Review loops use `MAX_ROUNDS=10` by default, shared across both loops. +- The task commit is a single commit created in Phase 9; interim WIP commits are NOT created. +- The `.gitignore` infra commit in Phase 1 is explicitly separate from the task commit and is allowed even on abort. + +--- + +## Verification Checklist + +- [ ] `ai_plan/` exists and `/ai_plan/` is in `.gitignore` +- [ ] `task-plan.md` created under `ai_plan/YYYY-MM-DD-/` +- [ ] Reviewer CLI + model + `MAX_ROUNDS` configured (or `skip`) +- [ ] Secret scan ran on every outbound reviewer payload +- [ ] Plan review completed (APPROVED, MAX_ROUNDS handled, or skipped) +- [ ] Phase 6 executed TDD-first for all behavior-changing steps (or documented skip) +- [ ] Phase 7 verification green before Phase 8 +- [ ] Implementation review completed (APPROVED, MAX_ROUNDS handled, or skipped) +- [ ] Single task commit created locally, no push without explicit yes +- [ ] Telegram notification attempted if configured +- [ ] `task-plan.md` Final Status filled in + +--- + +## Variant Hardening Notes — OpenCode + +- Must use OpenCode's native skill tool for sub-skill invocation. Do NOT use Claude's `Skill` tool syntax. OpenCode may load shared skill files from `~/.agents/skills/`, but invocation is still OpenCode-native. +- Phase 1 includes a Bootstrap Superpowers Context step that lists installed skills and confirms `superpowers/brainstorming`, `superpowers/test-driven-development`, `superpowers/verification-before-completion`, and `superpowers/finishing-a-development-branch` are discoverable before any other phase runs. +- Helper paths are `~/.config/opencode/skills/reviewer-runtime/{run-review.sh,notify-telegram.sh}`. +- OpenCode reviewer CLI branch (when `REVIEWER_CLI=opencode`): + - Binary: `opencode`. Non-interactive: `opencode run ""`. + - Model: `-m /` (e.g., `openai/gpt-5.4`, `anthropic/claude-sonnet-4-5`). + - Read-only posture: `--agent plan` (uses OpenCode's built-in plan primary agent; no explicit `--read-only` flag exists). + - Output: `--format json` for structured output. Extraction uses `jq` against the JSON event stream. + - Session resume: `-s ` or `--continue`. Fresh call each round is the recommended default since session id capture is less standardized than codex/cursor for headless runs. +- No plan-mode guard (OpenCode has no plan-mode concept). + +## Common Mistakes + +- Skipping the Bootstrap Superpowers Context step in Phase 1 (breaks native skill discovery). +- Using Claude `Skill` tool syntax, or treating shared `~/.agents/skills/` files as anything other than OpenCode-native skill entries. +- Forgetting to set `--agent plan` on opencode reviewer calls (would use the default `build` agent which can write files). +- Asking multiple clarifying questions in a single message. +- Skipping the per-payload secret scan because "the previous round was clean". +- Pushing the task commit without explicit user approval. +- Using a non-provider-qualified model string for opencode (e.g., `gpt-5.4` instead of `openai/gpt-5.4`). + +## Red Flags — Stop and Correct + +- You are invoking sub-skills via Claude's `Skill` tool or Codex native-discovery paths instead of OpenCode's native skill tool. +- You are running an opencode reviewer call without `--agent plan`. +- You did not announce which skill you invoked and why. +- You are proceeding to implementation review with failing lint/typecheck/tests. +- You are echoing raw secret-scan matches to the user or logs. +- You are pushing without explicit user approval. diff --git a/skills/do-task/_source/opencode/templates/task-plan.md b/skills/do-task/_source/opencode/templates/task-plan.md new file mode 100644 index 0000000..ce95197 --- /dev/null +++ b/skills/do-task/_source/opencode/templates/task-plan.md @@ -0,0 +1,143 @@ +# Task Plan: [Short Title] + +> **Variant guardrail (OpenCode):** Sub-skills (`brainstorming`, `test-driven-development`, `verification-before-completion`, `finishing-a-development-branch`, `using-git-worktrees`) MUST be invoked through OpenCode's native skill tool from `~/.config/opencode/skills/superpowers//SKILL.md`. Phase 1 MUST include the Bootstrap Superpowers Context step. Opencode reviewer calls MUST use `--agent plan` to stay read-only. + +## Metadata + +| Field | Value | +|-------|-------| +| Created | YYYY-MM-DD | +| Slug | YYYY-MM-DD- | +| Runtime | opencode | +| Reviewer CLI | codex \| claude \| cursor \| opencode \| pi | +| Reviewer Model | | +| MAX_ROUNDS | 10 | +| Branch Strategy | current-branch \| worktree | +| Branch Name | | +| Worktree Path | | +| Status | draft | + +### Status Enum (authoritative) + +| Value | Meaning | +|-------|---------| +| `draft` | Newly created; plan review not yet started | +| `plan-approved` | Plan review loop returned APPROVED | +| `implementation-in-progress` | Phase 6 executing | +| `implementation-approved` | Phase 8 review loop returned APPROVED; awaiting commit | +| `pushed` | Committed + pushed to remote | +| `local-only` | Committed locally; user declined push | +| `aborted-plan-review` | MAX_ROUNDS reached in Phase 5; user aborted | +| `aborted-impl-review` | MAX_ROUNDS reached in Phase 8; user aborted | +| `aborted-verification` | Phase 7 retries exhausted; user aborted | +| `failed` | Hard tooling failure | + +--- + +## Prompt + + + +## Interpretation + + + +## Assumptions + + + +## Files + + + +| Action | Path | Why | +|--------|------|-----| +| | | | + +## Approach + + + +## TDD Approach + + + +## Acceptance Criteria + +- [ ] +- [ ] + +## Verification + + + +## Rollback + + + +--- + +## Runtime State + + + +```yaml +plan_review_round: 0 +implementation_review_round: 0 +CODEX_PLAN_SESSION_ID: +CODEX_IMPL_SESSION_ID: +CURSOR_PLAN_SESSION_ID: +CURSOR_IMPL_SESSION_ID: +OPENCODE_PLAN_SESSION_ID: +OPENCODE_IMPL_SESSION_ID: +last_phase_entered: +last_round_ts: +last_scan_outcome_plan: +last_scan_outcome_impl: +verification_attempts: 0 +tests_added_count: 0 +tdd_used: false +``` + +## Review History + + + +| Timestamp (ISO-8601) | Loop | Round | Verdict | Summary | +|----------------------|------|-------|---------|---------| +| | | | | | + +## Final Status + + + +--- + +## Guardrails (do NOT remove) + +- This file is the single persistent artifact for `do-task`. Do not split it or delete it on success. +- `Status` must always match one of the 10 enum values. +- `Runtime State` is updated by the skill, not by the user. +- Review History is append-only. +- `last_scan_outcome_plan` and `last_scan_outcome_impl` record the most recent secret-scan result for each loop. They are informational; the scan itself runs per-payload with no caching. diff --git a/skills/do-task/_source/pi/SKILL.md b/skills/do-task/_source/pi/SKILL.md new file mode 100644 index 0000000..16afac2 --- /dev/null +++ b/skills/do-task/_source/pi/SKILL.md @@ -0,0 +1,210 @@ +--- +name: do-task +description: Execute a single user-supplied prompt end-to-end in pi with plan review, implementation review, verification, and one persistent task-plan artifact. +--- + +# Do Task (Pi) + +Execute an ad-hoc user prompt end-to-end: parse, clarify, plan, implement, verify, review, commit, and optionally push. + +This variant uses one persistent `task-plan.md` under `ai_plan/` and defaults to the current branch unless the prompt explicitly opts into a worktree workflow. + +## Shared Setup + +Before using this skill, read: + +- [docs/PI-SUPERPOWERS.md](../../../docs/PI-SUPERPOWERS.md) +- [docs/PI-COMMON-REVIEWER.md](../../../docs/PI-COMMON-REVIEWER.md) + +This workflow depends on: + +- Superpowers skills being visible to pi +- the pi reviewer-runtime helper being installed in a supported location + +## Prerequisite Check (MANDATORY) + +Required: + +- `pi --version` +- Superpowers `brainstorming` +- Superpowers `test-driven-development` +- Superpowers `verification-before-completion` +- Superpowers `finishing-a-development-branch` +- Superpowers `using-git-worktrees` when the prompt opts into a worktree +- pi reviewer runtime helper +- pi Telegram notifier helper + +Quick checks for common installs: + +```bash +pi --version +test -f ~/.agents/skills/superpowers/brainstorming/SKILL.md || test -f ~/.pi/agent/skills/superpowers/brainstorming/SKILL.md +test -f ~/.agents/skills/superpowers/test-driven-development/SKILL.md || test -f ~/.pi/agent/skills/superpowers/test-driven-development/SKILL.md +test -f ~/.agents/skills/superpowers/verification-before-completion/SKILL.md || test -f ~/.pi/agent/skills/superpowers/verification-before-completion/SKILL.md +test -f ~/.agents/skills/superpowers/finishing-a-development-branch/SKILL.md || test -f ~/.pi/agent/skills/superpowers/finishing-a-development-branch/SKILL.md +test -x .pi/skills/reviewer-runtime/pi/run-review.sh || test -x ~/.pi/agent/skills/reviewer-runtime/pi/run-review.sh +test -x .pi/skills/reviewer-runtime/pi/notify-telegram.sh || test -x ~/.pi/agent/skills/reviewer-runtime/pi/notify-telegram.sh +``` + +If you use a settings-defined skill path for Superpowers, confirm it matches [docs/PI-SUPERPOWERS.md](../../../docs/PI-SUPERPOWERS.md) before continuing. + +If you install the reviewer helper in a nonstandard location, confirm it matches [docs/PI-COMMON-REVIEWER.md](../../../docs/PI-COMMON-REVIEWER.md) before continuing. + +If any required dependency is missing, stop immediately and return: + +`Missing dependency: pi do-task requires the workflow skills and reviewer setup documented in docs/PI-SUPERPOWERS.md and docs/PI-COMMON-REVIEWER.md.` + +## Required Workflow Rules + +- Load the relevant workflow skill before entering its phase. If pi did not auto-load it, use `/skill:`. +- Announce skill usage explicitly: + - `I've read the [Skill Name] skill and I'm using it to [purpose].` +- Keep the `task-plan.md` artifact current as work progresses. +- Do not use deprecated wrapper CLIs. + +## Trigger Detection + +Always use this skill for: + +- `/do-task` +- `do this task` +- `do task ...` +- `execute this task` +- `make it so` +- `just do ...` when another skill is not a better fit + +Use current-branch execution by default. Only switch to a worktree when the prompt explicitly asks for one. + +## Process + +### Phase 1: Preflight + +1. Verify the repo: `git rev-parse --is-inside-work-tree` +2. Ensure `/ai_plan/` exists in `.gitignore` +3. Confirm the required workflow skills are available to pi +4. Announce each workflow skill before using it + +### Phase 2: Parse Prompt And Clarify + +1. Capture the user's prompt verbatim +2. Detect whether the prompt is concrete enough to proceed without questions +3. If needed, ask 1-3 short questions one at a time +4. Load `brainstorming` for behavior-changing work unless the task is pure documentation or pure comment/whitespace/rename work + +### Phase 3: Configure Reviewer + +If the user already specified reviewer settings, use them. Otherwise ask: + +Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, `pi`, or `skip` + +1. Which CLI should review the plan and implementation? +2. Reviewer model +3. Max rounds, default `10` + +Store `REVIEWER_CLI`, `REVIEWER_MODEL`, and `MAX_ROUNDS`. + +If `REVIEWER_CLI=pi`, verify the Pi reviewer binary before entering the review loop: + +```bash +pi --version +``` + +For shorthand `pi/`, split only on the first slash when the prefix is exactly `pi`; store the complete remainder in `REVIEWER_MODEL`. Examples: `pi/claude-opus-4-7` -> `claude-opus-4-7`, `pi/anthropic/claude-opus-4-7` -> `anthropic/claude-opus-4-7`, and `pi/openrouter/anthropic/claude-opus-4-7` -> `openrouter/anthropic/claude-opus-4-7`. + +When `REVIEWER_CLI=pi`, the reviewer model is configured independently from the pi model running this workflow. Use any configured pi model string, including provider-qualified model IDs. If the reviewer model or provider is unavailable, surface the review helper stderr/status and ask for a configured model; use `pi --list-models [search]` to inspect configured models. + +The pi reviewer command rendered into `/tmp/do-task-${REVIEW_KIND}-review-${REVIEW_ID}.sh` must be isolated and read-only: + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files --model "$REVIEWER_MODEL" --tools read,grep,find,ls -p "Read the file /tmp/do-task-${REVIEW_KIND}-${REVIEW_ID}.md and review." +``` + +The pi reviewer invocation must not load workflow skills and must not include `write`, `edit`, or `bash` tools. + +### Phase 4: Initialize `task-plan.md` + +1. Compute `ai_plan/YYYY-MM-DD-/` +2. Resume if an existing plan folder is active, otherwise create a new one +3. Write `task-plan.md` from this skill's `templates/task-plan.md` +4. Fill `Metadata`, `Prompt`, `Interpretation`, `Assumptions`, `Files`, `Approach`, `TDD Approach`, `Acceptance Criteria`, `Verification`, and `Rollback` +5. Set `Status: draft` + +If the prompt explicitly opts into a worktree, load `using-git-worktrees` before implementation. Otherwise remain on the current branch. + +### Phase 5: Plan Review Loop + +Skip this phase if `REVIEWER_CLI=skip`. + +1. Write a reviewer payload from `task-plan.md` +2. Strip the runtime-only sections before sending it out +3. Run the reviewer through the pi reviewer-runtime helper when available +4. Fix `P0`, `P1`, and `P2` findings before proceeding +5. Keep `P3` findings for optional cleanup +6. Set `Status: plan-approved` when the reviewer approves + +The reviewer response format must be: + +```text +## Summary +... + +## Findings +### P0 +- ... +### P1 +- ... +### P2 +- ... +### P3 +- ... + +## Verdict +VERDICT: APPROVED +``` + +### Phase 6: Execute + +1. Set `Status: implementation-in-progress` +2. Load `test-driven-development` for every behavior-changing edit unless `task-plan.md` explicitly records an allowed skip +3. Update `task-plan.md` as acceptance criteria are completed +4. Do not commit yet + +### Phase 7: Verification Gate + +1. Load `verification-before-completion` +2. Run the commands listed in `task-plan.md` +3. Fix failures and re-run verification until green +4. If verification stalls repeatedly, stop and surface the blocker + +### Phase 8: Implementation Review Loop + +Skip this phase if `REVIEWER_CLI=skip`. + +1. Build a review payload from the approved plan, current diff, and verification output +2. Run the reviewer through the pi reviewer-runtime helper +3. Address `P0`, `P1`, and `P2` findings before approval +4. Fix cheap `P3` findings when safe +5. Set `Status: implementation-approved` when approved + +### Phase 9: Commit And Push Decision + +1. Load `finishing-a-development-branch` +2. Stage only the intended files +3. Create one commit for the task +4. Ask whether to push or keep the work local + +### Phase 10: Telegram Completion Notification + +Resolve the helper in this order: + +```bash +TELEGRAM_NOTIFY_RUNTIME="" +for candidate in ".pi/skills/reviewer-runtime/pi/notify-telegram.sh" "$HOME/.pi/agent/skills/reviewer-runtime/pi/notify-telegram.sh"; do + if [ -x "$candidate" ]; then + TELEGRAM_NOTIFY_RUNTIME="$candidate" + break + fi +done +``` + +If the helper exists and both `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` are configured, send a short completion summary. Otherwise state that no Telegram completion notification was sent. diff --git a/skills/do-task/_source/pi/templates/task-plan.md b/skills/do-task/_source/pi/templates/task-plan.md new file mode 100644 index 0000000..4165ce1 --- /dev/null +++ b/skills/do-task/_source/pi/templates/task-plan.md @@ -0,0 +1,128 @@ +# Task Plan: [Short Title] + +> **Variant guardrail (pi):** Required workflow skills (`brainstorming`, `test-driven-development`, `verification-before-completion`, `finishing-a-development-branch`, `using-git-worktrees`) must be available to pi as documented in `docs/PI-SUPERPOWERS.md`. Load the relevant workflow skill before entering its matching phase. + +## Metadata + +| Field | Value | +|-------|-------| +| Created | YYYY-MM-DD | +| Slug | YYYY-MM-DD- | +| Runtime | pi | +| Reviewer CLI | codex \| claude \| cursor \| opencode \| pi | +| Reviewer Model | | +| MAX_ROUNDS | 10 | +| Branch Strategy | current-branch \| worktree | +| Branch Name | | +| Worktree Path | | +| Status | draft | + +### Status Enum (authoritative) + +| Value | Meaning | +|-------|---------| +| `draft` | Newly created; plan review not yet started | +| `plan-approved` | Plan review loop returned APPROVED | +| `implementation-in-progress` | Phase 6 executing | +| `implementation-approved` | Phase 8 review loop returned APPROVED; awaiting commit | +| `pushed` | Committed + pushed to remote | +| `local-only` | Committed locally; user declined push | +| `aborted-plan-review` | MAX_ROUNDS reached in Phase 5; user aborted | +| `aborted-impl-review` | MAX_ROUNDS reached in Phase 8; user aborted | +| `aborted-verification` | Phase 7 retries exhausted; user aborted | +| `failed` | Hard tooling failure | + +--- + +## Prompt + + + +## Interpretation + + + +## Assumptions + + + +## Files + + + +| Action | Path | Why | +|--------|------|-----| +| | | | + +## Approach + + + +## TDD Approach + + + +## Acceptance Criteria + +- [ ] +- [ ] + +## Verification + + + +## Rollback + + + +--- + +## Runtime State + +```yaml +plan_review_round: 0 +implementation_review_round: 0 +CODEX_PLAN_SESSION_ID: +CODEX_IMPL_SESSION_ID: +CURSOR_PLAN_SESSION_ID: +CURSOR_IMPL_SESSION_ID: +OPENCODE_PLAN_SESSION_ID: +OPENCODE_IMPL_SESSION_ID: +last_phase_entered: +last_round_ts: +last_scan_outcome_plan: +last_scan_outcome_impl: +verification_attempts: 0 +tests_added_count: 0 +tdd_used: false +``` + +## Review History + +| Timestamp (ISO-8601) | Loop | Round | Verdict | Summary | +|----------------------|------|-------|---------|---------| +| | | | | | + +## Final Status + + + +--- + +## Guardrails (do NOT remove) + +- This file is the single persistent artifact for `do-task`. Do not split it or delete it on success. +- `Status` must always match one of the enum values. +- `Runtime State` is updated by the skill, not by the user. +- Review History is append-only. diff --git a/skills/do-task/claude-code/.generated-manifest.json b/skills/do-task/claude-code/.generated-manifest.json new file mode 100644 index 0000000..9324dc3 --- /dev/null +++ b/skills/do-task/claude-code/.generated-manifest.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/do-task/claude-code", + "files": [ + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "0acbb9e6c130667d5dfb57a3c31cbb5f06a1570e8586dead032a7c3eae6bbc06" + }, + { + "path": "templates/task-plan.md", + "kind": "file", + "mode": "644", + "sha256": "7c34d2c380e9252cf23ae63581ef2c882c6be28b97f75463f2211f7db6e9e0ca" + } + ] +} diff --git a/skills/do-task/claude-code/SKILL.md b/skills/do-task/claude-code/SKILL.md index 1c61bb9..7535d9d 100644 --- a/skills/do-task/claude-code/SKILL.md +++ b/skills/do-task/claude-code/SKILL.md @@ -3,6 +3,8 @@ name: do-task description: Execute a single user-supplied prompt end-to-end with two reviewer loops (plan review + implementation review). ALWAYS invoke when the user says `/do-task`, "do this task", "do task ...", "execute this task", or "make it so". Also invoke on the hint phrase "just do ...". Do NOT invoke on "implement this" (that phrase is reserved for implement-plan). --- + + # Do Task (Claude Code) Execute an ad-hoc user prompt end-to-end: parse → clarify → plan (with reviewer loop) → implement (TDD-first where applicable) → verify → implementation review loop → commit → optional push → notify. diff --git a/skills/do-task/claude-code/templates/task-plan.md b/skills/do-task/claude-code/templates/task-plan.md index f637ecc..5144c97 100644 --- a/skills/do-task/claude-code/templates/task-plan.md +++ b/skills/do-task/claude-code/templates/task-plan.md @@ -1,3 +1,4 @@ + # Task Plan: [Short Title] > **Variant guardrail (Claude Code):** When generating or updating this file, the agent MUST be out of plan mode. Sub-skills (`brainstorming`, `test-driven-development`, `verification-before-completion`, `finishing-a-development-branch`, `using-git-worktrees`) MUST be invoked through the `Skill` tool explicitly — no shell wrappers. diff --git a/skills/do-task/codex/.generated-manifest.json b/skills/do-task/codex/.generated-manifest.json new file mode 100644 index 0000000..d9328ba --- /dev/null +++ b/skills/do-task/codex/.generated-manifest.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/do-task/codex", + "files": [ + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "534d823d34fcfa89935c3fd350726d921a57c806154a44e36e8bfc9bef1a5995" + }, + { + "path": "templates/task-plan.md", + "kind": "file", + "mode": "644", + "sha256": "7e460a5cd7df46daa5b1bf8daa7aaf07f5ac0ee26eee2320826a16aedf1cf39a" + } + ] +} diff --git a/skills/do-task/codex/SKILL.md b/skills/do-task/codex/SKILL.md index 1a9a172..cdeda54 100644 --- a/skills/do-task/codex/SKILL.md +++ b/skills/do-task/codex/SKILL.md @@ -3,6 +3,8 @@ name: do-task description: Execute a single user-supplied prompt end-to-end with two reviewer loops (plan review + implementation review) in Codex. ALWAYS invoke when the user says `/do-task`, "do this task", "do task ...", "execute this task", or "make it so". Also invoke on the hint phrase "just do ...". Do NOT invoke on "implement this" (that phrase is reserved for implement-plan). --- + + # Do Task (Codex Native Superpowers) Execute an ad-hoc user prompt end-to-end: parse → clarify → plan (with reviewer loop) → implement (TDD-first where applicable) → verify → implementation review loop → commit → optional push → notify. diff --git a/skills/do-task/codex/templates/task-plan.md b/skills/do-task/codex/templates/task-plan.md index fb31e06..9130d57 100644 --- a/skills/do-task/codex/templates/task-plan.md +++ b/skills/do-task/codex/templates/task-plan.md @@ -1,3 +1,4 @@ + # Task Plan: [Short Title] > **Variant guardrail (Codex):** Sub-skills (`brainstorming`, `test-driven-development`, `verification-before-completion`, `finishing-a-development-branch`, `using-git-worktrees`) MUST be invoked through native skill discovery from `~/.agents/skills/superpowers//SKILL.md` — no `superpowers-codex` CLI wrappers. Checklist-driven sub-skills MUST track items with `update_plan` todos. diff --git a/skills/do-task/cursor/.generated-manifest.json b/skills/do-task/cursor/.generated-manifest.json new file mode 100644 index 0000000..628586b --- /dev/null +++ b/skills/do-task/cursor/.generated-manifest.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/do-task/cursor", + "files": [ + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "3a255d12f67be0eef3c45969befb23e3283a8a91f061053d939714d50e5f8cee" + }, + { + "path": "templates/task-plan.md", + "kind": "file", + "mode": "644", + "sha256": "6af4c73434e932c1705ba13faf47be2152d61971f0b60ee9bece534d22493596" + } + ] +} diff --git a/skills/do-task/cursor/SKILL.md b/skills/do-task/cursor/SKILL.md index 8f4f6f8..d692ba6 100644 --- a/skills/do-task/cursor/SKILL.md +++ b/skills/do-task/cursor/SKILL.md @@ -3,6 +3,8 @@ name: do-task description: Execute a single user-supplied prompt end-to-end with two reviewer loops (plan review + implementation review) in Cursor Agent CLI. ALWAYS invoke when the user says `/do-task`, "do this task", "do task ...", "execute this task", or "make it so". Also invoke on the hint phrase "just do ...". Do NOT invoke on "implement this" (that phrase is reserved for implement-plan). --- + + # Do Task (Cursor Agent CLI) Execute an ad-hoc user prompt end-to-end: parse → clarify → plan (with reviewer loop) → implement (TDD-first where applicable) → verify → implementation review loop → commit → optional push → notify. diff --git a/skills/do-task/cursor/templates/task-plan.md b/skills/do-task/cursor/templates/task-plan.md index 6691020..89a664b 100644 --- a/skills/do-task/cursor/templates/task-plan.md +++ b/skills/do-task/cursor/templates/task-plan.md @@ -1,3 +1,4 @@ + # Task Plan: [Short Title] > **Variant guardrail (Cursor):** Sub-skills (`brainstorming`, `test-driven-development`, `verification-before-completion`, `finishing-a-development-branch`, `using-git-worktrees`) MUST be invoked through workspace discovery from `.cursor/skills/superpowers/skills//SKILL.md` or `~/.cursor/skills/superpowers/skills//SKILL.md`. Reviewer invocations MUST use `--mode=ask --trust --output-format json`. `jq` is a hard prerequisite. diff --git a/skills/do-task/opencode/.generated-manifest.json b/skills/do-task/opencode/.generated-manifest.json new file mode 100644 index 0000000..c330b00 --- /dev/null +++ b/skills/do-task/opencode/.generated-manifest.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/do-task/opencode", + "files": [ + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "ebbae7c050b3cfe0d1de8722cd71c8c10ba74de709adeec4ebb9645e2f770a03" + }, + { + "path": "templates/task-plan.md", + "kind": "file", + "mode": "644", + "sha256": "a4c0ad9a9faaf738c503b403a285b6a005ee245b6b05f5af973c8e0224f6fefe" + } + ] +} diff --git a/skills/do-task/opencode/SKILL.md b/skills/do-task/opencode/SKILL.md index 759fac9..1c1c803 100644 --- a/skills/do-task/opencode/SKILL.md +++ b/skills/do-task/opencode/SKILL.md @@ -3,6 +3,8 @@ name: do-task description: Execute a single user-supplied prompt end-to-end with two reviewer loops (plan review + implementation review) in OpenCode. ALWAYS invoke when the user says `/do-task`, "do this task", "do task ...", "execute this task", or "make it so". Also invoke on the hint phrase "just do ...". Do NOT invoke on "implement this" (that phrase is reserved for implement-plan). --- + + # Do Task (OpenCode) Execute an ad-hoc user prompt end-to-end: parse → clarify → plan (with reviewer loop) → implement (TDD-first where applicable) → verify → implementation review loop → commit → optional push → notify. diff --git a/skills/do-task/opencode/templates/task-plan.md b/skills/do-task/opencode/templates/task-plan.md index ce95197..d8cdbb5 100644 --- a/skills/do-task/opencode/templates/task-plan.md +++ b/skills/do-task/opencode/templates/task-plan.md @@ -1,3 +1,4 @@ + # Task Plan: [Short Title] > **Variant guardrail (OpenCode):** Sub-skills (`brainstorming`, `test-driven-development`, `verification-before-completion`, `finishing-a-development-branch`, `using-git-worktrees`) MUST be invoked through OpenCode's native skill tool from `~/.config/opencode/skills/superpowers//SKILL.md`. Phase 1 MUST include the Bootstrap Superpowers Context step. Opencode reviewer calls MUST use `--agent plan` to stay read-only. diff --git a/skills/do-task/pi/.generated-manifest.json b/skills/do-task/pi/.generated-manifest.json new file mode 100644 index 0000000..f26f760 --- /dev/null +++ b/skills/do-task/pi/.generated-manifest.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/do-task/pi", + "files": [ + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "4920ad0cdeda546b37432c2268159724de54ddb01922308f2df88fcca4db8d31" + }, + { + "path": "templates/task-plan.md", + "kind": "file", + "mode": "644", + "sha256": "fd38213fabf350e14b48c5209910d00c16ff74c455101618063835fa8c19e73e" + } + ] +} diff --git a/skills/do-task/pi/SKILL.md b/skills/do-task/pi/SKILL.md index 16afac2..3e4bb5f 100644 --- a/skills/do-task/pi/SKILL.md +++ b/skills/do-task/pi/SKILL.md @@ -3,6 +3,8 @@ name: do-task description: Execute a single user-supplied prompt end-to-end in pi with plan review, implementation review, verification, and one persistent task-plan artifact. --- + + # Do Task (Pi) Execute an ad-hoc user prompt end-to-end: parse, clarify, plan, implement, verify, review, commit, and optionally push. diff --git a/skills/do-task/pi/templates/task-plan.md b/skills/do-task/pi/templates/task-plan.md index 4165ce1..3ddfee7 100644 --- a/skills/do-task/pi/templates/task-plan.md +++ b/skills/do-task/pi/templates/task-plan.md @@ -1,3 +1,4 @@ + # Task Plan: [Short Title] > **Variant guardrail (pi):** Required workflow skills (`brainstorming`, `test-driven-development`, `verification-before-completion`, `finishing-a-development-branch`, `using-git-worktrees`) must be available to pi as documented in `docs/PI-SUPERPOWERS.md`. Load the relevant workflow skill before entering its matching phase. diff --git a/skills/implement-plan/_source/claude-code/SKILL.md b/skills/implement-plan/_source/claude-code/SKILL.md new file mode 100644 index 0000000..164c5f4 --- /dev/null +++ b/skills/implement-plan/_source/claude-code/SKILL.md @@ -0,0 +1,646 @@ +--- +name: implement-plan +description: Use when a plan folder (from create-plan) exists and needs to be executed in an isolated git worktree with iterative cross-model milestone review. ALWAYS invoke when user says "implement the plan", "execute the plan", "start implementation", "resume the plan", or similar execution requests. +--- + +# Implement Plan (Claude Code) + +Execute an existing plan (created by `create-plan`) in an isolated git worktree, with iterative cross-model review at each milestone boundary. + +## Prerequisite Check (MANDATORY) + +Required: + +- Plan folder exists under `ai_plan/` at project root +- `continuation-runbook.md` exists in plan folder +- `milestone-plan.md` exists in plan folder +- `story-tracker.md` exists in plan folder +- Git repo with worktree support: `git worktree list` +- Superpowers execution skills: + - `superpowers:executing-plans` + - `superpowers:using-git-worktrees` + - `superpowers:verification-before-completion` + - `superpowers:finishing-a-development-branch` + +If any dependency is missing, stop immediately and return: + +"Missing dependency: [specific missing item]. Ensure all prerequisites are met, then retry." + +If no plan folder exists: + +"No plan found under `ai_plan/`. Run `create-plan` first." + +## Process + +### Phase 1: Locate Plan + +1. Scan `ai_plan/` for plan directories (most recent first by date prefix). +2. If multiple plans exist, ask user which one to implement. +3. If no plan exists, stop: "No plan found. Run create-plan first." +4. Read `continuation-runbook.md` first (source of truth). +5. Read `story-tracker.md` to detect resume state (`in-dev` or `completed` stories). +6. Read `milestone-plan.md` for implementation details. + +### Phase 2: Configure Reviewer + +If the user has already specified a reviewer CLI and model (e.g., "implement the plan, review with claude sonnet"), use those values. Otherwise, ask: + +1. **Which CLI should review each milestone?** + - `codex` — OpenAI Codex CLI (`codex exec`) + - `claude` — Claude Code CLI (`claude -p`) + - `cursor` — Cursor Agent CLI (`cursor-agent -p`) + - `skip` — No external review, proceed with user approval only + +2. **Which model?** (only if a CLI was chosen) + - For `codex`: default `o4-mini`, alternatives: `gpt-5.3-codex`, `o3` + - For `claude`: default `sonnet`, alternatives: `opus`, `haiku` + - For `cursor`: **run `cursor-agent models` first** to see available models + - Accept any model string the user provides + +3. **Max review rounds per milestone?** (default: 10) + - If the user does not provide a value, set `MAX_ROUNDS=10`. + +Store `REVIEWER_CLI`, `REVIEWER_MODEL`, and `MAX_ROUNDS`. These values are fixed for the entire run. + +Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, `pi`, or `skip`. + +If `REVIEWER_CLI=pi`, verify the Pi reviewer binary before entering the review loop: + +```bash +pi --version +``` + +For shorthand `pi/`, split only on the first slash when the prefix is exactly `pi`; store the complete remainder in `REVIEWER_MODEL`. Examples: `pi/claude-opus-4-7` -> `claude-opus-4-7`, `pi/anthropic/claude-opus-4-7` -> `anthropic/claude-opus-4-7`, and `pi/openrouter/anthropic/claude-opus-4-7` -> `openrouter/anthropic/claude-opus-4-7`. + +When `REVIEWER_CLI=pi`, the reviewer model is configured independently from the model running this workflow. If the model/provider is unavailable, surface helper stderr/status and use `pi --list-models [search]` to inspect configured models. + +### Phase 3: Set Up Worktree (REQUIRED SUB-SKILL) + +Invoke `superpowers:using-git-worktrees` explicitly. + +1. Branch naming: `implement/` (e.g., `implement/2026-03-04-auth-system`). +2. Follow worktree skill's directory priority: `.worktrees/` > `worktrees/` > CLAUDE.md > ask user. +3. Verify `.gitignore` covers worktree directory. +4. Run project setup (auto-detect: `npm install`, `cargo build`, `pip install`, etc.). +5. Verify clean baseline (run tests). + +**Resume detection:** If `story-tracker.md` shows `in-dev` or `completed` stories, check if worktree branch already exists (`git worktree list`). If so, `cd` into existing worktree instead of creating a new one. + +### Phase 4: Execute Milestones (Loop) + +For each milestone (M1, M2, ...): + +#### Step 1: Read Milestone Spec + +Read the milestone section from `milestone-plan.md`. + +#### Step 2: Update Tracker + +Mark first story `in-dev` in `story-tracker.md`. + +#### Step 3: Implement Stories + +Execute each story in order. After completing each story: + +1. Mark `in-dev` -> `completed` in `story-tracker.md` +2. Update counts +3. Mark next story `in-dev` + +Commit hashes are not available yet — they are backfilled in Step 6 after the milestone is approved and committed. + +#### Step 4: Verify Milestone (REQUIRED SUB-SKILL) + +Invoke `superpowers:verification-before-completion` explicitly. + +```bash +# Lint changed files +# Typecheck +# Run tests (targeted first, then full suite) +``` + +All must pass before proceeding. If failures: fix, re-verify. Do NOT proceed to review with failures. + +#### Step 5: Milestone Review Loop + +Send to reviewer for approval **before committing**. See Phase 5 for details. The review payload uses working-tree diffs (`git diff` for unstaged, `git diff --staged` for staged changes). + +**Skip this step if reviewer was set to `skip`.** When skipped, present the milestone summary to the user and ask for approval directly. + +#### Step 6: Commit & Approve + +Only after the reviewer approves (or user overrides at max rounds): + +```bash +git add +git commit -m "feat(): implement milestone M - " +``` + +Do NOT push. After committing: + +1. Backfill the commit hash into the Notes column for all stories in this milestone in `story-tracker.md`. +2. Mark milestone as `approved` in `story-tracker.md`. +3. Move to next milestone. + +### Phase 5: Milestone Review Loop (Detail) + +**Skip this phase entirely if reviewer was set to `skip`.** + +#### Step 1: Generate Session ID + +```bash +REVIEW_ID=$(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) +``` + +Use `REVIEW_ID` for all milestone review temp file paths: + +- `/tmp/milestone-${REVIEW_ID}.md` +- `/tmp/milestone-review-${REVIEW_ID}.md` +- `/tmp/milestone-review-${REVIEW_ID}.json` +- `/tmp/milestone-review-${REVIEW_ID}.stderr` +- `/tmp/milestone-review-${REVIEW_ID}.status` +- `/tmp/milestone-review-${REVIEW_ID}.runner.out` +- `/tmp/milestone-review-${REVIEW_ID}.sh` + +Resolve the shared runtime helper path before writing the command script: + +```bash +REVIEWER_RUNTIME=~/.claude/skills/reviewer-runtime/run-review.sh +``` + +Set helper success-artifact args before writing the command script: + +```bash +HELPER_SUCCESS_FILE_ARGS=() +case "$REVIEWER_CLI" in + codex) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/milestone-review-${REVIEW_ID}.md) + ;; + cursor) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/milestone-review-${REVIEW_ID}.json) + ;; +esac +``` + +#### Step 2: Write Review Payload + +Write to `/tmp/milestone-${REVIEW_ID}.md`: + +```markdown +# Milestone M Review: + +## Milestone Spec (from plan) +[Copy milestone section from milestone-plan.md] + +## Acceptance Criteria +[Copy acceptance criteria checkboxes] + +## Changes Made (git diff) +[Output of: git diff -- for unstaged changes, or git diff --staged for staged changes] + +## Verification Output +### Lint +[lint output] +### Typecheck +[typecheck output] +### Tests +[test output with pass/fail counts] +``` + +#### Review Contract (Applies to Every Round) + +The reviewer response must use this structure: + +```text +## Summary +... + +## Findings +### P0 +- ... +### P1 +- ... +### P2 +- ... +### P3 +- ... + +## Verdict +VERDICT: APPROVED +``` + +Rules: + +- Order findings from `P0` to `P3`. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- Use `- None.` when a severity has no findings. +- `VERDICT: APPROVED` is allowed only when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking. +- The calling agent should still try to fix `P3` findings when they are cheap and safe. + +#### Liveness Contract (Applies While Review Is Running) + +- The shared reviewer runtime emits `state=in-progress note="In progress N"` heartbeats every 60 seconds while the reviewer child is alive. +- The calling agent must keep waiting as long as a fresh `In progress N` heartbeat keeps arriving roughly once per minute. +- Do not abort just because the review is slow, a soft timeout fired, or a `stall-warning` line appears, as long as the `In progress N` heartbeat continues. +- Treat missing heartbeats, `state=failed`, `state=completed-empty-output`, and `state=needs-operator-decision` as escalation signals. + +#### Step 3: Submit to Reviewer (Round 1) + +Write the reviewer invocation to `/tmp/milestone-review-${REVIEW_ID}.sh` as a bash script: + +```bash +#!/usr/bin/env bash +set -euo pipefail +``` + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call every round (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "Read the file /tmp/milestone-${REVIEW_ID}.md and review. Return exactly the required ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +```bash +codex exec \ + -m ${REVIEWER_MODEL} \ + -s read-only \ + -o /tmp/milestone-review-${REVIEW_ID}.md \ + "Review this milestone implementation. The spec, acceptance criteria, git diff, and verification output are in /tmp/milestone-${REVIEW_ID}.md. + +Evaluate: +1. Correctness — Does the implementation match the milestone spec? +2. Acceptance criteria — Are all criteria met? +3. Code quality — Clean, maintainable, no obvious issues? +4. Test coverage — Are changes adequately tested? +5. Security — Any security concerns introduced? + + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." +``` + +Do not try to capture the Codex session ID yet. When using the helper, extract it from `/tmp/milestone-review-${REVIEW_ID}.runner.out` after the command completes (look for `session id: <uuid>`), then store it as `CODEX_SESSION_ID` for resume in subsequent rounds. + +**If `REVIEWER_CLI` is `claude`:** + +```bash +claude -p \ + "Review this milestone implementation using the following spec, acceptance criteria, git diff, and verification output: + +$(cat /tmp/milestone-${REVIEW_ID}.md) + +Evaluate: +1. Correctness — Does the implementation match the milestone spec? +2. Acceptance criteria — Are all criteria met? +3. Code quality — Clean, maintainable, no obvious issues? +4. Test coverage — Are changes adequately tested? +5. Security — Any security concerns introduced? + + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +**If `REVIEWER_CLI` is `cursor`:** + +```bash +cursor-agent -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "Read the file /tmp/milestone-${REVIEW_ID}.md and review this milestone implementation. + +Evaluate: +1. Correctness — Does the implementation match the milestone spec? +2. Acceptance criteria — Are all criteria met? +3. Code quality — Clean, maintainable, no obvious issues? +4. Test coverage — Are changes adequately tested? +5. Security — Any security concerns introduced? + + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." \ + > /tmp/milestone-review-${REVIEW_ID}.json +``` + +For `cursor`, the command script writes raw JSON to `/tmp/milestone-review-${REVIEW_ID}.json`. Do not run `jq` extraction until after the helper or fallback execution completes. If `jq` is not installed, inform the user: `brew install jq` (macOS) or equivalent. + +Run the command script through the shared helper when available: + +```bash +if [ -x "$REVIEWER_RUNTIME" ]; then + "$REVIEWER_RUNTIME" \ + --command-file /tmp/milestone-review-${REVIEW_ID}.sh \ + --stdout-file /tmp/milestone-review-${REVIEW_ID}.runner.out \ + --stderr-file /tmp/milestone-review-${REVIEW_ID}.stderr \ + --status-file /tmp/milestone-review-${REVIEW_ID}.status \ + "${HELPER_SUCCESS_FILE_ARGS[@]}" +else + echo "Warning: reviewer runtime helper not found at $REVIEWER_RUNTIME; falling back to direct synchronous review." >&2 + bash /tmp/milestone-review-${REVIEW_ID}.sh >/tmp/milestone-review-${REVIEW_ID}.runner.out 2>/tmp/milestone-review-${REVIEW_ID}.stderr +fi +``` + +Run the helper in the foreground and watch its live stdout for `state=in-progress` heartbeats. If your agent environment buffers command output until exit, start the helper in the background and poll `/tmp/milestone-review-${REVIEW_ID}.status` separately instead of treating heartbeats as post-hoc-only data. + +After the command completes: + +- If `REVIEWER_CLI=cursor`, extract the final review text: + +```bash +CURSOR_SESSION_ID=$(jq -r '.session_id' /tmp/milestone-review-${REVIEW_ID}.json) +jq -r '.result' /tmp/milestone-review-${REVIEW_ID}.json > /tmp/milestone-review-${REVIEW_ID}.md +``` + +- If `REVIEWER_CLI=codex`, extract `CODEX_SESSION_ID` from `/tmp/milestone-review-${REVIEW_ID}.runner.out` after the helper or fallback run. If the review text is only in `.runner.out`, move or copy the actual review body into `/tmp/milestone-review-${REVIEW_ID}.md` before verdict parsing. +- If `REVIEWER_CLI=claude` or `REVIEWER_CLI=pi`, promote stdout captured by the helper or fallback runner into the markdown review file: + +```bash +cp /tmp/milestone-review-${REVIEW_ID}.runner.out /tmp/milestone-review-${REVIEW_ID}.md +``` + +Fallback is allowed only when the helper is missing or not executable. + +#### Step 4: Read Review & Check Verdict + +1. Read `/tmp/milestone-review-${REVIEW_ID}.md` +2. If the review failed, produced empty output, or reached helper timeout, also read: + - `/tmp/milestone-review-${REVIEW_ID}.stderr` + - `/tmp/milestone-review-${REVIEW_ID}.status` + - `/tmp/milestone-review-${REVIEW_ID}.runner.out` +3. Present review to the user: + +```markdown +## Milestone Review — Round N (reviewer: ${REVIEWER_CLI} / ${REVIEWER_MODEL}) + +[Reviewer feedback] +``` + +1. While the reviewer is still running, keep waiting as long as fresh `state=in-progress note="In progress N"` heartbeats continue to appear roughly once per minute. +2. Check verdict: + - **VERDICT: APPROVED** with no `P0`, `P1`, or `P2` findings -> proceed to Phase 4 Step 6 (commit & approve) + - **VERDICT: APPROVED** with only `P3` findings -> optionally fix the `P3` items if they are cheap and safe, then proceed + - **VERDICT: REVISE** or any `P0`, `P1`, or `P2` finding -> go to Step 5 + - No clear verdict but `P0`, `P1`, and `P2` are all `- None.` -> treat as approved + - Helper state `completed-empty-output` -> treat as failed review attempt, surface stderr/status, fix invocation or prompt handling, then retry + - Helper state `needs-operator-decision` -> surface status log and decide whether to extend the timeout, abort, or retry with different helper parameters + - Max rounds (`MAX_ROUNDS`) reached -> present to user for manual decision (proceed or stop) + +#### Step 5: Address Feedback & Re-verify + +1. Address the reviewer findings in priority order (`P0` -> `P1` -> `P2`, then `P3` when practical) (do NOT commit yet). +2. Re-run verification (lint/typecheck/tests) — all must pass. +3. Update `/tmp/milestone-${REVIEW_ID}.md` with new diff and verification output. + +Summarize revisions for the user: + +```markdown +### Revisions (Round N) +- [Change and reason, one bullet per issue addressed] +``` + +If a revision contradicts the user's explicit requirements, skip it and note it for the user. + +#### Step 6: Re-submit to Reviewer (Rounds 2-N) + +Rewrite `/tmp/milestone-review-${REVIEW_ID}.sh` for the next round. The script should contain the reviewer invocation only; do not run it directly. + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call with prior-round context (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "You previously reviewed this milestone and requested revisions. Read the updated payload at /tmp/milestone-${REVIEW_ID}.md and re-review using the same ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +Resume the existing session: + +```bash +codex exec resume ${CODEX_SESSION_ID} \ + -o /tmp/milestone-review-${REVIEW_ID}.md \ + "I've addressed your feedback. Updated diff and verification output are in /tmp/milestone-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." +``` + +If resume fails (session expired), fall back to fresh `codex exec` with context about prior rounds. + +**If `REVIEWER_CLI` is `claude`:** + +Fresh call with accumulated context (Claude CLI has no session resume): + +```bash +claude -p \ + "You previously reviewed milestone M<N> and requested revisions. + +Previous feedback summary: [key points from last review] + +I've addressed your feedback. Updated diff and verification output are below. + +$(cat /tmp/milestone-${REVIEW_ID}.md) + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +**If `REVIEWER_CLI` is `cursor`:** + +Resume the existing session: + +```bash +cursor-agent --resume ${CURSOR_SESSION_ID} -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "I've addressed your feedback. Updated diff and verification output are in /tmp/milestone-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." \ + > /tmp/milestone-review-${REVIEW_ID}.json +``` + +If resume fails, fall back to fresh `cursor-agent -p` with context about prior rounds. + +Do not run `jq` extraction until after the helper or fallback execution completes, then extract `/tmp/milestone-review-${REVIEW_ID}.md` from the JSON response. + +After updating `/tmp/milestone-review-${REVIEW_ID}.sh`, run the same helper/fallback flow from Round 1. + +Return to Step 4. + +#### Step 7: Cleanup Per Milestone + +```bash +rm -f \ + /tmp/milestone-${REVIEW_ID}.md \ + /tmp/milestone-review-${REVIEW_ID}.md \ + /tmp/milestone-review-${REVIEW_ID}.json \ + /tmp/milestone-review-${REVIEW_ID}.stderr \ + /tmp/milestone-review-${REVIEW_ID}.status \ + /tmp/milestone-review-${REVIEW_ID}.runner.out \ + /tmp/milestone-review-${REVIEW_ID}.sh +``` + +If the round failed, produced empty output, or reached operator-decision timeout, keep `.stderr`, `.status`, and `.runner.out` until the issue is diagnosed instead of deleting them immediately. + +### Phase 6: Completion (REQUIRED SUB-SKILL) + +After all milestones are approved and committed: + +1. Invoke `superpowers:finishing-a-development-branch` explicitly. +2. Run full test suite one final time — all must pass. +3. Merge the worktree branch into the parent branch: + +```bash +# From the main repo (not the worktree) +git merge implement/<plan-folder-name> +``` + +1. Delete the worktree and its branch: + +```bash +git worktree remove <worktree-path> +git branch -d implement/<plan-folder-name> +``` + +1. Mark plan status as `completed` in `story-tracker.md`. + +### Phase 7: Final Report + +Present summary: + +```markdown +## Implementation Complete + +**Plan:** <plan-folder-name> +**Milestones:** <N> completed, <N> approved +**Review rounds:** <total across all milestones> +**Branch:** implement/<plan-folder-name> (merged and deleted) +``` + +### Phase 8: Telegram Notification (MANDATORY) + +Resolve the Telegram notifier helper from the installed Claude Code skills directory: + +```bash +TELEGRAM_NOTIFY_RUNTIME=~/.claude/skills/reviewer-runtime/notify-telegram.sh +``` + +On every terminal outcome for the implement-plan run (fully completed, stopped after max rounds, skipped reviewer, or failure), send a Telegram summary if the helper exists and both `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` are configured: + +```bash +if [ -x "$TELEGRAM_NOTIFY_RUNTIME" ] && [ -n "${TELEGRAM_BOT_TOKEN:-}" ] && [ -n "${TELEGRAM_CHAT_ID:-}" ]; then + "$TELEGRAM_NOTIFY_RUNTIME" --message "implement-plan completed for <plan-folder-name>: <status summary>" +fi +``` + +Rules: + +- Telegram is the only supported notification path. Do not use desktop notifications, `say`, email, or any other notifier. +- Notification failures are non-blocking, but they must be surfaced to the user. +- Before stopping for any user interaction, approval, or manual decision, send a Telegram summary first if configured. +- If Telegram is not configured, state that no Telegram notification was sent. + +## Tracker Discipline (MANDATORY) + +**ALWAYS update `story-tracker.md` before/after each story. NEVER proceed with stale tracker state.** + +Before starting any story: + +1. Open `story-tracker.md` +2. Mark story `in-dev` +3. Add notes if relevant +4. Then begin implementation + +After completing any story: + +1. Mark story `completed` +2. Review pending stories +3. Update Last Updated and Stories Complete counts + +Note: Commit hashes are backfilled into story Notes after the milestone commit (Step 6), not per-story. + +## Verification Checklist + +- [ ] Plan folder located and all required files present +- [ ] Reviewer configured or explicitly skipped +- [ ] Max review rounds confirmed (default: 10) +- [ ] Worktree created with branch `implement/<plan-folder-name>` +- [ ] Worktree directory verified in .gitignore +- [ ] Baseline tests pass in worktree +- [ ] Each milestone: stories tracked (in-dev -> completed) +- [ ] Each milestone: lint/typecheck/tests pass before review +- [ ] Each milestone: reviewer approved (or max rounds + user override) +- [ ] Each milestone: committed locally only after approval +- [ ] Each milestone: marked approved in story-tracker.md +- [ ] All milestones completed, approved, and committed +- [ ] Final test suite passes +- [ ] Worktree branch merged to parent and worktree deleted +- [ ] Story tracker updated with final status +- [ ] Telegram notification attempted if configured diff --git a/skills/implement-plan/_source/codex/SKILL.md b/skills/implement-plan/_source/codex/SKILL.md new file mode 100644 index 0000000..be6e3f1 --- /dev/null +++ b/skills/implement-plan/_source/codex/SKILL.md @@ -0,0 +1,723 @@ +--- +name: implement-plan +description: Use when a plan folder (from create-plan) exists and needs to be executed in an isolated git worktree with iterative cross-model milestone review. ALWAYS invoke when user says "implement the plan", "execute the plan", "start implementation", "resume the plan", or similar execution requests. +--- + +# Implement Plan (Codex Native Superpowers) + +Execute an existing plan (created by `create-plan`) in an isolated git worktree, with iterative cross-model review at each milestone boundary. + +## Overview + +This skill wraps the Superpowers execution flow for Codex: + +1. Locate plan files under `ai_plan/` +2. Set up an isolated git worktree +3. Execute milestones one-by-one with lint/typecheck/test gates +4. Review each milestone with a second model/provider +5. Commit approved milestones, merge to parent branch, and delete worktree + +**Core principle:** Codex uses native skill discovery from `~/.agents/skills/`. Do not use deprecated `superpowers-codex bootstrap` or `use-skill` CLI commands. + +## Prerequisite Check (MANDATORY) + +Required: + +- Plan folder exists under `ai_plan/` at project root +- `continuation-runbook.md` exists in plan folder +- `milestone-plan.md` exists in plan folder +- `story-tracker.md` exists in plan folder +- Git repo with worktree support: `git worktree list` +- Superpowers skills symlink: `~/.agents/skills/superpowers -> ~/.codex/superpowers/skills` +- Superpowers execution skills: + - `superpowers:executing-plans` + - `superpowers:using-git-worktrees` + - `superpowers:verification-before-completion` + - `superpowers:finishing-a-development-branch` + +Verify before proceeding: + +```bash +test -L ~/.agents/skills/superpowers +test -f ~/.agents/skills/superpowers/executing-plans/SKILL.md +test -f ~/.agents/skills/superpowers/using-git-worktrees/SKILL.md +test -f ~/.agents/skills/superpowers/verification-before-completion/SKILL.md +test -f ~/.agents/skills/superpowers/finishing-a-development-branch/SKILL.md +``` + +If any dependency is missing, stop and return: + +`Missing dependency: [specific missing item]. Ensure all prerequisites are met, then retry.` + +If no plan folder exists: + +`No plan found under ai_plan/. Run create-plan first.` + +## Required Skill Invocation Rules + +- Invoke relevant skills through native discovery (no CLI wrapper). +- Announce skill usage explicitly: + - `I've read the [Skill Name] skill and I'm using it to [purpose].` +- For skills with checklists, track checklist items with `update_plan` todos. +- Tool mapping for Codex: + - `TodoWrite` -> `update_plan` + - `Task` subagents -> unavailable in Codex; do the work directly and state the limitation + - `Skill` -> use native skill discovery from `~/.agents/skills/` + +## Process + +### Phase 1: Locate Plan + +1. Scan `ai_plan/` for plan directories (most recent first by date prefix). +2. If multiple plans exist, ask user which one to implement. +3. If no plan exists, stop: "No plan found. Run create-plan first." +4. Read `continuation-runbook.md` first (source of truth). +5. Read `story-tracker.md` to detect resume state (`in-dev` or `completed` stories). +6. Read `milestone-plan.md` for implementation details. + +### Phase 2: Configure Reviewer + +If the user has already specified a reviewer CLI and model (e.g., "implement the plan, review with claude sonnet"), use those values. Otherwise, ask: + +1. **Which CLI should review each milestone?** + - `codex` — OpenAI Codex CLI (`codex exec`) + - `claude` — Claude Code CLI (`claude -p`) + - `cursor` — Cursor Agent CLI (`cursor-agent -p`) + - `skip` — No external review, proceed with user approval only + +2. **Which model?** (only if a CLI was chosen) + - For `codex`: default `o4-mini`, alternatives: `gpt-5.3-codex`, `o3` + - For `claude`: default `sonnet`, alternatives: `opus`, `haiku` + - For `cursor`: **run `cursor-agent models` first** to see available models + - Accept any model string the user provides + +3. **Max review rounds per milestone?** (default: 10) + - If the user does not provide a value, set `MAX_ROUNDS=10`. + +Store `REVIEWER_CLI`, `REVIEWER_MODEL`, and `MAX_ROUNDS`. These values are fixed for the entire run. + +Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, `pi`, or `skip`. + +If `REVIEWER_CLI=pi`, verify the Pi reviewer binary before entering the review loop: + +```bash +pi --version +``` + +For shorthand `pi/<pi-model-name>`, split only on the first slash when the prefix is exactly `pi`; store the complete remainder in `REVIEWER_MODEL`. Examples: `pi/claude-opus-4-7` -> `claude-opus-4-7`, `pi/anthropic/claude-opus-4-7` -> `anthropic/claude-opus-4-7`, and `pi/openrouter/anthropic/claude-opus-4-7` -> `openrouter/anthropic/claude-opus-4-7`. + +When `REVIEWER_CLI=pi`, the reviewer model is configured independently from the model running this workflow. If the model/provider is unavailable, surface helper stderr/status and use `pi --list-models [search]` to inspect configured models. + +### Phase 3: Set Up Worktree (REQUIRED SUB-SKILL) + +Invoke `superpowers:using-git-worktrees`. + +1. Branch naming: `implement/<plan-folder-name>` (e.g., `implement/2026-03-04-auth-system`). +2. Follow worktree skill's directory priority: `.worktrees/` > `worktrees/` > CLAUDE.md > ask user. +3. Verify `.gitignore` covers worktree directory. +4. Run project setup (auto-detect: `npm install`, `cargo build`, `pip install`, etc.). +5. Verify clean baseline (run tests). + +**Resume detection:** If `story-tracker.md` shows `in-dev` or `completed` stories, check if worktree branch already exists (`git worktree list`). If so, `cd` into existing worktree instead of creating a new one. + +### Phase 4: Execute Milestones (Loop) + +For each milestone (M1, M2, ...): + +#### Step 1: Read Milestone Spec + +Read the milestone section from `milestone-plan.md`. + +#### Step 2: Update Tracker + +Mark first story `in-dev` in `story-tracker.md`. + +#### Step 3: Implement Stories + +Execute each story in order. After completing each story: + +1. Mark `in-dev` -> `completed` in `story-tracker.md` +2. Update counts +3. Mark next story `in-dev` + +Commit hashes are not available yet — they are backfilled in Step 6 after the milestone is approved and committed. + +#### Step 4: Verify Milestone (REQUIRED SUB-SKILL) + +Invoke `superpowers:verification-before-completion`. + +```bash +# Lint changed files +# Typecheck +# Run tests (targeted first, then full suite) +``` + +All must pass before proceeding. If failures: fix, re-verify. Do NOT proceed to review with failures. + +#### Step 5: Milestone Review Loop + +Send to reviewer for approval **before committing**. See Phase 5 for details. The review payload uses working-tree diffs (`git diff` for unstaged, `git diff --staged` for staged changes). + +**Skip this step if reviewer was set to `skip`.** When skipped, present the milestone summary to the user and ask for approval directly. + +#### Step 6: Commit & Approve + +Only after the reviewer approves (or user overrides at max rounds): + +```bash +git add <changed-files> +git commit -m "feat(<scope>): implement milestone M<N> - <description>" +``` + +Do NOT push. After committing: + +1. Backfill the commit hash into the Notes column for all stories in this milestone in `story-tracker.md`. +2. Mark milestone as `approved` in `story-tracker.md`. +3. Move to next milestone. + +### Phase 5: Milestone Review Loop (Detail) + +**Skip this phase entirely if reviewer was set to `skip`.** + +#### Step 1: Generate Session ID + +```bash +REVIEW_ID=$(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) +``` + +Use `REVIEW_ID` for all milestone review temp file paths: + +- `/tmp/milestone-${REVIEW_ID}.md` +- `/tmp/milestone-review-${REVIEW_ID}.md` +- `/tmp/milestone-review-${REVIEW_ID}.json` +- `/tmp/milestone-review-${REVIEW_ID}.stderr` +- `/tmp/milestone-review-${REVIEW_ID}.status` +- `/tmp/milestone-review-${REVIEW_ID}.runner.out` +- `/tmp/milestone-review-${REVIEW_ID}.sh` + +Resolve the shared runtime helper path before writing the command script: + +```bash +REVIEWER_RUNTIME=~/.codex/skills/reviewer-runtime/run-review.sh +``` + +Set helper success-artifact args before writing the command script: + +```bash +HELPER_SUCCESS_FILE_ARGS=() +case "$REVIEWER_CLI" in + codex) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/milestone-review-${REVIEW_ID}.md) + ;; + cursor) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/milestone-review-${REVIEW_ID}.json) + ;; +esac +``` + +#### Step 2: Write Review Payload + +Write to `/tmp/milestone-${REVIEW_ID}.md`: + +```markdown +# Milestone M<N> Review: <title> + +## Milestone Spec (from plan) +[Copy milestone section from milestone-plan.md] + +## Acceptance Criteria +[Copy acceptance criteria checkboxes] + +## Changes Made (git diff) +[Output of: git diff -- for unstaged changes, or git diff --staged for staged changes] + +## Verification Output +### Lint +[lint output] +### Typecheck +[typecheck output] +### Tests +[test output with pass/fail counts] +``` + +#### Review Contract (Applies to Every Round) + +The reviewer response must use this structure: + +```text +## Summary +... + +## Findings +### P0 +- ... +### P1 +- ... +### P2 +- ... +### P3 +- ... + +## Verdict +VERDICT: APPROVED +``` + +Rules: + +- Order findings from `P0` to `P3`. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- Use `- None.` when a severity has no findings. +- `VERDICT: APPROVED` is allowed only when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking. +- The calling agent should still try to fix `P3` findings when they are cheap and safe. + +#### Liveness Contract (Applies While Review Is Running) + +- The shared reviewer runtime emits `state=in-progress note="In progress N"` heartbeats every 60 seconds while the reviewer child is alive. +- The calling agent must keep waiting as long as a fresh `In progress N` heartbeat keeps arriving roughly once per minute. +- Do not abort just because the review is slow, a soft timeout fired, or a `stall-warning` line appears, as long as the `In progress N` heartbeat continues. +- Treat missing heartbeats, `state=failed`, `state=completed-empty-output`, and `state=needs-operator-decision` as escalation signals. + +#### Step 3: Submit to Reviewer (Round 1) + +Write the reviewer invocation to `/tmp/milestone-review-${REVIEW_ID}.sh` as a bash script: + +```bash +#!/usr/bin/env bash +set -euo pipefail +``` + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call every round (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "Read the file /tmp/milestone-${REVIEW_ID}.md and review. Return exactly the required ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +```bash +codex exec \ + -m ${REVIEWER_MODEL} \ + -s read-only \ + -o /tmp/milestone-review-${REVIEW_ID}.md \ + "Review this milestone implementation. The spec, acceptance criteria, git diff, and verification output are in /tmp/milestone-${REVIEW_ID}.md. + +Evaluate: +1. Correctness — Does the implementation match the milestone spec? +2. Acceptance criteria — Are all criteria met? +3. Code quality — Clean, maintainable, no obvious issues? +4. Test coverage — Are changes adequately tested? +5. Security — Any security concerns introduced? + + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." +``` + +Do not try to capture the Codex session ID yet. When using the helper, extract it from `/tmp/milestone-review-${REVIEW_ID}.runner.out` after the command completes (look for `session id: <uuid>`), then store it as `CODEX_SESSION_ID` for resume in subsequent rounds. + +**If `REVIEWER_CLI` is `claude`:** + +```bash +claude -p \ + "Review this milestone implementation using the following spec, acceptance criteria, git diff, and verification output: + +$(cat /tmp/milestone-${REVIEW_ID}.md) + +Evaluate: +1. Correctness — Does the implementation match the milestone spec? +2. Acceptance criteria — Are all criteria met? +3. Code quality — Clean, maintainable, no obvious issues? +4. Test coverage — Are changes adequately tested? +5. Security — Any security concerns introduced? + + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +**If `REVIEWER_CLI` is `cursor`:** + +```bash +cursor-agent -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "Read the file /tmp/milestone-${REVIEW_ID}.md and review this milestone implementation. + +Evaluate: +1. Correctness — Does the implementation match the milestone spec? +2. Acceptance criteria — Are all criteria met? +3. Code quality — Clean, maintainable, no obvious issues? +4. Test coverage — Are changes adequately tested? +5. Security — Any security concerns introduced? + + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." \ + > /tmp/milestone-review-${REVIEW_ID}.json +``` + +For `cursor`, the command script writes raw JSON to `/tmp/milestone-review-${REVIEW_ID}.json`. Do not run `jq` extraction until after the helper or fallback execution completes. If `jq` is not installed, inform the user: `brew install jq` (macOS) or equivalent. + +Run the command script through the shared helper when available: + +```bash +if [ -x "$REVIEWER_RUNTIME" ]; then + "$REVIEWER_RUNTIME" \ + --command-file /tmp/milestone-review-${REVIEW_ID}.sh \ + --stdout-file /tmp/milestone-review-${REVIEW_ID}.runner.out \ + --stderr-file /tmp/milestone-review-${REVIEW_ID}.stderr \ + --status-file /tmp/milestone-review-${REVIEW_ID}.status \ + "${HELPER_SUCCESS_FILE_ARGS[@]}" +else + echo "Warning: reviewer runtime helper not found at $REVIEWER_RUNTIME; falling back to direct synchronous review." >&2 + bash /tmp/milestone-review-${REVIEW_ID}.sh >/tmp/milestone-review-${REVIEW_ID}.runner.out 2>/tmp/milestone-review-${REVIEW_ID}.stderr +fi +``` + +Run the helper in the foreground and watch its live stdout for `state=in-progress` heartbeats. If your agent environment buffers command output until exit, start the helper in the background and poll `/tmp/milestone-review-${REVIEW_ID}.status` separately instead of treating heartbeats as post-hoc-only data. + +After the command completes: + +- If `REVIEWER_CLI=cursor`, extract the final review text: + +```bash +CURSOR_SESSION_ID=$(jq -r '.session_id' /tmp/milestone-review-${REVIEW_ID}.json) +jq -r '.result' /tmp/milestone-review-${REVIEW_ID}.json > /tmp/milestone-review-${REVIEW_ID}.md +``` + +- If `REVIEWER_CLI=codex`, extract `CODEX_SESSION_ID` from `/tmp/milestone-review-${REVIEW_ID}.runner.out` after the helper or fallback run. If the review text is only in `.runner.out`, move or copy the actual review body into `/tmp/milestone-review-${REVIEW_ID}.md` before verdict parsing. +- If `REVIEWER_CLI=claude` or `REVIEWER_CLI=pi`, promote stdout captured by the helper or fallback runner into the markdown review file: + +```bash +cp /tmp/milestone-review-${REVIEW_ID}.runner.out /tmp/milestone-review-${REVIEW_ID}.md +``` + +Fallback is allowed only when the helper is missing or not executable. + +#### Step 4: Read Review & Check Verdict + +1. Read `/tmp/milestone-review-${REVIEW_ID}.md` +2. If the review failed, produced empty output, or reached helper timeout, also read: + - `/tmp/milestone-review-${REVIEW_ID}.stderr` + - `/tmp/milestone-review-${REVIEW_ID}.status` + - `/tmp/milestone-review-${REVIEW_ID}.runner.out` +3. Present review to the user: + +```markdown +## Milestone Review — Round N (reviewer: ${REVIEWER_CLI} / ${REVIEWER_MODEL}) + +[Reviewer feedback] +``` + +1. While the reviewer is still running, keep waiting as long as fresh `state=in-progress note="In progress N"` heartbeats continue to appear roughly once per minute. +2. Check verdict: + - **VERDICT: APPROVED** with no `P0`, `P1`, or `P2` findings -> proceed to Phase 4 Step 6 (commit & approve) + - **VERDICT: APPROVED** with only `P3` findings -> optionally fix the `P3` items if they are cheap and safe, then proceed + - **VERDICT: REVISE** or any `P0`, `P1`, or `P2` finding -> go to Step 5 + - No clear verdict but `P0`, `P1`, and `P2` are all `- None.` -> treat as approved + - Helper state `completed-empty-output` -> treat as failed review attempt, surface stderr/status, fix invocation or prompt handling, then retry + - Helper state `needs-operator-decision` -> surface status log and decide whether to extend the timeout, abort, or retry with different helper parameters + - Max rounds (`MAX_ROUNDS`) reached -> present to user for manual decision (proceed or stop) + +#### Step 5: Address Feedback & Re-verify + +1. Address the reviewer findings in priority order (`P0` -> `P1` -> `P2`, then `P3` when practical) (do NOT commit yet). +2. Re-run verification (lint/typecheck/tests) — all must pass. +3. Update `/tmp/milestone-${REVIEW_ID}.md` with new diff and verification output. + +Summarize revisions for the user: + +```markdown +### Revisions (Round N) +- [Change and reason, one bullet per issue addressed] +``` + +If a revision contradicts the user's explicit requirements, skip it and note it for the user. + +#### Step 6: Re-submit to Reviewer (Rounds 2-N) + +Rewrite `/tmp/milestone-review-${REVIEW_ID}.sh` for the next round. The script should contain the reviewer invocation only; do not run it directly. + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call with prior-round context (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "You previously reviewed this milestone and requested revisions. Read the updated payload at /tmp/milestone-${REVIEW_ID}.md and re-review using the same ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +Resume the existing session: + +```bash +codex exec resume ${CODEX_SESSION_ID} \ + -o /tmp/milestone-review-${REVIEW_ID}.md \ + "I've addressed your feedback. Updated diff and verification output are in /tmp/milestone-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." +``` + +If resume fails (session expired), fall back to fresh `codex exec` with context about prior rounds. + +**If `REVIEWER_CLI` is `claude`:** + +Fresh call with accumulated context (Claude CLI has no session resume): + +```bash +claude -p \ + "You previously reviewed milestone M<N> and requested revisions. + +Previous feedback summary: [key points from last review] + +I've addressed your feedback. Updated diff and verification output are below. + +$(cat /tmp/milestone-${REVIEW_ID}.md) + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +**If `REVIEWER_CLI` is `cursor`:** + +Resume the existing session: + +```bash +cursor-agent --resume ${CURSOR_SESSION_ID} -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "I've addressed your feedback. Updated diff and verification output are in /tmp/milestone-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." \ + > /tmp/milestone-review-${REVIEW_ID}.json +``` + +If resume fails, fall back to fresh `cursor-agent -p` with context about prior rounds. + +Do not run `jq` extraction until after the helper or fallback execution completes, then extract `/tmp/milestone-review-${REVIEW_ID}.md` from the JSON response. + +After updating `/tmp/milestone-review-${REVIEW_ID}.sh`, run the same helper/fallback flow from Round 1. + +Return to Step 4. + +#### Step 7: Cleanup Per Milestone + +```bash +rm -f \ + /tmp/milestone-${REVIEW_ID}.md \ + /tmp/milestone-review-${REVIEW_ID}.md \ + /tmp/milestone-review-${REVIEW_ID}.json \ + /tmp/milestone-review-${REVIEW_ID}.stderr \ + /tmp/milestone-review-${REVIEW_ID}.status \ + /tmp/milestone-review-${REVIEW_ID}.runner.out \ + /tmp/milestone-review-${REVIEW_ID}.sh +``` + +If the round failed, produced empty output, or reached operator-decision timeout, keep `.stderr`, `.status`, and `.runner.out` until the issue is diagnosed instead of deleting them immediately. + +### Phase 6: Completion (REQUIRED SUB-SKILL) + +After all milestones are approved and committed: + +1. Invoke `superpowers:finishing-a-development-branch`. +2. Run full test suite one final time — all must pass. +3. Merge the worktree branch into the parent branch: + +```bash +# From the main repo (not the worktree) +git merge implement/<plan-folder-name> +``` + +1. Delete the worktree and its branch: + +```bash +git worktree remove <worktree-path> +git branch -d implement/<plan-folder-name> +``` + +1. Mark plan status as `completed` in `story-tracker.md`. + +### Phase 7: Final Report + +Present summary: + +```markdown +## Implementation Complete + +**Plan:** <plan-folder-name> +**Milestones:** <N> completed, <N> approved +**Review rounds:** <total across all milestones> +**Branch:** implement/<plan-folder-name> (merged and deleted) +``` + +### Phase 8: Telegram Notification (MANDATORY) + +Resolve the Telegram notifier helper from the installed Codex skills directory: + +```bash +TELEGRAM_NOTIFY_RUNTIME=~/.codex/skills/reviewer-runtime/notify-telegram.sh +``` + +On every terminal outcome for the implement-plan run (fully completed, stopped after max rounds, skipped reviewer, or failure), send a Telegram summary if the helper exists and both `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` are configured: + +```bash +if [ -x "$TELEGRAM_NOTIFY_RUNTIME" ] && [ -n "${TELEGRAM_BOT_TOKEN:-}" ] && [ -n "${TELEGRAM_CHAT_ID:-}" ]; then + "$TELEGRAM_NOTIFY_RUNTIME" --message "implement-plan completed for <plan-folder-name>: <status summary>" +fi +``` + +Rules: + +- Telegram is the only supported notification path. Do not use desktop notifications, `say`, email, or any other notifier. +- Notification failures are non-blocking, but they must be surfaced to the user. +- Before stopping for any user interaction, approval, or manual decision, send a Telegram summary first if configured. +- If Telegram is not configured, state that no Telegram notification was sent. + +## Quick Reference + +| Phase | Action | Required Output | +|---|---|---| +| 1 | Locate plan in `ai_plan/` | Plan folder identified, files read | +| 2 | Configure reviewer CLI and model | `REVIEWER_CLI`, `REVIEWER_MODEL`, `MAX_ROUNDS` | +| 3 | Invoke `superpowers:using-git-worktrees` | Worktree created, baseline passing | +| 4 | Execute milestones (loop) | Stories tracked, verified, committed, reviewed | +| 5 | Milestone review loop (per milestone) | Reviewer approval or max rounds + user override | +| 6 | Invoke `superpowers:finishing-a-development-branch` | Branch merged to parent, worktree deleted | +| 7 | Final report | Summary presented | +| 8 | Send Telegram notification | User notified or notification status reported | + +## Tracker Discipline (MANDATORY) + +**ALWAYS update `story-tracker.md` before/after each story. NEVER proceed with stale tracker state.** + +Before starting any story: + +1. Open `story-tracker.md` +2. Mark story `in-dev` +3. Add notes if relevant +4. Then begin implementation + +After completing any story: + +1. Mark story `completed` +2. Review pending stories +3. Update Last Updated and Stories Complete counts + +Note: Commit hashes are backfilled into story Notes after the milestone commit (Step 6), not per-story. + +## Common Mistakes + +- Using deprecated commands like `superpowers-codex bootstrap` or `superpowers-codex use-skill`. +- Proceeding to milestone review with failing tests. +- Pushing commits before milestone approval. +- Skipping worktree setup and working directly on the main branch. +- Not capturing the Codex session ID for resume in subsequent review rounds. +- Forgetting to update `story-tracker.md` between stories. +- Creating a new worktree when one already exists for a resumed plan. +- Using any notification path other than Telegram. + +## Rationalizations and Counters + +| Rationalization | Counter | +|---|---| +| "Bootstrap CLI is faster" | Deprecated for Codex; native discovery is the supported path. | +| "I can skip the worktree for small plans" | Worktree isolation is mandatory — it protects the main branch. | +| "Tests passed earlier, I can skip re-verification" | Each milestone must be independently verified before review. | +| "The reviewer approved, I can skip my own validation" | Reviewer feedback supplements but does not replace your own verification. | +| "I can commit before the reviewer approves" | Code must only be committed after milestone approval — reviewers evaluate uncommitted diffs. | + +## Red Flags - Stop and Correct + +- You are about to run any `superpowers-codex` command. +- You are pushing commits without user approval. +- You did not announce which skill you invoked and why. +- You are proceeding to review with failing lint/typecheck/tests. +- You are skipping the worktree and working on the main branch. +- You are applying a reviewer suggestion that contradicts user requirements. + +## Verification Checklist + +- [ ] Plan folder located and all required files present +- [ ] Reviewer configured or explicitly skipped +- [ ] Max review rounds confirmed (default: 10) +- [ ] Worktree created with branch `implement/<plan-folder-name>` +- [ ] Worktree directory verified in .gitignore +- [ ] Baseline tests pass in worktree +- [ ] Each milestone: stories tracked (in-dev -> completed) +- [ ] Each milestone: lint/typecheck/tests pass before review +- [ ] Each milestone: reviewer approved (or max rounds + user override) +- [ ] Each milestone: committed locally only after approval +- [ ] Each milestone: marked approved in story-tracker.md +- [ ] All milestones completed, approved, and committed +- [ ] Final test suite passes +- [ ] Worktree branch merged to parent and worktree deleted +- [ ] Story tracker updated with final status +- [ ] Telegram notification attempted if configured diff --git a/skills/implement-plan/_source/cursor/SKILL.md b/skills/implement-plan/_source/cursor/SKILL.md new file mode 100644 index 0000000..b2e479b --- /dev/null +++ b/skills/implement-plan/_source/cursor/SKILL.md @@ -0,0 +1,727 @@ +--- +name: implement-plan +description: Use when a plan folder (from create-plan) exists and needs to be executed in an isolated git worktree with iterative cross-model milestone review in Cursor Agent CLI workflows. ALWAYS invoke when user says "implement the plan", "execute the plan", "start implementation", "resume the plan", or similar execution requests. +--- + +# Implement Plan (Cursor Agent CLI) + +Execute an existing plan (created by `create-plan`) in an isolated git worktree, with iterative cross-model review at each milestone boundary. + +## Overview + +This skill wraps the Superpowers execution flow for the Cursor Agent CLI (`cursor-agent`): + +1. Locate plan files under `ai_plan/` +2. Set up an isolated git worktree +3. Execute milestones one-by-one with lint/typecheck/test gates +4. Review each milestone with a second model/provider +5. Commit approved milestones, merge to parent branch, and delete worktree + +**Core principle:** Cursor Agent CLI discovers skills from `.cursor/skills/` (repo-local), `~/.cursor/skills/` (global), and installed Cursor plugin cache entries. It also reads `AGENTS.md` at the repo root for additional instructions. + +## Prerequisite Check (MANDATORY) + +Required: + +- Cursor Agent CLI: `cursor-agent --version` (install via `curl https://cursor.com/install -fsS | bash`). The binary is `cursor-agent` (installed to `~/.local/bin/`). Some environments alias it as `cursor agent` (subcommand of the Cursor IDE CLI) — both forms work, but this skill uses `cursor-agent` throughout. +- `jq` (required only if using `cursor` as the reviewer CLI): `jq --version` (install via `brew install jq` or your package manager) +- Plan folder exists under `ai_plan/` at project root +- `continuation-runbook.md` exists in plan folder +- `milestone-plan.md` exists in plan folder +- `story-tracker.md` exists in plan folder +- Git repo with worktree support: `git worktree list` +- Superpowers skills available from the Cursor plugin cache, `.cursor/skills/` (repo-local), or `~/.cursor/skills/` (global). Do not install both the plugin and a manual Superpowers copy, or Cursor may show duplicate skill entries. +- Superpowers execution skills: + - `superpowers:executing-plans` + - `superpowers:using-git-worktrees` + - `superpowers:verification-before-completion` + - `superpowers:finishing-a-development-branch` + +Verify before proceeding: + +```bash +cursor-agent --version +test -f .cursor/skills/superpowers/skills/executing-plans/SKILL.md || test -f ~/.cursor/skills/superpowers/skills/executing-plans/SKILL.md || find ~/.cursor/plugins/cache/cursor-public/superpowers -path '*/skills/executing-plans/SKILL.md' -print -quit 2>/dev/null | grep -q . +test -f .cursor/skills/superpowers/skills/using-git-worktrees/SKILL.md || test -f ~/.cursor/skills/superpowers/skills/using-git-worktrees/SKILL.md || find ~/.cursor/plugins/cache/cursor-public/superpowers -path '*/skills/using-git-worktrees/SKILL.md' -print -quit 2>/dev/null | grep -q . +test -f .cursor/skills/superpowers/skills/verification-before-completion/SKILL.md || test -f ~/.cursor/skills/superpowers/skills/verification-before-completion/SKILL.md || find ~/.cursor/plugins/cache/cursor-public/superpowers -path '*/skills/verification-before-completion/SKILL.md' -print -quit 2>/dev/null | grep -q . +test -f .cursor/skills/superpowers/skills/finishing-a-development-branch/SKILL.md || test -f ~/.cursor/skills/superpowers/skills/finishing-a-development-branch/SKILL.md || find ~/.cursor/plugins/cache/cursor-public/superpowers -path '*/skills/finishing-a-development-branch/SKILL.md' -print -quit 2>/dev/null | grep -q . +# Only if using cursor as reviewer CLI: +# jq --version +``` + +If any dependency is missing, stop and return: + +`Missing dependency: [specific missing item]. Install the Cursor Superpowers plugin or install Superpowers under .cursor/skills/ or ~/.cursor/skills/, then retry.` + +If no plan folder exists: + +`No plan found under ai_plan/. Run create-plan first.` + +## Required Skill Invocation Rules + +- Invoke relevant skills through Cursor-native discovery (`.cursor/skills/`, `~/.cursor/skills/`, or installed Cursor plugin cache entries). +- Announce skill usage explicitly: + - `I've read the [Skill Name] skill and I'm using it to [purpose].` +- For skills with checklists, track checklist items explicitly in conversation. + +## Process + +### Phase 1: Locate Plan + +1. Scan `ai_plan/` for plan directories (most recent first by date prefix). +2. If multiple plans exist, ask user which one to implement. +3. If no plan exists, stop: "No plan found. Run create-plan first." +4. Read `continuation-runbook.md` first (source of truth). +5. Read `story-tracker.md` to detect resume state (`in-dev` or `completed` stories). +6. Read `milestone-plan.md` for implementation details. + +### Phase 2: Configure Reviewer + +If the user has already specified a reviewer CLI and model (e.g., "implement the plan, review with codex o4-mini"), use those values. Otherwise, ask: + +1. **Which CLI should review each milestone?** + - `codex` — OpenAI Codex CLI (`codex exec`) + - `claude` — Claude Code CLI (`claude -p`) + - `cursor` — Cursor Agent CLI (`cursor-agent -p`) + - `skip` — No external review, proceed with user approval only + +2. **Which model?** (only if a CLI was chosen) + - For `codex`: default `o4-mini`, alternatives: `gpt-5.3-codex`, `o3` + - For `claude`: default `sonnet`, alternatives: `opus`, `haiku` + - For `cursor`: **run `cursor-agent models` first** to see available models + - Accept any model string the user provides + +3. **Max review rounds per milestone?** (default: 10) + - If the user does not provide a value, set `MAX_ROUNDS=10`. + +Store `REVIEWER_CLI`, `REVIEWER_MODEL`, and `MAX_ROUNDS`. These values are fixed for the entire run. + +Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, `pi`, or `skip`. + +If `REVIEWER_CLI=pi`, verify the Pi reviewer binary before entering the review loop: + +```bash +pi --version +``` + +For shorthand `pi/<pi-model-name>`, split only on the first slash when the prefix is exactly `pi`; store the complete remainder in `REVIEWER_MODEL`. Examples: `pi/claude-opus-4-7` -> `claude-opus-4-7`, `pi/anthropic/claude-opus-4-7` -> `anthropic/claude-opus-4-7`, and `pi/openrouter/anthropic/claude-opus-4-7` -> `openrouter/anthropic/claude-opus-4-7`. + +When `REVIEWER_CLI=pi`, the reviewer model is configured independently from the model running this workflow. If the model/provider is unavailable, surface helper stderr/status and use `pi --list-models [search]` to inspect configured models. + +### Phase 3: Set Up Worktree (REQUIRED SUB-SKILL) + +Invoke `superpowers:using-git-worktrees`. + +1. Branch naming: `implement/<plan-folder-name>` (e.g., `implement/2026-03-04-auth-system`). +2. Follow worktree skill's directory priority: `.worktrees/` > `worktrees/` > CLAUDE.md > ask user. +3. Verify `.gitignore` covers worktree directory. +4. Run project setup (auto-detect: `npm install`, `cargo build`, `pip install`, etc.). +5. Verify clean baseline (run tests). + +**Resume detection:** If `story-tracker.md` shows `in-dev` or `completed` stories, check if worktree branch already exists (`git worktree list`). If so, `cd` into existing worktree instead of creating a new one. + +### Phase 4: Execute Milestones (Loop) + +For each milestone (M1, M2, ...): + +#### Step 1: Read Milestone Spec + +Read the milestone section from `milestone-plan.md`. + +#### Step 2: Update Tracker + +Mark first story `in-dev` in `story-tracker.md`. + +#### Step 3: Implement Stories + +Execute each story in order. After completing each story: + +1. Mark `in-dev` -> `completed` in `story-tracker.md` +2. Update counts +3. Mark next story `in-dev` + +Commit hashes are not available yet — they are backfilled in Step 6 after the milestone is approved and committed. + +#### Step 4: Verify Milestone (REQUIRED SUB-SKILL) + +Invoke `superpowers:verification-before-completion`. + +```bash +# Lint changed files +# Typecheck +# Run tests (targeted first, then full suite) +``` + +All must pass before proceeding. If failures: fix, re-verify. Do NOT proceed to review with failures. + +#### Step 5: Milestone Review Loop + +Send to reviewer for approval **before committing**. See Phase 5 for details. The review payload uses working-tree diffs (`git diff` for unstaged, `git diff --staged` for staged changes). + +**Skip this step if reviewer was set to `skip`.** When skipped, present the milestone summary to the user and ask for approval directly. + +#### Step 6: Commit & Approve + +Only after the reviewer approves (or user overrides at max rounds): + +```bash +git add <changed-files> +git commit -m "feat(<scope>): implement milestone M<N> - <description>" +``` + +Do NOT push. After committing: + +1. Backfill the commit hash into the Notes column for all stories in this milestone in `story-tracker.md`. +2. Mark milestone as `approved` in `story-tracker.md`. +3. Move to next milestone. + +### Phase 5: Milestone Review Loop (Detail) + +**Skip this phase entirely if reviewer was set to `skip`.** + +#### Step 1: Generate Session ID + +```bash +REVIEW_ID=$(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) +``` + +Use `REVIEW_ID` for all milestone review temp file paths: + +- `/tmp/milestone-${REVIEW_ID}.md` +- `/tmp/milestone-review-${REVIEW_ID}.md` +- `/tmp/milestone-review-${REVIEW_ID}.json` +- `/tmp/milestone-review-${REVIEW_ID}.stderr` +- `/tmp/milestone-review-${REVIEW_ID}.status` +- `/tmp/milestone-review-${REVIEW_ID}.runner.out` +- `/tmp/milestone-review-${REVIEW_ID}.sh` + +Resolve the shared runtime helper path before writing the command script: + +```bash +if [ -x .cursor/skills/reviewer-runtime/run-review.sh ]; then + REVIEWER_RUNTIME=.cursor/skills/reviewer-runtime/run-review.sh +else + REVIEWER_RUNTIME=~/.cursor/skills/reviewer-runtime/run-review.sh +fi +``` + +Set helper success-artifact args before writing the command script: + +```bash +HELPER_SUCCESS_FILE_ARGS=() +case "$REVIEWER_CLI" in + codex) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/milestone-review-${REVIEW_ID}.md) + ;; + cursor) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/milestone-review-${REVIEW_ID}.json) + ;; +esac +``` + +#### Step 2: Write Review Payload + +Write to `/tmp/milestone-${REVIEW_ID}.md`: + +```markdown +# Milestone M<N> Review: <title> + +## Milestone Spec (from plan) +[Copy milestone section from milestone-plan.md] + +## Acceptance Criteria +[Copy acceptance criteria checkboxes] + +## Changes Made (git diff) +[Output of: git diff -- for unstaged changes, or git diff --staged for staged changes] + +## Verification Output +### Lint +[lint output] +### Typecheck +[typecheck output] +### Tests +[test output with pass/fail counts] +``` + +#### Review Contract (Applies to Every Round) + +The reviewer response must use this structure: + +```text +## Summary +... + +## Findings +### P0 +- ... +### P1 +- ... +### P2 +- ... +### P3 +- ... + +## Verdict +VERDICT: APPROVED +``` + +Rules: + +- Order findings from `P0` to `P3`. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- Use `- None.` when a severity has no findings. +- `VERDICT: APPROVED` is allowed only when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking. +- The calling agent should still try to fix `P3` findings when they are cheap and safe. + +#### Liveness Contract (Applies While Review Is Running) + +- The shared reviewer runtime emits `state=in-progress note="In progress N"` heartbeats every 60 seconds while the reviewer child is alive. +- The calling agent must keep waiting as long as a fresh `In progress N` heartbeat keeps arriving roughly once per minute. +- Do not abort just because the review is slow, a soft timeout fired, or a `stall-warning` line appears, as long as the `In progress N` heartbeat continues. +- Treat missing heartbeats, `state=failed`, `state=completed-empty-output`, and `state=needs-operator-decision` as escalation signals. + +#### Step 3: Submit to Reviewer (Round 1) + +Write the reviewer invocation to `/tmp/milestone-review-${REVIEW_ID}.sh` as a bash script: + +```bash +#!/usr/bin/env bash +set -euo pipefail +``` + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call every round (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "Read the file /tmp/milestone-${REVIEW_ID}.md and review. Return exactly the required ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +```bash +codex exec \ + -m ${REVIEWER_MODEL} \ + -s read-only \ + -o /tmp/milestone-review-${REVIEW_ID}.md \ + "Review this milestone implementation. The spec, acceptance criteria, git diff, and verification output are in /tmp/milestone-${REVIEW_ID}.md. + +Evaluate: +1. Correctness — Does the implementation match the milestone spec? +2. Acceptance criteria — Are all criteria met? +3. Code quality — Clean, maintainable, no obvious issues? +4. Test coverage — Are changes adequately tested? +5. Security — Any security concerns introduced? + + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." +``` + +Do not try to capture the Codex session ID yet. When using the helper, extract it from `/tmp/milestone-review-${REVIEW_ID}.runner.out` after the command completes (look for `session id: <uuid>`), then store it as `CODEX_SESSION_ID` for resume in subsequent rounds. + +**If `REVIEWER_CLI` is `claude`:** + +```bash +claude -p \ + "Review this milestone implementation using the following spec, acceptance criteria, git diff, and verification output: + +$(cat /tmp/milestone-${REVIEW_ID}.md) + +Evaluate: +1. Correctness — Does the implementation match the milestone spec? +2. Acceptance criteria — Are all criteria met? +3. Code quality — Clean, maintainable, no obvious issues? +4. Test coverage — Are changes adequately tested? +5. Security — Any security concerns introduced? + + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +**If `REVIEWER_CLI` is `cursor`:** + +```bash +cursor-agent -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "Read the file /tmp/milestone-${REVIEW_ID}.md and review this milestone implementation. + +Evaluate: +1. Correctness — Does the implementation match the milestone spec? +2. Acceptance criteria — Are all criteria met? +3. Code quality — Clean, maintainable, no obvious issues? +4. Test coverage — Are changes adequately tested? +5. Security — Any security concerns introduced? + + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." \ + > /tmp/milestone-review-${REVIEW_ID}.json +``` + +For `cursor`, the command script writes raw JSON to `/tmp/milestone-review-${REVIEW_ID}.json`. Do not run `jq` extraction until after the helper or fallback execution completes. + +Notes on Cursor flags: + +- `--mode=ask` — read-only mode, no file modifications +- `--trust` — trust workspace without prompting (required for non-interactive use) +- `-p` / `--print` — non-interactive mode, output to stdout +- `--output-format json` — structured output with `session_id` and `result` fields + +Run the command script through the shared helper when available: + +```bash +if [ -x "$REVIEWER_RUNTIME" ]; then + "$REVIEWER_RUNTIME" \ + --command-file /tmp/milestone-review-${REVIEW_ID}.sh \ + --stdout-file /tmp/milestone-review-${REVIEW_ID}.runner.out \ + --stderr-file /tmp/milestone-review-${REVIEW_ID}.stderr \ + --status-file /tmp/milestone-review-${REVIEW_ID}.status \ + "${HELPER_SUCCESS_FILE_ARGS[@]}" +else + echo "Warning: reviewer runtime helper not found at $REVIEWER_RUNTIME; falling back to direct synchronous review." >&2 + bash /tmp/milestone-review-${REVIEW_ID}.sh >/tmp/milestone-review-${REVIEW_ID}.runner.out 2>/tmp/milestone-review-${REVIEW_ID}.stderr +fi +``` + +Run the helper in the foreground and watch its live stdout for `state=in-progress` heartbeats. If your agent environment buffers command output until exit, start the helper in the background and poll `/tmp/milestone-review-${REVIEW_ID}.status` separately instead of treating heartbeats as post-hoc-only data. + +After the command completes: + +- If `REVIEWER_CLI=cursor`, extract the final review text: + +```bash +CURSOR_SESSION_ID=$(jq -r '.session_id' /tmp/milestone-review-${REVIEW_ID}.json) +jq -r '.result' /tmp/milestone-review-${REVIEW_ID}.json > /tmp/milestone-review-${REVIEW_ID}.md +``` + +- If `REVIEWER_CLI=codex`, extract `CODEX_SESSION_ID` from `/tmp/milestone-review-${REVIEW_ID}.runner.out` after the helper or fallback run. If the review text is only in `.runner.out`, move or copy the actual review body into `/tmp/milestone-review-${REVIEW_ID}.md` before verdict parsing. +- If `REVIEWER_CLI=claude` or `REVIEWER_CLI=pi`, promote stdout captured by the helper or fallback runner into the markdown review file: + +```bash +cp /tmp/milestone-review-${REVIEW_ID}.runner.out /tmp/milestone-review-${REVIEW_ID}.md +``` + +Fallback is allowed only when the helper is missing or not executable. + +#### Step 4: Read Review & Check Verdict + +1. Read `/tmp/milestone-review-${REVIEW_ID}.md` +2. If the review failed, produced empty output, or reached helper timeout, also read: + - `/tmp/milestone-review-${REVIEW_ID}.stderr` + - `/tmp/milestone-review-${REVIEW_ID}.status` + - `/tmp/milestone-review-${REVIEW_ID}.runner.out` +3. Present review to the user: + +```markdown +## Milestone Review — Round N (reviewer: ${REVIEWER_CLI} / ${REVIEWER_MODEL}) + +[Reviewer feedback] +``` + +1. While the reviewer is still running, keep waiting as long as fresh `state=in-progress note="In progress N"` heartbeats continue to appear roughly once per minute. +2. Check verdict: + - **VERDICT: APPROVED** with no `P0`, `P1`, or `P2` findings -> proceed to Phase 4 Step 6 (commit & approve) + - **VERDICT: APPROVED** with only `P3` findings -> optionally fix the `P3` items if they are cheap and safe, then proceed + - **VERDICT: REVISE** or any `P0`, `P1`, or `P2` finding -> go to Step 5 + - No clear verdict but `P0`, `P1`, and `P2` are all `- None.` -> treat as approved + - Helper state `completed-empty-output` -> treat as failed review attempt, surface stderr/status, fix invocation or prompt handling, then retry + - Helper state `needs-operator-decision` -> surface status log and decide whether to extend the timeout, abort, or retry with different helper parameters + - Max rounds (`MAX_ROUNDS`) reached -> present to user for manual decision (proceed or stop) + +#### Step 5: Address Feedback & Re-verify + +1. Address the reviewer findings in priority order (`P0` -> `P1` -> `P2`, then `P3` when practical) (do NOT commit yet). +2. Re-run verification (lint/typecheck/tests) — all must pass. +3. Update `/tmp/milestone-${REVIEW_ID}.md` with new diff and verification output. + +Summarize revisions for the user: + +```markdown +### Revisions (Round N) +- [Change and reason, one bullet per issue addressed] +``` + +If a revision contradicts the user's explicit requirements, skip it and note it for the user. + +#### Step 6: Re-submit to Reviewer (Rounds 2-N) + +Rewrite `/tmp/milestone-review-${REVIEW_ID}.sh` for the next round. The script should contain the reviewer invocation only; do not run it directly. + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call with prior-round context (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "You previously reviewed this milestone and requested revisions. Read the updated payload at /tmp/milestone-${REVIEW_ID}.md and re-review using the same ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +Resume the existing session: + +```bash +codex exec resume ${CODEX_SESSION_ID} \ + -o /tmp/milestone-review-${REVIEW_ID}.md \ + "I've addressed your feedback. Updated diff and verification output are in /tmp/milestone-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." +``` + +If resume fails (session expired), fall back to fresh `codex exec` with context about prior rounds. + +**If `REVIEWER_CLI` is `claude`:** + +Fresh call with accumulated context (Claude CLI has no session resume): + +```bash +claude -p \ + "You previously reviewed milestone M<N> and requested revisions. + +Previous feedback summary: [key points from last review] + +I've addressed your feedback. Updated diff and verification output are below. + +$(cat /tmp/milestone-${REVIEW_ID}.md) + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +**If `REVIEWER_CLI` is `cursor`:** + +Resume the existing session: + +```bash +cursor-agent --resume ${CURSOR_SESSION_ID} -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "I've addressed your feedback. Updated diff and verification output are in /tmp/milestone-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." \ + > /tmp/milestone-review-${REVIEW_ID}.json +``` + +If resume fails, fall back to fresh `cursor-agent -p` with context about prior rounds. + +Do not run `jq` extraction until after the helper or fallback execution completes, then extract `/tmp/milestone-review-${REVIEW_ID}.md` from the JSON response. + +After updating `/tmp/milestone-review-${REVIEW_ID}.sh`, run the same helper/fallback flow from Round 1. + +Return to Step 4. + +#### Step 7: Cleanup Per Milestone + +```bash +rm -f \ + /tmp/milestone-${REVIEW_ID}.md \ + /tmp/milestone-review-${REVIEW_ID}.md \ + /tmp/milestone-review-${REVIEW_ID}.json \ + /tmp/milestone-review-${REVIEW_ID}.stderr \ + /tmp/milestone-review-${REVIEW_ID}.status \ + /tmp/milestone-review-${REVIEW_ID}.runner.out \ + /tmp/milestone-review-${REVIEW_ID}.sh +``` + +If the round failed, produced empty output, or reached operator-decision timeout, keep `.stderr`, `.status`, and `.runner.out` until the issue is diagnosed instead of deleting them immediately. + +### Phase 6: Completion (REQUIRED SUB-SKILL) + +After all milestones are approved and committed: + +1. Invoke `superpowers:finishing-a-development-branch`. +2. Run full test suite one final time — all must pass. +3. Merge the worktree branch into the parent branch: + +```bash +# From the main repo (not the worktree) +git merge implement/<plan-folder-name> +``` + +1. Delete the worktree and its branch: + +```bash +git worktree remove <worktree-path> +git branch -d implement/<plan-folder-name> +``` + +1. Mark plan status as `completed` in `story-tracker.md`. + +### Phase 7: Final Report + +Present summary: + +```markdown +## Implementation Complete + +**Plan:** <plan-folder-name> +**Milestones:** <N> completed, <N> approved +**Review rounds:** <total across all milestones> +**Branch:** implement/<plan-folder-name> (merged and deleted) +``` + +### Phase 8: Telegram Notification (MANDATORY) + +Resolve the Telegram notifier helper from Cursor's installed skills directory: + +```bash +if [ -x .cursor/skills/reviewer-runtime/notify-telegram.sh ]; then + TELEGRAM_NOTIFY_RUNTIME=.cursor/skills/reviewer-runtime/notify-telegram.sh +else + TELEGRAM_NOTIFY_RUNTIME=~/.cursor/skills/reviewer-runtime/notify-telegram.sh +fi +``` + +On every terminal outcome for the implement-plan run (fully completed, stopped after max rounds, skipped reviewer, or failure), send a Telegram summary if the helper exists and both `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` are configured: + +```bash +if [ -x "$TELEGRAM_NOTIFY_RUNTIME" ] && [ -n "${TELEGRAM_BOT_TOKEN:-}" ] && [ -n "${TELEGRAM_CHAT_ID:-}" ]; then + "$TELEGRAM_NOTIFY_RUNTIME" --message "implement-plan completed for <plan-folder-name>: <status summary>" +fi +``` + +Rules: + +- Telegram is the only supported notification path. Do not use desktop notifications, `say`, email, or any other notifier. +- Notification failures are non-blocking, but they must be surfaced to the user. +- Before stopping for any user interaction, approval, or manual decision, send a Telegram summary first if configured. +- If Telegram is not configured, state that no Telegram notification was sent. + +## Quick Reference + +| Phase | Action | Required Output | +|---|---|---| +| 1 | Locate plan in `ai_plan/` | Plan folder identified, files read | +| 2 | Configure reviewer CLI and model | `REVIEWER_CLI`, `REVIEWER_MODEL`, `MAX_ROUNDS` | +| 3 | Invoke `superpowers:using-git-worktrees` | Worktree created, baseline passing | +| 4 | Execute milestones (loop) | Stories tracked, verified, committed, reviewed | +| 5 | Milestone review loop (per milestone) | Reviewer approval or max rounds + user override | +| 6 | Invoke `superpowers:finishing-a-development-branch` | Branch merged to parent, worktree deleted | +| 7 | Final report | Summary presented | +| 8 | Send Telegram notification | User notified or notification status reported | + +## Tracker Discipline (MANDATORY) + +Before starting any story: + +1. Open `story-tracker.md` +2. Mark story `in-dev` +3. Add notes if relevant +4. Then begin implementation + +After completing any story: + +1. Mark story `completed` +2. Review pending stories +3. Update Last Updated and Stories Complete counts + +Note: Commit hashes are backfilled into story Notes after the milestone commit (Step 6), not per-story. + +## Common Mistakes + +- Forgetting `--trust` flag when running `cursor-agent` non-interactively (causes interactive prompt). +- Using `--mode=agent` or `--force` for reviews (reviewer should be read-only, use `--mode=ask`). +- Proceeding to milestone review with failing tests. +- Pushing commits before milestone approval. +- Skipping worktree setup and working directly on the main branch. +- Forgetting to update `story-tracker.md` between stories. +- Creating a new worktree when one already exists for a resumed plan. +- Using any notification path other than Telegram. + +## Red Flags - Stop and Correct + +- You started implementing without reading `continuation-runbook.md` first. +- You are pushing commits without user approval. +- You did not announce which skill you invoked and why. +- You are proceeding to review with failing lint/typecheck/tests. +- You are skipping the worktree and working on the main branch. +- You are applying a reviewer suggestion that contradicts user requirements. +- Reviewer CLI is running with write permissions (must be read-only). + +## Verification Checklist + +- [ ] Plan folder located and all required files present +- [ ] Reviewer configured or explicitly skipped +- [ ] Max review rounds confirmed (default: 10) +- [ ] Worktree created with branch `implement/<plan-folder-name>` +- [ ] Worktree directory verified in .gitignore +- [ ] Baseline tests pass in worktree +- [ ] Each milestone: stories tracked (in-dev -> completed) +- [ ] Each milestone: lint/typecheck/tests pass before review +- [ ] Each milestone: reviewer approved (or max rounds + user override) +- [ ] Each milestone: committed locally only after approval +- [ ] Each milestone: marked approved in story-tracker.md +- [ ] All milestones completed, approved, and committed +- [ ] Final test suite passes +- [ ] Worktree branch merged to parent and worktree deleted +- [ ] Story tracker updated with final status +- [ ] Telegram notification attempted if configured diff --git a/skills/implement-plan/_source/opencode/SKILL.md b/skills/implement-plan/_source/opencode/SKILL.md new file mode 100644 index 0000000..d80cdcc --- /dev/null +++ b/skills/implement-plan/_source/opencode/SKILL.md @@ -0,0 +1,797 @@ +--- +name: implement-plan +description: Use when a plan folder (from create-plan) exists and needs to be executed in an isolated git worktree with iterative cross-model milestone review in OpenCode workflows. ALWAYS invoke when user says "implement the plan", "execute the plan", "start implementation", "resume the plan", or similar execution requests. +--- + +# Implement Plan (OpenCode) + +Execute an existing plan (created by `create-plan`) in an isolated git worktree, with iterative cross-model review at each milestone boundary. + +## Prerequisite Check (MANDATORY) + +This OpenCode variant depends on Superpowers execution skills being installed via OpenCode's native skill system. + +Required: + +- Plan folder exists under `ai_plan/` at project root +- `continuation-runbook.md` exists in plan folder +- `milestone-plan.md` exists in plan folder +- `story-tracker.md` exists in plan folder +- Git repo with worktree support: `git worktree list` +- OpenCode Superpowers skills available at `~/.agents/skills/superpowers` or `~/.config/opencode/skills/superpowers` +- Superpowers execution skills: + - `superpowers/executing-plans` + - `superpowers/using-git-worktrees` + - `superpowers/verification-before-completion` + - `superpowers/finishing-a-development-branch` + +Verify before proceeding: + +```bash +test -f ~/.agents/skills/superpowers/executing-plans/SKILL.md || test -f ~/.config/opencode/skills/superpowers/executing-plans/SKILL.md +test -f ~/.agents/skills/superpowers/using-git-worktrees/SKILL.md || test -f ~/.config/opencode/skills/superpowers/using-git-worktrees/SKILL.md +test -f ~/.agents/skills/superpowers/verification-before-completion/SKILL.md || test -f ~/.config/opencode/skills/superpowers/verification-before-completion/SKILL.md +test -f ~/.agents/skills/superpowers/finishing-a-development-branch/SKILL.md || test -f ~/.config/opencode/skills/superpowers/finishing-a-development-branch/SKILL.md +``` + +If dependencies are missing, stop immediately and return: + +"Missing dependency: OpenCode Superpowers execution skills are required (`superpowers/executing-plans`, `superpowers/using-git-worktrees`, `superpowers/verification-before-completion`, `superpowers/finishing-a-development-branch`). Install from https://github.com/obra/superpowers (OpenCode setup), then retry." + +If no plan folder exists: + +"No plan found under `ai_plan/`. Run `create-plan` first." + +## Process + +### Phase 1: Bootstrap Superpowers Context (REQUIRED) + +Use OpenCode's native skill tool: + +- list skills +- verify `superpowers/executing-plans`, `superpowers/using-git-worktrees`, `superpowers/verification-before-completion`, and `superpowers/finishing-a-development-branch` are discoverable + +### Phase 2: Locate Plan + +1. Scan `ai_plan/` for plan directories (most recent first by date prefix). +2. If multiple plans exist, ask user which one to implement. +3. If no plan exists, stop: "No plan found. Run create-plan first." +4. Read `continuation-runbook.md` first (source of truth). +5. Read `story-tracker.md` to detect resume state (`in-dev` or `completed` stories). +6. Read `milestone-plan.md` for implementation details. + +### Phase 3: Configure Reviewer + +If the user has already specified a reviewer CLI and model (e.g., "implement the plan, review with codex o4-mini"), use those values. Otherwise, ask: + +1. **Which CLI should review each milestone?** + - `codex` — OpenAI Codex CLI (`codex exec`) + - `claude` — Claude Code CLI (`claude -p`) + - `cursor` — Cursor Agent CLI (`cursor-agent -p`) + - `skip` — No external review, proceed with user approval only + +2. **Which model?** (only if a CLI was chosen) + - For `codex`: default `o4-mini`, alternatives: `gpt-5.3-codex`, `o3` + - For `claude`: default `sonnet`, alternatives: `opus`, `haiku` + - For `cursor`: **run `cursor-agent models` first** to see available models + - Accept any model string the user provides + +3. **Max review rounds per milestone?** (default: 10) + - If the user does not provide a value, set `MAX_ROUNDS=10`. + +Store `REVIEWER_CLI`, `REVIEWER_MODEL`, and `MAX_ROUNDS`. These values are fixed for the entire run. + +Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, `pi`, or `skip`. + +If `REVIEWER_CLI=pi`, verify the Pi reviewer binary before entering the review loop: + +```bash +pi --version +``` + +For shorthand `pi/<pi-model-name>`, split only on the first slash when the prefix is exactly `pi`; store the complete remainder in `REVIEWER_MODEL`. Examples: `pi/claude-opus-4-7` -> `claude-opus-4-7`, `pi/anthropic/claude-opus-4-7` -> `anthropic/claude-opus-4-7`, and `pi/openrouter/anthropic/claude-opus-4-7` -> `openrouter/anthropic/claude-opus-4-7`. + +When `REVIEWER_CLI=pi`, the reviewer model is configured independently from the model running this workflow. If the model/provider is unavailable, surface helper stderr/status and use `pi --list-models [search]` to inspect configured models. + +### Phase 4: Set Up Worktree (REQUIRED SUB-SKILL) + +Use OpenCode's native skill tool to load: + +- `superpowers/using-git-worktrees` + +Then: + +1. Branch naming: `implement/<plan-folder-name>` (e.g., `implement/2026-03-04-auth-system`). +2. Follow worktree skill's directory priority: `.worktrees/` > `worktrees/` > CLAUDE.md > ask user. +3. Verify `.gitignore` covers worktree directory. +4. Run project setup (auto-detect: `npm install`, `cargo build`, `pip install`, etc.). +5. Verify clean baseline (run tests). + +**Resume detection:** If `story-tracker.md` shows `in-dev` or `completed` stories, check if worktree branch already exists (`git worktree list`). If so, `cd` into existing worktree instead of creating a new one. + +### Phase 5: Execute Milestones (Loop) + +For each milestone (M1, M2, ...): + +#### Step 1: Read Milestone Spec + +Read the milestone section from `milestone-plan.md`. + +#### Step 2: Update Tracker + +Mark first story `in-dev` in `story-tracker.md`. + +#### Step 3: Implement Stories + +Execute each story in order. After completing each story: + +1. Mark `in-dev` -> `completed` in `story-tracker.md` +2. Update counts +3. Mark next story `in-dev` + +Commit hashes are not available yet — they are backfilled in Step 6 after the milestone is approved and committed. + +#### Step 4: Verify Milestone (REQUIRED SUB-SKILL) + +Use OpenCode's native skill tool to load: + +- `superpowers/verification-before-completion` + +```bash +# Lint changed files +# Typecheck +# Run tests (targeted first, then full suite) +``` + +All must pass before proceeding. If failures: fix, re-verify. Do NOT proceed to review with failures. + +#### Step 5: Milestone Review Loop + +Send to reviewer for approval **before committing**. See Phase 6 for details. The review payload uses working-tree diffs (`git diff` for unstaged, `git diff --staged` for staged changes). + +**Skip this step if reviewer was set to `skip`.** When skipped, present the milestone summary to the user and ask for approval directly. + +#### Step 6: Commit & Approve + +Only after the reviewer approves (or user overrides at max rounds): + +```bash +git add <changed-files> +git commit -m "feat(<scope>): implement milestone M<N> - <description>" +``` + +Do NOT push. After committing: + +1. Backfill the commit hash into the Notes column for all stories in this milestone in `story-tracker.md`. +2. Mark milestone as `approved` in `story-tracker.md`. +3. Move to next milestone. + +### Phase 6: Milestone Review Loop (Detail) + +**Skip this phase entirely if reviewer was set to `skip`.** + +#### Step 1: Generate Session ID + +```bash +REVIEW_ID=$(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) +``` + +Use `REVIEW_ID` for all milestone review temp file paths: + +- `/tmp/milestone-${REVIEW_ID}.md` +- `/tmp/milestone-review-${REVIEW_ID}.md` +- `/tmp/milestone-review-${REVIEW_ID}.json` +- `/tmp/milestone-review-${REVIEW_ID}.stderr` +- `/tmp/milestone-review-${REVIEW_ID}.status` +- `/tmp/milestone-review-${REVIEW_ID}.runner.out` +- `/tmp/milestone-review-${REVIEW_ID}.sh` + +Resolve the shared runtime helper path before writing the command script: + +```bash +REVIEWER_RUNTIME=~/.config/opencode/skills/reviewer-runtime/run-review.sh +``` + +Set helper success-artifact args before writing the command script: + +```bash +HELPER_SUCCESS_FILE_ARGS=() +case "$REVIEWER_CLI" in + codex) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/milestone-review-${REVIEW_ID}.md) + ;; + cursor) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/milestone-review-${REVIEW_ID}.json) + ;; + opencode) + HELPER_SUCCESS_FILE_ARGS+=(--success-file /tmp/milestone-review-${REVIEW_ID}.md) + ;; +esac +``` + +#### Step 2: Write Review Payload + +Write to `/tmp/milestone-${REVIEW_ID}.md`: + +```markdown +# Milestone M<N> Review: <title> + +## Milestone Spec (from plan) +[Copy milestone section from milestone-plan.md] + +## Acceptance Criteria +[Copy acceptance criteria checkboxes] + +## Changes Made (git diff) +[Output of: git diff -- for unstaged changes, or git diff --staged for staged changes] + +## Verification Output +### Lint +[lint output] +### Typecheck +[typecheck output] +### Tests +[test output with pass/fail counts] +``` + +#### Review Contract (Applies to Every Round) + +The reviewer response must use this structure: + +```text +## Summary +... + +## Findings +### P0 +- ... +### P1 +- ... +### P2 +- ... +### P3 +- ... + +## Verdict +VERDICT: APPROVED +``` + +Rules: + +- Order findings from `P0` to `P3`. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- Use `- None.` when a severity has no findings. +- `VERDICT: APPROVED` is allowed only when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking. +- The calling agent should still try to fix `P3` findings when they are cheap and safe. + +#### Liveness Contract (Applies While Review Is Running) + +- The shared reviewer runtime emits `state=in-progress note="In progress N"` heartbeats every 60 seconds while the reviewer child is alive. +- The calling agent must keep waiting as long as a fresh `In progress N` heartbeat keeps arriving roughly once per minute. +- Do not abort just because the review is slow, a soft timeout fired, or a `stall-warning` line appears, as long as the `In progress N` heartbeat continues. +- Treat missing heartbeats, `state=failed`, `state=completed-empty-output`, and `state=needs-operator-decision` as escalation signals. + +#### Step 3: Submit to Reviewer (Round 1) + +Write the reviewer invocation to `/tmp/milestone-review-${REVIEW_ID}.sh` as a bash script: + +```bash +#!/usr/bin/env bash +set -euo pipefail +``` + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call every round (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "Read the file /tmp/milestone-${REVIEW_ID}.md and review. Return exactly the required ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +```bash +codex exec \ + -m ${REVIEWER_MODEL} \ + -s read-only \ + -o /tmp/milestone-review-${REVIEW_ID}.md \ + "Review this milestone implementation. The spec, acceptance criteria, git diff, and verification output are in /tmp/milestone-${REVIEW_ID}.md. + +Evaluate: +1. Correctness — Does the implementation match the milestone spec? +2. Acceptance criteria — Are all criteria met? +3. Code quality — Clean, maintainable, no obvious issues? +4. Test coverage — Are changes adequately tested? +5. Security — Any security concerns introduced? + + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." +``` + +Do not try to capture the Codex session ID yet. When using the helper, extract it from `/tmp/milestone-review-${REVIEW_ID}.runner.out` after the command completes (look for `session id: <uuid>`), then store it as `CODEX_SESSION_ID` for resume in subsequent rounds. + +**If `REVIEWER_CLI` is `claude`:** + +```bash +claude -p \ + "Review this milestone implementation using the following spec, acceptance criteria, git diff, and verification output: + +$(cat /tmp/milestone-${REVIEW_ID}.md) + +Evaluate: +1. Correctness — Does the implementation match the milestone spec? +2. Acceptance criteria — Are all criteria met? +3. Code quality — Clean, maintainable, no obvious issues? +4. Test coverage — Are changes adequately tested? +5. Security — Any security concerns introduced? + + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +**If `REVIEWER_CLI` is `cursor`:** + +```bash +cursor-agent -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "Read the file /tmp/milestone-${REVIEW_ID}.md and review this milestone implementation. + +Evaluate: +1. Correctness — Does the implementation match the milestone spec? +2. Acceptance criteria — Are all criteria met? +3. Code quality — Clean, maintainable, no obvious issues? +4. Test coverage — Are changes adequately tested? +5. Security — Any security concerns introduced? + + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use `- None.` when a severity has no findings. +- `P0` = total blocker, `P1` = major risk, `P2` = must-fix before approval, `P3` = cosmetic / nice to have. +- End with exactly one verdict line: `VERDICT: APPROVED` or `VERDICT: REVISE` +- `VERDICT: APPROVED` is allowed only when there are no `P0`, `P1`, or `P2` findings. `P3` findings are non-blocking." \ + > /tmp/milestone-review-${REVIEW_ID}.json +``` + +For `cursor`, the command script writes raw JSON to `/tmp/milestone-review-${REVIEW_ID}.json`. Do not run `jq` extraction until after the helper or fallback execution completes. If `jq` is not installed, inform the user: `brew install jq` (macOS) or equivalent. + +**If `REVIEWER_CLI` is `opencode`:** + +OpenCode uses `--agent plan` for read-oriented review. Fresh call is the recommended default. + +Round 1: + +```bash +opencode run \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "Read the file /tmp/milestone-${REVIEW_ID}.md and review this milestone implementation. + +Evaluate: +1. Correctness — Does the implementation match the milestone spec? +2. Acceptance criteria — Are all criteria met? +3. Code quality — Clean, maintainable, no obvious issues? +4. Test coverage — Are changes adequately tested? +5. Security — Any security concerns introduced? + +Return exactly these sections in order: +## Summary +## Findings +### P0 +### P1 +### P2 +### P3 +## Verdict + +Rules: +- Order findings from highest severity to lowest. +- Use \`- None.\` when a severity has no findings. +- \`P0\` = total blocker, \`P1\` = major risk, \`P2\` = must-fix before approval, \`P3\` = cosmetic / nice to have. +- End with exactly one verdict line: \`VERDICT: APPROVED\` or \`VERDICT: REVISE\` +- \`VERDICT: APPROVED\` is allowed only when there are no \`P0\`, \`P1\`, or \`P2\` findings. \`P3\` findings are non-blocking." \ + > /tmp/milestone-review-${REVIEW_ID}.json +``` + +Round 2 and later (fresh-call, recommended default): + +```bash +opencode run \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "You previously reviewed this milestone and requested revisions. + +Previous feedback summary: [key points from last review] + +I've revised. Updated milestone payload is in /tmp/milestone-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same ## Summary, ## Findings, and ## Verdict structure as before." \ + > /tmp/milestone-review-${REVIEW_ID}.json +``` + +Extract the review body: + +```bash +jq -r '.[] | select(.type == "message" and .role == "assistant") | .content' \ + /tmp/milestone-review-${REVIEW_ID}.json \ + > /tmp/milestone-review-${REVIEW_ID}.md \ + || cp /tmp/milestone-review-${REVIEW_ID}.json /tmp/milestone-review-${REVIEW_ID}.md +``` + +If the JSON parse falls through, promote the raw JSON file as the review output. On any opencode +CLI or JSON parsing failure, treat this loop round as `completed-empty-output` and follow the +helper-failure escalation in Step 4. + +Run the command script through the shared helper when available: + +```bash +if [ -x "$REVIEWER_RUNTIME" ]; then + "$REVIEWER_RUNTIME" \ + --command-file /tmp/milestone-review-${REVIEW_ID}.sh \ + --stdout-file /tmp/milestone-review-${REVIEW_ID}.runner.out \ + --stderr-file /tmp/milestone-review-${REVIEW_ID}.stderr \ + --status-file /tmp/milestone-review-${REVIEW_ID}.status \ + "${HELPER_SUCCESS_FILE_ARGS[@]}" +else + echo "Warning: reviewer runtime helper not found at $REVIEWER_RUNTIME; falling back to direct synchronous review." >&2 + bash /tmp/milestone-review-${REVIEW_ID}.sh >/tmp/milestone-review-${REVIEW_ID}.runner.out 2>/tmp/milestone-review-${REVIEW_ID}.stderr +fi +``` + +Run the helper in the foreground and watch its live stdout for `state=in-progress` heartbeats. If your agent environment buffers command output until exit, start the helper in the background and poll `/tmp/milestone-review-${REVIEW_ID}.status` separately instead of treating heartbeats as post-hoc-only data. + +After the command completes: + +- If `REVIEWER_CLI=cursor`, extract the final review text: + +```bash +CURSOR_SESSION_ID=$(jq -r '.session_id' /tmp/milestone-review-${REVIEW_ID}.json) +jq -r '.result' /tmp/milestone-review-${REVIEW_ID}.json > /tmp/milestone-review-${REVIEW_ID}.md +``` + +- If `REVIEWER_CLI=codex`, extract `CODEX_SESSION_ID` from `/tmp/milestone-review-${REVIEW_ID}.runner.out` after the helper or fallback run. If the review text is only in `.runner.out`, move or copy the actual review body into `/tmp/milestone-review-${REVIEW_ID}.md` before verdict parsing. +- If `REVIEWER_CLI=opencode`, the `jq` extraction above covers output capture. If it falls through, copy runner output: `cp /tmp/milestone-review-${REVIEW_ID}.runner.out /tmp/milestone-review-${REVIEW_ID}.md`. On Round 1, also attempt to capture the session id for optional use in subsequent rounds: `OPENCODE_SESSION_ID=$(jq -r 'if type == "array" then (.[0] | (.id? // .session_id?)) else (.id? // .session_id?) end // empty' /tmp/milestone-review-${REVIEW_ID}.json 2>/dev/null || true)` +- If `REVIEWER_CLI=claude` or `REVIEWER_CLI=pi`, promote stdout captured by the helper or fallback runner into the markdown review file: + +```bash +cp /tmp/milestone-review-${REVIEW_ID}.runner.out /tmp/milestone-review-${REVIEW_ID}.md +``` + +Fallback is allowed only when the helper is missing or not executable. + +#### Step 4: Read Review & Check Verdict + +1. Read `/tmp/milestone-review-${REVIEW_ID}.md` +2. If the review failed, produced empty output, or reached helper timeout, also read: + - `/tmp/milestone-review-${REVIEW_ID}.stderr` + - `/tmp/milestone-review-${REVIEW_ID}.status` + - `/tmp/milestone-review-${REVIEW_ID}.runner.out` +3. Present review to the user: + +```markdown +## Milestone Review — Round N (reviewer: ${REVIEWER_CLI} / ${REVIEWER_MODEL}) + +[Reviewer feedback] +``` + +1. While the reviewer is still running, keep waiting as long as fresh `state=in-progress note="In progress N"` heartbeats continue to appear roughly once per minute. +2. Check verdict: + - **VERDICT: APPROVED** with no `P0`, `P1`, or `P2` findings -> proceed to Phase 5 Step 6 (commit & approve) + - **VERDICT: APPROVED** with only `P3` findings -> optionally fix the `P3` items if they are cheap and safe, then proceed + - **VERDICT: REVISE** or any `P0`, `P1`, or `P2` finding -> go to Step 5 + - No clear verdict but `P0`, `P1`, and `P2` are all `- None.` -> treat as approved + - Helper state `completed-empty-output` -> treat as failed review attempt, surface stderr/status, fix invocation or prompt handling, then retry + - Helper state `needs-operator-decision` -> surface status log and decide whether to extend the timeout, abort, or retry with different helper parameters + - Max rounds (`MAX_ROUNDS`) reached -> present to user for manual decision (proceed or stop) + +#### Step 5: Address Feedback & Re-verify + +1. Address the reviewer findings in priority order (`P0` -> `P1` -> `P2`, then `P3` when practical) (do NOT commit yet). +2. Re-run verification (lint/typecheck/tests) — all must pass. +3. Update `/tmp/milestone-${REVIEW_ID}.md` with new diff and verification output. + +Summarize revisions for the user: + +```markdown +### Revisions (Round N) +- [Change and reason, one bullet per issue addressed] +``` + +If a revision contradicts the user's explicit requirements, skip it and note it for the user. + +#### Step 6: Re-submit to Reviewer (Rounds 2-N) + +Rewrite `/tmp/milestone-review-${REVIEW_ID}.sh` for the next round. The script should contain the reviewer invocation only; do not run it directly. + +**If `REVIEWER_CLI` is `pi`:** + +Fresh call with prior-round context (Pi reviewer calls do not use session resume): + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files \ + --model "$REVIEWER_MODEL" \ + --tools read,grep,find,ls \ + -p "You previously reviewed this milestone and requested revisions. Read the updated payload at /tmp/milestone-${REVIEW_ID}.md and re-review using the same ## Summary, ## Findings, and ## Verdict structure." +``` + +**If `REVIEWER_CLI` is `codex`:** + +Resume the existing session: + +```bash +codex exec resume ${CODEX_SESSION_ID} \ + -o /tmp/milestone-review-${REVIEW_ID}.md \ + "I've addressed your feedback. Updated diff and verification output are in /tmp/milestone-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." +``` + +If resume fails (session expired), fall back to fresh `codex exec` with context about prior rounds. + +**If `REVIEWER_CLI` is `claude`:** + +Fresh call with accumulated context (Claude CLI has no session resume): + +```bash +claude -p \ + "You previously reviewed milestone M<N> and requested revisions. + +Previous feedback summary: [key points from last review] + +I've addressed your feedback. Updated diff and verification output are below. + +$(cat /tmp/milestone-${REVIEW_ID}.md) + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." \ + --model ${REVIEWER_MODEL} \ + --strict-mcp-config \ + --setting-sources user +``` + +**If `REVIEWER_CLI` is `cursor`:** + +Resume the existing session: + +```bash +cursor-agent --resume ${CURSOR_SESSION_ID} -p \ + --mode=ask \ + --model ${REVIEWER_MODEL} \ + --trust \ + --output-format json \ + "I've addressed your feedback. Updated diff and verification output are in /tmp/milestone-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same `## Summary`, `## Findings`, and `## Verdict` structure as before. +Keep findings ordered `P0` to `P3`, use `- None.` when a severity has no findings, and only use `VERDICT: APPROVED` when no `P0`, `P1`, or `P2` findings remain. `P3` findings are non-blocking." \ + > /tmp/milestone-review-${REVIEW_ID}.json +``` + +If resume fails, fall back to fresh `cursor-agent -p` with context about prior rounds. + +**If `REVIEWER_CLI` is `opencode`:** + +Fresh call (recommended default — opencode has no guaranteed stable session ID in headless mode): + +```bash +opencode run \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "You previously reviewed this milestone and requested revisions. + +Previous feedback summary: [key points from last review] + +I've addressed your feedback. Updated milestone payload is in /tmp/milestone-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same \`## Summary\`, \`## Findings\`, and \`## Verdict\` structure as before. +Keep findings ordered \`P0\` to \`P3\`, use \`- None.\` when a severity has no findings, and only use \`VERDICT: APPROVED\` when no \`P0\`, \`P1\`, or \`P2\` findings remain. \`P3\` findings are non-blocking." \ + > /tmp/milestone-review-${REVIEW_ID}.json + +jq -r '.[] | select(.type == "message" and .role == "assistant") | .content' \ + /tmp/milestone-review-${REVIEW_ID}.json \ + > /tmp/milestone-review-${REVIEW_ID}.md \ + || cp /tmp/milestone-review-${REVIEW_ID}.json /tmp/milestone-review-${REVIEW_ID}.md +``` + +Optional session-resume path (only if `OPENCODE_SESSION_ID` was captured on Round 1 and your installed opencode accepts `-s <id>` reliably in headless mode): + +```bash +opencode run \ + -s ${OPENCODE_SESSION_ID} \ + -m ${REVIEWER_MODEL} \ + --agent plan \ + --format json \ + "I've addressed your feedback on this milestone. Updated diff and verification output are in /tmp/milestone-${REVIEW_ID}.md. + +Changes made: +[List specific changes] + +Re-review using the same \`## Summary\`, \`## Findings\`, and \`## Verdict\` structure as before. +Keep findings ordered \`P0\` to \`P3\`, use \`- None.\` when a severity has no findings, and only use \`VERDICT: APPROVED\` when no \`P0\`, \`P1\`, or \`P2\` findings remain. \`P3\` findings are non-blocking." \ + > /tmp/milestone-review-${REVIEW_ID}.json + +jq -r '.[] | select(.type == "message" and .role == "assistant") | .content' \ + /tmp/milestone-review-${REVIEW_ID}.json \ + > /tmp/milestone-review-${REVIEW_ID}.md \ + || cp /tmp/milestone-review-${REVIEW_ID}.json /tmp/milestone-review-${REVIEW_ID}.md +``` + +If session resume fails (session expired or not supported), fall back to the fresh-call path above. + +Do not run `jq` extraction until after the helper or fallback execution completes, then extract `/tmp/milestone-review-${REVIEW_ID}.md` from the JSON response. + +After updating `/tmp/milestone-review-${REVIEW_ID}.sh`, run the same helper/fallback flow from Round 1. + +Return to Step 4. + +#### Step 7: Cleanup Per Milestone + +```bash +rm -f \ + /tmp/milestone-${REVIEW_ID}.md \ + /tmp/milestone-review-${REVIEW_ID}.md \ + /tmp/milestone-review-${REVIEW_ID}.json \ + /tmp/milestone-review-${REVIEW_ID}.stderr \ + /tmp/milestone-review-${REVIEW_ID}.status \ + /tmp/milestone-review-${REVIEW_ID}.runner.out \ + /tmp/milestone-review-${REVIEW_ID}.sh +``` + +If the round failed, produced empty output, or reached operator-decision timeout, keep `.stderr`, `.status`, and `.runner.out` until the issue is diagnosed instead of deleting them immediately. + +### Phase 7: Completion (REQUIRED SUB-SKILL) + +After all milestones are approved and committed: + +1. Use OpenCode's native skill tool to load: `superpowers/finishing-a-development-branch` +2. Run full test suite one final time — all must pass. +3. Merge the worktree branch into the parent branch: + +```bash +# From the main repo (not the worktree) +git merge implement/<plan-folder-name> +``` + +1. Delete the worktree and its branch: + +```bash +git worktree remove <worktree-path> +git branch -d implement/<plan-folder-name> +``` + +1. Mark plan status as `completed` in `story-tracker.md`. + +### Phase 8: Final Report + +Present summary: + +```markdown +## Implementation Complete + +**Plan:** <plan-folder-name> +**Milestones:** <N> completed, <N> approved +**Review rounds:** <total across all milestones> +**Branch:** implement/<plan-folder-name> (merged and deleted) +``` + +### Phase 9: Telegram Notification (MANDATORY) + +Resolve the Telegram notifier helper from the installed OpenCode skills directory: + +```bash +TELEGRAM_NOTIFY_RUNTIME=~/.config/opencode/skills/reviewer-runtime/notify-telegram.sh +``` + +On every terminal outcome for the implement-plan run (fully completed, stopped after max rounds, skipped reviewer, or failure), send a Telegram summary if the helper exists and both `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` are configured: + +```bash +if [ -x "$TELEGRAM_NOTIFY_RUNTIME" ] && [ -n "${TELEGRAM_BOT_TOKEN:-}" ] && [ -n "${TELEGRAM_CHAT_ID:-}" ]; then + "$TELEGRAM_NOTIFY_RUNTIME" --message "implement-plan completed for <plan-folder-name>: <status summary>" +fi +``` + +Rules: + +- Telegram is the only supported notification path. Do not use desktop notifications, `say`, email, or any other notifier. +- Notification failures are non-blocking, but they must be surfaced to the user. +- Before stopping for any user interaction, approval, or manual decision, send a Telegram summary first if configured. +- If Telegram is not configured, state that no Telegram notification was sent. + +## Tracker Discipline (MANDATORY) + +Before starting any story: + +1. Open `story-tracker.md` +2. Mark story `in-dev` +3. Add notes if relevant +4. Then begin implementation + +After completing any story: + +1. Mark story `completed` +2. Review pending stories +3. Update Last Updated and Stories Complete counts + +Note: Commit hashes are backfilled into story Notes after the milestone commit (Step 6), not per-story. + +## Verification Checklist + +- [ ] Plan folder located and all required files present +- [ ] Reviewer configured or explicitly skipped +- [ ] Max review rounds confirmed (default: 10) +- [ ] Worktree created with branch `implement/<plan-folder-name>` +- [ ] Worktree directory verified in .gitignore +- [ ] Baseline tests pass in worktree +- [ ] Each milestone: stories tracked (in-dev -> completed) +- [ ] Each milestone: lint/typecheck/tests pass before review +- [ ] Each milestone: reviewer approved (or max rounds + user override) +- [ ] Each milestone: committed locally only after approval +- [ ] Each milestone: marked approved in story-tracker.md +- [ ] All milestones completed, approved, and committed +- [ ] Final test suite passes +- [ ] Worktree branch merged to parent and worktree deleted +- [ ] Story tracker updated with final status +- [ ] Telegram notification attempted if configured diff --git a/skills/implement-plan/_source/pi/SKILL.md b/skills/implement-plan/_source/pi/SKILL.md new file mode 100644 index 0000000..eb4e77b --- /dev/null +++ b/skills/implement-plan/_source/pi/SKILL.md @@ -0,0 +1,241 @@ +--- +name: implement-plan +description: Use when a plan folder created by create-plan must be executed in pi with milestone verification, reviewer gates, local commits, and resumable tracker updates. +--- + +# Implement Plan (Pi) + +Execute an existing plan under `ai_plan/` milestone by milestone, using verification gates, reviewer approval, and local commits after each approved milestone. + +## Shared Setup + +Before using this skill, read: + +- [docs/PI-SUPERPOWERS.md](../../../docs/PI-SUPERPOWERS.md) +- [docs/PI-COMMON-REVIEWER.md](../../../docs/PI-COMMON-REVIEWER.md) + +This workflow depends on: + +- Superpowers execution skills being visible to pi +- the pi reviewer-runtime helper being installed in a supported location + +## Prerequisite Check (MANDATORY) + +Required: + +- `pi --version` +- a plan folder under `ai_plan/` +- `continuation-runbook.md` +- `milestone-plan.md` +- `story-tracker.md` +- git worktree support +- Superpowers `executing-plans` +- Superpowers `using-git-worktrees` +- Superpowers `verification-before-completion` +- Superpowers `finishing-a-development-branch` +- pi reviewer runtime helper +- pi Telegram notifier helper + +Quick checks for common installs: + +```bash +pi --version +git worktree list +test -f ~/.agents/skills/superpowers/executing-plans/SKILL.md || test -f ~/.pi/agent/skills/superpowers/executing-plans/SKILL.md +test -f ~/.agents/skills/superpowers/using-git-worktrees/SKILL.md || test -f ~/.pi/agent/skills/superpowers/using-git-worktrees/SKILL.md +test -f ~/.agents/skills/superpowers/verification-before-completion/SKILL.md || test -f ~/.pi/agent/skills/superpowers/verification-before-completion/SKILL.md +test -f ~/.agents/skills/superpowers/finishing-a-development-branch/SKILL.md || test -f ~/.pi/agent/skills/superpowers/finishing-a-development-branch/SKILL.md +test -x .pi/skills/reviewer-runtime/pi/run-review.sh || test -x ~/.pi/agent/skills/reviewer-runtime/pi/run-review.sh +test -x .pi/skills/reviewer-runtime/pi/notify-telegram.sh || test -x ~/.pi/agent/skills/reviewer-runtime/pi/notify-telegram.sh +``` + +If you use a settings-defined skill path for Superpowers, confirm it matches [docs/PI-SUPERPOWERS.md](../../../docs/PI-SUPERPOWERS.md) before continuing. + +If you install the reviewer helper in a nonstandard location, confirm it matches [docs/PI-COMMON-REVIEWER.md](../../../docs/PI-COMMON-REVIEWER.md) before continuing. + +If any dependency is missing, stop and return: + +`Missing dependency: pi implement-plan requires the execution skills and reviewer setup documented in docs/PI-SUPERPOWERS.md and docs/PI-COMMON-REVIEWER.md.` + +## Required Workflow Rules + +- Load the relevant workflow skill before entering its phase. If pi did not auto-load it, use `/skill:<name>`. +- Announce skill usage explicitly: + - `I've read the [Skill Name] skill and I'm using it to [purpose].` +- Update `story-tracker.md` before starting and after completing every story. +- Do not use deprecated wrapper CLIs. + +## Process + +### Phase 1: Locate Plan + +1. Scan `ai_plan/` and identify the target plan folder +2. Read `continuation-runbook.md` first +3. Read `story-tracker.md` to identify resume state +4. Read `milestone-plan.md` for the implementation spec + +### Phase 2: Configure Reviewer + +If the user already provided reviewer settings, use them. Otherwise ask: + +Reviewer CLI: `codex`, `claude`, `cursor`, `opencode`, `pi`, or `skip` + +1. Which CLI should review milestone implementations? +2. Reviewer model +3. Max rounds, default `10` + +Store `REVIEWER_CLI`, `REVIEWER_MODEL`, and `MAX_ROUNDS`. + +If `REVIEWER_CLI=pi`, verify the Pi reviewer binary before entering the review loop: + +```bash +pi --version +``` + +For shorthand `pi/<pi-model-name>`, split only on the first slash when the prefix is exactly `pi`; store the complete remainder in `REVIEWER_MODEL`. Examples: `pi/claude-opus-4-7` -> `claude-opus-4-7`, `pi/anthropic/claude-opus-4-7` -> `anthropic/claude-opus-4-7`, and `pi/openrouter/anthropic/claude-opus-4-7` -> `openrouter/anthropic/claude-opus-4-7`. + +When `REVIEWER_CLI=pi`, the reviewer model is configured independently from the pi model running this workflow. Use any configured pi model string, including provider-qualified model IDs. If the reviewer model or provider is unavailable, surface the review helper stderr/status and ask for a configured model; use `pi --list-models [search]` to inspect configured models. + +The pi reviewer command rendered into `/tmp/milestone-review-${REVIEW_ID}.sh` must be isolated and read-only: + +```bash +pi --no-session --no-skills --no-prompt-templates --no-extensions --no-context-files --model "$REVIEWER_MODEL" --tools read,grep,find,ls -p "Read the file /tmp/milestone-${REVIEW_ID}.md and review." +``` + +The pi reviewer invocation must not load workflow skills and must not include `write`, `edit`, or `bash` tools. + +### Phase 3: Set Up Workspace + +1. Load `using-git-worktrees` +2. Create or resume the implementation branch/worktree described by the plan +3. Verify baseline setup and tests before changing code + +### Phase 4: Execute Milestones + +For each milestone: + +1. Mark the next story `in-dev` in `story-tracker.md` +2. Implement the story +3. Mark the story `completed` +4. Continue until the milestone stories are done +5. Load `verification-before-completion` +6. Run lint, typecheck, and tests for the changed scope +7. Send the milestone diff and verification output to the reviewer before committing +8. Commit only after approval + +### Phase 5: Milestone Review Loop + +Skip this phase if `REVIEWER_CLI=skip`. + +#### Step 1: Generate Session ID + +```bash +REVIEW_ID=$(uuidgen | tr '[:upper:]' '[:lower:]' | head -c 8) +``` + +Use these temp artifacts: + +- `/tmp/milestone-${REVIEW_ID}.md` +- `/tmp/milestone-review-${REVIEW_ID}.md` +- `/tmp/milestone-review-${REVIEW_ID}.json` +- `/tmp/milestone-review-${REVIEW_ID}.stderr` +- `/tmp/milestone-review-${REVIEW_ID}.status` +- `/tmp/milestone-review-${REVIEW_ID}.runner.out` +- `/tmp/milestone-review-${REVIEW_ID}.sh` + +Resolve the pi reviewer runtime helper in this order: + +```bash +REVIEWER_RUNTIME="" +for candidate in ".pi/skills/reviewer-runtime/pi/run-review.sh" "$HOME/.pi/agent/skills/reviewer-runtime/pi/run-review.sh"; do + if [ -x "$candidate" ]; then + REVIEWER_RUNTIME="$candidate" + break + fi +done +``` + +#### Step 2: Build Review Payload + +Write the milestone spec, acceptance criteria, diff, and verification output to `/tmp/milestone-${REVIEW_ID}.md`. + +Reviewer responses must use this structure: + +```text +## Summary +... + +## Findings +### P0 +- ... +### P1 +- ... +### P2 +- ... +### P3 +- ... + +## Verdict +VERDICT: APPROVED +``` + +Rules: + +- Order findings from `P0` to `P3` +- Use `- None.` when a severity has no findings +- `VERDICT: APPROVED` is valid only when no `P0`, `P1`, or `P2` findings remain + +#### Step 3: Run Review + +Execute the reviewer command script through the helper when available: + +```bash +"$REVIEWER_RUNTIME" \ + --command-file /tmp/milestone-review-${REVIEW_ID}.sh \ + --stdout-file /tmp/milestone-review-${REVIEW_ID}.runner.out \ + --stderr-file /tmp/milestone-review-${REVIEW_ID}.stderr \ + --status-file /tmp/milestone-review-${REVIEW_ID}.status +``` + +Fallback to direct execution only if the helper is missing. + +#### Step 4: Handle Findings + +- Keep waiting while fresh `state=in-progress note="In progress N"` heartbeats continue +- Fix `P0`, `P1`, and `P2` findings before approval +- Fix cheap `P3` findings when safe +- Re-run verification after each revision + +### Phase 6: Commit And Track Approval + +After milestone approval: + +1. Commit the milestone locally +2. Backfill the commit hash into that milestone's story notes +3. Mark the milestone `approved` in `story-tracker.md` +4. Move to the next milestone + +### Phase 7: Finalization + +After all milestones are approved: + +1. Load `finishing-a-development-branch` +2. Run the full verification suite +3. Ask whether to push or keep the work local +4. Mark the plan completed in `story-tracker.md` + +### Phase 8: Telegram Completion Notification + +Resolve the helper in this order: + +```bash +TELEGRAM_NOTIFY_RUNTIME="" +for candidate in ".pi/skills/reviewer-runtime/pi/notify-telegram.sh" "$HOME/.pi/agent/skills/reviewer-runtime/pi/notify-telegram.sh"; do + if [ -x "$candidate" ]; then + TELEGRAM_NOTIFY_RUNTIME="$candidate" + break + fi +done +``` + +If the helper exists and both `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` are configured, send a short completion summary. Otherwise state that no Telegram completion notification was sent. diff --git a/skills/implement-plan/claude-code/.generated-manifest.json b/skills/implement-plan/claude-code/.generated-manifest.json new file mode 100644 index 0000000..2df30ff --- /dev/null +++ b/skills/implement-plan/claude-code/.generated-manifest.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/implement-plan/claude-code", + "files": [ + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "237d96bf6c8bf9ee4d014cd30607b383839a29d03635e7dd4f44b4804a14d11b" + } + ] +} diff --git a/skills/implement-plan/claude-code/SKILL.md b/skills/implement-plan/claude-code/SKILL.md index 164c5f4..1329776 100644 --- a/skills/implement-plan/claude-code/SKILL.md +++ b/skills/implement-plan/claude-code/SKILL.md @@ -3,6 +3,8 @@ name: implement-plan description: Use when a plan folder (from create-plan) exists and needs to be executed in an isolated git worktree with iterative cross-model milestone review. ALWAYS invoke when user says "implement the plan", "execute the plan", "start implementation", "resume the plan", or similar execution requests. --- +<!-- ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/implement-plan/_source/claude-code/ and run `pnpm run sync:pi`. --> + # Implement Plan (Claude Code) Execute an existing plan (created by `create-plan`) in an isolated git worktree, with iterative cross-model review at each milestone boundary. diff --git a/skills/implement-plan/codex/.generated-manifest.json b/skills/implement-plan/codex/.generated-manifest.json new file mode 100644 index 0000000..4222241 --- /dev/null +++ b/skills/implement-plan/codex/.generated-manifest.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/implement-plan/codex", + "files": [ + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "0e7998dd1161ca305773b572b7c82bceb43e2004dbcb9c817e38e5d974451766" + } + ] +} diff --git a/skills/implement-plan/codex/SKILL.md b/skills/implement-plan/codex/SKILL.md index be6e3f1..de17cf9 100644 --- a/skills/implement-plan/codex/SKILL.md +++ b/skills/implement-plan/codex/SKILL.md @@ -3,6 +3,8 @@ name: implement-plan description: Use when a plan folder (from create-plan) exists and needs to be executed in an isolated git worktree with iterative cross-model milestone review. ALWAYS invoke when user says "implement the plan", "execute the plan", "start implementation", "resume the plan", or similar execution requests. --- +<!-- ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/implement-plan/_source/codex/ and run `pnpm run sync:pi`. --> + # Implement Plan (Codex Native Superpowers) Execute an existing plan (created by `create-plan`) in an isolated git worktree, with iterative cross-model review at each milestone boundary. diff --git a/skills/implement-plan/cursor/.generated-manifest.json b/skills/implement-plan/cursor/.generated-manifest.json new file mode 100644 index 0000000..01606ae --- /dev/null +++ b/skills/implement-plan/cursor/.generated-manifest.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/implement-plan/cursor", + "files": [ + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "8d4e198ccc3ade97ed968b04e07282e9fae56150407bb06e9ee87963f3051cb4" + } + ] +} diff --git a/skills/implement-plan/cursor/SKILL.md b/skills/implement-plan/cursor/SKILL.md index b2e479b..270cc71 100644 --- a/skills/implement-plan/cursor/SKILL.md +++ b/skills/implement-plan/cursor/SKILL.md @@ -3,6 +3,8 @@ name: implement-plan description: Use when a plan folder (from create-plan) exists and needs to be executed in an isolated git worktree with iterative cross-model milestone review in Cursor Agent CLI workflows. ALWAYS invoke when user says "implement the plan", "execute the plan", "start implementation", "resume the plan", or similar execution requests. --- +<!-- ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/implement-plan/_source/cursor/ and run `pnpm run sync:pi`. --> + # Implement Plan (Cursor Agent CLI) Execute an existing plan (created by `create-plan`) in an isolated git worktree, with iterative cross-model review at each milestone boundary. diff --git a/skills/implement-plan/opencode/.generated-manifest.json b/skills/implement-plan/opencode/.generated-manifest.json new file mode 100644 index 0000000..64fef23 --- /dev/null +++ b/skills/implement-plan/opencode/.generated-manifest.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/implement-plan/opencode", + "files": [ + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "ec84e639b79117fd0dc6ceb30329e6e618e2c545271be51ec35335a0a0681758" + } + ] +} diff --git a/skills/implement-plan/opencode/SKILL.md b/skills/implement-plan/opencode/SKILL.md index d80cdcc..235583e 100644 --- a/skills/implement-plan/opencode/SKILL.md +++ b/skills/implement-plan/opencode/SKILL.md @@ -3,6 +3,8 @@ name: implement-plan description: Use when a plan folder (from create-plan) exists and needs to be executed in an isolated git worktree with iterative cross-model milestone review in OpenCode workflows. ALWAYS invoke when user says "implement the plan", "execute the plan", "start implementation", "resume the plan", or similar execution requests. --- +<!-- ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/implement-plan/_source/opencode/ and run `pnpm run sync:pi`. --> + # Implement Plan (OpenCode) Execute an existing plan (created by `create-plan`) in an isolated git worktree, with iterative cross-model review at each milestone boundary. diff --git a/skills/implement-plan/pi/.generated-manifest.json b/skills/implement-plan/pi/.generated-manifest.json new file mode 100644 index 0000000..31f014b --- /dev/null +++ b/skills/implement-plan/pi/.generated-manifest.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/implement-plan/pi", + "files": [ + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "578e5b4fbc443ade486e4aa034b0875ebfa7eefd3a1268852ac1c5cbf71f28bf" + } + ] +} diff --git a/skills/implement-plan/pi/SKILL.md b/skills/implement-plan/pi/SKILL.md index eb4e77b..f458dff 100644 --- a/skills/implement-plan/pi/SKILL.md +++ b/skills/implement-plan/pi/SKILL.md @@ -3,6 +3,8 @@ name: implement-plan description: Use when a plan folder created by create-plan must be executed in pi with milestone verification, reviewer gates, local commits, and resumable tracker updates. --- +<!-- ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/implement-plan/_source/pi/ and run `pnpm run sync:pi`. --> + # Implement Plan (Pi) Execute an existing plan under `ai_plan/` milestone by milestone, using verification gates, reviewer approval, and local commits after each approved milestone. diff --git a/skills/reviewer-runtime/pi/.generated-manifest.json b/skills/reviewer-runtime/pi/.generated-manifest.json new file mode 100644 index 0000000..765b757 --- /dev/null +++ b/skills/reviewer-runtime/pi/.generated-manifest.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/reviewer-runtime/pi", + "files": [ + { + "path": "notify-telegram.sh", + "kind": "file", + "mode": "755", + "sha256": "d7e52e3d2f7de83691470467ff3fec230bea10f128d5b188604e1bb5883d6d58" + }, + { + "path": "run-review.sh", + "kind": "file", + "mode": "755", + "sha256": "c5e4fd082ee1a14059183d2cd2c45653e16d63821edf9cf82e548c9307d29e75" + } + ] +} diff --git a/skills/reviewer-runtime/pi/notify-telegram.sh b/skills/reviewer-runtime/pi/notify-telegram.sh index d62931c..91377ec 100755 --- a/skills/reviewer-runtime/pi/notify-telegram.sh +++ b/skills/reviewer-runtime/pi/notify-telegram.sh @@ -1,8 +1,7 @@ #!/usr/bin/env bash +# ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/reviewer-runtime/notify-telegram.sh and run `pnpm run sync:pi`. set -euo pipefail -# Keep this file in sync with skills/reviewer-runtime/notify-telegram.sh. - DEFAULT_API_BASE_URL="https://api.telegram.org" DEFAULT_PARSE_MODE="HTML" MAX_MESSAGE_LENGTH=4096 diff --git a/skills/reviewer-runtime/pi/run-review.sh b/skills/reviewer-runtime/pi/run-review.sh index 4476a92..853b653 100755 --- a/skills/reviewer-runtime/pi/run-review.sh +++ b/skills/reviewer-runtime/pi/run-review.sh @@ -1,8 +1,7 @@ #!/usr/bin/env bash +# ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/reviewer-runtime/run-review.sh and run `pnpm run sync:pi`. set -euo pipefail -# Keep this file in sync with skills/reviewer-runtime/run-review.sh. - DEFAULT_POLL_SECONDS=10 DEFAULT_HEARTBEAT_SECONDS=60 DEFAULT_SOFT_TIMEOUT_SECONDS=600 @@ -146,6 +145,7 @@ kill_child_process_group() { fi } +# shellcheck disable=SC2329 handle_signal() { local signal_name=$1 INTERRUPTED=1 @@ -265,7 +265,6 @@ main() { local last_output_change_time=$START_TIME local last_heartbeat_time=$START_TIME local soft_timeout_logged=0 - local stall_warning_logged=0 local heartbeat_count=0 while kill -0 "$CHILD_PID" 2>/dev/null; do @@ -285,7 +284,6 @@ main() { if [[ "$stdout_bytes" -ne "$last_stdout_bytes" || "$stderr_bytes" -ne "$last_stderr_bytes" ]]; then last_output_change_time=$now - stall_warning_logged=0 state=running-active level=info note="reviewer output changed" @@ -296,7 +294,6 @@ main() { state=stall-warning level=warn note="no output growth for ${silent_for}s; process still alive" - stall_warning_logged=1 else state=running-silent level=info @@ -328,10 +325,9 @@ main() { set -e trap - EXIT - local final_stdout_bytes final_stderr_bytes + local final_stdout_bytes local success_file success_bytes final_stdout_bytes=$(file_bytes "$STDOUT_FILE") - final_stderr_bytes=$(file_bytes "$STDERR_FILE") if [[ "$child_exit_code" -eq 0 ]]; then if [[ "$final_stdout_bytes" -gt 0 ]]; then diff --git a/skills/reviewer-runtime/run-review.sh b/skills/reviewer-runtime/run-review.sh index bc6e3a1..40f102b 100755 --- a/skills/reviewer-runtime/run-review.sh +++ b/skills/reviewer-runtime/run-review.sh @@ -144,6 +144,7 @@ kill_child_process_group() { fi } +# shellcheck disable=SC2329 handle_signal() { local signal_name=$1 INTERRUPTED=1 @@ -263,7 +264,6 @@ main() { local last_output_change_time=$START_TIME local last_heartbeat_time=$START_TIME local soft_timeout_logged=0 - local stall_warning_logged=0 local heartbeat_count=0 while kill -0 "$CHILD_PID" 2>/dev/null; do @@ -283,7 +283,6 @@ main() { if [[ "$stdout_bytes" -ne "$last_stdout_bytes" || "$stderr_bytes" -ne "$last_stderr_bytes" ]]; then last_output_change_time=$now - stall_warning_logged=0 state=running-active level=info note="reviewer output changed" @@ -294,7 +293,6 @@ main() { state=stall-warning level=warn note="no output growth for ${silent_for}s; process still alive" - stall_warning_logged=1 else state=running-silent level=info @@ -326,10 +324,9 @@ main() { set -e trap - EXIT - local final_stdout_bytes final_stderr_bytes + local final_stdout_bytes local success_file success_bytes final_stdout_bytes=$(file_bytes "$STDOUT_FILE") - final_stderr_bytes=$(file_bytes "$STDERR_FILE") if [[ "$child_exit_code" -eq 0 ]]; then if [[ "$final_stdout_bytes" -gt 0 ]]; then diff --git a/skills/reviewer-runtime/tests/claude-review-template-guard.sh b/skills/reviewer-runtime/tests/claude-review-template-guard.sh index 45a42de..e4a7dd2 100755 --- a/skills/reviewer-runtime/tests/claude-review-template-guard.sh +++ b/skills/reviewer-runtime/tests/claude-review-template-guard.sh @@ -31,6 +31,7 @@ check_skill_file() { local file=$1 assert_contains "$file" "claude -p \\" + # shellcheck disable=SC2016 assert_contains "$file" '$(cat /tmp/' assert_contains "$file" "--strict-mcp-config" assert_contains "$file" "--setting-sources user" diff --git a/skills/reviewer-runtime/tests/smoke-test.sh b/skills/reviewer-runtime/tests/smoke-test.sh index 5597396..993a678 100755 --- a/skills/reviewer-runtime/tests/smoke-test.sh +++ b/skills/reviewer-runtime/tests/smoke-test.sh @@ -328,7 +328,9 @@ main() { local tmp_dir tmp_dir=$(mktemp -d) - trap "rm -rf '$tmp_dir'" EXIT + # Use SMOKE_TMP as a module-level var so the single-quoted trap expands lazily + SMOKE_TMP="$tmp_dir" + trap 'rm -rf "$SMOKE_TMP"' EXIT test_delayed_success "$tmp_dir" test_soft_timeout_continues "$tmp_dir" diff --git a/skills/reviewer-runtime/tests/telegram-notify-test.sh b/skills/reviewer-runtime/tests/telegram-notify-test.sh index d48341d..1b0f51d 100755 --- a/skills/reviewer-runtime/tests/telegram-notify-test.sh +++ b/skills/reviewer-runtime/tests/telegram-notify-test.sh @@ -144,7 +144,9 @@ main() { local tmp_dir tmp_dir=$(mktemp -d) - trap "rm -rf '$tmp_dir'" EXIT + # Use TG_TEST_TMP as a module-level var so the single-quoted trap expands lazily + TG_TEST_TMP="$tmp_dir" + trap 'rm -rf "$TG_TEST_TMP"' EXIT test_missing_credentials "$tmp_dir" test_rejects_message_and_message_file_together "$tmp_dir" diff --git a/skills/web-automation/_source/claude-code/SKILL.md b/skills/web-automation/_source/claude-code/SKILL.md new file mode 100644 index 0000000..86b6764 --- /dev/null +++ b/skills/web-automation/_source/claude-code/SKILL.md @@ -0,0 +1,99 @@ +--- +name: web-automation +description: Browse and scrape web pages using Playwright-compatible CloakBrowser. Use when automating web workflows, extracting rendered page content, handling authenticated sessions, or running multi-step browser flows. +--- + +# Web Automation with CloakBrowser (Claude Code) + +Automated web browsing and scraping using Playwright-compatible CloakBrowser with two execution paths: + +- one-shot extraction via `extract.js` +- broader stateful automation via `auth.ts`, `browse.ts`, `flow.ts`, `scan-local-app.ts`, and `scrape.ts` + +## Requirements + +- Node.js 20+ +- pnpm +- Network access to download the CloakBrowser binary on first use + +## First-Time Setup + +```bash +cd ~/.claude/skills/web-automation/scripts +pnpm install +npx cloakbrowser install +pnpm approve-builds +pnpm rebuild better-sqlite3 esbuild +``` + +## Updating CloakBrowser + +```bash +cd ~/.claude/skills/web-automation/scripts +pnpm up cloakbrowser playwright-core +npx cloakbrowser install +pnpm approve-builds +pnpm rebuild better-sqlite3 esbuild +``` + +## Prerequisite Check (MANDATORY) + +Before running automation, verify CloakBrowser and Playwright Core are installed and wired correctly. + +```bash +cd ~/.claude/skills/web-automation/scripts +node check-install.js +``` + +If the check fails, stop and return: + +"Missing dependency/config: web-automation requires `cloakbrowser` and `playwright-core` with CloakBrowser-based scripts. Run setup in this skill, then retry." + +If runtime fails with missing native bindings for `better-sqlite3` or `esbuild`, run: + +```bash +cd ~/.claude/skills/web-automation/scripts +pnpm approve-builds +pnpm rebuild better-sqlite3 esbuild +``` + +## When To Use Which Command + +- Use `node extract.js "<URL>"` for a one-shot rendered fetch with JSON output. +- Use `npx tsx scrape.ts ...` when you need markdown extraction, Readability cleanup, or selector-based scraping. +- Use `npx tsx browse.ts ...`, `auth.ts`, or `flow.ts` when the task needs login handling, persistent sessions, clicks, typing, screenshots, or multi-step navigation. +- Use `npx tsx scan-local-app.ts` when you need a configurable local-app smoke pass driven by `SCAN_*` and `CLOAKBROWSER_*` environment variables. + +## Quick Reference + +- Install check: `node check-install.js` +- One-shot JSON extract: `node extract.js "https://example.com"` +- Browse page: `npx tsx browse.ts --url "https://example.com"` +- Scrape markdown: `npx tsx scrape.ts --url "https://example.com" --mode main --output page.md` +- Authenticate: `npx tsx auth.ts --url "https://example.com/login"` +- Natural-language flow: `npx tsx flow.ts --instruction 'go to https://example.com then click on "Login" then type "user@example.com" in #email then press enter'` +- Local app smoke scan: `SCAN_BASE_URL=http://localhost:3000 SCAN_ROUTES=/,/dashboard npx tsx scan-local-app.ts` + +## Local App Smoke Scan + +`scan-local-app.ts` is intentionally generic. Configure it with environment variables instead of editing the file: + +- `SCAN_BASE_URL` +- `SCAN_LOGIN_PATH` +- `SCAN_USERNAME` +- `SCAN_PASSWORD` +- `SCAN_USERNAME_SELECTOR` +- `SCAN_PASSWORD_SELECTOR` +- `SCAN_SUBMIT_SELECTOR` +- `SCAN_ROUTES` +- `SCAN_REPORT_PATH` +- `SCAN_HEADLESS` + +If `SCAN_USERNAME` or `SCAN_PASSWORD` are omitted, the script falls back to `CLOAKBROWSER_USERNAME` and `CLOAKBROWSER_PASSWORD`. + +## Notes + +- Sessions persist in CloakBrowser profile storage. +- Use `--wait` for dynamic pages. +- Use `--mode selector --selector "..."` for targeted extraction. +- `extract.js` keeps a bounded stealth/rendered fetch path without needing a long-lived automation session. diff --git a/skills/web-automation/_source/codex/SKILL.md b/skills/web-automation/_source/codex/SKILL.md new file mode 100644 index 0000000..86876af --- /dev/null +++ b/skills/web-automation/_source/codex/SKILL.md @@ -0,0 +1,99 @@ +--- +name: web-automation +description: Browse and scrape web pages using Playwright-compatible CloakBrowser. Use when automating web workflows, extracting rendered page content, handling authenticated sessions, or running multi-step browser flows. +--- + +# Web Automation with CloakBrowser (Codex) + +Automated web browsing and scraping using Playwright-compatible CloakBrowser with two execution paths: + +- one-shot extraction via `extract.js` +- broader stateful automation via `auth.ts`, `browse.ts`, `flow.ts`, `scan-local-app.ts`, and `scrape.ts` + +## Requirements + +- Node.js 20+ +- pnpm +- Network access to download the CloakBrowser binary on first use + +## First-Time Setup + +```bash +cd ~/.codex/skills/web-automation/scripts +pnpm install +npx cloakbrowser install +pnpm approve-builds +pnpm rebuild better-sqlite3 esbuild +``` + +## Updating CloakBrowser + +```bash +cd ~/.codex/skills/web-automation/scripts +pnpm up cloakbrowser playwright-core +npx cloakbrowser install +pnpm approve-builds +pnpm rebuild better-sqlite3 esbuild +``` + +## Prerequisite Check (MANDATORY) + +Before running automation, verify CloakBrowser and Playwright Core are installed and wired correctly. + +```bash +cd ~/.codex/skills/web-automation/scripts +node check-install.js +``` + +If the check fails, stop and return: + +"Missing dependency/config: web-automation requires `cloakbrowser` and `playwright-core` with CloakBrowser-based scripts. Run setup in this skill, then retry." + +If runtime fails with missing native bindings for `better-sqlite3` or `esbuild`, run: + +```bash +cd ~/.codex/skills/web-automation/scripts +pnpm approve-builds +pnpm rebuild better-sqlite3 esbuild +``` + +## When To Use Which Command + +- Use `node extract.js "<URL>"` for a one-shot rendered fetch with JSON output. +- Use `npx tsx scrape.ts ...` when you need markdown extraction, Readability cleanup, or selector-based scraping. +- Use `npx tsx browse.ts ...`, `auth.ts`, or `flow.ts` when the task needs login handling, persistent sessions, clicks, typing, screenshots, or multi-step navigation. +- Use `npx tsx scan-local-app.ts` when you need a configurable local-app smoke pass driven by `SCAN_*` and `CLOAKBROWSER_*` environment variables. + +## Quick Reference + +- Install check: `node check-install.js` +- One-shot JSON extract: `node extract.js "https://example.com"` +- Browse page: `npx tsx browse.ts --url "https://example.com"` +- Scrape markdown: `npx tsx scrape.ts --url "https://example.com" --mode main --output page.md` +- Authenticate: `npx tsx auth.ts --url "https://example.com/login"` +- Natural-language flow: `npx tsx flow.ts --instruction 'go to https://example.com then click on "Login" then type "user@example.com" in #email then press enter'` +- Local app smoke scan: `SCAN_BASE_URL=http://localhost:3000 SCAN_ROUTES=/,/dashboard npx tsx scan-local-app.ts` + +## Local App Smoke Scan + +`scan-local-app.ts` is intentionally generic. Configure it with environment variables instead of editing the file: + +- `SCAN_BASE_URL` +- `SCAN_LOGIN_PATH` +- `SCAN_USERNAME` +- `SCAN_PASSWORD` +- `SCAN_USERNAME_SELECTOR` +- `SCAN_PASSWORD_SELECTOR` +- `SCAN_SUBMIT_SELECTOR` +- `SCAN_ROUTES` +- `SCAN_REPORT_PATH` +- `SCAN_HEADLESS` + +If `SCAN_USERNAME` or `SCAN_PASSWORD` are omitted, the script falls back to `CLOAKBROWSER_USERNAME` and `CLOAKBROWSER_PASSWORD`. + +## Notes + +- Sessions persist in CloakBrowser profile storage. +- Use `--wait` for dynamic pages. +- Use `--mode selector --selector "..."` for targeted extraction. +- `extract.js` keeps a bounded stealth/rendered fetch path without needing a long-lived automation session. diff --git a/skills/web-automation/_source/cursor/SKILL.md b/skills/web-automation/_source/cursor/SKILL.md new file mode 100644 index 0000000..adeef60 --- /dev/null +++ b/skills/web-automation/_source/cursor/SKILL.md @@ -0,0 +1,112 @@ +--- +name: web-automation +description: Browse and scrape web pages using Playwright-compatible CloakBrowser. Use when automating web workflows, extracting rendered page content, handling authenticated sessions, or running multi-step browser flows. +--- + +# Web Automation with CloakBrowser (Cursor) + +Automated web browsing and scraping using Playwright-compatible CloakBrowser with two execution paths: + +- one-shot extraction via `extract.js` +- broader stateful automation via `auth.ts`, `browse.ts`, `flow.ts`, `scan-local-app.ts`, and `scrape.ts` + +## Requirements + +- Node.js 20+ +- pnpm +- Network access to download the CloakBrowser binary on first use + +## First-Time Setup + +Repo-local install: + +```bash +cd .cursor/skills/web-automation/scripts +pnpm install +npx cloakbrowser install +pnpm approve-builds +pnpm rebuild better-sqlite3 esbuild +``` + +Global install: + +```bash +cd ~/.cursor/skills/web-automation/scripts +pnpm install +npx cloakbrowser install +pnpm approve-builds +pnpm rebuild better-sqlite3 esbuild +``` + +## Updating CloakBrowser + +Run from the installed `scripts/` directory: + +```bash +pnpm up cloakbrowser playwright-core +npx cloakbrowser install +pnpm approve-builds +pnpm rebuild better-sqlite3 esbuild +``` + +## Prerequisite Check (MANDATORY) + +Before running automation, verify CloakBrowser and Playwright Core are installed and wired correctly. + +```bash +cd .cursor/skills/web-automation/scripts || cd ~/.cursor/skills/web-automation/scripts +node check-install.js +``` + +If the check fails, stop and return: + +"Missing dependency/config: web-automation requires `cloakbrowser` and `playwright-core` with CloakBrowser-based scripts. Run setup in this skill, then retry." + +If runtime fails with missing native bindings for `better-sqlite3` or `esbuild`, run: + +```bash +cd .cursor/skills/web-automation/scripts || cd ~/.cursor/skills/web-automation/scripts +pnpm approve-builds +pnpm rebuild better-sqlite3 esbuild +``` + +## When To Use Which Command + +- Use `node extract.js "<URL>"` for a one-shot rendered fetch with JSON output. +- Use `npx tsx scrape.ts ...` when you need markdown extraction, Readability cleanup, or selector-based scraping. +- Use `npx tsx browse.ts ...`, `auth.ts`, or `flow.ts` when the task needs login handling, persistent sessions, clicks, typing, screenshots, or multi-step navigation. +- Use `npx tsx scan-local-app.ts` when you need a configurable local-app smoke pass driven by `SCAN_*` and `CLOAKBROWSER_*` environment variables. + +## Quick Reference + +- Install check: `node check-install.js` +- One-shot JSON extract: `node extract.js "https://example.com"` +- Browse page: `npx tsx browse.ts --url "https://example.com"` +- Scrape markdown: `npx tsx scrape.ts --url "https://example.com" --mode main --output page.md` +- Authenticate: `npx tsx auth.ts --url "https://example.com/login"` +- Natural-language flow: `npx tsx flow.ts --instruction 'go to https://example.com then click on "Login" then type "user@example.com" in #email then press enter'` +- Local app smoke scan: `SCAN_BASE_URL=http://localhost:3000 SCAN_ROUTES=/,/dashboard npx tsx scan-local-app.ts` + +## Local App Smoke Scan + +`scan-local-app.ts` is intentionally generic. Configure it with environment variables instead of editing the file: + +- `SCAN_BASE_URL` +- `SCAN_LOGIN_PATH` +- `SCAN_USERNAME` +- `SCAN_PASSWORD` +- `SCAN_USERNAME_SELECTOR` +- `SCAN_PASSWORD_SELECTOR` +- `SCAN_SUBMIT_SELECTOR` +- `SCAN_ROUTES` +- `SCAN_REPORT_PATH` +- `SCAN_HEADLESS` + +If `SCAN_USERNAME` or `SCAN_PASSWORD` are omitted, the script falls back to `CLOAKBROWSER_USERNAME` and `CLOAKBROWSER_PASSWORD`. + +## Notes + +- Sessions persist in CloakBrowser profile storage. +- Use `--wait` for dynamic pages. +- Use `--mode selector --selector "..."` for targeted extraction. +- `extract.js` keeps a bounded stealth/rendered fetch path without needing a long-lived automation session. diff --git a/skills/web-automation/_source/opencode/SKILL.md b/skills/web-automation/_source/opencode/SKILL.md new file mode 100644 index 0000000..01847ad --- /dev/null +++ b/skills/web-automation/_source/opencode/SKILL.md @@ -0,0 +1,99 @@ +--- +name: web-automation +description: Browse and scrape web pages using Playwright-compatible CloakBrowser. Use when automating web workflows, extracting rendered page content, handling authenticated sessions, or running multi-step browser flows. +--- + +# Web Automation with CloakBrowser (OpenCode) + +Automated web browsing and scraping using Playwright-compatible CloakBrowser with two execution paths: + +- one-shot extraction via `extract.js` +- broader stateful automation via `auth.ts`, `browse.ts`, `flow.ts`, `scan-local-app.ts`, and `scrape.ts` + +## Requirements + +- Node.js 20+ +- pnpm +- Network access to download the CloakBrowser binary on first use + +## First-Time Setup + +```bash +cd ~/.config/opencode/skills/web-automation/scripts +pnpm install +npx cloakbrowser install +pnpm approve-builds +pnpm rebuild better-sqlite3 esbuild +``` + +## Updating CloakBrowser + +```bash +cd ~/.config/opencode/skills/web-automation/scripts +pnpm up cloakbrowser playwright-core +npx cloakbrowser install +pnpm approve-builds +pnpm rebuild better-sqlite3 esbuild +``` + +## Prerequisite Check (MANDATORY) + +Before running automation, verify CloakBrowser and Playwright Core are installed and wired correctly. + +```bash +cd ~/.config/opencode/skills/web-automation/scripts +node check-install.js +``` + +If the check fails, stop and return: + +"Missing dependency/config: web-automation requires `cloakbrowser` and `playwright-core` with CloakBrowser-based scripts. Run setup in this skill, then retry." + +If runtime fails with missing native bindings for `better-sqlite3` or `esbuild`, run: + +```bash +cd ~/.config/opencode/skills/web-automation/scripts +pnpm approve-builds +pnpm rebuild better-sqlite3 esbuild +``` + +## When To Use Which Command + +- Use `node extract.js "<URL>"` for a one-shot rendered fetch with JSON output. +- Use `npx tsx scrape.ts ...` when you need markdown extraction, Readability cleanup, or selector-based scraping. +- Use `npx tsx browse.ts ...`, `auth.ts`, or `flow.ts` when the task needs login handling, persistent sessions, clicks, typing, screenshots, or multi-step navigation. +- Use `npx tsx scan-local-app.ts` when you need a configurable local-app smoke pass driven by `SCAN_*` and `CLOAKBROWSER_*` environment variables. + +## Quick Reference + +- Install check: `node check-install.js` +- One-shot JSON extract: `node extract.js "https://example.com"` +- Browse page: `npx tsx browse.ts --url "https://example.com"` +- Scrape markdown: `npx tsx scrape.ts --url "https://example.com" --mode main --output page.md` +- Authenticate: `npx tsx auth.ts --url "https://example.com/login"` +- Natural-language flow: `npx tsx flow.ts --instruction 'go to https://example.com then click on "Login" then type "user@example.com" in #email then press enter'` +- Local app smoke scan: `SCAN_BASE_URL=http://localhost:3000 SCAN_ROUTES=/,/dashboard npx tsx scan-local-app.ts` + +## Local App Smoke Scan + +`scan-local-app.ts` is intentionally generic. Configure it with environment variables instead of editing the file: + +- `SCAN_BASE_URL` +- `SCAN_LOGIN_PATH` +- `SCAN_USERNAME` +- `SCAN_PASSWORD` +- `SCAN_USERNAME_SELECTOR` +- `SCAN_PASSWORD_SELECTOR` +- `SCAN_SUBMIT_SELECTOR` +- `SCAN_ROUTES` +- `SCAN_REPORT_PATH` +- `SCAN_HEADLESS` + +If `SCAN_USERNAME` or `SCAN_PASSWORD` are omitted, the script falls back to `CLOAKBROWSER_USERNAME` and `CLOAKBROWSER_PASSWORD`. + +## Notes + +- Sessions persist in CloakBrowser profile storage. +- Use `--wait` for dynamic pages. +- Use `--mode selector --selector "..."` for targeted extraction. +- `extract.js` keeps a bounded stealth/rendered fetch path without needing a long-lived automation session. diff --git a/skills/web-automation/_source/pi/SKILL.md b/skills/web-automation/_source/pi/SKILL.md new file mode 100644 index 0000000..5a021e7 --- /dev/null +++ b/skills/web-automation/_source/pi/SKILL.md @@ -0,0 +1,122 @@ +--- +name: web-automation +description: Browse and scrape web pages using Playwright-compatible CloakBrowser. Use when automating web workflows, extracting rendered page content, handling authenticated sessions, or running multi-step browser flows. +--- + +# Web Automation with CloakBrowser (Pi) + +Automated web browsing and scraping for pi using the shared runtime bundle in `scripts/`. + +## Requirements + +- Node.js 20+ +- `pnpm` +- Network access to download the CloakBrowser binary on first use + +## First-Time Setup + +Global install: + +```bash +mkdir -p ~/.pi/agent/skills/web-automation +cp -R skills/web-automation/pi/* ~/.pi/agent/skills/web-automation/ +cd ~/.pi/agent/skills/web-automation/scripts +pnpm install +npx cloakbrowser install +pnpm approve-builds +pnpm rebuild better-sqlite3 esbuild +``` + +Project-local install: + +```bash +mkdir -p .pi/skills/web-automation +cp -R skills/web-automation/pi/* .pi/skills/web-automation/ +cd .pi/skills/web-automation/scripts +pnpm install +npx cloakbrowser install +pnpm approve-builds +pnpm rebuild better-sqlite3 esbuild +``` + +Pi can also load this repo through settings or package installs as documented in [docs/PI.md](../../../docs/PI.md). + +If you installed this repo from a local checkout with `./scripts/install-pi-package.sh`, the runtime stays in the checkout mirror at `pi-package/skills/web-automation/scripts`. + +## Updating CloakBrowser + +Run inside the installed `scripts/` directory for the pi skill. The commands below work for both global and project-local installs as long as you run them from the installed `scripts/` directory. + +```bash +pnpm up cloakbrowser playwright-core +npx cloakbrowser install +pnpm approve-builds +pnpm rebuild better-sqlite3 esbuild +``` + +## Prerequisite Check (MANDATORY) + +Before running automation, verify the runtime from the location that matches your install style: + +- local checkout package install: `pi-package/skills/web-automation/scripts` +- project-local copied install: `.pi/skills/web-automation/scripts` +- global copied install: `~/.pi/agent/skills/web-automation/scripts` + +```bash +cd pi-package/skills/web-automation/scripts +node check-install.js +``` + +If the check fails, stop and return: + +`Missing dependency/config: web-automation requires cloakbrowser and playwright-core with CloakBrowser-based scripts. Run setup in this skill, then retry.` + +If runtime fails with missing native bindings for `better-sqlite3` or `esbuild`, run the same commands from your installed `scripts/` directory: + +```bash +cd pi-package/skills/web-automation/scripts +pnpm approve-builds +pnpm rebuild better-sqlite3 esbuild +``` + +## When To Use Which Command + +- Use `node extract.js "<URL>"` for a one-shot rendered fetch with JSON output. +- Use `npx tsx scrape.ts ...` when you need markdown extraction, Readability cleanup, or selector-based scraping. +- Use `npx tsx browse.ts ...`, `auth.ts`, or `flow.ts` when the task needs login handling, persistent sessions, clicks, typing, screenshots, or multi-step navigation. +- Use `npx tsx scan-local-app.ts` when you need a configurable local-app smoke pass driven by `SCAN_*` and `CLOAKBROWSER_*` environment variables. + +## Quick Reference + +- Install check: `node check-install.js` +- One-shot JSON extract: `node extract.js "https://example.com"` +- Browse page: `npx tsx browse.ts --url "https://example.com"` +- Scrape markdown: `npx tsx scrape.ts --url "https://example.com" --mode main --output page.md` +- Authenticate: `npx tsx auth.ts --url "https://example.com/login"` +- Natural-language flow: `npx tsx flow.ts --instruction 'go to https://example.com then click on "Login" then type "user@example.com" in #email then press enter'` +- Local app smoke scan: `SCAN_BASE_URL=http://localhost:3000 SCAN_ROUTES=/,/dashboard npx tsx scan-local-app.ts` + +## Local App Smoke Scan + +`scan-local-app.ts` is intentionally generic. Configure it with environment variables instead of editing the file: + +- `SCAN_BASE_URL` +- `SCAN_LOGIN_PATH` +- `SCAN_USERNAME` +- `SCAN_PASSWORD` +- `SCAN_USERNAME_SELECTOR` +- `SCAN_PASSWORD_SELECTOR` +- `SCAN_SUBMIT_SELECTOR` +- `SCAN_ROUTES` +- `SCAN_REPORT_PATH` +- `SCAN_HEADLESS` + +If `SCAN_USERNAME` or `SCAN_PASSWORD` are omitted, the script falls back to `CLOAKBROWSER_USERNAME` and `CLOAKBROWSER_PASSWORD`. + +## Notes + +- Sessions persist in CloakBrowser profile storage. +- Use `--wait` for dynamic pages. +- Use `--mode selector --selector "..."` for targeted extraction. +- `extract.js` keeps a bounded stealth/rendered fetch path without needing a long-lived automation session. +- Package installs use the repo's `pi-package/skills/web-automation/` mirror so the installed skill directory name matches `web-automation`. diff --git a/skills/web-automation/claude-code/.generated-manifest.json b/skills/web-automation/claude-code/.generated-manifest.json new file mode 100644 index 0000000..964cfd2 --- /dev/null +++ b/skills/web-automation/claude-code/.generated-manifest.json @@ -0,0 +1,97 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/web-automation/claude-code", + "files": [ + { + "path": "scripts/auth.ts", + "kind": "file", + "mode": "644", + "sha256": "ce0a8aae0bc41b86e11aab51cc0e0cfa484a1934807f147c05c9bd38d416c066" + }, + { + "path": "scripts/browse.ts", + "kind": "file", + "mode": "644", + "sha256": "42da9cdc6806b8d7d8d814952ad9540033b6c6a4cbe9844ada328b2ceace67c9" + }, + { + "path": "scripts/check-install.js", + "kind": "file", + "mode": "644", + "sha256": "e46ee8cbe103794bf1e9c3466bb0fbd21079ceddc60ad9521299e8bc0150e48f" + }, + { + "path": "scripts/extract.js", + "kind": "file", + "mode": "644", + "sha256": "6fa2a0589de8afd6501e332e5fa263e1344187ea43a33590b431cdee59d04217" + }, + { + "path": "scripts/flow.ts", + "kind": "file", + "mode": "644", + "sha256": "b1c256bf6a206473512a4c0555c891893a48025529da282fa6cd07e68ad3d051" + }, + { + "path": "scripts/package.json", + "kind": "file", + "mode": "644", + "sha256": "8fd46cc1ab9f2b45b8f2b658479276a11a3b79710a530aac7148bc396e0edf35" + }, + { + "path": "scripts/pnpm-lock.yaml", + "kind": "file", + "mode": "644", + "sha256": "17017e15e8b04311f5d53bdd37065b2f5a514a3119f40a0403148440ed181437" + }, + { + "path": "scripts/scan-local-app.ts", + "kind": "file", + "mode": "644", + "sha256": "3f42f9bb2d355fefc8645d2b2acfa3107bd87f9c2579b2631c94132bed0abea4" + }, + { + "path": "scripts/scrape.ts", + "kind": "file", + "mode": "644", + "sha256": "a1a3d81d57d9e8ab1854ce3cb230bdd39ae1087ec50c9fe82cc58f5f2663ebeb" + }, + { + "path": "scripts/test-full.ts", + "kind": "file", + "mode": "644", + "sha256": "76a647e840753621445c36894bff62e163f6a2e4d0860fa8e64d8df45fe21e08" + }, + { + "path": "scripts/test-minimal.ts", + "kind": "file", + "mode": "644", + "sha256": "59e0b2319d3f7521b2a8a4fca2d779afaa157bf2d160160fdec8cb56bea30b4f" + }, + { + "path": "scripts/test-profile.ts", + "kind": "file", + "mode": "644", + "sha256": "6cf0141581a9275bfa8a070a36212cef5f6417d64df3df3e614ec682008376b9" + }, + { + "path": "scripts/tsconfig.json", + "kind": "file", + "mode": "644", + "sha256": "5f9a83c8caab167eb20defbb5afde58f2bb573a300af99654997dcb3372408e0" + }, + { + "path": "scripts/turndown-plugin-gfm.d.ts", + "kind": "file", + "mode": "644", + "sha256": "c5001c059b160eff18a4097a8a0a7b96689b4ebc374543c7d5bf6e40b0d8a5ac" + }, + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "18fccff0bceba538cac6491a5ff3e7090549aa13885d0ab5c6a73d4e8ca72f01" + } + ] +} diff --git a/skills/web-automation/claude-code/SKILL.md b/skills/web-automation/claude-code/SKILL.md index 86b6764..abe8476 100644 --- a/skills/web-automation/claude-code/SKILL.md +++ b/skills/web-automation/claude-code/SKILL.md @@ -3,6 +3,8 @@ name: web-automation description: Browse and scrape web pages using Playwright-compatible CloakBrowser. Use when automating web workflows, extracting rendered page content, handling authenticated sessions, or running multi-step browser flows. --- +<!-- ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/_source/claude-code/SKILL.md and run `pnpm run sync:pi`. --> + # Web Automation with CloakBrowser (Claude Code) Automated web browsing and scraping using Playwright-compatible CloakBrowser with two execution paths: diff --git a/skills/web-automation/claude-code/scripts/auth.ts b/skills/web-automation/claude-code/scripts/auth.ts index e79f23d..272ee6d 100644 --- a/skills/web-automation/claude-code/scripts/auth.ts +++ b/skills/web-automation/claude-code/scripts/auth.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. /** * Authentication handler for web automation diff --git a/skills/web-automation/claude-code/scripts/browse.ts b/skills/web-automation/claude-code/scripts/browse.ts index 01cf098..3d27c9e 100644 --- a/skills/web-automation/claude-code/scripts/browse.ts +++ b/skills/web-automation/claude-code/scripts/browse.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. /** * Browser launcher using CloakBrowser with persistent profile diff --git a/skills/web-automation/claude-code/scripts/check-install.js b/skills/web-automation/claude-code/scripts/check-install.js index 50e4884..cb7197d 100644 --- a/skills/web-automation/claude-code/scripts/check-install.js +++ b/skills/web-automation/claude-code/scripts/check-install.js @@ -1,4 +1,5 @@ #!/usr/bin/env node +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import fs from "node:fs"; import path from "node:path"; diff --git a/skills/web-automation/claude-code/scripts/extract.js b/skills/web-automation/claude-code/scripts/extract.js old mode 100755 new mode 100644 index 5e3908a..f7b5995 --- a/skills/web-automation/claude-code/scripts/extract.js +++ b/skills/web-automation/claude-code/scripts/extract.js @@ -1,4 +1,5 @@ #!/usr/bin/env node +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import fs from "node:fs"; import path from "node:path"; diff --git a/skills/web-automation/claude-code/scripts/flow.ts b/skills/web-automation/claude-code/scripts/flow.ts index 5d01e55..dfe2d8c 100644 --- a/skills/web-automation/claude-code/scripts/flow.ts +++ b/skills/web-automation/claude-code/scripts/flow.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import parseArgs from 'minimist'; import type { Page } from 'playwright-core'; diff --git a/skills/web-automation/claude-code/scripts/package.json b/skills/web-automation/claude-code/scripts/package.json index a2221e8..ce127dc 100644 --- a/skills/web-automation/claude-code/scripts/package.json +++ b/skills/web-automation/claude-code/scripts/package.json @@ -1,5 +1,5 @@ { - "name": "web-automation-scripts", + "name": "@ai-coding-skills/web-automation-claude-code", "version": "1.0.0", "description": "Web browsing and scraping scripts using CloakBrowser", "type": "module", @@ -32,5 +32,6 @@ "tsx": "^4.7.0", "typescript": "^5.3.0" }, - "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34" + "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34", + "private": true } diff --git a/skills/web-automation/claude-code/scripts/scan-local-app.ts b/skills/web-automation/claude-code/scripts/scan-local-app.ts index 6a05b35..00e213e 100644 --- a/skills/web-automation/claude-code/scripts/scan-local-app.ts +++ b/skills/web-automation/claude-code/scripts/scan-local-app.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { mkdirSync, writeFileSync } from 'fs'; import { dirname, resolve } from 'path'; diff --git a/skills/web-automation/claude-code/scripts/scrape.ts b/skills/web-automation/claude-code/scripts/scrape.ts index 0820de0..2a4b75d 100644 --- a/skills/web-automation/claude-code/scripts/scrape.ts +++ b/skills/web-automation/claude-code/scripts/scrape.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. /** * Web scraper that extracts content to markdown diff --git a/skills/web-automation/claude-code/scripts/test-full.ts b/skills/web-automation/claude-code/scripts/test-full.ts index 356bbab..16037ee 100644 --- a/skills/web-automation/claude-code/scripts/test-full.ts +++ b/skills/web-automation/claude-code/scripts/test-full.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { launchPersistentContext } from 'cloakbrowser'; import { homedir } from 'os'; import { join } from 'path'; diff --git a/skills/web-automation/claude-code/scripts/test-minimal.ts b/skills/web-automation/claude-code/scripts/test-minimal.ts index a5412e7..7cef2ce 100644 --- a/skills/web-automation/claude-code/scripts/test-minimal.ts +++ b/skills/web-automation/claude-code/scripts/test-minimal.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { launch } from 'cloakbrowser'; async function test() { diff --git a/skills/web-automation/claude-code/scripts/test-profile.ts b/skills/web-automation/claude-code/scripts/test-profile.ts index ec59ddd..604ec55 100644 --- a/skills/web-automation/claude-code/scripts/test-profile.ts +++ b/skills/web-automation/claude-code/scripts/test-profile.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { launchPersistentContext } from 'cloakbrowser'; import { homedir } from 'os'; import { join } from 'path'; diff --git a/skills/web-automation/claude-code/scripts/turndown-plugin-gfm.d.ts b/skills/web-automation/claude-code/scripts/turndown-plugin-gfm.d.ts index 316bed1..113765e 100644 --- a/skills/web-automation/claude-code/scripts/turndown-plugin-gfm.d.ts +++ b/skills/web-automation/claude-code/scripts/turndown-plugin-gfm.d.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. declare module 'turndown-plugin-gfm' { import TurndownService from 'turndown'; diff --git a/skills/web-automation/codex/.generated-manifest.json b/skills/web-automation/codex/.generated-manifest.json new file mode 100644 index 0000000..71b1029 --- /dev/null +++ b/skills/web-automation/codex/.generated-manifest.json @@ -0,0 +1,97 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/web-automation/codex", + "files": [ + { + "path": "scripts/auth.ts", + "kind": "file", + "mode": "644", + "sha256": "ce0a8aae0bc41b86e11aab51cc0e0cfa484a1934807f147c05c9bd38d416c066" + }, + { + "path": "scripts/browse.ts", + "kind": "file", + "mode": "644", + "sha256": "42da9cdc6806b8d7d8d814952ad9540033b6c6a4cbe9844ada328b2ceace67c9" + }, + { + "path": "scripts/check-install.js", + "kind": "file", + "mode": "644", + "sha256": "e46ee8cbe103794bf1e9c3466bb0fbd21079ceddc60ad9521299e8bc0150e48f" + }, + { + "path": "scripts/extract.js", + "kind": "file", + "mode": "644", + "sha256": "6fa2a0589de8afd6501e332e5fa263e1344187ea43a33590b431cdee59d04217" + }, + { + "path": "scripts/flow.ts", + "kind": "file", + "mode": "644", + "sha256": "b1c256bf6a206473512a4c0555c891893a48025529da282fa6cd07e68ad3d051" + }, + { + "path": "scripts/package.json", + "kind": "file", + "mode": "644", + "sha256": "70316e0d66acdc7554467ba26d8dda2585ea2e92b964fb100ff21d576b8e3b06" + }, + { + "path": "scripts/pnpm-lock.yaml", + "kind": "file", + "mode": "644", + "sha256": "17017e15e8b04311f5d53bdd37065b2f5a514a3119f40a0403148440ed181437" + }, + { + "path": "scripts/scan-local-app.ts", + "kind": "file", + "mode": "644", + "sha256": "3f42f9bb2d355fefc8645d2b2acfa3107bd87f9c2579b2631c94132bed0abea4" + }, + { + "path": "scripts/scrape.ts", + "kind": "file", + "mode": "644", + "sha256": "a1a3d81d57d9e8ab1854ce3cb230bdd39ae1087ec50c9fe82cc58f5f2663ebeb" + }, + { + "path": "scripts/test-full.ts", + "kind": "file", + "mode": "644", + "sha256": "76a647e840753621445c36894bff62e163f6a2e4d0860fa8e64d8df45fe21e08" + }, + { + "path": "scripts/test-minimal.ts", + "kind": "file", + "mode": "644", + "sha256": "59e0b2319d3f7521b2a8a4fca2d779afaa157bf2d160160fdec8cb56bea30b4f" + }, + { + "path": "scripts/test-profile.ts", + "kind": "file", + "mode": "644", + "sha256": "6cf0141581a9275bfa8a070a36212cef5f6417d64df3df3e614ec682008376b9" + }, + { + "path": "scripts/tsconfig.json", + "kind": "file", + "mode": "644", + "sha256": "5f9a83c8caab167eb20defbb5afde58f2bb573a300af99654997dcb3372408e0" + }, + { + "path": "scripts/turndown-plugin-gfm.d.ts", + "kind": "file", + "mode": "644", + "sha256": "c5001c059b160eff18a4097a8a0a7b96689b4ebc374543c7d5bf6e40b0d8a5ac" + }, + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "c5e92fbcc9c3b9dba9c571beedea9cf6d60e6eb7cf968ba461552e12a6481761" + } + ] +} diff --git a/skills/web-automation/codex/SKILL.md b/skills/web-automation/codex/SKILL.md index 86876af..638b416 100644 --- a/skills/web-automation/codex/SKILL.md +++ b/skills/web-automation/codex/SKILL.md @@ -3,6 +3,8 @@ name: web-automation description: Browse and scrape web pages using Playwright-compatible CloakBrowser. Use when automating web workflows, extracting rendered page content, handling authenticated sessions, or running multi-step browser flows. --- +<!-- ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/_source/codex/SKILL.md and run `pnpm run sync:pi`. --> + # Web Automation with CloakBrowser (Codex) Automated web browsing and scraping using Playwright-compatible CloakBrowser with two execution paths: diff --git a/skills/web-automation/codex/scripts/auth.ts b/skills/web-automation/codex/scripts/auth.ts index e79f23d..272ee6d 100644 --- a/skills/web-automation/codex/scripts/auth.ts +++ b/skills/web-automation/codex/scripts/auth.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. /** * Authentication handler for web automation diff --git a/skills/web-automation/codex/scripts/browse.ts b/skills/web-automation/codex/scripts/browse.ts index 01cf098..3d27c9e 100644 --- a/skills/web-automation/codex/scripts/browse.ts +++ b/skills/web-automation/codex/scripts/browse.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. /** * Browser launcher using CloakBrowser with persistent profile diff --git a/skills/web-automation/codex/scripts/check-install.js b/skills/web-automation/codex/scripts/check-install.js index 50e4884..cb7197d 100644 --- a/skills/web-automation/codex/scripts/check-install.js +++ b/skills/web-automation/codex/scripts/check-install.js @@ -1,4 +1,5 @@ #!/usr/bin/env node +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import fs from "node:fs"; import path from "node:path"; diff --git a/skills/web-automation/codex/scripts/extract.js b/skills/web-automation/codex/scripts/extract.js old mode 100755 new mode 100644 index 5e3908a..f7b5995 --- a/skills/web-automation/codex/scripts/extract.js +++ b/skills/web-automation/codex/scripts/extract.js @@ -1,4 +1,5 @@ #!/usr/bin/env node +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import fs from "node:fs"; import path from "node:path"; diff --git a/skills/web-automation/codex/scripts/flow.ts b/skills/web-automation/codex/scripts/flow.ts index 5d01e55..dfe2d8c 100644 --- a/skills/web-automation/codex/scripts/flow.ts +++ b/skills/web-automation/codex/scripts/flow.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import parseArgs from 'minimist'; import type { Page } from 'playwright-core'; diff --git a/skills/web-automation/codex/scripts/package.json b/skills/web-automation/codex/scripts/package.json index a2221e8..7a3e66a 100644 --- a/skills/web-automation/codex/scripts/package.json +++ b/skills/web-automation/codex/scripts/package.json @@ -1,5 +1,5 @@ { - "name": "web-automation-scripts", + "name": "@ai-coding-skills/web-automation-codex", "version": "1.0.0", "description": "Web browsing and scraping scripts using CloakBrowser", "type": "module", @@ -32,5 +32,6 @@ "tsx": "^4.7.0", "typescript": "^5.3.0" }, - "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34" + "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34", + "private": true } diff --git a/skills/web-automation/codex/scripts/scan-local-app.ts b/skills/web-automation/codex/scripts/scan-local-app.ts index 6a05b35..00e213e 100644 --- a/skills/web-automation/codex/scripts/scan-local-app.ts +++ b/skills/web-automation/codex/scripts/scan-local-app.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { mkdirSync, writeFileSync } from 'fs'; import { dirname, resolve } from 'path'; diff --git a/skills/web-automation/codex/scripts/scrape.ts b/skills/web-automation/codex/scripts/scrape.ts index 0820de0..2a4b75d 100644 --- a/skills/web-automation/codex/scripts/scrape.ts +++ b/skills/web-automation/codex/scripts/scrape.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. /** * Web scraper that extracts content to markdown diff --git a/skills/web-automation/codex/scripts/test-full.ts b/skills/web-automation/codex/scripts/test-full.ts index 356bbab..16037ee 100644 --- a/skills/web-automation/codex/scripts/test-full.ts +++ b/skills/web-automation/codex/scripts/test-full.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { launchPersistentContext } from 'cloakbrowser'; import { homedir } from 'os'; import { join } from 'path'; diff --git a/skills/web-automation/codex/scripts/test-minimal.ts b/skills/web-automation/codex/scripts/test-minimal.ts index a5412e7..7cef2ce 100644 --- a/skills/web-automation/codex/scripts/test-minimal.ts +++ b/skills/web-automation/codex/scripts/test-minimal.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { launch } from 'cloakbrowser'; async function test() { diff --git a/skills/web-automation/codex/scripts/test-profile.ts b/skills/web-automation/codex/scripts/test-profile.ts index ec59ddd..604ec55 100644 --- a/skills/web-automation/codex/scripts/test-profile.ts +++ b/skills/web-automation/codex/scripts/test-profile.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { launchPersistentContext } from 'cloakbrowser'; import { homedir } from 'os'; import { join } from 'path'; diff --git a/skills/web-automation/codex/scripts/turndown-plugin-gfm.d.ts b/skills/web-automation/codex/scripts/turndown-plugin-gfm.d.ts index 316bed1..113765e 100644 --- a/skills/web-automation/codex/scripts/turndown-plugin-gfm.d.ts +++ b/skills/web-automation/codex/scripts/turndown-plugin-gfm.d.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. declare module 'turndown-plugin-gfm' { import TurndownService from 'turndown'; diff --git a/skills/web-automation/cursor/.generated-manifest.json b/skills/web-automation/cursor/.generated-manifest.json new file mode 100644 index 0000000..97d560a --- /dev/null +++ b/skills/web-automation/cursor/.generated-manifest.json @@ -0,0 +1,97 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/web-automation/cursor", + "files": [ + { + "path": "scripts/auth.ts", + "kind": "file", + "mode": "644", + "sha256": "ce0a8aae0bc41b86e11aab51cc0e0cfa484a1934807f147c05c9bd38d416c066" + }, + { + "path": "scripts/browse.ts", + "kind": "file", + "mode": "644", + "sha256": "42da9cdc6806b8d7d8d814952ad9540033b6c6a4cbe9844ada328b2ceace67c9" + }, + { + "path": "scripts/check-install.js", + "kind": "file", + "mode": "644", + "sha256": "e46ee8cbe103794bf1e9c3466bb0fbd21079ceddc60ad9521299e8bc0150e48f" + }, + { + "path": "scripts/extract.js", + "kind": "file", + "mode": "644", + "sha256": "6fa2a0589de8afd6501e332e5fa263e1344187ea43a33590b431cdee59d04217" + }, + { + "path": "scripts/flow.ts", + "kind": "file", + "mode": "644", + "sha256": "b1c256bf6a206473512a4c0555c891893a48025529da282fa6cd07e68ad3d051" + }, + { + "path": "scripts/package.json", + "kind": "file", + "mode": "644", + "sha256": "53b20f0df2f15fab39b58375ada434a82d135fd52fb6d0138c16941b8675b631" + }, + { + "path": "scripts/pnpm-lock.yaml", + "kind": "file", + "mode": "644", + "sha256": "17017e15e8b04311f5d53bdd37065b2f5a514a3119f40a0403148440ed181437" + }, + { + "path": "scripts/scan-local-app.ts", + "kind": "file", + "mode": "644", + "sha256": "3f42f9bb2d355fefc8645d2b2acfa3107bd87f9c2579b2631c94132bed0abea4" + }, + { + "path": "scripts/scrape.ts", + "kind": "file", + "mode": "644", + "sha256": "a1a3d81d57d9e8ab1854ce3cb230bdd39ae1087ec50c9fe82cc58f5f2663ebeb" + }, + { + "path": "scripts/test-full.ts", + "kind": "file", + "mode": "644", + "sha256": "76a647e840753621445c36894bff62e163f6a2e4d0860fa8e64d8df45fe21e08" + }, + { + "path": "scripts/test-minimal.ts", + "kind": "file", + "mode": "644", + "sha256": "59e0b2319d3f7521b2a8a4fca2d779afaa157bf2d160160fdec8cb56bea30b4f" + }, + { + "path": "scripts/test-profile.ts", + "kind": "file", + "mode": "644", + "sha256": "6cf0141581a9275bfa8a070a36212cef5f6417d64df3df3e614ec682008376b9" + }, + { + "path": "scripts/tsconfig.json", + "kind": "file", + "mode": "644", + "sha256": "5f9a83c8caab167eb20defbb5afde58f2bb573a300af99654997dcb3372408e0" + }, + { + "path": "scripts/turndown-plugin-gfm.d.ts", + "kind": "file", + "mode": "644", + "sha256": "c5001c059b160eff18a4097a8a0a7b96689b4ebc374543c7d5bf6e40b0d8a5ac" + }, + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "3cba6cf3fee80db292440bfc22f0e97c6e3ffd7084000f4291e56ec1d7eb7bab" + } + ] +} diff --git a/skills/web-automation/cursor/SKILL.md b/skills/web-automation/cursor/SKILL.md index adeef60..af2e86b 100644 --- a/skills/web-automation/cursor/SKILL.md +++ b/skills/web-automation/cursor/SKILL.md @@ -3,6 +3,8 @@ name: web-automation description: Browse and scrape web pages using Playwright-compatible CloakBrowser. Use when automating web workflows, extracting rendered page content, handling authenticated sessions, or running multi-step browser flows. --- +<!-- ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/_source/cursor/SKILL.md and run `pnpm run sync:pi`. --> + # Web Automation with CloakBrowser (Cursor) Automated web browsing and scraping using Playwright-compatible CloakBrowser with two execution paths: diff --git a/skills/web-automation/cursor/scripts/auth.ts b/skills/web-automation/cursor/scripts/auth.ts index e79f23d..272ee6d 100644 --- a/skills/web-automation/cursor/scripts/auth.ts +++ b/skills/web-automation/cursor/scripts/auth.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. /** * Authentication handler for web automation diff --git a/skills/web-automation/cursor/scripts/browse.ts b/skills/web-automation/cursor/scripts/browse.ts index 01cf098..3d27c9e 100644 --- a/skills/web-automation/cursor/scripts/browse.ts +++ b/skills/web-automation/cursor/scripts/browse.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. /** * Browser launcher using CloakBrowser with persistent profile diff --git a/skills/web-automation/cursor/scripts/check-install.js b/skills/web-automation/cursor/scripts/check-install.js index 50e4884..cb7197d 100644 --- a/skills/web-automation/cursor/scripts/check-install.js +++ b/skills/web-automation/cursor/scripts/check-install.js @@ -1,4 +1,5 @@ #!/usr/bin/env node +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import fs from "node:fs"; import path from "node:path"; diff --git a/skills/web-automation/cursor/scripts/extract.js b/skills/web-automation/cursor/scripts/extract.js old mode 100755 new mode 100644 index 5e3908a..f7b5995 --- a/skills/web-automation/cursor/scripts/extract.js +++ b/skills/web-automation/cursor/scripts/extract.js @@ -1,4 +1,5 @@ #!/usr/bin/env node +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import fs from "node:fs"; import path from "node:path"; diff --git a/skills/web-automation/cursor/scripts/flow.ts b/skills/web-automation/cursor/scripts/flow.ts index 5d01e55..dfe2d8c 100644 --- a/skills/web-automation/cursor/scripts/flow.ts +++ b/skills/web-automation/cursor/scripts/flow.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import parseArgs from 'minimist'; import type { Page } from 'playwright-core'; diff --git a/skills/web-automation/cursor/scripts/package.json b/skills/web-automation/cursor/scripts/package.json index a2221e8..1cec30e 100644 --- a/skills/web-automation/cursor/scripts/package.json +++ b/skills/web-automation/cursor/scripts/package.json @@ -1,5 +1,5 @@ { - "name": "web-automation-scripts", + "name": "@ai-coding-skills/web-automation-cursor", "version": "1.0.0", "description": "Web browsing and scraping scripts using CloakBrowser", "type": "module", @@ -32,5 +32,6 @@ "tsx": "^4.7.0", "typescript": "^5.3.0" }, - "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34" + "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34", + "private": true } diff --git a/skills/web-automation/cursor/scripts/scan-local-app.ts b/skills/web-automation/cursor/scripts/scan-local-app.ts index 6a05b35..00e213e 100644 --- a/skills/web-automation/cursor/scripts/scan-local-app.ts +++ b/skills/web-automation/cursor/scripts/scan-local-app.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { mkdirSync, writeFileSync } from 'fs'; import { dirname, resolve } from 'path'; diff --git a/skills/web-automation/cursor/scripts/scrape.ts b/skills/web-automation/cursor/scripts/scrape.ts index 0820de0..2a4b75d 100644 --- a/skills/web-automation/cursor/scripts/scrape.ts +++ b/skills/web-automation/cursor/scripts/scrape.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. /** * Web scraper that extracts content to markdown diff --git a/skills/web-automation/cursor/scripts/test-full.ts b/skills/web-automation/cursor/scripts/test-full.ts index 356bbab..16037ee 100644 --- a/skills/web-automation/cursor/scripts/test-full.ts +++ b/skills/web-automation/cursor/scripts/test-full.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { launchPersistentContext } from 'cloakbrowser'; import { homedir } from 'os'; import { join } from 'path'; diff --git a/skills/web-automation/cursor/scripts/test-minimal.ts b/skills/web-automation/cursor/scripts/test-minimal.ts index a5412e7..7cef2ce 100644 --- a/skills/web-automation/cursor/scripts/test-minimal.ts +++ b/skills/web-automation/cursor/scripts/test-minimal.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { launch } from 'cloakbrowser'; async function test() { diff --git a/skills/web-automation/cursor/scripts/test-profile.ts b/skills/web-automation/cursor/scripts/test-profile.ts index ec59ddd..604ec55 100644 --- a/skills/web-automation/cursor/scripts/test-profile.ts +++ b/skills/web-automation/cursor/scripts/test-profile.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { launchPersistentContext } from 'cloakbrowser'; import { homedir } from 'os'; import { join } from 'path'; diff --git a/skills/web-automation/cursor/scripts/turndown-plugin-gfm.d.ts b/skills/web-automation/cursor/scripts/turndown-plugin-gfm.d.ts index 316bed1..113765e 100644 --- a/skills/web-automation/cursor/scripts/turndown-plugin-gfm.d.ts +++ b/skills/web-automation/cursor/scripts/turndown-plugin-gfm.d.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. declare module 'turndown-plugin-gfm' { import TurndownService from 'turndown'; diff --git a/skills/web-automation/opencode/.generated-manifest.json b/skills/web-automation/opencode/.generated-manifest.json new file mode 100644 index 0000000..765ba14 --- /dev/null +++ b/skills/web-automation/opencode/.generated-manifest.json @@ -0,0 +1,97 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/web-automation/opencode", + "files": [ + { + "path": "scripts/auth.ts", + "kind": "file", + "mode": "644", + "sha256": "ce0a8aae0bc41b86e11aab51cc0e0cfa484a1934807f147c05c9bd38d416c066" + }, + { + "path": "scripts/browse.ts", + "kind": "file", + "mode": "644", + "sha256": "42da9cdc6806b8d7d8d814952ad9540033b6c6a4cbe9844ada328b2ceace67c9" + }, + { + "path": "scripts/check-install.js", + "kind": "file", + "mode": "644", + "sha256": "e46ee8cbe103794bf1e9c3466bb0fbd21079ceddc60ad9521299e8bc0150e48f" + }, + { + "path": "scripts/extract.js", + "kind": "file", + "mode": "644", + "sha256": "6fa2a0589de8afd6501e332e5fa263e1344187ea43a33590b431cdee59d04217" + }, + { + "path": "scripts/flow.ts", + "kind": "file", + "mode": "644", + "sha256": "b1c256bf6a206473512a4c0555c891893a48025529da282fa6cd07e68ad3d051" + }, + { + "path": "scripts/package.json", + "kind": "file", + "mode": "644", + "sha256": "5f976f168e43948fda29d6749f7fc29991a2aace3d830c54c0184199cec222c4" + }, + { + "path": "scripts/pnpm-lock.yaml", + "kind": "file", + "mode": "644", + "sha256": "17017e15e8b04311f5d53bdd37065b2f5a514a3119f40a0403148440ed181437" + }, + { + "path": "scripts/scan-local-app.ts", + "kind": "file", + "mode": "644", + "sha256": "3f42f9bb2d355fefc8645d2b2acfa3107bd87f9c2579b2631c94132bed0abea4" + }, + { + "path": "scripts/scrape.ts", + "kind": "file", + "mode": "644", + "sha256": "a1a3d81d57d9e8ab1854ce3cb230bdd39ae1087ec50c9fe82cc58f5f2663ebeb" + }, + { + "path": "scripts/test-full.ts", + "kind": "file", + "mode": "644", + "sha256": "76a647e840753621445c36894bff62e163f6a2e4d0860fa8e64d8df45fe21e08" + }, + { + "path": "scripts/test-minimal.ts", + "kind": "file", + "mode": "644", + "sha256": "59e0b2319d3f7521b2a8a4fca2d779afaa157bf2d160160fdec8cb56bea30b4f" + }, + { + "path": "scripts/test-profile.ts", + "kind": "file", + "mode": "644", + "sha256": "6cf0141581a9275bfa8a070a36212cef5f6417d64df3df3e614ec682008376b9" + }, + { + "path": "scripts/tsconfig.json", + "kind": "file", + "mode": "644", + "sha256": "5f9a83c8caab167eb20defbb5afde58f2bb573a300af99654997dcb3372408e0" + }, + { + "path": "scripts/turndown-plugin-gfm.d.ts", + "kind": "file", + "mode": "644", + "sha256": "c5001c059b160eff18a4097a8a0a7b96689b4ebc374543c7d5bf6e40b0d8a5ac" + }, + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "43b6fd2cc553ca982ef34f4800594ab1371af1de2f16bd0fc7bc8e2bc85bdff1" + } + ] +} diff --git a/skills/web-automation/opencode/SKILL.md b/skills/web-automation/opencode/SKILL.md index 01847ad..1d7c082 100644 --- a/skills/web-automation/opencode/SKILL.md +++ b/skills/web-automation/opencode/SKILL.md @@ -3,6 +3,8 @@ name: web-automation description: Browse and scrape web pages using Playwright-compatible CloakBrowser. Use when automating web workflows, extracting rendered page content, handling authenticated sessions, or running multi-step browser flows. --- +<!-- ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/_source/opencode/SKILL.md and run `pnpm run sync:pi`. --> + # Web Automation with CloakBrowser (OpenCode) Automated web browsing and scraping using Playwright-compatible CloakBrowser with two execution paths: diff --git a/skills/web-automation/opencode/scripts/auth.ts b/skills/web-automation/opencode/scripts/auth.ts index e79f23d..272ee6d 100644 --- a/skills/web-automation/opencode/scripts/auth.ts +++ b/skills/web-automation/opencode/scripts/auth.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. /** * Authentication handler for web automation diff --git a/skills/web-automation/opencode/scripts/browse.ts b/skills/web-automation/opencode/scripts/browse.ts index 01cf098..3d27c9e 100644 --- a/skills/web-automation/opencode/scripts/browse.ts +++ b/skills/web-automation/opencode/scripts/browse.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. /** * Browser launcher using CloakBrowser with persistent profile diff --git a/skills/web-automation/opencode/scripts/check-install.js b/skills/web-automation/opencode/scripts/check-install.js index 50e4884..cb7197d 100644 --- a/skills/web-automation/opencode/scripts/check-install.js +++ b/skills/web-automation/opencode/scripts/check-install.js @@ -1,4 +1,5 @@ #!/usr/bin/env node +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import fs from "node:fs"; import path from "node:path"; diff --git a/skills/web-automation/opencode/scripts/extract.js b/skills/web-automation/opencode/scripts/extract.js old mode 100755 new mode 100644 index 5e3908a..f7b5995 --- a/skills/web-automation/opencode/scripts/extract.js +++ b/skills/web-automation/opencode/scripts/extract.js @@ -1,4 +1,5 @@ #!/usr/bin/env node +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import fs from "node:fs"; import path from "node:path"; diff --git a/skills/web-automation/opencode/scripts/flow.ts b/skills/web-automation/opencode/scripts/flow.ts index 5d01e55..dfe2d8c 100644 --- a/skills/web-automation/opencode/scripts/flow.ts +++ b/skills/web-automation/opencode/scripts/flow.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import parseArgs from 'minimist'; import type { Page } from 'playwright-core'; diff --git a/skills/web-automation/opencode/scripts/package.json b/skills/web-automation/opencode/scripts/package.json index a2221e8..6e8a066 100644 --- a/skills/web-automation/opencode/scripts/package.json +++ b/skills/web-automation/opencode/scripts/package.json @@ -1,5 +1,5 @@ { - "name": "web-automation-scripts", + "name": "@ai-coding-skills/web-automation-opencode", "version": "1.0.0", "description": "Web browsing and scraping scripts using CloakBrowser", "type": "module", @@ -32,5 +32,6 @@ "tsx": "^4.7.0", "typescript": "^5.3.0" }, - "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34" + "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34", + "private": true } diff --git a/skills/web-automation/opencode/scripts/scan-local-app.ts b/skills/web-automation/opencode/scripts/scan-local-app.ts index 6a05b35..00e213e 100644 --- a/skills/web-automation/opencode/scripts/scan-local-app.ts +++ b/skills/web-automation/opencode/scripts/scan-local-app.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { mkdirSync, writeFileSync } from 'fs'; import { dirname, resolve } from 'path'; diff --git a/skills/web-automation/opencode/scripts/scrape.ts b/skills/web-automation/opencode/scripts/scrape.ts index 0820de0..2a4b75d 100644 --- a/skills/web-automation/opencode/scripts/scrape.ts +++ b/skills/web-automation/opencode/scripts/scrape.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. /** * Web scraper that extracts content to markdown diff --git a/skills/web-automation/opencode/scripts/test-full.ts b/skills/web-automation/opencode/scripts/test-full.ts index 356bbab..16037ee 100644 --- a/skills/web-automation/opencode/scripts/test-full.ts +++ b/skills/web-automation/opencode/scripts/test-full.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { launchPersistentContext } from 'cloakbrowser'; import { homedir } from 'os'; import { join } from 'path'; diff --git a/skills/web-automation/opencode/scripts/test-minimal.ts b/skills/web-automation/opencode/scripts/test-minimal.ts index a5412e7..7cef2ce 100644 --- a/skills/web-automation/opencode/scripts/test-minimal.ts +++ b/skills/web-automation/opencode/scripts/test-minimal.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { launch } from 'cloakbrowser'; async function test() { diff --git a/skills/web-automation/opencode/scripts/test-profile.ts b/skills/web-automation/opencode/scripts/test-profile.ts index ec59ddd..604ec55 100644 --- a/skills/web-automation/opencode/scripts/test-profile.ts +++ b/skills/web-automation/opencode/scripts/test-profile.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { launchPersistentContext } from 'cloakbrowser'; import { homedir } from 'os'; import { join } from 'path'; diff --git a/skills/web-automation/opencode/scripts/turndown-plugin-gfm.d.ts b/skills/web-automation/opencode/scripts/turndown-plugin-gfm.d.ts index 316bed1..113765e 100644 --- a/skills/web-automation/opencode/scripts/turndown-plugin-gfm.d.ts +++ b/skills/web-automation/opencode/scripts/turndown-plugin-gfm.d.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. declare module 'turndown-plugin-gfm' { import TurndownService from 'turndown'; diff --git a/skills/web-automation/pi/.generated-manifest.json b/skills/web-automation/pi/.generated-manifest.json new file mode 100644 index 0000000..8d67b77 --- /dev/null +++ b/skills/web-automation/pi/.generated-manifest.json @@ -0,0 +1,97 @@ +{ + "$schema": "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json", + "generator": "scripts/generate-skills.mjs", + "generatedRoot": "skills/web-automation/pi", + "files": [ + { + "path": "scripts/auth.ts", + "kind": "file", + "mode": "644", + "sha256": "ce0a8aae0bc41b86e11aab51cc0e0cfa484a1934807f147c05c9bd38d416c066" + }, + { + "path": "scripts/browse.ts", + "kind": "file", + "mode": "644", + "sha256": "42da9cdc6806b8d7d8d814952ad9540033b6c6a4cbe9844ada328b2ceace67c9" + }, + { + "path": "scripts/check-install.js", + "kind": "file", + "mode": "644", + "sha256": "e46ee8cbe103794bf1e9c3466bb0fbd21079ceddc60ad9521299e8bc0150e48f" + }, + { + "path": "scripts/extract.js", + "kind": "file", + "mode": "644", + "sha256": "6fa2a0589de8afd6501e332e5fa263e1344187ea43a33590b431cdee59d04217" + }, + { + "path": "scripts/flow.ts", + "kind": "file", + "mode": "644", + "sha256": "b1c256bf6a206473512a4c0555c891893a48025529da282fa6cd07e68ad3d051" + }, + { + "path": "scripts/package.json", + "kind": "file", + "mode": "644", + "sha256": "0441607db0272fc991c893133cb69560274cbbde4e2104bf50b4ae1ec58fc39a" + }, + { + "path": "scripts/pnpm-lock.yaml", + "kind": "file", + "mode": "644", + "sha256": "17017e15e8b04311f5d53bdd37065b2f5a514a3119f40a0403148440ed181437" + }, + { + "path": "scripts/scan-local-app.ts", + "kind": "file", + "mode": "644", + "sha256": "3f42f9bb2d355fefc8645d2b2acfa3107bd87f9c2579b2631c94132bed0abea4" + }, + { + "path": "scripts/scrape.ts", + "kind": "file", + "mode": "644", + "sha256": "a1a3d81d57d9e8ab1854ce3cb230bdd39ae1087ec50c9fe82cc58f5f2663ebeb" + }, + { + "path": "scripts/test-full.ts", + "kind": "file", + "mode": "644", + "sha256": "76a647e840753621445c36894bff62e163f6a2e4d0860fa8e64d8df45fe21e08" + }, + { + "path": "scripts/test-minimal.ts", + "kind": "file", + "mode": "644", + "sha256": "59e0b2319d3f7521b2a8a4fca2d779afaa157bf2d160160fdec8cb56bea30b4f" + }, + { + "path": "scripts/test-profile.ts", + "kind": "file", + "mode": "644", + "sha256": "6cf0141581a9275bfa8a070a36212cef5f6417d64df3df3e614ec682008376b9" + }, + { + "path": "scripts/tsconfig.json", + "kind": "file", + "mode": "644", + "sha256": "5f9a83c8caab167eb20defbb5afde58f2bb573a300af99654997dcb3372408e0" + }, + { + "path": "scripts/turndown-plugin-gfm.d.ts", + "kind": "file", + "mode": "644", + "sha256": "c5001c059b160eff18a4097a8a0a7b96689b4ebc374543c7d5bf6e40b0d8a5ac" + }, + { + "path": "SKILL.md", + "kind": "file", + "mode": "644", + "sha256": "7ff56c1c50697439875f4dd0a7f7697962c8ba2105a4f66ab7b170f5dcc762bd" + } + ] +} diff --git a/skills/web-automation/pi/SKILL.md b/skills/web-automation/pi/SKILL.md index 5a021e7..53b403a 100644 --- a/skills/web-automation/pi/SKILL.md +++ b/skills/web-automation/pi/SKILL.md @@ -3,6 +3,8 @@ name: web-automation description: Browse and scrape web pages using Playwright-compatible CloakBrowser. Use when automating web workflows, extracting rendered page content, handling authenticated sessions, or running multi-step browser flows. --- +<!-- ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/_source/pi/SKILL.md and run `pnpm run sync:pi`. --> + # Web Automation with CloakBrowser (Pi) Automated web browsing and scraping for pi using the shared runtime bundle in `scripts/`. diff --git a/skills/web-automation/pi/scripts/auth.ts b/skills/web-automation/pi/scripts/auth.ts index e79f23d..272ee6d 100644 --- a/skills/web-automation/pi/scripts/auth.ts +++ b/skills/web-automation/pi/scripts/auth.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. /** * Authentication handler for web automation diff --git a/skills/web-automation/pi/scripts/browse.ts b/skills/web-automation/pi/scripts/browse.ts index 01cf098..3d27c9e 100644 --- a/skills/web-automation/pi/scripts/browse.ts +++ b/skills/web-automation/pi/scripts/browse.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. /** * Browser launcher using CloakBrowser with persistent profile diff --git a/skills/web-automation/pi/scripts/check-install.js b/skills/web-automation/pi/scripts/check-install.js index 2a60c8a..cb7197d 100644 --- a/skills/web-automation/pi/scripts/check-install.js +++ b/skills/web-automation/pi/scripts/check-install.js @@ -1,4 +1,5 @@ #!/usr/bin/env node +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import fs from "node:fs"; import path from "node:path"; @@ -18,11 +19,9 @@ async function main() { try { await import("cloakbrowser"); await import("playwright-core"); - await import("better-sqlite3"); - await import("esbuild"); } catch (error) { fail( - "Missing dependency/config: web-automation requires cloakbrowser, playwright-core, better-sqlite3, and esbuild.", + "Missing dependency/config: web-automation requires cloakbrowser and playwright-core.", error instanceof Error ? error.message : String(error) ); } @@ -34,7 +33,6 @@ async function main() { } process.stdout.write("OK: cloakbrowser + playwright-core installed\n"); - process.stdout.write("OK: better-sqlite3 + esbuild installed\n"); process.stdout.write("OK: CloakBrowser integration detected in browse.ts\n"); } diff --git a/skills/web-automation/pi/scripts/extract.js b/skills/web-automation/pi/scripts/extract.js old mode 100755 new mode 100644 index 5e3908a..f7b5995 --- a/skills/web-automation/pi/scripts/extract.js +++ b/skills/web-automation/pi/scripts/extract.js @@ -1,4 +1,5 @@ #!/usr/bin/env node +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import fs from "node:fs"; import path from "node:path"; diff --git a/skills/web-automation/pi/scripts/flow.ts b/skills/web-automation/pi/scripts/flow.ts index 5d01e55..dfe2d8c 100644 --- a/skills/web-automation/pi/scripts/flow.ts +++ b/skills/web-automation/pi/scripts/flow.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import parseArgs from 'minimist'; import type { Page } from 'playwright-core'; diff --git a/skills/web-automation/pi/scripts/package.json b/skills/web-automation/pi/scripts/package.json index a2221e8..e65f379 100644 --- a/skills/web-automation/pi/scripts/package.json +++ b/skills/web-automation/pi/scripts/package.json @@ -1,5 +1,5 @@ { - "name": "web-automation-scripts", + "name": "@ai-coding-skills/web-automation-pi", "version": "1.0.0", "description": "Web browsing and scraping scripts using CloakBrowser", "type": "module", @@ -32,5 +32,6 @@ "tsx": "^4.7.0", "typescript": "^5.3.0" }, - "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34" + "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34", + "private": true } diff --git a/skills/web-automation/pi/scripts/scan-local-app.ts b/skills/web-automation/pi/scripts/scan-local-app.ts index 6a05b35..00e213e 100644 --- a/skills/web-automation/pi/scripts/scan-local-app.ts +++ b/skills/web-automation/pi/scripts/scan-local-app.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { mkdirSync, writeFileSync } from 'fs'; import { dirname, resolve } from 'path'; diff --git a/skills/web-automation/pi/scripts/scrape.ts b/skills/web-automation/pi/scripts/scrape.ts index 0820de0..2a4b75d 100644 --- a/skills/web-automation/pi/scripts/scrape.ts +++ b/skills/web-automation/pi/scripts/scrape.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx tsx +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. /** * Web scraper that extracts content to markdown diff --git a/skills/web-automation/pi/scripts/test-full.ts b/skills/web-automation/pi/scripts/test-full.ts index 356bbab..16037ee 100644 --- a/skills/web-automation/pi/scripts/test-full.ts +++ b/skills/web-automation/pi/scripts/test-full.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { launchPersistentContext } from 'cloakbrowser'; import { homedir } from 'os'; import { join } from 'path'; diff --git a/skills/web-automation/pi/scripts/test-minimal.ts b/skills/web-automation/pi/scripts/test-minimal.ts index a5412e7..7cef2ce 100644 --- a/skills/web-automation/pi/scripts/test-minimal.ts +++ b/skills/web-automation/pi/scripts/test-minimal.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { launch } from 'cloakbrowser'; async function test() { diff --git a/skills/web-automation/pi/scripts/test-profile.ts b/skills/web-automation/pi/scripts/test-profile.ts index ec59ddd..604ec55 100644 --- a/skills/web-automation/pi/scripts/test-profile.ts +++ b/skills/web-automation/pi/scripts/test-profile.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. import { launchPersistentContext } from 'cloakbrowser'; import { homedir } from 'os'; import { join } from 'path'; diff --git a/skills/web-automation/pi/scripts/turndown-plugin-gfm.d.ts b/skills/web-automation/pi/scripts/turndown-plugin-gfm.d.ts index 316bed1..113765e 100644 --- a/skills/web-automation/pi/scripts/turndown-plugin-gfm.d.ts +++ b/skills/web-automation/pi/scripts/turndown-plugin-gfm.d.ts @@ -1,3 +1,4 @@ +// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`. declare module 'turndown-plugin-gfm' { import TurndownService from 'turndown'; diff --git a/skills/web-automation/shared/auth.ts b/skills/web-automation/shared/auth.ts new file mode 100644 index 0000000..e79f23d --- /dev/null +++ b/skills/web-automation/shared/auth.ts @@ -0,0 +1,575 @@ +#!/usr/bin/env npx tsx + +/** + * Authentication handler for web automation + * Supports generic form login and Microsoft SSO (MSAL) + * + * Usage: + * npx tsx auth.ts --url "https://example.com/login" --type form + * npx tsx auth.ts --url "https://example.com" --type msal + * npx tsx auth.ts --url "https://example.com" --type auto + */ + +import { getPage, launchBrowser } from './browse.js'; +import parseArgs from 'minimist'; +import type { Page, BrowserContext } from 'playwright-core'; +import { createInterface } from 'readline'; + +// Types +type AuthType = 'auto' | 'form' | 'msal'; + +interface AuthOptions { + url: string; + authType: AuthType; + credentials?: { + username: string; + password: string; + }; + headless?: boolean; + timeout?: number; +} + +interface AuthResult { + success: boolean; + finalUrl: string; + authType: AuthType; + message: string; +} + +// Get credentials from environment or options +function getCredentials(options?: { + username?: string; + password?: string; +}): { username: string; password: string } | null { + const username = options?.username || process.env.CLOAKBROWSER_USERNAME; + const password = options?.password || process.env.CLOAKBROWSER_PASSWORD; + + if (!username || !password) { + return null; + } + + return { username, password }; +} + +// Prompt user for input (for MFA or credentials) +async function promptUser(question: string, hidden = false): Promise<string> { + const rl = createInterface({ + input: process.stdin, + output: process.stdout, + }); + + return new Promise((resolve) => { + if (hidden) { + process.stdout.write(question); + // Note: This is a simple implementation. For production, use a proper hidden input library + } + rl.question(question, (answer) => { + rl.close(); + resolve(answer); + }); + }); +} + +// Detect authentication type from page +async function detectAuthType(page: Page): Promise<AuthType> { + const url = page.url(); + + // Check for Microsoft login + if ( + url.includes('login.microsoftonline.com') || + url.includes('login.live.com') || + url.includes('login.windows.net') + ) { + return 'msal'; + } + + // Check for common form login patterns + const hasLoginForm = await page.evaluate(() => { + const passwordField = document.querySelector( + 'input[type="password"], input[name*="password"], input[id*="password"]' + ); + const usernameField = document.querySelector( + 'input[type="email"], input[type="text"][name*="user"], input[type="text"][name*="email"], input[id*="user"], input[id*="email"]' + ); + return !!(passwordField && usernameField); + }); + + if (hasLoginForm) { + return 'form'; + } + + return 'auto'; +} + +// Handle generic form login +async function handleFormLogin( + page: Page, + credentials: { username: string; password: string }, + timeout: number +): Promise<boolean> { + console.log('Attempting form login...'); + + // Find and fill username/email field + const usernameSelectors = [ + 'input[type="email"]', + 'input[name*="user" i]', + 'input[name*="email" i]', + 'input[id*="user" i]', + 'input[id*="email" i]', + 'input[autocomplete="username"]', + 'input[type="text"]:first-of-type', + ]; + + let usernameField = null; + for (const selector of usernameSelectors) { + usernameField = await page.$(selector); + if (usernameField) break; + } + + if (!usernameField) { + console.error('Could not find username/email field'); + return false; + } + + await usernameField.fill(credentials.username); + console.log('Filled username field'); + + // Find and fill password field + const passwordSelectors = [ + 'input[type="password"]', + 'input[name*="password" i]', + 'input[id*="password" i]', + 'input[autocomplete="current-password"]', + ]; + + let passwordField = null; + for (const selector of passwordSelectors) { + passwordField = await page.$(selector); + if (passwordField) break; + } + + if (!passwordField) { + console.error('Could not find password field'); + return false; + } + + await passwordField.fill(credentials.password); + console.log('Filled password field'); + + // Check for "Remember me" checkbox and check it + const rememberCheckbox = await page.$( + 'input[type="checkbox"][name*="remember" i], input[type="checkbox"][id*="remember" i]' + ); + if (rememberCheckbox) { + await rememberCheckbox.check(); + console.log('Checked "Remember me" checkbox'); + } + + // Find and click submit button + const submitSelectors = [ + 'button[type="submit"]', + 'input[type="submit"]', + 'button:has-text("Sign in")', + 'button:has-text("Log in")', + 'button:has-text("Login")', + 'button:has-text("Submit")', + '[role="button"]:has-text("Sign in")', + ]; + + let submitButton = null; + for (const selector of submitSelectors) { + submitButton = await page.$(selector); + if (submitButton) break; + } + + if (!submitButton) { + // Try pressing Enter as fallback + await passwordField.press('Enter'); + } else { + await submitButton.click(); + } + + console.log('Submitted login form'); + + // Wait for navigation or error + try { + await page.waitForNavigation({ timeout, waitUntil: 'domcontentloaded' }); + return true; + } catch { + // Check if we're still on login page with error + const errorMessages = await page.$$eval( + '.error, .alert-danger, [role="alert"], .login-error', + (els) => els.map((el) => el.textContent?.trim()).filter(Boolean) + ); + + if (errorMessages.length > 0) { + console.error('Login error:', errorMessages.join(', ')); + return false; + } + + return true; // Might have succeeded without navigation + } +} + +// Handle Microsoft SSO login +async function handleMsalLogin( + page: Page, + credentials: { username: string; password: string }, + timeout: number +): Promise<boolean> { + console.log('Attempting Microsoft SSO login...'); + + const currentUrl = page.url(); + + // If not already on Microsoft login, wait for redirect + if (!currentUrl.includes('login.microsoftonline.com')) { + try { + await page.waitForURL('**/login.microsoftonline.com/**', { timeout: 10000 }); + } catch { + console.log('Not redirected to Microsoft login'); + return false; + } + } + + // Wait for email input + const emailInput = await page.waitForSelector( + 'input[type="email"], input[name="loginfmt"]', + { timeout } + ); + + if (!emailInput) { + console.error('Could not find email input on Microsoft login'); + return false; + } + + // Fill email and submit + await emailInput.fill(credentials.username); + console.log('Filled email field'); + + const nextButton = await page.$('input[type="submit"], button[type="submit"]'); + if (nextButton) { + await nextButton.click(); + } else { + await emailInput.press('Enter'); + } + + // Wait for password page + try { + await page.waitForSelector( + 'input[type="password"], input[name="passwd"]', + { timeout } + ); + } catch { + // Might be using passwordless auth or different flow + console.log('Password field not found - might be using different auth flow'); + return false; + } + + // Fill password + const passwordInput = await page.$('input[type="password"], input[name="passwd"]'); + if (!passwordInput) { + console.error('Could not find password input'); + return false; + } + + await passwordInput.fill(credentials.password); + console.log('Filled password field'); + + // Submit + const signInButton = await page.$('input[type="submit"], button[type="submit"]'); + if (signInButton) { + await signInButton.click(); + } else { + await passwordInput.press('Enter'); + } + + // Handle "Stay signed in?" prompt + try { + const staySignedInButton = await page.waitForSelector( + 'input[value="Yes"], button:has-text("Yes")', + { timeout: 5000 } + ); + if (staySignedInButton) { + await staySignedInButton.click(); + console.log('Clicked "Stay signed in" button'); + } + } catch { + // Prompt might not appear + } + + // Check for Conditional Access Policy error + const caError = await page.$('text=Conditional Access policy'); + if (caError) { + console.error('Blocked by Conditional Access Policy'); + // Take screenshot for debugging + await page.screenshot({ path: 'ca-policy-error.png' }); + console.log('Screenshot saved: ca-policy-error.png'); + return false; + } + + // Wait for redirect away from Microsoft login + try { + await page.waitForURL( + (url) => !url.href.includes('login.microsoftonline.com'), + { timeout } + ); + return true; + } catch { + return false; + } +} + +// Check if user is already authenticated +async function isAuthenticated(page: Page, targetUrl: string): Promise<boolean> { + const currentUrl = page.url(); + + // If we're on the target URL (not a login page), we're likely authenticated + if (currentUrl.startsWith(targetUrl)) { + // Check for common login page indicators + const isLoginPage = await page.evaluate(() => { + const loginIndicators = [ + 'input[type="password"]', + 'form[action*="login"]', + 'form[action*="signin"]', + '.login-form', + '#login', + ]; + return loginIndicators.some((sel) => document.querySelector(sel) !== null); + }); + + return !isLoginPage; + } + + return false; +} + +// Main authentication function +export async function authenticate(options: AuthOptions): Promise<AuthResult> { + const browser = await launchBrowser({ headless: options.headless ?? true }); + const page = await browser.newPage(); + const timeout = options.timeout ?? 30000; + + try { + // Navigate to URL + console.log(`Navigating to: ${options.url}`); + await page.goto(options.url, { timeout: 60000, waitUntil: 'domcontentloaded' }); + + // Check if already authenticated + if (await isAuthenticated(page, options.url)) { + return { + success: true, + finalUrl: page.url(), + authType: 'auto', + message: 'Already authenticated (session persisted from profile)', + }; + } + + // Get credentials + const credentials = options.credentials + ? options.credentials + : getCredentials(); + + if (!credentials) { + // No credentials - open interactive browser + console.log('\nNo credentials provided. Opening browser for manual login...'); + console.log('Please complete the login process manually.'); + console.log('The session will be saved to your profile.'); + + // Switch to headed mode for manual login + await browser.close(); + const interactiveBrowser = await launchBrowser({ headless: false }); + const interactivePage = await interactiveBrowser.newPage(); + await interactivePage.goto(options.url); + + await promptUser('\nPress Enter when you have completed login...'); + + const finalUrl = interactivePage.url(); + await interactiveBrowser.close(); + + return { + success: true, + finalUrl, + authType: 'auto', + message: 'Manual login completed - session saved to profile', + }; + } + + // Detect auth type if auto + let authType = options.authType; + if (authType === 'auto') { + authType = await detectAuthType(page); + console.log(`Detected auth type: ${authType}`); + } + + // Handle authentication based on type + let success = false; + switch (authType) { + case 'msal': + success = await handleMsalLogin(page, credentials, timeout); + break; + case 'form': + default: + success = await handleFormLogin(page, credentials, timeout); + break; + } + + const finalUrl = page.url(); + + return { + success, + finalUrl, + authType, + message: success + ? `Authentication successful - session saved to profile` + : 'Authentication failed', + }; + } finally { + await browser.close(); + } +} + +// Navigate to authenticated page (handles auth if needed) +export async function navigateAuthenticated( + url: string, + options?: { + credentials?: { username: string; password: string }; + headless?: boolean; + } +): Promise<{ page: Page; browser: BrowserContext }> { + const { page, browser } = await getPage({ headless: options?.headless ?? true }); + + await page.goto(url, { timeout: 60000, waitUntil: 'domcontentloaded' }); + + // Check if we need to authenticate + if (!(await isAuthenticated(page, url))) { + console.log('Session expired or not authenticated. Attempting login...'); + + // Get credentials + const credentials = options?.credentials ?? getCredentials(); + + if (!credentials) { + throw new Error( + 'Authentication required but no credentials provided. ' + + 'Set CLOAKBROWSER_USERNAME and CLOAKBROWSER_PASSWORD environment variables.' + ); + } + + // Detect and handle auth + const authType = await detectAuthType(page); + + let success = false; + if (authType === 'msal') { + success = await handleMsalLogin(page, credentials, 30000); + } else { + success = await handleFormLogin(page, credentials, 30000); + } + + if (!success) { + await browser.close(); + throw new Error('Authentication failed'); + } + + // Navigate back to original URL if we were redirected + if (!page.url().startsWith(url)) { + await page.goto(url, { timeout: 60000, waitUntil: 'domcontentloaded' }); + } + } + + return { page, browser }; +} + +// CLI entry point +async function main() { + const args = parseArgs(process.argv.slice(2), { + string: ['url', 'type', 'username', 'password'], + boolean: ['headless', 'help'], + default: { + type: 'auto', + headless: false, // Default to headed for auth so user can see/interact + }, + alias: { + u: 'url', + t: 'type', + h: 'help', + }, + }); + + if (args.help || !args.url) { + console.log(` +Web Authentication Handler + +Usage: + npx tsx auth.ts --url <url> [options] + +Options: + -u, --url <url> URL to authenticate (required) + -t, --type <type> Auth type: auto, form, or msal (default: auto) + --username <user> Username/email (or set CLOAKBROWSER_USERNAME env var) + --password <pass> Password (or set CLOAKBROWSER_PASSWORD env var) + --headless <bool> Run in headless mode (default: false for auth) + -h, --help Show this help message + +Auth Types: + auto Auto-detect authentication type + form Generic username/password form + msal Microsoft SSO (login.microsoftonline.com) + +Environment Variables: + CLOAKBROWSER_USERNAME Default username/email for authentication + CLOAKBROWSER_PASSWORD Default password for authentication + +Examples: + # Interactive login (no credentials, opens browser) + npx tsx auth.ts --url "https://example.com/login" + + # Form login with credentials + npx tsx auth.ts --url "https://example.com/login" --type form \\ + --username "user@example.com" --password "secret" + + # Microsoft SSO login + CLOAKBROWSER_USERNAME=user@company.com CLOAKBROWSER_PASSWORD=secret \\ + npx tsx auth.ts --url "https://internal.company.com" --type msal + +Notes: + - Session is saved to ~/.cloakbrowser-profile/ for persistence + - After successful auth, subsequent browses will be authenticated + - Use --headless false if you need to handle MFA manually +`); + process.exit(args.help ? 0 : 1); + } + + const authType = args.type as AuthType; + if (!['auto', 'form', 'msal'].includes(authType)) { + console.error(`Invalid auth type: ${authType}. Must be auto, form, or msal.`); + process.exit(1); + } + + try { + const result = await authenticate({ + url: args.url, + authType, + credentials: + args.username && args.password + ? { username: args.username, password: args.password } + : undefined, + headless: args.headless, + }); + + console.log(`\nAuthentication result:`); + console.log(` Success: ${result.success}`); + console.log(` Auth type: ${result.authType}`); + console.log(` Final URL: ${result.finalUrl}`); + console.log(` Message: ${result.message}`); + + process.exit(result.success ? 0 : 1); + } catch (error) { + console.error('Error:', error instanceof Error ? error.message : error); + process.exit(1); + } +} + +// Run if executed directly +const isMainModule = process.argv[1]?.includes('auth.ts'); +if (isMainModule) { + main(); +} diff --git a/skills/web-automation/shared/browse.ts b/skills/web-automation/shared/browse.ts new file mode 100644 index 0000000..01cf098 --- /dev/null +++ b/skills/web-automation/shared/browse.ts @@ -0,0 +1,188 @@ +#!/usr/bin/env npx tsx + +/** + * Browser launcher using CloakBrowser with persistent profile + * + * Usage: + * npx tsx browse.ts --url "https://example.com" + * npx tsx browse.ts --url "https://example.com" --screenshot --output page.png + * npx tsx browse.ts --url "https://example.com" --headless false --wait 5000 + */ + +import { launchPersistentContext } from 'cloakbrowser'; +import { homedir } from 'os'; +import { join } from 'path'; +import { existsSync, mkdirSync } from 'fs'; +import parseArgs from 'minimist'; +import type { Page, BrowserContext } from 'playwright-core'; + +interface BrowseOptions { + url: string; + headless?: boolean; + screenshot?: boolean; + output?: string; + wait?: number; + timeout?: number; + interactive?: boolean; +} + +interface BrowseResult { + title: string; + url: string; + screenshotPath?: string; +} + +function sleep(ms: number): Promise<void> { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +const getProfilePath = (): string => { + const customPath = process.env.CLOAKBROWSER_PROFILE_PATH; + if (customPath) return customPath; + + const profileDir = join(homedir(), '.cloakbrowser-profile'); + if (!existsSync(profileDir)) { + mkdirSync(profileDir, { recursive: true }); + } + return profileDir; +}; + +export async function launchBrowser(options: { + headless?: boolean; +}): Promise<BrowserContext> { + const profilePath = getProfilePath(); + const envHeadless = process.env.CLOAKBROWSER_HEADLESS; + const headless = options.headless ?? (envHeadless ? envHeadless === 'true' : true); + + console.log(`Using profile: ${profilePath}`); + console.log(`Headless mode: ${headless}`); + + const context = await launchPersistentContext({ + userDataDir: profilePath, + headless, + humanize: true, + }); + + return context; +} + +export async function browse(options: BrowseOptions): Promise<BrowseResult> { + const browser = await launchBrowser({ headless: options.headless }); + const page = browser.pages()[0] || await browser.newPage(); + + try { + console.log(`Navigating to: ${options.url}`); + await page.goto(options.url, { + timeout: options.timeout ?? 60000, + waitUntil: 'domcontentloaded', + }); + + if (options.wait) { + console.log(`Waiting ${options.wait}ms...`); + await sleep(options.wait); + } + + const result: BrowseResult = { + title: await page.title(), + url: page.url(), + }; + + console.log(`Page title: ${result.title}`); + console.log(`Final URL: ${result.url}`); + + if (options.screenshot) { + const outputPath = options.output ?? 'screenshot.png'; + await page.screenshot({ path: outputPath, fullPage: true }); + result.screenshotPath = outputPath; + console.log(`Screenshot saved: ${outputPath}`); + } + + if (options.interactive) { + console.log('\nInteractive mode - browser will stay open.'); + console.log('Press Ctrl+C to close.'); + await new Promise(() => {}); + } + + return result; + } finally { + if (!options.interactive) { + await browser.close(); + } + } +} + +export async function getPage(options?: { + headless?: boolean; +}): Promise<{ page: Page; browser: BrowserContext }> { + const browser = await launchBrowser({ headless: options?.headless }); + const page = browser.pages()[0] || await browser.newPage(); + return { page, browser }; +} + +async function main() { + const args = parseArgs(process.argv.slice(2), { + string: ['url', 'output'], + boolean: ['screenshot', 'headless', 'interactive', 'help'], + default: { + headless: true, + screenshot: false, + interactive: false, + }, + alias: { + u: 'url', + o: 'output', + s: 'screenshot', + h: 'help', + i: 'interactive', + }, + }); + + if (args.help || !args.url) { + console.log(` +Web Browser with CloakBrowser + +Usage: + npx tsx browse.ts --url <url> [options] + +Options: + -u, --url <url> URL to navigate to (required) + -s, --screenshot Take a screenshot of the page + -o, --output <path> Output path for screenshot (default: screenshot.png) + --headless <bool> Run in headless mode (default: true) + --wait <ms> Wait time after page load in milliseconds + --timeout <ms> Navigation timeout (default: 60000) + -i, --interactive Keep browser open for manual interaction + -h, --help Show this help message + +Examples: + npx tsx browse.ts --url "https://example.com" + npx tsx browse.ts --url "https://example.com" --screenshot --output page.png + npx tsx browse.ts --url "https://example.com" --headless false --interactive + +Environment Variables: + CLOAKBROWSER_PROFILE_PATH Custom profile directory (default: ~/.cloakbrowser-profile/) + CLOAKBROWSER_HEADLESS Default headless mode (true/false) +`); + process.exit(args.help ? 0 : 1); + } + + try { + await browse({ + url: args.url, + headless: args.headless, + screenshot: args.screenshot, + output: args.output, + wait: args.wait ? parseInt(args.wait, 10) : undefined, + timeout: args.timeout ? parseInt(args.timeout, 10) : undefined, + interactive: args.interactive, + }); + } catch (error) { + console.error('Error:', error instanceof Error ? error.message : error); + process.exit(1); + } +} + +const isMainModule = process.argv[1]?.includes('browse.ts'); +if (isMainModule) { + main(); +} diff --git a/skills/web-automation/shared/check-install.js b/skills/web-automation/shared/check-install.js new file mode 100644 index 0000000..50e4884 --- /dev/null +++ b/skills/web-automation/shared/check-install.js @@ -0,0 +1,40 @@ +#!/usr/bin/env node + +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +function fail(message, details) { + const payload = { error: message }; + if (details) payload.details = details; + process.stderr.write(`${JSON.stringify(payload)}\n`); + process.exit(1); +} + +async function main() { + try { + await import("cloakbrowser"); + await import("playwright-core"); + } catch (error) { + fail( + "Missing dependency/config: web-automation requires cloakbrowser and playwright-core.", + error instanceof Error ? error.message : String(error) + ); + } + + const browsePath = path.join(__dirname, "browse.ts"); + const browseSource = fs.readFileSync(browsePath, "utf8"); + if (!/launchPersistentContext/.test(browseSource) || !/from ['"]cloakbrowser['"]/.test(browseSource)) { + fail("browse.ts is not configured for CloakBrowser."); + } + + process.stdout.write("OK: cloakbrowser + playwright-core installed\n"); + process.stdout.write("OK: CloakBrowser integration detected in browse.ts\n"); +} + +main().catch((error) => { + fail("Install check failed.", error instanceof Error ? error.message : String(error)); +}); diff --git a/skills/web-automation/shared/extract.js b/skills/web-automation/shared/extract.js new file mode 100755 index 0000000..5e3908a --- /dev/null +++ b/skills/web-automation/shared/extract.js @@ -0,0 +1,188 @@ +#!/usr/bin/env node + +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const DEFAULT_WAIT_MS = 5000; +const MAX_WAIT_MS = 20000; +const NAV_TIMEOUT_MS = 30000; +const EXTRA_CHALLENGE_WAIT_MS = 8000; +const CONTENT_LIMIT = 12000; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +function fail(message, details) { + const payload = { error: message }; + if (details) payload.details = details; + process.stderr.write(`${JSON.stringify(payload)}\n`); + process.exit(1); +} + +function parseWaitTime(raw) { + const value = Number.parseInt(raw || `${DEFAULT_WAIT_MS}`, 10); + if (!Number.isFinite(value) || value < 0) return DEFAULT_WAIT_MS; + return Math.min(value, MAX_WAIT_MS); +} + +function parseTarget(rawUrl) { + if (!rawUrl) { + fail("Missing URL. Usage: node extract.js <URL>"); + } + + let parsed; + try { + parsed = new URL(rawUrl); + } catch (error) { + fail("Invalid URL.", error.message); + } + + if (!["http:", "https:"].includes(parsed.protocol)) { + fail("Only http and https URLs are allowed."); + } + + return parsed.toString(); +} + +function ensureParentDir(filePath) { + if (!filePath) return; + fs.mkdirSync(path.dirname(filePath), { recursive: true }); +} + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +async function detectChallenge(page) { + try { + return await page.evaluate(() => { + const text = (document.body?.innerText || "").toLowerCase(); + return ( + text.includes("checking your browser") || + text.includes("just a moment") || + text.includes("verify you are human") || + text.includes("press and hold") || + document.querySelector('iframe[src*="challenge"]') !== null || + document.querySelector('iframe[src*="cloudflare"]') !== null + ); + }); + } catch { + return false; + } +} + +async function loadCloakBrowser() { + try { + return await import("cloakbrowser"); + } catch (error) { + fail( + "CloakBrowser is not installed for this skill. Run pnpm install in this skill's scripts directory first.", + error.message + ); + } +} + +async function runWithStderrLogs(fn) { + const originalLog = console.log; + const originalError = console.error; + console.log = (...args) => process.stderr.write(`${args.join(" ")}\n`); + console.error = (...args) => process.stderr.write(`${args.join(" ")}\n`); + try { + return await fn(); + } finally { + console.log = originalLog; + console.error = originalError; + } +} + +async function main() { + const requestedUrl = parseTarget(process.argv[2]); + const waitTime = parseWaitTime(process.env.WAIT_TIME); + const screenshotPath = process.env.SCREENSHOT_PATH || ""; + const saveHtml = process.env.SAVE_HTML === "true"; + const headless = process.env.HEADLESS !== "false"; + const userAgent = process.env.USER_AGENT || undefined; + const startedAt = Date.now(); + const { ensureBinary, launchContext } = await loadCloakBrowser(); + + let context; + try { + await runWithStderrLogs(() => ensureBinary()); + + context = await runWithStderrLogs(() => launchContext({ + headless, + userAgent, + locale: "en-US", + viewport: { width: 1440, height: 900 }, + humanize: true, + })); + + const page = await context.newPage(); + const response = await page.goto(requestedUrl, { + waitUntil: "domcontentloaded", + timeout: NAV_TIMEOUT_MS + }); + + await sleep(waitTime); + + let challengeDetected = await detectChallenge(page); + if (challengeDetected) { + await sleep(EXTRA_CHALLENGE_WAIT_MS); + challengeDetected = await detectChallenge(page); + } + + const extracted = await page.evaluate((contentLimit) => { + const bodyText = document.body?.innerText || ""; + return { + finalUrl: window.location.href, + title: document.title || "", + content: bodyText.slice(0, contentLimit), + metaDescription: + document.querySelector('meta[name="description"]')?.content || + document.querySelector('meta[property="og:description"]')?.content || + "" + }; + }, CONTENT_LIMIT); + + const result = { + requestedUrl, + finalUrl: extracted.finalUrl, + title: extracted.title, + content: extracted.content, + metaDescription: extracted.metaDescription, + status: response ? response.status() : null, + challengeDetected, + elapsedSeconds: ((Date.now() - startedAt) / 1000).toFixed(2) + }; + + if (screenshotPath) { + ensureParentDir(screenshotPath); + await page.screenshot({ path: screenshotPath, fullPage: false, timeout: 10000 }); + result.screenshot = screenshotPath; + } + + if (saveHtml) { + const htmlTarget = screenshotPath + ? screenshotPath.replace(/\.[^.]+$/, ".html") + : path.resolve(__dirname, `page-${Date.now()}.html`); + ensureParentDir(htmlTarget); + fs.writeFileSync(htmlTarget, await page.content()); + result.htmlFile = htmlTarget; + } + + process.stdout.write(`${JSON.stringify(result, null, 2)}\n`); + await context.close(); + } catch (error) { + if (context) { + try { + await context.close(); + } catch { + // Ignore close errors after the primary failure. + } + } + fail("Scrape failed.", error.message); + } +} + +main(); diff --git a/skills/web-automation/shared/flow.ts b/skills/web-automation/shared/flow.ts new file mode 100644 index 0000000..5d01e55 --- /dev/null +++ b/skills/web-automation/shared/flow.ts @@ -0,0 +1,329 @@ +#!/usr/bin/env npx tsx + +import parseArgs from 'minimist'; +import type { Page } from 'playwright-core'; +import { launchBrowser } from './browse'; + +type Step = + | { action: 'goto'; url: string } + | { action: 'click'; selector?: string; text?: string; role?: string; name?: string } + | { action: 'type'; selector?: string; text: string } + | { action: 'press'; key: string; selector?: string } + | { action: 'wait'; ms: number } + | { action: 'screenshot'; path: string } + | { action: 'extract'; selector: string; count?: number }; + +function normalizeNavigationUrl(rawUrl: string): string { + let parsed: URL; + try { + parsed = new URL(rawUrl); + } catch { + throw new Error(`Invalid navigation URL: ${rawUrl}`); + } + + if (!['http:', 'https:'].includes(parsed.protocol)) { + throw new Error(`Only http and https URLs are allowed in flow steps: ${rawUrl}`); + } + + return parsed.toString(); +} + +function normalizeKey(k: string): string { + if (!k) return 'Enter'; + const lower = k.toLowerCase(); + if (lower === 'enter' || lower === 'return') return 'Enter'; + if (lower === 'tab') return 'Tab'; + if (lower === 'escape' || lower === 'esc') return 'Escape'; + return k; +} + +function splitInstructions(instruction: string): string[] { + return instruction + .split(/\bthen\b|;/gi) + .map((s) => s.trim()) + .filter(Boolean); +} + +function parseInstruction(instruction: string): Step[] { + const parts = splitInstructions(instruction); + const steps: Step[] = []; + + for (const p of parts) { + // go to https://... + const goto = p.match(/^(?:go to|open|navigate to)\s+(https?:\/\/\S+)/i); + if (goto) { + steps.push({ action: 'goto', url: normalizeNavigationUrl(goto[1]) }); + continue; + } + + // click on "text" or click #selector or click button "name" + const clickRole = p.match(/^click\s+(button|link|textbox|img|image|tab)\s+"([^"]+)"$/i); + if (clickRole) { + const role = clickRole[1].toLowerCase() === 'image' ? 'img' : clickRole[1].toLowerCase(); + steps.push({ action: 'click', role, name: clickRole[2] }); + continue; + } + const clickText = p.match(/^click(?: on)?\s+"([^"]+)"/i); + if (clickText) { + steps.push({ action: 'click', text: clickText[1] }); + continue; + } + const clickSelector = p.match(/^click(?: on)?\s+(#[\w-]+|\.[\w-]+|[a-z]+\[[^\]]+\])/i); + if (clickSelector) { + steps.push({ action: 'click', selector: clickSelector[1] }); + continue; + } + + // type "text" [in selector] + const typeInto = p.match(/^type\s+"([^"]+)"\s+in\s+(.+)$/i); + if (typeInto) { + steps.push({ action: 'type', text: typeInto[1], selector: typeInto[2].trim() }); + continue; + } + const typeOnly = p.match(/^type\s+"([^"]+)"$/i); + if (typeOnly) { + steps.push({ action: 'type', text: typeOnly[1] }); + continue; + } + + // press enter [in selector] + const pressIn = p.match(/^press\s+(\w+)\s+in\s+(.+)$/i); + if (pressIn) { + steps.push({ action: 'press', key: normalizeKey(pressIn[1]), selector: pressIn[2].trim() }); + continue; + } + const pressOnly = p.match(/^press\s+(\w+)$/i); + if (pressOnly) { + steps.push({ action: 'press', key: normalizeKey(pressOnly[1]) }); + continue; + } + + // wait 2s / wait 500ms + const waitS = p.match(/^wait\s+(\d+)\s*s(?:ec(?:onds?)?)?$/i); + if (waitS) { + steps.push({ action: 'wait', ms: parseInt(waitS[1], 10) * 1000 }); + continue; + } + const waitMs = p.match(/^wait\s+(\d+)\s*ms$/i); + if (waitMs) { + steps.push({ action: 'wait', ms: parseInt(waitMs[1], 10) }); + continue; + } + + // screenshot path + const shot = p.match(/^screenshot(?: to)?\s+(.+)$/i); + if (shot) { + steps.push({ action: 'screenshot', path: shot[1].trim() }); + continue; + } + + throw new Error(`Could not parse step: "${p}"`); + } + + return steps; +} + +function validateSteps(steps: Step[]): Step[] { + return steps.map((step) => + step.action === 'goto' + ? { + ...step, + url: normalizeNavigationUrl(step.url), + } + : step + ); +} + +function escapeRegExp(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +function isLikelyLoginText(text: string): boolean { + return /(login|accedi|sign\s*in|entra)/i.test(text); +} + +async function clickByText(page: Page, text: string): Promise<boolean> { + const patterns = [new RegExp(`^${escapeRegExp(text)}$`, 'i'), new RegExp(escapeRegExp(text), 'i')]; + + for (const pattern of patterns) { + const targets = [ + page.getByRole('button', { name: pattern }).first(), + page.getByRole('link', { name: pattern }).first(), + page.getByText(pattern).first(), + ]; + + for (const target of targets) { + if (await target.count()) { + try { + await target.click({ timeout: 8000 }); + return true; + } catch { + // keep trying next candidate + } + } + } + } + + return false; +} + +async function fallbackLoginNavigation(page: Page, requestedText: string): Promise<boolean> { + if (!isLikelyLoginText(requestedText)) return false; + + const current = new URL(page.url()); + + const candidateLinks = await page.evaluate(() => { + const loginTerms = ['login', 'accedi', 'sign in', 'entra']; + const anchors = Array.from(document.querySelectorAll('a[href], a[onclick], button[onclick]')) as Array<HTMLAnchorElement | HTMLButtonElement>; + + return anchors + .map((el) => { + const text = (el.textContent || '').trim().toLowerCase(); + const href = (el as HTMLAnchorElement).getAttribute('href') || ''; + return { text, href }; + }) + .filter((x) => x.text && loginTerms.some((t) => x.text.includes(t))) + .map((x) => x.href) + .filter(Boolean); + }); + + // Prefer real URLs (not javascript:) + const realCandidate = candidateLinks.find((h) => /login|account\/login/i.test(h) && !h.startsWith('javascript:')); + if (realCandidate) { + const target = new URL(realCandidate, page.url()).toString(); + await page.goto(target, { waitUntil: 'domcontentloaded', timeout: 60000 }); + return true; + } + + // Site-specific fallback for Corriere + if (/corriere\.it$/i.test(current.hostname) || /\.corriere\.it$/i.test(current.hostname)) { + await page.goto('https://www.corriere.it/account/login', { + waitUntil: 'domcontentloaded', + timeout: 60000, + }); + return true; + } + + return false; +} + +async function typeInBestTarget(page: Page, text: string, selector?: string) { + if (selector) { + await page.locator(selector).first().click({ timeout: 10000 }); + await page.locator(selector).first().fill(text); + return; + } + const loc = page.locator('input[name="q"], input[type="search"], input[type="text"], textarea').first(); + await loc.click({ timeout: 10000 }); + await loc.fill(text); +} + +async function pressOnTarget(page: Page, key: string, selector?: string) { + if (selector) { + await page.locator(selector).first().press(key); + return; + } + await page.keyboard.press(key); +} + +async function runSteps(page: Page, steps: Step[]) { + for (const step of steps) { + switch (step.action) { + case 'goto': + await page.goto(normalizeNavigationUrl(step.url), { + waitUntil: 'domcontentloaded', + timeout: 60000, + }); + break; + case 'click': + if (step.selector) { + await page.locator(step.selector).first().click({ timeout: 15000 }); + } else if (step.role && step.name) { + await page.getByRole(step.role as any, { name: new RegExp(escapeRegExp(step.name), 'i') }).first().click({ timeout: 15000 }); + } else if (step.text) { + const clicked = await clickByText(page, step.text); + if (!clicked) { + const recovered = await fallbackLoginNavigation(page, step.text); + if (!recovered) { + throw new Error(`Could not click target text: ${step.text}`); + } + } + } else { + throw new Error('click step missing selector/text/role'); + } + try { + await page.waitForLoadState('domcontentloaded', { timeout: 10000 }); + } catch { + // no navigation is fine + } + break; + case 'type': + await typeInBestTarget(page, step.text, step.selector); + break; + case 'press': + await pressOnTarget(page, step.key, step.selector); + break; + case 'wait': + await page.waitForTimeout(step.ms); + break; + case 'screenshot': + await page.screenshot({ path: step.path, fullPage: true }); + break; + case 'extract': { + const items = await page.locator(step.selector).allTextContents(); + const out = items.slice(0, step.count ?? items.length).map((t) => t.trim()).filter(Boolean); + console.log(JSON.stringify(out, null, 2)); + break; + } + default: + throw new Error('Unknown step'); + } + } +} + +async function main() { + const args = parseArgs(process.argv.slice(2), { + string: ['instruction', 'steps'], + boolean: ['headless', 'help'], + default: { headless: true }, + alias: { i: 'instruction', s: 'steps', h: 'help' }, + }); + + if (args.help || (!args.instruction && !args.steps)) { + console.log(` +General Web Flow Runner (CloakBrowser) + +Usage: + npx tsx flow.ts --instruction "go to https://example.com then type \"hello\" then press enter" + npx tsx flow.ts --steps '[{"action":"goto","url":"https://example.com"}]' + +Supported natural steps: + - go to/open/navigate to <url> + - click on "Text" + - click <css-selector> + - type "text" + - type "text" in <css-selector> + - press <key> + - press <key> in <css-selector> + - wait <N>s | wait <N>ms + - screenshot <path> +`); + process.exit(args.help ? 0 : 1); + } + + const steps = validateSteps(args.steps ? JSON.parse(args.steps) : parseInstruction(args.instruction)); + const browser = await launchBrowser({ headless: args.headless }); + const page = await browser.newPage(); + + try { + await runSteps(page, steps); + console.log('Flow complete. Final URL:', page.url()); + } finally { + await browser.close(); + } +} + +main().catch((e) => { + console.error('Error:', e instanceof Error ? e.message : e); + process.exit(1); +}); diff --git a/skills/web-automation/shared/node_modules/.bin/cloakbrowser b/skills/web-automation/shared/node_modules/.bin/cloakbrowser new file mode 100755 index 0000000..bae6895 --- /dev/null +++ b/skills/web-automation/shared/node_modules/.bin/cloakbrowser @@ -0,0 +1,21 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -z "$NODE_PATH" ]; then + export NODE_PATH="/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/cloakbrowser@0.3.26_playwright-core@1.59.1/node_modules/cloakbrowser/dist/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/cloakbrowser@0.3.26_playwright-core@1.59.1/node_modules/cloakbrowser/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/cloakbrowser@0.3.26_playwright-core@1.59.1/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/node_modules" +else + export NODE_PATH="/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/cloakbrowser@0.3.26_playwright-core@1.59.1/node_modules/cloakbrowser/dist/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/cloakbrowser@0.3.26_playwright-core@1.59.1/node_modules/cloakbrowser/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/cloakbrowser@0.3.26_playwright-core@1.59.1/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/node_modules:$NODE_PATH" +fi +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../cloakbrowser/dist/cli.js" "$@" +else + exec node "$basedir/../cloakbrowser/dist/cli.js" "$@" +fi diff --git a/skills/web-automation/shared/node_modules/.bin/esbuild b/skills/web-automation/shared/node_modules/.bin/esbuild new file mode 100755 index 0000000..d7462e4 --- /dev/null +++ b/skills/web-automation/shared/node_modules/.bin/esbuild @@ -0,0 +1,21 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -z "$NODE_PATH" ]; then + export NODE_PATH="/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/esbuild@0.27.0/node_modules/esbuild/bin/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/esbuild@0.27.0/node_modules/esbuild/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/esbuild@0.27.0/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/node_modules" +else + export NODE_PATH="/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/esbuild@0.27.0/node_modules/esbuild/bin/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/esbuild@0.27.0/node_modules/esbuild/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/esbuild@0.27.0/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/node_modules:$NODE_PATH" +fi +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../esbuild/bin/esbuild" "$@" +else + exec node "$basedir/../esbuild/bin/esbuild" "$@" +fi diff --git a/skills/web-automation/shared/node_modules/.bin/playwright-core b/skills/web-automation/shared/node_modules/.bin/playwright-core new file mode 100755 index 0000000..18fef7d --- /dev/null +++ b/skills/web-automation/shared/node_modules/.bin/playwright-core @@ -0,0 +1,21 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -z "$NODE_PATH" ]; then + export NODE_PATH="/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/playwright-core@1.59.1/node_modules/playwright-core/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/playwright-core@1.59.1/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/node_modules" +else + export NODE_PATH="/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/playwright-core@1.59.1/node_modules/playwright-core/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/playwright-core@1.59.1/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/node_modules:$NODE_PATH" +fi +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../playwright-core/cli.js" "$@" +else + exec node "$basedir/../playwright-core/cli.js" "$@" +fi diff --git a/skills/web-automation/shared/node_modules/.bin/tsc b/skills/web-automation/shared/node_modules/.bin/tsc new file mode 100755 index 0000000..0b2495e --- /dev/null +++ b/skills/web-automation/shared/node_modules/.bin/tsc @@ -0,0 +1,21 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -z "$NODE_PATH" ]; then + export NODE_PATH="/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/node_modules" +else + export NODE_PATH="/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/node_modules:$NODE_PATH" +fi +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@" +else + exec node "$basedir/../typescript/bin/tsc" "$@" +fi diff --git a/skills/web-automation/shared/node_modules/.bin/tsserver b/skills/web-automation/shared/node_modules/.bin/tsserver new file mode 100755 index 0000000..04e6978 --- /dev/null +++ b/skills/web-automation/shared/node_modules/.bin/tsserver @@ -0,0 +1,21 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -z "$NODE_PATH" ]; then + export NODE_PATH="/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/node_modules" +else + export NODE_PATH="/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/node_modules:$NODE_PATH" +fi +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@" +else + exec node "$basedir/../typescript/bin/tsserver" "$@" +fi diff --git a/skills/web-automation/shared/node_modules/.bin/tsx b/skills/web-automation/shared/node_modules/.bin/tsx new file mode 100755 index 0000000..e372e9d --- /dev/null +++ b/skills/web-automation/shared/node_modules/.bin/tsx @@ -0,0 +1,21 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -z "$NODE_PATH" ]; then + export NODE_PATH="/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/dist/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/tsx@4.21.0/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/node_modules" +else + export NODE_PATH="/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/dist/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/tsx@4.21.0/node_modules:/Users/stefano.fiorini/Documents/projects/ai-coding-skills-worktrees/2026-05-03-perform-code-optimization-and-document-cleanup/node_modules/.pnpm/node_modules:$NODE_PATH" +fi +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../tsx/dist/cli.mjs" "$@" +else + exec node "$basedir/../tsx/dist/cli.mjs" "$@" +fi diff --git a/skills/web-automation/shared/node_modules/@mozilla/readability b/skills/web-automation/shared/node_modules/@mozilla/readability new file mode 120000 index 0000000..82d0a38 --- /dev/null +++ b/skills/web-automation/shared/node_modules/@mozilla/readability @@ -0,0 +1 @@ +../../../../../node_modules/.pnpm/@mozilla+readability@0.5.0/node_modules/@mozilla/readability \ No newline at end of file diff --git a/skills/web-automation/shared/node_modules/@types/jsdom b/skills/web-automation/shared/node_modules/@types/jsdom new file mode 120000 index 0000000..46c7f6c --- /dev/null +++ b/skills/web-automation/shared/node_modules/@types/jsdom @@ -0,0 +1 @@ +../../../../../node_modules/.pnpm/@types+jsdom@21.1.7/node_modules/@types/jsdom \ No newline at end of file diff --git a/skills/web-automation/shared/node_modules/@types/minimist b/skills/web-automation/shared/node_modules/@types/minimist new file mode 120000 index 0000000..b382242 --- /dev/null +++ b/skills/web-automation/shared/node_modules/@types/minimist @@ -0,0 +1 @@ +../../../../../node_modules/.pnpm/@types+minimist@1.2.5/node_modules/@types/minimist \ No newline at end of file diff --git a/skills/web-automation/shared/node_modules/@types/turndown b/skills/web-automation/shared/node_modules/@types/turndown new file mode 120000 index 0000000..6f82dc9 --- /dev/null +++ b/skills/web-automation/shared/node_modules/@types/turndown @@ -0,0 +1 @@ +../../../../../node_modules/.pnpm/@types+turndown@5.0.6/node_modules/@types/turndown \ No newline at end of file diff --git a/skills/web-automation/shared/node_modules/better-sqlite3 b/skills/web-automation/shared/node_modules/better-sqlite3 new file mode 120000 index 0000000..552a383 --- /dev/null +++ b/skills/web-automation/shared/node_modules/better-sqlite3 @@ -0,0 +1 @@ +../../../../node_modules/.pnpm/better-sqlite3@12.9.0/node_modules/better-sqlite3 \ No newline at end of file diff --git a/skills/web-automation/shared/node_modules/cloakbrowser b/skills/web-automation/shared/node_modules/cloakbrowser new file mode 120000 index 0000000..fcd40ad --- /dev/null +++ b/skills/web-automation/shared/node_modules/cloakbrowser @@ -0,0 +1 @@ +../../../../node_modules/.pnpm/cloakbrowser@0.3.26_playwright-core@1.59.1/node_modules/cloakbrowser \ No newline at end of file diff --git a/skills/web-automation/shared/node_modules/esbuild b/skills/web-automation/shared/node_modules/esbuild new file mode 120000 index 0000000..189a889 --- /dev/null +++ b/skills/web-automation/shared/node_modules/esbuild @@ -0,0 +1 @@ +../../../../node_modules/.pnpm/esbuild@0.27.0/node_modules/esbuild \ No newline at end of file diff --git a/skills/web-automation/shared/node_modules/jsdom b/skills/web-automation/shared/node_modules/jsdom new file mode 120000 index 0000000..96b6eb7 --- /dev/null +++ b/skills/web-automation/shared/node_modules/jsdom @@ -0,0 +1 @@ +../../../../node_modules/.pnpm/jsdom@24.1.3/node_modules/jsdom \ No newline at end of file diff --git a/skills/web-automation/shared/node_modules/minimist b/skills/web-automation/shared/node_modules/minimist new file mode 120000 index 0000000..89a72b7 --- /dev/null +++ b/skills/web-automation/shared/node_modules/minimist @@ -0,0 +1 @@ +../../../../node_modules/.pnpm/minimist@1.2.8/node_modules/minimist \ No newline at end of file diff --git a/skills/web-automation/shared/node_modules/playwright-core b/skills/web-automation/shared/node_modules/playwright-core new file mode 120000 index 0000000..4f928c2 --- /dev/null +++ b/skills/web-automation/shared/node_modules/playwright-core @@ -0,0 +1 @@ +../../../../node_modules/.pnpm/playwright-core@1.59.1/node_modules/playwright-core \ No newline at end of file diff --git a/skills/web-automation/shared/node_modules/tsx b/skills/web-automation/shared/node_modules/tsx new file mode 120000 index 0000000..9bac0d1 --- /dev/null +++ b/skills/web-automation/shared/node_modules/tsx @@ -0,0 +1 @@ +../../../../node_modules/.pnpm/tsx@4.21.0/node_modules/tsx \ No newline at end of file diff --git a/skills/web-automation/shared/node_modules/turndown b/skills/web-automation/shared/node_modules/turndown new file mode 120000 index 0000000..3c5e7a2 --- /dev/null +++ b/skills/web-automation/shared/node_modules/turndown @@ -0,0 +1 @@ +../../../../node_modules/.pnpm/turndown@7.2.4/node_modules/turndown \ No newline at end of file diff --git a/skills/web-automation/shared/node_modules/turndown-plugin-gfm b/skills/web-automation/shared/node_modules/turndown-plugin-gfm new file mode 120000 index 0000000..5777825 --- /dev/null +++ b/skills/web-automation/shared/node_modules/turndown-plugin-gfm @@ -0,0 +1 @@ +../../../../node_modules/.pnpm/turndown-plugin-gfm@1.0.2/node_modules/turndown-plugin-gfm \ No newline at end of file diff --git a/skills/web-automation/shared/node_modules/typescript b/skills/web-automation/shared/node_modules/typescript new file mode 120000 index 0000000..8ca86b1 --- /dev/null +++ b/skills/web-automation/shared/node_modules/typescript @@ -0,0 +1 @@ +../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript \ No newline at end of file diff --git a/skills/web-automation/shared/package.json b/skills/web-automation/shared/package.json new file mode 100644 index 0000000..a2221e8 --- /dev/null +++ b/skills/web-automation/shared/package.json @@ -0,0 +1,36 @@ +{ + "name": "web-automation-scripts", + "version": "1.0.0", + "description": "Web browsing and scraping scripts using CloakBrowser", + "type": "module", + "scripts": { + "check-install": "node check-install.js", + "extract": "node extract.js", + "browse": "tsx browse.ts", + "auth": "tsx auth.ts", + "flow": "tsx flow.ts", + "scrape": "tsx scrape.ts", + "typecheck": "tsc --noEmit -p tsconfig.json", + "lint": "pnpm run typecheck && node --check check-install.js && node --check extract.js", + "fetch-browser": "npx cloakbrowser install" + }, + "dependencies": { + "@mozilla/readability": "^0.5.0", + "better-sqlite3": "^12.6.2", + "cloakbrowser": "^0.3.22", + "jsdom": "^24.0.0", + "minimist": "^1.2.8", + "playwright-core": "^1.59.1", + "turndown": "^7.1.2", + "turndown-plugin-gfm": "^1.0.2" + }, + "devDependencies": { + "@types/jsdom": "^21.1.6", + "@types/minimist": "^1.2.5", + "@types/turndown": "^5.0.4", + "esbuild": "0.27.0", + "tsx": "^4.7.0", + "typescript": "^5.3.0" + }, + "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34" +} diff --git a/skills/web-automation/shared/pnpm-lock.yaml b/skills/web-automation/shared/pnpm-lock.yaml new file mode 100644 index 0000000..59dba9c --- /dev/null +++ b/skills/web-automation/shared/pnpm-lock.yaml @@ -0,0 +1,1292 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@mozilla/readability': + specifier: ^0.5.0 + version: 0.5.0 + better-sqlite3: + specifier: ^12.6.2 + version: 12.8.0 + cloakbrowser: + specifier: ^0.3.22 + version: 0.3.22(mmdb-lib@3.0.1)(playwright-core@1.59.1) + jsdom: + specifier: ^24.0.0 + version: 24.1.3 + minimist: + specifier: ^1.2.8 + version: 1.2.8 + playwright-core: + specifier: ^1.59.1 + version: 1.59.1 + turndown: + specifier: ^7.1.2 + version: 7.2.2 + turndown-plugin-gfm: + specifier: ^1.0.2 + version: 1.0.2 + devDependencies: + '@types/jsdom': + specifier: ^21.1.6 + version: 21.1.7 + '@types/minimist': + specifier: ^1.2.5 + version: 1.2.5 + '@types/turndown': + specifier: ^5.0.4 + version: 5.0.6 + esbuild: + specifier: 0.27.0 + version: 0.27.0 + tsx: + specifier: ^4.7.0 + version: 4.21.0 + typescript: + specifier: ^5.3.0 + version: 5.9.3 + +packages: + + '@asamuzakjp/css-color@3.2.0': + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + + '@esbuild/aix-ppc64@0.27.0': + resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.0': + resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.0': + resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.0': + resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.0': + resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.0': + resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.0': + resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.0': + resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.0': + resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.0': + resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.0': + resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.0': + resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.0': + resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.0': + resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.0': + resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.0': + resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.0': + resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.0': + resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.0': + resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.0': + resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.0': + resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.0': + resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.0': + resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.0': + resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.0': + resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.0': + resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@mixmark-io/domino@2.2.0': + resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} + + '@mozilla/readability@0.5.0': + resolution: {integrity: sha512-Z+CZ3QaosfFaTqvhQsIktyGrjFjSC0Fa4EMph4mqKnWhmyoGICsV/8QK+8HpXut6zV7zwfWwqDmEjtk1Qf6EgQ==} + engines: {node: '>=14.0.0'} + + '@types/jsdom@21.1.7': + resolution: {integrity: sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==} + + '@types/minimist@1.2.5': + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + + '@types/node@25.0.6': + resolution: {integrity: sha512-NNu0sjyNxpoiW3YuVFfNz7mxSQ+S4X2G28uqg2s+CzoqoQjLPsWSbsFFyztIAqt2vb8kfEAsJNepMGPTxFDx3Q==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + + '@types/turndown@5.0.6': + resolution: {integrity: sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg==} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + better-sqlite3@12.8.0: + resolution: {integrity: sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ==} + engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + cloakbrowser@0.3.22: + resolution: {integrity: sha512-L2CWQiVdunhKslTli8HCe4INhaAt4npbvsM2Ox4/idqiRmT2BADndQ05eDS8TonNSWeWqbjsh04UhSZOD3B6mg==} + engines: {node: '>=18.0.0'} + hasBin: true + peerDependencies: + mmdb-lib: '>=2.0.0' + playwright-core: '>=1.40.0' + puppeteer-core: '>=21.0.0' + peerDependenciesMeta: + mmdb-lib: + optional: true + playwright-core: + optional: true + puppeteer-core: + optional: true + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + cssstyle@4.6.0: + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} + engines: {node: '>=18'} + + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.27.0: + resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} + engines: {node: '>=18'} + hasBin: true + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + jsdom@24.1.3: + resolution: {integrity: sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + mmdb-lib@3.0.1: + resolution: {integrity: sha512-dyAyMR+cRykZd1mw5altC9f4vKpCsuywPwo8l/L5fKqDay2zmqT0mF/BvUoXnQiqGn+nceO914rkPKJoyFnGxA==} + engines: {node: '>=10', npm: '>=6'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + + node-abi@3.85.0: + resolution: {integrity: sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==} + engines: {node: '>=10'} + + nwsapi@2.2.23: + resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + playwright-core@1.59.1: + resolution: {integrity: sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==} + engines: {node: '>=18'} + hasBin: true + + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. + hasBin: true + + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + rrweb-cssom@0.7.1: + resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} + + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar@7.5.13: + resolution: {integrity: sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==} + engines: {node: '>=18'} + + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + turndown-plugin-gfm@1.0.2: + resolution: {integrity: sha512-vwz9tfvF7XN/jE0dGoBei3FXWuvll78ohzCZQuOb+ZjWrs3a0XhQVomJEb2Qh4VHTPNRO4GPZh0V7VRbiWwkRg==} + + turndown@7.2.2: + resolution: {integrity: sha512-1F7db8BiExOKxjSMU2b7if62D/XOyQyZbPKq/nUwopfgnHlqXHqQ0lvfUTeUIr1lZJzOPFn43dODyMSIfvWRKQ==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + +snapshots: + + '@asamuzakjp/css-color@3.2.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 10.4.3 + + '@csstools/color-helpers@5.1.0': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-tokenizer@3.0.4': {} + + '@esbuild/aix-ppc64@0.27.0': + optional: true + + '@esbuild/android-arm64@0.27.0': + optional: true + + '@esbuild/android-arm@0.27.0': + optional: true + + '@esbuild/android-x64@0.27.0': + optional: true + + '@esbuild/darwin-arm64@0.27.0': + optional: true + + '@esbuild/darwin-x64@0.27.0': + optional: true + + '@esbuild/freebsd-arm64@0.27.0': + optional: true + + '@esbuild/freebsd-x64@0.27.0': + optional: true + + '@esbuild/linux-arm64@0.27.0': + optional: true + + '@esbuild/linux-arm@0.27.0': + optional: true + + '@esbuild/linux-ia32@0.27.0': + optional: true + + '@esbuild/linux-loong64@0.27.0': + optional: true + + '@esbuild/linux-mips64el@0.27.0': + optional: true + + '@esbuild/linux-ppc64@0.27.0': + optional: true + + '@esbuild/linux-riscv64@0.27.0': + optional: true + + '@esbuild/linux-s390x@0.27.0': + optional: true + + '@esbuild/linux-x64@0.27.0': + optional: true + + '@esbuild/netbsd-arm64@0.27.0': + optional: true + + '@esbuild/netbsd-x64@0.27.0': + optional: true + + '@esbuild/openbsd-arm64@0.27.0': + optional: true + + '@esbuild/openbsd-x64@0.27.0': + optional: true + + '@esbuild/openharmony-arm64@0.27.0': + optional: true + + '@esbuild/sunos-x64@0.27.0': + optional: true + + '@esbuild/win32-arm64@0.27.0': + optional: true + + '@esbuild/win32-ia32@0.27.0': + optional: true + + '@esbuild/win32-x64@0.27.0': + optional: true + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + + '@mixmark-io/domino@2.2.0': {} + + '@mozilla/readability@0.5.0': {} + + '@types/jsdom@21.1.7': + dependencies: + '@types/node': 25.0.6 + '@types/tough-cookie': 4.0.5 + parse5: 7.3.0 + + '@types/minimist@1.2.5': {} + + '@types/node@25.0.6': + dependencies: + undici-types: 7.16.0 + + '@types/tough-cookie@4.0.5': {} + + '@types/turndown@5.0.6': {} + + agent-base@7.1.4: {} + + asynckit@0.4.0: {} + + base64-js@1.5.1: {} + + better-sqlite3@12.8.0: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.3 + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + chownr@1.1.4: {} + + chownr@3.0.0: {} + + cloakbrowser@0.3.22(mmdb-lib@3.0.1)(playwright-core@1.59.1): + dependencies: + tar: 7.5.13 + optionalDependencies: + mmdb-lib: 3.0.1 + playwright-core: 1.59.1 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + cssstyle@4.6.0: + dependencies: + '@asamuzakjp/css-color': 3.2.0 + rrweb-cssom: 0.8.0 + + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-extend@0.6.0: {} + + delayed-stream@1.0.0: {} + + detect-libc@2.1.2: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + entities@6.0.1: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + esbuild@0.27.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.0 + '@esbuild/android-arm': 0.27.0 + '@esbuild/android-arm64': 0.27.0 + '@esbuild/android-x64': 0.27.0 + '@esbuild/darwin-arm64': 0.27.0 + '@esbuild/darwin-x64': 0.27.0 + '@esbuild/freebsd-arm64': 0.27.0 + '@esbuild/freebsd-x64': 0.27.0 + '@esbuild/linux-arm': 0.27.0 + '@esbuild/linux-arm64': 0.27.0 + '@esbuild/linux-ia32': 0.27.0 + '@esbuild/linux-loong64': 0.27.0 + '@esbuild/linux-mips64el': 0.27.0 + '@esbuild/linux-ppc64': 0.27.0 + '@esbuild/linux-riscv64': 0.27.0 + '@esbuild/linux-s390x': 0.27.0 + '@esbuild/linux-x64': 0.27.0 + '@esbuild/netbsd-arm64': 0.27.0 + '@esbuild/netbsd-x64': 0.27.0 + '@esbuild/openbsd-arm64': 0.27.0 + '@esbuild/openbsd-x64': 0.27.0 + '@esbuild/openharmony-arm64': 0.27.0 + '@esbuild/sunos-x64': 0.27.0 + '@esbuild/win32-arm64': 0.27.0 + '@esbuild/win32-ia32': 0.27.0 + '@esbuild/win32-x64': 0.27.0 + + expand-template@2.0.3: {} + + file-uri-to-path@1.0.0: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fs-constants@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-tsconfig@4.13.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + github-from-package@0.0.0: {} + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + is-potential-custom-element-name@1.0.1: {} + + jsdom@24.1.3: + dependencies: + cssstyle: 4.6.0 + data-urls: 5.0.0 + decimal.js: 10.6.0 + form-data: 4.0.5 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.23 + parse5: 7.3.0 + rrweb-cssom: 0.7.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.4 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.19.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + lru-cache@10.4.3: {} + + math-intrinsics@1.1.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-response@3.1.0: {} + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + minizlib@3.1.0: + dependencies: + minipass: 7.1.2 + + mkdirp-classic@0.5.3: {} + + mmdb-lib@3.0.1: + optional: true + + ms@2.1.3: {} + + napi-build-utils@2.0.0: {} + + node-abi@3.85.0: + dependencies: + semver: 7.7.3 + + nwsapi@2.2.23: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + playwright-core@1.59.1: {} + + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.85.0 + pump: 3.0.3 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + + psl@1.15.0: + dependencies: + punycode: 2.3.1 + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + punycode@2.3.1: {} + + querystringify@2.2.0: {} + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + requires-port@1.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + rrweb-cssom@0.7.1: {} + + rrweb-cssom@0.8.0: {} + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + semver@7.7.3: {} + + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-json-comments@2.0.1: {} + + symbol-tree@3.2.4: {} + + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.3 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar@7.5.13: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.1.0 + yallist: 5.0.0 + + tough-cookie@4.1.4: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + + tsx@4.21.0: + dependencies: + esbuild: 0.27.0 + get-tsconfig: 4.13.0 + optionalDependencies: + fsevents: 2.3.3 + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + turndown-plugin-gfm@1.0.2: {} + + turndown@7.2.2: + dependencies: + '@mixmark-io/domino': 2.2.0 + + typescript@5.9.3: {} + + undici-types@7.16.0: {} + + universalify@0.2.0: {} + + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + + util-deprecate@1.0.2: {} + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + + wrappy@1.0.2: {} + + ws@8.19.0: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + yallist@5.0.0: {} diff --git a/skills/web-automation/shared/scan-local-app.ts b/skills/web-automation/shared/scan-local-app.ts new file mode 100644 index 0000000..6a05b35 --- /dev/null +++ b/skills/web-automation/shared/scan-local-app.ts @@ -0,0 +1,174 @@ +#!/usr/bin/env npx tsx + +import { mkdirSync, writeFileSync } from 'fs'; +import { dirname, resolve } from 'path'; +import { getPage } from './browse.js'; + +type NavResult = { + requestedUrl: string; + url: string; + status: number | null; + title: string; + error?: string; +}; + +type RouteCheck = { + route: string; + result: NavResult; + heading: string | null; +}; + +const DEFAULT_BASE_URL = 'http://localhost:3000'; +const DEFAULT_REPORT_PATH = resolve(process.cwd(), 'scan-local-app.md'); + +function env(name: string): string | undefined { + const value = process.env[name]?.trim(); + return value ? value : undefined; +} + +function getRoutes(baseUrl: string): string[] { + const routeList = env('SCAN_ROUTES'); + if (routeList) { + return routeList + .split(',') + .map((route) => route.trim()) + .filter(Boolean) + .map((route) => new URL(route, baseUrl).toString()); + } + + return [baseUrl]; +} + +async function gotoWithStatus(page: any, url: string): Promise<NavResult> { + const response = await page + .goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 }) + .catch((error: unknown) => ({ error })); + + if (response?.error) { + return { + requestedUrl: url, + url: page.url(), + status: null, + title: await page.title().catch(() => ''), + error: String(response.error), + }; + } + + return { + requestedUrl: url, + url: page.url(), + status: response ? response.status() : null, + title: await page.title().catch(() => ''), + }; +} + +async function textOrNull(page: any, selector: string): Promise<string | null> { + const locator = page.locator(selector).first(); + try { + if ((await locator.count()) === 0) return null; + const value = await locator.textContent(); + return value ? value.trim().replace(/\s+/g, ' ') : null; + } catch { + return null; + } +} + +async function loginIfConfigured(page: any, baseUrl: string, lines: string[]) { + const loginPath = env('SCAN_LOGIN_PATH'); + const username = env('SCAN_USERNAME') ?? env('CLOAKBROWSER_USERNAME'); + const password = env('SCAN_PASSWORD') ?? env('CLOAKBROWSER_PASSWORD'); + const usernameSelector = env('SCAN_USERNAME_SELECTOR') ?? 'input[type="email"], input[name="email"]'; + const passwordSelector = env('SCAN_PASSWORD_SELECTOR') ?? 'input[type="password"], input[name="password"]'; + const submitSelector = env('SCAN_SUBMIT_SELECTOR') ?? 'button[type="submit"], input[type="submit"]'; + + if (!loginPath) { + lines.push('## Login'); + lines.push('- Skipped: set `SCAN_LOGIN_PATH` to enable login smoke checks.'); + lines.push(''); + return; + } + + const loginUrl = new URL(loginPath, baseUrl).toString(); + lines.push('## Login'); + lines.push(`- Login URL: ${loginUrl}`); + await gotoWithStatus(page, loginUrl); + + if (!username || !password) { + lines.push('- Skipped: set `SCAN_USERNAME`/`SCAN_PASSWORD` or `CLOAKBROWSER_USERNAME`/`CLOAKBROWSER_PASSWORD`.'); + lines.push(''); + return; + } + + await page.locator(usernameSelector).first().fill(username); + await page.locator(passwordSelector).first().fill(password); + await page.locator(submitSelector).first().click(); + await page.waitForTimeout(2500); + + lines.push(`- After submit URL: ${page.url()}`); + lines.push(`- Cookie count: ${(await page.context().cookies()).length}`); + lines.push(''); +} + +async function checkRoutes(page: any, baseUrl: string, lines: string[]) { + const routes = getRoutes(baseUrl); + const routeChecks: RouteCheck[] = []; + + for (const url of routes) { + const result = await gotoWithStatus(page, url); + const heading = await textOrNull(page, 'h1'); + routeChecks.push({ + route: url, + result, + heading, + }); + } + + lines.push('## Route Checks'); + for (const check of routeChecks) { + const relativeUrl = check.route.startsWith(baseUrl) ? check.route.slice(baseUrl.length) || '/' : check.route; + const finalPath = check.result.url.startsWith(baseUrl) + ? check.result.url.slice(baseUrl.length) || '/' + : check.result.url; + const suffix = check.heading ? `, h1="${check.heading}"` : ''; + const errorSuffix = check.result.error ? `, error="${check.result.error}"` : ''; + lines.push( + `- ${relativeUrl} → status ${check.result.status ?? 'ERR'} (final ${finalPath})${suffix}${errorSuffix}` + ); + } + lines.push(''); +} + +async function main() { + const baseUrl = env('SCAN_BASE_URL') ?? DEFAULT_BASE_URL; + const reportPath = resolve(env('SCAN_REPORT_PATH') ?? DEFAULT_REPORT_PATH); + const headless = (env('SCAN_HEADLESS') ?? env('CLOAKBROWSER_HEADLESS') ?? 'true') === 'true'; + const { page, browser } = await getPage({ headless }); + const lines: string[] = []; + + lines.push('# Web Automation Scan (local)'); + lines.push(''); + lines.push(`- Base URL: ${baseUrl}`); + lines.push(`- Timestamp: ${new Date().toISOString()}`); + lines.push(`- Headless: ${headless}`); + lines.push(`- Report Path: ${reportPath}`); + lines.push(''); + + try { + await loginIfConfigured(page, baseUrl, lines); + await checkRoutes(page, baseUrl, lines); + lines.push('## Notes'); + lines.push('- This generic smoke helper records route availability and top-level headings for a local app.'); + lines.push('- Configure login and route coverage with `SCAN_*` environment variables.'); + } finally { + await browser.close(); + } + + mkdirSync(dirname(reportPath), { recursive: true }); + writeFileSync(reportPath, `${lines.join('\n')}\n`, 'utf-8'); + console.log(`Report written to ${reportPath}`); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/skills/web-automation/shared/scrape.ts b/skills/web-automation/shared/scrape.ts new file mode 100644 index 0000000..0820de0 --- /dev/null +++ b/skills/web-automation/shared/scrape.ts @@ -0,0 +1,351 @@ +#!/usr/bin/env npx tsx + +/** + * Web scraper that extracts content to markdown + * + * Usage: + * npx tsx scrape.ts --url "https://example.com" --mode main + * npx tsx scrape.ts --url "https://example.com" --mode full --output page.md + * npx tsx scrape.ts --url "https://example.com" --mode selector --selector ".content" + */ + +import TurndownService from 'turndown'; +import * as turndownPluginGfm from 'turndown-plugin-gfm'; +import { Readability } from '@mozilla/readability'; +import { JSDOM } from 'jsdom'; +import { writeFileSync } from 'fs'; +import parseArgs from 'minimist'; +import { getPage } from './browse.js'; + +// Types +type ScrapeMode = 'main' | 'full' | 'selector'; + +interface ScrapeOptions { + url: string; + mode: ScrapeMode; + selector?: string; + output?: string; + includeLinks?: boolean; + includeTables?: boolean; + includeImages?: boolean; + headless?: boolean; + wait?: number; +} + +interface ScrapeResult { + title: string; + url: string; + markdown: string; + byline?: string; + excerpt?: string; +} + +// Configure Turndown for markdown conversion +function createTurndownService(options: { + includeLinks?: boolean; + includeTables?: boolean; + includeImages?: boolean; +}): TurndownService { + const turndown = new TurndownService({ + headingStyle: 'atx', + hr: '---', + bulletListMarker: '-', + codeBlockStyle: 'fenced', + fence: '```', + emDelimiter: '*', + strongDelimiter: '**', + linkStyle: 'inlined', + }); + + // Add GFM support (tables, strikethrough, task lists) + turndown.use(turndownPluginGfm.gfm); + + // Custom rule for code blocks with language detection + turndown.addRule('codeBlockWithLanguage', { + filter: (node) => { + return ( + node.nodeName === 'PRE' && + node.firstChild?.nodeName === 'CODE' + ); + }, + replacement: (_content, node) => { + const codeNode = node.firstChild as HTMLElement; + const className = codeNode.getAttribute('class') || ''; + const langMatch = className.match(/language-(\w+)/); + const lang = langMatch ? langMatch[1] : ''; + const code = codeNode.textContent || ''; + return `\n\n\`\`\`${lang}\n${code}\n\`\`\`\n\n`; + }, + }); + + // Remove images if not included + if (!options.includeImages) { + turndown.addRule('removeImages', { + filter: 'img', + replacement: () => '', + }); + } + + // Remove links but keep text if not included + if (!options.includeLinks) { + turndown.addRule('removeLinks', { + filter: 'a', + replacement: (content) => content, + }); + } + + // Remove script, style, nav, footer, aside elements + turndown.remove(['script', 'style', 'nav', 'footer', 'aside', 'noscript']); + + return turndown; +} + +// Extract main content using Readability +function extractMainContent(html: string, url: string): { + content: string; + title: string; + byline?: string; + excerpt?: string; +} { + const dom = new JSDOM(html, { url }); + const reader = new Readability(dom.window.document); + const article = reader.parse(); + + if (!article) { + throw new Error('Could not extract main content from page'); + } + + return { + content: article.content, + title: article.title, + byline: article.byline || undefined, + excerpt: article.excerpt || undefined, + }; +} + +// Scrape a URL and return markdown +export async function scrape(options: ScrapeOptions): Promise<ScrapeResult> { + const { page, browser } = await getPage({ headless: options.headless ?? true }); + + try { + // Navigate to URL + console.log(`Navigating to: ${options.url}`); + await page.goto(options.url, { + timeout: 60000, + waitUntil: 'domcontentloaded', + }); + + // Wait if specified + if (options.wait) { + console.log(`Waiting ${options.wait}ms for dynamic content...`); + await page.waitForTimeout(options.wait); + } + + const pageTitle = await page.title(); + const pageUrl = page.url(); + + let html: string; + let title = pageTitle; + let byline: string | undefined; + let excerpt: string | undefined; + + // Get HTML based on mode + switch (options.mode) { + case 'main': { + // Get full page HTML and extract with Readability + const fullHtml = await page.content(); + const extracted = extractMainContent(fullHtml, pageUrl); + html = extracted.content; + title = extracted.title || pageTitle; + byline = extracted.byline; + excerpt = extracted.excerpt; + break; + } + + case 'selector': { + if (!options.selector) { + throw new Error('Selector mode requires --selector option'); + } + const element = await page.$(options.selector); + if (!element) { + throw new Error(`Selector not found: ${options.selector}`); + } + html = await element.innerHTML(); + break; + } + + case 'full': + default: { + // Get body content, excluding common non-content elements + html = await page.evaluate(() => { + // Remove common non-content elements + const selectorsToRemove = [ + 'script', 'style', 'noscript', 'iframe', + 'nav', 'header', 'footer', '.cookie-banner', + '.advertisement', '.ads', '#ads', '.social-share', + '.comments', '#comments', '.sidebar' + ]; + + selectorsToRemove.forEach(selector => { + document.querySelectorAll(selector).forEach(el => el.remove()); + }); + + return document.body.innerHTML; + }); + break; + } + } + + // Convert to markdown + const turndown = createTurndownService({ + includeLinks: options.includeLinks ?? true, + includeTables: options.includeTables ?? true, + includeImages: options.includeImages ?? false, + }); + + let markdown = turndown.turndown(html); + + // Add title as H1 if not already present + if (!markdown.startsWith('# ')) { + markdown = `# ${title}\n\n${markdown}`; + } + + // Add metadata header + const metadataLines = [ + `<!-- Scraped from: ${pageUrl} -->`, + byline ? `<!-- Author: ${byline} -->` : null, + excerpt ? `<!-- Excerpt: ${excerpt} -->` : null, + `<!-- Scraped at: ${new Date().toISOString()} -->`, + '', + ].filter(Boolean); + + markdown = metadataLines.join('\n') + '\n' + markdown; + + // Clean up excessive whitespace + markdown = markdown + .replace(/\n{4,}/g, '\n\n\n') + .replace(/[ \t]+$/gm, '') + .trim(); + + const result: ScrapeResult = { + title, + url: pageUrl, + markdown, + byline, + excerpt, + }; + + // Save to file if output specified + if (options.output) { + writeFileSync(options.output, markdown, 'utf-8'); + console.log(`Markdown saved to: ${options.output}`); + } + + return result; + } finally { + await browser.close(); + } +} + +// CLI entry point +async function main() { + const args = parseArgs(process.argv.slice(2), { + string: ['url', 'mode', 'selector', 'output'], + boolean: ['headless', 'links', 'tables', 'images', 'help'], + default: { + mode: 'main', + headless: true, + links: true, + tables: true, + images: false, + }, + alias: { + u: 'url', + m: 'mode', + s: 'selector', + o: 'output', + h: 'help', + }, + }); + + if (args.help || !args.url) { + console.log(` +Web Scraper - Extract content to Markdown + +Usage: + npx tsx scrape.ts --url <url> [options] + +Options: + -u, --url <url> URL to scrape (required) + -m, --mode <mode> Scrape mode: main, full, or selector (default: main) + -s, --selector <sel> CSS selector for selector mode + -o, --output <path> Output file path for markdown + --headless <bool> Run in headless mode (default: true) + --wait <ms> Wait time for dynamic content + --links Include links in output (default: true) + --tables Include tables in output (default: true) + --images Include images in output (default: false) + -h, --help Show this help message + +Scrape Modes: + main Extract main article content using Readability (best for articles) + full Full page content with common elements removed + selector Extract specific element by CSS selector + +Examples: + npx tsx scrape.ts --url "https://docs.example.com/guide" --mode main + npx tsx scrape.ts --url "https://example.com" --mode full --output page.md + npx tsx scrape.ts --url "https://example.com" --mode selector --selector ".api-docs" + npx tsx scrape.ts --url "https://example.com" --mode main --no-links --output clean.md + +Output Format: + - GitHub Flavored Markdown (tables, strikethrough, task lists) + - Proper heading hierarchy + - Code blocks with language detection + - Metadata comments at top (source URL, date) +`); + process.exit(args.help ? 0 : 1); + } + + const mode = args.mode as ScrapeMode; + if (!['main', 'full', 'selector'].includes(mode)) { + console.error(`Invalid mode: ${mode}. Must be main, full, or selector.`); + process.exit(1); + } + + try { + const result = await scrape({ + url: args.url, + mode, + selector: args.selector, + output: args.output, + includeLinks: args.links, + includeTables: args.tables, + includeImages: args.images, + headless: args.headless, + wait: args.wait ? parseInt(args.wait, 10) : undefined, + }); + + // Print result summary + console.log(`\nScrape complete:`); + console.log(` Title: ${result.title}`); + console.log(` URL: ${result.url}`); + if (result.byline) console.log(` Author: ${result.byline}`); + console.log(` Markdown length: ${result.markdown.length} chars`); + + // Print markdown if not saved to file + if (!args.output) { + console.log('\n--- Markdown Output ---\n'); + console.log(result.markdown); + } + } catch (error) { + console.error('Error:', error instanceof Error ? error.message : error); + process.exit(1); + } +} + +// Run if executed directly +const isMainModule = process.argv[1]?.includes('scrape.ts'); +if (isMainModule) { + main(); +} diff --git a/skills/web-automation/shared/test-full.ts b/skills/web-automation/shared/test-full.ts new file mode 100644 index 0000000..356bbab --- /dev/null +++ b/skills/web-automation/shared/test-full.ts @@ -0,0 +1,36 @@ +import { launchPersistentContext } from 'cloakbrowser'; +import { homedir } from 'os'; +import { join } from 'path'; +import { mkdirSync, existsSync } from 'fs'; + +async function test() { + const profilePath = join(homedir(), '.cloakbrowser-profile'); + if (!existsSync(profilePath)) { + mkdirSync(profilePath, { recursive: true }); + } + + console.log('Profile path:', profilePath); + console.log('Launching CloakBrowser with full options...'); + + const browser = await launchPersistentContext({ + headless: true, + userDataDir: profilePath, + humanize: true, + }); + + console.log('Browser launched'); + const page = browser.pages()[0] || await browser.newPage(); + console.log('Page created'); + + await page.goto('https://github.com', { timeout: 30000 }); + console.log('Navigated to:', page.url()); + console.log('Title:', await page.title()); + + await page.screenshot({ path: '/tmp/github-test.png' }); + console.log('Screenshot saved'); + + await browser.close(); + console.log('Done'); +} + +test().catch(console.error); diff --git a/skills/web-automation/shared/test-minimal.ts b/skills/web-automation/shared/test-minimal.ts new file mode 100644 index 0000000..a5412e7 --- /dev/null +++ b/skills/web-automation/shared/test-minimal.ts @@ -0,0 +1,23 @@ +import { launch } from 'cloakbrowser'; + +async function test() { + console.log('Launching CloakBrowser with minimal config...'); + + const browser = await launch({ + headless: true, + humanize: true, + }); + + console.log('Browser launched'); + const page = await browser.newPage(); + console.log('Page created'); + + await page.goto('https://example.com', { timeout: 30000 }); + console.log('Navigated to:', page.url()); + console.log('Title:', await page.title()); + + await browser.close(); + console.log('Done'); +} + +test().catch(console.error); diff --git a/skills/web-automation/shared/test-profile.ts b/skills/web-automation/shared/test-profile.ts new file mode 100644 index 0000000..ec59ddd --- /dev/null +++ b/skills/web-automation/shared/test-profile.ts @@ -0,0 +1,33 @@ +import { launchPersistentContext } from 'cloakbrowser'; +import { homedir } from 'os'; +import { join } from 'path'; +import { mkdirSync, existsSync } from 'fs'; + +async function test() { + const profilePath = join(homedir(), '.cloakbrowser-profile'); + if (!existsSync(profilePath)) { + mkdirSync(profilePath, { recursive: true }); + } + + console.log('Profile path:', profilePath); + console.log('Launching with persistent userDataDir...'); + + const browser = await launchPersistentContext({ + headless: true, + userDataDir: profilePath, + humanize: true, + }); + + console.log('Browser launched'); + const page = browser.pages()[0] || await browser.newPage(); + console.log('Page created'); + + await page.goto('https://example.com', { timeout: 30000 }); + console.log('Navigated to:', page.url()); + console.log('Title:', await page.title()); + + await browser.close(); + console.log('Done'); +} + +test().catch(console.error); diff --git a/skills/web-automation/shared/tsconfig.json b/skills/web-automation/shared/tsconfig.json new file mode 100644 index 0000000..4c23583 --- /dev/null +++ b/skills/web-automation/shared/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/skills/web-automation/shared/turndown-plugin-gfm.d.ts b/skills/web-automation/shared/turndown-plugin-gfm.d.ts new file mode 100644 index 0000000..316bed1 --- /dev/null +++ b/skills/web-automation/shared/turndown-plugin-gfm.d.ts @@ -0,0 +1,8 @@ +declare module 'turndown-plugin-gfm' { + import TurndownService from 'turndown'; + + export function gfm(turndownService: TurndownService): void; + export function strikethrough(turndownService: TurndownService): void; + export function tables(turndownService: TurndownService): void; + export function taskListItems(turndownService: TurndownService): void; +}