@relvehq/agent-ready 0.3.0 → 0.3.1

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.
@@ -1,6 +1,6 @@
1
1
  import type { PageInput } from "./types";
2
2
  import type { PromptSettings } from "./prompts";
3
- export type IssueKind = "number" | "url" | "frontmatter" | "hash" | "section" | "h1" | "html" | "placeholder" | "tokens" | "empty";
3
+ export type IssueKind = "number" | "url" | "frontmatter" | "hash" | "section" | "h1" | "html" | "placeholder" | "tokens" | "empty" | "plan";
4
4
  export interface CheckIssue {
5
5
  kind: IssueKind;
6
6
  detail: string;
@@ -15,3 +15,5 @@ export interface CheckOptions {
15
15
  countTokens?: (text: string) => number;
16
16
  }
17
17
  export declare function checkDoc(doc: string, input: PageInput, settings: PromptSettings, opts?: CheckOptions): CheckResult;
18
+ /** First-column names of the tables under "## Pricing" / "## Plans" headings. */
19
+ export declare function planNames(prose: string): string[];
@@ -12,6 +12,9 @@
12
12
  // h1 not exactly one H1
13
13
  // html HTML tags in the document
14
14
  // placeholder a {{PLACEHOLDER}} or <Template field> left in
15
+ // plan a plan named in a "## Pricing" / "## Plans" table that the
16
+ // input never names (e.g. a made-up "Basic" built from the
17
+ // prices of other products shown on the page; 2026-10-29)
15
18
  // tokens over the prompt's token budget
16
19
  // empty no real body
17
20
  //
@@ -20,6 +23,7 @@
20
23
  // EDGE-SAFE (pure). It cannot catch a reworded claim; people still read.
21
24
  Object.defineProperty(exports, "__esModule", { value: true });
22
25
  exports.checkDoc = checkDoc;
26
+ exports.planNames = planNames;
23
27
  const enrich_1 = require("../core/enrich");
24
28
  const URL_RE = /https?:\/\/[^\s<>()[\]|"'`]+/g;
25
29
  const NUM_RE = /\d+(?:[.,]\d+)*/g;
@@ -100,8 +104,47 @@ function checkDoc(doc, input, settings, opts = {}) {
100
104
  seen.add(c);
101
105
  add("number", n);
102
106
  }
107
+ // Plans: every plan a Pricing / Plans table names must be named in the
108
+ // input. The number check alone passes a plan stitched together from
109
+ // prices that are on the page but belong to something else.
110
+ for (const name of planNames(prose)) {
111
+ if (!namedIn(haystack, name))
112
+ add("plan", name);
113
+ }
103
114
  return { ok: issues.length === 0, issues, tokens };
104
115
  }
116
+ /** First-column names of the tables under "## Pricing" / "## Plans" headings. */
117
+ function planNames(prose) {
118
+ const names = [];
119
+ for (const section of prose.split(/^(?=##\s)/m)) {
120
+ const heading = /^##\s+(.+)$/m.exec(section);
121
+ if (!heading || !/^(pricing|plans)\b/i.test(heading[1].trim()))
122
+ continue;
123
+ let header = true;
124
+ for (const line of section.split("\n")) {
125
+ if (!line.trim().startsWith("|")) {
126
+ header = true; // a new table starts with its own header row
127
+ continue;
128
+ }
129
+ if (/^\|[\s:|-]+\|?\s*$/.test(line.trim()))
130
+ continue; // separator row
131
+ if (header) {
132
+ header = false;
133
+ continue;
134
+ }
135
+ const cell = line.trim().replace(/^\|/, "").split("|")[0] ?? "";
136
+ const name = cell.replace(/[*_`]/g, "").replace(/\s+/g, " ").trim();
137
+ if (name)
138
+ names.push(name);
139
+ }
140
+ }
141
+ return names;
142
+ }
143
+ /** The name appears in the text as a whole word or phrase, ignoring case and spacing. */
144
+ function namedIn(text, name) {
145
+ const words = name.split(" ").map(escapeRe).join("\\s+");
146
+ return new RegExp(`(?<![A-Za-z0-9])${words}(?![A-Za-z0-9])`, "i").test(text);
147
+ }
105
148
  /** "1,400" = "1400", "0.10" = "0.1", "05" = "5". */
106
149
  function canonNum(s) {
107
150
  const plain = s.replace(/,(?=\d{3}\b)/g, "");