Skip to content

Commit a17b39f

Browse files
committed
fix(scaffold): preserve project-owned documents
1 parent ead2502 commit a17b39f

14 files changed

Lines changed: 637 additions & 89 deletions

README.md

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ bun run check
3636

3737
## Release Automation
3838

39-
Releases are tag-driven. Push a version tag that matches `package.json`, such as `v0.5.0`, and GitHub Actions will run the release workflow.
39+
Releases are tag-driven. Push a version tag that matches `package.json`, such as `v0.6.0`, and GitHub Actions will run the release workflow.
4040

4141
The release workflow:
4242

@@ -65,22 +65,24 @@ ssealed init [target] --scope backend|frontend|fullstack|general|mobile|infra|da
6565
ssealed update [target]
6666
ssealed upgrade [target]
6767
ssealed doctor [target]
68+
ssealed doctor [target] --strict
6869
ssealed init [target] --repo-type generic|cli-tool|api-service|desktop-app|library|web-app|mobile-app|sdk|worker-service|infra-module|data-pipeline|github-action|browser-extension|plugin|docs-site|monorepo
6970
ssealed init [target] --addon github-action --addon docs-site
7071
ssealed init [target] --density minimal|standard|strict
7172
ssealed init [target] --runner none|make|just|task|npm|pnpm
7273
ssealed update [target] --dry-run --json
7374
ssealed upgrade [target] --repo-type api-service --density strict --runner make --force
7475
ssealed doctor [target] --json
76+
ssealed doctor [target] --strict --json
7577
ssealed --help
7678
ssealed --version
7779
ssealed init --help
7880
```
7981

8082
`init` creates a new scaffold and refuses targets with an existing valid `.ssealed/manifest.json`.
81-
`update` reapplies the existing manifest settings and refreshes checksums without changing `scope`, repository type, addons, `density`, or `runner`.
83+
`update` reapplies the existing manifest settings, accepts project-owned edits to seeded documents, keeps deleted seeded documents retired, and refreshes manifest metadata without changing `scope`, repository type, addons, `density`, or `runner`.
8284
`upgrade` is the explicit path for changing scaffold settings.
83-
`doctor` checks manifest-tracked files for missing or modified content.
85+
`doctor` checks scaffold lifecycle metadata. It accepts normal project evolution for seeded documents by default. Use `doctor --strict` when you intentionally want checksum drift detection against accepted manifest content.
8486

8587
`--profile` remains accepted as an alias for `--repo-type`.
8688

@@ -152,17 +154,25 @@ Runner files are optional because many repositories already have their own task
152154

153155
## Overwrite Policy
154156

155-
Existing files are not overwritten by default. Identical files are marked `unchanged`. Different files are marked `conflict`. If any conflict exists and `--force` is not provided, no files are written.
157+
Existing files are not overwritten by default. Identical files are marked `unchanged`. During `init`, different existing files are marked `conflict`. During `update` and `upgrade`, seeded files that already have project-owned edits are marked `customized` and are not overwritten. Seeded files that were deleted after a previous run are marked `retired` and are not recreated.
156158

157-
`--force` overwrites conflicting files only when the current file content matches the checksum recorded for that path in the previous `.ssealed/manifest.json`. The manifest is a local previous-run record, not a security boundary, and it never authorizes overwriting unrelated user files at the same path. Existing user-authored `.gitignore` patterns are preserved even with `--force`; only the ssealed managed block is replaced.
159+
`--force` overwrites conflicting files only when the current file content matches the checksum recorded for that path in the previous `.ssealed/manifest.json`. The manifest is a local previous-run record, not a security boundary, and it never authorizes overwriting unrelated user files at the same path. Existing user-authored `.gitignore` patterns are preserved even with `--force`; only the ssealed managed block is replaced. Seeded files with project-owned edits stay project-owned unless their content still matches the previously accepted generated checksum.
158160

159161
## Manifest Behavior
160162

161-
Every write run refreshes `.ssealed/manifest.json` with tool version, generation timestamp, scope, profile, addons, density, runner, generated file paths, kinds, and SHA-256 checksums of normalized LF content. The `profile` field is retained for manifest compatibility and represents the selected primary repository type.
163+
Every write run refreshes `.ssealed/manifest.json` with tool version, generation timestamp, scope, profile, addons, density, runner, file paths, kinds, ownership, presence, lifecycle status, and SHA-256 checksums of normalized LF content. The `profile` field is retained for manifest compatibility and represents the selected primary repository type.
162164

163-
The manifest helps identify previously generated files, but it never authorizes silent overwrite of user-modified files.
165+
Manifest file ownership has three meanings:
164166

165-
`init` is intentionally conservative and refuses a target that already has a valid `.ssealed/manifest.json`. Use `update` to reapply the recorded scaffold settings, or use `upgrade` to explicitly change `scope`, repository type, addons, `density`, or `runner`. `update` rejects setting changes so old generated files do not silently become untracked scaffold leftovers.
167+
- `seeded`: ssealed created an initial document or repository file, but the project is expected to edit, move, or delete it over time.
168+
- `block-managed`: ssealed manages a bounded block or script set inside a user-owned file, such as `.gitignore` or generated validation scripts in `package.json`.
169+
- `managed`: ssealed owns the full file content as tool metadata.
170+
171+
Seeded files use `presence: optional`; missing seeded files can be retained as `status: retired`. Active files keep both `initialChecksum` and `acceptedChecksum` so tooling can distinguish original template content from project-accepted content. The legacy `checksum` field remains as the accepted checksum for compatibility.
172+
173+
The manifest helps identify previously generated files, but it never authorizes silent overwrite of user-modified files. Default `doctor` treats modified seeded files as `customized` and missing optional seeded files as `retired`, both of which are healthy lifecycle states. `doctor --strict` reports accepted-checksum drift for callers that need the old exact-content comparison.
174+
175+
`init` is intentionally conservative and refuses a target that already has a valid `.ssealed/manifest.json`. Use `update` to reapply the recorded scaffold settings, or use `upgrade` to explicitly change `scope`, repository type, addons, `density`, or `runner`. `update` rejects setting changes so old generated files do not silently become untracked scaffold leftovers, but it does not force project documents back to their seed text.
166176

167177
## Path Safety
168178

@@ -208,9 +218,10 @@ ssealed init --scope data --repo-type data-pipeline --addon docs-site
208218
ssealed update ./my-service --dry-run --json
209219
ssealed upgrade ./my-service --repo-type api-service --density strict --runner make --force
210220
ssealed doctor ./my-service --json
221+
ssealed doctor ./my-service --strict --json
211222
```
212223

213-
`--json` prints a public result shape with command, target, scope, profile, repoType, addons, density, runner, file paths, kinds, actions, reasons, conflicts, warnings, and written paths. Runtime failures also return `{ "ok": false, "error": { "code": "...", "message": "..." } }`. JSON output does not include generated file contents or existing file contents.
224+
`--json` prints a public result shape with command, target, scope, profile, repoType, addons, density, runner, file paths, kinds, ownership, presence, actions, lifecycle statuses, reasons, conflicts, warnings, and written paths. Runtime failures also return `{ "ok": false, "error": { "code": "...", "message": "..." } }`. JSON output does not include generated file contents or existing file contents.
214225

215226
## Why `.agents/skills`
216227

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ssealed",
3-
"version": "0.5.0",
3+
"version": "0.6.0",
44
"type": "module",
55
"description": "LLM-friendly design scaffold CLI for repository documentation, agent instructions, contracts, validations, and hygiene files.",
66
"repository": {

src/cli-main.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Usage:
1010
ssealed init [target] --scope backend|frontend|fullstack|general|mobile|infra|data [--repo-type generic|cli-tool|api-service|desktop-app|library|web-app|mobile-app|sdk|worker-service|infra-module|data-pipeline|github-action|browser-extension|plugin|docs-site|monorepo] [--addon cli-tool|api-service|desktop-app|library|web-app|mobile-app|sdk|worker-service|infra-module|data-pipeline|github-action|browser-extension|plugin|docs-site|monorepo] [--density minimal|standard|strict] [--runner none|make|just|task|npm|pnpm]
1111
ssealed update [target]
1212
ssealed upgrade [target] [--scope backend|frontend|fullstack|general|mobile|infra|data] [--repo-type generic|cli-tool|api-service|desktop-app|library|web-app|mobile-app|sdk|worker-service|infra-module|data-pipeline|github-action|browser-extension|plugin|docs-site|monorepo] [--addon cli-tool|api-service|desktop-app|library|web-app|mobile-app|sdk|worker-service|infra-module|data-pipeline|github-action|browser-extension|plugin|docs-site|monorepo] [--density minimal|standard|strict] [--runner none|make|just|task|npm|pnpm]
13-
ssealed doctor [target]
13+
ssealed doctor [target] [--strict]
1414
ssealed --help
1515
ssealed --version
1616
@@ -24,6 +24,7 @@ Options:
2424
--yes Never prompt.
2525
--dry-run Print planned operations without writing files.
2626
--force Overwrite conflicts only when current content matches previous manifest checksums.
27+
--strict Make doctor fail on any accepted-checksum drift.
2728
--json Print machine-readable JSON.
2829
`;
2930

@@ -33,7 +34,7 @@ Commands:
3334
init Create a new scaffold. Refuses targets with an existing valid manifest.
3435
update Reapply the existing manifest settings without changing scope, repo type, addons, density, or runner.
3536
upgrade Explicitly change scaffold settings and replan generated files.
36-
doctor Check manifest-tracked files for missing or modified content.
37+
doctor Check scaffold lifecycle metadata. Use --strict to require accepted checksums.
3738
3839
Scopes:
3940
backend
@@ -85,6 +86,7 @@ Examples:
8586
ssealed update ./my-service --yes
8687
ssealed upgrade ./my-service --repo-type api-service --density strict --runner make --yes --force
8788
ssealed doctor ./my-service --json
89+
ssealed doctor ./my-service --strict
8890
`;
8991

9092
interface ParsedScaffoldArgs {
@@ -98,6 +100,7 @@ interface ParsedScaffoldArgs {
98100
readonly yes?: boolean;
99101
readonly "dry-run"?: boolean;
100102
readonly force?: boolean;
103+
readonly strict?: boolean;
101104
readonly json?: boolean;
102105
readonly help?: boolean;
103106
};
@@ -166,6 +169,7 @@ export async function main(argv: readonly string[]): Promise<number> {
166169
yes: parsed.values.yes ?? false,
167170
dryRun: parsed.values["dry-run"] ?? false,
168171
force: parsed.values.force ?? false,
172+
strict: parsed.values.strict ?? false,
169173
json: parsed.values.json ?? false,
170174
...(parsed.values["repo-type"] === undefined ? {} : { repoType: parsed.values["repo-type"] }),
171175
...(parsed.values.profile === undefined ? {} : { profile: parsed.values.profile }),
@@ -196,6 +200,7 @@ function parseScaffoldArgs(args: readonly string[]): ParsedScaffoldArgs | Error
196200
yes: { type: "boolean", default: false },
197201
"dry-run": { type: "boolean", default: false },
198202
force: { type: "boolean", default: false },
203+
strict: { type: "boolean", default: false },
199204
json: { type: "boolean", default: false },
200205
help: { type: "boolean", short: "h", default: false },
201206
},

0 commit comments

Comments
 (0)