prompt-contract 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  English · [简体中文](README.zh-CN.md)
9
9
 
10
- ![PromptContract — a vague one-line request becomes a structured task spec (goal, scope, acceptance criteria, non-goals), verified by six hard rules via `prompt-prompt-contract check`. Real-LLM latency P50 2.3s; engine adds 0.005ms. CLI · MCP · Playground — zero dependencies, BYOK, offline-capable.](docs/assets/hero-cover.png)
10
+ ![PromptContract — a vague one-line request becomes a structured task spec (goal, scope, acceptance criteria, non-goals), verified by six hard rules via `prompt-contract check`. Real-LLM latency P50 2.3s; engine adds 0.005ms. CLI · MCP · Playground — zero dependencies, BYOK, offline-capable.](docs/assets/hero-cover.png)
11
11
 
12
12
  **PromptContract is a deterministic prompt-contract layer for AI coding agents.** It compiles a vague one-line request into a structured task specification — goal, scope, constraints, acceptance criteria — and verifies the result against six hard rules before you ever see it.
13
13
 
@@ -16,7 +16,7 @@ It is *not* a smarter brain: your model does the thinking, PromptContract makes
16
16
  ## Why PromptContract
17
17
 
18
18
  - **Stable agent inputs** — a profile plus hard constraints turn "a website for my dog" into goal / scope / acceptance criteria / explicit non-goals. Reduced retries and scope drift are hypotheses measured by the task-level harness, not current product claims.
19
- - **Six deterministic guardrails** — language consistency, enhanced-text-only, length & completeness, expand-don't-answer, no hallucinated tech. Every enhancement can be asserted with `prompt-prompt-contract check`; the same spec drives templates and tests.
19
+ - **Six deterministic guardrails** — language consistency, enhanced-text-only, length & completeness, expand-don't-answer, no hallucinated tech. Every enhancement can be asserted with `prompt-contract check`; the same spec drives templates and tests.
20
20
  - **One engine, three surfaces** — a CLI, an MCP server (agent-invoked **tool** + user-invoked **slash prompts**), and a browser playground. All share one zero-dependency core.
21
21
  - **Private by architecture** — bring your own key, no server in the middle, no telemetry, offline-capable via Ollama.
22
22
 
@@ -30,21 +30,23 @@ node packages/cli/bin/contract.js "帮我做一个展示我家狗的网站" \
30
30
  node packages/playground/serve.js # → http://127.0.0.1:8123/ (or ?demo=1 for the self-running demo)
31
31
  ```
32
32
 
33
- Install: `npm i -g prompt-contract` — or zero-install: `npx prompt-prompt-prompt-contract "your vague idea"`.
33
+ Install: `npm i -g prompt-contract` — or zero-install: `npx prompt-contract "your vague idea"`.
34
+
35
+ Playground in your browser: **[qianjinguo.github.io/prompt-contract](https://qianjinguo.github.io/prompt-contract/)** (BYOK — the key stays in your browser; endpoint must allow CORS).
34
36
 
35
37
  ## Your real model
36
38
 
37
39
  ```bash
38
40
  export CONTRACT_API_KEY=sk-xxx CONTRACT_MODEL=gpt-4o-mini # any OpenAI-compatible endpoint (vLLM, OpenRouter, your gateway…)
39
- prompt-prompt-contract "A website for my dog"
41
+ prompt-contract "A website for my dog"
40
42
 
41
43
  export CONTRACT_PROVIDER=deepseek CONTRACT_API_KEY=sk-… # vendor presets carry the base URL + a suggested model
42
44
  export CONTRACT_PROVIDER=qwen # also: glm, moonshot, groq, openrouter, lmstudio (keyless)
43
45
  export CONTRACT_PROVIDER=anthropic CONTRACT_API_KEY=sk-ant-… # native Messages API — thinking deltas dropped in transport
44
- prompt-prompt-contract "帮我写一封请假邮件"
46
+ prompt-contract "帮我写一封请假邮件"
45
47
 
46
48
  export CONTRACT_PROVIDER=ollama CONTRACT_MODEL=qwen3:4b # fully local/offline; keep_alive pins the model in RAM
47
- prompt-prompt-prompt-contract "帮我写一封请假邮件"
49
+ prompt-contract "帮我写一封请假邮件"
48
50
  ```
49
51
 
50
52
  Or write `~/.prompt-contract/config.json` once: `{ "provider": "openai", "baseUrl": "…", "apiKey": "…", "model": "…" }`.
@@ -93,7 +95,7 @@ raw input → script/scenario detect → profile + hard constraints + strength (
93
95
  ## Status & roadmap — stated honestly
94
96
 
95
97
  - **Shipped:** engine, CLI (`prompt-contract` / `check` / `doctor` / `profiles` / `spike-0` / `watch`), MCP server (tool + zero-key prompts), playground, 3 profiles, eval cases, CI matrix.
96
- - **Resident mode:** `prompt-prompt-contract watch` — select text anywhere on macOS, press ⌥B, and the enhanced prompt replaces your selection: clipboard backed up and restored, focus re-validated before pasting, gated by `prompt-prompt-contract spike-0` evidence (decision D7). See [docs/WATCH.md](docs/WATCH.md). `prompt-prompt-contract spike-0` itself remains a dry-run diagnostic and never pastes; see [docs/SPIKE-0.md](docs/SPIKE-0.md).
98
+ - **Resident mode:** `prompt-contract watch` — select text anywhere on macOS, press ⌥B, and the enhanced prompt replaces your selection: clipboard backed up and restored, focus re-validated before pasting, gated by `prompt-contract spike-0` evidence (decision D7). See [docs/WATCH.md](docs/WATCH.md). `prompt-contract spike-0` itself remains a dry-run diagnostic and never pastes; see [docs/SPIKE-0.md](docs/SPIKE-0.md).
97
99
  - **Open validation:** task-level outcome evaluation remains an evidence-gathering task. The harness and curated fixture set exist, but PromptContract's downstream effectiveness is still a hypothesis until a declared runner produces reviewed results.
98
100
  - Deferred: animated demo asset, IDE plugins, LLM-as-judge as *one* scorer inside the task-level eval.
99
101
 
package/README.zh-CN.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  [English](README.md) · 简体中文
8
8
 
9
- ![PromptContract——模糊的一句话被编译成结构化任务规范(目标 / 范围 / 验收标准 / 明确不做的事),六条硬规则经 `prompt-prompt-contract check` 校验。真实 LLM 端到端 P50 2.3s,引擎附加开销 0.005ms。CLI · MCP · Playground——零依赖、BYOK、可离线。](docs/assets/hero-cover.png)
9
+ ![PromptContract——模糊的一句话被编译成结构化任务规范(目标 / 范围 / 验收标准 / 明确不做的事),六条硬规则经 `prompt-contract check` 校验。真实 LLM 端到端 P50 2.3s,引擎附加开销 0.005ms。CLI · MCP · Playground——零依赖、BYOK、可离线。](docs/assets/hero-cover.png)
10
10
 
11
11
  **PromptContract 是面向 AI 编码 agent 的确定性 prompt 契约层**:把模糊的一句话编译成结构化任务规范(目标 / 范围 / 约束 / 验收标准),并在你看到结果之前用六条硬规则完成校验。
12
12
 
@@ -15,7 +15,7 @@
15
15
  ## 为什么
16
16
 
17
17
  - **稳定的 agent 输入**——profile + 硬约束把「帮我做个网站」编译成目标 / 范围 / 验收标准 / 明确不做的事;重试与范围漂移是否减少,仍须由任务级 harness 验证,当前不作产品结论
18
- - **六条确定性护栏**——语言一致性、只输出增强文本、长度与完整性、扩写而非回答、无幻觉技术栈;`prompt-prompt-contract check` 随时可断言,模板与测试共用同一份规格
18
+ - **六条确定性护栏**——语言一致性、只输出增强文本、长度与完整性、扩写而非回答、无幻觉技术栈;`prompt-contract check` 随时可断言,模板与测试共用同一份规格
19
19
  - **一个引擎、三个形态**——CLI、MCP server(agent 调用的 tool + 用户调用的斜杠 prompts)、浏览器 Playground,共享同一个零依赖内核
20
20
  - **隐私即架构**——BYOK、无中间服务、零遥测,Ollama 全本地可用
21
21
 
@@ -29,21 +29,23 @@ node packages/cli/bin/contract.js "帮我做一个展示我家狗的网站" \
29
29
  node packages/playground/serve.js # → http://127.0.0.1:8123/(?demo=1 为自运行演示)
30
30
  ```
31
31
 
32
- 安装:`npm i -g prompt-contract`;或零安装体验:`npx prompt-prompt-prompt-contract "你的模糊想法"`。
32
+ 安装:`npm i -g prompt-contract`;或零安装体验:`npx prompt-contract "你的模糊想法"`。
33
+
34
+ 在线 Playground:**[qianjinguo.github.io/prompt-contract](https://qianjinguo.github.io/prompt-contract/)**(BYOK——key 只留在你的浏览器本地;端点需允许 CORS)。
33
35
 
34
36
  ## 接入真实模型
35
37
 
36
38
  ```bash
37
39
  export CONTRACT_API_KEY=sk-xxx CONTRACT_MODEL=gpt-4o-mini # 任意 OpenAI 兼容端点(vLLM、OpenRouter、自家网关…)
38
- prompt-prompt-contract "A website for my dog"
40
+ prompt-contract "A website for my dog"
39
41
 
40
42
  export CONTRACT_PROVIDER=deepseek CONTRACT_API_KEY=sk-… # 厂商预设:自带 base URL 与建议默认模型
41
43
  export CONTRACT_PROVIDER=qwen # 另有 glm、moonshot、groq、openrouter、lmstudio(免 key)
42
44
  export CONTRACT_PROVIDER=anthropic CONTRACT_API_KEY=sk-ant-… # Anthropic 原生 Messages API——思考增量在传输层即被丢弃
43
- prompt-prompt-contract "帮我写一封请假邮件"
45
+ prompt-contract "帮我写一封请假邮件"
44
46
 
45
47
  export CONTRACT_PROVIDER=ollama CONTRACT_MODEL=qwen3:4b # 全本地;keep_alive 把模型钉在内存
46
- prompt-prompt-prompt-contract "帮我写一封请假邮件"
48
+ prompt-contract "帮我写一封请假邮件"
47
49
  ```
48
50
 
49
51
  或一次写入 `~/.prompt-contract/config.json`。
@@ -68,8 +70,8 @@ npm run bench # 引擎自身开销 P50 ≈ 0.005ms(预算 <5ms)
68
70
 
69
71
  ## 状态与路线图(诚实版)
70
72
 
71
- - **已交付**:引擎、CLI(含 `prompt-prompt-contract spike-0`、`prompt-prompt-contract watch` 常驻模式)、MCP server(tool + 零 key prompts)、Playground、3 个 profiles、eval 用例、CI 矩阵
72
- - **常驻模式**:`prompt-prompt-contract watch`——在 macOS 任意应用选中一段粗糙 prompt,按 ⌥B,增强结果原地替换选区:剪贴板先备份后恢复、回贴前焦点复验(漂移即放弃)、以 `prompt-prompt-contract spike-0` 证据为启动门控(决策 D7);详见 [docs/WATCH.md](docs/WATCH.md)。`prompt-prompt-contract spike-0` 本身仍是 dry-run 诊断,永不发送粘贴;见 [docs/SPIKE-0.md](docs/SPIKE-0.md)
73
+ - **已交付**:引擎、CLI(含 `prompt-contract spike-0`、`prompt-contract watch` 常驻模式)、MCP server(tool + 零 key prompts)、Playground、3 个 profiles、eval 用例、CI 矩阵
74
+ - **常驻模式**:`prompt-contract watch`——在 macOS 任意应用选中一段粗糙 prompt,按 ⌥B,增强结果原地替换选区:剪贴板先备份后恢复、回贴前焦点复验(漂移即放弃)、以 `prompt-contract spike-0` 证据为启动门控(决策 D7);详见 [docs/WATCH.md](docs/WATCH.md)。`prompt-contract spike-0` 本身仍是 dry-run 诊断,永不发送粘贴;见 [docs/SPIKE-0.md](docs/SPIKE-0.md)
73
75
  - **开放验证**:任务级效果评测的 harness 与任务 fixture 已交付,但还没有声明 runner 产生并复核结果;长期价值在此之前仍是假设
74
76
  - **推迟**:动画 demo 资产、IDE 插件、LLM-as-judge(作为任务级评测中的评分器之一)
75
77
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prompt-contract",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -10,7 +10,7 @@ import { loadProfiles, loadProfile, resolveConfig } from '../../core/src/node.js
10
10
  import { createOpenAIProvider } from '../../providers/src/openai.js';
11
11
  import { createAnthropicProvider } from '../../providers/src/anthropic.js';
12
12
  import { createOllamaProvider } from '../../providers/src/ollama.js';
13
- import { readFileSync, existsSync, writeFileSync } from 'node:fs';
13
+ import { writeFileSync } from 'node:fs';
14
14
  import { basename } from 'node:path';
15
15
  import {
16
16
  createMacOSAdapter,
@@ -27,18 +27,18 @@ if (basename(process.argv[1] || '') === 'contract') {
27
27
  process.stderr.write('[deprecated] this CLI is now `prompt-contract` (prompt-contract); the `prompt-contract` command will be removed in a future release.\n');
28
28
  }
29
29
 
30
- const VERSION = '0.3.0';
30
+ const VERSION = '0.3.1';
31
31
  const USAGE = `prompt-contract — one-key prompt enhancement (PromptContract v${VERSION})
32
32
 
33
33
  Usage:
34
- prompt-prompt-prompt-contract "build me a website for my dog" enhance a prompt (prints enhanced text to stdout)
34
+ prompt-contract "build me a website for my dog" enhance a prompt (prints enhanced text to stdout)
35
35
  cat prompt.txt | prompt-contract enhance from stdin
36
- prompt-prompt-contract profiles list built-in profiles
37
- prompt-prompt-prompt-contract check --original "..." --enhanced "..."
36
+ prompt-contract profiles list built-in profiles
37
+ prompt-contract check --original "..." --enhanced "..."
38
38
  run the six hard-constraint rule assertions
39
- prompt-prompt-contract doctor verify config, provider reachability, profiles
40
- prompt-prompt-prompt-contract spike-0 macOS-only capture/restore compatibility diagnostic (dry-run)
41
- prompt-prompt-prompt-contract watch resident mode: select text → hotkey → enhanced text replaces it (macOS; docs/WATCH.md)
39
+ prompt-contract doctor verify config, provider reachability, profiles
40
+ prompt-contract spike-0 macOS-only capture/restore compatibility diagnostic (dry-run)
41
+ prompt-contract watch resident mode: select text → hotkey → enhanced text replaces it (macOS; docs/WATCH.md)
42
42
 
43
43
  Options:
44
44
  -p, --profile <name> scenario profile (default: coding-agent)
@@ -103,11 +103,14 @@ function makeProvider(cfg, { warm = false } = {}) {
103
103
  return provider;
104
104
  }
105
105
 
106
- function readStdin() {
107
- try {
108
- if (existsSync('/dev/stdin') && !process.stdin.isTTY) return readFileSync(0, 'utf8');
109
- } catch { /* fall through */ }
110
- return '';
106
+ // Read piped stdin via the stream API. readFileSync(0) is unsafe here: once
107
+ // Node has touched stdin the fd can be non-blocking, and a sync read then
108
+ // throws EAGAIN an intermittent failure seen on macOS runners.
109
+ async function readStdin() {
110
+ if (process.stdin.isTTY) return '';
111
+ const chunks = [];
112
+ for await (const chunk of process.stdin) chunks.push(chunk);
113
+ return Buffer.concat(chunks).toString('utf8');
111
114
  }
112
115
 
113
116
  function printRules(rules) {
@@ -120,7 +123,7 @@ function printRules(rules) {
120
123
 
121
124
  async function cmdEnhance(flags) {
122
125
  let text = flags._.join(' ');
123
- if (!text.trim() && !process.stdin.isTTY) text = readStdin();
126
+ if (!text.trim() && !process.stdin.isTTY) text = await readStdin();
124
127
  const profile = loadProfile(flags.profile || 'coding-agent');
125
128
  const cfg = resolveConfig(flags);
126
129
  const provider = makeProvider(cfg, { warm: true });
@@ -148,7 +151,7 @@ async function cmdEnhance(flags) {
148
151
  if (!flags.noStream) {
149
152
  process.stderr.write(`\n— ${res.meta.profile} · ${res.meta.model} · ${res.meta.ms}ms · ${res.meta.chars} chars\n`);
150
153
  if (!rules.pass) {
151
- process.stderr.write('rule assertions (advisory — run `prompt-prompt-prompt-contract check` for gate mode):\n');
154
+ process.stderr.write('rule assertions (advisory — run `prompt-contract check` for gate mode):\n');
152
155
  for (const r of rules.results.filter((r) => !r.pass)) {
153
156
  process.stderr.write(` [FAIL] ${r.title}${r.detail ? ` — ${r.detail}` : ''}\n`);
154
157
  }
@@ -168,12 +171,12 @@ function cmdProfiles(flags) {
168
171
  return 0;
169
172
  }
170
173
 
171
- function cmdCheck(flags) {
174
+ async function cmdCheck(flags) {
172
175
  let original = flags.original;
173
176
  let enhanced = flags.enhanced;
174
177
  if (!original && !enhanced && !process.stdin.isTTY) {
175
178
  // accept "original\tenhanced" or JSON line on stdin
176
- const line = readStdin().trim();
179
+ const line = (await readStdin()).trim();
177
180
  try {
178
181
  const j = JSON.parse(line);
179
182
  original = j.original; enhanced = j.enhanced;
@@ -183,7 +186,7 @@ function cmdCheck(flags) {
183
186
  }
184
187
  }
185
188
  if (original === undefined || enhanced === undefined) {
186
- process.stderr.write('prompt-prompt-contract check requires --original and --enhanced (or a JSON {original, enhanced} line on stdin)\n');
189
+ process.stderr.write('prompt-contract check requires --original and --enhanced (or a JSON {original, enhanced} line on stdin)\n');
187
190
  return 2;
188
191
  }
189
192
  const rules = checkRules(original, enhanced, { maxChars: flags.maxChars ? parseInt(flags.maxChars, 10) : 800 });
@@ -219,7 +222,7 @@ async function cmdDoctor(flags) {
219
222
 
220
223
  async function cmdSpike0(flags) {
221
224
  if (!isMacOS) {
222
- process.stderr.write('prompt-prompt-contract spike-0 is macOS-only: requires pbpaste, pbcopy, and Accessibility-backed System Events.\n');
225
+ process.stderr.write('prompt-contract spike-0 is macOS-only: requires pbpaste, pbcopy, and Accessibility-backed System Events.\n');
223
226
  return 2;
224
227
  }
225
228
 
@@ -231,7 +234,7 @@ async function cmdSpike0(flags) {
231
234
  const pauseMs = flags.pauseMs === undefined ? 0 : Number.parseInt(flags.pauseMs, 10);
232
235
  const setupDelayMs = flags.setupDelayMs === undefined ? 0 : Number.parseInt(flags.setupDelayMs, 10);
233
236
  if (!Number.isInteger(iterations) || iterations < 1 || !Number.isInteger(settleMs) || settleMs < 0 || !Number.isInteger(pauseMs) || pauseMs < 0 || !Number.isInteger(setupDelayMs) || setupDelayMs < 0) {
234
- process.stderr.write('prompt-prompt-contract spike-0 requires non-negative integer --settle-ms/--pause-ms/--setup-delay-ms and positive integer --iterations\n');
237
+ process.stderr.write('prompt-contract spike-0 requires non-negative integer --settle-ms/--pause-ms/--setup-delay-ms and positive integer --iterations\n');
235
238
  return 2;
236
239
  }
237
240
 
@@ -265,7 +268,7 @@ async function main() {
265
268
  case undefined:
266
269
  case 'boost': return await cmdEnhance(flags);
267
270
  case 'profiles': return cmdProfiles(flags);
268
- case 'check': return cmdCheck(flags);
271
+ case 'check': return await cmdCheck(flags);
269
272
  case 'doctor': return await cmdDoctor(flags);
270
273
  case 'spike-0': return await cmdSpike0(flags);
271
274
  case 'watch': return await cmdWatch(flags);
@@ -6,7 +6,7 @@
6
6
  * - The user's clipboard is snapshotted and restored around every capture and paste.
7
7
  * - Paste only fires when the foreground/focus identity still matches capture time;
8
8
  * otherwise the cycle aborts with a notification (fail closed).
9
- * - Evidence gate: `prompt-prompt-contract watch` requires a passing `prompt-prompt-contract spike-0` report (--report) or an
9
+ * - Evidence gate: `prompt-contract watch` requires a passing `prompt-contract spike-0` report (--report) or an
10
10
  * explicit --force, honoring decision D7. Spike-0 itself never unlocks anything.
11
11
  * - --dry-run exercises capture + enhance but never issues ⌘V.
12
12
  *
@@ -486,7 +486,7 @@ async function defaultNotify({ title, message }) {
486
486
  }
487
487
 
488
488
  /**
489
- * `prompt-prompt-contract watch` entry point. Deps are injectable for tests; flags come from contract.js parseArgs.
489
+ * `prompt-contract watch` entry point. Deps are injectable for tests; flags come from contract.js parseArgs.
490
490
  */
491
491
  export async function runWatch(flags = {}, deps = {}) {
492
492
  const {
@@ -513,7 +513,7 @@ export async function runWatch(flags = {}, deps = {}) {
513
513
  readFile,
514
514
  });
515
515
  if (!gate.ok) {
516
- log(`prompt-prompt-contract watch refused to start (decision D7 evidence gate): ${gate.reason}
516
+ log(`prompt-contract watch refused to start (decision D7 evidence gate): ${gate.reason}
517
517
  Watch pastes over your selection, so it runs only with measured evidence. Either:
518
518
  - run prompt-contract spike-0 --json --output ~/.cache/prompt-contract/spike-0.json first, then
519
519
  prompt-contract watch --report ~/.cache/prompt-contract/spike-0.json
@@ -526,7 +526,7 @@ Watch pastes over your selection, so it runs only with measured evidence. Either
526
526
  try {
527
527
  parseHotkey(hotkeySpec);
528
528
  } catch (err) {
529
- log(`prompt-prompt-contract watch: ${err.message}
529
+ log(`prompt-contract watch: ${err.message}
530
530
  Fix it with --hotkey <spec> or the "hotkey" field in ~/.prompt-contract/config.json (e.g. "hotkey": "ctrl+alt+b").`);
531
531
  return 2;
532
532
  }
@@ -534,7 +534,7 @@ Fix it with --hotkey <spec> or the "hotkey" field in ~/.prompt-contract/config.j
534
534
  const macAdapter = adapter ?? createMacOSAdapter();
535
535
  const probe = await probeCaptureSafety(macAdapter);
536
536
  if (!probe.ok) {
537
- log(`prompt-prompt-contract watch startup probe failed: ${probe.error}`);
537
+ log(`prompt-contract watch startup probe failed: ${probe.error}`);
538
538
  return 2;
539
539
  }
540
540
  for (const warning of probe.warnings) log(`watch: warning — ${warning}`);
@@ -599,7 +599,7 @@ Fix it with --hotkey <spec> or the "hotkey" field in ~/.prompt-contract/config.j
599
599
  const suffix = flags.dryRun ? ' · DRY-RUN (never pastes)' : '';
600
600
  const usingStdin = !source && flags.trigger === 'stdin';
601
601
  const triggerDesc = usingStdin ? label : `${label} [${hotkeySpec}]`;
602
- log(`prompt-prompt-contract watch resident — ${triggerDesc} enhances the selection · profile=${profileName} provider=${providerLabel}${suffix}
602
+ log(`prompt-contract watch resident — ${triggerDesc} enhances the selection · profile=${profileName} provider=${providerLabel}${suffix}
603
603
  Ctrl+C to quit. Paste replaces the selected text; the clipboard is restored afterwards. See docs/WATCH.md.`);
604
604
 
605
605
  await stopped;
@@ -14,7 +14,7 @@ const QUOTE_PAIRS = [
14
14
  /**
15
15
  * Strip reasoning-model blocks (<think>/<thinking>/<reasoning>/<thought>) so chain-of-thought
16
16
  * from models like DeepSeek-R1, Qwen3-thinking, or Hermes never reaches a user surface
17
- * (critical for prompt-prompt-contract watch: the cleaned text is pasted into the user's document).
17
+ * (critical for prompt-contract watch: the cleaned text is pasted into the user's document).
18
18
  * A reasoning tag opened but never closed is cut to end-of-text (truncated streams).
19
19
  */
20
20
  const REASONING_TAGS = ['think', 'thinking', 'reasoning', 'thought'];
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The six hard constraints from the PRD appendix, as deterministic rule assertions.
3
- * Same spec drives three surfaces: `prompt-prompt-prompt-contract check` (CLI), playground badges, eval runner —
3
+ * Same spec drives three surfaces: `prompt-contract check` (CLI), playground badges, eval runner —
4
4
  * template and evaluation share one source of truth (PRD: 模板与评测共用同一份规格).
5
5
  *
6
6
  * Known heuristic limits (do not oversell — see docs/ACCEPTANCE.md "Evidence boundaries"):