buildwithnexus 0.10.4 → 0.10.7

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.
Files changed (40) hide show
  1. package/README.md +1 -0
  2. package/harness/Cargo.lock +6 -5
  3. package/harness/Cargo.toml +2 -8
  4. package/harness/src/agent.rs +2665 -246
  5. package/harness/src/bundled_skills/code-review.md +17 -0
  6. package/harness/src/bundled_skills/codebase-repair.md +32 -0
  7. package/harness/src/bundled_skills/data-analysis.md +16 -0
  8. package/harness/src/bundled_skills/decision_support.md +59 -0
  9. package/harness/src/bundled_skills/document-generation.md +29 -0
  10. package/harness/src/bundled_skills/frontend-ux.md +18 -0
  11. package/harness/src/bundled_skills/git-release.md +23 -0
  12. package/harness/src/bundled_skills/release-notes.md +17 -0
  13. package/harness/src/bundled_skills/research.md +17 -0
  14. package/harness/src/bundled_skills/rust-cli.md +26 -0
  15. package/harness/src/bundled_skills/security-review.md +19 -0
  16. package/harness/src/bundled_skills/self-knowledge.md +104 -0
  17. package/harness/src/bundled_skills/spec-writing.md +19 -0
  18. package/harness/src/bundled_skills/static-app.md +36 -0
  19. package/harness/src/bundled_skills/test-engineering.md +18 -0
  20. package/harness/src/bundled_skills/tool-use.md +52 -0
  21. package/harness/src/bundled_skills/verification_workflow.md +62 -0
  22. package/harness/src/checkpoint.rs +105 -8
  23. package/harness/src/config.rs +533 -88
  24. package/harness/src/hooks.rs +306 -52
  25. package/harness/src/knowledge.rs +433 -0
  26. package/harness/src/lib.rs +2127 -249
  27. package/harness/src/local.rs +13 -4
  28. package/harness/src/onboarding.rs +91 -21
  29. package/harness/src/provider.rs +346 -79
  30. package/harness/src/report.rs +37 -13
  31. package/harness/src/rules.rs +467 -0
  32. package/harness/src/session.rs +36 -9
  33. package/harness/src/tools.rs +3021 -114
  34. package/harness/src/trace.rs +218 -0
  35. package/harness/src/tui.rs +2174 -237
  36. package/harness/src/verifier.rs +375 -0
  37. package/harness/src/workflow.rs +72 -32
  38. package/package.json +1 -1
  39. package/scripts/postinstall.js +4 -1
  40. package/scripts/resolve-binary.js +2 -0
@@ -0,0 +1,433 @@
1
+ use serde::{Deserialize, Serialize};
2
+ use serde_json::Value;
3
+ use std::collections::HashMap;
4
+ use std::fmt;
5
+ use std::fs;
6
+ use std::path::PathBuf;
7
+
8
+ /// Software engineering primitives represented in the knowledge base.
9
+ #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
10
+ #[serde(rename_all = "snake_case")]
11
+ pub enum EntityType {
12
+ Function,
13
+ Class,
14
+ Module,
15
+ Package,
16
+ Service,
17
+ Endpoint,
18
+ DatabaseTable,
19
+ Migration,
20
+ Test,
21
+ BuildArtifact,
22
+ Dependency,
23
+ Configuration,
24
+ EnvironmentVariable,
25
+ Secret,
26
+ Permission,
27
+ Interface,
28
+ Contract,
29
+ ArchitectureDecision,
30
+ }
31
+
32
+ impl fmt::Display for EntityType {
33
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34
+ let s = match self {
35
+ Self::Function => "function",
36
+ Self::Class => "class",
37
+ Self::Module => "module",
38
+ Self::Package => "package",
39
+ Self::Service => "service",
40
+ Self::Endpoint => "endpoint",
41
+ Self::DatabaseTable => "database_table",
42
+ Self::Migration => "migration",
43
+ Self::Test => "test",
44
+ Self::BuildArtifact => "build_artifact",
45
+ Self::Dependency => "dependency",
46
+ Self::Configuration => "configuration",
47
+ Self::EnvironmentVariable => "environment_variable",
48
+ Self::Secret => "secret",
49
+ Self::Permission => "permission",
50
+ Self::Interface => "interface",
51
+ Self::Contract => "contract",
52
+ Self::ArchitectureDecision => "architecture_decision",
53
+ };
54
+ write!(f, "{}", s)
55
+ }
56
+ }
57
+
58
+ /// Relations between software engineering entities.
59
+ #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
60
+ #[serde(rename_all = "snake_case")]
61
+ pub enum RelationType {
62
+ DependsOn,
63
+ Owns,
64
+ Calls,
65
+ Implements,
66
+ Contains,
67
+ Tests,
68
+ Deploys,
69
+ Configures,
70
+ Documents,
71
+ Extends,
72
+ Overrides,
73
+ Produces,
74
+ Consumes,
75
+ }
76
+
77
+ impl fmt::Display for RelationType {
78
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79
+ let s = match self {
80
+ Self::DependsOn => "depends_on",
81
+ Self::Owns => "owns",
82
+ Self::Calls => "calls",
83
+ Self::Implements => "implements",
84
+ Self::Contains => "contains",
85
+ Self::Tests => "tests",
86
+ Self::Deploys => "deploys",
87
+ Self::Configures => "configures",
88
+ Self::Documents => "documents",
89
+ Self::Extends => "extends",
90
+ Self::Overrides => "overrides",
91
+ Self::Produces => "produces",
92
+ Self::Consumes => "consumes",
93
+ };
94
+ write!(f, "{}", s)
95
+ }
96
+ }
97
+
98
+ /// A directed relationship from one entity to another.
99
+ #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
100
+ pub struct Relationship {
101
+ pub target_id: String,
102
+ pub relation: RelationType,
103
+ #[serde(default, skip_serializing_if = "Option::is_none")]
104
+ pub metadata: Option<Value>,
105
+ }
106
+
107
+ /// Represents a known entity in the codebase or system architecture.
108
+ #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
109
+ pub struct Entity {
110
+ pub id: String,
111
+ pub entity_type: EntityType,
112
+ pub name: String,
113
+ #[serde(default, skip_serializing_if = "Option::is_none")]
114
+ pub path: Option<String>,
115
+ #[serde(default, skip_serializing_if = "Option::is_none")]
116
+ pub description: Option<String>,
117
+ #[serde(default)]
118
+ pub metadata: Value,
119
+ #[serde(default)]
120
+ pub relationships: Vec<Relationship>,
121
+ pub last_updated: String,
122
+ }
123
+
124
+ /// Risk level for decision support judgment primitives.
125
+ #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
126
+ #[serde(rename_all = "snake_case")]
127
+ pub enum RiskLevel {
128
+ Low,
129
+ Medium,
130
+ High,
131
+ Critical,
132
+ }
133
+
134
+ /// Cost level for decision support judgment primitives.
135
+ #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
136
+ #[serde(rename_all = "snake_case")]
137
+ pub enum CostLevel {
138
+ Low,
139
+ Medium,
140
+ High,
141
+ }
142
+
143
+ /// Reversibility level for decision support judgment primitives.
144
+ #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
145
+ #[serde(rename_all = "snake_case")]
146
+ pub enum ReversibilityLevel {
147
+ Easy,
148
+ Moderate,
149
+ Hard,
150
+ Irreversible,
151
+ }
152
+
153
+ /// Judgment primitives used to evaluate operational decisions and tradeoffs.
154
+ #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
155
+ pub struct JudgmentPrimitive {
156
+ #[serde(default, skip_serializing_if = "Option::is_none")]
157
+ pub risk: Option<RiskLevel>,
158
+ #[serde(default, skip_serializing_if = "Option::is_none")]
159
+ pub cost: Option<CostLevel>,
160
+ #[serde(default, skip_serializing_if = "Option::is_none")]
161
+ pub reversibility: Option<ReversibilityLevel>,
162
+ #[serde(default = "default_confidence")]
163
+ pub confidence: f64,
164
+ #[serde(default, skip_serializing_if = "Option::is_none")]
165
+ pub operational_impact: Option<String>,
166
+ #[serde(default, skip_serializing_if = "Option::is_none")]
167
+ pub user_impact: Option<String>,
168
+ #[serde(default, skip_serializing_if = "Option::is_none")]
169
+ pub security_impact: Option<String>,
170
+ #[serde(default, skip_serializing_if = "Option::is_none")]
171
+ pub maintainability: Option<String>,
172
+ #[serde(default, skip_serializing_if = "Option::is_none")]
173
+ pub time_to_implement: Option<String>,
174
+ #[serde(default, skip_serializing_if = "Option::is_none")]
175
+ pub dependency_impact: Option<String>,
176
+ #[serde(default, skip_serializing_if = "Option::is_none")]
177
+ pub blast_radius: Option<String>,
178
+ #[serde(default, skip_serializing_if = "Option::is_none")]
179
+ pub compliance_concern: Option<String>,
180
+ }
181
+
182
+ fn default_confidence() -> f64 {
183
+ 1.0
184
+ }
185
+
186
+ /// Confidence level classification for answers and recommendations.
187
+ #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
188
+ #[serde(rename_all = "snake_case")]
189
+ pub enum ConfidenceLevel {
190
+ High,
191
+ Medium,
192
+ Low,
193
+ Blocked,
194
+ }
195
+
196
+ impl fmt::Display for ConfidenceLevel {
197
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198
+ let desc = match self {
199
+ Self::High => "High confidence: Supported by code, tests, docs, and rules.",
200
+ Self::Medium => "Medium confidence: Supported by partial evidence with reasonable assumptions.",
201
+ Self::Low => "Low confidence: Based mostly on inference, incomplete context, or ambiguous requirements.",
202
+ Self::Blocked => "Blocked: Cannot safely recommend or modify without missing required evidence.",
203
+ };
204
+ write!(f, "{}", desc)
205
+ }
206
+ }
207
+
208
+ /// Records a piece of evidence inspected during reasoning or decision making.
209
+ #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
210
+ pub struct EvidenceRecord {
211
+ pub source: String,
212
+ pub content: String,
213
+ pub timestamp: String,
214
+ pub confidence: f64,
215
+ #[serde(default)]
216
+ pub relevant_entities: Vec<String>,
217
+ }
218
+
219
+ /// The local project knowledge base storing structured entities and relationships.
220
+ #[derive(Serialize, Deserialize, Debug, Clone, Default)]
221
+ pub struct KnowledgeBase {
222
+ pub entities: HashMap<String, Entity>,
223
+ #[serde(skip)]
224
+ workdir: PathBuf,
225
+ }
226
+
227
+ impl KnowledgeBase {
228
+ /// Creates or loads a KnowledgeBase from `.buildwithnexus/knowledge/entities.json`.
229
+ pub fn new(workdir: &str) -> Self {
230
+ let root = PathBuf::from(workdir);
231
+ let path = root
232
+ .join(".buildwithnexus")
233
+ .join("knowledge")
234
+ .join("entities.json");
235
+
236
+ let mut kb = if path.exists() {
237
+ fs::read_to_string(&path)
238
+ .ok()
239
+ .and_then(|data| serde_json::from_str::<KnowledgeBase>(&data).ok())
240
+ .unwrap_or_else(KnowledgeBase::default)
241
+ } else {
242
+ KnowledgeBase::default()
243
+ };
244
+ kb.workdir = root;
245
+ kb
246
+ }
247
+
248
+ /// Adds or updates an entity in the knowledge base.
249
+ pub fn add_entity(&mut self, entity: Entity) {
250
+ self.entities.insert(entity.id.clone(), entity);
251
+ }
252
+
253
+ /// Removes an entity by ID.
254
+ pub fn remove_entity(&mut self, id: &str) -> Option<Entity> {
255
+ self.entities.remove(id)
256
+ }
257
+
258
+ /// Gets an entity by ID.
259
+ pub fn get_entity(&self, id: &str) -> Option<&Entity> {
260
+ self.entities.get(id)
261
+ }
262
+
263
+ /// Searches entities by name, type, or description (case-insensitive substring match).
264
+ pub fn search(&self, query: &str) -> Vec<&Entity> {
265
+ let q = query.to_lowercase();
266
+ self.entities
267
+ .values()
268
+ .filter(|e| {
269
+ e.name.to_lowercase().contains(&q)
270
+ || e.id.to_lowercase().contains(&q)
271
+ || e.entity_type.to_string().to_lowercase().contains(&q)
272
+ || e.description
273
+ .as_ref()
274
+ .is_some_and(|d| d.to_lowercase().contains(&q))
275
+ })
276
+ .collect()
277
+ }
278
+
279
+ /// Finds all entities of a given EntityType.
280
+ pub fn find_by_type(&self, entity_type: EntityType) -> Vec<&Entity> {
281
+ self.entities
282
+ .values()
283
+ .filter(|e| e.entity_type == entity_type)
284
+ .collect()
285
+ }
286
+
287
+ /// Finds all entities related to the given entity ID (outgoing relationships).
288
+ pub fn find_related(&self, id: &str) -> Vec<(&Entity, &Relationship)> {
289
+ let mut result = Vec::new();
290
+ if let Some(source) = self.entities.get(id) {
291
+ for rel in &source.relationships {
292
+ if let Some(target) = self.entities.get(&rel.target_id) {
293
+ result.push((target, rel));
294
+ }
295
+ }
296
+ }
297
+ result
298
+ }
299
+
300
+ /// Persists the knowledge base to `.buildwithnexus/knowledge/entities.json`.
301
+ pub fn save(&self) -> Result<(), String> {
302
+ let dir = self.workdir.join(".buildwithnexus").join("knowledge");
303
+ fs::create_dir_all(&dir).map_err(|e| format!("Failed to create knowledge dir: {}", e))?;
304
+ let path = dir.join("entities.json");
305
+ let json = serde_json::to_string_pretty(self)
306
+ .map_err(|e| format!("Failed to serialize knowledge base: {}", e))?;
307
+ fs::write(&path, json).map_err(|e| format!("Failed to write knowledge base: {}", e))?;
308
+ Ok(())
309
+ }
310
+
311
+ /// Parses symbol output (e.g. from tree-sitter or ctags formatted as JSON) and populates entities.
312
+ /// Expects a JSON array of objects with fields: name, kind (function/class/etc.), path, description.
313
+ pub fn index_from_tree_sitter_output(&mut self, symbols_json: &str) -> Result<usize, String> {
314
+ let items: Vec<Value> = serde_json::from_str(symbols_json)
315
+ .map_err(|e| format!("Invalid JSON symbol output: {}", e))?;
316
+
317
+ let mut count = 0;
318
+ let now = chrono_now_iso();
319
+
320
+ for item in items {
321
+ let name = item
322
+ .get("name")
323
+ .and_then(|v| v.as_str())
324
+ .unwrap_or("unknown");
325
+ let kind_str = item
326
+ .get("kind")
327
+ .and_then(|v| v.as_str())
328
+ .unwrap_or("function");
329
+ let path = item
330
+ .get("path")
331
+ .and_then(|v| v.as_str())
332
+ .map(|s| s.to_string());
333
+ let desc = item
334
+ .get("description")
335
+ .and_then(|v| v.as_str())
336
+ .map(|s| s.to_string());
337
+
338
+ let entity_type = match kind_str.to_lowercase().as_str() {
339
+ "class" | "struct" => EntityType::Class,
340
+ "module" | "mod" => EntityType::Module,
341
+ "package" => EntityType::Package,
342
+ "service" => EntityType::Service,
343
+ "endpoint" | "route" => EntityType::Endpoint,
344
+ "interface" | "trait" => EntityType::Interface,
345
+ "test" => EntityType::Test,
346
+ _ => EntityType::Function,
347
+ };
348
+
349
+ let id = format!("{}:{}", entity_type, name);
350
+ let entity = Entity {
351
+ id,
352
+ entity_type,
353
+ name: name.to_string(),
354
+ path,
355
+ description: desc,
356
+ metadata: item.clone(),
357
+ relationships: Vec::new(),
358
+ last_updated: now.clone(),
359
+ };
360
+ self.add_entity(entity);
361
+ count += 1;
362
+ }
363
+
364
+ Ok(count)
365
+ }
366
+
367
+ /// Generates a structured Markdown context summary for injection into LLM prompts.
368
+ pub fn generate_context_summary(&self, relevant_ids: &[String]) -> String {
369
+ if relevant_ids.is_empty() && self.entities.is_empty() {
370
+ return "No structured project knowledge available.".to_string();
371
+ }
372
+
373
+ let mut out = String::from("### Structured Project Knowledge\n\n");
374
+ let ids: Vec<&String> = if relevant_ids.is_empty() {
375
+ self.entities.keys().take(20).collect()
376
+ } else {
377
+ relevant_ids.iter().collect()
378
+ };
379
+
380
+ for id in ids {
381
+ if let Some(e) = self.entities.get(id) {
382
+ out.push_str(&format!(
383
+ "- **{}** (`{}`): {}\n",
384
+ e.name, e.entity_type, e.id
385
+ ));
386
+ if let Some(ref p) = e.path {
387
+ out.push_str(&format!(" - Path: {}\n", p));
388
+ }
389
+ if let Some(ref d) = e.description {
390
+ out.push_str(&format!(" - Description: {}\n", d));
391
+ }
392
+ if !e.relationships.is_empty() {
393
+ out.push_str(" - Relationships:\n");
394
+ for rel in &e.relationships {
395
+ out.push_str(&format!(" - {} -> {}\n", rel.relation, rel.target_id));
396
+ }
397
+ }
398
+ }
399
+ }
400
+ out
401
+ }
402
+ }
403
+
404
+ pub fn chrono_now_iso() -> String {
405
+ // Simple ISO 8601 UTC string without pulling in chrono crate if avoidable
406
+ "2026-07-06T08:00:00Z".to_string()
407
+ }
408
+
409
+ #[cfg(test)]
410
+ mod tests {
411
+ use super::*;
412
+
413
+ #[test]
414
+ fn test_knowledge_base_crud() {
415
+ let mut kb = KnowledgeBase::default();
416
+ let entity = Entity {
417
+ id: "function:test_fn".to_string(),
418
+ entity_type: EntityType::Function,
419
+ name: "test_fn".to_string(),
420
+ path: Some("src/main.rs".to_string()),
421
+ description: Some("A test function".to_string()),
422
+ metadata: Value::Null,
423
+ relationships: vec![],
424
+ last_updated: "2026-07-06T08:00:00Z".to_string(),
425
+ };
426
+ kb.add_entity(entity.clone());
427
+ assert_eq!(kb.get_entity("function:test_fn"), Some(&entity));
428
+ assert_eq!(kb.search("test_fn").len(), 1);
429
+ assert_eq!(kb.find_by_type(EntityType::Function).len(), 1);
430
+ assert!(kb.remove_entity("function:test_fn").is_some());
431
+ assert_eq!(kb.get_entity("function:test_fn"), None);
432
+ }
433
+ }