@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.
- package/.agents/skills/deft/SKILL.md +6 -0
- package/.agents/skills/deft-directive-article-review/SKILL.md +11 -0
- package/.agents/skills/deft-directive-build/SKILL.md +10 -0
- package/.agents/skills/deft-directive-cost/SKILL.md +12 -0
- package/.agents/skills/deft-directive-debug/SKILL.md +13 -0
- package/.agents/skills/deft-directive-gh-arch/SKILL.md +11 -0
- package/.agents/skills/deft-directive-gh-slice/SKILL.md +10 -0
- package/.agents/skills/deft-directive-glossary/SKILL.md +10 -0
- package/.agents/skills/deft-directive-interview/SKILL.md +9 -0
- package/.agents/skills/deft-directive-pre-pr/SKILL.md +9 -0
- package/.agents/skills/deft-directive-refinement/SKILL.md +10 -0
- package/.agents/skills/deft-directive-release/SKILL.md +12 -0
- package/.agents/skills/deft-directive-review-cycle/SKILL.md +10 -0
- package/.agents/skills/deft-directive-setup/SKILL.md +10 -0
- package/.agents/skills/deft-directive-swarm/SKILL.md +10 -0
- package/.agents/skills/deft-directive-sync/SKILL.md +9 -0
- package/.agents/skills/deft-directive-triage/SKILL.md +9 -0
- package/.agents/skills/deft-directive-write-skill/SKILL.md +9 -0
- package/LICENSE.md +22 -0
- package/QUICK-START.md +167 -0
- package/UPGRADING.md +517 -0
- package/coding/build-output.md +28 -0
- package/coding/coding.md +235 -0
- package/coding/debugging.md +110 -0
- package/coding/holzmann.md +96 -0
- package/coding/hygiene.md +127 -0
- package/coding/security.md +158 -0
- package/coding/testing.md +162 -0
- package/coding/toolchain.md +44 -0
- package/commands.md +300 -0
- package/context/context.md +57 -0
- package/context/deterministic-split.md +67 -0
- package/context/examples.md +26 -0
- package/context/fractal-summaries.md +69 -0
- package/context/long-horizon.md +46 -0
- package/context/spec-deltas.md +177 -0
- package/context/tool-design.md +34 -0
- package/context/working-memory.md +62 -0
- package/contracts/boundary-maps.md +65 -0
- package/contracts/deterministic-questions.md +59 -0
- package/contracts/hierarchy.md +77 -0
- package/conventions/content-manifest.json +399 -0
- package/conventions/machine-generated-banner.md +130 -0
- package/conventions/references.md +120 -0
- package/conventions/rule-ownership.json +382 -0
- package/conventions/task-caching.md +43 -0
- package/conventions/vbrief-filenames.md +70 -0
- package/deployments/README.md +37 -0
- package/deployments/agentuity/README.md +138 -0
- package/deployments/agentuity/via-cli.md +380 -0
- package/deployments/agentuity/via-cloud.md +425 -0
- package/deployments/agentuity/via-github-actions.md +664 -0
- package/deployments/agentuity/via-gravity-network.md +606 -0
- package/deployments/agentuity/via-vpc.md +607 -0
- package/deployments/aws/README.md +38 -0
- package/deployments/aws/via-app-runner.md +612 -0
- package/deployments/aws/via-ecs-fargate.md +561 -0
- package/deployments/aws/via-elastic-beanstalk.md +628 -0
- package/deployments/aws/via-lambda.md +649 -0
- package/deployments/azure/README.md +37 -0
- package/deployments/azure/via-aks.md +390 -0
- package/deployments/azure/via-app-service.md +564 -0
- package/deployments/azure/via-container-apps.md +599 -0
- package/deployments/azure/via-functions.md +552 -0
- package/deployments/cloud-gov/README.md +63 -0
- package/deployments/cloud-gov/agents/compliance-docs.md +154 -0
- package/deployments/cloud-gov/agents.md +39 -0
- package/deployments/cloud-gov/cicd.md +64 -0
- package/deployments/cloud-gov/deployment.md +150 -0
- package/deployments/cloud-gov/logging.md +43 -0
- package/deployments/cloud-gov/manifest.md +121 -0
- package/deployments/cloud-gov/overview.md +58 -0
- package/deployments/cloud-gov/security.md +46 -0
- package/deployments/cloud-gov/services.md +72 -0
- package/deployments/cloud-gov/upstream/README.md +18 -0
- package/deployments/cloudflare/README.md +33 -0
- package/deployments/cloudflare/via-dashboard.md +83 -0
- package/deployments/cloudflare/via-git.md +90 -0
- package/deployments/cloudflare/via-github-actions.md +185 -0
- package/deployments/cloudflare/via-terraform.md +157 -0
- package/deployments/cloudflare/via-wrangler.md +165 -0
- package/deployments/fly-io/README.md +37 -0
- package/deployments/fly-io/via-dockerfile.md +648 -0
- package/deployments/fly-io/via-flyctl.md +653 -0
- package/deployments/fly-io/via-github-actions.md +695 -0
- package/deployments/fly-io/via-multi-region.md +598 -0
- package/deployments/google/README.md +34 -0
- package/deployments/google/via-app-engine.md +42 -0
- package/deployments/google/via-cloud-functions.md +23 -0
- package/deployments/google/via-cloud-run.md +330 -0
- package/deployments/google/via-gke.md +23 -0
- package/deployments/netlify/README.md +99 -0
- package/deployments/netlify/via-cli.md +17 -0
- package/deployments/netlify/via-functions.md +19 -0
- package/deployments/netlify/via-git.md +25 -0
- package/deployments/vercel/README.md +90 -0
- package/deployments/vercel/via-api.md +16 -0
- package/deployments/vercel/via-cli.md +17 -0
- package/deployments/vercel/via-git.md +24 -0
- package/docs/BROWNFIELD.md +179 -0
- package/docs/getting-started.md +137 -0
- package/docs/good-agents-md.md +137 -0
- package/events/README.md +89 -0
- package/events/event-record.schema.json +26 -0
- package/events/registry.json +166 -0
- package/events/registry.schema.json +71 -0
- package/glossary.md +145 -0
- package/incidents/README.md +81 -0
- package/incidents/_template.md +38 -0
- package/interfaces/cli.md +104 -0
- package/interfaces/rest.md +212 -0
- package/interfaces/tui.md +242 -0
- package/interfaces/web.md +123 -0
- package/languages/6502-DASM.md +132 -0
- package/languages/c.md +235 -0
- package/languages/commands.md +23 -0
- package/languages/cpp.md +132 -0
- package/languages/csharp.md +259 -0
- package/languages/dart.md +183 -0
- package/languages/delphi.md +218 -0
- package/languages/elixir.md +208 -0
- package/languages/go.md +78 -0
- package/languages/java.md +278 -0
- package/languages/javascript.md +163 -0
- package/languages/julia.md +175 -0
- package/languages/kotlin.md +193 -0
- package/languages/markdown.md +168 -0
- package/languages/mermaid.md +146 -0
- package/languages/officejs.md +392 -0
- package/languages/python.md +209 -0
- package/languages/r.md +163 -0
- package/languages/rust.md +216 -0
- package/languages/sql.md +216 -0
- package/languages/swift.md +153 -0
- package/languages/typescript.md +132 -0
- package/languages/vba.md +279 -0
- package/languages/vhdl.md +180 -0
- package/languages/visual-basic.md +151 -0
- package/languages/zig.md +196 -0
- package/meta/SOUL.md +27 -0
- package/meta/code-field.md +44 -0
- package/meta/morals.md +40 -0
- package/meta/philosophy.md +39 -0
- package/meta/project.md +49 -0
- package/meta/ralph.md +223 -0
- package/meta/security.md +80 -0
- package/meta/versioning.md +326 -0
- package/package.json +22 -0
- package/packs/lessons/lessons-pack-0.1.json +553 -0
- package/packs/patterns/patterns-pack-0.1.json +57 -0
- package/packs/rules/rules-pack-0.1.json +4767 -0
- package/packs/skills/skills-pack-0.1.json +262 -0
- package/packs/strategies/strategies-pack-0.1.json +167 -0
- package/packs/swarm-spec/swarm-spec-pack-0.1.json +17 -0
- package/patterns/executor-layer-credentials.md +227 -0
- package/patterns/llm-app.md +156 -0
- package/patterns/multi-agent.md +278 -0
- package/patterns/prompt-assembly-layer-ordering.md +154 -0
- package/patterns/role-as-overlay.md +179 -0
- package/platforms/2600.md +137 -0
- package/platforms/unity.md +329 -0
- package/references/composer-skill-porting.md +152 -0
- package/references/cost-models.md +163 -0
- package/references/ip-risk.md +246 -0
- package/references/plain-english-ux.md +275 -0
- package/resilience/context-pruning.md +67 -0
- package/resilience/continue-here.md +62 -0
- package/scm/changelog.md +276 -0
- package/scm/git.md +139 -0
- package/scm/github.md +265 -0
- package/secrets/.gitkeep +0 -0
- package/skills/deft-build/SKILL.md +20 -0
- package/skills/deft-directive-article-review/SKILL.md +156 -0
- package/skills/deft-directive-build/SKILL.md +302 -0
- package/skills/deft-directive-cost/SKILL.md +201 -0
- package/skills/deft-directive-debug/SKILL.md +140 -0
- package/skills/deft-directive-decompose/SKILL.md +96 -0
- package/skills/deft-directive-gh-arch/SKILL.md +160 -0
- package/skills/deft-directive-gh-slice/SKILL.md +199 -0
- package/skills/deft-directive-glossary/SKILL.md +118 -0
- package/skills/deft-directive-interview/SKILL.md +528 -0
- package/skills/deft-directive-pre-pr/SKILL.md +131 -0
- package/skills/deft-directive-probe/SKILL.md +127 -0
- package/skills/deft-directive-refinement/SKILL.md +403 -0
- package/skills/deft-directive-release/SKILL.md +266 -0
- package/skills/deft-directive-review-cycle/SKILL.md +401 -0
- package/skills/deft-directive-setup/SKILL.md +717 -0
- package/skills/deft-directive-swarm/SKILL.md +989 -0
- package/skills/deft-directive-sync/SKILL.md +288 -0
- package/skills/deft-directive-triage/SKILL.md +137 -0
- package/skills/deft-directive-write-skill/SKILL.md +169 -0
- package/skills/deft-interview/SKILL.md +16 -0
- package/skills/deft-pre-pr/SKILL.md +16 -0
- package/skills/deft-review-cycle/SKILL.md +16 -0
- package/skills/deft-roadmap-refresh/SKILL.md +16 -0
- package/skills/deft-setup/SKILL.md +20 -0
- package/skills/deft-swarm/SKILL.md +16 -0
- package/skills/deft-sync/SKILL.md +16 -0
- package/strategies/README.md +83 -0
- package/strategies/artifact-guards.md +85 -0
- package/strategies/bdd.md +115 -0
- package/strategies/brownfield.md +7 -0
- package/strategies/discuss.md +129 -0
- package/strategies/emit-hints.md +69 -0
- package/strategies/enterprise.md +193 -0
- package/strategies/interview.md +551 -0
- package/strategies/map.md +179 -0
- package/strategies/probe.md +151 -0
- package/strategies/rapid.md +155 -0
- package/strategies/research.md +155 -0
- package/strategies/roadmap.md +9 -0
- package/strategies/speckit.md +437 -0
- package/strategies/v0-20-contract.md +134 -0
- package/strategies/yolo.md +169 -0
- package/swarm/swarm.md +300 -0
- package/templates/COST-ESTIMATE.md +114 -0
- package/templates/PULL_REQUEST_TEMPLATE.md +35 -0
- package/templates/agent-prompt-preamble.md +409 -0
- package/templates/agents-entry.md +211 -0
- package/templates/agents-entry.placeholders.md +75 -0
- package/templates/embed.go +20 -0
- package/templates/embed_test.go +36 -0
- package/templates/make-spec-example.md +9 -0
- package/templates/make-spec.md +246 -0
- package/templates/project.md.template +52 -0
- package/templates/specification.md +1 -0
- package/templates/swarm-greptile-poller-prompt.md +556 -0
- package/templates/user.md.template +31 -0
- package/tools/RWLDL.md +80 -0
- package/tools/greptile.md +141 -0
- package/tools/installer.md +23 -0
- package/tools/taskfile-migration.md +32 -0
- package/tools/taskfile.md +185 -0
- package/tools/telemetry.md +285 -0
- package/vbrief/schemas/cache-meta.schema.json +137 -0
- package/vbrief/schemas/candidates.schema.json +130 -0
- package/vbrief/schemas/codebase-map.schema.json +213 -0
- package/vbrief/schemas/lessons-pack.schema.json +134 -0
- package/vbrief/schemas/patterns-pack.schema.json +84 -0
- package/vbrief/schemas/rules-pack.schema.json +105 -0
- package/vbrief/schemas/skills-pack.schema.json +94 -0
- package/vbrief/schemas/slices.schema.json +87 -0
- package/vbrief/schemas/strategies-pack.schema.json +89 -0
- package/vbrief/schemas/swarm-spec-pack.schema.json +84 -0
- package/vbrief/schemas/vbrief-core.schema.json +1022 -0
- package/vbrief/vbrief.md +684 -0
- package/verification/integration.md +76 -0
- package/verification/plan-checking.md +85 -0
- package/verification/uat.md +60 -0
- 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
|