hatch3r 1.7.1 → 1.7.5
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 +37 -11
- package/agents/hatch3r-a11y-auditor.md +4 -0
- package/agents/hatch3r-architect.md +4 -0
- package/agents/hatch3r-ci-watcher.md +4 -0
- package/agents/hatch3r-context-rules.md +4 -0
- package/agents/hatch3r-creator.md +4 -0
- package/agents/hatch3r-dependency-auditor.md +4 -0
- package/agents/hatch3r-devops.md +4 -0
- package/agents/hatch3r-docs-writer.md +4 -0
- package/agents/hatch3r-fixer.md +4 -0
- package/agents/hatch3r-handoff-loader.md +243 -0
- package/agents/hatch3r-handoff-preparer.md +134 -0
- package/agents/hatch3r-implementer.md +4 -0
- package/agents/hatch3r-learnings-loader.md +4 -0
- package/agents/hatch3r-lint-fixer.md +4 -0
- package/agents/hatch3r-perf-profiler.md +8 -0
- package/agents/hatch3r-researcher.md +4 -0
- package/agents/hatch3r-reviewer.md +92 -0
- package/agents/hatch3r-security-auditor.md +24 -0
- package/agents/hatch3r-test-writer.md +4 -0
- package/agents/modes/requirements-elicitation.md +4 -1
- package/agents/modes/similar-implementation.md +6 -0
- package/agents/modes/user-flows.md +76 -0
- package/agents/shared/quality-charter.md +128 -0
- package/commands/hatch3r-board-fill.md +1 -0
- package/commands/hatch3r-create.md +2 -0
- package/commands/hatch3r-handoff.md +126 -0
- package/commands/hatch3r-pr-resolve.md +4 -0
- package/commands/hatch3r-quick-change.md +4 -2
- package/commands/hatch3r-workflow.md +2 -0
- package/dist/cli/index.js +2321 -460
- package/dist/cli/index.js.map +1 -1
- package/package.json +4 -2
- package/rules/hatch3r-accessibility-standards.md +21 -0
- package/rules/hatch3r-accessibility-standards.mdc +21 -0
- package/rules/hatch3r-agent-orchestration.md +9 -1
- package/rules/hatch3r-agent-orchestration.mdc +9 -1
- package/rules/hatch3r-ai-evals.md +158 -0
- package/rules/hatch3r-ai-evals.mdc +154 -0
- package/rules/hatch3r-ai-ux-patterns.md +131 -0
- package/rules/hatch3r-ai-ux-patterns.mdc +127 -0
- package/rules/hatch3r-api-design.md +67 -9
- package/rules/hatch3r-api-design.mdc +67 -9
- package/rules/hatch3r-api-versioning.md +119 -0
- package/rules/hatch3r-api-versioning.mdc +115 -0
- package/rules/hatch3r-auth-patterns.md +170 -0
- package/rules/hatch3r-auth-patterns.mdc +166 -0
- package/rules/hatch3r-component-conventions.md +30 -0
- package/rules/hatch3r-component-conventions.mdc +30 -0
- package/rules/hatch3r-container-hardening.md +131 -0
- package/rules/hatch3r-container-hardening.mdc +127 -0
- package/rules/hatch3r-contract-testing.md +117 -0
- package/rules/hatch3r-contract-testing.mdc +113 -0
- package/rules/hatch3r-deep-context.md +2 -0
- package/rules/hatch3r-deep-context.mdc +2 -0
- package/rules/hatch3r-dependency-management.md +73 -1
- package/rules/hatch3r-dependency-management.mdc +72 -0
- package/rules/hatch3r-design-system-detection.md +142 -0
- package/rules/hatch3r-design-system-detection.mdc +138 -0
- package/rules/hatch3r-event-schema-evolution.md +90 -0
- package/rules/hatch3r-event-schema-evolution.mdc +86 -0
- package/rules/hatch3r-handoff-readiness.md +45 -0
- package/rules/hatch3r-handoff-readiness.mdc +40 -0
- package/rules/hatch3r-i18n.md +13 -0
- package/rules/hatch3r-i18n.mdc +13 -0
- package/rules/hatch3r-migrations.md +61 -16
- package/rules/hatch3r-migrations.mdc +61 -16
- package/rules/hatch3r-observability-logging.md +1 -1
- package/rules/hatch3r-observability-logging.mdc +1 -1
- package/rules/hatch3r-observability-metrics.md +1 -1
- package/rules/hatch3r-observability-metrics.mdc +1 -1
- package/rules/hatch3r-observability-tracing-detail.md +1 -1
- package/rules/hatch3r-observability-tracing-detail.mdc +1 -1
- package/rules/hatch3r-observability-tracing.md +1 -1
- package/rules/hatch3r-observability-tracing.mdc +1 -1
- package/rules/hatch3r-observability.md +1 -0
- package/rules/hatch3r-observability.mdc +1 -0
- package/rules/hatch3r-operability.md +149 -0
- package/rules/hatch3r-operability.mdc +145 -0
- package/rules/hatch3r-passkey-server.md +181 -0
- package/rules/hatch3r-passkey-server.mdc +177 -0
- package/rules/hatch3r-progressive-delivery.md +120 -0
- package/rules/hatch3r-progressive-delivery.mdc +116 -0
- package/rules/hatch3r-resilience-patterns.md +154 -0
- package/rules/hatch3r-resilience-patterns.mdc +150 -0
- package/rules/hatch3r-secrets-management.md +29 -0
- package/rules/hatch3r-secrets-management.mdc +29 -0
- package/rules/hatch3r-testing.md +139 -43
- package/rules/hatch3r-testing.mdc +139 -43
- package/rules/hatch3r-ux-states-and-flows.md +149 -0
- package/rules/hatch3r-ux-states-and-flows.mdc +145 -0
- package/skills/hatch3r-a11y-audit/SKILL.md +14 -0
- package/skills/hatch3r-ai-feature/SKILL.md +134 -0
- package/skills/hatch3r-api-spec/SKILL.md +5 -0
- package/skills/hatch3r-architecture-review/SKILL.md +14 -0
- package/skills/hatch3r-bug-fix/SKILL.md +5 -0
- package/skills/hatch3r-ci-pipeline/SKILL.md +14 -0
- package/skills/hatch3r-cli-aichat/SKILL.md +84 -0
- package/skills/hatch3r-cli-ast-grep/SKILL.md +85 -0
- package/skills/hatch3r-cli-az-devops/SKILL.md +89 -0
- package/skills/hatch3r-cli-bat/SKILL.md +85 -0
- package/skills/hatch3r-cli-comby/SKILL.md +85 -0
- package/skills/hatch3r-cli-csvkit/SKILL.md +84 -0
- package/skills/hatch3r-cli-delta/SKILL.md +86 -0
- package/skills/hatch3r-cli-difftastic/SKILL.md +84 -0
- package/skills/hatch3r-cli-docker/SKILL.md +89 -0
- package/skills/hatch3r-cli-duckdb/SKILL.md +84 -0
- package/skills/hatch3r-cli-fd/SKILL.md +85 -0
- package/skills/hatch3r-cli-fzf/SKILL.md +84 -0
- package/skills/hatch3r-cli-gh/SKILL.md +90 -0
- package/skills/hatch3r-cli-glab/SKILL.md +89 -0
- package/skills/hatch3r-cli-jq/SKILL.md +85 -0
- package/skills/hatch3r-cli-lazygit/SKILL.md +78 -0
- package/skills/hatch3r-cli-llm/SKILL.md +84 -0
- package/skills/hatch3r-cli-miller/SKILL.md +84 -0
- package/skills/hatch3r-cli-mods/SKILL.md +84 -0
- package/skills/hatch3r-cli-overview/SKILL.md +60 -0
- package/skills/hatch3r-cli-playwright/SKILL.md +89 -0
- package/skills/hatch3r-cli-podman/SKILL.md +84 -0
- package/skills/hatch3r-cli-ripgrep/SKILL.md +85 -0
- package/skills/hatch3r-cli-rtk/SKILL.md +91 -0
- package/skills/hatch3r-cli-sd/SKILL.md +85 -0
- package/skills/hatch3r-cli-stagehand/SKILL.md +79 -0
- package/skills/hatch3r-cli-taplo/SKILL.md +84 -0
- package/skills/hatch3r-cli-xsv/SKILL.md +89 -0
- package/skills/hatch3r-cli-yq/SKILL.md +85 -0
- package/skills/hatch3r-cli-zstd/SKILL.md +85 -0
- package/skills/hatch3r-context-health/SKILL.md +14 -0
- package/skills/hatch3r-cost-tracking/SKILL.md +14 -0
- package/skills/hatch3r-customize/SKILL.md +14 -0
- package/skills/hatch3r-dep-audit/SKILL.md +14 -0
- package/skills/hatch3r-design-system-detect/SKILL.md +162 -0
- package/skills/hatch3r-feature/SKILL.md +2 -0
- package/skills/hatch3r-gh-agentic-workflows/SKILL.md +13 -0
- package/skills/hatch3r-handoff-prepare/SKILL.md +160 -0
- package/skills/hatch3r-handoff-resume/SKILL.md +171 -0
- package/skills/hatch3r-incident-response/SKILL.md +14 -0
- package/skills/hatch3r-issue-workflow/SKILL.md +5 -0
- package/skills/hatch3r-logical-refactor/SKILL.md +14 -0
- package/skills/hatch3r-migration/SKILL.md +14 -0
- package/skills/hatch3r-observability-verify/SKILL.md +133 -0
- package/skills/hatch3r-perf-audit/SKILL.md +14 -0
- package/skills/hatch3r-pr-creation/SKILL.md +14 -0
- package/skills/hatch3r-qa-validation/SKILL.md +18 -0
- package/skills/hatch3r-recipe/SKILL.md +14 -0
- package/skills/hatch3r-refactor/SKILL.md +14 -0
- package/skills/hatch3r-release/SKILL.md +14 -0
- package/skills/hatch3r-reliability-verify/SKILL.md +144 -0
- package/skills/hatch3r-ui-ux-verify/SKILL.md +136 -0
- package/skills/hatch3r-visual-refactor/SKILL.md +15 -1
package/dist/cli/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var HATCH3R_VERSION;
|
|
|
14
14
|
var init_version = __esm({
|
|
15
15
|
"src/version.ts"() {
|
|
16
16
|
"use strict";
|
|
17
|
-
HATCH3R_VERSION = "1.7.
|
|
17
|
+
HATCH3R_VERSION = "1.7.5";
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
20
|
|
|
@@ -79,7 +79,8 @@ function printBox(title, lines, style = "info") {
|
|
|
79
79
|
const colors = {
|
|
80
80
|
success: "#10b981",
|
|
81
81
|
info: "#06b6d4",
|
|
82
|
-
error: "#ef4444"
|
|
82
|
+
error: "#ef4444",
|
|
83
|
+
warning: "#f59e0b"
|
|
83
84
|
};
|
|
84
85
|
const content = lines.join("\n");
|
|
85
86
|
console.log(
|
|
@@ -138,6 +139,15 @@ var init_ui = __esm({
|
|
|
138
139
|
});
|
|
139
140
|
|
|
140
141
|
// src/types.ts
|
|
142
|
+
function getMarkersForPath(filePath) {
|
|
143
|
+
if (filePath) {
|
|
144
|
+
const lower = filePath.toLowerCase();
|
|
145
|
+
if (lower.endsWith(".yml") || lower.endsWith(".yaml")) {
|
|
146
|
+
return { start: MANAGED_BLOCK_START_YAML, end: MANAGED_BLOCK_END_YAML };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return { start: MANAGED_BLOCK_START, end: MANAGED_BLOCK_END };
|
|
150
|
+
}
|
|
141
151
|
function sanitizeId(id) {
|
|
142
152
|
return id.replace(/[^a-zA-Z0-9._-]/g, "");
|
|
143
153
|
}
|
|
@@ -145,7 +155,7 @@ function toPrefixedId(id, prefix = HATCH3R_PREFIX) {
|
|
|
145
155
|
const base = id.replace(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`), "");
|
|
146
156
|
return `${prefix}${base}`;
|
|
147
157
|
}
|
|
148
|
-
var TOOLS, VALID_TOOLS, TOOL_CHOICES, WORKTREE_CAPABLE_TOOLS, MANAGED_BLOCK_START, MANAGED_BLOCK_END, HATCH3R_PREFIX, AGENTS_DIR, ARCHIVE_DIR, CUSTOMIZE_DIR, ERROR_CODE_TO_EXIT_CODE, HatchError, MANIFEST_FILE, WORKTREE_INCLUDE_FILE, DEFAULT_FEATURES, ENV_VAR_HELP, AVAILABLE_MCP_SERVERS;
|
|
158
|
+
var TOOLS, VALID_TOOLS, TOOL_CHOICES, WORKTREE_CAPABLE_TOOLS, MANAGED_BLOCK_START, MANAGED_BLOCK_END, MANAGED_BLOCK_START_YAML, MANAGED_BLOCK_END_YAML, MANAGED_BLOCK_VARIANTS, HATCH3R_PREFIX, AGENTS_DIR, ARCHIVE_DIR, CUSTOMIZE_DIR, ERROR_CODE_TO_EXIT_CODE, HatchError, MANIFEST_FILE, WORKTREE_INCLUDE_FILE, DEFAULT_FEATURES, ENV_VAR_HELP, AVAILABLE_MCP_SERVERS;
|
|
149
159
|
var init_types = __esm({
|
|
150
160
|
"src/types.ts"() {
|
|
151
161
|
"use strict";
|
|
@@ -155,6 +165,12 @@ var init_types = __esm({
|
|
|
155
165
|
WORKTREE_CAPABLE_TOOLS = /* @__PURE__ */ new Set(["claude"]);
|
|
156
166
|
MANAGED_BLOCK_START = "<!-- HATCH3R:BEGIN -->";
|
|
157
167
|
MANAGED_BLOCK_END = "<!-- HATCH3R:END -->";
|
|
168
|
+
MANAGED_BLOCK_START_YAML = "# HATCH3R:BEGIN";
|
|
169
|
+
MANAGED_BLOCK_END_YAML = "# HATCH3R:END";
|
|
170
|
+
MANAGED_BLOCK_VARIANTS = [
|
|
171
|
+
{ start: MANAGED_BLOCK_START, end: MANAGED_BLOCK_END },
|
|
172
|
+
{ start: MANAGED_BLOCK_START_YAML, end: MANAGED_BLOCK_END_YAML }
|
|
173
|
+
];
|
|
158
174
|
HATCH3R_PREFIX = "hatch3r-";
|
|
159
175
|
AGENTS_DIR = ".agents";
|
|
160
176
|
ARCHIVE_DIR = ".hatch3r-archive";
|
|
@@ -190,7 +206,8 @@ var init_types = __esm({
|
|
|
190
206
|
commands: true,
|
|
191
207
|
mcp: true,
|
|
192
208
|
githubAgents: true,
|
|
193
|
-
hooks: true
|
|
209
|
+
hooks: true,
|
|
210
|
+
handoffs: true
|
|
194
211
|
};
|
|
195
212
|
ENV_VAR_HELP = {
|
|
196
213
|
GITHUB_PAT: {
|
|
@@ -269,21 +286,29 @@ var init_types = __esm({
|
|
|
269
286
|
});
|
|
270
287
|
|
|
271
288
|
// src/merge/managedBlocks.ts
|
|
272
|
-
function
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
289
|
+
function detectMarkers(content) {
|
|
290
|
+
for (const variant of MANAGED_BLOCK_VARIANTS) {
|
|
291
|
+
const startIdx = content.indexOf(variant.start);
|
|
292
|
+
const endIdx = content.indexOf(variant.end);
|
|
293
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
294
|
+
return { variant, startIdx, endIdx };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
function insertManagedBlock(existingContent, managedContent, filePath) {
|
|
300
|
+
const outputMarkers = getMarkersForPath(filePath);
|
|
301
|
+
const detected = detectMarkers(existingContent);
|
|
302
|
+
if (!detected) {
|
|
279
303
|
throw new HatchError(
|
|
280
304
|
"Content must contain managed block markers (HATCH3R:BEGIN and HATCH3R:END)",
|
|
281
305
|
1,
|
|
282
306
|
"VALIDATION_ERROR"
|
|
283
307
|
);
|
|
284
308
|
}
|
|
285
|
-
const
|
|
286
|
-
const
|
|
309
|
+
const { variant, startIdx, endIdx } = detected;
|
|
310
|
+
const secondStart = existingContent.indexOf(variant.start, startIdx + 1);
|
|
311
|
+
const secondEnd = existingContent.indexOf(variant.end, endIdx + 1);
|
|
287
312
|
if (secondStart !== -1) {
|
|
288
313
|
throw new HatchError(
|
|
289
314
|
"Corrupted managed block: duplicate start marker found. Remove the duplicate before syncing.",
|
|
@@ -305,37 +330,35 @@ ${MANAGED_BLOCK_END}`;
|
|
|
305
330
|
"VALIDATION_ERROR"
|
|
306
331
|
);
|
|
307
332
|
}
|
|
333
|
+
const block = `${outputMarkers.start}
|
|
334
|
+
${managedContent.trim()}
|
|
335
|
+
${outputMarkers.end}`;
|
|
308
336
|
const before = existingContent.substring(0, startIdx);
|
|
309
|
-
const after = existingContent.substring(endIdx +
|
|
337
|
+
const after = existingContent.substring(endIdx + variant.end.length);
|
|
310
338
|
const result = `${before}${block}${after}`;
|
|
311
339
|
return result.endsWith("\n") ? result : result + "\n";
|
|
312
340
|
}
|
|
313
341
|
function extractManagedBlock(content) {
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
return null;
|
|
318
|
-
}
|
|
319
|
-
return content.substring(startIdx + MANAGED_BLOCK_START.length, endIdx).trim();
|
|
342
|
+
const detected = detectMarkers(content);
|
|
343
|
+
if (!detected) return null;
|
|
344
|
+
return content.substring(detected.startIdx + detected.variant.start.length, detected.endIdx).trim();
|
|
320
345
|
}
|
|
321
346
|
function extractCustomContent(content) {
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
const before = content.substring(0, startIdx).trim();
|
|
328
|
-
const after = content.substring(endIdx + MANAGED_BLOCK_END.length).trim();
|
|
347
|
+
const detected = detectMarkers(content);
|
|
348
|
+
if (!detected) return content;
|
|
349
|
+
const before = content.substring(0, detected.startIdx).trim();
|
|
350
|
+
const after = content.substring(detected.endIdx + detected.variant.end.length).trim();
|
|
329
351
|
return [before, after].filter(Boolean).join("\n\n");
|
|
330
352
|
}
|
|
331
|
-
function wrapInManagedBlock(content) {
|
|
332
|
-
|
|
353
|
+
function wrapInManagedBlock(content, filePath) {
|
|
354
|
+
const markers = getMarkersForPath(filePath);
|
|
355
|
+
return `${markers.start}
|
|
333
356
|
${content.trim()}
|
|
334
|
-
${
|
|
357
|
+
${markers.end}
|
|
335
358
|
`;
|
|
336
359
|
}
|
|
337
360
|
function hasManagedBlock(content) {
|
|
338
|
-
return content
|
|
361
|
+
return detectMarkers(content) !== null;
|
|
339
362
|
}
|
|
340
363
|
var init_managedBlocks = __esm({
|
|
341
364
|
"src/merge/managedBlocks.ts"() {
|
|
@@ -1327,7 +1350,7 @@ async function safeWriteFile(filePath, content, options = {}) {
|
|
|
1327
1350
|
const deniedFindings = customContent ? scanForDeniedPatterns(customContent) : [];
|
|
1328
1351
|
let merged;
|
|
1329
1352
|
try {
|
|
1330
|
-
merged = insertManagedBlock(existingContent, options.managedContent);
|
|
1353
|
+
merged = insertManagedBlock(existingContent, options.managedContent, filePath);
|
|
1331
1354
|
} catch {
|
|
1332
1355
|
const bakPath = filePath + ".bak";
|
|
1333
1356
|
await copyFile(filePath, bakPath);
|
|
@@ -1619,8 +1642,8 @@ async function resolvePatterns(rootDir, patterns) {
|
|
|
1619
1642
|
}
|
|
1620
1643
|
function isInsideWorktree(dir) {
|
|
1621
1644
|
try {
|
|
1622
|
-
const
|
|
1623
|
-
return
|
|
1645
|
+
const stat13 = statSync(join5(dir, ".git"));
|
|
1646
|
+
return stat13.isFile();
|
|
1624
1647
|
} catch {
|
|
1625
1648
|
return false;
|
|
1626
1649
|
}
|
|
@@ -1922,10 +1945,10 @@ async function cleanupWorktree(worktreeRoot) {
|
|
|
1922
1945
|
for (const entry of entries) {
|
|
1923
1946
|
const targetPath = join6(worktreeRoot, entry.pattern.replace(/\/$/, ""));
|
|
1924
1947
|
try {
|
|
1925
|
-
const
|
|
1926
|
-
if (
|
|
1948
|
+
const stat13 = await lstat(targetPath);
|
|
1949
|
+
if (stat13.isSymbolicLink()) {
|
|
1927
1950
|
await unlink2(targetPath);
|
|
1928
|
-
} else if (entry.strategy === "copy" &&
|
|
1951
|
+
} else if (entry.strategy === "copy" && stat13.isFile() && mainRoot) {
|
|
1929
1952
|
const sourcePath = join6(mainRoot, entry.pattern.replace(/\/$/, ""));
|
|
1930
1953
|
try {
|
|
1931
1954
|
const sourceContent = await readFile4(sourcePath, "utf-8");
|
|
@@ -2327,6 +2350,7 @@ __export(hatchJson_exports, {
|
|
|
2327
2350
|
extractPreservedManifestFields: () => extractPreservedManifestFields,
|
|
2328
2351
|
isValidGitBranchName: () => isValidGitBranchName,
|
|
2329
2352
|
migrateManifest: () => migrateManifest,
|
|
2353
|
+
readCliToolsConfig: () => readCliToolsConfig,
|
|
2330
2354
|
readManifest: () => readManifest,
|
|
2331
2355
|
removeManagedFile: () => removeManagedFile,
|
|
2332
2356
|
writeManifest: () => writeManifest
|
|
@@ -2394,6 +2418,9 @@ function createManifest(options) {
|
|
|
2394
2418
|
if (options.customization) {
|
|
2395
2419
|
manifest.customization = options.customization;
|
|
2396
2420
|
}
|
|
2421
|
+
if (options.cliTools) {
|
|
2422
|
+
manifest.cliTools = options.cliTools;
|
|
2423
|
+
}
|
|
2397
2424
|
if (options.languages && options.languages.length > 0 && options.languages[0] !== "unknown") {
|
|
2398
2425
|
manifest.languages = options.languages;
|
|
2399
2426
|
}
|
|
@@ -2607,6 +2634,7 @@ function extractPreservedManifestFields(manifest) {
|
|
|
2607
2634
|
if (manifest.repos) out.repos = manifest.repos;
|
|
2608
2635
|
if (manifest.packages) out.packages = manifest.packages;
|
|
2609
2636
|
if (manifest.workspace) out.workspace = manifest.workspace;
|
|
2637
|
+
if (manifest.cliTools) out.cliTools = manifest.cliTools;
|
|
2610
2638
|
if (manifest.worktree?.extraPatterns !== void 0 || manifest.worktree?.nodeModules !== void 0) {
|
|
2611
2639
|
out.worktreeExtras = {};
|
|
2612
2640
|
if (manifest.worktree.extraPatterns !== void 0) {
|
|
@@ -2644,6 +2672,9 @@ function applyPreservedManifestFields(manifest, preserved) {
|
|
|
2644
2672
|
if (preserved.repos) manifest.repos = preserved.repos;
|
|
2645
2673
|
if (preserved.packages) manifest.packages = preserved.packages;
|
|
2646
2674
|
if (preserved.workspace) manifest.workspace = preserved.workspace;
|
|
2675
|
+
if (preserved.cliTools && manifest.cliTools === void 0) {
|
|
2676
|
+
manifest.cliTools = preserved.cliTools;
|
|
2677
|
+
}
|
|
2647
2678
|
if (preserved.worktreeExtras && manifest.worktree?.enabled) {
|
|
2648
2679
|
if (preserved.worktreeExtras.extraPatterns !== void 0) {
|
|
2649
2680
|
manifest.worktree.extraPatterns = preserved.worktreeExtras.extraPatterns;
|
|
@@ -2653,6 +2684,9 @@ function applyPreservedManifestFields(manifest, preserved) {
|
|
|
2653
2684
|
}
|
|
2654
2685
|
}
|
|
2655
2686
|
}
|
|
2687
|
+
function readCliToolsConfig(m) {
|
|
2688
|
+
return m.cliTools ?? { enabled: false, selected: [] };
|
|
2689
|
+
}
|
|
2656
2690
|
var init_hatchJson = __esm({
|
|
2657
2691
|
"src/manifest/hatchJson.ts"() {
|
|
2658
2692
|
"use strict";
|
|
@@ -3101,6 +3135,16 @@ var init_agentToolAllowlist = __esm({
|
|
|
3101
3135
|
allowedTools: ["read", "search", "write", "execute"],
|
|
3102
3136
|
description: "Code implementation: file read/write, code search, command execution (tests, linters). No git, board, or web."
|
|
3103
3137
|
},
|
|
3138
|
+
{
|
|
3139
|
+
agentId: "hatch3r-handoff-preparer",
|
|
3140
|
+
allowedTools: ["read", "search", "write"],
|
|
3141
|
+
description: "Handoff preparation: read session state, search git/files for context, write canonical handoff to .agents/handoffs/active/. No execute (filesystem-only)."
|
|
3142
|
+
},
|
|
3143
|
+
{
|
|
3144
|
+
agentId: "hatch3r-handoff-loader",
|
|
3145
|
+
allowedTools: ["read", "search"],
|
|
3146
|
+
description: "Session-start loader: read .agents/handoffs/active/ and search git for branch context to surface active handoffs. No write, execute, or external IO."
|
|
3147
|
+
},
|
|
3104
3148
|
{
|
|
3105
3149
|
agentId: "hatch3r-reviewer",
|
|
3106
3150
|
allowedTools: ["read", "search"],
|
|
@@ -3332,7 +3376,7 @@ function filterByLanguages(items, projectLanguages) {
|
|
|
3332
3376
|
return itemLangTags.some((t) => relevant.has(t));
|
|
3333
3377
|
});
|
|
3334
3378
|
}
|
|
3335
|
-
var TAG_CORE, TAG_PLANNING, TAG_IMPLEMENTATION, TAG_REVIEW, TAG_DEVOPS, TAG_MAINTENANCE, TAG_BOARD, TAG_SECURITY, TAG_A11Y, TAG_PERFORMANCE, TAG_CUSTOMIZE, TAG_LANG_TYPESCRIPT, TAG_LANG_PYTHON, TAG_LANG_GO, TAG_LANG_RUST, TAG_LANG_JAVA, TAG_LANG_RUBY, WORKFLOW_TAGS, DOMAIN_TAGS, LANGUAGE_TO_TAG;
|
|
3379
|
+
var TAG_CORE, TAG_PLANNING, TAG_IMPLEMENTATION, TAG_REVIEW, TAG_DEVOPS, TAG_MAINTENANCE, TAG_BOARD, TAG_SECURITY, TAG_A11Y, TAG_PERFORMANCE, TAG_CUSTOMIZE, TAG_FRONTEND, TAG_UI, TAG_UX, TAG_DESIGN_SYSTEM, TAG_LANG_TYPESCRIPT, TAG_LANG_PYTHON, TAG_LANG_GO, TAG_LANG_RUST, TAG_LANG_JAVA, TAG_LANG_RUBY, WORKFLOW_TAGS, DOMAIN_TAGS, LANGUAGE_TO_TAG;
|
|
3336
3380
|
var init_tags = __esm({
|
|
3337
3381
|
"src/content/tags.ts"() {
|
|
3338
3382
|
"use strict";
|
|
@@ -3347,6 +3391,10 @@ var init_tags = __esm({
|
|
|
3347
3391
|
TAG_A11Y = "a11y";
|
|
3348
3392
|
TAG_PERFORMANCE = "performance";
|
|
3349
3393
|
TAG_CUSTOMIZE = "customize";
|
|
3394
|
+
TAG_FRONTEND = "frontend";
|
|
3395
|
+
TAG_UI = "ui";
|
|
3396
|
+
TAG_UX = "ux";
|
|
3397
|
+
TAG_DESIGN_SYSTEM = "design-system";
|
|
3350
3398
|
TAG_LANG_TYPESCRIPT = "lang:typescript";
|
|
3351
3399
|
TAG_LANG_PYTHON = "lang:python";
|
|
3352
3400
|
TAG_LANG_GO = "lang:go";
|
|
@@ -3366,7 +3414,11 @@ var init_tags = __esm({
|
|
|
3366
3414
|
TAG_SECURITY,
|
|
3367
3415
|
TAG_A11Y,
|
|
3368
3416
|
TAG_PERFORMANCE,
|
|
3369
|
-
TAG_CUSTOMIZE
|
|
3417
|
+
TAG_CUSTOMIZE,
|
|
3418
|
+
TAG_FRONTEND,
|
|
3419
|
+
TAG_UI,
|
|
3420
|
+
TAG_UX,
|
|
3421
|
+
TAG_DESIGN_SYSTEM
|
|
3370
3422
|
];
|
|
3371
3423
|
LANGUAGE_TO_TAG = {
|
|
3372
3424
|
typescript: TAG_LANG_TYPESCRIPT,
|
|
@@ -5249,6 +5301,77 @@ description: ${desc}
|
|
|
5249
5301
|
---`;
|
|
5250
5302
|
results.push(output(pathFn(skill.id), `${fm}
|
|
5251
5303
|
|
|
5304
|
+
${wrapInManagedBlock(content)}`, content));
|
|
5305
|
+
}
|
|
5306
|
+
return results;
|
|
5307
|
+
}
|
|
5308
|
+
/**
|
|
5309
|
+
* Read canonical skills with the CLI-tooling pivot filter applied.
|
|
5310
|
+
*
|
|
5311
|
+
* Filter rules (plan §4.6):
|
|
5312
|
+
* - Skills whose id does NOT start with `hatch3r-cli-` pass through
|
|
5313
|
+
* unchanged (every adapter still emits the non-CLI skill catalogue).
|
|
5314
|
+
* - When `manifest.cliTools` is absent or `enabled: false`, drop every
|
|
5315
|
+
* `hatch3r-cli-*` skill (master switch off).
|
|
5316
|
+
* - When `cliTools.enabled` is true, keep only those whose suffix
|
|
5317
|
+
* (after stripping `hatch3r-cli-`) appears in `cliTools.selected`.
|
|
5318
|
+
*
|
|
5319
|
+
* Wave 3 swaps each adapter's `processSkillsWithFm` /
|
|
5320
|
+
* `processSkillsRaw` call to the `*CliFiltered` variants below; the
|
|
5321
|
+
* filter helper is exposed protected so adapters with custom skill
|
|
5322
|
+
* pipelines can reuse it directly.
|
|
5323
|
+
*/
|
|
5324
|
+
async readCliFilteredSkills(ctx) {
|
|
5325
|
+
const all = await this.readTrackedCanonicalFiles(ctx.agentsDir, "skills");
|
|
5326
|
+
const cliCfg = ctx.manifest.cliTools ?? { enabled: false, selected: [] };
|
|
5327
|
+
const selected = new Set(cliCfg.selected ?? []);
|
|
5328
|
+
return all.filter((skill) => {
|
|
5329
|
+
if (!skill.id.startsWith("hatch3r-cli-")) return true;
|
|
5330
|
+
if (!cliCfg.enabled) return false;
|
|
5331
|
+
const cliId = skill.id.replace(/^hatch3r-cli-/, "");
|
|
5332
|
+
return selected.has(cliId);
|
|
5333
|
+
});
|
|
5334
|
+
}
|
|
5335
|
+
/**
|
|
5336
|
+
* CLI-filtered twin of {@link processSkillsRaw}. Adapters that emit
|
|
5337
|
+
* skills as raw managed-block files (no YAML frontmatter) call this
|
|
5338
|
+
* after Wave 3 instead of `processSkillsRaw` to honour
|
|
5339
|
+
* `manifest.cliTools.selected`.
|
|
5340
|
+
*/
|
|
5341
|
+
async processSkillsRawCliFiltered(ctx, pathFn) {
|
|
5342
|
+
if (!ctx.features.skills) return [];
|
|
5343
|
+
const results = [];
|
|
5344
|
+
const skills = await this.readCliFilteredSkills(ctx);
|
|
5345
|
+
for (const skill of skills) {
|
|
5346
|
+
const { content: raw, skip, warnings } = await applyCustomizationRaw(ctx.projectRoot, skill);
|
|
5347
|
+
this.warnings.push(...warnings);
|
|
5348
|
+
if (skip) continue;
|
|
5349
|
+
const content = this.substituteAskUserMarker(raw);
|
|
5350
|
+
results.push(output(pathFn(skill.id), wrapInManagedBlock(content), content));
|
|
5351
|
+
}
|
|
5352
|
+
return results;
|
|
5353
|
+
}
|
|
5354
|
+
/**
|
|
5355
|
+
* CLI-filtered twin of {@link processSkillsWithFm}. Adapters that emit
|
|
5356
|
+
* skills as managed-block files prefixed with a `name: + description:`
|
|
5357
|
+
* YAML stub call this after Wave 3 instead of `processSkillsWithFm`.
|
|
5358
|
+
*/
|
|
5359
|
+
async processSkillsWithFmCliFiltered(ctx, pathFn) {
|
|
5360
|
+
if (!ctx.features.skills) return [];
|
|
5361
|
+
const results = [];
|
|
5362
|
+
const skills = await this.readCliFilteredSkills(ctx);
|
|
5363
|
+
for (const skill of skills) {
|
|
5364
|
+
const { content: raw, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, skill);
|
|
5365
|
+
this.warnings.push(...warnings);
|
|
5366
|
+
if (skip) continue;
|
|
5367
|
+
const content = this.substituteAskUserMarker(raw);
|
|
5368
|
+
const desc = overrides.description ?? skill.description;
|
|
5369
|
+
const fm = `---
|
|
5370
|
+
name: ${skill.id}
|
|
5371
|
+
description: ${desc}
|
|
5372
|
+
---`;
|
|
5373
|
+
results.push(output(pathFn(skill.id), `${fm}
|
|
5374
|
+
|
|
5252
5375
|
${wrapInManagedBlock(content)}`, content));
|
|
5253
5376
|
}
|
|
5254
5377
|
return results;
|
|
@@ -5364,7 +5487,7 @@ var init_aider = __esm({
|
|
|
5364
5487
|
output("CONVENTIONS.md", wrapInManagedBlock(inner), inner)
|
|
5365
5488
|
];
|
|
5366
5489
|
results.push(
|
|
5367
|
-
...await this.
|
|
5490
|
+
...await this.processSkillsRawCliFiltered(ctx, (id) => `.aider/skills/${toPrefixedId(id)}/SKILL.md`)
|
|
5368
5491
|
);
|
|
5369
5492
|
results.push(output(".aider.conf.yml", [
|
|
5370
5493
|
"# Managed by hatch3r \u2014 do not edit manually",
|
|
@@ -5410,7 +5533,7 @@ var init_amazonq = __esm({
|
|
|
5410
5533
|
].join("\n").trim();
|
|
5411
5534
|
results.push(output(".amazonq/rules/hatch3r-agents.md", wrapInManagedBlock(inner), inner));
|
|
5412
5535
|
results.push(
|
|
5413
|
-
...await this.
|
|
5536
|
+
...await this.processSkillsRawCliFiltered(ctx, (id) => `.amazonq/rules/hatch3r-skill-${id}.md`)
|
|
5414
5537
|
);
|
|
5415
5538
|
const mcp = await this.readFilteredMcp(ctx);
|
|
5416
5539
|
if (mcp && Object.keys(mcp).length > 0) {
|
|
@@ -5538,7 +5661,7 @@ var init_antigravity = __esm({
|
|
|
5538
5661
|
].join("\n").trim();
|
|
5539
5662
|
results.push(output(".antigravity/rules.md", wrapInManagedBlock(inner), inner));
|
|
5540
5663
|
results.push(
|
|
5541
|
-
...await this.
|
|
5664
|
+
...await this.processSkillsRawCliFiltered(ctx, (id) => `.agent/skills/${toPrefixedId(id)}/SKILL.md`)
|
|
5542
5665
|
);
|
|
5543
5666
|
const mcp = await this.readFilteredMcp(ctx);
|
|
5544
5667
|
if (mcp && Object.keys(mcp).length > 0) {
|
|
@@ -5916,7 +6039,7 @@ ${wrapInManagedBlock(body)}`, body));
|
|
|
5916
6039
|
results.push(output(".claude/hooks/hatch3r-hooks.json", JSON.stringify(pluginHooksObj, null, 2)));
|
|
5917
6040
|
}
|
|
5918
6041
|
results.push(
|
|
5919
|
-
...await this.
|
|
6042
|
+
...await this.processSkillsRawCliFiltered(ctx, (id) => `.claude/skills/${toPrefixedId(id)}/SKILL.md`)
|
|
5920
6043
|
);
|
|
5921
6044
|
results.push(
|
|
5922
6045
|
...await this.processCommandsRaw(ctx, (id) => `.claude/commands/${toPrefixedId(id)}.md`)
|
|
@@ -5986,7 +6109,7 @@ Recommended model: ${model}. Select this model in the Roo Code model dropdown wh
|
|
|
5986
6109
|
}, null, 2)));
|
|
5987
6110
|
}
|
|
5988
6111
|
results.push(
|
|
5989
|
-
...await this.
|
|
6112
|
+
...await this.processSkillsRawCliFiltered(ctx, (id) => `.cline/skills/${toPrefixedId(id)}/SKILL.md`)
|
|
5990
6113
|
);
|
|
5991
6114
|
if (ctx.features.rules) {
|
|
5992
6115
|
const rules = await readCanonicalFiles(ctx.agentsDir, "rules", this.warnings);
|
|
@@ -6229,7 +6352,7 @@ var init_codex = __esm({
|
|
|
6229
6352
|
}
|
|
6230
6353
|
results.push(output(".codex/config.toml", configLines.join("\n")));
|
|
6231
6354
|
results.push(
|
|
6232
|
-
...await this.
|
|
6355
|
+
...await this.processSkillsRawCliFiltered(ctx, (id) => `.codex/skills/${toPrefixedId(id)}/SKILL.md`)
|
|
6233
6356
|
);
|
|
6234
6357
|
return results;
|
|
6235
6358
|
}
|
|
@@ -6374,9 +6497,10 @@ jobs:
|
|
|
6374
6497
|
run: ${install}
|
|
6375
6498
|
- name: Build
|
|
6376
6499
|
run: ${build}`;
|
|
6500
|
+
const copilotSetupStepsPath = ".github/workflows/copilot-setup-steps.yml";
|
|
6377
6501
|
results.push(output(
|
|
6378
|
-
|
|
6379
|
-
wrapInManagedBlock(copilotSetupStepsInner),
|
|
6502
|
+
copilotSetupStepsPath,
|
|
6503
|
+
wrapInManagedBlock(copilotSetupStepsInner, copilotSetupStepsPath),
|
|
6380
6504
|
copilotSetupStepsInner
|
|
6381
6505
|
));
|
|
6382
6506
|
for (const { rule, content, scope } of scopedRules) {
|
|
@@ -6443,7 +6567,7 @@ ${wrapInManagedBlock(content)}`, content));
|
|
|
6443
6567
|
}
|
|
6444
6568
|
}
|
|
6445
6569
|
results.push(
|
|
6446
|
-
...await this.
|
|
6570
|
+
...await this.processSkillsWithFmCliFiltered(ctx, (id) => `.github/skills/${toPrefixedId(id)}/SKILL.md`)
|
|
6447
6571
|
);
|
|
6448
6572
|
const mcp = await this.readFilteredMcp(ctx);
|
|
6449
6573
|
if (mcp && Object.keys(mcp).length > 0) {
|
|
@@ -6529,7 +6653,7 @@ ${lines.join("\n")}
|
|
|
6529
6653
|
}
|
|
6530
6654
|
}
|
|
6531
6655
|
results.push(
|
|
6532
|
-
...await this.
|
|
6656
|
+
...await this.processSkillsWithFmCliFiltered(ctx, (id) => `.cursor/skills/${toPrefixedId(id)}/SKILL.md`)
|
|
6533
6657
|
);
|
|
6534
6658
|
results.push(
|
|
6535
6659
|
...await this.processCommandsRaw(ctx, (id) => `.cursor/commands/${toPrefixedId(id)}.md`)
|
|
@@ -6682,7 +6806,7 @@ var init_gemini = __esm({
|
|
|
6682
6806
|
}
|
|
6683
6807
|
results.push(output(".gemini/settings.json", JSON.stringify(settings, null, 2)));
|
|
6684
6808
|
results.push(
|
|
6685
|
-
...await this.
|
|
6809
|
+
...await this.processSkillsRawCliFiltered(ctx, (id) => `.gemini/skills/${toPrefixedId(id)}/SKILL.md`)
|
|
6686
6810
|
);
|
|
6687
6811
|
if (ctx.features.commands) {
|
|
6688
6812
|
const commandsRaw = await readCanonicalFiles(ctx.agentsDir, "commands", this.warnings);
|
|
@@ -6714,7 +6838,6 @@ var init_goose = __esm({
|
|
|
6714
6838
|
init_types();
|
|
6715
6839
|
init_managedBlocks();
|
|
6716
6840
|
init_base();
|
|
6717
|
-
init_canonical();
|
|
6718
6841
|
init_customization();
|
|
6719
6842
|
init_mcp_utils();
|
|
6720
6843
|
GooseAdapter = class extends BaseAdapter {
|
|
@@ -6727,7 +6850,7 @@ var init_goose = __esm({
|
|
|
6727
6850
|
...await this.inlineAgents(ctx)
|
|
6728
6851
|
];
|
|
6729
6852
|
if (ctx.features.skills) {
|
|
6730
|
-
const skills = await
|
|
6853
|
+
const skills = await this.readCliFilteredSkills(ctx);
|
|
6731
6854
|
for (const skill of skills) {
|
|
6732
6855
|
const { content, skip, warnings } = await applyCustomizationRaw(ctx.projectRoot, skill);
|
|
6733
6856
|
this.warnings.push(...warnings);
|
|
@@ -6887,7 +7010,7 @@ ${content}`;
|
|
|
6887
7010
|
const inner = lines.join("\n").trim();
|
|
6888
7011
|
results.push(output(".kiro/steering/hatch3r-agents.md", wrapInManagedBlock(inner), inner));
|
|
6889
7012
|
results.push(
|
|
6890
|
-
...await this.
|
|
7013
|
+
...await this.processSkillsRawCliFiltered(ctx, (id) => `.kiro/steering/hatch3r-skill-${id}.md`)
|
|
6891
7014
|
);
|
|
6892
7015
|
const hooks = await this.readHooks(ctx);
|
|
6893
7016
|
for (const hook of hooks) {
|
|
@@ -7018,7 +7141,7 @@ ${wrapInManagedBlock(content)}`, content));
|
|
|
7018
7141
|
}
|
|
7019
7142
|
}
|
|
7020
7143
|
results.push(
|
|
7021
|
-
...await this.
|
|
7144
|
+
...await this.processSkillsRawCliFiltered(ctx, (id) => `.opencode/skills/${toPrefixedId(id)}/SKILL.md`)
|
|
7022
7145
|
);
|
|
7023
7146
|
results.push(
|
|
7024
7147
|
...await this.processCommandsRaw(ctx, (id) => `.opencode/commands/${toPrefixedId(id)}.md`)
|
|
@@ -7159,7 +7282,7 @@ ${wrapInManagedBlock(body)}`, body));
|
|
|
7159
7282
|
}
|
|
7160
7283
|
}
|
|
7161
7284
|
results.push(
|
|
7162
|
-
...await this.
|
|
7285
|
+
...await this.processSkillsWithFmCliFiltered(ctx, (id) => `.windsurf/skills/${toPrefixedId(id)}/SKILL.md`)
|
|
7163
7286
|
);
|
|
7164
7287
|
results.push(
|
|
7165
7288
|
...await this.processCommandsRaw(ctx, (id) => `.windsurf/workflows/${toPrefixedId(id)}.md`)
|
|
@@ -7284,6 +7407,9 @@ function getUnsupportedFeatureWarnings(tool, manifest) {
|
|
|
7284
7407
|
unsupported.push(label2);
|
|
7285
7408
|
}
|
|
7286
7409
|
}
|
|
7410
|
+
if (manifest.cliTools?.enabled && (manifest.cliTools.selected?.length ?? 0) > 0 && !caps.cliTools) {
|
|
7411
|
+
unsupported.push("CLI tool skills");
|
|
7412
|
+
}
|
|
7287
7413
|
if (unsupported.length === 0) return [];
|
|
7288
7414
|
const noun = unsupported.length === 1 ? "feature" : "features";
|
|
7289
7415
|
return [`${tool}: ${noun} enabled but not supported by this adapter: ${unsupported.join(", ")}`];
|
|
@@ -7344,28 +7470,32 @@ var init_adapters = __esm({
|
|
|
7344
7470
|
};
|
|
7345
7471
|
adapterCache = /* @__PURE__ */ new Map();
|
|
7346
7472
|
ADAPTER_CAPABILITIES = {
|
|
7347
|
-
cursor: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: true, prompts: false, githubAgents: false, worktree: true, customization: true, modelOverride: true, nativeQuestionTool: false },
|
|
7348
|
-
claude: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: true, prompts: false, githubAgents: false, worktree: true, customization: true, modelOverride: true, nativeQuestionTool: true },
|
|
7349
|
-
gemini: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: true, prompts: false, githubAgents: false, worktree: true, customization: true, modelOverride: true, nativeQuestionTool: false },
|
|
7350
|
-
cline: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: true, prompts: false, githubAgents: false, worktree: true, customization: true, modelOverride: true, nativeQuestionTool: false },
|
|
7351
|
-
codex: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: false, prompts: false, githubAgents: false, worktree: false, customization: true, modelOverride: true, nativeQuestionTool: false },
|
|
7352
|
-
"amazon-q": { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: false, prompts: false, githubAgents: false, worktree: false, customization: true, modelOverride: true, nativeQuestionTool: false },
|
|
7353
|
-
copilot: { agents: true, skills: true, rules: true, hooks: false, mcp: true, commands: true, prompts: true, githubAgents: true, worktree: true, customization: true, modelOverride: true, nativeQuestionTool: false },
|
|
7354
|
-
opencode: { agents: true, skills: true, rules: true, hooks: false, mcp: true, commands: true, prompts: false, githubAgents: false, worktree: false, customization: true, modelOverride: true, nativeQuestionTool: false },
|
|
7473
|
+
cursor: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: true, prompts: false, githubAgents: false, worktree: true, customization: true, modelOverride: true, nativeQuestionTool: false, cliTools: true },
|
|
7474
|
+
claude: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: true, prompts: false, githubAgents: false, worktree: true, customization: true, modelOverride: true, nativeQuestionTool: true, cliTools: true },
|
|
7475
|
+
gemini: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: true, prompts: false, githubAgents: false, worktree: true, customization: true, modelOverride: true, nativeQuestionTool: false, cliTools: true },
|
|
7476
|
+
cline: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: true, prompts: false, githubAgents: false, worktree: true, customization: true, modelOverride: true, nativeQuestionTool: false, cliTools: true },
|
|
7477
|
+
codex: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: false, prompts: false, githubAgents: false, worktree: false, customization: true, modelOverride: true, nativeQuestionTool: false, cliTools: true },
|
|
7478
|
+
"amazon-q": { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: false, prompts: false, githubAgents: false, worktree: false, customization: true, modelOverride: true, nativeQuestionTool: false, cliTools: true },
|
|
7479
|
+
copilot: { agents: true, skills: true, rules: true, hooks: false, mcp: true, commands: true, prompts: true, githubAgents: true, worktree: true, customization: true, modelOverride: true, nativeQuestionTool: false, cliTools: true },
|
|
7480
|
+
opencode: { agents: true, skills: true, rules: true, hooks: false, mcp: true, commands: true, prompts: false, githubAgents: false, worktree: false, customization: true, modelOverride: true, nativeQuestionTool: false, cliTools: true },
|
|
7355
7481
|
// C7.5-W2B2-H31 (D9-SA9.7.1): Windsurf shipped Cascade Hooks in v1.13.12 (2026-01-25).
|
|
7356
7482
|
// Hatch3r emits `.windsurf/hooks.json` per docs.windsurf.com/windsurf/cascade/hooks.md.
|
|
7357
|
-
windsurf: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: true, prompts: false, githubAgents: false, worktree: true, customization: true, modelOverride: true, nativeQuestionTool: false },
|
|
7483
|
+
windsurf: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: true, prompts: false, githubAgents: false, worktree: true, customization: true, modelOverride: true, nativeQuestionTool: false, cliTools: true },
|
|
7358
7484
|
// Amp reads AGENTS.md natively; the root file is written by generateRootAgentsMd()
|
|
7359
7485
|
// in init/update, not by this adapter. Amp also reads skills natively from
|
|
7360
7486
|
// `.agents/skills/` — populated by copyHatch3rFiles, not re-emitted by this
|
|
7361
7487
|
// adapter (re-emission corrupts SKILL.md frontmatter via managed-block wrap).
|
|
7362
|
-
// doGenerate() emits MCP settings only.
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
|
|
7488
|
+
// doGenerate() emits MCP settings only. cliTools: false — Amp reads
|
|
7489
|
+
// `hatch3r-cli-*` skills from the canonical `.agents/skills/` tree directly.
|
|
7490
|
+
amp: { agents: false, skills: false, rules: false, hooks: false, mcp: true, commands: false, prompts: false, githubAgents: false, worktree: false, customization: true, modelOverride: true, nativeQuestionTool: false, cliTools: false },
|
|
7491
|
+
kiro: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: false, prompts: false, githubAgents: false, worktree: false, customization: true, modelOverride: true, nativeQuestionTool: false, cliTools: true },
|
|
7492
|
+
aider: { agents: true, skills: true, rules: true, hooks: false, mcp: false, commands: false, prompts: false, githubAgents: false, worktree: false, customization: true, modelOverride: true, nativeQuestionTool: false, cliTools: true },
|
|
7493
|
+
goose: { agents: true, skills: true, rules: true, hooks: false, mcp: true, commands: false, prompts: false, githubAgents: false, worktree: false, customization: true, modelOverride: true, nativeQuestionTool: false, cliTools: true },
|
|
7494
|
+
// Zed has no skills surface (skills: false). cliTools: false — Wave 3 will
|
|
7495
|
+
// emit a one-line "Available CLI tool guides: ..." reference inside the
|
|
7496
|
+
// rules output instead of per-tool skill files.
|
|
7497
|
+
zed: { agents: true, skills: false, rules: true, hooks: false, mcp: true, commands: false, prompts: false, githubAgents: false, worktree: false, customization: false, modelOverride: false, nativeQuestionTool: false, cliTools: false },
|
|
7498
|
+
antigravity: { agents: true, skills: true, rules: true, hooks: false, mcp: true, commands: false, prompts: false, githubAgents: false, worktree: false, customization: true, modelOverride: true, nativeQuestionTool: false, cliTools: true }
|
|
7369
7499
|
};
|
|
7370
7500
|
}
|
|
7371
7501
|
});
|
|
@@ -7682,13 +7812,8 @@ async function fileIsUserWrapped(absPath) {
|
|
|
7682
7812
|
if (!hasManagedBlock(content)) {
|
|
7683
7813
|
return { wrapped: false };
|
|
7684
7814
|
}
|
|
7685
|
-
const
|
|
7686
|
-
|
|
7687
|
-
const endIdx = content.indexOf(endMarker);
|
|
7688
|
-
const after = endIdx === -1 ? "" : content.slice(endIdx + endMarker.length);
|
|
7689
|
-
const userBefore = before.trim();
|
|
7690
|
-
const userAfter = after.trim();
|
|
7691
|
-
return { wrapped: userBefore.length > 0 || userAfter.length > 0 };
|
|
7815
|
+
const userOutside = extractCustomContent(content).trim();
|
|
7816
|
+
return { wrapped: userOutside.length > 0 };
|
|
7692
7817
|
} catch (err) {
|
|
7693
7818
|
return { wrapped: false, error: err instanceof Error ? err.message : String(err) };
|
|
7694
7819
|
}
|
|
@@ -8319,7 +8444,7 @@ var init_retryWithBackoff = __esm({
|
|
|
8319
8444
|
|
|
8320
8445
|
// src/detect/installContext.ts
|
|
8321
8446
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
8322
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
8447
|
+
import { existsSync as existsSync3, lstatSync, readFileSync as readFileSync3 } from "fs";
|
|
8323
8448
|
import { dirname as dirname14, join as join28, normalize as normalize3, resolve as resolve4, sep as sep3 } from "path";
|
|
8324
8449
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
8325
8450
|
function isNpxPath(p) {
|
|
@@ -8365,12 +8490,25 @@ function npmGlobalRoot() {
|
|
|
8365
8490
|
}
|
|
8366
8491
|
return cachedNpmGlobalRoot;
|
|
8367
8492
|
}
|
|
8493
|
+
function isLinkedPackageDir(packageRoot) {
|
|
8494
|
+
try {
|
|
8495
|
+
return lstatSync(packageRoot).isSymbolicLink();
|
|
8496
|
+
} catch {
|
|
8497
|
+
return false;
|
|
8498
|
+
}
|
|
8499
|
+
}
|
|
8368
8500
|
function classifyInvocation(binPath, projectRoot) {
|
|
8369
8501
|
if (isNpxPath(binPath)) return "npx";
|
|
8370
8502
|
const globalRoot = npmGlobalRoot();
|
|
8371
|
-
if (globalRoot && binPath.startsWith(globalRoot))
|
|
8503
|
+
if (globalRoot && binPath.startsWith(globalRoot)) {
|
|
8504
|
+
if (isLinkedPackageDir(join28(globalRoot, "hatch3r"))) return "dev-source";
|
|
8505
|
+
return "global";
|
|
8506
|
+
}
|
|
8372
8507
|
const projectNodeModules = join28(projectRoot, "node_modules") + sep3;
|
|
8373
|
-
if (binPath.startsWith(projectNodeModules))
|
|
8508
|
+
if (binPath.startsWith(projectNodeModules)) {
|
|
8509
|
+
if (isLinkedPackageDir(join28(projectRoot, "node_modules", "hatch3r"))) return "dev-source";
|
|
8510
|
+
return "project-local";
|
|
8511
|
+
}
|
|
8374
8512
|
return "dev-source";
|
|
8375
8513
|
}
|
|
8376
8514
|
async function buildLocation(kind, binPath, packageRoot, cwdForPmDetection) {
|
|
@@ -8405,7 +8543,7 @@ async function surveyInstalls(rootDir) {
|
|
|
8405
8543
|
const alsoPresent = [];
|
|
8406
8544
|
if (invokedKind !== "project-local") {
|
|
8407
8545
|
const projectPkgRoot = join28(rootDir, "node_modules", "hatch3r");
|
|
8408
|
-
if (existsSync3(join28(projectPkgRoot, "package.json"))) {
|
|
8546
|
+
if (existsSync3(join28(projectPkgRoot, "package.json")) && !isLinkedPackageDir(projectPkgRoot)) {
|
|
8409
8547
|
const projectBin = join28(projectPkgRoot, "dist", "cli", "index.js");
|
|
8410
8548
|
alsoPresent.push(
|
|
8411
8549
|
await buildLocation("project-local", projectBin, projectPkgRoot, rootDir)
|
|
@@ -8416,7 +8554,7 @@ async function surveyInstalls(rootDir) {
|
|
|
8416
8554
|
const globalRoot = npmGlobalRoot();
|
|
8417
8555
|
if (globalRoot) {
|
|
8418
8556
|
const globalPkgRoot = join28(globalRoot, "hatch3r");
|
|
8419
|
-
if (existsSync3(join28(globalPkgRoot, "package.json"))) {
|
|
8557
|
+
if (existsSync3(join28(globalPkgRoot, "package.json")) && !isLinkedPackageDir(globalPkgRoot)) {
|
|
8420
8558
|
const globalBin = join28(globalPkgRoot, "dist", "cli", "index.js");
|
|
8421
8559
|
alsoPresent.push(
|
|
8422
8560
|
await buildLocation("global", globalBin, globalPkgRoot, rootDir)
|
|
@@ -8582,8 +8720,8 @@ import { appendFile as appendFile2, cp as cp4, mkdir as mkdir9, readFile as read
|
|
|
8582
8720
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
8583
8721
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
8584
8722
|
import { dirname as dirname15, join as join29, sep as sep4 } from "path";
|
|
8585
|
-
import
|
|
8586
|
-
import
|
|
8723
|
+
import chalk9 from "chalk";
|
|
8724
|
+
import inquirer8 from "inquirer";
|
|
8587
8725
|
function buildReExecPassThroughArgs(opts) {
|
|
8588
8726
|
const args = [];
|
|
8589
8727
|
if (opts?.yes) args.push("--yes");
|
|
@@ -8894,23 +9032,23 @@ async function runUpdateDryRun(rootDir, manifest, options = {}) {
|
|
|
8894
9032
|
}
|
|
8895
9033
|
}
|
|
8896
9034
|
const summaryLines = [];
|
|
8897
|
-
summaryLines.push(
|
|
8898
|
-
summaryLines.push(
|
|
9035
|
+
summaryLines.push(chalk9.dim(`Offline: ${options.offline ? "yes" : "no"}`));
|
|
9036
|
+
summaryLines.push(chalk9.dim(`Canonical candidate files: ${canonicalCandidates.length}`));
|
|
8899
9037
|
for (const [tool, bucket] of adapterChanges) {
|
|
8900
9038
|
if (bucket.error) {
|
|
8901
|
-
summaryLines.push(`${
|
|
9039
|
+
summaryLines.push(`${chalk9.red("x")} ${tool}: ${bucket.error}`);
|
|
8902
9040
|
continue;
|
|
8903
9041
|
}
|
|
8904
9042
|
const lines = [
|
|
8905
|
-
...bucket.added.map((p) => `${
|
|
8906
|
-
...bucket.modified.map((p) => `${
|
|
8907
|
-
...bucket.unchanged.map((p) => `${
|
|
9043
|
+
...bucket.added.map((p) => `${chalk9.green("+ added")} ${p}`),
|
|
9044
|
+
...bucket.modified.map((p) => `${chalk9.yellow("~ modified")} ${p}`),
|
|
9045
|
+
...bucket.unchanged.map((p) => `${chalk9.dim("= unchanged")} ${p}`)
|
|
8908
9046
|
];
|
|
8909
|
-
summaryLines.push(
|
|
9047
|
+
summaryLines.push(chalk9.bold(tool));
|
|
8910
9048
|
summaryLines.push(...lines.map((l) => ` ${l}`));
|
|
8911
9049
|
}
|
|
8912
9050
|
console.log();
|
|
8913
|
-
printBox("Update dry run (no writes)", summaryLines.length > 0 ? summaryLines : [
|
|
9051
|
+
printBox("Update dry run (no writes)", summaryLines.length > 0 ? summaryLines : [chalk9.dim("No adapters configured.")], "info");
|
|
8914
9052
|
return { canonicalCandidates, adapterChanges };
|
|
8915
9053
|
}
|
|
8916
9054
|
async function enumerateHatch3rFiles(srcDir, insideHatch3rDir, selectedIds) {
|
|
@@ -8976,7 +9114,7 @@ async function updateCommand(_opts) {
|
|
|
8976
9114
|
const manifest = await readManifest(rootDir);
|
|
8977
9115
|
if (!manifest) {
|
|
8978
9116
|
error("No .agents/hatch.json found.");
|
|
8979
|
-
console.log(
|
|
9117
|
+
console.log(chalk9.dim(" Run `npx hatch3r init` to set up your project first.\n"));
|
|
8980
9118
|
throw new HatchError("No .agents/hatch.json found.", 1, "CONFIG_ERROR");
|
|
8981
9119
|
}
|
|
8982
9120
|
const headless = !!_opts?.yes;
|
|
@@ -9067,14 +9205,14 @@ async function updateCommand(_opts) {
|
|
|
9067
9205
|
console.log();
|
|
9068
9206
|
warn("A clean reinit is recommended for this version update:");
|
|
9069
9207
|
for (const advisory of reinitAdvisories) {
|
|
9070
|
-
console.log(
|
|
9208
|
+
console.log(chalk9.dim(` - ${advisory.reason}`));
|
|
9071
9209
|
for (const change of advisory.changes ?? []) {
|
|
9072
|
-
console.log(
|
|
9210
|
+
console.log(chalk9.dim(` \u2022 ${change}`));
|
|
9073
9211
|
}
|
|
9074
9212
|
}
|
|
9075
9213
|
console.log();
|
|
9076
|
-
info(`Run ${
|
|
9077
|
-
console.log(
|
|
9214
|
+
info(`Run ${chalk9.bold("hatch3r clean")} and choose to reinitialize when prompted.`);
|
|
9215
|
+
console.log(chalk9.dim(" Your customizations and learnings will be preserved.\n"));
|
|
9078
9216
|
}
|
|
9079
9217
|
if (_opts?.diff && result.diffBefore && result.diffAfter) {
|
|
9080
9218
|
const diffLines = [];
|
|
@@ -9082,11 +9220,11 @@ async function updateCommand(_opts) {
|
|
|
9082
9220
|
const before = result.diffBefore.get(filePath) ?? null;
|
|
9083
9221
|
const after = result.diffAfter.get(filePath) ?? null;
|
|
9084
9222
|
if (before === null && after !== null) {
|
|
9085
|
-
diffLines.push(`${
|
|
9223
|
+
diffLines.push(`${chalk9.green("+ added")} ${filePath}`);
|
|
9086
9224
|
} else if (before !== null && after !== null && before !== after) {
|
|
9087
|
-
diffLines.push(`${
|
|
9225
|
+
diffLines.push(`${chalk9.yellow("~ modified")} ${filePath}`);
|
|
9088
9226
|
} else if (before !== null && after !== null && before === after) {
|
|
9089
|
-
diffLines.push(`${
|
|
9227
|
+
diffLines.push(`${chalk9.dim("= unchanged")} ${filePath}`);
|
|
9090
9228
|
}
|
|
9091
9229
|
}
|
|
9092
9230
|
if (diffLines.length > 0) {
|
|
@@ -9106,6 +9244,9 @@ async function updateCommand(_opts) {
|
|
|
9106
9244
|
label("Tools", `${compactedResult.syncedTools} tool(s) re-synced`),
|
|
9107
9245
|
label("Version", `v${compactedResult.version}`)
|
|
9108
9246
|
], "success");
|
|
9247
|
+
if (!m.cliTools || m.cliTools.selected.length === 0) {
|
|
9248
|
+
info("CLI tooling available as a token-efficient alternative to MCP \u2014 run `npx hatch3r cli-tools` to opt in.");
|
|
9249
|
+
}
|
|
9109
9250
|
if (isPipelineTimedOut(pipelineState)) {
|
|
9110
9251
|
const { report } = terminatePipeline(pipelineState);
|
|
9111
9252
|
warn(report.summary);
|
|
@@ -9153,7 +9294,7 @@ var init_update = __esm({
|
|
|
9153
9294
|
content.projectType = "brownfield";
|
|
9154
9295
|
content.teamSize = "team";
|
|
9155
9296
|
} else {
|
|
9156
|
-
const { projectType } = await
|
|
9297
|
+
const { projectType } = await inquirer8.prompt([
|
|
9157
9298
|
{
|
|
9158
9299
|
type: "select",
|
|
9159
9300
|
name: "projectType",
|
|
@@ -9165,7 +9306,7 @@ var init_update = __esm({
|
|
|
9165
9306
|
default: "brownfield"
|
|
9166
9307
|
}
|
|
9167
9308
|
]);
|
|
9168
|
-
const { teamSize } = await
|
|
9309
|
+
const { teamSize } = await inquirer8.prompt([
|
|
9169
9310
|
{
|
|
9170
9311
|
type: "select",
|
|
9171
9312
|
name: "teamSize",
|
|
@@ -9194,7 +9335,7 @@ var init_update = __esm({
|
|
|
9194
9335
|
if (headless) {
|
|
9195
9336
|
platform = "github";
|
|
9196
9337
|
} else {
|
|
9197
|
-
const answer = await
|
|
9338
|
+
const answer = await inquirer8.prompt([
|
|
9198
9339
|
{
|
|
9199
9340
|
type: "select",
|
|
9200
9341
|
name: "platform",
|
|
@@ -9216,7 +9357,7 @@ var init_update = __esm({
|
|
|
9216
9357
|
updated.project = updated.project || updated.repo;
|
|
9217
9358
|
notices.push("Migrated to GitHub platform (auto-detected from existing config)");
|
|
9218
9359
|
} else {
|
|
9219
|
-
const answers = await
|
|
9360
|
+
const answers = await inquirer8.prompt([
|
|
9220
9361
|
{ type: "input", name: "namespace", message: platform === "azure-devops" ? "Azure DevOps organization:" : "GitLab namespace (group or username):", default: updated.owner || void 0 },
|
|
9221
9362
|
{ type: "input", name: "project", message: platform === "azure-devops" ? "Azure DevOps project:" : "Project name:", default: updated.repo || void 0 },
|
|
9222
9363
|
{ type: "input", name: "repo", message: "Repository name:", default: updated.repo || void 0 }
|
|
@@ -9280,7 +9421,7 @@ var init_update = __esm({
|
|
|
9280
9421
|
if (headless) {
|
|
9281
9422
|
enabled = true;
|
|
9282
9423
|
} else {
|
|
9283
|
-
const answer = await
|
|
9424
|
+
const answer = await inquirer8.prompt([{
|
|
9284
9425
|
type: "confirm",
|
|
9285
9426
|
name: "enabled",
|
|
9286
9427
|
message: "hatch3r now supports worktree file isolation for parallel agent sessions. Enable it?",
|
|
@@ -9900,8 +10041,8 @@ async function worktreeCleanupCommand(opts = {}) {
|
|
|
9900
10041
|
init_ui();
|
|
9901
10042
|
init_types();
|
|
9902
10043
|
import { rm as rm4 } from "fs/promises";
|
|
9903
|
-
import
|
|
9904
|
-
import
|
|
10044
|
+
import chalk8 from "chalk";
|
|
10045
|
+
import inquirer7 from "inquirer";
|
|
9905
10046
|
|
|
9906
10047
|
// src/clean/index.ts
|
|
9907
10048
|
init_archive();
|
|
@@ -10281,8 +10422,8 @@ init_types();
|
|
|
10281
10422
|
import { access as access9, mkdir as mkdir8 } from "fs/promises";
|
|
10282
10423
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
10283
10424
|
import { basename as basename2, dirname as dirname12, join as join27 } from "path";
|
|
10284
|
-
import
|
|
10285
|
-
import
|
|
10425
|
+
import chalk7 from "chalk";
|
|
10426
|
+
import inquirer6 from "inquirer";
|
|
10286
10427
|
|
|
10287
10428
|
// src/detect/repoAnalyzer.ts
|
|
10288
10429
|
init_packageManager();
|
|
@@ -10738,6 +10879,813 @@ function isWSL() {
|
|
|
10738
10879
|
}
|
|
10739
10880
|
}
|
|
10740
10881
|
|
|
10882
|
+
// src/cli/shared/pickers.ts
|
|
10883
|
+
import inquirer4 from "inquirer";
|
|
10884
|
+
import chalk5 from "chalk";
|
|
10885
|
+
|
|
10886
|
+
// src/cliTools/registry.ts
|
|
10887
|
+
var AVAILABLE_CLI_TOOLS = {
|
|
10888
|
+
// ── Tier 1 (10 tools) ───────────────────────────────────────────
|
|
10889
|
+
ripgrep: {
|
|
10890
|
+
id: "ripgrep",
|
|
10891
|
+
probe: "rg",
|
|
10892
|
+
description: "Fast recursive grep with sane defaults and gitignore awareness",
|
|
10893
|
+
category: "search",
|
|
10894
|
+
tier: 1,
|
|
10895
|
+
install: {
|
|
10896
|
+
mac: [{ manager: "brew", command: "brew install ripgrep" }],
|
|
10897
|
+
linux: [{ manager: "apt", command: "sudo apt install ripgrep" }],
|
|
10898
|
+
win: [{ manager: "scoop", command: "scoop install ripgrep" }]
|
|
10899
|
+
},
|
|
10900
|
+
homepage: "https://github.com/BurntSushi/ripgrep"
|
|
10901
|
+
},
|
|
10902
|
+
fd: {
|
|
10903
|
+
id: "fd",
|
|
10904
|
+
probe: "fd",
|
|
10905
|
+
description: "User-friendly find replacement, gitignore-aware",
|
|
10906
|
+
category: "search",
|
|
10907
|
+
tier: 1,
|
|
10908
|
+
install: {
|
|
10909
|
+
mac: [{ manager: "brew", command: "brew install fd" }],
|
|
10910
|
+
linux: [{ manager: "apt", command: "sudo apt install fd-find" }],
|
|
10911
|
+
win: [{ manager: "scoop", command: "scoop install fd" }]
|
|
10912
|
+
},
|
|
10913
|
+
homepage: "https://github.com/sharkdp/fd"
|
|
10914
|
+
},
|
|
10915
|
+
jq: {
|
|
10916
|
+
id: "jq",
|
|
10917
|
+
probe: "jq",
|
|
10918
|
+
description: "JSON processor and query language",
|
|
10919
|
+
category: "json",
|
|
10920
|
+
tier: 1,
|
|
10921
|
+
install: {
|
|
10922
|
+
mac: [{ manager: "brew", command: "brew install jq" }],
|
|
10923
|
+
linux: [{ manager: "apt", command: "sudo apt install jq" }],
|
|
10924
|
+
win: [{ manager: "scoop", command: "scoop install jq" }]
|
|
10925
|
+
},
|
|
10926
|
+
homepage: "https://github.com/jqlang/jq"
|
|
10927
|
+
},
|
|
10928
|
+
yq: {
|
|
10929
|
+
id: "yq",
|
|
10930
|
+
probe: "yq",
|
|
10931
|
+
description: "YAML processor (mikefarah Go implementation)",
|
|
10932
|
+
category: "yaml",
|
|
10933
|
+
tier: 1,
|
|
10934
|
+
install: {
|
|
10935
|
+
mac: [{ manager: "brew", command: "brew install yq" }],
|
|
10936
|
+
linux: [{ manager: "snap", command: "sudo snap install yq" }],
|
|
10937
|
+
win: [{ manager: "scoop", command: "scoop install yq" }]
|
|
10938
|
+
},
|
|
10939
|
+
homepage: "https://github.com/mikefarah/yq"
|
|
10940
|
+
},
|
|
10941
|
+
gh: {
|
|
10942
|
+
id: "gh",
|
|
10943
|
+
probe: "gh",
|
|
10944
|
+
description: "GitHub CLI \u2014 repos, issues, PRs, releases, gists",
|
|
10945
|
+
category: "forge",
|
|
10946
|
+
tier: 1,
|
|
10947
|
+
install: {
|
|
10948
|
+
mac: [{ manager: "brew", command: "brew install gh" }],
|
|
10949
|
+
linux: [{ manager: "apt", command: "sudo apt install gh" }],
|
|
10950
|
+
win: [{ manager: "winget", command: "winget install GitHub.cli" }]
|
|
10951
|
+
},
|
|
10952
|
+
requiresEnv: ["GH_TOKEN"],
|
|
10953
|
+
homepage: "https://cli.github.com/"
|
|
10954
|
+
},
|
|
10955
|
+
delta: {
|
|
10956
|
+
id: "delta",
|
|
10957
|
+
probe: "delta",
|
|
10958
|
+
description: "Syntax-highlighting git diff pager",
|
|
10959
|
+
category: "git",
|
|
10960
|
+
tier: 1,
|
|
10961
|
+
install: {
|
|
10962
|
+
mac: [{ manager: "brew", command: "brew install git-delta" }],
|
|
10963
|
+
linux: [{ manager: "apt", command: "sudo apt install git-delta" }],
|
|
10964
|
+
win: [{ manager: "scoop", command: "scoop install delta" }]
|
|
10965
|
+
},
|
|
10966
|
+
homepage: "https://github.com/dandavison/delta"
|
|
10967
|
+
},
|
|
10968
|
+
bat: {
|
|
10969
|
+
id: "bat",
|
|
10970
|
+
probe: "bat",
|
|
10971
|
+
description: "cat clone with syntax highlighting and git integration",
|
|
10972
|
+
category: "view",
|
|
10973
|
+
tier: 1,
|
|
10974
|
+
install: {
|
|
10975
|
+
mac: [{ manager: "brew", command: "brew install bat" }],
|
|
10976
|
+
linux: [{ manager: "apt", command: "sudo apt install bat" }],
|
|
10977
|
+
win: [{ manager: "winget", command: "winget install sharkdp.bat" }]
|
|
10978
|
+
},
|
|
10979
|
+
homepage: "https://github.com/sharkdp/bat"
|
|
10980
|
+
},
|
|
10981
|
+
sd: {
|
|
10982
|
+
id: "sd",
|
|
10983
|
+
probe: "sd",
|
|
10984
|
+
description: "Intuitive sed replacement with literal string patterns",
|
|
10985
|
+
category: "edit",
|
|
10986
|
+
tier: 1,
|
|
10987
|
+
install: {
|
|
10988
|
+
mac: [{ manager: "brew", command: "brew install sd" }],
|
|
10989
|
+
linux: [{ manager: "cargo", command: "cargo install sd" }],
|
|
10990
|
+
win: [{ manager: "scoop", command: "scoop install sd" }]
|
|
10991
|
+
},
|
|
10992
|
+
homepage: "https://github.com/chmln/sd"
|
|
10993
|
+
},
|
|
10994
|
+
"ast-grep": {
|
|
10995
|
+
id: "ast-grep",
|
|
10996
|
+
probe: "sg",
|
|
10997
|
+
description: "Structural search and rewrite for code via AST patterns",
|
|
10998
|
+
category: "search",
|
|
10999
|
+
tier: 1,
|
|
11000
|
+
install: {
|
|
11001
|
+
mac: [{ manager: "brew", command: "brew install ast-grep" }],
|
|
11002
|
+
linux: [{ manager: "cargo", command: "cargo install ast-grep" }],
|
|
11003
|
+
win: [{ manager: "scoop", command: "scoop install ast-grep" }]
|
|
11004
|
+
},
|
|
11005
|
+
homepage: "https://ast-grep.github.io/"
|
|
11006
|
+
},
|
|
11007
|
+
zstd: {
|
|
11008
|
+
id: "zstd",
|
|
11009
|
+
probe: "zstd",
|
|
11010
|
+
description: "Fast lossless compression with high ratio",
|
|
11011
|
+
category: "archive",
|
|
11012
|
+
tier: 1,
|
|
11013
|
+
install: {
|
|
11014
|
+
mac: [{ manager: "brew", command: "brew install zstd" }],
|
|
11015
|
+
linux: [{ manager: "apt", command: "sudo apt install zstd" }],
|
|
11016
|
+
win: [{ manager: "winget", command: "winget install Facebook.Zstandard" }]
|
|
11017
|
+
},
|
|
11018
|
+
homepage: "https://github.com/facebook/zstd"
|
|
11019
|
+
},
|
|
11020
|
+
// ── Tier 2 (11 tools, conditional) ──────────────────────────────
|
|
11021
|
+
playwright: {
|
|
11022
|
+
id: "playwright",
|
|
11023
|
+
probe: "playwright",
|
|
11024
|
+
description: "Browser automation, web testing, and UI interaction",
|
|
11025
|
+
category: "browser",
|
|
11026
|
+
tier: 2,
|
|
11027
|
+
trigger: "web-project",
|
|
11028
|
+
install: {
|
|
11029
|
+
mac: [{ manager: "npm", command: "npm install -D @playwright/test && npx playwright install" }],
|
|
11030
|
+
linux: [{ manager: "npm", command: "npm install -D @playwright/test && npx playwright install --with-deps" }],
|
|
11031
|
+
win: [{ manager: "npm", command: "npm install -D @playwright/test && npx playwright install" }]
|
|
11032
|
+
},
|
|
11033
|
+
homepage: "https://playwright.dev/"
|
|
11034
|
+
},
|
|
11035
|
+
duckdb: {
|
|
11036
|
+
id: "duckdb",
|
|
11037
|
+
probe: "duckdb",
|
|
11038
|
+
description: "Embedded analytical database with first-class CSV/Parquet support",
|
|
11039
|
+
category: "data",
|
|
11040
|
+
tier: 2,
|
|
11041
|
+
trigger: "data-project",
|
|
11042
|
+
install: {
|
|
11043
|
+
mac: [{ manager: "brew", command: "brew install duckdb" }],
|
|
11044
|
+
linux: [{ manager: "curl", command: "curl https://install.duckdb.org | sh" }],
|
|
11045
|
+
win: [{ manager: "winget", command: "winget install DuckDB.cli" }]
|
|
11046
|
+
},
|
|
11047
|
+
homepage: "https://duckdb.org/"
|
|
11048
|
+
},
|
|
11049
|
+
xsv: {
|
|
11050
|
+
id: "xsv",
|
|
11051
|
+
probe: "xsv",
|
|
11052
|
+
description: "Fast CSV toolkit (slice, search, join, stats)",
|
|
11053
|
+
category: "data",
|
|
11054
|
+
tier: 2,
|
|
11055
|
+
trigger: "data-project",
|
|
11056
|
+
install: {
|
|
11057
|
+
mac: [{ manager: "brew", command: "brew install xsv" }],
|
|
11058
|
+
linux: [{ manager: "cargo", command: "cargo install xsv" }],
|
|
11059
|
+
win: [{ manager: "scoop", command: "scoop install xsv" }]
|
|
11060
|
+
},
|
|
11061
|
+
homepage: "https://github.com/BurntSushi/xsv"
|
|
11062
|
+
},
|
|
11063
|
+
taplo: {
|
|
11064
|
+
id: "taplo",
|
|
11065
|
+
probe: "taplo",
|
|
11066
|
+
description: "TOML toolkit (format, lint, query) for pyproject.toml / Cargo.toml",
|
|
11067
|
+
category: "yaml",
|
|
11068
|
+
tier: 2,
|
|
11069
|
+
trigger: "rust-project",
|
|
11070
|
+
install: {
|
|
11071
|
+
mac: [{ manager: "brew", command: "brew install taplo" }],
|
|
11072
|
+
linux: [{ manager: "cargo", command: "cargo install taplo-cli --locked" }],
|
|
11073
|
+
win: [{ manager: "scoop", command: "scoop install taplo" }]
|
|
11074
|
+
},
|
|
11075
|
+
homepage: "https://taplo.tamasfe.dev/"
|
|
11076
|
+
},
|
|
11077
|
+
glab: {
|
|
11078
|
+
id: "glab",
|
|
11079
|
+
probe: "glab",
|
|
11080
|
+
description: "GitLab CLI \u2014 merge requests, issues, pipelines",
|
|
11081
|
+
category: "forge",
|
|
11082
|
+
tier: 2,
|
|
11083
|
+
trigger: "gitlab-remote",
|
|
11084
|
+
install: {
|
|
11085
|
+
mac: [{ manager: "brew", command: "brew install glab" }],
|
|
11086
|
+
linux: [{ manager: "apt", command: "sudo apt install glab" }],
|
|
11087
|
+
win: [{ manager: "winget", command: "winget install GitLab.GLab" }]
|
|
11088
|
+
},
|
|
11089
|
+
requiresEnv: ["GITLAB_TOKEN"],
|
|
11090
|
+
homepage: "https://gitlab.com/gitlab-org/cli"
|
|
11091
|
+
},
|
|
11092
|
+
"az-devops": {
|
|
11093
|
+
id: "az-devops",
|
|
11094
|
+
probe: "az",
|
|
11095
|
+
description: "Azure DevOps work items, repos, pipelines via az CLI extension",
|
|
11096
|
+
category: "forge",
|
|
11097
|
+
tier: 2,
|
|
11098
|
+
trigger: "azure-remote",
|
|
11099
|
+
install: {
|
|
11100
|
+
mac: [{ manager: "brew", command: "brew install azure-cli && az extension add --name azure-devops" }],
|
|
11101
|
+
linux: [{ manager: "curl", command: "curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash && az extension add --name azure-devops" }],
|
|
11102
|
+
win: [{ manager: "winget", command: "winget install Microsoft.AzureCLI && az extension add --name azure-devops" }]
|
|
11103
|
+
},
|
|
11104
|
+
requiresEnv: ["AZURE_DEVOPS_PAT", "AZURE_DEVOPS_ORG"],
|
|
11105
|
+
homepage: "https://learn.microsoft.com/en-us/cli/azure/azure-devops"
|
|
11106
|
+
},
|
|
11107
|
+
docker: {
|
|
11108
|
+
id: "docker",
|
|
11109
|
+
probe: "docker",
|
|
11110
|
+
description: "Container runtime and CLI",
|
|
11111
|
+
category: "container",
|
|
11112
|
+
tier: 2,
|
|
11113
|
+
trigger: "docker-detected",
|
|
11114
|
+
install: {
|
|
11115
|
+
mac: [{ manager: "brew", command: "brew install --cask docker" }],
|
|
11116
|
+
linux: [{ manager: "curl", command: "curl -fsSL https://get.docker.com | sudo sh" }],
|
|
11117
|
+
win: [{ manager: "winget", command: "winget install Docker.DockerDesktop" }]
|
|
11118
|
+
},
|
|
11119
|
+
homepage: "https://docs.docker.com/get-docker/"
|
|
11120
|
+
},
|
|
11121
|
+
llm: {
|
|
11122
|
+
id: "llm",
|
|
11123
|
+
probe: "llm",
|
|
11124
|
+
description: "simonw/llm \u2014 invoke LLMs from the command line with prompt templates",
|
|
11125
|
+
category: "ai",
|
|
11126
|
+
tier: 2,
|
|
11127
|
+
trigger: "ci-llm-project",
|
|
11128
|
+
install: {
|
|
11129
|
+
mac: [{ manager: "brew", command: "brew install llm" }],
|
|
11130
|
+
linux: [{ manager: "pipx", command: "pipx install llm" }],
|
|
11131
|
+
win: [{ manager: "pipx", command: "pipx install llm" }]
|
|
11132
|
+
},
|
|
11133
|
+
homepage: "https://llm.datasette.io/"
|
|
11134
|
+
},
|
|
11135
|
+
fzf: {
|
|
11136
|
+
id: "fzf",
|
|
11137
|
+
probe: "fzf",
|
|
11138
|
+
description: "Interactive fuzzy finder for TTY pickers",
|
|
11139
|
+
category: "interactive",
|
|
11140
|
+
tier: 2,
|
|
11141
|
+
trigger: "interactive-tty",
|
|
11142
|
+
install: {
|
|
11143
|
+
mac: [{ manager: "brew", command: "brew install fzf" }],
|
|
11144
|
+
linux: [{ manager: "apt", command: "sudo apt install fzf" }],
|
|
11145
|
+
win: [{ manager: "scoop", command: "scoop install fzf" }]
|
|
11146
|
+
},
|
|
11147
|
+
homepage: "https://github.com/junegunn/fzf"
|
|
11148
|
+
},
|
|
11149
|
+
lazygit: {
|
|
11150
|
+
id: "lazygit",
|
|
11151
|
+
probe: "lazygit",
|
|
11152
|
+
description: "Terminal UI for git with keyboard-driven workflows",
|
|
11153
|
+
category: "git",
|
|
11154
|
+
tier: 2,
|
|
11155
|
+
trigger: "interactive-tty",
|
|
11156
|
+
install: {
|
|
11157
|
+
mac: [{ manager: "brew", command: "brew install lazygit" }],
|
|
11158
|
+
linux: [{ manager: "apt", command: "sudo apt install lazygit" }],
|
|
11159
|
+
win: [{ manager: "scoop", command: "scoop install lazygit" }]
|
|
11160
|
+
},
|
|
11161
|
+
homepage: "https://github.com/jesseduffield/lazygit"
|
|
11162
|
+
},
|
|
11163
|
+
difftastic: {
|
|
11164
|
+
id: "difftastic",
|
|
11165
|
+
probe: "difft",
|
|
11166
|
+
description: "Structural diff that understands syntax",
|
|
11167
|
+
category: "git",
|
|
11168
|
+
tier: 2,
|
|
11169
|
+
trigger: "interactive-tty",
|
|
11170
|
+
install: {
|
|
11171
|
+
mac: [{ manager: "brew", command: "brew install difftastic" }],
|
|
11172
|
+
linux: [{ manager: "cargo", command: "cargo install --locked difftastic" }],
|
|
11173
|
+
win: [{ manager: "scoop", command: "scoop install difftastic" }]
|
|
11174
|
+
},
|
|
11175
|
+
homepage: "https://difftastic.wilfred.me.uk/"
|
|
11176
|
+
},
|
|
11177
|
+
// ── Tier 3 (8 tools, opt-in advanced) ───────────────────────────
|
|
11178
|
+
rtk: {
|
|
11179
|
+
id: "rtk",
|
|
11180
|
+
probe: "rtk",
|
|
11181
|
+
description: "CLI output-compression proxy (see \u26A0 caveat)",
|
|
11182
|
+
category: "ai",
|
|
11183
|
+
tier: 3,
|
|
11184
|
+
caveat: "pipe-output-corruption",
|
|
11185
|
+
install: {
|
|
11186
|
+
mac: [{ manager: "brew", command: "brew install rtk-ai/tap/rtk" }],
|
|
11187
|
+
linux: [{ manager: "curl", command: "curl -fsSL https://rtk.dev/install.sh | sh" }],
|
|
11188
|
+
win: [{ manager: "scoop", command: "scoop install rtk" }]
|
|
11189
|
+
},
|
|
11190
|
+
homepage: "https://github.com/rtk-ai/rtk"
|
|
11191
|
+
},
|
|
11192
|
+
stagehand: {
|
|
11193
|
+
id: "stagehand",
|
|
11194
|
+
probe: "stagehand",
|
|
11195
|
+
description: "Browserbase Stagehand \u2014 AI-driven browser automation",
|
|
11196
|
+
category: "browser",
|
|
11197
|
+
tier: 3,
|
|
11198
|
+
install: {
|
|
11199
|
+
mac: [{ manager: "npm", command: "npm install -g @browserbasehq/stagehand" }],
|
|
11200
|
+
linux: [{ manager: "npm", command: "npm install -g @browserbasehq/stagehand" }],
|
|
11201
|
+
win: [{ manager: "npm", command: "npm install -g @browserbasehq/stagehand" }]
|
|
11202
|
+
},
|
|
11203
|
+
homepage: "https://github.com/browserbase/stagehand"
|
|
11204
|
+
},
|
|
11205
|
+
aichat: {
|
|
11206
|
+
id: "aichat",
|
|
11207
|
+
probe: "aichat",
|
|
11208
|
+
description: "Multi-provider LLM chat CLI with RAG and session memory",
|
|
11209
|
+
category: "ai",
|
|
11210
|
+
tier: 3,
|
|
11211
|
+
install: {
|
|
11212
|
+
mac: [{ manager: "brew", command: "brew install aichat" }],
|
|
11213
|
+
linux: [{ manager: "cargo", command: "cargo install aichat" }],
|
|
11214
|
+
win: [{ manager: "scoop", command: "scoop install aichat" }]
|
|
11215
|
+
},
|
|
11216
|
+
homepage: "https://github.com/sigoden/aichat"
|
|
11217
|
+
},
|
|
11218
|
+
mods: {
|
|
11219
|
+
id: "mods",
|
|
11220
|
+
probe: "mods",
|
|
11221
|
+
description: "Charm mods \u2014 Unix-friendly LLM pipeline tool",
|
|
11222
|
+
category: "ai",
|
|
11223
|
+
tier: 3,
|
|
11224
|
+
install: {
|
|
11225
|
+
mac: [{ manager: "brew", command: "brew install charmbracelet/tap/mods" }],
|
|
11226
|
+
linux: [{ manager: "apt", command: "sudo apt install mods" }],
|
|
11227
|
+
win: [{ manager: "scoop", command: "scoop install mods" }]
|
|
11228
|
+
},
|
|
11229
|
+
homepage: "https://github.com/charmbracelet/mods"
|
|
11230
|
+
},
|
|
11231
|
+
comby: {
|
|
11232
|
+
id: "comby",
|
|
11233
|
+
probe: "comby",
|
|
11234
|
+
description: "Structural search and replace across languages with declarative patterns",
|
|
11235
|
+
category: "search",
|
|
11236
|
+
tier: 3,
|
|
11237
|
+
install: {
|
|
11238
|
+
mac: [{ manager: "brew", command: "brew install comby" }],
|
|
11239
|
+
linux: [{ manager: "curl", command: "bash <(curl -sL get.comby.dev)" }],
|
|
11240
|
+
win: [{ manager: "scoop", command: "scoop install comby" }]
|
|
11241
|
+
},
|
|
11242
|
+
homepage: "https://comby.dev/"
|
|
11243
|
+
},
|
|
11244
|
+
miller: {
|
|
11245
|
+
id: "miller",
|
|
11246
|
+
probe: "mlr",
|
|
11247
|
+
description: "awk/sed/cut/join for CSV/TSV/JSON/Parquet streams",
|
|
11248
|
+
category: "data",
|
|
11249
|
+
tier: 3,
|
|
11250
|
+
install: {
|
|
11251
|
+
mac: [{ manager: "brew", command: "brew install miller" }],
|
|
11252
|
+
linux: [{ manager: "apt", command: "sudo apt install miller" }],
|
|
11253
|
+
win: [{ manager: "scoop", command: "scoop install miller" }]
|
|
11254
|
+
},
|
|
11255
|
+
homepage: "https://miller.readthedocs.io/"
|
|
11256
|
+
},
|
|
11257
|
+
csvkit: {
|
|
11258
|
+
id: "csvkit",
|
|
11259
|
+
probe: "csvlook",
|
|
11260
|
+
description: "csvkit \u2014 Python CSV toolkit (csvlook, csvsql, csvjoin, csvstat)",
|
|
11261
|
+
category: "data",
|
|
11262
|
+
tier: 3,
|
|
11263
|
+
install: {
|
|
11264
|
+
mac: [{ manager: "pipx", command: "pipx install csvkit" }],
|
|
11265
|
+
linux: [{ manager: "pipx", command: "pipx install csvkit" }],
|
|
11266
|
+
win: [{ manager: "pipx", command: "pipx install csvkit" }]
|
|
11267
|
+
},
|
|
11268
|
+
homepage: "https://csvkit.readthedocs.io/"
|
|
11269
|
+
},
|
|
11270
|
+
podman: {
|
|
11271
|
+
id: "podman",
|
|
11272
|
+
probe: "podman",
|
|
11273
|
+
description: "Daemonless container engine, rootless by default (Docker alternative)",
|
|
11274
|
+
category: "container",
|
|
11275
|
+
tier: 3,
|
|
11276
|
+
install: {
|
|
11277
|
+
mac: [{ manager: "brew", command: "brew install podman" }],
|
|
11278
|
+
linux: [{ manager: "apt", command: "sudo apt install podman" }],
|
|
11279
|
+
win: [{ manager: "winget", command: "winget install RedHat.Podman" }]
|
|
11280
|
+
},
|
|
11281
|
+
homepage: "https://podman.io/"
|
|
11282
|
+
}
|
|
11283
|
+
};
|
|
11284
|
+
var TIER1_CLI_TOOLS = [
|
|
11285
|
+
"ripgrep",
|
|
11286
|
+
"fd",
|
|
11287
|
+
"jq",
|
|
11288
|
+
"yq",
|
|
11289
|
+
"gh",
|
|
11290
|
+
"delta",
|
|
11291
|
+
"bat",
|
|
11292
|
+
"sd",
|
|
11293
|
+
"ast-grep",
|
|
11294
|
+
"zstd"
|
|
11295
|
+
];
|
|
11296
|
+
var TIER2_CLI_TOOLS_BY_TRIGGER = {
|
|
11297
|
+
"web-project": ["playwright"],
|
|
11298
|
+
"data-project": ["duckdb", "xsv"],
|
|
11299
|
+
"rust-project": ["taplo"],
|
|
11300
|
+
"python-project": ["taplo"],
|
|
11301
|
+
"docker-detected": ["docker"],
|
|
11302
|
+
"ci-llm-project": ["llm"],
|
|
11303
|
+
"interactive-tty": ["fzf", "lazygit", "difftastic"],
|
|
11304
|
+
"gitlab-remote": ["glab"],
|
|
11305
|
+
"azure-remote": ["az-devops"]
|
|
11306
|
+
};
|
|
11307
|
+
var TIER3_CLI_TOOLS = [
|
|
11308
|
+
"rtk",
|
|
11309
|
+
"stagehand",
|
|
11310
|
+
"aichat",
|
|
11311
|
+
"mods",
|
|
11312
|
+
"comby",
|
|
11313
|
+
"miller",
|
|
11314
|
+
"csvkit",
|
|
11315
|
+
"podman"
|
|
11316
|
+
];
|
|
11317
|
+
var DEFAULT_CLI_TOOLS = TIER1_CLI_TOOLS;
|
|
11318
|
+
var CLI_TOOL_SECRET_NOTES = Object.freeze(
|
|
11319
|
+
(() => {
|
|
11320
|
+
const out = {};
|
|
11321
|
+
for (const tool of Object.values(AVAILABLE_CLI_TOOLS)) {
|
|
11322
|
+
const env = tool.requiresEnv;
|
|
11323
|
+
if (env && env.length > 0) {
|
|
11324
|
+
out[tool.id] = [...env];
|
|
11325
|
+
}
|
|
11326
|
+
}
|
|
11327
|
+
return out;
|
|
11328
|
+
})()
|
|
11329
|
+
);
|
|
11330
|
+
|
|
11331
|
+
// src/cli/shared/pickers.ts
|
|
11332
|
+
function tierSeparator(tier) {
|
|
11333
|
+
const label2 = tier === 1 ? "\u2500\u2500 Tier 1 \u2014 default-on \u2500\u2500" : tier === 2 ? "\u2500\u2500 Tier 2 \u2014 conditional \u2500\u2500" : "\u2500\u2500 Tier 3 \u2014 opt-in advanced \u2500\u2500";
|
|
11334
|
+
return new inquirer4.Separator(label2);
|
|
11335
|
+
}
|
|
11336
|
+
function toolChoice(meta, preChecked) {
|
|
11337
|
+
const caveat = meta.caveat ? `${chalk5.yellow("\u26A0")} ` : "";
|
|
11338
|
+
const description = meta.description;
|
|
11339
|
+
return {
|
|
11340
|
+
name: `${caveat}${meta.id} \u2014 ${description}`,
|
|
11341
|
+
value: meta.id,
|
|
11342
|
+
checked: preChecked
|
|
11343
|
+
};
|
|
11344
|
+
}
|
|
11345
|
+
async function pickCliTools(opts = {}) {
|
|
11346
|
+
const existingSet = new Set(opts.existing ?? []);
|
|
11347
|
+
const suggestedSet = new Set(opts.tier2Suggested ?? []);
|
|
11348
|
+
const hasExistingSelection = (opts.existing?.length ?? 0) > 0;
|
|
11349
|
+
const tier1Choices = [];
|
|
11350
|
+
const tier2Choices = [];
|
|
11351
|
+
const tier3Choices = [];
|
|
11352
|
+
for (const id of TIER1_CLI_TOOLS) {
|
|
11353
|
+
const meta = AVAILABLE_CLI_TOOLS[id];
|
|
11354
|
+
if (!meta) continue;
|
|
11355
|
+
const checked = hasExistingSelection ? existingSet.has(id) : true;
|
|
11356
|
+
tier1Choices.push(toolChoice(meta, checked));
|
|
11357
|
+
}
|
|
11358
|
+
const seenTier2 = /* @__PURE__ */ new Set();
|
|
11359
|
+
for (const ids of Object.values(TIER2_CLI_TOOLS_BY_TRIGGER)) {
|
|
11360
|
+
for (const id of ids) {
|
|
11361
|
+
if (seenTier2.has(id)) continue;
|
|
11362
|
+
seenTier2.add(id);
|
|
11363
|
+
const meta = AVAILABLE_CLI_TOOLS[id];
|
|
11364
|
+
if (!meta) continue;
|
|
11365
|
+
const checked = hasExistingSelection ? existingSet.has(id) : suggestedSet.has(id);
|
|
11366
|
+
tier2Choices.push(toolChoice(meta, checked));
|
|
11367
|
+
}
|
|
11368
|
+
}
|
|
11369
|
+
for (const id of TIER3_CLI_TOOLS) {
|
|
11370
|
+
const meta = AVAILABLE_CLI_TOOLS[id];
|
|
11371
|
+
if (!meta) continue;
|
|
11372
|
+
const checked = hasExistingSelection && existingSet.has(id);
|
|
11373
|
+
tier3Choices.push(toolChoice(meta, checked));
|
|
11374
|
+
}
|
|
11375
|
+
const choices = [
|
|
11376
|
+
tierSeparator(1),
|
|
11377
|
+
...tier1Choices,
|
|
11378
|
+
tierSeparator(2),
|
|
11379
|
+
...tier2Choices,
|
|
11380
|
+
tierSeparator(3),
|
|
11381
|
+
...tier3Choices
|
|
11382
|
+
];
|
|
11383
|
+
const themeOption = opts.wslTheme ? { theme: opts.wslTheme } : {};
|
|
11384
|
+
const { tools } = await inquirer4.prompt([
|
|
11385
|
+
{
|
|
11386
|
+
type: "checkbox",
|
|
11387
|
+
name: "tools",
|
|
11388
|
+
message: "Select CLI tools (default tier-1 + project-triggered tier-2):",
|
|
11389
|
+
choices,
|
|
11390
|
+
...themeOption
|
|
11391
|
+
}
|
|
11392
|
+
]);
|
|
11393
|
+
return tools ?? [];
|
|
11394
|
+
}
|
|
11395
|
+
async function pickMcpServers(opts) {
|
|
11396
|
+
const platformMcp = PLATFORM_MCP_SERVER[opts.platform];
|
|
11397
|
+
const defaultSelection = opts.existing && opts.existing.length > 0 ? opts.existing : Array.from(/* @__PURE__ */ new Set([platformMcp, "playwright", "context7"]));
|
|
11398
|
+
const themeOption = opts.wslTheme ? { theme: opts.wslTheme } : {};
|
|
11399
|
+
const { mcp } = await inquirer4.prompt([
|
|
11400
|
+
{
|
|
11401
|
+
type: "checkbox",
|
|
11402
|
+
name: "mcp",
|
|
11403
|
+
message: "Select MCP servers:",
|
|
11404
|
+
choices: MCP_CHOICES,
|
|
11405
|
+
default: defaultSelection,
|
|
11406
|
+
...themeOption
|
|
11407
|
+
}
|
|
11408
|
+
]);
|
|
11409
|
+
const servers = mcp ?? [];
|
|
11410
|
+
if (!servers.includes(platformMcp)) {
|
|
11411
|
+
servers.unshift(platformMcp);
|
|
11412
|
+
}
|
|
11413
|
+
return servers;
|
|
11414
|
+
}
|
|
11415
|
+
async function confirmMcpGate(opts) {
|
|
11416
|
+
const defaultYes = opts.defaultYes ?? opts.hasExisting;
|
|
11417
|
+
const { proceed } = await inquirer4.prompt([
|
|
11418
|
+
{
|
|
11419
|
+
type: "confirm",
|
|
11420
|
+
name: "proceed",
|
|
11421
|
+
message: "Configure MCP servers? (CLI tools are recommended as the default)",
|
|
11422
|
+
default: defaultYes
|
|
11423
|
+
}
|
|
11424
|
+
]);
|
|
11425
|
+
return proceed;
|
|
11426
|
+
}
|
|
11427
|
+
|
|
11428
|
+
// src/cliTools/detect.ts
|
|
11429
|
+
import { spawn } from "child_process";
|
|
11430
|
+
var PROBE_TIMEOUT_MS = 2e3;
|
|
11431
|
+
function isSafeProbeName(name) {
|
|
11432
|
+
return /^[A-Za-z0-9._\-+]+$/.test(name);
|
|
11433
|
+
}
|
|
11434
|
+
async function probeBin(name) {
|
|
11435
|
+
if (!isSafeProbeName(name)) return "";
|
|
11436
|
+
const isWindows = process.platform === "win32";
|
|
11437
|
+
const [cmd, args] = isWindows ? ["where", [name]] : ["/bin/sh", ["-c", `command -v -- "${name}"`]];
|
|
11438
|
+
return new Promise((resolve5) => {
|
|
11439
|
+
let settled = false;
|
|
11440
|
+
let stdout = "";
|
|
11441
|
+
const child = spawn(cmd, args, {
|
|
11442
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
11443
|
+
windowsHide: true
|
|
11444
|
+
});
|
|
11445
|
+
const timer = setTimeout(() => {
|
|
11446
|
+
if (settled) return;
|
|
11447
|
+
settled = true;
|
|
11448
|
+
try {
|
|
11449
|
+
child.kill("SIGKILL");
|
|
11450
|
+
} catch {
|
|
11451
|
+
}
|
|
11452
|
+
resolve5("");
|
|
11453
|
+
}, PROBE_TIMEOUT_MS);
|
|
11454
|
+
child.stdout?.on("data", (chunk) => {
|
|
11455
|
+
stdout += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
11456
|
+
});
|
|
11457
|
+
child.on("error", () => {
|
|
11458
|
+
if (settled) return;
|
|
11459
|
+
settled = true;
|
|
11460
|
+
clearTimeout(timer);
|
|
11461
|
+
resolve5("");
|
|
11462
|
+
});
|
|
11463
|
+
child.on("close", (code) => {
|
|
11464
|
+
if (settled) return;
|
|
11465
|
+
settled = true;
|
|
11466
|
+
clearTimeout(timer);
|
|
11467
|
+
if (code !== 0) {
|
|
11468
|
+
resolve5("");
|
|
11469
|
+
return;
|
|
11470
|
+
}
|
|
11471
|
+
const first = stdout.split(/\r?\n/).map((s) => s.trim()).find((s) => s.length > 0) ?? "";
|
|
11472
|
+
resolve5(first);
|
|
11473
|
+
});
|
|
11474
|
+
});
|
|
11475
|
+
}
|
|
11476
|
+
async function detectCliTool(id) {
|
|
11477
|
+
const meta = AVAILABLE_CLI_TOOLS[id];
|
|
11478
|
+
const probe = meta?.probe ?? id;
|
|
11479
|
+
const path = await probeBin(probe);
|
|
11480
|
+
return {
|
|
11481
|
+
id,
|
|
11482
|
+
probe,
|
|
11483
|
+
installed: path.length > 0,
|
|
11484
|
+
path
|
|
11485
|
+
};
|
|
11486
|
+
}
|
|
11487
|
+
async function detectCliTools(ids) {
|
|
11488
|
+
return Promise.all(ids.map((id) => detectCliTool(id)));
|
|
11489
|
+
}
|
|
11490
|
+
async function findMissingCliTools(ids) {
|
|
11491
|
+
const results = await detectCliTools(ids);
|
|
11492
|
+
return results.filter((r) => !r.installed).map((r) => r.id);
|
|
11493
|
+
}
|
|
11494
|
+
|
|
11495
|
+
// src/cliTools/install.ts
|
|
11496
|
+
init_ui();
|
|
11497
|
+
import chalk6 from "chalk";
|
|
11498
|
+
import inquirer5 from "inquirer";
|
|
11499
|
+
|
|
11500
|
+
// src/cliTools/oneLiner.ts
|
|
11501
|
+
var GROUPABLE_MANAGERS = /* @__PURE__ */ new Set([
|
|
11502
|
+
"brew",
|
|
11503
|
+
"apt",
|
|
11504
|
+
"apt-get",
|
|
11505
|
+
"dnf",
|
|
11506
|
+
"yum",
|
|
11507
|
+
"pacman",
|
|
11508
|
+
"scoop",
|
|
11509
|
+
"cargo"
|
|
11510
|
+
]);
|
|
11511
|
+
var JOIN = " \\\n && ";
|
|
11512
|
+
function parseGroupable(command, manager) {
|
|
11513
|
+
if (!manager || !GROUPABLE_MANAGERS.has(manager)) return null;
|
|
11514
|
+
if (command.includes("&&") || command.includes("|") || command.includes(";")) return null;
|
|
11515
|
+
const re = new RegExp(`^(sudo\\s+)?${manager}\\s+install\\s+(\\S+)$`);
|
|
11516
|
+
const match = command.match(re);
|
|
11517
|
+
if (!match) return null;
|
|
11518
|
+
const sudo = match[1] ?? "";
|
|
11519
|
+
const pkg = match[2];
|
|
11520
|
+
return { prefix: `${sudo}${manager} install`, pkg };
|
|
11521
|
+
}
|
|
11522
|
+
function buildOneLiner(plans) {
|
|
11523
|
+
if (plans.length === 0) return "";
|
|
11524
|
+
const groupOrder = [];
|
|
11525
|
+
const groups = /* @__PURE__ */ new Map();
|
|
11526
|
+
const standalones = [];
|
|
11527
|
+
for (const plan of plans) {
|
|
11528
|
+
if (!plan.command) {
|
|
11529
|
+
standalones.push(`# install ${plan.id} manually: see ${plan.homepage}`);
|
|
11530
|
+
continue;
|
|
11531
|
+
}
|
|
11532
|
+
const parsed = parseGroupable(plan.command, plan.manager);
|
|
11533
|
+
if (!parsed) {
|
|
11534
|
+
standalones.push(plan.command);
|
|
11535
|
+
continue;
|
|
11536
|
+
}
|
|
11537
|
+
const existing = groups.get(parsed.prefix);
|
|
11538
|
+
if (existing) {
|
|
11539
|
+
existing.push(parsed.pkg);
|
|
11540
|
+
} else {
|
|
11541
|
+
groupOrder.push(parsed.prefix);
|
|
11542
|
+
groups.set(parsed.prefix, [parsed.pkg]);
|
|
11543
|
+
}
|
|
11544
|
+
}
|
|
11545
|
+
const chunks = [];
|
|
11546
|
+
for (const prefix of groupOrder) {
|
|
11547
|
+
const pkgs = groups.get(prefix);
|
|
11548
|
+
if (!pkgs) continue;
|
|
11549
|
+
chunks.push(`${prefix} ${pkgs.join(" ")}`);
|
|
11550
|
+
}
|
|
11551
|
+
for (const cmd of standalones) {
|
|
11552
|
+
chunks.push(cmd);
|
|
11553
|
+
}
|
|
11554
|
+
if (chunks.length === 0) return "";
|
|
11555
|
+
return chunks.join(JOIN);
|
|
11556
|
+
}
|
|
11557
|
+
|
|
11558
|
+
// src/cliTools/install.ts
|
|
11559
|
+
function currentOsKey() {
|
|
11560
|
+
if (process.platform === "win32") return "win";
|
|
11561
|
+
if (process.platform === "linux" || process.platform === "freebsd") return "linux";
|
|
11562
|
+
return "mac";
|
|
11563
|
+
}
|
|
11564
|
+
function buildInstallPlan(ids, os = currentOsKey()) {
|
|
11565
|
+
const plans = [];
|
|
11566
|
+
for (const id of ids) {
|
|
11567
|
+
const meta = AVAILABLE_CLI_TOOLS[id];
|
|
11568
|
+
if (!meta) continue;
|
|
11569
|
+
const cmd = meta.install[os]?.[0];
|
|
11570
|
+
plans.push({
|
|
11571
|
+
id: meta.id,
|
|
11572
|
+
probe: meta.probe,
|
|
11573
|
+
manager: cmd?.manager,
|
|
11574
|
+
command: cmd?.command,
|
|
11575
|
+
homepage: meta.homepage
|
|
11576
|
+
});
|
|
11577
|
+
}
|
|
11578
|
+
return plans;
|
|
11579
|
+
}
|
|
11580
|
+
async function offerInstaller(missing, opts = {}) {
|
|
11581
|
+
if (missing.length === 0) return true;
|
|
11582
|
+
const os = opts.os ?? currentOsKey();
|
|
11583
|
+
const interactive = opts.interactive ?? true;
|
|
11584
|
+
const plan = buildInstallPlan(missing, os);
|
|
11585
|
+
const osLabel = os === "mac" ? "macOS" : os === "linux" ? "Linux" : "Windows";
|
|
11586
|
+
console.log("");
|
|
11587
|
+
console.log(chalk6.yellow(`${missing.length} CLI tool${missing.length === 1 ? "" : "s"} not detected on PATH (${osLabel}):`));
|
|
11588
|
+
console.log("");
|
|
11589
|
+
for (const entry of plan) {
|
|
11590
|
+
const header = entry.manager ? `${chalk6.cyan(entry.id)} (${entry.manager})` : chalk6.cyan(entry.id);
|
|
11591
|
+
console.log(` ${header}`);
|
|
11592
|
+
if (entry.command) {
|
|
11593
|
+
console.log(` ${chalk6.gray("$")} ${entry.command}`);
|
|
11594
|
+
} else {
|
|
11595
|
+
console.log(` ${chalk6.gray("see")} ${entry.homepage}`);
|
|
11596
|
+
}
|
|
11597
|
+
}
|
|
11598
|
+
console.log("");
|
|
11599
|
+
console.log(chalk6.gray("hatch3r will not run these commands for you \u2014 copy-paste in your shell."));
|
|
11600
|
+
console.log("");
|
|
11601
|
+
const oneLiner = buildOneLiner(plan);
|
|
11602
|
+
if (oneLiner) {
|
|
11603
|
+
console.log(chalk6.yellow("Or copy-paste this one-liner to install everything at once:"));
|
|
11604
|
+
console.log("");
|
|
11605
|
+
for (const line of oneLiner.split("\n")) {
|
|
11606
|
+
console.log(` ${chalk6.cyan(line)}`);
|
|
11607
|
+
}
|
|
11608
|
+
console.log("");
|
|
11609
|
+
}
|
|
11610
|
+
if (!interactive) return true;
|
|
11611
|
+
const { proceed } = await inquirer5.prompt([
|
|
11612
|
+
{
|
|
11613
|
+
type: "confirm",
|
|
11614
|
+
name: "proceed",
|
|
11615
|
+
message: "Mark these tools as 'install pending' and continue?",
|
|
11616
|
+
default: true
|
|
11617
|
+
}
|
|
11618
|
+
]);
|
|
11619
|
+
return proceed;
|
|
11620
|
+
}
|
|
11621
|
+
function printMissingCliToolsDisclaimer(missing, totalSelected, os = currentOsKey()) {
|
|
11622
|
+
if (missing.length === 0) return;
|
|
11623
|
+
const plan = buildInstallPlan(missing, os);
|
|
11624
|
+
const oneLiner = buildOneLiner(plan);
|
|
11625
|
+
const osLabel = os === "mac" ? "macOS" : os === "linux" ? "Linux" : "Windows";
|
|
11626
|
+
const lines = [
|
|
11627
|
+
`${missing.length} of ${totalSelected} selected CLI tools are missing.`,
|
|
11628
|
+
`hatch3r does NOT install them for you.`,
|
|
11629
|
+
"",
|
|
11630
|
+
`Copy-paste to install everything (${osLabel}):`,
|
|
11631
|
+
"",
|
|
11632
|
+
...oneLiner.split("\n").map((l) => ` ${l}`)
|
|
11633
|
+
];
|
|
11634
|
+
printBox("CLI tools not installed", lines, "warning");
|
|
11635
|
+
}
|
|
11636
|
+
|
|
11637
|
+
// src/cliTools/triggers.ts
|
|
11638
|
+
var DATA_LANGUAGES = /* @__PURE__ */ new Set(["python", "r", "sql"]);
|
|
11639
|
+
var WEB_FRAMEWORKS = /* @__PURE__ */ new Set([
|
|
11640
|
+
"next",
|
|
11641
|
+
"nuxt",
|
|
11642
|
+
"astro",
|
|
11643
|
+
"sveltekit",
|
|
11644
|
+
"remix",
|
|
11645
|
+
"angular",
|
|
11646
|
+
"vue",
|
|
11647
|
+
"react",
|
|
11648
|
+
"svelte"
|
|
11649
|
+
]);
|
|
11650
|
+
function evaluateTriggers(repoInfo) {
|
|
11651
|
+
const active = /* @__PURE__ */ new Set();
|
|
11652
|
+
if (repoInfo.frameworks.some((f) => WEB_FRAMEWORKS.has(f))) {
|
|
11653
|
+
active.add("web-project");
|
|
11654
|
+
}
|
|
11655
|
+
if (repoInfo.languages.some((l) => DATA_LANGUAGES.has(l))) {
|
|
11656
|
+
active.add("data-project");
|
|
11657
|
+
}
|
|
11658
|
+
if (repoInfo.languages.includes("rust")) {
|
|
11659
|
+
active.add("rust-project");
|
|
11660
|
+
}
|
|
11661
|
+
if (repoInfo.languages.includes("python")) {
|
|
11662
|
+
active.add("python-project");
|
|
11663
|
+
}
|
|
11664
|
+
if (process.stdout && process.stdout.isTTY) {
|
|
11665
|
+
active.add("interactive-tty");
|
|
11666
|
+
}
|
|
11667
|
+
return active;
|
|
11668
|
+
}
|
|
11669
|
+
function evaluateTier2Triggers(repoInfo) {
|
|
11670
|
+
const triggers = evaluateTriggers(repoInfo);
|
|
11671
|
+
const ids = /* @__PURE__ */ new Set();
|
|
11672
|
+
for (const trigger of triggers) {
|
|
11673
|
+
for (const id of TIER2_CLI_TOOLS_BY_TRIGGER[trigger]) {
|
|
11674
|
+
ids.add(id);
|
|
11675
|
+
}
|
|
11676
|
+
}
|
|
11677
|
+
return [...ids];
|
|
11678
|
+
}
|
|
11679
|
+
function applyPlatformTriggers(platform, base) {
|
|
11680
|
+
const out = new Set(base);
|
|
11681
|
+
if (platform === "gitlab") {
|
|
11682
|
+
for (const id of TIER2_CLI_TOOLS_BY_TRIGGER["gitlab-remote"]) out.add(id);
|
|
11683
|
+
} else if (platform === "azure-devops") {
|
|
11684
|
+
for (const id of TIER2_CLI_TOOLS_BY_TRIGGER["azure-remote"]) out.add(id);
|
|
11685
|
+
}
|
|
11686
|
+
return [...out];
|
|
11687
|
+
}
|
|
11688
|
+
|
|
10741
11689
|
// src/cli/commands/init.ts
|
|
10742
11690
|
init_integrity();
|
|
10743
11691
|
init_version();
|
|
@@ -10819,6 +11767,15 @@ function validateWorkspaceManifest(data) {
|
|
|
10819
11767
|
if (typeof content.teamSize !== "string") return false;
|
|
10820
11768
|
if (!content.items || typeof content.items !== "object") return false;
|
|
10821
11769
|
}
|
|
11770
|
+
if (defaults.cliTools !== void 0) {
|
|
11771
|
+
if (typeof defaults.cliTools !== "object" || defaults.cliTools === null) return false;
|
|
11772
|
+
const cli = defaults.cliTools;
|
|
11773
|
+
if (typeof cli.enabled !== "boolean") return false;
|
|
11774
|
+
if (!Array.isArray(cli.selected)) return false;
|
|
11775
|
+
for (const id of cli.selected) {
|
|
11776
|
+
if (typeof id !== "string") return false;
|
|
11777
|
+
}
|
|
11778
|
+
}
|
|
10822
11779
|
for (const repo of obj.repos) {
|
|
10823
11780
|
if (!repo || typeof repo !== "object") return false;
|
|
10824
11781
|
const r = repo;
|
|
@@ -10933,7 +11890,30 @@ function resolveRepoConfig(defaults, overrides, protectedIds) {
|
|
|
10933
11890
|
}
|
|
10934
11891
|
}
|
|
10935
11892
|
}
|
|
10936
|
-
return {
|
|
11893
|
+
return {
|
|
11894
|
+
platform,
|
|
11895
|
+
tools,
|
|
11896
|
+
features,
|
|
11897
|
+
mcp,
|
|
11898
|
+
models,
|
|
11899
|
+
contentIds,
|
|
11900
|
+
excludedContent,
|
|
11901
|
+
addedContent,
|
|
11902
|
+
cliTools: defaults.cliTools
|
|
11903
|
+
};
|
|
11904
|
+
}
|
|
11905
|
+
function applyMemberCliToolsOverrides(workspaceDefault, memberLocal, memberExcluded) {
|
|
11906
|
+
if (!workspaceDefault && (!memberLocal || memberLocal.length === 0)) {
|
|
11907
|
+
return void 0;
|
|
11908
|
+
}
|
|
11909
|
+
const base = new Set(workspaceDefault?.selected ?? []);
|
|
11910
|
+
for (const id of memberLocal ?? []) base.add(id);
|
|
11911
|
+
for (const id of memberExcluded ?? []) base.delete(id);
|
|
11912
|
+
const selected = [...base];
|
|
11913
|
+
return {
|
|
11914
|
+
enabled: selected.length > 0,
|
|
11915
|
+
selected
|
|
11916
|
+
};
|
|
10937
11917
|
}
|
|
10938
11918
|
function buildSelectionFromIds(ids, baseSelection, allItems) {
|
|
10939
11919
|
const items = {
|
|
@@ -11163,6 +12143,11 @@ async function syncSingleRepo(workspaceRoot, wsManifest, wsChecksum, repoEntry,
|
|
|
11163
12143
|
gitRepo = existingManifest.repo;
|
|
11164
12144
|
}
|
|
11165
12145
|
if (!gitBranch) gitBranch = "main";
|
|
12146
|
+
const effectiveCliTools = applyMemberCliToolsOverrides(
|
|
12147
|
+
resolved.cliTools,
|
|
12148
|
+
existingManifest?.workspace?.localCliTools,
|
|
12149
|
+
existingManifest?.workspace?.excludedCliTools
|
|
12150
|
+
);
|
|
11166
12151
|
const manifest = createManifest({
|
|
11167
12152
|
platform: gitPlatform ?? resolved.platform,
|
|
11168
12153
|
owner: gitOwner,
|
|
@@ -11174,7 +12159,8 @@ async function syncSingleRepo(workspaceRoot, wsManifest, wsChecksum, repoEntry,
|
|
|
11174
12159
|
features: resolved.features,
|
|
11175
12160
|
mcpServers: resolved.mcp.servers,
|
|
11176
12161
|
content: effectiveSelection,
|
|
11177
|
-
languages: repoInfo.languages
|
|
12162
|
+
languages: repoInfo.languages,
|
|
12163
|
+
cliTools: effectiveCliTools
|
|
11178
12164
|
});
|
|
11179
12165
|
manifest.workspace = {
|
|
11180
12166
|
rootPath: relative4(repoDir, workspaceRoot),
|
|
@@ -11182,7 +12168,9 @@ async function syncSingleRepo(workspaceRoot, wsManifest, wsChecksum, repoEntry,
|
|
|
11182
12168
|
syncVersion: HATCH3R_VERSION,
|
|
11183
12169
|
workspaceChecksum: wsChecksum,
|
|
11184
12170
|
excludedContent: resolved.excludedContent.length > 0 ? resolved.excludedContent : void 0,
|
|
11185
|
-
localContent: existingManifest?.workspace?.localContent
|
|
12171
|
+
localContent: existingManifest?.workspace?.localContent,
|
|
12172
|
+
localCliTools: existingManifest?.workspace?.localCliTools,
|
|
12173
|
+
excludedCliTools: existingManifest?.workspace?.excludedCliTools
|
|
11186
12174
|
};
|
|
11187
12175
|
if (resolved.models) {
|
|
11188
12176
|
manifest.models = resolved.models;
|
|
@@ -11251,6 +12239,82 @@ var CONTENT_ROOT2 = findPackageRoot(__dirname2);
|
|
|
11251
12239
|
var DEFAULT_TOOLS = ["claude"];
|
|
11252
12240
|
var DEFAULT_FEATURE_KEYS = Object.keys(DEFAULT_FEATURES);
|
|
11253
12241
|
var DEFAULT_MCP = ["playwright", "github", "context7"];
|
|
12242
|
+
var HANDOFFS_README_SEED = `# Project Handoffs
|
|
12243
|
+
|
|
12244
|
+
This directory holds active and archived handoff documents surfaced by the
|
|
12245
|
+
\`hatch3r-handoff-loader\` agent at session start and consumed by
|
|
12246
|
+
\`/hatch3r-handoff resume\`.
|
|
12247
|
+
|
|
12248
|
+
## Layout
|
|
12249
|
+
|
|
12250
|
+
- \`active/<id>.md\` \u2014 handoffs in any non-terminal status (open, in-progress, blocked, handed-off, resumed)
|
|
12251
|
+
- \`archived/<id>.md\` \u2014 handoffs in terminal status (completed, expired, superseded)
|
|
12252
|
+
|
|
12253
|
+
## ID scheme
|
|
12254
|
+
|
|
12255
|
+
\`<YYYY-MM-DD>_T<HHmm>_<5hex>_<kebab-slug>\` \u2014 chronologically sortable, collision-safe.
|
|
12256
|
+
|
|
12257
|
+
Example: \`2026-05-17_T1430_a3f2c_issue-42-cache-refactor.md\`.
|
|
12258
|
+
|
|
12259
|
+
## Lifecycle
|
|
12260
|
+
|
|
12261
|
+
- Created by \`/hatch3r-handoff prepare\` or the \`on-context-switch\` hook.
|
|
12262
|
+
- Loaded at session start by \`hatch3r-handoff-loader\`.
|
|
12263
|
+
- Resumed via \`/hatch3r-handoff resume [<id>]\` (lists actives if no id given).
|
|
12264
|
+
- \`expires_after\`: ISO-8601 timestamp; preparer default stamps \`created + 30 days\`.
|
|
12265
|
+
- Archived (never deleted by hatch3r) on completion or expiry.
|
|
12266
|
+
|
|
12267
|
+
## Required frontmatter
|
|
12268
|
+
|
|
12269
|
+
| Field | Type | Notes |
|
|
12270
|
+
| --- | --- | --- |
|
|
12271
|
+
| \`id\` | string | Filename without \`.md\` |
|
|
12272
|
+
| \`type\` | literal \`handoff\` | |
|
|
12273
|
+
| \`created\` | ISO-8601 | Immutable |
|
|
12274
|
+
| \`updated\` | ISO-8601 | Re-stamped on status change |
|
|
12275
|
+
| \`status\` | enum | open \\| in-progress \\| blocked \\| handed-off \\| resumed \\| completed \\| archived |
|
|
12276
|
+
| \`source_agent\` | string | Tool/role that prepared the handoff |
|
|
12277
|
+
| \`target_agent\` | string | \`any\` allowed but warned (avoids handoff loops) |
|
|
12278
|
+
| \`git_ref\` | string | \`branch@sha7\` \u2014 staleness signal |
|
|
12279
|
+
| \`branch\` | string | |
|
|
12280
|
+
| \`confidence\` | 0..1 | |
|
|
12281
|
+
| \`completeness\` | 0..1 | |
|
|
12282
|
+
| \`integrity\` | string | \`sha256:<hex>\` \u2014 SHA-256 of body |
|
|
12283
|
+
|
|
12284
|
+
Optional: \`work_item\` (platform-prefixed: \`gh:owner/repo#42\`, \`ado:org/project:work-item/123\`, \`gl:owner/repo!42\`), \`expires_after\`, \`summary\` (\u2264200 chars), \`requirements\`, \`compaction_count\`, \`hatch3r_version\`, \`tags\`, \`superseded_by\`, \`parent_handoff\`.
|
|
12285
|
+
|
|
12286
|
+
## Body sections (required, in order)
|
|
12287
|
+
|
|
12288
|
+
Wrap the body in user-tier instruction-hierarchy markers:
|
|
12289
|
+
|
|
12290
|
+
\`\`\`markdown
|
|
12291
|
+
--- BEGIN USER-TIER CONTENT: handoff ---
|
|
12292
|
+
|
|
12293
|
+
## Problem (1-3 paragraphs)
|
|
12294
|
+
## Decisions (bullet list)
|
|
12295
|
+
## Work Done (from end-of-session Iteration Summary)
|
|
12296
|
+
## Work Remaining
|
|
12297
|
+
## Blockers
|
|
12298
|
+
## Next Steps (ordered list)
|
|
12299
|
+
## Build & Test Status (table: Check | Status | Notes)
|
|
12300
|
+
## File Manifest (table: Path | Status | Last action)
|
|
12301
|
+
|
|
12302
|
+
--- END USER-TIER CONTENT: handoff ---
|
|
12303
|
+
\`\`\`
|
|
12304
|
+
|
|
12305
|
+
## Caps and validation
|
|
12306
|
+
|
|
12307
|
+
- Body \u2264 50 KB, total file \u2264 60 KB.
|
|
12308
|
+
- Soft cap 25 active handoffs per repo (warn at 20, refuse briefing at 50).
|
|
12309
|
+
- Injection-pattern scan (P-LEARN-01..05) at write and read; reuses learnings catalog.
|
|
12310
|
+
- Integrity hash mismatch downgrades confidence to low; included with warning.
|
|
12311
|
+
|
|
12312
|
+
## Cross-tool portability
|
|
12313
|
+
|
|
12314
|
+
Handoffs are plain Markdown \u2014 readable by humans and any AI tool. Tool-specific adapters (Cursor, Claude, Copilot, etc.) surface active handoffs in their native context file on session-start so a handoff written from one tool resumes cleanly in another.
|
|
12315
|
+
|
|
12316
|
+
See \`agents/hatch3r-handoff-loader.md\`, \`skills/hatch3r-handoff-resume/SKILL.md\`, and \`rules/hatch3r-handoff-readiness.md\` for the full protocols.
|
|
12317
|
+
`;
|
|
11254
12318
|
var LEARNINGS_README_SEED = `# Project Learnings
|
|
11255
12319
|
|
|
11256
12320
|
This directory holds project-specific learnings surfaced by the
|
|
@@ -11357,7 +12421,7 @@ function selectionHasBoardContent(selection) {
|
|
|
11357
12421
|
function warnBoardPrerequisites(selection) {
|
|
11358
12422
|
if (!selectionHasBoardContent(selection)) return;
|
|
11359
12423
|
info(
|
|
11360
|
-
`Board commands selected. Prerequisites: ${
|
|
12424
|
+
`Board commands selected. Prerequisites: ${chalk7.bold("GitHub Projects V2")} must be enabled and your PAT needs the ${chalk7.bold("project")} scope. See ${chalk7.dim("https://docs.github.com/en/issues/planning-and-tracking-with-projects")}`
|
|
11361
12425
|
);
|
|
11362
12426
|
}
|
|
11363
12427
|
function languagesForSelection(repoInfo) {
|
|
@@ -11396,7 +12460,7 @@ async function runInit(options) {
|
|
|
11396
12460
|
}
|
|
11397
12461
|
}
|
|
11398
12462
|
async function runInitInner(options) {
|
|
11399
|
-
const { rootDir, platform, owner, repo, namespace, project, defaultBranch, tools, features, mcpServers, repoInfo, contentSelection, worktreeEnabled, customization } = options;
|
|
12463
|
+
const { rootDir, platform, owner, repo, namespace, project, defaultBranch, tools, features, mcpServers, repoInfo, contentSelection, worktreeEnabled, customization, cliTools } = options;
|
|
11400
12464
|
const skipInitPrompts = options.yes === true;
|
|
11401
12465
|
const agentsDir = join27(rootDir, AGENTS_DIR);
|
|
11402
12466
|
const totalSteps = 4;
|
|
@@ -11427,6 +12491,18 @@ async function runInitInner(options) {
|
|
|
11427
12491
|
throw err;
|
|
11428
12492
|
}
|
|
11429
12493
|
}
|
|
12494
|
+
await mkdir8(join27(agentsDir, "handoffs", "active"), { recursive: true });
|
|
12495
|
+
await mkdir8(join27(agentsDir, "handoffs", "archived"), { recursive: true });
|
|
12496
|
+
const handoffsReadmePath = join27(agentsDir, "handoffs", "README.md");
|
|
12497
|
+
try {
|
|
12498
|
+
await access9(handoffsReadmePath);
|
|
12499
|
+
} catch (err) {
|
|
12500
|
+
if (err.code === "ENOENT") {
|
|
12501
|
+
await safeWriteFile(handoffsReadmePath, HANDOFFS_README_SEED);
|
|
12502
|
+
} else {
|
|
12503
|
+
throw err;
|
|
12504
|
+
}
|
|
12505
|
+
}
|
|
11430
12506
|
const mcpPath = join27(agentsDir, "mcp", "mcp.json");
|
|
11431
12507
|
await filterMcpJsonOnDisk(mcpPath, new Set(mcpServers));
|
|
11432
12508
|
const canonicalAgentsMd = await generateCanonicalAgentsMd(agentsDir);
|
|
@@ -11435,7 +12511,7 @@ async function runInitInner(options) {
|
|
|
11435
12511
|
const s2 = createSpinner(step(2, totalSteps, "Preparing manifest..."));
|
|
11436
12512
|
s2.start();
|
|
11437
12513
|
const effectiveCustomization = customization ?? existingManifest?.customization;
|
|
11438
|
-
const manifest = createManifest({ platform, owner, repo, namespace, project, defaultBranch, tools, features, mcpServers, content: contentSelection, languages: repoInfo.languages, worktreeEnabled, customization: effectiveCustomization });
|
|
12514
|
+
const manifest = createManifest({ platform, owner, repo, namespace, project, defaultBranch, tools, features, mcpServers, content: contentSelection, languages: repoInfo.languages, worktreeEnabled, customization: effectiveCustomization, cliTools });
|
|
11439
12515
|
const preservedFields = options.preservedManifestFields ?? (existingManifest ? extractPreservedManifestFields(existingManifest) : void 0);
|
|
11440
12516
|
if (preservedFields) {
|
|
11441
12517
|
applyPreservedManifestFields(manifest, preservedFields);
|
|
@@ -11544,18 +12620,22 @@ async function runInitInner(options) {
|
|
|
11544
12620
|
const isGreenfield = repoInfo.languages.length === 1 && repoInfo.languages[0] === "unknown" && repoInfo.existingTools.length === 0 && !repoInfo.hasExistingAgents;
|
|
11545
12621
|
summaryLines.push("");
|
|
11546
12622
|
if (isGreenfield) {
|
|
11547
|
-
summaryLines.push(`${
|
|
12623
|
+
summaryLines.push(`${chalk7.cyan("\u2192")} Run ${chalk7.bold(formatCommandHint(tools, "project-spec"))} to define your new project`);
|
|
11548
12624
|
} else {
|
|
11549
|
-
summaryLines.push(`${
|
|
12625
|
+
summaryLines.push(`${chalk7.cyan("\u2192")} Run ${chalk7.bold(formatCommandHint(tools, "codebase-map"))} to map your existing codebase`);
|
|
11550
12626
|
}
|
|
11551
12627
|
if (envResult && envResult.newVars.length > 0) {
|
|
11552
12628
|
summaryLines.push("");
|
|
11553
|
-
summaryLines.push(`${
|
|
11554
|
-
summaryLines.push(` Then run: ${
|
|
12629
|
+
summaryLines.push(`${chalk7.yellow("!")} Add your secrets to ${chalk7.bold(".env.mcp")}: ${envResult.newVars.join(", ")}`);
|
|
12630
|
+
summaryLines.push(` Then run: ${chalk7.dim(getSourceEnvMcpCommand())}`);
|
|
11555
12631
|
}
|
|
11556
12632
|
printBox("Hatch complete", summaryLines, "success");
|
|
12633
|
+
if (cliTools && cliTools.selected.length > 0) {
|
|
12634
|
+
const finalMissing = await findMissingCliTools(cliTools.selected);
|
|
12635
|
+
printMissingCliToolsDisclaimer(finalMissing, cliTools.selected.length);
|
|
12636
|
+
}
|
|
11557
12637
|
if (!skipInitPrompts) {
|
|
11558
|
-
const { create } = await
|
|
12638
|
+
const { create } = await inquirer6.prompt([{
|
|
11559
12639
|
type: "confirm",
|
|
11560
12640
|
name: "create",
|
|
11561
12641
|
message: "Would you like to create your first custom artifact now?",
|
|
@@ -11590,7 +12670,7 @@ async function checkExisting(rootDir, skipPrompt, newSelection) {
|
|
|
11590
12670
|
}
|
|
11591
12671
|
}
|
|
11592
12672
|
}
|
|
11593
|
-
const { proceed } = await
|
|
12673
|
+
const { proceed } = await inquirer6.prompt([
|
|
11594
12674
|
{
|
|
11595
12675
|
type: "confirm",
|
|
11596
12676
|
name: "proceed",
|
|
@@ -11599,7 +12679,7 @@ async function checkExisting(rootDir, skipPrompt, newSelection) {
|
|
|
11599
12679
|
}
|
|
11600
12680
|
]);
|
|
11601
12681
|
if (!proceed) {
|
|
11602
|
-
console.log(
|
|
12682
|
+
console.log(chalk7.dim("\n Init cancelled.\n"));
|
|
11603
12683
|
throw new HatchError("Init cancelled.", 0);
|
|
11604
12684
|
}
|
|
11605
12685
|
}
|
|
@@ -11636,10 +12716,10 @@ async function initCommand(opts = {}) {
|
|
|
11636
12716
|
const detectedRepos = await detectSubRepos(rootDir);
|
|
11637
12717
|
if (opts.yes) {
|
|
11638
12718
|
opts.workspace = true;
|
|
11639
|
-
info(
|
|
12719
|
+
info(chalk7.dim(`No git repo found. ${detectedRepos.length} git repo(s) detected in subdirectories \u2014 initializing as workspace.`));
|
|
11640
12720
|
} else {
|
|
11641
12721
|
info(`No git repo found, but ${detectedRepos.length} git repo(s) detected in subdirectories.`);
|
|
11642
|
-
const { useWorkspace } = await
|
|
12722
|
+
const { useWorkspace } = await inquirer6.prompt([
|
|
11643
12723
|
{
|
|
11644
12724
|
type: "confirm",
|
|
11645
12725
|
name: "useWorkspace",
|
|
@@ -11671,7 +12751,7 @@ async function initCommand(opts = {}) {
|
|
|
11671
12751
|
}
|
|
11672
12752
|
if (repoInfo.isMonorepo) detected.push("monorepo");
|
|
11673
12753
|
if (detected.length > 0) {
|
|
11674
|
-
info(
|
|
12754
|
+
info(chalk7.dim(`Detected: ${detected.join(", ")}`));
|
|
11675
12755
|
}
|
|
11676
12756
|
if (opts.yes) {
|
|
11677
12757
|
const remoteUrl2 = getGitRemoteUrl();
|
|
@@ -11686,7 +12766,7 @@ async function initCommand(opts = {}) {
|
|
|
11686
12766
|
const invalid = rawTools.filter((t) => !VALID_TOOLS.has(t));
|
|
11687
12767
|
if (invalid.length > 0) {
|
|
11688
12768
|
error(`Invalid tool(s): ${invalid.join(", ")}`);
|
|
11689
|
-
console.log(
|
|
12769
|
+
console.log(chalk7.dim(` Valid tools: ${[...VALID_TOOLS].join(", ")}`));
|
|
11690
12770
|
throw new HatchError(`Invalid tool(s): ${invalid.join(", ")}`, 1, "VALIDATION_ERROR");
|
|
11691
12771
|
}
|
|
11692
12772
|
tools2 = rawTools;
|
|
@@ -11698,7 +12778,18 @@ async function initCommand(opts = {}) {
|
|
|
11698
12778
|
const worktreeEnabled2 = opts.worktree ?? tools2.some((t) => WORKTREE_CAPABLE_TOOLS.has(t));
|
|
11699
12779
|
const features2 = { ...DEFAULT_FEATURES };
|
|
11700
12780
|
const platformMcp = PLATFORM_MCP_SERVER[platform2];
|
|
11701
|
-
const mcpServers2 = features2.mcp ? Array.from(/* @__PURE__ */ new Set([platformMcp, ...DEFAULT_MCP.filter((s) => s !== "github")])) : [];
|
|
12781
|
+
const mcpServers2 = features2.mcp && opts.mcp ? Array.from(/* @__PURE__ */ new Set([platformMcp, ...DEFAULT_MCP.filter((s) => s !== "github")])) : [];
|
|
12782
|
+
let cliToolsConfig2;
|
|
12783
|
+
if (opts.noCliTools) {
|
|
12784
|
+
cliToolsConfig2 = { enabled: false, selected: [] };
|
|
12785
|
+
} else {
|
|
12786
|
+
const explicit = resolveCliToolsFlag(opts.cliTools, repoInfo, platform2);
|
|
12787
|
+
const selected = explicit ?? Array.from(/* @__PURE__ */ new Set([
|
|
12788
|
+
...DEFAULT_CLI_TOOLS,
|
|
12789
|
+
...applyPlatformTriggers(platform2, evaluateTier2Triggers(repoInfo))
|
|
12790
|
+
]));
|
|
12791
|
+
cliToolsConfig2 = { enabled: selected.length > 0, selected };
|
|
12792
|
+
}
|
|
11702
12793
|
const defaultBranch2 = parseGitDefaultBranch();
|
|
11703
12794
|
const isGreenfield = repoInfo.languages.length === 1 && repoInfo.languages[0] === "unknown" && repoInfo.existingTools.length === 0 && !repoInfo.hasExistingAgents;
|
|
11704
12795
|
const presetId = validateFlag(opts.preset, ["minimal", "standard", "full"], "full", "preset");
|
|
@@ -11714,13 +12805,13 @@ async function initCommand(opts = {}) {
|
|
|
11714
12805
|
}
|
|
11715
12806
|
warnBoardPrerequisites(contentSelection2);
|
|
11716
12807
|
await checkExisting(rootDir, true, contentSelection2);
|
|
11717
|
-
await runInit({ rootDir, platform: platform2, owner: owner2, repo: repo2, namespace: namespace2, project: project2, defaultBranch: defaultBranch2, tools: tools2, features: features2, mcpServers: mcpServers2, repoInfo, contentSelection: contentSelection2, worktreeEnabled: worktreeEnabled2, yes: true });
|
|
12808
|
+
await runInit({ rootDir, platform: platform2, owner: owner2, repo: repo2, namespace: namespace2, project: project2, defaultBranch: defaultBranch2, tools: tools2, features: features2, mcpServers: mcpServers2, repoInfo, contentSelection: contentSelection2, worktreeEnabled: worktreeEnabled2, cliTools: cliToolsConfig2, yes: true });
|
|
11718
12809
|
return;
|
|
11719
12810
|
}
|
|
11720
12811
|
console.log();
|
|
11721
12812
|
const remoteUrl = getGitRemoteUrl();
|
|
11722
12813
|
const detectedPlatform = detectPlatformFromRemote(remoteUrl);
|
|
11723
|
-
const platformAnswer = await
|
|
12814
|
+
const platformAnswer = await inquirer6.prompt([
|
|
11724
12815
|
{
|
|
11725
12816
|
type: "select",
|
|
11726
12817
|
name: "platform",
|
|
@@ -11739,7 +12830,7 @@ async function initCommand(opts = {}) {
|
|
|
11739
12830
|
let namespace;
|
|
11740
12831
|
let project;
|
|
11741
12832
|
if (platform === "azure-devops") {
|
|
11742
|
-
const adoAnswers = await
|
|
12833
|
+
const adoAnswers = await inquirer6.prompt([
|
|
11743
12834
|
{ type: "input", name: "org", message: "Azure DevOps organization:", default: remote.owner || void 0 },
|
|
11744
12835
|
{ type: "input", name: "project", message: "Azure DevOps project:" },
|
|
11745
12836
|
{ type: "input", name: "repo", message: "Repository name:", default: remote.repo || void 0 }
|
|
@@ -11749,7 +12840,7 @@ async function initCommand(opts = {}) {
|
|
|
11749
12840
|
namespace = owner;
|
|
11750
12841
|
project = sanitizeInput(adoAnswers.project);
|
|
11751
12842
|
} else if (platform === "gitlab") {
|
|
11752
|
-
const glAnswers = await
|
|
12843
|
+
const glAnswers = await inquirer6.prompt([
|
|
11753
12844
|
{ type: "input", name: "namespace", message: "GitLab namespace (group or username):", default: remote.owner || void 0 },
|
|
11754
12845
|
{ type: "input", name: "project", message: "Project name:", default: remote.repo || void 0 }
|
|
11755
12846
|
]);
|
|
@@ -11758,7 +12849,7 @@ async function initCommand(opts = {}) {
|
|
|
11758
12849
|
namespace = owner;
|
|
11759
12850
|
project = repo;
|
|
11760
12851
|
} else {
|
|
11761
|
-
const repoAnswers = await
|
|
12852
|
+
const repoAnswers = await inquirer6.prompt([
|
|
11762
12853
|
{ type: "input", name: "owner", message: "GitHub owner (org or username):", default: remote.owner || void 0 },
|
|
11763
12854
|
{ type: "input", name: "repo", message: "Repository name:", default: remote.repo || void 0 }
|
|
11764
12855
|
]);
|
|
@@ -11768,7 +12859,7 @@ async function initCommand(opts = {}) {
|
|
|
11768
12859
|
project = repo;
|
|
11769
12860
|
}
|
|
11770
12861
|
const defaultBranchDefault = parseGitDefaultBranch();
|
|
11771
|
-
const defaultBranchAnswers = await
|
|
12862
|
+
const defaultBranchAnswers = await inquirer6.prompt([
|
|
11772
12863
|
{
|
|
11773
12864
|
type: "input",
|
|
11774
12865
|
name: "defaultBranch",
|
|
@@ -11789,7 +12880,7 @@ async function initCommand(opts = {}) {
|
|
|
11789
12880
|
const isAutoGreenfield = repoInfo.languages.length === 1 && repoInfo.languages[0] === "unknown" && repoInfo.existingTools.length === 0 && !repoInfo.hasExistingAgents;
|
|
11790
12881
|
const greenfieldExcl = countProjectTypeExclusions("greenfield", filterIndex.items);
|
|
11791
12882
|
const brownfieldExcl = countProjectTypeExclusions("brownfield", filterIndex.items);
|
|
11792
|
-
const projectTypeAnswer = await
|
|
12883
|
+
const projectTypeAnswer = await inquirer6.prompt([
|
|
11793
12884
|
{
|
|
11794
12885
|
type: "select",
|
|
11795
12886
|
name: "projectType",
|
|
@@ -11803,7 +12894,7 @@ async function initCommand(opts = {}) {
|
|
|
11803
12894
|
]);
|
|
11804
12895
|
const projectType = projectTypeAnswer.projectType;
|
|
11805
12896
|
const soloExcl = countTeamSizeExclusions("solo", filterIndex.items);
|
|
11806
|
-
const teamSizeAnswer = await
|
|
12897
|
+
const teamSizeAnswer = await inquirer6.prompt([
|
|
11807
12898
|
{
|
|
11808
12899
|
type: "select",
|
|
11809
12900
|
name: "teamSize",
|
|
@@ -11817,7 +12908,7 @@ async function initCommand(opts = {}) {
|
|
|
11817
12908
|
]);
|
|
11818
12909
|
const teamSize = teamSizeAnswer.teamSize;
|
|
11819
12910
|
const totalItems = filterIndex.items.length;
|
|
11820
|
-
const presetAnswer = await
|
|
12911
|
+
const presetAnswer = await inquirer6.prompt([
|
|
11821
12912
|
{
|
|
11822
12913
|
type: "select",
|
|
11823
12914
|
name: "preset",
|
|
@@ -11836,7 +12927,7 @@ async function initCommand(opts = {}) {
|
|
|
11836
12927
|
}
|
|
11837
12928
|
]);
|
|
11838
12929
|
const selectedPreset = getPreset(presetAnswer.preset);
|
|
11839
|
-
const wslTheme = isWSL() ? { icon: { checked:
|
|
12930
|
+
const wslTheme = isWSL() ? { icon: { checked: chalk7.green("[x]"), unchecked: "[ ]", cursor: ">" } } : void 0;
|
|
11840
12931
|
let customSelections;
|
|
11841
12932
|
if (selectedPreset.id === "custom") {
|
|
11842
12933
|
const contentIndex = filterIndex;
|
|
@@ -11844,7 +12935,7 @@ async function initCommand(opts = {}) {
|
|
|
11844
12935
|
contentIndex.items,
|
|
11845
12936
|
(item) => item.protected || item.tags.includes("core")
|
|
11846
12937
|
);
|
|
11847
|
-
const customAnswer = await
|
|
12938
|
+
const customAnswer = await inquirer6.prompt([
|
|
11848
12939
|
{
|
|
11849
12940
|
type: "checkbox",
|
|
11850
12941
|
name: "items",
|
|
@@ -11856,7 +12947,7 @@ async function initCommand(opts = {}) {
|
|
|
11856
12947
|
customSelections = customAnswer.items;
|
|
11857
12948
|
}
|
|
11858
12949
|
const toolDefaults = repoInfo.existingTools.length > 0 ? repoInfo.existingTools : DEFAULT_TOOLS;
|
|
11859
|
-
const toolAnswers = await
|
|
12950
|
+
const toolAnswers = await inquirer6.prompt([
|
|
11860
12951
|
{
|
|
11861
12952
|
type: "checkbox",
|
|
11862
12953
|
name: "tools",
|
|
@@ -11872,7 +12963,7 @@ async function initCommand(opts = {}) {
|
|
|
11872
12963
|
if (opts.worktree !== void 0) {
|
|
11873
12964
|
worktreeEnabled = opts.worktree;
|
|
11874
12965
|
} else if (hasWorktreeTool) {
|
|
11875
|
-
const wtAnswer = await
|
|
12966
|
+
const wtAnswer = await inquirer6.prompt([{
|
|
11876
12967
|
type: "confirm",
|
|
11877
12968
|
name: "enabled",
|
|
11878
12969
|
message: "Enable worktree file isolation (for parallel agent sessions)?",
|
|
@@ -11882,14 +12973,50 @@ async function initCommand(opts = {}) {
|
|
|
11882
12973
|
} else {
|
|
11883
12974
|
worktreeEnabled = false;
|
|
11884
12975
|
}
|
|
12976
|
+
const tier2Suggested = Array.from(/* @__PURE__ */ new Set([
|
|
12977
|
+
...evaluateTier2Triggers(repoInfo),
|
|
12978
|
+
...applyPlatformTriggers(platform, [])
|
|
12979
|
+
]));
|
|
12980
|
+
const selectedCliTools = await pickCliTools({
|
|
12981
|
+
tier2Suggested,
|
|
12982
|
+
wslTheme
|
|
12983
|
+
});
|
|
12984
|
+
if (selectedCliTools.length > 0) {
|
|
12985
|
+
const detectSpinner2 = createSpinner(`Detecting ${selectedCliTools.length} CLI tool(s)...`);
|
|
12986
|
+
detectSpinner2.start();
|
|
12987
|
+
const missing = await findMissingCliTools(selectedCliTools);
|
|
12988
|
+
if (missing.length === 0) {
|
|
12989
|
+
detectSpinner2.succeed(`All ${selectedCliTools.length} CLI tool(s) detected on PATH`);
|
|
12990
|
+
} else {
|
|
12991
|
+
detectSpinner2.warn(`${selectedCliTools.length - missing.length}/${selectedCliTools.length} CLI tool(s) detected; ${missing.length} missing`);
|
|
12992
|
+
await offerInstaller(missing, { interactive: true });
|
|
12993
|
+
}
|
|
12994
|
+
const cliEnvVars = [];
|
|
12995
|
+
for (const id of selectedCliTools) {
|
|
12996
|
+
const notes = CLI_TOOL_SECRET_NOTES[id];
|
|
12997
|
+
if (notes && notes.length > 0) {
|
|
12998
|
+
cliEnvVars.push(`${id}: ${notes.join(", ")}`);
|
|
12999
|
+
}
|
|
13000
|
+
}
|
|
13001
|
+
if (cliEnvVars.length > 0) {
|
|
13002
|
+
info(chalk7.dim("CLI tool environment variables required:"));
|
|
13003
|
+
for (const note of cliEnvVars) {
|
|
13004
|
+
info(chalk7.dim(` ${note}`));
|
|
13005
|
+
}
|
|
13006
|
+
}
|
|
13007
|
+
}
|
|
13008
|
+
const cliToolsConfig = {
|
|
13009
|
+
enabled: selectedCliTools.length > 0,
|
|
13010
|
+
selected: selectedCliTools
|
|
13011
|
+
};
|
|
11885
13012
|
const secretNotes = tools.map((t) => TOOL_SECRET_NOTES[t]).filter(Boolean);
|
|
11886
13013
|
if (secretNotes.length > 0) {
|
|
11887
|
-
info(
|
|
13014
|
+
info(chalk7.dim("MCP secret loading by tool:"));
|
|
11888
13015
|
for (const note of secretNotes) {
|
|
11889
|
-
info(
|
|
13016
|
+
info(chalk7.dim(` ${note}`));
|
|
11890
13017
|
}
|
|
11891
13018
|
}
|
|
11892
|
-
const featureAnswers = await
|
|
13019
|
+
const featureAnswers = await inquirer6.prompt([
|
|
11893
13020
|
{
|
|
11894
13021
|
type: "checkbox",
|
|
11895
13022
|
name: "features",
|
|
@@ -11906,23 +13033,9 @@ async function initCommand(opts = {}) {
|
|
|
11906
13033
|
}
|
|
11907
13034
|
let mcpServers = [];
|
|
11908
13035
|
if (features.mcp) {
|
|
11909
|
-
const
|
|
11910
|
-
|
|
11911
|
-
|
|
11912
|
-
);
|
|
11913
|
-
const mcpAnswers = await inquirer4.prompt([
|
|
11914
|
-
{
|
|
11915
|
-
type: "checkbox",
|
|
11916
|
-
name: "mcp",
|
|
11917
|
-
message: "Select MCP servers:",
|
|
11918
|
-
choices: MCP_CHOICES,
|
|
11919
|
-
default: defaultMcpForPlatform,
|
|
11920
|
-
...wslTheme && { theme: wslTheme }
|
|
11921
|
-
}
|
|
11922
|
-
]);
|
|
11923
|
-
mcpServers = mcpAnswers.mcp ?? [];
|
|
11924
|
-
if (!mcpServers.includes(platformMcp)) {
|
|
11925
|
-
mcpServers.unshift(platformMcp);
|
|
13036
|
+
const proceedMcp = await confirmMcpGate({ hasExisting: false, defaultYes: false });
|
|
13037
|
+
if (proceedMcp) {
|
|
13038
|
+
mcpServers = await pickMcpServers({ platform, wslTheme });
|
|
11926
13039
|
}
|
|
11927
13040
|
}
|
|
11928
13041
|
const contentSelection = resolveSelection(selectedPreset, projectType, teamSize, filterIndex, customSelections, projectLanguages);
|
|
@@ -11932,7 +13045,7 @@ async function initCommand(opts = {}) {
|
|
|
11932
13045
|
}
|
|
11933
13046
|
warnBoardPrerequisites(contentSelection);
|
|
11934
13047
|
await checkExisting(rootDir, false, contentSelection);
|
|
11935
|
-
await runInit({ rootDir, platform, owner, repo, namespace, project, defaultBranch, tools, features, mcpServers, repoInfo, contentSelection, worktreeEnabled, yes: false });
|
|
13048
|
+
await runInit({ rootDir, platform, owner, repo, namespace, project, defaultBranch, tools, features, mcpServers, repoInfo, contentSelection, worktreeEnabled, cliTools: cliToolsConfig, yes: false });
|
|
11936
13049
|
}
|
|
11937
13050
|
async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
11938
13051
|
const headless = !!opts.yes;
|
|
@@ -11945,13 +13058,21 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
11945
13058
|
const tools2 = resolveToolsFromOpts(opts.tools, repoInfo);
|
|
11946
13059
|
const features2 = { ...DEFAULT_FEATURES };
|
|
11947
13060
|
const platformMcp = PLATFORM_MCP_SERVER[platform2];
|
|
11948
|
-
const mcpServers2 = features2.mcp ? Array.from(/* @__PURE__ */ new Set([platformMcp, ...DEFAULT_MCP.filter((s) => s !== "github")])) : [];
|
|
13061
|
+
const mcpServers2 = features2.mcp && opts.mcp ? Array.from(/* @__PURE__ */ new Set([platformMcp, ...DEFAULT_MCP.filter((s) => s !== "github")])) : [];
|
|
13062
|
+
const cliToolsBase = opts.noCliTools ? { enabled: false, selected: [] } : (() => {
|
|
13063
|
+
const explicit = resolveCliToolsFlag(opts.cliTools, repoInfo, platform2);
|
|
13064
|
+
const selected = explicit ?? Array.from(/* @__PURE__ */ new Set([
|
|
13065
|
+
...DEFAULT_CLI_TOOLS,
|
|
13066
|
+
...applyPlatformTriggers(platform2, evaluateTier2Triggers(repoInfo))
|
|
13067
|
+
]));
|
|
13068
|
+
return { enabled: selected.length > 0, selected };
|
|
13069
|
+
})();
|
|
11949
13070
|
const index = await buildContentIndex(CONTENT_ROOT2);
|
|
11950
13071
|
const projectLanguages = languagesForSelection(repoInfo);
|
|
11951
13072
|
const contentSelection2 = resolveSelection(getPreset("full"), "brownfield", "solo", index, void 0, projectLanguages);
|
|
11952
13073
|
const wsManifest2 = createWorkspaceManifest(
|
|
11953
13074
|
basename2(rootDir) || "workspace",
|
|
11954
|
-
{ platform: platform2, tools: tools2, features: features2, mcp: { servers: mcpServers2 }, content: contentSelection2 },
|
|
13075
|
+
{ platform: platform2, tools: tools2, features: features2, mcp: { servers: mcpServers2 }, cliTools: cliToolsBase, content: contentSelection2 },
|
|
11955
13076
|
[],
|
|
11956
13077
|
"manual"
|
|
11957
13078
|
);
|
|
@@ -11964,20 +13085,20 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
11964
13085
|
}));
|
|
11965
13086
|
wsSpinner.succeed(`Workspace: ${detectedRepos.length} repo(s) detected`);
|
|
11966
13087
|
console.log();
|
|
11967
|
-
console.log(
|
|
13088
|
+
console.log(chalk7.dim(" Repo Platform Owner/Repo Branch"));
|
|
11968
13089
|
for (const r of enriched) {
|
|
11969
13090
|
const name = (r.name ?? r.path).padEnd(16);
|
|
11970
13091
|
if (r.owner && r.repo) {
|
|
11971
13092
|
const platLabel = PLATFORM_DISPLAY_NAMES[r.platform].padEnd(14);
|
|
11972
13093
|
const identity = `${r.owner}/${r.repo}`.padEnd(32);
|
|
11973
|
-
console.log(` ${name}${
|
|
13094
|
+
console.log(` ${name}${chalk7.dim(platLabel)}${chalk7.dim(identity)}${chalk7.dim(r.defaultBranch)}`);
|
|
11974
13095
|
} else {
|
|
11975
|
-
console.log(` ${name}${
|
|
13096
|
+
console.log(` ${name}${chalk7.dim("(no remote detected)")}`);
|
|
11976
13097
|
}
|
|
11977
13098
|
}
|
|
11978
13099
|
console.log();
|
|
11979
13100
|
if (!headless) {
|
|
11980
|
-
const { acceptIdentity } = await
|
|
13101
|
+
const { acceptIdentity } = await inquirer6.prompt([
|
|
11981
13102
|
{
|
|
11982
13103
|
type: "confirm",
|
|
11983
13104
|
name: "acceptIdentity",
|
|
@@ -11987,9 +13108,9 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
11987
13108
|
]);
|
|
11988
13109
|
if (!acceptIdentity) {
|
|
11989
13110
|
for (const r of enriched) {
|
|
11990
|
-
console.log(
|
|
13111
|
+
console.log(chalk7.bold(`
|
|
11991
13112
|
${r.name ?? r.path}:`));
|
|
11992
|
-
const identity = await
|
|
13113
|
+
const identity = await inquirer6.prompt([
|
|
11993
13114
|
{ type: "input", name: "owner", message: " Owner:", default: r.owner || void 0 },
|
|
11994
13115
|
{ type: "input", name: "repo", message: " Repo:", default: r.repo || void 0 },
|
|
11995
13116
|
{
|
|
@@ -12018,12 +13139,23 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
12018
13139
|
let mcpServers;
|
|
12019
13140
|
let contentSelection;
|
|
12020
13141
|
let worktreeEnabled;
|
|
13142
|
+
let wsCliTools;
|
|
12021
13143
|
if (headless) {
|
|
12022
13144
|
tools = resolveToolsFromOpts(opts.tools, repoInfo);
|
|
12023
13145
|
worktreeEnabled = opts.worktree ?? tools.some((t) => WORKTREE_CAPABLE_TOOLS.has(t));
|
|
12024
13146
|
features = { ...DEFAULT_FEATURES };
|
|
12025
13147
|
const platformMcp = PLATFORM_MCP_SERVER[platform];
|
|
12026
|
-
mcpServers = features.mcp ? Array.from(/* @__PURE__ */ new Set([platformMcp, ...DEFAULT_MCP.filter((s) => s !== "github")])) : [];
|
|
13148
|
+
mcpServers = features.mcp && opts.mcp ? Array.from(/* @__PURE__ */ new Set([platformMcp, ...DEFAULT_MCP.filter((s) => s !== "github")])) : [];
|
|
13149
|
+
if (opts.noCliTools) {
|
|
13150
|
+
wsCliTools = { enabled: false, selected: [] };
|
|
13151
|
+
} else {
|
|
13152
|
+
const explicit = resolveCliToolsFlag(opts.cliTools, repoInfo, platform);
|
|
13153
|
+
const selected = explicit ?? Array.from(/* @__PURE__ */ new Set([
|
|
13154
|
+
...DEFAULT_CLI_TOOLS,
|
|
13155
|
+
...applyPlatformTriggers(platform, evaluateTier2Triggers(repoInfo))
|
|
13156
|
+
]));
|
|
13157
|
+
wsCliTools = { enabled: selected.length > 0, selected };
|
|
13158
|
+
}
|
|
12027
13159
|
const isGreenfield = repoInfo.languages.length === 1 && repoInfo.languages[0] === "unknown" && repoInfo.existingTools.length === 0 && !repoInfo.hasExistingAgents;
|
|
12028
13160
|
const presetId = validateFlag(opts.preset, ["minimal", "standard", "full"], "full", "preset");
|
|
12029
13161
|
const projectType = validateFlag(opts.projectType, ["greenfield", "brownfield"], isGreenfield ? "greenfield" : "brownfield", "project-type");
|
|
@@ -12033,13 +13165,13 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
12033
13165
|
const projectLanguages = languagesForSelection(repoInfo);
|
|
12034
13166
|
contentSelection = resolveSelection(preset, projectType, teamSize, index, void 0, projectLanguages);
|
|
12035
13167
|
} else {
|
|
12036
|
-
const wslTheme = isWSL() ? { icon: { checked:
|
|
13168
|
+
const wslTheme = isWSL() ? { icon: { checked: chalk7.green("[x]"), unchecked: "[ ]", cursor: ">" } } : void 0;
|
|
12037
13169
|
const wsFilterIndex = await buildContentIndex(CONTENT_ROOT2);
|
|
12038
13170
|
const projectLanguages = languagesForSelection(repoInfo);
|
|
12039
13171
|
const isAutoGreenfield = repoInfo.languages.length === 1 && repoInfo.languages[0] === "unknown" && repoInfo.existingTools.length === 0 && !repoInfo.hasExistingAgents;
|
|
12040
13172
|
const wsGreenfieldExcl = countProjectTypeExclusions("greenfield", wsFilterIndex.items);
|
|
12041
13173
|
const wsBrownfieldExcl = countProjectTypeExclusions("brownfield", wsFilterIndex.items);
|
|
12042
|
-
const projectTypeAnswer = await
|
|
13174
|
+
const projectTypeAnswer = await inquirer6.prompt([
|
|
12043
13175
|
{
|
|
12044
13176
|
type: "select",
|
|
12045
13177
|
name: "projectType",
|
|
@@ -12053,7 +13185,7 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
12053
13185
|
]);
|
|
12054
13186
|
const projectType = projectTypeAnswer.projectType;
|
|
12055
13187
|
const wsSoloExcl = countTeamSizeExclusions("solo", wsFilterIndex.items);
|
|
12056
|
-
const teamSizeAnswer = await
|
|
13188
|
+
const teamSizeAnswer = await inquirer6.prompt([
|
|
12057
13189
|
{
|
|
12058
13190
|
type: "select",
|
|
12059
13191
|
name: "teamSize",
|
|
@@ -12067,7 +13199,7 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
12067
13199
|
]);
|
|
12068
13200
|
const teamSize = teamSizeAnswer.teamSize;
|
|
12069
13201
|
const wsTotalItems = wsFilterIndex.items.length;
|
|
12070
|
-
const presetAnswer = await
|
|
13202
|
+
const presetAnswer = await inquirer6.prompt([
|
|
12071
13203
|
{
|
|
12072
13204
|
type: "select",
|
|
12073
13205
|
name: "preset",
|
|
@@ -12093,7 +13225,7 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
12093
13225
|
contentIndex.items,
|
|
12094
13226
|
(item) => item.protected || item.tags.includes("core")
|
|
12095
13227
|
);
|
|
12096
|
-
const customAnswer = await
|
|
13228
|
+
const customAnswer = await inquirer6.prompt([
|
|
12097
13229
|
{
|
|
12098
13230
|
type: "checkbox",
|
|
12099
13231
|
name: "items",
|
|
@@ -12105,7 +13237,7 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
12105
13237
|
customSelections = customAnswer.items;
|
|
12106
13238
|
}
|
|
12107
13239
|
const toolDefaults = repoInfo.existingTools.length > 0 ? repoInfo.existingTools : DEFAULT_TOOLS;
|
|
12108
|
-
const toolAnswers = await
|
|
13240
|
+
const toolAnswers = await inquirer6.prompt([
|
|
12109
13241
|
{
|
|
12110
13242
|
type: "checkbox",
|
|
12111
13243
|
name: "tools",
|
|
@@ -12120,7 +13252,7 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
12120
13252
|
if (opts.worktree !== void 0) {
|
|
12121
13253
|
worktreeEnabled = opts.worktree;
|
|
12122
13254
|
} else if (wsHasWorktreeTool) {
|
|
12123
|
-
const wsWtAnswer = await
|
|
13255
|
+
const wsWtAnswer = await inquirer6.prompt([{
|
|
12124
13256
|
type: "confirm",
|
|
12125
13257
|
name: "enabled",
|
|
12126
13258
|
message: "Enable worktree file isolation (for parallel agent sessions)?",
|
|
@@ -12130,14 +13262,37 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
12130
13262
|
} else {
|
|
12131
13263
|
worktreeEnabled = false;
|
|
12132
13264
|
}
|
|
13265
|
+
const wsTier2Suggested = Array.from(/* @__PURE__ */ new Set([
|
|
13266
|
+
...evaluateTier2Triggers(repoInfo),
|
|
13267
|
+
...applyPlatformTriggers(platform, [])
|
|
13268
|
+
]));
|
|
13269
|
+
const wsSelectedCliTools = await pickCliTools({
|
|
13270
|
+
tier2Suggested: wsTier2Suggested,
|
|
13271
|
+
wslTheme
|
|
13272
|
+
});
|
|
13273
|
+
if (wsSelectedCliTools.length > 0) {
|
|
13274
|
+
const wsDetectSpinner = createSpinner(`Detecting ${wsSelectedCliTools.length} CLI tool(s)...`);
|
|
13275
|
+
wsDetectSpinner.start();
|
|
13276
|
+
const wsMissing = await findMissingCliTools(wsSelectedCliTools);
|
|
13277
|
+
if (wsMissing.length === 0) {
|
|
13278
|
+
wsDetectSpinner.succeed(`All ${wsSelectedCliTools.length} CLI tool(s) detected on PATH`);
|
|
13279
|
+
} else {
|
|
13280
|
+
wsDetectSpinner.warn(`${wsSelectedCliTools.length - wsMissing.length}/${wsSelectedCliTools.length} CLI tool(s) detected; ${wsMissing.length} missing`);
|
|
13281
|
+
await offerInstaller(wsMissing, { interactive: true });
|
|
13282
|
+
}
|
|
13283
|
+
}
|
|
13284
|
+
wsCliTools = {
|
|
13285
|
+
enabled: wsSelectedCliTools.length > 0,
|
|
13286
|
+
selected: wsSelectedCliTools
|
|
13287
|
+
};
|
|
12133
13288
|
const wsSecretNotes = tools.map((t) => TOOL_SECRET_NOTES[t]).filter(Boolean);
|
|
12134
13289
|
if (wsSecretNotes.length > 0) {
|
|
12135
|
-
info(
|
|
13290
|
+
info(chalk7.dim("MCP secret loading by tool:"));
|
|
12136
13291
|
for (const note of wsSecretNotes) {
|
|
12137
|
-
info(
|
|
13292
|
+
info(chalk7.dim(` ${note}`));
|
|
12138
13293
|
}
|
|
12139
13294
|
}
|
|
12140
|
-
const featureAnswers = await
|
|
13295
|
+
const featureAnswers = await inquirer6.prompt([
|
|
12141
13296
|
{
|
|
12142
13297
|
type: "checkbox",
|
|
12143
13298
|
name: "features",
|
|
@@ -12154,23 +13309,9 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
12154
13309
|
}
|
|
12155
13310
|
mcpServers = [];
|
|
12156
13311
|
if (features.mcp) {
|
|
12157
|
-
const
|
|
12158
|
-
|
|
12159
|
-
|
|
12160
|
-
);
|
|
12161
|
-
const mcpAnswers = await inquirer4.prompt([
|
|
12162
|
-
{
|
|
12163
|
-
type: "checkbox",
|
|
12164
|
-
name: "mcp",
|
|
12165
|
-
message: "Select MCP servers:",
|
|
12166
|
-
choices: MCP_CHOICES,
|
|
12167
|
-
default: defaultMcpForPlatform,
|
|
12168
|
-
...wslTheme && { theme: wslTheme }
|
|
12169
|
-
}
|
|
12170
|
-
]);
|
|
12171
|
-
mcpServers = mcpAnswers.mcp ?? [];
|
|
12172
|
-
if (!mcpServers.includes(platformMcp)) {
|
|
12173
|
-
mcpServers.unshift(platformMcp);
|
|
13312
|
+
const wsProceedMcp = await confirmMcpGate({ hasExisting: false, defaultYes: false });
|
|
13313
|
+
if (wsProceedMcp) {
|
|
13314
|
+
mcpServers = await pickMcpServers({ platform, wslTheme });
|
|
12174
13315
|
}
|
|
12175
13316
|
}
|
|
12176
13317
|
contentSelection = resolveSelection(selectedPreset, projectType, teamSize, wsFilterIndex, customSelections, projectLanguages);
|
|
@@ -12195,6 +13336,7 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
12195
13336
|
repoInfo,
|
|
12196
13337
|
contentSelection,
|
|
12197
13338
|
worktreeEnabled,
|
|
13339
|
+
cliTools: wsCliTools,
|
|
12198
13340
|
yes: headless
|
|
12199
13341
|
});
|
|
12200
13342
|
let repoEntries;
|
|
@@ -12209,14 +13351,14 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
12209
13351
|
platform: r.platform || void 0
|
|
12210
13352
|
}));
|
|
12211
13353
|
} else {
|
|
12212
|
-
const wslTheme = isWSL() ? { icon: { checked:
|
|
12213
|
-
const { syncRepos } = await
|
|
13354
|
+
const wslTheme = isWSL() ? { icon: { checked: chalk7.green("[x]"), unchecked: "[ ]", cursor: ">" } } : void 0;
|
|
13355
|
+
const { syncRepos } = await inquirer6.prompt([
|
|
12214
13356
|
{
|
|
12215
13357
|
type: "checkbox",
|
|
12216
13358
|
name: "syncRepos",
|
|
12217
13359
|
message: "Select repos to sync workspace content to:",
|
|
12218
13360
|
choices: enriched.map((r) => ({
|
|
12219
|
-
name: `${r.name}${r.hasHatch3r ?
|
|
13361
|
+
name: `${r.name}${r.hasHatch3r ? chalk7.dim(" (has existing hatch3r)") : ""}`,
|
|
12220
13362
|
value: r.path,
|
|
12221
13363
|
checked: false
|
|
12222
13364
|
})),
|
|
@@ -12230,9 +13372,9 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
12230
13372
|
`${conflictingRepos.length} selected repo(s) already have hatch3r installed; their managed files will be overwritten by workspace content.`
|
|
12231
13373
|
);
|
|
12232
13374
|
for (const r of conflictingRepos) {
|
|
12233
|
-
console.log(
|
|
13375
|
+
console.log(chalk7.dim(` - ${r.name ?? r.path}`));
|
|
12234
13376
|
}
|
|
12235
|
-
const { confirmConflict } = await
|
|
13377
|
+
const { confirmConflict } = await inquirer6.prompt([
|
|
12236
13378
|
{
|
|
12237
13379
|
type: "confirm",
|
|
12238
13380
|
name: "confirmConflict",
|
|
@@ -12244,7 +13386,7 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
12244
13386
|
for (const r of conflictingRepos) {
|
|
12245
13387
|
syncSet.delete(r.path);
|
|
12246
13388
|
}
|
|
12247
|
-
info(
|
|
13389
|
+
info(chalk7.dim(" Skipped syncing conflicting repos. They remain registered in the workspace \u2014 run `hatch3r sync --repos <path>` after reviewing their managed files."));
|
|
12248
13390
|
}
|
|
12249
13391
|
}
|
|
12250
13392
|
repoEntries = enriched.map((r) => ({
|
|
@@ -12260,7 +13402,7 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
12260
13402
|
const dirName = basename2(rootDir) || "workspace";
|
|
12261
13403
|
const wsManifest = createWorkspaceManifest(
|
|
12262
13404
|
dirName,
|
|
12263
|
-
{ platform, tools, features, mcp: { servers: mcpServers }, content: contentSelection },
|
|
13405
|
+
{ platform, tools, features, mcp: { servers: mcpServers }, cliTools: wsCliTools, content: contentSelection },
|
|
12264
13406
|
repoEntries,
|
|
12265
13407
|
"manual"
|
|
12266
13408
|
);
|
|
@@ -12291,6 +13433,10 @@ async function runWorkspaceInit(rootDir, detectedRepos, repoInfo, opts) {
|
|
|
12291
13433
|
label("Manifest", `${AGENTS_DIR}/workspace.json`)
|
|
12292
13434
|
];
|
|
12293
13435
|
printBox("Workspace ready", wsLines, "success");
|
|
13436
|
+
if (wsCliTools.selected.length > 0) {
|
|
13437
|
+
const finalMissing = await findMissingCliTools(wsCliTools.selected);
|
|
13438
|
+
printMissingCliToolsDisclaimer(finalMissing, wsCliTools.selected.length);
|
|
13439
|
+
}
|
|
12294
13440
|
}
|
|
12295
13441
|
function resolveToolsFromOpts(toolsFlag, repoInfo) {
|
|
12296
13442
|
if (toolsFlag) {
|
|
@@ -12298,7 +13444,7 @@ function resolveToolsFromOpts(toolsFlag, repoInfo) {
|
|
|
12298
13444
|
const invalid = rawTools.filter((t) => !VALID_TOOLS.has(t));
|
|
12299
13445
|
if (invalid.length > 0) {
|
|
12300
13446
|
error(`Invalid tool(s): ${invalid.join(", ")}`);
|
|
12301
|
-
console.log(
|
|
13447
|
+
console.log(chalk7.dim(` Valid tools: ${[...VALID_TOOLS].join(", ")}`));
|
|
12302
13448
|
throw new HatchError(`Invalid tool(s): ${invalid.join(", ")}`, 1);
|
|
12303
13449
|
}
|
|
12304
13450
|
return rawTools;
|
|
@@ -12306,6 +13452,22 @@ function resolveToolsFromOpts(toolsFlag, repoInfo) {
|
|
|
12306
13452
|
if (repoInfo.existingTools.length > 0) return repoInfo.existingTools;
|
|
12307
13453
|
return DEFAULT_TOOLS;
|
|
12308
13454
|
}
|
|
13455
|
+
function resolveCliToolsFlag(flag, _repoInfo, _platform) {
|
|
13456
|
+
if (!flag) return void 0;
|
|
13457
|
+
const trimmed = flag.trim();
|
|
13458
|
+
if (trimmed === "") return void 0;
|
|
13459
|
+
if (trimmed === "tier1") return [...TIER1_CLI_TOOLS];
|
|
13460
|
+
if (trimmed === "all") return Object.keys(AVAILABLE_CLI_TOOLS);
|
|
13461
|
+
const rawIds = trimmed.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
13462
|
+
const valid = new Set(Object.keys(AVAILABLE_CLI_TOOLS));
|
|
13463
|
+
const invalid = rawIds.filter((id) => !valid.has(id));
|
|
13464
|
+
if (invalid.length > 0) {
|
|
13465
|
+
error(`Invalid CLI tool(s): ${invalid.join(", ")}`);
|
|
13466
|
+
console.log(chalk7.dim(` Valid ids: ${[...valid].join(", ")}`));
|
|
13467
|
+
throw new HatchError(`Invalid CLI tool(s): ${invalid.join(", ")}`, 1, "VALIDATION_ERROR");
|
|
13468
|
+
}
|
|
13469
|
+
return rawIds;
|
|
13470
|
+
}
|
|
12309
13471
|
|
|
12310
13472
|
// src/cli/commands/clean.ts
|
|
12311
13473
|
init_hatchJson();
|
|
@@ -12328,44 +13490,45 @@ function captureConfig(manifest) {
|
|
|
12328
13490
|
},
|
|
12329
13491
|
worktreeEnabled: manifest.worktree?.enabled ?? false,
|
|
12330
13492
|
customization: manifest.customization,
|
|
13493
|
+
cliTools: manifest.cliTools,
|
|
12331
13494
|
preservedFields: extractPreservedManifestFields(manifest)
|
|
12332
13495
|
};
|
|
12333
13496
|
}
|
|
12334
13497
|
function printInventory(inventory) {
|
|
12335
13498
|
const sections = [];
|
|
12336
13499
|
if (inventory.adapterFiles.length > 0) {
|
|
12337
|
-
sections.push(` ${
|
|
13500
|
+
sections.push(` ${chalk8.red("\xD7")} ${inventory.adapterFiles.length} adapter output file(s)`);
|
|
12338
13501
|
}
|
|
12339
13502
|
if (inventory.canonicalDir) {
|
|
12340
13503
|
if ((inventory.userContentCount ?? 0) > 0) {
|
|
12341
|
-
sections.push(` ${
|
|
13504
|
+
sections.push(` ${chalk8.red("\xD7")} .agents/ canonical directory ${chalk8.dim("(.agents/user/ preserved)")}`);
|
|
12342
13505
|
} else {
|
|
12343
|
-
sections.push(` ${
|
|
13506
|
+
sections.push(` ${chalk8.red("\xD7")} .agents/ canonical directory`);
|
|
12344
13507
|
}
|
|
12345
13508
|
}
|
|
12346
13509
|
if (inventory.worktreeInclude) {
|
|
12347
|
-
sections.push(` ${
|
|
13510
|
+
sections.push(` ${chalk8.red("\xD7")} .worktreeinclude`);
|
|
12348
13511
|
}
|
|
12349
13512
|
if (inventory.archiveDir) {
|
|
12350
|
-
sections.push(` ${
|
|
13513
|
+
sections.push(` ${chalk8.red("\xD7")} .hatch3r-archive/`);
|
|
12351
13514
|
}
|
|
12352
13515
|
if (inventory.envMcp) {
|
|
12353
|
-
sections.push(` ${
|
|
13516
|
+
sections.push(` ${chalk8.green("\u2713")} .env.mcp ${chalk8.dim("(kept \u2014 contains secrets)")}`);
|
|
12354
13517
|
}
|
|
12355
13518
|
if (inventory.customizeDir) {
|
|
12356
|
-
sections.push(` ${
|
|
13519
|
+
sections.push(` ${chalk8.green("\u2713")} .hatch3r/ ${chalk8.dim("(kept \u2014 customizations)")}`);
|
|
12357
13520
|
}
|
|
12358
13521
|
if ((inventory.userContentCount ?? 0) > 0) {
|
|
12359
13522
|
sections.push(
|
|
12360
|
-
` ${
|
|
13523
|
+
` ${chalk8.green("\u2713")} .agents/user/ ${chalk8.dim(`(${inventory.userContentCount} user artifact(s) \u2014 kept, user-authored)`)}`
|
|
12361
13524
|
);
|
|
12362
13525
|
}
|
|
12363
13526
|
if (inventory.learnings.length > 0) {
|
|
12364
|
-
sections.push(` ${
|
|
13527
|
+
sections.push(` ${chalk8.green("\u2713")} ${inventory.learnings.length} learning(s) ${chalk8.dim("(backed up for reinit)")}`);
|
|
12365
13528
|
}
|
|
12366
13529
|
if (sections.length > 0) {
|
|
12367
13530
|
console.log("");
|
|
12368
|
-
console.log(
|
|
13531
|
+
console.log(chalk8.bold(" Cleanup inventory:"));
|
|
12369
13532
|
for (const s of sections) {
|
|
12370
13533
|
console.log(s);
|
|
12371
13534
|
}
|
|
@@ -12389,22 +13552,22 @@ async function cleanCommand(opts = {}) {
|
|
|
12389
13552
|
printInventory(inventory);
|
|
12390
13553
|
if (inventory.isWorkspaceRoot) {
|
|
12391
13554
|
warn("This is a workspace root. Member repos still reference this workspace.");
|
|
12392
|
-
console.log(
|
|
13555
|
+
console.log(chalk8.dim(" Clean member repos individually or reinitialize them.\n"));
|
|
12393
13556
|
}
|
|
12394
13557
|
if (inventory.isWorkspaceMember) {
|
|
12395
|
-
warn(`This repo is managed by a workspace at ${
|
|
13558
|
+
warn(`This repo is managed by a workspace at ${chalk8.bold(inventory.workspaceRootPath ?? "..")}.`);
|
|
12396
13559
|
console.log("");
|
|
12397
13560
|
}
|
|
12398
13561
|
if (opts.dryRun) {
|
|
12399
13562
|
const result2 = await executeClean(rootDir, inventory, true);
|
|
12400
|
-
console.log(
|
|
13563
|
+
console.log(chalk8.bold(" Would remove:"));
|
|
12401
13564
|
for (const f of result2.removed) {
|
|
12402
|
-
console.log(` ${
|
|
13565
|
+
console.log(` ${chalk8.red("\xD7")} ${f}`);
|
|
12403
13566
|
}
|
|
12404
13567
|
if (result2.kept.length > 0) {
|
|
12405
|
-
console.log(
|
|
13568
|
+
console.log(chalk8.bold("\n Would keep:"));
|
|
12406
13569
|
for (const f of result2.kept) {
|
|
12407
|
-
console.log(` ${
|
|
13570
|
+
console.log(` ${chalk8.green("\u2713")} ${f}`);
|
|
12408
13571
|
}
|
|
12409
13572
|
}
|
|
12410
13573
|
console.log("");
|
|
@@ -12414,7 +13577,7 @@ async function cleanCommand(opts = {}) {
|
|
|
12414
13577
|
return;
|
|
12415
13578
|
}
|
|
12416
13579
|
if (!opts.yes) {
|
|
12417
|
-
const { proceed } = await
|
|
13580
|
+
const { proceed } = await inquirer7.prompt([
|
|
12418
13581
|
{
|
|
12419
13582
|
type: "confirm",
|
|
12420
13583
|
name: "proceed",
|
|
@@ -12423,7 +13586,7 @@ async function cleanCommand(opts = {}) {
|
|
|
12423
13586
|
}
|
|
12424
13587
|
]);
|
|
12425
13588
|
if (!proceed) {
|
|
12426
|
-
console.log(
|
|
13589
|
+
console.log(chalk8.dim("\n Clean cancelled.\n"));
|
|
12427
13590
|
if (learningsBackup) {
|
|
12428
13591
|
const { rm: rm5 } = await import("fs/promises");
|
|
12429
13592
|
await rm5(learningsBackup, { recursive: true, force: true });
|
|
@@ -12445,7 +13608,7 @@ async function cleanCommand(opts = {}) {
|
|
|
12445
13608
|
}
|
|
12446
13609
|
if (!opts.yes && config) {
|
|
12447
13610
|
console.log("");
|
|
12448
|
-
const { reinit } = await
|
|
13611
|
+
const { reinit } = await inquirer7.prompt([
|
|
12449
13612
|
{
|
|
12450
13613
|
type: "confirm",
|
|
12451
13614
|
name: "reinit",
|
|
@@ -12478,6 +13641,10 @@ async function cleanCommand(opts = {}) {
|
|
|
12478
13641
|
// manifest preserves integration config and per-artifact overrides
|
|
12479
13642
|
// across a clean -> reinit cycle.
|
|
12480
13643
|
customization: config.customization,
|
|
13644
|
+
// 1.7.5 (CLI-tooling pivot): carry the previous CLI-tools
|
|
13645
|
+
// selection forward so clean -> reinit does not silently
|
|
13646
|
+
// re-pick from the default.
|
|
13647
|
+
cliTools: config.cliTools,
|
|
12481
13648
|
// 1.7.1: carry full platform/user manifest state (board IDs,
|
|
12482
13649
|
// costTracking, specs, extension config, worktree extras) forward
|
|
12483
13650
|
// so a clean -> reinit cycle no longer wipes them.
|
|
@@ -12496,13 +13663,13 @@ async function cleanCommand(opts = {}) {
|
|
|
12496
13663
|
""
|
|
12497
13664
|
];
|
|
12498
13665
|
if (learningsBackup) {
|
|
12499
|
-
summaryLines2.push(`${
|
|
13666
|
+
summaryLines2.push(`${chalk8.green("\u2713")} Learnings restored`);
|
|
12500
13667
|
}
|
|
12501
13668
|
if (inventory.customizeDir) {
|
|
12502
|
-
summaryLines2.push(`${
|
|
13669
|
+
summaryLines2.push(`${chalk8.green("\u2713")} Customizations preserved`);
|
|
12503
13670
|
}
|
|
12504
13671
|
if (inventory.envMcp) {
|
|
12505
|
-
summaryLines2.push(`${
|
|
13672
|
+
summaryLines2.push(`${chalk8.green("\u2713")} .env.mcp preserved`);
|
|
12506
13673
|
}
|
|
12507
13674
|
printBox("Reinit complete", summaryLines2, "success");
|
|
12508
13675
|
} catch (err) {
|
|
@@ -12522,16 +13689,16 @@ async function cleanCommand(opts = {}) {
|
|
|
12522
13689
|
await rm4(learningsBackup, { recursive: true, force: true });
|
|
12523
13690
|
}
|
|
12524
13691
|
const summaryLines = [
|
|
12525
|
-
`${
|
|
13692
|
+
`${chalk8.red("\xD7")} ${result.removed.length} artifact(s) removed`
|
|
12526
13693
|
];
|
|
12527
13694
|
if (inventory.envMcp) {
|
|
12528
|
-
summaryLines.push(`${
|
|
13695
|
+
summaryLines.push(`${chalk8.green("\u2713")} .env.mcp preserved`);
|
|
12529
13696
|
}
|
|
12530
13697
|
if (inventory.customizeDir) {
|
|
12531
|
-
summaryLines.push(`${
|
|
13698
|
+
summaryLines.push(`${chalk8.green("\u2713")} .hatch3r/ customizations preserved`);
|
|
12532
13699
|
}
|
|
12533
13700
|
summaryLines.push("");
|
|
12534
|
-
summaryLines.push(`${
|
|
13701
|
+
summaryLines.push(`${chalk8.cyan("\u2192")} Run ${chalk8.bold("hatch3r init")} when ready to set up again.`);
|
|
12535
13702
|
printBox("Clean complete", summaryLines, "success");
|
|
12536
13703
|
}
|
|
12537
13704
|
|
|
@@ -12546,18 +13713,20 @@ init_paths();
|
|
|
12546
13713
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
12547
13714
|
import { readFile as readFile21 } from "fs/promises";
|
|
12548
13715
|
import { dirname as dirname16, join as join30 } from "path";
|
|
12549
|
-
import
|
|
12550
|
-
import
|
|
13716
|
+
import chalk10 from "chalk";
|
|
13717
|
+
import inquirer9 from "inquirer";
|
|
12551
13718
|
init_content();
|
|
12552
13719
|
init_agentsContent();
|
|
12553
13720
|
init_safeWrite();
|
|
12554
13721
|
init_worktree();
|
|
12555
13722
|
var __dirname4 = dirname16(fileURLToPath6(import.meta.url));
|
|
12556
|
-
function computeDiff(oldManifest, newTools, newFeatures, newMcp, newPlatform, newOwner, newRepo, newNamespace, newProject) {
|
|
13723
|
+
function computeDiff(oldManifest, newTools, newFeatures, newMcp, newPlatform, newOwner, newRepo, newNamespace, newProject, newCliToolIds) {
|
|
12557
13724
|
const oldToolSet = new Set(oldManifest.tools);
|
|
12558
13725
|
const newToolSet = new Set(newTools);
|
|
12559
13726
|
const oldMcpSet = new Set(oldManifest.mcp.servers);
|
|
12560
13727
|
const newMcpSet = new Set(newMcp);
|
|
13728
|
+
const oldCliSet = new Set(oldManifest.cliTools?.selected ?? []);
|
|
13729
|
+
const newCliSet = new Set(newCliToolIds);
|
|
12561
13730
|
const enabledFeatures = [];
|
|
12562
13731
|
const disabledFeatures = [];
|
|
12563
13732
|
for (const key of Object.keys(DEFAULT_FEATURES)) {
|
|
@@ -12574,11 +13743,13 @@ function computeDiff(oldManifest, newTools, newFeatures, newMcp, newPlatform, ne
|
|
|
12574
13743
|
platformChanged: newPlatform !== oldManifest.platform,
|
|
12575
13744
|
repoChanged: newOwner !== oldManifest.owner || newRepo !== oldManifest.repo || newNamespace !== oldManifest.namespace || newProject !== oldManifest.project,
|
|
12576
13745
|
addedContent: [],
|
|
12577
|
-
removedContent: []
|
|
13746
|
+
removedContent: [],
|
|
13747
|
+
addedCliTools: newCliToolIds.filter((id) => !oldCliSet.has(id)),
|
|
13748
|
+
removedCliTools: [...oldCliSet].filter((id) => !newCliSet.has(id))
|
|
12578
13749
|
};
|
|
12579
13750
|
}
|
|
12580
13751
|
function isDiffEmpty(diff) {
|
|
12581
|
-
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;
|
|
13752
|
+
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;
|
|
12582
13753
|
}
|
|
12583
13754
|
function printCurrentConfig(manifest) {
|
|
12584
13755
|
const platformLabel = manifest.platform ? `${PLATFORM_DISPLAY_NAMES[manifest.platform]} (${manifest.namespace || manifest.owner}/${manifest.project || manifest.repo})` : "Not set";
|
|
@@ -12589,9 +13760,13 @@ function printCurrentConfig(manifest) {
|
|
|
12589
13760
|
label("Platform", platformLabel),
|
|
12590
13761
|
label("Branch", branch),
|
|
12591
13762
|
label("Tools", toolNames),
|
|
12592
|
-
label("Features", enabledFeatures.join(", "))
|
|
12593
|
-
label("MCP", manifest.mcp.servers.length > 0 ? manifest.mcp.servers.join(", ") : "none")
|
|
13763
|
+
label("Features", enabledFeatures.join(", "))
|
|
12594
13764
|
];
|
|
13765
|
+
const cliSelected = manifest.cliTools?.selected ?? [];
|
|
13766
|
+
lines.push(label("CLI tools", cliSelected.length > 0 ? cliSelected.join(", ") : "none"));
|
|
13767
|
+
if (manifest.mcp.servers.length > 0) {
|
|
13768
|
+
lines.push(label("MCP", manifest.mcp.servers.join(", ")));
|
|
13769
|
+
}
|
|
12595
13770
|
if (manifest.content) {
|
|
12596
13771
|
const total = countSelectionItems(manifest.content);
|
|
12597
13772
|
lines.push(label("Content", `${total} items (${selectionSummary(manifest.content)})`));
|
|
@@ -12604,7 +13779,7 @@ async function configCommand() {
|
|
|
12604
13779
|
const manifest = await readManifest(rootDir);
|
|
12605
13780
|
if (!manifest) {
|
|
12606
13781
|
error("No .agents/hatch.json found.");
|
|
12607
|
-
console.log(
|
|
13782
|
+
console.log(chalk10.dim(" Run `npx hatch3r init` to set up your project first.\n"));
|
|
12608
13783
|
throw new HatchError("No .agents/hatch.json found.", 1, "CONFIG_ERROR");
|
|
12609
13784
|
}
|
|
12610
13785
|
const wsContext = await detectWorkspaceContext(rootDir);
|
|
@@ -12613,7 +13788,7 @@ async function configCommand() {
|
|
|
12613
13788
|
`This repo is managed by workspace at ${wsContext.workspaceRoot}. Changes here may be overwritten on next workspace sync.`
|
|
12614
13789
|
);
|
|
12615
13790
|
console.log();
|
|
12616
|
-
const { action } = await
|
|
13791
|
+
const { action } = await inquirer9.prompt([
|
|
12617
13792
|
{
|
|
12618
13793
|
type: "select",
|
|
12619
13794
|
name: "action",
|
|
@@ -12644,8 +13819,8 @@ async function configCommand() {
|
|
|
12644
13819
|
);
|
|
12645
13820
|
}
|
|
12646
13821
|
printCurrentConfig(manifest);
|
|
12647
|
-
const wslTheme = isWSL() ? { icon: { checked:
|
|
12648
|
-
const platformAnswer = await
|
|
13822
|
+
const wslTheme = isWSL() ? { icon: { checked: chalk10.green("[x]"), unchecked: "[ ]", cursor: ">" } } : void 0;
|
|
13823
|
+
const platformAnswer = await inquirer9.prompt([
|
|
12649
13824
|
{
|
|
12650
13825
|
type: "select",
|
|
12651
13826
|
name: "platform",
|
|
@@ -12664,7 +13839,7 @@ async function configCommand() {
|
|
|
12664
13839
|
let namespace;
|
|
12665
13840
|
let project;
|
|
12666
13841
|
if (platform === "azure-devops") {
|
|
12667
|
-
const adoAnswers = await
|
|
13842
|
+
const adoAnswers = await inquirer9.prompt([
|
|
12668
13843
|
{ type: "input", name: "org", message: "Azure DevOps organization:", default: manifest.owner || void 0 },
|
|
12669
13844
|
{ type: "input", name: "project", message: "Azure DevOps project:", default: manifest.project || void 0 },
|
|
12670
13845
|
{ type: "input", name: "repo", message: "Repository name:", default: manifest.repo || void 0 }
|
|
@@ -12674,7 +13849,7 @@ async function configCommand() {
|
|
|
12674
13849
|
namespace = owner;
|
|
12675
13850
|
project = sanitizeInput(adoAnswers.project);
|
|
12676
13851
|
} else if (platform === "gitlab") {
|
|
12677
|
-
const glAnswers = await
|
|
13852
|
+
const glAnswers = await inquirer9.prompt([
|
|
12678
13853
|
{ type: "input", name: "namespace", message: "GitLab namespace (group or username):", default: manifest.namespace || manifest.owner || void 0 },
|
|
12679
13854
|
{ type: "input", name: "project", message: "Project name:", default: manifest.project || manifest.repo || void 0 }
|
|
12680
13855
|
]);
|
|
@@ -12683,7 +13858,7 @@ async function configCommand() {
|
|
|
12683
13858
|
namespace = owner;
|
|
12684
13859
|
project = repo;
|
|
12685
13860
|
} else {
|
|
12686
|
-
const repoAnswers = await
|
|
13861
|
+
const repoAnswers = await inquirer9.prompt([
|
|
12687
13862
|
{ type: "input", name: "owner", message: "GitHub owner (org or username):", default: manifest.owner || void 0 },
|
|
12688
13863
|
{ type: "input", name: "repo", message: "Repository name:", default: manifest.repo || void 0 }
|
|
12689
13864
|
]);
|
|
@@ -12693,7 +13868,7 @@ async function configCommand() {
|
|
|
12693
13868
|
project = repo;
|
|
12694
13869
|
}
|
|
12695
13870
|
const currentBranch = manifest.board?.defaultBranch ?? "main";
|
|
12696
|
-
const branchAnswer = await
|
|
13871
|
+
const branchAnswer = await inquirer9.prompt([
|
|
12697
13872
|
{
|
|
12698
13873
|
type: "input",
|
|
12699
13874
|
name: "defaultBranch",
|
|
@@ -12710,7 +13885,7 @@ async function configCommand() {
|
|
|
12710
13885
|
}
|
|
12711
13886
|
]);
|
|
12712
13887
|
const defaultBranch = branchAnswer.defaultBranch.trim() || currentBranch;
|
|
12713
|
-
const toolAnswers = await
|
|
13888
|
+
const toolAnswers = await inquirer9.prompt([
|
|
12714
13889
|
{
|
|
12715
13890
|
type: "checkbox",
|
|
12716
13891
|
name: "tools",
|
|
@@ -12725,8 +13900,28 @@ async function configCommand() {
|
|
|
12725
13900
|
error("At least one tool must be selected.");
|
|
12726
13901
|
throw new HatchError("At least one tool must be selected.", 1, "VALIDATION_ERROR");
|
|
12727
13902
|
}
|
|
13903
|
+
const existingCliTools = manifest.cliTools?.selected ?? [];
|
|
13904
|
+
const selectedCliTools = await pickCliTools({
|
|
13905
|
+
existing: existingCliTools,
|
|
13906
|
+
wslTheme
|
|
13907
|
+
});
|
|
13908
|
+
if (selectedCliTools.length > 0) {
|
|
13909
|
+
const cliSpinner = createSpinner(`Detecting ${selectedCliTools.length} CLI tool(s)...`);
|
|
13910
|
+
cliSpinner.start();
|
|
13911
|
+
const missing = await findMissingCliTools(selectedCliTools);
|
|
13912
|
+
if (missing.length === 0) {
|
|
13913
|
+
cliSpinner.succeed(`All ${selectedCliTools.length} CLI tool(s) detected on PATH`);
|
|
13914
|
+
} else {
|
|
13915
|
+
cliSpinner.warn(`${selectedCliTools.length - missing.length}/${selectedCliTools.length} CLI tool(s) detected; ${missing.length} missing`);
|
|
13916
|
+
await offerInstaller(missing, { interactive: true });
|
|
13917
|
+
}
|
|
13918
|
+
}
|
|
13919
|
+
const cliToolsConfig = {
|
|
13920
|
+
enabled: selectedCliTools.length > 0,
|
|
13921
|
+
selected: selectedCliTools
|
|
13922
|
+
};
|
|
12728
13923
|
const currentFeatureKeys = Object.keys(DEFAULT_FEATURES).filter((k) => manifest.features[k]);
|
|
12729
|
-
const featureAnswers = await
|
|
13924
|
+
const featureAnswers = await inquirer9.prompt([
|
|
12730
13925
|
{
|
|
12731
13926
|
type: "checkbox",
|
|
12732
13927
|
name: "features",
|
|
@@ -12741,27 +13936,21 @@ async function configCommand() {
|
|
|
12741
13936
|
for (const k of Object.keys(features)) {
|
|
12742
13937
|
features[k] = selectedFeatures.includes(k);
|
|
12743
13938
|
}
|
|
12744
|
-
|
|
13939
|
+
const hasExistingMcp = manifest.mcp.servers.length > 0;
|
|
13940
|
+
let mcpServers = hasExistingMcp ? [...manifest.mcp.servers] : [];
|
|
12745
13941
|
if (features.mcp) {
|
|
12746
|
-
const
|
|
12747
|
-
|
|
12748
|
-
{
|
|
12749
|
-
|
|
12750
|
-
|
|
12751
|
-
|
|
12752
|
-
|
|
12753
|
-
default: manifest.mcp.servers,
|
|
12754
|
-
...wslTheme && { theme: wslTheme }
|
|
12755
|
-
}
|
|
12756
|
-
]);
|
|
12757
|
-
mcpServers = mcpAnswers.mcp ?? [];
|
|
12758
|
-
if (!mcpServers.includes(platformMcp)) {
|
|
12759
|
-
mcpServers.unshift(platformMcp);
|
|
13942
|
+
const proceedMcp = await confirmMcpGate({ hasExisting: hasExistingMcp });
|
|
13943
|
+
if (proceedMcp) {
|
|
13944
|
+
mcpServers = await pickMcpServers({
|
|
13945
|
+
platform,
|
|
13946
|
+
existing: manifest.mcp.servers,
|
|
13947
|
+
wslTheme
|
|
13948
|
+
});
|
|
12760
13949
|
}
|
|
12761
13950
|
}
|
|
12762
13951
|
const hasWorktreeTool = tools.some((t) => WORKTREE_CAPABLE_TOOLS.has(t));
|
|
12763
13952
|
if (hasWorktreeTool) {
|
|
12764
|
-
const wtAnswer = await
|
|
13953
|
+
const wtAnswer = await inquirer9.prompt([{
|
|
12765
13954
|
type: "confirm",
|
|
12766
13955
|
name: "enabled",
|
|
12767
13956
|
message: "Enable worktree file isolation (for parallel agent sessions)?",
|
|
@@ -12776,7 +13965,7 @@ async function configCommand() {
|
|
|
12776
13965
|
let contentMetadataChanged = false;
|
|
12777
13966
|
if (manifest.content) {
|
|
12778
13967
|
info(
|
|
12779
|
-
|
|
13968
|
+
chalk10.dim("Config adds/removes content items. To customize an item's behavior without ") + chalk10.dim("removing it, use .hatch3r/<type>/<id>.customize.yaml instead.")
|
|
12780
13969
|
);
|
|
12781
13970
|
console.log();
|
|
12782
13971
|
const contentRoot = findPackageRoot(__dirname4);
|
|
@@ -12785,7 +13974,7 @@ async function configCommand() {
|
|
|
12785
13974
|
const previousContent = manifest.content;
|
|
12786
13975
|
const { projectType, teamSize } = manifest.content;
|
|
12787
13976
|
const totalItems = index.items.length;
|
|
12788
|
-
const presetAnswer = await
|
|
13977
|
+
const presetAnswer = await inquirer9.prompt([
|
|
12789
13978
|
{
|
|
12790
13979
|
type: "select",
|
|
12791
13980
|
name: "preset",
|
|
@@ -12808,7 +13997,7 @@ async function configCommand() {
|
|
|
12808
13997
|
if (selectedPreset.id === "custom") {
|
|
12809
13998
|
const currentIds = getAllContentIds(manifest.content);
|
|
12810
13999
|
const groupedChoices = buildTagGroupedCustomContentChoices(index.items, (item) => currentIds.has(item.id));
|
|
12811
|
-
const customAnswer = await
|
|
14000
|
+
const customAnswer = await inquirer9.prompt([
|
|
12812
14001
|
{
|
|
12813
14002
|
type: "checkbox",
|
|
12814
14003
|
name: "items",
|
|
@@ -12855,7 +14044,7 @@ async function configCommand() {
|
|
|
12855
14044
|
console.log();
|
|
12856
14045
|
warn("Dependency warnings for removed content:");
|
|
12857
14046
|
for (const w of dependencyWarnings) {
|
|
12858
|
-
console.log(
|
|
14047
|
+
console.log(chalk10.dim(` ${w}`));
|
|
12859
14048
|
}
|
|
12860
14049
|
console.log();
|
|
12861
14050
|
}
|
|
@@ -12889,7 +14078,7 @@ async function configCommand() {
|
|
|
12889
14078
|
});
|
|
12890
14079
|
}
|
|
12891
14080
|
}
|
|
12892
|
-
const diff = computeDiff(manifest, tools, features, mcpServers, platform, owner, repo, namespace, project);
|
|
14081
|
+
const diff = computeDiff(manifest, tools, features, mcpServers, platform, owner, repo, namespace, project, selectedCliTools);
|
|
12893
14082
|
diff.addedContent = contentChanges.added;
|
|
12894
14083
|
diff.removedContent = contentChanges.removed;
|
|
12895
14084
|
if (isDiffEmpty(diff) && defaultBranch === currentBranch && !contentMetadataChanged) {
|
|
@@ -12926,6 +14115,7 @@ async function configCommand() {
|
|
|
12926
14115
|
manifest.tools = tools;
|
|
12927
14116
|
manifest.features = features;
|
|
12928
14117
|
manifest.mcp = { servers: mcpServers };
|
|
14118
|
+
manifest.cliTools = cliToolsConfig;
|
|
12929
14119
|
if (manifest.board) {
|
|
12930
14120
|
manifest.board.owner = owner;
|
|
12931
14121
|
manifest.board.repo = repo;
|
|
@@ -12973,37 +14163,43 @@ async function configCommand() {
|
|
|
12973
14163
|
console.log();
|
|
12974
14164
|
const summaryLines = [];
|
|
12975
14165
|
if (diff.addedTools.length > 0) {
|
|
12976
|
-
summaryLines.push(`${
|
|
14166
|
+
summaryLines.push(`${chalk10.green("+")} Tools added: ${diff.addedTools.map((t) => TOOL_DISPLAY_NAMES[t] ?? t).join(", ")}`);
|
|
12977
14167
|
}
|
|
12978
14168
|
if (diff.removedTools.length > 0) {
|
|
12979
|
-
summaryLines.push(`${
|
|
14169
|
+
summaryLines.push(`${chalk10.red("-")} Tools removed: ${diff.removedTools.map((t) => TOOL_DISPLAY_NAMES[t] ?? t).join(", ")}`);
|
|
12980
14170
|
}
|
|
12981
14171
|
if (diff.addedMcp.length > 0) {
|
|
12982
|
-
summaryLines.push(`${
|
|
14172
|
+
summaryLines.push(`${chalk10.green("+")} MCP added: ${diff.addedMcp.join(", ")}`);
|
|
12983
14173
|
}
|
|
12984
14174
|
if (diff.removedMcp.length > 0) {
|
|
12985
|
-
summaryLines.push(`${
|
|
14175
|
+
summaryLines.push(`${chalk10.red("-")} MCP removed: ${diff.removedMcp.join(", ")}`);
|
|
12986
14176
|
}
|
|
12987
14177
|
if (diff.enabledFeatures.length > 0) {
|
|
12988
|
-
summaryLines.push(`${
|
|
14178
|
+
summaryLines.push(`${chalk10.green("+")} Features enabled: ${diff.enabledFeatures.join(", ")}`);
|
|
12989
14179
|
}
|
|
12990
14180
|
if (diff.disabledFeatures.length > 0) {
|
|
12991
|
-
summaryLines.push(`${
|
|
14181
|
+
summaryLines.push(`${chalk10.red("-")} Features disabled: ${diff.disabledFeatures.join(", ")}`);
|
|
12992
14182
|
}
|
|
12993
14183
|
if (diff.platformChanged) {
|
|
12994
|
-
summaryLines.push(`${
|
|
14184
|
+
summaryLines.push(`${chalk10.yellow("~")} Platform: ${PLATFORM_DISPLAY_NAMES[platform]}`);
|
|
12995
14185
|
}
|
|
12996
14186
|
if (diff.repoChanged) {
|
|
12997
|
-
summaryLines.push(`${
|
|
14187
|
+
summaryLines.push(`${chalk10.yellow("~")} Repo: ${namespace}/${project}`);
|
|
12998
14188
|
}
|
|
12999
14189
|
if (diff.addedContent.length > 0) {
|
|
13000
|
-
summaryLines.push(`${
|
|
14190
|
+
summaryLines.push(`${chalk10.green("+")} Content added: ${diff.addedContent.length} item(s)`);
|
|
13001
14191
|
}
|
|
13002
14192
|
if (diff.removedContent.length > 0) {
|
|
13003
|
-
summaryLines.push(`${
|
|
14193
|
+
summaryLines.push(`${chalk10.red("-")} Content removed: ${diff.removedContent.length} item(s)`);
|
|
14194
|
+
}
|
|
14195
|
+
if (diff.addedCliTools.length > 0) {
|
|
14196
|
+
summaryLines.push(`${chalk10.green("+")} CLI tools added: ${diff.addedCliTools.join(", ")}`);
|
|
14197
|
+
}
|
|
14198
|
+
if (diff.removedCliTools.length > 0) {
|
|
14199
|
+
summaryLines.push(`${chalk10.red("-")} CLI tools removed: ${diff.removedCliTools.join(", ")}`);
|
|
13004
14200
|
}
|
|
13005
14201
|
if (defaultBranch !== currentBranch) {
|
|
13006
|
-
summaryLines.push(`${
|
|
14202
|
+
summaryLines.push(`${chalk10.yellow("~")} Default branch: ${defaultBranch}`);
|
|
13007
14203
|
}
|
|
13008
14204
|
summaryLines.push("");
|
|
13009
14205
|
summaryLines.push(label("Files", `${updateResult.copiedFiles} canonical files updated`));
|
|
@@ -13018,7 +14214,7 @@ async function configCommand() {
|
|
|
13018
14214
|
console.log();
|
|
13019
14215
|
info("Customizations migrated to .hatch3r/ (tool-agnostic):");
|
|
13020
14216
|
for (const m of allMigrations) {
|
|
13021
|
-
console.log(` ${
|
|
14217
|
+
console.log(` ${chalk10.dim(m.from)} ${chalk10.cyan("\u2192")} ${m.to}`);
|
|
13022
14218
|
}
|
|
13023
14219
|
console.log();
|
|
13024
14220
|
}
|
|
@@ -13026,12 +14222,12 @@ async function configCommand() {
|
|
|
13026
14222
|
console.log();
|
|
13027
14223
|
info("Tool migration notes:");
|
|
13028
14224
|
if (diff.removedTools.length > 0) {
|
|
13029
|
-
info(
|
|
13030
|
-
info(
|
|
14225
|
+
info(chalk10.dim(` Removed tool output archived to .hatch3r-archive/ (recoverable).`));
|
|
14226
|
+
info(chalk10.dim(` Customizations in .hatch3r/ are tool-agnostic and carry forward.`));
|
|
13031
14227
|
}
|
|
13032
14228
|
if (diff.addedTools.length > 0) {
|
|
13033
|
-
info(
|
|
13034
|
-
info(
|
|
14229
|
+
info(chalk10.dim(` New tool output generated. Restart your editor to pick up changes.`));
|
|
14230
|
+
info(chalk10.dim(` MCP secrets (.env.mcp) are shared across tools \u2014 no re-entry needed.`));
|
|
13035
14231
|
}
|
|
13036
14232
|
console.log();
|
|
13037
14233
|
}
|
|
@@ -13041,6 +14237,7 @@ async function configCommand() {
|
|
|
13041
14237
|
wsManifestFinal.defaults.tools = tools;
|
|
13042
14238
|
wsManifestFinal.defaults.features = features;
|
|
13043
14239
|
wsManifestFinal.defaults.mcp = { servers: mcpServers };
|
|
14240
|
+
wsManifestFinal.defaults.cliTools = cliToolsConfig;
|
|
13044
14241
|
if (manifest.content) {
|
|
13045
14242
|
wsManifestFinal.defaults.content = manifest.content;
|
|
13046
14243
|
}
|
|
@@ -13049,11 +14246,11 @@ async function configCommand() {
|
|
|
13049
14246
|
}
|
|
13050
14247
|
}
|
|
13051
14248
|
console.log();
|
|
13052
|
-
info(
|
|
14249
|
+
info(chalk10.bold("Workspace configuration"));
|
|
13053
14250
|
const currentRepos = wsManifestFinal.repos.map((r) => r.path);
|
|
13054
|
-
console.log(
|
|
13055
|
-
console.log(
|
|
13056
|
-
const { manageWorkspace } = await
|
|
14251
|
+
console.log(chalk10.dim(` Repos: ${currentRepos.join(", ") || "(none)"}`));
|
|
14252
|
+
console.log(chalk10.dim(` Sync strategy: ${wsManifestFinal.syncStrategy}`));
|
|
14253
|
+
const { manageWorkspace } = await inquirer9.prompt([
|
|
13057
14254
|
{
|
|
13058
14255
|
type: "confirm",
|
|
13059
14256
|
name: "manageWorkspace",
|
|
@@ -13066,7 +14263,7 @@ async function configCommand() {
|
|
|
13066
14263
|
const existingPaths = new Set(wsManifestFinal.repos.map((r) => r.path));
|
|
13067
14264
|
const newRepos = detectedRepos.filter((r) => !existingPaths.has(r.path));
|
|
13068
14265
|
if (newRepos.length > 0) {
|
|
13069
|
-
const { addRepos } = await
|
|
14266
|
+
const { addRepos } = await inquirer9.prompt([
|
|
13070
14267
|
{
|
|
13071
14268
|
type: "checkbox",
|
|
13072
14269
|
name: "addRepos",
|
|
@@ -13084,7 +14281,7 @@ async function configCommand() {
|
|
|
13084
14281
|
}
|
|
13085
14282
|
}
|
|
13086
14283
|
if (wsManifestFinal.repos.length > 0) {
|
|
13087
|
-
const { syncRepos } = await
|
|
14284
|
+
const { syncRepos } = await inquirer9.prompt([
|
|
13088
14285
|
{
|
|
13089
14286
|
type: "checkbox",
|
|
13090
14287
|
name: "syncRepos",
|
|
@@ -13103,7 +14300,7 @@ async function configCommand() {
|
|
|
13103
14300
|
}
|
|
13104
14301
|
}
|
|
13105
14302
|
if (wsManifestFinal.repos.length > 0) {
|
|
13106
|
-
const { editIdentity } = await
|
|
14303
|
+
const { editIdentity } = await inquirer9.prompt([
|
|
13107
14304
|
{
|
|
13108
14305
|
type: "select",
|
|
13109
14306
|
name: "editIdentity",
|
|
@@ -13127,9 +14324,9 @@ async function configCommand() {
|
|
|
13127
14324
|
info("Re-detected git identities for all repos.");
|
|
13128
14325
|
} else if (editIdentity === "edit") {
|
|
13129
14326
|
for (const repo2 of wsManifestFinal.repos) {
|
|
13130
|
-
console.log(
|
|
14327
|
+
console.log(chalk10.bold(`
|
|
13131
14328
|
${repo2.name ?? repo2.path}:`));
|
|
13132
|
-
const identity = await
|
|
14329
|
+
const identity = await inquirer9.prompt([
|
|
13133
14330
|
{ type: "input", name: "owner", message: " Owner:", default: repo2.owner || void 0 },
|
|
13134
14331
|
{ type: "input", name: "repo", message: " Repo:", default: repo2.repo || void 0 },
|
|
13135
14332
|
{
|
|
@@ -13152,7 +14349,7 @@ async function configCommand() {
|
|
|
13152
14349
|
}
|
|
13153
14350
|
}
|
|
13154
14351
|
}
|
|
13155
|
-
const { strategy } = await
|
|
14352
|
+
const { strategy } = await inquirer9.prompt([
|
|
13156
14353
|
{
|
|
13157
14354
|
type: "select",
|
|
13158
14355
|
name: "strategy",
|
|
@@ -13169,7 +14366,7 @@ async function configCommand() {
|
|
|
13169
14366
|
let syncAttempted = false;
|
|
13170
14367
|
let syncFailed = false;
|
|
13171
14368
|
if (syncCount > 0) {
|
|
13172
|
-
const { syncNow } = await
|
|
14369
|
+
const { syncNow } = await inquirer9.prompt([
|
|
13173
14370
|
{
|
|
13174
14371
|
type: "confirm",
|
|
13175
14372
|
name: "syncNow",
|
|
@@ -13211,6 +14408,10 @@ async function configCommand() {
|
|
|
13211
14408
|
await writeWorkspaceManifest(rootDir, wsManifestFinal);
|
|
13212
14409
|
}
|
|
13213
14410
|
}
|
|
14411
|
+
if (selectedCliTools.length > 0) {
|
|
14412
|
+
const finalMissing = await findMissingCliTools(selectedCliTools);
|
|
14413
|
+
printMissingCliToolsDisclaimer(finalMissing, selectedCliTools.length);
|
|
14414
|
+
}
|
|
13214
14415
|
}
|
|
13215
14416
|
|
|
13216
14417
|
// src/cli/commands/sync.ts
|
|
@@ -13219,7 +14420,7 @@ init_adapters();
|
|
|
13219
14420
|
import { appendFile as appendFile3, readFile as readFile23, stat as stat8, readdir as readdir13 } from "fs/promises";
|
|
13220
14421
|
import { join as join32 } from "path";
|
|
13221
14422
|
import { execFileSync as execFileSync8 } from "child_process";
|
|
13222
|
-
import
|
|
14423
|
+
import chalk11 from "chalk";
|
|
13223
14424
|
|
|
13224
14425
|
// src/adapters/contextBudget.ts
|
|
13225
14426
|
var CONTEXT_BUDGET_TOKENS = {
|
|
@@ -13453,14 +14654,14 @@ async function syncCommand(opts = {}) {
|
|
|
13453
14654
|
const wsContext = await detectWorkspaceContext(rootDir);
|
|
13454
14655
|
if (wsContext.type === "workspace-member") {
|
|
13455
14656
|
warn(
|
|
13456
|
-
`This repository appears to be managed by a workspace at ${wsContext.workspaceRoot ?? ".."}. Run ${
|
|
14657
|
+
`This repository appears to be managed by a workspace at ${wsContext.workspaceRoot ?? ".."}. Run ${chalk11.cyan("hatch3r sync")} from the workspace root to sync all repos.`
|
|
13457
14658
|
);
|
|
13458
14659
|
}
|
|
13459
14660
|
const agentsDir = join32(rootDir, AGENTS_DIR);
|
|
13460
14661
|
const manifest = await readManifest(rootDir);
|
|
13461
14662
|
if (!manifest) {
|
|
13462
14663
|
error("No .agents/hatch.json found.");
|
|
13463
|
-
console.log(
|
|
14664
|
+
console.log(chalk11.dim(" Run `npx hatch3r init` to set up your project first.\n"));
|
|
13464
14665
|
throw new HatchError("No .agents/hatch.json found.", 1, "CONFIG_ERROR");
|
|
13465
14666
|
}
|
|
13466
14667
|
const m = manifest;
|
|
@@ -13785,23 +14986,23 @@ async function syncCommand(opts = {}) {
|
|
|
13785
14986
|
}
|
|
13786
14987
|
console.log();
|
|
13787
14988
|
const icons = {
|
|
13788
|
-
created:
|
|
13789
|
-
updated:
|
|
14989
|
+
created: chalk11.green("+"),
|
|
14990
|
+
updated: chalk11.yellow("~"),
|
|
13790
14991
|
// G5: "unchanged" is a no-op action returned by safeWriteFile when the
|
|
13791
14992
|
// computed bytes match the file on disk. We render it the same as
|
|
13792
14993
|
// "skipped" (dim "=") so the human summary remains readable while still
|
|
13793
14994
|
// signalling that nothing changed.
|
|
13794
|
-
unchanged:
|
|
13795
|
-
skipped:
|
|
13796
|
-
"dry-run":
|
|
14995
|
+
unchanged: chalk11.dim("="),
|
|
14996
|
+
skipped: chalk11.dim("="),
|
|
14997
|
+
"dry-run": chalk11.cyan("?")
|
|
13797
14998
|
};
|
|
13798
14999
|
const compactedResults = compactPhaseOutput(results);
|
|
13799
15000
|
const summaryLines = compactedResults.map((r) => {
|
|
13800
15001
|
if (typeof r === "string") {
|
|
13801
|
-
return
|
|
15002
|
+
return chalk11.dim(r);
|
|
13802
15003
|
}
|
|
13803
|
-
const icon = icons[r.action] ??
|
|
13804
|
-
return `${icon} ${r.path} ${
|
|
15004
|
+
const icon = icons[r.action] ?? chalk11.dim(" ");
|
|
15005
|
+
return `${icon} ${r.path} ${chalk11.dim(`(${r.action})`)}`;
|
|
13805
15006
|
});
|
|
13806
15007
|
if (isPipelineTimedOut(pipelineState)) {
|
|
13807
15008
|
const { report } = terminatePipeline(pipelineState);
|
|
@@ -13813,11 +15014,11 @@ async function syncCommand(opts = {}) {
|
|
|
13813
15014
|
const before = diffBefore.get(filePath) ?? null;
|
|
13814
15015
|
const after = diffAfter.get(filePath) ?? null;
|
|
13815
15016
|
if (before === null && after !== null) {
|
|
13816
|
-
diffLines.push(`${
|
|
15017
|
+
diffLines.push(`${chalk11.green("+ added")} ${filePath}`);
|
|
13817
15018
|
} else if (before !== null && after !== null && before !== after) {
|
|
13818
|
-
diffLines.push(`${
|
|
15019
|
+
diffLines.push(`${chalk11.yellow("~ modified")} ${filePath}`);
|
|
13819
15020
|
} else if (before !== null && after !== null && before === after) {
|
|
13820
|
-
diffLines.push(`${
|
|
15021
|
+
diffLines.push(`${chalk11.dim("= unchanged")} ${filePath}`);
|
|
13821
15022
|
}
|
|
13822
15023
|
}
|
|
13823
15024
|
if (diffLines.length > 0) {
|
|
@@ -13848,7 +15049,7 @@ async function syncCommand(opts = {}) {
|
|
|
13848
15049
|
const syncableCount = wsManifest.repos.filter((r) => r.sync).length;
|
|
13849
15050
|
if (!syncReposRequested && !syncOnSync) {
|
|
13850
15051
|
if (syncableCount > 0) {
|
|
13851
|
-
info(`Workspace: ${syncableCount} repo(s) available for sync. Run ${
|
|
15052
|
+
info(`Workspace: ${syncableCount} repo(s) available for sync. Run ${chalk11.bold("hatch3r sync --repos")} to propagate.`);
|
|
13852
15053
|
}
|
|
13853
15054
|
return;
|
|
13854
15055
|
}
|
|
@@ -13887,12 +15088,12 @@ init_version();
|
|
|
13887
15088
|
init_customization();
|
|
13888
15089
|
init_content();
|
|
13889
15090
|
init_paths();
|
|
13890
|
-
import { readdir as
|
|
15091
|
+
import { readdir as readdir18, readFile as readFile28, access as access11, stat as stat11 } from "fs/promises";
|
|
13891
15092
|
import { existsSync as existsSync4 } from "fs";
|
|
13892
|
-
import { dirname as dirname18, join as
|
|
15093
|
+
import { dirname as dirname18, join as join37, posix as posix4 } from "path";
|
|
13893
15094
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
13894
|
-
import
|
|
13895
|
-
import { parse as
|
|
15095
|
+
import chalk12 from "chalk";
|
|
15096
|
+
import { parse as parseYaml6 } from "yaml";
|
|
13896
15097
|
|
|
13897
15098
|
// src/content/learningsValidation.ts
|
|
13898
15099
|
init_customization();
|
|
@@ -14035,6 +15236,306 @@ async function validateLearningsDirectory(learningsDir) {
|
|
|
14035
15236
|
};
|
|
14036
15237
|
}
|
|
14037
15238
|
|
|
15239
|
+
// src/content/handoffs/index.ts
|
|
15240
|
+
init_safeWrite();
|
|
15241
|
+
import { mkdir as mkdir10, readdir as readdir16, readFile as readFile26, stat as stat10, unlink as unlink4 } from "fs/promises";
|
|
15242
|
+
import { join as join35 } from "path";
|
|
15243
|
+
import { parse as parseYaml5, stringify as stringifyYaml } from "yaml";
|
|
15244
|
+
|
|
15245
|
+
// src/content/handoffs/validation.ts
|
|
15246
|
+
import { createHash as createHash5, randomBytes as randomBytes3 } from "crypto";
|
|
15247
|
+
import { readdir as readdir15, readFile as readFile25, stat as stat9 } from "fs/promises";
|
|
15248
|
+
import { join as join34 } from "path";
|
|
15249
|
+
import { parse as parseYaml4 } from "yaml";
|
|
15250
|
+
|
|
15251
|
+
// src/content/handoffs/schema.ts
|
|
15252
|
+
var HANDOFF_STATUSES = [
|
|
15253
|
+
"open",
|
|
15254
|
+
"in-progress",
|
|
15255
|
+
"blocked",
|
|
15256
|
+
"handed-off",
|
|
15257
|
+
"resumed",
|
|
15258
|
+
"completed",
|
|
15259
|
+
"archived"
|
|
15260
|
+
];
|
|
15261
|
+
function isHandoffStatus(value) {
|
|
15262
|
+
return typeof value === "string" && HANDOFF_STATUSES.includes(value);
|
|
15263
|
+
}
|
|
15264
|
+
|
|
15265
|
+
// src/content/handoffs/validation.ts
|
|
15266
|
+
var MAX_HANDOFF_BODY_BYTES = 51200;
|
|
15267
|
+
var MAX_HANDOFF_FILE_BYTES = 61440;
|
|
15268
|
+
var MAX_ACTIVE_HANDOFFS_PER_REPO = 25;
|
|
15269
|
+
var MAX_SUMMARY_LENGTH = 200;
|
|
15270
|
+
var REQUIRED_BODY_SECTIONS = [
|
|
15271
|
+
"Problem",
|
|
15272
|
+
"Decisions",
|
|
15273
|
+
"Work Done",
|
|
15274
|
+
"Work Remaining",
|
|
15275
|
+
"Blockers",
|
|
15276
|
+
"Next Steps",
|
|
15277
|
+
"Build & Test Status",
|
|
15278
|
+
"File Manifest"
|
|
15279
|
+
];
|
|
15280
|
+
var HANDOFF_ID_PATTERN = /^[12][0-9]{3}-[01][0-9]-[0-3][0-9]_T[0-2][0-9][0-5][0-9]_[0-9a-f]{5}_[a-z0-9][a-z0-9-]{0,59}$/;
|
|
15281
|
+
var BINARY_CONTENT_PATTERN2 = /\0/;
|
|
15282
|
+
var SECTION_HEADING_PATTERN = /^##\s+(.+?)\s*$/gm;
|
|
15283
|
+
var INTEGRITY_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
15284
|
+
function computeHandoffIntegrity(body) {
|
|
15285
|
+
const trimmed = body.trim();
|
|
15286
|
+
const hash = createHash5("sha256").update(trimmed, "utf-8").digest("hex");
|
|
15287
|
+
return `sha256:${hash}`;
|
|
15288
|
+
}
|
|
15289
|
+
function isHandoffExpired(handoff, now = /* @__PURE__ */ new Date()) {
|
|
15290
|
+
const expires = handoff.frontmatter.expires_after;
|
|
15291
|
+
if (!expires) return false;
|
|
15292
|
+
const expiresMs = Date.parse(expires);
|
|
15293
|
+
if (Number.isNaN(expiresMs)) return false;
|
|
15294
|
+
return expiresMs <= now.getTime();
|
|
15295
|
+
}
|
|
15296
|
+
function extractSectionHeadings(body) {
|
|
15297
|
+
const headings = [];
|
|
15298
|
+
SECTION_HEADING_PATTERN.lastIndex = 0;
|
|
15299
|
+
let match;
|
|
15300
|
+
while ((match = SECTION_HEADING_PATTERN.exec(body)) !== null) {
|
|
15301
|
+
headings.push(match[1].trim());
|
|
15302
|
+
}
|
|
15303
|
+
return headings;
|
|
15304
|
+
}
|
|
15305
|
+
function scanForInjectionPatterns(body) {
|
|
15306
|
+
const hits = [];
|
|
15307
|
+
for (const { patternId, pattern } of LEARNINGS_INJECTION_PATTERNS) {
|
|
15308
|
+
if (pattern.test(body)) hits.push(patternId);
|
|
15309
|
+
}
|
|
15310
|
+
return hits;
|
|
15311
|
+
}
|
|
15312
|
+
function validateHandoffContent(handoff, options = {}) {
|
|
15313
|
+
const errors = [];
|
|
15314
|
+
const warnings = [];
|
|
15315
|
+
const driftWarnings = [];
|
|
15316
|
+
const fm = handoff.frontmatter;
|
|
15317
|
+
if (typeof handoff.body !== "string" || handoff.body.trim().length === 0) {
|
|
15318
|
+
errors.push("Handoff body is empty.");
|
|
15319
|
+
} else if (BINARY_CONTENT_PATTERN2.test(handoff.body)) {
|
|
15320
|
+
errors.push(
|
|
15321
|
+
"Handoff body contains binary content (null bytes detected). Only UTF-8 text is allowed."
|
|
15322
|
+
);
|
|
15323
|
+
} else {
|
|
15324
|
+
const bodyBytes = Buffer.byteLength(handoff.body, "utf-8");
|
|
15325
|
+
if (bodyBytes > MAX_HANDOFF_BODY_BYTES) {
|
|
15326
|
+
errors.push(
|
|
15327
|
+
`Handoff body exceeds ${MAX_HANDOFF_BODY_BYTES} byte limit (${bodyBytes} bytes). Split or compact the handoff.`
|
|
15328
|
+
);
|
|
15329
|
+
}
|
|
15330
|
+
}
|
|
15331
|
+
if (typeof fm.id !== "string" || !HANDOFF_ID_PATTERN.test(fm.id)) {
|
|
15332
|
+
errors.push(
|
|
15333
|
+
`Handoff id is missing or malformed (expected pattern <YYYY-MM-DD>_T<HHmm>_<5hex>_<slug>, got "${String(fm.id)}").`
|
|
15334
|
+
);
|
|
15335
|
+
}
|
|
15336
|
+
if (fm.type !== "handoff") {
|
|
15337
|
+
errors.push(`Handoff frontmatter.type must be "handoff" (got "${String(fm.type)}").`);
|
|
15338
|
+
}
|
|
15339
|
+
if (typeof fm.created !== "string" || Number.isNaN(Date.parse(fm.created))) {
|
|
15340
|
+
errors.push(`Handoff frontmatter.created must be ISO-8601 (got "${String(fm.created)}").`);
|
|
15341
|
+
}
|
|
15342
|
+
if (typeof fm.updated !== "string" || Number.isNaN(Date.parse(fm.updated))) {
|
|
15343
|
+
errors.push(`Handoff frontmatter.updated must be ISO-8601 (got "${String(fm.updated)}").`);
|
|
15344
|
+
}
|
|
15345
|
+
if (!isHandoffStatus(fm.status)) {
|
|
15346
|
+
errors.push(`Handoff status is invalid: "${String(fm.status)}".`);
|
|
15347
|
+
}
|
|
15348
|
+
if (typeof fm.source_agent !== "string" || fm.source_agent.length === 0) {
|
|
15349
|
+
errors.push("Handoff source_agent is missing.");
|
|
15350
|
+
}
|
|
15351
|
+
if (typeof fm.target_agent !== "string" || fm.target_agent.length === 0) {
|
|
15352
|
+
errors.push("Handoff target_agent is missing.");
|
|
15353
|
+
} else if (fm.target_agent === "any") {
|
|
15354
|
+
warnings.push(
|
|
15355
|
+
'target_agent is "any" \u2014 explicit target_agent recommended to avoid handoff loops.'
|
|
15356
|
+
);
|
|
15357
|
+
}
|
|
15358
|
+
if (typeof fm.git_ref !== "string" || fm.git_ref.length === 0) {
|
|
15359
|
+
errors.push("Handoff git_ref is missing.");
|
|
15360
|
+
}
|
|
15361
|
+
if (typeof fm.branch !== "string" || fm.branch.length === 0) {
|
|
15362
|
+
errors.push("Handoff branch is missing.");
|
|
15363
|
+
}
|
|
15364
|
+
if (typeof fm.confidence !== "number" || Number.isNaN(fm.confidence) || fm.confidence < 0 || fm.confidence > 1) {
|
|
15365
|
+
errors.push(`Handoff confidence must be 0..1 inclusive (got ${String(fm.confidence)}).`);
|
|
15366
|
+
}
|
|
15367
|
+
if (typeof fm.completeness !== "number" || Number.isNaN(fm.completeness) || fm.completeness < 0 || fm.completeness > 1) {
|
|
15368
|
+
errors.push(`Handoff completeness must be 0..1 inclusive (got ${String(fm.completeness)}).`);
|
|
15369
|
+
}
|
|
15370
|
+
if (typeof fm.integrity !== "string" || !INTEGRITY_PATTERN.test(fm.integrity)) {
|
|
15371
|
+
errors.push(
|
|
15372
|
+
`Handoff integrity must be "sha256:<64-hex>" (got "${String(fm.integrity)}").`
|
|
15373
|
+
);
|
|
15374
|
+
} else if (typeof handoff.body === "string" && handoff.body.trim().length > 0) {
|
|
15375
|
+
const computed = computeHandoffIntegrity(handoff.body);
|
|
15376
|
+
if (computed !== fm.integrity) {
|
|
15377
|
+
errors.push(
|
|
15378
|
+
`Handoff integrity hash does not match body content (declared ${fm.integrity}, computed ${computed}).`
|
|
15379
|
+
);
|
|
15380
|
+
}
|
|
15381
|
+
}
|
|
15382
|
+
if (typeof fm.summary === "string" && fm.summary.length > MAX_SUMMARY_LENGTH) {
|
|
15383
|
+
warnings.push(
|
|
15384
|
+
`Handoff summary exceeds ${MAX_SUMMARY_LENGTH} chars (${fm.summary.length} chars). Compact the summary; full context belongs in the body.`
|
|
15385
|
+
);
|
|
15386
|
+
}
|
|
15387
|
+
if (typeof handoff.body === "string" && handoff.body.length > 0) {
|
|
15388
|
+
const present = new Set(extractSectionHeadings(handoff.body));
|
|
15389
|
+
for (const required of REQUIRED_BODY_SECTIONS) {
|
|
15390
|
+
if (!present.has(required)) {
|
|
15391
|
+
errors.push(`Handoff body is missing required section "## ${required}".`);
|
|
15392
|
+
}
|
|
15393
|
+
}
|
|
15394
|
+
if (!options.skipInjectionScan) {
|
|
15395
|
+
const hits = scanForInjectionPatterns(handoff.body);
|
|
15396
|
+
for (const patternId of hits) {
|
|
15397
|
+
errors.push(
|
|
15398
|
+
`Handoff body matches injection pattern ${patternId}. Review and sanitize before consuming.`
|
|
15399
|
+
);
|
|
15400
|
+
}
|
|
15401
|
+
}
|
|
15402
|
+
}
|
|
15403
|
+
if (isHandoffExpired(handoff, options.now)) {
|
|
15404
|
+
driftWarnings.push(
|
|
15405
|
+
`Handoff expired at ${String(fm.expires_after)}. Archive or refresh before resuming.`
|
|
15406
|
+
);
|
|
15407
|
+
}
|
|
15408
|
+
if (typeof options.currentGitRef === "string" && options.currentGitRef.length > 0) {
|
|
15409
|
+
if (fm.git_ref !== options.currentGitRef) {
|
|
15410
|
+
driftWarnings.push(
|
|
15411
|
+
`Handoff git_ref "${fm.git_ref}" differs from current "${options.currentGitRef}". Code has moved since the handoff was authored.`
|
|
15412
|
+
);
|
|
15413
|
+
}
|
|
15414
|
+
}
|
|
15415
|
+
const result = {
|
|
15416
|
+
valid: errors.length === 0,
|
|
15417
|
+
errors,
|
|
15418
|
+
warnings
|
|
15419
|
+
};
|
|
15420
|
+
if (driftWarnings.length > 0) result.driftWarnings = driftWarnings;
|
|
15421
|
+
return result;
|
|
15422
|
+
}
|
|
15423
|
+
async function loadHandoffFile(filePath) {
|
|
15424
|
+
let raw;
|
|
15425
|
+
let readError = null;
|
|
15426
|
+
try {
|
|
15427
|
+
const fileStat = await stat9(filePath);
|
|
15428
|
+
if (fileStat.size > MAX_HANDOFF_FILE_BYTES) {
|
|
15429
|
+
return {
|
|
15430
|
+
error: `Handoff file "${filePath}" exceeds ${MAX_HANDOFF_FILE_BYTES} byte limit (${fileStat.size} bytes).`
|
|
15431
|
+
};
|
|
15432
|
+
}
|
|
15433
|
+
raw = await readFile25(filePath, "utf-8");
|
|
15434
|
+
} catch (err) {
|
|
15435
|
+
readError = `Failed to read handoff "${filePath}": ${err.message}`;
|
|
15436
|
+
raw = "";
|
|
15437
|
+
}
|
|
15438
|
+
if (readError !== null) {
|
|
15439
|
+
return { error: readError };
|
|
15440
|
+
}
|
|
15441
|
+
if (!raw.startsWith("---")) {
|
|
15442
|
+
return { error: `Handoff "${filePath}" is missing YAML frontmatter delimiter.` };
|
|
15443
|
+
}
|
|
15444
|
+
const afterFirst = raw.slice(3);
|
|
15445
|
+
const newlineAfterFirst = afterFirst.indexOf("\n");
|
|
15446
|
+
if (newlineAfterFirst < 0) {
|
|
15447
|
+
return { error: `Handoff "${filePath}" has malformed frontmatter delimiter.` };
|
|
15448
|
+
}
|
|
15449
|
+
const fmStart = newlineAfterFirst + 1;
|
|
15450
|
+
const closeMatch = afterFirst.slice(fmStart).match(/\n---\s*(?:\r?\n|$)/);
|
|
15451
|
+
if (!closeMatch || typeof closeMatch.index !== "number") {
|
|
15452
|
+
return { error: `Handoff "${filePath}" frontmatter is not closed by "---".` };
|
|
15453
|
+
}
|
|
15454
|
+
const yamlBlock = afterFirst.slice(fmStart, fmStart + closeMatch.index);
|
|
15455
|
+
const bodyStart = fmStart + closeMatch.index + closeMatch[0].length;
|
|
15456
|
+
const body = afterFirst.slice(bodyStart);
|
|
15457
|
+
let parsed;
|
|
15458
|
+
let parseError = null;
|
|
15459
|
+
try {
|
|
15460
|
+
parsed = parseYaml4(yamlBlock);
|
|
15461
|
+
} catch (err) {
|
|
15462
|
+
parseError = `Handoff "${filePath}" has invalid YAML frontmatter: ${err.message}`;
|
|
15463
|
+
parsed = null;
|
|
15464
|
+
}
|
|
15465
|
+
if (parseError !== null) {
|
|
15466
|
+
return { error: parseError };
|
|
15467
|
+
}
|
|
15468
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
15469
|
+
return { error: `Handoff "${filePath}" frontmatter is not a YAML mapping.` };
|
|
15470
|
+
}
|
|
15471
|
+
return {
|
|
15472
|
+
handoff: {
|
|
15473
|
+
frontmatter: parsed,
|
|
15474
|
+
body,
|
|
15475
|
+
filePath
|
|
15476
|
+
}
|
|
15477
|
+
};
|
|
15478
|
+
}
|
|
15479
|
+
async function validateHandoffsDirectory(activeDir, options = {}) {
|
|
15480
|
+
const errors = [];
|
|
15481
|
+
const warnings = [];
|
|
15482
|
+
async function listMdFiles(dir) {
|
|
15483
|
+
try {
|
|
15484
|
+
const entries = await readdir15(dir);
|
|
15485
|
+
return entries.filter((f) => f.endsWith(".md"));
|
|
15486
|
+
} catch (err) {
|
|
15487
|
+
const code = err.code;
|
|
15488
|
+
if (code === "ENOENT") return null;
|
|
15489
|
+
warnings.push(`Failed to list handoff dir "${dir}": ${err.message}`);
|
|
15490
|
+
return null;
|
|
15491
|
+
}
|
|
15492
|
+
}
|
|
15493
|
+
const activeFiles = await listMdFiles(activeDir) ?? [];
|
|
15494
|
+
if (activeFiles.length > MAX_ACTIVE_HANDOFFS_PER_REPO) {
|
|
15495
|
+
warnings.push(
|
|
15496
|
+
`Active handoff count (${activeFiles.length}) exceeds soft cap (${MAX_ACTIVE_HANDOFFS_PER_REPO}). Archive completed handoffs to reduce drift surface.`
|
|
15497
|
+
);
|
|
15498
|
+
}
|
|
15499
|
+
for (const file of activeFiles) {
|
|
15500
|
+
const { handoff, error: error2 } = await loadHandoffFile(join34(activeDir, file));
|
|
15501
|
+
if (error2 || !handoff) {
|
|
15502
|
+
errors.push(error2 ?? `Failed to load handoff "${file}".`);
|
|
15503
|
+
continue;
|
|
15504
|
+
}
|
|
15505
|
+
const r = validateHandoffContent(handoff, { currentGitRef: options.currentGitRef });
|
|
15506
|
+
for (const e of r.errors) errors.push(`[${file}] ${e}`);
|
|
15507
|
+
for (const w of r.warnings) warnings.push(`[${file}] ${w}`);
|
|
15508
|
+
if (r.driftWarnings) {
|
|
15509
|
+
for (const dw of r.driftWarnings) warnings.push(`[${file}] ${dw}`);
|
|
15510
|
+
}
|
|
15511
|
+
}
|
|
15512
|
+
let archivedCount = 0;
|
|
15513
|
+
if (options.archivedDir) {
|
|
15514
|
+
const archivedFiles = await listMdFiles(options.archivedDir) ?? [];
|
|
15515
|
+
archivedCount = archivedFiles.length;
|
|
15516
|
+
for (const file of archivedFiles) {
|
|
15517
|
+
const { handoff, error: error2 } = await loadHandoffFile(join34(options.archivedDir, file));
|
|
15518
|
+
if (error2 || !handoff) {
|
|
15519
|
+
errors.push(error2 ?? `Failed to load archived handoff "${file}".`);
|
|
15520
|
+
continue;
|
|
15521
|
+
}
|
|
15522
|
+
const r = validateHandoffContent(handoff);
|
|
15523
|
+
for (const e of r.errors) errors.push(`[archived/${file}] ${e}`);
|
|
15524
|
+
for (const w of r.warnings) warnings.push(`[archived/${file}] ${w}`);
|
|
15525
|
+
}
|
|
15526
|
+
}
|
|
15527
|
+
return {
|
|
15528
|
+
valid: errors.length === 0,
|
|
15529
|
+
errors,
|
|
15530
|
+
warnings,
|
|
15531
|
+
activeCount: activeFiles.length,
|
|
15532
|
+
archivedCount
|
|
15533
|
+
};
|
|
15534
|
+
}
|
|
15535
|
+
|
|
15536
|
+
// src/content/handoffs/index.ts
|
|
15537
|
+
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
15538
|
+
|
|
14038
15539
|
// src/cli/commands/validate.ts
|
|
14039
15540
|
init_customize();
|
|
14040
15541
|
init_mcpEnv();
|
|
@@ -14171,8 +15672,8 @@ function detectSecrets(envVars) {
|
|
|
14171
15672
|
|
|
14172
15673
|
// src/pipeline/complianceVerification.ts
|
|
14173
15674
|
init_agentToolAllowlist();
|
|
14174
|
-
import { readFile as
|
|
14175
|
-
import { dirname as dirname17, join as
|
|
15675
|
+
import { readFile as readFile27, readdir as readdir17 } from "fs/promises";
|
|
15676
|
+
import { dirname as dirname17, join as join36 } from "path";
|
|
14176
15677
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
14177
15678
|
|
|
14178
15679
|
// src/pipeline/reviewLoop.ts
|
|
@@ -14211,13 +15712,13 @@ var RESILIENCE_MODULES = [
|
|
|
14211
15712
|
var __dirname5 = dirname17(fileURLToPath7(import.meta.url));
|
|
14212
15713
|
async function resolveCommandsDir() {
|
|
14213
15714
|
const candidates = [
|
|
14214
|
-
|
|
14215
|
-
|
|
14216
|
-
|
|
15715
|
+
join36(__dirname5, "..", "cli", "commands"),
|
|
15716
|
+
join36(__dirname5, "..", "..", "src", "cli", "commands"),
|
|
15717
|
+
join36(__dirname5, "..", "cli")
|
|
14217
15718
|
];
|
|
14218
15719
|
for (const candidate of candidates) {
|
|
14219
15720
|
try {
|
|
14220
|
-
const entries = await
|
|
15721
|
+
const entries = await readdir17(candidate);
|
|
14221
15722
|
if (entries.length > 0) return candidate;
|
|
14222
15723
|
} catch {
|
|
14223
15724
|
}
|
|
@@ -14230,11 +15731,11 @@ async function detectResilienceInvocations() {
|
|
|
14230
15731
|
if (!commandsDir) return invoked;
|
|
14231
15732
|
let entries;
|
|
14232
15733
|
try {
|
|
14233
|
-
entries = await
|
|
15734
|
+
entries = await readdir17(commandsDir, { recursive: true });
|
|
14234
15735
|
} catch {
|
|
14235
15736
|
return invoked;
|
|
14236
15737
|
}
|
|
14237
|
-
const files = entries.filter((e) => typeof e === "string" && (e.endsWith(".ts") || e.endsWith(".js"))).map((e) =>
|
|
15738
|
+
const files = entries.filter((e) => typeof e === "string" && (e.endsWith(".ts") || e.endsWith(".js"))).map((e) => join36(commandsDir, e));
|
|
14238
15739
|
const patterns = {};
|
|
14239
15740
|
for (const mod of RESILIENCE_MODULES) {
|
|
14240
15741
|
patterns[mod] = new RegExp(`pipeline/${mod}(?:\\.js)?["']`);
|
|
@@ -14242,7 +15743,7 @@ async function detectResilienceInvocations() {
|
|
|
14242
15743
|
for (const file of files) {
|
|
14243
15744
|
let contents;
|
|
14244
15745
|
try {
|
|
14245
|
-
contents = await
|
|
15746
|
+
contents = await readFile27(file, "utf-8");
|
|
14246
15747
|
} catch {
|
|
14247
15748
|
continue;
|
|
14248
15749
|
}
|
|
@@ -14403,6 +15904,8 @@ var DEFAULT_KNOWN_AGENTS = /* @__PURE__ */ new Set([
|
|
|
14403
15904
|
"hatch3r-devops",
|
|
14404
15905
|
"hatch3r-docs-writer",
|
|
14405
15906
|
"hatch3r-fixer",
|
|
15907
|
+
"hatch3r-handoff-loader",
|
|
15908
|
+
"hatch3r-handoff-preparer",
|
|
14406
15909
|
"hatch3r-implementer",
|
|
14407
15910
|
"hatch3r-learnings-loader",
|
|
14408
15911
|
"hatch3r-lint-fixer",
|
|
@@ -14434,7 +15937,7 @@ async function validateManifest2(rootDir, manifest, result) {
|
|
|
14434
15937
|
if (!manifest.tools || manifest.tools.length === 0) result.warnings.push("hatch.json: no tools configured");
|
|
14435
15938
|
for (const managedFile of manifest.managedFiles ?? []) {
|
|
14436
15939
|
try {
|
|
14437
|
-
await access11(
|
|
15940
|
+
await access11(join37(rootDir, managedFile));
|
|
14438
15941
|
} catch (err) {
|
|
14439
15942
|
if (err.code !== "ENOENT") throw err;
|
|
14440
15943
|
result.warnings.push(`Managed file missing from disk: ${managedFile}`);
|
|
@@ -14446,7 +15949,7 @@ async function validateDirectories(agentsDir, result) {
|
|
|
14446
15949
|
const optionalDirs = ["commands", "prompts", "mcp", "policy", "github-agents"];
|
|
14447
15950
|
for (const dir of requiredDirs) {
|
|
14448
15951
|
try {
|
|
14449
|
-
await access11(
|
|
15952
|
+
await access11(join37(agentsDir, dir));
|
|
14450
15953
|
} catch (err) {
|
|
14451
15954
|
if (err.code !== "ENOENT") throw err;
|
|
14452
15955
|
result.errors.push(`Required directory missing: .agents/${dir}/`);
|
|
@@ -14454,7 +15957,7 @@ async function validateDirectories(agentsDir, result) {
|
|
|
14454
15957
|
}
|
|
14455
15958
|
for (const dir of optionalDirs) {
|
|
14456
15959
|
try {
|
|
14457
|
-
await access11(
|
|
15960
|
+
await access11(join37(agentsDir, dir));
|
|
14458
15961
|
} catch (err) {
|
|
14459
15962
|
if (err.code !== "ENOENT") throw err;
|
|
14460
15963
|
verboseWarn(result, `Optional directory missing: .agents/${dir}/`);
|
|
@@ -14465,13 +15968,13 @@ async function validateFrontmatter(agentsDir, result) {
|
|
|
14465
15968
|
const requiredDirs = ["agents", "skills", "rules"];
|
|
14466
15969
|
const optionalDirs = ["commands", "prompts", "mcp", "policy", "github-agents"];
|
|
14467
15970
|
for (const dir of [...requiredDirs, ...optionalDirs]) {
|
|
14468
|
-
const dirPath =
|
|
15971
|
+
const dirPath = join37(agentsDir, dir);
|
|
14469
15972
|
try {
|
|
14470
|
-
const entries = await
|
|
15973
|
+
const entries = await readdir18(dirPath, { withFileTypes: true });
|
|
14471
15974
|
for (const entry of entries) {
|
|
14472
15975
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
14473
|
-
const filePath =
|
|
14474
|
-
const content = await
|
|
15976
|
+
const filePath = join37(dirPath, entry.name);
|
|
15977
|
+
const content = await readFile28(filePath, "utf-8");
|
|
14475
15978
|
if (!content.startsWith("---")) {
|
|
14476
15979
|
result.warnings.push(`Missing frontmatter: .agents/${dir}/${entry.name}`);
|
|
14477
15980
|
} else {
|
|
@@ -14480,7 +15983,7 @@ async function validateFrontmatter(agentsDir, result) {
|
|
|
14480
15983
|
result.errors.push(`Invalid frontmatter (no closing ---): .agents/${dir}/${entry.name}`);
|
|
14481
15984
|
} else {
|
|
14482
15985
|
const frontmatter = content.slice(3, endIdx).trim();
|
|
14483
|
-
const parsedFm =
|
|
15986
|
+
const parsedFm = parseYaml6(frontmatter);
|
|
14484
15987
|
const idField = dir === "github-agents" ? "name" : "id";
|
|
14485
15988
|
if (!parsedFm || typeof parsedFm !== "object" || !parsedFm[idField]) {
|
|
14486
15989
|
result.warnings.push(`Missing '${idField}' in frontmatter: .agents/${dir}/${entry.name}`);
|
|
@@ -14498,7 +16001,7 @@ async function validateFrontmatter(agentsDir, result) {
|
|
|
14498
16001
|
}
|
|
14499
16002
|
} else if (entry.isDirectory()) {
|
|
14500
16003
|
if (dir !== "skills") continue;
|
|
14501
|
-
const skillPath =
|
|
16004
|
+
const skillPath = join37(dirPath, entry.name, "SKILL.md");
|
|
14502
16005
|
try {
|
|
14503
16006
|
await access11(skillPath);
|
|
14504
16007
|
} catch (err) {
|
|
@@ -14512,7 +16015,7 @@ async function validateFrontmatter(agentsDir, result) {
|
|
|
14512
16015
|
}
|
|
14513
16016
|
}
|
|
14514
16017
|
try {
|
|
14515
|
-
await access11(
|
|
16018
|
+
await access11(join37(agentsDir, "AGENTS.md"));
|
|
14516
16019
|
} catch (err) {
|
|
14517
16020
|
if (err.code !== "ENOENT") throw err;
|
|
14518
16021
|
result.warnings.push("Missing .agents/AGENTS.md");
|
|
@@ -14634,29 +16137,29 @@ async function validateManagedFilePrefixes(manifest, result) {
|
|
|
14634
16137
|
}
|
|
14635
16138
|
async function validateHooks(agentsDir, manifest, result) {
|
|
14636
16139
|
if (!manifest.features.hooks) return;
|
|
14637
|
-
const hooksDir =
|
|
16140
|
+
const hooksDir = join37(agentsDir, "hooks");
|
|
14638
16141
|
try {
|
|
14639
|
-
const hookFiles = await
|
|
16142
|
+
const hookFiles = await readdir18(hooksDir);
|
|
14640
16143
|
const mdHooks = hookFiles.filter((f) => f.endsWith(".md"));
|
|
14641
16144
|
if (mdHooks.length === 0) {
|
|
14642
16145
|
result.warnings.push("Hooks feature enabled but no hook definitions found in .agents/hooks/");
|
|
14643
16146
|
}
|
|
14644
16147
|
let agentFiles;
|
|
14645
16148
|
try {
|
|
14646
|
-
const agentEntries = await
|
|
16149
|
+
const agentEntries = await readdir18(join37(agentsDir, "agents"));
|
|
14647
16150
|
agentFiles = new Set(agentEntries.filter((f) => f.endsWith(".md")));
|
|
14648
16151
|
} catch (err) {
|
|
14649
16152
|
if (err.code !== "ENOENT") throw err;
|
|
14650
16153
|
}
|
|
14651
16154
|
for (const hookFile of mdHooks) {
|
|
14652
|
-
const hookContent = await
|
|
16155
|
+
const hookContent = await readFile28(join37(hooksDir, hookFile), "utf-8");
|
|
14653
16156
|
if (!hookContent.startsWith("---")) {
|
|
14654
16157
|
result.warnings.push(`Hook missing frontmatter: .agents/hooks/${hookFile}`);
|
|
14655
16158
|
continue;
|
|
14656
16159
|
}
|
|
14657
16160
|
const endIdx = hookContent.indexOf("---", 3);
|
|
14658
16161
|
if (endIdx === -1) continue;
|
|
14659
|
-
const fm =
|
|
16162
|
+
const fm = parseYaml6(hookContent.slice(3, endIdx).trim());
|
|
14660
16163
|
if (fm?.event && typeof fm.event === "string") {
|
|
14661
16164
|
if (!isValidHookEvent(fm.event)) {
|
|
14662
16165
|
result.errors.push(`Hook "${hookFile}" has invalid event "${fm.event}". Valid events: pre-commit, post-merge, ci-failure, file-save, session-start, pre-push`);
|
|
@@ -14681,9 +16184,9 @@ async function validateHooks(agentsDir, manifest, result) {
|
|
|
14681
16184
|
}
|
|
14682
16185
|
async function validateMcp(agentsDir, manifest, result) {
|
|
14683
16186
|
if (!manifest.features.mcp || manifest.mcp.servers.length === 0) return;
|
|
14684
|
-
const mcpPath =
|
|
16187
|
+
const mcpPath = join37(agentsDir, "mcp", "mcp.json");
|
|
14685
16188
|
try {
|
|
14686
|
-
const mcpContent = await
|
|
16189
|
+
const mcpContent = await readFile28(mcpPath, "utf-8");
|
|
14687
16190
|
const mcpParsed = JSON.parse(mcpContent);
|
|
14688
16191
|
if (!mcpParsed.mcpServers || typeof mcpParsed.mcpServers !== "object") {
|
|
14689
16192
|
result.errors.push("MCP config missing 'mcpServers' key");
|
|
@@ -14696,6 +16199,18 @@ async function validateMcp(agentsDir, manifest, result) {
|
|
|
14696
16199
|
}
|
|
14697
16200
|
}
|
|
14698
16201
|
}
|
|
16202
|
+
async function validateCliTools(manifest, result) {
|
|
16203
|
+
const cli = manifest.cliTools;
|
|
16204
|
+
if (!cli?.enabled || cli.selected.length === 0) return;
|
|
16205
|
+
const detection = await detectCliTools(cli.selected);
|
|
16206
|
+
for (const r of detection) {
|
|
16207
|
+
if (!r.installed) {
|
|
16208
|
+
result.warnings.push(
|
|
16209
|
+
`CLI tool '${r.id}' not found on PATH \u2014 run \`npx hatch3r cli-tools install\``
|
|
16210
|
+
);
|
|
16211
|
+
}
|
|
16212
|
+
}
|
|
16213
|
+
}
|
|
14699
16214
|
async function validateModels(manifest, result) {
|
|
14700
16215
|
if (!manifest.models) return;
|
|
14701
16216
|
if (manifest.models.default && typeof manifest.models.default !== "string") {
|
|
@@ -14738,21 +16253,21 @@ async function validateCustomizeYaml(rootDir, result) {
|
|
|
14738
16253
|
enabled: "boolean"
|
|
14739
16254
|
};
|
|
14740
16255
|
for (const { dir } of CUSTOMIZATION_TYPES) {
|
|
14741
|
-
const customDir =
|
|
16256
|
+
const customDir = join37(rootDir, ".hatch3r", dir);
|
|
14742
16257
|
let files;
|
|
14743
16258
|
try {
|
|
14744
|
-
files = await
|
|
16259
|
+
files = await readdir18(customDir);
|
|
14745
16260
|
} catch (err) {
|
|
14746
16261
|
if (err.code === "ENOENT") continue;
|
|
14747
16262
|
throw err;
|
|
14748
16263
|
}
|
|
14749
16264
|
const yamlFiles = files.filter((f) => f.endsWith(".customize.yaml"));
|
|
14750
16265
|
for (const file of yamlFiles) {
|
|
14751
|
-
const filePath =
|
|
16266
|
+
const filePath = join37(customDir, file);
|
|
14752
16267
|
const itemId = file.replace(".customize.yaml", "");
|
|
14753
16268
|
let raw;
|
|
14754
16269
|
try {
|
|
14755
|
-
raw = await
|
|
16270
|
+
raw = await readFile28(filePath, "utf-8");
|
|
14756
16271
|
} catch {
|
|
14757
16272
|
continue;
|
|
14758
16273
|
}
|
|
@@ -14764,7 +16279,7 @@ async function validateCustomizeYaml(rootDir, result) {
|
|
|
14764
16279
|
}
|
|
14765
16280
|
let parsed;
|
|
14766
16281
|
try {
|
|
14767
|
-
parsed =
|
|
16282
|
+
parsed = parseYaml6(raw);
|
|
14768
16283
|
} catch {
|
|
14769
16284
|
result.errors.push(
|
|
14770
16285
|
`Invalid YAML syntax in .hatch3r/${dir}/${file}`
|
|
@@ -14815,13 +16330,13 @@ async function validateCustomizeYaml(rootDir, result) {
|
|
|
14815
16330
|
}
|
|
14816
16331
|
async function validateCustomizations(rootDir, agentsDir, manifest, result) {
|
|
14817
16332
|
for (const { dir, canonical } of CUSTOMIZATION_TYPES) {
|
|
14818
|
-
const customDir =
|
|
16333
|
+
const customDir = join37(rootDir, ".hatch3r", dir);
|
|
14819
16334
|
try {
|
|
14820
|
-
const customFiles = await
|
|
16335
|
+
const customFiles = await readdir18(customDir);
|
|
14821
16336
|
for (const file of customFiles) {
|
|
14822
16337
|
if (file.endsWith(".customize.yaml")) {
|
|
14823
16338
|
const itemId = file.replace(".customize.yaml", "");
|
|
14824
|
-
const canonicalPath = canonical === "skills" ?
|
|
16339
|
+
const canonicalPath = canonical === "skills" ? join37(agentsDir, canonical, itemId) : join37(agentsDir, canonical, `${itemId}.md`);
|
|
14825
16340
|
try {
|
|
14826
16341
|
await access11(canonicalPath);
|
|
14827
16342
|
} catch (err) {
|
|
@@ -14838,7 +16353,7 @@ async function validateCustomizations(rootDir, agentsDir, manifest, result) {
|
|
|
14838
16353
|
async function findContentFile(agentsDir, cfg, id) {
|
|
14839
16354
|
const baseId = id.startsWith("cmd-") ? id.slice(4) : id;
|
|
14840
16355
|
if (cfg.strategy === "subdir") {
|
|
14841
|
-
const path =
|
|
16356
|
+
const path = join37(agentsDir, cfg.dir, baseId, "SKILL.md");
|
|
14842
16357
|
try {
|
|
14843
16358
|
await access11(path);
|
|
14844
16359
|
return path;
|
|
@@ -14846,19 +16361,19 @@ async function findContentFile(agentsDir, cfg, id) {
|
|
|
14846
16361
|
return null;
|
|
14847
16362
|
}
|
|
14848
16363
|
}
|
|
14849
|
-
const root =
|
|
16364
|
+
const root = join37(agentsDir, cfg.dir);
|
|
14850
16365
|
const stack = [root];
|
|
14851
16366
|
const mdFiles = [];
|
|
14852
16367
|
while (stack.length > 0) {
|
|
14853
16368
|
const dir = stack.pop();
|
|
14854
16369
|
let entries;
|
|
14855
16370
|
try {
|
|
14856
|
-
entries = await
|
|
16371
|
+
entries = await readdir18(dir, { withFileTypes: true });
|
|
14857
16372
|
} catch {
|
|
14858
16373
|
continue;
|
|
14859
16374
|
}
|
|
14860
16375
|
for (const entry of entries) {
|
|
14861
|
-
const full =
|
|
16376
|
+
const full = join37(dir, entry.name);
|
|
14862
16377
|
if (entry.isDirectory()) {
|
|
14863
16378
|
stack.push(full);
|
|
14864
16379
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -14871,11 +16386,11 @@ async function findContentFile(agentsDir, cfg, id) {
|
|
|
14871
16386
|
}
|
|
14872
16387
|
for (const file of mdFiles) {
|
|
14873
16388
|
try {
|
|
14874
|
-
const raw = await
|
|
16389
|
+
const raw = await readFile28(file, "utf-8");
|
|
14875
16390
|
if (!raw.startsWith("---")) continue;
|
|
14876
16391
|
const endIdx = raw.indexOf("---", 3);
|
|
14877
16392
|
if (endIdx === -1) continue;
|
|
14878
|
-
const fm =
|
|
16393
|
+
const fm = parseYaml6(raw.slice(3, endIdx).trim());
|
|
14879
16394
|
const fmId = fm && typeof fm === "object" && typeof fm.id === "string" ? fm.id : null;
|
|
14880
16395
|
if (fmId && (fmId === id || fmId === baseId)) {
|
|
14881
16396
|
return file;
|
|
@@ -14910,9 +16425,9 @@ async function validateContentConsistency(rootDir, agentsDir, manifest, result)
|
|
|
14910
16425
|
for (const id of ids) allContentIds.add(id);
|
|
14911
16426
|
}
|
|
14912
16427
|
for (const { dir } of CUSTOMIZATION_TYPES) {
|
|
14913
|
-
const customDir =
|
|
16428
|
+
const customDir = join37(rootDir, ".hatch3r", dir);
|
|
14914
16429
|
try {
|
|
14915
|
-
const files = await
|
|
16430
|
+
const files = await readdir18(customDir);
|
|
14916
16431
|
for (const f of files.filter((f2) => f2.endsWith(".customize.yaml") || f2.endsWith(".customize.md"))) {
|
|
14917
16432
|
const itemId = f.replace(/\.customize\.(yaml|md)$/, "");
|
|
14918
16433
|
if (!allContentIds.has(itemId) && !allContentIds.has(`${HATCH3R_PREFIX}${itemId}`) && !allContentIds.has(`cmd-${itemId}`) && !allContentIds.has(`cmd-${HATCH3R_PREFIX}${itemId}`)) {
|
|
@@ -14924,7 +16439,7 @@ async function validateContentConsistency(rootDir, agentsDir, manifest, result)
|
|
|
14924
16439
|
}
|
|
14925
16440
|
}
|
|
14926
16441
|
}
|
|
14927
|
-
const learningsDir =
|
|
16442
|
+
const learningsDir = join37(agentsDir, "learnings");
|
|
14928
16443
|
const learningsResult = await validateLearningsDirectory(learningsDir);
|
|
14929
16444
|
for (const e of learningsResult.errors) {
|
|
14930
16445
|
result.errors.push(e);
|
|
@@ -14932,6 +16447,17 @@ async function validateContentConsistency(rootDir, agentsDir, manifest, result)
|
|
|
14932
16447
|
for (const w of learningsResult.warnings) {
|
|
14933
16448
|
result.warnings.push(w);
|
|
14934
16449
|
}
|
|
16450
|
+
const handoffsActiveDir = join37(agentsDir, "handoffs", "active");
|
|
16451
|
+
const handoffsArchivedDir = join37(agentsDir, "handoffs", "archived");
|
|
16452
|
+
const handoffsResult = await validateHandoffsDirectory(handoffsActiveDir, {
|
|
16453
|
+
archivedDir: handoffsArchivedDir
|
|
16454
|
+
});
|
|
16455
|
+
for (const e of handoffsResult.errors) {
|
|
16456
|
+
result.errors.push(e);
|
|
16457
|
+
}
|
|
16458
|
+
for (const w of handoffsResult.warnings) {
|
|
16459
|
+
result.warnings.push(w);
|
|
16460
|
+
}
|
|
14935
16461
|
}
|
|
14936
16462
|
var USER_CONTENT_ANTI_SLOP = [
|
|
14937
16463
|
"best possible",
|
|
@@ -14961,7 +16487,7 @@ var USER_CONTENT_TYPE_DIRS = {
|
|
|
14961
16487
|
async function validateUserContent(rootDir, agentsDir, result, index) {
|
|
14962
16488
|
const userRoot = resolveUserContentRoot(rootDir);
|
|
14963
16489
|
try {
|
|
14964
|
-
await
|
|
16490
|
+
await stat11(userRoot);
|
|
14965
16491
|
} catch (err) {
|
|
14966
16492
|
if (err.code === "ENOENT") return;
|
|
14967
16493
|
throw err;
|
|
@@ -14970,10 +16496,10 @@ async function validateUserContent(rootDir, agentsDir, result, index) {
|
|
|
14970
16496
|
if (userItems.length === 0) return;
|
|
14971
16497
|
for (const item of userItems) {
|
|
14972
16498
|
const fileLabel = `.agents/user/${item.relativePath}`;
|
|
14973
|
-
const absPath = item.type === "skill" ?
|
|
16499
|
+
const absPath = item.type === "skill" ? join37(userRoot, item.relativePath, "SKILL.md") : join37(userRoot, item.relativePath);
|
|
14974
16500
|
let raw;
|
|
14975
16501
|
try {
|
|
14976
|
-
raw = await
|
|
16502
|
+
raw = await readFile28(absPath, "utf-8");
|
|
14977
16503
|
} catch (err) {
|
|
14978
16504
|
result.errors.push(
|
|
14979
16505
|
`User content unreadable: ${fileLabel} (${err instanceof Error ? err.message : String(err)})`
|
|
@@ -14997,7 +16523,7 @@ async function validateUserContent(rootDir, agentsDir, result, index) {
|
|
|
14997
16523
|
const fmRaw = raw.slice(3, fmEnd).trim();
|
|
14998
16524
|
let fm;
|
|
14999
16525
|
try {
|
|
15000
|
-
fm =
|
|
16526
|
+
fm = parseYaml6(fmRaw);
|
|
15001
16527
|
} catch (err) {
|
|
15002
16528
|
result.errors.push(
|
|
15003
16529
|
`${fileLabel}: YAML parse error in frontmatter \u2014 ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -15057,7 +16583,7 @@ async function validateUserContent(rootDir, agentsDir, result, index) {
|
|
|
15057
16583
|
if (item.type === "rule") {
|
|
15058
16584
|
const mdcPath = absPath.replace(/\.md$/, ".mdc");
|
|
15059
16585
|
try {
|
|
15060
|
-
await
|
|
16586
|
+
await stat11(mdcPath);
|
|
15061
16587
|
} catch (err) {
|
|
15062
16588
|
if (err.code === "ENOENT") {
|
|
15063
16589
|
result.errors.push(
|
|
@@ -15225,16 +16751,16 @@ async function validateDocsCounts(rootDir) {
|
|
|
15225
16751
|
let checked = 0;
|
|
15226
16752
|
const actual = {};
|
|
15227
16753
|
const dirs = [
|
|
15228
|
-
["adapters",
|
|
15229
|
-
["commands",
|
|
15230
|
-
["agents",
|
|
15231
|
-
["skills",
|
|
15232
|
-
["rules",
|
|
15233
|
-
["hooks",
|
|
16754
|
+
["adapters", join37(rootDir, "src/adapters"), (e) => e.endsWith(".ts") && !["base.ts", "index.ts", "canonical.ts", "customization.ts", "types.ts", "mcp-utils.ts", "toml-utils.ts", "contextBudget.ts"].includes(e)],
|
|
16755
|
+
["commands", join37(rootDir, "src/cli/commands"), (e) => e.endsWith(".ts")],
|
|
16756
|
+
["agents", join37(rootDir, "agents"), (e) => e.endsWith(".md")],
|
|
16757
|
+
["skills", join37(rootDir, "skills"), (e) => true],
|
|
16758
|
+
["rules", join37(rootDir, "rules"), (e) => e.endsWith(".md")],
|
|
16759
|
+
["hooks", join37(rootDir, "hooks"), (e) => e.endsWith(".md")]
|
|
15234
16760
|
];
|
|
15235
16761
|
for (const [name, dir, filter] of dirs) {
|
|
15236
16762
|
try {
|
|
15237
|
-
const entries = await
|
|
16763
|
+
const entries = await readdir18(dir, { withFileTypes: true });
|
|
15238
16764
|
if (name === "skills") {
|
|
15239
16765
|
actual[name] = entries.filter((e) => e.isDirectory()).length;
|
|
15240
16766
|
} else {
|
|
@@ -15244,9 +16770,9 @@ async function validateDocsCounts(rootDir) {
|
|
|
15244
16770
|
actual[name] = 0;
|
|
15245
16771
|
}
|
|
15246
16772
|
}
|
|
15247
|
-
const readmePath =
|
|
16773
|
+
const readmePath = join37(rootDir, "README.md");
|
|
15248
16774
|
try {
|
|
15249
|
-
const readme = await
|
|
16775
|
+
const readme = await readFile28(readmePath, "utf-8");
|
|
15250
16776
|
const countPatterns = [
|
|
15251
16777
|
["adapters", /(\d+)\s+Adapters/i],
|
|
15252
16778
|
["skills", /(\d+)\s+skills/i],
|
|
@@ -15319,11 +16845,11 @@ async function validateCommand(opts) {
|
|
|
15319
16845
|
}
|
|
15320
16846
|
return;
|
|
15321
16847
|
}
|
|
15322
|
-
const agentsDir =
|
|
16848
|
+
const agentsDir = join37(rootDir, AGENTS_DIR);
|
|
15323
16849
|
const result = { errors: [], warnings: [] };
|
|
15324
16850
|
const spinner = jsonMode ? null : createSpinner("Validating .agents/ structure...");
|
|
15325
16851
|
spinner?.start();
|
|
15326
|
-
const cwdIsFrameworkSource = existsSync4(
|
|
16852
|
+
const cwdIsFrameworkSource = existsSync4(join37(rootDir, "agents")) && existsSync4(join37(rootDir, "skills")) && existsSync4(join37(rootDir, "rules")) && existsSync4(join37(rootDir, "commands"));
|
|
15327
16853
|
try {
|
|
15328
16854
|
await access11(agentsDir);
|
|
15329
16855
|
} catch (err) {
|
|
@@ -15361,9 +16887,9 @@ async function validateCommand(opts) {
|
|
|
15361
16887
|
printBox(
|
|
15362
16888
|
"Canonical content lint failed",
|
|
15363
16889
|
[
|
|
15364
|
-
`${
|
|
15365
|
-
`${
|
|
15366
|
-
|
|
16890
|
+
`${chalk12.red("\u2716")} ${result.errors.length} error(s)`,
|
|
16891
|
+
`${chalk12.yellow("\u26A0")} ${result.warnings.length} warning(s)`,
|
|
16892
|
+
chalk12.dim("(framework-source mode \u2014 .agents/ not required)")
|
|
15367
16893
|
],
|
|
15368
16894
|
"error"
|
|
15369
16895
|
);
|
|
@@ -15376,16 +16902,16 @@ async function validateCommand(opts) {
|
|
|
15376
16902
|
printBox(
|
|
15377
16903
|
"Canonical content lint",
|
|
15378
16904
|
[
|
|
15379
|
-
`${
|
|
15380
|
-
`${
|
|
15381
|
-
|
|
16905
|
+
`${chalk12.green("\u2714")} 0 errors`,
|
|
16906
|
+
`${chalk12.yellow("\u26A0")} ${result.warnings.length} warning(s)`,
|
|
16907
|
+
chalk12.dim("(framework-source mode \u2014 .agents/ not required)")
|
|
15382
16908
|
],
|
|
15383
16909
|
"success"
|
|
15384
16910
|
);
|
|
15385
16911
|
} else {
|
|
15386
16912
|
printBox(
|
|
15387
16913
|
"Canonical content lint",
|
|
15388
|
-
[
|
|
16914
|
+
[chalk12.green("All checks passed")],
|
|
15389
16915
|
"success"
|
|
15390
16916
|
);
|
|
15391
16917
|
}
|
|
@@ -15426,6 +16952,8 @@ async function validateCommand(opts) {
|
|
|
15426
16952
|
await validateHooks(agentsDir, manifest, result);
|
|
15427
16953
|
verbose("Checking MCP configuration...");
|
|
15428
16954
|
await validateMcp(agentsDir, manifest, result);
|
|
16955
|
+
verbose("Checking CLI tools...");
|
|
16956
|
+
await validateCliTools(manifest, result);
|
|
15429
16957
|
verbose("Checking model configuration...");
|
|
15430
16958
|
await validateModels(manifest, result);
|
|
15431
16959
|
verbose("Checking cost tracking...");
|
|
@@ -15482,7 +17010,7 @@ async function validateCommand(opts) {
|
|
|
15482
17010
|
let hasCustomizations = false;
|
|
15483
17011
|
for (const { dir } of CUSTOMIZATION_TYPES) {
|
|
15484
17012
|
try {
|
|
15485
|
-
const files = await
|
|
17013
|
+
const files = await readdir18(join37(rootDir, ".hatch3r", dir));
|
|
15486
17014
|
if (files.some((f) => f.endsWith(".customize.yaml") || f.endsWith(".customize.md"))) {
|
|
15487
17015
|
hasCustomizations = true;
|
|
15488
17016
|
break;
|
|
@@ -15510,7 +17038,7 @@ async function validateCommand(opts) {
|
|
|
15510
17038
|
return;
|
|
15511
17039
|
}
|
|
15512
17040
|
if (result.errors.length === 0 && result.warnings.length === 0) {
|
|
15513
|
-
printBox("Validation", [
|
|
17041
|
+
printBox("Validation", [chalk12.green("All checks passed")], "success");
|
|
15514
17042
|
if (hasCustomizations) {
|
|
15515
17043
|
printCustomizationHint();
|
|
15516
17044
|
}
|
|
@@ -15531,15 +17059,15 @@ async function validateCommand(opts) {
|
|
|
15531
17059
|
}
|
|
15532
17060
|
if (result.errors.length > 0) {
|
|
15533
17061
|
const summaryLines = [
|
|
15534
|
-
`${
|
|
15535
|
-
`${
|
|
17062
|
+
`${chalk12.red("\u2716")} ${result.errors.length} error(s)`,
|
|
17063
|
+
`${chalk12.yellow("\u26A0")} ${result.warnings.length} warning(s)`
|
|
15536
17064
|
];
|
|
15537
17065
|
printBox("Validation failed", summaryLines, "error");
|
|
15538
17066
|
throw new HatchError("Validation failed", 1, "VALIDATION_ERROR");
|
|
15539
17067
|
} else {
|
|
15540
17068
|
const summaryLines = [
|
|
15541
|
-
`${
|
|
15542
|
-
`${
|
|
17069
|
+
`${chalk12.green("\u2714")} 0 errors`,
|
|
17070
|
+
`${chalk12.yellow("\u26A0")} ${result.warnings.length} warning(s)`
|
|
15543
17071
|
];
|
|
15544
17072
|
printBox("Validation passed", summaryLines, "success");
|
|
15545
17073
|
}
|
|
@@ -15548,10 +17076,10 @@ async function validateCommand(opts) {
|
|
|
15548
17076
|
}
|
|
15549
17077
|
}
|
|
15550
17078
|
async function validateEnvMcpSecrets(rootDir, result) {
|
|
15551
|
-
const envMcpPath =
|
|
17079
|
+
const envMcpPath = join37(rootDir, ".env.mcp");
|
|
15552
17080
|
if (!existsSync4(envMcpPath)) return;
|
|
15553
17081
|
try {
|
|
15554
|
-
const raw = await
|
|
17082
|
+
const raw = await readFile28(envMcpPath, "utf-8");
|
|
15555
17083
|
const vars = parseEnvFile(raw);
|
|
15556
17084
|
const detection = detectSecrets(vars);
|
|
15557
17085
|
for (const finding of detection.findings) {
|
|
@@ -15568,7 +17096,7 @@ async function validateEnvMcpSecrets(rootDir, result) {
|
|
|
15568
17096
|
async function validateCanonicalDescriptionQuality(rootDir, result) {
|
|
15569
17097
|
const __filename = fileURLToPath8(import.meta.url);
|
|
15570
17098
|
const packageRoot = findPackageRoot(dirname18(__filename));
|
|
15571
|
-
const canonicalRoot = existsSync4(
|
|
17099
|
+
const canonicalRoot = existsSync4(join37(rootDir, "agents")) && existsSync4(join37(rootDir, "skills")) && existsSync4(join37(rootDir, "rules")) && existsSync4(join37(rootDir, "commands")) ? rootDir : packageRoot;
|
|
15572
17100
|
try {
|
|
15573
17101
|
const index = await buildContentIndex(canonicalRoot);
|
|
15574
17102
|
if (index.items.length === 0) return;
|
|
@@ -15607,14 +17135,14 @@ async function validateSecurityCompliance(result) {
|
|
|
15607
17135
|
}
|
|
15608
17136
|
function printCustomizationHint() {
|
|
15609
17137
|
console.log();
|
|
15610
|
-
info(
|
|
15611
|
-
console.log(
|
|
15612
|
-
console.log(
|
|
15613
|
-
console.log(
|
|
15614
|
-
console.log(
|
|
15615
|
-
console.log(
|
|
15616
|
-
console.log(
|
|
15617
|
-
console.log(
|
|
17138
|
+
info(chalk12.bold("Customization mechanisms detected. Quick reference:"));
|
|
17139
|
+
console.log(chalk12.dim(" 1. hatch3r- prefix: Files prefixed with hatch3r- are managed by hatch3r and"));
|
|
17140
|
+
console.log(chalk12.dim(" overwritten on update. Do not edit these directly."));
|
|
17141
|
+
console.log(chalk12.dim(" 2. Managed blocks: Sections between <!-- HATCH3R:BEGIN --> and"));
|
|
17142
|
+
console.log(chalk12.dim(" <!-- HATCH3R:END --> are auto-updated. Add content outside these markers."));
|
|
17143
|
+
console.log(chalk12.dim(" 3. .customize.yaml/.md: Place in .hatch3r/{type}/ to override model, scope,"));
|
|
17144
|
+
console.log(chalk12.dim(" description, or disable items. Use .customize.md for content additions."));
|
|
17145
|
+
console.log(chalk12.dim(" See: https://docs.hatch3r.com/docs/guides/customization"));
|
|
15618
17146
|
}
|
|
15619
17147
|
|
|
15620
17148
|
// src/cli/commands/verify.ts
|
|
@@ -15626,26 +17154,26 @@ init_pipelineTimeout();
|
|
|
15626
17154
|
init_phaseOutputSchema();
|
|
15627
17155
|
init_retryWithBackoff();
|
|
15628
17156
|
init_ui();
|
|
15629
|
-
import { join as
|
|
15630
|
-
import
|
|
17157
|
+
import { join as join38 } from "path";
|
|
17158
|
+
import chalk13 from "chalk";
|
|
15631
17159
|
async function runVerifyPass(agentsDir) {
|
|
15632
17160
|
const results = await verifyIntegrity(agentsDir);
|
|
15633
17161
|
if (results.length === 0) {
|
|
15634
17162
|
return { passed: true, counts: { pass: 0 }, hasModifiedOrMissing: false, hasTampered: false };
|
|
15635
17163
|
}
|
|
15636
17164
|
const icons = {
|
|
15637
|
-
pass:
|
|
15638
|
-
modified:
|
|
15639
|
-
missing:
|
|
15640
|
-
new:
|
|
15641
|
-
tampered:
|
|
17165
|
+
pass: chalk13.green("\u2714"),
|
|
17166
|
+
modified: chalk13.yellow("\u2716"),
|
|
17167
|
+
missing: chalk13.red("\u2716"),
|
|
17168
|
+
new: chalk13.cyan("+"),
|
|
17169
|
+
tampered: chalk13.red("\u26A0")
|
|
15642
17170
|
};
|
|
15643
17171
|
const labels = {
|
|
15644
|
-
pass:
|
|
15645
|
-
modified:
|
|
15646
|
-
missing:
|
|
15647
|
-
new:
|
|
15648
|
-
tampered:
|
|
17172
|
+
pass: chalk13.green("PASS"),
|
|
17173
|
+
modified: chalk13.yellow("MODIFIED"),
|
|
17174
|
+
missing: chalk13.red("MISSING"),
|
|
17175
|
+
new: chalk13.cyan("NEW"),
|
|
17176
|
+
tampered: chalk13.red("TAMPERED")
|
|
15649
17177
|
};
|
|
15650
17178
|
console.log();
|
|
15651
17179
|
for (const r of results) {
|
|
@@ -15665,11 +17193,11 @@ async function runVerifyPass(agentsDir) {
|
|
|
15665
17193
|
}
|
|
15666
17194
|
function printSummary(counts, title, style) {
|
|
15667
17195
|
const summaryLines = [];
|
|
15668
|
-
if ((counts.pass ?? 0) > 0) summaryLines.push(`${
|
|
15669
|
-
if ((counts.modified ?? 0) > 0) summaryLines.push(`${
|
|
15670
|
-
if ((counts.missing ?? 0) > 0) summaryLines.push(`${
|
|
15671
|
-
if ((counts.new ?? 0) > 0) summaryLines.push(`${
|
|
15672
|
-
if ((counts.tampered ?? 0) > 0) summaryLines.push(`${
|
|
17196
|
+
if ((counts.pass ?? 0) > 0) summaryLines.push(`${chalk13.green("\u2714")} Passed: ${counts.pass}`);
|
|
17197
|
+
if ((counts.modified ?? 0) > 0) summaryLines.push(`${chalk13.yellow("\u2716")} Modified: ${counts.modified}`);
|
|
17198
|
+
if ((counts.missing ?? 0) > 0) summaryLines.push(`${chalk13.red("\u2716")} Missing: ${counts.missing}`);
|
|
17199
|
+
if ((counts.new ?? 0) > 0) summaryLines.push(`${chalk13.cyan("+")} New: ${counts.new}`);
|
|
17200
|
+
if ((counts.tampered ?? 0) > 0) summaryLines.push(`${chalk13.red("\u26A0")} Tampered: ${counts.tampered}`);
|
|
15673
17201
|
printBox(title, summaryLines, style);
|
|
15674
17202
|
}
|
|
15675
17203
|
var MAX_FIX_ATTEMPTS = 5;
|
|
@@ -15680,7 +17208,7 @@ async function verifyCommand(options = {}) {
|
|
|
15680
17208
|
DEFAULT_PIPELINE_TIMEOUT_MS
|
|
15681
17209
|
);
|
|
15682
17210
|
const rootDir = process.cwd();
|
|
15683
|
-
const agentsDir =
|
|
17211
|
+
const agentsDir = join38(rootDir, AGENTS_DIR);
|
|
15684
17212
|
const spinner = createSpinner("Verifying file integrity...");
|
|
15685
17213
|
spinner.start();
|
|
15686
17214
|
const manifest = await readIntegrityManifest(agentsDir);
|
|
@@ -15706,7 +17234,7 @@ async function verifyCommand(options = {}) {
|
|
|
15706
17234
|
const compactedCounts = compactPhaseOutput(result.counts);
|
|
15707
17235
|
if (result.passed) {
|
|
15708
17236
|
if (compactedCounts.pass === 0) {
|
|
15709
|
-
printBox("Integrity", [
|
|
17237
|
+
printBox("Integrity", [chalk13.dim("No files to verify")], "info");
|
|
15710
17238
|
} else {
|
|
15711
17239
|
printSummary(compactedCounts, "Integrity check passed", "success");
|
|
15712
17240
|
}
|
|
@@ -15722,7 +17250,7 @@ async function verifyCommand(options = {}) {
|
|
|
15722
17250
|
error("Integrity manifest has been tampered with. Re-run `hatch3r update` to regenerate it.");
|
|
15723
17251
|
}
|
|
15724
17252
|
if ((result.counts.modified ?? 0) > 0) {
|
|
15725
|
-
info(`Modified files may have been tampered with. Run ${
|
|
17253
|
+
info(`Modified files may have been tampered with. Run ${chalk13.bold("hatch3r update")} to restore originals.`);
|
|
15726
17254
|
}
|
|
15727
17255
|
console.log();
|
|
15728
17256
|
throw new HatchError("Integrity check failed", 1, "INTEGRITY_ERROR");
|
|
@@ -15815,24 +17343,24 @@ init_adapters();
|
|
|
15815
17343
|
init_types();
|
|
15816
17344
|
init_managedBlocks();
|
|
15817
17345
|
init_integrity();
|
|
15818
|
-
import { access as access12, readFile as
|
|
15819
|
-
import { join as
|
|
15820
|
-
import
|
|
17346
|
+
import { access as access12, readFile as readFile29, readdir as readdir19, stat as stat12 } from "fs/promises";
|
|
17347
|
+
import { join as join39 } from "path";
|
|
17348
|
+
import chalk14 from "chalk";
|
|
15821
17349
|
init_ui();
|
|
15822
17350
|
async function dirCharCount(dir) {
|
|
15823
17351
|
let total = 0;
|
|
15824
17352
|
let entries;
|
|
15825
17353
|
try {
|
|
15826
|
-
entries = await
|
|
17354
|
+
entries = await readdir19(dir, { withFileTypes: true });
|
|
15827
17355
|
} catch {
|
|
15828
17356
|
return 0;
|
|
15829
17357
|
}
|
|
15830
17358
|
for (const entry of entries) {
|
|
15831
|
-
const fullPath =
|
|
17359
|
+
const fullPath = join39(dir, entry.name);
|
|
15832
17360
|
if (entry.isDirectory()) {
|
|
15833
17361
|
total += await dirCharCount(fullPath);
|
|
15834
17362
|
} else if (entry.isFile()) {
|
|
15835
|
-
const info2 = await
|
|
17363
|
+
const info2 = await stat12(fullPath);
|
|
15836
17364
|
total += info2.size;
|
|
15837
17365
|
}
|
|
15838
17366
|
}
|
|
@@ -15854,7 +17382,7 @@ async function runFastStatusCheck(rootDir, agentsDir, manifest) {
|
|
|
15854
17382
|
const successful = new Set(integrityManifest.successfulAdapters);
|
|
15855
17383
|
for (const tool of expected) {
|
|
15856
17384
|
if (!successful.has(tool)) {
|
|
15857
|
-
fileLines.push(
|
|
17385
|
+
fileLines.push(chalk14.yellow(`${tool}: last sync did not complete (check hatch3r sync output)`));
|
|
15858
17386
|
}
|
|
15859
17387
|
}
|
|
15860
17388
|
}
|
|
@@ -15862,21 +17390,21 @@ async function runFastStatusCheck(rootDir, agentsDir, manifest) {
|
|
|
15862
17390
|
const adapter = getAdapter(tool);
|
|
15863
17391
|
const paths = await adapter.getOutputPaths(agentsDir, manifest);
|
|
15864
17392
|
verbose(`${tool}: ${paths.length} output path(s) to check (fast path)`);
|
|
15865
|
-
fileLines.push(
|
|
17393
|
+
fileLines.push(chalk14.bold(`${tool}:`));
|
|
15866
17394
|
for (const p of paths) {
|
|
15867
|
-
const destPath =
|
|
17395
|
+
const destPath = join39(rootDir, p);
|
|
15868
17396
|
try {
|
|
15869
|
-
const fileStat = await
|
|
17397
|
+
const fileStat = await stat12(destPath);
|
|
15870
17398
|
if (fileStat.mtimeMs > sealMs) {
|
|
15871
|
-
fileLines.push(` ${
|
|
17399
|
+
fileLines.push(` ${chalk14.yellow("~")} ${p} ${chalk14.dim("(drifted)")}`);
|
|
15872
17400
|
stats.drifted++;
|
|
15873
17401
|
} else {
|
|
15874
|
-
fileLines.push(` ${
|
|
17402
|
+
fileLines.push(` ${chalk14.green("=")} ${p}`);
|
|
15875
17403
|
stats.synced++;
|
|
15876
17404
|
}
|
|
15877
17405
|
} catch (err) {
|
|
15878
17406
|
if (err.code !== "ENOENT") throw err;
|
|
15879
|
-
fileLines.push(` ${
|
|
17407
|
+
fileLines.push(` ${chalk14.red("+")} ${p} ${chalk14.dim("(missing)")}`);
|
|
15880
17408
|
stats.missing++;
|
|
15881
17409
|
}
|
|
15882
17410
|
}
|
|
@@ -15890,23 +17418,23 @@ async function runDeepStatusCheck(rootDir, agentsDir, manifest) {
|
|
|
15890
17418
|
const adapter = getAdapter(tool);
|
|
15891
17419
|
const outputs = await adapter.generate(agentsDir, manifest);
|
|
15892
17420
|
verbose(`${tool}: ${outputs.length} output file(s) to check`);
|
|
15893
|
-
fileLines.push(
|
|
17421
|
+
fileLines.push(chalk14.bold(`${tool}:`));
|
|
15894
17422
|
for (const out of outputs) {
|
|
15895
|
-
const destPath =
|
|
17423
|
+
const destPath = join39(rootDir, out.path);
|
|
15896
17424
|
try {
|
|
15897
|
-
const existing = await
|
|
17425
|
+
const existing = await readFile29(destPath, "utf-8");
|
|
15898
17426
|
const existingBlock = extractManagedBlock(existing);
|
|
15899
17427
|
const expectedBlock = out.managedContent ?? extractManagedBlock(out.content);
|
|
15900
17428
|
if (existingBlock !== null && expectedBlock !== null ? existingBlock === expectedBlock : existing === out.content) {
|
|
15901
|
-
fileLines.push(` ${
|
|
17429
|
+
fileLines.push(` ${chalk14.green("=")} ${out.path}`);
|
|
15902
17430
|
stats.synced++;
|
|
15903
17431
|
} else {
|
|
15904
|
-
fileLines.push(` ${
|
|
17432
|
+
fileLines.push(` ${chalk14.yellow("~")} ${out.path} ${chalk14.dim("(drifted)")}`);
|
|
15905
17433
|
stats.drifted++;
|
|
15906
17434
|
}
|
|
15907
17435
|
} catch (err) {
|
|
15908
17436
|
if (err.code !== "ENOENT") throw err;
|
|
15909
|
-
fileLines.push(` ${
|
|
17437
|
+
fileLines.push(` ${chalk14.red("+")} ${out.path} ${chalk14.dim("(missing)")}`);
|
|
15910
17438
|
stats.missing++;
|
|
15911
17439
|
}
|
|
15912
17440
|
}
|
|
@@ -15917,11 +17445,11 @@ async function statusCommand(opts) {
|
|
|
15917
17445
|
setVerbose(!!opts?.verbose);
|
|
15918
17446
|
printBanner(true);
|
|
15919
17447
|
const rootDir = process.cwd();
|
|
15920
|
-
const agentsDir =
|
|
17448
|
+
const agentsDir = join39(rootDir, AGENTS_DIR);
|
|
15921
17449
|
const manifest = await readManifest(rootDir);
|
|
15922
17450
|
if (!manifest) {
|
|
15923
17451
|
error("No .agents/hatch.json found.");
|
|
15924
|
-
console.log(
|
|
17452
|
+
console.log(chalk14.dim(" Run `npx hatch3r init` to set up your project first.\n"));
|
|
15925
17453
|
throw new HatchError("No .agents/hatch.json found.", 1, "CONFIG_ERROR");
|
|
15926
17454
|
}
|
|
15927
17455
|
const spinner = createSpinner("Checking sync status...");
|
|
@@ -15948,27 +17476,44 @@ async function statusCommand(opts) {
|
|
|
15948
17476
|
}
|
|
15949
17477
|
console.log();
|
|
15950
17478
|
const summaryLines = [
|
|
15951
|
-
`${
|
|
17479
|
+
`${chalk14.green("=")} In sync: ${stats.synced}`
|
|
15952
17480
|
];
|
|
15953
17481
|
if (stats.drifted > 0) {
|
|
15954
|
-
summaryLines.push(`${
|
|
17482
|
+
summaryLines.push(`${chalk14.yellow("~")} Drifted: ${stats.drifted}`);
|
|
15955
17483
|
}
|
|
15956
17484
|
if (stats.missing > 0) {
|
|
15957
|
-
summaryLines.push(`${
|
|
17485
|
+
summaryLines.push(`${chalk14.red("+")} Missing: ${stats.missing}`);
|
|
15958
17486
|
}
|
|
15959
17487
|
const totalChars = await dirCharCount(agentsDir);
|
|
15960
17488
|
const estimatedTokens = Math.round(totalChars / 4);
|
|
15961
17489
|
const formattedTokens = estimatedTokens.toLocaleString("en-US");
|
|
15962
|
-
summaryLines.push(`${
|
|
17490
|
+
summaryLines.push(`${chalk14.dim("~")} Estimated canonical tokens: ~${formattedTokens}`);
|
|
15963
17491
|
if (usedFastPath) {
|
|
15964
|
-
summaryLines.push(
|
|
17492
|
+
summaryLines.push(chalk14.dim("mode: fast (integrity-manifest). Pass --deep for byte-for-byte regeneration check."));
|
|
15965
17493
|
}
|
|
15966
17494
|
const style = stats.drifted > 0 || stats.missing > 0 ? "info" : "success";
|
|
15967
17495
|
printBox("Status", summaryLines, style);
|
|
15968
17496
|
if (stats.drifted > 0 || stats.missing > 0) {
|
|
15969
|
-
info(`Run ${
|
|
17497
|
+
info(`Run ${chalk14.bold("hatch3r sync")} to regenerate drifted/missing files.`);
|
|
15970
17498
|
console.log();
|
|
15971
17499
|
}
|
|
17500
|
+
const cliSelected = manifest.cliTools?.selected ?? [];
|
|
17501
|
+
if (manifest.cliTools?.enabled && cliSelected.length > 0) {
|
|
17502
|
+
const cliResults = await detectCliTools(cliSelected);
|
|
17503
|
+
const installed = cliResults.filter((r) => r.installed).length;
|
|
17504
|
+
const cliLines = [];
|
|
17505
|
+
cliLines.push(label("Installed", `${installed}/${cliResults.length}`));
|
|
17506
|
+
const missing = cliResults.filter((r) => !r.installed);
|
|
17507
|
+
if (missing.length > 0) {
|
|
17508
|
+
cliLines.push("");
|
|
17509
|
+
for (const r of missing) {
|
|
17510
|
+
cliLines.push(` ${chalk14.yellow("\u2717")} ${r.id} not on PATH`);
|
|
17511
|
+
}
|
|
17512
|
+
cliLines.push("");
|
|
17513
|
+
cliLines.push(chalk14.dim(`Run \`npx hatch3r cli-tools install\` to see install commands.`));
|
|
17514
|
+
}
|
|
17515
|
+
printBox("CLI tools", cliLines, missing.length === 0 ? "success" : "info");
|
|
17516
|
+
}
|
|
15972
17517
|
let userTypes = null;
|
|
15973
17518
|
let userTotal = 0;
|
|
15974
17519
|
let userLastModified = null;
|
|
@@ -16006,7 +17551,7 @@ async function statusCommand(opts) {
|
|
|
16006
17551
|
printBox("User content", userLines, "info");
|
|
16007
17552
|
}
|
|
16008
17553
|
if (manifest.tools.includes("codex")) {
|
|
16009
|
-
const overridePath =
|
|
17554
|
+
const overridePath = join39(rootDir, "AGENTS.override.md");
|
|
16010
17555
|
try {
|
|
16011
17556
|
await access12(overridePath);
|
|
16012
17557
|
warn(
|
|
@@ -16022,34 +17567,341 @@ async function statusCommand(opts) {
|
|
|
16022
17567
|
if (wsManifest && wsManifest.repos.length > 0) {
|
|
16023
17568
|
const wsLines = [];
|
|
16024
17569
|
for (const repo of wsManifest.repos) {
|
|
16025
|
-
const icon = repo.sync ?
|
|
17570
|
+
const icon = repo.sync ? chalk14.green("\u2713") : chalk14.dim("\u25CB");
|
|
16026
17571
|
let detail;
|
|
16027
17572
|
if (!repo.sync) {
|
|
16028
|
-
detail =
|
|
17573
|
+
detail = chalk14.dim("sync disabled");
|
|
16029
17574
|
} else if (repo.lastSync) {
|
|
16030
17575
|
const elapsed = Math.max(0, Date.now() - new Date(repo.lastSync).getTime());
|
|
16031
17576
|
const hours = Math.floor(elapsed / (1e3 * 60 * 60));
|
|
16032
17577
|
const timeAgo = hours < 1 ? "just now" : hours < 24 ? `${hours}h ago` : `${Math.floor(hours / 24)}d ago`;
|
|
16033
17578
|
detail = `synced ${timeAgo}`;
|
|
16034
17579
|
} else {
|
|
16035
|
-
detail =
|
|
17580
|
+
detail = chalk14.yellow("never synced");
|
|
16036
17581
|
}
|
|
16037
|
-
const identity = repo.owner && repo.repo ?
|
|
16038
|
-
const branch = repo.defaultBranch ?
|
|
17582
|
+
const identity = repo.owner && repo.repo ? chalk14.dim(`${repo.owner}/${repo.repo}`) : "";
|
|
17583
|
+
const branch = repo.defaultBranch ? chalk14.dim(`[${repo.defaultBranch}]`) : "";
|
|
16039
17584
|
const identityPart = identity || branch ? ` ${identity} ${branch}` : "";
|
|
16040
|
-
wsLines.push(`${icon} ${repo.name ?? repo.path}${identityPart} ${
|
|
17585
|
+
wsLines.push(`${icon} ${repo.name ?? repo.path}${identityPart} ${chalk14.dim(`(${detail})`)}`);
|
|
16041
17586
|
}
|
|
16042
17587
|
printBox(`Workspace: ${wsManifest.name} (${wsManifest.repos.length} repos)`, wsLines, "info");
|
|
16043
17588
|
}
|
|
16044
17589
|
if (manifest.workspace) {
|
|
16045
17590
|
const wsInfo = [
|
|
16046
|
-
`Managed by workspace at ${
|
|
17591
|
+
`Managed by workspace at ${chalk14.bold(manifest.workspace.rootPath)}`,
|
|
16047
17592
|
`Last synced: ${manifest.workspace.lastSync ? new Date(manifest.workspace.lastSync).toLocaleString() : "never"}`
|
|
16048
17593
|
];
|
|
16049
17594
|
printBox("Workspace member", wsInfo, "info");
|
|
16050
17595
|
}
|
|
16051
17596
|
}
|
|
16052
17597
|
|
|
17598
|
+
// src/cli/commands/mcp.ts
|
|
17599
|
+
init_hatchJson();
|
|
17600
|
+
init_types();
|
|
17601
|
+
init_mcpEnv();
|
|
17602
|
+
init_ui();
|
|
17603
|
+
import { existsSync as existsSync5 } from "fs";
|
|
17604
|
+
import { readFile as readFile30 } from "fs/promises";
|
|
17605
|
+
import { join as join40 } from "path";
|
|
17606
|
+
import chalk15 from "chalk";
|
|
17607
|
+
function requireManifest(rootDir, manifest) {
|
|
17608
|
+
if (!manifest) {
|
|
17609
|
+
error("No .agents/hatch.json found.");
|
|
17610
|
+
console.log(chalk15.dim(` Run \`npx hatch3r init\` to set up your project first.
|
|
17611
|
+
`));
|
|
17612
|
+
throw new HatchError("No .agents/hatch.json found.", 1, "CONFIG_ERROR");
|
|
17613
|
+
}
|
|
17614
|
+
}
|
|
17615
|
+
function wslThemeOrUndefined() {
|
|
17616
|
+
return isWSL() ? { icon: { checked: chalk15.green("[x]"), unchecked: "[ ]", cursor: ">" } } : void 0;
|
|
17617
|
+
}
|
|
17618
|
+
async function mcpSetupCommand() {
|
|
17619
|
+
printBanner(true);
|
|
17620
|
+
const rootDir = process.cwd();
|
|
17621
|
+
const manifest = await readManifest(rootDir);
|
|
17622
|
+
requireManifest(rootDir, manifest);
|
|
17623
|
+
const platform = manifest.platform ?? "github";
|
|
17624
|
+
const selected = await pickMcpServers({
|
|
17625
|
+
platform,
|
|
17626
|
+
existing: manifest.mcp.servers,
|
|
17627
|
+
wslTheme: wslThemeOrUndefined()
|
|
17628
|
+
});
|
|
17629
|
+
manifest.mcp = { servers: selected };
|
|
17630
|
+
await writeManifest(rootDir, manifest);
|
|
17631
|
+
if (selected.length > 0) {
|
|
17632
|
+
const envResult = await ensureEnvMcp(rootDir, selected);
|
|
17633
|
+
await ensureGitignoreEntry(rootDir);
|
|
17634
|
+
if (envResult.newVars.length > 0) {
|
|
17635
|
+
warn(`Add new secrets to .env.mcp: ${envResult.newVars.join(", ")}`);
|
|
17636
|
+
info(`Run this then start/restart your editor: ${getSourceEnvMcpCommand()}`);
|
|
17637
|
+
}
|
|
17638
|
+
}
|
|
17639
|
+
printBox(
|
|
17640
|
+
"MCP configured",
|
|
17641
|
+
[
|
|
17642
|
+
label("Servers", selected.length > 0 ? selected.join(", ") : "none"),
|
|
17643
|
+
label("Manifest", ".agents/hatch.json"),
|
|
17644
|
+
label("Next", "Run `npx hatch3r sync` to regenerate adapter MCP configs")
|
|
17645
|
+
],
|
|
17646
|
+
"success"
|
|
17647
|
+
);
|
|
17648
|
+
}
|
|
17649
|
+
async function mcpListCommand() {
|
|
17650
|
+
printBanner(true);
|
|
17651
|
+
const rootDir = process.cwd();
|
|
17652
|
+
const manifest = await readManifest(rootDir);
|
|
17653
|
+
requireManifest(rootDir, manifest);
|
|
17654
|
+
const servers = manifest.mcp.servers;
|
|
17655
|
+
const envPath = join40(rootDir, ".env.mcp");
|
|
17656
|
+
const hasEnvFile = existsSync5(envPath);
|
|
17657
|
+
const envExisting = hasEnvFile ? parseEnvFile(await readFile30(envPath, "utf-8")) : {};
|
|
17658
|
+
const requiredVars = collectRequiredEnvVars(servers);
|
|
17659
|
+
const missingVars = requiredVars.filter((v) => !(v.name in envExisting) || envExisting[v.name] === "");
|
|
17660
|
+
const lines = [];
|
|
17661
|
+
if (servers.length === 0) {
|
|
17662
|
+
lines.push("(no MCP servers configured)");
|
|
17663
|
+
lines.push("");
|
|
17664
|
+
lines.push("Run `npx hatch3r mcp setup` to open the server picker.");
|
|
17665
|
+
} else {
|
|
17666
|
+
for (const id of servers) {
|
|
17667
|
+
const meta = AVAILABLE_MCP_SERVERS[id];
|
|
17668
|
+
const desc = meta?.description ?? "(unknown server)";
|
|
17669
|
+
lines.push(` ${chalk15.cyan(id)} \u2014 ${desc}`);
|
|
17670
|
+
}
|
|
17671
|
+
lines.push("");
|
|
17672
|
+
lines.push(label(".env.mcp", hasEnvFile ? "present" : chalk15.yellow("missing")));
|
|
17673
|
+
if (requiredVars.length > 0) {
|
|
17674
|
+
lines.push(label("Required vars", requiredVars.map((v) => v.name).join(", ")));
|
|
17675
|
+
if (missingVars.length > 0) {
|
|
17676
|
+
lines.push(label("Missing", chalk15.yellow(missingVars.map((v) => v.name).join(", "))));
|
|
17677
|
+
} else {
|
|
17678
|
+
lines.push(label("Status", chalk15.green("all required vars set")));
|
|
17679
|
+
}
|
|
17680
|
+
}
|
|
17681
|
+
}
|
|
17682
|
+
printBox("MCP servers", lines, "info");
|
|
17683
|
+
}
|
|
17684
|
+
async function mcpRemoveCommand(id) {
|
|
17685
|
+
printBanner(true);
|
|
17686
|
+
const rootDir = process.cwd();
|
|
17687
|
+
const manifest = await readManifest(rootDir);
|
|
17688
|
+
requireManifest(rootDir, manifest);
|
|
17689
|
+
const before = manifest.mcp.servers;
|
|
17690
|
+
if (!before.includes(id)) {
|
|
17691
|
+
error(`MCP server "${id}" is not configured.`);
|
|
17692
|
+
console.log(chalk15.dim(` Current servers: ${before.length > 0 ? before.join(", ") : "(none)"}
|
|
17693
|
+
`));
|
|
17694
|
+
throw new HatchError(`MCP server "${id}" not configured`, 1, "VALIDATION_ERROR");
|
|
17695
|
+
}
|
|
17696
|
+
manifest.mcp = { servers: before.filter((s) => s !== id) };
|
|
17697
|
+
await writeManifest(rootDir, manifest);
|
|
17698
|
+
printBox(
|
|
17699
|
+
"MCP server removed",
|
|
17700
|
+
[
|
|
17701
|
+
label("Removed", id),
|
|
17702
|
+
label("Remaining", manifest.mcp.servers.length > 0 ? manifest.mcp.servers.join(", ") : "none"),
|
|
17703
|
+
label("Next", "Run `npx hatch3r sync` to regenerate adapter MCP configs")
|
|
17704
|
+
],
|
|
17705
|
+
"success"
|
|
17706
|
+
);
|
|
17707
|
+
}
|
|
17708
|
+
async function mcpEnvCheckCommand() {
|
|
17709
|
+
printBanner(true);
|
|
17710
|
+
const rootDir = process.cwd();
|
|
17711
|
+
const manifest = await readManifest(rootDir);
|
|
17712
|
+
requireManifest(rootDir, manifest);
|
|
17713
|
+
const servers = manifest.mcp.servers;
|
|
17714
|
+
const envPath = join40(rootDir, ".env.mcp");
|
|
17715
|
+
const hasEnvFile = existsSync5(envPath);
|
|
17716
|
+
const envExisting = hasEnvFile ? parseEnvFile(await readFile30(envPath, "utf-8")) : {};
|
|
17717
|
+
const lines = [];
|
|
17718
|
+
if (servers.length === 0) {
|
|
17719
|
+
lines.push("(no MCP servers configured \u2014 nothing to check)");
|
|
17720
|
+
printBox("MCP env check", lines, "info");
|
|
17721
|
+
return;
|
|
17722
|
+
}
|
|
17723
|
+
let missingTotal = 0;
|
|
17724
|
+
for (const id of servers) {
|
|
17725
|
+
const meta = AVAILABLE_MCP_SERVERS[id];
|
|
17726
|
+
const required = meta?.requiresEnv ?? [];
|
|
17727
|
+
if (required.length === 0) {
|
|
17728
|
+
lines.push(`${chalk15.green("\u2713")} ${id} \u2014 no env vars required`);
|
|
17729
|
+
continue;
|
|
17730
|
+
}
|
|
17731
|
+
const missing = required.filter((name) => !(name in envExisting) || envExisting[name] === "");
|
|
17732
|
+
if (missing.length === 0) {
|
|
17733
|
+
lines.push(`${chalk15.green("\u2713")} ${id} \u2014 ${required.join(", ")}`);
|
|
17734
|
+
} else {
|
|
17735
|
+
lines.push(`${chalk15.yellow("!")} ${id} \u2014 missing: ${missing.join(", ")}`);
|
|
17736
|
+
missingTotal += missing.length;
|
|
17737
|
+
}
|
|
17738
|
+
}
|
|
17739
|
+
lines.push("");
|
|
17740
|
+
lines.push(label(".env.mcp", hasEnvFile ? "present" : chalk15.yellow("missing")));
|
|
17741
|
+
if (missingTotal > 0) {
|
|
17742
|
+
lines.push(label("Action", `Fill ${missingTotal} env var(s) in .env.mcp, then \`${getSourceEnvMcpCommand()}\``));
|
|
17743
|
+
}
|
|
17744
|
+
printBox("MCP env check", lines, missingTotal > 0 ? "info" : "success");
|
|
17745
|
+
}
|
|
17746
|
+
|
|
17747
|
+
// src/cli/commands/cliTools.ts
|
|
17748
|
+
init_hatchJson();
|
|
17749
|
+
init_types();
|
|
17750
|
+
import chalk16 from "chalk";
|
|
17751
|
+
init_ui();
|
|
17752
|
+
function requireManifest2(_rootDir, manifest) {
|
|
17753
|
+
if (!manifest) {
|
|
17754
|
+
error("No .agents/hatch.json found.");
|
|
17755
|
+
console.log(chalk16.dim(` Run \`npx hatch3r init\` to set up your project first.
|
|
17756
|
+
`));
|
|
17757
|
+
throw new HatchError("No .agents/hatch.json found.", 1, "CONFIG_ERROR");
|
|
17758
|
+
}
|
|
17759
|
+
}
|
|
17760
|
+
function wslThemeOrUndefined2() {
|
|
17761
|
+
return isWSL() ? { icon: { checked: chalk16.green("[x]"), unchecked: "[ ]", cursor: ">" } } : void 0;
|
|
17762
|
+
}
|
|
17763
|
+
async function cliToolsCommand() {
|
|
17764
|
+
printBanner(true);
|
|
17765
|
+
const rootDir = process.cwd();
|
|
17766
|
+
const manifest = await readManifest(rootDir);
|
|
17767
|
+
requireManifest2(rootDir, manifest);
|
|
17768
|
+
const existing = manifest.cliTools?.selected ?? [];
|
|
17769
|
+
const selected = await pickCliTools({
|
|
17770
|
+
existing,
|
|
17771
|
+
wslTheme: wslThemeOrUndefined2()
|
|
17772
|
+
});
|
|
17773
|
+
if (selected.length > 0) {
|
|
17774
|
+
const spinner = createSpinner(`Detecting ${selected.length} CLI tool(s)...`);
|
|
17775
|
+
spinner.start();
|
|
17776
|
+
const missing = await findMissingCliTools(selected);
|
|
17777
|
+
if (missing.length === 0) {
|
|
17778
|
+
spinner.succeed(`All ${selected.length} CLI tool(s) detected on PATH`);
|
|
17779
|
+
} else {
|
|
17780
|
+
spinner.warn(`${selected.length - missing.length}/${selected.length} CLI tool(s) detected; ${missing.length} missing`);
|
|
17781
|
+
await offerInstaller(missing, { interactive: true });
|
|
17782
|
+
}
|
|
17783
|
+
const secretNotes = [];
|
|
17784
|
+
for (const id of selected) {
|
|
17785
|
+
const notes = CLI_TOOL_SECRET_NOTES[id];
|
|
17786
|
+
if (notes && notes.length > 0) {
|
|
17787
|
+
secretNotes.push(`${id}: ${notes.join(", ")}`);
|
|
17788
|
+
}
|
|
17789
|
+
}
|
|
17790
|
+
if (secretNotes.length > 0) {
|
|
17791
|
+
info(chalk16.dim("CLI tool environment variables required:"));
|
|
17792
|
+
for (const note of secretNotes) {
|
|
17793
|
+
info(chalk16.dim(` ${note}`));
|
|
17794
|
+
}
|
|
17795
|
+
}
|
|
17796
|
+
}
|
|
17797
|
+
const cliToolsConfig = {
|
|
17798
|
+
enabled: selected.length > 0,
|
|
17799
|
+
selected
|
|
17800
|
+
};
|
|
17801
|
+
manifest.cliTools = cliToolsConfig;
|
|
17802
|
+
await writeManifest(rootDir, manifest);
|
|
17803
|
+
printBox(
|
|
17804
|
+
"CLI tools configured",
|
|
17805
|
+
[
|
|
17806
|
+
label("Selected", selected.length > 0 ? selected.join(", ") : "none"),
|
|
17807
|
+
label("Manifest", ".agents/hatch.json"),
|
|
17808
|
+
label("Next", "Run `npx hatch3r sync` so adapters re-emit the filtered skill set")
|
|
17809
|
+
],
|
|
17810
|
+
"success"
|
|
17811
|
+
);
|
|
17812
|
+
if (selected.length > 0) {
|
|
17813
|
+
const finalMissing = await findMissingCliTools(selected);
|
|
17814
|
+
printMissingCliToolsDisclaimer(finalMissing, selected.length);
|
|
17815
|
+
}
|
|
17816
|
+
}
|
|
17817
|
+
async function cliToolsListCommand() {
|
|
17818
|
+
printBanner(true);
|
|
17819
|
+
const rootDir = process.cwd();
|
|
17820
|
+
const manifest = await readManifest(rootDir);
|
|
17821
|
+
requireManifest2(rootDir, manifest);
|
|
17822
|
+
const selected = manifest.cliTools?.selected ?? [];
|
|
17823
|
+
if (selected.length === 0) {
|
|
17824
|
+
printBox(
|
|
17825
|
+
"CLI tools",
|
|
17826
|
+
[
|
|
17827
|
+
"(no CLI tools selected)",
|
|
17828
|
+
"",
|
|
17829
|
+
"Run `npx hatch3r cli-tools` to open the picker."
|
|
17830
|
+
],
|
|
17831
|
+
"info"
|
|
17832
|
+
);
|
|
17833
|
+
return;
|
|
17834
|
+
}
|
|
17835
|
+
const results = await detectCliTools(selected);
|
|
17836
|
+
const lines = [];
|
|
17837
|
+
for (const r of results) {
|
|
17838
|
+
const meta = AVAILABLE_CLI_TOOLS[r.id];
|
|
17839
|
+
const tierLabel = meta ? `tier ${meta.tier}` : "(unknown)";
|
|
17840
|
+
const status = r.installed ? `${chalk16.green("\u2713")} ${r.path}` : `${chalk16.yellow("\u2717")} not on PATH`;
|
|
17841
|
+
lines.push(` ${chalk16.cyan(r.id)} (${tierLabel}) \u2014 ${status}`);
|
|
17842
|
+
}
|
|
17843
|
+
const installed = results.filter((r) => r.installed).length;
|
|
17844
|
+
lines.push("");
|
|
17845
|
+
lines.push(label("Detected", `${installed}/${results.length} installed`));
|
|
17846
|
+
printBox("CLI tools", lines, installed === results.length ? "success" : "info");
|
|
17847
|
+
}
|
|
17848
|
+
async function cliToolsInstallCommand() {
|
|
17849
|
+
printBanner(true);
|
|
17850
|
+
const rootDir = process.cwd();
|
|
17851
|
+
const manifest = await readManifest(rootDir);
|
|
17852
|
+
requireManifest2(rootDir, manifest);
|
|
17853
|
+
const selected = manifest.cliTools?.selected ?? [];
|
|
17854
|
+
if (selected.length === 0) {
|
|
17855
|
+
info("No CLI tools selected \u2014 run `npx hatch3r cli-tools` first to opt in.");
|
|
17856
|
+
return;
|
|
17857
|
+
}
|
|
17858
|
+
const spinner = createSpinner(`Detecting ${selected.length} CLI tool(s)...`);
|
|
17859
|
+
spinner.start();
|
|
17860
|
+
const missing = await findMissingCliTools(selected);
|
|
17861
|
+
if (missing.length === 0) {
|
|
17862
|
+
spinner.succeed(`All ${selected.length} CLI tool(s) already installed`);
|
|
17863
|
+
return;
|
|
17864
|
+
}
|
|
17865
|
+
spinner.warn(`${missing.length}/${selected.length} CLI tool(s) missing`);
|
|
17866
|
+
await offerInstaller(missing, { interactive: true });
|
|
17867
|
+
if (selected.length > 0) {
|
|
17868
|
+
const finalMissing = await findMissingCliTools(selected);
|
|
17869
|
+
printMissingCliToolsDisclaimer(finalMissing, selected.length);
|
|
17870
|
+
}
|
|
17871
|
+
}
|
|
17872
|
+
async function cliToolsDetectCommand() {
|
|
17873
|
+
printBanner(true);
|
|
17874
|
+
const rootDir = process.cwd();
|
|
17875
|
+
const manifest = await readManifest(rootDir);
|
|
17876
|
+
requireManifest2(rootDir, manifest);
|
|
17877
|
+
const selected = manifest.cliTools?.selected ?? [];
|
|
17878
|
+
if (selected.length === 0) {
|
|
17879
|
+
info("No CLI tools selected \u2014 run `npx hatch3r cli-tools` first to opt in.");
|
|
17880
|
+
return;
|
|
17881
|
+
}
|
|
17882
|
+
const results = await detectCliTools(selected);
|
|
17883
|
+
const lines = [];
|
|
17884
|
+
let installed = 0;
|
|
17885
|
+
for (const r of results) {
|
|
17886
|
+
if (r.installed) {
|
|
17887
|
+
lines.push(` ${chalk16.green("\u2713")} ${r.id} \u2014 ${r.path}`);
|
|
17888
|
+
installed++;
|
|
17889
|
+
} else {
|
|
17890
|
+
lines.push(` ${chalk16.yellow("\u2717")} ${r.id} \u2014 not on PATH`);
|
|
17891
|
+
}
|
|
17892
|
+
}
|
|
17893
|
+
lines.push("");
|
|
17894
|
+
lines.push(label("Installed", `${installed}/${results.length}`));
|
|
17895
|
+
if (installed < results.length) {
|
|
17896
|
+
lines.push("");
|
|
17897
|
+
lines.push(`Run ${chalk16.bold("npx hatch3r cli-tools install")} to see install commands.`);
|
|
17898
|
+
}
|
|
17899
|
+
printBox("CLI tool detection", lines, installed === results.length ? "success" : "info");
|
|
17900
|
+
if (installed < results.length) {
|
|
17901
|
+
warn(`${results.length - installed} CLI tool(s) not detected`);
|
|
17902
|
+
}
|
|
17903
|
+
}
|
|
17904
|
+
|
|
16053
17905
|
// src/cli/program.ts
|
|
16054
17906
|
init_version();
|
|
16055
17907
|
init_types();
|
|
@@ -16097,7 +17949,7 @@ function createProgram() {
|
|
|
16097
17949
|
program2.command("init").description("Install a complete agent setup into the current repo (first-run: creates .agents/ directory)").option(
|
|
16098
17950
|
"--tools <tools>",
|
|
16099
17951
|
`Comma-separated tools (${TOOL_CHOICES})`
|
|
16100
|
-
).option("--yes", "Skip interactive prompts, use defaults").option("--quick", "Skip all prompts and use smart defaults (alias for --yes)").option("--default", "Skip all prompts and use smart defaults (alias for --yes)").option("--preset <preset>", "Content preset: minimal, standard, full (default: full)").option("--project-type <type>", "Project type: greenfield, brownfield").option("--team-size <size>", "Team size: solo, team").option("--worktree", "Enable git worktree file isolation (overrides tool auto-detect)").option("--no-worktree", "Disable git worktree file isolation").option("--workspace", "Initialize as a multi-repo workspace").action(initCommand);
|
|
17952
|
+
).option("--yes", "Skip interactive prompts, use defaults").option("--quick", "Skip all prompts and use smart defaults (alias for --yes)").option("--default", "Skip all prompts and use smart defaults (alias for --yes)").option("--preset <preset>", "Content preset: minimal, standard, full (default: full)").option("--project-type <type>", "Project type: greenfield, brownfield").option("--team-size <size>", "Team size: solo, team").option("--worktree", "Enable git worktree file isolation (overrides tool auto-detect)").option("--no-worktree", "Disable git worktree file isolation").option("--workspace", "Initialize as a multi-repo workspace").option("--cli-tools <ids>", "CLI tools to opt in on --yes: 'tier1', 'all', or comma-separated ids (default: tier-1 + triggered tier-2)").option("--no-cli-tools", "Skip the CLI-tools opt-in on --yes").option("--mcp", "Re-opt-in to MCP servers on --yes (MCP is now opt-in by default)").action(initCommand);
|
|
16101
17953
|
program2.command("sync").description("Re-generate tool outputs from canonical .agents/ state (run after editing .agents/)").option("--repos [paths...]", "Sync workspace content to sub-repos (all opted-in if no paths given)").option("--dry-run", "Show what would change without modifying files").option("--diff", "Show a before/after diff summary for each generated file").option("--force", "Overwrite locally modified files in sub-repos").option("--minimal", "Generate stripped-down output (no comments, minimal formatting) to reduce token usage").option("--strict-budget", "Fail sync if any adapter's generated output exceeds its context budget (default: warn)").option("--verbose", "Show detailed output for each file processed").action(syncCommand);
|
|
16102
17954
|
program2.command("status").description("Check sync status between canonical .agents/ and generated files").option("--verbose", "Show detailed per-file status information").option("--deep", "Regenerate every adapter's output in-memory to compare byte-for-byte (slower; default uses integrity-manifest fast path)").action(statusCommand);
|
|
16103
17955
|
program2.command("update").description("Pull latest hatch3r templates with safe merge (preserves customizations)").option("--yes", "Skip interactive prompts, use defaults").option("--diff", "Show a before/after diff summary for each generated file").option("--force", "Override the preflight integrity check and proceed despite drift").option("--offline, --skip-fetch", "Skip the package fetch step; regenerate only from already-installed canonical content").option("--dry-run", "Preview what would change (added/modified/unchanged per adapter) without writing files").action(updateCommand);
|
|
@@ -16128,6 +17980,15 @@ function createProgram() {
|
|
|
16128
17980
|
).action(addCommand);
|
|
16129
17981
|
program2.command("worktree-setup [name]").description("Create a git worktree by name and populate hatch3r files (auto-resolved to .worktrees/<name>)").option("--from <path>", "Main repo path (auto-detected by default)").option("--from-path <path>", "Legacy mode: populate an existing worktree at <path> (skips git worktree add). Used by editor hooks.").option("--dry-run", "Show what would be done without changes").option("--force", "Overwrite existing files in the worktree").option("--yes", "Skip the secret-propagation confirmation prompt").action(worktreeSetupCommand);
|
|
16130
17982
|
program2.command("worktree-cleanup").description("Discover hatch3r-managed worktrees from the main repo, then clean files and remove the selected worktree(s)").option("--dry-run", "Show what would be done without changes").option("--all", "Skip the all/specific prompt and clean every hatch3r-managed worktree").option("--yes", "Skip selection and confirmation prompts (implies --all unless paths are filtered upstream)").option("--files-only", "Remove hatch3r-managed files only; keep the git worktree and its directory").action(worktreeCleanupCommand);
|
|
17983
|
+
const mcpCmd = program2.command("mcp").description("Manage MCP servers (now opt-in; CLI tools are the default)");
|
|
17984
|
+
mcpCmd.command("setup").description("Open the MCP server picker and update the manifest + .env.mcp").action(mcpSetupCommand);
|
|
17985
|
+
mcpCmd.command("list").description("Show current MCP server configuration plus .env.mcp status").action(mcpListCommand);
|
|
17986
|
+
mcpCmd.command("remove <id>").description("Remove an MCP server by id").action(mcpRemoveCommand);
|
|
17987
|
+
mcpCmd.command("env-check").description("Audit .env.mcp for missing required environment variables").action(mcpEnvCheckCommand);
|
|
17988
|
+
const cliCmd = program2.command("cli-tools").description("Manage CLI tool integrations (ripgrep, jq, gh, \u2026)").action(cliToolsCommand);
|
|
17989
|
+
cliCmd.command("list").description("Show current CLI tool selection plus detection status").action(cliToolsListCommand);
|
|
17990
|
+
cliCmd.command("install").description("Print install commands for any selected CLI tools missing on PATH").action(cliToolsInstallCommand);
|
|
17991
|
+
cliCmd.command("detect").description("Read-only detection report for the current CLI tool selection").action(cliToolsDetectCommand);
|
|
16131
17992
|
program2.on("command:*", (operands) => {
|
|
16132
17993
|
const cmd = operands[0];
|
|
16133
17994
|
if (cmd && AGENT_COMMAND_NAMES.has(cmd)) {
|
|
@@ -16177,15 +18038,15 @@ function classifyCliError(err, flags) {
|
|
|
16177
18038
|
// src/cli/shared/updateNotifier.ts
|
|
16178
18039
|
init_version();
|
|
16179
18040
|
import updateNotifier from "update-notifier";
|
|
16180
|
-
import
|
|
18041
|
+
import chalk17 from "chalk";
|
|
16181
18042
|
var ONE_DAY_MS = 1e3 * 60 * 60 * 24;
|
|
16182
18043
|
function buildMessage(update) {
|
|
16183
|
-
const fromTo = `${
|
|
16184
|
-
const cyan =
|
|
18044
|
+
const fromTo = `${chalk17.dim(update.current)} \u2192 ${chalk17.green(update.latest)}`;
|
|
18045
|
+
const cyan = chalk17.hex("#06b6d4");
|
|
16185
18046
|
return [
|
|
16186
18047
|
`Update available ${fromTo}`,
|
|
16187
|
-
|
|
16188
|
-
|
|
18048
|
+
chalk17.dim("Run ") + cyan("npm i -g hatch3r@latest") + chalk17.dim(" to update the CLI"),
|
|
18049
|
+
chalk17.dim("Then run ") + cyan("hatch3r update") + chalk17.dim(" to refresh project content")
|
|
16189
18050
|
].join("\n");
|
|
16190
18051
|
}
|
|
16191
18052
|
function checkForUpdates() {
|