fix(story-mode): judge each part of a Bash one-liner, and keep underscores in balloons

Found by watching real workers: `node --test` was drawn as plain terminal work,
`git -c user.name=x commit` was not seen as saving, and a probe such as
`pytest --version` became a failing test. Commands are now split into their
simple commands (quote-aware, at && || ; | and newlines); each part is judged by
what it starts with after env assignments and wrappers like timeout and sudo,
git's own -c/-C options are skipped, a --version/--help probe is not a run, and
the strongest intent wins (test, save, build, edit, run, look). File
housekeeping (mkdir, touch, cp, mv, rm) and git init/add are plain commands.

Balloons dropped every _ and * as markdown (test_wcount.py read testwcount.py);
now only paired emphasis at word boundaries and line-start markers go.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-10-18 09:38:24 +02:00
1 parent dfe023d49d
commit fc279f998c
2 files changed
+124 -27

No files matched your search

+30
View File
@@ -49,6 +49,27 @@ describe('classifyBashCommand', () => {
['ls 2>/dev/null', 'look'],
['curl -s localhost:3000/api/status', 'run'],
['node scripts/build.mjs', 'run'],
// Only a segment that RUNS a test is a test; naming a runner is not.
['which pytest', 'look'],
['python3 -m pytest --version', 'run'],
['pip show pytest', 'run'],
['python3 -c "import pytest"', 'run'],
['echo "run pytest later"', 'look'],
['node --test', 'test'],
['node --test timer.test.mjs 2>&1 | tail -20', 'test'],
['timeout 60 npm test', 'test'],
['FOO=1 CI=true pytest -q', 'test'],
['cd app && make test', 'test'],
['python3 -m unittest discover', 'test'],
['make', 'build'],
['shellcheck rename.sh', 'build'],
['node --check app.js && git init -q && git add -A && git commit -qm "init"', 'save'],
['git -c user.name=a -c user.email=b commit -m x', 'save'],
['git -C repo push', 'save'],
['git init && git add .', 'run'],
['mkdir -p samples && touch samples/a.jpg', 'run'],
['grep -n "a|b" file.txt', 'look'],
["printf 'x' > out.txt", 'edit'],
])('%s → %s', (command, intent) => {
expect(classifyBashCommand(command)).toBe(intent);
});
@@ -76,6 +97,15 @@ describe('text helpers', () => {
expect(firstSentence('No full stop here')).toBe('No full stop here');
});
it('firstSentence keeps underscores and stars that are not markdown', () => {
expect(firstSentence('I built wcount.py and test_wcount.py.')).toBe('I built wcount.py and test_wcount.py.');
expect(firstSentence('Renamed rename_by_date.sh to __init__.py style.')).toBe(
'Renamed rename_by_date.sh to __init__.py style.'
);
expect(firstSentence('It is *really* done and _very_ fast.')).toBe('It is really done and very fast.');
expect(firstSentence('## Summary\n- Fixed the 2*3 bug.')).toBe('Summary Fixed the 2*3 bug.');
});
it('formats durations for both captions', () => {
expect(roughDuration(432_000)).toBe('7 minutes');
expect(exactDuration(432_000)).toBe('7 min 12 s');