lib/notes-checklist.js
Rama main · réplica de lectura del repositorio original.
// Bounded source identities shared by checklist validation and reconciliation.
function items(text) {
var lines = (text || '').split('\n').slice(1);
var occurrences = Object.create(null);
var out = [];
var fence = null;
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
var match = line.match(/^\s*(\x60{3,}|~{3,})/);
if (match) { fence = fence === match[1].charAt(0) ? null : (fence || match[1].charAt(0)); continue; }
if (fence || !line.trim() || /^\s*#{1,6}\s/.test(line)) continue;
var occurrence = occurrences[line] || 0;
occurrences[line] = occurrence + 1;
var key = line + '\u0000' + occurrence;
if (i <= 1000 && key.length <= 2000 && encodeURIComponent(key).length <= 2000) out.push({ index: i, key: key });
}
return out;
}
function validState(value) {
if (!Array.isArray(value) && (!value || typeof value !== 'object')) return false;
var keys = Object.keys(value);
if (keys.length > 1000) return false;
for (var i = 0; i < keys.length; i++) {
if (keys[i].length > 2000 || typeof value[keys[i]] !== 'boolean') return false;
}
return true;
}
function normalizeState(value, text) {
var state = {};
items(text).forEach(function (item) {
var checked = value && value[Array.isArray(value) ? item.index : item.key];
if (typeof checked === 'boolean' && Object.keys(state).length < 1000) state[item.key] = checked;
});
return state;
}
function validateChange(note, changes) {
if (changes.checklistIndex === undefined) return null;
if (!Number.isInteger(changes.checklistIndex) || changes.checklistIndex < 0 || changes.checklistIndex > 1000) return null;
if (typeof changes.checklistChecked !== 'boolean' || typeof changes.checklistSource !== 'string' || typeof changes.checklistKey !== 'string') return null;
if (changes.checklistSource !== note.text || changes.checklistKey.length > 2000 || (changes.text !== undefined && changes.text !== note.text)) return null;
var item = items(note.text).find(function (item) { return item.index === changes.checklistIndex; });
var state = normalizeState(note.checklistState, note.text);
if (item && state[item.key] === undefined && Object.keys(state).length >= 1000) return null;
try { return item && item.key === decodeURIComponent(changes.checklistKey) ? item.key : null; } catch (error) { return null; }
}
// Saved booleans win over Markdown markers, matching the native task controls.
var MARKER = /^\s*(?:[-*+]\s+)?\[([ xX])\]\s*(.*)$/;
function describe(note) {
var text = note && typeof note.text === 'string' ? note.text : '';
var state = normalizeState(note && note.checklistState, text);
var lines = text.split('\n');
return items(text).map(function (item) {
var line = lines[item.index + 1];
var marker = line.match(MARKER);
var saved = typeof state[item.key] === 'boolean' ? state[item.key] : null;
return {
item: encodeURIComponent(item.key),
index: item.index,
text: marker ? marker[2] : line.trim(),
checked: saved !== null ? saved : !!(marker && marker[1].toLowerCase() === 'x'),
saved: saved,
};
});
}
// Prompt/knowledge projection of a checklist note with its effective checks.
function effectiveText(note) {
var text = note && typeof note.text === 'string' ? note.text : '';
if (!note || note.checklistMode !== true) return text;
var lines = text.split('\n');
describe(note).forEach(function (entry) {
lines[entry.index + 1] = '- [' + (entry.checked ? 'x' : ' ') + '] ' + entry.text;
});
return lines.join('\n');
}
function findItem(note, encodedKey) {
if (typeof encodedKey !== 'string') return null;
var key;
try { key = decodeURIComponent(encodedKey); } catch (error) { return null; }
return items(note && note.text).find(function (item) { return item.key === key; }) || null;
}
module.exports = {
validState: validState, normalizeState: normalizeState, validateChange: validateChange,
describe: describe, effectiveText: effectiveText, findItem: findItem,
};