skilldrop-cli 1.9.0 → 1.11.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/README.md +3 -2
- package/catalogue.json +11 -2
- package/model-routing.json +12 -0
- package/package.json +1 -1
- package/packs/educator/pack.json +25 -0
- package/packs/educator/skills/exam-paper-check/SKILL.md +125 -0
- package/packs/educator/skills/exam-paper-check/evals/eval_queries.json +30 -0
- package/packs/educator/skills/exam-paper-check/evals/evals.json +73 -0
- package/packs/educator/skills/exam-paper-check/evals/files/1/year9-science-scheme.md +16 -0
- package/packs/educator/skills/exam-paper-check/evals/files/1/year9-science.md +58 -0
- package/packs/educator/skills/exam-paper-check/evals/files/2/mock-paper1-ms.md +32 -0
- package/packs/educator/skills/exam-paper-check/evals/files/2/mock-paper1.md +60 -0
- package/packs/educator/skills/exam-paper-check/evals/files/3/hist1042-quiz-markscheme.md +37 -0
- package/packs/educator/skills/exam-paper-check/evals/files/3/hist1042-quiz.md +50 -0
- package/packs/educator/skills/exam-paper-check/evals/files/4/chem-mock-paper.md +51 -0
- package/packs/educator/skills/exam-paper-check/evals/files/4/chem-mock-scheme.md +32 -0
- package/packs/educator/skills/exam-paper-check/evals/files/5/econ-p2-paper.md +57 -0
- package/packs/educator/skills/exam-paper-check/evals/files/5/econ-p2-scheme.md +34 -0
- package/packs/educator/skills/exam-paper-check/examples/inputs/spec.json +3 -0
- package/packs/educator/skills/exam-paper-check/examples/inputs/year9-science-scheme.md +16 -0
- package/packs/educator/skills/exam-paper-check/examples/inputs/year9-science.md +58 -0
- package/packs/educator/skills/exam-paper-check/examples/year9-forces.md +68 -0
- package/packs/educator/skills/exam-paper-check/manifest.json +33 -0
- package/packs/educator/skills/exam-paper-check/scripts/exam_check.py +328 -0
- package/packs/educator/skills/exam-paper-check/templates/spec.json +11 -0
- package/packs/startup-legal/pack.json +20 -4
- package/packs/startup-legal/skills/lease-check/SKILL.md +119 -0
- package/packs/startup-legal/skills/lease-check/evals/eval_queries.json +30 -0
- package/packs/startup-legal/skills/lease-check/evals/evals.json +45 -0
- package/packs/startup-legal/skills/lease-check/evals/files/1/heads-of-terms.md +15 -0
- package/packs/startup-legal/skills/lease-check/evals/files/2/heads-of-terms.md +32 -0
- package/packs/startup-legal/skills/lease-check/evals/files/3/heads-of-agreement.md +29 -0
- package/packs/startup-legal/skills/lease-check/examples/first-office.md +75 -0
- package/packs/startup-legal/skills/lease-check/examples/inputs/heads-of-terms.md +15 -0
- package/packs/startup-legal/skills/lease-check/manifest.json +33 -0
- package/packs/startup-legal/skills/lease-check/reference.md +70 -0
- package/packs/startup-legal/skills/lease-check/scripts/lease_terms.py +276 -0
- package/packs/startup-legal/skills/lease-check/templates/terms.json +15 -0
- package/packs/startup-legal/skills/vesting-schedule/SKILL.md +122 -0
- package/packs/startup-legal/skills/vesting-schedule/evals/eval_queries.json +30 -0
- package/packs/startup-legal/skills/vesting-schedule/evals/evals.json +70 -0
- package/packs/startup-legal/skills/vesting-schedule/examples/cofounder-leaving.md +66 -0
- package/packs/startup-legal/skills/vesting-schedule/examples/inputs/vesting.json +8 -0
- package/packs/startup-legal/skills/vesting-schedule/manifest.json +33 -0
- package/packs/startup-legal/skills/vesting-schedule/scripts/vesting.py +280 -0
- package/packs/startup-legal/skills/vesting-schedule/templates/vesting.json +12 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Heads of Agreement (non-binding) - Commercial Lease
|
|
2
|
+
|
|
3
|
+
**Premises:** Lot 7, 112 Wattle Street, Brunswick VIC 3056 - ground floor laboratory and office, approx. 410 m2
|
|
4
|
+
|
|
5
|
+
**Lessor:** Wattle Street Holdings Pty Ltd
|
|
6
|
+
|
|
7
|
+
**Lessee:** Quokka Bio Pty Ltd (ACN 600 000 000)
|
|
8
|
+
|
|
9
|
+
**Term:** 3 years commencing 1 September 2027 and expiring 31 August 2030
|
|
10
|
+
|
|
11
|
+
**Option to renew:** One further term of 3 years. The option must be exercised by written notice given not more than 6 months and not less than 3 months before the expiry date.
|
|
12
|
+
|
|
13
|
+
**Base rent:** A$96,000 per annum plus GST, payable monthly in advance.
|
|
14
|
+
|
|
15
|
+
**Rent-free (incentive):** 3 months' rent-free from the commencement date. Rent commencement date: 1 November 2027.
|
|
16
|
+
|
|
17
|
+
**Annual rent reviews:** On each anniversary of the commencement date (1 September 2028 and 1 September 2029) the base rent is reviewed by CPI (All Groups, Melbourne), subject to a minimum increase of 2% and a maximum increase of 5% per review.
|
|
18
|
+
|
|
19
|
+
**Review on renewal:** At the start of any option term the rent is reviewed to current market rent, provided the rent will not be less than the rent payable immediately before the review.
|
|
20
|
+
|
|
21
|
+
**Outgoings:** Lessee to pay 100% of the outgoings for the premises (rates, land tax, insurance, building services, common area cleaning and maintenance). Estimate for year 1: A$14,400 per annum plus GST. No cap on annual increases.
|
|
22
|
+
|
|
23
|
+
**Lessee break:** The lessee may end the lease on 31 August 2029 by giving not less than 6 months' written notice, provided that (a) the lessee is not in breach of any term of the lease at the date of the notice or at the break date, and (b) the lessee pays a break fee equal to 3 months' base rent at the rate payable at the break date.
|
|
24
|
+
|
|
25
|
+
**Make good:** At the end of the lease the lessee must remove all fit-out and laboratory services and return the premises to base-building condition, at its cost.
|
|
26
|
+
|
|
27
|
+
**Permitted use:** PC2 laboratory and ancillary office.
|
|
28
|
+
|
|
29
|
+
**Security:** Bank guarantee equal to 3 months' base rent plus GST.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Worked example: heads of terms for a first office
|
|
2
|
+
|
|
3
|
+
## Input
|
|
4
|
+
|
|
5
|
+
> *"We've been offered heads of terms for our first office
|
|
6
|
+
> ([inputs/heads-of-terms.md](inputs/heads-of-terms.md)). The agent wants an answer this week.
|
|
7
|
+
> What are we committing to, and when could we get out?"*
|
|
8
|
+
|
|
9
|
+
The terms extracted are [`../templates/terms.json`](../templates/terms.json).
|
|
10
|
+
|
|
11
|
+
## What it computed
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
# Lease: dates and money
|
|
15
|
+
|
|
16
|
+
Term: 2027-01-01 to 2031-12-31 (60 months).
|
|
17
|
+
|
|
18
|
+
## Deadlines
|
|
19
|
+
|
|
20
|
+
| Date | What | Detail |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| 2027-04-01 | rent starts | after 3 months rent-free |
|
|
23
|
+
| 2029-07-01 | tenant break notice | for the break on 2030-01-01; conditions: all rent paid; vacant possession |
|
|
24
|
+
| 2030-01-01 | rent review | cpi, upward only |
|
|
25
|
+
| 2031-06-30 | notice to leave at the end | for the end on 2031-12-31 |
|
|
26
|
+
|
|
27
|
+
## Committed cost
|
|
28
|
+
|
|
29
|
+
| Up to | Rent | Service charge | Total |
|
|
30
|
+
|---|---:|---:|---:|
|
|
31
|
+
| to the break on 2030-01-01 | £99,000 | £14,400 | £113,400 |
|
|
32
|
+
| to the end of the term (2031-12-31) | £179,990 | £24,000 | £203,990 |
|
|
33
|
+
|
|
34
|
+
## Rent by year
|
|
35
|
+
|
|
36
|
+
| Year | From | Yearly rent | Paid that year |
|
|
37
|
+
|---:|---|---:|---:|
|
|
38
|
+
| 1 | 2027-01-01 | £36,000 | £27,000 |
|
|
39
|
+
| 2 | 2028-01-01 | £36,000 | £36,000 |
|
|
40
|
+
| 3 | 2029-01-01 | £36,000 | £36,000 |
|
|
41
|
+
| 4 | 2030-01-01 | £40,495 | £40,495 |
|
|
42
|
+
| 5 | 2031-01-01 | £40,495 | £40,495 |
|
|
43
|
+
|
|
44
|
+
Deposit: £18,000.
|
|
45
|
+
|
|
46
|
+
WARN the break on 2030-01-01 is conditional (all rent paid; vacant possession): one unmet condition, such as a small unpaid sum, can make it fail
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Reply
|
|
50
|
+
|
|
51
|
+
**Signing commits Brightloop to £113,400 up to the break on 1 January 2030** (33 months' rent
|
|
52
|
+
after the rent-free, plus service charge), **and £203,990 if you stay to the end** on 31
|
|
53
|
+
December 2031. The break needs written notice by **1 July 2029**, and as drafted it fails if any
|
|
54
|
+
sum is unpaid or the unit isn't empty on the day, which would keep you in to 2031 for another
|
|
55
|
+
£90,590.
|
|
56
|
+
|
|
57
|
+
**Fixes before signing**
|
|
58
|
+
- 🟥 **Personal guarantee.** Ask for: *"No personal guarantee; the Tenant will provide a rent
|
|
59
|
+
deposit of six months' rent instead."* The deposit is already there.
|
|
60
|
+
- 🟥 **Break conditions.** Ask for: *"The only condition of the break is that the Tenant has paid
|
|
61
|
+
the principal rent due up to the break date."*
|
|
62
|
+
- 🟧 **Full repairing with no schedule of condition.** Ask for a photographic schedule of
|
|
63
|
+
condition, attached to the lease, and repair limited to that condition.
|
|
64
|
+
- 🟧 **Service charge with no cap.** Ask for a cap at £4,800 rising with CPI, and no charges for
|
|
65
|
+
improvements or replacing plant.
|
|
66
|
+
- 🟨 **No sharing.** Ask for the right to share with group companies without consent, and to
|
|
67
|
+
sublet with consent not to be unreasonably withheld.
|
|
68
|
+
|
|
69
|
+
**Deadlines to diarise**
|
|
70
|
+
- 1 April 2027: rent starts.
|
|
71
|
+
- 1 July 2029: last day to serve the break notice.
|
|
72
|
+
- 1 January 2030: rent review, to £40,495 a year at the capped 4 % (CPI below the cap would mean less).
|
|
73
|
+
- 30 June 2031: notice to leave at the end.
|
|
74
|
+
|
|
75
|
+
**Worth a lawyer's hour:** the personal guarantee, if the landlord won't drop it.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Heads of terms — Unit 4, Foundry Yard, Example Town
|
|
2
|
+
|
|
3
|
+
- Landlord: Foundry Yard Estates Ltd
|
|
4
|
+
- Tenant: Brightloop Labs Ltd
|
|
5
|
+
- Premises: Unit 4, first floor, 2,100 sq ft
|
|
6
|
+
- Term: 5 years from 1 January 2027
|
|
7
|
+
- Rent: £36,000 a year exclusive, payable quarterly in advance
|
|
8
|
+
- Rent-free: first 3 months
|
|
9
|
+
- Rent review: at the end of year 3, to CPI over the preceding three years, compounded, with a cap of 4% a year and a collar of 1% a year; upward only
|
|
10
|
+
- Service charge: estimated £4,800 a year, no cap
|
|
11
|
+
- Deposit: 6 months' rent
|
|
12
|
+
- Tenant break: end of year 3, on 6 months' prior written notice, conditional on all sums due under the lease having been paid and vacant possession being given
|
|
13
|
+
- Repair: full repairing; no schedule of condition
|
|
14
|
+
- Alienation: no subletting or sharing
|
|
15
|
+
- Personal guarantee: from one director for the first 3 years
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lease-check",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Check a commercial lease before a startup signs it for its first office, studio or lab — a script turns the lease's terms into every date and sum the tenant must plan around (rent year by year through stepped rents, fixed and index-linked reviews with caps and collars, rent-free months, what the company is committed to pay up to each break and to the end, and each break, renewal and notice deadline as a date), then flags terms that can't all be true and deadlines already passed — and reads the lease for the clauses that hurt a small tenant. Use when a founder says \"review this office lease\", \"what's our break clause notice date\", \"how much are we committing to on this lease\", \"check this lease before I sign\", \"when can we get out of this lease\", or \"what does the rent review do\".",
|
|
5
|
+
"entrypoint": "SKILL.md",
|
|
6
|
+
"deps": {
|
|
7
|
+
"npm": [],
|
|
8
|
+
"pip": []
|
|
9
|
+
},
|
|
10
|
+
"env": {
|
|
11
|
+
"required": [],
|
|
12
|
+
"optional": []
|
|
13
|
+
},
|
|
14
|
+
"related": [],
|
|
15
|
+
"tags": [
|
|
16
|
+
"lease",
|
|
17
|
+
"office",
|
|
18
|
+
"rent-review",
|
|
19
|
+
"break-clause",
|
|
20
|
+
"startup",
|
|
21
|
+
"property"
|
|
22
|
+
],
|
|
23
|
+
"permissions": {
|
|
24
|
+
"network": [],
|
|
25
|
+
"commands": [],
|
|
26
|
+
"files": "named-paths",
|
|
27
|
+
"notes": "Reads the terms.json it is given and writes nothing."
|
|
28
|
+
},
|
|
29
|
+
"model": {
|
|
30
|
+
"tier": "standard",
|
|
31
|
+
"rationale": "Extracting lease terms and reading clauses from the tenant's side is bounded; the script does the dates and sums."
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Lease clauses that hurt a small tenant
|
|
2
|
+
|
|
3
|
+
For each: what to look for, why it hurts, and wording to ask for. Adapt numbers to the deal.
|
|
4
|
+
|
|
5
|
+
## Break clause
|
|
6
|
+
|
|
7
|
+
- **Look for:** conditions ("all rent paid", "material compliance with covenants", "vacant
|
|
8
|
+
possession"); notice in a strict form or to a specific address; a penalty; rent-free clawed
|
|
9
|
+
back on breaking.
|
|
10
|
+
- **Why it hurts:** courts apply break conditions strictly. A few pounds of unpaid service charge
|
|
11
|
+
or one item left behind can void the break and keep the tenant in to the end.
|
|
12
|
+
- **Ask for:** *"The only condition of the break is that the Tenant has paid the principal rent
|
|
13
|
+
due up to the break date."* Delete "material compliance" and "vacant possession" conditions, or
|
|
14
|
+
narrow vacant possession to "free of the Tenant's occupation".
|
|
15
|
+
|
|
16
|
+
## Rent review
|
|
17
|
+
|
|
18
|
+
- **Look for:** open-market reviews (unknown increase); upward-only wording; index-linked reviews
|
|
19
|
+
that compound several years at once; no cap.
|
|
20
|
+
- **Ask for:** a fixed increase, or index-linked with a cap of 3–4 % a year (compounded) and no
|
|
21
|
+
collar; reviews no more often than every three years.
|
|
22
|
+
|
|
23
|
+
## Repair, dilapidations and reinstatement
|
|
24
|
+
|
|
25
|
+
- **Look for:** "full repairing" obligations, especially on an older building; reinstatement of
|
|
26
|
+
all alterations at the end; no schedule of condition.
|
|
27
|
+
- **Why it hurts:** the tenant can be billed at the end to put the building in better condition
|
|
28
|
+
than when it moved in.
|
|
29
|
+
- **Ask for:** *"The Tenant is not required to put the Premises in better repair than shown in
|
|
30
|
+
the attached Schedule of Condition."* Photograph and attach it. Reinstatement only of
|
|
31
|
+
alterations the tenant made, and only if the landlord asks six months before the end.
|
|
32
|
+
|
|
33
|
+
## Service charge
|
|
34
|
+
|
|
35
|
+
- **Look for:** no cap; capital improvements or replacement of plant charged to tenants;
|
|
36
|
+
management fees as a percentage with no limit.
|
|
37
|
+
- **Ask for:** a cap at a stated sum rising with CPI; no charges for improvements, replacing
|
|
38
|
+
structure or plant, or remedying defects in the original build.
|
|
39
|
+
|
|
40
|
+
## Alienation (sharing, subletting, assigning)
|
|
41
|
+
|
|
42
|
+
- **Look for:** an absolute ban on sharing or subletting; consent needed to share with a group
|
|
43
|
+
company.
|
|
44
|
+
- **Ask for:** the right to share with group companies without consent, and to sublet or assign
|
|
45
|
+
with the landlord's consent "not to be unreasonably withheld or delayed".
|
|
46
|
+
|
|
47
|
+
## Guarantees and deposits
|
|
48
|
+
|
|
49
|
+
- **Look for:** a personal guarantee from a founder; a deposit of more than six months' rent;
|
|
50
|
+
no terms for its return.
|
|
51
|
+
- **Ask for:** no personal guarantee; a deposit of three to six months' rent, held in a
|
|
52
|
+
separate account, returned within 28 days of the end, and released early after two years of
|
|
53
|
+
on-time payment.
|
|
54
|
+
|
|
55
|
+
## Use, alterations and insurance
|
|
56
|
+
|
|
57
|
+
- **Look for:** a narrow permitted use that blocks growth (no lab use, no events); consent needed
|
|
58
|
+
for any internal alteration; insurance excluding terrorism or uninsured risks.
|
|
59
|
+
- **Ask for:** use "as offices and any other use within the same planning class"; internal
|
|
60
|
+
non-structural alterations without consent; rent suspended if the premises can't be used
|
|
61
|
+
after damage, including by uninsured risks, with a right to terminate if not restored within
|
|
62
|
+
a year.
|
|
63
|
+
|
|
64
|
+
## When a lawyer earns the fee
|
|
65
|
+
|
|
66
|
+
- a personal guarantee;
|
|
67
|
+
- full repairing terms on a building older than about 20 years;
|
|
68
|
+
- a break with conditions the landlord won't narrow;
|
|
69
|
+
- a lease that excludes statutory renewal protection where the law gives it;
|
|
70
|
+
- a long term (over five years) with open-market reviews.
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Turn a commercial lease's terms into the dates and money a tenant has to plan around. Stdlib only.
|
|
3
|
+
|
|
4
|
+
Reads a terms.json (start, length, rent and how it changes, rent-free months, service charge,
|
|
5
|
+
deposit, break clauses, renewal option, notice periods) and prints:
|
|
6
|
+
- rent year by year through stepped rents, fixed and index-linked reviews (with any cap and
|
|
7
|
+
collar), rent-free months and upward-only reviews
|
|
8
|
+
- what the tenant is committed to pay up to each break date and to the end of the term,
|
|
9
|
+
rent and service charge together
|
|
10
|
+
- every deadline as a date: each break notice, the renewal option's window, notice to leave,
|
|
11
|
+
each rent review, the end of the rent-free period
|
|
12
|
+
- deadlines already passed on the given date, and terms that can't all be true (a break
|
|
13
|
+
after the end of the term, a notice longer than the time left, rent-free longer than the term)
|
|
14
|
+
An open-market review can't be priced in advance; it is shown as unknown from that date on.
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
python3 lease_terms.py terms.json [--as-of 2027-03-01] [--json]
|
|
18
|
+
Exit codes: 0 computed, 1 a deadline passed or the terms contradict each other, 2 bad input.
|
|
19
|
+
"""
|
|
20
|
+
import argparse
|
|
21
|
+
import calendar
|
|
22
|
+
import datetime as dt
|
|
23
|
+
import json
|
|
24
|
+
import math
|
|
25
|
+
import sys
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class InputError(Exception):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def day(v, what):
|
|
33
|
+
try:
|
|
34
|
+
return dt.date.fromisoformat(str(v))
|
|
35
|
+
except ValueError:
|
|
36
|
+
raise InputError(f"{what} must be a date as YYYY-MM-DD")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def add_months(d, n):
|
|
40
|
+
y, m = divmod(d.year * 12 + d.month - 1 + n, 12)
|
|
41
|
+
return dt.date(y, m + 1, min(d.day, calendar.monthrange(y, m + 1)[1]))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def num(v, what, lo=0.0):
|
|
45
|
+
if isinstance(v, bool) or not isinstance(v, (int, float)) or not math.isfinite(v) or v < lo:
|
|
46
|
+
raise InputError(f"{what} must be a number of at least {lo:g}")
|
|
47
|
+
return float(v)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def rate(v, what):
|
|
51
|
+
v = num(v, what, -100)
|
|
52
|
+
return v / 100 if abs(v) >= 1 else v
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def whole(v, what, lo=0):
|
|
56
|
+
if isinstance(v, bool) or not isinstance(v, int) or v < lo:
|
|
57
|
+
raise InputError(f"{what} must be a whole number of months")
|
|
58
|
+
return v
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def load(d):
|
|
62
|
+
if not isinstance(d, dict):
|
|
63
|
+
raise InputError("terms.json must be an object")
|
|
64
|
+
start = day(d.get("start"), "start")
|
|
65
|
+
term = whole(d.get("term_months"), "term_months", 1)
|
|
66
|
+
rent = d.get("rent") or {}
|
|
67
|
+
if not isinstance(rent, dict) or "annual" not in rent:
|
|
68
|
+
raise InputError("`rent` needs `annual`, the starting yearly rent")
|
|
69
|
+
reviews = []
|
|
70
|
+
for i, r in enumerate(rent.get("reviews") or [], 1):
|
|
71
|
+
kind = str(r.get("type", "fixed")).lower()
|
|
72
|
+
if kind not in ("fixed", "index", "cpi", "rpi", "open market", "market"):
|
|
73
|
+
raise InputError(f"review {i}: type must be fixed, index (cpi/rpi) or open market")
|
|
74
|
+
rv = {"month": whole(r.get("month"), f"review {i}: month", 1), "type": kind,
|
|
75
|
+
"upward_only": bool(r.get("upward_only", True))}
|
|
76
|
+
if kind == "fixed":
|
|
77
|
+
rv["increase"] = rate(r.get("increase", 0), f"review {i}: increase")
|
|
78
|
+
elif kind in ("index", "cpi", "rpi"):
|
|
79
|
+
rv["assumed"] = rate(r.get("assumed", 0), f"review {i}: assumed index rise")
|
|
80
|
+
rv["cap"] = rate(r["cap"], f"review {i}: cap") if r.get("cap") is not None else None
|
|
81
|
+
rv["collar"] = rate(r["collar"], f"review {i}: collar") if r.get("collar") is not None else None
|
|
82
|
+
rv["compounded_years"] = whole(r.get("years", 1), f"review {i}: years", 1)
|
|
83
|
+
reviews.append(rv)
|
|
84
|
+
steps = [{"month": whole(s.get("from_month"), "stepped rent: from_month", 1),
|
|
85
|
+
"annual": num(s.get("annual"), "stepped rent: annual")} for s in rent.get("stepped") or []]
|
|
86
|
+
breaks = []
|
|
87
|
+
for i, b in enumerate(d.get("breaks") or [], 1):
|
|
88
|
+
bdate = day(b["date"], f"break {i}: date") if b.get("date") else None
|
|
89
|
+
if bdate is None and b.get("month") is None:
|
|
90
|
+
raise InputError(f"break {i}: give `month` (months from the start) or the lease's own `date`")
|
|
91
|
+
if bdate is not None:
|
|
92
|
+
months = (bdate.year - start.year) * 12 + bdate.month - start.month + (1 if bdate.day >= start.day - 1 else 0)
|
|
93
|
+
b = dict(b, month=b.get("month", max(1, months)))
|
|
94
|
+
breaks.append({"month": whole(b.get("month"), f"break {i}: month", 1), "date": bdate,
|
|
95
|
+
"notice": whole(b.get("notice_months"), f"break {i}: notice_months", 0),
|
|
96
|
+
"party": str(b.get("party", "tenant")),
|
|
97
|
+
"conditions": [str(c) for c in b.get("conditions") or []],
|
|
98
|
+
"penalty_months": num(b.get("penalty_months", 0), f"break {i}: penalty_months")})
|
|
99
|
+
sc = d.get("service_charge") or {}
|
|
100
|
+
dep = d.get("deposit") or {}
|
|
101
|
+
renewal = d.get("renewal_option")
|
|
102
|
+
return {
|
|
103
|
+
"currency": str(d.get("currency", "GBP")), "start": start, "term": term,
|
|
104
|
+
"annual": num(rent["annual"], "rent.annual"), "payable": str(rent.get("payable", "quarterly")).lower(),
|
|
105
|
+
"rent_free": whole(rent.get("rent_free_months", 0), "rent_free_months"),
|
|
106
|
+
"reviews": sorted(reviews, key=lambda r: r["month"]), "steps": sorted(steps, key=lambda s: s["month"]),
|
|
107
|
+
"service": num(sc.get("annual", 0), "service_charge.annual"),
|
|
108
|
+
"service_cap": num(sc["cap"], "service_charge.cap") if sc.get("cap") is not None else None,
|
|
109
|
+
"deposit": num(dep["amount"], "deposit.amount") if dep.get("amount") is not None else (
|
|
110
|
+
num(dep.get("months", 0), "deposit.months") / 12 * num(rent["annual"], "rent.annual")),
|
|
111
|
+
"breaks": sorted(breaks, key=lambda b: b["month"]),
|
|
112
|
+
"renewal": None if not renewal else {
|
|
113
|
+
"notice": whole(renewal.get("notice_months"), "renewal_option.notice_months", 0),
|
|
114
|
+
"earliest": whole(renewal.get("earliest_months_before_end", renewal.get("notice_months", 0)),
|
|
115
|
+
"renewal_option.earliest_months_before_end", 0)},
|
|
116
|
+
"leave_notice": whole(d["notice_to_leave_months"], "notice_to_leave_months")
|
|
117
|
+
if d.get("notice_to_leave_months") is not None else None,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def rent_by_month(t):
|
|
122
|
+
"""Yearly rate in force for each month, and None once an open-market review makes it unknown."""
|
|
123
|
+
rates = []
|
|
124
|
+
current = t["annual"]
|
|
125
|
+
steps = {s["month"]: s["annual"] for s in t["steps"]}
|
|
126
|
+
reviews = {r["month"]: r for r in t["reviews"]}
|
|
127
|
+
notes = []
|
|
128
|
+
for m in range(t["term"]):
|
|
129
|
+
if m in steps and current is not None:
|
|
130
|
+
current = steps[m]
|
|
131
|
+
if m in reviews and m > 0 and current is not None:
|
|
132
|
+
r = reviews[m]
|
|
133
|
+
if r["type"] == "fixed":
|
|
134
|
+
new = current * (1 + r["increase"])
|
|
135
|
+
elif r["type"] in ("index", "cpi", "rpi"):
|
|
136
|
+
rise = (1 + r["assumed"]) ** r["compounded_years"] - 1
|
|
137
|
+
lo = ((1 + r["collar"]) ** r["compounded_years"] - 1) if r["collar"] is not None else None
|
|
138
|
+
hi = ((1 + r["cap"]) ** r["compounded_years"] - 1) if r["cap"] is not None else None
|
|
139
|
+
if hi is not None:
|
|
140
|
+
rise = min(rise, hi)
|
|
141
|
+
if lo is not None:
|
|
142
|
+
rise = max(rise, lo)
|
|
143
|
+
new = current * (1 + rise)
|
|
144
|
+
else:
|
|
145
|
+
new = None
|
|
146
|
+
notes.append(f"open-market review at month {m}: rent unknown from then; budget for an increase")
|
|
147
|
+
if new is not None and r["upward_only"]:
|
|
148
|
+
new = max(new, current)
|
|
149
|
+
current = new
|
|
150
|
+
rates.append(current)
|
|
151
|
+
return rates, notes
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def cost(t, rates, months):
|
|
155
|
+
"""Rent (after rent-free) and service charge payable in the first `months` months."""
|
|
156
|
+
rent = 0.0
|
|
157
|
+
unknown = False
|
|
158
|
+
for m in range(min(months, t["term"])):
|
|
159
|
+
if m < t["rent_free"]:
|
|
160
|
+
continue
|
|
161
|
+
if rates[m] is None:
|
|
162
|
+
unknown = True
|
|
163
|
+
continue
|
|
164
|
+
rent += rates[m] / 12
|
|
165
|
+
service = t["service"] * min(months, t["term"]) / 12
|
|
166
|
+
if t["service_cap"] is not None:
|
|
167
|
+
service = min(service, t["service_cap"] * min(months, t["term"]) / 12)
|
|
168
|
+
return rent, service, unknown
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def analyse(t, as_of):
|
|
172
|
+
rates, notes = rent_by_month(t)
|
|
173
|
+
end = add_months(t["start"], t["term"]) - dt.timedelta(days=1) # a 5-year lease from 1 Jan ends on 31 Dec
|
|
174
|
+
fails, warns, deadlines = [], [], []
|
|
175
|
+
if t["rent_free"] >= t["term"]:
|
|
176
|
+
fails.append("the rent-free period is as long as the term")
|
|
177
|
+
if t["rent_free"]:
|
|
178
|
+
deadlines.append(("rent starts", add_months(t["start"], t["rent_free"]), f"after {t['rent_free']} months rent-free"))
|
|
179
|
+
for r in t["reviews"]:
|
|
180
|
+
when = add_months(t["start"], r["month"])
|
|
181
|
+
if r["month"] >= t["term"]:
|
|
182
|
+
fails.append(f"a rent review at month {r['month']} falls after the lease ends")
|
|
183
|
+
deadlines.append(("rent review", when, r["type"] + (", upward only" if r["upward_only"] else "")))
|
|
184
|
+
commitments = []
|
|
185
|
+
for b in t["breaks"]:
|
|
186
|
+
bdate = b["date"] or add_months(t["start"], b["month"])
|
|
187
|
+
notice_by = add_months(bdate, -b["notice"])
|
|
188
|
+
if b["month"] >= t["term"]:
|
|
189
|
+
fails.append(f"a break at month {b['month']} ({bdate}) is after the lease ends ({end})")
|
|
190
|
+
continue
|
|
191
|
+
if notice_by < t["start"]:
|
|
192
|
+
fails.append(f"the break on {bdate} needs {b['notice']} months' notice, which runs back before the lease starts")
|
|
193
|
+
deadlines.append((f"{b['party']} break notice", notice_by, f"for the break on {bdate}"
|
|
194
|
+
+ (f"; conditions: {'; '.join(b['conditions'])}" if b["conditions"] else "")))
|
|
195
|
+
rent, service, unknown = cost(t, rates, b["month"])
|
|
196
|
+
penalty = (rates[b["month"] - 1] or 0) / 12 * b["penalty_months"] if b["penalty_months"] else 0
|
|
197
|
+
commitments.append((f"to the break on {bdate}", rent + penalty, service, unknown, penalty))
|
|
198
|
+
if b["party"].lower() in ("landlord", "mutual", "either"):
|
|
199
|
+
warns.append(f"the landlord can end the lease on {bdate}; plan for having to move then")
|
|
200
|
+
if b["conditions"]:
|
|
201
|
+
warns.append(f"the break on {bdate} is conditional ({'; '.join(b['conditions'])}): one unmet condition, "
|
|
202
|
+
"such as a small unpaid sum, can make it fail")
|
|
203
|
+
rent, service, unknown = cost(t, rates, t["term"])
|
|
204
|
+
commitments.append((f"to the end of the term ({end})", rent, service, unknown, 0))
|
|
205
|
+
if t["renewal"]:
|
|
206
|
+
latest = add_months(end, -t["renewal"]["notice"])
|
|
207
|
+
earliest = add_months(end, -t["renewal"]["earliest"])
|
|
208
|
+
deadlines.append(("renewal option", latest, f"window opens {earliest}" if earliest < latest else "exact date"))
|
|
209
|
+
if t["leave_notice"] is not None:
|
|
210
|
+
deadlines.append(("notice to leave at the end", add_months(end, -t["leave_notice"]), f"for the end on {end}"))
|
|
211
|
+
deadlines.sort(key=lambda d: d[1])
|
|
212
|
+
for what, when, _ in deadlines:
|
|
213
|
+
if as_of and when < as_of and what not in ("rent review", "rent starts"):
|
|
214
|
+
fails.append(f"the {what} deadline ({when}) has already passed")
|
|
215
|
+
years = []
|
|
216
|
+
for y in range(math.ceil(t["term"] / 12)):
|
|
217
|
+
ms = range(12 * y, min(12 * (y + 1), t["term"]))
|
|
218
|
+
known = [rates[m] for m in ms if rates[m] is not None]
|
|
219
|
+
paid = sum((rates[m] or 0) / 12 for m in ms if m >= t["rent_free"])
|
|
220
|
+
years.append({"year": y + 1, "from": add_months(t["start"], 12 * y),
|
|
221
|
+
"rate": known[-1] if len(known) == len(ms) else None, "rent_paid": paid,
|
|
222
|
+
"unknown": len(known) < len(ms)})
|
|
223
|
+
return {"end": end, "years": years, "commitments": commitments, "deadlines": deadlines,
|
|
224
|
+
"notes": notes, "fails": fails, "warns": warns, "deposit": t["deposit"]}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def money(v, cur):
|
|
228
|
+
sym = {"GBP": "£", "USD": "$", "EUR": "€", "INR": "₹", "AUD": "A$", "CAD": "C$"}.get(cur.upper(), "")
|
|
229
|
+
return f"{sym}{v:,.0f}" if sym else f"{v:,.0f} {cur}"
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def report(t, r):
|
|
233
|
+
cur = t["currency"]
|
|
234
|
+
L = ["# Lease: dates and money", "", f"Term: {t['start']} to {r['end']} ({t['term']} months).", "",
|
|
235
|
+
"## Deadlines", "", "| Date | What | Detail |", "|---|---|---|"]
|
|
236
|
+
for what, when, detail in r["deadlines"]:
|
|
237
|
+
L.append(f"| {when} | {what} | {detail} |")
|
|
238
|
+
L += ["", "## Committed cost", "", "| Up to | Rent | Service charge | Total |", "|---|---:|---:|---:|"]
|
|
239
|
+
for label, rent, service, unknown, penalty in r["commitments"]:
|
|
240
|
+
flag = " + unknown open-market rent" if unknown else ""
|
|
241
|
+
pen = f" (incl. {money(penalty, cur)} break penalty)" if penalty else ""
|
|
242
|
+
L.append(f"| {label} | {money(rent, cur)}{pen}{flag} | {money(service, cur)} | {money(rent + service, cur)}{flag} |")
|
|
243
|
+
L += ["", "## Rent by year", "", "| Year | From | Yearly rent | Paid that year |", "|---:|---|---:|---:|"]
|
|
244
|
+
for y in r["years"]:
|
|
245
|
+
rate_s = money(y["rate"], cur) if y["rate"] is not None else "unknown (open market)"
|
|
246
|
+
L.append(f"| {y['year']} | {y['from']} | {rate_s} | {money(y['rent_paid'], cur)}{' + unknown' if y['unknown'] else ''} |")
|
|
247
|
+
if r["deposit"]:
|
|
248
|
+
L += ["", f"Deposit: {money(r['deposit'], cur)}."]
|
|
249
|
+
L += [""] + [f"NOTE {n}" for n in r["notes"]]
|
|
250
|
+
L += [f"FAIL {f}" for f in r["fails"]] + [f"WARN {w}" for w in r["warns"]]
|
|
251
|
+
return "\n".join(L).rstrip() + "\n"
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def main(argv=None):
|
|
255
|
+
ap = argparse.ArgumentParser(description="A lease's dates and money.")
|
|
256
|
+
ap.add_argument("terms")
|
|
257
|
+
ap.add_argument("--as-of", help="YYYY-MM-DD; deadlines before it are reported as passed")
|
|
258
|
+
ap.add_argument("--json", action="store_true")
|
|
259
|
+
args = ap.parse_args(argv)
|
|
260
|
+
try:
|
|
261
|
+
with open(args.terms, encoding="utf-8-sig") as f:
|
|
262
|
+
t = load(json.load(f))
|
|
263
|
+
as_of = day(args.as_of, "--as-of") if args.as_of else None
|
|
264
|
+
except (OSError, ValueError, UnicodeDecodeError) as e:
|
|
265
|
+
print(f"FAIL cannot read input: {e}")
|
|
266
|
+
return 2
|
|
267
|
+
except InputError as e:
|
|
268
|
+
print(f"FAIL {e}")
|
|
269
|
+
return 2
|
|
270
|
+
r = analyse(t, as_of)
|
|
271
|
+
print(json.dumps(r, indent=2, default=str) if args.json else report(t, r), end="" if not args.json else "\n")
|
|
272
|
+
return 1 if r["fails"] else 0
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
if __name__ == "__main__":
|
|
276
|
+
sys.exit(main())
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"currency": "GBP",
|
|
3
|
+
"start": "2027-01-01",
|
|
4
|
+
"term_months": 60,
|
|
5
|
+
"rent": {
|
|
6
|
+
"annual": 36000,
|
|
7
|
+
"payable": "quarterly",
|
|
8
|
+
"rent_free_months": 3,
|
|
9
|
+
"reviews": [{"month": 36, "type": "cpi", "assumed": 0.04, "cap": 0.04, "collar": 0.01, "years": 3, "upward_only": true}]
|
|
10
|
+
},
|
|
11
|
+
"service_charge": {"annual": 4800},
|
|
12
|
+
"deposit": {"months": 6},
|
|
13
|
+
"breaks": [{"month": 36, "notice_months": 6, "party": "tenant", "conditions": ["all rent paid", "vacant possession"]}],
|
|
14
|
+
"notice_to_leave_months": 6
|
|
15
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vesting-schedule
|
|
3
|
+
description: Work out vesting for founder stock, restricted stock, options and RSUs — each grant's cliff date and amount, every vesting date in whole shares, what is vested on any date, what happens when someone leaves (vested, forfeited or repurchasable, and the last day to exercise), single- and double-trigger acceleration on a sale, and the US 83(b) election deadline moved past weekends and holidays — computed by a script that keeps the totals exact. Use when a founder asks "how much has my co-founder vested", "what happens to her shares if she leaves", "when does the cliff hit", "build our vesting schedule", "when is the 83(b) deadline", or "what does double-trigger acceleration give us in a sale".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# vesting-schedule
|
|
7
|
+
|
|
8
|
+
Founders and early employees ask the same questions at the worst moments: a co-founder is
|
|
9
|
+
leaving, an acquirer is asking, a grant was signed last week and nobody remembers the 83(b).
|
|
10
|
+
The answers are date arithmetic that is easy to get wrong in ways that matter: a vesting start
|
|
11
|
+
on the 31st in a month with 30 days, a cliff that falls between two quarterly dates, rounding
|
|
12
|
+
that runs ahead of the grant, a 30-day deadline that lands on the Fourth of July. A script
|
|
13
|
+
computes every grant exactly; the agent turns the founder's documents into its input and the
|
|
14
|
+
result into the answer they need.
|
|
15
|
+
|
|
16
|
+
## How to respond
|
|
17
|
+
|
|
18
|
+
**These rules come before the steps and outrank them:**
|
|
19
|
+
|
|
20
|
+
- **Answer the question asked, first.** ✅ *"If Sam leaves on 20 August, 45,000 of their
|
|
21
|
+
120,000 options have vested and the other 75,000 lapse. They have until 18 November to
|
|
22
|
+
exercise."* Never mention a script, a JSON file, this skill's name, a path inside its folder
|
|
23
|
+
or a temporary path, and never call your work "the check" or "a tool".
|
|
24
|
+
- **Every share count and date comes from the computed schedule,** run on the final inputs.
|
|
25
|
+
Each scenario the person asks about (a leaving date, a closing date, "what if she's let go
|
|
26
|
+
in July instead") is its own run with that date. Never count months by hand: a 30-month
|
|
27
|
+
answer written as 31 is the commonest error in this work.
|
|
28
|
+
- **Stay on the questions asked,** in their order, and answer each fully. For a leaver that
|
|
29
|
+
means what happens to the unvested shares: who can buy them back, at what price if the
|
|
30
|
+
documents say, and the deadline for doing it as a date, against today's date, plus the last
|
|
31
|
+
day to exercise vested options.
|
|
32
|
+
- **The 83(b) deadline is never approximate.** Give the date, say it is 30 days from the grant
|
|
33
|
+
(or from an early exercise), and when day 30 falls on a weekend or federal holiday, say that
|
|
34
|
+
the deadline moves to the next business day and advise filing well before day 30 anyway.
|
|
35
|
+
When it has passed, say so plainly and what that means: the shares are taxed as they vest
|
|
36
|
+
instead of at grant. Recommend a tax adviser only for what to do next, not for the date.
|
|
37
|
+
- **Say which terms you assumed.** The vesting start (often earlier than the grant date), the
|
|
38
|
+
cliff, monthly or quarterly vesting, the exercise window after leaving (90 days is usual), the
|
|
39
|
+
company's buy-back window, what counts as a "good reason" resignation for double-trigger, and
|
|
40
|
+
how acceleration is ordered (single trigger here applies to what is unvested at the sale;
|
|
41
|
+
documents sometimes accelerate from the end of the schedule instead). Tag each `[assumption]` and
|
|
42
|
+
give the figure under the other reading when it changes the answer.
|
|
43
|
+
- **Name what is worth a lawyer's hour, and only that.** One or two lines: a leaver dispute,
|
|
44
|
+
acceleration terms an acquirer is pushing back on, a missed 83(b). Never refuse, never open
|
|
45
|
+
with a disclaimer.
|
|
46
|
+
- **Working files stay out of sight.** The input goes in a private temporary folder
|
|
47
|
+
(`mktemp -d`) and is deleted before replying. A spreadsheet they asked for goes in their
|
|
48
|
+
working directory under a plain name.
|
|
49
|
+
|
|
50
|
+
1. **Gather each grant** from the documents or the conversation: holder, type (founder stock,
|
|
51
|
+
restricted stock, option, RSU), shares, grant date, vesting start, months, cliff, frequency,
|
|
52
|
+
acceleration (single trigger: on a sale; double trigger: a sale and then losing the job within
|
|
53
|
+
a window), any leaving date, any early exercise, and whether an 83(b) was filed. Defaults
|
|
54
|
+
when unsaid: 48 months, a 12-month cliff, monthly, US. Ask at most two questions, only for a
|
|
55
|
+
missing share count or start date.
|
|
56
|
+
|
|
57
|
+
2. **Write the input** in the shape of [`templates/vesting.json`](templates/vesting.json). A sale
|
|
58
|
+
goes in `change_of_control` at the top.
|
|
59
|
+
|
|
60
|
+
3. **Run it** on the date they care about (today, a leaving date, a closing date).
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
work=$(mktemp -d 2>/dev/null || mktemp -d ./.work.XXXXXX) # a private folder for this run only
|
|
64
|
+
# Claude Code
|
|
65
|
+
python3 "${CLAUDE_SKILL_DIR}/scripts/vesting.py" "$work/vesting.json" --as-of 2027-06-30
|
|
66
|
+
# Other IDEs (from the skill folder); --csv writes every vesting date
|
|
67
|
+
python3 scripts/vesting.py "$work/vesting.json" --csv vesting-schedule.csv --full
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Exit 0 means computed, 1 means a check failed (a schedule that doesn't sum to its grant, or a
|
|
71
|
+
passed 83(b) deadline), 2 means the input can't be read. Rerun with the other reading of any
|
|
72
|
+
assumption that changes the answer.
|
|
73
|
+
|
|
74
|
+
4. **Reply in this order:**
|
|
75
|
+
1. **The answer** to their question, with the numbers and dates.
|
|
76
|
+
2. **Each grant's summary:** cliff date and amount, fully vested date, vested and unvested now.
|
|
77
|
+
3. **Deadlines:** 83(b) dates, last days to exercise.
|
|
78
|
+
4. **The schedule,** yearly in the reply (every date in a spreadsheet when asked).
|
|
79
|
+
5. **Assumptions,** one line each.
|
|
80
|
+
6. **What to have a lawyer or tax adviser look at,** one or two lines.
|
|
81
|
+
|
|
82
|
+
**Non-interactive runs** (subagent, CI, headless): unsaid terms take the defaults above, tagged
|
|
83
|
+
`[assumption]`. With no share count or no start date for a grant, emit `BLOCKED: need each
|
|
84
|
+
grant's shares and vesting start date`.
|
|
85
|
+
|
|
86
|
+
## Useful references in this skill
|
|
87
|
+
|
|
88
|
+
- [`templates/vesting.json`](templates/vesting.json) — the input: a founder with double-trigger acceleration, an engineer who leaves, an advisor on quarterly vesting
|
|
89
|
+
- [`scripts/vesting.py`](scripts/vesting.py) — cliffs, month-end dates, whole-share rounding, leavers, acceleration, 83(b) deadlines with US holidays
|
|
90
|
+
- [`examples/cofounder-leaving.md`](examples/cofounder-leaving.md) — worked example: a co-founder leaving 14 months in, with a missed 83(b) question
|
|
91
|
+
|
|
92
|
+
## Quality bar
|
|
93
|
+
|
|
94
|
+
- **Every share count and date comes from the computed schedule,** and each grant's dates sum to its shares.
|
|
95
|
+
- **A start on the 29th–31st vests on the last day of shorter months,** and leap years count.
|
|
96
|
+
- **The 83(b) deadline is an exact date,** moved past weekends and federal holidays, and a passed one is said plainly.
|
|
97
|
+
- **Leaving gives vested, unvested and the exercise deadline,** each from the leaving date, not today.
|
|
98
|
+
- **Every assumed term is stated,** with the other reading's figure when it changes the answer.
|
|
99
|
+
- **The reply answers the question first,** with no mention of scripts or files.
|
|
100
|
+
|
|
101
|
+
## When to use this skill
|
|
102
|
+
|
|
103
|
+
- ✅ A co-founder or early employee is leaving and everyone needs the numbers
|
|
104
|
+
- ✅ A new grant was signed: when does it cliff, and when is the 83(b) due
|
|
105
|
+
- ✅ An acquirer or investor asks what vests on a sale
|
|
106
|
+
- ✅ Building the schedule for a founder agreement or an option plan
|
|
107
|
+
|
|
108
|
+
## When NOT to use this skill
|
|
109
|
+
|
|
110
|
+
- ❌ Tax: what an 83(b) or an exercise costs, ISOs against NSOs, EMI or ESOP rules
|
|
111
|
+
- ❌ Ownership after a funding round: that's a cap table
|
|
112
|
+
- ❌ Drafting the stock purchase or option agreement
|
|
113
|
+
- ❌ Valuing the shares or setting a strike price
|
|
114
|
+
|
|
115
|
+
## Anti-patterns to avoid
|
|
116
|
+
|
|
117
|
+
- ❌ **"About 30 days from the grant."** The 83(b) date is exact, and a weekend moves it.
|
|
118
|
+
- ❌ **Vesting from the grant date when the agreement says an earlier start.** Founders often backdate the start to when they began.
|
|
119
|
+
- ❌ **Rounding each month up.** A schedule of 2,500.4 shares a month that rounds up overshoots the grant; round the running total down and settle on the last date.
|
|
120
|
+
- ❌ **Treating double-trigger as automatic on a sale.** It needs the second trigger.
|
|
121
|
+
- ❌ **Counting the leaver's vesting to today** instead of to their leaving date.
|
|
122
|
+
- ❌ **"The schedule shows…"** The founder wants the number.
|