universal-dev-standards 6.8.0 → 6.9.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 +12 -2
- package/bundled/ai/standards/acceptance-criteria-traceability.ai.yaml +14 -2
- package/bundled/ai/standards/adr-standards.ai.yaml +14 -2
- package/bundled/ai/standards/code-review.ai.yaml +13 -3
- package/bundled/ai/standards/commit-message.ai.yaml +8 -4
- package/bundled/ai/standards/deferred-item-exit.ai.yaml +225 -0
- package/bundled/ai/standards/feature-discovery-standards.ai.yaml +14 -2
- package/bundled/ai/standards/governance-layer.ai.yaml +128 -2
- package/bundled/ai/standards/logging.ai.yaml +2 -2
- package/bundled/ai/standards/retrospective-standards.ai.yaml +14 -2
- package/bundled/ai/standards/reverse-engineering-standards.ai.yaml +73 -2
- package/bundled/ai/standards/security-standards.ai.yaml +2 -2
- package/bundled/ai/standards/spec-driven-development.ai.yaml +14 -2
- package/bundled/ai/standards/tech-debt-standards.ai.yaml +87 -3
- package/bundled/ai/standards/turn-completion-integrity.ai.yaml +131 -0
- package/bundled/core/acceptance-criteria-traceability.md +5 -2
- package/bundled/core/adr-standards.md +26 -2
- package/bundled/core/code-review-checklist.md +5 -2
- package/bundled/core/context-aware-loading.md +1 -1
- package/bundled/core/deferred-item-exit.md +254 -0
- package/bundled/core/feature-discovery-standards.md +5 -1
- package/bundled/core/governance-layer.md +114 -2
- package/bundled/core/retrospective-standards.md +4 -2
- package/bundled/core/reverse-engineering-standards.md +81 -2
- package/bundled/core/spec-driven-development.md +8 -2
- package/bundled/core/tech-debt-standards.md +67 -8
- package/bundled/core/turn-completion-integrity.md +196 -0
- package/bundled/hooks/check-dangerous-cmd.mjs +60 -0
- package/bundled/hooks/check-logging-standard.mjs +59 -0
- package/bundled/hooks/check-turn-completion.mjs +233 -0
- package/bundled/hooks/inject-standards.mjs +183 -0
- package/bundled/hooks/telemetry-wrapper.mjs +77 -0
- package/bundled/hooks/turn-completion/detect.mjs +99 -0
- package/bundled/hooks/turn-completion/locales/en.mjs +159 -0
- package/bundled/hooks/turn-completion/locales/zh-TW.mjs +166 -0
- package/bundled/hooks/validate-commit-msg.mjs +104 -0
- package/bundled/locales/zh-CN/CHANGELOG.md +47 -3
- package/bundled/locales/zh-CN/CLAUDE.md +1 -1
- package/bundled/locales/zh-CN/README.md +2 -2
- package/bundled/locales/zh-CN/SECURITY.md +1 -1
- package/bundled/locales/zh-CN/core/adr-standards.md +1 -1
- package/bundled/locales/zh-CN/core/governance-layer.md +118 -6
- package/bundled/locales/zh-CN/core/retrospective-standards.md +1 -1
- package/bundled/locales/zh-CN/core/tech-debt-standards.md +71 -4
- package/bundled/locales/zh-CN/core/turn-completion-integrity.md +190 -0
- package/bundled/locales/zh-CN/docs/CHEATSHEET.md +8 -1
- package/bundled/locales/zh-CN/docs/CLI-INIT-OPTIONS.md +29 -68
- package/bundled/locales/zh-CN/docs/FEATURE-REFERENCE.md +25 -15
- package/bundled/locales/zh-CN/docs/USAGE-MODES-COMPARISON.md +1 -2
- package/bundled/locales/zh-CN/integrations/google-antigravity/{INSTRUCTIONS.md → AGENTS.md} +1 -1
- package/bundled/locales/zh-CN/integrations/google-antigravity/README.md +3 -3
- package/bundled/locales/zh-CN/skills/atdd-assistant/SKILL.md +2 -0
- package/bundled/locales/zh-CN/skills/bdd-assistant/SKILL.md +2 -0
- package/bundled/locales/zh-CN/skills/brainstorm-assistant/SKILL.md +22 -12
- package/bundled/locales/zh-CN/skills/brainstorm-assistant/guide.md +12 -9
- package/bundled/locales/zh-CN/skills/code-review-assistant/SKILL.md +1 -0
- package/bundled/locales/zh-CN/skills/commands/brainstorm.md +17 -13
- package/bundled/locales/zh-CN/skills/commands/config.md +0 -1
- package/bundled/locales/zh-CN/skills/commands/init.md +1 -2
- package/bundled/locales/zh-CN/skills/commit-standards/SKILL.md +2 -0
- package/bundled/locales/zh-CN/skills/contract-test-assistant/SKILL.md +1 -0
- package/bundled/locales/zh-CN/skills/dev-methodology/SKILL.md +2 -0
- package/bundled/locales/zh-CN/skills/observability-assistant/SKILL.md +1 -0
- package/bundled/locales/zh-CN/skills/project-structure-guide/SKILL.md +1 -0
- package/bundled/locales/zh-CN/skills/release-standards/SKILL.md +3 -0
- package/bundled/locales/zh-CN/skills/requirement-assistant/SKILL.md +2 -0
- package/bundled/locales/zh-CN/skills/reverse-engineer/SKILL.md +3 -0
- package/bundled/locales/zh-CN/skills/runbook-assistant/SKILL.md +1 -0
- package/bundled/locales/zh-CN/skills/slo-assistant/SKILL.md +1 -0
- package/bundled/locales/zh-CN/skills/tdd-assistant/SKILL.md +2 -0
- package/bundled/locales/zh-TW/CHANGELOG.md +47 -3
- package/bundled/locales/zh-TW/CLAUDE.md +1 -1
- package/bundled/locales/zh-TW/README.md +2 -2
- package/bundled/locales/zh-TW/SECURITY.md +1 -1
- package/bundled/locales/zh-TW/core/acceptance-criteria-traceability.md +2 -0
- package/bundled/locales/zh-TW/core/adr-standards.md +26 -5
- package/bundled/locales/zh-TW/core/code-review-checklist.md +2 -0
- package/bundled/locales/zh-TW/core/container-image-standards.md +2 -2
- package/bundled/locales/zh-TW/core/contract-testing-standards.md +2 -2
- package/bundled/locales/zh-TW/core/cross-flow-regression.md +8 -7
- package/bundled/locales/zh-TW/core/data-contract.md +2 -2
- package/bundled/locales/zh-TW/core/data-migration-testing.md +2 -2
- package/bundled/locales/zh-TW/core/data-pipeline.md +2 -2
- package/bundled/locales/zh-TW/core/deferred-item-exit.md +251 -0
- package/bundled/locales/zh-TW/core/documentation-writing-standards.md +228 -3
- package/bundled/locales/zh-TW/core/full-coverage-testing.md +15 -2
- package/bundled/locales/zh-TW/core/governance-layer.md +118 -5
- package/bundled/locales/zh-TW/core/iac-design-principles.md +2 -2
- package/bundled/locales/zh-TW/core/incident-response.md +2 -2
- package/bundled/locales/zh-TW/core/model-provenance.md +4 -2
- package/bundled/locales/zh-TW/core/pii-classification.md +42 -6
- package/bundled/locales/zh-TW/core/prd-standards.md +4 -2
- package/bundled/locales/zh-TW/core/product-metrics-standards.md +4 -2
- package/bundled/locales/zh-TW/core/release-readiness-gate.md +2 -2
- package/bundled/locales/zh-TW/core/resource-cost-boundary.md +2 -2
- package/bundled/locales/zh-TW/core/retrospective-standards.md +5 -3
- package/bundled/locales/zh-TW/core/reverse-engineering-standards.md +83 -5
- package/bundled/locales/zh-TW/core/runbook.md +2 -2
- package/bundled/locales/zh-TW/core/schema-evolution.md +2 -2
- package/bundled/locales/zh-TW/core/secret-management-standards.md +2 -2
- package/bundled/locales/zh-TW/core/slo-sli.md +2 -2
- package/bundled/locales/zh-TW/core/spec-driven-development.md +2 -0
- package/bundled/locales/zh-TW/core/tech-debt-standards.md +71 -4
- package/bundled/locales/zh-TW/core/turn-completion-integrity.md +190 -0
- package/bundled/locales/zh-TW/core/user-journey-testing.md +2 -2
- package/bundled/locales/zh-TW/core/user-story-mapping.md +2 -2
- package/bundled/locales/zh-TW/core/verification-oracle.md +2 -2
- package/bundled/locales/zh-TW/docs/CHEATSHEET.md +8 -1
- package/bundled/locales/zh-TW/docs/CLI-INIT-OPTIONS.md +29 -68
- package/bundled/locales/zh-TW/docs/FEATURE-REFERENCE.md +25 -15
- package/bundled/locales/zh-TW/docs/USAGE-MODES-COMPARISON.md +1 -2
- package/bundled/locales/zh-TW/integrations/google-antigravity/{INSTRUCTIONS.md → AGENTS.md} +1 -1
- package/bundled/locales/zh-TW/integrations/google-antigravity/README.md +3 -3
- package/bundled/locales/zh-TW/skills/adr-assistant/SKILL.md +1 -1
- package/bundled/locales/zh-TW/skills/atdd-assistant/SKILL.md +2 -0
- package/bundled/locales/zh-TW/skills/bdd-assistant/SKILL.md +2 -0
- package/bundled/locales/zh-TW/skills/brainstorm-assistant/SKILL.md +22 -12
- package/bundled/locales/zh-TW/skills/brainstorm-assistant/guide.md +12 -9
- package/bundled/locales/zh-TW/skills/code-review-assistant/SKILL.md +1 -0
- package/bundled/locales/zh-TW/skills/commands/brainstorm.md +17 -13
- package/bundled/locales/zh-TW/skills/commands/config.md +0 -1
- package/bundled/locales/zh-TW/skills/commands/init.md +1 -2
- package/bundled/locales/zh-TW/skills/commit-standards/SKILL.md +2 -0
- package/bundled/locales/zh-TW/skills/contract-test-assistant/SKILL.md +2 -1
- package/bundled/locales/zh-TW/skills/dev-methodology/SKILL.md +2 -0
- package/bundled/locales/zh-TW/skills/dev-workflow-guide/SKILL.md +1 -1
- package/bundled/locales/zh-TW/skills/knowledge-graph/guide.md +2 -2
- package/bundled/locales/zh-TW/skills/migration-assistant/SKILL.md +1 -1
- package/bundled/locales/zh-TW/skills/observability-assistant/SKILL.md +1 -0
- package/bundled/locales/zh-TW/skills/project-discovery/SKILL.md +1 -0
- package/bundled/locales/zh-TW/skills/project-structure-guide/SKILL.md +1 -0
- package/bundled/locales/zh-TW/skills/release-standards/SKILL.md +3 -0
- package/bundled/locales/zh-TW/skills/requirement-assistant/SKILL.md +2 -0
- package/bundled/locales/zh-TW/skills/reverse-engineer/SKILL.md +3 -0
- package/bundled/locales/zh-TW/skills/runbook-assistant/SKILL.md +1 -0
- package/bundled/locales/zh-TW/skills/slo-assistant/SKILL.md +1 -0
- package/bundled/locales/zh-TW/skills/tdd-assistant/SKILL.md +2 -0
- package/bundled/skills/atdd-assistant/SKILL.md +2 -0
- package/bundled/skills/bdd-assistant/SKILL.md +2 -0
- package/bundled/skills/brainstorm-assistant/SKILL.md +31 -13
- package/bundled/skills/brainstorm-assistant/guide.md +9 -6
- package/bundled/skills/code-review-assistant/SKILL.md +1 -0
- package/bundled/skills/commands/brainstorm.md +12 -9
- package/bundled/skills/commands/config.md +0 -1
- package/bundled/skills/commands/init.md +2 -3
- package/bundled/skills/commit-standards/SKILL.md +2 -0
- package/bundled/skills/contract-test-assistant/SKILL.md +1 -0
- package/bundled/skills/dev-methodology/SKILL.md +4 -0
- package/bundled/skills/observability-assistant/SKILL.md +1 -0
- package/bundled/skills/project-discovery/SKILL.md +1 -0
- package/bundled/skills/project-structure-guide/SKILL.md +1 -0
- package/bundled/skills/release-standards/SKILL.md +3 -0
- package/bundled/skills/requirement-assistant/SKILL.md +2 -0
- package/bundled/skills/reverse-engineer/SKILL.md +3 -0
- package/bundled/skills/runbook-assistant/SKILL.md +1 -0
- package/bundled/skills/slo-assistant/SKILL.md +1 -0
- package/bundled/skills/tdd-assistant/SKILL.md +2 -0
- package/bundled/templates/.ai-context.yaml.template +194 -0
- package/bundled/templates/CLAUDE.md.template +145 -0
- package/bundled/templates/DESIGN.md +237 -0
- package/bundled/templates/SKILL-BRIEF-TEMPLATE.md +57 -0
- package/bundled/templates/SKILL-CANDIDATES.md +39 -0
- package/bundled/templates/gates/check-error-exit.mjs +309 -0
- package/bundled/templates/mcp-config.json +10 -0
- package/bundled/templates/methodology-template.yaml +209 -0
- package/bundled/templates/migration-template.md +408 -0
- package/bundled/templates/requirement-checklist.md +410 -0
- package/bundled/templates/requirement-document-template.md +591 -0
- package/bundled/templates/requirement-template.md +881 -0
- package/bundled/templates/reverse-spec-template.md +409 -0
- package/bundled/templates/test-case-template.md +74 -0
- package/bundled/templates/test-plan-template.md +74 -0
- package/package.json +7 -5
- package/src/commands/audit.js +82 -0
- package/src/commands/check.js +66 -10
- package/src/commands/init.js +161 -16
- package/src/commands/update.js +286 -14
- package/src/compilers/claude-code-compiler.js +4 -1
- package/src/config/ai-agent-paths.js +62 -17
- package/src/core/constants.js +42 -11
- package/src/core/manifest.js +201 -3
- package/src/core/paths.js +2 -2
- package/src/i18n/messages.js +6 -29
- package/src/installers/hooks-installer.js +167 -75
- package/src/installers/integration-installer.js +9 -5
- package/src/prompts/init.js +14 -14
- package/src/utils/detector.js +21 -1
- package/src/utils/effect-boundary.js +1093 -0
- package/src/utils/hasher.js +166 -1
- package/src/utils/hook-stats.js +1 -1
- package/src/utils/integration-generator.js +79 -1
- package/src/utils/reference-sync.js +4 -1
- package/src/utils/yaml-generator.js +51 -9
- package/standards-registry.json +31 -8
package/src/commands/update.js
CHANGED
|
@@ -2,11 +2,11 @@ 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, readFileSync, unlinkSync } from 'fs';
|
|
6
|
-
import { join, basename } from 'path';
|
|
7
|
-
import { readManifest, writeManifest, copyStandard, isInitialized } from '../utils/copier.js';
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
|
|
6
|
+
import { join, basename, dirname, relative } from 'path';
|
|
7
|
+
import { readManifest, writeManifest, copyStandard, isInitialized, getRepoRoot } from '../utils/copier.js';
|
|
8
8
|
import { getRepositoryInfo, getAllStandards, getStandardSource } from '../utils/registry.js';
|
|
9
|
-
import { computeFileHash,
|
|
9
|
+
import { computeFileHash, planStandardsRemovals, refreshIntegrationBlockHashes } from '../utils/hasher.js';
|
|
10
10
|
import {
|
|
11
11
|
writeIntegrationFile,
|
|
12
12
|
getToolFilePath,
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
getCommandsDirForAgent
|
|
43
43
|
} from '../config/ai-agent-paths.js';
|
|
44
44
|
import { getMarketplaceSkillsInfo } from '../utils/github.js';
|
|
45
|
+
import { detectAITools } from '../utils/detector.js';
|
|
45
46
|
import {
|
|
46
47
|
promptSkillsInstallLocation,
|
|
47
48
|
promptCommandsInstallation
|
|
@@ -56,7 +57,110 @@ import {
|
|
|
56
57
|
import { restoreSingleFile } from './check.js';
|
|
57
58
|
import { guardAgainstSelfAdoption } from '../utils/detect-self-adoption.js';
|
|
58
59
|
import { resolveIntegrationFile } from '../core/constants.js';
|
|
59
|
-
import {
|
|
60
|
+
import {
|
|
61
|
+
mergeInstalledNames,
|
|
62
|
+
recordFileProvenance,
|
|
63
|
+
forgetFileProvenance,
|
|
64
|
+
establishProvenance,
|
|
65
|
+
isProvenanceEstablished
|
|
66
|
+
} from '../core/manifest.js';
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* How aggressive `uds update` is about deleting standards it no longer ships.
|
|
70
|
+
*
|
|
71
|
+
* XSPEC-384 §4 leaves the staging strategy for R2 undecided, so this is the
|
|
72
|
+
* most conservative of the three options on the table and it lives in one named
|
|
73
|
+
* place rather than being spread through the deletion path. Changing this
|
|
74
|
+
* constant is the whole change of default; the alternatives are:
|
|
75
|
+
*
|
|
76
|
+
* 'report-only' list what would go, delete nothing without --prune (current)
|
|
77
|
+
* 'ask' list, and prompt in interactive sessions only
|
|
78
|
+
* 'auto' delete as part of a normal update
|
|
79
|
+
*
|
|
80
|
+
* It starts at 'report-only' because the change is adopter-visible in the
|
|
81
|
+
* dangerous direction: files that survive today start disappearing. A default
|
|
82
|
+
* that deletes must not arrive in a patch release, and must not arrive at all
|
|
83
|
+
* before someone decides it should.
|
|
84
|
+
*/
|
|
85
|
+
export const PRUNE_POLICY = 'report-only';
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Decide whether this run is allowed to delete.
|
|
89
|
+
*
|
|
90
|
+
* `--prune` is deliberately its own flag rather than a use of `--yes`. `--yes`
|
|
91
|
+
* means "don't ask me about the thing I already asked for"; it is set in CI and
|
|
92
|
+
* in scripts, and letting it also authorise deletion would enable the
|
|
93
|
+
* destructive path silently in exactly the unattended environments that cannot
|
|
94
|
+
* notice it. Non-interactive runs therefore never delete unless --prune is
|
|
95
|
+
* explicit. (XSPEC-384 R3)
|
|
96
|
+
*
|
|
97
|
+
* @param {Object} plan - Result of planStandardsRemovals
|
|
98
|
+
* @param {Object} options - Command options
|
|
99
|
+
* @returns {boolean}
|
|
100
|
+
*/
|
|
101
|
+
export function shouldPrune(plan, options = {}) {
|
|
102
|
+
if (plan.remove.length === 0) return false;
|
|
103
|
+
if (options.prune) return true;
|
|
104
|
+
return PRUNE_POLICY === 'auto';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Print the removal plan, including its denominator. (XSPEC-384 R3)
|
|
109
|
+
*
|
|
110
|
+
* The counts are not decoration. A report that lists only the files it intends
|
|
111
|
+
* to touch is bit-for-bit identical to a report from a scan that only *saw*
|
|
112
|
+
* those files — which is the failure the previous code shipped for months,
|
|
113
|
+
* silently, because nothing ever printed how many files it had considered.
|
|
114
|
+
*
|
|
115
|
+
* @param {Object} plan - Result of planStandardsRemovals
|
|
116
|
+
* @param {boolean} provenanceWasEstablished - Whether ownership was already known
|
|
117
|
+
* @param {boolean} willDelete - Whether this run is going to act on the plan
|
|
118
|
+
*/
|
|
119
|
+
export function reportRemovalPlan(plan, provenanceWasEstablished, willDelete = false) {
|
|
120
|
+
if (plan.scanned === 0) return;
|
|
121
|
+
|
|
122
|
+
console.log();
|
|
123
|
+
console.log(chalk.gray(
|
|
124
|
+
` .standards/ examined: ${plan.scanned} file(s) — ` +
|
|
125
|
+
`${plan.census.udsOwned} written by UDS, ` +
|
|
126
|
+
`${plan.census.foreign} not ours, ` +
|
|
127
|
+
`${plan.census.unknown} ownership unknown, ` +
|
|
128
|
+
`${plan.excluded.length} excluded`
|
|
129
|
+
));
|
|
130
|
+
for (const { path: p, reason } of plan.excluded) {
|
|
131
|
+
console.log(chalk.gray(` excluded ${p} (${reason})`));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!provenanceWasEstablished) {
|
|
135
|
+
console.log(chalk.gray(
|
|
136
|
+
' first run with ownership tracking: files installed before this ' +
|
|
137
|
+
'release cannot be attributed yet, so none will be removed.'
|
|
138
|
+
));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (plan.remove.length === 0) {
|
|
142
|
+
if (plan.census.foreign > 0 || plan.census.unknown > 0) {
|
|
143
|
+
console.log(chalk.gray(
|
|
144
|
+
` kept ${plan.census.foreign + plan.census.unknown} file(s) UDS did not write`
|
|
145
|
+
));
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log();
|
|
151
|
+
console.log(chalk.yellow(
|
|
152
|
+
` ${plan.remove.length} file(s) are no longer shipped by UDS` +
|
|
153
|
+
(willDelete ? ' and will be removed:' : ':')
|
|
154
|
+
));
|
|
155
|
+
for (const { path: p, reason } of plan.remove) {
|
|
156
|
+
console.log(chalk.yellow(` - ${p} (${reason})`));
|
|
157
|
+
}
|
|
158
|
+
console.log(chalk.gray(
|
|
159
|
+
willDelete
|
|
160
|
+
? ' Removing now (--prune). Files UDS did not write are never touched.'
|
|
161
|
+
: ' Not deleted. Re-run with --prune to remove them.'
|
|
162
|
+
));
|
|
163
|
+
}
|
|
60
164
|
|
|
61
165
|
/**
|
|
62
166
|
* Determine the correct target directory for a standard file.
|
|
@@ -546,6 +650,21 @@ export async function updateCommand(options) {
|
|
|
546
650
|
errors: []
|
|
547
651
|
};
|
|
548
652
|
|
|
653
|
+
// Paths UDS actually wrote this run, taken from the copy result rather than
|
|
654
|
+
// recomputed from the source name. Recomputing is how `fileHashes` ended up
|
|
655
|
+
// with five `.standards/options/<name>.ai.yaml` entries for files that live
|
|
656
|
+
// at `.standards/options/<category>/<name>.ai.yaml`; a provenance record
|
|
657
|
+
// built the same way would claim files that do not exist and disown ones
|
|
658
|
+
// that do. (XSPEC-384 R1)
|
|
659
|
+
const writtenPaths = [];
|
|
660
|
+
const recordWritten = (result) => {
|
|
661
|
+
if (!result?.success || !result.data) return;
|
|
662
|
+
const abs = typeof result.data === 'string' ? result.data : null;
|
|
663
|
+
if (!abs) return;
|
|
664
|
+
const rel = relative(projectPath, abs).replace(/\\/g, '/');
|
|
665
|
+
if (rel && !rel.startsWith('..')) writtenPaths.push(rel);
|
|
666
|
+
};
|
|
667
|
+
|
|
549
668
|
// Update standards
|
|
550
669
|
const updateFormat = manifest.format || 'ai';
|
|
551
670
|
const allStdsUpdate = getAllStandards();
|
|
@@ -561,6 +680,7 @@ export async function updateCommand(options) {
|
|
|
561
680
|
const result = await copyStandard(sourcePath, getStandardTargetDir(sourcePath), projectPath);
|
|
562
681
|
if (result.success) {
|
|
563
682
|
results.updated.push(std);
|
|
683
|
+
recordWritten(result);
|
|
564
684
|
} else {
|
|
565
685
|
results.errors.push(`${std}: ${result.error}`);
|
|
566
686
|
}
|
|
@@ -572,6 +692,7 @@ export async function updateCommand(options) {
|
|
|
572
692
|
const result = await copyStandard(ext, '.standards', projectPath);
|
|
573
693
|
if (result.success) {
|
|
574
694
|
results.updated.push(ext);
|
|
695
|
+
recordWritten(result);
|
|
575
696
|
} else {
|
|
576
697
|
results.errors.push(`${ext}: ${result.error}`);
|
|
577
698
|
}
|
|
@@ -601,6 +722,7 @@ export async function updateCommand(options) {
|
|
|
601
722
|
if (result.success) {
|
|
602
723
|
manifest.standards.push(ns.source);
|
|
603
724
|
results.updated.push(ns.source);
|
|
725
|
+
recordWritten(result);
|
|
604
726
|
newCount++;
|
|
605
727
|
} else {
|
|
606
728
|
results.errors.push(`${ns.source}: ${result.error}`);
|
|
@@ -751,6 +873,20 @@ export async function updateCommand(options) {
|
|
|
751
873
|
manifest.fileHashes = {};
|
|
752
874
|
}
|
|
753
875
|
|
|
876
|
+
// The set of `.standards/` files the *current* registry says should exist.
|
|
877
|
+
//
|
|
878
|
+
// Built by accumulating inside the loops that already resolve each entry,
|
|
879
|
+
// rather than by a second function that re-derives the same paths. A second
|
|
880
|
+
// derivation is a second copy, and the two would answer "where does this
|
|
881
|
+
// standard live" differently the moment either is edited — which is the
|
|
882
|
+
// failure mode XSPEC-384 is about, not a shape to reproduce. (XSPEC-384 R2)
|
|
883
|
+
const desiredStandardsFiles = new Set();
|
|
884
|
+
|
|
885
|
+
// Standards whose ID no longer resolves against the registry. Collected here
|
|
886
|
+
// instead of being silently `continue`d, because "skipped" and "deleted
|
|
887
|
+
// upstream" were previously indistinguishable — issue #165. (XSPEC-384 R2)
|
|
888
|
+
const unresolvedStandardIds = [];
|
|
889
|
+
|
|
754
890
|
// Update hashes for standards (handles both ID format and legacy path format)
|
|
755
891
|
const hashFormat = manifest.format || 'ai';
|
|
756
892
|
const allStdsHash = getAllStandards();
|
|
@@ -759,7 +895,13 @@ export async function updateCommand(options) {
|
|
|
759
895
|
let resolvedPath = std;
|
|
760
896
|
if (!std.includes('/') && !std.includes('.')) {
|
|
761
897
|
const entry = allStdsHash.find(r => r.id === std);
|
|
762
|
-
if (!entry)
|
|
898
|
+
if (!entry) {
|
|
899
|
+
// Unrecognized ID: either a standard removed from the registry, or a
|
|
900
|
+
// stale AI tool name an old CLI wrote into `standards`. Either way it
|
|
901
|
+
// is no longer a thing we ship, so it must not enter the desired set.
|
|
902
|
+
unresolvedStandardIds.push(std);
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
763
905
|
const src = getStandardSource(entry, hashFormat);
|
|
764
906
|
if (src) resolvedPath = src;
|
|
765
907
|
}
|
|
@@ -767,6 +909,10 @@ export async function updateCommand(options) {
|
|
|
767
909
|
const relativePath = (resolvedPath.includes('options/')
|
|
768
910
|
? join('.standards', 'options', fileName)
|
|
769
911
|
: join('.standards', fileName)).replace(/\\/g, '/');
|
|
912
|
+
// Added before the hash check: a file that exists but failed to hash is
|
|
913
|
+
// still wanted. Nominating it for deletion because hashing failed would
|
|
914
|
+
// turn a transient read error into data loss.
|
|
915
|
+
desiredStandardsFiles.add(relativePath);
|
|
770
916
|
const fullPath = join(projectPath, relativePath);
|
|
771
917
|
const hashInfo = computeFileHash(fullPath);
|
|
772
918
|
if (hashInfo) {
|
|
@@ -779,6 +925,7 @@ export async function updateCommand(options) {
|
|
|
779
925
|
if (typeof ext !== 'string') continue;
|
|
780
926
|
const fileName = basename(ext);
|
|
781
927
|
const relativePath = join('.standards', fileName).replace(/\\/g, '/');
|
|
928
|
+
desiredStandardsFiles.add(relativePath);
|
|
782
929
|
const fullPath = join(projectPath, relativePath);
|
|
783
930
|
const hashInfo = computeFileHash(fullPath);
|
|
784
931
|
if (hashInfo) {
|
|
@@ -795,21 +942,63 @@ export async function updateCommand(options) {
|
|
|
795
942
|
}
|
|
796
943
|
}
|
|
797
944
|
|
|
798
|
-
//
|
|
799
|
-
//
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
945
|
+
// Record what UDS wrote this run, then decide what (if anything) may go.
|
|
946
|
+
// (XSPEC-384 R1/R2/R3)
|
|
947
|
+
const provenanceWasEstablished = isProvenanceEstablished(manifest);
|
|
948
|
+
let provenanceCarrier = manifest;
|
|
949
|
+
for (const written of [...writtenPaths, ...results.integrations]) {
|
|
950
|
+
provenanceCarrier = recordFileProvenance(provenanceCarrier, written, 'update');
|
|
951
|
+
}
|
|
952
|
+
manifest.provenance = provenanceCarrier.provenance;
|
|
953
|
+
|
|
954
|
+
// Drop `standards` entries the registry no longer knows about. Previously
|
|
955
|
+
// these were `continue`d in three separate loops and left in the manifest
|
|
956
|
+
// forever, so `upstream.version` advanced to "latest" while the manifest
|
|
957
|
+
// still listed standards that had not existed for releases. (issue #165)
|
|
958
|
+
if (unresolvedStandardIds.length > 0) {
|
|
959
|
+
manifest.standards = manifest.standards.filter(s => !unresolvedStandardIds.includes(s));
|
|
960
|
+
console.log();
|
|
961
|
+
console.log(chalk.yellow(
|
|
962
|
+
`⚠ ${unresolvedStandardIds.length} manifest entr${unresolvedStandardIds.length === 1 ? 'y is' : 'ies are'} no longer in the registry and ${unresolvedStandardIds.length === 1 ? 'was' : 'were'} dropped:`
|
|
963
|
+
));
|
|
964
|
+
for (const id of unresolvedStandardIds) {
|
|
965
|
+
console.log(chalk.gray(` - ${id}`));
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
const removalPlan = planStandardsRemovals(projectPath, manifest, desiredStandardsFiles);
|
|
970
|
+
// Decide before reporting, so the report states what this run will actually
|
|
971
|
+
// do. Reporting first meant printing "Not deleted. Re-run with --prune" on a
|
|
972
|
+
// run that was about to delete — a message contradicting the very action it
|
|
973
|
+
// preceded, which is worse than no message.
|
|
974
|
+
const mayPrune = shouldPrune(removalPlan, options);
|
|
975
|
+
reportRemovalPlan(removalPlan, provenanceWasEstablished, mayPrune);
|
|
976
|
+
|
|
977
|
+
if (mayPrune && removalPlan.remove.length > 0) {
|
|
978
|
+
for (const { path: orphan } of removalPlan.remove) {
|
|
804
979
|
try {
|
|
805
980
|
unlinkSync(join(projectPath, orphan));
|
|
806
981
|
results.updated.push(`removed: ${orphan}`);
|
|
982
|
+
delete manifest.fileHashes[orphan];
|
|
983
|
+
// Deleting the file is how we give up the claim on that path, so a
|
|
984
|
+
// file the user later creates under the same name is not inherited.
|
|
985
|
+
manifest.provenance = forgetFileProvenance(manifest, orphan).provenance;
|
|
807
986
|
} catch {
|
|
808
987
|
// Ignore removal errors
|
|
809
988
|
}
|
|
810
989
|
}
|
|
811
990
|
}
|
|
812
991
|
|
|
992
|
+
// Provenance is only a complete account of what we own once a run has
|
|
993
|
+
// written the whole installed set. Until then every unrecorded file is
|
|
994
|
+
// `unknown` rather than `foreign`, and nothing gets deleted on the strength
|
|
995
|
+
// of an absent record. Establishing it here — after the writes, not before —
|
|
996
|
+
// is what makes the first upgrade conservative by construction rather than
|
|
997
|
+
// by a flag someone has to remember. (XSPEC-384 §4)
|
|
998
|
+
if (results.errors.length === 0) {
|
|
999
|
+
manifest.provenance = establishProvenance(manifest).provenance;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
813
1002
|
// Migrate display_language: if manifest options is missing display_language,
|
|
814
1003
|
// attempt to backfill from ~/.udsrc ui.language so the project honours the
|
|
815
1004
|
// language the user already configured globally.
|
|
@@ -1366,11 +1555,58 @@ export async function updateCommand(options) {
|
|
|
1366
1555
|
|
|
1367
1556
|
console.log();
|
|
1368
1557
|
|
|
1558
|
+
// 錯誤訊息單一出口閘門:問過才寫,而且從不覆寫。
|
|
1559
|
+
await offerErrorExitGate(projectPath, options);
|
|
1560
|
+
|
|
1369
1561
|
// Exit explicitly to prevent hanging.
|
|
1370
1562
|
// T11: a partial update exits non-zero so CI / scripts can detect the failure.
|
|
1371
1563
|
process.exit(updateIncomplete ? 1 : 0);
|
|
1372
1564
|
}
|
|
1373
1565
|
|
|
1566
|
+
/**
|
|
1567
|
+
* 提供錯誤訊息單一出口閘門——**顯示它是什麼、問過、才寫,而且絕不覆寫既有檔案。**
|
|
1568
|
+
*
|
|
1569
|
+
* 🔴 為什麼不自動寫:`uds` 是別人裝進自己 repo 的工具。未經同意往陌生人的專案
|
|
1570
|
+
* 寫一支可執行腳本,比它要修的那個問題更糟——那會變成信任問題,而不是 bug。
|
|
1571
|
+
* 採用者說不要,我們就不寫,而且下次 `uds check` 只會再提一次,不會偷偷放進去。
|
|
1572
|
+
*
|
|
1573
|
+
* ⚠️ 已存在就直接跳過,連問都不問:那個檔案裡的 CONFIG 是採用者自己填的,
|
|
1574
|
+
* 我們沒有立場拿範本蓋掉它。要更新內容是另一件事,不在這裡做。
|
|
1575
|
+
*/
|
|
1576
|
+
async function offerErrorExitGate(projectPath, options) {
|
|
1577
|
+
const dest = join(projectPath, 'scripts', 'check-error-exit.mjs');
|
|
1578
|
+
if (existsSync(dest)) return; // 他們自己的檔案
|
|
1579
|
+
if (!existsSync(join(projectPath, 'src'))) return; // 沒有原始碼就沒有這個問題
|
|
1580
|
+
|
|
1581
|
+
const src = join(getRepoRoot(), 'templates', 'gates', 'check-error-exit.mjs');
|
|
1582
|
+
if (!existsSync(src)) return; // 範本沒出貨——沉默,不要假裝提供了
|
|
1583
|
+
|
|
1584
|
+
console.log(chalk.bold(' 錯誤訊息單一出口閘門'));
|
|
1585
|
+
console.log(chalk.gray(' 防的是:每個呼叫端各自把錯誤回應拼成給人看的字串。'));
|
|
1586
|
+
console.log(chalk.gray(' 第一處是實作,第二處開始就會各寫各的,而畫面上只剩一句 Bad Request。'));
|
|
1587
|
+
console.log(chalk.gray(` 要寫入:scripts/check-error-exit.mjs(純 Node、零相依、${Math.round(readFileSync(src, 'utf-8').length / 1024)}KB)`));
|
|
1588
|
+
console.log(chalk.gray(' 寫入後它會 exit 2 直到你填好 CONFIG——那是刻意的,'));
|
|
1589
|
+
console.log(chalk.gray(' 一支在新 repo 裡靜靜回綠的閘門跟一支沒裝的無從分辨。'));
|
|
1590
|
+
console.log();
|
|
1591
|
+
|
|
1592
|
+
const ok = await confirmOrFail({
|
|
1593
|
+
message: '要把這道閘門寫進 scripts/check-error-exit.mjs 嗎?',
|
|
1594
|
+
defaultValue: false, // 🔴 預設不寫。沉默不是同意。
|
|
1595
|
+
options
|
|
1596
|
+
});
|
|
1597
|
+
if (!ok) {
|
|
1598
|
+
console.log(chalk.gray(' 跳過。`uds check` 之後會再提醒,但不會自己寫進去。'));
|
|
1599
|
+
console.log();
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
1604
|
+
writeFileSync(dest, readFileSync(src, 'utf-8'), 'utf-8');
|
|
1605
|
+
console.log(chalk.green(' ✓ 已寫入 scripts/check-error-exit.mjs'));
|
|
1606
|
+
console.log(chalk.gray(' 下一步:填好檔頭的 CONFIG,然後把它接進你的 CI 或 pre-commit。'));
|
|
1607
|
+
console.log();
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1374
1610
|
/**
|
|
1375
1611
|
* Regenerate integration files for all configured AI tools
|
|
1376
1612
|
* Reusable core logic that can be called from both updateIntegrationsOnly and configureCommand
|
|
@@ -1845,7 +2081,35 @@ async function updateSkillsOnly(projectPath, manifest, options) {
|
|
|
1845
2081
|
// Convert legacy format to new format
|
|
1846
2082
|
const legacyLocation = manifest.skills.location;
|
|
1847
2083
|
if (legacyLocation === 'user' || legacyLocation === 'project') {
|
|
1848
|
-
|
|
2084
|
+
// 🔴 A legacy manifest records a LOCATION, never a tool — and this used to
|
|
2085
|
+
// fill the gap with `claude-code`, unconditionally. For everyone who ran the
|
|
2086
|
+
// old `uds init -y` in a codex or opencode repo, that guess is wrong and
|
|
2087
|
+
// self-perpetuating: the fix to `init.js` would never reach them, because
|
|
2088
|
+
// update would keep re-installing to `.claude/skills/` on the strength of a
|
|
2089
|
+
// tool name nobody ever recorded. (XSPEC-408 §18.3 — Codex does not read
|
|
2090
|
+
// that directory; measured, two arms, 2026-09-08.)
|
|
2091
|
+
//
|
|
2092
|
+
// So re-derive from what is actually detected in the repo, and keep
|
|
2093
|
+
// `claude-code` only as the fallback for when nothing is detected — which is
|
|
2094
|
+
// exactly the case the old line was written for.
|
|
2095
|
+
let detected = {};
|
|
2096
|
+
try {
|
|
2097
|
+
detected = detectAITools(projectPath) || {};
|
|
2098
|
+
} catch {
|
|
2099
|
+
// Detection failing puts us in the "we do not know" case, which is exactly
|
|
2100
|
+
// what the old unconditional `claude-code` line was for. Never let a probe
|
|
2101
|
+
// for extra information break the update that would have worked without it.
|
|
2102
|
+
detected = {};
|
|
2103
|
+
}
|
|
2104
|
+
const detectedSkillsTools = Object.entries(detected)
|
|
2105
|
+
.filter(([, present]) => present)
|
|
2106
|
+
.map(([key]) => (key === 'claudeCode' ? 'claude-code' : key === 'geminiCli' ? 'gemini-cli' : key))
|
|
2107
|
+
.filter(agent => {
|
|
2108
|
+
const config = getAgentConfig(agent);
|
|
2109
|
+
return Boolean(config?.supportsSkills && config?.skills);
|
|
2110
|
+
});
|
|
2111
|
+
const agents = detectedSkillsTools.length > 0 ? detectedSkillsTools : ['claude-code'];
|
|
2112
|
+
for (const agent of agents) skillsInstallations.push({ agent, level: legacyLocation });
|
|
1849
2113
|
} else if (legacyLocation === 'marketplace') {
|
|
1850
2114
|
console.log(chalk.yellow(msg.skillsViaMarketplace || 'Skills installed via Marketplace'));
|
|
1851
2115
|
console.log(chalk.gray(` ${msg.updateViaMarketplace || 'Update through Plugin Marketplace'}`));
|
|
@@ -1913,8 +2177,16 @@ async function updateSkillsOnly(projectPath, manifest, options) {
|
|
|
1913
2177
|
}).join(', ');
|
|
1914
2178
|
|
|
1915
2179
|
if (result.totalErrors === 0) {
|
|
2180
|
+
// 🔴 `{count}` is worded "AI tools" in every locale, and this passed
|
|
2181
|
+
// `totalInstalled` — the number of skill FILES written, summed across targets.
|
|
2182
|
+
// A two-tool update of 55 skills printed "Updated Skills for 165 AI tools".
|
|
2183
|
+
// It was invisible while this branch only ran from the interactive flow; the
|
|
2184
|
+
// fix to `init.js` makes it the common path, so it stops being invisible.
|
|
1916
2185
|
spinner.succeed((msg.skillsUpdated || 'Updated Skills in {count} locations: {locations}')
|
|
1917
|
-
|
|
2186
|
+
// Counted from the same list that builds `locations`, so the number and the
|
|
2187
|
+
// names can never disagree — and it does not depend on the shape of the
|
|
2188
|
+
// installer's return value.
|
|
2189
|
+
.replace('{count}', fileBasedInstallations.length)
|
|
1918
2190
|
.replace('{locations}', locations));
|
|
1919
2191
|
} else {
|
|
1920
2192
|
spinner.warn((msg.skillsUpdatedWithErrors || 'Updated Skills with {errors} errors')
|
|
@@ -33,9 +33,12 @@ export class ClaudeCodeCompiler extends BaseCompiler {
|
|
|
33
33
|
hooks[trigger] = [];
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// Claude Code dispatches on `type`; an entry without one is skipped
|
|
37
|
+
// silently. Measured 2026-09-08 with both shapes in the same event of the
|
|
38
|
+
// same session: the object ran, the bare string did not.
|
|
36
39
|
hooks[trigger].push({
|
|
37
40
|
matcher,
|
|
38
|
-
hooks: [`node ${hook_script}`],
|
|
41
|
+
hooks: [{ type: 'command', command: `node ${hook_script}` }],
|
|
39
42
|
});
|
|
40
43
|
}
|
|
41
44
|
|
|
@@ -139,7 +139,19 @@ export const AI_AGENT_PATHS = {
|
|
|
139
139
|
user: join(homedir(), '.roo', 'workflows')
|
|
140
140
|
},
|
|
141
141
|
supportsMarketplace: false,
|
|
142
|
-
|
|
142
|
+
// 🔴 Was '.claude/skills/', with no provenance. Roo Code's docs ENUMERATE where it
|
|
143
|
+
// looks for skills — `.roo/skills/`, `.agents/skills/`, and their `~` twins — and
|
|
144
|
+
// `.claude/skills/` is not among them. An enumerated discovery list that omits a
|
|
145
|
+
// path is a statement, not silence (same shape as the Codex list that this field
|
|
146
|
+
// was falsified against by measurement).
|
|
147
|
+
//
|
|
148
|
+
// ⚠️ Docs only, 2026-09-08: NOT measured, because there is no Roo Code CLI on this
|
|
149
|
+
// machine. Acting on it anyway because the asymmetry is one-sided — wrong here
|
|
150
|
+
// means skills land in `.roo/skills/`, which the docs confirm is read; leaving it
|
|
151
|
+
// meant `uds init` routed Roo Code to Claude's marketplace and installed **zero**
|
|
152
|
+
// skills, which is what it actually did until this change.
|
|
153
|
+
// (roocodeinc.github.io/Roo-Code/features/skills)
|
|
154
|
+
fallbackSkillsPath: null,
|
|
143
155
|
supportsSkills: true,
|
|
144
156
|
supportsTask: true,
|
|
145
157
|
supportsAgents: true
|
|
@@ -169,7 +181,14 @@ export const AI_AGENT_PATHS = {
|
|
|
169
181
|
},
|
|
170
182
|
workflows: null,
|
|
171
183
|
supportsMarketplace: false,
|
|
172
|
-
|
|
184
|
+
// 🔴 Was '.claude/skills/'. MEASURED FALSE 2026-09-08 (XSPEC-408 §18.3): two probe
|
|
185
|
+
// arms differing only in install path. At `.agents/skills` Codex reported "Skill
|
|
186
|
+
// descriptions were shortened to fit the 2% skills context budget — Codex can
|
|
187
|
+
// still see every skill", 18,096 input tokens. At `.claude/skills` it said
|
|
188
|
+
// nothing, 15,235. The discovery list documented above never included that
|
|
189
|
+
// directory; this field asserted otherwise with no provenance, and `check.js`
|
|
190
|
+
// printed it to adopters as "Can use fallback".
|
|
191
|
+
fallbackSkillsPath: null,
|
|
173
192
|
supportsSkills: true,
|
|
174
193
|
supportsTask: false,
|
|
175
194
|
supportsAgents: true
|
|
@@ -251,28 +270,54 @@ export const AI_AGENT_PATHS = {
|
|
|
251
270
|
},
|
|
252
271
|
'antigravity': {
|
|
253
272
|
name: 'Google Antigravity',
|
|
254
|
-
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
|
|
273
|
+
// Raised from 'minimal' 2026-09-08. It was minimal because UDS installed
|
|
274
|
+
// nothing for it — the tier was describing our own gap, not the tool. It now
|
|
275
|
+
// gets 55 skills and an instruction file, the same as the other 'partial'
|
|
276
|
+
// tools, and passes all six of that tier's rules.
|
|
277
|
+
tier: 'partial',
|
|
278
|
+
// Skills path CONFIRMED 2026-09-08, three independent ways:
|
|
279
|
+
// 1. The agy 1.0.14 binary carries the literal template
|
|
280
|
+
// `{workspace}/.agents/skills/{skill_name}/SKILL.md`.
|
|
281
|
+
// 2. The guide shipped with the tool
|
|
282
|
+
// (~/.gemini/antigravity-cli/builtin/skills/antigravity_guide/references/ide.md)
|
|
283
|
+
// says `<project-root>/.agents/` holds "project-specific rules, custom skills,
|
|
284
|
+
// and plugins".
|
|
285
|
+
// 3. A live two-arm run: a skill at `.agents/skills/` was listed back with its
|
|
286
|
+
// description verbatim; a decoy at `.agent/skills/` was not listed at all.
|
|
287
|
+
//
|
|
288
|
+
// 🔴 BOTH candidates previously recorded here were wrong — the official-plugin path
|
|
289
|
+
// and `.agent/skills/` (singular). Neither had been tested; declining to install was
|
|
290
|
+
// the right call for six months, and the answer came from the tool's own binary, not
|
|
291
|
+
// from either document.
|
|
259
292
|
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
//
|
|
293
|
+
// 🔴 PRECONDITION the adopter must meet: agy only discovers `.agents/` when the
|
|
294
|
+
// directory is a registered Antigravity project (~/.gemini/projects.json). Measured:
|
|
295
|
+
// the same probe repo returned only the builtin skill before `agy --new-project` and
|
|
296
|
+
// both skills after. Installing here is correct, but a user who never opens the
|
|
297
|
+
// project in Antigravity will see nothing — that is the tool's condition, not ours.
|
|
265
298
|
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
skills:
|
|
299
|
+
// No `user` path: the binary has exactly one skills template and it is
|
|
300
|
+
// workspace-scoped. A user-level path would be invented, which is how the two wrong
|
|
301
|
+
// candidates above got written down in the first place.
|
|
302
|
+
skills: {
|
|
303
|
+
project: '.agents/skills/'
|
|
304
|
+
},
|
|
305
|
+
// Shown to the adopter after a successful install. A field rather than a
|
|
306
|
+
// special case for this tool: the next tool with a precondition fills it in
|
|
307
|
+
// instead of adding a branch. Without it the condition lives only in a spec,
|
|
308
|
+
// and the adopter — who reads neither — sees a successful install and an
|
|
309
|
+
// assistant that picked nothing up.
|
|
310
|
+
installNote:
|
|
311
|
+
'Antigravity only discovers .agents/ once the directory is a registered '
|
|
312
|
+
+ 'Antigravity project. Open it in the IDE, or run `agy --new-project` here '
|
|
313
|
+
+ 'once. Measured 2026-09-08: the same repo reported only the built-in skill '
|
|
314
|
+
+ 'before that and all of them after.',
|
|
270
315
|
commands: null,
|
|
271
316
|
agents: null,
|
|
272
317
|
workflows: null,
|
|
273
318
|
supportsMarketplace: false,
|
|
274
319
|
fallbackSkillsPath: null,
|
|
275
|
-
supportsSkills: true,
|
|
320
|
+
supportsSkills: true,
|
|
276
321
|
supportsTask: false,
|
|
277
322
|
supportsAgents: false
|
|
278
323
|
}
|
package/src/core/constants.js
CHANGED
|
@@ -94,16 +94,25 @@ export const SUPPORTED_AI_TOOLS = {
|
|
|
94
94
|
category: 'secondary',
|
|
95
95
|
supports: ['skills']
|
|
96
96
|
},
|
|
97
|
-
'roo'
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
// 🔴 Key was 'roo' and the file was 'ROO.md'. Every other module calls this tool
|
|
98
|
+
// `roo-code` (`ai-agent-paths.js`, `REGISTRY.json`, `agent-adapter.js`,
|
|
99
|
+
// `agents-installer.js`), so every lookup here MISSED — and the miss was silent:
|
|
100
|
+
// `getToolFileName()` ends in `return \`${tool}.md\``, so a real `uds init` in a Roo
|
|
101
|
+
// Code repo wrote its instructions to `roo-code.md` in the repo root. Roo Code's
|
|
102
|
+
// docs name `.roo/rules/`, `.roorules`, and `AGENTS.md`; `roo-code.md` is read by
|
|
103
|
+
// nothing, and `ROO.md` was never written by anything either.
|
|
104
|
+
// File value matches REGISTRY.json's `instructionFile` for this agent.
|
|
105
|
+
// (roocodeinc.github.io/Roo-Code/features/custom-instructions, read 2026-09-08)
|
|
106
|
+
'roo-code': {
|
|
107
|
+
name: 'Roo Code',
|
|
108
|
+
file: '.roo/rules/uds-standards.md',
|
|
100
109
|
format: 'markdown',
|
|
101
110
|
category: 'secondary',
|
|
102
111
|
supports: ['skills', 'workflows']
|
|
103
112
|
},
|
|
104
113
|
'antigravity': {
|
|
105
114
|
name: 'Antigravity',
|
|
106
|
-
file: '
|
|
115
|
+
file: '.agents/AGENTS.md',
|
|
107
116
|
format: 'markdown',
|
|
108
117
|
category: 'secondary',
|
|
109
118
|
supports: ['skills', 'workflows']
|
|
@@ -122,7 +131,12 @@ export const SUPPORTED_AI_TOOLS = {
|
|
|
122
131
|
*/
|
|
123
132
|
export const LEGACY_TOOL_MAPPINGS = {
|
|
124
133
|
'codex': 'opencode',
|
|
125
|
-
'copilot': 'github-copilot'
|
|
134
|
+
'copilot': 'github-copilot',
|
|
135
|
+
// Maps the OLD key to the CORRECT one, never the reverse — an alias pointing at
|
|
136
|
+
// the wrong name would make the wrong name authoritative. No manifest in any of
|
|
137
|
+
// the five adopting repos records 'roo' (checked 2026-09-08); this exists for
|
|
138
|
+
// manifests written before the rename.
|
|
139
|
+
'roo': 'roo-code'
|
|
126
140
|
};
|
|
127
141
|
|
|
128
142
|
/**
|
|
@@ -141,10 +155,13 @@ export const FILE_EXTENSIONS = {
|
|
|
141
155
|
/**
|
|
142
156
|
* Content modes for integration files
|
|
143
157
|
*/
|
|
158
|
+
// `FULL: 'full'` was removed here (XSPEC-357 R7). It never produced different
|
|
159
|
+
// output from INDEX — the generator's only test on the mode is `=== 'minimal'`
|
|
160
|
+
// — so it was a second name for one behaviour, offered to adopters as a
|
|
161
|
+
// distinct choice. See SUPPORTED_CONTENT_MODES in core/manifest.js.
|
|
144
162
|
export const CONTENT_MODES = {
|
|
145
163
|
MINIMAL: 'minimal', // Reference-only content
|
|
146
|
-
INDEX: 'index'
|
|
147
|
-
FULL: 'full' // Full standard content embedded
|
|
164
|
+
INDEX: 'index' // Standard index with descriptions + compliance instructions
|
|
148
165
|
};
|
|
149
166
|
|
|
150
167
|
/**
|
|
@@ -541,13 +558,27 @@ export function resolveToolKey(entry) {
|
|
|
541
558
|
if (typeof entry !== 'string' || entry.length === 0) return null;
|
|
542
559
|
if (SUPPORTED_AI_TOOLS[entry]) return entry;
|
|
543
560
|
const norm = entry.replace(/\\/g, '/');
|
|
561
|
+
|
|
562
|
+
// Exact or path-suffix only. A bare basename match would let `MY-CLAUDE.md`
|
|
563
|
+
// or `CLAUDE.md.bak` pass as the managed integration file.
|
|
564
|
+
//
|
|
565
|
+
// 🔴 Two passes, and the order matters. Antigravity's file is
|
|
566
|
+
// `.agents/AGENTS.md` while OpenCode's is `AGENTS.md`, so a single pass
|
|
567
|
+
// resolved `.agents/AGENTS.md` to OpenCode — whichever tool happened to be
|
|
568
|
+
// declared first won a path the other had spelled out in full. An exactly
|
|
569
|
+
// declared path must beat another tool's suffix, and among suffixes the
|
|
570
|
+
// longest (most specific) one wins.
|
|
571
|
+
for (const [key, cfg] of Object.entries(SUPPORTED_AI_TOOLS)) {
|
|
572
|
+
if (cfg?.file && norm === cfg.file) return key;
|
|
573
|
+
}
|
|
574
|
+
let best = null;
|
|
544
575
|
for (const [key, cfg] of Object.entries(SUPPORTED_AI_TOOLS)) {
|
|
545
576
|
if (!cfg?.file) continue;
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
577
|
+
if (norm.endsWith(`/${cfg.file}`) && (!best || cfg.file.length > best.len)) {
|
|
578
|
+
best = { key, len: cfg.file.length };
|
|
579
|
+
}
|
|
549
580
|
}
|
|
550
|
-
return null;
|
|
581
|
+
return best ? best.key : null;
|
|
551
582
|
}
|
|
552
583
|
|
|
553
584
|
/** Entry (tool key OR file path) → repo-relative integration file, or null. */
|