antigravity-ai-kit 2.1.0 → 3.0.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/.agent/README.md +4 -4
- package/.agent/agents/README.md +16 -12
- package/.agent/agents/architect.md +1 -0
- package/.agent/agents/backend-specialist.md +11 -0
- package/.agent/agents/code-reviewer.md +1 -0
- package/.agent/agents/database-architect.md +11 -0
- package/.agent/agents/devops-engineer.md +11 -0
- package/.agent/agents/e2e-runner.md +1 -0
- package/.agent/agents/explorer-agent.md +11 -0
- package/.agent/agents/frontend-specialist.md +11 -0
- package/.agent/agents/mobile-developer.md +11 -0
- package/.agent/agents/performance-optimizer.md +11 -0
- package/.agent/agents/planner.md +1 -0
- package/.agent/agents/refactor-cleaner.md +1 -0
- package/.agent/agents/reliability-engineer.md +11 -0
- package/.agent/agents/security-reviewer.md +1 -0
- package/.agent/agents/sprint-orchestrator.md +10 -0
- package/.agent/agents/tdd-guide.md +1 -0
- package/.agent/commands/code-review.md +1 -0
- package/.agent/commands/debug.md +1 -0
- package/.agent/commands/deploy.md +1 -0
- package/.agent/commands/help.md +252 -31
- package/.agent/commands/plan.md +1 -0
- package/.agent/commands/status.md +1 -0
- package/.agent/commands/tdd.md +1 -0
- package/.agent/contexts/brainstorm.md +26 -0
- package/.agent/contexts/debug.md +28 -0
- package/.agent/contexts/implement.md +29 -0
- package/.agent/contexts/review.md +27 -0
- package/.agent/contexts/ship.md +28 -0
- package/.agent/engine/identity.json +13 -0
- package/.agent/engine/loading-rules.json +23 -1
- package/.agent/engine/marketplace-index.json +29 -0
- package/.agent/engine/reliability-config.json +14 -0
- package/.agent/engine/sdlc-map.json +44 -0
- package/.agent/engine/workflow-state.json +28 -2
- package/.agent/hooks/hooks.json +27 -25
- package/.agent/manifest.json +12 -4
- package/.agent/rules.md +2 -1
- package/.agent/skills/README.md +10 -5
- package/.agent/skills/i18n-localization/SKILL.md +191 -0
- package/.agent/skills/mcp-integration/SKILL.md +224 -0
- package/.agent/skills/parallel-agents/SKILL.md +1 -1
- package/.agent/skills/shell-conventions/SKILL.md +92 -0
- package/.agent/skills/ui-ux-pro-max/SKILL.md +557 -0
- package/.agent/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/.agent/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/.agent/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agent/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/.agent/templates/adr-template.md +32 -0
- package/.agent/templates/bug-report.md +37 -0
- package/.agent/templates/feature-request.md +32 -0
- package/.agent/workflows/README.md +92 -78
- package/.agent/workflows/brainstorm.md +154 -100
- package/.agent/workflows/create.md +142 -75
- package/.agent/workflows/debug.md +157 -98
- package/.agent/workflows/deploy.md +195 -144
- package/.agent/workflows/enhance.md +157 -65
- package/.agent/workflows/orchestrate.md +171 -114
- package/.agent/workflows/plan.md +147 -72
- package/.agent/workflows/preview.md +140 -83
- package/.agent/workflows/quality-gate.md +196 -0
- package/.agent/workflows/retrospective.md +197 -0
- package/.agent/workflows/review.md +188 -0
- package/.agent/workflows/status.md +142 -91
- package/.agent/workflows/test.md +168 -95
- package/.agent/workflows/ui-ux-pro-max.md +181 -127
- package/README.md +215 -78
- package/bin/ag-kit.js +344 -10
- package/lib/agent-registry.js +214 -0
- package/lib/agent-reputation.js +351 -0
- package/lib/cli-commands.js +235 -0
- package/lib/conflict-detector.js +245 -0
- package/lib/engineering-manager.js +354 -0
- package/lib/error-budget.js +294 -0
- package/lib/hook-system.js +252 -0
- package/lib/identity.js +245 -0
- package/lib/loading-engine.js +208 -0
- package/lib/marketplace.js +298 -0
- package/lib/plugin-system.js +604 -0
- package/lib/security-scanner.js +309 -0
- package/lib/self-healing.js +434 -0
- package/lib/session-manager.js +261 -0
- package/lib/skill-sandbox.js +244 -0
- package/lib/task-governance.js +523 -0
- package/lib/task-model.js +317 -0
- package/lib/updater.js +201 -0
- package/lib/verify.js +240 -0
- package/lib/workflow-engine.js +353 -0
- package/lib/workflow-persistence.js +160 -0
- package/package.json +7 -3
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Antigravity AI Kit — Error Budget Tracker
|
|
3
|
+
*
|
|
4
|
+
* Enables reliability-config.json error budget tracking with
|
|
5
|
+
* basic metrics collection. Records test, build, and deployment
|
|
6
|
+
* results and calculates budget health.
|
|
7
|
+
*
|
|
8
|
+
* @module lib/error-budget
|
|
9
|
+
* @author Emre Dursun
|
|
10
|
+
* @since v3.0.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const AGENT_DIR = '.agent';
|
|
19
|
+
const ENGINE_DIR = 'engine';
|
|
20
|
+
const RELIABILITY_CONFIG = 'reliability-config.json';
|
|
21
|
+
const METRICS_FILE = 'metrics.json';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {'HEALTHY' | 'WARNING' | 'EXHAUSTED'} BudgetStatus
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {object} MetricsData
|
|
29
|
+
* @property {number} testsPassed - Total tests passed
|
|
30
|
+
* @property {number} testsFailed - Total tests failed
|
|
31
|
+
* @property {number} buildsSucceeded - Total successful builds
|
|
32
|
+
* @property {number} buildsFailed - Total failed builds
|
|
33
|
+
* @property {number} deploysSucceeded - Total successful deploys
|
|
34
|
+
* @property {number} deploysRolledBack - Total rolled-back deploys
|
|
35
|
+
* @property {string} periodStart - ISO timestamp of period start
|
|
36
|
+
* @property {string} lastUpdated - ISO timestamp of last update
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {object} BudgetReport
|
|
41
|
+
* @property {BudgetStatus} status - Overall budget health
|
|
42
|
+
* @property {object} rates - Current failure rates
|
|
43
|
+
* @property {number} rates.testFailureRate - Test failure rate (%)
|
|
44
|
+
* @property {number} rates.buildFailureRate - Build failure rate (%)
|
|
45
|
+
* @property {number} rates.deployRollbackRate - Deploy rollback rate (%)
|
|
46
|
+
* @property {object} thresholds - Configured thresholds
|
|
47
|
+
* @property {number} thresholds.testFailureRatePercent - Max test failure rate
|
|
48
|
+
* @property {number} thresholds.buildFailureRatePercent - Max build failure rate
|
|
49
|
+
* @property {number} thresholds.deployRollbackRatePercent - Max deploy rollback rate
|
|
50
|
+
* @property {string[]} violations - Which metrics exceeded thresholds
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resolves the path to the metrics file.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} projectRoot - Root directory of the project
|
|
57
|
+
* @returns {string} Absolute path to metrics.json
|
|
58
|
+
*/
|
|
59
|
+
function resolveMetricsPath(projectRoot) {
|
|
60
|
+
return path.join(projectRoot, AGENT_DIR, ENGINE_DIR, METRICS_FILE);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resolves the path to the reliability config.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} projectRoot - Root directory of the project
|
|
67
|
+
* @returns {string} Absolute path to reliability-config.json
|
|
68
|
+
*/
|
|
69
|
+
function resolveConfigPath(projectRoot) {
|
|
70
|
+
return path.join(projectRoot, AGENT_DIR, ENGINE_DIR, RELIABILITY_CONFIG);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Loads the reliability configuration.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} projectRoot - Root directory of the project
|
|
77
|
+
* @returns {object} Parsed reliability config
|
|
78
|
+
*/
|
|
79
|
+
function loadConfig(projectRoot) {
|
|
80
|
+
const configPath = resolveConfigPath(projectRoot);
|
|
81
|
+
|
|
82
|
+
if (!fs.existsSync(configPath)) {
|
|
83
|
+
throw new Error(`Reliability config not found: ${configPath}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Loads metrics data from disk, or returns defaults if file doesn't exist.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} projectRoot - Root directory of the project
|
|
93
|
+
* @returns {MetricsData}
|
|
94
|
+
*/
|
|
95
|
+
function loadMetrics(projectRoot) {
|
|
96
|
+
const metricsPath = resolveMetricsPath(projectRoot);
|
|
97
|
+
|
|
98
|
+
if (!fs.existsSync(metricsPath)) {
|
|
99
|
+
return createEmptyMetrics();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
return JSON.parse(fs.readFileSync(metricsPath, 'utf-8'));
|
|
104
|
+
} catch {
|
|
105
|
+
return createEmptyMetrics();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Creates an empty metrics object.
|
|
111
|
+
*
|
|
112
|
+
* @returns {MetricsData}
|
|
113
|
+
*/
|
|
114
|
+
function createEmptyMetrics() {
|
|
115
|
+
return {
|
|
116
|
+
testsPassed: 0,
|
|
117
|
+
testsFailed: 0,
|
|
118
|
+
buildsSucceeded: 0,
|
|
119
|
+
buildsFailed: 0,
|
|
120
|
+
deploysSucceeded: 0,
|
|
121
|
+
deploysRolledBack: 0,
|
|
122
|
+
periodStart: new Date().toISOString(),
|
|
123
|
+
lastUpdated: new Date().toISOString(),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Writes metrics data to disk atomically.
|
|
129
|
+
*
|
|
130
|
+
* @param {string} projectRoot - Root directory of the project
|
|
131
|
+
* @param {MetricsData} metrics - Metrics data to write
|
|
132
|
+
* @returns {void}
|
|
133
|
+
*/
|
|
134
|
+
function writeMetrics(projectRoot, metrics) {
|
|
135
|
+
const metricsPath = resolveMetricsPath(projectRoot);
|
|
136
|
+
const tempPath = `${metricsPath}.tmp`;
|
|
137
|
+
|
|
138
|
+
metrics.lastUpdated = new Date().toISOString();
|
|
139
|
+
|
|
140
|
+
fs.writeFileSync(tempPath, JSON.stringify(metrics, null, 2) + '\n', 'utf-8');
|
|
141
|
+
fs.renameSync(tempPath, metricsPath);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Records the result of a test run.
|
|
146
|
+
*
|
|
147
|
+
* @param {string} projectRoot - Root directory of the project
|
|
148
|
+
* @param {number} passed - Number of tests passed
|
|
149
|
+
* @param {number} failed - Number of tests failed
|
|
150
|
+
* @returns {void}
|
|
151
|
+
*/
|
|
152
|
+
function recordTestResult(projectRoot, passed, failed) {
|
|
153
|
+
const metrics = loadMetrics(projectRoot);
|
|
154
|
+
metrics.testsPassed += passed;
|
|
155
|
+
metrics.testsFailed += failed;
|
|
156
|
+
writeMetrics(projectRoot, metrics);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Records the result of a build.
|
|
161
|
+
*
|
|
162
|
+
* @param {string} projectRoot - Root directory of the project
|
|
163
|
+
* @param {boolean} success - Whether the build succeeded
|
|
164
|
+
* @returns {void}
|
|
165
|
+
*/
|
|
166
|
+
function recordBuildResult(projectRoot, success) {
|
|
167
|
+
const metrics = loadMetrics(projectRoot);
|
|
168
|
+
|
|
169
|
+
if (success) {
|
|
170
|
+
metrics.buildsSucceeded += 1;
|
|
171
|
+
} else {
|
|
172
|
+
metrics.buildsFailed += 1;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
writeMetrics(projectRoot, metrics);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Records the result of a deployment.
|
|
180
|
+
*
|
|
181
|
+
* @param {string} projectRoot - Root directory of the project
|
|
182
|
+
* @param {boolean} success - Whether deploy succeeded
|
|
183
|
+
* @param {boolean} [rolledBack=false] - Whether it was rolled back
|
|
184
|
+
* @returns {void}
|
|
185
|
+
*/
|
|
186
|
+
function recordDeployResult(projectRoot, success, rolledBack = false) {
|
|
187
|
+
const metrics = loadMetrics(projectRoot);
|
|
188
|
+
|
|
189
|
+
if (success && !rolledBack) {
|
|
190
|
+
metrics.deploysSucceeded += 1;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (rolledBack) {
|
|
194
|
+
metrics.deploysRolledBack += 1;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
writeMetrics(projectRoot, metrics);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Calculates a failure rate percentage safely.
|
|
202
|
+
*
|
|
203
|
+
* @param {number} failed - Number of failures
|
|
204
|
+
* @param {number} total - Total attempts
|
|
205
|
+
* @returns {number} Failure rate as percentage (0-100)
|
|
206
|
+
*/
|
|
207
|
+
function calculateRate(failed, total) {
|
|
208
|
+
if (total === 0) {
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
return Number(((failed / total) * 100).toFixed(2));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Generates a budget health report.
|
|
216
|
+
*
|
|
217
|
+
* @param {string} projectRoot - Root directory of the project
|
|
218
|
+
* @returns {BudgetReport}
|
|
219
|
+
*/
|
|
220
|
+
function getBudgetReport(projectRoot) {
|
|
221
|
+
const config = loadConfig(projectRoot);
|
|
222
|
+
const metrics = loadMetrics(projectRoot);
|
|
223
|
+
const thresholds = config.errorBudget?.thresholds || {};
|
|
224
|
+
|
|
225
|
+
const testTotal = metrics.testsPassed + metrics.testsFailed;
|
|
226
|
+
const buildTotal = metrics.buildsSucceeded + metrics.buildsFailed;
|
|
227
|
+
const deployTotal = metrics.deploysSucceeded + metrics.deploysRolledBack;
|
|
228
|
+
|
|
229
|
+
const rates = {
|
|
230
|
+
testFailureRate: calculateRate(metrics.testsFailed, testTotal),
|
|
231
|
+
buildFailureRate: calculateRate(metrics.buildsFailed, buildTotal),
|
|
232
|
+
deployRollbackRate: calculateRate(metrics.deploysRolledBack, deployTotal),
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/** @type {string[]} */
|
|
236
|
+
const violations = [];
|
|
237
|
+
|
|
238
|
+
if (rates.testFailureRate > (thresholds.testFailureRatePercent || 5)) {
|
|
239
|
+
violations.push('testFailureRate');
|
|
240
|
+
}
|
|
241
|
+
if (rates.buildFailureRate > (thresholds.buildFailureRatePercent || 2)) {
|
|
242
|
+
violations.push('buildFailureRate');
|
|
243
|
+
}
|
|
244
|
+
if (rates.deployRollbackRate > (thresholds.deployRollbackRatePercent || 10)) {
|
|
245
|
+
violations.push('deployRollbackRate');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** @type {BudgetStatus} */
|
|
249
|
+
let status = 'HEALTHY';
|
|
250
|
+
|
|
251
|
+
if (violations.length > 0) {
|
|
252
|
+
status = 'EXHAUSTED';
|
|
253
|
+
} else {
|
|
254
|
+
// Warning if any rate is above 80% of threshold
|
|
255
|
+
const testWarning = rates.testFailureRate > (thresholds.testFailureRatePercent || 5) * 0.8;
|
|
256
|
+
const buildWarning = rates.buildFailureRate > (thresholds.buildFailureRatePercent || 2) * 0.8;
|
|
257
|
+
const deployWarning = rates.deployRollbackRate > (thresholds.deployRollbackRatePercent || 10) * 0.8;
|
|
258
|
+
|
|
259
|
+
if (testWarning || buildWarning || deployWarning) {
|
|
260
|
+
status = 'WARNING';
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
status,
|
|
266
|
+
rates,
|
|
267
|
+
thresholds: {
|
|
268
|
+
testFailureRatePercent: thresholds.testFailureRatePercent || 5,
|
|
269
|
+
buildFailureRatePercent: thresholds.buildFailureRatePercent || 2,
|
|
270
|
+
deployRollbackRatePercent: thresholds.deployRollbackRatePercent || 10,
|
|
271
|
+
},
|
|
272
|
+
violations,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Resets metrics for a new tracking period.
|
|
278
|
+
*
|
|
279
|
+
* @param {string} projectRoot - Root directory of the project
|
|
280
|
+
* @returns {void}
|
|
281
|
+
*/
|
|
282
|
+
function resetMetrics(projectRoot) {
|
|
283
|
+
writeMetrics(projectRoot, createEmptyMetrics());
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
module.exports = {
|
|
287
|
+
loadConfig,
|
|
288
|
+
loadMetrics,
|
|
289
|
+
recordTestResult,
|
|
290
|
+
recordBuildResult,
|
|
291
|
+
recordDeployResult,
|
|
292
|
+
getBudgetReport,
|
|
293
|
+
resetMetrics,
|
|
294
|
+
};
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Antigravity AI Kit — Hook Trigger System
|
|
3
|
+
*
|
|
4
|
+
* Event-driven lifecycle hook execution based on hooks.json.
|
|
5
|
+
* Evaluates hook actions and reports results with severity awareness.
|
|
6
|
+
*
|
|
7
|
+
* @module lib/hook-system
|
|
8
|
+
* @author Emre Dursun
|
|
9
|
+
* @since v3.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const AGENT_DIR = '.agent';
|
|
18
|
+
const HOOKS_DIR = 'hooks';
|
|
19
|
+
const HOOKS_FILE = 'hooks.json';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {object} ActionResult
|
|
23
|
+
* @property {string} action - Action description
|
|
24
|
+
* @property {'critical' | 'high' | 'medium' | 'low'} severity - Action severity
|
|
25
|
+
* @property {'block' | 'warn' | 'log'} onFailure - Failure behavior
|
|
26
|
+
* @property {'pass' | 'fail' | 'skip'} status - Evaluation result
|
|
27
|
+
* @property {string} reason - Reason for status
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {object} HookEvaluation
|
|
32
|
+
* @property {string} event - Hook event name
|
|
33
|
+
* @property {boolean} blocked - Whether any blocking action failed
|
|
34
|
+
* @property {number} passed - Count of passed actions
|
|
35
|
+
* @property {number} failed - Count of failed actions
|
|
36
|
+
* @property {number} skipped - Count of skipped actions
|
|
37
|
+
* @property {ActionResult[]} results - Individual action results
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Loads hooks definition from hooks.json.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} projectRoot - Root directory of the project
|
|
44
|
+
* @returns {object} Parsed hooks config
|
|
45
|
+
*/
|
|
46
|
+
function loadHooks(projectRoot) {
|
|
47
|
+
const hooksPath = path.join(projectRoot, AGENT_DIR, HOOKS_DIR, HOOKS_FILE);
|
|
48
|
+
|
|
49
|
+
if (!fs.existsSync(hooksPath)) {
|
|
50
|
+
return { hooks: [] };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return JSON.parse(fs.readFileSync(hooksPath, 'utf-8'));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Gets the action definitions for a specific event.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} eventName - Hook event name (e.g., 'session-start')
|
|
60
|
+
* @param {string} projectRoot - Root directory of the project
|
|
61
|
+
* @returns {object[]} Action definitions for this event
|
|
62
|
+
*/
|
|
63
|
+
function getHookActions(eventName, projectRoot) {
|
|
64
|
+
const config = loadHooks(projectRoot);
|
|
65
|
+
const hook = (config.hooks || []).find((h) => h.event === eventName);
|
|
66
|
+
|
|
67
|
+
if (!hook) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return hook.actions || [];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Evaluates a hook event against the current project state.
|
|
76
|
+
*
|
|
77
|
+
* This performs a "check" pass — it determines which actions would
|
|
78
|
+
* pass or fail without actually executing them. Actions are evaluated
|
|
79
|
+
* based on the existence of required files and configurations.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} eventName - Hook event name
|
|
82
|
+
* @param {object} context - Evaluation context
|
|
83
|
+
* @param {string} context.projectRoot - Root directory of the project
|
|
84
|
+
* @param {boolean} [context.gitClean] - Whether git status is clean
|
|
85
|
+
* @param {boolean} [context.testsPass] - Whether tests pass
|
|
86
|
+
* @param {boolean} [context.buildPass] - Whether build passes
|
|
87
|
+
* @param {boolean} [context.lintPass] - Whether lint passes
|
|
88
|
+
* @returns {HookEvaluation}
|
|
89
|
+
*/
|
|
90
|
+
function evaluateHook(eventName, context) {
|
|
91
|
+
const projectRoot = context.projectRoot;
|
|
92
|
+
const actions = getHookActions(eventName, projectRoot);
|
|
93
|
+
|
|
94
|
+
if (actions.length === 0) {
|
|
95
|
+
return { event: eventName, blocked: false, passed: 0, failed: 0, skipped: 0, results: [] };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** @type {ActionResult[]} */
|
|
99
|
+
const results = [];
|
|
100
|
+
let blocked = false;
|
|
101
|
+
|
|
102
|
+
for (const action of actions) {
|
|
103
|
+
const result = evaluateAction(action, context);
|
|
104
|
+
results.push(result);
|
|
105
|
+
|
|
106
|
+
if (result.status === 'fail' && action.onFailure === 'block') {
|
|
107
|
+
blocked = true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
event: eventName,
|
|
113
|
+
blocked,
|
|
114
|
+
passed: results.filter((r) => r.status === 'pass').length,
|
|
115
|
+
failed: results.filter((r) => r.status === 'fail').length,
|
|
116
|
+
skipped: results.filter((r) => r.status === 'skip').length,
|
|
117
|
+
results,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Evaluates a single action based on context.
|
|
123
|
+
*
|
|
124
|
+
* @param {object} action - Action definition from hooks.json
|
|
125
|
+
* @param {object} context - Evaluation context
|
|
126
|
+
* @returns {ActionResult}
|
|
127
|
+
*/
|
|
128
|
+
function evaluateAction(action, context) {
|
|
129
|
+
const actionLower = action.action.toLowerCase();
|
|
130
|
+
const projectRoot = context.projectRoot;
|
|
131
|
+
|
|
132
|
+
// File existence checks
|
|
133
|
+
if (actionLower.includes('session-context.md') || actionLower.includes('session-state.json')) {
|
|
134
|
+
const targetFile = actionLower.includes('session-context.md') ? 'session-context.md' : 'session-state.json';
|
|
135
|
+
const filePath = path.join(projectRoot, AGENT_DIR, targetFile);
|
|
136
|
+
const exists = fs.existsSync(filePath);
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
action: action.action,
|
|
140
|
+
severity: action.severity,
|
|
141
|
+
onFailure: action.onFailure,
|
|
142
|
+
status: exists ? 'pass' : 'fail',
|
|
143
|
+
reason: exists ? `${targetFile} exists` : `${targetFile} not found`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (actionLower.includes('loading-rules.json') || actionLower.includes('workflow-state.json')) {
|
|
148
|
+
const targetFile = actionLower.includes('loading-rules.json') ? 'loading-rules.json' : 'workflow-state.json';
|
|
149
|
+
const filePath = path.join(projectRoot, AGENT_DIR, 'engine', targetFile);
|
|
150
|
+
const exists = fs.existsSync(filePath);
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
action: action.action,
|
|
154
|
+
severity: action.severity,
|
|
155
|
+
onFailure: action.onFailure,
|
|
156
|
+
status: exists ? 'pass' : 'fail',
|
|
157
|
+
reason: exists ? `${targetFile} exists and readable` : `${targetFile} not found`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Git status check
|
|
162
|
+
if (actionLower.includes('git status')) {
|
|
163
|
+
const gitClean = context.gitClean !== undefined ? context.gitClean : true;
|
|
164
|
+
return {
|
|
165
|
+
action: action.action,
|
|
166
|
+
severity: action.severity,
|
|
167
|
+
onFailure: action.onFailure,
|
|
168
|
+
status: gitClean ? 'pass' : 'fail',
|
|
169
|
+
reason: gitClean ? 'Git status clean' : 'Git has uncommitted changes',
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Build/test/lint checks
|
|
174
|
+
if (actionLower.includes('build passes') || actionLower.includes('npm run build')) {
|
|
175
|
+
return {
|
|
176
|
+
action: action.action,
|
|
177
|
+
severity: action.severity,
|
|
178
|
+
onFailure: action.onFailure,
|
|
179
|
+
status: context.buildPass ? 'pass' : context.buildPass === false ? 'fail' : 'skip',
|
|
180
|
+
reason: context.buildPass ? 'Build passes' : context.buildPass === false ? 'Build failed' : 'Build not checked',
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (actionLower.includes('tests pass') || actionLower.includes('npm test')) {
|
|
185
|
+
return {
|
|
186
|
+
action: action.action,
|
|
187
|
+
severity: action.severity,
|
|
188
|
+
onFailure: action.onFailure,
|
|
189
|
+
status: context.testsPass ? 'pass' : context.testsPass === false ? 'fail' : 'skip',
|
|
190
|
+
reason: context.testsPass ? 'Tests pass' : context.testsPass === false ? 'Tests failed' : 'Tests not checked',
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (actionLower.includes('lint passes') || actionLower.includes('npm run lint')) {
|
|
195
|
+
return {
|
|
196
|
+
action: action.action,
|
|
197
|
+
severity: action.severity,
|
|
198
|
+
onFailure: action.onFailure,
|
|
199
|
+
status: context.lintPass ? 'pass' : context.lintPass === false ? 'fail' : 'skip',
|
|
200
|
+
reason: context.lintPass ? 'Lint passes' : context.lintPass === false ? 'Lint failed' : 'Lint not checked',
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Default: skip actions we can't evaluate programmatically
|
|
205
|
+
return {
|
|
206
|
+
action: action.action,
|
|
207
|
+
severity: action.severity,
|
|
208
|
+
onFailure: action.onFailure,
|
|
209
|
+
status: 'skip',
|
|
210
|
+
reason: 'Cannot evaluate programmatically — requires agent execution',
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Lists all available hook events.
|
|
216
|
+
*
|
|
217
|
+
* @param {string} projectRoot - Root directory of the project
|
|
218
|
+
* @returns {Array<{ event: string, description: string, actionCount: number }>}
|
|
219
|
+
*/
|
|
220
|
+
function listEvents(projectRoot) {
|
|
221
|
+
const config = loadHooks(projectRoot);
|
|
222
|
+
|
|
223
|
+
return (config.hooks || []).map((hook) => ({
|
|
224
|
+
event: hook.event,
|
|
225
|
+
description: hook.description,
|
|
226
|
+
actionCount: (hook.actions || []).length,
|
|
227
|
+
}));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Generates a full hook readiness report for all events.
|
|
232
|
+
*
|
|
233
|
+
* @param {string} projectRoot - Root directory of the project
|
|
234
|
+
* @returns {{ events: HookEvaluation[], totalActions: number, readyCount: number }}
|
|
235
|
+
*/
|
|
236
|
+
function getHookReport(projectRoot) {
|
|
237
|
+
const events = listEvents(projectRoot);
|
|
238
|
+
const context = { projectRoot };
|
|
239
|
+
|
|
240
|
+
const evaluations = events.map((e) => evaluateHook(e.event, context));
|
|
241
|
+
const totalActions = evaluations.reduce((sum, e) => sum + e.passed + e.failed + e.skipped, 0);
|
|
242
|
+
const readyCount = evaluations.filter((e) => !e.blocked).length;
|
|
243
|
+
|
|
244
|
+
return { events: evaluations, totalActions, readyCount };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
module.exports = {
|
|
248
|
+
getHookActions,
|
|
249
|
+
evaluateHook,
|
|
250
|
+
listEvents,
|
|
251
|
+
getHookReport,
|
|
252
|
+
};
|