fix(project-rules): qualify ad-hoc record identifiers
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02b22-9638-76d2-bbff-b7ea1770b118
This commit is contained in:
parent
fae2816199
commit
284e28112d
6 changed files with 83 additions and 15 deletions
|
|
@ -204,9 +204,12 @@ read/cache/index layer that rebuilds from files.
|
|||
the completion/archive date; the frontmatter `id` does not change.
|
||||
|
||||
**Ad Hoc Tasks:** small opportunistic fixes discovered during a session use
|
||||
`workplans/ADHOC-YYYY-MM-DD.md` with task ids `ADHOC-YYYY-MM-DD-T01`, etc. Use
|
||||
this only for low-risk work completed directly; create a normal workplan for
|
||||
anything needing analysis, design, approval, dependencies, or multiple phases.
|
||||
`workplans/ADHOC-YYYY-MM-DD.md`, workplan id
|
||||
`STATE-WP-ADHOC-YYYY-MM-DD`, and task ids
|
||||
`STATE-WP-ADHOC-YYYY-MM-DD-T01`, etc. Unqualified historic `ADHOC-*` ids are
|
||||
grandfathered and must not be copied into new records. Use this only for
|
||||
low-risk work completed directly; create a normal workplan for anything needing
|
||||
analysis, design, approval, dependencies, or multiple phases.
|
||||
|
||||
**Frontmatter:**
|
||||
|
||||
|
|
|
|||
|
|
@ -150,9 +150,13 @@ read/cache/index layer that rebuilds from files.
|
|||
the completion/archive date; the frontmatter `id` does not change.
|
||||
|
||||
**Ad Hoc Tasks:** small opportunistic fixes discovered during a session use
|
||||
`workplans/ADHOC-YYYY-MM-DD.md` with task ids `ADHOC-YYYY-MM-DD-T01`, etc. Use
|
||||
this only for low-risk work completed directly; create a normal workplan for
|
||||
anything needing analysis, design, approval, dependencies, or multiple phases.
|
||||
`workplans/ADHOC-YYYY-MM-DD.md`, workplan id
|
||||
`{WP_PREFIX}-ADHOC-YYYY-MM-DD`, and task ids
|
||||
`{WP_PREFIX}-ADHOC-YYYY-MM-DD-T01`, etc. `{WP_PREFIX}` includes its final `-WP`
|
||||
token. Unqualified historic `ADHOC-*` ids are grandfathered and must not be
|
||||
copied into new records. Use this only for low-risk work completed directly;
|
||||
create a normal workplan for anything needing analysis, design, approval,
|
||||
dependencies, or multiple phases.
|
||||
|
||||
**Frontmatter:**
|
||||
|
||||
|
|
|
|||
|
|
@ -16,10 +16,13 @@ prefix: `YYMMDD-{WP_PREFIX}-NNNN-<slug>.md`. The frontmatter id remains
|
|||
unchanged; the prefix is only for quick visual reference.
|
||||
|
||||
Small opportunistic tasks discovered during another session use **Ad Hoc Tasks**:
|
||||
`workplans/ADHOC-YYYY-MM-DD.md`, workplan slug `adhoc-YYYY-MM-DD`, and task ids
|
||||
`ADHOC-YYYY-MM-DD-T01`, `T02`, etc. Use adhocs only for low-risk work completed
|
||||
directly. Promote anything requiring analysis, design, approval, dependencies, or
|
||||
multiple planned phases into a normal workplan.
|
||||
`workplans/ADHOC-YYYY-MM-DD.md`, workplan id
|
||||
`{WP_PREFIX}-ADHOC-YYYY-MM-DD`, and task ids
|
||||
`{WP_PREFIX}-ADHOC-YYYY-MM-DD-T01`, `T02`, etc. `{WP_PREFIX}` includes its final
|
||||
`-WP` token. Unqualified historic `ADHOC-*` ids are grandfathered and must not
|
||||
be copied into new records. Use adhocs only for low-risk work completed directly.
|
||||
Promote anything requiring analysis, design, approval, dependencies, or multiple
|
||||
planned phases into a normal workplan.
|
||||
|
||||
Ecosystem todos from other agents arrive as `[repo:{REPO_SLUG}]` hub tasks —
|
||||
visible at session start. Pick one up by creating the workplan file, committing,
|
||||
|
|
|
|||
|
|
@ -74,8 +74,15 @@ SUPPORTED_WP_STATUSES = set(SUPPORTED_WORKSTREAM_STATUSES)
|
|||
VALID_TASK_STATUSES = set(CANONICAL_TASK_STATUSES)
|
||||
VALID_TASK_PRIORITIES = {"low", "medium", "high", "critical"}
|
||||
|
||||
_WP_ID_RE = re.compile(r"^(?:[A-Z]+-WP-\d+|ADHOC-\d{4}-\d{2}-\d{2})$")
|
||||
_TASK_ID_RE = re.compile(r"^(?:[A-Z]+-WP-\d+|ADHOC-\d{4}-\d{2}-\d{2})-T\d+$")
|
||||
_PREFIX_RE = r"[A-Z][A-Z0-9]*(?:-[A-Z][A-Z0-9]*)*"
|
||||
_WP_ID_RE = re.compile(
|
||||
rf"^(?:{_PREFIX_RE}-WP-\d+|{_PREFIX_RE}-WP-ADHOC-\d{{4}}-\d{{2}}-\d{{2}}"
|
||||
r"|ADHOC-\d{4}-\d{2}-\d{2})$"
|
||||
)
|
||||
_TASK_ID_RE = re.compile(
|
||||
rf"^(?:{_PREFIX_RE}-WP-\d+|{_PREFIX_RE}-WP-ADHOC-\d{{4}}-\d{{2}}-\d{{2}}"
|
||||
r"|ADHOC-\d{4}-\d{2}-\d{2})-T\d+$"
|
||||
)
|
||||
_TASK_BLOCK_RE = re.compile(r"```task\s*\n(.*?)\n```", re.DOTALL)
|
||||
_ARCHIVED_WP_RE = re.compile(r"^\d{6}-(.+\.md)$")
|
||||
|
||||
|
|
@ -223,12 +230,18 @@ def _check_workplan_file(wp_file: Path, report: Report) -> dict | None:
|
|||
wp_id = str(meta.get("id", ""))
|
||||
if not _WP_ID_RE.match(wp_id):
|
||||
report.add(Level.FAIL, "frontmatter-id-format",
|
||||
f"id must match [A-Z]+-WP-\\d+ (e.g. CUST-WP-0001), got {wp_id!r}", fname)
|
||||
"id must be a numbered or repository-qualified ad-hoc "
|
||||
f"workplan id, got {wp_id!r}", fname)
|
||||
else:
|
||||
report.add(Level.PASS, "frontmatter-id-format", f"id={wp_id}", fname)
|
||||
|
||||
# filename prefix
|
||||
if wp_id and not canonical_fname.startswith(wp_id):
|
||||
adhoc_filename_id = Path(canonical_fname).stem
|
||||
qualified_adhoc_filename = (
|
||||
adhoc_filename_id.startswith("ADHOC-")
|
||||
and wp_id.endswith(f"-WP-{adhoc_filename_id}")
|
||||
)
|
||||
if wp_id and not canonical_fname.startswith(wp_id) and not qualified_adhoc_filename:
|
||||
report.add(Level.WARN, "filename-id-prefix",
|
||||
f"Filename should start with id '{wp_id}', got {fname!r}", fname)
|
||||
elif wp_id:
|
||||
|
|
@ -260,7 +273,7 @@ def _check_workplan_file(wp_file: Path, report: Report) -> dict | None:
|
|||
report.add(Level.FAIL, "task-id", "Missing 'id' field", tref)
|
||||
elif not _TASK_ID_RE.match(t_id):
|
||||
report.add(Level.WARN, "task-id-format",
|
||||
f"id {t_id!r} doesn't match [A-Z]+-WP-\\d+-T\\d+", tref)
|
||||
f"id {t_id!r} is not a canonical or grandfathered task id", tref)
|
||||
|
||||
t_status = str(task.get("status", ""))
|
||||
if not t_status:
|
||||
|
|
|
|||
36
tests/test_validate_repo_adr.py
Normal file
36
tests/test_validate_repo_adr.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from scripts.validate_repo_adr import Report, _check_workplan_file
|
||||
|
||||
|
||||
def test_repository_qualified_ad_hoc_id_matches_daily_filename(tmp_path) -> None:
|
||||
workplans = tmp_path / "workplans"
|
||||
workplans.mkdir()
|
||||
path = workplans / "ADHOC-2026-08-23.md"
|
||||
path.write_text(
|
||||
"""---
|
||||
id: ACTIVITY-WP-ADHOC-2026-08-23
|
||||
type: workplan
|
||||
title: Daily repair
|
||||
domain: infotech
|
||||
status: finished
|
||||
owner: codex
|
||||
created: "2026-08-23"
|
||||
---
|
||||
|
||||
## Repair
|
||||
|
||||
```task
|
||||
id: ACTIVITY-WP-ADHOC-2026-08-23-T01
|
||||
status: done
|
||||
priority: low
|
||||
```
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
report = Report(repo_path=str(tmp_path))
|
||||
|
||||
_check_workplan_file(path, report)
|
||||
|
||||
assert not report.failures
|
||||
assert not [finding for finding in report.warnings if finding.check == "filename-id-prefix"]
|
||||
|
|
@ -356,6 +356,15 @@ that omit `domain_slug`. Two regression tests cover both paths; all 131
|
|||
consistency-check tests pass. This is a compatibility fix until the generator
|
||||
moves or retires, not new permanent State Hub authority.
|
||||
|
||||
**Ad-hoc identity compatibility repair (2026-08-23):** the surviving project
|
||||
instruction templates prescribed fleet-wide `ADHOC-YYYY-MM-DD` identifiers.
|
||||
Activity Core and Net Kingdom used that form on the same day, and deterministic
|
||||
derivation correctly refused the second record. Templates and the legacy ADR
|
||||
validator now accept and prescribe `{WP_PREFIX}-ADHOC-YYYY-MM-DD` while keeping
|
||||
the daily filename unchanged. A regression test covers the filename/id pairing.
|
||||
This prevents new collisions without making State Hub the long-term convention
|
||||
owner.
|
||||
|
||||
## Retire legacy surfaces
|
||||
|
||||
```task
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue