everything-gemini 1.9.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/README.md +32 -5
- package/agents/mlops-engineer.md +33 -0
- package/bin/everything-gemini +680 -6
- package/docs/LAUNCH_KIT_v2.0.0.md +187 -0
- package/docs/LAUNCH_KIT_v2.1.0.md +118 -0
- package/docs/architecture/progressive-disclosure.md +3 -3
- package/docs/index.html +23 -10
- package/examples/python-sdk/README.md +8 -0
- package/examples/python-sdk/autonomous_mlops_rag_pipeline.py +254 -0
- package/examples/python-sdk/harness2_recursive_loop.py +240 -0
- package/hooks/budget-guard/hooks.json +16 -0
- package/hooks/budget-guard/scripts/budget-gate.sh +57 -0
- package/mcp/README.md +1 -0
- package/mcp/mcp_config.example.json +7 -0
- package/package.json +1 -1
- package/scripts/harness_rollback.sh +26 -0
- package/scripts/harness_snapshot.sh +30 -0
- package/scripts/validate.sh +1 -1
- package/skills/chromadb-vector-store/SKILL.md +188 -0
- package/skills/dataproc-spark-analytics/SKILL.md +120 -0
- package/skills/google-flow-orchestrator/SKILL.md +174 -0
- package/skills/harness2-recursive-optimization/SKILL.md +240 -0
- package/skills/keda-event-autoscaling/SKILL.md +218 -0
- package/skills/opa-gatekeeper-policy/SKILL.md +200 -0
- package/skills/triton-inference-server/SKILL.md +221 -0
- package/templates/full-power/mcp_config.json +7 -0
- package/tests/cli.test.js +160 -11
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: triton-inference-server
|
|
3
|
+
description: Deploys, configures, and tunes NVIDIA Triton Inference Server for high-throughput, low-latency multi-model serving with dynamic batching and GPU acceleration.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Triton Inference Server
|
|
7
|
+
|
|
8
|
+
Provides production architecture and configuration patterns for serving deep learning and LLM models at enterprise scale with NVIDIA Triton Inference Server.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. Architecture & Execution Model
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Client Application (gRPC / HTTP KServe v2)
|
|
16
|
+
│
|
|
17
|
+
▼
|
|
18
|
+
┌──────────────────────────────────────────────┐
|
|
19
|
+
│ NVIDIA Triton Inference Server │
|
|
20
|
+
│ │
|
|
21
|
+
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
22
|
+
│ │ Dynamic Batching │ │ Model Concurrency│ │
|
|
23
|
+
│ │ Scheduler │ │ & GPU Instances │ │
|
|
24
|
+
│ └─────────┬────────┘ └────────┬─────────┘ │
|
|
25
|
+
│ │ │ │
|
|
26
|
+
│ ▼ ▼ │
|
|
27
|
+
│ ┌────────────────────────────────────────┐ │
|
|
28
|
+
│ │ Backend Engines │ │
|
|
29
|
+
│ │ - TensorRT-LLM (High-speed generative)│ │
|
|
30
|
+
│ │ - vLLM Engine (PagedAttention serving)│ │
|
|
31
|
+
│ │ - ONNX Runtime (Embedding & classifier│ │
|
|
32
|
+
│ │ - Python Backend (Custom preprocessing│ │
|
|
33
|
+
│ └────────────────────────────────────────┘ │
|
|
34
|
+
└──────────────────────┬───────────────────────┘
|
|
35
|
+
│
|
|
36
|
+
▼
|
|
37
|
+
NVIDIA Tensor Core GPUs (H100/A100/L4)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 2. Model Repository Layout
|
|
43
|
+
|
|
44
|
+
Triton requires a strict directory layout with versioned subdirectories and `config.pbtxt`:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
model_repository/
|
|
48
|
+
├── embedding_model/
|
|
49
|
+
│ ├── 1/
|
|
50
|
+
│ │ └── model.onnx
|
|
51
|
+
│ └── config.pbtxt
|
|
52
|
+
└── llm_generator/
|
|
53
|
+
├── 1/
|
|
54
|
+
│ └── model.py
|
|
55
|
+
└── config.pbtxt
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 3. High-Throughput `config.pbtxt` Configuration
|
|
61
|
+
|
|
62
|
+
Configures dynamic batching, GPU instance groups, and TensorRT acceleration:
|
|
63
|
+
|
|
64
|
+
```protobuf
|
|
65
|
+
name: "embedding_model"
|
|
66
|
+
platform: "onnxruntime_onnx"
|
|
67
|
+
max_batch_size: 128
|
|
68
|
+
|
|
69
|
+
input [
|
|
70
|
+
{
|
|
71
|
+
name: "input_ids"
|
|
72
|
+
data_type: TYPE_INT64
|
|
73
|
+
dims: [ -1 ]
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "attention_mask"
|
|
77
|
+
data_type: TYPE_INT64
|
|
78
|
+
dims: [ -1 ]
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
output [
|
|
83
|
+
{
|
|
84
|
+
name: "sentence_embedding"
|
|
85
|
+
data_type: TYPE_FP32
|
|
86
|
+
dims: [ 768 ]
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
# Maximize GPU utilization via dynamic queue batching
|
|
91
|
+
dynamic_batching {
|
|
92
|
+
max_queue_delay_microseconds: 5000 # 5ms SLA window
|
|
93
|
+
preferred_batch_size: [ 16, 32, 64, 128 ]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Run 2 concurrent model instances on GPU 0
|
|
97
|
+
instance_group [
|
|
98
|
+
{
|
|
99
|
+
count: 2
|
|
100
|
+
kind: KIND_GPU
|
|
101
|
+
gpus: [ 0 ]
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
optimization {
|
|
106
|
+
execution_accelerators {
|
|
107
|
+
gpu_execution_accelerator : [ {
|
|
108
|
+
name : "tensorrt"
|
|
109
|
+
parameters { key: "precision_mode" value: "FP16" }
|
|
110
|
+
parameters { key: "max_workspace_size_bytes" value: "4294967296" }
|
|
111
|
+
}]
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 4. High-Performance gRPC Client (Python)
|
|
119
|
+
|
|
120
|
+
Uses binary gRPC streaming with zero-copy serialization:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
import numpy as np
|
|
124
|
+
import tritonclient.grpc as grpcclient
|
|
125
|
+
from tritonclient.grpc import InferInput, InferRequestedOutput
|
|
126
|
+
|
|
127
|
+
def query_triton_embeddings(
|
|
128
|
+
texts_token_ids: np.ndarray,
|
|
129
|
+
attention_masks: np.ndarray,
|
|
130
|
+
server_url: str = "localhost:8001"
|
|
131
|
+
) -> np.ndarray:
|
|
132
|
+
"""Queries Triton via low-latency gRPC channel."""
|
|
133
|
+
with grpcclient.InferenceServerClient(url=server_url) as client:
|
|
134
|
+
# Check server readiness
|
|
135
|
+
if not client.is_server_ready():
|
|
136
|
+
raise RuntimeError("Triton server is not healthy.")
|
|
137
|
+
|
|
138
|
+
inputs = [
|
|
139
|
+
InferInput("input_ids", texts_token_ids.shape, "INT64"),
|
|
140
|
+
InferInput("attention_mask", attention_masks.shape, "INT64")
|
|
141
|
+
]
|
|
142
|
+
inputs[0].set_data_from_numpy(texts_token_ids)
|
|
143
|
+
inputs[1].set_data_from_numpy(attention_masks)
|
|
144
|
+
|
|
145
|
+
outputs = [InferRequestedOutput("sentence_embedding")]
|
|
146
|
+
|
|
147
|
+
response = client.infer(
|
|
148
|
+
model_name="embedding_model",
|
|
149
|
+
inputs=inputs,
|
|
150
|
+
outputs=outputs,
|
|
151
|
+
timeout=10.0
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
return response.as_numpy("sentence_embedding")
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 5. Production Docker & Kubernetes Deployment
|
|
160
|
+
|
|
161
|
+
```yaml
|
|
162
|
+
apiVersion: apps/v1
|
|
163
|
+
kind: Deployment
|
|
164
|
+
metadata:
|
|
165
|
+
name: triton-inference-server
|
|
166
|
+
namespace: mlops
|
|
167
|
+
spec:
|
|
168
|
+
replicas: 2
|
|
169
|
+
selector:
|
|
170
|
+
matchLabels:
|
|
171
|
+
app: triton
|
|
172
|
+
template:
|
|
173
|
+
metadata:
|
|
174
|
+
labels:
|
|
175
|
+
app: triton
|
|
176
|
+
spec:
|
|
177
|
+
containers:
|
|
178
|
+
- name: triton
|
|
179
|
+
image: nvcr.io/nvidia/tritonserver:24.08-py3
|
|
180
|
+
args: ["tritonserver", "--model-repository=/models", "--strict-model-config=true"]
|
|
181
|
+
resources:
|
|
182
|
+
limits:
|
|
183
|
+
nvidia.com/gpu: 1
|
|
184
|
+
memory: 16Gi
|
|
185
|
+
cpu: "8"
|
|
186
|
+
ports:
|
|
187
|
+
- containerPort: 8000 # HTTP
|
|
188
|
+
- containerPort: 8001 # gRPC
|
|
189
|
+
- containerPort: 8002 # Metrics (Prometheus)
|
|
190
|
+
livenessProbe:
|
|
191
|
+
httpGet:
|
|
192
|
+
path: /v2/health/live
|
|
193
|
+
port: 8000
|
|
194
|
+
initialDelaySeconds: 30
|
|
195
|
+
readinessProbe:
|
|
196
|
+
httpGet:
|
|
197
|
+
path: /v2/health/ready
|
|
198
|
+
port: 8000
|
|
199
|
+
initialDelaySeconds: 15
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 6. Performance Benchmarking & Profiling
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# Verify health and loaded models via v2 API
|
|
208
|
+
curl -s http://localhost:8000/v2/health/ready
|
|
209
|
+
curl -s http://localhost:8000/v2/models/embedding_model/ready
|
|
210
|
+
|
|
211
|
+
# Profile throughput and P99 latency with Perf Analyzer
|
|
212
|
+
perf_analyzer -m embedding_model \
|
|
213
|
+
-u localhost:8001 -i gRPC \
|
|
214
|
+
--concurrency-range 1:16 \
|
|
215
|
+
--shape input_ids:128 \
|
|
216
|
+
--shape attention_mask:128 \
|
|
217
|
+
--percentile 99
|
|
218
|
+
|
|
219
|
+
# Query Prometheus metrics
|
|
220
|
+
curl -s http://localhost:8002/metrics | grep nv_inference_
|
|
221
|
+
```
|
|
@@ -57,6 +57,13 @@
|
|
|
57
57
|
"puppeteer": {
|
|
58
58
|
"command": "npx",
|
|
59
59
|
"args": ["-y", "@modelcontextprotocol/server-puppeteer"]
|
|
60
|
+
},
|
|
61
|
+
"brave-search": {
|
|
62
|
+
"command": "npx",
|
|
63
|
+
"args": ["-y", "@modelcontextprotocol/server-brave-search"],
|
|
64
|
+
"env": {
|
|
65
|
+
"BRAVE_API_KEY": "${BRAVE_API_KEY}"
|
|
66
|
+
}
|
|
60
67
|
}
|
|
61
68
|
}
|
|
62
69
|
}
|
package/tests/cli.test.js
CHANGED
|
@@ -90,10 +90,10 @@ it('should benchmark ecosystem components and output valid JSON with --json', ()
|
|
|
90
90
|
const res = runCli('benchmark --json');
|
|
91
91
|
assert.strictEqual(res.status, 0);
|
|
92
92
|
const data = JSON.parse(res.stdout);
|
|
93
|
-
assert.strictEqual(data.totalComponents,
|
|
94
|
-
assert.strictEqual(data.progressiveSkillsCount,
|
|
95
|
-
assert.strictEqual(data.scopedAgentsCount,
|
|
96
|
-
assert.strictEqual(data.lifecycleHooksCount,
|
|
93
|
+
assert.strictEqual(data.totalComponents, 84);
|
|
94
|
+
assert.strictEqual(data.progressiveSkillsCount, 63);
|
|
95
|
+
assert.strictEqual(data.scopedAgentsCount, 13);
|
|
96
|
+
assert.strictEqual(data.lifecycleHooksCount, 8);
|
|
97
97
|
assert(data.totalRunbookTokens > 15000, 'Expected >15,000 runbook tokens');
|
|
98
98
|
assert(data.estimatedTokensSavedPerTurn > 8000, 'Expected significant token savings');
|
|
99
99
|
});
|
|
@@ -316,7 +316,7 @@ try {
|
|
|
316
316
|
const dryRes = runCli(`bundle --output "${bundleTarget}" --dry-run`);
|
|
317
317
|
assert.strictEqual(dryRes.status, 0);
|
|
318
318
|
assert.match(dryRes.stdout, /DRY RUN MODE ACTIVATED/);
|
|
319
|
-
assert.match(dryRes.stdout, /Skills to bundle:
|
|
319
|
+
assert.match(dryRes.stdout, /Skills to bundle: 63/);
|
|
320
320
|
assert.strictEqual(fs.existsSync(bundleTarget), false, 'Dry run should not create destination directory');
|
|
321
321
|
|
|
322
322
|
// JSON Dry-run mode
|
|
@@ -324,7 +324,7 @@ try {
|
|
|
324
324
|
assert.strictEqual(jsonRes.status, 0);
|
|
325
325
|
const dryData = JSON.parse(jsonRes.stdout);
|
|
326
326
|
assert.strictEqual(dryData.dryRun, true);
|
|
327
|
-
assert.strictEqual(dryData.manifest.components.totalCount,
|
|
327
|
+
assert.strictEqual(dryData.manifest.components.totalCount, 84);
|
|
328
328
|
|
|
329
329
|
// Live mode
|
|
330
330
|
const liveRes = runCli(`bundle --output "${bundleTarget}"`);
|
|
@@ -423,15 +423,164 @@ try {
|
|
|
423
423
|
assert.match(resRate.stdout, /Distributed Rate Limiting/);
|
|
424
424
|
});
|
|
425
425
|
|
|
426
|
+
// Info for mlops-engineer and v2.0.0 skills
|
|
427
|
+
it('should display info for mlops-engineer subagent and v2.0.0 skills', () => {
|
|
428
|
+
const resMl = runCli('info mlops-engineer');
|
|
429
|
+
assert.strictEqual(resMl.status, 0);
|
|
430
|
+
assert.match(resMl.stdout, /MLOps Engineer/);
|
|
431
|
+
|
|
432
|
+
const resKeda = runCli('info keda-event-autoscaling');
|
|
433
|
+
assert.strictEqual(resKeda.status, 0);
|
|
434
|
+
assert.match(resKeda.stdout, /KEDA/);
|
|
435
|
+
|
|
436
|
+
const resChroma = runCli('info chromadb-vector-store');
|
|
437
|
+
assert.strictEqual(resChroma.status, 0);
|
|
438
|
+
assert.match(resChroma.stdout, /ChromaDB/);
|
|
439
|
+
|
|
440
|
+
const resOpa = runCli('info opa-gatekeeper-policy');
|
|
441
|
+
assert.strictEqual(resOpa.status, 0);
|
|
442
|
+
assert.match(resOpa.stdout, /Gatekeeper/);
|
|
443
|
+
|
|
444
|
+
const resTriton = runCli('info triton-inference-server');
|
|
445
|
+
assert.strictEqual(resTriton.status, 0);
|
|
446
|
+
assert.match(resTriton.stdout, /Triton/);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// Info for v2.1.0 Google Research Harness2 & Google Family skills
|
|
450
|
+
it('should display info for v2.1.0 Harness2, Google Flow, and Dataproc Spark skills', () => {
|
|
451
|
+
const resH2 = runCli('info harness2-recursive-optimization');
|
|
452
|
+
assert.strictEqual(resH2.status, 0);
|
|
453
|
+
assert.match(resH2.stdout, /Harness² Recursive Optimization/);
|
|
454
|
+
|
|
455
|
+
const resFlow = runCli('info google-flow-orchestrator');
|
|
456
|
+
assert.strictEqual(resFlow.status, 0);
|
|
457
|
+
assert.match(resFlow.stdout, /Google Flow Orchestrator/);
|
|
458
|
+
|
|
459
|
+
const resSpark = runCli('info dataproc-spark-analytics');
|
|
460
|
+
assert.strictEqual(resSpark.status, 0);
|
|
461
|
+
assert.match(resSpark.stdout, /Dataproc Spark Analytics/);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Harness2 command and export format verification
|
|
465
|
+
it('should synthesize Google Research Harness2 workspace in dry-run, live, and JSON export modes', () => {
|
|
466
|
+
const h2Target = path.join(tempWorkspace, 'harness2-test-ws');
|
|
467
|
+
|
|
468
|
+
// Dry-run mode
|
|
469
|
+
const dryRes = runCli(`harness2 "${h2Target}" --dry-run`);
|
|
470
|
+
assert.strictEqual(dryRes.status, 0);
|
|
471
|
+
assert.match(dryRes.stdout, /DRY RUN MODE ACTIVATED/);
|
|
472
|
+
assert.match(dryRes.stdout, /Previewing Google Research Harness² Surfaces/);
|
|
473
|
+
assert.strictEqual(fs.existsSync(h2Target), false, 'Dry run should not create destination directory');
|
|
474
|
+
|
|
475
|
+
// JSON Dry-run mode
|
|
476
|
+
const jsonRes = runCli(`harness2 "${h2Target}" --dry-run --json`);
|
|
477
|
+
assert.strictEqual(jsonRes.status, 0);
|
|
478
|
+
const dryData = JSON.parse(jsonRes.stdout);
|
|
479
|
+
assert.strictEqual(dryData.dryRun, true);
|
|
480
|
+
assert.strictEqual(dryData.manifest.metrics.total_skills, 63);
|
|
481
|
+
assert.strictEqual(dryData.manifest.metrics.total_subagents, 13);
|
|
482
|
+
assert.strictEqual(dryData.manifest.metrics.total_hooks, 8);
|
|
483
|
+
|
|
484
|
+
// Live mode
|
|
485
|
+
const liveRes = runCli(`harness2 "${h2Target}"`);
|
|
486
|
+
assert.strictEqual(liveRes.status, 0);
|
|
487
|
+
assert.match(liveRes.stdout, /Google Research Harness² Workspace Synthesized Successfully/);
|
|
488
|
+
assert.strictEqual(fs.existsSync(path.join(h2Target, 'harness.yaml')), true);
|
|
489
|
+
assert.strictEqual(fs.existsSync(path.join(h2Target, 'harness.json')), true);
|
|
490
|
+
assert.strictEqual(fs.existsSync(path.join(h2Target, 'prompts', 'GEMINI.md')), true);
|
|
491
|
+
assert.strictEqual(fs.existsSync(path.join(h2Target, 'memory', 'memory_spec.json')), true);
|
|
492
|
+
assert.strictEqual(fs.existsSync(path.join(h2Target, 'guidance', 'tool_selection_rules.md')), true);
|
|
493
|
+
assert.strictEqual(fs.existsSync(path.join(h2Target, 'subagents', 'planner.md')), true);
|
|
494
|
+
assert.strictEqual(fs.existsSync(path.join(h2Target, 'skills', 'harness2-recursive-optimization', 'SKILL.md')), true);
|
|
495
|
+
assert.strictEqual(fs.existsSync(path.join(h2Target, 'scripts', 'harness_probe.sh')), true);
|
|
496
|
+
assert.strictEqual(fs.existsSync(path.join(h2Target, 'scripts', 'harness_snapshot.sh')), true);
|
|
497
|
+
assert.strictEqual(fs.existsSync(path.join(h2Target, 'scripts', 'harness_rollback.sh')), true);
|
|
498
|
+
|
|
499
|
+
// Export with --format harness2
|
|
500
|
+
const h2ExportTarget = path.join(tempWorkspace, 'harness2-export-ws');
|
|
501
|
+
const exportRes = runCli(`export --format harness2 --output "${h2ExportTarget}"`);
|
|
502
|
+
assert.strictEqual(exportRes.status, 0);
|
|
503
|
+
assert.strictEqual(fs.existsSync(path.join(h2ExportTarget, 'harness.yaml')), true);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// Graph command verification
|
|
507
|
+
it('should render interaction graph in terminal, mermaid, and JSON formats', () => {
|
|
508
|
+
const resTerm = runCli('graph');
|
|
509
|
+
assert.strictEqual(resTerm.status, 0);
|
|
510
|
+
assert.match(resTerm.stdout, /Multi-Agent Topology & Dependency Graph/);
|
|
511
|
+
assert.match(resTerm.stdout, /MLOPS-ENGINEER/);
|
|
512
|
+
|
|
513
|
+
const resMermaid = runCli('graph --mermaid');
|
|
514
|
+
assert.strictEqual(resMermaid.status, 0);
|
|
515
|
+
assert.match(resMermaid.stdout, /graph TD/);
|
|
516
|
+
assert.match(resMermaid.stdout, /A_mlops_engineer/);
|
|
517
|
+
|
|
518
|
+
const resJson = runCli('graph --json');
|
|
519
|
+
assert.strictEqual(resJson.status, 0);
|
|
520
|
+
const data = JSON.parse(resJson.stdout);
|
|
521
|
+
assert.ok(data.totalNodes > 50, 'Graph should contain > 50 nodes');
|
|
522
|
+
assert.ok(data.totalEdges > 50, 'Graph should contain > 50 edges');
|
|
523
|
+
assert.ok(data.nodes.find(n => n.id === 'agent:mlops-engineer'));
|
|
524
|
+
});
|
|
525
|
+
|
|
426
526
|
// Benchmark Components Verification
|
|
427
|
-
it('should report
|
|
527
|
+
it('should report 84 components, 63 skills, and 13 subagents in benchmark --json', () => {
|
|
428
528
|
const res = runCli('benchmark --json');
|
|
429
529
|
assert.strictEqual(res.status, 0);
|
|
430
530
|
const data = JSON.parse(res.stdout);
|
|
431
|
-
assert.strictEqual(data.totalComponents,
|
|
432
|
-
assert.strictEqual(data.progressiveSkillsCount,
|
|
433
|
-
assert.strictEqual(data.scopedAgentsCount,
|
|
434
|
-
assert.strictEqual(data.lifecycleHooksCount,
|
|
531
|
+
assert.strictEqual(data.totalComponents, 84, 'Total components should be 84');
|
|
532
|
+
assert.strictEqual(data.progressiveSkillsCount, 63, 'Progressive skills should be 63');
|
|
533
|
+
assert.strictEqual(data.scopedAgentsCount, 13, 'Scoped agents should be 13');
|
|
534
|
+
assert.strictEqual(data.lifecycleHooksCount, 8, 'Lifecycle hooks should be 8');
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// Harness² Run & Ingest-Trace tests
|
|
538
|
+
it('should support harness2 run with local benchmark, hard spend limits, and dry-run/json modes', () => {
|
|
539
|
+
const testTasksDir = path.join(tempWorkspace, 'harness2-tasks');
|
|
540
|
+
// Dry-run mode
|
|
541
|
+
const dryRes = runCli(`harness2 run --dir "${testTasksDir}" --bench local --max-usd 15 --dry-run`);
|
|
542
|
+
assert.strictEqual(dryRes.status, 0);
|
|
543
|
+
assert.match(dryRes.stdout, /DRY RUN MODE ACTIVATED/);
|
|
544
|
+
assert.match(dryRes.stdout, /Spend Ceiling: \$15\.00 USD/);
|
|
545
|
+
|
|
546
|
+
// JSON export mode
|
|
547
|
+
const jsonRes = runCli(`harness2 run --dir "${testTasksDir}" --bench local --max-usd 25 --json`);
|
|
548
|
+
assert.strictEqual(jsonRes.status, 0);
|
|
549
|
+
const runData = JSON.parse(jsonRes.stdout);
|
|
550
|
+
assert.strictEqual(runData.engine, 'everything-gemini-harness2-runner');
|
|
551
|
+
assert.strictEqual(runData.benchmark, 'local');
|
|
552
|
+
assert.strictEqual(runData.spendGuardrail.maxSpendUsd, 25);
|
|
553
|
+
assert.strictEqual(runData.result.status, 'PASSED');
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it('should support harness2 ingest-trace to generate automated regression probes', () => {
|
|
557
|
+
const sampleTracePath = path.join(tempWorkspace, 'production-failure.json');
|
|
558
|
+
fs.writeFileSync(sampleTracePath, JSON.stringify({
|
|
559
|
+
id: 'trace-spark-oom',
|
|
560
|
+
error: 'OutOfMemoryError: Container killed due to 16GB memory ceiling',
|
|
561
|
+
tool: 'dataproc-spark-analytics',
|
|
562
|
+
timestamp: '2026-10-19T18:00:00Z',
|
|
563
|
+
stack: 'Fatal: JVM worker heap exhausted at SparkExecutor.scala:42'
|
|
564
|
+
}, null, 2), 'utf8');
|
|
565
|
+
|
|
566
|
+
const traceOutDir = path.join(tempWorkspace, 'regression-tasks');
|
|
567
|
+
const ingestRes = runCli(`harness2 ingest-trace "${sampleTracePath}" --dir "${traceOutDir}"`);
|
|
568
|
+
assert.strictEqual(ingestRes.status, 0);
|
|
569
|
+
assert.match(ingestRes.stdout, /Production Trace Ingested into Harness² Regression Test/);
|
|
570
|
+
|
|
571
|
+
const generatedTask = path.join(traceOutDir, 'regression-trace-spark-oom.json');
|
|
572
|
+
assert.strictEqual(fs.existsSync(generatedTask), true);
|
|
573
|
+
const taskContent = JSON.parse(fs.readFileSync(generatedTask, 'utf8'));
|
|
574
|
+
assert.strictEqual(taskContent.id, 'regression-trace-spark-oom');
|
|
575
|
+
assert.strictEqual(taskContent.sourceTrace, 'production-failure.json');
|
|
576
|
+
assert.match(taskContent.errorCategory, /OutOfMemoryError/);
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it('should display info for budget-guard lifecycle hook', () => {
|
|
580
|
+
const res = runCli('info budget-guard');
|
|
581
|
+
assert.strictEqual(res.status, 0);
|
|
582
|
+
assert.match(res.stdout, /budget-guard/);
|
|
583
|
+
assert.match(res.stdout, /PreToolUse/);
|
|
435
584
|
});
|
|
436
585
|
|
|
437
586
|
} finally {
|