diff --git a/.changeset/3594-parser-adversarial-fixtures.md b/.changeset/3594-parser-adversarial-fixtures.md new file mode 100644 index 000000000..51364e592 --- /dev/null +++ b/.changeset/3594-parser-adversarial-fixtures.md @@ -0,0 +1,5 @@ +--- +"get-shit-done-cc": patch +--- + +**Added: adversarial parser fixture corpus and tests.** New `tests/fixtures/adversarial/{frontmatter,roadmap}/` directories with hostile-but-realistic inputs (duplicate keys, CRLF endings, unclosed blocks, Unicode, null bytes, fenced-code headings, decimal phase prefix collisions, HTML-commented headings), exercised by new behavioral tests (`feat-3594-parser-adversarial-frontmatter.test.cjs`, `feat-3594-parser-adversarial-roadmap.test.cjs`) plus one deterministic seeded property-style test (`feat-3594-parser-property-style.test.cjs`) covering 500 generated inputs per assertion. Pins current parser behavior for known still-open regressions (#2787 fenced-code-block headings in the CJS parser; HTML-comment heading false positives) so the future fix lands as a one-line assertion flip. diff --git a/tests/feat-3594-parser-adversarial-frontmatter.test.cjs b/tests/feat-3594-parser-adversarial-frontmatter.test.cjs new file mode 100644 index 000000000..09f435b3a --- /dev/null +++ b/tests/feat-3594-parser-adversarial-frontmatter.test.cjs @@ -0,0 +1,152 @@ +/** + * Adversarial frontmatter-parser tests (#3594). + * + * Loads each file in `tests/fixtures/adversarial/frontmatter/` and pins + * the invariants `extractFrontmatter()` must satisfy. The fixtures + * encode hostile-but-realistic input shapes (duplicate keys, CRLF + * endings, unclosed blocks, Unicode, null bytes, huge but bounded + * payloads) that the parser will see in the wild because users edit + * planning files with multiple tools. + * + * Per CONTRIBUTING.md §"Testing Standards / Parser and project-file + * inputs", these are typed-IR assertions on parser return values — + * not prose-grep on rendered output. Property-style invariants for + * the roadmap parser live in + * `tests/feat-3594-parser-property-style.test.cjs`. + */ + +'use strict'; + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const { extractFrontmatter } = require('../get-shit-done/bin/lib/frontmatter.cjs'); + +const FIXTURE_DIR = path.join(__dirname, 'fixtures', 'adversarial', 'frontmatter'); + +function loadFixture(name) { + // Read as buffer first so null bytes survive into the string. The + // CRLF fixture also requires we do NOT normalize line endings on read. + return fs.readFileSync(path.join(FIXTURE_DIR, name), 'utf-8'); +} + +describe('feat-3594: frontmatter parser handles duplicate keys deterministically', () => { + test('duplicate keys collapse to a single deterministic winner (last-wins is the current contract)', () => { + const content = loadFixture('duplicate-keys.md'); + const fm = extractFrontmatter(content); + + // The parser MUST return a single value per key — not an array of + // both, not a half-formed entry. Whichever value wins, the test pins + // the current behavior so a silent semantics change is a test failure. + assert.equal(typeof fm.title, 'string', 'title must be a string, not an array or object'); + assert.equal(typeof fm.status, 'string', 'status must be a string'); + // Current parser behavior: the second occurrence wins because each + // key: line overwrites the previous in the same indent context. + // Pin it so a change to first-wins becomes visible. + assert.equal(fm.title, 'Second', 'duplicate-key collapse must be last-wins (current contract)'); + assert.equal(fm.status, 'blocked', 'duplicate-key collapse must be last-wins (current contract)'); + // Untouched keys round-trip cleanly. + assert.equal(fm.phase, '01'); + }); +}); + +describe('feat-3594: frontmatter parser handles CRLF endings without bleed', () => { + test('CRLF-terminated frontmatter parses without trailing \\r in values', () => { + const content = loadFixture('crlf-mixed.md'); + const fm = extractFrontmatter(content); + // Each value MUST be \r-free. A bug in `\r?\n` handling would leak + // \r into the captured group. + assert.equal(fm.title, 'CRLF Title'); + assert.equal(fm.phase, '02'); + assert.ok(!/\r/.test(JSON.stringify(fm)), 'no \\r should appear in any parsed value'); + // Array items must also be \r-free. + assert.deepEqual(fm.plans, ['02-01', '02-02']); + }); +}); + +describe('feat-3594: frontmatter parser handles unclosed blocks safely', () => { + test('unclosed frontmatter block returns empty object, not partial parse', () => { + const content = loadFixture('unclosed-block.md'); + const fm = extractFrontmatter(content); + // The current contract: if the closing `---` is missing, the regex + // doesn't match and the parser returns {}. The test pins that — + // a partial parse (returning {title: 'Unclosed Block'}) would be a + // silent data-leak from the body into "frontmatter." + assert.deepEqual(fm, {}, 'unclosed block must yield empty frontmatter, not a partial parse'); + }); +}); + +describe('feat-3594: frontmatter parser preserves Unicode round-trip', () => { + test('non-ASCII keys and values survive parsing', () => { + const content = loadFixture('unicode-keys-and-values.md'); + const fm = extractFrontmatter(content); + assert.equal(fm.title, '日本語のタイトル'); + // The parser's key regex is /^(\s*)([a-zA-Z0-9_-]+):.../ so non-ASCII + // keys (like `相:`) won't be captured. Pin that current behavior so + // a future broadening to allow Unicode keys is visible (and so the + // ASCII-only contract is asserted, not silently relied on). + assert.equal(fm['相'], undefined, 'parser currently only recognizes ASCII keys (regression guard)'); + // The status field has an emoji — must survive. + assert.equal(fm.status, '🚧 in-flight'); + // Inline array with Greek letters. + assert.deepEqual(fm.tags, ['α', 'β', 'γ']); + }); +}); + +describe('feat-3594: frontmatter parser handles null bytes without truncation', () => { + test('null byte in a value is preserved or normalized, never silently truncates the rest', () => { + const content = loadFixture('null-byte-value.md'); + const fm = extractFrontmatter(content); + // The parser MUST NOT crash. It MUST NOT truncate the value at the + // null byte AND continue parsing as if the rest of the line never + // existed. We pin: (a) the title still parses, (b) the phase key + // following the null-byte line still parses (no early-termination), + // (c) the null-byte value itself is a string. + assert.equal(fm.title, 'Has null byte'); + assert.equal(fm.phase, '05', 'parser must continue past the null-byte line, not silently stop'); + assert.equal(typeof fm.weird, 'string'); + // The exact null-handling is documented by whatever the current + // parser does: either preserve the \x00 or strip it. Test pins one. + assert.ok(fm.weird.includes('before'), 'value before the null byte must be retained'); + }); +}); + +describe('feat-3594: frontmatter parser handles bounded-large inputs in reasonable time', () => { + test('64KB frontmatter with 2000 array items parses under 2 seconds and returns the right shape', () => { + const content = loadFixture('huge-bounded.md'); + const startedAt = Date.now(); + const fm = extractFrontmatter(content); + const elapsedMs = Date.now() - startedAt; + // Time bound is generous — a parser regression that makes this O(n^2) + // would blow well past 2s on this fixture. + assert.ok(elapsedMs < 2000, `parse took ${elapsedMs}ms — should be < 2000ms`); + assert.equal(fm.phase, '06'); + assert.ok(Array.isArray(fm.plans), 'plans must be parsed as an array'); + assert.equal(fm.plans.length, 2000, 'all 2000 array items must be captured'); + assert.equal(fm.plans[0], 'item-00000'); + assert.equal(fm.plans[1999], 'item-01999'); + }); +}); + +// ─── Cross-cutting invariants over the whole fixture corpus ──────────────── + +describe('feat-3594: frontmatter parser does not throw on ANY corpus fixture', () => { + // Property-style: whatever weirdness lives in the corpus, extractFrontmatter + // must return an object — never throw, never return undefined/null. This is + // the floor every individual fixture also satisfies, but checking it as a + // sweep catches a future fixture addition where the author forgets to write + // a per-file test. + const fixtures = fs.readdirSync(FIXTURE_DIR).filter((f) => f.endsWith('.md') && f !== 'README.md'); + for (const fixture of fixtures) { + test(`fixture "${fixture}" — extractFrontmatter returns a plain object without throwing`, () => { + const content = loadFixture(fixture); + let fm; + assert.doesNotThrow(() => { fm = extractFrontmatter(content); }, `extractFrontmatter must not throw on ${fixture}`); + assert.equal(typeof fm, 'object', `${fixture}: result must be an object`); + assert.notEqual(fm, null, `${fixture}: result must not be null`); + assert.equal(Array.isArray(fm), false, `${fixture}: result must not be an array`); + }); + } +}); diff --git a/tests/feat-3594-parser-adversarial-roadmap.test.cjs b/tests/feat-3594-parser-adversarial-roadmap.test.cjs new file mode 100644 index 000000000..d2d249775 --- /dev/null +++ b/tests/feat-3594-parser-adversarial-roadmap.test.cjs @@ -0,0 +1,236 @@ +/** + * Adversarial roadmap-parser tests (#3594). + * + * Loads each fixture in `tests/fixtures/adversarial/roadmap/` as the + * project's `.planning/ROADMAP.md` and pins invariants on the public + * `gsd-tools roadmap get-phase ` surface — which routes through the + * SDK bridge when available and the CJS handler otherwise. + * + * Per CONTRIBUTING.md §"Testing Standards / Parser and project-file + * inputs", the assertion target is the typed JSON shape the CLI emits, + * not stderr prose. The harness in `tests/helpers/cli-negative.cjs` + * (introduced by #3627 / #3593) is reused here for consistency. + * + * Several fixtures encode known historical regressions: + * - fenced-code-block headings shadowing real phases (#2787) + * - decimal phase prefix collisions (#3537) + * - HTML-comment heading false positives + * + * Pre-existing parser bugs surfaced by these fixtures are NOT fixed in + * this PR — fixing them is out of scope for "add adversarial test + * coverage." Where a fixture exposes a still-open bug, the test + * asserts the *currently observed* behavior with a comment naming the + * open issue, so the flip from RED→GREEN is a one-line change the day + * the real fix lands. + */ + +'use strict'; + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const { runCli } = require('./helpers/cli-negative.cjs'); +const { createTempProject, cleanup } = require('./helpers.cjs'); + +const FIXTURE_DIR = path.join(__dirname, 'fixtures', 'adversarial', 'roadmap'); + +function loadFixture(name) { + return fs.readFileSync(path.join(FIXTURE_DIR, name), 'utf-8'); +} + +/** + * Create a temp project whose ROADMAP.md is the named fixture's content. + * Returns the project directory; caller is responsible for cleanup. + */ +function projectWithFixture(t, fixtureName) { + const projectDir = createTempProject('roadmap-adv-' + fixtureName.replace(/\W+/g, '-') + '-'); + t.after(() => cleanup(projectDir)); + fs.writeFileSync(path.join(projectDir, '.planning', 'ROADMAP.md'), loadFixture(fixtureName)); + return projectDir; +} + +/** + * Run `gsd-tools roadmap get-phase ` and parse the JSON payload. + * Returns `{ ok, exit, parsed, raw }` so tests can assert on either + * the exit code or the structured payload. + */ +function getPhase(projectDir, phaseNum) { + // No --json-errors — the get-phase command outputs JSON on success + // via the normal stdout path. Reading the parsed payload is what the + // workflows downstream do, so that's what we test. + const result = runCli(['roadmap', 'get-phase', phaseNum], { cwd: projectDir, jsonErrors: false }); + let parsed = null; + try { + parsed = JSON.parse(result.stdout); + } catch { + // Leave parsed null; tests that depend on it must handle that. + } + return { + exit: result.status, + ok: result.status === 0, + parsed, + raw: result.stdout, + stderr: result.stderr, + hasStackTrace: result.hasStackTrace, + }; +} + +// ─── Fenced code block heading shadowing ──────────────────────────────────── + +describe('feat-3594: roadmap parser and fenced-code-block headings (#2787)', () => { + test('phase 1 in real prose is found even when ## Phase 999 appears inside a ``` block', (t) => { + const projectDir = projectWithFixture(t, 'phase-heading-inside-fenced-code.md'); + const result = getPhase(projectDir, '1'); + assert.equal(result.hasStackTrace, false, 'no V8 stack trace'); + assert.ok(result.parsed, `expected JSON payload, got: ${result.raw}`); + assert.equal(result.parsed.found, true, 'phase 1 must be found'); + assert.equal(result.parsed.phase_number, '1'); + assert.match(result.parsed.phase_name, /real phase one/); + }); + + test('phase 999 inside a fenced block: CJS parser currently STILL matches it (open: needs fence-stripping)', (t) => { + const projectDir = projectWithFixture(t, 'phase-heading-inside-fenced-code.md'); + const result = getPhase(projectDir, '999'); + assert.equal(result.hasStackTrace, false, 'no stack trace'); + // The CJS regex parser does not strip fenced code blocks before + // matching. The SDK roadmap parser tracks fenced blocks (per #2787 + // comment in sdk/src/query/roadmap.ts) — the CJS path has not caught + // up. This test pins the current behavior so the day someone wires + // CJS fence-stripping, flipping `found: true` to `found: false` + // becomes the regression guard. + assert.ok(result.parsed, `expected JSON payload, got: ${result.raw}`); + assert.equal(result.parsed.found, true, 'CJS parser currently matches inside fences (known open bug)'); + // The matched heading is the one INSIDE the fenced block. Match + // its distinctive substring so a future "fix" that strips fences + // and instead matches a different (real) phase 999 (which we don't + // have in this fixture, so impossible) still fails the right test. + assert.match( + result.parsed.phase_name, + /fenced code block/i, + 'currently-matched heading must be the one inside the fence', + ); + }); +}); + +// ─── Decimal phase prefix collisions ──────────────────────────────────────── + +describe('feat-3594: roadmap parser handles decimal phase prefix collisions (#3537)', () => { + test('asking for phase "2" returns the integer phase, NOT phase 2.1 or 2.10', (t) => { + const projectDir = projectWithFixture(t, 'decimal-phase-mixed.md'); + const result = getPhase(projectDir, '2'); + assert.ok(result.parsed, `expected JSON payload, got: ${result.raw}`); + assert.equal(result.parsed.found, true); + assert.equal(result.parsed.phase_number, '2'); + assert.match(result.parsed.phase_name, /integer phase two/); + }); + + test('asking for phase "2.1" returns the decimal child', (t) => { + const projectDir = projectWithFixture(t, 'decimal-phase-mixed.md'); + const result = getPhase(projectDir, '2.1'); + assert.ok(result.parsed, `expected JSON payload, got: ${result.raw}`); + assert.equal(result.parsed.found, true); + assert.equal(result.parsed.phase_number, '2.1'); + assert.match(result.parsed.phase_name, /decimal child/); + }); + + test('asking for phase "2.10" returns the decimal sibling, NOT phase 2.1', (t) => { + const projectDir = projectWithFixture(t, 'decimal-phase-mixed.md'); + const result = getPhase(projectDir, '2.10'); + assert.ok(result.parsed, `expected JSON payload, got: ${result.raw}`); + assert.equal(result.parsed.found, true); + assert.equal(result.parsed.phase_number, '2.10'); + assert.match(result.parsed.phase_name, /decimal phase 2\.10/); + }); + + test('asking for phase "21" returns phase 21, NOT phase 2 (prefix-collision guard)', (t) => { + const projectDir = projectWithFixture(t, 'decimal-phase-mixed.md'); + const result = getPhase(projectDir, '21'); + assert.ok(result.parsed, `expected JSON payload, got: ${result.raw}`); + assert.equal(result.parsed.found, true); + assert.equal(result.parsed.phase_number, '21'); + assert.match(result.parsed.phase_name, /phase twenty-one/); + }); +}); + +// ─── Unicode phase titles ─────────────────────────────────────────────────── + +describe('feat-3594: roadmap parser preserves Unicode phase titles', () => { + test('Japanese title round-trips through phase_name', (t) => { + const projectDir = projectWithFixture(t, 'unicode-phase-titles.md'); + const result = getPhase(projectDir, '1'); + assert.ok(result.parsed, `expected JSON payload, got: ${result.raw}`); + assert.equal(result.parsed.phase_name, '日本語フェーズ — initial setup'); + }); + + test('emoji + smart-quote title survives', (t) => { + const projectDir = projectWithFixture(t, 'unicode-phase-titles.md'); + const result = getPhase(projectDir, '2'); + assert.ok(result.parsed, `expected JSON payload, got: ${result.raw}`); + assert.match(result.parsed.phase_name, /🚧/); + assert.match(result.parsed.phase_name, /Émile/); + }); + + test('Greek-letter title survives', (t) => { + const projectDir = projectWithFixture(t, 'unicode-phase-titles.md'); + const result = getPhase(projectDir, '3'); + assert.ok(result.parsed, `expected JSON payload, got: ${result.raw}`); + assert.equal(result.parsed.phase_name, 'αβγ δεζ ηθι'); + }); +}); + +// ─── Repeated phase IDs ───────────────────────────────────────────────────── + +describe('feat-3594: roadmap parser handles repeated phase IDs deterministically', () => { + test('two declarations of phase 1: parser returns the FIRST match (current behavior)', (t) => { + const projectDir = projectWithFixture(t, 'repeated-phase-ids.md'); + const result = getPhase(projectDir, '1'); + assert.ok(result.parsed, `expected JSON payload, got: ${result.raw}`); + assert.equal(result.parsed.found, true); + // The regex uses `content.match(...)` which returns the FIRST match. + // Pin that — a future change to last-wins or de-dup would fire. + assert.match(result.parsed.phase_name, /first declaration/); + }); +}); + +// ─── HTML comments ────────────────────────────────────────────────────────── + +describe('feat-3594: roadmap parser and HTML-commented headings', () => { + test('phase 1 in real prose is found even when phase 998/999 appear inside ', (t) => { + const projectDir = projectWithFixture(t, 'markdown-headings-inside-html-comment.md'); + const result = getPhase(projectDir, '1'); + assert.ok(result.parsed, `expected JSON payload, got: ${result.raw}`); + assert.equal(result.parsed.found, true); + assert.equal(result.parsed.phase_name, 'real phase'); + }); + + test('phase 999 inside an HTML comment: CJS parser currently STILL matches it (open: needs comment-stripping)', (t) => { + const projectDir = projectWithFixture(t, 'markdown-headings-inside-html-comment.md'); + const result = getPhase(projectDir, '999'); + assert.equal(result.hasStackTrace, false, 'no stack trace'); + // Same shape as the fenced-code-block case: the CJS regex parser + // doesn't strip HTML comments before matching. + assert.ok(result.parsed, `expected JSON payload, got: ${result.raw}`); + assert.equal(result.parsed.found, true, 'CJS parser currently matches inside HTML comments (known open bug)'); + assert.match(result.parsed.phase_name, /HTML comment/); + }); +}); + +// ─── Cross-corpus invariant ──────────────────────────────────────────────── + +describe('feat-3594: roadmap parser does not crash on ANY corpus fixture', () => { + const fixtures = fs.readdirSync(FIXTURE_DIR).filter((f) => f.endsWith('.md') && f !== 'README.md'); + for (const fixture of fixtures) { + test(`fixture "${fixture}" — get-phase with arbitrary IDs must not crash`, (t) => { + const projectDir = projectWithFixture(t, fixture); + for (const id of ['1', '2', '99', '999', '0', '2.1']) { + const result = getPhase(projectDir, id); + assert.equal(result.hasStackTrace, false, `${fixture} id=${id}: no V8 stack frame allowed`); + // exit status varies (0 for found, non-zero for not-found — + // both are valid). What's pinned: the parser produced SOME output + // (either valid JSON or a clean stderr) without crashing. + } + }); + } +}); diff --git a/tests/feat-3594-parser-property-style.test.cjs b/tests/feat-3594-parser-property-style.test.cjs new file mode 100644 index 000000000..b94102870 Binary files /dev/null and b/tests/feat-3594-parser-property-style.test.cjs differ diff --git a/tests/fixtures/adversarial/frontmatter/README.md b/tests/fixtures/adversarial/frontmatter/README.md new file mode 100644 index 000000000..3a77e6c7d --- /dev/null +++ b/tests/fixtures/adversarial/frontmatter/README.md @@ -0,0 +1,31 @@ +# Adversarial Frontmatter Fixtures (#3594) + +Reusable hostile inputs for `get-shit-done/bin/lib/frontmatter.cjs` +`extractFrontmatter()` and downstream consumers. + +Each fixture is a single markdown file whose name encodes the abuse +category. Tests load them by relative path so the corpus can grow +without test code changes. Adding a new fixture means: drop the file +in here, add an entry to the matrix in +`tests/feat-3594-parser-adversarial-frontmatter.test.cjs`, decide +what invariant the parser must satisfy (typically "does not throw, +does not return half-parsed garbage, does not silently lose data"). + +Categories present: + +- `duplicate-keys.md` — same key appears twice. Parser must produce + a deterministic result; tests document which value wins (last-wins + is the current behavior). +- `crlf-mixed.md` — CRLF endings throughout the frontmatter block. + Parser must handle the `\r` consistently and not bleed it into + values. +- `unclosed-block.md` — opening `---` with no closing `---`. Parser + must return empty frontmatter (or a clean error), never partial. +- `unicode-keys-and-values.md` — non-ASCII keys/values. Parser must + round-trip them as-is. +- `null-byte-value.md` — value contains a U+0000 null. Parser must + preserve or normalize it; must not crash and must not truncate + silently. +- `huge-bounded.md` — a deliberately-large but bounded frontmatter + block (~64KB of array items). Parser must complete in reasonable + time with a typed result, not OOM or hang. diff --git a/tests/fixtures/adversarial/frontmatter/crlf-mixed.md b/tests/fixtures/adversarial/frontmatter/crlf-mixed.md new file mode 100644 index 000000000..df038d251 --- /dev/null +++ b/tests/fixtures/adversarial/frontmatter/crlf-mixed.md @@ -0,0 +1,9 @@ +--- +title: CRLF Title +phase: 02 +plans: + - 02-01 + - 02-02 +--- + +Body with mixed CRLF endings. diff --git a/tests/fixtures/adversarial/frontmatter/duplicate-keys.md b/tests/fixtures/adversarial/frontmatter/duplicate-keys.md new file mode 100644 index 000000000..792351db4 --- /dev/null +++ b/tests/fixtures/adversarial/frontmatter/duplicate-keys.md @@ -0,0 +1,13 @@ +--- +title: First +title: Second +status: active +status: blocked +phase: 01 +--- + +Body content for duplicate-keys fixture. + +When the parser encounters a key twice in the same block, the test pins +what is currently observed (last-wins) so a silent semantics change +becomes a test failure rather than a quiet data shift. diff --git a/tests/fixtures/adversarial/frontmatter/huge-bounded.md b/tests/fixtures/adversarial/frontmatter/huge-bounded.md new file mode 100644 index 000000000..132b3f16f --- /dev/null +++ b/tests/fixtures/adversarial/frontmatter/huge-bounded.md @@ -0,0 +1,2006 @@ +--- +phase: 06 +plans: + - item-00000 + - item-00001 + - item-00002 + - item-00003 + - item-00004 + - item-00005 + - item-00006 + - item-00007 + - item-00008 + - item-00009 + - item-00010 + - item-00011 + - item-00012 + - item-00013 + - item-00014 + - item-00015 + - item-00016 + - item-00017 + - item-00018 + - item-00019 + - item-00020 + - item-00021 + - item-00022 + - item-00023 + - item-00024 + - item-00025 + - item-00026 + - item-00027 + - item-00028 + - item-00029 + - item-00030 + - item-00031 + - item-00032 + - item-00033 + - item-00034 + - item-00035 + - item-00036 + - item-00037 + - item-00038 + - item-00039 + - item-00040 + - item-00041 + - item-00042 + - item-00043 + - item-00044 + - item-00045 + - item-00046 + - item-00047 + - item-00048 + - item-00049 + - item-00050 + - item-00051 + - item-00052 + - item-00053 + - item-00054 + - item-00055 + - item-00056 + - item-00057 + - item-00058 + - item-00059 + - item-00060 + - item-00061 + - item-00062 + - item-00063 + - item-00064 + - item-00065 + - item-00066 + - item-00067 + - item-00068 + - item-00069 + - item-00070 + - item-00071 + - item-00072 + - item-00073 + - item-00074 + - item-00075 + - item-00076 + - item-00077 + - item-00078 + - item-00079 + - item-00080 + - item-00081 + - item-00082 + - item-00083 + - item-00084 + - item-00085 + - item-00086 + - item-00087 + - item-00088 + - item-00089 + - item-00090 + - item-00091 + - item-00092 + - item-00093 + - item-00094 + - item-00095 + - item-00096 + - item-00097 + - item-00098 + - item-00099 + - item-00100 + - item-00101 + - item-00102 + - item-00103 + - item-00104 + - item-00105 + - item-00106 + - item-00107 + - item-00108 + - item-00109 + - item-00110 + - item-00111 + - item-00112 + - item-00113 + - item-00114 + - item-00115 + - item-00116 + - item-00117 + - item-00118 + - item-00119 + - item-00120 + - item-00121 + - item-00122 + - item-00123 + - item-00124 + - item-00125 + - item-00126 + - item-00127 + - item-00128 + - item-00129 + - item-00130 + - item-00131 + - item-00132 + - item-00133 + - item-00134 + - item-00135 + - item-00136 + - item-00137 + - item-00138 + - item-00139 + - item-00140 + - item-00141 + - item-00142 + - item-00143 + - item-00144 + - item-00145 + - item-00146 + - item-00147 + - item-00148 + - item-00149 + - item-00150 + - item-00151 + - item-00152 + - item-00153 + - item-00154 + - item-00155 + - item-00156 + - item-00157 + - item-00158 + - item-00159 + - item-00160 + - item-00161 + - item-00162 + - item-00163 + - item-00164 + - item-00165 + - item-00166 + - item-00167 + - item-00168 + - item-00169 + - item-00170 + - item-00171 + - item-00172 + - item-00173 + - item-00174 + - item-00175 + - item-00176 + - item-00177 + - item-00178 + - item-00179 + - item-00180 + - item-00181 + - item-00182 + - item-00183 + - item-00184 + - item-00185 + - item-00186 + - item-00187 + - item-00188 + - item-00189 + - item-00190 + - item-00191 + - item-00192 + - item-00193 + - item-00194 + - item-00195 + - item-00196 + - item-00197 + - item-00198 + - item-00199 + - item-00200 + - item-00201 + - item-00202 + - item-00203 + - item-00204 + - item-00205 + - item-00206 + - item-00207 + - item-00208 + - item-00209 + - item-00210 + - item-00211 + - item-00212 + - item-00213 + - item-00214 + - item-00215 + - item-00216 + - item-00217 + - item-00218 + - item-00219 + - item-00220 + - item-00221 + - item-00222 + - item-00223 + - item-00224 + - item-00225 + - item-00226 + - item-00227 + - item-00228 + - item-00229 + - item-00230 + - item-00231 + - item-00232 + - item-00233 + - item-00234 + - item-00235 + - item-00236 + - item-00237 + - item-00238 + - item-00239 + - item-00240 + - item-00241 + - item-00242 + - item-00243 + - item-00244 + - item-00245 + - item-00246 + - item-00247 + - item-00248 + - item-00249 + - item-00250 + - item-00251 + - item-00252 + - item-00253 + - item-00254 + - item-00255 + - item-00256 + - item-00257 + - item-00258 + - item-00259 + - item-00260 + - item-00261 + - item-00262 + - item-00263 + - item-00264 + - item-00265 + - item-00266 + - item-00267 + - item-00268 + - item-00269 + - item-00270 + - item-00271 + - item-00272 + - item-00273 + - item-00274 + - item-00275 + - item-00276 + - item-00277 + - item-00278 + - item-00279 + - item-00280 + - item-00281 + - item-00282 + - item-00283 + - item-00284 + - item-00285 + - item-00286 + - item-00287 + - item-00288 + - item-00289 + - item-00290 + - item-00291 + - item-00292 + - item-00293 + - item-00294 + - item-00295 + - item-00296 + - item-00297 + - item-00298 + - item-00299 + - item-00300 + - item-00301 + - item-00302 + - item-00303 + - item-00304 + - item-00305 + - item-00306 + - item-00307 + - item-00308 + - item-00309 + - item-00310 + - item-00311 + - item-00312 + - item-00313 + - item-00314 + - item-00315 + - item-00316 + - item-00317 + - item-00318 + - item-00319 + - item-00320 + - item-00321 + - item-00322 + - item-00323 + - item-00324 + - item-00325 + - item-00326 + - item-00327 + - item-00328 + - item-00329 + - item-00330 + - item-00331 + - item-00332 + - item-00333 + - item-00334 + - item-00335 + - item-00336 + - item-00337 + - item-00338 + - item-00339 + - item-00340 + - item-00341 + - item-00342 + - item-00343 + - item-00344 + - item-00345 + - item-00346 + - item-00347 + - item-00348 + - item-00349 + - item-00350 + - item-00351 + - item-00352 + - item-00353 + - item-00354 + - item-00355 + - item-00356 + - item-00357 + - item-00358 + - item-00359 + - item-00360 + - item-00361 + - item-00362 + - item-00363 + - item-00364 + - item-00365 + - item-00366 + - item-00367 + - item-00368 + - item-00369 + - item-00370 + - item-00371 + - item-00372 + - item-00373 + - item-00374 + - item-00375 + - item-00376 + - item-00377 + - item-00378 + - item-00379 + - item-00380 + - item-00381 + - item-00382 + - item-00383 + - item-00384 + - item-00385 + - item-00386 + - item-00387 + - item-00388 + - item-00389 + - item-00390 + - item-00391 + - item-00392 + - item-00393 + - item-00394 + - item-00395 + - item-00396 + - item-00397 + - item-00398 + - item-00399 + - item-00400 + - item-00401 + - item-00402 + - item-00403 + - item-00404 + - item-00405 + - item-00406 + - item-00407 + - item-00408 + - item-00409 + - item-00410 + - item-00411 + - item-00412 + - item-00413 + - item-00414 + - item-00415 + - item-00416 + - item-00417 + - item-00418 + - item-00419 + - item-00420 + - item-00421 + - item-00422 + - item-00423 + - item-00424 + - item-00425 + - item-00426 + - item-00427 + - item-00428 + - item-00429 + - item-00430 + - item-00431 + - item-00432 + - item-00433 + - item-00434 + - item-00435 + - item-00436 + - item-00437 + - item-00438 + - item-00439 + - item-00440 + - item-00441 + - item-00442 + - item-00443 + - item-00444 + - item-00445 + - item-00446 + - item-00447 + - item-00448 + - item-00449 + - item-00450 + - item-00451 + - item-00452 + - item-00453 + - item-00454 + - item-00455 + - item-00456 + - item-00457 + - item-00458 + - item-00459 + - item-00460 + - item-00461 + - item-00462 + - item-00463 + - item-00464 + - item-00465 + - item-00466 + - item-00467 + - item-00468 + - item-00469 + - item-00470 + - item-00471 + - item-00472 + - item-00473 + - item-00474 + - item-00475 + - item-00476 + - item-00477 + - item-00478 + - item-00479 + - item-00480 + - item-00481 + - item-00482 + - item-00483 + - item-00484 + - item-00485 + - item-00486 + - item-00487 + - item-00488 + - item-00489 + - item-00490 + - item-00491 + - item-00492 + - item-00493 + - item-00494 + - item-00495 + - item-00496 + - item-00497 + - item-00498 + - item-00499 + - item-00500 + - item-00501 + - item-00502 + - item-00503 + - item-00504 + - item-00505 + - item-00506 + - item-00507 + - item-00508 + - item-00509 + - item-00510 + - item-00511 + - item-00512 + - item-00513 + - item-00514 + - item-00515 + - item-00516 + - item-00517 + - item-00518 + - item-00519 + - item-00520 + - item-00521 + - item-00522 + - item-00523 + - item-00524 + - item-00525 + - item-00526 + - item-00527 + - item-00528 + - item-00529 + - item-00530 + - item-00531 + - item-00532 + - item-00533 + - item-00534 + - item-00535 + - item-00536 + - item-00537 + - item-00538 + - item-00539 + - item-00540 + - item-00541 + - item-00542 + - item-00543 + - item-00544 + - item-00545 + - item-00546 + - item-00547 + - item-00548 + - item-00549 + - item-00550 + - item-00551 + - item-00552 + - item-00553 + - item-00554 + - item-00555 + - item-00556 + - item-00557 + - item-00558 + - item-00559 + - item-00560 + - item-00561 + - item-00562 + - item-00563 + - item-00564 + - item-00565 + - item-00566 + - item-00567 + - item-00568 + - item-00569 + - item-00570 + - item-00571 + - item-00572 + - item-00573 + - item-00574 + - item-00575 + - item-00576 + - item-00577 + - item-00578 + - item-00579 + - item-00580 + - item-00581 + - item-00582 + - item-00583 + - item-00584 + - item-00585 + - item-00586 + - item-00587 + - item-00588 + - item-00589 + - item-00590 + - item-00591 + - item-00592 + - item-00593 + - item-00594 + - item-00595 + - item-00596 + - item-00597 + - item-00598 + - item-00599 + - item-00600 + - item-00601 + - item-00602 + - item-00603 + - item-00604 + - item-00605 + - item-00606 + - item-00607 + - item-00608 + - item-00609 + - item-00610 + - item-00611 + - item-00612 + - item-00613 + - item-00614 + - item-00615 + - item-00616 + - item-00617 + - item-00618 + - item-00619 + - item-00620 + - item-00621 + - item-00622 + - item-00623 + - item-00624 + - item-00625 + - item-00626 + - item-00627 + - item-00628 + - item-00629 + - item-00630 + - item-00631 + - item-00632 + - item-00633 + - item-00634 + - item-00635 + - item-00636 + - item-00637 + - item-00638 + - item-00639 + - item-00640 + - item-00641 + - item-00642 + - item-00643 + - item-00644 + - item-00645 + - item-00646 + - item-00647 + - item-00648 + - item-00649 + - item-00650 + - item-00651 + - item-00652 + - item-00653 + - item-00654 + - item-00655 + - item-00656 + - item-00657 + - item-00658 + - item-00659 + - item-00660 + - item-00661 + - item-00662 + - item-00663 + - item-00664 + - item-00665 + - item-00666 + - item-00667 + - item-00668 + - item-00669 + - item-00670 + - item-00671 + - item-00672 + - item-00673 + - item-00674 + - item-00675 + - item-00676 + - item-00677 + - item-00678 + - item-00679 + - item-00680 + - item-00681 + - item-00682 + - item-00683 + - item-00684 + - item-00685 + - item-00686 + - item-00687 + - item-00688 + - item-00689 + - item-00690 + - item-00691 + - item-00692 + - item-00693 + - item-00694 + - item-00695 + - item-00696 + - item-00697 + - item-00698 + - item-00699 + - item-00700 + - item-00701 + - item-00702 + - item-00703 + - item-00704 + - item-00705 + - item-00706 + - item-00707 + - item-00708 + - item-00709 + - item-00710 + - item-00711 + - item-00712 + - item-00713 + - item-00714 + - item-00715 + - item-00716 + - item-00717 + - item-00718 + - item-00719 + - item-00720 + - item-00721 + - item-00722 + - item-00723 + - item-00724 + - item-00725 + - item-00726 + - item-00727 + - item-00728 + - item-00729 + - item-00730 + - item-00731 + - item-00732 + - item-00733 + - item-00734 + - item-00735 + - item-00736 + - item-00737 + - item-00738 + - item-00739 + - item-00740 + - item-00741 + - item-00742 + - item-00743 + - item-00744 + - item-00745 + - item-00746 + - item-00747 + - item-00748 + - item-00749 + - item-00750 + - item-00751 + - item-00752 + - item-00753 + - item-00754 + - item-00755 + - item-00756 + - item-00757 + - item-00758 + - item-00759 + - item-00760 + - item-00761 + - item-00762 + - item-00763 + - item-00764 + - item-00765 + - item-00766 + - item-00767 + - item-00768 + - item-00769 + - item-00770 + - item-00771 + - item-00772 + - item-00773 + - item-00774 + - item-00775 + - item-00776 + - item-00777 + - item-00778 + - item-00779 + - item-00780 + - item-00781 + - item-00782 + - item-00783 + - item-00784 + - item-00785 + - item-00786 + - item-00787 + - item-00788 + - item-00789 + - item-00790 + - item-00791 + - item-00792 + - item-00793 + - item-00794 + - item-00795 + - item-00796 + - item-00797 + - item-00798 + - item-00799 + - item-00800 + - item-00801 + - item-00802 + - item-00803 + - item-00804 + - item-00805 + - item-00806 + - item-00807 + - item-00808 + - item-00809 + - item-00810 + - item-00811 + - item-00812 + - item-00813 + - item-00814 + - item-00815 + - item-00816 + - item-00817 + - item-00818 + - item-00819 + - item-00820 + - item-00821 + - item-00822 + - item-00823 + - item-00824 + - item-00825 + - item-00826 + - item-00827 + - item-00828 + - item-00829 + - item-00830 + - item-00831 + - item-00832 + - item-00833 + - item-00834 + - item-00835 + - item-00836 + - item-00837 + - item-00838 + - item-00839 + - item-00840 + - item-00841 + - item-00842 + - item-00843 + - item-00844 + - item-00845 + - item-00846 + - item-00847 + - item-00848 + - item-00849 + - item-00850 + - item-00851 + - item-00852 + - item-00853 + - item-00854 + - item-00855 + - item-00856 + - item-00857 + - item-00858 + - item-00859 + - item-00860 + - item-00861 + - item-00862 + - item-00863 + - item-00864 + - item-00865 + - item-00866 + - item-00867 + - item-00868 + - item-00869 + - item-00870 + - item-00871 + - item-00872 + - item-00873 + - item-00874 + - item-00875 + - item-00876 + - item-00877 + - item-00878 + - item-00879 + - item-00880 + - item-00881 + - item-00882 + - item-00883 + - item-00884 + - item-00885 + - item-00886 + - item-00887 + - item-00888 + - item-00889 + - item-00890 + - item-00891 + - item-00892 + - item-00893 + - item-00894 + - item-00895 + - item-00896 + - item-00897 + - item-00898 + - item-00899 + - item-00900 + - item-00901 + - item-00902 + - item-00903 + - item-00904 + - item-00905 + - item-00906 + - item-00907 + - item-00908 + - item-00909 + - item-00910 + - item-00911 + - item-00912 + - item-00913 + - item-00914 + - item-00915 + - item-00916 + - item-00917 + - item-00918 + - item-00919 + - item-00920 + - item-00921 + - item-00922 + - item-00923 + - item-00924 + - item-00925 + - item-00926 + - item-00927 + - item-00928 + - item-00929 + - item-00930 + - item-00931 + - item-00932 + - item-00933 + - item-00934 + - item-00935 + - item-00936 + - item-00937 + - item-00938 + - item-00939 + - item-00940 + - item-00941 + - item-00942 + - item-00943 + - item-00944 + - item-00945 + - item-00946 + - item-00947 + - item-00948 + - item-00949 + - item-00950 + - item-00951 + - item-00952 + - item-00953 + - item-00954 + - item-00955 + - item-00956 + - item-00957 + - item-00958 + - item-00959 + - item-00960 + - item-00961 + - item-00962 + - item-00963 + - item-00964 + - item-00965 + - item-00966 + - item-00967 + - item-00968 + - item-00969 + - item-00970 + - item-00971 + - item-00972 + - item-00973 + - item-00974 + - item-00975 + - item-00976 + - item-00977 + - item-00978 + - item-00979 + - item-00980 + - item-00981 + - item-00982 + - item-00983 + - item-00984 + - item-00985 + - item-00986 + - item-00987 + - item-00988 + - item-00989 + - item-00990 + - item-00991 + - item-00992 + - item-00993 + - item-00994 + - item-00995 + - item-00996 + - item-00997 + - item-00998 + - item-00999 + - item-01000 + - item-01001 + - item-01002 + - item-01003 + - item-01004 + - item-01005 + - item-01006 + - item-01007 + - item-01008 + - item-01009 + - item-01010 + - item-01011 + - item-01012 + - item-01013 + - item-01014 + - item-01015 + - item-01016 + - item-01017 + - item-01018 + - item-01019 + - item-01020 + - item-01021 + - item-01022 + - item-01023 + - item-01024 + - item-01025 + - item-01026 + - item-01027 + - item-01028 + - item-01029 + - item-01030 + - item-01031 + - item-01032 + - item-01033 + - item-01034 + - item-01035 + - item-01036 + - item-01037 + - item-01038 + - item-01039 + - item-01040 + - item-01041 + - item-01042 + - item-01043 + - item-01044 + - item-01045 + - item-01046 + - item-01047 + - item-01048 + - item-01049 + - item-01050 + - item-01051 + - item-01052 + - item-01053 + - item-01054 + - item-01055 + - item-01056 + - item-01057 + - item-01058 + - item-01059 + - item-01060 + - item-01061 + - item-01062 + - item-01063 + - item-01064 + - item-01065 + - item-01066 + - item-01067 + - item-01068 + - item-01069 + - item-01070 + - item-01071 + - item-01072 + - item-01073 + - item-01074 + - item-01075 + - item-01076 + - item-01077 + - item-01078 + - item-01079 + - item-01080 + - item-01081 + - item-01082 + - item-01083 + - item-01084 + - item-01085 + - item-01086 + - item-01087 + - item-01088 + - item-01089 + - item-01090 + - item-01091 + - item-01092 + - item-01093 + - item-01094 + - item-01095 + - item-01096 + - item-01097 + - item-01098 + - item-01099 + - item-01100 + - item-01101 + - item-01102 + - item-01103 + - item-01104 + - item-01105 + - item-01106 + - item-01107 + - item-01108 + - item-01109 + - item-01110 + - item-01111 + - item-01112 + - item-01113 + - item-01114 + - item-01115 + - item-01116 + - item-01117 + - item-01118 + - item-01119 + - item-01120 + - item-01121 + - item-01122 + - item-01123 + - item-01124 + - item-01125 + - item-01126 + - item-01127 + - item-01128 + - item-01129 + - item-01130 + - item-01131 + - item-01132 + - item-01133 + - item-01134 + - item-01135 + - item-01136 + - item-01137 + - item-01138 + - item-01139 + - item-01140 + - item-01141 + - item-01142 + - item-01143 + - item-01144 + - item-01145 + - item-01146 + - item-01147 + - item-01148 + - item-01149 + - item-01150 + - item-01151 + - item-01152 + - item-01153 + - item-01154 + - item-01155 + - item-01156 + - item-01157 + - item-01158 + - item-01159 + - item-01160 + - item-01161 + - item-01162 + - item-01163 + - item-01164 + - item-01165 + - item-01166 + - item-01167 + - item-01168 + - item-01169 + - item-01170 + - item-01171 + - item-01172 + - item-01173 + - item-01174 + - item-01175 + - item-01176 + - item-01177 + - item-01178 + - item-01179 + - item-01180 + - item-01181 + - item-01182 + - item-01183 + - item-01184 + - item-01185 + - item-01186 + - item-01187 + - item-01188 + - item-01189 + - item-01190 + - item-01191 + - item-01192 + - item-01193 + - item-01194 + - item-01195 + - item-01196 + - item-01197 + - item-01198 + - item-01199 + - item-01200 + - item-01201 + - item-01202 + - item-01203 + - item-01204 + - item-01205 + - item-01206 + - item-01207 + - item-01208 + - item-01209 + - item-01210 + - item-01211 + - item-01212 + - item-01213 + - item-01214 + - item-01215 + - item-01216 + - item-01217 + - item-01218 + - item-01219 + - item-01220 + - item-01221 + - item-01222 + - item-01223 + - item-01224 + - item-01225 + - item-01226 + - item-01227 + - item-01228 + - item-01229 + - item-01230 + - item-01231 + - item-01232 + - item-01233 + - item-01234 + - item-01235 + - item-01236 + - item-01237 + - item-01238 + - item-01239 + - item-01240 + - item-01241 + - item-01242 + - item-01243 + - item-01244 + - item-01245 + - item-01246 + - item-01247 + - item-01248 + - item-01249 + - item-01250 + - item-01251 + - item-01252 + - item-01253 + - item-01254 + - item-01255 + - item-01256 + - item-01257 + - item-01258 + - item-01259 + - item-01260 + - item-01261 + - item-01262 + - item-01263 + - item-01264 + - item-01265 + - item-01266 + - item-01267 + - item-01268 + - item-01269 + - item-01270 + - item-01271 + - item-01272 + - item-01273 + - item-01274 + - item-01275 + - item-01276 + - item-01277 + - item-01278 + - item-01279 + - item-01280 + - item-01281 + - item-01282 + - item-01283 + - item-01284 + - item-01285 + - item-01286 + - item-01287 + - item-01288 + - item-01289 + - item-01290 + - item-01291 + - item-01292 + - item-01293 + - item-01294 + - item-01295 + - item-01296 + - item-01297 + - item-01298 + - item-01299 + - item-01300 + - item-01301 + - item-01302 + - item-01303 + - item-01304 + - item-01305 + - item-01306 + - item-01307 + - item-01308 + - item-01309 + - item-01310 + - item-01311 + - item-01312 + - item-01313 + - item-01314 + - item-01315 + - item-01316 + - item-01317 + - item-01318 + - item-01319 + - item-01320 + - item-01321 + - item-01322 + - item-01323 + - item-01324 + - item-01325 + - item-01326 + - item-01327 + - item-01328 + - item-01329 + - item-01330 + - item-01331 + - item-01332 + - item-01333 + - item-01334 + - item-01335 + - item-01336 + - item-01337 + - item-01338 + - item-01339 + - item-01340 + - item-01341 + - item-01342 + - item-01343 + - item-01344 + - item-01345 + - item-01346 + - item-01347 + - item-01348 + - item-01349 + - item-01350 + - item-01351 + - item-01352 + - item-01353 + - item-01354 + - item-01355 + - item-01356 + - item-01357 + - item-01358 + - item-01359 + - item-01360 + - item-01361 + - item-01362 + - item-01363 + - item-01364 + - item-01365 + - item-01366 + - item-01367 + - item-01368 + - item-01369 + - item-01370 + - item-01371 + - item-01372 + - item-01373 + - item-01374 + - item-01375 + - item-01376 + - item-01377 + - item-01378 + - item-01379 + - item-01380 + - item-01381 + - item-01382 + - item-01383 + - item-01384 + - item-01385 + - item-01386 + - item-01387 + - item-01388 + - item-01389 + - item-01390 + - item-01391 + - item-01392 + - item-01393 + - item-01394 + - item-01395 + - item-01396 + - item-01397 + - item-01398 + - item-01399 + - item-01400 + - item-01401 + - item-01402 + - item-01403 + - item-01404 + - item-01405 + - item-01406 + - item-01407 + - item-01408 + - item-01409 + - item-01410 + - item-01411 + - item-01412 + - item-01413 + - item-01414 + - item-01415 + - item-01416 + - item-01417 + - item-01418 + - item-01419 + - item-01420 + - item-01421 + - item-01422 + - item-01423 + - item-01424 + - item-01425 + - item-01426 + - item-01427 + - item-01428 + - item-01429 + - item-01430 + - item-01431 + - item-01432 + - item-01433 + - item-01434 + - item-01435 + - item-01436 + - item-01437 + - item-01438 + - item-01439 + - item-01440 + - item-01441 + - item-01442 + - item-01443 + - item-01444 + - item-01445 + - item-01446 + - item-01447 + - item-01448 + - item-01449 + - item-01450 + - item-01451 + - item-01452 + - item-01453 + - item-01454 + - item-01455 + - item-01456 + - item-01457 + - item-01458 + - item-01459 + - item-01460 + - item-01461 + - item-01462 + - item-01463 + - item-01464 + - item-01465 + - item-01466 + - item-01467 + - item-01468 + - item-01469 + - item-01470 + - item-01471 + - item-01472 + - item-01473 + - item-01474 + - item-01475 + - item-01476 + - item-01477 + - item-01478 + - item-01479 + - item-01480 + - item-01481 + - item-01482 + - item-01483 + - item-01484 + - item-01485 + - item-01486 + - item-01487 + - item-01488 + - item-01489 + - item-01490 + - item-01491 + - item-01492 + - item-01493 + - item-01494 + - item-01495 + - item-01496 + - item-01497 + - item-01498 + - item-01499 + - item-01500 + - item-01501 + - item-01502 + - item-01503 + - item-01504 + - item-01505 + - item-01506 + - item-01507 + - item-01508 + - item-01509 + - item-01510 + - item-01511 + - item-01512 + - item-01513 + - item-01514 + - item-01515 + - item-01516 + - item-01517 + - item-01518 + - item-01519 + - item-01520 + - item-01521 + - item-01522 + - item-01523 + - item-01524 + - item-01525 + - item-01526 + - item-01527 + - item-01528 + - item-01529 + - item-01530 + - item-01531 + - item-01532 + - item-01533 + - item-01534 + - item-01535 + - item-01536 + - item-01537 + - item-01538 + - item-01539 + - item-01540 + - item-01541 + - item-01542 + - item-01543 + - item-01544 + - item-01545 + - item-01546 + - item-01547 + - item-01548 + - item-01549 + - item-01550 + - item-01551 + - item-01552 + - item-01553 + - item-01554 + - item-01555 + - item-01556 + - item-01557 + - item-01558 + - item-01559 + - item-01560 + - item-01561 + - item-01562 + - item-01563 + - item-01564 + - item-01565 + - item-01566 + - item-01567 + - item-01568 + - item-01569 + - item-01570 + - item-01571 + - item-01572 + - item-01573 + - item-01574 + - item-01575 + - item-01576 + - item-01577 + - item-01578 + - item-01579 + - item-01580 + - item-01581 + - item-01582 + - item-01583 + - item-01584 + - item-01585 + - item-01586 + - item-01587 + - item-01588 + - item-01589 + - item-01590 + - item-01591 + - item-01592 + - item-01593 + - item-01594 + - item-01595 + - item-01596 + - item-01597 + - item-01598 + - item-01599 + - item-01600 + - item-01601 + - item-01602 + - item-01603 + - item-01604 + - item-01605 + - item-01606 + - item-01607 + - item-01608 + - item-01609 + - item-01610 + - item-01611 + - item-01612 + - item-01613 + - item-01614 + - item-01615 + - item-01616 + - item-01617 + - item-01618 + - item-01619 + - item-01620 + - item-01621 + - item-01622 + - item-01623 + - item-01624 + - item-01625 + - item-01626 + - item-01627 + - item-01628 + - item-01629 + - item-01630 + - item-01631 + - item-01632 + - item-01633 + - item-01634 + - item-01635 + - item-01636 + - item-01637 + - item-01638 + - item-01639 + - item-01640 + - item-01641 + - item-01642 + - item-01643 + - item-01644 + - item-01645 + - item-01646 + - item-01647 + - item-01648 + - item-01649 + - item-01650 + - item-01651 + - item-01652 + - item-01653 + - item-01654 + - item-01655 + - item-01656 + - item-01657 + - item-01658 + - item-01659 + - item-01660 + - item-01661 + - item-01662 + - item-01663 + - item-01664 + - item-01665 + - item-01666 + - item-01667 + - item-01668 + - item-01669 + - item-01670 + - item-01671 + - item-01672 + - item-01673 + - item-01674 + - item-01675 + - item-01676 + - item-01677 + - item-01678 + - item-01679 + - item-01680 + - item-01681 + - item-01682 + - item-01683 + - item-01684 + - item-01685 + - item-01686 + - item-01687 + - item-01688 + - item-01689 + - item-01690 + - item-01691 + - item-01692 + - item-01693 + - item-01694 + - item-01695 + - item-01696 + - item-01697 + - item-01698 + - item-01699 + - item-01700 + - item-01701 + - item-01702 + - item-01703 + - item-01704 + - item-01705 + - item-01706 + - item-01707 + - item-01708 + - item-01709 + - item-01710 + - item-01711 + - item-01712 + - item-01713 + - item-01714 + - item-01715 + - item-01716 + - item-01717 + - item-01718 + - item-01719 + - item-01720 + - item-01721 + - item-01722 + - item-01723 + - item-01724 + - item-01725 + - item-01726 + - item-01727 + - item-01728 + - item-01729 + - item-01730 + - item-01731 + - item-01732 + - item-01733 + - item-01734 + - item-01735 + - item-01736 + - item-01737 + - item-01738 + - item-01739 + - item-01740 + - item-01741 + - item-01742 + - item-01743 + - item-01744 + - item-01745 + - item-01746 + - item-01747 + - item-01748 + - item-01749 + - item-01750 + - item-01751 + - item-01752 + - item-01753 + - item-01754 + - item-01755 + - item-01756 + - item-01757 + - item-01758 + - item-01759 + - item-01760 + - item-01761 + - item-01762 + - item-01763 + - item-01764 + - item-01765 + - item-01766 + - item-01767 + - item-01768 + - item-01769 + - item-01770 + - item-01771 + - item-01772 + - item-01773 + - item-01774 + - item-01775 + - item-01776 + - item-01777 + - item-01778 + - item-01779 + - item-01780 + - item-01781 + - item-01782 + - item-01783 + - item-01784 + - item-01785 + - item-01786 + - item-01787 + - item-01788 + - item-01789 + - item-01790 + - item-01791 + - item-01792 + - item-01793 + - item-01794 + - item-01795 + - item-01796 + - item-01797 + - item-01798 + - item-01799 + - item-01800 + - item-01801 + - item-01802 + - item-01803 + - item-01804 + - item-01805 + - item-01806 + - item-01807 + - item-01808 + - item-01809 + - item-01810 + - item-01811 + - item-01812 + - item-01813 + - item-01814 + - item-01815 + - item-01816 + - item-01817 + - item-01818 + - item-01819 + - item-01820 + - item-01821 + - item-01822 + - item-01823 + - item-01824 + - item-01825 + - item-01826 + - item-01827 + - item-01828 + - item-01829 + - item-01830 + - item-01831 + - item-01832 + - item-01833 + - item-01834 + - item-01835 + - item-01836 + - item-01837 + - item-01838 + - item-01839 + - item-01840 + - item-01841 + - item-01842 + - item-01843 + - item-01844 + - item-01845 + - item-01846 + - item-01847 + - item-01848 + - item-01849 + - item-01850 + - item-01851 + - item-01852 + - item-01853 + - item-01854 + - item-01855 + - item-01856 + - item-01857 + - item-01858 + - item-01859 + - item-01860 + - item-01861 + - item-01862 + - item-01863 + - item-01864 + - item-01865 + - item-01866 + - item-01867 + - item-01868 + - item-01869 + - item-01870 + - item-01871 + - item-01872 + - item-01873 + - item-01874 + - item-01875 + - item-01876 + - item-01877 + - item-01878 + - item-01879 + - item-01880 + - item-01881 + - item-01882 + - item-01883 + - item-01884 + - item-01885 + - item-01886 + - item-01887 + - item-01888 + - item-01889 + - item-01890 + - item-01891 + - item-01892 + - item-01893 + - item-01894 + - item-01895 + - item-01896 + - item-01897 + - item-01898 + - item-01899 + - item-01900 + - item-01901 + - item-01902 + - item-01903 + - item-01904 + - item-01905 + - item-01906 + - item-01907 + - item-01908 + - item-01909 + - item-01910 + - item-01911 + - item-01912 + - item-01913 + - item-01914 + - item-01915 + - item-01916 + - item-01917 + - item-01918 + - item-01919 + - item-01920 + - item-01921 + - item-01922 + - item-01923 + - item-01924 + - item-01925 + - item-01926 + - item-01927 + - item-01928 + - item-01929 + - item-01930 + - item-01931 + - item-01932 + - item-01933 + - item-01934 + - item-01935 + - item-01936 + - item-01937 + - item-01938 + - item-01939 + - item-01940 + - item-01941 + - item-01942 + - item-01943 + - item-01944 + - item-01945 + - item-01946 + - item-01947 + - item-01948 + - item-01949 + - item-01950 + - item-01951 + - item-01952 + - item-01953 + - item-01954 + - item-01955 + - item-01956 + - item-01957 + - item-01958 + - item-01959 + - item-01960 + - item-01961 + - item-01962 + - item-01963 + - item-01964 + - item-01965 + - item-01966 + - item-01967 + - item-01968 + - item-01969 + - item-01970 + - item-01971 + - item-01972 + - item-01973 + - item-01974 + - item-01975 + - item-01976 + - item-01977 + - item-01978 + - item-01979 + - item-01980 + - item-01981 + - item-01982 + - item-01983 + - item-01984 + - item-01985 + - item-01986 + - item-01987 + - item-01988 + - item-01989 + - item-01990 + - item-01991 + - item-01992 + - item-01993 + - item-01994 + - item-01995 + - item-01996 + - item-01997 + - item-01998 + - item-01999 +--- + +Body. diff --git a/tests/fixtures/adversarial/frontmatter/null-byte-value.md b/tests/fixtures/adversarial/frontmatter/null-byte-value.md new file mode 100644 index 000000000..ab78500ea Binary files /dev/null and b/tests/fixtures/adversarial/frontmatter/null-byte-value.md differ diff --git a/tests/fixtures/adversarial/frontmatter/unclosed-block.md b/tests/fixtures/adversarial/frontmatter/unclosed-block.md new file mode 100644 index 000000000..83f268032 --- /dev/null +++ b/tests/fixtures/adversarial/frontmatter/unclosed-block.md @@ -0,0 +1,6 @@ +--- +title: Unclosed Block +phase: 03 + +This frontmatter has no closing --- line, and the body starts here without +a delimiter. Parser must NOT consume body lines as frontmatter keys. diff --git a/tests/fixtures/adversarial/frontmatter/unicode-keys-and-values.md b/tests/fixtures/adversarial/frontmatter/unicode-keys-and-values.md new file mode 100644 index 000000000..a492e168b --- /dev/null +++ b/tests/fixtures/adversarial/frontmatter/unicode-keys-and-values.md @@ -0,0 +1,8 @@ +--- +title: 日本語のタイトル +相: 04 +status: 🚧 in-flight +tags: [α, β, γ] +--- + +Body. diff --git a/tests/fixtures/adversarial/roadmap/README.md b/tests/fixtures/adversarial/roadmap/README.md new file mode 100644 index 000000000..857dd0581 --- /dev/null +++ b/tests/fixtures/adversarial/roadmap/README.md @@ -0,0 +1,28 @@ +# Adversarial Roadmap Fixtures (#3594) + +Hostile / messy ROADMAP.md inputs for +`get-shit-done/bin/lib/roadmap.cjs` (`searchPhaseInContent`, +`cmdRoadmapGetPhase`, `cmdRoadmapAnalyze`) and the SDK roadmap parser +in `sdk/src/query/roadmap.ts`. + +Tests in `tests/feat-3594-parser-adversarial-roadmap.test.cjs` and +`tests/feat-3594-parser-property-style.test.cjs` consume these. + +Categories: + +- `phase-heading-inside-fenced-code.md` — a ``` md ``` block contains + a `## Phase 999: fake` heading. The parser MUST ignore headings + inside fenced code blocks. Historical regression #2787. +- `nested-fenced-code.md` — outer ``` ``` ``` ``` ` block with inner + ``` ``` ``` ``` ` block. Headings inside either layer must be + ignored. +- `unicode-phase-titles.md` — phase titles with non-ASCII characters. + Parser must preserve them in the returned `phase_name`. +- `repeated-phase-ids.md` — same integer phase number listed twice. + Parser behavior must be deterministic (first-wins or last-wins is + fine; the test pins whichever). +- `decimal-phase-mixed.md` — integer phase 2 and decimal phase 2.1 + share a prefix. Parser must not return phase 2 when asked for 2.1 + (or vice versa). Historical regression #3537. +- `markdown-headings-inside-html-comment.md` — ` ` patterns. Parser must not be fooled by comments. diff --git a/tests/fixtures/adversarial/roadmap/decimal-phase-mixed.md b/tests/fixtures/adversarial/roadmap/decimal-phase-mixed.md new file mode 100644 index 000000000..940f1adb8 --- /dev/null +++ b/tests/fixtures/adversarial/roadmap/decimal-phase-mixed.md @@ -0,0 +1,21 @@ +# Roadmap + +## Phase 2: integer phase two + +**Goal:** the integer parent. +**Requirements:** [R2] + +## Phase 2.1: decimal child of phase two + +**Goal:** decimal subphase. Must not be returned when caller asks for phase 2. +**Requirements:** [R2.1] + +## Phase 2.10: decimal phase 2.10 + +**Goal:** prefix-collision guard — "2.10" must NOT be matched by a "2.1" lookup. +**Requirements:** [R2.10] + +## Phase 21: phase twenty-one + +**Goal:** prefix-collision guard — "21" must NOT be matched by a "2" lookup. +**Requirements:** [R21] diff --git a/tests/fixtures/adversarial/roadmap/markdown-headings-inside-html-comment.md b/tests/fixtures/adversarial/roadmap/markdown-headings-inside-html-comment.md new file mode 100644 index 000000000..8582a23f9 --- /dev/null +++ b/tests/fixtures/adversarial/roadmap/markdown-headings-inside-html-comment.md @@ -0,0 +1,14 @@ +# Roadmap + + + +## Phase 1: real phase + +**Goal:** the only real phase. +**Requirements:** [R1] + + diff --git a/tests/fixtures/adversarial/roadmap/nested-fenced-code.md b/tests/fixtures/adversarial/roadmap/nested-fenced-code.md new file mode 100644 index 000000000..a079ac582 --- /dev/null +++ b/tests/fixtures/adversarial/roadmap/nested-fenced-code.md @@ -0,0 +1,20 @@ +# Roadmap + +````md +Outer block opened with four backticks. + +```md +## Phase 998: nested fake phase + +Inner block opened with three backticks. +``` + +## Phase 997: outer fake phase + +Still inside the outer four-tick block — the three-tick close above +does NOT terminate it. +```` + +## Phase 1: real phase + +**Goal:** the only real phase in this file. diff --git a/tests/fixtures/adversarial/roadmap/phase-heading-inside-fenced-code.md b/tests/fixtures/adversarial/roadmap/phase-heading-inside-fenced-code.md new file mode 100644 index 000000000..ab6588744 --- /dev/null +++ b/tests/fixtures/adversarial/roadmap/phase-heading-inside-fenced-code.md @@ -0,0 +1,18 @@ +# Roadmap + +Some intro prose. + +```md +## Phase 999: this heading is inside a fenced code block + +It must NOT be parsed as a real phase. Historical regression #2787. +``` + +## Phase 1: real phase one + +**Goal:** Ship something. +**Requirements:** [R1] + +## Phase 2: real phase two + +**Goal:** Ship something else. diff --git a/tests/fixtures/adversarial/roadmap/repeated-phase-ids.md b/tests/fixtures/adversarial/roadmap/repeated-phase-ids.md new file mode 100644 index 000000000..1404f57ce --- /dev/null +++ b/tests/fixtures/adversarial/roadmap/repeated-phase-ids.md @@ -0,0 +1,16 @@ +# Roadmap + +## Phase 1: first declaration of phase 1 + +**Goal:** the original. +**Requirements:** [R1] + +## Phase 2: phase two + +**Goal:** routine. +**Requirements:** [R2] + +## Phase 1: duplicate declaration of phase 1 + +**Goal:** this should not silently shadow the first one. +**Requirements:** [R3] diff --git a/tests/fixtures/adversarial/roadmap/unicode-phase-titles.md b/tests/fixtures/adversarial/roadmap/unicode-phase-titles.md new file mode 100644 index 000000000..479872468 --- /dev/null +++ b/tests/fixtures/adversarial/roadmap/unicode-phase-titles.md @@ -0,0 +1,15 @@ +# Roadmap + +## Phase 1: 日本語フェーズ — initial setup + +**Goal:** ensure non-ASCII titles round-trip. +**Requirements:** [R1] + +## Phase 2: 🚧 Émile's "phase" with smart quotes + +**Goal:** mixed Unicode + ASCII + smart quotes survive parsing. +**Requirements:** [R2] + +## Phase 3: αβγ δεζ ηθι + +**Goal:** Greek letters in the title.