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.
|
the completion/archive date; the frontmatter `id` does not change.
|
||||||
|
|
||||||
**Ad Hoc Tasks:** small opportunistic fixes discovered during a session use
|
**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
|
`workplans/ADHOC-YYYY-MM-DD.md`, workplan id
|
||||||
this only for low-risk work completed directly; create a normal workplan for
|
`STATE-WP-ADHOC-YYYY-MM-DD`, and task ids
|
||||||
anything needing analysis, design, approval, dependencies, or multiple phases.
|
`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:**
|
**Frontmatter:**
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -150,9 +150,13 @@ read/cache/index layer that rebuilds from files.
|
||||||
the completion/archive date; the frontmatter `id` does not change.
|
the completion/archive date; the frontmatter `id` does not change.
|
||||||
|
|
||||||
**Ad Hoc Tasks:** small opportunistic fixes discovered during a session use
|
**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
|
`workplans/ADHOC-YYYY-MM-DD.md`, workplan id
|
||||||
this only for low-risk work completed directly; create a normal workplan for
|
`{WP_PREFIX}-ADHOC-YYYY-MM-DD`, and task ids
|
||||||
anything needing analysis, design, approval, dependencies, or multiple phases.
|
`{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:**
|
**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.
|
unchanged; the prefix is only for quick visual reference.
|
||||||
|
|
||||||
Small opportunistic tasks discovered during another session use **Ad Hoc Tasks**:
|
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
|
`workplans/ADHOC-YYYY-MM-DD.md`, workplan id
|
||||||
`ADHOC-YYYY-MM-DD-T01`, `T02`, etc. Use adhocs only for low-risk work completed
|
`{WP_PREFIX}-ADHOC-YYYY-MM-DD`, and task ids
|
||||||
directly. Promote anything requiring analysis, design, approval, dependencies, or
|
`{WP_PREFIX}-ADHOC-YYYY-MM-DD-T01`, `T02`, etc. `{WP_PREFIX}` includes its final
|
||||||
multiple planned phases into a normal workplan.
|
`-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 —
|
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,
|
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_STATUSES = set(CANONICAL_TASK_STATUSES)
|
||||||
VALID_TASK_PRIORITIES = {"low", "medium", "high", "critical"}
|
VALID_TASK_PRIORITIES = {"low", "medium", "high", "critical"}
|
||||||
|
|
||||||
_WP_ID_RE = re.compile(r"^(?:[A-Z]+-WP-\d+|ADHOC-\d{4}-\d{2}-\d{2})$")
|
_PREFIX_RE = r"[A-Z][A-Z0-9]*(?:-[A-Z][A-Z0-9]*)*"
|
||||||
_TASK_ID_RE = re.compile(r"^(?:[A-Z]+-WP-\d+|ADHOC-\d{4}-\d{2}-\d{2})-T\d+$")
|
_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)
|
_TASK_BLOCK_RE = re.compile(r"```task\s*\n(.*?)\n```", re.DOTALL)
|
||||||
_ARCHIVED_WP_RE = re.compile(r"^\d{6}-(.+\.md)$")
|
_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", ""))
|
wp_id = str(meta.get("id", ""))
|
||||||
if not _WP_ID_RE.match(wp_id):
|
if not _WP_ID_RE.match(wp_id):
|
||||||
report.add(Level.FAIL, "frontmatter-id-format",
|
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:
|
else:
|
||||||
report.add(Level.PASS, "frontmatter-id-format", f"id={wp_id}", fname)
|
report.add(Level.PASS, "frontmatter-id-format", f"id={wp_id}", fname)
|
||||||
|
|
||||||
# filename prefix
|
# 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",
|
report.add(Level.WARN, "filename-id-prefix",
|
||||||
f"Filename should start with id '{wp_id}', got {fname!r}", fname)
|
f"Filename should start with id '{wp_id}', got {fname!r}", fname)
|
||||||
elif wp_id:
|
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)
|
report.add(Level.FAIL, "task-id", "Missing 'id' field", tref)
|
||||||
elif not _TASK_ID_RE.match(t_id):
|
elif not _TASK_ID_RE.match(t_id):
|
||||||
report.add(Level.WARN, "task-id-format",
|
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", ""))
|
t_status = str(task.get("status", ""))
|
||||||
if not t_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
|
consistency-check tests pass. This is a compatibility fix until the generator
|
||||||
moves or retires, not new permanent State Hub authority.
|
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
|
## Retire legacy surfaces
|
||||||
|
|
||||||
```task
|
```task
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue