universal-dev-standards 6.14.0-beta.3 → 6.14.0-beta.5

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.
Files changed (63) hide show
  1. package/bin/uds.js +7 -1
  2. package/bundled/ai/standards/ai-response-navigation.ai.yaml +43 -3
  3. package/bundled/ai/standards/checkin-standards.ai.yaml +25 -6
  4. package/bundled/ai/standards/full-coverage-testing.ai.yaml +46 -5
  5. package/bundled/ai/standards/pipeline-security-gates.ai.yaml +5 -1
  6. package/bundled/core/ai-response-navigation.md +128 -12
  7. package/bundled/core/full-coverage-testing.md +57 -3
  8. package/bundled/extensions/frameworks/fat-free-patterns.md +937 -0
  9. package/bundled/extensions/languages/csharp-style.md +464 -0
  10. package/bundled/extensions/languages/php-style.md +700 -0
  11. package/bundled/extensions/locales/zh-cn.md +717 -0
  12. package/bundled/extensions/locales/zh-tw.md +717 -0
  13. package/bundled/locales/COVERAGE.md +5 -4
  14. package/bundled/locales/zh-CN/CHANGELOG.md +54 -3
  15. package/bundled/locales/zh-CN/README.md +2 -2
  16. package/bundled/locales/zh-CN/SECURITY.md +1 -1
  17. package/bundled/locales/zh-CN/core/ai-response-navigation.md +110 -12
  18. package/bundled/locales/zh-CN/core/full-coverage-testing.md +61 -7
  19. package/bundled/locales/zh-CN/docs/CHEATSHEET.md +3 -1
  20. package/bundled/locales/zh-CN/docs/FEATURE-REFERENCE.md +8 -5
  21. package/bundled/locales/zh-CN/skills/README.md +1 -0
  22. package/bundled/locales/zh-CN/skills/comprehension-ladder/SKILL.md +289 -0
  23. package/bundled/locales/zh-CN/skills/comprehension-ladder/eval-cases.md +261 -0
  24. package/bundled/locales/zh-TW/CHANGELOG.md +54 -3
  25. package/bundled/locales/zh-TW/README.md +2 -2
  26. package/bundled/locales/zh-TW/SECURITY.md +1 -1
  27. package/bundled/locales/zh-TW/core/ai-response-navigation.md +110 -12
  28. package/bundled/locales/zh-TW/core/full-coverage-testing.md +61 -7
  29. package/bundled/locales/zh-TW/docs/CHEATSHEET.md +3 -1
  30. package/bundled/locales/zh-TW/docs/FEATURE-REFERENCE.md +8 -5
  31. package/bundled/locales/zh-TW/skills/README.md +1 -0
  32. package/bundled/locales/zh-TW/skills/comprehension-ladder/SKILL.md +289 -0
  33. package/bundled/locales/zh-TW/skills/comprehension-ladder/eval-cases.md +261 -0
  34. package/bundled/skills/README.md +1 -0
  35. package/bundled/skills/comprehension-ladder/SKILL.md +283 -0
  36. package/bundled/skills/comprehension-ladder/eval-cases.md +255 -0
  37. package/bundled/templates/gates/check-anti-fake-tests.mjs +991 -0
  38. package/bundled/templates/gates/check-stubs.mjs +644 -0
  39. package/package.json +3 -3
  40. package/src/commands/audit.js +11 -0
  41. package/src/commands/check.js +124 -24
  42. package/src/commands/init.js +45 -9
  43. package/src/commands/update.js +183 -21
  44. package/src/core/install-records.js +2 -1
  45. package/src/i18n/messages.js +50 -9
  46. package/src/installers/standards-installer.js +16 -23
  47. package/src/reconciler/backup-manager.js +418 -82
  48. package/src/reconciler/index.js +27 -5
  49. package/src/reconciler/install-roots.js +90 -0
  50. package/src/reconciler/plan-executor.js +33 -13
  51. package/src/uninstallers/hook-uninstaller.js +7 -4
  52. package/src/utils/command-hash-ownership.js +103 -0
  53. package/src/utils/copier.js +78 -1
  54. package/src/utils/gate-scripts.js +141 -0
  55. package/src/utils/git-hooks.js +8 -4
  56. package/src/utils/health-scorer.js +10 -7
  57. package/src/utils/locale.js +19 -0
  58. package/src/utils/skill-hash-ownership.js +64 -0
  59. package/src/utils/skills-installer.js +12 -1
  60. package/src/utils/test-change-check.js +160 -0
  61. package/src/utils/test-policy.js +214 -0
  62. package/src/utils/update-summary.js +29 -0
  63. package/standards-registry.json +21 -7
package/bin/uds.js CHANGED
@@ -106,9 +106,13 @@ program
106
106
  // Fallback to OS environment variable detection
107
107
  setLanguage(detectLanguage(null));
108
108
  })
109
- .hook('postAction', async (thisCommand) => {
109
+ .hook('postAction', async (thisCommand, actionCommand) => {
110
110
  const cmd = thisCommand.name();
111
111
  if (!shouldCheckUpdateForCommand(cmd)) return;
112
+ // XSPEC-454 R3: --offline promises no network. This hook runs after every command and
113
+ // asks the npm registry for the latest version; it ignored the flag, so `check --offline`
114
+ // and `audit --offline` still made that request (whenever the terminal was interactive).
115
+ if (actionCommand?.opts?.().offline) return;
112
116
  await printUpdateNoticeIfAvailable();
113
117
  });
114
118
 
@@ -253,6 +257,8 @@ program
253
257
  .option('--gh', 'Force gh CLI for submission')
254
258
  .option('--format <format>', 'Output format (json)')
255
259
  .option('--quiet', 'Summary only')
260
+ // XSPEC-454 R3: `check` and `update` already take --offline; `audit` rejected it with "unknown option".
261
+ .option('--offline', 'No network access at all: no CLI version check, and --report does not submit')
256
262
  .option('--score', 'Run multi-dimensional health score analysis')
257
263
  .option('--self', 'Self mode: analyze UDS repo itself (use with --score)')
258
264
  .option('--save', 'Save score snapshot for trend tracking (use with --score)')
@@ -3,14 +3,17 @@
3
3
 
4
4
  id: ai-response-navigation
5
5
  meta:
6
- version: "1.3.0"
7
- updated: "2026-08-17"
6
+ version: "1.4.0"
7
+ updated: "2026-10-05"
8
8
  source: core/ai-response-navigation.md
9
9
  description: >
10
10
  Every substantive AI response must include contextual next-step suggestions with recommended
11
11
  options (rules 1-6, required). Optional rules 7-11 govern the answer itself: lead with the
12
12
  finding, restate state across turns, no preamble, plain language as the subject, and a
13
- trade-off on every option rather than only the recommended one.
13
+ trade-off on every option rather than only the recommended one. Rule 12 is controlled
14
+ language: one required clause (a simplified text keeps the writer's hedges, never turning
15
+ "might" into "is") and optional language-neutral plain-writing principles. It ships no
16
+ English dictionary.
14
17
 
15
18
  rules:
16
19
  - id: navigation-footer
@@ -127,6 +130,8 @@ rules:
127
130
  Distinct from lead-with-the-finding: that rule orders finding before evidence, this one
128
131
  governs register. A response can lead with its finding and still state that finding in
129
132
  vocabulary only its author holds; both leave the reader unable to act.
133
+ Putting a claim in plain words is a rewrite, and a rewrite is where hedges get lost: when
134
+ the claim was hedged, keep-uncertainty-markers (rule 12.1, required) governs.
130
135
  priority: optional
131
136
 
132
137
  - id: every-option-carries-its-trade-off
@@ -143,6 +148,41 @@ rules:
143
148
  costlier to read, so the 1-5 cap matters more, not less.
144
149
  priority: optional
145
150
 
151
+ # ── Rule 12 (v1.4.0): controlled language, split into two entries ──
152
+ # One rule in the Markdown, two entries here because priority is one value per entry and the
153
+ # two halves are not the same strength. 12.1 is required: it is the only part whose failure makes
154
+ # the reader believe something untrue, and it needs no calibration (compare before and after).
155
+ # 12.2 is optional: its ranges are uncalibrated starting points with no checker, and a required
156
+ # rule with a threshold nobody can verify produces mechanical compliance.
157
+ # Takes the principles of ASD-STE100 and none of its English dictionary or tense rules.
158
+
159
+ - id: keep-uncertainty-markers
160
+ trigger: shortening, simplifying, rewriting or translating text for a reader who is not its author
161
+ instruction: >
162
+ Keep the writer's hedges (might, could, probably, appears to, not yet confirmed; 可能, 推斷,
163
+ 大概, 尚未確認). Do NOT turn an uncertain claim into a certain one, do NOT drop a hedge to save
164
+ words, and do NOT add a fact the source did not state (an invented cause, an invented
165
+ "confirmed"). A shorter sentence is the goal; a more certain one is not allowed. A hedge may go
166
+ only when the claim has since been verified, and then the verification (what was checked, with
167
+ what result) takes its place; deleting the hedge alone is not verification.
168
+ priority: required
169
+
170
+ - id: controlled-language
171
+ trigger: writing for a non-specialist reader who has to judge or approve something from the text
172
+ instruction: >
173
+ Apply language-neutral plain-writing principles, each in the unit the language itself uses and
174
+ with no word list: (1) short sentences, one idea each, counted in the language's own unit —
175
+ as a starting range not a limit, roughly 15-25 words in English and 25-40 characters in
176
+ Chinese, calibrated per language and reader; (2) one name per thing for the whole text;
177
+ (3) a clear subject and the active voice, the passive only when the actor is unknown or
178
+ irrelevant; (4) one step, one action, as a numbered list; (5) few semicolons; (6) numbers carry
179
+ units. Do NOT use ASD-STE100's approved dictionary or tense restrictions: they depend on English
180
+ and do not apply to Chinese or other non-English text. Do NOT gauge non-English text with a
181
+ counter that splits on whitespace or ASCII; it reads a Chinese paragraph as one word and always
182
+ passes. Optional because the ranges are uncalibrated and unchecked; keep-uncertainty-markers is
183
+ the part that does not bend.
184
+ priority: optional
185
+
146
186
  related_standards:
147
187
  - ai-command-behavior
148
188
  - ai-instruction-standards
@@ -12,7 +12,7 @@ standard:
12
12
  - "Documentation: docs updated, CHANGELOG updated"
13
13
 
14
14
  meta:
15
- version: "2.2.0"
15
+ version: "2.2.1"
16
16
  updated: "2026-07-09"
17
17
  source: core/checkin-standards.md
18
18
  description: Quality gates that must be passed before committing code
@@ -167,10 +167,29 @@ standard:
167
167
 
168
168
  physical_spec:
169
169
  type: custom_script
170
+ # Fail-closed (XSPEC-444 R1): a failing lint or test script fails this check.
171
+ # A script that is genuinely absent is reported (printed) and does not fail it —
172
+ # the hint is printed only for that case, never as a fallback for a failure.
173
+ # Inline Node keeps it portable (sh and cmd.exe) and lets it read package.json
174
+ # instead of guessing from exit codes. The npm-init placeholder test script
175
+ # ("no test specified") counts as absent. A package.json that exists but cannot
176
+ # be read fails the check. No package.json (not an npm project): hint, no failure.
170
177
  validator:
171
178
  command: >
172
- echo "🔍 UDS Check-in Gates:" &&
173
- (npm run lint --if-present || echo "ℹ️ No lint script") &&
174
- (npm test --if-present || echo "ℹ️ No test script") &&
175
- (test -f CHANGELOG.md || echo "ℹ️ No CHANGELOG.md")
176
- rule: "checkin_gates_passed"
179
+ node -e "
180
+ const fs=require('fs'),cp=require('child_process');
181
+ console.log('🔍 UDS Check-in Gates:');
182
+ let pkg;
183
+ try{pkg=JSON.parse(fs.readFileSync('package.json','utf8').replace(/^\uFEFF/,''))}
184
+ catch(e){if(e.code!=='ENOENT'){console.error('package.json cannot be read: '+e.message);process.exit(1)}}
185
+ if(pkg===undefined){console.log('ℹ️ No package.json - lint and tests were NOT run by this check (not an npm project)')}
186
+ else{const sc=(pkg&&pkg.scripts)||{};
187
+ for(const n of ['lint','test']){
188
+ const s=sc[n];
189
+ if(!s||(n==='test'&&/no test specified/.test(s))){console.log('ℹ️ No '+n+' script');continue}
190
+ const r=cp.spawnSync('npm run '+n,{stdio:'inherit',shell:true});
191
+ if(r.status!==0){console.error('FAILED: npm run '+n+' exited with '+r.status);process.exit(1)}
192
+ }}
193
+ if(!fs.existsSync('CHANGELOG.md'))console.log('ℹ️ No CHANGELOG.md');
194
+ "
195
+ rule: "checkin_gates_passed"
@@ -8,11 +8,11 @@ standard:
8
8
  description: Behavior-completeness full coverage paradigm replacing pyramid thresholds. Enforces anti-fake-test rules, STUB marker protocol, ratchet CI, and @ac traceability.
9
9
 
10
10
  meta:
11
- version: "1.1.0"
12
- updated: "2026-06-17"
11
+ version: "1.2.0"
12
+ updated: "2026-10-06"
13
13
  source: core/full-coverage-testing.md
14
14
  replaces: "testing pyramid thresholds (UT≥80%/IT≥70%/E2E happy-path-only)"
15
- xspec: "XSPEC-178"
15
+ xspec: "XSPEC-178, XSPEC-444"
16
16
  description: AI-era full coverage paradigm — cost of writing tests equals cost of writing code, so there is no reason to set lower thresholds for any test layer.
17
17
 
18
18
  rationale: |
@@ -180,6 +180,32 @@ standard:
180
180
  trigger: '"// WARNING: STUB" marker found in src/'
181
181
  message: "[STUB-WARN] STUB markers found. Must remove before merging to main."
182
182
 
183
+ # XSPEC-444 R2 + R5: what UDS itself ships so these rules are not only prose.
184
+ # Everything here WARNS by default; `mode: block` in the policy file tightens it.
185
+ shipped_gates:
186
+ default_mode: warn
187
+ policy_file: ".standards/test-policy.json" # optional; every list adds to the defaults
188
+ scripts: # written to scripts/ by `uds init`; never overwritten
189
+ - file: scripts/check-anti-fake-tests.mjs
190
+ finds: [no-assertion, tautology, all-skipped]
191
+ exit: "0 clean | 1 findings | 2 could not judge (never a pass)"
192
+ - file: scripts/check-stubs.mjs
193
+ finds: [stub-marker, not-implemented, empty-function]
194
+ exit: "0 clean | 1 findings | 2 could not judge (never a pass)"
195
+ pre_commit_checks: # run by `uds check` on what is staged
196
+ - id: code-without-test
197
+ finds: "code files changed and no test file changed in the same commit"
198
+ exempt: ["pure rename (git R100)", "policy exempt entry WITH a reason"]
199
+ - id: unclassified-file
200
+ finds: "a changed file of a type UDS does not recognise — listed, never assumed fine, never blocks"
201
+ language_coverage:
202
+ test_files: [javascript, typescript, python, java, kotlin, scala, csharp, go, rust, ruby, elixir, php, swift, dart, lua, cpp]
203
+ empty_function: [javascript, typescript, python, go, rust, ruby, php]
204
+ other_languages: "listed as NOT scanned — never counted as clean"
205
+ not_implemented_yet:
206
+ - "ratchet on the number of unpaired changes (spec does not define the baseline location or unit)"
207
+ - "per-commit exemption reason (a pre-commit hook cannot read the commit message)"
208
+
183
209
  migration_from_pyramid:
184
210
  deprecated:
185
211
  - "UT ≥ 80% coverage threshold"
@@ -225,7 +251,22 @@ standard:
225
251
 
226
252
  physical_spec:
227
253
  type: custom_script
254
+ # XSPEC-444 R5: this used to check that two scripts EXIST — scripts UDS did not ship.
255
+ # Now it RUNS them (`uds check --standard full-coverage-testing`); a missing script,
256
+ # or a scanner that exits non-zero, fails the check. The scanners' findings go to stderr
257
+ # (`stdio: ['ignore', 2, 2]`) because a failing validator's stdout is not shown. Inline Node
258
+ # keeps it portable.
228
259
  validator:
229
260
  command: >
230
- test -f scripts/check-stubs.sh && test -f scripts/check-anti-fake-tests.sh
231
- rule: "xspec178_enforcement_scripts_present"
261
+ node -e "
262
+ const fs=require('fs'),cp=require('child_process');
263
+ let bad=0;
264
+ for(const f of ['check-anti-fake-tests.mjs','check-stubs.mjs']){
265
+ const p='scripts/'+f;
266
+ if(!fs.existsSync(p)){console.error('MISSING: '+p+' (uds update offers to write it)');bad=1;continue}
267
+ const r=cp.spawnSync(process.execPath,[p],{stdio:['ignore',2,2]});
268
+ if(r.status!==0){console.error('FAILED: '+p+' exited with '+r.status);bad=1}
269
+ }
270
+ process.exit(bad)
271
+ "
272
+ rule: "full_coverage_gate_scripts_clean"
@@ -81,5 +81,9 @@ integration_points:
81
81
  physical_spec:
82
82
  type: custom_script
83
83
  validator:
84
- command: "grep -r 'secrets\\|sast\\|sca\\|dast' .github/workflows/ .gitlab-ci.yml Jenkinsfile 2>/dev/null | head -1 || echo 'no-ci-pipeline'"
84
+ # Fail-closed (XSPEC-444 R1 class sweep): the old form piped grep into `head -1`
85
+ # and fell back to `|| echo 'no-ci-pipeline'` — `head` always exits 0, so the
86
+ # check passed even when no pipeline mentioned any security gate. -q makes grep's
87
+ # own exit status the verdict: 0 only when a gate keyword is found.
88
+ command: "grep -rqE 'secrets|sast|sca|dast' .github/workflows/ .gitlab-ci.yml Jenkinsfile 2>/dev/null"
85
89
  rule: "security_gates_in_pipeline"
@@ -2,8 +2,8 @@
2
2
 
3
3
  > **Language**: English | [繁體中文](../locales/zh-TW/core/ai-response-navigation.md) | [简体中文](../locales/zh-CN/core/ai-response-navigation.md)
4
4
 
5
- **Version**: 1.3.0
6
- **Last Updated**: 2026-08-17
5
+ **Version**: 1.4.0
6
+ **Last Updated**: 2026-10-05
7
7
  **Applicability**: All projects using AI-assisted development
8
8
  **Scope**: universal
9
9
  **Industry Standards**: None (Emerging AI tool practice)
@@ -19,12 +19,15 @@ This standard defines navigation behavior for AI responses: every substantive AI
19
19
 
20
20
  **Solution**: A standard "Navigation Footer" appended to every substantive AI response, with contextual templates, recommendation marking, and adaptive option quantities.
21
21
 
22
- **Scope note (v1.2.0, extended in 1.3.0)**: Rules 1–6 govern what comes *after* the answer.
23
- Rules 7–11 — **all optional** — govern the answer itself: lead with the finding (R7), restate state
24
- across turns (R8), no preamble (R9), plain language as the subject (R10), and a trade-off on every
25
- option rather than only the recommended one (R11). They exist because a response can satisfy every
26
- one of Rules 1–6 while burying its conclusion, stating it in vocabulary only its author holds, or
27
- listing options the reader still has to compare themselves.
22
+ **Scope note (v1.2.0, extended in 1.3.0 and 1.4.0)**: Rules 1–6 govern what comes *after* the answer.
23
+ Rules 7–12 govern the answer itself: lead with the finding (R7), restate state
24
+ across turns (R8), no preamble (R9), plain language as the subject (R10), a trade-off on every
25
+ option rather than only the recommended one (R11), and controlled language (R12). Rules 7–11 are
26
+ **optional**. **Rule 12 is the one exception, and only in part**: its clause that a simplified text
27
+ must keep the writer's hedges is **required**; the rest of R12 is optional. They exist because a
28
+ response can satisfy every one of Rules 1–6 while burying its conclusion, stating it in vocabulary
29
+ only its author holds, listing options the reader still has to compare themselves, or being made
30
+ easy to read by being made more certain than the evidence.
28
31
 
29
32
  ---
30
33
 
@@ -104,11 +107,14 @@ Tier names are **vendor-neutral**. Each tool or platform maps these tiers to its
104
107
 
105
108
  ---
106
109
 
107
- ## The Answer Before the Navigation (Rules 7–11, Optional)
110
+ ## The Answer Before the Navigation (Rules 7–12)
108
111
 
109
112
  > **R7–R9 borrowed from**: [`ayghri/i-have-adhd`](https://github.com/ayghri/i-have-adhd) (MIT), 3 of its 10 rules.
110
113
  > **R10–R11 added in 1.3.0** from a different source — a user telling the author, twice in one session,
111
114
  > that a correct and complete answer was unreadable. R7–R9 had already shipped and were being followed.
115
+ > **R12 added in 1.4.0** from a third source: a public suggestion to ask an LLM to write at "about
116
+ > 80% of the way to" ASD-STE100, a controlled-English standard for technical documentation. Only its
117
+ > principles are taken; its English dictionary and tense rules are not (see Rule 12).
112
118
  > The other 7 were dropped: 2 are already covered by Rules 1–2 above, and 5 either conflict with
113
119
  > this standard (its "no recap / no closers" contradicts Rule 1's Navigation Footer; its
114
120
  > "cap lists at 5" would truncate evidence tables and traversal denominators) or duplicate
@@ -119,10 +125,12 @@ itself — a response could bury its conclusion under a wall of evidence and sti
119
125
  in this standard by appending a correct Navigation Footer. A reader who cannot find the answer is
120
126
  not helped by being told what to do next.
121
127
 
122
- **These five rules are optional**, in the same sense as Rule 6: adopting projects are not required
128
+ **Rules 7–11 are optional**, in the same sense as Rule 6: adopting projects are not required
123
129
  to enable them, and existing skills need no retroactive update. A project MAY promote any of them to
124
- required in its own configuration. What is *not* optional is that they have precise triggers — a rule
125
- phrased so loosely that it never fires is indistinguishable from not having the rule.
130
+ required in its own configuration. **Rule 12 has one required clause** (12.1) and an optional rest
131
+ (12.2); the split is argued inside the rule. What is *not* optional anywhere is that every rule has
132
+ a precise trigger — a rule phrased so loosely that it never fires is indistinguishable from not
133
+ having the rule.
126
134
 
127
135
  ### Rule 7: Lead With the Finding, Not the Process (Optional)
128
136
 
@@ -187,6 +195,11 @@ It governs which of the two is the subject of the sentence.
187
195
  response can lead with its finding and still state that finding in vocabulary only its author
188
196
  holds. Both failures leave the reader unable to act; they are different failures.
189
197
 
198
+ **Plain wording must not buy its plainness with certainty.** Putting an explanation in the
199
+ reader's words is a rewrite, and a rewrite is exactly where "might" quietly becomes "is". When R10
200
+ is applied to a claim the writer had hedged, [Rule 12](#rule-12-controlled-language-partly-required)
201
+ clause 12.1 (required) governs: the hedge stays.
202
+
190
203
  ### Rule 11: Every Option Carries Its Own Trade-off (Optional)
191
204
 
192
205
  **Trigger**: a response that asks the reader to choose between two or more courses of action.
@@ -214,6 +227,107 @@ worth stating, say so explicitly rather than leaving the column empty — an emp
214
227
  **Composes with Rule 4**: the option count stays bounded (1–5). Trade-offs make each option
215
228
  costlier to read, so this rule makes Rule 4's cap matter more, not less.
216
229
 
230
+ ### Rule 12: Controlled Language (Partly Required)
231
+
232
+ **Trigger**: writing, rewriting, shortening, simplifying or translating text for a reader who is
233
+ not its author — typically a non-specialist who has to judge or approve something from it.
234
+
235
+ Controlled languages (writing rules that trade variety for predictability) make text easier to
236
+ read. They have a known way of failing: the easiest sentence to read is a confident one, so
237
+ "simplification" drifts toward certainty. This rule takes the principles of controlled writing and
238
+ puts a hard stop on that drift.
239
+
240
+ #### 12.1 A simplified text keeps its hedges (Required)
241
+
242
+ A hedge is a word that tells the reader how far to trust a claim: *might, could, probably,
243
+ appears to, not yet confirmed* — 可能、推斷、大概、尚未確認. It is information, not padding.
244
+
245
+ When you shorten, simplify, rewrite or translate:
246
+
247
+ - **Do not turn an uncertain claim into a certain one.** If the source says "might", the result
248
+ says "might" (or the equivalent in the language of the result).
249
+ - **Do not drop the hedge to save words.** A shorter sentence is the goal; a more certain one is
250
+ not allowed.
251
+ - **Do not add facts the source did not state** — an invented cause or an invented "confirmed" is
252
+ the same failure in another form.
253
+ - A hedge may go only when the claim has since been verified, and then the verification (what was
254
+ checked, with what result) takes its place. Deleting the hedge alone is not verification.
255
+
256
+ **Why this one is required and the rest are not**: it is the only part whose failure misleads the
257
+ reader about what is true, rather than only making the text harder to read. It also needs no
258
+ calibration — the check is a comparison of the text before and after, which a person or a model
259
+ can do in any language — whereas every threshold below depends on language and audience.
260
+
261
+ #### 12.2 Plain-writing principles (Optional)
262
+
263
+ Apply these when the reader is a non-specialist. They are language-neutral: each is stated in the
264
+ unit the language itself uses, with no word list.
265
+
266
+ | Principle | What it asks for |
267
+ |-----------|------------------|
268
+ | **Short sentences, counted in the language's own unit** | One idea per sentence. As a *starting range*, not a limit: roughly 15–25 words in English, roughly 25–40 characters in Chinese. A sentence far beyond the range is a signal to split it, not a defect to count. Calibrate per language and per reader |
269
+ | **One thing, one name** | Pick one name for each thing and use it for the whole text. Do not vary it for style: a reader who sees a second name assumes a second thing |
270
+ | **A clear subject and the active voice** | Say who does what. The passive is acceptable when the actor is unknown or does not matter |
271
+ | **One step, one action** | A procedure is a numbered list with one action per item, not a paragraph |
272
+ | **Few semicolons** | A semicolon joins two ideas the reader must hold at once. Split into two sentences or a list |
273
+ | **Numbers carry units** | "30 seconds", "3 files", "NT$1,200" — never a bare "30" |
274
+
275
+ **Why optional**: the ranges above are starting points that have not been calibrated against
276
+ reader outcomes, and no checker enforces them. A *required* rule with a threshold nobody can
277
+ verify produces mechanical compliance — sentences split until they stop reading as sentences —
278
+ and an expert reader may be better served by denser text. These principles are guidance a writer
279
+ applies with judgment; 12.1 is the part that does not bend.
280
+
281
+ #### What this rule does not take from ASD-STE100
282
+
283
+ ASD-STE100's **approved dictionary** (one meaning per approved English word, with a closed list
284
+ of allowed words) and its **tense restrictions** depend on the English language. They do not apply
285
+ to Chinese or to other non-English text, and this standard ships **no word list of any kind**.
286
+ Only the principles in 12.2 are taken, restated so that each can be applied in any language.
287
+
288
+ For the same reason, do not use a counter that splits on whitespace or ASCII characters to gauge
289
+ non-English text: it reads a Chinese paragraph as one "word" and passes it however long it is. A
290
+ gauge that is always green on a language measures nothing in that language.
291
+
292
+ #### Example: one passage, three rewrites, and one that is not allowed
293
+
294
+ The example is in Chinese on purpose: the rule is language-neutral, and Chinese is where an
295
+ English-only approach does not carry over. The three valid versions keep the hedges 可能 (might),
296
+ 推斷 (inferred) and 尚未 (not yet) and add no fact the original lacks.
297
+
298
+ **Original**
299
+
300
+ ```text
301
+ 經過檢查,登入頁面在高流量時段回應變慢,這個問題可能是資料庫連線池被耗盡所造成的,我們推斷是因為上週的改版新增了一個會長時間佔用連線的查詢;目前尚未在測試環境重現,所以修復後的效果還需要被確認,建議在確認之前先不要對外宣布已經解決。
302
+ ```
303
+
304
+ **About 80%** — shorter sentences, still reads as prose
305
+
306
+ ```text
307
+ 登入頁面在高流量時段回應變慢。原因可能是資料庫連線池被耗盡。我們推斷,上週改版新增了一個查詢,它會長時間佔用連線。這一點尚未在測試環境重現,修復後有沒有效,也還沒確認。確認之前,建議先不要對外宣布已經解決。
308
+ ```
309
+
310
+ **Strict** — one idea per line, labelled, hedges kept
311
+
312
+ ```text
313
+ 登入頁面在高流量時段回應變慢。
314
+ 1. 原因:可能是資料庫連線池被耗盡。
315
+ 2. 推斷:上週改版新增了一個查詢,這個查詢可能長時間佔用連線。
316
+ 3. 狀態:尚未在測試環境重現。
317
+ 4. 修復效果:尚未確認。
318
+ 5. 建議:確認之前,不要對外宣布已解決。
319
+ ```
320
+
321
+ **Not a valid rewrite** — shortest of all, and wrong
322
+
323
+ ```text
324
+ 登入頁面變慢,原因是資料庫連線池被耗盡,已確認由上週改版造成。
325
+ ```
326
+
327
+ The last version is the shortest and the easiest to read, and it fails 12.1 twice: "可能" became
328
+ a flat statement of cause, and "尚未重現" became "已確認" (confirmed), a fact the original never
329
+ stated. A reader who approves a fix on the strength of it has been given something untrue.
330
+
217
331
  ---
218
332
 
219
333
  ## Contextual Templates
@@ -408,6 +522,7 @@ Each tool's integration layer is responsible for rendering the Navigation Footer
408
522
  | R9 | *(Optional)* No preamble. Closers still required — see R1 |
409
523
  | R10 | *(Optional)* Plain language is the subject; identifiers support it, after the claim |
410
524
  | R11 | *(Optional)* Every option states what it buys and costs — not only the recommended one |
525
+ | R12 | **12.1 *(Required)***: when simplifying, shortening or translating, keep the hedges — never turn "might" into "is". 12.2 *(Optional)*: short sentences in the language's own unit, one name per thing, active voice, one action per step, few semicolons, numbers with units. No English dictionary — it does not carry to other languages |
411
526
 
412
527
  | Exempt | Not Exempt |
413
528
  |--------|------------|
@@ -431,6 +546,7 @@ Each tool's integration layer is responsible for rendering the Navigation Footer
431
546
 
432
547
  | Version | Date | Changes |
433
548
  |---------|------|---------|
549
+ | 1.4.0 | 2026-10-05 | Add R12, controlled language, language-neutral. One clause is required (12.1: a simplified text keeps the writer's hedges — "might" does not become "is" and no unstated fact is added); the rest (12.2: sentence length in the language's own unit, one name per thing, active voice, one action per step, few semicolons, numbers with units) is optional, with the reason recorded. Takes the principles of ASD-STE100 and none of its English dictionary or tense rules, and says so in the standard. Ships a Chinese before/after example in three strictness levels plus one rewrite that is shorter and wrong. R10 now points to R12, because putting a claim in plain words is a rewrite and a rewrite is where hedges get lost |
434
550
  | 1.3.0 | 2026-08-17 | Add optional R10–R11. R10 governs register: plain language is the subject of the sentence and identifiers support it — distinct from R7, which orders finding before evidence, because a response can lead with its finding and still state it in vocabulary only its author holds. R11 extends Rule 2 from the recommended option to all of them: a list where only the recommendation is argued hands the comparison back to the reader, and an option shown without its cost reads as having none |
435
551
  | 1.2.0 | 2026-08-17 | Add optional R7–R9 governing the answer itself (lead with the finding, restate state, no preamble). Borrowed from `ayghri/i-have-adhd` (MIT), 3 of its 10 rules; the other 7 were dropped as duplicated by R1–R2, in conflict with R1, or covered by estimation-standards. Rules 1–6 could all be satisfied by a response that buries its conclusion — R7–R9 close that |
436
552
  | 1.1.0 | 2026-06-10 | Add R6 optional model tier annotation (`〔model: Fast\|Standard\|Capable〕`); vendor-neutral; no forced changes to existing skills |
@@ -1,7 +1,7 @@
1
1
  # Full Coverage Testing Standards
2
2
 
3
3
  > **AI-optimized version**: `ai/standards/full-coverage-testing.ai.yaml`
4
- > **XSPEC**: XSPEC-178
4
+ > **XSPEC**: XSPEC-178, XSPEC-444 (R2, R5 — pre-commit warnings and shipped gate scripts)
5
5
  > **Replaces**: Pyramid threshold model (UT≥80%, IT≥70%, E2E happy-path-only)
6
6
 
7
7
  ## Overview
@@ -228,6 +228,60 @@ Axis ⑨ is satisfied when this section declares all three: **derive** (Step 1 m
228
228
 
229
229
  ---
230
230
 
231
+ ## Pre-commit Warnings and Gate Scripts Shipped by UDS (XSPEC-444 R2, R5)
232
+
233
+ The rules above used to depend on scripts the standard told you to write yourself. `uds init` now writes the scanners for you, and `uds check` — the command UDS's pre-commit hook runs — prints what they and one more check find. **All of it warns by default and blocks nothing**; tightening is opt-in (see "Warn first, tighten later").
234
+
235
+ ### The two scanners (`uds init` writes them to `scripts/`)
236
+
237
+ | Script | Finds |
238
+ |--------|-------|
239
+ | `scripts/check-anti-fake-tests.mjs` | a test with **no assertion**; a test whose only assertions are **tautologies** (`expect(true).toBe(true)`, `assert 200 == 200`); a test file in which **every test is skipped or todo** |
240
+ | `scripts/check-stubs.mjs` | `// WARNING: STUB` markers; a body that says it is **not implemented** (`raise NotImplementedError`, `todo!()`, `TODO()` ...) with no marker beside it; a named function whose body is **empty** with no marker beside it |
241
+
242
+ - Pure Node, no dependencies, no test framework assumed. They are **your files**: edit them, wire them into CI. Run on its own, each exits non-zero when it finds something — `node scripts/check-stubs.mjs` is the pre-push/deploy gate this standard's deployment gates describe.
243
+ - `uds init` never overwrites a file that exists, and `uds update` never overwrites one either; for a project initialised earlier, `uds update` offers to write them (the prompt defaults to no; `uds update -y` answers it yes).
244
+ - With files staged they read only those files; with nothing staged (a CI run, a manual `uds check`) each walks the whole project — the walk stops at 200,000 files, and `uds check` gives each scanner 120 seconds (past that it reports "could not judge", never a pass) — so in a very large repository run them in CI rather than expecting them to be instant.
245
+ - A placeholder is *declared* by `STUB` or `COVERAGE_EXEMPT` on its line or the three above; a declared placeholder is reported once, as its marker.
246
+ - **Languages.** Test-file rules exist for JavaScript/TypeScript, Python, Java/Kotlin/Scala/C#, Go, Rust, Ruby, Elixir, PHP, Swift, Dart, Lua and C/C++. A test file in any **other** language is listed as "NOT scanned" — never counted as clean. Empty-function rules exist for JavaScript/TypeScript, Python, Go, Rust, Ruby and PHP; elsewhere markers and "not implemented" bodies are still found and the output says which languages had no empty-function rule.
247
+ - They read text; they do not run your tests. A helper that asserts under a name the scanner cannot recognise is reported as "no-assertion": name it `assert*` / `verify*` / `expect*`, or list its pattern under `assertionPatterns` in the policy file.
248
+ - Each run first checks itself against known fakes and known good tests; if that fails it exits `2` ("could not judge"), which is never a pass.
249
+
250
+ ### Code changed without a test (`uds check`, on staged changes)
251
+
252
+ When files are staged for commit, `uds check` compares what changed:
253
+
254
+ - code files changed and **no test file** changed in the same commit → a warning that lists the code files;
255
+ - a changed file of a type UDS does not recognise → a warning that lists it and says how to classify it (never assumed fine, never blocks);
256
+ - a deletion is not a change that needs a test; a **pure rename** (git's `R100`) and a path matching an `exempt` entry are exempt, and the output records the reason.
257
+
258
+ With nothing staged (a CI run, a manual `uds check`) the diff check says nothing; the two scanners then look at the whole project.
259
+
260
+ ### The policy file `.standards/test-policy.json` (optional)
261
+
262
+ Which paths are tests and which are code is data with defaults for the common ecosystems, not a list of frameworks. Every list **adds to** the defaults:
263
+
264
+ ```json
265
+ {
266
+ "mode": "warn",
267
+ "testDirs": ["integration"],
268
+ "testPatterns": ["*.itest.*"],
269
+ "sourceExtensions": ["zig"],
270
+ "nonCodeExtensions": ["gradle"],
271
+ "ignore": ["generated/**"],
272
+ "exempt": [{ "pattern": "src/gen/**", "reason": "generated by protoc" }],
273
+ "assertionPatterns": ["\\bmustMatch\\w*\\s*\\("]
274
+ }
275
+ ```
276
+
277
+ An `exempt` entry without a `reason` is not honored and is reported: an exemption must say why.
278
+
279
+ ### Warn first, tighten later
280
+
281
+ `"mode": "warn"` (the default) prints and lets the commit through. `"mode": "block"` makes `uds check` exit non-zero — and so stops the commit — when a scanner finds something, a scanner cannot judge, or code changed without a test. A file type UDS does not recognise never blocks. **Not implemented yet** (the specification does not say where a baseline would live or what it counts): a ratchet on the number of unpaired changes, and a per-commit exemption reason (a pre-commit hook cannot read the commit message).
282
+
283
+ ---
284
+
231
285
  ## Migration from Pyramid Model
232
286
 
233
287
  If your project previously used pyramid thresholds:
@@ -235,8 +289,8 @@ If your project previously used pyramid thresholds:
235
289
  1. **Delete** any hardcoded coverage thresholds from `jest.config.js` / `vitest.config.ts` (`coverageThreshold` option)
236
290
  2. **Install** `.coverage-baseline.json` with current coverage as the starting ratchet
237
291
  3. **Add** `scripts/check-coverage-ratchet.sh` to CI
238
- 4. **Add** `scripts/check-stubs.sh` to deploy.sh and pre-push hook
239
- 5. **Add** `scripts/check-anti-fake-tests.sh` to pre-commit or CI
292
+ 4. **Add** `scripts/check-stubs.mjs` to deploy.sh and pre-push hook (written by `uds init`; `uds update` offers it to an existing project)
293
+ 5. **Add** `scripts/check-anti-fake-tests.mjs` to pre-commit or CI (written by `uds init`; `uds check` already runs it and warns)
240
294
 
241
295
  The ratchet starts at your current coverage. From that point on, it can only increase.
242
296