@runecraft/grimoire 1.0.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/LICENSE +21 -0
- package/README.md +21 -0
- package/catalog.json +9 -0
- package/dist/grimoire.js +1758 -0
- package/package.json +54 -0
- package/references/definition-of-done.md +67 -0
- package/references/testing-patterns.md +260 -0
- package/skills/code-review-and-quality/README.md +13 -0
- package/skills/code-review-and-quality/SKILL.md +389 -0
- package/skills/code-simplification/README.md +13 -0
- package/skills/code-simplification/SKILL.md +338 -0
- package/skills/debugging-and-error-recovery/README.md +13 -0
- package/skills/debugging-and-error-recovery/SKILL.md +343 -0
- package/skills/debugging-and-error-recovery/scripts/__pycache__/triage_state.cpython-314.pyc +0 -0
- package/skills/debugging-and-error-recovery/scripts/triage_state.py +206 -0
- package/skills/deprecation-and-migration/README.md +13 -0
- package/skills/deprecation-and-migration/SKILL.md +248 -0
- package/skills/deprecation-and-migration/scripts/__pycache__/migration_tracker.cpython-314.pyc +0 -0
- package/skills/deprecation-and-migration/scripts/migration_tracker.py +237 -0
- package/skills/doubt-driven-development/README.md +13 -0
- package/skills/doubt-driven-development/SKILL.md +251 -0
- package/skills/git-commit-learning/.skill-meta.json +14 -0
- package/skills/git-commit-learning/README.md +205 -0
- package/skills/git-commit-learning/SKILL.md +435 -0
- package/skills/git-commit-learning/references/commit-patterns.md +595 -0
- package/skills/git-worktree/README.md +13 -0
- package/skills/git-worktree/SKILL.md +220 -0
- package/skills/idea-refine/README.md +13 -0
- package/skills/idea-refine/SKILL.md +186 -0
- package/skills/interview-me/README.md +13 -0
- package/skills/interview-me/SKILL.md +233 -0
- package/skills/linkedin-audit/SKILL.md +98 -0
- package/skills/linkedin-audit/references/dashboard-spec.md +43 -0
- package/skills/memory-management/README.md +13 -0
- package/skills/memory-management/SKILL.md +198 -0
- package/skills/security-and-hardening/README.md +13 -0
- package/skills/security-and-hardening/SKILL.md +472 -0
- package/skills/shipping-and-launch/README.md +13 -0
- package/skills/shipping-and-launch/SKILL.md +317 -0
- package/skills/skill-forge/README.md +153 -0
- package/skills/skill-forge/SKILL.md +291 -0
- package/skills/skill-forge/assets/SKILL.template.md +73 -0
- package/skills/skill-forge/references/authoring-patterns.md +249 -0
- package/skills/skill-forge/references/description-optimization.md +171 -0
- package/skills/skill-forge/references/output-evaluation.md +276 -0
- package/skills/skill-forge/references/scripts-guide.md +232 -0
- package/skills/skill-forge/references/spec.md +175 -0
- package/skills/skill-forge/scripts/validate.py +536 -0
- package/skills/spec-driven/.skill-meta.json +14 -0
- package/skills/spec-driven/README.md +335 -0
- package/skills/spec-driven/SKILL.md +174 -0
- package/skills/spec-driven/references/code-analysis.md +98 -0
- package/skills/spec-driven/references/coding-principles.md +56 -0
- package/skills/spec-driven/references/context-limits.md +31 -0
- package/skills/spec-driven/references/design.md +199 -0
- package/skills/spec-driven/references/discuss.md +136 -0
- package/skills/spec-driven/references/implement.md +425 -0
- package/skills/spec-driven/references/lessons.md +113 -0
- package/skills/spec-driven/references/memory.md +126 -0
- package/skills/spec-driven/references/specify.md +210 -0
- package/skills/spec-driven/references/sub-agents.md +96 -0
- package/skills/spec-driven/references/tasks.md +484 -0
- package/skills/spec-driven/references/validate.md +350 -0
- package/skills/spec-driven/scripts/__pycache__/lessons.cpython-314.pyc +0 -0
- package/skills/spec-driven/scripts/lessons.py +370 -0
- package/skills/spec-loop/README.md +36 -0
- package/skills/spec-loop/SKILL.md +61 -0
- package/skills/test-driven-development/README.md +13 -0
- package/skills/test-driven-development/SKILL.md +388 -0
- package/skills/typescript-patterns/README.md +13 -0
- package/skills/typescript-patterns/SKILL.md +346 -0
- package/skills/using-agent-skills/README.md +13 -0
- package/skills/using-agent-skills/SKILL.md +187 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
lessons.py — deterministic bookkeeping for the spec-driven lessons layer.
|
|
4
|
+
|
|
5
|
+
The LLM supplies judgment (which failure happened, how to phrase the lesson, what
|
|
6
|
+
signal grounds it). This script owns everything mechanical: IDs, distinct-feature
|
|
7
|
+
recurrence counting, candidate->confirmed promotion, pruning, demotion, and
|
|
8
|
+
rendering the human/agent-readable playbook. Bookkeeping by hand is exactly what
|
|
9
|
+
rots a lessons file, so it lives here, not in a prompt.
|
|
10
|
+
|
|
11
|
+
Canonical state: .specs/lessons.json (machine-owned — do NOT hand-edit)
|
|
12
|
+
Rendered view: .specs/LESSONS.md (regenerated on every write)
|
|
13
|
+
|
|
14
|
+
Pure standard library. No dependencies. Run from the project root (the dir that
|
|
15
|
+
contains .specs), or pass --root.
|
|
16
|
+
|
|
17
|
+
Commands:
|
|
18
|
+
add Record a grounded lesson from a verification signal.
|
|
19
|
+
list Print lessons (default: confirmed) for loading at Specify/Design.
|
|
20
|
+
penalize Mark a confirmed lesson as having failed when applied (-> quarantine).
|
|
21
|
+
prune Drop stale uncorroborated candidates (also runs automatically on add/list).
|
|
22
|
+
status Print counts (used by the self-check in validate.md).
|
|
23
|
+
init Create empty store + rendered file.
|
|
24
|
+
|
|
25
|
+
Exit codes: 0 ok, 2 usage/validation error (e.g. missing grounding).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import argparse
|
|
29
|
+
import datetime as _dt
|
|
30
|
+
import json
|
|
31
|
+
import os
|
|
32
|
+
import re
|
|
33
|
+
import sys
|
|
34
|
+
|
|
35
|
+
STORE_REL = os.path.join(".specs", "lessons.json")
|
|
36
|
+
RENDER_REL = os.path.join(".specs", "LESSONS.md")
|
|
37
|
+
|
|
38
|
+
SIGNALS = {
|
|
39
|
+
"ac_gap": "Acceptance criterion not covered / failed",
|
|
40
|
+
"surviving_mutant": "Discrimination sensor mutant survived (weak test)",
|
|
41
|
+
"spec_precision_gap": "Spec did not define a precise outcome",
|
|
42
|
+
"spec_deviation": "Implementation diverged from spec/design (SPEC_DEVIATION)",
|
|
43
|
+
"gate_fail": "Build-level gate check failed",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
DEFAULTS = {"promote_threshold": 2, "window_days": 45, "quarantine_threshold": 2}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _now():
|
|
50
|
+
return _dt.datetime.now(_dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _parse_date(s):
|
|
54
|
+
try:
|
|
55
|
+
return _dt.datetime.strptime(s, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=_dt.timezone.utc)
|
|
56
|
+
except Exception:
|
|
57
|
+
return _dt.datetime.now(_dt.timezone.utc)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _store_path(root):
|
|
61
|
+
return os.path.join(root, STORE_REL)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _render_path(root):
|
|
65
|
+
return os.path.join(root, RENDER_REL)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _load(root):
|
|
69
|
+
path = _store_path(root)
|
|
70
|
+
if not os.path.exists(path):
|
|
71
|
+
return {
|
|
72
|
+
"schema": 1,
|
|
73
|
+
"promote_threshold": DEFAULTS["promote_threshold"],
|
|
74
|
+
"window_days": DEFAULTS["window_days"],
|
|
75
|
+
"quarantine_threshold": DEFAULTS["quarantine_threshold"],
|
|
76
|
+
"next_id": 1,
|
|
77
|
+
"lessons": [],
|
|
78
|
+
}
|
|
79
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
80
|
+
data = json.load(f)
|
|
81
|
+
for k, v in DEFAULTS.items():
|
|
82
|
+
data.setdefault(k, v)
|
|
83
|
+
data.setdefault("schema", 1)
|
|
84
|
+
data.setdefault("next_id", 1)
|
|
85
|
+
data.setdefault("lessons", [])
|
|
86
|
+
return data
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _save(root, data):
|
|
90
|
+
os.makedirs(os.path.join(root, ".specs"), exist_ok=True)
|
|
91
|
+
with open(_store_path(root), "w", encoding="utf-8") as f:
|
|
92
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
93
|
+
f.write("\n")
|
|
94
|
+
_render(root, data)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _norm(text):
|
|
98
|
+
"""Normalized dedup key: lowercase, strip punctuation, collapse whitespace.
|
|
99
|
+
Exact-after-normalization only — no semantic matching (stdlib-only limitation).
|
|
100
|
+
Phrase lessons tersely and canonically so recurrences actually merge."""
|
|
101
|
+
t = text.lower().strip()
|
|
102
|
+
t = re.sub(r"[^a-z0-9\s]", " ", t)
|
|
103
|
+
t = re.sub(r"\s+", " ", t).strip()
|
|
104
|
+
return t
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _key(signal, text):
|
|
108
|
+
return signal + "::" + _norm(text)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _auto_prune(data):
|
|
112
|
+
"""Drop candidates that never recurred within the window. Mutates data."""
|
|
113
|
+
threshold = data["promote_threshold"]
|
|
114
|
+
window = data["window_days"]
|
|
115
|
+
now = _dt.datetime.now(_dt.timezone.utc)
|
|
116
|
+
kept = []
|
|
117
|
+
dropped = []
|
|
118
|
+
for l in data["lessons"]:
|
|
119
|
+
if l["status"] == "candidate" and l["recurrence"] < threshold:
|
|
120
|
+
age_days = (now - _parse_date(l.get("last_seen", l.get("created", _now())))).days
|
|
121
|
+
if age_days > window:
|
|
122
|
+
dropped.append(l["id"])
|
|
123
|
+
continue
|
|
124
|
+
kept.append(l)
|
|
125
|
+
data["lessons"] = kept
|
|
126
|
+
return dropped
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _find(data, signal, text):
|
|
130
|
+
k = _key(signal, text)
|
|
131
|
+
for l in data["lessons"]:
|
|
132
|
+
if l.get("key") == k:
|
|
133
|
+
return l
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _render(root, data):
|
|
138
|
+
lines = []
|
|
139
|
+
lines.append("# LESSONS — auto-maintained by scripts/lessons.py")
|
|
140
|
+
lines.append("")
|
|
141
|
+
lines.append("> Machine-owned. Do NOT hand-edit. Changes are overwritten on the next `lessons.py` write.")
|
|
142
|
+
lines.append("> Canonical state lives in `.specs/lessons.json`. Edit lessons only via the script.")
|
|
143
|
+
lines.append(f"> promote_threshold={data['promote_threshold']} distinct features · window_days={data['window_days']} · quarantine_threshold={data['quarantine_threshold']}")
|
|
144
|
+
lines.append("")
|
|
145
|
+
|
|
146
|
+
by_status = {"confirmed": [], "candidate": [], "quarantined": []}
|
|
147
|
+
for l in data["lessons"]:
|
|
148
|
+
by_status.get(l["status"], by_status["candidate"]).append(l)
|
|
149
|
+
|
|
150
|
+
def block(title, items, note):
|
|
151
|
+
out = [f"## {title}", ""]
|
|
152
|
+
if note:
|
|
153
|
+
out.append(note)
|
|
154
|
+
out.append("")
|
|
155
|
+
if not items:
|
|
156
|
+
out.append("_none_")
|
|
157
|
+
out.append("")
|
|
158
|
+
return out
|
|
159
|
+
for l in sorted(items, key=lambda x: x["id"]):
|
|
160
|
+
scope = f" · scope: `{l['scope']}`" if l.get("scope") else ""
|
|
161
|
+
out.append(f"### {l['id']} — {l['text']}")
|
|
162
|
+
out.append(
|
|
163
|
+
f"- signal: `{l['signal']}` · recurrence: {l['recurrence']} feature(s){scope} · harmful: {l.get('harmful', 0)}"
|
|
164
|
+
)
|
|
165
|
+
feats = ", ".join(l.get("features", [])) or "—"
|
|
166
|
+
out.append(f"- features: {feats}")
|
|
167
|
+
ev = l.get("evidence", [])
|
|
168
|
+
if ev:
|
|
169
|
+
out.append(f"- evidence: {ev[0]}" + (f" (+{len(ev) - 1} more)" if len(ev) > 1 else ""))
|
|
170
|
+
out.append(f"- last seen: {l.get('last_seen', '—')}")
|
|
171
|
+
out.append("")
|
|
172
|
+
return out
|
|
173
|
+
|
|
174
|
+
lines += block(
|
|
175
|
+
"Confirmed (load these at Specify/Design)",
|
|
176
|
+
by_status["confirmed"],
|
|
177
|
+
"Corroborated across multiple features. Safe to apply as guidance.",
|
|
178
|
+
)
|
|
179
|
+
lines += block(
|
|
180
|
+
"Candidates (under observation — do NOT load as guidance yet)",
|
|
181
|
+
by_status["candidate"],
|
|
182
|
+
"Seen once or not yet corroborated. Tracked, not trusted.",
|
|
183
|
+
)
|
|
184
|
+
lines += block(
|
|
185
|
+
"Quarantined (failed when applied — ignore)",
|
|
186
|
+
by_status["quarantined"],
|
|
187
|
+
"A confirmed lesson that recurred alongside failure. Kept for the maintainer to review.",
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
with open(_render_path(root), "w", encoding="utf-8") as f:
|
|
191
|
+
f.write("\n".join(lines).rstrip() + "\n")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# ----------------------------- commands -----------------------------
|
|
195
|
+
|
|
196
|
+
def cmd_init(root, args):
|
|
197
|
+
data = _load(root)
|
|
198
|
+
_save(root, data)
|
|
199
|
+
print(f"Initialized lessons store at {_store_path(root)} and {_render_path(root)}")
|
|
200
|
+
return 0
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def cmd_add(root, args):
|
|
204
|
+
signal = args.signal
|
|
205
|
+
source = (args.source or "").strip()
|
|
206
|
+
text = (args.text or "").strip()
|
|
207
|
+
feature = (args.feature or "").strip()
|
|
208
|
+
|
|
209
|
+
# Grounding is enforced here, deterministically — not left to the prompt.
|
|
210
|
+
if signal not in SIGNALS:
|
|
211
|
+
print(f"ERROR: --signal must be one of {sorted(SIGNALS)}", file=sys.stderr)
|
|
212
|
+
return 2
|
|
213
|
+
if not feature:
|
|
214
|
+
print("ERROR: --feature is required (the feature the signal came from).", file=sys.stderr)
|
|
215
|
+
return 2
|
|
216
|
+
if not source:
|
|
217
|
+
print("ERROR: --source is required (file:line / AC id / mutant id / SPEC_DEVIATION ref).", file=sys.stderr)
|
|
218
|
+
print(" A lesson with no grounding in validation.md is an opinion, not a lesson. Refused.", file=sys.stderr)
|
|
219
|
+
return 2
|
|
220
|
+
if len(text) < 12:
|
|
221
|
+
print("ERROR: --text too short. State the actionable lesson in one terse sentence.", file=sys.stderr)
|
|
222
|
+
return 2
|
|
223
|
+
|
|
224
|
+
data = _load(root)
|
|
225
|
+
_auto_prune(data)
|
|
226
|
+
existing = _find(data, signal, text)
|
|
227
|
+
now = _now()
|
|
228
|
+
|
|
229
|
+
if existing:
|
|
230
|
+
if feature not in existing["features"]:
|
|
231
|
+
existing["features"].append(feature)
|
|
232
|
+
existing["recurrence"] = len(existing["features"])
|
|
233
|
+
existing["last_seen"] = now
|
|
234
|
+
ev = source if not args.scope else f"{source} ({args.scope})"
|
|
235
|
+
if ev not in existing["evidence"]:
|
|
236
|
+
existing["evidence"].append(ev)
|
|
237
|
+
promoted = False
|
|
238
|
+
if existing["status"] == "candidate" and existing["recurrence"] >= data["promote_threshold"]:
|
|
239
|
+
existing["status"] = "confirmed"
|
|
240
|
+
promoted = True
|
|
241
|
+
_save(root, data)
|
|
242
|
+
msg = f"UPDATED {existing['id']} (recurrence={existing['recurrence']}, status={existing['status']})"
|
|
243
|
+
if promoted:
|
|
244
|
+
msg += " — PROMOTED to confirmed"
|
|
245
|
+
print(msg)
|
|
246
|
+
else:
|
|
247
|
+
lid = f"L-{data['next_id']:03d}"
|
|
248
|
+
data["next_id"] += 1
|
|
249
|
+
data["lessons"].append(
|
|
250
|
+
{
|
|
251
|
+
"id": lid,
|
|
252
|
+
"key": _key(signal, text),
|
|
253
|
+
"text": text,
|
|
254
|
+
"signal": signal,
|
|
255
|
+
"scope": (args.scope or "").strip(),
|
|
256
|
+
"status": "candidate",
|
|
257
|
+
"features": [feature],
|
|
258
|
+
"recurrence": 1,
|
|
259
|
+
"harmful": 0,
|
|
260
|
+
"evidence": [source if not args.scope else f"{source} ({args.scope})"],
|
|
261
|
+
"created": now,
|
|
262
|
+
"last_seen": now,
|
|
263
|
+
}
|
|
264
|
+
)
|
|
265
|
+
_save(root, data)
|
|
266
|
+
print(f"ADDED {lid} (status=candidate, recurrence=1)")
|
|
267
|
+
return 0
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def cmd_penalize(root, args):
|
|
271
|
+
data = _load(root)
|
|
272
|
+
target = None
|
|
273
|
+
for l in data["lessons"]:
|
|
274
|
+
if l["id"].lower() == args.id.lower():
|
|
275
|
+
target = l
|
|
276
|
+
break
|
|
277
|
+
if not target:
|
|
278
|
+
print(f"ERROR: no lesson with id {args.id}", file=sys.stderr)
|
|
279
|
+
return 2
|
|
280
|
+
target["harmful"] = target.get("harmful", 0) + 1
|
|
281
|
+
target["last_seen"] = _now()
|
|
282
|
+
if target["harmful"] >= data["quarantine_threshold"]:
|
|
283
|
+
target["status"] = "quarantined"
|
|
284
|
+
_save(root, data)
|
|
285
|
+
print(f"PENALIZED {target['id']} (harmful={target['harmful']}, status={target['status']})")
|
|
286
|
+
return 0
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def cmd_list(root, args):
|
|
290
|
+
data = _load(root)
|
|
291
|
+
if _auto_prune(data):
|
|
292
|
+
_save(root, data)
|
|
293
|
+
want = args.status
|
|
294
|
+
q = (args.query or "").lower().strip()
|
|
295
|
+
scope = (args.scope or "").lower().strip()
|
|
296
|
+
rows = []
|
|
297
|
+
for l in data["lessons"]:
|
|
298
|
+
if want != "all" and l["status"] != want:
|
|
299
|
+
continue
|
|
300
|
+
if q and q not in l["text"].lower():
|
|
301
|
+
continue
|
|
302
|
+
if scope and scope not in (l.get("scope", "").lower()):
|
|
303
|
+
continue
|
|
304
|
+
rows.append(l)
|
|
305
|
+
if not rows:
|
|
306
|
+
print(f"(no {want} lessons" + (f" matching '{q or scope}'" if (q or scope) else "") + ")")
|
|
307
|
+
return 0
|
|
308
|
+
for l in sorted(rows, key=lambda x: x["id"]):
|
|
309
|
+
sc = f" [scope:{l['scope']}]" if l.get("scope") else ""
|
|
310
|
+
print(f"{l['id']} ({l['status']}, x{l['recurrence']}){sc}: {l['text']}")
|
|
311
|
+
return 0
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def cmd_prune(root, args):
|
|
315
|
+
data = _load(root)
|
|
316
|
+
dropped = _auto_prune(data)
|
|
317
|
+
_save(root, data)
|
|
318
|
+
print(f"Pruned {len(dropped)} stale candidate(s): {', '.join(dropped) if dropped else '—'}")
|
|
319
|
+
return 0
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def cmd_status(root, args):
|
|
323
|
+
data = _load(root)
|
|
324
|
+
counts = {"confirmed": 0, "candidate": 0, "quarantined": 0}
|
|
325
|
+
for l in data["lessons"]:
|
|
326
|
+
counts[l["status"]] = counts.get(l["status"], 0) + 1
|
|
327
|
+
total = len(data["lessons"])
|
|
328
|
+
print(f"lessons: {total} total | confirmed={counts['confirmed']} candidate={counts['candidate']} quarantined={counts['quarantined']}")
|
|
329
|
+
return 0
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def main(argv=None):
|
|
333
|
+
p = argparse.ArgumentParser(prog="lessons.py", description="Deterministic lessons bookkeeping for spec-driven.")
|
|
334
|
+
p.add_argument("--root", default=".", help="Project root containing .specs/ (default: current dir)")
|
|
335
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
336
|
+
|
|
337
|
+
sp = sub.add_parser("init", help="Create empty store + rendered file")
|
|
338
|
+
sp.set_defaults(fn=cmd_init)
|
|
339
|
+
|
|
340
|
+
sp = sub.add_parser("add", help="Record a grounded lesson")
|
|
341
|
+
sp.add_argument("--feature", required=True)
|
|
342
|
+
sp.add_argument("--signal", required=True, choices=sorted(SIGNALS))
|
|
343
|
+
sp.add_argument("--source", required=True, help="file:line / AC id / mutant id / SPEC_DEVIATION ref")
|
|
344
|
+
sp.add_argument("--text", required=True, help="One terse, actionable sentence")
|
|
345
|
+
sp.add_argument("--scope", default="", help="Optional: path/layer/tag for retrieval filtering")
|
|
346
|
+
sp.set_defaults(fn=cmd_add)
|
|
347
|
+
|
|
348
|
+
sp = sub.add_parser("penalize", help="Mark a confirmed lesson as failed-when-applied")
|
|
349
|
+
sp.add_argument("--id", required=True)
|
|
350
|
+
sp.set_defaults(fn=cmd_penalize)
|
|
351
|
+
|
|
352
|
+
sp = sub.add_parser("list", help="Print lessons for loading")
|
|
353
|
+
sp.add_argument("--status", default="confirmed", choices=["confirmed", "candidate", "quarantined", "all"])
|
|
354
|
+
sp.add_argument("--query", default="", help="Substring filter on lesson text")
|
|
355
|
+
sp.add_argument("--scope", default="", help="Substring filter on scope")
|
|
356
|
+
sp.set_defaults(fn=cmd_list)
|
|
357
|
+
|
|
358
|
+
sp = sub.add_parser("prune", help="Drop stale uncorroborated candidates")
|
|
359
|
+
sp.set_defaults(fn=cmd_prune)
|
|
360
|
+
|
|
361
|
+
sp = sub.add_parser("status", help="Print counts")
|
|
362
|
+
sp.set_defaults(fn=cmd_status)
|
|
363
|
+
|
|
364
|
+
args = p.parse_args(argv)
|
|
365
|
+
root = os.path.abspath(args.root)
|
|
366
|
+
return args.fn(root, args)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
if __name__ == "__main__":
|
|
370
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# spec-loop
|
|
2
|
+
|
|
3
|
+
Milestone-loop runner: drives every `.specs/` artifact to completion — ROADMAP → milestones → tasks → verification gates → atomic commits → STATE.md.
|
|
4
|
+
|
|
5
|
+
| Field | Value |
|
|
6
|
+
|-------|-------|
|
|
7
|
+
| Version | 1.0.0 |
|
|
8
|
+
| Trigger | "execute the specs", "run the plan", "loop the milestones" |
|
|
9
|
+
| PT trigger | "executar as specs", "rodar o plano", "começar a executar", "siga o roadmap" |
|
|
10
|
+
|
|
11
|
+
**Use when** a tlc-spec-driven project has pending `tasks.md` items and you want autonomous execution, milestone by milestone, with verification gates and atomic commits.
|
|
12
|
+
|
|
13
|
+
**Do not use for** creating the plan itself — run [`spec-driven`](../spec-driven/README.md) first to produce the `.specs/` artifacts (ROADMAP.md, tasks.md, design.md, context.md), then hand execution to this skill.
|
|
14
|
+
|
|
15
|
+
## The loop
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
ROADMAP.md → pending milestones (M0..Mn)
|
|
19
|
+
→ feature tasks.md → atomic task
|
|
20
|
+
→ execute → verify ("Verificar:" criteria) → atomic commit → STATE.md
|
|
21
|
+
→ milestone gate (exit criteria) → next milestone
|
|
22
|
+
→ final spec.md acceptance (Success Criteria) → project done
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
One task at a time, in tasks.md order — never skip a "Depends on" edge. Verification runs for real: red means fix or stop, never advance. STATE.md is updated after every task.
|
|
26
|
+
|
|
27
|
+
## States
|
|
28
|
+
|
|
29
|
+
- `⬜ planned` → `▶️ in progress` → `✅ done` | `🛑 blocked` (reason + evidence)
|
|
30
|
+
- Milestone done only with green exit criteria; project done only with the spec.md final acceptance.
|
|
31
|
+
|
|
32
|
+
## Resume
|
|
33
|
+
|
|
34
|
+
Interrupted? Read STATE.md + recent commits → continue from the first non-done task. Never re-run tasks already done.
|
|
35
|
+
|
|
36
|
+
See [SKILL.md](SKILL.md) for the full process (rules, gates, escalation, delegation).
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-loop
|
|
3
|
+
description: |
|
|
4
|
+
Drive the execution of every .specs/ artifact to completion — milestone by
|
|
5
|
+
milestone, task by task, with verification gates, atomic commits and STATE.md
|
|
6
|
+
progress tracking. Use when user says "execute the specs", "run the plan",
|
|
7
|
+
"loop the milestones", "executar as specs", "rodar o plano", "começar a
|
|
8
|
+
executar", "siga o roadmap", or when a tlc-spec-driven project has pending
|
|
9
|
+
tasks.md items and the user wants autonomous execution.
|
|
10
|
+
license: CC-BY-4.0
|
|
11
|
+
metadata:
|
|
12
|
+
author: runecraft
|
|
13
|
+
version: 1.0.0
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Spec Loop
|
|
17
|
+
|
|
18
|
+
Executa todos os artefatos `.specs/` até a conclusão: ROADMAP → milestones → tasks → verificação → gates → commits atômicos → STATE.md.
|
|
19
|
+
|
|
20
|
+
## Loop principal
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
ROADMAP.md → milestones pendentes (M0..Mn)
|
|
24
|
+
→ tasks.md da feature → tarefa atômica
|
|
25
|
+
→ executar → verificar (critério "Verificar:") → commit atômico → STATE.md
|
|
26
|
+
→ gate do milestone (exit criteria) → próximo milestone
|
|
27
|
+
→ acceptance final do spec.md (Success Criteria) → projeto done
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Regras (ordem de prioridade)
|
|
31
|
+
|
|
32
|
+
1. **Ler antes de tocar**: ROADMAP.md (milestones), tasks.md (tarefas e dependências), design.md (tabelas de mapeamento são fonte da verdade), context.md (decisões AD-* são fechadas).
|
|
33
|
+
2. **Uma tarefa por vez**, na ordem do tasks.md; nunca pule uma dependência (campo "Depends on").
|
|
34
|
+
3. **Verificar antes de avançar**: toda tarefa tem critérios `**Verificar:**` — rode-os de fato; verificação vermelha = corrigir ou parar, nunca avançar.
|
|
35
|
+
4. **Commits atômicos** por tarefa concluída; mensagem com o REQ-ID/ID da tarefa quando existir.
|
|
36
|
+
5. **STATE.md atualizado após cada tarefa** (done/blocked + riscos novos observados).
|
|
37
|
+
6. **Safety valve**: se uma tarefa revelar >5 passos inesperados ou novas dependências → PARE e estenda tasks.md antes de continuar.
|
|
38
|
+
7. **Escalação**: blocker real → pare e reporte ao usuário com evidência; nenhuma decisão AD-* muda silenciosamente.
|
|
39
|
+
8. **Gates de milestone**: exit criteria do ROADMAP + grep guards (design.md §8) verdes antes de marcar o milestone como done.
|
|
40
|
+
9. **Escopo cirúrgico**: só arquivos da tarefa; nunca edite clones de referência, repos externos ou histórico git.
|
|
41
|
+
10. **Delegação opcional**: fatias mecânicas extensas → fighter/ranger; review de milestones → cleric. O loop permanece no agente principal.
|
|
42
|
+
|
|
43
|
+
## Estados
|
|
44
|
+
|
|
45
|
+
- `⬜ planned` → `▶️ in progress` → `✅ done` | `🛑 blocked` (motivo + evidência)
|
|
46
|
+
- Milestone done somente com exit criteria verdes.
|
|
47
|
+
- Projeto done somente com a acceptance final do spec.md.
|
|
48
|
+
|
|
49
|
+
## Retomada
|
|
50
|
+
|
|
51
|
+
Se interrompido: leia STATE.md + últimos commits → continue da primeira tarefa não-done. Nunca re-execute tarefas já done.
|
|
52
|
+
|
|
53
|
+
## Exemplo (Squad)
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
ROADMAP: M0 → M1 → {M2, M3} → M4 · M5 ∥ M4
|
|
57
|
+
1. M0: T-M0-01 (repo init) → verificar git status → commit → STATE.md
|
|
58
|
+
2. M0: T-M0-02..05 → gate M0 (exit criteria) → M0 ✅
|
|
59
|
+
3. M1: T-M1-01..12 (sweep) → full suite + grep guards → M1 ✅
|
|
60
|
+
...até a acceptance final de spec.md
|
|
61
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# test-driven-development
|
|
2
|
+
|
|
3
|
+
Drive development with tests. Fail first, make them pass, then refactor.
|
|
4
|
+
|
|
5
|
+
| Field | Value |
|
|
6
|
+
|-------|-------|
|
|
7
|
+
| Version | 1.0.0 |
|
|
8
|
+
| Trigger | `/test`, "TDD", "prove it", "80/15/5 pyramid", "Beyonce Rule" |
|
|
9
|
+
| PT trigger | `/teste`, "testes primeiro", "provar com teste" |
|
|
10
|
+
|
|
11
|
+
**Do not use for** production incident triage (use `/debug`) or cosmetic-only changes with no behavioral surface.
|
|
12
|
+
|
|
13
|
+
See [SKILL.md](SKILL.md) for the full process, and [references/testing-patterns.md](../../references/testing-patterns.md) for common testing patterns and the 80/15/5 pyramid.
|