@albinocrabs/feynman 0.2.4 → 0.2.6
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/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +47 -0
- package/README.md +125 -19
- package/SECURITY.md +1 -1
- package/bin/feynman.js +123 -38
- package/docs/architecture.md +12 -9
- package/hooks/feynman-activate.js +11 -4
- package/hooks/feynman-session-start.js +78 -0
- package/hooks/hooks.json +12 -2
- package/hooks.json +13 -2
- package/package.json +1 -1
- package/rules/feynman-activate.md +224 -1
- package/skills/feynman/SKILL.md +31 -10
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.2.6 - 2026-05-08
|
|
6
|
+
|
|
7
|
+
Changes since v0.2.5.
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- harden feynman runtime checks
|
|
12
|
+
- add roadmap phase uat risk patterns
|
|
13
|
+
- add sdlc output patterns to feynman
|
|
14
|
+
- add terminal-safe feynman rendering rules
|
|
15
|
+
|
|
16
|
+
### Fixes
|
|
17
|
+
|
|
18
|
+
- clarify terminal table rendering rules
|
|
19
|
+
- harden feynman hook lifecycle
|
|
20
|
+
|
|
21
|
+
### Documentation
|
|
22
|
+
|
|
23
|
+
- restore GSD validation coverage
|
|
24
|
+
|
|
25
|
+
### Tests
|
|
26
|
+
|
|
27
|
+
- cover codex app-server hook visibility
|
|
28
|
+
- cover ci threshold branches
|
|
29
|
+
|
|
30
|
+
### Maintenance
|
|
31
|
+
|
|
32
|
+
- use global gsd defaults
|
|
33
|
+
- remove gsd model bindings from repo config
|
|
34
|
+
|
|
35
|
+
## 0.2.5 - 2026-05-08
|
|
36
|
+
|
|
37
|
+
### Features
|
|
38
|
+
|
|
39
|
+
- add SessionStart hook priming alongside UserPromptSubmit reinforcement
|
|
40
|
+
- keep full diagram rules enabled by default for Claude Code and Codex
|
|
41
|
+
|
|
42
|
+
### Fixes
|
|
43
|
+
|
|
44
|
+
- preserve explicit /feynman off behavior with silent hooks
|
|
45
|
+
- keep feynman hooks quiet by omitting status messages
|
|
46
|
+
|
|
47
|
+
### Maintenance
|
|
48
|
+
|
|
49
|
+
- bump package and plugin manifest versions to 0.2.5
|
|
50
|
+
- expand tests for SessionStart registration and runtime behavior
|
|
51
|
+
|
|
5
52
|
## 0.2.4 - 2026-05-07
|
|
6
53
|
|
|
7
54
|
Changes since v0.2.2.
|
package/README.md
CHANGED
|
@@ -22,7 +22,10 @@
|
|
|
22
22
|
<a href="docs/object-passport.md">Passport</a> •
|
|
23
23
|
<a href="#before--after">Before/After</a> •
|
|
24
24
|
<a href="#install">Install</a> •
|
|
25
|
+
<a href="#verify-the-install">Verify</a> •
|
|
25
26
|
<a href="#intensity-levels">Levels</a> •
|
|
27
|
+
<a href="#security-notes">Security</a> •
|
|
28
|
+
<a href="#token-budget-and-output-size">Tokens</a> •
|
|
26
29
|
<a href="#lint">Lint</a> •
|
|
27
30
|
<a href="#examples">Examples</a> •
|
|
28
31
|
<a href="docs/launch.md">Launch</a> •
|
|
@@ -32,8 +35,8 @@
|
|
|
32
35
|
---
|
|
33
36
|
|
|
34
37
|
A [Claude Code](https://docs.anthropic.com/en/docs/claude-code) and Codex
|
|
35
|
-
plugin that automatically injects ASCII diagram rules
|
|
36
|
-
`UserPromptSubmit`
|
|
38
|
+
plugin that automatically injects ASCII diagram rules through `SessionStart`
|
|
39
|
+
and `UserPromptSubmit` hooks.
|
|
37
40
|
|
|
38
41
|
```bash
|
|
39
42
|
npx -y @albinocrabs/feynman@latest install --target all
|
|
@@ -173,16 +176,15 @@ feynman hook instead of adding duplicates.
|
|
|
173
176
|
git clone https://github.com/apolenkov/feynman && bash feynman/install.sh
|
|
174
177
|
```
|
|
175
178
|
|
|
176
|
-
Restart Claude Code or Codex
|
|
177
|
-
|
|
178
|
-
**Verify:** `npx @albinocrabs/feynman doctor --target claude` or `npx @albinocrabs/feynman doctor --target codex`
|
|
179
|
+
Restart Claude Code or Codex after install so a fresh `SessionStart` hook can
|
|
180
|
+
prime the session.
|
|
179
181
|
|
|
180
182
|
**Uninstall:** `npx @albinocrabs/feynman uninstall --target claude|codex|both|all|*`
|
|
181
183
|
|
|
182
184
|
**Plugin manifests:** this repo also ships `.claude-plugin/plugin.json`,
|
|
183
|
-
`hooks/hooks.json`, `.codex-plugin/plugin.json`, and `hooks.json`
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
`hooks/hooks.json`, `.codex-plugin/plugin.json`, and `hooks.json` for direct
|
|
186
|
+
client integrations. The npx installer remains the production fallback because
|
|
187
|
+
both clients still support direct user hook registration.
|
|
186
188
|
|
|
187
189
|
<details>
|
|
188
190
|
<summary>Manual install</summary>
|
|
@@ -193,14 +195,25 @@ Add to `~/.claude/settings.json` — use the absolute path, not `~/`
|
|
|
193
195
|
```json
|
|
194
196
|
{
|
|
195
197
|
"hooks": {
|
|
198
|
+
"SessionStart": [
|
|
199
|
+
{
|
|
200
|
+
"matcher": "startup|resume",
|
|
201
|
+
"hooks": [
|
|
202
|
+
{
|
|
203
|
+
"type": "command",
|
|
204
|
+
"command": "node \"/absolute/path/to/feynman/hooks/feynman-session-start.js\"",
|
|
205
|
+
"timeout": 5
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
],
|
|
196
210
|
"UserPromptSubmit": [
|
|
197
211
|
{
|
|
198
212
|
"hooks": [
|
|
199
213
|
{
|
|
200
214
|
"type": "command",
|
|
201
215
|
"command": "node \"/absolute/path/to/feynman/hooks/feynman-activate.js\"",
|
|
202
|
-
"timeout": 5
|
|
203
|
-
"statusMessage": "Injecting diagram rules..."
|
|
216
|
+
"timeout": 5
|
|
204
217
|
}
|
|
205
218
|
]
|
|
206
219
|
}
|
|
@@ -215,14 +228,25 @@ For Codex, add the same shape to `~/.codex/hooks.json` and set
|
|
|
215
228
|
```json
|
|
216
229
|
{
|
|
217
230
|
"hooks": {
|
|
231
|
+
"SessionStart": [
|
|
232
|
+
{
|
|
233
|
+
"matcher": "startup|resume",
|
|
234
|
+
"hooks": [
|
|
235
|
+
{
|
|
236
|
+
"type": "command",
|
|
237
|
+
"command": "FEYNMAN_HOME=\"$HOME/.codex\" node \"/absolute/path/to/feynman/hooks/feynman-session-start.js\"",
|
|
238
|
+
"timeout": 5
|
|
239
|
+
}
|
|
240
|
+
]
|
|
241
|
+
}
|
|
242
|
+
],
|
|
218
243
|
"UserPromptSubmit": [
|
|
219
244
|
{
|
|
220
245
|
"hooks": [
|
|
221
246
|
{
|
|
222
247
|
"type": "command",
|
|
223
248
|
"command": "FEYNMAN_HOME=\"$HOME/.codex\" node \"/absolute/path/to/feynman/hooks/feynman-activate.js\"",
|
|
224
|
-
"timeout": 5
|
|
225
|
-
"statusMessage": "Injecting diagram rules..."
|
|
249
|
+
"timeout": 5
|
|
226
250
|
}
|
|
227
251
|
]
|
|
228
252
|
}
|
|
@@ -232,6 +256,41 @@ For Codex, add the same shape to `~/.codex/hooks.json` and set
|
|
|
232
256
|
```
|
|
233
257
|
</details>
|
|
234
258
|
|
|
259
|
+
After install, feynman starts in `full` mode by default. Disable or change it
|
|
260
|
+
explicitly with `/feynman off`, `/feynman lite`, `/feynman full`, or
|
|
261
|
+
`/feynman ultra`.
|
|
262
|
+
|
|
263
|
+
## Verify the install
|
|
264
|
+
|
|
265
|
+
Run `doctor` after installing or after manually editing hooks:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
npx @albinocrabs/feynman doctor --target codex
|
|
269
|
+
npx @albinocrabs/feynman doctor --target claude
|
|
270
|
+
npx @albinocrabs/feynman doctor --target all
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
A healthy target reports both hook events and both hook script files as `OK`:
|
|
274
|
+
|
|
275
|
+
```text
|
|
276
|
+
[OK] hook registered (feynman-session-start.js in SessionStart)
|
|
277
|
+
[OK] hook registered (feynman-activate.js in UserPromptSubmit)
|
|
278
|
+
[OK] session hook script file exists and is readable
|
|
279
|
+
[OK] prompt hook script file exists and is readable
|
|
280
|
+
Status: OK
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
For Codex, runtime state should live under `~/.codex`:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
cat ~/.codex/.feynman/state.json
|
|
287
|
+
test -f ~/.codex/.feynman-active
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
For Claude Code, use `~/.claude` instead of `~/.codex`. `doctor` fails if a
|
|
291
|
+
registered hook command cannot be parsed to a real hook script, which catches
|
|
292
|
+
stale or broken manual installs.
|
|
293
|
+
|
|
235
294
|
## Intensity Levels
|
|
236
295
|
|
|
237
296
|
| Level | What draws | Use when |
|
|
@@ -276,6 +335,26 @@ This bypasses `~/.codex/hooks.json` hook execution entirely.
|
|
|
276
335
|
Regular `/feynman off` and `/feynman on` continue to use normal profile state
|
|
277
336
|
files (`~/.codex/.feynman-active`, `~/.codex/.feynman/state.json`).
|
|
278
337
|
|
|
338
|
+
## Security notes
|
|
339
|
+
|
|
340
|
+
feynman hooks are local prompt-context hooks. They do not require network
|
|
341
|
+
access, do not read repository files, and do not read credentials. The active
|
|
342
|
+
mode is stored only in the client-local state path:
|
|
343
|
+
|
|
344
|
+
```text
|
|
345
|
+
~/.claude/.feynman/state.json
|
|
346
|
+
~/.codex/.feynman/state.json
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
The hook runtime treats invalid state as disabled for that turn, removes the
|
|
350
|
+
activation flag, and stays silent. That prevents a corrupted state file from
|
|
351
|
+
silently forcing diagram rules into future prompts.
|
|
352
|
+
|
|
353
|
+
`uninstall` removes only feynman hook commands and preserves unrelated hooks in
|
|
354
|
+
the same hook group. `doctor` validates that registered commands point to real
|
|
355
|
+
hook scripts, so broken manual commands are visible before a session depends on
|
|
356
|
+
them.
|
|
357
|
+
|
|
279
358
|
## CLI examples
|
|
280
359
|
|
|
281
360
|
Quickly discover and view repository prompt templates:
|
|
@@ -481,24 +560,51 @@ from day one.
|
|
|
481
560
|
|
|
482
561
|
## How it works
|
|
483
562
|
|
|
484
|
-
The `
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
the
|
|
563
|
+
The `SessionStart` hook primes fresh Claude Code or Codex sessions with the
|
|
564
|
+
active rules, and the `UserPromptSubmit` hook reinforces them on every prompt.
|
|
565
|
+
Both hooks read the target-local state file
|
|
566
|
+
(`~/.claude/.feynman/state.json` or `~/.codex/.feynman/state.json`), extract
|
|
567
|
+
the rules for the active intensity level, and inject them into model context.
|
|
489
568
|
|
|
490
569
|
```
|
|
491
570
|
[your prompt]
|
|
492
571
|
+
|
|
493
|
-
[feynman rules]
|
|
572
|
+
[feynman rules] injected by hook
|
|
494
573
|
│
|
|
495
574
|
▼
|
|
496
|
-
|
|
575
|
+
[Claude Code]
|
|
576
|
+
or
|
|
577
|
+
[Codex]
|
|
497
578
|
│
|
|
498
579
|
▼
|
|
499
580
|
[structured response with ASCII diagrams]
|
|
500
581
|
```
|
|
501
582
|
|
|
583
|
+
## Token budget and output size
|
|
584
|
+
|
|
585
|
+
feynman always adds some input context when it is enabled, because the active
|
|
586
|
+
diagram rules are injected into the client prompt context. It does not add
|
|
587
|
+
visible output by itself; output changes only when the assistant uses the
|
|
588
|
+
rules.
|
|
589
|
+
|
|
590
|
+
Current rule payload sizes are approximately:
|
|
591
|
+
|
|
592
|
+
| Mode | Bytes | Approx tokens | Use when |
|
|
593
|
+
| ---- | ----- | ------------- | -------- |
|
|
594
|
+
| `lite` | 1307 | 317 | minimal flows and trees |
|
|
595
|
+
| `full` | 2180 | 532 | default diagram coverage |
|
|
596
|
+
| `ultra` | 1390 | 337 | force diagrams more often |
|
|
597
|
+
|
|
598
|
+
The token count is a rough `chars / 4` estimate, not a billing counter. The
|
|
599
|
+
actual number depends on the runtime tokenizer and surrounding hook envelope.
|
|
600
|
+
|
|
601
|
+
The plugin can reduce output size when a diagram replaces repeated prose,
|
|
602
|
+
especially for flows, status summaries, hierarchies, and comparisons. It can
|
|
603
|
+
increase output for short answers where a diagram would be unnecessary, so the
|
|
604
|
+
rules explicitly suppress diagrams for one- or two-sentence direct answers.
|
|
605
|
+
Use `/feynman lite` for lower overhead or `/feynman off` when token budget is
|
|
606
|
+
more important than visual structure.
|
|
607
|
+
|
|
502
608
|
## Release process
|
|
503
609
|
|
|
504
610
|
Every push runs tests on Node 18 and 20 across Ubuntu and macOS. The release
|
package/SECURITY.md
CHANGED
|
@@ -39,4 +39,4 @@ Before publishing a new npm version:
|
|
|
39
39
|
- GitHub release tag must match `package.json` version with a `v` prefix.
|
|
40
40
|
- GitHub Actions secret `NPM_TOKEN` must be present for first publish of a new version.
|
|
41
41
|
- npm provenance is enabled in the release workflow.
|
|
42
|
-
- Registry smoke verification must pass after publish (`npm view`, install from npm, `feynman doctor --target
|
|
42
|
+
- Registry smoke verification must pass after publish (`npm view`, install from npm, `feynman doctor --target all`).
|
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 };
|
|
@@ -108,7 +109,7 @@ ${c.bold('Options:')}
|
|
|
108
109
|
${c.bold('Examples:')}
|
|
109
110
|
npx @albinocrabs/feynman install
|
|
110
111
|
npx @albinocrabs/feynman install --target codex
|
|
111
|
-
npx @albinocrabs/feynman install --target
|
|
112
|
+
npx @albinocrabs/feynman install --target all
|
|
112
113
|
npx @albinocrabs/feynman install --target all
|
|
113
114
|
npx @albinocrabs/feynman doctor
|
|
114
115
|
feynman lint response.md
|
|
@@ -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
|
|
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
|
|
455
|
-
3. Hook script
|
|
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
|
|
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).
|
|
@@ -506,24 +507,61 @@ function writeSettings(target, settings) {
|
|
|
506
507
|
fs.writeFileSync(cfg.settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
507
508
|
}
|
|
508
509
|
|
|
510
|
+
function isFeynmanHookCommand(command) {
|
|
511
|
+
return typeof command === 'string' && (
|
|
512
|
+
command.includes('feynman-session-start.js') ||
|
|
513
|
+
command.includes('feynman-activate.js') ||
|
|
514
|
+
command.includes('feynman-lint.js')
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
509
518
|
function hasFeynmanHook(settings) {
|
|
510
|
-
|
|
519
|
+
const promptHook = ((settings.hooks && settings.hooks.UserPromptSubmit) || []).some(g =>
|
|
511
520
|
g.hooks && g.hooks.some(h => h.command && h.command.includes('feynman-activate.js'))
|
|
512
521
|
);
|
|
522
|
+
const sessionHook = ((settings.hooks && settings.hooks.SessionStart) || []).some(g =>
|
|
523
|
+
g.hooks && g.hooks.some(h => h.command && h.command.includes('feynman-session-start.js'))
|
|
524
|
+
);
|
|
525
|
+
return promptHook && sessionHook;
|
|
513
526
|
}
|
|
514
527
|
|
|
515
|
-
function
|
|
516
|
-
if (!settings.hooks
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
h.command.includes('feynman-lint.js')
|
|
522
|
-
)
|
|
523
|
-
))
|
|
528
|
+
function hasAnyFeynmanHook(settings) {
|
|
529
|
+
if (!settings.hooks) return false;
|
|
530
|
+
return ['SessionStart', 'UserPromptSubmit', 'Stop'].some(eventName =>
|
|
531
|
+
((settings.hooks && settings.hooks[eventName]) || []).some(g =>
|
|
532
|
+
g.hooks && g.hooks.some(h => isFeynmanHookCommand(h.command))
|
|
533
|
+
)
|
|
524
534
|
);
|
|
525
|
-
|
|
526
|
-
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function extractHookScriptPath(command, scriptName) {
|
|
538
|
+
if (typeof command !== 'string') return null;
|
|
539
|
+
const escaped = scriptName.replace(/\./g, '\\.');
|
|
540
|
+
const quotedPattern = new RegExp("[\"']([^\"']*" + escaped + ")[\"']");
|
|
541
|
+
const quoted = command.match(quotedPattern);
|
|
542
|
+
if (quoted) return quoted[1];
|
|
543
|
+
|
|
544
|
+
const unquotedPattern = new RegExp("(?:^|\\s)(/[^\\s\"';&|<>]*" + escaped + ")(?=$|\\s)");
|
|
545
|
+
const unquoted = command.match(unquotedPattern);
|
|
546
|
+
return unquoted ? unquoted[1] : null;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function removeFeynmanHooks(settings) {
|
|
550
|
+
if (!settings.hooks) return settings;
|
|
551
|
+
for (const eventName of ['SessionStart', 'UserPromptSubmit', 'Stop']) {
|
|
552
|
+
if (!Array.isArray(settings.hooks[eventName])) continue;
|
|
553
|
+
settings.hooks[eventName] = settings.hooks[eventName]
|
|
554
|
+
.map(g => {
|
|
555
|
+
if (!Array.isArray(g.hooks)) return g;
|
|
556
|
+
return {
|
|
557
|
+
...g,
|
|
558
|
+
hooks: g.hooks.filter(h => !isFeynmanHookCommand(h.command)),
|
|
559
|
+
};
|
|
560
|
+
})
|
|
561
|
+
.filter(g => !Array.isArray(g.hooks) || g.hooks.length > 0);
|
|
562
|
+
if (settings.hooks[eventName].length === 0) {
|
|
563
|
+
delete settings.hooks[eventName];
|
|
564
|
+
}
|
|
527
565
|
}
|
|
528
566
|
if (Object.keys(settings.hooks).length === 0) {
|
|
529
567
|
delete settings.hooks;
|
|
@@ -534,10 +572,21 @@ function removeFeynmanHooks(settings) {
|
|
|
534
572
|
function bootstrapState(target) {
|
|
535
573
|
const cfg = targetConfig(target);
|
|
536
574
|
fs.mkdirSync(cfg.feynmanDir, { recursive: true });
|
|
575
|
+
let state = DEFAULT_STATE;
|
|
537
576
|
if (!fs.existsSync(cfg.statePath)) {
|
|
538
577
|
fs.writeFileSync(cfg.statePath, JSON.stringify(DEFAULT_STATE, null, 2) + '\n');
|
|
578
|
+
} else {
|
|
579
|
+
try {
|
|
580
|
+
state = { ...DEFAULT_STATE, ...JSON.parse(fs.readFileSync(cfg.statePath, 'utf8')) };
|
|
581
|
+
} catch (_) {
|
|
582
|
+
fs.writeFileSync(cfg.statePath, JSON.stringify(DEFAULT_STATE, null, 2) + '\n');
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (state.enabled) {
|
|
586
|
+
fs.writeFileSync(cfg.flagPath, state.intensity || DEFAULT_STATE.intensity);
|
|
587
|
+
} else if (fs.existsSync(cfg.flagPath)) {
|
|
588
|
+
fs.unlinkSync(cfg.flagPath);
|
|
539
589
|
}
|
|
540
|
-
fs.writeFileSync(cfg.flagPath, DEFAULT_STATE.intensity);
|
|
541
590
|
}
|
|
542
591
|
|
|
543
592
|
function installClaudeCommand() {
|
|
@@ -561,6 +610,7 @@ function installOne(target, opts) {
|
|
|
561
610
|
// Read or create settings
|
|
562
611
|
const cfg = readSettings(target);
|
|
563
612
|
cfg.hooks = cfg.hooks || {};
|
|
613
|
+
cfg.hooks.SessionStart = cfg.hooks.SessionStart || [];
|
|
564
614
|
cfg.hooks.UserPromptSubmit = cfg.hooks.UserPromptSubmit || [];
|
|
565
615
|
|
|
566
616
|
// Idempotency check
|
|
@@ -572,20 +622,36 @@ function installOne(target, opts) {
|
|
|
572
622
|
return { target, already: true };
|
|
573
623
|
}
|
|
574
624
|
|
|
575
|
-
// If force
|
|
625
|
+
// If force or partial legacy install, remove old feynman entries first.
|
|
576
626
|
if (already && force) {
|
|
577
627
|
removeFeynmanHooks(cfg);
|
|
578
628
|
cfg.hooks = cfg.hooks || {};
|
|
629
|
+
cfg.hooks.SessionStart = cfg.hooks.SessionStart || [];
|
|
630
|
+
cfg.hooks.UserPromptSubmit = cfg.hooks.UserPromptSubmit || [];
|
|
631
|
+
} else if (!already) {
|
|
632
|
+
removeFeynmanHooks(cfg);
|
|
633
|
+
cfg.hooks = cfg.hooks || {};
|
|
634
|
+
cfg.hooks.SessionStart = cfg.hooks.SessionStart || [];
|
|
579
635
|
cfg.hooks.UserPromptSubmit = cfg.hooks.UserPromptSubmit || [];
|
|
580
636
|
}
|
|
581
637
|
|
|
582
|
-
// Append hook
|
|
638
|
+
// Append hook entries
|
|
639
|
+
const sessionEntry = {
|
|
640
|
+
hooks: [{
|
|
641
|
+
type: 'command',
|
|
642
|
+
command: hookCommandFor(target).replace(HOOK_PATH, SESSION_HOOK_PATH),
|
|
643
|
+
timeout: 5,
|
|
644
|
+
}]
|
|
645
|
+
};
|
|
646
|
+
if (target === 'codex') {
|
|
647
|
+
sessionEntry.matcher = 'startup|resume';
|
|
648
|
+
}
|
|
649
|
+
cfg.hooks.SessionStart.push(sessionEntry);
|
|
583
650
|
cfg.hooks.UserPromptSubmit.push({
|
|
584
651
|
hooks: [{
|
|
585
652
|
type: 'command',
|
|
586
653
|
command: hookCommandFor(target),
|
|
587
654
|
timeout: 5,
|
|
588
|
-
statusMessage: 'Injecting diagram rules...',
|
|
589
655
|
}]
|
|
590
656
|
});
|
|
591
657
|
|
|
@@ -623,7 +689,7 @@ function cmdInstall(opts) {
|
|
|
623
689
|
}
|
|
624
690
|
console.log('└──────────────────────────────────────────────────────────────┘');
|
|
625
691
|
console.log('');
|
|
626
|
-
console.log('Restart Claude Code or Codex to activate feynman.');
|
|
692
|
+
console.log('Restart Claude Code or Codex to activate feynman full mode.');
|
|
627
693
|
|
|
628
694
|
process.exit(0);
|
|
629
695
|
}
|
|
@@ -638,7 +704,7 @@ function uninstallOne(target) {
|
|
|
638
704
|
}
|
|
639
705
|
|
|
640
706
|
const cfg = readSettings(target);
|
|
641
|
-
const hadHook =
|
|
707
|
+
const hadHook = hasAnyFeynmanHook(cfg);
|
|
642
708
|
removeFeynmanHooks(cfg);
|
|
643
709
|
writeSettings(target, cfg);
|
|
644
710
|
|
|
@@ -684,40 +750,54 @@ function cmdDoctor(opts = {}) {
|
|
|
684
750
|
const settingsExists = fs.existsSync(tc.settingsPath);
|
|
685
751
|
check(`${tc.settingsPath.replace(HOME, '~')} present`, settingsExists);
|
|
686
752
|
|
|
687
|
-
// 2. UserPromptSubmit
|
|
753
|
+
// 2. SessionStart and UserPromptSubmit hooks reference feynman scripts
|
|
754
|
+
let sessionHookRegistered = false;
|
|
688
755
|
let hookRegistered = false;
|
|
756
|
+
let sessionHookAbsPath = null;
|
|
689
757
|
let hookAbsPath = null;
|
|
690
758
|
if (settingsExists) {
|
|
691
759
|
const cfg = readSettings(target);
|
|
760
|
+
const sessionEntries = (cfg.hooks && cfg.hooks.SessionStart) || [];
|
|
761
|
+
const feynmanSessionEntry = sessionEntries.find(g =>
|
|
762
|
+
g.hooks && g.hooks.some(h => h.command && h.command.includes('feynman-session-start.js'))
|
|
763
|
+
);
|
|
764
|
+
sessionHookRegistered = !!feynmanSessionEntry;
|
|
765
|
+
if (feynmanSessionEntry) {
|
|
766
|
+
const hookCmd = feynmanSessionEntry.hooks.find(h => h.command && h.command.includes('feynman-session-start.js')).command;
|
|
767
|
+
sessionHookAbsPath = extractHookScriptPath(hookCmd, 'feynman-session-start.js');
|
|
768
|
+
}
|
|
769
|
+
|
|
692
770
|
const entries = (cfg.hooks && cfg.hooks.UserPromptSubmit) || [];
|
|
693
771
|
const feynmanEntry = entries.find(g =>
|
|
694
772
|
g.hooks && g.hooks.some(h => h.command && h.command.includes('feynman-activate.js'))
|
|
695
773
|
);
|
|
696
774
|
hookRegistered = !!feynmanEntry;
|
|
697
775
|
if (feynmanEntry) {
|
|
698
|
-
// Extract the path from command: node "/abs/path/to/feynman-activate.js"
|
|
699
776
|
const hookCmd = feynmanEntry.hooks.find(h => h.command && h.command.includes('feynman-activate.js')).command;
|
|
700
|
-
|
|
701
|
-
if (match) hookAbsPath = match[1];
|
|
777
|
+
hookAbsPath = extractHookScriptPath(hookCmd, 'feynman-activate.js');
|
|
702
778
|
}
|
|
703
779
|
}
|
|
780
|
+
check('hook registered (feynman-session-start.js in SessionStart)', sessionHookRegistered);
|
|
704
781
|
check('hook registered (feynman-activate.js in UserPromptSubmit)', hookRegistered);
|
|
705
782
|
|
|
706
|
-
// 3. Hook script
|
|
783
|
+
// 3. Hook script files exist + readable
|
|
784
|
+
let sessionHookFileOk = false;
|
|
785
|
+
if (sessionHookAbsPath) {
|
|
786
|
+
try {
|
|
787
|
+
fs.accessSync(sessionHookAbsPath, fs.constants.R_OK);
|
|
788
|
+
sessionHookFileOk = true;
|
|
789
|
+
} catch (_) {}
|
|
790
|
+
}
|
|
791
|
+
check('session hook script file exists and is readable', sessionHookFileOk);
|
|
792
|
+
|
|
707
793
|
let hookFileOk = false;
|
|
708
794
|
if (hookAbsPath) {
|
|
709
795
|
try {
|
|
710
796
|
fs.accessSync(hookAbsPath, fs.constants.R_OK);
|
|
711
797
|
hookFileOk = true;
|
|
712
798
|
} catch (_) {}
|
|
713
|
-
} else if (hookRegistered) {
|
|
714
|
-
// Hook registered but path extraction failed — check default location
|
|
715
|
-
try {
|
|
716
|
-
fs.accessSync(HOOK_PATH, fs.constants.R_OK);
|
|
717
|
-
hookFileOk = true;
|
|
718
|
-
} catch (_) {}
|
|
719
799
|
}
|
|
720
|
-
check('hook script file exists and is readable', hookFileOk);
|
|
800
|
+
check('prompt hook script file exists and is readable', hookFileOk);
|
|
721
801
|
|
|
722
802
|
// 4. Rules file exists + non-empty
|
|
723
803
|
let rulesOk = false;
|
|
@@ -729,15 +809,20 @@ function cmdDoctor(opts = {}) {
|
|
|
729
809
|
|
|
730
810
|
// 5. state.json valid JSON + has enabled field
|
|
731
811
|
let stateOk = false;
|
|
812
|
+
let stateEnabled = false;
|
|
732
813
|
try {
|
|
733
814
|
const state = JSON.parse(fs.readFileSync(tc.statePath, 'utf8'));
|
|
734
815
|
stateOk = 'enabled' in state;
|
|
816
|
+
stateEnabled = state.enabled === true;
|
|
735
817
|
} catch (_) {}
|
|
736
818
|
check('state.json valid (has enabled field)', stateOk);
|
|
737
819
|
|
|
738
|
-
// 6. .feynman-active flag
|
|
820
|
+
// 6. .feynman-active flag matches state
|
|
739
821
|
const flagPresent = fs.existsSync(tc.flagPath);
|
|
740
|
-
check(
|
|
822
|
+
check(
|
|
823
|
+
stateEnabled ? '.feynman-active flag present when enabled' : '.feynman-active flag absent when disabled',
|
|
824
|
+
stateEnabled ? flagPresent : !flagPresent
|
|
825
|
+
);
|
|
741
826
|
|
|
742
827
|
// 7. (INFO) lint hook registered
|
|
743
828
|
let lintHookRegistered = false;
|
package/docs/architecture.md
CHANGED
|
@@ -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 `
|
|
10
|
-
|
|
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
|
-
|
|
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
|
|
29
|
-
│ absent + state.
|
|
30
|
-
│
|
|
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
|
|
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
|
|
35
|
-
// Intentionally disabled: flag absent
|
|
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
|
|
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
|
-
|
|
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,78 @@
|
|
|
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 writeState(state) {
|
|
21
|
+
fs.mkdirSync(FEYNMAN_DIR, { recursive: true });
|
|
22
|
+
fs.writeFileSync(STATE_PATH, JSON.stringify(state, null, 2));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readRules(intensity) {
|
|
26
|
+
const rulesContent = fs.readFileSync(RULES_PATH, 'utf8');
|
|
27
|
+
const selected = VALID_INTENSITIES.includes(intensity) ? intensity : 'full';
|
|
28
|
+
const openMarker = '<!-- ' + selected + ' -->';
|
|
29
|
+
const closeMarker = '<!-- /' + selected + ' -->';
|
|
30
|
+
const i1 = rulesContent.indexOf(openMarker);
|
|
31
|
+
const i2 = rulesContent.indexOf(closeMarker, i1);
|
|
32
|
+
if (i1 === -1 || i2 === -1) return '';
|
|
33
|
+
return rulesContent.slice(i1 + openMarker.length, i2).trim();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let input = '';
|
|
37
|
+
process.stdin.on('data', chunk => { input += chunk; });
|
|
38
|
+
process.stdin.on('end', () => {
|
|
39
|
+
try {
|
|
40
|
+
if (input.trim()) {
|
|
41
|
+
const data = JSON.parse(input);
|
|
42
|
+
const sessionId = data.session_id || '';
|
|
43
|
+
if (sessionId && /[/\\]|\.\./.test(sessionId)) process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const stateExists = fs.existsSync(STATE_PATH);
|
|
47
|
+
const flagExists = fs.existsSync(FLAG_PATH);
|
|
48
|
+
let state = { ...DEFAULT_STATE };
|
|
49
|
+
|
|
50
|
+
if (stateExists) {
|
|
51
|
+
try {
|
|
52
|
+
state = { ...DEFAULT_STATE, ...JSON.parse(fs.readFileSync(STATE_PATH, 'utf8')) };
|
|
53
|
+
} catch (_) {
|
|
54
|
+
try { fs.unlinkSync(FLAG_PATH); } catch (_) {}
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
writeState(state);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!state.enabled) {
|
|
62
|
+
try { fs.unlinkSync(FLAG_PATH); } catch (_) {}
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!flagExists) {
|
|
67
|
+
fs.writeFileSync(FLAG_PATH, state.intensity || DEFAULT_STATE.intensity);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const rulesText = readRules(state.intensity);
|
|
71
|
+
if (!rulesText) process.exit(0);
|
|
72
|
+
|
|
73
|
+
// SessionStart accepts plain stdout as context, matching caveman's hook shape.
|
|
74
|
+
process.stdout.write(rulesText);
|
|
75
|
+
} catch (_) {
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
});
|
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
|
@@ -111,6 +111,168 @@ Syntax:
|
|
|
111
111
|
item-3
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
### Terminal-safe rendering
|
|
115
|
+
|
|
116
|
+
Tables are allowed when they stay readable in terminal chat. Wide Markdown
|
|
117
|
+
tables are a rendering defect: convert them into a cleaner terminal layout
|
|
118
|
+
instead of letting columns wrap unpredictably.
|
|
119
|
+
|
|
120
|
+
Choose the layout by readability:
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
short matrix -> Markdown table is OK
|
|
124
|
+
status maps -> ASCII frame blocks
|
|
125
|
+
long rows -> key-value bullets
|
|
126
|
+
hierarchy -> ASCII tree
|
|
127
|
+
sequence -> arrow flow
|
|
128
|
+
comparison -> max 3 columns, max 10 words per cell
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Frame discipline:
|
|
132
|
+
|
|
133
|
+
```text
|
|
134
|
+
+---- Status ----+
|
|
135
|
+
| item-a | done |
|
|
136
|
+
| item-b | risk |
|
|
137
|
+
+----------------+
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
- Keep frame rows visually aligned.
|
|
141
|
+
- Keep diagram lines under about 88 columns when possible.
|
|
142
|
+
- If a row becomes long, split it into bullets or grouped frames instead of
|
|
143
|
+
widening the table.
|
|
144
|
+
- Optimize for human scanning: aligned labels, short cells, stable columns,
|
|
145
|
+
and no wrapped table rows.
|
|
146
|
+
- Use plain ASCII frames with `+`, `-`, and `|` when terminal compatibility
|
|
147
|
+
matters.
|
|
148
|
+
|
|
149
|
+
### SDLC output patterns
|
|
150
|
+
|
|
151
|
+
For engineering status, retrospectives, handoffs, reviews, and release notes,
|
|
152
|
+
choose a human-scannable shape before writing prose. Prefer compact blocks with
|
|
153
|
+
explicit evidence over long narrative.
|
|
154
|
+
|
|
155
|
+
Use these shapes:
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
status -> frame with state, branch, commit, checks, blocker
|
|
159
|
+
retro -> DONE / WORKED / FRAGILE / LEFT
|
|
160
|
+
handoff -> NOW / NEXT / FILES / COMMANDS / RISK
|
|
161
|
+
review -> FINDINGS first, then QUESTIONS, then SUMMARY
|
|
162
|
+
incident -> IMPACT / CAUSE / FIX / PREVENTION
|
|
163
|
+
release -> CHANGED / VERIFIED / RISK / ROLLBACK
|
|
164
|
+
decision -> CONTEXT / OPTIONS / CHOICE / CONSEQUENCE
|
|
165
|
+
verification -> command -> result -> evidence -> gap
|
|
166
|
+
roadmap -> NOW / NEXT / LATER / BLOCKED
|
|
167
|
+
phase -> GOAL / SCOPE / PLAN / VERIFY / EXIT
|
|
168
|
+
UAT -> SCENARIO / EXPECTED / ACTUAL / RESULT
|
|
169
|
+
risk register -> RISK / IMPACT / MITIGATION / OWNER
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Status block pattern:
|
|
173
|
+
|
|
174
|
+
```text
|
|
175
|
+
+---- Status ----+
|
|
176
|
+
| repo | name |
|
|
177
|
+
| branch | main |
|
|
178
|
+
| commit | abc12 |
|
|
179
|
+
| checks | PASS |
|
|
180
|
+
| blocker | none |
|
|
181
|
+
+----------------+
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Retro pattern:
|
|
185
|
+
|
|
186
|
+
```text
|
|
187
|
+
DONE:
|
|
188
|
+
- landed change with evidence
|
|
189
|
+
|
|
190
|
+
WORKED:
|
|
191
|
+
- useful command or decision
|
|
192
|
+
|
|
193
|
+
FRAGILE:
|
|
194
|
+
- risk or assumption
|
|
195
|
+
|
|
196
|
+
LEFT:
|
|
197
|
+
- next executable action
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Roadmap pattern:
|
|
201
|
+
|
|
202
|
+
```text
|
|
203
|
+
NOW:
|
|
204
|
+
- current milestone or active phase
|
|
205
|
+
|
|
206
|
+
NEXT:
|
|
207
|
+
- next executable phase
|
|
208
|
+
|
|
209
|
+
LATER:
|
|
210
|
+
- deferred work
|
|
211
|
+
|
|
212
|
+
BLOCKED:
|
|
213
|
+
- dependency or decision needed
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Phase pattern:
|
|
217
|
+
|
|
218
|
+
```text
|
|
219
|
+
GOAL:
|
|
220
|
+
- promised outcome
|
|
221
|
+
|
|
222
|
+
SCOPE:
|
|
223
|
+
- included / excluded boundaries
|
|
224
|
+
|
|
225
|
+
PLAN:
|
|
226
|
+
- implementation path
|
|
227
|
+
|
|
228
|
+
VERIFY:
|
|
229
|
+
- command, test, or review evidence
|
|
230
|
+
|
|
231
|
+
EXIT:
|
|
232
|
+
- condition for done
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
UAT pattern:
|
|
236
|
+
|
|
237
|
+
```text
|
|
238
|
+
SCENARIO:
|
|
239
|
+
- user action or workflow
|
|
240
|
+
|
|
241
|
+
EXPECTED:
|
|
242
|
+
- expected behavior
|
|
243
|
+
|
|
244
|
+
ACTUAL:
|
|
245
|
+
- observed behavior
|
|
246
|
+
|
|
247
|
+
RESULT:
|
|
248
|
+
- PASS / FAIL / BLOCKED
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Risk register pattern:
|
|
252
|
+
|
|
253
|
+
```text
|
|
254
|
+
▲ high
|
|
255
|
+
RISK:
|
|
256
|
+
- what can go wrong
|
|
257
|
+
IMPACT:
|
|
258
|
+
- why it matters
|
|
259
|
+
MITIGATION:
|
|
260
|
+
- concrete control
|
|
261
|
+
OWNER:
|
|
262
|
+
- agent / human / system
|
|
263
|
+
▼ low
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Rules:
|
|
267
|
+
|
|
268
|
+
- Put the answer first, evidence second, next action last.
|
|
269
|
+
- Never bury blockers in prose; give them their own label.
|
|
270
|
+
- For status and retro, avoid wide tables even when Markdown would be valid.
|
|
271
|
+
- Prefer `PASS`, `FAIL`, `BLOCKED`, `not run`, and exact command names.
|
|
272
|
+
- If verification was not run, say `not run` and name the command.
|
|
273
|
+
- For Russian chat, keep prose Russian and keep commands, paths, config keys,
|
|
274
|
+
commits, and status labels in English.
|
|
275
|
+
|
|
114
276
|
### When no diagram appears
|
|
115
277
|
|
|
116
278
|
Responses that are any of the following contain no diagram:
|
|
@@ -154,13 +316,74 @@ detail | detail
|
|
|
154
316
|
|
|
155
317
|
**Comparisons** — Same as full mode. Includes side-by-side ASCII columns.
|
|
156
318
|
|
|
157
|
-
**Status summaries** — Same as full mode. Uses
|
|
319
|
+
**Status summaries** — Same as full mode. Uses ASCII `+---` frame blocks.
|
|
158
320
|
|
|
159
321
|
**Priority orderings** — Same as full mode. Uses ▲▼ scale.
|
|
160
322
|
|
|
323
|
+
### Terminal-safe rendering
|
|
324
|
+
|
|
325
|
+
Tables are allowed when they are compact and readable. Wide Markdown tables are
|
|
326
|
+
a rendering defect in terminal chat. Prefer ASCII frames, key-value bullets,
|
|
327
|
+
trees, and short columns for long content. Keep diagram lines under about 88
|
|
328
|
+
columns when possible. If content is long, split it into bullets or grouped
|
|
329
|
+
frames instead of widening the visual block.
|
|
330
|
+
|
|
331
|
+
### SDLC output patterns
|
|
332
|
+
|
|
333
|
+
All full-mode SDLC patterns apply. In ultra mode, use them aggressively for any
|
|
334
|
+
engineering status, retrospective, review, release, handoff, decision, or
|
|
335
|
+
verification answer. The default shape is:
|
|
336
|
+
|
|
337
|
+
```text
|
|
338
|
+
[state] --> [evidence] --> [risk] --> [next]
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
For retrospectives, prefer:
|
|
342
|
+
|
|
343
|
+
```text
|
|
344
|
+
DONE / WORKED / FRAGILE / LEFT
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
For status answers, prefer:
|
|
348
|
+
|
|
349
|
+
```text
|
|
350
|
+
+---- Status ----+
|
|
351
|
+
| item | state |
|
|
352
|
+
| checks | PASS |
|
|
353
|
+
| blocker | none |
|
|
354
|
+
+----------------+
|
|
355
|
+
```
|
|
356
|
+
|
|
161
357
|
### When no diagram appears
|
|
162
358
|
|
|
163
359
|
The only response that contains no diagram is a single sentence of pure prose with no enumerable items, no steps, no comparisons, and no structure of any kind.
|
|
164
360
|
|
|
165
361
|
All other responses — including those with two or more items, any named concept with sub-parts, any sequence of actions, any set of options — include an ASCII diagram.
|
|
166
362
|
<!-- /ultra -->
|
|
363
|
+
|
|
364
|
+
### Russian terminal chat visual guardrail
|
|
365
|
+
|
|
366
|
+
For Russian terminal chat, avoid using ASCII frames as two-column tables when
|
|
367
|
+
values contain long Cyrillic or mixed Cyrillic/Latin text. Visual alignment can
|
|
368
|
+
look broken even when character counts are correct.
|
|
369
|
+
|
|
370
|
+
Prefer bullets for long Russian status facts:
|
|
371
|
+
|
|
372
|
+
Итог:
|
|
373
|
+
- Что сделали: dtp-retro стал русским по умолчанию.
|
|
374
|
+
- История: текущая session ищется автоматически.
|
|
375
|
+
- Форма: сначала смысл, потом proof/details.
|
|
376
|
+
- Статус: правки есть, commit ещё не сделан.
|
|
377
|
+
|
|
378
|
+
Avoid frame rows like this for long Russian text:
|
|
379
|
+
|
|
380
|
+
| что сделали | длинная русская строка ... |
|
|
381
|
+
| история | длинная смешанная строка ... |
|
|
382
|
+
|
|
383
|
+
Rule:
|
|
384
|
+
|
|
385
|
+
- frame with short values -> OK
|
|
386
|
+
- long Russian value -> bullets
|
|
387
|
+
- sequence -> arrows
|
|
388
|
+
- comparison -> max 3 short columns
|
|
389
|
+
- status with long text -> label bullets, not frame rows
|
package/skills/feynman/SKILL.md
CHANGED
|
@@ -26,11 +26,21 @@ Parse `$ARGUMENTS`:
|
|
|
26
26
|
```bash
|
|
27
27
|
node -e "
|
|
28
28
|
const fs = require('fs'), os = require('os'), path = require('path');
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
function clientHome() {
|
|
30
|
+
if (process.env.FEYNMAN_HOME) return process.env.FEYNMAN_HOME;
|
|
31
|
+
if (process.env.FEYNMAN_TARGET === 'codex') return path.join(os.homedir(), '.codex');
|
|
32
|
+
if (process.env.FEYNMAN_TARGET === 'claude') return path.join(os.homedir(), '.claude');
|
|
33
|
+
if (process.env.CODEX_HOME) return process.env.CODEX_HOME;
|
|
34
|
+
if (process.env.CODEX_THREAD_ID || process.env.CODEX_SANDBOX) return path.join(os.homedir(), '.codex');
|
|
35
|
+
if (process.env.CLAUDE_CONFIG_DIR) return process.env.CLAUDE_CONFIG_DIR;
|
|
36
|
+
return path.join(os.homedir(), '.claude');
|
|
37
|
+
}
|
|
38
|
+
const root = clientHome();
|
|
39
|
+
const stateFile = path.join(root, '.feynman', 'state.json');
|
|
40
|
+
const flagFile = path.join(root, '.feynman-active');
|
|
41
|
+
let st = {enabled: true, intensity: 'full', injections: 0};
|
|
32
42
|
try { st = JSON.parse(fs.readFileSync(stateFile, 'utf8')); } catch(e) {}
|
|
33
|
-
console.log('enabled:', st.enabled, '| intensity:', st.intensity, '| injections:', (st.injections ?? st.count ?? 0), '| flag:', fs.existsSync(flagFile));
|
|
43
|
+
console.log('target:', path.basename(root), '| enabled:', st.enabled, '| intensity:', st.intensity, '| injections:', (st.injections ?? st.count ?? 0), '| flag:', fs.existsSync(flagFile));
|
|
34
44
|
"
|
|
35
45
|
```
|
|
36
46
|
|
|
@@ -39,18 +49,29 @@ console.log('enabled:', st.enabled, '| intensity:', st.intensity, '| injections:
|
|
|
39
49
|
```bash
|
|
40
50
|
node -e "
|
|
41
51
|
const fs = require('fs'), os = require('os'), path = require('path');
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
function clientHome() {
|
|
53
|
+
if (process.env.FEYNMAN_HOME) return process.env.FEYNMAN_HOME;
|
|
54
|
+
if (process.env.FEYNMAN_TARGET === 'codex') return path.join(os.homedir(), '.codex');
|
|
55
|
+
if (process.env.FEYNMAN_TARGET === 'claude') return path.join(os.homedir(), '.claude');
|
|
56
|
+
if (process.env.CODEX_HOME) return process.env.CODEX_HOME;
|
|
57
|
+
if (process.env.CODEX_THREAD_ID || process.env.CODEX_SANDBOX) return path.join(os.homedir(), '.codex');
|
|
58
|
+
if (process.env.CLAUDE_CONFIG_DIR) return process.env.CLAUDE_CONFIG_DIR;
|
|
59
|
+
return path.join(os.homedir(), '.claude');
|
|
60
|
+
}
|
|
61
|
+
const root = clientHome();
|
|
62
|
+
const stateFile = path.join(root, '.feynman', 'state.json');
|
|
63
|
+
const flagFile = path.join(root, '.feynman-active');
|
|
44
64
|
const arg = (process.argv[1] || '').trim().toLowerCase();
|
|
45
65
|
const normalized = arg === 'start' ? 'on' : arg === 'stop' ? 'off' : arg;
|
|
46
|
-
let st = {enabled:
|
|
66
|
+
let st = {enabled: false, intensity: 'full', injections: 0};
|
|
47
67
|
try { st = JSON.parse(fs.readFileSync(stateFile, 'utf8')); } catch(e) {}
|
|
48
|
-
|
|
68
|
+
function writeFlag(value) { fs.mkdirSync(root, {recursive: true}); fs.writeFileSync(flagFile, value || 'full'); }
|
|
69
|
+
if (normalized === 'on') { st.enabled = true; writeFlag(st.intensity); }
|
|
49
70
|
if (normalized === 'off') { st.enabled = false; try { fs.unlinkSync(flagFile); } catch(e) {} }
|
|
50
|
-
if (['lite','full','ultra'].includes(normalized)) { st.intensity = normalized; st.enabled = true;
|
|
71
|
+
if (['lite','full','ultra'].includes(normalized)) { st.intensity = normalized; st.enabled = true; writeFlag(normalized); }
|
|
51
72
|
fs.mkdirSync(path.dirname(stateFile), {recursive: true});
|
|
52
73
|
fs.writeFileSync(stateFile, JSON.stringify(st, null, 2));
|
|
53
|
-
console.log(JSON.stringify(st));
|
|
74
|
+
console.log(JSON.stringify({target: path.basename(root), ...st}));
|
|
54
75
|
" "$ARGUMENTS"
|
|
55
76
|
```
|
|
56
77
|
|