@albinocrabs/feynman 0.2.4 → 0.2.5

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feynman",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Auto-inject ASCII diagram rules into Codex and Claude Code prompts.",
5
5
  "author": {
6
6
  "name": "apolenkov",
package/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  All notable changes to this project are documented here.
4
4
 
5
+ ## 0.2.5 - 2026-05-08
6
+
7
+ ### Features
8
+
9
+ - add SessionStart hook priming alongside UserPromptSubmit reinforcement
10
+ - keep full diagram rules enabled by default for Claude Code and Codex
11
+
12
+ ### Fixes
13
+
14
+ - preserve explicit /feynman off behavior with silent hooks
15
+ - keep feynman hooks quiet by omitting status messages
16
+
17
+ ### Maintenance
18
+
19
+ - bump package and plugin manifest versions to 0.2.5
20
+ - expand tests for SessionStart registration and runtime behavior
21
+
5
22
  ## 0.2.4 - 2026-05-07
6
23
 
7
24
  Changes since v0.2.2.
package/README.md CHANGED
@@ -199,8 +199,7 @@ Add to `~/.claude/settings.json` — use the absolute path, not `~/`
199
199
  {
200
200
  "type": "command",
201
201
  "command": "node \"/absolute/path/to/feynman/hooks/feynman-activate.js\"",
202
- "timeout": 5,
203
- "statusMessage": "Injecting diagram rules..."
202
+ "timeout": 5
204
203
  }
205
204
  ]
206
205
  }
@@ -221,8 +220,7 @@ For Codex, add the same shape to `~/.codex/hooks.json` and set
221
220
  {
222
221
  "type": "command",
223
222
  "command": "FEYNMAN_HOME=\"$HOME/.codex\" node \"/absolute/path/to/feynman/hooks/feynman-activate.js\"",
224
- "timeout": 5,
225
- "statusMessage": "Injecting diagram rules..."
223
+ "timeout": 5
226
224
  }
227
225
  ]
228
226
  }
@@ -232,6 +230,10 @@ For Codex, add the same shape to `~/.codex/hooks.json` and set
232
230
  ```
233
231
  </details>
234
232
 
233
+ After install, feynman starts in `full` mode by default. Disable or change it
234
+ explicitly with `/feynman off`, `/feynman lite`, `/feynman full`, or
235
+ `/feynman ultra`.
236
+
235
237
  ## Intensity Levels
236
238
 
237
239
  | Level | What draws | Use when |
@@ -481,11 +483,11 @@ from day one.
481
483
 
482
484
  ## How it works
483
485
 
484
- The `UserPromptSubmit` hook fires on every Claude Code or Codex prompt. The
485
- hook reads the target-local state file (`~/.claude/.feynman/state.json` or
486
- `~/.codex/.feynman/state.json`), extracts the rules for the active intensity
487
- level, and injects them as `additionalContext` — invisible to you, visible to
488
- the model on every message.
486
+ The `SessionStart` hook primes fresh Claude Code or Codex sessions with the
487
+ active rules, and the `UserPromptSubmit` hook reinforces them on every prompt.
488
+ Both hooks read the target-local state file
489
+ (`~/.claude/.feynman/state.json` or `~/.codex/.feynman/state.json`), extract
490
+ the rules for the active intensity level, and inject them into model context.
489
491
 
490
492
  ```
491
493
  [your prompt]
package/bin/feynman.js CHANGED
@@ -29,6 +29,7 @@ const HOME = os.homedir();
29
29
 
30
30
  // Hook script lives relative to this file
31
31
  const HOOK_PATH = path.resolve(__dirname, '..', 'hooks', 'feynman-activate.js');
32
+ const SESSION_HOOK_PATH = path.resolve(__dirname, '..', 'hooks', 'feynman-session-start.js');
32
33
  const RULES_PATH = path.resolve(__dirname, '..', 'rules', 'feynman-activate.md');
33
34
 
34
35
  const DEFAULT_STATE = { enabled: true, intensity: 'full', injections: 0 };
@@ -425,11 +426,11 @@ Claude creates:
425
426
  ~/.claude/.feynman-active — presence flag
426
427
 
427
428
  Codex creates:
428
- ~/.codex/hooks.json — UserPromptSubmit hook registration
429
+ ~/.codex/hooks.json — SessionStart + UserPromptSubmit hook registration
429
430
  ~/.codex/.feynman/state.json — feynman state (enabled, intensity, injections)
430
431
  ~/.codex/.feynman-active — presence flag
431
432
 
432
- Idempotent by default: skips if feynman-activate.js entry already exists.
433
+ Idempotent by default: skips if feynman hook entries already exist.
433
434
  `;
434
435
 
435
436
  const UNINSTALL_HELP = `${c.bold('feynman uninstall')} — remove feynman hook
@@ -451,11 +452,11 @@ ${c.bold('Usage:')}
451
452
 
452
453
  Checks:
453
454
  1. target hook config present
454
- 2. UserPromptSubmit hook references feynman-activate.js
455
- 3. Hook script file exists and is readable
455
+ 2. SessionStart and UserPromptSubmit hooks reference feynman scripts
456
+ 3. Hook script files exist and are readable
456
457
  4. Rules file exists and is non-empty
457
458
  5. state.json valid JSON with enabled field
458
- 6. .feynman-active flag present
459
+ 6. .feynman-active flag matches enabled state
459
460
  7. (INFO) lint hook registered (optional)
460
461
 
461
462
  Exit code: always 0 (advisory only).
@@ -507,23 +508,31 @@ function writeSettings(target, settings) {
507
508
  }
508
509
 
509
510
  function hasFeynmanHook(settings) {
510
- return ((settings.hooks && settings.hooks.UserPromptSubmit) || []).some(g =>
511
+ const promptHook = ((settings.hooks && settings.hooks.UserPromptSubmit) || []).some(g =>
511
512
  g.hooks && g.hooks.some(h => h.command && h.command.includes('feynman-activate.js'))
512
513
  );
514
+ const sessionHook = ((settings.hooks && settings.hooks.SessionStart) || []).some(g =>
515
+ g.hooks && g.hooks.some(h => h.command && h.command.includes('feynman-session-start.js'))
516
+ );
517
+ return promptHook && sessionHook;
513
518
  }
514
519
 
515
520
  function removeFeynmanHooks(settings) {
516
- if (!settings.hooks || !Array.isArray(settings.hooks.UserPromptSubmit)) return settings;
517
- settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter(g =>
518
- !(g.hooks && g.hooks.some(h =>
519
- h.command && (
520
- h.command.includes('feynman-activate.js') ||
521
- h.command.includes('feynman-lint.js')
522
- )
523
- ))
524
- );
525
- if (settings.hooks.UserPromptSubmit.length === 0) {
526
- delete settings.hooks.UserPromptSubmit;
521
+ if (!settings.hooks) return settings;
522
+ for (const eventName of ['SessionStart', 'UserPromptSubmit', 'Stop']) {
523
+ if (!Array.isArray(settings.hooks[eventName])) continue;
524
+ settings.hooks[eventName] = settings.hooks[eventName].filter(g =>
525
+ !(g.hooks && g.hooks.some(h =>
526
+ h.command && (
527
+ h.command.includes('feynman-session-start.js') ||
528
+ h.command.includes('feynman-activate.js') ||
529
+ h.command.includes('feynman-lint.js')
530
+ )
531
+ ))
532
+ );
533
+ if (settings.hooks[eventName].length === 0) {
534
+ delete settings.hooks[eventName];
535
+ }
527
536
  }
528
537
  if (Object.keys(settings.hooks).length === 0) {
529
538
  delete settings.hooks;
@@ -534,10 +543,21 @@ function removeFeynmanHooks(settings) {
534
543
  function bootstrapState(target) {
535
544
  const cfg = targetConfig(target);
536
545
  fs.mkdirSync(cfg.feynmanDir, { recursive: true });
546
+ let state = DEFAULT_STATE;
537
547
  if (!fs.existsSync(cfg.statePath)) {
538
548
  fs.writeFileSync(cfg.statePath, JSON.stringify(DEFAULT_STATE, null, 2) + '\n');
549
+ } else {
550
+ try {
551
+ state = { ...DEFAULT_STATE, ...JSON.parse(fs.readFileSync(cfg.statePath, 'utf8')) };
552
+ } catch (_) {
553
+ fs.writeFileSync(cfg.statePath, JSON.stringify(DEFAULT_STATE, null, 2) + '\n');
554
+ }
555
+ }
556
+ if (state.enabled) {
557
+ fs.writeFileSync(cfg.flagPath, state.intensity || DEFAULT_STATE.intensity);
558
+ } else if (fs.existsSync(cfg.flagPath)) {
559
+ fs.unlinkSync(cfg.flagPath);
539
560
  }
540
- fs.writeFileSync(cfg.flagPath, DEFAULT_STATE.intensity);
541
561
  }
542
562
 
543
563
  function installClaudeCommand() {
@@ -561,6 +581,7 @@ function installOne(target, opts) {
561
581
  // Read or create settings
562
582
  const cfg = readSettings(target);
563
583
  cfg.hooks = cfg.hooks || {};
584
+ cfg.hooks.SessionStart = cfg.hooks.SessionStart || [];
564
585
  cfg.hooks.UserPromptSubmit = cfg.hooks.UserPromptSubmit || [];
565
586
 
566
587
  // Idempotency check
@@ -572,20 +593,36 @@ function installOne(target, opts) {
572
593
  return { target, already: true };
573
594
  }
574
595
 
575
- // If force + already, remove old entry first
596
+ // If force or partial legacy install, remove old feynman entries first.
576
597
  if (already && force) {
577
598
  removeFeynmanHooks(cfg);
578
599
  cfg.hooks = cfg.hooks || {};
600
+ cfg.hooks.SessionStart = cfg.hooks.SessionStart || [];
601
+ cfg.hooks.UserPromptSubmit = cfg.hooks.UserPromptSubmit || [];
602
+ } else if (!already) {
603
+ removeFeynmanHooks(cfg);
604
+ cfg.hooks = cfg.hooks || {};
605
+ cfg.hooks.SessionStart = cfg.hooks.SessionStart || [];
579
606
  cfg.hooks.UserPromptSubmit = cfg.hooks.UserPromptSubmit || [];
580
607
  }
581
608
 
582
- // Append hook entry
609
+ // Append hook entries
610
+ const sessionEntry = {
611
+ hooks: [{
612
+ type: 'command',
613
+ command: hookCommandFor(target).replace(HOOK_PATH, SESSION_HOOK_PATH),
614
+ timeout: 5,
615
+ }]
616
+ };
617
+ if (target === 'codex') {
618
+ sessionEntry.matcher = 'startup|resume';
619
+ }
620
+ cfg.hooks.SessionStart.push(sessionEntry);
583
621
  cfg.hooks.UserPromptSubmit.push({
584
622
  hooks: [{
585
623
  type: 'command',
586
624
  command: hookCommandFor(target),
587
625
  timeout: 5,
588
- statusMessage: 'Injecting diagram rules...',
589
626
  }]
590
627
  });
591
628
 
@@ -623,7 +660,7 @@ function cmdInstall(opts) {
623
660
  }
624
661
  console.log('└──────────────────────────────────────────────────────────────┘');
625
662
  console.log('');
626
- console.log('Restart Claude Code or Codex to activate feynman.');
663
+ console.log('Restart Claude Code or Codex to activate feynman full mode.');
627
664
 
628
665
  process.exit(0);
629
666
  }
@@ -684,11 +721,24 @@ function cmdDoctor(opts = {}) {
684
721
  const settingsExists = fs.existsSync(tc.settingsPath);
685
722
  check(`${tc.settingsPath.replace(HOME, '~')} present`, settingsExists);
686
723
 
687
- // 2. UserPromptSubmit hook references feynman-activate.js
724
+ // 2. SessionStart and UserPromptSubmit hooks reference feynman scripts
725
+ let sessionHookRegistered = false;
688
726
  let hookRegistered = false;
727
+ let sessionHookAbsPath = null;
689
728
  let hookAbsPath = null;
690
729
  if (settingsExists) {
691
730
  const cfg = readSettings(target);
731
+ const sessionEntries = (cfg.hooks && cfg.hooks.SessionStart) || [];
732
+ const feynmanSessionEntry = sessionEntries.find(g =>
733
+ g.hooks && g.hooks.some(h => h.command && h.command.includes('feynman-session-start.js'))
734
+ );
735
+ sessionHookRegistered = !!feynmanSessionEntry;
736
+ if (feynmanSessionEntry) {
737
+ const hookCmd = feynmanSessionEntry.hooks.find(h => h.command && h.command.includes('feynman-session-start.js')).command;
738
+ const match = hookCmd.match(/"([^"]+feynman-session-start\.js)"/);
739
+ if (match) sessionHookAbsPath = match[1];
740
+ }
741
+
692
742
  const entries = (cfg.hooks && cfg.hooks.UserPromptSubmit) || [];
693
743
  const feynmanEntry = entries.find(g =>
694
744
  g.hooks && g.hooks.some(h => h.command && h.command.includes('feynman-activate.js'))
@@ -701,9 +751,24 @@ function cmdDoctor(opts = {}) {
701
751
  if (match) hookAbsPath = match[1];
702
752
  }
703
753
  }
754
+ check('hook registered (feynman-session-start.js in SessionStart)', sessionHookRegistered);
704
755
  check('hook registered (feynman-activate.js in UserPromptSubmit)', hookRegistered);
705
756
 
706
- // 3. Hook script file exists + readable
757
+ // 3. Hook script files exist + readable
758
+ let sessionHookFileOk = false;
759
+ if (sessionHookAbsPath) {
760
+ try {
761
+ fs.accessSync(sessionHookAbsPath, fs.constants.R_OK);
762
+ sessionHookFileOk = true;
763
+ } catch (_) {}
764
+ } else if (sessionHookRegistered) {
765
+ try {
766
+ fs.accessSync(SESSION_HOOK_PATH, fs.constants.R_OK);
767
+ sessionHookFileOk = true;
768
+ } catch (_) {}
769
+ }
770
+ check('session hook script file exists and is readable', sessionHookFileOk);
771
+
707
772
  let hookFileOk = false;
708
773
  if (hookAbsPath) {
709
774
  try {
@@ -717,7 +782,7 @@ function cmdDoctor(opts = {}) {
717
782
  hookFileOk = true;
718
783
  } catch (_) {}
719
784
  }
720
- check('hook script file exists and is readable', hookFileOk);
785
+ check('prompt hook script file exists and is readable', hookFileOk);
721
786
 
722
787
  // 4. Rules file exists + non-empty
723
788
  let rulesOk = false;
@@ -729,15 +794,20 @@ function cmdDoctor(opts = {}) {
729
794
 
730
795
  // 5. state.json valid JSON + has enabled field
731
796
  let stateOk = false;
797
+ let stateEnabled = false;
732
798
  try {
733
799
  const state = JSON.parse(fs.readFileSync(tc.statePath, 'utf8'));
734
800
  stateOk = 'enabled' in state;
801
+ stateEnabled = state.enabled === true;
735
802
  } catch (_) {}
736
803
  check('state.json valid (has enabled field)', stateOk);
737
804
 
738
- // 6. .feynman-active flag present
805
+ // 6. .feynman-active flag matches state
739
806
  const flagPresent = fs.existsSync(tc.flagPath);
740
- check('.feynman-active flag present', flagPresent);
807
+ check(
808
+ stateEnabled ? '.feynman-active flag present when enabled' : '.feynman-active flag absent when disabled',
809
+ stateEnabled ? flagPresent : !flagPresent
810
+ );
741
811
 
742
812
  // 7. (INFO) lint hook registered
743
813
  let lintHookRegistered = false;
@@ -6,16 +6,18 @@ Three independent layers: hook lifecycle, lint pipeline, and state schema.
6
6
 
7
7
  ## Layer 1: Hook Lifecycle
8
8
 
9
- The `UserPromptSubmit` hook fires before every Claude Code or Codex prompt.
10
- feynman intercepts the event, reads the active rules, and injects them as
11
- `additionalContext`.
9
+ The `SessionStart` hook primes fresh Claude Code or Codex sessions with the
10
+ active rules. The `UserPromptSubmit` hook fires before every prompt and
11
+ reinforces the same rules as `additionalContext`.
12
12
 
13
13
  ```
14
14
  ~/.claude/settings.json ~/.codex/hooks.json
15
15
 
16
- hooks.UserPromptSubmit fires on every prompt
16
+ ├─ hooks.SessionStart primes new sessions
17
+
18
+ └─ hooks.UserPromptSubmit reinforces every prompt
17
19
 
18
- hooks/feynman-activate.js
20
+ hooks/feynman-session-start.js + hooks/feynman-activate.js
19
21
 
20
22
  ├─ [0] FEYNMAN_HOME selects client state root
21
23
  │ unset → ~/.claude (backward compatible)
@@ -25,9 +27,10 @@ hooks/feynman-activate.js
25
27
  ├─ [1] validate session_id (path-traversal guard)
26
28
 
27
29
  ├─ [2] $FEYNMAN_HOME/.feynman-active ← flag file
28
- │ absent + no state.json → bootstrap first run
29
- │ absent + state.json exit 0 (user disabled)
30
- present continue
30
+ │ absent + no state.json → bootstrap first run
31
+ │ absent + state.enabled=true recreate flag
32
+ absent + state.enabled=false exit 0 (user disabled)
33
+ │ present → continue
31
34
 
32
35
  ├─ [3] $FEYNMAN_HOME/.feynman/state.json
33
36
  │ enabled: false → exit 0
@@ -121,7 +124,7 @@ All runtime state lives in two files under the selected client root:
121
124
  ~/.claude/ or ~/.codex/
122
125
  ├── .feynman-active ← presence flag
123
126
  │ present = feynman active
124
- │ absent = user disabled (state.json preserved)
127
+ │ absent = user disabled only when state.enabled=false
125
128
  │ content = current intensity string (informational)
126
129
 
127
130
  └── .feynman/
@@ -31,18 +31,25 @@ process.stdin.on('end', () => {
31
31
  if (sessionId && /[/\\]|\.\./.test(sessionId)) process.exit(0);
32
32
 
33
33
  // Step 2: flag file + first-run bootstrap (D-05, D-07, bug #35713)
34
- // True first run: neither flag nor state exists bootstrap both, then fall through
35
- // Intentionally disabled: flag absent but state exists exit 0 (user ran /feynman off)
34
+ // True first run: neither flag nor state exists -> bootstrap default full mode.
35
+ // Intentionally disabled: flag absent + state.enabled=false -> exit 0.
36
36
  const flagExists = fs.existsSync(FLAG_PATH);
37
37
  const stateExists = fs.existsSync(STATE_PATH);
38
38
  if (!flagExists) {
39
39
  if (!stateExists) {
40
- // First install bootstrap everything and activate
40
+ // First install: bootstrap and activate full mode.
41
41
  fs.mkdirSync(FEYNMAN_DIR, { recursive: true });
42
42
  fs.writeFileSync(STATE_PATH, JSON.stringify(DEFAULT_STATE, null, 2));
43
43
  fs.writeFileSync(FLAG_PATH, DEFAULT_STATE.intensity);
44
44
  } else {
45
- process.exit(0); // disabled intentionally by user
45
+ let existingState;
46
+ try {
47
+ existingState = { ...DEFAULT_STATE, ...JSON.parse(fs.readFileSync(STATE_PATH, 'utf8')) };
48
+ } catch (_) {
49
+ process.exit(0);
50
+ }
51
+ if (!existingState.enabled) process.exit(0);
52
+ fs.writeFileSync(FLAG_PATH, existingState.intensity || DEFAULT_STATE.intensity);
46
53
  }
47
54
  }
48
55
 
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ // feynman — SessionStart hook — injects active diagram rules at session start.
3
+ // UserPromptSubmit still reinforces rules every turn; this primes fresh sessions.
4
+ 'use strict';
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const os = require('os');
9
+
10
+ const HOME = os.homedir();
11
+ const CLIENT_HOME = process.env.FEYNMAN_HOME || path.join(HOME, '.claude');
12
+ const FEYNMAN_DIR = path.join(CLIENT_HOME, '.feynman');
13
+ const STATE_PATH = path.join(FEYNMAN_DIR, 'state.json');
14
+ const FLAG_PATH = path.join(CLIENT_HOME, '.feynman-active');
15
+ const RULES_PATH = path.join(__dirname, '..', 'rules', 'feynman-activate.md');
16
+
17
+ const DEFAULT_STATE = { enabled: true, intensity: 'full', injections: 0 };
18
+ const VALID_INTENSITIES = ['lite', 'full', 'ultra'];
19
+
20
+ function readState() {
21
+ try {
22
+ return { ...DEFAULT_STATE, ...JSON.parse(fs.readFileSync(STATE_PATH, 'utf8')) };
23
+ } catch (_) {
24
+ return { ...DEFAULT_STATE };
25
+ }
26
+ }
27
+
28
+ function writeState(state) {
29
+ fs.mkdirSync(FEYNMAN_DIR, { recursive: true });
30
+ fs.writeFileSync(STATE_PATH, JSON.stringify(state, null, 2));
31
+ }
32
+
33
+ function readRules(intensity) {
34
+ const rulesContent = fs.readFileSync(RULES_PATH, 'utf8');
35
+ const selected = VALID_INTENSITIES.includes(intensity) ? intensity : 'full';
36
+ const openMarker = '<!-- ' + selected + ' -->';
37
+ const closeMarker = '<!-- /' + selected + ' -->';
38
+ const i1 = rulesContent.indexOf(openMarker);
39
+ const i2 = rulesContent.indexOf(closeMarker, i1);
40
+ if (i1 === -1 || i2 === -1) return '';
41
+ return rulesContent.slice(i1 + openMarker.length, i2).trim();
42
+ }
43
+
44
+ let input = '';
45
+ process.stdin.on('data', chunk => { input += chunk; });
46
+ process.stdin.on('end', () => {
47
+ try {
48
+ if (input.trim()) {
49
+ const data = JSON.parse(input);
50
+ const sessionId = data.session_id || '';
51
+ if (sessionId && /[/\\]|\.\./.test(sessionId)) process.exit(0);
52
+ }
53
+
54
+ const stateExists = fs.existsSync(STATE_PATH);
55
+ const flagExists = fs.existsSync(FLAG_PATH);
56
+ const state = readState();
57
+
58
+ if (!stateExists) {
59
+ writeState(state);
60
+ }
61
+
62
+ if (!state.enabled) {
63
+ try { fs.unlinkSync(FLAG_PATH); } catch (_) {}
64
+ process.exit(0);
65
+ }
66
+
67
+ if (!flagExists) {
68
+ fs.writeFileSync(FLAG_PATH, state.intensity || DEFAULT_STATE.intensity);
69
+ }
70
+
71
+ const rulesText = readRules(state.intensity);
72
+ if (!rulesText) process.exit(0);
73
+
74
+ // SessionStart accepts plain stdout as context, matching caveman's hook shape.
75
+ process.stdout.write(rulesText);
76
+ } catch (_) {
77
+ process.exit(0);
78
+ }
79
+ });
package/hooks/hooks.json CHANGED
@@ -1,14 +1,24 @@
1
1
  {
2
2
  "description": "Inject feynman ASCII diagram rules before each Claude Code prompt.",
3
3
  "hooks": {
4
+ "SessionStart": [
5
+ {
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "FEYNMAN_HOME=\"$HOME/.claude\" node \"${CLAUDE_PLUGIN_ROOT}/hooks/feynman-session-start.js\"",
10
+ "timeout": 5
11
+ }
12
+ ]
13
+ }
14
+ ],
4
15
  "UserPromptSubmit": [
5
16
  {
6
17
  "hooks": [
7
18
  {
8
19
  "type": "command",
9
20
  "command": "FEYNMAN_HOME=\"$HOME/.claude\" node \"${CLAUDE_PLUGIN_ROOT}/hooks/feynman-activate.js\"",
10
- "timeout": 5,
11
- "statusMessage": "Injecting diagram rules..."
21
+ "timeout": 5
12
22
  }
13
23
  ]
14
24
  }
package/hooks.json CHANGED
@@ -1,13 +1,24 @@
1
1
  {
2
2
  "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "matcher": "startup|resume",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "PLUGIN_ROOT=\"${PLUGIN_ROOT:-$CLAUDE_PLUGIN_ROOT}\"; FEYNMAN_HOME=\"$HOME/.codex\" node \"$PLUGIN_ROOT/hooks/feynman-session-start.js\"",
10
+ "timeout": 5
11
+ }
12
+ ]
13
+ }
14
+ ],
3
15
  "UserPromptSubmit": [
4
16
  {
5
17
  "hooks": [
6
18
  {
7
19
  "type": "command",
8
20
  "command": "PLUGIN_ROOT=\"${PLUGIN_ROOT:-$CLAUDE_PLUGIN_ROOT}\"; FEYNMAN_HOME=\"$HOME/.codex\" node \"$PLUGIN_ROOT/hooks/feynman-activate.js\"",
9
- "timeout": 5,
10
- "statusMessage": "Injecting diagram rules..."
21
+ "timeout": 5
11
22
  }
12
23
  ]
13
24
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@albinocrabs/feynman",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Claude Code and Codex plugin that auto-injects ASCII diagram rules into every AI request via UserPromptSubmit hook",
5
5
  "license": "MIT",
6
6
  "author": "apolenkov",
@@ -28,7 +28,7 @@ node -e "
28
28
  const fs = require('fs'), os = require('os'), path = require('path');
29
29
  const stateFile = path.join(os.homedir(), '.claude', '.feynman', 'state.json');
30
30
  const flagFile = path.join(os.homedir(), '.claude', '.feynman-active');
31
- let st = {enabled: false, intensity: 'full', injections: 0};
31
+ let st = {enabled: true, intensity: 'full', injections: 0};
32
32
  try { st = JSON.parse(fs.readFileSync(stateFile, 'utf8')); } catch(e) {}
33
33
  console.log('enabled:', st.enabled, '| intensity:', st.intensity, '| injections:', (st.injections ?? st.count ?? 0), '| flag:', fs.existsSync(flagFile));
34
34
  "
@@ -43,7 +43,7 @@ const stateFile = path.join(os.homedir(), '.claude', '.feynman', 'state.json');
43
43
  const flagFile = path.join(os.homedir(), '.claude', '.feynman-active');
44
44
  const arg = (process.argv[1] || '').trim().toLowerCase();
45
45
  const normalized = arg === 'start' ? 'on' : arg === 'stop' ? 'off' : arg;
46
- let st = {enabled: true, intensity: 'full', injections: 0};
46
+ let st = {enabled: false, intensity: 'full', injections: 0};
47
47
  try { st = JSON.parse(fs.readFileSync(stateFile, 'utf8')); } catch(e) {}
48
48
  if (normalized === 'on') { st.enabled = true; fs.writeFileSync(flagFile, st.intensity); }
49
49
  if (normalized === 'off') { st.enabled = false; try { fs.unlinkSync(flagFile); } catch(e) {} }