wdi-method 0.6.15 → 0.6.19
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/CHANGELOG.md +244 -0
- package/README.md +538 -522
- package/kit/.constitution/method/README.md +1 -0
- package/kit/.constitution/method/ci-guide.md +150 -0
- package/kit/.constitution/method/document/corpus-guide.md +6 -0
- package/kit/.constitution/method/document/decision-guide.md +216 -208
- package/kit/.constitution/method/scripts/timeline.py +676 -663
- package/kit/.constitution/method/scripts/validate.py +124 -16
- package/kit/skills/wdi-autopilot/SKILL.md +440 -383
- package/kit/skills/wdi-build/SKILL.md +398 -393
- package/kit/skills/wdi-decision/SKILL.md +203 -197
- package/kit/skills/wdi-upgrade/SKILL.md +1 -0
- package/kit-overlay/AGENTS.md +1 -0
- package/kit-overlay/README.md +1 -0
- package/package.json +2 -1
|
@@ -1135,18 +1135,64 @@ def _dec_date(c: Corpus, dec: dict) -> dt.date | None:
|
|
|
1135
1135
|
return None
|
|
1136
1136
|
|
|
1137
1137
|
|
|
1138
|
+
# A mandate whose authority ONCE stood. `accepted` and `applied` are live; `superseded` is retired,
|
|
1139
|
+
# and retirement is NOT retroactive — the owner accepted it in person, and what was taken under it
|
|
1140
|
+
# while it stood stays accepted. Reading this status as present tense is what turned a whole run's
|
|
1141
|
+
# decisions red the day the owner changed one setting of the mandate, with no repair available that
|
|
1142
|
+
# does not falsify the record: the decisions are frozen, and the supersession really happened.
|
|
1143
|
+
MANDATE_STOOD = ("accepted", "applied", "superseded")
|
|
1144
|
+
|
|
1145
|
+
|
|
1146
|
+
def _dec_fm(c: Corpus, dec: dict) -> dict:
|
|
1147
|
+
"""The frontmatter of a decision's own file — `supersedes`/`superseded_by` live there in the
|
|
1148
|
+
template, and a product that wrote them only in the file is not wrong."""
|
|
1149
|
+
did = str(dec.get("id") or "")
|
|
1150
|
+
if not did:
|
|
1151
|
+
return {}
|
|
1152
|
+
for path in sorted(c.root.glob(f".control/decisions/{did}-*.md")):
|
|
1153
|
+
return frontmatter(path) or {}
|
|
1154
|
+
return {}
|
|
1155
|
+
|
|
1156
|
+
|
|
1157
|
+
def _revoked_on(c: Corpus, mandate: dict, by_id: dict[str, dict]) -> dt.date | None:
|
|
1158
|
+
"""The day a superseded mandate stopped delegating: the date of the decision that replaced it.
|
|
1159
|
+
|
|
1160
|
+
For a mandate, supersession IS revocation — `wdi-autopilot` names it as the way a run is ended
|
|
1161
|
+
for good, and the way a setting is changed. So it binds tighter than `expires`, and the window
|
|
1162
|
+
ends at whichever of the two came first.
|
|
1163
|
+
"""
|
|
1164
|
+
if str(mandate.get("status") or "") != "superseded":
|
|
1165
|
+
return None
|
|
1166
|
+
ref = str(mandate.get("superseded_by") or _dec_fm(c, mandate).get("superseded_by") or "").strip()
|
|
1167
|
+
if not ref:
|
|
1168
|
+
return None
|
|
1169
|
+
return _dec_date(c, by_id.get(ref) or {})
|
|
1170
|
+
|
|
1171
|
+
|
|
1138
1172
|
def mandate_accept(c: Corpus, r: Result) -> None:
|
|
1139
|
-
"""A decision accepted BY DELEGATION points at a
|
|
1173
|
+
"""A decision accepted BY DELEGATION points at a mandate that stood, and had not ended, when it was taken.
|
|
1140
1174
|
|
|
1141
1175
|
`wdi-autopilot` lets the agent accept decisions the owner would have accepted, and that is legal
|
|
1142
1176
|
only because the owner accepted the MANDATE in person. So three things hold: a mandate is never
|
|
1143
|
-
itself accepted by another decision — the chain of authority has a person at its root;
|
|
1144
|
-
|
|
1145
|
-
a `DEC-` names one that is `type: mandate`,
|
|
1177
|
+
itself accepted by another decision — the chain of authority has a person at its root; a mandate
|
|
1178
|
+
that stood names the day it ends, or it is standing permission; and a decision whose `accepted_by`
|
|
1179
|
+
is a `DEC-` names one that is `type: mandate`, stood on the decision's own date, and had not ended
|
|
1180
|
+
by then — expired, or superseded, whichever came first.
|
|
1181
|
+
|
|
1182
|
+
Every question it asks is about the PAST, so every answer is read from the past. A mandate's
|
|
1183
|
+
status today says when its authority ENDED, never that it was never granted: `superseded` is a
|
|
1184
|
+
retired mandate, and the decisions taken under it while it stood are still accepted. The one
|
|
1185
|
+
thing supersession does change is the window — see `_revoked_on`.
|
|
1146
1186
|
|
|
1147
1187
|
It says nothing about WHAT was decided — that is the ledger's job and the owner's review.
|
|
1148
1188
|
"""
|
|
1149
1189
|
by_id = {str(d.get("id")): d for d in c.decs}
|
|
1190
|
+
# Which mandates were actually USED. A retired mandate's obligations are read from what was taken
|
|
1191
|
+
# under it, not from its status: superseding one that delegated nothing owes no account of a run
|
|
1192
|
+
# that never happened, and superseding one that delegated forty decisions owes exactly what it
|
|
1193
|
+
# owed the day before — otherwise supersession is a way to make the ledger demand disappear.
|
|
1194
|
+
delegated_under = {str(d.get("accepted_by") or "").strip() for d in c.decs
|
|
1195
|
+
if str(d.get("type") or "") != "mandate"}
|
|
1150
1196
|
for dec in c.decs:
|
|
1151
1197
|
did = str(dec.get("id"))
|
|
1152
1198
|
ref = str(dec.get("accepted_by") or "").strip()
|
|
@@ -1156,7 +1202,7 @@ def mandate_accept(c: Corpus, r: Result) -> None:
|
|
|
1156
1202
|
r.fail("mandate-accept", did,
|
|
1157
1203
|
f"is a mandate accepted by delegation (`accepted_by: {ref}`) — the mandate is the one "
|
|
1158
1204
|
f"decision the owner accepts in person")
|
|
1159
|
-
if status in ("accepted", "applied"):
|
|
1205
|
+
if status in ("accepted", "applied") or (status == "superseded" and did in delegated_under):
|
|
1160
1206
|
if not ref:
|
|
1161
1207
|
r.fail("mandate-accept", did, "is an accepted mandate and `accepted_by` names nobody — "
|
|
1162
1208
|
"a person and a date is enough")
|
|
@@ -1177,6 +1223,17 @@ def mandate_accept(c: Corpus, r: Result) -> None:
|
|
|
1177
1223
|
# silently disables the lapse comparison for every decision taken under this mandate.
|
|
1178
1224
|
r.fail("mandate-accept", did, f"`mandate.expires: {raw}` is not a date — write `YYYY-MM-DD`. "
|
|
1179
1225
|
f"An expiry nothing can read stops nothing")
|
|
1226
|
+
if status == "superseded" and _revoked_on(c, dec, by_id) is None:
|
|
1227
|
+
# Retiring a mandate and not dating the retirement leaves the delegation reading as
|
|
1228
|
+
# good until `expires` — the opposite of what superseding it was for. Same failure as
|
|
1229
|
+
# the unparseable expiry above: a bound that looks present and compares nothing.
|
|
1230
|
+
r.fail("mandate-accept", did,
|
|
1231
|
+
"is a superseded mandate with decisions accepted under it, and nothing dates the "
|
|
1232
|
+
"supersession — `superseded_by` naming the decision that replaced it, and "
|
|
1233
|
+
"`supersedes` back on that one (both sides, decision-guide.md), is what says when "
|
|
1234
|
+
"the delegation was revoked. Until it is there the mandate reads as delegating "
|
|
1235
|
+
"right up to its `expires`. Recording a supersession is the one edit an applied "
|
|
1236
|
+
"decision allows")
|
|
1180
1237
|
continue
|
|
1181
1238
|
if not ref.startswith("DEC-"):
|
|
1182
1239
|
continue
|
|
@@ -1188,19 +1245,26 @@ def mandate_accept(c: Corpus, r: Result) -> None:
|
|
|
1188
1245
|
r.fail("mandate-accept", did, f"`accepted_by: {ref}` is not a `type: mandate` decision — only a mandate "
|
|
1189
1246
|
f"delegates acceptance")
|
|
1190
1247
|
continue
|
|
1191
|
-
if str(target.get("status") or "") not in
|
|
1192
|
-
r.fail("mandate-accept", did, f"`accepted_by: {ref}` is `{target.get('status')}
|
|
1193
|
-
f"
|
|
1248
|
+
if str(target.get("status") or "") not in MANDATE_STOOD:
|
|
1249
|
+
r.fail("mandate-accept", did, f"`accepted_by: {ref}` is `{target.get('status')}` — nothing was ever "
|
|
1250
|
+
f"delegated. A mandate delegates from `accepted` onward, and a "
|
|
1251
|
+
f"`superseded` one still stands for what was taken before it ended")
|
|
1194
1252
|
continue
|
|
1195
1253
|
params = target.get("mandate") if isinstance(target.get("mandate"), dict) else {}
|
|
1196
1254
|
expires = _dec_date(c, {"date": params.get("expires")})
|
|
1255
|
+
revoked = _revoked_on(c, target, by_id)
|
|
1197
1256
|
when = _dec_date(c, dec)
|
|
1257
|
+
# Whichever end came first is the one that counts.
|
|
1258
|
+
limit, ended, tail = expires, "expired on", "the delegation had lapsed"
|
|
1259
|
+
if revoked and (expires is None or revoked < expires):
|
|
1260
|
+
limit, ended, tail = (revoked, "was superseded on",
|
|
1261
|
+
"the delegation was revoked then, whatever its `expires` still says")
|
|
1198
1262
|
if when is None:
|
|
1199
1263
|
r.fail("mandate-accept", did, f"is accepted under `{ref}` but no date says when — `date:` in its "
|
|
1200
|
-
f"frontmatter is what the
|
|
1201
|
-
elif
|
|
1202
|
-
r.fail("mandate-accept", did, f"was taken on {when.isoformat()}, after `{ref}`
|
|
1203
|
-
f"{
|
|
1264
|
+
f"frontmatter is what the mandate's window is checked against")
|
|
1265
|
+
elif limit and when > limit:
|
|
1266
|
+
r.fail("mandate-accept", did, f"was taken on {when.isoformat()}, after `{ref}` {ended} "
|
|
1267
|
+
f"{limit.isoformat()} — {tail}")
|
|
1204
1268
|
|
|
1205
1269
|
|
|
1206
1270
|
def defect_root_cause(c: Corpus, r: Result) -> None: # was V20
|
|
@@ -1302,18 +1366,62 @@ PRUNE_DIRS = frozenset({
|
|
|
1302
1366
|
})
|
|
1303
1367
|
|
|
1304
1368
|
|
|
1369
|
+
_IGNORED: dict[Path, frozenset[str]] = {}
|
|
1370
|
+
|
|
1371
|
+
|
|
1372
|
+
def _git_ignored(root: Path) -> frozenset[str]:
|
|
1373
|
+
"""What git ignores in this tree, repo-relative posix — a whole ignored directory as `name/`.
|
|
1374
|
+
|
|
1375
|
+
ONE call for the whole run. `git check-ignore` per folder inside `os.walk` is one subprocess per
|
|
1376
|
+
folder, and on a big tree that costs more than the walk it is protecting.
|
|
1377
|
+
|
|
1378
|
+
Git answers rather than a hand-written parser, for the reason `_ignore_rule` sets out: nested
|
|
1379
|
+
`.gitignore` files, negation with `!`, and `core.excludesFile` are exactly where a parser of this
|
|
1380
|
+
one repo is wrong. `--directory` collapses a wholly-ignored folder into a single entry, which is
|
|
1381
|
+
the granularity the walker prunes at — and a folder holding TRACKED files is never collapsed, so
|
|
1382
|
+
corpus that is in git cannot be pruned away by this.
|
|
1383
|
+
|
|
1384
|
+
Outside a repo, or with no git, this is empty and PRUNE_DIRS carries the walk alone. That is why
|
|
1385
|
+
PRUNE_DIRS stays: it is the fallback, and a `node_modules/` nobody remembered to ignore still has
|
|
1386
|
+
to be pruned — a dangling symlink in one took a whole run down once.
|
|
1387
|
+
"""
|
|
1388
|
+
got = _IGNORED.get(root)
|
|
1389
|
+
if got is None:
|
|
1390
|
+
out = git(root, "ls-files", "--others", "--ignored", "--exclude-standard", "--directory", "-z")
|
|
1391
|
+
got = frozenset(x for x in (out or "").split("\0") if x)
|
|
1392
|
+
_IGNORED[root] = got
|
|
1393
|
+
return got
|
|
1394
|
+
|
|
1395
|
+
|
|
1305
1396
|
def _walk_corpus(root: Path, suffixes: tuple[str, ...]) -> list[Path]:
|
|
1306
|
-
"""Every file under `root` with one of `suffixes`, sorted, pruning PRUNE_DIRS
|
|
1397
|
+
"""Every file under `root` with one of `suffixes`, sorted, pruning PRUNE_DIRS and what git ignores.
|
|
1398
|
+
|
|
1399
|
+
Ignored material is not this product's corpus: it is not in the clone, nobody reviews it, and
|
|
1400
|
+
nothing in it can be repaired by the reader of a finding. A vendored upstream checkout under
|
|
1401
|
+
`.temp/` produced 172 `cites-resolve` findings in one repo, every one of them about somebody
|
|
1402
|
+
else's source tree.
|
|
1307
1403
|
|
|
1308
1404
|
Sorted because determinism is this script's contract: two runs over the same tree MUST report the
|
|
1309
1405
|
same thing in the same order.
|
|
1310
1406
|
"""
|
|
1407
|
+
ignored = _git_ignored(root)
|
|
1408
|
+
|
|
1409
|
+
def rel(path: Path) -> str:
|
|
1410
|
+
try:
|
|
1411
|
+
return path.relative_to(root).as_posix()
|
|
1412
|
+
except ValueError: # a walk that left the tree — treat it as unignored and let PRUNE_DIRS rule
|
|
1413
|
+
return ""
|
|
1414
|
+
|
|
1311
1415
|
out: list[Path] = []
|
|
1312
1416
|
for dirpath, dirnames, filenames in os.walk(root, onerror=lambda _e: None):
|
|
1313
|
-
|
|
1417
|
+
here = Path(dirpath)
|
|
1418
|
+
dirnames[:] = sorted(d for d in dirnames
|
|
1419
|
+
if d not in PRUNE_DIRS and f"{rel(here / d)}/" not in ignored)
|
|
1314
1420
|
for name in filenames:
|
|
1315
|
-
|
|
1316
|
-
|
|
1421
|
+
# An ignored FILE inside a folder that is otherwise corpus: git lists it on its own,
|
|
1422
|
+
# because `--directory` only collapses folders that are ignored whole.
|
|
1423
|
+
if name.endswith(suffixes) and rel(here / name) not in ignored:
|
|
1424
|
+
out.append(here / name)
|
|
1317
1425
|
return sorted(out)
|
|
1318
1426
|
|
|
1319
1427
|
|