@haus-tech/haus-workflow 0.13.1 → 0.13.2
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/CHANGELOG.md +6 -0
- package/dist/cli.js +212 -221
- package/library/catalog/manifest.json +1 -1
- package/package.json +5 -1
- package/tests/fixtures/catalog/policy-gates-manifest.json +120 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.13.2](https://github.com/WeAreHausTech/haus-workflow/compare/v0.13.1...v0.13.2) (2026-06-04)
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
- catalog-audit drift + scanner EMFILE (TDD) ([#65](https://github.com/WeAreHausTech/haus-workflow/issues/65)) ([815ad79](https://github.com/WeAreHausTech/haus-workflow/commit/815ad798b5c28b98f53a8b8fc62c4e8decf9350e))
|
|
8
|
+
|
|
3
9
|
## [0.13.1](https://github.com/WeAreHausTech/haus-workflow/compare/v0.13.0...v0.13.1) (2026-06-03)
|
|
4
10
|
|
|
5
11
|
### Bug Fixes
|
package/dist/cli.js
CHANGED
|
@@ -242,6 +242,16 @@ async function listFiles(root, patterns) {
|
|
|
242
242
|
function hashText(value) {
|
|
243
243
|
return `sha256-${crypto.createHash("sha256").update(value).digest("hex")}`;
|
|
244
244
|
}
|
|
245
|
+
async function mapWithConcurrency(items, fn, concurrency = 24) {
|
|
246
|
+
const size = Number.isFinite(concurrency) ? Math.max(1, Math.floor(concurrency)) : 24;
|
|
247
|
+
const results = new Array(items.length);
|
|
248
|
+
for (let i = 0; i < items.length; i += size) {
|
|
249
|
+
const batch = items.slice(i, i + size);
|
|
250
|
+
const settled = await Promise.all(batch.map((item, j) => fn(item, i + j)));
|
|
251
|
+
for (let j = 0; j < settled.length; j += 1) results[i + j] = settled[j];
|
|
252
|
+
}
|
|
253
|
+
return results;
|
|
254
|
+
}
|
|
245
255
|
|
|
246
256
|
// src/update/hash-installed.ts
|
|
247
257
|
var EMPTY_LOCK_PATHS_TOKEN = "haus-lock:empty-paths";
|
|
@@ -1069,32 +1079,202 @@ async function loadCatalog(root) {
|
|
|
1069
1079
|
return data?.items ?? [];
|
|
1070
1080
|
}
|
|
1071
1081
|
|
|
1082
|
+
// library/catalog/validation-rules.json
|
|
1083
|
+
var validation_rules_default = {
|
|
1084
|
+
forbiddenTags: [
|
|
1085
|
+
"python",
|
|
1086
|
+
"django",
|
|
1087
|
+
"go",
|
|
1088
|
+
"rust",
|
|
1089
|
+
"java",
|
|
1090
|
+
"spring",
|
|
1091
|
+
"kotlin",
|
|
1092
|
+
"swift",
|
|
1093
|
+
"android",
|
|
1094
|
+
"flutter",
|
|
1095
|
+
"dart",
|
|
1096
|
+
"c++",
|
|
1097
|
+
"perl",
|
|
1098
|
+
"defi",
|
|
1099
|
+
"trading"
|
|
1100
|
+
],
|
|
1101
|
+
bannedAgentPhrases: ["autonomous", "swarm", "delegate", "orchestrat", "marketplace"],
|
|
1102
|
+
requiredSkillSections: ["## Use when", "## Do not use when"],
|
|
1103
|
+
requiredAgentSections: ["## Use when", "## Do not use when", "## Verification"],
|
|
1104
|
+
riskyInstallPatterns: [
|
|
1105
|
+
{ source: "\\bnpx\\s+-y\\b", flags: "i" },
|
|
1106
|
+
{ source: "\\bnpx\\s+--yes\\b", flags: "i" },
|
|
1107
|
+
{ source: "\\byarn\\s+dlx\\b", flags: "i" },
|
|
1108
|
+
{ source: "\\bpnpm\\s+dlx\\b", flags: "i" }
|
|
1109
|
+
],
|
|
1110
|
+
allowedNpxPattern: { source: "\\bnpx\\s+tsx\\b", flags: "i" },
|
|
1111
|
+
anyNpxPattern: { source: "\\bnpx\\s+\\S+", flags: "i" },
|
|
1112
|
+
httpUrlPattern: { source: "^http:\\/\\/", flags: "i" },
|
|
1113
|
+
placeholderPattern: { source: "\\bTODO\\b|\\bPLACEHOLDER\\b", flags: "i" },
|
|
1114
|
+
allowedStacks: [
|
|
1115
|
+
"haus",
|
|
1116
|
+
"security",
|
|
1117
|
+
"quality",
|
|
1118
|
+
"frontend",
|
|
1119
|
+
"backend",
|
|
1120
|
+
"testing",
|
|
1121
|
+
"review",
|
|
1122
|
+
"workflow",
|
|
1123
|
+
"reference-pack",
|
|
1124
|
+
"core-skill",
|
|
1125
|
+
"workflow-skill",
|
|
1126
|
+
"stack-skill",
|
|
1127
|
+
"review-skill",
|
|
1128
|
+
"agent",
|
|
1129
|
+
"hook",
|
|
1130
|
+
"rule",
|
|
1131
|
+
"react",
|
|
1132
|
+
"typescript",
|
|
1133
|
+
"php",
|
|
1134
|
+
"csharp",
|
|
1135
|
+
"vendure",
|
|
1136
|
+
"vendure3",
|
|
1137
|
+
"nestjs",
|
|
1138
|
+
"graphql",
|
|
1139
|
+
"nx21",
|
|
1140
|
+
"turbo",
|
|
1141
|
+
"nextjs",
|
|
1142
|
+
"react19",
|
|
1143
|
+
"typescript5",
|
|
1144
|
+
"vite8",
|
|
1145
|
+
"tanstack-query",
|
|
1146
|
+
"tanstack-router",
|
|
1147
|
+
"radix",
|
|
1148
|
+
"radix-ui",
|
|
1149
|
+
"shadcn",
|
|
1150
|
+
"shadcn-ui",
|
|
1151
|
+
"tailwind",
|
|
1152
|
+
"tailwindcss",
|
|
1153
|
+
"scss",
|
|
1154
|
+
"scss-modules",
|
|
1155
|
+
"vue",
|
|
1156
|
+
"expressjs",
|
|
1157
|
+
"soup-base",
|
|
1158
|
+
"laravel",
|
|
1159
|
+
"laravel-nova",
|
|
1160
|
+
"wordpress",
|
|
1161
|
+
"bedrock",
|
|
1162
|
+
"elementor-pro",
|
|
1163
|
+
"acf-pro",
|
|
1164
|
+
"jetengine",
|
|
1165
|
+
"dotnet",
|
|
1166
|
+
"oidc",
|
|
1167
|
+
"azure-ad",
|
|
1168
|
+
"bankid",
|
|
1169
|
+
"myid",
|
|
1170
|
+
"cgi",
|
|
1171
|
+
"crypto",
|
|
1172
|
+
"collection2",
|
|
1173
|
+
"postgresql",
|
|
1174
|
+
"mariadb",
|
|
1175
|
+
"mssql",
|
|
1176
|
+
"elasticsearch",
|
|
1177
|
+
"yarn4",
|
|
1178
|
+
"pnpm89",
|
|
1179
|
+
"playwright",
|
|
1180
|
+
"testing-library",
|
|
1181
|
+
"phpunit",
|
|
1182
|
+
"storybook",
|
|
1183
|
+
"wisest",
|
|
1184
|
+
"vitest",
|
|
1185
|
+
"jest",
|
|
1186
|
+
"redis",
|
|
1187
|
+
"sanity",
|
|
1188
|
+
"strapi",
|
|
1189
|
+
"prisma",
|
|
1190
|
+
"cms",
|
|
1191
|
+
"database",
|
|
1192
|
+
"mysql",
|
|
1193
|
+
"saml2",
|
|
1194
|
+
"next-auth",
|
|
1195
|
+
"auth",
|
|
1196
|
+
"expo",
|
|
1197
|
+
"react-native",
|
|
1198
|
+
"mobile",
|
|
1199
|
+
"i18next",
|
|
1200
|
+
"i18n",
|
|
1201
|
+
"bullmq",
|
|
1202
|
+
"queue",
|
|
1203
|
+
"sentry",
|
|
1204
|
+
"observability",
|
|
1205
|
+
"tooling",
|
|
1206
|
+
"prettier",
|
|
1207
|
+
"eslint",
|
|
1208
|
+
"missing-prettier",
|
|
1209
|
+
"missing-eslint",
|
|
1210
|
+
"docker",
|
|
1211
|
+
"pm2",
|
|
1212
|
+
"deployer-php",
|
|
1213
|
+
"stripe",
|
|
1214
|
+
"qliro",
|
|
1215
|
+
"supabase",
|
|
1216
|
+
"payments"
|
|
1217
|
+
],
|
|
1218
|
+
alwaysAllowedTags: [
|
|
1219
|
+
"haus",
|
|
1220
|
+
"security",
|
|
1221
|
+
"quality",
|
|
1222
|
+
"review",
|
|
1223
|
+
"workflow",
|
|
1224
|
+
"baseline",
|
|
1225
|
+
"project-instructions"
|
|
1226
|
+
],
|
|
1227
|
+
patternTagSuffixes: ["-patterns"]
|
|
1228
|
+
};
|
|
1229
|
+
|
|
1230
|
+
// src/catalog/validation-rules.ts
|
|
1231
|
+
var toRegExp = (r) => new RegExp(r.source, r.flags);
|
|
1232
|
+
var FORBIDDEN_TAGS = validation_rules_default.forbiddenTags;
|
|
1233
|
+
var BANNED_AGENT_PHRASES = validation_rules_default.bannedAgentPhrases;
|
|
1234
|
+
var REQUIRED_SKILL_SECTIONS = validation_rules_default.requiredSkillSections;
|
|
1235
|
+
var REQUIRED_AGENT_SECTIONS = validation_rules_default.requiredAgentSections;
|
|
1236
|
+
var RISKY_INSTALL_PATTERNS = validation_rules_default.riskyInstallPatterns.map(toRegExp);
|
|
1237
|
+
var ALLOWED_NPX_PATTERN = toRegExp(validation_rules_default.allowedNpxPattern);
|
|
1238
|
+
var ANY_NPX_PATTERN = toRegExp(validation_rules_default.anyNpxPattern);
|
|
1239
|
+
var HTTP_URL_PATTERN = toRegExp(validation_rules_default.httpUrlPattern);
|
|
1240
|
+
var PLACEHOLDER_PATTERN = toRegExp(validation_rules_default.placeholderPattern);
|
|
1241
|
+
var ALLOWED_STACKS = validation_rules_default.allowedStacks;
|
|
1242
|
+
var ALWAYS_ALLOWED_TAGS = validation_rules_default.alwaysAllowedTags;
|
|
1243
|
+
var PATTERN_TAG_SUFFIXES = validation_rules_default.patternTagSuffixes;
|
|
1244
|
+
var ALLOWED_SET = new Set([...ALLOWED_STACKS, ...ALWAYS_ALLOWED_TAGS].map((t) => t.toLowerCase()));
|
|
1245
|
+
function isTagAllowed(tag) {
|
|
1246
|
+
const lower = tag.toLowerCase();
|
|
1247
|
+
if (ALLOWED_SET.has(lower)) return true;
|
|
1248
|
+
return PATTERN_TAG_SUFFIXES.some((suffix) => lower.endsWith(suffix));
|
|
1249
|
+
}
|
|
1250
|
+
function auditDisallowedTags(items) {
|
|
1251
|
+
const failures = [];
|
|
1252
|
+
for (const item of items) {
|
|
1253
|
+
if (!item.id) continue;
|
|
1254
|
+
for (const tag of Array.isArray(item.tags) ? item.tags : []) {
|
|
1255
|
+
if (!isTagAllowed(tag)) failures.push(`${item.id}: tag not in allowlist: "${tag}"`);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
return failures;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1072
1261
|
// src/commands/catalog-audit.ts
|
|
1073
|
-
|
|
1074
|
-
"python",
|
|
1075
|
-
"django",
|
|
1076
|
-
"go",
|
|
1077
|
-
"rust",
|
|
1078
|
-
"java",
|
|
1079
|
-
"spring",
|
|
1080
|
-
"kotlin",
|
|
1081
|
-
"swift",
|
|
1082
|
-
"android",
|
|
1083
|
-
"flutter",
|
|
1084
|
-
"dart",
|
|
1085
|
-
"c++",
|
|
1086
|
-
"perl",
|
|
1087
|
-
"defi",
|
|
1088
|
-
"trading"
|
|
1089
|
-
];
|
|
1090
|
-
async function runCatalogAudit() {
|
|
1091
|
-
const items = await loadCatalog(process.cwd());
|
|
1262
|
+
function auditForbiddenTags(items) {
|
|
1092
1263
|
const failures = [];
|
|
1264
|
+
const forbidden = new Set(FORBIDDEN_TAGS.map((w) => w.toLowerCase()));
|
|
1093
1265
|
for (const item of items) {
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1096
|
-
|
|
1266
|
+
for (const tag of item.tags) {
|
|
1267
|
+
if (forbidden.has(tag.toLowerCase())) failures.push(`${item.id} has unsupported tag ${tag}`);
|
|
1268
|
+
}
|
|
1269
|
+
for (const token of item.id.toLowerCase().split(/[^a-z0-9+]+/)) {
|
|
1270
|
+
if (forbidden.has(token)) failures.push(`${item.id} has unsupported tag ${token}`);
|
|
1271
|
+
}
|
|
1097
1272
|
}
|
|
1273
|
+
return failures;
|
|
1274
|
+
}
|
|
1275
|
+
async function runCatalogAudit() {
|
|
1276
|
+
const items = await loadCatalog(process.cwd());
|
|
1277
|
+
const failures = auditForbiddenTags(items);
|
|
1098
1278
|
if (failures.length) {
|
|
1099
1279
|
failures.forEach((f) => error(f));
|
|
1100
1280
|
process.exitCode = 1;
|
|
@@ -1831,20 +2011,13 @@ async function buildContentBlob(root, files) {
|
|
|
1831
2011
|
(f) => f.endsWith(".ts") || f.endsWith(".js") || f.endsWith(".php") || f.endsWith(".json") || f.endsWith(".yml") || f.endsWith(".yaml")
|
|
1832
2012
|
);
|
|
1833
2013
|
const slice = candidates.slice(0, 300);
|
|
1834
|
-
const
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
} catch {
|
|
1842
|
-
return "";
|
|
1843
|
-
}
|
|
1844
|
-
})
|
|
1845
|
-
);
|
|
1846
|
-
parts.push(...batch);
|
|
1847
|
-
}
|
|
2014
|
+
const parts = await mapWithConcurrency(slice, async (rel) => {
|
|
2015
|
+
try {
|
|
2016
|
+
return await readFile(path15.join(root, rel), "utf8");
|
|
2017
|
+
} catch {
|
|
2018
|
+
return "";
|
|
2019
|
+
}
|
|
2020
|
+
});
|
|
1848
2021
|
return parts.join("\n");
|
|
1849
2022
|
}
|
|
1850
2023
|
function renderSummary(context) {
|
|
@@ -1957,10 +2130,9 @@ async function scanProject(root, mode = "fast") {
|
|
|
1957
2130
|
composer: isRecord(composer?.require) ? Object.keys(composer.require) : []
|
|
1958
2131
|
};
|
|
1959
2132
|
const scanHashes = Object.fromEntries(
|
|
1960
|
-
await
|
|
1961
|
-
safeFiles
|
|
1962
|
-
|
|
1963
|
-
)
|
|
2133
|
+
await mapWithConcurrency(
|
|
2134
|
+
safeFiles,
|
|
2135
|
+
async (f) => [f, hashText(await readFile2(path16.join(root, f), "utf8"))]
|
|
1964
2136
|
)
|
|
1965
2137
|
);
|
|
1966
2138
|
const repoSummary = renderSummary(context);
|
|
@@ -3525,187 +3697,6 @@ async function runUpdate(options) {
|
|
|
3525
3697
|
// src/commands/validate-catalog.ts
|
|
3526
3698
|
import fs17 from "fs";
|
|
3527
3699
|
import path26 from "path";
|
|
3528
|
-
|
|
3529
|
-
// library/catalog/validation-rules.json
|
|
3530
|
-
var validation_rules_default = {
|
|
3531
|
-
forbiddenTags: [
|
|
3532
|
-
"python",
|
|
3533
|
-
"django",
|
|
3534
|
-
"go",
|
|
3535
|
-
"rust",
|
|
3536
|
-
"java",
|
|
3537
|
-
"spring",
|
|
3538
|
-
"kotlin",
|
|
3539
|
-
"swift",
|
|
3540
|
-
"android",
|
|
3541
|
-
"flutter",
|
|
3542
|
-
"dart",
|
|
3543
|
-
"c++",
|
|
3544
|
-
"perl",
|
|
3545
|
-
"defi",
|
|
3546
|
-
"trading"
|
|
3547
|
-
],
|
|
3548
|
-
bannedAgentPhrases: ["autonomous", "swarm", "delegate", "orchestrat", "marketplace"],
|
|
3549
|
-
requiredSkillSections: ["## Use when", "## Do not use when"],
|
|
3550
|
-
requiredAgentSections: ["## Use when", "## Do not use when", "## Verification"],
|
|
3551
|
-
riskyInstallPatterns: [
|
|
3552
|
-
{ source: "\\bnpx\\s+-y\\b", flags: "i" },
|
|
3553
|
-
{ source: "\\bnpx\\s+--yes\\b", flags: "i" },
|
|
3554
|
-
{ source: "\\byarn\\s+dlx\\b", flags: "i" },
|
|
3555
|
-
{ source: "\\bpnpm\\s+dlx\\b", flags: "i" }
|
|
3556
|
-
],
|
|
3557
|
-
allowedNpxPattern: { source: "\\bnpx\\s+tsx\\b", flags: "i" },
|
|
3558
|
-
anyNpxPattern: { source: "\\bnpx\\s+\\S+", flags: "i" },
|
|
3559
|
-
httpUrlPattern: { source: "^http:\\/\\/", flags: "i" },
|
|
3560
|
-
placeholderPattern: { source: "\\bTODO\\b|\\bPLACEHOLDER\\b", flags: "i" },
|
|
3561
|
-
allowedStacks: [
|
|
3562
|
-
"haus",
|
|
3563
|
-
"security",
|
|
3564
|
-
"quality",
|
|
3565
|
-
"frontend",
|
|
3566
|
-
"backend",
|
|
3567
|
-
"testing",
|
|
3568
|
-
"review",
|
|
3569
|
-
"workflow",
|
|
3570
|
-
"reference-pack",
|
|
3571
|
-
"core-skill",
|
|
3572
|
-
"workflow-skill",
|
|
3573
|
-
"stack-skill",
|
|
3574
|
-
"review-skill",
|
|
3575
|
-
"agent",
|
|
3576
|
-
"hook",
|
|
3577
|
-
"rule",
|
|
3578
|
-
"react",
|
|
3579
|
-
"typescript",
|
|
3580
|
-
"php",
|
|
3581
|
-
"csharp",
|
|
3582
|
-
"vendure",
|
|
3583
|
-
"vendure3",
|
|
3584
|
-
"nestjs",
|
|
3585
|
-
"graphql",
|
|
3586
|
-
"nx21",
|
|
3587
|
-
"turbo",
|
|
3588
|
-
"nextjs",
|
|
3589
|
-
"react19",
|
|
3590
|
-
"typescript5",
|
|
3591
|
-
"vite8",
|
|
3592
|
-
"tanstack-query",
|
|
3593
|
-
"tanstack-router",
|
|
3594
|
-
"radix",
|
|
3595
|
-
"radix-ui",
|
|
3596
|
-
"shadcn",
|
|
3597
|
-
"shadcn-ui",
|
|
3598
|
-
"tailwind",
|
|
3599
|
-
"tailwindcss",
|
|
3600
|
-
"scss",
|
|
3601
|
-
"scss-modules",
|
|
3602
|
-
"vue",
|
|
3603
|
-
"expressjs",
|
|
3604
|
-
"soup-base",
|
|
3605
|
-
"laravel",
|
|
3606
|
-
"laravel-nova",
|
|
3607
|
-
"wordpress",
|
|
3608
|
-
"bedrock",
|
|
3609
|
-
"elementor-pro",
|
|
3610
|
-
"acf-pro",
|
|
3611
|
-
"jetengine",
|
|
3612
|
-
"dotnet",
|
|
3613
|
-
"oidc",
|
|
3614
|
-
"azure-ad",
|
|
3615
|
-
"bankid",
|
|
3616
|
-
"myid",
|
|
3617
|
-
"cgi",
|
|
3618
|
-
"crypto",
|
|
3619
|
-
"collection2",
|
|
3620
|
-
"postgresql",
|
|
3621
|
-
"mariadb",
|
|
3622
|
-
"mssql",
|
|
3623
|
-
"elasticsearch",
|
|
3624
|
-
"yarn4",
|
|
3625
|
-
"pnpm89",
|
|
3626
|
-
"playwright",
|
|
3627
|
-
"testing-library",
|
|
3628
|
-
"phpunit",
|
|
3629
|
-
"storybook",
|
|
3630
|
-
"wisest",
|
|
3631
|
-
"vitest",
|
|
3632
|
-
"jest",
|
|
3633
|
-
"redis",
|
|
3634
|
-
"sanity",
|
|
3635
|
-
"strapi",
|
|
3636
|
-
"prisma",
|
|
3637
|
-
"cms",
|
|
3638
|
-
"database",
|
|
3639
|
-
"mysql",
|
|
3640
|
-
"saml2",
|
|
3641
|
-
"next-auth",
|
|
3642
|
-
"auth",
|
|
3643
|
-
"expo",
|
|
3644
|
-
"react-native",
|
|
3645
|
-
"mobile",
|
|
3646
|
-
"i18next",
|
|
3647
|
-
"i18n",
|
|
3648
|
-
"bullmq",
|
|
3649
|
-
"queue",
|
|
3650
|
-
"sentry",
|
|
3651
|
-
"observability",
|
|
3652
|
-
"tooling",
|
|
3653
|
-
"prettier",
|
|
3654
|
-
"eslint",
|
|
3655
|
-
"missing-prettier",
|
|
3656
|
-
"missing-eslint",
|
|
3657
|
-
"docker",
|
|
3658
|
-
"pm2",
|
|
3659
|
-
"deployer-php",
|
|
3660
|
-
"stripe",
|
|
3661
|
-
"qliro",
|
|
3662
|
-
"supabase",
|
|
3663
|
-
"payments"
|
|
3664
|
-
],
|
|
3665
|
-
alwaysAllowedTags: [
|
|
3666
|
-
"haus",
|
|
3667
|
-
"security",
|
|
3668
|
-
"quality",
|
|
3669
|
-
"review",
|
|
3670
|
-
"workflow",
|
|
3671
|
-
"baseline",
|
|
3672
|
-
"project-instructions"
|
|
3673
|
-
],
|
|
3674
|
-
patternTagSuffixes: ["-patterns"]
|
|
3675
|
-
};
|
|
3676
|
-
|
|
3677
|
-
// src/catalog/validation-rules.ts
|
|
3678
|
-
var toRegExp = (r) => new RegExp(r.source, r.flags);
|
|
3679
|
-
var FORBIDDEN_TAGS = validation_rules_default.forbiddenTags;
|
|
3680
|
-
var BANNED_AGENT_PHRASES = validation_rules_default.bannedAgentPhrases;
|
|
3681
|
-
var REQUIRED_SKILL_SECTIONS = validation_rules_default.requiredSkillSections;
|
|
3682
|
-
var REQUIRED_AGENT_SECTIONS = validation_rules_default.requiredAgentSections;
|
|
3683
|
-
var RISKY_INSTALL_PATTERNS = validation_rules_default.riskyInstallPatterns.map(toRegExp);
|
|
3684
|
-
var ALLOWED_NPX_PATTERN = toRegExp(validation_rules_default.allowedNpxPattern);
|
|
3685
|
-
var ANY_NPX_PATTERN = toRegExp(validation_rules_default.anyNpxPattern);
|
|
3686
|
-
var HTTP_URL_PATTERN = toRegExp(validation_rules_default.httpUrlPattern);
|
|
3687
|
-
var PLACEHOLDER_PATTERN = toRegExp(validation_rules_default.placeholderPattern);
|
|
3688
|
-
var ALLOWED_STACKS = validation_rules_default.allowedStacks;
|
|
3689
|
-
var ALWAYS_ALLOWED_TAGS = validation_rules_default.alwaysAllowedTags;
|
|
3690
|
-
var PATTERN_TAG_SUFFIXES = validation_rules_default.patternTagSuffixes;
|
|
3691
|
-
var ALLOWED_SET = new Set([...ALLOWED_STACKS, ...ALWAYS_ALLOWED_TAGS].map((t) => t.toLowerCase()));
|
|
3692
|
-
function isTagAllowed(tag) {
|
|
3693
|
-
const lower = tag.toLowerCase();
|
|
3694
|
-
if (ALLOWED_SET.has(lower)) return true;
|
|
3695
|
-
return PATTERN_TAG_SUFFIXES.some((suffix) => lower.endsWith(suffix));
|
|
3696
|
-
}
|
|
3697
|
-
function auditDisallowedTags(items) {
|
|
3698
|
-
const failures = [];
|
|
3699
|
-
for (const item of items) {
|
|
3700
|
-
if (!item.id) continue;
|
|
3701
|
-
for (const tag of Array.isArray(item.tags) ? item.tags : []) {
|
|
3702
|
-
if (!isTagAllowed(tag)) failures.push(`${item.id}: tag not in allowlist: "${tag}"`);
|
|
3703
|
-
}
|
|
3704
|
-
}
|
|
3705
|
-
return failures;
|
|
3706
|
-
}
|
|
3707
|
-
|
|
3708
|
-
// src/commands/validate-catalog.ts
|
|
3709
3700
|
function auditForbiddenStacks(items) {
|
|
3710
3701
|
const failures = [];
|
|
3711
3702
|
for (const item of items) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haus-tech/haus-workflow",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.2",
|
|
4
4
|
"description": "Haus AI workflow CLI for Claude Code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
"build": "tsup src/cli.ts --format esm --dts --clean --out-dir dist --external @inquirer/checkbox",
|
|
27
27
|
"dev": "tsx src/cli.ts",
|
|
28
28
|
"test": "node --import tsx --test tests/**/*.test.js",
|
|
29
|
+
"test:coverage": "c8 yarn test",
|
|
30
|
+
"coverage:check": "c8 --check-coverage yarn test",
|
|
29
31
|
"lint": "eslint src scripts",
|
|
30
32
|
"format:check": "prettier --check .",
|
|
31
33
|
"format": "prettier --write .",
|
|
@@ -38,6 +40,7 @@
|
|
|
38
40
|
"release:dry": "GITHUB_TOKEN=$(gh auth token) release-it --dry-run",
|
|
39
41
|
"prepack": "yarn build",
|
|
40
42
|
"verify": "yarn typecheck && yarn typecheck:scripts && yarn lint && yarn build && yarn test",
|
|
43
|
+
"verify:full": "yarn verify && yarn coverage:check",
|
|
41
44
|
"postinstall": "node ./scripts/postinstall.mjs || true",
|
|
42
45
|
"prepare": "lefthook install || true",
|
|
43
46
|
"security:audit": "yarn npm audit -A -R --severity high --environment production"
|
|
@@ -62,6 +65,7 @@
|
|
|
62
65
|
"@types/semver": "7.7.1",
|
|
63
66
|
"@typescript-eslint/eslint-plugin": "8.59.3",
|
|
64
67
|
"@typescript-eslint/parser": "8.59.3",
|
|
68
|
+
"c8": "11.0.0",
|
|
65
69
|
"eslint": "9.39.4",
|
|
66
70
|
"eslint-plugin-import": "2.32.0",
|
|
67
71
|
"lefthook": "1.13.6",
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1.0",
|
|
3
|
+
"_note": "Policy gates test fixture. One item per gate exercised by recommend-eligibility.test.js.",
|
|
4
|
+
"items": [
|
|
5
|
+
{
|
|
6
|
+
"id": "test.unsupported-python",
|
|
7
|
+
"source": "haus",
|
|
8
|
+
"type": "skill",
|
|
9
|
+
"path": "skills/test.unsupported-python",
|
|
10
|
+
"title": "Test: Unsupported Python",
|
|
11
|
+
"purpose": "Triggers UNSUPPORTED gate (python in tags).",
|
|
12
|
+
"tags": ["python", "backend"],
|
|
13
|
+
"repoRoles": [],
|
|
14
|
+
"default": false,
|
|
15
|
+
"tokenEstimate": 100
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "test.curated-not-approved",
|
|
19
|
+
"source": "curated",
|
|
20
|
+
"type": "skill",
|
|
21
|
+
"path": "skills/test.curated-not-approved",
|
|
22
|
+
"title": "Test: Curated Not Approved",
|
|
23
|
+
"purpose": "Triggers curated-not-approved gate (reviewStatus:candidate).",
|
|
24
|
+
"tags": ["frontend"],
|
|
25
|
+
"repoRoles": [],
|
|
26
|
+
"reviewStatus": "candidate",
|
|
27
|
+
"default": false,
|
|
28
|
+
"tokenEstimate": 100
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "test.curated-risk-blocked",
|
|
32
|
+
"source": "curated",
|
|
33
|
+
"type": "skill",
|
|
34
|
+
"path": "skills/test.curated-risk-blocked",
|
|
35
|
+
"title": "Test: Curated Risk Blocked",
|
|
36
|
+
"purpose": "Triggers curated-risk-blocked gate (reviewStatus:approved but riskLevel:blocked).",
|
|
37
|
+
"tags": ["frontend"],
|
|
38
|
+
"repoRoles": [],
|
|
39
|
+
"reviewStatus": "approved",
|
|
40
|
+
"riskLevel": "blocked",
|
|
41
|
+
"default": false,
|
|
42
|
+
"tokenEstimate": 100
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "test.env-management",
|
|
46
|
+
"source": "haus",
|
|
47
|
+
"type": "skill",
|
|
48
|
+
"path": "skills/test.env-management",
|
|
49
|
+
"title": "Test: Sensitive Secrets Item",
|
|
50
|
+
"purpose": "Triggers sensitive-policy gate (secrets in tags, matched by SENSITIVE_ITEM_KEYWORDS).",
|
|
51
|
+
"tags": ["secrets", "workflow"],
|
|
52
|
+
"repoRoles": [],
|
|
53
|
+
"default": false,
|
|
54
|
+
"tokenEstimate": 100
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "test.third-party-unapproved",
|
|
58
|
+
"source": "third-party-plugin",
|
|
59
|
+
"type": "skill",
|
|
60
|
+
"path": "skills/test.third-party-unapproved",
|
|
61
|
+
"title": "Test: Third-Party Unapproved",
|
|
62
|
+
"purpose": "Triggers source-approval gate (non-haus, non-curated, not in sources-report.json).",
|
|
63
|
+
"tags": ["typescript"],
|
|
64
|
+
"repoRoles": [],
|
|
65
|
+
"default": false,
|
|
66
|
+
"tokenEstimate": 100
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"id": "test.requires-svelte",
|
|
70
|
+
"source": "haus",
|
|
71
|
+
"type": "skill",
|
|
72
|
+
"path": "skills/test.requires-svelte",
|
|
73
|
+
"title": "Test: Requires Svelte",
|
|
74
|
+
"purpose": "Triggers requiresAny unsatisfied gate when svelte not in context.",
|
|
75
|
+
"tags": ["svelte"],
|
|
76
|
+
"repoRoles": [],
|
|
77
|
+
"requiresAny": [{ "dependency": "svelte" }],
|
|
78
|
+
"default": false,
|
|
79
|
+
"tokenEstimate": 100
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"id": "test.default-baseline",
|
|
83
|
+
"source": "haus",
|
|
84
|
+
"type": "skill",
|
|
85
|
+
"path": "skills/test.default-baseline",
|
|
86
|
+
"title": "Test: Default Baseline",
|
|
87
|
+
"purpose": "Passes all gates because default:true. Used to verify tokenEstimate is preserved.",
|
|
88
|
+
"tags": ["workflow"],
|
|
89
|
+
"repoRoles": [],
|
|
90
|
+
"default": true,
|
|
91
|
+
"tokenEstimate": 999
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"id": "test.role-matched",
|
|
95
|
+
"source": "haus",
|
|
96
|
+
"type": "skill",
|
|
97
|
+
"path": "skills/test.role-matched",
|
|
98
|
+
"title": "Test: Role Matched",
|
|
99
|
+
"purpose": "Recommended when context has nextjs stack. Tests positive matching path.",
|
|
100
|
+
"tags": ["nextjs", "frontend"],
|
|
101
|
+
"repoRoles": ["next-app"],
|
|
102
|
+
"requiresAny": [{ "stack": "nextjs" }],
|
|
103
|
+
"default": false,
|
|
104
|
+
"tokenEstimate": 100
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"id": "haus.nx21-monorepo-patterns",
|
|
108
|
+
"source": "haus",
|
|
109
|
+
"type": "skill",
|
|
110
|
+
"path": "skills/haus.nx21-monorepo-patterns",
|
|
111
|
+
"title": "Haus Nx 21 Monorepo Patterns",
|
|
112
|
+
"purpose": "Triggers required-role-missing gate when nx-monorepo role is absent. Hardcoded check in recommend().",
|
|
113
|
+
"tags": ["nx", "monorepo"],
|
|
114
|
+
"repoRoles": ["nx-monorepo"],
|
|
115
|
+
"requiresAny": [{ "stack": "nx" }],
|
|
116
|
+
"default": false,
|
|
117
|
+
"tokenEstimate": 100
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
}
|