hatch3r 2.0.0 → 2.1.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/README.md +1 -0
- package/dist/cli/index.js +783 -422
- package/package.json +4 -2
package/dist/cli/index.js
CHANGED
|
@@ -18,15 +18,15 @@ var __export = (target, all) => {
|
|
|
18
18
|
// src/version.ts
|
|
19
19
|
function resolveVersion() {
|
|
20
20
|
try {
|
|
21
|
-
return "2.
|
|
21
|
+
return "2.1.0";
|
|
22
22
|
} catch (defineMissing) {
|
|
23
23
|
void defineMissing;
|
|
24
24
|
try {
|
|
25
25
|
const { readFileSync: readFileSync5 } = __require("fs");
|
|
26
26
|
const { fileURLToPath: fileURLToPath11 } = __require("url");
|
|
27
|
-
const { dirname: dirname28, resolve:
|
|
27
|
+
const { dirname: dirname28, resolve: resolve10 } = __require("path");
|
|
28
28
|
const here = fileURLToPath11(import.meta.url);
|
|
29
|
-
const pkgPath =
|
|
29
|
+
const pkgPath = resolve10(dirname28(here), "..", "package.json");
|
|
30
30
|
const raw = readFileSync5(pkgPath, "utf8");
|
|
31
31
|
const parsed = JSON.parse(raw);
|
|
32
32
|
return parsed.version ?? "0.0.0-dev";
|
|
@@ -7078,6 +7078,7 @@ init_managedBlocks();
|
|
|
7078
7078
|
init_types();
|
|
7079
7079
|
import { readFile as readFile15, readdir as readdir10 } from "fs/promises";
|
|
7080
7080
|
import { join as join21 } from "path";
|
|
7081
|
+
import { parse as parseYaml4 } from "yaml";
|
|
7081
7082
|
|
|
7082
7083
|
// src/models/aliases.ts
|
|
7083
7084
|
var MODEL_ALIASES = {
|
|
@@ -10009,6 +10010,18 @@ function output(path, content, managedContent, sourceFiles) {
|
|
|
10009
10010
|
function defaultModelFormat(model) {
|
|
10010
10011
|
return { text: `**Recommended model:** \`${model}\`` };
|
|
10011
10012
|
}
|
|
10013
|
+
function toYamlDoubleQuotedScalar(value) {
|
|
10014
|
+
const singleLine = value.replace(/[\r\n]+/g, " ");
|
|
10015
|
+
const escaped = singleLine.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
10016
|
+
return `"${escaped}"`;
|
|
10017
|
+
}
|
|
10018
|
+
function extractArgumentHint(rawContent) {
|
|
10019
|
+
const match = rawContent.match(/^---\n([\s\S]*?)\n---/);
|
|
10020
|
+
if (!match) return void 0;
|
|
10021
|
+
const parsed = parseYaml4(match[1]);
|
|
10022
|
+
const hint = parsed?.["argument-hint"];
|
|
10023
|
+
return typeof hint === "string" && hint.trim().length > 0 ? hint : void 0;
|
|
10024
|
+
}
|
|
10012
10025
|
var BaseAdapter = class _BaseAdapter {
|
|
10013
10026
|
/**
|
|
10014
10027
|
* Per-invocation diagnostics surfaced from canonical reads, customization
|
|
@@ -10407,7 +10420,19 @@ var BaseAdapter = class _BaseAdapter {
|
|
|
10407
10420
|
}
|
|
10408
10421
|
return results;
|
|
10409
10422
|
}
|
|
10410
|
-
/**
|
|
10423
|
+
/**
|
|
10424
|
+
* Process skills and output each with YAML frontmatter (`name`,
|
|
10425
|
+
* `description`) at the path returned by `pathFn`.
|
|
10426
|
+
*
|
|
10427
|
+
* The `description:` is rendered as a YAML double-quoted scalar via
|
|
10428
|
+
* {@link toYamlDoubleQuotedScalar} — the same treatment {@link processCommandsWithFm}
|
|
10429
|
+
* applies to command descriptions. A skill description carrying `: ` (or a
|
|
10430
|
+
* leading YAML indicator / `"` / `#`) would otherwise make the byte-0
|
|
10431
|
+
* `description:` line an invalid plain scalar, and the `/` picker in Claude
|
|
10432
|
+
* Code / Cursor / Copilot would fall back to showing the `HATCH3R:BEGIN`
|
|
10433
|
+
* managed-block marker. `name` is the canonical id (a `hatch3r-` slug with no
|
|
10434
|
+
* quoting hazard) and stays unquoted, mirroring the command helper.
|
|
10435
|
+
*/
|
|
10411
10436
|
async processSkillsWithFm(ctx, pathFn) {
|
|
10412
10437
|
if (!ctx.features.skills) return [];
|
|
10413
10438
|
const results = [];
|
|
@@ -10421,7 +10446,7 @@ var BaseAdapter = class _BaseAdapter {
|
|
|
10421
10446
|
const desc = overrides.description ?? skill.description;
|
|
10422
10447
|
const fm = `---
|
|
10423
10448
|
name: ${skill.id}
|
|
10424
|
-
description: ${desc}
|
|
10449
|
+
description: ${toYamlDoubleQuotedScalar(desc)}
|
|
10425
10450
|
---`;
|
|
10426
10451
|
results.push(output(pathFn(skill.id), `${fm}
|
|
10427
10452
|
|
|
@@ -10505,7 +10530,7 @@ ${wrapManagedFor(pathFn(skill.id), content)}`, content, this.singleSource(skill)
|
|
|
10505
10530
|
if (skip) continue;
|
|
10506
10531
|
const content = this.substituteCanonicalContent(raw, ctx);
|
|
10507
10532
|
const desc = overrides.description ?? skill.description;
|
|
10508
|
-
const fmLines = [`name: ${skill.id}`, `description: ${desc}`];
|
|
10533
|
+
const fmLines = [`name: ${skill.id}`, `description: ${toYamlDoubleQuotedScalar(desc)}`];
|
|
10509
10534
|
if (opts?.emitAllowedTools && skill.allowedTools && skill.allowedTools.length > 0) {
|
|
10510
10535
|
fmLines.push(`allowed-tools: [${skill.allowedTools.map((t) => `"${t}"`).join(", ")}]`);
|
|
10511
10536
|
}
|
|
@@ -10533,6 +10558,57 @@ ${wrapManagedFor(pathFn(skill.id), content)}`, content, this.singleSource(skill)
|
|
|
10533
10558
|
}
|
|
10534
10559
|
return results;
|
|
10535
10560
|
}
|
|
10561
|
+
/**
|
|
10562
|
+
* Process commands and output each with YAML frontmatter (`name`,
|
|
10563
|
+
* `description`) prepended before the managed block, at the path returned by
|
|
10564
|
+
* `pathFn`.
|
|
10565
|
+
*
|
|
10566
|
+
* Twin of {@link processCommandsRaw} (same user-facing canonical read,
|
|
10567
|
+
* customization, token substitution, feature gate, and warnings handling) —
|
|
10568
|
+
* and the command analogue of {@link processSkillsWithFm} — but emits a
|
|
10569
|
+
* `---\nname:\ndescription:\n---` stub at byte 0 so the slash-command picker
|
|
10570
|
+
* in Claude Code / Cursor / Copilot shows the real description instead of the
|
|
10571
|
+
* `HATCH3R:BEGIN` managed-block marker (the picker reads `description:` at
|
|
10572
|
+
* byte 0). It uses {@link applyCustomization} (frontmatter-stripped body),
|
|
10573
|
+
* not {@link applyCustomizationRaw} (full file), so the canonical frontmatter
|
|
10574
|
+
* is not re-emitted inside the managed block. The description (and any
|
|
10575
|
+
* `argument-hint`) is rendered as a YAML double-quoted scalar via
|
|
10576
|
+
* {@link toYamlDoubleQuotedScalar} because command descriptions routinely
|
|
10577
|
+
* carry `: ` / `--`, and `argument-hint` values such as `[service-name]`
|
|
10578
|
+
* would otherwise parse as a YAML flow sequence, either of which breaks an
|
|
10579
|
+
* unquoted plain scalar.
|
|
10580
|
+
*
|
|
10581
|
+
* The canonical `argument-hint` field (when present) is re-emitted in the
|
|
10582
|
+
* stub so the picker's argument affordance survives at byte 0 (D5-33) — the
|
|
10583
|
+
* pre-fix `processCommandsRaw` carried it only below the marker where the
|
|
10584
|
+
* picker could not read it. Other canonical command frontmatter (governance
|
|
10585
|
+
* metadata like `orchestrator`/`agentPipeline`/`triage_tiers`) is
|
|
10586
|
+
* intentionally NOT hoisted: it is hatch3r-authoring data with no runtime or
|
|
10587
|
+
* picker consumer.
|
|
10588
|
+
*/
|
|
10589
|
+
async processCommandsWithFm(ctx, pathFn) {
|
|
10590
|
+
if (!ctx.features.commands) return [];
|
|
10591
|
+
const results = [];
|
|
10592
|
+
const commands = await this.readUserFacingCanonicalFiles(ctx.canonicalRoot, "commands", ctx.userRepoRoot);
|
|
10593
|
+
for (const cmd of commands) {
|
|
10594
|
+
this.throwIfAborted(ctx);
|
|
10595
|
+
const { content: raw, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, cmd);
|
|
10596
|
+
this.warnings.push(...warnings);
|
|
10597
|
+
if (skip) continue;
|
|
10598
|
+
const content = this.substituteCanonicalContent(raw, ctx);
|
|
10599
|
+
const desc = overrides.description ?? cmd.description;
|
|
10600
|
+
const fmLines = [`name: ${cmd.id}`, `description: ${toYamlDoubleQuotedScalar(desc)}`];
|
|
10601
|
+
const argumentHint = extractArgumentHint(cmd.rawContent);
|
|
10602
|
+
if (argumentHint) fmLines.push(`argument-hint: ${toYamlDoubleQuotedScalar(argumentHint)}`);
|
|
10603
|
+
const fm = `---
|
|
10604
|
+
${fmLines.join("\n")}
|
|
10605
|
+
---`;
|
|
10606
|
+
results.push(output(pathFn(cmd.id), `${fm}
|
|
10607
|
+
|
|
10608
|
+
${wrapManagedFor(pathFn(cmd.id), content)}`, content, this.singleSource(cmd)));
|
|
10609
|
+
}
|
|
10610
|
+
return results;
|
|
10611
|
+
}
|
|
10536
10612
|
/**
|
|
10537
10613
|
* Emit companion/reference content from a canonical subdirectory.
|
|
10538
10614
|
*
|
|
@@ -10848,9 +10924,12 @@ ${CACHE_BREAKPOINT_SENTINEL_END}`;
|
|
|
10848
10924
|
function rewrapWithCacheBreakpoints(out) {
|
|
10849
10925
|
if (!out.managedContent) return out;
|
|
10850
10926
|
const wrappedBody = withCacheBreakpoints(out.managedContent);
|
|
10927
|
+
const originalBlock = wrapManagedFor(out.path, out.managedContent);
|
|
10928
|
+
const blockIdx = out.content.indexOf(originalBlock);
|
|
10929
|
+
const prefix = blockIdx > 0 ? out.content.slice(0, blockIdx) : "";
|
|
10851
10930
|
return {
|
|
10852
10931
|
...out,
|
|
10853
|
-
content: wrapManagedFor(out.path, wrappedBody)
|
|
10932
|
+
content: `${prefix}${wrapManagedFor(out.path, wrappedBody)}`,
|
|
10854
10933
|
managedContent: wrappedBody
|
|
10855
10934
|
};
|
|
10856
10935
|
}
|
|
@@ -10945,6 +11024,7 @@ var AGENT_TEAMS_SECTION_MINIMAL = [
|
|
|
10945
11024
|
"",
|
|
10946
11025
|
"Use `/hatch3r-agent-team` for guided team creation."
|
|
10947
11026
|
];
|
|
11027
|
+
var AGENT_TEAM_DESCRIPTION = "Launch a Claude Code Agent Team that runs the hatch3r 4-phase pipeline \u2014 Research, Implement, Review, then parallel Quality gates \u2014 across coordinating teammates.";
|
|
10948
11028
|
var AGENT_TEAM_COMMAND = `# hatch3r Agent Team
|
|
10949
11029
|
|
|
10950
11030
|
Create a Claude Code Agent Team that follows the hatch3r 4-phase pipeline.
|
|
@@ -11403,12 +11483,12 @@ ${wrapManagedFor(agentPath, body)}`, body, claudeSingleSource(agent)));
|
|
|
11403
11483
|
".claude/hooks/pretooluse-allowlist.mjs",
|
|
11404
11484
|
buildClaudePreToolUseHookScript()
|
|
11405
11485
|
));
|
|
11406
|
-
const skillOutputs = await this.
|
|
11486
|
+
const skillOutputs = await this.processSkillsWithFmCliFiltered(
|
|
11407
11487
|
ctx,
|
|
11408
11488
|
(id) => `.claude/skills/${toPrefixedId(id)}/SKILL.md`
|
|
11409
11489
|
);
|
|
11410
11490
|
results.push(...skillOutputs.map(rewrapWithCacheBreakpoints));
|
|
11411
|
-
const commandOutputs = await this.
|
|
11491
|
+
const commandOutputs = await this.processCommandsWithFm(
|
|
11412
11492
|
ctx,
|
|
11413
11493
|
(id) => `.claude/commands/${toPrefixedId(id)}.md`
|
|
11414
11494
|
);
|
|
@@ -11448,7 +11528,18 @@ ${wrapManagedFor(agentPath, body)}`, body, claudeSingleSource(agent)));
|
|
|
11448
11528
|
);
|
|
11449
11529
|
}
|
|
11450
11530
|
const agentTeamBody = withCacheBreakpoints(AGENT_TEAM_COMMAND);
|
|
11451
|
-
|
|
11531
|
+
const agentTeamPath = ".claude/commands/hatch3r-agent-team.md";
|
|
11532
|
+
const agentTeamFm = `---
|
|
11533
|
+
name: hatch3r-agent-team
|
|
11534
|
+
description: "${AGENT_TEAM_DESCRIPTION}"
|
|
11535
|
+
---`;
|
|
11536
|
+
results.push(output(
|
|
11537
|
+
agentTeamPath,
|
|
11538
|
+
`${agentTeamFm}
|
|
11539
|
+
|
|
11540
|
+
${wrapManagedFor(agentTeamPath, agentTeamBody)}`,
|
|
11541
|
+
agentTeamBody
|
|
11542
|
+
));
|
|
11452
11543
|
return results;
|
|
11453
11544
|
}
|
|
11454
11545
|
};
|
|
@@ -11714,7 +11805,7 @@ ${wrapManagedFor(agentPath, content)}`, content, copilotSingleSource(agent)));
|
|
|
11714
11805
|
}
|
|
11715
11806
|
}
|
|
11716
11807
|
results.push(
|
|
11717
|
-
...await this.
|
|
11808
|
+
...await this.processCommandsWithFm(ctx, (id) => `.github/prompts/${toPrefixedId(id)}.prompt.md`)
|
|
11718
11809
|
);
|
|
11719
11810
|
if (ctx.features.githubAgents && !ctx.features.agents) {
|
|
11720
11811
|
const ghAgents = await this.readTrackedCanonicalFiles(ctx.canonicalRoot, "github-agents", ctx.userRepoRoot);
|
|
@@ -11883,7 +11974,7 @@ ${lines.join("\n")}
|
|
|
11883
11974
|
...await this.processSkillsWithFmCliFiltered(ctx, (id) => `.cursor/skills/${toPrefixedId(id)}/SKILL.md`)
|
|
11884
11975
|
);
|
|
11885
11976
|
results.push(
|
|
11886
|
-
...await this.
|
|
11977
|
+
...await this.processCommandsWithFm(ctx, (id) => `.cursor/commands/${toPrefixedId(id)}.md`)
|
|
11887
11978
|
);
|
|
11888
11979
|
const companionMappings = [
|
|
11889
11980
|
["agents/modes", ctx.features.agents, (f) => `.cursor/agents/modes/${f}`],
|
|
@@ -12528,7 +12619,7 @@ import { readFile as readFile28 } from "fs/promises";
|
|
|
12528
12619
|
// src/detect/repoAnalyzer.ts
|
|
12529
12620
|
import { access as access10, readFile as readFile19, readdir as readdir12, stat as stat8 } from "fs/promises";
|
|
12530
12621
|
import { join as join29 } from "path";
|
|
12531
|
-
import { parse as
|
|
12622
|
+
import { parse as parseYaml5, YAMLParseError } from "yaml";
|
|
12532
12623
|
|
|
12533
12624
|
// src/detect/conventionConflict.ts
|
|
12534
12625
|
var LINTER_TOOL_DIMENSION = {
|
|
@@ -12808,7 +12899,7 @@ async function collectWorkspaceGlobs(rootDir) {
|
|
|
12808
12899
|
return globs;
|
|
12809
12900
|
}
|
|
12810
12901
|
function parsePnpmWorkspacePackages(raw) {
|
|
12811
|
-
const doc =
|
|
12902
|
+
const doc = parseYaml5(raw);
|
|
12812
12903
|
if (!doc || typeof doc !== "object") return [];
|
|
12813
12904
|
const packages = doc.packages;
|
|
12814
12905
|
if (!Array.isArray(packages)) return [];
|
|
@@ -13591,7 +13682,13 @@ var FEATURE_CHOICES = [
|
|
|
13591
13682
|
{ name: "Commands", value: "commands" },
|
|
13592
13683
|
{ name: "MCP", value: "mcp" },
|
|
13593
13684
|
{ name: "Hooks", value: "hooks" },
|
|
13594
|
-
{ name: "GitHub agents", value: "githubAgents" }
|
|
13685
|
+
{ name: "GitHub agents", value: "githubAgents" },
|
|
13686
|
+
// 2.1.0 (Task D): handoffs was a live control surface (DEFAULT_FEATURES.handoffs
|
|
13687
|
+
// = true, threaded into bridge-orchestration emission) but absent from the
|
|
13688
|
+
// picker, so the config feature-rebuild loop silently forced it false on every
|
|
13689
|
+
// run. Listed here (default checked) so it round-trips; the rebuild loop is
|
|
13690
|
+
// additionally hardened to preserve unlisted features (see config.ts).
|
|
13691
|
+
{ name: "Handoffs", value: "handoffs" }
|
|
13595
13692
|
];
|
|
13596
13693
|
var MCP_CHOICES = Object.entries(AVAILABLE_MCP_SERVERS).map(([id, meta]) => ({
|
|
13597
13694
|
name: `${id}: ${meta.description}`,
|
|
@@ -14864,6 +14961,50 @@ function mcpServersStep(opts) {
|
|
|
14864
14961
|
}
|
|
14865
14962
|
};
|
|
14866
14963
|
}
|
|
14964
|
+
function maturityStep(opts) {
|
|
14965
|
+
return {
|
|
14966
|
+
id: "maturity",
|
|
14967
|
+
async run(_state, previous) {
|
|
14968
|
+
const answer = await inquirer6.prompt([
|
|
14969
|
+
{
|
|
14970
|
+
type: "select",
|
|
14971
|
+
name: "maturity",
|
|
14972
|
+
message: opts.message,
|
|
14973
|
+
choices: [
|
|
14974
|
+
{ name: "solo \u2014 individual / hobby; universal floor, warn-only gates", value: "solo" },
|
|
14975
|
+
{ name: "team \u2014 shared repo; + duplication/design discipline, strict gates", value: "team" },
|
|
14976
|
+
{ name: "scaleup \u2014 multi-team; + SLOs, tracing, performance budgets", value: "scaleup" },
|
|
14977
|
+
{ name: "enterprise \u2014 regulated; + mutation/contract testing, AI evals, FinOps", value: "enterprise" }
|
|
14978
|
+
],
|
|
14979
|
+
default: previous ?? opts.defaultMaturity
|
|
14980
|
+
}
|
|
14981
|
+
]);
|
|
14982
|
+
return isBack(answer.maturity) ? BACK : answer.maturity;
|
|
14983
|
+
}
|
|
14984
|
+
};
|
|
14985
|
+
}
|
|
14986
|
+
function confidenceFloorStep(opts) {
|
|
14987
|
+
return {
|
|
14988
|
+
id: "confidenceFloor",
|
|
14989
|
+
async run(state, previous) {
|
|
14990
|
+
const resolvedDefault = typeof opts.defaultFloor === "function" ? opts.defaultFloor(state) : opts.defaultFloor;
|
|
14991
|
+
const answer = await inquirer6.prompt([
|
|
14992
|
+
{
|
|
14993
|
+
type: "select",
|
|
14994
|
+
name: "confidenceFloor",
|
|
14995
|
+
message: opts.message,
|
|
14996
|
+
choices: [
|
|
14997
|
+
{ name: "any \u2014 second-pass only on a low-confidence reviewer finding", value: "any" },
|
|
14998
|
+
{ name: "medium \u2014 second-pass on any low-confidence finding", value: "medium" },
|
|
14999
|
+
{ name: "high \u2014 second-pass on non-high confidence + ASK on every low-confidence finding", value: "high" }
|
|
15000
|
+
],
|
|
15001
|
+
default: previous ?? resolvedDefault
|
|
15002
|
+
}
|
|
15003
|
+
]);
|
|
15004
|
+
return isBack(answer.confidenceFloor) ? BACK : answer.confidenceFloor;
|
|
15005
|
+
}
|
|
15006
|
+
};
|
|
15007
|
+
}
|
|
14867
15008
|
|
|
14868
15009
|
// src/cliTools/detect.ts
|
|
14869
15010
|
import { spawn } from "child_process";
|
|
@@ -14875,7 +15016,7 @@ async function probeBin(name) {
|
|
|
14875
15016
|
if (!isSafeProbeName(name)) return "";
|
|
14876
15017
|
const isWindows = process.platform === "win32";
|
|
14877
15018
|
const [cmd, args] = isWindows ? ["where", [name]] : ["/bin/sh", ["-c", `command -v -- "${name}"`]];
|
|
14878
|
-
return new Promise((
|
|
15019
|
+
return new Promise((resolve10) => {
|
|
14879
15020
|
let settled = false;
|
|
14880
15021
|
let stdout = "";
|
|
14881
15022
|
const child = spawn(cmd, args, {
|
|
@@ -14889,7 +15030,7 @@ async function probeBin(name) {
|
|
|
14889
15030
|
child.kill("SIGKILL");
|
|
14890
15031
|
} catch {
|
|
14891
15032
|
}
|
|
14892
|
-
|
|
15033
|
+
resolve10("");
|
|
14893
15034
|
}, PROBE_TIMEOUT_MS);
|
|
14894
15035
|
child.stdout?.on("data", (chunk) => {
|
|
14895
15036
|
stdout += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
@@ -14898,18 +15039,18 @@ async function probeBin(name) {
|
|
|
14898
15039
|
if (settled) return;
|
|
14899
15040
|
settled = true;
|
|
14900
15041
|
clearTimeout(timer);
|
|
14901
|
-
|
|
15042
|
+
resolve10("");
|
|
14902
15043
|
});
|
|
14903
15044
|
child.on("close", (code) => {
|
|
14904
15045
|
if (settled) return;
|
|
14905
15046
|
settled = true;
|
|
14906
15047
|
clearTimeout(timer);
|
|
14907
15048
|
if (code !== 0) {
|
|
14908
|
-
|
|
15049
|
+
resolve10("");
|
|
14909
15050
|
return;
|
|
14910
15051
|
}
|
|
14911
15052
|
const first = stdout.split(/\r?\n/).map((s) => s.trim()).find((s) => s.length > 0) ?? "";
|
|
14912
|
-
|
|
15053
|
+
resolve10(first);
|
|
14913
15054
|
});
|
|
14914
15055
|
});
|
|
14915
15056
|
}
|
|
@@ -14918,7 +15059,7 @@ async function runExtensionProbe(binary, args) {
|
|
|
14918
15059
|
for (const arg of args) {
|
|
14919
15060
|
if (!isSafeProbeName(arg)) return "";
|
|
14920
15061
|
}
|
|
14921
|
-
return new Promise((
|
|
15062
|
+
return new Promise((resolve10) => {
|
|
14922
15063
|
let settled = false;
|
|
14923
15064
|
let stdout = "";
|
|
14924
15065
|
const child = spawn(binary, [...args], {
|
|
@@ -14932,7 +15073,7 @@ async function runExtensionProbe(binary, args) {
|
|
|
14932
15073
|
child.kill("SIGKILL");
|
|
14933
15074
|
} catch {
|
|
14934
15075
|
}
|
|
14935
|
-
|
|
15076
|
+
resolve10("");
|
|
14936
15077
|
}, PROBE_TIMEOUT_MS);
|
|
14937
15078
|
child.stdout?.on("data", (chunk) => {
|
|
14938
15079
|
stdout += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
@@ -14941,17 +15082,17 @@ async function runExtensionProbe(binary, args) {
|
|
|
14941
15082
|
if (settled) return;
|
|
14942
15083
|
settled = true;
|
|
14943
15084
|
clearTimeout(timer);
|
|
14944
|
-
|
|
15085
|
+
resolve10("");
|
|
14945
15086
|
});
|
|
14946
15087
|
child.on("close", (code) => {
|
|
14947
15088
|
if (settled) return;
|
|
14948
15089
|
settled = true;
|
|
14949
15090
|
clearTimeout(timer);
|
|
14950
15091
|
if (code !== 0) {
|
|
14951
|
-
|
|
15092
|
+
resolve10("");
|
|
14952
15093
|
return;
|
|
14953
15094
|
}
|
|
14954
|
-
|
|
15095
|
+
resolve10(stdout);
|
|
14955
15096
|
});
|
|
14956
15097
|
});
|
|
14957
15098
|
}
|
|
@@ -15723,7 +15864,7 @@ init_safeWrite();
|
|
|
15723
15864
|
init_types();
|
|
15724
15865
|
import { readFile as readFile22 } from "fs/promises";
|
|
15725
15866
|
import { join as join33 } from "path";
|
|
15726
|
-
import { parse as
|
|
15867
|
+
import { parse as parseYaml6 } from "yaml";
|
|
15727
15868
|
|
|
15728
15869
|
// src/importers/shared.ts
|
|
15729
15870
|
var FRONTMATTER_REGEX2 = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?$/;
|
|
@@ -15744,7 +15885,7 @@ function parseAwesomeCursorrules(rawContent) {
|
|
|
15744
15885
|
const { frontmatterStr, body } = splitFrontmatter2(rawContent);
|
|
15745
15886
|
let description = "";
|
|
15746
15887
|
if (frontmatterStr != null) {
|
|
15747
|
-
const parsed =
|
|
15888
|
+
const parsed = parseYaml6(frontmatterStr);
|
|
15748
15889
|
if (parsed && typeof parsed.description === "string") description = parsed.description;
|
|
15749
15890
|
}
|
|
15750
15891
|
const canonical = {
|
|
@@ -15780,7 +15921,7 @@ async function parseAwesomeCursorrulesFile(rootDir) {
|
|
|
15780
15921
|
init_types();
|
|
15781
15922
|
import { readFile as readFile23, readdir as readdir13 } from "fs/promises";
|
|
15782
15923
|
import { join as join34 } from "path";
|
|
15783
|
-
import { parse as
|
|
15924
|
+
import { parse as parseYaml7 } from "yaml";
|
|
15784
15925
|
var COPILOT_IMPORT_PREFIX = `${HATCH3R_PREFIX}copilot-import-`;
|
|
15785
15926
|
var COPILOT_LEGACY_ID = `${COPILOT_IMPORT_PREFIX}repo-instructions`;
|
|
15786
15927
|
function normaliseApplyTo(applyTo) {
|
|
@@ -15797,7 +15938,7 @@ function parseCopilotInstruction(filename, rawContent) {
|
|
|
15797
15938
|
const { frontmatterStr, body } = splitFrontmatter2(rawContent);
|
|
15798
15939
|
let fm = {};
|
|
15799
15940
|
if (frontmatterStr != null) {
|
|
15800
|
-
const parsed =
|
|
15941
|
+
const parsed = parseYaml7(frontmatterStr);
|
|
15801
15942
|
if (parsed && typeof parsed === "object") {
|
|
15802
15943
|
const next = {};
|
|
15803
15944
|
if (typeof parsed.name === "string") next.name = parsed.name;
|
|
@@ -15828,7 +15969,7 @@ function parseCopilotLegacyInstructions(rawContent) {
|
|
|
15828
15969
|
const { frontmatterStr, body } = splitFrontmatter2(rawContent);
|
|
15829
15970
|
let description = "";
|
|
15830
15971
|
if (frontmatterStr != null) {
|
|
15831
|
-
const parsed =
|
|
15972
|
+
const parsed = parseYaml7(frontmatterStr);
|
|
15832
15973
|
if (parsed && typeof parsed.description === "string") description = parsed.description;
|
|
15833
15974
|
}
|
|
15834
15975
|
const canonical = {
|
|
@@ -15896,7 +16037,7 @@ init_types();
|
|
|
15896
16037
|
init_safeWrite();
|
|
15897
16038
|
import { readFile as readFile24, readdir as readdir14, mkdir as mkdir10 } from "fs/promises";
|
|
15898
16039
|
import { join as join35 } from "path";
|
|
15899
|
-
import { parse as
|
|
16040
|
+
import { parse as parseYaml8, stringify as yamlStringify2 } from "yaml";
|
|
15900
16041
|
var FRONTMATTER_REGEX3 = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?$/;
|
|
15901
16042
|
function slugifyCursorRuleId(filename) {
|
|
15902
16043
|
const base = filename.replace(/\.mdc$/i, "");
|
|
@@ -15921,7 +16062,7 @@ function parseCursorRule(filename, rawContent) {
|
|
|
15921
16062
|
let body;
|
|
15922
16063
|
if (match) {
|
|
15923
16064
|
const [, fmStr, bodyStr = ""] = match;
|
|
15924
|
-
const parsed =
|
|
16065
|
+
const parsed = parseYaml8(fmStr ?? "");
|
|
15925
16066
|
if (parsed && typeof parsed === "object") {
|
|
15926
16067
|
const fm = {};
|
|
15927
16068
|
if (typeof parsed.description === "string") fm.description = parsed.description;
|
|
@@ -15983,7 +16124,7 @@ async function parseCursorRulesDir(cursorDir) {
|
|
|
15983
16124
|
init_types();
|
|
15984
16125
|
import { readFile as readFile25, readdir as readdir15 } from "fs/promises";
|
|
15985
16126
|
import { join as join36 } from "path";
|
|
15986
|
-
import { parse as
|
|
16127
|
+
import { parse as parseYaml9 } from "yaml";
|
|
15987
16128
|
var WINDSURF_IMPORT_PREFIX = `${HATCH3R_PREFIX}windsurf-import-`;
|
|
15988
16129
|
var WINDSURF_LEGACY_ID = `${WINDSURF_IMPORT_PREFIX}windsurfrules`;
|
|
15989
16130
|
var VALID_TRIGGERS = /* @__PURE__ */ new Set([
|
|
@@ -16014,7 +16155,7 @@ function parseWindsurfRule(filename, rawContent) {
|
|
|
16014
16155
|
const { frontmatterStr, body } = splitFrontmatter2(rawContent);
|
|
16015
16156
|
let fm = {};
|
|
16016
16157
|
if (frontmatterStr != null) {
|
|
16017
|
-
const parsed =
|
|
16158
|
+
const parsed = parseYaml9(frontmatterStr);
|
|
16018
16159
|
if (parsed && typeof parsed === "object") {
|
|
16019
16160
|
const next = {};
|
|
16020
16161
|
if (typeof parsed.trigger === "string" && VALID_TRIGGERS.has(parsed.trigger)) {
|
|
@@ -18243,6 +18384,13 @@ async function initCommand(opts = {}) {
|
|
|
18243
18384
|
seededMaturityDefault,
|
|
18244
18385
|
"maturity"
|
|
18245
18386
|
);
|
|
18387
|
+
info(chalk7.dim(`Detected default branch: ${inferredDefaultBranch}`));
|
|
18388
|
+
info(
|
|
18389
|
+
chalk7.dim(
|
|
18390
|
+
opts.maturity === void 0 ? `Inferred maturity: ${inferredMaturity} (team size: ${inferredTeamSize})` : `Maturity: ${inferredMaturity} (from --maturity); team size: ${inferredTeamSize}`
|
|
18391
|
+
)
|
|
18392
|
+
);
|
|
18393
|
+
const promptMaturity = opts.maturity === void 0;
|
|
18246
18394
|
const explicitCliTools = opts.noCliTools ? [] : resolveCliToolsFlag(opts.cliTools, repoInfo, detectedPlatform);
|
|
18247
18395
|
const steps = [
|
|
18248
18396
|
platformStep({
|
|
@@ -18274,6 +18422,17 @@ async function initCommand(opts = {}) {
|
|
|
18274
18422
|
)
|
|
18275
18423
|
)
|
|
18276
18424
|
}),
|
|
18425
|
+
// C (2.1.0, P1): maturity prompt, seeded at the git-inferred tier. Always
|
|
18426
|
+
// shown on the interactive path; conditionally spread (matching the
|
|
18427
|
+
// cliToolsStep gate below) only so it is ABSENT from the array — not just
|
|
18428
|
+
// skipped — when `--maturity` resolved the tier from the flag. Decision 25
|
|
18429
|
+
// raised the interactive ceiling 5→6 to admit it; see `promptMaturity`.
|
|
18430
|
+
...promptMaturity ? [
|
|
18431
|
+
maturityStep({
|
|
18432
|
+
message: "Project maturity (investment depth):",
|
|
18433
|
+
defaultMaturity: inferredMaturity
|
|
18434
|
+
})
|
|
18435
|
+
] : [],
|
|
18277
18436
|
customItemsStep({
|
|
18278
18437
|
index: filterIndex,
|
|
18279
18438
|
// D10-13: floor + protected rows are locked-on by the picker itself;
|
|
@@ -18307,7 +18466,7 @@ async function initCommand(opts = {}) {
|
|
|
18307
18466
|
const defaultBranch = inferredDefaultBranch;
|
|
18308
18467
|
const projectType = inferredProjectType;
|
|
18309
18468
|
const teamSize = inferredTeamSize;
|
|
18310
|
-
const maturity = inferredMaturity;
|
|
18469
|
+
const maturity = stepState.maturity ?? inferredMaturity;
|
|
18311
18470
|
const selectedPreset = getPreset(stepState.preset);
|
|
18312
18471
|
const customSelections = stepState.customItems;
|
|
18313
18472
|
const tools = stepState.tools;
|
|
@@ -19860,7 +20019,7 @@ function defaultShouldRetry(err, _attempt) {
|
|
|
19860
20019
|
const type = classifyFailure(err);
|
|
19861
20020
|
return type === "transient";
|
|
19862
20021
|
}
|
|
19863
|
-
var realSleep = (ms) => new Promise((
|
|
20022
|
+
var realSleep = (ms) => new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
19864
20023
|
function computeBackoffDelay(attempt, initialDelayMs, maxDelayMs, backoffFactor) {
|
|
19865
20024
|
const exponent = Math.max(0, attempt - 1);
|
|
19866
20025
|
const raw = initialDelayMs * Math.pow(backoffFactor, exponent);
|
|
@@ -20626,13 +20785,13 @@ async function persistLearning(targetPath, body, options = {}) {
|
|
|
20626
20785
|
init_safeWrite();
|
|
20627
20786
|
import { mkdir as mkdir15, readdir as readdir21, readFile as readFile32, stat as stat13, unlink as unlink6 } from "fs/promises";
|
|
20628
20787
|
import { join as join47 } from "path";
|
|
20629
|
-
import { parse as
|
|
20788
|
+
import { parse as parseYaml11, stringify as stringifyYaml } from "yaml";
|
|
20630
20789
|
|
|
20631
20790
|
// src/content/handoffs/validation.ts
|
|
20632
20791
|
import { createHash as createHash8, randomBytes as randomBytes5 } from "crypto";
|
|
20633
20792
|
import { readdir as readdir20, readFile as readFile31, stat as stat12 } from "fs/promises";
|
|
20634
20793
|
import { join as join46 } from "path";
|
|
20635
|
-
import { parse as
|
|
20794
|
+
import { parse as parseYaml10 } from "yaml";
|
|
20636
20795
|
init_customization();
|
|
20637
20796
|
|
|
20638
20797
|
// src/content/handoffs/schema.ts
|
|
@@ -20847,7 +21006,7 @@ async function loadHandoffFile(filePath) {
|
|
|
20847
21006
|
let parsed;
|
|
20848
21007
|
let parseError = null;
|
|
20849
21008
|
try {
|
|
20850
|
-
parsed =
|
|
21009
|
+
parsed = parseYaml10(yamlBlock);
|
|
20851
21010
|
} catch (err) {
|
|
20852
21011
|
parseError = `Handoff "${filePath}" has invalid YAML frontmatter: ${err.message}`;
|
|
20853
21012
|
parsed = null;
|
|
@@ -20960,7 +21119,7 @@ function parseHandoffText(raw, filePath) {
|
|
|
20960
21119
|
const body = afterFirst.slice(bodyStart);
|
|
20961
21120
|
let parsed;
|
|
20962
21121
|
try {
|
|
20963
|
-
parsed =
|
|
21122
|
+
parsed = parseYaml11(yamlBlock);
|
|
20964
21123
|
} catch (err) {
|
|
20965
21124
|
console.error(
|
|
20966
21125
|
`hatch3r: failed to parse handoff frontmatter at ${filePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -22025,6 +22184,13 @@ function computeDiff(oldManifest, newTools, newFeatures, newMcp, newPlatform, ne
|
|
|
22025
22184
|
function isDiffEmpty(diff) {
|
|
22026
22185
|
return diff.addedTools.length === 0 && diff.removedTools.length === 0 && diff.addedMcp.length === 0 && diff.removedMcp.length === 0 && diff.enabledFeatures.length === 0 && diff.disabledFeatures.length === 0 && !diff.platformChanged && !diff.repoChanged && diff.addedContent.length === 0 && diff.removedContent.length === 0 && diff.addedCliTools.length === 0 && diff.removedCliTools.length === 0;
|
|
22027
22186
|
}
|
|
22187
|
+
function rebuildFeaturesFromSelection(prev, selected, pickerVisible) {
|
|
22188
|
+
const features = { ...DEFAULT_FEATURES, ...prev };
|
|
22189
|
+
for (const k of pickerVisible) {
|
|
22190
|
+
features[k] = selected.includes(k);
|
|
22191
|
+
}
|
|
22192
|
+
return features;
|
|
22193
|
+
}
|
|
22028
22194
|
function buildDiffSummaryLines(diff, platform, namespace, project, defaultBranch, currentBranch) {
|
|
22029
22195
|
const lines = [];
|
|
22030
22196
|
if (diff.addedTools.length > 0) {
|
|
@@ -22484,7 +22650,9 @@ async function configCommandImpl(rootDir, format, cliOpts, arg1, arg2) {
|
|
|
22484
22650
|
{
|
|
22485
22651
|
id: "features",
|
|
22486
22652
|
async run(_state, previous) {
|
|
22487
|
-
const currentFeatureKeys = previous ?? Object.keys(DEFAULT_FEATURES).filter(
|
|
22653
|
+
const currentFeatureKeys = previous ?? Object.keys(DEFAULT_FEATURES).filter(
|
|
22654
|
+
(k) => manifest.features[k] ?? DEFAULT_FEATURES[k]
|
|
22655
|
+
);
|
|
22488
22656
|
const featureAnswers = await inquirer11.prompt([
|
|
22489
22657
|
{
|
|
22490
22658
|
type: "checkbox",
|
|
@@ -22552,6 +22720,27 @@ async function configCommandImpl(rootDir, format, cliOpts, arg1, arg2) {
|
|
|
22552
22720
|
},
|
|
22553
22721
|
extraSkip: () => !manifest.content,
|
|
22554
22722
|
wslTheme
|
|
22723
|
+
}),
|
|
22724
|
+
// C (2.1.0, P1): interactive maturity surface — parity with the scalar
|
|
22725
|
+
// `config maturity=<tier>` form. Default = the persisted tier
|
|
22726
|
+
// (readMaturityTier resolves absence to "solo").
|
|
22727
|
+
maturityStep({
|
|
22728
|
+
message: "Maturity tier (investment depth):",
|
|
22729
|
+
defaultMaturity: readMaturityTier(manifest)
|
|
22730
|
+
}),
|
|
22731
|
+
// E (2.1.0, P1): interactive confidence_floor surface — parity with the
|
|
22732
|
+
// scalar `config confidence_floor=<v>` form. The default is resolved AT
|
|
22733
|
+
// PROMPT TIME from the in-run maturity (`state.maturity`, set by the
|
|
22734
|
+
// maturity step sequenced just above) rather than the pre-mutation
|
|
22735
|
+
// manifest, so a solo→scaleup bump made earlier in this same run seeds the
|
|
22736
|
+
// tier-correct floor ("high") instead of the stale solo default ("any").
|
|
22737
|
+
// readConfidenceFloor keeps an explicit persisted floor winning; only the
|
|
22738
|
+
// tier-derived fallback follows the new maturity. Without this, accepting
|
|
22739
|
+
// the seeded default after a maturity bump would persist a floor that
|
|
22740
|
+
// contradicts the chosen tier (Cursor Bugbot — floor pinned on tier bump).
|
|
22741
|
+
confidenceFloorStep({
|
|
22742
|
+
message: "Agent assertiveness floor (review/ASK gate):",
|
|
22743
|
+
defaultFloor: (state) => readConfidenceFloor({ ...manifest, maturity: state.maturity ?? manifest.maturity })
|
|
22555
22744
|
})
|
|
22556
22745
|
];
|
|
22557
22746
|
const stepState = await runStepMachine(steps);
|
|
@@ -22559,6 +22748,10 @@ async function configCommandImpl(rootDir, format, cliOpts, arg1, arg2) {
|
|
|
22559
22748
|
const { owner, repo, namespace, project } = stepState.identity;
|
|
22560
22749
|
const defaultBranch = stepState.defaultBranch;
|
|
22561
22750
|
const tools = stepState.tools;
|
|
22751
|
+
const selectedMaturity = stepState.maturity;
|
|
22752
|
+
const selectedConfidenceFloor = stepState.confidenceFloor;
|
|
22753
|
+
const maturityChanged = selectedMaturity !== void 0 && selectedMaturity !== readMaturityTier(manifest);
|
|
22754
|
+
const confidenceFloorChanged = selectedConfidenceFloor !== void 0 && selectedConfidenceFloor !== readConfidenceFloor(manifest);
|
|
22562
22755
|
if (tools.length === 0) {
|
|
22563
22756
|
error("At least one tool must be selected.");
|
|
22564
22757
|
throw new HatchError(
|
|
@@ -22584,11 +22777,11 @@ async function configCommandImpl(rootDir, format, cliOpts, arg1, arg2) {
|
|
|
22584
22777
|
enabled: selectedCliTools.length > 0,
|
|
22585
22778
|
selected: selectedCliTools
|
|
22586
22779
|
};
|
|
22587
|
-
const
|
|
22588
|
-
|
|
22589
|
-
|
|
22590
|
-
|
|
22591
|
-
|
|
22780
|
+
const features = rebuildFeaturesFromSelection(
|
|
22781
|
+
manifest.features,
|
|
22782
|
+
stepState.features,
|
|
22783
|
+
FEATURE_CHOICES.map((c) => c.value)
|
|
22784
|
+
);
|
|
22592
22785
|
const hasExistingMcp = manifest.mcp.servers.length > 0;
|
|
22593
22786
|
let mcpServers;
|
|
22594
22787
|
if (features.mcp) {
|
|
@@ -22700,7 +22893,7 @@ async function configCommandImpl(rootDir, format, cliOpts, arg1, arg2) {
|
|
|
22700
22893
|
contentMetadataChanged = previousContent.preset !== newSelection.preset || previousContent.projectType !== newSelection.projectType || previousContent.teamSize !== newSelection.teamSize;
|
|
22701
22894
|
}
|
|
22702
22895
|
const diff = computeDiff(manifest, tools, features, mcpServers, platform, owner, repo, namespace, project, selectedCliTools, contentChanges);
|
|
22703
|
-
if (isDiffEmpty(diff) && defaultBranch === currentBranch && !contentMetadataChanged) {
|
|
22896
|
+
if (isDiffEmpty(diff) && defaultBranch === currentBranch && !contentMetadataChanged && !maturityChanged && !confidenceFloorChanged) {
|
|
22704
22897
|
if (!isQuiet()) console.log();
|
|
22705
22898
|
info("No changes detected.");
|
|
22706
22899
|
if (!isQuiet()) console.log();
|
|
@@ -22708,6 +22901,12 @@ async function configCommandImpl(rootDir, format, cliOpts, arg1, arg2) {
|
|
|
22708
22901
|
}
|
|
22709
22902
|
if (cliOpts?.dryRun === true) {
|
|
22710
22903
|
const dryLines = buildDiffSummaryLines(diff, platform, namespace, project, defaultBranch, currentBranch);
|
|
22904
|
+
if (maturityChanged && selectedMaturity !== void 0) {
|
|
22905
|
+
dryLines.push(`${chalk11.cyan("~")} Maturity: ${selectedMaturity}`);
|
|
22906
|
+
}
|
|
22907
|
+
if (confidenceFloorChanged && selectedConfidenceFloor !== void 0) {
|
|
22908
|
+
dryLines.push(`${chalk11.cyan("~")} Confidence floor: ${selectedConfidenceFloor}`);
|
|
22909
|
+
}
|
|
22711
22910
|
dryLines.push("");
|
|
22712
22911
|
dryLines.push(
|
|
22713
22912
|
chalk11.dim(
|
|
@@ -22802,6 +23001,8 @@ async function configCommandImpl(rootDir, format, cliOpts, arg1, arg2) {
|
|
|
22802
23001
|
manifest.features = features;
|
|
22803
23002
|
manifest.mcp = { servers: mcpServers };
|
|
22804
23003
|
manifest.cliTools = cliToolsConfig;
|
|
23004
|
+
if (selectedMaturity !== void 0) manifest.maturity = selectedMaturity;
|
|
23005
|
+
if (selectedConfidenceFloor !== void 0) manifest.confidenceFloor = selectedConfidenceFloor;
|
|
22805
23006
|
if (manifest.board) {
|
|
22806
23007
|
manifest.board.owner = owner;
|
|
22807
23008
|
manifest.board.repo = repo;
|
|
@@ -22870,6 +23071,12 @@ async function configCommandImpl(rootDir, format, cliOpts, arg1, arg2) {
|
|
|
22870
23071
|
}
|
|
22871
23072
|
if (!isQuiet()) console.log();
|
|
22872
23073
|
const summaryLines = buildDiffSummaryLines(diff, platform, namespace, project, defaultBranch, currentBranch);
|
|
23074
|
+
if (maturityChanged && selectedMaturity !== void 0) {
|
|
23075
|
+
summaryLines.push(`${chalk11.cyan("~")} Maturity: ${selectedMaturity}`);
|
|
23076
|
+
}
|
|
23077
|
+
if (confidenceFloorChanged && selectedConfidenceFloor !== void 0) {
|
|
23078
|
+
summaryLines.push(`${chalk11.cyan("~")} Confidence floor: ${selectedConfidenceFloor}`);
|
|
23079
|
+
}
|
|
22873
23080
|
summaryLines.push("");
|
|
22874
23081
|
summaryLines.push(label("Files", `${updateResult.copiedFiles} canonical files updated`));
|
|
22875
23082
|
summaryLines.push(label("Tools", `${updateResult.syncedTools} tool(s) synced`));
|
|
@@ -23170,12 +23377,161 @@ async function configCommandImpl(rootDir, format, cliOpts, arg1, arg2) {
|
|
|
23170
23377
|
}
|
|
23171
23378
|
}
|
|
23172
23379
|
|
|
23173
|
-
// src/cli/commands/
|
|
23174
|
-
|
|
23175
|
-
import { join as join52 } from "path";
|
|
23380
|
+
// src/cli/commands/setup.ts
|
|
23381
|
+
init_types();
|
|
23176
23382
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
23383
|
+
import { access as access16, mkdir as mkdir17, readdir as readdir24 } from "fs/promises";
|
|
23384
|
+
import { basename as basename4, join as join50, resolve as resolve8 } from "path";
|
|
23177
23385
|
import chalk12 from "chalk";
|
|
23178
|
-
import
|
|
23386
|
+
import inquirer12 from "inquirer";
|
|
23387
|
+
init_ui();
|
|
23388
|
+
var EMPTINESS_IGNORED_ENTRIES = /* @__PURE__ */ new Set([".git"]);
|
|
23389
|
+
async function pathExists3(p) {
|
|
23390
|
+
try {
|
|
23391
|
+
await access16(p);
|
|
23392
|
+
return true;
|
|
23393
|
+
} catch (err) {
|
|
23394
|
+
verbose(`setup: path probe access(${p}) \u2192 ${err.code ?? "absent"}`);
|
|
23395
|
+
return false;
|
|
23396
|
+
}
|
|
23397
|
+
}
|
|
23398
|
+
async function isDirEffectivelyEmpty(dir) {
|
|
23399
|
+
try {
|
|
23400
|
+
const entries = await readdir24(dir);
|
|
23401
|
+
return entries.every((e) => EMPTINESS_IGNORED_ENTRIES.has(e));
|
|
23402
|
+
} catch (err) {
|
|
23403
|
+
if (err.code === "ENOENT") return true;
|
|
23404
|
+
throw err;
|
|
23405
|
+
}
|
|
23406
|
+
}
|
|
23407
|
+
function runGit(args, cwd) {
|
|
23408
|
+
execFileSync9("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
23409
|
+
}
|
|
23410
|
+
function isGhReady() {
|
|
23411
|
+
try {
|
|
23412
|
+
execFileSync9("gh", ["auth", "status"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
23413
|
+
return true;
|
|
23414
|
+
} catch (err) {
|
|
23415
|
+
verbose(`setup: gh availability probe failed \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
23416
|
+
return false;
|
|
23417
|
+
}
|
|
23418
|
+
}
|
|
23419
|
+
async function resolveTargetName(dir, cwd, headless) {
|
|
23420
|
+
if (dir && dir.trim().length > 0) return dir.trim();
|
|
23421
|
+
if (await isDirEffectivelyEmpty(cwd)) return ".";
|
|
23422
|
+
if (headless) return ".";
|
|
23423
|
+
const { dirName } = await inquirer12.prompt([
|
|
23424
|
+
{
|
|
23425
|
+
type: "input",
|
|
23426
|
+
name: "dirName",
|
|
23427
|
+
message: "Directory name for the new project:",
|
|
23428
|
+
validate: (v) => v && v.trim().length > 0 ? true : "Enter a non-empty directory name"
|
|
23429
|
+
}
|
|
23430
|
+
]);
|
|
23431
|
+
return dirName.trim();
|
|
23432
|
+
}
|
|
23433
|
+
async function setupCommand(dir, opts = {}) {
|
|
23434
|
+
const startMs = Date.now();
|
|
23435
|
+
const headless = opts.yes === true;
|
|
23436
|
+
const format = beginCommand(opts, { banner: "none", interactive: !headless });
|
|
23437
|
+
const cwd = process.cwd();
|
|
23438
|
+
const targetName = await resolveTargetName(dir, cwd, headless);
|
|
23439
|
+
const targetDir = resolve8(cwd, targetName);
|
|
23440
|
+
const alreadyExists = await pathExists3(targetDir);
|
|
23441
|
+
if (alreadyExists && !await isDirEffectivelyEmpty(targetDir)) {
|
|
23442
|
+
throw new HatchError(
|
|
23443
|
+
`Target directory ${chalk12.bold(targetName)} already exists and is not empty.`,
|
|
23444
|
+
void 0,
|
|
23445
|
+
"FS_ERROR",
|
|
23446
|
+
`Pick a new or empty directory \u2014 e.g. \`hatch3r setup <new-dir>\` \u2014 or clear the existing contents first.`
|
|
23447
|
+
);
|
|
23448
|
+
}
|
|
23449
|
+
const gitDirExists = await pathExists3(join50(targetDir, ".git"));
|
|
23450
|
+
const willCreateDir = !alreadyExists;
|
|
23451
|
+
const willGitInit = !gitDirExists;
|
|
23452
|
+
const ghReady = opts.remote === true ? isGhReady() : false;
|
|
23453
|
+
const repoName = basename4(targetDir);
|
|
23454
|
+
if (opts.dryRun) {
|
|
23455
|
+
const lines = [];
|
|
23456
|
+
lines.push(
|
|
23457
|
+
willCreateDir ? `${chalk12.green("+")} create directory ${chalk12.bold(targetName)}` : `${chalk12.dim("=")} use existing empty directory ${chalk12.bold(targetName)}`
|
|
23458
|
+
);
|
|
23459
|
+
lines.push(
|
|
23460
|
+
willGitInit ? `${chalk12.green("+")} run \`git init\` in ${chalk12.bold(targetName)}` : `${chalk12.dim("=")} git repository already present \u2014 skip \`git init\``
|
|
23461
|
+
);
|
|
23462
|
+
if (opts.remote === true) {
|
|
23463
|
+
lines.push(
|
|
23464
|
+
ghReady ? `${chalk12.green("+")} create GitHub remote \`${repoName}\` via \`gh repo create\`` : `${chalk12.yellow("!")} \`--remote\` set but \`gh\` is unavailable/unauthenticated \u2014 skip remote`
|
|
23465
|
+
);
|
|
23466
|
+
}
|
|
23467
|
+
lines.push(`${chalk12.cyan(">")} run \`hatch3r init\` in ${chalk12.bold(targetName)}`);
|
|
23468
|
+
finishCommand(format, {
|
|
23469
|
+
command: "setup",
|
|
23470
|
+
title: "Setup (dry run)",
|
|
23471
|
+
style: "info",
|
|
23472
|
+
lines,
|
|
23473
|
+
json: {
|
|
23474
|
+
dryRun: true,
|
|
23475
|
+
targetDir,
|
|
23476
|
+
createDir: willCreateDir,
|
|
23477
|
+
gitInit: willGitInit,
|
|
23478
|
+
remote: opts.remote === true ? { requested: true, ghReady } : { requested: false },
|
|
23479
|
+
wouldRunInit: true
|
|
23480
|
+
},
|
|
23481
|
+
nextSteps: ["Re-run without --dry-run to scaffold and start init."],
|
|
23482
|
+
startMs
|
|
23483
|
+
});
|
|
23484
|
+
return;
|
|
23485
|
+
}
|
|
23486
|
+
await mkdir17(targetDir, { recursive: true });
|
|
23487
|
+
if (willCreateDir) {
|
|
23488
|
+
info(`Created project directory ${chalk12.bold(targetName)}`);
|
|
23489
|
+
} else {
|
|
23490
|
+
info(`Using directory ${chalk12.bold(targetName)}`);
|
|
23491
|
+
}
|
|
23492
|
+
if (willGitInit) {
|
|
23493
|
+
runGit(["init"], targetDir);
|
|
23494
|
+
info("Initialized empty Git repository");
|
|
23495
|
+
} else {
|
|
23496
|
+
info("Git repository already present \u2014 skipping `git init`");
|
|
23497
|
+
}
|
|
23498
|
+
if (opts.remote === true) {
|
|
23499
|
+
if (ghReady) {
|
|
23500
|
+
try {
|
|
23501
|
+
runGit(["status"], targetDir);
|
|
23502
|
+
execFileSync9("gh", ["repo", "create", repoName, "--source", targetDir, "--private"], {
|
|
23503
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
23504
|
+
});
|
|
23505
|
+
info(`Created GitHub remote ${chalk12.bold(repoName)} (private)`);
|
|
23506
|
+
} catch (err) {
|
|
23507
|
+
warn(
|
|
23508
|
+
`Could not create the GitHub remote: ${err instanceof Error ? err.message : String(err)}. Continuing with the local project; run \`gh repo create\` manually later.`
|
|
23509
|
+
);
|
|
23510
|
+
}
|
|
23511
|
+
} else {
|
|
23512
|
+
warn(
|
|
23513
|
+
`--remote was requested but the GitHub CLI (gh) is not installed or not authenticated. Skipping remote creation. Install gh and run \`gh auth login\`, then \`gh repo create\` from the new directory. Continuing with the local scaffold.`
|
|
23514
|
+
);
|
|
23515
|
+
}
|
|
23516
|
+
}
|
|
23517
|
+
process.chdir(targetDir);
|
|
23518
|
+
await initCommand({
|
|
23519
|
+
tools: opts.tools,
|
|
23520
|
+
preset: opts.preset,
|
|
23521
|
+
maturity: opts.maturity,
|
|
23522
|
+
yes: opts.yes,
|
|
23523
|
+
quiet: opts.quiet,
|
|
23524
|
+
format: opts.format,
|
|
23525
|
+
verbose: opts.verbose
|
|
23526
|
+
});
|
|
23527
|
+
}
|
|
23528
|
+
|
|
23529
|
+
// src/cli/commands/sync.ts
|
|
23530
|
+
import { readFile as readFile37, stat as stat16, readdir as readdir27, mkdir as mkdir19 } from "fs/promises";
|
|
23531
|
+
import { join as join53 } from "path";
|
|
23532
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
23533
|
+
import chalk13 from "chalk";
|
|
23534
|
+
import { parse as parseYaml12 } from "yaml";
|
|
23179
23535
|
init_safeWrite();
|
|
23180
23536
|
init_types();
|
|
23181
23537
|
init_version();
|
|
@@ -23184,8 +23540,8 @@ init_failureLog();
|
|
|
23184
23540
|
// src/content/userContent.ts
|
|
23185
23541
|
init_safeWrite();
|
|
23186
23542
|
init_customization();
|
|
23187
|
-
import { readdir as
|
|
23188
|
-
import { dirname as dirname23, join as
|
|
23543
|
+
import { readdir as readdir25, mkdir as mkdir18, stat as stat15, readFile as readFile35 } from "fs/promises";
|
|
23544
|
+
import { dirname as dirname23, join as join51 } from "path";
|
|
23189
23545
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
23190
23546
|
import { stringify as yamlStringify4 } from "yaml";
|
|
23191
23547
|
init_promptGuard();
|
|
@@ -23219,14 +23575,14 @@ async function discoverUserContent(rootDir) {
|
|
|
23219
23575
|
if (type === "skill") {
|
|
23220
23576
|
let dirents;
|
|
23221
23577
|
try {
|
|
23222
|
-
dirents = await
|
|
23578
|
+
dirents = await readdir25(dir, { withFileTypes: true });
|
|
23223
23579
|
} catch (err) {
|
|
23224
23580
|
if (err.code === "ENOENT") continue;
|
|
23225
23581
|
throw err;
|
|
23226
23582
|
}
|
|
23227
23583
|
for (const d of dirents) {
|
|
23228
23584
|
if (!d.isDirectory()) continue;
|
|
23229
|
-
const skillFile =
|
|
23585
|
+
const skillFile = join51(dir, d.name, "SKILL.md");
|
|
23230
23586
|
try {
|
|
23231
23587
|
await stat15(skillFile);
|
|
23232
23588
|
out.push({ type, name: d.name, path: skillFile });
|
|
@@ -23241,7 +23597,7 @@ async function discoverUserContent(rootDir) {
|
|
|
23241
23597
|
} else {
|
|
23242
23598
|
let entries;
|
|
23243
23599
|
try {
|
|
23244
|
-
entries = await
|
|
23600
|
+
entries = await readdir25(dir);
|
|
23245
23601
|
} catch (err) {
|
|
23246
23602
|
if (err.code === "ENOENT") continue;
|
|
23247
23603
|
throw err;
|
|
@@ -23249,7 +23605,7 @@ async function discoverUserContent(rootDir) {
|
|
|
23249
23605
|
for (const f of entries) {
|
|
23250
23606
|
if (!f.endsWith(".md")) continue;
|
|
23251
23607
|
const name = f.replace(/\.md$/, "");
|
|
23252
|
-
out.push({ type, name, path:
|
|
23608
|
+
out.push({ type, name, path: join51(dir, f) });
|
|
23253
23609
|
}
|
|
23254
23610
|
}
|
|
23255
23611
|
}
|
|
@@ -23335,22 +23691,22 @@ function userTypeDir(userRoot, type) {
|
|
|
23335
23691
|
command: "commands",
|
|
23336
23692
|
hook: "hooks"
|
|
23337
23693
|
};
|
|
23338
|
-
return
|
|
23694
|
+
return join51(userRoot, plural[type]);
|
|
23339
23695
|
}
|
|
23340
23696
|
|
|
23341
23697
|
// src/content/learningsLoader.ts
|
|
23342
|
-
import { readFile as readFile36, readdir as
|
|
23698
|
+
import { readFile as readFile36, readdir as readdir26 } from "fs/promises";
|
|
23343
23699
|
import { appendFileSync as appendFileSync4, mkdirSync as mkdirSync4 } from "fs";
|
|
23344
|
-
import { dirname as dirname24, join as
|
|
23700
|
+
import { dirname as dirname24, join as join52 } from "path";
|
|
23345
23701
|
init_failureLog();
|
|
23346
23702
|
init_types();
|
|
23347
23703
|
async function loadValidatedLearnings(rootDir, options = {}) {
|
|
23348
23704
|
const onWarn = options.onWarn ?? (() => void 0);
|
|
23349
23705
|
const source = options.source ?? "learnings-loader";
|
|
23350
|
-
const learningsDir =
|
|
23706
|
+
const learningsDir = join52(rootDir, HATCH3R_DIR, "learnings");
|
|
23351
23707
|
let entries;
|
|
23352
23708
|
try {
|
|
23353
|
-
entries = await
|
|
23709
|
+
entries = await readdir26(learningsDir);
|
|
23354
23710
|
} catch (err) {
|
|
23355
23711
|
if (err.code === "ENOENT") {
|
|
23356
23712
|
return { loaded: [], skipped: [] };
|
|
@@ -23373,7 +23729,7 @@ async function loadValidatedLearnings(rootDir, options = {}) {
|
|
|
23373
23729
|
announceSkip(rootDir, source, fileName, reasons, onWarn);
|
|
23374
23730
|
continue;
|
|
23375
23731
|
}
|
|
23376
|
-
const absolutePath =
|
|
23732
|
+
const absolutePath = join52(learningsDir, fileName);
|
|
23377
23733
|
let content;
|
|
23378
23734
|
try {
|
|
23379
23735
|
content = await readFile36(absolutePath, "utf-8");
|
|
@@ -23432,7 +23788,7 @@ function appendFailureLog(rootDir, source, err) {
|
|
|
23432
23788
|
try {
|
|
23433
23789
|
const entry = createFailureLogEntry(source, err);
|
|
23434
23790
|
const line = formatLogEntry(entry) + "\n";
|
|
23435
|
-
const failurePath =
|
|
23791
|
+
const failurePath = join52(rootDir, HATCH3R_DIR, FAILURE_LOG_FILE);
|
|
23436
23792
|
mkdirSync4(dirname24(failurePath), { recursive: true });
|
|
23437
23793
|
appendFileSync4(failurePath, line);
|
|
23438
23794
|
} catch {
|
|
@@ -23557,7 +23913,7 @@ function selectionSetFromManifest(content) {
|
|
|
23557
23913
|
|
|
23558
23914
|
// src/cli/commands/sync.ts
|
|
23559
23915
|
async function checkSpecFreshness(rootDir) {
|
|
23560
|
-
const specsDir =
|
|
23916
|
+
const specsDir = join53(rootDir, "docs", "specs");
|
|
23561
23917
|
try {
|
|
23562
23918
|
await stat16(specsDir);
|
|
23563
23919
|
} catch (err) {
|
|
@@ -23567,11 +23923,11 @@ async function checkSpecFreshness(rootDir) {
|
|
|
23567
23923
|
}
|
|
23568
23924
|
let oldestSpecMtime = Date.now();
|
|
23569
23925
|
try {
|
|
23570
|
-
const entries = await
|
|
23926
|
+
const entries = await readdir27(specsDir, { withFileTypes: true, recursive: true });
|
|
23571
23927
|
for (const entry of entries) {
|
|
23572
23928
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
23573
23929
|
const parentPath = entry.parentPath ?? entry.path ?? specsDir;
|
|
23574
|
-
const fileStat = await stat16(
|
|
23930
|
+
const fileStat = await stat16(join53(parentPath, entry.name));
|
|
23575
23931
|
if (fileStat.mtimeMs < oldestSpecMtime) {
|
|
23576
23932
|
oldestSpecMtime = fileStat.mtimeMs;
|
|
23577
23933
|
}
|
|
@@ -23582,7 +23938,7 @@ async function checkSpecFreshness(rootDir) {
|
|
|
23582
23938
|
return;
|
|
23583
23939
|
}
|
|
23584
23940
|
try {
|
|
23585
|
-
const commitDate =
|
|
23941
|
+
const commitDate = execFileSync10("git", ["log", "-1", "--format=%ct"], { stdio: "pipe", timeout: 5e3 }).toString().trim();
|
|
23586
23942
|
const latestCommitMs = parseInt(commitDate, 10) * 1e3;
|
|
23587
23943
|
if (latestCommitMs > oldestSpecMtime) {
|
|
23588
23944
|
const daysSinceSpecUpdate = Math.floor((Date.now() - oldestSpecMtime) / (1e3 * 60 * 60 * 24));
|
|
@@ -23647,7 +24003,7 @@ async function syncCommand(opts = {}) {
|
|
|
23647
24003
|
}
|
|
23648
24004
|
}
|
|
23649
24005
|
const syncStartMs = Date.now();
|
|
23650
|
-
const syncWorkspace =
|
|
24006
|
+
const syncWorkspace = join53(rootDir, ".sync-workspace");
|
|
23651
24007
|
const checkpointMeta = () => ({
|
|
23652
24008
|
baselineSha: HATCH3R_VERSION,
|
|
23653
24009
|
lastPassedGateN: 0,
|
|
@@ -23668,7 +24024,7 @@ async function syncCommand(opts = {}) {
|
|
|
23668
24024
|
const checkpoint = await readCheckpoint(syncWorkspace);
|
|
23669
24025
|
if (checkpoint === null) {
|
|
23670
24026
|
warn(
|
|
23671
|
-
`\`hatch3r sync --resume\` requested but no checkpoint found at ${
|
|
24027
|
+
`\`hatch3r sync --resume\` requested but no checkpoint found at ${join53(syncWorkspace, "checkpoint.json")}. Continuing as a fresh sync.`
|
|
23672
24028
|
);
|
|
23673
24029
|
} else if (checkpoint.meta.baselineSha === HATCH3R_VERSION && checkpoint.status === "passed") {
|
|
23674
24030
|
info(
|
|
@@ -23688,14 +24044,14 @@ async function syncCommand(opts = {}) {
|
|
|
23688
24044
|
const wsContext = await detectWorkspaceContext(rootDir);
|
|
23689
24045
|
if (wsContext.type === "workspace-member") {
|
|
23690
24046
|
warn(
|
|
23691
|
-
`This repository appears to be managed by a workspace at ${wsContext.workspaceRoot ?? ".."}. Run ${
|
|
24047
|
+
`This repository appears to be managed by a workspace at ${wsContext.workspaceRoot ?? ".."}. Run ${chalk13.cyan("hatch3r sync")} from the workspace root to sync all repos.`
|
|
23692
24048
|
);
|
|
23693
24049
|
}
|
|
23694
24050
|
if (wsContext.type === "workspace-root" || wsContext.type === "workspace-member") {
|
|
23695
24051
|
enableDefaultCrossProcessLocking();
|
|
23696
24052
|
}
|
|
23697
24053
|
await migrateAgentsToHatch3r(rootDir);
|
|
23698
|
-
const hatch3rDir =
|
|
24054
|
+
const hatch3rDir = join53(rootDir, HATCH3R_DIR);
|
|
23699
24055
|
const manifest = await readManifest(rootDir);
|
|
23700
24056
|
assertManifest(manifest);
|
|
23701
24057
|
const m = manifest;
|
|
@@ -23740,7 +24096,7 @@ async function syncCommand(opts = {}) {
|
|
|
23740
24096
|
verbose(`User-content pre-flight scan skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
23741
24097
|
}
|
|
23742
24098
|
try {
|
|
23743
|
-
const learnings = await validateLearningsDirectory(
|
|
24099
|
+
const learnings = await validateLearningsDirectory(join53(rootDir, HATCH3R_DIR, "learnings"));
|
|
23744
24100
|
const benignWarnings = learnings.warnings.filter((w) => !learnings.injectionHits.includes(w));
|
|
23745
24101
|
if (benignWarnings.length > 0) {
|
|
23746
24102
|
warn(`Learnings content scan: ${benignWarnings.length} advisory(ies):`);
|
|
@@ -23769,7 +24125,7 @@ async function syncCommand(opts = {}) {
|
|
|
23769
24125
|
warn("Continuing with --force: invalid/poisoned learnings will be materialized into tool context.");
|
|
23770
24126
|
console.log();
|
|
23771
24127
|
}
|
|
23772
|
-
const pruned = await pruneHandoffs(
|
|
24128
|
+
const pruned = await pruneHandoffs(join53(rootDir, HATCH3R_DIR));
|
|
23773
24129
|
if (pruned.archived.length > 0) {
|
|
23774
24130
|
warn(
|
|
23775
24131
|
`Handoffs: quarantined ${pruned.archived.length} past-expiry handoff(s) to ${HATCH3R_DIR}/handoffs/archived/ (off the resume read path): ${pruned.archived.join(", ")}`
|
|
@@ -23777,8 +24133,8 @@ async function syncCommand(opts = {}) {
|
|
|
23777
24133
|
}
|
|
23778
24134
|
for (const w of pruned.warnings) warn(` ${w}`);
|
|
23779
24135
|
const handoffs = await validateHandoffsDirectory(
|
|
23780
|
-
|
|
23781
|
-
{ archivedDir:
|
|
24136
|
+
join53(rootDir, HATCH3R_DIR, "handoffs", "active"),
|
|
24137
|
+
{ archivedDir: join53(rootDir, HATCH3R_DIR, "handoffs", "archived") }
|
|
23782
24138
|
);
|
|
23783
24139
|
if (handoffs.warnings.length > 0) {
|
|
23784
24140
|
warn(`Handoffs content scan: ${handoffs.warnings.length} advisory(ies):`);
|
|
@@ -23832,16 +24188,16 @@ async function syncCommand(opts = {}) {
|
|
|
23832
24188
|
const diffBefore = /* @__PURE__ */ new Map();
|
|
23833
24189
|
const diffAfter = /* @__PURE__ */ new Map();
|
|
23834
24190
|
const canonicalContentRoot = resolveBundledContentRoot();
|
|
23835
|
-
const syncSnapshotPaths = [
|
|
24191
|
+
const syncSnapshotPaths = [join53(rootDir, HATCH3R_DIR, "hatch.json")];
|
|
23836
24192
|
if (m.worktree?.enabled) {
|
|
23837
|
-
syncSnapshotPaths.push(
|
|
24193
|
+
syncSnapshotPaths.push(join53(rootDir, WORKTREE_INCLUDE_FILE));
|
|
23838
24194
|
}
|
|
23839
24195
|
for (const rel of m.managedFiles) {
|
|
23840
|
-
syncSnapshotPaths.push(
|
|
24196
|
+
syncSnapshotPaths.push(join53(rootDir, rel));
|
|
23841
24197
|
}
|
|
23842
24198
|
if (m.managedFilesByAdapter) {
|
|
23843
24199
|
for (const paths of Object.values(m.managedFilesByAdapter)) {
|
|
23844
|
-
for (const rel of paths) syncSnapshotPaths.push(
|
|
24200
|
+
for (const rel of paths) syncSnapshotPaths.push(join53(rootDir, rel));
|
|
23845
24201
|
}
|
|
23846
24202
|
}
|
|
23847
24203
|
const syncSnapshot = await withSnapshot(
|
|
@@ -23862,7 +24218,7 @@ async function syncCommand(opts = {}) {
|
|
|
23862
24218
|
const newManagedByAdapter = {};
|
|
23863
24219
|
const orphanEntries = [];
|
|
23864
24220
|
let budgetGateFailed = false;
|
|
23865
|
-
const breakerStatePath =
|
|
24221
|
+
const breakerStatePath = join53(hatch3rDir, BREAKER_STATE_FILE);
|
|
23866
24222
|
let breakers = /* @__PURE__ */ new Map();
|
|
23867
24223
|
try {
|
|
23868
24224
|
const breakerLog = await readFile37(breakerStatePath, "utf-8");
|
|
@@ -23990,8 +24346,8 @@ async function syncCommand(opts = {}) {
|
|
|
23990
24346
|
const previewActive = !!opts.previewTool && opts.previewTool === tool;
|
|
23991
24347
|
for (const out of outputs) {
|
|
23992
24348
|
const isManagedMerge = Boolean(out.managedContent);
|
|
23993
|
-
const existing = await readFileOrNull2(
|
|
23994
|
-
const predicted = predictMergeAction(existing, out.content,
|
|
24349
|
+
const existing = await readFileOrNull2(join53(rootDir, out.path));
|
|
24350
|
+
const predicted = predictMergeAction(existing, out.content, join53(rootDir, out.path), {
|
|
23995
24351
|
managedContent: out.managedContent,
|
|
23996
24352
|
appendIfNoBlock: out.managedContent ? true : void 0,
|
|
23997
24353
|
force: opts.force
|
|
@@ -24003,18 +24359,18 @@ async function syncCommand(opts = {}) {
|
|
|
24003
24359
|
}
|
|
24004
24360
|
if (previewActive) {
|
|
24005
24361
|
const banner = `
|
|
24006
|
-
${
|
|
24362
|
+
${chalk13.dim("\u2500\u2500\u2500")} ${chalk13.bold(out.path)} ${chalk13.dim("\u2500".repeat(40))}`;
|
|
24007
24363
|
console.error(banner);
|
|
24008
24364
|
console.error(out.content);
|
|
24009
|
-
console.error(
|
|
24365
|
+
console.error(chalk13.dim("\u2500".repeat(60)));
|
|
24010
24366
|
}
|
|
24011
24367
|
}
|
|
24012
24368
|
} else {
|
|
24013
24369
|
for (const out of outputs) {
|
|
24014
24370
|
if (opts.diff) {
|
|
24015
|
-
diffBefore.set(out.path, await readFileOrNull2(
|
|
24371
|
+
diffBefore.set(out.path, await readFileOrNull2(join53(rootDir, out.path)));
|
|
24016
24372
|
}
|
|
24017
|
-
const fullPath =
|
|
24373
|
+
const fullPath = join53(rootDir, out.path);
|
|
24018
24374
|
const isManagedMerge = Boolean(out.managedContent);
|
|
24019
24375
|
if (out.managedContent) {
|
|
24020
24376
|
const result = await safeWriteFile(fullPath, out.content, {
|
|
@@ -24047,7 +24403,7 @@ ${chalk12.dim("\u2500\u2500\u2500")} ${chalk12.bold(out.path)} ${chalk12.dim("\u
|
|
|
24047
24403
|
}
|
|
24048
24404
|
addManagedFile(m, out.path);
|
|
24049
24405
|
if (opts.diff) {
|
|
24050
|
-
diffAfter.set(out.path, await readFileOrNull2(
|
|
24406
|
+
diffAfter.set(out.path, await readFileOrNull2(join53(rootDir, out.path)));
|
|
24051
24407
|
}
|
|
24052
24408
|
}
|
|
24053
24409
|
}
|
|
@@ -24058,8 +24414,8 @@ ${chalk12.dim("\u2500\u2500\u2500")} ${chalk12.bold(out.path)} ${chalk12.dim("\u
|
|
|
24058
24414
|
for (const p of perPackage) {
|
|
24059
24415
|
if (opts.dryRun) {
|
|
24060
24416
|
const isManagedMerge = Boolean(p.output.managedContent);
|
|
24061
|
-
const existing = await readFileOrNull2(
|
|
24062
|
-
const predicted = predictMergeAction(existing, p.output.content,
|
|
24417
|
+
const existing = await readFileOrNull2(join53(rootDir, p.output.path));
|
|
24418
|
+
const predicted = predictMergeAction(existing, p.output.content, join53(rootDir, p.output.path), {
|
|
24063
24419
|
managedContent: p.output.managedContent,
|
|
24064
24420
|
appendIfNoBlock: p.output.managedContent ? true : void 0,
|
|
24065
24421
|
force: opts.force
|
|
@@ -24071,7 +24427,7 @@ ${chalk12.dim("\u2500\u2500\u2500")} ${chalk12.bold(out.path)} ${chalk12.dim("\u
|
|
|
24071
24427
|
}
|
|
24072
24428
|
continue;
|
|
24073
24429
|
}
|
|
24074
|
-
const fullPath =
|
|
24430
|
+
const fullPath = join53(rootDir, p.output.path);
|
|
24075
24431
|
if (opts.diff) {
|
|
24076
24432
|
diffBefore.set(p.output.path, await readFileOrNull2(fullPath));
|
|
24077
24433
|
}
|
|
@@ -24171,7 +24527,7 @@ ${chalk12.dim("\u2500\u2500\u2500")} ${chalk12.bold(out.path)} ${chalk12.dim("\u
|
|
|
24171
24527
|
await recordPhase(1, "passed");
|
|
24172
24528
|
if (breakers.size > 0) {
|
|
24173
24529
|
try {
|
|
24174
|
-
await
|
|
24530
|
+
await mkdir19(hatch3rDir, { recursive: true });
|
|
24175
24531
|
await safeWriteFile(breakerStatePath, serializeBreakerMap(breakers));
|
|
24176
24532
|
verbose(`Persisted ${breakers.size} circuit breaker(s) to ${BREAKER_STATE_FILE}`);
|
|
24177
24533
|
} catch (err) {
|
|
@@ -24214,7 +24570,7 @@ ${chalk12.dim("\u2500\u2500\u2500")} ${chalk12.bold(out.path)} ${chalk12.dim("\u
|
|
|
24214
24570
|
const wtContent = await generateWorktreeInclude(m, rootDir);
|
|
24215
24571
|
const wtManaged = extractManagedContent(wtContent);
|
|
24216
24572
|
const wtResult = await safeWriteFile(
|
|
24217
|
-
|
|
24573
|
+
join53(rootDir, WORKTREE_INCLUDE_FILE),
|
|
24218
24574
|
wtContent,
|
|
24219
24575
|
{ managedContent: wtManaged, appendIfNoBlock: true, force: opts.force }
|
|
24220
24576
|
);
|
|
@@ -24281,7 +24637,7 @@ ${chalk12.dim("\u2500\u2500\u2500")} ${chalk12.bold(out.path)} ${chalk12.dim("\u
|
|
|
24281
24637
|
const CUSTOMIZE_DIRS = ["agents", "commands", "skills", "rules"];
|
|
24282
24638
|
for (const dir of CUSTOMIZE_DIRS) {
|
|
24283
24639
|
try {
|
|
24284
|
-
const files = await
|
|
24640
|
+
const files = await readdir27(join53(rootDir, ".hatch3r", dir));
|
|
24285
24641
|
for (const f of files.filter((f2) => f2.endsWith(".customize.yaml") || f2.endsWith(".customize.md"))) {
|
|
24286
24642
|
const itemId = f.replace(/\.customize\.(yaml|md)$/, "");
|
|
24287
24643
|
const prefixed = `hatch3r-${itemId}`;
|
|
@@ -24289,14 +24645,14 @@ ${chalk12.dim("\u2500\u2500\u2500")} ${chalk12.bold(out.path)} ${chalk12.dim("\u
|
|
|
24289
24645
|
warn(`Orphaned customization: .hatch3r/${dir}/${f} \u2014 content no longer in manifest. Consider removing it.`);
|
|
24290
24646
|
}
|
|
24291
24647
|
if (f.endsWith(".customize.yaml")) {
|
|
24292
|
-
const yamlPath =
|
|
24648
|
+
const yamlPath = join53(rootDir, ".hatch3r", dir, f);
|
|
24293
24649
|
try {
|
|
24294
24650
|
const raw = await readFile37(yamlPath, "utf-8");
|
|
24295
24651
|
if (Buffer.byteLength(raw, "utf-8") > 10240) {
|
|
24296
24652
|
warn(`.hatch3r/${dir}/${f} exceeds 10KB and will be skipped during adapter generation. Trim or split the override.`);
|
|
24297
24653
|
} else {
|
|
24298
24654
|
try {
|
|
24299
|
-
|
|
24655
|
+
parseYaml12(raw);
|
|
24300
24656
|
} catch (parseErr) {
|
|
24301
24657
|
const msg = parseErr instanceof Error ? parseErr.message : String(parseErr);
|
|
24302
24658
|
warn(`Invalid YAML in .hatch3r/${dir}/${f} \u2014 ${msg}. Run \`npx hatch3r validate\` for the full error context.`);
|
|
@@ -24319,7 +24675,7 @@ ${chalk12.dim("\u2500\u2500\u2500")} ${chalk12.bold(out.path)} ${chalk12.dim("\u
|
|
|
24319
24675
|
}
|
|
24320
24676
|
console.log();
|
|
24321
24677
|
const icons = {
|
|
24322
|
-
created:
|
|
24678
|
+
created: chalk13.green("+"),
|
|
24323
24679
|
// F10.4-1: split the prior `updated` icon into `merged` (managed-block
|
|
24324
24680
|
// merge — user content preserved) and `regenerated` (full-file
|
|
24325
24681
|
// overwrite) so the customization-preservation messaging in the
|
|
@@ -24327,24 +24683,24 @@ ${chalk12.dim("\u2500\u2500\u2500")} ${chalk12.bold(out.path)} ${chalk12.dim("\u
|
|
|
24327
24683
|
// fallback for any call-path that has not yet adopted the
|
|
24328
24684
|
// `renderAction()` heuristic at the call-site (none in this file
|
|
24329
24685
|
// after F10.4-1; reserved for forward-compatibility).
|
|
24330
|
-
updated:
|
|
24331
|
-
merged:
|
|
24332
|
-
regenerated:
|
|
24686
|
+
updated: chalk13.yellow("~"),
|
|
24687
|
+
merged: chalk13.cyan("*"),
|
|
24688
|
+
regenerated: chalk13.yellow("~"),
|
|
24333
24689
|
// G5: "unchanged" is a no-op action returned by safeWriteFile when the
|
|
24334
24690
|
// computed bytes match the file on disk. We render it the same as
|
|
24335
24691
|
// "skipped" (dim "=") so the human summary remains readable while still
|
|
24336
24692
|
// signalling that nothing changed.
|
|
24337
|
-
unchanged:
|
|
24338
|
-
skipped:
|
|
24339
|
-
"dry-run":
|
|
24693
|
+
unchanged: chalk13.dim("="),
|
|
24694
|
+
skipped: chalk13.dim("="),
|
|
24695
|
+
"dry-run": chalk13.cyan("?")
|
|
24340
24696
|
};
|
|
24341
24697
|
const compactedResults = compactPhaseOutput(results);
|
|
24342
24698
|
const summaryLines = compactedResults.map((r) => {
|
|
24343
24699
|
if (typeof r === "string") {
|
|
24344
|
-
return
|
|
24700
|
+
return chalk13.dim(r);
|
|
24345
24701
|
}
|
|
24346
|
-
const icon = icons[r.action] ??
|
|
24347
|
-
return `${icon} ${r.path} ${
|
|
24702
|
+
const icon = icons[r.action] ?? chalk13.dim(" ");
|
|
24703
|
+
return `${icon} ${r.path} ${chalk13.dim(`(${r.action})`)}`;
|
|
24348
24704
|
});
|
|
24349
24705
|
const actionCounts = {};
|
|
24350
24706
|
for (const r of results) {
|
|
@@ -24353,15 +24709,15 @@ ${chalk12.dim("\u2500\u2500\u2500")} ${chalk12.bold(out.path)} ${chalk12.dim("\u
|
|
|
24353
24709
|
}
|
|
24354
24710
|
}
|
|
24355
24711
|
const tallyParts = [];
|
|
24356
|
-
if (actionCounts.created) tallyParts.push(
|
|
24357
|
-
if (actionCounts.merged) tallyParts.push(
|
|
24358
|
-
if (actionCounts.regenerated) tallyParts.push(
|
|
24359
|
-
if (actionCounts.updated) tallyParts.push(
|
|
24360
|
-
if (actionCounts.unchanged) tallyParts.push(
|
|
24361
|
-
if (actionCounts.skipped) tallyParts.push(
|
|
24362
|
-
if (actionCounts["dry-run"]) tallyParts.push(
|
|
24712
|
+
if (actionCounts.created) tallyParts.push(chalk13.green(`${actionCounts.created} created`));
|
|
24713
|
+
if (actionCounts.merged) tallyParts.push(chalk13.cyan(`${actionCounts.merged} merged (your edits preserved)`));
|
|
24714
|
+
if (actionCounts.regenerated) tallyParts.push(chalk13.yellow(`${actionCounts.regenerated} regenerated (full overwrite)`));
|
|
24715
|
+
if (actionCounts.updated) tallyParts.push(chalk13.yellow(`${actionCounts.updated} updated`));
|
|
24716
|
+
if (actionCounts.unchanged) tallyParts.push(chalk13.dim(`${actionCounts.unchanged} unchanged`));
|
|
24717
|
+
if (actionCounts.skipped) tallyParts.push(chalk13.dim(`${actionCounts.skipped} skipped`));
|
|
24718
|
+
if (actionCounts["dry-run"]) tallyParts.push(chalk13.cyan(`${actionCounts["dry-run"]} dry-run`));
|
|
24363
24719
|
if (tallyParts.length > 0) {
|
|
24364
|
-
summaryLines.unshift(tallyParts.join(
|
|
24720
|
+
summaryLines.unshift(tallyParts.join(chalk13.dim(" \xB7 ")));
|
|
24365
24721
|
summaryLines.splice(1, 0, "");
|
|
24366
24722
|
}
|
|
24367
24723
|
if (opts.diff && diffBefore.size > 0) {
|
|
@@ -24370,11 +24726,11 @@ ${chalk12.dim("\u2500\u2500\u2500")} ${chalk12.bold(out.path)} ${chalk12.dim("\u
|
|
|
24370
24726
|
const before = diffBefore.get(filePath) ?? null;
|
|
24371
24727
|
const after = diffAfter.get(filePath) ?? null;
|
|
24372
24728
|
if (before === null && after !== null) {
|
|
24373
|
-
diffLines.push(`${
|
|
24729
|
+
diffLines.push(`${chalk13.green("+ added")} ${filePath}`);
|
|
24374
24730
|
} else if (before !== null && after !== null && before !== after) {
|
|
24375
|
-
diffLines.push(`${
|
|
24731
|
+
diffLines.push(`${chalk13.yellow("~ modified")} ${filePath}`);
|
|
24376
24732
|
} else if (before !== null && after !== null && before === after) {
|
|
24377
|
-
diffLines.push(`${
|
|
24733
|
+
diffLines.push(`${chalk13.dim("= unchanged")} ${filePath}`);
|
|
24378
24734
|
}
|
|
24379
24735
|
}
|
|
24380
24736
|
if (diffLines.length > 0) {
|
|
@@ -24385,7 +24741,7 @@ ${chalk12.dim("\u2500\u2500\u2500")} ${chalk12.bold(out.path)} ${chalk12.dim("\u
|
|
|
24385
24741
|
const boxTitle = opts.dryRun ? "Sync dry run complete" : adapterFailures.length > 0 ? "Sync complete (with warnings)" : "Sync complete";
|
|
24386
24742
|
if (syncSessionId) {
|
|
24387
24743
|
summaryLines.push("");
|
|
24388
|
-
summaryLines.push(`${
|
|
24744
|
+
summaryLines.push(`${chalk13.dim("Snapshot:")} ${syncSessionId} ${chalk13.dim(`(revert: hatch3r rollback --session=${syncSessionId})`)}`);
|
|
24389
24745
|
}
|
|
24390
24746
|
finishCommand(format, {
|
|
24391
24747
|
command: "sync",
|
|
@@ -24421,11 +24777,11 @@ ${chalk12.dim("\u2500\u2500\u2500")} ${chalk12.bold(out.path)} ${chalk12.dim("\u
|
|
|
24421
24777
|
const head = activeIds.slice(0, 4).join(", ");
|
|
24422
24778
|
const tail = activeIds.length > 4 ? ` \u2026 (+${activeIds.length - 4} more)` : "";
|
|
24423
24779
|
info(
|
|
24424
|
-
`Customizations applied: ${
|
|
24780
|
+
`Customizations applied: ${chalk13.bold(String(c.active))} active (${head}${tail})` + (c.skipped > 0 ? `, ${c.skipped} skipped` : "") + (c.failed > 0 ? `, ${chalk13.red(String(c.failed))} failed` : "") + (c.inert > 0 ? `, ${chalk13.yellow(String(c.inert))} inert` : "")
|
|
24425
24781
|
);
|
|
24426
24782
|
} else if (c.skipped > 0 || c.failed > 0 || c.inert > 0) {
|
|
24427
24783
|
info(
|
|
24428
|
-
`Customizations: 0 active` + (c.skipped > 0 ? `, ${c.skipped} skipped` : "") + (c.failed > 0 ? `, ${
|
|
24784
|
+
`Customizations: 0 active` + (c.skipped > 0 ? `, ${c.skipped} skipped` : "") + (c.failed > 0 ? `, ${chalk13.red(String(c.failed))} failed` : "") + (c.inert > 0 ? `, ${chalk13.yellow(String(c.inert))} inert` : "") + ` (run \`hatch3r explain --customizations\` for detail).`
|
|
24429
24785
|
);
|
|
24430
24786
|
}
|
|
24431
24787
|
}
|
|
@@ -24481,7 +24837,7 @@ ${chalk12.dim("\u2500\u2500\u2500")} ${chalk12.bold(out.path)} ${chalk12.dim("\u
|
|
|
24481
24837
|
const syncableCount = wsManifest.repos.filter((r) => r.sync).length;
|
|
24482
24838
|
if (!syncReposRequested && !syncOnSync) {
|
|
24483
24839
|
if (syncableCount > 0) {
|
|
24484
|
-
info(`Workspace: ${syncableCount} repo(s) available for sync. Run ${
|
|
24840
|
+
info(`Workspace: ${syncableCount} repo(s) available for sync. Run ${chalk13.bold("hatch3r sync --repos")} to propagate.`);
|
|
24485
24841
|
}
|
|
24486
24842
|
return;
|
|
24487
24843
|
}
|
|
@@ -24513,13 +24869,13 @@ ${chalk12.dim("\u2500\u2500\u2500")} ${chalk12.bold(out.path)} ${chalk12.dim("\u
|
|
|
24513
24869
|
}
|
|
24514
24870
|
|
|
24515
24871
|
// src/cli/commands/validate.ts
|
|
24516
|
-
import { readdir as
|
|
24872
|
+
import { readdir as readdir29, readFile as readFile39, access as access17, stat as stat17 } from "fs/promises";
|
|
24517
24873
|
import { existsSync as existsSync5 } from "fs";
|
|
24518
24874
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
24519
|
-
import { dirname as dirname26, join as
|
|
24875
|
+
import { dirname as dirname26, join as join55, posix as posix3 } from "path";
|
|
24520
24876
|
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
24521
|
-
import
|
|
24522
|
-
import { parse as
|
|
24877
|
+
import chalk14 from "chalk";
|
|
24878
|
+
import { parse as parseYaml13 } from "yaml";
|
|
24523
24879
|
init_types();
|
|
24524
24880
|
init_version();
|
|
24525
24881
|
init_customization();
|
|
@@ -24689,8 +25045,8 @@ function detectSecrets(envVars) {
|
|
|
24689
25045
|
|
|
24690
25046
|
// src/pipeline/complianceVerification.ts
|
|
24691
25047
|
init_agentToolAllowlist();
|
|
24692
|
-
import { readFile as readFile38, readdir as
|
|
24693
|
-
import { dirname as dirname25, join as
|
|
25048
|
+
import { readFile as readFile38, readdir as readdir28 } from "fs/promises";
|
|
25049
|
+
import { dirname as dirname25, join as join54 } from "path";
|
|
24694
25050
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
24695
25051
|
|
|
24696
25052
|
// src/pipeline/reviewLoop.ts
|
|
@@ -24794,13 +25150,13 @@ var RESILIENCE_MODULES = [
|
|
|
24794
25150
|
var __dirname5 = dirname25(fileURLToPath8(import.meta.url));
|
|
24795
25151
|
async function resolveCommandsDir() {
|
|
24796
25152
|
const candidates = [
|
|
24797
|
-
|
|
24798
|
-
|
|
24799
|
-
|
|
25153
|
+
join54(__dirname5, "..", "cli", "commands"),
|
|
25154
|
+
join54(__dirname5, "..", "..", "src", "cli", "commands"),
|
|
25155
|
+
join54(__dirname5, "..", "cli")
|
|
24800
25156
|
];
|
|
24801
25157
|
for (const candidate of candidates) {
|
|
24802
25158
|
try {
|
|
24803
|
-
const entries = await
|
|
25159
|
+
const entries = await readdir28(candidate);
|
|
24804
25160
|
if (entries.length > 0) return candidate;
|
|
24805
25161
|
} catch (err) {
|
|
24806
25162
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -24815,13 +25171,13 @@ async function detectResilienceInvocations() {
|
|
|
24815
25171
|
if (!commandsDir) return invoked;
|
|
24816
25172
|
let entries;
|
|
24817
25173
|
try {
|
|
24818
|
-
entries = await
|
|
25174
|
+
entries = await readdir28(commandsDir, { recursive: true });
|
|
24819
25175
|
} catch (err) {
|
|
24820
25176
|
const message = err instanceof Error ? err.message : String(err);
|
|
24821
25177
|
verbose(`complianceVerification: detectResilienceInvocations readdir(${commandsDir}) \u2192 empty \u2014 ${message}`);
|
|
24822
25178
|
return invoked;
|
|
24823
25179
|
}
|
|
24824
|
-
const files = entries.filter((e) => typeof e === "string" && (e.endsWith(".ts") || e.endsWith(".js"))).map((e) =>
|
|
25180
|
+
const files = entries.filter((e) => typeof e === "string" && (e.endsWith(".ts") || e.endsWith(".js"))).map((e) => join54(commandsDir, e));
|
|
24825
25181
|
const patterns = {};
|
|
24826
25182
|
for (const mod of RESILIENCE_MODULES) {
|
|
24827
25183
|
patterns[mod] = new RegExp(`pipeline/${mod}(?:\\.js)?["']`);
|
|
@@ -24874,17 +25230,17 @@ async function verifyAdapterSignalPropagation() {
|
|
|
24874
25230
|
warnings: [],
|
|
24875
25231
|
async generate(_canonicalRoot, _manifest, _userRepoRoot, _generationMode, signal) {
|
|
24876
25232
|
signalDefined = signal !== void 0;
|
|
24877
|
-
await new Promise((
|
|
25233
|
+
await new Promise((resolve10) => {
|
|
24878
25234
|
if (signal?.aborted) {
|
|
24879
25235
|
signalAborted = true;
|
|
24880
|
-
|
|
25236
|
+
resolve10();
|
|
24881
25237
|
return;
|
|
24882
25238
|
}
|
|
24883
25239
|
signal?.addEventListener(
|
|
24884
25240
|
"abort",
|
|
24885
25241
|
() => {
|
|
24886
25242
|
signalAborted = true;
|
|
24887
|
-
|
|
25243
|
+
resolve10();
|
|
24888
25244
|
},
|
|
24889
25245
|
{ once: true }
|
|
24890
25246
|
);
|
|
@@ -25260,7 +25616,7 @@ async function validateManifest2(rootDir, manifest, result) {
|
|
|
25260
25616
|
if (!manifest.tools || manifest.tools.length === 0) result.warnings.push("hatch.json: no tools configured");
|
|
25261
25617
|
for (const managedFile of manifest.managedFiles ?? []) {
|
|
25262
25618
|
try {
|
|
25263
|
-
await
|
|
25619
|
+
await access17(join55(rootDir, managedFile));
|
|
25264
25620
|
} catch (err) {
|
|
25265
25621
|
if (err.code !== "ENOENT") throw err;
|
|
25266
25622
|
result.warnings.push(`Managed file missing from disk: ${managedFile}`);
|
|
@@ -25272,7 +25628,7 @@ async function validateDirectories(canonicalRoot, result) {
|
|
|
25272
25628
|
const optionalDirs = ["commands", "prompts", "mcp", "policy", "github-agents"];
|
|
25273
25629
|
for (const dir of requiredDirs) {
|
|
25274
25630
|
try {
|
|
25275
|
-
await
|
|
25631
|
+
await access17(join55(canonicalRoot, dir));
|
|
25276
25632
|
} catch (err) {
|
|
25277
25633
|
if (err.code !== "ENOENT") throw err;
|
|
25278
25634
|
result.errors.push(`Required canonical directory missing: ${dir}/ (under bundled content root)`);
|
|
@@ -25280,7 +25636,7 @@ async function validateDirectories(canonicalRoot, result) {
|
|
|
25280
25636
|
}
|
|
25281
25637
|
for (const dir of optionalDirs) {
|
|
25282
25638
|
try {
|
|
25283
|
-
await
|
|
25639
|
+
await access17(join55(canonicalRoot, dir));
|
|
25284
25640
|
} catch (err) {
|
|
25285
25641
|
if (err.code !== "ENOENT") throw err;
|
|
25286
25642
|
verboseWarn(result, `Optional canonical directory missing: ${dir}/ (under bundled content root)`);
|
|
@@ -25291,12 +25647,12 @@ async function validateFrontmatter(canonicalRoot, result) {
|
|
|
25291
25647
|
const requiredDirs = ["agents", "skills", "rules"];
|
|
25292
25648
|
const optionalDirs = ["commands", "prompts", "mcp", "policy", "github-agents"];
|
|
25293
25649
|
for (const dir of [...requiredDirs, ...optionalDirs]) {
|
|
25294
|
-
const dirPath =
|
|
25650
|
+
const dirPath = join55(canonicalRoot, dir);
|
|
25295
25651
|
try {
|
|
25296
|
-
const entries = await
|
|
25652
|
+
const entries = await readdir29(dirPath, { withFileTypes: true });
|
|
25297
25653
|
for (const entry of entries) {
|
|
25298
25654
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
25299
|
-
const filePath =
|
|
25655
|
+
const filePath = join55(dirPath, entry.name);
|
|
25300
25656
|
const content = await readFile39(filePath, "utf-8");
|
|
25301
25657
|
const label2 = `${dir}/${entry.name}`;
|
|
25302
25658
|
if (!content.startsWith("---")) {
|
|
@@ -25307,7 +25663,7 @@ async function validateFrontmatter(canonicalRoot, result) {
|
|
|
25307
25663
|
result.errors.push(`Invalid frontmatter (no closing ---): ${label2}`);
|
|
25308
25664
|
} else {
|
|
25309
25665
|
const frontmatter = content.slice(3, endIdx).trim();
|
|
25310
|
-
const parsedFm =
|
|
25666
|
+
const parsedFm = parseYaml13(frontmatter);
|
|
25311
25667
|
const idField = dir === "github-agents" ? "name" : "id";
|
|
25312
25668
|
if (!parsedFm || typeof parsedFm !== "object" || !parsedFm[idField]) {
|
|
25313
25669
|
result.warnings.push(`Missing '${idField}' in frontmatter: ${label2}`);
|
|
@@ -25328,9 +25684,9 @@ async function validateFrontmatter(canonicalRoot, result) {
|
|
|
25328
25684
|
}
|
|
25329
25685
|
} else if (entry.isDirectory()) {
|
|
25330
25686
|
if (dir !== "skills") continue;
|
|
25331
|
-
const skillPath =
|
|
25687
|
+
const skillPath = join55(dirPath, entry.name, "SKILL.md");
|
|
25332
25688
|
try {
|
|
25333
|
-
await
|
|
25689
|
+
await access17(skillPath);
|
|
25334
25690
|
} catch (err) {
|
|
25335
25691
|
if (err.code !== "ENOENT") throw err;
|
|
25336
25692
|
result.warnings.push(`Skill directory missing SKILL.md: ${dir}/${entry.name}/`);
|
|
@@ -25544,29 +25900,29 @@ async function validateManagedFilePrefixes(manifest, result) {
|
|
|
25544
25900
|
}
|
|
25545
25901
|
async function validateHooks(canonicalRoot, manifest, result) {
|
|
25546
25902
|
if (!manifest.features.hooks) return;
|
|
25547
|
-
const hooksDir =
|
|
25903
|
+
const hooksDir = join55(canonicalRoot, "hooks");
|
|
25548
25904
|
try {
|
|
25549
|
-
const hookFiles = await
|
|
25905
|
+
const hookFiles = await readdir29(hooksDir);
|
|
25550
25906
|
const mdHooks = hookFiles.filter((f) => f.endsWith(".md"));
|
|
25551
25907
|
if (mdHooks.length === 0) {
|
|
25552
25908
|
result.warnings.push("Hooks feature enabled but no hook definitions found in bundled hooks/");
|
|
25553
25909
|
}
|
|
25554
25910
|
let agentFiles;
|
|
25555
25911
|
try {
|
|
25556
|
-
const agentEntries = await
|
|
25912
|
+
const agentEntries = await readdir29(join55(canonicalRoot, "agents"));
|
|
25557
25913
|
agentFiles = new Set(agentEntries.filter((f) => f.endsWith(".md")));
|
|
25558
25914
|
} catch (err) {
|
|
25559
25915
|
if (err.code !== "ENOENT") throw err;
|
|
25560
25916
|
}
|
|
25561
25917
|
for (const hookFile of mdHooks) {
|
|
25562
|
-
const hookContent = await readFile39(
|
|
25918
|
+
const hookContent = await readFile39(join55(hooksDir, hookFile), "utf-8");
|
|
25563
25919
|
if (!hookContent.startsWith("---")) {
|
|
25564
25920
|
result.warnings.push(`Hook missing frontmatter: hooks/${hookFile}`);
|
|
25565
25921
|
continue;
|
|
25566
25922
|
}
|
|
25567
25923
|
const endIdx = hookContent.indexOf("---", 3);
|
|
25568
25924
|
if (endIdx === -1) continue;
|
|
25569
|
-
const fm =
|
|
25925
|
+
const fm = parseYaml13(hookContent.slice(3, endIdx).trim());
|
|
25570
25926
|
if (fm?.event && typeof fm.event === "string") {
|
|
25571
25927
|
if (!isValidHookEvent(fm.event)) {
|
|
25572
25928
|
result.errors.push(`Hook "${hookFile}" has invalid event "${fm.event}". Valid events: ${[...VALID_HOOK_EVENTS].join(", ")}`);
|
|
@@ -25591,7 +25947,7 @@ async function validateHooks(canonicalRoot, manifest, result) {
|
|
|
25591
25947
|
}
|
|
25592
25948
|
async function validateMcp(canonicalRoot, manifest, result) {
|
|
25593
25949
|
if (!manifest.features.mcp || manifest.mcp.servers.length === 0) return;
|
|
25594
|
-
const mcpPath =
|
|
25950
|
+
const mcpPath = join55(canonicalRoot, "mcp", "mcp.json");
|
|
25595
25951
|
try {
|
|
25596
25952
|
const mcpContent = await readFile39(mcpPath, "utf-8");
|
|
25597
25953
|
const mcpParsed = JSON.parse(mcpContent);
|
|
@@ -25660,17 +26016,17 @@ async function validateCustomizeYaml(rootDir, result) {
|
|
|
25660
26016
|
enabled: "boolean"
|
|
25661
26017
|
};
|
|
25662
26018
|
for (const { dir } of CUSTOMIZATION_TYPES2) {
|
|
25663
|
-
const customDir =
|
|
26019
|
+
const customDir = join55(rootDir, ".hatch3r", dir);
|
|
25664
26020
|
let files;
|
|
25665
26021
|
try {
|
|
25666
|
-
files = await
|
|
26022
|
+
files = await readdir29(customDir);
|
|
25667
26023
|
} catch (err) {
|
|
25668
26024
|
if (err.code === "ENOENT") continue;
|
|
25669
26025
|
throw err;
|
|
25670
26026
|
}
|
|
25671
26027
|
const yamlFiles = files.filter((f) => f.endsWith(".customize.yaml"));
|
|
25672
26028
|
for (const file of yamlFiles) {
|
|
25673
|
-
const filePath =
|
|
26029
|
+
const filePath = join55(customDir, file);
|
|
25674
26030
|
const itemId = file.replace(".customize.yaml", "");
|
|
25675
26031
|
let raw;
|
|
25676
26032
|
try {
|
|
@@ -25686,7 +26042,7 @@ async function validateCustomizeYaml(rootDir, result) {
|
|
|
25686
26042
|
}
|
|
25687
26043
|
let parsed;
|
|
25688
26044
|
try {
|
|
25689
|
-
parsed =
|
|
26045
|
+
parsed = parseYaml13(raw);
|
|
25690
26046
|
} catch {
|
|
25691
26047
|
result.errors.push(
|
|
25692
26048
|
`Invalid YAML syntax in .hatch3r/${dir}/${file}`
|
|
@@ -25737,10 +26093,10 @@ async function validateCustomizeYaml(rootDir, result) {
|
|
|
25737
26093
|
}
|
|
25738
26094
|
async function validateCustomizations(rootDir, agentsDir, manifest, result) {
|
|
25739
26095
|
for (const { dir, canonical } of CUSTOMIZATION_TYPES2) {
|
|
25740
|
-
const customDir =
|
|
26096
|
+
const customDir = join55(rootDir, ".hatch3r", dir);
|
|
25741
26097
|
const strategy = canonical === "skills" ? "subdir" : "glob";
|
|
25742
26098
|
try {
|
|
25743
|
-
const customFiles = await
|
|
26099
|
+
const customFiles = await readdir29(customDir);
|
|
25744
26100
|
for (const file of customFiles) {
|
|
25745
26101
|
if (file.endsWith(".customize.yaml")) {
|
|
25746
26102
|
const itemId = file.replace(".customize.yaml", "");
|
|
@@ -25758,9 +26114,9 @@ async function validateCustomizations(rootDir, agentsDir, manifest, result) {
|
|
|
25758
26114
|
async function findContentFile(agentsDir, cfg, id) {
|
|
25759
26115
|
const baseId = id.startsWith("cmd-") ? id.slice(4) : id;
|
|
25760
26116
|
if (cfg.strategy === "subdir") {
|
|
25761
|
-
const path =
|
|
26117
|
+
const path = join55(agentsDir, cfg.dir, baseId, "SKILL.md");
|
|
25762
26118
|
try {
|
|
25763
|
-
await
|
|
26119
|
+
await access17(path);
|
|
25764
26120
|
return path;
|
|
25765
26121
|
} catch (err) {
|
|
25766
26122
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -25768,21 +26124,21 @@ async function findContentFile(agentsDir, cfg, id) {
|
|
|
25768
26124
|
return null;
|
|
25769
26125
|
}
|
|
25770
26126
|
}
|
|
25771
|
-
const root =
|
|
26127
|
+
const root = join55(agentsDir, cfg.dir);
|
|
25772
26128
|
const stack = [root];
|
|
25773
26129
|
const mdFiles = [];
|
|
25774
26130
|
while (stack.length > 0) {
|
|
25775
26131
|
const dir = stack.pop();
|
|
25776
26132
|
let entries;
|
|
25777
26133
|
try {
|
|
25778
|
-
entries = await
|
|
26134
|
+
entries = await readdir29(dir, { withFileTypes: true });
|
|
25779
26135
|
} catch (err) {
|
|
25780
26136
|
const message = err instanceof Error ? err.message : String(err);
|
|
25781
26137
|
verbose(`validate: findContentFile readdir(${dir}) skipped \u2014 ${message}`);
|
|
25782
26138
|
continue;
|
|
25783
26139
|
}
|
|
25784
26140
|
for (const entry of entries) {
|
|
25785
|
-
const full =
|
|
26141
|
+
const full = join55(dir, entry.name);
|
|
25786
26142
|
if (entry.isDirectory()) {
|
|
25787
26143
|
stack.push(full);
|
|
25788
26144
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -25799,7 +26155,7 @@ async function findContentFile(agentsDir, cfg, id) {
|
|
|
25799
26155
|
if (!raw.startsWith("---")) continue;
|
|
25800
26156
|
const endIdx = raw.indexOf("---", 3);
|
|
25801
26157
|
if (endIdx === -1) continue;
|
|
25802
|
-
const fm =
|
|
26158
|
+
const fm = parseYaml13(raw.slice(3, endIdx).trim());
|
|
25803
26159
|
const fmId = fm && typeof fm === "object" && typeof fm.id === "string" ? fm.id : null;
|
|
25804
26160
|
if (fmId && (fmId === id || fmId === baseId)) {
|
|
25805
26161
|
return file;
|
|
@@ -25836,9 +26192,9 @@ async function validateContentConsistency(rootDir, agentsDir, manifest, result)
|
|
|
25836
26192
|
for (const id of ids) allContentIds.add(id);
|
|
25837
26193
|
}
|
|
25838
26194
|
for (const { dir } of CUSTOMIZATION_TYPES2) {
|
|
25839
|
-
const customDir =
|
|
26195
|
+
const customDir = join55(rootDir, ".hatch3r", dir);
|
|
25840
26196
|
try {
|
|
25841
|
-
const files = await
|
|
26197
|
+
const files = await readdir29(customDir);
|
|
25842
26198
|
for (const f of files.filter((f2) => f2.endsWith(".customize.yaml") || f2.endsWith(".customize.md"))) {
|
|
25843
26199
|
const itemId = f.replace(/\.customize\.(yaml|md)$/, "");
|
|
25844
26200
|
if (!allContentIds.has(itemId) && !allContentIds.has(`${HATCH3R_PREFIX}${itemId}`) && !allContentIds.has(`cmd-${itemId}`) && !allContentIds.has(`cmd-${HATCH3R_PREFIX}${itemId}`)) {
|
|
@@ -25850,7 +26206,7 @@ async function validateContentConsistency(rootDir, agentsDir, manifest, result)
|
|
|
25850
26206
|
}
|
|
25851
26207
|
}
|
|
25852
26208
|
}
|
|
25853
|
-
const learningsDir =
|
|
26209
|
+
const learningsDir = join55(rootDir, HATCH3R_DIR, "learnings");
|
|
25854
26210
|
const learningsResult = await validateLearningsDirectory(learningsDir);
|
|
25855
26211
|
for (const e of learningsResult.errors) {
|
|
25856
26212
|
result.errors.push(e);
|
|
@@ -25858,8 +26214,8 @@ async function validateContentConsistency(rootDir, agentsDir, manifest, result)
|
|
|
25858
26214
|
for (const w of learningsResult.warnings) {
|
|
25859
26215
|
result.warnings.push(w);
|
|
25860
26216
|
}
|
|
25861
|
-
const handoffsActiveDir =
|
|
25862
|
-
const handoffsArchivedDir =
|
|
26217
|
+
const handoffsActiveDir = join55(rootDir, HATCH3R_DIR, "handoffs", "active");
|
|
26218
|
+
const handoffsArchivedDir = join55(rootDir, HATCH3R_DIR, "handoffs", "archived");
|
|
25863
26219
|
const handoffsResult = await validateHandoffsDirectory(handoffsActiveDir, {
|
|
25864
26220
|
archivedDir: handoffsArchivedDir
|
|
25865
26221
|
});
|
|
@@ -25907,7 +26263,7 @@ async function validateUserContent(rootDir, agentsDir, result, index) {
|
|
|
25907
26263
|
if (userItems.length === 0) return;
|
|
25908
26264
|
for (const item of userItems) {
|
|
25909
26265
|
const fileLabel = `.hatch3r/overrides/${item.relativePath}`;
|
|
25910
|
-
const absPath = item.type === "skill" ?
|
|
26266
|
+
const absPath = item.type === "skill" ? join55(userRoot, item.relativePath, "SKILL.md") : join55(userRoot, item.relativePath);
|
|
25911
26267
|
let raw;
|
|
25912
26268
|
try {
|
|
25913
26269
|
raw = await readFile39(absPath, "utf-8");
|
|
@@ -25934,7 +26290,7 @@ async function validateUserContent(rootDir, agentsDir, result, index) {
|
|
|
25934
26290
|
const fmRaw = raw.slice(3, fmEnd).trim();
|
|
25935
26291
|
let fm;
|
|
25936
26292
|
try {
|
|
25937
|
-
fm =
|
|
26293
|
+
fm = parseYaml13(fmRaw);
|
|
25938
26294
|
} catch (err) {
|
|
25939
26295
|
result.errors.push(
|
|
25940
26296
|
`${fileLabel}: YAML parse error in frontmatter \u2014 ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -26270,7 +26626,7 @@ async function commandOrchestrates(commandPath, result) {
|
|
|
26270
26626
|
if (endIdx === -1) return false;
|
|
26271
26627
|
let fm;
|
|
26272
26628
|
try {
|
|
26273
|
-
fm =
|
|
26629
|
+
fm = parseYaml13(raw.slice(3, endIdx).trim());
|
|
26274
26630
|
} catch (err) {
|
|
26275
26631
|
result.warnings.push(
|
|
26276
26632
|
`Could not parse frontmatter of command ${commandPath} to verify Decision-13 orchestrator status \u2014 treating its id collision as a duplicate (${err instanceof Error ? err.message : String(err)})`
|
|
@@ -26302,10 +26658,10 @@ async function skillDocumentsDecision13Split(skillPath, result) {
|
|
|
26302
26658
|
async function listMarkdownFiles(dirPath) {
|
|
26303
26659
|
const found = [];
|
|
26304
26660
|
try {
|
|
26305
|
-
const entries = await
|
|
26661
|
+
const entries = await readdir29(dirPath, { withFileTypes: true });
|
|
26306
26662
|
for (const entry of entries) {
|
|
26307
26663
|
if (entry.name.startsWith(".")) continue;
|
|
26308
|
-
const childPath =
|
|
26664
|
+
const childPath = join55(dirPath, entry.name);
|
|
26309
26665
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
26310
26666
|
found.push(childPath);
|
|
26311
26667
|
} else if (entry.isDirectory()) {
|
|
@@ -26363,7 +26719,7 @@ async function validateContentBody2(canonicalRoot, result, strictContent) {
|
|
|
26363
26719
|
const scanDirs = ["agents", "commands", "rules", "skills", "hooks"];
|
|
26364
26720
|
const sink = strictContent ? result.errors : result.warnings;
|
|
26365
26721
|
for (const dir of scanDirs) {
|
|
26366
|
-
const dirPath =
|
|
26722
|
+
const dirPath = join55(canonicalRoot, dir);
|
|
26367
26723
|
const files = await listMarkdownFiles(dirPath);
|
|
26368
26724
|
for (const filePath of files) {
|
|
26369
26725
|
let raw;
|
|
@@ -26380,7 +26736,7 @@ async function validateContentBody2(canonicalRoot, result, strictContent) {
|
|
|
26380
26736
|
const endIdx = raw.indexOf("---", 3);
|
|
26381
26737
|
if (endIdx !== -1) {
|
|
26382
26738
|
try {
|
|
26383
|
-
parsedFm =
|
|
26739
|
+
parsedFm = parseYaml13(raw.slice(3, endIdx).trim());
|
|
26384
26740
|
} catch (err) {
|
|
26385
26741
|
const message = err instanceof Error ? err.message : String(err);
|
|
26386
26742
|
sink.push(
|
|
@@ -26443,10 +26799,10 @@ function checkAmbiguityGate(body) {
|
|
|
26443
26799
|
return { hasMarker, referencesProtocol };
|
|
26444
26800
|
}
|
|
26445
26801
|
async function validateAgentToolPolicyCoverage(canonicalRoot, result, userRepoRoot) {
|
|
26446
|
-
const agentsDir =
|
|
26802
|
+
const agentsDir = join55(canonicalRoot, "agents");
|
|
26447
26803
|
let entries;
|
|
26448
26804
|
try {
|
|
26449
|
-
entries = await
|
|
26805
|
+
entries = await readdir29(agentsDir, { withFileTypes: true });
|
|
26450
26806
|
} catch (err) {
|
|
26451
26807
|
if (err.code === "ENOENT") {
|
|
26452
26808
|
entries = [];
|
|
@@ -26457,7 +26813,7 @@ async function validateAgentToolPolicyCoverage(canonicalRoot, result, userRepoRo
|
|
|
26457
26813
|
const filesystemIds = [];
|
|
26458
26814
|
for (const entry of entries) {
|
|
26459
26815
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
26460
|
-
const filePath =
|
|
26816
|
+
const filePath = join55(agentsDir, entry.name);
|
|
26461
26817
|
let raw;
|
|
26462
26818
|
try {
|
|
26463
26819
|
raw = await readFile39(filePath, "utf-8");
|
|
@@ -26470,7 +26826,7 @@ async function validateAgentToolPolicyCoverage(canonicalRoot, result, userRepoRo
|
|
|
26470
26826
|
if (endIdx === -1) continue;
|
|
26471
26827
|
let fm;
|
|
26472
26828
|
try {
|
|
26473
|
-
fm =
|
|
26829
|
+
fm = parseYaml13(raw.slice(3, endIdx).trim());
|
|
26474
26830
|
} catch (err) {
|
|
26475
26831
|
const message = err instanceof Error ? err.message : String(err);
|
|
26476
26832
|
result.warnings.push(
|
|
@@ -26497,10 +26853,10 @@ async function validateAgentToolPolicyCoverage(canonicalRoot, result, userRepoRo
|
|
|
26497
26853
|
}
|
|
26498
26854
|
async function scanUserAgentPolicyCoverage(userRepoRoot, result) {
|
|
26499
26855
|
if (!userRepoRoot) return;
|
|
26500
|
-
const userAgentsDir =
|
|
26856
|
+
const userAgentsDir = join55(resolveUserContentRoot(userRepoRoot), "agents");
|
|
26501
26857
|
let entries;
|
|
26502
26858
|
try {
|
|
26503
|
-
entries = await
|
|
26859
|
+
entries = await readdir29(userAgentsDir, { withFileTypes: true });
|
|
26504
26860
|
} catch (err) {
|
|
26505
26861
|
if (err.code === "ENOENT") return;
|
|
26506
26862
|
throw err;
|
|
@@ -26509,7 +26865,7 @@ async function scanUserAgentPolicyCoverage(userRepoRoot, result) {
|
|
|
26509
26865
|
const known = new Set(ALL_TOOL_CATEGORIES2);
|
|
26510
26866
|
for (const entry of entries) {
|
|
26511
26867
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
26512
|
-
const filePath =
|
|
26868
|
+
const filePath = join55(userAgentsDir, entry.name);
|
|
26513
26869
|
let raw;
|
|
26514
26870
|
try {
|
|
26515
26871
|
raw = await readFile39(filePath, "utf-8");
|
|
@@ -26522,7 +26878,7 @@ async function scanUserAgentPolicyCoverage(userRepoRoot, result) {
|
|
|
26522
26878
|
if (endIdx === -1) continue;
|
|
26523
26879
|
let fm;
|
|
26524
26880
|
try {
|
|
26525
|
-
fm =
|
|
26881
|
+
fm = parseYaml13(raw.slice(3, endIdx).trim());
|
|
26526
26882
|
} catch (err) {
|
|
26527
26883
|
const message = err instanceof Error ? err.message : String(err);
|
|
26528
26884
|
result.warnings.push(
|
|
@@ -26603,10 +26959,10 @@ function lineDisclaimsCapability(line) {
|
|
|
26603
26959
|
return new RegExp(`\\bdo(?:es)?\\s+${notMarker}\\s+need\\b`, "i").test(line) || /\b(?:no longer use|never use|out of scope|not (?:in scope|required))\b/i.test(line) || /^\s*[-*]\s*\*\*Never:\*\*/.test(line);
|
|
26604
26960
|
}
|
|
26605
26961
|
async function validateAgentBodyCapabilityCoverage(canonicalRoot, result) {
|
|
26606
|
-
const agentsDir =
|
|
26962
|
+
const agentsDir = join55(canonicalRoot, "agents");
|
|
26607
26963
|
const { getAgentToolPolicy: getAgentToolPolicy2 } = await Promise.resolve().then(() => (init_agentToolAllowlist(), agentToolAllowlist_exports));
|
|
26608
26964
|
for (const agentId of D5_2_BODY_CAPABILITY_AGENTS) {
|
|
26609
|
-
const filePath =
|
|
26965
|
+
const filePath = join55(agentsDir, `${agentId}.md`);
|
|
26610
26966
|
let raw;
|
|
26611
26967
|
try {
|
|
26612
26968
|
raw = await readFile39(filePath, "utf-8");
|
|
@@ -26644,17 +27000,17 @@ async function validateAgentBodyCapabilityCoverage(canonicalRoot, result) {
|
|
|
26644
27000
|
}
|
|
26645
27001
|
}
|
|
26646
27002
|
async function validateSkillAllowedTools(canonicalRoot, result) {
|
|
26647
|
-
const skillsDir =
|
|
27003
|
+
const skillsDir = join55(canonicalRoot, "skills");
|
|
26648
27004
|
let entries;
|
|
26649
27005
|
try {
|
|
26650
|
-
entries = await
|
|
27006
|
+
entries = await readdir29(skillsDir, { withFileTypes: true });
|
|
26651
27007
|
} catch (err) {
|
|
26652
27008
|
if (err.code === "ENOENT") return;
|
|
26653
27009
|
throw err;
|
|
26654
27010
|
}
|
|
26655
27011
|
for (const entry of entries) {
|
|
26656
27012
|
if (!entry.isDirectory()) continue;
|
|
26657
|
-
const skillPath =
|
|
27013
|
+
const skillPath = join55(skillsDir, entry.name, "SKILL.md");
|
|
26658
27014
|
let raw;
|
|
26659
27015
|
try {
|
|
26660
27016
|
raw = await readFile39(skillPath, "utf-8");
|
|
@@ -26667,7 +27023,7 @@ async function validateSkillAllowedTools(canonicalRoot, result) {
|
|
|
26667
27023
|
if (endIdx === -1) continue;
|
|
26668
27024
|
let fm;
|
|
26669
27025
|
try {
|
|
26670
|
-
fm =
|
|
27026
|
+
fm = parseYaml13(raw.slice(3, endIdx).trim());
|
|
26671
27027
|
} catch (err) {
|
|
26672
27028
|
const message = err instanceof Error ? err.message : String(err);
|
|
26673
27029
|
result.warnings.push(
|
|
@@ -26693,16 +27049,16 @@ async function validateDocsCounts(rootDir) {
|
|
|
26693
27049
|
let checked = 0;
|
|
26694
27050
|
const actual = {};
|
|
26695
27051
|
const dirs = [
|
|
26696
|
-
["adapters",
|
|
26697
|
-
["commands",
|
|
26698
|
-
["agents",
|
|
26699
|
-
["skills",
|
|
26700
|
-
["rules",
|
|
26701
|
-
["hooks",
|
|
27052
|
+
["adapters", join55(rootDir, "src/adapters"), (e) => e.endsWith(".ts") && !["base.ts", "index.ts", "canonical.ts", "customization.ts", "types.ts", "mcp-utils.ts", "contextBudget.ts"].includes(e)],
|
|
27053
|
+
["commands", join55(rootDir, "src/cli/commands"), (e) => e.endsWith(".ts")],
|
|
27054
|
+
["agents", join55(rootDir, "agents"), (e) => e.endsWith(".md")],
|
|
27055
|
+
["skills", join55(rootDir, "skills"), (_e) => true],
|
|
27056
|
+
["rules", join55(rootDir, "rules"), (e) => e.endsWith(".md")],
|
|
27057
|
+
["hooks", join55(rootDir, "hooks"), (e) => e.endsWith(".md")]
|
|
26702
27058
|
];
|
|
26703
27059
|
for (const [name, dir, filter] of dirs) {
|
|
26704
27060
|
try {
|
|
26705
|
-
const entries = await
|
|
27061
|
+
const entries = await readdir29(dir, { withFileTypes: true });
|
|
26706
27062
|
if (name === "skills") {
|
|
26707
27063
|
actual[name] = entries.filter((e) => e.isDirectory()).length;
|
|
26708
27064
|
} else {
|
|
@@ -26712,7 +27068,7 @@ async function validateDocsCounts(rootDir) {
|
|
|
26712
27068
|
actual[name] = 0;
|
|
26713
27069
|
}
|
|
26714
27070
|
}
|
|
26715
|
-
const readmePath =
|
|
27071
|
+
const readmePath = join55(rootDir, "README.md");
|
|
26716
27072
|
try {
|
|
26717
27073
|
const readme = await readFile39(readmePath, "utf-8");
|
|
26718
27074
|
const countPatterns = [
|
|
@@ -26744,14 +27100,14 @@ async function scanManagedBlockTampering(rootDir) {
|
|
|
26744
27100
|
async function collect(dir) {
|
|
26745
27101
|
let entries;
|
|
26746
27102
|
try {
|
|
26747
|
-
entries = await
|
|
27103
|
+
entries = await readdir29(dir, { withFileTypes: true });
|
|
26748
27104
|
} catch (err) {
|
|
26749
27105
|
const message = err instanceof Error ? err.message : String(err);
|
|
26750
27106
|
verbose(`validate: tamper-scan readdir(${dir}) skipped \u2014 ${message}`);
|
|
26751
27107
|
return;
|
|
26752
27108
|
}
|
|
26753
27109
|
for (const entry of entries) {
|
|
26754
|
-
const full =
|
|
27110
|
+
const full = join55(dir, entry.name);
|
|
26755
27111
|
if (entry.isDirectory()) {
|
|
26756
27112
|
await collect(full);
|
|
26757
27113
|
} else if (entry.isFile() && candidateExt.some((e) => entry.name.toLowerCase().endsWith(e))) {
|
|
@@ -26760,7 +27116,7 @@ async function scanManagedBlockTampering(rootDir) {
|
|
|
26760
27116
|
}
|
|
26761
27117
|
}
|
|
26762
27118
|
for (const d of adapterDirs) {
|
|
26763
|
-
await collect(
|
|
27119
|
+
await collect(join55(rootDir, d));
|
|
26764
27120
|
}
|
|
26765
27121
|
for (const file of files) {
|
|
26766
27122
|
let content;
|
|
@@ -26859,29 +27215,29 @@ ${transcript}`);
|
|
|
26859
27215
|
}
|
|
26860
27216
|
function runRuleParityCheck(packageRoot, result) {
|
|
26861
27217
|
runSubValidator(
|
|
26862
|
-
|
|
27218
|
+
join55(packageRoot, "scripts", "validate-rule-parity.ts"),
|
|
26863
27219
|
"validate:rule-parity",
|
|
26864
27220
|
result
|
|
26865
27221
|
);
|
|
26866
27222
|
runSubValidator(
|
|
26867
|
-
|
|
27223
|
+
join55(packageRoot, "scripts", "validate-rule-pillar-currency.ts"),
|
|
26868
27224
|
"validate:rule-pillar-currency",
|
|
26869
27225
|
result
|
|
26870
27226
|
);
|
|
26871
27227
|
}
|
|
26872
27228
|
function runEfficiencyInvariantCheck(packageRoot, result) {
|
|
26873
27229
|
runSubValidator(
|
|
26874
|
-
|
|
27230
|
+
join55(packageRoot, "scripts", "validate-efficiency-invariants.ts"),
|
|
26875
27231
|
"validate:efficiency-invariants",
|
|
26876
27232
|
result
|
|
26877
27233
|
);
|
|
26878
27234
|
runSubValidator(
|
|
26879
|
-
|
|
27235
|
+
join55(packageRoot, "scripts", "validate-bridge-budget.ts"),
|
|
26880
27236
|
"validate:bridge-budget",
|
|
26881
27237
|
result
|
|
26882
27238
|
);
|
|
26883
27239
|
runSubValidator(
|
|
26884
|
-
|
|
27240
|
+
join55(packageRoot, "scripts", "validate-fanout-emission.ts"),
|
|
26885
27241
|
"validate:fanout-emission",
|
|
26886
27242
|
result
|
|
26887
27243
|
);
|
|
@@ -27025,7 +27381,7 @@ async function validateCommand(opts) {
|
|
|
27025
27381
|
if (types.size === 2 && types.has("command") && types.has("skill")) {
|
|
27026
27382
|
const commandPath = collision.existingType === "command" ? collision.existingPath : collision.duplicatePath;
|
|
27027
27383
|
const skillPath = collision.existingType === "skill" ? collision.existingPath : collision.duplicatePath;
|
|
27028
|
-
const qualifies = await commandOrchestrates(
|
|
27384
|
+
const qualifies = await commandOrchestrates(join55(canonicalRoot, commandPath), result);
|
|
27029
27385
|
if (!qualifies) {
|
|
27030
27386
|
result.warnings.push(
|
|
27031
27387
|
`Content ID collision: "${collision.id}" exists as both a command (${commandPath}) and a skill (${skillPath}), but the command is not orchestrator:true with a non-empty agentPipeline \u2014 per Decision 13 this is a duplicate: collapse to one artifact or promote the command to a real orchestrator (.claude/rules/content-authoring.md item 9)`
|
|
@@ -27033,7 +27389,7 @@ async function validateCommand(opts) {
|
|
|
27033
27389
|
continue;
|
|
27034
27390
|
}
|
|
27035
27391
|
const documented = await skillDocumentsDecision13Split(
|
|
27036
|
-
|
|
27392
|
+
join55(canonicalRoot, skillPath),
|
|
27037
27393
|
result
|
|
27038
27394
|
);
|
|
27039
27395
|
if (!documented) {
|
|
@@ -27086,7 +27442,7 @@ async function validateCommand(opts) {
|
|
|
27086
27442
|
let hasCustomizations = false;
|
|
27087
27443
|
for (const { dir } of CUSTOMIZATION_TYPES2) {
|
|
27088
27444
|
try {
|
|
27089
|
-
const files = await
|
|
27445
|
+
const files = await readdir29(join55(rootDir, ".hatch3r", dir));
|
|
27090
27446
|
if (files.some((f) => f.endsWith(".customize.yaml") || f.endsWith(".customize.md"))) {
|
|
27091
27447
|
hasCustomizations = true;
|
|
27092
27448
|
break;
|
|
@@ -27116,7 +27472,7 @@ async function validateCommand(opts) {
|
|
|
27116
27472
|
return;
|
|
27117
27473
|
}
|
|
27118
27474
|
if (result.errors.length === 0 && result.warnings.length === 0) {
|
|
27119
|
-
printBox("Validation", [
|
|
27475
|
+
printBox("Validation", [chalk14.green("All checks passed")], "success");
|
|
27120
27476
|
printNextSteps([
|
|
27121
27477
|
"Run `hatch3r sync` if you changed `.hatch3r/` overrides since the last generation."
|
|
27122
27478
|
]);
|
|
@@ -27147,8 +27503,8 @@ async function validateCommand(opts) {
|
|
|
27147
27503
|
}
|
|
27148
27504
|
if (result.errors.length > 0) {
|
|
27149
27505
|
const summaryLines = [
|
|
27150
|
-
`${
|
|
27151
|
-
`${
|
|
27506
|
+
`${chalk14.red("\u2716")} ${result.errors.length} error(s)`,
|
|
27507
|
+
`${chalk14.yellow("\u26A0")} ${result.warnings.length} warning(s)`
|
|
27152
27508
|
];
|
|
27153
27509
|
printBox("Validation failed", summaryLines, "error");
|
|
27154
27510
|
printNextSteps(["Re-run with `--verbose` to see each failing check."]);
|
|
@@ -27160,8 +27516,8 @@ async function validateCommand(opts) {
|
|
|
27160
27516
|
);
|
|
27161
27517
|
} else {
|
|
27162
27518
|
const summaryLines = [
|
|
27163
|
-
`${
|
|
27164
|
-
`${
|
|
27519
|
+
`${chalk14.green("\u2714")} 0 errors`,
|
|
27520
|
+
`${chalk14.yellow("\u26A0")} ${result.warnings.length} warning(s)`
|
|
27165
27521
|
];
|
|
27166
27522
|
printBox("Validation passed", summaryLines, "success");
|
|
27167
27523
|
printNextSteps([
|
|
@@ -27174,7 +27530,7 @@ async function validateCommand(opts) {
|
|
|
27174
27530
|
}
|
|
27175
27531
|
}
|
|
27176
27532
|
async function validateEnvMcpSecrets(rootDir, result) {
|
|
27177
|
-
const envMcpPath =
|
|
27533
|
+
const envMcpPath = join55(rootDir, ".env.mcp");
|
|
27178
27534
|
if (!existsSync5(envMcpPath)) return;
|
|
27179
27535
|
try {
|
|
27180
27536
|
const raw = await readFile39(envMcpPath, "utf-8");
|
|
@@ -27196,7 +27552,7 @@ async function validateEnvMcpSecrets(rootDir, result) {
|
|
|
27196
27552
|
async function validateCanonicalDescriptionQuality(rootDir, result) {
|
|
27197
27553
|
const __filename = fileURLToPath9(import.meta.url);
|
|
27198
27554
|
const packageRoot = findPackageRoot(dirname26(__filename));
|
|
27199
|
-
const canonicalRoot = existsSync5(
|
|
27555
|
+
const canonicalRoot = existsSync5(join55(rootDir, "agents")) && existsSync5(join55(rootDir, "skills")) && existsSync5(join55(rootDir, "rules")) && existsSync5(join55(rootDir, "commands")) ? rootDir : packageRoot;
|
|
27200
27556
|
try {
|
|
27201
27557
|
const index = await buildContentIndex(canonicalRoot);
|
|
27202
27558
|
if (index.items.length === 0) return;
|
|
@@ -27235,24 +27591,24 @@ async function validateSecurityCompliance(result) {
|
|
|
27235
27591
|
}
|
|
27236
27592
|
function printCustomizationHint() {
|
|
27237
27593
|
console.log();
|
|
27238
|
-
info(
|
|
27239
|
-
console.log(
|
|
27240
|
-
console.log(
|
|
27241
|
-
console.log(
|
|
27242
|
-
console.log(
|
|
27243
|
-
console.log(
|
|
27244
|
-
console.log(
|
|
27245
|
-
console.log(
|
|
27594
|
+
info(chalk14.bold("Customization mechanisms detected. Quick reference:"));
|
|
27595
|
+
console.log(chalk14.dim(" 1. hatch3r- prefix: Files prefixed with hatch3r- are managed by hatch3r and"));
|
|
27596
|
+
console.log(chalk14.dim(" overwritten on update. Do not edit these directly."));
|
|
27597
|
+
console.log(chalk14.dim(" 2. Managed blocks: Sections between <!-- HATCH3R:BEGIN --> and"));
|
|
27598
|
+
console.log(chalk14.dim(" <!-- HATCH3R:END --> are auto-updated. Add content outside these markers."));
|
|
27599
|
+
console.log(chalk14.dim(" 3. .customize.yaml/.md: Place in .hatch3r/{type}/ to override model, scope,"));
|
|
27600
|
+
console.log(chalk14.dim(" description, or disable items. Use .customize.md for content additions."));
|
|
27601
|
+
console.log(chalk14.dim(" See: https://docs.hatch3r.com/docs/guides/customization"));
|
|
27246
27602
|
}
|
|
27247
27603
|
|
|
27248
27604
|
// src/cli/commands/verify.ts
|
|
27249
27605
|
init_types();
|
|
27250
|
-
import
|
|
27606
|
+
import chalk16 from "chalk";
|
|
27251
27607
|
|
|
27252
27608
|
// src/cli/commands/status.ts
|
|
27253
|
-
import { access as
|
|
27254
|
-
import { join as
|
|
27255
|
-
import
|
|
27609
|
+
import { access as access18, readFile as readFile40 } from "fs/promises";
|
|
27610
|
+
import { join as join56 } from "path";
|
|
27611
|
+
import chalk15 from "chalk";
|
|
27256
27612
|
init_types();
|
|
27257
27613
|
init_managedBlocks();
|
|
27258
27614
|
init_version();
|
|
@@ -27260,7 +27616,7 @@ init_ui();
|
|
|
27260
27616
|
async function loadProvenanceBaseline(rootDir) {
|
|
27261
27617
|
const baseline = /* @__PURE__ */ new Map();
|
|
27262
27618
|
try {
|
|
27263
|
-
const raw = await readFile40(
|
|
27619
|
+
const raw = await readFile40(join56(rootDir, HATCH3R_DIR, "provenance.json"), "utf-8");
|
|
27264
27620
|
const parsed = JSON.parse(raw);
|
|
27265
27621
|
for (const entry of parsed.outputs ?? []) {
|
|
27266
27622
|
if (typeof entry.path === "string" && typeof entry.contentHash === "string") {
|
|
@@ -27307,7 +27663,7 @@ async function computeAdapterDrift(rootDir, manifest) {
|
|
|
27307
27663
|
verbose(`${tool}: ${outputs.length} output file(s) to check`);
|
|
27308
27664
|
for (const out of outputs) {
|
|
27309
27665
|
seenPaths.add(out.path);
|
|
27310
|
-
const destPath =
|
|
27666
|
+
const destPath = join56(rootDir, out.path);
|
|
27311
27667
|
try {
|
|
27312
27668
|
const existing = await readFile40(destPath, "utf-8");
|
|
27313
27669
|
const existingBlock = extractManagedBlock(existing, out.path);
|
|
@@ -27363,7 +27719,7 @@ async function computeAdapterDrift(rootDir, manifest) {
|
|
|
27363
27719
|
if (seenPaths.has(tracked)) continue;
|
|
27364
27720
|
if (NON_ADAPTER_MANAGED_FILES.has(tracked)) continue;
|
|
27365
27721
|
try {
|
|
27366
|
-
await
|
|
27722
|
+
await access18(join56(rootDir, tracked));
|
|
27367
27723
|
entries.push({ path: tracked, tool: "(unowned)", status: "unexpected" });
|
|
27368
27724
|
counts.unexpected++;
|
|
27369
27725
|
} catch (err) {
|
|
@@ -27376,14 +27732,14 @@ async function computeAdapterDrift(rootDir, manifest) {
|
|
|
27376
27732
|
function driftKindTag(kind) {
|
|
27377
27733
|
switch (kind) {
|
|
27378
27734
|
case "user-modified":
|
|
27379
|
-
return
|
|
27735
|
+
return chalk15.yellow(" (your edit)");
|
|
27380
27736
|
case "canonical-outdated":
|
|
27381
|
-
return
|
|
27737
|
+
return chalk15.cyan(" (canonical changed)");
|
|
27382
27738
|
case "both":
|
|
27383
|
-
return
|
|
27739
|
+
return chalk15.red(" (your edit + canonical changed)");
|
|
27384
27740
|
case "unknown":
|
|
27385
27741
|
default:
|
|
27386
|
-
return
|
|
27742
|
+
return chalk15.dim(" (no baseline)");
|
|
27387
27743
|
}
|
|
27388
27744
|
}
|
|
27389
27745
|
function renderDiffSummaryLines(report) {
|
|
@@ -27391,16 +27747,16 @@ function renderDiffSummaryLines(report) {
|
|
|
27391
27747
|
for (const entry of report.entries) {
|
|
27392
27748
|
switch (entry.status) {
|
|
27393
27749
|
case "in-sync":
|
|
27394
|
-
lines.push(`${
|
|
27750
|
+
lines.push(`${chalk15.dim("= unchanged")} ${entry.path}`);
|
|
27395
27751
|
break;
|
|
27396
27752
|
case "modified":
|
|
27397
|
-
lines.push(`${
|
|
27753
|
+
lines.push(`${chalk15.yellow("~ modified")} ${entry.path}`);
|
|
27398
27754
|
break;
|
|
27399
27755
|
case "missing":
|
|
27400
|
-
lines.push(`${
|
|
27756
|
+
lines.push(`${chalk15.green("+ added")} ${entry.path}`);
|
|
27401
27757
|
break;
|
|
27402
27758
|
case "unexpected":
|
|
27403
|
-
lines.push(`${
|
|
27759
|
+
lines.push(`${chalk15.red("! orphan")} ${entry.path}`);
|
|
27404
27760
|
break;
|
|
27405
27761
|
}
|
|
27406
27762
|
}
|
|
@@ -27415,20 +27771,20 @@ function renderDriftLines(report) {
|
|
|
27415
27771
|
}
|
|
27416
27772
|
const lines = [];
|
|
27417
27773
|
for (const [tool, items] of byTool) {
|
|
27418
|
-
lines.push(
|
|
27774
|
+
lines.push(chalk15.bold(`${tool}:`));
|
|
27419
27775
|
for (const entry of items) {
|
|
27420
27776
|
switch (entry.status) {
|
|
27421
27777
|
case "in-sync":
|
|
27422
|
-
lines.push(` ${
|
|
27778
|
+
lines.push(` ${chalk15.green("=")} ${entry.path}`);
|
|
27423
27779
|
break;
|
|
27424
27780
|
case "modified":
|
|
27425
|
-
lines.push(` ${
|
|
27781
|
+
lines.push(` ${chalk15.yellow("~")} ${entry.path} ${chalk15.dim("(drifted)")}${driftKindTag(entry.driftKind)}`);
|
|
27426
27782
|
break;
|
|
27427
27783
|
case "missing":
|
|
27428
|
-
lines.push(` ${
|
|
27784
|
+
lines.push(` ${chalk15.red("+")} ${entry.path} ${chalk15.dim("(missing)")}`);
|
|
27429
27785
|
break;
|
|
27430
27786
|
case "unexpected":
|
|
27431
|
-
lines.push(` ${
|
|
27787
|
+
lines.push(` ${chalk15.red("!")} ${entry.path} ${chalk15.dim("(unexpected: not produced by any current adapter)")}`);
|
|
27432
27788
|
break;
|
|
27433
27789
|
}
|
|
27434
27790
|
}
|
|
@@ -27482,21 +27838,21 @@ async function statusCommand(opts) {
|
|
|
27482
27838
|
}
|
|
27483
27839
|
if (!isQuiet()) console.log();
|
|
27484
27840
|
const summaryLines = [
|
|
27485
|
-
`${
|
|
27841
|
+
`${chalk15.green("=")} In sync: ${report.counts.synced}`
|
|
27486
27842
|
];
|
|
27487
27843
|
if (report.counts.modified > 0) {
|
|
27488
|
-
summaryLines.push(`${
|
|
27844
|
+
summaryLines.push(`${chalk15.yellow("~")} Drifted: ${report.counts.modified}`);
|
|
27489
27845
|
const dk = report.driftKindCounts;
|
|
27490
|
-
if (dk.userModified > 0) summaryLines.push(` ${
|
|
27491
|
-
if (dk.canonicalOutdated > 0) summaryLines.push(` ${
|
|
27492
|
-
if (dk.both > 0) summaryLines.push(` ${
|
|
27493
|
-
if (dk.unknown > 0) summaryLines.push(` ${
|
|
27846
|
+
if (dk.userModified > 0) summaryLines.push(` ${chalk15.yellow("\u2022")} your edits: ${dk.userModified}`);
|
|
27847
|
+
if (dk.canonicalOutdated > 0) summaryLines.push(` ${chalk15.cyan("\u2022")} canonical changed: ${dk.canonicalOutdated}`);
|
|
27848
|
+
if (dk.both > 0) summaryLines.push(` ${chalk15.red("\u2022")} edits + canonical: ${dk.both}`);
|
|
27849
|
+
if (dk.unknown > 0) summaryLines.push(` ${chalk15.dim("\u2022")} no baseline: ${dk.unknown}`);
|
|
27494
27850
|
}
|
|
27495
27851
|
if (report.counts.missing > 0) {
|
|
27496
|
-
summaryLines.push(`${
|
|
27852
|
+
summaryLines.push(`${chalk15.red("+")} Missing: ${report.counts.missing}`);
|
|
27497
27853
|
}
|
|
27498
27854
|
if (report.counts.unexpected > 0) {
|
|
27499
|
-
summaryLines.push(`${
|
|
27855
|
+
summaryLines.push(`${chalk15.red("!")} Unexpected: ${report.counts.unexpected}`);
|
|
27500
27856
|
}
|
|
27501
27857
|
const hasDrift = report.counts.modified > 0 || report.counts.missing > 0 || report.counts.unexpected > 0;
|
|
27502
27858
|
const style = hasDrift ? "info" : "success";
|
|
@@ -27517,30 +27873,30 @@ async function statusCommand(opts) {
|
|
|
27517
27873
|
const dk = report.driftKindCounts;
|
|
27518
27874
|
if (dk.canonicalOutdated > 0 && dk.userModified === 0 && dk.both === 0 && dk.unknown === 0) {
|
|
27519
27875
|
info(
|
|
27520
|
-
`Run ${
|
|
27876
|
+
`Run ${chalk15.bold("hatch3r sync")} to update ${dk.canonicalOutdated} file(s) whose ${chalk15.cyan("canonical content changed")}. No local edits were detected, so regenerating is safe.`
|
|
27521
27877
|
);
|
|
27522
27878
|
} else if ((dk.userModified > 0 || dk.both > 0) && dk.canonicalOutdated === 0 && dk.unknown === 0) {
|
|
27523
27879
|
info(
|
|
27524
|
-
`${
|
|
27880
|
+
`${chalk15.bold("hatch3r sync overwrites the managed block.")} ${dk.userModified + dk.both} drifted file(s) carry ${chalk15.yellow("your edits")}${dk.both > 0 ? " (some also have newer canonical content)" : ""} \u2014 back them up before running sync if you want to keep them.`
|
|
27525
27881
|
);
|
|
27526
27882
|
} else {
|
|
27527
27883
|
info(
|
|
27528
|
-
`Drifted files are tagged above: ${
|
|
27884
|
+
`Drifted files are tagged above: ${chalk15.cyan("(canonical changed)")} is safe to ${chalk15.bold("hatch3r sync")}; ${chalk15.yellow("(your edit)")} / ${chalk15.red("(your edit + canonical changed)")} would be overwritten \u2014 back up first. ${chalk15.dim("(no baseline)")} means sync has not run since this file was tracked; run sync once to record a baseline for future attribution.`
|
|
27529
27885
|
);
|
|
27530
27886
|
}
|
|
27531
27887
|
console.log();
|
|
27532
27888
|
}
|
|
27533
27889
|
if (report.counts.missing > 0) {
|
|
27534
|
-
info(`Run ${
|
|
27890
|
+
info(`Run ${chalk15.bold("hatch3r sync")} to regenerate missing files.`);
|
|
27535
27891
|
console.log();
|
|
27536
27892
|
}
|
|
27537
27893
|
if (report.counts.unexpected > 0) {
|
|
27538
|
-
info(`Unexpected files are tracked in the manifest but no longer produced. Run ${
|
|
27894
|
+
info(`Unexpected files are tracked in the manifest but no longer produced. Run ${chalk15.bold("hatch3r clean")} to remove them, or remove them manually.`);
|
|
27539
27895
|
console.log();
|
|
27540
27896
|
}
|
|
27541
27897
|
if (hasDrift) {
|
|
27542
27898
|
console.log(
|
|
27543
|
-
|
|
27899
|
+
chalk15.dim(
|
|
27544
27900
|
" Note: drift detection covers hatch3r-managed blocks only (HATCH3R:BEGIN/END). Content outside the markers is yours \u2014 use `git diff` to inspect it."
|
|
27545
27901
|
)
|
|
27546
27902
|
);
|
|
@@ -27557,13 +27913,13 @@ async function statusCommand(opts) {
|
|
|
27557
27913
|
cliLines.push("");
|
|
27558
27914
|
for (const r of missing) {
|
|
27559
27915
|
if (r.extensionMissing) {
|
|
27560
|
-
cliLines.push(` ${
|
|
27916
|
+
cliLines.push(` ${chalk15.yellow("\u2717")} ${r.id} \u2014 extension missing: ${r.extensionMissing}`);
|
|
27561
27917
|
} else {
|
|
27562
|
-
cliLines.push(` ${
|
|
27918
|
+
cliLines.push(` ${chalk15.yellow("\u2717")} ${r.id} not on PATH`);
|
|
27563
27919
|
}
|
|
27564
27920
|
}
|
|
27565
27921
|
cliLines.push("");
|
|
27566
|
-
cliLines.push(
|
|
27922
|
+
cliLines.push(chalk15.dim(`Run \`npx hatch3r cli-tools install\` to see install commands.`));
|
|
27567
27923
|
}
|
|
27568
27924
|
printBox("CLI tools", cliLines, missing.length === 0 ? "success" : "info");
|
|
27569
27925
|
}
|
|
@@ -27618,7 +27974,7 @@ async function statusCommand(opts) {
|
|
|
27618
27974
|
spaceLines.push(` ${a.axis.padEnd(14)}${a.count} metric(s), mean ${a.mean.toFixed(2)}`);
|
|
27619
27975
|
}
|
|
27620
27976
|
spaceLines.push(
|
|
27621
|
-
|
|
27977
|
+
chalk15.dim(` ${spaceTelemetry.recordCount} record(s) over the last ${SPACE_TELEMETRY_WINDOW_DAYS} day(s)`)
|
|
27622
27978
|
);
|
|
27623
27979
|
printBox("Developer productivity (SPACE)", spaceLines, "info");
|
|
27624
27980
|
}
|
|
@@ -27629,16 +27985,16 @@ async function statusCommand(opts) {
|
|
|
27629
27985
|
);
|
|
27630
27986
|
if (customizationSummary.entries.length > 0) {
|
|
27631
27987
|
const c = customizationSummary.counts;
|
|
27632
|
-
const oneLine = `${
|
|
27988
|
+
const oneLine = `${chalk15.bold(String(c.active))} active` + (c.skipped > 0 ? `, ${chalk15.yellow(String(c.skipped))} skipped` : "") + (c.failed > 0 ? `, ${chalk15.red(String(c.failed))} failed` : "") + // D10-29: inert overrides (deselected artifact, no adapter emits them)
|
|
27633
27989
|
// surface in the one-liner so an override that silently does nothing is
|
|
27634
27990
|
// not hidden behind the active count.
|
|
27635
|
-
(c.inert > 0 ? `, ${
|
|
27991
|
+
(c.inert > 0 ? `, ${chalk15.yellow(String(c.inert))} inert` : "");
|
|
27636
27992
|
const customLines = [oneLine];
|
|
27637
27993
|
if (opts?.verbose) {
|
|
27638
27994
|
customLines.push("");
|
|
27639
27995
|
for (const entry of customizationSummary.entries) {
|
|
27640
|
-
const icon = entry.outcome === "failed" ?
|
|
27641
|
-
const reason = entry.reason ?
|
|
27996
|
+
const icon = entry.outcome === "failed" ? chalk15.red("\u2717") : entry.outcome === "skipped" ? chalk15.yellow("\u25CB") : entry.outcome === "inert" ? chalk15.yellow("\u2298") : entry.outcome === "active" ? chalk15.green("\u2713") : chalk15.dim("\xB7");
|
|
27997
|
+
const reason = entry.reason ? chalk15.dim(` \u2014 ${entry.reason}`) : "";
|
|
27642
27998
|
customLines.push(` ${icon} ${entry.type}/${entry.id}${reason}`);
|
|
27643
27999
|
}
|
|
27644
28000
|
} else {
|
|
@@ -27651,18 +28007,18 @@ async function statusCommand(opts) {
|
|
|
27651
28007
|
if (visible.length > 0) {
|
|
27652
28008
|
customLines.push("");
|
|
27653
28009
|
for (const entry of visible) {
|
|
27654
|
-
const icon = entry.outcome === "failed" ?
|
|
27655
|
-
const reason = entry.reason ?
|
|
28010
|
+
const icon = entry.outcome === "failed" ? chalk15.red("\u2717") : entry.outcome === "skipped" ? chalk15.yellow("\u25CB") : entry.outcome === "inert" ? chalk15.yellow("\u2298") : chalk15.green("\u2713");
|
|
28011
|
+
const reason = entry.reason ? chalk15.dim(` \u2014 ${entry.reason}`) : "";
|
|
27656
28012
|
customLines.push(` ${icon} ${entry.type}/${entry.id}${reason}`);
|
|
27657
28013
|
}
|
|
27658
28014
|
if (hiddenActive > 0) {
|
|
27659
28015
|
customLines.push(
|
|
27660
|
-
|
|
28016
|
+
chalk15.dim(` \u2026 +${hiddenActive} more active (run with --verbose for the full list)`)
|
|
27661
28017
|
);
|
|
27662
28018
|
}
|
|
27663
28019
|
}
|
|
27664
28020
|
if (c.failed > 0 || c.skipped > 0 || c.inert > 0) {
|
|
27665
|
-
customLines.push(
|
|
28021
|
+
customLines.push(chalk15.dim(` Run \`hatch3r explain --customizations\` for the per-artifact table.`));
|
|
27666
28022
|
}
|
|
27667
28023
|
}
|
|
27668
28024
|
printBox(
|
|
@@ -27678,28 +28034,28 @@ async function statusCommand(opts) {
|
|
|
27678
28034
|
if (wsManifest && wsManifest.repos.length > 0) {
|
|
27679
28035
|
const wsLines = [];
|
|
27680
28036
|
for (const repo of wsManifest.repos) {
|
|
27681
|
-
const icon = repo.sync ?
|
|
28037
|
+
const icon = repo.sync ? chalk15.green("\u2713") : chalk15.dim("\u25CB");
|
|
27682
28038
|
let detail;
|
|
27683
28039
|
if (!repo.sync) {
|
|
27684
|
-
detail =
|
|
28040
|
+
detail = chalk15.dim("sync disabled");
|
|
27685
28041
|
} else if (repo.lastSync) {
|
|
27686
28042
|
const elapsed = Math.max(0, Date.now() - new Date(repo.lastSync).getTime());
|
|
27687
28043
|
const hours = Math.floor(elapsed / (1e3 * 60 * 60));
|
|
27688
28044
|
const timeAgo = hours < 1 ? "just now" : hours < 24 ? `${hours}h ago` : `${Math.floor(hours / 24)}d ago`;
|
|
27689
28045
|
detail = `synced ${timeAgo}`;
|
|
27690
28046
|
} else {
|
|
27691
|
-
detail =
|
|
28047
|
+
detail = chalk15.yellow("never synced");
|
|
27692
28048
|
}
|
|
27693
|
-
const identity = repo.owner && repo.repo ?
|
|
27694
|
-
const branch = repo.defaultBranch ?
|
|
28049
|
+
const identity = repo.owner && repo.repo ? chalk15.dim(`${repo.owner}/${repo.repo}`) : "";
|
|
28050
|
+
const branch = repo.defaultBranch ? chalk15.dim(`[${repo.defaultBranch}]`) : "";
|
|
27695
28051
|
const identityPart = identity || branch ? ` ${identity} ${branch}` : "";
|
|
27696
|
-
wsLines.push(`${icon} ${repo.name ?? repo.path}${identityPart} ${
|
|
28052
|
+
wsLines.push(`${icon} ${repo.name ?? repo.path}${identityPart} ${chalk15.dim(`(${detail})`)}`);
|
|
27697
28053
|
}
|
|
27698
28054
|
printBox(`Workspace: ${wsManifest.name} (${wsManifest.repos.length} repos)`, wsLines, "info");
|
|
27699
28055
|
}
|
|
27700
28056
|
if (manifest.workspace) {
|
|
27701
28057
|
const wsInfo = [
|
|
27702
|
-
`Managed by workspace at ${
|
|
28058
|
+
`Managed by workspace at ${chalk15.bold(manifest.workspace.rootPath)}`,
|
|
27703
28059
|
`Last synced: ${manifest.workspace.lastSync ? new Date(manifest.workspace.lastSync).toLocaleString() : "never"}`
|
|
27704
28060
|
];
|
|
27705
28061
|
printBox("Workspace member", wsInfo, "info");
|
|
@@ -27713,16 +28069,16 @@ var DEFAULT_MAX_FIX_ATTEMPTS = 2;
|
|
|
27713
28069
|
var MAX_FIX_ATTEMPTS_CEILING = 5;
|
|
27714
28070
|
function buildSummaryLines(report) {
|
|
27715
28071
|
const summaryLines = [
|
|
27716
|
-
`${
|
|
28072
|
+
`${chalk16.green("\u2714")} In sync: ${report.counts.synced}`
|
|
27717
28073
|
];
|
|
27718
28074
|
if (report.counts.modified > 0) {
|
|
27719
|
-
summaryLines.push(`${
|
|
28075
|
+
summaryLines.push(`${chalk16.yellow("~")} Drifted: ${report.counts.modified}`);
|
|
27720
28076
|
}
|
|
27721
28077
|
if (report.counts.missing > 0) {
|
|
27722
|
-
summaryLines.push(`${
|
|
28078
|
+
summaryLines.push(`${chalk16.red("+")} Missing: ${report.counts.missing}`);
|
|
27723
28079
|
}
|
|
27724
28080
|
if (report.counts.unexpected > 0) {
|
|
27725
|
-
summaryLines.push(`${
|
|
28081
|
+
summaryLines.push(`${chalk16.red("!")} Unexpected: ${report.counts.unexpected}`);
|
|
27726
28082
|
}
|
|
27727
28083
|
return summaryLines;
|
|
27728
28084
|
}
|
|
@@ -27756,7 +28112,7 @@ function maybePrintVerboseDrift(options, report) {
|
|
|
27756
28112
|
}
|
|
27757
28113
|
function printTamperWarnings(tamperWarnings) {
|
|
27758
28114
|
if (tamperWarnings.length === 0) return;
|
|
27759
|
-
const lines = tamperWarnings.map((w) => `${
|
|
28115
|
+
const lines = tamperWarnings.map((w) => `${chalk16.yellow("!")} ${w}`);
|
|
27760
28116
|
printBox(
|
|
27761
28117
|
`Managed-block structural warnings (${tamperWarnings.length})`,
|
|
27762
28118
|
lines,
|
|
@@ -27844,7 +28200,7 @@ async function verifyCommand(options = {}) {
|
|
|
27844
28200
|
maybePrintDiffSummary(options, report);
|
|
27845
28201
|
printTamperWarnings(tamperWarnings);
|
|
27846
28202
|
if (options.fix) {
|
|
27847
|
-
info(`--fix could not clear all drift \u2014 run ${
|
|
28203
|
+
info(`--fix could not clear all drift \u2014 run ${chalk16.bold("hatch3r sync")} or inspect the failing tool(s).`);
|
|
27848
28204
|
} else {
|
|
27849
28205
|
info(recoveryHint);
|
|
27850
28206
|
printNextSteps([
|
|
@@ -27853,7 +28209,7 @@ async function verifyCommand(options = {}) {
|
|
|
27853
28209
|
}
|
|
27854
28210
|
if (!isQuiet()) {
|
|
27855
28211
|
console.log(
|
|
27856
|
-
|
|
28212
|
+
chalk16.dim(
|
|
27857
28213
|
" Note: verify covers hatch3r-managed blocks only (HATCH3R:BEGIN/END). Content outside the markers is yours \u2014 use `git diff` to inspect it."
|
|
27858
28214
|
)
|
|
27859
28215
|
);
|
|
@@ -27872,11 +28228,11 @@ init_observability();
|
|
|
27872
28228
|
init_costEstimator();
|
|
27873
28229
|
init_types();
|
|
27874
28230
|
init_version();
|
|
27875
|
-
import { readFile as readFile41, access as
|
|
27876
|
-
import { join as
|
|
28231
|
+
import { readFile as readFile41, access as access19 } from "fs/promises";
|
|
28232
|
+
import { join as join57, dirname as dirname27 } from "path";
|
|
27877
28233
|
import { fileURLToPath as fileURLToPath10 } from "url";
|
|
27878
|
-
import
|
|
27879
|
-
import { parse as
|
|
28234
|
+
import chalk17 from "chalk";
|
|
28235
|
+
import { parse as parseYaml14 } from "yaml";
|
|
27880
28236
|
init_ui();
|
|
27881
28237
|
var __dirname6 = dirname27(fileURLToPath10(import.meta.url));
|
|
27882
28238
|
var FRONTMATTER_REGEX4 = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
@@ -27887,12 +28243,12 @@ async function resolveCommandPath(rootDir, commandId) {
|
|
|
27887
28243
|
// Wave 7: legacy `.agents/commands/` probe for pre-1.9 installs that
|
|
27888
28244
|
// still ship a project-side canonical tree. New installs resolve via
|
|
27889
28245
|
// the bundled package root (second candidate).
|
|
27890
|
-
|
|
27891
|
-
|
|
28246
|
+
join57(rootDir, ".agents", "commands", filename),
|
|
28247
|
+
join57(findPackageRoot(__dirname6), "commands", filename)
|
|
27892
28248
|
];
|
|
27893
28249
|
for (const candidate of candidates) {
|
|
27894
28250
|
try {
|
|
27895
|
-
await
|
|
28251
|
+
await access19(candidate);
|
|
27896
28252
|
return candidate;
|
|
27897
28253
|
} catch (err) {
|
|
27898
28254
|
if (err.code !== "ENOENT") throw err;
|
|
@@ -27916,7 +28272,7 @@ function parseCommandFrontmatter(raw, filePath) {
|
|
|
27916
28272
|
);
|
|
27917
28273
|
}
|
|
27918
28274
|
const [, frontmatterStr] = match;
|
|
27919
|
-
const parsed =
|
|
28275
|
+
const parsed = parseYaml14(frontmatterStr ?? "");
|
|
27920
28276
|
if (!parsed || typeof parsed !== "object") {
|
|
27921
28277
|
throw new HatchError(
|
|
27922
28278
|
`Invalid frontmatter in ${filePath}: expected YAML object.`,
|
|
@@ -28018,12 +28374,12 @@ function computeTierRows(fm, bodyCharCount, agentDefCharCounts, options) {
|
|
|
28018
28374
|
async function resolveAgentDefCharCounts(fm) {
|
|
28019
28375
|
const ids = new Set(fm.agentPipeline);
|
|
28020
28376
|
if (fm.triageTiers.includes(3)) ids.add(RESEARCH_MODE_AGENT_ID);
|
|
28021
|
-
const agentsDir =
|
|
28377
|
+
const agentsDir = join57(findPackageRoot(__dirname6), "agents");
|
|
28022
28378
|
const counts = /* @__PURE__ */ new Map();
|
|
28023
28379
|
await Promise.all(
|
|
28024
28380
|
[...ids].map(async (id) => {
|
|
28025
28381
|
try {
|
|
28026
|
-
const raw = await readFile41(
|
|
28382
|
+
const raw = await readFile41(join57(agentsDir, `${id}.md`), "utf-8");
|
|
28027
28383
|
counts.set(id, raw.length);
|
|
28028
28384
|
} catch (err) {
|
|
28029
28385
|
verbose(
|
|
@@ -28082,7 +28438,7 @@ async function explainCommand(opts) {
|
|
|
28082
28438
|
if (modesRequested > 1) {
|
|
28083
28439
|
error("Conflicting flags: --cost, --customizations, --source, and --efficiency are mutually exclusive.");
|
|
28084
28440
|
if (!isQuiet()) {
|
|
28085
|
-
console.log(
|
|
28441
|
+
console.log(chalk17.dim(" Pick one mode per invocation."));
|
|
28086
28442
|
console.log();
|
|
28087
28443
|
}
|
|
28088
28444
|
throw new HatchError(
|
|
@@ -28095,10 +28451,10 @@ async function explainCommand(opts) {
|
|
|
28095
28451
|
if (modesRequested === 0) {
|
|
28096
28452
|
error("Missing required mode flag: pass --cost <command-id>, --customizations, --source <output-path>, OR --efficiency.");
|
|
28097
28453
|
if (!isQuiet()) {
|
|
28098
|
-
console.log(
|
|
28099
|
-
console.log(
|
|
28100
|
-
console.log(
|
|
28101
|
-
console.log(
|
|
28454
|
+
console.log(chalk17.dim(" Example: hatch3r explain --cost hatch3r-quick-change"));
|
|
28455
|
+
console.log(chalk17.dim(" Example: hatch3r explain --customizations"));
|
|
28456
|
+
console.log(chalk17.dim(" Example: hatch3r explain --source CLAUDE.md"));
|
|
28457
|
+
console.log(chalk17.dim(" Example: hatch3r explain --efficiency"));
|
|
28102
28458
|
console.log();
|
|
28103
28459
|
}
|
|
28104
28460
|
throw new HatchError(
|
|
@@ -28224,24 +28580,24 @@ async function explainCommand(opts) {
|
|
|
28224
28580
|
`${"Tier".padEnd(COL_LABEL)}${"Sub-agents".padEnd(COL_AGENTS)}${"Input".padEnd(COL_TOKENS)}${"Output".padEnd(COL_TOKENS)}${"Total".padEnd(COL_TOKENS)}${"USD".padEnd(COL_USD)}`
|
|
28225
28581
|
);
|
|
28226
28582
|
const tableWidth = COL_LABEL + COL_AGENTS + COL_TOKENS * 3 + COL_USD;
|
|
28227
|
-
tableLines.push(
|
|
28583
|
+
tableLines.push(chalk17.dim("\u2500".repeat(tableWidth)));
|
|
28228
28584
|
for (const row of rows) {
|
|
28229
28585
|
tableLines.push(
|
|
28230
28586
|
`${row.label.padEnd(COL_LABEL)}${String(row.subAgents).padEnd(COL_AGENTS)}${formatTokens(row.inputTokens).padEnd(COL_TOKENS)}${formatTokens(row.outputTokens).padEnd(COL_TOKENS)}${formatTokens(row.totalTokens).padEnd(COL_TOKENS)}${formatUsd(row.usd).padEnd(COL_USD)}`
|
|
28231
28587
|
);
|
|
28232
28588
|
}
|
|
28233
|
-
tableLines.push(
|
|
28589
|
+
tableLines.push(chalk17.dim("\u2500".repeat(tableWidth)));
|
|
28234
28590
|
tableLines.push(
|
|
28235
28591
|
`${"All tiers (sum)".padEnd(COL_LABEL)}${"\u2014".padEnd(COL_AGENTS)}${"".padEnd(COL_TOKENS)}${"".padEnd(COL_TOKENS)}${formatTokens(totalTokens).padEnd(COL_TOKENS)}${formatUsd(totalUsd).padEnd(COL_USD)}`
|
|
28236
28592
|
);
|
|
28237
28593
|
printBox("Per-tier cost estimate", tableLines, "info");
|
|
28238
28594
|
info(
|
|
28239
|
-
|
|
28595
|
+
chalk17.dim(
|
|
28240
28596
|
`Rates: $${inputRate}/1M input, $${outputRate}/1M output${cacheHitRatio > 0 ? `, ${Math.round(cacheHitRatio * 100)}% input cache-hit (cached input billed at 0.1x)` : ""}. Token counts use CHARS_PER_TOKEN=${CHARS_PER_TOKEN} (English prose heuristic).`
|
|
28241
28597
|
)
|
|
28242
28598
|
);
|
|
28243
28599
|
info(
|
|
28244
|
-
|
|
28600
|
+
chalk17.dim(
|
|
28245
28601
|
`Input basis: orchestrator body once (${formatTokens(estimateTokens(bodyCharCount, CHARS_PER_TOKEN))} tokens) + per sub-agent its own agents/<id>.md + a ~${formatTokens(estimateTokens(TASK_CONTEXT_ALLOWANCE_CHARS, CHARS_PER_TOKEN))}-token task allowance. An unsized actor (tier-1 inline path / missing agent def) bills at ~${formatTokens(estimateTokens(UNKNOWN_AGENT_DEF_CHARS, CHARS_PER_TOKEN))} tokens. Upper-bound estimate.`
|
|
28246
28602
|
)
|
|
28247
28603
|
);
|
|
@@ -28287,7 +28643,7 @@ async function explainCustomizationsMode(format = "human") {
|
|
|
28287
28643
|
if (summary.entries.length === 0) {
|
|
28288
28644
|
printBox(
|
|
28289
28645
|
"Customizations",
|
|
28290
|
-
[
|
|
28646
|
+
[chalk17.dim("No .customize.yaml or .customize.md files found under .hatch3r/{agents,skills,commands,rules}/.")],
|
|
28291
28647
|
"info"
|
|
28292
28648
|
);
|
|
28293
28649
|
return;
|
|
@@ -28319,7 +28675,7 @@ async function explainCustomizationsMode(format = "human") {
|
|
|
28319
28675
|
tableLines.push(
|
|
28320
28676
|
`${"Type".padEnd(COL_TYPE)}${"Id".padEnd(COL_ID)}${"Outcome".padEnd(COL_OUTCOME)}${"Overrides".padEnd(COL_OVERRIDES)}${"Reason".padEnd(COL_REASON)}`
|
|
28321
28677
|
);
|
|
28322
|
-
tableLines.push(
|
|
28678
|
+
tableLines.push(chalk17.dim("\u2500".repeat(COL_TYPE + COL_ID + COL_OUTCOME + COL_OVERRIDES + COL_REASON)));
|
|
28323
28679
|
for (const entry of sorted) {
|
|
28324
28680
|
const overridesParts = [];
|
|
28325
28681
|
if (entry.appliedOverrides.description !== void 0) overridesParts.push("desc");
|
|
@@ -28327,19 +28683,19 @@ async function explainCustomizationsMode(format = "human") {
|
|
|
28327
28683
|
if (entry.appliedOverrides.scope !== void 0) overridesParts.push("scope");
|
|
28328
28684
|
if (entry.appliedOverrides.enabled !== void 0) overridesParts.push(`enabled=${entry.appliedOverrides.enabled}`);
|
|
28329
28685
|
if (entry.hasMd && !overridesParts.includes("md")) overridesParts.push("md");
|
|
28330
|
-
const overridesCell = overridesParts.length > 0 ? overridesParts.join(",") :
|
|
28686
|
+
const overridesCell = overridesParts.length > 0 ? overridesParts.join(",") : chalk17.dim("\u2014");
|
|
28331
28687
|
const outcomeCell = (() => {
|
|
28332
28688
|
switch (entry.outcome) {
|
|
28333
28689
|
case "failed":
|
|
28334
|
-
return
|
|
28690
|
+
return chalk17.red("failed");
|
|
28335
28691
|
case "skipped":
|
|
28336
|
-
return
|
|
28692
|
+
return chalk17.yellow("skipped");
|
|
28337
28693
|
case "inert":
|
|
28338
|
-
return
|
|
28694
|
+
return chalk17.yellow("inert");
|
|
28339
28695
|
case "active":
|
|
28340
|
-
return
|
|
28696
|
+
return chalk17.green("active");
|
|
28341
28697
|
default:
|
|
28342
|
-
return
|
|
28698
|
+
return chalk17.dim("none");
|
|
28343
28699
|
}
|
|
28344
28700
|
})();
|
|
28345
28701
|
const idCell = entry.id.length > COL_ID - 1 ? `${entry.id.slice(0, COL_ID - 2)}\u2026` : entry.id;
|
|
@@ -28357,13 +28713,13 @@ async function explainCustomizationsMode(format = "human") {
|
|
|
28357
28713
|
// string stays byte-identical for repos with no deselected overrides.
|
|
28358
28714
|
(summary.counts.inert > 0 ? `, ${summary.counts.inert} inert` : "");
|
|
28359
28715
|
info(
|
|
28360
|
-
`${
|
|
28716
|
+
`${chalk17.bold("Customizations:")} ${summaryLine}. ` + chalk17.dim(`Run \`hatch3r status\` for a one-line summary.`)
|
|
28361
28717
|
);
|
|
28362
28718
|
console.log();
|
|
28363
28719
|
}
|
|
28364
28720
|
async function explainEfficiencyMode(format = "human") {
|
|
28365
28721
|
const rootDir = process.cwd();
|
|
28366
|
-
const logPath =
|
|
28722
|
+
const logPath = join57(rootDir, HATCH3R_DIR, "efficiency-events.jsonl");
|
|
28367
28723
|
let raw;
|
|
28368
28724
|
try {
|
|
28369
28725
|
raw = await readFile41(logPath, "utf-8");
|
|
@@ -28373,7 +28729,7 @@ async function explainEfficiencyMode(format = "human") {
|
|
|
28373
28729
|
error(`No efficiency telemetry found at ${HATCH3R_DIR}/efficiency-events.jsonl.`);
|
|
28374
28730
|
if (!isQuiet()) {
|
|
28375
28731
|
console.log(
|
|
28376
|
-
|
|
28732
|
+
chalk17.dim(
|
|
28377
28733
|
" Telemetry is gated by HATCH3R_EFFICIENCY_TELEMETRY=1. Set the env var and re-run the orchestrator, then re-invoke `hatch3r explain --efficiency`."
|
|
28378
28734
|
)
|
|
28379
28735
|
);
|
|
@@ -28479,10 +28835,10 @@ async function explainEfficiencyMode(format = "human") {
|
|
|
28479
28835
|
tableLines.push(
|
|
28480
28836
|
`${"Artifact".padEnd(COL_ART)}${"Phase".padEnd(COL_PHASE)}${"Inv".padEnd(COL_INVOK)}${"In".padEnd(COL_TOK)}${"Out".padEnd(COL_TOK)}${"Latency(ms)".padEnd(COL_LAT)}`
|
|
28481
28837
|
);
|
|
28482
|
-
tableLines.push(
|
|
28838
|
+
tableLines.push(chalk17.dim("\u2500".repeat(COL_ART + COL_PHASE + COL_INVOK + COL_TOK * 2 + COL_LAT)));
|
|
28483
28839
|
if (agg.spawnInvocations > 0) {
|
|
28484
28840
|
tableLines.push(
|
|
28485
|
-
`${artId.padEnd(COL_ART)}${"(spawns)".padEnd(COL_PHASE)}${String(agg.spawnInvocations).padEnd(COL_INVOK)}${"\u2014".padEnd(COL_TOK)}${"\u2014".padEnd(COL_TOK)}` +
|
|
28841
|
+
`${artId.padEnd(COL_ART)}${"(spawns)".padEnd(COL_PHASE)}${String(agg.spawnInvocations).padEnd(COL_INVOK)}${"\u2014".padEnd(COL_TOK)}${"\u2014".padEnd(COL_TOK)}` + chalk17.dim(`${agg.spawnSubAgents} sub-agent(s) total`)
|
|
28486
28842
|
);
|
|
28487
28843
|
}
|
|
28488
28844
|
const sortedPhases = [...agg.phases.keys()].sort((a, b) => a.localeCompare(b));
|
|
@@ -28495,7 +28851,7 @@ async function explainEfficiencyMode(format = "human") {
|
|
|
28495
28851
|
printBox(artId, tableLines, "info");
|
|
28496
28852
|
}
|
|
28497
28853
|
info(
|
|
28498
|
-
|
|
28854
|
+
chalk17.dim(
|
|
28499
28855
|
"Aggregates are sums across all sessions in the log. Truncate `.hatch3r/efficiency-events.jsonl` to reset."
|
|
28500
28856
|
)
|
|
28501
28857
|
);
|
|
@@ -28504,7 +28860,7 @@ async function explainEfficiencyMode(format = "human") {
|
|
|
28504
28860
|
async function explainSourceMode(subject, detail = false, format = "human") {
|
|
28505
28861
|
const jsonMode = format === "json";
|
|
28506
28862
|
const rootDir = process.cwd();
|
|
28507
|
-
const provenancePath =
|
|
28863
|
+
const provenancePath = join57(rootDir, HATCH3R_DIR, "provenance.json");
|
|
28508
28864
|
let manifest;
|
|
28509
28865
|
try {
|
|
28510
28866
|
const raw = await readFile41(provenancePath, "utf-8");
|
|
@@ -28522,7 +28878,7 @@ async function explainSourceMode(subject, detail = false, format = "human") {
|
|
|
28522
28878
|
});
|
|
28523
28879
|
} else {
|
|
28524
28880
|
error(`No provenance manifest found at ${HATCH3R_DIR}/provenance.json.`);
|
|
28525
|
-
console.log(
|
|
28881
|
+
console.log(chalk17.dim(" Run `hatch3r sync` to generate adapter outputs and record their canonical sources."));
|
|
28526
28882
|
console.log();
|
|
28527
28883
|
}
|
|
28528
28884
|
throw new HatchError(
|
|
@@ -28554,7 +28910,7 @@ async function explainSourceMode(subject, detail = false, format = "human") {
|
|
|
28554
28910
|
}
|
|
28555
28911
|
printBox(
|
|
28556
28912
|
"Source provenance",
|
|
28557
|
-
[
|
|
28913
|
+
[chalk17.dim(`No outputs recorded in ${HATCH3R_DIR}/provenance.json. Run \`hatch3r sync\` first.`)],
|
|
28558
28914
|
"info"
|
|
28559
28915
|
);
|
|
28560
28916
|
return;
|
|
@@ -28629,18 +28985,18 @@ async function explainSourceMode(subject, detail = false, format = "human") {
|
|
|
28629
28985
|
tableLines.push(
|
|
28630
28986
|
`${"Output".padEnd(COL_OUTPUT)}${"Adapter".padEnd(COL_ADAPTER)}Sources`
|
|
28631
28987
|
);
|
|
28632
|
-
tableLines.push(
|
|
28988
|
+
tableLines.push(chalk17.dim("\u2500".repeat(COL_OUTPUT + COL_ADAPTER + 8)));
|
|
28633
28989
|
for (const entry of sorted) {
|
|
28634
28990
|
const pathCell = entry.path.length > COL_OUTPUT - 1 ? `${entry.path.slice(0, COL_OUTPUT - 2)}\u2026` : entry.path;
|
|
28635
28991
|
const count = entry.sourceFiles.length;
|
|
28636
|
-
const countCell = count === 0 ?
|
|
28992
|
+
const countCell = count === 0 ? chalk17.dim("0") : String(count);
|
|
28637
28993
|
tableLines.push(
|
|
28638
28994
|
`${pathCell.padEnd(COL_OUTPUT)}${entry.adapter.padEnd(COL_ADAPTER)}${countCell}`
|
|
28639
28995
|
);
|
|
28640
28996
|
}
|
|
28641
28997
|
printBox("Per-output source counts", tableLines, "info");
|
|
28642
28998
|
info(
|
|
28643
|
-
|
|
28999
|
+
chalk17.dim(
|
|
28644
29000
|
"Showing source counts. Run `hatch3r explain --source <output-path>` for one output's full list, or `hatch3r explain --source all --verbose` for every list."
|
|
28645
29001
|
)
|
|
28646
29002
|
);
|
|
@@ -28657,8 +29013,8 @@ async function explainSourceMode(subject, detail = false, format = "human") {
|
|
|
28657
29013
|
error(`Output path not recorded in provenance: ${normalized}`);
|
|
28658
29014
|
const known = outputs.map((o) => o.path).sort();
|
|
28659
29015
|
const preview = known.slice(0, 8).join(", ") + (known.length > 8 ? `, \u2026 (${known.length} total)` : "");
|
|
28660
|
-
console.log(
|
|
28661
|
-
console.log(
|
|
29016
|
+
console.log(chalk17.dim(` Recorded outputs: ${preview}`));
|
|
29017
|
+
console.log(chalk17.dim(" Tip: run `hatch3r explain --source all` to list every recorded output."));
|
|
28662
29018
|
console.log();
|
|
28663
29019
|
throw new HatchError(
|
|
28664
29020
|
`Output path not recorded: ${normalized}`,
|
|
@@ -28681,33 +29037,33 @@ function printSourceBlock(entry, manifest) {
|
|
|
28681
29037
|
if (manifest.lastRunId) lines.push(label("Run id", manifest.lastRunId));
|
|
28682
29038
|
}
|
|
28683
29039
|
if (entry.sourceFiles.length === 0) {
|
|
28684
|
-
lines.push(label("Sources",
|
|
29040
|
+
lines.push(label("Sources", chalk17.dim("(none recorded)")));
|
|
28685
29041
|
} else {
|
|
28686
29042
|
lines.push(label("Sources", `${entry.sourceFiles.length} canonical file(s):`));
|
|
28687
29043
|
for (const src of entry.sourceFiles) {
|
|
28688
|
-
lines.push(` ${
|
|
29044
|
+
lines.push(` ${chalk17.cyan("\u2022")} ${src}`);
|
|
28689
29045
|
}
|
|
28690
29046
|
}
|
|
28691
29047
|
printBox(`Source: ${entry.path}`, lines, "info");
|
|
28692
29048
|
}
|
|
28693
29049
|
|
|
28694
29050
|
// src/cli/commands/rollback.ts
|
|
28695
|
-
import
|
|
28696
|
-
import
|
|
29051
|
+
import chalk18 from "chalk";
|
|
29052
|
+
import inquirer13 from "inquirer";
|
|
28697
29053
|
init_types();
|
|
28698
29054
|
init_safeWrite();
|
|
28699
29055
|
init_ui();
|
|
28700
29056
|
function formatSnapshotRow(meta) {
|
|
28701
29057
|
const ts = meta.timestamp.replace("T", " ").replace(/\.[0-9]+Z$/, "Z");
|
|
28702
29058
|
const count = `${meta.paths.length} file${meta.paths.length === 1 ? "" : "s"}`;
|
|
28703
|
-
return ` ${
|
|
29059
|
+
return ` ${chalk18.cyan("\u2022")} ${chalk18.bold(meta.sessionId)} ${chalk18.dim(ts)} ${chalk18.dim(count)}`;
|
|
28704
29060
|
}
|
|
28705
29061
|
async function confirmRollback(meta) {
|
|
28706
|
-
const { confirmed } = await
|
|
29062
|
+
const { confirmed } = await inquirer13.prompt([
|
|
28707
29063
|
{
|
|
28708
29064
|
type: "confirm",
|
|
28709
29065
|
name: "confirmed",
|
|
28710
|
-
message: `Restore ${meta.paths.length} file(s) from session ${
|
|
29066
|
+
message: `Restore ${meta.paths.length} file(s) from session ${chalk18.bold(meta.sessionId)} (captured ${meta.timestamp})? This overwrites the current on-disk state.`,
|
|
28711
29067
|
default: false
|
|
28712
29068
|
}
|
|
28713
29069
|
]);
|
|
@@ -28731,7 +29087,7 @@ async function rollbackCommand(opts = {}) {
|
|
|
28731
29087
|
const snapshots = await listSnapshots();
|
|
28732
29088
|
const meta = snapshots.find((s) => s.sessionId === opts.session);
|
|
28733
29089
|
if (!meta) {
|
|
28734
|
-
error(`Session ${
|
|
29090
|
+
error(`Session ${chalk18.bold(opts.session)} not found.`);
|
|
28735
29091
|
info("Run `hatch3r rollback list` to see available sessions.");
|
|
28736
29092
|
throw new HatchError(
|
|
28737
29093
|
`Session ${opts.session} not found.`,
|
|
@@ -28763,10 +29119,10 @@ async function rollbackCommand(opts = {}) {
|
|
|
28763
29119
|
const result = await applyRollback(meta.sessionId, { dryRun: opts.dryRun });
|
|
28764
29120
|
spinner.stop();
|
|
28765
29121
|
const summaryLines = [
|
|
28766
|
-
`${
|
|
29122
|
+
`${chalk18.green("\u2714")} Files restored: ${result.filesRestored} / ${meta.paths.length}`
|
|
28767
29123
|
];
|
|
28768
29124
|
if (result.errors.length > 0) {
|
|
28769
|
-
summaryLines.push(`${
|
|
29125
|
+
summaryLines.push(`${chalk18.red("\u2718")} Errors: ${result.errors.length}`);
|
|
28770
29126
|
}
|
|
28771
29127
|
const tag = opts.dryRun ? "rollback (dry-run)" : "rollback";
|
|
28772
29128
|
const jsonPayload = {
|
|
@@ -28830,19 +29186,19 @@ async function rollbackListCommand(opts = {}) {
|
|
|
28830
29186
|
info("Snapshots are captured by long-running orchestrators (init, sync, audit).");
|
|
28831
29187
|
return;
|
|
28832
29188
|
}
|
|
28833
|
-
console.log(
|
|
29189
|
+
console.log(chalk18.bold(`Snapshot sessions (${snapshots.length}):`));
|
|
28834
29190
|
for (const meta of snapshots) {
|
|
28835
29191
|
console.log(formatSnapshotRow(meta));
|
|
28836
29192
|
}
|
|
28837
29193
|
console.log();
|
|
28838
|
-
info(`Run ${
|
|
29194
|
+
info(`Run ${chalk18.cyan("hatch3r rollback --session=<id>")} to restore a session.`);
|
|
28839
29195
|
}
|
|
28840
29196
|
|
|
28841
29197
|
// src/cli/commands/show.ts
|
|
28842
29198
|
import { readFile as readFile42 } from "fs/promises";
|
|
28843
|
-
import { join as
|
|
28844
|
-
import
|
|
28845
|
-
import { parse as
|
|
29199
|
+
import { join as join58 } from "path";
|
|
29200
|
+
import chalk19 from "chalk";
|
|
29201
|
+
import { parse as parseYaml15 } from "yaml";
|
|
28846
29202
|
init_types();
|
|
28847
29203
|
init_ui();
|
|
28848
29204
|
var FRONTMATTER_REGEX5 = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
@@ -28851,11 +29207,11 @@ function parseFrontmatter2(raw) {
|
|
|
28851
29207
|
const match = raw.match(FRONTMATTER_REGEX5);
|
|
28852
29208
|
if (!match) return { frontmatter: {}, body: raw };
|
|
28853
29209
|
try {
|
|
28854
|
-
const parsed =
|
|
29210
|
+
const parsed = parseYaml15(match[1]);
|
|
28855
29211
|
return { frontmatter: parsed ?? {}, body: match[2] };
|
|
28856
29212
|
} catch (err) {
|
|
28857
29213
|
console.error(
|
|
28858
|
-
` ${
|
|
29214
|
+
` ${chalk19.yellow("\u26A0")} show: YAML frontmatter parse failed \u2014 ${err instanceof Error ? err.message : String(err)}. Treating as frontmatter-less.`
|
|
28859
29215
|
);
|
|
28860
29216
|
return { frontmatter: {}, body: match[2] };
|
|
28861
29217
|
}
|
|
@@ -28896,7 +29252,7 @@ async function showCommand(idArg, opts = {}) {
|
|
|
28896
29252
|
);
|
|
28897
29253
|
}
|
|
28898
29254
|
const fileRoot = item.source === "user" && userRoot ? userRoot : canonicalRoot;
|
|
28899
|
-
const filePath =
|
|
29255
|
+
const filePath = join58(fileRoot, item.relativePath);
|
|
28900
29256
|
const raw = await readFile42(filePath, "utf-8");
|
|
28901
29257
|
const { frontmatter, body } = parseFrontmatter2(raw);
|
|
28902
29258
|
const headerLines = [
|
|
@@ -28949,7 +29305,7 @@ async function showCommand(idArg, opts = {}) {
|
|
|
28949
29305
|
if (truncated) {
|
|
28950
29306
|
console.log();
|
|
28951
29307
|
console.log(
|
|
28952
|
-
|
|
29308
|
+
chalk19.dim(` \u2026 ${bodyLines.length - BODY_PREVIEW_LINES} more line(s) \u2014 open ${filePath} for the full body.`)
|
|
28953
29309
|
);
|
|
28954
29310
|
}
|
|
28955
29311
|
console.log();
|
|
@@ -29015,12 +29371,12 @@ async function listCommand(typeArg, opts = {}) {
|
|
|
29015
29371
|
info(`No artifacts of type "${type}" found.`);
|
|
29016
29372
|
return;
|
|
29017
29373
|
}
|
|
29018
|
-
console.log(
|
|
29374
|
+
console.log(chalk19.bold(`${type} (${items.length}):`));
|
|
29019
29375
|
for (const item of items) {
|
|
29020
29376
|
const tagPreview = item.tags.slice(0, 3).join(", ") + (item.tags.length > 3 ? ` \u2026 (+${item.tags.length - 3})` : "");
|
|
29021
|
-
const protectedTag = item.protected ?
|
|
29022
|
-
const sourceTag = item.source === "user" ?
|
|
29023
|
-
console.log(` ${
|
|
29377
|
+
const protectedTag = item.protected ? chalk19.red(" [protected]") : "";
|
|
29378
|
+
const sourceTag = item.source === "user" ? chalk19.yellow(" [user]") : "";
|
|
29379
|
+
console.log(` ${chalk19.cyan("\u2022")} ${chalk19.bold(item.id)}${protectedTag}${sourceTag} ${chalk19.dim(tagPreview)}`);
|
|
29024
29380
|
}
|
|
29025
29381
|
console.log();
|
|
29026
29382
|
info(`Run \`hatch3r show <id>\` to inspect frontmatter + body preview.`);
|
|
@@ -29031,13 +29387,13 @@ init_types();
|
|
|
29031
29387
|
init_version();
|
|
29032
29388
|
init_ui();
|
|
29033
29389
|
import { readFile as readFile43 } from "fs/promises";
|
|
29034
|
-
import { join as
|
|
29035
|
-
import
|
|
29390
|
+
import { join as join59 } from "path";
|
|
29391
|
+
import chalk20 from "chalk";
|
|
29036
29392
|
async function provenanceCommand(opts) {
|
|
29037
29393
|
const format = beginCommand(opts ?? {}, { banner: "compact" });
|
|
29038
29394
|
const jsonMode = format === "json";
|
|
29039
29395
|
const rootDir = process.cwd();
|
|
29040
|
-
const provenancePath =
|
|
29396
|
+
const provenancePath = join59(rootDir, HATCH3R_DIR, "provenance.json");
|
|
29041
29397
|
let manifest;
|
|
29042
29398
|
try {
|
|
29043
29399
|
const raw = await readFile43(provenancePath, "utf-8");
|
|
@@ -29056,7 +29412,7 @@ async function provenanceCommand(opts) {
|
|
|
29056
29412
|
} else {
|
|
29057
29413
|
error(`No provenance manifest found at ${HATCH3R_DIR}/provenance.json.`);
|
|
29058
29414
|
console.log(
|
|
29059
|
-
|
|
29415
|
+
chalk20.dim(" Run `hatch3r sync` to generate adapter outputs and record their canonical sources.")
|
|
29060
29416
|
);
|
|
29061
29417
|
console.log();
|
|
29062
29418
|
}
|
|
@@ -29108,7 +29464,7 @@ async function provenanceCommand(opts) {
|
|
|
29108
29464
|
}
|
|
29109
29465
|
const adapterLines = [];
|
|
29110
29466
|
for (const [adapter, count] of [...perAdapter.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
29111
|
-
adapterLines.push(`${
|
|
29467
|
+
adapterLines.push(`${chalk20.cyan("\u2022")} ${adapter.padEnd(12)} ${count} output(s)`);
|
|
29112
29468
|
}
|
|
29113
29469
|
finishCommand(format, {
|
|
29114
29470
|
command: "provenance",
|
|
@@ -29123,9 +29479,9 @@ async function provenanceCommand(opts) {
|
|
|
29123
29479
|
|
|
29124
29480
|
// src/cli/commands/deps.ts
|
|
29125
29481
|
import { readFile as readFile44 } from "fs/promises";
|
|
29126
|
-
import { join as
|
|
29127
|
-
import
|
|
29128
|
-
import { parse as
|
|
29482
|
+
import { join as join60 } from "path";
|
|
29483
|
+
import chalk21 from "chalk";
|
|
29484
|
+
import { parse as parseYaml16 } from "yaml";
|
|
29129
29485
|
init_types();
|
|
29130
29486
|
init_ui();
|
|
29131
29487
|
var FRONTMATTER_REGEX6 = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
@@ -29133,10 +29489,10 @@ function parseFrontmatter3(raw, sourcePath) {
|
|
|
29133
29489
|
const match = raw.match(FRONTMATTER_REGEX6);
|
|
29134
29490
|
if (!match) return {};
|
|
29135
29491
|
try {
|
|
29136
|
-
return
|
|
29492
|
+
return parseYaml16(match[1]) ?? {};
|
|
29137
29493
|
} catch (err) {
|
|
29138
29494
|
console.error(
|
|
29139
|
-
` ${
|
|
29495
|
+
` ${chalk21.yellow("\u26A0")} deps: YAML frontmatter parse failed${sourcePath ? ` in ${sourcePath}` : ""} \u2014 ${err instanceof Error ? err.message : String(err)}`
|
|
29140
29496
|
);
|
|
29141
29497
|
return {};
|
|
29142
29498
|
}
|
|
@@ -29205,7 +29561,7 @@ async function depsCommand(idArg, opts = {}) {
|
|
|
29205
29561
|
);
|
|
29206
29562
|
}
|
|
29207
29563
|
const fileRoot = item.source === "user" && userRoot ? userRoot : canonicalRoot;
|
|
29208
|
-
const filePath = item.type === "skill" ?
|
|
29564
|
+
const filePath = item.type === "skill" ? join60(fileRoot, item.relativePath, "SKILL.md") : join60(fileRoot, item.relativePath);
|
|
29209
29565
|
let frontmatter = {};
|
|
29210
29566
|
let body = "";
|
|
29211
29567
|
try {
|
|
@@ -29235,7 +29591,7 @@ async function depsCommand(idArg, opts = {}) {
|
|
|
29235
29591
|
for (const candidate of index.items) {
|
|
29236
29592
|
if (candidate.id === item.id) continue;
|
|
29237
29593
|
const candFileRoot = candidate.source === "user" && userRoot ? userRoot : canonicalRoot;
|
|
29238
|
-
const candPath = candidate.type === "skill" ?
|
|
29594
|
+
const candPath = candidate.type === "skill" ? join60(candFileRoot, candidate.relativePath, "SKILL.md") : join60(candFileRoot, candidate.relativePath);
|
|
29239
29595
|
try {
|
|
29240
29596
|
const candRaw = await readFile44(candPath, "utf-8");
|
|
29241
29597
|
const candFm = parseFrontmatter3(candRaw, candidate.relativePath);
|
|
@@ -29248,7 +29604,7 @@ async function depsCommand(idArg, opts = {}) {
|
|
|
29248
29604
|
} catch (err) {
|
|
29249
29605
|
const code = err.code ?? "UNKNOWN";
|
|
29250
29606
|
console.error(
|
|
29251
|
-
` ${
|
|
29607
|
+
` ${chalk21.dim("\u2139")} deps: skipping ${candidate.relativePath} (${code}) \u2014 cannot scan upstream references`
|
|
29252
29608
|
);
|
|
29253
29609
|
}
|
|
29254
29610
|
}
|
|
@@ -29289,50 +29645,50 @@ async function depsCommand(idArg, opts = {}) {
|
|
|
29289
29645
|
headerLines.push(label("Orchestrator", String(frontmatter.orchestrator)));
|
|
29290
29646
|
}
|
|
29291
29647
|
printBox(`Dependencies: ${item.id}`, headerLines, "info");
|
|
29292
|
-
console.log(
|
|
29648
|
+
console.log(chalk21.bold("Downstream (this artifact delegates to, from frontmatter):"));
|
|
29293
29649
|
if (downstream.length === 0) {
|
|
29294
|
-
console.log(
|
|
29650
|
+
console.log(chalk21.dim(" (no agentPipeline / delegates entries in frontmatter)"));
|
|
29295
29651
|
} else {
|
|
29296
29652
|
for (let i = 0; i < downstream.length; i++) {
|
|
29297
29653
|
const { id, resolved } = downstream[i];
|
|
29298
|
-
const status = resolved ?
|
|
29299
|
-
const tag = resolved ? "" :
|
|
29300
|
-
console.log(` ${status} ${id}${tag} ${
|
|
29654
|
+
const status = resolved ? chalk21.green("\u2713") : chalk21.red("\u2717");
|
|
29655
|
+
const tag = resolved ? "" : chalk21.red(" [not in content index]");
|
|
29656
|
+
console.log(` ${status} ${id}${tag} ${chalk21.dim(`(${sources[i]})`)}`);
|
|
29301
29657
|
}
|
|
29302
29658
|
}
|
|
29303
29659
|
console.log();
|
|
29304
29660
|
const refCount = refs.skills.length + refs.rules.length + refs.mcpServers.length;
|
|
29305
29661
|
console.log(
|
|
29306
|
-
|
|
29662
|
+
chalk21.bold("References (best-effort, prose-derived \u2014 not authoritative):")
|
|
29307
29663
|
);
|
|
29308
29664
|
if (refCount === 0) {
|
|
29309
|
-
console.log(
|
|
29665
|
+
console.log(chalk21.dim(" (no skill / rule / MCP references found in body prose)"));
|
|
29310
29666
|
} else {
|
|
29311
29667
|
for (const id of refs.skills) {
|
|
29312
|
-
console.log(` ${
|
|
29668
|
+
console.log(` ${chalk21.cyan("\u2022")} ${id} ${chalk21.dim("(skill)")}`);
|
|
29313
29669
|
}
|
|
29314
29670
|
for (const id of refs.rules) {
|
|
29315
|
-
console.log(` ${
|
|
29671
|
+
console.log(` ${chalk21.cyan("\u2022")} ${id} ${chalk21.dim("(rule)")}`);
|
|
29316
29672
|
}
|
|
29317
29673
|
for (const name of refs.mcpServers) {
|
|
29318
|
-
console.log(` ${
|
|
29674
|
+
console.log(` ${chalk21.cyan("\u2022")} ${name} ${chalk21.dim("(MCP server, name-matched)")}`);
|
|
29319
29675
|
}
|
|
29320
29676
|
}
|
|
29321
29677
|
console.log();
|
|
29322
|
-
console.log(
|
|
29678
|
+
console.log(chalk21.bold("Upstream (these artifacts delegate to this one):"));
|
|
29323
29679
|
if (upstream.length === 0) {
|
|
29324
|
-
console.log(
|
|
29680
|
+
console.log(chalk21.dim(" (no artifacts reference this one in their agentPipeline / delegates)"));
|
|
29325
29681
|
} else {
|
|
29326
29682
|
for (const u of upstream) {
|
|
29327
|
-
console.log(` ${
|
|
29683
|
+
console.log(` ${chalk21.cyan("\u2022")} ${u.id} ${chalk21.dim(`(${u.type}, via ${u.via})`)}`);
|
|
29328
29684
|
}
|
|
29329
29685
|
}
|
|
29330
29686
|
console.log();
|
|
29331
29687
|
}
|
|
29332
29688
|
|
|
29333
29689
|
// src/cli/commands/learn.ts
|
|
29334
|
-
import { mkdir as
|
|
29335
|
-
import { basename as
|
|
29690
|
+
import { mkdir as mkdir20, readFile as readFile45 } from "fs/promises";
|
|
29691
|
+
import { basename as basename5, isAbsolute as isAbsolute5, join as join61, resolve as resolve9 } from "path";
|
|
29336
29692
|
init_types();
|
|
29337
29693
|
init_ui();
|
|
29338
29694
|
var FRONTMATTER_REGEX7 = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
@@ -29361,7 +29717,7 @@ async function learnCaptureCommand(options) {
|
|
|
29361
29717
|
);
|
|
29362
29718
|
}
|
|
29363
29719
|
const rootDir = process.cwd();
|
|
29364
|
-
const sourcePath = isAbsolute5(options.file) ? options.file :
|
|
29720
|
+
const sourcePath = isAbsolute5(options.file) ? options.file : resolve9(rootDir, options.file);
|
|
29365
29721
|
let raw;
|
|
29366
29722
|
try {
|
|
29367
29723
|
raw = await readFile45(sourcePath, "utf-8");
|
|
@@ -29375,7 +29731,7 @@ async function learnCaptureCommand(options) {
|
|
|
29375
29731
|
"Confirm the path your /learn flow wrote to exists and is readable, then re-run `hatch3r learn capture --file <path>`."
|
|
29376
29732
|
);
|
|
29377
29733
|
}
|
|
29378
|
-
const destName = typeof options.as === "string" && options.as.trim().length > 0 ? options.as.trim() :
|
|
29734
|
+
const destName = typeof options.as === "string" && options.as.trim().length > 0 ? options.as.trim() : basename5(sourcePath);
|
|
29379
29735
|
const nameErrors = validateLearningFileName(destName);
|
|
29380
29736
|
if (nameErrors.length > 0) {
|
|
29381
29737
|
for (const e of nameErrors) error(e);
|
|
@@ -29386,8 +29742,8 @@ async function learnCaptureCommand(options) {
|
|
|
29386
29742
|
"Use a `.md` filename with only alphanumerics, hyphens, underscores, and dots (no path separators)."
|
|
29387
29743
|
);
|
|
29388
29744
|
}
|
|
29389
|
-
const learningsDir =
|
|
29390
|
-
const targetPath =
|
|
29745
|
+
const learningsDir = join61(rootDir, HATCH3R_DIR, "learnings");
|
|
29746
|
+
const targetPath = join61(learningsDir, destName);
|
|
29391
29747
|
const { body, declaredIntegrity } = extractBodyAndIntegrity(raw);
|
|
29392
29748
|
if (declaredIntegrity) {
|
|
29393
29749
|
const recomputed = computeLearningIntegrity(body);
|
|
@@ -29410,7 +29766,7 @@ async function learnCaptureCommand(options) {
|
|
|
29410
29766
|
);
|
|
29411
29767
|
}
|
|
29412
29768
|
const dryRun = options.dryRun === true;
|
|
29413
|
-
if (!dryRun) await
|
|
29769
|
+
if (!dryRun) await mkdir20(learningsDir, { recursive: true });
|
|
29414
29770
|
const result = await persistLearning(targetPath, raw, {
|
|
29415
29771
|
source: "learn-command",
|
|
29416
29772
|
dryRun
|
|
@@ -29488,13 +29844,13 @@ async function learnCaptureCommand(options) {
|
|
|
29488
29844
|
// src/cli/commands/mcp.ts
|
|
29489
29845
|
import { existsSync as existsSync6 } from "fs";
|
|
29490
29846
|
import { readFile as readFile46 } from "fs/promises";
|
|
29491
|
-
import { join as
|
|
29492
|
-
import
|
|
29847
|
+
import { join as join62 } from "path";
|
|
29848
|
+
import chalk22 from "chalk";
|
|
29493
29849
|
init_safeWrite();
|
|
29494
29850
|
init_types();
|
|
29495
29851
|
init_ui();
|
|
29496
29852
|
function wslThemeOrUndefined() {
|
|
29497
|
-
return isWSL() ? { icon: { checked:
|
|
29853
|
+
return isWSL() ? { icon: { checked: chalk22.green("[x]"), unchecked: "[ ]", cursor: ">" } } : void 0;
|
|
29498
29854
|
}
|
|
29499
29855
|
async function sweepOrphanTmpAtEntry(rootDir) {
|
|
29500
29856
|
try {
|
|
@@ -29545,8 +29901,8 @@ async function mcpSetupCommand(opts = {}) {
|
|
|
29545
29901
|
await ensureGitignoreEntry(rootDir);
|
|
29546
29902
|
if (envResult.newVars.length > 0) {
|
|
29547
29903
|
envAdvisoryLines.push("");
|
|
29548
|
-
envAdvisoryLines.push(`${
|
|
29549
|
-
envAdvisoryLines.push(` Then run: ${
|
|
29904
|
+
envAdvisoryLines.push(`${chalk22.yellow("!")} Add new secrets to ${chalk22.bold(".env.mcp")}: ${envResult.newVars.join(", ")}`);
|
|
29905
|
+
envAdvisoryLines.push(` Then run: ${chalk22.dim(getSourceEnvMcpCommand())}`);
|
|
29550
29906
|
}
|
|
29551
29907
|
}
|
|
29552
29908
|
finishCommand(format, {
|
|
@@ -29569,7 +29925,7 @@ async function mcpListCommand(opts = {}) {
|
|
|
29569
29925
|
const manifest = await readManifest(rootDir);
|
|
29570
29926
|
assertManifest(manifest);
|
|
29571
29927
|
const servers = manifest.mcp.servers;
|
|
29572
|
-
const envPath =
|
|
29928
|
+
const envPath = join62(rootDir, ".env.mcp");
|
|
29573
29929
|
const hasEnvFile = existsSync6(envPath);
|
|
29574
29930
|
const envExisting = hasEnvFile ? parseEnvFile(await readFile46(envPath, "utf-8")) : {};
|
|
29575
29931
|
const requiredVars = collectRequiredEnvVars(servers);
|
|
@@ -29583,16 +29939,16 @@ async function mcpListCommand(opts = {}) {
|
|
|
29583
29939
|
for (const id of servers) {
|
|
29584
29940
|
const meta = AVAILABLE_MCP_SERVERS[id];
|
|
29585
29941
|
const desc = meta?.description ?? "(unknown server)";
|
|
29586
|
-
lines.push(` ${
|
|
29942
|
+
lines.push(` ${chalk22.cyan(id)} \u2014 ${desc}`);
|
|
29587
29943
|
}
|
|
29588
29944
|
lines.push("");
|
|
29589
|
-
lines.push(label(".env.mcp", hasEnvFile ? "present" :
|
|
29945
|
+
lines.push(label(".env.mcp", hasEnvFile ? "present" : chalk22.yellow("missing")));
|
|
29590
29946
|
if (requiredVars.length > 0) {
|
|
29591
29947
|
lines.push(label("Required vars", requiredVars.map((v) => v.name).join(", ")));
|
|
29592
29948
|
if (missingVars.length > 0) {
|
|
29593
|
-
lines.push(label("Missing",
|
|
29949
|
+
lines.push(label("Missing", chalk22.yellow(missingVars.map((v) => v.name).join(", "))));
|
|
29594
29950
|
} else {
|
|
29595
|
-
lines.push(label("Status",
|
|
29951
|
+
lines.push(label("Status", chalk22.green("all required vars set")));
|
|
29596
29952
|
}
|
|
29597
29953
|
}
|
|
29598
29954
|
}
|
|
@@ -29619,7 +29975,7 @@ async function mcpRemoveCommand(id, opts = {}) {
|
|
|
29619
29975
|
if (!before.includes(id)) {
|
|
29620
29976
|
error(`MCP server "${id}" is not configured.`);
|
|
29621
29977
|
if (!isQuiet()) {
|
|
29622
|
-
console.log(
|
|
29978
|
+
console.log(chalk22.dim(` Current servers: ${before.length > 0 ? before.join(", ") : "(none)"}
|
|
29623
29979
|
`));
|
|
29624
29980
|
}
|
|
29625
29981
|
throw new HatchError(
|
|
@@ -29674,7 +30030,7 @@ async function mcpEnvCheckCommand(opts = {}) {
|
|
|
29674
30030
|
const manifest = await readManifest(rootDir);
|
|
29675
30031
|
assertManifest(manifest);
|
|
29676
30032
|
const servers = manifest.mcp.servers;
|
|
29677
|
-
const envPath =
|
|
30033
|
+
const envPath = join62(rootDir, ".env.mcp");
|
|
29678
30034
|
const hasEnvFile = existsSync6(envPath);
|
|
29679
30035
|
const envExisting = hasEnvFile ? parseEnvFile(await readFile46(envPath, "utf-8")) : {};
|
|
29680
30036
|
const lines = [];
|
|
@@ -29695,21 +30051,21 @@ async function mcpEnvCheckCommand(opts = {}) {
|
|
|
29695
30051
|
const meta = AVAILABLE_MCP_SERVERS[id];
|
|
29696
30052
|
const required = meta?.requiresEnv ?? [];
|
|
29697
30053
|
if (required.length === 0) {
|
|
29698
|
-
lines.push(`${
|
|
30054
|
+
lines.push(`${chalk22.green("\u2713")} ${id} \u2014 no env vars required`);
|
|
29699
30055
|
serverReports.push({ id, required: [], missing: [] });
|
|
29700
30056
|
continue;
|
|
29701
30057
|
}
|
|
29702
30058
|
const missing = required.filter((name) => !(name in envExisting) || envExisting[name] === "");
|
|
29703
30059
|
serverReports.push({ id, required, missing });
|
|
29704
30060
|
if (missing.length === 0) {
|
|
29705
|
-
lines.push(`${
|
|
30061
|
+
lines.push(`${chalk22.green("\u2713")} ${id} \u2014 ${required.join(", ")}`);
|
|
29706
30062
|
} else {
|
|
29707
|
-
lines.push(`${
|
|
30063
|
+
lines.push(`${chalk22.yellow("!")} ${id} \u2014 missing: ${missing.join(", ")}`);
|
|
29708
30064
|
missingTotal += missing.length;
|
|
29709
30065
|
}
|
|
29710
30066
|
}
|
|
29711
30067
|
lines.push("");
|
|
29712
|
-
lines.push(label(".env.mcp", hasEnvFile ? "present" :
|
|
30068
|
+
lines.push(label(".env.mcp", hasEnvFile ? "present" : chalk22.yellow("missing")));
|
|
29713
30069
|
if (missingTotal > 0) {
|
|
29714
30070
|
lines.push(label("Action", `Fill ${missingTotal} env var(s) in .env.mcp, then \`${getSourceEnvMcpCommand()}\``));
|
|
29715
30071
|
}
|
|
@@ -29723,11 +30079,11 @@ async function mcpEnvCheckCommand(opts = {}) {
|
|
|
29723
30079
|
}
|
|
29724
30080
|
|
|
29725
30081
|
// src/cli/commands/cliTools.ts
|
|
29726
|
-
import
|
|
30082
|
+
import chalk23 from "chalk";
|
|
29727
30083
|
init_safeWrite();
|
|
29728
30084
|
init_ui();
|
|
29729
30085
|
function wslThemeOrUndefined2() {
|
|
29730
|
-
return isWSL() ? { icon: { checked:
|
|
30086
|
+
return isWSL() ? { icon: { checked: chalk23.green("[x]"), unchecked: "[ ]", cursor: ">" } } : void 0;
|
|
29731
30087
|
}
|
|
29732
30088
|
async function cliToolsCommand(opts = {}) {
|
|
29733
30089
|
const format = beginCommand(opts, { banner: "compact", interactive: true });
|
|
@@ -29785,9 +30141,9 @@ async function cliToolsCommand(opts = {}) {
|
|
|
29785
30141
|
}
|
|
29786
30142
|
}
|
|
29787
30143
|
if (secretNotes.length > 0) {
|
|
29788
|
-
info(
|
|
30144
|
+
info(chalk23.dim("CLI tool environment variables required:"));
|
|
29789
30145
|
for (const note of secretNotes) {
|
|
29790
|
-
info(
|
|
30146
|
+
info(chalk23.dim(` ${note}`));
|
|
29791
30147
|
}
|
|
29792
30148
|
}
|
|
29793
30149
|
}
|
|
@@ -29841,13 +30197,13 @@ async function cliToolsListCommand(opts = {}) {
|
|
|
29841
30197
|
const tierLabel2 = meta ? `tier ${meta.tier}` : "(unknown)";
|
|
29842
30198
|
let status;
|
|
29843
30199
|
if (r.installed) {
|
|
29844
|
-
status = `${
|
|
30200
|
+
status = `${chalk23.green("\u2713")} ${r.path}`;
|
|
29845
30201
|
} else if (r.extensionMissing) {
|
|
29846
|
-
status = `${
|
|
30202
|
+
status = `${chalk23.yellow("\u2717")} extension missing: ${r.extensionMissing}`;
|
|
29847
30203
|
} else {
|
|
29848
|
-
status = `${
|
|
30204
|
+
status = `${chalk23.yellow("\u2717")} not on PATH`;
|
|
29849
30205
|
}
|
|
29850
|
-
lines.push(` ${
|
|
30206
|
+
lines.push(` ${chalk23.cyan(r.id)} (${tierLabel2}) \u2014 ${status}`);
|
|
29851
30207
|
}
|
|
29852
30208
|
const installed = results.filter((r) => r.installed).length;
|
|
29853
30209
|
lines.push("");
|
|
@@ -29919,19 +30275,19 @@ async function cliToolsDetectCommand(opts = {}) {
|
|
|
29919
30275
|
let installed = 0;
|
|
29920
30276
|
for (const r of results) {
|
|
29921
30277
|
if (r.installed) {
|
|
29922
|
-
lines.push(` ${
|
|
30278
|
+
lines.push(` ${chalk23.green("\u2713")} ${r.id} \u2014 ${r.path}`);
|
|
29923
30279
|
installed++;
|
|
29924
30280
|
} else if (r.extensionMissing) {
|
|
29925
|
-
lines.push(` ${
|
|
30281
|
+
lines.push(` ${chalk23.yellow("\u2717")} ${r.id} \u2014 extension missing: ${r.extensionMissing}`);
|
|
29926
30282
|
} else {
|
|
29927
|
-
lines.push(` ${
|
|
30283
|
+
lines.push(` ${chalk23.yellow("\u2717")} ${r.id} \u2014 not on PATH`);
|
|
29928
30284
|
}
|
|
29929
30285
|
}
|
|
29930
30286
|
lines.push("");
|
|
29931
30287
|
lines.push(label("Installed", `${installed}/${results.length}`));
|
|
29932
30288
|
if (installed < results.length) {
|
|
29933
30289
|
lines.push("");
|
|
29934
|
-
lines.push(`Run ${
|
|
30290
|
+
lines.push(`Run ${chalk23.bold("npx hatch3r cli-tools install")} to see install commands.`);
|
|
29935
30291
|
}
|
|
29936
30292
|
finishCommand(format, {
|
|
29937
30293
|
command: "cli-tools detect",
|
|
@@ -30019,6 +30375,11 @@ function createProgram() {
|
|
|
30019
30375
|
"Output format for CI consumers: human (default) or json",
|
|
30020
30376
|
"human"
|
|
30021
30377
|
).option("--dry-run", "Preview what init would create or change without writing files").option("--verbose", "Show detailed per-step output").option("--no-banner", "Skip the ASCII banner at startup").option("--resume", "Resume from the last checkpoint in .init-workspace/checkpoint.json").option("--maturity <tier>", "Project maturity tier: solo, team, scaleup, enterprise (default: solo) \u2014 calibrates investment depth; does not change which content is installed").option("--role <role>", "Role bundle: reviewer, security-lead, senior-eng \u2014 filters content to items tagged for the named role").option("--facets <list>", "Comma-separated graduated-customization facets to add on top of the preset: a11y, performance, observability").option("--per-package", "On a monorepo, also copy adapter output under each package (default: root-only). Capped at 25 packages, batched, and .gitignore'd").action(initCommand);
|
|
30378
|
+
program2.command("setup [dir]").description("Scaffold a fresh project (mkdir + git init, optional GitHub remote) then chain into `hatch3r init`. Needs only Node + git; --remote is opt-in").option("--remote", "Create a private GitHub remote via `gh repo create` after git init (skipped with a warning when gh is missing or unauthenticated)").option("--tools <tools>", `Comma-separated tools (${TOOL_CHOICES})`).option("--preset <preset>", "Content preset forwarded to init (e.g. minimal, standard, full, web-app)").option("--maturity <tier>", "Project maturity tier forwarded to init: solo, team, scaleup, enterprise (default: solo)").option("--yes", "Skip interactive prompts (scaffold + init use smart defaults)").option(
|
|
30379
|
+
"--format <format>",
|
|
30380
|
+
"Output format for CI consumers: human (default) or json",
|
|
30381
|
+
"human"
|
|
30382
|
+
).option("--quiet", "Suppress stdout chrome (banner, spinner, boxes); stderr diagnostics still emit").option("--dry-run", "Preview the scaffold plan (create dir, git init, remote, then run init) without writing anything").option("--verbose", "Show detailed per-step output").action(setupCommand);
|
|
30022
30383
|
program2.command("sync").description("Re-generate tool outputs from bundled canonical content (run after updating hatch3r or editing .hatch3r/ overrides)").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("--clean-orphans", "Remove generated adapter output files that no longer match canonical-inventory naming (no hatch3r- prefix). Default is informational only.").option("--verbose", "Show detailed output for each file processed").option("--resume", "Resume from the last checkpoint in .sync-workspace/checkpoint.json").option(
|
|
30023
30384
|
"--concurrency <n>",
|
|
30024
30385
|
"Parallel workspace sub-repo sync limit (default: min(CPU count, 8); raise on SSD-bound runners)"
|
|
@@ -30249,7 +30610,7 @@ function classifyCliError(err, flags) {
|
|
|
30249
30610
|
|
|
30250
30611
|
// src/cli/shared/errors.ts
|
|
30251
30612
|
init_types();
|
|
30252
|
-
import
|
|
30613
|
+
import chalk24 from "chalk";
|
|
30253
30614
|
import boxen2 from "boxen";
|
|
30254
30615
|
var DEFAULT_RECOVERY_HINT = {
|
|
30255
30616
|
VALIDATION_ERROR: "Re-run with --verbose to see the failing checks, then fix the listed errors and re-run.",
|
|
@@ -30272,11 +30633,11 @@ function formatActionableError(err, flags = {}) {
|
|
|
30272
30633
|
const runId2 = getRunId();
|
|
30273
30634
|
const hint = resolveRecoveryHint(err);
|
|
30274
30635
|
if (hint) {
|
|
30275
|
-
const title =
|
|
30636
|
+
const title = chalk24.red.bold("hatch3r error");
|
|
30276
30637
|
const body = `${err.message}
|
|
30277
30638
|
|
|
30278
|
-
${
|
|
30279
|
-
${
|
|
30639
|
+
${chalk24.dim("Try:")} ${hint}
|
|
30640
|
+
${chalk24.dim("Run id:")} ${runId2}`;
|
|
30280
30641
|
const box = boxen2(body, {
|
|
30281
30642
|
title,
|
|
30282
30643
|
titleAlignment: "left",
|
|
@@ -30341,15 +30702,15 @@ function writeFormattedCliError(formatted) {
|
|
|
30341
30702
|
// src/cli/shared/updateNotifier.ts
|
|
30342
30703
|
init_version();
|
|
30343
30704
|
import updateNotifier from "update-notifier";
|
|
30344
|
-
import
|
|
30705
|
+
import chalk25 from "chalk";
|
|
30345
30706
|
var ONE_DAY_MS = 1e3 * 60 * 60 * 24;
|
|
30346
30707
|
function buildMessage(update) {
|
|
30347
|
-
const fromTo = `${
|
|
30348
|
-
const cyan =
|
|
30708
|
+
const fromTo = `${chalk25.dim(update.current)} \u2192 ${chalk25.green(update.latest)}`;
|
|
30709
|
+
const cyan = chalk25.hex("#06b6d4");
|
|
30349
30710
|
return [
|
|
30350
30711
|
`Update available ${fromTo}`,
|
|
30351
|
-
|
|
30352
|
-
|
|
30712
|
+
chalk25.dim("Run ") + cyan("npm i -g hatch3r@latest") + chalk25.dim(" to update the CLI"),
|
|
30713
|
+
chalk25.dim("Then run ") + cyan("hatch3r update") + chalk25.dim(" to refresh project content")
|
|
30353
30714
|
].join("\n");
|
|
30354
30715
|
}
|
|
30355
30716
|
function checkForUpdates() {
|
|
@@ -30375,7 +30736,7 @@ function checkForUpdates() {
|
|
|
30375
30736
|
}
|
|
30376
30737
|
|
|
30377
30738
|
// src/cli/shared/backablePrompts.ts
|
|
30378
|
-
import
|
|
30739
|
+
import inquirer14 from "inquirer";
|
|
30379
30740
|
import {
|
|
30380
30741
|
createPrompt,
|
|
30381
30742
|
useState,
|
|
@@ -30873,10 +31234,10 @@ function registerBackablePrompts() {
|
|
|
30873
31234
|
if (registered) return;
|
|
30874
31235
|
if (!process.stdin.isTTY) return;
|
|
30875
31236
|
registered = true;
|
|
30876
|
-
|
|
30877
|
-
|
|
30878
|
-
|
|
30879
|
-
|
|
31237
|
+
inquirer14.registerPrompt("select", backableSelect);
|
|
31238
|
+
inquirer14.registerPrompt("checkbox", backableCheckbox);
|
|
31239
|
+
inquirer14.registerPrompt("input", backableInput);
|
|
31240
|
+
inquirer14.registerPrompt("confirm", backableConfirm);
|
|
30880
31241
|
}
|
|
30881
31242
|
|
|
30882
31243
|
// src/cli/shared/invokedCommand.ts
|