azcodr 2.1.0 → 2.3.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 (96) hide show
  1. package/.agents/scripts/agent_guard.js +63 -0
  2. package/.agents/skills/lets-build/SKILL.md +1 -1
  3. package/.github/workflows/ci.yml +9 -0
  4. package/AGENTS.md +1 -1
  5. package/CONTRIBUTING.md +79 -0
  6. package/README.md +4 -3
  7. package/SECURITY.md +32 -0
  8. package/bin/azcodr.js +3 -1
  9. package/data/memory.template +36 -0
  10. package/docs/rules/clean_code.md +13 -0
  11. package/docs/tipping-points.md +37 -0
  12. package/lib/agent-guard-command.d.ts +28 -0
  13. package/lib/agent-guard-command.d.ts.map +1 -0
  14. package/lib/agent-guard-command.js +103 -0
  15. package/lib/agent-guard-command.js.map +1 -0
  16. package/lib/agent-guard-file.d.ts +32 -0
  17. package/lib/agent-guard-file.d.ts.map +1 -0
  18. package/lib/agent-guard-file.js +77 -0
  19. package/lib/agent-guard-file.js.map +1 -0
  20. package/lib/agent-guard-tdd.d.ts +34 -0
  21. package/lib/agent-guard-tdd.d.ts.map +1 -0
  22. package/lib/agent-guard-tdd.js +77 -0
  23. package/lib/agent-guard-tdd.js.map +1 -0
  24. package/lib/agent-guard.d.ts +38 -0
  25. package/lib/agent-guard.d.ts.map +1 -0
  26. package/lib/agent-guard.js +80 -0
  27. package/lib/agent-guard.js.map +1 -0
  28. package/lib/boundaries.d.ts +41 -0
  29. package/lib/boundaries.d.ts.map +1 -0
  30. package/lib/boundaries.js +158 -0
  31. package/lib/boundaries.js.map +1 -0
  32. package/lib/cli-parse.d.ts +4 -0
  33. package/lib/cli-parse.d.ts.map +1 -1
  34. package/lib/cli-parse.js +23 -5
  35. package/lib/cli-parse.js.map +1 -1
  36. package/lib/cli.d.ts +14 -0
  37. package/lib/cli.d.ts.map +1 -1
  38. package/lib/cli.js +33 -5
  39. package/lib/cli.js.map +1 -1
  40. package/lib/errors.d.ts +15 -0
  41. package/lib/errors.d.ts.map +1 -1
  42. package/lib/errors.js +9 -0
  43. package/lib/errors.js.map +1 -1
  44. package/lib/git.d.ts +8 -0
  45. package/lib/git.d.ts.map +1 -1
  46. package/lib/git.js +8 -0
  47. package/lib/git.js.map +1 -1
  48. package/lib/guards.d.ts +22 -1
  49. package/lib/guards.d.ts.map +1 -1
  50. package/lib/guards.js +23 -2
  51. package/lib/guards.js.map +1 -1
  52. package/lib/index.d.ts +34 -0
  53. package/lib/index.d.ts.map +1 -1
  54. package/lib/index.js +25 -0
  55. package/lib/index.js.map +1 -1
  56. package/lib/links.d.ts +14 -0
  57. package/lib/links.d.ts.map +1 -1
  58. package/lib/links.js +11 -0
  59. package/lib/links.js.map +1 -1
  60. package/lib/permissions.d.ts +4 -0
  61. package/lib/permissions.d.ts.map +1 -1
  62. package/lib/permissions.js +4 -0
  63. package/lib/permissions.js.map +1 -1
  64. package/lib/repo.d.ts +10 -0
  65. package/lib/repo.d.ts.map +1 -1
  66. package/lib/repo.js +11 -4
  67. package/lib/repo.js.map +1 -1
  68. package/lib/scaffold.d.ts +29 -5
  69. package/lib/scaffold.d.ts.map +1 -1
  70. package/lib/scaffold.js +27 -25
  71. package/lib/scaffold.js.map +1 -1
  72. package/lib/validate.d.ts +33 -0
  73. package/lib/validate.d.ts.map +1 -0
  74. package/lib/validate.js +24 -0
  75. package/lib/validate.js.map +1 -0
  76. package/memory.md +16 -0
  77. package/package.json +7 -6
  78. package/scripts/validate/links.js +0 -1
  79. package/scripts/validate/root.js +1 -1
  80. package/src/agent-guard-command.ts +118 -0
  81. package/src/agent-guard-file.ts +107 -0
  82. package/src/agent-guard-tdd.ts +96 -0
  83. package/src/agent-guard.ts +123 -0
  84. package/src/boundaries.ts +198 -0
  85. package/src/cli-parse.ts +26 -4
  86. package/src/cli.ts +41 -15
  87. package/src/errors.ts +15 -0
  88. package/src/git.ts +8 -0
  89. package/src/guards.ts +27 -2
  90. package/src/index.ts +61 -0
  91. package/src/links.ts +14 -0
  92. package/src/permissions.ts +4 -0
  93. package/src/repo.ts +14 -4
  94. package/src/scaffold.ts +42 -36
  95. package/src/validate.ts +47 -0
  96. package/scripts/test_coverage.js +0 -66
package/src/guards.ts CHANGED
@@ -2,9 +2,22 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import os from 'node:os';
4
4
  import process from 'node:process';
5
+ import { fileURLToPath } from 'node:url';
5
6
  import { ScaffoldError, ERROR_CODES } from './errors.js';
6
- import { getTemplateDir } from './scaffold.js';
7
7
 
8
+ /**
9
+ * Resolves the root filesystem directory of the azcodr template files.
10
+ *
11
+ * @returns Absolute filesystem path to the template root directory.
12
+ */
13
+ export function getTemplateDir(): string {
14
+ const currentDir = import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url));
15
+ return path.resolve(currentDir, '..');
16
+ }
17
+
18
+ /**
19
+ * Options for configuring protected target verification.
20
+ */
8
21
  export interface ProtectedTargetOptions {
9
22
  templateDir?: string;
10
23
  force?: boolean;
@@ -14,7 +27,12 @@ export interface ProtectedTargetOptions {
14
27
 
15
28
  /**
16
29
  * Filesystem scope guard (Supply Chain: filesystem access).
17
- * Constrains all reads/writes to targetDir / templateDir.
30
+ * Constrains all reads/writes to targetDir / templateDir, throwing if candidate escapes root.
31
+ *
32
+ * @param root - Absolute base directory.
33
+ * @param candidate - Candidate file or directory path to check.
34
+ * @param message - Optional custom error message.
35
+ * @throws {ScaffoldError} When candidate path escapes root directory.
18
36
  */
19
37
  export function assertInside(root: string, candidate: string, message?: string): void {
20
38
  const resolvedRoot = path.resolve(root);
@@ -29,6 +47,9 @@ export function assertInside(root: string, candidate: string, message?: string):
29
47
  * Normalizes a path for protected-target comparison. On Windows the filesystem
30
48
  * is case-insensitive, so `C:\Users\Name` and `c:\users\name` are the same
31
49
  * directory and must compare equal.
50
+ *
51
+ * @param p - File or directory path to normalize.
52
+ * @returns Normalized path string.
32
53
  */
33
54
  export function normalizeForComparison(p: string): string {
34
55
  const resolved = path.resolve(p);
@@ -82,6 +103,10 @@ function resolvesToProtectedLocation(resolvedTarget: string, home: string): bool
82
103
  *
83
104
  * Lexical comparison is not enough: a symlink pointing at home must also be
84
105
  * caught, so an existing target is resolved with realpath before comparing.
106
+ *
107
+ * @param targetDir - The target directory path to evaluate.
108
+ * @param options - Optional configuration for protected target evaluation.
109
+ * @returns True if target is a protected location, false otherwise.
85
110
  */
86
111
  export function isProtectedTarget(targetDir: string, options: ProtectedTargetOptions = {}): boolean {
87
112
  const resolvedTarget = path.resolve(targetDir);
package/src/index.ts CHANGED
@@ -1,3 +1,14 @@
1
+ /**
2
+ * @module @azcodr/azcodr
3
+ * Enterprise Architecture & Agentic Engineering Starter Template.
4
+ *
5
+ * Provides problem-first, topology-aligned project scaffolding with strict systemic atomicity,
6
+ * filesystem scope guards, git worktree initialization, and multi-agent harness parity.
7
+ */
8
+
9
+ /**
10
+ * Union type of all valid machine-readable error codes.
11
+ */
1
12
  export type ScaffoldErrorCode =
2
13
  | 'E_TARGET_IS_TEMPLATE'
3
14
  | 'E_TARGET_NOT_EMPTY'
@@ -6,6 +17,9 @@ export type ScaffoldErrorCode =
6
17
  | 'E_GIT_BLOCKED'
7
18
  | 'E_PATH_ESCAPE';
8
19
 
20
+ /**
21
+ * Core scaffolding functions, guards, utilities, error classes, and constants.
22
+ */
9
23
  export {
10
24
  scaffold,
11
25
  validateTarget,
@@ -25,6 +39,49 @@ export {
25
39
  ERROR_CODES
26
40
  } from './scaffold.js';
27
41
 
42
+ /**
43
+ * Standalone architecture validation engine.
44
+ */
45
+ export { validate, createSilentReporter } from './validate.js';
46
+ export type { ValidationResult, ValidationReporter } from './validate.js';
47
+
48
+ /**
49
+ * Agent-runtime architectural enforcement and safety guards.
50
+ */
51
+ export {
52
+ inspectPreTool,
53
+ inspectCommand,
54
+ inspectFileWrite,
55
+ inspectTddRequirement,
56
+ readTddState,
57
+ writeTddState
58
+ } from './agent-guard.js';
59
+ export type {
60
+ GuardDecision,
61
+ GuardOptions,
62
+ ToolEnvelope,
63
+ CommandViolation,
64
+ FileCheckResult,
65
+ SessionTddState
66
+ } from './agent-guard.js';
67
+
68
+ /**
69
+ * Architectural boundary enforcement and dependency cycle prevention.
70
+ */
71
+ export {
72
+ inspectBoundaries,
73
+ detectDependencyCycles,
74
+ detectBoundaryViolations,
75
+ findSourceFiles,
76
+ extractLocalImports,
77
+ buildDependencyGraph,
78
+ DEFAULT_BOUNDARY_RULES
79
+ } from './boundaries.js';
80
+ export type { BoundaryRule, BoundaryViolation, BoundaryReport } from './boundaries.js';
81
+
82
+ /**
83
+ * Type definitions and option interfaces for scaffolding operations.
84
+ */
28
85
  export type {
29
86
  ScaffoldOptions,
30
87
  ScaffoldResult,
@@ -36,4 +93,8 @@ export type {
36
93
  } from './scaffold.js';
37
94
 
38
95
  import scaffoldModule from './scaffold.js';
96
+
97
+ /**
98
+ * Default export providing the unified azcodr scaffolding module.
99
+ */
39
100
  export default scaffoldModule;
package/src/links.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
 
4
+ /**
5
+ * Configuration options for symlink and pointer creation.
6
+ */
4
7
  export interface EnsureSymlinkOptions {
5
8
  targetDir: string;
6
9
  linkName: string;
@@ -70,6 +73,11 @@ function copyFallback(targetDir: string, linkName: string, targetFileName: strin
70
73
  /**
71
74
  * Detects whether linkName and targetFileName refer to the same entry on a case-insensitive filesystem.
72
75
  * Uses exact directory listing (not existsSync, which lies on case-insensitive systems).
76
+ *
77
+ * @param targetDir - Directory containing the file entries.
78
+ * @param linkName - Name of the proposed symlink or alias entry.
79
+ * @param targetFileName - Target file name to match against.
80
+ * @returns True if both names resolve to the same disk file on a case-insensitive filesystem.
73
81
  */
74
82
  export function isSameCaseInsensitiveFile(
75
83
  targetDir: string,
@@ -90,6 +98,9 @@ export function isSameCaseInsensitiveFile(
90
98
 
91
99
  /**
92
100
  * Safely creates or updates a symbolic link, falling back to a file copy if symlinks are unsupported.
101
+ *
102
+ * @param options - Options specifying target directory, link name, target file name, and dry-run mode.
103
+ * @returns True if the symlink or copy fallback was successfully established.
93
104
  */
94
105
  export function ensureSymlink(options: EnsureSymlinkOptions): boolean {
95
106
  const { targetDir, linkName, targetFileName, dryRun = false } = options;
@@ -110,6 +121,9 @@ export function ensureSymlink(options: EnsureSymlinkOptions): boolean {
110
121
  * Creates a symlink, falling back to a text pointer (not a full copy).
111
122
  * Used for .github/copilot-instructions.md where a full AGENTS.md copy would
112
123
  * break relative markdown links (they resolve from .github/, not root).
124
+ *
125
+ * @param options - Options specifying target directory, link name, target file name, and dry-run mode.
126
+ * @returns True if the symlink or pointer fallback was successfully established.
113
127
  */
114
128
  export function ensureSymlinkOrPointer(options: EnsureSymlinkOptions): boolean {
115
129
  const { targetDir, linkName, targetFileName, dryRun = false } = options;
@@ -25,6 +25,10 @@ function chmodShFilesIn(scriptsDir: string, dryRun: boolean): string[] {
25
25
 
26
26
  /**
27
27
  * Ensures all bash scripts in agent and skill directories have executable permissions (0o755).
28
+ *
29
+ * @param targetDir - Root directory containing .agents and skills.
30
+ * @param dryRun - When true, simulates permission changes without modifying disk.
31
+ * @returns Array of file paths that were modified or would be modified.
28
32
  */
29
33
  export function makeScriptsExecutable(targetDir: string, dryRun = false): string[] {
30
34
  const agentScriptsDir = path.join(targetDir, '.agents', 'scripts');
package/src/repo.ts CHANGED
@@ -5,6 +5,9 @@ import { runGit } from './git.js';
5
5
 
6
6
  const INITIAL_COMMIT_MESSAGE = 'chore: initial scaffold from azcodr template';
7
7
 
8
+ /**
9
+ * Options controlling git repository initialization.
10
+ */
8
11
  export interface InitGitOptions {
9
12
  noGit?: boolean;
10
13
  dryRun?: boolean;
@@ -13,6 +16,9 @@ export interface InitGitOptions {
13
16
  /**
14
17
  * Detects whether targetDir is already inside an existing Git worktree.
15
18
  * Socket note: shell access is limited to `git rev-parse` via runGit (no shell).
19
+ *
20
+ * @param targetDir - Directory path to inspect.
21
+ * @returns True if target directory is within a git worktree, false otherwise.
16
22
  */
17
23
  export function isInsideGitWorkTree(targetDir: string): boolean {
18
24
  try {
@@ -53,10 +59,10 @@ function commitWithFallbackIdentity(targetDir: string): void {
53
59
  ...options,
54
60
  env: {
55
61
  ...process.env,
56
- GIT_AUTHOR_NAME: 'Subodh Khanal',
57
- GIT_AUTHOR_EMAIL: 'prosubodh@gmail.com',
58
- GIT_COMMITTER_NAME: 'Subodh Khanal',
59
- GIT_COMMITTER_EMAIL: 'prosubodh@gmail.com'
62
+ GIT_AUTHOR_NAME: 'azcodr[bot]',
63
+ GIT_AUTHOR_EMAIL: 'bot@azcodr.internal',
64
+ GIT_COMMITTER_NAME: 'azcodr[bot]',
65
+ GIT_COMMITTER_EMAIL: 'bot@azcodr.internal'
60
66
  }
61
67
  });
62
68
  }
@@ -74,6 +80,10 @@ function initialCommit(targetDir: string): void {
74
80
  /**
75
81
  * Initializes a git repository in the target directory if not already inside one.
76
82
  * Socket note: only allowlisted `git init/branch/add/commit` via execFileSync, cwd scoped.
83
+ *
84
+ * @param targetDir - Directory where git repository should be initialized.
85
+ * @param options - Configuration options for git initialization.
86
+ * @returns True if repository was initialized and committed, false otherwise.
77
87
  */
78
88
  export function initGit(targetDir: string, options: InitGitOptions = {}): boolean {
79
89
  const { noGit = false, dryRun = false } = options;
package/src/scaffold.ts CHANGED
@@ -1,17 +1,21 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import process from 'node:process';
4
- import { fileURLToPath } from 'node:url';
5
4
  import { ScaffoldError, ERROR_CODES } from './errors.js';
6
5
  import type { ScaffoldErrorCode, ScaffoldErrorShape } from './errors.js';
7
6
  import { runGit } from './git.js';
8
- import { assertInside, isProtectedTarget } from './guards.js';
7
+ import { assertInside, isProtectedTarget, getTemplateDir } from './guards.js';
9
8
  import { ensureSymlink, ensureSymlinkOrPointer, isSameCaseInsensitiveFile } from './links.js';
10
9
  import type { EnsureSymlinkOptions } from './links.js';
11
10
  import { makeScriptsExecutable } from './permissions.js';
12
11
  import { isInsideGitWorkTree, initGit } from './repo.js';
13
12
  import type { InitGitOptions } from './repo.js';
14
13
 
14
+ export { getTemplateDir };
15
+
16
+ /**
17
+ * Canonical list of template files and directories copied into a new workspace.
18
+ */
15
19
  export const TEMPLATE_ITEMS: readonly string[] = [
16
20
  'AGENTS.md',
17
21
  'memory.md',
@@ -25,6 +29,9 @@ export const TEMPLATE_ITEMS: readonly string[] = [
25
29
  'LICENSE'
26
30
  ];
27
31
 
32
+ /**
33
+ * Configuration options for the primary scaffold orchestration function.
34
+ */
28
35
  export interface ScaffoldOptions {
29
36
  targetDir?: string;
30
37
  force?: boolean;
@@ -34,6 +41,9 @@ export interface ScaffoldOptions {
34
41
  silent?: boolean;
35
42
  }
36
43
 
44
+ /**
45
+ * Result object returned upon completion of a scaffold operation.
46
+ */
37
47
  export interface ScaffoldResult {
38
48
  success: boolean;
39
49
  targetDir: string;
@@ -42,6 +52,9 @@ export interface ScaffoldResult {
42
52
  actions: string[];
43
53
  }
44
54
 
55
+ /**
56
+ * Options controlling pre-scaffold target directory validation.
57
+ */
45
58
  export interface ValidateTargetOptions {
46
59
  templateDir?: string;
47
60
  force?: boolean;
@@ -49,16 +62,13 @@ export interface ValidateTargetOptions {
49
62
  allowProtected?: boolean;
50
63
  }
51
64
 
65
+ /**
66
+ * Options controlling template file copying and file generation.
67
+ */
52
68
  export interface CopyTemplateOptions {
53
69
  dryRun?: boolean;
54
70
  }
55
71
 
56
- /**
57
- * Returns the root path to the azcodr template files.
58
- */
59
- export function getTemplateDir(): string {
60
- return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
61
- }
62
72
 
63
73
  function rejectTemplateSelf(resolvedTarget: string, resolvedTemplate: string): void {
64
74
  if (resolvedTarget !== resolvedTemplate) return;
@@ -94,6 +104,10 @@ function rejectNonEmptyTarget(resolvedTarget: string, force: boolean): void {
94
104
 
95
105
  /**
96
106
  * Validates the target directory to ensure it is suitable for scaffolding.
107
+ *
108
+ * @param targetDir - Target directory path to validate.
109
+ * @param options - Validation options including force, dry-run, and protected overrides.
110
+ * @throws {ScaffoldError} When target is invalid, protected, or non-empty without force.
97
111
  */
98
112
  export function validateTarget(targetDir: string, options: ValidateTargetOptions = {}): void {
99
113
  const { templateDir = getTemplateDir(), force = false, dryRun = false, allowProtected = false } = options;
@@ -109,6 +123,10 @@ export function validateTarget(targetDir: string, options: ValidateTargetOptions
109
123
  }
110
124
 
111
125
  function resolveTemplateItem(resolvedTemplate: string, item: string): string {
126
+ if (item === 'memory.md') {
127
+ const memTpl = path.join(resolvedTemplate, 'data', 'memory.template');
128
+ if (fs.existsSync(memTpl)) return memTpl;
129
+ }
112
130
  let srcPath = path.join(resolvedTemplate, item);
113
131
  if (item === '.gitignore' && !fs.existsSync(srcPath)) {
114
132
  const npmIgnorePath = path.join(resolvedTemplate, '.npmignore');
@@ -169,7 +187,7 @@ function starterPackageJson(resolvedTarget: string): Record<string, unknown> {
169
187
  description: 'Scaffolded with azcodr enterprise architecture template',
170
188
  scripts: {
171
189
  test: 'node --test',
172
- 'test:coverage': 'node scripts/test_coverage.js',
190
+ 'test:coverage': 'jest --coverage',
173
191
  lint: 'echo "No linter configured yet. Run /lets-build to configure toolchain."',
174
192
  validate: 'node scripts/validate-cli.js'
175
193
  }
@@ -197,6 +215,11 @@ function chmodTemplateScripts(inspectDir: string, dryRun: boolean): string[] {
197
215
  /**
198
216
  * Recursively copies template files into the target directory and sets up symlinks and permissions.
199
217
  * Socket note: filesystem access is scoped to templateDir -> targetDir only (assertInside).
218
+ *
219
+ * @param targetDir - Destination directory path.
220
+ * @param templateDir - Source template directory path.
221
+ * @param options - Copy options including dry-run flag.
222
+ * @returns Array of recorded scaffolding action descriptions.
200
223
  */
201
224
  export function copyTemplate(
202
225
  targetDir: string,
@@ -220,6 +243,9 @@ export function copyTemplate(
220
243
 
221
244
  /**
222
245
  * High-level orchestration function to scaffold the azcodr workspace into targetDir.
246
+ *
247
+ * @param options - Scaffolding options.
248
+ * @returns Result object containing success flag, target directory, and action log.
223
249
  */
224
250
  export function scaffold(options: ScaffoldOptions = {}): ScaffoldResult {
225
251
  const {
@@ -238,36 +264,16 @@ export function scaffold(options: ScaffoldOptions = {}): ScaffoldResult {
238
264
  }
239
265
 
240
266
  export {
241
- ensureSymlink,
242
- ensureSymlinkOrPointer,
243
- isSameCaseInsensitiveFile,
244
- makeScriptsExecutable,
245
- isInsideGitWorkTree,
246
- initGit,
247
- runGit,
248
- assertInside,
249
- ScaffoldError,
250
- ERROR_CODES,
251
- isProtectedTarget
267
+ ensureSymlink, ensureSymlinkOrPointer, isSameCaseInsensitiveFile,
268
+ makeScriptsExecutable, isInsideGitWorkTree, initGit, runGit,
269
+ assertInside, ScaffoldError, ERROR_CODES, isProtectedTarget
252
270
  };
253
271
 
254
272
  export type { EnsureSymlinkOptions, InitGitOptions, ScaffoldErrorCode, ScaffoldErrorShape };
255
273
 
256
274
  export default {
257
- scaffold,
258
- validateTarget,
259
- copyTemplate,
260
- ensureSymlink,
261
- ensureSymlinkOrPointer,
262
- isSameCaseInsensitiveFile,
263
- makeScriptsExecutable,
264
- isInsideGitWorkTree,
265
- initGit,
266
- getTemplateDir,
267
- runGit,
268
- assertInside,
269
- TEMPLATE_ITEMS,
270
- ScaffoldError,
271
- ERROR_CODES,
272
- isProtectedTarget
275
+ scaffold, validateTarget, copyTemplate, ensureSymlink,
276
+ ensureSymlinkOrPointer, isSameCaseInsensitiveFile, makeScriptsExecutable,
277
+ isInsideGitWorkTree, initGit, getTemplateDir, runGit,
278
+ assertInside, TEMPLATE_ITEMS, ScaffoldError, ERROR_CODES, isProtectedTarget
273
279
  };
@@ -0,0 +1,47 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath, pathToFileURL } from 'node:url';
3
+
4
+ export interface ValidationResult {
5
+ errors: number;
6
+ warnings: number;
7
+ validatedRules: number;
8
+ validatedSkills: number;
9
+ totalLinks: number;
10
+ brokenLinks: string[];
11
+ }
12
+
13
+ export interface ValidationReporter {
14
+ pass: (msg: string) => void;
15
+ warn: (msg: string) => void;
16
+ fail: (msg: string) => void;
17
+ log: (msg: string) => void;
18
+ heading: (msg: string) => void;
19
+ }
20
+
21
+ /**
22
+ * Validates the agentic architecture and governance files of a workspace.
23
+ *
24
+ * @param workspaceRoot - Path to the workspace directory (defaults to cwd).
25
+ * @param reporter - Optional custom reporter hooks.
26
+ * @returns Object containing error and warning tallies and validation counts.
27
+ */
28
+ export async function validate(
29
+ workspaceRoot: string = process.cwd(),
30
+ reporter?: ValidationReporter
31
+ ): Promise<ValidationResult> {
32
+ const baseDir = import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url));
33
+ const scriptPath = path.resolve(baseDir, '../scripts/validate.js');
34
+ const mod = await import(pathToFileURL(scriptPath).href);
35
+ return mod.runValidation(path.resolve(workspaceRoot), reporter);
36
+ }
37
+
38
+ const noop = (): void => {};
39
+
40
+ /**
41
+ * Creates a silent reporter that suppresses all status reports.
42
+ */
43
+ export function createSilentReporter(): ValidationReporter {
44
+ return { pass: noop, warn: noop, fail: noop, log: noop, heading: noop };
45
+ }
46
+
47
+ export default { validate, createSilentReporter };
@@ -1,66 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { spawnSync } from 'node:child_process';
4
- import process from 'node:process';
5
-
6
- // Native threshold flags and include filtering were added in Node v22.8.0.
7
- // Fail closed on older runtimes instead of silently passing without a gate.
8
- const [major, minor] = process.versions.node.split('.').map(Number);
9
- const supportsThresholds = major > 22 || (major === 22 && minor >= 8);
10
- if (!supportsThresholds) {
11
- console.error(
12
- `test:coverage requires Node >=22.8.0 for threshold enforcement (current: ${process.versions.node}). ` +
13
- `Upgrade Node, or invoke the runner directly: node --test --experimental-test-coverage.`
14
- );
15
- process.exit(1);
16
- }
17
-
18
- const args = [
19
- '--test',
20
- '--experimental-test-coverage',
21
- '--test-coverage-include=bin/**',
22
- '--test-coverage-include=lib/**',
23
- // Explicit file, not scripts/**: validate-cli.js is a two-line process entry
24
- // that only runs when invoked directly, so it cannot be covered in-process.
25
- // Every other script IS gated.
26
- '--test-coverage-include=scripts/validate.js',
27
- '--test-coverage-include=scripts/validate/io.js',
28
- '--test-coverage-include=scripts/validate/text.js',
29
- '--test-coverage-include=scripts/validate/parity.js',
30
- '--test-coverage-include=scripts/validate/adr.js',
31
- '--test-coverage-include=scripts/validate/root.js',
32
- '--test-coverage-include=scripts/validate/rules.js',
33
- '--test-coverage-include=scripts/validate/skills.js',
34
- '--test-coverage-include=scripts/validate/links.js',
35
- '--test-coverage-include=scripts/test_coverage.js',
36
- // bin/, lib/ and test_coverage.js hold no environment-dependent defensive
37
- // branches and are held at 100%. validate.js is a filesystem auditor: it
38
- // contains fault handlers for conditions this host cannot produce (creating
39
- // symlinks requires Developer Mode; a RangeError needs a tree thousands of
40
- // levels deep). Those are exercised by tests/validate-faults.test.js where
41
- // the fault can be injected, so the gate here is deliberately lower rather
42
- // than silently passing.
43
- '--test-coverage-branches=98',
44
- '--test-coverage-functions=97',
45
- '--test-coverage-lines=100'
46
- ];
47
-
48
- const result = spawnSync(process.execPath, args, {
49
- stdio: 'inherit',
50
- env: process.env
51
- });
52
-
53
- if (result.error) {
54
- console.error(result.error);
55
- process.exit(1);
56
- }
57
-
58
- // A null status means the child was killed by a signal (OOM, SIGKILL from a
59
- // runner timeout). `status ?? 0` reported success for a crashed runner, which
60
- // is how a green build can carry zero coverage data.
61
- if (result.signal) {
62
- console.error(`test runner terminated by signal ${result.signal}; coverage gate could not be evaluated.`);
63
- process.exit(1);
64
- }
65
-
66
- process.exit(result.status ?? 1);