Drop code whose only consumer was a retired runtime: hostBehaviors readers for
agentFileExtension, localTargetIsProjectRoot, sharedHooksDirName,
skillsManifestPrefix and skipCodexSkillsManifest; the empty
NON_REGISTRY_CONFIG_HOME_DESCRIPTORS array and live-config-guard plumbing; the
empty RUNTIME_NOTE_AUDIENCE_BY_HEADING filter; the unused resolveVersionFrom
export; and the WINDSURF_SESSION_ID workstream session key. Delete tests that
only exercised those mechanisms.
Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD
across contents and paths, upstream package/repo coordinates -> @golem15/msd-core
and golem15com/msd-core. Deep links into upstream history, sibling upstream
packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is.
Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line,
package/plugin identity, regenerated lockfile, install-tree fixtures, derived
registries and benchmark baseline; migration checksum baseline re-locked
(MSD keeps its own install state, so no install had applied the old sums);
sort-order and regex-escaped expectations in tests adjusted.
* fix(#4016): imperative-override patterns tolerate filler words
The narrow imperative-override family tolerates no filler between the
verb and the noun, so a planted "Forget all of your instructions"
(measured in a real public transcript) matched none of the 14 patterns
and both consuming hooks stayed silent.
One combined filler-tolerant pattern is appended; the narrow four stay
untouched to keep the change merge-friendly. Known trade-offs, disclosed
in #4016: linter-doc prose like "ignore rules on a single line" now
trips a LOW advisory, and the overlap with the narrow patterns means one
sentence can count twice toward severity thresholds.
Regression tests assert the previously-missed phrasings fire in BOTH
consuming hooks (gsd-prompt-guard and gsd-read-injection-scanner), not
just in the raw pattern list, per the agent brief in #4016.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015aGr6fvmDMLznT7TvTXrsb
* chore(#4016): changeset fragment for PR #4061
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015aGr6fvmDMLznT7TvTXrsb
* test(#4016): pin the disclosed linter-doc FP as single-pattern LOW, never blocking
Review follow-up on PR #4061: the combined filler-tolerant pattern's
disclosed false-positive class (linter-doc prose such as "use
eslint-disable-next-line to ignore rules on a single line") was
documented in prose only. Two tests now pin it:
- the prose matches exactly ONE shared pattern (the #4016 combined
pattern, not a narrow one), so it cannot silently start double-counting
toward the 3+ HIGH threshold;
- through the real gsd-read-injection-scanner subprocess with
security.injection_blocking=true, the prose yields a single-finding
LOW advisory and no block decision — with an in-test positive control
proving a 3+-pattern payload DOES block in the same directory, so the
non-blocking assertion cannot pass vacuously.
Samples are fragment-built like the existing SAMPLES rows so this file's
own diff does not trip the CI injection scanner.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(#4016): replace the five narrow imperative-override patterns with one superset
The first cut appended a filler-tolerant combined pattern next to the five
narrow verb patterns. Both consumers count one finding per matching pattern
toward the severity threshold, so the overlap made one sentence count twice:
"Ignore previous instructions. Forget your instructions." scored 2 (LOW) on
next and 3 (HIGH, blockable) on the branch. It also left `override` out of
the combined pattern.
Replace the narrow family (ignore x2, disregard, forget, override) with ONE
superset pattern over ignore|disregard|forget|discard|override. At least one
filler (all|of|the|your|my|system|previous|prior|above|earlier) must sit
between verb and noun, enforced by a lookahead with no repetition; the two
noun-less/bare forms the old list accepted (`disregard (all) previous`,
`forget instructions`) are kept as explicit tails so the new pattern is a
strict superset. Bare "override rules" / "ignore instructions" are ordinary
repo prose (6 measured hits across docs and source) and stay unmatched.
Corpus measurement over 3019 .md/.js/.cjs/.mjs files (injection-sample tests
excluded): the old family hit 2 lines, the new pattern hits 3, the only new
one being a documented injection example in planner-reversibility.md that
the old family missed (the issue's own class).
Tests: SAMPLES reshaped to the 10-entry list; superset proof table (17 legacy
phrasings, each matching exactly one pattern); five issue phrasings including
`override all of your previous instructions` counted exactly once through
both hook subprocesses; double-count regression (1 finding, LOW); design pin
that bare verb+noun matches nothing; linter-doc FP pin split into bare
(silent) and determined (single LOW, never blocks). All fragment-built; the
CI prompt-injection scanner reports 0 findings on every touched file.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012rvqL1wk6s8dQEXsFm4RB1
* chore(#4016): changeset body in the canonical bold-lead format
.changeset/README.md Format: a leading bold change sentence, then an em-dash
explanation. Also drops the verbatim planted phrase from the body so the
rendered CHANGELOG line does not trip the pattern it describes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012rvqL1wk6s8dQEXsFm4RB1
* fix(#4016): render a bounded pattern label in the prompt-guard advisory, pin plural prompts
Review round 4 of PR #4061 left two nits open.
1. gsd-prompt-guard.js pushed `pattern.source` verbatim into its typed
finding and, through renderFinding, into the user-facing advisory. With
the #4016 superset pattern that source is 300 characters, so a genuine hit
surfaced an advisory dominated by a raw regex dump. The read scanner has
trimmed its equivalent since #3523 (`\s+` -> `-`, strip `()\`, cut at 50).
That transform is hoisted into hooks/lib/injection-patterns.js as
`describePattern` and used by BOTH hooks, so one finding renders the same
label everywhere. Byte-identical to the scanner's old inline output for
all 10 patterns (measured). No new staging dependency: both hooks already
require this module.
2. The noun alternation `prompts?` had no positive coverage for the plural
branch. One filler-regression row now exercises `... previous prompts ...`
and runs through the existing once-per-hook, exactly-one-pattern loops.
The parity test's prompt-guard count assertion moves off substring-matching
the advisory prose onto the typed `findings` surface added in #3546, per
CONTRIBUTING's raw-text-matching prohibition. New test: the superset source
exceeds the bound (positive control), the prompt guard never embeds it, and
both hooks carry the identical label in `findings[0].match`.
Tests: parity, read-scanner, kimi field-shadowing, prompt-injection-scan,
hooks-crash-policy, dead-exports: 206 run, 196 pass, 0 fail, 10 pre-existing
platform skips. eslint clean; changeset lint ok; hooks runtime-build-seam lint
ok; the CI prompt-injection scanner reports 0 findings on the PR diff.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GGp8kEB5zCDmJ6TYHP1Nj
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>