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 +9 -7
- package/README.zh-CN.md +10 -8
- package/package.json +1 -1
- package/packages/cli/bin/contract.js +24 -21
- package/packages/cli/src/watch.js +6 -6
- package/packages/core/src/clean.js +1 -1
- package/packages/core/src/rules.js +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
English · [简体中文](README.zh-CN.md)
|
|
9
9
|
|
|
10
|
-

|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-

|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
45
|
+
prompt-contract "帮我写一封请假邮件"
|
|
44
46
|
|
|
45
47
|
export CONTRACT_PROVIDER=ollama CONTRACT_MODEL=qwen3:4b # 全本地;keep_alive 把模型钉在内存
|
|
46
|
-
prompt-
|
|
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-
|
|
72
|
-
- **常驻模式**:`prompt-
|
|
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
|
@@ -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 {
|
|
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.
|
|
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-
|
|
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-
|
|
37
|
-
prompt-
|
|
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-
|
|
40
|
-
prompt-
|
|
41
|
-
prompt-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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"):
|