@deftai/directive-content 0.55.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.
Files changed (250) hide show
  1. package/.agents/skills/deft/SKILL.md +6 -0
  2. package/.agents/skills/deft-directive-article-review/SKILL.md +11 -0
  3. package/.agents/skills/deft-directive-build/SKILL.md +10 -0
  4. package/.agents/skills/deft-directive-cost/SKILL.md +12 -0
  5. package/.agents/skills/deft-directive-debug/SKILL.md +13 -0
  6. package/.agents/skills/deft-directive-gh-arch/SKILL.md +11 -0
  7. package/.agents/skills/deft-directive-gh-slice/SKILL.md +10 -0
  8. package/.agents/skills/deft-directive-glossary/SKILL.md +10 -0
  9. package/.agents/skills/deft-directive-interview/SKILL.md +9 -0
  10. package/.agents/skills/deft-directive-pre-pr/SKILL.md +9 -0
  11. package/.agents/skills/deft-directive-refinement/SKILL.md +10 -0
  12. package/.agents/skills/deft-directive-release/SKILL.md +12 -0
  13. package/.agents/skills/deft-directive-review-cycle/SKILL.md +10 -0
  14. package/.agents/skills/deft-directive-setup/SKILL.md +10 -0
  15. package/.agents/skills/deft-directive-swarm/SKILL.md +10 -0
  16. package/.agents/skills/deft-directive-sync/SKILL.md +9 -0
  17. package/.agents/skills/deft-directive-triage/SKILL.md +9 -0
  18. package/.agents/skills/deft-directive-write-skill/SKILL.md +9 -0
  19. package/LICENSE.md +22 -0
  20. package/QUICK-START.md +167 -0
  21. package/UPGRADING.md +517 -0
  22. package/coding/build-output.md +28 -0
  23. package/coding/coding.md +235 -0
  24. package/coding/debugging.md +110 -0
  25. package/coding/holzmann.md +96 -0
  26. package/coding/hygiene.md +127 -0
  27. package/coding/security.md +158 -0
  28. package/coding/testing.md +162 -0
  29. package/coding/toolchain.md +44 -0
  30. package/commands.md +300 -0
  31. package/context/context.md +57 -0
  32. package/context/deterministic-split.md +67 -0
  33. package/context/examples.md +26 -0
  34. package/context/fractal-summaries.md +69 -0
  35. package/context/long-horizon.md +46 -0
  36. package/context/spec-deltas.md +177 -0
  37. package/context/tool-design.md +34 -0
  38. package/context/working-memory.md +62 -0
  39. package/contracts/boundary-maps.md +65 -0
  40. package/contracts/deterministic-questions.md +59 -0
  41. package/contracts/hierarchy.md +77 -0
  42. package/conventions/content-manifest.json +399 -0
  43. package/conventions/machine-generated-banner.md +130 -0
  44. package/conventions/references.md +120 -0
  45. package/conventions/rule-ownership.json +382 -0
  46. package/conventions/task-caching.md +43 -0
  47. package/conventions/vbrief-filenames.md +70 -0
  48. package/deployments/README.md +37 -0
  49. package/deployments/agentuity/README.md +138 -0
  50. package/deployments/agentuity/via-cli.md +380 -0
  51. package/deployments/agentuity/via-cloud.md +425 -0
  52. package/deployments/agentuity/via-github-actions.md +664 -0
  53. package/deployments/agentuity/via-gravity-network.md +606 -0
  54. package/deployments/agentuity/via-vpc.md +607 -0
  55. package/deployments/aws/README.md +38 -0
  56. package/deployments/aws/via-app-runner.md +612 -0
  57. package/deployments/aws/via-ecs-fargate.md +561 -0
  58. package/deployments/aws/via-elastic-beanstalk.md +628 -0
  59. package/deployments/aws/via-lambda.md +649 -0
  60. package/deployments/azure/README.md +37 -0
  61. package/deployments/azure/via-aks.md +390 -0
  62. package/deployments/azure/via-app-service.md +564 -0
  63. package/deployments/azure/via-container-apps.md +599 -0
  64. package/deployments/azure/via-functions.md +552 -0
  65. package/deployments/cloud-gov/README.md +63 -0
  66. package/deployments/cloud-gov/agents/compliance-docs.md +154 -0
  67. package/deployments/cloud-gov/agents.md +39 -0
  68. package/deployments/cloud-gov/cicd.md +64 -0
  69. package/deployments/cloud-gov/deployment.md +150 -0
  70. package/deployments/cloud-gov/logging.md +43 -0
  71. package/deployments/cloud-gov/manifest.md +121 -0
  72. package/deployments/cloud-gov/overview.md +58 -0
  73. package/deployments/cloud-gov/security.md +46 -0
  74. package/deployments/cloud-gov/services.md +72 -0
  75. package/deployments/cloud-gov/upstream/README.md +18 -0
  76. package/deployments/cloudflare/README.md +33 -0
  77. package/deployments/cloudflare/via-dashboard.md +83 -0
  78. package/deployments/cloudflare/via-git.md +90 -0
  79. package/deployments/cloudflare/via-github-actions.md +185 -0
  80. package/deployments/cloudflare/via-terraform.md +157 -0
  81. package/deployments/cloudflare/via-wrangler.md +165 -0
  82. package/deployments/fly-io/README.md +37 -0
  83. package/deployments/fly-io/via-dockerfile.md +648 -0
  84. package/deployments/fly-io/via-flyctl.md +653 -0
  85. package/deployments/fly-io/via-github-actions.md +695 -0
  86. package/deployments/fly-io/via-multi-region.md +598 -0
  87. package/deployments/google/README.md +34 -0
  88. package/deployments/google/via-app-engine.md +42 -0
  89. package/deployments/google/via-cloud-functions.md +23 -0
  90. package/deployments/google/via-cloud-run.md +330 -0
  91. package/deployments/google/via-gke.md +23 -0
  92. package/deployments/netlify/README.md +99 -0
  93. package/deployments/netlify/via-cli.md +17 -0
  94. package/deployments/netlify/via-functions.md +19 -0
  95. package/deployments/netlify/via-git.md +25 -0
  96. package/deployments/vercel/README.md +90 -0
  97. package/deployments/vercel/via-api.md +16 -0
  98. package/deployments/vercel/via-cli.md +17 -0
  99. package/deployments/vercel/via-git.md +24 -0
  100. package/docs/BROWNFIELD.md +179 -0
  101. package/docs/getting-started.md +137 -0
  102. package/docs/good-agents-md.md +137 -0
  103. package/events/README.md +89 -0
  104. package/events/event-record.schema.json +26 -0
  105. package/events/registry.json +166 -0
  106. package/events/registry.schema.json +71 -0
  107. package/glossary.md +145 -0
  108. package/incidents/README.md +81 -0
  109. package/incidents/_template.md +38 -0
  110. package/interfaces/cli.md +104 -0
  111. package/interfaces/rest.md +212 -0
  112. package/interfaces/tui.md +242 -0
  113. package/interfaces/web.md +123 -0
  114. package/languages/6502-DASM.md +132 -0
  115. package/languages/c.md +235 -0
  116. package/languages/commands.md +23 -0
  117. package/languages/cpp.md +132 -0
  118. package/languages/csharp.md +259 -0
  119. package/languages/dart.md +183 -0
  120. package/languages/delphi.md +218 -0
  121. package/languages/elixir.md +208 -0
  122. package/languages/go.md +78 -0
  123. package/languages/java.md +278 -0
  124. package/languages/javascript.md +163 -0
  125. package/languages/julia.md +175 -0
  126. package/languages/kotlin.md +193 -0
  127. package/languages/markdown.md +168 -0
  128. package/languages/mermaid.md +146 -0
  129. package/languages/officejs.md +392 -0
  130. package/languages/python.md +209 -0
  131. package/languages/r.md +163 -0
  132. package/languages/rust.md +216 -0
  133. package/languages/sql.md +216 -0
  134. package/languages/swift.md +153 -0
  135. package/languages/typescript.md +132 -0
  136. package/languages/vba.md +279 -0
  137. package/languages/vhdl.md +180 -0
  138. package/languages/visual-basic.md +151 -0
  139. package/languages/zig.md +196 -0
  140. package/meta/SOUL.md +27 -0
  141. package/meta/code-field.md +44 -0
  142. package/meta/morals.md +40 -0
  143. package/meta/philosophy.md +39 -0
  144. package/meta/project.md +49 -0
  145. package/meta/ralph.md +223 -0
  146. package/meta/security.md +80 -0
  147. package/meta/versioning.md +326 -0
  148. package/package.json +22 -0
  149. package/packs/lessons/lessons-pack-0.1.json +553 -0
  150. package/packs/patterns/patterns-pack-0.1.json +57 -0
  151. package/packs/rules/rules-pack-0.1.json +4767 -0
  152. package/packs/skills/skills-pack-0.1.json +262 -0
  153. package/packs/strategies/strategies-pack-0.1.json +167 -0
  154. package/packs/swarm-spec/swarm-spec-pack-0.1.json +17 -0
  155. package/patterns/executor-layer-credentials.md +227 -0
  156. package/patterns/llm-app.md +156 -0
  157. package/patterns/multi-agent.md +278 -0
  158. package/patterns/prompt-assembly-layer-ordering.md +154 -0
  159. package/patterns/role-as-overlay.md +179 -0
  160. package/platforms/2600.md +137 -0
  161. package/platforms/unity.md +329 -0
  162. package/references/composer-skill-porting.md +152 -0
  163. package/references/cost-models.md +163 -0
  164. package/references/ip-risk.md +246 -0
  165. package/references/plain-english-ux.md +275 -0
  166. package/resilience/context-pruning.md +67 -0
  167. package/resilience/continue-here.md +62 -0
  168. package/scm/changelog.md +276 -0
  169. package/scm/git.md +139 -0
  170. package/scm/github.md +265 -0
  171. package/secrets/.gitkeep +0 -0
  172. package/skills/deft-build/SKILL.md +20 -0
  173. package/skills/deft-directive-article-review/SKILL.md +156 -0
  174. package/skills/deft-directive-build/SKILL.md +302 -0
  175. package/skills/deft-directive-cost/SKILL.md +201 -0
  176. package/skills/deft-directive-debug/SKILL.md +140 -0
  177. package/skills/deft-directive-decompose/SKILL.md +96 -0
  178. package/skills/deft-directive-gh-arch/SKILL.md +160 -0
  179. package/skills/deft-directive-gh-slice/SKILL.md +199 -0
  180. package/skills/deft-directive-glossary/SKILL.md +118 -0
  181. package/skills/deft-directive-interview/SKILL.md +528 -0
  182. package/skills/deft-directive-pre-pr/SKILL.md +131 -0
  183. package/skills/deft-directive-probe/SKILL.md +127 -0
  184. package/skills/deft-directive-refinement/SKILL.md +403 -0
  185. package/skills/deft-directive-release/SKILL.md +266 -0
  186. package/skills/deft-directive-review-cycle/SKILL.md +401 -0
  187. package/skills/deft-directive-setup/SKILL.md +717 -0
  188. package/skills/deft-directive-swarm/SKILL.md +989 -0
  189. package/skills/deft-directive-sync/SKILL.md +288 -0
  190. package/skills/deft-directive-triage/SKILL.md +137 -0
  191. package/skills/deft-directive-write-skill/SKILL.md +169 -0
  192. package/skills/deft-interview/SKILL.md +16 -0
  193. package/skills/deft-pre-pr/SKILL.md +16 -0
  194. package/skills/deft-review-cycle/SKILL.md +16 -0
  195. package/skills/deft-roadmap-refresh/SKILL.md +16 -0
  196. package/skills/deft-setup/SKILL.md +20 -0
  197. package/skills/deft-swarm/SKILL.md +16 -0
  198. package/skills/deft-sync/SKILL.md +16 -0
  199. package/strategies/README.md +83 -0
  200. package/strategies/artifact-guards.md +85 -0
  201. package/strategies/bdd.md +115 -0
  202. package/strategies/brownfield.md +7 -0
  203. package/strategies/discuss.md +129 -0
  204. package/strategies/emit-hints.md +69 -0
  205. package/strategies/enterprise.md +193 -0
  206. package/strategies/interview.md +551 -0
  207. package/strategies/map.md +179 -0
  208. package/strategies/probe.md +151 -0
  209. package/strategies/rapid.md +155 -0
  210. package/strategies/research.md +155 -0
  211. package/strategies/roadmap.md +9 -0
  212. package/strategies/speckit.md +437 -0
  213. package/strategies/v0-20-contract.md +134 -0
  214. package/strategies/yolo.md +169 -0
  215. package/swarm/swarm.md +300 -0
  216. package/templates/COST-ESTIMATE.md +114 -0
  217. package/templates/PULL_REQUEST_TEMPLATE.md +35 -0
  218. package/templates/agent-prompt-preamble.md +409 -0
  219. package/templates/agents-entry.md +211 -0
  220. package/templates/agents-entry.placeholders.md +75 -0
  221. package/templates/embed.go +20 -0
  222. package/templates/embed_test.go +36 -0
  223. package/templates/make-spec-example.md +9 -0
  224. package/templates/make-spec.md +246 -0
  225. package/templates/project.md.template +52 -0
  226. package/templates/specification.md +1 -0
  227. package/templates/swarm-greptile-poller-prompt.md +556 -0
  228. package/templates/user.md.template +31 -0
  229. package/tools/RWLDL.md +80 -0
  230. package/tools/greptile.md +141 -0
  231. package/tools/installer.md +23 -0
  232. package/tools/taskfile-migration.md +32 -0
  233. package/tools/taskfile.md +185 -0
  234. package/tools/telemetry.md +285 -0
  235. package/vbrief/schemas/cache-meta.schema.json +137 -0
  236. package/vbrief/schemas/candidates.schema.json +130 -0
  237. package/vbrief/schemas/codebase-map.schema.json +213 -0
  238. package/vbrief/schemas/lessons-pack.schema.json +134 -0
  239. package/vbrief/schemas/patterns-pack.schema.json +84 -0
  240. package/vbrief/schemas/rules-pack.schema.json +105 -0
  241. package/vbrief/schemas/skills-pack.schema.json +94 -0
  242. package/vbrief/schemas/slices.schema.json +87 -0
  243. package/vbrief/schemas/strategies-pack.schema.json +89 -0
  244. package/vbrief/schemas/swarm-spec-pack.schema.json +84 -0
  245. package/vbrief/schemas/vbrief-core.schema.json +1022 -0
  246. package/vbrief/vbrief.md +684 -0
  247. package/verification/integration.md +76 -0
  248. package/verification/plan-checking.md +85 -0
  249. package/verification/uat.md +60 -0
  250. package/verification/verification.md +117 -0
@@ -0,0 +1,392 @@
1
+ # Office.js Standards (Excel JavaScript API)
2
+
3
+ Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
+
5
+ **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md) | [typescript.md](./typescript.md) | [telemetry.md](../tools/telemetry.md)
6
+
7
+ **Stack**: TypeScript 5.0+, Office.js (Excel JavaScript API); Build: webpack/Vite; Testing: Vitest + office-addin-mock; Manifest: unified JSON manifest (preferred) or XML manifest; Scaffolding: Yeoman (`yo office`) or manual
8
+
9
+ **Note**: Office.js builds on TypeScript but has a fundamentally different execution model — proxy objects, `RequestContext`, `context.sync()` batching, and platform-dependent API surfaces. The general [typescript.md](./typescript.md) standards apply to all TypeScript code in the project. This module adds the Office.js-specific patterns that generic TypeScript standards do not cover.
10
+
11
+ ## Standards
12
+
13
+ ### Documentation
14
+ - ! TSDoc comments on all exported functions, classes, and custom function definitions
15
+ - ! Document `@param`, `@returns`, `@throws` for public APIs
16
+ - ! Document platform availability when using APIs not in the base requirement set (e.g., `// Requires: ExcelApi 1.9+`)
17
+ - ~ Document `context.sync()` boundaries in complex functions with inline comments explaining what is being batched
18
+
19
+ ### Execution Model
20
+ - ! All Excel API interactions MUST happen inside `Excel.run(async (context) => { ... })`
21
+ - ! Minimize `context.sync()` calls — batch reads and writes between syncs
22
+ - ! `load()` properties before reading them — proxy objects do not pre-populate
23
+ - ! Specify only the properties you need in `load()`: `range.load("values, address")` not `range.load()`
24
+ - ⊗ Access proxy object properties before calling `context.sync()` after `load()` — values are not populated until sync completes
25
+ - ⊗ Store proxy objects outside their `Excel.run` scope — they become invalid after the run completes
26
+ - ⊗ Nest `Excel.run()` calls — use a single run with multiple syncs when needed
27
+
28
+ ### Sync Batching
29
+ - ! Group related reads together, sync once, then process results
30
+ - ! Group related writes together, sync once at the end
31
+ - ~ The ideal pattern is: load → sync → compute → write → sync (two syncs per operation)
32
+ - ⊗ Sync after every individual read or write — this defeats the batching model and causes severe performance degradation
33
+ - ⊗ `context.sync()` inside a loop — batch the operations and sync once after the loop
34
+
35
+ ```typescript path=null start=null
36
+ // ✓ Correct: batch reads, sync, compute, batch writes, sync
37
+ await Excel.run(async (context) => {
38
+ const revenue = context.workbook.names.getItem("in_base_revenue").getRange();
39
+ const growthRate = context.workbook.names.getItem("as_growth_rate").getRange();
40
+ revenue.load("values");
41
+ growthRate.load("values");
42
+ await context.sync();
43
+
44
+ const projected = computeProjection(revenue.values, growthRate.values[0][0]);
45
+
46
+ const output = context.workbook.names.getItem("calc_projected_revenue").getRange();
47
+ output.values = projected;
48
+ await context.sync();
49
+ });
50
+
51
+ // ⊗ Wrong: sync inside loop
52
+ await Excel.run(async (context) => {
53
+ const table = context.workbook.tables.getItem("RevenueTable");
54
+ const rows = table.rows;
55
+ rows.load("count");
56
+ await context.sync();
57
+ for (let i = 0; i < rows.count; i++) {
58
+ const row = rows.getItemAt(i);
59
+ row.load("values");
60
+ await context.sync(); // ⊗ N round trips instead of 1
61
+ // ...
62
+ }
63
+ });
64
+ ```
65
+
66
+ ### Error Handling
67
+ - ! Wrap `Excel.run()` calls in try/catch at the call site
68
+ - ! Check for `OfficeExtension.Error` and extract `code`, `message`, `debugInfo`
69
+ - ! Handle `InvalidReference` errors specifically — they indicate a range that no longer exists (row/column deleted)
70
+ - ! Handle `ItemNotFound` errors when looking up named ranges, tables, or sheets that may not exist
71
+ - ⊗ Swallow `context.sync()` errors — they indicate failed API operations that leave the workbook in an unknown state
72
+ - ~ Provide user-facing error messages that distinguish between "model error" (the xlconfig is wrong) and "runtime error" (Excel API failure)
73
+
74
+ ```typescript path=null start=null
75
+ try {
76
+ await Excel.run(async (context) => {
77
+ // ... operations ...
78
+ await context.sync();
79
+ });
80
+ } catch (error) {
81
+ if (error instanceof OfficeExtension.Error) {
82
+ switch (error.code) {
83
+ case "ItemNotFound":
84
+ showError(`Named range not found: ${error.message}`);
85
+ break;
86
+ case "InvalidReference":
87
+ showError("A referenced cell no longer exists. Check if rows/columns were deleted.");
88
+ break;
89
+ default:
90
+ showError(`Excel error: ${error.message}`);
91
+ }
92
+ } else {
93
+ throw error;
94
+ }
95
+ }
96
+ ```
97
+
98
+ ### Custom Functions
99
+ - ! Use the `@customfunction` JSDoc tag for registration
100
+ - ! Custom functions run in a separate runtime — they have NO access to the DOM, task pane, or `Office.context`
101
+ - ⊗ Import task pane code into custom function modules — they share no runtime
102
+ - ! Custom functions that call external APIs MUST handle timeouts and return meaningful error values
103
+ - ! Use streaming functions (`@streaming`) for real-time data (prices, rates) — return `invocation.setResult()` on each update
104
+ - ! Cancel streaming gracefully via `invocation.onCanceled`
105
+ - ⊗ Synchronous custom functions that perform I/O — all I/O must be async
106
+
107
+ ```typescript path=null start=null
108
+ /**
109
+ * Calculates projected revenue for a given year.
110
+ * @customfunction PROJECTED_REVENUE
111
+ * @param baseRevenue Base year revenue
112
+ * @param growthRate Annual growth rate (decimal, e.g. 0.05 for 5%)
113
+ * @param years Number of years to project
114
+ * @returns Projected revenue
115
+ */
116
+ export function projectedRevenue(
117
+ baseRevenue: number,
118
+ growthRate: number,
119
+ years: number
120
+ ): number {
121
+ return baseRevenue * Math.pow(1 + growthRate, years);
122
+ }
123
+ ```
124
+
125
+ ### Named Ranges and Tables
126
+ - ! Reference named ranges and Excel Tables (`ListObject`) by name — never by hardcoded cell address
127
+ - ! Use `context.workbook.names.getItemOrNullObject(name)` to safely check for named range existence
128
+ - ! Use `table.columns.getItemOrNullObject(columnName)` for safe column lookup
129
+ - ⊗ Hardcode cell addresses (`"B5"`, `"Sheet1!$C$10"`) in production code
130
+ - ~ Use helper functions that centralize range resolution from config or named ranges
131
+
132
+ ```typescript path=null start=null
133
+ // ✓ Batching-friendly: no internal sync — load all named items and call context.sync() once in the caller
134
+ function loadNamedItem(
135
+ context: Excel.RequestContext,
136
+ name: string
137
+ ): Excel.NamedItem {
138
+ const item = context.workbook.names.getItemOrNullObject(name);
139
+ item.load("isNullObject");
140
+ return item; // caller must await context.sync() before checking isNullObject
141
+ }
142
+ ```
143
+
144
+ ### Platform Awareness
145
+ - ! Check API requirement sets before using platform-specific features
146
+ - ! Document minimum requirement set in manifest and README
147
+ - ~ Test on all target platforms (Windows, Mac, Web) — API availability varies
148
+ - ! Use `Office.context.requirements.isSetSupported("ExcelApi", "1.9")` before calling 1.9+ APIs
149
+ - ⊗ Assume all APIs are available on all platforms — Excel for Web and Mac have smaller API surfaces than Windows
150
+
151
+ ```typescript path=null start=null
152
+ function supportsDataValidation(): boolean {
153
+ return Office.context.requirements.isSetSupported("ExcelApi", "1.8");
154
+ }
155
+
156
+ async function applyValidation(range: Excel.Range, context: Excel.RequestContext) {
157
+ if (!supportsDataValidation()) {
158
+ console.warn("Data validation not supported on this platform");
159
+ return;
160
+ }
161
+ // ... apply validation ...
162
+ }
163
+ ```
164
+
165
+ ### Task Pane UI
166
+ - ! Task pane is a web page rendered in a side panel — standard HTML/CSS/JS
167
+ - ! Use the shared runtime model when the task pane and custom functions need to share state
168
+ - ~ Keep task pane UI simple — it is a narrow panel, not a full web app
169
+ - ! Use `Office.onReady()` to initialize — do not access Office APIs before this resolves
170
+ - ⊗ Block the task pane UI during long `Excel.run()` operations — show a loading indicator and keep the pane responsive
171
+
172
+ ### Manifest
173
+ - ~ Prefer the unified JSON manifest (Teams-compatible, modern) over the XML manifest (legacy)
174
+ - ! Declare the minimum API requirement set your add-in actually needs — do not over-declare
175
+ - ! Declare all permissions your add-in requires (read/write document, network access)
176
+ - ! If the add-in works in shared/coauthoring scenarios: in XML manifests set `<SupportsSharedFolders>true</SupportsSharedFolders>`; in JSON manifests set `"authorization": { "permissions": { "supportsSharedFolders": true } }` in the extension definition
177
+
178
+ ### Testing
179
+ See [testing.md](../coding/testing.md).
180
+
181
+ - ! Use Vitest (or Jest) for unit tests
182
+ - ! Use `office-addin-mock` to mock `Excel.RequestContext`, workbook, worksheets, and ranges
183
+ - ! Test business logic separately from Office.js API interactions — extract pure computation functions
184
+ - ! Test custom functions as pure functions (they should be side-effect-free)
185
+ - ! Test error paths: `ItemNotFound`, `InvalidReference`, network failures for external API calls
186
+ - ⊗ Test only the happy path — Office.js has many failure modes that must be covered
187
+ - Files: `*.spec.ts` or `*.test.ts`
188
+
189
+ ### Coverage
190
+ - ! ≥ 85% coverage on business logic modules
191
+ - ! ≥ 70% coverage on Office.js integration modules (mocking limitations reduce achievable coverage)
192
+ - ! Count src/\*
193
+ - ! Exclude: entry points, generated code, manifest files, webpack/vite config
194
+
195
+ ### Style
196
+ - ! Follow all rules from [typescript.md](./typescript.md) — ESLint, Prettier, strict mode, no `any`
197
+ - ! Use `async`/`await` exclusively — Office.js is promise-based, never use callbacks
198
+ - ~ Separate Office.js API code from business logic: `services/` (pure logic), `office/` (API interactions), `ui/` (task pane)
199
+
200
+ ### Security
201
+ - ⊗ Hardcode API keys, tokens, or credentials in add-in source
202
+ - ! Use `Office.context.auth.getAccessToken()` for SSO when authenticating against Microsoft Graph or backend APIs
203
+ - ⊗ Store sensitive data in `Office.context.document.settings` — it persists in the workbook and is visible to anyone who opens it
204
+ - ! Validate all data received from external APIs before writing to the workbook
205
+ - ⊗ Use `eval()` or dynamically construct Office.js API calls from user input
206
+
207
+ ### Telemetry
208
+ - See [telemetry.md](../tools/telemetry.md)
209
+ - ~ Structured logging for production (console in dev, backend logging service in production)
210
+ - ~ Sentry.io or equivalent for error tracking in deployed add-ins
211
+ - ~ Log `Excel.run()` durations and `context.sync()` counts for performance monitoring
212
+
213
+ ## Commands
214
+
215
+ ```bash
216
+ task officejs:dev # Start dev server + sideload add-in
217
+ task officejs:build # Production build
218
+ task officejs:test # Run unit tests (Vitest + office-addin-mock)
219
+ task officejs:lint # ESLint with Office.js rules
220
+ task officejs:typecheck # tsc --noEmit
221
+ task officejs:manifest # Validate manifest against schema
222
+ task officejs:sideload # Sideload add-in into Excel for testing
223
+ task check # Pre-commit: lint + typecheck + test
224
+ ```
225
+
226
+ ## Patterns
227
+
228
+ ### Standard Excel.run Pattern
229
+ ```typescript path=null start=null
230
+ export async function updateRevenueSection(modelConfig: ModelConfig): Promise<void> {
231
+ try {
232
+ await Excel.run(async (context) => {
233
+ // 1. Load — batch ALL reads in one sync: existence flags + range values
234
+ // getItemOrNullObject chains are safe: null objects propagate through getRange()
235
+ const revenueItem = loadNamedItem(context, "in_base_revenue");
236
+ const growthItem = loadNamedItem(context, "as_revenue_growth_rate");
237
+ const outputItem = loadNamedItem(context, "calc_projected_revenue");
238
+ const revenueRange = revenueItem.getRange(); // safe before sync: null objects chain
239
+ const growthRange = growthItem.getRange();
240
+ revenueRange.load("values");
241
+ growthRange.load("values");
242
+ await context.sync(); // sync 1 — existence flags + range values
243
+
244
+ // 2. Check existence after sync
245
+ if (revenueItem.isNullObject || growthItem.isNullObject || outputItem.isNullObject) {
246
+ throw new ModelConfigError(
247
+ "Missing required named ranges: in_base_revenue, as_revenue_growth_rate, calc_projected_revenue"
248
+ );
249
+ }
250
+
251
+ // 3. Compute — pure logic, no API calls
252
+ const baseRevenue = revenueRange.values[0][0] as number;
253
+ const growthRate = growthRange.values[0][0] as number;
254
+ const projected = projectRevenue(baseRevenue, growthRate, modelConfig.projectionYears);
255
+
256
+ // 4. Write — batch all writes
257
+ const outputRange = outputItem.getRange();
258
+ outputRange.values = projected.map((v) => [v]);
259
+ outputRange.format.font.bold = false; // formula role: no bold
260
+ outputRange.numberFormat = [["#,##0"]];
261
+ await context.sync(); // sync 2 — all writes committed
262
+ });
263
+ } catch (error) {
264
+ handleOfficeError(error, "updateRevenueSection");
265
+ }
266
+ }
267
+ ```
268
+
269
+ ### Centralized Error Handler
270
+ ```typescript path=null start=null
271
+ export class ModelConfigError extends Error {
272
+ constructor(message: string) {
273
+ super(message);
274
+ this.name = "ModelConfigError";
275
+ }
276
+ }
277
+
278
+ export function handleOfficeError(error: unknown, source: string): never {
279
+ if (error instanceof ModelConfigError) {
280
+ showUserError(`Model configuration error in ${source}: ${error.message}`);
281
+ throw error;
282
+ }
283
+ if (error instanceof OfficeExtension.Error) {
284
+ const detail = `[${error.code}] ${error.message}`;
285
+ console.error(`Office.js error in ${source}:`, detail, error.debugInfo);
286
+ showUserError(`Excel error in ${source}: ${detail}`);
287
+ throw error;
288
+ }
289
+ throw error;
290
+ }
291
+ ```
292
+
293
+ ### Platform-Safe API Access
294
+ ```typescript path=null start=null
295
+ const API_REQUIREMENTS: Record<string, string> = {
296
+ dataValidation: "ExcelApi 1.8",
297
+ chartEvents: "ExcelApi 1.8",
298
+ customFunctions: "CustomFunctionsRuntime 1.1",
299
+ dynamicArrays: "ExcelApi 1.12",
300
+ };
301
+
302
+ export function requiresApi(feature: keyof typeof API_REQUIREMENTS): boolean {
303
+ const [setName, version] = API_REQUIREMENTS[feature].split(" ");
304
+ return Office.context.requirements.isSetSupported(setName, version);
305
+ }
306
+ ```
307
+
308
+ ### Mocked Test Setup
309
+ ```typescript path=null start=null
310
+ import { describe, it, expect, vi, beforeEach } from "vitest";
311
+ import { OfficeMockObject } from "office-addin-mock";
312
+
313
+ const mockData = {
314
+ context: {
315
+ workbook: {
316
+ names: {
317
+ getItemOrNullObject: vi.fn(),
318
+ },
319
+ },
320
+ },
321
+ };
322
+
323
+ describe("updateRevenueSection", () => {
324
+ let mockContext: OfficeMockObject;
325
+
326
+ beforeEach(() => {
327
+ mockContext = new OfficeMockObject(mockData);
328
+ vi.clearAllMocks();
329
+ });
330
+
331
+ it("throws ModelConfigError when named range is missing", async () => {
332
+ mockData.context.workbook.names.getItemOrNullObject.mockReturnValue({
333
+ isNullObject: true,
334
+ load: vi.fn(),
335
+ });
336
+
337
+ await expect(updateRevenueSection(testConfig)).rejects.toThrow(ModelConfigError);
338
+ });
339
+ });
340
+ ```
341
+
342
+ ## Project Structure
343
+
344
+ ```
345
+ src/
346
+ office/ # Office.js API interaction layer
347
+ excel-service.ts
348
+ range-helpers.ts
349
+ format-helpers.ts
350
+ services/ # Pure business logic (no Office.js imports)
351
+ revenue.ts
352
+ projection.ts
353
+ validation.ts
354
+ functions/ # Custom functions (separate runtime)
355
+ functions.ts
356
+ ui/ # Task pane UI
357
+ taskpane.html
358
+ taskpane.ts
359
+ config/ # Model config types and loaders
360
+ model-config.ts
361
+ tests/
362
+ services/ # Business logic tests (no mocking needed)
363
+ office/ # Office.js integration tests (mocked context)
364
+ functions/ # Custom function tests (pure function tests)
365
+ manifest.json # Unified JSON manifest (or manifest.xml for legacy)
366
+ webpack.config.js # Build config
367
+ ```
368
+
369
+ ## Anti-Patterns
370
+
371
+ Items marked ⊗ in Standards above are not repeated here.
372
+
373
+ - ⊗ **Sync in a loop**: `for (...) { range.load(); await context.sync(); }` — load all ranges, sync once
374
+ - ⊗ **Stored proxy objects**: Saving a `Range` from one `Excel.run` for use in another — proxy objects expire
375
+ - ⊗ **Full `load()`**: `range.load()` loads every property — specify only what you need
376
+ - ⊗ **Mixed runtimes**: Importing task pane modules into custom function files or vice versa
377
+ - ⊗ **Hardcoded addresses**: `sheet.getRange("B5:B16")` — use named ranges or config-driven resolution
378
+ - ⊗ **Platform assumptions**: Using ExcelApi 1.12 features without checking `isSetSupported`
379
+
380
+ ## Compliance Checklist
381
+
382
+ - ! All Excel interactions inside `Excel.run()`
383
+ - ! Minimal `context.sync()` calls — batch reads and writes
384
+ - ! `load()` with specific properties before read
385
+ - ! Named ranges or table references — no hardcoded addresses
386
+ - ! Error handling for `OfficeExtension.Error` with specific codes
387
+ - ! Platform requirement checks for non-baseline APIs
388
+ - ! Business logic separated from Office.js API layer
389
+ - ! Custom functions isolated in separate runtime-safe modules
390
+ - ! Follow all [typescript.md](./typescript.md) rules for general TypeScript code
391
+ - ! See [testing.md](../coding/testing.md) for testing requirements
392
+ - ! Run `task check` before commit
@@ -0,0 +1,209 @@
1
+ # Python Standards
2
+
3
+ Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
+
5
+ **⚠️ See also** (load only when needed):
6
+ - [../main.md](../../main.md) - General AI guidelines
7
+ - [../PROJECT.md](../../PROJECT.md) - For project-specific overrides
8
+ - [../coding/testing.md](../coding/testing.md) - When writing tests
9
+
10
+ **Stack**: Python 3.11+, pytest; Web: Flask/FastAPI; CLI: typer[all]; TUI: textual[dev]
11
+
12
+ ## Standards
13
+
14
+ ### Documentation
15
+ - ! PEP 257 docstrings for all public APIs
16
+
17
+ ### Testing
18
+ See [../coding/testing.md](../coding/testing.md) for universal requirements.
19
+
20
+ - ! Use pytest + pytest-cov + pytest-mock
21
+ - Files: `test_*.py` or `*_test.py`
22
+
23
+ ### Coverage
24
+ - ! ≥85% coverage
25
+ - ! Count src/\*
26
+ - ! Exclude entry points, scripts, generated code
27
+ - ! Display-bound entry points (pygame/tkinter/PyQt/Kivy/Electron event loops) cannot run headlessly — exclude them via `[tool.coverage.run] omit` so the threshold measures logic modules only (see `Headless GUI / event-loop testing` under Patterns)
28
+
29
+ ### Style
30
+ - ! Follow PEP 8 via ruff + black + isort
31
+
32
+ ### Type Hints
33
+ - ! Use PEP 484 type hints on all functions/methods
34
+ - ! Pass mypy strict mode
35
+
36
+ ### Data Validation
37
+ - ~ Use Pydantic BaseModel for data crossing module/API boundaries
38
+ - ⊗ Raw dicts/lists for shared, persisted, or returned data
39
+ - ~ Use `strict=True`, `extra='forbid'` for data models
40
+ - ~ Use `frozen=True` for immutable shared data
41
+ - ~ Layered validation: type constraints → field validators → model validators
42
+ - ! Swarm/parallel work: strict + frozen mandatory
43
+
44
+ ## Commands
45
+
46
+ See [commands.md](./commands.md).
47
+
48
+ ## Patterns
49
+
50
+ ### Testing
51
+ **Parametrize**: `@pytest.mark.parametrize("a,b",[(1,2)])`, add `ids=[]` for names
52
+ **Fixtures**: `@pytest.fixture; yield val; cleanup()` for setup/teardown
53
+ **HTTP**: Flask: `app.test_client()`; FastAPI: `TestClient(app)`
54
+ **Mock**: `mocker.patch("mod.X")` or `@patch("mod.X")`
55
+ **Class**: `@pytest.fixture(autouse=True)` in class for shared setup
56
+ **Property Testing**: `hypothesis` for property-based tests
57
+ **Factories**: `pydantic-factories` for test data generation
58
+
59
+ ### Headless GUI / event-loop testing
60
+ Display-bound entry points (pygame, tkinter, PyQt/PySide, Kivy, Electron bridges) start an event loop that needs a real display, so they cannot execute under headless CI or a swarm-agent session. Measuring them drags overall coverage below the 85% threshold even when every logic module is fully tested — the agent reports an inflated per-session coverage that collapses when the full `src/` is measured. Two complementary patterns keep the gate honest.
61
+
62
+ **1. Headless display driver (test what you can).** For pygame/SDL, force the dummy video driver BEFORE importing the library so windowless smoke tests still run:
63
+ ```python
64
+ import os
65
+
66
+ os.environ.setdefault("SDL_VIDEODRIVER", "dummy") # ! set BEFORE importing pygame
67
+ os.environ.setdefault("SDL_AUDIODRIVER", "dummy")
68
+
69
+ import pygame # now safe to import under headless CI
70
+ ```
71
+ For tkinter, skip the test when no display is available:
72
+ ```python
73
+ import pytest
74
+
75
+ tk = pytest.importorskip("tkinter")
76
+
77
+ def _has_display() -> bool:
78
+ try:
79
+ root = tk.Tk()
80
+ except tk.TclError:
81
+ return False
82
+ root.destroy()
83
+ return True
84
+
85
+ requires_display = pytest.mark.skipif(
86
+ not _has_display(), reason="no display available (headless CI)"
87
+ )
88
+ ```
89
+
90
+ **2. Coverage omit (exclude the unreachable loop).** Keep the blocking event loop in a thin entry-point module (e.g. `src/ui.py`) that holds no business logic, then omit it so the threshold reflects logic modules only:
91
+ ```toml
92
+ [tool.coverage.run]
93
+ omit = [
94
+ "*/tests/*",
95
+ "*/venv/*",
96
+ "*/.venv/*",
97
+ "src/ui.py", # display-bound pygame/tkinter event loop — cannot run headlessly (#1027)
98
+ ]
99
+ ```
100
+ - ~ Keep display-bound modules thin: push testable logic (state, scoring, input handling) into separate, fully-tested modules so only the event-loop shell is omitted
101
+ - ⊗ Omit a module that mixes business logic with the event loop — refactor the logic out first, then omit only the loop
102
+
103
+ ### Data Models
104
+ **Pydantic BaseModel**:
105
+ ```python
106
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
107
+
108
+ class User(BaseModel):
109
+ model_config = ConfigDict(
110
+ strict=True, # ! Type coercion off
111
+ extra='forbid', # ! Reject unknown fields
112
+ frozen=True, # ~ Immutable (recommended for shared data)
113
+ )
114
+
115
+ id: int = Field(..., gt=0)
116
+ username: str = Field(..., min_length=3, max_length=32)
117
+
118
+ @field_validator('username')
119
+ @classmethod
120
+ def validate_username(cls, v: str) -> str:
121
+ if not v.isalnum():
122
+ raise ValueError("username must be alphanumeric")
123
+ return v
124
+ ```
125
+
126
+ **Immutable Functional Style**:
127
+ ```python
128
+ def process_order(order: Order) -> ProcessedOrder:
129
+ # Immutable transformation
130
+ data = order.model_dump()
131
+ # ... pure transformations ...
132
+ return ProcessedOrder(**data)
133
+ ```
134
+
135
+ ### Performance
136
+
137
+ **Validation:**
138
+ - ~ Use `model_validate_json()` over `json.loads()` + `model_validate()` (faster)
139
+ - ~ Reuse `TypeAdapter` instances for repeated validations
140
+ - ~ Use `fail_fast=True` on sequences to short-circuit bad items (Pydantic 2.8+)
141
+ - ~ Move heavy validators to Field constraints for hot paths
142
+
143
+ **Extreme Performance:**
144
+ - ? Consider `msgspec` for extreme perf needs (but prefer Pydantic for most)
145
+
146
+ **Telemetry:**
147
+ - See [../tools/telemetry.md](../tools/telemetry.md) for recommendations
148
+ - ~ Structured logging (structlog) for production
149
+ - ~ Sentry.io for error tracking
150
+ - ? logfire or OpenTelemetry for distributed tracing
151
+
152
+ ## pyproject.toml
153
+
154
+ ```toml
155
+ [project]
156
+ requires-python=">=3.11"
157
+ dependencies=["flask>=3.0.0"] # or fastapi/typer[all]/textual[dev]
158
+ [dependency-groups] # PEP 735 — uv syncs these automatically without extra flags
159
+ dev=["pytest>=7.4","pytest-cov>=4.1","pytest-mock>=3.12","hypothesis>=6.0","black>=23","isort>=5.12","ruff>=0.1","mypy>=1.7","pydantic>=2.0"]
160
+ [project.optional-dependencies] # pip/hatch compat — requires: uv sync --extra dev / pip install -e ".[dev]"
161
+ prod=["pydantic>=2.0","logfire>=0.1"] # ~ Observability for production
162
+ [tool.pytest.ini_options]
163
+ testpaths=["tests"]
164
+ python_files=["test_*.py","*_test.py"]
165
+ addopts="--cov=src --cov-report=html --cov-report=term-missing"
166
+ [tool.coverage.run]
167
+ omit=["*/tests/*","*/venv/*","*/.venv/*"]
168
+ [tool.coverage.report]
169
+ fail_under=85
170
+ [tool.black]
171
+ line-length=100
172
+ [tool.isort]
173
+ profile="black"
174
+ line_length=100
175
+ [tool.ruff]
176
+ line-length=100
177
+ select=["E","F","W","I","N","UP","B","A","C4","DTZ","T10","PIE","PT","RET","SIM"]
178
+ [tool.mypy]
179
+ python_version="3.11"
180
+ warn_return_any=true
181
+ warn_unused_configs=true
182
+ disallow_untyped_defs=true
183
+ ```
184
+
185
+ ## Hygiene
186
+
187
+ **Types:**
188
+ - ⊗ `# type: ignore` without an inline comment explaining exactly why it is safe
189
+ - ⊗ `Any` as a function return type where the concrete type is knowable
190
+ - ⊗ Bare `object` or untyped containers (`list`, `dict` with no generics) on public APIs
191
+
192
+ **Error handling:**
193
+ - ⊗ Bare `except:` or `except Exception: pass` — catch the specific exception; log or re-raise
194
+ - ⊗ Returning `None` or a neutral default to mask an exception — let it propagate
195
+
196
+ **Dead code:**
197
+ - ~ Run `vulture` to detect unused functions, classes, and variables
198
+ - ~ Add `vulture . --min-confidence 80` as a `task hygiene` target
199
+
200
+ **Circular dependencies:**
201
+ - ~ Run `pydeps <package>` or `importlab` to detect import cycles; resolve by extracting shared types to a lower-level module
202
+ - ⊗ Circular imports between modules — see [coding/hygiene.md](../coding/hygiene.md) for resolution pattern
203
+
204
+ ## Compliance Checklist
205
+
206
+ - ! Follow PEP 257 (docstrings) and PEP 484 (type hints)
207
+ - ! See [../coding/testing.md](../coding/testing.md) for testing requirements
208
+ - ~ Use Pydantic for data validation
209
+ - ! Run `task check` before commit