@litfamily/litgrok 1.0.13 → 1.0.15

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 (114) hide show
  1. package/.grok/hooks/auto-handoff-gate.mjs +15 -2
  2. package/.grok/rules/00-litgrok.md +1 -1
  3. package/.grok/skills/frontend-ui-ux/THIRD-PARTY-NOTICE.txt +0 -5
  4. package/.grok/skills/frontend-ui-ux/references/slop-register.md +4 -4
  5. package/.grok/skills/frontend-ui-ux/scripts/import-design-intelligence.mjs +1 -1
  6. package/.grok/skills/lit-docx/SKILL.md +59 -11
  7. package/.grok/skills/lit-docx/examples/01-report-ko.md +137 -0
  8. package/.grok/skills/lit-docx/examples/02-brief-ko.md +82 -0
  9. package/.grok/skills/lit-docx/examples/03-manual-ko.md +118 -0
  10. package/.grok/skills/lit-docx/examples/04-proposal-ko.md +92 -0
  11. package/.grok/skills/lit-docx/examples/05-memo-en.md +42 -0
  12. package/.grok/skills/lit-docx/examples/06-journal-en.md +90 -0
  13. package/.grok/skills/lit-docx/examples/07-report-en.md +103 -0
  14. package/.grok/skills/lit-docx/examples/08-proposal-en.md +90 -0
  15. package/.grok/skills/lit-docx/examples/09-memo-ko.md +41 -0
  16. package/.grok/skills/lit-docx/references/components.md +151 -0
  17. package/.grok/skills/lit-docx/references/direction-step.md +129 -0
  18. package/.grok/skills/lit-docx/references/page-composition.md +129 -0
  19. package/.grok/skills/lit-docx/references/tonalities/brief.md +98 -0
  20. package/.grok/skills/lit-docx/references/tonalities/journal.md +100 -0
  21. package/.grok/skills/lit-docx/references/tonalities/manual.md +107 -0
  22. package/.grok/skills/lit-docx/references/tonalities/memo.md +95 -0
  23. package/.grok/skills/lit-docx/references/tonalities/proposal.md +105 -0
  24. package/.grok/skills/lit-docx/references/tonalities/report.md +114 -0
  25. package/.grok/skills/lit-docx/scripts/convert_md_to_docx.py +394 -51
  26. package/.grok/skills/lit-docx/scripts/docx_design.py +2446 -0
  27. package/.grok/skills/lit-docx/scripts/docx_gate.py +203 -0
  28. package/.grok/skills/lit-docx/scripts/docx_layout.py +959 -0
  29. package/.grok/skills/lit-docx/scripts/run.mjs +3 -2
  30. package/.grok/skills/lit-docx/scripts/slop_lint.py +63 -17
  31. package/.grok/skills/lit-docx/templates/tonalities/brief.yaml +29 -0
  32. package/.grok/skills/lit-docx/templates/tonalities/journal.yaml +26 -0
  33. package/.grok/skills/lit-docx/templates/tonalities/manual.yaml +27 -0
  34. package/.grok/skills/lit-docx/templates/tonalities/memo.yaml +27 -0
  35. package/.grok/skills/lit-docx/templates/tonalities/proposal.yaml +27 -0
  36. package/.grok/skills/lit-docx/templates/tonalities/report.yaml +27 -0
  37. package/.grok/skills/lit-pptx/NOTICE.md +1 -1
  38. package/.grok/skills/lit-pptx/SKILL.md +70 -14
  39. package/.grok/skills/lit-pptx/agents/analyst.md +1 -1
  40. package/.grok/skills/lit-pptx/agents/thesis.md +2 -2
  41. package/.grok/skills/lit-pptx/examples/01-pitch-signal-ko.md +175 -0
  42. package/.grok/skills/lit-pptx/examples/02-business-review-ledger-ko.md +199 -0
  43. package/.grok/skills/lit-pptx/examples/03-research-talk-paper-en.md +177 -0
  44. package/.grok/skills/lit-pptx/examples/04-lecture-chalk-ko.md +176 -0
  45. package/.grok/skills/lit-pptx/examples/05-data-review-night-en.md +221 -0
  46. package/.grok/skills/lit-pptx/examples/06-image-led-atlas-en.md +143 -0
  47. package/.grok/skills/lit-pptx/examples/07-briefing-gazette-ko.md +192 -0
  48. package/.grok/skills/lit-pptx/examples/08-status-update-ledger-en.md +168 -0
  49. package/.grok/skills/lit-pptx/examples/09-pitch-studio-en.md +157 -0
  50. package/.grok/skills/lit-pptx/examples/10-image-led-studio-ko.md +175 -0
  51. package/.grok/skills/lit-pptx/examples/11-business-review-night-en.md +181 -0
  52. package/.grok/skills/lit-pptx/examples/12-status-update-gazette-ko.md +199 -0
  53. package/.grok/skills/lit-pptx/pretendard-font/public/static/Pretendard-Bold.otf +0 -0
  54. package/.grok/skills/lit-pptx/pretendard-font/public/static/Pretendard-Regular.otf +0 -0
  55. package/.grok/skills/lit-pptx/references/density-and-fill.md +155 -0
  56. package/.grok/skills/lit-pptx/references/direction-step.md +211 -0
  57. package/.grok/skills/lit-pptx/references/layout-families.md +193 -0
  58. package/.grok/skills/lit-pptx/references/title-treatments.md +134 -0
  59. package/.grok/skills/lit-pptx/references/tonalities/atlas.md +124 -0
  60. package/.grok/skills/lit-pptx/references/tonalities/chalk.md +135 -0
  61. package/.grok/skills/lit-pptx/references/tonalities/gazette.md +127 -0
  62. package/.grok/skills/lit-pptx/references/tonalities/ledger.md +134 -0
  63. package/.grok/skills/lit-pptx/references/tonalities/night.md +132 -0
  64. package/.grok/skills/lit-pptx/references/tonalities/paper.md +127 -0
  65. package/.grok/skills/lit-pptx/references/tonalities/signal.md +118 -0
  66. package/.grok/skills/lit-pptx/references/tonalities/studio.md +123 -0
  67. package/.grok/skills/lit-pptx/scripts/compile-deck.js +65 -17
  68. package/.grok/skills/lit-pptx/scripts/craft_extras.py +82 -18
  69. package/.grok/skills/lit-pptx/scripts/deck_output.py +598 -0
  70. package/.grok/skills/lit-pptx/scripts/embed_fonts.py +1 -1
  71. package/.grok/skills/lit-pptx/scripts/inventory.py +6 -2
  72. package/.grok/skills/lit-pptx/scripts/lib/directive-parser.js +2 -2
  73. package/.grok/skills/lit-pptx/scripts/lib/grid-resolver.js +521 -0
  74. package/.grok/skills/lit-pptx/scripts/lib/layout-resolver.js +401 -16
  75. package/.grok/skills/lit-pptx/scripts/lib/markdown-loader.js +6 -4
  76. package/.grok/skills/lit-pptx/scripts/lib/render-adapter-html.js +2 -2
  77. package/.grok/skills/lit-pptx/scripts/lib/render-adapter-pptx.js +500 -126
  78. package/.grok/skills/lit-pptx/scripts/lib/render-pack.js +3558 -0
  79. package/.grok/skills/lit-pptx/scripts/lib/slide-ast.js +54 -6
  80. package/.grok/skills/lit-pptx/scripts/lib/template-registry.js +335 -11
  81. package/.grok/skills/lit-pptx/scripts/qa_deck.py +43 -10
  82. package/.grok/skills/lit-pptx/scripts/run.mjs +3 -2
  83. package/.grok/skills/lit-pptx/scripts/validate_pptx.py +17 -5
  84. package/.grok/skills/lit-pptx/templates/boilerplate_professional.css +6 -6
  85. package/.grok/skills/lit-pptx/templates/enrolled/AZURE-A2Z/layout-mapping.yaml +1 -1
  86. package/.grok/skills/lit-pptx/templates/enrolled/AZURE-A2Z/template.yaml +4 -1
  87. package/.grok/skills/lit-pptx/templates/enrolled/AZURE-PRO/layout-mapping.yaml +1 -1
  88. package/.grok/skills/lit-pptx/templates/enrolled/AZURE-PRO/template.yaml +4 -1
  89. package/.grok/skills/lit-pptx/templates/tonalities/atlas/pack.yaml +99 -0
  90. package/.grok/skills/lit-pptx/templates/tonalities/chalk/pack.yaml +104 -0
  91. package/.grok/skills/lit-pptx/templates/tonalities/gazette/pack.yaml +99 -0
  92. package/.grok/skills/lit-pptx/templates/tonalities/ledger/pack.yaml +100 -0
  93. package/.grok/skills/lit-pptx/templates/tonalities/night/pack.yaml +100 -0
  94. package/.grok/skills/lit-pptx/templates/tonalities/paper/pack.yaml +105 -0
  95. package/.grok/skills/lit-pptx/templates/tonalities/signal/pack.yaml +101 -0
  96. package/.grok/skills/lit-pptx/templates/tonalities/studio/pack.yaml +99 -0
  97. package/.grok/skills/lit-typographic-motion/THIRD_PARTY_NOTICES +1 -1
  98. package/.grok/skills/lit-typographic-motion/fonts/pins.json +6 -6
  99. package/.grok/skills/lit-typographic-motion/references/runtime.md +1 -1
  100. package/.grok/skills/lit-typographic-motion/references/style-bible.md +2 -2
  101. package/.grok/skills/lit-typographic-motion/references/type-craft.md +2 -2
  102. package/.grok/skills/lit-typographic-motion/scripts/lib/gate.mjs +1 -1
  103. package/.grok/skills/litgrok/SKILL.md +1 -1
  104. package/.grok-plugin/plugin.json +1 -1
  105. package/CHANGELOG.md +11 -0
  106. package/README.md +9 -9
  107. package/README_ko-KR.md +9 -9
  108. package/docs/assets/readme/badge-version.svg +1 -1
  109. package/docs/reference.md +1 -1
  110. package/docs/reference_ko-KR.md +1 -1
  111. package/package.json +1 -1
  112. package/plugin.json +1 -1
  113. package/.grok/skills/lit-pptx/pretendard-font/public/static/PretendardGOV-Bold.otf +0 -0
  114. package/.grok/skills/lit-pptx/pretendard-font/public/static/PretendardGOV-Regular.otf +0 -0
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env python3
2
+ """One pass/fail gate for a converted .docx and the Markdown it came from.
3
+
4
+ It reads five things and prints one JSON report (exit 0 = PASS, 1 = FAIL):
5
+
6
+ package the file is a readable ZIP with a document part, no NaN attribute values, and
7
+ python-docx reopens it
8
+ content the body has text, every Markdown heading reached the document, no unfilled blank
9
+ ([제품명], XXX, TBD, ○○, {{ … }}) is left, and no frontmatter was printed as text
10
+ lint slop_lint's prose rules on the source (journal outline rules only for a manuscript)
11
+ and its DOCX audits: numeric-column alignment (OF-302), Hangul font pairing, and on
12
+ a publisher profile that profile's design rules
13
+ output docx_layout's checks: heading labels and order, the notice's colon, the restraint rules
14
+ of a tonality document, and with --layout the page checks on a LibreOffice render (fill,
15
+ spill onto a near-empty page, stranded headings, split short lists, a heading apart from
16
+ its table, table and figure splits, component variety, cover)
17
+ compare with --compare OTHER.docx, two tonalities of one source must differ in at least three
18
+ structural features
19
+
20
+ Usage:
21
+ node .grok/skills/lit-docx/scripts/run.mjs docx_gate.py report.docx --source report.md --layout
22
+ node .grok/skills/lit-docx/scripts/run.mjs docx_gate.py paper.docx --source paper.md --publisher elsevier --kind manuscript
23
+
24
+ A PASS means no defect was found. Whether the pages read well is decided by looking at them.
25
+ """
26
+ from __future__ import annotations
27
+
28
+ import argparse
29
+ import json
30
+ import re
31
+ import shutil
32
+ import subprocess
33
+ import sys
34
+ import tempfile
35
+ import zipfile
36
+ from pathlib import Path
37
+
38
+ from docx import Document
39
+
40
+ import docx_layout
41
+ import slop_lint
42
+ from convert_md_to_docx import parse_frontmatter
43
+
44
+ SKILL = Path(__file__).resolve().parents[1]
45
+ HEADING = re.compile(r"^(#{1,6})\s+(.+?)\s*#*\s*$")
46
+ # A bracket holding a digit is a citation marker ([1], [S3]); a blank holds none.
47
+ BLANK = re.compile(r"\[(?![^\]\n]*\d)[^\]\n]{1,24}\]|\bX{3,}\b|\bTBD\b|\bTODO\b|○○|OOO|\{\{[^}]*\}\}|_{4,}|_{2,}")
48
+ LEAK = re.compile(r"^(?:---|title:|subtitle:|author:|authors:|date:|tonality:)\s*", re.M)
49
+ # Audits every profile shares; the rest belong to a named publisher profile.
50
+ SHARED_DESIGN = {"rule-45-highlighted-text", "rule-46-tracked-changes", "rule-55-ellipsis"}
51
+
52
+
53
+ def check_package(path: Path) -> dict:
54
+ problems = []
55
+ try:
56
+ with zipfile.ZipFile(path) as archive:
57
+ names = set(archive.namelist())
58
+ if "word/document.xml" not in names or "[Content_Types].xml" not in names:
59
+ problems.append("document part or content types missing")
60
+ bad = archive.testzip()
61
+ if bad:
62
+ problems.append(f"corrupt member {bad}")
63
+ for name in names:
64
+ if name.endswith(".xml") and b'="NaN"' in archive.read(name):
65
+ problems.append(f"NaN attribute value in {name}")
66
+ Document(str(path))
67
+ except (zipfile.BadZipFile, KeyError, ValueError, OSError) as error:
68
+ problems.append(f"cannot open: {error}")
69
+ return {"pass": not problems, "problems": problems}
70
+
71
+
72
+ def source_headings(text: str) -> list[str]:
73
+ found, fenced = [], False
74
+ for line in text.splitlines():
75
+ if line.lstrip().startswith(("```", "~~~")):
76
+ fenced = not fenced
77
+ continue
78
+ match = None if fenced else HEADING.match(line)
79
+ if match:
80
+ found.append(re.sub(r"[*_`]", "", match.group(2)).strip())
81
+ return found
82
+
83
+
84
+ def check_content(path: Path, markdown: str | None) -> dict:
85
+ document = Document(str(path))
86
+ paragraphs = [p.text.strip() for p in document.paragraphs if p.text.strip()]
87
+ cells = [cell.text for table in document.tables for row in table.rows for cell in row.cells]
88
+ blanks = sorted({m.group(0) for text in paragraphs + cells for m in BLANK.finditer(text)})
89
+ leaked = [p for p in paragraphs[:8] if LEAK.match(p)]
90
+ # A heading may stand inside a side-by-side columns table, so cells count as text too.
91
+ seen = docx_layout.norm("".join(paragraphs + cells))
92
+ missing = [h for h in source_headings(markdown)] if markdown else []
93
+ missing = [h for h in missing if docx_layout.norm(h) not in seen]
94
+ chars = sum(map(len, paragraphs)) + sum(map(len, cells))
95
+ return {"pass": chars > 0 and not blanks and not leaked and not missing, "characters": chars,
96
+ "blanks": blanks[:20], "frontmatter_leak": leaked[:4], "missing_headings": missing[:20]}
97
+
98
+
99
+ def lint(source: Path | None, path: Path, publisher: str | None, kind: str) -> dict:
100
+ registry = SKILL / "templates" / "registry.yaml"
101
+ profile = slop_lint.load_publisher(registry, publisher or "korean-generic")
102
+ rules = slop_lint.load_phrase_rules(SKILL / "references" / "slop_phrase_list.yaml")
103
+ prose, locale = [], "auto"
104
+ if source is not None:
105
+ genre = "manuscript" if kind == "manuscript" else "general"
106
+ prose, _front, _body, locale = slop_lint.lint_text(source.read_text(encoding="utf-8"), profile, rules, "auto", genre)
107
+ design = slop_lint.audit_docx_design(path, profile)
108
+ if publisher is None:
109
+ design = [f for f in design if f.rule_id in SHARED_DESIGN]
110
+ craft = slop_lint.audit_docx_craft(path)
111
+ if locale in {"ko", "mixed"} or source is None:
112
+ design += slop_lint.audit_docx_cjk(path)
113
+ row = lambda f: {"rule": f.rule_id, "severity": f.severity, "line": f.line, "message": f.message, "excerpt": f.excerpt[:80]} # noqa: E731
114
+ blocking = [row(f) for f in prose + design + craft if f.severity == "HIGH"]
115
+ return {"pass": not blocking, "locale": locale, "blocking": blocking[:40],
116
+ "advisories": [row(f) for f in prose + design + craft if f.severity != "HIGH"][:40]}
117
+
118
+
119
+ def render(path: Path, out: Path) -> Path | None:
120
+ """PDF through LibreOffice with a private profile, so a running office session is never touched."""
121
+ soffice = shutil.which("soffice")
122
+ if not soffice:
123
+ return None
124
+ profile = out / "profile"
125
+ subprocess.run([soffice, f"-env:UserInstallation=file://{profile}", "--headless", "--convert-to", "pdf",
126
+ "--outdir", str(out), str(path)], capture_output=True, text=True, timeout=180)
127
+ pdf = out / f"{path.stem}.pdf"
128
+ return pdf if pdf.exists() else None
129
+
130
+
131
+ def output_checks(path: Path, source: Path | None, args) -> dict:
132
+ front = parse_frontmatter(source.read_text(encoding="utf-8"))[0] if source else {}
133
+ tonality = bool(args.tonality or front.get("tonality"))
134
+ info = docx_layout.read_docx(path)
135
+ found = docx_layout.structural(info, front, args.kind == "manuscript", tonality) + docx_layout.restraint(path, tonality)
136
+ result = {"components": info["components"], "rendered": False}
137
+ if args.compare:
138
+ mine, other = docx_layout.structure(path), docx_layout.structure(args.compare.expanduser().resolve())
139
+ differ = docx_layout.structure_diff(mine, other)
140
+ result["structure"] = {"differ": differ}
141
+ if len(differ) < 3:
142
+ found.append(docx_layout.finding("tonality.structure", "FAIL", None,
143
+ f"{len(differ)} structural difference(s): {', '.join(differ) or 'none'}",
144
+ "Tonalities differ in structure: title block, summary, numbering, components, running head, contents."))
145
+ if args.layout:
146
+ with tempfile.TemporaryDirectory(prefix="litgrok-docx-gate-") as tmp:
147
+ try:
148
+ pdf = render(path, Path(tmp))
149
+ except subprocess.TimeoutExpired:
150
+ pdf = None
151
+ if pdf is None:
152
+ result["render"] = "LibreOffice is unavailable or the render failed; the page checks did not run"
153
+ else:
154
+ paged, pages = docx_layout.paged(info, docx_layout.read_pdf(pdf), tonality, front)
155
+ found += paged
156
+ result.update(pages, rendered=True)
157
+ result["findings"] = [f for f in found if f["severity"] == "FAIL"]
158
+ result["advisories"] = [f for f in found if f["severity"] != "FAIL"]
159
+ return result
160
+
161
+
162
+ def main(argv: list[str] | None = None) -> int:
163
+ parser = argparse.ArgumentParser(description="Pass/fail gate for a converted DOCX.")
164
+ parser.add_argument("docx", type=Path)
165
+ parser.add_argument("--source", type=Path, help="the Markdown the DOCX was converted from")
166
+ parser.add_argument("--publisher", help="the publisher profile used (korean-generic, elsevier, acs, ieee, nature); omit otherwise")
167
+ parser.add_argument("--kind", choices=("report", "manuscript"), default="report")
168
+ parser.add_argument("--tonality", help="the tonality used, when the source frontmatter does not name it")
169
+ parser.add_argument("--layout", action="store_true", help="render with LibreOffice and run the page checks")
170
+ parser.add_argument("--compare", type=Path, help="the same source in another tonality")
171
+ args = parser.parse_args(argv)
172
+
173
+ path = args.docx.expanduser().resolve()
174
+ source = args.source.expanduser().resolve() if args.source else None
175
+ package = check_package(path)
176
+ empty = {"pass": False}
177
+ content = check_content(path, source.read_text(encoding="utf-8") if source else None) if package["pass"] else empty
178
+ lint_report = lint(source, path, args.publisher, args.kind) if package["pass"] else empty
179
+ output = output_checks(path, source, args) if package["pass"] else {"findings": [], "advisories": []}
180
+
181
+ reasons = []
182
+ if not package["pass"]:
183
+ reasons.append("package: " + "; ".join(package["problems"][:5]))
184
+ if content.get("blanks"):
185
+ reasons.append(f"content: unfilled blanks {content['blanks'][:5]} — write realistic values and label them as examples")
186
+ if content.get("frontmatter_leak"):
187
+ reasons.append("content: frontmatter printed as body text")
188
+ if content.get("missing_headings"):
189
+ reasons.append(f"content: headings missing from the document {content['missing_headings'][:5]}")
190
+ if package["pass"] and not content.get("characters"):
191
+ reasons.append("content: the document has no text")
192
+ if lint_report.get("blocking"):
193
+ reasons.append("lint: " + ", ".join(sorted({f["rule"] for f in lint_report["blocking"]})))
194
+ if output["findings"]:
195
+ reasons.append("output: " + ", ".join(sorted({f["check"] for f in output["findings"]})))
196
+ report = {"file": str(path), "pass": not reasons, "failure_reasons": reasons, "profile": args.publisher or "plain",
197
+ "kind": args.kind, "package": package, "content": content, "lint": lint_report, "output": output}
198
+ print(json.dumps(report, ensure_ascii=False, indent=2))
199
+ return 0 if report["pass"] else 1
200
+
201
+
202
+ if __name__ == "__main__":
203
+ sys.exit(main())