@shrkcrft/generator 0.1.0-alpha.25 → 0.1.0-alpha.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,6 +12,24 @@ export interface ISavedPlanExpectedChange {
12
12
  type: string;
13
13
  relativePath: string;
14
14
  sizeBytes: number;
15
+ /**
16
+ * SHA-256 hex digest of the exact rendered body that `gen --print` shows.
17
+ * Always emitted by `buildSavedPlan`; may be absent on legacy plans written
18
+ * before body/digest persistence. The plan is the review/apply artifact, so
19
+ * this digest lets a later review or apply verify — WITHOUT re-rendering —
20
+ * that the live content still matches what was previewed. Covered by the
21
+ * HMAC signature (canonical JSON includes the whole `expectedChanges`).
22
+ */
23
+ sha256?: string;
24
+ /**
25
+ * The exact rendered file body that would be written — byte-identical to
26
+ * what `gen --print` / `--show-content` displays. Embedded so the saved
27
+ * plan carries enough to be reviewed for correctness, diffed against HEAD,
28
+ * and re-applied deterministically instead of storing only a byte count.
29
+ * Always emitted by `buildSavedPlan`; may be absent on legacy plans. Covered
30
+ * by the HMAC signature.
31
+ */
32
+ body?: string;
15
33
  /**
16
34
  * v2-only — the operation intent that produced this change. Present iff
17
35
  * the schema is `sharkcraft.plan/v2`. Tampering with this field invalidates
@@ -87,12 +105,20 @@ export interface BuildSavedPlanInput {
87
105
  * resulting plan is tagged `sharkcraft.plan/v2`; otherwise v1.
88
106
  */
89
107
  export declare function buildSavedPlan(input: BuildSavedPlanInput): ISavedPlan;
108
+ /** SHA-256 hex digest of a rendered file body (UTF-8). */
109
+ export declare function sha256Hex(body: string): string;
90
110
  export declare function savePlanToFile(plan: ISavedPlan, filePath: string): Result<void, AppError>;
91
111
  export declare function readPlanFromFile(filePath: string): Result<ISavedPlan, AppError>;
92
112
  export interface IPlanDiff {
93
113
  relativePath: string;
94
- /** "added" | "removed" | "type-changed" | "size-changed" | "operation-changed" */
95
- kind: 'added' | 'removed' | 'type-changed' | 'size-changed' | 'operation-changed';
114
+ /**
115
+ * "added" | "removed" | "type-changed" | "size-changed" |
116
+ * "operation-changed" | "content-changed". `content-changed` fires when the
117
+ * live body's digest differs from the saved `sha256` even though the byte
118
+ * count matches — a same-size content edit that a size check alone would
119
+ * miss.
120
+ */
121
+ kind: 'added' | 'removed' | 'type-changed' | 'size-changed' | 'operation-changed' | 'content-changed';
96
122
  detail?: string;
97
123
  }
98
124
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"saved-plan.d.ts","sourceRoot":"","sources":["../src/saved-plan.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"saved-plan.d.ts","sourceRoot":"","sources":["../src/saved-plan.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { dirname } from 'node:path';
3
3
  import { mkdirSync } from 'node:fs';
4
+ import { createHash } from 'node:crypto';
4
5
  import { AppErrorImpl, ERROR_CODES, err, ok } from '@shrkcrft/core';
5
6
  /** v1 schema marker — kept for legacy CREATE-only plans. */
6
7
  export const SAVED_PLAN_SCHEMA_V1 = 'sharkcraft.plan/v1';
@@ -20,6 +21,11 @@ export function buildSavedPlan(input) {
20
21
  type: String(c.type),
21
22
  relativePath: c.relativePath,
22
23
  sizeBytes: c.sizeBytes,
24
+ // Persist the exact rendered body (what `--print` shows) plus its
25
+ // digest, so the saved plan IS the reviewable / re-appliable artifact
26
+ // rather than a bare byte count. Both are covered by the HMAC signature.
27
+ sha256: sha256Hex(c.contents),
28
+ body: c.contents,
23
29
  };
24
30
  if (c.operation !== undefined)
25
31
  entry.operation = c.operation;
@@ -41,6 +47,10 @@ export function buildSavedPlan(input) {
41
47
  out.folderOps = [...input.folderOps];
42
48
  return out;
43
49
  }
50
+ /** SHA-256 hex digest of a rendered file body (UTF-8). */
51
+ export function sha256Hex(body) {
52
+ return createHash('sha256').update(body, 'utf8').digest('hex');
53
+ }
44
54
  export function savePlanToFile(plan, filePath) {
45
55
  try {
46
56
  mkdirSync(dirname(filePath), { recursive: true });
@@ -202,6 +212,16 @@ export function diffPlanChanges(saved, fresh) {
202
212
  detail: `${expected.sizeBytes}B → ${actual.sizeBytes}B`,
203
213
  });
204
214
  }
215
+ else if (expected.sha256 !== undefined &&
216
+ sha256Hex(actual.contents) !== expected.sha256) {
217
+ // Same byte count, different bytes: only the persisted digest catches
218
+ // this. Legacy plans without `sha256` skip the check (size-only).
219
+ out.push({
220
+ relativePath: expected.relativePath,
221
+ kind: 'content-changed',
222
+ detail: 'body digest mismatch (same size, different content)',
223
+ });
224
+ }
205
225
  }
206
226
  for (const [key, actual] of freshByKey) {
207
227
  if (!expectedByKey.has(key)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shrkcrft/generator",
3
- "version": "0.1.0-alpha.25",
3
+ "version": "0.1.0-alpha.27",
4
4
  "description": "SharkCraft plan-first generator: GenerationPlan, FileChange, dry-run, safe writes.",
5
5
  "license": "MIT",
6
6
  "author": "SharkCraft contributors",
@@ -43,10 +43,10 @@
43
43
  "typecheck": "tsc --noEmit -p tsconfig.json"
44
44
  },
45
45
  "dependencies": {
46
- "@shrkcrft/core": "^0.1.0-alpha.25",
47
- "@shrkcrft/templates": "^0.1.0-alpha.25",
48
- "@shrkcrft/rules": "^0.1.0-alpha.25",
49
- "@shrkcrft/paths": "^0.1.0-alpha.25"
46
+ "@shrkcrft/core": "^0.1.0-alpha.27",
47
+ "@shrkcrft/templates": "^0.1.0-alpha.27",
48
+ "@shrkcrft/rules": "^0.1.0-alpha.27",
49
+ "@shrkcrft/paths": "^0.1.0-alpha.27"
50
50
  },
51
51
  "publishConfig": {
52
52
  "access": "public"