helixmind 0.7.2 → 0.8.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.
Files changed (74) hide show
  1. package/README.md +34 -3
  2. package/dist/cli/agent/loop.d.ts.map +1 -1
  3. package/dist/cli/agent/loop.js +8 -4
  4. package/dist/cli/agent/loop.js.map +1 -1
  5. package/dist/cli/agent/permissions.d.ts +8 -2
  6. package/dist/cli/agent/permissions.d.ts.map +1 -1
  7. package/dist/cli/agent/permissions.js +19 -8
  8. package/dist/cli/agent/permissions.js.map +1 -1
  9. package/dist/cli/agent/tools/bug-list.js +8 -1
  10. package/dist/cli/agent/tools/bug-list.js.map +1 -1
  11. package/dist/cli/agent/tools/git-commit.js +12 -13
  12. package/dist/cli/agent/tools/git-commit.js.map +1 -1
  13. package/dist/cli/agent/tools/git-diff.js +3 -4
  14. package/dist/cli/agent/tools/git-diff.js.map +1 -1
  15. package/dist/cli/agent/tools/git-exec.d.ts +36 -0
  16. package/dist/cli/agent/tools/git-exec.d.ts.map +1 -0
  17. package/dist/cli/agent/tools/git-exec.js +56 -0
  18. package/dist/cli/agent/tools/git-exec.js.map +1 -0
  19. package/dist/cli/agent/tools/git-log.js +4 -5
  20. package/dist/cli/agent/tools/git-log.js.map +1 -1
  21. package/dist/cli/agent/tools/git-status.js +6 -6
  22. package/dist/cli/agent/tools/git-status.js.map +1 -1
  23. package/dist/cli/agent/tools/registry.d.ts +9 -0
  24. package/dist/cli/agent/tools/registry.d.ts.map +1 -1
  25. package/dist/cli/agent/tools/registry.js.map +1 -1
  26. package/dist/cli/agent/tools/run-command.js +22 -0
  27. package/dist/cli/agent/tools/run-command.js.map +1 -1
  28. package/dist/cli/agent/tools/web-research.js +7 -3
  29. package/dist/cli/agent/tools/web-research.js.map +1 -1
  30. package/dist/cli/bugs/journal.d.ts +11 -0
  31. package/dist/cli/bugs/journal.d.ts.map +1 -1
  32. package/dist/cli/bugs/journal.js +67 -2
  33. package/dist/cli/bugs/journal.js.map +1 -1
  34. package/dist/cli/bugs/types.d.ts +2 -0
  35. package/dist/cli/bugs/types.d.ts.map +1 -1
  36. package/dist/cli/commands/chat.d.ts.map +1 -1
  37. package/dist/cli/commands/chat.js +167 -16
  38. package/dist/cli/commands/chat.js.map +1 -1
  39. package/dist/cli/core/stdin-envelope.d.ts +41 -0
  40. package/dist/cli/core/stdin-envelope.d.ts.map +1 -0
  41. package/dist/cli/core/stdin-envelope.js +107 -0
  42. package/dist/cli/core/stdin-envelope.js.map +1 -0
  43. package/dist/cli/jarvis/notifications.js +1 -1
  44. package/dist/cli/jarvis/telemetry.js +1 -1
  45. package/dist/cli/jarvis/types.d.ts +2 -2
  46. package/dist/cli/jarvis/types.d.ts.map +1 -1
  47. package/dist/cli/jarvis/types.js.map +1 -1
  48. package/dist/cli/license/checker.js +1 -1
  49. package/dist/cli/ui/select-menu.d.ts +12 -0
  50. package/dist/cli/ui/select-menu.d.ts.map +1 -1
  51. package/dist/cli/ui/select-menu.js +47 -27
  52. package/dist/cli/ui/select-menu.js.map +1 -1
  53. package/dist/cli/validation/static-checks.d.ts.map +1 -1
  54. package/dist/cli/validation/static-checks.js +62 -3
  55. package/dist/cli/validation/static-checks.js.map +1 -1
  56. package/dist/cli/validation/stats.d.ts.map +1 -1
  57. package/dist/cli/validation/stats.js +5 -3
  58. package/dist/cli/validation/stats.js.map +1 -1
  59. package/dist/spiral/cloud/search-provider.d.ts.map +1 -1
  60. package/dist/spiral/cloud/search-provider.js +29 -1
  61. package/dist/spiral/cloud/search-provider.js.map +1 -1
  62. package/dist/spiral/engine.d.ts +13 -0
  63. package/dist/spiral/engine.d.ts.map +1 -1
  64. package/dist/spiral/engine.js +35 -0
  65. package/dist/spiral/engine.js.map +1 -1
  66. package/dist/spiral/injection.d.ts +13 -0
  67. package/dist/spiral/injection.d.ts.map +1 -1
  68. package/dist/spiral/injection.js +66 -1
  69. package/dist/spiral/injection.js.map +1 -1
  70. package/dist/spiral/provenance.d.ts +37 -0
  71. package/dist/spiral/provenance.d.ts.map +1 -0
  72. package/dist/spiral/provenance.js +167 -0
  73. package/dist/spiral/provenance.js.map +1 -0
  74. package/package.json +2 -2
@@ -3,6 +3,7 @@ import { Writable } from 'node:stream';
3
3
  import { homedir } from 'node:os';
4
4
  import { join, basename } from 'node:path';
5
5
  import { writeFileSync } from 'node:fs';
6
+ import { markPrepended } from '../core/stdin-envelope.js';
6
7
  import { ConfigStore } from '../config/store.js';
7
8
  import { createProvider, registerFreeModel } from '../providers/registry.js';
8
9
  import { registerModelContextLength } from '../providers/model-limits.js';
@@ -517,6 +518,71 @@ export async function chatCommand(options) {
517
518
  const roundToolCounts = new Map();
518
519
  const roundFilesTouched = new Set();
519
520
  let recapEnabled = true; // Can be toggled via /recap
521
+ // audit-016/v0.8.0: brain growth telemetry for the session-exit display.
522
+ // Captured lazily on first spiral access; the diff is rendered before
523
+ // graceful exit so users SEE memory compounding ("+12 patterns learned,
524
+ // 3 stale retired"). The first session shows just the absolute count.
525
+ let brainStartNodes = null;
526
+ let brainStartConfidence = null;
527
+ function captureBrainStart() {
528
+ if (brainStartNodes !== null)
529
+ return;
530
+ try {
531
+ brainStartNodes = spiralEngine?.status?.().total_nodes ?? null;
532
+ }
533
+ catch {
534
+ brainStartNodes = null;
535
+ }
536
+ try {
537
+ brainStartConfidence = jarvisIdentity?.getIdentity?.()?.traits?.confidence ?? null;
538
+ }
539
+ catch {
540
+ brainStartConfidence = null;
541
+ }
542
+ }
543
+ function renderBrainGrowth() {
544
+ if (!spiralEngine)
545
+ return;
546
+ let nodesNow;
547
+ try {
548
+ nodesNow = spiralEngine.status().total_nodes;
549
+ }
550
+ catch {
551
+ return;
552
+ }
553
+ const stale = (() => {
554
+ try {
555
+ return spiralEngine.getStaleSeenLifetime?.() ?? 0;
556
+ }
557
+ catch {
558
+ return 0;
559
+ }
560
+ })();
561
+ let confNow = null;
562
+ try {
563
+ confNow = jarvisIdentity?.getIdentity?.()?.traits?.confidence ?? null;
564
+ }
565
+ catch { /* ignore */ }
566
+ const parts = [];
567
+ if (brainStartNodes !== null) {
568
+ const delta = nodesNow - brainStartNodes;
569
+ if (delta > 0)
570
+ parts.push(chalk.green(`+${delta} pattern${delta !== 1 ? 's' : ''} learned`));
571
+ else if (delta < 0)
572
+ parts.push(chalk.dim(`${delta} compacted`));
573
+ }
574
+ if (stale > 0)
575
+ parts.push(chalk.yellow(`${stale} stale retired`));
576
+ if (confNow !== null && brainStartConfidence !== null && Math.abs(confNow - brainStartConfidence) > 0.001) {
577
+ const delta = confNow - brainStartConfidence;
578
+ const arrow = delta > 0 ? chalk.green('↑') : chalk.red('↓');
579
+ parts.push(`confidence ${brainStartConfidence.toFixed(2)}${arrow}${confNow.toFixed(2)}`);
580
+ }
581
+ if (parts.length === 0) {
582
+ parts.push(chalk.dim(`brain has ${nodesNow} pattern${nodesNow !== 1 ? 's' : ''}`));
583
+ }
584
+ process.stdout.write(chalk.dim(' 🧠 ') + parts.join(chalk.dim(' · ')) + '\n');
585
+ }
520
586
  function recordRoundTool(toolName, input) {
521
587
  roundToolCounts.set(toolName, (roundToolCounts.get(toolName) ?? 0) + 1);
522
588
  const fileTools = new Set(['read_file', 'write_file', 'edit_file']);
@@ -738,6 +804,11 @@ export async function chatCommand(options) {
738
804
  const dataDir = resolveSpiralDir(scope, process.cwd());
739
805
  const spiralConfig = loadSpiralConfig(dataDir);
740
806
  const engine = new SpiralEngine(spiralConfig);
807
+ // audit-014/v0.8.0 "Compounds": pin engine to project root so
808
+ // store() can capture file provenance and the injection layer can
809
+ // detect stale recall. Without this, the [STALE] marking is silent
810
+ // and the compounds feature is effectively disabled.
811
+ engine.setProjectRoot(projectRoot);
741
812
  await engine.initialize();
742
813
  return engine;
743
814
  }
@@ -757,6 +828,8 @@ export async function chatCommand(options) {
757
828
  spiralInitPromise = initSpiralEngine(scope).then(async (engine) => {
758
829
  spiralEngine = engine;
759
830
  spiralInitPromise = null;
831
+ // audit-016: capture session-start metrics for the growth display.
832
+ captureBrainStart();
760
833
  if (engine && brainUrl) {
761
834
  try {
762
835
  const { startLiveBrain } = await import('../brain/generator.js');
@@ -2515,6 +2588,13 @@ export async function chatCommand(options) {
2515
2588
  ctrlCCount++;
2516
2589
  if (ctrlCCount >= 2) {
2517
2590
  process.stdout.write('\n');
2591
+ // audit-016: brain growth display before saving + exit. Single
2592
+ // line, only when a spiral exists, never blocks. Compounding made
2593
+ // visible \u2014 the User-sp\u00fcrbare Pro-tier value moment.
2594
+ try {
2595
+ renderBrainGrowth();
2596
+ }
2597
+ catch { /* never block exit */ }
2518
2598
  renderInfo('Force exit \u2014 saving state...');
2519
2599
  if (spiralEngine) {
2520
2600
  try {
@@ -2681,7 +2761,12 @@ export async function chatCommand(options) {
2681
2761
  {
2682
2762
  let lastRawEscTime = 0;
2683
2763
  const RAW_ESC_THRESHOLD = 800; // ms
2684
- process.stdin.prependListener('data', async (chunk) => {
2764
+ // audit-005: tag the raw ESC detector so any modal-stdin envelope (rewind
2765
+ // browser, plan browser, …) restores it via prependListener instead of
2766
+ // .on(), preserving its first-in-line position. Without the tag, after
2767
+ // the first modal exit readline's data parser could land before the ESC
2768
+ // detector and break double-ESC + bracketed-paste detection.
2769
+ const escDetector = markPrepended(async (chunk) => {
2685
2770
  if (fullScreenBrowserOpen)
2686
2771
  return;
2687
2772
  const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
@@ -2790,6 +2875,7 @@ export async function chatCommand(options) {
2790
2875
  // Everything else (text, arrow keys, etc.) — don't reset timer
2791
2876
  // because arrow key \x1b[A could arrive just after a real ESC press
2792
2877
  });
2878
+ process.stdin.prependListener('data', escDetector);
2793
2879
  async function openRewindBrowser() {
2794
2880
  // Don't re-enter if already open
2795
2881
  if (fullScreenBrowserOpen)
@@ -2820,13 +2906,20 @@ export async function chatCommand(options) {
2820
2906
  // Fullscreen overlay: suspend=false removes stdout hook entirely so the
2821
2907
  // Rewind browser can write directly to the terminal without buffering.
2822
2908
  chrome.deactivate({ suspend: false });
2823
- // CRITICAL: Remove ALL stdin data+keypress listeners to prevent readline's
2824
- // internal handler from buffering keypresses during Rewind navigation.
2825
- // The browser adds its own raw 'data' listener for arrow keys / ESC.
2909
+ // audit-011: snapshot+restore data listeners only. Do NOT touch
2910
+ // 'keypress' listeners Node's readline attaches an internal data
2911
+ // parser when the first keypress listener is added, and removeAll +
2912
+ // re-add can cause it to register a second parser, which makes every
2913
+ // typed character afterwards fire twice ("double-write" symptom).
2914
+ // Keypress listeners are gated by fullScreenBrowserOpen anyway, so
2915
+ // they're safe to leave attached during the modal.
2916
+ //
2917
+ // Restore data listeners with prepend-symmetry: any listener tagged
2918
+ // via markPrepended() (the raw ESC detector at chat.ts:2915) is
2919
+ // re-attached as prepended so readline's data parser cannot land in
2920
+ // front of it after rl.resume().
2826
2921
  const savedDataListeners = process.stdin.rawListeners('data').slice();
2827
- const savedKeypressListeners = process.stdin.rawListeners('keypress').slice();
2828
2922
  process.stdin.removeAllListeners('data');
2829
- process.stdin.removeAllListeners('keypress');
2830
2923
  let didRevertWithMessage = false;
2831
2924
  try {
2832
2925
  try {
@@ -2856,11 +2949,16 @@ export async function chatCommand(options) {
2856
2949
  }
2857
2950
  finally {
2858
2951
  // Guaranteed listener restore, even if the browser or revert threw.
2952
+ // Tagged listeners (raw ESC detector) re-attach as prepended.
2953
+ const PREPENDED = Symbol.for('helixmind.stdin.prepended');
2859
2954
  for (const listener of savedDataListeners) {
2860
- process.stdin.on('data', listener);
2861
- }
2862
- for (const listener of savedKeypressListeners) {
2863
- process.stdin.on('keypress', listener);
2955
+ const isPrepended = Boolean(listener[PREPENDED]);
2956
+ if (isPrepended) {
2957
+ process.stdin.prependListener('data', listener);
2958
+ }
2959
+ else {
2960
+ process.stdin.on('data', listener);
2961
+ }
2864
2962
  }
2865
2963
  if (!didRevertWithMessage) {
2866
2964
  inputMgr.setLine('');
@@ -3706,13 +3804,20 @@ export async function chatCommand(options) {
3706
3804
  fullScreenBrowserOpen = true;
3707
3805
  rl.pause();
3708
3806
  chrome.deactivate({ suspend: false });
3709
- // Isolate stdin: remove all data listeners to prevent readline buffering
3807
+ // audit-011: same envelope as openRewindBrowser. Snapshot only data
3808
+ // listeners; restore with prepend-symmetry for tagged ones.
3710
3809
  const savedPlanListeners = process.stdin.rawListeners('data').slice();
3711
3810
  process.stdin.removeAllListeners('data');
3712
3811
  const { choice } = await runPlanBrowser(plan);
3713
- // Restore stdin listeners
3812
+ const PREPENDED = Symbol.for('helixmind.stdin.prepended');
3714
3813
  for (const listener of savedPlanListeners) {
3715
- process.stdin.on('data', listener);
3814
+ const isPrepended = Boolean(listener[PREPENDED]);
3815
+ if (isPrepended) {
3816
+ process.stdin.prependListener('data', listener);
3817
+ }
3818
+ else {
3819
+ process.stdin.on('data', listener);
3820
+ }
3716
3821
  }
3717
3822
  inputMgr.setLine('');
3718
3823
  chrome.activate();
@@ -3918,7 +4023,27 @@ export async function chatCommand(options) {
3918
4023
  else {
3919
4024
  currentStepFile = '';
3920
4025
  }
3921
- }, jarvisLearning);
4026
+ }, jarvisLearning, undefined, // runtime
4027
+ undefined, // skillManager
4028
+ // audit-006: feed validation outcome into identity calibration.
4029
+ (status, taskCategory) => {
4030
+ if (!jarvisIdentity)
4031
+ return;
4032
+ if (status === 'passed') {
4033
+ jarvisIdentity.recordEvent({
4034
+ type: 'task_completed',
4035
+ taskId: `validation:${taskCategory}`,
4036
+ summary: 'validation passed',
4037
+ });
4038
+ }
4039
+ else if (status === 'errors' || status === 'max_loops') {
4040
+ jarvisIdentity.recordEvent({
4041
+ type: 'task_failed',
4042
+ taskId: `validation:${taskCategory}`,
4043
+ error: `validation ${status}`,
4044
+ });
4045
+ }
4046
+ });
3922
4047
  }
3923
4048
  }
3924
4049
  agentRunning = false;
@@ -4121,6 +4246,13 @@ export async function chatCommand(options) {
4121
4246
  await spiralEngine.saveState(messages);
4122
4247
  }
4123
4248
  catch { /* best effort */ }
4249
+ // audit-016: render the growth line BEFORE close so the spiral is
4250
+ // still queryable. The graceful path (rl close → here) is the
4251
+ // common exit; the force-exit path renders earlier (Ctrl+C×2).
4252
+ try {
4253
+ renderBrainGrowth();
4254
+ }
4255
+ catch { /* never block exit */ }
4124
4256
  spiralEngine.close();
4125
4257
  }
4126
4258
  // Close browser if open
@@ -4157,7 +4289,13 @@ async function runBackgroundSession(session, prompt, provider, project, spiralEn
4157
4289
  durationMs: session.elapsed,
4158
4290
  };
4159
4291
  }
4160
- async function sendAgentMessage(input, agentHistory, provider, project, spiralEngine, config, permissions, undoStack, checkpointStore, controller, activity, sessionBuffer, onTokens, onToolCall, onAgentStart, validationOpts, bugJournal, browserController, visionProcessor, onBrowserScreenshot, jarvisContext, onStepInfo, learningJournal, runtime, skillManager) {
4292
+ async function sendAgentMessage(input, agentHistory, provider, project, spiralEngine, config, permissions, undoStack, checkpointStore, controller, activity, sessionBuffer, onTokens, onToolCall, onAgentStart, validationOpts, bugJournal, browserController, visionProcessor, onBrowserScreenshot, jarvisContext, onStepInfo, learningJournal, runtime, skillManager,
4293
+ // audit-006: optional callback fired once per validation run. Used by the
4294
+ // foreground chat path to feed the result into JarvisIdentityManager so
4295
+ // confidence calibration actually updates after successful agent turns.
4296
+ // Background-session callers leave this undefined so they do not pollute
4297
+ // the user's confidence score with their own outcomes.
4298
+ onValidationComplete) {
4161
4299
  // User message was rendered by renderUserMessage() in the caller before entering here.
4162
4300
  const executionRoot = runtime?.executionRoot ?? process.cwd();
4163
4301
  // Intent Detection: Check if user wants to feed the codebase
@@ -4345,6 +4483,19 @@ async function sendAgentMessage(input, agentHistory, provider, project, spiralEn
4345
4483
  process.stdout.write(renderValidationSummary(valResult, validationOpts.verbose) + '\n');
4346
4484
  // Store stats in spiral
4347
4485
  await storeValidationResult(valResult, classification.category, spiralEngine || undefined);
4486
+ // audit-006: bridge validation outcome into the identity
4487
+ // calibration layer. Previously the confidence score was
4488
+ // initialized at 0.5 and only nudged by proposal approvals and
4489
+ // the autonomous-task scheduler; the regular agent turn that
4490
+ // ran validation never updated it. This is why the user-visible
4491
+ // confidence stayed stuck at 0.52 across many sessions despite
4492
+ // successful fixes. We treat 'passed' as task_completed and
4493
+ // 'errors'/'max_loops' as task_failed; 'warnings' is neutral
4494
+ // (do not penalize).
4495
+ try {
4496
+ onValidationComplete?.(valResult.status, classification.category);
4497
+ }
4498
+ catch { /* never block on a calibration callback */ }
4348
4499
  }
4349
4500
  }
4350
4501
  }
@@ -5831,7 +5982,7 @@ async function handleSlashCommand(input, messages, agentHistory, config, spiralE
5831
5982
  cancelLabel: 'Cancel',
5832
5983
  });
5833
5984
  if (modeIdx === docsItemIdx) {
5834
- const docsUrl = 'https://helixmind.dev/docs/security-monitor';
5985
+ const docsUrl = 'https://helix-mind.ai/docs/security-monitor';
5835
5986
  const { exec } = await import('node:child_process');
5836
5987
  const { platform } = await import('node:os');
5837
5988
  const openCmd = platform() === 'win32' ? `start "" "${docsUrl}"`