@@ -2,19 +2,42 @@ import { existsSync, readFileSync } from 'node:fs'
22import { resolve } from 'node:path'
33import { CONTEXT_REL_PATH , type ContextFile } from './write-context.js'
44
5+ /**
6+ * Validate that a parsed JSON value has the minimum shape callers rely
7+ * on. We only check the fields downstream code dereferences without
8+ * a guard — `integration`, `packageManager`, and `schemas`. Other
9+ * fields (cliVersion, generatedAt, etc.) are informational and absent
10+ * values won't crash anything.
11+ *
12+ * A wider schema check would belong in a runtime validator (zod, etc.);
13+ * this is the minimum to keep `stash status`, `stash plan`, and `stash
14+ * impl` from hard-failing on a hand-edited or partial-write file.
15+ */
16+ function isContextFile ( x : unknown ) : x is ContextFile {
17+ if ( ! x || typeof x !== 'object' ) return false
18+ const obj = x as Record < string , unknown >
19+ return (
20+ typeof obj . integration === 'string' &&
21+ typeof obj . packageManager === 'string' &&
22+ Array . isArray ( obj . schemas )
23+ )
24+ }
25+
526/**
627 * Read the `.cipherstash/context.json` file written by `stash init`.
7- * Returns `undefined` when the file is missing or malformed — both `stash
8- * plan` and `stash impl` use that signal to point the user back at
9- * `stash init` rather than crashing.
28+ * Returns `undefined` when the file is missing, unparseable, or doesn't
29+ * have the expected shape — both `stash plan` and `stash impl` use that
30+ * signal to point the user back at `stash init` rather than crashing.
1031 *
11- * Never throws on bad input. Malformed JSON is treated as "no context."
32+ * Never throws on bad input. Malformed JSON and wrong-shape objects are
33+ * both treated as "no context."
1234 */
1335export function readContextFile ( cwd : string ) : ContextFile | undefined {
1436 const path = resolve ( cwd , CONTEXT_REL_PATH )
1537 if ( ! existsSync ( path ) ) return undefined
1638 try {
17- return JSON . parse ( readFileSync ( path , 'utf-8' ) ) as ContextFile
39+ const parsed = JSON . parse ( readFileSync ( path , 'utf-8' ) ) as unknown
40+ return isContextFile ( parsed ) ? parsed : undefined
1841 } catch {
1942 return undefined
2043 }
0 commit comments