Skip to content

Commit 59a6133

Browse files
Merge 1.7.4 — Rounding Out the Sounds
2 parents c17d7a2 + 48d4fcd commit 59a6133

13 files changed

Lines changed: 320 additions & 21 deletions

File tree

1.86 KB
Binary file not shown.

Book/CONLANG_DEVELOPMENT/chapters/a-command-reference.typ

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Add `--help` to any command for its full signature.
1212
#section("Setup")
1313

1414
/ `init <name>`: Create a language sub-book with its five chapters.
15+
/ `scaffold --from "<description>" [--out F]`: (AI) Propose a starter phoneme inventory from a short description, as pasteable Phonology-chapter HJSON (preview-only).
1516
/ `list`: List every defined language with summary counts.
1617
/ `doctor <lang> [--json]`: A health check — phonotactic and lexical problems plus how much of your manuscript's language-words the lexicon already covers.
1718

@@ -68,6 +69,7 @@ Add `--help` to any command for its full signature.
6869

6970
/ `metrics <lang> [--json]`: Quantitative sound-system metrics — phoneme entropy, the Zipf fit, phonotactic saturation, mora weight.
7071
/ `naturalness <lang> [--json]`: Judge the phoneme inventory against cross-linguistic tendencies (voicing symmetry, place coverage, near-universals, size) into a 0–1 score.
72+
/ `suggest-phonemes <lang> [--json]`: Recommend phonemes that would round out the inventory (voiced counterparts, missing near-universals). Advisory.
7173
/ `pairs <lang> [--limit N] [--json]`: Find minimal pairs and the distinctive feature each turns on — the functional load of your contrasts.
7274
/ `harmony <lang> [--json]`: Detect vowel harmony (backness, rounding) by how consistently a word's vowels agree.
7375
/ `distribution <lang> [--json]`: Where each phoneme appears (onset / nucleus / coda, word edges) and any restricted distributions.

Book/CONLANG_DEVELOPMENT/design.typ

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@
210210
#place(bottom + center, dy: -30mm, align(center)[
211211
#text(font: body_family, size: 10pt, fill: ink_smoke, book_author)
212212
#v(2mm)
213-
#text(font: body_family, size: 9pt, fill: ink_smoke, book_year + " · examples assume Inkhaven 1.7.3 or newer")
213+
#text(font: body_family, size: 9pt, fill: ink_smoke, book_year + " · examples assume Inkhaven 1.7.4 or newer")
214214
])
215215
]
216216
pagebreak()

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "inkhaven"
3-
version = "1.7.3"
3+
version = "1.7.4"
44
edition = "2024"
55
# Rust 1.85 stabilised edition 2024. Bump alongside any toolchain-only
66
# feature usage (let-else / chained-comparisons / etc. are already stable).
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Inkhaven 1.7.4 — Rounding Out the Sounds
2+
3+
_Two more phonology tools that go from analysis to advice: a recommender that
4+
tells you what your inventory is missing, and an AI scaffold that proposes a
5+
starter inventory from a description. This rounds out the linguistic layer's
6+
phonology work. No new runtime crates._
7+
8+
## What is your inventory missing?
9+
10+
`naturalness` (1.7.1) tells you where your inventory has gaps. `suggest-phonemes`
11+
tells you what would fill them:
12+
13+
```sh
14+
inkhaven language suggest-phonemes Eldar
15+
```
16+
17+
Through the distinctive-feature matrix it recommends the **voiced counterpart**
18+
missing beside a voiceless obstruent (a `/ɡ/` for your `/k/`), and any
19+
**near-universal segment** the inventory lacks. Every suggestion is advisory — a
20+
"you could add this" — and changes nothing.
21+
22+
## A starter inventory from a description
23+
24+
Beginning a language from a blank page is the hardest part. `scaffold` gives you
25+
somewhere to start:
26+
27+
```sh
28+
inkhaven language scaffold --from "a flowing, vowel-rich island language"
29+
```
30+
31+
The model proposes a coherent starter phoneme inventory and a few syllable
32+
templates, which Inkhaven **validates by parsing it back** (so it never emits
33+
something malformed) and prints as pasteable Phonology-chapter HJSON. It is
34+
preview-only: create a language with `inkhaven language init <name>` and drop the
35+
block into its Phonology chapter. `--out` writes it to a file.
36+
37+
## Dependencies & compatibility
38+
39+
**No new runtime crates.** Everything is additive — two new `language`
40+
subcommands (`suggest-phonemes`, `scaffold`); existing projects and languages are
41+
unchanged. The companion book's command reference is updated to match.
42+
Warning-free (binary and tests). Test suite → 2514.
43+
44+
Every prior release lives under
45+
[`Documentation/RELEASE_NOTES/`](.).

README.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,34 +21,34 @@ one HJSON line away.
2121

2222
![Inkhaven screenshot](screen.png)
2323

24-
## Latest release · 1.7.3The Analyst's Bench
24+
## Latest release · 1.7.4Rounding Out the Sounds
2525

26-
Read the full notes: [`Documentation/RELEASE_NOTES/1.7.3.md`](Documentation/RELEASE_NOTES/1.7.3.md)
26+
Read the full notes: [`Documentation/RELEASE_NOTES/1.7.4.md`](Documentation/RELEASE_NOTES/1.7.4.md)
2727

28-
Three more read-only analyses round out the phonology bench, plus a one-page grammar
29-
sketch that gathers the whole suite into a single readable overview. **No new runtime
30-
crates.** Test suite → 2511, warning-free.
28+
Two more phonology tools that go from analysis to advice: a recommender for what your
29+
inventory is missing, and an AI scaffold that proposes a starter inventory from a
30+
description. **No new runtime crates.** Test suite → 2514, warning-free.
3131

32-
### Vowel harmony & positional distribution
32+
### What is your inventory missing?
3333

34-
`inkhaven language harmony <lang>` measures how consistently a word's vowels agree on
35-
backness and rounding — a *strong*/*tendency*/*none* verdict for vowel harmony.
36-
`inkhaven language distribution <lang>` reports *where* each phoneme appears (onset /
37-
nucleus / coda, word edges) and surfaces **restricted distributions** — the consonant
38-
confined to codas, or barred from a word edge.
34+
`inkhaven language suggest-phonemes <lang>` recommends the phonemes that would round out
35+
the inventory — the voiced counterpart missing beside a voiceless obstruent, a
36+
near-universal segment the inventory lacks — via the distinctive-feature matrix. Advisory;
37+
changes nothing.
3938

40-
### A one-page sketch
39+
### A starter inventory from a description
4140

42-
`inkhaven language sketch <lang>` assembles the phonology, typology and lexicon analyses
43-
into three short prose paragraphs — the analysis dashboard as a page you can read.
44-
Deterministic, needs no provider, always current; `--out` writes it to a file.
41+
`inkhaven language scaffold --from "a flowing, vowel-rich island language"` asks the model
42+
for a coherent starter phoneme inventory and syllable templates, **validates it by parsing
43+
it back**, and prints it as pasteable Phonology-chapter HJSON. Preview-only — a place to
44+
start a new language from a blank page.
4545

4646
### Dependencies & compatibility
4747

48-
**No new runtime crates.** Everything is additive — three new `language` subcommands
49-
(`harmony`, `distribution`, `sketch`); existing projects and languages are unchanged. The
48+
**No new runtime crates.** Everything is additive — two new `language` subcommands
49+
(`suggest-phonemes`, `scaffold`); existing projects and languages are unchanged. The
5050
companion book's command reference is updated to match. Warning-free (binary and tests).
51-
Test suite → 2511.
51+
Test suite → 2514.
5252

5353
Every prior release lives under
5454
[`Documentation/RELEASE_NOTES/`](Documentation/RELEASE_NOTES/).

src/cli/language/metrics.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,37 @@ fn print_harmony(language: &str, r: &HarmonyReport) {
223223
}
224224
}
225225

226+
/// LING-1 Wave-2 — `inkhaven language suggest-phonemes <lang>`: recommend
227+
/// phonemes that would round out the inventory (voiced counterparts, missing
228+
/// near-universals), via the feature matrix. Advisory; changes nothing.
229+
pub(crate) fn suggest_phonemes(project: &Path, language: &str, json: bool) -> Result<()> {
230+
let (store, hierarchy, lang_book) = open_lang_book(project, language)?;
231+
let phon = load_phonology(&store, &hierarchy, &lang_book)?.unwrap_or_default();
232+
let report = crate::conlang::suggest::suggest(&phon);
233+
234+
if json {
235+
println!(
236+
"{}",
237+
serde_json::to_string_pretty(&report)
238+
.map_err(|e| Error::Store(format!("serializing suggestions: {e}")))?
239+
);
240+
return Ok(());
241+
}
242+
243+
println!("phoneme suggestions · {language}");
244+
if report.phoneme_count == 0 {
245+
println!(" (no phoneme inventory — define one in the Phonology chapter)");
246+
} else if report.suggestions.is_empty() {
247+
println!(" the inventory is well-rounded — nothing obvious to add.");
248+
} else {
249+
for s in &report.suggestions {
250+
println!(" + /{}/ — {}", s.ipa, s.reason);
251+
}
252+
println!(" (advisory — add any you like in the Phonology chapter; nothing changed.)");
253+
}
254+
Ok(())
255+
}
256+
226257
/// LING-1 L-P4 — `inkhaven language sketch <lang>`: a one-page, deterministic
227258
/// overview of the language, assembling the whole analysis suite into prose.
228259
pub(crate) fn sketch(project: &Path, language: &str, out: Option<&str>) -> Result<()> {

src/cli/language/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,12 @@ pub fn run(project: &Path, cmd: LanguageCommand) -> Result<()> {
191191
distribution(project, &language, json)
192192
}
193193
LanguageCommand::Sketch { language, out } => sketch(project, &language, out.as_deref()),
194+
LanguageCommand::SuggestPhonemes { language, json } => {
195+
suggest_phonemes(project, &language, json)
196+
}
197+
LanguageCommand::Scaffold { from, out, provider } => {
198+
scaffold_from(project, &from, out.as_deref(), provider.as_deref())
199+
}
194200
LanguageCommand::Trace { language, rule, limit, json } => {
195201
trace(project, &language, &rule, limit, json)
196202
}

src/cli/language/scaffold.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,66 @@ use crate::error::{Error, Result};
88

99
use super::*;
1010

11+
const SCAFFOLD_SYSTEM: &str = "You are a phonologist scaffolding a starter phoneme inventory for a \
12+
constructed language from a short description. Output ONLY a single HJSON block in EXACTLY this shape, \
13+
no prose before or after:\n\
14+
{\n phonemes: [\n { ipa: \"p\", kind: \"consonant\" }\n { ipa: \"a\", kind: \"vowel\" }\n ]\n \
15+
classes: { C: [\"p\", \"t\", \"k\"], V: [\"a\", \"i\", \"u\"] }\n templates: [ { role: \"root\", pattern: \"CVCV\" } ]\n}\n\
16+
Use only standard IPA symbols in the `ipa` fields. Choose an inventory (roughly 12–28 phonemes) and a few \
17+
syllable templates that fit the described character. Keep it coherent and typologically plausible.";
18+
19+
/// LING-1 L-P4 — `inkhaven language scaffold --from "<description>"`: AI-propose a
20+
/// starter phoneme inventory (as pasteable Phonology-chapter HJSON) from a short
21+
/// description. Preview-only: it prints (or writes with `--out`) a validated
22+
/// block; it never touches an existing language.
23+
pub(crate) fn scaffold_from(
24+
project: &Path,
25+
description: &str,
26+
out: Option<&str>,
27+
provider: Option<&str>,
28+
) -> Result<()> {
29+
let layout = ProjectLayout::new(project);
30+
layout.require_initialized()?;
31+
let cfg = Config::load_layered(&layout.config_path())?;
32+
let ai = crate::ai::AiClient::from_config(&cfg.llm)?;
33+
let (model, _env) = ai.resolve_provider(&cfg.llm, provider)?;
34+
eprintln!("inkhaven language scaffold · model: {model}");
35+
36+
let raw = crate::ai::stream::collect_blocking(
37+
ai.client.clone(),
38+
model.to_string(),
39+
Some(SCAFFOLD_SYSTEM.to_string()),
40+
format!("Description: {description}"),
41+
)
42+
.map_err(Error::Config)?;
43+
44+
// Validate: the proposal must parse as a Phonology, or we don't emit garbage.
45+
let phon = crate::conlang::Phonology::from_hjson(&raw)
46+
.map_err(|e| Error::Config(format!("the model's proposal did not parse: {e}")))?
47+
.ok_or_else(|| Error::Config("the model produced an empty inventory".to_string()))?;
48+
49+
let block = crate::language_entry::extract_hjson_block(&raw).unwrap_or(raw.trim()).trim();
50+
match out {
51+
Some(path) => {
52+
crate::io_atomic::write(Path::new(path), format!("{block}\n").as_bytes())
53+
.map_err(|e| Error::Config(format!("write {path}: {e}")))?;
54+
eprintln!(
55+
"wrote a {}-phoneme starter inventory to {path}",
56+
phon.phonemes.len()
57+
);
58+
}
59+
None => {
60+
eprintln!(
61+
"proposed a {}-phoneme inventory — create a language with `inkhaven language init \
62+
<name>` and paste this into its Phonology chapter:\n",
63+
phon.phonemes.len()
64+
);
65+
println!("{block}");
66+
}
67+
}
68+
Ok(())
69+
}
70+
1171
/// Open a project and resolve a language sub-book under the `Language`
1272
/// system book. The shared front-half of every conlang command — returns the
1373
/// open `Store` (kept alive for the DuckDB lock), the loaded `Hierarchy`, and

0 commit comments

Comments
 (0)