cclaw-cli 0.51.27 → 0.51.28

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.
@@ -105,6 +105,24 @@ function sectionBodyByHeadingPrefix(sections, prefix) {
105
105
  }
106
106
  return null;
107
107
  }
108
+ /**
109
+ * Build a regex that matches `<field>: <value>` even when the field name
110
+ * and/or value are wrapped in markdown emphasis (`*`, `**`, `_`, `__`).
111
+ *
112
+ * The shipped templates render fields as `- **Field name:** value`, so any
113
+ * structural check that searches for `Field:\s*token` against the rendered
114
+ * artifact must tolerate the closing `**` between the colon and the value.
115
+ *
116
+ * `field` is treated as literal text (regex meta-characters are escaped).
117
+ * `value` is inserted verbatim so callers can pass alternation
118
+ * (`STARTUP|BUILDER|...`). `flags` defaults to case-insensitive Unicode.
119
+ */
120
+ function markdownFieldRegex(field, value, flags = "iu") {
121
+ const escapedField = field.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
122
+ const emph = "[*_]{0,2}";
123
+ const source = `(?:^|[\\s>])${emph}\\s*${escapedField}\\s*${emph}\\s*:\\s*${emph}\\s*(?:${value})\\b`;
124
+ return new RegExp(source, flags);
125
+ }
108
126
  export function extractMarkdownSectionBody(markdown, section) {
109
127
  return sectionBodyByName(extractH2Sections(markdown), section);
110
128
  }
@@ -211,7 +229,7 @@ function normalizeDesignDiagramTier(value) {
211
229
  if (!value)
212
230
  return null;
213
231
  const normalized = value.trim().toLowerCase();
214
- if (/^light(?:weight)?$/u.test(normalized))
232
+ if (/^(?:lite|light|lightweight)$/u.test(normalized))
215
233
  return "lightweight";
216
234
  if (/^standard$/u.test(normalized))
217
235
  return "standard";
@@ -224,12 +242,21 @@ function parseApproachTierSection(sectionBody) {
224
242
  return null;
225
243
  for (const line of sectionBody.split(/\r?\n/u)) {
226
244
  const cleaned = line.replace(/[*_`]/gu, "").trim();
227
- const directMatch = /(?:^|\b)tier\s*:\s*(lightweight|light|standard|deep)\b/iu.exec(cleaned);
245
+ const directMatch = /(?:^|\b)tier\s*:\s*(lite|lightweight|light|standard|deep)\b/iu.exec(cleaned);
228
246
  if (directMatch) {
229
- return normalizeDesignDiagramTier(directMatch[1] ?? null);
247
+ const captured = directMatch[1] ?? "";
248
+ const remainder = cleaned.slice(cleaned.toLowerCase().indexOf("tier") + 4);
249
+ const tierTokens = remainder.match(/\b(?:lite|lightweight|light|standard|deep)\b/giu) ?? [];
250
+ const distinct = new Set(tierTokens.map((token) => token.toLowerCase()));
251
+ if (distinct.size >= 2) {
252
+ // Multi-token line is the unfilled template placeholder
253
+ // (`Tier: lite | standard | deep`); treat as no decision.
254
+ continue;
255
+ }
256
+ return normalizeDesignDiagramTier(captured);
230
257
  }
231
258
  }
232
- const token = /\b(lightweight|light|standard|deep)\b/iu.exec(sectionBody)?.[1] ?? null;
259
+ const token = /\b(lite|lightweight|light|standard|deep)\b/iu.exec(sectionBody)?.[1] ?? null;
233
260
  return normalizeDesignDiagramTier(token);
234
261
  }
235
262
  async function resolveDesignDiagramTier(projectRoot, track, designRaw) {
@@ -1782,15 +1809,31 @@ export async function lintArtifact(projectRoot, stage, track = "standard") {
1782
1809
  // to a single row (defeating the "2-3 distinct approaches" gate).
1783
1810
  const tierBody = sectionBodyByName(sections, "Approach Tier");
1784
1811
  if (tierBody !== null) {
1785
- const hasTierToken = /\b(?:lightweight|standard|deep)\b/iu.test(tierBody);
1812
+ // Token vocabulary covers `lite`, `Lightweight`, `Standard`, and
1813
+ // `Deep` (case-insensitive). A line that lists ≥2 distinct tokens is
1814
+ // the unfilled template placeholder (`Tier: lite | standard | deep`)
1815
+ // and must not silently pass; we look for at least one decision line
1816
+ // with exactly one token, while ignoring placeholder lines.
1817
+ const cleanedLines = tierBody
1818
+ .split("\n")
1819
+ .map((line) => line.replace(/[*_`]/gu, ""));
1820
+ const lineTokenCounts = cleanedLines.map((line) => {
1821
+ const tokens = line.match(/\b(?:lite|lightweight|light|standard|deep)\b/giu) ?? [];
1822
+ return new Set(tokens.map((token) => token.toLowerCase())).size;
1823
+ });
1824
+ const hasDecisionLine = lineTokenCounts.some((count) => count === 1);
1825
+ const hasPlaceholderLine = lineTokenCounts.some((count) => count >= 2);
1826
+ const ok = hasDecisionLine;
1786
1827
  findings.push({
1787
1828
  section: "Approach Tier Classification",
1788
1829
  required: true,
1789
- rule: "Approach Tier must explicitly classify depth as Lightweight, Standard, or Deep.",
1790
- found: hasTierToken,
1791
- details: hasTierToken
1792
- ? "Approach Tier includes a recognized depth token."
1793
- : "Approach Tier is missing a recognized depth token (Lightweight/Standard/Deep)."
1830
+ rule: "Approach Tier must explicitly classify depth as one of `lite` (a.k.a. `Lightweight`), `Standard`, or `Deep`.",
1831
+ found: ok,
1832
+ details: ok
1833
+ ? "Approach Tier includes a single recognized depth token."
1834
+ : hasPlaceholderLine
1835
+ ? "Approach Tier still lists multiple tier tokens (template placeholder); pick exactly one of `lite`/`Lightweight`, `Standard`, or `Deep`."
1836
+ : "Approach Tier is missing a recognized depth token (`lite`/`Lightweight`, `Standard`, or `Deep`)."
1794
1837
  });
1795
1838
  }
1796
1839
  const approachesBody = sectionBodyByName(sections, "Approaches");
@@ -1931,16 +1974,30 @@ export async function lintArtifact(projectRoot, stage, track = "standard") {
1931
1974
  // approach detail cards, anti-sycophancy stamp).
1932
1975
  const modeBody = sectionBodyByName(sections, "Mode Block");
1933
1976
  if (modeBody !== null) {
1934
- const modeRegex = /\bMode:\s*(STARTUP|BUILDER|ENGINEERING|OPS|RESEARCH)\b/u;
1935
- const ok = modeRegex.test(modeBody);
1977
+ const modeTokens = ["STARTUP", "BUILDER", "ENGINEERING", "OPS", "RESEARCH"];
1978
+ const modeRegex = markdownFieldRegex("Mode", modeTokens.join("|"), "u");
1979
+ const tokenMatches = new Set();
1980
+ const lineRegex = new RegExp(modeRegex.source, "gu");
1981
+ for (const match of modeBody.matchAll(lineRegex)) {
1982
+ const token = (match[0].match(/STARTUP|BUILDER|ENGINEERING|OPS|RESEARCH/u) ?? [""])[0];
1983
+ if (token)
1984
+ tokenMatches.add(token);
1985
+ }
1986
+ const placeholderLine = modeBody
1987
+ .split("\n")
1988
+ .find((line) => /\bMode\b\s*[*_]{0,2}\s*:/iu.test(line) && (line.match(/STARTUP|BUILDER|ENGINEERING|OPS|RESEARCH/giu) ?? []).length >= 2);
1989
+ const isPlaceholder = Boolean(placeholderLine);
1990
+ const ok = tokenMatches.size === 1 && !isPlaceholder;
1936
1991
  findings.push({
1937
1992
  section: "Mode Block Token",
1938
1993
  required: true,
1939
1994
  rule: "Mode Block must declare exactly one mode token: STARTUP, BUILDER, ENGINEERING, OPS, or RESEARCH.",
1940
1995
  found: ok,
1941
1996
  details: ok
1942
- ? "Recognized mode token detected."
1943
- : "Mode Block is missing a recognized mode token (STARTUP/BUILDER/ENGINEERING/OPS/RESEARCH)."
1997
+ ? `Recognized mode token detected: ${[...tokenMatches][0] ?? ""}.`
1998
+ : isPlaceholder
1999
+ ? "Mode Block still lists multiple mode tokens (template placeholder); pick exactly one of STARTUP/BUILDER/ENGINEERING/OPS/RESEARCH."
2000
+ : "Mode Block is missing a recognized mode token (STARTUP/BUILDER/ENGINEERING/OPS/RESEARCH)."
1944
2001
  });
1945
2002
  }
1946
2003
  const forcingBody = sectionBodyByName(sections, "Forcing Questions");
@@ -2038,7 +2095,7 @@ export async function lintArtifact(projectRoot, stage, track = "standard") {
2038
2095
  }
2039
2096
  const stampBody = sectionBodyByName(sections, "Anti-Sycophancy Stamp");
2040
2097
  if (stampBody !== null) {
2041
- const acknowledged = /forbidden response openers acknowledged:\s*(yes|true|y)\b/iu.test(stampBody);
2098
+ const acknowledged = markdownFieldRegex("Forbidden response openers acknowledged", "yes|true|y").test(stampBody);
2042
2099
  findings.push({
2043
2100
  section: "Anti-Sycophancy Acknowledgement",
2044
2101
  required: true,
@@ -2137,7 +2194,7 @@ export async function lintArtifact(projectRoot, stage, track = "standard") {
2137
2194
  }
2138
2195
  const regressionBody = sectionBodyByName(sections, "Regression Iron Rule");
2139
2196
  if (regressionBody !== null) {
2140
- const ack = /iron rule acknowledged:\s*(yes|true|y)\b/iu.test(regressionBody);
2197
+ const ack = markdownFieldRegex("Iron rule acknowledged", "yes|true|y").test(regressionBody);
2141
2198
  findings.push({
2142
2199
  section: "Regression Iron Rule Acknowledgement",
2143
2200
  required: true,
@@ -12,7 +12,7 @@ const REQUIRED_TOP_LEVEL_FIELDS = {
12
12
  };
13
13
  const STAGE_TAXONOMIES = {
14
14
  brainstorm: {
15
- approachTier: ["Lightweight", "Standard", "Deep"],
15
+ approachTier: ["Lightweight", "Standard", "Deep", "lite", "standard", "deep"],
16
16
  approachRole: ["baseline", "challenger", "wild-card"],
17
17
  approachUpside: ["low", "modest", "high", "higher"]
18
18
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.51.27",
3
+ "version": "0.51.28",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {