hatch3r 1.7.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/agents/hatch3r-architect.md +1 -1
- package/agents/hatch3r-fixer.md +1 -1
- package/agents/hatch3r-implementer.md +1 -1
- package/agents/hatch3r-researcher.md +1 -1
- package/agents/modes/requirements-elicitation.md +1 -0
- package/agents/shared/quality-charter.md +1 -0
- package/agents/shared/user-question-protocol.md +95 -0
- package/commands/board/shared-azure-devops.md +2 -0
- package/commands/board/shared-github.md +17 -0
- package/commands/board/shared-gitlab.md +4 -0
- package/commands/hatch3r-board-fill.md +1 -1
- package/commands/hatch3r-board-pickup.md +1 -1
- package/commands/hatch3r-board-shared.md +21 -0
- package/commands/hatch3r-pr-resolve.md +668 -0
- package/commands/hatch3r-quick-change.md +1 -1
- package/commands/hatch3r-report.md +167 -0
- package/commands/hatch3r-revision.md +1 -1
- package/commands/hatch3r-workflow.md +1 -1
- package/dist/cli/index.js +951 -647
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
- package/rules/hatch3r-agent-orchestration.md +23 -0
- package/rules/hatch3r-agent-orchestration.mdc +23 -0
- package/rules/hatch3r-deep-context.md +1 -1
- package/rules/hatch3r-deep-context.mdc +1 -1
- package/rules/hatch3r-iteration-summary.md +2 -0
- package/rules/hatch3r-iteration-summary.mdc +2 -0
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.1";
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
20
|
|
|
@@ -307,7 +307,8 @@ ${MANAGED_BLOCK_END}`;
|
|
|
307
307
|
}
|
|
308
308
|
const before = existingContent.substring(0, startIdx);
|
|
309
309
|
const after = existingContent.substring(endIdx + MANAGED_BLOCK_END.length);
|
|
310
|
-
|
|
310
|
+
const result = `${before}${block}${after}`;
|
|
311
|
+
return result.endsWith("\n") ? result : result + "\n";
|
|
311
312
|
}
|
|
312
313
|
function extractManagedBlock(content) {
|
|
313
314
|
const startIdx = content.indexOf(MANAGED_BLOCK_START);
|
|
@@ -330,7 +331,8 @@ function extractCustomContent(content) {
|
|
|
330
331
|
function wrapInManagedBlock(content) {
|
|
331
332
|
return `${MANAGED_BLOCK_START}
|
|
332
333
|
${content.trim()}
|
|
333
|
-
${MANAGED_BLOCK_END}
|
|
334
|
+
${MANAGED_BLOCK_END}
|
|
335
|
+
`;
|
|
334
336
|
}
|
|
335
337
|
function hasManagedBlock(content) {
|
|
336
338
|
return content.includes(MANAGED_BLOCK_START) && content.includes(MANAGED_BLOCK_END);
|
|
@@ -1307,7 +1309,8 @@ async function safeWriteFile(filePath, content, options = {}) {
|
|
|
1307
1309
|
if (options.managedContent) {
|
|
1308
1310
|
if (!hasManagedBlock(existingContent)) {
|
|
1309
1311
|
if (options.appendIfNoBlock) {
|
|
1310
|
-
|
|
1312
|
+
let prepended = [content.trim(), "", existingContent.trimStart()].join("\n");
|
|
1313
|
+
if (!prepended.endsWith("\n")) prepended += "\n";
|
|
1311
1314
|
if (skipIfUnchanged && prepended === existingContent) {
|
|
1312
1315
|
return { path: filePath, action: "unchanged" };
|
|
1313
1316
|
}
|
|
@@ -2319,7 +2322,9 @@ var init_archive = __esm({
|
|
|
2319
2322
|
var hatchJson_exports = {};
|
|
2320
2323
|
__export(hatchJson_exports, {
|
|
2321
2324
|
addManagedFile: () => addManagedFile,
|
|
2325
|
+
applyPreservedManifestFields: () => applyPreservedManifestFields,
|
|
2322
2326
|
createManifest: () => createManifest,
|
|
2327
|
+
extractPreservedManifestFields: () => extractPreservedManifestFields,
|
|
2323
2328
|
isValidGitBranchName: () => isValidGitBranchName,
|
|
2324
2329
|
migrateManifest: () => migrateManifest,
|
|
2325
2330
|
readManifest: () => readManifest,
|
|
@@ -2590,6 +2595,64 @@ function addManagedFile(manifest, filePath) {
|
|
|
2590
2595
|
function removeManagedFile(manifest, filePath) {
|
|
2591
2596
|
manifest.managedFiles = manifest.managedFiles.filter((f) => f !== filePath);
|
|
2592
2597
|
}
|
|
2598
|
+
function extractPreservedManifestFields(manifest) {
|
|
2599
|
+
const out = {};
|
|
2600
|
+
if (manifest.board) out.board = manifest.board;
|
|
2601
|
+
if (manifest.costTracking) out.costTracking = manifest.costTracking;
|
|
2602
|
+
if (manifest.specs) out.specs = manifest.specs;
|
|
2603
|
+
if (manifest.userContent) out.userContent = manifest.userContent;
|
|
2604
|
+
if (manifest.hooks) out.hooks = manifest.hooks;
|
|
2605
|
+
if (manifest.models) out.models = manifest.models;
|
|
2606
|
+
if (manifest.claude) out.claude = manifest.claude;
|
|
2607
|
+
if (manifest.repos) out.repos = manifest.repos;
|
|
2608
|
+
if (manifest.packages) out.packages = manifest.packages;
|
|
2609
|
+
if (manifest.workspace) out.workspace = manifest.workspace;
|
|
2610
|
+
if (manifest.worktree?.extraPatterns !== void 0 || manifest.worktree?.nodeModules !== void 0) {
|
|
2611
|
+
out.worktreeExtras = {};
|
|
2612
|
+
if (manifest.worktree.extraPatterns !== void 0) {
|
|
2613
|
+
out.worktreeExtras.extraPatterns = manifest.worktree.extraPatterns;
|
|
2614
|
+
}
|
|
2615
|
+
if (manifest.worktree.nodeModules !== void 0) {
|
|
2616
|
+
out.worktreeExtras.nodeModules = manifest.worktree.nodeModules;
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
return out;
|
|
2620
|
+
}
|
|
2621
|
+
function applyPreservedManifestFields(manifest, preserved) {
|
|
2622
|
+
if (preserved.board) {
|
|
2623
|
+
if (manifest.board) {
|
|
2624
|
+
manifest.board = {
|
|
2625
|
+
...preserved.board,
|
|
2626
|
+
owner: manifest.board.owner,
|
|
2627
|
+
repo: manifest.board.repo,
|
|
2628
|
+
defaultBranch: manifest.board.defaultBranch ?? preserved.board.defaultBranch
|
|
2629
|
+
};
|
|
2630
|
+
} else {
|
|
2631
|
+
manifest.board = {
|
|
2632
|
+
...preserved.board,
|
|
2633
|
+
owner: manifest.owner || preserved.board.owner,
|
|
2634
|
+
repo: manifest.repo || preserved.board.repo
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
if (preserved.costTracking) manifest.costTracking = preserved.costTracking;
|
|
2639
|
+
if (preserved.specs) manifest.specs = preserved.specs;
|
|
2640
|
+
if (preserved.userContent) manifest.userContent = preserved.userContent;
|
|
2641
|
+
if (preserved.hooks) manifest.hooks = preserved.hooks;
|
|
2642
|
+
if (preserved.models) manifest.models = preserved.models;
|
|
2643
|
+
if (preserved.claude) manifest.claude = preserved.claude;
|
|
2644
|
+
if (preserved.repos) manifest.repos = preserved.repos;
|
|
2645
|
+
if (preserved.packages) manifest.packages = preserved.packages;
|
|
2646
|
+
if (preserved.workspace) manifest.workspace = preserved.workspace;
|
|
2647
|
+
if (preserved.worktreeExtras && manifest.worktree?.enabled) {
|
|
2648
|
+
if (preserved.worktreeExtras.extraPatterns !== void 0) {
|
|
2649
|
+
manifest.worktree.extraPatterns = preserved.worktreeExtras.extraPatterns;
|
|
2650
|
+
}
|
|
2651
|
+
if (preserved.worktreeExtras.nodeModules !== void 0) {
|
|
2652
|
+
manifest.worktree.nodeModules = preserved.worktreeExtras.nodeModules;
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2593
2656
|
var init_hatchJson = __esm({
|
|
2594
2657
|
"src/manifest/hatchJson.ts"() {
|
|
2595
2658
|
"use strict";
|
|
@@ -2954,205 +3017,498 @@ var init_canonical = __esm({
|
|
|
2954
3017
|
}
|
|
2955
3018
|
});
|
|
2956
3019
|
|
|
2957
|
-
// src/
|
|
2958
|
-
function
|
|
2959
|
-
return
|
|
2960
|
-
}
|
|
2961
|
-
function resolveLanguageTags(projectLanguages) {
|
|
2962
|
-
const result = /* @__PURE__ */ new Set();
|
|
2963
|
-
for (const lang of projectLanguages) {
|
|
2964
|
-
const tag = LANGUAGE_TO_TAG[lang];
|
|
2965
|
-
if (tag) result.add(tag);
|
|
2966
|
-
}
|
|
2967
|
-
return result;
|
|
2968
|
-
}
|
|
2969
|
-
function filterByLanguages(items, projectLanguages) {
|
|
2970
|
-
if (projectLanguages.length === 0) return [...items];
|
|
2971
|
-
const relevant = resolveLanguageTags(projectLanguages);
|
|
2972
|
-
return items.filter((item) => {
|
|
2973
|
-
if (item.protected) return true;
|
|
2974
|
-
const itemLangTags = item.tags.filter(isLanguageTag);
|
|
2975
|
-
if (itemLangTags.length === 0) return true;
|
|
2976
|
-
return itemLangTags.some((t) => relevant.has(t));
|
|
2977
|
-
});
|
|
2978
|
-
}
|
|
2979
|
-
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;
|
|
2980
|
-
var init_tags = __esm({
|
|
2981
|
-
"src/content/tags.ts"() {
|
|
2982
|
-
"use strict";
|
|
2983
|
-
TAG_CORE = "core";
|
|
2984
|
-
TAG_PLANNING = "planning";
|
|
2985
|
-
TAG_IMPLEMENTATION = "implementation";
|
|
2986
|
-
TAG_REVIEW = "review";
|
|
2987
|
-
TAG_DEVOPS = "devops";
|
|
2988
|
-
TAG_MAINTENANCE = "maintenance";
|
|
2989
|
-
TAG_BOARD = "board";
|
|
2990
|
-
TAG_SECURITY = "security";
|
|
2991
|
-
TAG_A11Y = "a11y";
|
|
2992
|
-
TAG_PERFORMANCE = "performance";
|
|
2993
|
-
TAG_CUSTOMIZE = "customize";
|
|
2994
|
-
TAG_LANG_TYPESCRIPT = "lang:typescript";
|
|
2995
|
-
TAG_LANG_PYTHON = "lang:python";
|
|
2996
|
-
TAG_LANG_GO = "lang:go";
|
|
2997
|
-
TAG_LANG_RUST = "lang:rust";
|
|
2998
|
-
TAG_LANG_JAVA = "lang:java";
|
|
2999
|
-
TAG_LANG_RUBY = "lang:ruby";
|
|
3000
|
-
WORKFLOW_TAGS = [
|
|
3001
|
-
TAG_CORE,
|
|
3002
|
-
TAG_PLANNING,
|
|
3003
|
-
TAG_IMPLEMENTATION,
|
|
3004
|
-
TAG_REVIEW,
|
|
3005
|
-
TAG_DEVOPS,
|
|
3006
|
-
TAG_MAINTENANCE
|
|
3007
|
-
];
|
|
3008
|
-
DOMAIN_TAGS = [
|
|
3009
|
-
TAG_BOARD,
|
|
3010
|
-
TAG_SECURITY,
|
|
3011
|
-
TAG_A11Y,
|
|
3012
|
-
TAG_PERFORMANCE,
|
|
3013
|
-
TAG_CUSTOMIZE
|
|
3014
|
-
];
|
|
3015
|
-
LANGUAGE_TO_TAG = {
|
|
3016
|
-
typescript: TAG_LANG_TYPESCRIPT,
|
|
3017
|
-
javascript: TAG_LANG_TYPESCRIPT,
|
|
3018
|
-
// JS projects also benefit from TS rules
|
|
3019
|
-
python: TAG_LANG_PYTHON,
|
|
3020
|
-
go: TAG_LANG_GO,
|
|
3021
|
-
rust: TAG_LANG_RUST,
|
|
3022
|
-
java: TAG_LANG_JAVA,
|
|
3023
|
-
kotlin: TAG_LANG_JAVA,
|
|
3024
|
-
// Kotlin shares Java ecosystem
|
|
3025
|
-
ruby: TAG_LANG_RUBY
|
|
3026
|
-
};
|
|
3027
|
-
}
|
|
3028
|
-
});
|
|
3029
|
-
|
|
3030
|
-
// src/content/index.ts
|
|
3031
|
-
import { readFile as readFile9, readdir as readdir6, cp as cp2, mkdir as mkdir4, rm as rm2, stat as stat5 } from "fs/promises";
|
|
3032
|
-
import { createHash as createHash3 } from "crypto";
|
|
3033
|
-
import { join as join12, dirname as dirname6, normalize, isAbsolute, posix as posix2 } from "path";
|
|
3034
|
-
function assertSafePath(relativePath, label2) {
|
|
3035
|
-
const sanitized = relativePath.replace(/\0/g, "");
|
|
3036
|
-
const normalized = normalize(sanitized);
|
|
3037
|
-
if (normalized.startsWith("..") || isAbsolute(normalized)) {
|
|
3038
|
-
throw new HatchError(`Unsafe path detected in ${label2}: ${relativePath}`, 1, "FS_ERROR");
|
|
3039
|
-
}
|
|
3040
|
-
if (sanitized !== relativePath) {
|
|
3041
|
-
throw new HatchError(`Unsafe path detected in ${label2}: ${relativePath}`, 1, "FS_ERROR");
|
|
3042
|
-
}
|
|
3020
|
+
// src/pipeline/agentToolAllowlist.ts
|
|
3021
|
+
function getAgentToolPolicy(agentId) {
|
|
3022
|
+
return policyMap.get(agentId);
|
|
3043
3023
|
}
|
|
3044
|
-
function
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3024
|
+
function levenshtein(a, b) {
|
|
3025
|
+
if (a === b) return 0;
|
|
3026
|
+
if (a.length === 0) return b.length;
|
|
3027
|
+
if (b.length === 0) return a.length;
|
|
3028
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
3029
|
+
let curr = new Array(b.length + 1);
|
|
3030
|
+
for (let i = 1; i <= a.length; i++) {
|
|
3031
|
+
curr[0] = i;
|
|
3032
|
+
for (let j = 1; j <= b.length; j++) {
|
|
3033
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
3034
|
+
curr[j] = Math.min(
|
|
3035
|
+
curr[j - 1] + 1,
|
|
3036
|
+
// insertion
|
|
3037
|
+
prev[j] + 1,
|
|
3038
|
+
// deletion
|
|
3039
|
+
prev[j - 1] + cost
|
|
3040
|
+
// substitution
|
|
3041
|
+
);
|
|
3042
|
+
}
|
|
3043
|
+
[prev, curr] = [curr, prev];
|
|
3050
3044
|
}
|
|
3051
|
-
return [
|
|
3045
|
+
return prev[b.length];
|
|
3052
3046
|
}
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
for (const
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
} catch {
|
|
3062
|
-
continue;
|
|
3063
|
-
}
|
|
3064
|
-
const refs = extractContentReferences(content);
|
|
3065
|
-
for (const ref of refs) {
|
|
3066
|
-
if (ref === item.id) continue;
|
|
3067
|
-
if (!allIds.has(ref) && !allIds.has(`${COMMAND_ID_PREFIX}${ref}`)) {
|
|
3068
|
-
warnings.push(
|
|
3069
|
-
`${item.type} "${item.id}" references "${ref}" which does not exist in the content index`
|
|
3070
|
-
);
|
|
3071
|
-
}
|
|
3047
|
+
function suggestNearestCategory(tool) {
|
|
3048
|
+
let bestMatch;
|
|
3049
|
+
let bestDistance = Infinity;
|
|
3050
|
+
for (const known of ALL_TOOL_CATEGORIES) {
|
|
3051
|
+
const dist = levenshtein(tool, known);
|
|
3052
|
+
if (dist < bestDistance) {
|
|
3053
|
+
bestDistance = dist;
|
|
3054
|
+
bestMatch = known;
|
|
3072
3055
|
}
|
|
3073
3056
|
}
|
|
3074
|
-
return
|
|
3057
|
+
return bestDistance <= 2 ? bestMatch : void 0;
|
|
3075
3058
|
}
|
|
3076
|
-
function
|
|
3059
|
+
function validateToolPolicies(policies = AGENT_TOOL_POLICIES) {
|
|
3077
3060
|
const warnings = [];
|
|
3078
|
-
const
|
|
3079
|
-
const
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3061
|
+
const knownCategories = new Set(ALL_TOOL_CATEGORIES);
|
|
3062
|
+
for (const policy of policies) {
|
|
3063
|
+
if (policy.allowedTools.length === 0) {
|
|
3064
|
+
warnings.push(`Agent "${policy.agentId}" has an empty tool allowlist \u2014 it cannot invoke any tools.`);
|
|
3065
|
+
}
|
|
3066
|
+
const hasAll = ALL_TOOL_CATEGORIES.every(
|
|
3067
|
+
(cat) => policy.allowedTools.includes(cat)
|
|
3068
|
+
);
|
|
3069
|
+
if (hasAll) {
|
|
3083
3070
|
warnings.push(
|
|
3084
|
-
`
|
|
3071
|
+
`Agent "${policy.agentId}" has access to all tool categories \u2014 consider restricting to least privilege.`
|
|
3085
3072
|
);
|
|
3086
3073
|
}
|
|
3074
|
+
for (const tool of policy.allowedTools) {
|
|
3075
|
+
if (!knownCategories.has(tool)) {
|
|
3076
|
+
const suggestion = suggestNearestCategory(tool);
|
|
3077
|
+
const didYouMean = suggestion ? ` Did you mean "${suggestion}"?` : "";
|
|
3078
|
+
throw new HatchError(
|
|
3079
|
+
`Invalid tool policy for agent "${policy.agentId}": unknown tool category "${tool}".${didYouMean} Valid categories: ${ALL_TOOL_CATEGORIES.join(", ")}.`,
|
|
3080
|
+
1,
|
|
3081
|
+
"VALIDATION_ERROR"
|
|
3082
|
+
);
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3087
3085
|
}
|
|
3088
3086
|
return warnings;
|
|
3089
3087
|
}
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3088
|
+
var AGENT_TOOL_POLICIES, policyMap, ALL_TOOL_CATEGORIES;
|
|
3089
|
+
var init_agentToolAllowlist = __esm({
|
|
3090
|
+
"src/pipeline/agentToolAllowlist.ts"() {
|
|
3091
|
+
"use strict";
|
|
3092
|
+
init_types();
|
|
3093
|
+
AGENT_TOOL_POLICIES = [
|
|
3094
|
+
{
|
|
3095
|
+
agentId: "hatch3r-researcher",
|
|
3096
|
+
allowedTools: ["read", "search", "web", "mcp"],
|
|
3097
|
+
description: "Read-only research: file reading, code search, web research, MCP queries. No write or execute."
|
|
3098
|
+
},
|
|
3099
|
+
{
|
|
3100
|
+
agentId: "hatch3r-implementer",
|
|
3101
|
+
allowedTools: ["read", "search", "write", "execute"],
|
|
3102
|
+
description: "Code implementation: file read/write, code search, command execution (tests, linters). No git, board, or web."
|
|
3103
|
+
},
|
|
3104
|
+
{
|
|
3105
|
+
agentId: "hatch3r-reviewer",
|
|
3106
|
+
allowedTools: ["read", "search"],
|
|
3107
|
+
description: "Code review: file reading and code search only. No write, execute, git, or board."
|
|
3108
|
+
},
|
|
3109
|
+
{
|
|
3110
|
+
agentId: "hatch3r-fixer",
|
|
3111
|
+
allowedTools: ["read", "search", "write", "execute"],
|
|
3112
|
+
description: "Fix application: file read/write, code search, command execution. No git, board, or web."
|
|
3113
|
+
},
|
|
3114
|
+
{
|
|
3115
|
+
agentId: "hatch3r-test-writer",
|
|
3116
|
+
allowedTools: ["read", "search", "write", "execute"],
|
|
3117
|
+
description: "Test writing: file read/write, code search, test execution. No git, board, or web."
|
|
3118
|
+
},
|
|
3119
|
+
{
|
|
3120
|
+
agentId: "hatch3r-security-auditor",
|
|
3121
|
+
allowedTools: ["read", "search", "execute"],
|
|
3122
|
+
description: "Security audit: file reading, code search, security tool execution. No write, git, board, or web."
|
|
3123
|
+
},
|
|
3124
|
+
{
|
|
3125
|
+
agentId: "hatch3r-docs-writer",
|
|
3126
|
+
allowedTools: ["read", "search", "write"],
|
|
3127
|
+
description: "Documentation: file read/write, code search. No execute, git, board, or web."
|
|
3128
|
+
},
|
|
3129
|
+
{
|
|
3130
|
+
agentId: "hatch3r-lint-fixer",
|
|
3131
|
+
allowedTools: ["read", "search", "write", "execute"],
|
|
3132
|
+
description: "Lint fixing: file read/write, code search, linter execution. No git, board, or web."
|
|
3133
|
+
},
|
|
3134
|
+
{
|
|
3135
|
+
agentId: "hatch3r-a11y-auditor",
|
|
3136
|
+
allowedTools: ["read", "search", "execute"],
|
|
3137
|
+
description: "Accessibility audit: file reading, code search, a11y tool execution. No write, git, board, or web."
|
|
3138
|
+
},
|
|
3139
|
+
{
|
|
3140
|
+
agentId: "hatch3r-perf-profiler",
|
|
3141
|
+
allowedTools: ["read", "search", "execute"],
|
|
3142
|
+
description: "Performance profiling: file reading, code search, profiler execution. No write, git, board, or web."
|
|
3143
|
+
},
|
|
3144
|
+
{
|
|
3145
|
+
agentId: "hatch3r-dependency-auditor",
|
|
3146
|
+
allowedTools: ["read", "search", "execute"],
|
|
3147
|
+
description: "Dependency audit: file reading, code search, audit tool execution. No write, git, board, or web."
|
|
3148
|
+
},
|
|
3149
|
+
{
|
|
3150
|
+
agentId: "hatch3r-architect",
|
|
3151
|
+
allowedTools: ["read", "search", "write"],
|
|
3152
|
+
description: "Architecture: file read/write (docs/ADRs), code search. No execute, git, board, or web."
|
|
3153
|
+
},
|
|
3154
|
+
{
|
|
3155
|
+
agentId: "hatch3r-devops",
|
|
3156
|
+
allowedTools: ["read", "search", "write", "execute"],
|
|
3157
|
+
description: "DevOps: file read/write, code search, CI/CD command execution. No git, board, or web."
|
|
3158
|
+
},
|
|
3159
|
+
{
|
|
3160
|
+
agentId: "hatch3r-ci-watcher",
|
|
3161
|
+
allowedTools: ["read", "search"],
|
|
3162
|
+
description: "CI monitoring: file reading, code search. No write, execute, git, board, or web."
|
|
3163
|
+
},
|
|
3164
|
+
{
|
|
3165
|
+
agentId: "hatch3r-context-rules",
|
|
3166
|
+
allowedTools: ["read", "search"],
|
|
3167
|
+
description: "Context loading: file reading and code search only. No write, execute, git, board, or web."
|
|
3168
|
+
},
|
|
3169
|
+
{
|
|
3170
|
+
agentId: "hatch3r-learnings-loader",
|
|
3171
|
+
allowedTools: ["read", "search"],
|
|
3172
|
+
description: "Learnings loading: file reading and code search only. No write, execute, git, board, or web."
|
|
3173
|
+
}
|
|
3174
|
+
];
|
|
3175
|
+
policyMap = new Map(
|
|
3176
|
+
AGENT_TOOL_POLICIES.map((p) => [p.agentId, p])
|
|
3177
|
+
);
|
|
3178
|
+
ALL_TOOL_CATEGORIES = [
|
|
3179
|
+
"read",
|
|
3180
|
+
"search",
|
|
3181
|
+
"write",
|
|
3182
|
+
"execute",
|
|
3183
|
+
"web",
|
|
3184
|
+
"mcp",
|
|
3185
|
+
"git",
|
|
3186
|
+
"board"
|
|
3187
|
+
];
|
|
3188
|
+
}
|
|
3189
|
+
});
|
|
3190
|
+
|
|
3191
|
+
// src/pipeline/adapterToolTranslator.ts
|
|
3192
|
+
function toClaudeToolsFrontmatter(agentId) {
|
|
3193
|
+
const policy = getAgentToolPolicy(agentId);
|
|
3194
|
+
if (!policy) return null;
|
|
3195
|
+
const tools = resolveNativeTools(policy.allowedTools, CLAUDE_CATEGORY_MAP);
|
|
3196
|
+
if (tools.length === 0) return null;
|
|
3197
|
+
return tools.join(", ");
|
|
3198
|
+
}
|
|
3199
|
+
function toCopilotToolsFrontmatter(agentId) {
|
|
3200
|
+
const policy = getAgentToolPolicy(agentId);
|
|
3201
|
+
if (!policy) return null;
|
|
3202
|
+
const tools = resolveNativeTools(policy.allowedTools, COPILOT_CATEGORY_MAP);
|
|
3203
|
+
return tools.length === 0 ? null : tools;
|
|
3204
|
+
}
|
|
3205
|
+
function toWindsurfToolsFrontmatter(agentId) {
|
|
3206
|
+
const policy = getAgentToolPolicy(agentId);
|
|
3207
|
+
if (!policy) return null;
|
|
3208
|
+
const tools = resolveNativeTools(policy.allowedTools, WINDSURF_CATEGORY_MAP);
|
|
3209
|
+
if (tools.length === 0) return null;
|
|
3210
|
+
return tools.join(", ");
|
|
3211
|
+
}
|
|
3212
|
+
function toCursorReadonlyFrontmatter(agentId) {
|
|
3213
|
+
const policy = getAgentToolPolicy(agentId);
|
|
3214
|
+
if (!policy) return null;
|
|
3215
|
+
const hasWrite = policy.allowedTools.includes("write");
|
|
3216
|
+
const hasExecute = policy.allowedTools.includes("execute");
|
|
3217
|
+
return !hasWrite && !hasExecute;
|
|
3218
|
+
}
|
|
3219
|
+
function getAskUserToolEntry(adapter) {
|
|
3220
|
+
return ASK_USER_TOOLS[adapter] ?? null;
|
|
3221
|
+
}
|
|
3222
|
+
function toAskUserPlatformNote(adapter) {
|
|
3223
|
+
const entry = getAskUserToolEntry(adapter);
|
|
3224
|
+
if (entry === null) {
|
|
3225
|
+
return [
|
|
3226
|
+
`**Platform:** No documented native question tool for \`${adapter}\`.`,
|
|
3227
|
+
"Use the Plain-Text Fallback Template below for every ASK checkpoint."
|
|
3228
|
+
].join(" ");
|
|
3229
|
+
}
|
|
3230
|
+
const hint = entry.invocationHint ? ` ${entry.invocationHint}` : "";
|
|
3231
|
+
return [
|
|
3232
|
+
`**Platform:** Invoke the \`${entry.name}\` tool for every ASK checkpoint on \`${adapter}\`.${hint}`,
|
|
3233
|
+
"Use the Plain-Text Fallback Template only when the tool cannot represent the question (e.g., long free-text answers)."
|
|
3234
|
+
].join(" ");
|
|
3235
|
+
}
|
|
3236
|
+
function buildAskUserPlatformTable() {
|
|
3237
|
+
const rows = Object.entries(ASK_USER_TOOLS).map(([adapter, entry]) => {
|
|
3238
|
+
if (entry === null) {
|
|
3239
|
+
return `| \`${adapter}\` | _No documented native tool \u2014 use the Plain-Text Fallback Template below._ |`;
|
|
3240
|
+
}
|
|
3241
|
+
return `| \`${adapter}\` | Invoke the \`${entry.name}\` tool for every ASK checkpoint. |`;
|
|
3242
|
+
});
|
|
3243
|
+
return [
|
|
3244
|
+
"| Adapter | Platform-Native Question Tool |",
|
|
3245
|
+
"|---------|-------------------------------|",
|
|
3246
|
+
...rows
|
|
3247
|
+
].join("\n");
|
|
3248
|
+
}
|
|
3249
|
+
function substituteCanonicalPlatformMarker(content) {
|
|
3250
|
+
if (!content.includes(PLATFORM_TOOL_MARKER)) return content;
|
|
3251
|
+
return content.split(PLATFORM_TOOL_MARKER).join(buildAskUserPlatformTable());
|
|
3252
|
+
}
|
|
3253
|
+
function resolveNativeTools(categories, map) {
|
|
3254
|
+
const out = /* @__PURE__ */ new Set();
|
|
3255
|
+
for (const cat of categories) {
|
|
3256
|
+
const native = map[cat];
|
|
3257
|
+
if (!native) continue;
|
|
3258
|
+
for (const t of native) out.add(t);
|
|
3259
|
+
}
|
|
3260
|
+
return [...out];
|
|
3261
|
+
}
|
|
3262
|
+
var CLAUDE_CATEGORY_MAP, COPILOT_CATEGORY_MAP, WINDSURF_CATEGORY_MAP, ASK_USER_TOOLS, PLATFORM_TOOL_MARKER;
|
|
3263
|
+
var init_adapterToolTranslator = __esm({
|
|
3264
|
+
"src/pipeline/adapterToolTranslator.ts"() {
|
|
3265
|
+
"use strict";
|
|
3266
|
+
init_agentToolAllowlist();
|
|
3267
|
+
CLAUDE_CATEGORY_MAP = {
|
|
3268
|
+
read: ["Read", "NotebookRead"],
|
|
3269
|
+
search: ["Grep", "Glob"],
|
|
3270
|
+
write: ["Edit", "MultiEdit", "Write", "NotebookEdit"],
|
|
3271
|
+
execute: ["Bash"],
|
|
3272
|
+
web: ["WebSearch", "WebFetch"],
|
|
3273
|
+
mcp: [],
|
|
3274
|
+
// MCP tools are scoped via the `mcpServers` frontmatter field, not `tools`.
|
|
3275
|
+
git: ["Bash"],
|
|
3276
|
+
// Git is driven via Bash; callers that grant git retain execute semantics.
|
|
3277
|
+
board: []
|
|
3278
|
+
// Project-board tooling is MCP-driven; see mcp mapping.
|
|
3279
|
+
};
|
|
3280
|
+
COPILOT_CATEGORY_MAP = {
|
|
3281
|
+
read: ["read"],
|
|
3282
|
+
search: ["search"],
|
|
3283
|
+
write: ["edit"],
|
|
3284
|
+
execute: ["execute"],
|
|
3285
|
+
web: ["web"],
|
|
3286
|
+
mcp: [],
|
|
3287
|
+
// MCP exposure is controlled via `mcp-servers`, not `tools`.
|
|
3288
|
+
git: ["execute"],
|
|
3289
|
+
board: []
|
|
3290
|
+
};
|
|
3291
|
+
WINDSURF_CATEGORY_MAP = CLAUDE_CATEGORY_MAP;
|
|
3292
|
+
ASK_USER_TOOLS = {
|
|
3293
|
+
claude: { name: "AskUserQuestion" },
|
|
3294
|
+
cursor: null,
|
|
3295
|
+
copilot: null,
|
|
3296
|
+
windsurf: null,
|
|
3297
|
+
codex: null,
|
|
3298
|
+
cline: null,
|
|
3299
|
+
opencode: null,
|
|
3300
|
+
amp: null,
|
|
3301
|
+
aider: null,
|
|
3302
|
+
kiro: null,
|
|
3303
|
+
goose: null,
|
|
3304
|
+
zed: null,
|
|
3305
|
+
"amazon-q": null,
|
|
3306
|
+
gemini: null,
|
|
3307
|
+
antigravity: null
|
|
3308
|
+
};
|
|
3309
|
+
PLATFORM_TOOL_MARKER = "<!-- HATCH3R:PLATFORM-TOOL -->";
|
|
3310
|
+
}
|
|
3311
|
+
});
|
|
3312
|
+
|
|
3313
|
+
// src/content/tags.ts
|
|
3314
|
+
function isLanguageTag(tag) {
|
|
3315
|
+
return tag.startsWith("lang:");
|
|
3316
|
+
}
|
|
3317
|
+
function resolveLanguageTags(projectLanguages) {
|
|
3318
|
+
const result = /* @__PURE__ */ new Set();
|
|
3319
|
+
for (const lang of projectLanguages) {
|
|
3320
|
+
const tag = LANGUAGE_TO_TAG[lang];
|
|
3321
|
+
if (tag) result.add(tag);
|
|
3322
|
+
}
|
|
3323
|
+
return result;
|
|
3324
|
+
}
|
|
3325
|
+
function filterByLanguages(items, projectLanguages) {
|
|
3326
|
+
if (projectLanguages.length === 0) return [...items];
|
|
3327
|
+
const relevant = resolveLanguageTags(projectLanguages);
|
|
3328
|
+
return items.filter((item) => {
|
|
3329
|
+
if (item.protected) return true;
|
|
3330
|
+
const itemLangTags = item.tags.filter(isLanguageTag);
|
|
3331
|
+
if (itemLangTags.length === 0) return true;
|
|
3332
|
+
return itemLangTags.some((t) => relevant.has(t));
|
|
3333
|
+
});
|
|
3334
|
+
}
|
|
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;
|
|
3336
|
+
var init_tags = __esm({
|
|
3337
|
+
"src/content/tags.ts"() {
|
|
3338
|
+
"use strict";
|
|
3339
|
+
TAG_CORE = "core";
|
|
3340
|
+
TAG_PLANNING = "planning";
|
|
3341
|
+
TAG_IMPLEMENTATION = "implementation";
|
|
3342
|
+
TAG_REVIEW = "review";
|
|
3343
|
+
TAG_DEVOPS = "devops";
|
|
3344
|
+
TAG_MAINTENANCE = "maintenance";
|
|
3345
|
+
TAG_BOARD = "board";
|
|
3346
|
+
TAG_SECURITY = "security";
|
|
3347
|
+
TAG_A11Y = "a11y";
|
|
3348
|
+
TAG_PERFORMANCE = "performance";
|
|
3349
|
+
TAG_CUSTOMIZE = "customize";
|
|
3350
|
+
TAG_LANG_TYPESCRIPT = "lang:typescript";
|
|
3351
|
+
TAG_LANG_PYTHON = "lang:python";
|
|
3352
|
+
TAG_LANG_GO = "lang:go";
|
|
3353
|
+
TAG_LANG_RUST = "lang:rust";
|
|
3354
|
+
TAG_LANG_JAVA = "lang:java";
|
|
3355
|
+
TAG_LANG_RUBY = "lang:ruby";
|
|
3356
|
+
WORKFLOW_TAGS = [
|
|
3357
|
+
TAG_CORE,
|
|
3358
|
+
TAG_PLANNING,
|
|
3359
|
+
TAG_IMPLEMENTATION,
|
|
3360
|
+
TAG_REVIEW,
|
|
3361
|
+
TAG_DEVOPS,
|
|
3362
|
+
TAG_MAINTENANCE
|
|
3363
|
+
];
|
|
3364
|
+
DOMAIN_TAGS = [
|
|
3365
|
+
TAG_BOARD,
|
|
3366
|
+
TAG_SECURITY,
|
|
3367
|
+
TAG_A11Y,
|
|
3368
|
+
TAG_PERFORMANCE,
|
|
3369
|
+
TAG_CUSTOMIZE
|
|
3370
|
+
];
|
|
3371
|
+
LANGUAGE_TO_TAG = {
|
|
3372
|
+
typescript: TAG_LANG_TYPESCRIPT,
|
|
3373
|
+
javascript: TAG_LANG_TYPESCRIPT,
|
|
3374
|
+
// JS projects also benefit from TS rules
|
|
3375
|
+
python: TAG_LANG_PYTHON,
|
|
3376
|
+
go: TAG_LANG_GO,
|
|
3377
|
+
rust: TAG_LANG_RUST,
|
|
3378
|
+
java: TAG_LANG_JAVA,
|
|
3379
|
+
kotlin: TAG_LANG_JAVA,
|
|
3380
|
+
// Kotlin shares Java ecosystem
|
|
3381
|
+
ruby: TAG_LANG_RUBY
|
|
3382
|
+
};
|
|
3383
|
+
}
|
|
3384
|
+
});
|
|
3385
|
+
|
|
3386
|
+
// src/content/index.ts
|
|
3387
|
+
import { readFile as readFile9, readdir as readdir6, cp as cp2, mkdir as mkdir4, rm as rm2, stat as stat5 } from "fs/promises";
|
|
3388
|
+
import { createHash as createHash3 } from "crypto";
|
|
3389
|
+
import { join as join12, dirname as dirname6, normalize, isAbsolute, posix as posix2 } from "path";
|
|
3390
|
+
function assertSafePath(relativePath, label2) {
|
|
3391
|
+
const sanitized = relativePath.replace(/\0/g, "");
|
|
3392
|
+
const normalized = normalize(sanitized);
|
|
3393
|
+
if (normalized.startsWith("..") || isAbsolute(normalized)) {
|
|
3394
|
+
throw new HatchError(`Unsafe path detected in ${label2}: ${relativePath}`, 1, "FS_ERROR");
|
|
3395
|
+
}
|
|
3396
|
+
if (sanitized !== relativePath) {
|
|
3397
|
+
throw new HatchError(`Unsafe path detected in ${label2}: ${relativePath}`, 1, "FS_ERROR");
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
function extractContentReferences(content) {
|
|
3401
|
+
const refs = /* @__PURE__ */ new Set();
|
|
3402
|
+
const pattern = /`((?:cmd-)?hatch3r-[a-z0-9-]+)`/g;
|
|
3403
|
+
let match;
|
|
3404
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
3405
|
+
refs.add(match[1]);
|
|
3406
|
+
}
|
|
3407
|
+
return [...refs];
|
|
3408
|
+
}
|
|
3409
|
+
async function validateCrossReferences(contentRoot, index) {
|
|
3410
|
+
const warnings = [];
|
|
3411
|
+
const allIds = new Set(index.items.map((item) => item.id));
|
|
3412
|
+
for (const item of index.items) {
|
|
3413
|
+
let content;
|
|
3414
|
+
try {
|
|
3415
|
+
const filePath = item.type === "skill" ? join12(contentRoot, item.relativePath, "SKILL.md") : join12(contentRoot, `${item.relativePath}`);
|
|
3416
|
+
content = await readFile9(filePath, "utf-8");
|
|
3417
|
+
} catch {
|
|
3418
|
+
continue;
|
|
3419
|
+
}
|
|
3420
|
+
const refs = extractContentReferences(content);
|
|
3421
|
+
for (const ref of refs) {
|
|
3422
|
+
if (ref === item.id) continue;
|
|
3423
|
+
if (!allIds.has(ref) && !allIds.has(`${COMMAND_ID_PREFIX}${ref}`)) {
|
|
3424
|
+
warnings.push(
|
|
3425
|
+
`${item.type} "${item.id}" references "${ref}" which does not exist in the content index`
|
|
3426
|
+
);
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
return { warnings };
|
|
3431
|
+
}
|
|
3432
|
+
function validateOrchestrationDependencies(selection) {
|
|
3433
|
+
const warnings = [];
|
|
3434
|
+
const selectedAgents = new Set(selection.items.agents);
|
|
3435
|
+
const hasOrchestration = selection.items.rules.includes("hatch3r-agent-orchestration");
|
|
3436
|
+
if (!hasOrchestration) return warnings;
|
|
3437
|
+
for (const agentId of ORCHESTRATION_REQUIRED_AGENTS) {
|
|
3438
|
+
if (!selectedAgents.has(agentId)) {
|
|
3439
|
+
warnings.push(
|
|
3440
|
+
`Orchestration pipeline requires agent "${agentId}" but it is not in the content selection. The 4-phase pipeline (Research \u2192 Implement \u2192 Review \u2192 Quality) will be incomplete.`
|
|
3441
|
+
);
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
return warnings;
|
|
3445
|
+
}
|
|
3446
|
+
function typeIdKey(type, id) {
|
|
3447
|
+
return `${type}:${id}`;
|
|
3448
|
+
}
|
|
3449
|
+
function getAllItemsById(index, id) {
|
|
3450
|
+
return index.items.filter((item) => item.id === id);
|
|
3451
|
+
}
|
|
3452
|
+
function applyCommandPrefix(id, type) {
|
|
3453
|
+
return type === "command" ? `${COMMAND_ID_PREFIX}${id}` : id;
|
|
3454
|
+
}
|
|
3455
|
+
async function scanContentRoot(rootPath, source, items) {
|
|
3456
|
+
for (const config of CONTENT_TYPE_CONFIGS) {
|
|
3457
|
+
if (source === "user" && config.type !== "agent" && config.type !== "skill" && config.type !== "rule" && config.type !== "command" && config.type !== "hook") {
|
|
3458
|
+
continue;
|
|
3459
|
+
}
|
|
3460
|
+
const dirPath = join12(rootPath, config.dir);
|
|
3461
|
+
if (config.strategy === "subdirectory") {
|
|
3462
|
+
let dirents;
|
|
3463
|
+
try {
|
|
3464
|
+
dirents = (await readdir6(dirPath, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
|
|
3465
|
+
} catch (err) {
|
|
3466
|
+
if (err.code === "ENOENT") continue;
|
|
3467
|
+
throw err;
|
|
3468
|
+
}
|
|
3469
|
+
for (const dirent of dirents) {
|
|
3470
|
+
if (!dirent.isDirectory()) continue;
|
|
3471
|
+
const skillPath = join12(dirPath, dirent.name, "SKILL.md");
|
|
3472
|
+
try {
|
|
3473
|
+
const raw = await readFile9(skillPath, "utf-8");
|
|
3474
|
+
const { metadata } = parseFrontmatter(raw);
|
|
3475
|
+
const rawId = metadata.id || metadata.name || dirent.name;
|
|
3476
|
+
const id = applyCommandPrefix(rawId, config.type);
|
|
3477
|
+
const item = {
|
|
3478
|
+
id,
|
|
3479
|
+
type: config.type,
|
|
3480
|
+
description: metadata.description ?? "",
|
|
3481
|
+
tags: metadata.tags ?? [],
|
|
3482
|
+
protected: metadata.protected,
|
|
3483
|
+
relativePath: posix2.join(config.dir, dirent.name),
|
|
3484
|
+
source
|
|
3485
|
+
};
|
|
3486
|
+
if (source === "user") {
|
|
3487
|
+
const adapters = parseAdaptersFrontmatter(raw);
|
|
3488
|
+
if (adapters) item.adapters = adapters;
|
|
3489
|
+
}
|
|
3490
|
+
items.push(item);
|
|
3491
|
+
} catch (err) {
|
|
3492
|
+
if (err.code !== "ENOENT") throw err;
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
} else {
|
|
3496
|
+
let entries;
|
|
3497
|
+
try {
|
|
3498
|
+
const all = await readdir6(dirPath);
|
|
3499
|
+
entries = all.filter((f) => f.endsWith(".md")).sort();
|
|
3500
|
+
} catch (err) {
|
|
3501
|
+
if (err.code === "ENOENT") continue;
|
|
3502
|
+
throw err;
|
|
3503
|
+
}
|
|
3504
|
+
for (const file of entries) {
|
|
3505
|
+
const filePath = join12(dirPath, file);
|
|
3506
|
+
const raw = await readFile9(filePath, "utf-8");
|
|
3507
|
+
const { metadata } = parseFrontmatter(raw);
|
|
3508
|
+
const rawId = metadata.id || metadata.name || file.replace(/\.md$/, "");
|
|
3509
|
+
const id = applyCommandPrefix(rawId, config.type);
|
|
3510
|
+
const item = {
|
|
3511
|
+
id,
|
|
3156
3512
|
type: config.type,
|
|
3157
3513
|
description: metadata.description ?? "",
|
|
3158
3514
|
tags: metadata.tags ?? [],
|
|
@@ -3470,8 +3826,26 @@ async function copySelectedContent(contentRoot, agentsDir, selection, index, opt
|
|
|
3470
3826
|
} catch (err) {
|
|
3471
3827
|
if (err.code !== "ENOENT") throw err;
|
|
3472
3828
|
}
|
|
3829
|
+
await substitutePlatformToolMarker(agentsDir);
|
|
3473
3830
|
return copied;
|
|
3474
3831
|
}
|
|
3832
|
+
async function substitutePlatformToolMarker(agentsDir) {
|
|
3833
|
+
const sharedDir = join12(agentsDir, "agents", "shared");
|
|
3834
|
+
let entries;
|
|
3835
|
+
try {
|
|
3836
|
+
entries = await readdir6(sharedDir, { withFileTypes: true });
|
|
3837
|
+
} catch (err) {
|
|
3838
|
+
if (err.code === "ENOENT") return;
|
|
3839
|
+
throw err;
|
|
3840
|
+
}
|
|
3841
|
+
for (const entry of entries) {
|
|
3842
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
3843
|
+
const filePath = join12(sharedDir, entry.name);
|
|
3844
|
+
const content = await readFile9(filePath, "utf-8");
|
|
3845
|
+
if (!content.includes(PLATFORM_TOOL_MARKER)) continue;
|
|
3846
|
+
await atomicWriteFile(filePath, substituteCanonicalPlatformMarker(content));
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3475
3849
|
async function buildSelectionsFromDisk(agentsDir) {
|
|
3476
3850
|
const items = {
|
|
3477
3851
|
agents: [],
|
|
@@ -3624,6 +3998,7 @@ var init_content = __esm({
|
|
|
3624
3998
|
"use strict";
|
|
3625
3999
|
init_canonical();
|
|
3626
4000
|
init_safeWrite();
|
|
4001
|
+
init_adapterToolTranslator();
|
|
3627
4002
|
init_types();
|
|
3628
4003
|
init_tags();
|
|
3629
4004
|
ORCHESTRATION_REQUIRED_AGENTS = [
|
|
@@ -3947,8 +4322,7 @@ async function generateRootAgentsMd(agentsDir) {
|
|
|
3947
4322
|
return { full: AGENTS_MD_FULL, inner: AGENTS_MD_INNER };
|
|
3948
4323
|
}
|
|
3949
4324
|
const inner = sections.join("\n");
|
|
3950
|
-
const full =
|
|
3951
|
-
`;
|
|
4325
|
+
const full = wrapInManagedBlock(inner);
|
|
3952
4326
|
return { full, inner };
|
|
3953
4327
|
}
|
|
3954
4328
|
async function generateCanonicalAgentsMd(agentsDir) {
|
|
@@ -4172,8 +4546,7 @@ You are running the **minimal** content preset \u2014 only core agents and workf
|
|
|
4172
4546
|
"- Skills: `/.agents/skills/`",
|
|
4173
4547
|
"- Commands: `/.agents/commands/`"
|
|
4174
4548
|
].join("\n");
|
|
4175
|
-
AGENTS_MD_FULL =
|
|
4176
|
-
`;
|
|
4549
|
+
AGENTS_MD_FULL = wrapInManagedBlock(AGENTS_MD_INNER);
|
|
4177
4550
|
}
|
|
4178
4551
|
});
|
|
4179
4552
|
|
|
@@ -4609,6 +4982,7 @@ var init_base = __esm({
|
|
|
4609
4982
|
init_customization();
|
|
4610
4983
|
init_mcp_utils();
|
|
4611
4984
|
init_hooks();
|
|
4985
|
+
init_adapterToolTranslator();
|
|
4612
4986
|
BaseAdapter = class {
|
|
4613
4987
|
warnings = [];
|
|
4614
4988
|
/**
|
|
@@ -4805,9 +5179,10 @@ var init_base = __esm({
|
|
|
4805
5179
|
);
|
|
4806
5180
|
const minimal = this.isMinimal(ctx);
|
|
4807
5181
|
for (const rule of rules) {
|
|
4808
|
-
const { content, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, rule);
|
|
5182
|
+
const { content: raw, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, rule);
|
|
4809
5183
|
this.warnings.push(...warnings);
|
|
4810
5184
|
if (skip) continue;
|
|
5185
|
+
const content = this.substituteAskUserMarker(raw);
|
|
4811
5186
|
const desc = overrides.description ?? rule.description;
|
|
4812
5187
|
if (minimal) {
|
|
4813
5188
|
lines.push(`## ${rule.id}`, "", this.stripMinimal(content), "");
|
|
@@ -4824,9 +5199,10 @@ var init_base = __esm({
|
|
|
4824
5199
|
const agents = await this.readUserFacingCanonicalFiles(ctx.agentsDir, "agents");
|
|
4825
5200
|
const minimal = this.isMinimal(ctx);
|
|
4826
5201
|
for (const agent of agents) {
|
|
4827
|
-
const { content, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, agent);
|
|
5202
|
+
const { content: raw, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, agent);
|
|
4828
5203
|
this.warnings.push(...warnings);
|
|
4829
5204
|
if (skip) continue;
|
|
5205
|
+
const content = this.substituteAskUserMarker(raw);
|
|
4830
5206
|
const model = resolveAgentModel(agent.id, agent, ctx.manifest, overrides);
|
|
4831
5207
|
const desc = overrides.description ?? agent.description;
|
|
4832
5208
|
const fmt = model ? (formatModel ?? defaultModelFormat)(model) : void 0;
|
|
@@ -4848,9 +5224,10 @@ var init_base = __esm({
|
|
|
4848
5224
|
const results = [];
|
|
4849
5225
|
const skills = await this.readTrackedCanonicalFiles(ctx.agentsDir, "skills");
|
|
4850
5226
|
for (const skill of skills) {
|
|
4851
|
-
const { content, skip, warnings } = await applyCustomizationRaw(ctx.projectRoot, skill);
|
|
5227
|
+
const { content: raw, skip, warnings } = await applyCustomizationRaw(ctx.projectRoot, skill);
|
|
4852
5228
|
this.warnings.push(...warnings);
|
|
4853
5229
|
if (skip) continue;
|
|
5230
|
+
const content = this.substituteAskUserMarker(raw);
|
|
4854
5231
|
results.push(output(pathFn(skill.id), wrapInManagedBlock(content), content));
|
|
4855
5232
|
}
|
|
4856
5233
|
return results;
|
|
@@ -4861,9 +5238,10 @@ var init_base = __esm({
|
|
|
4861
5238
|
const results = [];
|
|
4862
5239
|
const skills = await this.readTrackedCanonicalFiles(ctx.agentsDir, "skills");
|
|
4863
5240
|
for (const skill of skills) {
|
|
4864
|
-
const { content, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, skill);
|
|
5241
|
+
const { content: raw, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, skill);
|
|
4865
5242
|
this.warnings.push(...warnings);
|
|
4866
5243
|
if (skip) continue;
|
|
5244
|
+
const content = this.substituteAskUserMarker(raw);
|
|
4867
5245
|
const desc = overrides.description ?? skill.description;
|
|
4868
5246
|
const fm = `---
|
|
4869
5247
|
name: ${skill.id}
|
|
@@ -4881,9 +5259,10 @@ ${wrapInManagedBlock(content)}`, content));
|
|
|
4881
5259
|
const results = [];
|
|
4882
5260
|
const commands = await this.readUserFacingCanonicalFiles(ctx.agentsDir, "commands");
|
|
4883
5261
|
for (const cmd of commands) {
|
|
4884
|
-
const { content, skip, warnings } = await applyCustomizationRaw(ctx.projectRoot, cmd);
|
|
5262
|
+
const { content: raw, skip, warnings } = await applyCustomizationRaw(ctx.projectRoot, cmd);
|
|
4885
5263
|
this.warnings.push(...warnings);
|
|
4886
5264
|
if (skip) continue;
|
|
5265
|
+
const content = this.substituteAskUserMarker(raw);
|
|
4887
5266
|
results.push(output(pathFn(cmd.id), wrapInManagedBlock(content), content));
|
|
4888
5267
|
}
|
|
4889
5268
|
return results;
|
|
@@ -4937,469 +5316,240 @@ ${wrapInManagedBlock(content)}`, content));
|
|
|
4937
5316
|
return ctx.generationMode === "minimal";
|
|
4938
5317
|
}
|
|
4939
5318
|
/**
|
|
4940
|
-
*
|
|
4941
|
-
*
|
|
4942
|
-
*
|
|
5319
|
+
* Replace the `<!-- HATCH3R:PLATFORM-TOOL -->` marker in canonical content
|
|
5320
|
+
* with the per-adapter platform-note paragraph. Idempotent and a no-op
|
|
5321
|
+
* when the marker is absent.
|
|
5322
|
+
*
|
|
5323
|
+
* See agents/shared/user-question-protocol.md and
|
|
5324
|
+
* src/pipeline/adapterToolTranslator.ts::toAskUserPlatformNote.
|
|
4943
5325
|
*/
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
result = result.replace(/^[-*_]{3,}\s*$/gm, "");
|
|
4948
|
-
result = result.replace(/\n{3,}/g, "\n\n");
|
|
4949
|
-
result = result.trim();
|
|
4950
|
-
return result;
|
|
4951
|
-
}
|
|
4952
|
-
};
|
|
4953
|
-
}
|
|
4954
|
-
});
|
|
4955
|
-
|
|
4956
|
-
// src/adapters/aider.ts
|
|
4957
|
-
var AiderAdapter;
|
|
4958
|
-
var init_aider = __esm({
|
|
4959
|
-
"src/adapters/aider.ts"() {
|
|
4960
|
-
"use strict";
|
|
4961
|
-
init_types();
|
|
4962
|
-
init_managedBlocks();
|
|
4963
|
-
init_base();
|
|
4964
|
-
AiderAdapter = class extends BaseAdapter {
|
|
4965
|
-
name = "aider";
|
|
4966
|
-
async doGenerate(ctx) {
|
|
4967
|
-
const inner = [
|
|
4968
|
-
...await this.bridgeHeader(ctx),
|
|
4969
|
-
...await this.inlineRules(ctx),
|
|
4970
|
-
...await this.inlineAgents(ctx)
|
|
4971
|
-
].join("\n").trim();
|
|
4972
|
-
const results = [
|
|
4973
|
-
output("CONVENTIONS.md", wrapInManagedBlock(inner), inner)
|
|
4974
|
-
];
|
|
4975
|
-
results.push(
|
|
4976
|
-
...await this.processSkillsRaw(ctx, (id) => `.aider/skills/${toPrefixedId(id)}/SKILL.md`)
|
|
4977
|
-
);
|
|
4978
|
-
results.push(output(".aider.conf.yml", [
|
|
4979
|
-
"# Managed by hatch3r \u2014 do not edit manually",
|
|
4980
|
-
"read:",
|
|
4981
|
-
" - CONVENTIONS.md",
|
|
4982
|
-
" - .agents/AGENTS.md",
|
|
4983
|
-
"auto-lint: true",
|
|
4984
|
-
""
|
|
4985
|
-
].join("\n")));
|
|
4986
|
-
return results;
|
|
4987
|
-
}
|
|
4988
|
-
};
|
|
4989
|
-
}
|
|
4990
|
-
});
|
|
4991
|
-
|
|
4992
|
-
// src/adapters/amazonq.ts
|
|
4993
|
-
function mapToAmazonQEvent(event) {
|
|
4994
|
-
const mapping = {
|
|
4995
|
-
"session-start": "agentSpawn",
|
|
4996
|
-
"pre-commit": "preToolUse",
|
|
4997
|
-
"file-save": "postToolUse",
|
|
4998
|
-
"post-merge": "postToolUse",
|
|
4999
|
-
"ci-failure": "stop"
|
|
5000
|
-
};
|
|
5001
|
-
return mapping[event] ?? null;
|
|
5002
|
-
}
|
|
5003
|
-
var AmazonQAdapter;
|
|
5004
|
-
var init_amazonq = __esm({
|
|
5005
|
-
"src/adapters/amazonq.ts"() {
|
|
5006
|
-
"use strict";
|
|
5007
|
-
init_types();
|
|
5008
|
-
init_managedBlocks();
|
|
5009
|
-
init_base();
|
|
5010
|
-
init_customization();
|
|
5011
|
-
AmazonQAdapter = class extends BaseAdapter {
|
|
5012
|
-
name = "amazon-q";
|
|
5013
|
-
async doGenerate(ctx) {
|
|
5014
|
-
const results = [];
|
|
5015
|
-
const inner = [
|
|
5016
|
-
...await this.bridgeHeader(ctx),
|
|
5017
|
-
...await this.inlineRules(ctx),
|
|
5018
|
-
...await this.inlineAgents(ctx)
|
|
5019
|
-
].join("\n").trim();
|
|
5020
|
-
results.push(output(".amazonq/rules/hatch3r-agents.md", wrapInManagedBlock(inner), inner));
|
|
5021
|
-
results.push(
|
|
5022
|
-
...await this.processSkillsRaw(ctx, (id) => `.amazonq/rules/hatch3r-skill-${id}.md`)
|
|
5023
|
-
);
|
|
5024
|
-
const mcp = await this.readFilteredMcp(ctx);
|
|
5025
|
-
if (mcp && Object.keys(mcp).length > 0) {
|
|
5026
|
-
const entries = this.buildStdMcpEntries(mcp, "shell");
|
|
5027
|
-
if (Object.keys(entries).length > 0) {
|
|
5028
|
-
results.push(output(".amazonq/mcp.json", JSON.stringify({ mcpServers: entries }, null, 2)));
|
|
5029
|
-
}
|
|
5030
|
-
}
|
|
5031
|
-
const hooks = await this.readHooks(ctx);
|
|
5032
|
-
const descriptorHooks = this.buildDescriptorHooks(hooks);
|
|
5033
|
-
if (ctx.features.agents) {
|
|
5034
|
-
const agents = await this.readUserFacingCanonicalFiles(ctx.agentsDir, "agents");
|
|
5035
|
-
for (const agent of agents) {
|
|
5036
|
-
const { content, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, agent);
|
|
5037
|
-
this.warnings.push(...warnings);
|
|
5038
|
-
if (skip) continue;
|
|
5039
|
-
const desc = overrides.description ?? agent.description;
|
|
5040
|
-
const descriptor = {
|
|
5041
|
-
name: toPrefixedId(agent.id),
|
|
5042
|
-
description: desc,
|
|
5043
|
-
instructions: content,
|
|
5044
|
-
// C7.5-W2B2-H33: hatch3r writes .amazonq/mcp.json; setting this to
|
|
5045
|
-
// true makes new agent descriptors inherit it, otherwise Q CLI
|
|
5046
|
-
// ignores the legacy MCP file when a cli-agent is spawned.
|
|
5047
|
-
useLegacyMcpJson: true
|
|
5048
|
-
};
|
|
5049
|
-
if (Object.keys(descriptorHooks).length > 0) {
|
|
5050
|
-
descriptor.hooks = descriptorHooks;
|
|
5051
|
-
}
|
|
5052
|
-
results.push(output(
|
|
5053
|
-
`.amazonq/cli-agents/${toPrefixedId(agent.id)}.json`,
|
|
5054
|
-
JSON.stringify(descriptor, null, 2)
|
|
5055
|
-
));
|
|
5056
|
-
}
|
|
5057
|
-
}
|
|
5058
|
-
if (hooks.length > 0) {
|
|
5059
|
-
const hookLines = ["# Hatch3r Hooks", ""];
|
|
5060
|
-
for (const hook of hooks) {
|
|
5061
|
-
const amazonQEvent = mapToAmazonQEvent(hook.event);
|
|
5062
|
-
if (!amazonQEvent) continue;
|
|
5063
|
-
hookLines.push(`## ${hook.id}`, "");
|
|
5064
|
-
hookLines.push(`**Event:** ${amazonQEvent} (${hook.event})`);
|
|
5065
|
-
hookLines.push(`**Agent:** ${hook.agent}`);
|
|
5066
|
-
hookLines.push(`**Description:** ${hook.description}`);
|
|
5067
|
-
if (hook.condition?.globs) {
|
|
5068
|
-
hookLines.push(`**Globs:** ${hook.condition.globs.join(", ")}`);
|
|
5069
|
-
}
|
|
5070
|
-
hookLines.push("");
|
|
5071
|
-
hookLines.push(`HATCH3R_HOOK_ACTIVATED: When this hook's event (${hook.event}) is triggered, you MUST spawn the ${hook.agent} agent now. Read and follow the ${hook.agent} agent protocol in \`.agents/agents/${toPrefixedId(hook.agent)}.md\`.`);
|
|
5072
|
-
hookLines.push("");
|
|
5073
|
-
}
|
|
5074
|
-
if (hookLines.length > 2) {
|
|
5075
|
-
const hookContent = hookLines.join("\n");
|
|
5076
|
-
results.push(output(".amazonq/rules/hatch3r-hooks.md", wrapInManagedBlock(hookContent), hookContent));
|
|
5077
|
-
}
|
|
5078
|
-
}
|
|
5079
|
-
return results;
|
|
5326
|
+
substituteAskUserMarker(content) {
|
|
5327
|
+
if (!content.includes(PLATFORM_TOOL_MARKER)) return content;
|
|
5328
|
+
return content.split(PLATFORM_TOOL_MARKER).join(toAskUserPlatformNote(this.name));
|
|
5080
5329
|
}
|
|
5081
|
-
/**
|
|
5082
|
-
*
|
|
5083
|
-
*
|
|
5084
|
-
*
|
|
5085
|
-
* `postToolUse`) so each canonical event key holds an array of entries.
|
|
5086
|
-
*
|
|
5087
|
-
* Each entry emits an `echo` command that carries the HATCH3R_HOOK_ACTIVATED
|
|
5088
|
-
* directive so the surrounding agent (which reads the rules-bridge file)
|
|
5089
|
-
* knows which hatch3r hook fired and which agent to dispatch.
|
|
5330
|
+
/**
|
|
5331
|
+
* Strip verbose content for minimal generation mode.
|
|
5332
|
+
* Removes markdown comments, collapses excessive blank lines,
|
|
5333
|
+
* strips decorative formatting, and trims descriptions.
|
|
5090
5334
|
*/
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
command: `echo ${JSON.stringify(marker)}`
|
|
5099
|
-
};
|
|
5100
|
-
(grouped[amazonQEvent] ??= []).push(entry);
|
|
5101
|
-
}
|
|
5102
|
-
return grouped;
|
|
5335
|
+
stripMinimal(content) {
|
|
5336
|
+
let result = content;
|
|
5337
|
+
result = result.replace(/<!--[\s\S]*?-->/g, "");
|
|
5338
|
+
result = result.replace(/^[-*_]{3,}\s*$/gm, "");
|
|
5339
|
+
result = result.replace(/\n{3,}/g, "\n\n");
|
|
5340
|
+
result = result.trim();
|
|
5341
|
+
return result;
|
|
5103
5342
|
}
|
|
5104
5343
|
};
|
|
5105
5344
|
}
|
|
5106
5345
|
});
|
|
5107
5346
|
|
|
5108
|
-
// src/adapters/
|
|
5109
|
-
var
|
|
5110
|
-
var
|
|
5111
|
-
"src/adapters/
|
|
5347
|
+
// src/adapters/aider.ts
|
|
5348
|
+
var AiderAdapter;
|
|
5349
|
+
var init_aider = __esm({
|
|
5350
|
+
"src/adapters/aider.ts"() {
|
|
5112
5351
|
"use strict";
|
|
5352
|
+
init_types();
|
|
5353
|
+
init_managedBlocks();
|
|
5113
5354
|
init_base();
|
|
5114
|
-
|
|
5115
|
-
name = "
|
|
5355
|
+
AiderAdapter = class extends BaseAdapter {
|
|
5356
|
+
name = "aider";
|
|
5116
5357
|
async doGenerate(ctx) {
|
|
5117
|
-
const
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5358
|
+
const inner = [
|
|
5359
|
+
...await this.bridgeHeader(ctx),
|
|
5360
|
+
...await this.inlineRules(ctx),
|
|
5361
|
+
...await this.inlineAgents(ctx)
|
|
5362
|
+
].join("\n").trim();
|
|
5363
|
+
const results = [
|
|
5364
|
+
output("CONVENTIONS.md", wrapInManagedBlock(inner), inner)
|
|
5365
|
+
];
|
|
5366
|
+
results.push(
|
|
5367
|
+
...await this.processSkillsRaw(ctx, (id) => `.aider/skills/${toPrefixedId(id)}/SKILL.md`)
|
|
5368
|
+
);
|
|
5369
|
+
results.push(output(".aider.conf.yml", [
|
|
5370
|
+
"# Managed by hatch3r \u2014 do not edit manually",
|
|
5371
|
+
"read:",
|
|
5372
|
+
" - CONVENTIONS.md",
|
|
5373
|
+
" - .agents/AGENTS.md",
|
|
5374
|
+
"auto-lint: true",
|
|
5375
|
+
""
|
|
5376
|
+
].join("\n")));
|
|
5125
5377
|
return results;
|
|
5126
5378
|
}
|
|
5127
5379
|
};
|
|
5128
5380
|
}
|
|
5129
5381
|
});
|
|
5130
5382
|
|
|
5131
|
-
// src/adapters/
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5383
|
+
// src/adapters/amazonq.ts
|
|
5384
|
+
function mapToAmazonQEvent(event) {
|
|
5385
|
+
const mapping = {
|
|
5386
|
+
"session-start": "agentSpawn",
|
|
5387
|
+
"pre-commit": "preToolUse",
|
|
5388
|
+
"file-save": "postToolUse",
|
|
5389
|
+
"post-merge": "postToolUse",
|
|
5390
|
+
"ci-failure": "stop"
|
|
5391
|
+
};
|
|
5392
|
+
return mapping[event] ?? null;
|
|
5393
|
+
}
|
|
5394
|
+
var AmazonQAdapter;
|
|
5395
|
+
var init_amazonq = __esm({
|
|
5396
|
+
"src/adapters/amazonq.ts"() {
|
|
5135
5397
|
"use strict";
|
|
5136
5398
|
init_types();
|
|
5137
5399
|
init_managedBlocks();
|
|
5138
5400
|
init_base();
|
|
5139
|
-
|
|
5140
|
-
|
|
5401
|
+
init_customization();
|
|
5402
|
+
AmazonQAdapter = class extends BaseAdapter {
|
|
5403
|
+
name = "amazon-q";
|
|
5141
5404
|
async doGenerate(ctx) {
|
|
5142
5405
|
const results = [];
|
|
5143
5406
|
const inner = [
|
|
5144
|
-
...await this.bridgeHeader(ctx
|
|
5407
|
+
...await this.bridgeHeader(ctx),
|
|
5145
5408
|
...await this.inlineRules(ctx),
|
|
5146
5409
|
...await this.inlineAgents(ctx)
|
|
5147
5410
|
].join("\n").trim();
|
|
5148
|
-
results.push(output(".
|
|
5411
|
+
results.push(output(".amazonq/rules/hatch3r-agents.md", wrapInManagedBlock(inner), inner));
|
|
5149
5412
|
results.push(
|
|
5150
|
-
...await this.processSkillsRaw(ctx, (id) => `.
|
|
5413
|
+
...await this.processSkillsRaw(ctx, (id) => `.amazonq/rules/hatch3r-skill-${id}.md`)
|
|
5151
5414
|
);
|
|
5152
5415
|
const mcp = await this.readFilteredMcp(ctx);
|
|
5153
5416
|
if (mcp && Object.keys(mcp).length > 0) {
|
|
5154
5417
|
const entries = this.buildStdMcpEntries(mcp, "shell");
|
|
5155
|
-
if (Object.keys(entries).length > 0) {
|
|
5156
|
-
results.push(output(".
|
|
5157
|
-
}
|
|
5158
|
-
}
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
}
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
if (policy.allowedTools.length === 0) {
|
|
5209
|
-
warnings.push(`Agent "${policy.agentId}" has an empty tool allowlist \u2014 it cannot invoke any tools.`);
|
|
5210
|
-
}
|
|
5211
|
-
const hasAll = ALL_TOOL_CATEGORIES.every(
|
|
5212
|
-
(cat) => policy.allowedTools.includes(cat)
|
|
5213
|
-
);
|
|
5214
|
-
if (hasAll) {
|
|
5215
|
-
warnings.push(
|
|
5216
|
-
`Agent "${policy.agentId}" has access to all tool categories \u2014 consider restricting to least privilege.`
|
|
5217
|
-
);
|
|
5218
|
-
}
|
|
5219
|
-
for (const tool of policy.allowedTools) {
|
|
5220
|
-
if (!knownCategories.has(tool)) {
|
|
5221
|
-
const suggestion = suggestNearestCategory(tool);
|
|
5222
|
-
const didYouMean = suggestion ? ` Did you mean "${suggestion}"?` : "";
|
|
5223
|
-
throw new HatchError(
|
|
5224
|
-
`Invalid tool policy for agent "${policy.agentId}": unknown tool category "${tool}".${didYouMean} Valid categories: ${ALL_TOOL_CATEGORIES.join(", ")}.`,
|
|
5225
|
-
1,
|
|
5226
|
-
"VALIDATION_ERROR"
|
|
5227
|
-
);
|
|
5228
|
-
}
|
|
5229
|
-
}
|
|
5230
|
-
}
|
|
5231
|
-
return warnings;
|
|
5232
|
-
}
|
|
5233
|
-
var AGENT_TOOL_POLICIES, policyMap, ALL_TOOL_CATEGORIES;
|
|
5234
|
-
var init_agentToolAllowlist = __esm({
|
|
5235
|
-
"src/pipeline/agentToolAllowlist.ts"() {
|
|
5236
|
-
"use strict";
|
|
5237
|
-
init_types();
|
|
5238
|
-
AGENT_TOOL_POLICIES = [
|
|
5239
|
-
{
|
|
5240
|
-
agentId: "hatch3r-researcher",
|
|
5241
|
-
allowedTools: ["read", "search", "web", "mcp"],
|
|
5242
|
-
description: "Read-only research: file reading, code search, web research, MCP queries. No write or execute."
|
|
5243
|
-
},
|
|
5244
|
-
{
|
|
5245
|
-
agentId: "hatch3r-implementer",
|
|
5246
|
-
allowedTools: ["read", "search", "write", "execute"],
|
|
5247
|
-
description: "Code implementation: file read/write, code search, command execution (tests, linters). No git, board, or web."
|
|
5248
|
-
},
|
|
5249
|
-
{
|
|
5250
|
-
agentId: "hatch3r-reviewer",
|
|
5251
|
-
allowedTools: ["read", "search"],
|
|
5252
|
-
description: "Code review: file reading and code search only. No write, execute, git, or board."
|
|
5253
|
-
},
|
|
5254
|
-
{
|
|
5255
|
-
agentId: "hatch3r-fixer",
|
|
5256
|
-
allowedTools: ["read", "search", "write", "execute"],
|
|
5257
|
-
description: "Fix application: file read/write, code search, command execution. No git, board, or web."
|
|
5258
|
-
},
|
|
5259
|
-
{
|
|
5260
|
-
agentId: "hatch3r-test-writer",
|
|
5261
|
-
allowedTools: ["read", "search", "write", "execute"],
|
|
5262
|
-
description: "Test writing: file read/write, code search, test execution. No git, board, or web."
|
|
5263
|
-
},
|
|
5264
|
-
{
|
|
5265
|
-
agentId: "hatch3r-security-auditor",
|
|
5266
|
-
allowedTools: ["read", "search", "execute"],
|
|
5267
|
-
description: "Security audit: file reading, code search, security tool execution. No write, git, board, or web."
|
|
5268
|
-
},
|
|
5269
|
-
{
|
|
5270
|
-
agentId: "hatch3r-docs-writer",
|
|
5271
|
-
allowedTools: ["read", "search", "write"],
|
|
5272
|
-
description: "Documentation: file read/write, code search. No execute, git, board, or web."
|
|
5273
|
-
},
|
|
5274
|
-
{
|
|
5275
|
-
agentId: "hatch3r-lint-fixer",
|
|
5276
|
-
allowedTools: ["read", "search", "write", "execute"],
|
|
5277
|
-
description: "Lint fixing: file read/write, code search, linter execution. No git, board, or web."
|
|
5278
|
-
},
|
|
5279
|
-
{
|
|
5280
|
-
agentId: "hatch3r-a11y-auditor",
|
|
5281
|
-
allowedTools: ["read", "search", "execute"],
|
|
5282
|
-
description: "Accessibility audit: file reading, code search, a11y tool execution. No write, git, board, or web."
|
|
5283
|
-
},
|
|
5284
|
-
{
|
|
5285
|
-
agentId: "hatch3r-perf-profiler",
|
|
5286
|
-
allowedTools: ["read", "search", "execute"],
|
|
5287
|
-
description: "Performance profiling: file reading, code search, profiler execution. No write, git, board, or web."
|
|
5288
|
-
},
|
|
5289
|
-
{
|
|
5290
|
-
agentId: "hatch3r-dependency-auditor",
|
|
5291
|
-
allowedTools: ["read", "search", "execute"],
|
|
5292
|
-
description: "Dependency audit: file reading, code search, audit tool execution. No write, git, board, or web."
|
|
5293
|
-
},
|
|
5294
|
-
{
|
|
5295
|
-
agentId: "hatch3r-architect",
|
|
5296
|
-
allowedTools: ["read", "search", "write"],
|
|
5297
|
-
description: "Architecture: file read/write (docs/ADRs), code search. No execute, git, board, or web."
|
|
5298
|
-
},
|
|
5299
|
-
{
|
|
5300
|
-
agentId: "hatch3r-devops",
|
|
5301
|
-
allowedTools: ["read", "search", "write", "execute"],
|
|
5302
|
-
description: "DevOps: file read/write, code search, CI/CD command execution. No git, board, or web."
|
|
5303
|
-
},
|
|
5304
|
-
{
|
|
5305
|
-
agentId: "hatch3r-ci-watcher",
|
|
5306
|
-
allowedTools: ["read", "search"],
|
|
5307
|
-
description: "CI monitoring: file reading, code search. No write, execute, git, board, or web."
|
|
5308
|
-
},
|
|
5309
|
-
{
|
|
5310
|
-
agentId: "hatch3r-context-rules",
|
|
5311
|
-
allowedTools: ["read", "search"],
|
|
5312
|
-
description: "Context loading: file reading and code search only. No write, execute, git, board, or web."
|
|
5313
|
-
},
|
|
5314
|
-
{
|
|
5315
|
-
agentId: "hatch3r-learnings-loader",
|
|
5316
|
-
allowedTools: ["read", "search"],
|
|
5317
|
-
description: "Learnings loading: file reading and code search only. No write, execute, git, board, or web."
|
|
5418
|
+
if (Object.keys(entries).length > 0) {
|
|
5419
|
+
results.push(output(".amazonq/mcp.json", JSON.stringify({ mcpServers: entries }, null, 2)));
|
|
5420
|
+
}
|
|
5421
|
+
}
|
|
5422
|
+
const hooks = await this.readHooks(ctx);
|
|
5423
|
+
const descriptorHooks = this.buildDescriptorHooks(hooks);
|
|
5424
|
+
if (ctx.features.agents) {
|
|
5425
|
+
const agents = await this.readUserFacingCanonicalFiles(ctx.agentsDir, "agents");
|
|
5426
|
+
for (const agent of agents) {
|
|
5427
|
+
const { content, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, agent);
|
|
5428
|
+
this.warnings.push(...warnings);
|
|
5429
|
+
if (skip) continue;
|
|
5430
|
+
const desc = overrides.description ?? agent.description;
|
|
5431
|
+
const descriptor = {
|
|
5432
|
+
name: toPrefixedId(agent.id),
|
|
5433
|
+
description: desc,
|
|
5434
|
+
instructions: content,
|
|
5435
|
+
// C7.5-W2B2-H33: hatch3r writes .amazonq/mcp.json; setting this to
|
|
5436
|
+
// true makes new agent descriptors inherit it, otherwise Q CLI
|
|
5437
|
+
// ignores the legacy MCP file when a cli-agent is spawned.
|
|
5438
|
+
useLegacyMcpJson: true
|
|
5439
|
+
};
|
|
5440
|
+
if (Object.keys(descriptorHooks).length > 0) {
|
|
5441
|
+
descriptor.hooks = descriptorHooks;
|
|
5442
|
+
}
|
|
5443
|
+
results.push(output(
|
|
5444
|
+
`.amazonq/cli-agents/${toPrefixedId(agent.id)}.json`,
|
|
5445
|
+
JSON.stringify(descriptor, null, 2)
|
|
5446
|
+
));
|
|
5447
|
+
}
|
|
5448
|
+
}
|
|
5449
|
+
if (hooks.length > 0) {
|
|
5450
|
+
const hookLines = ["# Hatch3r Hooks", ""];
|
|
5451
|
+
for (const hook of hooks) {
|
|
5452
|
+
const amazonQEvent = mapToAmazonQEvent(hook.event);
|
|
5453
|
+
if (!amazonQEvent) continue;
|
|
5454
|
+
hookLines.push(`## ${hook.id}`, "");
|
|
5455
|
+
hookLines.push(`**Event:** ${amazonQEvent} (${hook.event})`);
|
|
5456
|
+
hookLines.push(`**Agent:** ${hook.agent}`);
|
|
5457
|
+
hookLines.push(`**Description:** ${hook.description}`);
|
|
5458
|
+
if (hook.condition?.globs) {
|
|
5459
|
+
hookLines.push(`**Globs:** ${hook.condition.globs.join(", ")}`);
|
|
5460
|
+
}
|
|
5461
|
+
hookLines.push("");
|
|
5462
|
+
hookLines.push(`HATCH3R_HOOK_ACTIVATED: When this hook's event (${hook.event}) is triggered, you MUST spawn the ${hook.agent} agent now. Read and follow the ${hook.agent} agent protocol in \`.agents/agents/${toPrefixedId(hook.agent)}.md\`.`);
|
|
5463
|
+
hookLines.push("");
|
|
5464
|
+
}
|
|
5465
|
+
if (hookLines.length > 2) {
|
|
5466
|
+
const hookContent = hookLines.join("\n");
|
|
5467
|
+
results.push(output(".amazonq/rules/hatch3r-hooks.md", wrapInManagedBlock(hookContent), hookContent));
|
|
5468
|
+
}
|
|
5469
|
+
}
|
|
5470
|
+
return results;
|
|
5318
5471
|
}
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5472
|
+
/**
|
|
5473
|
+
* Build the `hooks` map for a cli-agent descriptor from hatch3r canonical
|
|
5474
|
+
* hook definitions. Group multiple hatch3r hooks that map to the same AWS
|
|
5475
|
+
* canonical event (e.g. `file-save` + `post-merge` both map to
|
|
5476
|
+
* `postToolUse`) so each canonical event key holds an array of entries.
|
|
5477
|
+
*
|
|
5478
|
+
* Each entry emits an `echo` command that carries the HATCH3R_HOOK_ACTIVATED
|
|
5479
|
+
* directive so the surrounding agent (which reads the rules-bridge file)
|
|
5480
|
+
* knows which hatch3r hook fired and which agent to dispatch.
|
|
5481
|
+
*/
|
|
5482
|
+
buildDescriptorHooks(hooks) {
|
|
5483
|
+
const grouped = {};
|
|
5484
|
+
for (const hook of hooks) {
|
|
5485
|
+
const amazonQEvent = mapToAmazonQEvent(hook.event);
|
|
5486
|
+
if (!amazonQEvent) continue;
|
|
5487
|
+
const marker = `HATCH3R_HOOK_ACTIVATED id=${hook.id} event=${hook.event} agent=${hook.agent}`;
|
|
5488
|
+
const entry = {
|
|
5489
|
+
command: `echo ${JSON.stringify(marker)}`
|
|
5490
|
+
};
|
|
5491
|
+
(grouped[amazonQEvent] ??= []).push(entry);
|
|
5492
|
+
}
|
|
5493
|
+
return grouped;
|
|
5494
|
+
}
|
|
5495
|
+
};
|
|
5333
5496
|
}
|
|
5334
5497
|
});
|
|
5335
5498
|
|
|
5336
|
-
// src/
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
const tools = resolveNativeTools(policy.allowedTools, CLAUDE_CATEGORY_MAP);
|
|
5341
|
-
if (tools.length === 0) return null;
|
|
5342
|
-
return tools.join(", ");
|
|
5343
|
-
}
|
|
5344
|
-
function toCopilotToolsFrontmatter(agentId) {
|
|
5345
|
-
const policy = getAgentToolPolicy(agentId);
|
|
5346
|
-
if (!policy) return null;
|
|
5347
|
-
const tools = resolveNativeTools(policy.allowedTools, COPILOT_CATEGORY_MAP);
|
|
5348
|
-
return tools.length === 0 ? null : tools;
|
|
5349
|
-
}
|
|
5350
|
-
function toWindsurfToolsFrontmatter(agentId) {
|
|
5351
|
-
const policy = getAgentToolPolicy(agentId);
|
|
5352
|
-
if (!policy) return null;
|
|
5353
|
-
const tools = resolveNativeTools(policy.allowedTools, WINDSURF_CATEGORY_MAP);
|
|
5354
|
-
if (tools.length === 0) return null;
|
|
5355
|
-
return tools.join(", ");
|
|
5356
|
-
}
|
|
5357
|
-
function toCursorReadonlyFrontmatter(agentId) {
|
|
5358
|
-
const policy = getAgentToolPolicy(agentId);
|
|
5359
|
-
if (!policy) return null;
|
|
5360
|
-
const hasWrite = policy.allowedTools.includes("write");
|
|
5361
|
-
const hasExecute = policy.allowedTools.includes("execute");
|
|
5362
|
-
return !hasWrite && !hasExecute;
|
|
5363
|
-
}
|
|
5364
|
-
function resolveNativeTools(categories, map) {
|
|
5365
|
-
const out = /* @__PURE__ */ new Set();
|
|
5366
|
-
for (const cat of categories) {
|
|
5367
|
-
const native = map[cat];
|
|
5368
|
-
if (!native) continue;
|
|
5369
|
-
for (const t of native) out.add(t);
|
|
5370
|
-
}
|
|
5371
|
-
return [...out];
|
|
5372
|
-
}
|
|
5373
|
-
var CLAUDE_CATEGORY_MAP, COPILOT_CATEGORY_MAP, WINDSURF_CATEGORY_MAP;
|
|
5374
|
-
var init_adapterToolTranslator = __esm({
|
|
5375
|
-
"src/pipeline/adapterToolTranslator.ts"() {
|
|
5499
|
+
// src/adapters/amp.ts
|
|
5500
|
+
var AmpAdapter;
|
|
5501
|
+
var init_amp = __esm({
|
|
5502
|
+
"src/adapters/amp.ts"() {
|
|
5376
5503
|
"use strict";
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5504
|
+
init_base();
|
|
5505
|
+
AmpAdapter = class extends BaseAdapter {
|
|
5506
|
+
name = "amp";
|
|
5507
|
+
async doGenerate(ctx) {
|
|
5508
|
+
const results = [];
|
|
5509
|
+
const mcp = await this.readFilteredMcp(ctx);
|
|
5510
|
+
if (mcp && Object.keys(mcp).length > 0) {
|
|
5511
|
+
const entries = this.buildStdMcpEntries(mcp, "shell");
|
|
5512
|
+
if (Object.keys(entries).length > 0) {
|
|
5513
|
+
results.push(output(".amp/settings.json", JSON.stringify({ "amp.mcpServers": entries }, null, 2)));
|
|
5514
|
+
}
|
|
5515
|
+
}
|
|
5516
|
+
return results;
|
|
5517
|
+
}
|
|
5390
5518
|
};
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5519
|
+
}
|
|
5520
|
+
});
|
|
5521
|
+
|
|
5522
|
+
// src/adapters/antigravity.ts
|
|
5523
|
+
var AntigravityAdapter;
|
|
5524
|
+
var init_antigravity = __esm({
|
|
5525
|
+
"src/adapters/antigravity.ts"() {
|
|
5526
|
+
"use strict";
|
|
5527
|
+
init_types();
|
|
5528
|
+
init_managedBlocks();
|
|
5529
|
+
init_base();
|
|
5530
|
+
AntigravityAdapter = class extends BaseAdapter {
|
|
5531
|
+
name = "antigravity";
|
|
5532
|
+
async doGenerate(ctx) {
|
|
5533
|
+
const results = [];
|
|
5534
|
+
const inner = [
|
|
5535
|
+
...await this.bridgeHeader(ctx, ".agents/AGENTS.md"),
|
|
5536
|
+
...await this.inlineRules(ctx),
|
|
5537
|
+
...await this.inlineAgents(ctx)
|
|
5538
|
+
].join("\n").trim();
|
|
5539
|
+
results.push(output(".antigravity/rules.md", wrapInManagedBlock(inner), inner));
|
|
5540
|
+
results.push(
|
|
5541
|
+
...await this.processSkillsRaw(ctx, (id) => `.agent/skills/${toPrefixedId(id)}/SKILL.md`)
|
|
5542
|
+
);
|
|
5543
|
+
const mcp = await this.readFilteredMcp(ctx);
|
|
5544
|
+
if (mcp && Object.keys(mcp).length > 0) {
|
|
5545
|
+
const entries = this.buildStdMcpEntries(mcp, "shell");
|
|
5546
|
+
if (Object.keys(entries).length > 0) {
|
|
5547
|
+
results.push(output(".antigravity/settings.json", JSON.stringify({ mcpServers: entries }, null, 2)));
|
|
5548
|
+
}
|
|
5549
|
+
}
|
|
5550
|
+
return results;
|
|
5551
|
+
}
|
|
5401
5552
|
};
|
|
5402
|
-
WINDSURF_CATEGORY_MAP = CLAUDE_CATEGORY_MAP;
|
|
5403
5553
|
}
|
|
5404
5554
|
});
|
|
5405
5555
|
|
|
@@ -6120,7 +6270,7 @@ var init_packageManager = __esm({
|
|
|
6120
6270
|
});
|
|
6121
6271
|
|
|
6122
6272
|
// src/adapters/copilot.ts
|
|
6123
|
-
var CopilotAdapter;
|
|
6273
|
+
var COPILOT_ENFORCEMENT_ADDENDUM, CopilotAdapter;
|
|
6124
6274
|
var init_copilot = __esm({
|
|
6125
6275
|
"src/adapters/copilot.ts"() {
|
|
6126
6276
|
"use strict";
|
|
@@ -6132,6 +6282,30 @@ var init_copilot = __esm({
|
|
|
6132
6282
|
init_customization();
|
|
6133
6283
|
init_packageManager();
|
|
6134
6284
|
init_adapterToolTranslator();
|
|
6285
|
+
COPILOT_ENFORCEMENT_ADDENDUM = `## Copilot Enforcement Model (no hook surface)
|
|
6286
|
+
|
|
6287
|
+
GitHub Copilot Chat does not expose a PreToolUse or pre-edit hook
|
|
6288
|
+
(see \`src/adapters/index.ts\` \u2014 \`copilot\` is the only adapter with
|
|
6289
|
+
\`hooks: false\` in \`ADAPTER_CAPABILITIES\`). Hatch3r cannot block
|
|
6290
|
+
code-writing tool calls server-side for Copilot. Enforcement is
|
|
6291
|
+
therefore trust-based \u2014 the directives in this file and in
|
|
6292
|
+
\`.github/instructions/\` are normative, not advisory.
|
|
6293
|
+
|
|
6294
|
+
Self-detectable drift indicators (halt the current turn if any appear):
|
|
6295
|
+
|
|
6296
|
+
- Missing pipeline-state header on a tracked Tier 2+ task (see
|
|
6297
|
+
\`hatch3r-agent-orchestration\` \u2192 Per-Turn Pipeline-State Header).
|
|
6298
|
+
- A call to \`replace_string_in_file\`, \`multi_replace_string_in_file\`,
|
|
6299
|
+
\`create_file\`, or any code-writing tool before the user has
|
|
6300
|
+
confirmed the Pre-Implementation Summary on a Tier 3 task (see
|
|
6301
|
+
\`hatch3r-deep-context\` \u2192 Tier 3 \u2014 Deep).
|
|
6302
|
+
- An \`Edit\` / \`Write\` invocation from the orchestrator turn that
|
|
6303
|
+
did not immediately follow a SUCCESS report from \`hatch3r-implementer\`
|
|
6304
|
+
via the \`Task\` tool.
|
|
6305
|
+
|
|
6306
|
+
On any drift, halt and re-delegate via \`hatch3r-implementer\` (Phase 2)
|
|
6307
|
+
or \`hatch3r-fixer\` (Phase 3). The only carve-out is \`hatch3r-quick-change\`
|
|
6308
|
+
Tier 1 trivial single-line edits per its declared scope.`;
|
|
6135
6309
|
CopilotAdapter = class extends BaseAdapter {
|
|
6136
6310
|
name = "copilot";
|
|
6137
6311
|
async doGenerate(ctx) {
|
|
@@ -6142,9 +6316,10 @@ var init_copilot = __esm({
|
|
|
6142
6316
|
const rules = await readCanonicalFiles(ctx.agentsDir, "rules", this.warnings);
|
|
6143
6317
|
const sortedRules = sortByPrecedence(rules);
|
|
6144
6318
|
for (const rule of sortedRules) {
|
|
6145
|
-
const { content, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, rule);
|
|
6319
|
+
const { content: rawContent, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, rule);
|
|
6146
6320
|
this.warnings.push(...warnings);
|
|
6147
6321
|
if (skip) continue;
|
|
6322
|
+
const content = this.substituteAskUserMarker(rawContent);
|
|
6148
6323
|
const scope = overrides.scope ?? rule.scope;
|
|
6149
6324
|
if (scope && scope !== "always") {
|
|
6150
6325
|
scopedRules.push({ rule: { ...rule, description: overrides.description ?? rule.description }, content, scope });
|
|
@@ -6162,6 +6337,8 @@ var init_copilot = __esm({
|
|
|
6162
6337
|
"",
|
|
6163
6338
|
bridgeOrchestration,
|
|
6164
6339
|
"",
|
|
6340
|
+
COPILOT_ENFORCEMENT_ADDENDUM,
|
|
6341
|
+
"",
|
|
6165
6342
|
"## Hatch3r Rules",
|
|
6166
6343
|
"",
|
|
6167
6344
|
...alwaysRules.map(
|
|
@@ -6199,7 +6376,7 @@ jobs:
|
|
|
6199
6376
|
run: ${build}`;
|
|
6200
6377
|
results.push(output(
|
|
6201
6378
|
".github/workflows/copilot-setup-steps.yml",
|
|
6202
|
-
wrapInManagedBlock(copilotSetupStepsInner)
|
|
6379
|
+
wrapInManagedBlock(copilotSetupStepsInner),
|
|
6203
6380
|
copilotSetupStepsInner
|
|
6204
6381
|
));
|
|
6205
6382
|
for (const { rule, content, scope } of scopedRules) {
|
|
@@ -6227,9 +6404,10 @@ ${wrapInManagedBlock(body)}`,
|
|
|
6227
6404
|
if (ctx.features.agents) {
|
|
6228
6405
|
const agents = await this.readUserFacingCanonicalFiles(ctx.agentsDir, "agents");
|
|
6229
6406
|
for (const agent of agents) {
|
|
6230
|
-
const { content, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, agent);
|
|
6407
|
+
const { content: rawContent, skip, overrides, warnings } = await applyCustomization(ctx.projectRoot, agent);
|
|
6231
6408
|
this.warnings.push(...warnings);
|
|
6232
6409
|
if (skip) continue;
|
|
6410
|
+
const content = this.substituteAskUserMarker(rawContent);
|
|
6233
6411
|
const model = resolveAgentModel(agent.id, agent, ctx.manifest, overrides);
|
|
6234
6412
|
const desc = overrides.description ?? agent.description;
|
|
6235
6413
|
const prefixedId = toPrefixedId(agent.id);
|
|
@@ -7166,28 +7344,28 @@ var init_adapters = __esm({
|
|
|
7166
7344
|
};
|
|
7167
7345
|
adapterCache = /* @__PURE__ */ new Map();
|
|
7168
7346
|
ADAPTER_CAPABILITIES = {
|
|
7169
|
-
cursor: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: true, prompts: false, githubAgents: false, worktree: true, customization: true, modelOverride: true },
|
|
7170
|
-
claude: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: true, prompts: false, githubAgents: false, worktree: true, customization: true, modelOverride: true },
|
|
7171
|
-
gemini: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: true, prompts: false, githubAgents: false, worktree: true, customization: true, modelOverride: true },
|
|
7172
|
-
cline: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: true, prompts: false, githubAgents: false, worktree: true, customization: true, modelOverride: true },
|
|
7173
|
-
codex: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: false, prompts: false, githubAgents: false, worktree: false, customization: true, modelOverride: true },
|
|
7174
|
-
"amazon-q": { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: false, prompts: false, githubAgents: false, worktree: false, customization: true, modelOverride: true },
|
|
7175
|
-
copilot: { agents: true, skills: true, rules: true, hooks: false, mcp: true, commands: true, prompts: true, githubAgents: true, worktree: true, customization: true, modelOverride: true },
|
|
7176
|
-
opencode: { agents: true, skills: true, rules: true, hooks: false, mcp: true, commands: true, prompts: false, githubAgents: false, worktree: false, customization: true, modelOverride: true },
|
|
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 },
|
|
7177
7355
|
// C7.5-W2B2-H31 (D9-SA9.7.1): Windsurf shipped Cascade Hooks in v1.13.12 (2026-01-25).
|
|
7178
7356
|
// Hatch3r emits `.windsurf/hooks.json` per docs.windsurf.com/windsurf/cascade/hooks.md.
|
|
7179
|
-
windsurf: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: true, prompts: false, githubAgents: false, worktree: true, customization: true, modelOverride: true },
|
|
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 },
|
|
7180
7358
|
// Amp reads AGENTS.md natively; the root file is written by generateRootAgentsMd()
|
|
7181
7359
|
// in init/update, not by this adapter. Amp also reads skills natively from
|
|
7182
7360
|
// `.agents/skills/` — populated by copyHatch3rFiles, not re-emitted by this
|
|
7183
7361
|
// adapter (re-emission corrupts SKILL.md frontmatter via managed-block wrap).
|
|
7184
7362
|
// doGenerate() emits MCP settings only.
|
|
7185
|
-
amp: { agents: false, skills: false, rules: false, hooks: false, mcp: true, commands: false, prompts: false, githubAgents: false, worktree: false, customization: true, modelOverride: true },
|
|
7186
|
-
kiro: { agents: true, skills: true, rules: true, hooks: true, mcp: true, commands: false, prompts: false, githubAgents: false, worktree: false, customization: true, modelOverride: true },
|
|
7187
|
-
aider: { agents: true, skills: true, rules: true, hooks: false, mcp: false, commands: false, prompts: false, githubAgents: false, worktree: false, customization: true, modelOverride: true },
|
|
7188
|
-
goose: { agents: true, skills: true, rules: true, hooks: false, mcp: true, commands: false, prompts: false, githubAgents: false, worktree: false, customization: true, modelOverride: true },
|
|
7189
|
-
zed: { agents: true, skills: false, rules: true, hooks: false, mcp: true, commands: false, prompts: false, githubAgents: false, worktree: false, customization: false, modelOverride: false },
|
|
7190
|
-
antigravity: { agents: true, skills: true, rules: true, hooks: false, mcp: true, commands: false, prompts: false, githubAgents: false, worktree: false, customization: true, modelOverride: true }
|
|
7363
|
+
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 },
|
|
7364
|
+
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 },
|
|
7365
|
+
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 },
|
|
7366
|
+
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 },
|
|
7367
|
+
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 },
|
|
7368
|
+
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 }
|
|
7191
7369
|
};
|
|
7192
7370
|
}
|
|
7193
7371
|
});
|
|
@@ -11115,6 +11293,62 @@ tags: [<tag>, ...]
|
|
|
11115
11293
|
The loader agent applies content-security and integrity checks to every
|
|
11116
11294
|
entry; see \`hatch3r-learnings-loader\` for the full protocol.
|
|
11117
11295
|
|
|
11296
|
+
## Recommended First Learning \u2014 Pipeline Drift
|
|
11297
|
+
|
|
11298
|
+
Copy the markdown block below into \`.agents/learnings/pipeline-drift-rule-73.md\`
|
|
11299
|
+
to prime your AI tool against the bypass pattern reported in hatch3r
|
|
11300
|
+
issue #73 (GitHub Copilot Chat skipping the four-phase sub-agent
|
|
11301
|
+
pipeline on Tier-3 epics). The \`hatch3r-learnings-loader\` agent will
|
|
11302
|
+
surface it on session start.
|
|
11303
|
+
|
|
11304
|
+
\`\`\`markdown
|
|
11305
|
+
---
|
|
11306
|
+
id: pipeline-drift-rule-73
|
|
11307
|
+
category: pitfall
|
|
11308
|
+
area: orchestration
|
|
11309
|
+
recorded: 2026-05-12
|
|
11310
|
+
source: manual
|
|
11311
|
+
confidence: high
|
|
11312
|
+
author: human
|
|
11313
|
+
tags: [orchestration, copilot, drift]
|
|
11314
|
+
---
|
|
11315
|
+
|
|
11316
|
+
## Learning
|
|
11317
|
+
|
|
11318
|
+
The hatch3r four-phase sub-agent pipeline (Research -> Implement ->
|
|
11319
|
+
Review -> Quality) is trust-based on Copilot Chat \u2014 Copilot has
|
|
11320
|
+
\`hooks: false\` in \`src/adapters/index.ts\`, exposes no PreToolUse /
|
|
11321
|
+
pre-edit hook, and does not surface its chat transcript to external
|
|
11322
|
+
processes. Drift is invisible by default: Copilot can call
|
|
11323
|
+
\`multi_replace_string_in_file\` / \`create_file\` inline on a Tier-3
|
|
11324
|
+
task and the build can still pass.
|
|
11325
|
+
|
|
11326
|
+
Self-detectable signals:
|
|
11327
|
+
|
|
11328
|
+
- The orchestrator's reply does NOT start with the
|
|
11329
|
+
\`[hatch3r-pipeline: phase N | last: ... | next: ...]\` header on
|
|
11330
|
+
a tracked Tier 2+ task -> halt and re-ground.
|
|
11331
|
+
- A code-writing tool was called before the user confirmed the
|
|
11332
|
+
Pre-Implementation Summary on a Tier 3 task -> bypass mode.
|
|
11333
|
+
- An \`Edit\` / \`Write\` / equivalent fired from the orchestrator
|
|
11334
|
+
turn rather than from inside a \`hatch3r-implementer\` Task
|
|
11335
|
+
sub-agent -> bypass mode.
|
|
11336
|
+
|
|
11337
|
+
## Evidence
|
|
11338
|
+
|
|
11339
|
+
- Issue: https://github.com/hatch3r-dev/hatch3r/issues/73
|
|
11340
|
+
- Rules: \`rules/hatch3r-agent-orchestration.md\` (Per-Turn
|
|
11341
|
+
Pipeline-State Header, Mandatory Delegation Directive);
|
|
11342
|
+
\`rules/hatch3r-deep-context.md\` (Tier 3 \u2014 Deep hard gate).
|
|
11343
|
+
- Adapter capability: \`src/adapters/index.ts\` \u2014 \`copilot\` is the
|
|
11344
|
+
only adapter with \`hooks: false\`.
|
|
11345
|
+
\`\`\`
|
|
11346
|
+
|
|
11347
|
+
Customize the \`recorded\` date and \`tags\` to match your setup.
|
|
11348
|
+
Adapters other than Copilot also benefit from this learning when
|
|
11349
|
+
the bypass pattern is plausible on their host (e.g., long-context
|
|
11350
|
+
sessions on any adapter).
|
|
11351
|
+
|
|
11118
11352
|
Delete this README once you have authored real learnings.
|
|
11119
11353
|
`;
|
|
11120
11354
|
function selectionHasBoardContent(selection) {
|
|
@@ -11200,7 +11434,12 @@ async function runInitInner(options) {
|
|
|
11200
11434
|
s1.succeed(step(1, totalSteps, `Canonical files created (${countSelectionItems(contentSelection)} items)`));
|
|
11201
11435
|
const s2 = createSpinner(step(2, totalSteps, "Preparing manifest..."));
|
|
11202
11436
|
s2.start();
|
|
11203
|
-
const
|
|
11437
|
+
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 });
|
|
11439
|
+
const preservedFields = options.preservedManifestFields ?? (existingManifest ? extractPreservedManifestFields(existingManifest) : void 0);
|
|
11440
|
+
if (preservedFields) {
|
|
11441
|
+
applyPreservedManifestFields(manifest, preservedFields);
|
|
11442
|
+
}
|
|
11204
11443
|
s2.succeed(step(2, totalSteps, "Manifest prepared"));
|
|
11205
11444
|
const s3 = createSpinner(
|
|
11206
11445
|
step(3, totalSteps, `Generating ${tools.map((t) => TOOL_DISPLAY_NAMES[t] ?? t).join(", ")} output...`)
|
|
@@ -12069,6 +12308,7 @@ function resolveToolsFromOpts(toolsFlag, repoInfo) {
|
|
|
12069
12308
|
}
|
|
12070
12309
|
|
|
12071
12310
|
// src/cli/commands/clean.ts
|
|
12311
|
+
init_hatchJson();
|
|
12072
12312
|
function captureConfig(manifest) {
|
|
12073
12313
|
return {
|
|
12074
12314
|
platform: manifest.platform ?? "github",
|
|
@@ -12087,7 +12327,8 @@ function captureConfig(manifest) {
|
|
|
12087
12327
|
items: { agents: [], skills: [], rules: [], commands: [], prompts: [], hooks: [], githubAgents: [] }
|
|
12088
12328
|
},
|
|
12089
12329
|
worktreeEnabled: manifest.worktree?.enabled ?? false,
|
|
12090
|
-
customization: manifest.customization
|
|
12330
|
+
customization: manifest.customization,
|
|
12331
|
+
preservedFields: extractPreservedManifestFields(manifest)
|
|
12091
12332
|
};
|
|
12092
12333
|
}
|
|
12093
12334
|
function printInventory(inventory) {
|
|
@@ -12237,6 +12478,10 @@ async function cleanCommand(opts = {}) {
|
|
|
12237
12478
|
// manifest preserves integration config and per-artifact overrides
|
|
12238
12479
|
// across a clean -> reinit cycle.
|
|
12239
12480
|
customization: config.customization,
|
|
12481
|
+
// 1.7.1: carry full platform/user manifest state (board IDs,
|
|
12482
|
+
// costTracking, specs, extension config, worktree extras) forward
|
|
12483
|
+
// so a clean -> reinit cycle no longer wipes them.
|
|
12484
|
+
preservedManifestFields: config.preservedFields,
|
|
12240
12485
|
// Reinit-after-clean already prompted the user; suppress runInit's
|
|
12241
12486
|
// own post-init create-prompt so we do not stack two confirmations.
|
|
12242
12487
|
yes: true
|
|
@@ -13039,7 +13284,7 @@ function normalizeToRepoPath(absPath, rootDir) {
|
|
|
13039
13284
|
const rel = relative6(rootDir, absPath);
|
|
13040
13285
|
return sep5 === "/" ? rel : rel.split(sep5).join(posix3.sep);
|
|
13041
13286
|
}
|
|
13042
|
-
function buildProvenanceManifest(hatchVersion, rootDir, perAdapterOutputs) {
|
|
13287
|
+
function buildProvenanceManifest(hatchVersion, rootDir, perAdapterOutputs, previousManifest) {
|
|
13043
13288
|
const entries = [];
|
|
13044
13289
|
for (const { adapter, outputs } of perAdapterOutputs) {
|
|
13045
13290
|
for (const out of outputs) {
|
|
@@ -13056,6 +13301,9 @@ function buildProvenanceManifest(hatchVersion, rootDir, perAdapterOutputs) {
|
|
|
13056
13301
|
if (byAdapter !== 0) return byAdapter;
|
|
13057
13302
|
return a.path.localeCompare(b.path);
|
|
13058
13303
|
});
|
|
13304
|
+
if (previousManifest && previousManifest.hatchVersion === hatchVersion && provenanceEntriesEqual(previousManifest.entries, entries)) {
|
|
13305
|
+
return previousManifest;
|
|
13306
|
+
}
|
|
13059
13307
|
return {
|
|
13060
13308
|
version: 1,
|
|
13061
13309
|
generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -13063,10 +13311,62 @@ function buildProvenanceManifest(hatchVersion, rootDir, perAdapterOutputs) {
|
|
|
13063
13311
|
entries
|
|
13064
13312
|
};
|
|
13065
13313
|
}
|
|
13314
|
+
function provenanceEntriesEqual(a, b) {
|
|
13315
|
+
if (a.length !== b.length) return false;
|
|
13316
|
+
for (let i = 0; i < a.length; i++) {
|
|
13317
|
+
const ea = a[i];
|
|
13318
|
+
const eb = b[i];
|
|
13319
|
+
if (ea.adapter !== eb.adapter) return false;
|
|
13320
|
+
if (ea.path !== eb.path) return false;
|
|
13321
|
+
if (ea.sourceFiles.length !== eb.sourceFiles.length) return false;
|
|
13322
|
+
for (let j = 0; j < ea.sourceFiles.length; j++) {
|
|
13323
|
+
if (ea.sourceFiles[j] !== eb.sourceFiles[j]) return false;
|
|
13324
|
+
}
|
|
13325
|
+
}
|
|
13326
|
+
return true;
|
|
13327
|
+
}
|
|
13066
13328
|
async function writeProvenanceManifest(agentsDir, manifest) {
|
|
13067
13329
|
const filePath = join31(agentsDir, PROVENANCE_FILE);
|
|
13068
13330
|
await atomicWriteFile(filePath, JSON.stringify(manifest, null, 2) + "\n");
|
|
13069
13331
|
}
|
|
13332
|
+
async function readProvenanceManifest(agentsDir) {
|
|
13333
|
+
const filePath = join31(agentsDir, PROVENANCE_FILE);
|
|
13334
|
+
let raw;
|
|
13335
|
+
try {
|
|
13336
|
+
raw = await readFile22(filePath, "utf-8");
|
|
13337
|
+
} catch (err) {
|
|
13338
|
+
if (err.code === "ENOENT") return null;
|
|
13339
|
+
throw err;
|
|
13340
|
+
}
|
|
13341
|
+
let parsed;
|
|
13342
|
+
try {
|
|
13343
|
+
parsed = JSON.parse(raw);
|
|
13344
|
+
} catch (err) {
|
|
13345
|
+
if (err instanceof SyntaxError) return null;
|
|
13346
|
+
throw err;
|
|
13347
|
+
}
|
|
13348
|
+
if (!isProvenanceManifest(parsed)) return null;
|
|
13349
|
+
return parsed;
|
|
13350
|
+
}
|
|
13351
|
+
function isProvenanceManifest(data) {
|
|
13352
|
+
if (typeof data !== "object" || data === null) return false;
|
|
13353
|
+
const obj = data;
|
|
13354
|
+
if (typeof obj.version !== "number") return false;
|
|
13355
|
+
if (typeof obj.generated !== "string") return false;
|
|
13356
|
+
if (typeof obj.hatchVersion !== "string") return false;
|
|
13357
|
+
if (!Array.isArray(obj.entries)) return false;
|
|
13358
|
+
for (const e of obj.entries) {
|
|
13359
|
+
if (typeof e !== "object" || e === null) return false;
|
|
13360
|
+
const entry = e;
|
|
13361
|
+
if (typeof entry.adapter !== "string") return false;
|
|
13362
|
+
if (typeof entry.path !== "string") return false;
|
|
13363
|
+
if (!Array.isArray(entry.sourceFiles)) return false;
|
|
13364
|
+
for (const s of entry.sourceFiles) {
|
|
13365
|
+
if (typeof s !== "string") return false;
|
|
13366
|
+
}
|
|
13367
|
+
}
|
|
13368
|
+
return true;
|
|
13369
|
+
}
|
|
13070
13370
|
|
|
13071
13371
|
// src/cli/commands/sync.ts
|
|
13072
13372
|
init_archive();
|
|
@@ -13442,12 +13742,16 @@ async function syncCommand(opts = {}) {
|
|
|
13442
13742
|
`Integrity manifest regenerated with ${successfulAdapters.length}/${m.tools.length} adapters successful. Re-run sync after resolving errors to produce a complete manifest.`
|
|
13443
13743
|
);
|
|
13444
13744
|
}
|
|
13745
|
+
const previousProvenanceManifest = await readProvenanceManifest(agentsDir);
|
|
13445
13746
|
const provenanceManifest = buildProvenanceManifest(
|
|
13446
13747
|
HATCH3R_VERSION,
|
|
13447
13748
|
rootDir,
|
|
13448
|
-
perAdapterOutputs
|
|
13749
|
+
perAdapterOutputs,
|
|
13750
|
+
previousProvenanceManifest
|
|
13449
13751
|
);
|
|
13450
|
-
|
|
13752
|
+
if (provenanceManifest !== previousProvenanceManifest) {
|
|
13753
|
+
await writeProvenanceManifest(agentsDir, provenanceManifest);
|
|
13754
|
+
}
|
|
13451
13755
|
const orphanDiag = formatOrphanCleanupDiagnostic(orphanEntries);
|
|
13452
13756
|
if (orphanDiag) warn(orphanDiag);
|
|
13453
13757
|
const mergedByAdapter = { ...previousManagedByAdapter };
|