hatch3r 1.6.0 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +259 -184
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var HATCH3R_VERSION;
|
|
|
14
14
|
var init_version = __esm({
|
|
15
15
|
"src/version.ts"() {
|
|
16
16
|
"use strict";
|
|
17
|
-
HATCH3R_VERSION = "1.6.
|
|
17
|
+
HATCH3R_VERSION = "1.6.2";
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
20
|
|
|
@@ -2181,7 +2181,9 @@ function createManifest(options) {
|
|
|
2181
2181
|
if (options.defaultBranch) {
|
|
2182
2182
|
manifest.board = createMinimalBoardConfig(owner, repo, options.defaultBranch);
|
|
2183
2183
|
}
|
|
2184
|
-
|
|
2184
|
+
const autoEnable = options.tools.some((t) => WORKTREE_CAPABLE_TOOLS.has(t));
|
|
2185
|
+
const shouldEnable = options.worktreeEnabled ?? autoEnable;
|
|
2186
|
+
if (shouldEnable) {
|
|
2185
2187
|
manifest.worktree = { enabled: true };
|
|
2186
2188
|
}
|
|
2187
2189
|
return manifest;
|
|
@@ -2399,7 +2401,7 @@ var init_resolve2 = __esm({
|
|
|
2399
2401
|
|
|
2400
2402
|
// src/adapters/canonical.ts
|
|
2401
2403
|
import { readFile as readFile9, readdir as readdir6, lstat as lstat2 } from "fs/promises";
|
|
2402
|
-
import { join as join12 } from "path";
|
|
2404
|
+
import { join as join12, relative as relative3 } from "path";
|
|
2403
2405
|
import { parse as parseYaml2 } from "yaml";
|
|
2404
2406
|
function precedenceRank(value) {
|
|
2405
2407
|
if (value && RULE_PRECEDENCE_VALUES.includes(value)) {
|
|
@@ -2414,6 +2416,16 @@ function sortByPrecedence(items) {
|
|
|
2414
2416
|
return a.id.localeCompare(b.id);
|
|
2415
2417
|
});
|
|
2416
2418
|
}
|
|
2419
|
+
function filterUserFacing(files, expectedFrontmatterType, baseDir) {
|
|
2420
|
+
return files.filter((file) => {
|
|
2421
|
+
const rel = relative3(baseDir, file.sourcePath);
|
|
2422
|
+
if (rel === "" || rel.startsWith("..")) return true;
|
|
2423
|
+
if (/^[A-Za-z]:[\\/]/.test(rel)) return true;
|
|
2424
|
+
if (rel.includes("/") || rel.includes("\\")) return false;
|
|
2425
|
+
if (file.frontmatterType && file.frontmatterType !== expectedFrontmatterType) return false;
|
|
2426
|
+
return true;
|
|
2427
|
+
});
|
|
2428
|
+
}
|
|
2417
2429
|
function classifyFsError(err) {
|
|
2418
2430
|
const code = err?.code;
|
|
2419
2431
|
if (code === "ENOENT") return "NOT_FOUND";
|
|
@@ -2460,6 +2472,7 @@ function parseFrontmatter(rawContent, typeMismatches) {
|
|
|
2460
2472
|
type: "rule",
|
|
2461
2473
|
description: ""
|
|
2462
2474
|
};
|
|
2475
|
+
let rawType;
|
|
2463
2476
|
if (parsed && typeof parsed === "object") {
|
|
2464
2477
|
const scalarFields = ["id", "type", "description"];
|
|
2465
2478
|
for (const field of scalarFields) {
|
|
@@ -2467,6 +2480,7 @@ function parseFrontmatter(rawContent, typeMismatches) {
|
|
|
2467
2480
|
if (raw === void 0) continue;
|
|
2468
2481
|
if (typeof raw === "string") {
|
|
2469
2482
|
metadata[field] = raw;
|
|
2483
|
+
if (field === "type") rawType = raw;
|
|
2470
2484
|
} else if (typeMismatches) {
|
|
2471
2485
|
typeMismatches.push(
|
|
2472
2486
|
`${field} field must be a string, got ${describeYamlType(raw)} (value: ${JSON.stringify(raw)})`
|
|
@@ -2502,7 +2516,7 @@ function parseFrontmatter(rawContent, typeMismatches) {
|
|
|
2502
2516
|
}
|
|
2503
2517
|
metadata.type = metadata.type ?? "rule";
|
|
2504
2518
|
metadata.description = metadata.description ?? "";
|
|
2505
|
-
return { metadata, content: content ?? "" };
|
|
2519
|
+
return { metadata, content: content ?? "", rawType };
|
|
2506
2520
|
}
|
|
2507
2521
|
async function readSingleMd(fullPath, fileType, fallbackId) {
|
|
2508
2522
|
let stats;
|
|
@@ -2533,11 +2547,19 @@ async function readSingleMd(fullPath, fileType, fallbackId) {
|
|
|
2533
2547
|
const errorResult = makeErrorResult(fullPath, err, "YAML_PARSE_ERROR");
|
|
2534
2548
|
return errorResult;
|
|
2535
2549
|
}
|
|
2536
|
-
const { metadata, content } = parsed;
|
|
2550
|
+
const { metadata, content, rawType } = parsed;
|
|
2537
2551
|
const id = metadata.id || metadata.name || fallbackId;
|
|
2538
2552
|
const canonical = {
|
|
2539
2553
|
id,
|
|
2540
2554
|
type: fileType,
|
|
2555
|
+
// Preserve the author-declared frontmatter `type` alongside the reader
|
|
2556
|
+
// bucket so downstream filters (see `filterUserFacing`) can distinguish
|
|
2557
|
+
// user-invocable commands/agents from companion content
|
|
2558
|
+
// (`shared-context`, `reference`, `mode`) within the same directory.
|
|
2559
|
+
// `rawType` is undefined when the author omitted `type:`, so files
|
|
2560
|
+
// without an explicit declaration fall through the filter's
|
|
2561
|
+
// back-compat path and are kept.
|
|
2562
|
+
frontmatterType: rawType,
|
|
2541
2563
|
description: metadata.description ?? "",
|
|
2542
2564
|
scope: metadata.scope,
|
|
2543
2565
|
model: metadata.model,
|
|
@@ -2959,7 +2981,7 @@ function resolveSelection(preset, projectType, teamSize, index, customSelections
|
|
|
2959
2981
|
(item) => item.protected || !item.tags.includes("greenfield") || item.tags.some((t) => t !== "greenfield" && t !== "team" && t !== "solo")
|
|
2960
2982
|
);
|
|
2961
2983
|
}
|
|
2962
|
-
if (teamSize === "solo") {
|
|
2984
|
+
if (teamSize === "solo" && preset.id !== "full") {
|
|
2963
2985
|
selected = selected.filter((item) => {
|
|
2964
2986
|
if (item.protected) return true;
|
|
2965
2987
|
if (!item.tags.includes("team") && !item.tags.includes("board")) return true;
|
|
@@ -4231,7 +4253,7 @@ var init_hooks = __esm({
|
|
|
4231
4253
|
});
|
|
4232
4254
|
|
|
4233
4255
|
// src/adapters/base.ts
|
|
4234
|
-
import { dirname as dirname7 } from "path";
|
|
4256
|
+
import { dirname as dirname7, join as join17 } from "path";
|
|
4235
4257
|
function output(path, content, managedContent) {
|
|
4236
4258
|
return { path, content, managedContent, action: "create" };
|
|
4237
4259
|
}
|
|
@@ -4345,6 +4367,27 @@ var init_base = __esm({
|
|
|
4345
4367
|
}
|
|
4346
4368
|
return files;
|
|
4347
4369
|
}
|
|
4370
|
+
/**
|
|
4371
|
+
* Read canonical commands or agents and filter to only those that should
|
|
4372
|
+
* appear in a tool's user-facing command/agent picker. Wraps
|
|
4373
|
+
* {@link readCanonicalFiles} + {@link filterUserFacing} and applies
|
|
4374
|
+
* provenance tracking only to the surviving files, so filtered-out
|
|
4375
|
+
* companion content does not pollute the adapter's source-file manifest.
|
|
4376
|
+
*
|
|
4377
|
+
* Filter rules are documented on {@link filterUserFacing}: files in
|
|
4378
|
+
* support subdirectories (`commands/board/`, `agents/modes/`, etc.) and
|
|
4379
|
+
* top-level files with a non-primary frontmatter `type:` (e.g.
|
|
4380
|
+
* `shared-context`, `reference`, `mode`) are excluded.
|
|
4381
|
+
*/
|
|
4382
|
+
async readUserFacingCanonicalFiles(agentsDir, type) {
|
|
4383
|
+
const files = await readCanonicalFiles(agentsDir, type, this.warnings);
|
|
4384
|
+
const expectedType = type === "commands" ? "command" : "agent";
|
|
4385
|
+
const filtered = filterUserFacing(files, expectedType, join17(agentsDir, type));
|
|
4386
|
+
for (const f of filtered) {
|
|
4387
|
+
if (f.sourcePath) this._trackedSourceFiles.add(f.sourcePath);
|
|
4388
|
+
}
|
|
4389
|
+
return filtered;
|
|
4390
|
+
}
|
|
4348
4391
|
/**
|
|
4349
4392
|
* Returns the raw bridge orchestration content (no surrounding headers).
|
|
4350
4393
|
* Use this when the adapter needs custom formatting around the bridge content.
|
|
@@ -4401,7 +4444,7 @@ var init_base = __esm({
|
|
|
4401
4444
|
async inlineAgents(ctx, formatModel) {
|
|
4402
4445
|
if (!ctx.features.agents) return [];
|
|
4403
4446
|
const lines = [];
|
|
4404
|
-
const agents = await this.
|
|
4447
|
+
const agents = await this.readUserFacingCanonicalFiles(ctx.agentsDir, "agents");
|
|
4405
4448
|
const minimal = this.isMinimal(ctx);
|
|
4406
4449
|
for (const agent of agents) {
|
|
4407
4450
|
const { content, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, agent);
|
|
@@ -4459,7 +4502,7 @@ ${wrapInManagedBlock(content)}`, content));
|
|
|
4459
4502
|
async processCommandsRaw(ctx, pathFn) {
|
|
4460
4503
|
if (!ctx.features.commands) return [];
|
|
4461
4504
|
const results = [];
|
|
4462
|
-
const commands = await this.
|
|
4505
|
+
const commands = await this.readUserFacingCanonicalFiles(ctx.agentsDir, "commands");
|
|
4463
4506
|
for (const cmd of commands) {
|
|
4464
4507
|
const { content, skip, warnings } = await applyCustomizationRaw(ctx.projectRoot, cmd);
|
|
4465
4508
|
this.warnings.push(...warnings);
|
|
@@ -4561,7 +4604,7 @@ var init_agentsmd = __esm({
|
|
|
4561
4604
|
sections.push("");
|
|
4562
4605
|
}
|
|
4563
4606
|
if (ctx.features.agents) {
|
|
4564
|
-
const agents = await
|
|
4607
|
+
const agents = await this.readUserFacingCanonicalFiles(ctx.agentsDir, "agents");
|
|
4565
4608
|
for (const agent of agents) {
|
|
4566
4609
|
const { content, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, agent);
|
|
4567
4610
|
this.warnings.push(...warnings);
|
|
@@ -4695,7 +4738,6 @@ var init_amazonq = __esm({
|
|
|
4695
4738
|
init_types();
|
|
4696
4739
|
init_managedBlocks();
|
|
4697
4740
|
init_base();
|
|
4698
|
-
init_canonical();
|
|
4699
4741
|
init_customization();
|
|
4700
4742
|
AmazonQAdapter = class extends BaseAdapter {
|
|
4701
4743
|
name = "amazon-q";
|
|
@@ -4720,7 +4762,7 @@ var init_amazonq = __esm({
|
|
|
4720
4762
|
const hooks = await this.readHooks(ctx);
|
|
4721
4763
|
const descriptorHooks = this.buildDescriptorHooks(hooks);
|
|
4722
4764
|
if (ctx.features.agents) {
|
|
4723
|
-
const agents = await
|
|
4765
|
+
const agents = await this.readUserFacingCanonicalFiles(ctx.agentsDir, "agents");
|
|
4724
4766
|
for (const agent of agents) {
|
|
4725
4767
|
const { content, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, agent);
|
|
4726
4768
|
this.warnings.push(...warnings);
|
|
@@ -5358,7 +5400,7 @@ ${content}`;
|
|
|
5358
5400
|
}
|
|
5359
5401
|
}
|
|
5360
5402
|
if (ctx.features.agents) {
|
|
5361
|
-
const agents = await
|
|
5403
|
+
const agents = await this.readUserFacingCanonicalFiles(ctx.agentsDir, "agents");
|
|
5362
5404
|
for (const agent of agents) {
|
|
5363
5405
|
const { content, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, agent);
|
|
5364
5406
|
this.warnings.push(...warnings);
|
|
@@ -5509,7 +5551,7 @@ var init_cline = __esm({
|
|
|
5509
5551
|
const results = [];
|
|
5510
5552
|
const customModes = [];
|
|
5511
5553
|
if (ctx.features.agents) {
|
|
5512
|
-
const agents = await
|
|
5554
|
+
const agents = await this.readUserFacingCanonicalFiles(ctx.agentsDir, "agents");
|
|
5513
5555
|
for (const agent of agents) {
|
|
5514
5556
|
const { content, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, agent);
|
|
5515
5557
|
this.warnings.push(...warnings);
|
|
@@ -5710,7 +5752,7 @@ var init_codex = __esm({
|
|
|
5710
5752
|
}
|
|
5711
5753
|
}
|
|
5712
5754
|
if (ctx.features.agents) {
|
|
5713
|
-
const agents = await
|
|
5755
|
+
const agents = await this.readUserFacingCanonicalFiles(ctx.agentsDir, "agents");
|
|
5714
5756
|
for (const agent of agents) {
|
|
5715
5757
|
const { content, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, agent);
|
|
5716
5758
|
this.warnings.push(...warnings);
|
|
@@ -5791,11 +5833,11 @@ var init_codex = __esm({
|
|
|
5791
5833
|
|
|
5792
5834
|
// src/detect/packageManager.ts
|
|
5793
5835
|
import { access as access6 } from "fs/promises";
|
|
5794
|
-
import { join as
|
|
5836
|
+
import { join as join18 } from "path";
|
|
5795
5837
|
async function detectPackageManager(rootDir) {
|
|
5796
5838
|
for (const { file, name } of LOCK_FILE_MAP) {
|
|
5797
5839
|
try {
|
|
5798
|
-
await access6(
|
|
5840
|
+
await access6(join18(rootDir, file));
|
|
5799
5841
|
return { name, ...PM_INFO[name] };
|
|
5800
5842
|
} catch (err) {
|
|
5801
5843
|
if (err.code !== "ENOENT") throw err;
|
|
@@ -5927,7 +5969,7 @@ ${wrapInManagedBlock(body)}`,
|
|
|
5927
5969
|
);
|
|
5928
5970
|
}
|
|
5929
5971
|
if (ctx.features.agents) {
|
|
5930
|
-
const agents = await
|
|
5972
|
+
const agents = await this.readUserFacingCanonicalFiles(ctx.agentsDir, "agents");
|
|
5931
5973
|
for (const agent of agents) {
|
|
5932
5974
|
const { content, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, agent);
|
|
5933
5975
|
this.warnings.push(...warnings);
|
|
@@ -6032,7 +6074,7 @@ var init_cursor = __esm({
|
|
|
6032
6074
|
}
|
|
6033
6075
|
}
|
|
6034
6076
|
if (ctx.features.agents) {
|
|
6035
|
-
const agents = await
|
|
6077
|
+
const agents = await this.readUserFacingCanonicalFiles(ctx.agentsDir, "agents");
|
|
6036
6078
|
for (const agent of agents) {
|
|
6037
6079
|
const { content, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, agent);
|
|
6038
6080
|
this.warnings.push(...warnings);
|
|
@@ -6138,6 +6180,7 @@ New to this project's agent setup? Progress through these stages:
|
|
|
6138
6180
|
});
|
|
6139
6181
|
|
|
6140
6182
|
// src/adapters/gemini.ts
|
|
6183
|
+
import { join as join19 } from "path";
|
|
6141
6184
|
function mapToGeminiEvent(event) {
|
|
6142
6185
|
const mapping = {
|
|
6143
6186
|
"pre-commit": "BeforeTool",
|
|
@@ -6208,7 +6251,8 @@ var init_gemini = __esm({
|
|
|
6208
6251
|
...await this.processSkillsRaw(ctx, (id) => `.gemini/skills/${toPrefixedId(id)}/SKILL.md`)
|
|
6209
6252
|
);
|
|
6210
6253
|
if (ctx.features.commands) {
|
|
6211
|
-
const
|
|
6254
|
+
const commandsRaw = await readCanonicalFiles(ctx.agentsDir, "commands", this.warnings);
|
|
6255
|
+
const commands = filterUserFacing(commandsRaw, "command", join19(ctx.agentsDir, "commands"));
|
|
6212
6256
|
for (const cmd of commands) {
|
|
6213
6257
|
const { content, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, cmd);
|
|
6214
6258
|
this.warnings.push(...warnings);
|
|
@@ -6242,7 +6286,7 @@ var init_goose = __esm({
|
|
|
6242
6286
|
GooseAdapter = class extends BaseAdapter {
|
|
6243
6287
|
name = "goose";
|
|
6244
6288
|
async doGenerate(ctx) {
|
|
6245
|
-
const agents = ctx.features.agents ? await
|
|
6289
|
+
const agents = ctx.features.agents ? await this.readUserFacingCanonicalFiles(ctx.agentsDir, "agents") : [];
|
|
6246
6290
|
const lines = [
|
|
6247
6291
|
...await this.bridgeHeader(ctx),
|
|
6248
6292
|
...await this.inlineRules(ctx),
|
|
@@ -6521,7 +6565,7 @@ var init_opencode = __esm({
|
|
|
6521
6565
|
}
|
|
6522
6566
|
results.push(output("opencode.json", JSON.stringify(opencodeConfig, null, 2)));
|
|
6523
6567
|
if (ctx.features.agents) {
|
|
6524
|
-
const agents = await
|
|
6568
|
+
const agents = await this.readUserFacingCanonicalFiles(ctx.agentsDir, "agents");
|
|
6525
6569
|
for (const agent of agents) {
|
|
6526
6570
|
const { content, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, agent);
|
|
6527
6571
|
this.warnings.push(...warnings);
|
|
@@ -6600,7 +6644,7 @@ var init_windsurf = __esm({
|
|
|
6600
6644
|
*/
|
|
6601
6645
|
async windsurfAgentToolPolicies(ctx) {
|
|
6602
6646
|
if (!ctx.features.agents) return [];
|
|
6603
|
-
const agents = await
|
|
6647
|
+
const agents = await this.readUserFacingCanonicalFiles(ctx.agentsDir, "agents");
|
|
6604
6648
|
const rows = [];
|
|
6605
6649
|
for (const agent of agents) {
|
|
6606
6650
|
const { skip } = await applyCustomization(ctx.projectRoot, agent);
|
|
@@ -6892,7 +6936,7 @@ var init_adapters = __esm({
|
|
|
6892
6936
|
// src/env/mcpEnv.ts
|
|
6893
6937
|
import { readFile as readFile15 } from "fs/promises";
|
|
6894
6938
|
import { existsSync } from "fs";
|
|
6895
|
-
import { join as
|
|
6939
|
+
import { join as join21 } from "path";
|
|
6896
6940
|
function getSourceEnvMcpCommand() {
|
|
6897
6941
|
return process.platform === "win32" ? SOURCE_POWERSHELL : SOURCE_POSIX;
|
|
6898
6942
|
}
|
|
@@ -6962,7 +7006,7 @@ function parseEnvFile(content) {
|
|
|
6962
7006
|
return result;
|
|
6963
7007
|
}
|
|
6964
7008
|
async function ensureGitignoreEntry(rootDir) {
|
|
6965
|
-
const gitignorePath =
|
|
7009
|
+
const gitignorePath = join21(rootDir, ".gitignore");
|
|
6966
7010
|
let content = "";
|
|
6967
7011
|
try {
|
|
6968
7012
|
content = await readFile15(gitignorePath, "utf-8");
|
|
@@ -6978,7 +7022,7 @@ async function ensureGitignoreEntry(rootDir) {
|
|
|
6978
7022
|
`);
|
|
6979
7023
|
}
|
|
6980
7024
|
async function ensureEnvMcp(rootDir, servers) {
|
|
6981
|
-
const envPath =
|
|
7025
|
+
const envPath = join21(rootDir, ENV_MCP_FILE);
|
|
6982
7026
|
const vars = collectRequiredEnvVars(servers);
|
|
6983
7027
|
if (vars.length === 0) {
|
|
6984
7028
|
return { action: "skipped", path: ENV_MCP_FILE, newVars: [] };
|
|
@@ -7016,11 +7060,11 @@ var init_mcpEnv = __esm({
|
|
|
7016
7060
|
|
|
7017
7061
|
// src/cli/shared/paths.ts
|
|
7018
7062
|
import { existsSync as existsSync2 } from "fs";
|
|
7019
|
-
import { dirname as dirname8, join as
|
|
7063
|
+
import { dirname as dirname8, join as join22 } from "path";
|
|
7020
7064
|
function findPackageRoot(startDir) {
|
|
7021
7065
|
let dir = startDir;
|
|
7022
7066
|
while (dir !== dirname8(dir)) {
|
|
7023
|
-
if (existsSync2(
|
|
7067
|
+
if (existsSync2(join22(dir, "package.json"))) return dir;
|
|
7024
7068
|
dir = dirname8(dir);
|
|
7025
7069
|
}
|
|
7026
7070
|
return startDir;
|
|
@@ -7079,7 +7123,7 @@ var init_checkpoints = __esm({
|
|
|
7079
7123
|
|
|
7080
7124
|
// src/merge/orphanCleanup.ts
|
|
7081
7125
|
import { access as access10, readFile as readFile19, unlink as unlink3 } from "fs/promises";
|
|
7082
|
-
import { basename as basename3, dirname as dirname11, relative as
|
|
7126
|
+
import { basename as basename3, dirname as dirname11, relative as relative5, resolve as resolve3 } from "path";
|
|
7083
7127
|
function isManagedOutputBasename(fileName) {
|
|
7084
7128
|
return fileName.startsWith(HATCH3R_PREFIX) || NN_HATCH3R_PREFIX_RE2.test(fileName);
|
|
7085
7129
|
}
|
|
@@ -7094,7 +7138,7 @@ function diffOrphanCandidates(previousPaths, currentPaths) {
|
|
|
7094
7138
|
}
|
|
7095
7139
|
function isPathInKnownAdapterRoot(relPath, rootDir) {
|
|
7096
7140
|
const abs = resolve3(rootDir, relPath);
|
|
7097
|
-
const rel =
|
|
7141
|
+
const rel = relative5(rootDir, abs);
|
|
7098
7142
|
if (rel.startsWith("..") || resolve3(rootDir, rel) !== abs) return false;
|
|
7099
7143
|
if (rel === "" || rel === ".") return false;
|
|
7100
7144
|
const posix4 = rel.split(/[\\/]/).join("/");
|
|
@@ -7814,7 +7858,7 @@ __export(update_exports, {
|
|
|
7814
7858
|
import { appendFile, cp as cp4, mkdir as mkdir8, readFile as readFile20, readdir as readdir11, stat as stat6 } from "fs/promises";
|
|
7815
7859
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
7816
7860
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7817
|
-
import { dirname as dirname12, join as
|
|
7861
|
+
import { dirname as dirname12, join as join26 } from "path";
|
|
7818
7862
|
import chalk7 from "chalk";
|
|
7819
7863
|
import inquirer5 from "inquirer";
|
|
7820
7864
|
async function readFileOrNull(filePath) {
|
|
@@ -7826,7 +7870,7 @@ async function readFileOrNull(filePath) {
|
|
|
7826
7870
|
}
|
|
7827
7871
|
async function appendFailure(agentsDir, phase, error2, tool) {
|
|
7828
7872
|
try {
|
|
7829
|
-
const logPath =
|
|
7873
|
+
const logPath = join26(agentsDir, FAILURE_LOG_FILE);
|
|
7830
7874
|
const entry = createFailureLogEntry(phase, error2, {
|
|
7831
7875
|
tool,
|
|
7832
7876
|
version: HATCH3R_VERSION
|
|
@@ -7855,8 +7899,8 @@ async function copyHatch3rFiles(srcDir, destDir, insideHatch3rDir = false, selec
|
|
|
7855
7899
|
throw err;
|
|
7856
7900
|
}
|
|
7857
7901
|
for (const entry of entries) {
|
|
7858
|
-
const srcPath =
|
|
7859
|
-
const destPath =
|
|
7902
|
+
const srcPath = join26(srcDir, entry.name);
|
|
7903
|
+
const destPath = join26(destDir, entry.name);
|
|
7860
7904
|
if (entry.isDirectory()) {
|
|
7861
7905
|
if (selectedIds && entry.name.startsWith(HATCH3R_PREFIX)) {
|
|
7862
7906
|
if (!selectedIds.has(entry.name)) continue;
|
|
@@ -7868,7 +7912,7 @@ async function copyHatch3rFiles(srcDir, destDir, insideHatch3rDir = false, selec
|
|
|
7868
7912
|
insideHatch3rDir || !entry.name.startsWith(HATCH3R_PREFIX),
|
|
7869
7913
|
selectedIds
|
|
7870
7914
|
);
|
|
7871
|
-
copied.push(...subCopied.map((p) =>
|
|
7915
|
+
copied.push(...subCopied.map((p) => join26(entry.name, p)));
|
|
7872
7916
|
} else if (entry.name.startsWith(HATCH3R_PREFIX) || insideHatch3rDir || ALWAYS_COPY_FILES.has(entry.name)) {
|
|
7873
7917
|
if (selectedIds && entry.name.startsWith(HATCH3R_PREFIX)) {
|
|
7874
7918
|
const baseId = entry.name.replace(/\.(md|mdc)$/, "");
|
|
@@ -7919,7 +7963,7 @@ async function runPackageUpdate(rootDir, options = {}) {
|
|
|
7919
7963
|
async function runRegenerate(rootDir, manifest, options = {}) {
|
|
7920
7964
|
const offset = options.stepOffset ?? 0;
|
|
7921
7965
|
const total = options.totalSteps ?? 3;
|
|
7922
|
-
const agentsDir =
|
|
7966
|
+
const agentsDir = join26(rootDir, AGENTS_DIR);
|
|
7923
7967
|
const contentRoot = findPackageRoot(__dirname3);
|
|
7924
7968
|
const s1 = createSpinner(step(offset + 1, total, "Updating canonical files..."));
|
|
7925
7969
|
s1.start();
|
|
@@ -7932,18 +7976,18 @@ async function runRegenerate(rootDir, manifest, options = {}) {
|
|
|
7932
7976
|
}
|
|
7933
7977
|
const copied = [];
|
|
7934
7978
|
for (const dir of CONTENT_DIRS) {
|
|
7935
|
-
const srcDir =
|
|
7979
|
+
const srcDir = join26(contentRoot, dir);
|
|
7936
7980
|
try {
|
|
7937
|
-
const dirCopied = await copyHatch3rFiles(srcDir,
|
|
7938
|
-
copied.push(...dirCopied.map((p) =>
|
|
7981
|
+
const dirCopied = await copyHatch3rFiles(srcDir, join26(agentsDir, dir), false, selectedIds);
|
|
7982
|
+
copied.push(...dirCopied.map((p) => join26(dir, p)));
|
|
7939
7983
|
} catch (err) {
|
|
7940
7984
|
if (err.code !== "ENOENT") throw err;
|
|
7941
7985
|
}
|
|
7942
7986
|
}
|
|
7943
7987
|
const canonicalAgentsMd = await generateCanonicalAgentsMd(agentsDir);
|
|
7944
|
-
await safeWriteFile(
|
|
7988
|
+
await safeWriteFile(join26(agentsDir, "AGENTS.md"), canonicalAgentsMd);
|
|
7945
7989
|
const rootAgentsMd = await generateRootAgentsMd(agentsDir);
|
|
7946
|
-
await safeWriteFile(
|
|
7990
|
+
await safeWriteFile(join26(rootDir, "AGENTS.md"), rootAgentsMd.full, {
|
|
7947
7991
|
managedContent: rootAgentsMd.inner
|
|
7948
7992
|
});
|
|
7949
7993
|
s1.succeed(step(offset + 1, total, `Updated ${copied.length} canonical files`));
|
|
@@ -7991,9 +8035,9 @@ async function runRegenerate(rootDir, manifest, options = {}) {
|
|
|
7991
8035
|
const toolPaths = [];
|
|
7992
8036
|
for (const out of outputs) {
|
|
7993
8037
|
if (options.diff) {
|
|
7994
|
-
diffBefore.set(out.path, await readFileOrNull(
|
|
8038
|
+
diffBefore.set(out.path, await readFileOrNull(join26(rootDir, out.path)));
|
|
7995
8039
|
}
|
|
7996
|
-
const fullPath =
|
|
8040
|
+
const fullPath = join26(rootDir, out.path);
|
|
7997
8041
|
if (out.managedContent) {
|
|
7998
8042
|
await safeWriteFile(fullPath, out.content, {
|
|
7999
8043
|
managedContent: out.managedContent
|
|
@@ -8004,7 +8048,7 @@ async function runRegenerate(rootDir, manifest, options = {}) {
|
|
|
8004
8048
|
addManagedFile(manifest, out.path);
|
|
8005
8049
|
toolPaths.push(out.path);
|
|
8006
8050
|
if (options.diff) {
|
|
8007
|
-
diffAfter.set(out.path, await readFileOrNull(
|
|
8051
|
+
diffAfter.set(out.path, await readFileOrNull(join26(rootDir, out.path)));
|
|
8008
8052
|
}
|
|
8009
8053
|
}
|
|
8010
8054
|
newManagedByAdapter[tool] = toolPaths;
|
|
@@ -8065,7 +8109,7 @@ async function runRegenerate(rootDir, manifest, options = {}) {
|
|
|
8065
8109
|
const wtContent = await generateWorktreeInclude(manifest, rootDir);
|
|
8066
8110
|
const wtManaged = extractManagedContent(wtContent);
|
|
8067
8111
|
await safeWriteFile(
|
|
8068
|
-
|
|
8112
|
+
join26(rootDir, WORKTREE_INCLUDE_FILE),
|
|
8069
8113
|
wtContent,
|
|
8070
8114
|
{ managedContent: wtManaged }
|
|
8071
8115
|
);
|
|
@@ -8101,7 +8145,7 @@ async function runRegenerate(rootDir, manifest, options = {}) {
|
|
|
8101
8145
|
};
|
|
8102
8146
|
}
|
|
8103
8147
|
async function runUpdateDryRun(rootDir, manifest, options = {}) {
|
|
8104
|
-
const agentsDir =
|
|
8148
|
+
const agentsDir = join26(rootDir, AGENTS_DIR);
|
|
8105
8149
|
const contentRoot = findPackageRoot(__dirname3);
|
|
8106
8150
|
let selectedIds;
|
|
8107
8151
|
if (manifest.content) {
|
|
@@ -8112,9 +8156,9 @@ async function runUpdateDryRun(rootDir, manifest, options = {}) {
|
|
|
8112
8156
|
}
|
|
8113
8157
|
const canonicalCandidates = [];
|
|
8114
8158
|
for (const dir of CONTENT_DIRS) {
|
|
8115
|
-
const srcDir =
|
|
8159
|
+
const srcDir = join26(contentRoot, dir);
|
|
8116
8160
|
const entries = await enumerateHatch3rFiles(srcDir, false, selectedIds);
|
|
8117
|
-
for (const rel of entries) canonicalCandidates.push(
|
|
8161
|
+
for (const rel of entries) canonicalCandidates.push(join26(dir, rel));
|
|
8118
8162
|
}
|
|
8119
8163
|
const adapterChanges = /* @__PURE__ */ new Map();
|
|
8120
8164
|
for (const tool of manifest.tools) {
|
|
@@ -8128,7 +8172,7 @@ async function runUpdateDryRun(rootDir, manifest, options = {}) {
|
|
|
8128
8172
|
}
|
|
8129
8173
|
const outputs = generationResult.outputs ?? [];
|
|
8130
8174
|
for (const out of outputs) {
|
|
8131
|
-
const existing = await readFileOrNull(
|
|
8175
|
+
const existing = await readFileOrNull(join26(rootDir, out.path));
|
|
8132
8176
|
if (existing === null) bucket.added.push(out.path);
|
|
8133
8177
|
else if (existing !== out.content) bucket.modified.push(out.path);
|
|
8134
8178
|
else bucket.unchanged.push(out.path);
|
|
@@ -8168,7 +8212,7 @@ async function enumerateHatch3rFiles(srcDir, insideHatch3rDir, selectedIds) {
|
|
|
8168
8212
|
}
|
|
8169
8213
|
const out = [];
|
|
8170
8214
|
for (const entry of entries) {
|
|
8171
|
-
const srcPath =
|
|
8215
|
+
const srcPath = join26(srcDir, entry.name);
|
|
8172
8216
|
if (entry.isDirectory()) {
|
|
8173
8217
|
if (selectedIds && entry.name.startsWith(HATCH3R_PREFIX)) {
|
|
8174
8218
|
if (!selectedIds.has(entry.name)) continue;
|
|
@@ -8178,7 +8222,7 @@ async function enumerateHatch3rFiles(srcDir, insideHatch3rDir, selectedIds) {
|
|
|
8178
8222
|
insideHatch3rDir || !entry.name.startsWith(HATCH3R_PREFIX),
|
|
8179
8223
|
selectedIds
|
|
8180
8224
|
);
|
|
8181
|
-
out.push(...sub.map((p) =>
|
|
8225
|
+
out.push(...sub.map((p) => join26(entry.name, p)));
|
|
8182
8226
|
} else if (entry.name.startsWith(HATCH3R_PREFIX) || insideHatch3rDir || ALWAYS_COPY_FILES.has(entry.name)) {
|
|
8183
8227
|
if (selectedIds && entry.name.startsWith(HATCH3R_PREFIX)) {
|
|
8184
8228
|
const baseId = entry.name.replace(/\.(md|mdc)$/, "");
|
|
@@ -8230,7 +8274,7 @@ async function updateCommand(_opts) {
|
|
|
8230
8274
|
for (const notice of allNotices) {
|
|
8231
8275
|
warn(notice);
|
|
8232
8276
|
}
|
|
8233
|
-
const agentsDir =
|
|
8277
|
+
const agentsDir = join26(rootDir, AGENTS_DIR);
|
|
8234
8278
|
const integrityResults = await verifyIntegrity(agentsDir);
|
|
8235
8279
|
const modified = integrityResults.filter((r) => r.status === "modified");
|
|
8236
8280
|
const missing = integrityResults.filter((r) => r.status === "missing");
|
|
@@ -8375,7 +8419,7 @@ var init_update = __esm({
|
|
|
8375
8419
|
id: "content-selections-init",
|
|
8376
8420
|
condition: async (manifest) => manifest.content === void 0,
|
|
8377
8421
|
execute: async (manifest, rootDir, headless) => {
|
|
8378
|
-
const agentsDir =
|
|
8422
|
+
const agentsDir = join26(rootDir, AGENTS_DIR);
|
|
8379
8423
|
const content = await buildSelectionsFromDisk(agentsDir);
|
|
8380
8424
|
if (headless) {
|
|
8381
8425
|
content.projectType = "brownfield";
|
|
@@ -8464,12 +8508,12 @@ var init_update = __esm({
|
|
|
8464
8508
|
{
|
|
8465
8509
|
id: "customize-yaml-size",
|
|
8466
8510
|
condition: async (_manifest, rootDir) => {
|
|
8467
|
-
const agentsDir =
|
|
8511
|
+
const agentsDir = join26(rootDir, AGENTS_DIR);
|
|
8468
8512
|
try {
|
|
8469
8513
|
const entries = await readdir11(agentsDir, { recursive: true });
|
|
8470
8514
|
for (const entry of entries) {
|
|
8471
8515
|
if (typeof entry === "string" && entry.endsWith(".customize.yaml")) {
|
|
8472
|
-
const s = await stat6(
|
|
8516
|
+
const s = await stat6(join26(agentsDir, entry));
|
|
8473
8517
|
if (s.size > 10240) return true;
|
|
8474
8518
|
}
|
|
8475
8519
|
}
|
|
@@ -8480,12 +8524,12 @@ var init_update = __esm({
|
|
|
8480
8524
|
},
|
|
8481
8525
|
execute: async (manifest, rootDir, _headless) => {
|
|
8482
8526
|
const notices = [];
|
|
8483
|
-
const agentsDir =
|
|
8527
|
+
const agentsDir = join26(rootDir, AGENTS_DIR);
|
|
8484
8528
|
try {
|
|
8485
8529
|
const entries = await readdir11(agentsDir, { recursive: true });
|
|
8486
8530
|
for (const entry of entries) {
|
|
8487
8531
|
if (typeof entry === "string" && entry.endsWith(".customize.yaml")) {
|
|
8488
|
-
const s = await stat6(
|
|
8532
|
+
const s = await stat6(join26(agentsDir, entry));
|
|
8489
8533
|
if (s.size > 10240) {
|
|
8490
8534
|
notices.push(`Large customize file detected: ${entry} (${Math.round(s.size / 1024)}KB) \u2014 consider splitting`);
|
|
8491
8535
|
}
|
|
@@ -8520,7 +8564,7 @@ var init_update = __esm({
|
|
|
8520
8564
|
const notices = [];
|
|
8521
8565
|
if (enabled) {
|
|
8522
8566
|
const wtContent = await generateWorktreeInclude(updated, rootDir);
|
|
8523
|
-
await safeWriteFile(
|
|
8567
|
+
await safeWriteFile(join26(rootDir, WORKTREE_INCLUDE_FILE), wtContent, {
|
|
8524
8568
|
appendIfNoBlock: true
|
|
8525
8569
|
});
|
|
8526
8570
|
notices.push("Worktree isolation enabled \u2014 .worktreeinclude generated");
|
|
@@ -9036,14 +9080,14 @@ init_worktree();
|
|
|
9036
9080
|
init_types();
|
|
9037
9081
|
import { access as access9, mkdir as mkdir7, readFile as readFile18 } from "fs/promises";
|
|
9038
9082
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9039
|
-
import { basename as basename2, dirname as dirname10, join as
|
|
9083
|
+
import { basename as basename2, dirname as dirname10, join as join25 } from "path";
|
|
9040
9084
|
import chalk5 from "chalk";
|
|
9041
9085
|
import inquirer3 from "inquirer";
|
|
9042
9086
|
|
|
9043
9087
|
// src/detect/repoAnalyzer.ts
|
|
9044
9088
|
init_packageManager();
|
|
9045
9089
|
import { access as access7, readFile as readFile14, readdir as readdir10 } from "fs/promises";
|
|
9046
|
-
import { join as
|
|
9090
|
+
import { join as join20 } from "path";
|
|
9047
9091
|
async function analyzeRepo(rootDir) {
|
|
9048
9092
|
const [languages, pm, isMonorepo, hasExistingAgents, existingTools, frameworks, linters, testFrameworks, ciProviders] = await Promise.all([
|
|
9049
9093
|
detectLanguages(rootDir),
|
|
@@ -9094,7 +9138,7 @@ async function detectLanguages(rootDir) {
|
|
|
9094
9138
|
};
|
|
9095
9139
|
for (const [lang, files] of Object.entries(indicators)) {
|
|
9096
9140
|
for (const file of files) {
|
|
9097
|
-
if (await pathExists(
|
|
9141
|
+
if (await pathExists(join20(rootDir, file))) {
|
|
9098
9142
|
languages.push(lang);
|
|
9099
9143
|
break;
|
|
9100
9144
|
}
|
|
@@ -9114,13 +9158,13 @@ async function detectLanguages(rootDir) {
|
|
|
9114
9158
|
return languages;
|
|
9115
9159
|
}
|
|
9116
9160
|
async function detectMonorepo(rootDir) {
|
|
9117
|
-
if (await pathExists(
|
|
9118
|
-
if (await pathExists(
|
|
9119
|
-
if (await pathExists(
|
|
9120
|
-
if (await pathExists(
|
|
9121
|
-
if (await pathExists(
|
|
9161
|
+
if (await pathExists(join20(rootDir, "pnpm-workspace.yaml"))) return true;
|
|
9162
|
+
if (await pathExists(join20(rootDir, "lerna.json"))) return true;
|
|
9163
|
+
if (await pathExists(join20(rootDir, "nx.json"))) return true;
|
|
9164
|
+
if (await pathExists(join20(rootDir, "turbo.json"))) return true;
|
|
9165
|
+
if (await pathExists(join20(rootDir, "pants.toml"))) return true;
|
|
9122
9166
|
try {
|
|
9123
|
-
const pkgJson = await readFile14(
|
|
9167
|
+
const pkgJson = await readFile14(join20(rootDir, "package.json"), "utf-8");
|
|
9124
9168
|
const pkg = JSON.parse(pkgJson);
|
|
9125
9169
|
if (pkg.workspaces) return true;
|
|
9126
9170
|
} catch (err) {
|
|
@@ -9130,11 +9174,11 @@ async function detectMonorepo(rootDir) {
|
|
|
9130
9174
|
return false;
|
|
9131
9175
|
}
|
|
9132
9176
|
async function detectExistingAgents(rootDir) {
|
|
9133
|
-
return pathExists(
|
|
9177
|
+
return pathExists(join20(rootDir, ".agents"));
|
|
9134
9178
|
}
|
|
9135
9179
|
var TOOL_INDICATORS = [
|
|
9136
9180
|
{ tool: "cursor", paths: [".cursor"] },
|
|
9137
|
-
{ tool: "copilot", paths: [
|
|
9181
|
+
{ tool: "copilot", paths: [join20(".github", "copilot-instructions.md")] },
|
|
9138
9182
|
{ tool: "claude", paths: ["CLAUDE.md", ".claude"] },
|
|
9139
9183
|
{ tool: "opencode", paths: ["opencode.json", "opencode.jsonc"] },
|
|
9140
9184
|
{ tool: "windsurf", paths: [".windsurfrules"] },
|
|
@@ -9153,7 +9197,7 @@ async function detectExistingTools(rootDir) {
|
|
|
9153
9197
|
const results = await Promise.allSettled(
|
|
9154
9198
|
TOOL_INDICATORS.map(async ({ tool, paths }) => {
|
|
9155
9199
|
for (const p of paths) {
|
|
9156
|
-
if (await pathExists(
|
|
9200
|
+
if (await pathExists(join20(rootDir, p))) return tool;
|
|
9157
9201
|
}
|
|
9158
9202
|
return null;
|
|
9159
9203
|
})
|
|
@@ -9203,7 +9247,7 @@ async function detectFrameworks(rootDir) {
|
|
|
9203
9247
|
const configResults = await Promise.allSettled(
|
|
9204
9248
|
FRAMEWORK_CONFIG_INDICATORS.map(async ({ framework, configs }) => {
|
|
9205
9249
|
for (const cfg of configs) {
|
|
9206
|
-
if (await pathExists(
|
|
9250
|
+
if (await pathExists(join20(rootDir, cfg))) return framework;
|
|
9207
9251
|
}
|
|
9208
9252
|
return null;
|
|
9209
9253
|
})
|
|
@@ -9214,7 +9258,7 @@ async function detectFrameworks(rootDir) {
|
|
|
9214
9258
|
}
|
|
9215
9259
|
}
|
|
9216
9260
|
try {
|
|
9217
|
-
const raw = await readFile14(
|
|
9261
|
+
const raw = await readFile14(join20(rootDir, "package.json"), "utf-8");
|
|
9218
9262
|
const pkg = JSON.parse(raw);
|
|
9219
9263
|
const allDeps = {
|
|
9220
9264
|
...pkg.dependencies,
|
|
@@ -9232,7 +9276,7 @@ async function detectFrameworks(rootDir) {
|
|
|
9232
9276
|
const nonJsResults = await Promise.allSettled(
|
|
9233
9277
|
NON_JS_FRAMEWORK_INDICATORS.map(async ({ framework, configs }) => {
|
|
9234
9278
|
for (const cfg of configs) {
|
|
9235
|
-
if (await pathExists(
|
|
9279
|
+
if (await pathExists(join20(rootDir, cfg))) return framework;
|
|
9236
9280
|
}
|
|
9237
9281
|
return null;
|
|
9238
9282
|
})
|
|
@@ -9268,7 +9312,7 @@ async function detectLinters(rootDir) {
|
|
|
9268
9312
|
const results = await Promise.allSettled(
|
|
9269
9313
|
LINTER_INDICATORS.map(async ({ name, configs }) => {
|
|
9270
9314
|
for (const cfg of configs) {
|
|
9271
|
-
if (await pathExists(
|
|
9315
|
+
if (await pathExists(join20(rootDir, cfg))) return name;
|
|
9272
9316
|
}
|
|
9273
9317
|
return null;
|
|
9274
9318
|
})
|
|
@@ -9300,7 +9344,7 @@ async function detectTestFrameworks(rootDir) {
|
|
|
9300
9344
|
const results = await Promise.allSettled(
|
|
9301
9345
|
TEST_FRAMEWORK_INDICATORS.map(async ({ name, configs }) => {
|
|
9302
9346
|
for (const cfg of configs) {
|
|
9303
|
-
if (await pathExists(
|
|
9347
|
+
if (await pathExists(join20(rootDir, cfg))) return name;
|
|
9304
9348
|
}
|
|
9305
9349
|
return null;
|
|
9306
9350
|
})
|
|
@@ -9329,7 +9373,7 @@ async function detectCIProviders(rootDir) {
|
|
|
9329
9373
|
const results = await Promise.allSettled(
|
|
9330
9374
|
CI_PROVIDER_INDICATORS.map(async ({ name, configs }) => {
|
|
9331
9375
|
for (const cfg of configs) {
|
|
9332
|
-
if (await pathExists(
|
|
9376
|
+
if (await pathExists(join20(rootDir, cfg))) return name;
|
|
9333
9377
|
}
|
|
9334
9378
|
return null;
|
|
9335
9379
|
})
|
|
@@ -9545,7 +9589,7 @@ init_types();
|
|
|
9545
9589
|
init_version();
|
|
9546
9590
|
init_safeWrite();
|
|
9547
9591
|
import { readFile as readFile16 } from "fs/promises";
|
|
9548
|
-
import { join as
|
|
9592
|
+
import { join as join23, normalize as normalize2, isAbsolute as isAbsolute2 } from "path";
|
|
9549
9593
|
function isUnsafeRepoPath(repoPath) {
|
|
9550
9594
|
if (repoPath.includes("\0")) return true;
|
|
9551
9595
|
if (isAbsolute2(repoPath)) return true;
|
|
@@ -9591,7 +9635,7 @@ function validateWorkspaceManifest(data) {
|
|
|
9591
9635
|
return true;
|
|
9592
9636
|
}
|
|
9593
9637
|
async function readWorkspaceManifest(rootDir) {
|
|
9594
|
-
const manifestPath =
|
|
9638
|
+
const manifestPath = join23(rootDir, AGENTS_DIR, WORKSPACE_MANIFEST_FILE);
|
|
9595
9639
|
let raw;
|
|
9596
9640
|
try {
|
|
9597
9641
|
raw = await readFile16(manifestPath, "utf-8");
|
|
@@ -9621,7 +9665,7 @@ async function readWorkspaceManifest(rootDir) {
|
|
|
9621
9665
|
return parsed;
|
|
9622
9666
|
}
|
|
9623
9667
|
async function writeWorkspaceManifest(rootDir, manifest) {
|
|
9624
|
-
const manifestPath =
|
|
9668
|
+
const manifestPath = join23(rootDir, AGENTS_DIR, WORKSPACE_MANIFEST_FILE);
|
|
9625
9669
|
await atomicWriteFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
9626
9670
|
}
|
|
9627
9671
|
function createWorkspaceManifest(name, defaults, repos, syncStrategy = "manual") {
|
|
@@ -9647,7 +9691,7 @@ init_agentsContent();
|
|
|
9647
9691
|
init_paths();
|
|
9648
9692
|
import { createHash as createHash4 } from "crypto";
|
|
9649
9693
|
import { mkdir as mkdir6, access as access8, readFile as readFile17 } from "fs/promises";
|
|
9650
|
-
import { join as
|
|
9694
|
+
import { join as join24, relative as relative4 } from "path";
|
|
9651
9695
|
import { fileURLToPath } from "url";
|
|
9652
9696
|
import { dirname as dirname9 } from "path";
|
|
9653
9697
|
init_mcpEnv();
|
|
@@ -9780,11 +9824,11 @@ async function estimateTokensForContent(contentIds, index) {
|
|
|
9780
9824
|
for (const item of items) {
|
|
9781
9825
|
try {
|
|
9782
9826
|
if (item.type === "skill") {
|
|
9783
|
-
const skillPath =
|
|
9827
|
+
const skillPath = join24(CONTENT_ROOT, item.relativePath, "SKILL.md");
|
|
9784
9828
|
const content = await readFile17(skillPath, "utf-8");
|
|
9785
9829
|
totalChars += content.length;
|
|
9786
9830
|
} else {
|
|
9787
|
-
const filePath =
|
|
9831
|
+
const filePath = join24(CONTENT_ROOT, item.relativePath);
|
|
9788
9832
|
const content = await readFile17(filePath, "utf-8");
|
|
9789
9833
|
totalChars += content.length;
|
|
9790
9834
|
}
|
|
@@ -9804,7 +9848,7 @@ async function syncWorkspaceRepos(workspaceRoot, options = {}) {
|
|
|
9804
9848
|
const protectedIds = new Set(
|
|
9805
9849
|
index.items.filter((item) => item.protected).map((item) => item.id)
|
|
9806
9850
|
);
|
|
9807
|
-
const wsAgentsDir =
|
|
9851
|
+
const wsAgentsDir = join24(workspaceRoot, AGENTS_DIR);
|
|
9808
9852
|
const integrityResults = await verifyIntegrity(wsAgentsDir);
|
|
9809
9853
|
const tampered = integrityResults.filter(
|
|
9810
9854
|
(r) => r.status === "modified" || r.status === "tampered"
|
|
@@ -9857,8 +9901,8 @@ async function syncWorkspaceRepos(workspaceRoot, options = {}) {
|
|
|
9857
9901
|
return { repos: results };
|
|
9858
9902
|
}
|
|
9859
9903
|
async function syncSingleRepo(workspaceRoot, wsManifest, wsChecksum, repoEntry, index, protectedIds, options) {
|
|
9860
|
-
const repoDir =
|
|
9861
|
-
const repoAgentsDir =
|
|
9904
|
+
const repoDir = join24(workspaceRoot, repoEntry.path);
|
|
9905
|
+
const repoAgentsDir = join24(repoDir, AGENTS_DIR);
|
|
9862
9906
|
try {
|
|
9863
9907
|
await access8(repoDir);
|
|
9864
9908
|
} catch {
|
|
@@ -9900,7 +9944,7 @@ async function syncSingleRepo(workspaceRoot, wsManifest, wsChecksum, repoEntry,
|
|
|
9900
9944
|
}
|
|
9901
9945
|
}
|
|
9902
9946
|
const canonicalAgentsMd = await generateCanonicalAgentsMd(repoAgentsDir);
|
|
9903
|
-
await safeWriteFile(
|
|
9947
|
+
await safeWriteFile(join24(repoAgentsDir, "AGENTS.md"), canonicalAgentsMd, { force: true });
|
|
9904
9948
|
const repoInfo = await analyzeRepo(repoDir);
|
|
9905
9949
|
let gitOwner = repoEntry.owner ?? "";
|
|
9906
9950
|
let gitRepo = repoEntry.repo ?? "";
|
|
@@ -9932,7 +9976,7 @@ async function syncSingleRepo(workspaceRoot, wsManifest, wsChecksum, repoEntry,
|
|
|
9932
9976
|
languages: repoInfo.languages
|
|
9933
9977
|
});
|
|
9934
9978
|
manifest.workspace = {
|
|
9935
|
-
rootPath:
|
|
9979
|
+
rootPath: relative4(repoDir, workspaceRoot),
|
|
9936
9980
|
lastSync: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9937
9981
|
syncVersion: HATCH3R_VERSION,
|
|
9938
9982
|
workspaceChecksum: wsChecksum,
|
|
@@ -9944,7 +9988,7 @@ async function syncSingleRepo(workspaceRoot, wsManifest, wsChecksum, repoEntry,
|
|
|
9944
9988
|
}
|
|
9945
9989
|
await writeManifest(repoDir, manifest);
|
|
9946
9990
|
const rootAgentsMd = await generateRootAgentsMd(repoAgentsDir);
|
|
9947
|
-
await safeWriteFile(
|
|
9991
|
+
await safeWriteFile(join24(repoDir, "AGENTS.md"), rootAgentsMd.full, {
|
|
9948
9992
|
managedContent: rootAgentsMd.inner,
|
|
9949
9993
|
appendIfNoBlock: true
|
|
9950
9994
|
});
|
|
@@ -9958,7 +10002,7 @@ async function syncSingleRepo(workspaceRoot, wsManifest, wsChecksum, repoEntry,
|
|
|
9958
10002
|
options.onWarn?.(w);
|
|
9959
10003
|
}
|
|
9960
10004
|
for (const out of outputs) {
|
|
9961
|
-
await safeWriteFile(
|
|
10005
|
+
await safeWriteFile(join24(repoDir, out.path), out.content, {
|
|
9962
10006
|
managedContent: out.managedContent,
|
|
9963
10007
|
appendIfNoBlock: true
|
|
9964
10008
|
});
|
|
@@ -10095,8 +10139,8 @@ async function runInit(options) {
|
|
|
10095
10139
|
}
|
|
10096
10140
|
}
|
|
10097
10141
|
async function runInitInner(options) {
|
|
10098
|
-
const { rootDir, platform, owner, repo, namespace, project, defaultBranch, tools, features, mcpServers, repoInfo, contentSelection } = options;
|
|
10099
|
-
const agentsDir =
|
|
10142
|
+
const { rootDir, platform, owner, repo, namespace, project, defaultBranch, tools, features, mcpServers, repoInfo, contentSelection, worktreeEnabled } = options;
|
|
10143
|
+
const agentsDir = join25(rootDir, AGENTS_DIR);
|
|
10100
10144
|
const totalSteps = 4;
|
|
10101
10145
|
const s1 = createSpinner(step(1, totalSteps, "Creating canonical files..."));
|
|
10102
10146
|
s1.start();
|
|
@@ -10114,8 +10158,8 @@ async function runInitInner(options) {
|
|
|
10114
10158
|
}
|
|
10115
10159
|
}
|
|
10116
10160
|
}
|
|
10117
|
-
await mkdir7(
|
|
10118
|
-
const learningsReadmePath =
|
|
10161
|
+
await mkdir7(join25(agentsDir, "learnings"), { recursive: true });
|
|
10162
|
+
const learningsReadmePath = join25(agentsDir, "learnings", "README.md");
|
|
10119
10163
|
try {
|
|
10120
10164
|
await access9(learningsReadmePath);
|
|
10121
10165
|
} catch (err) {
|
|
@@ -10125,7 +10169,7 @@ async function runInitInner(options) {
|
|
|
10125
10169
|
throw err;
|
|
10126
10170
|
}
|
|
10127
10171
|
}
|
|
10128
|
-
const mcpPath =
|
|
10172
|
+
const mcpPath = join25(agentsDir, "mcp", "mcp.json");
|
|
10129
10173
|
try {
|
|
10130
10174
|
const mcpRaw = await readFile18(mcpPath, "utf-8");
|
|
10131
10175
|
const mcpParsed = JSON.parse(mcpRaw);
|
|
@@ -10149,18 +10193,18 @@ async function runInitInner(options) {
|
|
|
10149
10193
|
if (!isExpected) throw err;
|
|
10150
10194
|
}
|
|
10151
10195
|
const canonicalAgentsMd = await generateCanonicalAgentsMd(agentsDir);
|
|
10152
|
-
await safeWriteFile(
|
|
10196
|
+
await safeWriteFile(join25(agentsDir, "AGENTS.md"), canonicalAgentsMd, { force: true });
|
|
10153
10197
|
s1.succeed(step(1, totalSteps, `Canonical files created (${countSelectionItems(contentSelection)} items)`));
|
|
10154
10198
|
const s2 = createSpinner(step(2, totalSteps, "Preparing manifest..."));
|
|
10155
10199
|
s2.start();
|
|
10156
|
-
const manifest = createManifest({ platform, owner, repo, namespace, project, defaultBranch, tools, features, mcpServers, content: contentSelection, languages: repoInfo.languages });
|
|
10200
|
+
const manifest = createManifest({ platform, owner, repo, namespace, project, defaultBranch, tools, features, mcpServers, content: contentSelection, languages: repoInfo.languages, worktreeEnabled });
|
|
10157
10201
|
s2.succeed(step(2, totalSteps, "Manifest prepared"));
|
|
10158
10202
|
const s3 = createSpinner(
|
|
10159
10203
|
step(3, totalSteps, `Generating ${tools.map((t) => TOOL_DISPLAY_NAMES[t] ?? t).join(", ")} output...`)
|
|
10160
10204
|
);
|
|
10161
10205
|
s3.start();
|
|
10162
10206
|
const rootAgentsMd = await generateRootAgentsMd(agentsDir);
|
|
10163
|
-
await safeWriteFile(
|
|
10207
|
+
await safeWriteFile(join25(rootDir, "AGENTS.md"), rootAgentsMd.full, {
|
|
10164
10208
|
managedContent: rootAgentsMd.inner,
|
|
10165
10209
|
appendIfNoBlock: true
|
|
10166
10210
|
});
|
|
@@ -10176,7 +10220,7 @@ async function runInitInner(options) {
|
|
|
10176
10220
|
}
|
|
10177
10221
|
const toolPaths = [];
|
|
10178
10222
|
for (const out of outputs) {
|
|
10179
|
-
await safeWriteFile(
|
|
10223
|
+
await safeWriteFile(join25(rootDir, out.path), out.content, {
|
|
10180
10224
|
managedContent: out.managedContent,
|
|
10181
10225
|
appendIfNoBlock: true
|
|
10182
10226
|
});
|
|
@@ -10207,14 +10251,10 @@ async function runInitInner(options) {
|
|
|
10207
10251
|
warn(w);
|
|
10208
10252
|
}
|
|
10209
10253
|
}
|
|
10210
|
-
const hasWorktreeTool = tools.some((t) => WORKTREE_CAPABLE_TOOLS.has(t));
|
|
10211
|
-
if (hasWorktreeTool) {
|
|
10212
|
-
manifest.worktree = manifest.worktree ?? { enabled: true };
|
|
10213
|
-
}
|
|
10214
10254
|
if (manifest.worktree?.enabled) {
|
|
10215
10255
|
const wtContent = await generateWorktreeInclude(manifest, rootDir);
|
|
10216
10256
|
const wtManaged = extractManagedContent(wtContent);
|
|
10217
|
-
await safeWriteFile(
|
|
10257
|
+
await safeWriteFile(join25(rootDir, WORKTREE_INCLUDE_FILE), wtContent, {
|
|
10218
10258
|
managedContent: wtManaged,
|
|
10219
10259
|
appendIfNoBlock: true
|
|
10220
10260
|
});
|
|
@@ -10274,7 +10314,7 @@ async function runInitInner(options) {
|
|
|
10274
10314
|
printBox("Hatch complete", summaryLines, "success");
|
|
10275
10315
|
}
|
|
10276
10316
|
async function checkExisting(rootDir, skipPrompt, newSelection) {
|
|
10277
|
-
const hatchJsonPath =
|
|
10317
|
+
const hatchJsonPath = join25(rootDir, AGENTS_DIR, "hatch.json");
|
|
10278
10318
|
try {
|
|
10279
10319
|
await access9(hatchJsonPath);
|
|
10280
10320
|
if (!skipPrompt) {
|
|
@@ -10400,6 +10440,7 @@ async function initCommand(opts = {}) {
|
|
|
10400
10440
|
} else {
|
|
10401
10441
|
tools2 = DEFAULT_TOOLS;
|
|
10402
10442
|
}
|
|
10443
|
+
const worktreeEnabled2 = opts.worktree ?? tools2.some((t) => WORKTREE_CAPABLE_TOOLS.has(t));
|
|
10403
10444
|
const features2 = { ...DEFAULT_FEATURES };
|
|
10404
10445
|
const platformMcp = PLATFORM_MCP_SERVER[platform2];
|
|
10405
10446
|
const mcpServers2 = features2.mcp ? Array.from(/* @__PURE__ */ new Set([platformMcp, ...DEFAULT_MCP.filter((s) => s !== "github")])) : [];
|
|
@@ -10418,7 +10459,7 @@ async function initCommand(opts = {}) {
|
|
|
10418
10459
|
}
|
|
10419
10460
|
warnBoardPrerequisites(contentSelection2);
|
|
10420
10461
|
await checkExisting(rootDir, true, contentSelection2);
|
|
10421
|
-
await runInit({ rootDir, platform: platform2, owner: owner2, repo: repo2, namespace: namespace2, project: project2, defaultBranch: defaultBranch2, tools: tools2, features: features2, mcpServers: mcpServers2, repoInfo, contentSelection: contentSelection2 });
|
|
10462
|
+
await runInit({ rootDir, platform: platform2, owner: owner2, repo: repo2, namespace: namespace2, project: project2, defaultBranch: defaultBranch2, tools: tools2, features: features2, mcpServers: mcpServers2, repoInfo, contentSelection: contentSelection2, worktreeEnabled: worktreeEnabled2 });
|
|
10422
10463
|
return;
|
|
10423
10464
|
}
|
|
10424
10465
|
console.log();
|
|
@@ -10571,6 +10612,21 @@ async function initCommand(opts = {}) {
|
|
|
10571
10612
|
}
|
|
10572
10613
|
]);
|
|
10573
10614
|
const tools = toolAnswers.tools.length > 0 ? toolAnswers.tools : DEFAULT_TOOLS;
|
|
10615
|
+
const hasWorktreeTool = tools.some((t) => WORKTREE_CAPABLE_TOOLS.has(t));
|
|
10616
|
+
let worktreeEnabled;
|
|
10617
|
+
if (opts.worktree !== void 0) {
|
|
10618
|
+
worktreeEnabled = opts.worktree;
|
|
10619
|
+
} else if (hasWorktreeTool) {
|
|
10620
|
+
const wtAnswer = await inquirer3.prompt([{
|
|
10621
|
+
type: "confirm",
|
|
10622
|
+
name: "enabled",
|
|
10623
|
+
message: "Enable worktree file isolation (for parallel agent sessions)?",
|
|
10624
|
+
default: true
|
|
10625
|
+
}]);
|
|
10626
|
+
worktreeEnabled = wtAnswer.enabled;
|
|
10627
|
+
} else {
|
|
10628
|
+
worktreeEnabled = false;
|
|
10629
|
+
}
|
|
10574
10630
|
const secretNotes = tools.map((t) => TOOL_SECRET_NOTES[t]).filter(Boolean);
|
|
10575
10631
|
if (secretNotes.length > 0) {
|
|
10576
10632
|
info(chalk5.dim("MCP secret loading by tool:"));
|
|
@@ -10621,7 +10677,7 @@ async function initCommand(opts = {}) {
|
|
|
10621
10677
|
}
|
|
10622
10678
|
warnBoardPrerequisites(contentSelection);
|
|
10623
10679
|
await checkExisting(rootDir, false, contentSelection);
|
|
10624
|
-
await runInit({ rootDir, platform, owner, repo, namespace, project, defaultBranch, tools, features, mcpServers, repoInfo, contentSelection });
|
|
10680
|
+
await runInit({ rootDir, platform, owner, repo, namespace, project, defaultBranch, tools, features, mcpServers, repoInfo, contentSelection, worktreeEnabled });
|
|
10625
10681
|
}
|
|
10626
10682
|
async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
10627
10683
|
const headless = !!opts.yes;
|
|
@@ -10649,7 +10705,7 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
10649
10705
|
}
|
|
10650
10706
|
const enriched = detectedRepos.map((r) => ({
|
|
10651
10707
|
...r,
|
|
10652
|
-
...detectRepoGitIdentity(
|
|
10708
|
+
...detectRepoGitIdentity(join25(rootDir, r.path))
|
|
10653
10709
|
}));
|
|
10654
10710
|
wsSpinner.succeed(`Workspace: ${detectedRepos.length} repo(s) detected`);
|
|
10655
10711
|
console.log();
|
|
@@ -10706,8 +10762,10 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
10706
10762
|
let features;
|
|
10707
10763
|
let mcpServers;
|
|
10708
10764
|
let contentSelection;
|
|
10765
|
+
let worktreeEnabled;
|
|
10709
10766
|
if (headless) {
|
|
10710
10767
|
tools = resolveToolsFromOpts(opts.tools, repoInfo);
|
|
10768
|
+
worktreeEnabled = opts.worktree ?? tools.some((t) => WORKTREE_CAPABLE_TOOLS.has(t));
|
|
10711
10769
|
features = { ...DEFAULT_FEATURES };
|
|
10712
10770
|
const platformMcp = PLATFORM_MCP_SERVER[platform];
|
|
10713
10771
|
mcpServers = features.mcp ? Array.from(/* @__PURE__ */ new Set([platformMcp, ...DEFAULT_MCP.filter((s) => s !== "github")])) : [];
|
|
@@ -10803,6 +10861,20 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
10803
10861
|
}
|
|
10804
10862
|
]);
|
|
10805
10863
|
tools = toolAnswers.tools.length > 0 ? toolAnswers.tools : DEFAULT_TOOLS;
|
|
10864
|
+
const wsHasWorktreeTool = tools.some((t) => WORKTREE_CAPABLE_TOOLS.has(t));
|
|
10865
|
+
if (opts.worktree !== void 0) {
|
|
10866
|
+
worktreeEnabled = opts.worktree;
|
|
10867
|
+
} else if (wsHasWorktreeTool) {
|
|
10868
|
+
const wsWtAnswer = await inquirer3.prompt([{
|
|
10869
|
+
type: "confirm",
|
|
10870
|
+
name: "enabled",
|
|
10871
|
+
message: "Enable worktree file isolation (for parallel agent sessions)?",
|
|
10872
|
+
default: true
|
|
10873
|
+
}]);
|
|
10874
|
+
worktreeEnabled = wsWtAnswer.enabled;
|
|
10875
|
+
} else {
|
|
10876
|
+
worktreeEnabled = false;
|
|
10877
|
+
}
|
|
10806
10878
|
const wsSecretNotes = tools.map((t) => TOOL_SECRET_NOTES[t]).filter(Boolean);
|
|
10807
10879
|
if (wsSecretNotes.length > 0) {
|
|
10808
10880
|
info(chalk5.dim("MCP secret loading by tool:"));
|
|
@@ -10866,7 +10938,8 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
10866
10938
|
features,
|
|
10867
10939
|
mcpServers,
|
|
10868
10940
|
repoInfo,
|
|
10869
|
-
contentSelection
|
|
10941
|
+
contentSelection,
|
|
10942
|
+
worktreeEnabled
|
|
10870
10943
|
});
|
|
10871
10944
|
let repoEntries;
|
|
10872
10945
|
if (headless) {
|
|
@@ -10995,7 +11068,8 @@ function captureConfig(manifest) {
|
|
|
10995
11068
|
projectType: "brownfield",
|
|
10996
11069
|
teamSize: "solo",
|
|
10997
11070
|
items: { agents: [], skills: [], rules: [], commands: [], prompts: [], hooks: [], githubAgents: [] }
|
|
10998
|
-
}
|
|
11071
|
+
},
|
|
11072
|
+
worktreeEnabled: manifest.worktree?.enabled ?? false
|
|
10999
11073
|
};
|
|
11000
11074
|
}
|
|
11001
11075
|
function printInventory(inventory) {
|
|
@@ -11130,7 +11204,8 @@ async function cleanCommand(opts = {}) {
|
|
|
11130
11204
|
features: config.features,
|
|
11131
11205
|
mcpServers: config.mcpServers,
|
|
11132
11206
|
repoInfo,
|
|
11133
|
-
contentSelection: config.contentSelection
|
|
11207
|
+
contentSelection: config.contentSelection,
|
|
11208
|
+
worktreeEnabled: config.worktreeEnabled
|
|
11134
11209
|
};
|
|
11135
11210
|
await runInit(initOpts);
|
|
11136
11211
|
if (learningsBackup) {
|
|
@@ -11191,7 +11266,7 @@ init_archive();
|
|
|
11191
11266
|
init_paths();
|
|
11192
11267
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
11193
11268
|
import { readFile as readFile21 } from "fs/promises";
|
|
11194
|
-
import { dirname as dirname13, join as
|
|
11269
|
+
import { dirname as dirname13, join as join27 } from "path";
|
|
11195
11270
|
import chalk8 from "chalk";
|
|
11196
11271
|
import inquirer6 from "inquirer";
|
|
11197
11272
|
init_content();
|
|
@@ -11426,7 +11501,7 @@ async function configCommand() {
|
|
|
11426
11501
|
);
|
|
11427
11502
|
console.log();
|
|
11428
11503
|
const contentRoot = findPackageRoot(__dirname4);
|
|
11429
|
-
const agentsDir =
|
|
11504
|
+
const agentsDir = join27(rootDir, AGENTS_DIR);
|
|
11430
11505
|
const index = await buildContentIndex(contentRoot);
|
|
11431
11506
|
const previousContent = manifest.content;
|
|
11432
11507
|
const { projectType, teamSize } = manifest.content;
|
|
@@ -11480,7 +11555,7 @@ async function configCommand() {
|
|
|
11480
11555
|
const keepItem = index.byId.get(keepId);
|
|
11481
11556
|
if (!keepItem) continue;
|
|
11482
11557
|
try {
|
|
11483
|
-
const filePath = keepItem.type === "skill" ?
|
|
11558
|
+
const filePath = keepItem.type === "skill" ? join27(agentsDir, keepItem.relativePath, "SKILL.md") : join27(agentsDir, keepItem.relativePath);
|
|
11484
11559
|
const content = await readFile21(filePath, "utf-8");
|
|
11485
11560
|
const refs = extractContentReferences(content);
|
|
11486
11561
|
if (refs.includes(removedId)) {
|
|
@@ -11528,9 +11603,9 @@ async function configCommand() {
|
|
|
11528
11603
|
contentMetadataChanged = previousContent.preset !== newSelection.preset || previousContent.projectType !== newSelection.projectType || previousContent.teamSize !== newSelection.teamSize;
|
|
11529
11604
|
if (contentChanges.added.length > 0 || contentChanges.removed.length > 0) {
|
|
11530
11605
|
const canonicalAgentsMd = await generateCanonicalAgentsMd(agentsDir);
|
|
11531
|
-
await safeWriteFile(
|
|
11606
|
+
await safeWriteFile(join27(agentsDir, "AGENTS.md"), canonicalAgentsMd);
|
|
11532
11607
|
const rootAgentsMd = await generateRootAgentsMd(agentsDir);
|
|
11533
|
-
await safeWriteFile(
|
|
11608
|
+
await safeWriteFile(join27(rootDir, "AGENTS.md"), rootAgentsMd.full, {
|
|
11534
11609
|
managedContent: rootAgentsMd.inner
|
|
11535
11610
|
});
|
|
11536
11611
|
}
|
|
@@ -11598,7 +11673,7 @@ async function configCommand() {
|
|
|
11598
11673
|
if (manifest.worktree?.enabled) {
|
|
11599
11674
|
const wtContent = await generateWorktreeInclude(manifest, rootDir);
|
|
11600
11675
|
const wtManaged = extractManagedContent(wtContent);
|
|
11601
|
-
await safeWriteFile(
|
|
11676
|
+
await safeWriteFile(join27(rootDir, WORKTREE_INCLUDE_FILE), wtContent, {
|
|
11602
11677
|
managedContent: wtManaged
|
|
11603
11678
|
});
|
|
11604
11679
|
}
|
|
@@ -11764,7 +11839,7 @@ async function configCommand() {
|
|
|
11764
11839
|
]);
|
|
11765
11840
|
if (editIdentity === "detect") {
|
|
11766
11841
|
for (const repo2 of wsManifestFinal.repos) {
|
|
11767
|
-
const identity = detectRepoGitIdentity(
|
|
11842
|
+
const identity = detectRepoGitIdentity(join27(rootDir, repo2.path));
|
|
11768
11843
|
repo2.owner = identity.owner || void 0;
|
|
11769
11844
|
repo2.repo = identity.repo || void 0;
|
|
11770
11845
|
repo2.defaultBranch = identity.defaultBranch || void 0;
|
|
@@ -11863,7 +11938,7 @@ async function configCommand() {
|
|
|
11863
11938
|
init_hatchJson();
|
|
11864
11939
|
init_adapters();
|
|
11865
11940
|
import { appendFile as appendFile2, readFile as readFile23, stat as stat7, readdir as readdir12 } from "fs/promises";
|
|
11866
|
-
import { join as
|
|
11941
|
+
import { join as join29 } from "path";
|
|
11867
11942
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
11868
11943
|
import chalk9 from "chalk";
|
|
11869
11944
|
|
|
@@ -11925,10 +12000,10 @@ init_integrity();
|
|
|
11925
12000
|
// src/integrity/provenance.ts
|
|
11926
12001
|
init_safeWrite();
|
|
11927
12002
|
import { readFile as readFile22 } from "fs/promises";
|
|
11928
|
-
import { join as
|
|
12003
|
+
import { join as join28, relative as relative6, posix as posix2, sep as sep2 } from "path";
|
|
11929
12004
|
var PROVENANCE_FILE = ".provenance.json";
|
|
11930
12005
|
function normalizeToRepoPath(absPath, rootDir) {
|
|
11931
|
-
const rel =
|
|
12006
|
+
const rel = relative6(rootDir, absPath);
|
|
11932
12007
|
return sep2 === "/" ? rel : rel.split(sep2).join(posix2.sep);
|
|
11933
12008
|
}
|
|
11934
12009
|
function buildProvenanceManifest(hatchVersion, rootDir, perAdapterOutputs) {
|
|
@@ -11956,7 +12031,7 @@ function buildProvenanceManifest(hatchVersion, rootDir, perAdapterOutputs) {
|
|
|
11956
12031
|
};
|
|
11957
12032
|
}
|
|
11958
12033
|
async function writeProvenanceManifest(agentsDir, manifest) {
|
|
11959
|
-
const filePath =
|
|
12034
|
+
const filePath = join28(agentsDir, PROVENANCE_FILE);
|
|
11960
12035
|
await atomicWriteFile(filePath, JSON.stringify(manifest, null, 2) + "\n");
|
|
11961
12036
|
}
|
|
11962
12037
|
|
|
@@ -11972,7 +12047,7 @@ init_phaseOutputSchema();
|
|
|
11972
12047
|
init_retryWithBackoff();
|
|
11973
12048
|
init_ui();
|
|
11974
12049
|
async function checkSpecFreshness(rootDir) {
|
|
11975
|
-
const specsDir =
|
|
12050
|
+
const specsDir = join29(rootDir, "docs", "specs");
|
|
11976
12051
|
try {
|
|
11977
12052
|
await stat7(specsDir);
|
|
11978
12053
|
} catch {
|
|
@@ -11984,7 +12059,7 @@ async function checkSpecFreshness(rootDir) {
|
|
|
11984
12059
|
for (const entry of entries) {
|
|
11985
12060
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
11986
12061
|
const parentPath = entry.parentPath ?? entry.path ?? specsDir;
|
|
11987
|
-
const fileStat = await stat7(
|
|
12062
|
+
const fileStat = await stat7(join29(parentPath, entry.name));
|
|
11988
12063
|
if (fileStat.mtimeMs < oldestSpecMtime) {
|
|
11989
12064
|
oldestSpecMtime = fileStat.mtimeMs;
|
|
11990
12065
|
}
|
|
@@ -12008,7 +12083,7 @@ async function checkSpecFreshness(rootDir) {
|
|
|
12008
12083
|
}
|
|
12009
12084
|
async function appendFailure2(agentsDir, phase, error2, tool) {
|
|
12010
12085
|
try {
|
|
12011
|
-
const logPath =
|
|
12086
|
+
const logPath = join29(agentsDir, FAILURE_LOG_FILE);
|
|
12012
12087
|
const entry = createFailureLogEntry(phase, error2, {
|
|
12013
12088
|
tool,
|
|
12014
12089
|
version: HATCH3R_VERSION
|
|
@@ -12048,7 +12123,7 @@ async function syncCommand(opts = {}) {
|
|
|
12048
12123
|
`This repository appears to be managed by a workspace at ${wsContext.workspaceRoot ?? ".."}. Run ${chalk9.cyan("hatch3r sync")} from the workspace root to sync all repos.`
|
|
12049
12124
|
);
|
|
12050
12125
|
}
|
|
12051
|
-
const agentsDir =
|
|
12126
|
+
const agentsDir = join29(rootDir, AGENTS_DIR);
|
|
12052
12127
|
const manifest = await readManifest(rootDir);
|
|
12053
12128
|
if (!manifest) {
|
|
12054
12129
|
error("No .agents/hatch.json found.");
|
|
@@ -12092,8 +12167,8 @@ async function syncCommand(opts = {}) {
|
|
|
12092
12167
|
const diffBefore = /* @__PURE__ */ new Map();
|
|
12093
12168
|
const diffAfter = /* @__PURE__ */ new Map();
|
|
12094
12169
|
if (opts.diff) {
|
|
12095
|
-
diffBefore.set("AGENTS.md", await readFileOrNull2(
|
|
12096
|
-
diffBefore.set(`${AGENTS_DIR}/AGENTS.md`, await readFileOrNull2(
|
|
12170
|
+
diffBefore.set("AGENTS.md", await readFileOrNull2(join29(rootDir, "AGENTS.md")));
|
|
12171
|
+
diffBefore.set(`${AGENTS_DIR}/AGENTS.md`, await readFileOrNull2(join29(agentsDir, "AGENTS.md")));
|
|
12097
12172
|
}
|
|
12098
12173
|
const s1 = createSpinner(step(++currentStep, totalSteps, "Syncing AGENTS.md..."));
|
|
12099
12174
|
s1.start();
|
|
@@ -12107,18 +12182,18 @@ async function syncCommand(opts = {}) {
|
|
|
12107
12182
|
diffAfter.set(`${AGENTS_DIR}/AGENTS.md`, canonicalAgentsMd);
|
|
12108
12183
|
}
|
|
12109
12184
|
} else {
|
|
12110
|
-
const agentsMdResult = await safeWriteFile(
|
|
12185
|
+
const agentsMdResult = await safeWriteFile(join29(rootDir, "AGENTS.md"), rootAgentsMd.full, {
|
|
12111
12186
|
managedContent: rootAgentsMd.inner
|
|
12112
12187
|
});
|
|
12113
12188
|
if (agentsMdResult.warning) warn(agentsMdResult.warning);
|
|
12114
12189
|
results.push({ path: "AGENTS.md", action: agentsMdResult.action });
|
|
12115
12190
|
const canonicalAgentsMd = await generateCanonicalAgentsMd(agentsDir);
|
|
12116
|
-
const canonicalResult = await safeWriteFile(
|
|
12191
|
+
const canonicalResult = await safeWriteFile(join29(agentsDir, "AGENTS.md"), canonicalAgentsMd);
|
|
12117
12192
|
if (canonicalResult.warning) warn(canonicalResult.warning);
|
|
12118
12193
|
results.push({ path: `${AGENTS_DIR}/AGENTS.md`, action: canonicalResult.action });
|
|
12119
12194
|
if (opts.diff) {
|
|
12120
|
-
diffAfter.set("AGENTS.md", await readFileOrNull2(
|
|
12121
|
-
diffAfter.set(`${AGENTS_DIR}/AGENTS.md`, await readFileOrNull2(
|
|
12195
|
+
diffAfter.set("AGENTS.md", await readFileOrNull2(join29(rootDir, "AGENTS.md")));
|
|
12196
|
+
diffAfter.set(`${AGENTS_DIR}/AGENTS.md`, await readFileOrNull2(join29(agentsDir, "AGENTS.md")));
|
|
12122
12197
|
}
|
|
12123
12198
|
}
|
|
12124
12199
|
s1.succeed(step(currentStep, totalSteps, opts.dryRun ? "AGENTS.md (dry run)" : "AGENTS.md synced"));
|
|
@@ -12199,16 +12274,16 @@ async function syncCommand(opts = {}) {
|
|
|
12199
12274
|
for (const out of outputs) {
|
|
12200
12275
|
results.push({ path: out.path, action: "dry-run" });
|
|
12201
12276
|
if (opts.diff) {
|
|
12202
|
-
diffBefore.set(out.path, await readFileOrNull2(
|
|
12277
|
+
diffBefore.set(out.path, await readFileOrNull2(join29(rootDir, out.path)));
|
|
12203
12278
|
diffAfter.set(out.path, out.content);
|
|
12204
12279
|
}
|
|
12205
12280
|
}
|
|
12206
12281
|
} else {
|
|
12207
12282
|
for (const out of outputs) {
|
|
12208
12283
|
if (opts.diff) {
|
|
12209
|
-
diffBefore.set(out.path, await readFileOrNull2(
|
|
12284
|
+
diffBefore.set(out.path, await readFileOrNull2(join29(rootDir, out.path)));
|
|
12210
12285
|
}
|
|
12211
|
-
const fullPath =
|
|
12286
|
+
const fullPath = join29(rootDir, out.path);
|
|
12212
12287
|
if (out.managedContent) {
|
|
12213
12288
|
const result = await safeWriteFile(fullPath, out.content, {
|
|
12214
12289
|
managedContent: out.managedContent
|
|
@@ -12223,7 +12298,7 @@ async function syncCommand(opts = {}) {
|
|
|
12223
12298
|
results.push({ path: out.path, action: result.action });
|
|
12224
12299
|
}
|
|
12225
12300
|
if (opts.diff) {
|
|
12226
|
-
diffAfter.set(out.path, await readFileOrNull2(
|
|
12301
|
+
diffAfter.set(out.path, await readFileOrNull2(join29(rootDir, out.path)));
|
|
12227
12302
|
}
|
|
12228
12303
|
}
|
|
12229
12304
|
}
|
|
@@ -12285,7 +12360,7 @@ async function syncCommand(opts = {}) {
|
|
|
12285
12360
|
const wtContent = await generateWorktreeInclude(m, rootDir);
|
|
12286
12361
|
const wtManaged = extractManagedContent(wtContent);
|
|
12287
12362
|
const wtResult = await safeWriteFile(
|
|
12288
|
-
|
|
12363
|
+
join29(rootDir, WORKTREE_INCLUDE_FILE),
|
|
12289
12364
|
wtContent,
|
|
12290
12365
|
{ managedContent: wtManaged }
|
|
12291
12366
|
);
|
|
@@ -12346,7 +12421,7 @@ async function syncCommand(opts = {}) {
|
|
|
12346
12421
|
const CUSTOMIZE_DIRS = ["agents", "commands", "skills", "rules"];
|
|
12347
12422
|
for (const dir of CUSTOMIZE_DIRS) {
|
|
12348
12423
|
try {
|
|
12349
|
-
const files = await readdir12(
|
|
12424
|
+
const files = await readdir12(join29(rootDir, ".hatch3r", dir));
|
|
12350
12425
|
for (const f of files.filter((f2) => f2.endsWith(".customize.yaml") || f2.endsWith(".customize.md"))) {
|
|
12351
12426
|
const itemId = f.replace(/\.customize\.(yaml|md)$/, "");
|
|
12352
12427
|
const prefixed = `hatch3r-${itemId}`;
|
|
@@ -12460,7 +12535,7 @@ init_content();
|
|
|
12460
12535
|
init_paths();
|
|
12461
12536
|
import { readdir as readdir15, readFile as readFile26, access as access11 } from "fs/promises";
|
|
12462
12537
|
import { existsSync as existsSync3 } from "fs";
|
|
12463
|
-
import { dirname as dirname15, join as
|
|
12538
|
+
import { dirname as dirname15, join as join32, posix as posix3 } from "path";
|
|
12464
12539
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
12465
12540
|
import chalk10 from "chalk";
|
|
12466
12541
|
import { parse as parseYaml4 } from "yaml";
|
|
@@ -12468,7 +12543,7 @@ import { parse as parseYaml4 } from "yaml";
|
|
|
12468
12543
|
// src/content/learningsValidation.ts
|
|
12469
12544
|
init_customization();
|
|
12470
12545
|
import { readFile as readFile24, readdir as readdir13 } from "fs/promises";
|
|
12471
|
-
import { join as
|
|
12546
|
+
import { join as join30 } from "path";
|
|
12472
12547
|
var MAX_LEARNING_FILE_BYTES = 65536;
|
|
12473
12548
|
var MAX_LEARNINGS_TOTAL_BYTES = 524288;
|
|
12474
12549
|
var MAX_LEARNING_FILE_COUNT = 50;
|
|
@@ -12572,7 +12647,7 @@ async function validateLearningsDirectory(learningsDir) {
|
|
|
12572
12647
|
const nameErrors = validateLearningFileName(file);
|
|
12573
12648
|
errors.push(...nameErrors);
|
|
12574
12649
|
try {
|
|
12575
|
-
const content = await readFile24(
|
|
12650
|
+
const content = await readFile24(join30(learningsDir, file), "utf-8");
|
|
12576
12651
|
const byteLength = Buffer.byteLength(content, "utf-8");
|
|
12577
12652
|
totalBytes += byteLength;
|
|
12578
12653
|
const result = validateLearningContent(content, file);
|
|
@@ -12743,7 +12818,7 @@ function detectSecrets(envVars) {
|
|
|
12743
12818
|
// src/pipeline/complianceVerification.ts
|
|
12744
12819
|
init_agentToolAllowlist();
|
|
12745
12820
|
import { readFile as readFile25, readdir as readdir14 } from "fs/promises";
|
|
12746
|
-
import { dirname as dirname14, join as
|
|
12821
|
+
import { dirname as dirname14, join as join31 } from "path";
|
|
12747
12822
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
12748
12823
|
|
|
12749
12824
|
// src/pipeline/reviewLoop.ts
|
|
@@ -12782,9 +12857,9 @@ var RESILIENCE_MODULES = [
|
|
|
12782
12857
|
var __dirname5 = dirname14(fileURLToPath5(import.meta.url));
|
|
12783
12858
|
async function resolveCommandsDir() {
|
|
12784
12859
|
const candidates = [
|
|
12785
|
-
|
|
12786
|
-
|
|
12787
|
-
|
|
12860
|
+
join31(__dirname5, "..", "cli", "commands"),
|
|
12861
|
+
join31(__dirname5, "..", "..", "src", "cli", "commands"),
|
|
12862
|
+
join31(__dirname5, "..", "cli")
|
|
12788
12863
|
];
|
|
12789
12864
|
for (const candidate of candidates) {
|
|
12790
12865
|
try {
|
|
@@ -12805,7 +12880,7 @@ async function detectResilienceInvocations() {
|
|
|
12805
12880
|
} catch {
|
|
12806
12881
|
return invoked;
|
|
12807
12882
|
}
|
|
12808
|
-
const files = entries.filter((e) => typeof e === "string" && (e.endsWith(".ts") || e.endsWith(".js"))).map((e) =>
|
|
12883
|
+
const files = entries.filter((e) => typeof e === "string" && (e.endsWith(".ts") || e.endsWith(".js"))).map((e) => join31(commandsDir, e));
|
|
12809
12884
|
const patterns = {};
|
|
12810
12885
|
for (const mod of RESILIENCE_MODULES) {
|
|
12811
12886
|
patterns[mod] = new RegExp(`pipeline/${mod}(?:\\.js)?["']`);
|
|
@@ -12998,7 +13073,7 @@ async function validateManifest2(rootDir, manifest, result) {
|
|
|
12998
13073
|
if (!manifest.tools || manifest.tools.length === 0) result.warnings.push("hatch.json: no tools configured");
|
|
12999
13074
|
for (const managedFile of manifest.managedFiles ?? []) {
|
|
13000
13075
|
try {
|
|
13001
|
-
await access11(
|
|
13076
|
+
await access11(join32(rootDir, managedFile));
|
|
13002
13077
|
} catch (err) {
|
|
13003
13078
|
if (err.code !== "ENOENT") throw err;
|
|
13004
13079
|
result.warnings.push(`Managed file missing from disk: ${managedFile}`);
|
|
@@ -13010,7 +13085,7 @@ async function validateDirectories(agentsDir, result) {
|
|
|
13010
13085
|
const optionalDirs = ["commands", "prompts", "mcp", "policy", "github-agents"];
|
|
13011
13086
|
for (const dir of requiredDirs) {
|
|
13012
13087
|
try {
|
|
13013
|
-
await access11(
|
|
13088
|
+
await access11(join32(agentsDir, dir));
|
|
13014
13089
|
} catch (err) {
|
|
13015
13090
|
if (err.code !== "ENOENT") throw err;
|
|
13016
13091
|
result.errors.push(`Required directory missing: .agents/${dir}/`);
|
|
@@ -13018,7 +13093,7 @@ async function validateDirectories(agentsDir, result) {
|
|
|
13018
13093
|
}
|
|
13019
13094
|
for (const dir of optionalDirs) {
|
|
13020
13095
|
try {
|
|
13021
|
-
await access11(
|
|
13096
|
+
await access11(join32(agentsDir, dir));
|
|
13022
13097
|
} catch (err) {
|
|
13023
13098
|
if (err.code !== "ENOENT") throw err;
|
|
13024
13099
|
result.warnings.push(`Optional directory missing: .agents/${dir}/`);
|
|
@@ -13029,12 +13104,12 @@ async function validateFrontmatter(agentsDir, result) {
|
|
|
13029
13104
|
const requiredDirs = ["agents", "skills", "rules"];
|
|
13030
13105
|
const optionalDirs = ["commands", "prompts", "mcp", "policy", "github-agents"];
|
|
13031
13106
|
for (const dir of [...requiredDirs, ...optionalDirs]) {
|
|
13032
|
-
const dirPath =
|
|
13107
|
+
const dirPath = join32(agentsDir, dir);
|
|
13033
13108
|
try {
|
|
13034
13109
|
const entries = await readdir15(dirPath, { withFileTypes: true });
|
|
13035
13110
|
for (const entry of entries) {
|
|
13036
13111
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
13037
|
-
const filePath =
|
|
13112
|
+
const filePath = join32(dirPath, entry.name);
|
|
13038
13113
|
const content = await readFile26(filePath, "utf-8");
|
|
13039
13114
|
if (!content.startsWith("---")) {
|
|
13040
13115
|
result.warnings.push(`Missing frontmatter: .agents/${dir}/${entry.name}`);
|
|
@@ -13057,7 +13132,7 @@ async function validateFrontmatter(agentsDir, result) {
|
|
|
13057
13132
|
}
|
|
13058
13133
|
}
|
|
13059
13134
|
} else if (entry.isDirectory()) {
|
|
13060
|
-
const skillPath =
|
|
13135
|
+
const skillPath = join32(dirPath, entry.name, "SKILL.md");
|
|
13061
13136
|
try {
|
|
13062
13137
|
await access11(skillPath);
|
|
13063
13138
|
} catch (err) {
|
|
@@ -13071,7 +13146,7 @@ async function validateFrontmatter(agentsDir, result) {
|
|
|
13071
13146
|
}
|
|
13072
13147
|
}
|
|
13073
13148
|
try {
|
|
13074
|
-
await access11(
|
|
13149
|
+
await access11(join32(agentsDir, "AGENTS.md"));
|
|
13075
13150
|
} catch (err) {
|
|
13076
13151
|
if (err.code !== "ENOENT") throw err;
|
|
13077
13152
|
result.warnings.push("Missing .agents/AGENTS.md");
|
|
@@ -13138,7 +13213,7 @@ async function validateManagedFilePrefixes(manifest, result) {
|
|
|
13138
13213
|
}
|
|
13139
13214
|
async function validateHooks(agentsDir, manifest, result) {
|
|
13140
13215
|
if (!manifest.features.hooks) return;
|
|
13141
|
-
const hooksDir =
|
|
13216
|
+
const hooksDir = join32(agentsDir, "hooks");
|
|
13142
13217
|
try {
|
|
13143
13218
|
const hookFiles = await readdir15(hooksDir);
|
|
13144
13219
|
const mdHooks = hookFiles.filter((f) => f.endsWith(".md"));
|
|
@@ -13147,13 +13222,13 @@ async function validateHooks(agentsDir, manifest, result) {
|
|
|
13147
13222
|
}
|
|
13148
13223
|
let agentFiles;
|
|
13149
13224
|
try {
|
|
13150
|
-
const agentEntries = await readdir15(
|
|
13225
|
+
const agentEntries = await readdir15(join32(agentsDir, "agents"));
|
|
13151
13226
|
agentFiles = new Set(agentEntries.filter((f) => f.endsWith(".md")));
|
|
13152
13227
|
} catch (err) {
|
|
13153
13228
|
if (err.code !== "ENOENT") throw err;
|
|
13154
13229
|
}
|
|
13155
13230
|
for (const hookFile of mdHooks) {
|
|
13156
|
-
const hookContent = await readFile26(
|
|
13231
|
+
const hookContent = await readFile26(join32(hooksDir, hookFile), "utf-8");
|
|
13157
13232
|
if (!hookContent.startsWith("---")) {
|
|
13158
13233
|
result.warnings.push(`Hook missing frontmatter: .agents/hooks/${hookFile}`);
|
|
13159
13234
|
continue;
|
|
@@ -13185,7 +13260,7 @@ async function validateHooks(agentsDir, manifest, result) {
|
|
|
13185
13260
|
}
|
|
13186
13261
|
async function validateMcp(agentsDir, manifest, result) {
|
|
13187
13262
|
if (!manifest.features.mcp || manifest.mcp.servers.length === 0) return;
|
|
13188
|
-
const mcpPath =
|
|
13263
|
+
const mcpPath = join32(agentsDir, "mcp", "mcp.json");
|
|
13189
13264
|
try {
|
|
13190
13265
|
const mcpContent = await readFile26(mcpPath, "utf-8");
|
|
13191
13266
|
const mcpParsed = JSON.parse(mcpContent);
|
|
@@ -13242,7 +13317,7 @@ async function validateCustomizeYaml(rootDir, result) {
|
|
|
13242
13317
|
enabled: "boolean"
|
|
13243
13318
|
};
|
|
13244
13319
|
for (const { dir } of CUSTOMIZATION_TYPES) {
|
|
13245
|
-
const customDir =
|
|
13320
|
+
const customDir = join32(rootDir, ".hatch3r", dir);
|
|
13246
13321
|
let files;
|
|
13247
13322
|
try {
|
|
13248
13323
|
files = await readdir15(customDir);
|
|
@@ -13252,7 +13327,7 @@ async function validateCustomizeYaml(rootDir, result) {
|
|
|
13252
13327
|
}
|
|
13253
13328
|
const yamlFiles = files.filter((f) => f.endsWith(".customize.yaml"));
|
|
13254
13329
|
for (const file of yamlFiles) {
|
|
13255
|
-
const filePath =
|
|
13330
|
+
const filePath = join32(customDir, file);
|
|
13256
13331
|
const itemId = file.replace(".customize.yaml", "");
|
|
13257
13332
|
let raw;
|
|
13258
13333
|
try {
|
|
@@ -13319,13 +13394,13 @@ async function validateCustomizeYaml(rootDir, result) {
|
|
|
13319
13394
|
}
|
|
13320
13395
|
async function validateCustomizations(rootDir, agentsDir, manifest, result) {
|
|
13321
13396
|
for (const { dir, canonical } of CUSTOMIZATION_TYPES) {
|
|
13322
|
-
const customDir =
|
|
13397
|
+
const customDir = join32(rootDir, ".hatch3r", dir);
|
|
13323
13398
|
try {
|
|
13324
13399
|
const customFiles = await readdir15(customDir);
|
|
13325
13400
|
for (const file of customFiles) {
|
|
13326
13401
|
if (file.endsWith(".customize.yaml")) {
|
|
13327
13402
|
const itemId = file.replace(".customize.yaml", "");
|
|
13328
|
-
const canonicalPath = canonical === "skills" ?
|
|
13403
|
+
const canonicalPath = canonical === "skills" ? join32(agentsDir, canonical, itemId) : join32(agentsDir, canonical, `${itemId}.md`);
|
|
13329
13404
|
try {
|
|
13330
13405
|
await access11(canonicalPath);
|
|
13331
13406
|
} catch (err) {
|
|
@@ -13353,7 +13428,7 @@ async function validateContentConsistency(rootDir, agentsDir, manifest, result)
|
|
|
13353
13428
|
for (const [key, cfg] of Object.entries(contentDirs)) {
|
|
13354
13429
|
const ids = manifest.content.items[key];
|
|
13355
13430
|
for (const id of ids) {
|
|
13356
|
-
const checkPath = cfg.strategy === "subdir" ?
|
|
13431
|
+
const checkPath = cfg.strategy === "subdir" ? join32(agentsDir, cfg.dir, id, "SKILL.md") : join32(agentsDir, cfg.dir, `${id}.md`);
|
|
13357
13432
|
try {
|
|
13358
13433
|
await access11(checkPath);
|
|
13359
13434
|
} catch {
|
|
@@ -13366,7 +13441,7 @@ async function validateContentConsistency(rootDir, agentsDir, manifest, result)
|
|
|
13366
13441
|
for (const id of ids) allContentIds.add(id);
|
|
13367
13442
|
}
|
|
13368
13443
|
for (const { dir } of CUSTOMIZATION_TYPES) {
|
|
13369
|
-
const customDir =
|
|
13444
|
+
const customDir = join32(rootDir, ".hatch3r", dir);
|
|
13370
13445
|
try {
|
|
13371
13446
|
const files = await readdir15(customDir);
|
|
13372
13447
|
for (const f of files.filter((f2) => f2.endsWith(".customize.yaml") || f2.endsWith(".customize.md"))) {
|
|
@@ -13380,7 +13455,7 @@ async function validateContentConsistency(rootDir, agentsDir, manifest, result)
|
|
|
13380
13455
|
}
|
|
13381
13456
|
}
|
|
13382
13457
|
}
|
|
13383
|
-
const learningsDir =
|
|
13458
|
+
const learningsDir = join32(agentsDir, "learnings");
|
|
13384
13459
|
const learningsResult = await validateLearningsDirectory(learningsDir);
|
|
13385
13460
|
for (const e of learningsResult.errors) {
|
|
13386
13461
|
result.errors.push(e);
|
|
@@ -13500,12 +13575,12 @@ async function validateDocsCounts(rootDir) {
|
|
|
13500
13575
|
let checked = 0;
|
|
13501
13576
|
const actual = {};
|
|
13502
13577
|
const dirs = [
|
|
13503
|
-
["adapters",
|
|
13504
|
-
["commands",
|
|
13505
|
-
["agents",
|
|
13506
|
-
["skills",
|
|
13507
|
-
["rules",
|
|
13508
|
-
["hooks",
|
|
13578
|
+
["adapters", join32(rootDir, "src/adapters"), (e) => e.endsWith(".ts") && !["base.ts", "index.ts", "canonical.ts", "customization.ts", "types.ts", "mcp-utils.ts", "toml-utils.ts", "contextBudget.ts", "agentsmd.ts"].includes(e)],
|
|
13579
|
+
["commands", join32(rootDir, "src/cli/commands"), (e) => e.endsWith(".ts")],
|
|
13580
|
+
["agents", join32(rootDir, "agents"), (e) => e.endsWith(".md")],
|
|
13581
|
+
["skills", join32(rootDir, "skills"), (e) => true],
|
|
13582
|
+
["rules", join32(rootDir, "rules"), (e) => e.endsWith(".md")],
|
|
13583
|
+
["hooks", join32(rootDir, "hooks"), (e) => e.endsWith(".md")]
|
|
13509
13584
|
];
|
|
13510
13585
|
for (const [name, dir, filter] of dirs) {
|
|
13511
13586
|
try {
|
|
@@ -13519,7 +13594,7 @@ async function validateDocsCounts(rootDir) {
|
|
|
13519
13594
|
actual[name] = 0;
|
|
13520
13595
|
}
|
|
13521
13596
|
}
|
|
13522
|
-
const readmePath =
|
|
13597
|
+
const readmePath = join32(rootDir, "README.md");
|
|
13523
13598
|
try {
|
|
13524
13599
|
const readme = await readFile26(readmePath, "utf-8");
|
|
13525
13600
|
const countPatterns = [
|
|
@@ -13593,11 +13668,11 @@ async function validateCommand(opts) {
|
|
|
13593
13668
|
}
|
|
13594
13669
|
return;
|
|
13595
13670
|
}
|
|
13596
|
-
const agentsDir =
|
|
13671
|
+
const agentsDir = join32(rootDir, AGENTS_DIR);
|
|
13597
13672
|
const result = { errors: [], warnings: [] };
|
|
13598
13673
|
const spinner = jsonMode ? null : createSpinner("Validating .agents/ structure...");
|
|
13599
13674
|
spinner?.start();
|
|
13600
|
-
const cwdIsFrameworkSource = existsSync3(
|
|
13675
|
+
const cwdIsFrameworkSource = existsSync3(join32(rootDir, "agents")) && existsSync3(join32(rootDir, "skills")) && existsSync3(join32(rootDir, "rules")) && existsSync3(join32(rootDir, "commands"));
|
|
13601
13676
|
try {
|
|
13602
13677
|
await access11(agentsDir);
|
|
13603
13678
|
} catch (err) {
|
|
@@ -13752,7 +13827,7 @@ async function validateCommand(opts) {
|
|
|
13752
13827
|
let hasCustomizations = false;
|
|
13753
13828
|
for (const { dir } of CUSTOMIZATION_TYPES) {
|
|
13754
13829
|
try {
|
|
13755
|
-
const files = await readdir15(
|
|
13830
|
+
const files = await readdir15(join32(rootDir, ".hatch3r", dir));
|
|
13756
13831
|
if (files.some((f) => f.endsWith(".customize.yaml") || f.endsWith(".customize.md"))) {
|
|
13757
13832
|
hasCustomizations = true;
|
|
13758
13833
|
break;
|
|
@@ -13818,7 +13893,7 @@ async function validateCommand(opts) {
|
|
|
13818
13893
|
}
|
|
13819
13894
|
}
|
|
13820
13895
|
async function validateEnvMcpSecrets(rootDir, result) {
|
|
13821
|
-
const envMcpPath =
|
|
13896
|
+
const envMcpPath = join32(rootDir, ".env.mcp");
|
|
13822
13897
|
if (!existsSync3(envMcpPath)) return;
|
|
13823
13898
|
try {
|
|
13824
13899
|
const raw = await readFile26(envMcpPath, "utf-8");
|
|
@@ -13838,7 +13913,7 @@ async function validateEnvMcpSecrets(rootDir, result) {
|
|
|
13838
13913
|
async function validateCanonicalDescriptionQuality(rootDir, result) {
|
|
13839
13914
|
const __filename = fileURLToPath6(import.meta.url);
|
|
13840
13915
|
const packageRoot = findPackageRoot(dirname15(__filename));
|
|
13841
|
-
const canonicalRoot = existsSync3(
|
|
13916
|
+
const canonicalRoot = existsSync3(join32(rootDir, "agents")) && existsSync3(join32(rootDir, "skills")) && existsSync3(join32(rootDir, "rules")) && existsSync3(join32(rootDir, "commands")) ? rootDir : packageRoot;
|
|
13842
13917
|
try {
|
|
13843
13918
|
const index = await buildContentIndex(canonicalRoot);
|
|
13844
13919
|
if (index.items.length === 0) return;
|
|
@@ -13884,7 +13959,7 @@ init_pipelineTimeout();
|
|
|
13884
13959
|
init_phaseOutputSchema();
|
|
13885
13960
|
init_retryWithBackoff();
|
|
13886
13961
|
init_ui();
|
|
13887
|
-
import { join as
|
|
13962
|
+
import { join as join33 } from "path";
|
|
13888
13963
|
import chalk11 from "chalk";
|
|
13889
13964
|
async function runVerifyPass(agentsDir) {
|
|
13890
13965
|
const results = await verifyIntegrity(agentsDir);
|
|
@@ -13938,7 +14013,7 @@ async function verifyCommand(options = {}) {
|
|
|
13938
14013
|
DEFAULT_PIPELINE_TIMEOUT_MS
|
|
13939
14014
|
);
|
|
13940
14015
|
const rootDir = process.cwd();
|
|
13941
|
-
const agentsDir =
|
|
14016
|
+
const agentsDir = join33(rootDir, AGENTS_DIR);
|
|
13942
14017
|
const spinner = createSpinner("Verifying file integrity...");
|
|
13943
14018
|
spinner.start();
|
|
13944
14019
|
const manifest = await readIntegrityManifest(agentsDir);
|
|
@@ -14075,7 +14150,7 @@ init_managedBlocks();
|
|
|
14075
14150
|
init_integrity();
|
|
14076
14151
|
init_ui();
|
|
14077
14152
|
import { access as access12, readFile as readFile27, readdir as readdir16, stat as stat8 } from "fs/promises";
|
|
14078
|
-
import { join as
|
|
14153
|
+
import { join as join34 } from "path";
|
|
14079
14154
|
import chalk12 from "chalk";
|
|
14080
14155
|
async function dirCharCount(dir) {
|
|
14081
14156
|
let total = 0;
|
|
@@ -14086,7 +14161,7 @@ async function dirCharCount(dir) {
|
|
|
14086
14161
|
return 0;
|
|
14087
14162
|
}
|
|
14088
14163
|
for (const entry of entries) {
|
|
14089
|
-
const fullPath =
|
|
14164
|
+
const fullPath = join34(dir, entry.name);
|
|
14090
14165
|
if (entry.isDirectory()) {
|
|
14091
14166
|
total += await dirCharCount(fullPath);
|
|
14092
14167
|
} else if (entry.isFile()) {
|
|
@@ -14122,7 +14197,7 @@ async function runFastStatusCheck(rootDir, agentsDir, manifest) {
|
|
|
14122
14197
|
verbose(`${tool}: ${paths.length} output path(s) to check (fast path)`);
|
|
14123
14198
|
fileLines.push(chalk12.bold(`${tool}:`));
|
|
14124
14199
|
for (const p of paths) {
|
|
14125
|
-
const destPath =
|
|
14200
|
+
const destPath = join34(rootDir, p);
|
|
14126
14201
|
try {
|
|
14127
14202
|
const fileStat = await stat8(destPath);
|
|
14128
14203
|
if (fileStat.mtimeMs > sealMs) {
|
|
@@ -14150,7 +14225,7 @@ async function runDeepStatusCheck(rootDir, agentsDir, manifest) {
|
|
|
14150
14225
|
verbose(`${tool}: ${outputs.length} output file(s) to check`);
|
|
14151
14226
|
fileLines.push(chalk12.bold(`${tool}:`));
|
|
14152
14227
|
for (const out of outputs) {
|
|
14153
|
-
const destPath =
|
|
14228
|
+
const destPath = join34(rootDir, out.path);
|
|
14154
14229
|
try {
|
|
14155
14230
|
const existing = await readFile27(destPath, "utf-8");
|
|
14156
14231
|
const existingBlock = extractManagedBlock(existing);
|
|
@@ -14175,7 +14250,7 @@ async function statusCommand(opts) {
|
|
|
14175
14250
|
setVerbose(!!opts?.verbose);
|
|
14176
14251
|
printBanner(true);
|
|
14177
14252
|
const rootDir = process.cwd();
|
|
14178
|
-
const agentsDir =
|
|
14253
|
+
const agentsDir = join34(rootDir, AGENTS_DIR);
|
|
14179
14254
|
const manifest = await readManifest(rootDir);
|
|
14180
14255
|
if (!manifest) {
|
|
14181
14256
|
error("No .agents/hatch.json found.");
|
|
@@ -14228,7 +14303,7 @@ async function statusCommand(opts) {
|
|
|
14228
14303
|
console.log();
|
|
14229
14304
|
}
|
|
14230
14305
|
if (manifest.tools.includes("codex")) {
|
|
14231
|
-
const overridePath =
|
|
14306
|
+
const overridePath = join34(rootDir, "AGENTS.override.md");
|
|
14232
14307
|
try {
|
|
14233
14308
|
await access12(overridePath);
|
|
14234
14309
|
warn(
|
|
@@ -14319,7 +14394,7 @@ function createProgram() {
|
|
|
14319
14394
|
program2.command("init").description("Install a complete agent setup into the current repo (first-run: creates .agents/ directory)").option(
|
|
14320
14395
|
"--tools <tools>",
|
|
14321
14396
|
`Comma-separated tools (${TOOL_CHOICES})`
|
|
14322
|
-
).option("--yes", "Skip interactive prompts, use defaults").option("--quick", "Skip all prompts and use smart defaults (alias for --yes)").option("--default", "Skip all prompts and use smart defaults (alias for --yes)").option("--preset <preset>", "Content preset: minimal, standard, full (default: full)").option("--project-type <type>", "Project type: greenfield, brownfield").option("--team-size <size>", "Team size: solo, team").option("--workspace", "Initialize as a multi-repo workspace").action(initCommand);
|
|
14397
|
+
).option("--yes", "Skip interactive prompts, use defaults").option("--quick", "Skip all prompts and use smart defaults (alias for --yes)").option("--default", "Skip all prompts and use smart defaults (alias for --yes)").option("--preset <preset>", "Content preset: minimal, standard, full (default: full)").option("--project-type <type>", "Project type: greenfield, brownfield").option("--team-size <size>", "Team size: solo, team").option("--worktree", "Enable git worktree file isolation (overrides tool auto-detect)").option("--no-worktree", "Disable git worktree file isolation").option("--workspace", "Initialize as a multi-repo workspace").action(initCommand);
|
|
14323
14398
|
program2.command("sync").description("Re-generate tool outputs from canonical .agents/ state (run after editing .agents/)").option("--repos [paths...]", "Sync workspace content to sub-repos (all opted-in if no paths given)").option("--dry-run", "Show what would change without modifying files").option("--diff", "Show a before/after diff summary for each generated file").option("--force", "Overwrite locally modified files in sub-repos").option("--minimal", "Generate stripped-down output (no comments, minimal formatting) to reduce token usage").option("--strict-budget", "Fail sync if any adapter's generated output exceeds its context budget (default: warn)").option("--verbose", "Show detailed output for each file processed").action(syncCommand);
|
|
14324
14399
|
program2.command("status").description("Check sync status between canonical .agents/ and generated files").option("--verbose", "Show detailed per-file status information").option("--deep", "Regenerate every adapter's output in-memory to compare byte-for-byte (slower; default uses integrity-manifest fast path)").action(statusCommand);
|
|
14325
14400
|
program2.command("update").description("Pull latest hatch3r templates with safe merge (preserves customizations)").option("--yes", "Skip interactive prompts, use defaults").option("--diff", "Show a before/after diff summary for each generated file").option("--force", "Override the preflight integrity check and proceed despite drift").option("--offline, --skip-fetch", "Skip the package fetch step; regenerate only from already-installed canonical content").option("--dry-run", "Preview what would change (added/modified/unchanged per adapter) without writing files").action(updateCommand);
|