A semantic file-system projection engine, not a programmable copy utility.
Declared target shape
+
Observed workspace facts
+
Transformation policy
│
▼
Authorized file-system execution
│
▼
Verified target shape
Other systems declare the shape. File System Shaper resolves, applies, and proves the shape.
See docs/fs-shaper-intent-discussion.md for the full architecture rationale.
contracts/ JSON Schemas for the shape contract, resolved plan, and receipt
features/ Gherkin feature + scenario tests (Node's native test runner)
semantic-authority/ resolver (owns decisions) + executor (owns mechanics) + orchestrator
adapters/file-system/ mechanical file-system ports (stat, mkdir, copy, rename, hash)
cli/ CLI entrypoint
The most important boundary is between the resolver and the executor:
- Semantic resolver (
semantic-authority/resolves-file-system-shape.ts) reads the declared shape and observed facts, and produces a fully-resolved plan —authorizedorrejected— before anything is mutated. - Executor (
semantic-authority/executes-resolved-file-system-shape.ts) performs only what the plan authorizes: create directory, copy, move, verify hash. It does not decide.
npm install
npm test
npx tsx cli/shapes-file-system.ts \
--contract ./file-system-shape.json \
--source-root ./candidate \
--target-root ./workspaceThe CLI prints a file-system-shape-execution-receipt.v1 JSON receipt to stdout and exits non-zero if the shape was rejected.
Contracts declare policy explicitly — see contracts/file-system-shape.schema.v1.json:
missingSource: reject— every declared source must existexistingTarget: reject | replace— whether an existing target file may be overwrittenpathTraversal: reject— mapping paths may not escape their workspace rootsymbolicLinks: reject— symlinked sources/targets are treated as non-file and rejected
Copy mappings are idempotent: if the target already has the same content as the source (by SHA-256), the mapping resolves to declare-noop and no mutation occurs.
first run → SHAPE_APPLIED
second run → SHAPE_ALREADY_SATISFIED
Move mappings consume their source, so a repeated run reports the mapping's source as missing — this is expected policy-driven rejection, not a bug.