feat(dreaming): add dreaming#1442
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…enerate-skills.yml Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@greptileai review Auto-nudge from |
…food dump Add .manuscripts/20260618T050021Z-649b15eb/ (acceptance proof + research provenance pointer to the 20260617-233018 print run) to satisfy the publish-package completeness gate. Remove dogfood-live-raw.txt, which leaked local user paths — same call the previous publish made. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@greptileai review Auto-nudge from |
Greptile SummaryThis PR adds a new
Confidence Score: 5/5Safe to merge; all novel commands are additive, the previous PR empty-resource bug in workflow archive is fixed, and no regressions touch existing library code. The new CLI is self-contained under library/education/dreaming and makes no changes to shared library infrastructure. Previously flagged issues (http corpus fetch, FTS5 single-token quoting, CSV line counter, empty workflow resources) are either addressed in this rebuild or carry over from the prior review. The two remaining new findings are rounding imprecision for negative JSON output values and a misleading last-0-days human output, neither of which affects data correctness or command reliability. library/education/dreaming/internal/cli/roadmap.go (round1/round2 helpers used across plan, roadmap, and diet JSON output) and library/education/dreaming/internal/cli/diet.go (window=0 human output). Important Files Changed
|
| switch { | ||
| case flagRegex != "" || flagTense != "": | ||
| pattern := flagRegex | ||
| if flagTense != "" { | ||
| pattern = tensePresets[flagTense] | ||
| } |
There was a problem hiding this comment.
When both
--tense and --regex are provided, the regex pattern is silently discarded with no warning. A user who specifies --regex "\bhab(ía|ían)\b" --tense imperfect will see only the tense preset results — their explicit regex is dropped without any feedback, which is confusing and hard to diagnose.
| switch { | |
| case flagRegex != "" || flagTense != "": | |
| pattern := flagRegex | |
| if flagTense != "" { | |
| pattern = tensePresets[flagTense] | |
| } | |
| switch { | |
| case flagRegex != "" || flagTense != "": | |
| if flagRegex != "" && flagTense != "" { | |
| fmt.Fprintln(cmd.ErrOrStderr(), "warning: --tense overrides --regex when both are specified") | |
| } | |
| pattern := flagRegex | |
| if flagTense != "" { | |
| pattern = tensePresets[flagTense] | |
| } |
|
|
||
| // openCorpusSource returns a reader for a local path or an https:// URL. | ||
| func openCorpusSource(ctx context.Context, src string) (io.ReadCloser, error) { | ||
| if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") { |
There was a problem hiding this comment.
The
openCorpusSource function accepts plain http:// URLs in addition to https://, allowing corpus data to be fetched over an unencrypted channel. A network observer can inject arbitrary JSON into the response, causing crafted cue text to be merged persistently into the local SQLite store.
| if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") { | |
| if strings.HasPrefix(src, "https://") { |
| if seconds <= 0 { | ||
| return nil, fmt.Errorf("CSV line %d: non-positive duration", line) | ||
| } |
There was a problem hiding this comment.
A row with a zero or negative duration aborts the entire import with an error, whereas a row with a missing date is silently skipped. This asymmetry means a single summary row with
0 minutes fails the whole run and discards all previously valid parsed rows.
| if seconds <= 0 { | |
| return nil, fmt.Errorf("CSV line %d: non-positive duration", line) | |
| } | |
| if seconds <= 0 { | |
| continue // skip rows with zero/negative duration (e.g. summary/note rows) | |
| } |
Greptile SummaryThis PR introduces a full-featured offline-first CLI (
Confidence Score: 4/5The new CLI module is self-contained and additive; no existing library code is modified. The two concrete issues found are limited to user-facing error messaging quality, not data integrity. The concordance command passes single-word FTS5 queries unescaped, so a query like library/education/dreaming/internal/cli/concordance.go (FTS5 query escaping), library/education/dreaming/internal/cli/external_import.go (CSV line counter), and the three novel-command test stubs (next_test.go, diet_test.go, external_import_test.go). Important Files Changed
|
| func ftsQuery(q string) string { | ||
| q = strings.TrimSpace(q) | ||
| if q == "" { | ||
| return q | ||
| } | ||
| if strings.ContainsAny(q, " \t") { | ||
| return `"` + strings.ReplaceAll(q, `"`, `""`) + `"` | ||
| } | ||
| return q | ||
| } |
There was a problem hiding this comment.
FTS5 special characters in single-word queries produce cryptic SQLite errors. A user query like
*, (foo, or -bar is passed directly to FTS5 as an unquoted token, which the engine rejects with a raw parse error (fts5: syntax error near "*") rather than a helpful message. Multi-word inputs are correctly phrase-quoted, but single-word inputs bypass all escaping. Wrapping single tokens in double quotes the same way multi-word phrases are handled fixes this.
| func ftsQuery(q string) string { | |
| q = strings.TrimSpace(q) | |
| if q == "" { | |
| return q | |
| } | |
| if strings.ContainsAny(q, " \t") { | |
| return `"` + strings.ReplaceAll(q, `"`, `""`) + `"` | |
| } | |
| return q | |
| } | |
| func ftsQuery(q string) string { | |
| q = strings.TrimSpace(q) | |
| if q == "" { | |
| return q | |
| } | |
| // Always phrase-quote: wrapping in double-quotes makes FTS5 treat the | |
| // input as a literal phrase regardless of whether it is a single word or | |
| // multiple words. This prevents FTS5 special characters (*, -, +, (, ), | |
| // ^, AND, OR, NOT, NEAR) from being interpreted as operators and causing | |
| // a cryptic "fts5: syntax error" instead of a user-friendly no-results. | |
| return `"` + strings.ReplaceAll(q, `"`, `""`) + `"` | |
| } |
| line := 1 | ||
| for { | ||
| rec, err := r.Read() | ||
| if err == io.EOF { | ||
| break | ||
| } | ||
| if err != nil { | ||
| return nil, fmt.Errorf("CSV line %d: %w", line, err) | ||
| } | ||
| line++ |
There was a problem hiding this comment.
The
line counter is incremented only after a successful r.Read(), so when the CSV reader itself returns an error (malformed quoting, unexpected field count, etc.) the error message cites the previous row's number. For example, a malformed first data row (file line 2) is reported as "CSV line 1". Moving the increment before the error check fixes the off-by-one.
| line := 1 | |
| for { | |
| rec, err := r.Read() | |
| if err == io.EOF { | |
| break | |
| } | |
| if err != nil { | |
| return nil, fmt.Errorf("CSV line %d: %w", line, err) | |
| } | |
| line++ | |
| line := 1 | |
| for { | |
| line++ | |
| rec, err := r.Read() | |
| if err == io.EOF { | |
| break | |
| } | |
| if err != nil { | |
| return nil, fmt.Errorf("CSV line %d: %w", line, err) | |
| } |
| func TestNovelNextCommandTODO(t *testing.T) { | ||
| t.Skip("TODO: implement table-driven tests for next") |
There was a problem hiding this comment.
Novel command tests are all empty stubs. The three most complex novel commands —
next, diet, and external import — each ship with a test file containing only t.Skip("TODO"). The dreaming_domain_logic_test.go covers shared helpers well, but the command-level SQL paths (level-band query, difficulty ordering, trend analysis, CSV row parsing/validation) have no coverage. A parseExternalCSV unit test in particular would have caught the line-counter off-by-one noted separately. This pattern also appears in diet_test.go and external_import_test.go.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
…hardcoded empty list
resources was []string{}, so every archive run iterated nothing and
reported 'Archived 0 items across 0 resources'. Fall back to
defaultSyncResources() exactly like the sync command. Verified live:
archive now syncs 4 resources.
Addresses greptile review on mvanhorn#1442.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hardcoded empty list
resources was []string{}, so every archive run iterated nothing and
reported 'Archived 0 items across 0 resources'. Fall back to
defaultSyncResources() exactly like the sync command. Verified live:
archive now syncs 4 resources.
Addresses greptile review on mvanhorn#1442.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
b203237 to
5cb5ac4
Compare
|
@greptileai review Auto-nudge from |
dreaming
Every Dreaming Spanish and French tracking feature in one offline-first, agent-native CLI: roadmap math, transcript concordance, and bulk hour-logging.
API: dreaming | Category: education | Press version: 4.24.0
Spec: Hand-authored from the internal Netlify Functions API (no official spec)
Publication Path
New print. (Supersedes closed PR #1279: its branch base predated ~1,200 commits on main, which made the changed-CLI Govulncheck selection scan 304 unrelated modules and fail on pre-existing stale go.mod files elsewhere. This PR is rebuilt on current main and includes the fix for the greptile review finding on
nextordering — unrated videos now sort last.)CLI Shape
Novel Commands
nextexternal importdietroadmaptranscriptconcordanceWhat This CLI Does
Dreaming's web app shows your hours but won't plan, search, or bulk-import for you, and the community's features are scattered across a dozen browser extensions and scripts. This CLI unifies them into one agent-native tool backed by a local SQLite mirror of your catalog, daily series, and external-hours log - so
nextpicks your next video offline,external importclears a CSV backlog in one shot, androadmaplays out the whole L1-L7 fluency ladder with personalized ETAs.The Hero Feature - search Dreaming videos by content.
It also builds a searchable transcript corpus: transcript sync caches cue-level captions, concordance does keyword/phrase/regex/verb-tense KWIC search across them (each hit links to the video), and the corpus can be exported/imported so a complete corpus is built once and shared (impersonal data only). Example: search for video content related to what you are learning - specific tenses like Subjunctive or phrases like "lo que".
The CLI and skill will also download a video by ID or search the catalog by name and download it via ffmpeg/yt-dlp.
Manuscripts
Validation Results
Gaps
.printing-press.jsonhas nodescriptionorspec_urlfield (description above is from the README/SKILL; the API has no official spec to link).categorywas set at publish time: the v4.27.1 binary's category enum does not yet includeeducation, though the library tree does — packaged via--category otherwith the correct education module path, then corrected in the manifest.🤖 Generated with Claude Code