Compare commits
2 Commits
fix/pi-ski
...
fix/toolin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cd6c83b9b | ||
|
|
b0b2c20da0 |
@@ -5,39 +5,10 @@ Tool suites live at `~/.config/mosaic/tools/<suite>/`. This is the index only.
|
|||||||
read it (or the relevant service guide) when your task actually touches that service.
|
read it (or the relevant service guide) when your task actually touches that service.
|
||||||
Project-specific tooling belongs in the project's `AGENTS.md`, not here.
|
Project-specific tooling belongs in the project's `AGENTS.md`, not here.
|
||||||
|
|
||||||
## ⚡ Most-used fleet tools (reach for these FIRST — don't hand-roll)
|
|
||||||
|
|
||||||
You are a Mosaic fleet agent. These cover the highest-frequency cross-agent and git-provider
|
|
||||||
tasks — use them before improvising with raw `tmux send-keys`, raw `tea`/`gh`/`glab`, or `curl`.
|
|
||||||
|
|
||||||
**1. Message another agent** → `tools/tmux/agent-send.sh` (NOT raw `tmux send-keys`):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
tools/tmux/agent-send.sh -s <target-session> -m "message" # or -f <file> to send a file's contents
|
|
||||||
```
|
|
||||||
|
|
||||||
The coordinator session is `mos-claude` — send status, findings, and questions there.
|
|
||||||
|
|
||||||
**2. Issues / PRs / milestones** → `tools/git/*.sh` wrappers (before raw `tea`/`gh`/`glab`):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
tools/git/pr-create.sh ... tools/git/issue-create.sh ... tools/git/pr-merge.sh ...
|
|
||||||
tools/git/ci-queue-wait.sh --purpose push|merge # REQUIRED before any push/merge
|
|
||||||
```
|
|
||||||
|
|
||||||
**GITEA_LOGIN gotcha** — the wrappers default to login `mosaicstack`; on a USC repo that fails with
|
|
||||||
`gitea / Error: GetUserByName ... not found`. Pick the login from the repo's `origin` host first:
|
|
||||||
|
|
||||||
| origin host | login |
|
|
||||||
| --------------------- | ---------------------------------------- |
|
|
||||||
| `git.uscllc.com` | `export GITEA_LOGIN=usc` |
|
|
||||||
| `git.mosaicstack.dev` | default `mosaicstack` (no export needed) |
|
|
||||||
|
|
||||||
## Suites (use wrappers first)
|
## Suites (use wrappers first)
|
||||||
|
|
||||||
| Suite | Path | Purpose |
|
| Suite | Path | Purpose |
|
||||||
| ---------- | ------------------------------------------------ | ------------------------------------------------------------------------ |
|
| ---------- | ------------------------------------------------ | ------------------------------------------------------------------------ |
|
||||||
| tmux | `tools/tmux/agent-send.sh` | inter-agent messaging (see "Most-used" above) |
|
|
||||||
| git | `tools/git/*.sh` | issues, PRs, milestones, CI queue guard (platform-auto-detected) |
|
| git | `tools/git/*.sh` | issues, PRs, milestones, CI queue guard (platform-auto-detected) |
|
||||||
| woodpecker | `tools/woodpecker/*.sh` | CI pipelines (`-a mosaic`\|`usc`; match git remote host) |
|
| woodpecker | `tools/woodpecker/*.sh` | CI pipelines (`-a mosaic`\|`usc`; match git remote host) |
|
||||||
| portainer | `tools/portainer/*.sh` | Docker Swarm stacks (status/redeploy/list) |
|
| portainer | `tools/portainer/*.sh` | Docker Swarm stacks (status/redeploy/list) |
|
||||||
|
|||||||
@@ -29,21 +29,7 @@ Pi supports `--models` for Ctrl+P model cycling during a session. Use cheaper mo
|
|||||||
|
|
||||||
### Skills
|
### Skills
|
||||||
|
|
||||||
By default the launcher starts Pi with `--no-skills` to keep startup context small, then
|
Mosaic skills are loaded natively via Pi's `--skill` flag. Skills are discovered from:
|
||||||
force-loads a small set of fleet-critical skills via explicit `--skill` flags (an explicit
|
|
||||||
`--skill` overrides `--no-skills` for that path). The default forced set is `mosaic-tools`
|
|
||||||
(the must-use `~/.config/mosaic/tools/` cheatsheet: inter-agent messaging + git wrappers).
|
|
||||||
|
|
||||||
Tune skill loading with environment variables:
|
|
||||||
|
|
||||||
- `MOSAIC_PI_FORCE_SKILLS` — colon-separated skill dir names to force-load (default: `mosaic-tools`;
|
|
||||||
set to an empty string to disable force-loading). Missing skills are skipped silently.
|
|
||||||
- `MOSAIC_PI_SKILL_MODE=all` — link every skill found in `~/.config/mosaic/{skills,skills-local}/`
|
|
||||||
(full catalog; larger context).
|
|
||||||
- `MOSAIC_PI_SKILL_MODE=discover` — let Pi discover skills natively (no `--no-skills`), still
|
|
||||||
force-loading the fleet set on top.
|
|
||||||
|
|
||||||
Skills are discovered from:
|
|
||||||
|
|
||||||
- `~/.config/mosaic/skills/` (Mosaic global skills)
|
- `~/.config/mosaic/skills/` (Mosaic global skills)
|
||||||
- `~/.pi/agent/skills/` (Pi global skills)
|
- `~/.pi/agent/skills/` (Pi global skills)
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
2. Do NOT ask for routine confirmation before required push/merge/issue-close/release/tag actions.
|
2. Do NOT ask for routine confirmation before required push/merge/issue-close/release/tag actions.
|
||||||
3. Completion is forbidden at PR-open stage.
|
3. Completion is forbidden at PR-open stage.
|
||||||
4. Completion requires merged PR to `main` + terminal green CI + linked issue/internal task closed.
|
4. Completion requires merged PR to `main` + terminal green CI + linked issue/internal task closed.
|
||||||
5. Before push or merge, run queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
5. Before push or merge, run queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
||||||
6. For issue/PR/milestone operations, use Mosaic wrappers first (`~/.config/mosaic/tools/git/*.sh`).
|
6. For issue/PR/milestone operations, use Mosaic wrappers first (`~/.config/mosaic/rails/git/*.sh`).
|
||||||
7. If any required wrapper command fails: report `blocked` with the exact failed wrapper command and stop.
|
7. If any required wrapper command fails: report `blocked` with the exact failed wrapper command and stop.
|
||||||
8. Do NOT stop at "PR created" and do NOT ask "should I merge?" for routine flow.
|
8. Do NOT stop at "PR created" and do NOT ask "should I merge?" for routine flow.
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ ${QUALITY_GATES}
|
|||||||
2. If external git provider is available (Gitea/GitHub/GitLab), create/update issue(s) before coding and map them in `docs/TASKS.md`.
|
2. If external git provider is available (Gitea/GitHub/GitLab), create/update issue(s) before coding and map them in `docs/TASKS.md`.
|
||||||
3. If no external provider is available, use internal refs in `docs/TASKS.md` (example: `TASKS:T1`).
|
3. If no external provider is available, use internal refs in `docs/TASKS.md` (example: `TASKS:T1`).
|
||||||
4. Keep `docs/TASKS.md` status in sync with actual progress until completion.
|
4. Keep `docs/TASKS.md` status in sync with actual progress until completion.
|
||||||
5. For issue/PR/milestone actions, detect platform and use `~/.config/mosaic/tools/git/*.sh` wrappers first (no raw `gh`/`tea`/`glab` as first choice).
|
5. For issue/PR/milestone actions, detect platform and use `~/.config/mosaic/rails/git/*.sh` wrappers first (no raw `gh`/`tea`/`glab` as first choice).
|
||||||
6. If wrapper-driven merge/CI/issue-closure fails, report blocker with the exact failed wrapper command and stop (do not claim completion).
|
6. If wrapper-driven merge/CI/issue-closure fails, report blocker with the exact failed wrapper command and stop (do not claim completion).
|
||||||
|
|
||||||
## Documentation Contract
|
## Documentation Contract
|
||||||
@@ -88,7 +88,7 @@ Reference:
|
|||||||
5. Do not mark implementation complete until PR is merged.
|
5. Do not mark implementation complete until PR is merged.
|
||||||
6. Do not mark implementation complete until CI/pipeline status is terminal green.
|
6. Do not mark implementation complete until CI/pipeline status is terminal green.
|
||||||
7. Close linked issues/tasks only after merge + green CI.
|
7. Close linked issues/tasks only after merge + green CI.
|
||||||
8. Before push or merge, run CI queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
8. Before push or merge, run CI queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
||||||
|
|
||||||
## Container Release Strategy (When Applicable)
|
## Container Release Strategy (When Applicable)
|
||||||
|
|
||||||
@@ -138,8 +138,8 @@ When completing an orchestrated task:
|
|||||||
### Post-Coding Review
|
### Post-Coding Review
|
||||||
After implementing changes, code review is REQUIRED for any source-code modification.
|
After implementing changes, code review is REQUIRED for any source-code modification.
|
||||||
For orchestrated tasks, the orchestrator will run:
|
For orchestrated tasks, the orchestrator will run:
|
||||||
1. **Codex code review** — `~/.config/mosaic/tools/codex/codex-code-review.sh --uncommitted`
|
1. **Codex code review** — `~/.config/mosaic/rails/codex/codex-code-review.sh --uncommitted`
|
||||||
2. **Codex security review** — `~/.config/mosaic/tools/codex/codex-security-review.sh --uncommitted`
|
2. **Codex security review** — `~/.config/mosaic/rails/codex/codex-security-review.sh --uncommitted`
|
||||||
3. If blockers/critical findings: remediation task created
|
3. If blockers/critical findings: remediation task created
|
||||||
4. If clean: task marked done
|
4. If clean: task marked done
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ ${QUALITY_GATES}
|
|||||||
## Issue Tracking
|
## Issue Tracking
|
||||||
|
|
||||||
Use external git provider issues when available. If no external provider exists, `docs/TASKS.md` is the canonical tracker for tasks, milestones, and issue-equivalent work.
|
Use external git provider issues when available. If no external provider exists, `docs/TASKS.md` is the canonical tracker for tasks, milestones, and issue-equivalent work.
|
||||||
For issue/PR/milestone operations, detect platform and use `~/.config/mosaic/tools/git/*.sh` wrappers first; do not use raw `gh`/`tea`/`glab` as first choice.
|
For issue/PR/milestone operations, detect platform and use `~/.config/mosaic/rails/git/*.sh` wrappers first; do not use raw `gh`/`tea`/`glab` as first choice.
|
||||||
If wrapper-driven merge/CI/issue-closure fails, report blocker with exact failed wrapper command and stop.
|
If wrapper-driven merge/CI/issue-closure fails, report blocker with exact failed wrapper command and stop.
|
||||||
Do NOT stop at "PR created" and do NOT ask "should I merge?" or "should I close the issue?" for routine delivery flow.
|
Do NOT stop at "PR created" and do NOT ask "should I merge?" or "should I close the issue?" for routine delivery flow.
|
||||||
|
|
||||||
@@ -147,9 +147,9 @@ Do NOT stop at "PR created" and do NOT ask "should I merge?" or "should I close
|
|||||||
5. Ensure `docs/PRD.md` or `docs/PRD.json` exists and is current before coding.
|
5. Ensure `docs/PRD.md` or `docs/PRD.json` exists and is current before coding.
|
||||||
6. Create scratchpad: `docs/scratchpads/{task-id}-{short-name}.md` and include issue/internal ref.
|
6. Create scratchpad: `docs/scratchpads/{task-id}-{short-name}.md` and include issue/internal ref.
|
||||||
7. Update `docs/TASKS.md` status + issue/internal ref before coding.
|
7. Update `docs/TASKS.md` status + issue/internal ref before coding.
|
||||||
8. Before push, run CI queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push -B main`.
|
8. Before push, run CI queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose push -B main`.
|
||||||
9. Open PR to `main` for delivery changes (no direct push to `main`).
|
9. Open PR to `main` for delivery changes (no direct push to `main`).
|
||||||
10. Before merge, run CI queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose merge -B main`.
|
10. Before merge, run CI queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose merge -B main`.
|
||||||
11. Merge PRs that pass required checks and review gates with squash strategy only.
|
11. Merge PRs that pass required checks and review gates with squash strategy only.
|
||||||
12. Reference issues/internal refs in commits (`Fixes #123`, `Refs #123`, or `Refs TASKS:T1`).
|
12. Reference issues/internal refs in commits (`Fixes #123`, `Refs #123`, or `Refs TASKS:T1`).
|
||||||
13. Close issue/internal task only after testing and documentation gates pass, PR merge is complete, and CI/pipeline status is terminal green.
|
13. Close issue/internal task only after testing and documentation gates pass, PR merge is complete, and CI/pipeline status is terminal green.
|
||||||
@@ -176,10 +176,10 @@ Run independent reviews:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Code quality review (Codex)
|
# Code quality review (Codex)
|
||||||
~/.config/mosaic/tools/codex/codex-code-review.sh --uncommitted
|
~/.config/mosaic/rails/codex/codex-code-review.sh --uncommitted
|
||||||
|
|
||||||
# Security review (Codex)
|
# Security review (Codex)
|
||||||
~/.config/mosaic/tools/codex/codex-security-review.sh --uncommitted
|
~/.config/mosaic/rails/codex/codex-security-review.sh --uncommitted
|
||||||
```
|
```
|
||||||
|
|
||||||
**Fallback:** If Codex is unavailable, use Claude's built-in review skills.
|
**Fallback:** If Codex is unavailable, use Claude's built-in review skills.
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
2. Do NOT ask for routine confirmation before required push/merge/issue-close/release/tag actions.
|
2. Do NOT ask for routine confirmation before required push/merge/issue-close/release/tag actions.
|
||||||
3. Completion is forbidden at PR-open stage.
|
3. Completion is forbidden at PR-open stage.
|
||||||
4. Completion requires merged PR to `main` + terminal green CI + linked issue/internal task closed.
|
4. Completion requires merged PR to `main` + terminal green CI + linked issue/internal task closed.
|
||||||
5. Before push or merge, run queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
5. Before push or merge, run queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
||||||
6. For issue/PR/milestone operations, use Mosaic wrappers first (`~/.config/mosaic/tools/git/*.sh`).
|
6. For issue/PR/milestone operations, use Mosaic wrappers first (`~/.config/mosaic/rails/git/*.sh`).
|
||||||
7. If any required wrapper command fails: report `blocked` with the exact failed wrapper command and stop.
|
7. If any required wrapper command fails: report `blocked` with the exact failed wrapper command and stop.
|
||||||
8. Do NOT stop at "PR created" and do NOT ask "should I merge?" for routine flow.
|
8. Do NOT stop at "PR created" and do NOT ask "should I merge?" for routine flow.
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ ruff check . && mypy . && pytest tests/
|
|||||||
2. If external git provider is available (Gitea/GitHub/GitLab), create/update issue(s) before coding and map them in `docs/TASKS.md`.
|
2. If external git provider is available (Gitea/GitHub/GitLab), create/update issue(s) before coding and map them in `docs/TASKS.md`.
|
||||||
3. If no external provider is available, use internal refs in `docs/TASKS.md` (example: `TASKS:T1`).
|
3. If no external provider is available, use internal refs in `docs/TASKS.md` (example: `TASKS:T1`).
|
||||||
4. Keep `docs/TASKS.md` status in sync with actual progress until completion.
|
4. Keep `docs/TASKS.md` status in sync with actual progress until completion.
|
||||||
5. For issue/PR/milestone actions, detect platform and use `~/.config/mosaic/tools/git/*.sh` wrappers first (no raw `gh`/`tea`/`glab` as first choice).
|
5. For issue/PR/milestone actions, detect platform and use `~/.config/mosaic/rails/git/*.sh` wrappers first (no raw `gh`/`tea`/`glab` as first choice).
|
||||||
6. If wrapper-driven merge/CI/issue-closure fails, report blocker with the exact failed wrapper command and stop (do not claim completion).
|
6. If wrapper-driven merge/CI/issue-closure fails, report blocker with the exact failed wrapper command and stop (do not claim completion).
|
||||||
|
|
||||||
## Documentation Contract
|
## Documentation Contract
|
||||||
@@ -97,7 +97,7 @@ Reference:
|
|||||||
5. Do not mark implementation complete until PR is merged.
|
5. Do not mark implementation complete until PR is merged.
|
||||||
6. Do not mark implementation complete until CI/pipeline status is terminal green.
|
6. Do not mark implementation complete until CI/pipeline status is terminal green.
|
||||||
7. Close linked issues/tasks only after merge + green CI.
|
7. Close linked issues/tasks only after merge + green CI.
|
||||||
8. Before push or merge, run CI queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
8. Before push or merge, run CI queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
||||||
|
|
||||||
|
|
||||||
## Container Release Strategy (When Applicable)
|
## Container Release Strategy (When Applicable)
|
||||||
@@ -139,8 +139,8 @@ Use `${TASK_PREFIX}` for orchestrated tasks (e.g., `${TASK_PREFIX}-SEC-001`).
|
|||||||
### Post-Coding Review
|
### Post-Coding Review
|
||||||
After implementing changes, code review is REQUIRED for any source-code modification.
|
After implementing changes, code review is REQUIRED for any source-code modification.
|
||||||
For orchestrated tasks, the orchestrator will run:
|
For orchestrated tasks, the orchestrator will run:
|
||||||
1. **Codex code review** — `~/.config/mosaic/tools/codex/codex-code-review.sh --uncommitted`
|
1. **Codex code review** — `~/.config/mosaic/rails/codex/codex-code-review.sh --uncommitted`
|
||||||
2. **Codex security review** — `~/.config/mosaic/tools/codex/codex-security-review.sh --uncommitted`
|
2. **Codex security review** — `~/.config/mosaic/rails/codex/codex-security-review.sh --uncommitted`
|
||||||
3. If blockers/critical findings: remediation task created
|
3. If blockers/critical findings: remediation task created
|
||||||
4. If clean: task marked done
|
4. If clean: task marked done
|
||||||
|
|
||||||
|
|||||||
@@ -159,10 +159,10 @@ Run independent reviews:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Code quality review (Codex)
|
# Code quality review (Codex)
|
||||||
~/.config/mosaic/tools/codex/codex-code-review.sh --uncommitted
|
~/.config/mosaic/rails/codex/codex-code-review.sh --uncommitted
|
||||||
|
|
||||||
# Security review (Codex)
|
# Security review (Codex)
|
||||||
~/.config/mosaic/tools/codex/codex-security-review.sh --uncommitted
|
~/.config/mosaic/rails/codex/codex-security-review.sh --uncommitted
|
||||||
```
|
```
|
||||||
|
|
||||||
See `~/.config/mosaic/guides/CODE-REVIEW.md` for the full review checklist.
|
See `~/.config/mosaic/guides/CODE-REVIEW.md` for the full review checklist.
|
||||||
@@ -186,7 +186,7 @@ See `~/.config/mosaic/guides/DOCUMENTATION.md` for required documentation delive
|
|||||||
## Issue Tracking
|
## Issue Tracking
|
||||||
|
|
||||||
Use external git provider issues when available. If no external provider exists, `docs/TASKS.md` is the canonical tracker for tasks, milestones, and issue-equivalent work.
|
Use external git provider issues when available. If no external provider exists, `docs/TASKS.md` is the canonical tracker for tasks, milestones, and issue-equivalent work.
|
||||||
For issue/PR/milestone operations, detect platform and use `~/.config/mosaic/tools/git/*.sh` wrappers first; do not use raw `gh`/`tea`/`glab` as first choice.
|
For issue/PR/milestone operations, detect platform and use `~/.config/mosaic/rails/git/*.sh` wrappers first; do not use raw `gh`/`tea`/`glab` as first choice.
|
||||||
If wrapper-driven merge/CI/issue-closure fails, report blocker with exact failed wrapper command and stop.
|
If wrapper-driven merge/CI/issue-closure fails, report blocker with exact failed wrapper command and stop.
|
||||||
Do NOT stop at "PR created" and do NOT ask "should I merge?" or "should I close the issue?" for routine delivery flow.
|
Do NOT stop at "PR created" and do NOT ask "should I merge?" or "should I close the issue?" for routine delivery flow.
|
||||||
|
|
||||||
@@ -198,9 +198,9 @@ Do NOT stop at "PR created" and do NOT ask "should I merge?" or "should I close
|
|||||||
5. Ensure `docs/PRD.md` or `docs/PRD.json` exists and is current before coding.
|
5. Ensure `docs/PRD.md` or `docs/PRD.json` exists and is current before coding.
|
||||||
6. Create scratchpad: `docs/scratchpads/{task-id}-{short-name}.md` and include issue/internal ref.
|
6. Create scratchpad: `docs/scratchpads/{task-id}-{short-name}.md` and include issue/internal ref.
|
||||||
7. Update `docs/TASKS.md` status + issue/internal ref before coding.
|
7. Update `docs/TASKS.md` status + issue/internal ref before coding.
|
||||||
8. Before push, run CI queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push -B main`.
|
8. Before push, run CI queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose push -B main`.
|
||||||
9. Open PR to `main` for delivery changes (no direct push to `main`).
|
9. Open PR to `main` for delivery changes (no direct push to `main`).
|
||||||
10. Before merge, run CI queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose merge -B main`.
|
10. Before merge, run CI queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose merge -B main`.
|
||||||
11. Merge PRs that pass required checks and review gates with squash strategy only.
|
11. Merge PRs that pass required checks and review gates with squash strategy only.
|
||||||
12. Reference issues/internal refs in commits (`Fixes #123`, `Refs #123`, or `Refs TASKS:T1`).
|
12. Reference issues/internal refs in commits (`Fixes #123`, `Refs #123`, or `Refs TASKS:T1`).
|
||||||
13. Close issue/internal task only after testing and documentation gates pass, PR merge is complete, and CI/pipeline status is terminal green.
|
13. Close issue/internal task only after testing and documentation gates pass, PR merge is complete, and CI/pipeline status is terminal green.
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
2. Do NOT ask for routine confirmation before required push/merge/issue-close/release/tag actions.
|
2. Do NOT ask for routine confirmation before required push/merge/issue-close/release/tag actions.
|
||||||
3. Completion is forbidden at PR-open stage.
|
3. Completion is forbidden at PR-open stage.
|
||||||
4. Completion requires merged PR to `main` + terminal green CI + linked issue/internal task closed.
|
4. Completion requires merged PR to `main` + terminal green CI + linked issue/internal task closed.
|
||||||
5. Before push or merge, run queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
5. Before push or merge, run queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
||||||
6. For issue/PR/milestone operations, use Mosaic wrappers first (`~/.config/mosaic/tools/git/*.sh`).
|
6. For issue/PR/milestone operations, use Mosaic wrappers first (`~/.config/mosaic/rails/git/*.sh`).
|
||||||
7. If any required wrapper command fails: report `blocked` with the exact failed wrapper command and stop.
|
7. If any required wrapper command fails: report `blocked` with the exact failed wrapper command and stop.
|
||||||
8. Do NOT stop at "PR created" and do NOT ask "should I merge?" for routine flow.
|
8. Do NOT stop at "PR created" and do NOT ask "should I merge?" for routine flow.
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ pnpm typecheck && pnpm lint && pnpm test
|
|||||||
2. If external git provider is available (Gitea/GitHub/GitLab), create/update issue(s) before coding and map them in `docs/TASKS.md`.
|
2. If external git provider is available (Gitea/GitHub/GitLab), create/update issue(s) before coding and map them in `docs/TASKS.md`.
|
||||||
3. If no external provider is available, use internal refs in `docs/TASKS.md` (example: `TASKS:T1`).
|
3. If no external provider is available, use internal refs in `docs/TASKS.md` (example: `TASKS:T1`).
|
||||||
4. Keep `docs/TASKS.md` status in sync with actual progress until completion.
|
4. Keep `docs/TASKS.md` status in sync with actual progress until completion.
|
||||||
5. For issue/PR/milestone actions, detect platform and use `~/.config/mosaic/tools/git/*.sh` wrappers first (no raw `gh`/`tea`/`glab` as first choice).
|
5. For issue/PR/milestone actions, detect platform and use `~/.config/mosaic/rails/git/*.sh` wrappers first (no raw `gh`/`tea`/`glab` as first choice).
|
||||||
6. If wrapper-driven merge/CI/issue-closure fails, report blocker with the exact failed wrapper command and stop (do not claim completion).
|
6. If wrapper-driven merge/CI/issue-closure fails, report blocker with the exact failed wrapper command and stop (do not claim completion).
|
||||||
|
|
||||||
## Documentation Contract
|
## Documentation Contract
|
||||||
@@ -101,7 +101,7 @@ Reference:
|
|||||||
5. Do not mark implementation complete until PR is merged.
|
5. Do not mark implementation complete until PR is merged.
|
||||||
6. Do not mark implementation complete until CI/pipeline status is terminal green.
|
6. Do not mark implementation complete until CI/pipeline status is terminal green.
|
||||||
7. Close linked issues/tasks only after merge + green CI.
|
7. Close linked issues/tasks only after merge + green CI.
|
||||||
8. Before push or merge, run CI queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
8. Before push or merge, run CI queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
||||||
|
|
||||||
|
|
||||||
## Container Release Strategy (When Applicable)
|
## Container Release Strategy (When Applicable)
|
||||||
@@ -143,8 +143,8 @@ Use `${TASK_PREFIX}` for orchestrated tasks (e.g., `${TASK_PREFIX}-SEC-001`).
|
|||||||
### Post-Coding Review
|
### Post-Coding Review
|
||||||
After implementing changes, code review is REQUIRED for any source-code modification.
|
After implementing changes, code review is REQUIRED for any source-code modification.
|
||||||
For orchestrated tasks, the orchestrator will run:
|
For orchestrated tasks, the orchestrator will run:
|
||||||
1. **Codex code review** — `~/.config/mosaic/tools/codex/codex-code-review.sh --uncommitted`
|
1. **Codex code review** — `~/.config/mosaic/rails/codex/codex-code-review.sh --uncommitted`
|
||||||
2. **Codex security review** — `~/.config/mosaic/tools/codex/codex-security-review.sh --uncommitted`
|
2. **Codex security review** — `~/.config/mosaic/rails/codex/codex-security-review.sh --uncommitted`
|
||||||
3. If blockers/critical findings: remediation task created
|
3. If blockers/critical findings: remediation task created
|
||||||
4. If clean: task marked done
|
4. If clean: task marked done
|
||||||
|
|
||||||
|
|||||||
@@ -191,10 +191,10 @@ Run independent reviews:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Code quality review (Codex)
|
# Code quality review (Codex)
|
||||||
~/.config/mosaic/tools/codex/codex-code-review.sh --uncommitted
|
~/.config/mosaic/rails/codex/codex-code-review.sh --uncommitted
|
||||||
|
|
||||||
# Security review (Codex)
|
# Security review (Codex)
|
||||||
~/.config/mosaic/tools/codex/codex-security-review.sh --uncommitted
|
~/.config/mosaic/rails/codex/codex-security-review.sh --uncommitted
|
||||||
```
|
```
|
||||||
|
|
||||||
See `~/.config/mosaic/guides/CODE-REVIEW.md` for the full review checklist.
|
See `~/.config/mosaic/guides/CODE-REVIEW.md` for the full review checklist.
|
||||||
@@ -218,7 +218,7 @@ See `~/.config/mosaic/guides/DOCUMENTATION.md` for required documentation delive
|
|||||||
## Issue Tracking
|
## Issue Tracking
|
||||||
|
|
||||||
Use external git provider issues when available. If no external provider exists, `docs/TASKS.md` is the canonical tracker for tasks, milestones, and issue-equivalent work.
|
Use external git provider issues when available. If no external provider exists, `docs/TASKS.md` is the canonical tracker for tasks, milestones, and issue-equivalent work.
|
||||||
For issue/PR/milestone operations, detect platform and use `~/.config/mosaic/tools/git/*.sh` wrappers first; do not use raw `gh`/`tea`/`glab` as first choice.
|
For issue/PR/milestone operations, detect platform and use `~/.config/mosaic/rails/git/*.sh` wrappers first; do not use raw `gh`/`tea`/`glab` as first choice.
|
||||||
If wrapper-driven merge/CI/issue-closure fails, report blocker with exact failed wrapper command and stop.
|
If wrapper-driven merge/CI/issue-closure fails, report blocker with exact failed wrapper command and stop.
|
||||||
Do NOT stop at "PR created" and do NOT ask "should I merge?" or "should I close the issue?" for routine delivery flow.
|
Do NOT stop at "PR created" and do NOT ask "should I merge?" or "should I close the issue?" for routine delivery flow.
|
||||||
|
|
||||||
@@ -230,9 +230,9 @@ Do NOT stop at "PR created" and do NOT ask "should I merge?" or "should I close
|
|||||||
5. Ensure `docs/PRD.md` or `docs/PRD.json` exists and is current before coding.
|
5. Ensure `docs/PRD.md` or `docs/PRD.json` exists and is current before coding.
|
||||||
6. Create scratchpad: `docs/scratchpads/{task-id}-{short-name}.md` and include issue/internal ref.
|
6. Create scratchpad: `docs/scratchpads/{task-id}-{short-name}.md` and include issue/internal ref.
|
||||||
7. Update `docs/TASKS.md` status + issue/internal ref before coding.
|
7. Update `docs/TASKS.md` status + issue/internal ref before coding.
|
||||||
8. Before push, run CI queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push -B main`.
|
8. Before push, run CI queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose push -B main`.
|
||||||
9. Open PR to `main` for delivery changes (no direct push to `main`).
|
9. Open PR to `main` for delivery changes (no direct push to `main`).
|
||||||
10. Before merge, run CI queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose merge -B main`.
|
10. Before merge, run CI queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose merge -B main`.
|
||||||
11. Merge PRs that pass required checks and review gates with squash strategy only.
|
11. Merge PRs that pass required checks and review gates with squash strategy only.
|
||||||
12. Reference issues/internal refs in commits (`Fixes #123`, `Refs #123`, or `Refs TASKS:T1`).
|
12. Reference issues/internal refs in commits (`Fixes #123`, `Refs #123`, or `Refs TASKS:T1`).
|
||||||
13. Close issue/internal task only after testing and documentation gates pass, PR merge is complete, and CI/pipeline status is terminal green.
|
13. Close issue/internal task only after testing and documentation gates pass, PR merge is complete, and CI/pipeline status is terminal green.
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
2. Do NOT ask for routine confirmation before required push/merge/issue-close/release/tag actions.
|
2. Do NOT ask for routine confirmation before required push/merge/issue-close/release/tag actions.
|
||||||
3. Completion is forbidden at PR-open stage.
|
3. Completion is forbidden at PR-open stage.
|
||||||
4. Completion requires merged PR to `main` + terminal green CI + linked issue/internal task closed.
|
4. Completion requires merged PR to `main` + terminal green CI + linked issue/internal task closed.
|
||||||
5. Before push or merge, run queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
5. Before push or merge, run queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
||||||
6. For issue/PR/milestone operations, use Mosaic wrappers first (`~/.config/mosaic/tools/git/*.sh`).
|
6. For issue/PR/milestone operations, use Mosaic wrappers first (`~/.config/mosaic/rails/git/*.sh`).
|
||||||
7. If any required wrapper command fails: report `blocked` with the exact failed wrapper command and stop.
|
7. If any required wrapper command fails: report `blocked` with the exact failed wrapper command and stop.
|
||||||
8. Do NOT stop at "PR created" and do NOT ask "should I merge?" for routine flow.
|
8. Do NOT stop at "PR created" and do NOT ask "should I merge?" for routine flow.
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ uv run ruff check src/ tests/ && uv run ruff format --check src/ && uv run mypy
|
|||||||
2. If external git provider is available (Gitea/GitHub/GitLab), create/update issue(s) before coding and map them in `docs/TASKS.md`.
|
2. If external git provider is available (Gitea/GitHub/GitLab), create/update issue(s) before coding and map them in `docs/TASKS.md`.
|
||||||
3. If no external provider is available, use internal refs in `docs/TASKS.md` (example: `TASKS:T1`).
|
3. If no external provider is available, use internal refs in `docs/TASKS.md` (example: `TASKS:T1`).
|
||||||
4. Keep `docs/TASKS.md` status in sync with actual progress until completion.
|
4. Keep `docs/TASKS.md` status in sync with actual progress until completion.
|
||||||
5. For issue/PR/milestone actions, detect platform and use `~/.config/mosaic/tools/git/*.sh` wrappers first (no raw `gh`/`tea`/`glab` as first choice).
|
5. For issue/PR/milestone actions, detect platform and use `~/.config/mosaic/rails/git/*.sh` wrappers first (no raw `gh`/`tea`/`glab` as first choice).
|
||||||
6. If wrapper-driven merge/CI/issue-closure fails, report blocker with the exact failed wrapper command and stop (do not claim completion).
|
6. If wrapper-driven merge/CI/issue-closure fails, report blocker with the exact failed wrapper command and stop (do not claim completion).
|
||||||
|
|
||||||
## Documentation Contract
|
## Documentation Contract
|
||||||
@@ -87,7 +87,7 @@ Reference:
|
|||||||
5. Do not mark implementation complete until PR is merged.
|
5. Do not mark implementation complete until PR is merged.
|
||||||
6. Do not mark implementation complete until CI/pipeline status is terminal green.
|
6. Do not mark implementation complete until CI/pipeline status is terminal green.
|
||||||
7. Close linked issues/tasks only after merge + green CI.
|
7. Close linked issues/tasks only after merge + green CI.
|
||||||
8. Before push or merge, run CI queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
8. Before push or merge, run CI queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
||||||
|
|
||||||
## Container Release Strategy (When Applicable)
|
## Container Release Strategy (When Applicable)
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ uv run ruff check src/ tests/ && uv run ruff format --check src/ && uv run mypy
|
|||||||
## Issue Tracking
|
## Issue Tracking
|
||||||
|
|
||||||
Use external git provider issues when available. If no external provider exists, `docs/TASKS.md` is the canonical tracker for tasks, milestones, and issue-equivalent work.
|
Use external git provider issues when available. If no external provider exists, `docs/TASKS.md` is the canonical tracker for tasks, milestones, and issue-equivalent work.
|
||||||
For issue/PR/milestone operations, detect platform and use `~/.config/mosaic/tools/git/*.sh` wrappers first; do not use raw `gh`/`tea`/`glab` as first choice.
|
For issue/PR/milestone operations, detect platform and use `~/.config/mosaic/rails/git/*.sh` wrappers first; do not use raw `gh`/`tea`/`glab` as first choice.
|
||||||
If wrapper-driven merge/CI/issue-closure fails, report blocker with exact failed wrapper command and stop.
|
If wrapper-driven merge/CI/issue-closure fails, report blocker with exact failed wrapper command and stop.
|
||||||
Do NOT stop at "PR created" and do NOT ask "should I merge?" or "should I close the issue?" for routine delivery flow.
|
Do NOT stop at "PR created" and do NOT ask "should I merge?" or "should I close the issue?" for routine delivery flow.
|
||||||
|
|
||||||
@@ -146,9 +146,9 @@ Do NOT stop at "PR created" and do NOT ask "should I merge?" or "should I close
|
|||||||
5. Ensure `docs/PRD.md` or `docs/PRD.json` exists and is current before coding.
|
5. Ensure `docs/PRD.md` or `docs/PRD.json` exists and is current before coding.
|
||||||
6. Create scratchpad: `docs/scratchpads/{task-id}-{short-name}.md` and include issue/internal ref.
|
6. Create scratchpad: `docs/scratchpads/{task-id}-{short-name}.md` and include issue/internal ref.
|
||||||
7. Update `docs/TASKS.md` status + issue/internal ref before coding.
|
7. Update `docs/TASKS.md` status + issue/internal ref before coding.
|
||||||
8. Before push, run CI queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push -B main`.
|
8. Before push, run CI queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose push -B main`.
|
||||||
9. Open PR to `main` for delivery changes (no direct push to `main`).
|
9. Open PR to `main` for delivery changes (no direct push to `main`).
|
||||||
10. Before merge, run CI queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose merge -B main`.
|
10. Before merge, run CI queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose merge -B main`.
|
||||||
11. Merge PRs that pass required checks and review gates with squash strategy only.
|
11. Merge PRs that pass required checks and review gates with squash strategy only.
|
||||||
12. Reference issues/internal refs in commits (`Fixes #123`, `Refs #123`, or `Refs TASKS:T1`).
|
12. Reference issues/internal refs in commits (`Fixes #123`, `Refs #123`, or `Refs TASKS:T1`).
|
||||||
13. Close issue/internal task only after testing and documentation gates pass, PR merge is complete, and CI/pipeline status is terminal green.
|
13. Close issue/internal task only after testing and documentation gates pass, PR merge is complete, and CI/pipeline status is terminal green.
|
||||||
@@ -171,8 +171,8 @@ If you modify source code, independent code review is REQUIRED before completion
|
|||||||
Run independent reviews:
|
Run independent reviews:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
~/.config/mosaic/tools/codex/codex-code-review.sh --uncommitted
|
~/.config/mosaic/rails/codex/codex-code-review.sh --uncommitted
|
||||||
~/.config/mosaic/tools/codex/codex-security-review.sh --uncommitted
|
~/.config/mosaic/rails/codex/codex-security-review.sh --uncommitted
|
||||||
```
|
```
|
||||||
|
|
||||||
See `~/.config/mosaic/guides/CODE-REVIEW.md` for the full review checklist.
|
See `~/.config/mosaic/guides/CODE-REVIEW.md` for the full review checklist.
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
2. Do NOT ask for routine confirmation before required push/merge/issue-close/release/tag actions.
|
2. Do NOT ask for routine confirmation before required push/merge/issue-close/release/tag actions.
|
||||||
3. Completion is forbidden at PR-open stage.
|
3. Completion is forbidden at PR-open stage.
|
||||||
4. Completion requires merged PR to `main` + terminal green CI + linked issue/internal task closed.
|
4. Completion requires merged PR to `main` + terminal green CI + linked issue/internal task closed.
|
||||||
5. Before push or merge, run queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
5. Before push or merge, run queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
||||||
6. For issue/PR/milestone operations, use Mosaic wrappers first (`~/.config/mosaic/tools/git/*.sh`).
|
6. For issue/PR/milestone operations, use Mosaic wrappers first (`~/.config/mosaic/rails/git/*.sh`).
|
||||||
7. If any required wrapper command fails: report `blocked` with the exact failed wrapper command and stop.
|
7. If any required wrapper command fails: report `blocked` with the exact failed wrapper command and stop.
|
||||||
8. Do NOT stop at "PR created" and do NOT ask "should I merge?" for routine flow.
|
8. Do NOT stop at "PR created" and do NOT ask "should I merge?" for routine flow.
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ uv run ruff check src/ tests/ && uv run ruff format --check src/ && uv run mypy
|
|||||||
2. If external git provider is available (Gitea/GitHub/GitLab), create/update issue(s) before coding and map them in `docs/TASKS.md`.
|
2. If external git provider is available (Gitea/GitHub/GitLab), create/update issue(s) before coding and map them in `docs/TASKS.md`.
|
||||||
3. If no external provider is available, use internal refs in `docs/TASKS.md` (example: `TASKS:T1`).
|
3. If no external provider is available, use internal refs in `docs/TASKS.md` (example: `TASKS:T1`).
|
||||||
4. Keep `docs/TASKS.md` status in sync with actual progress until completion.
|
4. Keep `docs/TASKS.md` status in sync with actual progress until completion.
|
||||||
5. For issue/PR/milestone actions, detect platform and use `~/.config/mosaic/tools/git/*.sh` wrappers first (no raw `gh`/`tea`/`glab` as first choice).
|
5. For issue/PR/milestone actions, detect platform and use `~/.config/mosaic/rails/git/*.sh` wrappers first (no raw `gh`/`tea`/`glab` as first choice).
|
||||||
6. If wrapper-driven merge/CI/issue-closure fails, report blocker with the exact failed wrapper command and stop (do not claim completion).
|
6. If wrapper-driven merge/CI/issue-closure fails, report blocker with the exact failed wrapper command and stop (do not claim completion).
|
||||||
|
|
||||||
## Documentation Contract
|
## Documentation Contract
|
||||||
@@ -84,7 +84,7 @@ Reference:
|
|||||||
5. Do not mark implementation complete until PR is merged.
|
5. Do not mark implementation complete until PR is merged.
|
||||||
6. Do not mark implementation complete until CI/pipeline status is terminal green.
|
6. Do not mark implementation complete until CI/pipeline status is terminal green.
|
||||||
7. Close linked issues/tasks only after merge + green CI.
|
7. Close linked issues/tasks only after merge + green CI.
|
||||||
8. Before push or merge, run CI queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
8. Before push or merge, run CI queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
||||||
|
|
||||||
## Container Release Strategy (When Applicable)
|
## Container Release Strategy (When Applicable)
|
||||||
|
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ uv run ruff check src/ tests/ && uv run ruff format --check src/ && uv run mypy
|
|||||||
## Issue Tracking
|
## Issue Tracking
|
||||||
|
|
||||||
Use external git provider issues when available. If no external provider exists, `docs/TASKS.md` is the canonical tracker for tasks, milestones, and issue-equivalent work.
|
Use external git provider issues when available. If no external provider exists, `docs/TASKS.md` is the canonical tracker for tasks, milestones, and issue-equivalent work.
|
||||||
For issue/PR/milestone operations, detect platform and use `~/.config/mosaic/tools/git/*.sh` wrappers first; do not use raw `gh`/`tea`/`glab` as first choice.
|
For issue/PR/milestone operations, detect platform and use `~/.config/mosaic/rails/git/*.sh` wrappers first; do not use raw `gh`/`tea`/`glab` as first choice.
|
||||||
If wrapper-driven merge/CI/issue-closure fails, report blocker with exact failed wrapper command and stop.
|
If wrapper-driven merge/CI/issue-closure fails, report blocker with exact failed wrapper command and stop.
|
||||||
Do NOT stop at "PR created" and do NOT ask "should I merge?" or "should I close the issue?" for routine delivery flow.
|
Do NOT stop at "PR created" and do NOT ask "should I merge?" or "should I close the issue?" for routine delivery flow.
|
||||||
|
|
||||||
@@ -136,9 +136,9 @@ Do NOT stop at "PR created" and do NOT ask "should I merge?" or "should I close
|
|||||||
5. Ensure `docs/PRD.md` or `docs/PRD.json` exists and is current before coding.
|
5. Ensure `docs/PRD.md` or `docs/PRD.json` exists and is current before coding.
|
||||||
6. Create scratchpad: `docs/scratchpads/{task-id}-{short-name}.md` and include issue/internal ref.
|
6. Create scratchpad: `docs/scratchpads/{task-id}-{short-name}.md` and include issue/internal ref.
|
||||||
7. Update `docs/TASKS.md` status + issue/internal ref before coding.
|
7. Update `docs/TASKS.md` status + issue/internal ref before coding.
|
||||||
8. Before push, run CI queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push -B main`.
|
8. Before push, run CI queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose push -B main`.
|
||||||
9. Open PR to `main` for delivery changes (no direct push to `main`).
|
9. Open PR to `main` for delivery changes (no direct push to `main`).
|
||||||
10. Before merge, run CI queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose merge -B main`.
|
10. Before merge, run CI queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose merge -B main`.
|
||||||
11. Merge PRs that pass required checks and review gates with squash strategy only.
|
11. Merge PRs that pass required checks and review gates with squash strategy only.
|
||||||
12. Reference issues/internal refs in commits (`Fixes #123`, `Refs #123`, or `Refs TASKS:T1`).
|
12. Reference issues/internal refs in commits (`Fixes #123`, `Refs #123`, or `Refs TASKS:T1`).
|
||||||
13. Close issue/internal task only after testing and documentation gates pass, PR merge is complete, and CI/pipeline status is terminal green.
|
13. Close issue/internal task only after testing and documentation gates pass, PR merge is complete, and CI/pipeline status is terminal green.
|
||||||
@@ -161,8 +161,8 @@ If you modify source code, independent code review is REQUIRED before completion
|
|||||||
Run independent reviews:
|
Run independent reviews:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
~/.config/mosaic/tools/codex/codex-code-review.sh --uncommitted
|
~/.config/mosaic/rails/codex/codex-code-review.sh --uncommitted
|
||||||
~/.config/mosaic/tools/codex/codex-security-review.sh --uncommitted
|
~/.config/mosaic/rails/codex/codex-security-review.sh --uncommitted
|
||||||
```
|
```
|
||||||
|
|
||||||
See `~/.config/mosaic/guides/CODE-REVIEW.md` for the full review checklist.
|
See `~/.config/mosaic/guides/CODE-REVIEW.md` for the full review checklist.
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
2. Do NOT ask for routine confirmation before required push/merge/issue-close/release/tag actions.
|
2. Do NOT ask for routine confirmation before required push/merge/issue-close/release/tag actions.
|
||||||
3. Completion is forbidden at PR-open stage.
|
3. Completion is forbidden at PR-open stage.
|
||||||
4. Completion requires merged PR to `main` + terminal green CI + linked issue/internal task closed.
|
4. Completion requires merged PR to `main` + terminal green CI + linked issue/internal task closed.
|
||||||
5. Before push or merge, run queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
5. Before push or merge, run queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
||||||
6. For issue/PR/milestone operations, use Mosaic wrappers first (`~/.config/mosaic/tools/git/*.sh`).
|
6. For issue/PR/milestone operations, use Mosaic wrappers first (`~/.config/mosaic/rails/git/*.sh`).
|
||||||
7. If any required wrapper command fails: report `blocked` with the exact failed wrapper command and stop.
|
7. If any required wrapper command fails: report `blocked` with the exact failed wrapper command and stop.
|
||||||
8. Do NOT stop at "PR created" and do NOT ask "should I merge?" for routine flow.
|
8. Do NOT stop at "PR created" and do NOT ask "should I merge?" for routine flow.
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ ${QUALITY_GATES}
|
|||||||
2. If external git provider is available (Gitea/GitHub/GitLab), create/update issue(s) before coding and map them in `docs/TASKS.md`.
|
2. If external git provider is available (Gitea/GitHub/GitLab), create/update issue(s) before coding and map them in `docs/TASKS.md`.
|
||||||
3. If no external provider is available, use internal refs in `docs/TASKS.md` (example: `TASKS:T1`).
|
3. If no external provider is available, use internal refs in `docs/TASKS.md` (example: `TASKS:T1`).
|
||||||
4. Keep `docs/TASKS.md` status in sync with actual progress until completion.
|
4. Keep `docs/TASKS.md` status in sync with actual progress until completion.
|
||||||
5. For issue/PR/milestone actions, detect platform and use `~/.config/mosaic/tools/git/*.sh` wrappers first (no raw `gh`/`tea`/`glab` as first choice).
|
5. For issue/PR/milestone actions, detect platform and use `~/.config/mosaic/rails/git/*.sh` wrappers first (no raw `gh`/`tea`/`glab` as first choice).
|
||||||
6. If wrapper-driven merge/CI/issue-closure fails, report blocker with the exact failed wrapper command and stop (do not claim completion).
|
6. If wrapper-driven merge/CI/issue-closure fails, report blocker with the exact failed wrapper command and stop (do not claim completion).
|
||||||
|
|
||||||
## Documentation Contract
|
## Documentation Contract
|
||||||
@@ -85,7 +85,7 @@ Reference:
|
|||||||
5. Do not mark implementation complete until PR is merged.
|
5. Do not mark implementation complete until PR is merged.
|
||||||
6. Do not mark implementation complete until CI/pipeline status is terminal green.
|
6. Do not mark implementation complete until CI/pipeline status is terminal green.
|
||||||
7. Close linked issues/tasks only after merge + green CI.
|
7. Close linked issues/tasks only after merge + green CI.
|
||||||
8. Before push or merge, run CI queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
8. Before push or merge, run CI queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose push|merge -B main`.
|
||||||
|
|
||||||
## Container Release Strategy (When Applicable)
|
## Container Release Strategy (When Applicable)
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ ${QUALITY_GATES}
|
|||||||
## Issue Tracking
|
## Issue Tracking
|
||||||
|
|
||||||
Use external git provider issues when available. If no external provider exists, `docs/TASKS.md` is the canonical tracker for tasks, milestones, and issue-equivalent work.
|
Use external git provider issues when available. If no external provider exists, `docs/TASKS.md` is the canonical tracker for tasks, milestones, and issue-equivalent work.
|
||||||
For issue/PR/milestone operations, detect platform and use `~/.config/mosaic/tools/git/*.sh` wrappers first; do not use raw `gh`/`tea`/`glab` as first choice.
|
For issue/PR/milestone operations, detect platform and use `~/.config/mosaic/rails/git/*.sh` wrappers first; do not use raw `gh`/`tea`/`glab` as first choice.
|
||||||
If wrapper-driven merge/CI/issue-closure fails, report blocker with exact failed wrapper command and stop.
|
If wrapper-driven merge/CI/issue-closure fails, report blocker with exact failed wrapper command and stop.
|
||||||
Do NOT stop at "PR created" and do NOT ask "should I merge?" or "should I close the issue?" for routine delivery flow.
|
Do NOT stop at "PR created" and do NOT ask "should I merge?" or "should I close the issue?" for routine delivery flow.
|
||||||
|
|
||||||
@@ -133,9 +133,9 @@ Do NOT stop at "PR created" and do NOT ask "should I merge?" or "should I close
|
|||||||
5. Ensure `docs/PRD.md` or `docs/PRD.json` exists and is current before coding.
|
5. Ensure `docs/PRD.md` or `docs/PRD.json` exists and is current before coding.
|
||||||
6. Create scratchpad: `docs/scratchpads/{task-id}-{short-name}.md` and include issue/internal ref.
|
6. Create scratchpad: `docs/scratchpads/{task-id}-{short-name}.md` and include issue/internal ref.
|
||||||
7. Update `docs/TASKS.md` status + issue/internal ref before coding.
|
7. Update `docs/TASKS.md` status + issue/internal ref before coding.
|
||||||
8. Before push, run CI queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push -B main`.
|
8. Before push, run CI queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose push -B main`.
|
||||||
9. Open PR to `main` for delivery changes (no direct push to `main`).
|
9. Open PR to `main` for delivery changes (no direct push to `main`).
|
||||||
10. Before merge, run CI queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose merge -B main`.
|
10. Before merge, run CI queue guard: `~/.config/mosaic/rails/git/ci-queue-wait.sh --purpose merge -B main`.
|
||||||
11. Merge PRs that pass required checks and review gates with squash strategy only.
|
11. Merge PRs that pass required checks and review gates with squash strategy only.
|
||||||
12. Reference issues/internal refs in commits (`Fixes #123`, `Refs #123`, or `Refs TASKS:T1`).
|
12. Reference issues/internal refs in commits (`Fixes #123`, `Refs #123`, or `Refs TASKS:T1`).
|
||||||
13. Close issue/internal task only after testing and documentation gates pass, PR merge is complete, and CI/pipeline status is terminal green.
|
13. Close issue/internal task only after testing and documentation gates pass, PR merge is complete, and CI/pipeline status is terminal green.
|
||||||
@@ -159,10 +159,10 @@ Run independent reviews:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Code quality review (Codex)
|
# Code quality review (Codex)
|
||||||
~/.config/mosaic/tools/codex/codex-code-review.sh --uncommitted
|
~/.config/mosaic/rails/codex/codex-code-review.sh --uncommitted
|
||||||
|
|
||||||
# Security review (Codex)
|
# Security review (Codex)
|
||||||
~/.config/mosaic/tools/codex/codex-security-review.sh --uncommitted
|
~/.config/mosaic/rails/codex/codex-security-review.sh --uncommitted
|
||||||
```
|
```
|
||||||
|
|
||||||
**Fallback:** If Codex is unavailable, use Claude's built-in review skills.
|
**Fallback:** If Codex is unavailable, use Claude's built-in review skills.
|
||||||
|
|||||||
@@ -1,129 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
#
|
|
||||||
# lane-brief.sh — live dispatch brief for a repo "lane" (milestone/label), straight
|
|
||||||
# from current Gitea state. Defeats stale worker self-report: workers brief from
|
|
||||||
# static notes and routinely report issues "todo" that are already CLOSED, forcing
|
|
||||||
# the orchestrator to re-verify each one before dispatch. This returns the CURRENT
|
|
||||||
# open set, classified for dispatch, in one call.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# lane-brief.sh -r <owner/repo> [-m <milestone>] [-l <label>] [-L <login>] [-n <limit>]
|
|
||||||
# lane-brief.sh -r usc/uconnect -m "M2M Part Search (0.0.45)"
|
|
||||||
# lane-brief.sh -r usc/uconnect -l domain/6-security
|
|
||||||
#
|
|
||||||
# Reliable signals (closed issues are excluded by definition — that's the point):
|
|
||||||
# - open-vs-closed : authoritative; this is the stale-intake failure mode.
|
|
||||||
# - PR-linkage : an open PR referencing the issue = work underway.
|
|
||||||
# Assignees/dependencies are intentionally NOT trusted as "available" signals —
|
|
||||||
# fleets that track work-state out-of-band (tmux board, issue text) leave them
|
|
||||||
# empty in Gitea. Output therefore partitions by PR presence and the OPEN-NO-PR set
|
|
||||||
# is "dispatch candidates to cross-check against the live fleet", not a blind list.
|
|
||||||
#
|
|
||||||
# Login resolution order: -L flag > $GITEA_LOGIN > owner inference (usc->usc,
|
|
||||||
# mosaicstack/mosaic->mosaicstack) > detect-platform.sh default-login fallback.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
# shellcheck source=/dev/null
|
|
||||||
source "$SCRIPT_DIR/detect-platform.sh"
|
|
||||||
|
|
||||||
REPO="" MILESTONE="" LABEL="" LOGIN="" LIMIT=100
|
|
||||||
while getopts "r:m:l:L:n:h" opt; do
|
|
||||||
case "$opt" in
|
|
||||||
r) REPO="$OPTARG" ;;
|
|
||||||
m) MILESTONE="$OPTARG" ;;
|
|
||||||
l) LABEL="$OPTARG" ;;
|
|
||||||
L) LOGIN="$OPTARG" ;;
|
|
||||||
n) LIMIT="$OPTARG" ;;
|
|
||||||
h) grep '^#' "$0" | sed 's/^# \?//'; exit 0 ;;
|
|
||||||
*) echo "see -h" >&2; exit 2 ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
[[ -n "$REPO" ]] || { echo "FATAL: -r <owner/repo> required" >&2; exit 2; }
|
|
||||||
|
|
||||||
# Resolve login: explicit -L, then $GITEA_LOGIN, then owner inference, then the
|
|
||||||
# shared default-login resolver. Owner inference comes before the shared fallback
|
|
||||||
# because the latter is not owner-aware (picks the default tea login), which is
|
|
||||||
# wrong for cross-instance lanes.
|
|
||||||
if [[ -z "$LOGIN" ]]; then
|
|
||||||
if [[ -n "${GITEA_LOGIN:-}" ]]; then
|
|
||||||
LOGIN="$GITEA_LOGIN"
|
|
||||||
else
|
|
||||||
case "${REPO%%/*}" in
|
|
||||||
usc|USC) LOGIN=usc ;;
|
|
||||||
mosaicstack|mosaic) LOGIN=mosaicstack ;;
|
|
||||||
*) LOGIN="$(get_gitea_login_for_repo_override 2>/dev/null || true)" ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
[[ -n "$LOGIN" ]] || { echo "FATAL: could not resolve a Gitea login for $REPO (pass -L or set GITEA_LOGIN)" >&2; exit 2; }
|
|
||||||
|
|
||||||
command -v tea >/dev/null || { echo "FATAL: tea not found" >&2; exit 1; }
|
|
||||||
command -v jq >/dev/null || { echo "FATAL: jq not found" >&2; exit 1; }
|
|
||||||
|
|
||||||
ISSUES_JSON="$(tea issues list --repo "$REPO" --login "$LOGIN" --state open --limit "$LIMIT" \
|
|
||||||
--fields index,title,assignees,milestone,labels --output json 2>/dev/null)" || {
|
|
||||||
echo "FATAL: tea issues list failed for $REPO (login=$LOGIN)" >&2; exit 1; }
|
|
||||||
|
|
||||||
# Open PRs, to cross-ref which issues already have work in flight. An issue is
|
|
||||||
# "work underway" if an open PR links to it. Two link signals are honored:
|
|
||||||
# (a) a closing keyword in the PR BODY — Gitea's auto-close set (close/closes/
|
|
||||||
# closed, fix/fixes/fixed, resolve/resolves/resolved), case-insensitive,
|
|
||||||
# directly preceding `#N`. This is the AUTHORITATIVE link Gitea itself uses
|
|
||||||
# to associate a PR with the issue it resolves; a body-only "Closes #546"
|
|
||||||
# is the common case and MUST count. The earlier version inspected only the
|
|
||||||
# PR index/title/head TSV (never the body or Gitea linkage), so a body-only
|
|
||||||
# reference was invisible and the linked OPEN issue was misclassified as a
|
|
||||||
# dispatch candidate — re-dispatchable in-flight work (the #546/#547 defect).
|
|
||||||
# (b) a bare #N in the PR title, or an issue number embedded in the head branch
|
|
||||||
# (feat/546-x, fix-546) — the weaker heuristic preserved from prior behavior.
|
|
||||||
# Bare #N mentions in the BODY are deliberately NOT treated as links: PR bodies
|
|
||||||
# routinely name unrelated issues in prose ("relevant to the #538 line of work"),
|
|
||||||
# and counting those would wrongly mark live, dispatchable issues as in-flight.
|
|
||||||
# Only the closing-keyword form is a commitment to resolve that issue. Requiring
|
|
||||||
# `#` to directly follow the keyword also keeps cross-repo `owner/repo#N` forms
|
|
||||||
# from leaking a foreign issue number into this per-repo lane (cross-repo lanes
|
|
||||||
# are run per-repo). JSON (not TSV) is used so multi-line bodies parse cleanly.
|
|
||||||
PRS_JSON="$(tea pulls list --repo "$REPO" --login "$LOGIN" --state open \
|
|
||||||
--fields index,title,head,body --output json 2>/dev/null || echo '[]')"
|
|
||||||
[[ -n "$PRS_JSON" ]] || PRS_JSON='[]'
|
|
||||||
|
|
||||||
# \b anchors the keyword to a word start so embedded substrings do not match
|
|
||||||
# (e.g. "prefix #5", "disclosed #7" must NOT be read as "fix #5" / "closed #7").
|
|
||||||
GITEA_CLOSE_KW='close[sd]?|fix(e[sd])?|resolve[sd]?'
|
|
||||||
PR_BODY_REFS="$(printf '%s' "$PRS_JSON" | jq -r '.[] | .body // ""' 2>/dev/null \
|
|
||||||
| grep -oiE "\\b(${GITEA_CLOSE_KW})[[:space:]:]+#[0-9]+" | grep -oE '[0-9]+' || true)"
|
|
||||||
PR_TITLE_HEAD_REFS="$(printf '%s' "$PRS_JSON" \
|
|
||||||
| jq -r '.[] | [ (.title // ""), (.head // "" | if type=="object" then (.ref // "") else . end) ] | join(" ")' 2>/dev/null \
|
|
||||||
| grep -oE '#[0-9]+|[/-][0-9]{3,}' | grep -oE '[0-9]+' || true)"
|
|
||||||
PR_ISSUE_REFS="$(printf '%s\n%s\n' "$PR_BODY_REFS" "$PR_TITLE_HEAD_REFS" | grep -E '^[0-9]+$' | sort -u || true)"
|
|
||||||
|
|
||||||
ts="$(date -u '+%Y-%m-%d %H:%MZ' 2>/dev/null || echo '?')"
|
|
||||||
filt="$REPO"; [[ -n "$MILESTONE" ]] && filt="$filt · milestone:'$MILESTONE'"; [[ -n "$LABEL" ]] && filt="$filt · label:'$LABEL'"
|
|
||||||
echo "LANE BRIEF — $filt · $ts (login=$LOGIN)"
|
|
||||||
echo "(open issues only; closed are excluded by definition — that's the point)"
|
|
||||||
echo
|
|
||||||
|
|
||||||
# Label match is exact-token against tea's space-separated labels string (so -l
|
|
||||||
# "security" does NOT match label "domain/6-security"). Caveat: label names that
|
|
||||||
# themselves contain spaces aren't distinguishable in tea's string form.
|
|
||||||
printf '%s' "$ISSUES_JSON" | jq -r --arg ms "$MILESTONE" --arg lb "$LABEL" --arg prs "$PR_ISSUE_REFS" '
|
|
||||||
($prs | split("\n") | map(select(length>0))) as $prrefs
|
|
||||||
| map(
|
|
||||||
select( ($ms=="" or .milestone==$ms)
|
|
||||||
and ($lb=="" or ((.labels//"") | split(" ") | index($lb) != null)) )
|
|
||||||
| . + { assigned: ((.assignees//"")|length>0),
|
|
||||||
haspr: (.index as $ix | ($prrefs | index($ix)) != null) }
|
|
||||||
)
|
|
||||||
| (map(select(.haspr|not))) as $candidates
|
|
||||||
| (map(select(.haspr))) as $inflight
|
|
||||||
| "DISPATCH CANDIDATES (open · no open PR) — \($candidates|length) [cross-check vs live fleet]:",
|
|
||||||
( $candidates[] | " #\(.index) \(.title[0:90])\(if .assigned then " (gitea-assignee set)" else "" end)" ),
|
|
||||||
"",
|
|
||||||
"WORK UNDERWAY (open · PR in flight) — \($inflight|length):",
|
|
||||||
( $inflight[] | " #\(.index) \(.title[0:80]) [PR open]" )
|
|
||||||
'
|
|
||||||
echo
|
|
||||||
echo "Closed issues are excluded — do NOT take a worker's self-reported 'todo' on faith."
|
|
||||||
echo "Candidates = open + no PR; confirm against the live fleet before dispatch"
|
|
||||||
echo "(fleets that don't self-assign in Gitea leave 'unassigned' meaningless)."
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Regression harness for lane-brief.sh PR->issue linkage classification.
|
|
||||||
#
|
|
||||||
# Covers the #546/#547 defect: lane-brief.sh inspected only the PR index/title/head
|
|
||||||
# fields and never the PR BODY, so an open PR whose body says "Closes #546" did not
|
|
||||||
# mark issue #546 as work-underway — #546 was listed as a DISPATCH CANDIDATE and was
|
|
||||||
# re-dispatchable in-flight work.
|
|
||||||
#
|
|
||||||
# Asserts:
|
|
||||||
# 1. an open issue closed-keyword-linked from a PR BODY ("Closes #546") is
|
|
||||||
# classified WORK UNDERWAY, not a dispatch candidate.
|
|
||||||
# 2. a BARE "#777" prose mention in a PR body does NOT classify #777 as
|
|
||||||
# work-underway (only Gitea closing keywords are a real link) — #777 stays a
|
|
||||||
# dispatch candidate.
|
|
||||||
# 3. NON-VACUITY / RED-ON-REVERT: a copy of the script with the body-scan removed
|
|
||||||
# misclassifies #546 as a dispatch candidate — proving the body-scan is exactly
|
|
||||||
# what fixes the defect and that assertion 1 fails if the fix is reverted.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
LANE_BRIEF="$SCRIPT_DIR/lane-brief.sh"
|
|
||||||
WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/lane-brief-pr-linkage}"
|
|
||||||
BIN_DIR="$WORK_DIR/bin"
|
|
||||||
|
|
||||||
rm -rf "$WORK_DIR"
|
|
||||||
mkdir -p "$BIN_DIR"
|
|
||||||
|
|
||||||
# --- fake `tea`: serves a fixed open-issue set and one open PR. ----------------
|
|
||||||
# PR #547 body uses a closing keyword for #546 ("Closes #546") and a BARE mention
|
|
||||||
# of #777 ("the #777 line of work"). #777 must NOT be treated as linked.
|
|
||||||
cat > "$BIN_DIR/tea" <<'SH'
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
case "${1:-} ${2:-}" in
|
|
||||||
"issues list")
|
|
||||||
cat <<'JSON'
|
|
||||||
[
|
|
||||||
{"index":"546","title":"lane-brief + ci-wait orchestration tooling","assignees":[],"milestone":null,"labels":""},
|
|
||||||
{"index":"777","title":"unrelated downstream item","assignees":[],"milestone":null,"labels":""},
|
|
||||||
{"index":"999","title":"item only named inside the word hotfix","assignees":[],"milestone":null,"labels":""}
|
|
||||||
]
|
|
||||||
JSON
|
|
||||||
;;
|
|
||||||
"pulls list")
|
|
||||||
cat <<'JSON'
|
|
||||||
[
|
|
||||||
{"index":"547","title":"feat(framework/tools): orchestration helpers","head":"feat/orchestration-tools-lane-brief-ci-wait","body":"Two additive orchestration tools.\n\nCloses #546.\n\nLogin resolution is relevant to the #777 line of work but does not touch it.\nThis shipped as a hotfix #999 earlier — that bare reference must not link it.\n\nFixes #546\n"}
|
|
||||||
]
|
|
||||||
JSON
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "fake-tea: unhandled: $*" >&2; exit 1 ;;
|
|
||||||
esac
|
|
||||||
SH
|
|
||||||
chmod +x "$BIN_DIR/tea"
|
|
||||||
|
|
||||||
run_brief() { # $1 = script path
|
|
||||||
PATH="$BIN_DIR:$PATH" "$1" -r mosaic/stack -L test-login 2>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
# Extract the issue numbers under a named section header until the next blank line.
|
|
||||||
section_nums() { # $1 = output $2 = header-prefix
|
|
||||||
printf '%s\n' "$1" | awk -v h="$2" '
|
|
||||||
index($0,h)==1 {grab=1; next}
|
|
||||||
grab && /^[[:space:]]*$/ {grab=0}
|
|
||||||
grab && match($0, /#[0-9]+/) { print substr($0, RSTART+1, RLENGTH-1) }
|
|
||||||
'
|
|
||||||
}
|
|
||||||
|
|
||||||
fail() { echo "FAIL: $1" >&2; exit 1; }
|
|
||||||
contains() { printf '%s\n' "$1" | grep -qx "$2"; }
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Fixed (current) script behavior
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
OUT="$(run_brief "$LANE_BRIEF")"
|
|
||||||
CAND="$(section_nums "$OUT" 'DISPATCH CANDIDATES')"
|
|
||||||
UNDER="$(section_nums "$OUT" 'WORK UNDERWAY')"
|
|
||||||
|
|
||||||
echo "--- lane-brief output (fixed) ---"; printf '%s\n' "$OUT"
|
|
||||||
echo "--- candidates: [$(printf '%s' "$CAND" | tr '\n' ' ')] underway: [$(printf '%s' "$UNDER" | tr '\n' ' ')] ---"
|
|
||||||
|
|
||||||
contains "$UNDER" 546 || fail "#546 (PR body 'Closes #546') should be WORK UNDERWAY"
|
|
||||||
contains "$CAND" 546 && fail "#546 must NOT be a dispatch candidate (it has an open PR)"
|
|
||||||
contains "$CAND" 777 || fail "#777 (only a bare prose mention) should remain a dispatch candidate"
|
|
||||||
contains "$UNDER" 777 && fail "#777 must NOT be work-underway — bare body mentions are not links"
|
|
||||||
contains "$CAND" 999 || fail "#999 ('hotfix #999' — keyword is a substring) should remain a candidate"
|
|
||||||
contains "$UNDER" 999 && fail "#999 must NOT be work-underway — word-boundary must reject 'hotfix'"
|
|
||||||
echo "PASS: body closing-keyword link classifies #546 underway; bare #777 / substring #999 stay candidates"
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# NON-VACUITY: revert the body-scan and prove #546 regresses to a candidate.
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
REVERTED="$SCRIPT_DIR/.lane-brief.reverted.$$.sh"
|
|
||||||
trap 'rm -f "$REVERTED"' EXIT
|
|
||||||
# Drop the PR_BODY_REFS contribution from the union (simulates the pre-fix script
|
|
||||||
# that only looked at index/title/head). Sibling `source detect-platform.sh` still
|
|
||||||
# resolves because the copy lives in the same dir.
|
|
||||||
# shellcheck disable=SC2016 # single-quoted on purpose: sed needs the literal $PR_BODY_REFS
|
|
||||||
sed 's/"\$PR_BODY_REFS"/""/' "$LANE_BRIEF" > "$REVERTED"
|
|
||||||
chmod +x "$REVERTED"
|
|
||||||
grep -q 'PR_BODY_REFS' "$REVERTED" || fail "revert sed anchor not found — test is stale"
|
|
||||||
|
|
||||||
ROUT="$(run_brief "$REVERTED")"
|
|
||||||
RCAND="$(section_nums "$ROUT" 'DISPATCH CANDIDATES')"
|
|
||||||
RUNDER="$(section_nums "$ROUT" 'WORK UNDERWAY')"
|
|
||||||
echo "--- candidates(reverted): [$(printf '%s' "$RCAND" | tr '\n' ' ')] underway: [$(printf '%s' "$RUNDER" | tr '\n' ' ')] ---"
|
|
||||||
|
|
||||||
contains "$RCAND" 546 || fail "non-vacuity broken: reverted script should misclassify #546 as a candidate"
|
|
||||||
contains "$RUNDER" 546 && fail "non-vacuity broken: reverted script should NOT mark #546 underway"
|
|
||||||
echo "PASS (RED-on-revert): without the body-scan, #546 regresses to a dispatch candidate"
|
|
||||||
|
|
||||||
echo "ALL PASS: test-lane-brief-pr-linkage.sh"
|
|
||||||
@@ -26,12 +26,11 @@ A Woodpecker API token is required. To configure:
|
|||||||
|
|
||||||
## Scripts
|
## Scripts
|
||||||
|
|
||||||
| Script | Purpose |
|
| Script | Purpose |
|
||||||
| --------------------- | -------------------------------------------- |
|
| --------------------- | ------------------------------------------- |
|
||||||
| `pipeline-list.sh` | List recent pipelines for a repo |
|
| `pipeline-list.sh` | List recent pipelines for a repo |
|
||||||
| `pipeline-status.sh` | Get status of a specific or latest pipeline |
|
| `pipeline-status.sh` | Get status of a specific or latest pipeline |
|
||||||
| `pipeline-trigger.sh` | Trigger a new pipeline build |
|
| `pipeline-trigger.sh` | Trigger a new pipeline build |
|
||||||
| `ci-wait.sh` | Block until pipeline(s) reach terminal state |
|
|
||||||
|
|
||||||
## Common Options
|
## Common Options
|
||||||
|
|
||||||
@@ -56,7 +55,4 @@ A Woodpecker API token is required. To configure:
|
|||||||
|
|
||||||
# Trigger a build on a specific branch
|
# Trigger a build on a specific branch
|
||||||
~/.config/mosaic/tools/woodpecker/pipeline-trigger.sh -b feature/my-branch
|
~/.config/mosaic/tools/woodpecker/pipeline-trigger.sh -b feature/my-branch
|
||||||
|
|
||||||
# Block until one or more pipelines finish (event-driven CI wait)
|
|
||||||
~/.config/mosaic/tools/woodpecker/ci-wait.sh -r usc/uconnect -n 3917 -n 3918
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# ci-wait.sh — block until one or more Woodpecker pipelines reach terminal state.
|
|
||||||
#
|
|
||||||
# Problem it solves: orchestrators hand-author a `while true; curl .../repos/1/pipelines/$n
|
|
||||||
# ...; sleep` loop for every CI wait. Those loops HARDCODE Woodpecker repo id 1 (only
|
|
||||||
# correct for whichever repo happens to be id 1), re-implement URL building with raw
|
|
||||||
# curl, and tend to get armed as tight <300s ScheduleWakeup polls (each poll = a full
|
|
||||||
# wake+reload+recheck cycle). This encapsulates the loop once, on top of the existing
|
|
||||||
# `pipeline-status.sh` wrapper (which resolves repo->id correctly and is instance-aware),
|
|
||||||
# so a CI wait becomes a one-liner.
|
|
||||||
#
|
|
||||||
# Intended use: as the COMMAND of a Monitor / event-driven re-invoke (primary), paired
|
|
||||||
# with a single long (>=1500s) timed fallback — NOT as a tight standalone poll.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# ci-wait.sh -r <owner/repo> -n <num> [-n <num> ...] [-a <instance>] [-i <interval>] [-t <timeout>]
|
|
||||||
# ci-wait.sh -r usc/uconnect -n 3917 -n 3918 # wait for both, infer instance
|
|
||||||
# ci-wait.sh -r usc/uconnect -n 3922 -a usc -i 30 -t 2400
|
|
||||||
#
|
|
||||||
# Instance is inferred from the owner (usc->usc, mosaicstack/mosaic->mosaic) unless -a given.
|
|
||||||
# Exit: 0 = all pipelines terminal AND all 'success'; 1 = >=1 terminal non-success;
|
|
||||||
# 2 = usage/precondition error; 3 = timeout before all terminal.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Resolve pipeline-status.sh as a sibling, matching how the woodpecker tools source
|
|
||||||
# _lib.sh — works under the installed runtime AND an in-repo checkout, no MOSAIC_HOME dep.
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
PS="$SCRIPT_DIR/pipeline-status.sh"
|
|
||||||
|
|
||||||
REPO="" INSTANCE="" INTERVAL=30 TIMEOUT=3600
|
|
||||||
NUMS=()
|
|
||||||
while getopts "r:n:a:i:t:h" opt; do
|
|
||||||
case "$opt" in
|
|
||||||
r) REPO="$OPTARG" ;;
|
|
||||||
n) NUMS+=("$OPTARG") ;;
|
|
||||||
a) INSTANCE="$OPTARG" ;;
|
|
||||||
i) INTERVAL="$OPTARG" ;;
|
|
||||||
t) TIMEOUT="$OPTARG" ;;
|
|
||||||
h) grep '^#' "$0" | sed 's/^# \?//'; exit 0 ;;
|
|
||||||
*) echo "see -h" >&2; exit 2 ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
[[ -n "$REPO" ]] || { echo "FATAL: -r <owner/repo> required" >&2; exit 2; }
|
|
||||||
[[ ${#NUMS[@]} -gt 0 ]] || { echo "FATAL: at least one -n <pipeline-number> required" >&2; exit 2; }
|
|
||||||
[[ -x "$PS" ]] || { echo "FATAL: pipeline-status.sh not found/executable at $PS" >&2; exit 2; }
|
|
||||||
|
|
||||||
# Infer Woodpecker instance from owner unless overridden (matches the git-wrapper convention).
|
|
||||||
if [[ -z "$INSTANCE" ]]; then
|
|
||||||
case "${REPO%%/*}" in
|
|
||||||
usc|USC) INSTANCE=usc ;;
|
|
||||||
mosaicstack|mosaic) INSTANCE=mosaic ;;
|
|
||||||
*) echo "FATAL: cannot infer Woodpecker instance for owner '${REPO%%/*}' — pass -a <instance>" >&2; exit 2 ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
command -v jq >/dev/null || { echo "FATAL: jq not found" >&2; exit 2; }
|
|
||||||
|
|
||||||
TERMINAL_RE='^(success|failure|error|killed|declined|blocked)$'
|
|
||||||
declare -A STATE=() # num -> terminal status, once reached
|
|
||||||
start=$(date +%s 2>/dev/null || echo 0)
|
|
||||||
|
|
||||||
echo "ci-wait: $REPO pipelines [${NUMS[*]}] (instance=$INSTANCE, every ${INTERVAL}s, timeout ${TIMEOUT}s)"
|
|
||||||
while true; do
|
|
||||||
for n in "${NUMS[@]}"; do
|
|
||||||
[[ -n "${STATE[$n]:-}" ]] && continue
|
|
||||||
s=$("$PS" -r "$REPO" -n "$n" -a "$INSTANCE" -f json 2>/dev/null | jq -r '.status // empty' 2>/dev/null || true)
|
|
||||||
if [[ "$s" =~ $TERMINAL_RE ]]; then
|
|
||||||
STATE[$n]="$s"
|
|
||||||
echo " pipeline $n TERMINAL: $s"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
# all terminal?
|
|
||||||
if [[ ${#STATE[@]} -eq ${#NUMS[@]} ]]; then
|
|
||||||
bad=0
|
|
||||||
for n in "${NUMS[@]}"; do [[ "${STATE[$n]}" == "success" ]] || bad=1; done
|
|
||||||
if [[ $bad -eq 0 ]]; then echo "ci-wait: ALL SUCCESS"; exit 0; fi
|
|
||||||
echo "ci-wait: all terminal, NOT all success — $(for n in "${NUMS[@]}"; do printf '%s=%s ' "$n" "${STATE[$n]}"; done)"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
now=$(date +%s 2>/dev/null || echo 0)
|
|
||||||
if [[ "$start" != 0 && $((now - start)) -ge $TIMEOUT ]]; then
|
|
||||||
echo "ci-wait: TIMEOUT after ${TIMEOUT}s — pending: $(for n in "${NUMS[@]}"; do [[ -z "${STATE[$n]:-}" ]] && printf '%s ' "$n"; done)"
|
|
||||||
exit 3
|
|
||||||
fi
|
|
||||||
sleep "$INTERVAL"
|
|
||||||
done
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Regression harness for ci-wait.sh terminal-state aggregation and exit codes.
|
|
||||||
#
|
|
||||||
# ci-wait.sh wraps pipeline-status.sh and blocks until every requested pipeline
|
|
||||||
# reaches a terminal Woodpecker state, then maps the aggregate to an exit code.
|
|
||||||
# That contract is what callers arm a Monitor/timed-fallback around, so it must be
|
|
||||||
# exact. This harness drives ci-wait.sh against a stub pipeline-status.sh whose
|
|
||||||
# per-pipeline status is fixture-controlled, and asserts the full exit matrix:
|
|
||||||
#
|
|
||||||
# 0 = every pipeline terminal AND all 'success'
|
|
||||||
# 1 = every pipeline terminal, at least one non-success
|
|
||||||
# 2 = usage/precondition error (missing -n)
|
|
||||||
# 3 = timeout before all pipelines terminal
|
|
||||||
#
|
|
||||||
# Non-vacuity: each case pins a DISTINCT exit code to a distinct fixture, so a
|
|
||||||
# regression in success-aggregation (case 0 vs 1), terminal detection (case 3),
|
|
||||||
# or arg validation (case 2) flips exactly one assertion RED.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
CIW_SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ci-wait.sh"
|
|
||||||
WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/ci-wait-exit-matrix}"
|
|
||||||
TOOL_DIR="$WORK_DIR/tool"
|
|
||||||
|
|
||||||
rm -rf "$WORK_DIR"
|
|
||||||
mkdir -p "$TOOL_DIR"
|
|
||||||
|
|
||||||
# ci-wait.sh resolves pipeline-status.sh as a sibling ($SCRIPT_DIR/pipeline-status.sh),
|
|
||||||
# so we run a COPY of ci-wait.sh next to a stub sibling we control.
|
|
||||||
cp "$CIW_SRC" "$TOOL_DIR/ci-wait.sh"
|
|
||||||
chmod +x "$TOOL_DIR/ci-wait.sh"
|
|
||||||
|
|
||||||
# Stub pipeline-status.sh: emits {"status":"<s>"} where <s> comes from env
|
|
||||||
# CIW_STATUS_<num> (default "running" = non-terminal, drives the timeout path).
|
|
||||||
cat > "$TOOL_DIR/pipeline-status.sh" <<'SH'
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
num=""
|
|
||||||
while getopts "r:n:a:f:" opt; do case "$opt" in n) num="$OPTARG" ;; *) : ;; esac; done
|
|
||||||
var="CIW_STATUS_${num}"
|
|
||||||
printf '{"status":"%s"}\n' "${!var:-running}"
|
|
||||||
SH
|
|
||||||
chmod +x "$TOOL_DIR/pipeline-status.sh"
|
|
||||||
|
|
||||||
CIW="$TOOL_DIR/ci-wait.sh"
|
|
||||||
|
|
||||||
run_expect() { # $1 = expected exit $2 = label ; rest = args
|
|
||||||
local want="$1" label="$2"; shift 2
|
|
||||||
local rc=0
|
|
||||||
"$CIW" "$@" >/dev/null 2>&1 || rc=$?
|
|
||||||
if [[ "$rc" -ne "$want" ]]; then
|
|
||||||
echo "FAIL [$label]: expected exit $want, got $rc" >&2; exit 1
|
|
||||||
fi
|
|
||||||
echo "PASS [$label]: exit $rc"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 0 — both pipelines terminal + success
|
|
||||||
CIW_STATUS_100=success CIW_STATUS_101=success \
|
|
||||||
run_expect 0 "all-success" -r mosaic/stack -n 100 -n 101 -a mosaic -i 1 -t 30
|
|
||||||
|
|
||||||
# 1 — both terminal, one failure
|
|
||||||
CIW_STATUS_100=success CIW_STATUS_101=failure \
|
|
||||||
run_expect 1 "terminal-not-success" -r mosaic/stack -n 100 -n 101 -a mosaic -i 1 -t 30
|
|
||||||
|
|
||||||
# 1 — other terminal non-success states still map to 1 (error/killed)
|
|
||||||
CIW_STATUS_100=error CIW_STATUS_101=killed \
|
|
||||||
run_expect 1 "terminal-error-killed" -r mosaic/stack -n 100 -n 101 -a mosaic -i 1 -t 30
|
|
||||||
|
|
||||||
# 3 — a pipeline never reaches terminal state before timeout
|
|
||||||
CIW_STATUS_100=success CIW_STATUS_101=running \
|
|
||||||
run_expect 3 "timeout-pending" -r mosaic/stack -n 100 -n 101 -a mosaic -i 1 -t 0
|
|
||||||
|
|
||||||
# 2 — usage error: no -n
|
|
||||||
run_expect 2 "usage-missing-n" -r mosaic/stack -a mosaic
|
|
||||||
|
|
||||||
echo "ALL PASS: test-ci-wait-exit-matrix.sh"
|
|
||||||
@@ -1,15 +1,6 @@
|
|||||||
import { describe, it, expect, vi, beforeEach, afterEach, type MockInstance } from 'vitest';
|
import { describe, it, expect, vi, beforeEach, afterEach, type MockInstance } from 'vitest';
|
||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { mkdtempSync, mkdirSync, writeFileSync, symlinkSync, rmSync } from 'node:fs';
|
import { buildPiSkillArgs, registerRuntimeLaunchers, type RuntimeLaunchHandler } from './launch.js';
|
||||||
import { tmpdir } from 'node:os';
|
|
||||||
import { join } from 'node:path';
|
|
||||||
import {
|
|
||||||
buildPiSkillArgs,
|
|
||||||
enumerateSkillDirs,
|
|
||||||
piForceSkillNames,
|
|
||||||
registerRuntimeLaunchers,
|
|
||||||
type RuntimeLaunchHandler,
|
|
||||||
} from './launch.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for the commander wiring between `mosaic <runtime>` / `mosaic yolo <runtime>`
|
* Tests for the commander wiring between `mosaic <runtime>` / `mosaic yolo <runtime>`
|
||||||
@@ -32,7 +23,6 @@ function buildProgram(handler: RuntimeLaunchHandler): Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fakeSkills = ['--skill', '/skills/test-driven-development', '--skill', '/skills/pdf'];
|
const fakeSkills = ['--skill', '/skills/test-driven-development', '--skill', '/skills/pdf'];
|
||||||
const fakeForced = ['--skill', '/skills/mosaic-tools'];
|
|
||||||
|
|
||||||
// `process.exit` returns `never`, so vi.spyOn demands a replacement with the
|
// `process.exit` returns `never`, so vi.spyOn demands a replacement with the
|
||||||
// same signature. We throw from the mock to short-circuit into test-land.
|
// same signature. We throw from the mock to short-circuit into test-land.
|
||||||
@@ -76,42 +66,16 @@ describe('registerRuntimeLaunchers — non-yolo subcommands', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('buildPiSkillArgs', () => {
|
describe('buildPiSkillArgs', () => {
|
||||||
it('disables auto-discovery but force-loads fleet-critical skills by default', () => {
|
it('defaults to disabling Pi skill discovery to keep startup context small', () => {
|
||||||
expect(buildPiSkillArgs([], {}, fakeSkills, fakeForced)).toEqual([
|
expect(buildPiSkillArgs([], {}, fakeSkills)).toEqual(['--no-skills']);
|
||||||
'--no-skills',
|
|
||||||
'--skill',
|
|
||||||
'/skills/mosaic-tools',
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ignores _runtimeArgs (user --skill flags reach Pi via the launch handler, not here)', () => {
|
it('keeps explicit user skills while disabling automatic discovery', () => {
|
||||||
expect(buildPiSkillArgs(['--skill', '/tmp/custom'], {}, fakeSkills, fakeForced)).toEqual([
|
expect(buildPiSkillArgs(['--skill', '/tmp/custom'], {}, fakeSkills)).toEqual(['--no-skills']);
|
||||||
'--no-skills',
|
|
||||||
'--skill',
|
|
||||||
'/skills/mosaic-tools',
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('emits only --no-skills when no forced skills are present on disk', () => {
|
it('supports legacy all-skills mode without double-loading settings skills', () => {
|
||||||
expect(buildPiSkillArgs([], {}, fakeSkills, [])).toEqual(['--no-skills']);
|
expect(buildPiSkillArgs([], { MOSAIC_PI_SKILL_MODE: 'all' }, fakeSkills)).toEqual([
|
||||||
});
|
|
||||||
|
|
||||||
it('all-skills mode merges the forced set in without duplicating discovered skills', () => {
|
|
||||||
expect(buildPiSkillArgs([], { MOSAIC_PI_SKILL_MODE: 'all' }, fakeSkills, fakeForced)).toEqual([
|
|
||||||
'--no-skills',
|
|
||||||
'--skill',
|
|
||||||
'/skills/test-driven-development',
|
|
||||||
'--skill',
|
|
||||||
'/skills/pdf',
|
|
||||||
'--skill',
|
|
||||||
'/skills/mosaic-tools',
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('all-skills mode does not double-load a forced skill already discovered', () => {
|
|
||||||
expect(
|
|
||||||
buildPiSkillArgs([], { MOSAIC_PI_SKILL_MODE: 'all' }, fakeSkills, ['--skill', '/skills/pdf']),
|
|
||||||
).toEqual([
|
|
||||||
'--no-skills',
|
'--no-skills',
|
||||||
'--skill',
|
'--skill',
|
||||||
'/skills/test-driven-development',
|
'/skills/test-driven-development',
|
||||||
@@ -120,117 +84,8 @@ describe('buildPiSkillArgs', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('force-loads fleet skills under native Pi discovery when not already discoverable', () => {
|
it('supports native Pi discovery when explicitly requested', () => {
|
||||||
// Empty native set => Pi would not find mosaic-tools on its own, so force it.
|
expect(buildPiSkillArgs([], { MOSAIC_PI_SKILL_MODE: 'discover' }, fakeSkills)).toEqual([]);
|
||||||
expect(
|
|
||||||
buildPiSkillArgs([], { MOSAIC_PI_SKILL_MODE: 'discover' }, fakeSkills, fakeForced, new Set()),
|
|
||||||
).toEqual(['--skill', '/skills/mosaic-tools']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('discover mode drops a forced skill Pi already discovers natively (no double-load)', () => {
|
|
||||||
// mosaic-tools is reachable from a Pi native root, so native discovery
|
|
||||||
// covers it — forcing it again would register the same skill twice.
|
|
||||||
expect(
|
|
||||||
buildPiSkillArgs(
|
|
||||||
[],
|
|
||||||
{ MOSAIC_PI_SKILL_MODE: 'discover' },
|
|
||||||
fakeSkills,
|
|
||||||
fakeForced,
|
|
||||||
new Set(['/skills/mosaic-tools']),
|
|
||||||
),
|
|
||||||
).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('discover mode keeps a forced skill that no native root provides', () => {
|
|
||||||
expect(
|
|
||||||
buildPiSkillArgs(
|
|
||||||
[],
|
|
||||||
{ MOSAIC_PI_SKILL_MODE: 'discover' },
|
|
||||||
fakeSkills,
|
|
||||||
fakeForced,
|
|
||||||
new Set(['/skills/some-other-skill']),
|
|
||||||
),
|
|
||||||
).toEqual(['--skill', '/skills/mosaic-tools']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('discover mode collapses a forced skill listed twice to a single --skill', () => {
|
|
||||||
// Mirror 'all' mode: intra-forced-set duplicates (same realpath) dedup.
|
|
||||||
expect(
|
|
||||||
buildPiSkillArgs(
|
|
||||||
[],
|
|
||||||
{ MOSAIC_PI_SKILL_MODE: 'discover' },
|
|
||||||
fakeSkills,
|
|
||||||
['--skill', '/skills/mosaic-tools', '--skill', '/skills/mosaic-tools'],
|
|
||||||
new Set(),
|
|
||||||
),
|
|
||||||
).toEqual(['--skill', '/skills/mosaic-tools']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('enumerateSkillDirs (real FS)', () => {
|
|
||||||
let root: string;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
root = mkdtempSync(join(tmpdir(), 'mosaic-skills-'));
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
rmSync(root, { recursive: true, force: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
function makeSkill(parent: string, name: string): string {
|
|
||||||
const dir = join(parent, name);
|
|
||||||
mkdirSync(dir, { recursive: true });
|
|
||||||
writeFileSync(join(dir, 'SKILL.md'), `# ${name}\n`);
|
|
||||||
return dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
it('accepts a symlinked skill dir (regression: synced fleet skills are symlinks)', () => {
|
|
||||||
// Real skill lives under `canonical/`; the scanned root only has a symlink to it.
|
|
||||||
const canonical = makeSkill(join(root, 'canonical'), 'mosaic-tools');
|
|
||||||
const scanned = join(root, 'scanned');
|
|
||||||
mkdirSync(scanned, { recursive: true });
|
|
||||||
symlinkSync(canonical, join(scanned, 'mosaic-tools'), 'dir');
|
|
||||||
|
|
||||||
expect(enumerateSkillDirs([scanned])).toEqual(['--skill', join(scanned, 'mosaic-tools')]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('dedups by real path when the same skill is reachable from two roots', () => {
|
|
||||||
// Root A holds the real dir; root B symlinks to it — one --skill, not two.
|
|
||||||
const rootA = join(root, 'a');
|
|
||||||
const rootB = join(root, 'b');
|
|
||||||
const real = makeSkill(rootA, 'mosaic-tools');
|
|
||||||
mkdirSync(rootB, { recursive: true });
|
|
||||||
symlinkSync(real, join(rootB, 'mosaic-tools'), 'dir');
|
|
||||||
|
|
||||||
expect(enumerateSkillDirs([rootA, rootB])).toEqual(['--skill', real]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('skips directories without a SKILL.md and missing roots', () => {
|
|
||||||
mkdirSync(join(root, 'present', 'not-a-skill'), { recursive: true });
|
|
||||||
makeSkill(join(root, 'present'), 'real-skill');
|
|
||||||
|
|
||||||
expect(enumerateSkillDirs([join(root, 'present'), join(root, 'does-not-exist')])).toEqual([
|
|
||||||
'--skill',
|
|
||||||
join(root, 'present', 'real-skill'),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('piForceSkillNames', () => {
|
|
||||||
it('defaults to mosaic-tools when MOSAIC_PI_FORCE_SKILLS is unset', () => {
|
|
||||||
expect(piForceSkillNames({})).toEqual(['mosaic-tools']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('treats an empty string as "disable force-loading" (distinct from unset)', () => {
|
|
||||||
expect(piForceSkillNames({ MOSAIC_PI_FORCE_SKILLS: '' })).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('parses a colon list, trimming blanks and whitespace', () => {
|
|
||||||
expect(piForceSkillNames({ MOSAIC_PI_FORCE_SKILLS: 'mosaic-tools: mosaic-gitea ::' })).toEqual([
|
|
||||||
'mosaic-tools',
|
|
||||||
'mosaic-gitea',
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { execFileSync, execSync, spawnSync } from 'node:child_process';
|
import { execFileSync, execSync, spawnSync } from 'node:child_process';
|
||||||
import {
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, rmSync } from 'node:fs';
|
||||||
existsSync,
|
|
||||||
mkdirSync,
|
|
||||||
readFileSync,
|
|
||||||
writeFileSync,
|
|
||||||
readdirSync,
|
|
||||||
realpathSync,
|
|
||||||
rmSync,
|
|
||||||
} from 'node:fs';
|
|
||||||
import { createRequire } from 'node:module';
|
import { createRequire } from 'node:module';
|
||||||
import { homedir } from 'node:os';
|
import { homedir } from 'node:os';
|
||||||
import { join, dirname } from 'node:path';
|
import { join, dirname } from 'node:path';
|
||||||
@@ -436,74 +428,25 @@ function ensureRuntimeConfig(runtime: RuntimeName, destPath: string): void {
|
|||||||
|
|
||||||
// ─── Pi skill/extension discovery ────────────────────────────────────────────
|
// ─── Pi skill/extension discovery ────────────────────────────────────────────
|
||||||
|
|
||||||
/** Resolve a skill dir to its canonical real path so symlinked duplicates
|
function discoverPiSkills(): string[] {
|
||||||
* (e.g. ~/.pi/agent/skills/X -> ~/.config/mosaic/skills/X) collapse to one key.
|
|
||||||
* Falls back to the literal path if it can't be resolved (e.g. broken link). */
|
|
||||||
function skillRealPath(dir: string): string {
|
|
||||||
try {
|
|
||||||
return realpathSync(dir);
|
|
||||||
} catch {
|
|
||||||
return dir;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Skill roots Pi auto-discovers natively (no `--skill` needed): its global
|
|
||||||
* skills dir and the project-local one relative to the launch cwd. */
|
|
||||||
function piNativeSkillRoots(cwd: string = process.cwd()): string[] {
|
|
||||||
return [join(homedir(), '.pi', 'agent', 'skills'), join(cwd, '.pi', 'skills')];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Enumerate skill dirs under a set of roots, deduped by real path. A directory
|
|
||||||
* counts as a skill when it (or its symlink target) contains a SKILL.md.
|
|
||||||
* Exported for tests (real-FS coverage of symlink acceptance + realpath dedup). */
|
|
||||||
export function enumerateSkillDirs(roots: string[]): string[] {
|
|
||||||
const seen = new Set<string>();
|
|
||||||
const args: string[] = [];
|
const args: string[] = [];
|
||||||
for (const skillsRoot of roots) {
|
for (const skillsRoot of [join(MOSAIC_HOME, 'skills'), join(MOSAIC_HOME, 'skills-local')]) {
|
||||||
if (!existsSync(skillsRoot)) continue;
|
if (!existsSync(skillsRoot)) continue;
|
||||||
try {
|
try {
|
||||||
for (const entry of readdirSync(skillsRoot, { withFileTypes: true })) {
|
for (const entry of readdirSync(skillsRoot, { withFileTypes: true })) {
|
||||||
// Synced fleet skills land as symlinks, so accept both dirs and links.
|
if (!entry.isDirectory()) continue;
|
||||||
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
||||||
const skillDir = join(skillsRoot, entry.name);
|
const skillDir = join(skillsRoot, entry.name);
|
||||||
if (!existsSync(join(skillDir, 'SKILL.md'))) continue;
|
if (existsSync(join(skillDir, 'SKILL.md'))) {
|
||||||
const key = skillRealPath(skillDir);
|
args.push('--skill', skillDir);
|
||||||
if (seen.has(key)) continue;
|
}
|
||||||
seen.add(key);
|
|
||||||
args.push('--skill', skillDir);
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// skip unreadable roots
|
// skip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Every skill dir Pi would link under `MOSAIC_PI_SKILL_MODE=all`: the Mosaic
|
|
||||||
* global/local catalog plus Pi's own native roots. `--no-skills` suppresses
|
|
||||||
* native auto-discovery, so 'all' must re-add the native roots explicitly or
|
|
||||||
* they would be silently dropped. Deduped by real path. */
|
|
||||||
function discoverPiSkills(cwd: string = process.cwd()): string[] {
|
|
||||||
return enumerateSkillDirs([
|
|
||||||
join(MOSAIC_HOME, 'skills'),
|
|
||||||
join(MOSAIC_HOME, 'skills-local'),
|
|
||||||
...piNativeSkillRoots(cwd),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Real paths of skills Pi will auto-discover from its native roots. Used to
|
|
||||||
* drop redundant force-loads in 'discover' mode (which keeps native discovery
|
|
||||||
* on) so the same skill is not registered twice. */
|
|
||||||
function piNativeSkillRealPaths(cwd: string = process.cwd()): Set<string> {
|
|
||||||
const args = enumerateSkillDirs(piNativeSkillRoots(cwd));
|
|
||||||
const set = new Set<string>();
|
|
||||||
for (let i = 1; i < args.length; i += 2) {
|
|
||||||
const dir = args[i];
|
|
||||||
if (dir !== undefined) set.add(skillRealPath(dir));
|
|
||||||
}
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
|
|
||||||
type PiSkillMode = 'none' | 'all' | 'discover';
|
type PiSkillMode = 'none' | 'all' | 'discover';
|
||||||
|
|
||||||
function normalizePiSkillMode(env: NodeJS.ProcessEnv): PiSkillMode {
|
function normalizePiSkillMode(env: NodeJS.ProcessEnv): PiSkillMode {
|
||||||
@@ -512,96 +455,22 @@ function normalizePiSkillMode(env: NodeJS.ProcessEnv): PiSkillMode {
|
|||||||
return 'none';
|
return 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fleet-critical Pi skills that are force-loaded on every Pi launch regardless
|
|
||||||
* of MOSAIC_PI_SKILL_MODE. They cover the highest-frequency cross-agent and
|
|
||||||
* git-provider operations where Pi workers historically improvised raw CLIs
|
|
||||||
* (raw `tmux send-keys`, raw `tea`/`gh`/`glab`) instead of the maintained
|
|
||||||
* `~/.config/mosaic/tools/` wrappers.
|
|
||||||
*
|
|
||||||
* An explicit `--skill <dir>` overrides `--no-skills` for that path, so forcing
|
|
||||||
* a single targeted skill surfaces the must-use toolkit without loading the full
|
|
||||||
* ~100-skill catalog (context bloat). Missing skills are skipped silently, so
|
|
||||||
* this is a no-op until the named skill is synced into ~/.config/mosaic/skills/.
|
|
||||||
*
|
|
||||||
* Override with MOSAIC_PI_FORCE_SKILLS (colon-separated skill dir names; set to
|
|
||||||
* an empty string to disable force-loading entirely).
|
|
||||||
*/
|
|
||||||
const DEFAULT_PI_FORCE_SKILLS = ['mosaic-tools'];
|
|
||||||
|
|
||||||
export function piForceSkillNames(env: NodeJS.ProcessEnv): string[] {
|
|
||||||
const override = env['MOSAIC_PI_FORCE_SKILLS'];
|
|
||||||
if (override === undefined) return DEFAULT_PI_FORCE_SKILLS;
|
|
||||||
return override
|
|
||||||
.split(':')
|
|
||||||
.map((name) => name.trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
}
|
|
||||||
|
|
||||||
function forcedPiSkillArgs(env: NodeJS.ProcessEnv = process.env): string[] {
|
|
||||||
const args: string[] = [];
|
|
||||||
for (const name of piForceSkillNames(env)) {
|
|
||||||
const skillDir = join(MOSAIC_HOME, 'skills', name);
|
|
||||||
if (existsSync(join(skillDir, 'SKILL.md'))) {
|
|
||||||
args.push('--skill', skillDir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Concatenate `--skill <dir>` arg groups, dropping any skill already seen.
|
|
||||||
* Dedup is by real path, so a forced skill and the same skill reached via a
|
|
||||||
* different (e.g. symlinked) directory collapse to a single `--skill`. */
|
|
||||||
function mergeSkillArgs(...groups: string[][]): string[] {
|
|
||||||
const seen = new Set<string>();
|
|
||||||
const out: string[] = [];
|
|
||||||
for (const group of groups) {
|
|
||||||
for (let i = 0; i < group.length; i += 2) {
|
|
||||||
const dir = group[i + 1];
|
|
||||||
if (group[i] !== '--skill' || dir === undefined) continue;
|
|
||||||
const key = skillRealPath(dir);
|
|
||||||
if (seen.has(key)) continue;
|
|
||||||
seen.add(key);
|
|
||||||
out.push('--skill', dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildPiSkillArgs(
|
export function buildPiSkillArgs(
|
||||||
_runtimeArgs: string[],
|
_runtimeArgs: string[],
|
||||||
env: NodeJS.ProcessEnv = process.env,
|
env: NodeJS.ProcessEnv = process.env,
|
||||||
discoveredSkillArgs: string[] = discoverPiSkills(),
|
discoveredSkillArgs: string[] = discoverPiSkills(),
|
||||||
forcedSkillArgs: string[] = forcedPiSkillArgs(env),
|
|
||||||
nativeSkillRealPaths: Set<string> = piNativeSkillRealPaths(),
|
|
||||||
): string[] {
|
): string[] {
|
||||||
const mode = normalizePiSkillMode(env);
|
const mode = normalizePiSkillMode(env);
|
||||||
|
|
||||||
if (mode === 'discover') {
|
if (mode === 'discover') {
|
||||||
// Native Pi discovery stays on, so only force-load fleet skills it will NOT
|
return [];
|
||||||
// already find under its native roots — otherwise the same skill is
|
|
||||||
// registered twice (once natively, once via --skill). mergeSkillArgs first
|
|
||||||
// collapses any intra-forced-set realpath duplicates, mirroring 'all' mode.
|
|
||||||
const deduped = mergeSkillArgs(forcedSkillArgs);
|
|
||||||
const out: string[] = [];
|
|
||||||
for (let i = 0; i < deduped.length; i += 2) {
|
|
||||||
const dir = deduped[i + 1];
|
|
||||||
if (deduped[i] !== '--skill' || dir === undefined) continue;
|
|
||||||
if (nativeSkillRealPaths.has(skillRealPath(dir))) continue;
|
|
||||||
out.push('--skill', dir);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === 'all') {
|
if (mode === 'all') {
|
||||||
// 'all' links the full catalog; merge in the forced set so fleet-critical
|
return ['--no-skills', ...discoveredSkillArgs];
|
||||||
// skills are guaranteed present even if they live only under skills-local/.
|
|
||||||
// discoverPiSkills already covers Pi's native roots, which `--no-skills`
|
|
||||||
// would otherwise suppress.
|
|
||||||
return ['--no-skills', ...mergeSkillArgs(discoveredSkillArgs, forcedSkillArgs)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['--no-skills', ...forcedSkillArgs];
|
return ['--no-skills'];
|
||||||
}
|
}
|
||||||
|
|
||||||
function discoverPiExtension(): string[] {
|
function discoverPiExtension(): string[] {
|
||||||
|
|||||||
Reference in New Issue
Block a user