@deftai/directive-content 0.59.0 → 0.61.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.
Files changed (190) hide show
  1. package/.githooks/pre-commit +10 -128
  2. package/.githooks/pre-push +8 -108
  3. package/Taskfile.yml +48 -58
  4. package/UPGRADING.md +19 -3
  5. package/docs/assets/directive-lifecycle-diagram.png +0 -0
  6. package/docs/directive-lifecycle.md +73 -0
  7. package/docs/getting-started.md +5 -1
  8. package/package.json +3 -3
  9. package/packs/skills/skills-pack-0.1.json +1 -1
  10. package/packs/strategies/strategies-pack-0.1.json +19 -19
  11. package/scm/github.md +37 -6
  12. package/skills/deft-directive-setup/SKILL.md +24 -15
  13. package/strategies/speckit.md +14 -14
  14. package/strategies/v0-20-contract.md +12 -1
  15. package/tasks/change.yml +16 -31
  16. package/tasks/ci.yml +8 -0
  17. package/tasks/commit.yml +12 -19
  18. package/tasks/core.yml +10 -0
  19. package/tasks/engine.yml +42 -0
  20. package/tasks/framework.yml +3 -0
  21. package/tasks/install.yml +20 -19
  22. package/tasks/migrate.yml +26 -15
  23. package/tasks/project.yml +26 -0
  24. package/tasks/toolchain.yml +15 -5
  25. package/tasks/vbrief.yml +4 -3
  26. package/tasks/verify.yml +12 -14
  27. package/templates/agents-entry.md +1 -1
  28. package/scripts/_agents_md.py +0 -494
  29. package/scripts/_cache_fetch.py +0 -635
  30. package/scripts/_cache_quota.py +0 -529
  31. package/scripts/_cache_refresh.py +0 -163
  32. package/scripts/_cache_validate.py +0 -209
  33. package/scripts/_content_root.py +0 -42
  34. package/scripts/_doctor_state.py +0 -277
  35. package/scripts/_event_detect.py +0 -305
  36. package/scripts/_events.py +0 -514
  37. package/scripts/_lifecycle_hygiene.py +0 -568
  38. package/scripts/_pathspec.py +0 -91
  39. package/scripts/_policy_show_cli.py +0 -266
  40. package/scripts/_precutover.py +0 -92
  41. package/scripts/_project_context.py +0 -224
  42. package/scripts/_project_definition_io.py +0 -164
  43. package/scripts/_relocate_snapshot.py +0 -209
  44. package/scripts/_relocate_states.py +0 -343
  45. package/scripts/_resolve_preflight_path.py +0 -152
  46. package/scripts/_safe_subprocess.py +0 -167
  47. package/scripts/_session_start_hook.py +0 -205
  48. package/scripts/_sor_gate_diff.py +0 -365
  49. package/scripts/_stdio_utf8.py +0 -59
  50. package/scripts/_triage_bootstrap_gitignore.py +0 -904
  51. package/scripts/_triage_classify_cli.py +0 -122
  52. package/scripts/_triage_queue_cli.py +0 -625
  53. package/scripts/_triage_scope_cli.py +0 -343
  54. package/scripts/_triage_scope_drift_cli.py +0 -121
  55. package/scripts/_triage_scope_ignores.py +0 -286
  56. package/scripts/_triage_scope_milestone.py +0 -432
  57. package/scripts/_triage_scope_mutations.py +0 -337
  58. package/scripts/_triage_scope_renderers.py +0 -207
  59. package/scripts/_triage_smoketest_stages.py +0 -674
  60. package/scripts/_triage_subscribe_cli.py +0 -140
  61. package/scripts/_triage_welcome_cli.py +0 -421
  62. package/scripts/_vbrief_build.py +0 -239
  63. package/scripts/_vbrief_fidelity.py +0 -479
  64. package/scripts/_vbrief_legacy.py +0 -589
  65. package/scripts/_vbrief_reconciliation.py +0 -883
  66. package/scripts/_vbrief_routing.py +0 -277
  67. package/scripts/_vbrief_safety.py +0 -778
  68. package/scripts/_vbrief_sources.py +0 -312
  69. package/scripts/_vbrief_speckit.py +0 -262
  70. package/scripts/_vbrief_story_quality.py +0 -353
  71. package/scripts/_vbrief_validation.py +0 -299
  72. package/scripts/build_dist.py +0 -412
  73. package/scripts/cache.py +0 -1078
  74. package/scripts/cache_scanner.py +0 -745
  75. package/scripts/candidates_log.py +0 -432
  76. package/scripts/capacity_backfill.py +0 -680
  77. package/scripts/capacity_show.py +0 -653
  78. package/scripts/ci_local.py +0 -689
  79. package/scripts/code_structure_validate.py +0 -765
  80. package/scripts/codebase_default_extractor.py +0 -495
  81. package/scripts/codebase_map.py +0 -304
  82. package/scripts/codebase_map_fresh.py +0 -104
  83. package/scripts/codebase_projection_registry.py +0 -94
  84. package/scripts/codebase_provider.py +0 -582
  85. package/scripts/doctor.py +0 -2552
  86. package/scripts/framework_commands.py +0 -505
  87. package/scripts/gh_rest.py +0 -882
  88. package/scripts/github_auth_modes.py +0 -437
  89. package/scripts/github_body.py +0 -292
  90. package/scripts/ip_risk.py +0 -531
  91. package/scripts/issue_emit.py +0 -670
  92. package/scripts/issue_ingest.py +0 -1064
  93. package/scripts/migrate_preflight.py +0 -418
  94. package/scripts/migrate_vbrief.py +0 -2677
  95. package/scripts/monitor_pr.py +0 -401
  96. package/scripts/pack_migrate_lessons.py +0 -336
  97. package/scripts/pack_migrate_patterns.py +0 -254
  98. package/scripts/pack_migrate_rules.py +0 -350
  99. package/scripts/pack_migrate_skills.py +0 -423
  100. package/scripts/pack_migrate_strategies.py +0 -311
  101. package/scripts/pack_migrate_swarm_spec.py +0 -250
  102. package/scripts/pack_render.py +0 -434
  103. package/scripts/packs_slice.py +0 -712
  104. package/scripts/platform_capabilities.py +0 -336
  105. package/scripts/policy.py +0 -2826
  106. package/scripts/policy_set.py +0 -324
  107. package/scripts/pr_check_closing_keywords.py +0 -524
  108. package/scripts/pr_check_protected_issues.py +0 -267
  109. package/scripts/pr_merge_readiness.py +0 -1004
  110. package/scripts/pr_wait_mergeable.py +0 -669
  111. package/scripts/prd_render.py +0 -159
  112. package/scripts/preflight_architecture_sor.py +0 -974
  113. package/scripts/preflight_branch.py +0 -289
  114. package/scripts/preflight_cache.py +0 -974
  115. package/scripts/preflight_gh.py +0 -721
  116. package/scripts/preflight_implementation.py +0 -272
  117. package/scripts/preflight_story_start.py +0 -838
  118. package/scripts/preflight_wip_cap.py +0 -149
  119. package/scripts/probe_session.py +0 -545
  120. package/scripts/project_render.py +0 -293
  121. package/scripts/quarantine_ext.py +0 -237
  122. package/scripts/reconcile_issues.py +0 -1442
  123. package/scripts/refresh-path.ps1 +0 -107
  124. package/scripts/release.py +0 -2030
  125. package/scripts/release_e2e.py +0 -1011
  126. package/scripts/release_publish.py +0 -486
  127. package/scripts/release_rollback.py +0 -980
  128. package/scripts/relocate.py +0 -1034
  129. package/scripts/resolve_changelog_unreleased.py +0 -667
  130. package/scripts/resolve_version.py +0 -490
  131. package/scripts/resume_conditions.py +0 -706
  132. package/scripts/ritual_sentinel.py +0 -609
  133. package/scripts/roadmap_render.py +0 -635
  134. package/scripts/rule_ownership_lint.py +0 -325
  135. package/scripts/scm.py +0 -591
  136. package/scripts/scope_audit_log.py +0 -387
  137. package/scripts/scope_decompose.py +0 -654
  138. package/scripts/scope_demote.py +0 -509
  139. package/scripts/scope_lifecycle.py +0 -1126
  140. package/scripts/scope_undo.py +0 -772
  141. package/scripts/session_start.py +0 -406
  142. package/scripts/setup_ghx.py +0 -339
  143. package/scripts/setup_windows.ps1 +0 -220
  144. package/scripts/slice_audit.py +0 -585
  145. package/scripts/slice_record.py +0 -530
  146. package/scripts/slice_record_existing.py +0 -692
  147. package/scripts/slug_normalize.py +0 -178
  148. package/scripts/spec_render.py +0 -477
  149. package/scripts/spec_validate.py +0 -238
  150. package/scripts/subagent_monitor.py +0 -658
  151. package/scripts/swarm_complete_cohort.py +0 -644
  152. package/scripts/swarm_launch.py +0 -1206
  153. package/scripts/swarm_readiness.py +0 -554
  154. package/scripts/swarm_verify_review_clean.py +0 -438
  155. package/scripts/swarm_worktrees.py +0 -497
  156. package/scripts/toolchain-check.py +0 -52
  157. package/scripts/triage_actions.py +0 -871
  158. package/scripts/triage_bootstrap.py +0 -1153
  159. package/scripts/triage_bulk.py +0 -630
  160. package/scripts/triage_classify.py +0 -932
  161. package/scripts/triage_help.py +0 -1685
  162. package/scripts/triage_queue.py +0 -1944
  163. package/scripts/triage_reconcile.py +0 -581
  164. package/scripts/triage_refresh.py +0 -643
  165. package/scripts/triage_scope.py +0 -999
  166. package/scripts/triage_scope_drift.py +0 -575
  167. package/scripts/triage_smoketest.py +0 -396
  168. package/scripts/triage_subscribe.py +0 -399
  169. package/scripts/triage_summary.py +0 -1011
  170. package/scripts/triage_welcome.py +0 -1178
  171. package/scripts/ts_check_lane.py +0 -86
  172. package/scripts/validate-links.py +0 -64
  173. package/scripts/validate_strategy_output.py +0 -212
  174. package/scripts/vbrief_activate.py +0 -228
  175. package/scripts/vbrief_migrate_conformance.py +0 -368
  176. package/scripts/vbrief_reconcile_graph.py +0 -306
  177. package/scripts/vbrief_reconcile_labels.py +0 -460
  178. package/scripts/vbrief_reconcile_umbrellas.py +0 -741
  179. package/scripts/vbrief_validate.py +0 -1144
  180. package/scripts/verify-stubs.py +0 -61
  181. package/scripts/verify_capacity.py +0 -160
  182. package/scripts/verify_encoding.py +0 -699
  183. package/scripts/verify_hooks_installed.py +0 -206
  184. package/scripts/verify_investigation.py +0 -360
  185. package/scripts/verify_judgment_gates.py +0 -827
  186. package/scripts/verify_no_task_runtime.py +0 -171
  187. package/scripts/verify_scm_boundary.py +0 -509
  188. package/scripts/verify_session_ritual.py +0 -389
  189. package/scripts/verify_tools.py +0 -426
  190. package/scripts/verify_vbrief_conformance.py +0 -478
@@ -1,1685 +0,0 @@
1
- # ruff: noqa: E501 -- registry literal carries long usage/example strings
2
- # that are easier to read on one line than wrapped. The 100-char ceiling is
3
- # enforced everywhere else in this file (it is the rest of the codebase's
4
- # default); the registry block is the documented exception.
5
- """triage_help.py -- categorized verb-help surface (#1150 / N10).
6
-
7
- This module is the single source of truth for the *user-facing* help text
8
- of every ``triage:*`` and ``scope:*`` task. It powers three surfaces:
9
-
10
- 1. ``task triage`` (bare invocation) -- categorized verb list grouped by
11
- role (Session-start / State verbs / Read verbs / Lifecycle /
12
- Subscription mutation / Archive-rotation).
13
- 2. ``task scope`` (bare invocation) -- categorized verb list grouped by
14
- role (Promote / demote, Activate / complete, Reversibility).
15
- 3. ``<verb> --help`` -- structured per-verb help with description,
16
- flags + defaults, 2-3 examples and cross-references to related verbs
17
- + umbrella children. Verb scripts call :func:`intercept_help` early
18
- in ``main()`` so the structured renderer wins over argparse default.
19
-
20
- Adding a new ``triage:*`` / ``scope:*`` verb is one dict-insert under
21
- :data:`REGISTRY` plus a single entry in :data:`CATEGORIES_TRIAGE` or
22
- :data:`CATEGORIES_SCOPE`. See ``CONTRIBUTING.md`` § "Adding a new
23
- triage / scope verb" for the contributor walkthrough.
24
-
25
- Forward-looking placeholders ("coming in <child>") are intentionally
26
- listed for D17 (metrics) / D19 (archive rotation) / D20 (audit-log
27
- rotation) so the verb-space catalog is stable across the umbrella's
28
- remaining children -- a fresh contributor sees the *shape* of the
29
- surface even when a verb has not yet landed.
30
-
31
- Programmatic API
32
- ----------------
33
-
34
- * :class:`VerbHelp` -- frozen registry entry shape.
35
- * :data:`REGISTRY` -- ``{verb_name: VerbHelp}`` lookup.
36
- * :data:`CATEGORIES_TRIAGE` / :data:`CATEGORIES_SCOPE` -- ordered
37
- ``[(category_label, [verb_names])]`` for the bare-list renderers.
38
- * :data:`SCRIPT_SUBCOMMAND_MAP` -- ``{script_module: {subcommand: verb}}``
39
- used by :func:`intercept_help` to resolve a verb from argv at runtime.
40
- * :func:`render_category_list` -- pure text renderer for the bare
41
- invocation surface.
42
- * :func:`render_verb_help` -- pure text renderer for the ``--help``
43
- surface.
44
- * :func:`intercept_help` -- ``main()`` shim that prints structured help
45
- and returns ``0`` when ``--help`` / ``-h`` is in ``argv``; returns
46
- ``None`` otherwise.
47
-
48
- CLI
49
- ---
50
-
51
- ``python -m scripts.triage_help <category>`` prints the bare-list for
52
- ``triage`` or ``scope``. ``python -m scripts.triage_help help <verb>``
53
- prints the structured per-verb help. ``python -m scripts.triage_help
54
- list`` dumps every registered verb for tooling discovery.
55
-
56
- Refs
57
- ----
58
-
59
- * Umbrella: #1119.
60
- * This child: #1150 / N10.
61
- * Reciprocal child for ``task slice:record-existing``: #1147 / N7 (verb
62
- space outside the triage/scope namespaces; intentionally NOT
63
- registered here so the registry boundary stays sharp).
64
- """
65
-
66
- from __future__ import annotations
67
-
68
- import contextlib
69
- import sys
70
- from collections.abc import Sequence
71
- from dataclasses import dataclass, field
72
-
73
- # UTF-8 self-reconfigure -- the help renderer prints arrows / em-dashes
74
- # / ⚠ glyphs that cp1252 cannot encode (#814).
75
- for _stream in (sys.stdout, sys.stderr):
76
- if hasattr(_stream, "reconfigure"):
77
- with contextlib.suppress(AttributeError, ValueError):
78
- _stream.reconfigure(encoding="utf-8", errors="replace")
79
-
80
-
81
- # ---------------------------------------------------------------------------
82
- # Registry data shape
83
- # ---------------------------------------------------------------------------
84
-
85
-
86
- @dataclass(frozen=True)
87
- class VerbHelp:
88
- """Structured help metadata for a single ``triage:*`` / ``scope:*`` verb.
89
-
90
- Attributes
91
- ----------
92
- name:
93
- Canonical verb name as the user invokes it (``triage:queue``,
94
- ``scope:promote``). Matches the Taskfile task name.
95
- summary:
96
- One-line description used in the bare-list category renderer.
97
- Keep <= 70 chars so the category list stays scannable.
98
- refs:
99
- Bracketed cross-ref tag printed alongside ``summary`` in the
100
- category renderer (e.g. ``"(D11)"`` or ``"(D19, coming)"``).
101
- description:
102
- Full prose description printed as the first paragraph of the
103
- ``--help`` surface. May be multi-line.
104
- usage:
105
- Synopsis line (e.g.
106
- ``"task triage:queue [--limit=N] [--state=<filter>]"``).
107
- flags:
108
- List of ``(flag, default, description)`` triples. ``default``
109
- is rendered verbatim -- use ``"(required)"`` for required
110
- flags, ``"(none)"`` for unset defaults.
111
- examples:
112
- 2-3 example invocations. Rendered as a fenced indented block.
113
- see_also:
114
- Verb names and umbrella-child references to surface at the
115
- bottom of the ``--help`` output. Use plain verb names
116
- (``"task triage:show"``) or umbrella IDs (``"#1119 / D11"``).
117
- placeholder:
118
- True for not-yet-landed verbs (e.g. D17 metrics / D19 archive
119
- rotation). The renderer adds a ``"(not yet implemented)"``
120
- note so the operator knows the verb is forward-looking.
121
- """
122
-
123
- name: str
124
- summary: str
125
- refs: str
126
- description: str
127
- usage: str
128
- flags: Sequence[tuple[str, str, str]] = field(default_factory=tuple)
129
- examples: Sequence[str] = field(default_factory=tuple)
130
- see_also: Sequence[str] = field(default_factory=tuple)
131
- placeholder: bool = False
132
-
133
-
134
- # ---------------------------------------------------------------------------
135
- # Registry -- single source of truth for every triage:* / scope:* verb
136
- # ---------------------------------------------------------------------------
137
-
138
-
139
- def _entry(*args, **kwargs) -> VerbHelp:
140
- """Convenience constructor so the registry literal stays compact."""
141
- return VerbHelp(*args, **kwargs)
142
-
143
-
144
- REGISTRY: dict[str, VerbHelp] = {
145
- # --- Session-start cross-namespace surfaces --------------------------
146
- "task triage:summary": _entry(
147
- name="task triage:summary",
148
- summary="One-line state for session-start ritual",
149
- refs="(D2 / #1122)",
150
- description=(
151
- "Emit the one-line triage state consumed by the session-start "
152
- "ritual. Always exits 0 (status surface, not a gate); appends "
153
- "a JSONL record to vbrief/.eval/summary-history.jsonl for "
154
- "observability."
155
- ),
156
- usage="task triage:summary [-- --json] [--no-history]",
157
- flags=(
158
- ("--json", "(off)", "Emit the structured record as JSON instead of the one-liner."),
159
- ("--no-history", "(off)", "Suppress the history sidecar append (test-only)."),
160
- ("--project-root PATH", "(cwd)", "Project root override (Taskfile threads USER_WORKING_DIR)."),
161
- ),
162
- examples=(
163
- "task triage:summary",
164
- "task triage:summary -- --json | jq",
165
- ),
166
- see_also=(
167
- "task triage:queue",
168
- "task verify:cache-fresh",
169
- "#1119 / D2",
170
- ),
171
- ),
172
- "task verify:cache-fresh": _entry(
173
- name="task verify:cache-fresh",
174
- summary="Pre-start_agent freshness gate",
175
- refs="(D5 / #1127)",
176
- description=(
177
- "Detection-bound gate that refuses dispatch when the local "
178
- "cache is stale or the target issue is outside the active "
179
- "subscription. Exit 0 = clear, 1 = blocked (stale / outside "
180
- "scope / wrong decision), 2 = config error (cache missing)."
181
- ),
182
- usage="task verify:cache-fresh [-- --for-issue N] [--allow-stale]",
183
- flags=(
184
- ("--for-issue N", "(none)", "Gate on a specific upstream issue number."),
185
- ("--allow-stale", "(off)", "Operator-audited override; logged to stderr."),
186
- ),
187
- examples=(
188
- "task verify:cache-fresh",
189
- "task verify:cache-fresh -- --for-issue 1150",
190
- ),
191
- see_also=(
192
- "task triage:bootstrap",
193
- "task cache:fetch-all",
194
- "#1119 / D5",
195
- ),
196
- ),
197
- # --- State verbs (mutate audit log) ----------------------------------
198
- "task triage:accept": _entry(
199
- name="task triage:accept",
200
- summary="Mark issue accepted; chains into scope:promote w/ flag",
201
- refs="(D18 / #845)",
202
- description=(
203
- "Record an `accept` audit entry against an issue. With the "
204
- "D18 reciprocity flag, chains into scope:promote --from-issue "
205
- "so the upstream cache and the vBRIEF lifecycle stay in sync."
206
- ),
207
- usage="task triage:accept -- --issue N --repo owner/name [--actor STR]",
208
- flags=(
209
- ("--issue N", "(required)", "Issue number."),
210
- ("--repo owner/name", "(required)", "Upstream repo."),
211
- ("--actor STR", "(env DEFT_TRIAGE_ACTOR)", "Override the audit actor field."),
212
- ),
213
- examples=(
214
- "task triage:accept -- --issue 42 --repo deftai/directive",
215
- ),
216
- see_also=(
217
- "task triage:status",
218
- "task scope:promote",
219
- "#1119 / D18",
220
- ),
221
- ),
222
- "task triage:defer": _entry(
223
- name="task triage:defer",
224
- summary="Defer with reason + optional resume-on condition",
225
- refs="(D3 / #1123)",
226
- description=(
227
- "Record a `defer` audit entry against an issue. --reason is "
228
- "required (replaces free-text defer per D3). --resume-on "
229
- "accepts the v1 grammar (ref:closed:#N | ref:merged:#N | "
230
- "date:>=YYYY-MM-DD | pending-count:>=N | pending-count:<=N, "
231
- "joined by AND/OR)."
232
- ),
233
- usage="task triage:defer -- --issue N --repo owner/name --reason 'why' [--resume-on EXPR] [--actor STR]",
234
- flags=(
235
- ("--issue N", "(required)", "Issue number."),
236
- ("--repo owner/name", "(required)", "Upstream repo."),
237
- ("--reason 'why'", "(required)", "Structured rationale (D3 enforced)."),
238
- ("--resume-on EXPR", "(none)", "Resume-condition expression."),
239
- ("--actor STR", "(env DEFT_TRIAGE_ACTOR)", "Override the audit actor field."),
240
- ),
241
- examples=(
242
- "task triage:defer -- --issue 42 --repo deftai/directive --reason 'waiting on upstream'",
243
- "task triage:defer -- --issue 42 --repo deftai/directive --reason 'needs PR 99' --resume-on 'ref:merged:#99'",
244
- ),
245
- see_also=(
246
- "task triage:status",
247
- "task triage:history",
248
- "task triage:bulk-defer",
249
- "#1119 / D3",
250
- ),
251
- ),
252
- "task triage:reject": _entry(
253
- name="task triage:reject",
254
- summary="Close upstream with comment + label",
255
- refs="(#845)",
256
- description=(
257
- "Close the upstream issue, apply the `triage-rejected` label, "
258
- "and record a `reject` audit entry. Rolls audit back on gh "
259
- "failure so the cache and upstream stay consistent."
260
- ),
261
- usage="task triage:reject -- --issue N --repo owner/name --reason 'why' [--actor STR]",
262
- flags=(
263
- ("--issue N", "(required)", "Issue number."),
264
- ("--repo owner/name", "(required)", "Upstream repo."),
265
- ("--reason 'why'", "(required)", "Reason recorded on the close comment."),
266
- ("--actor STR", "(env DEFT_TRIAGE_ACTOR)", "Override the audit actor field."),
267
- ),
268
- examples=(
269
- "task triage:reject -- --issue 42 --repo deftai/directive --reason 'out of scope'",
270
- ),
271
- see_also=(
272
- "task triage:bulk-reject",
273
- "task triage:reset",
274
- "#1119 / #845",
275
- ),
276
- ),
277
- "task triage:needs-ac": _entry(
278
- name="task triage:needs-ac",
279
- summary="Post comment requesting AC",
280
- refs="(#845)",
281
- description=(
282
- "Mark an issue as needing acceptance criteria and post an "
283
- "AC-request comment upstream. Records a `needs-ac` audit "
284
- "entry against the cache."
285
- ),
286
- usage="task triage:needs-ac -- --issue N --repo owner/name [--comment STR] [--actor STR]",
287
- flags=(
288
- ("--issue N", "(required)", "Issue number."),
289
- ("--repo owner/name", "(required)", "Upstream repo."),
290
- ("--comment STR", "(canned)", "Override the AC-request comment text."),
291
- ("--actor STR", "(env DEFT_TRIAGE_ACTOR)", "Override the audit actor field."),
292
- ),
293
- examples=(
294
- "task triage:needs-ac -- --issue 42 --repo deftai/directive",
295
- ),
296
- see_also=(
297
- "task triage:bulk-needs-ac",
298
- "task triage:status",
299
- "#1119 / #845",
300
- ),
301
- ),
302
- "task triage:mark-duplicate": _entry(
303
- name="task triage:mark-duplicate",
304
- summary="Link as duplicate of another (validated)",
305
- refs="(#845)",
306
- description=(
307
- "Link an issue as a duplicate of another cached issue. The "
308
- "target issue must already exist in the unified cache; the "
309
- "audit entry is rejected otherwise."
310
- ),
311
- usage="task triage:mark-duplicate -- --issue N --of M --repo owner/name [--actor STR]",
312
- flags=(
313
- ("--issue N", "(required)", "Issue number to mark as duplicate."),
314
- ("--of M", "(required)", "Canonical issue number (validated against the cache)."),
315
- ("--repo owner/name", "(required)", "Upstream repo."),
316
- ("--actor STR", "(env DEFT_TRIAGE_ACTOR)", "Override the audit actor field."),
317
- ),
318
- examples=(
319
- "task triage:mark-duplicate -- --issue 42 --of 17 --repo deftai/directive",
320
- ),
321
- see_also=(
322
- "task triage:history",
323
- "#1119 / #845",
324
- ),
325
- ),
326
- "task triage:reset": _entry(
327
- name="task triage:reset",
328
- summary="Undo prior decision (Layer 5 reversibility)",
329
- refs="(#845)",
330
- description=(
331
- "Append a `reset` audit entry that references the prior "
332
- "decision. Does NOT delete history -- the audit log is "
333
- "append-only by design; reset is the supported reversibility "
334
- "primitive."
335
- ),
336
- usage="task triage:reset -- --issue N --repo owner/name [--actor STR]",
337
- flags=(
338
- ("--issue N", "(required)", "Issue number."),
339
- ("--repo owner/name", "(required)", "Upstream repo."),
340
- ("--actor STR", "(env DEFT_TRIAGE_ACTOR)", "Override the audit actor field."),
341
- ),
342
- examples=(
343
- "task triage:reset -- --issue 42 --repo deftai/directive",
344
- ),
345
- see_also=(
346
- "task triage:history",
347
- "task scope:undo",
348
- "#1119 / #845",
349
- ),
350
- ),
351
- "task triage:status": _entry(
352
- name="task triage:status",
353
- summary="Print latest triage decision (read-only)",
354
- refs="(#845)",
355
- description=(
356
- "Print the latest triage decision for an issue from the "
357
- "append-only audit log. Read-only; no mutations, no "
358
- "subprocess calls."
359
- ),
360
- usage="task triage:status -- --issue N --repo owner/name",
361
- flags=(
362
- ("--issue N", "(required)", "Issue number."),
363
- ("--repo owner/name", "(required)", "Upstream repo."),
364
- ),
365
- examples=(
366
- "task triage:status -- --issue 42 --repo deftai/directive",
367
- ),
368
- see_also=(
369
- "task triage:history",
370
- "task triage:show",
371
- "#1119 / #845",
372
- ),
373
- ),
374
- "task triage:history": _entry(
375
- name="task triage:history",
376
- summary="Print full triage timeline (read-only)",
377
- refs="(#845)",
378
- description=(
379
- "Print the full triage timeline for an issue ordered by "
380
- "timestamp ascending. Read-only."
381
- ),
382
- usage="task triage:history -- --issue N --repo owner/name",
383
- flags=(
384
- ("--issue N", "(required)", "Issue number."),
385
- ("--repo owner/name", "(required)", "Upstream repo."),
386
- ),
387
- examples=(
388
- "task triage:history -- --issue 42 --repo deftai/directive",
389
- ),
390
- see_also=(
391
- "task triage:status",
392
- "task triage:audit",
393
- "#1119 / #845",
394
- ),
395
- ),
396
- # --- Read verbs ------------------------------------------------------
397
- "task triage:queue": _entry(
398
- name="task triage:queue",
399
- summary="Ranked candidate list",
400
- refs="(D11 / #1128)",
401
- description=(
402
- "Print the ranked triage queue from the local cache. Groups "
403
- "(display order): [ORPHAN] -> [RESUME] -> [URGENT] -> untriaged "
404
- "-> other -> [BLOCKED]. Within-group default = updated_at desc; "
405
- "consumer plan.policy.triageRankingLabels[] re-orders within-group "
406
- "by matched-label declared order. Items whose linked vBRIEF is "
407
- "blocked (status:blocked / unresolved depends_on) are demoted into "
408
- "[BLOCKED] unless --include-blocked is passed (#1286)."
409
- ),
410
- usage="task triage:queue [-- --limit=N] [--include-blocked] [--repo=owner/name]",
411
- flags=(
412
- ("--limit N", "10", "Max rows to print."),
413
- ("--include-blocked", "(off)", "Re-surface blocked items into their natural group (#1286)."),
414
- ("--repo owner/name", "(git remote)", "Explicit repo override."),
415
- ),
416
- examples=(
417
- "task triage:queue",
418
- "task triage:queue -- --limit=20 --state=accept",
419
- "task triage:queue -- --format=json | jq '.[] | select(.score > 5)'",
420
- ),
421
- see_also=(
422
- "task triage:show",
423
- "task triage:audit",
424
- "#1119 / D11",
425
- ),
426
- ),
427
- "task triage:audit": _entry(
428
- name="task triage:audit",
429
- summary="Session-summary view + slice-aware audit flags",
430
- refs="(D11/D13 / #1128, #1180)",
431
- description=(
432
- "Audit-log surface used by D2 (#1122) for triage:summary "
433
- "integration and by D4 (#1124) for cap-reached error "
434
- "messages. --vbrief-staleness flags audit entries that "
435
- "reference vBRIEFs newer than their last decision."
436
- ),
437
- usage="task triage:audit [-- --format=text|json] [--vbrief-staleness] [--since=<window>] [--action=<verb>]",
438
- flags=(
439
- ("--format text|json", "text", "Output shape."),
440
- ("--vbrief-staleness", "(off)", "Flag entries referencing newer vBRIEFs."),
441
- ("--since WINDOW", "(all)", "Time window (e.g. '24h', '7d')."),
442
- ("--action VERB", "(all)", "Filter by audit verb."),
443
- ("--repo owner/name", "(git remote)", "Explicit repo override."),
444
- ),
445
- examples=(
446
- "task triage:audit",
447
- "task triage:audit -- --format=json --since=7d",
448
- ),
449
- see_also=(
450
- "task triage:queue",
451
- "task triage:history",
452
- "#1119 / D11",
453
- ),
454
- ),
455
- "task triage:show": _entry(
456
- name="task triage:show",
457
- summary="Per-issue detail with optional drift diff",
458
- refs="(D11 / #1128)",
459
- description=(
460
- "Per-issue read-only detail (cached upstream payload + "
461
- "latest triage decision + audit timeline). Useful before "
462
- "running triage:accept / triage:defer to confirm context."
463
- ),
464
- usage="task triage:show -- <N> [--repo=owner/name]",
465
- flags=(
466
- ("<N>", "(required)", "Issue number (positional)."),
467
- ("--repo owner/name", "(git remote)", "Explicit repo override."),
468
- ),
469
- examples=(
470
- "task triage:show -- 42",
471
- "task triage:show -- 42 --repo deftai/directive",
472
- ),
473
- see_also=(
474
- "task triage:queue",
475
- "task triage:status",
476
- "#1119 / D11",
477
- ),
478
- ),
479
- "task triage:scope": _entry(
480
- name="task triage:scope",
481
- summary="Active subscription inspection",
482
- refs="(D12 / #1131, D14 / #1133, D14c / #1182)",
483
- description=(
484
- "Inspect / mutate / diff the typed plan.policy.triageScope[] "
485
- "subscription and the triageScopeIgnores[] companion list. "
486
- "Defaults to read-only --list."
487
- ),
488
- usage="task triage:scope -- [--list] [--add-label=L | --add-milestone=M | --ignore-label=L] [--diff-from-upstream --repo OWNER/NAME] [--refresh-denominator --repo OWNER/NAME --count N]",
489
- flags=(
490
- ("--list", "(default)", "Print the active subscription rules."),
491
- ("--add-label L", "(none)", "Append a label rule to the subscription."),
492
- ("--add-milestone M", "(none)", "Append a milestone rule."),
493
- ("--ignore-label L", "(none)", "Append a label to triageScopeIgnores[]."),
494
- ("--diff-from-upstream", "(off)", "Diff cached scope vs live upstream."),
495
- ("--repo owner/name", "(git remote)", "Required for --diff-from-upstream."),
496
- ),
497
- examples=(
498
- "task triage:scope",
499
- "task triage:scope -- --add-label='area:swarm'",
500
- ),
501
- see_also=(
502
- "task triage:subscribe",
503
- "task triage:scope-drift",
504
- "#1119 / D12",
505
- ),
506
- ),
507
- "task triage:scope-drift": _entry(
508
- name="task triage:scope-drift",
509
- summary="Detect labels/milestones outside subscription",
510
- refs="(D14 / #1133)",
511
- description=(
512
- "Detect subscription drift: labels / milestones that appear "
513
- "on cached open issues but are NOT in plan.policy.triageScope. "
514
- "Suggests `task triage:subscribe` follow-ups or explicit "
515
- "ignore-list mutations."
516
- ),
517
- usage="task triage:scope-drift [-- --ignore-label=L | --ignore-milestone=M]",
518
- flags=(
519
- ("--ignore-label L", "(none)", "Append label to triageScopeIgnores[]."),
520
- ("--ignore-milestone M", "(none)", "Append milestone to triageScopeIgnores[]."),
521
- ),
522
- examples=(
523
- "task triage:scope-drift",
524
- "task triage:scope-drift -- --ignore-label='wontfix'",
525
- ),
526
- see_also=(
527
- "task triage:scope",
528
- "task triage:subscribe",
529
- "#1119 / D14",
530
- ),
531
- ),
532
- "task triage:classify": _entry(
533
- name="task triage:classify",
534
- summary="Inspect / validate auto-classification surface",
535
- refs="(D10 / #1129)",
536
- description=(
537
- "Inspect or validate the auto-classification rule set. "
538
- "--list renders effective rules (framework universal first, "
539
- "consumer overrides next). --validate exits non-zero on a "
540
- "malformed plan.policy.triageAutoClassify."
541
- ),
542
- usage="task triage:classify -- [--list | --validate]",
543
- flags=(
544
- ("--list", "(default)", "Print effective rules + hold markers."),
545
- ("--validate", "(off)", "Validate plan.policy.triageAutoClassify."),
546
- ),
547
- examples=(
548
- "task triage:classify -- --list",
549
- "task triage:classify -- --validate",
550
- ),
551
- see_also=(
552
- "task triage:bootstrap",
553
- "task triage:queue",
554
- "#1119 / D10",
555
- ),
556
- ),
557
- # --- Lifecycle -------------------------------------------------------
558
- "task triage:bootstrap": _entry(
559
- name="task triage:bootstrap",
560
- summary="Populate cache + auto-classify",
561
- refs="(D10 / #845 Story 6)",
562
- description=(
563
- "Idempotent bootstrap installer: populates the unified cache "
564
- "via cache:fetch-all, runs auto-classification, and emits a "
565
- "structured recap. --json emits one object per step for "
566
- "scripted consumers."
567
- ),
568
- usage="task triage:bootstrap [-- --repo owner/name] [--state STR] [--limit N] [--batch-size N] [--delay-ms N] [--fetch-timeout-s S] [--quiet] [--json]",
569
- flags=(
570
- ("--repo owner/name", "(git remote)", "Upstream repo to populate."),
571
- ("--state STR", "open", "Issue state filter forwarded to cache:fetch-all."),
572
- ("--limit N", "(none)", "Cap on issues fetched."),
573
- ("--fetch-timeout-s S", "(env, 300)", "Watchdog wall-clock cap (#952)."),
574
- ("--quiet", "(off)", "Suppress per-step progress lines."),
575
- ("--json", "(off)", "Structured JSON output."),
576
- ),
577
- examples=(
578
- "task triage:bootstrap",
579
- "task triage:bootstrap -- --repo deftai/directive --limit 50",
580
- ),
581
- see_also=(
582
- "task triage:welcome",
583
- "task triage:classify",
584
- "task cache:fetch-all",
585
- "#1119 / D10",
586
- ),
587
- ),
588
- "task triage:welcome": _entry(
589
- name="task triage:welcome",
590
- summary="Single-entry-point upgrade ritual",
591
- refs="(N3 / #1143)",
592
- description=(
593
- "6-phase onboarding ritual: detect prior state, prompt for "
594
- "subscription scope, run triage:bootstrap, prompt for "
595
- "wipCap, offer WIP relief, print triage:summary. Idempotent "
596
- "on re-run; safe entrypoint for fresh consumers."
597
- ),
598
- usage="task triage:welcome [-- --no-subprocess]",
599
- flags=(
600
- ("--no-subprocess", "(off)", "Dry-mode: don't shell out to sibling tasks."),
601
- ),
602
- examples=(
603
- "task triage:welcome",
604
- ),
605
- see_also=(
606
- "task triage:bootstrap",
607
- "task triage:summary",
608
- "#1119 / N3",
609
- ),
610
- ),
611
- "task triage:reconcile": _entry(
612
- name="task triage:reconcile",
613
- summary="Self-heal audit log from on-disk vBRIEFs",
614
- refs="(#1468)",
615
- description=(
616
- "Idempotent repair verb: derive missing `accept` decisions "
617
- "for proposed/pending/active vBRIEFs that carry an "
618
- "x-vbrief/github-issue reference but have no entry in "
619
- "vbrief/.eval/candidates.jsonl. Recovers triage state after "
620
- "the gitignored audit log is reset/lost (#1464) without a "
621
- "full cache re-fetch. Never overrides an existing decision, "
622
- "so a re-run is a no-op."
623
- ),
624
- usage="task triage:reconcile [-- --repo owner/name] [--dry-run] [--json]",
625
- flags=(
626
- ("--repo owner/name", "(ref URI / git remote)", "Fallback repo for refs lacking owner/name."),
627
- ("--dry-run", "(off)", "Report what would be restored without writing."),
628
- ("--json", "(off)", "Structured JSON output."),
629
- ),
630
- examples=(
631
- "task triage:reconcile -- --dry-run",
632
- "task triage:reconcile -- --repo deftai/directive",
633
- ),
634
- see_also=(
635
- "task triage:summary",
636
- "task triage:bootstrap",
637
- "#1119 / #1468",
638
- ),
639
- ),
640
- # --- Bulk variants (#845 Story 4) ------------------------------------
641
- "task triage:bulk-accept": _entry(
642
- name="task triage:bulk-accept",
643
- summary="Bulk accept cached candidates by filter",
644
- refs="(#845 Story 4 / #915)",
645
- description=(
646
- "Bulk-accept every cached candidate matching the supplied "
647
- "filters. Terminal records (accept/reject/mark-duplicate) "
648
- "ALWAYS short-circuit. Add --re-action to act on issues "
649
- "whose LATEST audit record is defer/needs-ac."
650
- ),
651
- usage="task triage:bulk-accept -- --repo OWNER/NAME [--label L] [--author A] [--age-days N] [--cluster C] [--re-action]",
652
- flags=(
653
- ("--repo OWNER/NAME", "(required)", "Upstream repo."),
654
- ("--label L", "(none)", "Filter: only issues carrying this label."),
655
- ("--author A", "(none)", "Filter: only issues by this author."),
656
- ("--age-days N", "(none)", "Filter: only issues older than N days."),
657
- ("--cluster C", "(none)", "Filter: only issues tagged cluster:<C>."),
658
- ("--re-action", "(off)", "Re-action defer/needs-ac records."),
659
- ),
660
- examples=(
661
- "task triage:bulk-accept -- --repo deftai/directive --label good-first-issue",
662
- ),
663
- see_also=(
664
- "task triage:accept",
665
- "task triage:bulk-defer",
666
- "#1119 / #845",
667
- ),
668
- ),
669
- "task triage:bulk-reject": _entry(
670
- name="task triage:bulk-reject",
671
- summary="Bulk reject cached candidates by filter",
672
- refs="(#845 Story 4 / #915)",
673
- description=(
674
- "Bulk-reject every cached candidate matching the filters. "
675
- "--reason is required and is recorded both in the audit log "
676
- "and on the upstream close comment."
677
- ),
678
- usage="task triage:bulk-reject -- --repo OWNER/NAME --reason 'why' [--label L] [--author A] [--age-days N] [--cluster C] [--re-action]",
679
- flags=(
680
- ("--repo OWNER/NAME", "(required)", "Upstream repo."),
681
- ("--reason 'why'", "(required)", "Reason recorded on close comments."),
682
- ("--label L", "(none)", "Filter: only issues with this label."),
683
- ("--re-action", "(off)", "Re-action defer/needs-ac records."),
684
- ),
685
- examples=(
686
- "task triage:bulk-reject -- --repo deftai/directive --reason 'no longer relevant' --age-days 365",
687
- ),
688
- see_also=(
689
- "task triage:reject",
690
- "task triage:bulk-accept",
691
- "#1119 / #845",
692
- ),
693
- ),
694
- "task triage:bulk-defer": _entry(
695
- name="task triage:bulk-defer",
696
- summary="Bulk defer cached candidates by filter",
697
- refs="(#845 Story 4 / #915)",
698
- description=(
699
- "Bulk-defer every cached candidate matching the filters. "
700
- "Use --re-action to re-action issues whose latest record "
701
- "is already defer/needs-ac."
702
- ),
703
- usage="task triage:bulk-defer -- --repo OWNER/NAME [--label L] [--author A] [--age-days N] [--cluster C] [--re-action]",
704
- flags=(
705
- ("--repo OWNER/NAME", "(required)", "Upstream repo."),
706
- ("--label L", "(none)", "Filter: only issues with this label."),
707
- ("--re-action", "(off)", "Re-action defer/needs-ac records."),
708
- ),
709
- examples=(
710
- "task triage:bulk-defer -- --repo deftai/directive --label needs-design",
711
- ),
712
- see_also=(
713
- "task triage:defer",
714
- "task triage:bulk-accept",
715
- "#1119 / #845",
716
- ),
717
- ),
718
- "task triage:bulk-needs-ac": _entry(
719
- name="task triage:bulk-needs-ac",
720
- summary="Bulk needs-ac cached candidates by filter",
721
- refs="(#845 Story 4 / #915)",
722
- description=(
723
- "Bulk-needs-ac every cached candidate matching the filters; "
724
- "posts the canned AC-request comment upstream on each."
725
- ),
726
- usage="task triage:bulk-needs-ac -- --repo OWNER/NAME [--label L] [--author A] [--age-days N] [--cluster C] [--re-action]",
727
- flags=(
728
- ("--repo OWNER/NAME", "(required)", "Upstream repo."),
729
- ("--label L", "(none)", "Filter: only issues with this label."),
730
- ("--re-action", "(off)", "Re-action defer/needs-ac records."),
731
- ),
732
- examples=(
733
- "task triage:bulk-needs-ac -- --repo deftai/directive --age-days 30",
734
- ),
735
- see_also=(
736
- "task triage:needs-ac",
737
- "#1119 / #845",
738
- ),
739
- ),
740
- "task triage:refresh-active": _entry(
741
- name="task triage:refresh-active",
742
- summary="Pre-swarm freshness gate for vbrief/active/",
743
- refs="(#845 Story 4)",
744
- description=(
745
- "Detect drift between cached and live `gh issue view` for "
746
- "every issue referenced from vbrief/active/*.vbrief.json. "
747
- "Interactive prompts on each drift: proceed-with-stale, "
748
- "refresh-and-update-local, or defer-from-this-batch."
749
- ),
750
- usage="task triage:refresh-active [-- --project-root PATH]",
751
- flags=(
752
- ("--project-root PATH", "(cwd)", "Project root containing vbrief/active/."),
753
- ),
754
- examples=(
755
- "task triage:refresh-active",
756
- ),
757
- see_also=(
758
- "task verify:cache-fresh",
759
- "task triage:audit",
760
- "#1119 / #845",
761
- ),
762
- ),
763
- "task triage:smoketest": _entry(
764
- name="task triage:smoketest",
765
- summary="End-to-end synthetic smoketest (hermetic)",
766
- refs="(N6 / #1146)",
767
- description=(
768
- "End-to-end synthetic test of the cache-as-operator-"
769
- "working-set surface. Runs the full triage lifecycle against "
770
- "the bundled fixture and asserts the expected audit-log "
771
- "shape at every stage. Exit 0 on PASS, 1 on first failure."
772
- ),
773
- usage="task triage:smoketest [-- --verbose] [--keep-tempdir] [--cache-only]",
774
- flags=(
775
- ("--verbose", "(off)", "Print each assertion to stderr as it runs."),
776
- ("--keep-tempdir", "(off)", "Don't clean up the temp working dir on exit."),
777
- ("--cache-only", "(off)", "Skip vBRIEF-mutating stages (faster)."),
778
- ),
779
- examples=(
780
- "task triage:smoketest",
781
- "task triage:smoketest -- --cache-only --verbose",
782
- ),
783
- see_also=(
784
- "task triage:bootstrap",
785
- "#1119 / N6",
786
- ),
787
- ),
788
- # --- Subscription mutation ------------------------------------------
789
- "task triage:subscribe": _entry(
790
- name="task triage:subscribe",
791
- summary="Add label/milestone/issue to subscription",
792
- refs="(D14 / #1133)",
793
- description=(
794
- "Subscribe to a label / milestone / issue by appending a "
795
- "rule to plan.policy.triageScope[]. Emits a JSONL audit "
796
- "record to vbrief/.eval/subscription-history.jsonl."
797
- ),
798
- usage="task triage:subscribe -- (--label=L | --milestone=M | --issue=N)",
799
- flags=(
800
- ("--label L", "(none)", "Subscribe to a label."),
801
- ("--milestone M", "(none)", "Subscribe to a milestone."),
802
- ("--issue N", "(none)", "Subscribe to a single issue."),
803
- ),
804
- examples=(
805
- "task triage:subscribe -- --label='area:swarm'",
806
- "task triage:subscribe -- --milestone='v0.32.0'",
807
- ),
808
- see_also=(
809
- "task triage:unsubscribe",
810
- "task triage:scope",
811
- "#1119 / D14",
812
- ),
813
- ),
814
- "task triage:unsubscribe": _entry(
815
- name="task triage:unsubscribe",
816
- summary="Remove label/milestone/issue from subscription",
817
- refs="(D14 / #1133)",
818
- description=(
819
- "Unsubscribe a label / milestone / issue by removing the "
820
- "matching rule from plan.policy.triageScope[]. Idempotent "
821
- "(no-op on already-missing entries). Emits a JSONL audit "
822
- "record."
823
- ),
824
- usage="task triage:unsubscribe -- (--label=L | --milestone=M | --issue=N)",
825
- flags=(
826
- ("--label L", "(none)", "Unsubscribe a label."),
827
- ("--milestone M", "(none)", "Unsubscribe a milestone."),
828
- ("--issue N", "(none)", "Unsubscribe a single issue."),
829
- ),
830
- examples=(
831
- "task triage:unsubscribe -- --label='area:swarm'",
832
- ),
833
- see_also=(
834
- "task triage:subscribe",
835
- "task triage:scope",
836
- "#1119 / D14",
837
- ),
838
- ),
839
- # --- Archive / rotation (forward-looking placeholders) --------------
840
- "task triage:audit:prune": _entry(
841
- name="task triage:audit:prune",
842
- summary="Operator-invoked archive of closed-terminal entries",
843
- refs="(D19, coming)",
844
- description=(
845
- "Move closed-terminal audit entries (accept/reject/"
846
- "mark-duplicate after the upstream issue is closed) into "
847
- "the archive sidecar so the live cache stays focused on "
848
- "open work."
849
- ),
850
- usage="task triage:audit:prune [-- --dry-run] [--older-than-days N]",
851
- flags=(
852
- ("--dry-run", "(off)", "Preview eligible entries without writing."),
853
- ("--older-than-days N", "30", "Age threshold."),
854
- ),
855
- examples=(
856
- "task triage:audit:prune -- --dry-run",
857
- ),
858
- see_also=(
859
- "task triage:archive-list",
860
- "task triage:restore-from-archive",
861
- "#1119 / D19",
862
- ),
863
- placeholder=True,
864
- ),
865
- "task triage:archive-list": _entry(
866
- name="task triage:archive-list",
867
- summary="List archived entries",
868
- refs="(D19, coming)",
869
- description=(
870
- "Read-only listing of archived audit entries from the "
871
- "archive sidecar. Useful before restoring an entry."
872
- ),
873
- usage="task triage:archive-list [-- --since=WINDOW]",
874
- flags=(
875
- ("--since WINDOW", "(all)", "Time window (e.g. '30d', '6mo')."),
876
- ),
877
- examples=(
878
- "task triage:archive-list",
879
- ),
880
- see_also=(
881
- "task triage:audit:prune",
882
- "#1119 / D19",
883
- ),
884
- placeholder=True,
885
- ),
886
- "task triage:restore-from-archive": _entry(
887
- name="task triage:restore-from-archive",
888
- summary="Restore archived entry to live cache",
889
- refs="(D19, coming)",
890
- description=(
891
- "Move an archived entry back to the live audit log so it "
892
- "becomes visible to triage:queue / triage:audit again."
893
- ),
894
- usage="task triage:restore-from-archive -- <N>",
895
- flags=(
896
- ("<N>", "(required)", "Archived entry number to restore."),
897
- ),
898
- examples=(
899
- "task triage:restore-from-archive -- 42",
900
- ),
901
- see_also=(
902
- "task triage:archive-list",
903
- "#1119 / D19",
904
- ),
905
- placeholder=True,
906
- ),
907
- "task triage:audit-log:rotate": _entry(
908
- name="task triage:audit-log:rotate",
909
- summary="Rotate candidates.jsonl when bounded",
910
- refs="(D20, coming)",
911
- description=(
912
- "Rotate vbrief/.eval/candidates.jsonl when it exceeds the "
913
- "configured bound. Compacts terminal entries and preserves "
914
- "the open-work tail."
915
- ),
916
- usage="task triage:audit-log:rotate [-- --max-lines N]",
917
- flags=(
918
- ("--max-lines N", "(consumer default)", "Bound at which rotation fires."),
919
- ),
920
- examples=(
921
- "task triage:audit-log:rotate -- --max-lines 10000",
922
- ),
923
- see_also=(
924
- "task triage:audit:prune",
925
- "#1119 / D20",
926
- ),
927
- placeholder=True,
928
- ),
929
- "task triage:metrics": _entry(
930
- name="task triage:metrics",
931
- summary="Trend lines from summary-history.jsonl",
932
- refs="(D17, coming)",
933
- description=(
934
- "Print trend lines computed from "
935
- "vbrief/.eval/summary-history.jsonl: decisions-per-day, "
936
- "defer/accept ratio, stale-defer drift, etc."
937
- ),
938
- usage="task triage:metrics [-- --window=7d] [--format=text|json]",
939
- flags=(
940
- ("--window WINDOW", "7d", "Time window over which to aggregate."),
941
- ("--format text|json", "text", "Output shape."),
942
- ),
943
- examples=(
944
- "task triage:metrics -- --window 30d",
945
- ),
946
- see_also=(
947
- "task triage:summary",
948
- "#1119 / D17",
949
- ),
950
- placeholder=True,
951
- ),
952
- # --- Scope lifecycle verbs (#845, D1, D15, D18) ----------------------
953
- "task scope:promote": _entry(
954
- name="task scope:promote",
955
- summary="proposed/ -> pending/ (set status pending)",
956
- refs="(#845, D18 / #1119)",
957
- description=(
958
- "Promote a vBRIEF scope from vbrief/proposed/ to "
959
- "vbrief/pending/ and set plan.status='pending'. D18 adds "
960
- "--from-issue=N for cache-reciprocity-checked promotion."
961
- ),
962
- usage="task scope:promote -- <file> [--force] | task scope:promote -- --from-issue=N",
963
- flags=(
964
- ("<file>", "(required)", "Path to vBRIEF (relative resolved against project root)."),
965
- ("--from-issue=N", "(none)", "Promote with cache-reciprocity check (D18)."),
966
- ("--force", "(off)", "Override the WIP cap (#1124 / D4); records audit entry."),
967
- ("--project-root PATH", "(detected)", "Consumer project root override."),
968
- ),
969
- examples=(
970
- "task scope:promote -- vbrief/proposed/2026-05-19-foo.vbrief.json",
971
- "task scope:promote -- --from-issue=1150",
972
- ),
973
- see_also=(
974
- "task scope:demote",
975
- "task scope:activate",
976
- "task vbrief:activate",
977
- "#1119 / D18",
978
- ),
979
- ),
980
- "task scope:demote": _entry(
981
- name="task scope:demote",
982
- summary="pending/ -> proposed/ (set status proposed)",
983
- refs="(D1 / #1121)",
984
- description=(
985
- "Demote a vBRIEF scope from vbrief/pending/ back to "
986
- "vbrief/proposed/ and append a structured audit entry "
987
- "(including a demote_meta block) to "
988
- "vbrief/.eval/scope-lifecycle.jsonl. Supports single-file "
989
- "and --batch (cohort shrink / cap relief) modes."
990
- ),
991
- usage="task scope:demote -- <file> [--reason TEXT] | task scope:demote -- --batch [--older-than-days N]",
992
- flags=(
993
- ("<file>", "(required for single)", "Path to vBRIEF."),
994
- ("--batch", "(off)", "Batch mode: demote every pending older than --older-than-days."),
995
- ("--older-than-days N", "45", "Batch-mode age threshold."),
996
- ("--reason TEXT", "operator-requested", "Free-text reason for single-demote."),
997
- ("--actor STR", "operator", "Actor identity recorded in the audit entry."),
998
- ),
999
- examples=(
1000
- "task scope:demote -- vbrief/pending/2026-05-19-foo.vbrief.json",
1001
- "task scope:demote -- --batch --older-than-days 60",
1002
- ),
1003
- see_also=(
1004
- "task scope:undo",
1005
- "task scope:promote",
1006
- "#1119 / D1",
1007
- ),
1008
- ),
1009
- "task scope:activate": _entry(
1010
- name="task scope:activate",
1011
- summary="pending/ -> active/ (set status running)",
1012
- refs="(#845)",
1013
- description=(
1014
- "Activate a pending vBRIEF: move to vbrief/active/ and set "
1015
- "plan.status='running'. Required step before "
1016
- "vbrief:preflight will exit 0 (the #810 implementation "
1017
- "intent gate)."
1018
- ),
1019
- usage="task scope:activate -- <file>",
1020
- flags=(
1021
- ("<file>", "(required)", "Path to vBRIEF."),
1022
- ("--project-root PATH", "(detected)", "Consumer project root override."),
1023
- ),
1024
- examples=(
1025
- "task scope:activate -- vbrief/pending/2026-05-19-foo.vbrief.json",
1026
- ),
1027
- see_also=(
1028
- "task vbrief:activate",
1029
- "task scope:complete",
1030
- "#1119 / #845",
1031
- ),
1032
- ),
1033
- "task scope:complete": _entry(
1034
- name="task scope:complete",
1035
- summary="active/ -> completed/ (set status completed)",
1036
- refs="(#845)",
1037
- description=(
1038
- "Mark an active vBRIEF complete and move it to "
1039
- "vbrief/completed/. Terminal transition; use scope:undo if "
1040
- "you need reversibility (refused on terminal actions per "
1041
- "D15)."
1042
- ),
1043
- usage="task scope:complete -- <file>",
1044
- flags=(
1045
- ("<file>", "(required)", "Path to vBRIEF."),
1046
- ("--project-root PATH", "(detected)", "Consumer project root override."),
1047
- ),
1048
- examples=(
1049
- "task scope:complete -- vbrief/active/2026-05-19-foo.vbrief.json",
1050
- ),
1051
- see_also=(
1052
- "task scope:fail",
1053
- "task scope:cancel",
1054
- "#1119 / #845",
1055
- ),
1056
- ),
1057
- "task scope:fail": _entry(
1058
- name="task scope:fail",
1059
- summary="active/ -> completed/ (set status failed)",
1060
- refs="(#845, #614)",
1061
- description=(
1062
- "Terminal failed transition. Mirrors scope:complete but "
1063
- "sets plan.status='failed'. Use when a scope was attempted "
1064
- "but could not be completed (external blocker, "
1065
- "infeasibility, exhausted retries) and should NOT be "
1066
- "cancelled."
1067
- ),
1068
- usage="task scope:fail -- <file>",
1069
- flags=(
1070
- ("<file>", "(required)", "Path to vBRIEF."),
1071
- ("--project-root PATH", "(detected)", "Consumer project root override."),
1072
- ),
1073
- examples=(
1074
- "task scope:fail -- vbrief/active/2026-05-19-foo.vbrief.json",
1075
- ),
1076
- see_also=(
1077
- "task scope:complete",
1078
- "task scope:cancel",
1079
- "#1119 / #845",
1080
- ),
1081
- ),
1082
- "task scope:cancel": _entry(
1083
- name="task scope:cancel",
1084
- summary="any -> cancelled/ (set status cancelled)",
1085
- refs="(#845)",
1086
- description=(
1087
- "Cancel a vBRIEF from any folder. Use when the scope is no "
1088
- "longer wanted / superseded / obsolete (vs scope:fail which "
1089
- "means \"tried and failed\")."
1090
- ),
1091
- usage="task scope:cancel -- <file>",
1092
- flags=(
1093
- ("<file>", "(required)", "Path to vBRIEF."),
1094
- ("--project-root PATH", "(detected)", "Consumer project root override."),
1095
- ),
1096
- examples=(
1097
- "task scope:cancel -- vbrief/pending/2026-05-19-foo.vbrief.json",
1098
- ),
1099
- see_also=(
1100
- "task scope:restore",
1101
- "task scope:undo",
1102
- "#1119 / #845",
1103
- ),
1104
- ),
1105
- "task scope:restore": _entry(
1106
- name="task scope:restore",
1107
- summary="cancelled/ -> proposed/ (set status proposed)",
1108
- refs="(#845)",
1109
- description=(
1110
- "Restore a cancelled vBRIEF back to vbrief/proposed/ "
1111
- "(status='proposed'). Use to re-enter the lifecycle after a "
1112
- "scope:cancel."
1113
- ),
1114
- usage="task scope:restore -- <file>",
1115
- flags=(
1116
- ("<file>", "(required)", "Path to vBRIEF."),
1117
- ("--project-root PATH", "(detected)", "Consumer project root override."),
1118
- ),
1119
- examples=(
1120
- "task scope:restore -- vbrief/cancelled/2026-05-19-foo.vbrief.json",
1121
- ),
1122
- see_also=(
1123
- "task scope:cancel",
1124
- "task scope:promote",
1125
- "#1119 / #845",
1126
- ),
1127
- ),
1128
- "task scope:block": _entry(
1129
- name="task scope:block",
1130
- summary="stays in active/ (set status blocked)",
1131
- refs="(#845)",
1132
- description=(
1133
- "Mark an active scope as blocked without moving it out of "
1134
- "active/. Use when waiting on an external dependency."
1135
- ),
1136
- usage="task scope:block -- <file>",
1137
- flags=(
1138
- ("<file>", "(required)", "Path to vBRIEF."),
1139
- ("--project-root PATH", "(detected)", "Consumer project root override."),
1140
- ),
1141
- examples=(
1142
- "task scope:block -- vbrief/active/2026-05-19-foo.vbrief.json",
1143
- ),
1144
- see_also=(
1145
- "task scope:unblock",
1146
- "#1119 / #845",
1147
- ),
1148
- ),
1149
- "task scope:unblock": _entry(
1150
- name="task scope:unblock",
1151
- summary="stays in active/ (set status running)",
1152
- refs="(#845)",
1153
- description=(
1154
- "Clear a blocked status on an active scope, returning it "
1155
- "to plan.status='running'."
1156
- ),
1157
- usage="task scope:unblock -- <file>",
1158
- flags=(
1159
- ("<file>", "(required)", "Path to vBRIEF."),
1160
- ("--project-root PATH", "(detected)", "Consumer project root override."),
1161
- ),
1162
- examples=(
1163
- "task scope:unblock -- vbrief/active/2026-05-19-foo.vbrief.json",
1164
- ),
1165
- see_also=(
1166
- "task scope:block",
1167
- "#1119 / #845",
1168
- ),
1169
- ),
1170
- "task scope:undo": _entry(
1171
- name="task scope:undo",
1172
- summary="Undo demote/cancel/restore via audit-log id",
1173
- refs="(D15 / #1134)",
1174
- description=(
1175
- "Reverse a scope-lifecycle audit entry by decision_id or "
1176
- "every entry tagged with batch_id. Terminal actions "
1177
- "(complete / fail) are REFUSED -- use a fresh scope:promote "
1178
- "instead. Supports --dry-run preview and --latest "
1179
- "convenience selector."
1180
- ),
1181
- usage="task scope:undo -- <decision_id> | --decision-id=<uuid> | --batch-id=<uuid> | --latest [--dry-run]",
1182
- flags=(
1183
- ("<decision_id>", "(one of)", "Positional decision_id (shorthand for --decision-id)."),
1184
- ("--decision-id UUID", "(one of)", "Decision id of a single audit entry to undo."),
1185
- ("--batch-id UUID", "(one of)", "Reverse every entry tagged with this batch_id."),
1186
- ("--latest", "(one of)", "Reverse the most-recent reversible entry."),
1187
- ("--dry-run", "(off)", "Preview the reversal without writing."),
1188
- ("--actor STR", "operator", "Actor identity recorded on the new undo entry."),
1189
- ),
1190
- examples=(
1191
- "task scope:undo -- --latest --dry-run",
1192
- "task scope:undo -- --batch-id=00000000-0000-0000-0000-000000000001",
1193
- ),
1194
- see_also=(
1195
- "task scope:demote",
1196
- "task triage:reset",
1197
- "#1119 / D15",
1198
- ),
1199
- ),
1200
- "task scope:decompose": _entry(
1201
- name="task scope:decompose",
1202
- summary="Apply/check an approved epic story decomposition",
1203
- refs="(deft-directive-decompose skill)",
1204
- description=(
1205
- "Validate or apply an approved epic/phase -> story "
1206
- "decomposition draft. The draft is a temporary proposal "
1207
- "artifact, not a vBRIEF. Writes pending child vBRIEFs and "
1208
- "wires references back into the parent epic."
1209
- ),
1210
- usage="task scope:decompose -- <parent> --draft <draft> [--check] [--date YYYY-MM-DD]",
1211
- flags=(
1212
- (
1213
- "<parent>",
1214
- "(conditional)",
1215
- "Parent epic/phase vBRIEF path; required with --draft, omit only for --check no-op.",
1216
- ),
1217
- (
1218
- "--draft PATH",
1219
- "(required)",
1220
- "Approved decomposition JSON draft; prefer vbrief/.eval/decompositions/<parent-slug>.json.",
1221
- ),
1222
- ("--check", "(off)", "Validate only; do not write."),
1223
- ("--date YYYY-MM-DD", "today", "Creation date for generated child filenames."),
1224
- ),
1225
- examples=(
1226
- "task scope:decompose -- vbrief/active/epic.vbrief.json --draft vbrief/.eval/decompositions/epic.json --check",
1227
- ),
1228
- see_also=(
1229
- "task scope:promote",
1230
- "skills/deft-directive-decompose/SKILL.md",
1231
- ),
1232
- ),
1233
- }
1234
-
1235
-
1236
- # ---------------------------------------------------------------------------
1237
- # Category structure for bare-list rendering
1238
- # ---------------------------------------------------------------------------
1239
-
1240
-
1241
- CATEGORIES_TRIAGE: list[tuple[str, list[str]]] = [
1242
- (
1243
- "Session-start",
1244
- [
1245
- "task triage:summary",
1246
- "task verify:cache-fresh",
1247
- ],
1248
- ),
1249
- (
1250
- "State verbs (mutate audit log)",
1251
- [
1252
- "task triage:accept",
1253
- "task triage:defer",
1254
- "task triage:reject",
1255
- "task triage:needs-ac",
1256
- "task triage:mark-duplicate",
1257
- "task triage:reset",
1258
- "task triage:status",
1259
- "task triage:history",
1260
- ],
1261
- ),
1262
- (
1263
- "Read verbs",
1264
- [
1265
- "task triage:queue",
1266
- "task triage:audit",
1267
- "task triage:show",
1268
- "task triage:scope",
1269
- "task triage:scope-drift",
1270
- "task triage:classify",
1271
- ],
1272
- ),
1273
- (
1274
- "Lifecycle",
1275
- [
1276
- "task triage:bootstrap",
1277
- "task triage:welcome",
1278
- "task triage:reconcile",
1279
- ],
1280
- ),
1281
- (
1282
- "Bulk variants",
1283
- [
1284
- "task triage:bulk-accept",
1285
- "task triage:bulk-reject",
1286
- "task triage:bulk-defer",
1287
- "task triage:bulk-needs-ac",
1288
- "task triage:refresh-active",
1289
- "task triage:smoketest",
1290
- ],
1291
- ),
1292
- (
1293
- "Subscription mutation",
1294
- [
1295
- "task triage:subscribe",
1296
- "task triage:unsubscribe",
1297
- ],
1298
- ),
1299
- (
1300
- "Archive / rotation",
1301
- [
1302
- "task triage:audit:prune",
1303
- "task triage:archive-list",
1304
- "task triage:restore-from-archive",
1305
- "task triage:audit-log:rotate",
1306
- "task triage:metrics",
1307
- ],
1308
- ),
1309
- ]
1310
-
1311
-
1312
- CATEGORIES_SCOPE: list[tuple[str, list[str]]] = [
1313
- (
1314
- "Promote / demote",
1315
- [
1316
- "task scope:promote",
1317
- "task scope:demote",
1318
- ],
1319
- ),
1320
- (
1321
- "Activate / complete",
1322
- [
1323
- "task scope:activate",
1324
- "task scope:complete",
1325
- "task scope:fail",
1326
- "task scope:cancel",
1327
- "task scope:block",
1328
- "task scope:unblock",
1329
- ],
1330
- ),
1331
- (
1332
- "Reversibility",
1333
- [
1334
- "task scope:undo",
1335
- "task scope:restore",
1336
- ],
1337
- ),
1338
- (
1339
- "Decomposition",
1340
- [
1341
- "task scope:decompose",
1342
- ],
1343
- ),
1344
- ]
1345
-
1346
-
1347
- # ---------------------------------------------------------------------------
1348
- # Script -> (subcommand -> verb) mapping for the intercept_help shim
1349
- # ---------------------------------------------------------------------------
1350
- #
1351
- # Each verb script is expected to call ``intercept_help(<script_name>, argv)``
1352
- # at the very top of its ``main()``. The shim looks up the script under
1353
- # ``SCRIPT_SUBCOMMAND_MAP`` to resolve which verb the user is running:
1354
- #
1355
- # * If the script has only ``{"__default__": "task triage:X"}``, the verb
1356
- # is fixed and we print help unconditionally on --help/-h.
1357
- # * If the script declares subcommand entries, the shim inspects argv for
1358
- # the first positional non-flag arg and looks it up. The fallback when
1359
- # no positional matches is the ``__default__`` key (if present); otherwise
1360
- # ``--help`` is delegated to argparse so the user still gets *some* help.
1361
-
1362
-
1363
- SCRIPT_SUBCOMMAND_MAP: dict[str, dict[str, str]] = {
1364
- "triage_actions": {
1365
- "accept": "task triage:accept",
1366
- "reject": "task triage:reject",
1367
- "defer": "task triage:defer",
1368
- "needs-ac": "task triage:needs-ac",
1369
- "mark-duplicate": "task triage:mark-duplicate",
1370
- "status": "task triage:status",
1371
- "reset": "task triage:reset",
1372
- "history": "task triage:history",
1373
- },
1374
- "triage_bootstrap": {"__default__": "task triage:bootstrap"},
1375
- "triage_bulk": {
1376
- "accept": "task triage:bulk-accept",
1377
- "reject": "task triage:bulk-reject",
1378
- "defer": "task triage:bulk-defer",
1379
- "needs-ac": "task triage:bulk-needs-ac",
1380
- },
1381
- "triage_refresh": {"__default__": "task triage:refresh-active"},
1382
- "triage_classify": {"__default__": "task triage:classify"},
1383
- "triage_scope": {"__default__": "task triage:scope"},
1384
- "triage_scope_drift": {"__default__": "task triage:scope-drift"},
1385
- # ``triage_subscribe`` is the ONLY multi-subcommand entry that also
1386
- # declares ``__default__``. The Taskfile (``tasks/triage-subscribe.yml``)
1387
- # always passes the positional (``subscribe`` or ``unsubscribe``) as the
1388
- # first arg, so under the documented user-facing surface the fallback
1389
- # never fires. The fallback exists for two off-Taskfile paths:
1390
- # (1) direct invocations like ``python scripts/triage_subscribe.py --help``
1391
- # (no positional) -- the operator sees ``task triage:subscribe`` help,
1392
- # which is the canonical/primary verb in the subscribe<->unsubscribe
1393
- # pair (you subscribe first; unsubscribe is the inverse).
1394
- # (2) a hypothetical alternate Taskfile that drops the positional --
1395
- # same behaviour, documented contract.
1396
- # Pinned by ``test_intercept_help_triage_subscribe_*`` in
1397
- # ``tests/test_triage_help.py``; Greptile finding 3267953653 on PR #1227.
1398
- # Refs #1228 (this PR's docs/coverage gap).
1399
- "triage_subscribe": {
1400
- "subscribe": "task triage:subscribe",
1401
- "unsubscribe": "task triage:unsubscribe",
1402
- "__default__": "task triage:subscribe",
1403
- },
1404
- "triage_summary": {"__default__": "task triage:summary"},
1405
- "triage_reconcile": {"__default__": "task triage:reconcile"},
1406
- "triage_queue": {
1407
- "queue": "task triage:queue",
1408
- "show": "task triage:show",
1409
- "audit": "task triage:audit",
1410
- },
1411
- "triage_welcome": {"__default__": "task triage:welcome"},
1412
- "triage_smoketest": {"__default__": "task triage:smoketest"},
1413
- "scope_lifecycle": {
1414
- "promote": "task scope:promote",
1415
- "activate": "task scope:activate",
1416
- "complete": "task scope:complete",
1417
- "fail": "task scope:fail",
1418
- "cancel": "task scope:cancel",
1419
- "restore": "task scope:restore",
1420
- "block": "task scope:block",
1421
- "unblock": "task scope:unblock",
1422
- },
1423
- "scope_demote": {"__default__": "task scope:demote"},
1424
- "scope_undo": {"__default__": "task scope:undo"},
1425
- "scope_decompose": {"__default__": "task scope:decompose"},
1426
- }
1427
-
1428
-
1429
- # ---------------------------------------------------------------------------
1430
- # Renderers
1431
- # ---------------------------------------------------------------------------
1432
-
1433
-
1434
- def _format_category_row(verb: str, entry: VerbHelp, *, summary_col: int = 60) -> str:
1435
- """Render a single line in the bare-list category view.
1436
-
1437
- ``verb`` is the user-facing form (``task triage:queue``); ``summary_col``
1438
- pads the summary so the cross-ref tag right-aligns.
1439
- """
1440
- prefix = f" {verb:<28s}"
1441
- middle = f"{entry.summary}"
1442
- middle = middle if len(middle) <= summary_col else middle[: summary_col - 1] + "…"
1443
- padded = f"{middle:<{summary_col}s}"
1444
- return f"{prefix}{padded} {entry.refs}"
1445
-
1446
-
1447
- def render_category_list(category: str) -> str:
1448
- """Render the bare-invocation categorized verb list.
1449
-
1450
- ``category`` is ``"triage"`` or ``"scope"``. Raises ``ValueError`` on
1451
- any other value.
1452
- """
1453
- if category == "triage":
1454
- title = "Task triage — operator-facing cache verbs"
1455
- sections = CATEGORIES_TRIAGE
1456
- suffix = (
1457
- "Run any verb with --help for usage examples (e.g. "
1458
- "`task triage:queue --help`)."
1459
- )
1460
- elif category == "scope":
1461
- title = "Task scope — vBRIEF lifecycle verbs"
1462
- sections = CATEGORIES_SCOPE
1463
- suffix = (
1464
- "Run any verb with --help for usage examples (e.g. "
1465
- "`task scope:promote --help`)."
1466
- )
1467
- else:
1468
- raise ValueError(
1469
- f"unknown category {category!r}; expected 'triage' or 'scope'"
1470
- )
1471
-
1472
- lines: list[str] = [title, ""]
1473
- for label, verbs in sections:
1474
- lines.append(f"{label}:")
1475
- for verb in verbs:
1476
- entry = REGISTRY.get(verb)
1477
- if entry is None:
1478
- # Defensive -- if a category references a verb that is
1479
- # not yet in the registry, surface the gap loudly.
1480
- lines.append(f" {verb:<28s}(missing registry entry)")
1481
- continue
1482
- lines.append(_format_category_row(verb, entry))
1483
- lines.append("")
1484
- lines.append(suffix)
1485
- return "\n".join(lines)
1486
-
1487
-
1488
- def render_verb_help(verb: str) -> str:
1489
- """Render the structured ``--help`` output for a single verb.
1490
-
1491
- Raises ``KeyError`` if ``verb`` is not registered.
1492
- """
1493
- entry = REGISTRY.get(verb)
1494
- if entry is None:
1495
- raise KeyError(
1496
- f"unknown verb {verb!r}; not in scripts/triage_help.py REGISTRY. "
1497
- "Run `task triage` or `task scope` to see the catalog."
1498
- )
1499
-
1500
- lines: list[str] = []
1501
- header = f"{entry.name} -- {entry.summary} {entry.refs}".rstrip()
1502
- lines.append(header)
1503
- if entry.placeholder:
1504
- lines.append(" (not yet implemented -- placeholder entry; see refs)")
1505
- lines.append("")
1506
- lines.append(entry.description)
1507
- lines.append("")
1508
- lines.append("Usage:")
1509
- lines.append(f" {entry.usage}")
1510
- if entry.flags:
1511
- lines.append("")
1512
- lines.append("Flags:")
1513
- # Width-align the flag column for scannability.
1514
- flag_width = max(len(f) for f, _, _ in entry.flags)
1515
- flag_width = min(flag_width, 32)
1516
- for flag, default, desc in entry.flags:
1517
- head = f" {flag:<{flag_width}s}"
1518
- tail = f"{desc} (default: {default})" if default else desc
1519
- lines.append(f"{head} {tail}")
1520
- if entry.examples:
1521
- lines.append("")
1522
- lines.append("Examples:")
1523
- for ex in entry.examples:
1524
- lines.append(f" {ex}")
1525
- if entry.see_also:
1526
- lines.append("")
1527
- lines.append("See also:")
1528
- for ref in entry.see_also:
1529
- lines.append(f" {ref}")
1530
- return "\n".join(lines)
1531
-
1532
-
1533
- # ---------------------------------------------------------------------------
1534
- # Intercept helper used by verb scripts
1535
- # ---------------------------------------------------------------------------
1536
-
1537
-
1538
- HELP_FLAGS = ("--help", "-h")
1539
-
1540
-
1541
- def _argv_or_default(argv: list[str] | None) -> list[str]:
1542
- return list(argv) if argv is not None else list(sys.argv[1:])
1543
-
1544
-
1545
- def _has_help_flag(argv: list[str]) -> bool:
1546
- return any(arg in HELP_FLAGS for arg in argv)
1547
-
1548
-
1549
- def _resolve_verb_from_argv(
1550
- script_name: str, argv: list[str]
1551
- ) -> str | None:
1552
- """Look up the verb for ``script_name`` based on argv.
1553
-
1554
- Returns the canonical verb (e.g. ``"task triage:queue"``) or ``None``
1555
- if the script is unmapped or no matching subcommand was found.
1556
- """
1557
- sub_map = SCRIPT_SUBCOMMAND_MAP.get(script_name)
1558
- if sub_map is None:
1559
- return None
1560
- # Single-verb scripts: just take the default.
1561
- if "__default__" in sub_map and len(sub_map) == 1:
1562
- return sub_map["__default__"]
1563
- # Multi-subcommand scripts: find the first positional non-flag arg.
1564
- for arg in argv:
1565
- if not arg.startswith("-"):
1566
- verb = sub_map.get(arg)
1567
- if verb is not None:
1568
- return verb
1569
- # No matching positional; fall back to default if present.
1570
- return sub_map.get("__default__")
1571
-
1572
-
1573
- def intercept_help(
1574
- script_name: str,
1575
- argv: list[str] | None = None,
1576
- *,
1577
- out: object | None = None,
1578
- ) -> int | None:
1579
- """Print structured help and return 0 if ``--help`` / ``-h`` is in argv.
1580
-
1581
- Verb scripts call this at the top of ``main()``::
1582
-
1583
- def main(argv=None):
1584
- rc = intercept_help("triage_queue", argv)
1585
- if rc is not None:
1586
- return rc
1587
- ...
1588
-
1589
- Returns ``None`` when no help flag was found (caller proceeds with
1590
- argparse). Returns ``0`` after printing structured help; the caller
1591
- should propagate that exit code so argparse never runs.
1592
-
1593
- ``out`` is for tests -- defaults to ``sys.stdout``. Anything with a
1594
- ``write`` method works.
1595
- """
1596
- args = _argv_or_default(argv)
1597
- if not _has_help_flag(args):
1598
- return None
1599
-
1600
- verb = _resolve_verb_from_argv(script_name, args)
1601
- sink = out if out is not None else sys.stdout
1602
- if verb is None or verb not in REGISTRY:
1603
- # Unmapped script or unknown subcommand: print the appropriate
1604
- # category catalog hint and let argparse take over by returning
1605
- # None. (We intentionally do NOT swallow --help for scripts that
1606
- # are outside our registry; the existing argparse --help should
1607
- # still run.)
1608
- return None
1609
- try:
1610
- sink.write(render_verb_help(verb) + "\n")
1611
- except (KeyError, ValueError) as exc: # pragma: no cover -- defensive
1612
- sink.write(f"triage_help: {exc}\n")
1613
- return 2
1614
- return 0
1615
-
1616
-
1617
- # ---------------------------------------------------------------------------
1618
- # CLI entrypoint -- python -m scripts.triage_help <category|help|list>
1619
- # ---------------------------------------------------------------------------
1620
-
1621
-
1622
- _USAGE = (
1623
- "usage: python -m scripts.triage_help <triage|scope|help <verb>|list>\n"
1624
- "\n"
1625
- " triage Print the categorized triage verb list.\n"
1626
- " scope Print the categorized scope verb list.\n"
1627
- " help <verb> Print structured help for a single verb.\n"
1628
- " Accepts both 'task triage:queue' and 'triage:queue'.\n"
1629
- " list Print every registered verb (tooling discovery).\n"
1630
- )
1631
-
1632
-
1633
- def _normalize_verb_arg(raw: str) -> str:
1634
- """Accept both ``triage:queue`` and ``task triage:queue`` forms."""
1635
- raw = raw.strip()
1636
- if raw.startswith("task "):
1637
- return raw
1638
- return f"task {raw}"
1639
-
1640
-
1641
- def main(argv: list[str] | None = None) -> int: # noqa: PLR0911
1642
- args = _argv_or_default(argv)
1643
- if not args:
1644
- sys.stderr.write(_USAGE)
1645
- return 2
1646
- head, *rest = args
1647
- if head in ("-h", "--help"):
1648
- sys.stdout.write(_USAGE)
1649
- return 0
1650
- if head == "triage":
1651
- sys.stdout.write(render_category_list("triage") + "\n")
1652
- return 0
1653
- if head == "scope":
1654
- sys.stdout.write(render_category_list("scope") + "\n")
1655
- return 0
1656
- if head == "list":
1657
- for verb in sorted(REGISTRY):
1658
- entry = REGISTRY[verb]
1659
- tag = " [coming]" if entry.placeholder else ""
1660
- sys.stdout.write(f"{verb}{tag}\n")
1661
- return 0
1662
- if head == "help":
1663
- if not rest:
1664
- sys.stderr.write(
1665
- "triage_help: missing <verb> argument for `help`.\n"
1666
- )
1667
- sys.stderr.write(_USAGE)
1668
- return 2
1669
- verb = _normalize_verb_arg(rest[0])
1670
- if verb not in REGISTRY:
1671
- sys.stderr.write(
1672
- f"triage_help: unknown verb {verb!r}. "
1673
- "Run `python -m scripts.triage_help list` to see all "
1674
- "registered verbs.\n"
1675
- )
1676
- return 2
1677
- sys.stdout.write(render_verb_help(verb) + "\n")
1678
- return 0
1679
- sys.stderr.write(f"triage_help: unknown command {head!r}.\n")
1680
- sys.stderr.write(_USAGE)
1681
- return 2
1682
-
1683
-
1684
- if __name__ == "__main__":
1685
- sys.exit(main())