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.
package/dist/artifact-linter.js
CHANGED
|
@@ -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 (/^
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1790
|
-
found:
|
|
1791
|
-
details:
|
|
1792
|
-
? "Approach Tier includes a recognized depth token."
|
|
1793
|
-
:
|
|
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
|
|
1935
|
-
const
|
|
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
|
-
?
|
|
1943
|
-
:
|
|
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 =
|
|
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 =
|
|
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
|
},
|