@jcode.labs/ragmir 2.1.0 → 2.2.0

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.
@@ -7,7 +7,6 @@ export declare const CONFIG_PATH = ".ragmir/config.json";
7
7
  export declare const LEGACY_CONFIG_PATH = ".kb/config.json";
8
8
  export declare const DEFAULT_SKILL_TARGET_DIR = ".ragmir/skills";
9
9
  export declare const RAGMIR_PROJECT_ROOT_ENV = "RAGMIR_PROJECT_ROOT";
10
- export declare const SOURCES_FILE_HEADER: string[];
11
10
  export declare const RAGMIR_GITIGNORE_ENTRY = ".ragmir/";
12
11
  export declare const LEGACY_KB_GITIGNORE_ENTRY = ".kb/";
13
12
  export declare const LEGACY_PRIVATE_GITIGNORE_ENTRY = "private/";
@@ -1 +1 @@
1
- {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAExC,eAAO,MAAM,UAAU,YAAY,CAAA;AACnC,eAAO,MAAM,aAAa,QAAQ,CAAA;AAClC,eAAO,MAAM,kBAAkB,YAAY,CAAA;AAC3C,eAAO,MAAM,cAAc,gBAAsB,CAAA;AACjD,eAAO,MAAM,WAAW,wBAA8B,CAAA;AACtD,eAAO,MAAM,kBAAkB,oBAAiC,CAAA;AAChE,eAAO,MAAM,wBAAwB,mBAAyB,CAAA;AAE9D,eAAO,MAAM,uBAAuB,wBAAwB,CAAA;AAE5D,eAAO,MAAM,mBAAmB,UAM/B,CAAA;AAED,eAAO,MAAM,sBAAsB,aAAmB,CAAA;AACtD,eAAO,MAAM,yBAAyB,SAAsB,CAAA;AAC5D,eAAO,MAAM,8BAA8B,aAA2B,CAAA;AACtE,eAAO,MAAM,uCAAuC,eAA6B,CAAA;AAEjF,eAAO,MAAM,cAAc,EAAE,IAAI,CAAC,MAAM,EAAE,aAAa,CA+BtD,CAAA;AAED,eAAO,MAAM,qBAAqB,EAAE,IAAI,CAAC,MAAM,EAAE,aAAa,CAM7D,CAAA"}
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAExC,eAAO,MAAM,UAAU,YAAY,CAAA;AACnC,eAAO,MAAM,aAAa,QAAQ,CAAA;AAClC,eAAO,MAAM,kBAAkB,YAAY,CAAA;AAC3C,eAAO,MAAM,cAAc,gBAAsB,CAAA;AACjD,eAAO,MAAM,WAAW,wBAA8B,CAAA;AACtD,eAAO,MAAM,kBAAkB,oBAAiC,CAAA;AAChE,eAAO,MAAM,wBAAwB,mBAAyB,CAAA;AAE9D,eAAO,MAAM,uBAAuB,wBAAwB,CAAA;AAE5D,eAAO,MAAM,sBAAsB,aAAmB,CAAA;AACtD,eAAO,MAAM,yBAAyB,SAAsB,CAAA;AAC5D,eAAO,MAAM,8BAA8B,aAA2B,CAAA;AACtE,eAAO,MAAM,uCAAuC,eAA6B,CAAA;AAEjF,eAAO,MAAM,cAAc,EAAE,IAAI,CAAC,MAAM,EAAE,aAAa,CA+BtD,CAAA;AAED,eAAO,MAAM,qBAAqB,EAAE,IAAI,CAAC,MAAM,EAAE,aAAa,CAM7D,CAAA"}
package/dist/defaults.js CHANGED
@@ -6,13 +6,6 @@ export const CONFIG_PATH = `${RAGMIR_DIR}/config.json`;
6
6
  export const LEGACY_CONFIG_PATH = `${LEGACY_KB_DIR}/config.json`;
7
7
  export const DEFAULT_SKILL_TARGET_DIR = `${RAGMIR_DIR}/skills`;
8
8
  export const RAGMIR_PROJECT_ROOT_ENV = "RAGMIR_PROJECT_ROOT";
9
- export const SOURCES_FILE_HEADER = [
10
- "# Optional extra source paths or glob patterns, one per line.",
11
- "# Relative paths resolve from the project root. Prefix glob exclusions with !.",
12
- "# Example: ../apps/*/docs/**/*.md",
13
- "# Example: !../apps/**/node_modules/**",
14
- "",
15
- ];
16
9
  export const RAGMIR_GITIGNORE_ENTRY = `${RAGMIR_DIR}/`;
17
10
  export const LEGACY_KB_GITIGNORE_ENTRY = `${LEGACY_KB_DIR}/`;
18
11
  export const LEGACY_PRIVATE_GITIGNORE_ENTRY = `${LEGACY_PRIVATE_DIR}/`;
@@ -1 +1 @@
1
- {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,CAAA;AACnC,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAA;AAClC,MAAM,CAAC,MAAM,kBAAkB,GAAG,SAAS,CAAA;AAC3C,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,UAAU,MAAM,CAAA;AACjD,MAAM,CAAC,MAAM,WAAW,GAAG,GAAG,UAAU,cAAc,CAAA;AACtD,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,aAAa,cAAc,CAAA;AAChE,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,UAAU,SAAS,CAAA;AAE9D,MAAM,CAAC,MAAM,uBAAuB,GAAG,qBAAqB,CAAA;AAE5D,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,+DAA+D;IAC/D,gFAAgF;IAChF,mCAAmC;IACnC,wCAAwC;IACxC,EAAE;CACH,CAAA;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,UAAU,GAAG,CAAA;AACtD,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,aAAa,GAAG,CAAA;AAC5D,MAAM,CAAC,MAAM,8BAA8B,GAAG,GAAG,kBAAkB,GAAG,CAAA;AACtE,MAAM,CAAC,MAAM,uCAAuC,GAAG,GAAG,kBAAkB,KAAK,CAAA;AAEjF,MAAM,CAAC,MAAM,cAAc,GAAgC;IACzD,MAAM,EAAE,cAAc;IACtB,UAAU,EAAE,GAAG,UAAU,UAAU;IACnC,WAAW,EAAE,GAAG,UAAU,cAAc;IACxC,OAAO,EAAE,EAAE;IACX,aAAa,EAAE,GAAG,UAAU,aAAa;IACzC,kBAAkB,EAAE,GAAG,UAAU,SAAS;IAC1C,SAAS,EAAE,QAAQ;IACnB,iBAAiB,EAAE,YAAY;IAC/B,cAAc,EAAE,qCAAqC;IACrD,6BAA6B,EAAE,KAAK;IACpC,SAAS,EAAE;QACT,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,IAAI;QACb,QAAQ,EAAE,EAAE;KACb;IACD,SAAS,EAAE,IAAI;IACf,UAAU,EAAE,EAAE;IACd,IAAI,EAAE,CAAC;IACP,SAAS,EAAE,IAAI;IACf,YAAY,EAAE,GAAG;IACjB,YAAY,EAAE,UAAU;IACxB,iBAAiB,EAAE,CAAC;IACpB,kBAAkB,EAAE,EAAE;IACtB,iBAAiB,EAAE,EAAE;IACrB,aAAa,EAAE,EAAE;IACjB,eAAe,EAAE,OAAO;IACxB,eAAe,EAAE,EAAE;IACnB,iBAAiB,EAAE,OAAO;IAC1B,iBAAiB,EAAE,EAAE;IACrB,mBAAmB,EAAE,OAAO;CAC7B,CAAA;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAgC;IAChE,GAAG,cAAc;IACjB,MAAM,EAAE,kBAAkB;IAC1B,UAAU,EAAE,GAAG,aAAa,UAAU;IACtC,WAAW,EAAE,GAAG,aAAa,cAAc;IAC3C,aAAa,EAAE,GAAG,aAAa,aAAa;CAC7C,CAAA"}
1
+ {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,CAAA;AACnC,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAA;AAClC,MAAM,CAAC,MAAM,kBAAkB,GAAG,SAAS,CAAA;AAC3C,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,UAAU,MAAM,CAAA;AACjD,MAAM,CAAC,MAAM,WAAW,GAAG,GAAG,UAAU,cAAc,CAAA;AACtD,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,aAAa,cAAc,CAAA;AAChE,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,UAAU,SAAS,CAAA;AAE9D,MAAM,CAAC,MAAM,uBAAuB,GAAG,qBAAqB,CAAA;AAE5D,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,UAAU,GAAG,CAAA;AACtD,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,aAAa,GAAG,CAAA;AAC5D,MAAM,CAAC,MAAM,8BAA8B,GAAG,GAAG,kBAAkB,GAAG,CAAA;AACtE,MAAM,CAAC,MAAM,uCAAuC,GAAG,GAAG,kBAAkB,KAAK,CAAA;AAEjF,MAAM,CAAC,MAAM,cAAc,GAAgC;IACzD,MAAM,EAAE,cAAc;IACtB,UAAU,EAAE,GAAG,UAAU,UAAU;IACnC,WAAW,EAAE,GAAG,UAAU,cAAc;IACxC,OAAO,EAAE,EAAE;IACX,aAAa,EAAE,GAAG,UAAU,aAAa;IACzC,kBAAkB,EAAE,GAAG,UAAU,SAAS;IAC1C,SAAS,EAAE,QAAQ;IACnB,iBAAiB,EAAE,YAAY;IAC/B,cAAc,EAAE,qCAAqC;IACrD,6BAA6B,EAAE,KAAK;IACpC,SAAS,EAAE;QACT,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,IAAI;QACb,QAAQ,EAAE,EAAE;KACb;IACD,SAAS,EAAE,IAAI;IACf,UAAU,EAAE,EAAE;IACd,IAAI,EAAE,CAAC;IACP,SAAS,EAAE,IAAI;IACf,YAAY,EAAE,GAAG;IACjB,YAAY,EAAE,UAAU;IACxB,iBAAiB,EAAE,CAAC;IACpB,kBAAkB,EAAE,EAAE;IACtB,iBAAiB,EAAE,EAAE;IACrB,aAAa,EAAE,EAAE;IACjB,eAAe,EAAE,OAAO;IACxB,eAAe,EAAE,EAAE;IACnB,iBAAiB,EAAE,OAAO;IAC1B,iBAAiB,EAAE,EAAE;IACrB,mBAAmB,EAAE,OAAO;CAC7B,CAAA;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAgC;IAChE,GAAG,cAAc;IACjB,MAAM,EAAE,kBAAkB;IAC1B,UAAU,EAAE,GAAG,aAAa,UAAU;IACtC,WAAW,EAAE,GAAG,aAAa,cAAc;IAC3C,aAAa,EAAE,GAAG,aAAa,aAAa;CAC7C,CAAA"}
package/dist/sources.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export interface SourceEntriesResult {
2
+ /** Where the listed entries come from: always config.json now. */
2
3
  sourcesFile: string;
3
4
  entries: string[];
4
5
  }
@@ -7,10 +8,22 @@ export interface AddSourceEntriesOptions {
7
8
  entries: readonly string[];
8
9
  }
9
10
  export interface AddSourceEntriesResult {
11
+ /** config.json path the entries were written to. */
10
12
  sourcesFile: string;
11
13
  added: string[];
12
14
  skipped: string[];
13
15
  }
16
+ /**
17
+ * List the configured source entries. Sources live in the `sources` array of
18
+ * `.ragmir/config.json` (the primary mechanism). A legacy `.ragmir/sources.txt`
19
+ * is still read and merged when present so existing projects keep working, but
20
+ * it is never created or written by these functions anymore.
21
+ */
14
22
  export declare function listSourceEntries(cwd?: string): Promise<SourceEntriesResult>;
23
+ /**
24
+ * Add source entries to the `sources` array of `.ragmir/config.json`. Does not
25
+ * touch the legacy `sources.txt` file anymore; entries already present there
26
+ * are treated as existing (skipped) to avoid duplicates.
27
+ */
15
28
  export declare function addSourceEntries(options: AddSourceEntriesOptions): Promise<AddSourceEntriesResult>;
16
29
  //# sourceMappingURL=sources.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sources.d.ts","sourceRoot":"","sources":["../src/sources.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAC3B;AAED,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,wBAAsB,iBAAiB,CAAC,GAAG,SAAgB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAOzF;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,sBAAsB,CAAC,CA+BjC"}
1
+ {"version":3,"file":"sources.d.ts","sourceRoot":"","sources":["../src/sources.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,mBAAmB;IAClC,kEAAkE;IAClE,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAC3B;AAED,MAAM,WAAW,sBAAsB;IACrC,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,SAAgB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAczF;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,sBAAsB,CAAC,CAkCjC"}
package/dist/sources.js CHANGED
@@ -1,27 +1,48 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { loadConfig } from "./config.js";
5
- import { SOURCES_FILE_HEADER } from "./defaults.js";
4
+ import { findProjectConfig, loadConfig } from "./config.js";
5
+ /**
6
+ * List the configured source entries. Sources live in the `sources` array of
7
+ * `.ragmir/config.json` (the primary mechanism). A legacy `.ragmir/sources.txt`
8
+ * is still read and merged when present so existing projects keep working, but
9
+ * it is never created or written by these functions anymore.
10
+ */
6
11
  export async function listSourceEntries(cwd = process.cwd()) {
7
12
  const config = await loadConfig(cwd);
8
- const content = await readSourcesFile(config.sourcesFile);
9
- return {
10
- sourcesFile: config.sourcesFile,
11
- entries: content ? parseSourceEntries(content) : [],
12
- };
13
+ const projectConfig = findProjectConfig(cwd);
14
+ const configEntries = await readConfigSources(projectConfig);
15
+ const legacyEntries = await readLegacySourcesTxt(config.sourcesFile);
16
+ const seen = new Set();
17
+ const entries = [];
18
+ for (const entry of [...configEntries, ...legacyEntries]) {
19
+ if (!seen.has(entry)) {
20
+ seen.add(entry);
21
+ entries.push(entry);
22
+ }
23
+ }
24
+ return { sourcesFile: projectConfig.configPath, entries };
13
25
  }
26
+ /**
27
+ * Add source entries to the `sources` array of `.ragmir/config.json`. Does not
28
+ * touch the legacy `sources.txt` file anymore; entries already present there
29
+ * are treated as existing (skipped) to avoid duplicates.
30
+ */
14
31
  export async function addSourceEntries(options) {
15
- const entries = normalizeRequestedEntries(options.entries);
16
- if (entries.length === 0) {
32
+ const requested = normalizeRequestedEntries(options.entries);
33
+ if (requested.length === 0) {
17
34
  throw new Error("At least one source path or glob is required.");
18
35
  }
19
36
  const config = await loadConfig(options.cwd);
20
- const content = await readSourcesFile(config.sourcesFile);
21
- const existingEntries = new Set(content ? parseSourceEntries(content) : []);
37
+ const projectConfig = findProjectConfig(options.cwd ?? process.cwd());
38
+ const configEntries = await readConfigSources(projectConfig);
39
+ const existingEntries = new Set([
40
+ ...configEntries,
41
+ ...(await readLegacySourcesTxt(config.sourcesFile)),
42
+ ]);
22
43
  const added = [];
23
44
  const skipped = [];
24
- for (const entry of entries) {
45
+ for (const entry of requested) {
25
46
  if (existingEntries.has(entry)) {
26
47
  skipped.push(entry);
27
48
  continue;
@@ -30,20 +51,49 @@ export async function addSourceEntries(options) {
30
51
  added.push(entry);
31
52
  }
32
53
  if (added.length > 0) {
33
- await mkdir(path.dirname(config.sourcesFile), { recursive: true });
34
- await writeFile(config.sourcesFile, nextSourcesFileContent(content, added), "utf8");
54
+ await writeConfigSources(projectConfig, [...configEntries, ...added]);
35
55
  }
36
56
  return {
37
- sourcesFile: config.sourcesFile,
57
+ sourcesFile: projectConfig.configPath,
38
58
  added,
39
59
  skipped,
40
60
  };
41
61
  }
42
- async function readSourcesFile(sourcesFile) {
62
+ /** Read the raw `sources` array from config.json without resolving paths. */
63
+ async function readConfigSources(projectConfig) {
64
+ if (!existsSync(projectConfig.configPath)) {
65
+ return [];
66
+ }
67
+ try {
68
+ const raw = JSON.parse(await readFile(projectConfig.configPath, "utf8"));
69
+ if (isRecord(raw) && Array.isArray(raw.sources)) {
70
+ return raw.sources.filter((entry) => typeof entry === "string");
71
+ }
72
+ }
73
+ catch {
74
+ // Fall back to empty if the config is unreadable; loadConfig surfaces real errors elsewhere.
75
+ }
76
+ return [];
77
+ }
78
+ /** Write the `sources` array back into config.json, preserving other fields. */
79
+ async function writeConfigSources(projectConfig, entries) {
80
+ await mkdir(path.dirname(projectConfig.configPath), { recursive: true });
81
+ const raw = existsSync(projectConfig.configPath)
82
+ ? JSON.parse(await readFile(projectConfig.configPath, "utf8"))
83
+ : {};
84
+ if (!isRecord(raw)) {
85
+ throw new Error(`${path.relative(projectConfig.projectRoot, projectConfig.configPath)} must contain a JSON object.`);
86
+ }
87
+ raw.sources = [...entries];
88
+ await writeFile(projectConfig.configPath, `${JSON.stringify(raw, null, 2)}\n`, "utf8");
89
+ }
90
+ /** Read the legacy sources.txt read-only for backward compatibility. */
91
+ async function readLegacySourcesTxt(sourcesFile) {
43
92
  if (!existsSync(sourcesFile)) {
44
- return null;
93
+ return [];
45
94
  }
46
- return readFile(sourcesFile, "utf8");
95
+ const content = await readFile(sourcesFile, "utf8");
96
+ return parseSourceEntries(content);
47
97
  }
48
98
  function parseSourceEntries(content) {
49
99
  return content
@@ -64,8 +114,7 @@ function normalizeRequestedEntries(entries) {
64
114
  }
65
115
  return normalized;
66
116
  }
67
- function nextSourcesFileContent(currentContent, added) {
68
- const base = currentContent === null ? SOURCES_FILE_HEADER.join("\n") : currentContent.trimEnd();
69
- return `${base ? `${base}\n` : ""}${added.join("\n")}\n`;
117
+ function isRecord(value) {
118
+ return typeof value === "object" && value !== null && !Array.isArray(value);
70
119
  }
71
120
  //# sourceMappingURL=sources.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sources.js","sourceRoot":"","sources":["../src/sources.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sources.js","sourceRoot":"","sources":["../src/sources.ts"],"names":[],"mappings":"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"}
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "2.1.0";
1
+ export declare const VERSION = "2.2.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
- export const VERSION = "2.1.0";
1
+ export const VERSION = "2.2.0";
2
2
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jcode.labs/ragmir",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Ragmir Core: open-source sovereign local RAG for confidential datasets and AI agents.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -68,7 +68,7 @@
68
68
  "unpdf": "^1.4.0",
69
69
  "yaml": "^2.9.0",
70
70
  "zod": "^4.1.13",
71
- "@jcode.labs/ragmir-tts": "2.1.0"
71
+ "@jcode.labs/ragmir-tts": "2.2.0"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@types/html-to-text": "^9.0.4",
@@ -79,9 +79,9 @@
79
79
  },
80
80
  "scripts": {
81
81
  "build": "tsc -p tsconfig.json",
82
- "check": "tsc -p tsconfig.json --noEmit",
82
+ "check": "pnpm --filter @jcode.labs/ragmir-tts build && tsc -p tsconfig.json --noEmit",
83
83
  "mcp:smoke": "pnpm build && node scripts/mcp-smoke.mjs",
84
84
  "package:check": "publint",
85
- "test": "vitest run"
85
+ "test": "pnpm --filter @jcode.labs/ragmir-tts build && vitest run"
86
86
  }
87
87
  }
@@ -1,17 +1,35 @@
1
1
  ---
2
2
  name: ragmir-audio-summary
3
3
  description: >-
4
- Create an optional spoken audio summary from a Ragmir local knowledge base. Use when the user asks
5
- for an audio, TTS, spoken brief, briefing, narration, or listenable summary based on private
6
- repository documents indexed by Ragmir. The skill is confidentiality-first: gather facts through
7
- Ragmir, write only a temporary narration text file outside the repository, and render the final
8
- audio under ignored local Ragmir state unless the user explicitly chooses another output path.
4
+ Create an intention-driven, memorable spoken audio summary from a Ragmir local knowledge base. Use
5
+ when the user asks for an audio, TTS, spoken brief, briefing, narration, or listenable summary
6
+ based on private repository documents indexed by Ragmir. The skill is confidentiality-first: it
7
+ filters everything against the user's intent, writes only a temporary narration text file outside
8
+ the repository, and renders the final audio under ignored local Ragmir state unless the user
9
+ explicitly chooses another output path.
9
10
  ---
10
11
 
11
12
  # Ragmir Audio Summary
12
13
 
13
- Use this skill to turn a confidential local Ragmir knowledge base into an optional audio summary.
14
- The knowledge base stays local; the final audio is a generated artifact and must not be committed.
14
+ Use this skill to turn a confidential local Ragmir knowledge base into a spoken summary that
15
+ respects the listener: only what serves their intent, structured to be intelligible in one pass, and
16
+ designed to be retained.
17
+
18
+ The knowledge base stays local. The final audio is a generated artifact and must not be committed.
19
+
20
+ The goal is not a "basic summary read aloud". It is a deliberate narration built from five
21
+ techniques:
22
+
23
+ 1. **Intent-first filtering** — drop everything that does not serve what the user actually needs.
24
+ 2. **Density-driven duration** — short for a small topic, long for a large one. Never pad.
25
+ 3. **Pyramid structure** — answer first, then the supporting points, then a recap.
26
+ 4. **Writing for the ear** — short sentences, spoken signposting, deliberate TTS punctuation.
27
+ 5. **Active recall** — two-to-three self-check questions that force retrieval and anchor memory.
28
+
29
+ Two reference files sit next to this skill and are read while drafting:
30
+
31
+ - `listening-style.md` — how to write prose a TTS engine can pronounce and a listener can follow.
32
+ - `narration-templates.md` — skeletons per format, plus signposting and active-recall catalogues.
15
33
 
16
34
  ## Confidentiality Rules
17
35
 
@@ -25,7 +43,22 @@ The knowledge base stays local; the final audio is a generated artifact and must
25
43
  - Render audio under `.ragmir/audio/` by default. This directory is ignored by Git when Ragmir is installed.
26
44
  - Never stage or commit generated audio, temporary text, WAV, AIFF, or intermediate files.
27
45
 
28
- ## 1. Verify The Knowledge Base
46
+ ## 1. Capture The Intent Before Searching
47
+
48
+ Do not run any search yet. First make the intent explicit so every later step filters by it. Confirm
49
+ with the user when it is ambiguous, or infer it from their request and state your assumption.
50
+
51
+ Capture four things:
52
+
53
+ - **Purpose** — what the listener will do with this: decide, learn, monitor, compare, or plan.
54
+ - **Subject** — the precise question or topic, narrower than "summarize everything".
55
+ - **Depth** — a quick scan or a deep dossier. Let the user steer, but default from the material.
56
+ - **Language** — the spoken language, passed later to `--lang` (`fr` default, `en`, `es`).
57
+
58
+ This is the guardrail against superfluous data: anything that does not advance the captured intent
59
+ gets dropped at the drafting step, not added because it was "also in the documents".
60
+
61
+ ## 2. Verify The Knowledge Base
29
62
 
30
63
  From the repository root, run:
31
64
 
@@ -48,9 +81,10 @@ pnpm exec ragmir audit --unsupported
48
81
  has no warnings. Do not create an audio summary from stale or incomplete evidence unless the user
49
82
  explicitly accepts that limitation.
50
83
 
51
- ## 2. Search Deeply Before Writing
84
+ ## 3. Search Deeply, Guided By The Intent
52
85
 
53
- Use Ragmir search or MCP tools to gather evidence before drafting the narration.
86
+ Use Ragmir search or MCP tools to gather evidence, and derive the queries from the intent captured
87
+ in step 1 — not from the subject alone.
54
88
 
55
89
  For a broad summary, run multiple searches:
56
90
 
@@ -63,26 +97,72 @@ pnpm exec ragmir ask "<specific synthesis question>" --top-k 8
63
97
  When MCP is available, prefer `ragmir_search`, `ragmir_ask`, `ragmir_audit`, and
64
98
  `ragmir_security_audit` over shell commands.
65
99
 
66
- Keep citations in your working notes, but do not read long raw passages aloud. The audio should be a
67
- clear synthesis, not a dump of source text.
100
+ Keep citations in your working notes, but do not read long raw passages aloud. The audio is a clear
101
+ synthesis, not a dump of source text.
102
+
103
+ While searching, also gauge the **density of relevant material** for step 4:
104
+
105
+ - How many passages directly serve the intent?
106
+ - Do they overlap, reinforce, or contradict each other?
107
+ - Are there clear facets (chapters) or one tight question?
108
+
109
+ ## 4. Choose The Format From The Density
110
+
111
+ Pick the format from the relevant material found, not from a fixed default. Length follows material;
112
+ never pad.
113
+
114
+ | Format | Use when | Word budget | Duration |
115
+ | --- | --- | --- | --- |
116
+ | **Micro-brief** | One clear question or little relevant material | 150-220 words | ~60-90 s |
117
+ | **Brief standard** | Moderate material, a few angles worth keeping | 450-750 words | ~3-5 min |
118
+ | **Dossier long** | Dense, multi-faceted material needing chapters | 1500-3000 words | ~10-20 min |
119
+
120
+ If two formats plausibly fit, prefer the shorter one unless the user asked for depth. A short,
121
+ dense summary respects the listener more than a padded one.
122
+
123
+ If the user explicitly asks for a long audio on a thin topic, say so in the final report and keep
124
+ the narration tight rather than inflated.
125
+
126
+ ## 5. Write For Listening
127
+
128
+ Write one flowing narration in the chosen language. No markdown, headings, bullets, tables, SSML, or
129
+ XML tags in the spoken text. Open `listening-style.md` for the full rules; the cardinal ones:
130
+
131
+ - **Pyramid first.** State the key message (one-to-four points to retain) up front, then develop it,
132
+ then recap it. This exploits primacy and recency.
133
+ - **Signpost, do not list.** Use spoken transitions ("D'abord…, Ensuite…, Enfin…") instead of
134
+ enumerated lists. See `narration-templates.md` for the catalogue.
135
+ - **Short sentences.** One idea per sentence, twenty words or fewer. Split long ones.
136
+ - **Deliberate punctuation.** Periods for full pauses, commas for micro-pauses, `…` for the
137
+ deliberate beat before an active-recall answer. Avoid semicolons and exclamation marks.
138
+ - **Pronounceable.** Expand acronyms on first use, write numbers and symbols in words, never read
139
+ URLs, paths, or code aloud.
140
+
141
+ Read the draft aloud once at a normal pace. If you stumble or run out of breath, the sentence is too
142
+ long or too dense — fix it.
68
143
 
69
- ## 3. Write For Listening
144
+ ## 6. Anchor Memorability
70
145
 
71
- Write one flowing narration in the user's working language. Do not use markdown, headings, bullets,
72
- tables, SSML, XML tags, or stage directions in the spoken text.
146
+ Build two devices into every narration, systematically:
73
147
 
74
- Good audio structure:
148
+ 1. **Recap the key points twice** — once right after the opening message, once at the very end.
149
+ Same points, compressed, in different words the second time.
150
+ 2. **Active recall** — end with self-check questions that force retrieval, not recognition:
151
+ - One question for a micro-brief, two-to-three for a standard brief, three-to-five for a dossier.
152
+ - Each question targets a key point from the recap, not a peripheral detail.
153
+ - Phrase so the answer is not leaked: "Quelle est la durée du préavis?" beats "Le préavis est-il
154
+ de trente jours?".
155
+ - After each question mark, insert `…` so the TTS pause gives the listener a beat to answer
156
+ silently, then state the answer in the fewest words possible.
75
157
 
76
- 1. Start with the purpose of the summary and the two-to-four ideas to retain.
77
- 2. Explain the current evidence in plain language.
78
- 3. Separate proven facts from uncertainty.
79
- 4. Highlight decisions, risks, deadlines, and missing documents.
80
- 5. End with a concise recap and two or three self-check questions.
158
+ For the full skeleton per format and the active-recall catalogue by intent, see
159
+ `narration-templates.md`.
81
160
 
82
- Use short sentences and natural punctuation. Spell acronyms and symbols in a way a TTS engine can
83
- pronounce.
161
+ Respect the Ragmir evidence separation in spoken form using language cues rather than formatting:
162
+ state proven facts plainly, mark inferences ("Cela suggère que…"), mark uncertainty ("Reste à
163
+ confirmer…"), and name missing documents.
84
164
 
85
- ## 4. Render The Audio
165
+ ## 7. Render The Audio
86
166
 
87
167
  Create the output directory and write the narration to a temp file outside the repo:
88
168
 
@@ -140,11 +220,13 @@ Helper engine selection:
140
220
 
141
221
  Voice can be selected with `TTS_VOICE`. Speed for Edge can be selected with `TTS_RATE`.
142
222
 
143
- ## 5. Report The Result
223
+ ## 8. Report The Result
144
224
 
145
225
  After rendering, report:
146
226
 
147
227
  - the audio path;
228
+ - the **format chosen** (micro-brief, brief standard, dossier long) and the **intention served**;
229
+ - the **estimated duration** (`word count / 140`) versus the target budget;
148
230
  - which renderer/model was used or requested;
149
231
  - whether remote model downloads or online TTS were allowed;
150
232
  - any evidence limitation, such as stale index, missing documents, or weak search results.
@@ -0,0 +1,91 @@
1
+ # Listening Style Guide
2
+
3
+ Reference for the narration-writing step of `ragmir-audio-summary`. This file defines how to turn
4
+ retrieved evidence into spoken text that a TTS engine can pronounce and a listener can follow and
5
+ remember in one pass. There is no second chance to re-read audio.
6
+
7
+ ## Pacing And Duration
8
+
9
+ - Conversational narration lands around **130-150 words per minute**. Plan on **140 words/minute**
10
+ as the working estimate.
11
+ - Estimated duration = `word count / 140`. Keep a short narration under the target; never pad to
12
+ reach a target.
13
+ - Per-format word budgets (use them as ceilings, not goals):
14
+
15
+ | Format | Target duration | Word budget |
16
+ | --- | --- | --- |
17
+ | Micro-brief | ~60-90 s | 150-220 words |
18
+ | Brief standard | ~3-5 min | 450-750 words |
19
+ | Dossier long | ~10-20 min | 1500-3000 words |
20
+
21
+ If the relevant material does not fill the budget, stop earlier. Padding makes audio undrinkable.
22
+
23
+ ## Sentence Shape
24
+
25
+ - One idea per sentence. If you need "and" twice, split the sentence.
26
+ - Aim for **20 words or fewer** per sentence. A few longer sentences are fine for rhythm; never chain
27
+ three long ones in a row.
28
+ - Prefer subject-verb-object order. TTS engines and the human ear both handle it best.
29
+ - Avoid stacked subordinate clauses and parentheticals. They trip the reader and the listener.
30
+
31
+ Before: "Le contrat, qui avait été signé, malgré des réserves émises tardivement, par les deux
32
+ parties, prévoit une rupture."
33
+
34
+ After: "Les deux parties ont signé le contrat, avec des réserves de dernière minute. Il prévoit
35
+ une rupture."
36
+
37
+ ## Punctuation For TTS
38
+
39
+ Punctuation is the only cue a TTS engine has for pausing. Use it deliberately.
40
+
41
+ - **Comma** `,` — short micro-pause. Use it to separate clauses and ease breathing points.
42
+ - **Period** `.` — full pause. End sentences here. Favour periods over commas when in doubt.
43
+ - **Ellipsis** `…` — deliberate longer pause. Use it before the answer in an active-recall question,
44
+ so the listener gets a beat to think (about three seconds).
45
+ - **Colon** `:` — read as a mild pause; acceptable when introducing a short enumeration spoken as
46
+ prose, but prefer rephrasing into a plain sentence.
47
+ - **Semicolon** `;` — avoid. Engines hesitate inconsistently. Split into two sentences.
48
+ - **Exclamation** `!` — avoid for TTS. It can trigger an unnatural spike in pitch.
49
+ - **Question mark** `?` — use it for the active-recall questions; the engine raises intonation
50
+ naturally.
51
+
52
+ ## Pronounceability
53
+
54
+ Anything the engine might misread must be rewritten for the ear.
55
+
56
+ - **Acronyms**: expand on first use, then use the short form if it is pronounceable. "Le RGPD, le
57
+ Règlement Général sur la Protection des Données, ..."
58
+ - **Numbers**: write them the way they should be read. "1500" can read as "mille cinq cents" or
59
+ "quinze cents" — write the intended form. For money, spell the currency: "douze mille euros".
60
+ - **Symbols**: rewrite in words. `&` becomes "et", `%` becomes "pour cent", `->` becomes "mène à".
61
+ - **Dates and times**: write them fully. "Le quinze mars deux mille vingt-cinq", not "2025-03-15".
62
+ - **URLs, paths, code**: never read them aloud. Paraphrase: "le fichier de configuration du
63
+ projet" instead of `ragmir/config.json`.
64
+ - **Foreign terms**: if the term has no clean translation, introduce it once and keep it.
65
+
66
+ ## Anti-Patterns
67
+
68
+ Avoid these — they are the main causes of "imbuvable" audio.
69
+
70
+ - **Jargon without setup.** If a technical term is needed, define it in one short clause first.
71
+ - **Spoken bullet lists.** Enumerations read aloud are exhausting. Convert lists into flowing
72
+ sentences with signposting ("D'abord…, Ensuite…, Pour finir…"). See `narration-templates.md`.
73
+ - **Meta-commentary.** Drop "Dans ce résumé, nous allons parler de…". Open on the substance.
74
+ - **Padding to reach a duration.** Restating the same idea in more words lowers density and respect
75
+ for the listener. Shorter is better than watered down.
76
+ - **Reading raw source passages.** The audio is a synthesis, not a quote dump. Reformulate in plain
77
+ speech and keep the citation in your working notes, not in the narration.
78
+ - **Overload.** More than ~4 new ideas in a brief is too many to retain. Group, merge, or defer.
79
+
80
+ ## Language
81
+
82
+ - Write the narration in the same language passed to `--lang` (`fr` default, `en`, `es`).
83
+ - Match the working language the user used when asking for the summary when it is one of the three
84
+ supported languages.
85
+ - Keep numerals, units, and proper nouns pronounceable in the target language.
86
+
87
+ ## Self-Check Before Rendering
88
+
89
+ Read the draft aloud once at a normal pace. If you stumble or run out of breath on a sentence, it is
90
+ too long or too dense. Fix it, then estimate the duration with `word count / 140` and confirm it
91
+ matches the chosen format's budget.
@@ -0,0 +1,162 @@
1
+ # Narration Templates
2
+
3
+ Reference for the drafting step of `ragmir-audio-summary`. These are **thinking skeletons**, not
4
+ output formatting. The final narration is plain flowing prose with no markdown, headings, bullets,
5
+ tables, SSML, or XML tags. The skeletons below only shape how you order and connect ideas before you
6
+ write the spoken text.
7
+
8
+ Pick the format chosen in the skill, then fill the skeleton with the evidence that serves the user's
9
+ intent. Drop anything that does not serve the intent — that is how you avoid superfluous data.
10
+
11
+ ## Format Selection Recap
12
+
13
+ | Format | When to use | Word budget |
14
+ | --- | --- | --- |
15
+ | Micro-brief | Little relevant material, or one clear question | 150-220 words |
16
+ | Brief standard | Moderate material, a few angles worth keeping | 450-750 words |
17
+ | Dossier long | Dense, multi-faceted material needing chapters | 1500-3000 words |
18
+
19
+ Length follows relevant material. Never pad.
20
+
21
+ ---
22
+
23
+ ## Micro-Brief Skeleton
24
+
25
+ Spoken-prose shape (write it as one flowing passage, not as headings):
26
+
27
+ 1. **Hook + intent** — one sentence naming the subject and why the listener asked.
28
+ 2. **Key idea** — the single thing to remember, stated up front (primacy).
29
+ 3. **Evidence** — one short, plain-language reason or fact that supports it.
30
+ 4. **Uncertainty / caveat** — one sentence if the evidence is partial or stale; omit only if solid.
31
+ 5. **Recap + recall** — restate the key idea in other words, then ask one active-recall question,
32
+ pause with `…`, and give the answer.
33
+
34
+ Abstract example (do not copy the words, follow the shape):
35
+
36
+ > "Vous vouliez savoir si la clause de rupture est applicable. Oui, elle l'est, mais seulement après
37
+ > un préavis de trente jours. Le contrat le prévoit explicitement à la section terminaison. En
38
+ > revanche, la date de départ du préavis reste ambiguë, aucun courrier ne la fixe clairement.
39
+ > Retenez donc une chose: rupture possible, préavis de trente jours, point de départ à confirmer.
40
+ > Question: quelle est la durée du préavis prévu? … Trente jours."
41
+
42
+ ---
43
+
44
+ ## Brief Standard Skeleton (Pyramid + SCQA)
45
+
46
+ The standard brief follows Barbara Minto's pyramid: answer first, then supporting points, then
47
+ recap. Wrap it in a short Situation-Complication-Question-Answer opening.
48
+
49
+ 1. **Situation** — one or two sentences framing the context the listener already shares.
50
+ 2. **Complication** — the tension, change, or question that prompted the summary.
51
+ 3. **Question** — the explicit question the summary answers.
52
+ 4. **Answer (the key message)** — the one-to-three points to retain, stated first (primacy).
53
+ 5. **Developed points** — each of the two-to-four points expanded in plain speech, grouped with
54
+ signposting ("D'abord…, Ensuite…, Enfin…"). Proven facts only; separate from inference.
55
+ 6. **Uncertainty, risks, decisions, missing documents** — a short passage on what is not yet
56
+ settled or needs action.
57
+ 7. **Recap** — restate the two-to-four key points in compressed form (recency).
58
+ 8. **Active recall** — two or three recall questions, each followed by a `…` pause and the answer.
59
+
60
+ Abstract example (shape only):
61
+
62
+ > "Le projet a atteint sa phase de test. Le souci, c'est que deux blocages apparaissent en
63
+ > parallèle. Faut-il continuer ou mettre en pause? La réponse tient en deux points: continuez sur
64
+ > le module A, mais mettez en pause le module B. D'abord, le module A passe ses tests sans
65
+ > régression. Ensuite, le module B dépend d'un composant externe encore instable. Ce qu'on ne sait
66
+ > pas encore, c'est la date de livraison du composant externe. Donc retenez: module A en avant,
67
+ > module B en attente, dépendance externe à confirmer. Première question: quel module peut
68
+ > continuer sans risque? … Le module A. Deuxième question: pourquoi mettre en pause le module B? … À cause d'un
69
+ > composant externe instable."
70
+
71
+ ---
72
+
73
+ ## Dossier Long Skeleton (Chaptered)
74
+
75
+ Use only when the material genuinely has several distinct facets. Each chapter is a self-contained
76
+ mini-brief with its own mini-recap, so a listener can step away and come back.
77
+
78
+ 1. **Promise** — what the whole audio covers and the three-to-five landmarks to listen for. State
79
+ the key message of the whole dossier up front.
80
+ 2. **Chapter 1** — signpost the chapter ("Premier point: …"). Give its mini pyramid
81
+ (answer → evidence → caveat), then a one-sentence mini-recap.
82
+ 3. **Chapter 2** — same shape, explicit transition ("Deuxième point: …").
83
+ 4. **Continue** for each facet. Keep chapters balanced in length; merge thin ones.
84
+ 5. **Global recap** — compress the key message of every chapter into one flowing passage (recency).
85
+ 6. **Active recall** — three to five recall questions across the chapters, each with a `…` pause
86
+ and the answer. Spread the questions across chapters, not only the last one.
87
+
88
+ Transitions between chapters are essential in long audio — see the signposting catalogue below.
89
+
90
+ Abstract shape:
91
+
92
+ > "Ce dossier couvre les trois enjeux du contrat: la durée, la rupture, et les pénalités. En une
93
+ > ligne: la durée est ferme, la rupture est conditionnée, les pénalités sont plafonnées. Premier
94
+ > point: la durée. Le contrat court sur trois ans, fermes. … [chapitre] … Donc, durée ferme de
95
+ > trois ans. Deuxième point: la rupture. … [chapitre] … Donc, rupture possible sous préavis.
96
+ > Troisième point: les pénalités. … [chapitre] … Donc, pénalités plafonnées à dix pour cent. Pour
97
+ > récapituler l'ensemble: trois ans fermes, rupture sous préavis, pénalités plafonnées. Première
98
+ > question: quelle est la durée ferme du contrat? … Trois ans. …"
99
+
100
+ ---
101
+
102
+ ## Signposting Catalogue
103
+
104
+ Signposting is the connective tissue that makes audio followable without visuals. Use these spoken
105
+ transitions instead of lists or headings.
106
+
107
+ | Move | Spoken cue (FR examples) |
108
+ | --- | --- |
109
+ | Open | "Voici l'essentiel sur…" / "Vous vouliez savoir si… La réponse courte est…" |
110
+ | Enumerate | "D'abord…, Ensuite…, Enfin…" / "Il y a trois points…" (then speak each) |
111
+ | Contrast | "En revanche…" / "Mais attention…" / "À l'inverse…" |
112
+ | Cause / effect | "Parce que…" / "C'est pourquoi…" / "La conséquence est…" |
113
+ | Emphasize | "Retenez ceci…" / "Le point clé, c'est…" |
114
+ | Transition chapter | "Deuxième point…" / "Passons à…" / "Cela nous amène à…" |
115
+ | Mini-recap | "En un mot…" / "Donc, pour ce point…" |
116
+ | Close | "Pour récapituler…" / "En résumé…" |
117
+ | Recall question | "Question: …? … [answer]" |
118
+
119
+ When enumerating, say the count first ("trois points") then speak each one with "D'abord / Ensuite /
120
+ Enfin" — never read a literal list.
121
+
122
+ ---
123
+
124
+ ## Active-Recall Question Catalogue
125
+
126
+ Active recall forces the listener to retrieve from memory, which anchors retention far better than
127
+ passive re-exposure. Phrase questions so they require **recall**, not recognition (no multiple
128
+ choice, no "est-ce que c'est X?" with the answer embedded).
129
+
130
+ ### By intent
131
+
132
+ | Intent | What to drill | Question shape (FR examples) |
133
+ | --- | --- | --- |
134
+ | Decide | The decision and its one main driver | "Quelle option recommande-t-on, et pourquoi?" |
135
+ | Learn | The definitions and the one mechanism to remember | "Comment définit-on X, et qu'est-ce qui le déclenche?" |
136
+ | Monitor | The thresholds and the alert condition | "À partir de quel seuil déclenche-t-on l'alerte?" |
137
+ | Compare | The decisive differentiator | "Quelle est la principale différence entre A et B?" |
138
+ | Plan | The next concrete action and its owner | "Quelle est la première action à mener, et par qui?" |
139
+
140
+ ### Rules
141
+
142
+ - Two to three questions for a brief, three to five for a dossier. One is enough for a micro-brief.
143
+ - Each question targets a key point from the recap, not a peripheral detail.
144
+ - After the question mark, insert `…` so the TTS pause gives the listener a beat to answer silently,
145
+ then state the answer in the fewest words possible.
146
+ - Do not reveal the answer inside the question. "Le préavis est-il de trente jours?" leaks the
147
+ answer — prefer "Quelle est la durée du préavis?".
148
+
149
+ ---
150
+
151
+ ## Evidence Separation In Spoken Form
152
+
153
+ Spoken audio can still respect the Ragmir separation of evidence and inference. Use language cues,
154
+ not formatting:
155
+
156
+ - Proven fact: state plainly. "Le contrat fixe le préavis à trente jours."
157
+ - Inference: mark it. "Cela suggère que…" / "On peut en déduire que…"
158
+ - Uncertainty: mark it. "Ce qu'on ne sait pas encore, c'est…" / "Reste à confirmer…"
159
+ - Missing document: name it. "Il manque le courrier de notification, donc…"
160
+ - Decision / action: mark it. "La décision à prendre est…"
161
+
162
+ This keeps the narration honest without leaning on tables or footnotes that audio cannot carry.