neohive 6.2.2 → 6.4.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.
@@ -115,11 +115,13 @@ module.exports = function (ctx) {
115
115
  reviews.push(review);
116
116
  writeJsonFile(REVIEWS_FILE, reviews);
117
117
 
118
- broadcastSystemMessage(`[REVIEW] ${state.registeredName} requests review of "${review.file}": ${review.description || 'No description'}. Call submit_review("${review.id}", "approved"/"changes_requested", "your feedback") to review.`, state.registeredName);
118
+ broadcastSystemMessage(`[REVIEW REQUEST] ${state.registeredName} requests review of "${review.file}": ${review.description || 'No description'}. To review: (1) read the file "${review.file}", (2) call submit_review("${review.id}", "approved"/"changes_requested", "<your findings — min 50 chars>"). Feedback is required and must be substantive.`, state.registeredName);
119
119
  touchActivity();
120
- return { success: true, review_id: review.id, file: review.file, message: 'Review requested. Team has been notified.' };
120
+ return { success: true, review_id: review.id, file: review.file, next_action: 'Call listen() to wait for the review.' };
121
121
  }
122
122
 
123
+ const REVIEW_FEEDBACK_MIN_LENGTH = 50;
124
+
123
125
  function toolSubmitReview(reviewId, status, feedback) {
124
126
  if (!state.registeredName) return { error: 'You must call register() first' };
125
127
 
@@ -131,6 +133,26 @@ module.exports = function (ctx) {
131
133
  if (!review) return { error: `Review not found: ${reviewId}` };
132
134
  if (review.requested_by === state.registeredName) return { error: 'Cannot review your own code.' };
133
135
 
136
+ // Enforce substantive feedback — rubber-stamping is not allowed
137
+ const feedbackText = (feedback || '').trim();
138
+ if (!feedbackText) {
139
+ return {
140
+ error: `Feedback is required. You must read "${review.file}" and describe what you found before submitting a review.`,
141
+ next_action: `Read the file "${review.file}" first, then call submit_review("${reviewId}", "${status}", "<your findings>").`,
142
+ };
143
+ }
144
+ if (feedbackText.length < REVIEW_FEEDBACK_MIN_LENGTH) {
145
+ return {
146
+ error: `Feedback too short (${feedbackText.length} chars, minimum ${REVIEW_FEEDBACK_MIN_LENGTH}). Describe specific findings — what you read, what issues you found or verified, and why you ${status === 'approved' ? 'approve' : 'request changes'}.`,
147
+ next_action: `Read the file "${review.file}" first, then call submit_review("${reviewId}", "${status}", "<your detailed findings>").`,
148
+ };
149
+ }
150
+
151
+ // Log audit entry for thin approvals (short feedback on an approval)
152
+ if (status === 'approved' && feedbackText.length < 150) {
153
+ logViolation('thin_review', state.registeredName, `Approved "${review.file}" with minimal feedback (${feedbackText.length} chars): "${feedbackText.substring(0, 100)}"`);
154
+ }
155
+
134
156
  review.status = status;
135
157
  review.reviewer = state.registeredName;
136
158
  review.feedback = (feedback || '').substring(0, 2000);
@@ -191,7 +213,10 @@ module.exports = function (ctx) {
191
213
  writeJsonFile(REVIEWS_FILE, reviews);
192
214
  touchActivity();
193
215
 
194
- const result = { success: true, review_id: reviewId, status: review.status, message: `Review submitted: ${review.status}` };
216
+ const reviewNextAction = review.status === 'approved'
217
+ ? 'Call listen() to continue.'
218
+ : 'Call listen() — the author will fix and resubmit.';
219
+ const result = { success: true, review_id: reviewId, status: review.status, next_action: reviewNextAction };
195
220
  if (review.review_round) result.review_round = review.review_round;
196
221
  if (review.auto_approved) result.auto_approved = true;
197
222
  return result;
@@ -381,12 +406,12 @@ module.exports = function (ctx) {
381
406
  {
382
407
  name: 'request_review',
383
408
  description: 'Request a code review from the team. Creates a review request and notifies all agents.',
384
- inputSchema: { type: 'object', properties: { file_path: { type: 'string', description: 'File to review' }, description: { type: 'string', description: 'What to focus on in the review' } }, required: ['file_path'], additionalProperties: false },
409
+ inputSchema: { type: 'object', properties: { file_path: { type: 'string', description: 'File to review', maxLength: 500 }, description: { type: 'string', description: 'What to focus on in the review', maxLength: 2000 } }, required: ['file_path'], additionalProperties: false },
385
410
  },
386
411
  {
387
412
  name: 'submit_review',
388
- description: 'Submit a code review — approve or request changes with feedback.',
389
- inputSchema: { type: 'object', properties: { review_id: { type: 'string', description: 'Review ID' }, status: { type: 'string', enum: ['approved', 'changes_requested'], description: 'Review result' }, feedback: { type: 'string', description: 'Your review feedback (max 2000 chars)' } }, required: ['review_id', 'status'], additionalProperties: false },
413
+ description: 'Submit a code review — approve or request changes. You MUST read the file under review before calling this. Feedback is required (minimum 50 chars) and must describe specific findings — what you read, what issues you found or confirmed. Rubber-stamp approvals are rejected.',
414
+ inputSchema: { type: 'object', properties: { review_id: { type: 'string', description: 'Review ID', maxLength: 50 }, status: { type: 'string', enum: ['approved', 'changes_requested'], description: 'Review result' }, feedback: { type: 'string', description: 'Your findings from reading the file (required, min 50 chars). Describe what you read and what you found — bugs, security issues, correctness, or confirmation that the code is clean.', maxLength: 2000, minLength: 50 } }, required: ['review_id', 'status', 'feedback'], additionalProperties: false },
390
415
  },
391
416
  // Rules
392
417
  {
@@ -395,7 +420,7 @@ module.exports = function (ctx) {
395
420
  inputSchema: {
396
421
  type: 'object',
397
422
  properties: {
398
- text: { type: 'string', description: 'The rule text' },
423
+ text: { type: 'string', description: 'The rule text', maxLength: 2000 },
399
424
  category: { type: 'string', description: 'Rule category: safety, workflow, code-style, communication, custom' },
400
425
  scope: {
401
426
  type: 'object',
@@ -430,7 +455,7 @@ module.exports = function (ctx) {
430
455
  {
431
456
  name: 'log_violation',
432
457
  description: 'Log a workflow rule violation to the audit trail. Used automatically by review gates, or manually to flag issues.',
433
- inputSchema: { type: 'object', properties: { type: { type: 'string', description: 'Violation type: review_skipped, push_without_approval, rule_violated, etc.' }, details: { type: 'string', description: 'Description of the violation' } }, required: ['type'], additionalProperties: false },
458
+ inputSchema: { type: 'object', properties: { type: { type: 'string', description: 'Violation type: review_skipped, push_without_approval, rule_violated, etc.', maxLength: 100 }, details: { type: 'string', description: 'Description of the violation', maxLength: 2000 } }, required: ['type'], additionalProperties: false },
434
459
  },
435
460
  {
436
461
  name: 'request_push_approval',
@@ -132,7 +132,7 @@ module.exports = function (ctx) {
132
132
  total_messages: compressed.segments.reduce((s, seg) => s + seg.message_count, 0) + recent.length,
133
133
  compressed_count: compressed.segments.reduce((s, seg) => s + seg.message_count, 0),
134
134
  recent_count: recent.length,
135
- hint: 'Compressed segments summarize older messages. Recent messages are shown verbatim.',
135
+ next_action: 'Call listen() to receive messages.',
136
136
  };
137
137
  }
138
138
 
@@ -223,9 +223,9 @@ module.exports = function (ctx) {
223
223
  progress,
224
224
  your_tasks: myActiveTasks.map(t => ({ id: t.id, title: t.title, status: t.status })),
225
225
  your_completed: myCompletedCount,
226
- hint: myActiveTasks.length > 0
227
- ? `You have ${myActiveTasks.length} active task(s). Continue working.`
228
- : 'You are now briefed. Check active tasks and start contributing.',
226
+ next_action: myActiveTasks.length > 0
227
+ ? `You have ${myActiveTasks.length} active task(s). Continue working, then call listen().`
228
+ : 'Call listen() to receive messages and start working.',
229
229
  };
230
230
  }
231
231
 
@@ -56,7 +56,9 @@ module.exports = function (ctx) {
56
56
  result.preview = `${latest.from}: "${latest.content.substring(0, 80).replace(/\n/g, ' ')}..."`;
57
57
  const oldestAge = Math.round((Date.now() - new Date(unconsumed[0].timestamp).getTime()) / 1000);
58
58
  result.urgency = oldestAge > 120 ? 'critical' : oldestAge > 30 ? 'urgent' : 'normal';
59
- result.action_required = 'You have unread messages. Call listen() to receive and process them. Do NOT call check_messages() again — it does not consume messages and you will see the same messages repeatedly.';
59
+ result.next_action = 'Call listen() to receive and process these messages.';
60
+ } else {
61
+ result.next_action = 'Call listen() to wait for new messages.';
60
62
  }
61
63
 
62
64
  return result;
package/tools/safety.js CHANGED
@@ -34,7 +34,7 @@ module.exports = function (ctx) {
34
34
  locks[normalized] = { agent: state.registeredName, since: new Date().toISOString() };
35
35
  writeJsonFile(LOCKS_FILE, locks);
36
36
  touchActivity();
37
- return { success: true, file: normalized, message: `File locked. Other agents cannot edit "${normalized}" until you call unlock_file().` };
37
+ return { success: true, file: normalized, next_action: 'Edit the file, then call unlock_file() when done.' };
38
38
  }
39
39
 
40
40
  function toolUnlockFile(filePath) {
@@ -48,15 +48,15 @@ module.exports = function (ctx) {
48
48
  if (lock.agent === state.registeredName) { delete locks[fp]; count++; }
49
49
  }
50
50
  writeJsonFile(LOCKS_FILE, locks);
51
- return { success: true, unlocked: count, message: `Unlocked ${count} file(s).` };
51
+ return { success: true, unlocked: count, message: `Unlocked ${count} file(s).`, next_action: 'Call listen() to receive messages.' };
52
52
  }
53
53
 
54
- if (!locks[normalized]) return { success: true, message: 'File was not locked.' };
54
+ if (!locks[normalized]) return { success: true, message: 'File was not locked.', next_action: 'Call listen() to receive messages.' };
55
55
  if (locks[normalized].agent !== state.registeredName) return { error: `File is locked by ${locks[normalized].agent}, not you.` };
56
56
 
57
57
  delete locks[normalized];
58
58
  writeJsonFile(LOCKS_FILE, locks);
59
- return { success: true, file: normalized, message: 'File unlocked.' };
59
+ return { success: true, file: normalized, message: 'File unlocked.', next_action: 'Call listen() to receive messages.' };
60
60
  }
61
61
 
62
62
  // --- Dependencies ---
package/tools/tasks.js CHANGED
@@ -81,7 +81,7 @@ module.exports = function (ctx) {
81
81
  saveTasks(tasks);
82
82
  touchActivity();
83
83
 
84
- const result = { success: true, task_id: task.id, assignee: task.assignee };
84
+ const result = { success: true, task_id: task.id, assignee: task.assignee, next_action: 'Call listen() to receive updates.' };
85
85
  if (taskChannel) result.channel = taskChannel;
86
86
  return result;
87
87
  }
@@ -160,6 +160,7 @@ module.exports = function (ctx) {
160
160
  task_id: task.id,
161
161
  status: 'in_review',
162
162
  review_id: reviewId,
163
+ next_action: 'Call listen() to wait for the reviewer to approve.',
163
164
  message: `Cannot mark done — a reviewer is online and no approval exists. Review ${reviewId} auto-created. Wait for approval, then try again.`,
164
165
  };
165
166
  }
@@ -291,7 +292,11 @@ module.exports = function (ctx) {
291
292
  for (const n of notifications) { helpers.sendSystemMessage(n.agent, n.message); }
292
293
  } catch (e) { /* hooks not available */ }
293
294
 
294
- return { success: true, task_id: task.id, status: task.status, title: task.title };
295
+ const nextAction = status === 'done' ? 'Send a summary of what you did via send_message(), then call listen().'
296
+ : status === 'in_progress' ? `Do the work on "${task.title}", then call update_task("${task.id}", "done") when finished.`
297
+ : status === 'blocked' ? 'Send a message explaining the blocker, then call listen().'
298
+ : 'Call listen() to receive updates.';
299
+ return { success: true, task_id: task.id, status: task.status, title: task.title, next_action: nextAction };
295
300
  }
296
301
 
297
302
  // --- List Tasks ---
@@ -393,9 +398,9 @@ module.exports = function (ctx) {
393
398
  inputSchema: {
394
399
  type: 'object',
395
400
  properties: {
396
- title: { type: 'string', description: 'Short task title' },
397
- description: { type: 'string', description: 'Detailed task description' },
398
- assignee: { type: 'string', description: 'Agent to assign to (optional, auto-assigns with 2 agents)' },
401
+ title: { type: 'string', description: 'Short task title', maxLength: 200 },
402
+ description: { type: 'string', description: 'Detailed task description', maxLength: 5000 },
403
+ assignee: { type: 'string', description: 'Agent to assign to (optional, auto-assigns with 2 agents)', maxLength: 50 },
399
404
  },
400
405
  required: ['title'],
401
406
  additionalProperties: false,
@@ -407,9 +412,9 @@ module.exports = function (ctx) {
407
412
  inputSchema: {
408
413
  type: 'object',
409
414
  properties: {
410
- task_id: { type: 'string', description: 'Task ID to update' },
415
+ task_id: { type: 'string', description: 'Task ID to update', maxLength: 50 },
411
416
  status: { type: 'string', enum: ['pending', 'in_progress', 'in_review', 'done', 'blocked', 'blocked_permanent'], description: 'New status' },
412
- notes: { type: 'string', description: 'Optional progress note' },
417
+ notes: { type: 'string', description: 'Optional progress note', maxLength: 2000 },
413
418
  },
414
419
  required: ['task_id', 'status'],
415
420
  additionalProperties: false,
@@ -102,7 +102,7 @@ module.exports = function (ctx) {
102
102
  autonomous,
103
103
  parallel,
104
104
  started_steps: startedSteps.map(s => ({ id: s.id, description: s.description, assignee: s.assignee })),
105
- message: autonomous ? 'Autonomous workflow created. All agents should call get_work() to enter the proactive work loop.' : undefined,
105
+ next_action: autonomous ? 'Call get_work() for your assignment.' : 'Call listen() to receive updates.',
106
106
  };
107
107
  }
108
108
 
@@ -179,6 +179,7 @@ module.exports = function (ctx) {
179
179
  next_steps: nextSteps.length > 0 ? nextSteps.map(s => ({ id: s.id, description: s.description, assignee: s.assignee })) : null,
180
180
  progress: `${doneCount}/${wf.steps.length} (${pct}%)`,
181
181
  workflow_status: wf.status,
182
+ next_action: wf.autonomous ? 'Call get_work() for your next assignment.' : 'Call listen() to receive the next step.',
182
183
  };
183
184
  }
184
185