@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,277 @@
|
|
|
1
|
+
"""Lifecycle folder routing for reconciled scope items (Agent B, #499).
|
|
2
|
+
|
|
3
|
+
Single source of truth for the lifecycle <-> status mapping used by
|
|
4
|
+
``migrate:vbrief``. The mapping mirrors the authoritative table in master
|
|
5
|
+
tracking issue #506 (Shared conventions) and the schema vocabulary in
|
|
6
|
+
``vbrief/schemas/vbrief-core.schema.json``:
|
|
7
|
+
|
|
8
|
+
proposed/ <-> draft | proposed
|
|
9
|
+
pending/ <-> approved | pending
|
|
10
|
+
active/ <-> running | blocked
|
|
11
|
+
completed/ <-> completed
|
|
12
|
+
cancelled/ <-> cancelled
|
|
13
|
+
|
|
14
|
+
The migrator MUST NOT emit the legacy value ``in_progress`` -- this was the
|
|
15
|
+
critical correction to the original #499 issue body. Use ``running``.
|
|
16
|
+
|
|
17
|
+
Exposes:
|
|
18
|
+
* FOLDER_TO_STATUSES / STATUS_TO_FOLDER
|
|
19
|
+
* folder_for_status(status) -> folder
|
|
20
|
+
* default_status_for_folder(folder) -> status
|
|
21
|
+
* plan_status_matches_folder(status, folder) -> bool
|
|
22
|
+
* build_scope_vbrief_from_reconciled(reconciled, repo_url) -> dict
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import sys
|
|
28
|
+
from datetime import UTC, datetime
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
# Make the sibling ``_vbrief_build`` helper importable whether this module is
|
|
33
|
+
# imported as part of the ``scripts/`` package layout or as a top-level module.
|
|
34
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
35
|
+
|
|
36
|
+
from _vbrief_build import ( # noqa: E402
|
|
37
|
+
MIGRATOR_METADATA_KEY as _MIGRATOR_METADATA_KEY,
|
|
38
|
+
create_scope_vbrief as _create_scope_vbrief,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _migration_timestamp() -> str:
|
|
43
|
+
"""Return an ISO-8601 UTC timestamp for ``vBRIEFInfo.updated`` stamps.
|
|
44
|
+
|
|
45
|
+
Emitted at second precision. This helper is ONLY the fallback used when
|
|
46
|
+
``build_scope_vbrief_from_reconciled(..., migration_timestamp=None)`` is
|
|
47
|
+
called directly. Under the normal ``migrate()`` entry point the caller
|
|
48
|
+
always passes ``migration_timestamp=migrate_vbrief._MIGRATION_TIMESTAMP``
|
|
49
|
+
(a module-level constant stamped once per migration run), so this helper
|
|
50
|
+
is effectively unreachable there.
|
|
51
|
+
|
|
52
|
+
Test-pinning knob: deterministic migrate() tests (for example the
|
|
53
|
+
byte-for-byte golden-fixture suite in ``test_migrate_vbrief.py``) MUST
|
|
54
|
+
monkeypatch ``migrate_vbrief._MIGRATION_TIMESTAMP`` -- NOT this helper --
|
|
55
|
+
because the full migrate() path always threads the module-level constant
|
|
56
|
+
through to ``build_scope_vbrief_from_reconciled(migration_timestamp=...)``.
|
|
57
|
+
Callers that invoke ``build_scope_vbrief_from_reconciled`` directly can
|
|
58
|
+
either pass ``migration_timestamp=`` explicitly or monkeypatch this helper.
|
|
59
|
+
"""
|
|
60
|
+
return datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
61
|
+
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
# Lifecycle <-> status mapping (#506 Shared conventions, schema-locked)
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
FOLDER_TO_STATUSES: dict[str, tuple[str, ...]] = {
|
|
67
|
+
"proposed": ("draft", "proposed"),
|
|
68
|
+
"pending": ("approved", "pending"),
|
|
69
|
+
"active": ("running", "blocked"),
|
|
70
|
+
"completed": ("completed",),
|
|
71
|
+
"cancelled": ("cancelled",),
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
STATUS_TO_FOLDER: dict[str, str] = {
|
|
75
|
+
status: folder
|
|
76
|
+
for folder, statuses in FOLDER_TO_STATUSES.items()
|
|
77
|
+
for status in statuses
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
LIFECYCLE_FOLDERS: tuple[str, ...] = tuple(FOLDER_TO_STATUSES.keys())
|
|
81
|
+
|
|
82
|
+
# Canonical default status the migrator emits when a folder is chosen but no
|
|
83
|
+
# sharper signal exists (e.g. orphans routed to proposed/ use ``proposed`` not
|
|
84
|
+
# ``draft``; reconciled-active with no explicit blocked signal uses ``running``).
|
|
85
|
+
DEFAULT_STATUS_FOR_FOLDER: dict[str, str] = {
|
|
86
|
+
"proposed": "proposed",
|
|
87
|
+
"pending": "pending",
|
|
88
|
+
"active": "running",
|
|
89
|
+
"completed": "completed",
|
|
90
|
+
"cancelled": "cancelled",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def folder_for_status(status: str) -> str:
|
|
95
|
+
"""Return the canonical lifecycle folder for a schema status.
|
|
96
|
+
|
|
97
|
+
Raises ``ValueError`` for unknown statuses so callers can surface the
|
|
98
|
+
corruption early rather than silently routing to ``pending/``.
|
|
99
|
+
"""
|
|
100
|
+
try:
|
|
101
|
+
return STATUS_TO_FOLDER[status]
|
|
102
|
+
except KeyError as exc: # pragma: no cover - defensive
|
|
103
|
+
raise ValueError(
|
|
104
|
+
f"No lifecycle folder defined for status {status!r}; "
|
|
105
|
+
f"expected one of {sorted(STATUS_TO_FOLDER)}."
|
|
106
|
+
) from exc
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def default_status_for_folder(folder: str) -> str:
|
|
110
|
+
"""Return the canonical default status the migrator uses for a folder."""
|
|
111
|
+
try:
|
|
112
|
+
return DEFAULT_STATUS_FOR_FOLDER[folder]
|
|
113
|
+
except KeyError as exc: # pragma: no cover - defensive
|
|
114
|
+
raise ValueError(
|
|
115
|
+
f"Unknown lifecycle folder {folder!r}; expected one of "
|
|
116
|
+
f"{sorted(DEFAULT_STATUS_FOR_FOLDER)}."
|
|
117
|
+
) from exc
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def plan_status_matches_folder(status: str, folder: str) -> bool:
|
|
121
|
+
"""Return True if ``status`` is permitted inside ``folder/`` per #506."""
|
|
122
|
+
return status in FOLDER_TO_STATUSES.get(folder, ())
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
# Scope vBRIEF construction from reconciled item
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _narrative_str(value: Any) -> str:
|
|
131
|
+
"""Coerce a narrative field to a stripped string (schema requires strings)."""
|
|
132
|
+
if value is None:
|
|
133
|
+
return ""
|
|
134
|
+
if isinstance(value, str):
|
|
135
|
+
return value.strip()
|
|
136
|
+
return str(value).strip()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def build_scope_vbrief_from_reconciled(
|
|
140
|
+
reconciled: dict,
|
|
141
|
+
repo_url: str = "",
|
|
142
|
+
migration_timestamp: str | None = None,
|
|
143
|
+
) -> dict:
|
|
144
|
+
"""Build a scope vBRIEF dict from a reconciled item (#496 + #499 + #616).
|
|
145
|
+
|
|
146
|
+
``reconciled`` is a dict with the following recognised keys (produced by
|
|
147
|
+
``_vbrief_reconciliation.reconcile_scope_items``):
|
|
148
|
+
|
|
149
|
+
number, task_id, title, status, folder, description, description_source,
|
|
150
|
+
status_source, title_source, phase, phase_description, tier, spec_phase,
|
|
151
|
+
roadmap_summary, source_conflict, override_applied, references.
|
|
152
|
+
|
|
153
|
+
The output preserves the ``_create_scope_vbrief`` envelope shape that
|
|
154
|
+
tests already rely on. Per issue #616 (option A, scope-clamped) the
|
|
155
|
+
user-visible ``plan.narratives`` is left almost EMPTY on per-issue
|
|
156
|
+
scope vBRIEFs -- ROADMAP rows do not carry enough data to populate
|
|
157
|
+
any canonical narrative key meaningfully. Reconciliation provenance
|
|
158
|
+
(Description / Description_source / Status_source / Title_source /
|
|
159
|
+
SpecPhase / RoadmapSummary / SourceConflict) is relocated to
|
|
160
|
+
``plan.metadata['x-migrator']`` so downstream tooling that cares
|
|
161
|
+
about SPEC/ROADMAP lineage can still read it without the invented
|
|
162
|
+
keys leaking into the user-facing summary surface.
|
|
163
|
+
|
|
164
|
+
``SourceSection`` is the named exception to the #616 clamp: it
|
|
165
|
+
remains in ``plan.narratives`` because it is a deliberate,
|
|
166
|
+
user-visible audit-trail narrative (``ROADMAP Completed section``
|
|
167
|
+
vs ``ROADMAP active phase``) added by #593 so operators can audit
|
|
168
|
+
the routing decision post-migration without re-running the
|
|
169
|
+
migrator. Unlike the reconciler-internal provenance above (which
|
|
170
|
+
really is plumbing noise), SourceSection is intentional signal.
|
|
171
|
+
The Windows task-dispatch regression asserts
|
|
172
|
+
``plan.narratives.SourceSection`` specifically.
|
|
173
|
+
"""
|
|
174
|
+
status = reconciled.get("status") or default_status_for_folder(
|
|
175
|
+
reconciled.get("folder", "pending")
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Seed with the shared helper so origin-provenance (references) and the
|
|
179
|
+
# vBRIEFInfo envelope stay consistent with non-reconciled scope vBRIEFs.
|
|
180
|
+
seed_item = {
|
|
181
|
+
"number": reconciled.get("number", ""),
|
|
182
|
+
"title": reconciled.get("title", "Untitled"),
|
|
183
|
+
"phase": reconciled.get("phase", ""),
|
|
184
|
+
"tier": reconciled.get("tier", ""),
|
|
185
|
+
}
|
|
186
|
+
scope = _create_scope_vbrief(
|
|
187
|
+
seed_item,
|
|
188
|
+
repo_url=repo_url,
|
|
189
|
+
status=status,
|
|
190
|
+
phase_description=reconciled.get("phase_description", ""),
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# #616: migrator provenance flows into plan.metadata['x-migrator'],
|
|
194
|
+
# NOT plan.narratives. The shared ``create_scope_vbrief`` helper
|
|
195
|
+
# already seeded Phase / Tier / PhaseDescription under the same key
|
|
196
|
+
# when populated; we extend that bucket with reconciler-specific
|
|
197
|
+
# fields so a single metadata blob captures the full lineage.
|
|
198
|
+
plan_meta = scope["plan"].setdefault("metadata", {})
|
|
199
|
+
migrator_meta = plan_meta.setdefault(_MIGRATOR_METADATA_KEY, {})
|
|
200
|
+
|
|
201
|
+
def _store(key: str, value: Any) -> None:
|
|
202
|
+
coerced = _narrative_str(value)
|
|
203
|
+
if coerced:
|
|
204
|
+
migrator_meta[key] = coerced
|
|
205
|
+
|
|
206
|
+
_store("Description", reconciled.get("description"))
|
|
207
|
+
_store("Description_source", reconciled.get("description_source"))
|
|
208
|
+
_store("Status_source", reconciled.get("status_source"))
|
|
209
|
+
_store("Title_source", reconciled.get("title_source"))
|
|
210
|
+
_store("SpecPhase", reconciled.get("spec_phase"))
|
|
211
|
+
_store("RoadmapSummary", reconciled.get("roadmap_summary"))
|
|
212
|
+
_store("SourceConflict", reconciled.get("source_conflict"))
|
|
213
|
+
|
|
214
|
+
# #593: SourceSection is the named exception to the #616 narrative
|
|
215
|
+
# clamp -- it is a deliberate, user-visible audit-trail narrative
|
|
216
|
+
# (``ROADMAP Completed section`` vs ``ROADMAP active phase``) that
|
|
217
|
+
# operators need surfaced at the narrative level so the routing
|
|
218
|
+
# decision is auditable without re-running the migrator. Unlike the
|
|
219
|
+
# reconciler-internal provenance above (Description_source /
|
|
220
|
+
# Status_source / ...), which is plumbing noise, SourceSection is
|
|
221
|
+
# intended signal. The Windows task-dispatch regression asserts
|
|
222
|
+
# ``plan.narratives.SourceSection`` specifically. Single source of
|
|
223
|
+
# truth: we record it in narratives only, not duplicated under
|
|
224
|
+
# x-migrator metadata.
|
|
225
|
+
source_section = _narrative_str(reconciled.get("source_section"))
|
|
226
|
+
if source_section:
|
|
227
|
+
narratives = scope["plan"].setdefault("narratives", {})
|
|
228
|
+
narratives["SourceSection"] = source_section
|
|
229
|
+
|
|
230
|
+
# Clean up an empty migrator bucket so the emitted JSON doesn't
|
|
231
|
+
# carry an empty ``metadata.x-migrator`` payload on fully bare
|
|
232
|
+
# reconciled items (happens in unit tests that bypass the
|
|
233
|
+
# reconciler). Mirrors the clean-up path in ``create_scope_vbrief``
|
|
234
|
+
# where plan.metadata is only materialised when there is something
|
|
235
|
+
# to store.
|
|
236
|
+
if not migrator_meta:
|
|
237
|
+
plan_meta.pop(_MIGRATOR_METADATA_KEY, None)
|
|
238
|
+
if not plan_meta:
|
|
239
|
+
scope["plan"].pop("metadata", None)
|
|
240
|
+
|
|
241
|
+
# #593: stamp ``vBRIEFInfo.updated`` with the migration timestamp when
|
|
242
|
+
# we route an item to ``completed/``. The vBRIEF carries completion
|
|
243
|
+
# provenance in its envelope so downstream tooling that sorts by
|
|
244
|
+
# completion time has a non-null date to work with. Active/pending/
|
|
245
|
+
# proposed items do not receive an ``updated`` stamp because the
|
|
246
|
+
# scope has not yet reached a terminal state. ``migration_timestamp``
|
|
247
|
+
# is pinnable by callers (tests monkeypatch it for byte-for-byte
|
|
248
|
+
# fixture determinism).
|
|
249
|
+
if reconciled.get("status") == "completed":
|
|
250
|
+
envelope = scope.setdefault("vBRIEFInfo", {})
|
|
251
|
+
if isinstance(envelope, dict):
|
|
252
|
+
envelope.setdefault(
|
|
253
|
+
"updated", migration_timestamp or _migration_timestamp()
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Preserve any explicitly supplied references (e.g. spec back-link) on top
|
|
257
|
+
# of the origin-provenance reference set by ``_create_scope_vbrief``.
|
|
258
|
+
extra_refs = reconciled.get("references") or []
|
|
259
|
+
if extra_refs:
|
|
260
|
+
existing = scope["plan"].setdefault("references", [])
|
|
261
|
+
for ref in extra_refs:
|
|
262
|
+
if isinstance(ref, dict) and ref not in existing:
|
|
263
|
+
existing.append(ref)
|
|
264
|
+
|
|
265
|
+
return scope
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
__all__ = [
|
|
269
|
+
"DEFAULT_STATUS_FOR_FOLDER",
|
|
270
|
+
"FOLDER_TO_STATUSES",
|
|
271
|
+
"LIFECYCLE_FOLDERS",
|
|
272
|
+
"STATUS_TO_FOLDER",
|
|
273
|
+
"build_scope_vbrief_from_reconciled",
|
|
274
|
+
"default_status_for_folder",
|
|
275
|
+
"folder_for_status",
|
|
276
|
+
"plan_status_matches_folder",
|
|
277
|
+
]
|