dsh-policy-brief-draft — Policy brief element completeness and data-freshness check
dsh-policy-brief-draft reads one policy-brief outline — the document header plus one row per section — and checks that outline's own completeness and internal consistency: that each section states its point, that a point carries supporting evidence, that the evidence cites a source, that the data cutoff parses and is not later than the review date, that the figures fall inside the data-freshness window you configure, that a recommendation names who would implement it, that the header declares the brief's title and its recipient, and that no section number is repeated.
What it looks like

Real output from this plugin over its own PB-001 test fixture — not a mock-up. The rule pack ships no invented quotations, so a finding names both the clause it applied and the fact that the clause text was not obtained.
What it answers
| You ask | What it answers |
|---|---|
| One section is all material and no conclusion: the point column is empty. | PB-001 reports that section, because every section is expected to state its point. It checks only that the point cell is filled, so it does not judge whether the point is right or whether it says anything new. The rule is capped at warn because the clause number was not obtained. |
| We filled in the evidence column and the source column. What does the check actually verify? | Only that both cells are filled. PB-002 requires evidence for every point and PB-003 requires sourceRef beside it. PB-002 does not judge whether the evidence is sufficient, real, or actually supports the point; PB-003 cannot check that the source exists or that the figures match it, because the plugin reads the checklist and never the sources, so a fabricated citation passes. |
The data cutoff reads 2026-03-15 09:30, and one section carries a date later than today. |
Both 2026-03-15 and 2026-03-15 09:30 are read. PB-004 reports a dataAsOf that falls after the review date, meaning either the date was filled in wrongly or the section cites data that does not exist yet, and a value it cannot parse is reported on its own instead of being silently skipped. It does not judge whether the data itself is reliable. |
The freshness rule reports skipped. Does that mean the data is fine? |
No. PB-005 ships with maxDays: 0, which means unconfigured, so it reports itself in skipped rather than inventing a number; set maxDays (for example maxDays: 90) to switch it on. A hit then means only that the data is older than the window you set, never that it is unusable, and citing older data is sometimes right, so say why in the remark. The rule is info. |
| A section makes a recommendation but never says who would carry it out. | PB-006 reports that section: once the recommendation cell is filled, the implementer cell is required. A section that only analyses the current situation and makes no recommendation is not reported, so the rule does not nag status-only sections. It checks that the implementer is named, not whether the recommendation is feasible or that body the right one. |
| What must the brief itself declare, and may the same section number appear twice? | PB-007 requires the header to declare both title and recipient, and further header fields such as classification or topic can be added to its fields if your own form records them. PB-008 reports a sectionNo that appears twice in the table, ignoring whitespace, because a repeated number stops a reviewer from pointing at one point unambiguously. PB-007 checks only that those header cells are filled, not that the title is apt or the recipient the right body, and PB-008 compares the numbers alone. |
Standards it follows
| Document | Number | Cited by rules |
|---|---|---|
| 《党政机关公文处理工作条例》 | 中办发〔2012〕14号(本次未取得条文) | PB-001, PB-002, PB-003, PB-004, PB-005, PB-006, PB-007, PB-008 |
Boundary: this plugin checks a 政策专报要素核对表 for what a brief can be held to mechanically — that each section states its point, that a point has supporting evidence, that the evidence cites a source, that the data date parses and is not in the future, that the figures are fresh enough under your window, that a recommendation names who would implement it, that the brief names its title and recipient, and that section numbers are unique. It does not decide whether a point is right, whether the evidence is sufficient, whether a recommendation is feasible, whether the data is reliable, or whether the brief should be submitted. Those are judgements about research quality and decision value.
⚠️ What this plugin can and cannot see
It reads a checklist, not the sources or the data. So it can only check that a source is cited — never that the source exists or that the figures match it.
PB-003says so in its own note, and the README's troubleshooting section repeats it: a fabricated citation will pass, because verifying sources is a different job.《党政机关公文处理工作条例》(中办发〔2012〕14号) was obtained and read verbatim, and
rules/evidence/clause-verification.mdrecords which articles were quoted — article 8(10) (a report serves to brief a superior body), article 19 (drafting: 「分析问题实事求是」「所提政策措施和办法切实可行」「观点鲜明」) and article 20(4), which lists 「引文等是否准确」 as a review point before issuance.The rule
excerptfields still say "本次未取得", and every rule remainswarnorinfo. The regulation governs how a document is drafted and issued; this plugin checks whether a brief's outline records its points, evidence, sources and recommendations. Calling a blank column adirectbreach of 「观点鲜明」 would dress a register gap up as a regulatory one — the over-claim this family exists to avoid. Half the rules rest on your own editorial conventions in any case.The data-freshness window ships unset. How stale is too stale is your editorial rule — a quick brief may demand last month's figures while an annual analysis legitimately cites the year's data — so
PB-005'smaxDaysstarts at0and the rule reports itself inskippedrather than inventing a number. A finding there means "older than the window you set", never "unusable"; citing older data is sometimes right, and the fix is to say why in the remark.
Compatibility
| Surface | Status |
|---|---|
| Harness | Peer range >=0.1.2-rc.1 <0.2.0 || >=0.2.0-0 <0.3.0 — verified to accept both 0.2.0-rc.2 and 0.2.1-alpha.1. engines.dsh is deliberately not declared: it has no reader and cannot reject a host |
| Node | `^22.19.0 |
| Platforms | All (plain ESM; no native code, no network, no model call) |
| Tool mode | Works in native, ptc and both; for a long brief use ptc |
What it does
Registers the policy_brief_draft tool. It reads one brief outline — the document header plus one row per
section — applies a versioned rule pack, and returns a report.
| Rule | Check | Severity | Basis kind |
|---|---|---|---|
PB-001 |
each section states its point | warn | principle |
PB-002 |
each point has supporting evidence | warn | principle |
PB-003 |
the evidence cites a source | warn | principle |
PB-004 |
the data date parses and is not in the future | warn | principle |
PB-005 |
the data is fresh enough under your window (off by default) | info | local |
PB-006 |
a recommendation names its implementer | warn | principle |
PB-007 |
the brief names its title and recipient | warn | principle |
PB-008 |
section numbers are unique | warn | principle |
Install
dsh plugin --profile <name> add dsh-policy-brief-draft
dsh --profile <name> --dump-config | grep 'dsh-policy-brief-draft'
Configuration
| Key | Type | Default | Description |
|---|---|---|---|
rulesFile |
string | rules/policy-brief-draft.yaml |
Rule-pack path, relative to the package root |
disabledRules |
string[] | [] |
Rule ids to stop running; each appears in skipped |
onlyRules |
string[] | [] |
Run only these rule ids; empty runs every rule |
skipNotes |
string | "" |
Note appended to every skipped reason |
timeoutMs |
number | 120000 |
Cooperative tool timeout budget |
Rule-level parameters worth knowing:
PB-005maxDays— your freshness window in days.0means the rule does not run.PB-006conditionField/requiredFields— what triggers the implementer requirement; the recommendation column by default. Sections that analyse without recommending are not asked for one.PB-007fields— the header fields that must be present; title and recipient by default. Addclassificationif your briefs carry a security marking.
Material format
The tool accepts JSON or YAML:
title: 某某领域投资形势分析专报
topic: 投资形势
recipient: 某某部门
draftedAt: 2026-03-12
classification: 内部
rows:
- { 序号: '1', 章节: 一、现状,
核心观点: 本领域投资增速连续三个季度回落,结构性分化明显,
支撑依据: 全国固定资产投资统计月报(2026 年 2 月)显示本领域投资同比增长 3.1%,
出处: 国家统计局月度数据,2026 年 3 月发布, 数据截止日期: 2026-02-28,
风险提示: 月度数据存在季节因素,需结合同比口径判断 }
Column names are matched case-insensitively and ignoring spaces, underscores and hyphens; the outline's own
column names are kept, so a finding names the column it read. Dates may be 2026-02-28 or
2026-02-28 09:30.
Rule sources
Rule data lives in rules/policy-brief-draft.yaml. The pack's header states the citation gap in full, and each
rule's note repeats the part that matters for that rule. The load-time guard that normally enforces "an
excerpt must be a real quotation of at least eight characters" cannot tell a quotation from a description —
so this pack leans on the header, the per-rule notes and a test that asserts every excerpt admits the gap.
Troubleshooting
PB-003passes a source I know is invented. It cannot check that: it verifies a source is cited, not that it exists or says what the brief claims.PB-005reports itself as skipped. No freshness window is set. How stale is too stale is your editorial rule, and the plugin will not invent one.PB-005fires on data I deliberately cited. Older data is sometimes the right data. Either widenmaxDaysor record the reason in the remark — the finding is about the window, not about the data.PB-006does not fire on an analysis section. That is deliberate: the implementer is required only when a recommendation is actually made.PB-004fires on a date in the future. Either the data date or the drafting date is wrong; a brief cannot rest on figures that do not exist yet.- The plugin installs but the tool never appears. Check that
mainresolves tolib/index.mjsand thatpnpm run buildproduced it; a wrongmainmakes the loader skip the entry silently. dsh plugin addrefuses the package as incompatible. The peer range covers0.1.xand0.2.x; if your runtime sits outside it, grant an explicit exemption:dsh plugin --profile <name> allow-version dsh-policy-brief-draft@0.1.0 --dsh-version <runtime> --accept-riskcheckreportsmanifest-peersas failed. The static checker compares against a hard-coded peer range that predates the 0.2 line. The runtime enforces peer compatibility at install time, so the declared range is the correct one; this is a known upstream issue indsh-plugin-dev.
Development
pnpm install
pnpm run typecheck # tsc --noEmit
pnpm test # vitest, the shared table-plugin suite plus paired fixtures
pnpm run build # tsdown -> lib/index.mjs + lib/index.d.mts
node ../scripts/sync-shared.mjs dsh-policy-brief-draft # refresh src/shared from ../_shared
The plugin is data-only: src/model.ts declares the table shape, the shared kit supplies the reader and
the check engine, and the rule pack declares every check.
License
Apache License 2.0 © 2026 dsh-policy-brief-draft contributors.
No comments yet. Be the first to write one.