(
{ file_path, content },
{ readFileState, updateFileHistoryState, dynamicSkillDirTriggers },
_,
parentMessage,
)
| 221 | return { result: true } |
| 222 | }, |
| 223 | async call( |
| 224 | { file_path, content }, |
| 225 | { readFileState, updateFileHistoryState, dynamicSkillDirTriggers }, |
| 226 | _, |
| 227 | parentMessage, |
| 228 | ) { |
| 229 | const fullFilePath = expandPath(file_path) |
| 230 | const dir = dirname(fullFilePath) |
| 231 | |
| 232 | // Discover skills from this file's path (fire-and-forget, non-blocking) |
| 233 | const cwd = getCwd() |
| 234 | const newSkillDirs = await discoverSkillDirsForPaths([fullFilePath], cwd) |
| 235 | if (newSkillDirs.length > 0) { |
| 236 | // Store discovered dirs for attachment display |
| 237 | for (const dir of newSkillDirs) { |
| 238 | dynamicSkillDirTriggers?.add(dir) |
| 239 | } |
| 240 | // Don't await - let skill loading happen in the background |
| 241 | addSkillDirectories(newSkillDirs).catch(() => {}) |
| 242 | } |
| 243 | |
| 244 | // Activate conditional skills whose path patterns match this file |
| 245 | activateConditionalSkillsForPaths([fullFilePath], cwd) |
| 246 | |
| 247 | await diagnosticTracker.beforeFileEdited(fullFilePath) |
| 248 | |
| 249 | // Ensure parent directory exists before the atomic read-modify-write section. |
| 250 | // Must stay OUTSIDE the critical section below (a yield between the staleness |
| 251 | // check and writeTextContent lets concurrent edits interleave), and BEFORE the |
| 252 | // write (lazy-mkdir-on-ENOENT would fire a spurious tengu_atomic_write_error |
| 253 | // inside writeFileSyncAndFlush_DEPRECATED before ENOENT propagates back). |
| 254 | await getFsImplementation().mkdir(dir) |
| 255 | if (fileHistoryEnabled()) { |
| 256 | // Backup captures pre-edit content — safe to call before the staleness |
| 257 | // check (idempotent v1 backup keyed on content hash; if staleness fails |
| 258 | // later we just have an unused backup, not corrupt state). |
| 259 | await fileHistoryTrackEdit( |
| 260 | updateFileHistoryState, |
| 261 | fullFilePath, |
| 262 | parentMessage.uuid, |
| 263 | ) |
| 264 | } |
| 265 | |
| 266 | // Load current state and confirm no changes since last read. |
| 267 | // Please avoid async operations between here and writing to disk to preserve atomicity. |
| 268 | let meta: ReturnType<typeof readFileSyncWithMetadata> | null |
| 269 | try { |
| 270 | meta = readFileSyncWithMetadata(fullFilePath) |
| 271 | } catch (e) { |
| 272 | if (isENOENT(e)) { |
| 273 | meta = null |
| 274 | } else { |
| 275 | throw e |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | if (meta !== null) { |
| 280 | const lastWriteTime = getFileModificationTime(fullFilePath) |
nothing calls this directly
no test coverage detected