universal-dev-standards 6.1.0 → 6.2.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/bundled/locales/zh-CN/CHANGELOG.md +46 -4
- package/bundled/locales/zh-CN/README.md +75 -33
- package/bundled/locales/zh-CN/SECURITY.md +1 -1
- package/bundled/locales/zh-CN/core/behavior-snapshot.md +2 -2
- package/bundled/locales/zh-CN/core/data-migration-testing.md +2 -2
- package/bundled/locales/zh-CN/docs/CHEATSHEET.md +4 -4
- package/bundled/locales/zh-CN/docs/CLI-INIT-OPTIONS.md +9 -3
- package/bundled/locales/zh-CN/docs/FEATURE-REFERENCE.md +10 -11
- package/bundled/locales/zh-CN/docs/USER-MANUAL.md +40 -21
- package/bundled/locales/zh-CN/integrations/gemini-cli/README.md +12 -0
- package/bundled/locales/zh-CN/skills/ac-coverage/SKILL.md +11 -5
- package/bundled/locales/zh-CN/skills/adr-assistant/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/commands/brainstorm.md +2 -2
- package/bundled/locales/zh-CN/skills/commit-standards/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/contract-test-assistant/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/deploy-assistant/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/dev-methodology/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/dev-workflow-guide/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/journey-test-assistant/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/knowledge-graph/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/knowledge-graph/guide.md +2 -2
- package/bundled/locales/zh-CN/skills/migration-assistant/SKILL.md +188 -2
- package/bundled/locales/zh-CN/skills/observability-assistant/guide.md +2 -2
- package/bundled/locales/zh-CN/skills/orchestrate/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/plan/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/push/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/retrospective-assistant/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/reverse-engineer/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/runbook-assistant/guide.md +2 -2
- package/bundled/locales/zh-CN/skills/skill-builder/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/slo-assistant/guide.md +2 -2
- package/bundled/locales/zh-CN/skills/spec-derivation/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/spec-driven-dev/SKILL.md +40 -2
- package/bundled/locales/zh-CN/skills/sweep/SKILL.md +2 -2
- package/bundled/locales/zh-CN/skills/testing-guide/SKILL.md +2 -2
- package/bundled/locales/zh-TW/CHANGELOG.md +47 -4
- package/bundled/locales/zh-TW/README.md +75 -33
- package/bundled/locales/zh-TW/SECURITY.md +1 -1
- package/bundled/locales/zh-TW/core/audit-trail.md +2 -2
- package/bundled/locales/zh-TW/core/behavior-snapshot.md +2 -2
- package/bundled/locales/zh-TW/core/browser-compatibility-standards.md +18 -7
- package/bundled/locales/zh-TW/docs/CHEATSHEET.md +4 -4
- package/bundled/locales/zh-TW/docs/CLI-INIT-OPTIONS.md +9 -3
- package/bundled/locales/zh-TW/docs/FEATURE-REFERENCE.md +10 -11
- package/bundled/locales/zh-TW/docs/USER-MANUAL.md +40 -21
- package/bundled/locales/zh-TW/integrations/gemini-cli/README.md +12 -0
- package/bundled/skills/commands/journey-test.md +80 -6
- package/bundled/skills/commands/skill-builder.md +75 -6
- package/package.json +3 -3
- package/src/commands/check.js +77 -20
- package/src/commands/config.js +9 -0
- package/src/commands/init.js +15 -1
- package/src/commands/release.js +1 -1
- package/src/commands/update.js +82 -31
- package/src/config/ai-agent-paths.js +47 -7
- package/src/core/constants.js +61 -1
- package/src/core/manifest.js +56 -0
- package/src/flow/flow-parser.js +1 -1
- package/src/flow/gate-loader.js +1 -1
- package/src/i18n/messages.js +12 -6
- package/src/installers/standards-installer.js +10 -20
- package/src/reconciler/actual-state-scanner.js +118 -43
- package/src/reconciler/desired-state-calculator.js +157 -43
- package/src/reconciler/diff-engine.js +7 -2
- package/src/reconciler/manifest-migrator.js +5 -2
- package/src/reconciler/plan-executor.js +53 -14
- package/src/uninstallers/integration-uninstaller.js +7 -1
- package/src/utils/config-loader.js +1 -1
- package/src/utils/config-manager.js +1 -1
- package/src/utils/github.js +5 -1
- package/src/utils/hasher.js +7 -4
- package/src/utils/integration-generator.js +121 -78
- package/src/utils/registry.js +39 -0
- package/src/utils/skills-installer.js +49 -34
- package/src/utils/skills-source.js +51 -0
- package/src/utils/standard-fixer.js +1 -1
- package/src/utils/standard-validator.js +1 -1
- package/standards-registry.json +21 -11
|
@@ -33,10 +33,79 @@ It is backed by the `skill-builder` skill (`skills/skill-builder/SKILL.md`).
|
|
|
33
33
|
| Complex | any of the above is exceeded | Create an XSPEC first (`/sdd`), then build |
|
|
34
34
|
| Delta | modifying an existing Skill | Append `## MODIFIED` / `## ADDED`, bump version |
|
|
35
35
|
|
|
36
|
-
##
|
|
36
|
+
## AI Agent Behavior | AI 代理行為
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
> Follows [AI Command Behavior Standards](../../core/ai-command-behavior.md)
|
|
39
|
+
|
|
40
|
+
### Entry Router | 進入路由
|
|
41
|
+
|
|
42
|
+
| Input | AI Action |
|
|
43
|
+
|-------|-----------|
|
|
44
|
+
| `/skill-builder` | 讀取 `SKILL-CANDIDATES.md`:有已達 3 次的候選 → 列出供選擇;無 → 詢問重複流程的描述 |
|
|
45
|
+
| `/skill-builder <process description>` | 以該描述進入 Step 1,直接開始擷取流程 |
|
|
46
|
+
|
|
47
|
+
### Interaction Script | 互動腳本
|
|
48
|
+
|
|
49
|
+
先讀取 `skills/skill-builder/SKILL.md`,取得完整決策樹、放置規則、工作流程與輸出檢查表。
|
|
50
|
+
|
|
51
|
+
#### Step 1–2: 擷取流程並登記候選
|
|
52
|
+
|
|
53
|
+
1. 擷取重複序列:步驟與順序、目前手動執行過幾次、觸及哪些工具或檔案
|
|
54
|
+
2. 開啟專案的 `SKILL-CANDIDATES.md`(首次則從 `templates/SKILL-CANDIDATES.md` 複製)
|
|
55
|
+
|
|
56
|
+
**Decision: 觸發次數**
|
|
57
|
+
- IF 尚未登記 → 新增列,填入目前次數
|
|
58
|
+
- IF 已登記 → 次數 +1
|
|
59
|
+
- IF 次數 < 3 → **告知尚未達觸發門檻,登記後停止,不建立 SKILL.md**
|
|
60
|
+
- IF 次數 ≥ 3 → 標記觸發 ✅,繼續 Step 3
|
|
61
|
+
|
|
62
|
+
🛑 **STOP**: 次數未達 3 時停止於此,等待使用者明確要求才破例
|
|
63
|
+
|
|
64
|
+
#### Step 3: 路徑選擇
|
|
65
|
+
|
|
66
|
+
回答 4 個判斷問題,判定路徑:
|
|
67
|
+
|
|
68
|
+
- IF ≤7 步、無分支、<3 個標準、無原始碼產出 → **Simple**(Step 4a)
|
|
69
|
+
- IF 上述任一超出 → **Complex**(Step 4b)
|
|
70
|
+
- IF 修改既有 Skill → **Delta**(Step 4c)
|
|
71
|
+
|
|
72
|
+
🛑 **STOP**: 展示判定結果與理由後等待使用者確認路徑
|
|
73
|
+
|
|
74
|
+
#### Step 4: 依路徑分流
|
|
75
|
+
|
|
76
|
+
- **4a Simple** — 以 `templates/SKILL-BRIEF-TEMPLATE.md` 填寫 Brief:觸發情境、3–7 個核心步驟、2–3 條 AC、明確的 out of scope
|
|
77
|
+
- **4b Complex** — 執行 `/sdd` 建立 XSPEC;**XSPEC 未 Approved 前不進入 Step 5**
|
|
78
|
+
- **4c Delta** — 標定既有 SKILL.md 受影響章節,於檔末加 `## MODIFIED Requirements` 或 `## ADDED Requirements`
|
|
79
|
+
|
|
80
|
+
#### Step 5–7: 產出與登記
|
|
81
|
+
|
|
82
|
+
1. **放置決策**:步驟引用專案特有路徑(如 `TECH-RADAR.md`、`DEC-*.md`)→ `{project}/.claude/skills/`;步驟為通用 → UDS `skills/{name}/` **並建立 zh-TW locale**
|
|
83
|
+
2. 由 Brief 或 XSPEC 生成 SKILL.md,核對 frontmatter:`name`、`scope`、`description`、`allowed-tools`
|
|
84
|
+
3. 回填 `SKILL-CANDIDATES.md`:觸發 ✅、Skill 欄填入
|
|
85
|
+
4. 依 skill 的 commit 格式提交
|
|
86
|
+
|
|
87
|
+
🛑 **STOP**: commit 前展示輸出檢查表結果,等待使用者確認
|
|
88
|
+
|
|
89
|
+
### Stop Points | 停止點
|
|
90
|
+
|
|
91
|
+
| Stop Point | 等待內容 |
|
|
92
|
+
|-----------|---------|
|
|
93
|
+
| 觸發次數 < 3 | 使用者是否明確要求破例建立 |
|
|
94
|
+
| 路徑判定後 | 使用者確認 Simple / Complex / Delta |
|
|
95
|
+
| Complex 路徑 | XSPEC 經 `/sdd` 建立並 Approved |
|
|
96
|
+
| commit 前 | 使用者確認輸出檢查表 |
|
|
97
|
+
|
|
98
|
+
### Error Handling | 錯誤處理
|
|
99
|
+
|
|
100
|
+
| Error Condition | AI Action |
|
|
101
|
+
|-----------------|-----------|
|
|
102
|
+
| 未提供流程描述且無候選 | 詢問重複流程內容,**不得臆造流程** |
|
|
103
|
+
| `SKILL-CANDIDATES.md` 不存在 | 從 `templates/SKILL-CANDIDATES.md` 複製後繼續 |
|
|
104
|
+
| 觸發次數不足 3 次 | 登記候選後停止,說明門檻理由 |
|
|
105
|
+
| 同名 Skill 已存在 | 改走 Delta 路徑,不覆蓋既有 SKILL.md |
|
|
106
|
+
| 放置位置無法判定 | 列出「專案特有 vs 通用」判準請使用者裁定 |
|
|
107
|
+
|
|
108
|
+
## References | 參考
|
|
109
|
+
|
|
110
|
+
- [Skill Builder Skill](../skill-builder/SKILL.md)
|
|
111
|
+
- Related: [/sdd](./sdd.md) (Complex path creates an XSPEC first)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-dev-standards",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"description": "CLI tool for adopting Universal Development Standards",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"documentation",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"@inquirer/prompts": "^8.4.2",
|
|
63
63
|
"chalk": "^5.3.0",
|
|
64
64
|
"commander": "^15.0.0",
|
|
65
|
-
"js-yaml": "^
|
|
65
|
+
"js-yaml": "^5.2.1",
|
|
66
66
|
"ora": "^9.4.0"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"@vitest/coverage-v8": "^4.1.5",
|
|
71
71
|
"ajv": "^8.20.0",
|
|
72
72
|
"ajv-formats": "^3.0.1",
|
|
73
|
-
"eslint": "10.
|
|
73
|
+
"eslint": "10.7.0",
|
|
74
74
|
"glob": "^13.0.1",
|
|
75
75
|
"globals": "17.7.0",
|
|
76
76
|
"husky": "^9.1.7",
|
package/src/commands/check.js
CHANGED
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
parseReferences,
|
|
31
31
|
compareStandardsWithReferences
|
|
32
32
|
} from '../utils/reference-sync.js';
|
|
33
|
-
import { extractMarkedContent, getToolFilePath } from '../utils/integration-generator.js';
|
|
33
|
+
import { extractMarkedContent, getToolFilePath, parseStandardsIndexCount } from '../utils/integration-generator.js';
|
|
34
34
|
import { getToolFormat } from '../core/constants.js';
|
|
35
35
|
import { checkForUpdates } from '../utils/npm-registry.js';
|
|
36
36
|
import { writeUpdateCache } from '../utils/update-checker.js';
|
|
@@ -39,17 +39,15 @@ import { WorkflowGate } from '../utils/workflow-gate.js';
|
|
|
39
39
|
import { t, setLanguage, isLanguageExplicitlySet } from '../i18n/messages.js';
|
|
40
40
|
import { guardAgainstSelfAdoption } from '../utils/detect-self-adoption.js';
|
|
41
41
|
import { lintAll as lintI18nAll, partitionFindings as partitionI18nFindings } from '../lint/i18n.js';
|
|
42
|
+
import { resolveIntegrationFile } from '../core/constants.js';
|
|
42
43
|
|
|
43
44
|
/**
|
|
44
45
|
* Display the summary of file integrity status
|
|
45
46
|
*/
|
|
46
47
|
function displayFileIntegritySummary(fileStatus, msg) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
48
|
+
// XSPEC-342 R4(安靜通過):不再逐檔列印「✓ 未變更」——實測佔 `uds check` 輸出約 70%
|
|
49
|
+
// (121 行中 85 行),把真正該讀的訊息(如標準落後)淹沒,也讓輸出大到被 agent 截斷。
|
|
50
|
+
// 未變更數仍保留在下方 summary 一行;此處只列印「需要注意」的檔(modified/missing/noHash)。
|
|
53
51
|
if (fileStatus.modified.length > 0) {
|
|
54
52
|
for (const file of fileStatus.modified) {
|
|
55
53
|
console.log(chalk.yellow(` ⚠ ${file} (${msg.modified})`));
|
|
@@ -164,7 +162,9 @@ function performFileIntegrityCheck(projectPath, manifest, msg) {
|
|
|
164
162
|
}
|
|
165
163
|
|
|
166
164
|
// Check integrations
|
|
167
|
-
for (const
|
|
165
|
+
for (const intEntry of manifest.integrations) {
|
|
166
|
+
// 兩種形狀都要能解出路徑(XSPEC-343 R1)
|
|
167
|
+
const int = resolveIntegrationFile(intEntry) || intEntry;
|
|
168
168
|
const filePath = join(projectPath, int);
|
|
169
169
|
if (existsSync(filePath)) {
|
|
170
170
|
fileStatus.noHash.push(int);
|
|
@@ -221,7 +221,7 @@ function initializeCheckContext(projectPath) {
|
|
|
221
221
|
/**
|
|
222
222
|
* Display standards adoption status and update information
|
|
223
223
|
*/
|
|
224
|
-
function displayAdoptionStatus(manifest, msg, common, repoInfo) {
|
|
224
|
+
function displayAdoptionStatus(manifest, msg, common, repoInfo, standardsUpdate) {
|
|
225
225
|
console.log(chalk.green(msg.standardsInitialized));
|
|
226
226
|
console.log();
|
|
227
227
|
console.log(chalk.cyan(msg.adoptionStatus));
|
|
@@ -229,9 +229,15 @@ function displayAdoptionStatus(manifest, msg, common, repoInfo) {
|
|
|
229
229
|
console.log(chalk.gray(` ${common.version}: ${manifest.upstream.version}`));
|
|
230
230
|
console.log();
|
|
231
231
|
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
|
|
232
|
+
// XSPEC-342 R1:判斷「已裝標準」是否落後 **npm 最新版**,而非 CLI 自己 bundled 的副本。
|
|
233
|
+
// 舊 bug:拿 manifest.upstream.version 比 repoInfo.standards.version(=跑這支 CLI 帶的
|
|
234
|
+
// 標準副本)。CLI 一舊,bundled < npm 最新,就會吐出「6.1.0 → 5.12.1」這種倒退訊息,
|
|
235
|
+
// 且永遠說不出「你的標準過期了」。standardsUpdate = checkForUpdates(已裝標準版本):
|
|
236
|
+
// available = 已裝 < npm 最新。離線時為 null → 靜默略過(不誤用 bundled 版本比對)。
|
|
237
|
+
if (standardsUpdate && !standardsUpdate.offline && standardsUpdate.available) {
|
|
238
|
+
console.log(chalk.yellow(msg.updateAvailable
|
|
239
|
+
.replace('{current}', manifest.upstream.version)
|
|
240
|
+
.replace('{latest}', standardsUpdate.latestVersion)));
|
|
235
241
|
console.log(chalk.gray(` ${msg.runUpdate}`));
|
|
236
242
|
console.log();
|
|
237
243
|
}
|
|
@@ -308,10 +314,17 @@ export async function checkCommand(options = {}) {
|
|
|
308
314
|
console.log(chalk.bold(msg.title));
|
|
309
315
|
console.log(chalk.gray('─'.repeat(50)));
|
|
310
316
|
|
|
311
|
-
//
|
|
312
|
-
|
|
317
|
+
// XSPEC-342 R1:先問 npm 最新,判斷「已裝標準」是否落後(離線則跳過,不誤用 CLI bundled 版本)。
|
|
318
|
+
// fetchLatestVersion 有 60s 模組級快取,故此處與下方 checkCliVersion 共兩次呼叫只打一次網路。
|
|
319
|
+
let standardsUpdate = null;
|
|
320
|
+
if (!options.offline) {
|
|
321
|
+
standardsUpdate = await checkForUpdates(manifest.upstream.version, {
|
|
322
|
+
checkBeta: manifest.upstream.version.includes('-')
|
|
323
|
+
});
|
|
324
|
+
}
|
|
313
325
|
|
|
314
|
-
//
|
|
326
|
+
// Display adoption info
|
|
327
|
+
displayAdoptionStatus(manifest, msg, common, repoInfo, standardsUpdate);
|
|
315
328
|
|
|
316
329
|
// Check for CLI updates from npm registry (unless --offline)
|
|
317
330
|
if (!options.offline) {
|
|
@@ -717,7 +730,9 @@ function removeFromManifest(manifest, relativePath) {
|
|
|
717
730
|
const fileName = basename(relativePath);
|
|
718
731
|
manifest.standards = manifest.standards.filter(s => !s.endsWith(fileName));
|
|
719
732
|
manifest.extensions = manifest.extensions.filter(e => typeof e !== 'string' || !e.endsWith(fileName));
|
|
720
|
-
manifest.integrations = manifest.integrations.filter(
|
|
733
|
+
manifest.integrations = manifest.integrations.filter(
|
|
734
|
+
i => (resolveIntegrationFile(i) || i) !== relativePath
|
|
735
|
+
);
|
|
721
736
|
|
|
722
737
|
// Remove from integrationBlockHashes
|
|
723
738
|
if (manifest.integrationBlockHashes && manifest.integrationBlockHashes[relativePath]) {
|
|
@@ -751,7 +766,7 @@ export function getSourcePathFromRelative(manifest, relativePath) {
|
|
|
751
766
|
}
|
|
752
767
|
|
|
753
768
|
// Check integrations - these might need special handling
|
|
754
|
-
if (manifest.integrations.
|
|
769
|
+
if (manifest.integrations.some(i => (resolveIntegrationFile(i) || i) === relativePath)) {
|
|
755
770
|
// Integration files have different source paths
|
|
756
771
|
const integrationMappings = {
|
|
757
772
|
'.cursorrules': 'integrations/cursor/.cursorrules',
|
|
@@ -821,7 +836,8 @@ async function migrateToHashBasedTracking(projectPath, manifest) {
|
|
|
821
836
|
}
|
|
822
837
|
|
|
823
838
|
// Process integrations
|
|
824
|
-
for (const
|
|
839
|
+
for (const intEntry of manifest.integrations) {
|
|
840
|
+
const int = resolveIntegrationFile(intEntry) || intEntry;
|
|
825
841
|
const fullPath = join(projectPath, int);
|
|
826
842
|
|
|
827
843
|
const hashInfo = computeFileHash(fullPath);
|
|
@@ -1138,6 +1154,27 @@ function checkIntegrationFiles(manifest, projectPath, msg) {
|
|
|
1138
1154
|
// Report status - use all installed standards as the total
|
|
1139
1155
|
const totalTrackable = standardsFiles.length;
|
|
1140
1156
|
|
|
1157
|
+
// Index-mode blocks deliberately do NOT enumerate standards (XSPEC-358 R1);
|
|
1158
|
+
// grepping for each name reports a false failure and points at `uds update`,
|
|
1159
|
+
// which regenerates the identical block. Assert the declared count instead.
|
|
1160
|
+
const declaredCount = parseStandardsIndexCount(content);
|
|
1161
|
+
if (declaredCount !== null) {
|
|
1162
|
+
if (declaredCount === totalTrackable) {
|
|
1163
|
+
console.log(chalk.green(` ✓ ${toolFile}:`));
|
|
1164
|
+
console.log(chalk.gray(` ${msg.standardsIndexPresent}`));
|
|
1165
|
+
console.log(chalk.gray(` ${msg.standardsIndexCount
|
|
1166
|
+
? msg.standardsIndexCount.replace('{count}', declaredCount)
|
|
1167
|
+
: `Index declares ${declaredCount} standards (matches manifest)`}`));
|
|
1168
|
+
} else {
|
|
1169
|
+
console.log(chalk.yellow(` ⚠ ${toolFile}:`));
|
|
1170
|
+
console.log(chalk.yellow(` ${msg.standardsIndexCountMismatch
|
|
1171
|
+
? msg.standardsIndexCountMismatch.replace('{declared}', declaredCount).replace('{actual}', totalTrackable)
|
|
1172
|
+
: `Index declares ${declaredCount} standards but the manifest has ${totalTrackable}`}`));
|
|
1173
|
+
hasIssues = true;
|
|
1174
|
+
}
|
|
1175
|
+
continue;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1141
1178
|
if (hasStandardsIndex && missingStandards.length === 0) {
|
|
1142
1179
|
console.log(chalk.green(` ✓ ${toolFile}:`));
|
|
1143
1180
|
console.log(chalk.gray(` ${msg.standardsIndexPresent}`));
|
|
@@ -1209,6 +1246,25 @@ function checkAgentsMdSync(manifest, projectPath, msg) {
|
|
|
1209
1246
|
const content = readFileSync(agentsMdPath, 'utf-8');
|
|
1210
1247
|
const installedStandards = (manifest.standards || []).map(s => basename(s));
|
|
1211
1248
|
|
|
1249
|
+
// Same as the AI-tool integration check above: an index-mode block declares a
|
|
1250
|
+
// count instead of listing names, so the name grep below cannot apply to it.
|
|
1251
|
+
const declaredCount = parseStandardsIndexCount(content);
|
|
1252
|
+
if (declaredCount !== null) {
|
|
1253
|
+
const actual = installedStandards.length;
|
|
1254
|
+
if (declaredCount === actual) {
|
|
1255
|
+
console.log(chalk.green(` ✓ AGENTS.md ${msg.standardsSynced || 'standards synced'} (${declaredCount})`));
|
|
1256
|
+
} else {
|
|
1257
|
+
console.log(chalk.yellow(` ⚠ AGENTS.md ${msg.standardsOutOfSync || 'standards out of sync'} (${declaredCount} != ${actual})`));
|
|
1258
|
+
console.log(chalk.gray(` ${msg.runUpdateToSync || 'Run "uds update" to sync'}`));
|
|
1259
|
+
}
|
|
1260
|
+
const idxLineCount = content.split('\n').length;
|
|
1261
|
+
if (idxLineCount > 150) {
|
|
1262
|
+
console.log(chalk.yellow(` ⚠ AGENTS.md ${msg.exceedsLineLimit || 'exceeds 150 line limit'} (${idxLineCount} lines)`));
|
|
1263
|
+
}
|
|
1264
|
+
console.log();
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1212
1268
|
// Check standards listed in AGENTS.md vs manifest
|
|
1213
1269
|
const aiYamlStandards = installedStandards.filter(s => s.endsWith('.ai.yaml'));
|
|
1214
1270
|
let listedCount = 0;
|
|
@@ -1254,7 +1310,8 @@ function checkReferenceSync(manifest, projectPath, msg) {
|
|
|
1254
1310
|
|
|
1255
1311
|
// Pre-scan: collect results for files that have references
|
|
1256
1312
|
const results = [];
|
|
1257
|
-
for (const
|
|
1313
|
+
for (const integrationEntry of manifest.integrations) {
|
|
1314
|
+
const integrationPath = resolveIntegrationFile(integrationEntry) || integrationEntry;
|
|
1258
1315
|
const fullPath = join(projectPath, integrationPath);
|
|
1259
1316
|
|
|
1260
1317
|
if (!existsSync(fullPath)) continue;
|
|
@@ -1690,7 +1747,7 @@ function getFileStatusCounts(manifest, projectPath) {
|
|
|
1690
1747
|
const allFiles = [
|
|
1691
1748
|
...manifest.standards.map(s => `.standards/${basename(s)}`),
|
|
1692
1749
|
...manifest.extensions.filter(e => typeof e === 'string').map(e => `.standards/${basename(e)}`),
|
|
1693
|
-
...manifest.integrations
|
|
1750
|
+
...manifest.integrations.map(i => resolveIntegrationFile(i) || i)
|
|
1694
1751
|
];
|
|
1695
1752
|
for (const relativePath of allFiles) {
|
|
1696
1753
|
const fullPath = join(projectPath, relativePath);
|
package/src/commands/config.js
CHANGED
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
} from '../utils/integration-generator.js';
|
|
50
50
|
import { getMarketplaceSkillsInfo } from '../utils/github.js';
|
|
51
51
|
import { regenerateIntegrations } from './update.js';
|
|
52
|
+
import { mergeInstalledNames } from '../core/manifest.js';
|
|
52
53
|
|
|
53
54
|
/**
|
|
54
55
|
* Get localized message with fallback (for config-specific keys)
|
|
@@ -995,6 +996,7 @@ export async function runProjectConfiguration(options) {
|
|
|
995
996
|
manifest.skills.installations.push(inst);
|
|
996
997
|
}
|
|
997
998
|
}
|
|
999
|
+
manifest.skills.names = mergeInstalledNames(manifest.skills.names, skillResult);
|
|
998
1000
|
if (skillResult.allFileHashes) {
|
|
999
1001
|
manifest.skillHashes = { ...(manifest.skillHashes || {}), ...skillResult.allFileHashes };
|
|
1000
1002
|
}
|
|
@@ -1031,6 +1033,7 @@ export async function runProjectConfiguration(options) {
|
|
|
1031
1033
|
manifest.commands.installations.push(inst);
|
|
1032
1034
|
}
|
|
1033
1035
|
}
|
|
1036
|
+
manifest.commands.names = mergeInstalledNames(manifest.commands.names, cmdResult);
|
|
1034
1037
|
if (cmdResult.allFileHashes) {
|
|
1035
1038
|
manifest.commandHashes = { ...(manifest.commandHashes || {}), ...cmdResult.allFileHashes };
|
|
1036
1039
|
}
|
|
@@ -1234,6 +1237,7 @@ async function handleSkillsConfiguration(manifest, projectPath, msgObj, common,
|
|
|
1234
1237
|
} else {
|
|
1235
1238
|
manifest.skills.installations.push(installations[0]);
|
|
1236
1239
|
}
|
|
1240
|
+
manifest.skills.names = mergeInstalledNames(manifest.skills.names, result);
|
|
1237
1241
|
writeManifest(manifest, projectPath);
|
|
1238
1242
|
return;
|
|
1239
1243
|
}
|
|
@@ -1368,6 +1372,7 @@ async function handleSkillsConfiguration(manifest, projectPath, msgObj, common,
|
|
|
1368
1372
|
// Update manifest - clear declined status for installed tools
|
|
1369
1373
|
manifest.skills = manifest.skills || {};
|
|
1370
1374
|
manifest.skills.installations = installations;
|
|
1375
|
+
manifest.skills.names = mergeInstalledNames(manifest.skills.names, result);
|
|
1371
1376
|
if (manifest.declinedFeatures?.skills) {
|
|
1372
1377
|
manifest.declinedFeatures.skills = manifest.declinedFeatures.skills.filter(
|
|
1373
1378
|
tool => !declinedToolsWithSupport.includes(tool)
|
|
@@ -1402,6 +1407,7 @@ async function handleSkillsConfiguration(manifest, projectPath, msgObj, common,
|
|
|
1402
1407
|
// Update manifest
|
|
1403
1408
|
manifest.skills = manifest.skills || {};
|
|
1404
1409
|
manifest.skills.installations = installations;
|
|
1410
|
+
manifest.skills.names = mergeInstalledNames(manifest.skills.names, result);
|
|
1405
1411
|
writeManifest(manifest, projectPath);
|
|
1406
1412
|
}
|
|
1407
1413
|
|
|
@@ -1465,6 +1471,7 @@ async function handleCommandsConfiguration(manifest, projectPath, msgObj, common
|
|
|
1465
1471
|
} else {
|
|
1466
1472
|
manifest.commands.installations.push(installations[0]);
|
|
1467
1473
|
}
|
|
1474
|
+
manifest.commands.names = mergeInstalledNames(manifest.commands.names, result);
|
|
1468
1475
|
writeManifest(manifest, projectPath);
|
|
1469
1476
|
return;
|
|
1470
1477
|
}
|
|
@@ -1566,6 +1573,7 @@ async function handleCommandsConfiguration(manifest, projectPath, msgObj, common
|
|
|
1566
1573
|
// Update manifest - clear declined status for installed tools
|
|
1567
1574
|
manifest.commands = manifest.commands || {};
|
|
1568
1575
|
manifest.commands.installations = declinedCommandsWithSupport;
|
|
1576
|
+
manifest.commands.names = mergeInstalledNames(manifest.commands.names, result);
|
|
1569
1577
|
if (manifest.declinedFeatures?.commands) {
|
|
1570
1578
|
manifest.declinedFeatures.commands = manifest.declinedFeatures.commands.filter(
|
|
1571
1579
|
tool => !declinedCommandsWithSupport.includes(tool)
|
|
@@ -1601,5 +1609,6 @@ async function handleCommandsConfiguration(manifest, projectPath, msgObj, common
|
|
|
1601
1609
|
// Update manifest
|
|
1602
1610
|
manifest.commands = manifest.commands || {};
|
|
1603
1611
|
manifest.commands.installations = selectedAgents;
|
|
1612
|
+
manifest.commands.names = mergeInstalledNames(manifest.commands.names, result);
|
|
1604
1613
|
writeManifest(manifest, projectPath);
|
|
1605
1614
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -240,7 +240,21 @@ export async function initCommand(options) {
|
|
|
240
240
|
* - Node.js projects: use husky
|
|
241
241
|
* - Non-Node.js projects: write native .git/hooks/pre-commit
|
|
242
242
|
*/
|
|
243
|
-
export async function setupHuskyHook(projectPath) {
|
|
243
|
+
export async function setupHuskyHook(projectPath, { allowInTest = false } = {}) {
|
|
244
|
+
// 2026-07-30:本函式在跑測試時改寫了 universal-dev-standards 自己
|
|
245
|
+
// ——`npm install --save-dev husky`、改 package.json、建 .husky/pre-commit。
|
|
246
|
+
//
|
|
247
|
+
// 路徑是:測試呼叫 `initCommand({...})`(三個 init 測試檔都沒帶路徑、
|
|
248
|
+
// 也沒 mock child_process)→ `init.js` 取 `process.cwd()` → 走到這裡。
|
|
249
|
+
// 而下面那個 `.git` 是唯一的守衛,它只問「cwd 是不是一個 repo」:
|
|
250
|
+
// 從 `cli/` 跑 → cwd 沒有 .git → 提早 return,無害;
|
|
251
|
+
// 從 repo root 跑 → cwd **有** .git → 照跑,動到開發者自己的 repo。
|
|
252
|
+
// 同一批測試、不同 cwd,一個無害一個會寫檔。
|
|
253
|
+
//
|
|
254
|
+
// 守衛用顯式 opt-in 而非「偵測到測試就一律跳過」:`init.husky.test.js` 有十幾條
|
|
255
|
+
// 測試是**刻意**在暫存目錄裡驗這個函式的行為,一律跳過會把它們變成永遠通過的空測試。
|
|
256
|
+
if (process.env.VITEST && !allowInTest) return;
|
|
257
|
+
|
|
244
258
|
const hasGit = existsSync(join(projectPath, '.git'));
|
|
245
259
|
if (!hasGit) return;
|
|
246
260
|
|
package/src/commands/release.js
CHANGED
package/src/commands/update.js
CHANGED
|
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import { select, confirm as inquirerConfirm, checkbox, Separator } from '@inquirer/prompts';
|
|
4
4
|
import { execSync } from 'child_process';
|
|
5
|
-
import { existsSync, unlinkSync } from 'fs';
|
|
5
|
+
import { existsSync, readFileSync, unlinkSync } from 'fs';
|
|
6
6
|
import { join, basename } from 'path';
|
|
7
7
|
import { readManifest, writeManifest, copyStandard, isInitialized } from '../utils/copier.js';
|
|
8
8
|
import { getRepositoryInfo, getAllStandards, getStandardSource } from '../utils/registry.js';
|
|
@@ -11,7 +11,10 @@ import {
|
|
|
11
11
|
writeIntegrationFile,
|
|
12
12
|
getToolFilePath,
|
|
13
13
|
writeAgentsMdSummary,
|
|
14
|
-
resolveContentModeForTool
|
|
14
|
+
resolveContentModeForTool,
|
|
15
|
+
generateIntegrationContent,
|
|
16
|
+
extractMarkedContent,
|
|
17
|
+
buildToolIntegrationConfig
|
|
15
18
|
} from '../utils/integration-generator.js';
|
|
16
19
|
import {
|
|
17
20
|
calculateCategoriesFromStandards,
|
|
@@ -51,6 +54,8 @@ import {
|
|
|
51
54
|
} from '../reconciler/index.js';
|
|
52
55
|
import { restoreSingleFile } from './check.js';
|
|
53
56
|
import { guardAgainstSelfAdoption } from '../utils/detect-self-adoption.js';
|
|
57
|
+
import { resolveIntegrationFile } from '../core/constants.js';
|
|
58
|
+
import { mergeInstalledNames } from '../core/manifest.js';
|
|
54
59
|
|
|
55
60
|
/**
|
|
56
61
|
* Determine the correct target directory for a standard file.
|
|
@@ -335,8 +340,12 @@ export async function updateCommand(options) {
|
|
|
335
340
|
}
|
|
336
341
|
|
|
337
342
|
// Handle --integrations-only option
|
|
343
|
+
// ⚠️ 這個分支在 --plan 檢查(見下)**之前**,所以必須自己處理 --plan——
|
|
344
|
+
// 否則 `--plan --integrations-only` 會靜默寫檔。2026-07-30 實測踩到:
|
|
345
|
+
// 以該組合驗證 XSPEC-358 的區塊變更時,它改了 machine-setup 的
|
|
346
|
+
// CLAUDE.md 與 .standards/manifest.json,而旗標文件寫的是 "without executing"。
|
|
338
347
|
if (options.integrationsOnly) {
|
|
339
|
-
await updateIntegrationsOnly(projectPath, manifest);
|
|
348
|
+
await updateIntegrationsOnly(projectPath, manifest, options);
|
|
340
349
|
return;
|
|
341
350
|
}
|
|
342
351
|
|
|
@@ -847,8 +856,9 @@ export async function updateCommand(options) {
|
|
|
847
856
|
const fileName = basename(ext);
|
|
848
857
|
allTrackedFiles.push(join('.standards', fileName));
|
|
849
858
|
}
|
|
850
|
-
for (const
|
|
851
|
-
|
|
859
|
+
for (const intEntry of (manifest.integrations || [])) {
|
|
860
|
+
// 兩種形狀都要能解出路徑(XSPEC-343 R1)
|
|
861
|
+
const filePath = resolveIntegrationFile(intEntry) || getToolFilePath(intEntry);
|
|
852
862
|
if (filePath) {
|
|
853
863
|
allTrackedFiles.push(filePath);
|
|
854
864
|
}
|
|
@@ -951,6 +961,7 @@ export async function updateCommand(options) {
|
|
|
951
961
|
...(manifest.skills.installations || []),
|
|
952
962
|
...installSkills
|
|
953
963
|
];
|
|
964
|
+
manifest.skills.names = mergeInstalledNames(manifest.skills.names, skillResult);
|
|
954
965
|
|
|
955
966
|
// Derive location from installations if not set
|
|
956
967
|
if (!manifest.skills.location) {
|
|
@@ -987,6 +998,7 @@ export async function updateCommand(options) {
|
|
|
987
998
|
// Update manifest version
|
|
988
999
|
if (!manifest.skills) manifest.skills = {};
|
|
989
1000
|
manifest.skills.version = repoInfo.skills.version;
|
|
1001
|
+
manifest.skills.names = mergeInstalledNames(manifest.skills.names, updateResult);
|
|
990
1002
|
|
|
991
1003
|
// Derive location from installations if not set
|
|
992
1004
|
if (!manifest.skills.location && manifest.skills.installations?.length > 0) {
|
|
@@ -1028,6 +1040,7 @@ export async function updateCommand(options) {
|
|
|
1028
1040
|
...(manifest.commands.installations || []),
|
|
1029
1041
|
...installCommands
|
|
1030
1042
|
];
|
|
1043
|
+
manifest.commands.names = mergeInstalledNames(manifest.commands.names, cmdResult);
|
|
1031
1044
|
|
|
1032
1045
|
// Update command hashes for integrity tracking
|
|
1033
1046
|
if (cmdResult.allFileHashes) {
|
|
@@ -1053,6 +1066,7 @@ export async function updateCommand(options) {
|
|
|
1053
1066
|
// Update manifest version
|
|
1054
1067
|
if (!manifest.commands) manifest.commands = {};
|
|
1055
1068
|
manifest.commands.version = repoInfo.skills.version;
|
|
1069
|
+
manifest.commands.names = mergeInstalledNames(manifest.commands.names, updateCmdResult);
|
|
1056
1070
|
|
|
1057
1071
|
// Update command hashes for integrity tracking
|
|
1058
1072
|
if (updateCmdResult.allFileHashes) {
|
|
@@ -1127,6 +1141,7 @@ export async function updateCommand(options) {
|
|
|
1127
1141
|
manifest.skills.installed = true;
|
|
1128
1142
|
manifest.skills.version = repoInfo.skills.version;
|
|
1129
1143
|
manifest.skills.installations = [...(manifest.skills.installations || []), ...missingSkills];
|
|
1144
|
+
manifest.skills.names = mergeInstalledNames(manifest.skills.names, result);
|
|
1130
1145
|
if (result.allFileHashes) {
|
|
1131
1146
|
if (!manifest.skillHashes) manifest.skillHashes = {};
|
|
1132
1147
|
Object.assign(manifest.skillHashes, result.allFileHashes);
|
|
@@ -1144,6 +1159,7 @@ export async function updateCommand(options) {
|
|
|
1144
1159
|
const result = await installSkillsToMultipleAgents(outdatedSkills, null, projectPath, skillsLocale);
|
|
1145
1160
|
if (!manifest.skills) manifest.skills = {};
|
|
1146
1161
|
manifest.skills.version = repoInfo.skills.version;
|
|
1162
|
+
manifest.skills.names = mergeInstalledNames(manifest.skills.names, result);
|
|
1147
1163
|
if (!manifest.skills.location && manifest.skills.installations?.length > 0) {
|
|
1148
1164
|
const levels = manifest.skills.installations.map(s => s.level).filter(Boolean);
|
|
1149
1165
|
const uniqueLevels = [...new Set(levels)];
|
|
@@ -1168,6 +1184,7 @@ export async function updateCommand(options) {
|
|
|
1168
1184
|
manifest.commands.installed = true;
|
|
1169
1185
|
manifest.commands.version = repoInfo.skills.version;
|
|
1170
1186
|
manifest.commands.installations = [...(manifest.commands.installations || []), ...missingCommands];
|
|
1187
|
+
manifest.commands.names = mergeInstalledNames(manifest.commands.names, result);
|
|
1171
1188
|
if (result.allFileHashes) {
|
|
1172
1189
|
if (!manifest.commandHashes) manifest.commandHashes = {};
|
|
1173
1190
|
replaceCommandHashesForUpdatedAgents(manifest.commandHashes, result.allFileHashes);
|
|
@@ -1185,6 +1202,7 @@ export async function updateCommand(options) {
|
|
|
1185
1202
|
const result = await installCommandsToMultipleAgents(outdatedCommands, null, projectPath, skillsLocale);
|
|
1186
1203
|
if (!manifest.commands) manifest.commands = {};
|
|
1187
1204
|
manifest.commands.version = repoInfo.skills.version;
|
|
1205
|
+
manifest.commands.names = mergeInstalledNames(manifest.commands.names, result);
|
|
1188
1206
|
if (result.allFileHashes) {
|
|
1189
1207
|
if (!manifest.commandHashes) manifest.commandHashes = {};
|
|
1190
1208
|
replaceCommandHashesForUpdatedAgents(manifest.commandHashes, result.allFileHashes);
|
|
@@ -1319,17 +1337,6 @@ export function regenerateIntegrations(projectPath, manifest) {
|
|
|
1319
1337
|
return { success: true, updated: [], errors: [] };
|
|
1320
1338
|
}
|
|
1321
1339
|
|
|
1322
|
-
// Build installed standards list
|
|
1323
|
-
const installedStandardsList = manifest.standards?.map(s => basename(s)) || [];
|
|
1324
|
-
|
|
1325
|
-
// Determine language setting
|
|
1326
|
-
let commonLanguage = 'en';
|
|
1327
|
-
if ((manifest.options?.output_language || manifest.options?.commit_language) === 'bilingual') {
|
|
1328
|
-
commonLanguage = 'bilingual';
|
|
1329
|
-
} else if ((manifest.options?.output_language || manifest.options?.commit_language) === 'traditional-chinese') {
|
|
1330
|
-
commonLanguage = 'zh-tw';
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
1340
|
const results = {
|
|
1334
1341
|
updated: [],
|
|
1335
1342
|
errors: []
|
|
@@ -1345,19 +1352,8 @@ export function regenerateIntegrations(projectPath, manifest) {
|
|
|
1345
1352
|
continue; // Skip if already generated (AGENTS.md sharing)
|
|
1346
1353
|
}
|
|
1347
1354
|
|
|
1348
|
-
//
|
|
1349
|
-
const
|
|
1350
|
-
const resolvedMode = resolveContentModeForTool(tool, savedMode);
|
|
1351
|
-
const toolConfig = {
|
|
1352
|
-
tool,
|
|
1353
|
-
categories: ['anti-hallucination', 'commit-standards', 'code-review'],
|
|
1354
|
-
language: commonLanguage,
|
|
1355
|
-
installedStandards: installedStandardsList,
|
|
1356
|
-
contentMode: resolvedMode.contentMode,
|
|
1357
|
-
level: resolvedMode.level,
|
|
1358
|
-
// Pass output_language for dynamic commit standards generation
|
|
1359
|
-
outputLanguage: manifest.options?.output_language || manifest.options?.commit_language || 'english'
|
|
1360
|
-
};
|
|
1355
|
+
// Shared with the reconciler so both paths emit the identical block.
|
|
1356
|
+
const toolConfig = buildToolIntegrationConfig(manifest, tool);
|
|
1361
1357
|
|
|
1362
1358
|
const result = writeIntegrationFile(tool, toolConfig, projectPath);
|
|
1363
1359
|
if (result.success) {
|
|
@@ -1392,7 +1388,7 @@ export function regenerateIntegrations(projectPath, manifest) {
|
|
|
1392
1388
|
// Regenerate universal AGENTS.md if enabled and not already covered
|
|
1393
1389
|
if (manifest.generateAgentsMd && !generatedFiles.has('AGENTS.md')) {
|
|
1394
1390
|
const summaryConfig = {
|
|
1395
|
-
installedStandards:
|
|
1391
|
+
installedStandards: (manifest.standards || []).map(s => basename(s)),
|
|
1396
1392
|
language: manifest.options?.display_language || 'en',
|
|
1397
1393
|
outputLanguage: manifest.options?.output_language || manifest.options?.commit_language || 'english',
|
|
1398
1394
|
standardOptions: manifest.options || {}
|
|
@@ -1422,7 +1418,7 @@ export function regenerateIntegrations(projectPath, manifest) {
|
|
|
1422
1418
|
* @param {string} projectPath - Project path
|
|
1423
1419
|
* @param {Object} manifest - Manifest object
|
|
1424
1420
|
*/
|
|
1425
|
-
async function updateIntegrationsOnly(projectPath, manifest) {
|
|
1421
|
+
async function updateIntegrationsOnly(projectPath, manifest, options = {}) {
|
|
1426
1422
|
const msg = t().commands.update;
|
|
1427
1423
|
|
|
1428
1424
|
console.log(chalk.cyan(msg.updatingIntegrationsOnly));
|
|
@@ -1436,6 +1432,59 @@ async function updateIntegrationsOnly(projectPath, manifest) {
|
|
|
1436
1432
|
return;
|
|
1437
1433
|
}
|
|
1438
1434
|
|
|
1435
|
+
// --plan:只比對、不寫任何檔(連 manifest 也不寫)。
|
|
1436
|
+
// 這一段刻意不呼叫 regenerateIntegrations——那個函式會寫檔,
|
|
1437
|
+
// 「先寫再還原」的作法在中途掛掉時會留下壞狀態,比沒有 dry-run 更糟。
|
|
1438
|
+
if (options.plan) {
|
|
1439
|
+
console.log(chalk.bold('=== Integration Plan (dry run — nothing is written) ==='));
|
|
1440
|
+
const wouldChange = [];
|
|
1441
|
+
const unchanged = [];
|
|
1442
|
+
const seen = new Set();
|
|
1443
|
+
for (const tool of aiTools) {
|
|
1444
|
+
const targetFile = getToolFilePath(tool);
|
|
1445
|
+
if (seen.has(targetFile)) continue;
|
|
1446
|
+
seen.add(targetFile);
|
|
1447
|
+
const savedMode = manifest.contentMode || 'auto';
|
|
1448
|
+
const resolvedMode = resolveContentModeForTool(tool, savedMode);
|
|
1449
|
+
const next = generateIntegrationContent({
|
|
1450
|
+
tool,
|
|
1451
|
+
categories: ['anti-hallucination', 'commit-standards', 'code-review'],
|
|
1452
|
+
language: (manifest.options?.output_language || manifest.options?.commit_language) === 'bilingual'
|
|
1453
|
+
? 'bilingual'
|
|
1454
|
+
: (manifest.options?.output_language || manifest.options?.commit_language) === 'traditional-chinese'
|
|
1455
|
+
? 'zh-tw' : 'en',
|
|
1456
|
+
installedStandards: manifest.standards?.map((x) => basename(x)) || [],
|
|
1457
|
+
contentMode: resolvedMode.contentMode,
|
|
1458
|
+
level: resolvedMode.level,
|
|
1459
|
+
outputLanguage: manifest.options?.output_language || manifest.options?.commit_language || 'english'
|
|
1460
|
+
});
|
|
1461
|
+
const full = join(projectPath, targetFile);
|
|
1462
|
+
const current = existsSync(full) ? readFileSync(full, 'utf8') : '';
|
|
1463
|
+
const format = targetFile.endsWith('.md') ? 'markdown' : 'plaintext';
|
|
1464
|
+
const cur = extractMarkedContent(current, format).content || '';
|
|
1465
|
+
// 比對正規化過的內容:只關心「受管區塊會不會變」,不關心尾端空白。
|
|
1466
|
+
if (cur.trim() === String(next).trim()) {
|
|
1467
|
+
unchanged.push(targetFile);
|
|
1468
|
+
} else {
|
|
1469
|
+
const delta = String(next).trim().length - cur.trim().length;
|
|
1470
|
+
wouldChange.push(`${targetFile} (${delta >= 0 ? '+' : ''}${delta} bytes in the UDS block)`);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
if (wouldChange.length > 0) {
|
|
1474
|
+
console.log(chalk.yellow(` ~ Would update (${wouldChange.length}):`));
|
|
1475
|
+
for (const f of wouldChange) console.log(chalk.gray(` ${f}`));
|
|
1476
|
+
}
|
|
1477
|
+
if (unchanged.length > 0) {
|
|
1478
|
+
console.log(chalk.gray(` = Unchanged (${unchanged.length}): ${unchanged.join(', ')}`));
|
|
1479
|
+
}
|
|
1480
|
+
console.log();
|
|
1481
|
+
console.log(chalk.gray(' Also NOT written in plan mode: .standards/manifest.json'));
|
|
1482
|
+
console.log(chalk.gray(' (a real run rewrites it — version, fileHashes, integrationBlockHashes,'));
|
|
1483
|
+
console.log(chalk.gray(' and it normalises any standards entries stored as full paths)'));
|
|
1484
|
+
console.log();
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1439
1488
|
const spinner = ora(msg.regeneratingIntegrations).start();
|
|
1440
1489
|
|
|
1441
1490
|
// Use reusable regeneration function
|
|
@@ -1698,6 +1747,7 @@ async function updateSkillsOnly(projectPath, manifest, options) {
|
|
|
1698
1747
|
// Update manifest
|
|
1699
1748
|
manifest.skills.version = latestVersion;
|
|
1700
1749
|
manifest.skills.installations = skillsInstallations;
|
|
1750
|
+
manifest.skills.names = mergeInstalledNames(manifest.skills.names, result);
|
|
1701
1751
|
|
|
1702
1752
|
// Update skill hashes for integrity tracking
|
|
1703
1753
|
if (result.allFileHashes) {
|
|
@@ -1804,6 +1854,7 @@ async function updateCommandsOnly(projectPath, manifest, options) {
|
|
|
1804
1854
|
}
|
|
1805
1855
|
return inst;
|
|
1806
1856
|
});
|
|
1857
|
+
manifest.commands.names = mergeInstalledNames(manifest.commands.names, result);
|
|
1807
1858
|
|
|
1808
1859
|
// Update command hashes for integrity tracking
|
|
1809
1860
|
if (result.allFileHashes) {
|