@haus-tech/haus-workflow 0.10.1 → 0.11.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 +138 -123
- package/dist/cli.js +224 -169
- package/library/catalog/manifest.json +264 -494
- package/library/catalog/sources.yaml +56 -56
- package/library/global/agents/haus-code-reviewer.md +3 -2
- package/library/global/agents/haus-docs-researcher.md +3 -2
- package/library/global/agents/haus-planner.md +3 -2
- package/library/global/agents/haus-security-reviewer.md +3 -2
- package/library/global/agents/haus-test-reviewer.md +3 -2
- package/library/global/skills/haus-workflow/SKILL.md +21 -14
- package/library/global/templates/agentic-workflow-standard.md +279 -0
- package/package.json +3 -3
- package/tests/README.md +19 -19
- package/tests/fixtures/catalog/manifest.json +111 -78
- package/library/global/templates/haus-way-of-work.md +0 -40
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 path28 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
|
|
@@ -158,8 +158,8 @@ async function getCacheManifestAge() {
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
// src/claude/write-claude-files.ts
|
|
161
|
-
import
|
|
162
|
-
import
|
|
161
|
+
import path10 from "path";
|
|
162
|
+
import fs9 from "fs-extra";
|
|
163
163
|
|
|
164
164
|
// src/update/hash-installed.ts
|
|
165
165
|
import path3 from "path";
|
|
@@ -512,7 +512,8 @@ import path7 from "path";
|
|
|
512
512
|
import fs6 from "fs-extra";
|
|
513
513
|
var BLOCK_BEGIN = "<!-- HAUS:BEGIN haus-imports v=1 -->";
|
|
514
514
|
var BLOCK_END = "<!-- HAUS:END haus-imports -->";
|
|
515
|
-
var IMPORT_CONTENT = `@.haus-workflow/
|
|
515
|
+
var IMPORT_CONTENT = `@.haus-workflow/WORKFLOW.md
|
|
516
|
+
@.haus-workflow/workflow-config.md
|
|
516
517
|
@.haus-workflow/project.md`;
|
|
517
518
|
function buildImportBlock() {
|
|
518
519
|
return `${BLOCK_BEGIN}
|
|
@@ -562,20 +563,52 @@ async function writeRootClaudeMd(root, dryRun) {
|
|
|
562
563
|
return filePath;
|
|
563
564
|
}
|
|
564
565
|
|
|
565
|
-
// src/claude/write-
|
|
566
|
-
import os3 from "os";
|
|
566
|
+
// src/claude/write-workflow-config.ts
|
|
567
567
|
import path8 from "path";
|
|
568
568
|
import fs7 from "fs-extra";
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
);
|
|
577
|
-
|
|
578
|
-
|
|
569
|
+
function buildWorkflowConfig(ctx) {
|
|
570
|
+
const pm = ctx.packageManager === "unknown" ? "npm" : ctx.packageManager;
|
|
571
|
+
const testCmd = pm + " test";
|
|
572
|
+
const auditCmd = pm + " audit";
|
|
573
|
+
return "# Project workflow configuration\n\n> Project-specific values for the workflow standard in WORKFLOW.md.\n> Edit freely \u2014 this file is project-owned and will not be overwritten by haus.\n\n## Source-of-truth documents\n- Spec: <!-- fill in path, e.g. docs/SPEC.md -->\n- Design: <!-- fill in path, e.g. docs/DESIGN.md -->\n- UX flows: <!-- fill in path, e.g. docs/UX.md -->\n\n## Commands\n- Test (unit + integration): `" + testCmd + "`\n- Test (E2E): <!-- fill in command -->\n- Type check: <!-- fill in command, e.g. tsc --noEmit -->\n- Lint: <!-- fill in command, e.g. npm run lint -->\n- Lint fix: <!-- fill in command, e.g. npm run lint -- --fix -->\n- Format check: <!-- fill in command, e.g. prettier --check . -->\n- Security audit: `" + auditCmd + "`\n\n## Validation library\n<!-- fill in, e.g. zod, yup, joi -->\n\n## Highest-stakes logic\n<!-- fill in domain areas requiring TDD-only treatment, e.g. payment flows, auth, medical data -->\n\n## Pre-commit tool\n<!-- fill in, e.g. lefthook, husky -->\n";
|
|
574
|
+
}
|
|
575
|
+
async function writeWorkflowConfig(root, dryRun) {
|
|
576
|
+
const destPath = hausPath(root, "workflow-config.md");
|
|
577
|
+
const printable = displayPath(root, destPath);
|
|
578
|
+
if (await fs7.pathExists(destPath)) {
|
|
579
|
+
if (dryRun) log(printable + ": exists (project-owned, skipping)");
|
|
580
|
+
return destPath;
|
|
581
|
+
}
|
|
582
|
+
const ctx = await readJson(hausPath(root, "context-map.json")) ?? {
|
|
583
|
+
mode: "fast",
|
|
584
|
+
generatedAt: "",
|
|
585
|
+
root,
|
|
586
|
+
repoName: path8.basename(root),
|
|
587
|
+
packageManager: "unknown",
|
|
588
|
+
repoRoles: [],
|
|
589
|
+
confidence: 0,
|
|
590
|
+
detectedStacks: {},
|
|
591
|
+
dependencies: [],
|
|
592
|
+
securityRisks: [],
|
|
593
|
+
crossRepoHints: [],
|
|
594
|
+
warnings: []
|
|
595
|
+
};
|
|
596
|
+
const content = buildWorkflowConfig(ctx);
|
|
597
|
+
if (dryRun) {
|
|
598
|
+
log(printable + ": would create");
|
|
599
|
+
return destPath;
|
|
600
|
+
}
|
|
601
|
+
await writeText(destPath, content);
|
|
602
|
+
return destPath;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// src/claude/write-workflow.ts
|
|
606
|
+
import path9 from "path";
|
|
607
|
+
import fs8 from "fs-extra";
|
|
608
|
+
|
|
609
|
+
// src/claude/managed-template.ts
|
|
610
|
+
function normaliseLF(content) {
|
|
611
|
+
return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
579
612
|
}
|
|
580
613
|
function parseHausManagedHeader(line) {
|
|
581
614
|
const match = line.match(/<!-- HAUS-MANAGED id=([\w.:-]+)/);
|
|
@@ -583,23 +616,32 @@ function parseHausManagedHeader(line) {
|
|
|
583
616
|
const hashMatch = line.match(/hash=(sha256-[a-f0-9]+)/);
|
|
584
617
|
return { id: match[1], hash: hashMatch?.[1] };
|
|
585
618
|
}
|
|
586
|
-
|
|
619
|
+
|
|
620
|
+
// src/claude/write-workflow.ts
|
|
621
|
+
var STABLE_ID2 = "template.workflow";
|
|
622
|
+
var SCHEMA_VERSION2 = "1";
|
|
623
|
+
var TEMPLATE_REL = "library/global/templates/agentic-workflow-standard.md";
|
|
624
|
+
var CATALOG_CACHE_TEMPLATE = path9.join(CACHE_DIR, "templates/agentic-workflow-standard.md");
|
|
625
|
+
function makeWorkflowHeader(pkgVersion, contentHash) {
|
|
626
|
+
return `<!-- HAUS-MANAGED id=${STABLE_ID2} v=${SCHEMA_VERSION2} source=@haus-tech/haus-workflow@${pkgVersion} hash=${contentHash} -->`;
|
|
627
|
+
}
|
|
628
|
+
async function writeWorkflow(root, pkgVersion, dryRun) {
|
|
587
629
|
const cachePath = CATALOG_CACHE_TEMPLATE;
|
|
588
|
-
const packagePath =
|
|
589
|
-
const templatePath = await
|
|
590
|
-
if (!await
|
|
591
|
-
warn(`
|
|
630
|
+
const packagePath = path9.join(packageRoot(), TEMPLATE_REL);
|
|
631
|
+
const templatePath = await fs8.pathExists(cachePath) ? cachePath : packagePath;
|
|
632
|
+
if (!await fs8.pathExists(templatePath)) {
|
|
633
|
+
warn(`Workflow template not found \u2014 run \`haus update\` to fetch from catalog`);
|
|
592
634
|
return null;
|
|
593
635
|
}
|
|
594
|
-
const templateContent = await
|
|
595
|
-
const contentHash = hashText(templateContent);
|
|
596
|
-
const header =
|
|
636
|
+
const templateContent = await fs8.readFile(templatePath, "utf8");
|
|
637
|
+
const contentHash = hashText(normaliseLF(templateContent));
|
|
638
|
+
const header = makeWorkflowHeader(pkgVersion, contentHash);
|
|
597
639
|
const next = `${header}
|
|
598
640
|
${templateContent}`;
|
|
599
|
-
const destPath = hausPath(root, "
|
|
641
|
+
const destPath = hausPath(root, "WORKFLOW.md");
|
|
600
642
|
const printable = displayPath(root, destPath);
|
|
601
|
-
if (await
|
|
602
|
-
const existing = await
|
|
643
|
+
if (await fs8.pathExists(destPath)) {
|
|
644
|
+
const existing = await fs8.readFile(destPath, "utf8");
|
|
603
645
|
const firstLine = existing.split("\n")[0] ?? "";
|
|
604
646
|
const parsed = parseHausManagedHeader(firstLine);
|
|
605
647
|
if (!parsed) {
|
|
@@ -611,7 +653,7 @@ ${templateContent}`;
|
|
|
611
653
|
return null;
|
|
612
654
|
}
|
|
613
655
|
const existingContent = existing.slice(firstLine.length + 1);
|
|
614
|
-
if (parsed.hash && hashText(existingContent) !== parsed.hash) {
|
|
656
|
+
if (parsed.hash && hashText(normaliseLF(existingContent)) !== parsed.hash) {
|
|
615
657
|
warn(`${printable}: content modified by user \u2014 skipping. Use --force to overwrite.`);
|
|
616
658
|
return null;
|
|
617
659
|
}
|
|
@@ -621,7 +663,7 @@ ${templateContent}`;
|
|
|
621
663
|
}
|
|
622
664
|
}
|
|
623
665
|
if (dryRun) {
|
|
624
|
-
const prev = await
|
|
666
|
+
const prev = await fs8.pathExists(destPath) ? await fs8.readFile(destPath, "utf8") : "";
|
|
625
667
|
if (!prev) {
|
|
626
668
|
log(createUnifiedDiff(printable, "", next));
|
|
627
669
|
} else {
|
|
@@ -648,7 +690,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds) {
|
|
|
648
690
|
estimatedTokenReductionPct: 0
|
|
649
691
|
};
|
|
650
692
|
const pkgRoot = packageRoot();
|
|
651
|
-
const hausVersion = (await readJson(
|
|
693
|
+
const hausVersion = (await readJson(path10.join(pkgRoot, "package.json")))?.version ?? "0.0.0";
|
|
652
694
|
const coreFiles = [
|
|
653
695
|
claudePath(root, "settings.json"),
|
|
654
696
|
claudePath(root, "rules", "haus.md"),
|
|
@@ -657,9 +699,15 @@ async function writeClaudeFiles(root, dryRun, selectedIds) {
|
|
|
657
699
|
claudePath(root, "commands", "haus-review.md")
|
|
658
700
|
];
|
|
659
701
|
const rootClaudeMdPath = await writeRootClaudeMd(root, dryRun);
|
|
660
|
-
const
|
|
702
|
+
const workflowPath = await writeWorkflow(root, hausVersion, dryRun);
|
|
703
|
+
const workflowConfigPath = await writeWorkflowConfig(root, dryRun);
|
|
661
704
|
const projectFactsPath = await writeProjectFacts(root, hausVersion, dryRun);
|
|
662
|
-
const p6Files = [
|
|
705
|
+
const p6Files = [
|
|
706
|
+
rootClaudeMdPath,
|
|
707
|
+
projectFactsPath,
|
|
708
|
+
...workflowPath ? [workflowPath] : [],
|
|
709
|
+
...workflowConfigPath ? [workflowConfigPath] : []
|
|
710
|
+
];
|
|
663
711
|
const files = dryRun ? [...coreFiles, ...p6Files] : [
|
|
664
712
|
...coreFiles,
|
|
665
713
|
...p6Files,
|
|
@@ -670,7 +718,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds) {
|
|
|
670
718
|
await writeManagedJson(root, claudePath(root, "settings.json"), hookSettings, dryRun);
|
|
671
719
|
if (!dryRun) await assertPostApplySettingsMatchCanonical(root, hookSettings);
|
|
672
720
|
const configPath = hausPath(root, "config.json");
|
|
673
|
-
if (!await
|
|
721
|
+
if (!await fs9.pathExists(configPath)) {
|
|
674
722
|
await writeManagedJson(root, configPath, DEFAULT_HOOKS_CONFIG, dryRun);
|
|
675
723
|
}
|
|
676
724
|
await writeManagedText(
|
|
@@ -698,12 +746,12 @@ async function writeClaudeFiles(root, dryRun, selectedIds) {
|
|
|
698
746
|
dryRun
|
|
699
747
|
);
|
|
700
748
|
const fixtureManifestPath = process.env["HAUS_FIXTURE_CATALOG"];
|
|
701
|
-
const manifestPath = fixtureManifestPath ??
|
|
702
|
-
const manifestDir =
|
|
749
|
+
const manifestPath = fixtureManifestPath ?? path10.join(pkgRoot, "library", "catalog", "manifest.json");
|
|
750
|
+
const manifestDir = path10.dirname(manifestPath);
|
|
703
751
|
const manifest = await readJson(manifestPath) ?? { items: [] };
|
|
704
752
|
const manifestById = new Map((manifest.items ?? []).map((item) => [item.id, item]));
|
|
705
753
|
const cacheManifest = await readJson(
|
|
706
|
-
|
|
754
|
+
path10.join(CACHE_DIR, "manifest.json")
|
|
707
755
|
);
|
|
708
756
|
const cacheManifestById = new Map((cacheManifest?.items ?? []).map((item) => [item.id, item]));
|
|
709
757
|
const installedPathsByItem = /* @__PURE__ */ new Map();
|
|
@@ -725,23 +773,23 @@ async function writeClaudeFiles(root, dryRun, selectedIds) {
|
|
|
725
773
|
}
|
|
726
774
|
}
|
|
727
775
|
const cachedItem = cacheManifestById.get(item.id);
|
|
728
|
-
const cachePath = cachedItem?.path ?
|
|
729
|
-
const sourcePath = cachePath && await
|
|
776
|
+
const cachePath = cachedItem?.path ? path10.join(CACHE_DIR, cachedItem.path) : null;
|
|
777
|
+
const sourcePath = cachePath && await fs9.pathExists(cachePath) ? cachePath : path10.join(manifestDir, manifestItem.path);
|
|
730
778
|
const target = item.type === "agent" ? "agents" : item.type === "template" ? "templates" : "skills";
|
|
731
|
-
const destination = claudePath(root, target,
|
|
732
|
-
if (await
|
|
779
|
+
const destination = claudePath(root, target, path10.basename(sourcePath));
|
|
780
|
+
if (await fs9.pathExists(sourcePath)) {
|
|
733
781
|
if (dryRun) {
|
|
734
|
-
const exists = await
|
|
782
|
+
const exists = await fs9.pathExists(destination);
|
|
735
783
|
log(
|
|
736
784
|
`${displayPath(root, destination)}: ${exists ? "would overwrite" : "would create"} (${item.id})`
|
|
737
785
|
);
|
|
738
786
|
} else {
|
|
739
|
-
await
|
|
740
|
-
await
|
|
787
|
+
await fs9.ensureDir(path10.dirname(destination));
|
|
788
|
+
await fs9.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
|
|
741
789
|
}
|
|
742
790
|
files.push(destination);
|
|
743
791
|
const current = installedPathsByItem.get(item.id) ?? [];
|
|
744
|
-
installedPathsByItem.set(item.id, [...current,
|
|
792
|
+
installedPathsByItem.set(item.id, [...current, path10.relative(root, destination)]);
|
|
745
793
|
installedIds.add(item.id);
|
|
746
794
|
} else {
|
|
747
795
|
warn(
|
|
@@ -792,7 +840,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds) {
|
|
|
792
840
|
return [...new Set(files)];
|
|
793
841
|
}
|
|
794
842
|
async function writeManagedText(root, filePath, nextText, dryRun) {
|
|
795
|
-
const prev = await
|
|
843
|
+
const prev = await fs9.pathExists(filePath) ? await fs9.readFile(filePath, "utf8") : "";
|
|
796
844
|
const printable = displayPath(root, filePath);
|
|
797
845
|
if (dryRun) {
|
|
798
846
|
if (!prev) {
|
|
@@ -819,7 +867,7 @@ async function writeManagedJson(root, filePath, value, dryRun) {
|
|
|
819
867
|
|
|
820
868
|
// src/commands/apply.ts
|
|
821
869
|
async function cacheHasItems() {
|
|
822
|
-
const data = await readJson(
|
|
870
|
+
const data = await readJson(path11.join(CACHE_DIR, "manifest.json"));
|
|
823
871
|
return Array.isArray(data?.items) && data.items.length > 0;
|
|
824
872
|
}
|
|
825
873
|
async function runApply(options) {
|
|
@@ -886,9 +934,9 @@ async function runApply(options) {
|
|
|
886
934
|
}
|
|
887
935
|
|
|
888
936
|
// src/catalog/load-catalog.ts
|
|
889
|
-
import
|
|
890
|
-
import
|
|
891
|
-
var CACHE_MANIFEST =
|
|
937
|
+
import os3 from "os";
|
|
938
|
+
import path12 from "path";
|
|
939
|
+
var CACHE_MANIFEST = path12.join(os3.homedir(), CATALOG_CACHE_SUBDIR, "manifest.json");
|
|
892
940
|
async function loadCatalog(root) {
|
|
893
941
|
const envPath = process.env["HAUS_FIXTURE_CATALOG"];
|
|
894
942
|
if (envPath) {
|
|
@@ -897,10 +945,10 @@ async function loadCatalog(root) {
|
|
|
897
945
|
}
|
|
898
946
|
const cacheData = await readJson(CACHE_MANIFEST);
|
|
899
947
|
if (cacheData?.items?.length) return cacheData.items;
|
|
900
|
-
const localManifest =
|
|
948
|
+
const localManifest = path12.join(root, "library/catalog/manifest.json");
|
|
901
949
|
const localData = await readJson(localManifest);
|
|
902
950
|
if (localData?.items?.length) return localData.items;
|
|
903
|
-
const packageManifest =
|
|
951
|
+
const packageManifest = path12.join(packageRoot(), "library/catalog/manifest.json");
|
|
904
952
|
const data = await readJson(packageManifest);
|
|
905
953
|
return data?.items ?? [];
|
|
906
954
|
}
|
|
@@ -940,7 +988,7 @@ async function runCatalogAudit() {
|
|
|
940
988
|
}
|
|
941
989
|
|
|
942
990
|
// src/commands/config.ts
|
|
943
|
-
import
|
|
991
|
+
import path13 from "path";
|
|
944
992
|
var CONFIG_PATH2 = ".haus-workflow/config.json";
|
|
945
993
|
var HOOK_ALIASES = {
|
|
946
994
|
"hook.context": "context",
|
|
@@ -954,7 +1002,7 @@ async function runConfig(key, action) {
|
|
|
954
1002
|
);
|
|
955
1003
|
}
|
|
956
1004
|
const root = process.cwd();
|
|
957
|
-
const configPath =
|
|
1005
|
+
const configPath = path13.join(root, CONFIG_PATH2);
|
|
958
1006
|
const existing = await readJson(configPath);
|
|
959
1007
|
const cfg = existing ?? structuredClone(DEFAULT_HOOKS_CONFIG);
|
|
960
1008
|
cfg.hooks ??= {};
|
|
@@ -1312,7 +1360,7 @@ function computeRuleIntents(rule) {
|
|
|
1312
1360
|
|
|
1313
1361
|
// src/scanner/scan-project.ts
|
|
1314
1362
|
import { readFile } from "fs/promises";
|
|
1315
|
-
import
|
|
1363
|
+
import path15 from "path";
|
|
1316
1364
|
|
|
1317
1365
|
// src/utils/audit-checks.ts
|
|
1318
1366
|
function isRecord(v) {
|
|
@@ -1339,8 +1387,8 @@ function compareVersions(a, b) {
|
|
|
1339
1387
|
}
|
|
1340
1388
|
|
|
1341
1389
|
// src/scanner/detect-package-manager.ts
|
|
1342
|
-
import
|
|
1343
|
-
import
|
|
1390
|
+
import path14 from "path";
|
|
1391
|
+
import fs10 from "fs-extra";
|
|
1344
1392
|
function detectPackageManager(root, packageManagerField) {
|
|
1345
1393
|
const field = String(packageManagerField ?? "").trim();
|
|
1346
1394
|
if (field.startsWith("yarn@")) {
|
|
@@ -1358,9 +1406,9 @@ function detectPackageManager(root, packageManagerField) {
|
|
|
1358
1406
|
if (satisfiesVersion(version, ">=9")) return "npm";
|
|
1359
1407
|
return "unknown";
|
|
1360
1408
|
}
|
|
1361
|
-
if (
|
|
1362
|
-
if (
|
|
1363
|
-
if (
|
|
1409
|
+
if (fs10.existsSync(path14.join(root, "yarn.lock"))) return "yarn";
|
|
1410
|
+
if (fs10.existsSync(path14.join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
1411
|
+
if (fs10.existsSync(path14.join(root, "package-lock.json"))) return "npm";
|
|
1364
1412
|
return "unknown";
|
|
1365
1413
|
}
|
|
1366
1414
|
|
|
@@ -1421,8 +1469,8 @@ function blocked(rel) {
|
|
|
1421
1469
|
return SENSITIVE.some((x) => x.test(rel));
|
|
1422
1470
|
}
|
|
1423
1471
|
async function scanProject(root, mode = "fast") {
|
|
1424
|
-
const pkg = await readJson(
|
|
1425
|
-
const composer = await readJson(
|
|
1472
|
+
const pkg = await readJson(path15.join(root, "package.json"));
|
|
1473
|
+
const composer = await readJson(path15.join(root, "composer.json"));
|
|
1426
1474
|
const files = await listFiles(root, SAFE_FILES);
|
|
1427
1475
|
const safeFiles = files.filter((f) => !blocked(f));
|
|
1428
1476
|
const deps = dependencySet(pkg, composer);
|
|
@@ -1450,7 +1498,7 @@ async function scanProject(root, mode = "fast") {
|
|
|
1450
1498
|
mode,
|
|
1451
1499
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1452
1500
|
root,
|
|
1453
|
-
repoName: String(pkg?.name ??
|
|
1501
|
+
repoName: String(pkg?.name ?? path15.basename(root)),
|
|
1454
1502
|
packageManager,
|
|
1455
1503
|
repoRoles: roles,
|
|
1456
1504
|
confidence: computeConfidence(roles, stacks),
|
|
@@ -1467,7 +1515,7 @@ async function scanProject(root, mode = "fast") {
|
|
|
1467
1515
|
const scanHashes = Object.fromEntries(
|
|
1468
1516
|
await Promise.all(
|
|
1469
1517
|
safeFiles.map(
|
|
1470
|
-
async (f) => [f, hashText(await readFile(
|
|
1518
|
+
async (f) => [f, hashText(await readFile(path15.join(root, f), "utf8"))]
|
|
1471
1519
|
)
|
|
1472
1520
|
)
|
|
1473
1521
|
);
|
|
@@ -1634,7 +1682,7 @@ async function hasNeedle(root, files, needle) {
|
|
|
1634
1682
|
);
|
|
1635
1683
|
for (const rel of candidates.slice(0, 300)) {
|
|
1636
1684
|
try {
|
|
1637
|
-
const content = await readFile(
|
|
1685
|
+
const content = await readFile(path15.join(root, rel), "utf8");
|
|
1638
1686
|
if (content.includes(needle)) return true;
|
|
1639
1687
|
} catch {
|
|
1640
1688
|
continue;
|
|
@@ -1727,8 +1775,8 @@ async function runContext(options) {
|
|
|
1727
1775
|
}
|
|
1728
1776
|
|
|
1729
1777
|
// src/commands/doctor.ts
|
|
1730
|
-
import
|
|
1731
|
-
import
|
|
1778
|
+
import path16 from "path";
|
|
1779
|
+
import fs11 from "fs-extra";
|
|
1732
1780
|
|
|
1733
1781
|
// src/update/npm-version.ts
|
|
1734
1782
|
var NPM_PACKAGE_NAME = "@haus-tech/haus-workflow";
|
|
@@ -1798,7 +1846,7 @@ async function runDoctor(options) {
|
|
|
1798
1846
|
const enabled = await isHookEnabled(root, key);
|
|
1799
1847
|
log(`- HOOK ${key}: ${enabled ? "enabled" : "disabled (default)"}`);
|
|
1800
1848
|
}
|
|
1801
|
-
const rootClaudeMdPath =
|
|
1849
|
+
const rootClaudeMdPath = path16.join(root, "CLAUDE.md");
|
|
1802
1850
|
const rootClaudeMdContent = await readText(rootClaudeMdPath);
|
|
1803
1851
|
if (!rootClaudeMdContent) {
|
|
1804
1852
|
warn("- CLAUDE.md: missing (run `haus apply --write` to create)");
|
|
@@ -1807,41 +1855,48 @@ async function runDoctor(options) {
|
|
|
1807
1855
|
} else {
|
|
1808
1856
|
log("- CLAUDE.md: import block present");
|
|
1809
1857
|
}
|
|
1810
|
-
const
|
|
1811
|
-
const
|
|
1812
|
-
if (!
|
|
1813
|
-
warn("- .haus-workflow/
|
|
1858
|
+
const workflowPath = hausPath(root, "WORKFLOW.md");
|
|
1859
|
+
const workflowExists = await fs11.pathExists(workflowPath);
|
|
1860
|
+
if (!workflowExists) {
|
|
1861
|
+
warn("- .haus-workflow/WORKFLOW.md: missing (run `haus apply --write`)");
|
|
1814
1862
|
} else {
|
|
1815
|
-
const
|
|
1816
|
-
const firstLine =
|
|
1863
|
+
const workflowContent = await readText(workflowPath);
|
|
1864
|
+
const firstLine = workflowContent?.split("\n")[0] ?? "";
|
|
1817
1865
|
if (!firstLine.includes("HAUS-MANAGED")) {
|
|
1818
|
-
|
|
1866
|
+
log("- .haus-workflow/WORKFLOW.md: OK (user-owned)");
|
|
1819
1867
|
} else {
|
|
1820
1868
|
const storedHashMatch = firstLine.match(/hash=(sha256-[a-f0-9]+)/);
|
|
1821
|
-
const
|
|
1869
|
+
const cachePath = path16.join(CACHE_DIR, "templates/agentic-workflow-standard.md");
|
|
1870
|
+
const bundledPath = path16.join(
|
|
1822
1871
|
packageRoot(),
|
|
1823
1872
|
"library",
|
|
1824
1873
|
"global",
|
|
1825
1874
|
"templates",
|
|
1826
|
-
"
|
|
1875
|
+
"agentic-workflow-standard.md"
|
|
1827
1876
|
);
|
|
1877
|
+
const templatePath = await fs11.pathExists(cachePath) ? cachePath : bundledPath;
|
|
1828
1878
|
const templateContent = await readText(templatePath);
|
|
1829
1879
|
if (storedHashMatch && templateContent) {
|
|
1830
|
-
const currentHash = hashText(templateContent);
|
|
1880
|
+
const currentHash = hashText(normaliseLF(templateContent));
|
|
1831
1881
|
if (storedHashMatch[1] !== currentHash) {
|
|
1832
|
-
warn(
|
|
1833
|
-
"- .haus-workflow/haus-way-of-work.md: stale (template updated \u2014 run `haus apply --write`)"
|
|
1834
|
-
);
|
|
1882
|
+
warn("- .haus-workflow/WORKFLOW.md: stale (template updated \u2014 run `haus apply --write`)");
|
|
1835
1883
|
} else {
|
|
1836
|
-
log("- .haus-workflow/
|
|
1884
|
+
log("- .haus-workflow/WORKFLOW.md: OK");
|
|
1837
1885
|
}
|
|
1838
1886
|
} else {
|
|
1839
|
-
log("- .haus-workflow/
|
|
1887
|
+
log("- .haus-workflow/WORKFLOW.md: OK");
|
|
1840
1888
|
}
|
|
1841
1889
|
}
|
|
1842
1890
|
}
|
|
1891
|
+
const workflowConfigPath = hausPath(root, "workflow-config.md");
|
|
1892
|
+
const workflowConfigExists = await fs11.pathExists(workflowConfigPath);
|
|
1893
|
+
if (!workflowConfigExists) {
|
|
1894
|
+
warn("- .haus-workflow/workflow-config.md: missing (run `haus apply --write`)");
|
|
1895
|
+
} else {
|
|
1896
|
+
log("- .haus-workflow/workflow-config.md: OK (project-owned)");
|
|
1897
|
+
}
|
|
1843
1898
|
const projectMdPath = hausPath(root, "project.md");
|
|
1844
|
-
const projectMdExists = await
|
|
1899
|
+
const projectMdExists = await fs11.pathExists(projectMdPath);
|
|
1845
1900
|
if (!projectMdExists) {
|
|
1846
1901
|
warn("- .haus-workflow/project.md: missing (run `haus apply --write`)");
|
|
1847
1902
|
} else {
|
|
@@ -1864,7 +1919,7 @@ async function runDoctor(options) {
|
|
|
1864
1919
|
log(`- CATALOG CACHE: OK (${cacheAgeDays}d old)`);
|
|
1865
1920
|
}
|
|
1866
1921
|
}
|
|
1867
|
-
const pkgJson = await readJson(
|
|
1922
|
+
const pkgJson = await readJson(path16.join(packageRoot(), "package.json"));
|
|
1868
1923
|
const currentVersion = pkgJson?.version ?? "0.0.0";
|
|
1869
1924
|
const npmStatus = await fetchNpmVersionStatus(currentVersion);
|
|
1870
1925
|
if (npmStatus.updateAvailable && npmStatus.latest !== null) {
|
|
@@ -2031,8 +2086,8 @@ async function runGuard(kind, _options) {
|
|
|
2031
2086
|
}
|
|
2032
2087
|
|
|
2033
2088
|
// src/commands/init.ts
|
|
2034
|
-
import
|
|
2035
|
-
import
|
|
2089
|
+
import path17 from "path";
|
|
2090
|
+
import fs12 from "fs-extra";
|
|
2036
2091
|
|
|
2037
2092
|
// src/utils/exec.ts
|
|
2038
2093
|
import { execa } from "execa";
|
|
@@ -2575,8 +2630,8 @@ async function runSetupProject(options) {
|
|
|
2575
2630
|
// src/commands/init.ts
|
|
2576
2631
|
async function runInit(options) {
|
|
2577
2632
|
const root = process.cwd();
|
|
2578
|
-
const hausDir =
|
|
2579
|
-
const alreadyInit = await
|
|
2633
|
+
const hausDir = path17.join(root, ".haus-workflow");
|
|
2634
|
+
const alreadyInit = await fs12.pathExists(hausDir);
|
|
2580
2635
|
if (alreadyInit) {
|
|
2581
2636
|
log("Haus AI already initialized in this project.");
|
|
2582
2637
|
log("Run `haus setup-project` to reconfigure.");
|
|
@@ -2588,8 +2643,8 @@ async function runInit(options) {
|
|
|
2588
2643
|
|
|
2589
2644
|
// src/install/apply.ts
|
|
2590
2645
|
import crypto2 from "crypto";
|
|
2591
|
-
import
|
|
2592
|
-
import
|
|
2646
|
+
import path20 from "path";
|
|
2647
|
+
import fs14 from "fs-extra";
|
|
2593
2648
|
|
|
2594
2649
|
// src/install/header.ts
|
|
2595
2650
|
var MD_PREFIX = "<!-- HAUS-MANAGED";
|
|
@@ -2622,14 +2677,14 @@ ${content}`;
|
|
|
2622
2677
|
}
|
|
2623
2678
|
|
|
2624
2679
|
// src/install/manifest.ts
|
|
2625
|
-
import
|
|
2626
|
-
import
|
|
2680
|
+
import os4 from "os";
|
|
2681
|
+
import path18 from "path";
|
|
2627
2682
|
var MANIFEST_SCHEMA = "haus-install-manifest/1";
|
|
2628
2683
|
function globalClaudeDir() {
|
|
2629
|
-
return
|
|
2684
|
+
return path18.join(os4.homedir(), ".claude");
|
|
2630
2685
|
}
|
|
2631
2686
|
function hausManifestPath() {
|
|
2632
|
-
return
|
|
2687
|
+
return path18.join(globalClaudeDir(), "haus", "install-manifest.json");
|
|
2633
2688
|
}
|
|
2634
2689
|
async function readManifest() {
|
|
2635
2690
|
return readJson(hausManifestPath());
|
|
@@ -2648,10 +2703,10 @@ function buildManifest(source, files, hooks) {
|
|
|
2648
2703
|
}
|
|
2649
2704
|
|
|
2650
2705
|
// src/install/settings-merge.ts
|
|
2651
|
-
import
|
|
2652
|
-
import
|
|
2706
|
+
import path19 from "path";
|
|
2707
|
+
import fs13 from "fs-extra";
|
|
2653
2708
|
function settingsJsonPath() {
|
|
2654
|
-
return
|
|
2709
|
+
return path19.join(globalClaudeDir(), "settings.json");
|
|
2655
2710
|
}
|
|
2656
2711
|
async function readSettings() {
|
|
2657
2712
|
const parsed = await readJson(settingsJsonPath());
|
|
@@ -2707,7 +2762,7 @@ function stripHausHooks(settings) {
|
|
|
2707
2762
|
async function loadHooksFragment(fragmentPath) {
|
|
2708
2763
|
let raw;
|
|
2709
2764
|
try {
|
|
2710
|
-
raw = await
|
|
2765
|
+
raw = await fs13.readJson(fragmentPath);
|
|
2711
2766
|
} catch {
|
|
2712
2767
|
return [];
|
|
2713
2768
|
}
|
|
@@ -2722,40 +2777,40 @@ function hashContent(content) {
|
|
|
2722
2777
|
}
|
|
2723
2778
|
function sourceVersion() {
|
|
2724
2779
|
try {
|
|
2725
|
-
const pkgPath =
|
|
2726
|
-
const pkg = JSON.parse(
|
|
2780
|
+
const pkgPath = path20.join(packageRoot(), "package.json");
|
|
2781
|
+
const pkg = JSON.parse(fs14.readFileSync(pkgPath, "utf8"));
|
|
2727
2782
|
return `${pkg.name ?? "haus"}@${pkg.version ?? "0.0.0"}`;
|
|
2728
2783
|
} catch {
|
|
2729
2784
|
return "haus@0.0.0";
|
|
2730
2785
|
}
|
|
2731
2786
|
}
|
|
2732
2787
|
function globalSrcDir() {
|
|
2733
|
-
return
|
|
2788
|
+
return path20.join(packageRoot(), "library", "global");
|
|
2734
2789
|
}
|
|
2735
2790
|
function collectSourceFiles(srcDir, claudeDir) {
|
|
2736
2791
|
const entries = [];
|
|
2737
|
-
const skillsDir =
|
|
2738
|
-
if (
|
|
2739
|
-
for (const skillName of
|
|
2740
|
-
const skillFile =
|
|
2741
|
-
if (
|
|
2792
|
+
const skillsDir = path20.join(srcDir, "skills");
|
|
2793
|
+
if (fs14.pathExistsSync(skillsDir)) {
|
|
2794
|
+
for (const skillName of fs14.readdirSync(skillsDir)) {
|
|
2795
|
+
const skillFile = path20.join(skillsDir, skillName, "SKILL.md");
|
|
2796
|
+
if (fs14.pathExistsSync(skillFile)) {
|
|
2742
2797
|
entries.push({
|
|
2743
2798
|
stableId: `skill.${skillName}`,
|
|
2744
|
-
srcRelPath:
|
|
2745
|
-
destPath:
|
|
2799
|
+
srcRelPath: path20.join("library", "global", "skills", skillName, "SKILL.md"),
|
|
2800
|
+
destPath: path20.join(claudeDir, "skills", skillName, "SKILL.md")
|
|
2746
2801
|
});
|
|
2747
2802
|
}
|
|
2748
2803
|
}
|
|
2749
2804
|
}
|
|
2750
|
-
const agentsDir =
|
|
2751
|
-
if (
|
|
2752
|
-
for (const agentFile of
|
|
2805
|
+
const agentsDir = path20.join(srcDir, "agents");
|
|
2806
|
+
if (fs14.pathExistsSync(agentsDir)) {
|
|
2807
|
+
for (const agentFile of fs14.readdirSync(agentsDir)) {
|
|
2753
2808
|
if (!agentFile.endsWith(".md")) continue;
|
|
2754
2809
|
const agentName = agentFile.replace(/\.md$/, "");
|
|
2755
2810
|
entries.push({
|
|
2756
2811
|
stableId: `agent.${agentName}`,
|
|
2757
|
-
srcRelPath:
|
|
2758
|
-
destPath:
|
|
2812
|
+
srcRelPath: path20.join("library", "global", "agents", agentFile),
|
|
2813
|
+
destPath: path20.join(claudeDir, "agents", agentFile)
|
|
2759
2814
|
});
|
|
2760
2815
|
}
|
|
2761
2816
|
}
|
|
@@ -2779,7 +2834,7 @@ async function applyInstall(options = {}) {
|
|
|
2779
2834
|
};
|
|
2780
2835
|
const manifestFiles = [];
|
|
2781
2836
|
for (const entry of sourceFiles) {
|
|
2782
|
-
const srcPath =
|
|
2837
|
+
const srcPath = path20.join(packageRoot(), entry.srcRelPath);
|
|
2783
2838
|
const rawContent = await readText(srcPath);
|
|
2784
2839
|
if (rawContent === void 0) {
|
|
2785
2840
|
warn(`Source file not found: ${entry.srcRelPath}`);
|
|
@@ -2799,7 +2854,7 @@ async function applyInstall(options = {}) {
|
|
|
2799
2854
|
}
|
|
2800
2855
|
continue;
|
|
2801
2856
|
}
|
|
2802
|
-
const destExists =
|
|
2857
|
+
const destExists = fs14.pathExistsSync(entry.destPath);
|
|
2803
2858
|
if (destExists) {
|
|
2804
2859
|
const currentContent = await readText(entry.destPath);
|
|
2805
2860
|
if (currentContent !== void 0) {
|
|
@@ -2835,7 +2890,7 @@ async function applyInstall(options = {}) {
|
|
|
2835
2890
|
schemaVersion: SCHEMA_VERSION3
|
|
2836
2891
|
});
|
|
2837
2892
|
}
|
|
2838
|
-
const fragmentPath =
|
|
2893
|
+
const fragmentPath = path20.join(srcDir, "settings-fragments", "hooks.json");
|
|
2839
2894
|
const fragments = await loadHooksFragment(fragmentPath);
|
|
2840
2895
|
const settings = await readSettings();
|
|
2841
2896
|
const { settings: mergedSettings, addedIds } = mergeHooks(settings, fragments);
|
|
@@ -2844,13 +2899,13 @@ async function applyInstall(options = {}) {
|
|
|
2844
2899
|
const currentDestPaths = new Set(sourceFiles.map((f) => f.destPath));
|
|
2845
2900
|
for (const entry of existingManifest.files) {
|
|
2846
2901
|
if (currentDestPaths.has(entry.destPath)) continue;
|
|
2847
|
-
if (!
|
|
2902
|
+
if (!fs14.pathExistsSync(entry.destPath)) continue;
|
|
2848
2903
|
const content = await readText(entry.destPath);
|
|
2849
2904
|
if (!content) continue;
|
|
2850
2905
|
const hasHeader = parseMarkdownHeader(content) !== void 0;
|
|
2851
2906
|
const currentHash = hashContent(content);
|
|
2852
2907
|
if (hasHeader && currentHash === entry.hash) {
|
|
2853
|
-
if (!dryRun) await
|
|
2908
|
+
if (!dryRun) await fs14.remove(entry.destPath);
|
|
2854
2909
|
result.deleted.push(entry.destPath);
|
|
2855
2910
|
} else {
|
|
2856
2911
|
warn(`Orphaned file ${entry.destPath} was user-modified \u2014 leaving in place`);
|
|
@@ -3033,20 +3088,20 @@ async function runScan(options) {
|
|
|
3033
3088
|
}
|
|
3034
3089
|
|
|
3035
3090
|
// src/commands/undo.ts
|
|
3036
|
-
import
|
|
3037
|
-
import
|
|
3091
|
+
import path21 from "path";
|
|
3092
|
+
import fs15 from "fs-extra";
|
|
3038
3093
|
var CLAUDE_DIR = ".claude";
|
|
3039
3094
|
async function runUndo(options) {
|
|
3040
3095
|
const root = process.cwd();
|
|
3041
|
-
const targets = [
|
|
3042
|
-
const existing = targets.filter((p) =>
|
|
3096
|
+
const targets = [path21.join(root, CLAUDE_DIR), path21.join(root, HAUS_DIR)];
|
|
3097
|
+
const existing = targets.filter((p) => fs15.existsSync(p));
|
|
3043
3098
|
if (existing.length === 0) {
|
|
3044
3099
|
log("Nothing to remove: no .claude/ or .haus-workflow/ in this directory.");
|
|
3045
3100
|
return;
|
|
3046
3101
|
}
|
|
3047
3102
|
if (!options.yes) {
|
|
3048
3103
|
const ok = await confirm(
|
|
3049
|
-
`Remove ${existing.map((p) =>
|
|
3104
|
+
`Remove ${existing.map((p) => path21.relative(root, p)).join(" and ")}? This cannot be undone.`
|
|
3050
3105
|
);
|
|
3051
3106
|
if (!ok) {
|
|
3052
3107
|
log("Cancelled.");
|
|
@@ -3054,15 +3109,15 @@ async function runUndo(options) {
|
|
|
3054
3109
|
}
|
|
3055
3110
|
}
|
|
3056
3111
|
for (const p of existing) {
|
|
3057
|
-
await
|
|
3058
|
-
log(`Removed ${
|
|
3112
|
+
await fs15.remove(p);
|
|
3113
|
+
log(`Removed ${path21.relative(root, p)}`);
|
|
3059
3114
|
}
|
|
3060
3115
|
}
|
|
3061
3116
|
|
|
3062
3117
|
// src/install/uninstall.ts
|
|
3063
3118
|
import crypto3 from "crypto";
|
|
3064
|
-
import
|
|
3065
|
-
import
|
|
3119
|
+
import path22 from "path";
|
|
3120
|
+
import fs16 from "fs-extra";
|
|
3066
3121
|
async function runUninstall(options = {}) {
|
|
3067
3122
|
const { force = false } = options;
|
|
3068
3123
|
const manifest = await readManifest();
|
|
@@ -3072,7 +3127,7 @@ async function runUninstall(options = {}) {
|
|
|
3072
3127
|
return result;
|
|
3073
3128
|
}
|
|
3074
3129
|
for (const entry of manifest.files) {
|
|
3075
|
-
const exists =
|
|
3130
|
+
const exists = fs16.pathExistsSync(entry.destPath);
|
|
3076
3131
|
if (!exists) continue;
|
|
3077
3132
|
const content = await readText(entry.destPath);
|
|
3078
3133
|
if (content === void 0) continue;
|
|
@@ -3090,22 +3145,22 @@ async function runUninstall(options = {}) {
|
|
|
3090
3145
|
result.skipped.push(entry.destPath);
|
|
3091
3146
|
continue;
|
|
3092
3147
|
}
|
|
3093
|
-
await
|
|
3094
|
-
await pruneEmptyDir(
|
|
3148
|
+
await fs16.remove(entry.destPath);
|
|
3149
|
+
await pruneEmptyDir(path22.dirname(entry.destPath));
|
|
3095
3150
|
result.deleted.push(entry.destPath);
|
|
3096
3151
|
}
|
|
3097
3152
|
const settings = await readSettings();
|
|
3098
3153
|
const stripped = stripHausHooks(settings);
|
|
3099
3154
|
await writeSettings(stripped);
|
|
3100
3155
|
result.hooksStripped = true;
|
|
3101
|
-
const hausDir =
|
|
3156
|
+
const hausDir = path22.join(globalClaudeDir(), "haus");
|
|
3102
3157
|
const manifestPath = hausManifestPath();
|
|
3103
|
-
if (
|
|
3104
|
-
await
|
|
3158
|
+
if (fs16.pathExistsSync(manifestPath)) {
|
|
3159
|
+
await fs16.remove(manifestPath);
|
|
3105
3160
|
}
|
|
3106
|
-
if (
|
|
3107
|
-
const remaining = await
|
|
3108
|
-
if (remaining.length === 0) await
|
|
3161
|
+
if (fs16.pathExistsSync(hausDir)) {
|
|
3162
|
+
const remaining = await fs16.readdir(hausDir);
|
|
3163
|
+
if (remaining.length === 0) await fs16.remove(hausDir);
|
|
3109
3164
|
}
|
|
3110
3165
|
return result;
|
|
3111
3166
|
}
|
|
@@ -3124,8 +3179,8 @@ function printUninstallResult(result) {
|
|
|
3124
3179
|
}
|
|
3125
3180
|
async function pruneEmptyDir(dir) {
|
|
3126
3181
|
try {
|
|
3127
|
-
const entries = await
|
|
3128
|
-
if (entries.length === 0) await
|
|
3182
|
+
const entries = await fs16.readdir(dir);
|
|
3183
|
+
if (entries.length === 0) await fs16.remove(dir);
|
|
3129
3184
|
} catch {
|
|
3130
3185
|
}
|
|
3131
3186
|
}
|
|
@@ -3143,7 +3198,7 @@ async function runUninstallCommand(options) {
|
|
|
3143
3198
|
}
|
|
3144
3199
|
|
|
3145
3200
|
// src/commands/update.ts
|
|
3146
|
-
import
|
|
3201
|
+
import path24 from "path";
|
|
3147
3202
|
|
|
3148
3203
|
// src/update/diff-generated-files.ts
|
|
3149
3204
|
function diffGeneratedFiles() {
|
|
@@ -3170,7 +3225,7 @@ function summarizeLockDiff(before, after) {
|
|
|
3170
3225
|
|
|
3171
3226
|
// src/update/lockfile.ts
|
|
3172
3227
|
import { mkdir, readFile as readFile2, copyFile } from "fs/promises";
|
|
3173
|
-
import
|
|
3228
|
+
import path23 from "path";
|
|
3174
3229
|
async function checkLock(root) {
|
|
3175
3230
|
const lock = await readJson(hausPath(root, "haus.lock.json")) ?? [];
|
|
3176
3231
|
const hasValidVersions = lock.every(
|
|
@@ -3191,7 +3246,7 @@ async function applyLock(root) {
|
|
|
3191
3246
|
try {
|
|
3192
3247
|
const backupDir = hausPath(root, "backups");
|
|
3193
3248
|
await mkdir(backupDir, { recursive: true });
|
|
3194
|
-
await copyFile(lockPath,
|
|
3249
|
+
await copyFile(lockPath, path23.join(backupDir, `haus.lock.${Date.now()}.json`));
|
|
3195
3250
|
} catch {
|
|
3196
3251
|
}
|
|
3197
3252
|
const enriched = await Promise.all(
|
|
@@ -3213,7 +3268,7 @@ function diffLock(before, after) {
|
|
|
3213
3268
|
}
|
|
3214
3269
|
async function hasLocalOverrides(root) {
|
|
3215
3270
|
try {
|
|
3216
|
-
await readFile2(
|
|
3271
|
+
await readFile2(path23.join(root, ".claude", "settings.json"), "utf8");
|
|
3217
3272
|
return true;
|
|
3218
3273
|
} catch {
|
|
3219
3274
|
return false;
|
|
@@ -3225,7 +3280,7 @@ var NPM_PACKAGE_NAME2 = "@haus-tech/haus-workflow";
|
|
|
3225
3280
|
async function runUpdate(options) {
|
|
3226
3281
|
const root = process.cwd();
|
|
3227
3282
|
if (options.check) {
|
|
3228
|
-
const pkgJson2 = await readJson(
|
|
3283
|
+
const pkgJson2 = await readJson(path24.join(packageRoot(), "package.json"));
|
|
3229
3284
|
const currentVersion2 = pkgJson2?.version ?? "0.0.0";
|
|
3230
3285
|
const [status, npmVersion, latestCatalogTag] = await Promise.all([
|
|
3231
3286
|
checkLock(root),
|
|
@@ -3252,7 +3307,7 @@ async function runUpdate(options) {
|
|
|
3252
3307
|
if (!status.ok) process.exitCode = 1;
|
|
3253
3308
|
return;
|
|
3254
3309
|
}
|
|
3255
|
-
const pkgJson = await readJson(
|
|
3310
|
+
const pkgJson = await readJson(path24.join(packageRoot(), "package.json"));
|
|
3256
3311
|
const currentVersion = pkgJson?.version ?? "0.0.0";
|
|
3257
3312
|
const npmStatus = await fetchNpmVersionStatus(currentVersion);
|
|
3258
3313
|
if (npmStatus.updateAvailable && npmStatus.latest !== null) {
|
|
@@ -3282,14 +3337,14 @@ async function runUpdate(options) {
|
|
|
3282
3337
|
}
|
|
3283
3338
|
|
|
3284
3339
|
// src/commands/validate-catalog.ts
|
|
3285
|
-
import
|
|
3286
|
-
import
|
|
3340
|
+
import fs17 from "fs";
|
|
3341
|
+
import path26 from "path";
|
|
3287
3342
|
|
|
3288
3343
|
// src/catalog/allowed-stacks.ts
|
|
3289
|
-
import
|
|
3344
|
+
import path25 from "path";
|
|
3290
3345
|
async function readAllowedStacks(root) {
|
|
3291
3346
|
const data = await readJson(
|
|
3292
|
-
|
|
3347
|
+
path25.join(root, "library", "catalog", "allowed-stacks.json")
|
|
3293
3348
|
);
|
|
3294
3349
|
return data?.stacks ?? [];
|
|
3295
3350
|
}
|
|
@@ -3394,23 +3449,23 @@ function auditShippedFiles(manifestDir, items) {
|
|
|
3394
3449
|
const failures = [];
|
|
3395
3450
|
for (const item of items) {
|
|
3396
3451
|
if (!item.path) continue;
|
|
3397
|
-
const absPath =
|
|
3452
|
+
const absPath = path26.join(manifestDir, item.path);
|
|
3398
3453
|
if (item.type === "skill") {
|
|
3399
|
-
const skillMd =
|
|
3400
|
-
if (!
|
|
3401
|
-
failures.push(`${item.id}: missing ${
|
|
3454
|
+
const skillMd = path26.join(absPath, "SKILL.md");
|
|
3455
|
+
if (!fs17.existsSync(skillMd)) {
|
|
3456
|
+
failures.push(`${item.id}: missing ${path26.relative(manifestDir, skillMd)}`);
|
|
3402
3457
|
continue;
|
|
3403
3458
|
}
|
|
3404
|
-
const text =
|
|
3459
|
+
const text = fs17.readFileSync(skillMd, "utf8");
|
|
3405
3460
|
for (const section of REQUIRED_SKILL_SECTIONS) {
|
|
3406
3461
|
if (!text.includes(section)) failures.push(`${item.id}: SKILL.md missing ${section}`);
|
|
3407
3462
|
}
|
|
3408
3463
|
} else if (item.type === "agent") {
|
|
3409
|
-
if (!
|
|
3464
|
+
if (!fs17.existsSync(absPath)) {
|
|
3410
3465
|
failures.push(`${item.id}: missing agent file ${item.path}`);
|
|
3411
3466
|
continue;
|
|
3412
3467
|
}
|
|
3413
|
-
const text =
|
|
3468
|
+
const text = fs17.readFileSync(absPath, "utf8");
|
|
3414
3469
|
if (!text.startsWith("---")) failures.push(`${item.id}: agent file missing YAML frontmatter`);
|
|
3415
3470
|
for (const section of REQUIRED_AGENT_SECTIONS) {
|
|
3416
3471
|
if (!text.includes(section)) failures.push(`${item.id}: agent file missing ${section}`);
|
|
@@ -3421,7 +3476,7 @@ function auditShippedFiles(manifestDir, items) {
|
|
|
3421
3476
|
failures.push(`${item.id}: agent file contains disallowed phrase "${phrase}"`);
|
|
3422
3477
|
}
|
|
3423
3478
|
} else if (item.type === "template") {
|
|
3424
|
-
if (!
|
|
3479
|
+
if (!fs17.existsSync(absPath)) {
|
|
3425
3480
|
failures.push(`${item.id}: missing template file ${item.path}`);
|
|
3426
3481
|
}
|
|
3427
3482
|
}
|
|
@@ -3432,11 +3487,11 @@ function auditMarkdownContent(manifestDir) {
|
|
|
3432
3487
|
const failures = [];
|
|
3433
3488
|
const dirs = ["skills", "agents"];
|
|
3434
3489
|
for (const dir of dirs) {
|
|
3435
|
-
const abs =
|
|
3436
|
-
if (!
|
|
3490
|
+
const abs = path26.join(manifestDir, dir);
|
|
3491
|
+
if (!fs17.existsSync(abs)) continue;
|
|
3437
3492
|
walkMd(abs, (file) => {
|
|
3438
|
-
const text =
|
|
3439
|
-
const rel =
|
|
3493
|
+
const text = fs17.readFileSync(file, "utf8");
|
|
3494
|
+
const rel = path26.relative(manifestDir, file);
|
|
3440
3495
|
const lines = text.split(/\r?\n/);
|
|
3441
3496
|
for (let i = 0; i < lines.length; i++) {
|
|
3442
3497
|
const line = lines[i] ?? "";
|
|
@@ -3455,8 +3510,8 @@ function auditMarkdownContent(manifestDir) {
|
|
|
3455
3510
|
return failures;
|
|
3456
3511
|
}
|
|
3457
3512
|
function walkMd(dir, fn) {
|
|
3458
|
-
for (const entry of
|
|
3459
|
-
const full =
|
|
3513
|
+
for (const entry of fs17.readdirSync(dir, { withFileTypes: true })) {
|
|
3514
|
+
const full = path26.join(dir, entry.name);
|
|
3460
3515
|
if (entry.isDirectory()) walkMd(full, fn);
|
|
3461
3516
|
else if (entry.name.endsWith(".md")) fn(full);
|
|
3462
3517
|
}
|
|
@@ -3467,8 +3522,8 @@ async function runValidateCatalog(manifestPath) {
|
|
|
3467
3522
|
process.exitCode = 1;
|
|
3468
3523
|
return;
|
|
3469
3524
|
}
|
|
3470
|
-
const abs =
|
|
3471
|
-
const manifestDir =
|
|
3525
|
+
const abs = path26.resolve(process.cwd(), manifestPath);
|
|
3526
|
+
const manifestDir = path26.dirname(abs);
|
|
3472
3527
|
const data = await readJson(abs);
|
|
3473
3528
|
if (!data?.items) {
|
|
3474
3529
|
error(`Could not read catalog manifest at ${abs}`);
|
|
@@ -3507,7 +3562,7 @@ async function runValidateCatalog(manifestPath) {
|
|
|
3507
3562
|
}
|
|
3508
3563
|
|
|
3509
3564
|
// src/commands/workspace.ts
|
|
3510
|
-
import
|
|
3565
|
+
import path27 from "path";
|
|
3511
3566
|
import YAML from "yaml";
|
|
3512
3567
|
async function runWorkspace(action) {
|
|
3513
3568
|
if (action === "init") {
|
|
@@ -3540,7 +3595,7 @@ relationships: []
|
|
|
3540
3595
|
const summaries = [];
|
|
3541
3596
|
const ownership = {};
|
|
3542
3597
|
for (const repo of repos) {
|
|
3543
|
-
const repoRoot =
|
|
3598
|
+
const repoRoot = path27.resolve(process.cwd(), repo.path);
|
|
3544
3599
|
const result = await scanProject(repoRoot, "fast");
|
|
3545
3600
|
summaries.push({
|
|
3546
3601
|
name: repo.name,
|
|
@@ -3576,7 +3631,7 @@ ${summaries.map(
|
|
|
3576
3631
|
// src/cli.ts
|
|
3577
3632
|
function cliVersion() {
|
|
3578
3633
|
try {
|
|
3579
|
-
const pkgPath =
|
|
3634
|
+
const pkgPath = path28.join(packageRoot(), "package.json");
|
|
3580
3635
|
const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
|
|
3581
3636
|
return pkg.version ?? "0.0.0";
|
|
3582
3637
|
} catch {
|
|
@@ -3586,7 +3641,7 @@ function cliVersion() {
|
|
|
3586
3641
|
var program = new Command();
|
|
3587
3642
|
function validateRuntimeNodeVersion() {
|
|
3588
3643
|
try {
|
|
3589
|
-
const pkgPath =
|
|
3644
|
+
const pkgPath = path28.join(packageRoot(), "package.json");
|
|
3590
3645
|
const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
|
|
3591
3646
|
const requiredRange = pkg.engines?.node;
|
|
3592
3647
|
if (requiredRange && !satisfiesVersion(process.version, requiredRange)) {
|