calliope-ts 0.0.2 → 0.0.4

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.
Files changed (160) hide show
  1. package/README.md +32 -20
  2. package/dist/caesura.d.ts +33 -0
  3. package/dist/caesura.d.ts.map +1 -0
  4. package/dist/caesura.js +202 -0
  5. package/dist/calliope/boundaries.d.ts +19 -0
  6. package/dist/calliope/boundaries.d.ts.map +1 -0
  7. package/dist/calliope/boundaries.js +182 -0
  8. package/dist/calliope/bracketing.d.ts +11 -0
  9. package/dist/calliope/bracketing.d.ts.map +1 -0
  10. package/dist/calliope/bracketing.js +416 -0
  11. package/dist/calliope/deps.d.ts +4 -0
  12. package/dist/calliope/deps.d.ts.map +1 -0
  13. package/dist/calliope/deps.js +181 -0
  14. package/dist/calliope/engine.d.ts +3 -0
  15. package/dist/calliope/engine.d.ts.map +1 -0
  16. package/dist/calliope/engine.js +71 -0
  17. package/dist/calliope/feats.d.ts +9 -0
  18. package/dist/calliope/feats.d.ts.map +1 -0
  19. package/dist/calliope/feats.js +45 -0
  20. package/dist/calliope/names.d.ts +7 -0
  21. package/dist/calliope/names.d.ts.map +1 -0
  22. package/dist/calliope/names.js +42 -0
  23. package/dist/calliope/postag.d.ts +8 -0
  24. package/dist/calliope/postag.d.ts.map +1 -0
  25. package/dist/calliope/postag.js +250 -0
  26. package/dist/calliope/prosodic.d.ts +3 -0
  27. package/dist/calliope/prosodic.d.ts.map +1 -0
  28. package/dist/calliope/prosodic.js +275 -0
  29. package/dist/calliope/relstress.d.ts +4 -0
  30. package/dist/calliope/relstress.d.ts.map +1 -0
  31. package/dist/calliope/relstress.js +688 -0
  32. package/dist/calliope/stressrules.d.ts +28 -0
  33. package/dist/calliope/stressrules.d.ts.map +1 -0
  34. package/dist/calliope/stressrules.js +147 -0
  35. package/dist/calliope/syntax.d.ts +38 -0
  36. package/dist/calliope/syntax.d.ts.map +1 -0
  37. package/dist/calliope/syntax.js +234 -0
  38. package/dist/calliope/udpos.d.ts +13 -0
  39. package/dist/calliope/udpos.d.ts.map +1 -0
  40. package/dist/calliope/udpos.js +156 -0
  41. package/dist/clio/caesura.d.ts +27 -0
  42. package/dist/clio/caesura.d.ts.map +1 -0
  43. package/dist/clio/caesura.js +148 -0
  44. package/dist/clio/depfix.d.ts +13 -0
  45. package/dist/clio/depfix.d.ts.map +1 -0
  46. package/dist/clio/depfix.js +84 -0
  47. package/dist/clio/display.d.ts +32 -0
  48. package/dist/clio/display.d.ts.map +1 -0
  49. package/dist/clio/display.js +976 -0
  50. package/dist/clio/engine.d.ts +3 -0
  51. package/dist/clio/engine.d.ts.map +1 -0
  52. package/dist/clio/engine.js +28 -0
  53. package/dist/clio/parser.d.ts +10 -0
  54. package/dist/clio/parser.d.ts.map +1 -0
  55. package/dist/clio/parser.js +696 -0
  56. package/dist/clio/phonological.d.ts +41 -0
  57. package/dist/clio/phonological.d.ts.map +1 -0
  58. package/dist/clio/phonological.js +788 -0
  59. package/dist/clio/phrasestress.d.ts +6 -0
  60. package/dist/clio/phrasestress.d.ts.map +1 -0
  61. package/dist/clio/phrasestress.js +106 -0
  62. package/dist/clio/pipeline.d.ts +11 -0
  63. package/dist/clio/pipeline.d.ts.map +1 -0
  64. package/dist/clio/pipeline.js +147 -0
  65. package/dist/clio/rhyme.d.ts +65 -0
  66. package/dist/clio/rhyme.d.ts.map +1 -0
  67. package/dist/clio/rhyme.js +761 -0
  68. package/dist/clio/scandroid.d.ts +17 -0
  69. package/dist/clio/scandroid.d.ts.map +1 -0
  70. package/dist/clio/scandroid.js +435 -0
  71. package/dist/clio/scansion.d.ts +46 -0
  72. package/dist/clio/scansion.d.ts.map +1 -0
  73. package/dist/clio/scansion.js +1086 -0
  74. package/dist/clio/semantics.d.ts +44 -0
  75. package/dist/clio/semantics.d.ts.map +1 -0
  76. package/dist/clio/semantics.js +139 -0
  77. package/dist/clio/stress.d.ts +83 -0
  78. package/dist/clio/stress.d.ts.map +1 -0
  79. package/dist/clio/stress.js +1737 -0
  80. package/dist/clio/tagfix.d.ts +6 -0
  81. package/dist/clio/tagfix.d.ts.map +1 -0
  82. package/dist/clio/tagfix.js +101 -0
  83. package/dist/display.d.ts +0 -6
  84. package/dist/display.d.ts.map +1 -1
  85. package/dist/display.js +322 -141
  86. package/dist/engine.d.ts +9 -0
  87. package/dist/engine.d.ts.map +1 -0
  88. package/dist/engine.js +12 -0
  89. package/dist/index.d.ts +4 -3
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +169 -26
  92. package/dist/parser.d.ts +3 -3
  93. package/dist/parser.d.ts.map +1 -1
  94. package/dist/parser.js +320 -563
  95. package/dist/phonological.d.ts +1 -1
  96. package/dist/phonological.d.ts.map +1 -1
  97. package/dist/phonological.js +73 -4
  98. package/dist/phrasestress.d.ts +6 -0
  99. package/dist/phrasestress.d.ts.map +1 -0
  100. package/dist/phrasestress.js +106 -0
  101. package/dist/rhyme.d.ts +40 -1
  102. package/dist/rhyme.d.ts.map +1 -1
  103. package/dist/rhyme.js +435 -7
  104. package/dist/scansion.d.ts +9 -0
  105. package/dist/scansion.d.ts.map +1 -1
  106. package/dist/scansion.js +145 -18
  107. package/dist/semantics.d.ts +44 -0
  108. package/dist/semantics.d.ts.map +1 -0
  109. package/dist/semantics.js +139 -0
  110. package/dist/stress.d.ts +127 -4
  111. package/dist/stress.d.ts.map +1 -1
  112. package/dist/stress.js +834 -61
  113. package/dist/types.d.ts +15 -0
  114. package/dist/types.d.ts.map +1 -1
  115. package/package.json +5 -3
  116. package/src/caesura.ts +201 -0
  117. package/src/calliope/boundaries.ts +190 -0
  118. package/src/calliope/bracketing.ts +390 -0
  119. package/src/calliope/deps.ts +160 -0
  120. package/src/calliope/engine.ts +77 -0
  121. package/src/calliope/feats.ts +46 -0
  122. package/src/calliope/names.ts +44 -0
  123. package/src/calliope/postag.ts +253 -0
  124. package/src/calliope/prosodic.ts +262 -0
  125. package/src/calliope/relstress.ts +645 -0
  126. package/src/calliope/stressrules.ts +147 -0
  127. package/src/calliope/syntax.ts +218 -0
  128. package/src/calliope/udpos.ts +152 -0
  129. package/src/calliope_src_contents.md +19049 -0
  130. package/src/clio/caesura.ts +145 -0
  131. package/src/clio/depfix.ts +88 -0
  132. package/src/clio/display.ts +1042 -0
  133. package/src/clio/engine.ts +38 -0
  134. package/src/clio/parser.ts +845 -0
  135. package/src/clio/phonological.ts +849 -0
  136. package/src/clio/phrasestress.ts +108 -0
  137. package/src/clio/pipeline.ts +154 -0
  138. package/src/clio/rhyme.ts +740 -0
  139. package/src/clio/scandroid.ts +434 -0
  140. package/src/clio/scansion.ts +1130 -0
  141. package/src/clio/semantics.ts +134 -0
  142. package/src/clio/stress.ts +1731 -0
  143. package/src/clio/tagfix.ts +104 -0
  144. package/src/display.ts +321 -137
  145. package/src/engine.ts +22 -0
  146. package/src/index.ts +176 -32
  147. package/src/parser.ts +346 -682
  148. package/src/phonological.ts +72 -4
  149. package/src/phrasestress.ts +108 -0
  150. package/src/rhyme.ts +428 -8
  151. package/src/scansion.ts +136 -15
  152. package/src/semantics.ts +134 -0
  153. package/src/stress.ts +794 -64
  154. package/src/types.ts +39 -4
  155. package/tests/DataForHayesLinesOnly.txt +364 -0
  156. package/tests/DataForHayesStressSymbolsRevised.txt +728 -0
  157. package/tests/basic.test.ts +443 -37
  158. package/tests/bench-hayes.mjs +72 -0
  159. package/tests/wagner-stress.test.ts +188 -0
  160. package/vitest.config.ts +0 -15
@@ -0,0 +1,6 @@
1
+ import { ClsWord } from '../types.js';
2
+ /**
3
+ * Populate `word.phraseStress` for every word of a parsed sentence.
4
+ */
5
+ export declare function computePhraseStress(words: ClsWord[]): void;
6
+ //# sourceMappingURL=phrasestress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phrasestress.d.ts","sourceRoot":"","sources":["../../src/clio/phrasestress.ts"],"names":[],"mappings":"AAgCA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAyBtC;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,CAkB1D"}
@@ -0,0 +1,106 @@
1
+ // phrasestress.ts — McAleese's Phrase-Stress phase (previously SKIPPED).
2
+ //
3
+ // The pipeline used to map lexical stress straight to relative stress (plus a
4
+ // single "+1 on the rightmost content word"), leaving `ClsWord.phraseStress`
5
+ // a vestigial field stuck at 0. This module restores the genuine phase that
6
+ // sits BETWEEN lexical and relative stress in McAleese's procedure (thesis
7
+ // Appendix A; Chomsky & Halle's Nuclear + Compound Stress Rules):
8
+ //
9
+ // 1. every word starts at 1;
10
+ // 2. the Compound Stress Rule pins a compound's subordinate element at the
11
+ // floor while its principal stays in the ramp;
12
+ // 3. the recursive Nuclear Stress Rule ramps the principal stress of each
13
+ // successive *stressed* word left-to-right, so prominence rises to the
14
+ // phrase's nuclear (rightmost) peak.
15
+ //
16
+ // For the right-branching structure of English declaratives the recursive NSR
17
+ // reduces to a monotone ramp over the stressed words — function words and
18
+ // compound-subordinates pinned at 1. NOTE the convention: here 1 is the FLOOR
19
+ // and the ramp climbs RIGHTWARD to the nuclear peak (the "If hairs…" example
20
+ // below). McAleese's "Mary ate sweet ice cream" worked example uses the
21
+ // OPPOSITE (SPE) numbering — 1 = PRIMARY/strongest, weakened outward — so his
22
+ // ICE cream (primary on "ice") is reproduced here as ice=PEAK, cream=floor
23
+ // (the compound's HEAD "cream" is the subordinate, pinned at 1):
24
+ //
25
+ // "Mary ate sweet ice cream" -> Mary2 ate3 sweet4 ice5 cream1 (ICE cream: head subordinate)
26
+ // "If hairs be wires, black wires grow on her head" -> 1 2 3 4 5 6 7 1 1 8
27
+ //
28
+ // (Marked / left-branching structures are approximated by the same ramp; a
29
+ // constituent-tree-driven refinement over `sentence.nodes` is a flagged
30
+ // follow-up. This module reads only POS / content flags and surface order,
31
+ // and writes only the previously-unused phraseStress field — purely additive.)
32
+ import { isPunctuation } from './parser.js';
33
+ import { compoundStressSide } from './stress.js';
34
+ /** Sentence-final punctuation resets the nuclear ramp (the NSR is
35
+ * sentence-bounded). A comma / colon / dash does NOT — the ramp runs across
36
+ * an internal intonational break (cf. "If hairs…, black wires…" → 1234…5,678). */
37
+ const SENTENCE_FINAL = new Set(['.', '!', '?', '…']);
38
+ /** Common-/proper-noun POS tags, gating which compounds floor an element. */
39
+ const NOUN_TAGS = new Set(['NN', 'NNS', 'NNP', 'NNPS']);
40
+ /**
41
+ * Proclitic POS tags pinned at the phrase-stress floor: the NSR ramp applies to
42
+ * *stressed words* (nouns, adjectives, adverbs, and VERBS — including the
43
+ * copula/auxiliary "be" and modals, which bear lexical stress even though they
44
+ * reduce in the contour), while pure clitics carry none. Verbs are NOT here,
45
+ * so McAleese's "if hairs BE wires…" keeps `be` in the ramp (=3). A word the
46
+ * earlier pipeline promoted to content (a phrasal-verb particle, a focus
47
+ * demonstrative — `isContent === true`) is un-pinned and ramps.
48
+ */
49
+ const PINNED_POS = new Set([
50
+ 'IN', 'TO', 'DT', 'CC', 'PRP', 'PRP$', 'WP', 'WP$', 'WDT', 'EX', 'POS',
51
+ ]);
52
+ /**
53
+ * Populate `word.phraseStress` for every word of a parsed sentence.
54
+ */
55
+ export function computePhraseStress(words) {
56
+ const subordinate = compoundSubordinates(words);
57
+ let ramp = 1; // running nuclear ramp; the floor for pinned words is 1
58
+ for (const w of words) {
59
+ if (isPunctuation(w.lexicalClass)) {
60
+ w.phraseStress = 0;
61
+ if (SENTENCE_FINAL.has(w.word))
62
+ ramp = 1;
63
+ continue;
64
+ }
65
+ // Pinned at the floor: a proclitic function word bears no nuclear ramp, and
66
+ // a compound's subordinate element is demoted by the Compound Stress Rule.
67
+ const isClitic = PINNED_POS.has(w.lexicalClass) && !w.isContent;
68
+ if (isClitic || subordinate.has(w)) {
69
+ w.phraseStress = 1;
70
+ continue;
71
+ }
72
+ w.phraseStress = ++ramp;
73
+ }
74
+ }
75
+ /**
76
+ * Identify the subordinate (pinned) element of each surface-adjacent compound,
77
+ * delegating the direction to `compoundStressSide` (the shared rule, so phrase
78
+ * stress and the lexical compound pass cannot disagree).
79
+ * - fore-stress ('left') → the HEAD (right) element is subordinate: SEA·shore,
80
+ * ICE cream, KITCHen table — the Compound Stress Rule default for N+N;
81
+ * - right-stress ('right') → the MODIFIER (left) is subordinate: apple PIE,
82
+ * and proper-name pairs (New YORK);
83
+ * - Adjective+noun ("sweet cream") is phrasal, not a compound (`side` is
84
+ * 'right' there too, so the modifier "sweet" is pinned and "cream" ramps —
85
+ * i.e. end-stress, as desired).
86
+ */
87
+ function compoundSubordinates(words) {
88
+ const subs = new Set();
89
+ for (let i = 0; i + 1 < words.length; i++) {
90
+ const a = words[i], b = words[i + 1];
91
+ if (isPunctuation(a.lexicalClass) || isPunctuation(b.lexicalClass))
92
+ continue;
93
+ if (!a.isContent || !b.isContent)
94
+ continue;
95
+ const side = compoundStressSide(a.word, a.lexicalClass, b.word, b.lexicalClass);
96
+ // Floor an element only for a genuine N+N compound; an Adj+N ("sweet ice",
97
+ // "red car") is phrasal — its end-stress emerges from the ramp itself, so we
98
+ // pin nothing and let the adjective ramp (McAleese: sweet=4, above cream).
99
+ const bothNouns = NOUN_TAGS.has(a.lexicalClass) && NOUN_TAGS.has(b.lexicalClass);
100
+ if (side === 'left')
101
+ subs.add(b); // fore-stress compound: head subordinate (ICE cream)
102
+ else if (side === 'right' && bothNouns)
103
+ subs.add(a); // right-stress N+N: modifier subordinate (apple PIE)
104
+ }
105
+ return subs;
106
+ }
@@ -0,0 +1,11 @@
1
+ import { type ReadingStanza } from './display.js';
2
+ import type { LineResult } from '../types.js';
3
+ export type { ReadingStanza } from './display.js';
4
+ export { renderUnifiedDisplay, renderReadingView, renderFullLegend } from './display.js';
5
+ export { parseDocument, isPunctuation } from './parser.js';
6
+ export { renderHierarchy, renderKeyStresses } from './phonological.js';
7
+ export { clioEngine } from './engine.js';
8
+ export declare function analyzeStanzasClio(text: string): LineResult[][];
9
+ export declare function analyzeTextClio(text: string): LineResult[];
10
+ export declare function analyzeReadingDocumentClio(text: string): ReadingStanza[];
11
+ //# sourceMappingURL=pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/clio/pipeline.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,KAAK,EAC6D,UAAU,EAClF,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAGlD,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzF,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAkEzC,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,EAAE,CAyB/D;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,CAE1D;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAyBxE"}
@@ -0,0 +1,147 @@
1
+ // clio/pipeline.ts — the FROZEN Clio analysis pipeline (the "control group").
2
+ //
3
+ // This is a verbatim snapshot of index.ts's orchestration (processLine +
4
+ // applyContinuityRename + analyzeStanzas/analyzeText/analyzeReadingDocument) as it
5
+ // stood at the structure-first rebuild, wired EXCLUSIVELY to clio-local frozen
6
+ // modules (clio/parser, clio/scansion, clio/rhyme, clio/scandroid, clio/phonological,
7
+ // clio/display) and hard-wired to clioEngine. Nothing here imports the live
8
+ // top-level pipeline, so changes to the regular (Calliope) pipeline — including the
9
+ // shared scorer — CANNOT affect Clio's output. Clio is grounded in this tree alone.
10
+ //
11
+ // DO NOT evolve this file or any clio/* module with the Calliope rebuild.
12
+ import { parseDocument } from './parser.js';
13
+ import { renderHierarchy, renderKeyStresses } from './phonological.js';
14
+ import { extractKeyStresses, scoreMeters, applyStanzaConsensus, applyRhythmLayer, applyMetricalityLayer, } from './scansion.js';
15
+ import { applyRhymeAndForm } from './rhyme.js';
16
+ import { scandroidCorralWeird, scandroidMaximizeNormal, stressToMarks } from './scandroid.js';
17
+ import { clioEngine } from './engine.js';
18
+ // Frozen Clio renderers + parse entry, so the CLI can render Clio output entirely
19
+ // from the clio/ tree (live display.ts changes cannot reach Clio).
20
+ export { renderUnifiedDisplay, renderReadingView, renderFullLegend } from './display.js';
21
+ export { parseDocument, isPunctuation } from './parser.js';
22
+ export { renderHierarchy, renderKeyStresses } from './phonological.js';
23
+ export { clioEngine } from './engine.js';
24
+ /** Scan one verse line (which may parse into several grammatical sentences). */
25
+ function processLine(sents) {
26
+ if (sents.length === 0)
27
+ return null;
28
+ const iusPerSent = [];
29
+ for (const sent of sents) {
30
+ iusPerSent.push(clioEngine.analyzeSentence(sent));
31
+ }
32
+ const words = sents.flatMap(s => s.words);
33
+ const ius = iusPerSent.flat();
34
+ let merged;
35
+ if (sents.length === 1) {
36
+ merged = sents[0];
37
+ }
38
+ else {
39
+ words.forEach((w, i) => { w.index = i + 1; });
40
+ merged = {
41
+ index: sents[0].index, nodes: null,
42
+ dependencies: sents.flatMap(s => s.dependencies), words, xml: '',
43
+ };
44
+ }
45
+ const keyStresses = extractKeyStresses(ius, words);
46
+ const phonoDetail = scoreMeters(keyStresses, words, ius);
47
+ phonoDetail.all = renderHierarchy(ius, words);
48
+ phonoDetail.keyStresses = renderKeyStresses(ius, words, keyStresses);
49
+ const stressPattern = words.flatMap(w => w.syllables.map(s => s.relativeStress ?? 'w'));
50
+ const marks = stressToMarks(stressPattern);
51
+ const actualFeet = phonoDetail.footCount > 0 ? phonoDetail.footCount : 5;
52
+ const corral = scandroidCorralWeird(marks, actualFeet);
53
+ const max = scandroidMaximizeNormal(marks, actualFeet);
54
+ const corralResult = corral.footlist.length
55
+ ? { meter: 'iambic', scansion: corral.footlist.map(f => f.replace(/[()]/g, '')).join(' | '),
56
+ certainty: 0, weightScore: 0, maxPossibleWeight: 0, algorithm: 'Scandroid Corral the Weird' }
57
+ : undefined;
58
+ const maxResult = max.footlist.length
59
+ ? { meter: 'iambic', scansion: max.footlist.map(f => f.replace(/[()]/g, '')).join(' | '),
60
+ certainty: 0, weightScore: 0, maxPossibleWeight: 0, algorithm: 'Scandroid Maximise the Normal' }
61
+ : undefined;
62
+ return {
63
+ sentence: merged, phonologicalHierarchy: ius, keyStresses,
64
+ phonologicalScansion: phonoDetail, scandroidCorral: corralResult, scandroidMaximise: maxResult,
65
+ };
66
+ }
67
+ function applyContinuityRename(results) {
68
+ const noted = results.filter(r => r.phonologicalScansion.rhythmNote).length;
69
+ if (results.length > 0 && noted >= results.length / 2)
70
+ return;
71
+ for (const res of results) {
72
+ const d = res.phonologicalScansion;
73
+ if (!d.consensusMeter)
74
+ continue;
75
+ const family = d.consensusMeter.split(' ')[0];
76
+ const forced = scoreMeters(res.keyStresses, res.sentence.words, res.phonologicalHierarchy, family);
77
+ if (!forced || forced.meterName === 'free verse' || forced.footCount <= 0)
78
+ continue;
79
+ d.standaloneMeter = d.meter;
80
+ d.meter = forced.meter;
81
+ d.meterName = forced.meterName;
82
+ d.footCount = forced.footCount;
83
+ d.scansion = forced.scansion;
84
+ d.certainty = forced.certainty;
85
+ d.summary = forced.summary;
86
+ d.consensusMeter = undefined;
87
+ }
88
+ }
89
+ export function analyzeStanzasClio(text) {
90
+ const stanzas = text.split(/\n\s*\n/);
91
+ const results = [];
92
+ for (const stanza of stanzas) {
93
+ const lines = stanza.split('\n').filter(l => l.trim() !== '');
94
+ const stanzaResults = [];
95
+ for (const line of lines) {
96
+ const doc = parseDocument(line);
97
+ const res = processLine(doc.sentences);
98
+ if (res)
99
+ stanzaResults.push(res);
100
+ }
101
+ applyStanzaConsensus(stanzaResults.map(r => r.phonologicalScansion));
102
+ applyRhythmLayer(stanzaResults.map(r => r.phonologicalScansion));
103
+ applyContinuityRename(stanzaResults);
104
+ results.push(stanzaResults);
105
+ }
106
+ if (results.length > 1) {
107
+ const all = results.flat();
108
+ applyStanzaConsensus(all.map(r => r.phonologicalScansion));
109
+ applyContinuityRename(all);
110
+ for (const st of results)
111
+ applyRhythmLayer(st.map(r => r.phonologicalScansion));
112
+ }
113
+ applyMetricalityLayer(results.flatMap(st => st.map(r => r.phonologicalScansion)));
114
+ applyRhymeAndForm(results);
115
+ return results;
116
+ }
117
+ export function analyzeTextClio(text) {
118
+ return analyzeStanzasClio(text).flat();
119
+ }
120
+ export function analyzeReadingDocumentClio(text) {
121
+ const stanzas = text.split(/\n\s*\n/);
122
+ const out = [];
123
+ for (const stanza of stanzas) {
124
+ const rawLines = stanza.split('\n').filter(l => l.trim() !== '');
125
+ if (rawLines.length === 0)
126
+ continue;
127
+ const lines = rawLines.map(raw => {
128
+ const doc = parseDocument(raw);
129
+ const res = processLine(doc.sentences);
130
+ return { raw, results: res ? [res] : [] };
131
+ });
132
+ applyStanzaConsensus(lines.flatMap(l => l.results.map(r => r.phonologicalScansion)));
133
+ applyRhythmLayer(lines.flatMap(l => l.results.map(r => r.phonologicalScansion)));
134
+ applyContinuityRename(lines.flatMap(l => l.results));
135
+ out.push({ lines });
136
+ }
137
+ if (out.length > 1) {
138
+ const all = out.flatMap(st => st.lines.flatMap(l => l.results));
139
+ applyStanzaConsensus(all.map(r => r.phonologicalScansion));
140
+ applyContinuityRename(all);
141
+ for (const st of out)
142
+ applyRhythmLayer(st.lines.flatMap(l => l.results.map(r => r.phonologicalScansion)));
143
+ }
144
+ applyMetricalityLayer(out.flatMap(st => st.lines.flatMap(l => l.results.map(r => r.phonologicalScansion))));
145
+ applyRhymeAndForm(out.map(st => st.lines.flatMap(l => l.results)));
146
+ return out;
147
+ }
@@ -0,0 +1,65 @@
1
+ import { LineResult, StressLevel } from '../types.js';
2
+ export type RhymeTypeName = 'identical' | 'rich' | 'perfect' | 'family' | 'assonant' | 'consonant' | 'augmented' | 'diminished' | 'wrenched' | 'eye';
3
+ export interface RhymePair {
4
+ type: RhymeTypeName;
5
+ /** masculine (stress on final syllable) / feminine (penult) / dactylic (antepenult) */
6
+ structure?: 'masculine' | 'feminine' | 'dactylic';
7
+ }
8
+ /** Classify the rhyme relation between two line-end words (or null). */
9
+ export declare function classifyRhymePair(a: string, b: string): RhymePair | null;
10
+ export interface LineRhyme {
11
+ endWord: string;
12
+ letter: string;
13
+ type?: RhymeTypeName;
14
+ matchedLine?: number;
15
+ }
16
+ /** Detect a stanza's rhyme scheme from its line-end words. Strong rhymes
17
+ * bind; slant-tier rhymes bind only when no strong candidate exists. */
18
+ export declare function detectScheme(endWords: string[]): LineRhyme[];
19
+ /**
20
+ * Annotate rhyme letters/types (`detail.rhyme`) and form verdicts
21
+ * (`detail.formNote`) across a poem. Stanza forms are per-stanza; a poem-level
22
+ * form (sonnet, terza rima) overrides stanza notes. Annotation-only — no
23
+ * meter/scansion/certainty is touched.
24
+ */
25
+ export declare function applyRhymeAndForm(stanzas: LineResult[][]): void;
26
+ export interface RhymeRel {
27
+ fromWord: string;
28
+ fromLabel: string;
29
+ toWord: string;
30
+ toLabel: string;
31
+ letter: string;
32
+ type?: RhymeTypeName;
33
+ kind: 'end' | 'caesural' | 'head';
34
+ topStress: StressLevel;
35
+ }
36
+ export interface Phonopoetics {
37
+ endScheme: string;
38
+ end: RhymeRel[];
39
+ caesural: RhymeRel[];
40
+ head: RhymeRel[];
41
+ alliteration: {
42
+ label: string;
43
+ words: string[];
44
+ }[];
45
+ acrostics: {
46
+ labels: string[];
47
+ firsts: string[];
48
+ word: string;
49
+ }[];
50
+ }
51
+ /** Whole-poem phonopoetic analysis: structured data for the Phonopoetics
52
+ * synopsis section. Read-only — never mutates the per-line scansion. */
53
+ export declare function analyzePhonopoetics(stanzas: LineResult[][]): Phonopoetics;
54
+ /** A labelled line of the poem synopsis (display renders each). */
55
+ export interface SynopsisRow {
56
+ label: string;
57
+ value: string;
58
+ }
59
+ /**
60
+ * Build the cumulative poem synopsis: top meter(s)/mixed meters (accentual forms
61
+ * reported in beats), the poem-wide rhyme scheme (with internal rhymes), and the
62
+ * poetic-form alignment. Reads only existing per-line determinations.
63
+ */
64
+ export declare function summarizePoem(stanzas: LineResult[][]): SynopsisRow[];
65
+ //# sourceMappingURL=rhyme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rhyme.d.ts","sourceRoot":"","sources":["../../src/clio/rhyme.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAW,UAAU,EAA8B,WAAW,EAAE,MAAM,aAAa,CAAC;AAK3F,MAAM,MAAM,aAAa,GACrB,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAC3C,UAAU,GAAG,WAAW,GAAG,WAAW,GAAG,YAAY,GACrD,UAAU,GAAG,KAAK,CAAC;AAEvB,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,aAAa,CAAC;IACpB,uFAAuF;IACvF,SAAS,CAAC,EAAE,WAAW,GAAG,UAAU,GAAG,UAAU,CAAC;CACnD;AA6DD,wEAAwE;AACxE,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAkDxE;AAQD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;yEACyE;AACzE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,CA6C5D;AAqMD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,GAAG,IAAI,CAkB/D;AAsCD,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IAAG,OAAO,EAAE,MAAM,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,IAAI,EAAE,KAAK,GAAG,UAAU,GAAG,MAAM,CAAC;IAClC,SAAS,EAAE,WAAW,CAAC;CACxB;AACD,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,QAAQ,EAAE,CAAC;IAChB,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,YAAY,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;IACnD,SAAS,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACnE;AAED;0EAC0E;AAC1E,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,GAAG,YAAY,CAsIzE;AAgCD,mEAAmE;AACnE,MAAM,WAAW,WAAW;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;CAAE;AA2B9D;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,GAAG,WAAW,EAAE,CA0DpE"}