(state)
| 113 | } |
| 114 | |
| 115 | function normalizeState(state) { |
| 116 | if (!state || typeof state !== 'object' || Array.isArray(state)) { |
| 117 | return defaultState(); |
| 118 | } |
| 119 | if (!state.stats || typeof state.stats !== 'object') { |
| 120 | state.stats = { scansRun: 0, markersFound: 0, intakeItemsCreated: 0 }; |
| 121 | } |
| 122 | if (Array.isArray(state.pendingActions)) { |
| 123 | // Legacy array format: re-key entries by marker hash. |
| 124 | const migrated = {}; |
| 125 | for (const entry of state.pendingActions) { |
| 126 | if (!entry || !entry.file || !entry.action || !entry.raw) continue; |
| 127 | const seen = entry.timestamp || new Date().toISOString(); |
| 128 | migrated[markerHash(entry)] = { |
| 129 | file: entry.file, |
| 130 | line: entry.line, |
| 131 | action: entry.action, |
| 132 | description: entry.description || '', |
| 133 | raw: entry.raw, |
| 134 | firstSeen: seen, |
| 135 | lastSeen: seen, |
| 136 | }; |
| 137 | } |
| 138 | state.pendingActions = migrated; |
| 139 | } else if (!state.pendingActions || typeof state.pendingActions !== 'object') { |
| 140 | state.pendingActions = {}; |
| 141 | } |
| 142 | if ( |
| 143 | !state.processedMarkers || |
| 144 | typeof state.processedMarkers !== 'object' || |
| 145 | Array.isArray(state.processedMarkers) |
| 146 | ) { |
| 147 | // Legacy "{file}:{line}:{action}" strings cannot be re-keyed by hash |
| 148 | // (raw marker text is unrecoverable), so start fresh. |
| 149 | state.processedMarkers = {}; |
| 150 | } |
| 151 | return state; |
| 152 | } |
| 153 | |
| 154 | function readState() { |
| 155 | if (!fs.existsSync(STATE_PATH)) { |
no test coverage detected