@haus-tech/haus-workflow 0.15.0 → 0.16.0
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 +10 -0
- package/dist/cli.js +264 -132
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.16.0](https://github.com/WeAreHausTech/haus-workflow/compare/v0.15.0...v0.16.0) (2026-06-05)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- support catalog contexts and refresh-aware sync ([1ab1d6c](https://github.com/WeAreHausTech/haus-workflow/commit/1ab1d6c1ca91a412fc4e7269e1616050c4397101))
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
- Add forbidden-content, sources-report, and drift ([e7cbd07](https://github.com/WeAreHausTech/haus-workflow/commit/e7cbd076f7f8d8a21555c5e0cdba058b9788ee29))
|
|
12
|
+
|
|
3
13
|
## [0.15.0](https://github.com/WeAreHausTech/haus-workflow/compare/v0.14.0...v0.15.0) (2026-06-05)
|
|
4
14
|
|
|
5
15
|
### Features
|
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import path34 from "path";
|
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
|
|
8
8
|
// src/commands/apply.ts
|
|
9
|
-
import
|
|
9
|
+
import path13 from "path";
|
|
10
10
|
import checkbox from "@inquirer/checkbox";
|
|
11
11
|
import fs11 from "fs-extra";
|
|
12
12
|
|
|
@@ -32,7 +32,9 @@ var CATALOG_REF = process.env.HAUS_CATALOG_REF ?? "main";
|
|
|
32
32
|
var CATALOG_CACHE_SUBDIR = ".claude/haus/catalog-cache";
|
|
33
33
|
|
|
34
34
|
// src/catalog/remote-catalog.ts
|
|
35
|
-
|
|
35
|
+
function getCacheDir() {
|
|
36
|
+
return process.env["HAUS_CATALOG_CACHE_DIR_OVERRIDE"] ?? path.join(os.homedir(), CATALOG_CACHE_SUBDIR);
|
|
37
|
+
}
|
|
36
38
|
var REMOTE_BASE = process.env["HAUS_CATALOG_REMOTE_BASE"] ?? `${CATALOG_REPO_URL}/${CATALOG_REF}`;
|
|
37
39
|
var REMOTE_MANIFEST_URL = `${REMOTE_BASE}/manifest.json`;
|
|
38
40
|
async function fetchText(url) {
|
|
@@ -54,15 +56,29 @@ async function fetchRemoteManifest() {
|
|
|
54
56
|
return null;
|
|
55
57
|
}
|
|
56
58
|
}
|
|
59
|
+
async function writeTextIfChanged(dest, text) {
|
|
60
|
+
if (await fs.pathExists(dest)) {
|
|
61
|
+
const local = await fs.readFile(dest, "utf8");
|
|
62
|
+
if (local === text) return "unchanged";
|
|
63
|
+
await fs.writeFile(dest, text, "utf8");
|
|
64
|
+
return "updated";
|
|
65
|
+
}
|
|
66
|
+
await fs.ensureDir(path.dirname(dest));
|
|
67
|
+
await fs.writeFile(dest, text, "utf8");
|
|
68
|
+
return "created";
|
|
69
|
+
}
|
|
57
70
|
var WORKFLOW_TEMPLATE_REL = "templates/agentic-workflow-standard.md";
|
|
58
71
|
async function readWorkflowTemplate(opts = {}) {
|
|
59
|
-
const dest = path.join(
|
|
60
|
-
if (await fs.pathExists(dest)) return fs.readFile(dest, "utf8");
|
|
72
|
+
const dest = path.join(getCacheDir(), WORKFLOW_TEMPLATE_REL);
|
|
61
73
|
const text = await fetchText(`${REMOTE_BASE}/${WORKFLOW_TEMPLATE_REL}`);
|
|
62
|
-
if (text === null)
|
|
74
|
+
if (text === null) {
|
|
75
|
+
if (await fs.pathExists(dest)) return fs.readFile(dest, "utf8");
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
63
78
|
if (!opts.dryRun) {
|
|
64
|
-
await
|
|
65
|
-
|
|
79
|
+
await writeTextIfChanged(dest, text);
|
|
80
|
+
} else if (await fs.pathExists(dest)) {
|
|
81
|
+
return fs.readFile(dest, "utf8");
|
|
66
82
|
}
|
|
67
83
|
return text;
|
|
68
84
|
}
|
|
@@ -87,37 +103,37 @@ async function downloadSkillReferences(item, destDir) {
|
|
|
87
103
|
warn(`Skipping reference "${ref}" for ${item.id}: path traversal detected`);
|
|
88
104
|
continue;
|
|
89
105
|
}
|
|
90
|
-
if (await fs.pathExists(refDest)) continue;
|
|
91
106
|
const text = await fetchText(`${REMOTE_BASE}/${item.path}/${ref}`);
|
|
92
107
|
if (text === null) {
|
|
93
108
|
warn(`Failed to fetch reference "${ref}" for ${item.id}`);
|
|
94
109
|
continue;
|
|
95
110
|
}
|
|
96
|
-
await
|
|
97
|
-
await fs.writeFile(refDest, text, "utf8");
|
|
111
|
+
await writeTextIfChanged(refDest, text);
|
|
98
112
|
}
|
|
99
113
|
}
|
|
100
114
|
async function syncRemoteCatalog() {
|
|
101
115
|
const items = await fetchRemoteManifest();
|
|
102
116
|
if (!items) {
|
|
103
117
|
warn("Remote catalog fetch failed \u2014 using bundled catalog");
|
|
104
|
-
return { newItems: [], unchanged: 0, failed: [] };
|
|
118
|
+
return { newItems: [], refreshed: [], unchanged: 0, failed: [] };
|
|
105
119
|
}
|
|
120
|
+
const cacheDir = getCacheDir();
|
|
106
121
|
try {
|
|
107
|
-
await fs.ensureDir(
|
|
122
|
+
await fs.ensureDir(cacheDir);
|
|
108
123
|
await fs.writeFile(
|
|
109
|
-
path.join(
|
|
124
|
+
path.join(cacheDir, "manifest.json"),
|
|
110
125
|
`${JSON.stringify({ items }, null, 2)}
|
|
111
126
|
`,
|
|
112
127
|
"utf8"
|
|
113
128
|
);
|
|
114
129
|
} catch (err) {
|
|
115
130
|
warn(
|
|
116
|
-
`Catalog cache not writable (${
|
|
131
|
+
`Catalog cache not writable (${cacheDir}) \u2014 skipping cache sync: ${err instanceof Error ? err.message : String(err)}`
|
|
117
132
|
);
|
|
118
|
-
return { newItems: [], unchanged: 0, failed: [] };
|
|
133
|
+
return { newItems: [], refreshed: [], unchanged: 0, failed: [] };
|
|
119
134
|
}
|
|
120
135
|
const newItems = [];
|
|
136
|
+
const refreshed = [];
|
|
121
137
|
let unchanged = 0;
|
|
122
138
|
const failed = [];
|
|
123
139
|
for (const item of items) {
|
|
@@ -129,18 +145,13 @@ async function syncRemoteCatalog() {
|
|
|
129
145
|
continue;
|
|
130
146
|
}
|
|
131
147
|
if (item.type === "skill") {
|
|
132
|
-
const destDir = safeJoin(
|
|
148
|
+
const destDir = safeJoin(getCacheDir(), item.path);
|
|
133
149
|
if (!destDir) {
|
|
134
150
|
warn(`Skipping ${item.id}: path traversal detected`);
|
|
135
151
|
failed.push(item.id);
|
|
136
152
|
continue;
|
|
137
153
|
}
|
|
138
154
|
const dest = path.join(destDir, "SKILL.md");
|
|
139
|
-
if (await fs.pathExists(dest)) {
|
|
140
|
-
await downloadSkillReferences(item, destDir);
|
|
141
|
-
unchanged++;
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
155
|
const url = `${REMOTE_BASE}/${item.path}/SKILL.md`;
|
|
145
156
|
const text = await fetchText(url);
|
|
146
157
|
if (!text) {
|
|
@@ -149,25 +160,22 @@ async function syncRemoteCatalog() {
|
|
|
149
160
|
continue;
|
|
150
161
|
}
|
|
151
162
|
try {
|
|
152
|
-
await
|
|
153
|
-
await fs.writeFile(dest, text, "utf8");
|
|
163
|
+
const outcome = await writeTextIfChanged(dest, text);
|
|
154
164
|
await downloadSkillReferences(item, destDir);
|
|
155
|
-
newItems.push(item.id);
|
|
165
|
+
if (outcome === "created") newItems.push(item.id);
|
|
166
|
+
else if (outcome === "updated") refreshed.push(item.id);
|
|
167
|
+
else unchanged++;
|
|
156
168
|
} catch (err) {
|
|
157
169
|
warn(`Failed to cache ${item.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
158
170
|
failed.push(item.id);
|
|
159
171
|
}
|
|
160
172
|
} else {
|
|
161
|
-
const dest = safeJoin(
|
|
173
|
+
const dest = safeJoin(getCacheDir(), item.path);
|
|
162
174
|
if (!dest) {
|
|
163
175
|
warn(`Skipping ${item.id}: path traversal detected`);
|
|
164
176
|
failed.push(item.id);
|
|
165
177
|
continue;
|
|
166
178
|
}
|
|
167
|
-
if (await fs.pathExists(dest)) {
|
|
168
|
-
unchanged++;
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
179
|
const url = `${REMOTE_BASE}/${item.path}`;
|
|
172
180
|
const text = await fetchText(url);
|
|
173
181
|
if (!text) {
|
|
@@ -176,16 +184,17 @@ async function syncRemoteCatalog() {
|
|
|
176
184
|
continue;
|
|
177
185
|
}
|
|
178
186
|
try {
|
|
179
|
-
await
|
|
180
|
-
|
|
181
|
-
|
|
187
|
+
const outcome = await writeTextIfChanged(dest, text);
|
|
188
|
+
if (outcome === "created") newItems.push(item.id);
|
|
189
|
+
else if (outcome === "updated") refreshed.push(item.id);
|
|
190
|
+
else unchanged++;
|
|
182
191
|
} catch (err) {
|
|
183
192
|
warn(`Failed to cache ${item.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
184
193
|
failed.push(item.id);
|
|
185
194
|
}
|
|
186
195
|
}
|
|
187
196
|
}
|
|
188
|
-
return { newItems, unchanged, failed };
|
|
197
|
+
return { newItems, refreshed, unchanged, failed };
|
|
189
198
|
}
|
|
190
199
|
var CATALOG_TAGS_API_URL = "https://api.github.com/repos/WeAreHausTech/haus-workflow-catalog/tags";
|
|
191
200
|
async function fetchLatestCatalogTag() {
|
|
@@ -204,7 +213,7 @@ async function fetchLatestCatalogTag() {
|
|
|
204
213
|
}
|
|
205
214
|
async function getCacheManifestAge() {
|
|
206
215
|
try {
|
|
207
|
-
const stat = await fs.stat(path.join(
|
|
216
|
+
const stat = await fs.stat(path.join(getCacheDir(), "manifest.json"));
|
|
208
217
|
return Date.now() - stat.mtimeMs;
|
|
209
218
|
} catch {
|
|
210
219
|
return null;
|
|
@@ -599,21 +608,21 @@ var PROJECT_HOOK_FRAGMENTS = [
|
|
|
599
608
|
id: "haus.context-hook",
|
|
600
609
|
gate: "keep",
|
|
601
610
|
event: "UserPromptSubmit",
|
|
602
|
-
command: "haus context --from-hook
|
|
611
|
+
command: "haus context --from-hook"
|
|
603
612
|
},
|
|
604
613
|
{
|
|
605
614
|
id: "haus.guard-file",
|
|
606
615
|
gate: "keep",
|
|
607
616
|
event: "PreToolUse",
|
|
608
617
|
matcher: "Read|Edit|Write",
|
|
609
|
-
command: "haus guard file-access --from-hook
|
|
618
|
+
command: "haus guard file-access --from-hook"
|
|
610
619
|
},
|
|
611
620
|
{
|
|
612
621
|
id: "haus.guard-bash",
|
|
613
622
|
gate: "keep",
|
|
614
623
|
event: "PreToolUse",
|
|
615
624
|
matcher: "Bash",
|
|
616
|
-
command: "haus guard bash --from-hook
|
|
625
|
+
command: "haus guard bash --from-hook"
|
|
617
626
|
}
|
|
618
627
|
];
|
|
619
628
|
async function readProjectSettings(root) {
|
|
@@ -637,11 +646,54 @@ async function applyProjectSettingsMerge(root) {
|
|
|
637
646
|
}
|
|
638
647
|
|
|
639
648
|
// src/claude/write-claude-files.ts
|
|
640
|
-
import
|
|
649
|
+
import path12 from "path";
|
|
641
650
|
import fs10 from "fs-extra";
|
|
642
651
|
|
|
643
|
-
// src/
|
|
652
|
+
// src/catalog/load-catalog.ts
|
|
644
653
|
import path6 from "path";
|
|
654
|
+
async function loadCatalogContext(root) {
|
|
655
|
+
const envPath = process.env["HAUS_FIXTURE_CATALOG"];
|
|
656
|
+
if (envPath) {
|
|
657
|
+
const data2 = await readJson(envPath);
|
|
658
|
+
return {
|
|
659
|
+
items: data2?.items ?? [],
|
|
660
|
+
contentRoot: path6.dirname(envPath),
|
|
661
|
+
source: "fixture"
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
const cacheDir = getCacheDir();
|
|
665
|
+
const cacheManifestPath = path6.join(cacheDir, "manifest.json");
|
|
666
|
+
const cacheData = await readJson(cacheManifestPath);
|
|
667
|
+
if (cacheData?.items?.length) {
|
|
668
|
+
return { items: cacheData.items, contentRoot: cacheDir, source: "cache" };
|
|
669
|
+
}
|
|
670
|
+
const localManifest = path6.join(root, "library/catalog/manifest.json");
|
|
671
|
+
const localData = await readJson(localManifest);
|
|
672
|
+
if (localData?.items?.length) {
|
|
673
|
+
return {
|
|
674
|
+
items: localData.items,
|
|
675
|
+
contentRoot: path6.dirname(localManifest),
|
|
676
|
+
source: "local"
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
const packageManifest = path6.join(packageRoot(), "library/catalog/manifest.json");
|
|
680
|
+
const data = await readJson(packageManifest);
|
|
681
|
+
return {
|
|
682
|
+
items: data?.items ?? [],
|
|
683
|
+
contentRoot: path6.dirname(packageManifest),
|
|
684
|
+
source: "bundled"
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
async function loadCatalog(root) {
|
|
688
|
+
const ctx = await loadCatalogContext(root);
|
|
689
|
+
return ctx.items;
|
|
690
|
+
}
|
|
691
|
+
function catalogItemContentPath(contentRoot, item) {
|
|
692
|
+
return path6.join(contentRoot, item.path);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// src/update/hash-installed.ts
|
|
696
|
+
import path7 from "path";
|
|
645
697
|
import fg2 from "fast-glob";
|
|
646
698
|
import fs4 from "fs-extra";
|
|
647
699
|
var EMPTY_LOCK_PATHS_TOKEN = "haus-lock:empty-paths";
|
|
@@ -652,7 +704,7 @@ async function hashInstalledPaths(root, relPaths) {
|
|
|
652
704
|
const normalized = [...new Set(relPaths.map((p) => p.replace(/\\/g, "/")))].sort();
|
|
653
705
|
const fileDigests = [];
|
|
654
706
|
for (const rel of normalized) {
|
|
655
|
-
const abs =
|
|
707
|
+
const abs = path7.join(root, rel);
|
|
656
708
|
if (!await fs4.pathExists(abs)) continue;
|
|
657
709
|
const stat = await fs4.stat(abs);
|
|
658
710
|
if (stat.isFile()) {
|
|
@@ -663,8 +715,8 @@ async function hashInstalledPaths(root, relPaths) {
|
|
|
663
715
|
if (!stat.isDirectory()) continue;
|
|
664
716
|
const inner = await fg2("**/*", { cwd: abs, onlyFiles: true, dot: true });
|
|
665
717
|
for (const sub of inner.sort()) {
|
|
666
|
-
const relFile =
|
|
667
|
-
const absFile =
|
|
718
|
+
const relFile = path7.join(rel, sub).replace(/\\/g, "/");
|
|
719
|
+
const absFile = path7.join(abs, sub);
|
|
668
720
|
const body = await fs4.readFile(absFile, "utf8");
|
|
669
721
|
fileDigests.push({ rel: relFile, digest: hashText(body) });
|
|
670
722
|
}
|
|
@@ -699,7 +751,7 @@ function summarizeDiff(diffText) {
|
|
|
699
751
|
}
|
|
700
752
|
|
|
701
753
|
// src/claude/load-hooks-config.ts
|
|
702
|
-
import
|
|
754
|
+
import path8 from "path";
|
|
703
755
|
var CONFIG_PATH = ".haus-workflow/config.json";
|
|
704
756
|
var DEFAULT_HOOKS_CONFIG = {
|
|
705
757
|
hooks: {
|
|
@@ -707,7 +759,7 @@ var DEFAULT_HOOKS_CONFIG = {
|
|
|
707
759
|
}
|
|
708
760
|
};
|
|
709
761
|
async function isHookEnabled(root, key) {
|
|
710
|
-
const cfg = await readJson(
|
|
762
|
+
const cfg = await readJson(path8.join(root, CONFIG_PATH));
|
|
711
763
|
return cfg?.hooks?.[key]?.enabled === true;
|
|
712
764
|
}
|
|
713
765
|
|
|
@@ -719,25 +771,25 @@ var CANONICAL_HOOKS = {
|
|
|
719
771
|
hooks: {
|
|
720
772
|
UserPromptSubmit: [
|
|
721
773
|
{
|
|
722
|
-
hooks: [{ type: "command", command: "haus context --from-hook
|
|
774
|
+
hooks: [{ type: "command", command: "haus context --from-hook" }]
|
|
723
775
|
}
|
|
724
776
|
],
|
|
725
777
|
PreToolUse: [
|
|
726
778
|
{
|
|
727
779
|
matcher: "Read|Edit|Write",
|
|
728
|
-
hooks: [{ type: "command", command: "haus guard file-access --from-hook
|
|
780
|
+
hooks: [{ type: "command", command: "haus guard file-access --from-hook" }]
|
|
729
781
|
},
|
|
730
782
|
{
|
|
731
783
|
matcher: "Bash",
|
|
732
|
-
hooks: [{ type: "command", command: "haus guard bash --from-hook
|
|
784
|
+
hooks: [{ type: "command", command: "haus guard bash --from-hook" }]
|
|
733
785
|
}
|
|
734
786
|
]
|
|
735
787
|
}
|
|
736
788
|
};
|
|
737
789
|
var STABLE_HOOK_IDS = {
|
|
738
|
-
"haus context --from-hook
|
|
739
|
-
"haus guard file-access --from-hook
|
|
740
|
-
"haus guard bash --from-hook
|
|
790
|
+
"haus context --from-hook": "haus.context-hook",
|
|
791
|
+
"haus guard file-access --from-hook": "haus.guard-file",
|
|
792
|
+
"haus guard bash --from-hook": "haus.guard-bash"
|
|
741
793
|
};
|
|
742
794
|
async function loadClaudeHooksSettings() {
|
|
743
795
|
return { ...CANONICAL_HOOKS, permissions: { deny: buildDenyRules() } };
|
|
@@ -833,7 +885,7 @@ async function verifyProjectSettingsHooksContract(root) {
|
|
|
833
885
|
}
|
|
834
886
|
|
|
835
887
|
// src/claude/write-root-claude-md.ts
|
|
836
|
-
import
|
|
888
|
+
import path9 from "path";
|
|
837
889
|
import fs6 from "fs-extra";
|
|
838
890
|
var BLOCK_BEGIN = "<!-- HAUS:BEGIN haus-imports v=1 -->";
|
|
839
891
|
var BLOCK_END = "<!-- HAUS:END haus-imports -->";
|
|
@@ -873,7 +925,7 @@ ${block}
|
|
|
873
925
|
`;
|
|
874
926
|
}
|
|
875
927
|
async function writeRootClaudeMd(root, dryRun) {
|
|
876
|
-
const filePath =
|
|
928
|
+
const filePath = path9.join(root, "CLAUDE.md");
|
|
877
929
|
const block = buildImportBlock();
|
|
878
930
|
const prev = await fs6.pathExists(filePath) ? await fs6.readFile(filePath, "utf8") : "";
|
|
879
931
|
const next = injectHausBlock(prev, block);
|
|
@@ -898,11 +950,11 @@ async function writeRootClaudeMd(root, dryRun) {
|
|
|
898
950
|
}
|
|
899
951
|
|
|
900
952
|
// src/claude/write-workflow-config.ts
|
|
901
|
-
import
|
|
953
|
+
import path11 from "path";
|
|
902
954
|
import fs8 from "fs-extra";
|
|
903
955
|
|
|
904
956
|
// src/claude/derive-workflow-config.ts
|
|
905
|
-
import
|
|
957
|
+
import path10 from "path";
|
|
906
958
|
import fs7 from "fs-extra";
|
|
907
959
|
function binCmd(pm, bin, args) {
|
|
908
960
|
const tail = args ? ` ${args}` : "";
|
|
@@ -912,7 +964,7 @@ function binCmd(pm, bin, args) {
|
|
|
912
964
|
}
|
|
913
965
|
async function deriveWorkflowConfig(root, ctx) {
|
|
914
966
|
const pm = ctx.packageManager === "unknown" ? "npm" : ctx.packageManager;
|
|
915
|
-
const pkg = await readJson(
|
|
967
|
+
const pkg = await readJson(path10.join(root, "package.json"));
|
|
916
968
|
const scripts = pkg?.scripts ?? {};
|
|
917
969
|
const deps = new Set(ctx.dependencies);
|
|
918
970
|
const stacks = Object.values(ctx.detectedStacks ?? {}).flat();
|
|
@@ -922,7 +974,7 @@ async function deriveWorkflowConfig(root, ctx) {
|
|
|
922
974
|
return null;
|
|
923
975
|
};
|
|
924
976
|
const hasDep = (name) => deps.has(name);
|
|
925
|
-
const exists = (rel) => fs7.pathExistsSync(
|
|
977
|
+
const exists = (rel) => fs7.pathExistsSync(path10.join(root, rel));
|
|
926
978
|
const hasPlaywright = hasDep("@playwright/test") || stacks.includes("playwright");
|
|
927
979
|
const hasCypress = hasDep("cypress");
|
|
928
980
|
const preCommitTool = exists("lefthook.yml") || exists("lefthook.yaml") ? "lefthook" : exists(".husky") || hasDep("husky") || (scripts.prepare ?? "").includes("husky") ? "husky" : exists(".pre-commit-config.yaml") ? "pre-commit (Python framework)" : null;
|
|
@@ -999,7 +1051,7 @@ async function writeWorkflowConfig(root, dryRun, opts = {}) {
|
|
|
999
1051
|
const ctx = await readJson(hausPath(root, "context-map.json")) ?? {
|
|
1000
1052
|
...FALLBACK_CONTEXT,
|
|
1001
1053
|
root,
|
|
1002
|
-
repoName:
|
|
1054
|
+
repoName: path11.basename(root)
|
|
1003
1055
|
};
|
|
1004
1056
|
const values = await deriveWorkflowConfig(root, ctx);
|
|
1005
1057
|
if (exists) {
|
|
@@ -1108,7 +1160,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
|
|
|
1108
1160
|
estimatedTokenReductionPct: 0
|
|
1109
1161
|
};
|
|
1110
1162
|
const pkgRoot = packageRoot();
|
|
1111
|
-
const hausVersion2 = (await readJson(
|
|
1163
|
+
const hausVersion2 = (await readJson(path12.join(pkgRoot, "package.json")))?.version ?? "0.0.0";
|
|
1112
1164
|
const coreFiles = [
|
|
1113
1165
|
claudePath(root, "settings.json"),
|
|
1114
1166
|
claudePath(root, "rules", "haus.md"),
|
|
@@ -1167,15 +1219,8 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
|
|
|
1167
1219
|
"- Never read secrets.\n- Block dangerous shell commands.\n",
|
|
1168
1220
|
dryRun
|
|
1169
1221
|
);
|
|
1170
|
-
const
|
|
1171
|
-
const
|
|
1172
|
-
const manifestDir = path11.dirname(manifestPath2);
|
|
1173
|
-
const manifest = await readJson(manifestPath2) ?? { items: [] };
|
|
1174
|
-
const manifestById = new Map((manifest.items ?? []).map((item) => [item.id, item]));
|
|
1175
|
-
const cacheManifest = await readJson(
|
|
1176
|
-
path11.join(CACHE_DIR, "manifest.json")
|
|
1177
|
-
);
|
|
1178
|
-
const cacheManifestById = new Map((cacheManifest?.items ?? []).map((item) => [item.id, item]));
|
|
1222
|
+
const { items: manifestItems, contentRoot } = await loadCatalogContext(root);
|
|
1223
|
+
const manifestById = new Map(manifestItems.map((item) => [item.id, item]));
|
|
1179
1224
|
const installedPathsByItem = /* @__PURE__ */ new Map();
|
|
1180
1225
|
const installedIds = /* @__PURE__ */ new Set();
|
|
1181
1226
|
const catalogItems = selectedIds !== void 0 ? rec.recommended.filter((r) => selectedIds.includes(r.id)) : rec.recommended;
|
|
@@ -1194,11 +1239,9 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
|
|
|
1194
1239
|
continue;
|
|
1195
1240
|
}
|
|
1196
1241
|
}
|
|
1197
|
-
const
|
|
1198
|
-
const cachePath = cachedItem?.path ? path11.join(CACHE_DIR, cachedItem.path) : null;
|
|
1199
|
-
const sourcePath = cachePath && await fs10.pathExists(cachePath) ? cachePath : path11.join(manifestDir, manifestItem.path);
|
|
1242
|
+
const sourcePath = catalogItemContentPath(contentRoot, manifestItem);
|
|
1200
1243
|
const target = item.type === "agent" ? "agents" : item.type === "template" ? "templates" : "skills";
|
|
1201
|
-
const destination = claudePath(root, target,
|
|
1244
|
+
const destination = claudePath(root, target, path12.basename(sourcePath));
|
|
1202
1245
|
if (await fs10.pathExists(sourcePath)) {
|
|
1203
1246
|
if (dryRun) {
|
|
1204
1247
|
const exists = await fs10.pathExists(destination);
|
|
@@ -1206,12 +1249,12 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
|
|
|
1206
1249
|
`${displayPath(root, destination)}: ${exists ? "would overwrite" : "would create"} (${item.id})`
|
|
1207
1250
|
);
|
|
1208
1251
|
} else {
|
|
1209
|
-
await fs10.ensureDir(
|
|
1252
|
+
await fs10.ensureDir(path12.dirname(destination));
|
|
1210
1253
|
await fs10.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
|
|
1211
1254
|
}
|
|
1212
1255
|
files.push(destination);
|
|
1213
1256
|
const current = installedPathsByItem.get(item.id) ?? [];
|
|
1214
|
-
installedPathsByItem.set(item.id, [...current,
|
|
1257
|
+
installedPathsByItem.set(item.id, [...current, path12.relative(root, destination)]);
|
|
1215
1258
|
installedIds.add(item.id);
|
|
1216
1259
|
} else {
|
|
1217
1260
|
warn(
|
|
@@ -1289,7 +1332,7 @@ async function writeManagedJson(root, filePath, value, dryRun) {
|
|
|
1289
1332
|
|
|
1290
1333
|
// src/commands/apply.ts
|
|
1291
1334
|
async function cacheHasItems() {
|
|
1292
|
-
const data = await readJson(
|
|
1335
|
+
const data = await readJson(path13.join(getCacheDir(), "manifest.json"));
|
|
1293
1336
|
return Array.isArray(data?.items) && data.items.length > 0;
|
|
1294
1337
|
}
|
|
1295
1338
|
async function runApply(options) {
|
|
@@ -1333,17 +1376,9 @@ async function runApply(options) {
|
|
|
1333
1376
|
const rec = await readJson(hausPath(root, "recommendation.json"));
|
|
1334
1377
|
const catalogItemCount = selectedIds !== void 0 ? selectedIds.length : rec?.recommended.length ?? 0;
|
|
1335
1378
|
if (catalogItemCount > 0 && !await cacheHasItems()) {
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
);
|
|
1340
|
-
} else {
|
|
1341
|
-
error(
|
|
1342
|
-
"Catalog cache is empty \u2014 cannot install catalog items. Run `haus update` first, or pass --allow-empty-cache to apply core files only."
|
|
1343
|
-
);
|
|
1344
|
-
process.exitCode = 1;
|
|
1345
|
-
return;
|
|
1346
|
-
}
|
|
1379
|
+
warn(
|
|
1380
|
+
isDryRun ? "Catalog cache is empty \u2014 `haus apply --write` will skip catalog items. Run `haus update` first." : "Catalog cache is empty \u2014 catalog items will be skipped. Run `haus update` first, or pass --allow-empty-cache to silence this warning."
|
|
1381
|
+
);
|
|
1347
1382
|
}
|
|
1348
1383
|
}
|
|
1349
1384
|
const files = await writeClaudeFiles(root, isDryRun, selectedIds, {
|
|
@@ -1369,26 +1404,6 @@ async function refreshProjectApply(root) {
|
|
|
1369
1404
|
return writeClaudeFiles(root, false, void 0, { refillConfig: false });
|
|
1370
1405
|
}
|
|
1371
1406
|
|
|
1372
|
-
// src/catalog/load-catalog.ts
|
|
1373
|
-
import os4 from "os";
|
|
1374
|
-
import path13 from "path";
|
|
1375
|
-
var CACHE_MANIFEST = path13.join(os4.homedir(), CATALOG_CACHE_SUBDIR, "manifest.json");
|
|
1376
|
-
async function loadCatalog(root) {
|
|
1377
|
-
const envPath = process.env["HAUS_FIXTURE_CATALOG"];
|
|
1378
|
-
if (envPath) {
|
|
1379
|
-
const data2 = await readJson(envPath);
|
|
1380
|
-
return data2?.items ?? [];
|
|
1381
|
-
}
|
|
1382
|
-
const cacheData = await readJson(CACHE_MANIFEST);
|
|
1383
|
-
if (cacheData?.items?.length) return cacheData.items;
|
|
1384
|
-
const localManifest = path13.join(root, "library/catalog/manifest.json");
|
|
1385
|
-
const localData = await readJson(localManifest);
|
|
1386
|
-
if (localData?.items?.length) return localData.items;
|
|
1387
|
-
const packageManifest = path13.join(packageRoot(), "library/catalog/manifest.json");
|
|
1388
|
-
const data = await readJson(packageManifest);
|
|
1389
|
-
return data?.items ?? [];
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
1407
|
// library/catalog/validation-rules.json
|
|
1393
1408
|
var validation_rules_default = {
|
|
1394
1409
|
forbiddenTags: [
|
|
@@ -2342,6 +2357,38 @@ ${describeRepo(context)}
|
|
|
2342
2357
|
`;
|
|
2343
2358
|
}
|
|
2344
2359
|
|
|
2360
|
+
// src/scanner/write-sources-report.ts
|
|
2361
|
+
function buildSourcesReport(items) {
|
|
2362
|
+
const statusBySource = /* @__PURE__ */ new Map();
|
|
2363
|
+
for (const item of items) {
|
|
2364
|
+
const src = item.source?.trim();
|
|
2365
|
+
if (!src || src === "haus") continue;
|
|
2366
|
+
if (src === "curated") {
|
|
2367
|
+
if (item.reviewStatus === "approved" && item.riskLevel !== "blocked") {
|
|
2368
|
+
statusBySource.set("curated", "approved");
|
|
2369
|
+
} else if (!statusBySource.has("curated")) {
|
|
2370
|
+
statusBySource.set("curated", "candidate");
|
|
2371
|
+
}
|
|
2372
|
+
continue;
|
|
2373
|
+
}
|
|
2374
|
+
if (item.reviewStatus === "approved" && item.riskLevel !== "blocked") {
|
|
2375
|
+
statusBySource.set(src, "approved");
|
|
2376
|
+
} else if (!statusBySource.has(src)) {
|
|
2377
|
+
statusBySource.set(src, "candidate");
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
return {
|
|
2381
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2382
|
+
items: [...statusBySource.entries()].map(([id, status]) => ({ id, status })).sort((a, b) => a.id.localeCompare(b.id))
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
async function writeSourcesReport(root) {
|
|
2386
|
+
const items = await loadCatalog(root);
|
|
2387
|
+
const report = buildSourcesReport(items);
|
|
2388
|
+
await writeJson(hausPath(root, "sources-report.json"), report);
|
|
2389
|
+
return report;
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2345
2392
|
// src/scanner/scan-project.ts
|
|
2346
2393
|
var SAFE_FILES = [
|
|
2347
2394
|
"package.json",
|
|
@@ -2450,6 +2497,7 @@ async function scanProject(root, mode = "fast") {
|
|
|
2450
2497
|
await writeJson(hausPath(root, "dependency-map.json"), dependencyMap);
|
|
2451
2498
|
await writeJson(hausPath(root, "scan-hashes.json"), scanHashes);
|
|
2452
2499
|
await writeText(hausPath(root, "repo-summary.md"), repoSummary);
|
|
2500
|
+
await writeSourcesReport(root);
|
|
2453
2501
|
return { ...context, dependencyMap, scanHashes, repoSummary };
|
|
2454
2502
|
}
|
|
2455
2503
|
|
|
@@ -2461,6 +2509,18 @@ async function readContextOrScan(root) {
|
|
|
2461
2509
|
return scan;
|
|
2462
2510
|
}
|
|
2463
2511
|
|
|
2512
|
+
// src/security/secret-patterns.ts
|
|
2513
|
+
var SECRET_PATTERNS = [
|
|
2514
|
+
/api[_-]?key\s*[:=]\s*\S+/i,
|
|
2515
|
+
/token\s*[:=]\s*\S+/i,
|
|
2516
|
+
/password\s*[:=]\s*\S+/i
|
|
2517
|
+
];
|
|
2518
|
+
|
|
2519
|
+
// src/security/redact-sensitive.ts
|
|
2520
|
+
function redactSensitive(input2) {
|
|
2521
|
+
return SECRET_PATTERNS.reduce((acc, pattern) => acc.replace(pattern, "[REDACTED]"), input2);
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2464
2524
|
// src/commands/context.ts
|
|
2465
2525
|
async function runContext(options) {
|
|
2466
2526
|
const root = process.cwd();
|
|
@@ -2509,7 +2569,7 @@ async function runContext(options) {
|
|
|
2509
2569
|
}),
|
|
2510
2570
|
summary
|
|
2511
2571
|
];
|
|
2512
|
-
const text = lines.join("\n");
|
|
2572
|
+
const text = redactSensitive(lines.join("\n"));
|
|
2513
2573
|
log(options.fromHook ? text.slice(0, 3e3) : text);
|
|
2514
2574
|
}
|
|
2515
2575
|
|
|
@@ -2648,7 +2708,7 @@ async function runDoctor(options) {
|
|
|
2648
2708
|
ok("- .haus-workflow/WORKFLOW.md: OK (user-owned)");
|
|
2649
2709
|
} else {
|
|
2650
2710
|
const storedHashMatch = firstLine.match(/hash=(sha256-[a-f0-9]+)/);
|
|
2651
|
-
const cachePath = path19.join(
|
|
2711
|
+
const cachePath = path19.join(getCacheDir(), "templates/agentic-workflow-standard.md");
|
|
2652
2712
|
const bundledPath = path19.join(
|
|
2653
2713
|
packageRoot(),
|
|
2654
2714
|
"library",
|
|
@@ -2723,7 +2783,9 @@ async function runDoctor(options) {
|
|
|
2723
2783
|
`A newer haus (${npmStatus.latest}) is available`,
|
|
2724
2784
|
`npm install -g ${NPM_PACKAGE_NAME}`
|
|
2725
2785
|
);
|
|
2726
|
-
process.
|
|
2786
|
+
if (!process.env["HAUS_FIXTURE_CATALOG"]) {
|
|
2787
|
+
process.exitCode = 1;
|
|
2788
|
+
}
|
|
2727
2789
|
} else if (npmStatus.latest !== null) {
|
|
2728
2790
|
ok(`- CLI: ${currentVersion} (up to date)`);
|
|
2729
2791
|
} else {
|
|
@@ -2916,23 +2978,7 @@ async function readChangedFiles(root) {
|
|
|
2916
2978
|
}
|
|
2917
2979
|
|
|
2918
2980
|
// src/recommender/policies.ts
|
|
2919
|
-
var UNSUPPORTED =
|
|
2920
|
-
"python",
|
|
2921
|
-
"django",
|
|
2922
|
-
"go",
|
|
2923
|
-
"rust",
|
|
2924
|
-
"java",
|
|
2925
|
-
"spring",
|
|
2926
|
-
"kotlin",
|
|
2927
|
-
"swift",
|
|
2928
|
-
"android",
|
|
2929
|
-
"flutter",
|
|
2930
|
-
"dart",
|
|
2931
|
-
"c++",
|
|
2932
|
-
"perl",
|
|
2933
|
-
"defi",
|
|
2934
|
-
"trading"
|
|
2935
|
-
];
|
|
2981
|
+
var UNSUPPORTED = FORBIDDEN_TAGS;
|
|
2936
2982
|
function matchRequiresAny(clauses, ctx) {
|
|
2937
2983
|
for (const clause of clauses) {
|
|
2938
2984
|
if ("stack" in clause) {
|
|
@@ -3218,6 +3264,15 @@ async function runSetupCore(root, opts) {
|
|
|
3218
3264
|
return baseResult;
|
|
3219
3265
|
}
|
|
3220
3266
|
}
|
|
3267
|
+
if (!dryRun && !process.env["HAUS_FIXTURE_CATALOG"]) {
|
|
3268
|
+
log("Syncing remote catalog...");
|
|
3269
|
+
const sync = await syncRemoteCatalog();
|
|
3270
|
+
if (sync.newItems.length > 0) {
|
|
3271
|
+
log(`Catalog cache populated: ${sync.newItems.length} new item(s).`);
|
|
3272
|
+
} else if (sync.refreshed.length > 0) {
|
|
3273
|
+
log(`Catalog cache refreshed: ${sync.refreshed.length} updated item(s).`);
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3221
3276
|
const files = await writeClaudeFiles(root, dryRun ?? false);
|
|
3222
3277
|
log("Applied files:");
|
|
3223
3278
|
files.forEach((f) => log(`- ${displayPath(root, f)}`));
|
|
@@ -3570,10 +3625,14 @@ async function runRecommend(options) {
|
|
|
3570
3625
|
|
|
3571
3626
|
// src/commands/refresh.ts
|
|
3572
3627
|
async function runRefresh() {
|
|
3573
|
-
const
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3628
|
+
const root = process.cwd();
|
|
3629
|
+
const context = await scanProject(root, "fast");
|
|
3630
|
+
const recommendation = await recommend(root, context);
|
|
3631
|
+
await writeJson(hausPath(root, "recommendation.json"), recommendation);
|
|
3632
|
+
log("Haus refresh complete");
|
|
3633
|
+
log(`Roles: ${context.repoRoles.join(", ") || "unknown"}`);
|
|
3634
|
+
log(`Package manager: ${context.packageManager}`);
|
|
3635
|
+
log(`Recommended items: ${recommendation.recommended.length}`);
|
|
3577
3636
|
}
|
|
3578
3637
|
|
|
3579
3638
|
// src/commands/scan.ts
|
|
@@ -3827,7 +3886,17 @@ async function checkLock(root) {
|
|
|
3827
3886
|
(item) => !item.version || normalizeVersion(item.version) !== null
|
|
3828
3887
|
);
|
|
3829
3888
|
const catalogRef = lock[0]?.catalogRef ?? null;
|
|
3830
|
-
|
|
3889
|
+
const drift = [];
|
|
3890
|
+
for (const item of lock) {
|
|
3891
|
+
if (!item.hash) continue;
|
|
3892
|
+
const paths = Array.isArray(item.paths) ? item.paths.map(String) : [];
|
|
3893
|
+
const actual = await hashInstalledPaths(root, paths);
|
|
3894
|
+
if (item.hash !== actual) {
|
|
3895
|
+
drift.push({ id: item.id, expected: item.hash, actual });
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
const ok = lock.length > 0 && hasValidVersions && drift.length === 0;
|
|
3899
|
+
return { ok, count: lock.length, catalogRef, drift, driftCount: drift.length };
|
|
3831
3900
|
}
|
|
3832
3901
|
async function applyLock(root) {
|
|
3833
3902
|
const lockPath = hausPath(root, "haus.lock.json");
|
|
@@ -3902,6 +3971,7 @@ async function runUpdate(options) {
|
|
|
3902
3971
|
)
|
|
3903
3972
|
);
|
|
3904
3973
|
if (!status.ok) process.exitCode = 1;
|
|
3974
|
+
if (status.driftCount > 0) process.exitCode = 1;
|
|
3905
3975
|
return;
|
|
3906
3976
|
}
|
|
3907
3977
|
const pkgJson = await readJson(path25.join(packageRoot(), "package.json"));
|
|
@@ -3924,7 +3994,12 @@ async function runUpdate(options) {
|
|
|
3924
3994
|
if (sync.newItems.length > 0) {
|
|
3925
3995
|
log(`Catalog updated: ${sync.newItems.length} new item(s): ${sync.newItems.join(", ")}`);
|
|
3926
3996
|
log("Run `haus recommend && haus apply --write` to install new skills.");
|
|
3927
|
-
}
|
|
3997
|
+
}
|
|
3998
|
+
if (sync.refreshed.length > 0) {
|
|
3999
|
+
log(`Catalog refreshed: ${sync.refreshed.length} updated item(s): ${sync.refreshed.join(", ")}`);
|
|
4000
|
+
log("Run `haus apply --write` to install refreshed skill content.");
|
|
4001
|
+
}
|
|
4002
|
+
if (sync.newItems.length === 0 && sync.refreshed.length === 0 && sync.unchanged > 0) {
|
|
3928
4003
|
log(`Catalog up to date (${sync.unchanged} item(s) unchanged).`);
|
|
3929
4004
|
}
|
|
3930
4005
|
if (sync.failed.length > 0) {
|
|
@@ -3978,6 +4053,32 @@ async function detectGlobalInstallDrift() {
|
|
|
3978
4053
|
// src/commands/validate-catalog.ts
|
|
3979
4054
|
import fs18 from "fs";
|
|
3980
4055
|
import path26 from "path";
|
|
4056
|
+
|
|
4057
|
+
// src/catalog/forbidden-content.ts
|
|
4058
|
+
var PROSE_FORBIDDEN_TAGS = FORBIDDEN_TAGS.filter((t) => t.toLowerCase() !== "go");
|
|
4059
|
+
function escapeRegExp(value) {
|
|
4060
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4061
|
+
}
|
|
4062
|
+
function extractUseWhenSection(text) {
|
|
4063
|
+
const marker = "## Use when";
|
|
4064
|
+
const idx = text.toLowerCase().indexOf(marker.toLowerCase());
|
|
4065
|
+
if (idx < 0) return "";
|
|
4066
|
+
const tail = text.slice(idx + marker.length);
|
|
4067
|
+
const next = tail.search(/\n##\s+/);
|
|
4068
|
+
return next < 0 ? tail : tail.slice(0, next);
|
|
4069
|
+
}
|
|
4070
|
+
function auditForbiddenTagsInText(text, label) {
|
|
4071
|
+
const body = extractUseWhenSection(text);
|
|
4072
|
+
if (!body.trim()) return [];
|
|
4073
|
+
const failures = [];
|
|
4074
|
+
for (const word of PROSE_FORBIDDEN_TAGS) {
|
|
4075
|
+
const re = new RegExp(`\\b${escapeRegExp(word)}\\b`, "i");
|
|
4076
|
+
if (re.test(body)) failures.push(`${label}: forbidden stack/tag "${word}" in content`);
|
|
4077
|
+
}
|
|
4078
|
+
return failures;
|
|
4079
|
+
}
|
|
4080
|
+
|
|
4081
|
+
// src/commands/validate-catalog.ts
|
|
3981
4082
|
function auditForbiddenStacks(items) {
|
|
3982
4083
|
const failures = [];
|
|
3983
4084
|
for (const item of items) {
|
|
@@ -4056,6 +4157,9 @@ function auditShippedFiles(manifestDir, items) {
|
|
|
4056
4157
|
for (const section of REQUIRED_SKILL_SECTIONS) {
|
|
4057
4158
|
if (!text.includes(section)) failures.push(`${item.id}: SKILL.md missing ${section}`);
|
|
4058
4159
|
}
|
|
4160
|
+
failures.push(
|
|
4161
|
+
...auditForbiddenTagsInText(text, `${item.id}: ${path26.relative(manifestDir, skillMd)}`)
|
|
4162
|
+
);
|
|
4059
4163
|
} else if (item.type === "agent") {
|
|
4060
4164
|
if (!fs18.existsSync(absPath)) {
|
|
4061
4165
|
failures.push(`${item.id}: missing agent file ${item.path}`);
|
|
@@ -4071,17 +4175,42 @@ function auditShippedFiles(manifestDir, items) {
|
|
|
4071
4175
|
if (lower.includes(phrase))
|
|
4072
4176
|
failures.push(`${item.id}: agent file contains disallowed phrase "${phrase}"`);
|
|
4073
4177
|
}
|
|
4178
|
+
failures.push(
|
|
4179
|
+
...auditForbiddenTagsInText(text, `${item.id}: ${path26.relative(manifestDir, absPath)}`)
|
|
4180
|
+
);
|
|
4074
4181
|
} else if (item.type === "template") {
|
|
4075
4182
|
if (!fs18.existsSync(absPath)) {
|
|
4076
4183
|
failures.push(`${item.id}: missing template file ${item.path}`);
|
|
4184
|
+
continue;
|
|
4077
4185
|
}
|
|
4186
|
+
failures.push(...auditTemplateContent(manifestDir, absPath, item.id));
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
4189
|
+
return failures;
|
|
4190
|
+
}
|
|
4191
|
+
function auditTemplateContent(manifestDir, absPath, itemId) {
|
|
4192
|
+
const rel = path26.relative(manifestDir, absPath);
|
|
4193
|
+
const text = fs18.readFileSync(absPath, "utf8");
|
|
4194
|
+
const failures = [];
|
|
4195
|
+
const lines = text.split(/\r?\n/);
|
|
4196
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4197
|
+
const line2 = lines[i] ?? "";
|
|
4198
|
+
if (PLACEHOLDER_PATTERN.test(line2)) {
|
|
4199
|
+
failures.push(`${rel}:${i + 1}: TODO or placeholder in shipped content`);
|
|
4200
|
+
}
|
|
4201
|
+
if (RISKY_INSTALL_PATTERNS.some((re) => re.test(line2))) {
|
|
4202
|
+
failures.push(`${rel}:${i + 1}: risky install pattern`);
|
|
4203
|
+
}
|
|
4204
|
+
if (ANY_NPX_PATTERN.test(line2) && !ALLOWED_NPX_PATTERN.test(line2)) {
|
|
4205
|
+
failures.push(`${rel}:${i + 1}: disallowed npx (only npx tsx allowed)`);
|
|
4078
4206
|
}
|
|
4079
4207
|
}
|
|
4208
|
+
failures.push(...auditForbiddenTagsInText(text, `${itemId}: ${rel}`));
|
|
4080
4209
|
return failures;
|
|
4081
4210
|
}
|
|
4082
4211
|
function auditMarkdownContent(manifestDir) {
|
|
4083
4212
|
const failures = [];
|
|
4084
|
-
const dirs = ["skills", "agents"];
|
|
4213
|
+
const dirs = ["skills", "agents", "templates"];
|
|
4085
4214
|
for (const dir of dirs) {
|
|
4086
4215
|
const abs = path26.join(manifestDir, dir);
|
|
4087
4216
|
if (!fs18.existsSync(abs)) continue;
|
|
@@ -4101,6 +4230,9 @@ function auditMarkdownContent(manifestDir) {
|
|
|
4101
4230
|
failures.push(`${rel}:${i + 1}: disallowed npx (only npx tsx allowed)`);
|
|
4102
4231
|
}
|
|
4103
4232
|
}
|
|
4233
|
+
if (!rel.includes("/references/")) {
|
|
4234
|
+
failures.push(...auditForbiddenTagsInText(text, rel));
|
|
4235
|
+
}
|
|
4104
4236
|
});
|
|
4105
4237
|
}
|
|
4106
4238
|
return failures;
|