universal-dev-standards 6.7.4 → 6.8.0
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/bin/uds.js +7 -0
- package/bundled/ai/standards/ai-instruction-standards.ai.yaml +6 -6
- package/bundled/core/ai-instruction-standards.md +9 -7
- package/bundled/locales/zh-CN/CHANGELOG.md +32 -3
- package/bundled/locales/zh-CN/README.md +6 -6
- package/bundled/locales/zh-CN/SECURITY.md +1 -1
- package/bundled/locales/zh-CN/core/ai-instruction-standards.md +10 -8
- package/bundled/locales/zh-CN/docs/CHEATSHEET.md +20 -6
- package/bundled/locales/zh-CN/docs/FEATURE-REFERENCE.md +149 -11
- package/bundled/locales/zh-CN/skills/agents/README.md +1 -1
- package/bundled/locales/zh-CN/skills/reverse-engineer/tdd-analysis.md +13 -23
- package/bundled/locales/zh-CN/skills/spec-derivation/SKILL.md +5 -1
- package/bundled/locales/zh-CN/skills/workflows/README.md +2 -11
- package/bundled/locales/zh-TW/CHANGELOG.md +32 -3
- package/bundled/locales/zh-TW/README.md +6 -6
- package/bundled/locales/zh-TW/SECURITY.md +1 -1
- package/bundled/locales/zh-TW/core/ai-instruction-standards.md +10 -8
- package/bundled/locales/zh-TW/docs/CHEATSHEET.md +20 -6
- package/bundled/locales/zh-TW/docs/FEATURE-REFERENCE.md +149 -11
- package/bundled/locales/zh-TW/skills/agents/README.md +1 -1
- package/bundled/locales/zh-TW/skills/reverse-engineer/tdd-analysis.md +13 -23
- package/bundled/locales/zh-TW/skills/spec-derivation/SKILL.md +5 -1
- package/bundled/locales/zh-TW/skills/workflows/README.md +2 -11
- package/bundled/skills/agents/README.md +1 -1
- package/bundled/skills/audit-assistant/SKILL.md +24 -1
- package/bundled/skills/changelog-guide/SKILL.md +24 -1
- package/bundled/skills/commit-standards/SKILL.md +24 -1
- package/bundled/skills/docs-generator/SKILL.md +24 -1
- package/bundled/skills/project-discovery/SKILL.md +24 -1
- package/bundled/skills/release-standards/SKILL.md +24 -1
- package/bundled/skills/reverse-engineer/SKILL.md +24 -1
- package/bundled/skills/reverse-engineer/tdd-analysis.md +16 -23
- package/bundled/skills/spec-derivation/SKILL.md +24 -1
- package/bundled/skills/workflows/README.md +2 -11
- package/package.json +4 -2
- package/src/commands/lint.js +96 -0
- package/src/commands/quickstart.js +16 -13
- package/src/i18n/messages.js +3 -3
- package/src/reconciler/actual-state-scanner.js +14 -3
- package/src/utils/hasher.js +63 -8
- package/src/utils/integration-generator.js +21 -3
- package/src/utils/skills-installer.js +17 -3
- package/src/utils/spec-linter.js +35 -76
- package/standards-registry.json +7 -7
- package/src/commands/sync.js +0 -133
|
@@ -8,7 +8,30 @@ description: |
|
|
|
8
8
|
Keywords: docgen, usage docs, cheatsheet, feature reference, generated documentation, 使用文件, 速查表, 文件產生.
|
|
9
9
|
allowed-tools: Read, Write, Grep, Glob, Bash(*)
|
|
10
10
|
argument-hint: "[config file | 設定檔]"
|
|
11
|
-
|
|
11
|
+
status: stable
|
|
12
|
+
# 2026-08-18: `disable-model-invocation: true` removed, and a status recorded.
|
|
13
|
+
#
|
|
14
|
+
# The flag was applied by d415937e alongside the description rewrite and, like
|
|
15
|
+
# the two lifted on 2026-08-17, followed no stateable rule. These eight were
|
|
16
|
+
# left alone that day for a reason that was correct at the time: the rule
|
|
17
|
+
# settled on was "a reference is model-invocable", and none of them carried a
|
|
18
|
+
# `status` at all, so lifting them would have replaced one unruled state with
|
|
19
|
+
# one unruled action.
|
|
20
|
+
#
|
|
21
|
+
# Measured 2026-08-18, which is what closed it: all eight carry a full
|
|
22
|
+
# `Use when:` trigger and a `Not for:` exclusion, all eight describe an action
|
|
23
|
+
# rather than reference material, and all eight already have a slash command —
|
|
24
|
+
# which is exactly the shape of `code-review-assistant`, whose paired `/code-review`
|
|
25
|
+
# was ruled not to justify the flag. `journey-test-assistant` is the standing
|
|
26
|
+
# precedent: same "Generate X" shape, `status: stable`, never disabled.
|
|
27
|
+
#
|
|
28
|
+
# `stable` rather than a new value: `skills/` uses reference, stable and
|
|
29
|
+
# experimental, and inventing a fourth would be the same unruled-action mistake
|
|
30
|
+
# in different clothing.
|
|
31
|
+
#
|
|
32
|
+
# The cost of being wrong is asymmetric and observable in only one direction.
|
|
33
|
+
# Over-triggering shows up and is undone by deleting a line; a skill that is
|
|
34
|
+
# structurally unable to fire produces no signal at all. (XSPEC-378 R5)
|
|
12
35
|
---
|
|
13
36
|
|
|
14
37
|
# Documentation Generator | 文件產生器
|
|
@@ -8,7 +8,30 @@ description: |
|
|
|
8
8
|
Keywords: discovery, project assessment, legacy onboarding, risk register, technical debt, 現況評估, 專案盤點, 風險登記簿.
|
|
9
9
|
allowed-tools: Read, Grep, Glob, Bash(npm test:*), Bash(npm audit:*), Bash(npm outdated:*)
|
|
10
10
|
argument-hint: "[feature area | 功能範圍]"
|
|
11
|
-
|
|
11
|
+
status: stable
|
|
12
|
+
# 2026-08-18: `disable-model-invocation: true` removed, and a status recorded.
|
|
13
|
+
#
|
|
14
|
+
# The flag was applied by d415937e alongside the description rewrite and, like
|
|
15
|
+
# the two lifted on 2026-08-17, followed no stateable rule. These eight were
|
|
16
|
+
# left alone that day for a reason that was correct at the time: the rule
|
|
17
|
+
# settled on was "a reference is model-invocable", and none of them carried a
|
|
18
|
+
# `status` at all, so lifting them would have replaced one unruled state with
|
|
19
|
+
# one unruled action.
|
|
20
|
+
#
|
|
21
|
+
# Measured 2026-08-18, which is what closed it: all eight carry a full
|
|
22
|
+
# `Use when:` trigger and a `Not for:` exclusion, all eight describe an action
|
|
23
|
+
# rather than reference material, and all eight already have a slash command —
|
|
24
|
+
# which is exactly the shape of `code-review-assistant`, whose paired `/code-review`
|
|
25
|
+
# was ruled not to justify the flag. `journey-test-assistant` is the standing
|
|
26
|
+
# precedent: same "Generate X" shape, `status: stable`, never disabled.
|
|
27
|
+
#
|
|
28
|
+
# `stable` rather than a new value: `skills/` uses reference, stable and
|
|
29
|
+
# experimental, and inventing a fourth would be the same unruled-action mistake
|
|
30
|
+
# in different clothing.
|
|
31
|
+
#
|
|
32
|
+
# The cost of being wrong is asymmetric and observable in only one direction.
|
|
33
|
+
# Over-triggering shows up and is undone by deleting a line; a skill that is
|
|
34
|
+
# structurally unable to fire produces no signal at all. (XSPEC-378 R5)
|
|
12
35
|
---
|
|
13
36
|
|
|
14
37
|
# Project Discovery | 專案現況評估
|
|
@@ -9,7 +9,30 @@ description: |
|
|
|
9
9
|
allowed-tools: Read, Write, Grep, Bash(git:*), Bash(npm version:*)
|
|
10
10
|
argument-hint: "[version]"
|
|
11
11
|
prerequisites: ["release-check"]
|
|
12
|
-
|
|
12
|
+
status: stable
|
|
13
|
+
# 2026-08-18: `disable-model-invocation: true` removed, and a status recorded.
|
|
14
|
+
#
|
|
15
|
+
# The flag was applied by d415937e alongside the description rewrite and, like
|
|
16
|
+
# the two lifted on 2026-08-17, followed no stateable rule. These eight were
|
|
17
|
+
# left alone that day for a reason that was correct at the time: the rule
|
|
18
|
+
# settled on was "a reference is model-invocable", and none of them carried a
|
|
19
|
+
# `status` at all, so lifting them would have replaced one unruled state with
|
|
20
|
+
# one unruled action.
|
|
21
|
+
#
|
|
22
|
+
# Measured 2026-08-18, which is what closed it: all eight carry a full
|
|
23
|
+
# `Use when:` trigger and a `Not for:` exclusion, all eight describe an action
|
|
24
|
+
# rather than reference material, and all eight already have a slash command —
|
|
25
|
+
# which is exactly the shape of `code-review-assistant`, whose paired `/code-review`
|
|
26
|
+
# was ruled not to justify the flag. `journey-test-assistant` is the standing
|
|
27
|
+
# precedent: same "Generate X" shape, `status: stable`, never disabled.
|
|
28
|
+
#
|
|
29
|
+
# `stable` rather than a new value: `skills/` uses reference, stable and
|
|
30
|
+
# experimental, and inventing a fourth would be the same unruled-action mistake
|
|
31
|
+
# in different clothing.
|
|
32
|
+
#
|
|
33
|
+
# The cost of being wrong is asymmetric and observable in only one direction.
|
|
34
|
+
# Over-triggering shows up and is undone by deleting a line; a skill that is
|
|
35
|
+
# structurally unable to fire produces no signal at all. (XSPEC-378 R5)
|
|
13
36
|
---
|
|
14
37
|
|
|
15
38
|
# Release Assistant | 發布助手
|
|
@@ -8,7 +8,30 @@ description: |
|
|
|
8
8
|
Keywords: reverse engineering, system archeology, legacy code, spec extraction, data model, runtime, 反向工程, 系統考古, 規格提取.
|
|
9
9
|
allowed-tools: Read, Grep, Glob, Bash(pg_dump:*), Bash(mysql:*), Bash(sqlite3:*), Bash(npm run:*), Bash(cat:*), Bash(docker:*)
|
|
10
10
|
argument-hint: "[spec|data|runtime|bdd|tdd] <input>"
|
|
11
|
-
|
|
11
|
+
status: stable
|
|
12
|
+
# 2026-08-18: `disable-model-invocation: true` removed, and a status recorded.
|
|
13
|
+
#
|
|
14
|
+
# The flag was applied by d415937e alongside the description rewrite and, like
|
|
15
|
+
# the two lifted on 2026-08-17, followed no stateable rule. These eight were
|
|
16
|
+
# left alone that day for a reason that was correct at the time: the rule
|
|
17
|
+
# settled on was "a reference is model-invocable", and none of them carried a
|
|
18
|
+
# `status` at all, so lifting them would have replaced one unruled state with
|
|
19
|
+
# one unruled action.
|
|
20
|
+
#
|
|
21
|
+
# Measured 2026-08-18, which is what closed it: all eight carry a full
|
|
22
|
+
# `Use when:` trigger and a `Not for:` exclusion, all eight describe an action
|
|
23
|
+
# rather than reference material, and all eight already have a slash command —
|
|
24
|
+
# which is exactly the shape of `code-review-assistant`, whose paired `/code-review`
|
|
25
|
+
# was ruled not to justify the flag. `journey-test-assistant` is the standing
|
|
26
|
+
# precedent: same "Generate X" shape, `status: stable`, never disabled.
|
|
27
|
+
#
|
|
28
|
+
# `stable` rather than a new value: `skills/` uses reference, stable and
|
|
29
|
+
# experimental, and inventing a fourth would be the same unruled-action mistake
|
|
30
|
+
# in different clothing.
|
|
31
|
+
#
|
|
32
|
+
# The cost of being wrong is asymmetric and observable in only one direction.
|
|
33
|
+
# Over-triggering shows up and is undone by deleting a line; a skill that is
|
|
34
|
+
# structurally unable to fire produces no signal at all. (XSPEC-378 R5)
|
|
12
35
|
---
|
|
13
36
|
|
|
14
37
|
# Reverse Engineering Assistant | 反向工程助手
|
|
@@ -597,9 +597,22 @@ Mark both scenarios as [Inferred] with shared test reference
|
|
|
597
597
|
|
|
598
598
|
## CI/CD Integration
|
|
599
599
|
|
|
600
|
-
### GitHub Actions
|
|
600
|
+
### GitHub Actions — not currently scriptable
|
|
601
|
+
|
|
602
|
+
There is no `uds reverse-tdd` CLI command. Coverage-gap analysis is delivered
|
|
603
|
+
as the **`reverse-tdd`** AI agent (`uds agent install reverse-tdd`) — an
|
|
604
|
+
interactive, LLM-driven analysis run from your AI tool, not a deterministic
|
|
605
|
+
script that emits a `coverage.json` file. A prior version of this doc showed
|
|
606
|
+
a GitHub Actions step invoking a CLI command that never existed (XSPEC-383
|
|
607
|
+
R4, 2026-08-19).
|
|
608
|
+
|
|
609
|
+
The sketch below is **illustrative only** — it shows the shape a scriptable
|
|
610
|
+
CI integration would need if one is built, not something you can paste into
|
|
611
|
+
a workflow today:
|
|
601
612
|
|
|
602
613
|
```yaml
|
|
614
|
+
# ASPIRATIONAL — no scriptable equivalent exists yet.
|
|
615
|
+
# Today: run the reverse-tdd agent interactively from your AI tool instead.
|
|
603
616
|
name: BDD Coverage Check
|
|
604
617
|
|
|
605
618
|
on: [pull_request]
|
|
@@ -610,29 +623,9 @@ jobs:
|
|
|
610
623
|
steps:
|
|
611
624
|
- uses: actions/checkout@v4
|
|
612
625
|
|
|
613
|
-
- name: Run BDD Coverage Analysis
|
|
614
|
-
run: |
|
|
615
|
-
uds reverse-tdd features/ --output coverage.json
|
|
616
|
-
|
|
617
|
-
- name: Check Coverage Threshold
|
|
626
|
+
- name: Run BDD Coverage Analysis (not yet implemented)
|
|
618
627
|
run: |
|
|
619
|
-
|
|
620
|
-
if (( $(echo "$COVERAGE < 0.80" | bc -l) )); then
|
|
621
|
-
echo "Coverage $COVERAGE below 80% threshold"
|
|
622
|
-
exit 1
|
|
623
|
-
fi
|
|
624
|
-
|
|
625
|
-
- name: Comment on PR
|
|
626
|
-
uses: actions/github-script@v6
|
|
627
|
-
with:
|
|
628
|
-
script: |
|
|
629
|
-
const coverage = require('./coverage.json');
|
|
630
|
-
github.rest.issues.createComment({
|
|
631
|
-
issue_number: context.issue.number,
|
|
632
|
-
owner: context.repo.owner,
|
|
633
|
-
repo: context.repo.repo,
|
|
634
|
-
body: `## BDD Coverage Report\n\nCoverage: ${(coverage.summary.effectiveCoverage * 100).toFixed(1)}%`
|
|
635
|
-
});
|
|
628
|
+
echo "No CLI equivalent yet — see reverse-tdd agent for interactive use"
|
|
636
629
|
```
|
|
637
630
|
|
|
638
631
|
---
|
|
@@ -9,7 +9,30 @@ description: |
|
|
|
9
9
|
allowed-tools: Read, Write, Grep, Glob
|
|
10
10
|
argument-hint: "[all|bdd|tdd|it|e2e|atdd] <spec-file>"
|
|
11
11
|
prerequisites: ["spec-approved"]
|
|
12
|
-
|
|
12
|
+
status: stable
|
|
13
|
+
# 2026-08-18: `disable-model-invocation: true` removed, and a status recorded.
|
|
14
|
+
#
|
|
15
|
+
# The flag was applied by d415937e alongside the description rewrite and, like
|
|
16
|
+
# the two lifted on 2026-08-17, followed no stateable rule. These eight were
|
|
17
|
+
# left alone that day for a reason that was correct at the time: the rule
|
|
18
|
+
# settled on was "a reference is model-invocable", and none of them carried a
|
|
19
|
+
# `status` at all, so lifting them would have replaced one unruled state with
|
|
20
|
+
# one unruled action.
|
|
21
|
+
#
|
|
22
|
+
# Measured 2026-08-18, which is what closed it: all eight carry a full
|
|
23
|
+
# `Use when:` trigger and a `Not for:` exclusion, all eight describe an action
|
|
24
|
+
# rather than reference material, and all eight already have a slash command —
|
|
25
|
+
# which is exactly the shape of `code-review-assistant`, whose paired `/code-review`
|
|
26
|
+
# was ruled not to justify the flag. `journey-test-assistant` is the standing
|
|
27
|
+
# precedent: same "Generate X" shape, `status: stable`, never disabled.
|
|
28
|
+
#
|
|
29
|
+
# `stable` rather than a new value: `skills/` uses reference, stable and
|
|
30
|
+
# experimental, and inventing a fourth would be the same unruled-action mistake
|
|
31
|
+
# in different clothing.
|
|
32
|
+
#
|
|
33
|
+
# The cost of being wrong is asymmetric and observable in only one direction.
|
|
34
|
+
# Over-triggering shows up and is undone by deleting a line; a skill that is
|
|
35
|
+
# structurally unable to fire produces no signal at all. (XSPEC-378 R5)
|
|
13
36
|
---
|
|
14
37
|
|
|
15
38
|
# Forward Derivation | 正向推演
|
|
@@ -157,18 +157,9 @@ steps:
|
|
|
157
157
|
|
|
158
158
|
## Usage
|
|
159
159
|
|
|
160
|
-
###
|
|
160
|
+
### Getting Workflows
|
|
161
161
|
|
|
162
|
-
|
|
163
|
-
# List available workflows
|
|
164
|
-
uds workflow list
|
|
165
|
-
|
|
166
|
-
# Install specific workflow
|
|
167
|
-
uds workflow install integrated-flow
|
|
168
|
-
|
|
169
|
-
# Install all workflows
|
|
170
|
-
uds workflow install --all
|
|
171
|
-
```
|
|
162
|
+
`uds workflow` was removed in v6.0.0 (process orchestration moved to the adoption layer — DEC-049; see [MIGRATION-v6.md §3](../../docs/MIGRATION-v6.md#3-removed-4-deprecated-cli-commands)). There is no CLI install step: workflow definitions live directly under `skills/workflows/*.workflow.yaml` in this repo (see the table above), so reference them by path or copy them into your project's `.claude/workflows/`.
|
|
172
163
|
|
|
173
164
|
### Execution
|
|
174
165
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-dev-standards",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.8.0",
|
|
4
4
|
"description": "CLI tool for adopting Universal Development Standards",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"documentation",
|
|
@@ -56,7 +56,9 @@
|
|
|
56
56
|
"prepare": "node ../scripts/setup-husky.mjs",
|
|
57
57
|
"test:upgrade": "node scripts/test-upgrade-path.mjs",
|
|
58
58
|
"check:bundle-parity": "node scripts/check-bundle-parity.mjs",
|
|
59
|
-
"prepack": "node scripts/prepack.mjs"
|
|
59
|
+
"prepack": "node scripts/prepack.mjs",
|
|
60
|
+
"check:reachability": "node scripts/check-module-reachability.mjs",
|
|
61
|
+
"check:command-existence": "node scripts/check-command-existence.mjs"
|
|
60
62
|
},
|
|
61
63
|
"dependencies": {
|
|
62
64
|
"@inquirer/prompts": "^8.4.2",
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `uds lint` — dependency validity + size checks against installed specs
|
|
3
|
+
* (specs/*.md).
|
|
4
|
+
*
|
|
5
|
+
* XSPEC-383 R5 (Option E). `cli/src/utils/spec-linter.js` and this command's
|
|
6
|
+
* JSON shape existed since 2026-04-07, but no `uds lint` command was ever
|
|
7
|
+
* registered — VibeOps's `lint-executor.ts` has been calling
|
|
8
|
+
* `npx uds lint --json` since the same day and getting `command not found`
|
|
9
|
+
* every time, four and a half months, without anyone noticing (see
|
|
10
|
+
* `cli/scripts/check-module-reachability.mjs` for the full incident).
|
|
11
|
+
*
|
|
12
|
+
* This registers the command with exactly the two checks that survived
|
|
13
|
+
* `lintAll()`'s AC-coverage removal (see spec-linter.js for why AC coverage
|
|
14
|
+
* was dropped rather than patched). The `--json` shape is deliberately NOT a
|
|
15
|
+
* new design — it matches the shape VibeOps's `lint-executor.ts` has already
|
|
16
|
+
* been parsing (`result.summary.fail`, `result.results[].specId/.status/.message`)
|
|
17
|
+
* since it was written, so wiring this up does not also require a change on
|
|
18
|
+
* the VibeOps side.
|
|
19
|
+
*
|
|
20
|
+
* @module commands/lint
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import chalk from 'chalk';
|
|
24
|
+
import { lintAll } from '../utils/spec-linter.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Render a one-line human message for a single spec's lint result.
|
|
28
|
+
* Exported for tests; also used to build the `message` field of `--json`
|
|
29
|
+
* output.
|
|
30
|
+
*/
|
|
31
|
+
export function buildMessage(result) {
|
|
32
|
+
const parts = [];
|
|
33
|
+
if (result.deps.broken.length > 0) {
|
|
34
|
+
const targets = result.deps.broken.map((b) => b.target).join(', ');
|
|
35
|
+
parts.push(
|
|
36
|
+
`${result.deps.broken.length} broken dependenc${result.deps.broken.length === 1 ? 'y' : 'ies'}: ${targets}`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
parts.push(`${result.size.effectiveLines} effective lines (${result.size.status})`);
|
|
40
|
+
return parts.join('; ');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function lintCommand(options = {}) {
|
|
44
|
+
const projectPath = process.cwd();
|
|
45
|
+
const result = lintAll(projectPath);
|
|
46
|
+
|
|
47
|
+
if (options.json) {
|
|
48
|
+
const payload = {
|
|
49
|
+
summary: result.summary,
|
|
50
|
+
results: result.results.map((r) => ({
|
|
51
|
+
specId: r.spec,
|
|
52
|
+
status: r.status,
|
|
53
|
+
message: buildMessage(r),
|
|
54
|
+
})),
|
|
55
|
+
};
|
|
56
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
57
|
+
if (result.summary.fail > 0) process.exitCode = 1;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log();
|
|
62
|
+
console.log(chalk.bold('Spec Lint'));
|
|
63
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
64
|
+
|
|
65
|
+
if (!result.specsDirExists) {
|
|
66
|
+
// 查無 spec 目錄 must say so explicitly — an empty { pass: 0, warn: 0,
|
|
67
|
+
// fail: 0 } summary is indistinguishable from "checked, all clean" unless
|
|
68
|
+
// the command says out loud that nothing was scanned.
|
|
69
|
+
console.log(chalk.yellow(` 查無 spec 目錄(./${result.specsDir}/ 不存在)`));
|
|
70
|
+
console.log(chalk.gray(' 沒有東西被掃描——這不代表沒有問題,代表沒有檢查。'));
|
|
71
|
+
console.log();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(chalk.gray(` 掃描 ${result.results.length} 份 spec(./${result.specsDir}/)`));
|
|
76
|
+
console.log();
|
|
77
|
+
|
|
78
|
+
for (const r of result.results) {
|
|
79
|
+
const msg = buildMessage(r);
|
|
80
|
+
if (r.status === 'fail') {
|
|
81
|
+
console.log(chalk.red(` ✗ ${r.spec}: ${msg}`));
|
|
82
|
+
} else if (r.status === 'warn') {
|
|
83
|
+
console.log(chalk.yellow(` ⚠ ${r.spec}: ${msg}`));
|
|
84
|
+
} else {
|
|
85
|
+
console.log(chalk.green(` ✓ ${r.spec}: ${msg}`));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log();
|
|
90
|
+
console.log(
|
|
91
|
+
chalk.gray(` ${result.summary.pass} pass, ${result.summary.warn} warn, ${result.summary.fail} fail`)
|
|
92
|
+
);
|
|
93
|
+
console.log();
|
|
94
|
+
|
|
95
|
+
if (result.summary.fail > 0) process.exitCode = 1;
|
|
96
|
+
}
|
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Reduces cognitive load by guiding users to common workflow paths.
|
|
5
5
|
*
|
|
6
|
+
* ⚠️ Every `cmd` that starts with `uds ` MUST be a command this CLI actually
|
|
7
|
+
* registers, with flags it actually accepts. This is the one place whose entire
|
|
8
|
+
* job is "find the right commands quickly", so a wrong entry here does the exact
|
|
9
|
+
* opposite of what the command exists for.
|
|
10
|
+
*
|
|
11
|
+
* 2026-08-19: four entries pointed at things that do not exist — `uds lint`
|
|
12
|
+
* (twice), `uds sync` (twice, one of them the sole content of a whole
|
|
13
|
+
* workflow), `uds check --spec-size`, and `uds spec create --boost`. Verify with
|
|
14
|
+
* `uds <cmd> --definitely-not-a-real-flag` and look for "unknown command";
|
|
15
|
+
* `uds <cmd> --help` is NOT a valid check — commander prints the general help
|
|
16
|
+
* for an unknown command instead of erroring, so --help reports every name as
|
|
17
|
+
* valid.
|
|
18
|
+
*
|
|
6
19
|
* @module commands/quickstart
|
|
7
20
|
*/
|
|
8
21
|
|
|
@@ -24,11 +37,10 @@ export const WORKFLOWS = [
|
|
|
24
37
|
name: 'Full SDD Spec Flow (Boost)',
|
|
25
38
|
description: 'Complete spec-driven development for complex features',
|
|
26
39
|
steps: [
|
|
27
|
-
{ cmd: '
|
|
28
|
-
{ cmd: 'uds
|
|
40
|
+
{ cmd: '# Use the /sdd skill', desc: 'Full spec lifecycle with review (see `uds spec --help`)' },
|
|
41
|
+
{ cmd: 'uds spec create "your feature" --scope fullstack', desc: 'Create the spec, scoped' },
|
|
29
42
|
{ cmd: 'uds spec confirm SPEC-XXX', desc: 'Confirm after review' },
|
|
30
43
|
{ cmd: '# Implement with /derive → /tdd', desc: 'Use forward derivation and TDD' },
|
|
31
|
-
{ cmd: 'uds sync', desc: 'Export context for session resume' },
|
|
32
44
|
],
|
|
33
45
|
},
|
|
34
46
|
{
|
|
@@ -46,19 +58,10 @@ export const WORKFLOWS = [
|
|
|
46
58
|
description: 'Audit standards compliance and spec quality',
|
|
47
59
|
steps: [
|
|
48
60
|
{ cmd: 'uds check', desc: 'Check standards file integrity' },
|
|
49
|
-
{ cmd: 'uds check --
|
|
50
|
-
{ cmd: 'uds lint', desc: 'Lint specs for AC coverage and dependency validity' },
|
|
61
|
+
{ cmd: 'uds check --i18n', desc: 'Run i18n lint rules across canonical + locale variants' },
|
|
51
62
|
{ cmd: 'uds audit', desc: 'Deep health diagnosis' },
|
|
52
63
|
],
|
|
53
64
|
},
|
|
54
|
-
{
|
|
55
|
-
name: 'Resume Previous Work',
|
|
56
|
-
description: 'Restore context from a previous session',
|
|
57
|
-
steps: [
|
|
58
|
-
{ cmd: 'uds sync', desc: 'Generate context.md from git diff + workflow state' },
|
|
59
|
-
{ cmd: 'cat .workflow-state/context.md', desc: 'Read context in new session' },
|
|
60
|
-
],
|
|
61
|
-
},
|
|
62
65
|
];
|
|
63
66
|
|
|
64
67
|
/**
|
package/src/i18n/messages.js
CHANGED
|
@@ -757,7 +757,7 @@ export const messages = {
|
|
|
757
757
|
actionsAvailable: 'Actions available:',
|
|
758
758
|
restoreOption: '• Run `uds check --restore` to restore all modified/missing files',
|
|
759
759
|
diffOption: '• Run `uds check --diff` to view changes',
|
|
760
|
-
interactiveOption: '• Run `uds check
|
|
760
|
+
interactiveOption: '• Run `uds check` for file-by-file decisions (interactive by default)',
|
|
761
761
|
// Interactive mode
|
|
762
762
|
interactiveMode: 'Interactive Mode:',
|
|
763
763
|
filesNeedAttention: '{count} file(s) need attention.',
|
|
@@ -1991,7 +1991,7 @@ export const messages = {
|
|
|
1991
1991
|
actionsAvailable: '可用操作:',
|
|
1992
1992
|
restoreOption: '• 執行 `uds check --restore` 還原所有已修改/遺失的檔案',
|
|
1993
1993
|
diffOption: '• 執行 `uds check --diff` 檢視變更',
|
|
1994
|
-
interactiveOption: '• 執行 `uds check
|
|
1994
|
+
interactiveOption: '• 執行 `uds check` 逐一處理檔案(預設為互動模式)',
|
|
1995
1995
|
// Interactive mode
|
|
1996
1996
|
interactiveMode: '互動模式:',
|
|
1997
1997
|
filesNeedAttention: '{count} 個檔案需要注意。',
|
|
@@ -3240,7 +3240,7 @@ export const messages = {
|
|
|
3240
3240
|
actionsAvailable: '可用操作:',
|
|
3241
3241
|
restoreOption: '• 执行 `uds check --restore` 恢复所有已修改/缺失文件',
|
|
3242
3242
|
diffOption: '• 执行 `uds check --diff` 查看更改',
|
|
3243
|
-
interactiveOption: '• 执行 `uds check
|
|
3243
|
+
interactiveOption: '• 执行 `uds check` 逐文件决策(默认为交互模式)',
|
|
3244
3244
|
// Interactive mode
|
|
3245
3245
|
interactiveMode: '交互模式:',
|
|
3246
3246
|
filesNeedAttention: '{count} 个文件需要关注。',
|
|
@@ -11,7 +11,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
|
|
11
11
|
import { createHash } from 'crypto';
|
|
12
12
|
import { join, relative } from 'path';
|
|
13
13
|
import { readManifest } from '../core/manifest.js';
|
|
14
|
-
import { computeFileHash, computeIntegrationBlockHash } from '../utils/hasher.js';
|
|
14
|
+
import { computeFileHash, computeIntegrationBlockHash, normalizeLineEndings } from '../utils/hasher.js';
|
|
15
15
|
import { SUPPORTED_AI_TOOLS, UDS_MARKERS } from '../core/constants.js';
|
|
16
16
|
import { getSkillsDirForAgent, getCommandsDirForAgent, getCommandFileExtension } from '../config/ai-agent-paths.js';
|
|
17
17
|
import { getSkillsSourceEntryNames, getAvailableCommandNames } from '../utils/skills-installer.js';
|
|
@@ -322,6 +322,13 @@ function shippedCommandNames() {
|
|
|
322
322
|
* materialising a second array first. The format is the contract; if it changes
|
|
323
323
|
* in one place and not the other every skill reports as changed, which the
|
|
324
324
|
* paired tests in `skill-content-hash.test.js` exist to catch.
|
|
325
|
+
*
|
|
326
|
+
* Content is line-ending normalized before hashing (GitHub issue #155): these
|
|
327
|
+
* are files installed into the adopter's own project, which `git checkout`
|
|
328
|
+
* may have rewritten to CRLF under `core.autocrlf=true` on Windows even
|
|
329
|
+
* though the desired side (`computeSkillContentHash`, reading UDS's own
|
|
330
|
+
* package source) never sees a `\r`. Without matching normalization here,
|
|
331
|
+
* every skill would report as changed on every Windows `uds update`.
|
|
325
332
|
*/
|
|
326
333
|
function hashInstalledSkillDir(dirPath) {
|
|
327
334
|
let entries;
|
|
@@ -346,7 +353,7 @@ function hashInstalledSkillDir(dirPath) {
|
|
|
346
353
|
}
|
|
347
354
|
h.update(name);
|
|
348
355
|
h.update('\0');
|
|
349
|
-
h.update(content);
|
|
356
|
+
h.update(normalizeLineEndings(content));
|
|
350
357
|
h.update('\0');
|
|
351
358
|
}
|
|
352
359
|
return `sha256:${h.digest('hex')}`;
|
|
@@ -437,12 +444,16 @@ function scanCommands(state, projectPath, manifest) {
|
|
|
437
444
|
// Content hash of the installed file, for UDS-managed commands only —
|
|
438
445
|
// same reasoning as skills: an adopter's own command has no desired
|
|
439
446
|
// counterpart to compare against. (XSPEC-382 R7)
|
|
447
|
+
//
|
|
448
|
+
// Line-ending normalized before hashing (GitHub issue #155), matching
|
|
449
|
+
// `computeCommandContentHash` on the desired side — the same CRLF
|
|
450
|
+
// checkout risk `hashInstalledSkillDir` above documents applies here.
|
|
440
451
|
const cmdUdsManaged = shippedCommandNames().has(cmdName);
|
|
441
452
|
let cmdHash = null;
|
|
442
453
|
if (cmdUdsManaged) {
|
|
443
454
|
try {
|
|
444
455
|
cmdHash = `sha256:${createHash('sha256')
|
|
445
|
-
.update(readFileSync(join(cmdsDir, entry.name), 'utf-8'))
|
|
456
|
+
.update(normalizeLineEndings(readFileSync(join(cmdsDir, entry.name), 'utf-8')))
|
|
446
457
|
.digest('hex')}`;
|
|
447
458
|
} catch {
|
|
448
459
|
// Unreadable → no hash. A partial answer would claim a match that
|
package/src/utils/hasher.js
CHANGED
|
@@ -1,22 +1,75 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
|
-
import { readFileSync,
|
|
2
|
+
import { readFileSync, existsSync, readdirSync } from 'fs';
|
|
3
3
|
import { join, relative } from 'path';
|
|
4
4
|
import { UDS_MARKERS } from '../core/constants.js';
|
|
5
5
|
import { resolveIntegrationFile } from '../core/constants.js';
|
|
6
6
|
|
|
7
|
+
// GitHub issue #155. `git config core.autocrlf true` (the common
|
|
8
|
+
// Windows default) rewrites LF to CRLF on checkout. The manifest's stored
|
|
9
|
+
// hashes are computed from the LF bytes git carries in the blob (that is what
|
|
10
|
+
// every non-Windows install reads), so a Windows working tree — content
|
|
11
|
+
// byte-for-byte what `git status` calls clean — hashed to something else
|
|
12
|
+
// entirely. Every `.standards/*` file, every skill/command file, and every
|
|
13
|
+
// CLAUDE.md/AGENTS.md UDS block came back "modified" although nothing had
|
|
14
|
+
// changed. Normalizing line endings before hashing (both when the manifest is
|
|
15
|
+
// written and when it is compared) makes the hash line-ending agnostic, so
|
|
16
|
+
// LF and CRLF checkouts of the same content agree.
|
|
17
|
+
//
|
|
18
|
+
// This intentionally makes "someone converted this file's line endings, text
|
|
19
|
+
// otherwise identical" invisible to `uds check`/`uds update`. That is the
|
|
20
|
+
// point, not a gap: line-ending convention is a checkout artifact, not
|
|
21
|
+
// content a standards library should track as a modification — it is the
|
|
22
|
+
// same normalization `git diff`/`git status` already apply before deciding a
|
|
23
|
+
// tracked file is dirty.
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Heuristic binary-content detector, matching the approach git itself uses:
|
|
27
|
+
* a NUL byte anywhere in the first 8000 bytes marks the buffer as binary.
|
|
28
|
+
* Binary content must never be line-ending-normalized — flipping `\r\n` bytes
|
|
29
|
+
* inside an image or font would silently corrupt the hash rather than make it
|
|
30
|
+
* platform-agnostic, and could hide (or manufacture) a real difference.
|
|
31
|
+
* Every file `uds` currently manages under `.standards/`, skills, and
|
|
32
|
+
* commands is text (`.md`/`.yaml`/`.json`), but this function is the general
|
|
33
|
+
* per-file entry point (also used for arbitrary skill directory contents),
|
|
34
|
+
* so the check stays in place for whatever gets added later.
|
|
35
|
+
* @param {Buffer} buffer
|
|
36
|
+
* @returns {boolean} True if the buffer looks binary
|
|
37
|
+
*/
|
|
38
|
+
export function isBinaryContent(buffer) {
|
|
39
|
+
const sampleSize = Math.min(buffer.length, 8000);
|
|
40
|
+
for (let i = 0; i < sampleSize; i++) {
|
|
41
|
+
if (buffer[i] === 0) return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Normalize CRLF and lone-CR line endings to LF.
|
|
48
|
+
* @param {string} text
|
|
49
|
+
* @returns {string} Normalized text
|
|
50
|
+
*/
|
|
51
|
+
export function normalizeLineEndings(text) {
|
|
52
|
+
return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
7
55
|
/**
|
|
8
|
-
* Compute SHA-256 hash for a file
|
|
56
|
+
* Compute SHA-256 hash for a file, normalizing line endings first (unless the
|
|
57
|
+
* file is binary — see `isBinaryContent`). `size` is the byte length of the
|
|
58
|
+
* normalized content, not the raw on-disk size, so the two stay consistent
|
|
59
|
+
* with each other for the quick-reject check in `compareFileHash`.
|
|
9
60
|
* @param {string} filePath - Absolute file path
|
|
10
61
|
* @returns {Object|null} { hash, size } or null if file doesn't exist
|
|
11
62
|
*/
|
|
12
63
|
export function computeFileHash(filePath) {
|
|
13
64
|
try {
|
|
14
|
-
const
|
|
65
|
+
const raw = readFileSync(filePath);
|
|
66
|
+
const content = isBinaryContent(raw)
|
|
67
|
+
? raw
|
|
68
|
+
: Buffer.from(normalizeLineEndings(raw.toString('utf-8')), 'utf-8');
|
|
15
69
|
const hash = createHash('sha256').update(content).digest('hex');
|
|
16
|
-
const stats = statSync(filePath);
|
|
17
70
|
return {
|
|
18
71
|
hash: `sha256:${hash}`,
|
|
19
|
-
size:
|
|
72
|
+
size: content.length
|
|
20
73
|
};
|
|
21
74
|
} catch {
|
|
22
75
|
return null;
|
|
@@ -276,7 +329,10 @@ function extractBlockContent(content, format) {
|
|
|
276
329
|
*/
|
|
277
330
|
export function computeIntegrationBlockHash(filePath) {
|
|
278
331
|
try {
|
|
279
|
-
|
|
332
|
+
// CLAUDE.md / AGENTS.md are always text — no binary check needed here,
|
|
333
|
+
// unlike `computeFileHash`.
|
|
334
|
+
const rawContent = readFileSync(filePath, 'utf-8');
|
|
335
|
+
const content = normalizeLineEndings(rawContent);
|
|
280
336
|
const format = detectFormat(filePath);
|
|
281
337
|
const { blockContent } = extractBlockContent(content, format);
|
|
282
338
|
|
|
@@ -287,13 +343,12 @@ export function computeIntegrationBlockHash(filePath) {
|
|
|
287
343
|
|
|
288
344
|
const blockHash = createHash('sha256').update(blockContent).digest('hex');
|
|
289
345
|
const fullHash = createHash('sha256').update(content).digest('hex');
|
|
290
|
-
const stats = statSync(filePath);
|
|
291
346
|
|
|
292
347
|
return {
|
|
293
348
|
blockHash: `sha256:${blockHash}`,
|
|
294
349
|
blockSize: Buffer.byteLength(blockContent, 'utf-8'),
|
|
295
350
|
fullHash: `sha256:${fullHash}`,
|
|
296
|
-
fullSize:
|
|
351
|
+
fullSize: Buffer.byteLength(content, 'utf-8')
|
|
297
352
|
};
|
|
298
353
|
} catch {
|
|
299
354
|
return null;
|
|
@@ -3181,8 +3181,8 @@ export function parseStandardsIndexCount(content) {
|
|
|
3181
3181
|
export function wrapWithMarkers(content, format) {
|
|
3182
3182
|
const markers = UDS_MARKERS[format] || UDS_MARKERS.markdown;
|
|
3183
3183
|
const warning = format === 'plaintext'
|
|
3184
|
-
? '# WARNING: This block is managed by UDS (universal-dev-standards). DO NOT manually edit. Use \'npx uds
|
|
3185
|
-
: '<!-- WARNING: This block is managed by UDS (universal-dev-standards). DO NOT manually edit. Use \'npx uds
|
|
3184
|
+
? '# WARNING: This block is managed by UDS (universal-dev-standards). DO NOT manually edit. Use \'npx uds init\' or \'npx uds update\' to modify.'
|
|
3185
|
+
: '<!-- WARNING: This block is managed by UDS (universal-dev-standards). DO NOT manually edit. Use \'npx uds init\' or \'npx uds update\' to modify. -->';
|
|
3186
3186
|
// 冪等:warning 位於 markers **內部**,而 extractMarkedContent 取出的內容也含它,
|
|
3187
3187
|
// 於是重新包裝會疊出第二份(dev-platform CLAUDE.md 實測 178/179 兩行完全相同)。
|
|
3188
3188
|
// 這裡先剝掉內容開頭既有的 warning,不論上游哪條路徑造成都能修掉。
|
|
@@ -3362,7 +3362,25 @@ export function generateAgentsMdSummary(config = {}) {
|
|
|
3362
3362
|
lines.push('# AGENTS.md');
|
|
3363
3363
|
lines.push('');
|
|
3364
3364
|
lines.push('> Auto-generated by [Universal Dev Standards (UDS)](https://github.com/AsiaOstrich/universal-dev-standards).');
|
|
3365
|
-
lines.push('>
|
|
3365
|
+
lines.push('>');
|
|
3366
|
+
// Say it as an instruction, and say what this file is NOT.
|
|
3367
|
+
//
|
|
3368
|
+
// The previous line — "Full standards available in the `.standards/` directory" — was a
|
|
3369
|
+
// description, and a description asks for nothing. Measured 2026-07-23: Codex read this
|
|
3370
|
+
// file, listed the 65 standards it indexes, and opened none of them, so the rules had the
|
|
3371
|
+
// same effect as not installing UDS at all. Measured again 2026-08-18 on a fresh
|
|
3372
|
+
// `uds init -y`: the generated file is 5,667 bytes containing 69 filename references and
|
|
3373
|
+
// **zero rule statements**. That is not a bug in the generator — 143 `.ai.yaml` files come
|
|
3374
|
+
// to roughly 248k tokens, so inlining them is not possible — which is exactly why the file
|
|
3375
|
+
// has to be explicit that it is an index and that the rules are elsewhere.
|
|
3376
|
+
//
|
|
3377
|
+
// This does not prove the rules get read; only XSPEC-357's P7 probe can measure that, and
|
|
3378
|
+
// it is not built yet. It removes the one thing that was certainly wrong: a file that read
|
|
3379
|
+
// as though it carried the standards when it carried their filenames. (XSPEC-357 R7)
|
|
3380
|
+
lines.push('> **This file is an index, not the standards.** The rules are NOT reproduced here.');
|
|
3381
|
+
lines.push('> Before acting on anything below, open the relevant file under `.standards/`');
|
|
3382
|
+
lines.push('> and follow its contents. Working from this summary alone means working');
|
|
3383
|
+
lines.push('> without the standards.');
|
|
3366
3384
|
lines.push('');
|
|
3367
3385
|
|
|
3368
3386
|
// Build & Test Commands (auto-detect project type)
|