feat: library entry + section-scoped regenerateSections - #12
Conversation
The composer's iterative rediscovery path, at exactly the ledger's granularity — decideRegeneration's invariant restated per section: regeneration never destroys human-authored content, no force override. - src/regenerate.ts: regenerateSections(existing, fresh) — tool-owned sections (recorded hash matches content) refresh from the fresh document; human-owned sections (hash mismatch) are preserved verbatim WITH their recorded hash, so the ownership signal survives; governance and every non-generated key carry over by construction; a document without a ledger is refused. One safe additive refinement: inside a human-owned components section, fresh components with NEW ids are added (pure addition destroys nothing — the common new-component-in-source case). Existing entries are never modified; prop-level refresh of enriched components is explicitly out of scope until a per-component ledger exists. - src/index.ts library entry: exportProject(configPath) (load -> generate -> validate, no writes) + regenerateSections + the ownership primitives (GENERATED_SECTIONS, AWAITING_AUTHORSHIP, buildLedger, sectionHash, decideRegeneration). main -> dist/index.js, exports map added (nothing imported the package root before; the bin is unchanged). - 6 new tests pinned against REAL generator output over the shadcn-demo fixture (refusal, preserve+refresh split, hash-signal survival, pure addition, no-modify guarantee, missing-section flagging, clean round trip). 111/112 total pass (1 pre-existing skip). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d-install boundary - CI hardware timed out the per-test generator invocation (Babel + react-docgen per call); the suite now extracts ONCE and hands out clones. - Version 0.4.0 (new public API surface: library entry + regenerateSections). - scripts/pack-test.sh (CI-wired): from the installed tarball, exportProject keeps the ledger, regenerateSections preserves enrichment + governance with the human-owned hash signal intact, ledger-less documents fail closed with the no-force wording, and the bin + version resolve. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Final release verification (train pass)
0.4.0 release notes:
Publish after merge: |
There was a problem hiding this comment.
Pull request overview
Adds a programmatic library entrypoint and section-scoped regeneration/merge behavior to support iterative “rediscovery” workflows (e.g., dspack-studio composer) while preserving human-authored edits based on the x-bootstrap ledger.
Changes:
- Introduces
regenerateSections(existing, fresh)to refresh tool-owned sections and preserve human-owned sections (with pure-addition behavior for new component IDs). - Adds
exportProject(configPath)as an in-process generate → validate pipeline (no filesystem writes). - Updates packaging/CI to publish/consume a library entry (
main/exports) and adds a pack-and-install boundary test.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tests/regenerate.test.ts | Adds test coverage for section-scoped regeneration semantics against real generator output. |
| src/regenerate.ts | Implements ledger-driven section merge logic and regeneration reporting. |
| src/index.ts | Adds library surface exports and exportProject API. |
| scripts/pack-test.sh | Adds a tarball install smoke test to verify published-package behavior. |
| package.json | Bumps version to 0.4.0 and adds main/exports for the new library entry. |
| .github/workflows/test.yml | Runs the new pack-and-install boundary test in CI. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import type { DspackDocument } from './types.js'; | ||
|
|
||
| export { loadConfig, type ExporterConfig, type ResolvedConfig } from './config.js'; | ||
| export { generateDocument, generatedAtFromEnv, GENERATOR_VERSION, type GenerateResult } from './generate.js'; |
| REPO_ROOT="$OLDPWD" node smoke.mjs | ||
|
|
||
| ./node_modules/.bin/dspack-export --help 2>/dev/null | head -1 >/dev/null || node node_modules/.bin/dspack-export 2>/dev/null || true | ||
| node -e "const p=require('@aestheticfunction/dspack-export/package.json'); if(p.version!=='0.4.0') throw new Error('version mismatch: '+p.version); console.log('bin + version OK:', p.version)" |
| } | ||
| const freshLedger = ledgerOf(fresh); | ||
| if (!freshLedger?.generated) { | ||
| return { ok: false, reason: 'the fresh document carries no x-bootstrap ledger; regenerate it with this tool first' }; |
What
The iterative rediscovery path the dspack-studio composer needs (Phase 2), at exactly the ledger's granularity.
decideRegeneration's whole-file refusal table stays the answer forgenerateover an existing path; this adds the merge path for documents a human has enriched:regenerateSections(existing, fresh)— tool-owned sections (recorded hash still matches content) refresh from the fresh document; human-owned sections (hash mismatch) are preserved verbatim with their recorded hash, so the ownership signal survives regeneration; governance and all non-generated keys carry over by construction; a ledger-less document is refused ("human-owned in full; there is no force override"). One safe additive refinement: inside a human-ownedcomponentssection, fresh components with new ids are added — pure addition destroys nothing, and it covers the common rediscovery case (new component in source). Existing entries are never modified.exportProject(configPath)— the programmaticgenerate(load → generate → schema-validate, zero writes) so the studio agent stops shelling the CLI.src/index.ts,main/exports→dist/index.js): also exports the ownership primitives (GENERATED_SECTIONS,AWAITING_AUTHORSHIP,buildLedger,sectionHash,decideRegeneration) so consumers reason with the same vocabulary. Nothing imported the package root before (bin-only usage); the bin is unchanged.Deliberate scope boundary
Prop-level refresh of an enriched component (e.g. a new cva variant on an already-human-edited entry) is out: it would need a three-way merge base the section-level ledger does not record. The honest v1 reports and preserves; a per-component ledger is the future work if drift pressure demands it.
Verification
6 new tests pinned against real generator output over the shadcn-demo fixture: refusal without ledger, preserve+refresh split with hash-signal survival (
sectionHash(components) != recordedafter merge;tokensmatches), pure addition, the never-modify guarantee (sparklyvariant in fresh does not reach the enriched entry), missing-section flagging, clean round-trip. 111/112 total (1 pre-existing skip); build green; library entry smoke-imported.Suggested version: 0.4.0 (new public API surface). Publishes via the existing tag-triggered OIDC workflow after merge.
🤖 Generated with Claude Code