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/utils/hasher.js
CHANGED
|
@@ -3,6 +3,7 @@ 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
|
+
import { isProvenanceEstablished } from '../core/manifest.js';
|
|
6
7
|
|
|
7
8
|
// GitHub issue #155. `git config core.autocrlf true` (the common
|
|
8
9
|
// Windows default) rewrites LF to CRLF on checkout. The manifest's stored
|
|
@@ -240,6 +241,15 @@ function scanDirectory(dirPath, basePath) {
|
|
|
240
241
|
|
|
241
242
|
/**
|
|
242
243
|
* Scan for untracked files in .standards/ and integration locations
|
|
244
|
+
*
|
|
245
|
+
* ⚠ "Untracked" here means only "absent from `manifest.fileHashes`". It is NOT
|
|
246
|
+
* an ownership predicate, and must not be used to decide what may be deleted —
|
|
247
|
+
* that is what it was used for, and it is why a user's hand-written file was
|
|
248
|
+
* removed without warning (issue #168). Use `planStandardsRemovals` /
|
|
249
|
+
* `classifyFileOwnership`, which separate "did UDS write this" from "is this
|
|
250
|
+
* still shipped". This function survives for reporting callers that genuinely
|
|
251
|
+
* want the hash-table question. (XSPEC-384 R1)
|
|
252
|
+
*
|
|
243
253
|
* @param {string} projectPath - Project root path
|
|
244
254
|
* @param {Object} manifest - Manifest object
|
|
245
255
|
* @returns {string[]} Array of relative paths to untracked files
|
|
@@ -271,7 +281,7 @@ export function scanForUntrackedFiles(projectPath, manifest) {
|
|
|
271
281
|
'.clinerules',
|
|
272
282
|
'.github/copilot-instructions.md',
|
|
273
283
|
'CLAUDE.md',
|
|
274
|
-
'
|
|
284
|
+
'.agents/AGENTS.md'
|
|
275
285
|
];
|
|
276
286
|
|
|
277
287
|
for (const intFile of knownIntegrations) {
|
|
@@ -284,6 +294,161 @@ export function scanForUntrackedFiles(projectPath, manifest) {
|
|
|
284
294
|
return untracked;
|
|
285
295
|
}
|
|
286
296
|
|
|
297
|
+
/**
|
|
298
|
+
* The three states a file under `.standards/` can be in. (XSPEC-384 R1)
|
|
299
|
+
*
|
|
300
|
+
* `scanForUntrackedFiles` answers a two-state question — in `fileHashes` or
|
|
301
|
+
* not — and the caller then treated "not" as "not ours, delete it". The world
|
|
302
|
+
* has a third state: files UDS did write whose manifest record was lost. The
|
|
303
|
+
* old predicate folded that into the same branch as a user's own file, and the
|
|
304
|
+
* branch deletes.
|
|
305
|
+
*/
|
|
306
|
+
export const FILE_OWNERSHIP = {
|
|
307
|
+
/** UDS wrote this file; we may remove it when it stops being shipped. */
|
|
308
|
+
UDS: 'uds-owned',
|
|
309
|
+
/** Provenance is complete and does not name this file: it is not ours. */
|
|
310
|
+
FOREIGN: 'foreign',
|
|
311
|
+
/** No provenance yet — we cannot tell, so we must not act destructively. */
|
|
312
|
+
UNKNOWN: 'unknown'
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
/** Paths that are never candidates for anything, with the reason. */
|
|
316
|
+
const STRUCTURAL_EXCLUSIONS = new Map([
|
|
317
|
+
['.standards/manifest.json', 'the manifest itself']
|
|
318
|
+
]);
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Decide who owns one file under `.standards/`.
|
|
322
|
+
*
|
|
323
|
+
* @param {string} relPath - Path relative to project root (forward slashes)
|
|
324
|
+
* @param {Object} manifest - Manifest object
|
|
325
|
+
* @returns {string} One of FILE_OWNERSHIP
|
|
326
|
+
*/
|
|
327
|
+
export function classifyFileOwnership(relPath, manifest) {
|
|
328
|
+
const normalized = (relPath || '').replace(/\\/g, '/');
|
|
329
|
+
const provenanceFiles = manifest?.provenance?.files;
|
|
330
|
+
if (provenanceFiles && Object.prototype.hasOwnProperty.call(provenanceFiles, normalized)) {
|
|
331
|
+
return FILE_OWNERSHIP.UDS;
|
|
332
|
+
}
|
|
333
|
+
// Pre-provenance evidence. A path in `fileHashes` got there because UDS put
|
|
334
|
+
// it there, so it is still proof of authorship — just weaker proof, since it
|
|
335
|
+
// is also the table that loses entries. Reading it here (rather than only
|
|
336
|
+
// reading provenance) is what keeps the first upgrade from disowning every
|
|
337
|
+
// file installed before provenance existed.
|
|
338
|
+
if (manifest?.fileHashes && Object.prototype.hasOwnProperty.call(manifest.fileHashes, normalized)) {
|
|
339
|
+
return FILE_OWNERSHIP.UDS;
|
|
340
|
+
}
|
|
341
|
+
// No record either way. Whether that means "not ours" depends entirely on
|
|
342
|
+
// whether our records are complete yet.
|
|
343
|
+
return isProvenanceEstablished(manifest) ? FILE_OWNERSHIP.FOREIGN : FILE_OWNERSHIP.UNKNOWN;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Walk `.standards/` and classify everything in it.
|
|
348
|
+
*
|
|
349
|
+
* A walk, not a list of expected names: `.standards/` is an open set — UDS's
|
|
350
|
+
* own docs invite teams to add project-specific files to it — so any
|
|
351
|
+
* enumeration of "files we know about" is stale the moment someone adds one,
|
|
352
|
+
* and being absent from that enumeration is precisely what used to get a file
|
|
353
|
+
* deleted. (XSPEC-384 R1)
|
|
354
|
+
*
|
|
355
|
+
* @param {string} projectPath - Project root path
|
|
356
|
+
* @param {Object} manifest - Manifest object
|
|
357
|
+
* @returns {{scanned:number, excluded:Array, udsOwned:string[], foreign:string[], unknown:string[]}}
|
|
358
|
+
*/
|
|
359
|
+
export function classifyStandardsFiles(projectPath, manifest) {
|
|
360
|
+
const result = { scanned: 0, excluded: [], udsOwned: [], foreign: [], unknown: [] };
|
|
361
|
+
|
|
362
|
+
const standardsDir = join(projectPath, '.standards');
|
|
363
|
+
if (!existsSync(standardsDir)) return result;
|
|
364
|
+
|
|
365
|
+
for (const relPath of scanDirectory(standardsDir, projectPath)) {
|
|
366
|
+
const normalized = relPath.replace(/\\/g, '/');
|
|
367
|
+
result.scanned++;
|
|
368
|
+
|
|
369
|
+
const exclusionReason = STRUCTURAL_EXCLUSIONS.get(normalized);
|
|
370
|
+
if (exclusionReason) {
|
|
371
|
+
result.excluded.push({ path: normalized, reason: exclusionReason });
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
switch (classifyFileOwnership(normalized, manifest)) {
|
|
376
|
+
case FILE_OWNERSHIP.UDS:
|
|
377
|
+
result.udsOwned.push(normalized);
|
|
378
|
+
break;
|
|
379
|
+
case FILE_OWNERSHIP.FOREIGN:
|
|
380
|
+
result.foreign.push(normalized);
|
|
381
|
+
break;
|
|
382
|
+
default:
|
|
383
|
+
result.unknown.push(normalized);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return result;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Decide which files under `.standards/` `uds update` may delete.
|
|
392
|
+
*
|
|
393
|
+
* Deletion now needs two independent facts to line up, one per axis:
|
|
394
|
+
*
|
|
395
|
+
* 1. UDS wrote the file (ownership — provenance, or a legacy `fileHashes`
|
|
396
|
+
* entry as weaker evidence of the same thing)
|
|
397
|
+
* 2. UDS no longer ships it (currency — absence from `desiredFiles`, which
|
|
398
|
+
* the caller derives from the registry it just resolved)
|
|
399
|
+
*
|
|
400
|
+
* Every other combination is kept, and says why it was kept. The old rule
|
|
401
|
+
* collapsed both axes onto `fileHashes` membership, so it deleted files it had
|
|
402
|
+
* never written (#168) while retaining files it no longer shipped (#165).
|
|
403
|
+
*
|
|
404
|
+
* Nothing is removed when `desiredFiles` is null: a caller that cannot say what
|
|
405
|
+
* should exist has not established fact 2, and "I don't know" must not resolve
|
|
406
|
+
* to the destructive branch — that was the original defect.
|
|
407
|
+
*
|
|
408
|
+
* @param {string} projectPath - Project root path
|
|
409
|
+
* @param {Object} manifest - Manifest object
|
|
410
|
+
* @param {Set<string>|null} desiredFiles - Files the current registry says should exist
|
|
411
|
+
* @returns {{scanned:number, excluded:Array, remove:Array, keep:Array, census:Object}}
|
|
412
|
+
*/
|
|
413
|
+
export function planStandardsRemovals(projectPath, manifest, desiredFiles = null) {
|
|
414
|
+
const census = classifyStandardsFiles(projectPath, manifest);
|
|
415
|
+
const remove = [];
|
|
416
|
+
const keep = [];
|
|
417
|
+
|
|
418
|
+
const desiredKnown = desiredFiles instanceof Set;
|
|
419
|
+
|
|
420
|
+
for (const path of census.udsOwned) {
|
|
421
|
+
if (!desiredKnown) {
|
|
422
|
+
keep.push({ path, reason: 'UDS-owned, but this run could not determine what is still shipped' });
|
|
423
|
+
} else if (desiredFiles.has(path)) {
|
|
424
|
+
keep.push({ path, reason: 'UDS-owned and still shipped' });
|
|
425
|
+
} else {
|
|
426
|
+
remove.push({ path, reason: 'UDS-owned and no longer shipped by the registry' });
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
for (const path of census.foreign) {
|
|
431
|
+
keep.push({ path, reason: 'not written by UDS' });
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
for (const path of census.unknown) {
|
|
435
|
+
keep.push({ path, reason: 'ownership unknown (manifest predates provenance)' });
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
scanned: census.scanned,
|
|
440
|
+
excluded: census.excluded,
|
|
441
|
+
remove,
|
|
442
|
+
keep,
|
|
443
|
+
census: {
|
|
444
|
+
udsOwned: census.udsOwned.length,
|
|
445
|
+
foreign: census.foreign.length,
|
|
446
|
+
unknown: census.unknown.length,
|
|
447
|
+
desired: desiredKnown ? desiredFiles.size : null
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
287
452
|
/**
|
|
288
453
|
* Detect file format based on file path
|
|
289
454
|
* @param {string} filePath - File path
|
package/src/utils/hook-stats.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hook Statistics - Context-aware loading learning loop
|
|
3
3
|
*
|
|
4
|
-
* Records and analyzes trigger statistics from inject-standards.
|
|
4
|
+
* Records and analyzes trigger statistics from inject-standards.mjs hook.
|
|
5
5
|
* Privacy: never records full prompt content or file paths.
|
|
6
6
|
*
|
|
7
7
|
* @module utils/hook-stats
|
|
@@ -32,7 +32,10 @@ export function resolveContentModeForTool(tool, userContentMode) {
|
|
|
32
32
|
? { contentMode: 'minimal', level: 3 }
|
|
33
33
|
: { contentMode: 'index', level: 2 };
|
|
34
34
|
case 'partial':
|
|
35
|
-
|
|
35
|
+
// Was 'full'. Every partial-tier tool was being auto-assigned a mode
|
|
36
|
+
// that generated exactly the same bytes as 'index', so this is a rename
|
|
37
|
+
// of what these tools already got, not a change to it. (XSPEC-357 R7)
|
|
38
|
+
return { contentMode: 'index', level: 1 };
|
|
36
39
|
case 'preview':
|
|
37
40
|
return { contentMode: 'index', level: 2 };
|
|
38
41
|
case 'minimal':
|
|
@@ -70,6 +73,31 @@ function getToolFileName(tool) {
|
|
|
70
73
|
if (KNOWN_TOOL_FILES.has(tool) || /\.(md|yaml|yml|json)$/i.test(tool)) {
|
|
71
74
|
return tool;
|
|
72
75
|
}
|
|
76
|
+
|
|
77
|
+
// 🔴 A tool THIS system already knows, missing from THIS table, is an internal
|
|
78
|
+
// inconsistency — never a future tool. It must fail rather than get a generated
|
|
79
|
+
// filename.
|
|
80
|
+
//
|
|
81
|
+
// Measured 2026-09-08: `roo-code` is the key used by `ai-agent-paths.js`,
|
|
82
|
+
// `REGISTRY.json`, `agent-adapter.js` and `agents-installer.js`, while
|
|
83
|
+
// `SUPPORTED_AI_TOOLS` said `roo`. Every lookup here missed and fell through, so a
|
|
84
|
+
// real `uds init` in a Roo Code repo wrote its instructions to `roo-code.md` — a
|
|
85
|
+
// filename Roo Code's docs never mention. Nothing failed. Nothing warned. Same
|
|
86
|
+
// class as `.codex/skills/`, which this repo's own comments call "a directory UDS
|
|
87
|
+
// invented"; the line below was a *rule* for inventing them.
|
|
88
|
+
//
|
|
89
|
+
// ⚠️ The fallback itself is KEPT, because it is a deliberate design decision, not
|
|
90
|
+
// an accident: three tests assert it as "forward compatibility with future tools".
|
|
91
|
+
// Narrowing beats reversing — a genuinely unknown name still gets `<name>.md`; only
|
|
92
|
+
// the self-contradiction fails.
|
|
93
|
+
if (getAgentConfig(normalizedName)) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`getToolFileName: "${tool}" is a known agent in ai-agent-paths.js but is missing ` +
|
|
96
|
+
'from SUPPORTED_AI_TOOLS in cli/src/core/constants.js. Add it with the file that ' +
|
|
97
|
+
`tool actually reads. Refusing to invent "${tool}.md" — that is how a Roo Code ` +
|
|
98
|
+
'repo got its instructions written to a file Roo Code never reads.'
|
|
99
|
+
);
|
|
100
|
+
}
|
|
73
101
|
return `${tool}.md`;
|
|
74
102
|
}
|
|
75
103
|
|
|
@@ -2398,6 +2426,52 @@ All responses should be in **Traditional Chinese (繁體中文)**, with technica
|
|
|
2398
2426
|
}
|
|
2399
2427
|
};
|
|
2400
2428
|
|
|
2429
|
+
/**
|
|
2430
|
+
* Generate the index disclosure that opens every UDS-managed block.
|
|
2431
|
+
*
|
|
2432
|
+
* XSPEC-357 R7 — the block an adopter receives is an index. In no content mode
|
|
2433
|
+
* does it carry rule bodies, and inlining them is not an option: 143 `.ai.yaml`
|
|
2434
|
+
* files come to roughly 248k tokens. What can be fixed is the block pretending
|
|
2435
|
+
* otherwise. A file that lists 72 standard paths under a heading reading
|
|
2436
|
+
* "Standards Compliance Instructions" reads, to an agent, like the compliance
|
|
2437
|
+
* instructions — and Codex was measured on 2026-07-23 doing exactly that:
|
|
2438
|
+
* it enumerated the standards and opened none of them.
|
|
2439
|
+
*
|
|
2440
|
+
* This wording was added to the universal `AGENTS.md` summary on 2026-08-18
|
|
2441
|
+
* (`generateAgentsMdSummary`). That fixed one of the two producers. The other
|
|
2442
|
+
* one is this — and the split is worse than it sounds, because the two are
|
|
2443
|
+
* mutually exclusive for the same filename: selecting codex or opencode turns
|
|
2444
|
+
* the universal summary OFF and routes `AGENTS.md` through here instead. The
|
|
2445
|
+
* disclosure written for Codex was therefore in the only file a Codex adopter
|
|
2446
|
+
* never receives. Measured 2026-08-20 before this change: 8 distinct adopter
|
|
2447
|
+
* files across 3 content modes, 0 carrying the disclosure.
|
|
2448
|
+
*
|
|
2449
|
+
* @param {string} format - Output format: 'markdown' or 'plaintext'
|
|
2450
|
+
* @param {string} language - Language: 'en', 'zh-tw', 'zh-cn' or 'bilingual'
|
|
2451
|
+
* @returns {string} Disclosure paragraph, already format-adjusted
|
|
2452
|
+
*/
|
|
2453
|
+
export function generateIndexDisclosure(format, language = 'en') {
|
|
2454
|
+
const lines = language === 'en'
|
|
2455
|
+
? [
|
|
2456
|
+
'**This block is an index, not the standards.** The rules are NOT reproduced here.',
|
|
2457
|
+
'Before acting on anything below, open the relevant file under `.standards/` and',
|
|
2458
|
+
'follow its contents. Working from this block alone means working without the',
|
|
2459
|
+
'standards.'
|
|
2460
|
+
]
|
|
2461
|
+
: [
|
|
2462
|
+
'**這個區塊是索引,不是標準本文。** 規則並未複製於此。',
|
|
2463
|
+
'在依照下方任何一項行動之前,請打開 `.standards/` 底下對應的檔案並遵守其內容。',
|
|
2464
|
+
'只憑這個區塊工作,等同於沒有採用標準。'
|
|
2465
|
+
];
|
|
2466
|
+
|
|
2467
|
+
if (format === 'markdown') {
|
|
2468
|
+
return lines.map((l) => `> ${l}`).join('\n');
|
|
2469
|
+
}
|
|
2470
|
+
// Plaintext targets (.cursorrules / .clinerules / .windsurfrules) render
|
|
2471
|
+
// neither blockquotes nor backticks, so both are stripped rather than shown.
|
|
2472
|
+
return lines.map((l) => l.replace(/\*\*/g, '').replace(/`/g, '')).join('\n');
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2401
2475
|
/**
|
|
2402
2476
|
* Generate minimal standards reference for minimal content mode
|
|
2403
2477
|
* @param {string[]} installedStandards - List of installed standard file paths
|
|
@@ -2733,6 +2807,10 @@ export function generateIntegrationContent(config) {
|
|
|
2733
2807
|
}
|
|
2734
2808
|
|
|
2735
2809
|
if (installedStandards.length > 0) {
|
|
2810
|
+
// XSPEC-357 R7 — every mode, every tool. Placed first because it is a
|
|
2811
|
+
// precondition for reading the rest, not a footnote to it.
|
|
2812
|
+
standardsContent = generateIndexDisclosure(format, language) + '\n\n' + standardsContent;
|
|
2813
|
+
|
|
2736
2814
|
if (contentMode === 'minimal') {
|
|
2737
2815
|
// Minimal mode: simple reference list
|
|
2738
2816
|
standardsContent += generateMinimalStandardsReference(
|
|
@@ -296,7 +296,10 @@ export function getToolFromPath(integrationPath) {
|
|
|
296
296
|
'.windsurfrules': 'windsurf',
|
|
297
297
|
'.clinerules': 'cline',
|
|
298
298
|
'.github/copilot-instructions.md': 'copilot',
|
|
299
|
-
|
|
299
|
+
// Antigravity never read INSTRUCTIONS.md.
|
|
300
|
+
// Measured 2026-09-08 with two positive controls in the same run: tokens planted in `AGENTS.md` and `.agents/AGENTS.md` both came back with correct attribution; the one in INSTRUCTIONS.md did not.
|
|
301
|
+
// `.agents/AGENTS.md` is used rather than the repo root so it does not collide with Codex/OpenCode, which both target root AGENTS.md.
|
|
302
|
+
'.agents/AGENTS.md': 'antigravity',
|
|
300
303
|
'CLAUDE.md': 'claude-code',
|
|
301
304
|
'.standards/CLAUDE.md': 'claude-code',
|
|
302
305
|
'AGENTS.md': 'codex'
|
|
@@ -4,8 +4,44 @@
|
|
|
4
4
|
* Generates AI-optimized YAML from parsed Markdown structure.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { execFileSync } from 'node:child_process';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { dirname, resolve } from 'node:path';
|
|
10
|
+
|
|
7
11
|
import { SECTION_MAPPINGS, STANDARD_ID_MAPPING } from './conversion-rules.js';
|
|
8
12
|
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
// cli/src/utils/yaml-generator.js -> cli/src/utils -> cli/src -> cli -> repo root
|
|
16
|
+
const REPO_ROOT = resolve(__dirname, '../../..');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* XSPEC-392 R2: `meta.updated` must reflect when the source file's content
|
|
20
|
+
* actually last changed, not a hand-typed `**Last Updated**` line (which can
|
|
21
|
+
* go stale relative to the file it lives in), and — this is the part that is
|
|
22
|
+
* not negotiable — never a `new Date()` value standing in for "the day this
|
|
23
|
+
* script happened to run".
|
|
24
|
+
*
|
|
25
|
+
* Returns an ISO date (YYYY-MM-DD) from git history for `relativeSourcePath`,
|
|
26
|
+
* or `null` when git has no record for it (untracked/new file, git missing,
|
|
27
|
+
* or any other failure). A missing `updated` field is honest; a fabricated
|
|
28
|
+
* one is not — so callers must omit the field entirely rather than fall back
|
|
29
|
+
* to any other date source.
|
|
30
|
+
*/
|
|
31
|
+
function getGitLastModifiedDate(relativeSourcePath) {
|
|
32
|
+
if (!relativeSourcePath) return null;
|
|
33
|
+
try {
|
|
34
|
+
const out = execFileSync(
|
|
35
|
+
'git',
|
|
36
|
+
['log', '-1', '--format=%ad', '--date=short', '--', relativeSourcePath],
|
|
37
|
+
{ cwd: REPO_ROOT, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }
|
|
38
|
+
).trim();
|
|
39
|
+
return out || null;
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
9
45
|
/**
|
|
10
46
|
* Generate AI-YAML structure from parsed Markdown
|
|
11
47
|
* @param {Object} parsed - Parsed Markdown structure from md-parser
|
|
@@ -18,16 +54,22 @@ export function generateAiYaml(parsed, options = {}) {
|
|
|
18
54
|
// Generate ID
|
|
19
55
|
const id = generateStandardId(filename, parsed.metadata.title);
|
|
20
56
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
description: parsed.purpose || parsed.metadata.title || ''
|
|
29
|
-
}
|
|
57
|
+
const sourcePath = inferSourcePath(filename, locale);
|
|
58
|
+
|
|
59
|
+
// Build YAML structure. `updated` is set from git history below, and is
|
|
60
|
+
// deliberately absent (not null) when git has no record for the file —
|
|
61
|
+
// see getGitLastModifiedDate.
|
|
62
|
+
const meta = {
|
|
63
|
+
version: parsed.metadata.version || '1.0.0'
|
|
30
64
|
};
|
|
65
|
+
const gitUpdated = getGitLastModifiedDate(sourcePath);
|
|
66
|
+
if (gitUpdated) {
|
|
67
|
+
meta.updated = gitUpdated;
|
|
68
|
+
}
|
|
69
|
+
meta.source = sourcePath;
|
|
70
|
+
meta.description = parsed.purpose || parsed.metadata.title || '';
|
|
71
|
+
|
|
72
|
+
const yaml = { id, meta };
|
|
31
73
|
|
|
32
74
|
// Add language if locale specified
|
|
33
75
|
if (locale) {
|
package/standards-registry.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.9.0",
|
|
4
4
|
"lastUpdated": "2026-05-13",
|
|
5
5
|
"description": "Standards registry for universal-dev-standards with integrated skills and AI-optimized formats",
|
|
6
6
|
"formats": {
|
|
@@ -58,14 +58,14 @@
|
|
|
58
58
|
"standards": {
|
|
59
59
|
"name": "universal-dev-standards",
|
|
60
60
|
"url": "https://github.com/AsiaOstrich/universal-dev-standards",
|
|
61
|
-
"version": "6.
|
|
61
|
+
"version": "6.9.0"
|
|
62
62
|
},
|
|
63
63
|
"skills": {
|
|
64
64
|
"name": "universal-dev-standards",
|
|
65
65
|
"url": "https://github.com/AsiaOstrich/universal-dev-standards",
|
|
66
66
|
"localPath": "skills",
|
|
67
67
|
"rawUrl": "https://raw.githubusercontent.com/AsiaOstrich/universal-dev-standards/main/skills",
|
|
68
|
-
"version": "6.
|
|
68
|
+
"version": "6.9.0",
|
|
69
69
|
"note": "Skills are now included in the main repository under skills/"
|
|
70
70
|
}
|
|
71
71
|
},
|
|
@@ -736,6 +736,17 @@
|
|
|
736
736
|
"category": "core",
|
|
737
737
|
"description": "Four behavioral disciplines for AI agents: Ask (surface assumptions), Simple (minimum code), Precision (scope only what's needed), Test (loop until verified)"
|
|
738
738
|
},
|
|
739
|
+
{
|
|
740
|
+
"id": "turn-completion-integrity",
|
|
741
|
+
"name": "Turn Completion Integrity",
|
|
742
|
+
"nameZh": "回合收尾完整性標準",
|
|
743
|
+
"source": {
|
|
744
|
+
"human": "core/turn-completion-integrity.md",
|
|
745
|
+
"ai": "ai/standards/turn-completion-integrity.ai.yaml"
|
|
746
|
+
},
|
|
747
|
+
"category": "core",
|
|
748
|
+
"description": "An agent must not end a turn having stated a next action it did not take; enforced at turn end by a Stop hook, with a per-language corpus that runs in CI"
|
|
749
|
+
},
|
|
739
750
|
{
|
|
740
751
|
"id": "ai-friendly-architecture",
|
|
741
752
|
"name": "AI-Friendly Architecture",
|
|
@@ -1569,7 +1580,7 @@
|
|
|
1569
1580
|
"id": "antigravity-integration",
|
|
1570
1581
|
"name": "Google Antigravity Instructions",
|
|
1571
1582
|
"nameZh": "Google Antigravity 指令",
|
|
1572
|
-
"source": "integrations/google-antigravity/
|
|
1583
|
+
"source": "integrations/google-antigravity/AGENTS.md",
|
|
1573
1584
|
"category": "integration",
|
|
1574
1585
|
"skillName": null,
|
|
1575
1586
|
"description": "System prompt snippets for Google Antigravity agent",
|
|
@@ -1766,6 +1777,18 @@
|
|
|
1766
1777
|
"skillName": null,
|
|
1767
1778
|
"description": "Aim a fix at the set, not the member. Walk the set from the source the system reads, never from a typed list; print the denominator and what was excluded; prove the check non-vacuous per sub-set"
|
|
1768
1779
|
},
|
|
1780
|
+
{
|
|
1781
|
+
"id": "deferred-item-exit",
|
|
1782
|
+
"name": "Deferred Item Exit Standard",
|
|
1783
|
+
"nameZh": "延後項目出口標準",
|
|
1784
|
+
"source": {
|
|
1785
|
+
"human": "core/deferred-item-exit.md",
|
|
1786
|
+
"ai": "ai/standards/deferred-item-exit.ai.yaml"
|
|
1787
|
+
},
|
|
1788
|
+
"category": "reference",
|
|
1789
|
+
"skillName": null,
|
|
1790
|
+
"description": "A deferred item must leave the document: a traceable exit outside it, identified beside the item. The carrier is deliberately unspecified. A present link is not a working link — verified and unverified links are two states, never one green. Anchor on document structure, not wording"
|
|
1791
|
+
},
|
|
1769
1792
|
{
|
|
1770
1793
|
"id": "verification-evidence",
|
|
1771
1794
|
"name": "Verification Evidence Standard",
|
|
@@ -2260,7 +2283,7 @@
|
|
|
2260
2283
|
"id": "license-compliance",
|
|
2261
2284
|
"name": "License Compliance Standards",
|
|
2262
2285
|
"nameZh": "授權合規標準",
|
|
2263
|
-
"version": "6.
|
|
2286
|
+
"version": "6.9.0",
|
|
2264
2287
|
"source": {
|
|
2265
2288
|
"human": "core/license-compliance.md",
|
|
2266
2289
|
"ai": "ai/standards/license-compliance.ai.yaml"
|
|
@@ -2272,7 +2295,7 @@
|
|
|
2272
2295
|
"id": "verification-oracle",
|
|
2273
2296
|
"name": "Verification Oracle Standards",
|
|
2274
2297
|
"nameZh": "驗證 Oracle 標準",
|
|
2275
|
-
"version": "6.
|
|
2298
|
+
"version": "6.9.0",
|
|
2276
2299
|
"source": {
|
|
2277
2300
|
"human": "core/verification-oracle.md",
|
|
2278
2301
|
"ai": "ai/standards/verification-oracle.ai.yaml"
|
|
@@ -2284,7 +2307,7 @@
|
|
|
2284
2307
|
"id": "model-provenance",
|
|
2285
2308
|
"name": "Model Provenance Policy Standards",
|
|
2286
2309
|
"nameZh": "模型來源政策標準",
|
|
2287
|
-
"version": "6.
|
|
2310
|
+
"version": "6.9.0",
|
|
2288
2311
|
"source": {
|
|
2289
2312
|
"human": "core/model-provenance.md",
|
|
2290
2313
|
"ai": "ai/standards/model-provenance.ai.yaml"
|
|
@@ -2296,7 +2319,7 @@
|
|
|
2296
2319
|
"id": "resource-cost-boundary",
|
|
2297
2320
|
"name": "Resource / Cost Boundary Declaration Standards",
|
|
2298
2321
|
"nameZh": "資源/成本邊界宣告標準",
|
|
2299
|
-
"version": "6.
|
|
2322
|
+
"version": "6.9.0",
|
|
2300
2323
|
"source": {
|
|
2301
2324
|
"human": "core/resource-cost-boundary.md",
|
|
2302
2325
|
"ai": "ai/standards/resource-cost-boundary.ai.yaml"
|