@litfamily/litopencode 1.0.16 → 1.0.17

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 (117) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README-Ko-KR.md +11 -11
  3. package/README.md +11 -11
  4. package/dist/activation-routing.js +1 -1
  5. package/dist/features.js +2 -2
  6. package/dist/skills.js +6 -4
  7. package/docs/assets/readme/badge-version.svg +1 -1
  8. package/docs/reference-Ko-KR.md +1 -1
  9. package/package.json +1 -1
  10. package/skills/frontend-ui-ux/data/THIRD-PARTY-NOTICE.txt +1 -3
  11. package/skills/lit-docx/SKILL.md +80 -19
  12. package/skills/lit-docx/references/components.md +253 -0
  13. package/skills/lit-docx/references/direction-step.md +234 -0
  14. package/skills/lit-docx/references/examples/01-report-ko.md +160 -0
  15. package/skills/lit-docx/references/examples/02-brief-ko.md +58 -0
  16. package/skills/lit-docx/references/examples/03-manual-ko.md +107 -0
  17. package/skills/lit-docx/references/examples/04-proposal-ko.md +93 -0
  18. package/skills/lit-docx/references/examples/05-memo-ko.md +42 -0
  19. package/skills/lit-docx/references/examples/06-journal-en.md +84 -0
  20. package/skills/lit-docx/references/examples/07-report-en.md +137 -0
  21. package/skills/lit-docx/references/examples/08-journal-ko.md +93 -0
  22. package/skills/lit-docx/references/examples/09-brief-en.md +64 -0
  23. package/skills/lit-docx/references/page-composition.md +306 -0
  24. package/skills/lit-docx/references/tonalities/brief.md +104 -0
  25. package/skills/lit-docx/references/tonalities/journal.md +101 -0
  26. package/skills/lit-docx/references/tonalities/manual.md +111 -0
  27. package/skills/lit-docx/references/tonalities/memo.md +105 -0
  28. package/skills/lit-docx/references/tonalities/proposal.md +101 -0
  29. package/skills/lit-docx/references/tonalities/report.md +126 -0
  30. package/skills/lit-docx/run.py +2 -1
  31. package/skills/lit-docx/scripts/convert_md_to_docx.py +384 -67
  32. package/skills/lit-docx/scripts/craft_docx.py +204 -1
  33. package/skills/lit-docx/scripts/docx_design.py +2445 -0
  34. package/skills/lit-docx/scripts/docx_layout.py +959 -0
  35. package/skills/lit-docx/scripts/slop_lint.py +23 -6
  36. package/skills/lit-docx/templates/tonalities/brief.yaml +29 -0
  37. package/skills/lit-docx/templates/tonalities/journal.yaml +26 -0
  38. package/skills/lit-docx/templates/tonalities/manual.yaml +27 -0
  39. package/skills/lit-docx/templates/tonalities/memo.yaml +27 -0
  40. package/skills/lit-docx/templates/tonalities/proposal.yaml +27 -0
  41. package/skills/lit-docx/templates/tonalities/report.yaml +27 -0
  42. package/skills/lit-pptx/NOTICE.md +1 -1
  43. package/skills/lit-pptx/SKILL.md +108 -19
  44. package/skills/lit-pptx/pretendard-font/public/static/Pretendard-Bold.otf +0 -0
  45. package/skills/lit-pptx/pretendard-font/public/static/Pretendard-Regular.otf +0 -0
  46. package/skills/lit-pptx/references/density-and-fill.md +283 -0
  47. package/skills/lit-pptx/references/direction-step.md +249 -0
  48. package/skills/lit-pptx/references/examples/01-ops-review-ledger-ko.md +173 -0
  49. package/skills/lit-pptx/references/examples/02-launch-pitch-signal-en.md +153 -0
  50. package/skills/lit-pptx/references/examples/03-field-survey-atlas-ko.md +120 -0
  51. package/skills/lit-pptx/references/examples/04-lecture-chalk-ko.md +132 -0
  52. package/skills/lit-pptx/references/examples/05-research-talk-paper-en.md +162 -0
  53. package/skills/lit-pptx/references/examples/06-policy-briefing-gazette-ko.md +176 -0
  54. package/skills/lit-pptx/references/examples/07-annual-story-studio-en.md +131 -0
  55. package/skills/lit-pptx/references/examples/08-reliability-keynote-night-en.md +182 -0
  56. package/skills/lit-pptx/references/examples/09-status-update-ledger-en.md +147 -0
  57. package/skills/lit-pptx/references/examples/10-investor-pitch-signal-ko.md +136 -0
  58. package/skills/lit-pptx/references/examples/11-forecast-review-night-ko.md +177 -0
  59. package/skills/lit-pptx/references/examples/12-exhibition-proposal-studio-ko.md +149 -0
  60. package/skills/lit-pptx/references/layout-families.md +470 -0
  61. package/skills/lit-pptx/references/title-treatments.md +271 -0
  62. package/skills/lit-pptx/references/tonalities/atlas.md +132 -0
  63. package/skills/lit-pptx/references/tonalities/chalk.md +132 -0
  64. package/skills/lit-pptx/references/tonalities/gazette.md +137 -0
  65. package/skills/lit-pptx/references/tonalities/ledger.md +151 -0
  66. package/skills/lit-pptx/references/tonalities/night.md +148 -0
  67. package/skills/lit-pptx/references/tonalities/paper.md +136 -0
  68. package/skills/lit-pptx/references/tonalities/signal.md +148 -0
  69. package/skills/lit-pptx/references/tonalities/studio.md +149 -0
  70. package/skills/lit-pptx/scripts/compile-deck.js +68 -21
  71. package/skills/lit-pptx/scripts/craft-thresholds.json +45 -1
  72. package/skills/lit-pptx/scripts/craft_extras.py +37 -8
  73. package/skills/lit-pptx/scripts/deck_output.py +654 -0
  74. package/skills/lit-pptx/scripts/embed_fonts.py +2 -2
  75. package/skills/lit-pptx/scripts/layout_inventory.py +35 -2
  76. package/skills/lit-pptx/scripts/lib/directive-parser.js +2 -2
  77. package/skills/lit-pptx/scripts/lib/font-map.js +4 -4
  78. package/skills/lit-pptx/scripts/lib/grid-resolver.js +521 -0
  79. package/skills/lit-pptx/scripts/lib/layout-resolver.js +403 -21
  80. package/skills/lit-pptx/scripts/lib/markdown-loader.js +6 -4
  81. package/skills/lit-pptx/scripts/lib/render-adapter-html.js +3 -9
  82. package/skills/lit-pptx/scripts/lib/render-adapter-pptx.js +500 -113
  83. package/skills/lit-pptx/scripts/lib/render-pack.js +3558 -0
  84. package/skills/lit-pptx/scripts/lib/slide-ast.js +55 -24
  85. package/skills/lit-pptx/scripts/lib/spec-validator.js +2 -2
  86. package/skills/lit-pptx/scripts/lib/template-registry.js +335 -11
  87. package/skills/lit-pptx/scripts/qa_deck.py +48 -13
  88. package/skills/lit-pptx/scripts/validate_pptx.py +17 -5
  89. package/skills/lit-pptx/specs/template-enrollment-contract.md +4 -4
  90. package/skills/lit-pptx/templates/boilerplate_professional.css +13 -13
  91. package/skills/lit-pptx/templates/enrolled/AZURE-A2Z/layout-mapping.yaml +1 -1
  92. package/skills/lit-pptx/templates/enrolled/AZURE-A2Z/template.yaml +5 -2
  93. package/skills/lit-pptx/templates/enrolled/AZURE-PRO/capabilities.yaml +20 -20
  94. package/skills/lit-pptx/templates/enrolled/AZURE-PRO/layout-mapping.yaml +1 -11
  95. package/skills/lit-pptx/templates/enrolled/AZURE-PRO/template.yaml +10 -7
  96. package/skills/lit-pptx/templates/enrolled/BOILERPLATE-PRETENDARD/capabilities.yaml +17 -17
  97. package/skills/lit-pptx/templates/enrolled/BOILERPLATE-PRETENDARD/template.yaml +8 -8
  98. package/skills/lit-pptx/templates/tonalities/atlas/pack.yaml +105 -0
  99. package/skills/lit-pptx/templates/tonalities/chalk/pack.yaml +110 -0
  100. package/skills/lit-pptx/templates/tonalities/gazette/pack.yaml +106 -0
  101. package/skills/lit-pptx/templates/tonalities/ledger/pack.yaml +107 -0
  102. package/skills/lit-pptx/templates/tonalities/night/pack.yaml +106 -0
  103. package/skills/lit-pptx/templates/tonalities/paper/pack.yaml +111 -0
  104. package/skills/lit-pptx/templates/tonalities/signal/pack.yaml +107 -0
  105. package/skills/lit-pptx/templates/tonalities/studio/pack.yaml +105 -0
  106. package/skills/lit-typographic-motion/THIRD_PARTY_NOTICES +1 -1
  107. package/skills/lit-typographic-motion/engine/stage/capture.mjs +2 -2
  108. package/skills/lit-typographic-motion/engine/treatment.mjs +1 -1
  109. package/skills/lit-typographic-motion/fonts/pins.json +2 -2
  110. package/skills/lit-typographic-motion/gate.mjs +1 -1
  111. package/skills/lit-typographic-motion/references/stage.md +1 -1
  112. package/skills/lit-typographic-motion/references/style-bibles.md +2 -2
  113. package/skills/lit-typographic-motion/references/treatment.md +1 -1
  114. package/skills/lit-typographic-motion/references/type-craft.md +3 -3
  115. package/skills/managed-skill-manifest.json +288 -44
  116. package/skills/lit-pptx/pretendard-font/public/static/LitOpenCodeSans-Bold.otf +0 -0
  117. package/skills/lit-pptx/pretendard-font/public/static/LitOpenCodeSans-Regular.otf +0 -0
@@ -1,9 +1,34 @@
1
- """Source-only DOCX craft checks with explicit measurement tiers."""
1
+ """DOCX craft checks with explicit measurement tiers, and the document gate built on them.
2
+
3
+ `analyse()` is the source-only craft floor (OF-301 prose measure, OF-302 numeric alignment) that
4
+ slop_lint's audit route reports. Run as a script, this file is the document gate (`run.py qa`):
5
+
6
+ 1. Integrity the package opens, its parts parse and python-docx reopens it
7
+ 2. Content the document has body text, every Markdown heading reached it, no unfilled blank
8
+ ([제품명], XXX, TBD, ○○, {{…}}) is left and no frontmatter leaked into the body
9
+ 3. Lint slop_lint's prose rules on the source (report kind drops the manuscript outline rules),
10
+ its design, Hangul-pairing and craft audit on the DOCX
11
+ 4. Output docx_layout's checks: noun-phrase headings and heading order always, the restraint
12
+ checks (fail under a tonality), with --layout the rendered pages (fill, stranded headings,
13
+ splits, component variety, cover block), with --compare the structural difference
14
+ between two tonalities of one source
15
+
16
+ python3 run.py qa draft.docx --source draft.md --layout [--tonality Report] [--kind report]
17
+ python3 run.py qa draft.docx --source draft.md --publisher elsevier --kind manuscript
18
+ python3 run.py qa a.docx --source draft.md --compare b.docx
19
+
20
+ Prints JSON; exit 0 on pass, 1 on fail. A pass is defect absence, never a verdict on the pages.
21
+ """
2
22
 
3
23
  from __future__ import annotations
4
24
 
25
+ import argparse
5
26
  import json
6
27
  import re
28
+ import shutil
29
+ import subprocess
30
+ import sys
31
+ import tempfile
7
32
  import zipfile
8
33
  from pathlib import Path
9
34
  from xml.etree import ElementTree as ET
@@ -81,3 +106,181 @@ def analyse(path):
81
106
  if value != "right":
82
107
  findings.append({"rule": "OF-302", "severity": "HIGH" if value else "MEDIUM", "tier": "measured" if value else "derived", "where": f"table {table_no} row {row_no} col {column + 1}", "value": value, "threshold": "right"})
83
108
  return findings
109
+
110
+
111
+ HEADING = re.compile(r"^(#{1,6})\s+(.+?)\s*#*\s*$")
112
+ # A bracket holding a digit is a citation ([1], [S3]); a blank holds none.
113
+ BLANK = re.compile(r"\[(?![^\]\n]*\d)[^\]\n]{1,24}\]|\bX{3,}\b|\bTBD\b|\bTODO\b|○○|OOO|\{\{[^}]*\}\}|_{4,}|_{2,}")
114
+ LEAKED_FRONTMATTER = re.compile(r"^(?:---|title:|author:|authors:|date:|subtitle:)\s*", re.M)
115
+ # Under the plain profile only the rules every profile shares apply to the Word file.
116
+ SHARED_DESIGN_RULES = {"rule-45-highlighted-text", "rule-46-tracked-changes", "rule-55-ellipsis", "rule-62-numeric-alignment"}
117
+
118
+
119
+ def _integrity(path):
120
+ problems = []
121
+ try:
122
+ with zipfile.ZipFile(path) as archive:
123
+ names = set(archive.namelist())
124
+ for part in ("[Content_Types].xml", "word/document.xml", "word/styles.xml"):
125
+ if part not in names:
126
+ problems.append(f"missing {part}")
127
+ for name in names:
128
+ if name.endswith((".xml", ".rels")):
129
+ try:
130
+ ET.fromstring(archive.read(name))
131
+ except ET.ParseError as error:
132
+ problems.append(f"{name}: {error}")
133
+ except (OSError, zipfile.BadZipFile) as error:
134
+ problems.append(str(error))
135
+ if not problems:
136
+ from docx import Document
137
+ try:
138
+ Document(str(path))
139
+ except Exception as error: # python-docx raises many kinds on a broken part
140
+ problems.append(f"python-docx cannot reopen it: {error}")
141
+ return {"pass": not problems, "problems": problems[:20]}
142
+
143
+
144
+ def _headings(markdown_text):
145
+ found, fenced = [], False
146
+ for line in markdown_text.splitlines():
147
+ if line.lstrip().startswith(("```", "~~~")):
148
+ fenced = not fenced
149
+ continue
150
+ match = None if fenced else HEADING.match(line)
151
+ if match:
152
+ found.append(re.sub(r"[*_`]", "", match.group(2)).strip())
153
+ return found
154
+
155
+
156
+ def _content(path, markdown_text, layout):
157
+ from docx import Document
158
+ document = Document(str(path))
159
+ paragraphs = [p.text.strip() for p in document.paragraphs if p.text.strip()]
160
+ cells = [cell.text for table in document.tables for row in table.rows for cell in row.cells]
161
+ blanks = sorted({match.group(0) for text in paragraphs + cells for match in BLANK.finditer(text)})
162
+ leaked = [text for text in paragraphs[:8] if LEAKED_FRONTMATTER.match(text)]
163
+ missing = []
164
+ if markdown_text:
165
+ # A heading may stand inside a side-by-side columns row, so table text counts too.
166
+ text = layout.norm("".join(paragraphs + cells))
167
+ missing = [heading for heading in _headings(markdown_text) if layout.norm(heading) not in text]
168
+ chars = sum(map(len, paragraphs)) + sum(map(len, cells))
169
+ return {"pass": chars > 0 and not missing and not blanks and not leaked, "blanks": blanks[:20],
170
+ "frontmatter_leak": leaked[:4], "missing_headings": missing[:20], "paragraphs": len(paragraphs),
171
+ "tables": len(document.tables), "body_chars": chars}
172
+
173
+
174
+ def _lint(source, path, publisher_name, kind, explicit):
175
+ import slop_lint
176
+ here = Path(__file__).resolve().parent
177
+ publisher = slop_lint.load_publisher(here.parent / "templates" / "registry.yaml", publisher_name)
178
+ prose, locale = [], "auto"
179
+ if source is not None:
180
+ phrases = slop_lint.load_phrase_rules(here.parent / "references" / "slop_phrase_list.yaml")
181
+ prose, _front, _body, locale = slop_lint.lint_text(source.read_text(encoding="utf-8"), publisher, phrases, "auto")
182
+ if kind == "report":
183
+ prose = [item for item in prose if item.rule_id not in slop_lint.MANUSCRIPT_ONLY_RULES]
184
+ elif not explicit:
185
+ prose = [item for item in prose if item.rule_id != "rule-08-structure-order"]
186
+ design = slop_lint.audit_docx_design(path, publisher)
187
+ if locale in {"ko", "mixed"} or source is None:
188
+ design += slop_lint.audit_docx_cjk(path)
189
+ craft = slop_lint.audit_docx_craft(path)
190
+ design += [item for item in craft if item.severity == "HIGH"]
191
+ if not explicit:
192
+ design = [item for item in design if item.rule_id in SHARED_DESIGN_RULES]
193
+ entry = lambda item: {"rule": item.rule_id, "line": item.line, "section": item.section, "message": item.message, "excerpt": item.excerpt} # noqa: E731
194
+ return {"pass": not prose and not design, "locale": locale, "prose": [entry(item) for item in prose][:40],
195
+ "design": [entry(item) for item in design][:40], "advisories": [entry(item) for item in craft if item.severity != "HIGH"][:40]}
196
+
197
+
198
+ def _render(path, folder):
199
+ """The document as PDF through soffice with a private profile, or (None, reason)."""
200
+ office = shutil.which("soffice") or shutil.which("libreoffice")
201
+ if office is None:
202
+ return None, "soffice is not on PATH; the page checks did not run"
203
+ profile = Path(folder) / "profile"
204
+ command = [office, f"-env:UserInstallation=file://{profile}", "--headless", "--convert-to", "pdf", "--outdir", str(folder), str(path)]
205
+ try:
206
+ subprocess.run(command, capture_output=True, timeout=180, check=False)
207
+ except subprocess.TimeoutExpired:
208
+ return None, "soffice did not finish within 180 s; the page checks did not run"
209
+ pdf = Path(folder) / (Path(path).stem + ".pdf")
210
+ return (pdf, "soffice") if pdf.exists() else (None, "soffice wrote no PDF; the page checks did not run")
211
+
212
+
213
+ def _output(path, source, args, layout):
214
+ from convert_md_to_docx import parse_frontmatter
215
+ front = parse_frontmatter(source.read_text(encoding="utf-8"))[0] if source else {}
216
+ tonality = bool(args.tonality or front.get("tonality"))
217
+ info = layout.read_docx(path)
218
+ found = layout.structural(info, front, args.kind == "manuscript", tonality) + layout.restraint(path, tonality)
219
+ result = {"components": info["components"], "rendered": False}
220
+ if args.compare:
221
+ mine, other = layout.structure(path), layout.structure(args.compare.expanduser().resolve())
222
+ differ = layout.structure_diff(mine, other)
223
+ result["structure"] = {"this": mine, "other": other, "differ": differ}
224
+ if len(differ) < 3:
225
+ found.append(layout.finding("tonality.structure", "FAIL", None,
226
+ f"the two documents differ in {len(differ)} structural feature(s): {', '.join(differ) or 'none'} (at least 3)",
227
+ "Set the tonalities apart in structure: title block, summary form, numbering, component set, running head, contents."))
228
+ if args.layout:
229
+ with tempfile.TemporaryDirectory(prefix="litopencode-docx-pages-") as folder:
230
+ pdf, how = _render(path, folder)
231
+ if pdf is None:
232
+ result["render"] = how
233
+ else:
234
+ page_found, pages = layout.paged(info, layout.read_pdf(pdf), tonality, front)
235
+ found += page_found
236
+ result.update(pages, rendered=True)
237
+ result["findings"] = [item for item in found if item["severity"] == "FAIL"]
238
+ result["advisories"] = [item for item in found if item["severity"] != "FAIL"]
239
+ return result
240
+
241
+
242
+ def main(argv=None):
243
+ parser = argparse.ArgumentParser(description="Pass/fail gate for a converted DOCX.")
244
+ parser.add_argument("docx", type=Path)
245
+ parser.add_argument("--source", type=Path, default=None, help="the Markdown the DOCX was converted from")
246
+ parser.add_argument("--publisher", default=None, help="the publisher profile it was converted with; omit for a tonality or the plain profile")
247
+ parser.add_argument("--kind", choices=("report", "manuscript"), default="report")
248
+ parser.add_argument("--layout", action="store_true", help="render through soffice and check the pages")
249
+ parser.add_argument("--tonality", default=None, help="the tonality it was converted with, when the frontmatter does not name it")
250
+ parser.add_argument("--compare", type=Path, default=None, help="the same source in another tonality; fail under three structural differences")
251
+ args = parser.parse_args(argv)
252
+ sys.dont_write_bytecode = True
253
+ import docx_layout as layout
254
+
255
+ path = args.docx.expanduser().resolve()
256
+ source = args.source.expanduser().resolve() if args.source else None
257
+ integrity = _integrity(path)
258
+ if integrity["pass"]:
259
+ content = _content(path, source.read_text(encoding="utf-8") if source else None, layout)
260
+ lint = _lint(source, path, args.publisher or "korean-generic", args.kind, args.publisher is not None)
261
+ output = _output(path, source, args, layout)
262
+ else:
263
+ content, lint, output = {"pass": False}, {"pass": False}, {"findings": [], "advisories": []}
264
+ reasons = []
265
+ if not integrity["pass"]:
266
+ reasons.append("integrity: " + "; ".join(integrity["problems"][:5]))
267
+ if content.get("blanks"):
268
+ reasons.append(f"content: unfilled blanks {content['blanks'][:5]} — fill them with realistic values labelled as examples or assumptions")
269
+ if content.get("frontmatter_leak"):
270
+ reasons.append("content: frontmatter printed as body text — convert with run.py md-to-docx, which reads it")
271
+ if integrity["pass"] and not content["pass"] and not content["blanks"] and not content["frontmatter_leak"]:
272
+ reasons.append(f"content: empty document or headings missing from the output {content['missing_headings'][:5]}")
273
+ if lint.get("prose"):
274
+ reasons.append(f"prose lint: {len(lint['prose'])} finding(s)")
275
+ if lint.get("design"):
276
+ reasons.append(f"design audit: {len(lint['design'])} finding(s)")
277
+ if output["findings"]:
278
+ reasons.append("output checks: " + ", ".join(sorted({item["check"] for item in output["findings"]})))
279
+ report = {"file": str(path), "pass": not reasons, "failure_reasons": reasons, "profile": args.publisher or "plain",
280
+ "kind": args.kind, "integrity": integrity, "content": content, "lint": lint, "output": output}
281
+ print(json.dumps(report, ensure_ascii=False, indent=2))
282
+ return 0 if report["pass"] else 1
283
+
284
+
285
+ if __name__ == "__main__":
286
+ raise SystemExit(main())