fix(tooling): hexagen-monaco 0.8.0 + CI / architecture gates - #37
Conversation
The wizard that scaffolded this repo wrote .architecture/manifest.yaml with a workspaceTemplate key that only exists in the manifest schema from @hexagen-monaco 0.6.0 onward, while pinning ^0.4.0 — which caret semantics on 0.x can never resolve past 0.4.x. The resolved 0.4.2 CLIs reject the manifest (unrecognized_keys), so every hexagen command (sync, arch validate, validate-templates, hexagen-lint) failed at load. Verified against the published artifacts: 0.4.2 rejects this manifest, 0.6.0 loads it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The manifest and linter invariants still described the original scaffold, not the project that evolved from it: - adapter packages (CreativeGeneration, GovernanceAndCompliance, Distribution) implement ports owned by CampaignOrchestration, so they legitimately import it — declare that in depends_on and whitelist it for the arch-linter (edges depend on the core, per .agents/architecture.md); - domain value-object folders are named value-objects, not value_objects, so the layer config no longer plans snake_case duplicates; - the two external-service-client.out-port.ts stubs were deliberately deleted as dead code in the coverage work, so the spec no longer asks sync to recreate them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First successful `yarn sync` since scaffold time. The engine:
- removed the two empty `export {}` placeholder barrels in shared
(domain/value-objects, infrastructure/adapters) and their parent
re-exports — completing the dead-stub cleanup from the coverage work;
- introduced per-layer adapter barrels (adapters/index.ts) and rewired
each infrastructure/index.ts through them; CreativeGeneration's now
also re-exports fonts.js and safe-path.js, which the old barrel missed;
- added empty application-layer skeletons for the adapter contexts and
`"dependencies": {}` to shared/package.json (engine conventions).
Verified in a throwaway clone before applying here: a second sync is
byte-level idempotent, and build/typecheck/lint/test:cov (287 tests,
100% coverage thresholds) all pass on the synced tree. The run log
(SYNC-MIGRATION-REPORT.md) is regenerated per invocation, so it is
gitignored rather than committed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0.7.0 ships the upstream fixes this branch was blocked on (see the 2026-06-09 session-log entry): honest exit codes, read-only dry-run, truthful op counts, and a dedicated --check drift mode. sync:check is the script CI gates on.
…shared package
yarn 4 strips an empty "dependencies": {} from workspace manifests on
every install; hexagen <=0.6.0 re-added it on every sync (protected-key
merge re-injects missing keys), so the file flip-flopped between the two
tools forever. 0.7.0's generator emits the block only when non-empty, so
yarn's form is now the converged state.
Wires the two gates the 2026-06-09 session deliberately left out (at 0.6.0 every failure mode exited 0, so they would have passed vacuously): - sync:check — read-only drift gate, exit 1 iff the committed tree differs from what .architecture/manifest.yaml generates - lint:arch — layer/import rule enforcement Both run ahead of the build (sub-second, fail-fast). Install switches to --immutable now that the lockfile is committed, which also keeps an install-mutated lockfile from tripping sync --check's clean-tree preflight with a confusing message.
Code Review by Qodo
Context used✅ Compliance rules (platform):
4 rules 1. Overbroad arch whitelist
|
Reviewer's guideRead order: The one-line story: 0.6.0's CLIs couldn't fail loudly, so CI never gated on them; 0.7.0's can, so now it does. What to scrutinize
Known cosmetic nit (upstream, not actionable here)Dry-run logs each planned barrel op twice; the summary table and exit code count it once. Tracked upstream in hexagen-monaco. Not in this PR
|
PR Summary by Qodofix(tooling): hexagen 0.7.0 and CI architecture drift/lint gates WalkthroughsDescription• Bump @hexagen-monaco sync/arch-linter to ^0.7.0 and add sync:check script. • Gate CI on architecture drift (sync --check) and layer rules (arch validate) before build. • Update architecture spec/exports to match repo shape and generated barrel layout. Diagramgraph TD
CI["GitHub Actions CI"] --> Install["yarn install --immutable"] --> SyncCheck["sync:check (hexagen sync --check)"] --> Tree[("Repo working tree")]
Install --> ArchLint["lint:arch (hexagen arch validate)"] --> Rules[("Arch rules")]
SyncCheck --> Manifest[(".architecture/manifest.yaml")]
ArchLint --> LinterCfg[("linter-config.yaml")]
SyncCheck --> Barrels["Generated barrel exports"] --> Tree
High-Level AssessmentThe following are alternative approaches to this PR: 1. Gate on sync --dry-run + diff parsing
2. Pre-commit hook for sync/arch validation
3. Only run arch gates on changed paths (selective CI)
Recommendation: Proceed with the PR’s approach: hexagen 0.7.0 makes sync --check a trustworthy, read-only CI drift gate with meaningful exit codes. Keeping the gates fail-fast ahead of the build is the right tradeoff given their low runtime and high signal for generated-artifact drift and architecture rule violations. File ChangesEnhancement (5)
Refactor (5)
Documentation (1)
Other (4)
|
CI's first run of the new drift gate caught a real reproducibility gap: git cannot track empty directories, so the 22 generated-but-still-empty layer dirs (entities/, value-objects/, ports/in|out, use-cases/, adapters/) existed locally but not in a fresh checkout — sync --check correctly reported 22 pending creates. .gitkeep makes the generated layout git-representable; the sync engine probes the directory, not its contents, so a kept dir contributes zero ops. Also removes five stale value_objects/ (underscore) dirs orphaned by the 2026-06-09 layer-naming reconciliation — empty, so they never appeared in git status.
Reviewer's guide — delta (first CI run caught a real gap)What happened: the drift gate failed its own debut (run 27446848304): Why it's a true positive, not a gate bug: git cannot represent an empty directory. The dirs existed in every long-lived working copy (the June sync session created them) but in no fresh checkout — so the committed tree genuinely did not converge. The local runs passed for the same reason the bug survived: nobody's working copy was fresh. The fix (
Upstream note: this failure mode awaits every hexagen consumer that wires |
Drop the @campaignfoundry/CampaignOrchestration/** global_whitelist entry. The package's exports map exposes only "." (ports/types are re-exported through the root), so no subpath import can resolve and none exist in the tree — the /** pattern only added breadth to the arch gate without covering any real edge. The exact-root entry stays: it is load-bearing, since adapter packages import the ports inward from the core. lint:arch stays green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verification of the Qodo review findingsI dug into both findings against the actual arch-linter source, the package 1. "Overbroad arch whitelist" — ✅ valid kernel, but overstated as a Bug → fixed in dd6c645What's right: the mechanism is accurate. What's overstated:
Fix applied: dropped the 2. "Stale test plan statement" — ✅ valid as an Informational nit, but mis-attributed and out of scope (no change)What's right: Corrections:
Leaving it as-is — it reads correctly as a historical planning record, and editing an out-of-scope doc here would only widen this PR's surface. Happy to update the wording to past tense in a docs-only follow-up if preferred. Net: one safe one-line tightening applied (dd6c645) for finding #1; finding #2 is a correct-but-minor, out-of-scope doc nit left for a separate pass. 🤖 Generated with Claude Code |
Both @hexagen-monaco/sync and @hexagen-monaco/arch-linter are now published at 0.8.0 (latest). 0.8.0 is functionally identical to 0.7.1 for both (only version bumps since v0.7.1); the substantive work — sync's manifest-migration feature and the arch-linter cross-package-violation refactor — shipped in 0.7.1. ^0.7.0 excluded 0.8.0 (0.x caret locks the minor), so this bumps the ceiling to align the consumer with the generator's published latest and clear the version skew. lint:arch stays green on the 0.8.0 linter. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
yarn.lock is committed, so setup-node can key a yarn cache on it. Enabling cache: yarn requires Corepack active BEFORE setup-node — otherwise its cache probe runs Yarn Classic and reports the wrong folder — so Enable Corepack now runs ahead of the setup-node step. Caches Berry's global cache (enableGlobalCache: true -> ~/.yarn/berry/cache). A miss only re-downloads (install --immutable stays authoritative), so it can't turn a green build red. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GitHub defaults the JavaScript action runtime to Node 24 on 2026-06-16 (Node 20 deprecation; removed fall 2026). checkout@v4, setup-node@v4, and cache@v4 declare using: node20; their v5 majors are node24-native and nothing else (verified release notes). ubuntu-latest satisfies the required runner >= v2.327.1. The setup-node cache: yarn config and corepack ordering are preserved; node-version "22" (the build Node) is unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docs(agents): session-log for hexagen 0.8.0 + Node-24 CI work (#37 follow-up)
What
Completes the hexagen tooling arc this branch started on 2026-06-09: bumps
@hexagen-monaco/sync+@hexagen-monaco/arch-linterto^0.7.0and wires the CI gates the first session deliberately left out.Why now
The 2026-06-09 session repaired the toolchain to 0.6.0 but did not gate CI on it, because at 0.6.0 every failure mode exited 0 — a gate would have passed vacuously (see
.agents/session-log.md). Everything on that session's upstream blocker list shipped in hexagen-monaco 0.7.0:--dry-runnot read-only (deleted barrels, wrote reports)Total ops : 0git reset --hard && git clean -fdin this repoChanges
package.json: both pins^0.6.0→^0.7.0(resolve to 0.7.0); newsync:checkscript.packages/shared/package.json: accepted yarn-4's normalized form (no empty"dependencies": {}). yarn 4 strips the empty block on every install and ≤0.6.0 sync re-added it on every run — a permanent churn loop between the two tools. 0.7.0 emits the block only when non-empty, so yarn's form is now the converged state..github/workflows/ci.yml:yarn install --immutable(lockfile is committed; also keeps an install-mutated lockfile from tripping the drift gate's clean-tree preflight)yarn sync:check— drift gate: exit 1 iff the committed tree differs from what.architecture/manifest.yamlgeneratesyarn lint:arch— layer/import rule enforcementVerification
sync:checkon the converged tree: exit 0,Total ops : 0(the 0.6.0 CLI showed 43 phantom ops here)Drift detected: 1 pending change(s) (1 to create, …); tree restored, re-verified clean. The gate is non-vacuous in both directions.lint:arch: compliant, exit 0yarn install --immutable,build,typecheck,lint: all greentest:cov: 287/287, 100% statements/branches/functions/linesThe earlier commits on this branch (0.6.0 repair: pin fix,
.architecture/reconciliation, first converged sync) are documented in the session log and were already reviewed in-session; this PR delivers the whole arc together.First-run catch (red → green)
The gate's very first CI execution (run 27446848304) failed — legitimately: git can't track empty directories, so the 22 generated-but-still-empty layer dirs existed locally but not in the fresh checkout, and
sync --checkcorrectly reported22 pending change(s) (22 to create). "Converged on my machine" was not "converged in a clone."Fix (
11aff11):.gitkeepin all 22 skeleton dirs (the engine probes the directory, not its contents — a kept dir contributes zero ops), plus removal of five stalevalue_objects/(underscore) dirs orphaned by the June layer-naming reconciliation that empty-dir invisibility had been hiding. Verified via fresh clone (install --immutable+ both gates green) before pushing; CI run 27447001987 confirms.Filed upstream (hexagen-monaco): the generator should emit the skeleton in a git-representable form so consumers don't each rediscover this.
Summary by CodeRabbit
Chores
CI/Build