|
|
|
|
@@ -65,11 +65,19 @@ function cmdStateGet(cwd, section, raw) {
|
|
|
|
|
// Try to find markdown section or field
|
|
|
|
|
const fieldEscaped = section.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
|
|
|
|
|
|
// Check for **field:** value
|
|
|
|
|
const fieldPattern = new RegExp(`\\*\\*${fieldEscaped}:\\*\\*\\s*(.*)`, 'i');
|
|
|
|
|
const fieldMatch = content.match(fieldPattern);
|
|
|
|
|
if (fieldMatch) {
|
|
|
|
|
output({ [section]: fieldMatch[1].trim() }, raw, fieldMatch[1].trim());
|
|
|
|
|
// Check for **field:** value (bold format)
|
|
|
|
|
const boldPattern = new RegExp(`\\*\\*${fieldEscaped}:\\*\\*\\s*(.*)`, 'i');
|
|
|
|
|
const boldMatch = content.match(boldPattern);
|
|
|
|
|
if (boldMatch) {
|
|
|
|
|
output({ [section]: boldMatch[1].trim() }, raw, boldMatch[1].trim());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for field: value (plain format)
|
|
|
|
|
const plainPattern = new RegExp(`^${fieldEscaped}:\\s*(.*)`, 'im');
|
|
|
|
|
const plainMatch = content.match(plainPattern);
|
|
|
|
|
if (plainMatch) {
|
|
|
|
|
output({ [section]: plainMatch[1].trim() }, raw, plainMatch[1].trim());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -106,10 +114,15 @@ function cmdStatePatch(cwd, patches, raw) {
|
|
|
|
|
|
|
|
|
|
for (const [field, value] of Object.entries(patches)) {
|
|
|
|
|
const fieldEscaped = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
|
const pattern = new RegExp(`(\\*\\*${fieldEscaped}:\\*\\*\\s*)(.*)`, 'i');
|
|
|
|
|
// Try **Field:** bold format first, then plain Field: format
|
|
|
|
|
const boldPattern = new RegExp(`(\\*\\*${fieldEscaped}:\\*\\*\\s*)(.*)`, 'i');
|
|
|
|
|
const plainPattern = new RegExp(`(^${fieldEscaped}:\\s*)(.*)`, 'im');
|
|
|
|
|
|
|
|
|
|
if (pattern.test(content)) {
|
|
|
|
|
content = content.replace(pattern, (_match, prefix) => `${prefix}${value}`);
|
|
|
|
|
if (boldPattern.test(content)) {
|
|
|
|
|
content = content.replace(boldPattern, (_match, prefix) => `${prefix}${value}`);
|
|
|
|
|
results.updated.push(field);
|
|
|
|
|
} else if (plainPattern.test(content)) {
|
|
|
|
|
content = content.replace(plainPattern, (_match, prefix) => `${prefix}${value}`);
|
|
|
|
|
results.updated.push(field);
|
|
|
|
|
} else {
|
|
|
|
|
results.failed.push(field);
|
|
|
|
|
@@ -135,9 +148,15 @@ function cmdStateUpdate(cwd, field, value) {
|
|
|
|
|
try {
|
|
|
|
|
let content = fs.readFileSync(statePath, 'utf-8');
|
|
|
|
|
const fieldEscaped = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
|
const pattern = new RegExp(`(\\*\\*${fieldEscaped}:\\*\\*\\s*)(.*)`, 'i');
|
|
|
|
|
if (pattern.test(content)) {
|
|
|
|
|
content = content.replace(pattern, (_match, prefix) => `${prefix}${value}`);
|
|
|
|
|
// Try **Field:** bold format first, then plain Field: format
|
|
|
|
|
const boldPattern = new RegExp(`(\\*\\*${fieldEscaped}:\\*\\*\\s*)(.*)`, 'i');
|
|
|
|
|
const plainPattern = new RegExp(`(^${fieldEscaped}:\\s*)(.*)`, 'im');
|
|
|
|
|
if (boldPattern.test(content)) {
|
|
|
|
|
content = content.replace(boldPattern, (_match, prefix) => `${prefix}${value}`);
|
|
|
|
|
writeStateMd(statePath, content, cwd);
|
|
|
|
|
output({ updated: true });
|
|
|
|
|
} else if (plainPattern.test(content)) {
|
|
|
|
|
content = content.replace(plainPattern, (_match, prefix) => `${prefix}${value}`);
|
|
|
|
|
writeStateMd(statePath, content, cwd);
|
|
|
|
|
output({ updated: true });
|
|
|
|
|
} else {
|
|
|
|
|
@@ -152,16 +171,26 @@ function cmdStateUpdate(cwd, field, value) {
|
|
|
|
|
|
|
|
|
|
function stateExtractField(content, fieldName) {
|
|
|
|
|
const escaped = fieldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
|
const pattern = new RegExp(`\\*\\*${escaped}:\\*\\*\\s*(.+)`, 'i');
|
|
|
|
|
const match = content.match(pattern);
|
|
|
|
|
return match ? match[1].trim() : null;
|
|
|
|
|
// Try **Field:** bold format first
|
|
|
|
|
const boldPattern = new RegExp(`\\*\\*${escaped}:\\*\\*\\s*(.+)`, 'i');
|
|
|
|
|
const boldMatch = content.match(boldPattern);
|
|
|
|
|
if (boldMatch) return boldMatch[1].trim();
|
|
|
|
|
// Fall back to plain Field: format
|
|
|
|
|
const plainPattern = new RegExp(`^${escaped}:\\s*(.+)`, 'im');
|
|
|
|
|
const plainMatch = content.match(plainPattern);
|
|
|
|
|
return plainMatch ? plainMatch[1].trim() : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function stateReplaceField(content, fieldName, newValue) {
|
|
|
|
|
const escaped = fieldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
|
|
const pattern = new RegExp(`(\\*\\*${escaped}:\\*\\*\\s*)(.*)`, 'i');
|
|
|
|
|
if (pattern.test(content)) {
|
|
|
|
|
return content.replace(pattern, (_match, prefix) => `${prefix}${newValue}`);
|
|
|
|
|
// Try **Field:** bold format first, then plain Field: format
|
|
|
|
|
const boldPattern = new RegExp(`(\\*\\*${escaped}:\\*\\*\\s*)(.*)`, 'i');
|
|
|
|
|
if (boldPattern.test(content)) {
|
|
|
|
|
return content.replace(boldPattern, (_match, prefix) => `${prefix}${newValue}`);
|
|
|
|
|
}
|
|
|
|
|
const plainPattern = new RegExp(`(^${escaped}:\\s*)(.*)`, 'im');
|
|
|
|
|
if (plainPattern.test(content)) {
|
|
|
|
|
return content.replace(plainPattern, (_match, prefix) => `${prefix}${newValue}`);
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
@@ -256,9 +285,15 @@ function cmdStateUpdateProgress(cwd, raw) {
|
|
|
|
|
const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(barWidth - filled);
|
|
|
|
|
const progressStr = `[${bar}] ${percent}%`;
|
|
|
|
|
|
|
|
|
|
const progressPattern = /(\*\*Progress:\*\*\s*).*/i;
|
|
|
|
|
if (progressPattern.test(content)) {
|
|
|
|
|
content = content.replace(progressPattern, (_match, prefix) => `${prefix}${progressStr}`);
|
|
|
|
|
// Try **Progress:** bold format first, then plain Progress: format
|
|
|
|
|
const boldProgressPattern = /(\*\*Progress:\*\*\s*).*/i;
|
|
|
|
|
const plainProgressPattern = /^(Progress:\s*).*/im;
|
|
|
|
|
if (boldProgressPattern.test(content)) {
|
|
|
|
|
content = content.replace(boldProgressPattern, (_match, prefix) => `${prefix}${progressStr}`);
|
|
|
|
|
writeStateMd(statePath, content, cwd);
|
|
|
|
|
output({ updated: true, percent, completed: totalSummaries, total: totalPlans, bar: progressStr }, raw, progressStr);
|
|
|
|
|
} else if (plainProgressPattern.test(content)) {
|
|
|
|
|
content = content.replace(plainProgressPattern, (_match, prefix) => `${prefix}${progressStr}`);
|
|
|
|
|
writeStateMd(statePath, content, cwd);
|
|
|
|
|
output({ updated: true, percent, completed: totalSummaries, total: totalPlans, bar: progressStr }, raw, progressStr);
|
|
|
|
|
} else {
|
|
|
|
|
@@ -414,11 +449,17 @@ function cmdStateSnapshot(cwd, raw) {
|
|
|
|
|
|
|
|
|
|
const content = fs.readFileSync(statePath, 'utf-8');
|
|
|
|
|
|
|
|
|
|
// Helper to extract **Field:** value patterns
|
|
|
|
|
// Helper to extract field values — supports both **Field:** bold format
|
|
|
|
|
// and plain Field: format (STATE.md may use either depending on version)
|
|
|
|
|
const extractField = (fieldName) => {
|
|
|
|
|
const pattern = new RegExp(`\\*\\*${fieldName}:\\*\\*\\s*(.+)`, 'i');
|
|
|
|
|
const match = content.match(pattern);
|
|
|
|
|
return match ? match[1].trim() : null;
|
|
|
|
|
// Try **Field:** format first (bold markdown)
|
|
|
|
|
const boldPattern = new RegExp(`\\*\\*${fieldName}:\\*\\*\\s*(.+)`, 'i');
|
|
|
|
|
const boldMatch = content.match(boldPattern);
|
|
|
|
|
if (boldMatch) return boldMatch[1].trim();
|
|
|
|
|
// Fall back to plain Field: format
|
|
|
|
|
const plainPattern = new RegExp(`^${fieldName}:\\s*(.+)`, 'im');
|
|
|
|
|
const plainMatch = content.match(plainPattern);
|
|
|
|
|
return plainMatch ? plainMatch[1].trim() : null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Extract basic fields
|
|
|
|
|
@@ -477,9 +518,12 @@ function cmdStateSnapshot(cwd, raw) {
|
|
|
|
|
const sessionMatch = content.match(/##\s*Session\s*\n([\s\S]*?)(?=\n##|$)/i);
|
|
|
|
|
if (sessionMatch) {
|
|
|
|
|
const sessionSection = sessionMatch[1];
|
|
|
|
|
const lastDateMatch = sessionSection.match(/\*\*Last Date:\*\*\s*(.+)/i);
|
|
|
|
|
const stoppedAtMatch = sessionSection.match(/\*\*Stopped At:\*\*\s*(.+)/i);
|
|
|
|
|
const resumeFileMatch = sessionSection.match(/\*\*Resume File:\*\*\s*(.+)/i);
|
|
|
|
|
const lastDateMatch = sessionSection.match(/\*\*Last Date:\*\*\s*(.+)/i)
|
|
|
|
|
|| sessionSection.match(/^Last Date:\s*(.+)/im);
|
|
|
|
|
const stoppedAtMatch = sessionSection.match(/\*\*Stopped At:\*\*\s*(.+)/i)
|
|
|
|
|
|| sessionSection.match(/^Stopped At:\s*(.+)/im);
|
|
|
|
|
const resumeFileMatch = sessionSection.match(/\*\*Resume File:\*\*\s*(.+)/i)
|
|
|
|
|
|| sessionSection.match(/^Resume File:\s*(.+)/im);
|
|
|
|
|
|
|
|
|
|
if (lastDateMatch) session.last_date = lastDateMatch[1].trim();
|
|
|
|
|
if (stoppedAtMatch) session.stopped_at = stoppedAtMatch[1].trim();
|
|
|
|
|
@@ -513,10 +557,14 @@ function cmdStateSnapshot(cwd, raw) {
|
|
|
|
|
* reliably via `state json` instead of fragile regex parsing.
|
|
|
|
|
*/
|
|
|
|
|
function buildStateFrontmatter(bodyContent, cwd) {
|
|
|
|
|
// Supports both **Field:** bold and plain Field: format (see state-snapshot)
|
|
|
|
|
const extractField = (fieldName) => {
|
|
|
|
|
const pattern = new RegExp(`\\*\\*${fieldName}:\\*\\*\\s*(.+)`, 'i');
|
|
|
|
|
const match = bodyContent.match(pattern);
|
|
|
|
|
return match ? match[1].trim() : null;
|
|
|
|
|
const boldPattern = new RegExp(`\\*\\*${fieldName}:\\*\\*\\s*(.+)`, 'i');
|
|
|
|
|
const boldMatch = bodyContent.match(boldPattern);
|
|
|
|
|
if (boldMatch) return boldMatch[1].trim();
|
|
|
|
|
const plainPattern = new RegExp(`^${fieldName}:\\s*(.+)`, 'im');
|
|
|
|
|
const plainMatch = bodyContent.match(plainPattern);
|
|
|
|
|
return plainMatch ? plainMatch[1].trim() : null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const currentPhase = extractField('Current Phase');
|
|
|
|
|
|