@habitat-ai/cli 0.3.1 → 0.4.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/dist/application.d.ts +1 -1
- package/dist/application.js +3 -3
- package/dist/commands/check.d.ts +15 -0
- package/dist/commands/check.js +32 -0
- package/dist/commands/hook.d.ts +12 -0
- package/dist/commands/hook.js +25 -0
- package/dist/commands/resolve.d.ts +9 -0
- package/dist/commands/resolve.js +15 -0
- package/dist/generators/init.d.ts +3 -0
- package/dist/generators/init.js +10 -0
- package/dist/generators/remove-hook.d.ts +3 -0
- package/dist/generators/remove-hook.js +6 -0
- package/dist/lib/binding.d.ts +10 -0
- package/dist/lib/binding.js +16 -0
- package/dist/lib/output.d.ts +9 -0
- package/dist/lib/output.js +22 -0
- package/dist/nx/initialization.d.ts +45 -0
- package/dist/nx/initialization.js +201 -0
- package/dist/nx/projection.d.ts +29 -0
- package/dist/nx/projection.js +293 -0
- package/dist/nx-generators.d.ts +31 -0
- package/dist/nx-generators.js +34 -0
- package/dist/nx-plugin.d.ts +2 -0
- package/dist/nx-plugin.js +11 -52397
- package/generators.json +2 -2
- package/oclif.manifest.json +101 -2
- package/package.json +16 -28
- package/dist/composition.d.ts +0 -8
- package/dist/composition.js +0 -59
- package/dist/generators/init.cjs +0 -252
- package/dist/generators/remove-hook.cjs +0 -191
- package/src/nx-plugin.d.ts +0 -4
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { posix } from "node:path";
|
|
2
|
+
const HABITAT_CATALOG_PATHS = [
|
|
3
|
+
".habitat/blueprints/*/blueprint.toml",
|
|
4
|
+
".habitat/index.json",
|
|
5
|
+
".habitat/**/rule.json",
|
|
6
|
+
"**/habitat.toml",
|
|
7
|
+
];
|
|
8
|
+
const HABITAT_AUTHORITY_GLOB = `{${HABITAT_CATALOG_PATHS.join(",")}}`;
|
|
9
|
+
const DEFAULT_CHECK_TARGET = "check:policy";
|
|
10
|
+
const HABITAT_EXECUTABLE = "habitat";
|
|
11
|
+
const PORTABLE_OWNER_PROJECT = /^[A-Za-z0-9@][A-Za-z0-9@._/+:-]*$/u;
|
|
12
|
+
/**
|
|
13
|
+
* Projects resolved Habitat applications and compatibility rules into native Nx targets.
|
|
14
|
+
*
|
|
15
|
+
* The factory receives the app-owned workspace client and runtime cache facts.
|
|
16
|
+
* It does not select providers, discover authority, execute checks, or name
|
|
17
|
+
* projects.
|
|
18
|
+
*/
|
|
19
|
+
export function createHabitatNxPlugin(binding) {
|
|
20
|
+
const createNodes = [
|
|
21
|
+
HABITAT_AUTHORITY_GLOB,
|
|
22
|
+
async (configFiles, _options, context) => {
|
|
23
|
+
const matchedFiles = [...new Set(configFiles.map(normalizeWorkspacePath))].sort();
|
|
24
|
+
const client = await binding.clientForWorkspace(context.workspaceRoot);
|
|
25
|
+
const result = await client.catalog.resolve({});
|
|
26
|
+
if (result._tag === "Rejected")
|
|
27
|
+
throw rejectedCatalogError(result);
|
|
28
|
+
return projectCatalogTargets(matchedFiles, result.catalog, binding.runtimeInputs);
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
return Object.freeze({ createNodes });
|
|
32
|
+
}
|
|
33
|
+
function projectCatalogTargets(matchedFiles, catalog, runtimeInputs) {
|
|
34
|
+
if (catalog.applications.length === 0 && catalog.compatibility.rules.length === 0)
|
|
35
|
+
return [];
|
|
36
|
+
const authorityFiles = new Set(matchedFiles);
|
|
37
|
+
const instances = indexInstances(catalog.instances);
|
|
38
|
+
const rootsByOwner = new Map();
|
|
39
|
+
const ownersByRoot = new Map();
|
|
40
|
+
const projects = new Map();
|
|
41
|
+
for (const [ownerProject, ownerRoot] of Object.entries(catalog.compatibility.ownerRoots).sort(([left], [right]) => compareText(left, right))) {
|
|
42
|
+
recordOwnerRoot(ownerProject, ownerRoot, rootsByOwner, ownersByRoot);
|
|
43
|
+
}
|
|
44
|
+
for (const application of catalog.applications) {
|
|
45
|
+
const instance = requireApplicationInstance(application, instances);
|
|
46
|
+
const manifestPath = requireMatchedManifest(application.manifestPath, authorityFiles, `instance '${instance.id}'`);
|
|
47
|
+
const root = recordOwnerRoot(application.ownerProject, projectRootFor(manifestPath), rootsByOwner, ownersByRoot);
|
|
48
|
+
const project = projectFor(projects, root, application.ownerProject);
|
|
49
|
+
project.applicationManifests.add(manifestPath);
|
|
50
|
+
const targetName = applicationTargetName(application);
|
|
51
|
+
addLeafTarget(project, targetName, applicationTarget(application, runtimeInputs));
|
|
52
|
+
}
|
|
53
|
+
for (const rule of catalog.compatibility.rules) {
|
|
54
|
+
const root = rootsByOwner.get(rule.ownerProject);
|
|
55
|
+
if (root === undefined) {
|
|
56
|
+
throw new Error(`Habitat Nx projection rejected compatibility rule '${rule.ruleId}': owner '${rule.ownerProject}' has no root.`);
|
|
57
|
+
}
|
|
58
|
+
const manifestPath = requireMatchedManifest(rule.manifestPath, authorityFiles, `compatibility rule '${rule.ruleId}'`);
|
|
59
|
+
const project = projectFor(projects, root, rule.ownerProject);
|
|
60
|
+
project.compatibilityManifests.add(manifestPath);
|
|
61
|
+
addLeafTarget(project, compatibilityTargetName(rule), compatibilityTarget(rule, runtimeInputs));
|
|
62
|
+
}
|
|
63
|
+
return [...projects.entries()]
|
|
64
|
+
.sort(([left], [right]) => compareText(left, right))
|
|
65
|
+
.map(([root, project]) => {
|
|
66
|
+
project.targets[DEFAULT_CHECK_TARGET] = ownerTarget(project.ownerProject, ownerInputs(project.targets, runtimeInputs), project.compatibilityManifests.size > 0);
|
|
67
|
+
return [
|
|
68
|
+
projectionSource(project),
|
|
69
|
+
{
|
|
70
|
+
projects: {
|
|
71
|
+
[root]: {
|
|
72
|
+
targets: Object.fromEntries(Object.entries(project.targets).sort(([left], [right]) => compareText(left, right))),
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function recordOwnerRoot(ownerProject, ownerRoot, rootsByOwner, ownersByRoot) {
|
|
80
|
+
const root = normalizeWorkspacePath(ownerRoot);
|
|
81
|
+
const priorRoot = rootsByOwner.get(ownerProject);
|
|
82
|
+
if (priorRoot !== undefined && priorRoot !== root) {
|
|
83
|
+
throw new Error(`Habitat Nx projection rejected owner '${ownerProject}': roots '${priorRoot}' and '${root}' collide.`);
|
|
84
|
+
}
|
|
85
|
+
const priorOwner = ownersByRoot.get(root);
|
|
86
|
+
if (priorOwner !== undefined && priorOwner !== ownerProject) {
|
|
87
|
+
throw new Error(`Habitat Nx projection rejected root '${root}': owners '${priorOwner}' and '${ownerProject}' collide.`);
|
|
88
|
+
}
|
|
89
|
+
rootsByOwner.set(ownerProject, root);
|
|
90
|
+
ownersByRoot.set(root, ownerProject);
|
|
91
|
+
return root;
|
|
92
|
+
}
|
|
93
|
+
function projectFor(projects, root, ownerProject) {
|
|
94
|
+
const project = projects.get(root);
|
|
95
|
+
if (project !== undefined) {
|
|
96
|
+
if (project.ownerProject !== ownerProject) {
|
|
97
|
+
throw new Error(`Habitat Nx projection rejected root '${root}': owners '${project.ownerProject}' and '${ownerProject}' collide.`);
|
|
98
|
+
}
|
|
99
|
+
return project;
|
|
100
|
+
}
|
|
101
|
+
const created = {
|
|
102
|
+
ownerProject,
|
|
103
|
+
applicationManifests: new Set(),
|
|
104
|
+
compatibilityManifests: new Set(),
|
|
105
|
+
targets: {},
|
|
106
|
+
};
|
|
107
|
+
projects.set(root, created);
|
|
108
|
+
return created;
|
|
109
|
+
}
|
|
110
|
+
function requireMatchedManifest(path, authorityFiles, subject) {
|
|
111
|
+
const manifestPath = normalizeWorkspacePath(path);
|
|
112
|
+
if (!authorityFiles.has(manifestPath)) {
|
|
113
|
+
throw new Error(`Habitat Nx projection rejected ${subject}: manifest '${manifestPath}' is outside the matched authority files.`);
|
|
114
|
+
}
|
|
115
|
+
return manifestPath;
|
|
116
|
+
}
|
|
117
|
+
function addLeafTarget(project, targetName, target) {
|
|
118
|
+
if (Object.hasOwn(project.targets, targetName)) {
|
|
119
|
+
throw new Error(`Habitat Nx projection rejected duplicate target '${project.ownerProject}:${targetName}'.`);
|
|
120
|
+
}
|
|
121
|
+
project.targets[targetName] = target;
|
|
122
|
+
}
|
|
123
|
+
function projectionSource(project) {
|
|
124
|
+
const applicationManifest = [...project.applicationManifests].sort(compareText)[0];
|
|
125
|
+
if (applicationManifest !== undefined)
|
|
126
|
+
return applicationManifest;
|
|
127
|
+
const compatibilityManifest = [...project.compatibilityManifests].sort(compareText)[0];
|
|
128
|
+
if (compatibilityManifest === undefined) {
|
|
129
|
+
throw new Error(`Habitat Nx projection rejected owner '${project.ownerProject}': projected targets have no manifest.`);
|
|
130
|
+
}
|
|
131
|
+
return compatibilityManifest;
|
|
132
|
+
}
|
|
133
|
+
function requireApplicationInstance(application, instances) {
|
|
134
|
+
const instance = instances.get(application.instanceId);
|
|
135
|
+
if (!instance) {
|
|
136
|
+
throw new Error(`Habitat Nx projection rejected application '${application.ruleId}': instance '${application.instanceId}' is absent.`);
|
|
137
|
+
}
|
|
138
|
+
const mismatches = [
|
|
139
|
+
instance.ownerProject === application.ownerProject
|
|
140
|
+
? undefined
|
|
141
|
+
: `owner '${application.ownerProject}' does not match '${instance.ownerProject}'`,
|
|
142
|
+
instance.blueprint === application.blueprint
|
|
143
|
+
? undefined
|
|
144
|
+
: `blueprint '${application.blueprint}' does not match '${instance.blueprint}'`,
|
|
145
|
+
instance.blueprintVersion === application.blueprintVersion
|
|
146
|
+
? undefined
|
|
147
|
+
: `blueprint version '${application.blueprintVersion}' does not match '${instance.blueprintVersion}'`,
|
|
148
|
+
normalizeWorkspacePath(instance.manifestPath) ===
|
|
149
|
+
normalizeWorkspacePath(application.manifestPath)
|
|
150
|
+
? undefined
|
|
151
|
+
: `manifest '${application.manifestPath}' does not match '${instance.manifestPath}'`,
|
|
152
|
+
].filter((mismatch) => mismatch !== undefined);
|
|
153
|
+
if (mismatches.length > 0) {
|
|
154
|
+
throw new Error(`Habitat Nx projection rejected instance '${instance.id}': ${mismatches.join("; ")}.`);
|
|
155
|
+
}
|
|
156
|
+
return instance;
|
|
157
|
+
}
|
|
158
|
+
function indexInstances(instances) {
|
|
159
|
+
const indexed = new Map();
|
|
160
|
+
for (const instance of instances) {
|
|
161
|
+
if (indexed.has(instance.id)) {
|
|
162
|
+
throw new Error(`Habitat Nx projection rejected duplicate instance '${instance.id}'.`);
|
|
163
|
+
}
|
|
164
|
+
indexed.set(instance.id, instance);
|
|
165
|
+
}
|
|
166
|
+
return indexed;
|
|
167
|
+
}
|
|
168
|
+
function applicationTargetName(application) {
|
|
169
|
+
return `habitat:application:${application.instanceId}:${application.ruleId}`;
|
|
170
|
+
}
|
|
171
|
+
function compatibilityTargetName(rule) {
|
|
172
|
+
return `habitat:rule:${rule.ruleId}`;
|
|
173
|
+
}
|
|
174
|
+
function applicationTarget(application, runtimeInputs) {
|
|
175
|
+
return {
|
|
176
|
+
command: `${HABITAT_EXECUTABLE} check --instance ${application.instanceId} --rule ${application.ruleId}`,
|
|
177
|
+
cache: true,
|
|
178
|
+
inputs: applicationInputs(application, runtimeInputs),
|
|
179
|
+
outputs: [],
|
|
180
|
+
options: { cwd: "{workspaceRoot}" },
|
|
181
|
+
metadata: {
|
|
182
|
+
description: `Check Habitat application ${application.instanceId}/${application.ruleId}`,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function compatibilityTarget(rule, runtimeInputs) {
|
|
187
|
+
return {
|
|
188
|
+
command: `${HABITAT_EXECUTABLE} check --rule ${rule.ruleId}`,
|
|
189
|
+
cache: true,
|
|
190
|
+
inputs: compatibilityInputs(rule, runtimeInputs),
|
|
191
|
+
outputs: [],
|
|
192
|
+
options: { cwd: "{workspaceRoot}" },
|
|
193
|
+
metadata: { description: `Check Habitat compatibility rule ${rule.ruleId}` },
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function ownerTarget(ownerProject, inputs, hasCompatibility) {
|
|
197
|
+
if (!PORTABLE_OWNER_PROJECT.test(ownerProject)) {
|
|
198
|
+
throw new Error(`Habitat Nx projection rejected owner '${ownerProject}': identity is not portable as an Nx command argument.`);
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
command: `${HABITAT_EXECUTABLE} check --owner ${ownerProject}`,
|
|
202
|
+
cache: true,
|
|
203
|
+
inputs,
|
|
204
|
+
outputs: [],
|
|
205
|
+
options: { cwd: "{workspaceRoot}" },
|
|
206
|
+
metadata: {
|
|
207
|
+
description: hasCompatibility
|
|
208
|
+
? `Check resolved Habitat policy owned by ${ownerProject}`
|
|
209
|
+
: `Check resolved Habitat applications owned by ${ownerProject}`,
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function ownerInputs(targets, runtimeInputs) {
|
|
214
|
+
const runtimeStrings = new Set(runtimeInputs.filter((input) => typeof input === "string"));
|
|
215
|
+
const corpusInputs = new Set();
|
|
216
|
+
for (const target of Object.values(targets)) {
|
|
217
|
+
for (const input of target.inputs ?? []) {
|
|
218
|
+
if (typeof input === "string" && !runtimeStrings.has(input))
|
|
219
|
+
corpusInputs.add(input);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return [...runtimeInputs, ...[...corpusInputs].sort(compareText)];
|
|
223
|
+
}
|
|
224
|
+
function applicationInputs(application, runtimeInputs) {
|
|
225
|
+
const files = new Set(HABITAT_CATALOG_PATHS.map(workspaceInput));
|
|
226
|
+
if (application.runner.name === "grit") {
|
|
227
|
+
files.add(workspaceInput(application.runner.pattern.relativePath));
|
|
228
|
+
for (const entry of application.runner.acquisition.entries) {
|
|
229
|
+
addSubjectInputs(files, entry.path, entry.kind);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
files.add(workspaceInput(application.runner.structure.relativePath));
|
|
234
|
+
for (const binding of application.runner.rootBindings) {
|
|
235
|
+
if (binding.path !== undefined)
|
|
236
|
+
addSubjectInputs(files, binding.path, binding.kind);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return [...runtimeInputs, ...[...files].sort()];
|
|
240
|
+
}
|
|
241
|
+
function compatibilityInputs(rule, runtimeInputs) {
|
|
242
|
+
const files = new Set(HABITAT_CATALOG_PATHS.map(workspaceInput));
|
|
243
|
+
files.add(workspaceInput(".habitat/**"));
|
|
244
|
+
files.add(workspaceInput(rule.manifestPath));
|
|
245
|
+
files.add(workspaceInput(rule.baseline.relativePath));
|
|
246
|
+
for (const pattern of rule.coveragePatterns)
|
|
247
|
+
files.add(workspaceInput(pattern));
|
|
248
|
+
if (rule.runner.name === "grit") {
|
|
249
|
+
files.add(workspaceInput(rule.runner.pattern.relativePath));
|
|
250
|
+
for (const entry of rule.runner.acquisition.entries) {
|
|
251
|
+
addSubjectInputs(files, entry.path, entry.kind);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
files.add(workspaceInput(rule.runner.structure.relativePath));
|
|
256
|
+
}
|
|
257
|
+
return [...runtimeInputs, ...[...files].sort(compareText)];
|
|
258
|
+
}
|
|
259
|
+
function addSubjectInputs(target, path, kind) {
|
|
260
|
+
const exact = workspaceInput(path);
|
|
261
|
+
if (kind === "directory" && path === ".") {
|
|
262
|
+
target.add("{workspaceRoot}/**/*");
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
target.add(exact);
|
|
266
|
+
if (kind === "directory")
|
|
267
|
+
target.add(`${exact}/**/*`);
|
|
268
|
+
}
|
|
269
|
+
function workspaceInput(path) {
|
|
270
|
+
const normalized = normalizeWorkspacePath(path);
|
|
271
|
+
return normalized === "." ? "{workspaceRoot}" : `{workspaceRoot}/${normalized}`;
|
|
272
|
+
}
|
|
273
|
+
function projectRootFor(manifestPath) {
|
|
274
|
+
const root = posix.dirname(manifestPath);
|
|
275
|
+
return root === "" ? "." : root;
|
|
276
|
+
}
|
|
277
|
+
function normalizeWorkspacePath(path) {
|
|
278
|
+
const slashPath = path.replaceAll("\\", "/");
|
|
279
|
+
if (posix.isAbsolute(slashPath) || /^[A-Za-z]:\//.test(slashPath)) {
|
|
280
|
+
throw new Error(`Habitat Nx projection requires a workspace-relative path: '${path}'.`);
|
|
281
|
+
}
|
|
282
|
+
const normalized = posix.normalize(slashPath).replace(/^\.\//, "");
|
|
283
|
+
if (normalized === ".." || normalized.startsWith("../")) {
|
|
284
|
+
throw new Error(`Habitat Nx projection path escapes the workspace: '${path}'.`);
|
|
285
|
+
}
|
|
286
|
+
return normalized === "" ? "." : normalized;
|
|
287
|
+
}
|
|
288
|
+
function rejectedCatalogError(result) {
|
|
289
|
+
return new Error(`Habitat catalog resolution rejected during Nx projection: ${JSON.stringify(result.issues)}`);
|
|
290
|
+
}
|
|
291
|
+
function compareText(left, right) {
|
|
292
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
293
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Exact app-owned identities projected by the public Habitat Nx generators. */
|
|
2
|
+
export declare const habitatConsumerBinding: {
|
|
3
|
+
readonly gritPackage: "@getgrit/cli";
|
|
4
|
+
readonly nxPlugin: "@habitat-ai/cli/nx-plugin";
|
|
5
|
+
readonly predecessorNxPlugins: readonly [{
|
|
6
|
+
readonly plugin: "@habitat/cli/nx-plugin";
|
|
7
|
+
readonly options: {
|
|
8
|
+
readonly checkTargetName: "check:policy";
|
|
9
|
+
};
|
|
10
|
+
}];
|
|
11
|
+
readonly hook: {
|
|
12
|
+
readonly _habitat: {
|
|
13
|
+
readonly identity: "@habitat-ai/cli:agent-stop";
|
|
14
|
+
readonly revision: 1;
|
|
15
|
+
};
|
|
16
|
+
readonly hooks: [{
|
|
17
|
+
readonly type: "command";
|
|
18
|
+
readonly command: "bash -lc 'repo=\"${CODEX_WORKSPACE_ROOT:-${CLAUDE_PROJECT_DIR:-}}\"; if [ -n \"$repo\" ]; then repo=\"$(git -C \"$repo\" rev-parse --show-toplevel 2>/dev/null)\"; else repo=\"$(git rev-parse --show-toplevel 2>/dev/null)\"; fi && cd \"$repo\" 2>/dev/null || { printf \"%s\\n\" \"Habitat agent-stop hook must run inside the repository worktree.\" >&2; exit 2; }; bunx --bun --no-install --package @habitat-ai/cli habitat hook agent-stop'";
|
|
19
|
+
readonly timeout: 120;
|
|
20
|
+
readonly statusMessage: "Checking Habitat structure laws";
|
|
21
|
+
}];
|
|
22
|
+
};
|
|
23
|
+
readonly predecessorHooks: readonly [{
|
|
24
|
+
readonly hooks: [{
|
|
25
|
+
readonly type: "command";
|
|
26
|
+
readonly command: "bash -lc 'repo=\"${CODEX_WORKSPACE_ROOT:-${CLAUDE_PROJECT_DIR:-}}\"; if [ -n \"$repo\" ]; then repo=\"$(git -C \"$repo\" rev-parse --show-toplevel 2>/dev/null)\"; else repo=\"$(git rev-parse --show-toplevel 2>/dev/null)\"; fi && cd \"$repo\" 2>/dev/null || { printf \"%s\\n\" \"Habitat agent-stop hook must run inside the repository worktree.\" >&2; exit 2; }; bun habitat hook agent-stop'";
|
|
27
|
+
readonly timeout: 120;
|
|
28
|
+
readonly statusMessage: "Checking Habitat structure laws";
|
|
29
|
+
}];
|
|
30
|
+
}];
|
|
31
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Exact app-owned identities projected by the public Habitat Nx generators. */
|
|
2
|
+
export const habitatConsumerBinding = {
|
|
3
|
+
gritPackage: "@getgrit/cli",
|
|
4
|
+
nxPlugin: "@habitat-ai/cli/nx-plugin",
|
|
5
|
+
predecessorNxPlugins: [
|
|
6
|
+
{
|
|
7
|
+
plugin: "@habitat/cli/nx-plugin",
|
|
8
|
+
options: { checkTargetName: "check:policy" },
|
|
9
|
+
},
|
|
10
|
+
],
|
|
11
|
+
hook: {
|
|
12
|
+
_habitat: { identity: "@habitat-ai/cli:agent-stop", revision: 1 },
|
|
13
|
+
hooks: [
|
|
14
|
+
{
|
|
15
|
+
type: "command",
|
|
16
|
+
command: 'bash -lc \'repo="${CODEX_WORKSPACE_ROOT:-${CLAUDE_PROJECT_DIR:-}}"; if [ -n "$repo" ]; then repo="$(git -C "$repo" rev-parse --show-toplevel 2>/dev/null)"; else repo="$(git rev-parse --show-toplevel 2>/dev/null)"; fi && cd "$repo" 2>/dev/null || { printf "%s\\n" "Habitat agent-stop hook must run inside the repository worktree." >&2; exit 2; }; bunx --bun --no-install --package @habitat-ai/cli habitat hook agent-stop\'',
|
|
17
|
+
timeout: 120,
|
|
18
|
+
statusMessage: "Checking Habitat structure laws",
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
predecessorHooks: [
|
|
23
|
+
{
|
|
24
|
+
hooks: [
|
|
25
|
+
{
|
|
26
|
+
type: "command",
|
|
27
|
+
command: 'bash -lc \'repo="${CODEX_WORKSPACE_ROOT:-${CLAUDE_PROJECT_DIR:-}}"; if [ -n "$repo" ]; then repo="$(git -C "$repo" rev-parse --show-toplevel 2>/dev/null)"; else repo="$(git rev-parse --show-toplevel 2>/dev/null)"; fi && cd "$repo" 2>/dev/null || { printf "%s\\n" "Habitat agent-stop hook must run inside the repository worktree." >&2; exit 2; }; bun habitat hook agent-stop\'',
|
|
28
|
+
timeout: 120,
|
|
29
|
+
statusMessage: "Checking Habitat structure laws",
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
};
|