fix(linkedin-studio): S13 — close S12 WARN ($-scalar + false-green test) + $-safety lint guard
Closes the 2 grep/Read-verified findings from the S12 cold full-brief re-review (docs/remediation/review.md, WARN 0/1/1/0, 0 dropped) and closes the $-injection CLASS — not the line — across the whole state-updater.mjs mutation surface. See docs/remediation/review.md (S13 ALLOW, 0/0/0/0) for the full closure record: replaceField -> replacement function; the 3 additive-insert sites -> functions (m === $1, behavior-preserving); a scalar assert.match pins last_post_topic; and a behavioral, coverage-complete, self-testing Section 12 guard (check-replace-safety.mjs) that is mutation-proven. Docs three-doc + residuals updated. test-runner.sh 71/0/0, node --test 98/98.
This commit is contained in:
parent
36f79dd702
commit
431a893f7c
10 changed files with 665 additions and 9 deletions
|
|
@ -12,9 +12,15 @@ const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
|||
const STATE_FILE = process.env.STATE_FILE || join(HOME, '.claude', 'linkedin-studio.local.md');
|
||||
|
||||
function replaceField(content, field, value) {
|
||||
// Replacement FUNCTION, not string: most call sites pass dates/integers/booleans,
|
||||
// but `:58` passes the untrusted `last_post_topic`. In a replacement *string*,
|
||||
// `$&`/`` $` ``/`$'`/`$$` (and `$n` group refs) are special, so a `$`-bearing topic
|
||||
// (e.g. "$& budget") would expand `$&` to the whole matched line and silently
|
||||
// corrupt the scalar. A function inserts `value` verbatim — closing the last member
|
||||
// of the `$`-injection class the S12 section-append fix targeted.
|
||||
return content.replace(
|
||||
new RegExp(`^${field}: .*`, 'm'),
|
||||
`${field}: ${value}`
|
||||
() => `${field}: ${value}`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -237,7 +243,7 @@ export function recordFirstHourPlan(stateContent, { planDate, postTopic = '', ta
|
|||
content = replaceField(content, 'last_firsthour_date', `"${planDate}"`);
|
||||
changes.push(`last_firsthour_date → ${planDate}`);
|
||||
} else if (/^last_post_date: .*/m.test(content)) {
|
||||
content = content.replace(/^(last_post_date: .*)$/m, `$1\nlast_firsthour_date: "${planDate}"`);
|
||||
content = content.replace(/^(last_post_date: .*)$/m, (m) => `${m}\nlast_firsthour_date: "${planDate}"`); // function, not string: `m` === the matched line (was `$1`); keeps planDate `$`-safe by construction
|
||||
changes.push(`last_firsthour_date → ${planDate}`);
|
||||
}
|
||||
|
||||
|
|
@ -296,10 +302,10 @@ export function recordOutreachContact(stateContent, { contactDate, track = '', p
|
|||
content = replaceField(content, 'last_outreach_date', `"${contactDate}"`);
|
||||
changes.push(`last_outreach_date → ${contactDate}`);
|
||||
} else if (/^last_firsthour_date: .*/m.test(content)) {
|
||||
content = content.replace(/^(last_firsthour_date: .*)$/m, `$1\nlast_outreach_date: "${contactDate}"`);
|
||||
content = content.replace(/^(last_firsthour_date: .*)$/m, (m) => `${m}\nlast_outreach_date: "${contactDate}"`); // function, not string: `m` === the matched line (was `$1`); keeps contactDate `$`-safe by construction
|
||||
changes.push(`last_outreach_date → ${contactDate}`);
|
||||
} else if (/^last_post_date: .*/m.test(content)) {
|
||||
content = content.replace(/^(last_post_date: .*)$/m, `$1\nlast_outreach_date: "${contactDate}"`);
|
||||
content = content.replace(/^(last_post_date: .*)$/m, (m) => `${m}\nlast_outreach_date: "${contactDate}"`); // function, not string: `m` === the matched line (was `$1`); keeps contactDate `$`-safe by construction
|
||||
changes.push(`last_outreach_date → ${contactDate}`);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue