freeharness 0.4.1 → 0.4.2

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 (194) hide show
  1. package/README.md +8 -7
  2. package/canonical/HARVEST.yaml +25 -14
  3. package/canonical/PROVENANCE.yaml +1 -1
  4. package/canonical/agents/README.md +77 -36
  5. package/canonical/agents/agentic-architect.md +2 -1
  6. package/canonical/agents/database-reviewer.md +6 -8
  7. package/canonical/agents/explorer-ai-engineer-analyst.md +2 -6
  8. package/canonical/agents/implementer.md +30 -0
  9. package/canonical/agents/pr-reviewer.md +9 -11
  10. package/canonical/agents/python-reviewer.md +7 -9
  11. package/canonical/agents/report-formatter.md +7 -3
  12. package/canonical/agents/security-reviewer.md +7 -8
  13. package/canonical/agents/test-writer.md +5 -2
  14. package/canonical/hooks/cost-prices.json +11 -9
  15. package/canonical/hooks/freeharness-config-guard.mjs +8072 -87
  16. package/canonical/hooks/freeharness-context-monitor.mjs +88 -54
  17. package/canonical/hooks/freeharness-converge.mjs +78 -23
  18. package/canonical/hooks/freeharness-cost-tracker.mjs +1129 -218
  19. package/canonical/hooks/freeharness-memory-guard.mjs +20 -23
  20. package/canonical/hooks/freeharness-precompact.mjs +217 -87
  21. package/canonical/hooks/freeharness-quota-tap.mjs +173 -0
  22. package/canonical/hooks/freeharness-reload-context.mjs +197 -0
  23. package/canonical/hooks/freeharness-repeat-tool.mjs +1 -1
  24. package/canonical/hooks/freeharness-sdd-context.mjs +970 -0
  25. package/canonical/hooks/freeharness-session-start +297 -59
  26. package/canonical/hooks/hooks.claude-code.json +1 -1
  27. package/canonical/hooks/hooks.config-guard.claude-code.json +3 -2
  28. package/canonical/hooks/hooks.context-monitor.claude-code.json +1 -1
  29. package/canonical/hooks/hooks.converge.claude-code.json +1 -1
  30. package/canonical/hooks/hooks.memory-guard.claude-code.json +3 -2
  31. package/canonical/hooks/hooks.reload-context.claude-code.json +15 -0
  32. package/canonical/openspec/schema.spec-driven.yaml +29 -8
  33. package/canonical/openspec/templates/follow-ups.md +3 -2
  34. package/canonical/openspec/templates/proposal.md +1 -1
  35. package/canonical/openspec/templates/tasks.md +8 -0
  36. package/canonical/rules/CLAUDE.md +5 -4
  37. package/canonical/rules/routing.md +10 -9
  38. package/canonical/skills/agent-evaluation/SKILL.md +17 -6
  39. package/canonical/skills/agent-evaluation/evals/cases.yaml +38 -23
  40. package/canonical/skills/agentic-architecture/SKILL.md +4 -0
  41. package/canonical/skills/agentic-architecture/evals/cases.yaml +27 -0
  42. package/canonical/skills/code-review/SKILL.md +9 -2
  43. package/canonical/skills/code-review/evals/cases.yaml +1118 -27
  44. package/canonical/skills/git-workflow/SKILL.md +2 -0
  45. package/canonical/skills/local-code/SKILL.md +2 -2
  46. package/canonical/skills/local-code/evals/cases.yaml +1 -1
  47. package/canonical/skills/report-formatting/SKILL.md +10 -2
  48. package/canonical/skills/report-formatting/reference/ARCHITECTURE.md +3 -3
  49. package/canonical/skills/report-formatting/reference/HELPERS.md +10 -5
  50. package/canonical/skills/report-formatting/reference/RELEASE.md +1 -2
  51. package/canonical/skills/report-formatting/reference/TEMPLATES.md +3 -2
  52. package/canonical/skills/report-formatting/reference/authoring.md +25 -37
  53. package/canonical/skills/report-formatting/scripts/build-architecture.js +1 -1
  54. package/canonical/skills/report-formatting/scripts/build-md-claro.js +5 -8
  55. package/canonical/skills/report-formatting/scripts/check-env.js +0 -4
  56. package/canonical/skills/report-formatting/scripts/docmodel.js +3 -11
  57. package/canonical/skills/report-formatting/scripts/example-build.js +8 -10
  58. package/canonical/skills/report-formatting/scripts/example_build_ArchitectureReview_docx.js +8 -10
  59. package/canonical/skills/report-formatting/scripts/example_build_ExecutiveReport_docx.js +8 -10
  60. package/canonical/skills/report-formatting/scripts/theme-claro.js +39 -10
  61. package/canonical/skills/subagent-driven-development/SKILL.md +35 -28
  62. package/canonical/skills/subagent-driven-development/evals/cases.yaml +3496 -19
  63. package/canonical/skills/subagent-driven-development/reference/brief.md +185 -35
  64. package/canonical/skills/subagent-driven-development/reference/handoff.md +82 -2
  65. package/canonical/skills/subagent-driven-development/reference/loop.md +57 -0
  66. package/canonical/skills/subagent-driven-development/reference/trees.md +40 -0
  67. package/canonical/skills/subagent-driven-development/reference/workspace.md +31 -0
  68. package/canonical/skills/subagent-driven-development/scripts/validate-handoff.mjs +4771 -326
  69. package/dist/adapters/harness/claude-code.d.ts +19 -3
  70. package/dist/adapters/harness/claude-code.js +204 -39
  71. package/dist/adapters/harness/opencode.js +16 -2
  72. package/dist/adapters/harness/plugin.js +28 -17
  73. package/dist/adapters/memory/doctor.d.ts +174 -29
  74. package/dist/adapters/memory/doctor.js +758 -134
  75. package/dist/adapters/memory/migrate.d.ts +95 -0
  76. package/dist/adapters/memory/migrate.js +497 -0
  77. package/dist/adapters/memory/paths.d.ts +22 -0
  78. package/dist/adapters/memory/paths.js +85 -0
  79. package/dist/adapters/memory/vendored.d.ts +56 -29
  80. package/dist/adapters/memory/vendored.js +191 -154
  81. package/dist/adapters/sync/diff.js +4 -1
  82. package/dist/cli/cleanup-prompt.d.ts +35 -0
  83. package/dist/cli/cleanup-prompt.js +68 -0
  84. package/dist/cli/cleanup.d.ts +22 -24
  85. package/dist/cli/cleanup.js +182 -68
  86. package/dist/cli/cost-quota.d.ts +158 -0
  87. package/dist/cli/cost-quota.js +645 -0
  88. package/dist/cli/cost.d.ts +408 -46
  89. package/dist/cli/cost.js +1731 -57
  90. package/dist/cli/doctor.d.ts +52 -13
  91. package/dist/cli/doctor.js +193 -44
  92. package/dist/cli/hard-cleanup.d.ts +93 -0
  93. package/dist/cli/hard-cleanup.js +552 -0
  94. package/dist/cli/install-facts.d.ts +28 -0
  95. package/dist/cli/install-facts.js +66 -0
  96. package/dist/cli/json-key-edit.d.ts +18 -0
  97. package/dist/cli/json-key-edit.js +118 -0
  98. package/dist/cli/memory-import.d.ts +74 -0
  99. package/dist/cli/memory-import.js +469 -0
  100. package/dist/cli/plugin-validate.d.ts +12 -0
  101. package/dist/cli/plugin-validate.js +43 -0
  102. package/dist/cli/uninstall.d.ts +1 -1
  103. package/dist/cli/uninstall.js +2 -2
  104. package/dist/cli/upstream-launchers.d.ts +49 -0
  105. package/dist/cli/upstream-launchers.js +287 -0
  106. package/dist/index.js +247 -70
  107. package/dist/infra/fsutil.d.ts +31 -3
  108. package/dist/infra/fsutil.js +69 -6
  109. package/dist/infra/manifest.d.ts +2 -1
  110. package/dist/infra/manifest.js +3 -2
  111. package/dist/infra/prices.d.ts +18 -0
  112. package/dist/infra/prices.js +64 -0
  113. package/node_modules/@freeharness/core/dist/tiers.d.ts +10 -1
  114. package/node_modules/@freeharness/core/dist/tiers.js +24 -2
  115. package/node_modules/@freeharness/core/package.json +1 -1
  116. package/node_modules/@freeharness/sdd/PROVENANCE.yaml +279 -2
  117. package/node_modules/@freeharness/sdd/dist/src/bin/openspec.js +34 -8
  118. package/node_modules/@freeharness/sdd/dist/src/shim/archive.d.ts +7 -6
  119. package/node_modules/@freeharness/sdd/dist/src/shim/archive.js +72 -64
  120. package/node_modules/@freeharness/sdd/dist/src/shim/follow-up.d.ts +48 -16
  121. package/node_modules/@freeharness/sdd/dist/src/shim/follow-up.js +168 -70
  122. package/node_modules/@freeharness/sdd/dist/src/shim/instructions.d.ts +36 -6
  123. package/node_modules/@freeharness/sdd/dist/src/shim/instructions.js +110 -49
  124. package/node_modules/@freeharness/sdd/dist/src/shim/list.d.ts +1 -1
  125. package/node_modules/@freeharness/sdd/dist/src/shim/list.js +57 -14
  126. package/node_modules/@freeharness/sdd/dist/src/shim/nested-change.d.ts +7 -0
  127. package/node_modules/@freeharness/sdd/dist/src/shim/nested-change.js +118 -0
  128. package/node_modules/@freeharness/sdd/dist/src/shim/new-change.js +3 -1
  129. package/node_modules/@freeharness/sdd/dist/src/shim/proposal-capabilities.d.ts +12 -0
  130. package/node_modules/@freeharness/sdd/dist/src/shim/proposal-capabilities.js +94 -0
  131. package/node_modules/@freeharness/sdd/dist/src/shim/shared.d.ts +17 -0
  132. package/node_modules/@freeharness/sdd/dist/src/shim/shared.js +182 -1
  133. package/node_modules/@freeharness/sdd/dist/src/shim/status.d.ts +2 -2
  134. package/node_modules/@freeharness/sdd/dist/src/shim/status.js +34 -5
  135. package/node_modules/@freeharness/sdd/dist/src/shim/validate.d.ts +9 -0
  136. package/node_modules/@freeharness/sdd/dist/src/shim/validate.js +49 -41
  137. package/node_modules/@freeharness/sdd/dist/src/shim/warnings.d.ts +21 -0
  138. package/node_modules/@freeharness/sdd/dist/src/shim/warnings.js +66 -0
  139. package/node_modules/@freeharness/sdd/dist/vendor/openspec/apply/line-endings.d.ts +4 -0
  140. package/node_modules/@freeharness/sdd/dist/vendor/openspec/apply/line-endings.js +17 -0
  141. package/node_modules/@freeharness/sdd/dist/vendor/openspec/apply/specs-apply.js +95 -22
  142. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/change-status-policy.d.ts +6 -0
  143. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/change-status-policy.js +14 -6
  144. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/index.d.ts +1 -1
  145. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/index.js +1 -1
  146. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/outputs.d.ts +3 -3
  147. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/outputs.js +24 -4
  148. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/task-progress.js +4 -7
  149. package/node_modules/@freeharness/sdd/dist/vendor/openspec/engine/types.js +13 -1
  150. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/apply-change.js +21 -6
  151. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/archive-change.js +80 -19
  152. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/explore.js +42 -22
  153. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/project-root.d.ts +32 -0
  154. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/project-root.js +39 -0
  155. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/propose.js +16 -9
  156. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/sync-specs.js +36 -1
  157. package/node_modules/@freeharness/sdd/dist/vendor/openspec/generation/templates/workflows/update-change.js +65 -30
  158. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/constants.d.ts +1 -1
  159. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/constants.js +1 -1
  160. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/change-parser.d.ts +8 -13
  161. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/change-parser.js +79 -102
  162. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/markdown-parser.js +4 -9
  163. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-blocks.d.ts +50 -10
  164. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-blocks.js +165 -37
  165. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-text.d.ts +24 -3
  166. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/requirement-text.js +55 -8
  167. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/parsers/spec-structure.js +4 -1
  168. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/purpose-placeholder.js +19 -9
  169. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/spec-discovery.d.ts +10 -0
  170. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/spec-discovery.js +115 -0
  171. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/task-lines.d.ts +3 -3
  172. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/task-lines.js +20 -20
  173. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/validator.d.ts +6 -0
  174. package/node_modules/@freeharness/sdd/dist/vendor/openspec/validation/validator.js +75 -14
  175. package/node_modules/@freeharness/sdd/package.json +1 -1
  176. package/node_modules/@freeharness/sdd/vendor/openspec/assets/schema.upstream.yaml +47 -7
  177. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-apply-change/SKILL.md +25 -7
  178. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-archive-change/SKILL.md +50 -11
  179. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-bulk-archive-change/SKILL.md +48 -10
  180. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-continue-change/SKILL.md +27 -7
  181. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-explore/SKILL.md +25 -12
  182. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-ff-change/SKILL.md +17 -5
  183. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-new-change/SKILL.md +12 -1
  184. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-onboard/SKILL.md +47 -33
  185. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-propose/SKILL.md +17 -5
  186. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-sync-specs/SKILL.md +29 -1
  187. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-update-change/SKILL.md +31 -13
  188. package/node_modules/@freeharness/sdd/vendor/openspec/assets/skills/openspec-verify-change/SKILL.md +75 -27
  189. package/package.json +6 -4
  190. package/provenance/canonical.PROVENANCE.yaml +1 -1
  191. package/provenance/memory.PROVENANCE.yaml +458 -6
  192. package/provenance/sdd.PROVENANCE.yaml +279 -2
  193. package/canonical/hooks/freeharness-auto-memory.mjs +0 -158
  194. package/canonical/hooks/hooks.auto-memory.claude-code.json +0 -16
@@ -20,6 +20,7 @@ Git operations are deterministic and reversible. The orchestrator runs them dire
20
20
  <push_and_pr>
21
21
  1. Never commit directly on `main`/`master`: create `type/short-topic` from the current branch first.
22
22
  2. `git push -u origin <branch>`. Never `--force` or `--force-with-lease` on a shared branch; if history must change, ask the user and explain the consequence.
23
+ The config guard hook denies history-destroying git commands (e.g. `reset --hard`, `--force` push, `branch -D`, `clean -f`, discarding `checkout`/`restore`, `stash drop`, `worktree remove --force` outside the temp root) from the model, also when a same-line variable, `xargs ... sh -c` or interpreter code that spawns processes runs them; the user runs an intended one with the `!` prompt prefix. Git text inside a quoted argument, a `sed`/`grep`/`awk` script or interpreter code without a process-spawning call passes. The hook lets `--force-with-lease` through, so the shared-branch ban on it rests on the rule above.
23
24
  3. Open the PR with `gh pr create`: title = the commit summary (or the change id for spec-driven work), body = summary of what and why, the OpenSpec change reference, the verification evidence (test/type/lint commands and results), and a checklist of review items. Draft when verification is incomplete.
24
25
  4. Report the PR URL.
25
26
  </push_and_pr>
@@ -34,6 +35,7 @@ Local branches whose remote is gone: `git fetch --prune`, list `git branch -vv |
34
35
  - Never offer "discard" as an option for uncommitted work: the choices are commit, stash (`git stash push -u` inside that worktree) or leave it.
35
36
  - Never operate on another worktree's branch: no `git switch`/`git checkout` to a branch checked out elsewhere, no `gh pr checkout` in the current tree unless the user asks; inspect PRs with `gh pr view`, `gh pr diff`, `git show <ref>:<path>` against fetched refs.
36
37
  - Rebase conflicts: resolve only in files this branch modified; a conflict in a file it did not touch means abort and ask.
38
+ - SDD task trees under `.worktrees/<change>/<task>` are created, landed and removed by the subagent-driven-development tree rule (`validate-handoff.mjs tree` and `commit --tree`), never by hand.
37
39
  </worktrees>
38
40
 
39
41
  <output_format>
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: local-code
3
- description: Use when a file exceeds 100 lines, a log or stack trace is large, or a bulk multi-file scan or AST summary is needed before reading: offloads ingestion to the local llama.cpp server (llama-server on localhost:8080) and returns a compressed digest. Falls back to Grep/Glob when the server is down.
3
+ description: Use when a log or stack trace is large, or a bulk multi-file scan or AST summary is needed before reading: offloads ingestion to llama-server on localhost:8080 and returns a compressed digest. Falls back to Grep/Glob when the server is down.
4
4
  tools: Read, Glob, Grep, Bash
5
5
  ---
6
6
 
@@ -14,7 +14,7 @@ tools: Read, Glob, Grep, Bash
14
14
 
15
15
  <workflow>
16
16
  1. Identify Files:
17
- Identify candidate files using `Glob` or `Grep`. For files exceeding 100 lines, delegate reading to the local model.
17
+ Identify candidate files using `Glob` or `Grep`. Read source files directly with `Read` line ranges; delegate large logs, stack traces, bulk multi-file scans, or an AST or call-graph summary the caller asks for, to the local model.
18
18
 
19
19
  2. Execute Local Extraction:
20
20
  Call the local extractor pointing to the skill script path:
@@ -75,7 +75,7 @@ cases:
75
75
  dogfood: 2026-09-06 STT
76
76
  provisional: true
77
77
  input: "Read src/stt/config.py (40 lines) and tell me the default sample rate."
78
- expected: "A 40-line file is read directly; the local extractor is not invoked below the 100-line threshold."
78
+ expected: "A 40-line file is read directly; the local extractor is not invoked for a single small source file."
79
79
  graders:
80
80
  - type: tool_used
81
81
  expected: "Read:src/stt/config.py"
@@ -54,9 +54,17 @@ Do not author a standalone JS script.
54
54
  </pipeline_routing>
55
55
 
56
56
  <editorial_invariants>
57
+ - Page identity (every .docx, categories A and B): the layout of the reference release
58
+ document (stt-whisperx — Release 0.4.2), as encoded in `scripts/theme-claro.js` — A4,
59
+ `compact` geometry (1 cm left/right, top 1.35 cm, bottom 1.03 cm, header 0.31 cm, footer
60
+ 0.29 cm), header `<app> — <document type>` italic grey 9 pt with the Claro logo right and
61
+ a bottom rule, footer `<author> — <role> | <area>` left and `Confidencial – Uso Interno |
62
+ Página X de Y` right over a top rule, `ELABORAÇÃO` block at the end. Use the theme
63
+ defaults (`T.headerLine`, `T.footerAuthorLine`, `T.HOUSE`); never hand-set margins,
64
+ header or footer. Details: `reference/authoring.md`.
57
65
  - Typography: Arial for prose, Consolas for code/identifiers. Body justified at 1.15 line spacing.
58
66
  - Pagination integrity: Heading lines enforced with `keepNext`; short tables and code blocks kept on single page; zero orphaned lines.
59
67
  - Semaphore colors: Red = Critical/Action, Amber = Attention/Hypothesis, Green = Healthy/Target, Blue = Insight.
60
- - File Lock Safety: If `~$<name>.docx` exists, Microsoft Word has the file locked. Write to an incremented filename (`<name>_v2.docx`) to prevent corruption.
61
- - Deliverable: Deliver `.docx` or `.pptx` only. Never leave throwaway validation PDFs in production paths.
68
+ - File Lock Safety: If `~$<name>.docx` exists, Microsoft Word has the file locked. Never overwrite it: the category A engines write `<name>-new.docx` automatically; a category B build writes `<name>-new.docx` the same way.
69
+ - Deliverable: `.docx` or `.pptx` only. No PDF is generated, not even as a check: the `.docx` is the print master (printed from Word).
62
70
  </editorial_invariants>
@@ -12,7 +12,7 @@ build fails on an unknown section, a missing mandatory section or a malformed ta
12
12
  node scripts/build-architecture.js <architecture.md> <out.docx>
13
13
  ```
14
14
 
15
- Deliver only the `.docx` (PDF render only as a throwaway check in a scratch directory).
15
+ Deliver only the `.docx` (print master). No PDF is generated.
16
16
  Same page identity as every house document: Claro logo, compact layout (1 cm lateral
17
17
  margins), justified 1.15 text, two fonts, blocks flush with the text. Cover page +
18
18
  static table of contents (titles only, no field update prompt; Word's navigation pane
@@ -77,8 +77,8 @@ sections as H1 (numbered), `###` as numbered H2 (N.k), `####` as H3; ```mermaid
77
77
  rendered and embedded (scaled to the text width and at most ~half a page; `<!-- width:
78
78
  70 -->` before a diagram scales it); "Figura N – …" / "Tabela N – …" lines become
79
79
  captions; `> **Nota:** …` callouts; pipe tables (`<!-- cols: 30 70 -->` fixes widths);
80
- `ELABORAÇÃO` block at the end; header "Documento de Arquitetura de Software — <app> |
81
- <project>".
80
+ `ELABORAÇÃO` block at the end; header "<app> — Arquitetura de Software" (house
81
+ identity, reference/authoring.md).
82
82
 
83
83
  ## Diagram set (minimum)
84
84
 
@@ -14,9 +14,10 @@ Import: `const T = require("./theme-claro");`
14
14
  - Lead-in sentences before a code block or table take `{ keepNext: true }` so the
15
15
  sentence and the block never separate.
16
16
  - Page geometry by layout — select it with `T.configure({ layout })` **before building
17
- any block**, because the usable width derives from it: `report` (2 cm all round,
18
- header/footer at 1.25 cm) and `compact` (release documents: 1 cm left/right, top
19
- 1.35 cm, bottom 1.03 cm, header 0.31 cm, footer 0.29 cm from the page edge).
17
+ any block**, because the usable width derives from it: `compact` (default, the house
18
+ page of every .docx: 1 cm left/right, top 1.35 cm, bottom 1.03 cm, header 0.31 cm,
19
+ footer 0.29 cm from the page edge) and `report` (opt-out: 2 cm all round,
20
+ header/footer at 1.25 cm).
20
21
  `T.configure({ page: { left, right, top, bottom, header, footer } })` overrides in DXA
21
22
  ("personalizado"). `T.contentWidth()` is the usable width; tables, code blocks,
22
23
  callouts, stat cards and images span exactly that width and are centered, so their
@@ -94,9 +95,13 @@ Import: `const T = require("./theme-claro");`
94
95
  (spread: `children.push(...T.coverBlock({...}))`). `rule: false` for the compact
95
96
  release front page (title + metaTable, no cover rule).
96
97
  - `T.authorBlock({ heading, name, role, area, project })` -> array; heading defaults to
97
- "ELABORAÇÃO E ANÁLISE" ("ELABORAÇÃO" for release docs).
98
+ "ELABORAÇÃO", name/role to the house author (`T.HOUSE`).
98
99
  - `T.buildDocument({ children, headerText, footerAuthor, footerNote, layout, page })` ->
99
- Document for `Packer.toBuffer`. `footerNote` defaults to "Confidencial – Uso Interno".
100
+ Document for `Packer.toBuffer`. `footerAuthor` defaults to `T.footerAuthorLine()`,
101
+ `footerNote` to "Confidencial – Uso Interno".
102
+ - `T.headerLine(app, docType)` -> `"<app> — <document type>"`; `T.footerAuthorLine({ author, role, area })`
103
+ -> `"<author> — <role> | <area>"` (missing fields keep the house value). Page identity:
104
+ reference/authoring.md.
100
105
  Prefer `T.configure({ layout })` at the top of the script; passing `layout`/`page` here
101
106
  applies them to the page only and warns (blocks were already sized).
102
107
 
@@ -15,8 +15,7 @@ unknown change category — the structure is the model, not a suggestion.
15
15
  node scripts/build-release.js <release.md> <out.docx>
16
16
  ```
17
17
 
18
- Deliver only the `.docx`. A PDF render (soffice) is allowed only as a throwaway visual
19
- check in a scratch directory; it is never written next to the deliverable.
18
+ Deliver only the `.docx` (print master). No PDF is generated.
20
19
 
21
20
  ## Where the structure comes from (2024-2026 practice)
22
21
 
@@ -36,8 +36,9 @@ project data, clear + rebuild a slide. Preferred over rebuilding by hand; see PP
36
36
 
37
37
  All .docx templates share the house typography (reference/HELPERS.md, "House
38
38
  typography"): two fonts, justified 1.15 text, headings kept with their text, tables and
39
- code blocks never cut by a page break. The release and architecture models use `layout: "compact"` (1 cm lateral margins);
40
- the others keep the default `report` geometry.
39
+ code blocks never cut by a page break — and the house page identity of the release
40
+ document (compact geometry, header `<app> — <document type>`, footer author + page
41
+ count; reference/authoring.md). `.docx` is the only output; no PDF is generated.
41
42
 
42
43
  ## Adding a new template
43
44
 
@@ -4,50 +4,38 @@ Defaults for the authorship block and footer. Override if the user names another
4
4
  author/project. These are house defaults, not hardcoded — pass different values
5
5
  to the helpers when needed.
6
6
 
7
- ## Default author
7
+ ## House page identity (every .docx)
8
8
 
9
- - Name: André Negrelli
10
- - Role: Tech Lead — AI Data Engineer
11
- - Area: DADOS & IA | Tech Dados & IA — Aquisição e Transformação de Dados & IA
12
- - Project: configurable per document (e.g. "Projeto STT — Claro")
9
+ Measured from the reference release document (stt-whisperx — Release 0.4.2) and encoded
10
+ as the defaults of `scripts/theme-claro.js`; categories A and B use it unchanged.
13
11
 
14
- ```js
15
- T.authorBlock({
16
- name: "André Negrelli",
17
- role: "Tech Lead — AI Data Engineer",
18
- area: "DADOS & IA | Tech Dados & IA — Aquisição e Transformação de Dados & IA",
19
- project: "<Project> — <Client> · <DATE>",
20
- });
21
- ```
12
+ | Element | Value |
13
+ |---|---|
14
+ | Page | A4 portrait (11906 x 16838 DXA) |
15
+ | Layout | `compact` (default): left/right 1 cm (567), top 1.35 cm (767), bottom 1.03 cm (586), header 0.31 cm (177), footer 0.29 cm (162) from the edge |
16
+ | Header content | `<app> — <document type>` (`T.headerLine(app, type)`), e.g. `stt-whisperx — Release` |
17
+ | Header format | Arial italic 9 pt grey `666666`; Claro logo 24 px on a right tab at the text width; bottom rule 3/8 pt `E5D3D3`; 5 pt after |
18
+ | Footer content | left `<author> — <role> \| <area>` (`T.footerAuthorLine()`); right `Confidencial – Uso Interno \| Página X de Y` (fields) |
19
+ | Footer format | Arial 9 pt grey `666666`; top rule 3/8 pt `E5D3D3`; 5 pt before; right tab at the text width |
20
+ | Closing block | `ELABORAÇÃO` (default heading of `T.authorBlock`) with name, role, project |
22
21
 
23
- Footer:
22
+ House author (`T.HOUSE`): André Negrelli, Tech Lead AI Data Engineer, DADOS & IA. Override
23
+ only when the user names another author/project; pass the fields, not a new format.
24
24
 
25
25
  ```js
26
- T.buildDocument({
27
- children,
28
- headerText: "<Title> | <Project>",
29
- footerAuthor: "André Negrelli — DADOS & IA",
30
- });
26
+ T.configure({ layout: "compact" }); // first line of the build (already the default)
27
+ T.authorBlock({ project: "Speech-to-text Claro / stt-whisperx" });
28
+ T.buildDocument({ children, headerText: T.headerLine("stt-whisperx", "Release") }); // footer = T.footerAuthorLine()
31
29
  ```
32
30
 
33
- ## Release documents
34
-
35
- Shorter identity, measured from the reference release document
36
- (stt-whisperx — Release 0.4.2): header `"<app> — Release"`, footer author
37
- `"André Negrelli — Tech Lead AI Data Engineer | DADOS & IA"`, authorship heading
38
- `"ELABORAÇÃO"` with name, role and `"<Project> — Claro / <app>"`, `T.configure({ layout: "compact" })`.
39
-
40
- ```js
41
- T.authorBlock({ heading: "ELABORAÇÃO", name: "André Negrelli", role: "Tech Lead AI Data Engineer", project: "Speech-to-text Claro / stt-whisperx" });
42
- T.configure({ layout: "compact" }); // first line of the build
43
- T.buildDocument({ children, headerText: "stt-whisperx — Release", footerAuthor: "André Negrelli — Tech Lead AI Data Engineer | DADOS & IA" });
44
- ```
31
+ Document types in the header: `Release`, `Arquitetura de Software`, `Relatório Técnico`,
32
+ `Relatório Executivo`, `Revisão Arquitetural`. `layout: "report"` (2 cm all round) is an
33
+ opt-out used only when the user asks for it.
45
34
 
46
35
  ## Document identity (Claro theme)
47
36
 
48
- - A4 page, Arial for prose + Consolas for code (no third font); geometry per layout
49
- (`report`: 2 cm all round; `compact` for release documents: 1 cm left/right, tight
50
- top/bottom, header/footer close to the edge). Blocks span the text width, centered.
37
+ - A4 page, Arial for prose + Consolas for code (no third font); `compact` geometry for
38
+ every document (table above). Blocks span the text width, centered.
51
39
  - Body text justified, 1.15 line spacing; headings keep with the next paragraph;
52
40
  tables, code blocks and callouts are never cut by a page break.
53
41
  - Claro institutional palette (red anchors, gray hierarchy): red headings, red
@@ -57,9 +45,9 @@ T.buildDocument({ children, headerText: "stt-whisperx — Release", footerAuthor
57
45
  coverBlock) and small in the top-right of every page header (via buildDocument).
58
46
  Both degrade cleanly if the asset is missing. Use `T.logoRun({height})` to place
59
47
  it elsewhere.
60
- - Italic grey header with a bottom rule + logo; footer with author on the left and
61
- "Confidencial – Uso Interno | Página X de Y" on the right.
62
- - Centered "ELABORAÇÃO E ANÁLISE" authorship block at the end.
48
+ - Header and footer exactly as the table above.
49
+ - Centered "ELABORAÇÃO" authorship block at the end.
50
+ - Deliverable is the .docx (print master); no PDF is generated.
63
51
  - Output language: PT-BR by default (client-facing); change per request.
64
52
 
65
53
  The logo asset is shared by both formats at scripts/assets/claro-logo.png (pptx uses
@@ -21,7 +21,7 @@ const model = {
21
21
  tagline: (m) => "System Design",
22
22
  org: (m) => m.org || m.project,
23
23
  title: (m) => m.title,
24
- headerText: (m) => `Documento de Arquitetura de Software — ${m.app} | ${m.project || ""}`.replace(/\s+\|\s+$/, ""),
24
+ headerText: (m) => `${m.app} — Arquitetura de Software`, // house header "<app> — <document type>"
25
25
  requiredMeta: ["app", "title", "version", "date", "status"],
26
26
  meta: [
27
27
  ["app", "Aplicação"], ["doc_type", "Tipo de documento"], ["version", "Versão do documento"],
@@ -210,15 +210,12 @@ while (i < md.length) {
210
210
  }
211
211
 
212
212
  // ---- assemble -------------------------------------------------------------
213
- const front = T.coverBlock({ title: opt.title || path.basename(inputPath, ".md"), subtitle: opt.subtitle || undefined });
213
+ const title = opt.title || path.basename(inputPath, ".md");
214
+ const front = T.coverBlock({ title, subtitle: opt.subtitle || undefined, rule: false }); // compact front page, as the release
214
215
  if (opt.meta) {
215
216
  const rows = opt.meta.split(";").map((pair) => pair.split("=")).filter((r) => r.length === 2).map(([k, v]) => [k.trim(), v.trim()]);
216
217
  if (rows.length) front.push(T.metaTable(rows), T.spacer(120));
217
218
  }
218
- const tail = opt.author ? T.authorBlock({ name: opt.author, role: opt.role, project: opt.project }) : [];
219
- const doc = T.buildDocument({ children: [...front, T.spacer(160), ...children, ...tail], headerText: opt.header, footerAuthor: opt.author ? `${opt.author} — ${opt.role}` : "" });
220
- Packer.toBuffer(doc).then((buf) => {
221
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
222
- fs.writeFileSync(outputPath, buf);
223
- console.log("Gerado:", outputPath, `(${buf.length} bytes)`, `diagramas: ${diagramIndex}`);
224
- });
219
+ const tail = T.authorBlock({ name: opt.author || undefined, role: opt.role || undefined, area: T.HOUSE.area, project: opt.project || undefined });
220
+ const doc = T.buildDocument({ children: [...front, T.spacer(160), ...children, ...tail], headerText: opt.header || title, footerAuthor: T.footerAuthorLine({ author: opt.author, role: opt.role }) });
221
+ T.saveDocx(doc, outputPath).then(() => console.log(`diagramas: ${diagramIndex}`));
@@ -49,10 +49,6 @@ function hasChrome() {
49
49
  if (hasChrome()) ok("Chrome/Chromium para renderizar Mermaid");
50
50
  else info("Chrome ausente (só p/ diagramas) → npx puppeteer browsers install chrome-headless-shell");
51
51
 
52
- // validação opcional (soffice)
53
- try { execSync("soffice --version", { stdio: "pipe" }); ok("LibreOffice (soffice) — validação visual opcional"); }
54
- catch { info("LibreOffice ausente (opcional, só p/ gerar PDF de conferência)"); }
55
-
56
52
  console.log("");
57
53
  if (fail) { console.log(`\x1b[31m${fail} pré-requisito(s) obrigatório(s) faltando.\x1b[0m\n`); process.exit(1); }
58
54
  console.log("\x1b[32mPronto para gerar relatórios.\x1b[0m\n");
@@ -259,18 +259,10 @@ async function build(model, inputPath, outputPath) {
259
259
  children.push(...renderBlocks(body, ctx));
260
260
  }
261
261
 
262
- children.push(...T.authorBlock({ heading: model.authorHeading || "ELABORAÇÃO", name: meta.author || "André Negrelli", role: meta.role || "Tech Lead AI Data Engineer", area: meta.area, project: meta.project || meta.app }));
263
- const doc = T.buildDocument({ children, headerText: model.headerText(meta), footerAuthor: `${meta.author || "André Negrelli"} — ${meta.role || "Tech Lead AI Data Engineer"} | ${meta.area || "DADOS & IA"}`, updateFields: false });
262
+ children.push(...T.authorBlock({ heading: model.authorHeading || "ELABORAÇÃO", name: meta.author || T.HOUSE.author, role: meta.role || T.HOUSE.role, area: meta.area, project: meta.project || meta.app }));
263
+ const doc = T.buildDocument({ children, headerText: model.headerText(meta), footerAuthor: T.footerAuthorLine({ author: meta.author, role: meta.role, area: meta.area }), updateFields: false });
264
264
 
265
- // never overwrite a document Word holds open (lock "~$<name>"): Word reloads images lazily and would save broken links
266
- const outAbs = path.resolve(outputPath);
267
- const lock = path.join(path.dirname(outAbs), "~$" + path.basename(outAbs).slice(2));
268
- const target = fs.existsSync(lock) ? outAbs.replace(/\.docx$/i, "-new.docx") : outAbs;
269
- if (target !== outAbs) console.warn(`${model.name} model: ${path.basename(outAbs)} is open in Word; writing ${path.basename(target)} instead — close Word and rename.`);
270
- fs.mkdirSync(path.dirname(target), { recursive: true });
271
- const buf = await Packer.toBuffer(doc);
272
- fs.writeFileSync(target, buf);
273
- console.log("Generated:", target, `(${buf.length} bytes)`);
265
+ await T.saveDocx(doc, outputPath); // Word lock guard lives in the theme (one implementation)
274
266
  }
275
267
 
276
268
  /** CLI wrapper: `node build-<model>.js <content.md> <out.docx>`. */
@@ -20,6 +20,8 @@ const path = require("path");
20
20
  const { Packer, AlignmentType } = require("docx");
21
21
  const T = require("./theme-claro");
22
22
 
23
+ T.configure({ layout: "compact" }); // house page (release geometry); set before any block
24
+
23
25
  const children = [];
24
26
 
25
27
  // ---- CAPA ----
@@ -98,22 +100,18 @@ children.push(T.codeBlock([
98
100
 
99
101
  // ---- AUTORIA ----
100
102
  children.push(...T.authorBlock({
101
- name: "André Negrelli",
102
- role: "Tech Lead — AI Data Engineer",
103
- area: "DADOS & IA | Tech Dados & IA — Aquisição e Transformação de Dados & IA",
103
+ heading: "ELABORAÇÃO",
104
+ name: T.HOUSE.author,
105
+ role: T.HOUSE.role,
104
106
  project: "Projeto STT — Claro · 19/06/2026",
105
107
  }));
106
108
 
107
109
  // ---- MONTAGEM ----
108
110
  const doc = T.buildDocument({
109
111
  children,
110
- headerText: "Título do Relatório | STT Claro",
111
- footerAuthor: "André Negrelli — DADOS & IA",
112
+ headerText: T.headerLine("<app>", "Relatório Técnico"), // house header "<app> — <document type>"
113
+ footerAuthor: T.footerAuthorLine(), // "André Negrelli — Tech Lead AI Data Engineer | DADOS & IA"
112
114
  });
113
115
 
114
116
  const outPath = process.argv[2] || path.join(process.cwd(), "out", "Exemplo_Tema_Claro.docx");
115
- fs.mkdirSync(path.dirname(outPath), { recursive: true });
116
- Packer.toBuffer(doc).then((buf) => {
117
- fs.writeFileSync(outPath, buf);
118
- console.log("Gerado:", outPath, `(${buf.length} bytes)`);
119
- });
117
+ T.saveDocx(doc, outPath); // writes <name>-new.docx when Word holds the file open
@@ -24,6 +24,8 @@ const path = require("path");
24
24
  const { Packer, AlignmentType } = require("docx");
25
25
  const T = require("./theme-claro");
26
26
 
27
+ T.configure({ layout: "compact" }); // house page (release geometry); set before any block
28
+
27
29
  const children = [];
28
30
 
29
31
  /** Severity scale shared by the findings table and the summary cards. */
@@ -175,21 +177,17 @@ children.push(T.callout([
175
177
 
176
178
  // ---- AUTHORSHIP ----
177
179
  children.push(...T.authorBlock({
178
- name: "André Negrelli",
179
- role: "Tech Lead — AI Data Engineer",
180
- area: "DADOS & IA | Tech Dados & IA — Aquisição e Transformação de Dados & IA",
180
+ heading: "ELABORAÇÃO",
181
+ name: T.HOUSE.author,
182
+ role: T.HOUSE.role,
181
183
  project: "<Projeto> — Claro · DD/MM/AAAA",
182
184
  }));
183
185
 
184
186
  const doc = T.buildDocument({
185
187
  children,
186
- headerText: "Revisão Arquitetural | <Projeto> Claro",
187
- footerAuthor: "André Negrelli — DADOS & IA",
188
+ headerText: T.headerLine("<app>", "Revisão Arquitetural"), // house header "<app> — <document type>"
189
+ footerAuthor: T.footerAuthorLine(), // "André Negrelli — Tech Lead AI Data Engineer | DADOS & IA"
188
190
  });
189
191
 
190
192
  const outPath = process.argv[2] || path.join(process.cwd(), "out", "Revisao_Arquitetural.docx");
191
- fs.mkdirSync(path.dirname(outPath), { recursive: true });
192
- Packer.toBuffer(doc).then((buf) => {
193
- fs.writeFileSync(outPath, buf);
194
- console.log("Gerado:", outPath, `(${buf.length} bytes)`);
195
- });
193
+ T.saveDocx(doc, outPath); // writes <name>-new.docx when Word holds the file open
@@ -19,6 +19,8 @@ const path = require("path");
19
19
  const { Packer, AlignmentType } = require("docx");
20
20
  const T = require("./theme-claro");
21
21
 
22
+ T.configure({ layout: "compact" }); // house page (release geometry); set before any block
23
+
22
24
  const children = [];
23
25
 
24
26
  // ---- COVER (rich) ----
@@ -94,21 +96,17 @@ children.push(T.callout([
94
96
 
95
97
  // ---- AUTHORSHIP ----
96
98
  children.push(...T.authorBlock({
97
- name: "André Negrelli",
98
- role: "Tech Lead — AI Data Engineer",
99
- area: "DADOS & IA | Tech Dados & IA — Aquisição e Transformação de Dados & IA",
99
+ heading: "ELABORAÇÃO",
100
+ name: T.HOUSE.author,
101
+ role: T.HOUSE.role,
100
102
  project: "Projeto STT — Claro · 22/06/2026",
101
103
  }));
102
104
 
103
105
  const doc = T.buildDocument({
104
106
  children,
105
- headerText: "Relatório Executivo | STT Claro",
106
- footerAuthor: "André Negrelli — DADOS & IA",
107
+ headerText: T.headerLine("<app>", "Relatório Executivo"), // house header "<app> — <document type>"
108
+ footerAuthor: T.footerAuthorLine(), // "André Negrelli — Tech Lead AI Data Engineer | DADOS & IA"
107
109
  });
108
110
 
109
111
  const outPath = process.argv[2] || path.join(process.cwd(), "out", "Relatorio_Executivo.docx");
110
- fs.mkdirSync(path.dirname(outPath), { recursive: true });
111
- Packer.toBuffer(doc).then((buf) => {
112
- fs.writeFileSync(outPath, buf);
113
- console.log("Gerado:", outPath, `(${buf.length} bytes)`);
114
- });
112
+ T.saveDocx(doc, outPath); // writes <name>-new.docx when Word holds the file open
@@ -12,7 +12,7 @@
12
12
  const fs = require("fs");
13
13
  const path = require("path");
14
14
  const {
15
- Document, Paragraph, TextRun, Table, TableRow, TableCell,
15
+ Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
16
16
  Header, Footer, AlignmentType, BorderStyle, WidthType, ShadingType,
17
17
  VerticalAlign, PageNumber, ImageRun, PageBreak,
18
18
  } = require("docx");
@@ -31,9 +31,10 @@ const C = {
31
31
  noteFill: "FFF2CC", dangerFill: "FFE0E0", successFill: "E2EFDA", infoFill: "FBEDED",
32
32
  rule: "E2000F", codeFill: "F5F5F5", codeText: "1F1F1F", codeBar: "555555",
33
33
  };
34
- // A4 portrait. Geometry per layout (DXA): "report" is the classic page (2 cm all
35
- // round); "compact" is the release page — 1 cm lateral margins, tight top/bottom,
36
- // header and footer close to the page edge. Blocks (tables, code, callouts,
34
+ // A4 portrait. Geometry per layout (DXA): "compact" is the house page, measured from
35
+ // the reference release document (stt-whisperx — Release 0.4.2) and the default for
36
+ // every .docx — 1 cm lateral margins, top 1.35 cm, bottom 1.03 cm, header 0.31 cm and
37
+ // footer 0.29 cm from the page edge; "report" (2 cm all round) is an explicit opt-out. Blocks (tables, code, callouts,
37
38
  // images) span exactly the text width and are centered, so their edges meet the
38
39
  // justified text. Select the layout with T.configure({ layout }) BEFORE building
39
40
  // blocks: the usable width depends on it.
@@ -42,6 +43,15 @@ const LAYOUTS = {
42
43
  report: { top: 1134, bottom: 1134, left: 1134, right: 1134, header: 708, footer: 708 },
43
44
  compact: { top: 767, bottom: 586, left: 567, right: 567, header: 177, footer: 162 },
44
45
  };
46
+ // House identity (same reference document): header "<app> — <document type>" in italic
47
+ // grey 9 pt with the Claro logo (24 px) right-aligned over a bottom rule; footer over a top
48
+ // rule with "<author> — <role> | <area>" on the left and "<note> | Página X de Y" on the
49
+ // right (9 pt grey). The .docx is the print master: no PDF is produced.
50
+ const HOUSE = { author: "André Negrelli", role: "Tech Lead AI Data Engineer", area: "DADOS & IA", footerNote: "Confidencial – Uso Interno" };
51
+ /** Footer author line "<author> — <role> | <area>"; o overrides any field (falsy keeps the house value). */
52
+ const footerAuthorLine = (o = {}) => `${o.author || HOUSE.author} — ${o.role || HOUSE.role} | ${o.area || HOUSE.area}`;
53
+ /** Header line "<app> — <document type>" (e.g. "stt-whisperx — Release"). */
54
+ const headerLine = (app, docType) => [app, docType].filter(Boolean).join(" — ");
45
55
  const CONTENT_W = 9640; // legacy constant (report layout); prefer contentWidth()
46
56
  const EMU_PER_PX = 9525;
47
57
  // Font sizes in half-points: body 11 pt, code 9 pt, table cell 10 pt, cell code 8.5 pt.
@@ -51,7 +61,7 @@ const LOGO_PATH = path.join(__dirname, "assets", "claro-logo.png");
51
61
 
52
62
  // Mutable house defaults; T.configure({ justify: false }) opts out for a build.
53
63
  // Justified text needs automatic hyphenation to avoid rivers of white space.
54
- const DEFAULTS = { justify: true, hyphenate: true, line: 276, lang: "pt-BR", layout: "report", page: {} };
64
+ const DEFAULTS = { justify: true, hyphenate: true, line: 276, lang: "pt-BR", layout: "compact", page: {} };
55
65
  /** Override the house defaults (layout, page overrides, justify, hyphenate, line, lang) for the current build. */
56
66
  function configure(o = {}) { Object.assign(DEFAULTS, o); }
57
67
  /** Page geometry of the configured layout, with DEFAULTS.page overrides applied. */
@@ -324,8 +334,8 @@ function coverBlock({ title, subtitle, tagline, org, metaLines = [], rule = true
324
334
  }
325
335
 
326
336
  // ---- AUTHORSHIP BLOCK ----
327
- /** Authorship block closing the document; heading defaults to "ELABORAÇÃO E ANÁLISE". */
328
- function authorBlock({ heading: hd = "ELABORAÇÃO E ANÁLISE", name, role, area, project } = {}) {
337
+ /** Authorship block closing the document; heading "ELABORAÇÃO", name/role default to the house author. */
338
+ function authorBlock({ heading: hd = "ELABORAÇÃO", name = HOUSE.author, role = HOUSE.role, area, project } = {}) {
329
339
  const out = [];
330
340
  const line = (text, o) => new Paragraph({ alignment: AlignmentType.CENTER, keepNext: true, keepLines: true, spacing: o.spacing, children: [t(text, o.run)] });
331
341
  out.push(spacer(240));
@@ -339,8 +349,8 @@ function authorBlock({ heading: hd = "ELABORAÇÃO E ANÁLISE", name, role, area
339
349
  }
340
350
 
341
351
  // ---- DOCUMENT ----
342
- /** Final Document: A4, header/footer, lists, house defaults; layout "report" | "compact", page = DXA overrides. */
343
- function buildDocument({ children, headerText = "", footerAuthor = "", footerNote = "Confidencial – Uso Interno", layout, page, updateFields = false }) {
352
+ /** Final Document: A4, house header/footer, lists, house defaults; layout "compact" (default) | "report", page = DXA overrides. */
353
+ function buildDocument({ children, headerText = "", footerAuthor = footerAuthorLine(), footerNote = HOUSE.footerNote, layout, page, updateFields = false }) {
344
354
  if ((layout && layout !== DEFAULTS.layout) || page) {
345
355
  console.warn("theme-claro: pass layout/page to T.configure() before building blocks; buildDocument() applies them to the page only");
346
356
  configure({ layout: layout || DEFAULTS.layout, page: page || DEFAULTS.page });
@@ -387,9 +397,28 @@ function buildDocument({ children, headerText = "", footerAuthor = "", footerNot
387
397
  });
388
398
  }
389
399
 
400
+ /**
401
+ * Write the Document to outPath, never over a file Word holds open: with a lock
402
+ * "~$<name>" beside it, writes "<name>-new.docx" and warns (Word reloads images lazily
403
+ * and would save broken links). Resolves to the path actually written.
404
+ */
405
+ async function saveDocx(doc, outPath) {
406
+ const outAbs = path.resolve(outPath);
407
+ const lock = path.join(path.dirname(outAbs), "~$" + path.basename(outAbs).slice(2));
408
+ const target = fs.existsSync(lock) ? outAbs.replace(/\.docx$/i, "-new.docx") : outAbs;
409
+ if (target !== outAbs) console.warn(`${path.basename(outAbs)} is open in Word; writing ${path.basename(target)} instead — close Word and rename.`);
410
+ fs.mkdirSync(path.dirname(target), { recursive: true });
411
+ const buf = await Packer.toBuffer(doc);
412
+ fs.writeFileSync(target, buf);
413
+ console.log("Generated:", target, `(${buf.length} bytes)`);
414
+ return target;
415
+ }
416
+
390
417
  module.exports = {
391
418
  // tokens
392
419
  C, FONT, MONO, SZ, CONTENT_W, PAGE, LAYOUTS, DEFAULTS, configure, contentWidth, geometry, fitCols,
420
+ // house identity
421
+ HOUSE, footerAuthorLine, headerLine,
393
422
  // runs
394
423
  t, mono, cap, rich, rc,
395
424
  // blocks
@@ -399,5 +428,5 @@ module.exports = {
399
428
  // highlights
400
429
  callout, calloutLine, codeBlock, statCards,
401
430
  // media / structure
402
- image, logoRun, coverBlock, authorBlock, buildDocument, LOGO_PATH,
431
+ image, logoRun, coverBlock, authorBlock, buildDocument, saveDocx, LOGO_PATH,
403
432
  };