@smartmemory/compose 0.2.21-beta → 0.2.22-beta

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.
@@ -32,7 +32,7 @@ import fs from 'node:fs';
32
32
  import path from 'node:path';
33
33
  import { fileURLToPath } from 'node:url';
34
34
  import { FEATURE_CODE_RE_STRICT, validateCode } from './feature-code.js';
35
- import { parseRoadmap } from './roadmap-parser.js';
35
+ import { parseRoadmap, splitRoadmapCells } from './roadmap-parser.js';
36
36
  import { listFeatures, readFeature } from './feature-json.js';
37
37
  import { loadExternalPrefixes } from './project-paths.js';
38
38
  import { checkRoundtrip, LOSSY_LABELS } from './roadmap-roundtrip.js';
@@ -161,7 +161,9 @@ export function loadValidationContext(cwd, options = {}) {
161
161
  const rowMatch = rawLine.match(/^\|(.+)\|\s*$/);
162
162
  if (!rowMatch) { inTable = false; sawSeparator = false; continue; }
163
163
 
164
- const cols = rowMatch[1].split('|').map((c) => c.trim());
164
+ // Escaped-pipe-aware split (COMP-MCP-VALIDATE-4): a `\|` in a description
165
+ // cell must not shift status-column detection and read prose as the status.
166
+ const cols = splitRoadmapCells(rawLine);
165
167
 
166
168
  // Detect header row by column names. Recognize common column-name variants
167
169
  // (feature/code/item/name) and (status/state) so non-canonical tables that
@@ -19,6 +19,7 @@ import { join, resolve, dirname } from 'path';
19
19
  import { fileURLToPath } from 'url';
20
20
  import { SchemaValidator } from '../server/schema-validator.js';
21
21
  import { FEATURE_CODE_RE_STRICT } from './feature-code.js';
22
+ import { splitRoadmapCells } from './roadmap-parser.js';
22
23
 
23
24
  const __dirname = dirname(fileURLToPath(import.meta.url));
24
25
  const SCHEMA_PATH = resolve(__dirname, '../contracts/feature-json.schema.json');
@@ -108,7 +109,9 @@ export function scanRoadmapRows(roadmapPath) {
108
109
  if (/^##\s+/.test(rawLine)) { inTable = false; sawSeparator = false; codeIdx = statusIdx = -1; continue; }
109
110
  const rowMatch = rawLine.match(/^\|(.+)\|\s*$/);
110
111
  if (!rowMatch) { inTable = false; sawSeparator = false; continue; }
111
- const cols = rowMatch[1].split('|').map((c) => c.trim());
112
+ // Escaped-pipe-aware split (COMP-MCP-VALIDATE-4): keep column detection stable
113
+ // when a description cell contains `\|`.
114
+ const cols = splitRoadmapCells(rawLine);
112
115
  const lower = cols.map((c) => c.toLowerCase());
113
116
  const featureColIdx = lower.findIndex((c) => ['feature', 'code', 'item', 'name'].includes(c));
114
117
  const statusColIdx = lower.findIndex((c) => ['status', 'state'].includes(c));
@@ -21,6 +21,24 @@ const MILESTONE_HEADING_RE = /^###\s+(.+?)(?:\s*:\s*(.+))?$/;
21
21
  const TABLE_ROW_RE = /^\|(.+)\|$/;
22
22
  const FENCE_RE = /^```/;
23
23
 
24
+ /**
25
+ * Split a markdown table row into trimmed cells, honoring escaped pipes.
26
+ * Splits on UNESCAPED `|` only (`/(?<!\\)\|/`), drops the leading/trailing empty
27
+ * cells from the outer pipes, and unescapes `\|` → `|` in each cell. Symmetric
28
+ * with `escCell()` in roadmap-gen.js. For pipe-free rows this is identical to a
29
+ * naive `split('|')`.
30
+ *
31
+ * The canonical row splitter — every ROADMAP-row parse site (validator,
32
+ * write-guard, this parser) must use it so a `\|` in a description cell can never
33
+ * shift status-column detection (COMP-MCP-VALIDATE-4).
34
+ *
35
+ * @param {string} rawLine - A full table row line, e.g. "| a | b \\| c | PLANNED |"
36
+ * @returns {string[]} trimmed, unescaped cells
37
+ */
38
+ export function splitRoadmapCells(rawLine) {
39
+ return rawLine.trim().split(/(?<!\\)\|/).slice(1, -1).map((c) => c.trim().replace(/\\\|/g, '|'));
40
+ }
41
+
24
42
  /**
25
43
  * @typedef {{ code: string, description: string, status: string, phaseId: string, position: number }} FeatureEntry
26
44
  */
@@ -112,10 +130,8 @@ export function parseRoadmap(text) {
112
130
  continue;
113
131
  }
114
132
 
115
- // Split on UNESCAPED pipes only, then unescape `\|` → `|` in each cell.
116
- // Symmetric with escCell() in roadmap-gen.js. For pipe-free rows this is
117
- // identical to split('|') (no backslashes, no lookbehind matches).
118
- const cells = trimmed.split(/(?<!\\)\|/).slice(1, -1).map(c => c.trim().replace(/\\\|/g, '|'));
133
+ // Escaped-pipe-aware cell split (the canonical splitter see splitRoadmapCells).
134
+ const cells = splitRoadmapCells(trimmed);
119
135
 
120
136
  // Skip separator rows (|---|---|---|)
121
137
  if (cells.every(c => /^[-:]+$/.test(c))) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartmemory/compose",
3
- "version": "0.2.21-beta",
3
+ "version": "0.2.22-beta",
4
4
  "description": "Structured AI dev pipeline — goal-to-product orchestration with gates, iteration loops, and feature lifecycle management.",
5
5
  "author": "SmartMemory",
6
6
  "license": "MIT",