@willbooster/shared-lib 10.24.0 → 11.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/githubTemplates.cjs +1 -1
- package/dist/githubTemplates.cjs.map +1 -1
- package/dist/githubTemplates.d.ts +1 -1
- package/dist/githubTemplates.js +1 -1
- package/dist/githubTemplates.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/markdown.cjs +5 -6
- package/dist/markdown.cjs.map +1 -1
- package/dist/markdown.d.ts +3 -11
- package/dist/markdown.js +5 -6
- package/dist/markdown.js.map +1 -1
- package/dist/text.cjs +6 -1
- package/dist/text.cjs.map +1 -1
- package/dist/text.d.ts +15 -0
- package/dist/text.js +6 -1
- package/dist/text.js.map +1 -1
- package/package.json +5 -5
package/dist/githubTemplates.cjs
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
- Start the body with \`Close #<n>\` only when the PR resolves an existing issue.\n${t}`,r=[{heading:`Problem`,requirement:`what happens, and what should happen instead`},{heading:`Evidence`,requirement:`reproduction steps, logs, run IDs, or links`},{heading:`Impact`,requirement:`who or what is affected, and how badly`},{heading:`Proposal`,requirement:`the fix you have in mind, and the root cause if known`}],i=[{heading:`Background and goal`,requirement:`the problem being solved, for whom, and what outcome counts as success`},{heading:`Behavior`,requirement:`every user- or caller-observable behavior, including inputs, outputs, error cases, edge cases, and interactions with existing behavior, stated precisely enough that two implementers would build the same thing`},{heading:`Acceptance criteria`,requirement:`checkable conditions (Given/When/Then or an equivalent) that decide whether the implementation is done`},{heading:`Scope and non-goals`,requirement:`what the change covers and what it deliberately leaves out, so an implementer knows where to stop`},{heading:`Design constraints`,requirement:`the data, API, schema, UI, and configuration changes, naming, and the existing code or conventions the implementation must fit — as far as the writer intends to constrain them, with everything else explicitly left to the implementer`},{heading:`Compatibility and migration`,requirement:`what existing data, users, callers, or configurations are affected and how the transition happens`},{heading:`Verification`,requirement:`how the change is tested or demonstrated, including what needs no test and why`},{heading:`Open questions`,requirement:`undecided points, each with its options; empty once the specification is implementable`}];function a(e){return e.map(e=>`- **${e.heading}**: ${e.requirement}`).join(`
|
|
3
3
|
`)}function o(e){return e.map(e=>`## ${e.heading}\n\n<!-- ${e.requirement.charAt(0).toUpperCase()}${e.requirement.slice(1)}. -->`).join(`
|
|
4
4
|
|
|
5
|
-
`)}exports.BUG_ISSUE_SECTIONS=r,exports.CHANGE_ISSUE_SECTIONS=i,exports.ISSUE_TEMPLATE_RULES="- Follow the closest template under `.github/ISSUE_TEMPLATE/`: `bug.md` for wrong behavior, `change.md` for anything to build or alter; a question or note fitting neither, or a repository without templates, needs no template.\n- Title: a Conventional Commits prefix for the type that fits the change (`feat:`, `fix:`, `refactor:`, `docs:`, `chore:`, ...)
|
|
5
|
+
`)}exports.BUG_ISSUE_SECTIONS=r,exports.CHANGE_ISSUE_SECTIONS=i,exports.ISSUE_TEMPLATE_RULES="- Follow the closest template under `.github/ISSUE_TEMPLATE/`: `bug.md` for wrong behavior, `change.md` for anything to build or alter; a question or note fitting neither, or a repository without templates, needs no template.\n- Title: a Conventional Commits prefix for the type that fits the change (`feat:`, `fix:`, `refactor:`, `docs:`, `chore:`, ...).\n- The YAML front matter between the `---` lines is metadata, not body text: submit only the content below the closing `---` as the body.",exports.PULL_REQUEST_BODY_RULES=n,exports.PULL_REQUEST_REQUIREMENTS_RULES=t,exports.PULL_REQUEST_SECTIONS=e,exports.renderSectionChecklist=a,exports.renderSectionTemplate=o;
|
|
6
6
|
//# sourceMappingURL=githubTemplates.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"githubTemplates.cjs","names":["PULL_REQUEST_SECTIONS","PULL_REQUEST_REQUIREMENTS_RULES","PULL_REQUEST_BODY_RULES","BUG_ISSUE_SECTIONS","CHANGE_ISSUE_SECTIONS","renderSectionChecklist","sections","section","renderSectionTemplate"],"sources":["../src/githubTemplates.ts"],"sourcesContent":["/**\n * A section of a PR or issue body: its heading and what the section must state. `requirement`\n * starts lowercase and ends without punctuation, so that it reads both as a placeholder sentence\n * (capitalized, with a period appended) and as a checklist clause after the heading.\n */\nexport interface TemplateSection {\n readonly heading: string;\n readonly requirement: string;\n}\n\n/** Sections of a pull request body, in the order they appear in the PR template. */\nexport const PULL_REQUEST_SECTIONS: readonly TemplateSection[] = [\n {\n heading: 'Why',\n requirement: 'the problem, with the numbers or observations behind it, and why this approach over the alternatives',\n },\n {\n heading: 'Requirements',\n requirement:\n \"the requirements the change must satisfy and the guarantees it must keep as they stand now, one per line, each marked `required` (asked for by the requester, or an existing contract callers depend on) or `chosen` (the implementer's own decision with the reason that decided it, which a simpler design may replace); state the requester's instructions in the requester's terms, whether they came from an issue or a conversation, and keep each line as it is until the requester overrides or withdraws the instruction behind it or the decision behind it changes, and then rewrite or remove that line instead of adding one beside it\",\n },\n {\n heading: 'Customer Summary',\n requirement:\n 'behavior, workflow, or user-visible changes, written for readers who know nothing about the implementation',\n },\n {\n heading: 'Technical Summary',\n requirement:\n 'decisions and their reasons, data flow, where to look (the files that matter most), and what to check hardest: the parts whose correctness is argued rather than proven',\n },\n {\n heading: 'Testing',\n requirement: 'commands run, tests added, and what was NOT exercised and why',\n },\n {\n heading: 'Notes',\n requirement: 'known limitations, compatibility and migration, follow-up work; delete this section if there is none',\n },\n];\n\n/**\n * How a tool that writes or rewrites a PR body derives it from the repository's PR template, as\n * markdown bullets for an agent's instructions. Stated once here and rendered by every writer (the\n * generated agent instructions, the PR-drafting skills) so the writers cannot drift apart; the\n * template-less branch lets the same text serve repositories outside the organization.\n */\nconst PULL_REQUEST_TEMPLATE_RULES = `- Base the PR body on \\`.github/pull_request_template.md\\` when creating or updating a PR, even when a skill or workflow supplies its own skeleton: keep the template's headings in order, fill each section with what its placeholder comment asks for at a length fitting the change (a sentence for a small change, numbered subsections for a large one), and delete the placeholder comments and an empty Notes section. Without a template, use no fixed headings: state the scope, the motivation, and the verification concisely.\n- Start the body with \\`Close #<n>\\` only when the PR resolves an existing issue.`;\n\n/**\n * How a tool that writes or rewrites a PR body fills the Requirements section of\n * `PULL_REQUEST_SECTIONS`, as markdown bullets for an agent's instructions. Skills that draft PR\n * bodies render this text instead of restating it, so the writers and the reviewers that read the\n * section as the boundary a fix must keep never disagree on what a line means.\n */\nexport const PULL_REQUEST_REQUIREMENTS_RULES = `- Requirements section: the requirements in force now, one line per requirement, taken from the requester's instructions in the requester's terms (the issue, the conversation that asked for the change), each marked \\`required\\` (asked for, or an existing contract callers depend on) or \\`chosen\\` (your own decision with the reason that decided it, which a simpler design may replace). Never infer a requirement from the diff: when no issue, PR message, or conversation states the request, write \\`required: not recorded — ask the requester\\` as the only request-derived line (\\`required\\` lines for existing contracts may still be listed) and say so in Notes.\n- When updating an existing section, keep each line until the requester overrides or withdraws the instruction behind it or the decision behind it changes, then rewrite or remove that line in place, never appending one beside it or marking when a line was added (e.g., \"(added)\"); with no instructions available, keep the requester-derived lines unchanged. When the current design cannot be understood without the change of direction, state it in one sentence in Notes. Write afresh by the rule above a section that merely describes the implementation or holds only the template's placeholder or a \\`required: not recorded\\` line.\n- Place the section where the template puts it; when the template has no such heading, right after the issue-closing line, or at the top of the body when there is none.\n- Never drop or weaken a \\`required\\` line to fit what was implemented: when one cannot hold in the diff, keep it and say so in Notes.`;\n\n/** The complete rules for writing a PR body: the template rules followed by the Requirements rules. */\nexport const PULL_REQUEST_BODY_RULES = `${PULL_REQUEST_TEMPLATE_RULES}\\n${PULL_REQUEST_REQUIREMENTS_RULES}`;\n\n/**\n * How a tool that creates an issue derives it from the repository's issue templates, as markdown\n * bullets for an agent's instructions; shared by the same writers as `PULL_REQUEST_BODY_RULES`.\n * Guidance the templates state in their own placeholder comments (which sections to keep, deleting\n * the comments) is deliberately not repeated here, since the writer reads the template anyway.\n */\nexport const ISSUE_TEMPLATE_RULES = `- Follow the closest template under \\`.github/ISSUE_TEMPLATE/\\`: \\`bug.md\\` for wrong behavior, \\`change.md\\` for anything to build or alter; a question or note fitting neither, or a repository without templates, needs no template.\n- Title: a Conventional Commits prefix for the type that fits the change (\\`feat:\\`, \\`fix:\\`, \\`refactor:\\`, \\`docs:\\`, \\`chore:\\`, ...); when it differs from the template's \\`title\\` prefix, replace the template's type label (\\`t: ...\\`) with the one matching the type.\n- The YAML front matter between the \\`---\\` lines is metadata, not body text: pass its \\`labels\\` via \\`--label\\` and submit only the content below the closing \\`---\\` as the body.`;\n\n/** Sections of a bug report, in the order they appear in the issue template. */\nexport const BUG_ISSUE_SECTIONS: readonly TemplateSection[] = [\n { heading: 'Problem', requirement: 'what happens, and what should happen instead' },\n { heading: 'Evidence', requirement: 'reproduction steps, logs, run IDs, or links' },\n { heading: 'Impact', requirement: 'who or what is affected, and how badly' },\n { heading: 'Proposal', requirement: 'the fix you have in mind, and the root cause if known' },\n];\n\n/**\n * Sections a change specification must settle before a third party can implement it, defined\n * once so that the issue template and any tool that drafts or reviews specifications against\n * these sections cannot drift apart; checklist consumers render it with `renderSectionChecklist`.\n * The first three sections suffice for a small change, so they come first.\n */\nexport const CHANGE_ISSUE_SECTIONS: readonly TemplateSection[] = [\n {\n heading: 'Background and goal',\n requirement: 'the problem being solved, for whom, and what outcome counts as success',\n },\n {\n heading: 'Behavior',\n requirement:\n 'every user- or caller-observable behavior, including inputs, outputs, error cases, edge cases, and interactions with existing behavior, stated precisely enough that two implementers would build the same thing',\n },\n {\n heading: 'Acceptance criteria',\n requirement:\n 'checkable conditions (Given/When/Then or an equivalent) that decide whether the implementation is done',\n },\n {\n heading: 'Scope and non-goals',\n requirement: 'what the change covers and what it deliberately leaves out, so an implementer knows where to stop',\n },\n {\n heading: 'Design constraints',\n requirement:\n 'the data, API, schema, UI, and configuration changes, naming, and the existing code or conventions the implementation must fit — as far as the writer intends to constrain them, with everything else explicitly left to the implementer',\n },\n {\n heading: 'Compatibility and migration',\n requirement: 'what existing data, users, callers, or configurations are affected and how the transition happens',\n },\n {\n heading: 'Verification',\n requirement: 'how the change is tested or demonstrated, including what needs no test and why',\n },\n {\n heading: 'Open questions',\n requirement: 'undecided points, each with its options; empty once the specification is implementable',\n },\n];\n\n/** Renders sections as markdown bullets (`- **Heading**: requirement`). */\nexport function renderSectionChecklist(sections: readonly TemplateSection[]): string {\n return sections.map((section) => `- **${section.heading}**: ${section.requirement}`).join('\\n');\n}\n\n/** Renders sections as markdown headings, each followed by its requirement as a placeholder comment. */\nexport function renderSectionTemplate(sections: readonly TemplateSection[]): string {\n return sections\n .map(\n (section) =>\n `## ${section.heading}\\n\\n<!-- ${section.requirement.charAt(0).toUpperCase()}${section.requirement.slice(1)}. -->`\n )\n .join('\\n\\n');\n}\n"],"mappings":"A,aAWA,MAAaA,EAAoD,CAC/D,CACE,QAAS,MACT,YAAa,sGACf,EACA,CACE,QAAS,eACT,YACE,qnBACJ,EACA,CACE,QAAS,mBACT,YACE,4GACJ,EACA,CACE,QAAS,oBACT,YACE,yKACJ,EACA,CACE,QAAS,UACT,YAAa,+DACf,EACA,CACE,QAAS,QACT,YAAa,sGACf,CACF,EAiBaC,EAAkC,ujDAMlCC,EAA0B;qFAAmCD,IAa7DE,EAAiD,CAC5D,CAAE,QAAS,UAAW,YAAa,8CAA+C,EAClF,CAAE,QAAS,WAAY,YAAa,6CAA8C,EAClF,CAAE,QAAS,SAAU,YAAa,wCAAyC,EAC3E,CAAE,QAAS,WAAY,YAAa,uDAAwD,CAC9F,EAQaC,EAAoD,CAC/D,CACE,QAAS,sBACT,YAAa,wEACf,EACA,CACE,QAAS,WACT,YACE,kNACJ,EACA,CACE,QAAS,sBACT,YACE,wGACJ,EACA,CACE,QAAS,sBACT,YAAa,mGACf,EACA,CACE,QAAS,qBACT,YACE,0OACJ,EACA,CACE,QAAS,8BACT,YAAa,mGACf,EACA,CACE,QAAS,eACT,YAAa,gFACf,EACA,CACE,QAAS,iBACT,YAAa,wFACf,CACF,EAGA,SAAgBC,EAAuB,EAA8C,CACnF,OAAOC,EAAS,IAAK,GAAY,OAAOC,EAAQ,QAAQ,MAAMA,EAAQ,aAAa,CAAC,CAAC,KAAK;CAAI,CAChG,CAGA,SAAgBC,EAAsB,EAA8C,CAClF,OAAOF,EACJ,IACE,GACC,MAAMC,EAAQ,QAAQ,WAAWA,EAAQ,YAAY,OAAO,CAAC,CAAC,CAAC,YAAY,IAAIA,EAAQ,YAAY,MAAM,CAAC,EAAE,MAChH,CAAC,CACA,KAAK;;CAAM,CAChB,C"}
|
|
1
|
+
{"version":3,"file":"githubTemplates.cjs","names":["PULL_REQUEST_SECTIONS","PULL_REQUEST_REQUIREMENTS_RULES","PULL_REQUEST_BODY_RULES","BUG_ISSUE_SECTIONS","CHANGE_ISSUE_SECTIONS","renderSectionChecklist","sections","section","renderSectionTemplate"],"sources":["../src/githubTemplates.ts"],"sourcesContent":["/**\n * A section of a PR or issue body: its heading and what the section must state. `requirement`\n * starts lowercase and ends without punctuation, so that it reads both as a placeholder sentence\n * (capitalized, with a period appended) and as a checklist clause after the heading.\n */\nexport interface TemplateSection {\n readonly heading: string;\n readonly requirement: string;\n}\n\n/** Sections of a pull request body, in the order they appear in the PR template. */\nexport const PULL_REQUEST_SECTIONS: readonly TemplateSection[] = [\n {\n heading: 'Why',\n requirement: 'the problem, with the numbers or observations behind it, and why this approach over the alternatives',\n },\n {\n heading: 'Requirements',\n requirement:\n \"the requirements the change must satisfy and the guarantees it must keep as they stand now, one per line, each marked `required` (asked for by the requester, or an existing contract callers depend on) or `chosen` (the implementer's own decision with the reason that decided it, which a simpler design may replace); state the requester's instructions in the requester's terms, whether they came from an issue or a conversation, and keep each line as it is until the requester overrides or withdraws the instruction behind it or the decision behind it changes, and then rewrite or remove that line instead of adding one beside it\",\n },\n {\n heading: 'Customer Summary',\n requirement:\n 'behavior, workflow, or user-visible changes, written for readers who know nothing about the implementation',\n },\n {\n heading: 'Technical Summary',\n requirement:\n 'decisions and their reasons, data flow, where to look (the files that matter most), and what to check hardest: the parts whose correctness is argued rather than proven',\n },\n {\n heading: 'Testing',\n requirement: 'commands run, tests added, and what was NOT exercised and why',\n },\n {\n heading: 'Notes',\n requirement: 'known limitations, compatibility and migration, follow-up work; delete this section if there is none',\n },\n];\n\n/**\n * How a tool that writes or rewrites a PR body derives it from the repository's PR template, as\n * markdown bullets for an agent's instructions. Stated once here and rendered by every writer (the\n * generated agent instructions, the PR-drafting skills) so the writers cannot drift apart; the\n * template-less branch lets the same text serve repositories outside the organization.\n */\nconst PULL_REQUEST_TEMPLATE_RULES = `- Base the PR body on \\`.github/pull_request_template.md\\` when creating or updating a PR, even when a skill or workflow supplies its own skeleton: keep the template's headings in order, fill each section with what its placeholder comment asks for at a length fitting the change (a sentence for a small change, numbered subsections for a large one), and delete the placeholder comments and an empty Notes section. Without a template, use no fixed headings: state the scope, the motivation, and the verification concisely.\n- Start the body with \\`Close #<n>\\` only when the PR resolves an existing issue.`;\n\n/**\n * How a tool that writes or rewrites a PR body fills the Requirements section of\n * `PULL_REQUEST_SECTIONS`, as markdown bullets for an agent's instructions. Skills that draft PR\n * bodies render this text instead of restating it, so the writers and the reviewers that read the\n * section as the boundary a fix must keep never disagree on what a line means.\n */\nexport const PULL_REQUEST_REQUIREMENTS_RULES = `- Requirements section: the requirements in force now, one line per requirement, taken from the requester's instructions in the requester's terms (the issue, the conversation that asked for the change), each marked \\`required\\` (asked for, or an existing contract callers depend on) or \\`chosen\\` (your own decision with the reason that decided it, which a simpler design may replace). Never infer a requirement from the diff: when no issue, PR message, or conversation states the request, write \\`required: not recorded — ask the requester\\` as the only request-derived line (\\`required\\` lines for existing contracts may still be listed) and say so in Notes.\n- When updating an existing section, keep each line until the requester overrides or withdraws the instruction behind it or the decision behind it changes, then rewrite or remove that line in place, never appending one beside it or marking when a line was added (e.g., \"(added)\"); with no instructions available, keep the requester-derived lines unchanged. When the current design cannot be understood without the change of direction, state it in one sentence in Notes. Write afresh by the rule above a section that merely describes the implementation or holds only the template's placeholder or a \\`required: not recorded\\` line.\n- Place the section where the template puts it; when the template has no such heading, right after the issue-closing line, or at the top of the body when there is none.\n- Never drop or weaken a \\`required\\` line to fit what was implemented: when one cannot hold in the diff, keep it and say so in Notes.`;\n\n/** The complete rules for writing a PR body: the template rules followed by the Requirements rules. */\nexport const PULL_REQUEST_BODY_RULES = `${PULL_REQUEST_TEMPLATE_RULES}\\n${PULL_REQUEST_REQUIREMENTS_RULES}`;\n\n/**\n * How a tool that creates an issue derives it from the repository's issue templates, as markdown\n * bullets for an agent's instructions; shared by the same writers as `PULL_REQUEST_BODY_RULES`.\n * Guidance the templates state in their own placeholder comments (which sections to keep, deleting\n * the comments) is deliberately not repeated here, since the writer reads the template anyway.\n */\nexport const ISSUE_TEMPLATE_RULES = `- Follow the closest template under \\`.github/ISSUE_TEMPLATE/\\`: \\`bug.md\\` for wrong behavior, \\`change.md\\` for anything to build or alter; a question or note fitting neither, or a repository without templates, needs no template.\n- Title: a Conventional Commits prefix for the type that fits the change (\\`feat:\\`, \\`fix:\\`, \\`refactor:\\`, \\`docs:\\`, \\`chore:\\`, ...).\n- The YAML front matter between the \\`---\\` lines is metadata, not body text: submit only the content below the closing \\`---\\` as the body.`;\n\n/** Sections of a bug report, in the order they appear in the issue template. */\nexport const BUG_ISSUE_SECTIONS: readonly TemplateSection[] = [\n { heading: 'Problem', requirement: 'what happens, and what should happen instead' },\n { heading: 'Evidence', requirement: 'reproduction steps, logs, run IDs, or links' },\n { heading: 'Impact', requirement: 'who or what is affected, and how badly' },\n { heading: 'Proposal', requirement: 'the fix you have in mind, and the root cause if known' },\n];\n\n/**\n * Sections a change specification must settle before a third party can implement it, defined\n * once so that the issue template and any tool that drafts or reviews specifications against\n * these sections cannot drift apart; checklist consumers render it with `renderSectionChecklist`.\n * The first three sections suffice for a small change, so they come first.\n */\nexport const CHANGE_ISSUE_SECTIONS: readonly TemplateSection[] = [\n {\n heading: 'Background and goal',\n requirement: 'the problem being solved, for whom, and what outcome counts as success',\n },\n {\n heading: 'Behavior',\n requirement:\n 'every user- or caller-observable behavior, including inputs, outputs, error cases, edge cases, and interactions with existing behavior, stated precisely enough that two implementers would build the same thing',\n },\n {\n heading: 'Acceptance criteria',\n requirement:\n 'checkable conditions (Given/When/Then or an equivalent) that decide whether the implementation is done',\n },\n {\n heading: 'Scope and non-goals',\n requirement: 'what the change covers and what it deliberately leaves out, so an implementer knows where to stop',\n },\n {\n heading: 'Design constraints',\n requirement:\n 'the data, API, schema, UI, and configuration changes, naming, and the existing code or conventions the implementation must fit — as far as the writer intends to constrain them, with everything else explicitly left to the implementer',\n },\n {\n heading: 'Compatibility and migration',\n requirement: 'what existing data, users, callers, or configurations are affected and how the transition happens',\n },\n {\n heading: 'Verification',\n requirement: 'how the change is tested or demonstrated, including what needs no test and why',\n },\n {\n heading: 'Open questions',\n requirement: 'undecided points, each with its options; empty once the specification is implementable',\n },\n];\n\n/** Renders sections as markdown bullets (`- **Heading**: requirement`). */\nexport function renderSectionChecklist(sections: readonly TemplateSection[]): string {\n return sections.map((section) => `- **${section.heading}**: ${section.requirement}`).join('\\n');\n}\n\n/** Renders sections as markdown headings, each followed by its requirement as a placeholder comment. */\nexport function renderSectionTemplate(sections: readonly TemplateSection[]): string {\n return sections\n .map(\n (section) =>\n `## ${section.heading}\\n\\n<!-- ${section.requirement.charAt(0).toUpperCase()}${section.requirement.slice(1)}. -->`\n )\n .join('\\n\\n');\n}\n"],"mappings":"A,aAWA,MAAaA,EAAoD,CAC/D,CACE,QAAS,MACT,YAAa,sGACf,EACA,CACE,QAAS,eACT,YACE,qnBACJ,EACA,CACE,QAAS,mBACT,YACE,4GACJ,EACA,CACE,QAAS,oBACT,YACE,yKACJ,EACA,CACE,QAAS,UACT,YAAa,+DACf,EACA,CACE,QAAS,QACT,YAAa,sGACf,CACF,EAiBaC,EAAkC,ujDAMlCC,EAA0B;qFAAmCD,IAa7DE,EAAiD,CAC5D,CAAE,QAAS,UAAW,YAAa,8CAA+C,EAClF,CAAE,QAAS,WAAY,YAAa,6CAA8C,EAClF,CAAE,QAAS,SAAU,YAAa,wCAAyC,EAC3E,CAAE,QAAS,WAAY,YAAa,uDAAwD,CAC9F,EAQaC,EAAoD,CAC/D,CACE,QAAS,sBACT,YAAa,wEACf,EACA,CACE,QAAS,WACT,YACE,kNACJ,EACA,CACE,QAAS,sBACT,YACE,wGACJ,EACA,CACE,QAAS,sBACT,YAAa,mGACf,EACA,CACE,QAAS,qBACT,YACE,0OACJ,EACA,CACE,QAAS,8BACT,YAAa,mGACf,EACA,CACE,QAAS,eACT,YAAa,gFACf,EACA,CACE,QAAS,iBACT,YAAa,wFACf,CACF,EAGA,SAAgBC,EAAuB,EAA8C,CACnF,OAAOC,EAAS,IAAK,GAAY,OAAOC,EAAQ,QAAQ,MAAMA,EAAQ,aAAa,CAAC,CAAC,KAAK;CAAI,CAChG,CAGA,SAAgBC,EAAsB,EAA8C,CAClF,OAAOF,EACJ,IACE,GACC,MAAMC,EAAQ,QAAQ,WAAWA,EAAQ,YAAY,OAAO,CAAC,CAAC,CAAC,YAAY,IAAIA,EAAQ,YAAY,MAAM,CAAC,EAAE,MAChH,CAAC,CACA,KAAK;;CAAM,CAChB,C"}
|
|
@@ -24,7 +24,7 @@ export declare const PULL_REQUEST_BODY_RULES = "- Base the PR body on `.github/p
|
|
|
24
24
|
* Guidance the templates state in their own placeholder comments (which sections to keep, deleting
|
|
25
25
|
* the comments) is deliberately not repeated here, since the writer reads the template anyway.
|
|
26
26
|
*/
|
|
27
|
-
export declare const ISSUE_TEMPLATE_RULES = "- Follow the closest template under `.github/ISSUE_TEMPLATE/`: `bug.md` for wrong behavior, `change.md` for anything to build or alter; a question or note fitting neither, or a repository without templates, needs no template.\n- Title: a Conventional Commits prefix for the type that fits the change (`feat:`, `fix:`, `refactor:`, `docs:`, `chore:`, ...)
|
|
27
|
+
export declare const ISSUE_TEMPLATE_RULES = "- Follow the closest template under `.github/ISSUE_TEMPLATE/`: `bug.md` for wrong behavior, `change.md` for anything to build or alter; a question or note fitting neither, or a repository without templates, needs no template.\n- Title: a Conventional Commits prefix for the type that fits the change (`feat:`, `fix:`, `refactor:`, `docs:`, `chore:`, ...).\n- The YAML front matter between the `---` lines is metadata, not body text: submit only the content below the closing `---` as the body.";
|
|
28
28
|
/** Sections of a bug report, in the order they appear in the issue template. */
|
|
29
29
|
export declare const BUG_ISSUE_SECTIONS: readonly TemplateSection[];
|
|
30
30
|
/**
|
package/dist/githubTemplates.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const e=[{heading:`Why`,requirement:`the problem, with the numbers or observations behind it, and why this approach over the alternatives`},{heading:`Requirements`,requirement:"the requirements the change must satisfy and the guarantees it must keep as they stand now, one per line, each marked `required` (asked for by the requester, or an existing contract callers depend on) or `chosen` (the implementer's own decision with the reason that decided it, which a simpler design may replace); state the requester's instructions in the requester's terms, whether they came from an issue or a conversation, and keep each line as it is until the requester overrides or withdraws the instruction behind it or the decision behind it changes, and then rewrite or remove that line instead of adding one beside it"},{heading:`Customer Summary`,requirement:`behavior, workflow, or user-visible changes, written for readers who know nothing about the implementation`},{heading:`Technical Summary`,requirement:`decisions and their reasons, data flow, where to look (the files that matter most), and what to check hardest: the parts whose correctness is argued rather than proven`},{heading:`Testing`,requirement:`commands run, tests added, and what was NOT exercised and why`},{heading:`Notes`,requirement:`known limitations, compatibility and migration, follow-up work; delete this section if there is none`}],t="- Requirements section: the requirements in force now, one line per requirement, taken from the requester's instructions in the requester's terms (the issue, the conversation that asked for the change), each marked `required` (asked for, or an existing contract callers depend on) or `chosen` (your own decision with the reason that decided it, which a simpler design may replace). Never infer a requirement from the diff: when no issue, PR message, or conversation states the request, write `required: not recorded — ask the requester` as the only request-derived line (`required` lines for existing contracts may still be listed) and say so in Notes.\n- When updating an existing section, keep each line until the requester overrides or withdraws the instruction behind it or the decision behind it changes, then rewrite or remove that line in place, never appending one beside it or marking when a line was added (e.g., \"(added)\"); with no instructions available, keep the requester-derived lines unchanged. When the current design cannot be understood without the change of direction, state it in one sentence in Notes. Write afresh by the rule above a section that merely describes the implementation or holds only the template's placeholder or a `required: not recorded` line.\n- Place the section where the template puts it; when the template has no such heading, right after the issue-closing line, or at the top of the body when there is none.\n- Never drop or weaken a `required` line to fit what was implemented: when one cannot hold in the diff, keep it and say so in Notes.",n=`- Base the PR body on \`.github/pull_request_template.md\` when creating or updating a PR, even when a skill or workflow supplies its own skeleton: keep the template's headings in order, fill each section with what its placeholder comment asks for at a length fitting the change (a sentence for a small change, numbered subsections for a large one), and delete the placeholder comments and an empty Notes section. Without a template, use no fixed headings: state the scope, the motivation, and the verification concisely.
|
|
2
|
-
- Start the body with \`Close #<n>\` only when the PR resolves an existing issue.\n${t}`,r="- Follow the closest template under `.github/ISSUE_TEMPLATE/`: `bug.md` for wrong behavior, `change.md` for anything to build or alter; a question or note fitting neither, or a repository without templates, needs no template.\n- Title: a Conventional Commits prefix for the type that fits the change (`feat:`, `fix:`, `refactor:`, `docs:`, `chore:`, ...)
|
|
2
|
+
- Start the body with \`Close #<n>\` only when the PR resolves an existing issue.\n${t}`,r="- Follow the closest template under `.github/ISSUE_TEMPLATE/`: `bug.md` for wrong behavior, `change.md` for anything to build or alter; a question or note fitting neither, or a repository without templates, needs no template.\n- Title: a Conventional Commits prefix for the type that fits the change (`feat:`, `fix:`, `refactor:`, `docs:`, `chore:`, ...).\n- The YAML front matter between the `---` lines is metadata, not body text: submit only the content below the closing `---` as the body.",i=[{heading:`Problem`,requirement:`what happens, and what should happen instead`},{heading:`Evidence`,requirement:`reproduction steps, logs, run IDs, or links`},{heading:`Impact`,requirement:`who or what is affected, and how badly`},{heading:`Proposal`,requirement:`the fix you have in mind, and the root cause if known`}],a=[{heading:`Background and goal`,requirement:`the problem being solved, for whom, and what outcome counts as success`},{heading:`Behavior`,requirement:`every user- or caller-observable behavior, including inputs, outputs, error cases, edge cases, and interactions with existing behavior, stated precisely enough that two implementers would build the same thing`},{heading:`Acceptance criteria`,requirement:`checkable conditions (Given/When/Then or an equivalent) that decide whether the implementation is done`},{heading:`Scope and non-goals`,requirement:`what the change covers and what it deliberately leaves out, so an implementer knows where to stop`},{heading:`Design constraints`,requirement:`the data, API, schema, UI, and configuration changes, naming, and the existing code or conventions the implementation must fit — as far as the writer intends to constrain them, with everything else explicitly left to the implementer`},{heading:`Compatibility and migration`,requirement:`what existing data, users, callers, or configurations are affected and how the transition happens`},{heading:`Verification`,requirement:`how the change is tested or demonstrated, including what needs no test and why`},{heading:`Open questions`,requirement:`undecided points, each with its options; empty once the specification is implementable`}];function o(e){return e.map(e=>`- **${e.heading}**: ${e.requirement}`).join(`
|
|
3
3
|
`)}function s(e){return e.map(e=>`## ${e.heading}\n\n<!-- ${e.requirement.charAt(0).toUpperCase()}${e.requirement.slice(1)}. -->`).join(`
|
|
4
4
|
|
|
5
5
|
`)}export{i as BUG_ISSUE_SECTIONS,a as CHANGE_ISSUE_SECTIONS,r as ISSUE_TEMPLATE_RULES,n as PULL_REQUEST_BODY_RULES,t as PULL_REQUEST_REQUIREMENTS_RULES,e as PULL_REQUEST_SECTIONS,o as renderSectionChecklist,s as renderSectionTemplate};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"githubTemplates.js","names":["PULL_REQUEST_SECTIONS","PULL_REQUEST_REQUIREMENTS_RULES","PULL_REQUEST_BODY_RULES","ISSUE_TEMPLATE_RULES","BUG_ISSUE_SECTIONS","CHANGE_ISSUE_SECTIONS","renderSectionChecklist","sections","section","renderSectionTemplate"],"sources":["../src/githubTemplates.ts"],"sourcesContent":["/**\n * A section of a PR or issue body: its heading and what the section must state. `requirement`\n * starts lowercase and ends without punctuation, so that it reads both as a placeholder sentence\n * (capitalized, with a period appended) and as a checklist clause after the heading.\n */\nexport interface TemplateSection {\n readonly heading: string;\n readonly requirement: string;\n}\n\n/** Sections of a pull request body, in the order they appear in the PR template. */\nexport const PULL_REQUEST_SECTIONS: readonly TemplateSection[] = [\n {\n heading: 'Why',\n requirement: 'the problem, with the numbers or observations behind it, and why this approach over the alternatives',\n },\n {\n heading: 'Requirements',\n requirement:\n \"the requirements the change must satisfy and the guarantees it must keep as they stand now, one per line, each marked `required` (asked for by the requester, or an existing contract callers depend on) or `chosen` (the implementer's own decision with the reason that decided it, which a simpler design may replace); state the requester's instructions in the requester's terms, whether they came from an issue or a conversation, and keep each line as it is until the requester overrides or withdraws the instruction behind it or the decision behind it changes, and then rewrite or remove that line instead of adding one beside it\",\n },\n {\n heading: 'Customer Summary',\n requirement:\n 'behavior, workflow, or user-visible changes, written for readers who know nothing about the implementation',\n },\n {\n heading: 'Technical Summary',\n requirement:\n 'decisions and their reasons, data flow, where to look (the files that matter most), and what to check hardest: the parts whose correctness is argued rather than proven',\n },\n {\n heading: 'Testing',\n requirement: 'commands run, tests added, and what was NOT exercised and why',\n },\n {\n heading: 'Notes',\n requirement: 'known limitations, compatibility and migration, follow-up work; delete this section if there is none',\n },\n];\n\n/**\n * How a tool that writes or rewrites a PR body derives it from the repository's PR template, as\n * markdown bullets for an agent's instructions. Stated once here and rendered by every writer (the\n * generated agent instructions, the PR-drafting skills) so the writers cannot drift apart; the\n * template-less branch lets the same text serve repositories outside the organization.\n */\nconst PULL_REQUEST_TEMPLATE_RULES = `- Base the PR body on \\`.github/pull_request_template.md\\` when creating or updating a PR, even when a skill or workflow supplies its own skeleton: keep the template's headings in order, fill each section with what its placeholder comment asks for at a length fitting the change (a sentence for a small change, numbered subsections for a large one), and delete the placeholder comments and an empty Notes section. Without a template, use no fixed headings: state the scope, the motivation, and the verification concisely.\n- Start the body with \\`Close #<n>\\` only when the PR resolves an existing issue.`;\n\n/**\n * How a tool that writes or rewrites a PR body fills the Requirements section of\n * `PULL_REQUEST_SECTIONS`, as markdown bullets for an agent's instructions. Skills that draft PR\n * bodies render this text instead of restating it, so the writers and the reviewers that read the\n * section as the boundary a fix must keep never disagree on what a line means.\n */\nexport const PULL_REQUEST_REQUIREMENTS_RULES = `- Requirements section: the requirements in force now, one line per requirement, taken from the requester's instructions in the requester's terms (the issue, the conversation that asked for the change), each marked \\`required\\` (asked for, or an existing contract callers depend on) or \\`chosen\\` (your own decision with the reason that decided it, which a simpler design may replace). Never infer a requirement from the diff: when no issue, PR message, or conversation states the request, write \\`required: not recorded — ask the requester\\` as the only request-derived line (\\`required\\` lines for existing contracts may still be listed) and say so in Notes.\n- When updating an existing section, keep each line until the requester overrides or withdraws the instruction behind it or the decision behind it changes, then rewrite or remove that line in place, never appending one beside it or marking when a line was added (e.g., \"(added)\"); with no instructions available, keep the requester-derived lines unchanged. When the current design cannot be understood without the change of direction, state it in one sentence in Notes. Write afresh by the rule above a section that merely describes the implementation or holds only the template's placeholder or a \\`required: not recorded\\` line.\n- Place the section where the template puts it; when the template has no such heading, right after the issue-closing line, or at the top of the body when there is none.\n- Never drop or weaken a \\`required\\` line to fit what was implemented: when one cannot hold in the diff, keep it and say so in Notes.`;\n\n/** The complete rules for writing a PR body: the template rules followed by the Requirements rules. */\nexport const PULL_REQUEST_BODY_RULES = `${PULL_REQUEST_TEMPLATE_RULES}\\n${PULL_REQUEST_REQUIREMENTS_RULES}`;\n\n/**\n * How a tool that creates an issue derives it from the repository's issue templates, as markdown\n * bullets for an agent's instructions; shared by the same writers as `PULL_REQUEST_BODY_RULES`.\n * Guidance the templates state in their own placeholder comments (which sections to keep, deleting\n * the comments) is deliberately not repeated here, since the writer reads the template anyway.\n */\nexport const ISSUE_TEMPLATE_RULES = `- Follow the closest template under \\`.github/ISSUE_TEMPLATE/\\`: \\`bug.md\\` for wrong behavior, \\`change.md\\` for anything to build or alter; a question or note fitting neither, or a repository without templates, needs no template.\n- Title: a Conventional Commits prefix for the type that fits the change (\\`feat:\\`, \\`fix:\\`, \\`refactor:\\`, \\`docs:\\`, \\`chore:\\`, ...); when it differs from the template's \\`title\\` prefix, replace the template's type label (\\`t: ...\\`) with the one matching the type.\n- The YAML front matter between the \\`---\\` lines is metadata, not body text: pass its \\`labels\\` via \\`--label\\` and submit only the content below the closing \\`---\\` as the body.`;\n\n/** Sections of a bug report, in the order they appear in the issue template. */\nexport const BUG_ISSUE_SECTIONS: readonly TemplateSection[] = [\n { heading: 'Problem', requirement: 'what happens, and what should happen instead' },\n { heading: 'Evidence', requirement: 'reproduction steps, logs, run IDs, or links' },\n { heading: 'Impact', requirement: 'who or what is affected, and how badly' },\n { heading: 'Proposal', requirement: 'the fix you have in mind, and the root cause if known' },\n];\n\n/**\n * Sections a change specification must settle before a third party can implement it, defined\n * once so that the issue template and any tool that drafts or reviews specifications against\n * these sections cannot drift apart; checklist consumers render it with `renderSectionChecklist`.\n * The first three sections suffice for a small change, so they come first.\n */\nexport const CHANGE_ISSUE_SECTIONS: readonly TemplateSection[] = [\n {\n heading: 'Background and goal',\n requirement: 'the problem being solved, for whom, and what outcome counts as success',\n },\n {\n heading: 'Behavior',\n requirement:\n 'every user- or caller-observable behavior, including inputs, outputs, error cases, edge cases, and interactions with existing behavior, stated precisely enough that two implementers would build the same thing',\n },\n {\n heading: 'Acceptance criteria',\n requirement:\n 'checkable conditions (Given/When/Then or an equivalent) that decide whether the implementation is done',\n },\n {\n heading: 'Scope and non-goals',\n requirement: 'what the change covers and what it deliberately leaves out, so an implementer knows where to stop',\n },\n {\n heading: 'Design constraints',\n requirement:\n 'the data, API, schema, UI, and configuration changes, naming, and the existing code or conventions the implementation must fit — as far as the writer intends to constrain them, with everything else explicitly left to the implementer',\n },\n {\n heading: 'Compatibility and migration',\n requirement: 'what existing data, users, callers, or configurations are affected and how the transition happens',\n },\n {\n heading: 'Verification',\n requirement: 'how the change is tested or demonstrated, including what needs no test and why',\n },\n {\n heading: 'Open questions',\n requirement: 'undecided points, each with its options; empty once the specification is implementable',\n },\n];\n\n/** Renders sections as markdown bullets (`- **Heading**: requirement`). */\nexport function renderSectionChecklist(sections: readonly TemplateSection[]): string {\n return sections.map((section) => `- **${section.heading}**: ${section.requirement}`).join('\\n');\n}\n\n/** Renders sections as markdown headings, each followed by its requirement as a placeholder comment. */\nexport function renderSectionTemplate(sections: readonly TemplateSection[]): string {\n return sections\n .map(\n (section) =>\n `## ${section.heading}\\n\\n<!-- ${section.requirement.charAt(0).toUpperCase()}${section.requirement.slice(1)}. -->`\n )\n .join('\\n\\n');\n}\n"],"mappings":"AAWA,MAAaA,EAAoD,CAC/D,CACE,QAAS,MACT,YAAa,sGACf,EACA,CACE,QAAS,eACT,YACE,qnBACJ,EACA,CACE,QAAS,mBACT,YACE,4GACJ,EACA,CACE,QAAS,oBACT,YACE,yKACJ,EACA,CACE,QAAS,UACT,YAAa,+DACf,EACA,CACE,QAAS,QACT,YAAa,sGACf,CACF,EAiBaC,EAAkC,ujDAMlCC,EAA0B;qFAAmCD,IAQ7DE,EAAuB,qpBAKvBC,EAAiD,CAC5D,CAAE,QAAS,UAAW,YAAa,8CAA+C,EAClF,CAAE,QAAS,WAAY,YAAa,6CAA8C,EAClF,CAAE,QAAS,SAAU,YAAa,wCAAyC,EAC3E,CAAE,QAAS,WAAY,YAAa,uDAAwD,CAC9F,EAQaC,EAAoD,CAC/D,CACE,QAAS,sBACT,YAAa,wEACf,EACA,CACE,QAAS,WACT,YACE,kNACJ,EACA,CACE,QAAS,sBACT,YACE,wGACJ,EACA,CACE,QAAS,sBACT,YAAa,mGACf,EACA,CACE,QAAS,qBACT,YACE,0OACJ,EACA,CACE,QAAS,8BACT,YAAa,mGACf,EACA,CACE,QAAS,eACT,YAAa,gFACf,EACA,CACE,QAAS,iBACT,YAAa,wFACf,CACF,EAGA,SAAgBC,EAAuB,EAA8C,CACnF,OAAOC,EAAS,IAAK,GAAY,OAAOC,EAAQ,QAAQ,MAAMA,EAAQ,aAAa,CAAC,CAAC,KAAK;CAAI,CAChG,CAGA,SAAgBC,EAAsB,EAA8C,CAClF,OAAOF,EACJ,IACE,GACC,MAAMC,EAAQ,QAAQ,WAAWA,EAAQ,YAAY,OAAO,CAAC,CAAC,CAAC,YAAY,IAAIA,EAAQ,YAAY,MAAM,CAAC,EAAE,MAChH,CAAC,CACA,KAAK;;CAAM,CAChB,C"}
|
|
1
|
+
{"version":3,"file":"githubTemplates.js","names":["PULL_REQUEST_SECTIONS","PULL_REQUEST_REQUIREMENTS_RULES","PULL_REQUEST_BODY_RULES","ISSUE_TEMPLATE_RULES","BUG_ISSUE_SECTIONS","CHANGE_ISSUE_SECTIONS","renderSectionChecklist","sections","section","renderSectionTemplate"],"sources":["../src/githubTemplates.ts"],"sourcesContent":["/**\n * A section of a PR or issue body: its heading and what the section must state. `requirement`\n * starts lowercase and ends without punctuation, so that it reads both as a placeholder sentence\n * (capitalized, with a period appended) and as a checklist clause after the heading.\n */\nexport interface TemplateSection {\n readonly heading: string;\n readonly requirement: string;\n}\n\n/** Sections of a pull request body, in the order they appear in the PR template. */\nexport const PULL_REQUEST_SECTIONS: readonly TemplateSection[] = [\n {\n heading: 'Why',\n requirement: 'the problem, with the numbers or observations behind it, and why this approach over the alternatives',\n },\n {\n heading: 'Requirements',\n requirement:\n \"the requirements the change must satisfy and the guarantees it must keep as they stand now, one per line, each marked `required` (asked for by the requester, or an existing contract callers depend on) or `chosen` (the implementer's own decision with the reason that decided it, which a simpler design may replace); state the requester's instructions in the requester's terms, whether they came from an issue or a conversation, and keep each line as it is until the requester overrides or withdraws the instruction behind it or the decision behind it changes, and then rewrite or remove that line instead of adding one beside it\",\n },\n {\n heading: 'Customer Summary',\n requirement:\n 'behavior, workflow, or user-visible changes, written for readers who know nothing about the implementation',\n },\n {\n heading: 'Technical Summary',\n requirement:\n 'decisions and their reasons, data flow, where to look (the files that matter most), and what to check hardest: the parts whose correctness is argued rather than proven',\n },\n {\n heading: 'Testing',\n requirement: 'commands run, tests added, and what was NOT exercised and why',\n },\n {\n heading: 'Notes',\n requirement: 'known limitations, compatibility and migration, follow-up work; delete this section if there is none',\n },\n];\n\n/**\n * How a tool that writes or rewrites a PR body derives it from the repository's PR template, as\n * markdown bullets for an agent's instructions. Stated once here and rendered by every writer (the\n * generated agent instructions, the PR-drafting skills) so the writers cannot drift apart; the\n * template-less branch lets the same text serve repositories outside the organization.\n */\nconst PULL_REQUEST_TEMPLATE_RULES = `- Base the PR body on \\`.github/pull_request_template.md\\` when creating or updating a PR, even when a skill or workflow supplies its own skeleton: keep the template's headings in order, fill each section with what its placeholder comment asks for at a length fitting the change (a sentence for a small change, numbered subsections for a large one), and delete the placeholder comments and an empty Notes section. Without a template, use no fixed headings: state the scope, the motivation, and the verification concisely.\n- Start the body with \\`Close #<n>\\` only when the PR resolves an existing issue.`;\n\n/**\n * How a tool that writes or rewrites a PR body fills the Requirements section of\n * `PULL_REQUEST_SECTIONS`, as markdown bullets for an agent's instructions. Skills that draft PR\n * bodies render this text instead of restating it, so the writers and the reviewers that read the\n * section as the boundary a fix must keep never disagree on what a line means.\n */\nexport const PULL_REQUEST_REQUIREMENTS_RULES = `- Requirements section: the requirements in force now, one line per requirement, taken from the requester's instructions in the requester's terms (the issue, the conversation that asked for the change), each marked \\`required\\` (asked for, or an existing contract callers depend on) or \\`chosen\\` (your own decision with the reason that decided it, which a simpler design may replace). Never infer a requirement from the diff: when no issue, PR message, or conversation states the request, write \\`required: not recorded — ask the requester\\` as the only request-derived line (\\`required\\` lines for existing contracts may still be listed) and say so in Notes.\n- When updating an existing section, keep each line until the requester overrides or withdraws the instruction behind it or the decision behind it changes, then rewrite or remove that line in place, never appending one beside it or marking when a line was added (e.g., \"(added)\"); with no instructions available, keep the requester-derived lines unchanged. When the current design cannot be understood without the change of direction, state it in one sentence in Notes. Write afresh by the rule above a section that merely describes the implementation or holds only the template's placeholder or a \\`required: not recorded\\` line.\n- Place the section where the template puts it; when the template has no such heading, right after the issue-closing line, or at the top of the body when there is none.\n- Never drop or weaken a \\`required\\` line to fit what was implemented: when one cannot hold in the diff, keep it and say so in Notes.`;\n\n/** The complete rules for writing a PR body: the template rules followed by the Requirements rules. */\nexport const PULL_REQUEST_BODY_RULES = `${PULL_REQUEST_TEMPLATE_RULES}\\n${PULL_REQUEST_REQUIREMENTS_RULES}`;\n\n/**\n * How a tool that creates an issue derives it from the repository's issue templates, as markdown\n * bullets for an agent's instructions; shared by the same writers as `PULL_REQUEST_BODY_RULES`.\n * Guidance the templates state in their own placeholder comments (which sections to keep, deleting\n * the comments) is deliberately not repeated here, since the writer reads the template anyway.\n */\nexport const ISSUE_TEMPLATE_RULES = `- Follow the closest template under \\`.github/ISSUE_TEMPLATE/\\`: \\`bug.md\\` for wrong behavior, \\`change.md\\` for anything to build or alter; a question or note fitting neither, or a repository without templates, needs no template.\n- Title: a Conventional Commits prefix for the type that fits the change (\\`feat:\\`, \\`fix:\\`, \\`refactor:\\`, \\`docs:\\`, \\`chore:\\`, ...).\n- The YAML front matter between the \\`---\\` lines is metadata, not body text: submit only the content below the closing \\`---\\` as the body.`;\n\n/** Sections of a bug report, in the order they appear in the issue template. */\nexport const BUG_ISSUE_SECTIONS: readonly TemplateSection[] = [\n { heading: 'Problem', requirement: 'what happens, and what should happen instead' },\n { heading: 'Evidence', requirement: 'reproduction steps, logs, run IDs, or links' },\n { heading: 'Impact', requirement: 'who or what is affected, and how badly' },\n { heading: 'Proposal', requirement: 'the fix you have in mind, and the root cause if known' },\n];\n\n/**\n * Sections a change specification must settle before a third party can implement it, defined\n * once so that the issue template and any tool that drafts or reviews specifications against\n * these sections cannot drift apart; checklist consumers render it with `renderSectionChecklist`.\n * The first three sections suffice for a small change, so they come first.\n */\nexport const CHANGE_ISSUE_SECTIONS: readonly TemplateSection[] = [\n {\n heading: 'Background and goal',\n requirement: 'the problem being solved, for whom, and what outcome counts as success',\n },\n {\n heading: 'Behavior',\n requirement:\n 'every user- or caller-observable behavior, including inputs, outputs, error cases, edge cases, and interactions with existing behavior, stated precisely enough that two implementers would build the same thing',\n },\n {\n heading: 'Acceptance criteria',\n requirement:\n 'checkable conditions (Given/When/Then or an equivalent) that decide whether the implementation is done',\n },\n {\n heading: 'Scope and non-goals',\n requirement: 'what the change covers and what it deliberately leaves out, so an implementer knows where to stop',\n },\n {\n heading: 'Design constraints',\n requirement:\n 'the data, API, schema, UI, and configuration changes, naming, and the existing code or conventions the implementation must fit — as far as the writer intends to constrain them, with everything else explicitly left to the implementer',\n },\n {\n heading: 'Compatibility and migration',\n requirement: 'what existing data, users, callers, or configurations are affected and how the transition happens',\n },\n {\n heading: 'Verification',\n requirement: 'how the change is tested or demonstrated, including what needs no test and why',\n },\n {\n heading: 'Open questions',\n requirement: 'undecided points, each with its options; empty once the specification is implementable',\n },\n];\n\n/** Renders sections as markdown bullets (`- **Heading**: requirement`). */\nexport function renderSectionChecklist(sections: readonly TemplateSection[]): string {\n return sections.map((section) => `- **${section.heading}**: ${section.requirement}`).join('\\n');\n}\n\n/** Renders sections as markdown headings, each followed by its requirement as a placeholder comment. */\nexport function renderSectionTemplate(sections: readonly TemplateSection[]): string {\n return sections\n .map(\n (section) =>\n `## ${section.heading}\\n\\n<!-- ${section.requirement.charAt(0).toUpperCase()}${section.requirement.slice(1)}. -->`\n )\n .join('\\n\\n');\n}\n"],"mappings":"AAWA,MAAaA,EAAoD,CAC/D,CACE,QAAS,MACT,YAAa,sGACf,EACA,CACE,QAAS,eACT,YACE,qnBACJ,EACA,CACE,QAAS,mBACT,YACE,4GACJ,EACA,CACE,QAAS,oBACT,YACE,yKACJ,EACA,CACE,QAAS,UACT,YAAa,+DACf,EACA,CACE,QAAS,QACT,YAAa,sGACf,CACF,EAiBaC,EAAkC,ujDAMlCC,EAA0B;qFAAmCD,IAQ7DE,EAAuB,gfAKvBC,EAAiD,CAC5D,CAAE,QAAS,UAAW,YAAa,8CAA+C,EAClF,CAAE,QAAS,WAAY,YAAa,6CAA8C,EAClF,CAAE,QAAS,SAAU,YAAa,wCAAyC,EAC3E,CAAE,QAAS,WAAY,YAAa,uDAAwD,CAC9F,EAQaC,EAAoD,CAC/D,CACE,QAAS,sBACT,YAAa,wEACf,EACA,CACE,QAAS,WACT,YACE,kNACJ,EACA,CACE,QAAS,sBACT,YACE,wGACJ,EACA,CACE,QAAS,sBACT,YAAa,mGACf,EACA,CACE,QAAS,qBACT,YACE,0OACJ,EACA,CACE,QAAS,8BACT,YAAa,mGACf,EACA,CACE,QAAS,eACT,YAAa,gFACf,EACA,CACE,QAAS,iBACT,YAAa,wFACf,CACF,EAGA,SAAgBC,EAAuB,EAA8C,CACnF,OAAOC,EAAS,IAAK,GAAY,OAAOC,EAAQ,QAAQ,MAAMA,EAAQ,aAAa,CAAC,CAAC,KAAK;CAAI,CAChG,CAGA,SAAgBC,EAAsB,EAA8C,CAClF,OAAOF,EACJ,IACE,GACC,MAAMC,EAAQ,QAAQ,WAAWA,EAAQ,YAAY,OAAO,CAAC,CAAC,CAAC,YAAY,IAAIA,EAAQ,YAAY,MAAM,CAAC,EAAE,MAChH,CAAC,CACA,KAAK;;CAAM,CAChB,C"}
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./appEnv.cjs"),t=require("./array.cjs"),n=require("./assert.cjs"),r=require("./concurrency.cjs"),i=require("./crypto.cjs"),a=require("./githubTemplates.cjs"),o=require("./sleep.cjs"),s=require("./error.cjs"),c=require("./filePath.cjs"),l=require("./formatIsoDate.cjs"),u=require("./github.cjs"),d=require("./http.cjs"),f=require("./foreignCjk.cjs"),p=require("./humanize.cjs"),m=require("./mail.cjs"),h=require("./object.cjs"),g=require("./markdown.cjs"),_=require("./parseIsoDate.cjs"),v=require("./parseCommandLineArgs.cjs"),y=require("./number.cjs"),b=require("./text.cjs"),x=require("./prompt.cjs"),S=require("./promptFormatters.cjs"),C=require("./shuffle.cjs"),w=require("./recoverJson.cjs"),T=require("./sqlite.cjs"),E=require("./testingRules.cjs"),D=require("./throttledFetch.cjs"),O=require("./timeout.cjs"),k=require("./url.cjs"),A=require("./yaml.cjs"),j=require("./zenkaku.cjs");exports.BUG_ISSUE_SECTIONS=a.BUG_ISSUE_SECTIONS,exports.CHANGE_ISSUE_SECTIONS=a.CHANGE_ISSUE_SECTIONS,exports.ISSUE_TEMPLATE_RULES=a.ISSUE_TEMPLATE_RULES,exports.PULL_REQUEST_BODY_RULES=a.PULL_REQUEST_BODY_RULES,exports.PULL_REQUEST_REQUIREMENTS_RULES=a.PULL_REQUEST_REQUIREMENTS_RULES,exports.PULL_REQUEST_SECTIONS=a.PULL_REQUEST_SECTIONS,exports.TEST_WRITING_RULES=E.TEST_WRITING_RULES,exports.chunk=t.chunk,exports.clamp=y.clamp,exports.createThrottledFetch=D.createThrottledFetch,exports.detectForeignCjkInJapanese=f.detectForeignCjkInJapanese,exports.ensureTruthy=n.ensureTruthy,exports.errorify=s.errorify,exports.escapeHtml=b.escapeHtml,exports.escapeLikePattern=T.escapeLikePattern,exports.escapePromptTag=x.escapePromptTag,exports.escapeRegExp=b.escapeRegExp,exports.extractCodeBlocks=g.extractCodeBlocks,exports.extractIfSingleOutermostCodeBlock=g.extractIfSingleOutermostCodeBlock,exports.extractSections=g.extractSections,exports.extractTaggedCodeBlock=g.extractTaggedCodeBlock,exports.findForeignCjkCharactersInJapanese=f.findForeignCjkCharactersInJapanese,exports.forEachConcurrently=r.forEachConcurrently,exports.formatDuration=p.formatDuration,exports.formatElapsedTimeInJapanese=p.formatElapsedTimeInJapanese,exports.formatFilesForPrompt=S.formatFilesForPrompt,exports.formatIsoDateInTimeZone=l.formatIsoDateInTimeZone,exports.formatMessagesForPrompt=S.formatMessagesForPrompt,exports.formatPrompt=x.formatPrompt,exports.getAppEnv=e.getAppEnv,exports.getConnectionLevelSqlitePragmas=T.getConnectionLevelSqlitePragmas,exports.getErrorMessage=s.getErrorMessage,exports.getFileExtension=c.getFileExtension,exports.getFormString=d.getFormString,exports.getPersistentSqlitePragmas=T.getPersistentSqlitePragmas,exports.getSafeRedirectPath=k.getSafeRedirectPath,exports.getTildeFence=b.getTildeFence,exports.hasErrorCode=s.hasErrorCode,exports.humanizeNumber=p.humanizeNumber,exports.ignoreEnoent=s.ignoreEnoent,exports.ignoreEnoentAsync=s.ignoreEnoentAsync,exports.ignoreError=s.ignoreError,exports.ignoreErrorAsync=s.ignoreErrorAsync,exports.isRecord=h.isRecord,exports.mailTemplates=m.mailTemplates,exports.mapConcurrently=r.mapConcurrently,exports.omitUndefined=h.omitUndefined,exports.parseBearerToken=d.parseBearerToken,exports.parseCommandLineArgs=v.parseCommandLineArgs,exports.parseGitHubRepositoryFullName=u.parseGitHubRepositoryFullName,exports.parseIsoDate=_.parseIsoDate,exports.parseMarkdownSections=g.parseMarkdownSections,exports.parsePositiveInteger=y.parsePositiveInteger,exports.quoteForShell=b.quoteForShell,exports.raceWithTimeout=O.raceWithTimeout,exports.recoverJson=w.recoverJson,exports.renderSectionChecklist=a.renderSectionChecklist,exports.renderSectionTemplate=a.renderSectionTemplate,exports.serializeForPrompt=x.serializeForPrompt,exports.serializeForPromptInTag=x.serializeForPromptInTag,exports.sha256HexAsync=i.sha256HexAsync,exports.shuffle=C.shuffle,exports.shuffleWithSeed=C.shuffleWithSeed,exports.sleep=o.sleep,exports.timingSafeEqualStringAsync=i.timingSafeEqualStringAsync,exports.toCodeBlock=b.toCodeBlock,exports.toTildeCodeBlock=b.toTildeCodeBlock,exports.truncate=b.truncate,exports.truncateForPrompt=x.truncateForPrompt,exports.withRetry=s.withRetry,exports.yamlStringifyOptions=A.yamlStringifyOptions,exports.zenkakuAlphanumericalsToHankaku=j.zenkakuAlphanumericalsToHankaku;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./appEnv.cjs"),t=require("./array.cjs"),n=require("./assert.cjs"),r=require("./concurrency.cjs"),i=require("./crypto.cjs"),a=require("./githubTemplates.cjs"),o=require("./sleep.cjs"),s=require("./error.cjs"),c=require("./filePath.cjs"),l=require("./formatIsoDate.cjs"),u=require("./github.cjs"),d=require("./http.cjs"),f=require("./foreignCjk.cjs"),p=require("./humanize.cjs"),m=require("./mail.cjs"),h=require("./object.cjs"),g=require("./markdown.cjs"),_=require("./parseIsoDate.cjs"),v=require("./parseCommandLineArgs.cjs"),y=require("./number.cjs"),b=require("./text.cjs"),x=require("./prompt.cjs"),S=require("./promptFormatters.cjs"),C=require("./shuffle.cjs"),w=require("./recoverJson.cjs"),T=require("./sqlite.cjs"),E=require("./testingRules.cjs"),D=require("./throttledFetch.cjs"),O=require("./timeout.cjs"),k=require("./url.cjs"),A=require("./yaml.cjs"),j=require("./zenkaku.cjs");exports.BUG_ISSUE_SECTIONS=a.BUG_ISSUE_SECTIONS,exports.CHANGE_ISSUE_SECTIONS=a.CHANGE_ISSUE_SECTIONS,exports.ISSUE_TEMPLATE_RULES=a.ISSUE_TEMPLATE_RULES,exports.PULL_REQUEST_BODY_RULES=a.PULL_REQUEST_BODY_RULES,exports.PULL_REQUEST_REQUIREMENTS_RULES=a.PULL_REQUEST_REQUIREMENTS_RULES,exports.PULL_REQUEST_SECTIONS=a.PULL_REQUEST_SECTIONS,exports.TEST_WRITING_RULES=E.TEST_WRITING_RULES,exports.chunk=t.chunk,exports.clamp=y.clamp,exports.createThrottledFetch=D.createThrottledFetch,exports.detectForeignCjkInJapanese=f.detectForeignCjkInJapanese,exports.ensureTruthy=n.ensureTruthy,exports.errorify=s.errorify,exports.escapeHtml=b.escapeHtml,exports.escapeLikePattern=T.escapeLikePattern,exports.escapePromptTag=x.escapePromptTag,exports.escapeRegExp=b.escapeRegExp,exports.extractCodeBlocks=g.extractCodeBlocks,exports.extractIfSingleOutermostCodeBlock=g.extractIfSingleOutermostCodeBlock,exports.extractSections=g.extractSections,exports.extractTaggedCodeBlock=g.extractTaggedCodeBlock,exports.findForeignCjkCharactersInJapanese=f.findForeignCjkCharactersInJapanese,exports.forEachConcurrently=r.forEachConcurrently,exports.formatDuration=p.formatDuration,exports.formatElapsedTimeInJapanese=p.formatElapsedTimeInJapanese,exports.formatFilesForPrompt=S.formatFilesForPrompt,exports.formatIsoDateInTimeZone=l.formatIsoDateInTimeZone,exports.formatMessagesForPrompt=S.formatMessagesForPrompt,exports.formatPrompt=x.formatPrompt,exports.getAppEnv=e.getAppEnv,exports.getConnectionLevelSqlitePragmas=T.getConnectionLevelSqlitePragmas,exports.getErrorMessage=s.getErrorMessage,exports.getFileExtension=c.getFileExtension,exports.getFormString=d.getFormString,exports.getPersistentSqlitePragmas=T.getPersistentSqlitePragmas,exports.getSafeRedirectPath=k.getSafeRedirectPath,exports.getTildeFence=b.getTildeFence,exports.hasErrorCode=s.hasErrorCode,exports.humanizeNumber=p.humanizeNumber,exports.ignoreEnoent=s.ignoreEnoent,exports.ignoreEnoentAsync=s.ignoreEnoentAsync,exports.ignoreError=s.ignoreError,exports.ignoreErrorAsync=s.ignoreErrorAsync,exports.isRecord=h.isRecord,exports.mailTemplates=m.mailTemplates,exports.mapConcurrently=r.mapConcurrently,exports.omitUndefined=h.omitUndefined,exports.parseBearerToken=d.parseBearerToken,exports.parseCommandLineArgs=v.parseCommandLineArgs,exports.parseGitHubRepositoryFullName=u.parseGitHubRepositoryFullName,exports.parseIsoDate=_.parseIsoDate,exports.parseMarkdownSections=g.parseMarkdownSections,exports.parsePositiveInteger=y.parsePositiveInteger,exports.quoteForShell=b.quoteForShell,exports.raceWithTimeout=O.raceWithTimeout,exports.recoverJson=w.recoverJson,exports.renderSectionChecklist=a.renderSectionChecklist,exports.renderSectionTemplate=a.renderSectionTemplate,exports.serializeForPrompt=x.serializeForPrompt,exports.serializeForPromptInTag=x.serializeForPromptInTag,exports.sha256HexAsync=i.sha256HexAsync,exports.shuffle=C.shuffle,exports.shuffleWithSeed=C.shuffleWithSeed,exports.sleep=o.sleep,exports.timingSafeEqualStringAsync=i.timingSafeEqualStringAsync,exports.toCodeBlock=b.toCodeBlock,exports.toTildeCodeBlock=b.toTildeCodeBlock,exports.truncate=b.truncate,exports.truncateForPrompt=x.truncateForPrompt,exports.unwrapFencedReply=b.unwrapFencedReply,exports.withRetry=s.withRetry,exports.yamlStringifyOptions=A.yamlStringifyOptions,exports.zenkakuAlphanumericalsToHankaku=j.zenkakuAlphanumericalsToHankaku;
|
package/dist/index.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export { recoverJson } from './recoverJson.js';
|
|
|
25
25
|
export type { JsonRepair, RecoveredJson, JsonRecovery } from './recoverJson.js';
|
|
26
26
|
export { sleep } from './sleep.js';
|
|
27
27
|
export { escapeLikePattern, getConnectionLevelSqlitePragmas, getPersistentSqlitePragmas } from './sqlite.js';
|
|
28
|
-
export { escapeHtml, escapeRegExp, getTildeFence, quoteForShell, toCodeBlock, toTildeCodeBlock, truncate, } from './text.js';
|
|
28
|
+
export { escapeHtml, escapeRegExp, getTildeFence, quoteForShell, toCodeBlock, toTildeCodeBlock, truncate, unwrapFencedReply, } from './text.js';
|
|
29
29
|
export { TEST_WRITING_RULES } from './testingRules.js';
|
|
30
30
|
export { createThrottledFetch } from './throttledFetch.js';
|
|
31
31
|
export { raceWithTimeout } from './timeout.js';
|
|
@@ -35,7 +35,7 @@ export { zenkakuAlphanumericalsToHankaku } from './zenkaku.js';
|
|
|
35
35
|
export type { AppEnv } from './appEnv.js';
|
|
36
36
|
export type { RetryOptions } from './error.js';
|
|
37
37
|
export type { ForeignCjkLanguage } from './foreignCjk.js';
|
|
38
|
-
export type { CodeBlock,
|
|
38
|
+
export type { CodeBlock, MarkdownSection } from './markdown.js';
|
|
39
39
|
export type { TemplateSection } from './githubTemplates.js';
|
|
40
40
|
export type { ThrottledFetchOptions } from './throttledFetch.js';
|
|
41
41
|
export type { RaceWithTimeoutResult } from './timeout.js';
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getAppEnv as e}from"./appEnv.js";import{chunk as t}from"./array.js";import{ensureTruthy as n}from"./assert.js";import{forEachConcurrently as r,mapConcurrently as i}from"./concurrency.js";import{sha256HexAsync as a,timingSafeEqualStringAsync as o}from"./crypto.js";import{BUG_ISSUE_SECTIONS as s,CHANGE_ISSUE_SECTIONS as c,ISSUE_TEMPLATE_RULES as l,PULL_REQUEST_BODY_RULES as u,PULL_REQUEST_REQUIREMENTS_RULES as d,PULL_REQUEST_SECTIONS as f,renderSectionChecklist as p,renderSectionTemplate as m}from"./githubTemplates.js";import{sleep as h}from"./sleep.js";import{errorify as g,getErrorMessage as _,hasErrorCode as v,ignoreEnoent as y,ignoreEnoentAsync as b,ignoreError as x,ignoreErrorAsync as S,withRetry as C}from"./error.js";import{getFileExtension as w}from"./filePath.js";import{formatIsoDateInTimeZone as T}from"./formatIsoDate.js";import{parseGitHubRepositoryFullName as E}from"./github.js";import{getFormString as D,parseBearerToken as O}from"./http.js";import{detectForeignCjkInJapanese as k,findForeignCjkCharactersInJapanese as A}from"./foreignCjk.js";import{formatDuration as j,formatElapsedTimeInJapanese as M,humanizeNumber as N}from"./humanize.js";import{mailTemplates as P}from"./mail.js";import{isRecord as F,omitUndefined as I}from"./object.js";import{extractCodeBlocks as L,extractIfSingleOutermostCodeBlock as R,extractSections as z,extractTaggedCodeBlock as B,parseMarkdownSections as V}from"./markdown.js";import{parseIsoDate as H}from"./parseIsoDate.js";import{parseCommandLineArgs as U}from"./parseCommandLineArgs.js";import{clamp as W,parsePositiveInteger as G}from"./number.js";import{escapeHtml as K,escapeRegExp as q,getTildeFence as J,quoteForShell as Y,toCodeBlock as X,toTildeCodeBlock as Z,truncate as Q}from"./text.js";import{escapePromptTag as
|
|
1
|
+
import{getAppEnv as e}from"./appEnv.js";import{chunk as t}from"./array.js";import{ensureTruthy as n}from"./assert.js";import{forEachConcurrently as r,mapConcurrently as i}from"./concurrency.js";import{sha256HexAsync as a,timingSafeEqualStringAsync as o}from"./crypto.js";import{BUG_ISSUE_SECTIONS as s,CHANGE_ISSUE_SECTIONS as c,ISSUE_TEMPLATE_RULES as l,PULL_REQUEST_BODY_RULES as u,PULL_REQUEST_REQUIREMENTS_RULES as d,PULL_REQUEST_SECTIONS as f,renderSectionChecklist as p,renderSectionTemplate as m}from"./githubTemplates.js";import{sleep as h}from"./sleep.js";import{errorify as g,getErrorMessage as _,hasErrorCode as v,ignoreEnoent as y,ignoreEnoentAsync as b,ignoreError as x,ignoreErrorAsync as S,withRetry as C}from"./error.js";import{getFileExtension as w}from"./filePath.js";import{formatIsoDateInTimeZone as T}from"./formatIsoDate.js";import{parseGitHubRepositoryFullName as E}from"./github.js";import{getFormString as D,parseBearerToken as O}from"./http.js";import{detectForeignCjkInJapanese as k,findForeignCjkCharactersInJapanese as A}from"./foreignCjk.js";import{formatDuration as j,formatElapsedTimeInJapanese as M,humanizeNumber as N}from"./humanize.js";import{mailTemplates as P}from"./mail.js";import{isRecord as F,omitUndefined as I}from"./object.js";import{extractCodeBlocks as L,extractIfSingleOutermostCodeBlock as R,extractSections as z,extractTaggedCodeBlock as B,parseMarkdownSections as V}from"./markdown.js";import{parseIsoDate as H}from"./parseIsoDate.js";import{parseCommandLineArgs as U}from"./parseCommandLineArgs.js";import{clamp as W,parsePositiveInteger as G}from"./number.js";import{escapeHtml as K,escapeRegExp as q,getTildeFence as J,quoteForShell as Y,toCodeBlock as X,toTildeCodeBlock as Z,truncate as Q,unwrapFencedReply as $}from"./text.js";import{escapePromptTag as ee,formatPrompt as te,serializeForPrompt as ne,serializeForPromptInTag as re,truncateForPrompt as ie}from"./prompt.js";import{formatFilesForPrompt as ae,formatMessagesForPrompt as oe}from"./promptFormatters.js";import{shuffle as se,shuffleWithSeed as ce}from"./shuffle.js";import{recoverJson as le}from"./recoverJson.js";import{escapeLikePattern as ue,getConnectionLevelSqlitePragmas as de,getPersistentSqlitePragmas as fe}from"./sqlite.js";import{TEST_WRITING_RULES as pe}from"./testingRules.js";import{createThrottledFetch as me}from"./throttledFetch.js";import{raceWithTimeout as he}from"./timeout.js";import{getSafeRedirectPath as ge}from"./url.js";import{yamlStringifyOptions as _e}from"./yaml.js";import{zenkakuAlphanumericalsToHankaku as ve}from"./zenkaku.js";export{s as BUG_ISSUE_SECTIONS,c as CHANGE_ISSUE_SECTIONS,l as ISSUE_TEMPLATE_RULES,u as PULL_REQUEST_BODY_RULES,d as PULL_REQUEST_REQUIREMENTS_RULES,f as PULL_REQUEST_SECTIONS,pe as TEST_WRITING_RULES,t as chunk,W as clamp,me as createThrottledFetch,k as detectForeignCjkInJapanese,n as ensureTruthy,g as errorify,K as escapeHtml,ue as escapeLikePattern,ee as escapePromptTag,q as escapeRegExp,L as extractCodeBlocks,R as extractIfSingleOutermostCodeBlock,z as extractSections,B as extractTaggedCodeBlock,A as findForeignCjkCharactersInJapanese,r as forEachConcurrently,j as formatDuration,M as formatElapsedTimeInJapanese,ae as formatFilesForPrompt,T as formatIsoDateInTimeZone,oe as formatMessagesForPrompt,te as formatPrompt,e as getAppEnv,de as getConnectionLevelSqlitePragmas,_ as getErrorMessage,w as getFileExtension,D as getFormString,fe as getPersistentSqlitePragmas,ge as getSafeRedirectPath,J as getTildeFence,v as hasErrorCode,N as humanizeNumber,y as ignoreEnoent,b as ignoreEnoentAsync,x as ignoreError,S as ignoreErrorAsync,F as isRecord,P as mailTemplates,i as mapConcurrently,I as omitUndefined,O as parseBearerToken,U as parseCommandLineArgs,E as parseGitHubRepositoryFullName,H as parseIsoDate,V as parseMarkdownSections,G as parsePositiveInteger,Y as quoteForShell,he as raceWithTimeout,le as recoverJson,p as renderSectionChecklist,m as renderSectionTemplate,ne as serializeForPrompt,re as serializeForPromptInTag,a as sha256HexAsync,se as shuffle,ce as shuffleWithSeed,h as sleep,o as timingSafeEqualStringAsync,X as toCodeBlock,Z as toTildeCodeBlock,Q as truncate,ie as truncateForPrompt,$ as unwrapFencedReply,C as withRetry,_e as yamlStringifyOptions,ve as zenkakuAlphanumericalsToHankaku};
|
package/dist/markdown.cjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
"use strict";function e(e){let t=
|
|
2
|
-
`),isClosed:i<t.length}),e=i}return n}function t(e,t){let
|
|
3
|
-
`)}function
|
|
4
|
-
`).trim()}}
|
|
5
|
-
`)
|
|
6
|
-
`),isWhole:i>=r}}function m(e){let t=[];for(let n=0;n<e.length;n++){let r=e[n]??``,i=h(r);if(i){n=g(e,n+1,i);continue}let a=/^(#{1,6})(?:[ \t]+(.*))?$/u.exec(r);if(!a)continue;let o=(a[2]??``).replace(/(?:^|[ \t]+)#+[ \t]*$/u,``).trim().replaceAll(/(`+)(.+?)\1/gu,(e,t,n)=>n.startsWith(` `)&&n.endsWith(` `)?n.slice(1,-1):n);t.push({index:n,depth:a[1]?.length??1,text:o})}return t}function h(e){let t=/^( {0,3})(`{3,}|~{3,})(.*)$/u.exec(e),n=t?.[2],r=t?.[3]??``;if(!(!n||n.startsWith("`")&&r.includes("`")))return{char:n.charAt(0),length:n.length,indent:t[1]?.length??0,language:r.trim().split(/\s/u)[0]?.toLowerCase()??``}}function g(e,t,n){let r=t;for(;r<e.length&&!_(e[r]??``,n);)r++;return r}function _(e,t){let n=/^ {0,3}(`{3,}|~{3,})[ \t]*$/u.exec(e)?.[1];return n?.charAt(0)===t.char&&n.length>=t.length}function v(e,t){let n=0;for(;n<t&&e[n]===` `;)n++;return e.slice(n)}function y(e){return e.split(/\r?\n/u)}exports.extractCodeBlocks=e,exports.extractIfSingleOutermostCodeBlock=t,exports.extractSections=c,exports.extractTaggedCodeBlock=o,exports.parseMarkdownSections=l;
|
|
1
|
+
"use strict";function e(e){let t=g(e),n=[];for(let e=0;e<t.length;e++){let r=f(t[e]??``);if(!r)continue;let i=p(t,e+1,r);n.push({...r.language&&{language:r.language},code:t.slice(e+1,i).map(e=>h(e,r.indent)).join(`
|
|
2
|
+
`),isClosed:i<t.length}),e=i}return n}function t(e,t){let n=u(e);return!n||t?.length&&!t.some(e=>e.toLowerCase()===n.language)?e:n.code}const n=/* @__PURE__ */ new Set([``,`markdown`,`md`]);function r(e,t){let r=`<${t}>`,a=i(g(e),r);if(a!==void 0)return a;let o=u(e);if(o?.isWhole&&n.has(o.language))return i(g(o.code),r)}function i(e,t){for(let n=0;n<e.length;n++){let r=e[n]??``,i=f(r);if(i){n=p(e,n+1,i);continue}if(r.trim()!==t)continue;let a=n+1;for(;a<e.length&&!e[a]?.trim();)a++;let o=f(e[a]??``);if(!o)continue;let s=p(e,a+1,o);if(s<e.length)return e.slice(a+1,s).map(e=>h(e,o.indent)).join(`
|
|
3
|
+
`).trim()}}function a(e,t){let n=o(e).map(e=>({...e,key:s(e.heading)})),r=[...new Set(t)].map(e=>({name:e,key:s(e)})),i=Object.create(null);for(let{name:e,key:t}of r){let a;for(let r of n)r.key===t&&(!a||r.depth<a.depth||r.depth===a.depth&&r.heading===e&&a.heading!==e)&&(a=r);a&&(a.heading!==e&&r.some(n=>n.name!==e&&n.key===t)||a.content&&(i[e]=a.content))}return i}function o(e){let t=u(e),r=t&&n.has(t.language)?t:void 0;if(r?.isWhole)return l(r.code);let i=l(e);return i.length===0&&r?l(r.code):i}function s(e){return c(c(e).replace(/^\d+(?:\.\d+)*(?:\.\s+|\)\s*|\s+)/u,``)).replaceAll(/\s+/gu,` `).toLowerCase()}function c(e){return e.replaceAll(/^[\s*_`~"'「」]+|[\s*_`~"'「」::]+$/gu,``)}function l(e){let t=g(e),n=d(t);return n.map((e,r)=>{let i=r+1;for(;i<n.length&&(n[i]?.depth??0)>e.depth;)i++;return{depth:e.depth,heading:e.text,content:t.slice(e.index+1,n[i]?.index).join(`
|
|
4
|
+
`).trim()}})}function u(e){let t=g(e.replace(/^\s*\n/u,``).trimEnd()),n=f(t[0]??``);if(!n)return;let r=t.length-1,i=r>0&&m(t[r]??``,n)?r:p(t,1,n);return{language:n.language,code:t.slice(1,i).map(e=>h(e,n.indent)).join(`
|
|
5
|
+
`),isWhole:i>=r}}function d(e){let t=[];for(let n=0;n<e.length;n++){let r=e[n]??``,i=f(r);if(i){n=p(e,n+1,i);continue}let a=/^(#{1,6})(?:[ \t]+(.*))?$/u.exec(r);if(!a)continue;let o=(a[2]??``).replace(/(?:^|[ \t]+)#+[ \t]*$/u,``).trim().replaceAll(/(`+)(.+?)\1/gu,(e,t,n)=>n.startsWith(` `)&&n.endsWith(` `)?n.slice(1,-1):n);t.push({index:n,depth:a[1]?.length??1,text:o})}return t}function f(e){let t=/^( {0,3})(`{3,}|~{3,})(.*)$/u.exec(e),n=t?.[2],r=t?.[3]??``;if(!(!n||n.startsWith("`")&&r.includes("`")))return{char:n.charAt(0),length:n.length,indent:t[1]?.length??0,language:r.trim().split(/\s/u)[0]?.toLowerCase()??``}}function p(e,t,n){let r=t;for(;r<e.length&&!m(e[r]??``,n);)r++;return r}function m(e,t){let n=/^ {0,3}(`{3,}|~{3,})[ \t]*$/u.exec(e)?.[1];return n?.charAt(0)===t.char&&n.length>=t.length}function h(e,t){let n=0;for(;n<t&&e[n]===` `;)n++;return e.slice(n)}function g(e){return e.split(/\r?\n/u)}exports.extractCodeBlocks=e,exports.extractIfSingleOutermostCodeBlock=t,exports.extractSections=a,exports.extractTaggedCodeBlock=r,exports.parseMarkdownSections=o;
|
|
7
6
|
//# sourceMappingURL=markdown.cjs.map
|
package/dist/markdown.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown.cjs","names":["extractCodeBlocks","lines","splitLines","markdown","blocks","index","fence","parseOpeningFence","end","findClosingFence","stripIndent","line","extractIfSingleOutermostCodeBlock","isLanguageList","languagesOrOptions","isAllowed","languages","allowed","language","known","findBlockWithKnownFence","text","block","findOutermostCodeBlock","value","start","findSentOpeningFence","trimmed","isSentFence","opener","WRAPPED_MARKDOWN_LANGUAGES","extractTaggedCodeBlock","tag","tagName","found","findTaggedCodeBlock","wrapper","enclosingFence","fenceIndex","contentLine","extractSections","candidates","parseMarkdownSections","section","normalizeHeading","requested","names","name","sections","best","candidate","key","other","splitSections","stripHeadingEdges","heading","headings","findHeadings","nextIndex","lastIndex","isClosingFence","match","content","info","closer","indent"],"sources":["../src/markdown.ts"],"sourcesContent":["export interface CodeBlock {\n /** The first word of the info string, lowercased; `undefined` when the fence has none. */\n language?: string;\n code: string;\n /** `false` when the text ends before the closing fence, e.g. because the LLM output was cut off. */\n isClosed: boolean;\n}\n\ninterface Fence {\n char: string;\n length: number;\n indent: number;\n language: string;\n}\n\n/**\n * Extracts the fenced code blocks (backticks or tildes, any fence length) of Markdown text such as an LLM response.\n * A block runs to the first closing fence of the same character that is at least as long as its opening fence,\n * so a longer fence can hold shorter fences. Only fences indented by at most three spaces are recognized; fences\n * inside block quotes, deeply nested list items, and indented code blocks are not.\n */\nexport function extractCodeBlocks(markdown: string): CodeBlock[] {\n const lines = splitLines(markdown);\n const blocks: CodeBlock[] = [];\n for (let index = 0; index < lines.length; index++) {\n const fence = parseOpeningFence(lines[index] ?? '');\n if (!fence) continue;\n const end = findClosingFence(lines, index + 1, fence);\n blocks.push({\n ...(fence.language && { language: fence.language }),\n code: lines\n .slice(index + 1, end)\n .map((line) => stripIndent(line, fence.indent))\n .join('\\n'),\n isClosed: end < lines.length,\n });\n index = end;\n }\n return blocks;\n}\n\nexport interface ExtractCodeBlockOptions {\n /** Allowed languages, compared case-insensitively; an omitted or empty list allows any language. */\n languages?: readonly string[];\n /** The exact fence the caller asked the LLM to wrap its response in, e.g. `getTildeFence(text)`. */\n fence?: string;\n}\n\n/**\n * Returns the contents of the code block that an LLM wrapped its whole response in, or `text` itself when the\n * response does not start with a fence or the fence's language is not allowed. A plain array is taken as `languages`.\n * When the response also ends with a matching closing fence, everything between its first and last lines is returned,\n * so fences the LLM nested without lengthening the outer fence stay in the contents. Otherwise the first block is\n * returned and text after it is dropped; an unclosed block runs to the end of the text.\n * With `fence`, a preamble and a postamble around the block are dropped first: everything between the first line\n * holding exactly `fence` and an allowed language or none, outside blocks of other languages, and the last later line\n * holding exactly `fence` is returned. Without such a pair, the rules above apply.\n */\nexport function extractIfSingleOutermostCodeBlock(\n text: string,\n languagesOrOptions?: readonly string[] | ExtractCodeBlockOptions\n): string {\n const { languages, fence }: ExtractCodeBlockOptions = isLanguageList(languagesOrOptions)\n ? { languages: languagesOrOptions }\n : (languagesOrOptions ?? {});\n const isAllowed = (language: string): boolean =>\n !languages?.length || languages.some((allowed) => allowed.toLowerCase() === language);\n const known = fence === undefined ? undefined : findBlockWithKnownFence(text, fence, isAllowed);\n if (known !== undefined) return known;\n const block = findOutermostCodeBlock(text);\n if (!block || !isAllowed(block.language)) return text;\n return block.code;\n}\n\nfunction isLanguageList(value: readonly string[] | ExtractCodeBlockOptions | undefined): value is readonly string[] {\n return Array.isArray(value);\n}\n\nfunction findBlockWithKnownFence(\n text: string,\n fence: string,\n isAllowed: (language: string) => boolean\n): string | undefined {\n const lines = splitLines(text);\n const end = lines.findLastIndex((line) => line.trimEnd() === fence);\n const start = findSentOpeningFence(lines, end, fence, isAllowed);\n if (start === undefined) return;\n return lines.slice(start + 1, end).join('\\n');\n}\n\n/** Returns the index of the first line before `end` opening `fence` with an allowed language or none, skipping other blocks. */\nfunction findSentOpeningFence(\n lines: readonly string[],\n end: number,\n fence: string,\n isAllowed: (language: string) => boolean\n): number | undefined {\n for (let index = 0; index < end; index++) {\n const line = lines[index] ?? '';\n const trimmed = line.trimEnd();\n const isSentFence = trimmed.startsWith(fence) && trimmed.charAt(fence.length) !== fence.charAt(0);\n const language = trimmed.slice(fence.length).trim().split(/\\s/u)[0]?.toLowerCase() ?? '';\n if (isSentFence && (language === '' || isAllowed(language))) return index;\n const opener = parseOpeningFence(line);\n if (opener) index = findClosingFence(lines, index + 1, opener);\n }\n}\n\nconst WRAPPED_MARKDOWN_LANGUAGES = new Set(['', 'markdown', 'md']);\n\n/**\n * Returns the trimmed contents of the first closed fenced code block (backticks or tildes) that follows a line holding\n * only `<tagName>` in text such as an LLM response, e.g. `<answer>\\n~~~\\n42\\n~~~`, or `undefined` when there is none.\n * Only blank lines may separate the tag line from the opening fence. Mentions of the tag within prose lines and tag\n * lines inside other fenced blocks (e.g. a format example) are skipped, but a whole-response `markdown`, `md`, or\n * unlabeled wrapper is read inside, as `parseMarkdownSections` does.\n */\nexport function extractTaggedCodeBlock(text: string, tagName: string): string | undefined {\n const tag = `<${tagName}>`;\n const found = findTaggedCodeBlock(splitLines(text), tag);\n if (found !== undefined) return found;\n // Only a real wrapper hides every tag line from the scan above; a leading example block paired with the tagged\n // block's closing fence merely looks like one, so its contents must not be read first.\n const wrapper = findOutermostCodeBlock(text);\n if (wrapper?.isWhole && WRAPPED_MARKDOWN_LANGUAGES.has(wrapper.language)) {\n return findTaggedCodeBlock(splitLines(wrapper.code), tag);\n }\n}\n\nfunction findTaggedCodeBlock(lines: readonly string[], tag: string): string | undefined {\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index] ?? '';\n const enclosingFence = parseOpeningFence(line);\n if (enclosingFence) {\n index = findClosingFence(lines, index + 1, enclosingFence);\n continue;\n }\n if (line.trim() !== tag) continue;\n let fenceIndex = index + 1;\n while (fenceIndex < lines.length && !lines[fenceIndex]?.trim()) fenceIndex++;\n const fence = parseOpeningFence(lines[fenceIndex] ?? '');\n if (!fence) continue;\n const end = findClosingFence(lines, fenceIndex + 1, fence);\n if (end < lines.length) {\n return lines\n .slice(fenceIndex + 1, end)\n .map((contentLine) => stripIndent(contentLine, fence.indent))\n .join('\\n')\n .trim();\n }\n }\n}\n\nexport interface MarkdownSection {\n depth: number;\n /** The heading text with the closing `#` sequence and inline-code backticks removed. */\n heading: string;\n /** The source text up to the next heading at the same or a shallower depth, trimmed; subsections are included. */\n content: string;\n}\n\n/**\n * Extracts the content of the section headed by each of `names` from Markdown text such as an LLM response, keyed by\n * the given names. A heading matches a name when both are equal after removing decorations at their edges (such as\n * `**bold**`, backticks, and quotes), a leading number like `1.` or `2.1`, and a trailing colon, ignoring case and spacing;\n * headings at any depth are candidates.\n * Among matching headings, the shallowest wins, then one equal to the name as written, then the first; each heading\n * serves at most one name. When distinct requested names normalize alike, only exact matches at the shallowest\n * matching depth are returned. Missing, empty, or ambiguous sections are absent from the prototype-free result.\n */\nexport function extractSections<const Name extends string>(\n markdown: string,\n names: readonly Name[]\n): Partial<Record<Name, string>> {\n const candidates = parseMarkdownSections(markdown).map((section) => ({\n ...section,\n key: normalizeHeading(section.heading),\n }));\n const requested = [...new Set(names)].map((name) => ({ name, key: normalizeHeading(name) }));\n const sections: Partial<Record<Name, string>> = Object.create(null);\n for (const { name, key } of requested) {\n let best: (typeof candidates)[number] | undefined;\n for (const candidate of candidates) {\n if (candidate.key !== key) continue;\n if (\n !best ||\n candidate.depth < best.depth ||\n (candidate.depth === best.depth && candidate.heading === name && best.heading !== name)\n ) {\n best = candidate;\n }\n }\n if (!best) continue;\n if (best.heading !== name && requested.some((other) => other.name !== name && other.key === key)) continue;\n if (best.content) sections[name] = best.content;\n }\n return sections;\n}\n\n/**\n * Returns every ATX heading of Markdown text in order, with the source text under it.\n * Only headings starting at the beginning of a line outside fenced code blocks count; setext headings (underlined\n * with `=` or `-`) are ignored. A whole-response `markdown`, `md`, or unlabeled wrapper is read inside as\n * `extractIfSingleOutermostCodeBlock` finds it, preserving nested fences. A leading wrapper followed by prose is\n * also read inside, but only when there are no headings outside fences.\n */\nexport function parseMarkdownSections(markdown: string): MarkdownSection[] {\n const block = findOutermostCodeBlock(markdown);\n const wrapper = block && WRAPPED_MARKDOWN_LANGUAGES.has(block.language) ? block : undefined;\n if (wrapper?.isWhole) return splitSections(wrapper.code);\n const sections = splitSections(markdown);\n return sections.length === 0 && wrapper ? splitSections(wrapper.code) : sections;\n}\n\nfunction normalizeHeading(heading: string): string {\n return stripHeadingEdges(stripHeadingEdges(heading).replace(/^\\d+(?:\\.\\d+)*(?:\\.\\s+|\\)\\s*|\\s+)/u, ''))\n .replaceAll(/\\s+/gu, ' ')\n .toLowerCase();\n}\n\nfunction stripHeadingEdges(text: string): string {\n // Only the edges: `_` and `*` inside a heading belong to names such as `my_file.py`.\n return text.replaceAll(/^[\\s*_`~\"'「」]+|[\\s*_`~\"'「」::]+$/gu, '');\n}\n\nfunction splitSections(markdown: string): MarkdownSection[] {\n const lines = splitLines(markdown);\n const headings = findHeadings(lines);\n return headings.map((heading, index) => {\n let nextIndex = index + 1;\n while (nextIndex < headings.length && (headings[nextIndex]?.depth ?? 0) > heading.depth) nextIndex++;\n return {\n depth: heading.depth,\n heading: heading.text,\n content: lines\n .slice(heading.index + 1, headings[nextIndex]?.index)\n .join('\\n')\n .trim(),\n };\n });\n}\n\nfunction findOutermostCodeBlock(text: string): { language: string; code: string; isWhole: boolean } | undefined {\n // Keep the opening fence's indentation, which is stripped from the contents.\n const lines = splitLines(text.replace(/^\\s*\\n/u, '').trimEnd());\n const fence = parseOpeningFence(lines[0] ?? '');\n if (!fence) return;\n const lastIndex = lines.length - 1;\n const end =\n lastIndex > 0 && isClosingFence(lines[lastIndex] ?? '', fence) ? lastIndex : findClosingFence(lines, 1, fence);\n return {\n language: fence.language,\n code: lines\n .slice(1, end)\n .map((line) => stripIndent(line, fence.indent))\n .join('\\n'),\n isWhole: end >= lastIndex,\n };\n}\n\nfunction findHeadings(lines: readonly string[]): { index: number; depth: number; text: string }[] {\n const headings: { index: number; depth: number; text: string }[] = [];\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index] ?? '';\n const fence = parseOpeningFence(line);\n if (fence) {\n index = findClosingFence(lines, index + 1, fence);\n continue;\n }\n const match = /^(#{1,6})(?:[ \\t]+(.*))?$/u.exec(line);\n if (!match) continue;\n const text = (match[2] ?? '')\n // The closing sequence of an ATX heading needs a space before it, so `# C#` keeps its `#`.\n .replace(/(?:^|[ \\t]+)#+[ \\t]*$/u, '')\n .trim()\n .replaceAll(/(`+)(.+?)\\1/gu, (_match, _delimiter: string, content: string) =>\n content.startsWith(' ') && content.endsWith(' ') ? content.slice(1, -1) : content\n );\n headings.push({ index, depth: match[1]?.length ?? 1, text });\n }\n return headings;\n}\n\nfunction parseOpeningFence(line: string): Fence | undefined {\n const match = /^( {0,3})(`{3,}|~{3,})(.*)$/u.exec(line);\n const fence = match?.[2];\n const info = match?.[3] ?? '';\n // A backtick in the info string makes the line inline code instead of a fence.\n if (!fence || (fence.startsWith('`') && info.includes('`'))) return;\n return {\n char: fence.charAt(0),\n length: fence.length,\n indent: match[1]?.length ?? 0,\n language: info.trim().split(/\\s/u)[0]?.toLowerCase() ?? '',\n };\n}\n\n/** Returns the index of the line closing `fence`, or the number of lines when it is never closed. */\nfunction findClosingFence(lines: readonly string[], start: number, fence: Fence): number {\n let index = start;\n while (index < lines.length && !isClosingFence(lines[index] ?? '', fence)) index++;\n return index;\n}\n\nfunction isClosingFence(line: string, fence: Fence): boolean {\n const closer = /^ {0,3}(`{3,}|~{3,})[ \\t]*$/u.exec(line)?.[1];\n return closer?.charAt(0) === fence.char && closer.length >= fence.length;\n}\n\nfunction stripIndent(line: string, indent: number): string {\n let index = 0;\n while (index < indent && line[index] === ' ') index++;\n return line.slice(index);\n}\n\nfunction splitLines(text: string): string[] {\n return text.split(/\\r?\\n/u);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"markdown.cjs","names":["extractCodeBlocks","lines","splitLines","markdown","blocks","index","fence","parseOpeningFence","end","findClosingFence","stripIndent","line","extractIfSingleOutermostCodeBlock","block","findOutermostCodeBlock","text","languages","language","WRAPPED_MARKDOWN_LANGUAGES","extractTaggedCodeBlock","tag","tagName","found","findTaggedCodeBlock","wrapper","enclosingFence","fenceIndex","contentLine","extractSections","candidates","parseMarkdownSections","section","normalizeHeading","requested","names","name","sections","best","candidate","key","other","splitSections","stripHeadingEdges","heading","headings","findHeadings","nextIndex","lastIndex","isClosingFence","match","content","info","start","closer","indent"],"sources":["../src/markdown.ts"],"sourcesContent":["export interface CodeBlock {\n /** The first word of the info string, lowercased; `undefined` when the fence has none. */\n language?: string;\n code: string;\n /** `false` when the text ends before the closing fence, e.g. because the LLM output was cut off. */\n isClosed: boolean;\n}\n\ninterface Fence {\n char: string;\n length: number;\n indent: number;\n language: string;\n}\n\n/**\n * Extracts the fenced code blocks (backticks or tildes, any fence length) of Markdown text such as an LLM response.\n * A block runs to the first closing fence of the same character that is at least as long as its opening fence,\n * so a longer fence can hold shorter fences. Only fences indented by at most three spaces are recognized; fences\n * inside block quotes, deeply nested list items, and indented code blocks are not.\n */\nexport function extractCodeBlocks(markdown: string): CodeBlock[] {\n const lines = splitLines(markdown);\n const blocks: CodeBlock[] = [];\n for (let index = 0; index < lines.length; index++) {\n const fence = parseOpeningFence(lines[index] ?? '');\n if (!fence) continue;\n const end = findClosingFence(lines, index + 1, fence);\n blocks.push({\n ...(fence.language && { language: fence.language }),\n code: lines\n .slice(index + 1, end)\n .map((line) => stripIndent(line, fence.indent))\n .join('\\n'),\n isClosed: end < lines.length,\n });\n index = end;\n }\n return blocks;\n}\n\n/**\n * Returns the contents of the code block that an LLM wrapped its whole response in, or `text` itself when the\n * response does not start with a fence or the fence's language is not one of a nonempty `languages` list\n * (compared case-insensitively). An omitted or empty list allows any language.\n * When the response also ends with a matching closing fence, everything between its first and last lines is returned,\n * so fences the LLM nested without lengthening the outer fence stay in the contents. Otherwise the first block is\n * returned and text after it is dropped; an unclosed block runs to the end of the text.\n */\nexport function extractIfSingleOutermostCodeBlock(text: string, languages?: readonly string[]): string {\n const block = findOutermostCodeBlock(text);\n if (!block) return text;\n if (languages?.length && !languages.some((language) => language.toLowerCase() === block.language)) return text;\n return block.code;\n}\n\nconst WRAPPED_MARKDOWN_LANGUAGES = new Set(['', 'markdown', 'md']);\n\n/**\n * Returns the trimmed contents of the first closed fenced code block (backticks or tildes) that follows a line holding\n * only `<tagName>` in text such as an LLM response, e.g. `<answer>\\n~~~\\n42\\n~~~`, or `undefined` when there is none.\n * Only blank lines may separate the tag line from the opening fence. Mentions of the tag within prose lines and tag\n * lines inside other fenced blocks (e.g. a format example) are skipped, but a whole-response `markdown`, `md`, or\n * unlabeled wrapper is read inside, as `parseMarkdownSections` does.\n */\nexport function extractTaggedCodeBlock(text: string, tagName: string): string | undefined {\n const tag = `<${tagName}>`;\n const found = findTaggedCodeBlock(splitLines(text), tag);\n if (found !== undefined) return found;\n // Only a real wrapper hides every tag line from the scan above; a leading example block paired with the tagged\n // block's closing fence merely looks like one, so its contents must not be read first.\n const wrapper = findOutermostCodeBlock(text);\n if (wrapper?.isWhole && WRAPPED_MARKDOWN_LANGUAGES.has(wrapper.language)) {\n return findTaggedCodeBlock(splitLines(wrapper.code), tag);\n }\n}\n\nfunction findTaggedCodeBlock(lines: readonly string[], tag: string): string | undefined {\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index] ?? '';\n const enclosingFence = parseOpeningFence(line);\n if (enclosingFence) {\n index = findClosingFence(lines, index + 1, enclosingFence);\n continue;\n }\n if (line.trim() !== tag) continue;\n let fenceIndex = index + 1;\n while (fenceIndex < lines.length && !lines[fenceIndex]?.trim()) fenceIndex++;\n const fence = parseOpeningFence(lines[fenceIndex] ?? '');\n if (!fence) continue;\n const end = findClosingFence(lines, fenceIndex + 1, fence);\n if (end < lines.length) {\n return lines\n .slice(fenceIndex + 1, end)\n .map((contentLine) => stripIndent(contentLine, fence.indent))\n .join('\\n')\n .trim();\n }\n }\n}\n\nexport interface MarkdownSection {\n depth: number;\n /** The heading text with the closing `#` sequence and inline-code backticks removed. */\n heading: string;\n /** The source text up to the next heading at the same or a shallower depth, trimmed; subsections are included. */\n content: string;\n}\n\n/**\n * Extracts the content of the section headed by each of `names` from Markdown text such as an LLM response, keyed by\n * the given names. A heading matches a name when both are equal after removing decorations at their edges (such as\n * `**bold**`, backticks, and quotes), a leading number like `1.` or `2.1`, and a trailing colon, ignoring case and spacing;\n * headings at any depth are candidates.\n * Among matching headings, the shallowest wins, then one equal to the name as written, then the first; each heading\n * serves at most one name. When distinct requested names normalize alike, only exact matches at the shallowest\n * matching depth are returned. Missing, empty, or ambiguous sections are absent from the prototype-free result.\n */\nexport function extractSections<const Name extends string>(\n markdown: string,\n names: readonly Name[]\n): Partial<Record<Name, string>> {\n const candidates = parseMarkdownSections(markdown).map((section) => ({\n ...section,\n key: normalizeHeading(section.heading),\n }));\n const requested = [...new Set(names)].map((name) => ({ name, key: normalizeHeading(name) }));\n const sections: Partial<Record<Name, string>> = Object.create(null);\n for (const { name, key } of requested) {\n let best: (typeof candidates)[number] | undefined;\n for (const candidate of candidates) {\n if (candidate.key !== key) continue;\n if (\n !best ||\n candidate.depth < best.depth ||\n (candidate.depth === best.depth && candidate.heading === name && best.heading !== name)\n ) {\n best = candidate;\n }\n }\n if (!best) continue;\n if (best.heading !== name && requested.some((other) => other.name !== name && other.key === key)) continue;\n if (best.content) sections[name] = best.content;\n }\n return sections;\n}\n\n/**\n * Returns every ATX heading of Markdown text in order, with the source text under it.\n * Only headings starting at the beginning of a line outside fenced code blocks count; setext headings (underlined\n * with `=` or `-`) are ignored. A whole-response `markdown`, `md`, or unlabeled wrapper is read inside as\n * `extractIfSingleOutermostCodeBlock` finds it, preserving nested fences. A leading wrapper followed by prose is\n * also read inside, but only when there are no headings outside fences.\n */\nexport function parseMarkdownSections(markdown: string): MarkdownSection[] {\n const block = findOutermostCodeBlock(markdown);\n const wrapper = block && WRAPPED_MARKDOWN_LANGUAGES.has(block.language) ? block : undefined;\n if (wrapper?.isWhole) return splitSections(wrapper.code);\n const sections = splitSections(markdown);\n return sections.length === 0 && wrapper ? splitSections(wrapper.code) : sections;\n}\n\nfunction normalizeHeading(heading: string): string {\n return stripHeadingEdges(stripHeadingEdges(heading).replace(/^\\d+(?:\\.\\d+)*(?:\\.\\s+|\\)\\s*|\\s+)/u, ''))\n .replaceAll(/\\s+/gu, ' ')\n .toLowerCase();\n}\n\nfunction stripHeadingEdges(text: string): string {\n // Only the edges: `_` and `*` inside a heading belong to names such as `my_file.py`.\n return text.replaceAll(/^[\\s*_`~\"'「」]+|[\\s*_`~\"'「」::]+$/gu, '');\n}\n\nfunction splitSections(markdown: string): MarkdownSection[] {\n const lines = splitLines(markdown);\n const headings = findHeadings(lines);\n return headings.map((heading, index) => {\n let nextIndex = index + 1;\n while (nextIndex < headings.length && (headings[nextIndex]?.depth ?? 0) > heading.depth) nextIndex++;\n return {\n depth: heading.depth,\n heading: heading.text,\n content: lines\n .slice(heading.index + 1, headings[nextIndex]?.index)\n .join('\\n')\n .trim(),\n };\n });\n}\n\nfunction findOutermostCodeBlock(text: string): { language: string; code: string; isWhole: boolean } | undefined {\n // Keep the opening fence's indentation, which is stripped from the contents.\n const lines = splitLines(text.replace(/^\\s*\\n/u, '').trimEnd());\n const fence = parseOpeningFence(lines[0] ?? '');\n if (!fence) return;\n const lastIndex = lines.length - 1;\n const end =\n lastIndex > 0 && isClosingFence(lines[lastIndex] ?? '', fence) ? lastIndex : findClosingFence(lines, 1, fence);\n return {\n language: fence.language,\n code: lines\n .slice(1, end)\n .map((line) => stripIndent(line, fence.indent))\n .join('\\n'),\n isWhole: end >= lastIndex,\n };\n}\n\nfunction findHeadings(lines: readonly string[]): { index: number; depth: number; text: string }[] {\n const headings: { index: number; depth: number; text: string }[] = [];\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index] ?? '';\n const fence = parseOpeningFence(line);\n if (fence) {\n index = findClosingFence(lines, index + 1, fence);\n continue;\n }\n const match = /^(#{1,6})(?:[ \\t]+(.*))?$/u.exec(line);\n if (!match) continue;\n const text = (match[2] ?? '')\n // The closing sequence of an ATX heading needs a space before it, so `# C#` keeps its `#`.\n .replace(/(?:^|[ \\t]+)#+[ \\t]*$/u, '')\n .trim()\n .replaceAll(/(`+)(.+?)\\1/gu, (_match, _delimiter: string, content: string) =>\n content.startsWith(' ') && content.endsWith(' ') ? content.slice(1, -1) : content\n );\n headings.push({ index, depth: match[1]?.length ?? 1, text });\n }\n return headings;\n}\n\nfunction parseOpeningFence(line: string): Fence | undefined {\n const match = /^( {0,3})(`{3,}|~{3,})(.*)$/u.exec(line);\n const fence = match?.[2];\n const info = match?.[3] ?? '';\n // A backtick in the info string makes the line inline code instead of a fence.\n if (!fence || (fence.startsWith('`') && info.includes('`'))) return;\n return {\n char: fence.charAt(0),\n length: fence.length,\n indent: match[1]?.length ?? 0,\n language: info.trim().split(/\\s/u)[0]?.toLowerCase() ?? '',\n };\n}\n\n/** Returns the index of the line closing `fence`, or the number of lines when it is never closed. */\nfunction findClosingFence(lines: readonly string[], start: number, fence: Fence): number {\n let index = start;\n while (index < lines.length && !isClosingFence(lines[index] ?? '', fence)) index++;\n return index;\n}\n\nfunction isClosingFence(line: string, fence: Fence): boolean {\n const closer = /^ {0,3}(`{3,}|~{3,})[ \\t]*$/u.exec(line)?.[1];\n return closer?.charAt(0) === fence.char && closer.length >= fence.length;\n}\n\nfunction stripIndent(line: string, indent: number): string {\n let index = 0;\n while (index < indent && line[index] === ' ') index++;\n return line.slice(index);\n}\n\nfunction splitLines(text: string): string[] {\n return text.split(/\\r?\\n/u);\n}\n"],"mappings":"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"}
|
package/dist/markdown.d.ts
CHANGED
|
@@ -12,23 +12,15 @@ export interface CodeBlock {
|
|
|
12
12
|
* inside block quotes, deeply nested list items, and indented code blocks are not.
|
|
13
13
|
*/
|
|
14
14
|
export declare function extractCodeBlocks(markdown: string): CodeBlock[];
|
|
15
|
-
export interface ExtractCodeBlockOptions {
|
|
16
|
-
/** Allowed languages, compared case-insensitively; an omitted or empty list allows any language. */
|
|
17
|
-
languages?: readonly string[];
|
|
18
|
-
/** The exact fence the caller asked the LLM to wrap its response in, e.g. `getTildeFence(text)`. */
|
|
19
|
-
fence?: string;
|
|
20
|
-
}
|
|
21
15
|
/**
|
|
22
16
|
* Returns the contents of the code block that an LLM wrapped its whole response in, or `text` itself when the
|
|
23
|
-
* response does not start with a fence or the fence's language is not
|
|
17
|
+
* response does not start with a fence or the fence's language is not one of a nonempty `languages` list
|
|
18
|
+
* (compared case-insensitively). An omitted or empty list allows any language.
|
|
24
19
|
* When the response also ends with a matching closing fence, everything between its first and last lines is returned,
|
|
25
20
|
* so fences the LLM nested without lengthening the outer fence stay in the contents. Otherwise the first block is
|
|
26
21
|
* returned and text after it is dropped; an unclosed block runs to the end of the text.
|
|
27
|
-
* With `fence`, a preamble and a postamble around the block are dropped first: everything between the first line
|
|
28
|
-
* holding exactly `fence` and an allowed language or none, outside blocks of other languages, and the last later line
|
|
29
|
-
* holding exactly `fence` is returned. Without such a pair, the rules above apply.
|
|
30
22
|
*/
|
|
31
|
-
export declare function extractIfSingleOutermostCodeBlock(text: string,
|
|
23
|
+
export declare function extractIfSingleOutermostCodeBlock(text: string, languages?: readonly string[]): string;
|
|
32
24
|
/**
|
|
33
25
|
* Returns the trimmed contents of the first closed fenced code block (backticks or tildes) that follows a line holding
|
|
34
26
|
* only `<tagName>` in text such as an LLM response, e.g. `<answer>\n~~~\n42\n~~~`, or `undefined` when there is none.
|
package/dist/markdown.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
function e(e){let t=
|
|
2
|
-
`),isClosed:i<t.length}),e=i}return n}function t(e,t){let
|
|
3
|
-
`)}function
|
|
4
|
-
`).trim()}}
|
|
5
|
-
`)
|
|
6
|
-
`),isWhole:i>=r}}function m(e){let t=[];for(let n=0;n<e.length;n++){let r=e[n]??``,i=h(r);if(i){n=g(e,n+1,i);continue}let a=/^(#{1,6})(?:[ \t]+(.*))?$/u.exec(r);if(!a)continue;let o=(a[2]??``).replace(/(?:^|[ \t]+)#+[ \t]*$/u,``).trim().replaceAll(/(`+)(.+?)\1/gu,(e,t,n)=>n.startsWith(` `)&&n.endsWith(` `)?n.slice(1,-1):n);t.push({index:n,depth:a[1]?.length??1,text:o})}return t}function h(e){let t=/^( {0,3})(`{3,}|~{3,})(.*)$/u.exec(e),n=t?.[2],r=t?.[3]??``;if(!(!n||n.startsWith("`")&&r.includes("`")))return{char:n.charAt(0),length:n.length,indent:t[1]?.length??0,language:r.trim().split(/\s/u)[0]?.toLowerCase()??``}}function g(e,t,n){let r=t;for(;r<e.length&&!_(e[r]??``,n);)r++;return r}function _(e,t){let n=/^ {0,3}(`{3,}|~{3,})[ \t]*$/u.exec(e)?.[1];return n?.charAt(0)===t.char&&n.length>=t.length}function v(e,t){let n=0;for(;n<t&&e[n]===` `;)n++;return e.slice(n)}function y(e){return e.split(/\r?\n/u)}export{e as extractCodeBlocks,t as extractIfSingleOutermostCodeBlock,c as extractSections,o as extractTaggedCodeBlock,l as parseMarkdownSections};
|
|
1
|
+
function e(e){let t=g(e),n=[];for(let e=0;e<t.length;e++){let r=f(t[e]??``);if(!r)continue;let i=p(t,e+1,r);n.push({...r.language&&{language:r.language},code:t.slice(e+1,i).map(e=>h(e,r.indent)).join(`
|
|
2
|
+
`),isClosed:i<t.length}),e=i}return n}function t(e,t){let n=u(e);return!n||t?.length&&!t.some(e=>e.toLowerCase()===n.language)?e:n.code}const n=/* @__PURE__ */ new Set([``,`markdown`,`md`]);function r(e,t){let r=`<${t}>`,a=i(g(e),r);if(a!==void 0)return a;let o=u(e);if(o?.isWhole&&n.has(o.language))return i(g(o.code),r)}function i(e,t){for(let n=0;n<e.length;n++){let r=e[n]??``,i=f(r);if(i){n=p(e,n+1,i);continue}if(r.trim()!==t)continue;let a=n+1;for(;a<e.length&&!e[a]?.trim();)a++;let o=f(e[a]??``);if(!o)continue;let s=p(e,a+1,o);if(s<e.length)return e.slice(a+1,s).map(e=>h(e,o.indent)).join(`
|
|
3
|
+
`).trim()}}function a(e,t){let n=o(e).map(e=>({...e,key:s(e.heading)})),r=[...new Set(t)].map(e=>({name:e,key:s(e)})),i=Object.create(null);for(let{name:e,key:t}of r){let a;for(let r of n)r.key===t&&(!a||r.depth<a.depth||r.depth===a.depth&&r.heading===e&&a.heading!==e)&&(a=r);a&&(a.heading!==e&&r.some(n=>n.name!==e&&n.key===t)||a.content&&(i[e]=a.content))}return i}function o(e){let t=u(e),r=t&&n.has(t.language)?t:void 0;if(r?.isWhole)return l(r.code);let i=l(e);return i.length===0&&r?l(r.code):i}function s(e){return c(c(e).replace(/^\d+(?:\.\d+)*(?:\.\s+|\)\s*|\s+)/u,``)).replaceAll(/\s+/gu,` `).toLowerCase()}function c(e){return e.replaceAll(/^[\s*_`~"'「」]+|[\s*_`~"'「」::]+$/gu,``)}function l(e){let t=g(e),n=d(t);return n.map((e,r)=>{let i=r+1;for(;i<n.length&&(n[i]?.depth??0)>e.depth;)i++;return{depth:e.depth,heading:e.text,content:t.slice(e.index+1,n[i]?.index).join(`
|
|
4
|
+
`).trim()}})}function u(e){let t=g(e.replace(/^\s*\n/u,``).trimEnd()),n=f(t[0]??``);if(!n)return;let r=t.length-1,i=r>0&&m(t[r]??``,n)?r:p(t,1,n);return{language:n.language,code:t.slice(1,i).map(e=>h(e,n.indent)).join(`
|
|
5
|
+
`),isWhole:i>=r}}function d(e){let t=[];for(let n=0;n<e.length;n++){let r=e[n]??``,i=f(r);if(i){n=p(e,n+1,i);continue}let a=/^(#{1,6})(?:[ \t]+(.*))?$/u.exec(r);if(!a)continue;let o=(a[2]??``).replace(/(?:^|[ \t]+)#+[ \t]*$/u,``).trim().replaceAll(/(`+)(.+?)\1/gu,(e,t,n)=>n.startsWith(` `)&&n.endsWith(` `)?n.slice(1,-1):n);t.push({index:n,depth:a[1]?.length??1,text:o})}return t}function f(e){let t=/^( {0,3})(`{3,}|~{3,})(.*)$/u.exec(e),n=t?.[2],r=t?.[3]??``;if(!(!n||n.startsWith("`")&&r.includes("`")))return{char:n.charAt(0),length:n.length,indent:t[1]?.length??0,language:r.trim().split(/\s/u)[0]?.toLowerCase()??``}}function p(e,t,n){let r=t;for(;r<e.length&&!m(e[r]??``,n);)r++;return r}function m(e,t){let n=/^ {0,3}(`{3,}|~{3,})[ \t]*$/u.exec(e)?.[1];return n?.charAt(0)===t.char&&n.length>=t.length}function h(e,t){let n=0;for(;n<t&&e[n]===` `;)n++;return e.slice(n)}function g(e){return e.split(/\r?\n/u)}export{e as extractCodeBlocks,t as extractIfSingleOutermostCodeBlock,a as extractSections,r as extractTaggedCodeBlock,o as parseMarkdownSections};
|
|
7
6
|
//# sourceMappingURL=markdown.js.map
|
package/dist/markdown.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown.js","names":["extractCodeBlocks","lines","splitLines","markdown","blocks","index","fence","parseOpeningFence","end","findClosingFence","stripIndent","line","extractIfSingleOutermostCodeBlock","isLanguageList","languagesOrOptions","isAllowed","languages","allowed","language","known","findBlockWithKnownFence","text","block","findOutermostCodeBlock","value","start","findSentOpeningFence","trimmed","isSentFence","opener","WRAPPED_MARKDOWN_LANGUAGES","extractTaggedCodeBlock","tag","tagName","found","findTaggedCodeBlock","wrapper","enclosingFence","fenceIndex","contentLine","extractSections","candidates","parseMarkdownSections","section","normalizeHeading","requested","names","name","sections","best","candidate","key","other","splitSections","stripHeadingEdges","heading","headings","findHeadings","nextIndex","lastIndex","isClosingFence","match","content","info","closer","indent"],"sources":["../src/markdown.ts"],"sourcesContent":["export interface CodeBlock {\n /** The first word of the info string, lowercased; `undefined` when the fence has none. */\n language?: string;\n code: string;\n /** `false` when the text ends before the closing fence, e.g. because the LLM output was cut off. */\n isClosed: boolean;\n}\n\ninterface Fence {\n char: string;\n length: number;\n indent: number;\n language: string;\n}\n\n/**\n * Extracts the fenced code blocks (backticks or tildes, any fence length) of Markdown text such as an LLM response.\n * A block runs to the first closing fence of the same character that is at least as long as its opening fence,\n * so a longer fence can hold shorter fences. Only fences indented by at most three spaces are recognized; fences\n * inside block quotes, deeply nested list items, and indented code blocks are not.\n */\nexport function extractCodeBlocks(markdown: string): CodeBlock[] {\n const lines = splitLines(markdown);\n const blocks: CodeBlock[] = [];\n for (let index = 0; index < lines.length; index++) {\n const fence = parseOpeningFence(lines[index] ?? '');\n if (!fence) continue;\n const end = findClosingFence(lines, index + 1, fence);\n blocks.push({\n ...(fence.language && { language: fence.language }),\n code: lines\n .slice(index + 1, end)\n .map((line) => stripIndent(line, fence.indent))\n .join('\\n'),\n isClosed: end < lines.length,\n });\n index = end;\n }\n return blocks;\n}\n\nexport interface ExtractCodeBlockOptions {\n /** Allowed languages, compared case-insensitively; an omitted or empty list allows any language. */\n languages?: readonly string[];\n /** The exact fence the caller asked the LLM to wrap its response in, e.g. `getTildeFence(text)`. */\n fence?: string;\n}\n\n/**\n * Returns the contents of the code block that an LLM wrapped its whole response in, or `text` itself when the\n * response does not start with a fence or the fence's language is not allowed. A plain array is taken as `languages`.\n * When the response also ends with a matching closing fence, everything between its first and last lines is returned,\n * so fences the LLM nested without lengthening the outer fence stay in the contents. Otherwise the first block is\n * returned and text after it is dropped; an unclosed block runs to the end of the text.\n * With `fence`, a preamble and a postamble around the block are dropped first: everything between the first line\n * holding exactly `fence` and an allowed language or none, outside blocks of other languages, and the last later line\n * holding exactly `fence` is returned. Without such a pair, the rules above apply.\n */\nexport function extractIfSingleOutermostCodeBlock(\n text: string,\n languagesOrOptions?: readonly string[] | ExtractCodeBlockOptions\n): string {\n const { languages, fence }: ExtractCodeBlockOptions = isLanguageList(languagesOrOptions)\n ? { languages: languagesOrOptions }\n : (languagesOrOptions ?? {});\n const isAllowed = (language: string): boolean =>\n !languages?.length || languages.some((allowed) => allowed.toLowerCase() === language);\n const known = fence === undefined ? undefined : findBlockWithKnownFence(text, fence, isAllowed);\n if (known !== undefined) return known;\n const block = findOutermostCodeBlock(text);\n if (!block || !isAllowed(block.language)) return text;\n return block.code;\n}\n\nfunction isLanguageList(value: readonly string[] | ExtractCodeBlockOptions | undefined): value is readonly string[] {\n return Array.isArray(value);\n}\n\nfunction findBlockWithKnownFence(\n text: string,\n fence: string,\n isAllowed: (language: string) => boolean\n): string | undefined {\n const lines = splitLines(text);\n const end = lines.findLastIndex((line) => line.trimEnd() === fence);\n const start = findSentOpeningFence(lines, end, fence, isAllowed);\n if (start === undefined) return;\n return lines.slice(start + 1, end).join('\\n');\n}\n\n/** Returns the index of the first line before `end` opening `fence` with an allowed language or none, skipping other blocks. */\nfunction findSentOpeningFence(\n lines: readonly string[],\n end: number,\n fence: string,\n isAllowed: (language: string) => boolean\n): number | undefined {\n for (let index = 0; index < end; index++) {\n const line = lines[index] ?? '';\n const trimmed = line.trimEnd();\n const isSentFence = trimmed.startsWith(fence) && trimmed.charAt(fence.length) !== fence.charAt(0);\n const language = trimmed.slice(fence.length).trim().split(/\\s/u)[0]?.toLowerCase() ?? '';\n if (isSentFence && (language === '' || isAllowed(language))) return index;\n const opener = parseOpeningFence(line);\n if (opener) index = findClosingFence(lines, index + 1, opener);\n }\n}\n\nconst WRAPPED_MARKDOWN_LANGUAGES = new Set(['', 'markdown', 'md']);\n\n/**\n * Returns the trimmed contents of the first closed fenced code block (backticks or tildes) that follows a line holding\n * only `<tagName>` in text such as an LLM response, e.g. `<answer>\\n~~~\\n42\\n~~~`, or `undefined` when there is none.\n * Only blank lines may separate the tag line from the opening fence. Mentions of the tag within prose lines and tag\n * lines inside other fenced blocks (e.g. a format example) are skipped, but a whole-response `markdown`, `md`, or\n * unlabeled wrapper is read inside, as `parseMarkdownSections` does.\n */\nexport function extractTaggedCodeBlock(text: string, tagName: string): string | undefined {\n const tag = `<${tagName}>`;\n const found = findTaggedCodeBlock(splitLines(text), tag);\n if (found !== undefined) return found;\n // Only a real wrapper hides every tag line from the scan above; a leading example block paired with the tagged\n // block's closing fence merely looks like one, so its contents must not be read first.\n const wrapper = findOutermostCodeBlock(text);\n if (wrapper?.isWhole && WRAPPED_MARKDOWN_LANGUAGES.has(wrapper.language)) {\n return findTaggedCodeBlock(splitLines(wrapper.code), tag);\n }\n}\n\nfunction findTaggedCodeBlock(lines: readonly string[], tag: string): string | undefined {\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index] ?? '';\n const enclosingFence = parseOpeningFence(line);\n if (enclosingFence) {\n index = findClosingFence(lines, index + 1, enclosingFence);\n continue;\n }\n if (line.trim() !== tag) continue;\n let fenceIndex = index + 1;\n while (fenceIndex < lines.length && !lines[fenceIndex]?.trim()) fenceIndex++;\n const fence = parseOpeningFence(lines[fenceIndex] ?? '');\n if (!fence) continue;\n const end = findClosingFence(lines, fenceIndex + 1, fence);\n if (end < lines.length) {\n return lines\n .slice(fenceIndex + 1, end)\n .map((contentLine) => stripIndent(contentLine, fence.indent))\n .join('\\n')\n .trim();\n }\n }\n}\n\nexport interface MarkdownSection {\n depth: number;\n /** The heading text with the closing `#` sequence and inline-code backticks removed. */\n heading: string;\n /** The source text up to the next heading at the same or a shallower depth, trimmed; subsections are included. */\n content: string;\n}\n\n/**\n * Extracts the content of the section headed by each of `names` from Markdown text such as an LLM response, keyed by\n * the given names. A heading matches a name when both are equal after removing decorations at their edges (such as\n * `**bold**`, backticks, and quotes), a leading number like `1.` or `2.1`, and a trailing colon, ignoring case and spacing;\n * headings at any depth are candidates.\n * Among matching headings, the shallowest wins, then one equal to the name as written, then the first; each heading\n * serves at most one name. When distinct requested names normalize alike, only exact matches at the shallowest\n * matching depth are returned. Missing, empty, or ambiguous sections are absent from the prototype-free result.\n */\nexport function extractSections<const Name extends string>(\n markdown: string,\n names: readonly Name[]\n): Partial<Record<Name, string>> {\n const candidates = parseMarkdownSections(markdown).map((section) => ({\n ...section,\n key: normalizeHeading(section.heading),\n }));\n const requested = [...new Set(names)].map((name) => ({ name, key: normalizeHeading(name) }));\n const sections: Partial<Record<Name, string>> = Object.create(null);\n for (const { name, key } of requested) {\n let best: (typeof candidates)[number] | undefined;\n for (const candidate of candidates) {\n if (candidate.key !== key) continue;\n if (\n !best ||\n candidate.depth < best.depth ||\n (candidate.depth === best.depth && candidate.heading === name && best.heading !== name)\n ) {\n best = candidate;\n }\n }\n if (!best) continue;\n if (best.heading !== name && requested.some((other) => other.name !== name && other.key === key)) continue;\n if (best.content) sections[name] = best.content;\n }\n return sections;\n}\n\n/**\n * Returns every ATX heading of Markdown text in order, with the source text under it.\n * Only headings starting at the beginning of a line outside fenced code blocks count; setext headings (underlined\n * with `=` or `-`) are ignored. A whole-response `markdown`, `md`, or unlabeled wrapper is read inside as\n * `extractIfSingleOutermostCodeBlock` finds it, preserving nested fences. A leading wrapper followed by prose is\n * also read inside, but only when there are no headings outside fences.\n */\nexport function parseMarkdownSections(markdown: string): MarkdownSection[] {\n const block = findOutermostCodeBlock(markdown);\n const wrapper = block && WRAPPED_MARKDOWN_LANGUAGES.has(block.language) ? block : undefined;\n if (wrapper?.isWhole) return splitSections(wrapper.code);\n const sections = splitSections(markdown);\n return sections.length === 0 && wrapper ? splitSections(wrapper.code) : sections;\n}\n\nfunction normalizeHeading(heading: string): string {\n return stripHeadingEdges(stripHeadingEdges(heading).replace(/^\\d+(?:\\.\\d+)*(?:\\.\\s+|\\)\\s*|\\s+)/u, ''))\n .replaceAll(/\\s+/gu, ' ')\n .toLowerCase();\n}\n\nfunction stripHeadingEdges(text: string): string {\n // Only the edges: `_` and `*` inside a heading belong to names such as `my_file.py`.\n return text.replaceAll(/^[\\s*_`~\"'「」]+|[\\s*_`~\"'「」::]+$/gu, '');\n}\n\nfunction splitSections(markdown: string): MarkdownSection[] {\n const lines = splitLines(markdown);\n const headings = findHeadings(lines);\n return headings.map((heading, index) => {\n let nextIndex = index + 1;\n while (nextIndex < headings.length && (headings[nextIndex]?.depth ?? 0) > heading.depth) nextIndex++;\n return {\n depth: heading.depth,\n heading: heading.text,\n content: lines\n .slice(heading.index + 1, headings[nextIndex]?.index)\n .join('\\n')\n .trim(),\n };\n });\n}\n\nfunction findOutermostCodeBlock(text: string): { language: string; code: string; isWhole: boolean } | undefined {\n // Keep the opening fence's indentation, which is stripped from the contents.\n const lines = splitLines(text.replace(/^\\s*\\n/u, '').trimEnd());\n const fence = parseOpeningFence(lines[0] ?? '');\n if (!fence) return;\n const lastIndex = lines.length - 1;\n const end =\n lastIndex > 0 && isClosingFence(lines[lastIndex] ?? '', fence) ? lastIndex : findClosingFence(lines, 1, fence);\n return {\n language: fence.language,\n code: lines\n .slice(1, end)\n .map((line) => stripIndent(line, fence.indent))\n .join('\\n'),\n isWhole: end >= lastIndex,\n };\n}\n\nfunction findHeadings(lines: readonly string[]): { index: number; depth: number; text: string }[] {\n const headings: { index: number; depth: number; text: string }[] = [];\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index] ?? '';\n const fence = parseOpeningFence(line);\n if (fence) {\n index = findClosingFence(lines, index + 1, fence);\n continue;\n }\n const match = /^(#{1,6})(?:[ \\t]+(.*))?$/u.exec(line);\n if (!match) continue;\n const text = (match[2] ?? '')\n // The closing sequence of an ATX heading needs a space before it, so `# C#` keeps its `#`.\n .replace(/(?:^|[ \\t]+)#+[ \\t]*$/u, '')\n .trim()\n .replaceAll(/(`+)(.+?)\\1/gu, (_match, _delimiter: string, content: string) =>\n content.startsWith(' ') && content.endsWith(' ') ? content.slice(1, -1) : content\n );\n headings.push({ index, depth: match[1]?.length ?? 1, text });\n }\n return headings;\n}\n\nfunction parseOpeningFence(line: string): Fence | undefined {\n const match = /^( {0,3})(`{3,}|~{3,})(.*)$/u.exec(line);\n const fence = match?.[2];\n const info = match?.[3] ?? '';\n // A backtick in the info string makes the line inline code instead of a fence.\n if (!fence || (fence.startsWith('`') && info.includes('`'))) return;\n return {\n char: fence.charAt(0),\n length: fence.length,\n indent: match[1]?.length ?? 0,\n language: info.trim().split(/\\s/u)[0]?.toLowerCase() ?? '',\n };\n}\n\n/** Returns the index of the line closing `fence`, or the number of lines when it is never closed. */\nfunction findClosingFence(lines: readonly string[], start: number, fence: Fence): number {\n let index = start;\n while (index < lines.length && !isClosingFence(lines[index] ?? '', fence)) index++;\n return index;\n}\n\nfunction isClosingFence(line: string, fence: Fence): boolean {\n const closer = /^ {0,3}(`{3,}|~{3,})[ \\t]*$/u.exec(line)?.[1];\n return closer?.charAt(0) === fence.char && closer.length >= fence.length;\n}\n\nfunction stripIndent(line: string, indent: number): string {\n let index = 0;\n while (index < indent && line[index] === ' ') index++;\n return line.slice(index);\n}\n\nfunction splitLines(text: string): string[] {\n return text.split(/\\r?\\n/u);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"markdown.js","names":["extractCodeBlocks","lines","splitLines","markdown","blocks","index","fence","parseOpeningFence","end","findClosingFence","stripIndent","line","extractIfSingleOutermostCodeBlock","block","findOutermostCodeBlock","text","languages","language","WRAPPED_MARKDOWN_LANGUAGES","extractTaggedCodeBlock","tag","tagName","found","findTaggedCodeBlock","wrapper","enclosingFence","fenceIndex","contentLine","extractSections","candidates","parseMarkdownSections","section","normalizeHeading","requested","names","name","sections","best","candidate","key","other","splitSections","stripHeadingEdges","heading","headings","findHeadings","nextIndex","lastIndex","isClosingFence","match","content","info","start","closer","indent"],"sources":["../src/markdown.ts"],"sourcesContent":["export interface CodeBlock {\n /** The first word of the info string, lowercased; `undefined` when the fence has none. */\n language?: string;\n code: string;\n /** `false` when the text ends before the closing fence, e.g. because the LLM output was cut off. */\n isClosed: boolean;\n}\n\ninterface Fence {\n char: string;\n length: number;\n indent: number;\n language: string;\n}\n\n/**\n * Extracts the fenced code blocks (backticks or tildes, any fence length) of Markdown text such as an LLM response.\n * A block runs to the first closing fence of the same character that is at least as long as its opening fence,\n * so a longer fence can hold shorter fences. Only fences indented by at most three spaces are recognized; fences\n * inside block quotes, deeply nested list items, and indented code blocks are not.\n */\nexport function extractCodeBlocks(markdown: string): CodeBlock[] {\n const lines = splitLines(markdown);\n const blocks: CodeBlock[] = [];\n for (let index = 0; index < lines.length; index++) {\n const fence = parseOpeningFence(lines[index] ?? '');\n if (!fence) continue;\n const end = findClosingFence(lines, index + 1, fence);\n blocks.push({\n ...(fence.language && { language: fence.language }),\n code: lines\n .slice(index + 1, end)\n .map((line) => stripIndent(line, fence.indent))\n .join('\\n'),\n isClosed: end < lines.length,\n });\n index = end;\n }\n return blocks;\n}\n\n/**\n * Returns the contents of the code block that an LLM wrapped its whole response in, or `text` itself when the\n * response does not start with a fence or the fence's language is not one of a nonempty `languages` list\n * (compared case-insensitively). An omitted or empty list allows any language.\n * When the response also ends with a matching closing fence, everything between its first and last lines is returned,\n * so fences the LLM nested without lengthening the outer fence stay in the contents. Otherwise the first block is\n * returned and text after it is dropped; an unclosed block runs to the end of the text.\n */\nexport function extractIfSingleOutermostCodeBlock(text: string, languages?: readonly string[]): string {\n const block = findOutermostCodeBlock(text);\n if (!block) return text;\n if (languages?.length && !languages.some((language) => language.toLowerCase() === block.language)) return text;\n return block.code;\n}\n\nconst WRAPPED_MARKDOWN_LANGUAGES = new Set(['', 'markdown', 'md']);\n\n/**\n * Returns the trimmed contents of the first closed fenced code block (backticks or tildes) that follows a line holding\n * only `<tagName>` in text such as an LLM response, e.g. `<answer>\\n~~~\\n42\\n~~~`, or `undefined` when there is none.\n * Only blank lines may separate the tag line from the opening fence. Mentions of the tag within prose lines and tag\n * lines inside other fenced blocks (e.g. a format example) are skipped, but a whole-response `markdown`, `md`, or\n * unlabeled wrapper is read inside, as `parseMarkdownSections` does.\n */\nexport function extractTaggedCodeBlock(text: string, tagName: string): string | undefined {\n const tag = `<${tagName}>`;\n const found = findTaggedCodeBlock(splitLines(text), tag);\n if (found !== undefined) return found;\n // Only a real wrapper hides every tag line from the scan above; a leading example block paired with the tagged\n // block's closing fence merely looks like one, so its contents must not be read first.\n const wrapper = findOutermostCodeBlock(text);\n if (wrapper?.isWhole && WRAPPED_MARKDOWN_LANGUAGES.has(wrapper.language)) {\n return findTaggedCodeBlock(splitLines(wrapper.code), tag);\n }\n}\n\nfunction findTaggedCodeBlock(lines: readonly string[], tag: string): string | undefined {\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index] ?? '';\n const enclosingFence = parseOpeningFence(line);\n if (enclosingFence) {\n index = findClosingFence(lines, index + 1, enclosingFence);\n continue;\n }\n if (line.trim() !== tag) continue;\n let fenceIndex = index + 1;\n while (fenceIndex < lines.length && !lines[fenceIndex]?.trim()) fenceIndex++;\n const fence = parseOpeningFence(lines[fenceIndex] ?? '');\n if (!fence) continue;\n const end = findClosingFence(lines, fenceIndex + 1, fence);\n if (end < lines.length) {\n return lines\n .slice(fenceIndex + 1, end)\n .map((contentLine) => stripIndent(contentLine, fence.indent))\n .join('\\n')\n .trim();\n }\n }\n}\n\nexport interface MarkdownSection {\n depth: number;\n /** The heading text with the closing `#` sequence and inline-code backticks removed. */\n heading: string;\n /** The source text up to the next heading at the same or a shallower depth, trimmed; subsections are included. */\n content: string;\n}\n\n/**\n * Extracts the content of the section headed by each of `names` from Markdown text such as an LLM response, keyed by\n * the given names. A heading matches a name when both are equal after removing decorations at their edges (such as\n * `**bold**`, backticks, and quotes), a leading number like `1.` or `2.1`, and a trailing colon, ignoring case and spacing;\n * headings at any depth are candidates.\n * Among matching headings, the shallowest wins, then one equal to the name as written, then the first; each heading\n * serves at most one name. When distinct requested names normalize alike, only exact matches at the shallowest\n * matching depth are returned. Missing, empty, or ambiguous sections are absent from the prototype-free result.\n */\nexport function extractSections<const Name extends string>(\n markdown: string,\n names: readonly Name[]\n): Partial<Record<Name, string>> {\n const candidates = parseMarkdownSections(markdown).map((section) => ({\n ...section,\n key: normalizeHeading(section.heading),\n }));\n const requested = [...new Set(names)].map((name) => ({ name, key: normalizeHeading(name) }));\n const sections: Partial<Record<Name, string>> = Object.create(null);\n for (const { name, key } of requested) {\n let best: (typeof candidates)[number] | undefined;\n for (const candidate of candidates) {\n if (candidate.key !== key) continue;\n if (\n !best ||\n candidate.depth < best.depth ||\n (candidate.depth === best.depth && candidate.heading === name && best.heading !== name)\n ) {\n best = candidate;\n }\n }\n if (!best) continue;\n if (best.heading !== name && requested.some((other) => other.name !== name && other.key === key)) continue;\n if (best.content) sections[name] = best.content;\n }\n return sections;\n}\n\n/**\n * Returns every ATX heading of Markdown text in order, with the source text under it.\n * Only headings starting at the beginning of a line outside fenced code blocks count; setext headings (underlined\n * with `=` or `-`) are ignored. A whole-response `markdown`, `md`, or unlabeled wrapper is read inside as\n * `extractIfSingleOutermostCodeBlock` finds it, preserving nested fences. A leading wrapper followed by prose is\n * also read inside, but only when there are no headings outside fences.\n */\nexport function parseMarkdownSections(markdown: string): MarkdownSection[] {\n const block = findOutermostCodeBlock(markdown);\n const wrapper = block && WRAPPED_MARKDOWN_LANGUAGES.has(block.language) ? block : undefined;\n if (wrapper?.isWhole) return splitSections(wrapper.code);\n const sections = splitSections(markdown);\n return sections.length === 0 && wrapper ? splitSections(wrapper.code) : sections;\n}\n\nfunction normalizeHeading(heading: string): string {\n return stripHeadingEdges(stripHeadingEdges(heading).replace(/^\\d+(?:\\.\\d+)*(?:\\.\\s+|\\)\\s*|\\s+)/u, ''))\n .replaceAll(/\\s+/gu, ' ')\n .toLowerCase();\n}\n\nfunction stripHeadingEdges(text: string): string {\n // Only the edges: `_` and `*` inside a heading belong to names such as `my_file.py`.\n return text.replaceAll(/^[\\s*_`~\"'「」]+|[\\s*_`~\"'「」::]+$/gu, '');\n}\n\nfunction splitSections(markdown: string): MarkdownSection[] {\n const lines = splitLines(markdown);\n const headings = findHeadings(lines);\n return headings.map((heading, index) => {\n let nextIndex = index + 1;\n while (nextIndex < headings.length && (headings[nextIndex]?.depth ?? 0) > heading.depth) nextIndex++;\n return {\n depth: heading.depth,\n heading: heading.text,\n content: lines\n .slice(heading.index + 1, headings[nextIndex]?.index)\n .join('\\n')\n .trim(),\n };\n });\n}\n\nfunction findOutermostCodeBlock(text: string): { language: string; code: string; isWhole: boolean } | undefined {\n // Keep the opening fence's indentation, which is stripped from the contents.\n const lines = splitLines(text.replace(/^\\s*\\n/u, '').trimEnd());\n const fence = parseOpeningFence(lines[0] ?? '');\n if (!fence) return;\n const lastIndex = lines.length - 1;\n const end =\n lastIndex > 0 && isClosingFence(lines[lastIndex] ?? '', fence) ? lastIndex : findClosingFence(lines, 1, fence);\n return {\n language: fence.language,\n code: lines\n .slice(1, end)\n .map((line) => stripIndent(line, fence.indent))\n .join('\\n'),\n isWhole: end >= lastIndex,\n };\n}\n\nfunction findHeadings(lines: readonly string[]): { index: number; depth: number; text: string }[] {\n const headings: { index: number; depth: number; text: string }[] = [];\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index] ?? '';\n const fence = parseOpeningFence(line);\n if (fence) {\n index = findClosingFence(lines, index + 1, fence);\n continue;\n }\n const match = /^(#{1,6})(?:[ \\t]+(.*))?$/u.exec(line);\n if (!match) continue;\n const text = (match[2] ?? '')\n // The closing sequence of an ATX heading needs a space before it, so `# C#` keeps its `#`.\n .replace(/(?:^|[ \\t]+)#+[ \\t]*$/u, '')\n .trim()\n .replaceAll(/(`+)(.+?)\\1/gu, (_match, _delimiter: string, content: string) =>\n content.startsWith(' ') && content.endsWith(' ') ? content.slice(1, -1) : content\n );\n headings.push({ index, depth: match[1]?.length ?? 1, text });\n }\n return headings;\n}\n\nfunction parseOpeningFence(line: string): Fence | undefined {\n const match = /^( {0,3})(`{3,}|~{3,})(.*)$/u.exec(line);\n const fence = match?.[2];\n const info = match?.[3] ?? '';\n // A backtick in the info string makes the line inline code instead of a fence.\n if (!fence || (fence.startsWith('`') && info.includes('`'))) return;\n return {\n char: fence.charAt(0),\n length: fence.length,\n indent: match[1]?.length ?? 0,\n language: info.trim().split(/\\s/u)[0]?.toLowerCase() ?? '',\n };\n}\n\n/** Returns the index of the line closing `fence`, or the number of lines when it is never closed. */\nfunction findClosingFence(lines: readonly string[], start: number, fence: Fence): number {\n let index = start;\n while (index < lines.length && !isClosingFence(lines[index] ?? '', fence)) index++;\n return index;\n}\n\nfunction isClosingFence(line: string, fence: Fence): boolean {\n const closer = /^ {0,3}(`{3,}|~{3,})[ \\t]*$/u.exec(line)?.[1];\n return closer?.charAt(0) === fence.char && closer.length >= fence.length;\n}\n\nfunction stripIndent(line: string, indent: number): string {\n let index = 0;\n while (index < indent && line[index] === ' ') index++;\n return line.slice(index);\n}\n\nfunction splitLines(text: string): string[] {\n return text.split(/\\r?\\n/u);\n}\n"],"mappings":"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"}
|
package/dist/text.cjs
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
"use strict";function e(e){return e.replaceAll(/[$()*+.?[\\\]^{|}]/gu,String.raw`\$&`)}function t(e){return/^[\w./-]+$/u.test(e)?e:`'${e.replaceAll(`'`,String.raw`'\''`)}'`}function n(e,t=``){return a(`~`,e,t)}function r(e,t=``){return a("`",e,t)}function i(e){return o(`~`,e)}function a(e,t,n){let r=0;for(;n[r]===e;)r++;let i=o(e,t)+e.repeat(r);return`${i}${n.slice(r)}\n${t}${t.endsWith(`
|
|
2
2
|
`)?``:`
|
|
3
|
-
`}${i}`}function o(e,t){let n=e.repeat(3),r=2,i=t.indexOf(n);for(;i!==-1;){let a=i+3;for(;t[a]===e;)a++;r=Math.max(r,a-i),i=t.indexOf(n,a)}return e.repeat(r+1)}function s(e,t
|
|
3
|
+
`}${i}`}function o(e,t){let n=e.repeat(3),r=2,i=t.indexOf(n);for(;i!==-1;){let a=i+3;for(;t[a]===e;)a++;r=Math.max(r,a-i),i=t.indexOf(n,a)}return e.repeat(r+1)}function s(e,t){if(t.length<3||f(t,`~`)!==t.length)throw RangeError(`fence must be a run of at least three tildes: ${t}`);let n=(e.startsWith(``)?e.slice(1):e).replaceAll(`\r
|
|
4
|
+
`,`
|
|
5
|
+
`).split(`
|
|
6
|
+
`);return c(n,t.length)??l(n)}function c(e,t){let n=e.findIndex(e=>f(e,`~`)>=t);if(n===-1)return;let r=f(e[n]??``,`~`),i=e.findLastIndex((e,t)=>t>n&&d(e,`~`,r));if(i!==-1)return e.slice(n+1,i).join(`
|
|
7
|
+
`)}function l(e){let t=e.findIndex(e=>e.trim()!==``),n=e.findLastIndex(e=>e.trim()!==``);if(t===n)return;let r=e[t]??``,i=f(r,"`");if(i<3||!u(r.slice(i))||!d(e[n]??``,"`",i))return;let a=e.slice(t+1,n);if(!a.some(e=>f(e,"`")>=i))return a.join(`
|
|
8
|
+
`)}function u(e){let t=e.trim(),n=0;for(;n<t.length&&t[n]!==` `&&t[n]!==` `;)n++;let r=t.slice(0,n).toLowerCase();return r===``||r===`md`||r===`markdown`}function d(e,t,n){let r=e.trimEnd();return r.length>=n&&f(r,t)===r.length}function f(e,t){let n=0;for(;e[n]===t;)n++;return n}function p(e,t,n=`…`){if(e.length<=t)return e;if(t<n.length)throw RangeError(`maxLength (${t}) must be at least the ellipsis length (${n.length})`);return`${m(e,t-n.length)}${n}`}function m(e,t){let n=e.charCodeAt(t-1);return e.slice(0,n>=55296&&n<=56319?t-1:t)}const h={'"':`"`,"&":`&`,"'":`'`,"<":`<`,">":`>`},g=/["&'<>]/gu;function _(e){return e.search(g)===-1?e:e.replaceAll(g,e=>h[e])}exports.escapeHtml=_,exports.escapeRegExp=e,exports.getTildeFence=i,exports.quoteForShell=t,exports.sliceWithoutSplittingSurrogates=m,exports.toCodeBlock=r,exports.toTildeCodeBlock=n,exports.truncate=p,exports.unwrapFencedReply=s;
|
|
4
9
|
//# sourceMappingURL=text.cjs.map
|
package/dist/text.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.cjs","names":["escapeRegExp","text","quoteForShell","value","toTildeCodeBlock","toFencedCodeBlock","language","toCodeBlock","getTildeFence","getFence","languageStart","fenceChar","fence","shortestFence","longestRun","start","end","truncate","maxLength","ellipsis","sliceWithoutSplittingSurrogates","lastKeptUnit","htmlEntities","htmlSpecialCharactersPattern","escapeHtml","character"],"sources":["../src/text.ts"],"sourcesContent":["/** Escapes every RegExp metacharacter so the text matches itself literally. */\nexport function escapeRegExp(text: string): string {\n return text.replaceAll(/[$()*+.?[\\\\\\]^{|}]/gu, String.raw`\\$&`);\n}\n\n/** A POSIX shell word: safe characters pass through, anything else is single-quoted. */\nexport function quoteForShell(value: string): string {\n return /^[\\w./-]+$/u.test(value) ? value : `'${value.replaceAll(\"'\", String.raw`'\\''`)}'`;\n}\n\n/**\n * Wraps text in a Markdown code block fenced with tildes, which are rarer than backticks in Markdown content.\n * The fence is longer than any run of tildes in the text so that the text cannot close it.\n */\nexport function toTildeCodeBlock(text: string, language = ''): string {\n return toFencedCodeBlock('~', text, language);\n}\n\n/**\n * Wraps text in a Markdown code block fenced with backticks.\n * The fence is longer than any run of backticks in the text so that the text cannot close it.\n */\nexport function toCodeBlock(text: string, language = ''): string {\n return toFencedCodeBlock('`', text, language);\n}\n\n/** Returns the tilde fence `toTildeCodeBlock` wraps `text` in: at least `~~~`, longer than any run of tildes in it. */\nexport function getTildeFence(text: string): string {\n return getFence('~', text);\n}\n\nfunction toFencedCodeBlock(fenceChar: string, text: string, language: string): string {\n let languageStart = 0;\n while (language[languageStart] === fenceChar) languageStart++;\n const fence = getFence(fenceChar, text) + fenceChar.repeat(languageStart);\n return `${fence}${language.slice(languageStart)}\\n${text}${text.endsWith('\\n') ? '' : '\\n'}${fence}`;\n}\n\nfunction getFence(fenceChar: string, text: string): string {\n const shortestFence = fenceChar.repeat(3);\n let longestRun = 2;\n let start = text.indexOf(shortestFence);\n while (start !== -1) {\n let end = start + 3;\n while (text[end] === fenceChar) end++;\n longestRun = Math.max(longestRun, end - start);\n start = text.indexOf(shortestFence, end);\n }\n return fenceChar.repeat(longestRun + 1);\n}\n\n/**\n * Shortens text to at most `maxLength` UTF-16 code units, including the `ellipsis` that marks the cut.\n * Throws a `RangeError` when `maxLength` cannot hold the ellipsis.\n */\nexport function truncate(text: string, maxLength: number, ellipsis = '…'): string {\n if (text.length <= maxLength) return text;\n if (maxLength < ellipsis.length) {\n throw new RangeError(`maxLength (${maxLength}) must be at least the ellipsis length (${ellipsis.length})`);\n }\n return `${sliceWithoutSplittingSurrogates(text, maxLength - ellipsis.length)}${ellipsis}`;\n}\n\n/**\n * Returns the first `end` code units of `text`, one fewer when the last kept unit is a high surrogate, whose low half\n * is either cut off or absent from `text`, because an unpaired surrogate is replaced with U+FFFD by encoders.\n */\nexport function sliceWithoutSplittingSurrogates(text: string, end: number): string {\n // oxlint-disable-next-line unicorn/prefer-code-point -- the check is on a single UTF-16 code unit\n const lastKeptUnit = text.charCodeAt(end - 1);\n return text.slice(0, lastKeptUnit >= 0xd8_00 && lastKeptUnit <= 0xdb_ff ? end - 1 : end);\n}\n\nconst htmlEntities: Readonly<Record<string, string>> = {\n '\"': '"',\n '&': '&',\n \"'\": ''',\n '<': '<',\n '>': '>',\n};\n\nconst htmlSpecialCharactersPattern = /[\"&'<>]/gu;\n\n/** Escapes `&`, `<`, `>`, `\"`, and `'`, so the result is safe in HTML text and in quoted attribute values. */\nexport function escapeHtml(text: string): string {\n // Measured in V8 and JavaScriptCore: a native `search` first is faster than `replaceAll` alone for text without these\n // characters, and one native `replaceAll` beats a per-code-unit loop for text where they are rare.\n return text.search(htmlSpecialCharactersPattern) === -1\n ? text\n : text.replaceAll(htmlSpecialCharactersPattern, (character) => htmlEntities[character] as string);\n}\n"],"mappings":"A,aACA,SAAgBA,EAAa,EAAsB,CACjD,OAAOC,EAAK,WAAW,uBAAwB,OAAO,GAAG,KAAK,CAChE,CAGA,SAAgBC,EAAc,EAAuB,CACnD,MAAO,cAAc,KAAKC,CAAK,EAAIA,EAAQ,IAAIA,EAAM,WAAW,IAAK,OAAO,GAAG,MAAM,EAAE,EACzF,CAMA,SAAgBC,EAAiB,EAAc,EAAW,GAAY,CACpE,OAAOC,EAAkB,IAAKJ,EAAMK,CAAQ,CAC9C,CAMA,SAAgBC,EAAY,EAAc,EAAW,GAAY,CAC/D,OAAOF,EAAkB,IAAKJ,EAAMK,CAAQ,CAC9C,CAGA,SAAgBE,EAAc,EAAsB,CAClD,OAAOC,EAAS,IAAKR,CAAI,CAC3B,CAEA,SAASI,EAAkB,EAAmB,EAAc,EAA0B,CACpF,IAAIK,EAAgB,EACpB,KAAOJ,EAASI,KAAmBC,GAAW,IAC9C,IAAMC,EAAQH,EAASE,EAAWV,CAAI,EAAIU,EAAU,OAAOD,CAAa,EACxE,MAAO,GAAGE,IAAQN,EAAS,MAAMI,CAAa,EAAE,IAAIT,IAAOA,EAAK,SAAS;CAAI,EAAI,GAAK;IAAOW,GAC/F,CAEA,SAASH,EAAS,EAAmB,EAAsB,CACzD,IAAMI,EAAgBF,EAAU,OAAO,CAAC,EACpCG,EAAa,EACbC,EAAQd,EAAK,QAAQY,CAAa,EACtC,KAAOE,IAAU,IAAI,CACnB,IAAIC,EAAMD,EAAQ,EAClB,KAAOd,EAAKe,KAASL,GAAW,IAChC,EAAa,KAAK,IAAIG,EAAYE,EAAMD,CAAK,EAC7C,EAAQd,EAAK,QAAQY,EAAeG,CAAG,CACzC,CACA,OAAOL,EAAU,OAAOG,EAAa,CAAC,CACxC,CAMA,SAAgBG,EAAS,EAAc,EAAmB,EAAW,IAAa,CAChF,GAAIhB,EAAK,QAAUiB,EAAW,OAAOjB,EACrC,GAAIiB,EAAYC,EAAS,OACvB,MAAU,WAAW,cAAcD,EAAU,0CAA0CC,EAAS,OAAO,EAAE,EAE3G,MAAO,GAAGC,EAAgCnB,EAAMiB,EAAYC,EAAS,MAAM,IAAIA,GACjF,CAMA,SAAgBC,EAAgC,EAAc,EAAqB,CAEjF,IAAMC,EAAepB,EAAK,WAAWe,EAAM,CAAC,EAC5C,OAAOf,EAAK,MAAM,EAAGoB,GAAgB,OAAWA,GAAgB,MAAUL,EAAM,EAAIA,CAAG,CACzF,CAEA,MAAMM,EAAiD,CACrD,IAAK,SACL,IAAK,QACL,IAAK,QACL,IAAK,OACL,IAAK,MACP,EAEMC,EAA+B,YAGrC,SAAgBC,EAAW,EAAsB,CAG/C,OAAOvB,EAAK,OAAOsB,CAA4B,IAAM,GACjDtB,EACAA,EAAK,WAAWsB,EAA+B,GAAcD,EAAaG,EAAoB,CACpG,C"}
|
|
1
|
+
{"version":3,"file":"text.cjs","names":["escapeRegExp","text","quoteForShell","value","toTildeCodeBlock","toFencedCodeBlock","language","toCodeBlock","getTildeFence","getFence","languageStart","fenceChar","fence","shortestFence","longestRun","start","end","unwrapFencedReply","countLeading","lines","unwrapTildeWrapper","unwrapBacktickWrapper","line","minLength","openingLength","index","isFenceOnly","opening","length","isMarkdownInfo","inner","trimmed","info","wordEnd","word","count","char","truncate","maxLength","ellipsis","sliceWithoutSplittingSurrogates","lastKeptUnit","htmlEntities","htmlSpecialCharactersPattern","escapeHtml","character"],"sources":["../src/text.ts"],"sourcesContent":["/** Escapes every RegExp metacharacter so the text matches itself literally. */\nexport function escapeRegExp(text: string): string {\n return text.replaceAll(/[$()*+.?[\\\\\\]^{|}]/gu, String.raw`\\$&`);\n}\n\n/** A POSIX shell word: safe characters pass through, anything else is single-quoted. */\nexport function quoteForShell(value: string): string {\n return /^[\\w./-]+$/u.test(value) ? value : `'${value.replaceAll(\"'\", String.raw`'\\''`)}'`;\n}\n\n/**\n * Wraps text in a Markdown code block fenced with tildes, which are rarer than backticks in Markdown content.\n * The fence is longer than any run of tildes in the text so that the text cannot close it.\n */\nexport function toTildeCodeBlock(text: string, language = ''): string {\n return toFencedCodeBlock('~', text, language);\n}\n\n/**\n * Wraps text in a Markdown code block fenced with backticks.\n * The fence is longer than any run of backticks in the text so that the text cannot close it.\n */\nexport function toCodeBlock(text: string, language = ''): string {\n return toFencedCodeBlock('`', text, language);\n}\n\n/** Returns the tilde fence `toTildeCodeBlock` wraps `text` in: at least `~~~`, longer than any run of tildes in it. */\nexport function getTildeFence(text: string): string {\n return getFence('~', text);\n}\n\nfunction toFencedCodeBlock(fenceChar: string, text: string, language: string): string {\n let languageStart = 0;\n while (language[languageStart] === fenceChar) languageStart++;\n const fence = getFence(fenceChar, text) + fenceChar.repeat(languageStart);\n return `${fence}${language.slice(languageStart)}\\n${text}${text.endsWith('\\n') ? '' : '\\n'}${fence}`;\n}\n\nfunction getFence(fenceChar: string, text: string): string {\n const shortestFence = fenceChar.repeat(3);\n let longestRun = 2;\n let start = text.indexOf(shortestFence);\n while (start !== -1) {\n let end = start + 3;\n while (text[end] === fenceChar) end++;\n longestRun = Math.max(longestRun, end - start);\n start = text.indexOf(shortestFence, end);\n }\n return fenceChar.repeat(longestRun + 1);\n}\n\n/**\n * Returns the document an LLM replied with inside the wrapper fence it was asked to keep, or `undefined` when the reply\n * is not wrapped as required so that the caller can retry. `fence` is the tilde fence the document was sent in, e.g.\n * `getTildeFence(document)`; this relies on it being longer than any run of tildes in the document.\n * A leading BOM is removed, CRLF is read as LF, and trailing whitespace is ignored on fence lines. Fences start at the\n * beginning of a line. The result is the lines strictly between the wrapper's fence lines joined with LF, untrimmed,\n * so a final newline of the document is not kept.\n * - Tilde wrapper: the first line starting with at least `fence.length` tildes, followed by any info string, opens it,\n * and the last later line of only tildes, at least as many as the opening run, closes it. Text around it, backtick\n * fence lines included, is dropped.\n * - Otherwise, backtick wrapper: the first nonblank line is a run of at least three backticks with no info string or\n * one whose first word is `md` or `markdown` (case-insensitively), the last nonblank line is only backticks, at least\n * as many, and no line between them starts with that many backticks.\n */\nexport function unwrapFencedReply(text: string, fence: string): string | undefined {\n if (fence.length < 3 || countLeading(fence, '~') !== fence.length) {\n throw new RangeError(`fence must be a run of at least three tildes: ${fence}`);\n }\n const lines = (text.startsWith('\\uFEFF') ? text.slice(1) : text).replaceAll('\\r\\n', '\\n').split('\\n');\n return unwrapTildeWrapper(lines, fence.length) ?? unwrapBacktickWrapper(lines);\n}\n\nfunction unwrapTildeWrapper(lines: readonly string[], minLength: number): string | undefined {\n const start = lines.findIndex((line) => countLeading(line, '~') >= minLength);\n if (start === -1) return;\n const openingLength = countLeading(lines[start] ?? '', '~');\n const end = lines.findLastIndex((line, index) => index > start && isFenceOnly(line, '~', openingLength));\n if (end === -1) return;\n return lines.slice(start + 1, end).join('\\n');\n}\n\nfunction unwrapBacktickWrapper(lines: readonly string[]): string | undefined {\n const start = lines.findIndex((line) => line.trim() !== '');\n const end = lines.findLastIndex((line) => line.trim() !== '');\n if (start === end) return;\n const opening = lines[start] ?? '';\n const length = countLeading(opening, '`');\n if (length < 3 || !isMarkdownInfo(opening.slice(length)) || !isFenceOnly(lines[end] ?? '', '`', length)) return;\n const inner = lines.slice(start + 1, end);\n if (inner.some((line) => countLeading(line, '`') >= length)) return;\n return inner.join('\\n');\n}\n\nfunction isMarkdownInfo(info: string): boolean {\n const trimmed = info.trim();\n let wordEnd = 0;\n while (wordEnd < trimmed.length && trimmed[wordEnd] !== ' ' && trimmed[wordEnd] !== '\\t') wordEnd++;\n const word = trimmed.slice(0, wordEnd).toLowerCase();\n return word === '' || word === 'md' || word === 'markdown';\n}\n\nfunction isFenceOnly(line: string, fenceChar: string, minLength: number): boolean {\n const trimmed = line.trimEnd();\n return trimmed.length >= minLength && countLeading(trimmed, fenceChar) === trimmed.length;\n}\n\nfunction countLeading(line: string, char: string): number {\n let count = 0;\n while (line[count] === char) count++;\n return count;\n}\n\n/**\n * Shortens text to at most `maxLength` UTF-16 code units, including the `ellipsis` that marks the cut.\n * Throws a `RangeError` when `maxLength` cannot hold the ellipsis.\n */\nexport function truncate(text: string, maxLength: number, ellipsis = '…'): string {\n if (text.length <= maxLength) return text;\n if (maxLength < ellipsis.length) {\n throw new RangeError(`maxLength (${maxLength}) must be at least the ellipsis length (${ellipsis.length})`);\n }\n return `${sliceWithoutSplittingSurrogates(text, maxLength - ellipsis.length)}${ellipsis}`;\n}\n\n/**\n * Returns the first `end` code units of `text`, one fewer when the last kept unit is a high surrogate, whose low half\n * is either cut off or absent from `text`, because an unpaired surrogate is replaced with U+FFFD by encoders.\n */\nexport function sliceWithoutSplittingSurrogates(text: string, end: number): string {\n // oxlint-disable-next-line unicorn/prefer-code-point -- the check is on a single UTF-16 code unit\n const lastKeptUnit = text.charCodeAt(end - 1);\n return text.slice(0, lastKeptUnit >= 0xd8_00 && lastKeptUnit <= 0xdb_ff ? end - 1 : end);\n}\n\nconst htmlEntities: Readonly<Record<string, string>> = {\n '\"': '"',\n '&': '&',\n \"'\": ''',\n '<': '<',\n '>': '>',\n};\n\nconst htmlSpecialCharactersPattern = /[\"&'<>]/gu;\n\n/** Escapes `&`, `<`, `>`, `\"`, and `'`, so the result is safe in HTML text and in quoted attribute values. */\nexport function escapeHtml(text: string): string {\n // Measured in V8 and JavaScriptCore: a native `search` first is faster than `replaceAll` alone for text without these\n // characters, and one native `replaceAll` beats a per-code-unit loop for text where they are rare.\n return text.search(htmlSpecialCharactersPattern) === -1\n ? text\n : text.replaceAll(htmlSpecialCharactersPattern, (character) => htmlEntities[character] as string);\n}\n"],"mappings":"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"}
|
package/dist/text.d.ts
CHANGED
|
@@ -14,6 +14,21 @@ export declare function toTildeCodeBlock(text: string, language?: string): strin
|
|
|
14
14
|
export declare function toCodeBlock(text: string, language?: string): string;
|
|
15
15
|
/** Returns the tilde fence `toTildeCodeBlock` wraps `text` in: at least `~~~`, longer than any run of tildes in it. */
|
|
16
16
|
export declare function getTildeFence(text: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Returns the document an LLM replied with inside the wrapper fence it was asked to keep, or `undefined` when the reply
|
|
19
|
+
* is not wrapped as required so that the caller can retry. `fence` is the tilde fence the document was sent in, e.g.
|
|
20
|
+
* `getTildeFence(document)`; this relies on it being longer than any run of tildes in the document.
|
|
21
|
+
* A leading BOM is removed, CRLF is read as LF, and trailing whitespace is ignored on fence lines. Fences start at the
|
|
22
|
+
* beginning of a line. The result is the lines strictly between the wrapper's fence lines joined with LF, untrimmed,
|
|
23
|
+
* so a final newline of the document is not kept.
|
|
24
|
+
* - Tilde wrapper: the first line starting with at least `fence.length` tildes, followed by any info string, opens it,
|
|
25
|
+
* and the last later line of only tildes, at least as many as the opening run, closes it. Text around it, backtick
|
|
26
|
+
* fence lines included, is dropped.
|
|
27
|
+
* - Otherwise, backtick wrapper: the first nonblank line is a run of at least three backticks with no info string or
|
|
28
|
+
* one whose first word is `md` or `markdown` (case-insensitively), the last nonblank line is only backticks, at least
|
|
29
|
+
* as many, and no line between them starts with that many backticks.
|
|
30
|
+
*/
|
|
31
|
+
export declare function unwrapFencedReply(text: string, fence: string): string | undefined;
|
|
17
32
|
/**
|
|
18
33
|
* Shortens text to at most `maxLength` UTF-16 code units, including the `ellipsis` that marks the cut.
|
|
19
34
|
* Throws a `RangeError` when `maxLength` cannot hold the ellipsis.
|
package/dist/text.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
function e(e){return e.replaceAll(/[$()*+.?[\\\]^{|}]/gu,String.raw`\$&`)}function t(e){return/^[\w./-]+$/u.test(e)?e:`'${e.replaceAll(`'`,String.raw`'\''`)}'`}function n(e,t=``){return a(`~`,e,t)}function r(e,t=``){return a("`",e,t)}function i(e){return o(`~`,e)}function a(e,t,n){let r=0;for(;n[r]===e;)r++;let i=o(e,t)+e.repeat(r);return`${i}${n.slice(r)}\n${t}${t.endsWith(`
|
|
2
2
|
`)?``:`
|
|
3
|
-
`}${i}`}function o(e,t){let n=e.repeat(3),r=2,i=t.indexOf(n);for(;i!==-1;){let a=i+3;for(;t[a]===e;)a++;r=Math.max(r,a-i),i=t.indexOf(n,a)}return e.repeat(r+1)}function s(e,t
|
|
3
|
+
`}${i}`}function o(e,t){let n=e.repeat(3),r=2,i=t.indexOf(n);for(;i!==-1;){let a=i+3;for(;t[a]===e;)a++;r=Math.max(r,a-i),i=t.indexOf(n,a)}return e.repeat(r+1)}function s(e,t){if(t.length<3||f(t,`~`)!==t.length)throw RangeError(`fence must be a run of at least three tildes: ${t}`);let n=(e.startsWith(``)?e.slice(1):e).replaceAll(`\r
|
|
4
|
+
`,`
|
|
5
|
+
`).split(`
|
|
6
|
+
`);return c(n,t.length)??l(n)}function c(e,t){let n=e.findIndex(e=>f(e,`~`)>=t);if(n===-1)return;let r=f(e[n]??``,`~`),i=e.findLastIndex((e,t)=>t>n&&d(e,`~`,r));if(i!==-1)return e.slice(n+1,i).join(`
|
|
7
|
+
`)}function l(e){let t=e.findIndex(e=>e.trim()!==``),n=e.findLastIndex(e=>e.trim()!==``);if(t===n)return;let r=e[t]??``,i=f(r,"`");if(i<3||!u(r.slice(i))||!d(e[n]??``,"`",i))return;let a=e.slice(t+1,n);if(!a.some(e=>f(e,"`")>=i))return a.join(`
|
|
8
|
+
`)}function u(e){let t=e.trim(),n=0;for(;n<t.length&&t[n]!==` `&&t[n]!==` `;)n++;let r=t.slice(0,n).toLowerCase();return r===``||r===`md`||r===`markdown`}function d(e,t,n){let r=e.trimEnd();return r.length>=n&&f(r,t)===r.length}function f(e,t){let n=0;for(;e[n]===t;)n++;return n}function p(e,t,n=`…`){if(e.length<=t)return e;if(t<n.length)throw RangeError(`maxLength (${t}) must be at least the ellipsis length (${n.length})`);return`${m(e,t-n.length)}${n}`}function m(e,t){let n=e.charCodeAt(t-1);return e.slice(0,n>=55296&&n<=56319?t-1:t)}const h={'"':`"`,"&":`&`,"'":`'`,"<":`<`,">":`>`},g=/["&'<>]/gu;function _(e){return e.search(g)===-1?e:e.replaceAll(g,e=>h[e])}export{_ as escapeHtml,e as escapeRegExp,i as getTildeFence,t as quoteForShell,m as sliceWithoutSplittingSurrogates,r as toCodeBlock,n as toTildeCodeBlock,p as truncate,s as unwrapFencedReply};
|
|
4
9
|
//# sourceMappingURL=text.js.map
|
package/dist/text.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.js","names":["escapeRegExp","text","quoteForShell","value","toTildeCodeBlock","toFencedCodeBlock","language","toCodeBlock","getTildeFence","getFence","languageStart","fenceChar","fence","shortestFence","longestRun","start","end","truncate","maxLength","ellipsis","sliceWithoutSplittingSurrogates","lastKeptUnit","htmlEntities","htmlSpecialCharactersPattern","escapeHtml","character"],"sources":["../src/text.ts"],"sourcesContent":["/** Escapes every RegExp metacharacter so the text matches itself literally. */\nexport function escapeRegExp(text: string): string {\n return text.replaceAll(/[$()*+.?[\\\\\\]^{|}]/gu, String.raw`\\$&`);\n}\n\n/** A POSIX shell word: safe characters pass through, anything else is single-quoted. */\nexport function quoteForShell(value: string): string {\n return /^[\\w./-]+$/u.test(value) ? value : `'${value.replaceAll(\"'\", String.raw`'\\''`)}'`;\n}\n\n/**\n * Wraps text in a Markdown code block fenced with tildes, which are rarer than backticks in Markdown content.\n * The fence is longer than any run of tildes in the text so that the text cannot close it.\n */\nexport function toTildeCodeBlock(text: string, language = ''): string {\n return toFencedCodeBlock('~', text, language);\n}\n\n/**\n * Wraps text in a Markdown code block fenced with backticks.\n * The fence is longer than any run of backticks in the text so that the text cannot close it.\n */\nexport function toCodeBlock(text: string, language = ''): string {\n return toFencedCodeBlock('`', text, language);\n}\n\n/** Returns the tilde fence `toTildeCodeBlock` wraps `text` in: at least `~~~`, longer than any run of tildes in it. */\nexport function getTildeFence(text: string): string {\n return getFence('~', text);\n}\n\nfunction toFencedCodeBlock(fenceChar: string, text: string, language: string): string {\n let languageStart = 0;\n while (language[languageStart] === fenceChar) languageStart++;\n const fence = getFence(fenceChar, text) + fenceChar.repeat(languageStart);\n return `${fence}${language.slice(languageStart)}\\n${text}${text.endsWith('\\n') ? '' : '\\n'}${fence}`;\n}\n\nfunction getFence(fenceChar: string, text: string): string {\n const shortestFence = fenceChar.repeat(3);\n let longestRun = 2;\n let start = text.indexOf(shortestFence);\n while (start !== -1) {\n let end = start + 3;\n while (text[end] === fenceChar) end++;\n longestRun = Math.max(longestRun, end - start);\n start = text.indexOf(shortestFence, end);\n }\n return fenceChar.repeat(longestRun + 1);\n}\n\n/**\n * Shortens text to at most `maxLength` UTF-16 code units, including the `ellipsis` that marks the cut.\n * Throws a `RangeError` when `maxLength` cannot hold the ellipsis.\n */\nexport function truncate(text: string, maxLength: number, ellipsis = '…'): string {\n if (text.length <= maxLength) return text;\n if (maxLength < ellipsis.length) {\n throw new RangeError(`maxLength (${maxLength}) must be at least the ellipsis length (${ellipsis.length})`);\n }\n return `${sliceWithoutSplittingSurrogates(text, maxLength - ellipsis.length)}${ellipsis}`;\n}\n\n/**\n * Returns the first `end` code units of `text`, one fewer when the last kept unit is a high surrogate, whose low half\n * is either cut off or absent from `text`, because an unpaired surrogate is replaced with U+FFFD by encoders.\n */\nexport function sliceWithoutSplittingSurrogates(text: string, end: number): string {\n // oxlint-disable-next-line unicorn/prefer-code-point -- the check is on a single UTF-16 code unit\n const lastKeptUnit = text.charCodeAt(end - 1);\n return text.slice(0, lastKeptUnit >= 0xd8_00 && lastKeptUnit <= 0xdb_ff ? end - 1 : end);\n}\n\nconst htmlEntities: Readonly<Record<string, string>> = {\n '\"': '"',\n '&': '&',\n \"'\": ''',\n '<': '<',\n '>': '>',\n};\n\nconst htmlSpecialCharactersPattern = /[\"&'<>]/gu;\n\n/** Escapes `&`, `<`, `>`, `\"`, and `'`, so the result is safe in HTML text and in quoted attribute values. */\nexport function escapeHtml(text: string): string {\n // Measured in V8 and JavaScriptCore: a native `search` first is faster than `replaceAll` alone for text without these\n // characters, and one native `replaceAll` beats a per-code-unit loop for text where they are rare.\n return text.search(htmlSpecialCharactersPattern) === -1\n ? text\n : text.replaceAll(htmlSpecialCharactersPattern, (character) => htmlEntities[character] as string);\n}\n"],"mappings":"AACA,SAAgBA,EAAa,EAAsB,CACjD,OAAOC,EAAK,WAAW,uBAAwB,OAAO,GAAG,KAAK,CAChE,CAGA,SAAgBC,EAAc,EAAuB,CACnD,MAAO,cAAc,KAAKC,CAAK,EAAIA,EAAQ,IAAIA,EAAM,WAAW,IAAK,OAAO,GAAG,MAAM,EAAE,EACzF,CAMA,SAAgBC,EAAiB,EAAc,EAAW,GAAY,CACpE,OAAOC,EAAkB,IAAKJ,EAAMK,CAAQ,CAC9C,CAMA,SAAgBC,EAAY,EAAc,EAAW,GAAY,CAC/D,OAAOF,EAAkB,IAAKJ,EAAMK,CAAQ,CAC9C,CAGA,SAAgBE,EAAc,EAAsB,CAClD,OAAOC,EAAS,IAAKR,CAAI,CAC3B,CAEA,SAASI,EAAkB,EAAmB,EAAc,EAA0B,CACpF,IAAIK,EAAgB,EACpB,KAAOJ,EAASI,KAAmBC,GAAW,IAC9C,IAAMC,EAAQH,EAASE,EAAWV,CAAI,EAAIU,EAAU,OAAOD,CAAa,EACxE,MAAO,GAAGE,IAAQN,EAAS,MAAMI,CAAa,EAAE,IAAIT,IAAOA,EAAK,SAAS;CAAI,EAAI,GAAK;IAAOW,GAC/F,CAEA,SAASH,EAAS,EAAmB,EAAsB,CACzD,IAAMI,EAAgBF,EAAU,OAAO,CAAC,EACpCG,EAAa,EACbC,EAAQd,EAAK,QAAQY,CAAa,EACtC,KAAOE,IAAU,IAAI,CACnB,IAAIC,EAAMD,EAAQ,EAClB,KAAOd,EAAKe,KAASL,GAAW,IAChC,EAAa,KAAK,IAAIG,EAAYE,EAAMD,CAAK,EAC7C,EAAQd,EAAK,QAAQY,EAAeG,CAAG,CACzC,CACA,OAAOL,EAAU,OAAOG,EAAa,CAAC,CACxC,CAMA,SAAgBG,EAAS,EAAc,EAAmB,EAAW,IAAa,CAChF,GAAIhB,EAAK,QAAUiB,EAAW,OAAOjB,EACrC,GAAIiB,EAAYC,EAAS,OACvB,MAAU,WAAW,cAAcD,EAAU,0CAA0CC,EAAS,OAAO,EAAE,EAE3G,MAAO,GAAGC,EAAgCnB,EAAMiB,EAAYC,EAAS,MAAM,IAAIA,GACjF,CAMA,SAAgBC,EAAgC,EAAc,EAAqB,CAEjF,IAAMC,EAAepB,EAAK,WAAWe,EAAM,CAAC,EAC5C,OAAOf,EAAK,MAAM,EAAGoB,GAAgB,OAAWA,GAAgB,MAAUL,EAAM,EAAIA,CAAG,CACzF,CAEA,MAAMM,EAAiD,CACrD,IAAK,SACL,IAAK,QACL,IAAK,QACL,IAAK,OACL,IAAK,MACP,EAEMC,EAA+B,YAGrC,SAAgBC,EAAW,EAAsB,CAG/C,OAAOvB,EAAK,OAAOsB,CAA4B,IAAM,GACjDtB,EACAA,EAAK,WAAWsB,EAA+B,GAAcD,EAAaG,EAAoB,CACpG,C"}
|
|
1
|
+
{"version":3,"file":"text.js","names":["escapeRegExp","text","quoteForShell","value","toTildeCodeBlock","toFencedCodeBlock","language","toCodeBlock","getTildeFence","getFence","languageStart","fenceChar","fence","shortestFence","longestRun","start","end","unwrapFencedReply","countLeading","lines","unwrapTildeWrapper","unwrapBacktickWrapper","line","minLength","openingLength","index","isFenceOnly","opening","length","isMarkdownInfo","inner","trimmed","info","wordEnd","word","count","char","truncate","maxLength","ellipsis","sliceWithoutSplittingSurrogates","lastKeptUnit","htmlEntities","htmlSpecialCharactersPattern","escapeHtml","character"],"sources":["../src/text.ts"],"sourcesContent":["/** Escapes every RegExp metacharacter so the text matches itself literally. */\nexport function escapeRegExp(text: string): string {\n return text.replaceAll(/[$()*+.?[\\\\\\]^{|}]/gu, String.raw`\\$&`);\n}\n\n/** A POSIX shell word: safe characters pass through, anything else is single-quoted. */\nexport function quoteForShell(value: string): string {\n return /^[\\w./-]+$/u.test(value) ? value : `'${value.replaceAll(\"'\", String.raw`'\\''`)}'`;\n}\n\n/**\n * Wraps text in a Markdown code block fenced with tildes, which are rarer than backticks in Markdown content.\n * The fence is longer than any run of tildes in the text so that the text cannot close it.\n */\nexport function toTildeCodeBlock(text: string, language = ''): string {\n return toFencedCodeBlock('~', text, language);\n}\n\n/**\n * Wraps text in a Markdown code block fenced with backticks.\n * The fence is longer than any run of backticks in the text so that the text cannot close it.\n */\nexport function toCodeBlock(text: string, language = ''): string {\n return toFencedCodeBlock('`', text, language);\n}\n\n/** Returns the tilde fence `toTildeCodeBlock` wraps `text` in: at least `~~~`, longer than any run of tildes in it. */\nexport function getTildeFence(text: string): string {\n return getFence('~', text);\n}\n\nfunction toFencedCodeBlock(fenceChar: string, text: string, language: string): string {\n let languageStart = 0;\n while (language[languageStart] === fenceChar) languageStart++;\n const fence = getFence(fenceChar, text) + fenceChar.repeat(languageStart);\n return `${fence}${language.slice(languageStart)}\\n${text}${text.endsWith('\\n') ? '' : '\\n'}${fence}`;\n}\n\nfunction getFence(fenceChar: string, text: string): string {\n const shortestFence = fenceChar.repeat(3);\n let longestRun = 2;\n let start = text.indexOf(shortestFence);\n while (start !== -1) {\n let end = start + 3;\n while (text[end] === fenceChar) end++;\n longestRun = Math.max(longestRun, end - start);\n start = text.indexOf(shortestFence, end);\n }\n return fenceChar.repeat(longestRun + 1);\n}\n\n/**\n * Returns the document an LLM replied with inside the wrapper fence it was asked to keep, or `undefined` when the reply\n * is not wrapped as required so that the caller can retry. `fence` is the tilde fence the document was sent in, e.g.\n * `getTildeFence(document)`; this relies on it being longer than any run of tildes in the document.\n * A leading BOM is removed, CRLF is read as LF, and trailing whitespace is ignored on fence lines. Fences start at the\n * beginning of a line. The result is the lines strictly between the wrapper's fence lines joined with LF, untrimmed,\n * so a final newline of the document is not kept.\n * - Tilde wrapper: the first line starting with at least `fence.length` tildes, followed by any info string, opens it,\n * and the last later line of only tildes, at least as many as the opening run, closes it. Text around it, backtick\n * fence lines included, is dropped.\n * - Otherwise, backtick wrapper: the first nonblank line is a run of at least three backticks with no info string or\n * one whose first word is `md` or `markdown` (case-insensitively), the last nonblank line is only backticks, at least\n * as many, and no line between them starts with that many backticks.\n */\nexport function unwrapFencedReply(text: string, fence: string): string | undefined {\n if (fence.length < 3 || countLeading(fence, '~') !== fence.length) {\n throw new RangeError(`fence must be a run of at least three tildes: ${fence}`);\n }\n const lines = (text.startsWith('\\uFEFF') ? text.slice(1) : text).replaceAll('\\r\\n', '\\n').split('\\n');\n return unwrapTildeWrapper(lines, fence.length) ?? unwrapBacktickWrapper(lines);\n}\n\nfunction unwrapTildeWrapper(lines: readonly string[], minLength: number): string | undefined {\n const start = lines.findIndex((line) => countLeading(line, '~') >= minLength);\n if (start === -1) return;\n const openingLength = countLeading(lines[start] ?? '', '~');\n const end = lines.findLastIndex((line, index) => index > start && isFenceOnly(line, '~', openingLength));\n if (end === -1) return;\n return lines.slice(start + 1, end).join('\\n');\n}\n\nfunction unwrapBacktickWrapper(lines: readonly string[]): string | undefined {\n const start = lines.findIndex((line) => line.trim() !== '');\n const end = lines.findLastIndex((line) => line.trim() !== '');\n if (start === end) return;\n const opening = lines[start] ?? '';\n const length = countLeading(opening, '`');\n if (length < 3 || !isMarkdownInfo(opening.slice(length)) || !isFenceOnly(lines[end] ?? '', '`', length)) return;\n const inner = lines.slice(start + 1, end);\n if (inner.some((line) => countLeading(line, '`') >= length)) return;\n return inner.join('\\n');\n}\n\nfunction isMarkdownInfo(info: string): boolean {\n const trimmed = info.trim();\n let wordEnd = 0;\n while (wordEnd < trimmed.length && trimmed[wordEnd] !== ' ' && trimmed[wordEnd] !== '\\t') wordEnd++;\n const word = trimmed.slice(0, wordEnd).toLowerCase();\n return word === '' || word === 'md' || word === 'markdown';\n}\n\nfunction isFenceOnly(line: string, fenceChar: string, minLength: number): boolean {\n const trimmed = line.trimEnd();\n return trimmed.length >= minLength && countLeading(trimmed, fenceChar) === trimmed.length;\n}\n\nfunction countLeading(line: string, char: string): number {\n let count = 0;\n while (line[count] === char) count++;\n return count;\n}\n\n/**\n * Shortens text to at most `maxLength` UTF-16 code units, including the `ellipsis` that marks the cut.\n * Throws a `RangeError` when `maxLength` cannot hold the ellipsis.\n */\nexport function truncate(text: string, maxLength: number, ellipsis = '…'): string {\n if (text.length <= maxLength) return text;\n if (maxLength < ellipsis.length) {\n throw new RangeError(`maxLength (${maxLength}) must be at least the ellipsis length (${ellipsis.length})`);\n }\n return `${sliceWithoutSplittingSurrogates(text, maxLength - ellipsis.length)}${ellipsis}`;\n}\n\n/**\n * Returns the first `end` code units of `text`, one fewer when the last kept unit is a high surrogate, whose low half\n * is either cut off or absent from `text`, because an unpaired surrogate is replaced with U+FFFD by encoders.\n */\nexport function sliceWithoutSplittingSurrogates(text: string, end: number): string {\n // oxlint-disable-next-line unicorn/prefer-code-point -- the check is on a single UTF-16 code unit\n const lastKeptUnit = text.charCodeAt(end - 1);\n return text.slice(0, lastKeptUnit >= 0xd8_00 && lastKeptUnit <= 0xdb_ff ? end - 1 : end);\n}\n\nconst htmlEntities: Readonly<Record<string, string>> = {\n '\"': '"',\n '&': '&',\n \"'\": ''',\n '<': '<',\n '>': '>',\n};\n\nconst htmlSpecialCharactersPattern = /[\"&'<>]/gu;\n\n/** Escapes `&`, `<`, `>`, `\"`, and `'`, so the result is safe in HTML text and in quoted attribute values. */\nexport function escapeHtml(text: string): string {\n // Measured in V8 and JavaScriptCore: a native `search` first is faster than `replaceAll` alone for text without these\n // characters, and one native `replaceAll` beats a per-code-unit loop for text where they are rare.\n return text.search(htmlSpecialCharactersPattern) === -1\n ? text\n : text.replaceAll(htmlSpecialCharactersPattern, (character) => htmlEntities[character] as string);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@willbooster/shared-lib",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "11.1.0",
|
|
4
4
|
"description": "Shared library for WillBooster projects",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "WillBooster Inc.",
|
|
@@ -50,13 +50,13 @@
|
|
|
50
50
|
"@tsconfig/bun": "1.0.11",
|
|
51
51
|
"@tsconfig/node-lts": "24.0.1",
|
|
52
52
|
"@tsconfig/node-ts": "23.6.4",
|
|
53
|
-
"@types/bun": "1.4.
|
|
53
|
+
"@types/bun": "1.4.4",
|
|
54
54
|
"@types/node": "26.6.5",
|
|
55
55
|
"@willbooster/oxlint-config": "1.4.9",
|
|
56
|
-
"@willbooster/wb": "
|
|
56
|
+
"@willbooster/wb": "23.0.2",
|
|
57
57
|
"build-ts": "21.0.29",
|
|
58
|
-
"oxlint": "1.
|
|
59
|
-
"oxlint-tsgolint": "7.0.
|
|
58
|
+
"oxlint": "1.89.0",
|
|
59
|
+
"oxlint-tsgolint": "7.0.2005",
|
|
60
60
|
"yaml": "2.9.1"
|
|
61
61
|
}
|
|
62
62
|
}
|