workflow-toolkit 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/.agents/skills/ponytail/SKILL.md +111 -0
  2. package/.agents/skills/ponytail-audit/SKILL.md +37 -0
  3. package/.agents/skills/ponytail-debt/SKILL.md +41 -0
  4. package/.agents/skills/ponytail-gain/SKILL.md +47 -0
  5. package/.agents/skills/ponytail-help/SKILL.md +70 -0
  6. package/.agents/skills/ponytail-review/SKILL.md +52 -0
  7. package/.agents/skills/prompt-review/SKILL.md +60 -0
  8. package/.agents/skills/wtk/SKILL.md +64 -0
  9. package/.agents/skills/wtk/references/artifacts.md +82 -0
  10. package/.agents/skills/wtk/references/evidence.md +68 -0
  11. package/.agents/skills/wtk/references/git.md +75 -0
  12. package/.agents/skills/wtk/references/test-contract.md +76 -0
  13. package/.agents/skills/wtk/references/validation.md +127 -0
  14. package/.agents/skills/wtk-config/SKILL.md +102 -0
  15. package/.agents/skills/wtk-config/assets/agents/claude/deep-reviewer.md +40 -0
  16. package/.agents/skills/wtk-config/assets/agents/claude/designer.md +57 -0
  17. package/.agents/skills/wtk-config/assets/agents/claude/explorer.md +41 -0
  18. package/.agents/skills/wtk-config/assets/agents/claude/implementer.md +57 -0
  19. package/.agents/skills/wtk-config/assets/agents/claude/planner.md +68 -0
  20. package/.agents/skills/wtk-config/assets/agents/claude/verifier.md +80 -0
  21. package/.agents/skills/wtk-config/assets/agents/codex/deep-reviewer.toml +41 -0
  22. package/.agents/skills/wtk-config/assets/agents/codex/designer.toml +55 -0
  23. package/.agents/skills/wtk-config/assets/agents/codex/explorer.toml +35 -0
  24. package/.agents/skills/wtk-config/assets/agents/codex/implementer.toml +52 -0
  25. package/.agents/skills/wtk-config/assets/agents/codex/planner.toml +66 -0
  26. package/.agents/skills/wtk-config/assets/agents/codex/verifier.toml +78 -0
  27. package/.agents/skills/wtk-config/assets/agents/cursor/deep-reviewer.md +38 -0
  28. package/.agents/skills/wtk-config/assets/agents/cursor/designer.md +55 -0
  29. package/.agents/skills/wtk-config/assets/agents/cursor/explorer.md +39 -0
  30. package/.agents/skills/wtk-config/assets/agents/cursor/implementer.md +55 -0
  31. package/.agents/skills/wtk-config/assets/agents/cursor/planner.md +66 -0
  32. package/.agents/skills/wtk-config/assets/agents/cursor/verifier.md +79 -0
  33. package/.agents/skills/wtk-config/scripts/ad-index.py +123 -0
  34. package/.agents/skills/wtk-config/scripts/repository_intelligence.py +671 -0
  35. package/.agents/skills/wtk-config/scripts/workflow_config.py +972 -0
  36. package/.agents/skills/wtk-deep-review/SKILL.md +164 -0
  37. package/.agents/skills/wtk-deep-review/assets/PROMPT.md +56 -0
  38. package/.agents/skills/wtk-deep-review/assets/REVIEW_UI.html +1340 -0
  39. package/.agents/skills/wtk-deep-review/assets/findings.schema.json +113 -0
  40. package/.agents/skills/wtk-deep-review/references/context-pack.md +88 -0
  41. package/.agents/skills/wtk-deep-review/references/orchestration.md +164 -0
  42. package/.agents/skills/wtk-deep-review/references/output-contracts.md +149 -0
  43. package/.agents/skills/wtk-deep-review/references/publish-github.md +83 -0
  44. package/.agents/skills/wtk-deep-review/references/state-and-learnings.md +64 -0
  45. package/.agents/skills/wtk-deep-review/references/subagent-runtimes.md +33 -0
  46. package/.agents/skills/wtk-deep-review/references/taxonomy.md +70 -0
  47. package/.agents/skills/wtk-deep-review/scripts/_common.py +371 -0
  48. package/.agents/skills/wtk-deep-review/scripts/build_jobs.py +561 -0
  49. package/.agents/skills/wtk-deep-review/scripts/build_knowledge.py +349 -0
  50. package/.agents/skills/wtk-deep-review/scripts/build_manifest.py +479 -0
  51. package/.agents/skills/wtk-deep-review/scripts/graft_context.py +160 -0
  52. package/.agents/skills/wtk-deep-review/scripts/graphify_context.py +75 -0
  53. package/.agents/skills/wtk-deep-review/scripts/merge_findings.py +313 -0
  54. package/.agents/skills/wtk-deep-review/scripts/render_html.py +240 -0
  55. package/.agents/skills/wtk-deep-review/scripts/render_review.py +282 -0
  56. package/.agents/skills/wtk-deep-review/scripts/run_jobs.py +436 -0
  57. package/.agents/skills/wtk-deep-review/scripts/token_metrics.py +369 -0
  58. package/.agents/skills/wtk-discover/SKILL.md +270 -0
  59. package/.agents/skills/wtk-discover/references/document-format.md +149 -0
  60. package/.agents/skills/wtk-implement/SKILL.md +95 -0
  61. package/.agents/skills/wtk-implement/references/checklist-format.md +116 -0
  62. package/.agents/skills/wtk-implement/references/screens.md +129 -0
  63. package/.agents/skills/wtk-implement/references/test-policy.md +152 -0
  64. package/.agents/skills/wtk-implement/references/verify.md +297 -0
  65. package/.agents/skills/wtk-knowledge-check/SKILL.md +16 -0
  66. package/.agents/skills/wtk-knowledge-check/scripts/check.ts +502 -0
  67. package/.agents/skills/wtk-knowledge-check/scripts/cli.ts +32 -0
  68. package/.agents/skills/wtk-knowledge-check/scripts/frontmatter.ts +44 -0
  69. package/.agents/skills/wtk-lean/.skill-meta.json +6 -0
  70. package/.agents/skills/wtk-lean/NOTICE.md +18 -0
  71. package/.agents/skills/wtk-lean/SKILL.md +137 -0
  72. package/.agents/skills/wtk-lean/references/build.md +168 -0
  73. package/.agents/skills/wtk-lean/references/checks.md +253 -0
  74. package/.agents/skills/wtk-lean/references/memory.md +156 -0
  75. package/.agents/skills/wtk-lean/references/plan.md +514 -0
  76. package/.agents/skills/wtk-lean/references/verify.md +336 -0
  77. package/.agents/skills/wtk-lean/scripts/check_commit.py +121 -0
  78. package/.agents/skills/wtk-lean/scripts/fixtures/checks.md +98 -0
  79. package/.agents/skills/wtk-lean/scripts/fixtures/plan.md +119 -0
  80. package/.agents/skills/wtk-lean/scripts/fixtures/verification.md +41 -0
  81. package/.agents/skills/wtk-lean/scripts/lessons.py +412 -0
  82. package/.agents/skills/wtk-lean/scripts/selftest.py +401 -0
  83. package/.agents/skills/wtk-lean/scripts/validate_checks.py +417 -0
  84. package/.agents/skills/wtk-lean/scripts/validate_plan.py +580 -0
  85. package/.agents/skills/wtk-lean/scripts/validate_verification.py +357 -0
  86. package/.agents/skills/wtk-plan/SKILL.md +102 -0
  87. package/.agents/skills/wtk-plan/references/document-format.md +222 -0
  88. package/.agents/skills/wtk-qa/SKILL.md +14 -0
  89. package/.agents/skills/wtk-qa-execute/SKILL.md +115 -0
  90. package/.agents/skills/wtk-qa-execute/references/fix-loop.md +23 -0
  91. package/.agents/skills/wtk-qa-execute/references/session-protocol.md +25 -0
  92. package/.agents/skills/wtk-qa-plan/SKILL.md +111 -0
  93. package/.agents/skills/wtk-qa-plan/references/profile.md +35 -0
  94. package/.agents/skills/wtk-ship/SKILL.md +49 -0
  95. package/.agents/skills/wtk-ship/remediation.py +170 -0
  96. package/.agents/skills/wtk-ship/scripts/close_feature.py +83 -0
  97. package/.agents/skills/wtk-ship/scripts/review_convergence.py +373 -0
  98. package/.wtk.toml.example +93 -0
  99. package/AGENTS.md +129 -0
  100. package/NOTICE.md +12 -0
  101. package/README.md +440 -0
  102. package/bin/wtk.js +25 -0
  103. package/docs/toolkit/README.md +56 -0
  104. package/docs/toolkit/decisions.md +61 -0
  105. package/docs/toolkit/guidelines/CONTEXT-BUDGET.md +64 -0
  106. package/docs/toolkit/guidelines/DX.md +67 -0
  107. package/docs/toolkit/guidelines/FRONTEND.md +73 -0
  108. package/docs/toolkit/guidelines/KNOWLEDGE-WIKI.md +128 -0
  109. package/docs/toolkit/guidelines/MODELING.md +134 -0
  110. package/docs/toolkit/guidelines/QA-EXECUTION.md +46 -0
  111. package/docs/toolkit/guidelines/QA-SCENARIOS.md +165 -0
  112. package/docs/toolkit/guidelines/REVIEW-ROUNDS.md +160 -0
  113. package/docs/toolkit/guidelines/SECURITY.md +160 -0
  114. package/docs/toolkit/guidelines/UI-UX.md +117 -0
  115. package/docs/toolkit/guidelines/WORKFLOW-MEMORY.md +73 -0
  116. package/docs/toolkit/guidelines.md +50 -0
  117. package/docs/toolkit/loop.md +77 -0
  118. package/docs/toolkit/purpose.md +51 -0
  119. package/docs/toolkit/repository-intelligence.md +53 -0
  120. package/docs/toolkit/reviews.md +77 -0
  121. package/knowledge/AGENTS.md +316 -0
  122. package/knowledge/raw/README.md +17 -0
  123. package/package.json +74 -0
  124. package/scripts/install_security_skills.py +816 -0
  125. package/scripts/installer/engine.js +254 -0
  126. package/scripts/installer/knowledge.js +28 -0
  127. package/scripts/installer/packets.js +77 -0
  128. package/scripts/installer/terminal.js +98 -0
  129. package/scripts/installer/transaction.js +75 -0
  130. package/skills-lock.json +92 -0
  131. package/templates/adoption/agents/core.md +23 -0
  132. package/templates/adoption/agents/quality.md +3 -0
  133. package/templates/adoption/knowledge/wiki/architecture/index.md +3 -0
  134. package/templates/adoption/knowledge/wiki/decisions/index.md +3 -0
  135. package/templates/adoption/knowledge/wiki/design/index.md +3 -0
  136. package/templates/adoption/knowledge/wiki/domain/index.md +3 -0
  137. package/templates/adoption/knowledge/wiki/index.md +3 -0
  138. package/templates/adoption/knowledge/wiki/log.md +3 -0
  139. package/templates/adoption/knowledge/wiki/open-questions/index.md +3 -0
  140. package/templates/adoption/knowledge/wiki/product/index.md +3 -0
  141. package/templates/adoption/knowledge/wiki/research/index.md +3 -0
  142. package/templates/adoption/product/AGENT-CONTEXT.md +25 -0
@@ -0,0 +1,357 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ validate_verification.py - deterministic completion gate for a feature.
4
+
5
+ The skill's strongest invariant is "a feature is done when an independent Verifier's
6
+ report accounts for every check." That is prose the model has to remember, and prose
7
+ is what a long trajectory quietly drops. This turns it into a checkable pass/fail run
8
+ as the closing step, so declaring a feature done without a real report fails loudly.
9
+
10
+ It does NOT merely check that verification.md exists. A report that exists but holds
11
+ the template placeholder, cites no evidence, or records a surviving mutant next to a
12
+ PASS verdict would sail through a shallow existence check while proving nothing. The
13
+ gate reads the report's own tables and refuses a verdict its rows contradict.
14
+
15
+ Pure standard library, zero dependencies. Operates only on the .specs/ markdown
16
+ artifacts, so it stays stack-agnostic.
17
+
18
+ What it checks:
19
+ ERROR - no verification.md (Verify is the closing step of the flow, not an option)
20
+ ERROR - the verdict is FAIL, missing, or still the '[PASS | FAIL]' placeholder
21
+ ERROR - PASS with no file:line evidence anywhere (evidence-or-zero)
22
+ ERROR - profile standard/ui with no fault rows, or with no recomputed Coverage section
23
+ ERROR - PASS while a fault row says the mutant survived
24
+ ERROR - PASS while a Coverage row leaves a member Unproven
25
+ ERROR - PASS while a binding source leaves an element Uncovered
26
+ ERROR - PASS while a check row's Result is not PASS
27
+ ERROR - PASS while a Test policy row's expectation is not met
28
+ ERROR - the report's profile does not match the one checks.md was approved under
29
+ ERROR - profile ui, but the report carries no binding-sources section (step 1 vanished)
30
+ ERROR - profile standard/ui with Test policy rows in checks.md, but no verdicts in the report
31
+ WARN - the report says self-verified (author == verifier: degraded gate)
32
+ WARN - no `Profile:` line, so a skipped step cannot be told from a forgotten one
33
+ WARN - no `Round:` line
34
+
35
+ Usage:
36
+ python3 <skill-dir>/scripts/validate_verification.py [feature] [--root DIR] [--strict]
37
+
38
+ Invoke from the skill directory that ships this script (not the project root).
39
+ Pass --root when cwd is not the project that contains .specs/.
40
+
41
+ Exit codes: 0 ok, 1 the feature is not done, 2 usage error.
42
+ """
43
+
44
+ import argparse
45
+ import os
46
+ import re
47
+ import sys
48
+
49
+ EVIDENCE_RE = re.compile(r"[\w./-]+\.[A-Za-z0-9]+:\d+")
50
+ PROFILE_RE = re.compile(r"^\**Profile\**\s*:\s*`?(\w+)`?", re.IGNORECASE | re.MULTILINE)
51
+ EMPTY_CELL = {"", "-", "—", "–", "none", "n/a", "na", "nothing"}
52
+ SURVIVED_RE = re.compile(r"\b(no|survived|alive|not killed)\b", re.IGNORECASE)
53
+ UNMET_RE = re.compile(r"\b(no|not met|unmet|fail|failed|gap)\b", re.IGNORECASE)
54
+
55
+
56
+ def _feature_dirs(root):
57
+ base = os.path.join(root, ".specs", "features")
58
+ if not os.path.isdir(base):
59
+ return base, []
60
+ return base, [d for d in sorted(os.listdir(base)) if os.path.isdir(os.path.join(base, d))]
61
+
62
+
63
+ def split_row(line):
64
+ return [c.strip() for c in line.strip().strip("|").split("|")]
65
+
66
+
67
+ def is_separator(line):
68
+ return bool(re.match(r"^\s*\|?[\s:|-]+\|?\s*$", line)) and "-" in line
69
+
70
+
71
+ def strip_fences(text):
72
+ """Drop fenced blocks: a report that pasted the template must not be read as data."""
73
+ out, in_fence = [], False
74
+ for ln in text.splitlines():
75
+ if ln.strip().startswith("```"):
76
+ in_fence = not in_fence
77
+ continue
78
+ if not in_fence:
79
+ out.append(ln)
80
+ return out
81
+
82
+
83
+ def find_tables(lines):
84
+ """Return [(header_cells_lowercased, [data_row_cells, ...]), ...] for every table."""
85
+ tables, buf = [], []
86
+ for ln in lines + [""]:
87
+ stripped = ln.strip()
88
+ if stripped.startswith("|"):
89
+ buf.append(stripped)
90
+ continue
91
+ if buf:
92
+ rows = [split_row(r) for r in buf if not is_separator(r)]
93
+ if len(rows) >= 2:
94
+ tables.append(([c.lower() for c in rows[0]], rows[1:]))
95
+ buf = []
96
+ return tables
97
+
98
+
99
+ def column(header, *names):
100
+ for i, cell in enumerate(header):
101
+ for n in names:
102
+ if n in cell:
103
+ return i
104
+ return None
105
+
106
+
107
+ def has_heading(lines, *names):
108
+ for ln in lines:
109
+ s = ln.strip()
110
+ for n in names:
111
+ if re.match(r"^#{1,4}\s+" + re.escape(n) + r"\b", s, re.IGNORECASE):
112
+ return True
113
+ return False
114
+
115
+
116
+ def _profile(text):
117
+ m = PROFILE_RE.search(text)
118
+ return m.group(1).lower() if m else None
119
+
120
+
121
+ def _checks_context(fdir):
122
+ """What checks.md was approved under: (profile, has a Test policy section)."""
123
+ path = os.path.join(fdir, "checks.md")
124
+ if not os.path.exists(path):
125
+ return None, False
126
+ body = open(path, encoding="utf-8", errors="replace").read()
127
+ lines = strip_fences(body)
128
+ return _profile("\n".join(lines)), has_heading(lines, "Test policy")
129
+
130
+
131
+ def _verdict(lines):
132
+ """Return 'pass', 'fail', 'unfilled', or None."""
133
+ candidates = [
134
+ ln for ln in lines
135
+ if re.search(r"^\**verdict\**\s*:", ln.strip(), re.IGNORECASE)
136
+ or re.search(r"^#{1,4}\s*verification\b", ln.strip(), re.IGNORECASE)
137
+ ]
138
+ hay = " ".join(candidates) if candidates else "\n".join(lines)
139
+ has_pass = re.search(r"\bPASS\b", hay) is not None
140
+ has_fail = re.search(r"\bFAIL\b", hay) is not None
141
+ if has_pass and has_fail:
142
+ return "unfilled"
143
+ if has_pass:
144
+ return "pass"
145
+ if has_fail:
146
+ return "fail"
147
+ return None
148
+
149
+
150
+ def _check_feature(fdir, name):
151
+ errors, warnings = [], []
152
+ vpath = os.path.join(fdir, "verification.md")
153
+ if not os.path.exists(vpath):
154
+ errors.append(
155
+ f"{name}: no verification.md - the feature is not done until a fresh Verifier "
156
+ f"(author != verifier) writes it over <feature base>..HEAD with every check"
157
+ )
158
+ return errors, warnings
159
+
160
+ text = open(vpath, encoding="utf-8", errors="replace").read()
161
+ lines = strip_fences(text)
162
+ body = "\n".join(lines)
163
+
164
+ verdict = _verdict(lines)
165
+ if verdict is None:
166
+ errors.append(f"{name}: no PASS/FAIL verdict (a prose-only report does not count)")
167
+ elif verdict == "unfilled":
168
+ errors.append(f"{name}: verdict is still the template placeholder '[PASS | FAIL]'")
169
+ elif verdict == "fail":
170
+ errors.append(f"{name}: verdict is FAIL - route the ranked gaps back as fixes, then re-verify")
171
+
172
+ # The profile decides which steps run, so a report that quietly declares a cheaper one
173
+ # than the feature was approved under makes an entire step disappear without a trace.
174
+ report_profile = _profile(body)
175
+ approved_profile, policy_rows_exist = _checks_context(fdir)
176
+ if report_profile is None:
177
+ warnings.append(f"{name}: no `Profile:` line - a step that did not run is indistinguishable from one forgotten")
178
+ elif approved_profile and report_profile != approved_profile:
179
+ errors.append(
180
+ f"{name}: report says profile '{report_profile}' but checks.md was approved under "
181
+ f"'{approved_profile}' - the profile decides which steps run, so a mismatch silently drops one"
182
+ )
183
+ effective = approved_profile or report_profile
184
+ if not re.search(r"^\**round\**\s*:", body, re.IGNORECASE | re.MULTILINE):
185
+ warnings.append(f"{name}: no `Round:` line - a scoped re-verification must say what was carried forward")
186
+ if re.search(r"self[- ]verified", body, re.IGNORECASE):
187
+ warnings.append(
188
+ f"{name}: report is self-verified (author == verifier) - a degraded gate, "
189
+ f"since a self-check reproduces the author's own blind spot"
190
+ )
191
+
192
+ if verdict != "pass":
193
+ return errors, warnings
194
+
195
+ if not EVIDENCE_RE.search(body):
196
+ errors.append(f"{name}: PASS but cites no file:line evidence - evidence-or-zero not satisfied")
197
+
198
+ # A profile-scoped step that produced no section did not run. Requiring the section is what
199
+ # makes "skipped" distinguishable from "forgotten" - otherwise that is only a sentence.
200
+ if effective in ("standard", "ui") and not has_heading(lines, "Coverage"):
201
+ errors.append(
202
+ f"{name}: profile is {effective} but there is no `## Coverage` section - the join has to "
203
+ f"be recomputed from the authority over each set, not read back from the author's table"
204
+ )
205
+ if effective == "ui" and not has_heading(lines, "Binding sources", "Binding source"):
206
+ errors.append(
207
+ f"{name}: profile is ui but there is no `## Binding sources` section - step 1 is the "
208
+ f"only step that can catch a check contradicting the design, and no later step can"
209
+ )
210
+ if effective in ("standard", "ui") and policy_rows_exist and not has_heading(lines, "Test policy"):
211
+ errors.append(
212
+ f"{name}: checks.md carries Test policy rows but the report gives no verdict on them - "
213
+ f"those rows are the bar the author built under, so an unmet one is a finding"
214
+ )
215
+
216
+ tables = find_tables(lines)
217
+ saw_faults = False
218
+
219
+ for header, rows in tables:
220
+ killed = column(header, "killed")
221
+ if killed is not None:
222
+ saw_faults = True
223
+ for r in rows:
224
+ if killed >= len(r):
225
+ continue
226
+ cell = r[killed]
227
+ if cell.lower() in EMPTY_CELL and cell.lower() != "no":
228
+ continue
229
+ if SURVIVED_RE.match(cell) or "survived" in cell.lower():
230
+ errors.append(
231
+ f"{name}: PASS but a mutant survived ({r[0][:50]}) - the assertion would "
232
+ f"pass under a plausible wrong implementation"
233
+ )
234
+
235
+ unproven = column(header, "unproven")
236
+ if unproven is not None:
237
+ for r in rows:
238
+ if unproven < len(r) and r[unproven].lower() not in EMPTY_CELL:
239
+ errors.append(f"{name}: PASS but Coverage leaves '{r[unproven][:50]}' unproven ({r[0][:40]})")
240
+
241
+ uncovered = column(header, "uncovered")
242
+ if uncovered is not None:
243
+ for r in rows:
244
+ if uncovered < len(r) and r[uncovered].lower() not in EMPTY_CELL:
245
+ errors.append(
246
+ f"{name}: PASS but a binding source leaves '{r[uncovered][:50]}' uncovered ({r[0][:40]})"
247
+ )
248
+
249
+ result = column(header, "result")
250
+ if result is not None and column(header, "check", "claim") is not None:
251
+ for r in rows:
252
+ if result < len(r) and r[result] and "pass" not in r[result].lower():
253
+ errors.append(f"{name}: PASS but check {r[0][:20]} reports '{r[result][:30]}'")
254
+
255
+ met = column(header, "expectation met", "met")
256
+ if met is not None:
257
+ for r in rows:
258
+ if met < len(r) and r[met] and UNMET_RE.search(r[met]):
259
+ errors.append(
260
+ f"{name}: PASS but a Test policy row is unmet ({r[0][:40]}: '{r[met][:40]}') "
261
+ f"- the rows priced work the checks do not name"
262
+ )
263
+
264
+ # Fault injection is profile-scoped (standard, ui), like tlc-implement. Under `light` a
265
+ # report with no fault rows is correct, and the profile line is what says so.
266
+ if effective in ("standard", "ui") and not saw_faults:
267
+ errors.append(
268
+ f"{name}: profile is {effective} but there are no fault rows - a green suite proves the "
269
+ f"tests execute, only a killed mutant proves they can fail"
270
+ )
271
+
272
+ return errors, warnings
273
+
274
+
275
+ def _appears_complete(fdir):
276
+ if os.path.exists(os.path.join(fdir, "verification.md")):
277
+ return True
278
+ checks = os.path.join(fdir, "checks.md")
279
+ if not os.path.exists(checks):
280
+ return False
281
+ body = open(checks, encoding="utf-8", errors="replace").read()
282
+ if not re.search(r"^\**\s*C\d+\s*\**\s*[-–—:]", body, re.MULTILINE):
283
+ return False
284
+ if re.search(r"^\s*-\s*\[\s\]", body, re.MULTILINE):
285
+ return False
286
+ return True
287
+
288
+
289
+ def _resolve(root, feature):
290
+ base, dirs = _feature_dirs(root)
291
+ # An explicit argument is answered on its own terms: "that feature is not here" beats
292
+ # "there is no features directory", which is true but not what was asked.
293
+ if feature:
294
+ fdir = feature if os.path.isdir(feature) else os.path.join(base, feature)
295
+ if not os.path.isdir(fdir):
296
+ print(f"validate_verification: feature not found: {feature}", file=sys.stderr)
297
+ raise SystemExit(2)
298
+ return [(fdir, os.path.basename(fdir.rstrip("/")))]
299
+ if not os.path.isdir(base):
300
+ print(f"validate_verification: no {base} directory - nothing to check.")
301
+ return []
302
+ if len(dirs) == 1:
303
+ return [(os.path.join(base, dirs[0]), dirs[0])]
304
+ if not dirs:
305
+ print("validate_verification: no features under .specs/features/ - nothing to check.")
306
+ return []
307
+ picked = [(os.path.join(base, d), d) for d in dirs if _appears_complete(os.path.join(base, d))]
308
+ if not picked:
309
+ print("validate_verification: no completed feature detected (all in progress) - nothing to gate.")
310
+ return picked
311
+
312
+
313
+ def main(argv=None):
314
+ p = argparse.ArgumentParser(
315
+ prog="validate_verification.py",
316
+ description="Completion gate: a done feature needs a real PASS report its own rows do not contradict.",
317
+ )
318
+ p.add_argument("feature", nargs="?", default=None, help="Feature dir or name")
319
+ p.add_argument("--root", default=".", help="Project root containing .specs/ (default: current dir)")
320
+ p.add_argument("--strict", action="store_true", help="Treat warnings as errors")
321
+ p.add_argument("--allow-empty", action="store_true",
322
+ help="Exit 0 when there is no completed feature to gate (repo-wide sweeps)")
323
+ args = p.parse_args(argv)
324
+ root = os.path.abspath(args.root)
325
+
326
+ targets = _resolve(root, args.feature)
327
+ if not targets and not args.allow_empty:
328
+ # Gating nothing is not a pass. This is the completion gate for a feature that was just
329
+ # built, so an empty run means the report could not be found - which reads as green to
330
+ # anything checking only the exit code. Exit 2 says "could not gate", like the other
331
+ # validators do, and leaves 0 to mean a report was read and held up.
332
+ print(
333
+ "validate_verification: gated nothing - this is NOT a pass. Point it at the feature "
334
+ "(validate_verification.py <feature> --root <project>), or pass --allow-empty if you "
335
+ "meant to sweep a repo that has no completed feature yet.",
336
+ file=sys.stderr,
337
+ )
338
+ return 2
339
+
340
+ all_errors, all_warnings = [], []
341
+ for fdir, name in targets:
342
+ e, w = _check_feature(fdir, name)
343
+ all_errors += e
344
+ all_warnings += w
345
+
346
+ for w in all_warnings:
347
+ print(f" WARN {w}")
348
+ for e in all_errors:
349
+ print(f" ERROR {e}")
350
+ checked = ", ".join(name for _, name in targets) or "(none)"
351
+ fail = all_errors or (all_warnings and args.strict)
352
+ print(f"\nvalidate_verification: {len(all_errors)} error(s), {len(all_warnings)} warning(s) across [{checked}]")
353
+ return 1 if fail else 0
354
+
355
+
356
+ if __name__ == "__main__":
357
+ raise SystemExit(main())
@@ -0,0 +1,102 @@
1
+ ---
2
+ name: wtk-plan
3
+ description: 'Turn decided sources into modular tasks with observable criteria and explicit scope. Use when writing tasks from a PRD, design, RFC, or ticket; not discovery or implementation.'
4
+ license: CC-BY-4.0
5
+ metadata:
6
+ author: Tech Leads Club - github.com/tech-leads-club
7
+ version: 0.2.0
8
+ ---
9
+
10
+ # TLC Plan
11
+
12
+ Turn decided work into `.tasks/<name>.md` with observable criteria with concrete values, grounded
13
+ in the repository. A one-line ticket can be a decision; an unshaped wish belongs in discovery.
14
+ This skill plans modular work and does not implement it.
15
+
16
+ ## Source and scope
17
+
18
+ Read the source and the code relevant to its claims. Resolve factual questions from existing code,
19
+ conventions and documentation. Correct factual source errors within the authorized scope; a conflict
20
+ with an approved product decision needs clarification, not a silent rewrite.
21
+
22
+ Each criterion must describe an observable outcome with a concrete field, status, bound or other
23
+ value and an explicit boundary. It must be possible to name an execution that settles the claim.
24
+ Separate behavior from distributional service targets such as p95 latency or uptime; record how the
25
+ target is measured under observability instead of letting a behavior test claim to prove it.
26
+
27
+ For a guarantee that something will not happen, identify the mechanism preventing it, either in
28
+ existing code or a `Decided` row. Check relevant failure paths: a retry after remote success and
29
+ local failure is different from a retry after nothing happened. An absent mechanism is a decision
30
+ gap, not a guaranteed outcome.
31
+
32
+ Keep new capabilities outside the task unless the user authorizes them. Preserve already-settled
33
+ intent and use the task as the decision record; linked sources retain the reasoning. If they later
34
+ diverge on a binding decision, resolve that conflict before building.
35
+
36
+ ## Slice and task boundaries
37
+
38
+ A slice is one observable outcome, not a layer such as schema or endpoints. Enumerate the source's
39
+ slices, then default to one task for the whole source. Split only for a demonstrated order constraint,
40
+ an answer only another party can supply, or different team ownership.
41
+
42
+ Use project conventions or reachable issue-tracker evidence for task size; git history describes
43
+ pull-request size, not task size. For a large task, show its irreversible decisions and actual seams
44
+ so the user can choose a meaningful cut. Do not impose a fixed slice, file or agent count.
45
+
46
+ Ground what the change disturbs: existing terms, data and callers, plus choices that introduce a new
47
+ pattern. `Decided` records only hard-to-reverse choices, their literal shape and rejected alternative.
48
+ Reversible placement, helpers and class structure belong to the builder and the diff.
49
+
50
+ ## Surface and requirement coverage
51
+
52
+ Walk only the surface kinds the source exposes. Record each applicable item's landing in
53
+ `## Observable`: an existing criterion, `existing - <what>`, `n/a - <reason>`, or `Unresolved <n>`.
54
+ Use `None - no user-facing surface` when appropriate. A missing decision becomes a question; the
55
+ walk never creates a criterion just to fill a row.
56
+
57
+ | Surface | Decisions to account for |
58
+ | --- | --- |
59
+ | Screen/view | Empty, loading, error and unauthorized states; density, ordering and destructive confirmation |
60
+ | API/webhook | Response and error shapes/codes, caller authority, versioning and rate-limit behavior |
61
+ | Command/job | Output, verbosity, flags/defaults, exit codes and partial failure |
62
+ | Document/copy | Structure, tone, depth and the reader's next action |
63
+ | Organized collection | Grouping, naming, ordering, duplicates and exceptions |
64
+
65
+ Record the landing for each of the nine implicit dimensions in `Swept`: validation, failure modes,
66
+ idempotency and retry, authorization, concurrency and ordering, data lifecycle, external-dependency
67
+ failure, state transitions, and observability. Each lands on an existing criterion, existing behavior,
68
+ `n/a` with a reason, or `Unresolved`; these dimensions do not authorize extra scope.
69
+
70
+ A cited criterion must observe that dimension. For example, rejecting an existing duplicate does
71
+ not prove concurrent writes are safe, and event deduplication does not prove ordering. Keep the
72
+ landings explicit so `wtk-implement` can derive proofs from them without repeating the discovery.
73
+
74
+ ## Decisions and unresolved questions
75
+
76
+ Resolve routine facts and reversible choices from the repository and existing authorization. Ask
77
+ only when a material choice belongs to the user and remains unsettled; continue independent work
78
+ while waiting. Give concrete options and a recommendation when useful, without manufacturing a
79
+ quota of questions or requiring a particular answer format.
80
+
81
+ Use safe defaults for non-blocking ambiguity, marking `Kind: open` and the default in `Until answered`.
82
+ Mark missing decisions that prevent a satisfiable criterion as `blocks`; prerequisites
83
+ that only prevent real-world activation use `blocks go-live`. Preserve the output contract's literal
84
+ fields and values. A proposed default is not approval; explicit delegation such as “you decide” is,
85
+ and is recorded as `user delegated` on the Sources line.
86
+
87
+ ## Deliverable
88
+
89
+ When writing the task, read [document-format.md](references/document-format.md). Keep its literal
90
+ headings and required fields, including `Observable`, `Swept`, `Decided` and `Unresolved`.
91
+ Use existing source material to complete the artifact without asking the user to repeat it.
92
+
93
+ Present the task or justified split, followed by unresolved decisions with blockers first. Complete
94
+ the requested planning artifact and its applicable checks; do not begin implementation unless it
95
+ was also authorized. A source with unresolved product intent remains provisional.
96
+
97
+ Tasks are verification units; pull requests are review units. Preparation may belong in a commit
98
+ or PR without being its own task. If review needs smaller PRs, propose that separately using the
99
+ project's review conventions; preserve order constraints needed for safe intermediate states.
100
+
101
+ For library-specific facts not established locally, use the project's documentation tools and
102
+ current official documentation. Record uncertainty rather than inventing an API or behavior.