qfai 1.7.8 → 1.7.9
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/assets/init/.qfai/assistant/skills/qfai-prototyping/SKILL.md +35 -6
- package/assets/init/.qfai/assistant/skills/qfai-prototyping-full-harness/SKILL.md +185 -0
- package/dist/cli/index.cjs +317 -125
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +315 -123
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +293 -101
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +291 -99
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.cjs
CHANGED
|
@@ -1739,8 +1739,8 @@ var import_promises7 = require("fs/promises");
|
|
|
1739
1739
|
var import_node_path8 = __toESM(require("path"), 1);
|
|
1740
1740
|
var import_node_url2 = require("url");
|
|
1741
1741
|
async function resolveToolVersion() {
|
|
1742
|
-
if ("1.7.
|
|
1743
|
-
return "1.7.
|
|
1742
|
+
if ("1.7.9".length > 0) {
|
|
1743
|
+
return "1.7.9";
|
|
1744
1744
|
}
|
|
1745
1745
|
try {
|
|
1746
1746
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -4906,7 +4906,7 @@ function extractRouteHintsFromEvidence(evidence) {
|
|
|
4906
4906
|
|
|
4907
4907
|
// src/cli/commands/report.ts
|
|
4908
4908
|
var import_promises61 = require("fs/promises");
|
|
4909
|
-
var
|
|
4909
|
+
var import_node_path66 = __toESM(require("path"), 1);
|
|
4910
4910
|
|
|
4911
4911
|
// src/core/normalize.ts
|
|
4912
4912
|
function normalizeIssuePaths(root, issues) {
|
|
@@ -5002,7 +5002,7 @@ async function createPhaseGuardResult(phase, blockedIssue) {
|
|
|
5002
5002
|
|
|
5003
5003
|
// src/core/report.ts
|
|
5004
5004
|
var import_promises59 = require("fs/promises");
|
|
5005
|
-
var
|
|
5005
|
+
var import_node_path64 = __toESM(require("path"), 1);
|
|
5006
5006
|
|
|
5007
5007
|
// src/core/contractIndex.ts
|
|
5008
5008
|
var import_promises18 = require("fs/promises");
|
|
@@ -5699,6 +5699,9 @@ function asTagArray(value) {
|
|
|
5699
5699
|
return [];
|
|
5700
5700
|
}
|
|
5701
5701
|
|
|
5702
|
+
// src/core/validate.ts
|
|
5703
|
+
var import_node_path63 = __toESM(require("path"), 1);
|
|
5704
|
+
|
|
5702
5705
|
// src/core/waivers.ts
|
|
5703
5706
|
var import_promises20 = require("fs/promises");
|
|
5704
5707
|
var import_node_path22 = __toESM(require("path"), 1);
|
|
@@ -13098,7 +13101,7 @@ function collectLayer(layer, layerName, target, errors) {
|
|
|
13098
13101
|
}
|
|
13099
13102
|
function flattenTokens(obj, prefix, target, errors) {
|
|
13100
13103
|
for (const [key, value] of Object.entries(obj)) {
|
|
13101
|
-
const
|
|
13104
|
+
const path69 = `${prefix}.${key}`;
|
|
13102
13105
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
13103
13106
|
const record2 = value;
|
|
13104
13107
|
if ("$value" in record2) {
|
|
@@ -13114,9 +13117,9 @@ function flattenTokens(obj, prefix, target, errors) {
|
|
|
13114
13117
|
if (typeof record2.platform === "string") {
|
|
13115
13118
|
token.platform = record2.platform;
|
|
13116
13119
|
}
|
|
13117
|
-
target.set(
|
|
13120
|
+
target.set(path69, token);
|
|
13118
13121
|
} else {
|
|
13119
|
-
flattenTokens(record2,
|
|
13122
|
+
flattenTokens(record2, path69, target, errors);
|
|
13120
13123
|
}
|
|
13121
13124
|
}
|
|
13122
13125
|
}
|
|
@@ -13126,44 +13129,44 @@ function resolveAllReferences(result) {
|
|
|
13126
13129
|
for (const [key, val] of result.primitives) allTokens.set(key, val);
|
|
13127
13130
|
for (const [key, val] of result.semantics) allTokens.set(key, val);
|
|
13128
13131
|
for (const [key, val] of result.components) allTokens.set(key, val);
|
|
13129
|
-
for (const [
|
|
13130
|
-
resolveTokenRef(
|
|
13132
|
+
for (const [path69] of allTokens) {
|
|
13133
|
+
resolveTokenRef(path69, allTokens, /* @__PURE__ */ new Set(), 0, result);
|
|
13131
13134
|
}
|
|
13132
13135
|
}
|
|
13133
|
-
function resolveTokenRef(
|
|
13134
|
-
if (result.resolved.has(
|
|
13135
|
-
return result.resolved.get(
|
|
13136
|
+
function resolveTokenRef(path69, allTokens, visited, depth, result) {
|
|
13137
|
+
if (result.resolved.has(path69)) {
|
|
13138
|
+
return result.resolved.get(path69);
|
|
13136
13139
|
}
|
|
13137
13140
|
if (depth > MAX_RESOLVE_DEPTH) {
|
|
13138
13141
|
result.errors.push({
|
|
13139
|
-
message: `Max reference depth exceeded at: ${
|
|
13140
|
-
path:
|
|
13142
|
+
message: `Max reference depth exceeded at: ${path69}`,
|
|
13143
|
+
path: path69
|
|
13141
13144
|
});
|
|
13142
13145
|
return void 0;
|
|
13143
13146
|
}
|
|
13144
|
-
if (visited.has(
|
|
13147
|
+
if (visited.has(path69)) {
|
|
13145
13148
|
result.errors.push({
|
|
13146
|
-
message: `Circular reference detected: ${
|
|
13147
|
-
path:
|
|
13149
|
+
message: `Circular reference detected: ${path69}`,
|
|
13150
|
+
path: path69
|
|
13148
13151
|
});
|
|
13149
13152
|
return void 0;
|
|
13150
13153
|
}
|
|
13151
|
-
const token = allTokens.get(
|
|
13154
|
+
const token = allTokens.get(path69);
|
|
13152
13155
|
if (!token) {
|
|
13153
13156
|
return void 0;
|
|
13154
13157
|
}
|
|
13155
13158
|
if (typeof token.$value !== "string") {
|
|
13156
13159
|
const rawValue2 = stringifyTokenValue(token.$value);
|
|
13157
|
-
result.resolved.set(
|
|
13160
|
+
result.resolved.set(path69, rawValue2);
|
|
13158
13161
|
return rawValue2;
|
|
13159
13162
|
}
|
|
13160
13163
|
const rawValue = stringifyTokenValue(token.$value);
|
|
13161
13164
|
const refs = [...rawValue.matchAll(REF_PATTERN)];
|
|
13162
13165
|
if (refs.length === 0) {
|
|
13163
|
-
result.resolved.set(
|
|
13166
|
+
result.resolved.set(path69, rawValue);
|
|
13164
13167
|
return rawValue;
|
|
13165
13168
|
}
|
|
13166
|
-
visited.add(
|
|
13169
|
+
visited.add(path69);
|
|
13167
13170
|
let resolved = rawValue;
|
|
13168
13171
|
for (const ref of refs) {
|
|
13169
13172
|
const refPath = ref[1];
|
|
@@ -13171,8 +13174,8 @@ function resolveTokenRef(path67, allTokens, visited, depth, result) {
|
|
|
13171
13174
|
const refToken = allTokens.get(refPath);
|
|
13172
13175
|
if (!refToken) {
|
|
13173
13176
|
result.errors.push({
|
|
13174
|
-
message: `Unresolved token reference: {${refPath}} at ${
|
|
13175
|
-
path:
|
|
13177
|
+
message: `Unresolved token reference: {${refPath}} at ${path69}`,
|
|
13178
|
+
path: path69
|
|
13176
13179
|
});
|
|
13177
13180
|
continue;
|
|
13178
13181
|
}
|
|
@@ -13181,7 +13184,7 @@ function resolveTokenRef(path67, allTokens, visited, depth, result) {
|
|
|
13181
13184
|
resolved = resolved.split(`{${refPath}}`).join(refValue);
|
|
13182
13185
|
}
|
|
13183
13186
|
}
|
|
13184
|
-
result.resolved.set(
|
|
13187
|
+
result.resolved.set(path69, resolved);
|
|
13185
13188
|
return resolved;
|
|
13186
13189
|
}
|
|
13187
13190
|
function stringifyTokenValue(value) {
|
|
@@ -16912,9 +16915,64 @@ function hasAntiPatternMention(section, code) {
|
|
|
16912
16915
|
}
|
|
16913
16916
|
|
|
16914
16917
|
// src/core/validators/discussionDesignHardening.ts
|
|
16918
|
+
var import_node_path58 = __toESM(require("path"), 1);
|
|
16919
|
+
|
|
16920
|
+
// src/core/detection/surfaceType.ts
|
|
16921
|
+
var import_promises55 = require("fs/promises");
|
|
16915
16922
|
var import_node_path57 = __toESM(require("path"), 1);
|
|
16923
|
+
var VALID_SURFACES = /* @__PURE__ */ new Set([
|
|
16924
|
+
"web-ui",
|
|
16925
|
+
"mobile-ui",
|
|
16926
|
+
"desktop-ui",
|
|
16927
|
+
"mixed",
|
|
16928
|
+
"non-ui"
|
|
16929
|
+
]);
|
|
16916
16930
|
var HTML_TAG_RE = /<(?:style|div|section|span|button|input|form|header|footer|nav|main|aside)\b/i;
|
|
16917
16931
|
var MERMAID_SCREEN_FLOW_RE = /```mermaid[\s\S]*?(?:stateDiagram|flowchart|graph)[\s\S]*?(?:Screen|Page|View|Dashboard|Login|Settings|Home)\b/i;
|
|
16932
|
+
var YAML_SURFACE_RE = /^\s*-\s*surface(?:_type)?:\s*(\S+)/im;
|
|
16933
|
+
var TABLE_SURFACE_RE = /\|\s*Surface Type\s*\|\s*(\S+)\s*\|/i;
|
|
16934
|
+
var SCREEN_CONTRACT_YAML_RE = /screens:\s*\n\s*-\s*route:/;
|
|
16935
|
+
function parseSurface(raw) {
|
|
16936
|
+
const s = raw.toLowerCase();
|
|
16937
|
+
return VALID_SURFACES.has(s) ? s : void 0;
|
|
16938
|
+
}
|
|
16939
|
+
async function detectSurfaceType(root) {
|
|
16940
|
+
for (const fileName of ["01_Spec.md", "01_Context.md"]) {
|
|
16941
|
+
const content = await readSafe2(import_node_path57.default.join(root, fileName));
|
|
16942
|
+
const match = YAML_SURFACE_RE.exec(content);
|
|
16943
|
+
if (match?.[1]) {
|
|
16944
|
+
const surface = parseSurface(match[1]);
|
|
16945
|
+
if (surface) return surface;
|
|
16946
|
+
}
|
|
16947
|
+
}
|
|
16948
|
+
const storyContent = await readSafe2(import_node_path57.default.join(root, "03_Story-Workshop.md"));
|
|
16949
|
+
if (storyContent) {
|
|
16950
|
+
const tableMatch = TABLE_SURFACE_RE.exec(storyContent);
|
|
16951
|
+
if (tableMatch?.[1]) {
|
|
16952
|
+
const surface = parseSurface(tableMatch[1]);
|
|
16953
|
+
if (surface) return surface;
|
|
16954
|
+
}
|
|
16955
|
+
}
|
|
16956
|
+
try {
|
|
16957
|
+
await (0, import_promises55.readdir)(import_node_path57.default.join(root, "uiux"));
|
|
16958
|
+
return "web-ui";
|
|
16959
|
+
} catch {
|
|
16960
|
+
}
|
|
16961
|
+
const contractsContent = await readSafe2(import_node_path57.default.join(root, "uiux", "40_contracts.md"));
|
|
16962
|
+
if (contractsContent && SCREEN_CONTRACT_YAML_RE.test(contractsContent)) return "web-ui";
|
|
16963
|
+
if (storyContent) {
|
|
16964
|
+
const stripped = storyContent.replace(/```[\s\S]*?```/g, "").replace(/`[^`]+`/g, "");
|
|
16965
|
+
if (HTML_TAG_RE.test(stripped)) return "web-ui";
|
|
16966
|
+
if (MERMAID_SCREEN_FLOW_RE.test(storyContent)) return "web-ui";
|
|
16967
|
+
}
|
|
16968
|
+
return "non-ui";
|
|
16969
|
+
}
|
|
16970
|
+
async function isUiBearingSurface(root) {
|
|
16971
|
+
const surface = await detectSurfaceType(root);
|
|
16972
|
+
return surface !== "non-ui";
|
|
16973
|
+
}
|
|
16974
|
+
|
|
16975
|
+
// src/core/validators/discussionDesignHardening.ts
|
|
16918
16976
|
var DDS_HEADING = "## Design Direction Summary";
|
|
16919
16977
|
var DDS_SUBSECTIONS = [
|
|
16920
16978
|
"Option Comparison",
|
|
@@ -16927,20 +16985,8 @@ var DDS_SUBSECTIONS = [
|
|
|
16927
16985
|
var REQUIRED_STATES = ["empty", "loading", "error", "populated"];
|
|
16928
16986
|
var COMPETITIVE_REF_FIELDS = ["adopted_points", "rejected_points", "local_translation"];
|
|
16929
16987
|
var PLACEHOLDER_RE = /^(?:tbd|todo|n\/a|na|xxx|\?\?\?|placeholder)$/i;
|
|
16930
|
-
var SURFACE_TYPE_RE = /\|\s*Surface Type\s*\|\s*(\S+)\s*\|/i;
|
|
16931
16988
|
async function isUiBearing(packRoot) {
|
|
16932
|
-
|
|
16933
|
-
const content = await readSafe2(storyPath);
|
|
16934
|
-
if (!content) return false;
|
|
16935
|
-
const surfaceMatch = SURFACE_TYPE_RE.exec(content);
|
|
16936
|
-
if (surfaceMatch?.[1]) {
|
|
16937
|
-
const surface = surfaceMatch[1].toLowerCase();
|
|
16938
|
-
if (surface === "non-ui") return false;
|
|
16939
|
-
if (["web-ui", "mobile-ui", "desktop-ui", "mixed"].includes(surface)) return true;
|
|
16940
|
-
}
|
|
16941
|
-
if (HTML_TAG_RE.test(content)) return true;
|
|
16942
|
-
if (MERMAID_SCREEN_FLOW_RE.test(content)) return true;
|
|
16943
|
-
return false;
|
|
16989
|
+
return isUiBearingSurface(packRoot);
|
|
16944
16990
|
}
|
|
16945
16991
|
function extractDdsSection(content) {
|
|
16946
16992
|
const idx = content.indexOf(DDS_HEADING);
|
|
@@ -16972,7 +17018,7 @@ function extractOptionNames(optionSection) {
|
|
|
16972
17018
|
}
|
|
16973
17019
|
async function validateDdsPresence(packRoot) {
|
|
16974
17020
|
const issues = [];
|
|
16975
|
-
const storyPath =
|
|
17021
|
+
const storyPath = import_node_path58.default.join(packRoot, "03_Story-Workshop.md");
|
|
16976
17022
|
const content = await readSafe2(storyPath);
|
|
16977
17023
|
const relPath = "03_Story-Workshop.md";
|
|
16978
17024
|
if (!content || !content.includes(DDS_HEADING)) {
|
|
@@ -17006,7 +17052,7 @@ async function validateDdsPresence(packRoot) {
|
|
|
17006
17052
|
}
|
|
17007
17053
|
async function validateOptionComparison2(packRoot) {
|
|
17008
17054
|
const issues = [];
|
|
17009
|
-
const storyPath =
|
|
17055
|
+
const storyPath = import_node_path58.default.join(packRoot, "03_Story-Workshop.md");
|
|
17010
17056
|
const content = await readSafe2(storyPath);
|
|
17011
17057
|
if (!content) return issues;
|
|
17012
17058
|
const dds = extractDdsSection(content);
|
|
@@ -17030,7 +17076,7 @@ async function validateOptionComparison2(packRoot) {
|
|
|
17030
17076
|
}
|
|
17031
17077
|
async function validateAnchorScreen(packRoot) {
|
|
17032
17078
|
const issues = [];
|
|
17033
|
-
const storyPath =
|
|
17079
|
+
const storyPath = import_node_path58.default.join(packRoot, "03_Story-Workshop.md");
|
|
17034
17080
|
const content = await readSafe2(storyPath);
|
|
17035
17081
|
if (!content) return issues;
|
|
17036
17082
|
const dds = extractDdsSection(content);
|
|
@@ -17072,7 +17118,7 @@ async function validateAnchorScreen(packRoot) {
|
|
|
17072
17118
|
}
|
|
17073
17119
|
async function validateCompetitiveRefs2(packRoot) {
|
|
17074
17120
|
const issues = [];
|
|
17075
|
-
const sourcesPath =
|
|
17121
|
+
const sourcesPath = import_node_path58.default.join(packRoot, "04_Sources.md");
|
|
17076
17122
|
const content = await readSafe2(sourcesPath);
|
|
17077
17123
|
if (!content) return issues;
|
|
17078
17124
|
const registryHeadingRe = /^##\s+Competitive Reference Registry\b.*$/m;
|
|
@@ -17129,7 +17175,7 @@ function fieldGuidance(field) {
|
|
|
17129
17175
|
}
|
|
17130
17176
|
async function validateCtaHierarchy(packRoot) {
|
|
17131
17177
|
const issues = [];
|
|
17132
|
-
const storyPath =
|
|
17178
|
+
const storyPath = import_node_path58.default.join(packRoot, "03_Story-Workshop.md");
|
|
17133
17179
|
const content = await readSafe2(storyPath);
|
|
17134
17180
|
if (!content) return issues;
|
|
17135
17181
|
const dds = extractDdsSection(content);
|
|
@@ -17167,7 +17213,7 @@ async function validateCtaHierarchy(packRoot) {
|
|
|
17167
17213
|
}
|
|
17168
17214
|
async function validateStateCoverage(packRoot) {
|
|
17169
17215
|
const issues = [];
|
|
17170
|
-
const storyPath =
|
|
17216
|
+
const storyPath = import_node_path58.default.join(packRoot, "03_Story-Workshop.md");
|
|
17171
17217
|
const content = await readSafe2(storyPath);
|
|
17172
17218
|
if (!content) return issues;
|
|
17173
17219
|
const dds = extractDdsSection(content);
|
|
@@ -17192,7 +17238,7 @@ async function validateStateCoverage(packRoot) {
|
|
|
17192
17238
|
}
|
|
17193
17239
|
async function validateDesignAntiGoals(packRoot) {
|
|
17194
17240
|
const issues = [];
|
|
17195
|
-
const storyPath =
|
|
17241
|
+
const storyPath = import_node_path58.default.join(packRoot, "03_Story-Workshop.md");
|
|
17196
17242
|
const content = await readSafe2(storyPath);
|
|
17197
17243
|
if (!content) return issues;
|
|
17198
17244
|
const dds = extractDdsSection(content);
|
|
@@ -17229,7 +17275,7 @@ async function validateDesignAntiGoals(packRoot) {
|
|
|
17229
17275
|
return issues;
|
|
17230
17276
|
}
|
|
17231
17277
|
async function validateDiscussionDesignHardening(root, config) {
|
|
17232
|
-
const discussionDir =
|
|
17278
|
+
const discussionDir = import_node_path58.default.join(root, config.paths.discussionDir);
|
|
17233
17279
|
const packRoot = await findLatestDiscussionPackDir(discussionDir);
|
|
17234
17280
|
if (!packRoot) return [];
|
|
17235
17281
|
const uiBearing = await isUiBearing(packRoot);
|
|
@@ -17246,8 +17292,8 @@ async function validateDiscussionDesignHardening(root, config) {
|
|
|
17246
17292
|
}
|
|
17247
17293
|
|
|
17248
17294
|
// src/core/validators/designAudit.ts
|
|
17249
|
-
var
|
|
17250
|
-
var
|
|
17295
|
+
var import_promises56 = require("fs/promises");
|
|
17296
|
+
var import_node_path59 = __toESM(require("path"), 1);
|
|
17251
17297
|
var COSMETIC_CATEGORIES = ["generic-shell", "stock-imagery", "placeholder-copy"];
|
|
17252
17298
|
function resolveAuditConfig(config) {
|
|
17253
17299
|
const audit = config.uiux?.audit;
|
|
@@ -17329,19 +17375,19 @@ var RAW_COLOR_RE = /#[0-9a-fA-F]{3,8}\b|rgb\([^)]+\)|rgba\([^)]+\)|hsl\([^)]+\)|
|
|
|
17329
17375
|
async function checkTokenDrift(root, auditConfig, cfg) {
|
|
17330
17376
|
const findings = [];
|
|
17331
17377
|
const configuredDir = cfg.uiux?.designTokensDir;
|
|
17332
|
-
const tokensDir = configuredDir ?
|
|
17378
|
+
const tokensDir = configuredDir ? import_node_path59.default.resolve(root, configuredDir) : import_node_path59.default.join(root, cfg.paths.contractsDir, "design");
|
|
17333
17379
|
let hasTokenFiles = false;
|
|
17334
17380
|
try {
|
|
17335
|
-
const entries = await (0,
|
|
17381
|
+
const entries = await (0, import_promises56.readdir)(tokensDir);
|
|
17336
17382
|
hasTokenFiles = entries.some((e) => /\.ya?ml$/i.test(e));
|
|
17337
17383
|
} catch {
|
|
17338
17384
|
return findings;
|
|
17339
17385
|
}
|
|
17340
17386
|
if (!hasTokenFiles) return findings;
|
|
17341
|
-
const contractsUiDir =
|
|
17387
|
+
const contractsUiDir = import_node_path59.default.join(root, cfg.paths.contractsDir, "ui");
|
|
17342
17388
|
let htmlFiles = [];
|
|
17343
17389
|
try {
|
|
17344
|
-
const entries = await (0,
|
|
17390
|
+
const entries = await (0, import_promises56.readdir)(contractsUiDir);
|
|
17345
17391
|
htmlFiles = entries.filter((e) => /\.html?$/i.test(e));
|
|
17346
17392
|
} catch {
|
|
17347
17393
|
return findings;
|
|
@@ -17349,7 +17395,7 @@ async function checkTokenDrift(root, auditConfig, cfg) {
|
|
|
17349
17395
|
let rawCount = 0;
|
|
17350
17396
|
const sampleLiterals = [];
|
|
17351
17397
|
for (const htmlFile of htmlFiles) {
|
|
17352
|
-
const content = await readSafe2(
|
|
17398
|
+
const content = await readSafe2(import_node_path59.default.join(contractsUiDir, htmlFile));
|
|
17353
17399
|
if (!content) continue;
|
|
17354
17400
|
const matches = content.match(RAW_COLOR_RE);
|
|
17355
17401
|
if (matches) {
|
|
@@ -17400,12 +17446,12 @@ function deduplicateFindings(issues, maxPerRule) {
|
|
|
17400
17446
|
async function validateDesignAudit(root, config) {
|
|
17401
17447
|
const auditConfig = resolveAuditConfig(config);
|
|
17402
17448
|
if (!auditConfig.enabled) return [];
|
|
17403
|
-
const discussionDir =
|
|
17449
|
+
const discussionDir = import_node_path59.default.join(root, config.paths.discussionDir);
|
|
17404
17450
|
const packRoot = await findLatestDiscussionPackDir(discussionDir);
|
|
17405
17451
|
if (!packRoot) return [];
|
|
17406
17452
|
const uiBearing = await isUiBearing(packRoot);
|
|
17407
17453
|
if (!uiBearing) return [];
|
|
17408
|
-
const storyPath =
|
|
17454
|
+
const storyPath = import_node_path59.default.join(packRoot, "03_Story-Workshop.md");
|
|
17409
17455
|
const content = await readSafe2(storyPath);
|
|
17410
17456
|
if (!content) return [];
|
|
17411
17457
|
const findings = [];
|
|
@@ -17417,8 +17463,8 @@ async function validateDesignAudit(root, config) {
|
|
|
17417
17463
|
|
|
17418
17464
|
// src/core/validators/designSlop.ts
|
|
17419
17465
|
var import_node_fs2 = require("fs");
|
|
17420
|
-
var
|
|
17421
|
-
var
|
|
17466
|
+
var import_promises57 = require("fs/promises");
|
|
17467
|
+
var import_node_path60 = __toESM(require("path"), 1);
|
|
17422
17468
|
var import_node_url4 = require("url");
|
|
17423
17469
|
function isValidSlopPattern(rule) {
|
|
17424
17470
|
if (typeof rule !== "object" || rule === null) return false;
|
|
@@ -17426,7 +17472,7 @@ function isValidSlopPattern(rule) {
|
|
|
17426
17472
|
return typeof r.id === "string" && typeof r.category === "string" && typeof r.tier === "number" && Array.isArray(r.scopes) && typeof r.match === "string" && typeof r.message === "string" && typeof r.guidance === "string";
|
|
17427
17473
|
}
|
|
17428
17474
|
async function loadSlopPatterns(jsonPath) {
|
|
17429
|
-
const raw = await (0,
|
|
17475
|
+
const raw = await (0, import_promises57.readFile)(jsonPath, "utf-8");
|
|
17430
17476
|
const parsed = JSON.parse(raw);
|
|
17431
17477
|
if (!Array.isArray(parsed)) return [];
|
|
17432
17478
|
return parsed.filter((r) => isValidSlopPattern(r));
|
|
@@ -17434,11 +17480,11 @@ async function loadSlopPatterns(jsonPath) {
|
|
|
17434
17480
|
function defaultPatternsPath() {
|
|
17435
17481
|
const base = __filename;
|
|
17436
17482
|
const basePath = base.startsWith("file:") ? (0, import_node_url4.fileURLToPath)(base) : base;
|
|
17437
|
-
const baseDir =
|
|
17483
|
+
const baseDir = import_node_path60.default.dirname(basePath);
|
|
17438
17484
|
const candidates = [
|
|
17439
|
-
|
|
17440
|
-
|
|
17441
|
-
|
|
17485
|
+
import_node_path60.default.join(baseDir, "designSlopPatterns.json"),
|
|
17486
|
+
import_node_path60.default.resolve(baseDir, "../../../assets/validators/designSlopPatterns.json"),
|
|
17487
|
+
import_node_path60.default.resolve(baseDir, "../../assets/validators/designSlopPatterns.json")
|
|
17442
17488
|
];
|
|
17443
17489
|
for (const c of candidates) {
|
|
17444
17490
|
if ((0, import_node_fs2.existsSync)(c)) return c;
|
|
@@ -17449,7 +17495,7 @@ async function validateDesignSlop(root, config) {
|
|
|
17449
17495
|
const auditConfig = resolveAuditConfig(config);
|
|
17450
17496
|
if (!auditConfig.enabled) return [];
|
|
17451
17497
|
if (!auditConfig.slopDetection) return [];
|
|
17452
|
-
const discussionDir =
|
|
17498
|
+
const discussionDir = import_node_path60.default.join(root, config.paths.discussionDir);
|
|
17453
17499
|
const packRoot = await findLatestDiscussionPackDir(discussionDir);
|
|
17454
17500
|
if (!packRoot) return [];
|
|
17455
17501
|
const uiBearing = await isUiBearing(packRoot);
|
|
@@ -17470,7 +17516,7 @@ async function validateDesignSlop(root, config) {
|
|
|
17470
17516
|
continue;
|
|
17471
17517
|
}
|
|
17472
17518
|
for (const scope of pattern.scopes) {
|
|
17473
|
-
const filePath =
|
|
17519
|
+
const filePath = import_node_path60.default.join(packRoot, scope);
|
|
17474
17520
|
const content = await readSafe2(filePath);
|
|
17475
17521
|
if (!content) continue;
|
|
17476
17522
|
if (regex.test(content) && !seenRules.has(pattern.id)) {
|
|
@@ -17496,40 +17542,8 @@ var import_promises58 = require("fs/promises");
|
|
|
17496
17542
|
var import_node_path61 = __toESM(require("path"), 1);
|
|
17497
17543
|
|
|
17498
17544
|
// src/core/validators/uixDetection.ts
|
|
17499
|
-
var import_promises57 = require("fs/promises");
|
|
17500
|
-
var import_node_path60 = __toESM(require("path"), 1);
|
|
17501
|
-
var UI_BEARING_SURFACES = /* @__PURE__ */ new Set(["web-ui", "mobile-ui", "desktop-ui", "mixed"]);
|
|
17502
|
-
var NON_UI_SURFACES = /* @__PURE__ */ new Set(["non-ui"]);
|
|
17503
|
-
function stripCodeBlocks(content) {
|
|
17504
|
-
let stripped = content.replace(/```[\s\S]*?```/g, "");
|
|
17505
|
-
stripped = stripped.replace(/`[^`]+`/g, "");
|
|
17506
|
-
return stripped;
|
|
17507
|
-
}
|
|
17508
|
-
var HTML_TAG_RE2 = /<(?:style|div|section|span|button|input|form|header|footer|nav|main|aside)\b/i;
|
|
17509
|
-
var MERMAID_SCREEN_FLOW_RE2 = /```mermaid[\s\S]*?(?:stateDiagram)[\s\S]*?(?:Screen|Page|View|Dashboard|Login|Settings|Home)\b/i;
|
|
17510
|
-
var SCREEN_CONTRACT_YAML_RE = /screens:\s*\n\s*-\s*route:/;
|
|
17511
17545
|
async function isUiBearingSpec(root) {
|
|
17512
|
-
|
|
17513
|
-
const surfaceMatch = /^\s*-\s*surface:\s*(\S+)/im.exec(specContent);
|
|
17514
|
-
if (surfaceMatch?.[1]) {
|
|
17515
|
-
const surface = surfaceMatch[1].toLowerCase();
|
|
17516
|
-
if (UI_BEARING_SURFACES.has(surface)) return true;
|
|
17517
|
-
if (NON_UI_SURFACES.has(surface)) return false;
|
|
17518
|
-
}
|
|
17519
|
-
try {
|
|
17520
|
-
await (0, import_promises57.readdir)(import_node_path60.default.join(root, "uiux"));
|
|
17521
|
-
return true;
|
|
17522
|
-
} catch {
|
|
17523
|
-
}
|
|
17524
|
-
const contractsContent = await readSafe2(import_node_path60.default.join(root, "uiux", "40_contracts.md"));
|
|
17525
|
-
if (contractsContent && SCREEN_CONTRACT_YAML_RE.test(contractsContent)) return true;
|
|
17526
|
-
const storyContent = await readSafe2(import_node_path60.default.join(root, "03_Story-Workshop.md"));
|
|
17527
|
-
if (storyContent) {
|
|
17528
|
-
const stripped = stripCodeBlocks(storyContent);
|
|
17529
|
-
if (HTML_TAG_RE2.test(stripped)) return true;
|
|
17530
|
-
if (MERMAID_SCREEN_FLOW_RE2.test(storyContent)) return true;
|
|
17531
|
-
}
|
|
17532
|
-
return false;
|
|
17546
|
+
return isUiBearingSurface(root);
|
|
17533
17547
|
}
|
|
17534
17548
|
|
|
17535
17549
|
// src/core/validators/uixValidators.ts
|
|
@@ -17945,6 +17959,177 @@ async function runAllUixValidators(root, config) {
|
|
|
17945
17959
|
return issues;
|
|
17946
17960
|
}
|
|
17947
17961
|
|
|
17962
|
+
// src/core/validators/skill/fullHarnessSkill.ts
|
|
17963
|
+
var import_node_path62 = __toESM(require("path"), 1);
|
|
17964
|
+
function harnessIssue(code, message, severity, file, suggestedAction) {
|
|
17965
|
+
return {
|
|
17966
|
+
code,
|
|
17967
|
+
severity,
|
|
17968
|
+
category: "compatibility",
|
|
17969
|
+
message,
|
|
17970
|
+
file,
|
|
17971
|
+
suggested_action: suggestedAction
|
|
17972
|
+
};
|
|
17973
|
+
}
|
|
17974
|
+
var WORKFLOW_INDICATORS = ["iteration", "loop", "convergence", "evidence"];
|
|
17975
|
+
var EVIDENCE_INDICATORS = [
|
|
17976
|
+
"render evidence",
|
|
17977
|
+
"test results",
|
|
17978
|
+
"validator output",
|
|
17979
|
+
"evidence collection"
|
|
17980
|
+
];
|
|
17981
|
+
var REVIEWER_INDICATORS = ["reviewer", "review findings", "review summary"];
|
|
17982
|
+
var CALIBRATION_INDICATORS = ["calibration", "scoring-ready", "threshold"];
|
|
17983
|
+
async function validateFullHarnessSkill(skillsDir) {
|
|
17984
|
+
const issues = [];
|
|
17985
|
+
const skillPath = import_node_path62.default.join(skillsDir, "qfai-prototyping-full-harness");
|
|
17986
|
+
const skillMdPath = import_node_path62.default.join(skillPath, "SKILL.md");
|
|
17987
|
+
const content = await readSafe2(skillMdPath);
|
|
17988
|
+
const skillFileExists = content.length > 0;
|
|
17989
|
+
if (!skillFileExists) {
|
|
17990
|
+
issues.push(
|
|
17991
|
+
harnessIssue(
|
|
17992
|
+
"UIX-VAL-FULL-HARNESS-ENTRYPOINT-MISSING",
|
|
17993
|
+
"Dedicated full-harness skill file is missing.",
|
|
17994
|
+
"error",
|
|
17995
|
+
"qfai-prototyping-full-harness/SKILL.md",
|
|
17996
|
+
"Create a dedicated full-harness skill file at the skills directory."
|
|
17997
|
+
)
|
|
17998
|
+
);
|
|
17999
|
+
return {
|
|
18000
|
+
skillFileExists: false,
|
|
18001
|
+
hasWorkflowLoop: false,
|
|
18002
|
+
hasEvidenceCollection: false,
|
|
18003
|
+
hasReviewerInvocation: false,
|
|
18004
|
+
hasCalibrationObligation: false,
|
|
18005
|
+
issues
|
|
18006
|
+
};
|
|
18007
|
+
}
|
|
18008
|
+
const lower = content.toLowerCase();
|
|
18009
|
+
const hasWorkflowLoop = WORKFLOW_INDICATORS.some((i) => lower.includes(i)) && !isRoutingOnly(lower);
|
|
18010
|
+
const hasEvidenceCollection = EVIDENCE_INDICATORS.some((i) => lower.includes(i));
|
|
18011
|
+
const hasReviewerInvocation = REVIEWER_INDICATORS.some((i) => lower.includes(i));
|
|
18012
|
+
const hasCalibrationObligation = CALIBRATION_INDICATORS.some((i) => lower.includes(i));
|
|
18013
|
+
if (!hasWorkflowLoop) {
|
|
18014
|
+
issues.push(
|
|
18015
|
+
harnessIssue(
|
|
18016
|
+
"UIX-VAL-FULL-HARNESS-NO-WORKFLOW",
|
|
18017
|
+
"Full-harness skill file does not define a workflow loop.",
|
|
18018
|
+
"error",
|
|
18019
|
+
"qfai-prototyping-full-harness/SKILL.md",
|
|
18020
|
+
"Add workflow loop definition with iteration, evidence, review, and calibration steps."
|
|
18021
|
+
)
|
|
18022
|
+
);
|
|
18023
|
+
}
|
|
18024
|
+
return {
|
|
18025
|
+
skillFileExists,
|
|
18026
|
+
hasWorkflowLoop,
|
|
18027
|
+
hasEvidenceCollection,
|
|
18028
|
+
hasReviewerInvocation,
|
|
18029
|
+
hasCalibrationObligation,
|
|
18030
|
+
issues
|
|
18031
|
+
};
|
|
18032
|
+
}
|
|
18033
|
+
function isRoutingOnly(content) {
|
|
18034
|
+
const routingPatterns = [
|
|
18035
|
+
/^this\s+skill\s+(?:routes?|redirects?|delegates?)\s+to/im,
|
|
18036
|
+
/^see\s+`?qfai\s+prototyping/im
|
|
18037
|
+
];
|
|
18038
|
+
return routingPatterns.some((p) => p.test(content));
|
|
18039
|
+
}
|
|
18040
|
+
|
|
18041
|
+
// src/core/validators/skill/prototypingSkill.ts
|
|
18042
|
+
var BANNED_PHRASES = [
|
|
18043
|
+
"must run runtime checks",
|
|
18044
|
+
"ui routes reachable",
|
|
18045
|
+
"api non-404",
|
|
18046
|
+
"db objects present"
|
|
18047
|
+
];
|
|
18048
|
+
var REQUIRED_MODES = ["low-cost", "standard", "full-harness"];
|
|
18049
|
+
var STATIC_FIRST_INDICATORS = [
|
|
18050
|
+
"static-first",
|
|
18051
|
+
"static checks",
|
|
18052
|
+
"no runtime",
|
|
18053
|
+
"file-based"
|
|
18054
|
+
];
|
|
18055
|
+
var RUNTIME_HEAVY_INDICATORS = [
|
|
18056
|
+
"browser required by default",
|
|
18057
|
+
"runtime mandatory",
|
|
18058
|
+
"always execute runtime"
|
|
18059
|
+
];
|
|
18060
|
+
function skillIssue(code, message, severity, suggestedAction) {
|
|
18061
|
+
return {
|
|
18062
|
+
code,
|
|
18063
|
+
severity,
|
|
18064
|
+
category: "compatibility",
|
|
18065
|
+
message,
|
|
18066
|
+
file: "SKILL.md",
|
|
18067
|
+
suggested_action: suggestedAction
|
|
18068
|
+
};
|
|
18069
|
+
}
|
|
18070
|
+
function scanBannedPhrases(content) {
|
|
18071
|
+
const lower = content.toLowerCase();
|
|
18072
|
+
return BANNED_PHRASES.filter((phrase) => lower.includes(phrase));
|
|
18073
|
+
}
|
|
18074
|
+
function checkModeHeadings(content) {
|
|
18075
|
+
const present = [];
|
|
18076
|
+
const missing = [];
|
|
18077
|
+
for (const mode of REQUIRED_MODES) {
|
|
18078
|
+
const pattern = new RegExp(`^#+\\s+.*${mode.replace("-", "[-\\s]")}`, "im");
|
|
18079
|
+
if (pattern.test(content)) {
|
|
18080
|
+
present.push(mode);
|
|
18081
|
+
} else {
|
|
18082
|
+
missing.push(mode);
|
|
18083
|
+
}
|
|
18084
|
+
}
|
|
18085
|
+
return { present, missing };
|
|
18086
|
+
}
|
|
18087
|
+
function hasNonUiNaDocumentation(content) {
|
|
18088
|
+
const lower = content.toLowerCase();
|
|
18089
|
+
return lower.includes("n/a") && (lower.includes("non-ui") || lower.includes("non_ui"));
|
|
18090
|
+
}
|
|
18091
|
+
function isStaticFirstAligned(content) {
|
|
18092
|
+
const lower = content.toLowerCase();
|
|
18093
|
+
const hasStatic = STATIC_FIRST_INDICATORS.some((i) => lower.includes(i));
|
|
18094
|
+
const hasRuntime = RUNTIME_HEAVY_INDICATORS.some((i) => lower.includes(i));
|
|
18095
|
+
return hasStatic && !hasRuntime;
|
|
18096
|
+
}
|
|
18097
|
+
function validatePrototypingSkillContent(content) {
|
|
18098
|
+
const bannedPhraseMatches = scanBannedPhrases(content);
|
|
18099
|
+
const { present: modesPresent, missing: modesMissing } = checkModeHeadings(content);
|
|
18100
|
+
const nonUiPath = hasNonUiNaDocumentation(content);
|
|
18101
|
+
const staticFirst = isStaticFirstAligned(content);
|
|
18102
|
+
const issues = [];
|
|
18103
|
+
if (bannedPhraseMatches.length > 0) {
|
|
18104
|
+
issues.push(
|
|
18105
|
+
skillIssue(
|
|
18106
|
+
"UIX-VAL-SKILL-BANNED-PHRASE",
|
|
18107
|
+
`Prototyping skill contains banned phrases: ${bannedPhraseMatches.join(", ")}`,
|
|
18108
|
+
"error",
|
|
18109
|
+
"Remove banned runtime-heavy phrases from the prototyping skill body."
|
|
18110
|
+
)
|
|
18111
|
+
);
|
|
18112
|
+
}
|
|
18113
|
+
if (modesMissing.length > 0) {
|
|
18114
|
+
issues.push(
|
|
18115
|
+
skillIssue(
|
|
18116
|
+
"UIX-VAL-SKILL-MODE-MISSING",
|
|
18117
|
+
`Prototyping skill missing mode sections: ${modesMissing.join(", ")}`,
|
|
18118
|
+
"error",
|
|
18119
|
+
`Add section headings for: ${modesMissing.join(", ")}`
|
|
18120
|
+
)
|
|
18121
|
+
);
|
|
18122
|
+
}
|
|
18123
|
+
return {
|
|
18124
|
+
bannedPhraseMatches,
|
|
18125
|
+
modesPresent,
|
|
18126
|
+
modesMissing,
|
|
18127
|
+
hasNonUiNaPath: nonUiPath,
|
|
18128
|
+
isStaticFirstAligned: staticFirst,
|
|
18129
|
+
issues
|
|
18130
|
+
};
|
|
18131
|
+
}
|
|
18132
|
+
|
|
17948
18133
|
// src/core/validate.ts
|
|
17949
18134
|
var UIUX_VALIDATION_BUDGET_MS = 2e3;
|
|
17950
18135
|
async function validateProject(root, configResult, options = {}) {
|
|
@@ -17979,6 +18164,11 @@ async function validateProject(root, configResult, options = {}) {
|
|
|
17979
18164
|
rule: "uiux.performanceBudget"
|
|
17980
18165
|
});
|
|
17981
18166
|
}
|
|
18167
|
+
const skillsDir = resolvePath(root, config, "skillsDir");
|
|
18168
|
+
const fullHarnessResult = await validateFullHarnessSkill(skillsDir);
|
|
18169
|
+
const prototypingSkillPath = import_node_path63.default.join(skillsDir, "qfai-prototyping", "SKILL.md");
|
|
18170
|
+
const prototypingSkillContent = await readSafe2(prototypingSkillPath);
|
|
18171
|
+
const prototypingSkillResult = prototypingSkillContent.length > 0 ? validatePrototypingSkillContent(prototypingSkillContent) : { issues: [] };
|
|
17982
18172
|
const findings = [
|
|
17983
18173
|
...configIssues,
|
|
17984
18174
|
...await validateRepositoryHygiene(root, config),
|
|
@@ -18009,6 +18199,8 @@ async function validateProject(root, configResult, options = {}) {
|
|
|
18009
18199
|
...await validateNavigationFlow(root, config),
|
|
18010
18200
|
...await validateRenderCritique(root, config),
|
|
18011
18201
|
...await validateDesignFidelity(root, config),
|
|
18202
|
+
...fullHarnessResult.issues,
|
|
18203
|
+
...prototypingSkillResult.issues,
|
|
18012
18204
|
...uiuxIssues
|
|
18013
18205
|
];
|
|
18014
18206
|
const { issues, waivers } = await applyWaivers(root, findings);
|
|
@@ -18052,15 +18244,15 @@ var REPORT_GUARDRAILS_MAX = 20;
|
|
|
18052
18244
|
var REPORT_TEST_STRATEGY_SAMPLE_LIMIT = 20;
|
|
18053
18245
|
var SC_TAG_RE4 = /^SC-\d{4}-\d{4}$/;
|
|
18054
18246
|
async function createReportData(root, validation, configResult) {
|
|
18055
|
-
const resolvedRoot =
|
|
18247
|
+
const resolvedRoot = import_node_path64.default.resolve(root);
|
|
18056
18248
|
const resolved = configResult ?? await loadConfig(resolvedRoot);
|
|
18057
18249
|
const config = resolved.config;
|
|
18058
18250
|
const configPath = resolved.configPath;
|
|
18059
18251
|
const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
|
|
18060
18252
|
const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
|
|
18061
|
-
const apiRoot =
|
|
18062
|
-
const uiRoot =
|
|
18063
|
-
const dbRoot =
|
|
18253
|
+
const apiRoot = import_node_path64.default.join(contractsRoot, "api");
|
|
18254
|
+
const uiRoot = import_node_path64.default.join(contractsRoot, "ui");
|
|
18255
|
+
const dbRoot = import_node_path64.default.join(contractsRoot, "db");
|
|
18064
18256
|
const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
|
|
18065
18257
|
const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
|
|
18066
18258
|
const specEntries = await collectSpecEntries(specsRoot);
|
|
@@ -19345,7 +19537,7 @@ function buildHotspots(issues) {
|
|
|
19345
19537
|
async function collectTddCoverage(entries) {
|
|
19346
19538
|
const specs = [];
|
|
19347
19539
|
for (const entry of entries) {
|
|
19348
|
-
const testCasesPath =
|
|
19540
|
+
const testCasesPath = import_node_path64.default.join(entry.dir, "06_Test-Cases.md");
|
|
19349
19541
|
let tcContent;
|
|
19350
19542
|
try {
|
|
19351
19543
|
tcContent = await (0, import_promises59.readFile)(testCasesPath, "utf-8");
|
|
@@ -19380,7 +19572,7 @@ async function collectTddCoverage(entries) {
|
|
|
19380
19572
|
});
|
|
19381
19573
|
continue;
|
|
19382
19574
|
}
|
|
19383
|
-
const tddListPath =
|
|
19575
|
+
const tddListPath = import_node_path64.default.join(entry.dir, "tdd", "test-list.md");
|
|
19384
19576
|
let tddContent;
|
|
19385
19577
|
try {
|
|
19386
19578
|
tddContent = await (0, import_promises59.readFile)(tddListPath, "utf-8");
|
|
@@ -19467,7 +19659,7 @@ async function collectTddCoverage(entries) {
|
|
|
19467
19659
|
|
|
19468
19660
|
// src/core/specPackReport.ts
|
|
19469
19661
|
var import_promises60 = require("fs/promises");
|
|
19470
|
-
var
|
|
19662
|
+
var import_node_path65 = __toESM(require("path"), 1);
|
|
19471
19663
|
var REQUIRED_LEDGER_COLUMNS = [
|
|
19472
19664
|
"trace_id",
|
|
19473
19665
|
"obj_id",
|
|
@@ -19485,8 +19677,8 @@ async function writeSpecPackReports(root, config) {
|
|
|
19485
19677
|
const entries = await collectSpecEntries(specsRoot);
|
|
19486
19678
|
const contractIndex = await buildContractIndex(root, config);
|
|
19487
19679
|
for (const entry of entries) {
|
|
19488
|
-
const specName =
|
|
19489
|
-
const outputDir =
|
|
19680
|
+
const specName = import_node_path65.default.basename(entry.dir);
|
|
19681
|
+
const outputDir = import_node_path65.default.join(outRoot, specName);
|
|
19490
19682
|
await (0, import_promises60.mkdir)(outputDir, { recursive: true });
|
|
19491
19683
|
const [acText, tcText, exText, ledgerText] = await Promise.all([
|
|
19492
19684
|
readSafe12(entry.acceptanceCriteriaPath),
|
|
@@ -19513,13 +19705,13 @@ async function writeSpecPackReports(root, config) {
|
|
|
19513
19705
|
});
|
|
19514
19706
|
const graph = buildTraceabilityGraph(ledgerRows);
|
|
19515
19707
|
await (0, import_promises60.writeFile)(
|
|
19516
|
-
|
|
19708
|
+
import_node_path65.default.join(outputDir, "coverage.md"),
|
|
19517
19709
|
`${formatCoverageMarkdown(specName, coverage)}
|
|
19518
19710
|
`,
|
|
19519
19711
|
"utf-8"
|
|
19520
19712
|
);
|
|
19521
19713
|
await (0, import_promises60.writeFile)(
|
|
19522
|
-
|
|
19714
|
+
import_node_path65.default.join(outputDir, "traceability-graph.json"),
|
|
19523
19715
|
`${JSON.stringify(graph, null, 2)}
|
|
19524
19716
|
`,
|
|
19525
19717
|
"utf-8"
|
|
@@ -19712,7 +19904,7 @@ function warnIfTruncated(scan, context) {
|
|
|
19712
19904
|
|
|
19713
19905
|
// src/cli/commands/report.ts
|
|
19714
19906
|
async function runReport(options) {
|
|
19715
|
-
const root =
|
|
19907
|
+
const root = import_node_path66.default.resolve(options.root);
|
|
19716
19908
|
const configResult = await loadConfig(root);
|
|
19717
19909
|
let validation;
|
|
19718
19910
|
let blockedByPhaseGuard = false;
|
|
@@ -19728,7 +19920,7 @@ async function runReport(options) {
|
|
|
19728
19920
|
validation = normalized;
|
|
19729
19921
|
} else {
|
|
19730
19922
|
const input = options.inputPath ?? configResult.config.output.validateJsonPath;
|
|
19731
|
-
const inputPath =
|
|
19923
|
+
const inputPath = import_node_path66.default.isAbsolute(input) ? input : import_node_path66.default.resolve(root, input);
|
|
19732
19924
|
try {
|
|
19733
19925
|
validation = await readValidationResult(inputPath);
|
|
19734
19926
|
} catch (err) {
|
|
@@ -19755,10 +19947,10 @@ async function runReport(options) {
|
|
|
19755
19947
|
warnIfTruncated(data.traceability.testFiles, "report");
|
|
19756
19948
|
const output = options.format === "json" ? formatReportJson(data) : options.baseUrl ? formatReportMarkdown(data, { baseUrl: options.baseUrl }) : formatReportMarkdown(data);
|
|
19757
19949
|
const outRoot = resolvePath(root, configResult.config, "outDir");
|
|
19758
|
-
const defaultOut = options.format === "json" ?
|
|
19950
|
+
const defaultOut = options.format === "json" ? import_node_path66.default.join(outRoot, "report.json") : import_node_path66.default.join(outRoot, "report.md");
|
|
19759
19951
|
const out = options.outPath ?? defaultOut;
|
|
19760
|
-
const outPath =
|
|
19761
|
-
await (0, import_promises61.mkdir)(
|
|
19952
|
+
const outPath = import_node_path66.default.isAbsolute(out) ? out : import_node_path66.default.resolve(root, out);
|
|
19953
|
+
await (0, import_promises61.mkdir)(import_node_path66.default.dirname(outPath), { recursive: true });
|
|
19762
19954
|
await (0, import_promises61.writeFile)(outPath, `${output}
|
|
19763
19955
|
`, "utf-8");
|
|
19764
19956
|
await writeSpecPackReports(root, configResult.config);
|
|
@@ -19834,21 +20026,21 @@ function isMissingFileError2(error2) {
|
|
|
19834
20026
|
return record2.code === "ENOENT";
|
|
19835
20027
|
}
|
|
19836
20028
|
async function writeValidationResult(root, outputPath, result) {
|
|
19837
|
-
const abs =
|
|
19838
|
-
await (0, import_promises61.mkdir)(
|
|
20029
|
+
const abs = import_node_path66.default.isAbsolute(outputPath) ? outputPath : import_node_path66.default.resolve(root, outputPath);
|
|
20030
|
+
await (0, import_promises61.mkdir)(import_node_path66.default.dirname(abs), { recursive: true });
|
|
19839
20031
|
await (0, import_promises61.writeFile)(abs, `${JSON.stringify(result, null, 2)}
|
|
19840
20032
|
`, "utf-8");
|
|
19841
20033
|
}
|
|
19842
20034
|
|
|
19843
20035
|
// src/cli/commands/validate.ts
|
|
19844
20036
|
var import_promises63 = require("fs/promises");
|
|
19845
|
-
var
|
|
20037
|
+
var import_node_path68 = __toESM(require("path"), 1);
|
|
19846
20038
|
|
|
19847
20039
|
// src/core/runLog.ts
|
|
19848
20040
|
var import_promises62 = require("fs/promises");
|
|
19849
|
-
var
|
|
20041
|
+
var import_node_path67 = __toESM(require("path"), 1);
|
|
19850
20042
|
async function writeValidateRunLog(input) {
|
|
19851
|
-
const root =
|
|
20043
|
+
const root = import_node_path67.default.resolve(input.root);
|
|
19852
20044
|
const outDir = resolvePath(root, input.config, "outDir");
|
|
19853
20045
|
await (0, import_promises62.mkdir)(outDir, { recursive: true });
|
|
19854
20046
|
const { runId, reportDir } = await allocateRunReportDir(outDir, input.startedAt);
|
|
@@ -19895,10 +20087,10 @@ async function writeValidateRunLog(input) {
|
|
|
19895
20087
|
errors,
|
|
19896
20088
|
warnings
|
|
19897
20089
|
});
|
|
19898
|
-
await writeJson(
|
|
19899
|
-
await writeJson(
|
|
19900
|
-
await writeJson(
|
|
19901
|
-
await (0, import_promises62.writeFile)(
|
|
20090
|
+
await writeJson(import_node_path67.default.join(reportDir, "run.json"), runJson);
|
|
20091
|
+
await writeJson(import_node_path67.default.join(reportDir, "validator.json"), validatorJson);
|
|
20092
|
+
await writeJson(import_node_path67.default.join(reportDir, "traceability.json"), traceabilityJson);
|
|
20093
|
+
await (0, import_promises62.writeFile)(import_node_path67.default.join(reportDir, "summary.md"), `${summaryMd}
|
|
19902
20094
|
`, "utf-8");
|
|
19903
20095
|
return {
|
|
19904
20096
|
runId,
|
|
@@ -20022,7 +20214,7 @@ async function allocateRunReportDir(outDir, startedAt) {
|
|
|
20022
20214
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
20023
20215
|
const candidateDate = new Date(startedAt.getTime() + attempt);
|
|
20024
20216
|
const runId = `run-${formatTimestamp17(candidateDate)}`;
|
|
20025
|
-
const reportDir =
|
|
20217
|
+
const reportDir = import_node_path67.default.join(outDir, runId);
|
|
20026
20218
|
try {
|
|
20027
20219
|
await (0, import_promises62.mkdir)(reportDir);
|
|
20028
20220
|
return { runId, reportDir };
|
|
@@ -20053,7 +20245,7 @@ function shouldFail(result, failOn) {
|
|
|
20053
20245
|
// src/cli/commands/validate.ts
|
|
20054
20246
|
async function runValidate(options) {
|
|
20055
20247
|
const startedAt = /* @__PURE__ */ new Date();
|
|
20056
|
-
const root =
|
|
20248
|
+
const root = import_node_path68.default.resolve(options.root);
|
|
20057
20249
|
const configResult = await loadConfig(root);
|
|
20058
20250
|
const blockedIssue = buildCiRefinementIssue(options.phase);
|
|
20059
20251
|
const blockedByPhaseGuard = blockedIssue !== null;
|
|
@@ -20209,12 +20401,12 @@ function issueKey(issue2) {
|
|
|
20209
20401
|
}
|
|
20210
20402
|
async function emitJson(result, root, jsonPath) {
|
|
20211
20403
|
const abs = resolveJsonPath(root, jsonPath);
|
|
20212
|
-
await (0, import_promises63.mkdir)(
|
|
20404
|
+
await (0, import_promises63.mkdir)(import_node_path68.default.dirname(abs), { recursive: true });
|
|
20213
20405
|
await (0, import_promises63.writeFile)(abs, `${JSON.stringify(result, null, 2)}
|
|
20214
20406
|
`, "utf-8");
|
|
20215
20407
|
}
|
|
20216
20408
|
function resolveJsonPath(root, jsonPath) {
|
|
20217
|
-
return
|
|
20409
|
+
return import_node_path68.default.isAbsolute(jsonPath) ? jsonPath : import_node_path68.default.resolve(root, jsonPath);
|
|
20218
20410
|
}
|
|
20219
20411
|
var GITHUB_ANNOTATION_LIMIT = 100;
|
|
20220
20412
|
var ISSUE_EXPECTED_BY_CODE = {
|