PerryLink/dsh-contract-stance

dsh-contract-stance

合同条款立场核对:依据《中华人民共和国民法典》。仅提示差异,不作出定性结论。

未分類contractcordisdeepseek

dsh-contract-stance — Contract clause stance register completeness and self-consistency check

English · 简体中文 · Español · Português · हिन्दी

dsh-contract-stance reads one clause-stance register — the contract header plus one row per clause — and checks that register's own completeness and self-consistency: that each clause's text is recorded, that your stance comes from your own vocabulary, that the risk grade does too, that a must-have clause records both a fallback position and an owner, that clause numbers are unique, that the register names the contract and your side, and that no unreplaced placeholder survives in the clause text.

Install

dsh plugin --profile <name> add dsh-contract-stance
dsh --profile <name> --dump-config | grep 'dsh-contract-stance'

What it looks like

*Workflow overview: call → plugin → result. The examples below use the real command surface.*

What it answers

Examples

The table below is the example set: the first row is the detailed walkthrough, the remaining rows are short cases.

Detailed output

Tool contract_stance

Input

~~~json {"rows":[{"条款号":"第 8.2 条","条款主题":"逾期付款违约金","条款原文":"", ...}]} ~~~

Output

~~~json { "id": "dsh-contract-stance.CS-001.de09b5a6", "ruleId": "CS-001", "severity": "warn", "locator": { "row": 1, "field": "text" }, "found": "第 1 行「text」为空", "expected": "「text」应填写", "fix": "补填条款原文;本条只核对是否摘录,不判断条款是否可接受" } ~~~

*Observed from dsh-contract-stance/tests/fixtures/CS-001/CS-001-unsafe.json; dsh-contract-stance/tests/fixtures/CS-001/cases.json; dsh-contract-stance/rules/contract-stance.yaml; dsh-contract-stance/tests/index.test.ts*

You ask · What it answers A clause row carries a topic, but the clause-text column is blank. Is that reported? · Yes. CS-001 requires the text column on every row: without the wording, a stance cannot be checked against what it is a stance on. The rule checks only that the wording is recorded — it does not judge whether the clause should be accepted or whether it carries legal risk. The stance and the risk grade are both filled in, yet every row comes back as skipped. Why? · Both value lists ship empty. CS-002 accepts a stance only from the list configured for it, and CS-003 does the same for the risk grade; with no list configured neither rule can run, so each reports itself in skipped rather than passing quietly. CS-002 checks only that the value is on your list, not that the stance is appropriate; CS-003 checks only that the grade is on your list, not how high the actual risk of that clause is. A clause is marked must-have, but nobody wrote how far we can concede or who decides. · CS-004 reads the register's own must-have column: for a row marked 是, Y, yes, true, 必保 or √ (the values set by conditionValues) it requires both the fallback and the owner column to be filled. It checks only that those two are filled, not whether the fallback is reasonable; which clauses count as must-have depends entirely on the project, and the rule never decides that. The same clause number appears on two rows, one per negotiating draft. Is that a finding? · CS-005 reports a repeated clause number, comparing with whitespace ignored. A repeated number makes it impossible to point at one clause precisely. One clause appearing once per draft is a normal shape: distinguish it in the version column instead of reusing the number. No clause requires clause numbers to be unique — uniqueness is what makes the register referable. The header does not say which contract this is, nor which side we are. · CS-006 requires the material's header to state contractName and party: without those two, a stance cannot be traced to a specific transaction and party. If your form carries a version column for negotiating drafts, add it to that rule's fields. The rule only checks that the header declares those two. The clause text was copied straight from a model form and still contains 【】 or TBD. · CS-007 reports clause text that still holds any of its placeholder terms — 【, 】, {{, }}, XXX, xxx, 待填, 待补充, TBD, todo, 示例 — which can be adjusted to your own template. Referring to a model form is allowed; the danger is that a template placeholder is read as wording already reviewed. The rule only looks for those terms in the text column, not at the clause itself.

Standards it follows

Document · Number · Cited by rules 《中华人民共和国民法典》 · 现行版本与条号本次未核实 · CS-001, CS-002, CS-003, CS-004, CS-005, CS-006, CS-007

Boundary: this plugin checks a 合同条款立场台账 for what a register can be held to — that each clause's text is recorded, that your stance comes from your vocabulary, that the risk grade does too, that a must-have clause records both a fallback position and an owner, that clause numbers are unique, that the register names the contract and your side, and that no placeholder survives. It does not decide whether a clause should be accepted, whether a risk is bearable, whether to sign, or whether a negotiating strategy is sound. Those depend on the deal, the counterparty's leverage and your own risk appetite.

### ⚠️ What the citation rests on — and one thing this plugin must never be given
《中华人民共和国民法典》was obtained and read verbatim, and rules/evidence/clause-verification.md
records what was quoted: article 465 (a contract lawfully formed is protected by law), article 470 (contract
content 一般包括 the parties, subject matter, quantity, quality, price, performance terms and liability for
breach), article 509, article 577 (the consequences of breach) and article 584 (damages include expected
profits but are capped by foreseeability).
The contract part does not regulate a negotiation register. It governs formation, validity, performance and
liability; the register's columns come from your enterprise's own contract management rules. So the excerpt
fields still say "本次未取得" and every rule stays warn or info: **"this column is blank" must not be read
as "this breaches the Civil Code". Note also that article 470 says 一般包括**, not 应当包括 — one more
reason CS-001 does not claim a direct obligation.
⚠️ Do not add a limitation-period rule to this plugin. The Code *does* fix numbers — article 188 sets a
three-year limitation (twenty years at the outside) and article 203 rolls a period forward off a public
holiday — which makes "just compare the dates" look tempting. It is not workable here: the period runs from
when the creditor knew or ought to have known of the harm and the debtor, a finding of fact the register
does not record; the period is also subject to suspension (article 194) and interruption (article 195), and
article 196 lists claims the limitation does not apply to at all. A date-arithmetic rule would dress a factual
finding up as a calculation, which is precisely the over-claim this family avoids.
No judgement about the substance of a clause is made anywhere in this plugin. It reads your own columns
and checks only that they are filled and drawn from your vocabulary. Two vocabularies ship empty — the
stance list (CS-002) and the risk grades (CS-003) — so with nothing configured those rules report
themselves in skipped rather than passing quietly.

Compatibility

Surface · Status Harness · Peer range >=0.1.2-rc.1 =0.2.0-0 =0.2.1-0 =24.0.0 Platforms · All (plain ESM; no native code, no network, no model call) Tool mode · Works in native and ptc; for a full contract use ptc

What it does

Registers the contract_stance tool. It reads one clause-position register — the contract header plus one row per clause — applies a versioned rule pack, and returns a report.

Rule · Check · Severity · Basis kind CS-001 · every clause's text is recorded · warn · principle CS-002 · the stance comes from your vocabulary (off by default) · info · principle CS-003 · the risk grade comes from your vocabulary (off by default) · info · direct CS-004 · a must-have clause records a fallback and an owner · warn · direct CS-005 · clause numbers are unique · warn · principle CS-006 · the register names the contract and your side · warn · direct CS-007 · the clause text holds no unreplaced placeholder · warn · principle

Configuration

Key · Type · Default · Description rulesFile · string · rules/contract-stance.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:

  • CS-002 values — your stance vocabulary, e.g. [坚持, 可谈, 接受, 须删除]. Empty means no check.
  • CS-003 values — your risk grades, e.g. [高, 中, 低]. Empty means no check.
  • CS-004 conditionValues — the values in your 是否必保 column that mark a clause must-have, by default

[是, Y, yes, true, 必保, √].

  • CS-007 terms — the placeholders to look for.

Material format

The tool accepts JSON or YAML:

contractNo: HT-2026-018
contractName: 某某设备采购合同
party: 某某公司
version: 第 3 轮谈判稿
rows:
  - { 条款号: 第 8.2 条, 条款主题: 逾期付款违约金,
      条款原文: 买方逾期付款的,每逾期一日按未付金额的万分之五向卖方支付违约金。,
      本方立场: 可谈, 风险等级: 中, 风险说明: 违约金比例高于本方惯例且未设上限,
      退让底线: 比例可降至万分之三,须增加总额不超过合同额 5% 的上限,
      是否必保: 是, 谈判责任人: 王经理 }

Column names are matched case-insensitively and ignoring spaces, underscores and hyphens; the register's own column names are kept, so a finding names the column it read.

Rule sources

Rule data lives in rules/contract-stance.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

  • `CS-002` or `CS-003` reports itself as skipped. Its vocabulary is empty. Stance and risk wording is your

institution's convention, and the plugin will not guess it.

  • `CS-004` fires on a clause marked must-have. It records no fallback or no owner. A must-have clause

without a stated floor cannot be negotiated on the spot.

  • `CS-001` fires although the clause is quoted elsewhere. The text column itself is empty. The check

reads the register, not the contract document.

  • `CS-005` fires on one clause twice. That is legitimate across negotiation rounds — distinguish the rows

in the version column rather than reusing the clause number.

  • The plugin installs but the tool never appears. Check that main resolves to lib/index.mjs and

that pnpm run build produced it; a wrong main makes the loader skip the entry silently.

  • `dsh plugin add` refuses the package as incompatible. The peer range covers 0.1.x and 0.2.x; if

your runtime sits outside it, grant an explicit exemption: dsh plugin --profile allow-version dsh-contract-stance@0.1.0 --dsh-version --accept-risk

  • `check` reports `manifest-peers` as 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 in dsh-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-contract-stance   # 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.

PerryLink DSH Plugin Family

This project is part of the DeepSeek Harness plugin family from PerryLink. The roster stays collapsed below so every README keeps a short first screen; expand it to see every row and its status.