@haus-tech/haus-workflow 0.17.1 → 0.18.1
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 +16 -0
- package/README.md +14 -3
- package/dist/cli.js +80 -19
- package/library/catalog/manifest.json +2 -2
- package/library/catalog/validation-rules.json +2 -3
- package/package.json +1 -1
- package/tests/fixtures/catalog/skills/auth-oidc-azure-bankid-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/bullmq-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/database-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/dotnet-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/dotnet-service-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/eslint-setup/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/expo-react-native-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/global-engineering-rules/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/i18next-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/jest-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/laravel-nova-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/laravel-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/nestjs-graphql-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/nextauth-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/nextjs-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/nx21-monorepo-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/package-manager-yarn4-pnpm89/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/phpunit-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/playwright-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/prettier-setup/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/prisma-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/production-readiness-review/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/qliro-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/radix-shadcn-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/react-router-v7-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/react19-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/sanity-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/security-review/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/sentry-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/storybook-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/strapi-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/stripe-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/supabase-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/tailwind-scss-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/tanstack-query-router-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/testing-library-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/turbo-monorepo-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/typescript5-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/vendure-app-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/vendure-plugin-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/vite8-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/vitest-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/vue-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/wordpress-acf-elementor-jetengine-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/wordpress-bedrock-patterns/SKILL.md +6 -1
- package/tests/fixtures/catalog/skills/wordpress-patterns/SKILL.md +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.18.1](https://github.com/WeAreHausTech/haus-workflow/compare/v0.18.0...v0.18.1) (2026-06-09)
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
- **ci:** improve PR creation logic in sync-catalog-fixture workflow ([d7717d6](https://github.com/WeAreHausTech/haus-workflow/commit/d7717d6024bb400b02f3254ff7f2d33b6e1fb4c6))
|
|
8
|
+
|
|
9
|
+
## [0.18.0](https://github.com/WeAreHausTech/haus-workflow/compare/v0.17.1...v0.18.0) (2026-06-09)
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- prune stale catalog copies and simplify validators ([#83](https://github.com/WeAreHausTech/haus-workflow/issues/83)) ([f87541c](https://github.com/WeAreHausTech/haus-workflow/commit/f87541cc182e9cf2461ba6b388dc227aba88900e))
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
- **validate:** parse folded YAML description frontmatter ([#84](https://github.com/WeAreHausTech/haus-workflow/issues/84)) ([677d7de](https://github.com/WeAreHausTech/haus-workflow/commit/677d7def4e838c4e5381f34c870a5525c4614073))
|
|
18
|
+
|
|
3
19
|
## [0.17.1](https://github.com/WeAreHausTech/haus-workflow/compare/v0.17.0...v0.17.1) (2026-06-09)
|
|
4
20
|
|
|
5
21
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -66,12 +66,13 @@ haus setup-project # re-run setup on existing project
|
|
|
66
66
|
haus scan # scan repo and write context-map
|
|
67
67
|
haus recommend # recommend catalog items (binary eligibility)
|
|
68
68
|
haus apply --dry-run # preview what would be written
|
|
69
|
-
haus apply --write # write .claude/ files
|
|
69
|
+
haus apply --write # write .claude/ files (skills, agents, commands, templates)
|
|
70
|
+
haus apply --select # interactively choose which recommended items to install
|
|
70
71
|
haus apply --refill-config # fill still-blank workflow-config.md fields, keep edits
|
|
71
72
|
haus context --task "<task>" # select context rules for a task (token-budgeted)
|
|
72
|
-
haus update # sync remote catalog +
|
|
73
|
+
haus update # sync remote catalog + re-apply project files
|
|
73
74
|
haus update --check # check for updates without applying
|
|
74
|
-
haus undo #
|
|
75
|
+
haus undo # remove haus-managed project files (lock-tracked paths)
|
|
75
76
|
haus doctor # health check: hooks, CLAUDE.md, imports, catalog cache
|
|
76
77
|
haus config # manage hook configuration
|
|
77
78
|
haus guard # test bash/file-access guards
|
|
@@ -91,9 +92,19 @@ yarn verify # typecheck + lint + build + test
|
|
|
91
92
|
yarn dev <cmd> # run CLI without building (tsx)
|
|
92
93
|
```
|
|
93
94
|
|
|
95
|
+
### Catalog
|
|
96
|
+
|
|
97
|
+
Content lives in [`haus-workflow-catalog`](https://github.com/WeAreHausTech/haus-workflow-catalog)
|
|
98
|
+
(71 items at v2.5.0). Fetched at runtime from `main` (override with `HAUS_CATALOG_REF`).
|
|
99
|
+
Validation rules sync from catalog → `library/catalog/validation-rules.json` (ADR-0001).
|
|
100
|
+
|
|
101
|
+
On `haus apply` / `haus update`, items **removed from the catalog** are pruned from the
|
|
102
|
+
project when their on-disk copy still matches the lock hash; user-edited copies are kept.
|
|
103
|
+
|
|
94
104
|
### Internal docs
|
|
95
105
|
|
|
96
106
|
- [Architecture](docs/architecture.md)
|
|
97
107
|
- [CLI reference](docs/cli.md)
|
|
98
108
|
- [Security](docs/security.md)
|
|
99
109
|
- [Developer scripts](docs/dev.md)
|
|
110
|
+
- [Runbook](docs/runbook.md)
|
package/dist/cli.js
CHANGED
|
@@ -1262,6 +1262,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
|
|
|
1262
1262
|
);
|
|
1263
1263
|
}
|
|
1264
1264
|
}
|
|
1265
|
+
await cleanupStaleCatalogItems(root, new Set(manifestById.keys()), dryRun);
|
|
1265
1266
|
if (dryRun) return [...new Set(files)];
|
|
1266
1267
|
const installedItems = catalogItems.filter((r) => installedIds.has(r.id));
|
|
1267
1268
|
await writeManagedJson(
|
|
@@ -1304,6 +1305,50 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
|
|
|
1304
1305
|
await writeManagedJson(root, hausPath(root, "haus.lock.json"), lock, false);
|
|
1305
1306
|
return [...new Set(files)];
|
|
1306
1307
|
}
|
|
1308
|
+
async function cleanupStaleCatalogItems(root, knownIds, dryRun) {
|
|
1309
|
+
const prevLock = await readJson(hausPath(root, "haus.lock.json"));
|
|
1310
|
+
if (!prevLock?.length) return;
|
|
1311
|
+
for (const entry of prevLock) {
|
|
1312
|
+
if (!entry.id || knownIds.has(entry.id)) continue;
|
|
1313
|
+
const relPaths = entry.paths ?? [];
|
|
1314
|
+
if (relPaths.length === 0) continue;
|
|
1315
|
+
const existing = [];
|
|
1316
|
+
for (const rel of relPaths) {
|
|
1317
|
+
if (await fs10.pathExists(path12.join(root, rel))) existing.push(rel);
|
|
1318
|
+
}
|
|
1319
|
+
if (existing.length === 0) continue;
|
|
1320
|
+
if (entry.hash === void 0) {
|
|
1321
|
+
warn(
|
|
1322
|
+
`Stale catalog item ${entry.id} has no lock hash \u2014 leaving in place: ${existing.join(", ")}`
|
|
1323
|
+
);
|
|
1324
|
+
continue;
|
|
1325
|
+
}
|
|
1326
|
+
const currentHash = await hashInstalledPaths(root, relPaths);
|
|
1327
|
+
if (currentHash !== entry.hash) {
|
|
1328
|
+
warn(
|
|
1329
|
+
`Stale catalog item ${entry.id} was modified locally \u2014 leaving in place: ${existing.join(", ")}`
|
|
1330
|
+
);
|
|
1331
|
+
continue;
|
|
1332
|
+
}
|
|
1333
|
+
for (const rel of existing) {
|
|
1334
|
+
const abs = path12.join(root, rel);
|
|
1335
|
+
if (dryRun) {
|
|
1336
|
+
log(`[dry-run] would remove stale ${displayPath(root, abs)} (${entry.id})`);
|
|
1337
|
+
continue;
|
|
1338
|
+
}
|
|
1339
|
+
await fs10.remove(abs);
|
|
1340
|
+
await pruneEmptyDir(path12.dirname(abs));
|
|
1341
|
+
log(`Removed stale ${displayPath(root, abs)} (${entry.id})`);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
async function pruneEmptyDir(dir) {
|
|
1346
|
+
try {
|
|
1347
|
+
const entries = await fs10.readdir(dir);
|
|
1348
|
+
if (entries.length === 0) await fs10.remove(dir);
|
|
1349
|
+
} catch {
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1307
1352
|
async function writeManagedText(root, filePath, nextText, dryRun) {
|
|
1308
1353
|
const prev = await fs10.pathExists(filePath) ? await fs10.readFile(filePath, "utf8") : "";
|
|
1309
1354
|
const printable = displayPath(root, filePath);
|
|
@@ -1424,7 +1469,7 @@ var validation_rules_default = {
|
|
|
1424
1469
|
"trading"
|
|
1425
1470
|
],
|
|
1426
1471
|
bannedAgentPhrases: ["autonomous", "swarm", "delegate", "orchestrat", "marketplace"],
|
|
1427
|
-
|
|
1472
|
+
requiredSkillFrontmatter: ["description"],
|
|
1428
1473
|
requiredAgentSections: ["## Use when", "## Do not use when", "## Verification"],
|
|
1429
1474
|
riskyInstallPatterns: [
|
|
1430
1475
|
{ source: "\\bnpx\\s+-y\\b", flags: "i" },
|
|
@@ -1549,19 +1594,14 @@ var validation_rules_default = {
|
|
|
1549
1594
|
"baseline",
|
|
1550
1595
|
"project-instructions"
|
|
1551
1596
|
],
|
|
1552
|
-
patternTagSuffixes: ["-patterns"]
|
|
1553
|
-
skillSectionExemptSources: ["curated"]
|
|
1597
|
+
patternTagSuffixes: ["-patterns"]
|
|
1554
1598
|
};
|
|
1555
1599
|
|
|
1556
1600
|
// src/catalog/validation-rules.ts
|
|
1557
1601
|
var toRegExp = (r) => new RegExp(r.source, r.flags);
|
|
1558
1602
|
var FORBIDDEN_TAGS = validation_rules_default.forbiddenTags;
|
|
1559
1603
|
var BANNED_AGENT_PHRASES = validation_rules_default.bannedAgentPhrases;
|
|
1560
|
-
var
|
|
1561
|
-
var SKILL_SECTION_EXEMPT_SOURCES = validation_rules_default.skillSectionExemptSources;
|
|
1562
|
-
function isVerbatimSuperpowersMarkdownPath(rel) {
|
|
1563
|
-
return rel.replace(/\\/g, "/").includes("/superpowers/");
|
|
1564
|
-
}
|
|
1604
|
+
var REQUIRED_SKILL_FRONTMATTER = validation_rules_default.requiredSkillFrontmatter;
|
|
1565
1605
|
var REQUIRED_AGENT_SECTIONS = validation_rules_default.requiredAgentSections;
|
|
1566
1606
|
var RISKY_INSTALL_PATTERNS = validation_rules_default.riskyInstallPatterns.map(toRegExp);
|
|
1567
1607
|
var ALLOWED_NPX_PATTERN = toRegExp(validation_rules_default.allowedNpxPattern);
|
|
@@ -3819,7 +3859,7 @@ async function runUninstall(options = {}) {
|
|
|
3819
3859
|
continue;
|
|
3820
3860
|
}
|
|
3821
3861
|
await fs17.remove(entry.destPath);
|
|
3822
|
-
await
|
|
3862
|
+
await pruneEmptyDir2(path23.dirname(entry.destPath));
|
|
3823
3863
|
result.deleted.push(entry.destPath);
|
|
3824
3864
|
}
|
|
3825
3865
|
const settings = await readSettings();
|
|
@@ -3850,7 +3890,7 @@ function printUninstallResult(result) {
|
|
|
3850
3890
|
log("Haus hook entries removed from ~/.claude/settings.json");
|
|
3851
3891
|
}
|
|
3852
3892
|
}
|
|
3853
|
-
async function
|
|
3893
|
+
async function pruneEmptyDir2(dir) {
|
|
3854
3894
|
try {
|
|
3855
3895
|
const entries = await fs17.readdir(dir);
|
|
3856
3896
|
if (entries.length === 0) await fs17.remove(dir);
|
|
@@ -4086,8 +4126,33 @@ function extractUseWhenSection(text) {
|
|
|
4086
4126
|
const next = tail.search(/\n##\s+/);
|
|
4087
4127
|
return next < 0 ? tail : tail.slice(0, next);
|
|
4088
4128
|
}
|
|
4129
|
+
function isYamlBlockScalarHeader(rest) {
|
|
4130
|
+
return /^[>|][-+]?(\d+)?(?:\s+#.*)?$/.test(rest);
|
|
4131
|
+
}
|
|
4132
|
+
function extractFrontmatterDescription(text) {
|
|
4133
|
+
const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
4134
|
+
if (!m) return "";
|
|
4135
|
+
const lines = m[1].split(/\r?\n/);
|
|
4136
|
+
const idx = lines.findIndex((l) => /^description:[ \t]*/.test(l));
|
|
4137
|
+
if (idx < 0) return "";
|
|
4138
|
+
const rest = lines[idx].replace(/^description:[ \t]*/, "").trim();
|
|
4139
|
+
if (!rest) return "";
|
|
4140
|
+
if (!isYamlBlockScalarHeader(rest)) {
|
|
4141
|
+
return rest.replace(/^["']|["']$/g, "").trim();
|
|
4142
|
+
}
|
|
4143
|
+
const body = [];
|
|
4144
|
+
for (let j = idx + 1; j < lines.length; j++) {
|
|
4145
|
+
const line2 = lines[j];
|
|
4146
|
+
if (/^[a-zA-Z_][\w.-]*:[ \t]/.test(line2)) break;
|
|
4147
|
+
if (line2.trim() === "") continue;
|
|
4148
|
+
if (/^\s+/.test(line2)) body.push(line2.trimStart());
|
|
4149
|
+
else break;
|
|
4150
|
+
}
|
|
4151
|
+
return body.join(" ").replace(/\s+/g, " ").trim();
|
|
4152
|
+
}
|
|
4089
4153
|
function auditForbiddenTagsInText(text, label) {
|
|
4090
|
-
const body =
|
|
4154
|
+
const body = `${extractFrontmatterDescription(text)}
|
|
4155
|
+
${extractUseWhenSection(text)}`;
|
|
4091
4156
|
if (!body.trim()) return [];
|
|
4092
4157
|
const failures = [];
|
|
4093
4158
|
for (const word of PROSE_FORBIDDEN_TAGS) {
|
|
@@ -4173,10 +4238,10 @@ function auditShippedFiles(manifestDir, items) {
|
|
|
4173
4238
|
continue;
|
|
4174
4239
|
}
|
|
4175
4240
|
const text = fs18.readFileSync(skillMd, "utf8");
|
|
4176
|
-
const
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4241
|
+
const description = extractFrontmatterDescription(text);
|
|
4242
|
+
for (const key of REQUIRED_SKILL_FRONTMATTER) {
|
|
4243
|
+
if (key === "description" && !description) {
|
|
4244
|
+
failures.push(`${item.id}: SKILL.md missing non-empty frontmatter 'description:'`);
|
|
4180
4245
|
}
|
|
4181
4246
|
}
|
|
4182
4247
|
failures.push(
|
|
@@ -4245,13 +4310,9 @@ function auditMarkdownContent(manifestDir) {
|
|
|
4245
4310
|
walkMd(abs, (file) => {
|
|
4246
4311
|
const text = fs18.readFileSync(file, "utf8");
|
|
4247
4312
|
const rel = path26.relative(manifestDir, file);
|
|
4248
|
-
if (isVerbatimSuperpowersMarkdownPath(rel)) return;
|
|
4249
4313
|
const lines = text.split(/\r?\n/);
|
|
4250
4314
|
for (let i = 0; i < lines.length; i++) {
|
|
4251
4315
|
const line2 = lines[i] ?? "";
|
|
4252
|
-
if (PLACEHOLDER_PATTERN.test(line2)) {
|
|
4253
|
-
failures.push(`${rel}:${i + 1}: TODO or placeholder in shipped content`);
|
|
4254
|
-
}
|
|
4255
4316
|
if (RISKY_INSTALL_PATTERNS.some((re) => re.test(line2))) {
|
|
4256
4317
|
failures.push(`${rel}:${i + 1}: risky install pattern`);
|
|
4257
4318
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.
|
|
2
|
+
"version": "2.6.1",
|
|
3
3
|
"items": [
|
|
4
4
|
{
|
|
5
5
|
"id": "haus.nextjs-patterns",
|
|
@@ -1650,7 +1650,7 @@
|
|
|
1650
1650
|
},
|
|
1651
1651
|
{
|
|
1652
1652
|
"id": "haus.superpowers-brainstorming",
|
|
1653
|
-
"version": "1.0.
|
|
1653
|
+
"version": "1.0.2",
|
|
1654
1654
|
"source": "curated",
|
|
1655
1655
|
"type": "skill",
|
|
1656
1656
|
"path": "skills/superpowers/brainstorming",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"trading"
|
|
18
18
|
],
|
|
19
19
|
"bannedAgentPhrases": ["autonomous", "swarm", "delegate", "orchestrat", "marketplace"],
|
|
20
|
-
"
|
|
20
|
+
"requiredSkillFrontmatter": ["description"],
|
|
21
21
|
"requiredAgentSections": ["## Use when", "## Do not use when", "## Verification"],
|
|
22
22
|
"riskyInstallPatterns": [
|
|
23
23
|
{ "source": "\\bnpx\\s+-y\\b", "flags": "i" },
|
|
@@ -142,6 +142,5 @@
|
|
|
142
142
|
"baseline",
|
|
143
143
|
"project-instructions"
|
|
144
144
|
],
|
|
145
|
-
"patternTagSuffixes": ["-patterns"]
|
|
146
|
-
"skillSectionExemptSources": ["curated"]
|
|
145
|
+
"patternTagSuffixes": ["-patterns"]
|
|
147
146
|
}
|
package/package.json
CHANGED