refactor(template-policy): align generated policy.yaml with post-D5b loader shape#29
Merged
Merged
Conversation
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>
There was a problem hiding this comment.
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.hbswith loader-required shape (pre_write.enforced_patterns: [],pre_pr.docs_sync_conditional: [],post_merge.skills_conditional[0].trigger,pre_compact.persistincludingunsaved_pattern_candidates, and keepskills_allowedomitted). - Add
bin/tests/test_template_loader_contract.pyto 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 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 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). |
| - **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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
meta-repo ↔ templatedocumentado desde D5b y reforzado en F3 (cada escenario de selftest sobre-escribíasynthetic/policy.yamlpara evadir el shape pre-D5b emitido por el generator).policy.yamlque emitetemplates/policy.yaml.hbsparsea con el loader actual dehooks/_lib/policy.pysin devolverNoneen los 5 accessors loader-relevant. Lockdown víabin/tests/test_template_loader_contract.py(Python-side, A6) sobre los 3 profiles canónicos.bin/_selftest.py. D3 + D6 conservan overlays mínimos por diseño explícito (A1 emiteenforced_patterns: [], A2 omiteskills_allowed— overlays inyectan entries para ejercer deny paths).Decisiones ratificadas en Fase -1
lifecycle.pre_write.enforced_patterns: [](clave presente, lista vacía). Loader devuelvePreWriteRules(()), noNone. Proyectos generados no heredan los enforcement patterns del meta-repo.skills_allowedclave omitida. Loader devuelveNone(deferred), reflejando el estado del meta-repo hasta que cada proyecto pueble su allowlist.lifecycle.pre_compact.persistcon los 3 items canónicos (decisions_in_flight,phase_minus_one_state,unsaved_pattern_candidates).lifecycle.post_merge.skills_conditional[0].triggercon globs genéricos stack-agnostic (unión TS+Python:src/**,lib/**,*.py,package.json,pyproject.toml) +skip_if_onlypara docs +min_files_changed: 2. Suficiente para quepost_merge_trigger()devuelva un dataclass tipado; los proyectos afinan cuando estabilicen convenciones.Ajustes durante Fase -1 (anti-overselling)
templates/requirements-dev.txt.hbs(el contract test no demostró necesidad real).post_merge.triggersuficiente 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).hooks/,hooks/_lib/policy.py,policy.yaml(root),.claude/skills/**,agents/**,.claude/rules/skills-map.md.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