{"record":{"id":"34647dcb4dbbbd16","repo":"affaan-m/ECC","slug":"project-memory-gitignore-does-not-contain-the-req","errorCode":null,"errorMessage":"Project memory .gitignore does not contain the required fail-closed rules.","messagePattern":"Project memory \\.gitignore does not contain the required fail-closed rules\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/memory-vault.js","lineNumber":257,"sourceCode":"  if (operationError) throw operationError;\n  if (cleanupError) throw cleanupError;\n}\n\nfunction ensureProjectScopeIgnored(roots, scope) {\n  if (scope !== 'project') return;\n  const root = roots.project;\n  const ignorePath = path.join(root, '.gitignore');\n  try {\n    writeCreateOnlyTextFile(ignorePath, PROJECT_MEMORY_GITIGNORE, root);\n  } catch (error) {\n    if (!error || error.code !== 'EEXIST') throw error;\n    const existing = readRegularTextFile(ignorePath, {\n      label: 'project memory .gitignore',\n      maxBytes: MAX_DOCUMENT_BYTES,\n      trustedRoot: root,\n    });\n    if (existing !== PROJECT_MEMORY_GITIGNORE) {\n      throw new Error(\n        'Project memory .gitignore does not contain the required fail-closed rules.'\n      );\n    }\n  }\n}\n\nfunction normalizeScopes(scopes = MEMORY_SCOPES) {\n  const values = Array.isArray(scopes) ? scopes : [scopes];\n  return uniqueStrings(values, {\n    label: 'scopes',\n    limit: MEMORY_SCOPES.length,\n    validator: value => validateEnum(value, MEMORY_SCOPES, 'memory scope'),\n  });\n}\n\nfunction initializeVault(options = {}) {\n  const roots = options.roots || resolveVaultRoots(options);\n  const scopes = normalizeScopes(options.scopes || DEFAULT_RECALL_SCOPES);","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/memory-vault.js#L239-L275","documentation":"When saving to the project scope, ensureProjectScopeIgnored creates a .gitignore whose exact contents are '*\\n!.gitignore\\n' (ignore everything, fail-closed). If a .gitignore already exists at the project memory root and its contents do not byte-match that required string, the vault refuses to proceed rather than silently widening what gets committed. This prevents an existing permissive .gitignore from leaking memory files into git.","triggerScenarios":"Calling saveMemory (or initializeVault) with scope 'project' when <projectRoot>/.ecc/memory/project/.gitignore already exists with any content other than exactly '*\\n!.gitignore\\n'. Triggered by a user hand-creating a .gitignore, a previous vault version writing a different format, or an editor adding a trailing newline.","commonSituations":"A user manually authored a .gitignore to keep certain memories tracked; a CI step wrote its own ignore rules; line-ending normalization (CRLF) on Windows changed the byte content; a git hook or template injected content; a prior ECC version used a different ignore string.","solutions":["Open <projectRoot>/.ecc/memory/project/.gitignore and replace its contents with exactly two lines: '*' and '!.gitignore' (with a trailing newline, LF not CRLF).","If you intentionally want to track certain memory files, that intent is incompatible with the project scope's fail-closed policy — store those memories in the user or team scope instead.","Delete the .gitignore and let the vault recreate it on the next save: rm <root>/.gitignore, then re-run saveMemory.","Run git config core.autocrlf false (or add a .gitattributes) so git does not rewrite the file's line endings after creation."],"exampleFix":"# before: .ecc/memory/project/.gitignore contains '*.md\\n!important.md\\n'\n# replace with the fail-closed policy exactly\nprintf '*\\n!.gitignore\\n' > .ecc/memory/project/.gitignore\n# after: contents are exactly '*\\n!.gitignore\\n'","handlingStrategy":"validation","validationCode":"const fs = require('fs');\nconst path = require('path');\nconst REQUIRED = '*\\n!.gitignore\\n';\nfunction assertGitignoreCorrect(projectRoot) {\n  const p = path.join(projectRoot, '.ecc', 'memory', 'project', '.gitignore');\n  if (fs.existsSync(p) && fs.readFileSync(p, 'utf8') !== REQUIRED) {\n    throw new Error('Project memory .gitignore does not contain the required fail-closed rules.');\n  }\n}","typeGuard":null,"tryCatchPattern":"try { saveMemory({ ...input, scope: 'project' }); }\ncatch (error) {\n  if (/fail-closed rules/.test(error.message)) {\n    console.error('Fix .ecc/memory/project/.gitignore to be exactly:*\\\\n!.gitignore\\\\n');\n    return;\n  }\n  throw error;\n}","preventionTips":["Never hand-edit .ecc/memory/project/.gitignore — let the vault create it.","Add a .gitattributes forcing LF for that path so git does not rewrite line endings.","If you need certain memories tracked, use the user or team scope, not project.","After cloning, run initializeVault before saving to let it create the .gitignore."],"tags":["security","memory-vault","git","configuration","validation"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}