@deftai/directive-content 0.55.1 → 0.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.githooks/pre-commit +143 -0
- package/.githooks/pre-push +121 -0
- package/QUICK-START.md +13 -3
- package/Taskfile.yml +934 -0
- package/UPGRADING.md +82 -11
- package/events/README.md +3 -3
- package/package.json +5 -4
- package/packs/skills/skills-pack-0.1.json +22 -22
- package/scripts/_agents_md.py +494 -0
- package/scripts/_cache_fetch.py +635 -0
- package/scripts/_cache_quota.py +529 -0
- package/scripts/_cache_refresh.py +163 -0
- package/scripts/_cache_validate.py +209 -0
- package/scripts/_content_root.py +42 -0
- package/scripts/_doctor_state.py +277 -0
- package/scripts/_event_detect.py +305 -0
- package/scripts/_events.py +514 -0
- package/scripts/_lifecycle_hygiene.py +568 -0
- package/scripts/_pathspec.py +91 -0
- package/scripts/_policy_show_cli.py +266 -0
- package/scripts/_precutover.py +92 -0
- package/scripts/_project_context.py +224 -0
- package/scripts/_project_definition_io.py +164 -0
- package/scripts/_relocate_snapshot.py +209 -0
- package/scripts/_relocate_states.py +343 -0
- package/scripts/_resolve_preflight_path.py +152 -0
- package/scripts/_safe_subprocess.py +167 -0
- package/scripts/_session_start_hook.py +205 -0
- package/scripts/_sor_gate_diff.py +365 -0
- package/scripts/_stdio_utf8.py +59 -0
- package/scripts/_triage_bootstrap_gitignore.py +904 -0
- package/scripts/_triage_classify_cli.py +122 -0
- package/scripts/_triage_queue_cli.py +625 -0
- package/scripts/_triage_scope_cli.py +343 -0
- package/scripts/_triage_scope_drift_cli.py +121 -0
- package/scripts/_triage_scope_ignores.py +286 -0
- package/scripts/_triage_scope_milestone.py +432 -0
- package/scripts/_triage_scope_mutations.py +337 -0
- package/scripts/_triage_scope_renderers.py +207 -0
- package/scripts/_triage_smoketest_stages.py +674 -0
- package/scripts/_triage_subscribe_cli.py +140 -0
- package/scripts/_triage_welcome_cli.py +421 -0
- package/scripts/_vbrief_build.py +239 -0
- package/scripts/_vbrief_fidelity.py +479 -0
- package/scripts/_vbrief_legacy.py +589 -0
- package/scripts/_vbrief_reconciliation.py +883 -0
- package/scripts/_vbrief_routing.py +277 -0
- package/scripts/_vbrief_safety.py +778 -0
- package/scripts/_vbrief_sources.py +312 -0
- package/scripts/_vbrief_speckit.py +262 -0
- package/scripts/_vbrief_story_quality.py +353 -0
- package/scripts/_vbrief_validation.py +299 -0
- package/scripts/build_dist.py +412 -0
- package/scripts/cache.py +1078 -0
- package/scripts/cache_scanner.py +745 -0
- package/scripts/candidates_log.py +432 -0
- package/scripts/capacity_backfill.py +680 -0
- package/scripts/capacity_show.py +653 -0
- package/scripts/ci_local.py +689 -0
- package/scripts/code_structure_validate.py +765 -0
- package/scripts/codebase_default_extractor.py +495 -0
- package/scripts/codebase_map.py +304 -0
- package/scripts/codebase_map_fresh.py +104 -0
- package/scripts/codebase_projection_registry.py +94 -0
- package/scripts/codebase_provider.py +582 -0
- package/scripts/doctor.py +2257 -0
- package/scripts/framework_commands.py +505 -0
- package/scripts/gh_rest.py +882 -0
- package/scripts/github_auth_modes.py +437 -0
- package/scripts/github_body.py +292 -0
- package/scripts/ip_risk.py +531 -0
- package/scripts/issue_emit.py +670 -0
- package/scripts/issue_ingest.py +1064 -0
- package/scripts/migrate_preflight.py +418 -0
- package/scripts/migrate_vbrief.py +2677 -0
- package/scripts/monitor_pr.py +401 -0
- package/scripts/pack_migrate_lessons.py +336 -0
- package/scripts/pack_migrate_patterns.py +254 -0
- package/scripts/pack_migrate_rules.py +350 -0
- package/scripts/pack_migrate_skills.py +423 -0
- package/scripts/pack_migrate_strategies.py +311 -0
- package/scripts/pack_migrate_swarm_spec.py +250 -0
- package/scripts/pack_render.py +434 -0
- package/scripts/packs_slice.py +712 -0
- package/scripts/platform_capabilities.py +336 -0
- package/scripts/policy.py +2826 -0
- package/scripts/policy_set.py +324 -0
- package/scripts/pr_check_closing_keywords.py +524 -0
- package/scripts/pr_check_protected_issues.py +267 -0
- package/scripts/pr_merge_readiness.py +1004 -0
- package/scripts/pr_wait_mergeable.py +669 -0
- package/scripts/prd_render.py +159 -0
- package/scripts/preflight_architecture_sor.py +974 -0
- package/scripts/preflight_branch.py +289 -0
- package/scripts/preflight_cache.py +974 -0
- package/scripts/preflight_gh.py +721 -0
- package/scripts/preflight_implementation.py +272 -0
- package/scripts/preflight_story_start.py +838 -0
- package/scripts/preflight_wip_cap.py +149 -0
- package/scripts/probe_session.py +545 -0
- package/scripts/project_render.py +293 -0
- package/scripts/quarantine_ext.py +237 -0
- package/scripts/reconcile_issues.py +1442 -0
- package/scripts/refresh-path.ps1 +107 -0
- package/scripts/release.py +2030 -0
- package/scripts/release_e2e.py +1011 -0
- package/scripts/release_publish.py +486 -0
- package/scripts/release_rollback.py +980 -0
- package/scripts/relocate.py +1034 -0
- package/scripts/resolve_changelog_unreleased.py +667 -0
- package/scripts/resolve_version.py +490 -0
- package/scripts/resume_conditions.py +706 -0
- package/scripts/ritual_sentinel.py +609 -0
- package/scripts/roadmap_render.py +635 -0
- package/scripts/rule_ownership_lint.py +325 -0
- package/scripts/scm.py +591 -0
- package/scripts/scope_audit_log.py +387 -0
- package/scripts/scope_decompose.py +654 -0
- package/scripts/scope_demote.py +509 -0
- package/scripts/scope_lifecycle.py +1126 -0
- package/scripts/scope_undo.py +772 -0
- package/scripts/session_start.py +406 -0
- package/scripts/setup_ghx.py +339 -0
- package/scripts/setup_windows.ps1 +220 -0
- package/scripts/slice_audit.py +585 -0
- package/scripts/slice_record.py +530 -0
- package/scripts/slice_record_existing.py +692 -0
- package/scripts/slug_normalize.py +178 -0
- package/scripts/spec_render.py +477 -0
- package/scripts/spec_validate.py +238 -0
- package/scripts/subagent_monitor.py +658 -0
- package/scripts/swarm_complete_cohort.py +644 -0
- package/scripts/swarm_launch.py +1206 -0
- package/scripts/swarm_readiness.py +554 -0
- package/scripts/swarm_verify_review_clean.py +438 -0
- package/scripts/swarm_worktrees.py +497 -0
- package/scripts/toolchain-check.py +52 -0
- package/scripts/triage_actions.py +871 -0
- package/scripts/triage_bootstrap.py +1153 -0
- package/scripts/triage_bulk.py +630 -0
- package/scripts/triage_classify.py +932 -0
- package/scripts/triage_help.py +1685 -0
- package/scripts/triage_queue.py +1944 -0
- package/scripts/triage_reconcile.py +581 -0
- package/scripts/triage_refresh.py +643 -0
- package/scripts/triage_scope.py +999 -0
- package/scripts/triage_scope_drift.py +575 -0
- package/scripts/triage_smoketest.py +396 -0
- package/scripts/triage_subscribe.py +399 -0
- package/scripts/triage_summary.py +1011 -0
- package/scripts/triage_welcome.py +1178 -0
- package/scripts/ts_check_lane.py +86 -0
- package/scripts/validate-links.py +64 -0
- package/scripts/validate_strategy_output.py +212 -0
- package/scripts/vbrief_activate.py +228 -0
- package/scripts/vbrief_migrate_conformance.py +368 -0
- package/scripts/vbrief_reconcile_graph.py +306 -0
- package/scripts/vbrief_reconcile_labels.py +460 -0
- package/scripts/vbrief_reconcile_umbrellas.py +741 -0
- package/scripts/vbrief_validate.py +1195 -0
- package/scripts/verify-stubs.py +61 -0
- package/scripts/verify_capacity.py +160 -0
- package/scripts/verify_encoding.py +699 -0
- package/scripts/verify_hooks_installed.py +206 -0
- package/scripts/verify_investigation.py +360 -0
- package/scripts/verify_judgment_gates.py +827 -0
- package/scripts/verify_no_task_runtime.py +171 -0
- package/scripts/verify_scm_boundary.py +509 -0
- package/scripts/verify_session_ritual.py +389 -0
- package/scripts/verify_tools.py +426 -0
- package/scripts/verify_vbrief_conformance.py +478 -0
- package/skills/deft-directive-swarm/SKILL.md +7 -26
- package/skills/deft-directive-sync/SKILL.md +1 -1
- package/tasks/architecture.yml +13 -0
- package/tasks/cache.yml +69 -0
- package/tasks/capacity.yml +38 -0
- package/tasks/change.yml +46 -0
- package/tasks/changelog.yml +24 -0
- package/tasks/ci.yml +49 -0
- package/tasks/codebase.yml +47 -0
- package/tasks/commit.yml +30 -0
- package/tasks/core.yml +126 -0
- package/tasks/deployments.yml +54 -0
- package/tasks/framework.yml +74 -0
- package/tasks/install.yml +60 -0
- package/tasks/issue.yml +50 -0
- package/tasks/migrate.yml +73 -0
- package/tasks/packs.yml +92 -0
- package/tasks/policy.yml +75 -0
- package/tasks/pr.yml +89 -0
- package/tasks/prd.yml +39 -0
- package/tasks/project.yml +27 -0
- package/tasks/reconcile.yml +32 -0
- package/tasks/relocate.yml +56 -0
- package/tasks/roadmap.yml +28 -0
- package/tasks/scm.yml +126 -0
- package/tasks/scope-undo.yml +36 -0
- package/tasks/scope.yml +141 -0
- package/tasks/session.yml +19 -0
- package/tasks/setup.yml +37 -0
- package/tasks/slice.yml +69 -0
- package/tasks/spec.yml +41 -0
- package/tasks/swarm.yml +85 -0
- package/tasks/toolchain.yml +13 -0
- package/tasks/triage-actions.yml +94 -0
- package/tasks/triage-bootstrap.yml +43 -0
- package/tasks/triage-bulk.yml +75 -0
- package/tasks/triage-classify.yml +30 -0
- package/tasks/triage-queue.yml +50 -0
- package/tasks/triage-reconcile.yml +29 -0
- package/tasks/triage-scope-drift.yml +29 -0
- package/tasks/triage-scope.yml +31 -0
- package/tasks/triage-smoketest.yml +33 -0
- package/tasks/triage-subscribe.yml +36 -0
- package/tasks/triage-summary.yml +29 -0
- package/tasks/triage-welcome.yml +32 -0
- package/tasks/ts.yml +328 -0
- package/tasks/vbrief.yml +206 -0
- package/tasks/verify.yml +292 -0
- package/templates/agents-entry.md +2 -2
|
@@ -0,0 +1,1685 @@
|
|
|
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())
|