@haus-tech/haus-workflow 0.12.0 → 0.12.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 +7 -0
- package/dist/cli.js +103 -91
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.12.1](https://github.com/WeAreHausTech/haus-workflow/compare/v0.12.0...v0.12.1) (2026-06-03)
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
- **template:** address review — dry-run safety, empty-body, hermetic test ([6744f94](https://github.com/WeAreHausTech/haus-workflow/commit/6744f94104b03de51829962c25b7706d3d0124bf))
|
|
8
|
+
- **template:** fetch workflow standard from catalog when cache is empty ([0168f5e](https://github.com/WeAreHausTech/haus-workflow/commit/0168f5ed028f8d3bf6588297d5472511cf4813b6))
|
|
9
|
+
|
|
3
10
|
## [0.12.0](https://github.com/WeAreHausTech/haus-workflow/compare/v0.11.1...v0.12.0) (2026-06-03)
|
|
4
11
|
|
|
5
12
|
### Features
|
package/dist/cli.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { readFileSync as readFileSync3 } from "fs";
|
|
5
|
-
import
|
|
5
|
+
import path29 from "path";
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
|
|
8
8
|
// src/commands/apply.ts
|
|
9
|
-
import
|
|
9
|
+
import path11 from "path";
|
|
10
10
|
import checkbox from "@inquirer/checkbox";
|
|
11
11
|
|
|
12
12
|
// src/catalog/remote-catalog.ts
|
|
@@ -53,6 +53,18 @@ async function fetchRemoteManifest() {
|
|
|
53
53
|
return null;
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
+
var WORKFLOW_TEMPLATE_REL = "templates/agentic-workflow-standard.md";
|
|
57
|
+
async function readWorkflowTemplate(opts = {}) {
|
|
58
|
+
const dest = path.join(CACHE_DIR, WORKFLOW_TEMPLATE_REL);
|
|
59
|
+
if (await fs.pathExists(dest)) return fs.readFile(dest, "utf8");
|
|
60
|
+
const text = await fetchText(`${REMOTE_BASE}/${WORKFLOW_TEMPLATE_REL}`);
|
|
61
|
+
if (text === null) return null;
|
|
62
|
+
if (!opts.dryRun) {
|
|
63
|
+
await fs.ensureDir(path.dirname(dest));
|
|
64
|
+
await fs.writeFile(dest, text, "utf8");
|
|
65
|
+
}
|
|
66
|
+
return text;
|
|
67
|
+
}
|
|
56
68
|
function isSafeCatalogPath(itemPath) {
|
|
57
69
|
if (!itemPath || path.isAbsolute(itemPath) || itemPath.includes("\\")) return false;
|
|
58
70
|
const normalized = path.normalize(itemPath);
|
|
@@ -80,7 +92,8 @@ async function syncRemoteCatalog() {
|
|
|
80
92
|
let unchanged = 0;
|
|
81
93
|
const failed = [];
|
|
82
94
|
for (const item of items) {
|
|
83
|
-
if (item.type !== "skill" && item.type !== "agent" || !item.path)
|
|
95
|
+
if (item.type !== "skill" && item.type !== "agent" && item.type !== "template" || !item.path)
|
|
96
|
+
continue;
|
|
84
97
|
if (!isSafeCatalogPath(item.path)) {
|
|
85
98
|
warn(`Skipping ${item.id}: invalid path "${item.path}"`);
|
|
86
99
|
failed.push(item.id);
|
|
@@ -158,7 +171,7 @@ async function getCacheManifestAge() {
|
|
|
158
171
|
}
|
|
159
172
|
|
|
160
173
|
// src/claude/write-claude-files.ts
|
|
161
|
-
import
|
|
174
|
+
import path10 from "path";
|
|
162
175
|
import fs10 from "fs-extra";
|
|
163
176
|
|
|
164
177
|
// src/update/hash-installed.ts
|
|
@@ -403,8 +416,8 @@ function buildDenyRules() {
|
|
|
403
416
|
for (const command of DANGEROUS_COMMANDS) {
|
|
404
417
|
rules.push(`Bash(${command}:*)`);
|
|
405
418
|
}
|
|
406
|
-
for (const
|
|
407
|
-
const pattern = SENSITIVE_DIRS.has(
|
|
419
|
+
for (const path30 of SENSITIVE_PATHS) {
|
|
420
|
+
const pattern = SENSITIVE_DIRS.has(path30) ? `${path30}/**` : path30;
|
|
408
421
|
for (const tool of FILE_TOOLS) {
|
|
409
422
|
rules.push(`${tool}(${pattern})`);
|
|
410
423
|
}
|
|
@@ -802,7 +815,6 @@ async function writeWorkflowConfig(root, dryRun, opts = {}) {
|
|
|
802
815
|
}
|
|
803
816
|
|
|
804
817
|
// src/claude/write-workflow.ts
|
|
805
|
-
import path10 from "path";
|
|
806
818
|
import fs9 from "fs-extra";
|
|
807
819
|
|
|
808
820
|
// src/claude/managed-template.ts
|
|
@@ -819,17 +831,17 @@ function parseHausManagedHeader(line2) {
|
|
|
819
831
|
// src/claude/write-workflow.ts
|
|
820
832
|
var STABLE_ID2 = "template.workflow";
|
|
821
833
|
var SCHEMA_VERSION2 = "1";
|
|
822
|
-
var CATALOG_CACHE_TEMPLATE = path10.join(CACHE_DIR, "templates/agentic-workflow-standard.md");
|
|
823
834
|
function makeWorkflowHeader(pkgVersion, contentHash) {
|
|
824
835
|
return `<!-- HAUS-MANAGED id=${STABLE_ID2} v=${SCHEMA_VERSION2} source=@haus-tech/haus-workflow@${pkgVersion} hash=${contentHash} -->`;
|
|
825
836
|
}
|
|
826
837
|
async function writeWorkflow(root, pkgVersion, dryRun) {
|
|
827
|
-
|
|
828
|
-
|
|
838
|
+
const templateContent = await readWorkflowTemplate({ dryRun });
|
|
839
|
+
if (templateContent === null) {
|
|
840
|
+
warn(
|
|
841
|
+
`Workflow template could not be fetched from the catalog \u2014 check your network, then re-run \`haus apply --write\` (or \`haus update\`)`
|
|
842
|
+
);
|
|
829
843
|
return null;
|
|
830
844
|
}
|
|
831
|
-
const templatePath = CATALOG_CACHE_TEMPLATE;
|
|
832
|
-
const templateContent = await fs9.readFile(templatePath, "utf8");
|
|
833
845
|
const contentHash = hashText(normaliseLF(templateContent));
|
|
834
846
|
const header = makeWorkflowHeader(pkgVersion, contentHash);
|
|
835
847
|
const next = `${header}
|
|
@@ -886,7 +898,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
|
|
|
886
898
|
estimatedTokenReductionPct: 0
|
|
887
899
|
};
|
|
888
900
|
const pkgRoot = packageRoot();
|
|
889
|
-
const hausVersion = (await readJson(
|
|
901
|
+
const hausVersion = (await readJson(path10.join(pkgRoot, "package.json")))?.version ?? "0.0.0";
|
|
890
902
|
const coreFiles = [
|
|
891
903
|
claudePath(root, "settings.json"),
|
|
892
904
|
claudePath(root, "rules", "haus.md"),
|
|
@@ -944,12 +956,12 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
|
|
|
944
956
|
dryRun
|
|
945
957
|
);
|
|
946
958
|
const fixtureManifestPath = process.env["HAUS_FIXTURE_CATALOG"];
|
|
947
|
-
const manifestPath = fixtureManifestPath ??
|
|
948
|
-
const manifestDir =
|
|
959
|
+
const manifestPath = fixtureManifestPath ?? path10.join(pkgRoot, "library", "catalog", "manifest.json");
|
|
960
|
+
const manifestDir = path10.dirname(manifestPath);
|
|
949
961
|
const manifest = await readJson(manifestPath) ?? { items: [] };
|
|
950
962
|
const manifestById = new Map((manifest.items ?? []).map((item) => [item.id, item]));
|
|
951
963
|
const cacheManifest = await readJson(
|
|
952
|
-
|
|
964
|
+
path10.join(CACHE_DIR, "manifest.json")
|
|
953
965
|
);
|
|
954
966
|
const cacheManifestById = new Map((cacheManifest?.items ?? []).map((item) => [item.id, item]));
|
|
955
967
|
const installedPathsByItem = /* @__PURE__ */ new Map();
|
|
@@ -971,10 +983,10 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
|
|
|
971
983
|
}
|
|
972
984
|
}
|
|
973
985
|
const cachedItem = cacheManifestById.get(item.id);
|
|
974
|
-
const cachePath = cachedItem?.path ?
|
|
975
|
-
const sourcePath = cachePath && await fs10.pathExists(cachePath) ? cachePath :
|
|
986
|
+
const cachePath = cachedItem?.path ? path10.join(CACHE_DIR, cachedItem.path) : null;
|
|
987
|
+
const sourcePath = cachePath && await fs10.pathExists(cachePath) ? cachePath : path10.join(manifestDir, manifestItem.path);
|
|
976
988
|
const target = item.type === "agent" ? "agents" : item.type === "template" ? "templates" : "skills";
|
|
977
|
-
const destination = claudePath(root, target,
|
|
989
|
+
const destination = claudePath(root, target, path10.basename(sourcePath));
|
|
978
990
|
if (await fs10.pathExists(sourcePath)) {
|
|
979
991
|
if (dryRun) {
|
|
980
992
|
const exists = await fs10.pathExists(destination);
|
|
@@ -982,12 +994,12 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
|
|
|
982
994
|
`${displayPath(root, destination)}: ${exists ? "would overwrite" : "would create"} (${item.id})`
|
|
983
995
|
);
|
|
984
996
|
} else {
|
|
985
|
-
await fs10.ensureDir(
|
|
997
|
+
await fs10.ensureDir(path10.dirname(destination));
|
|
986
998
|
await fs10.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
|
|
987
999
|
}
|
|
988
1000
|
files.push(destination);
|
|
989
1001
|
const current = installedPathsByItem.get(item.id) ?? [];
|
|
990
|
-
installedPathsByItem.set(item.id, [...current,
|
|
1002
|
+
installedPathsByItem.set(item.id, [...current, path10.relative(root, destination)]);
|
|
991
1003
|
installedIds.add(item.id);
|
|
992
1004
|
} else {
|
|
993
1005
|
warn(
|
|
@@ -1065,7 +1077,7 @@ async function writeManagedJson(root, filePath, value, dryRun) {
|
|
|
1065
1077
|
|
|
1066
1078
|
// src/commands/apply.ts
|
|
1067
1079
|
async function cacheHasItems() {
|
|
1068
|
-
const data = await readJson(
|
|
1080
|
+
const data = await readJson(path11.join(CACHE_DIR, "manifest.json"));
|
|
1069
1081
|
return Array.isArray(data?.items) && data.items.length > 0;
|
|
1070
1082
|
}
|
|
1071
1083
|
async function runApply(options) {
|
|
@@ -1135,8 +1147,8 @@ async function runApply(options) {
|
|
|
1135
1147
|
|
|
1136
1148
|
// src/catalog/load-catalog.ts
|
|
1137
1149
|
import os3 from "os";
|
|
1138
|
-
import
|
|
1139
|
-
var CACHE_MANIFEST =
|
|
1150
|
+
import path12 from "path";
|
|
1151
|
+
var CACHE_MANIFEST = path12.join(os3.homedir(), CATALOG_CACHE_SUBDIR, "manifest.json");
|
|
1140
1152
|
async function loadCatalog(root) {
|
|
1141
1153
|
const envPath = process.env["HAUS_FIXTURE_CATALOG"];
|
|
1142
1154
|
if (envPath) {
|
|
@@ -1145,10 +1157,10 @@ async function loadCatalog(root) {
|
|
|
1145
1157
|
}
|
|
1146
1158
|
const cacheData = await readJson(CACHE_MANIFEST);
|
|
1147
1159
|
if (cacheData?.items?.length) return cacheData.items;
|
|
1148
|
-
const localManifest =
|
|
1160
|
+
const localManifest = path12.join(root, "library/catalog/manifest.json");
|
|
1149
1161
|
const localData = await readJson(localManifest);
|
|
1150
1162
|
if (localData?.items?.length) return localData.items;
|
|
1151
|
-
const packageManifest =
|
|
1163
|
+
const packageManifest = path12.join(packageRoot(), "library/catalog/manifest.json");
|
|
1152
1164
|
const data = await readJson(packageManifest);
|
|
1153
1165
|
return data?.items ?? [];
|
|
1154
1166
|
}
|
|
@@ -1188,7 +1200,7 @@ async function runCatalogAudit() {
|
|
|
1188
1200
|
}
|
|
1189
1201
|
|
|
1190
1202
|
// src/commands/config.ts
|
|
1191
|
-
import
|
|
1203
|
+
import path13 from "path";
|
|
1192
1204
|
var CONFIG_PATH2 = ".haus-workflow/config.json";
|
|
1193
1205
|
var HOOK_ALIASES = {
|
|
1194
1206
|
"hook.context": "context"
|
|
@@ -1201,7 +1213,7 @@ async function runConfig(key, action) {
|
|
|
1201
1213
|
);
|
|
1202
1214
|
}
|
|
1203
1215
|
const root = process.cwd();
|
|
1204
|
-
const configPath =
|
|
1216
|
+
const configPath = path13.join(root, CONFIG_PATH2);
|
|
1205
1217
|
const existing = await readJson(configPath);
|
|
1206
1218
|
const cfg = existing ?? structuredClone(DEFAULT_HOOKS_CONFIG);
|
|
1207
1219
|
cfg.hooks ??= {};
|
|
@@ -1588,7 +1600,7 @@ function selectRules(recommended, task, taskIntents) {
|
|
|
1588
1600
|
|
|
1589
1601
|
// src/scanner/scan-project.ts
|
|
1590
1602
|
import { readFile as readFile2 } from "fs/promises";
|
|
1591
|
-
import
|
|
1603
|
+
import path17 from "path";
|
|
1592
1604
|
|
|
1593
1605
|
// src/utils/audit-checks.ts
|
|
1594
1606
|
function isRecord(v) {
|
|
@@ -1615,7 +1627,7 @@ function compareVersions(a, b) {
|
|
|
1615
1627
|
}
|
|
1616
1628
|
|
|
1617
1629
|
// src/scanner/detect-package-manager.ts
|
|
1618
|
-
import
|
|
1630
|
+
import path14 from "path";
|
|
1619
1631
|
import fs11 from "fs-extra";
|
|
1620
1632
|
function detectPackageManager(root, packageManagerField) {
|
|
1621
1633
|
const field = String(packageManagerField ?? "").trim();
|
|
@@ -1634,9 +1646,9 @@ function detectPackageManager(root, packageManagerField) {
|
|
|
1634
1646
|
if (satisfiesVersion(version, ">=9")) return "npm";
|
|
1635
1647
|
return "unknown";
|
|
1636
1648
|
}
|
|
1637
|
-
if (fs11.existsSync(
|
|
1638
|
-
if (fs11.existsSync(
|
|
1639
|
-
if (fs11.existsSync(
|
|
1649
|
+
if (fs11.existsSync(path14.join(root, "yarn.lock"))) return "yarn";
|
|
1650
|
+
if (fs11.existsSync(path14.join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
1651
|
+
if (fs11.existsSync(path14.join(root, "package-lock.json"))) return "npm";
|
|
1640
1652
|
return "unknown";
|
|
1641
1653
|
}
|
|
1642
1654
|
|
|
@@ -1809,7 +1821,7 @@ function runDetection(ctx, rules = STACK_RULES) {
|
|
|
1809
1821
|
}
|
|
1810
1822
|
|
|
1811
1823
|
// src/scanner/detection.ts
|
|
1812
|
-
import
|
|
1824
|
+
import path15 from "path";
|
|
1813
1825
|
var UNSUPPORTED_MARKERS = {
|
|
1814
1826
|
"requirements.txt": "python",
|
|
1815
1827
|
"pyproject.toml": "python",
|
|
@@ -1863,14 +1875,14 @@ function finalizeRoles(registryRoles, deps, files) {
|
|
|
1863
1875
|
function collectUnsupportedSignals(files) {
|
|
1864
1876
|
return [
|
|
1865
1877
|
...new Set(
|
|
1866
|
-
files.map((f) => UNSUPPORTED_MARKERS[
|
|
1878
|
+
files.map((f) => UNSUPPORTED_MARKERS[path15.basename(f)]).filter((s) => Boolean(s))
|
|
1867
1879
|
)
|
|
1868
1880
|
].sort();
|
|
1869
1881
|
}
|
|
1870
1882
|
|
|
1871
1883
|
// src/scanner/render.ts
|
|
1872
1884
|
import { readFile } from "fs/promises";
|
|
1873
|
-
import
|
|
1885
|
+
import path16 from "path";
|
|
1874
1886
|
|
|
1875
1887
|
// src/scanner/role-labels.ts
|
|
1876
1888
|
var ROLE_LABELS = {
|
|
@@ -1936,7 +1948,7 @@ async function buildContentBlob(root, files) {
|
|
|
1936
1948
|
const batch = await Promise.all(
|
|
1937
1949
|
slice.slice(i, i + CHUNK).map(async (rel) => {
|
|
1938
1950
|
try {
|
|
1939
|
-
return await readFile(
|
|
1951
|
+
return await readFile(path16.join(root, rel), "utf8");
|
|
1940
1952
|
} catch {
|
|
1941
1953
|
return "";
|
|
1942
1954
|
}
|
|
@@ -2010,8 +2022,8 @@ var SAFE_FILES = [
|
|
|
2010
2022
|
"Gemfile"
|
|
2011
2023
|
];
|
|
2012
2024
|
async function scanProject(root, mode = "fast") {
|
|
2013
|
-
const pkg = await readJson(
|
|
2014
|
-
const composer = await readJson(
|
|
2025
|
+
const pkg = await readJson(path17.join(root, "package.json"));
|
|
2026
|
+
const composer = await readJson(path17.join(root, "composer.json"));
|
|
2015
2027
|
const files = await listFiles(root, SAFE_FILES);
|
|
2016
2028
|
const safeFiles = files.filter((f) => !blocked(f));
|
|
2017
2029
|
const deps = dependencySet(pkg, composer);
|
|
@@ -2045,7 +2057,7 @@ async function scanProject(root, mode = "fast") {
|
|
|
2045
2057
|
mode,
|
|
2046
2058
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2047
2059
|
root,
|
|
2048
|
-
repoName: String(pkg?.name ??
|
|
2060
|
+
repoName: String(pkg?.name ?? path17.basename(root)),
|
|
2049
2061
|
packageManager,
|
|
2050
2062
|
repoRoles: roles,
|
|
2051
2063
|
confidence: computeConfidence(roles, stacks),
|
|
@@ -2064,7 +2076,7 @@ async function scanProject(root, mode = "fast") {
|
|
|
2064
2076
|
const scanHashes = Object.fromEntries(
|
|
2065
2077
|
await Promise.all(
|
|
2066
2078
|
safeFiles.map(
|
|
2067
|
-
async (f) => [f, hashText(await readFile2(
|
|
2079
|
+
async (f) => [f, hashText(await readFile2(path17.join(root, f), "utf8"))]
|
|
2068
2080
|
)
|
|
2069
2081
|
)
|
|
2070
2082
|
);
|
|
@@ -2148,7 +2160,7 @@ async function runContext(options) {
|
|
|
2148
2160
|
}
|
|
2149
2161
|
|
|
2150
2162
|
// src/commands/doctor.ts
|
|
2151
|
-
import
|
|
2163
|
+
import path18 from "path";
|
|
2152
2164
|
import fs12 from "fs-extra";
|
|
2153
2165
|
|
|
2154
2166
|
// src/update/npm-version.ts
|
|
@@ -2229,7 +2241,7 @@ async function runDoctor(options) {
|
|
|
2229
2241
|
const enabled = await isHookEnabled(root, key);
|
|
2230
2242
|
ok(`- HOOK ${key}: ${enabled ? "enabled" : "disabled (default)"}`);
|
|
2231
2243
|
}
|
|
2232
|
-
const rootClaudeMdPath =
|
|
2244
|
+
const rootClaudeMdPath = path18.join(root, "CLAUDE.md");
|
|
2233
2245
|
const rootClaudeMdContent = await readText(rootClaudeMdPath);
|
|
2234
2246
|
if (!rootClaudeMdContent) {
|
|
2235
2247
|
flag(
|
|
@@ -2282,8 +2294,8 @@ async function runDoctor(options) {
|
|
|
2282
2294
|
ok("- .haus-workflow/WORKFLOW.md: OK (user-owned)");
|
|
2283
2295
|
} else {
|
|
2284
2296
|
const storedHashMatch = firstLine.match(/hash=(sha256-[a-f0-9]+)/);
|
|
2285
|
-
const cachePath =
|
|
2286
|
-
const bundledPath =
|
|
2297
|
+
const cachePath = path18.join(CACHE_DIR, "templates/agentic-workflow-standard.md");
|
|
2298
|
+
const bundledPath = path18.join(
|
|
2287
2299
|
packageRoot(),
|
|
2288
2300
|
"library",
|
|
2289
2301
|
"global",
|
|
@@ -2365,7 +2377,7 @@ async function runDoctor(options) {
|
|
|
2365
2377
|
ok(`- CATALOG CACHE: OK (${cacheAgeDays}d old)`);
|
|
2366
2378
|
}
|
|
2367
2379
|
}
|
|
2368
|
-
const pkgJson = await readJson(
|
|
2380
|
+
const pkgJson = await readJson(path18.join(packageRoot(), "package.json"));
|
|
2369
2381
|
const currentVersion = pkgJson?.version ?? "0.0.0";
|
|
2370
2382
|
const npmStatus = await fetchNpmVersionStatus(currentVersion);
|
|
2371
2383
|
if (npmStatus.updateAvailable && npmStatus.latest !== null) {
|
|
@@ -2507,7 +2519,7 @@ async function runGuard(kind, _options) {
|
|
|
2507
2519
|
}
|
|
2508
2520
|
|
|
2509
2521
|
// src/commands/init.ts
|
|
2510
|
-
import
|
|
2522
|
+
import path19 from "path";
|
|
2511
2523
|
import fs13 from "fs-extra";
|
|
2512
2524
|
|
|
2513
2525
|
// src/recommender/ecosystem.ts
|
|
@@ -3074,7 +3086,7 @@ async function runSetupProject(options) {
|
|
|
3074
3086
|
// src/commands/init.ts
|
|
3075
3087
|
async function runInit(options) {
|
|
3076
3088
|
const root = process.cwd();
|
|
3077
|
-
const hausDir =
|
|
3089
|
+
const hausDir = path19.join(root, ".haus-workflow");
|
|
3078
3090
|
const alreadyInit = await fs13.pathExists(hausDir);
|
|
3079
3091
|
if (alreadyInit) {
|
|
3080
3092
|
log("Haus AI already initialized in this project.");
|
|
@@ -3087,7 +3099,7 @@ async function runInit(options) {
|
|
|
3087
3099
|
|
|
3088
3100
|
// src/install/apply.ts
|
|
3089
3101
|
import crypto2 from "crypto";
|
|
3090
|
-
import
|
|
3102
|
+
import path22 from "path";
|
|
3091
3103
|
import fs15 from "fs-extra";
|
|
3092
3104
|
|
|
3093
3105
|
// src/install/allow-rules.ts
|
|
@@ -3135,13 +3147,13 @@ ${content2}`;
|
|
|
3135
3147
|
|
|
3136
3148
|
// src/install/manifest.ts
|
|
3137
3149
|
import os4 from "os";
|
|
3138
|
-
import
|
|
3150
|
+
import path20 from "path";
|
|
3139
3151
|
var MANIFEST_SCHEMA = "haus-install-manifest/1";
|
|
3140
3152
|
function globalClaudeDir() {
|
|
3141
|
-
return
|
|
3153
|
+
return path20.join(os4.homedir(), ".claude");
|
|
3142
3154
|
}
|
|
3143
3155
|
function hausManifestPath() {
|
|
3144
|
-
return
|
|
3156
|
+
return path20.join(globalClaudeDir(), "haus", "install-manifest.json");
|
|
3145
3157
|
}
|
|
3146
3158
|
async function readManifest() {
|
|
3147
3159
|
return readJson(hausManifestPath());
|
|
@@ -3160,10 +3172,10 @@ function buildManifest(source, files, hooks) {
|
|
|
3160
3172
|
}
|
|
3161
3173
|
|
|
3162
3174
|
// src/install/settings-merge.ts
|
|
3163
|
-
import
|
|
3175
|
+
import path21 from "path";
|
|
3164
3176
|
import fs14 from "fs-extra";
|
|
3165
3177
|
function settingsJsonPath() {
|
|
3166
|
-
return
|
|
3178
|
+
return path21.join(globalClaudeDir(), "settings.json");
|
|
3167
3179
|
}
|
|
3168
3180
|
async function readSettings() {
|
|
3169
3181
|
const parsed = await readJson(settingsJsonPath());
|
|
@@ -3319,7 +3331,7 @@ function hashContent(content2) {
|
|
|
3319
3331
|
}
|
|
3320
3332
|
function sourceVersion() {
|
|
3321
3333
|
try {
|
|
3322
|
-
const pkgPath =
|
|
3334
|
+
const pkgPath = path22.join(packageRoot(), "package.json");
|
|
3323
3335
|
const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf8"));
|
|
3324
3336
|
return `${pkg.name ?? "haus"}@${pkg.version ?? "0.0.0"}`;
|
|
3325
3337
|
} catch {
|
|
@@ -3327,32 +3339,32 @@ function sourceVersion() {
|
|
|
3327
3339
|
}
|
|
3328
3340
|
}
|
|
3329
3341
|
function globalSrcDir() {
|
|
3330
|
-
return
|
|
3342
|
+
return path22.join(packageRoot(), "library", "global");
|
|
3331
3343
|
}
|
|
3332
3344
|
function collectSourceFiles(srcDir, claudeDir) {
|
|
3333
3345
|
const entries = [];
|
|
3334
|
-
const skillsDir =
|
|
3346
|
+
const skillsDir = path22.join(srcDir, "skills");
|
|
3335
3347
|
if (fs15.pathExistsSync(skillsDir)) {
|
|
3336
3348
|
for (const skillName of fs15.readdirSync(skillsDir)) {
|
|
3337
|
-
const skillFile =
|
|
3349
|
+
const skillFile = path22.join(skillsDir, skillName, "SKILL.md");
|
|
3338
3350
|
if (fs15.pathExistsSync(skillFile)) {
|
|
3339
3351
|
entries.push({
|
|
3340
3352
|
stableId: `skill.${skillName}`,
|
|
3341
|
-
srcRelPath:
|
|
3342
|
-
destPath:
|
|
3353
|
+
srcRelPath: path22.join("library", "global", "skills", skillName, "SKILL.md"),
|
|
3354
|
+
destPath: path22.join(claudeDir, "skills", skillName, "SKILL.md")
|
|
3343
3355
|
});
|
|
3344
3356
|
}
|
|
3345
3357
|
}
|
|
3346
3358
|
}
|
|
3347
|
-
const commandsDir =
|
|
3359
|
+
const commandsDir = path22.join(srcDir, "commands");
|
|
3348
3360
|
if (fs15.pathExistsSync(commandsDir)) {
|
|
3349
3361
|
for (const fileName of fs15.readdirSync(commandsDir)) {
|
|
3350
3362
|
if (!fileName.endsWith(".md")) continue;
|
|
3351
3363
|
const commandName = fileName.slice(0, -".md".length);
|
|
3352
3364
|
entries.push({
|
|
3353
3365
|
stableId: `command.${commandName}`,
|
|
3354
|
-
srcRelPath:
|
|
3355
|
-
destPath:
|
|
3366
|
+
srcRelPath: path22.join("library", "global", "commands", fileName),
|
|
3367
|
+
destPath: path22.join(claudeDir, "commands", fileName)
|
|
3356
3368
|
});
|
|
3357
3369
|
}
|
|
3358
3370
|
}
|
|
@@ -3376,7 +3388,7 @@ async function applyInstall(options = {}) {
|
|
|
3376
3388
|
};
|
|
3377
3389
|
const manifestFiles = [];
|
|
3378
3390
|
for (const entry of sourceFiles) {
|
|
3379
|
-
const srcPath =
|
|
3391
|
+
const srcPath = path22.join(packageRoot(), entry.srcRelPath);
|
|
3380
3392
|
const rawContent = await readText(srcPath);
|
|
3381
3393
|
if (rawContent === void 0) {
|
|
3382
3394
|
warn(`Source file not found: ${entry.srcRelPath}`);
|
|
@@ -3432,7 +3444,7 @@ async function applyInstall(options = {}) {
|
|
|
3432
3444
|
schemaVersion: SCHEMA_VERSION3
|
|
3433
3445
|
});
|
|
3434
3446
|
}
|
|
3435
|
-
const fragmentPath =
|
|
3447
|
+
const fragmentPath = path22.join(srcDir, "settings-fragments", "hooks.json");
|
|
3436
3448
|
const fragments = await loadHooksFragment(fragmentPath);
|
|
3437
3449
|
const settings = await readSettings();
|
|
3438
3450
|
const { settings: hookSettings, addedIds } = mergeHooks(settings, fragments);
|
|
@@ -3572,12 +3584,12 @@ async function runScan(options) {
|
|
|
3572
3584
|
}
|
|
3573
3585
|
|
|
3574
3586
|
// src/commands/undo.ts
|
|
3575
|
-
import
|
|
3587
|
+
import path23 from "path";
|
|
3576
3588
|
import fs16 from "fs-extra";
|
|
3577
3589
|
var CLAUDE_DIR = ".claude";
|
|
3578
3590
|
async function runUndo(options) {
|
|
3579
3591
|
const root = process.cwd();
|
|
3580
|
-
const targets = [
|
|
3592
|
+
const targets = [path23.join(root, CLAUDE_DIR), path23.join(root, HAUS_DIR)];
|
|
3581
3593
|
const existing = targets.filter((p) => fs16.existsSync(p));
|
|
3582
3594
|
if (existing.length === 0) {
|
|
3583
3595
|
log("Nothing to remove: no .claude/ or .haus-workflow/ in this directory.");
|
|
@@ -3585,7 +3597,7 @@ async function runUndo(options) {
|
|
|
3585
3597
|
}
|
|
3586
3598
|
if (!options.yes) {
|
|
3587
3599
|
const ok = await confirm(
|
|
3588
|
-
`Remove ${existing.map((p) =>
|
|
3600
|
+
`Remove ${existing.map((p) => path23.relative(root, p)).join(" and ")}? This cannot be undone.`
|
|
3589
3601
|
);
|
|
3590
3602
|
if (!ok) {
|
|
3591
3603
|
log("Cancelled.");
|
|
@@ -3594,13 +3606,13 @@ async function runUndo(options) {
|
|
|
3594
3606
|
}
|
|
3595
3607
|
for (const p of existing) {
|
|
3596
3608
|
await fs16.remove(p);
|
|
3597
|
-
log(`Removed ${
|
|
3609
|
+
log(`Removed ${path23.relative(root, p)}`);
|
|
3598
3610
|
}
|
|
3599
3611
|
}
|
|
3600
3612
|
|
|
3601
3613
|
// src/install/uninstall.ts
|
|
3602
3614
|
import crypto3 from "crypto";
|
|
3603
|
-
import
|
|
3615
|
+
import path24 from "path";
|
|
3604
3616
|
import fs17 from "fs-extra";
|
|
3605
3617
|
async function runUninstall(options = {}) {
|
|
3606
3618
|
const { force = false } = options;
|
|
@@ -3630,14 +3642,14 @@ async function runUninstall(options = {}) {
|
|
|
3630
3642
|
continue;
|
|
3631
3643
|
}
|
|
3632
3644
|
await fs17.remove(entry.destPath);
|
|
3633
|
-
await pruneEmptyDir(
|
|
3645
|
+
await pruneEmptyDir(path24.dirname(entry.destPath));
|
|
3634
3646
|
result.deleted.push(entry.destPath);
|
|
3635
3647
|
}
|
|
3636
3648
|
const settings = await readSettings();
|
|
3637
3649
|
const stripped = stripHausHooks(stripHausAllow(stripHausDeny(settings)));
|
|
3638
3650
|
await writeSettings(stripped);
|
|
3639
3651
|
result.hooksStripped = true;
|
|
3640
|
-
const hausDir =
|
|
3652
|
+
const hausDir = path24.join(globalClaudeDir(), "haus");
|
|
3641
3653
|
const manifestPath = hausManifestPath();
|
|
3642
3654
|
if (fs17.pathExistsSync(manifestPath)) {
|
|
3643
3655
|
await fs17.remove(manifestPath);
|
|
@@ -3682,7 +3694,7 @@ async function runUninstallCommand(options) {
|
|
|
3682
3694
|
}
|
|
3683
3695
|
|
|
3684
3696
|
// src/commands/update.ts
|
|
3685
|
-
import
|
|
3697
|
+
import path26 from "path";
|
|
3686
3698
|
|
|
3687
3699
|
// src/update/diff-generated-files.ts
|
|
3688
3700
|
function diffGeneratedFiles() {
|
|
@@ -3709,7 +3721,7 @@ function summarizeLockDiff(before, after) {
|
|
|
3709
3721
|
|
|
3710
3722
|
// src/update/lockfile.ts
|
|
3711
3723
|
import { mkdir, readFile as readFile3, copyFile } from "fs/promises";
|
|
3712
|
-
import
|
|
3724
|
+
import path25 from "path";
|
|
3713
3725
|
async function checkLock(root) {
|
|
3714
3726
|
const lock = await readJson(hausPath(root, "haus.lock.json")) ?? [];
|
|
3715
3727
|
const hasValidVersions = lock.every(
|
|
@@ -3730,7 +3742,7 @@ async function applyLock(root) {
|
|
|
3730
3742
|
try {
|
|
3731
3743
|
const backupDir = hausPath(root, "backups");
|
|
3732
3744
|
await mkdir(backupDir, { recursive: true });
|
|
3733
|
-
await copyFile(lockPath,
|
|
3745
|
+
await copyFile(lockPath, path25.join(backupDir, `haus.lock.${Date.now()}.json`));
|
|
3734
3746
|
} catch {
|
|
3735
3747
|
}
|
|
3736
3748
|
const enriched = await Promise.all(
|
|
@@ -3752,7 +3764,7 @@ function diffLock(before, after) {
|
|
|
3752
3764
|
}
|
|
3753
3765
|
async function hasLocalOverrides(root) {
|
|
3754
3766
|
try {
|
|
3755
|
-
await readFile3(
|
|
3767
|
+
await readFile3(path25.join(root, ".claude", "settings.json"), "utf8");
|
|
3756
3768
|
return true;
|
|
3757
3769
|
} catch {
|
|
3758
3770
|
return false;
|
|
@@ -3764,7 +3776,7 @@ var NPM_PACKAGE_NAME2 = "@haus-tech/haus-workflow";
|
|
|
3764
3776
|
async function runUpdate(options) {
|
|
3765
3777
|
const root = process.cwd();
|
|
3766
3778
|
if (options.check) {
|
|
3767
|
-
const pkgJson2 = await readJson(
|
|
3779
|
+
const pkgJson2 = await readJson(path26.join(packageRoot(), "package.json"));
|
|
3768
3780
|
const currentVersion2 = pkgJson2?.version ?? "0.0.0";
|
|
3769
3781
|
const [status, npmVersion, latestCatalogTag] = await Promise.all([
|
|
3770
3782
|
checkLock(root),
|
|
@@ -3791,7 +3803,7 @@ async function runUpdate(options) {
|
|
|
3791
3803
|
if (!status.ok) process.exitCode = 1;
|
|
3792
3804
|
return;
|
|
3793
3805
|
}
|
|
3794
|
-
const pkgJson = await readJson(
|
|
3806
|
+
const pkgJson = await readJson(path26.join(packageRoot(), "package.json"));
|
|
3795
3807
|
const currentVersion = pkgJson?.version ?? "0.0.0";
|
|
3796
3808
|
const npmStatus = await fetchNpmVersionStatus(currentVersion);
|
|
3797
3809
|
if (npmStatus.updateAvailable && npmStatus.latest !== null) {
|
|
@@ -3822,7 +3834,7 @@ async function runUpdate(options) {
|
|
|
3822
3834
|
|
|
3823
3835
|
// src/commands/validate-catalog.ts
|
|
3824
3836
|
import fs18 from "fs";
|
|
3825
|
-
import
|
|
3837
|
+
import path27 from "path";
|
|
3826
3838
|
|
|
3827
3839
|
// library/catalog/validation-rules.json
|
|
3828
3840
|
var validation_rules_default = {
|
|
@@ -4071,11 +4083,11 @@ function auditShippedFiles(manifestDir, items) {
|
|
|
4071
4083
|
const failures = [];
|
|
4072
4084
|
for (const item of items) {
|
|
4073
4085
|
if (!item.path) continue;
|
|
4074
|
-
const absPath =
|
|
4086
|
+
const absPath = path27.join(manifestDir, item.path);
|
|
4075
4087
|
if (item.type === "skill") {
|
|
4076
|
-
const skillMd =
|
|
4088
|
+
const skillMd = path27.join(absPath, "SKILL.md");
|
|
4077
4089
|
if (!fs18.existsSync(skillMd)) {
|
|
4078
|
-
failures.push(`${item.id}: missing ${
|
|
4090
|
+
failures.push(`${item.id}: missing ${path27.relative(manifestDir, skillMd)}`);
|
|
4079
4091
|
continue;
|
|
4080
4092
|
}
|
|
4081
4093
|
const text = fs18.readFileSync(skillMd, "utf8");
|
|
@@ -4109,11 +4121,11 @@ function auditMarkdownContent(manifestDir) {
|
|
|
4109
4121
|
const failures = [];
|
|
4110
4122
|
const dirs = ["skills", "agents"];
|
|
4111
4123
|
for (const dir of dirs) {
|
|
4112
|
-
const abs =
|
|
4124
|
+
const abs = path27.join(manifestDir, dir);
|
|
4113
4125
|
if (!fs18.existsSync(abs)) continue;
|
|
4114
4126
|
walkMd(abs, (file) => {
|
|
4115
4127
|
const text = fs18.readFileSync(file, "utf8");
|
|
4116
|
-
const rel =
|
|
4128
|
+
const rel = path27.relative(manifestDir, file);
|
|
4117
4129
|
const lines = text.split(/\r?\n/);
|
|
4118
4130
|
for (let i = 0; i < lines.length; i++) {
|
|
4119
4131
|
const line2 = lines[i] ?? "";
|
|
@@ -4133,7 +4145,7 @@ function auditMarkdownContent(manifestDir) {
|
|
|
4133
4145
|
}
|
|
4134
4146
|
function walkMd(dir, fn) {
|
|
4135
4147
|
for (const entry of fs18.readdirSync(dir, { withFileTypes: true })) {
|
|
4136
|
-
const full =
|
|
4148
|
+
const full = path27.join(dir, entry.name);
|
|
4137
4149
|
if (entry.isDirectory()) walkMd(full, fn);
|
|
4138
4150
|
else if (entry.name.endsWith(".md")) fn(full);
|
|
4139
4151
|
}
|
|
@@ -4144,8 +4156,8 @@ async function runValidateCatalog(manifestPath) {
|
|
|
4144
4156
|
process.exitCode = 1;
|
|
4145
4157
|
return;
|
|
4146
4158
|
}
|
|
4147
|
-
const abs =
|
|
4148
|
-
const manifestDir =
|
|
4159
|
+
const abs = path27.resolve(process.cwd(), manifestPath);
|
|
4160
|
+
const manifestDir = path27.dirname(abs);
|
|
4149
4161
|
const data = await readJson(abs);
|
|
4150
4162
|
if (!data?.items) {
|
|
4151
4163
|
error(`Could not read catalog manifest at ${abs}`);
|
|
@@ -4174,7 +4186,7 @@ async function runValidateCatalog(manifestPath) {
|
|
|
4174
4186
|
}
|
|
4175
4187
|
|
|
4176
4188
|
// src/commands/workspace.ts
|
|
4177
|
-
import
|
|
4189
|
+
import path28 from "path";
|
|
4178
4190
|
import YAML from "yaml";
|
|
4179
4191
|
async function runWorkspace(action) {
|
|
4180
4192
|
if (action === "init") {
|
|
@@ -4207,7 +4219,7 @@ relationships: []
|
|
|
4207
4219
|
const summaries = [];
|
|
4208
4220
|
const ownership = {};
|
|
4209
4221
|
for (const repo of repos) {
|
|
4210
|
-
const repoRoot =
|
|
4222
|
+
const repoRoot = path28.resolve(process.cwd(), repo.path);
|
|
4211
4223
|
const result = await scanProject(repoRoot, "fast");
|
|
4212
4224
|
summaries.push({
|
|
4213
4225
|
name: repo.name,
|
|
@@ -4243,7 +4255,7 @@ ${summaries.map(
|
|
|
4243
4255
|
// src/cli.ts
|
|
4244
4256
|
function cliVersion() {
|
|
4245
4257
|
try {
|
|
4246
|
-
const pkgPath =
|
|
4258
|
+
const pkgPath = path29.join(packageRoot(), "package.json");
|
|
4247
4259
|
const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
|
|
4248
4260
|
return pkg.version ?? "0.0.0";
|
|
4249
4261
|
} catch {
|
|
@@ -4253,7 +4265,7 @@ function cliVersion() {
|
|
|
4253
4265
|
var program = new Command();
|
|
4254
4266
|
function validateRuntimeNodeVersion() {
|
|
4255
4267
|
try {
|
|
4256
|
-
const pkgPath =
|
|
4268
|
+
const pkgPath = path29.join(packageRoot(), "package.json");
|
|
4257
4269
|
const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
|
|
4258
4270
|
const requiredRange = pkg.engines?.node;
|
|
4259
4271
|
if (requiredRange && !satisfiesVersion(process.version, requiredRange)) {
|