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.
Files changed (194) hide show
  1. package/bin/uds.js +12 -2
  2. package/bundled/ai/standards/acceptance-criteria-traceability.ai.yaml +14 -2
  3. package/bundled/ai/standards/adr-standards.ai.yaml +14 -2
  4. package/bundled/ai/standards/code-review.ai.yaml +13 -3
  5. package/bundled/ai/standards/commit-message.ai.yaml +8 -4
  6. package/bundled/ai/standards/deferred-item-exit.ai.yaml +225 -0
  7. package/bundled/ai/standards/feature-discovery-standards.ai.yaml +14 -2
  8. package/bundled/ai/standards/governance-layer.ai.yaml +128 -2
  9. package/bundled/ai/standards/logging.ai.yaml +2 -2
  10. package/bundled/ai/standards/retrospective-standards.ai.yaml +14 -2
  11. package/bundled/ai/standards/reverse-engineering-standards.ai.yaml +73 -2
  12. package/bundled/ai/standards/security-standards.ai.yaml +2 -2
  13. package/bundled/ai/standards/spec-driven-development.ai.yaml +14 -2
  14. package/bundled/ai/standards/tech-debt-standards.ai.yaml +87 -3
  15. package/bundled/ai/standards/turn-completion-integrity.ai.yaml +131 -0
  16. package/bundled/core/acceptance-criteria-traceability.md +5 -2
  17. package/bundled/core/adr-standards.md +26 -2
  18. package/bundled/core/code-review-checklist.md +5 -2
  19. package/bundled/core/context-aware-loading.md +1 -1
  20. package/bundled/core/deferred-item-exit.md +254 -0
  21. package/bundled/core/feature-discovery-standards.md +5 -1
  22. package/bundled/core/governance-layer.md +114 -2
  23. package/bundled/core/retrospective-standards.md +4 -2
  24. package/bundled/core/reverse-engineering-standards.md +81 -2
  25. package/bundled/core/spec-driven-development.md +8 -2
  26. package/bundled/core/tech-debt-standards.md +67 -8
  27. package/bundled/core/turn-completion-integrity.md +196 -0
  28. package/bundled/hooks/check-dangerous-cmd.mjs +60 -0
  29. package/bundled/hooks/check-logging-standard.mjs +59 -0
  30. package/bundled/hooks/check-turn-completion.mjs +233 -0
  31. package/bundled/hooks/inject-standards.mjs +183 -0
  32. package/bundled/hooks/telemetry-wrapper.mjs +77 -0
  33. package/bundled/hooks/turn-completion/detect.mjs +99 -0
  34. package/bundled/hooks/turn-completion/locales/en.mjs +159 -0
  35. package/bundled/hooks/turn-completion/locales/zh-TW.mjs +166 -0
  36. package/bundled/hooks/validate-commit-msg.mjs +104 -0
  37. package/bundled/locales/zh-CN/CHANGELOG.md +47 -3
  38. package/bundled/locales/zh-CN/CLAUDE.md +1 -1
  39. package/bundled/locales/zh-CN/README.md +2 -2
  40. package/bundled/locales/zh-CN/SECURITY.md +1 -1
  41. package/bundled/locales/zh-CN/core/adr-standards.md +1 -1
  42. package/bundled/locales/zh-CN/core/governance-layer.md +118 -6
  43. package/bundled/locales/zh-CN/core/retrospective-standards.md +1 -1
  44. package/bundled/locales/zh-CN/core/tech-debt-standards.md +71 -4
  45. package/bundled/locales/zh-CN/core/turn-completion-integrity.md +190 -0
  46. package/bundled/locales/zh-CN/docs/CHEATSHEET.md +8 -1
  47. package/bundled/locales/zh-CN/docs/CLI-INIT-OPTIONS.md +29 -68
  48. package/bundled/locales/zh-CN/docs/FEATURE-REFERENCE.md +25 -15
  49. package/bundled/locales/zh-CN/docs/USAGE-MODES-COMPARISON.md +1 -2
  50. package/bundled/locales/zh-CN/integrations/google-antigravity/{INSTRUCTIONS.md → AGENTS.md} +1 -1
  51. package/bundled/locales/zh-CN/integrations/google-antigravity/README.md +3 -3
  52. package/bundled/locales/zh-CN/skills/atdd-assistant/SKILL.md +2 -0
  53. package/bundled/locales/zh-CN/skills/bdd-assistant/SKILL.md +2 -0
  54. package/bundled/locales/zh-CN/skills/brainstorm-assistant/SKILL.md +22 -12
  55. package/bundled/locales/zh-CN/skills/brainstorm-assistant/guide.md +12 -9
  56. package/bundled/locales/zh-CN/skills/code-review-assistant/SKILL.md +1 -0
  57. package/bundled/locales/zh-CN/skills/commands/brainstorm.md +17 -13
  58. package/bundled/locales/zh-CN/skills/commands/config.md +0 -1
  59. package/bundled/locales/zh-CN/skills/commands/init.md +1 -2
  60. package/bundled/locales/zh-CN/skills/commit-standards/SKILL.md +2 -0
  61. package/bundled/locales/zh-CN/skills/contract-test-assistant/SKILL.md +1 -0
  62. package/bundled/locales/zh-CN/skills/dev-methodology/SKILL.md +2 -0
  63. package/bundled/locales/zh-CN/skills/observability-assistant/SKILL.md +1 -0
  64. package/bundled/locales/zh-CN/skills/project-structure-guide/SKILL.md +1 -0
  65. package/bundled/locales/zh-CN/skills/release-standards/SKILL.md +3 -0
  66. package/bundled/locales/zh-CN/skills/requirement-assistant/SKILL.md +2 -0
  67. package/bundled/locales/zh-CN/skills/reverse-engineer/SKILL.md +3 -0
  68. package/bundled/locales/zh-CN/skills/runbook-assistant/SKILL.md +1 -0
  69. package/bundled/locales/zh-CN/skills/slo-assistant/SKILL.md +1 -0
  70. package/bundled/locales/zh-CN/skills/tdd-assistant/SKILL.md +2 -0
  71. package/bundled/locales/zh-TW/CHANGELOG.md +47 -3
  72. package/bundled/locales/zh-TW/CLAUDE.md +1 -1
  73. package/bundled/locales/zh-TW/README.md +2 -2
  74. package/bundled/locales/zh-TW/SECURITY.md +1 -1
  75. package/bundled/locales/zh-TW/core/acceptance-criteria-traceability.md +2 -0
  76. package/bundled/locales/zh-TW/core/adr-standards.md +26 -5
  77. package/bundled/locales/zh-TW/core/code-review-checklist.md +2 -0
  78. package/bundled/locales/zh-TW/core/container-image-standards.md +2 -2
  79. package/bundled/locales/zh-TW/core/contract-testing-standards.md +2 -2
  80. package/bundled/locales/zh-TW/core/cross-flow-regression.md +8 -7
  81. package/bundled/locales/zh-TW/core/data-contract.md +2 -2
  82. package/bundled/locales/zh-TW/core/data-migration-testing.md +2 -2
  83. package/bundled/locales/zh-TW/core/data-pipeline.md +2 -2
  84. package/bundled/locales/zh-TW/core/deferred-item-exit.md +251 -0
  85. package/bundled/locales/zh-TW/core/documentation-writing-standards.md +228 -3
  86. package/bundled/locales/zh-TW/core/full-coverage-testing.md +15 -2
  87. package/bundled/locales/zh-TW/core/governance-layer.md +118 -5
  88. package/bundled/locales/zh-TW/core/iac-design-principles.md +2 -2
  89. package/bundled/locales/zh-TW/core/incident-response.md +2 -2
  90. package/bundled/locales/zh-TW/core/model-provenance.md +4 -2
  91. package/bundled/locales/zh-TW/core/pii-classification.md +42 -6
  92. package/bundled/locales/zh-TW/core/prd-standards.md +4 -2
  93. package/bundled/locales/zh-TW/core/product-metrics-standards.md +4 -2
  94. package/bundled/locales/zh-TW/core/release-readiness-gate.md +2 -2
  95. package/bundled/locales/zh-TW/core/resource-cost-boundary.md +2 -2
  96. package/bundled/locales/zh-TW/core/retrospective-standards.md +5 -3
  97. package/bundled/locales/zh-TW/core/reverse-engineering-standards.md +83 -5
  98. package/bundled/locales/zh-TW/core/runbook.md +2 -2
  99. package/bundled/locales/zh-TW/core/schema-evolution.md +2 -2
  100. package/bundled/locales/zh-TW/core/secret-management-standards.md +2 -2
  101. package/bundled/locales/zh-TW/core/slo-sli.md +2 -2
  102. package/bundled/locales/zh-TW/core/spec-driven-development.md +2 -0
  103. package/bundled/locales/zh-TW/core/tech-debt-standards.md +71 -4
  104. package/bundled/locales/zh-TW/core/turn-completion-integrity.md +190 -0
  105. package/bundled/locales/zh-TW/core/user-journey-testing.md +2 -2
  106. package/bundled/locales/zh-TW/core/user-story-mapping.md +2 -2
  107. package/bundled/locales/zh-TW/core/verification-oracle.md +2 -2
  108. package/bundled/locales/zh-TW/docs/CHEATSHEET.md +8 -1
  109. package/bundled/locales/zh-TW/docs/CLI-INIT-OPTIONS.md +29 -68
  110. package/bundled/locales/zh-TW/docs/FEATURE-REFERENCE.md +25 -15
  111. package/bundled/locales/zh-TW/docs/USAGE-MODES-COMPARISON.md +1 -2
  112. package/bundled/locales/zh-TW/integrations/google-antigravity/{INSTRUCTIONS.md → AGENTS.md} +1 -1
  113. package/bundled/locales/zh-TW/integrations/google-antigravity/README.md +3 -3
  114. package/bundled/locales/zh-TW/skills/adr-assistant/SKILL.md +1 -1
  115. package/bundled/locales/zh-TW/skills/atdd-assistant/SKILL.md +2 -0
  116. package/bundled/locales/zh-TW/skills/bdd-assistant/SKILL.md +2 -0
  117. package/bundled/locales/zh-TW/skills/brainstorm-assistant/SKILL.md +22 -12
  118. package/bundled/locales/zh-TW/skills/brainstorm-assistant/guide.md +12 -9
  119. package/bundled/locales/zh-TW/skills/code-review-assistant/SKILL.md +1 -0
  120. package/bundled/locales/zh-TW/skills/commands/brainstorm.md +17 -13
  121. package/bundled/locales/zh-TW/skills/commands/config.md +0 -1
  122. package/bundled/locales/zh-TW/skills/commands/init.md +1 -2
  123. package/bundled/locales/zh-TW/skills/commit-standards/SKILL.md +2 -0
  124. package/bundled/locales/zh-TW/skills/contract-test-assistant/SKILL.md +2 -1
  125. package/bundled/locales/zh-TW/skills/dev-methodology/SKILL.md +2 -0
  126. package/bundled/locales/zh-TW/skills/dev-workflow-guide/SKILL.md +1 -1
  127. package/bundled/locales/zh-TW/skills/knowledge-graph/guide.md +2 -2
  128. package/bundled/locales/zh-TW/skills/migration-assistant/SKILL.md +1 -1
  129. package/bundled/locales/zh-TW/skills/observability-assistant/SKILL.md +1 -0
  130. package/bundled/locales/zh-TW/skills/project-discovery/SKILL.md +1 -0
  131. package/bundled/locales/zh-TW/skills/project-structure-guide/SKILL.md +1 -0
  132. package/bundled/locales/zh-TW/skills/release-standards/SKILL.md +3 -0
  133. package/bundled/locales/zh-TW/skills/requirement-assistant/SKILL.md +2 -0
  134. package/bundled/locales/zh-TW/skills/reverse-engineer/SKILL.md +3 -0
  135. package/bundled/locales/zh-TW/skills/runbook-assistant/SKILL.md +1 -0
  136. package/bundled/locales/zh-TW/skills/slo-assistant/SKILL.md +1 -0
  137. package/bundled/locales/zh-TW/skills/tdd-assistant/SKILL.md +2 -0
  138. package/bundled/skills/atdd-assistant/SKILL.md +2 -0
  139. package/bundled/skills/bdd-assistant/SKILL.md +2 -0
  140. package/bundled/skills/brainstorm-assistant/SKILL.md +31 -13
  141. package/bundled/skills/brainstorm-assistant/guide.md +9 -6
  142. package/bundled/skills/code-review-assistant/SKILL.md +1 -0
  143. package/bundled/skills/commands/brainstorm.md +12 -9
  144. package/bundled/skills/commands/config.md +0 -1
  145. package/bundled/skills/commands/init.md +2 -3
  146. package/bundled/skills/commit-standards/SKILL.md +2 -0
  147. package/bundled/skills/contract-test-assistant/SKILL.md +1 -0
  148. package/bundled/skills/dev-methodology/SKILL.md +4 -0
  149. package/bundled/skills/observability-assistant/SKILL.md +1 -0
  150. package/bundled/skills/project-discovery/SKILL.md +1 -0
  151. package/bundled/skills/project-structure-guide/SKILL.md +1 -0
  152. package/bundled/skills/release-standards/SKILL.md +3 -0
  153. package/bundled/skills/requirement-assistant/SKILL.md +2 -0
  154. package/bundled/skills/reverse-engineer/SKILL.md +3 -0
  155. package/bundled/skills/runbook-assistant/SKILL.md +1 -0
  156. package/bundled/skills/slo-assistant/SKILL.md +1 -0
  157. package/bundled/skills/tdd-assistant/SKILL.md +2 -0
  158. package/bundled/templates/.ai-context.yaml.template +194 -0
  159. package/bundled/templates/CLAUDE.md.template +145 -0
  160. package/bundled/templates/DESIGN.md +237 -0
  161. package/bundled/templates/SKILL-BRIEF-TEMPLATE.md +57 -0
  162. package/bundled/templates/SKILL-CANDIDATES.md +39 -0
  163. package/bundled/templates/gates/check-error-exit.mjs +309 -0
  164. package/bundled/templates/mcp-config.json +10 -0
  165. package/bundled/templates/methodology-template.yaml +209 -0
  166. package/bundled/templates/migration-template.md +408 -0
  167. package/bundled/templates/requirement-checklist.md +410 -0
  168. package/bundled/templates/requirement-document-template.md +591 -0
  169. package/bundled/templates/requirement-template.md +881 -0
  170. package/bundled/templates/reverse-spec-template.md +409 -0
  171. package/bundled/templates/test-case-template.md +74 -0
  172. package/bundled/templates/test-plan-template.md +74 -0
  173. package/package.json +7 -5
  174. package/src/commands/audit.js +82 -0
  175. package/src/commands/check.js +66 -10
  176. package/src/commands/init.js +161 -16
  177. package/src/commands/update.js +286 -14
  178. package/src/compilers/claude-code-compiler.js +4 -1
  179. package/src/config/ai-agent-paths.js +62 -17
  180. package/src/core/constants.js +42 -11
  181. package/src/core/manifest.js +201 -3
  182. package/src/core/paths.js +2 -2
  183. package/src/i18n/messages.js +6 -29
  184. package/src/installers/hooks-installer.js +167 -75
  185. package/src/installers/integration-installer.js +9 -5
  186. package/src/prompts/init.js +14 -14
  187. package/src/utils/detector.js +21 -1
  188. package/src/utils/effect-boundary.js +1093 -0
  189. package/src/utils/hasher.js +166 -1
  190. package/src/utils/hook-stats.js +1 -1
  191. package/src/utils/integration-generator.js +79 -1
  192. package/src/utils/reference-sync.js +4 -1
  193. package/src/utils/yaml-generator.js +51 -9
  194. package/standards-registry.json +31 -8
@@ -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
- 'INSTRUCTIONS.md'
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
@@ -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.js hook.
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
- return { contentMode: 'full', level: 1 };
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
- 'INSTRUCTIONS.md': 'antigravity',
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
- // Build YAML structure
22
- const yaml = {
23
- id,
24
- meta: {
25
- version: parsed.metadata.version || '1.0.0',
26
- updated: parsed.metadata.updated || new Date().toISOString().split('T')[0],
27
- source: inferSourcePath(filename, locale),
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) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "version": "6.8.0",
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.8.0"
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.8.0",
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/INSTRUCTIONS.md",
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.8.0",
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.8.0",
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.8.0",
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.8.0",
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"