@ankhorage/doctor 0.11.7 → 0.11.8

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.8
4
+
5
+ ### Patch Changes
6
+
7
+ - 53a39da: Consume canonical source-architecture and public-package profile rules from `@ankhorage/policy/architecture` instead of duplicating them in Doctor.
8
+
3
9
  ## 0.11.7
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # @ankhorage/doctor
5
5
 
6
- ![license: MIT](././paradox/badges/license.svg) ![npm: v0.11.7](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![paradox: warnings](././paradox/badges/docs.svg)
6
+ ![license: MIT](././paradox/badges/license.svg) ![npm: v0.11.8](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![paradox: warnings](././paradox/badges/docs.svg)
7
7
 
8
8
  Executable Ankh provider and standalone CLI for lightweight repo and package compliance diagnostics.
9
9
 
@@ -1 +1 @@
1
- {"version":3,"file":"analysis.d.ts","sourceRoot":"","sources":["../src/analysis.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC5F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;AAChE,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,SAAS,GAAG,MAAM,GAAG,UAAU,CAAC;AACvE,MAAM,MAAM,uBAAuB,GACjC,kBAAkB,GAAG,aAAa,GAAG,aAAa,GAAG,aAAa,CAAC;AAErE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;CACjC;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACjD,QAAQ,CAAC,WAAW,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAClD,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACrD,QAAQ,CAAC,WAAW,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAClD,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACxD,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAChD,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AA6CD,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CA+I/B"}
1
+ {"version":3,"file":"analysis.d.ts","sourceRoot":"","sources":["../src/analysis.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC5F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGtD,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;AAChE,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,SAAS,GAAG,MAAM,GAAG,UAAU,CAAC;AACvE,MAAM,MAAM,uBAAuB,GACjC,kBAAkB,GAAG,aAAa,GAAG,aAAa,GAAG,aAAa,CAAC;AAErE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;CACjC;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACjD,QAAQ,CAAC,WAAW,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAClD,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,aAAa,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACrD,QAAQ,CAAC,WAAW,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAClD,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACxD,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAChD,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AA+BD,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CA+I/B"}
package/dist/analysis.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { promises as fs } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
+ import { ARCHITECTURE_POLICY } from '@ankhorage/policy/architecture';
4
5
  import { uniqueSortedStrings } from '@ankhorage/utility/array';
6
+ import { getArchitecturePolicyRule } from './utils/getArchitecturePolicyRule.js';
5
7
  const REPO_MARKERS = [
6
8
  { label: '.git', relativePath: '.git' },
7
9
  { label: '.github/workflows', relativePath: path.join('.github', 'workflows') },
@@ -13,21 +15,7 @@ const REPO_MARKERS = [
13
15
  { label: 'package-lock.json', relativePath: 'package-lock.json' },
14
16
  { label: 'yarn.lock', relativePath: 'yarn.lock' },
15
17
  ];
16
- const REQUIRED_PUBLIC_PACKAGE_SCRIPTS = [
17
- 'build',
18
- 'typecheck',
19
- 'lint',
20
- 'lint:fix',
21
- 'format',
22
- 'format:check',
23
- 'test',
24
- 'test:standalone',
25
- 'knip:check',
26
- 'docs',
27
- 'changeset',
28
- 'changeset:status',
29
- 'version-packages',
30
- ];
18
+ const PUBLIC_PACKAGE_POLICY = ARCHITECTURE_POLICY.publicPackage;
31
19
  const PROVIDER_SOURCE_CANDIDATES = [
32
20
  path.join('src', 'ankh.provider.ts'),
33
21
  path.join('src', 'ankh.provider.tsx'),
@@ -146,11 +134,9 @@ export async function analyzeDoctorTarget(request) {
146
134
  async function analyzeRepoPolicy(request) {
147
135
  const diagnostics = [];
148
136
  if (request.profile === 'public-package') {
149
- await maybeRequirePath(diagnostics, request.plannedChanges, request.targetPath, 'README.md', 'repo.readme.required', 'create-file');
150
- await maybeRequirePath(diagnostics, request.plannedChanges, request.targetPath, 'CHANGELOG.md', 'repo.changelog.required', 'create-file');
151
- await maybeRequirePath(diagnostics, request.plannedChanges, request.targetPath, 'LICENSE', 'repo.license.required', 'create-file');
152
- await maybeRequirePath(diagnostics, request.plannedChanges, request.targetPath, '.changeset', 'repo.changeset.required', 'create-directory');
153
- await maybeRequirePath(diagnostics, request.plannedChanges, request.targetPath, path.join('.github', 'workflows'), 'repo.workflows.required', 'create-directory');
137
+ for (const artifact of PUBLIC_PACKAGE_POLICY.requiredRepoPaths) {
138
+ await maybeRequirePath(diagnostics, request.plannedChanges, request.targetPath, artifact.path, artifact.ruleId, artifact.kind === 'file' ? 'create-file' : 'create-directory');
139
+ }
154
140
  }
155
141
  if (request.profile === 'integration-monorepo') {
156
142
  if (!request.repoMarkers.includes('package.json')) {
@@ -205,29 +191,11 @@ async function analyzePackagePolicy(request) {
205
191
  const diagnostics = [];
206
192
  const { packageJson } = request;
207
193
  if (request.profile === 'public-package') {
208
- requireNonEmptyStringField({
209
- diagnostics,
210
- packageJson,
211
- packageJsonPath: request.packageJsonPath,
212
- profile: request.profile,
213
- ruleId: 'package.json.name.required',
214
- fieldName: 'name',
215
- });
216
- requireNonEmptyStringField({
217
- diagnostics,
218
- packageJson,
219
- packageJsonPath: request.packageJsonPath,
220
- profile: request.profile,
221
- ruleId: 'package.json.version.required',
222
- fieldName: 'version',
223
- });
224
- requireNonEmptyStringField({
194
+ requirePublicPackageFields({
225
195
  diagnostics,
226
196
  packageJson,
227
197
  packageJsonPath: request.packageJsonPath,
228
198
  profile: request.profile,
229
- ruleId: 'package.json.type.required',
230
- fieldName: 'type',
231
199
  });
232
200
  }
233
201
  if (request.profile !== 'public-package') {
@@ -240,107 +208,34 @@ async function analyzePackagePolicy(request) {
240
208
  targetPath: request.targetPath,
241
209
  }));
242
210
  }
243
- requireNonEmptyStringField({
244
- diagnostics,
245
- packageJson,
246
- packageJsonPath: request.packageJsonPath,
247
- profile: request.profile,
248
- ruleId: 'package.json.description.required',
249
- fieldName: 'description',
250
- });
251
- requireRecordField({
252
- diagnostics,
253
- packageJson,
254
- packageJsonPath: request.packageJsonPath,
255
- profile: request.profile,
256
- ruleId: 'package.json.repository.required',
257
- fieldName: 'repository',
258
- });
259
- requireNonEmptyStringField({
260
- diagnostics,
261
- packageJson,
262
- packageJsonPath: request.packageJsonPath,
263
- profile: request.profile,
264
- ruleId: 'package.json.homepage.required',
265
- fieldName: 'homepage',
266
- });
267
- requireRecordField({
268
- diagnostics,
269
- packageJson,
270
- packageJsonPath: request.packageJsonPath,
271
- profile: request.profile,
272
- ruleId: 'package.json.bugs.required',
273
- fieldName: 'bugs',
274
- });
275
- requireNonEmptyStringField({
276
- diagnostics,
277
- packageJson,
278
- packageJsonPath: request.packageJsonPath,
279
- profile: request.profile,
280
- ruleId: 'package.json.license.required',
281
- fieldName: 'license',
282
- });
283
- requireStringArrayField({
284
- diagnostics,
285
- packageJson,
286
- packageJsonPath: request.packageJsonPath,
287
- profile: request.profile,
288
- ruleId: 'package.json.keywords.required',
289
- fieldName: 'keywords',
290
- });
291
- requireStringArrayField({
292
- diagnostics,
293
- packageJson,
294
- packageJsonPath: request.packageJsonPath,
295
- profile: request.profile,
296
- ruleId: 'package.json.files.required',
297
- fieldName: 'files',
298
- });
299
- requireRecordField({
300
- diagnostics,
301
- packageJson,
302
- packageJsonPath: request.packageJsonPath,
303
- profile: request.profile,
304
- ruleId: 'package.json.exports.required',
305
- fieldName: 'exports',
306
- });
307
- requireRecordField({
308
- diagnostics,
309
- packageJson,
310
- packageJsonPath: request.packageJsonPath,
311
- profile: request.profile,
312
- ruleId: 'package.json.publish-config.required',
313
- fieldName: 'publishConfig',
314
- });
315
211
  if (packageJson.private === true) {
316
212
  diagnostics.push(createDiagnostic({
317
213
  code: 'field-invalid',
318
214
  message: 'Public package profiles must not set private: true.',
319
215
  path: request.packageJsonPath,
320
216
  profile: request.profile,
321
- ruleId: 'package.json.private.public-package-disallowed',
322
- severity: 'error',
217
+ ...architecturePolicyRuleFields(PUBLIC_PACKAGE_POLICY.privateDisallowedRuleId),
323
218
  }));
324
219
  }
325
- if (packageJson.type !== undefined && packageJson.type !== 'module') {
220
+ if (packageJson.type !== undefined &&
221
+ packageJson.type !== PUBLIC_PACKAGE_POLICY.packageType.value) {
326
222
  diagnostics.push(createDiagnostic({
327
223
  code: 'field-invalid',
328
- message: 'package.json "type" must be "module".',
224
+ message: `package.json "type" must be "${PUBLIC_PACKAGE_POLICY.packageType.value}".`,
329
225
  path: request.packageJsonPath,
330
226
  profile: request.profile,
331
- ruleId: 'package.json.type.module',
332
- severity: 'error',
227
+ ...architecturePolicyRuleFields(PUBLIC_PACKAGE_POLICY.packageType.ruleId),
333
228
  }));
334
229
  }
335
230
  const { publishConfig } = packageJson;
336
- if (isRecord(publishConfig) && publishConfig.access !== 'public') {
231
+ if (isRecord(publishConfig) &&
232
+ publishConfig.access !== PUBLIC_PACKAGE_POLICY.publishAccess.value) {
337
233
  diagnostics.push(createDiagnostic({
338
234
  code: isNonEmptyString(publishConfig.access) ? 'field-invalid' : 'field-missing',
339
- message: 'package.json publishConfig.access must exist and equal "public".',
235
+ message: `package.json publishConfig.access must exist and equal "${PUBLIC_PACKAGE_POLICY.publishAccess.value}".`,
340
236
  path: request.packageJsonPath,
341
237
  profile: request.profile,
342
- ruleId: 'package.json.publish-config.public',
343
- severity: 'error',
238
+ ...architecturePolicyRuleFields(PUBLIC_PACKAGE_POLICY.publishAccess.ruleId),
344
239
  }));
345
240
  }
346
241
  if (!isNonEmptyString(packageJson.packageManager)) {
@@ -349,14 +244,13 @@ async function analyzePackagePolicy(request) {
349
244
  message: 'package.json must define a Bun-aligned packageManager field.',
350
245
  path: request.packageJsonPath,
351
246
  profile: request.profile,
352
- ruleId: 'package.json.package-manager.required',
353
- severity: 'error',
247
+ ...architecturePolicyRuleFields(PUBLIC_PACKAGE_POLICY.packageManager.requiredRuleId),
354
248
  }));
355
249
  request.plannedChanges.push(createPlannedChange({
356
250
  description: `Add packageManager: "bun@${Bun.version}" to package.json.`,
357
251
  filePath: request.packageJsonPath,
358
252
  kind: 'update-json',
359
- ruleId: 'package.json.package-manager.required',
253
+ ruleId: PUBLIC_PACKAGE_POLICY.packageManager.requiredRuleId,
360
254
  }));
361
255
  }
362
256
  else if (!packageJson.packageManager.startsWith('bun@')) {
@@ -365,20 +259,18 @@ async function analyzePackagePolicy(request) {
365
259
  message: 'package.json packageManager must be Bun-aligned, for example "bun@1.x".',
366
260
  path: request.packageJsonPath,
367
261
  profile: request.profile,
368
- ruleId: 'package.json.package-manager.bun',
369
- severity: 'error',
262
+ ...architecturePolicyRuleFields(PUBLIC_PACKAGE_POLICY.packageManager.bunRuleId),
370
263
  }));
371
264
  }
372
265
  const scripts = isRecord(packageJson.scripts) ? packageJson.scripts : null;
373
- for (const scriptName of REQUIRED_PUBLIC_PACKAGE_SCRIPTS) {
374
- if (scripts === null || !isNonEmptyString(scripts[scriptName])) {
266
+ for (const requirement of PUBLIC_PACKAGE_POLICY.requiredScripts) {
267
+ if (scripts === null || !isNonEmptyString(scripts[requirement.name])) {
375
268
  diagnostics.push(createDiagnostic({
376
269
  code: 'missing-script',
377
- message: `Missing required package script: ${scriptName}`,
270
+ message: `Missing required package script: ${requirement.name}`,
378
271
  path: request.packageJsonPath,
379
272
  profile: request.profile,
380
- ruleId: mapScriptRule(scriptName),
381
- severity: 'error',
273
+ ...architecturePolicyRuleFields(requirement.ruleId),
382
274
  }));
383
275
  }
384
276
  }
@@ -392,8 +284,7 @@ async function analyzePackagePolicy(request) {
392
284
  message: 'Public package repos must declare TypeScript in dependencies or devDependencies.',
393
285
  path: request.packageJsonPath,
394
286
  profile: request.profile,
395
- ruleId: 'package.dependencies.typescript.required',
396
- severity: 'error',
287
+ ...architecturePolicyRuleFields(PUBLIC_PACKAGE_POLICY.dependencyRules.typescript),
397
288
  }));
398
289
  }
399
290
  const changesetsDependencyDiagnostic = createChangesetsDependencyDiagnostic({
@@ -412,8 +303,7 @@ async function analyzePackagePolicy(request) {
412
303
  message: 'Public package repos must declare @types/bun in devDependencies.',
413
304
  path: request.packageJsonPath,
414
305
  profile: request.profile,
415
- ruleId: 'package.dependencies.types-bun.required',
416
- severity: 'error',
306
+ ...architecturePolicyRuleFields(PUBLIC_PACKAGE_POLICY.dependencyRules.bunTypes),
417
307
  }));
418
308
  }
419
309
  if (!hasDependency(devDependencies, '@types/node')) {
@@ -422,8 +312,7 @@ async function analyzePackagePolicy(request) {
422
312
  message: 'Public package repos must declare @types/node in devDependencies.',
423
313
  path: request.packageJsonPath,
424
314
  profile: request.profile,
425
- ruleId: 'package.dependencies.types-node.required',
426
- severity: 'error',
315
+ ...architecturePolicyRuleFields(PUBLIC_PACKAGE_POLICY.dependencyRules.nodeTypes),
427
316
  }));
428
317
  }
429
318
  if (expectsDevtools(packageJson) && !hasDependency(devDependencies, '@ankhorage/devtools')) {
@@ -432,8 +321,7 @@ async function analyzePackagePolicy(request) {
432
321
  message: 'This package consumes shared Ankhorage lint/format/knip tooling and must declare @ankhorage/devtools.',
433
322
  path: request.packageJsonPath,
434
323
  profile: request.profile,
435
- ruleId: 'package.dependencies.devtools.required',
436
- severity: 'error',
324
+ ...architecturePolicyRuleFields(PUBLIC_PACKAGE_POLICY.dependencyRules.devtools),
437
325
  }));
438
326
  }
439
327
  if (packageJson.name !== '@ankhorage/paradox' &&
@@ -444,8 +332,7 @@ async function analyzePackagePolicy(request) {
444
332
  message: 'This package owns Paradox docs generation and must declare @ankhorage/paradox.',
445
333
  path: request.packageJsonPath,
446
334
  profile: request.profile,
447
- ruleId: 'package.dependencies.paradox.required',
448
- severity: 'error',
335
+ ...architecturePolicyRuleFields(PUBLIC_PACKAGE_POLICY.dependencyRules.paradox),
449
336
  }));
450
337
  }
451
338
  return diagnostics.concat(await validateAnkhMetadataAndProvider({
@@ -474,8 +361,7 @@ function createChangesetsDependencyDiagnostic(request) {
474
361
  : 'Public package repos must not declare @changesets/cli directly; @ankhorage/devtools owns Changesets execution.',
475
362
  path: request.packageJsonPath,
476
363
  profile: request.profile,
477
- ruleId: 'package.dependencies.changesets.required',
478
- severity: 'error',
364
+ ...architecturePolicyRuleFields(PUBLIC_PACKAGE_POLICY.dependencyRules.changesets),
479
365
  });
480
366
  }
481
367
  async function validateAnkhMetadataAndProvider(request) {
@@ -731,6 +617,30 @@ async function isProviderPackage(targetPath) {
731
617
  }
732
618
  return false;
733
619
  }
620
+ /*** Validate every generic public-package metadata field from canonical Policy. */
621
+ function requirePublicPackageFields(request) {
622
+ for (const requirement of PUBLIC_PACKAGE_POLICY.requiredFields) {
623
+ const input = {
624
+ diagnostics: request.diagnostics,
625
+ fieldName: requirement.name,
626
+ packageJson: request.packageJson,
627
+ packageJsonPath: request.packageJsonPath,
628
+ profile: request.profile,
629
+ ruleId: requirement.ruleId,
630
+ };
631
+ switch (requirement.kind) {
632
+ case 'non-empty-string':
633
+ requireNonEmptyStringField(input);
634
+ break;
635
+ case 'record':
636
+ requireRecordField(input);
637
+ break;
638
+ case 'string-array':
639
+ requireStringArrayField(input);
640
+ break;
641
+ }
642
+ }
643
+ }
734
644
  async function maybeRequirePath(diagnostics, plannedChanges, rootPath, relativePath, ruleId, kind) {
735
645
  const absolutePath = path.join(rootPath, relativePath);
736
646
  if (await pathExists(absolutePath)) {
@@ -741,8 +651,7 @@ async function maybeRequirePath(diagnostics, plannedChanges, rootPath, relativeP
741
651
  message: `Missing expected repo artifact: ${relativePath}`,
742
652
  path: absolutePath,
743
653
  profile: 'public-package',
744
- ruleId,
745
- severity: 'error',
654
+ ...architecturePolicyRuleFields(ruleId),
746
655
  }));
747
656
  plannedChanges.push(createPlannedChange({
748
657
  description: `Create missing ${relativePath}.`,
@@ -760,8 +669,7 @@ function requireNonEmptyStringField(request) {
760
669
  message: `package.json must define a non-empty "${request.fieldName}" field.`,
761
670
  path: request.packageJsonPath,
762
671
  profile: request.profile,
763
- ruleId: request.ruleId,
764
- severity: 'error',
672
+ ...architecturePolicyRuleFields(request.ruleId),
765
673
  }));
766
674
  }
767
675
  function requireRecordField(request) {
@@ -773,8 +681,7 @@ function requireRecordField(request) {
773
681
  message: `package.json must define an object "${request.fieldName}" field.`,
774
682
  path: request.packageJsonPath,
775
683
  profile: request.profile,
776
- ruleId: request.ruleId,
777
- severity: 'error',
684
+ ...architecturePolicyRuleFields(request.ruleId),
778
685
  }));
779
686
  }
780
687
  function requireStringArrayField(request) {
@@ -786,8 +693,7 @@ function requireStringArrayField(request) {
786
693
  message: `package.json must define a non-empty string array "${request.fieldName}" field.`,
787
694
  path: request.packageJsonPath,
788
695
  profile: request.profile,
789
- ruleId: request.ruleId,
790
- severity: 'error',
696
+ ...architecturePolicyRuleFields(request.ruleId),
791
697
  }));
792
698
  }
793
699
  async function expectsParadox(targetPath, packageJson) {
@@ -835,36 +741,6 @@ function validateAnkhMetadataShape(value, providerPackage) {
835
741
  }
836
742
  return null;
837
743
  }
838
- function mapScriptRule(scriptName) {
839
- switch (scriptName) {
840
- case 'build':
841
- return 'package.scripts.build.required';
842
- case 'typecheck':
843
- return 'package.scripts.typecheck.required';
844
- case 'lint':
845
- return 'package.scripts.lint.required';
846
- case 'lint:fix':
847
- return 'package.scripts.lint-fix.required';
848
- case 'format':
849
- return 'package.scripts.format.required';
850
- case 'format:check':
851
- return 'package.scripts.format-check.required';
852
- case 'test':
853
- return 'package.scripts.test.required';
854
- case 'test:standalone':
855
- return 'package.scripts.standalone.required';
856
- case 'knip:check':
857
- return 'package.scripts.knip.required';
858
- case 'docs':
859
- return 'package.scripts.docs.required';
860
- case 'changeset':
861
- return 'package.scripts.changeset.required';
862
- case 'changeset:status':
863
- return 'package.scripts.changeset-status.required';
864
- case 'version-packages':
865
- return 'package.scripts.version-packages.required';
866
- }
867
- }
868
744
  function createEarlyResult(targetPath, diagnostic) {
869
745
  return {
870
746
  appliedChecks: [],
@@ -877,6 +753,11 @@ function createEarlyResult(targetPath, diagnostic) {
877
753
  targetPath,
878
754
  };
879
755
  }
756
+ /*** Return canonical rule id and severity for a Policy-owned generic package rule. */
757
+ function architecturePolicyRuleFields(ruleId) {
758
+ const rule = getArchitecturePolicyRule(ruleId);
759
+ return { ruleId: rule.id, severity: rule.severity };
760
+ }
880
761
  function createDiagnostic(diagnostic) {
881
762
  return diagnostic;
882
763
  }