* refactor: extract the .ad script codec into packages/ad-script
Moves the mutually-coupled .ad read/write codec (script.ts, script-utils.ts,
script-formatting.ts, open-script.ts) plus the target-v1 annotation SERDE
slice of target-identity.ts into a new private leaf package,
@agent-device/ad-script, exporting only `.`. This is option 1 from the P5
scoping dossier on #1478: the codec is shared by the daemon's session-script
publication writer, the future replay engine, the CLI's `replay export`, and
Maestro's failure-label formatting, so it can no longer live in root src/
once packages/ad-replay lands (R11 forbids a package reaching into root src),
and a second export subpath or writer-half duplication are both ruled out by
existing gates/tests.
target-identity.ts keeps only the record/replay-shared classification core
(classifyTargetBindingMatch, local-identity/ancestry-prefix matching),
importing its shared types from the new package. Every real consumer
(re-derived by grep, not the dossier's list alone) is rewired to
@agent-device/ad-script.
Refs #1478
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: trim the ad-script façade to real consumers, lock the one-export boundary
- packages/ad-script/src/index.ts: drop parseReplaySeriesFlags,
formatTargetAnnotationCommentLine, parseTargetAnnotationCommentLine,
TargetAnnotationLineParseResult, and TargetRect from the public façade —
none has a consumer outside the package (re-swept every remaining export
by grep; everything else kept has at least one real external importer).
The functions/types stay exported from their declaring internal modules
for the package's own internal use (script.ts, script-formatting.ts).
- scripts/layering/package-boundaries.test.ts: add the parallel R11
assertions "the real tree parses, declares, and passes R11" already makes
for maestro/provider-webdriver/provider-limrun/xml — ad-script exports
exactly `.`, depends on exactly contracts+kernel, and is declared in root
package.json — plus ad-script entries in the deep-resolution rejection
coverage. Verified the lock catches a regression: temporarily added a
fake `./codec` export to packages/ad-script/package.json and confirmed
both the export-key-list assertion and the deep-resolution-rejection
assertion fail; removed the plant and reconfirmed green.
Refs #1478
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: remove polynomial-redos ambiguity from the target-v1 annotation line regex
CodeQL js/polynomial-redos flagged TARGET_ANNOTATION_LINE_RE
(packages/ad-script/src/internal/target-annotation-serde.ts): the payload
group's `\s+(.*)` let `\s+` and the unconstrained `.*` both match whitespace,
so a run of separator whitespace that ultimately fails to complete the match
has many `\s+`/`.*` splits to backtrack through before concluding failure.
Anchor the payload group on `\S` (the exact complement of `\s`), so the
mandatory `\s+` separator and the payload's first character can never
overlap — the split point becomes unique and no backtracking is possible.
Behavior-preserving: the only caller (parseTargetAnnotationCommentLine)
always matches against an already-.trim()-ed line, whose last character
(whenever the tag matches at all) is never whitespace — so a payload section
`\S.*` would reject (content that is entirely whitespace) can never reach
this regex through the real call path. Verified against the frozen
replay-compat corpus and the full serde/parser test suites, unmodified.
Added a regression test with the exact adversarial shape CodeQL/the reviewer
cited (many tab pairs after the version digits), asserting sub-second parse.
Refs #1478
Co-Authored-By: Claude <noreply@anthropic.com>
* test(ad-script): pin the annotation-line pattern's linear rejection directly
The entry-point adversarial case matched greedily even with the retired
regex (trim strips edge whitespace and per-line input carries no newline),
so it proved nothing about the pattern. The regression surface is the
pattern itself: an interior tab run with an x-newline tail fails the match,
which the retired form re-split quadratically (3.7s at 100k tabs) and the
\S anchor rejects in one attempt.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>