Skip to content

refactor(template-policy): align generated policy.yaml with post-D5b loader shape#29

Merged
javiAI merged 5 commits into
mainfrom
refactor/template-policy-d5b-migration
Apr 27, 2026
Merged

refactor(template-policy): align generated policy.yaml with post-D5b loader shape#29
javiAI merged 5 commits into
mainfrom
refactor/template-policy-d5b-migration

Conversation

@javiAI
Copy link
Copy Markdown
Owner

@javiAI javiAI commented Apr 27, 2026

Summary

  • Cierra el drift meta-repo ↔ template documentado desde D5b y reforzado en F3 (cada escenario de selftest sobre-escribía synthetic/policy.yaml para evadir el shape pre-D5b emitido por el generator).
  • Scope literal: el policy.yaml que emite templates/policy.yaml.hbs parsea con el loader actual de hooks/_lib/policy.py sin devolver None en los 5 accessors loader-relevant. Lockdown vía bin/tests/test_template_loader_contract.py (Python-side, A6) sobre los 3 profiles canónicos.
  • Selftest cleanup: overlays D4 + D5 removidos en bin/_selftest.py. D3 + D6 conservan overlays mínimos por diseño explícito (A1 emite enforced_patterns: [], A2 omite skills_allowed — overlays inyectan entries para ejercer deny paths).

Decisiones ratificadas en Fase -1

  • A1lifecycle.pre_write.enforced_patterns: [] (clave presente, lista vacía). Loader devuelve PreWriteRules(()), no None. Proyectos generados no heredan los enforcement patterns del meta-repo.
  • A2skills_allowed clave omitida. Loader devuelve None (deferred), reflejando el estado del meta-repo hasta que cada proyecto pueble su allowlist.
  • A3lifecycle.pre_compact.persist con los 3 items canónicos (decisions_in_flight, phase_minus_one_state, unsaved_pattern_candidates).
  • A4lifecycle.post_merge.skills_conditional[0].trigger con globs genéricos stack-agnostic (unión TS+Python: src/**, lib/**, *.py, package.json, pyproject.toml) + skip_if_only para docs + min_files_changed: 2. Suficiente para que post_merge_trigger() devuelva un dataclass tipado; los proyectos afinan cuando estabilicen convenciones.
  • A5 — RED contract test → GREEN bundle único (template + 3 snapshots + cleanup overlays selftest) → docs-sync. NO commit-por-profile.
  • A6 — contract test Python-side. NO TS reimplementation.

Ajustes durante Fase -1 (anti-overselling)

  • NO se creó templates/requirements-dev.txt.hbs (el contract test no demostró necesidad real).
  • Framing literal: "policy.yaml generado parsea con loader actual", no "7 hooks blackbox".
  • post_merge.trigger suficiente para remover overlays F3 D4+D5, no diseño perfecto para todos los proyectos.

Test plan

  • pytest -q (full): 671 passed + 1 skipped (vs main 644 + 1 skip = neto +27 contract tests).
  • npx vitest run: 515 passed / 0 failed.
  • bin/pos-selftest.sh: 5/5 escenarios verdes sin overlays D4/D5.
  • bin/tests/test_template_loader_contract.py --collect-only: 27 tests collected (5 classes, 9 test methods, parametrizadas × 3 profiles canónicos).
  • Scope adherence: no se tocó hooks/, hooks/_lib/policy.py, policy.yaml (root), .claude/skills/**, agents/**, .claude/rules/skills-map.md.
  • Pre-commit-review (subagent): no findings ≥ confidence 80; scope + invariantes limpios.

Docs-sync incluido en este PR

  • ROADMAP.md: fila refactor flippeada a ✅; § progreso nuevo bajo Fase F; carry-over post-F4 flippeado.
  • HANDOFF.md §1: snapshot actualizado (current branch, entregables, suite numbers). §7: drift gotcha rewritten as cerrado. §9: removed from carry-over list.
  • MASTER_PLAN.md § F3b: stub flippeado a ✅ PR pendiente con A1–A6 + 3 ajustes + entregables + contract lockdowns.
  • docs/ARCHITECTURE.md: § 10 Selftest drift cerrado + § 13 deferral flippeado.
  • .claude/rules/hooks.md: § Drift temporal flippeado a cerrado con A1–A4 enumerados.

🤖 Generated with Claude Code

Javier and others added 4 commits April 27, 2026 23:31
Fase 0 kickoff + Fase 1 (RED) para `refactor/template-policy-d5b-migration`.

## Scope literal

`policy.yaml` emitido por `templates/policy.yaml.hbs` parsea con los 5 accessors
de `hooks/_lib/policy.py` sin devolver `None` en los loader-relevant. Cierra el
drift documentado desde D5b y los overlays por escenario en `bin/_selftest.py`.

NO es "7 hooks blackbox end-to-end": es shape-conformance del template contra
el loader actual. Lo demás queda explícitamente diferido.

## Decisiones Fase -1 ratificadas

- A1 — `lifecycle.pre_write.enforced_patterns: []` (lista vacía, NO ausente).
  Loader → `PreWriteRules(())`.  No copiar patrones meta-repo.
- A2 — `skills_allowed` clave omitida. Loader → `None` (deferred).
- A3 — `lifecycle.pre_compact.persist` con los 3 items canónicos
  (decisions_in_flight, phase_minus_one_state, unsaved_pattern_candidates).
- A4 — `lifecycle.post_merge.skills_conditional[0].trigger` genérico
  conservador (src/**, lib/**, *.py, package.json, pyproject.toml). Suficiente
  para quitar overlays F3, no diseño perfecto. Comentario explícito.
- A5 — un solo commit GREEN (template + 3 snapshots + limpieza overlays).
- A6 — contract test Python-side, real loader, real generator. Sin
  reimplementar contrato en TS.

## Archivos en este commit (RED)

- `bin/tests/test_template_loader_contract.py` (NEW, 182 líneas, 7 clases × 3
  profiles parametrizados → 27 casos; 21 failed + 6 passed contra `main`).

Los 6 que pasan ya cubren estado deseado final
(`TestPreCompactRules::test_returns_dataclass_not_none` + `TestSkillsAllowed`).

## Próximos commits planeados

- GREEN: migrar `templates/policy.yaml.hbs` + re-snapshot 3 profiles +
  eliminar `POLICY_*_ONLY` overlays de `bin/_selftest.py`.
- Docs-sync: ROADMAP, HANDOFF, MASTER_PLAN §F3b → ✅, ARCHITECTURE §7,
  `.claude/rules/hooks.md § Drift temporal` → cerrado.

## Ajustes obligatorios respetados

1. NO `templates/requirements-dev.txt.hbs` salvo que el contract test
   demuestre necesidad real (no la demuestra hoy — diferido).
2. Framing trimmed: "policy.yaml generado por template parsea con el
   loader actual", no "7 hooks blackbox".
3. `post_merge` trigger es suficiente para quitar overlays F3, no un
   diseño perfecto para todos los proyectos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…loader shape

Closes the documented drift between meta-repo policy.yaml (post-D5b shape)
and templates/policy.yaml.hbs (pre-D5b shape). After this commit, a
generated project policy.yaml parses cleanly with hooks/_lib/policy.py
without per-scenario overlays in F3 selftest.

Ratified Fase -1 decisions (A1-A6):
- A1: lifecycle.pre_write.enforced_patterns: [] (key present, empty list).
  Loader returns PreWriteRules(()), not None. Generated projects do not
  inherit meta-repo enforcement patterns.
- A2: skills_allowed key omitted. Loader returns None (deferred), matching
  meta-repo state until skills become populated.
- A3: lifecycle.pre_compact.persist contains the three canonical items
  (decisions_in_flight, phase_minus_one_state, unsaved_pattern_candidates).
- A4: lifecycle.post_merge.skills_conditional[0].trigger present with
  conservative generic globs (src/**, lib/**, *.py, package.json,
  pyproject.toml). Sufficient to make post_merge_trigger() return a typed
  dataclass; projects tune once conventions stabilize.
- A5: single GREEN commit (template + 3 snapshot regenerations + selftest
  overlay cleanup).
- A6: contract test runs Python-side against real loader + real generator
  output; no TS reimplementation.

bin/_selftest.py overlay cleanup:
- D4 (pre-pr-gate): POLICY_DOCS_SYNC_ONLY overlay removed; template now
  emits matching baseline.
- D5 (post-action): POLICY_POST_MERGE_ONLY overlay removed; scenario
  refactored to commit src/feature.py + src/helper.py (matches new generic
  trigger globs and min_files_changed: 2).
- D3 (pre-write-guard): overlay kept by design (A1 emits empty list);
  comment refreshed.
- D6 (stop-policy-check): overlay kept by design (A2 omits key); comment
  refreshed.

Tests:
- 27/27 contract tests pass (bin/tests/test_template_loader_contract.py;
  9 cases x 3 canonical profiles).
- 645 pytest + 515 vitest, no regression.
- pos-selftest 5/5 scenarios pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…hooks rule

Reflects closure of meta-repo <-> template drift in this branch:
- ROADMAP: Fase F flipped to fully closed; refactor row flipped to ✅ with
  scope summary; new § "refactor/template-policy-d5b-migration" entry under
  Progreso Fase F; carry-over post-F4 flipped to ✅.
- HANDOFF §1: snapshot updated (current branch + entregables + suite numbers
  645/515/5-of-5). §7: drift gotcha rewritten as "cerrado" with contract
  test reference. §9: removed refactor from carry-over list.
- MASTER_PLAN: § F3b stub flipped to ✅ PR pendiente with ratified
  decisions A1-A6, three Fase -1 ajustes (no requirements-dev, literal
  framing, post_merge minimum-viable), entregables, contract lockdowns.
  § F4 carry-over flipped to ✅.
- docs/ARCHITECTURE.md § 10 Selftest: drift abierto block rewritten as
  drift cerrado; § 13 deferral flipped from "drift abierto post-D5b" to
  "cerrada post-F4". Selftest narrative updated to reflect that D4/D5
  scenarios run against template baseline, not overlays.
- .claude/rules/hooks.md § Drift temporal: section header flipped to
  "cerrado en `refactor/template-policy-d5b-migration`"; ratified A1-A4
  decisions enumerated; contract test + selftest cleanup documented.

Per Fase -1 scope: did not touch hooks/, hooks/_lib/policy.py, policy.yaml,
.claude/skills/, agents/, .claude/rules/skills-map.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Writer-scoped pass on docs already in branch diff:
- A4 description: globs are stack-agnostic union, not stack-conditional.
- Math: 5 classes, 9 test methods, x 3 profiles = 27 (was written as
  5 x 9 x 3 which would be 135).
- Suite numbers reflect actual main baseline (644+1 skip) and actual
  post-cierre count (671+1 skip), not the 666 cited from F4 ROADMAP
  (off-by-20 pre-existing in F4 docs, not corrected here).

No behavior change. Only files already in branch diff edited (ROADMAP,
MASTER_PLAN). What was not touched: HANDOFF (numbers there were already
accurate to the smaller-scope pytest run I quoted), ARCHITECTURE,
hooks rule.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 27, 2026 21:56
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the generated policy.yaml template to match the current post-D5b loader expectations (hooks/_lib/policy.py), adds a Python-side contract test to prevent future drift, and simplifies selftest scenarios that no longer need policy overlays now that the template is aligned.

Changes:

  • Align templates/policy.yaml.hbs with loader-required shape (pre_write.enforced_patterns: [], pre_pr.docs_sync_conditional: [], post_merge.skills_conditional[0].trigger, pre_compact.persist including unsaved_pattern_candidates, and keep skills_allowed omitted).
  • Add bin/tests/test_template_loader_contract.py to lock down loader compatibility against real generator output across canonical profiles.
  • Remove no-longer-needed selftest overlays for D4/D5 and update documentation to reflect the closed drift.

Reviewed changes

Copilot reviewed 9 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
templates/policy.yaml.hbs Emits policy fields in the shape required by the Python loader (avoids None in key accessors).
generator/snapshots/nextjs-app/policy.yaml.snap Snapshot regenerated to reflect updated policy template output.
generator/snapshots/cli-tool/policy.yaml.snap Snapshot regenerated to reflect updated policy template output.
generator/snapshots/agent-sdk/policy.yaml.snap Snapshot regenerated to reflect updated policy template output.
bin/tests/test_template_loader_contract.py New contract tests exercising real generator output + real loader accessors across canonical profiles.
bin/_selftest.py Removes D4/D5 policy overlays and adjusts D5 scenario changes to match new generic trigger.
docs/ARCHITECTURE.md Updates selftest documentation to reflect drift closure and reduced overlays.
ROADMAP.md Marks the refactor as completed and documents the contract/cleanup work.
MASTER_PLAN.md Updates the plan/status narrative to reflect drift closure and contract tests.
HANDOFF.md Updates current-branch snapshot and drift status narrative.
.claude/rules/hooks.md Updates hook rules documentation to reflect that the template drift is now closed and contract-tested.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread HANDOFF.md Outdated
Comment on lines 9 to 10
- `refactor/template-policy-d5b-migration` entregó: `templates/policy.yaml.hbs` migrado al shape contractual con loader (A1 `pre_write.enforced_patterns: []` + A2 `skills_allowed` omitido + A3 `pre_compact.persist` 3 items canónicos + A4 `post_merge.skills_conditional[0].trigger` con globs genéricos conservadores) + 3 snapshots regenerados (cli-tool, nextjs-app, agent-sdk) + cleanup de overlays D4+D5 en `bin/_selftest.py` (D3+D6 mantienen overlays mínimos por diseño explícito). Contract test Python-side `bin/tests/test_template_loader_contract.py` corre los 5 accessors reales del loader sobre el output del generator real. Suite: 645 passed + 1 skipped (vs baseline F4 666; +27 contract tests, sin regresión). Vitest 515/515. Selftest 5/5 escenarios verdes sin overlays para D4/D5.
- F4 entregó: `.claude-plugin/marketplace.json` (manifest oficial Claude Code marketplace primitive: top-level `{name, owner, plugins, metadata}` + `owner.name="javiAI"` + `plugins[0].source.{source:github, repo:javiAI/project-operating-system, ref:v0.1.0}`) + `.github/workflows/release.yml` (5 jobs: version-match → selftest + build-bundle → publish-release → mirror-marketplace condicional via `vars.POS_MARKETPLACE_REPO`) + `docs/RELEASE.md` (runbook de versionado + bundle + flujo + recovery + activación de mirror) + bump `plugin.json.version` 0.0.1→0.1.0 (single source of truth: tag git = `v${version}`; `marketplace.json.source.ref` espeja). Bundle release curated plugin-only (excluye `generator/`, `templates/`, `questionnaire/`, `tools/`). Repo público `javiAI/pos-marketplace` **diferido** — creación manual cuando se decida ir live; mirror skippea silenciosamente si `POS_MARKETPLACE_REPO` está vacío. Suite: 665 passed + 1 skipped.
Comment thread ROADMAP.md Outdated
Comment on lines 742 to 744
- `refactor/template-policy-d5b-migration` — ✅ cerrada post-F4. Migra `templates/policy.yaml.hbs` al shape contractual con el loader (`pre_write.enforced_patterns: []`, `pre_pr.docs_sync_conditional: []`, `pre_compact.persist` con 3 items, `post_merge.skills_conditional[0].trigger` con globs genéricos conservadores, `skills_allowed` omitido por diseño). Contract test Python-side (`bin/tests/test_template_loader_contract.py`) corre los accessors reales del loader contra output del generator real sobre los 3 profiles canónicos. Overlays de D4 y D5 removidos en `bin/_selftest.py`; D3 y D6 mantienen overlays mínimos por diseño explícito (A1 emite lista vacía, A2 omite la clave). Suite post: 645 pytest + 515 vitest + selftest 5/5.

**Criterio de salida**: 665 verdes + 1 skip. Sin regresión sobre F3. Docs-sync dentro del PR (ROADMAP § F4 + HANDOFF §1/§9/§22 + MASTER_PLAN § Rama F4 expandida + `.claude/rules/ci-cd.md` release job promovido + `docs/ARCHITECTURE.md § 13 Marketplace + Release flow` reescrita + `docs/RELEASE.md` nuevo runbook). `pre-pr-gate.py` aprueba este mismo PR — required `ROADMAP.md` + `HANDOFF.md` satisfecho; conditional `.github/**` no está bajo `generator|hooks|skills|patterns` (no requirement adicional).
Comment thread ROADMAP.md Outdated
- **Framing literal**: scope descrito como "policy.yaml generado por template parsea con el loader actual" — **no** como "7 hooks blackbox" (overselling de implicaciones downstream que no eran parte de este PR).
- **post_merge trigger**: globs genéricos suficientes para remover overlays F3 D4+D5, **no** un diseño perfecto de triggers para cualquier proyecto futuro. Los proyectos afinan cuando sus convenciones estabilicen.

**Criterio de salida**: 645 + 1 skip + selftest 5/5. Docs-sync dentro del PR (ROADMAP fila + § progreso + carry-over post-F4 flippeado a ✅; HANDOFF §1 + §9; MASTER_PLAN § F3b stub flippeado a cerrada; `docs/ARCHITECTURE.md § 10 Selftest end-to-end` drift cerrado + § 13 deferral flippeado; `.claude/rules/hooks.md` § Drift cerrado).
…view

Copilot review caught three internal contradictions where this branchs docs
cited stale partial-scope test counts (645 from a pytest subset over
bin/tests/ + hooks/tests/, plus a typo cascade of 666 copied from F4s 665).

Ground truth from full pytest on this branch:
  671 passed + 1 skipped (vs main baseline 644 + 1 skip = neto +27 contract tests)

Edits (3, all one-liners):
- HANDOFF.md:9 -- 645 + 1 skipped vs baseline F4 666 -> 671 + 1 skipped vs
  main baseline 644 + 1 skip
- ROADMAP.md:744 -- Suite post: 645 pytest -> Suite post: 671 pytest
- ROADMAP.md:780 -- Criterio de salida: 645 + 1 skip -> 671 + 1 skip

The 645 reference in MASTER_PLAN.md:852 is left intact -- it is F4s
historical baseline F3 645 + 20 netos calculation, not this branchs
suite citation.

Co-Authored-By: Claude Opus 4.7 noreply@anthropic.com
@javiAI javiAI merged commit 0ff429e into main Apr 27, 2026
7 checks passed
@javiAI javiAI deleted the refactor/template-policy-d5b-migration branch April 27, 2026 22:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants