# ✂ docujoint template — adapt this to your team; delete this line when it is yours.
# Linear-in-a-KB — a tracker format with Linear's default vocabulary, expressed
# as declarations. Everything Linear hard-codes is a line you can edit here:
#
#   · the STATUSES are Linear's own defaults (Triage, Backlog, Todo,
#     In Progress, In Review, Done, Canceled, Duplicate) — rename them, add a
#     `Shaping` or a `Ready to Deploy`, and every view, form and chart follows;
#   · their five CATEGORIES (backlog / unstarted / started / completed /
#     canceled) are not machinery here — they are the `where:` filters the
#     dashboard's views spell out, which is all they ever were;
#   · issues belong to TEAM documents; PROJECTS are documents with milestones
#     and a health, linked from the issue's Project column;
#   · status is a STORED column, deliberately: a tracker's status is a
#     planning decision a person makes, not evidence the engine can derive.
#     What the engine still owns is the vocabulary (an unknown status is an
#     error, not a new lane) and the forms (every write goes through the
#     declared doors).

format:
  name: linear
  version: "0.1"
  okf_version: "0.1"

vault:
  reserved_files: [index.md, log.md]
  links:
    forbid:
      - { rule: wikilink, severity: error, code: wikilink }
    broken_internal: warn
  index: { generated: true }
  tree:
    - { path: "Teams/",    types: [team] }
    - { path: "Projects/", types: [project] }
    - { path: "Docs/",     types: [doc] }

shared:
  empty_markers: ["—", "-", ""]
  stub_marker: "_To be documented._"
  # what this format's values MEAN — Linear's semantics, your words to change
  colors:
    Triage: warning
    Backlog: muted
    Todo: muted
    "In Progress": warning
    "In Review": info
    Done: success
    Canceled: muted
    Duplicate: muted
    Urgent: error
    High: warning
    Medium: info
    Low: muted
    Planned: info
    Completed: success
    "On track": success
    "At risk": warning
    "Off track": error
    shipped: success
    upcoming: muted
  enums:
    # Linear's default issue lifecycle, verbatim (References/Linear.md r2:
    # "ship their default as the template's starting vocabulary"). The five
    # categories live in the dashboard's where-clauses, not here.
    status: [Triage, Backlog, Todo, In Progress, In Review, Done, Canceled, Duplicate]
    # No priority is the EMPTY cell — a declared empty marker, not a value.
    priority: [Urgent, High, Medium, Low]
    project_status: [Backlog, Planned, In Progress, Completed, Canceled]
    health: [On track, At risk, Off track]

# Relations (E38): typed, directed references BETWEEN records, declared as
# vocabulary here and carried by ordinary columns below (columns[].relation).
# The cell is a comma-joined uuid list — the address that survives renames —
# and the UI resolves titles. `blocked-by` is DERIVED: one stored direction,
# read from either end.
relations:
  blocks:
    label: Blocks
    about: this issue must ship before the target can
    direction: directed
    inverse: blocked-by
    inverse_label: Blocked by
    to: [issues]
  relates-to:
    label: Related
    about: worth reading together — no direction implied
    direction: symmetric
    to: [issues, milestones]

markdown:
  comments: [html, obsidian]

blocks:
  # The issue — Linear's object model as declared columns. Identity is the
  # uuid (survives renames, addressable by `dj rows patch --update <uuid>`);
  # the ID is the human handle, unique per document like TEAM-n is per team.
  # `tier: operational` — issues are WORKING-LAYER records (E35, decision
  # 0019): they live in the host's store, never in a commit, so a status
  # change is one PATCH with no referee and no re-bake. New KBs are born
  # migrated: the template ships NO committed sidecar for this block, and
  # `dj pull --records` mirrors the working layer into
  # `*.records.local.yaml` beside each document.
  issues:
    heading: Issues
    tier: operational
    identity: uuid
    columns:
      - { name: ID, id_grammar: "i<n>", unique: error, sequential: warn }
      - { name: Title, required: true }
      - { name: Status, enum: shared.status, unknown: error, code: issue-status }
      - { name: Priority, optional: true, enum: shared.priority, unknown: error, code: issue-priority }
      - { name: Assignee, optional: true }
      - { name: Labels, optional: true }
      # a LINK to the project document, so the graph joins issues to projects
      - { name: Project, optional: true }
      - { name: Estimate, optional: true, type: number }
      - { name: Due, optional: true, type: date }
      # relation columns (E38): uuid lists addressing other records — the
      # blocked-by reading of Blocks is derived, never a column of its own
      - { name: Blocks, optional: true, relation: blocks }
      - { name: Related, optional: true, relation: relates-to }
    forms:
      # the pickers — Linear's `s`, `p`, `a` keys, as declared doors. On the
      # rendered dashboard these are the cell controls; on the wire they are
      # the same guarded command every write path uses.
      set-status:
        label: Status
        set: { Status: "{value}" }
        fields:
          - { name: value, kind: options, from_enum: Status }
      set-priority:
        label: Priority
        set: { Priority: "{value}" }
        fields:
          - { name: value, kind: options, from_enum: Priority }
      assign:
        label: Assign
        set: { Assignee: "{who}" }
        fields:
          # kind: person — the picker offers whoever the HOST's directory
          # names (cloud: your org's members; local: people.yaml), and writes
          # the portable @handle. No directory: a plain text field, so the
          # form never stops working away from its host.
          - { name: who, label: Assignee, kind: person }
      # the relation door (E38): kind record offers records of the relation's
      # to: blocks and writes the picked UUIDs — the same guarded command
      # every other write speaks
      set-blocks:
        label: Blocks
        set: { Blocks: "{targets}" }
        fields:
          - { name: targets, label: Blocks, kind: record, relation: blocks, multi: true }
    # Linear's triage guard ("set a priority before accepting") lives in the
    # DASHBOARD as the needs-priority indicator: block-level checks speak bare
    # truthiness, and "status is not Triage" is an equality — the dashboard's
    # query grammar has it, so the guard is a number on the front page instead.
    display:
      intro: prose
      layout: { narrow: cards }
      columns:
        - { col: ID, style: mono, card: eyebrow }
        - { col: Title, style: md, card: title }
        - { col: Status, style: progress, card: badge, control: { form: set-status } }
        - { col: Priority, style: chip, card: badge, control: { form: set-priority } }
        # no control: here — a single person-kind field fits none of the three
        # control rungs (options / record / text, E43 §13.2), so a declared
        # control would render nothing and load with a control-unbound warn;
        # Assign keeps its row-action surface instead, which is where the
        # picker has actually lived since E35.
        - { col: Assignee, style: person, card: hide }
        - { col: Labels, style: chip, card: badge }
        - { col: Project, style: md, card: badge }
        - { col: Estimate, style: mono, card: hide }
        - { col: Due, style: mono, card: hide }
        # style: record — the uuid list drawn as resolved title chips; the
        # bound form makes the cell authorable in three places, all one write:
        # the table cell (chips gain + and ✕), the record page's section, and
        # the row's action panel wherever the cell itself does not render
        - { col: Blocks, style: record, card: hide, control: { form: set-blocks } }

  # A project's milestones — name, target, and whether it shipped. The gantt
  # reads Target for the bar and `shipped` for the progress fill.
  milestones:
    heading: Milestones
    identity: uuid
    columns:
      - { name: ID, id_grammar: "m<n>", unique: error }
      - { name: Milestone, required: true }
      - { name: Target, optional: true, type: date }
      - { name: Done, optional: true }
      - { name: Notes, optional: true }
      # the CANONICAL end of relates-to (E38): milestones live in committed
      # sidecars, so a Related cell here moves by commit and passes the
      # referee — the same relation, the other storage discipline
      - { name: Related, optional: true, relation: relates-to }
    derive:
      state:
        - { when: "done", then: shipped }
        - { else: upcoming }
    display:
      intro: prose
      layout: { narrow: cards }
      columns:
        - { col: ID, style: mono, state: marker, card: eyebrow }
        - { col: Milestone, style: md, card: title }
        - { col: Target, style: mono }
        - { col: Done, style: md }
        - { col: Notes, style: md }

types:
  # A team: the container issues belong to. `key` is the human prefix your
  # people say out loud ("ENG-42"); the block's own ids stay i<n> per document.
  team:
    frontmatter:
      required: { title: string, description: string }
      optional: { tags: "string[]", key: string, timestamp: date }
    sections:
      expect: [Issues]
      duplicates: error
    blocks: [issues]

  # A project: a goal-shaped container with dates, a health, and milestones.
  # Issues point AT it (the Project column links here); the timeline view
  # draws it from start to end with milestone progress as the fill.
  project:
    frontmatter:
      required: { title: string, description: string,
                  status: { enum: [Backlog, Planned, In Progress, Completed, Canceled] } }
      optional: { tags: "string[]", lead: string, health: { enum: [On track, At risk, Off track] },
                  start: date, end: date, timestamp: date }
    sections:
      duplicates: error
    blocks: [milestones]

  # A doc: the PRD/spec that lives BESIDE the work — Linear's collaborative
  # documents, except the document is the system of record here.
  doc:
    frontmatter:
      required: { title: string, description: string }
      optional: { tags: "string[]", timestamp: date }
    sections:
      duplicates: error
    blocks: []
