datarim-doctor
Schema and migration semantics for /dr-doctor — thin one-liner contract, 6-pass migration, data-loss safety, conflict resolution. Loaded by self-heal.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Schema and migration semantics for /dr-doctor — thin one-liner contract, 6-pass migration, data-loss safety, conflict resolution. Loaded by self-heal.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | datarim-doctor |
| description | Schema and migration semantics for /dr-doctor — thin one-liner contract, 6-pass migration, data-loss safety, conflict resolution. Loaded by self-heal. |
| current_aal | 2 |
| target_aal | 3 |
This skill is the runtime knowledge module for /dr-doctor. It defines the strict active-index contract for tasks.md / activeContext.md, the relaxed line-oriented ledger contract for backlog.md, the 6-pass migration algorithm that scripts/datarim-doctor.sh applies, and the data-loss safety contract that wraps every --fix invocation.
Loaded by:
/dr-doctor (always)/dr-init self-heal (when --quiet probe returns exit 1)/dr-archive line-format gate (on failure, to explain non-compliance)Not loaded by other commands — they read operational files as line-oriented ledgers and resolve a description pointer when one is present.
tasks.md and activeContext.md are strict pointer indexes. backlog.md is a
pending-work ledger: each entry remains one line and machine-parseable, but may
preserve its pending-work description inline until the task is promoted.
Goals:
datarim/tasks/{TASK-ID}-task-description.md; pointerless backlog prose is intake context, not an active plan./dr-doctor can run any number of times without drift.progress.md is abolished, and the legacy activeContext.md § Последние завершённые rolling log is abolished as well. Completion history lives only in documentation/archive/{area}/archive-{TASK-ID}.md and git log.
Exact regex constants are sourced from scripts/lib/schema-regex.sh. Do not
redefine them inside Doctor or a downstream validator.
tasks.md and activeContext.md use ONELINER_RE:
^- ([A-Z]{2,10}-[0-9]{4}) · (STATUS) · P[0-3] · L[1-4] · (.+) → tasks/\1-(task-description|init-task)\.md$
ONELINER_RE accepts in_progress|blocked|not_started|pending|blocked-pending|cancelled
for legacy and migration compatibility. Canonical active-index writers emit
in_progress|blocked|not_started; pending and blocked-pending belong to
the backlog intake flow, and cancelled is archived from the backlog rather
than mirrored as an active task.
Active-index pointer: required. Separator: · (U+00B7 MIDDLE DOT, not a
bullet or period). Arrow: → (U+2192). The pointer task ID must match the
entry task ID.
Compliant active entry:
- <TASK-ID-A> · in_progress · P1 · L3 · <Title> → tasks/<TASK-ID-A>-task-description.md
backlog.md uses BACKLOG_ITEM_RE, not the active-index regex. It accepts the
wider backlog status set (pending, blocked-pending, cancelled,
superseded, absorbed, deferred, plus active states), priorities P0-P4,
optional bold priority/complexity tokens, and a nonempty single-line inline
description.
Pointer: optional for backlog entries. An existing description may be
linked, but a compliant inline entry is not a Doctor finding and --fix must
not truncate it or invent a relocation.
- <TASK-ID-B> · pending · P2 · L2 · <Inline pending-work description>
- <TASK-ID-C> · blocked · P3 · L2 · <Title> → tasks/<TASK-ID-C>-task-description.md
Section headers and blank lines are allowed; only task bullet lines are schema validated.
activeContext.md thin contractActive-Tasks-only mirror. The file is bounded (≤ 30 lines) and contains a strict mirror of tasks.md § Active:
# Active Context
## Active Tasks
<!-- strict mirror of tasks.md § Active — identical lines, identical order -->
- <TASK-ID-A> · in_progress · P1 · L3 · <Title> → tasks/<TASK-ID-A>-task-description.md
The legacy ## Последние завершённые section is abolished — /dr-doctor removes it during Pass 3. Completion history must be looked up in documentation/archive/ or git log instead of being mirrored into a rolling section.
progress.mdAbolished. /dr-doctor --fix deletes the file unconditionally during Pass 3. No rolling completion log is maintained anywhere; per-task notes live in datarim/tasks/{TASK-ID}-task-description.md § Implementation Notes, and historical context lives in documentation/archive/.
datarim/tasks/{TASK-ID}-task-description.md is the only place that holds the task's content. Format:
---
id: <TASK-ID> # regex ^[A-Z]{2,10}-[0-9]{4}$
title: <string> # ≤ 80 chars, single line
status: <enum> # in_progress|blocked|not_started|pending|blocked-pending|cancelled
priority: <enum> # P0|P1|P2|P3
complexity: <enum> # L1|L2|L3|L4
type: <string> # free-form (e.g. framework, infra, content, bugfix)
project: <string> # free-form (e.g. Datarim, Arcanada, Verdicus)
started: <date> # YYYY-MM-DD
parent: <TASK-ID|null> # null if no parent
related: <list[TASK-ID]> # YAML list, empty list ok
prd: <relpath|null> # e.g. prd/PRD-{TASK-ID}.md, null if none
plan: <relpath|null> # e.g. plans/{TASK-ID}-plan.md, null if none
---
All 12 keys are mandatory. Schema is closed — additional keys are NOT added by /dr-doctor. Project-specific extensions go inside the body, not in frontmatter.
Five canonical sections (in order):
## Overview
2–5 sentences. Problem + outcome.
## Acceptance Criteria
- [ ] AC-1: …
- [ ] AC-2: …
## Constraints
Bullet list of immutable boundaries (security, performance, compatibility).
## Out of Scope
Explicit non-goals. What this task does NOT do.
## Related
Cross-references: parent PRD, sibling tasks, prior reflection notes.
Implementation Notes (free-form scratch, optional) MAY follow as ## Implementation Notes.
Discussion / decisions log MAY follow as ## Decisions.
Anything beyond ~250 lines is a smell — split into a PRD or design doc.
--fix) — 8 passesApplied by scripts/datarim-doctor.sh --fix. Single transactional sequence guarded by the data-loss safety contract (see next section). Atomic per file via mv tmp file.
## Backlog section reject in tasks.mdtasks.md contains a top-level ## Backlog header, emit finding '## Backlog' section forbidden in tasks.md — move bullets manually to backlog.md and exit 1 in dry-run mode.--fix does NOT auto-migrate the section (cross-task hunk corruption risk). The section is preserved verbatim; the operator manually relocates bullets to backlog.md with correct task IDs and statuses.datarim/tasks.md and datarim/backlog.md for legacy block-style headings: ^### ([A-Z]{2,10}-[0-9]{4}(?:-[A-Za-z0-9]+)*):?\s*(.*)$. Trailing colon is optional; compound IDs (e.g. PREFIX-NNNN-FOLLOWUP-slug) accepted.### ID: heading or section break.- or * allowed):
statuspriority complexity type started parent related prdplanid. Second capture group → title (truncated to 80 chars on word boundary). If title text is empty (e.g. ### PREFIX-NNNN-FOLLOWUP-slug with no trailing text), synthesise title from the compound suffix: strip the literal FOLLOWUP- token, replace remaining hyphens with spaces, sentence-case the first character; append « follow-up » suffix when the literal FOLLOWUP appeared in the ID.## Overview (until first sub-heading) and pass through other sub-headings unchanged.pending ↔ not_started resolved by source file (tasks.md → not_started/in_progress/blocked; backlog.md → pending/blocked-pending/cancelled).P[0-3]; missing → P3.L[1-4]; missing → L2 (most common default).TUNE → Datarim, INFRA → Arcanada, …) or unknown.datarim/tasks/{TASK-ID}-task-description.md. Skip if already exists with valid frontmatter (idempotent).## Active (in_progress/blocked/not_started); backlog.md → ## Pending (pending/blocked-pending/cancelled).mv tasks.md.tmp tasks.md.activeContext.md + progress.md retirement**Current Task:** {ID} line into ## Active Tasks list with the corresponding one-liner; mirror is bounded to ≤ 30 lines.## Последние завершённые section if present (abolished — see § activeContext.md thin contract above). progress.md if it exists.backlog-archive.md migrationbacklog-archive.md.documentation/archive/cancelled/archive-{TASK-ID}.md (header notes synthesised from backlog-archive.md by datarim-doctor.sh Pass 4).documentation/archive/{area}/archive-{TASK-ID}.md; unrecognised area falls back to general/.--conflict-policy=prompt|keep|overwrite|skip|abort (default prompt; auto-skip in non-TTY); --no-prompt is the canonical CI alias for skip.backlog-archive.md is preserved in-tree as backlog-archive.md.pre-v2.bak (sidecar; operator-visible).Strips legacy archive sections (## Archived in tasks.md / backlog.md; ### Archived, ### Recently Archived, ## Последние завершённые in activeContext.md) and migrates each archive bullet to a canonical documentation/archive/{area}/archive-{TASK-ID}.md doc. The canonical thin-index contract (§ activeContext.md thin contract above, § Operational File Schema) prohibits archive sections in operational files: completion history lives in documentation/archive/, recency hint is computed at runtime via /dr-status --recent N. Pass 6 enforces that contract.
Four archive-bullet shapes are recognised (priority S1 → S2 → S4 → S3):
- **TASK-ID** — title (YYYY-MM-DD) → documentation/archive/{area}/archive-TASK-ID.md- **TASK-ID** (status, YYYY-MM-DD) — title (status ∈ completed | cancelled | …)- **TASK-ID** context-words — title (context word(s) between **ID** and em-dash)- **TASK-ID** — title (no date, no link, no mid-bold context)Task IDs may be compound — <PREFIX-NNNN>, <PREFIX-NNNN>-<SUFFIX>, <PREFIX-NNNN>-<FOLLOWUP-SLUG>. Numeric component (-[0-9]{4}) is required; suffix (-[A-Za-z0-9]+)* is optional.
Per bullet:
^[A-Z]{2,10}-[0-9]{4}(-[A-Za-z0-9]+)*$. Invalid → preserve in operational file with manual-migration marker.→ documentation/archive/{path}.md, prefer that path as canonical; otherwise fall back to prefix_to_area() (prefix→area mapping) → documentation/archive/{area}/archive-{ID}.md.documentation/archive/; violation rejects explicit pointer and falls back to prefix_to_area. If fallback also escapes → preserve, warn.{ID} literal inside → strip bullet from operational file.find documentation/archive/ -name "archive-{ID}.md" (depth ≤ 3) checks every area subdir; if found with ID literal, strip bullet with warning archive at unexpected area. Otherwise synthesise stub with frontmatter (id, title, status, {status}_at, source: synthesised from operational-file by datarim-doctor.sh Pass 6, original_block_sha) + body = original bullet content; strip bullet.{ID} literal → invoke resolve_conflict() (--no-prompt defaults to skip in non-TTY); on skip, preserve bullet in operational file with <!-- bullets pending manual migration … --> marker; on overwrite, synthesise stub.Headerless fallback: operational files without any archive section header are processed line-by-line. Bullets parseable via S1–S4 are candidates; bullets with explicit non-terminal status (in_progress, not_started, pending, blocked, approved, review, active) are passed through as active content. Other parseable bullets follow the same dispatch as the headered branch (explicit pointer → defensive find → synthesise). Non-parseable lines (one-liner thin-index entries, headers, frontmatter) pass through unchanged.
Unparseable bullets (no shape match) → preserve with warning Pass 6: unparseable archive bullet. Operator fixes manually.
After per-file processing, Doctor logs a one-line summary: Pass 6 {file}: parsed={N} stripped={M} synthesised={K} skipped={L}. Distributed users see exactly what migrated; tarball backup covers rollback.
Idempotent: files without any archive header early-return; second --fix on a migrated tree produces zero changes.
Counter-example — what Doctor MUST NOT do (Approach D, rejected on QA): add a whitelist exception that preserves archive sections by design. This would legalise non-compliant pattern, accumulate token-bloat in operational files (12-18 KB on typical installations × 10-30 reads per session = 120-540 KB lost tokens), and contradict the canonical contract datarim-system.md § activeContext.md thin contract («one section only», v1.19.1). Migration, not preservation, is the correct enforcement.
Repo-local convention emits a one-line HTML comment after each archival:
<!-- {ID} {verb} {YYYY-MM-DD} → documentation/archive/{area}/archive-{ID}.md ({optional context}) -->
Recognised status verbs: archived | cancelled | superseded | closed | dropped. Both arrow forms accepted: → and ->.
Algorithm per operational file (tasks.md, backlog.md, activeContext.md):
documentation/archive/...md).validate_relpath against documentation/archive/ root rejects .. segments.archive-{ID}.md (no cross-ID strip).[ -f "{ROOT_ABS}/../{relpath}" ].stripped++.Pass 7: archive file missing for {ID}: {relpath} (preserving comment); counter preserved++.mv tmpfile target only when stripped > 0.Log line: Pass 7 {file}: stripped={N} preserved={M}. Idempotent: second --fix finds the same set of comments minus the previously-stripped lines; stripped=0 on second run.
Pass 7 WARN is an observability rail, not a fallback. The preserved++ counter and the Pass 7: archive file missing for {ID}: {relpath} log line exist to surface a genuine data gap — an archive comment citing a file that does not exist on disk. When this WARN fires, the correct operator action is to file a fixture against the framework repo reproducing the gap, not to suppress or silence the WARN; treating it as noise defeats the purpose of the verified-strip contract.
Bats fixture marker discipline. Every new bats fixture section that embeds TASK-ID-shaped literals (e.g. TUNE-0197, ARCA-0001) MUST wrap those literals in its own <!-- gate:history-allowed --> / <!-- /gate:history-allowed --> marker pair (skills/evolution/history-agnostic-gate.md § Escape Hatch). Do not rely on a marker pair added earlier in the file — the diff-only gate treats markers as line-scoped context, so a fixture section outside any marker pair of its own is not covered by one opened elsewhere in the same file.
--fix finishes the four mutating passes, the script composes the existing scan dispatch in dry-run mode and re-validates the tree..pre-v2.bak sidecar present (when Pass 4 ran), and an immediate second --fix is a no-op (idempotency).Before Pass 1: if every operational file is already in canonical shape — zero ### TASK-ID: headings in tasks.md / backlog.md, no legacy backlog-archive.md (or only the .pre-v2.bak sidecar remains), progress.md does not exist, no ## Последние завершённые section in activeContext.md, and every bullet line matches the regex applicable to its file — exit 0 immediately. Cheap probe used by /dr-init self-heal.
Defence-in-depth around --fix mode. Every --fix invocation MUST satisfy all four rails; violation of any rail aborts the run with state preserved.
umask 077 tarball of the entire datarim/ root to ${DATARIM_DOCTOR_BACKUP_DIR:-/tmp}/datarim-backup-{TS}.tgz. Path is surfaced in the success summary so the operator can locate it for manual rollback..pre-v2.bak sidecar in-tree alongside the original (operator-visible, survives normal git workflows). Pass 5 asserts the sidecar exists.PARSED_COUNT) and after rewrite (EMITTED_COUNT). Invariant: EMITTED_COUNT ≥ PARSED_COUNT. Violation triggers restore_backup_and_die(): removes mutated state in-place, tar -xzf the pre-write tarball back over the tree, and exits 2 with emitted=N < parsed=M (data loss detected).install.sh default mode, ~/.claude/scripts/datarim-doctor.sh is a directory-symlink target of the canonical Datarim repo path. Divergence between runtime and repo is impossible by construction; rogue v2 binaries cannot be silently dropped on top of the runtime.The contract is a hard precondition for any future --fix change: new mutating passes MUST plug into the same PARSED_COUNT / EMITTED_COUNT accounting and respect the tarball restore path.
tasks/{TASK-ID}-task-description.md.bak-{timestamp} and write canonical version. Operator merges manually if needed.If a task ID appears in both files (legacy state):
tasks.md (active wins over pending).backlog.md.Source-file state wins (tasks.md status overrides description's frontmatter status if they disagree). Description frontmatter is rewritten to match.
Resolved by --conflict-policy:
prompt (default in TTY) — interactive choice per conflicting archive-{TASK-ID}.md.keep — preserve existing archive file untouched, log skip.overwrite — replace existing archive file with synthesised stub, original moved to .bak-{TS}.skip — same as keep but quiet (default in non-TTY; --no-prompt alias).abort — fail the migration on first conflict; safety contract restores the tree.scripts/lib/canonicalise.sh rejects any path that resolves outside $DATARIM_ROOT (lexical canonicalisation, no I/O). Tool exits 4. Operator inspects the entry manually.
datarim/tasks.md — exit 0; nothing to do.→ character — escaped or rejected (regex disallows). Operator must rename.iconv).flock $DATARIM_ROOT/.dr-doctor.lock. Second instance exits 3.tasks/ subdirectory — created with mkdir -p before any description file write.documentation/archive/{area}/ — created with mkdir -p during Pass 4 dispatch.wiki/_raw_/ semantic-orphan check — advisory-only pass (scope all): flags a file whose basename
shares no token (≥4 chars, alnum-only) with its first 300 bytes of content — a signal of an accidental
paste into the wrong file. Report-only; --fix does not touch wiki/_raw_/.scripts/datarim-doctor.sh [OPTIONS]
OPTIONS:
--fix Apply fixes (default: dry-run)
--scope=<scope> One of: tasks|backlog|active|backlog-archive|progress|descriptions|all
(default: all)
--root=<path> Datarim root (default: walk up from $PWD)
--quiet Exit-code only (used by /dr-init self-heal)
--no-prompt Skip Pass 4 conflicts (alias for --conflict-policy=skip)
--conflict-policy=<policy> One of: prompt|keep|overwrite|skip|abort
(default: prompt; auto-skip in non-TTY)
--help
ENVIRONMENT:
DATARIM_DOCTOR_BACKUP_DIR Override pre-write tarball directory (default: /tmp)
EXIT CODES:
0 Compliant (or --fix succeeded)
1 Non-compliant findings (dry-run)
2 Migration error (--fix aborted; tarball restored, state preserved)
3 Concurrent invocation (lock held)
4 Path traversal / security violation
64 Usage error
pre-archive-check.sh runs the line-format validator before /dr-archive proceeds. Lines that don't match the canonical regex block the archive with: BLOCK: {file} contains non-compliant lines (run /dr-doctor).
Escape hatch: pre-archive-check.sh --no-schema-check (used during in-flight migration; not for normal use).
commands/dr-doctor.md — operator-facing wrapper.scripts/datarim-doctor.sh — implementation.tests/datarim-doctor.bats — covers compliance detection, 6-pass migration, safety contract (tarball + invariant + restore), conflict policies, regex compliance, CLI/UX.skills/datarim-system/SKILL.md — broader Datarim file layout and path resolution.Core Datarim rules. Load this entry first, then only the fragment needed for paths, storage, numbering, backlog, routing, or archive behavior.
Post-QA hardening — detects task type (code, docs, research, legal, content, infra) and applies the matching verification checklist before archiving.
Testing pyramid, frameworks, mocking. Load first; then the fragment for the active gate (live smoke, silent failure, bats, legacy triage).
Preserve Datarim task continuity while orchestrated Claude Code or Codex sessions compact or clear context at deterministic pressure thresholds.
Immutability contract for all pipeline stages: artefact freeze, V-AC parity, non-code parity, anti-tautological rule, and return-to-source transition.
Init-task artefact: verbatim operator brief + append-log, mandatory read by every pipeline command. Source of truth for operator intent.