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/.agent/templates/4-pillar-spec.md b/.agent/templates/4-pillar-spec.md index 9800077..407f44b 100644 --- a/.agent/templates/4-pillar-spec.md +++ b/.agent/templates/4-pillar-spec.md @@ -1,7 +1,7 @@ --- template: 4-pillar-spec -version: 2.0.0 -usage: Post as GitHubMCP:add_issue_comment on the tracking issue after P1 creates the sealed issue. +version: 2.1.0 +usage: Authoritative sealed body for GitHub Issues created at Gate 1.4 via scripts/seal-issue.sh. --- # 4-Pillar Inception Spec β€” Issue #{{ISSUE_ID}}: {{ISSUE_TITLE}} @@ -37,8 +37,6 @@ Feature: {{FEATURE_NAME}} - [ ] Zero regressions on existing Roborazzi snapshots. - [ ] `./scripts/quality-check.sh` exits 0. -**Metadata**: **Milestone**: `{{MILESTONE_NAME}}` Β· **Priority**: `{{PRIORITY_P0_P1_P2}}` Β· **Size**: `{{SIZE_XS_TO_XL}}` Β· **Estimate**: `{{ESTIMATE}}` - --- ## Pillar 1 Β· Design Spec (P2 β€” Design Lead) diff --git a/.agent/templates/epic-spec.md b/.agent/templates/epic-spec.md index 556b10c..55c0975 100644 --- a/.agent/templates/epic-spec.md +++ b/.agent/templates/epic-spec.md @@ -1,25 +1,23 @@ --- template: epic-spec -version: 1.0.0 -usage: Use for Complexity L issues. Post as GitHubMCP:add_issue_comment on the parent Epic. -guardrail: Zero Branch on Epic β€” child issues must be created for implementation. +version: 2.0.0 +usage: Authoritative sealed body for Epic Issues created at Gate 1.4 via scripts/seal-issue.sh. +guardrail: Epic Branch Isolation β€” child tasks branch from and merge into epic/issue-<id>-<slug>. --- # Epic Architectural Spec: #{{EPIC_ID}} β€” {{EPIC_TITLE}} -> **Zero Branch Guardrail**: This issue is an architectural container. -> Creating branches or committing code directly on #{{EPIC_ID}} is **strictly forbidden**. -> Implementation proceeds exclusively via atomic child issues (< 300 diff lines) merged sequentially to `main`. +> **Epic Branch Isolation Guardrail**: This issue is an architectural container. +> An isolated integration branch `epic/issue-{{EPIC_ID}}-{{EPIC_SLUG}}` is created upon sealing. +> Direct commits on `epic/**` are strictly forbidden. Implementation proceeds via atomic child issues (< 300 diff lines) branching from and merging into the Epic integration branch before a final release PR lands on `main`. --- ## 🎯 1. Vision & Strategic Objectives - **Goal**: {{EPIC_GOAL_SUMMARY}} -- **Priority**: {{PRIORITY_P0_P1_P2}} -- **Size**: `{{SIZE_L_OR_XL}}` (Epic Gated) -- **Estimate**: {{ESTIMATE_DAYS_OR_POINTS}} -- **Target Milestone**: `{{MILESTONE_NAME}}` +- **Core Value & User Impact**: {{VALUE_PROPOSITION_SUMMARY}} +- **Target Scope**: {{FUNCTIONAL_SCOPE_SUMMARY}} --- @@ -34,28 +32,28 @@ guardrail: Zero Branch on Epic β€” child issues must be created for implementati ## πŸ—ΊοΈ 3. Sequential Decomposition DAG -Child issues must be implemented in topological order. Intermediate child PRs carry the `skip-release` label to merge silently without triggering tags or distribution builds. +Child issues must be implemented in topological order with strict WIP = 1. Child PRs target the Epic integration branch with `skip-release`. -| Seq | Issue Title | Type | Target Diff | Depends On | Silent (`skip-release`) | +| Seq | Child Issue Title | Type | Target Diff | Depends On | PR Base Target | |:---|:---|:---|:---|:---|:---| -| 01 | `{{CHILD_1_TITLE}}` | `{{TYPE}}` | < 300 lines | None | βœ… Yes | -| 02 | `{{CHILD_2_TITLE}}` | `{{TYPE}}` | < 300 lines | Step 01 | βœ… Yes | -| 03 | `{{CHILD_3_TITLE}}` | `{{TYPE}}` | < 300 lines | Step 02 | ❌ No (Triggers Release) | +| 01 | `{{CHILD_1_TITLE}}` | `{{TYPE}}` | < 300 lines | None | `epic/issue-{{EPIC_ID}}-...` | +| 02 | `{{CHILD_2_TITLE}}` | `{{TYPE}}` | < 300 lines | Step 01 | `epic/issue-{{EPIC_ID}}-...` | +| 03 | `{{CHILD_3_TITLE}}` | `{{TYPE}}` | < 300 lines | Step 02 | `epic/issue-{{EPIC_ID}}-...` | --- -## πŸ“‹ 4. Child Issues Checklist +## πŸ“‹ 4. Native Sub-Issues Architecture -- [ ] #{{CHILD_1_ID}} β€” `{{CHILD_1_TITLE}}` (parent: #{{EPIC_ID}}) -- [ ] #{{CHILD_2_ID}} β€” `{{CHILD_2_TITLE}}` (parent: #{{EPIC_ID}}) -- [ ] #{{CHILD_3_ID}} β€” `{{CHILD_3_TITLE}}` (parent: #{{EPIC_ID}}) +Child tasks are sealed sequentially via JIT (`./scripts/seal-issue.sh --from-plan`) with native `--parent {{EPIC_ID}}`. +Tracking, status, and completion roll-ups are managed natively via GitHub's Sub-issues hierarchy on this ticket. --- ## βœ… 5. Definition of Done (Epic Level) -- [ ] All child issues merged sequentially into `main` with 100% CI pass. +- [ ] All child issues merged sequentially into `epic/issue-{{EPIC_ID}}-{{EPIC_SLUG}}` with 100% CI pass. - [ ] Expand/Contract phases validated (Phase 1 Expand tests pass; Phase 2 Contract safely executed). - [ ] Zero regressions in existing Roborazzi snapshots and Firestore security rules. -- [ ] Final child PR merged without `skip-release` label, successfully cutting release. -- [ ] Milestone completion check passed; Epic issue #{{EPIC_ID}} closed. +- [ ] Full Quality Airbag passed on the consolidated Epic integration branch. +- [ ] Final Epic PR (`epic/issue-{{EPIC_ID}}-... β†’ main`) merged with consolidated commit message closing #{{EPIC_ID}} and all child issues. +- [ ] Epic issue #{{EPIC_ID}} closed; release train successfully triggered. diff --git a/.agent/templates/pr-walkthrough.md b/.agent/templates/pr-walkthrough.md index 21d57b5..f94023a 100644 --- a/.agent/templates/pr-walkthrough.md +++ b/.agent/templates/pr-walkthrough.md @@ -16,9 +16,10 @@ Closes #{{ISSUE_ID}} ## πŸ“Œ Summary -**Branch**: `{{BRANCH_NAME}}` β†’ `main` +**Branch**: `{{BRANCH_NAME}}` β†’ `{{BASE_BRANCH|main}}` **Type**: `{{TYPE}}({{SCOPE}})` **Related Issue**: [#{{ISSUE_ID}} {{ISSUE_TITLE}}](https://github.com/{{OWNER}}/{{REPO}}/issues/{{ISSUE_ID}}) +*(If child task)* **Parent Epic**: [#{{PARENT_ID}} {{PARENT_TITLE}}](https://github.com/{{OWNER}}/{{REPO}}/issues/{{PARENT_ID}}) {{CHANGE_SUMMARY_BULLET_1}} {{CHANGE_SUMMARY_BULLET_2}} diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 0f85739..98cd8a6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,7 +1,7 @@ name: πŸ› Bug Report description: Report a malfunction, regression, or synchronization issue title: "fix(<scope>): <short descriptive bug title>" -labels: ["type:bug"] +labels: ["bug"] body: - type: markdown attributes: @@ -14,14 +14,13 @@ body: label: Scope / Affected Domain description: Choose the primary domain where the bug occurs. options: - - tasks (Creation, editing, deletion, mental load completion) - - matrix (Eisenhower matrix display or interaction) - - duo (Real-time synchronization, duo pairing) - - analytics (Gauge calculation, trends, charts) - - auth (Google Sign-In, sessions) - - sync (Firestore, Room, offline) - - ui (Display, visual glitch, animation) - - ai (Gemini classification, API timeout) + - core (Local Room database, data repositories, clean architecture) + - ui (Jetpack Compose components, themes, animations, Roborazzi) + - ai (Gemini multimodal logic, smart agents, function calling) + - sync (Firestore synchronization, offline-first reconciliation) + - analytics (Zero-PII telemetry, adoption metrics, audit trails) + - tooling (Operational scripts, CI/CD automation, hooks) + - governance (Rules, personas, multi-agent protocol, templates) validations: required: true - type: textarea @@ -29,7 +28,7 @@ body: attributes: label: Bug Description description: Clear and concise description of the issue encountered. - placeholder: "Example: When switching to the Duo tab, the loading state remains stuck..." + placeholder: "Example: When navigating back from the details screen, the state resets unexpectedly..." validations: required: true - type: textarea @@ -39,8 +38,8 @@ body: description: What are the exact steps leading to the problem? placeholder: | 1. Open the application - 2. Go to the 'Matrix' tab - 3. Drag a task to the 'Delegate' quadrant + 2. Navigate to the target screen + 3. Trigger the action 4. Observe the error... validations: required: true @@ -49,7 +48,7 @@ body: attributes: label: Expected vs. Actual Behavior description: What should have happened, and what actually occurred? - placeholder: "Expected: Task moves to target quadrant with smooth animation.\nActual: Crash or task remains unchanged." + placeholder: "Expected: State is preserved smoothly with no animation glitch.\nActual: State resets or crash occurs." validations: required: true - type: input @@ -57,7 +56,7 @@ body: attributes: label: Test Environment description: Device model, Android OS version, application version. - placeholder: "Pixel 8 Pro - Android 14 / Emulator API 34 - v0.1.0" + placeholder: "Pixel 8 Pro - Android 14 / Emulator API 34 - v1.0.0" validations: required: false - type: textarea diff --git a/.github/ISSUE_TEMPLATE/feature_idea.yml b/.github/ISSUE_TEMPLATE/feature_idea.yml index 1a12495..db3ebc6 100644 --- a/.github/ISSUE_TEMPLATE/feature_idea.yml +++ b/.github/ISSUE_TEMPLATE/feature_idea.yml @@ -1,34 +1,34 @@ name: πŸ’‘ Feature Proposal description: Propose a new feature or improvement for Agentic Android Kernel title: "feat(<scope>): <short descriptive title>" -labels: ["type:feature"] +labels: ["feature"] body: - type: markdown attributes: value: | ### 🧠 Feature Proposal for Agentic Android Kernel - Thank you for contributing to mental load reduction! Please fill out this form to structure your feature following the Spec-Driven (4 Pillars) framework. + Thank you for contributing to the Agentic Android Delivery Kernel! Please fill out this form to propose a new feature adhering to the Spec-Driven (4 Pillars) framework. - type: dropdown id: scope attributes: label: Scope / Functional Domain description: Choose the primary domain affected by this feature. options: - - tasks (Mental load management, recurrence, cycles) - - matrix (Eisenhower matrix, 4-quadrant prioritization) - - duo (Couple mode, task rotation, shared synchronization) - - analytics (Trend charts, mental balance, gauges) - - core (Local Room database, Firestore synchronization, Auth) - - ai (Gemini automated classification, smart suggestions) - - ui (Visual components, serene animations, Theme) + - core (Local Room database, data repositories, clean architecture) + - ui (Jetpack Compose components, themes, animations, Roborazzi) + - ai (Gemini multimodal logic, smart agents, function calling) + - sync (Firestore synchronization, offline-first reconciliation) + - analytics (Zero-PII telemetry, adoption metrics, audit trails) + - tooling (Operational scripts, CI/CD automation, hooks) + - governance (Rules, personas, multi-agent protocol, templates) validations: required: true - type: textarea id: problem-context attributes: - label: Context & Mental Load Problem Statement - description: What is the real problem or friction experienced by the user or duo? How does it increase mental load? - placeholder: "Example: Recurring household tasks are often manually reassigned, causing friction and asymmetric load..." + label: Context & Problem Statement + description: What is the real problem or friction experienced? Why is this feature needed? + placeholder: "Example: Offline users currently experience delay when syncing items..." validations: required: true - type: textarea @@ -36,7 +36,7 @@ body: attributes: label: Proposed Solution & User Experience (UX) description: Describe precisely the desired solution, user interactions, and expected outcome. - placeholder: "Example: Introduce an automatic alternating rotation rule upon completion of each recurring task..." + placeholder: "Example: Introduce an exponential backoff retry mechanism with local Room queue..." validations: required: true - type: textarea @@ -44,7 +44,7 @@ body: attributes: label: Desired Impact Metrics & Telemetry description: Which telemetry events (event_name, parameters) or usage metrics will measure adoption and success? - placeholder: "Example:\n- event_name: 'recurring_task_created' (params: frequency, is_duo_rotating, area)\n- event_name: 'duo_rotation_assigned' (params: frequency, rotation_role, cycle_count)" + placeholder: "Example:\n- event_name: 'feature_activated' (params: feature_id, source)\n- event_name: 'sync_completed' (params: latency_ms, items_count)" validations: required: false - type: textarea @@ -52,7 +52,7 @@ body: attributes: label: Technical Architecture & Implementation Notes (Optional) description: Anticipated implementation details (Room Schema, StateFlow, ViewModel, Jetpack Compose, Gemini, Firestore). - placeholder: "Example: Add 'rotationRule' field to MentalLoadItem, update SecondBrainRepository..." + placeholder: "Example: Add 'priority' field to Entity, update KernelRepository..." validations: required: false - type: checkboxes @@ -65,7 +65,7 @@ body: required: true - label: 100% Jetpack Compose UI compliant with Serene UI principles (gentle animations, zero friction) required: true - - label: Formalized Analytics & Observability contract + - label: Formalized Analytics & Observability contract (Zero-PII) required: true - label: Unit tests & Robolectric (100% passing on './scripts/quality-check.sh') required: true 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/docs/scripts-reference.md b/docs/scripts-reference.md index 9465973..18f51e7 100644 --- a/docs/scripts-reference.md +++ b/docs/scripts-reference.md @@ -67,6 +67,8 @@ graph TD | [`scripts/generate-screenshots.sh`](../scripts/generate-screenshots.sh) | Bash | Atomic | Executes Robolectric/Roborazzi UI tests to record and generate local screenshots in `build/outputs/roborazzi`. | `sync-stitch`, Design System capture update | | [`scripts/upload-screenshots.py`](../scripts/upload-screenshots.py) | Python 3 | Atomic | Validates and maps Roborazzi screenshot baselines to Google Stitch `screen_id`s for in-place synchronization. | `sync-stitch`, `upload-screenshots.py` | | [`scripts/inspect-ide.sh`](../scripts/inspect-ide.sh) | Bash | Atomic | Executes Android Studio / IntelliJ IDEA code inspection engine in headless mode with default project profiles. | Advanced IDE quality audit | +| [`scripts/seal-issue.sh`](../scripts/seal-issue.sh) | Bash | Atomic | Atomically seals GitHub issues from local plans (JIT), binds milestones, links `--parent`, and triggers metadata sync. | `plan-issue`, Inception Gate 1.4 | +| [`scripts/sync-project-metadata.mjs`](../scripts/sync-project-metadata.mjs) | Node.js (ESM) | Atomic | Synchronizes native GitHub Projects v2 fields (`Priority`, `Size`, `Estimate`, `Status`) via GraphQL. | `seal-issue.sh`, post-checkout | --- @@ -200,10 +202,37 @@ graph TD --- +### 3.9 `scripts/seal-issue.sh` +* **Role**: Deterministic Just-In-Time (JIT) issue sealer and Epic branch initializer. +* **Workflow**: + - Parses frontmatter and body from `implementation_plan.md`. + - Creates the GitHub issue via `gh issue create` with native `--milestone` and `--parent`. + - Synchronizes project fields (`Priority`, `Size`, `Estimate`, `Status`) via `scripts/sync-project-metadata.mjs`. + - Initializes isolated Epic branches (`epic/**`) when `type: epic` or `Size: L/XL`. + - Updates local implementation plan with issue ID and approved status. +* **Usage**: + ```bash + ./scripts/seal-issue.sh --from-plan [plan.md] [--dry-run] + ./scripts/seal-issue.sh --sync <issue_id> [options] + ``` + +--- + +### 3.10 `scripts/sync-project-metadata.mjs` +* **Role**: Direct GraphQL synchronizer for GitHub Projects v2 custom fields. +* **Fields managed**: `Priority` (P0-P2), `Size` (XS-XL), `Estimate` (number), `Status` (Backlog to Done). +* **Usage**: + ```bash + node scripts/sync-project-metadata.mjs <issue_id> --priority P1 --size S --estimate 1.0 --status Ready + ``` + +--- + ## 4. Composition Matrix (Skills & Pipelines βž” Scripts & MCP) | Skill / Pipeline | Invoked Tools (in execution order) | |---|---| +| **`plan-issue`** (`/plan-issue`) | 1. `scripts/seal-issue.sh` (JIT Sealing at Gate 1.4)<br>2. `scripts/sync-project-metadata.mjs` | | **`quality-airbag`** (`/quality-check`) | 1. `scripts/validate-docs.sh`<br>2. `scripts/quality-check.sh`<br>3. `scripts/test-runtime-guardrails.mjs` | | **`open-pr`** (`/open-pr`) | 1. `scripts/quality-check.sh` (Full quality airbag)<br>2. Walkthrough generation & PR creation | | **`distribute-local`** (`/distribute-local`) | 1. `scripts/quality-check.sh` (Recommended)<br>2. `scripts/deploy-app-distribution.sh` | @@ -211,3 +240,4 @@ graph TD | **`triage-feedback`** (`/triage-feedback`) | 100% native MCP (`GitHubMCP`: `search_issues`, `add_issue_comment`, `create_issue`) | | **Delivery Pipeline & Quality Gate (`delivery-pipeline.yml`)** | 1. `scripts/validate-docs.sh`<br>2. `scripts/quality-check.sh` (`./gradlew codeSanityCheck`)<br>3. APK build & Firebase App Distribution deployment via Gradle | + diff --git a/scripts/seal-issue.sh b/scripts/seal-issue.sh new file mode 100755 index 0000000..ce1034d --- /dev/null +++ b/scripts/seal-issue.sh @@ -0,0 +1,218 @@ +#!/usr/bin/env bash +# ============================================================================== +# Agentic Android Delivery Kernel β€” Deterministic Issue Sealer +# ============================================================================== +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +function print_usage() { + cat <<EOF +Usage: seal-issue.sh [options] + +Modes: + --from-plan [path] Seal issue atomically from plan (default: implementation_plan.md) + --sync <issue_id> Synchronize Project v2 metadata for an existing issue + --dry-run Simulate without remote mutations + -h, --help Display help + +Options (for --sync mode): + --priority <P0|P1|P2> Set Priority + --size <XS|S|M|L|XL> Set Size + --estimate <number> Set Estimate + --status <StatusName> Set Status +EOF +} + +MODE="" +PLAN_FILE="implementation_plan.md" +DRY_RUN=false +SYNC_ISSUE_ID="" +EXTRA_ARGS=() + +while [[ $# -gt 0 ]]; do + case "$1" in + --from-plan) + MODE="plan" + if [[ $# -gt 1 && ! "$2" =~ ^-- ]]; then PLAN_FILE="$2"; shift; fi + shift + ;; + --sync) + MODE="sync" + if [[ $# -gt 1 && ! "$2" =~ ^-- ]]; then SYNC_ISSUE_ID="$2"; shift; fi + shift + ;; + --dry-run) DRY_RUN=true; shift ;; + -h|--help) print_usage; exit 0 ;; + *) EXTRA_ARGS+=("$1"); shift ;; + esac +done + +if [ -z "$MODE" ]; then + if [ -f "$ROOT_DIR/$PLAN_FILE" ] || [ -f "$PLAN_FILE" ]; then MODE="plan"; else print_usage; exit 1; fi +fi + +if [ "$MODE" = "sync" ]; then + if [ -z "$SYNC_ISSUE_ID" ]; then + echo "❌ Error: --sync requires a numeric Issue ID." + exit 1 + fi + SYNC_SCRIPT="$SCRIPT_DIR/sync-project-metadata.mjs" + DRY_FLAG="" + if [ "$DRY_RUN" = true ]; then DRY_FLAG="--dry-run"; fi + node "$SYNC_SCRIPT" "$SYNC_ISSUE_ID" "${EXTRA_ARGS[@]}" $DRY_FLAG + exit 0 +fi + +TARGET_PLAN="$ROOT_DIR/$PLAN_FILE" +if [ ! -f "$TARGET_PLAN" ]; then + if [ -f "$PLAN_FILE" ]; then TARGET_PLAN="$PLAN_FILE"; else + echo "❌ Error: Plan file '$PLAN_FILE' not found." + exit 1 + fi +fi + +echo "==================================================" +echo "πŸ”’ Agentic Android Kernel β€” JIT Issue Sealer" +echo "==================================================" +echo "πŸ“„ Target Plan: $TARGET_PLAN" + +TEMP_VARS=$(mktemp) +TEMP_BODY=$(mktemp) + +node -e ' + const fs = require("fs"); + const planPath = process.argv[1]; + const varsPath = process.argv[2]; + const bodyPath = process.argv[3]; + const content = fs.readFileSync(planPath, "utf8"); + + let type = "feat", scope = "core", parent = "", milestone = ""; + let priority = "P1", size = "M", estimate = "1.0", titleRaw = ""; + + const fmM = content.match(/^---\s*\n([\s\S]*?)\n---/); + if (fmM) { + const y = fmM[1]; + const getVal = (regex, def) => { const m = y.match(regex); return m && m[1] !== "null" ? m[1].trim() : def; }; + type = getVal(/type:\s*["\x27]?([^"\x27\n\r]+)/, "feat"); + scope = getVal(/scope:\s*["\x27]?([^"\x27\n\r]+)/, "core"); + parent = getVal(/parent:\s*["\x27]?([^"\x27\n\r]+)/, ""); + milestone = getVal(/milestone:\s*["\x27]?([^"\x27\n\r]+)/, ""); + priority = getVal(/priority:\s*["\x27]?([^"\x27\n\r]+)/, "P1"); + size = getVal(/size:\s*["\x27]?([^"\x27\n\r]+)/, "M"); + estimate = getVal(/estimate:\s*([0-9.]+)/, "1.0"); + } + + const lines = content.split("\n"); + for (const line of lines) { + if (line.startsWith("# ")) { + titleRaw = line.substring(2).trim(); + break; + } + } + + const cleanTitle = titleRaw.replace(/^[a-zA-Z0-9_-]+(\([a-zA-Z0-9_.-]+\))?:\s*/, ""); + const formattedTitle = (scope && scope !== "core") ? `${type}(${scope}): ${cleanTitle}` : `${type}: ${cleanTitle}`; + const slug = cleanTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").substring(0, 40); + + fs.writeFileSync(varsPath, `TYPE="${type}"\nSCOPE="${scope}"\nPARENT="${parent}"\nMILESTONE="${milestone}"\nPRIORITY="${priority}"\nSIZE="${size}"\nESTIMATE="${estimate}"\nFORMATTED_TITLE="${formattedTitle.replace(/"/g, "\\\"")}"\nSLUG="${slug}"\n`, "utf8"); + + let body = content.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, ""); + body = body.split("\n").filter(line => { + if (/^>\s*\*\*Parent Epic\*\*/i.test(line)) return false; + if (/^>\s*\*\*Sprint\*\*/i.test(line)) return false; + if (/^>\s*\*\*Milestone\*\*/i.test(line)) return false; + return true; + }).join("\n"); + body = body.replace(/##\s*🧭\s*Native Metadata Triad[\s\S]*?(?=\n##|\n---|$)/i, ""); + fs.writeFileSync(bodyPath, body.trim() + "\n", "utf8"); +' "$TARGET_PLAN" "$TEMP_VARS" "$TEMP_BODY" + +# shellcheck disable=SC1090 +source "$TEMP_VARS" +rm -f "$TEMP_VARS" + +echo "πŸ“Œ Resolved Metadata:" +echo " β€’ Title: $FORMATTED_TITLE" +echo " β€’ Type: $TYPE" +echo " β€’ Scope: $SCOPE" +echo " β€’ Priority: $PRIORITY" +echo " β€’ Size: $SIZE" +echo " β€’ Estimate: $ESTIMATE" +echo " β€’ Milestone: ${MILESTONE:-'(none)'}" +echo " β€’ Parent: ${PARENT:-'(none)'}" + +if [ "$DRY_RUN" = true ]; then + echo "" + echo "πŸ” [Dry-Run] Issue Body Preview:" + head -n 8 "$TEMP_BODY" + echo "..." + echo "βœ… [Dry-Run] Simulation complete. Zero remote mutations." + rm -f "$TEMP_BODY" + 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 "${LABEL_TYPE},source:internal" +) +if [ -n "$MILESTONE" ]; then GH_ARGS+=(--milestone "$MILESTONE"); fi +if [ -n "$PARENT" ]; then GH_ARGS+=(--parent "$PARENT"); fi + +ISSUE_URL=$(gh "${GH_ARGS[@]}") +rm -f "$TEMP_BODY" + +if [ -z "$ISSUE_URL" ]; then + echo "❌ Error: Failed to create issue." + exit 1 +fi + +ISSUE_ID=$(echo "$ISSUE_URL" | grep -oE '[0-9]+$') +echo "βœ… Sealed Issue #$ISSUE_ID: $ISSUE_URL" + +SYNC_SCRIPT="$SCRIPT_DIR/sync-project-metadata.mjs" +if [ -f "$SYNC_SCRIPT" ]; then + echo "πŸ“Š Synchronizing Project v2 fields..." + node "$SYNC_SCRIPT" "$ISSUE_ID" \ + --priority "$PRIORITY" \ + --size "$SIZE" \ + --estimate "$ESTIMATE" \ + --status "Ready" || true +fi + +BRANCH_NAME="${TYPE}/issue-${ISSUE_ID}-${SLUG}" +if [ "$TYPE" = "epic" ] || [ "$SIZE" = "L" ] || [ "$SIZE" = "XL" ]; then + BRANCH_NAME="epic/issue-${ISSUE_ID}-${SLUG}" + echo "πŸ›οΈ Creating Epic Branch: $BRANCH_NAME..." + git checkout -b "$BRANCH_NAME" origin/main + git push -u origin "$BRANCH_NAME" || true +fi + +node -e ' + const fs = require("fs"); + const p = process.argv[1], id = process.argv[2], b = process.argv[3]; + let c = fs.readFileSync(p, "utf8"); + c = c.replace(/issue:\s*(null|[0-9]+)/, "issue: " + id); + c = c.replace(/branch:\s*(null|["\x27]?[^"\x27\n\r]+)/, "branch: " + b); + c = c.replace(/status:\s*proposed/, "status: approved"); + c = c.replace(/gate:\s*Gate 1\.4 \(Inception Halt\)/, "gate: Gate 1.4 (Inception Halt - APPROVED)"); + fs.writeFileSync(p, c, "utf8"); +' "$TARGET_PLAN" "$ISSUE_ID" "$BRANCH_NAME" + +echo "==================================================" +echo "πŸŽ‰ Issue #$ISSUE_ID successfully sealed and synced!" +echo " Branch: $BRANCH_NAME" +echo " URL: $ISSUE_URL" +echo "==================================================" diff --git a/scripts/sync-project-metadata.mjs b/scripts/sync-project-metadata.mjs new file mode 100755 index 0000000..294ef2f --- /dev/null +++ b/scripts/sync-project-metadata.mjs @@ -0,0 +1,165 @@ +#!/usr/bin/env node +import { execSync } from 'child_process'; +import fs from 'fs'; +import path from 'path'; + +function printUsage() { + console.log(`Usage: sync-project-metadata.mjs <issue_id> [options] +Options: + --priority <P0|P1|P2> Set Priority field + --size <XS|S|M|L|XL> Set Size field + --estimate <number> Set Estimate field + --status <StatusName> Set Status (Backlog, Ready, In progress, In review, Done) + --dry-run Simulate without mutations + --json Output results as JSON + -h, --help Display help`); +} + +const args = process.argv.slice(2); +if (args.length === 0 || args.includes('-h') || args.includes('--help')) { + printUsage(); + process.exit(0); +} + +const issueArg = args[0]; +if (!issueArg || isNaN(Number(issueArg))) { + console.error(`❌ Error: First argument must be numeric Issue ID. Received: '${issueArg}'`); + process.exit(1); +} + +const issueNumber = parseInt(issueArg, 10); +let priorityVal = null, sizeVal = null, estimateVal = null, statusVal = null; +let dryRun = false, jsonOutput = false; + +for (let i = 1; i < args.length; i++) { + const arg = args[i]; + if (arg === '--priority' && i + 1 < args.length) priorityVal = args[++i]; + else if (arg === '--size' && i + 1 < args.length) sizeVal = args[++i]; + else if (arg === '--estimate' && i + 1 < args.length) estimateVal = parseFloat(args[++i]); + else if (arg === '--status' && i + 1 < args.length) statusVal = args[++i]; + else if (arg === '--dry-run') dryRun = true; + else if (arg === '--json') jsonOutput = true; +} + +let owner = '', repo = '', projectNumber = 2, projectEnabled = true; + +try { + const cfgPath = path.resolve(process.cwd(), 'kernel.config.json'); + if (fs.existsSync(cfgPath)) { + const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8')); + owner = cfg?.git?.owner || ''; + repo = cfg?.git?.repo || ''; + projectNumber = cfg?.githubProject?.projectNumber ?? 2; + projectEnabled = cfg?.githubProject?.enabled ?? true; + } +} catch {} + +if (!owner || !repo) { + try { + const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf8' }).trim(); + const match = remoteUrl.match(/[:/]([^/]+)\/([^/]+?)(?:\.git)?$/); + if (match) { owner = match[1]; repo = match[2]; } + } catch {} +} + +if (!projectEnabled) { + if (!jsonOutput) console.log(`ℹ️ [SyncMetadata] Projects disabled. Skipping #${issueNumber}.`); + process.exit(0); +} + +function runGh(ghArgs) { + return execSync(`gh ${ghArgs}`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }); +} + +function gql(query, variables = {}) { + const varArgs = Object.entries(variables) + .map(([k, v]) => `-F ${k}=${typeof v === 'number' ? v : `"${v}"`}`).join(' '); + return JSON.parse(runGh(`api graphql -f query='${query.replace(/\n/g, ' ')}' ${varArgs}`)).data; +} + +const results = { issueNumber, projectNumber, updated: [], dryRun }; + +try { + const query = `query($login:String!,$pNum:Int!,$repo:String!,$issue:Int!){ + user(login:$login){projectV2(number:$pNum){id fields(first:30){nodes{ + ...on ProjectV2Field{id name dataType} + ...on ProjectV2SingleSelectField{id name dataType options{id name}} + }}}} + repository(owner:$login,name:$repo){issue(number:$issue){id projectItems(first:10){nodes{id project{id}}}}} + }`; + + let data = gql(query, { login: owner, pNum: projectNumber, repo, issue: issueNumber }); + let project = data?.user?.projectV2; + const issue = data?.repository?.issue; + + if (!project) { + const orgQuery = query.replace('user(login:$login)', 'organization(login:$login)'); + data = gql(orgQuery, { login: owner, pNum: projectNumber, repo, issue: issueNumber }); + project = data?.organization?.projectV2; + } + + if (!project) throw new Error(`Project #${projectNumber} not found.`); + if (!issue) throw new Error(`Issue #${issueNumber} not found in '${owner}/${repo}'.`); + + const fields = project.fields?.nodes || []; + let projectItem = (issue.projectItems?.nodes || []).find(n => n.project?.id === project.id); + let itemId = projectItem?.id; + + if (!itemId) { + if (dryRun) { + results.itemAdded = true; + itemId = 'SIMULATED_ID'; + } else { + const addRes = gql( + `mutation($p:ID!,$c:ID!){addProjectV2ItemById(input:{projectId:$p,contentId:$c}){item{id}}}`, + { p: project.id, c: issue.id } + ); + itemId = addRes?.addProjectV2ItemById?.item?.id; + results.itemAdded = true; + } + } + + function updateSelect(fieldName, targetValue) { + if (!targetValue) return; + const f = fields.find(x => x.name?.toLowerCase() === fieldName.toLowerCase() && x.options); + if (!f) return; + const opt = f.options.find(o => o.name?.toLowerCase() === targetValue.toLowerCase()); + if (!opt) return; + + if (!dryRun) { + 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 }); + } + + function updateNumber(fieldName, targetValue) { + if (targetValue === null || isNaN(targetValue)) return; + const f = fields.find(x => x.name?.toLowerCase() === fieldName.toLowerCase() && x.dataType === 'NUMBER'); + if (!f) return; + + if (!dryRun) { + runGh(`api graphql -f query='mutation { updateProjectV2ItemFieldValue(input: { projectId: "${project.id}", itemId: "${itemId}", fieldId: "${f.id}", value: { number: ${targetValue} } }) { projectV2Item { id } } }'`); + } + results.updated.push({ field: f.name, value: targetValue }); + } + + updateSelect('Priority', priorityVal); + updateSelect('Size', sizeVal); + updateNumber('Estimate', estimateVal); + updateSelect('Status', statusVal); + + if (jsonOutput) { + console.log(JSON.stringify(results, null, 2)); + } else { + console.log(`βœ… [SyncMetadata] Issue #${issueNumber} synchronized on Project #${projectNumber}:`); + for (const u of results.updated) console.log(` β€’ ${u.field}: ${u.value}`); + if (dryRun) console.log(` (Dry-run simulation mode)`); + } +} catch (err) { + if (jsonOutput) { + console.error(JSON.stringify({ error: err.message, issueNumber }, null, 2)); + } else { + console.error(`❌ [SyncMetadata] Error on #${issueNumber}:`, err.message); + } + process.exit(1); +} 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 { diff --git a/scripts/validate-docs.sh b/scripts/validate-docs.sh index 033a1c9..768ec54 100755 --- a/scripts/validate-docs.sh +++ b/scripts/validate-docs.sh @@ -133,6 +133,8 @@ check_file ".agent/rules/firebase-standards.md" "Firebase Standards & Security" echo "" echo "βš™οΈ 8. Local Hooks & Installation Scripts:" check_executable "scripts/install-hooks.sh" "Hooks Installation Script" +check_executable "scripts/seal-issue.sh" "Deterministic Issue Sealer Script" 8000 +check_executable "scripts/sync-project-metadata.mjs" "Projects v2 Metadata Sync Script" 7000 check_executable ".agent/hooks/pre-commit-airbag.sh" "Pre-Commit Airbag Hook" check_executable ".agent/hooks/post-merge-dual-sync.sh" "Post-Merge Dual-Sync Hook" check_file ".agent/hooks.json" "Native Antigravity Hooks Declaration" 1000