diff --git a/.env.dev b/.env.dev index 6125069..c49568e 100644 --- a/.env.dev +++ b/.env.dev @@ -10,3 +10,4 @@ COM_ERMNVLDMR_STATIC_S3_REGION="encrypted:BAj3iuo7tWWNR2V4yQ+LHnFCB4JcZp7TTfUtvv COM_ERMNVLDMR_STATIC_S3_ENDPOINT_URL="encrypted:BNkQrgsrxtz544vuJbgwWUh5cPjHG5wbmHNcxTUWrzbFJJsdncaAitviCvIrS7Q41w+FiWkssv3D05Tu8Inn2P62E6G3PU4gNxO2oLdmGEdrzUqB/xvxOvLvos42j54QOZf7lOqMPKH6SL2/rheCAo10gSCicCqkfh4xsCAbYpyL" COM_ERMNVLDMR_STATIC_AWS_ACCESS_KEY_ID="encrypted:BCsp8p4bp3cB+9grxHo634isRUaFGU3MOc1lr8ClSFRYLjqjqukyrbJCTZWV2Xdo8iQ33EylmbzUltbwzTWMQcjJSvIskesVLyV93q60o/Mu9L9jg/apwHuBgHv4zVuHstJ0zkHWgapbNTIlWD3RCm0ujeX/H1gBH3U=" COM_ERMNVLDMR_STATIC_AWS_SECRET_ACCESS_KEY="encrypted:BM7aPliOyiPrqFPZMfqpn534Nu+Y0Nbh/YRoxv2KY6XAi2dNO0LSEf97DqSUQYdsnu9iibZmVNktF2K/D6mtHr9+gumF6KsmaLH65KE7c9ly7NdFQHL9I26wxRq1Sr3Zgrfs954J6oURSlBRKqJFdcTsBltuuupGuqJHyLS9+DE0jD+bl0Qg0lA=" +SOPS_AGE_KEY="encrypted:BOJ7q79Tws4DMxpFuSymgv//IC4f+j713Bd7mNlUCEaUYVSfJMayMVPzks0FcsYo12XvlbH6gQ+tCf3t9yj/LcKKJzl3wtxlSVjvCRr0hasEBohcoxkLnFZ73DFQGzWcVC5Jf5IMK7qdYdNVzW0dVY/sn/NlTgl+A3YB6/08XYPhljl9993ky76zQqWDJQ66bKJ7YxWvst2TUNoT0lJEL0M4pg9ztRsuGqNZ" diff --git a/.gemini/skills/github-fix/SKILL.md b/.gemini/skills/github-fix/SKILL.md new file mode 100644 index 0000000..b60f092 --- /dev/null +++ b/.gemini/skills/github-fix/SKILL.md @@ -0,0 +1,175 @@ +--- +name: github-fix +description: Diagnose CI failures and review feedback on GitHub Pull Requests or Issues, repair code, verify quality checks, and commit changes. +--- + +# GitHub Fix Skill + +Use this skill when a user submits a `/fix` command on a GitHub Pull Request or Issue. + +## Overview + +The `/fix` command resolves build failures, test errors, and reviewer feedback. This skill enforces unified diagnosis, clean code repair, strict verification, and transparent progress reporting. + +```text +/fix [optional instructions] + │ + ├── 1. Unified Diagnosis (Fetch CI logs, inspect diff, parse comments) + ├── 2. Target Code Repair (Fix build errors and review feedback in one pass) + ├── 3. Verification (Run typecheck, lint, and tests) + ├── 4. Git Operations (Commit and push to the active branch) + └── 5. Thread Summary (Post itemized comment with mandatory disclosure) +``` + +--- + +## Operational Workflow + +### Step 1: Unified Diagnosis + +Collect all diagnostic context before making code edits. + +#### Case A: Pull Request Context +1. Identify the current PR number and branch: + ```bash + gh pr view --json number,headRefName,baseRefName + ``` +2. Fetch failed CI workflow logs: + ```bash + gh run view --log-failed + ``` +3. Inspect current changes in the PR: + ```bash + gh pr diff + ``` +4. Read all review comments and discussion threads: + ```bash + gh pr view --comments + ``` +5. Check inline review comments on specific files: + ```bash + gh api repos/{owner}/{repo}/pulls/{pull_number}/comments + ``` + +#### Case B: Issue Context +1. Read the issue description and thread comments: + ```bash + gh issue view --comments + ``` +2. Extract error stack traces, reproduction steps, and user instructions. + +--- + +### Step 2: Target Code Repair + +Apply all necessary corrections in a single pass. + +1. Address all failures from CI logs (TypeScript errors, linter violations, test failures). +2. Apply requested changes from code review comments. +3. Incorporate any optional user instructions provided with `/fix`. +4. Obey all repository conventions defined in `GEMINI.md`: + - **Strict TypeScript**: Do not use `any`, `as Type` assertions, or non-null `!` assertions. Use `castRef` from `@ermnvldmr/stl` when casting DOM references. + - **Type Imports**: Use `import type` for type declarations. + - **Interfaces**: Define object contracts with `interface`, not `type` aliases. + - **TSDoc**: Add descriptive JSDoc comments to all exported symbols and interfaces. + - **Styling**: Use Tailwind CSS v4 and `cva`. Merge class names with `cn` from `@ermnvldmr/stl`. + - **UI Primitives**: Use React Aria hooks and Radix UI primitives. + - **Import Boundaries**: Use relative paths for files within the same workspace package. + +--- + +### Step 3: Verification + +Execute the complete verification sequence. All checks must pass with zero warnings or errors before proceeding. + +```bash +# 1. Verify TypeScript types +pnpm typecheck + +# 2. Verify ESLint rules +pnpm lint + +# 3. Run unit and component tests +pnpm test +``` + +> [!IMPORTANT] +> If any check fails, resolve the failure immediately and rerun the verification sequence. + +--- + +### Step 4: Git Operations + +Stage, commit, and push the resolved changes to the active branch. + +1. Stage modified and created files: + ```bash + git add + ``` +2. Commit with standard message: + ```bash + git commit -m "fix: apply review adjustments and CI fixes" + ``` +3. Push changes to the remote branch: + ```bash + git push origin HEAD + ``` + +--- + +### Step 5: Post Summary Comment + +Post an itemized summary comment to the GitHub PR or Issue thread. + +1. Format the comment with the template below. +2. Submit the comment via GitHub CLI: + - For a Pull Request: + ```bash + gh pr comment --body "" + ``` + - For an Issue: + ```bash + gh issue comment --body "" + ``` + +--- + +## GitHub Comment Template + +```markdown +### Automated Fix Summary + +I addressed the reported issues and review feedback. + +#### Root Causes Identified +- **CI / Test Failure**: `` +- **Review Feedback**: `` + +#### Changes Applied +- `packages//src/...`: Fixed `` +- `services//src/...`: Updated `` + +#### Verification Results +- `pnpm typecheck` passed (0 errors). +- `pnpm lint` passed (0 warnings). +- `pnpm test` passed (all tests clean). + +Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit. +``` + +--- + +## Verification Checklist + +Before completing the task, verify every item in this checklist: + +- [ ] Failed CI logs inspected with `gh run view --log-failed`. +- [ ] PR diff and review comments retrieved and analyzed. +- [ ] Code repairs satisfy all requirements without introducing regressions. +- [ ] No `any`, `as Type`, or `!` operators introduced. +- [ ] `pnpm typecheck` completed with 0 errors. +- [ ] `pnpm lint` completed with 0 warnings. +- [ ] `pnpm test` completed with all tests passing. +- [ ] Changes committed and pushed to the active branch. +- [ ] Summary comment posted to the PR or Issue. +- [ ] Mandatory disclosure line included at the end of the comment. diff --git a/.gemini/skills/github-implement/SKILL.md b/.gemini/skills/github-implement/SKILL.md new file mode 100644 index 0000000..2cff66f --- /dev/null +++ b/.gemini/skills/github-implement/SKILL.md @@ -0,0 +1,125 @@ +--- +name: github-implement +description: Implement code changes from an approved issue plan, run monorepo validation, and create a pull request. +--- + +# GitHub Implement Skill + +Use this skill when an issue receives the `/implement` command. + +## Overview + +This skill guides the implementation of approved design plans. It enforces repository rules, executes automated validation, and prepares pull requests for the `next` branch. + +## Procedural Workflow + +### Phase 1: Context Ingestion + +1. Read the complete issue description and title. +2. Locate the approved implementation plan in the issue comments. +3. Parse additional instructions from the `/implement` comment. +4. If requirements conflict or miss critical details, stop and request clarification. + +### Phase 2: Code Implementation + +Apply all modifications according to `GEMINI.md` conventions: + +1. **TypeScript Constraints**: + - Enable strict typing for all new code. + - Do not use the `any` type. + - Do not use type assertions (`as Type`). + - Do not use the non-null assertion operator (`!`). + - Use explicit type guards or helper functions such as `castRef`. + - Use `import type` for type-only imports. + - Define object structures with `interface`, not `type` aliases. + +2. **Documentation Rules**: + - Add JSDoc comments to all exported functions, interfaces, and types. + - Include clear description text in every JSDoc block. + +3. **Component Architecture**: + - Place components in dedicated directories under `packages/ui/src/components//`. + - Provide `.tsx` for component logic. + - Provide `.stories.tsx` for Storybook stories. + - Provide `.test.tsx` for Vitest tests. + - Use `cva` for component variant management. + - Merge CSS utility classes with `cn(...)` from `@ermnvldmr/stl`. + - Use React Aria hooks and Radix UI primitives for accessible controls. + +4. **Monorepo Imports and State**: + - Use relative paths within the same package. + - Do not use self-referential package names. + - Use standard React state primitives. Do not add unapproved state managers. + +5. **Protected Boundaries**: + - Do not modify `.env.keys`, `pnpm-lock.yaml`, or `LICENSE`. + - Do not edit content files in `services/docs/content/` or `services/www/content/` unless explicitly instructed. + +### Phase 3: Monorepo Verification & Self-Correction + +Run validation commands in strict sequence: + +1. Run `pnpm typecheck`. Ensure zero compiler errors. +2. Run `pnpm lint`. Ensure zero warnings and zero errors. +3. Run `pnpm test`. Ensure all unit and component tests pass. +4. Run `pnpm build`. Ensure clean compilation across all packages. + +**Iterative Self-Correction Loop**: +- If any check fails, inspect the diagnostic output. +- Identify the root cause. +- Apply targeted fixes adhering to all code rules. +- Re-run the verification sequence from step 1. +- Repeat until all four checks pass completely. + +### Phase 4: Commit & Pull Request Preparation + +1. **Commit Message**: + - Format commits according to Conventional Commits: `: implement issue # ()`. + - Example: `feat: implement issue #42 (add theme toggle)`. + +2. **Pull Request Target**: + - Set base branch to `next`. + +3. **Pull Request Body**: + - Link the issue: `Resolves #<number>`. + - Provide a concise summary of changes. + - Detail the implementation walkthrough. + - List the executed verification checks. + - Append the mandatory attribution line. + +4. **Mandatory Attribution**: + - Conclude all PR bodies and issue comments with: + `Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.` + +## Pull Request Template + +```markdown +## Summary +Resolves #<number> + +<Brief explanation of the implemented feature or fix> + +## Walkthrough of Changes +- `<package-or-path>`: <Description of specific change> +- `<package-or-path>`: <Description of specific change> + +## Verification Results +- [x] `pnpm typecheck` passed (0 errors) +- [x] `pnpm lint` passed (0 warnings) +- [x] `pnpm test` passed (all tests green) +- [x] `pnpm build` passed (clean build) + +Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit. +``` + +## Verification Checklist + +- [ ] Issue context, approved plan, and user instructions are satisfied. +- [ ] TypeScript strict mode rules are respected (`no any`, `no as`, `no !`). +- [ ] Exported symbols include JSDoc comments. +- [ ] UI components reside in dedicated folders with tests and stories. +- [ ] Monorepo boundaries and relative imports are respected. +- [ ] All four verification checks passed cleanly. +- [ ] Target branch is set to `next`. +- [ ] PR description includes `Resolves #<number>`. +- [ ] Mandatory attribution line is present. diff --git a/.gemini/skills/github-plan/SKILL.md b/.gemini/skills/github-plan/SKILL.md new file mode 100644 index 0000000..02cac81 --- /dev/null +++ b/.gemini/skills/github-plan/SKILL.md @@ -0,0 +1,136 @@ +--- +name: github-plan +description: Analyze an issue thread, iterate on feedback, and produce or update an implementation plan for ermnvldmr.com. +--- + +# GitHub Issue Planning Skill (ASD-STE100 Standard) + +## Overview + +Use this skill when an issue receives the `/plan [optional instructions]` command. + +This skill creates or updates an implementation plan before code modification starts. All outputs must follow ASD-STE100 language constraints and repository standards. + +--- + +## ASD-STE100 Writing Rules + +Follow these rules for all generated plans and documentation: + +| Rule | Limit | Approved Example | Non-Approved Example | +| :--- | :--- | :--- | :--- | +| **Procedural Sentences** | Max **20 words** | "Run `pnpm test`. Verify that all unit tests pass." | "You should run the pnpm test command to verify that all the unit tests pass properly without errors." | +| **Descriptive Sentences** | Max **25 words** | "The `Button` component uses `cva` to manage style variants." | "The Button component is implemented using the cva library because it helps us manage complex style variants across different themes easily." | +| **Voice** | **Active voice** | "The function validates the user input." | "The user input is validated by the function." | +| **Mood and Tense** | **Present tense** and **Imperative mood** | "Click **Submit**. The form transmits the data." | "You will click submit and the form will be transmitting the data." | +| **Noun Clusters** | Max **3 nouns** | "Theme configuration file" | "Theme variant style configuration file" | + +### Controlled Vocabulary Guidelines +- Do not use conversational filler (for example: "basically", "let's dive in", "simply", "feel free to"). +- Use approved verbs: use *use* (not *utilize*), *stop* (not *terminate*), *start* (not *initiate*), *show* (not *exhibit*). +- Use clear and literal technical terms. + +--- + +## Planning Responsibilities + +### 1. Ingest Full Issue Context +- Read the issue title, issue description, and all subsequent comments. +- Identify user requirements, technical constraints, and reported symptoms. +- Inspect relevant source files in the repository before proposing changes. +- Read `GEMINI.md` to confirm current project conventions. + +### 2. Multi-Turn Plan Iteration +- Check if an earlier implementation plan exists in the issue thread. +- If an earlier plan exists: + - Do not create a plan from zero. + - Retain approved architectural decisions from previous turns. + - Apply the requested modifications or additions from the latest comment. + - Summarize the specific adjustments made to the previous plan. +- If no earlier plan exists: + - Create a new baseline implementation plan from the issue requirements. + +### 3. Enforce Repository Architecture (`GEMINI.md`) +Every proposed plan must comply with repository architecture: +- **Build System**: Use Rsbuild and Rspack. Do not propose Vite, Webpack, or Next.js. +- **Strict TypeScript**: + - Do not use `any`. + - Do not use type assertions (`as Type`). Use type guards or `castRef` from `@ermnvldmr/stl`. + - Do not use non-null assertions (`!`). + - Use `import type` for type-only imports. + - Define object shapes with `interface`, not `type` aliases. + - Require JSDoc comments on all exported symbols. +- **Component Architecture** (`packages/ui`): + - Place components in `packages/ui/src/components/<ComponentName>/`. + - Provide `<ComponentName>.tsx`, `<ComponentName>.stories.tsx`, and `<ComponentName>.test.tsx`. + - Use `cva` for variant definitions. + - Merge class names with `cn(...)` from `@ermnvldmr/stl`. +- **Accessibility & State**: + - Use React Aria hooks (`useButton`, `useFocusRing`, `useHover`) and Radix UI primitives. + - Use standard React state (`useState`, `useReducer`, `useContext`) and `react-stately`. + - Do not introduce external state management libraries. +- **Monorepo Import Rules**: + - Use relative import paths within the same package. + - Do not import `@ermnvldmr/<package>` inside `<package>` itself. +- **Protected Files**: + - Do not modify `.env.keys`, `pnpm-lock.yaml`, `LICENSE`, `services/docs/content/`, or `services/www/content/`. + +--- + +## Output Plan Structure + +Format the generated plan with the following markdown template: + +```markdown +### Goal +[Describe the objective in 1-2 sentences. Keep each sentence under 25 words.] + +### Architecture & Technical Decisions +- [Decision 1: Explain pattern, primitive, or component structure.] +- [Decision 2: Explain typing or API contracts.] +- [Decision 3: Explain state management or accessibility integration.] + +### File Changes +- `[NEW]` `packages/ui/src/components/NewComponent/NewComponent.tsx` - [Purpose] +- `[NEW]` `packages/ui/src/components/NewComponent/NewComponent.stories.tsx` - [Purpose] +- `[NEW]` `packages/ui/src/components/NewComponent/NewComponent.test.tsx` - [Purpose] +- `[MODIFY]` `packages/ui/src/index.ts` - [Export new component] + +### Verification Plan +1. `pnpm typecheck` - Verify zero TypeScript compiler errors. +2. `pnpm lint` - Verify zero ESLint warnings and errors. +3. `pnpm test` - Run unit and integration tests. +4. `pnpm build` - Run full workspace build. + +### Open Questions +- [List open questions or ambiguities. If no open questions exist, write "None.".] + +Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit. +``` + +--- + +## Common Traps and Solutions + +| Anti-Pattern | Correct Action | +| :--- | :--- | +| Regenerating a new plan after user feedback | Update the existing plan and highlight modified sections. | +| Using `as Type` assertions in the proposed code | Propose explicit type guards or `castRef` helpers. | +| Missing Storybook or Vitest files for UI components | Include `.stories.tsx` and `.test.tsx` in the file plan. | +| Proposing external state libraries | Use React 19 primitives and `react-stately`. | +| Omitting the attribution line | Append the mandatory disclosure line at the end. | + +--- + +## Verification Checklist + +Before posting the plan response, verify all items: + +- [ ] The issue thread context and all user comments are analyzed. +- [ ] Existing plans are updated rather than replaced from scratch. +- [ ] Proposed code satisfies all `GEMINI.md` constraints (`strict: true`, no `any`, no `as`, no `!`). +- [ ] All UI components include unit tests and Storybook stories. +- [ ] All sentences follow ASD-STE100 limits (<=20 words for instructions, <=25 words for descriptions). +- [ ] Active voice is used throughout the plan. +- [ ] The output concludes with: `Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.` + diff --git a/.github/workflows/gemini-manual.yml b/.github/workflows/gemini-manual.yml new file mode 100644 index 0000000..e788e1c --- /dev/null +++ b/.github/workflows/gemini-manual.yml @@ -0,0 +1,261 @@ +name: gemini - manual + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + +concurrency: + group: agent-issue-${{ github.event.issue.number || github.event.pull_request.number }} + cancel-in-progress: false + +permissions: + contents: write + issues: write + pull-requests: write + actions: read + +jobs: + agent-dispatch: + name: Agent / Dispatch + if: > + (github.event.comment.author_association == 'OWNER' || + github.event.comment.author_association == 'MEMBER' || + github.event.comment.author_association == 'COLLABORATOR') && + (contains(github.event.comment.body, '/plan') || + contains(github.event.comment.body, '/implement') || + contains(github.event.comment.body, '/fix')) + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Verify Collaborator Authorization + id: auth-check + run: | + AUTHOR_ASSOC="${{ github.event.comment.author_association }}" + echo "Author association: $AUTHOR_ASSOC" + if [[ "$AUTHOR_ASSOC" != "OWNER" && "$AUTHOR_ASSOC" != "MEMBER" && "$AUTHOR_ASSOC" != "COLLABORATOR" ]]; then + echo "::error::Unauthorized trigger by user with association '$AUTHOR_ASSOC'." + exit 1 + fi + + - name: Acknowledge Command (Add Eyes Reaction) + if: success() + env: + GH_TOKEN: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }} + COMMENT_ID: ${{ github.event.comment.id }} + run: | + gh api "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}/reactions" -f content=eyes || true + + - name: Checkout Codebase + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }} + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.12.3 + + - name: Setup Node.js with pnpm cache + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + + - name: Install Dependencies + run: pnpm install --frozen-lockfile + + - name: Restore Agent Conversation Context + uses: actions/cache@v4 + id: agent-context-cache + with: + path: | + ~/.gemini/antigravity-cli/conversations + ~/.gemini/antigravity-cli/brain + key: agent-context-issue-${{ github.event.issue.number }}-${{ github.run_id }} + restore-keys: | + agent-context-issue-${{ github.event.issue.number }}- + + - name: Setup Antigravity CLI & Credentials + env: + AGY_AUTH_TOKEN: ${{ secrets.AGY_AUTH_TOKEN }} + run: | + mkdir -p ~/.gemini/antigravity-cli + if [ -n "$AGY_AUTH_TOKEN" ]; then + if echo "$AGY_AUTH_TOKEN" | base64 --decode >/dev/null 2>&1; then + echo "$AGY_AUTH_TOKEN" | base64 --decode > ~/.gemini/antigravity-cli/antigravity-oauth-token + else + echo "$AGY_AUTH_TOKEN" > ~/.gemini/antigravity-cli/antigravity-oauth-token + fi + chmod 600 ~/.gemini/antigravity-cli/antigravity-oauth-token + else + echo "::warning::AGY_AUTH_TOKEN secret is not set." + fi + + - name: Configure Git Bot Identity + run: | + git config --global user.name "AI Assistant Bot" + git config --global user.email "bot@users.noreply.github.com" + + # --- COMMAND: /plan --- + - name: Handle /plan (Issue Planning) + if: contains(github.event.comment.body, '/plan') && github.event.issue.pull_request == null + env: + GH_TOKEN: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + COMMENT_BODY: ${{ github.event.comment.body }} + run: | + echo "Generating plan for Issue #${ISSUE_NUMBER}..." + ISSUE_DATA=$(gh issue view "$ISSUE_NUMBER" --comments --json title,body,comments) + + CONTINUE_FLAG="" + if [ -d "$HOME/.gemini/antigravity-cli/conversations" ] && [ "$(ls -A $HOME/.gemini/antigravity-cli/conversations 2>/dev/null)" ]; then + CONTINUE_FLAG="--continue" + fi + + PROMPT="You are operating via GitHub Actions on Issue #${ISSUE_NUMBER}.\n\nIssue Context:\n${ISSUE_DATA}\n\nLatest instruction:\n${COMMENT_BODY}\n\nPlease generate/update a detailed technical implementation plan adhering to project GEMINI.md guidelines." + + PLAN_OUTPUT=$(agy $CONTINUE_FLAG --mode plan -p "$PROMPT" || echo "Agent encountered an error generating the plan.") + + DISCLOSURE="\n\n---\n*Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.*" + gh issue comment "$ISSUE_NUMBER" --body "${PLAN_OUTPUT}${DISCLOSURE}" + + # --- COMMAND: /implement --- + - name: Handle /implement (Issue Implementation & PR) + if: contains(github.event.comment.body, '/implement') && github.event.issue.pull_request == null + env: + GH_TOKEN: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + COMMENT_BODY: ${{ github.event.comment.body }} + run: | + BRANCH_NAME="agent/issue-${ISSUE_NUMBER}" + git checkout -b "$BRANCH_NAME" + + ISSUE_DATA=$(gh issue view "$ISSUE_NUMBER" --comments --json title,body,comments) + + CONTINUE_FLAG="" + if [ -d "$HOME/.gemini/antigravity-cli/conversations" ] && [ "$(ls -A $HOME/.gemini/antigravity-cli/conversations 2>/dev/null)" ]; then + CONTINUE_FLAG="--continue" + fi + + PROMPT="You are operating via GitHub Actions to implement Issue #${ISSUE_NUMBER}.\n\nContext:\n${ISSUE_DATA}\n\nLatest instruction:\n${COMMENT_BODY}\n\nImplement the changes cleanly in the repository according to GEMINI.md rules." + agy $CONTINUE_FLAG --dangerously-skip-permissions -p "$PROMPT" + + echo "Running repository verification gates..." + pnpm typecheck + pnpm lint + pnpm test + + ISSUE_TITLE=$(gh issue view "$ISSUE_NUMBER" --json title -q .title) + git add -A + if git diff --staged --quiet; then + echo "No changes produced by agent." + gh issue comment "$ISSUE_NUMBER" --body "No changes were produced for implementation.\n\n---\n*Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.*" + exit 0 + fi + + git commit -m "feat: implement issue #${ISSUE_NUMBER} (${ISSUE_TITLE})" + git push -u origin "$BRANCH_NAME" --force + + PR_BODY="Resolves #${ISSUE_NUMBER}\n\nAutomated implementation generated via \`/implement\`.\n\n---\n*Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.*" + gh pr create \ + --title "feat: ${ISSUE_TITLE}" \ + --body "$PR_BODY" \ + --base next \ + --head "$BRANCH_NAME" + + # --- COMMAND: /fix --- + - name: Handle /fix (PR Review Adjustments & CI Failure Debugging) + if: contains(github.event.comment.body, '/fix') + env: + GH_TOKEN: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }} + ISSUE_OR_PR_NUMBER: ${{ github.event.issue.number }} + COMMENT_BODY: ${{ github.event.comment.body }} + run: | + IS_PR="${{ github.event.issue.pull_request != null }}" + if [ "$IS_PR" = "true" ]; then + # Verify PR is not from an untrusted cross-repository fork + IS_CROSS_REPO=$(gh pr view "$ISSUE_OR_PR_NUMBER" --json isCrossRepository -q .isCrossRepository) + if [ "$IS_CROSS_REPO" = "true" ]; then + echo "::error::Cross-repository fork PRs cannot be automatically processed in privileged context." + exit 1 + fi + + gh pr checkout "$ISSUE_OR_PR_NUMBER" + BRANCH_NAME=$(git branch --show-current) + PR_DIFF=$(gh pr diff "$ISSUE_OR_PR_NUMBER" || true) + + FAILED_RUN_ID=$(gh run list --branch "$BRANCH_NAME" --status failure --limit 1 --json databaseId -q '.[0].databaseId' || true) + CI_LOGS="" + if [ -n "$FAILED_RUN_ID" ] && [ "$FAILED_RUN_ID" != "null" ]; then + echo "Found failed CI run: #$FAILED_RUN_ID. Fetching failure logs..." + CI_LOGS=$(gh run view "$FAILED_RUN_ID" --log-failed | tail -n 200 || true) + fi + + CONTINUE_FLAG="" + if [ -d "$HOME/.gemini/antigravity-cli/conversations" ] && [ "$(ls -A $HOME/.gemini/antigravity-cli/conversations 2>/dev/null)" ]; then + CONTINUE_FLAG="--continue" + fi + + PROMPT="You are operating via GitHub Actions to fix/adjust PR #${ISSUE_OR_PR_NUMBER} on branch ${BRANCH_NAME}.\n\nFeedback / Comment:\n${COMMENT_BODY}\n\nPR Diff:\n${PR_DIFF}\n\nFailed CI Logs (if any):\n${CI_LOGS}\n\nApply the necessary fixes, making sure all lint, typecheck, and test checks pass." + agy $CONTINUE_FLAG --dangerously-skip-permissions -p "$PROMPT" + + echo "Running repository verification gates..." + pnpm typecheck + pnpm lint + pnpm test + + git add -A + if git diff --staged --quiet; then + echo "No additional changes were needed." + gh pr comment "$ISSUE_OR_PR_NUMBER" --body "Analyzed the request; no additional changes were needed.\n\n---\n*Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.*" + exit 0 + fi + + git commit -m "fix: apply review adjustments and CI fixes" + git push origin "$BRANCH_NAME" + + gh pr comment "$ISSUE_OR_PR_NUMBER" --body "Applied adjustments and verified build/test suite.\n\n---\n*Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.*" + else + echo "Handling /fix on Issue #${ISSUE_OR_PR_NUMBER}..." + BRANCH_NAME="agent/fix-issue-${ISSUE_OR_PR_NUMBER}" + git checkout -b "$BRANCH_NAME" + + ISSUE_DATA=$(gh issue view "$ISSUE_OR_PR_NUMBER" --comments --json title,body,comments) + PROMPT="Fix issue #${ISSUE_OR_PR_NUMBER}.\n\nContext:\n${ISSUE_DATA}\n\nInstruction:\n${COMMENT_BODY}" + agy --dangerously-skip-permissions -p "$PROMPT" + + pnpm typecheck + pnpm lint + pnpm test + + git add -A + git commit -m "fix: resolve issue #${ISSUE_OR_PR_NUMBER}" + git push -u origin "$BRANCH_NAME" --force + + gh pr create \ + --title "fix: resolve issue #${ISSUE_OR_PR_NUMBER}" \ + --body "Resolves #${ISSUE_OR_PR_NUMBER}\n\nFix applied via \`/fix\`.\n\n---\n*Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.*" \ + --base next \ + --head "$BRANCH_NAME" + fi + + - name: Complete Command (Add Rocket Reaction) + if: success() + env: + GH_TOKEN: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }} + COMMENT_ID: ${{ github.event.comment.id }} + run: | + gh api "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}/reactions" -f content=rocket || true + + - name: Report Failure on Comment + if: failure() + env: + GH_TOKEN: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + gh issue comment "$ISSUE_NUMBER" --body "⚠️ Workflow failed during execution. View run logs: [Action Run #${{ github.run_id }}](${RUN_URL})\n\n---\n*Disclosure: I'm an AI assistant helping deytenit; I'm not deytenit.*" || true diff --git a/.github/workflows/services-www-next-checkout-release-branch.yml b/.github/workflows/services-www-next-checkout-release-branch.yml deleted file mode 100644 index 54299a0..0000000 --- a/.github/workflows/services-www-next-checkout-release-branch.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: services/www - next/checkout-release-branch - -on: - workflow_dispatch: - inputs: - increment_major: - description: 'Force major version increment (e.g. v2.0 instead of v1.1)' - type: boolean - required: false - default: false - -permissions: - contents: read - actions: read - -jobs: - workflow-release-branch: - name: Workflow - Create or Update Release Branch - runs-on: ubuntu-latest - permissions: - contents: write - actions: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Determine Mode and Branch Name - id: plan - run: | - REF="${{ github.ref_name }}" - if [[ "$REF" =~ ^releases/www/v[0-9]+\.[0-9]+$ ]]; then - # Dispatched against an existing release branch: update it. - echo "mode=update" >> "$GITHUB_OUTPUT" - echo "branch_name=$REF" >> "$GITHUB_OUTPUT" - else - # Dispatched against next (or any other ref): create a new release branch. - BRANCH_NAME=$(bash ci/scripts/semantic-release.sh checkout services/www "${{ inputs.increment_major }}") - echo "mode=create" >> "$GITHUB_OUTPUT" - echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT" - fi - - - name: Create and Push Branch - if: steps.plan.outputs.mode == 'create' - run: | - git checkout -b ${{ steps.plan.outputs.branch_name }} - git push origin ${{ steps.plan.outputs.branch_name }} - - - name: Rebase Existing Branch onto next - if: steps.plan.outputs.mode == 'update' - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - # Bring the release branch up to date with its parent (next), - # replaying any release-branch-only commits on top of the latest next. - git checkout ${{ steps.plan.outputs.branch_name }} - git fetch origin next - git rebase origin/next - git push --force-with-lease origin ${{ steps.plan.outputs.branch_name }} - - - name: Trigger Release Workflow - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh workflow run services-www-releases-required.yml --ref ${{ steps.plan.outputs.branch_name }} diff --git a/.github/workflows/services-www-next-required.yml b/.github/workflows/services-www-next-required.yml index ef907e0..ce84f8c 100644 --- a/.github/workflows/services-www-next-required.yml +++ b/.github/workflows/services-www-next-required.yml @@ -10,6 +10,7 @@ on: - 'packages/i18n/**' - '.github/workflows/services-www-next-required.yml' - 'pnpm-lock.yaml' + workflow_dispatch: concurrency: group: ci-www-${{ github.ref_name }} @@ -330,17 +331,25 @@ jobs: path: services/www/rsdoctor/ retention-days: 30 - release-docker: - name: Release - Docker Image - needs: build-www + build-docker: + name: Build - Docker Image + needs: [lint-www, typecheck-www, test-www, test-storybook, build-www] runs-on: ubuntu-latest permissions: contents: read packages: write + outputs: + short_sha: ${{ steps.vars.outputs.short_sha }} steps: - name: Checkout repository uses: actions/checkout@v4 + - name: Calculate Short SHA + id: vars + run: | + SHORT_SHA="sha-$(echo ${{ github.sha }} | cut -c1-7)" + echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT + - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: @@ -348,18 +357,8 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ghcr.io/${{ github.repository }}/www - tags: | - type=raw,value=next - type=sha,format=short - - - name: Compute short SHA - id: vars - run: echo "short_sha=sha-$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - name: Build and push Docker image uses: docker/build-push-action@v5 @@ -367,8 +366,121 @@ jobs: context: . file: ci/docker/www/Dockerfile push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + tags: ghcr.io/${{ github.repository }}/www:${{ steps.vars.outputs.short_sha }} build-args: | COMMIT_SHA=${{ steps.vars.outputs.short_sha }} VERSION=${{ steps.vars.outputs.short_sha }} + + release-tag-docker: + name: Release - Tag & Docker + needs: build-docker + runs-on: ubuntu-latest + environment: services-www-releases-publish + permissions: + contents: write + packages: write + outputs: + version: ${{ steps.calver.outputs.version }} + short_sha: ${{ needs.build-docker.outputs.short_sha }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Calculate CalVer Version + id: calver + run: | + VERSION=$(bash ci/scripts/calver-release.sh) + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Create and Push Tag + run: | + TAG_NAME="${{ steps.calver.outputs.version }}" + git tag "$TAG_NAME" + git push origin "$TAG_NAME" + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Tag and Push Release Docker Image + run: | + docker buildx imagetools create \ + ghcr.io/${{ github.repository }}/www:${{ needs.build-docker.outputs.short_sha }} \ + --tag ghcr.io/${{ github.repository }}/www:${{ steps.calver.outputs.version }} + + deploy: + name: Deploy - Kubernetes (GitOps) + needs: release-tag-docker + runs-on: ubuntu-latest + environment: + name: services-www-releases-deploy + url: https://ermnvldmr.com + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: next + token: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }} + + - name: Setup Kustomize + uses: imranismail/setup-kustomize@v2 + with: + kustomize-version: "5.3.0" + + - name: Deploy via GitOps + run: | + WWW_TAG="${{ needs.release-tag-docker.outputs.short_sha }}" + bash ci/scripts/deploy-gitops.sh www "${WWW_TAG}" + + verify-rollout: + name: Verify - Production Rollout + needs: [release-tag-docker, deploy] + runs-on: ubuntu-latest + steps: + - name: Await production rollout + run: | + TARGET_COMMIT="${{ needs.release-tag-docker.outputs.short_sha }}" + ENDPOINT="https://www.ermnvldmr.com/version.json" + + echo "========================================================" + echo "Verifying production deployment on $ENDPOINT" + echo "Expected commit: $TARGET_COMMIT" + echo "========================================================" + + MAX_ATTEMPTS=45 + ATTEMPT=1 + + while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do + echo "Attempt $ATTEMPT/$MAX_ATTEMPTS: Polling $ENDPOINT..." + + RESPONSE=$(curl -sfL --max-time 10 -H "User-Agent: GitHub-Actions-Rollout-Verifier" "$ENDPOINT" 2>/dev/null || true) + + if [ -n "$RESPONSE" ]; then + LIVE_COMMIT=$(echo "$RESPONSE" | jq -r '.commit // empty') + + echo " Live commit: ${LIVE_COMMIT:-<none>}" + + if [ "$LIVE_COMMIT" = "$TARGET_COMMIT" ]; then + echo "✅ Success! Production has successfully rolled out $TARGET_COMMIT." + exit 0 + fi + else + echo " Endpoint did not return valid JSON or timed out." + fi + + ATTEMPT=$((ATTEMPT + 1)) + sleep 10 + done + + echo "❌ Deployment verification timed out after 7.5 minutes." + exit 1 diff --git a/.github/workflows/services-www-releases-dispatch-release.yml b/.github/workflows/services-www-releases-dispatch-release.yml deleted file mode 100644 index c6acd92..0000000 --- a/.github/workflows/services-www-releases-dispatch-release.yml +++ /dev/null @@ -1,382 +0,0 @@ -name: services/www - releases/dispatch-release - -on: - workflow_dispatch: - inputs: - skip_ci: - description: 'Skip CI checks (fast build, push & GitOps deploy)' - type: boolean - required: false - default: false - -permissions: - contents: read - actions: read - -jobs: - setup-stage: - name: Setup - Stage - if: ${{ !inputs.skip_ci }} - runs-on: ubuntu-latest - outputs: - cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.12.3 - - - name: Setup Node.js with pnpm cache - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - - name: Cache pnpm store - id: cache-pnpm-store - uses: actions/cache@v3 - with: - path: ~/.pnpm-store - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - build-configs: - name: Build - Configs - needs: setup-stage - if: ${{ !inputs.skip_ci }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.12.3 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - name: Install dependencies - run: pnpm install --frozen-lockfile - - name: Build Rsbuild config - run: pnpm --filter @ermnvldmr/rsbuild-config build - - name: Build Storybook config - run: pnpm --filter @ermnvldmr/storybook-config build - - name: Build i18n - run: pnpm --filter @ermnvldmr/i18n build - - name: Upload Config Artifacts - uses: actions/upload-artifact@v4 - with: - name: config-artifacts - path: | - packages/rsbuild-config/dist - packages/storybook-config/dist - packages/i18n/dist - retention-days: 1 - - lint-www: - name: Lint - WWW - needs: build-configs - if: ${{ !inputs.skip_ci }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.12.3 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - name: Install dependencies - run: pnpm install --frozen-lockfile - - name: Download Config Artifacts - uses: actions/download-artifact@v4 - with: - name: config-artifacts - path: packages - - name: Lint WWW service - run: pnpm --filter @ermnvldmr/www lint - - typecheck-www: - name: Typecheck - WWW - needs: build-configs - if: ${{ !inputs.skip_ci }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.12.3 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - name: Install dependencies - run: pnpm install --frozen-lockfile - - name: Download Config Artifacts - uses: actions/download-artifact@v4 - with: - name: config-artifacts - path: packages - - name: Typecheck WWW service - run: pnpm --filter @ermnvldmr/www typecheck - - build-storybook: - name: Build - Storybook - needs: build-configs - if: ${{ !inputs.skip_ci }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.12.3 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - name: Install dependencies - run: pnpm install --frozen-lockfile - - name: Download Config Artifacts - uses: actions/download-artifact@v4 - with: - name: config-artifacts - path: packages - - name: Build WWW Storybook - run: pnpm --filter @ermnvldmr/www build-storybook - - name: Upload Storybook build - uses: actions/upload-artifact@v4 - with: - name: storybook-static - path: services/www/storybook-static/ - retention-days: 1 - - test-www: - name: Test - Unit - needs: build-configs - if: ${{ !inputs.skip_ci }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.12.3 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - name: Install dependencies - run: pnpm install --frozen-lockfile - - name: Download Config Artifacts - uses: actions/download-artifact@v4 - with: - name: config-artifacts - path: packages - - name: Test WWW service - run: pnpm --filter @ermnvldmr/www test:coverage - - test-storybook: - name: Test - Storybook - needs: build-storybook - if: ${{ !inputs.skip_ci }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.12.3 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - name: Install dependencies - run: pnpm install --frozen-lockfile - - name: Download Config Artifacts - uses: actions/download-artifact@v4 - with: - name: config-artifacts - path: packages - - name: Install Playwright browsers - run: pnpm --filter @ermnvldmr/www exec playwright install --with-deps chromium - - name: Download Storybook build - uses: actions/download-artifact@v4 - with: - name: storybook-static - path: services/www/storybook-static/ - - name: Serve Storybook and run tests - run: pnpm --filter @ermnvldmr/www test:storybook:ci - - build-www: - name: Build - WWW Service - needs: build-configs - if: ${{ !inputs.skip_ci }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.12.3 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - name: Install dependencies - run: pnpm install --frozen-lockfile - - name: Download Config Artifacts - uses: actions/download-artifact@v4 - with: - name: config-artifacts - path: packages - - name: Build WWW service - run: pnpm --filter @ermnvldmr/www build - - release-tag-docker: - name: Release - Tag & Docker - needs: [lint-www, typecheck-www, test-www, test-storybook, build-www] - if: ${{ always() && (inputs.skip_ci || (!contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled'))) }} - runs-on: ubuntu-latest - environment: services-www-releases-publish - permissions: - contents: write - packages: write - outputs: - version: ${{ steps.version.outputs.version }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Calculate Release Version - id: version - run: | - RELEASE_VERSION=$(bash ci/scripts/semantic-release.sh release ${{ github.ref_name }}) - # Extract MAJOR.MINOR for tagging - MAJOR_MINOR=$(echo "${{ github.ref_name }}" | cut -d/ -f3 | sed 's/^v//') - MAJOR=$(echo "$MAJOR_MINOR" | cut -d. -f1) - SHORT_SHA="sha-$(echo ${{ github.sha }} | cut -c1-7)" - echo "version=$RELEASE_VERSION" >> $GITHUB_OUTPUT - echo "major_minor=$MAJOR_MINOR" >> $GITHUB_OUTPUT - echo "major=$MAJOR" >> $GITHUB_OUTPUT - echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT - - - name: Create and Push Tag - run: | - TAG_NAME="releases/www/v${{ steps.version.outputs.version }}" - git tag $TAG_NAME - git push origin $TAG_NAME - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ghcr.io/${{ github.repository }}/www - tags: | - type=raw,value=v${{ steps.version.outputs.version }} - type=raw,value=v${{ steps.version.outputs.major_minor }} - type=raw,value=v${{ steps.version.outputs.major }} - type=raw,value=latest - type=sha,format=short - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: . - file: ci/docker/www/Dockerfile - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: | - COMMIT_SHA=${{ steps.version.outputs.short_sha }} - VERSION=v${{ steps.version.outputs.version }} - - deploy: - name: Deploy - Kubernetes (GitOps) - needs: release-tag-docker - runs-on: ubuntu-latest - environment: - name: services-www-releases-deploy - url: https://ermnvldmr.com - permissions: - contents: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - ref: next - token: ${{ secrets.GITOPS_TOKEN || secrets.GITHUB_TOKEN }} - - - name: Setup Kustomize - uses: imranismail/setup-kustomize@v2 - with: - kustomize-version: "5.3.0" - - - name: Deploy via GitOps - run: | - WWW_TAG="sha-$(echo ${{ github.sha }} | cut -c1-7)" - bash ci/scripts/deploy-gitops.sh www "${WWW_TAG}" - - verify-rollout: - name: Verify - Production Rollout - needs: [release-tag-docker, deploy] - runs-on: ubuntu-latest - steps: - - name: Await production rollout - run: | - TARGET_COMMIT="sha-$(echo ${{ github.sha }} | cut -c1-7)" - TARGET_VERSION="v${{ needs.release-tag-docker.outputs.version }}" - ENDPOINT="https://www.ermnvldmr.com/version.json" - - echo "========================================================" - echo "Verifying production deployment on $ENDPOINT" - echo "Expected commit: $TARGET_COMMIT" - echo "Expected version: $TARGET_VERSION" - echo "========================================================" - - MAX_ATTEMPTS=45 - ATTEMPT=1 - - while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do - echo "Attempt $ATTEMPT/$MAX_ATTEMPTS: Polling $ENDPOINT..." - - RESPONSE=$(curl -sfL --max-time 10 -H "User-Agent: GitHub-Actions-Rollout-Verifier" "$ENDPOINT" 2>/dev/null || true) - - if [ -n "$RESPONSE" ]; then - LIVE_COMMIT=$(echo "$RESPONSE" | jq -r '.commit // empty') - LIVE_VERSION=$(echo "$RESPONSE" | jq -r '.version // empty') - - echo " Live commit: ${LIVE_COMMIT:-<none>}" - echo " Live version: ${LIVE_VERSION:-<none>}" - - if [ "$LIVE_COMMIT" = "$TARGET_COMMIT" ] || [ "$LIVE_VERSION" = "$TARGET_VERSION" ]; then - echo "✅ Success! Production has successfully rolled out $TARGET_VERSION ($TARGET_COMMIT)." - exit 0 - fi - else - echo " Endpoint did not return valid JSON or timed out." - fi - - ATTEMPT=$((ATTEMPT + 1)) - sleep 10 - done - - echo "❌ Deployment verification timed out after 7.5 minutes." - exit 1 diff --git a/.github/workflows/services-www-releases-required.yml b/.github/workflows/services-www-releases-required.yml deleted file mode 100644 index 78f312d..0000000 --- a/.github/workflows/services-www-releases-required.yml +++ /dev/null @@ -1,278 +0,0 @@ -name: services/www - releases/required - -on: - push: - branches: ['releases/www/**'] - workflow_dispatch: - -concurrency: - group: ci-www-rel-${{ github.ref_name }} - cancel-in-progress: true - -permissions: - contents: read - actions: read - -jobs: - setup-stage: - name: Setup - Stage - runs-on: ubuntu-latest - outputs: - cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.12.3 - - - name: Setup Node.js with pnpm cache - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - - name: Cache pnpm store - id: cache-pnpm-store - uses: actions/cache@v3 - with: - path: ~/.pnpm-store - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - build-configs: - name: Build - Configs - needs: setup-stage - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.12.3 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - name: Install dependencies - run: pnpm install --frozen-lockfile - - name: Build Rsbuild config - run: pnpm --filter @ermnvldmr/rsbuild-config build - - name: Build Storybook config - run: pnpm --filter @ermnvldmr/storybook-config build - - name: Build i18n - run: pnpm --filter @ermnvldmr/i18n build - - name: Upload Config Artifacts - uses: actions/upload-artifact@v4 - with: - name: config-artifacts - path: | - packages/rsbuild-config/dist - packages/storybook-config/dist - packages/i18n/dist - retention-days: 1 - - lint-www: - name: Lint - WWW - needs: build-configs - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.12.3 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - name: Install dependencies - run: pnpm install --frozen-lockfile - - name: Download Config Artifacts - uses: actions/download-artifact@v4 - with: - name: config-artifacts - path: packages - - name: Lint WWW service - run: pnpm --filter @ermnvldmr/www lint - - typecheck-www: - name: Typecheck - WWW - needs: build-configs - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.12.3 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - name: Install dependencies - run: pnpm install --frozen-lockfile - - name: Download Config Artifacts - uses: actions/download-artifact@v4 - with: - name: config-artifacts - path: packages - - name: Typecheck WWW service - run: pnpm --filter @ermnvldmr/www typecheck - - build-storybook: - name: Build - Storybook - needs: build-configs - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.12.3 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - name: Install dependencies - run: pnpm install --frozen-lockfile - - name: Download Config Artifacts - uses: actions/download-artifact@v4 - with: - name: config-artifacts - path: packages - - name: Build WWW Storybook - run: pnpm --filter @ermnvldmr/www build-storybook - - name: Upload Storybook build - uses: actions/upload-artifact@v4 - with: - name: storybook-static - path: services/www/storybook-static/ - retention-days: 1 - - test-www: - name: Test - Unit - needs: build-configs - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.12.3 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - name: Install dependencies - run: pnpm install --frozen-lockfile - - name: Download Config Artifacts - uses: actions/download-artifact@v4 - with: - name: config-artifacts - path: packages - - name: Test WWW service - run: pnpm --filter @ermnvldmr/www test:coverage - - test-storybook: - name: Test - Storybook - needs: build-storybook - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.12.3 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - name: Install dependencies - run: pnpm install --frozen-lockfile - - name: Download Config Artifacts - uses: actions/download-artifact@v4 - with: - name: config-artifacts - path: packages - - name: Install Playwright browsers - run: pnpm --filter @ermnvldmr/www exec playwright install --with-deps chromium - - name: Download Storybook build - uses: actions/download-artifact@v4 - with: - name: storybook-static - path: services/www/storybook-static/ - - name: Serve Storybook and run tests - run: pnpm --filter @ermnvldmr/www test:storybook:ci - - build-www: - name: Build - WWW Service - needs: build-configs - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.12.3 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - name: Install dependencies - run: pnpm install --frozen-lockfile - - name: Download Config Artifacts - uses: actions/download-artifact@v4 - with: - name: config-artifacts - path: packages - - name: Build WWW service - run: pnpm --filter @ermnvldmr/www build - - release-rc-docker: - name: Release - RC Docker Image - needs: build-www - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Calculate RC Version - id: version - run: | - RC_VERSION=$(bash ci/scripts/semantic-release.sh rc ${{ github.ref_name }}) - echo "rc_version=$RC_VERSION" >> $GITHUB_OUTPUT - - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ghcr.io/${{ github.repository }}/www - tags: | - type=raw,value=v${{ steps.version.outputs.rc_version }} - type=sha,format=short - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: . - file: ci/docker/www/Dockerfile - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} diff --git a/GEMINI.md b/GEMINI.md index d14cf9d..ae077a1 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -123,7 +123,7 @@ ermnvldmr.com/ - **DO NOT** use self-referential package imports within workspace packages; use relative paths. - **DO NOT** suppress linter or compiler errors with `@ts-ignore`, `@ts-nocheck`, or inline eslint disables without explicit user approval. - **DO NOT** commit unencrypted `.env` files or secret keys (`.env.keys`). -- **DO NOT** run `git commit` or execute automated git commits under any circumstances. +- **DO NOT** run destructive Git operations (such as force pushing to `main`/`next` or dropping uncommitted user work) without authorization. ## 5. Critical Workflows & Safety Directives @@ -143,5 +143,4 @@ The agent MUST NOT modify the following files or directories unless explicitly r - `.env.keys` (untracked decryption keys). - `pnpm-lock.yaml` (dependencies must be managed exclusively through `pnpm` CLI commands). - `LICENSE` file. -- `ci/` deployment configurations, Dockerfiles, and GitHub Actions workflows. - `services/docs/content/` and `services/www/content/` creative and literary works. diff --git a/ci/scripts/calver-release.sh b/ci/scripts/calver-release.sh new file mode 100755 index 0000000..96ecf3a --- /dev/null +++ b/ci/scripts/calver-release.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -euo pipefail + +YEAR_MONTH=$(date -u +'%Y.%m') +git fetch --tags --quiet 2>/dev/null || true + +# Find highest MICRO version for current YYYY.MM (matching tags formatted like YYYY.MM.MICRO) +LATEST_TAG=$(git tag -l "${YEAR_MONTH}.*" | sort -V | tail -n1 || echo "") + +if [ -z "$LATEST_TAG" ]; then + MICRO=0 +else + LATEST_MICRO="${LATEST_TAG#"${YEAR_MONTH}."}" + if [[ "$LATEST_MICRO" =~ ^[0-9]+$ ]]; then + MICRO=$((LATEST_MICRO + 1)) + else + MICRO=0 + fi +fi + +echo "${YEAR_MONTH}.${MICRO}" diff --git a/ci/scripts/semantic-release.sh b/ci/scripts/semantic-release.sh deleted file mode 100755 index e228130..0000000 --- a/ci/scripts/semantic-release.sh +++ /dev/null @@ -1,116 +0,0 @@ -#!/bin/bash -set -e - -MODE=$1 -TARGET=$2 -INCREMENT_MAJOR=${3:-false} - -if [ -z "$MODE" ] || [ -z "$TARGET" ]; then - echo "Usage: $0 <mode: checkout|rc|release> <target: service_path|branch_name> [increment_major: true|false]" - exit 1 -fi - -case $MODE in - checkout) - SERVICE_PATH=$TARGET - SERVICE_NAME=$(basename "$SERVICE_PATH") - - # Ensure we have the latest tags and branches - git fetch --all --tags --quiet 2>/dev/null || true - - BRANCH_NAME=$(node -e ' - const { execSync } = require("child_process"); - const serviceName = process.argv[1]; - const servicePath = process.argv[2]; - const incrementMajor = process.argv[3] === "true" || process.argv[3] === "major" || process.argv[3] === "1"; - - let rawBranchesAndTags = ""; - try { - const remoteBranches = execSync(`git branch -r --list "origin/releases/${serviceName}/v*"`, { encoding: "utf8" }); - const localBranches = execSync(`git branch --list "releases/${serviceName}/v*"`, { encoding: "utf8" }); - const tags = execSync(`git tag -l "releases/${serviceName}/v*"`, { encoding: "utf8" }); - rawBranchesAndTags = `${remoteBranches}\n${localBranches}\n${tags}`; - } catch (e) {} - - const versionMatches = Array.from(rawBranchesAndTags.matchAll(/releases\/[^\/]+\/v([0-9]+)\.([0-9]+)/g)); - - if (versionMatches.length === 0) { - let pkgVersion = "1.0"; - try { - pkgVersion = require(`./${servicePath}/package.json`).version.split(".").slice(0, 2).join("."); - } catch (e) {} - console.log(`releases/${serviceName}/v${pkgVersion}`); - process.exit(0); - } - - let maxMajor = 1; - let maxMinor = 0; - for (const match of versionMatches) { - const maj = parseInt(match[1], 10); - const min = parseInt(match[2], 10); - if (!isNaN(maj) && !isNaN(min)) { - if (maj > maxMajor || (maj === maxMajor && min > maxMinor)) { - maxMajor = maj; - maxMinor = min; - } - } - } - - if (incrementMajor) { - console.log(`releases/${serviceName}/v${maxMajor + 1}.0`); - } else { - console.log(`releases/${serviceName}/v${maxMajor}.${maxMinor + 1}`); - } - ' "$SERVICE_NAME" "$SERVICE_PATH" "$INCREMENT_MAJOR") - - echo "$BRANCH_NAME" - ;; - rc|release) - BRANCH_NAME=$TARGET - # Extract service name and major.minor from branch: releases/www/v1.2 - if [[ ! $BRANCH_NAME =~ ^releases/([^/]+)/v([0-9]+\.[0-9]+)$ ]]; then - echo "Error: Branch name must follow 'releases/<service>/v<major>.<minor>' format" - exit 1 - fi - SERVICE_NAME="${BASH_REMATCH[1]}" - MAJOR_MINOR="${BASH_REMATCH[2]}" - - TAG_PREFIX="releases/$SERVICE_NAME/v$MAJOR_MINOR" - - # Ensure we have the latest tags - git fetch --tags --quiet - - # Find the latest patch version for this Major.Minor - LATEST_TAG=$(git tag -l "$TAG_PREFIX.*" | grep -v "\-rc" | sort -V | tail -n1 || echo "") - - if [ -z "$LATEST_TAG" ]; then - PATCH=0 - else - LATEST_VERSION=${LATEST_TAG#"$TAG_PREFIX."} - PATCH=$((LATEST_VERSION + 1)) - fi - - VERSION_TAG="$MAJOR_MINOR.$PATCH" - - if [ "$MODE" == "release" ]; then - echo "$VERSION_TAG" - else - # Calculate RC based on commit count since last tag - # This ensures unique, increasing RC numbers without needing to tag every RC - LAST_SERVICE_TAG=$(git describe --tags --match "releases/$SERVICE_NAME/*" --abbrev=0 HEAD 2>/dev/null || echo "") - - if [ -z "$LAST_SERVICE_TAG" ]; then - COMMITS_COUNT=$(git rev-list --count HEAD) - else - COMMITS_COUNT=$(git rev-list --count "$LAST_SERVICE_TAG"..HEAD) - fi - - RC=$((COMMITS_COUNT + 1)) - echo "$VERSION_TAG-rc.$RC" - fi - ;; - *) - echo "Unknown mode: $MODE" - exit 1 - ;; -esac diff --git a/services/docs/content/en/ermnvldmr/deployment-gitops.md b/services/docs/content/en/ermnvldmr/deployment-gitops.md index 05b32ea..888266f 100644 --- a/services/docs/content/en/ermnvldmr/deployment-gitops.md +++ b/services/docs/content/en/ermnvldmr/deployment-gitops.md @@ -39,10 +39,10 @@ ci/k8s/ * **Workflow**: `.github/workflows/services-docs-next-required.yml`. * **Action**: Builds Docker image, updates `ci/k8s/apps/docs/kustomization.yaml`, commits to `next`, and runs automated `verify-rollout`. -2. **`services/www` (Semantic Release Dispatch)**: - * **Trigger**: Dispatched via `.github/workflows/services-www-releases-dispatch-release.yml`. - * **Standard Mode**: Runs full verification suite (Vitest, Storybook Playwright, Lint, Typecheck, Build) ➔ Tags git release ➔ Pushes image to GHCR ➔ Updates GitOps tag ➔ Runs `verify-rollout`. - * **Fast-Track Mode (`skip_ci: true`)**: Bypasses test suites for emergency deploys and rollbacks, immediately building the image, updating GitOps, and verifying the production rollout. +2. **`services/www` (Trunk-Based CD with CalVer Release Gate)**: + * **Trigger**: Push to `next` modifying `services/www/**` or workspace dependencies (`packages/ui/**`, `packages/stl/**`, `packages/i18n/**`), or manual `workflow_dispatch`. + * **Workflow**: `.github/workflows/services-www-next-required.yml`. + * **Pipeline**: Full validation suite (Vitest, Storybook Playwright, Lint, Typecheck, Build) ➔ Builds Docker image with `sha-<commit>` tag ➔ Approval gate (`services-www-releases-publish`) tags CalVer `YYYY.MM.MICRO` in Git & GHCR ➔ Approval gate (`services-www-releases-deploy`) updates GitOps tag ➔ Runs `verify-rollout`. --- diff --git a/services/docs/content/ru/ermnvldmr/deployment-gitops.md b/services/docs/content/ru/ermnvldmr/deployment-gitops.md index 6da023b..2ea5905 100644 --- a/services/docs/content/ru/ermnvldmr/deployment-gitops.md +++ b/services/docs/content/ru/ermnvldmr/deployment-gitops.md @@ -39,10 +39,10 @@ ci/k8s/ * **Workflow**: `.github/workflows/services-docs-next-required.yml`. * **Действие**: Сборка Docker-образа, обновление `ci/k8s/apps/docs/kustomization.yaml`, коммит в `next` и запуск задачи `verify-rollout`. -2. **`services/www` (Семантический релиз через Dispatch)**: - * **Триггер**: Запуск вручную через `.github/workflows/services-www-releases-dispatch-release.yml`. - * **Стандартный режим**: Полный цикл тестов (Vitest, Storybook Playwright, Lint, Typecheck, Build) ➔ Git-тег релиза ➔ Пуш в GHCR ➔ Обновление тега в GitOps ➔ Проверка через `verify-rollout`. - * **Быстрый режим (`skip_ci: true`)**: Пропуск тестов для экстренных деплоев и откатов с мгновенной сборкой образа, пушем в GitOps и верификацией. +2. **`services/www` (Trunk-Based CD с релизным гейтом CalVer)**: + * **Триггер**: Push в ветку `next` с изменениями в `services/www/**` или зависимых пакетах (`packages/ui/**`, `packages/stl/**`, `packages/i18n/**`), либо ручной `workflow_dispatch`. + * **Workflow**: `.github/workflows/services-www-next-required.yml`. + * **Пайплайн**: Полный цикл тестов и проверок (Vitest, Storybook Playwright, Lint, Typecheck, Build) ➔ Сборка Docker-образа с тегом `sha-<commit>` ➔ Гейт подтверждения (`services-www-releases-publish`) выставляет тег CalVer `YYYY.MM.MICRO` в Git и GHCR ➔ Гейт подтверждения (`services-www-releases-deploy`) обновляет GitOps ➔ Проверка раскатки через `verify-rollout`. ---