Skip to main content
jg-plans

Shared documents + deterministic tools

Plain Markdown.
Precise tooling.

Shared Markdown plans with acceptance criteria, read and edited by a TypeScript library and CLI.

Markdown protocol · TypeScript library · CLI · MIT licensed

Create

Start with one readable file.

Numbered acceptance criteria give every requirement a stable ID from the first draft.

01 Markdown source

plan.md
---
id: 1792612920_search-feedback-criteria
created_at: 2026-10-21T20:02:00Z
updated_at: 2026-10-21T20:02:00Z
revision: 1
status: pending
protocol: "0.2"
priority: medium
repositories:
  - example/search
error_codes:
  - repository: example/search
    registry: docs/errors.md
    codes:
      - 4100
      - SEARCH_FILTER_INVALID
---
# Search feedback

Explain empty search results and give people a useful next action.

## Acceptance criteria

- AC-01: Empty search results explain why nothing matched.
- AC-02: The empty state offers one next action, such as clearing the active filters.
- AC-03: The explanation and the action work with a keyboard and a screen reader.

02 Complete API result

createPlan(options)
JSON · complete result
{
  "directory": "jg-plans/search-feedback",
  "plan": {
    "name": "search-feedback-criteria",
    "source": "---\nid: 1792612920_search-feedback-criteria\ncreated_at: 2026-10-21T20:02:00Z\nupdated_at: 2026-10-21T20:02:00Z\nrevision: 1\nstatus: pending\nprotocol: \"0.2\"\npriority: medium\nrepositories:\n  - example/search\nerror_codes:\n  - repository: example/search\n    registry: docs/errors.md\n    codes:\n      - 4100\n      - SEARCH_FILTER_INVALID\n---\n# Search feedback\n\nExplain empty search results and give people a useful next action.\n\n## Acceptance criteria\n\n- AC-01: Empty search results explain why nothing matched.\n- AC-02: The empty state offers one next action, such as clearing the active filters.\n- AC-03: The explanation and the action work with a keyboard and a screen reader.\n",
    "body": "# Search feedback\n\nExplain empty search results and give people a useful next action.\n\n## Acceptance criteria\n\n- AC-01: Empty search results explain why nothing matched.\n- AC-02: The empty state offers one next action, such as clearing the active filters.\n- AC-03: The explanation and the action work with a keyboard and a screen reader.\n",
    "frontmatter": {
      "start": 0,
      "contentStart": 4,
      "contentEnd": 331,
      "end": 335,
      "closed": true
    },
    "rawMetadata": {
      "id": "1792612920_search-feedback-criteria",
      "created_at": "2026-10-21T20:02:00Z",
      "updated_at": "2026-10-21T20:02:00Z",
      "revision": 1,
      "status": "pending",
      "protocol": "0.2",
      "priority": "medium",
      "repositories": [
        "example/search"
      ],
      "error_codes": [
        {
          "repository": "example/search",
          "registry": "docs/errors.md",
          "codes": [
            4100,
            "SEARCH_FILTER_INVALID"
          ]
        }
      ]
    },
    "metadata": {
      "id": "1792612920_search-feedback-criteria",
      "created_at": "2026-10-21T20:02:00Z",
      "updated_at": "2026-10-21T20:02:00Z",
      "revision": 1,
      "status": "pending",
      "protocol": "0.2",
      "priority": "medium",
      "repositories": [
        "example/search"
      ],
      "error_codes": [
        {
          "repository": "example/search",
          "registry": "docs/errors.md",
          "codes": [
            4100,
            "SEARCH_FILTER_INVALID"
          ]
        }
      ]
    },
    "preamble": {
      "start": 0,
      "end": 422
    },
    "sections": [
      {
        "title": "Acceptance criteria",
        "heading": {
          "start": 422,
          "end": 445
        },
        "body": {
          "start": 445,
          "end": 673
        }
      }
    ],
    "protocol": "0.2",
    "criteria": [
      {
        "id": "AC-01",
        "idText": "AC-01",
        "text": "Empty search results explain why nothing matched.",
        "location": {
          "offset": 448,
          "line": 24,
          "column": 3
        },
        "range": {
          "start": 446,
          "end": 504
        }
      },
      {
        "id": "AC-02",
        "idText": "AC-02",
        "text": "The empty state offers one next action, such as clearing the active filters.",
        "location": {
          "offset": 507,
          "line": 25,
          "column": 3
        },
        "range": {
          "start": 505,
          "end": 590
        }
      },
      {
        "id": "AC-03",
        "idText": "AC-03",
        "text": "The explanation and the action work with a keyboard and a screen reader.",
        "location": {
          "offset": 593,
          "line": 26,
          "column": 3
        },
        "range": {
          "start": 591,
          "end": 672
        }
      }
    ],
    "diagnostics": [],
    "sha256": "946816c6f612bb4f868842168bcfd802637e84a40d77c238904afb0f9ba3cbbf"
  },
  "milestones": null,
  "diagnostics": []
}
Consumer-derived summary
Consumer-derived summary · not an API return
{
  "plan": "readable",
  "milestones": "missing",
  "revision": 1,
  "status": "pending",
  "criteria": [
    "AC-01",
    "AC-02",
    "AC-03"
  ]
}
Public API call · excerpt
Selected API call · inputs from this example
const created = await createPlan({
  root: './jg-plans',
  slug: 'search-feedback',
  body, // the Markdown body shown below
  metadata, // the displayed example metadata, including protocol: '0.2'
});

Inspect

Read what is actually there.

One selected directory returns metadata, criteria and diagnostics without recreating anything.

01 Markdown source

plan.md
---
id: 1792612920_search-feedback-criteria
created_at: 2026-10-21T20:02:00Z
updated_at: 2026-10-21T20:02:00Z
revision: 1
status: pending
protocol: "0.2"
priority: medium
repositories:
  - example/search
error_codes:
  - repository: example/search
    registry: docs/errors.md
    codes:
      - 4100
      - SEARCH_FILTER_INVALID
---
# Search feedback

Explain empty search results and give people a useful next action.

## Acceptance criteria

- AC-01: Empty search results explain why nothing matched.
- AC-02: The empty state offers one next action, such as clearing the active filters.
- AC-03: The explanation and the action work with a keyboard and a screen reader.

02 Complete API result

inspectPlan(directory)
JSON · complete result
{
  "directory": "jg-plans/search-feedback",
  "plan": {
    "state": "readable",
    "path": "jg-plans/search-feedback/plan.md",
    "sha256": "946816c6f612bb4f868842168bcfd802637e84a40d77c238904afb0f9ba3cbbf",
    "value": {
      "name": "search-feedback-criteria",
      "source": "---\nid: 1792612920_search-feedback-criteria\ncreated_at: 2026-10-21T20:02:00Z\nupdated_at: 2026-10-21T20:02:00Z\nrevision: 1\nstatus: pending\nprotocol: \"0.2\"\npriority: medium\nrepositories:\n  - example/search\nerror_codes:\n  - repository: example/search\n    registry: docs/errors.md\n    codes:\n      - 4100\n      - SEARCH_FILTER_INVALID\n---\n# Search feedback\n\nExplain empty search results and give people a useful next action.\n\n## Acceptance criteria\n\n- AC-01: Empty search results explain why nothing matched.\n- AC-02: The empty state offers one next action, such as clearing the active filters.\n- AC-03: The explanation and the action work with a keyboard and a screen reader.\n",
      "body": "# Search feedback\n\nExplain empty search results and give people a useful next action.\n\n## Acceptance criteria\n\n- AC-01: Empty search results explain why nothing matched.\n- AC-02: The empty state offers one next action, such as clearing the active filters.\n- AC-03: The explanation and the action work with a keyboard and a screen reader.\n",
      "frontmatter": {
        "start": 0,
        "contentStart": 4,
        "contentEnd": 331,
        "end": 335,
        "closed": true
      },
      "rawMetadata": {
        "id": "1792612920_search-feedback-criteria",
        "created_at": "2026-10-21T20:02:00Z",
        "updated_at": "2026-10-21T20:02:00Z",
        "revision": 1,
        "status": "pending",
        "protocol": "0.2",
        "priority": "medium",
        "repositories": [
          "example/search"
        ],
        "error_codes": [
          {
            "repository": "example/search",
            "registry": "docs/errors.md",
            "codes": [
              4100,
              "SEARCH_FILTER_INVALID"
            ]
          }
        ]
      },
      "metadata": {
        "id": "1792612920_search-feedback-criteria",
        "created_at": "2026-10-21T20:02:00Z",
        "updated_at": "2026-10-21T20:02:00Z",
        "revision": 1,
        "status": "pending",
        "protocol": "0.2",
        "priority": "medium",
        "repositories": [
          "example/search"
        ],
        "error_codes": [
          {
            "repository": "example/search",
            "registry": "docs/errors.md",
            "codes": [
              4100,
              "SEARCH_FILTER_INVALID"
            ]
          }
        ]
      },
      "preamble": {
        "start": 0,
        "end": 422
      },
      "sections": [
        {
          "title": "Acceptance criteria",
          "heading": {
            "start": 422,
            "end": 445
          },
          "body": {
            "start": 445,
            "end": 673
          }
        }
      ],
      "protocol": "0.2",
      "criteria": [
        {
          "id": "AC-01",
          "idText": "AC-01",
          "text": "Empty search results explain why nothing matched.",
          "location": {
            "offset": 448,
            "line": 24,
            "column": 3
          },
          "range": {
            "start": 446,
            "end": 504
          }
        },
        {
          "id": "AC-02",
          "idText": "AC-02",
          "text": "The empty state offers one next action, such as clearing the active filters.",
          "location": {
            "offset": 507,
            "line": 25,
            "column": 3
          },
          "range": {
            "start": 505,
            "end": 590
          }
        },
        {
          "id": "AC-03",
          "idText": "AC-03",
          "text": "The explanation and the action work with a keyboard and a screen reader.",
          "location": {
            "offset": 593,
            "line": 26,
            "column": 3
          },
          "range": {
            "start": 591,
            "end": 672
          }
        }
      ],
      "diagnostics": []
    }
  },
  "milestones": {
    "state": "missing",
    "path": "jg-plans/search-feedback/milestones.md"
  },
  "criteria": {
    "protocol": "0.2",
    "criteria": [
      {
        "id": "AC-01",
        "text": "Empty search results explain why nothing matched.",
        "location": {
          "offset": 448,
          "line": 24,
          "column": 3
        },
        "milestones": [],
        "progress": "pending"
      },
      {
        "id": "AC-02",
        "text": "The empty state offers one next action, such as clearing the active filters.",
        "location": {
          "offset": 507,
          "line": 25,
          "column": 3
        },
        "milestones": [],
        "progress": "pending"
      },
      {
        "id": "AC-03",
        "text": "The explanation and the action work with a keyboard and a screen reader.",
        "location": {
          "offset": 593,
          "line": 26,
          "column": 3
        },
        "milestones": [],
        "progress": "pending"
      }
    ],
    "diagnostics": []
  },
  "diagnostics": [
    {
      "document": "milestones",
      "code": "MISSING_MILESTONES",
      "severity": "info",
      "message": "The optional milestone file is absent."
    }
  ]
}
Consumer-derived summary
Consumer-derived summary · not an API return
{
  "plan": "readable",
  "milestones": "missing",
  "metadata": {
    "id": "1792612920_search-feedback-criteria",
    "created_at": "2026-10-21T20:02:00Z",
    "updated_at": "2026-10-21T20:02:00Z",
    "revision": 1,
    "status": "pending",
    "protocol": "0.2",
    "priority": "medium",
    "repositories": [
      "example/search"
    ],
    "error_codes": [
      {
        "repository": "example/search",
        "registry": "docs/errors.md",
        "codes": [
          4100,
          "SEARCH_FILTER_INVALID"
        ]
      }
    ]
  },
  "criteria": [
    {
      "id": "AC-01",
      "progress": "pending"
    },
    {
      "id": "AC-02",
      "progress": "pending"
    },
    {
      "id": "AC-03",
      "progress": "pending"
    }
  ]
}
Public API call · excerpt
Selected API call · inputs from this example
const observed = await inspectPlan(created.directory);

Update

Make an explicit, guarded edit.

Milestones name the criteria they deliver, and each criterion's progress follows their status.

01 Markdown source

plan.md
---
id: 1792612920_search-feedback-criteria
created_at: 2026-10-21T20:02:00Z
updated_at: "2026-10-21T20:04:00Z"
revision: 1
status: "in progress"
protocol: "0.2"
priority: medium
repositories:
  - example/search
error_codes:
  - repository: example/search
    registry: docs/errors.md
    codes:
      - 4100
      - SEARCH_FILTER_INVALID
---
# Search feedback

Explain empty search results and give people a useful next action.

## Acceptance criteria

- AC-01: Empty search results explain why nothing matched.
- AC-02: The empty state offers one next action, such as clearing the active filters.
- AC-03: The explanation and the action work with a keyboard and a screen reader.

02 Complete API result

inspectPlan(directory)
JSON · complete result
{
  "directory": "jg-plans/search-feedback",
  "plan": {
    "state": "readable",
    "path": "jg-plans/search-feedback/plan.md",
    "sha256": "98f8d72a31336f3c7f0f98b709f8789d300f9047eb10195beabcbb52d57af116",
    "value": {
      "name": "search-feedback-criteria",
      "source": "---\nid: 1792612920_search-feedback-criteria\ncreated_at: 2026-10-21T20:02:00Z\nupdated_at: \"2026-10-21T20:04:00Z\"\nrevision: 1\nstatus: \"in progress\"\nprotocol: \"0.2\"\npriority: medium\nrepositories:\n  - example/search\nerror_codes:\n  - repository: example/search\n    registry: docs/errors.md\n    codes:\n      - 4100\n      - SEARCH_FILTER_INVALID\n---\n# Search feedback\n\nExplain empty search results and give people a useful next action.\n\n## Acceptance criteria\n\n- AC-01: Empty search results explain why nothing matched.\n- AC-02: The empty state offers one next action, such as clearing the active filters.\n- AC-03: The explanation and the action work with a keyboard and a screen reader.\n",
      "body": "# Search feedback\n\nExplain empty search results and give people a useful next action.\n\n## Acceptance criteria\n\n- AC-01: Empty search results explain why nothing matched.\n- AC-02: The empty state offers one next action, such as clearing the active filters.\n- AC-03: The explanation and the action work with a keyboard and a screen reader.\n",
      "frontmatter": {
        "start": 0,
        "contentStart": 4,
        "contentEnd": 339,
        "end": 343,
        "closed": true
      },
      "rawMetadata": {
        "id": "1792612920_search-feedback-criteria",
        "created_at": "2026-10-21T20:02:00Z",
        "updated_at": "2026-10-21T20:04:00Z",
        "revision": 1,
        "status": "in progress",
        "protocol": "0.2",
        "priority": "medium",
        "repositories": [
          "example/search"
        ],
        "error_codes": [
          {
            "repository": "example/search",
            "registry": "docs/errors.md",
            "codes": [
              4100,
              "SEARCH_FILTER_INVALID"
            ]
          }
        ]
      },
      "metadata": {
        "id": "1792612920_search-feedback-criteria",
        "created_at": "2026-10-21T20:02:00Z",
        "updated_at": "2026-10-21T20:04:00Z",
        "revision": 1,
        "status": "in progress",
        "protocol": "0.2",
        "priority": "medium",
        "repositories": [
          "example/search"
        ],
        "error_codes": [
          {
            "repository": "example/search",
            "registry": "docs/errors.md",
            "codes": [
              4100,
              "SEARCH_FILTER_INVALID"
            ]
          }
        ]
      },
      "preamble": {
        "start": 0,
        "end": 430
      },
      "sections": [
        {
          "title": "Acceptance criteria",
          "heading": {
            "start": 430,
            "end": 453
          },
          "body": {
            "start": 453,
            "end": 681
          }
        }
      ],
      "protocol": "0.2",
      "criteria": [
        {
          "id": "AC-01",
          "idText": "AC-01",
          "text": "Empty search results explain why nothing matched.",
          "location": {
            "offset": 456,
            "line": 24,
            "column": 3
          },
          "range": {
            "start": 454,
            "end": 512
          }
        },
        {
          "id": "AC-02",
          "idText": "AC-02",
          "text": "The empty state offers one next action, such as clearing the active filters.",
          "location": {
            "offset": 515,
            "line": 25,
            "column": 3
          },
          "range": {
            "start": 513,
            "end": 598
          }
        },
        {
          "id": "AC-03",
          "idText": "AC-03",
          "text": "The explanation and the action work with a keyboard and a screen reader.",
          "location": {
            "offset": 601,
            "line": 26,
            "column": 3
          },
          "range": {
            "start": 599,
            "end": 680
          }
        }
      ],
      "diagnostics": []
    }
  },
  "milestones": {
    "state": "readable",
    "path": "jg-plans/search-feedback/milestones.md",
    "sha256": "46d4f0bd33d023830f4b9fc772cdd817b46d239c23166c3016fd0d08294f307a",
    "value": {
      "source": "---\njg_skills:\n  plan_id: \"1792612920_search-feedback-criteria\"\n  plan_revision: 1\n  plan_content_sha256: 21da41286dc3d021cdc6adddef1283d7affe70b92832824bd00244ff1b9058d8\n---\n\n## Milestone 1: Explain empty results\n**Status:** done\n**Milestone ID:** 3c7a2f1e-9b4d-4e8a-a6f2-1d5c7e9b0a34\n**Acceptance criteria:** AC-01\n\nAdd the explanation to the empty results view. Review it in both themes.\n\n## Milestone 2: Offer a next action\n**Status:** in progress\n**Milestone ID:** 8e1d6b2a-4f7c-4a3e-b9d1-6c2e8f4a7b15\n**Acceptance criteria:** AC-02, AC-03\n\nAdd the clear-filters action, then check it with a keyboard and a screen reader.\n",
      "body": "\n## Milestone 1: Explain empty results\n**Status:** done\n**Milestone ID:** 3c7a2f1e-9b4d-4e8a-a6f2-1d5c7e9b0a34\n**Acceptance criteria:** AC-01\n\nAdd the explanation to the empty results view. Review it in both themes.\n\n## Milestone 2: Offer a next action\n**Status:** in progress\n**Milestone ID:** 8e1d6b2a-4f7c-4a3e-b9d1-6c2e8f4a7b15\n**Acceptance criteria:** AC-02, AC-03\n\nAdd the clear-filters action, then check it with a keyboard and a screen reader.\n",
      "frontmatter": {
        "start": 0,
        "contentStart": 4,
        "contentEnd": 171,
        "end": 175,
        "closed": true
      },
      "rawMetadata": {
        "jg_skills": {
          "plan_id": "1792612920_search-feedback-criteria",
          "plan_revision": 1,
          "plan_content_sha256": "21da41286dc3d021cdc6adddef1283d7affe70b92832824bd00244ff1b9058d8"
        }
      },
      "preamble": {
        "start": 0,
        "end": 176
      },
      "sections": [
        {
          "title": "Milestone 1: Explain empty results",
          "heading": {
            "start": 176,
            "end": 214
          },
          "body": {
            "start": 214,
            "end": 392
          }
        },
        {
          "title": "Milestone 2: Offer a next action",
          "heading": {
            "start": 392,
            "end": 428
          },
          "body": {
            "start": 428,
            "end": 627
          }
        }
      ],
      "overviewTables": [],
      "milestones": [
        {
          "ordinal": 1,
          "ordinalText": "1",
          "title": "Explain empty results",
          "status": "done",
          "id": "3c7a2f1e-9b4d-4e8a-a6f2-1d5c7e9b0a34",
          "location": {
            "offset": 176,
            "line": 8,
            "column": 1
          },
          "headingEnd": 214,
          "end": 392,
          "body": "**Status:** done\n**Milestone ID:** 3c7a2f1e-9b4d-4e8a-a6f2-1d5c7e9b0a34\n**Acceptance criteria:** AC-01\n\nAdd the explanation to the empty results view. Review it in both themes.\n\n",
          "statusMarkers": [
            {
              "value": "done",
              "location": {
                "offset": 214,
                "line": 9,
                "column": 1
              },
              "valueStart": 226,
              "valueEnd": 230,
              "lineStart": 214,
              "lineEnd": 231
            }
          ],
          "idMarkers": [
            {
              "value": "3c7a2f1e-9b4d-4e8a-a6f2-1d5c7e9b0a34",
              "location": {
                "offset": 231,
                "line": 10,
                "column": 1
              },
              "valueStart": 249,
              "valueEnd": 285,
              "lineStart": 231,
              "lineEnd": 286
            }
          ],
          "criteria": [
            "AC-01"
          ],
          "criteriaMarkers": [
            {
              "value": "AC-01",
              "location": {
                "offset": 286,
                "line": 11,
                "column": 1
              },
              "valueStart": 311,
              "valueEnd": 316,
              "lineStart": 286,
              "lineEnd": 317
            }
          ]
        },
        {
          "ordinal": 2,
          "ordinalText": "2",
          "title": "Offer a next action",
          "status": "in progress",
          "id": "8e1d6b2a-4f7c-4a3e-b9d1-6c2e8f4a7b15",
          "location": {
            "offset": 392,
            "line": 15,
            "column": 1
          },
          "headingEnd": 428,
          "end": 627,
          "body": "**Status:** in progress\n**Milestone ID:** 8e1d6b2a-4f7c-4a3e-b9d1-6c2e8f4a7b15\n**Acceptance criteria:** AC-02, AC-03\n\nAdd the clear-filters action, then check it with a keyboard and a screen reader.\n",
          "statusMarkers": [
            {
              "value": "in progress",
              "location": {
                "offset": 428,
                "line": 16,
                "column": 1
              },
              "valueStart": 440,
              "valueEnd": 451,
              "lineStart": 428,
              "lineEnd": 452
            }
          ],
          "idMarkers": [
            {
              "value": "8e1d6b2a-4f7c-4a3e-b9d1-6c2e8f4a7b15",
              "location": {
                "offset": 452,
                "line": 17,
                "column": 1
              },
              "valueStart": 470,
              "valueEnd": 506,
              "lineStart": 452,
              "lineEnd": 507
            }
          ],
          "criteria": [
            "AC-02",
            "AC-03"
          ],
          "criteriaMarkers": [
            {
              "value": "AC-02, AC-03",
              "location": {
                "offset": 507,
                "line": 18,
                "column": 1
              },
              "valueStart": 532,
              "valueEnd": 544,
              "lineStart": 507,
              "lineEnd": 545
            }
          ]
        }
      ],
      "overview": [],
      "overviewPresent": false,
      "diagnostics": []
    }
  },
  "criteria": {
    "protocol": "0.2",
    "criteria": [
      {
        "id": "AC-01",
        "text": "Empty search results explain why nothing matched.",
        "location": {
          "offset": 456,
          "line": 24,
          "column": 3
        },
        "milestones": [
          {
            "ordinal": 1,
            "id": "3c7a2f1e-9b4d-4e8a-a6f2-1d5c7e9b0a34"
          }
        ],
        "progress": "done"
      },
      {
        "id": "AC-02",
        "text": "The empty state offers one next action, such as clearing the active filters.",
        "location": {
          "offset": 515,
          "line": 25,
          "column": 3
        },
        "milestones": [
          {
            "ordinal": 2,
            "id": "8e1d6b2a-4f7c-4a3e-b9d1-6c2e8f4a7b15"
          }
        ],
        "progress": "in progress"
      },
      {
        "id": "AC-03",
        "text": "The explanation and the action work with a keyboard and a screen reader.",
        "location": {
          "offset": 601,
          "line": 26,
          "column": 3
        },
        "milestones": [
          {
            "ordinal": 2,
            "id": "8e1d6b2a-4f7c-4a3e-b9d1-6c2e8f4a7b15"
          }
        ],
        "progress": "in progress"
      }
    ],
    "diagnostics": []
  },
  "diagnostics": []
}
  • AC-01done
  • AC-02in progress
  • AC-03in progress
Mutation and check results
updatePlan(directory, patch) · complete result
{
  "directory": "jg-plans/search-feedback",
  "document": "plan",
  "changed": true,
  "sha256": "98f8d72a31336f3c7f0f98b709f8789d300f9047eb10195beabcbb52d57af116",
  "diagnostics": [],
  "assignedMilestoneIds": []
}
writePlanDocument(directory, input) · complete result
{
  "directory": "jg-plans/search-feedback",
  "document": "milestones",
  "changed": true,
  "sha256": "46d4f0bd33d023830f4b9fc772cdd817b46d239c23166c3016fd0d08294f307a",
  "diagnostics": [],
  "assignedMilestoneIds": []
}
Consumer-derived summary
Consumer-derived summary · not an API return
{
  "revision": 1,
  "status": "in progress",
  "milestones": [
    {
      "id": "3c7a2f1e-9b4d-4e8a-a6f2-1d5c7e9b0a34",
      "title": "Explain empty results",
      "status": "done"
    },
    {
      "id": "8e1d6b2a-4f7c-4a3e-b9d1-6c2e8f4a7b15",
      "title": "Offer a next action",
      "status": "in progress"
    }
  ],
  "criteria": [
    {
      "id": "AC-01",
      "milestones": [
        1
      ],
      "progress": "done"
    },
    {
      "id": "AC-02",
      "milestones": [
        2
      ],
      "progress": "in progress"
    },
    {
      "id": "AC-03",
      "milestones": [
        2
      ],
      "progress": "in progress"
    }
  ],
  "binding": "current"
}
Public API call · excerpt
Selected API call · inputs from this example
if (observed.plan.state === 'readable') {
  await updatePlan(directory, {
    document: 'plan',
    expectedSha256: observed.plan.sha256,
    set: { status: 'in progress', updated_at },
  });
}
// Add the explicitly prepared milestone source as a separate guarded write.
await writePlanDocument(directory, {
  document: 'milestones',
  expectedSha256: null,
  source: milestoneSource,
});
const current = await inspectPlan(directory);

Revise

Change the intent. Keep the context.

A new criterion stays flagged by check until a milestone delivers it; existing IDs stay byte-for-byte.

Before · revision 1After · revision 2
---id: 1792612920_search-feedback-criteriacreated_at: 2026-10-21T20:02:00Zupdated_at: "2026-10-21T20:04:00Z"Removed line: revision: 1Added line: revision: 2status: "in progress"protocol: "0.2"priority: mediumrepositories:  - example/searcherror_codes:  - repository: example/search    registry: docs/errors.md    codes:      - 4100      - SEARCH_FILTER_INVALID---# Search feedback Explain empty search results and give people a useful next action. ## Acceptance criteria - AC-01: Empty search results explain why nothing matched.- AC-02: The empty state offers one next action, such as clearing the active filters.- AC-03: The explanation and the action work with a keyboard and a screen reader.Added line: - AC-04: Search suggests spelling corrections.

check · plan.md line 27

UNASSIGNED_CRITERIONAC-04

No milestone delivers AC-04 until the next breakdown.

Milestones · exact original bytes

  • Explain empty resultsdone

    3c7a2f1e-9b4d-4e8a-a6f2-1d5c7e9b0a34

  • Offer a next actionin progress

    8e1d6b2a-4f7c-4a3e-b9d1-6c2e8f4a7b15

Skills binding · stale after revision

Done markers stay; skills wait for a fresh breakdown before executing.

Actual revision observation
Complete revised plan.md
---
id: 1792612920_search-feedback-criteria
created_at: 2026-10-21T20:02:00Z
updated_at: "2026-10-21T20:04:00Z"
revision: 2
status: "in progress"
protocol: "0.2"
priority: medium
repositories:
  - example/search
error_codes:
  - repository: example/search
    registry: docs/errors.md
    codes:
      - 4100
      - SEARCH_FILTER_INVALID
---
# Search feedback

Explain empty search results and give people a useful next action.

## Acceptance criteria

- AC-01: Empty search results explain why nothing matched.
- AC-02: The empty state offers one next action, such as clearing the active filters.
- AC-03: The explanation and the action work with a keyboard and a screen reader.
- AC-04: Search suggests spelling corrections.

inspectPlan(directory) · complete result

JSON · complete result
{
  "directory": "jg-plans/search-feedback",
  "plan": {
    "state": "readable",
    "path": "jg-plans/search-feedback/plan.md",
    "sha256": "99cbfac6e98dcfe04052fef6a5031e586464f8be578d0a491691158b8c5beb4a",
    "value": {
      "name": "search-feedback-criteria",
      "source": "---\nid: 1792612920_search-feedback-criteria\ncreated_at: 2026-10-21T20:02:00Z\nupdated_at: \"2026-10-21T20:04:00Z\"\nrevision: 2\nstatus: \"in progress\"\nprotocol: \"0.2\"\npriority: medium\nrepositories:\n  - example/search\nerror_codes:\n  - repository: example/search\n    registry: docs/errors.md\n    codes:\n      - 4100\n      - SEARCH_FILTER_INVALID\n---\n# Search feedback\n\nExplain empty search results and give people a useful next action.\n\n## Acceptance criteria\n\n- AC-01: Empty search results explain why nothing matched.\n- AC-02: The empty state offers one next action, such as clearing the active filters.\n- AC-03: The explanation and the action work with a keyboard and a screen reader.\n- AC-04: Search suggests spelling corrections.\n",
      "body": "# Search feedback\n\nExplain empty search results and give people a useful next action.\n\n## Acceptance criteria\n\n- AC-01: Empty search results explain why nothing matched.\n- AC-02: The empty state offers one next action, such as clearing the active filters.\n- AC-03: The explanation and the action work with a keyboard and a screen reader.\n- AC-04: Search suggests spelling corrections.\n",
      "frontmatter": {
        "start": 0,
        "contentStart": 4,
        "contentEnd": 339,
        "end": 343,
        "closed": true
      },
      "rawMetadata": {
        "id": "1792612920_search-feedback-criteria",
        "created_at": "2026-10-21T20:02:00Z",
        "updated_at": "2026-10-21T20:04:00Z",
        "revision": 2,
        "status": "in progress",
        "protocol": "0.2",
        "priority": "medium",
        "repositories": [
          "example/search"
        ],
        "error_codes": [
          {
            "repository": "example/search",
            "registry": "docs/errors.md",
            "codes": [
              4100,
              "SEARCH_FILTER_INVALID"
            ]
          }
        ]
      },
      "metadata": {
        "id": "1792612920_search-feedback-criteria",
        "created_at": "2026-10-21T20:02:00Z",
        "updated_at": "2026-10-21T20:04:00Z",
        "revision": 2,
        "status": "in progress",
        "protocol": "0.2",
        "priority": "medium",
        "repositories": [
          "example/search"
        ],
        "error_codes": [
          {
            "repository": "example/search",
            "registry": "docs/errors.md",
            "codes": [
              4100,
              "SEARCH_FILTER_INVALID"
            ]
          }
        ]
      },
      "preamble": {
        "start": 0,
        "end": 430
      },
      "sections": [
        {
          "title": "Acceptance criteria",
          "heading": {
            "start": 430,
            "end": 453
          },
          "body": {
            "start": 453,
            "end": 728
          }
        }
      ],
      "protocol": "0.2",
      "criteria": [
        {
          "id": "AC-01",
          "idText": "AC-01",
          "text": "Empty search results explain why nothing matched.",
          "location": {
            "offset": 456,
            "line": 24,
            "column": 3
          },
          "range": {
            "start": 454,
            "end": 512
          }
        },
        {
          "id": "AC-02",
          "idText": "AC-02",
          "text": "The empty state offers one next action, such as clearing the active filters.",
          "location": {
            "offset": 515,
            "line": 25,
            "column": 3
          },
          "range": {
            "start": 513,
            "end": 598
          }
        },
        {
          "id": "AC-03",
          "idText": "AC-03",
          "text": "The explanation and the action work with a keyboard and a screen reader.",
          "location": {
            "offset": 601,
            "line": 26,
            "column": 3
          },
          "range": {
            "start": 599,
            "end": 680
          }
        },
        {
          "id": "AC-04",
          "idText": "AC-04",
          "text": "Search suggests spelling corrections.",
          "location": {
            "offset": 683,
            "line": 27,
            "column": 3
          },
          "range": {
            "start": 681,
            "end": 727
          }
        }
      ],
      "diagnostics": []
    }
  },
  "milestones": {
    "state": "readable",
    "path": "jg-plans/search-feedback/milestones.md",
    "sha256": "46d4f0bd33d023830f4b9fc772cdd817b46d239c23166c3016fd0d08294f307a",
    "value": {
      "source": "---\njg_skills:\n  plan_id: \"1792612920_search-feedback-criteria\"\n  plan_revision: 1\n  plan_content_sha256: 21da41286dc3d021cdc6adddef1283d7affe70b92832824bd00244ff1b9058d8\n---\n\n## Milestone 1: Explain empty results\n**Status:** done\n**Milestone ID:** 3c7a2f1e-9b4d-4e8a-a6f2-1d5c7e9b0a34\n**Acceptance criteria:** AC-01\n\nAdd the explanation to the empty results view. Review it in both themes.\n\n## Milestone 2: Offer a next action\n**Status:** in progress\n**Milestone ID:** 8e1d6b2a-4f7c-4a3e-b9d1-6c2e8f4a7b15\n**Acceptance criteria:** AC-02, AC-03\n\nAdd the clear-filters action, then check it with a keyboard and a screen reader.\n",
      "body": "\n## Milestone 1: Explain empty results\n**Status:** done\n**Milestone ID:** 3c7a2f1e-9b4d-4e8a-a6f2-1d5c7e9b0a34\n**Acceptance criteria:** AC-01\n\nAdd the explanation to the empty results view. Review it in both themes.\n\n## Milestone 2: Offer a next action\n**Status:** in progress\n**Milestone ID:** 8e1d6b2a-4f7c-4a3e-b9d1-6c2e8f4a7b15\n**Acceptance criteria:** AC-02, AC-03\n\nAdd the clear-filters action, then check it with a keyboard and a screen reader.\n",
      "frontmatter": {
        "start": 0,
        "contentStart": 4,
        "contentEnd": 171,
        "end": 175,
        "closed": true
      },
      "rawMetadata": {
        "jg_skills": {
          "plan_id": "1792612920_search-feedback-criteria",
          "plan_revision": 1,
          "plan_content_sha256": "21da41286dc3d021cdc6adddef1283d7affe70b92832824bd00244ff1b9058d8"
        }
      },
      "preamble": {
        "start": 0,
        "end": 176
      },
      "sections": [
        {
          "title": "Milestone 1: Explain empty results",
          "heading": {
            "start": 176,
            "end": 214
          },
          "body": {
            "start": 214,
            "end": 392
          }
        },
        {
          "title": "Milestone 2: Offer a next action",
          "heading": {
            "start": 392,
            "end": 428
          },
          "body": {
            "start": 428,
            "end": 627
          }
        }
      ],
      "overviewTables": [],
      "milestones": [
        {
          "ordinal": 1,
          "ordinalText": "1",
          "title": "Explain empty results",
          "status": "done",
          "id": "3c7a2f1e-9b4d-4e8a-a6f2-1d5c7e9b0a34",
          "location": {
            "offset": 176,
            "line": 8,
            "column": 1
          },
          "headingEnd": 214,
          "end": 392,
          "body": "**Status:** done\n**Milestone ID:** 3c7a2f1e-9b4d-4e8a-a6f2-1d5c7e9b0a34\n**Acceptance criteria:** AC-01\n\nAdd the explanation to the empty results view. Review it in both themes.\n\n",
          "statusMarkers": [
            {
              "value": "done",
              "location": {
                "offset": 214,
                "line": 9,
                "column": 1
              },
              "valueStart": 226,
              "valueEnd": 230,
              "lineStart": 214,
              "lineEnd": 231
            }
          ],
          "idMarkers": [
            {
              "value": "3c7a2f1e-9b4d-4e8a-a6f2-1d5c7e9b0a34",
              "location": {
                "offset": 231,
                "line": 10,
                "column": 1
              },
              "valueStart": 249,
              "valueEnd": 285,
              "lineStart": 231,
              "lineEnd": 286
            }
          ],
          "criteria": [
            "AC-01"
          ],
          "criteriaMarkers": [
            {
              "value": "AC-01",
              "location": {
                "offset": 286,
                "line": 11,
                "column": 1
              },
              "valueStart": 311,
              "valueEnd": 316,
              "lineStart": 286,
              "lineEnd": 317
            }
          ]
        },
        {
          "ordinal": 2,
          "ordinalText": "2",
          "title": "Offer a next action",
          "status": "in progress",
          "id": "8e1d6b2a-4f7c-4a3e-b9d1-6c2e8f4a7b15",
          "location": {
            "offset": 392,
            "line": 15,
            "column": 1
          },
          "headingEnd": 428,
          "end": 627,
          "body": "**Status:** in progress\n**Milestone ID:** 8e1d6b2a-4f7c-4a3e-b9d1-6c2e8f4a7b15\n**Acceptance criteria:** AC-02, AC-03\n\nAdd the clear-filters action, then check it with a keyboard and a screen reader.\n",
          "statusMarkers": [
            {
              "value": "in progress",
              "location": {
                "offset": 428,
                "line": 16,
                "column": 1
              },
              "valueStart": 440,
              "valueEnd": 451,
              "lineStart": 428,
              "lineEnd": 452
            }
          ],
          "idMarkers": [
            {
              "value": "8e1d6b2a-4f7c-4a3e-b9d1-6c2e8f4a7b15",
              "location": {
                "offset": 452,
                "line": 17,
                "column": 1
              },
              "valueStart": 470,
              "valueEnd": 506,
              "lineStart": 452,
              "lineEnd": 507
            }
          ],
          "criteria": [
            "AC-02",
            "AC-03"
          ],
          "criteriaMarkers": [
            {
              "value": "AC-02, AC-03",
              "location": {
                "offset": 507,
                "line": 18,
                "column": 1
              },
              "valueStart": 532,
              "valueEnd": 544,
              "lineStart": 507,
              "lineEnd": 545
            }
          ]
        }
      ],
      "overview": [],
      "overviewPresent": false,
      "diagnostics": []
    }
  },
  "criteria": {
    "protocol": "0.2",
    "criteria": [
      {
        "id": "AC-01",
        "text": "Empty search results explain why nothing matched.",
        "location": {
          "offset": 456,
          "line": 24,
          "column": 3
        },
        "milestones": [
          {
            "ordinal": 1,
            "id": "3c7a2f1e-9b4d-4e8a-a6f2-1d5c7e9b0a34"
          }
        ],
        "progress": "done"
      },
      {
        "id": "AC-02",
        "text": "The empty state offers one next action, such as clearing the active filters.",
        "location": {
          "offset": 515,
          "line": 25,
          "column": 3
        },
        "milestones": [
          {
            "ordinal": 2,
            "id": "8e1d6b2a-4f7c-4a3e-b9d1-6c2e8f4a7b15"
          }
        ],
        "progress": "in progress"
      },
      {
        "id": "AC-03",
        "text": "The explanation and the action work with a keyboard and a screen reader.",
        "location": {
          "offset": 601,
          "line": 26,
          "column": 3
        },
        "milestones": [
          {
            "ordinal": 2,
            "id": "8e1d6b2a-4f7c-4a3e-b9d1-6c2e8f4a7b15"
          }
        ],
        "progress": "in progress"
      },
      {
        "id": "AC-04",
        "text": "Search suggests spelling corrections.",
        "location": {
          "offset": 683,
          "line": 27,
          "column": 3
        },
        "milestones": [],
        "progress": null
      }
    ],
    "diagnostics": [
      {
        "document": "plan",
        "code": "UNASSIGNED_CRITERION",
        "message": "No milestone covers this criterion.",
        "severity": "error",
        "location": {
          "offset": 683,
          "line": 27,
          "column": 3
        }
      }
    ]
  },
  "diagnostics": [
    {
      "document": "plan",
      "code": "UNASSIGNED_CRITERION",
      "message": "No milestone covers this criterion.",
      "severity": "error",
      "location": {
        "offset": 683,
        "line": 27,
        "column": 3
      }
    }
  ]
}
Mutation and check results
writePlanDocument(directory, input) · complete result
{
  "directory": "jg-plans/search-feedback",
  "document": "plan",
  "changed": true,
  "sha256": "99cbfac6e98dcfe04052fef6a5031e586464f8be578d0a491691158b8c5beb4a",
  "diagnostics": [],
  "assignedMilestoneIds": []
}
checkPlan(documents) · complete result
{
  "conforms": false,
  "diagnostics": [
    {
      "document": "plan",
      "code": "UNASSIGNED_CRITERION",
      "message": "No milestone covers this criterion.",
      "severity": "error",
      "location": {
        "offset": 683,
        "line": 27,
        "column": 3
      }
    }
  ]
}
Consumer-derived summary
Consumer-derived summary · not an API return
{
  "revision": 2,
  "status": "in progress",
  "milestones": [
    {
      "id": "3c7a2f1e-9b4d-4e8a-a6f2-1d5c7e9b0a34",
      "title": "Explain empty results",
      "status": "done"
    },
    {
      "id": "8e1d6b2a-4f7c-4a3e-b9d1-6c2e8f4a7b15",
      "title": "Offer a next action",
      "status": "in progress"
    }
  ],
  "criteria": [
    {
      "id": "AC-01",
      "milestones": [
        1
      ],
      "progress": "done"
    },
    {
      "id": "AC-02",
      "milestones": [
        2
      ],
      "progress": "in progress"
    },
    {
      "id": "AC-03",
      "milestones": [
        2
      ],
      "progress": "in progress"
    },
    {
      "id": "AC-04",
      "milestones": [],
      "progress": null
    }
  ],
  "binding": "stale",
  "check": {
    "conforms": false,
    "findings": [
      {
        "code": "UNASSIGNED_CRITERION",
        "criterion": "AC-04",
        "line": 27
      }
    ]
  }
}
Public API call · excerpt
Selected API call · inputs from this example
if (current.plan.state === 'readable') {
  await writePlanDocument(directory, {
    document: 'plan',
    expectedSha256: current.plan.sha256,
    body: revisedBody,
    set: { revision: 2 },
  });
}
const revised = await inspectPlan(directory);
if (revised.plan.state === 'readable' && revised.milestones.state === 'readable') {
  const check = checkPlan({
    plan: revised.plan.value.source,
    milestones: revised.milestones.value.source,
  });
}

Complete reference-library results. Runtime directory and document-path fields are shown as jg-plans/search-feedback and its document paths. Source, body, metadata, diagnostics, IDs, and hashes are unchanged. Summaries, criterion progress, and the skills binding are consumer-derived views.

From a file to your product

Build the tool
you have in mind.

jg-plans reads the documents; your app decides what to show, from progress to agent context.

See the integration guide

Agent context

Give an agent the selected plan and the work still ahead.

Custom automation

Connect JSON observations to your own project rules.

YOUR DASHBOARD / EXAMPLE VIEW

Search feedback

in progress

Milestones

  1. 1Explain empty resultsdone
  2. 2Offer a next actionin progress

Acceptance criteria

  • AC-01done

    Empty search results explain why nothing matched.

    Delivered by milestone 1

  • AC-02in progress

    The empty state offers one next action, such as clearing the active filters.

    Delivered by milestone 2

  • AC-03in progress

    The explanation and the action work with a keyboard and a screen reader.

    Delivered by milestone 2

Parsed from the published criteria example at build time.

Checked before work starts

Catch mistakes on their line.

check names every protocol mistake in a plan and its milestones.

4 findingsplan.md
  1. 1---
  2. 2id: 1792612980_export-reports-criteria
  3. 3created_at: 2026-10-21T20:03:00Z
  4. 4updated_at: 2026-10-21T20:03:00Z
  5. 5revision: 1
  6. 6status: blockedINVALID_STATUS
  7. 7protocol: "0.2"
  8. 8---
  9. 9
  10. 10# Export reports
  11. 11
  12. 12Let people download a report as CSV.
  13. 13
  14. 14## Acceptance criteria
  15. 15
  16. 16- AC-01: The export contains every visible column.
  17. 17- AC-01: The export keeps the current sort order.DUPLICATE_CRITERION
  18. 18- AC-2: The file name includes the report date.INVALID_CRITERION_ID
  19. 19- AC-03: Large exports finish without timing out.UNASSIGNED_CRITERION
1 findingmilestones.md
  1. 1## Milestone 1: Build the export
  2. 2**Status:** pending
  3. 3**Milestone ID:** 6f2b9c4d-1e7a-4d3b-8c5f-2a9e7d1b4c63
  4. 4**Acceptance criteria:** AC-01, AC-07UNKNOWN_CRITERION
  5. 5
  6. 6Generate the CSV from the visible table.

Actual check output for the published criteria-invalid example.

A public API. A local CLI.

Read it. Inspect it.
Edit it precisely.

  1. 1
    You read the plan
    Complete

    sha256 98f8d72a3133…

  2. 2
    Someone revises it
    Complete

    sha256 99cbfac6e98d…

  3. 3
    Your stale write is refused
    Error
    JG_PLANS_SOURCE_CHANGED
Verified package · 0.5.1

npm install jg-plans@0.5.1

Node.js 24.15.0+ · Protocol 0.2 is provisional.

your-application.ts
import { inspectPlan, updatePlan } from 'jg-plans';

const current = await inspectPlan('./coding-agent-planning/example');
if (current.plan.state === 'readable') {
  await updatePlan(current.directory, {
    document: 'plan',
    expectedSha256: current.plan.sha256,
    set: { status: 'in progress' },
  });
}

Three pieces. Clear responsibilities.

A format to share.
A workflow to make your own.

  1. 01 / DOCUMENT FORMAT

    your-project/

    plan.md

    milestones.md optional

    The protocol

    Shared fields, markers and criteria; ordinary Markdown for the rest.

    Explore the format →
  2. 02 / DOCUMENT OPERATIONS

    parseinspectcheckupdate

    The library & CLI

    Structured observations and guarded edits that keep unrelated text.

    Meet the tools →
  3. 03 / APPLICATION BEHAVIOR

    ApprovalsExecutionCoordinationRetention

    Your implementation

    Your project owns approvals, execution and every other decision.

    Build an integration →

As much structure as the work needs

Start small. Stay in control.

  • plan.md

    One file is enough.

    Identity, revision and status above ordinary Markdown.

  • + milestones.md

    Break it down when useful.

    Numbered milestones name the criteria they deliver.

  • edit · move · delete

    Your files remain yours.

    Custom fields survive edits, and deleted plans stay deleted.

A project's plan files
  • your-project
    • jg-plans
      • 1792612920_search-feedback-criteria
        • plan.md
        • milestones.md