@ryuenn3123/agentic-senior-core 4.3.15 → 5.1.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 (198) hide show
  1. package/.agents/plugins/marketplace.json +21 -0
  2. package/.agents/rules/agentic-senior-core.md +126 -0
  3. package/.claude-plugin/marketplace.json +17 -0
  4. package/.claude-plugin/plugin.json +17 -0
  5. package/.clinerules/agentic-senior-core.md +94 -0
  6. package/.codex-plugin/plugin.json +24 -0
  7. package/.cursor/rules/agentic-senior-core.mdc +99 -0
  8. package/.devin-plugin/plugin.json +13 -0
  9. package/.github/copilot-instructions.md +94 -0
  10. package/.github/plugin/marketplace.json +20 -0
  11. package/.github/plugin/plugin.json +16 -0
  12. package/.kiro/steering/agentic-senior-core.md +94 -0
  13. package/.openclaw/skills/asc/SKILL.md +23 -0
  14. package/.openclaw/skills/asc-audit/SKILL.md +28 -0
  15. package/.openclaw/skills/asc-review/SKILL.md +54 -0
  16. package/.opencode/plugins/agentic-senior-core.mjs +31 -0
  17. package/.windsurf/rules/agentic-senior-core.md +94 -0
  18. package/AGENTS.md +126 -179
  19. package/CONTRIBUTING.md +93 -63
  20. package/README.md +160 -111
  21. package/__init__.py +61 -0
  22. package/bin/agentic-senior-core.js +41 -43
  23. package/commands/asc-audit.md +1 -0
  24. package/commands/asc-audit.toml +2 -0
  25. package/commands/asc-help.md +9 -0
  26. package/commands/asc-help.toml +2 -0
  27. package/commands/asc-refactor.md +1 -0
  28. package/commands/asc-refactor.toml +2 -0
  29. package/commands/asc-review.md +1 -0
  30. package/commands/asc-review.toml +2 -0
  31. package/gemini-extension.json +10 -0
  32. package/hooks/copilot-hooks.json +21 -0
  33. package/hooks/hooks.json +31 -0
  34. package/hooks/session-start.js +36 -0
  35. package/hooks/subagent-start.js +38 -0
  36. package/lib/cli/commands/adapter.mjs +109 -0
  37. package/lib/cli/commands/clean.mjs +63 -0
  38. package/lib/cli/commands/status.mjs +94 -0
  39. package/package.json +43 -67
  40. package/plugin.yaml +16 -0
  41. package/scripts/uninstall.js +38 -0
  42. package/skills/asc/SKILL.md +16 -0
  43. package/skills/asc-audit/SKILL.md +28 -0
  44. package/skills/asc-refactor/SKILL.md +35 -0
  45. package/skills/asc-review/SKILL.md +54 -0
  46. package/.agent-context/policies/llm-judge-threshold.json +0 -29
  47. package/.agent-context/prompts/bootstrap-design.md +0 -63
  48. package/.agent-context/prompts/compact-natural-mode.md +0 -100
  49. package/.agent-context/prompts/init-project.md +0 -60
  50. package/.agent-context/prompts/refactor.md +0 -38
  51. package/.agent-context/prompts/review-code.md +0 -31
  52. package/.agent-context/review-checklists/architecture-review.md +0 -57
  53. package/.agent-context/review-checklists/pr-checklist.md +0 -136
  54. package/.agent-context/rules/api-docs.md +0 -24
  55. package/.agent-context/rules/api-versioning.md +0 -16
  56. package/.agent-context/rules/architecture.md +0 -33
  57. package/.agent-context/rules/background-jobs.md +0 -17
  58. package/.agent-context/rules/config-and-flags.md +0 -16
  59. package/.agent-context/rules/database-design.md +0 -18
  60. package/.agent-context/rules/docker-runtime.md +0 -17
  61. package/.agent-context/rules/error-handling.md +0 -16
  62. package/.agent-context/rules/event-driven.md +0 -16
  63. package/.agent-context/rules/frontend-architecture.md +0 -54
  64. package/.agent-context/rules/microservices.md +0 -16
  65. package/.agent-context/rules/migrations.md +0 -16
  66. package/.agent-context/rules/observability.md +0 -16
  67. package/.agent-context/rules/performance.md +0 -16
  68. package/.agent-context/rules/realtime.md +0 -16
  69. package/.agent-context/rules/resilience.md +0 -18
  70. package/.agent-context/rules/security.md +0 -18
  71. package/.agent-context/rules/testing.md +0 -16
  72. package/.agent-context/state/README.md +0 -25
  73. package/.agent-context/state/architecture-map.md +0 -40
  74. package/.agent-context/state/benchmark-comparison-schema.json +0 -181
  75. package/.agent-context/state/benchmark-reproducibility.json +0 -87
  76. package/.agent-context/state/benchmark-thresholds.json +0 -10
  77. package/.agent-context/state/benchmark-writer-judge-config.json +0 -58
  78. package/.agent-context/state/dependency-map.md +0 -41
  79. package/.agent-context/state/memory-adapter-contract.json +0 -52
  80. package/.agent-context/state/memory-schema-v1.json +0 -88
  81. package/GEMINI.md +0 -1
  82. package/benchmarks/README.md +0 -60
  83. package/benchmarks/compact-natural-mode/fixtures.mjs +0 -359
  84. package/benchmarks/compact-natural-mode/scorer.mjs +0 -331
  85. package/benchmarks/runtime-token-saver/fixtures.mjs +0 -836
  86. package/lib/cli/adaptive-context/catalog.mjs +0 -390
  87. package/lib/cli/adaptive-context/file-signals.mjs +0 -100
  88. package/lib/cli/adaptive-context/implications.mjs +0 -44
  89. package/lib/cli/adaptive-context.mjs +0 -365
  90. package/lib/cli/audits/typography-palette-anti-repeat/color-utils.mjs +0 -156
  91. package/lib/cli/audits/typography-palette-anti-repeat/file-scanner.mjs +0 -103
  92. package/lib/cli/audits/typography-palette-anti-repeat/typography-utils.mjs +0 -70
  93. package/lib/cli/audits/typography-palette-anti-repeat-audit.mjs +0 -239
  94. package/lib/cli/backup.mjs +0 -210
  95. package/lib/cli/commands/audit-design-anti-repeat.mjs +0 -39
  96. package/lib/cli/commands/context.mjs +0 -140
  97. package/lib/cli/commands/init/project-context.mjs +0 -60
  98. package/lib/cli/commands/init/runtime-environment.mjs +0 -59
  99. package/lib/cli/commands/init/setup-decisions.mjs +0 -83
  100. package/lib/cli/commands/init.mjs +0 -654
  101. package/lib/cli/commands/launch.mjs +0 -76
  102. package/lib/cli/commands/optimize.mjs +0 -284
  103. package/lib/cli/commands/rollback.mjs +0 -64
  104. package/lib/cli/commands/upgrade/token-optimization-state.mjs +0 -51
  105. package/lib/cli/commands/upgrade.mjs +0 -451
  106. package/lib/cli/compiler.mjs +0 -570
  107. package/lib/cli/constants.mjs +0 -136
  108. package/lib/cli/detector/constants.mjs +0 -135
  109. package/lib/cli/detector/design-evidence/collector.mjs +0 -256
  110. package/lib/cli/detector/design-evidence/constants.mjs +0 -39
  111. package/lib/cli/detector/design-evidence/file-traversal.mjs +0 -83
  112. package/lib/cli/detector/design-evidence/structured-attribute-evidence.mjs +0 -117
  113. package/lib/cli/detector/design-evidence/summary.mjs +0 -109
  114. package/lib/cli/detector/design-evidence/utility-helpers.mjs +0 -122
  115. package/lib/cli/detector/design-evidence.mjs +0 -25
  116. package/lib/cli/detector/stack-detection.mjs +0 -243
  117. package/lib/cli/detector/ui-signals.mjs +0 -150
  118. package/lib/cli/detector/workspace-scan.mjs +0 -177
  119. package/lib/cli/detector.mjs +0 -23
  120. package/lib/cli/init-detection-flow.mjs +0 -48
  121. package/lib/cli/init-options.mjs +0 -237
  122. package/lib/cli/init-selection.mjs +0 -29
  123. package/lib/cli/memory-continuity.mjs +0 -537
  124. package/lib/cli/preflight.mjs +0 -76
  125. package/lib/cli/project-scaffolder/constants.mjs +0 -69
  126. package/lib/cli/project-scaffolder/discovery.mjs +0 -272
  127. package/lib/cli/project-scaffolder/prompt-builders.mjs +0 -171
  128. package/lib/cli/project-scaffolder/storage.mjs +0 -144
  129. package/lib/cli/project-scaffolder/ui-scope-detection.mjs +0 -36
  130. package/lib/cli/project-scaffolder.mjs +0 -29
  131. package/lib/cli/rollback.mjs +0 -66
  132. package/lib/cli/token-optimization.mjs +0 -486
  133. package/lib/cli/utils/filesystem.mjs +0 -81
  134. package/lib/cli/utils/managed-surface.mjs +0 -280
  135. package/lib/cli/utils/prompting.mjs +0 -44
  136. package/lib/cli/utils.mjs +0 -479
  137. package/mcp.json +0 -134
  138. package/scripts/adaptive-context/fixtures.mjs +0 -183
  139. package/scripts/adaptive-context-benchmark.mjs +0 -9
  140. package/scripts/ascx-runtime-token-saver-benchmark.mjs +0 -9
  141. package/scripts/audit-cache-layer-contract.mjs +0 -263
  142. package/scripts/audit-caching-scope-hygiene.mjs +0 -268
  143. package/scripts/audit-file-size.mjs +0 -219
  144. package/scripts/audit-reflection-citations.mjs +0 -163
  145. package/scripts/audit-release-bundle.mjs +0 -170
  146. package/scripts/audit-rule-id-uniqueness.mjs +0 -313
  147. package/scripts/audit-typography-palette-anti-repeat.mjs +0 -120
  148. package/scripts/benchmark-evidence-bundle.mjs +0 -645
  149. package/scripts/benchmark-gate.mjs +0 -121
  150. package/scripts/benchmark-intelligence.mjs +0 -33
  151. package/scripts/benchmark-writer-judge-matrix.mjs +0 -379
  152. package/scripts/build-release-benchmark-bundle.mjs +0 -202
  153. package/scripts/bump-version.mjs +0 -103
  154. package/scripts/clean-local-artifacts.mjs +0 -78
  155. package/scripts/compact-natural-mode-benchmark.mjs +0 -9
  156. package/scripts/context-triggered-audit.mjs +0 -391
  157. package/scripts/detection-benchmark.mjs +0 -127
  158. package/scripts/docs-quality-drift-report.mjs +0 -358
  159. package/scripts/documentation-boundary-audit.mjs +0 -305
  160. package/scripts/explain-on-demand-audit.mjs +0 -427
  161. package/scripts/forbidden-content-check.mjs +0 -123
  162. package/scripts/frontend-usability-audit.mjs +0 -243
  163. package/scripts/generate-sbom.mjs +0 -61
  164. package/scripts/governance-weekly-report.mjs +0 -466
  165. package/scripts/llm-judge/checklist-loader.mjs +0 -45
  166. package/scripts/llm-judge/constants.mjs +0 -66
  167. package/scripts/llm-judge/diff-collection.mjs +0 -74
  168. package/scripts/llm-judge/prompting.mjs +0 -78
  169. package/scripts/llm-judge/providers.mjs +0 -111
  170. package/scripts/llm-judge/verdict.mjs +0 -134
  171. package/scripts/llm-judge.mjs +0 -200
  172. package/scripts/memory-continuity-benchmark.mjs +0 -322
  173. package/scripts/quality-trend-report.mjs +0 -289
  174. package/scripts/release-gate/audit-checks.mjs +0 -441
  175. package/scripts/release-gate/constants.mjs +0 -53
  176. package/scripts/release-gate/runtime.mjs +0 -63
  177. package/scripts/release-gate/static-checks.mjs +0 -182
  178. package/scripts/release-gate.mjs +0 -42
  179. package/scripts/rules-guardian-audit.mjs +0 -580
  180. package/scripts/single-source-lazy-loading-audit.mjs +0 -449
  181. package/scripts/sync-thin-adapters.mjs +0 -72
  182. package/scripts/token-optimization-benchmark.mjs +0 -252
  183. package/scripts/ui-design-judge/constants.mjs +0 -13
  184. package/scripts/ui-design-judge/design-execution-summary.mjs +0 -81
  185. package/scripts/ui-design-judge/git-input.mjs +0 -134
  186. package/scripts/ui-design-judge/prompting.mjs +0 -52
  187. package/scripts/ui-design-judge/providers.mjs +0 -102
  188. package/scripts/ui-design-judge/reporting.mjs +0 -183
  189. package/scripts/ui-design-judge/rubric-calibration.mjs +0 -214
  190. package/scripts/ui-design-judge/rubric-goldset.json +0 -188
  191. package/scripts/ui-design-judge.mjs +0 -319
  192. package/scripts/ui-rubric-calibration.mjs +0 -35
  193. package/scripts/validate/config.mjs +0 -519
  194. package/scripts/validate/coverage-checks.mjs +0 -446
  195. package/scripts/validate/file-structure.mjs +0 -162
  196. package/scripts/validate/markdown-content.mjs +0 -109
  197. package/scripts/validate/project-metadata.mjs +0 -166
  198. package/scripts/validate.mjs +0 -338
@@ -1,358 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * docs-quality-drift-report.mjs
5
- *
6
- * Generates a machine-readable documentation quality drift artifact.
7
- * Tracks plain-language readability signals and trend deltas over time.
8
- */
9
-
10
- import { existsSync, readFileSync } from 'node:fs';
11
- import fs from 'node:fs/promises';
12
- import { dirname, join, relative, resolve } from 'node:path';
13
- import { fileURLToPath } from 'node:url';
14
-
15
- const SCRIPT_FILE_PATH = fileURLToPath(import.meta.url);
16
- const SCRIPT_DIR = dirname(SCRIPT_FILE_PATH);
17
- const REPOSITORY_ROOT = resolve(SCRIPT_DIR, '..');
18
- const REPORT_PATH = join(REPOSITORY_ROOT, '.agent-context', 'state', 'docs-quality-drift-report.json');
19
- const ARGUMENT_FLAGS = new Set(process.argv.slice(2));
20
- const isStdoutOnlyMode = ARGUMENT_FLAGS.has('--stdout-only');
21
- const HISTORY_LIMIT = 52;
22
- const LONG_SENTENCE_WORD_THRESHOLD = 28;
23
-
24
- const MONITORED_STATIC_FILE_PATHS = [
25
- 'README.md',
26
- 'CHANGELOG.md',
27
- 'AGENTS.md',
28
- 'CLAUDE.md',
29
- 'GEMINI.md',
30
- 'docs/deep-analysis-and-roadmap-backlog.md',
31
- ];
32
-
33
- const MONITORED_DIRECTORY_PATHS = [
34
- 'docs',
35
- '.agent-context/prompts',
36
- '.agent-context/review-checklists',
37
- ];
38
-
39
- const FORBIDDEN_BUZZWORDS = [
40
- 'delve',
41
- 'leverage',
42
- 'robust',
43
- 'utilize',
44
- 'seamless',
45
- ];
46
-
47
- function normalizeLineEndings(content) {
48
- return content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
49
- }
50
-
51
- function extractWordCount(content) {
52
- const words = content.match(/[A-Za-z0-9']+/g) || [];
53
- return words.length;
54
- }
55
-
56
- function extractSentenceWordCounts(content) {
57
- const sentenceFragments = content
58
- .split(/[.!?]+/u)
59
- .map((fragment) => fragment.trim())
60
- .filter((fragment) => fragment.length > 0);
61
-
62
- const sentenceWordCounts = [];
63
- for (const sentenceFragment of sentenceFragments) {
64
- const sentenceWordCount = extractWordCount(sentenceFragment);
65
- if (sentenceWordCount > 0) {
66
- sentenceWordCounts.push(sentenceWordCount);
67
- }
68
- }
69
-
70
- return sentenceWordCounts;
71
- }
72
-
73
- function countEmoji(content) {
74
- const emojiMatches = content.match(/[\p{Extended_Pictographic}]/gu) || [];
75
- return emojiMatches.length;
76
- }
77
-
78
- function countForbiddenBuzzwords(content) {
79
- const buzzwordCounts = {};
80
-
81
- for (const forbiddenBuzzword of FORBIDDEN_BUZZWORDS) {
82
- const buzzwordRegex = new RegExp(`\\b${forbiddenBuzzword}\\b`, 'gi');
83
- const matchCount = (content.match(buzzwordRegex) || []).length;
84
- buzzwordCounts[forbiddenBuzzword] = matchCount;
85
- }
86
-
87
- return buzzwordCounts;
88
- }
89
-
90
- async function collectMarkdownFiles(directoryPath) {
91
- const markdownFilePaths = [];
92
-
93
- async function walk(currentDirectoryPath) {
94
- if (!existsSync(currentDirectoryPath)) {
95
- return;
96
- }
97
-
98
- const directoryEntries = await fs.readdir(currentDirectoryPath, { withFileTypes: true });
99
-
100
- for (const directoryEntry of directoryEntries) {
101
- if (directoryEntry.name === 'node_modules' || directoryEntry.name === '.git' || directoryEntry.name === '.benchmarks') {
102
- continue;
103
- }
104
-
105
- const entryPath = join(currentDirectoryPath, directoryEntry.name);
106
-
107
- if (directoryEntry.isDirectory()) {
108
- await walk(entryPath);
109
- continue;
110
- }
111
-
112
- if (directoryEntry.isFile() && directoryEntry.name.toLowerCase().endsWith('.md')) {
113
- markdownFilePaths.push(entryPath);
114
- }
115
- }
116
- }
117
-
118
- await walk(directoryPath);
119
- return markdownFilePaths;
120
- }
121
-
122
- async function collectMonitoredFiles() {
123
- const collectedFilePathSet = new Set();
124
-
125
- for (const relativeFilePath of MONITORED_STATIC_FILE_PATHS) {
126
- const absoluteFilePath = join(REPOSITORY_ROOT, relativeFilePath);
127
- if (existsSync(absoluteFilePath)) {
128
- collectedFilePathSet.add(absoluteFilePath);
129
- }
130
- }
131
-
132
- for (const relativeDirectoryPath of MONITORED_DIRECTORY_PATHS) {
133
- const absoluteDirectoryPath = join(REPOSITORY_ROOT, relativeDirectoryPath);
134
- const markdownFiles = await collectMarkdownFiles(absoluteDirectoryPath);
135
-
136
- for (const markdownFilePath of markdownFiles) {
137
- collectedFilePathSet.add(markdownFilePath);
138
- }
139
- }
140
-
141
- return Array.from(collectedFilePathSet).sort((firstPath, secondPath) => firstPath.localeCompare(secondPath));
142
- }
143
-
144
- function readJsonOrNull(filePath) {
145
- if (!existsSync(filePath)) {
146
- return null;
147
- }
148
-
149
- try {
150
- return JSON.parse(readFileSync(filePath, 'utf8'));
151
- } catch {
152
- return null;
153
- }
154
- }
155
-
156
- function clamp(value, minimum, maximum) {
157
- return Math.max(minimum, Math.min(maximum, value));
158
- }
159
-
160
- function buildQualityScore(metrics) {
161
- const longSentencePenalty = metrics.longSentenceRatePercent * 0.4;
162
- const buzzwordPenalty = metrics.totalForbiddenBuzzwordHits * 1.5;
163
- const emojiPenalty = metrics.emojiCount * 5;
164
- const averageSentencePenalty = metrics.averageWordsPerSentence > 24
165
- ? (metrics.averageWordsPerSentence - 24) * 1.2
166
- : 0;
167
-
168
- const rawScore = 100 - longSentencePenalty - buzzwordPenalty - emojiPenalty - averageSentencePenalty;
169
- return Number(clamp(rawScore, 0, 100).toFixed(2));
170
- }
171
-
172
- function summarizeTrend(currentSummary, previousSummary) {
173
- if (!previousSummary) {
174
- return {
175
- hasBaseline: false,
176
- trend: 'baseline-created',
177
- deltaQualityScore: null,
178
- deltaLongSentenceRatePercent: null,
179
- deltaForbiddenBuzzwordHits: null,
180
- deltaEmojiCount: null,
181
- };
182
- }
183
-
184
- const deltaQualityScore = Number((currentSummary.qualityScore - previousSummary.qualityScore).toFixed(2));
185
- const deltaLongSentenceRatePercent = Number((currentSummary.longSentenceRatePercent - previousSummary.longSentenceRatePercent).toFixed(2));
186
- const deltaForbiddenBuzzwordHits = currentSummary.totalForbiddenBuzzwordHits - previousSummary.totalForbiddenBuzzwordHits;
187
- const deltaEmojiCount = currentSummary.emojiCount - previousSummary.emojiCount;
188
-
189
- let trend = 'stable';
190
- if (deltaQualityScore >= 1 && deltaLongSentenceRatePercent <= 0 && deltaForbiddenBuzzwordHits <= 0 && deltaEmojiCount <= 0) {
191
- trend = 'improving';
192
- } else if (deltaQualityScore <= -1 || deltaLongSentenceRatePercent > 0 || deltaForbiddenBuzzwordHits > 0 || deltaEmojiCount > 0) {
193
- trend = 'regressing';
194
- }
195
-
196
- return {
197
- hasBaseline: true,
198
- trend,
199
- deltaQualityScore,
200
- deltaLongSentenceRatePercent,
201
- deltaForbiddenBuzzwordHits,
202
- deltaEmojiCount,
203
- };
204
- }
205
-
206
- function buildHistoryEntry(report) {
207
- return {
208
- generatedAt: report.generatedAt,
209
- qualityScore: report.summary.qualityScore,
210
- documentCount: report.summary.documentCount,
211
- averageWordsPerSentence: report.summary.averageWordsPerSentence,
212
- longSentenceRatePercent: report.summary.longSentenceRatePercent,
213
- totalForbiddenBuzzwordHits: report.summary.totalForbiddenBuzzwordHits,
214
- emojiCount: report.summary.emojiCount,
215
- };
216
- }
217
-
218
- function mergeHistory(previousReport, currentHistoryEntry) {
219
- const existingHistory = Array.isArray(previousReport?.history) ? previousReport.history : [];
220
- const mergedHistory = [...existingHistory, currentHistoryEntry];
221
-
222
- if (mergedHistory.length <= HISTORY_LIMIT) {
223
- return mergedHistory;
224
- }
225
-
226
- return mergedHistory.slice(mergedHistory.length - HISTORY_LIMIT);
227
- }
228
-
229
- async function runDocsQualityDriftReport() {
230
- const monitoredFilePaths = await collectMonitoredFiles();
231
-
232
- const fileSummaries = [];
233
- let totalLineCount = 0;
234
- let totalWordCount = 0;
235
- let totalSentenceCount = 0;
236
- let totalLongSentenceCount = 0;
237
- let totalEmojiCount = 0;
238
- const forbiddenBuzzwordTotals = Object.fromEntries(FORBIDDEN_BUZZWORDS.map((word) => [word, 0]));
239
-
240
- for (const monitoredFilePath of monitoredFilePaths) {
241
- const rawContent = await fs.readFile(monitoredFilePath, 'utf8');
242
- const normalizedContent = normalizeLineEndings(rawContent);
243
-
244
- const lineCount = normalizedContent.length === 0 ? 0 : normalizedContent.split('\n').length;
245
- const wordCount = extractWordCount(normalizedContent);
246
- const sentenceWordCounts = extractSentenceWordCounts(normalizedContent);
247
- const sentenceCount = sentenceWordCounts.length;
248
- const longSentenceCount = sentenceWordCounts.filter((sentenceWordCount) => sentenceWordCount > LONG_SENTENCE_WORD_THRESHOLD).length;
249
- const emojiCount = countEmoji(normalizedContent);
250
- const forbiddenBuzzwordCounts = countForbiddenBuzzwords(normalizedContent);
251
-
252
- totalLineCount += lineCount;
253
- totalWordCount += wordCount;
254
- totalSentenceCount += sentenceCount;
255
- totalLongSentenceCount += longSentenceCount;
256
- totalEmojiCount += emojiCount;
257
-
258
- for (const forbiddenBuzzword of FORBIDDEN_BUZZWORDS) {
259
- forbiddenBuzzwordTotals[forbiddenBuzzword] += forbiddenBuzzwordCounts[forbiddenBuzzword];
260
- }
261
-
262
- fileSummaries.push({
263
- filePath: relative(REPOSITORY_ROOT, monitoredFilePath).replace(/\\/g, '/'),
264
- lineCount,
265
- wordCount,
266
- sentenceCount,
267
- longSentenceCount,
268
- emojiCount,
269
- forbiddenBuzzwordCounts,
270
- });
271
- }
272
-
273
- const averageWordsPerSentence = totalSentenceCount === 0
274
- ? 0
275
- : Number((totalWordCount / totalSentenceCount).toFixed(2));
276
- const longSentenceRatePercent = totalSentenceCount === 0
277
- ? 0
278
- : Number(((totalLongSentenceCount / totalSentenceCount) * 100).toFixed(2));
279
- const totalForbiddenBuzzwordHits = FORBIDDEN_BUZZWORDS.reduce(
280
- (sum, forbiddenBuzzword) => sum + forbiddenBuzzwordTotals[forbiddenBuzzword],
281
- 0
282
- );
283
-
284
- const summary = {
285
- documentCount: monitoredFilePaths.length,
286
- totalLineCount,
287
- totalWordCount,
288
- totalSentenceCount,
289
- averageWordsPerSentence,
290
- longSentenceCount: totalLongSentenceCount,
291
- longSentenceRatePercent,
292
- emojiCount: totalEmojiCount,
293
- forbiddenBuzzwordTotals,
294
- totalForbiddenBuzzwordHits,
295
- qualityScore: 0,
296
- };
297
-
298
- summary.qualityScore = buildQualityScore(summary);
299
-
300
- const previousReport = readJsonOrNull(REPORT_PATH);
301
- const previousSummary = previousReport?.summary || null;
302
- const trend = summarizeTrend(summary, previousSummary);
303
-
304
- const sortedBuzzwordBreakdown = Object.entries(forbiddenBuzzwordTotals)
305
- .map(([term, hits]) => ({ term, hits }))
306
- .sort((firstEntry, secondEntry) => secondEntry.hits - firstEntry.hits);
307
-
308
- const docsQualityDriftReportSnapshot = {
309
- generatedAt: new Date().toISOString(),
310
- reportName: 'docs-quality-drift-report',
311
- passed: summary.emojiCount === 0,
312
- methodology: {
313
- monitoredStaticFiles: MONITORED_STATIC_FILE_PATHS,
314
- monitoredDirectories: MONITORED_DIRECTORY_PATHS,
315
- forbiddenBuzzwords: FORBIDDEN_BUZZWORDS,
316
- longSentenceWordThreshold: LONG_SENTENCE_WORD_THRESHOLD,
317
- },
318
- summary,
319
- trend,
320
- buzzwordBreakdown: sortedBuzzwordBreakdown,
321
- topLongSentenceRiskFiles: fileSummaries
322
- .map((fileSummary) => ({
323
- filePath: fileSummary.filePath,
324
- longSentenceCount: fileSummary.longSentenceCount,
325
- }))
326
- .filter((fileSummary) => fileSummary.longSentenceCount > 0)
327
- .sort((firstFile, secondFile) => secondFile.longSentenceCount - firstFile.longSentenceCount)
328
- .slice(0, 10),
329
- fileSummaries,
330
- artifact: {
331
- path: REPORT_PATH,
332
- writeMode: isStdoutOnlyMode ? 'stdout-only' : 'stdout-and-file',
333
- },
334
- };
335
-
336
- const history = mergeHistory(previousReport, buildHistoryEntry(docsQualityDriftReportSnapshot));
337
- const docsQualityDriftReport = {
338
- ...docsQualityDriftReportSnapshot,
339
- history,
340
- };
341
-
342
- if (!isStdoutOnlyMode) {
343
- await fs.mkdir(dirname(REPORT_PATH), { recursive: true });
344
- await fs.writeFile(REPORT_PATH, JSON.stringify(docsQualityDriftReport, null, 2) + '\n', 'utf8');
345
- }
346
-
347
- return docsQualityDriftReport;
348
- }
349
-
350
- runDocsQualityDriftReport()
351
- .then((docsQualityDriftReport) => {
352
- console.log(JSON.stringify(docsQualityDriftReport, null, 2));
353
- })
354
- .catch((docsQualityError) => {
355
- const errorMessage = docsQualityError instanceof Error ? docsQualityError.message : String(docsQualityError);
356
- console.error(`Docs quality drift report failed: ${errorMessage}`);
357
- process.exit(1);
358
- });
@@ -1,305 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * documentation-boundary-audit.mjs
5
- *
6
- * Enforces documentation sync only on changed scope boundaries.
7
- * If public surface, API contract, or database structure files change,
8
- * matching documentation updates must be present in the same change scope.
9
- */
10
-
11
- import { execFileSync } from 'node:child_process';
12
- import { dirname, resolve } from 'node:path';
13
- import { fileURLToPath } from 'node:url';
14
-
15
- const __filename = fileURLToPath(import.meta.url);
16
- const __dirname = dirname(__filename);
17
- const REPOSITORY_ROOT = resolve(__dirname, '..');
18
- const DOCUMENTATION_BOUNDARY_AUDIT_REPORT_VERSION = '2.1.0';
19
- const AUTO_DOCS_SYNC_SCOPE_PHASE = 'phase-1';
20
- const AUTO_DOCS_SYNC_SCOPE_BOUNDARIES = [
21
- 'public-surface',
22
- 'api-contract',
23
- 'database-structure',
24
- ];
25
-
26
- const CORE_DOCUMENTATION_FILES = new Set(['README.md', 'CHANGELOG.md']);
27
-
28
- const BOUNDARY_RULES = [
29
- {
30
- boundaryName: 'public-surface',
31
- requirement: 'Public surface changes must update README.md, CHANGELOG.md, or docs/* in the same scope.',
32
- requiredDocumentationUpdates: [
33
- 'README.md',
34
- 'CHANGELOG.md',
35
- 'docs/architecture-decision-record.md',
36
- 'docs/flow-overview.md',
37
- ],
38
- trigger(filePath) {
39
- return /^(bin\/|lib\/|scripts\/)/.test(filePath) && !isDocumentationFilePath(filePath);
40
- },
41
- docsMatcher(filePath) {
42
- return filePath === 'README.md' || filePath === 'CHANGELOG.md' || filePath.startsWith('docs/');
43
- },
44
- },
45
- {
46
- boundaryName: 'api-contract',
47
- requirement: 'API endpoint or contract changes must update API/OpenAPI documentation in the same scope.',
48
- requiredDocumentationUpdates: [
49
- 'docs/api-contract.md',
50
- 'docs/flow-overview.md',
51
- '.agent-context/rules/api-docs.md',
52
- 'README.md',
53
- ],
54
- trigger(filePath) {
55
- return !isDocumentationFilePath(filePath)
56
- && (
57
- /(api|openapi|controller|route|endpoint)/i.test(filePath)
58
- || /api[-_/]?contract/i.test(filePath)
59
- );
60
- },
61
- docsMatcher(filePath) {
62
- return filePath === '.agent-context/rules/api-docs.md'
63
- || /^(docs\/.*(api|contract|openapi))/i.test(filePath)
64
- || filePath === 'README.md';
65
- },
66
- },
67
- {
68
- boundaryName: 'database-structure',
69
- requirement: 'Database structure changes must update schema or migration documentation in the same scope.',
70
- requiredDocumentationUpdates: [
71
- 'docs/database-schema.md',
72
- 'docs/flow-overview.md',
73
- '.agent-context/rules/database-design.md',
74
- 'README.md',
75
- ],
76
- trigger(filePath) {
77
- // Path-based exclusion: design-contract JSON shape lives under
78
- // lib/cli/project-scaffolder/. Files like research-dossier-migration.mjs
79
- // describe agent-prompt JSON contract migrations, not database schema
80
- // or persistent-store migrations, so they must not trigger this boundary
81
- // even when the filename contains the words "schema" or "migration".
82
- // Tests for those design-contract migrations are excluded for the same
83
- // reason.
84
- if (filePath.startsWith('lib/cli/project-scaffolder/')) {
85
- return false;
86
- }
87
- if (filePath.startsWith('tests/') && /research-dossier|design-contract/i.test(filePath)) {
88
- return false;
89
- }
90
- return !isDocumentationFilePath(filePath)
91
- && /(database|schema|migration|repository|sql|prisma|typeorm|knex)/i.test(filePath);
92
- },
93
- docsMatcher(filePath) {
94
- return filePath === '.agent-context/rules/database-design.md'
95
- || /^(docs\/.*(database|schema|migration))/i.test(filePath)
96
- || filePath === 'README.md';
97
- },
98
- },
99
- ];
100
-
101
- function normalizeFilePath(filePath) {
102
- return filePath.replace(/\\/g, '/').replace(/^\.\//, '');
103
- }
104
-
105
- function parseGitFileList(rawOutput) {
106
- if (typeof rawOutput !== 'string' || rawOutput.trim().length === 0) {
107
- return [];
108
- }
109
-
110
- return rawOutput
111
- .split(/\r?\n/)
112
- .map((filePath) => filePath.trim())
113
- .filter((filePath) => filePath.length > 0)
114
- .map(normalizeFilePath);
115
- }
116
-
117
- function runGitFileQuery(commandArguments) {
118
- try {
119
- const rawOutput = execFileSync('git', commandArguments, {
120
- cwd: REPOSITORY_ROOT,
121
- encoding: 'utf8',
122
- maxBuffer: 1024 * 1024,
123
- stdio: ['ignore', 'pipe', 'ignore'],
124
- });
125
-
126
- return parseGitFileList(rawOutput);
127
- } catch {
128
- return [];
129
- }
130
- }
131
-
132
- function uniqueSorted(filePaths) {
133
- return Array.from(new Set(filePaths)).sort((leftPath, rightPath) => leftPath.localeCompare(rightPath));
134
- }
135
-
136
- function collectChangedFiles() {
137
- const workingTreeFiles = runGitFileQuery(['diff', '--name-only']);
138
- const stagedFiles = runGitFileQuery(['diff', '--name-only', '--cached']);
139
- const workingScopeFiles = uniqueSorted([...workingTreeFiles, ...stagedFiles]);
140
-
141
- if (workingScopeFiles.length > 0) {
142
- return {
143
- source: 'working-tree-and-index',
144
- files: workingScopeFiles,
145
- };
146
- }
147
-
148
- const latestCommitRangeFiles = runGitFileQuery(['diff', '--name-only', 'HEAD~1..HEAD']);
149
- if (latestCommitRangeFiles.length > 0) {
150
- return {
151
- source: 'latest-commit-range',
152
- files: uniqueSorted(latestCommitRangeFiles),
153
- };
154
- }
155
-
156
- const headCommitFiles = runGitFileQuery(['show', '--pretty=format:', '--name-only', 'HEAD']);
157
- if (headCommitFiles.length > 0) {
158
- return {
159
- source: 'head-commit',
160
- files: uniqueSorted(headCommitFiles),
161
- };
162
- }
163
-
164
- return {
165
- source: 'none',
166
- files: [],
167
- };
168
- }
169
-
170
- function isDocumentationFilePath(filePath) {
171
- return CORE_DOCUMENTATION_FILES.has(filePath)
172
- || filePath.startsWith('docs/')
173
- || filePath.startsWith('.agent-context/review-checklists/')
174
- || filePath === '.agent-context/rules/api-docs.md'
175
- || filePath === '.agent-context/rules/database-design.md';
176
- }
177
-
178
- function evaluateBoundary(boundaryRule, changedFiles, changedDocumentationFiles) {
179
- const boundaryChangedFiles = changedFiles.filter((filePath) => boundaryRule.trigger(filePath));
180
- const expectedDocumentationPaths = Array.isArray(boundaryRule.requiredDocumentationUpdates)
181
- ? boundaryRule.requiredDocumentationUpdates
182
- : [];
183
-
184
- if (boundaryChangedFiles.length === 0) {
185
- return {
186
- boundaryName: boundaryRule.boundaryName,
187
- requirement: boundaryRule.requirement,
188
- triggered: false,
189
- passed: true,
190
- changedFiles: [],
191
- documentationFiles: [],
192
- expectedDocumentationPaths,
193
- missingDocumentationUpdates: false,
194
- requiredActions: [],
195
- details: 'Boundary not triggered by changed scope.',
196
- };
197
- }
198
-
199
- const matchingDocumentationFiles = changedDocumentationFiles.filter((filePath) => boundaryRule.docsMatcher(filePath));
200
- const boundaryPassed = matchingDocumentationFiles.length > 0;
201
- const requiredActions = boundaryPassed
202
- ? []
203
- : [
204
- `Update one or more boundary docs: ${expectedDocumentationPaths.join(', ')}`,
205
- 'Re-run scripts/documentation-boundary-audit.mjs before merge.',
206
- ];
207
-
208
- const details = boundaryPassed
209
- ? `Boundary triggered and synchronized with documentation updates: ${matchingDocumentationFiles.join(', ')}`
210
- : 'Boundary triggered without required documentation updates.';
211
-
212
- return {
213
- boundaryName: boundaryRule.boundaryName,
214
- requirement: boundaryRule.requirement,
215
- triggered: true,
216
- passed: boundaryPassed,
217
- changedFiles: boundaryChangedFiles,
218
- documentationFiles: matchingDocumentationFiles,
219
- expectedDocumentationPaths,
220
- missingDocumentationUpdates: !boundaryPassed,
221
- requiredActions,
222
- details,
223
- };
224
- }
225
-
226
- function runDocumentationBoundaryAudit() {
227
- const changedScope = collectChangedFiles();
228
- const changedFiles = changedScope.files;
229
- const changedDocumentationFiles = changedFiles.filter(isDocumentationFilePath);
230
-
231
- const boundaryResults = BOUNDARY_RULES.map((boundaryRule) => (
232
- evaluateBoundary(boundaryRule, changedFiles, changedDocumentationFiles)
233
- ));
234
-
235
- const violations = boundaryResults
236
- .filter((boundaryResult) => boundaryResult.triggered && !boundaryResult.passed)
237
- .map((boundaryResult) => ({
238
- boundaryName: boundaryResult.boundaryName,
239
- requirement: boundaryResult.requirement,
240
- changedFiles: boundaryResult.changedFiles,
241
- expectedDocumentationPaths: boundaryResult.expectedDocumentationPaths,
242
- requiredActions: boundaryResult.requiredActions,
243
- diagnosticCode: `BOUNDARY_${boundaryResult.boundaryName.toUpperCase().replace(/-/g, '_')}_DOCS_SYNC_REQUIRED`,
244
- }));
245
-
246
- const failures = violations.map((violation) => {
247
- const affectedFiles = violation.changedFiles.join(', ');
248
- return `${violation.boundaryName}: ${violation.requirement} Changed files: ${affectedFiles}`;
249
- });
250
-
251
- const reportPayload = {
252
- generatedAt: new Date().toISOString(),
253
- reportVersion: DOCUMENTATION_BOUNDARY_AUDIT_REPORT_VERSION,
254
- auditName: 'documentation-boundary-audit',
255
- source: changedScope.source,
256
- changedFileCount: changedFiles.length,
257
- changedFiles,
258
- boundaryResults,
259
- violations,
260
- passed: failures.length === 0,
261
- failureCount: failures.length,
262
- failures,
263
- };
264
-
265
- const triggeredBoundaryResults = boundaryResults.filter((boundaryResult) => boundaryResult.triggered);
266
- const passedTriggeredBoundaryResults = triggeredBoundaryResults.filter((boundaryResult) => boundaryResult.passed);
267
- const scopeMatchedDocumentationFiles = uniqueSorted(
268
- triggeredBoundaryResults.flatMap((boundaryResult) => boundaryResult.documentationFiles),
269
- );
270
- const scopeMatchedDocumentationFileSet = new Set(scopeMatchedDocumentationFiles);
271
- const outOfScopeDocumentationFiles = changedDocumentationFiles.filter(
272
- (filePath) => !scopeMatchedDocumentationFileSet.has(filePath),
273
- );
274
-
275
- const precisionNumerator = scopeMatchedDocumentationFiles.length;
276
- const precisionDenominator = changedDocumentationFiles.length;
277
- const recallNumerator = passedTriggeredBoundaryResults.length;
278
- const recallDenominator = triggeredBoundaryResults.length;
279
-
280
- const precision = precisionDenominator > 0 ? precisionNumerator / precisionDenominator : 1;
281
- const recall = recallDenominator > 0 ? recallNumerator / recallDenominator : 1;
282
-
283
- reportPayload.autoDocsSyncScope = {
284
- phase: AUTO_DOCS_SYNC_SCOPE_PHASE,
285
- bounded: true,
286
- explicitBoundaries: AUTO_DOCS_SYNC_SCOPE_BOUNDARIES,
287
- };
288
-
289
- reportPayload.rolloutMetrics = {
290
- measuredAt: reportPayload.generatedAt,
291
- precision,
292
- recall,
293
- precisionNumerator,
294
- precisionDenominator,
295
- recallNumerator,
296
- recallDenominator,
297
- scopeMatchedDocumentationFiles,
298
- outOfScopeDocumentationFiles,
299
- };
300
-
301
- console.log(JSON.stringify(reportPayload, null, 2));
302
- process.exit(reportPayload.passed ? 0 : 1);
303
- }
304
-
305
- runDocumentationBoundaryAudit();