gaia-framework 1.105.1 → 1.127.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.claude/commands/gaia-bridge-disable.md +18 -0
  2. package/.claude/commands/gaia-bridge-enable.md +18 -0
  3. package/.claude/commands/gaia-fill-test-gaps.md +17 -0
  4. package/CLAUDE.md +64 -1
  5. package/_gaia/_config/gaia-help.csv +2 -0
  6. package/_gaia/_config/global.yaml +14 -1
  7. package/_gaia/_config/lifecycle-sequence.yaml +23 -3
  8. package/_gaia/_config/skill-manifest.csv +1 -0
  9. package/_gaia/_config/workflow-manifest.csv +2 -0
  10. package/_gaia/core/agents/orchestrator.md +1 -1
  11. package/_gaia/core/protocols/review-gate-check.xml +45 -5
  12. package/_gaia/core/validators/test-environment-validator.js +191 -0
  13. package/_gaia/core/workflows/bridge-toggle/checklist.md +11 -0
  14. package/_gaia/core/workflows/bridge-toggle/instructions.xml +69 -0
  15. package/_gaia/core/workflows/bridge-toggle/workflow.yaml +27 -0
  16. package/_gaia/dev/skills/_skill-index.yaml +13 -0
  17. package/_gaia/dev/skills/code-review-standards.md +50 -0
  18. package/_gaia/dev/skills/edge-cases.md +201 -0
  19. package/_gaia/lifecycle/knowledge/brownfield/ci-test-detection.md +194 -0
  20. package/_gaia/lifecycle/knowledge/brownfield/test-execution-scan.md +13 -0
  21. package/_gaia/lifecycle/skills/document-rulesets.md +93 -3
  22. package/_gaia/lifecycle/templates/story-template.md +7 -7
  23. package/_gaia/lifecycle/templates/test-gap-analysis-template.md +221 -0
  24. package/_gaia/lifecycle/workflows/4-implementation/check-review-gate/checklist.md +1 -1
  25. package/_gaia/lifecycle/workflows/4-implementation/check-review-gate/instructions.xml +11 -11
  26. package/_gaia/lifecycle/workflows/4-implementation/code-review/instructions.xml +1 -1
  27. package/_gaia/lifecycle/workflows/4-implementation/create-story/instructions.xml +73 -2
  28. package/_gaia/lifecycle/workflows/4-implementation/dev-story/instructions.xml +25 -2
  29. package/_gaia/lifecycle/workflows/4-implementation/retrospective/instructions.xml +1 -1
  30. package/_gaia/lifecycle/workflows/4-implementation/run-all-reviews/instructions.xml +132 -9
  31. package/_gaia/lifecycle/workflows/4-implementation/security-review/checklist.md +2 -0
  32. package/_gaia/lifecycle/workflows/4-implementation/sprint-planning/instructions.xml +13 -0
  33. package/_gaia/lifecycle/workflows/4-implementation/sprint-planning/workflow.yaml +8 -0
  34. package/_gaia/lifecycle/workflows/4-implementation/validate-story/checklist.md +1 -0
  35. package/_gaia/lifecycle/workflows/4-implementation/validate-story/instructions.xml +11 -0
  36. package/_gaia/lifecycle/workflows/5-deployment/deployment-checklist/instructions.xml +11 -0
  37. package/_gaia/lifecycle/workflows/anytime/brownfield-onboarding/instructions.xml +48 -1
  38. package/_gaia/lifecycle/workflows/anytime/brownfield-onboarding/workflow.yaml +10 -0
  39. package/_gaia/testing/agents/test-architect.md +2 -0
  40. package/_gaia/testing/workflows/fill-test-gaps/checklist.md +16 -0
  41. package/_gaia/testing/workflows/fill-test-gaps/instructions.xml +128 -0
  42. package/_gaia/testing/workflows/fill-test-gaps/workflow.yaml +30 -0
  43. package/_gaia/testing/workflows/test-gap-analysis/instructions.xml +47 -14
  44. package/_gaia/testing/workflows/test-gap-analysis/workflow.yaml +1 -0
  45. package/gaia-install.sh +37 -0
  46. package/package.json +3 -3
@@ -0,0 +1,18 @@
1
+ ---
2
+ name: 'bridge-disable'
3
+ description: 'Disable the Test Execution Bridge in global.yaml.'
4
+ model: sonnet
5
+ ---
6
+
7
+ IT IS CRITICAL THAT YOU FOLLOW THESE STEPS:
8
+
9
+ <steps CRITICAL="TRUE">
10
+ 1. LOAD the FULL {project-root}/_gaia/core/engine/workflow.xml
11
+ 2. READ its entire contents — this is the CORE OS
12
+ 3. Pass {project-root}/_gaia/core/workflows/bridge-toggle/workflow.yaml as 'workflow-config'
13
+ 4. Set parameter: --mode disable
14
+ 5. Follow workflow.xml instructions EXACTLY
15
+ 6. Save outputs after EACH section
16
+ </steps>
17
+
18
+ $ARGUMENTS
@@ -0,0 +1,18 @@
1
+ ---
2
+ name: 'bridge-enable'
3
+ description: 'Enable the Test Execution Bridge in global.yaml.'
4
+ model: sonnet
5
+ ---
6
+
7
+ IT IS CRITICAL THAT YOU FOLLOW THESE STEPS:
8
+
9
+ <steps CRITICAL="TRUE">
10
+ 1. LOAD the FULL {project-root}/_gaia/core/engine/workflow.xml
11
+ 2. READ its entire contents — this is the CORE OS
12
+ 3. Pass {project-root}/_gaia/core/workflows/bridge-toggle/workflow.yaml as 'workflow-config'
13
+ 4. Set parameter: --mode enable
14
+ 5. Follow workflow.xml instructions EXACTLY
15
+ 6. Save outputs after EACH section
16
+ </steps>
17
+
18
+ $ARGUMENTS
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: 'fill-test-gaps'
3
+ description: 'Read gap report, triage by severity and story, propose remediation actions. Use when "fill test gaps".'
4
+ model: opus
5
+ ---
6
+
7
+ IT IS CRITICAL THAT YOU FOLLOW THESE STEPS:
8
+
9
+ <steps CRITICAL="TRUE">
10
+ 1. LOAD the FULL {project-root}/_gaia/core/engine/workflow.xml
11
+ 2. READ its entire contents — this is the CORE OS
12
+ 3. Pass {project-root}/_gaia/testing/workflows/fill-test-gaps/workflow.yaml as 'workflow-config'
13
+ 4. Follow workflow.xml instructions EXACTLY
14
+ 5. Save outputs after EACH section
15
+ </steps>
16
+
17
+ $ARGUMENTS
package/CLAUDE.md CHANGED
@@ -140,17 +140,34 @@ backlog → validating → ready-for-dev → in-progress → invalid → review
140
140
  ```
141
141
 
142
142
  **Review Gate:** A story in `review` requires ALL six reviews to pass before moving to `done`:
143
- - `/gaia-code-review` — APPROVE or REQUEST_CHANGES
143
+ - `/gaia-code-review` — PASSED or FAILED
144
144
  - `/gaia-qa-tests` — PASSED or FAILED
145
145
  - `/gaia-security-review` — PASSED or FAILED
146
146
  - `/gaia-test-automate` — PASSED or FAILED
147
147
  - `/gaia-test-review` — PASSED or FAILED
148
148
  - `/gaia-review-perf` — PASSED or FAILED
149
149
 
150
+ **Gate status vocabulary** (canonical, enforced by `/gaia-validate-story`): `UNVERIFIED` (default, not yet run) | `PASSED` (review passed) | `FAILED` (review failed). No other values are permitted in the Review Gate table. Code Review uses `APPROVE`/`REQUEST_CHANGES` as its internal verdict keyword in the report body, but writes `PASSED`/`FAILED` to the Review Gate row.
151
+
150
152
  Run `/gaia-run-all-reviews` to execute all six reviews sequentially via subagents — one command instead of six.
151
153
 
152
154
  If any review fails, the story returns to `in-progress`. The Review Gate table in the story file tracks progress.
153
155
 
156
+ ### Review Gate-to-Tier Mapping (E17-S12, FR-195)
157
+
158
+ When the Test Execution Bridge (ADR-028) is enabled, each review gate is linked to a set of test tiers (from the E17-S11 three-tier model) whose evidence is required to produce a PASSED verdict. The canonical mapping lives in `Gaia-framework/src/bridge/review-gate-tier-mapping.js` (`DEFAULT_GATE_TIER_MAPPING`) and can be overridden per-project via the `tiers.gate_mapping` block in `test-environment.yaml`.
159
+
160
+ | Review Gate | Required Tiers |
161
+ |---|---|
162
+ | `/gaia-qa-tests` | Tier 1 + Tier 2 (unit + integration) |
163
+ | `/gaia-test-automate` | Tier 1 (unit) |
164
+ | `/gaia-test-review` | Tier 2 (integration) |
165
+ | `/gaia-review-perf` | Tier 3 (e2e) |
166
+ | `/gaia-security-review` | Tier 2 + Tier 3 (integration + e2e) |
167
+ | `/gaia-code-review` | no tier (static analysis only) |
168
+
169
+ When a gate is UNVERIFIED, the Nudge Block surfaces the required tiers (e.g., "run Tier 1 + Tier 2 tests") via `formatNudgeSuggestion(gate, mapping)`. Full rationale and override semantics live in architecture §10.20.4.
170
+
154
171
  ### Infra Review Gate Substitutions
155
172
 
156
173
  For infrastructure stories (those whose `traces_to` field contains `IR-###`, `OR-###`, or `SR-###` requirement IDs), 4 of the 6 review gates use adapted criteria. Code Review and Security Review remain unchanged for all story types.
@@ -166,6 +183,52 @@ For infrastructure stories (those whose `traces_to` field contains `IR-###`, `OR
166
183
 
167
184
  **Detection mechanism:** The `review-gate-check` protocol reads the story's `traces_to` field and checks the requirement ID prefix. Each story is evaluated independently — platform projects with mixed stories get per-story gate selection based on their own requirement prefix.
168
185
 
186
+ ## Bridge Scope
187
+
188
+ The Test Execution Bridge (ADR-028, architecture §10.20) orchestrates test runs ONLY. The bridge does not deploy services, does not modify databases, and does not alter any infrastructure. This is a hard scope constraint enforced in code (FR-203) and must be preserved in every future change.
189
+
190
+ **Supported stacks (built-in adapters, architecture §10.20.11):**
191
+
192
+ The bridge ships with five static-import stack adapters, selected automatically by `getAdapter()` in `Gaia-framework/src/bridge/adapters/index.js`. Priority order is deterministic: `javascript → python → java → go → flutter`.
193
+
194
+ | Stack | Representative runner command | Detection pattern |
195
+ |---|---|---|
196
+ | JavaScript / TypeScript | `npx vitest run` (also `npm test`, Jest, Mocha, TAP) | `package.json` |
197
+ | Python | `pytest` | `pyproject.toml` / `pytest.ini` / `setup.cfg` / `setup.py` |
198
+ | Java | `mvn test` (also `gradle test`) | `pom.xml` / `build.gradle` |
199
+ | Go | `go test ./...` | `go.mod` |
200
+ | Flutter / Dart | `flutter test` | `pubspec.yaml` |
201
+
202
+ Adding a new stack adapter is documented in `docs/architecture/bridge-adapter-contract.md`. External / dynamic adapter loading is explicitly out of scope (architecture §10.20.11.4, threat T37).
203
+
204
+ **The bridge DOES:**
205
+ - Invoke project-owned test runners via standard CLI commands — one adapter per stack, one representative runner shown per row above
206
+ - Trigger a single CI workflow declared in `test-environment.yaml` via `gh workflow run`
207
+ - Poll the CI run until terminal state and fetch the run log
208
+ - Parse runner/CI output into the `test-results/{story_key}-execution.json` evidence schema
209
+ - Reject commands containing shell chaining operators (`;`, `&&`, `||`, `|`, `>`, `<`) outside of quoted arguments
210
+ - Reject any command not explicitly allowlisted from `test-environment.yaml` runners or the `package.json` test script
211
+
212
+ **The bridge DOES NOT:**
213
+ - Deploy services, applications, or container images
214
+ - Provision, modify, or tear down infrastructure (no `terraform apply`, no `kubectl apply`, no `docker run -d`)
215
+ - Alter databases (no migrations, no seed scripts, no schema changes)
216
+ - Commit code, push branches, or mutate the git repository
217
+ - Execute arbitrary shell commands or shell substitution (`` ` `` and `$()` are always rejected)
218
+ - Trigger any GitHub Actions workflow other than the `ci_workflow` declared in `test-environment.yaml`
219
+
220
+ **Enforcement points:**
221
+ - `Gaia-framework/src/bridge/bridge-scope-guard.js` — shared scope guard module exporting `assertInScope`, `assertCommandAllowed`, `assertCiWorkflowAllowed`
222
+ - Layer 2 local execution (`layer-2-local-execution.js`) calls all three guards before `spawn`
223
+ - Layer 2 CI execution (`layer-2-ci-execution.js`) calls the shell-operator guard on the runner command and the CI workflow allowlist guard before `gh workflow run`
224
+
225
+ **Threat model:** Architecture §10.20.10 enumerates the five bridge threats:
226
+ - **T20** — Environment misconfiguration (runner declared in `test-environment.yaml` does not match project stack). Mitigated by Layer 0 readiness checks and `assertCommandAllowed`.
227
+ - **T21** — Runner discovery failure (Layer 1 cannot match story key to test files). Mitigated by structured Layer 1 failure + `bridge_status: runner_not_found` evidence fallback.
228
+ - **T22** — Execution timeout (subprocess or CI workflow hangs). Mitigated by NFR-033 configurable timeout + SIGTERM/SIGKILL escalation.
229
+ - **T23** — Subprocess runaway via shell injection (chaining/substitution/redirection operators). Mitigated by `assertInScope` scope guard.
230
+ - **T24** — CI API unavailability (`gh` missing, auth expired, network failure). Mitigated by `defaultGhCheck` probe and local fallback + `assertCiWorkflowAllowed` on the fallback workflow.
231
+
169
232
  ## Memory Hygiene
170
233
 
171
234
  Agent memory sidecars accumulate decisions across sessions. Run `/gaia-memory-hygiene` periodically (recommended before each sprint) to detect stale, contradicted, or orphaned entries by cross-referencing sidecar decisions against current planning and architecture artifacts.
@@ -51,6 +51,8 @@ module,phase,name,code,command,required,agent-name,description,output-location
51
51
  "testing","anytime","test-review","test-review","gaia-test-review","false","test-architect","Review test quality","docs/test-artifacts"
52
52
  "testing","anytime","nfr-assessment","nfr","gaia-nfr","false","test-architect","Assess non-functional requirements","docs/test-artifacts"
53
53
  "testing","anytime","traceability","trace","gaia-trace","false","test-architect","Generate traceability matrix","docs/test-artifacts"
54
+ "testing","anytime","test-gap-analysis","test-gap-analysis","gaia-test-gap-analysis","false","test-architect","Scan test suite against requirements to identify coverage gaps","docs/test-artifacts"
55
+ "testing","4-implementation","fill-test-gaps","fill-test-gaps","gaia-fill-test-gaps","false","test-architect","End-to-end remediation for test-gap-analysis findings (sub-workflow composition)","docs/test-artifacts"
54
56
  "lifecycle","3-solutioning","security-threat-model","threat-model","gaia-threat-model","false","security","Create STRIDE/DREAD threat model","docs/planning-artifacts"
55
57
  "lifecycle","4-implementation","security-review","security-review","gaia-security-review","false","security","Pre-merge OWASP security review","docs/implementation-artifacts"
56
58
  "lifecycle","3-solutioning","infrastructure-design","infra-design","gaia-infra-design","false","devops","Design deployment topology and IaC","docs/planning-artifacts"
@@ -3,7 +3,7 @@
3
3
  # After modifying this file, run /gaia-build-configs to regenerate resolved configs.
4
4
 
5
5
  framework_name: "GAIA"
6
- framework_version: "1.105.1"
6
+ framework_version: "1.127.2"
7
7
 
8
8
  # User settings
9
9
  user_name: "jlouage"
@@ -48,3 +48,16 @@ installed_path: "{project-root}/_gaia"
48
48
  config_path: "{project-root}/_gaia/_config"
49
49
  memory_path: "{project-root}/_memory"
50
50
  checkpoint_path: "{project-root}/_memory/checkpoints"
51
+
52
+ # Test Execution Bridge (ADR-028, FR-202, NFR-035)
53
+ # Opt-in subsystem that runs tests during the post-review phase of applicable workflows.
54
+ # When bridge_enabled is false (the default), ALL bridge layers are completely bypassed
55
+ # with zero behavior change — no log messages, no file reads. This is the safety toggle
56
+ # for the entire bridge subsystem. Existing installations are unaffected by default.
57
+ test_execution_bridge:
58
+ # Master switch. false = bridge completely inactive (default, opt-in semantics).
59
+ # Set to true to activate the bridge in applicable workflows.
60
+ bridge_enabled: false
61
+ # Maximum wall-clock seconds the bridge will wait for a test run before aborting.
62
+ # Only consulted when bridge_enabled is true.
63
+ timeout_seconds: 300
@@ -399,6 +399,13 @@ sequence:
399
399
  standalone: true
400
400
  note: "Return to current lifecycle phase"
401
401
 
402
+ bridge-toggle:
403
+ module: core
404
+ command: /gaia-bridge-enable
405
+ next:
406
+ standalone: true
407
+ note: "Run /gaia-build-configs to regenerate resolved configs after toggling"
408
+
402
409
  party-mode:
403
410
  module: core
404
411
  command: /gaia-party
@@ -573,10 +580,23 @@ sequence:
573
580
  next:
574
581
  standalone: true
575
582
  suggestions:
583
+ - command: /gaia-sprint-plan
584
+ context: "To schedule remediation stories discovered by the gap analysis"
585
+ - command: /gaia-trace
586
+ context: "To update traceability matrix after closing coverage gaps"
576
587
  - command: /gaia-test-design
577
- context: "To design tests covering newly identified coverage gaps"
578
- - command: /gaia-test-automate
579
- context: "To automate tests filling the identified gaps"
588
+ context: "To redesign the test plan based on gap analysis findings"
589
+
590
+ fill-test-gaps:
591
+ module: testing
592
+ command: /gaia-fill-test-gaps
593
+ next:
594
+ standalone: true
595
+ suggestions:
596
+ - command: /gaia-test-gap-analysis
597
+ context: "To regenerate the gap analysis report before triaging"
598
+ - command: /gaia-sprint-plan
599
+ context: "To schedule remediation stories from the triage table"
580
600
 
581
601
  traceability:
582
602
  module: testing
@@ -14,3 +14,4 @@ name,displayName,description,path,applicable_agents
14
14
  "memory-management-cross-agent","Memory Management Cross-Agent","Cross-agent memory read protocol for loading other agents' sidecar files","_gaia/lifecycle/skills/memory-management-cross-agent.md","all"
15
15
  "document-rulesets","Document Rulesets","Artifact type detection, document-specific validation rulesets (prd, arch, ux, test-plan, epics), two-pass validation logic","_gaia/lifecycle/skills/document-rulesets.md","validator"
16
16
  "figma-integration","Figma Integration","Design tool detection, token extraction (W3C DTCG), component specs, frame generation, asset export, per-stack resolution","_gaia/dev/skills/figma-integration.md","all-dev"
17
+ "edge-cases","Edge Cases","Structured edge case analysis for M+ stories — boundary, error, timing, concurrency, integration, security, data, environment categories","_gaia/dev/skills/edge-cases.md","all-dev,sm,pm,qa,test-architect"
@@ -1,5 +1,6 @@
1
1
  name,displayName,description,module,phase,path,command,agent
2
2
  "brainstorming","Brainstorming","Facilitated brainstorming session","core","anytime","_gaia/core/workflows/brainstorming/workflow.yaml","gaia-brainstorming","orchestrator"
3
+ "bridge-toggle","Bridge Toggle","Enable or disable the Test Execution Bridge in global.yaml","core","anytime","_gaia/core/workflows/bridge-toggle/workflow.yaml","gaia-bridge-enable","orchestrator"
3
4
  "party-mode","Party Mode","Multi-agent group discussion","core","anytime","_gaia/core/workflows/party-mode/workflow.yaml","gaia-party","orchestrator"
4
5
  "advanced-elicitation","Advanced Elicitation","Deep requirements elicitation","lifecycle","1-analysis","_gaia/lifecycle/workflows/1-analysis/advanced-elicitation/workflow.yaml","gaia-advanced-elicitation","orchestrator"
5
6
  "brainstorm-project","Brainstorm Project","Brainstorm a new project idea","lifecycle","1-analysis","_gaia/lifecycle/workflows/1-analysis/brainstorm-project/workflow.yaml","gaia-brainstorm","analyst"
@@ -47,6 +48,7 @@ name,displayName,description,module,phase,path,command,agent
47
48
  "nfr-assessment","NFR Assessment","Assess non-functional requirements","testing","anytime","_gaia/testing/workflows/nfr-assessment/workflow.yaml","gaia-nfr","test-architect"
48
49
  "traceability","Traceability","Generate traceability matrix","testing","anytime","_gaia/testing/workflows/traceability/workflow.yaml","gaia-trace","test-architect"
49
50
  "test-gap-analysis","Test Gap Analysis","Scan test suite against requirements to identify coverage gaps","testing","anytime","_gaia/testing/workflows/test-gap-analysis/workflow.yaml","gaia-test-gap-analysis","test-architect"
51
+ "fill-test-gaps","Fill Test Gaps","End-to-end remediation for test-gap-analysis findings (sub-workflow composition)","testing","4-implementation","_gaia/testing/workflows/fill-test-gaps/workflow.yaml","gaia-fill-test-gaps","test-architect"
50
52
  "security-threat-model","Security Threat Model","Create STRIDE/DREAD threat model","lifecycle","3-solutioning","_gaia/lifecycle/workflows/3-solutioning/security-threat-model/workflow.yaml","gaia-threat-model","security"
51
53
  "security-review","Security Review","Pre-merge OWASP security review","lifecycle","4-implementation","_gaia/lifecycle/workflows/4-implementation/security-review/workflow.yaml","gaia-security-review","security"
52
54
  "infrastructure-design","Infrastructure Design","Design deployment topology and IaC","lifecycle","3-solutioning","_gaia/lifecycle/workflows/3-solutioning/infrastructure-design/workflow.yaml","gaia-infra-design","devops"
@@ -122,7 +122,7 @@ You must fully embody this agent's persona and follow the activation protocol EX
122
122
 
123
123
  {if review_count > 0:}
124
124
  Stories still in review:
125
- {for each: story key, which reviews PASSED/FAILED/PENDING}
125
+ {for each: story key, which reviews UNVERIFIED/PASSED/FAILED}
126
126
 
127
127
  {if failed_count > 0:}
128
128
  Failed stories:
@@ -27,9 +27,20 @@
27
27
 
28
28
  <step n="1" title="Read Review Gate and Determine Gate Type">
29
29
  <action>Read the story file's Review Gate table</action>
30
- <action>If Review Gate section is missing: initialize it with EXACTLY 6 rows — Code Review (PENDING), QA Tests (PENDING), Security Review (PENDING), Test Automation (PENDING), Test Review (PENDING), Performance Review (PENDING). Do NOT add any other rows.</action>
30
+ <action>If Review Gate section is missing: initialize it with EXACTLY 6 rows — Code Review (UNVERIFIED), QA Tests (UNVERIFIED), Security Review (UNVERIFIED), Test Automation (UNVERIFIED), Test Review (UNVERIFIED), Performance Review (UNVERIFIED). Do NOT add any other rows.</action>
31
31
  <action>If Review Gate table has extra rows beyond the 6 valid ones: remove the invalid rows</action>
32
- <action>Parse each row: Review name, Status (PENDING | PASSED | FAILED), Report link</action>
32
+ <action>Parse each row: Review name, Status (UNVERIFIED | PASSED | FAILED), Report link</action>
33
+
34
+ <!-- Legacy Status Normalization (E17-S2 / FR-191 / NFR-035)
35
+ Stories created before the UNVERIFIED/PASSED/FAILED vocabulary (E17-S1)
36
+ may carry legacy status values such as a literal dash ("-"), blank cells,
37
+ or the word "pending". These are treated as "not yet run" and MUST be
38
+ normalized to UNVERIFIED BEFORE gate evaluation in Step 2 so that Step 2
39
+ operates on canonical values only. NFR-035 requires this backward
40
+ compatibility — existing stories must not be broken by the vocabulary
41
+ change.
42
+ -->
43
+ <action>LEGACY NORMALIZATION (E17-S2 / NFR-035): Before evaluating gates, normalize every row's status by converting legacy values to the canonical vocabulary. For each of the 6 rows, if the Status cell is any of: a literal dash character "-", blank (empty cell or whitespace-only), or the word "pending" (case-insensitive), replace it with UNVERIFIED. This normalization happens in-memory during parsing — it does NOT rewrite the story file unless a subsequent review workflow persists its own update. Any other legacy value not in {"-", blank, "pending"} is left as-is and will be flagged by the PASSED/FAILED check in Step 2. The canonical mapping is: "-" → UNVERIFIED, blank → UNVERIFIED, "pending" → UNVERIFIED. After this step every row's in-memory status MUST be one of UNVERIFIED, PASSED, or FAILED.</action>
33
44
 
34
45
  <!-- Infra Gate Detection (FR-129): per-story gate type selection based on requirement ID prefix -->
35
46
  <action>Read the story file's YAML frontmatter traces_to field (e.g., traces_to: [IR-001, FR-128])</action>
@@ -44,14 +55,43 @@
44
55
  <critical>
45
56
  <mandate>You MUST execute the transition even if the gate was already fully passed before this run. The purpose is to ensure story status matches gate state.</mandate>
46
57
  <mandate>Use the status-sync protocol to update story status — this ensures both story file and sprint-status.yaml stay in sync.</mandate>
58
+ <mandate>HARD GATE (E17-S15 / A-050): A story transitioning from 'review' to 'done' MUST have a {story_key}-review-summary.md file in {implementation_artifacts}/. This is enforced structurally — not advisory. The only exception is when the story has zero individual review reports (i.e., it never actually entered the review process).</mandate>
47
59
  </critical>
48
60
  <action>Check two things: (1) the Review Gate table rows, (2) the current story status</action>
49
61
  <action>If ALL 6 rows show PASSED AND story status is 'review': read the "Definition of Done" section — verify every item is checked (- [x]). If any item is unchecked: log "BLOCKED: DoD incomplete — {unchecked items}". Do NOT transition to done.</action>
50
- <action>If ALL 6 rows show PASSED AND all DoD items checked AND story status is 'review':
62
+ <action>REVIEW SUMMARY HARD GATE (E17-S15 / A-050 / AC1, AC2, AC3): If ALL 6 rows show PASSED AND all DoD items checked AND story status is 'review', perform the review-summary.md existence check BEFORE invoking status-sync:
63
+ 1. Build the summary file path: {implementation_artifacts}/{story_key}-review-summary.md
64
+ 2. Check whether the summary file exists on disk.
65
+ 3. If the summary file EXISTS: the hard gate passes — proceed to invoke status-sync below.
66
+ 4. If the summary file is MISSING: apply the skip-when-never-reviewed exception. Count how many of the 6 individual review reports exist on disk:
67
+ - {implementation_artifacts}/{story_key}-review.md (Code Review)
68
+ - {implementation_artifacts}/{story_key}-security-review.md (Security Review)
69
+ - {test_artifacts}/{story_key}-qa-tests.md (QA Tests)
70
+ - {test_artifacts}/{story_key}-test-automation.md (Test Automation)
71
+ - {test_artifacts}/{story_key}-test-review.md (Test Review)
72
+ - {implementation_artifacts}/{story_key}-performance-review.md (Performance Review)
73
+ If ALL 6 individual review reports are ALSO missing (count == 0), the story never entered review — the summary is not required for this edge case. Log "Review summary check skipped: story {story_key} has no review reports on disk (story was not actually reviewed)." and proceed to invoke status-sync below.
74
+ If ANY of the 6 individual review reports exist (count greater than 0) but the summary is missing, the hard gate FAILS. HALT with this exact message and do NOT invoke status-sync: "Review summary missing for {story_key}. Run `/gaia-run-all-reviews {story_key}` to generate the summary, or create it manually via `/gaia-create-review-summary {story_key}`."</action>
75
+ <action>If ALL 6 rows show PASSED AND all DoD items checked AND story status is 'review' AND the review-summary hard gate passed above:
51
76
  <invoke-protocol ref="status-sync" story_key="{story_key}" new_status="done" source_workflow="review-gate-check" />
52
77
  </action>
53
78
  <action>If ALL 6 rows show PASSED AND the story status is already 'done': log "Story already done. No update needed."</action>
54
- <action>If any row is FAILED: log which reviews failed. Do not change story status.</action>
55
- <action>If any row is PENDING: log which reviews are still pending. Do not change story status.</action>
79
+
80
+ <!-- FAILED Gate Reporting (E17-S2 / FR-191 / AC3)
81
+ When any gate is FAILED the protocol surfaces which gates failed and
82
+ returns the story to in-progress so the developer can address the
83
+ review feedback and re-enter the review cycle.
84
+ -->
85
+ <action>FAILED GATE REPORTING (AC3): If any row has Status == FAILED, collect the list of failed gate names into a comma-separated list (e.g., "Code Review, Security Review"). Emit this exact message format: "Review gate FAILED for {story_key}. Failed gates: {list of FAILED gate names}. Story is returned to in-progress so the developer can address the review feedback." Then invoke the status-sync protocol to transition the story from 'review' to 'in-progress' — the story MUST be returned to in-progress when any gate is FAILED:
86
+ <invoke-protocol ref="status-sync" story_key="{story_key}" new_status="in-progress" source_workflow="review-gate-check" />
87
+ </action>
88
+
89
+ <!-- UNVERIFIED Gate Reporting (E17-S2 / FR-191 / AC4)
90
+ When any gate is UNVERIFIED the protocol reports the count and the
91
+ specific gate names of those not yet run. It blocks advancement to
92
+ done without changing the story status — the story stays in 'review'
93
+ until the outstanding reviews are executed.
94
+ -->
95
+ <action>UNVERIFIED GATE REPORTING (AC4): If no row is FAILED but any row has Status == UNVERIFIED, count the UNVERIFIED rows (N) and collect the list of gate names of the UNVERIFIED rows. Emit this exact message format: "{N} gates not yet run for {story_key}. Outstanding gate names: {list of UNVERIFIED gate names}. Run the corresponding review workflows (/gaia-code-review, /gaia-qa-tests, /gaia-security-review, /gaia-test-automate, /gaia-test-review, /gaia-review-perf) or /gaia-run-all-reviews to complete them." The count N and the names MUST both appear in the emitted message. This blocks advancement to done — do NOT change the story status; the story remains in 'review' until all gates show PASSED.</action>
56
96
  </step>
57
97
  </protocol>
@@ -194,6 +194,182 @@ function validateRunnerEntry(runner, index) {
194
194
  return warnings;
195
195
  }
196
196
 
197
+ // ─── E25-S6: tiers.stack_hints validation ──────────────────────
198
+ //
199
+ // The generic parseSimpleYaml above only supports 2-level maps, so the
200
+ // deeply-nested `tiers.stack_hints.{pytest_markers,gradle_tasks,go_build_tags,
201
+ // flutter_suites}` block is scanned directly from the raw YAML text. This keeps
202
+ // the existing parser untouched and avoids introducing a new YAML dependency
203
+ // (same approach used elsewhere in the bridge adapters — see ADR-038 §10.20.11).
204
+ //
205
+ // Contract: FR-312, ADR-038 §10.20.11 (stack adapter registry).
206
+ // See docs/test-artifacts/test-environment.yaml.example for canonical usage.
207
+
208
+ /**
209
+ * Allowed keys inside `tiers.stack_hints`. Any other key is a validation error
210
+ * naming the unknown key and the accepted keys (AC6).
211
+ */
212
+ const STACK_HINT_KEYS = Object.freeze([
213
+ "pytest_markers",
214
+ "gradle_tasks",
215
+ "go_build_tags",
216
+ "flutter_suites",
217
+ ]);
218
+
219
+ /**
220
+ * Extract the `tiers.stack_hints` block from raw YAML text using a
221
+ * line-oriented indent scan. Returns an object describing the block and any
222
+ * shape violations — does not throw. When the block is absent, returns null.
223
+ *
224
+ * Supported shapes (partial blocks are valid — unset tiers fall back to the
225
+ * adapter default, per Dev Notes):
226
+ * tiers:
227
+ * stack_hints:
228
+ * pytest_markers: ["slow", "integration"]
229
+ * gradle_tasks:
230
+ * unit: test
231
+ * integration: integrationTest
232
+ * e2e: e2eTest
233
+ * go_build_tags: [integration, e2e]
234
+ * flutter_suites:
235
+ * unit: test/
236
+ * integration: integration_test/
237
+ * e2e: integration_test/e2e/
238
+ *
239
+ * @param {string} text
240
+ * @returns {{ raw: object, warnings: string[] }|null}
241
+ */
242
+ function extractStackHints(text) {
243
+ const lines = text.split(/\r?\n/);
244
+
245
+ // Locate `tiers:` at column 0.
246
+ let tiersIdx = -1;
247
+ for (let i = 0; i < lines.length; i++) {
248
+ if (/^tiers\s*:\s*(#.*)?$/.test(lines[i])) {
249
+ tiersIdx = i;
250
+ break;
251
+ }
252
+ }
253
+ if (tiersIdx === -1) return null;
254
+
255
+ // Find `stack_hints:` nested under `tiers:` (indent >= 2, < indent of next
256
+ // top-level key). We just look line-by-line for a `^(\s+)stack_hints\s*:`
257
+ // whose indent is > 0 and occurs before any column-0 key after `tiers:`.
258
+ let hintsIdx = -1;
259
+ let hintsIndent = -1;
260
+ for (let i = tiersIdx + 1; i < lines.length; i++) {
261
+ const l = lines[i];
262
+ if (l.trim() === "" || l.trimStart().startsWith("#")) continue;
263
+ const indent = l.length - l.trimStart().length;
264
+ if (indent === 0) break; // left the tiers: subtree
265
+ if (/^\s+stack_hints\s*:\s*(#.*)?$/.test(l)) {
266
+ hintsIdx = i;
267
+ hintsIndent = indent;
268
+ break;
269
+ }
270
+ }
271
+ if (hintsIdx === -1) return null;
272
+
273
+ // Walk child lines whose indent is > hintsIndent. Stop when indent <= hintsIndent.
274
+ const warnings = [];
275
+ const block = {};
276
+ let currentKey = null;
277
+ let currentKeyIndent = -1;
278
+ let currentSubMap = null;
279
+
280
+ const recordUnknown = (key) => {
281
+ warnings.push(
282
+ `Unknown key 'tiers.stack_hints.${key}'. Accepted keys: ${STACK_HINT_KEYS.join(", ")}.`
283
+ );
284
+ };
285
+
286
+ for (let i = hintsIdx + 1; i < lines.length; i++) {
287
+ const rawLine = lines[i];
288
+ const stripped = rawLine.replace(/#.*$/, "").trimEnd();
289
+ if (stripped.trim() === "") continue;
290
+ const indent = stripped.length - stripped.trimStart().length;
291
+ if (indent <= hintsIndent) break; // left stack_hints subtree
292
+
293
+ const trimmed = stripped.trim();
294
+
295
+ // Top-level stack_hints key (first indent level beneath stack_hints:).
296
+ // Track the first child indent we see — any line at that indent is a key.
297
+ if (currentKeyIndent === -1 || indent === currentKeyIndent) {
298
+ currentKeyIndent = indent;
299
+ const colonIdx = trimmed.indexOf(":");
300
+ if (colonIdx === -1) continue;
301
+ const key = trimmed.substring(0, colonIdx).trim();
302
+ const val = trimmed.substring(colonIdx + 1).trim();
303
+
304
+ if (!STACK_HINT_KEYS.includes(key)) {
305
+ recordUnknown(key);
306
+ currentKey = null;
307
+ currentSubMap = null;
308
+ continue;
309
+ }
310
+
311
+ currentKey = key;
312
+ if (val === "") {
313
+ // Expect a sub-map on following lines (e.g., gradle_tasks: / flutter_suites:)
314
+ currentSubMap = {};
315
+ block[key] = currentSubMap;
316
+ } else if (val.startsWith("[") && val.endsWith("]")) {
317
+ // Flow sequence: pytest_markers: [a, b]
318
+ const items = val
319
+ .slice(1, -1)
320
+ .split(",")
321
+ .map((s) => s.trim().replace(/^['"]|['"]$/g, ""))
322
+ .filter((s) => s !== "");
323
+ // Validate shape: string[] only
324
+ const invalid = items.find((x) => typeof x !== "string");
325
+ if (invalid !== undefined) {
326
+ warnings.push(
327
+ `Invalid shape for 'tiers.stack_hints.${key}': expected array of strings.`
328
+ );
329
+ }
330
+ block[key] = items;
331
+ currentSubMap = null;
332
+ } else {
333
+ // Scalar value where an array or map was expected — shape error.
334
+ warnings.push(
335
+ `Invalid shape for 'tiers.stack_hints.${key}': expected ${
336
+ key === "pytest_markers" || key === "go_build_tags"
337
+ ? "array of strings"
338
+ : "map of { unit, integration, e2e }"
339
+ }, got scalar.`
340
+ );
341
+ block[key] = parseScalar(val);
342
+ currentSubMap = null;
343
+ }
344
+ continue;
345
+ }
346
+
347
+ // Nested sub-map entry (gradle_tasks / flutter_suites children).
348
+ if (indent > currentKeyIndent && currentKey && currentSubMap) {
349
+ const colonIdx = trimmed.indexOf(":");
350
+ if (colonIdx === -1) continue;
351
+ const k = trimmed.substring(0, colonIdx).trim();
352
+ const v = trimmed.substring(colonIdx + 1).trim();
353
+ // Only unit / integration / e2e are meaningful for tier maps, but we
354
+ // record whatever keys appear — downstream adapters pick the ones they need.
355
+ if (v === "") continue;
356
+ const parsed = parseScalar(v);
357
+ // For gradle_tasks / flutter_suites, values must be strings.
358
+ if (
359
+ (currentKey === "gradle_tasks" || currentKey === "flutter_suites") &&
360
+ typeof parsed !== "string"
361
+ ) {
362
+ warnings.push(
363
+ `Invalid shape for 'tiers.stack_hints.${currentKey}.${k}': expected string, got ${typeof parsed}.`
364
+ );
365
+ }
366
+ currentSubMap[k] = parsed;
367
+ }
368
+ }
369
+
370
+ return { raw: block, warnings };
371
+ }
372
+
197
373
  // ─── Public API ─────────────────────────────────────────────────
198
374
 
199
375
  /**
@@ -285,8 +461,23 @@ export function validateTestEnvironment(content, options = {}) {
285
461
  }
286
462
  }
287
463
 
464
+ // E25-S6 / FR-312 / ADR-038 §10.20.11: validate `tiers.stack_hints` block.
465
+ // The generic parseSimpleYaml above only supports 2-level maps, so the
466
+ // nested stack_hints block is scanned directly from the raw text.
467
+ const stackHints = extractStackHints(content);
468
+ if (stackHints && stackHints.warnings.length > 0) {
469
+ warnings.push(...stackHints.warnings);
470
+ }
471
+
288
472
  return {
289
473
  valid: warnings.length === 0,
290
474
  warnings,
475
+ // Expose the parsed block for adapter consumers (AC3 wiring). Null when
476
+ // the block is absent — callers must fall back to adapter defaults.
477
+ stackHints: stackHints ? stackHints.raw : null,
291
478
  };
292
479
  }
480
+
481
+ // E25-S6: exported for unit tests and downstream consumers that need direct
482
+ // access to the scanner without running the full validator.
483
+ export { extractStackHints, STACK_HINT_KEYS };
@@ -0,0 +1,11 @@
1
+ # Bridge Toggle — Post-Complete Checklist
2
+
3
+ ## Validation Items
4
+
5
+ - [ ] `bridge_enabled` flag in global.yaml is in the target state (true for enable, false for disable)
6
+ - [ ] All YAML comments in global.yaml are preserved after the write
7
+ - [ ] No other keys in global.yaml were modified (only `bridge_enabled` value changed)
8
+ - [ ] Idempotent behavior verified: invoking the same mode twice produces no write on the second invocation
9
+ - [ ] Post-toggle summary was displayed with previous state, new state, and next-step suggestion
10
+ - [ ] Summary includes reminder to run `/gaia-build-configs`
11
+ - [ ] For disable mode: post-flip checks section was skipped entirely
@@ -0,0 +1,69 @@
1
+ <workflow name="bridge-toggle">
2
+ <critical>
3
+ <mandate>This workflow modifies global.yaml — preserve ALL comments, key ordering, and formatting.</mandate>
4
+ <mandate>Use regex-based in-place edit targeting ONLY the bridge_enabled line — never regenerate the full file.</mandate>
5
+ <mandate>Idempotent: if the flag is already in the target state, do NOT write the file.</mandate>
6
+ </critical>
7
+
8
+ <step n="1" title="Read Current Bridge State">
9
+ <action>Read {project-root}/_gaia/_config/global.yaml</action>
10
+ <action>Extract test_execution_bridge.bridge_enabled value</action>
11
+ <action>If the test_execution_bridge section is missing: treat bridge_enabled as false (AC3)</action>
12
+ <action>If the section exists but bridge_enabled key is missing: treat as false (AC3)</action>
13
+ <action>Capture the raw file bytes for idempotency verification</action>
14
+ <action>Report: "Current bridge state: {state}"</action>
15
+ </step>
16
+
17
+ <step n="2" title="Idempotency Check">
18
+ <action>Compare current state against target mode (enable → true, disable → false)</action>
19
+ <check if="current_state == target_state">
20
+ Report "Bridge already {enabled|disabled}" and exit with status ok.
21
+ Do NOT write global.yaml. A byte-level diff must show zero changes.
22
+ </check>
23
+ </step>
24
+
25
+ <step n="3" title="Write Updated State">
26
+ <action>Use regex-based in-place edit to update ONLY the bridge_enabled: line within the test_execution_bridge: section</action>
27
+ <action>Regex pattern: /^(\s+bridge_enabled:\s*)(true|false)/m — replace capture group 2 with target value</action>
28
+ <action>This preserves inline comments on the same line and all surrounding YAML content</action>
29
+ <action>If the test_execution_bridge section is missing: emit error "test_execution_bridge section not found in global.yaml — cannot toggle. Add the section first (see ADR-028 §10.20.7)."</action>
30
+ <action>Write the updated content back to global.yaml</action>
31
+ </step>
32
+
33
+ <!-- Step 4: Post-Flip Checks (Enable Mode Only) — E17-S22
34
+ Delivered by E17-S22. After the flag flip in Step 3, detect and validate
35
+ docs/test-artifacts/test-environment.yaml and produce a structured
36
+ `post_flip_result` object for Step 5's summary composer. This step is
37
+ skipped on disable mode (AC7) and when Step 3 was an idempotent no-op
38
+ (Test Scenario #6 — no state transition means no post-flip checks). -->
39
+ <step n="4" title="Post-Flip Checks (Enable Only)">
40
+ <action if="mode == disable">Skip — disable mode does not perform post-flip checks (AC7). Set post_flip_result = {kind: "skipped", reason: "disable-mode"} and proceed to Step 5.</action>
41
+ <action if="mode == enable and not changed">Skip — no state transition occurred (bridge was already enabled). Set post_flip_result = {kind: "skipped", reason: "idempotent"} and proceed to Step 5.</action>
42
+ <action if="mode == enable and changed">Invoke runPostFlipChecks from {project-path}/src/bridge/bridge-post-flip-checks.js with {projectRoot, mode: "enable", changed: true, yolo: {yolo_mode}}. This module performs the filesystem stat of docs/test-artifacts/test-environment.yaml (resolved relative to {project-root}, NOT {project-path} — per AC) and calls into the existing E17-S7 validator at Gaia-framework/_gaia/core/validators/test-environment-validator.js. Capture its return value as post_flip_result.</action>
43
+ <action if="post_flip_result.kind == 'present_valid'">Collect post_flip_result.runners[] (name + tier) for inclusion in Step 5's summary. No prompt is shown. Proceed to Step 5.</action>
44
+ <action if="post_flip_result.kind == 'present_invalid'">Collect post_flip_result.errors[] as warnings for inclusion in Step 5's summary. Per AC5, do NOT roll back the bridge_enabled flag flip — the user can manually repair the manifest and re-run /gaia-build-configs. Proceed to Step 5.</action>
45
+ <action if="post_flip_result.kind == 'absent' and not yolo_mode">Render the 3-option prompt — options payload is available as post_flip_result.options (POST_FLIP_ABSENT_OPTIONS from bridge-post-flip-checks.js). Path A (ADR-028 §10.20.12.3): none of the options auto-invoke any sub-workflow. Present the three options exactly as written in the options list and ask the user to select one.</action>
46
+ <ask if="post_flip_result.kind == 'absent' and not yolo_mode">
47
+ `docs/test-artifacts/test-environment.yaml` was not found. The bridge is enabled, but Layer 1 will fail-fast at invocation time until the manifest is created. Select a next-step suggestion:
48
+
49
+ [a] Run `/gaia-brownfield` to auto-generate test-environment.yaml (next-step suggestion — NOT auto-invoked)
50
+ [b] Copy `docs/test-artifacts/test-environment.yaml.example` to `docs/test-artifacts/test-environment.yaml` and customize
51
+ [c] Skip — bridge is enabled but will fail-fast at Layer 1 with a clear error message until the manifest is created
52
+
53
+ Choose [a/b/c]:
54
+ </ask>
55
+ <action if="post_flip_result.kind == 'absent' and not yolo_mode">Record the user's selection as post_flip_result.choice (one of "a", "b", "c"). Per AC4, do NOT invoke any sub-workflow regardless of choice. Selection (a) does NOT run /gaia-brownfield; selection (b) does NOT copy the example file. Both are next-step suggestions the user must act on in the next conversation turn. Proceed to Step 5.</action>
56
+ <action if="post_flip_result.kind == 'absent' and yolo_mode">YOLO mode auto-selects option (c) Skip. runPostFlipChecks has already set post_flip_result.choice = "c" and post_flip_result.yoloAutoSkipped = true. Log a warning: "Bridge is enabled but docs/test-artifacts/test-environment.yaml is missing — Layer 1 will fail-fast until the manifest is created." Proceed to Step 5.</action>
57
+ <action>Pass post_flip_result to Step 5's summary composer (buildSummary receives it as the `postFlipResult` field).</action>
58
+ </step>
59
+
60
+ <step n="5" title="Post-Toggle Summary">
61
+ <action>Invoke buildSummary from {project-path}/src/bridge/bridge-toggle.js with {previousState, newState, mode, changed, postFlipResult} — the postFlipResult field is the structured object captured by Step 4 (or {kind: "skipped", ...} on disable / idempotent paths).</action>
62
+ <action>Display the returned summary. It always includes: previous state, new state, mode, and whether a write occurred.</action>
63
+ <action if="mode == enable and post_flip_result.kind == 'present_valid'">Summary includes the detected runners table (name + tier) produced by Step 4.</action>
64
+ <action if="mode == enable and post_flip_result.kind == 'present_invalid'">Summary includes the schema validation errors as warnings. The bridge_enabled flag is NOT rolled back (AC5).</action>
65
+ <action if="mode == enable and post_flip_result.kind == 'absent'">Summary includes the user's selected option (a/b/c) from Step 4, or — in YOLO mode — the auto-selected skip warning.</action>
66
+ <action>AC6: the summary always ends with the next-step suggestion "Run `/gaia-build-configs` to regenerate the resolved configs so the bridge_enabled change takes effect." — regardless of which Step 4 branch ran (present_valid, present_invalid, absent with any choice, skipped, or idempotent).</action>
67
+ <action if="mode == disable">Summary only confirms new state and reminds about /gaia-build-configs. No post-flip check output (AC7 — Step 4 was skipped).</action>
68
+ </step>
69
+ </workflow>