From 2a14d7c0c917ce80b085bcca774ddb34079c7fa0 Mon Sep 17 00:00:00 2001 From: NicolasVD Date: Fri, 25 Sep 2026 15:27:45 +0200 Subject: [PATCH] feat(governance): harden governance rules, hook guards, and CI pipeline for epic branching (#17) --- .agent/hooks/branch-guard.mjs | 10 ++-- .agent/hooks/plan-guard.mjs | 7 +-- .agent/hooks/post-merge-dual-sync.sh | 15 ++++++ .agent/hooks/pre-invocation-anchor.sh | 8 ++- .agent/personas/p1-product-planner.md | 22 +++----- .agent/personas/p4-system-architect.md | 5 +- .agent/rules/agent-lifecycle.md | 72 +++++++++++-------------- .agent/rules/backlog-planner.md | 54 +++++++++---------- .agent/rules/git-workflow.md | 47 +++++++--------- .agent/skills/open-pr/SKILL.md | 10 ++-- .agent/skills/plan-issue/SKILL.md | 57 ++++++++------------ .github/workflows/delivery-pipeline.yml | 8 +-- AGENTS.md | 29 +++++----- scripts/seal-issue.sh | 9 +++- scripts/sync-project-metadata.mjs | 5 +- scripts/test-runtime-guardrails.mjs | 20 +++++++ 16 files changed, 192 insertions(+), 186 deletions(-) diff --git a/.agent/hooks/branch-guard.mjs b/.agent/hooks/branch-guard.mjs index 42ecf85..11d6f56 100755 --- a/.agent/hooks/branch-guard.mjs +++ b/.agent/hooks/branch-guard.mjs @@ -38,11 +38,11 @@ function isArtifactPath(targetFile, repoRoot) { function inspectFileWrite(targetFile, currentBranch, blockedBranches, repoRoot) { if (isArtifactPath(targetFile, repoRoot)) return { allow: true }; - if (blockedBranches.includes(currentBranch)) { - return { - allow: false, - reason: `[Rule 0 Violation S-04] Direct modification to '${targetFile}' on '${currentBranch}' is blocked. Cut a dedicated feature branch first.` - }; + const isEpic = currentBranch.startsWith('epic/'); + if (blockedBranches.includes(currentBranch) || isEpic) { + const code = isEpic ? 'Rule 0.1 Violation E-04' : 'Rule 0 Violation S-04'; + const msg = isEpic ? 'Direct write on epic branch blocked. Cut a child branch first.' : 'Direct write on protected branch blocked. Cut a feature branch first.'; + return { allow: false, reason: `[${code}] ${msg}` }; } return { allow: true }; } diff --git a/.agent/hooks/plan-guard.mjs b/.agent/hooks/plan-guard.mjs index a04c3d7..c8eafa7 100755 --- a/.agent/hooks/plan-guard.mjs +++ b/.agent/hooks/plan-guard.mjs @@ -79,10 +79,11 @@ function inspectCommand(commandLine, currentBranch, blockedBranches) { }; } - if (blockedBranches.includes(currentBranch)) { + if (blockedBranches.includes(currentBranch) || currentBranch.startsWith('epic/')) { + const code = currentBranch.startsWith('epic/') ? 'Rule 0.1 Violation E-01' : 'Rule 0 Violation S-01'; return { allow: false, - reason: `[Rule 0 Violation S-01] Direct 'git commit' on protected branch '${currentBranch}' is blocked. Cut a dedicated feature branch first.` + reason: `[${code}] Direct 'git commit' on protected branch '${currentBranch}' is blocked. Cut a dedicated child feature branch first.` }; } } @@ -106,7 +107,7 @@ function inspectCommand(commandLine, currentBranch, blockedBranches) { } } - if (subcommand === 'merge' && blockedBranches.includes(currentBranch)) { + if (subcommand === 'merge' && (blockedBranches.includes(currentBranch) || currentBranch.startsWith('epic/'))) { return { allow: false, reason: `[Rule 0 Violation S-01] Direct 'git merge' on protected branch '${currentBranch}' is blocked. Merge via PR at Gate 3.5.` }; } } diff --git a/.agent/hooks/post-merge-dual-sync.sh b/.agent/hooks/post-merge-dual-sync.sh index 859a156..0552869 100755 --- a/.agent/hooks/post-merge-dual-sync.sh +++ b/.agent/hooks/post-merge-dual-sync.sh @@ -83,4 +83,19 @@ else echo "ℹ️ Issue #$ISSUE_MATCH does not carry 'source:crashlytics'. Dual-sync skipped." fi +# 4. Dynamic Base Branch Synchronization +if [ -n "$PR_NUMBER" ]; then + TARGET_BASE=$(gh pr view "$PR_NUMBER" --json baseRefName --jq '.baseRefName' 2>/dev/null || echo "main") + HEAD_BRANCH=$(gh pr view "$PR_NUMBER" --json headRefName --jq '.headRefName' 2>/dev/null || echo "") + if [ -n "$TARGET_BASE" ]; then + echo "🌿 Synchronizing base branch: $TARGET_BASE..." + git checkout "$TARGET_BASE" 2>/dev/null || true + git pull origin "$TARGET_BASE" 2>/dev/null || true + if [ -n "$HEAD_BRANCH" ] && [ "$HEAD_BRANCH" != "$TARGET_BASE" ]; then + git branch -d "$HEAD_BRANCH" 2>/dev/null || true + fi + git fetch --prune 2>/dev/null || true + fi +fi + exit 0 diff --git a/.agent/hooks/pre-invocation-anchor.sh b/.agent/hooks/pre-invocation-anchor.sh index 66a4f1b..337de35 100755 --- a/.agent/hooks/pre-invocation-anchor.sh +++ b/.agent/hooks/pre-invocation-anchor.sh @@ -9,9 +9,15 @@ if [ -f "kernel.config.json" ]; then fi if [ -n "$BRANCH" ] && [ "$BRANCH" != "$DEF" ] && [ "$BRANCH" != "main" ] && [ "$BRANCH" != "master" ]; then - cat <`. - **No match** → proceed to sealed issue creation. -### 2. Sealed Issue Creation -Issues are immutable upon creation (**Rule A — Append-Only**). -```json -{ - "owner": "", "repo": "", - "title": "(): ", - "body": "", - "assignees": [""], - "labels": ["", "source:"] -} -``` -Set at creation and assigned on GitHub Projects v2: +### 2. Just-In-Time (JIT) Issue Sealing +Specifications are elaborated locally in `implementation_plan.md` (`issue: null`). +Upon human written approval at Gate 1.4, P1 orchestrates atomic issue sealing via `./scripts/seal-issue.sh --from-plan`. +Native flags and Project v2 metadata: - **Priority**: `P0` (Blocker/Fatal) · `P1` (Major) · `P2` (Minor) -- **Estimate** (co-owned with P4): Numeric estimate in days or story points -- **Status**: `Backlog` +- **Estimate** (co-owned with P4): Numeric estimate in days or points +- **Parent**: `--parent ` if child task +- **Milestone**: `--milestone ""` +- **Status**: `Ready` ### 3. Milestone & Cycle Scoping - Create and scope GitHub Milestones (release boundaries). diff --git a/.agent/personas/p4-system-architect.md b/.agent/personas/p4-system-architect.md index 1811707..73eb22c 100644 --- a/.agent/personas/p4-system-architect.md +++ b/.agent/personas/p4-system-architect.md @@ -71,8 +71,9 @@ Assign numeric `Estimate` (days or story points, co-owned with P1). ### 5. Architectural Split Mandate (Epic Gating) When Size is `L` or `XL`: - **Reject Monolithic PRs**: Strictly forbid single large branches or PRs. -- **Decomposition DAG**: Produce a topological DAG of atomic child issues (< 300 diff lines each) referencing `parent: #` via `.agent/templates/epic-spec.md`. -- Enforce trunk-based sequential delivery with `skip-release` for intermediate child PRs. +- **Epic Branch Isolation**: Establish integration branch `epic/issue--`. +- **Decomposition DAG**: Produce a topological DAG of atomic child tasks (< 300 diff lines each) referencing `parent: #` via `.agent/templates/epic-spec.md`. +- Enforce sequential child PR delivery targeting `epic/**` with `skip-release` before a consolidated release PR to `main`. ### 6. Infrastructure Lock Protocol Flag conflicts on shared infrastructure files: `AppDatabase.kt`, `firestore.rules`, `res/values/strings.xml`. Block concurrent branches and require sequential merge. diff --git a/.agent/rules/agent-lifecycle.md b/.agent/rules/agent-lifecycle.md index a0e1326..3063525 100644 --- a/.agent/rules/agent-lifecycle.md +++ b/.agent/rules/agent-lifecycle.md @@ -41,45 +41,35 @@ Every task follows a strict **3-Phase Deterministic Lifecycle** executed by 6 En ## 2. Phase 1 · Inception & Scoping (Personas 1–4) -### Step 1.0 · Sealed Issue Creation (P1 — Product Planner) -**Absolute rule**: no branch, no file edit, no PR without a prior GitHub Issue. - -Anti-duplication first: `GitHubMCP:search_issues` → `{ "query": "repo:/ is:issue " }` - -If no duplicate, create sealed issue via `GitHubMCP:create_issue` with title `(): `, assign `@me`, and retrieve `<id>`. -Conversation naming: `[#<id>] <type>(<scope>): <title>` - -Kanban attachment: -```bash -gh project item-add <PROJECT> --owner @me --url "https://github.com/<owner>/<repo>/issues/<id>" -gh project item-edit <PROJECT> --owner @me --url "..." --field "Status" --value "Backlog" -``` +### Step 1.0 · Anti-Duplication & Local Inception (P1 & P4) +**Rule 0 (JIT Issue-First)**: Zero code, branch, or premature GitHub issues before Gate 1.4. +- Anti-duplication first: `GitHubMCP:search_issues` → `{ "query": "repo:<owner>/<repo> is:issue <keywords>" }` +- P1 & P4 elaborate specification locally in `implementation_plan.md` artifact (`issue: null`, `RequestFeedback: true`). +- P4 consolidates `Size` (XS–XL) and `Estimate` into Pillar 3 before sealing. ### Step 1.1 · Rule A — Issue & Branch Immutability (Append-Only) > [!CAUTION] > **Issue title and initial body are permanently sealed upon creation (READ-ONLY).** -- `GitHubMCP:update_issue` targeting `title` or `body` is **strictly forbidden**. +- `GitHubMCP:update_issue` targeting `title` or `body` is strictly forbidden. - Branch type is immutable: a `feat/issue-<id>-*` stays `feat/` even if Phase 2 reveals bugs. -- Never rename a branch mid-flight. Scope revisions/corrections MUST use `GitHubMCP:add_issue_comment`. +- Scope revisions/corrections MUST use `GitHubMCP:add_issue_comment`. ### Step 1.2 · 4-Pillar Spec Orchestration (P1 orchestrates P2–P4) -Each persona contributes to the canonical 4-Pillar Spec posted via `GitHubMCP:add_issue_comment`: - - **P1 Product Planner** → User Story (Gherkin) & 3-State Access Matrix (Guest/Solo/Duo). -- **P2 Design Lead** → Pillar 1: Material 3 tokens, WCAG 2.1 AAA, 4-state UI matrix, Roborazzi expectations (or explicit `N/A — No visual/UI changes`). +- **P2 Design Lead** → Pillar 1: Material 3 tokens, WCAG 2.1 AAA, 4-state UI matrix, Roborazzi expectations (or `N/A — No visual/UI changes`). - **P3 Privacy & Data Lead** → Pillar 2: Zero-PII telemetry, value bucketing, GDPR/AI Act compliance. -- **P4 System Architect** → Pillar 3: Room schema/DDL, Firestore rules delta, Clean MVI, infra locks (`AppDatabase.kt`, `firestore.rules`, `strings.xml`), Size & Estimate. +- **P4 System Architect** → Pillar 3: Room schema/DDL, Firestore rules delta, Clean MVI, infra locks, Size & Estimate. -### Step 1.3 · Dual-Write Pattern & Gate 1.4 Approval (Rule 1.5) -- **Canonical Remote Truth**: The sealed GitHub issue and its 4-Pillar comment serve as the authoritative project contract. -- **Local IDE Mirror**: To harmonize with Antigravity IDE native planning capabilities, the Orchestrator mirrors the 4-Pillar spec locally into the `implementation_plan.md` artifact (`RequestFeedback: true`, `UserFacing: true`). -- **Gate 1.4 Hard Stop**: **STOP AND WAIT**. The agent must not create any git branch or edit any source file until the user provides explicit approval. -- Post-approval: Kanban status transitions to `Ready`. +### Step 1.3 · Gate 1.4 Approval & JIT Sealing (Rule 1.5) +- **Gate 1.4 Hard Stop**: **STOP AND WAIT**. Zero branches, code edits, or remote issue creation before explicit user approval. +- **JIT Sealing via CLI**: Upon user approval, execute: + `./scripts/seal-issue.sh --from-plan [path_to_plan]` + This creates the GitHub issue, applies native flags (`--milestone`, `--parent`), attaches Project v2 metadata (`Priority`, `Size`, `Estimate`, `Status: Ready`), and initializes `epic/**` branch if Epic. ### Step 1.4 · Hand-off to Persona 5 -Phase 1 is complete. Dedicated branch creation and all source edits begin exclusively in Phase 2. +Phase 1 is complete. Dedicated branch creation and source edits begin exclusively in Phase 2. --- @@ -87,14 +77,17 @@ Phase 1 is complete. Dedicated branch creation and all source edits begin exclus ### Step 2.1 · Dedicated Branch Creation (Branch-First Isolation & WIP = 1) **WIP = 1 Pre-check**: `gh pr list --state open` must be empty. If an open PR exists, STOP until it merges. +Base branch resolution: +- Standalone / Epic: branched from `main`. +- Epic Child Task: branched from active parent branch (`epic/issue-<epic_id>-<slug>`). ```bash -git checkout main && git pull origin main +git checkout <base_branch> && git pull origin <base_branch> git checkout -b <type>/issue-<id>-<short-kebab-slug> ``` -Or remotely via `GitHubMCP:create_branch` with `from_branch: "main"`. Kanban → `In Progress`. +Kanban status transitions to `In Progress`. -**FORBIDDEN**: any file edit on `main` or starting while another PR is open. +**FORBIDDEN**: any file edit on `main` or `epic/*` or starting while another PR is open. ### Step 2.2 · Native Implementation - **Clean MVI**: `data/model` → `data/repository` → `ui/viewmodel` (StateFlow) → `ui/components` (Compose). @@ -105,7 +98,7 @@ Or remotely via `GitHubMCP:create_branch` with `from_branch: "main"`. Kanban → ### Step 2.3 · Atomic Commits on Dedicated Branch Format: `<type>(<scope>): <present-tense description>` -Push exclusively to `<type>/issue-<id>-<slug>`. Zero commits on `main`. +Push exclusively to `<type>/issue-<id>-<slug>`. Zero commits on `main` or `epic/*`. ### Step 2.4 · Hand-off to Persona 6 Phase 2 complete. Delivery and observability sync begin in Phase 3. @@ -126,32 +119,31 @@ Anti-duplicate & WIP check: `gh pr list --state open` If no open PR: ```bash -git fetch origin main && git rebase origin/main +git fetch origin <base_branch> && git rebase origin/<base_branch> git push -u origin <type>/issue-<id>-<slug> -gh pr create --base main --head <type>/issue-<id>-<slug> \ +gh pr create --base <base_branch> --head <type>/issue-<id>-<slug> \ --title "<type>(<scope>): <title>" --body-file ./walkthrough.md ``` -- Assign to `@me`. Attach to "Project Kanban" → `In Review`. +- PR targets `epic/**` with `skip-release` for intermediate child PRs; `main` for standalone or consolidated Epic release PRs. +- Assign to `@me`. Never add PRs directly to Project board (board hygiene). - PR body hosts **exclusively** the Walkthrough (`Closes #<id>`, files, test proofs, Roborazzi snapshots). -- **STOP & WAIT FOR USER APPROVAL**. +- **STOP & WAIT FOR USER APPROVAL (Gate 3.5)**. ### Step 3.3 · Zero Auto-Merge Rule > [!CAUTION] > The agent NEVER merges a PR autonomously. Present the PR link and stop. Merge only after the user says *"Tu peux merger"* or equivalent. -### Step 3.4 · Post-Merge Branch Cleanup +### Step 3.4 · Post-Merge Branch Cleanup & Dynamic Sync > [!IMPORTANT] -> Mandatory remote branch deletion. Never leave orphan branches on `origin`. +> Mandatory remote branch deletion and base synchronization via post-merge hook. +Run the post-merge hook: ```bash -git checkout main && git pull origin main -git branch -d <type>/issue-<id>-<slug> -git push origin --delete <type>/issue-<id>-<slug> -git fetch --prune +./.agent/hooks/post-merge-dual-sync.sh <pr_number> ``` -Issue auto-closed by `Closes #<id>`. Kanban → `Done`. +This hook dynamically switches to the target base branch (`epic/**` or `main`), pulls latest, and cleans local/remote branches. Issue auto-closed by `Closes #<id>`. Kanban → `Done`. ### Step 3.5 · Crashlytics Dual-Sync Closure (P6 — mandatory for `source:crashlytics`) diff --git a/.agent/rules/backlog-planner.md b/.agent/rules/backlog-planner.md index 9c003ab..0586833 100644 --- a/.agent/rules/backlog-planner.md +++ b/.agent/rules/backlog-planner.md @@ -42,21 +42,24 @@ Configured on GitHub Projects v2 board. Revisions require an `add_issue_comment` ## 4. The 10 Golden Rules of Backlog Governance -### 🚨 Rule 0 · No Branch or Code Without a GitHub Issue +### 🚨 Rule 0 · JIT Issue Sealing (Gate 1.4 Hand-off) Every task (feature, bug, refactor, chore, docs) requires: -1. A GitHub Issue created **before** any development starts. -2. A dedicated `<type>/issue-<id>-<slug>` branch cut from up-to-date `main`. -3. A 1:1 PR closing the issue (`Closes #<id>`). +1. Inception and 4-Pillars elaborated locally in `implementation_plan.md` (`issue: null`). +2. Sizing and estimation consolidated in Pillar 3 before Gate 1.4. +3. Sealing via `./scripts/seal-issue.sh --from-plan` exclusively **after** user approval at Gate 1.4. +4. Dedicated branch `<type>/issue-<id>-<slug>` cut from `main` or active Epic branch. +5. A 1:1 PR closing the issue (`Closes #<id>`). -> ⛔ **FORBIDDEN**: coding on `main`, creating a branch/PR without an Issue number. +> ⛔ **FORBIDDEN**: coding on `main` or `epic/*`, creating a branch or premature GitHub stubs before Gate 1.4. --- -### 🏛️ Rule 0.1 · Epic Gating (Size: L / XL) +### 🏛️ Rule 0.1 · Epic Branch Isolation Protocol (Size: L / XL) Issues rated `Size: L` or `Size: XL` (or `Estimate >= 3d`) are classified as **Epics**. -- **Zero Branch Guardrail**: Never branch or commit directly on an Epic issue. -- **Sequential Decomposition**: P1 & P4 decompose Epics into atomic child issues (< 300 diff lines) referencing the parent (`parent: #<id>`), merged sequentially to `main` (Trunk-Based) using [`.agent/templates/epic-spec.md`](../templates/epic-spec.md). -- **Silent Merges (`skip-release`)**: Intermediate child PRs carry `skip-release`. +- **Integration Branch**: Upon sealing, an isolated branch `epic/issue-<id>-<slug>` is created from `origin/main`. +- **Zero Direct Commits**: Committing or merging directly on `epic/**` is strictly forbidden. +- **Sequential Child Tasks**: Decomposed into atomic child tasks (< 300 diff lines) with native `--parent <epic_id>`. Each child task runs its own JIT Inception, branches from and merges into `epic/**` with `skip-release`. +- **Consolidated Release**: Once all child tasks merge into `epic/**`, a final PR targets `main`, closing the parent Epic and child issues. --- @@ -85,30 +88,26 @@ Every Issue and PR must be assigned to `@me` at creation. No unowned tickets. --- -### 📋 Rule 3 · Kanban Attachment & Milestone Linking -**At `Backlog` creation**: Attach to project board and assign Priority, Size, Estimate, Status. -**At `Ready` transition**: Link active Cycle and target Milestone: -```bash -gh issue edit <id> --milestone "<Milestone>" -gh project item-edit <PROJECT> --owner @me --url "..." --field "Status" --value "Ready" -``` +### 📋 Rule 3 · Kanban Attachment & Native Metadata +Native metadata (`Priority`, `Size`, `Estimate`, `Status`) is managed on Project v2 via `sync-project-metadata.mjs` or `seal-issue.sh`. Narrative metadata is never mixed into issue bodies. Column lifecycle: `Backlog` → `Ready` → `In Progress` → `In Review` → `Done`. --- ### 📝 Rule 4 · Issue vs PR Separation & Dual-Write Pattern -- **Issue**: hosts canonical 4-Pillar Plan via comment ([`.agent/templates/4-pillar-spec.md`](../templates/4-pillar-spec.md)). +- **Issue**: hosts canonical 4-Pillar Plan ([`.agent/templates/4-pillar-spec.md`](../templates/4-pillar-spec.md)). - **Dual-Write**: spec is mirrored to local `implementation_plan.md` artifact at Gate 1.4 for Antigravity IDE harmony. - **PR**: hosts exclusively the Walkthrough ([`.agent/templates/pr-walkthrough.md`](../templates/pr-walkthrough.md)). Zero heredocs. --- ### 🌿 Rule 5 · Branch-First Isolation +Cut branch from `main` or `epic/issue-<epic_id>-<slug>`: ```bash -git checkout main && git pull origin main +git checkout <base_branch> && git pull origin <base_branch> git checkout -b <type>/issue-<id>-<slug> ``` -**FORBIDDEN**: editing files on `main` or any generic branch before this sequence. +**FORBIDDEN**: editing files on `main` or `epic/*` before cutting the branch. --- @@ -163,26 +162,21 @@ All specifications adhere to [`.agent/templates/4-pillar-spec.md`](../templates/ --- -## 5. Inception Workflow (Chat-to-Issue) +## 5. Inception Workflow (JIT Sealing) ``` User Request │ ▼ P1: Anti-duplication (GitHubMCP:search_issues) │ - ▼ P1: Create sealed Issue + assign Priority/Estimate + link Milestone - │ - ▼ P1: Kanban attachment (Backlog) - │ - ├─▶ P2: Pillar 1 comment (Design Spec) - ├─▶ P3: Pillar 2 comment (Data & Privacy Spec) - └─▶ P4: Pillar 3 comment (Technical Blueprint) + assign Size/Estimate + ▼ P1/P4: Local 4-Pillars (implementation_plan.md, issue: null) │ - ▼ P1: Cycle & Milestone linking → Kanban to Ready + ▼ Gate 1.4: Strict halt for human written approval │ - ▼ User explicit approval (Rule 1.5 of agent-lifecycle.md) + ▼ JIT Sealing: ./scripts/seal-issue.sh --from-plan + │ (Native Milestone, Parent, Projects v2 Priority/Size/Estimate) │ - ▼ Hand-off to Personas 5 & 6 (git-workflow.md) + ▼ Hand-off to P5 (Branch cut, WIP=1) & P6 ``` --- diff --git a/.agent/rules/git-workflow.md b/.agent/rules/git-workflow.md index d5a4bc1..3b55820 100644 --- a/.agent/rules/git-workflow.md +++ b/.agent/rules/git-workflow.md @@ -52,6 +52,7 @@ ## 3. Branch Naming Convention ``` +Epic Branch : epic/issue-<id>-<short-kebab-description> Issue-tracked : <type>/issue-<id>-<short-kebab-description> Standalone : <type>/<short-kebab-description> ``` @@ -106,13 +107,14 @@ Format: `<type>(<scope>): <present-tense description>` ### Phase A — Implementation (Persona 5) 1. **Branch creation & In-Progress Sync**: + - Standalone / Epic: branched from `main`. + - Epic child task: branched from active parent branch (`epic/issue-<epic_id>-<slug>`). ```bash - git checkout main && git pull + git checkout <base_branch> && git pull git checkout -b <type>/issue-<id>-<slug> - # Automatically triggered in background via .agent/hooks/post-checkout (or run manually): node .agent/sidecars/sync-issue-progress.mjs <id> ``` - > **Automated Sidecar Binding**: The Git hook `.agent/hooks/post-checkout` triggers `.agent/sidecars/sync-issue-progress.mjs <id>` in the background upon checkout, binding the open milestone, active cycle, and moving the issue to **"In Progress"** on GitHub Project #1. + > **Automated Sidecar Binding**: `.agent/hooks/post-checkout` triggers `.agent/sidecars/sync-issue-progress.mjs <id>` in the background upon checkout, binding the open milestone, active cycle, and moving the issue to **"In Progress"** on GitHub Project #2. 2. **Implementation**: Clean MVI (`data/model → data/repository → ui/viewmodel → ui/components`). Zero hardcoded strings. 3-State Access parity. 3. **Atomic commits** exclusively on `<type>/issue-<id>-<slug>`. @@ -131,17 +133,15 @@ Format: `<type>(<scope>): <present-tense description>` 6. **PR with Walkthrough** (using [`.agent/templates/pr-walkthrough.md`](../templates/pr-walkthrough.md) via `--body-file`): ```bash - gh pr create --base main --head <type>/issue-<id>-<slug> \ + gh pr create --base <base_branch> --head <type>/issue-<id>-<slug> \ --title "<type>(<scope>): <title>" --body-file ./walkthrough.md ``` - Assign to `@me`. Attach to "Project Kanban". **STOP & WAIT FOR USER APPROVAL**. + - Target base: `epic/**` with `skip-release` for intermediate child tasks; `main` for standalone or consolidated Epic release PRs. + - Assign to `@me`. Never add PRs directly to Project board (board hygiene). **STOP & WAIT FOR USER APPROVAL (Gate 3.5)**. 7. **Post-merge cleanup** (after explicit user approval): ```bash - git checkout main && git pull origin main - git branch -d <type>/issue-<id>-<slug> - git push origin --delete <type>/issue-<id>-<slug> - git fetch --prune + ./.agent/hooks/post-merge-dual-sync.sh <pr_number> ``` 8. **Crashlytics Dual-Sync Closure** (mandatory if `source:crashlytics`): @@ -169,17 +169,13 @@ Allowed release attachments: - ✅ Auto-generated release notes (GitHub `generate_release_notes: true`) Strictly forbidden from GitHub Release attachments: -- ⛔ `agent.md` -- ⛔ `DESIGN.md` -- ⛔ `design-system.md` +- ⛔ `agent.md`, `DESIGN.md`, `design-system.md` - ⛔ Any `.agent/rules/*.md`, `.agent/playbooks/*.md`, or `.agent/skills/*.md` file - ⛔ Any internal markdown or governance document -Checksum generation (before GitHub Release attachment): +Checksum generation: ```bash -sha256sum app/build/outputs/apk/release/app-release.apk \ - app/build/outputs/apk/debug/app-debug.apk \ - > checksums.sha256 +sha256sum app/build/outputs/apk/release/app-release.apk > checksums.sha256 ``` --- @@ -187,25 +183,18 @@ sha256sum app/build/outputs/apk/release/app-release.apk \ ## 8. Automated CI/CD Architecture ### A. Delivery Pipeline & Quality Gate (`.github/workflows/delivery-pipeline.yml`) -Unified workflow for PR checks, `main` push quality gate, dev distribution, and milestone releases: +Unified workflow for PR checks (`main`, `epic/**`), `main` push quality gate, dev distribution, and milestone releases: - **PR & Push `main`**: JDK 21 (Temurin) · `./scripts/validate-docs.sh` · `./gradlew codeSanityCheck`. - Cache: `gradle/actions/setup-gradle@v4` with `cache-read-only: true`. ``` -PR targeting main - ──► Job 1: Quality Gate (codeSanityCheck + validate-docs.sh) - -Push/Merge main (Tier 1) - ──► Job 1: Quality Gate (codeSanityCheck + validate-docs.sh) - ──► Job 2: Build + Firebase App Distribution (admin, testers) — NO tag, NO GitHub Release - -Milestone 100% complete → manual trigger (Tier 2) - ──► Job 3: SemVer tag (vX.Y.Z) + GitHub Release (APK + checksums only, zero internal docs) +PR (main / epic/**) ──► Job 1: Quality Gate (codeSanityCheck + validate-docs.sh) +Push main (Tier 1) ──► Job 1: Quality Gate + Job 2: Firebase App Distribution +Milestone (Tier 2) ──► Job 3: SemVer tag + GitHub Release (APK + checksums only) ``` ### C. Local Distribution (Manual / Ad-hoc) ```bash -./scripts/deploy-app-distribution.sh # latest commit message -./scripts/deploy-app-distribution.sh "Fix Duo Mode sync" # custom release notes -./scripts/deploy-app-distribution.sh "Release Notes" release # signed Release APK +./scripts/deploy-app-distribution.sh "Release Notes" [release] +``` ``` diff --git a/.agent/skills/open-pr/SKILL.md b/.agent/skills/open-pr/SKILL.md index 6a7b3f7..7b516dc 100644 --- a/.agent/skills/open-pr/SKILL.md +++ b/.agent/skills/open-pr/SKILL.md @@ -46,16 +46,18 @@ gh pr list --state open ``` If an open PR exists, STOP. The active PR must be reviewed and merged first. -### Step 3: Rebase on Main & Push Dedicated Branch +### Step 3: Rebase on Base Branch & Push Dedicated Branch ```bash CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) -git fetch origin main && git rebase origin/main +BASE_BRANCH="main" # Or epic/issue-<epic_id>-<slug> if child task +git fetch origin "$BASE_BRANCH" && git rebase "origin/$BASE_BRANCH" git push -u origin "$CURRENT_BRANCH" ``` ### Step 4: Generate Walkthrough from Template Populate [`.agent/templates/pr-walkthrough.md`](../../templates/pr-walkthrough.md) in `./walkthrough.md`: - Link source issue: `Closes #<issue_id>` +- Base branch: `{{BASE_BRANCH|main}}` - Summary of changes - Affected files and diff statistics - Quality Airbag verification table @@ -66,7 +68,7 @@ Populate [`.agent/templates/pr-walkthrough.md`](../../templates/pr-walkthrough.m Inspect staged changes and conventional commit type: - If type is `docs` or `chore(governance)`, OR - If no files under `app/` are touched, OR -- If PR is an intermediate child issue of an Epic: +- If PR is an intermediate child task of an Epic: **Append `--label "skip-release"`** (or pass `["skip-release"]` in labels array to MCP). ```json @@ -75,7 +77,7 @@ GitHubMCP:create_pull_request { "repo": "<repo>", "title": "<type>(<scope>): <description>", "head": "<CURRENT_BRANCH>", - "base": "main", + "base": "<BASE_BRANCH>", "body": "..." } ``` diff --git a/.agent/skills/plan-issue/SKILL.md b/.agent/skills/plan-issue/SKILL.md index 983ce62..9e041ff 100644 --- a/.agent/skills/plan-issue/SKILL.md +++ b/.agent/skills/plan-issue/SKILL.md @@ -38,45 +38,30 @@ GitHubMCP:search_issues { "q": "repo:<owner>/<repo> is:issue <keywords>" } ``` If an open issue already covers the scope, switch to that issue or add context via comment. -### Step 3: Sealed Issue Creation & Native Project Metadata (Persona 1) -If new, P1 creates the issue assigned strictly to `@me` (`@me`): -```json -GitHubMCP:create_issue { - "owner": "@me", - "repo": "<repo>", - "title": "<type>(<scope>): <explicit title>", - "body": "## Context & User Story\n...", - "labels": ["<type>", "source:internal"], - "assignees": ["@me"] -} -``` -P1 assigns the native **GitHub Projects v2 Metadata**: -- `Priority`: `P0` / `P1` / `P2` -- `Size`: `XS` / `S` / `M` / `L` / `XL` (determined with P4) -- `Estimate`: Numeric estimate in points or days (co-owned with P4) -- `Status`: `Backlog` - -### Step 4: 4-Pillar Spec Orchestration & Conditional P2 Design Gate -Consortium members populate [`.agent/templates/4-pillar-spec.md`](../../templates/4-pillar-spec.md) through contextual triage: +### Step 3: Local 4-Pillar Spec Orchestration & Sizing Consolidation +Consortium members populate [`.agent/templates/4-pillar-spec.md`](../../templates/4-pillar-spec.md) locally in `implementation_plan.md` (`issue: null`): 1. **P1 (Product Planner)**: User Story (Gherkin) & 3-State Access Matrix (Guest/Solo/Duo). -2. **P2 (Design Lead — Conditional Gate)**: - - **UI Changes**: If changes touch `@Composable`, screens, or theme tokens, P2 defines Material 3 tokens, component states, and Roborazzi snapshot expectations. - - **Non-UI Changes**: For pure backend, Room, Firestore rules, CI/CD, scripts, or chores, mark: `N/A — No visual/UI changes`. - - **Explicit User Override**: If prompt explicitly requests to skip design (e.g. *"skip design"*), bypass P2 immediately. -3. **P3 (Privacy & Data Lead)**: Pillar 2 Data Spec (Zero-PII telemetry, event taxonomy, GDPR). -4. **P4 (System Architect)**: Pillar 3 Technical Blueprint (Room, Firestore, architecture boundaries, `Size` and `Estimate`). +2. **P2 (Design Lead — Conditional Gate)**: Material 3 tokens, component states, Roborazzi expectations (or `N/A — No visual/UI changes`). Bypass immediately if requested (*"skip design"*). +3. **P3 (Privacy & Data Lead)**: Zero-PII telemetry, event taxonomy, GDPR/AI Act compliance. +4. **P4 (System Architect)**: Room, Firestore, Clean MVI, and consolidated `Size` (XS–XL) and `Estimate`. -### Step 4.1: Complexity L/XL Route — Epic Decomposition (Rule 0.1) +### Step 3.1: Complexity L/XL Route — Epic Decomposition (Rule 0.1) If `Size` is `L` or `XL` (or `Estimate >= 3d`): -- **Classify as Epic**: Zero Branch Guardrail — strictly forbidden to branch or commit on this issue. -- **Decompose**: P1 & P4 produce [`.agent/templates/epic-spec.md`](../../templates/epic-spec.md) detailing the child-issue DAG (< 300 diff lines each). -- Intermediate child PRs carry the `skip-release` label. +- P1 & P4 produce [`.agent/templates/epic-spec.md`](../../templates/epic-spec.md) detailing sequential child tasks (< 300 diff lines each). +- Epic integration branch `epic/issue-<id>-<slug>` will be created upon sealing. +- Intermediate child PRs carry `skip-release`. -### Step 5: Publish Spec & Dual-Write Pattern (Rule A) -- **Canonical Remote Source**: Post compiled spec via `GitHubMCP:add_issue_comment`. Never edit original issue body. -- **Local IDE Mirror**: Mirror 4-Pillar spec into `implementation_plan.md` artifact (`RequestFeedback: true`, `UserFacing: true`) prepending the YAML state header. - -### Step 6: Step 1.4 Gating Check (STOP & WAIT) +### Step 4: Step 1.4 Gating Check (STOP & WAIT) > [!CAUTION] -> **Step 1.4 Gate**: Do NOT create branches or write code until the user gives explicit written approval on the plan (Rule 1.5 of `agent-lifecycle.md`). +> **Step 1.4 Gate**: Do NOT create branches, code edits, or remote GitHub issues until the user provides explicit written approval on `implementation_plan.md` (Rule 1.5). + +### Step 5: Just-In-Time (JIT) Sealing via CLI +Upon explicit human approval: +```bash +./scripts/seal-issue.sh --from-plan [path_to_plan] +``` +- Creates the GitHub issue assigned to `@me` with native flags (`--milestone`, `--parent`). +- Synchronizes Project v2 metadata atomically (`Priority`, `Size`, `Estimate`, `Status: Ready`). +- Automatically cuts and pushes `epic/issue-<id>-<slug>` from `origin/main` if Epic. +- Updates local `implementation_plan.md` header with sealed `issue: <id>`, `branch: ...`, and `status: approved`. diff --git a/.github/workflows/delivery-pipeline.yml b/.github/workflows/delivery-pipeline.yml index 4811f75..3ef5cd9 100644 --- a/.github/workflows/delivery-pipeline.yml +++ b/.github/workflows/delivery-pipeline.yml @@ -4,6 +4,7 @@ on: pull_request: branches: - main + - 'epic/**' push: branches: - main @@ -108,9 +109,10 @@ jobs: id: check_code run: | if [ "${{ github.event_name }}" = "pull_request" ]; then - # Compare les fichiers modifiés par rapport à la branche main - CHANGED_FILES=$(git diff --name-only origin/main...HEAD) - echo "Fichiers modifiés :" + BASE_REF="${{ github.base_ref || 'main' }}" + # Compare les fichiers modifiés par rapport à la branche cible du PR + CHANGED_FILES=$(git diff --name-only "origin/${BASE_REF}...HEAD") + echo "Fichiers modifiés (vs origin/${BASE_REF}) :" echo "$CHANGED_FILES" if echo "$CHANGED_FILES" | grep -qE '^(app/|gradle/|build\.gradle\.kts|settings\.gradle\.kts|kernel\.config\.json)'; then diff --git a/AGENTS.md b/AGENTS.md index 1304dee..906779f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,31 +20,32 @@ The agent's root identity on **Agentic Android Delivery Kernel** is strictly the ```mermaid flowchart LR A[Prompt Inception] --> P1[P1: Product Planner] - P1 -->|Sealed Issue + 4 Pillars| G14{Gate 1.4: Inception Halt} - G14 -->|Human Written Approval| P5[P5: Software Engineer] - P5 -->|Branch + Tests Green| P6[P6: Release Manager] + P1 -->|Local 4 Pillars| G14{Gate 1.4: Inception Halt} + G14 -->|Human Approval| Seal[JIT Sealer: seal-issue.sh] + Seal -->|Branch + WIP=1| P5[P5: Software Engineer] + P5 -->|Tests Green| P6[P6: Release Manager] P6 -->|PR Walkthrough| G35{Gate 3.5: Auto-Merge Lock} G35 -->|Human Confirm| Main[Squash Merge & Dual-Sync] ``` 1. **Phase 1 — Inception (Personas 1–4)**: - Activates **Persona 1 (Product Planner)**: anti-duplicate search via `GitHubMCP:search_issues`. - - P1 calls `GitHubMCP:create_issue` to seal the tracking issue assigned to `@me` with native GitHub Projects v2 metadata (`Priority`, `Size`, `Estimate`). - - P1 orchestrates the canonical 4-Pillar Spec comment via `GitHubMCP:add_issue_comment`. - - **Dual-Write Pattern**: Mirrors the spec locally into `implementation_plan.md` artifact (`RequestFeedback: true`, `UserFacing: true`) for IDE alignment. - - **Gate 1.4 Verification (STOP & WAIT)**: Strict halt. Zero branches or code edits before explicit written approval. + - P1 & P4 elaborate technical specification locally in `implementation_plan.md` artifact (`RequestFeedback: true`, `UserFacing: true`). + - P4 consolidates `Size` (XS–XL) and `Estimate` into Pillar 3 prior to sealing. + - **Gate 1.4 Verification (STOP & WAIT)**: Strict halt. Zero branches, code edits, or premature GitHub issues before explicit written approval. + - Upon Gate 1.4 approval: `./scripts/seal-issue.sh --from-plan` seals the tracking issue assigned to `@me` with native flags (`--milestone`, `--parent`), associates Project v2 fields (`Priority`, `Size`, `Estimate`, `Status: Ready`), and initializes integration branch if Epic. 2. **Phase 2 — Delivery (Persona 5)**: - - Activated **ONLY** after explicit Gate 1.4 approval. - - P5 cuts dedicated branch `<type>/issue-<id>-<slug>` from default branch (WIP = 1). + - Activated **ONLY** after explicit Gate 1.4 approval and JIT sealing. + - P5 cuts dedicated branch `<type>/issue-<id>-<slug>` from default branch or active Epic branch (WIP = 1). - P5 implements code adhering to [`.agent/playbooks/android-standards.md`](.agent/playbooks/android-standards.md). - P5 executes the Quality Airbag (`./scripts/quality-check.sh`, `./scripts/validate-docs.sh`). 3. **Phase 3 — Release (Persona 6)**: - Activates **Persona 6 (Release Manager)**: pushes branch and opens PR via `GitHubMCP:create_pull_request`. - - Labeled `skip-release` for docs, governance, and non-`app/` diffs. + - PR targets `epic/**` with `skip-release` for intermediate child tasks; targets `main` for standalone or final consolidated Epic PRs. - **Gate 3.5 — Zero Auto-Merge Lock**: Halts with PR link. Merges exclusively after explicit user confirmation (*"Tu peux merger"*). - - Executes squash merge, runs `.agent/hooks/post-merge-dual-sync.sh`, and prunes branches. + - Executes squash merge, runs `.agent/hooks/post-merge-dual-sync.sh` (dynamically synchronizing base branch), and prunes branches. ### 1.2 Conditional P2 Design Gate @@ -68,10 +69,10 @@ Pillar 1 (Design Spec) enforcement is contextual and adaptive: ## 2. Core Governance & System Invariants -1. **Rule 0 (Issue-First)**: Zero code, branch, or PR without a prior sealed GitHub Issue. +1. **Rule 0 (JIT Issue-First)**: Zero code, branch, or premature GitHub issue stubs before Gate 1.4. Issues are sealed Just-In-Time via `./scripts/seal-issue.sh`. 2. **Rule A (Append-Only Immutability)**: Issue title and body are READ-ONLY once created. Revisions appended via comments. -3. **Rule 0.1 (Epic Gating)**: Issues evaluated as `Size: L` or `Size: XL` require decomposition into atomic child issues (< 300 diff lines) via [`.agent/templates/epic-spec.md`](.agent/templates/epic-spec.md). Zero branching on Epics. -4. **WIP = 1**: Exactly 1 issue `In Progress` and at most 1 PR `In Review` at any time. +3. **Rule 0.1 (Epic Branch Isolation Protocol)**: Epics (`Size: L/XL`) establish an isolated integration branch `epic/issue-<id>-<slug>`. Direct commits on `epic/**` are strictly forbidden. Implementation proceeds via atomic child issues (< 300 diff lines) branching from and merging into `epic/**` before a consolidated release PR lands on `main`. +4. **WIP = 1**: Exactly 1 issue `In Progress` and at most 1 PR `In Review` at any time (Macro Epic in progress, Micro child WIP = 1). 5. **Dual-Write Pattern**: Canonical spec resides on GitHub Issue; mirrored to local `implementation_plan.md` for IDE harmony. 6. **Zero Auto-Merge**: The agent never merges autonomously without human confirmation. diff --git a/scripts/seal-issue.sh b/scripts/seal-issue.sh index 017cdee..ce1034d 100755 --- a/scripts/seal-issue.sh +++ b/scripts/seal-issue.sh @@ -153,13 +153,20 @@ if [ "$DRY_RUN" = true ]; then exit 0 fi +LABEL_TYPE="$TYPE" +case "$TYPE" in + feat) LABEL_TYPE="feature" ;; + fix) LABEL_TYPE="bug" ;; + docs) LABEL_TYPE="documentation" ;; +esac + echo "🚀 Sealing Issue on GitHub via CLI..." GH_ARGS=( issue create --title "$FORMATTED_TITLE" --body-file "$TEMP_BODY" --assignee "@me" - --label "${TYPE},source:internal" + --label "${LABEL_TYPE},source:internal" ) if [ -n "$MILESTONE" ]; then GH_ARGS+=(--milestone "$MILESTONE"); fi if [ -n "$PARENT" ]; then GH_ARGS+=(--parent "$PARENT"); fi diff --git a/scripts/sync-project-metadata.mjs b/scripts/sync-project-metadata.mjs index 533c9c3..294ef2f 100755 --- a/scripts/sync-project-metadata.mjs +++ b/scripts/sync-project-metadata.mjs @@ -127,10 +127,7 @@ try { if (!opt) return; if (!dryRun) { - gql( - `mutation($p:ID!,$i:ID!,$f:ID!, $v:String!){updateProjectV2ItemFieldValue(input:{projectId:$p,itemId:$i,fieldId:$f,value:{singleSelectOptionId:$v}}){projectV2Item{id}}}`, - { p: project.id, i: itemId, f: f.id, v: opt.id } - ); + runGh(`api graphql -f query='mutation { updateProjectV2ItemFieldValue(input: { projectId: "${project.id}", itemId: "${itemId}", fieldId: "${f.id}", value: { singleSelectOptionId: "${opt.id}" } }) { projectV2Item { id } } }'`); } results.updated.push({ field: f.name, value: opt.name }); } diff --git a/scripts/test-runtime-guardrails.mjs b/scripts/test-runtime-guardrails.mjs index 8070f68..e87ee28 100644 --- a/scripts/test-runtime-guardrails.mjs +++ b/scripts/test-runtime-guardrails.mjs @@ -146,6 +146,20 @@ const testCasesPlanGuard = [ expectAllow: false, expectedErrorSubstring: 'Direct \'git push\' targeting protected branch' }, + { + name: 'Reject git commit on epic integration branch', + cmd: 'git commit -m "feat: illegal direct commit"', + branch: 'epic/issue-12-test-epic', + expectAllow: false, + expectedErrorSubstring: 'Direct \'git commit\' on protected branch' + }, + { + name: 'Reject git merge on epic integration branch', + cmd: 'git merge feature-branch', + branch: 'epic/issue-12-test-epic', + expectAllow: false, + expectedErrorSubstring: 'Direct \'git merge\' on protected branch' + }, // Permitted Commands { @@ -233,6 +247,12 @@ const testCasesBranchGuard = [ branch: 'main', expectAllow: false }, + { + name: 'Deny direct write to source file on epic integration branch', + file: 'app/src/main/java/MainActivity.kt', + branch: 'epic/issue-12-test-epic', + expectAllow: false + }, // Legitimate External Brain Artifacts: Must be ALLOWED on any branch {