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.
- package/.agents/scripts/agent_guard.js +63 -0
- package/.agents/skills/lets-build/SKILL.md +1 -1
- package/.github/workflows/ci.yml +9 -0
- package/AGENTS.md +1 -1
- package/CONTRIBUTING.md +79 -0
- package/README.md +4 -3
- package/SECURITY.md +32 -0
- package/bin/azcodr.js +3 -1
- package/data/memory.template +36 -0
- package/docs/rules/clean_code.md +13 -0
- package/docs/tipping-points.md +37 -0
- package/lib/agent-guard-command.d.ts +28 -0
- package/lib/agent-guard-command.d.ts.map +1 -0
- package/lib/agent-guard-command.js +103 -0
- package/lib/agent-guard-command.js.map +1 -0
- package/lib/agent-guard-file.d.ts +32 -0
- package/lib/agent-guard-file.d.ts.map +1 -0
- package/lib/agent-guard-file.js +77 -0
- package/lib/agent-guard-file.js.map +1 -0
- package/lib/agent-guard-tdd.d.ts +34 -0
- package/lib/agent-guard-tdd.d.ts.map +1 -0
- package/lib/agent-guard-tdd.js +77 -0
- package/lib/agent-guard-tdd.js.map +1 -0
- package/lib/agent-guard.d.ts +38 -0
- package/lib/agent-guard.d.ts.map +1 -0
- package/lib/agent-guard.js +80 -0
- package/lib/agent-guard.js.map +1 -0
- package/lib/boundaries.d.ts +41 -0
- package/lib/boundaries.d.ts.map +1 -0
- package/lib/boundaries.js +158 -0
- package/lib/boundaries.js.map +1 -0
- package/lib/cli-parse.d.ts +4 -0
- package/lib/cli-parse.d.ts.map +1 -1
- package/lib/cli-parse.js +23 -5
- package/lib/cli-parse.js.map +1 -1
- package/lib/cli.d.ts +14 -0
- package/lib/cli.d.ts.map +1 -1
- package/lib/cli.js +33 -5
- package/lib/cli.js.map +1 -1
- package/lib/errors.d.ts +15 -0
- package/lib/errors.d.ts.map +1 -1
- package/lib/errors.js +9 -0
- package/lib/errors.js.map +1 -1
- package/lib/git.d.ts +8 -0
- package/lib/git.d.ts.map +1 -1
- package/lib/git.js +8 -0
- package/lib/git.js.map +1 -1
- package/lib/guards.d.ts +22 -1
- package/lib/guards.d.ts.map +1 -1
- package/lib/guards.js +23 -2
- package/lib/guards.js.map +1 -1
- package/lib/index.d.ts +34 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +25 -0
- package/lib/index.js.map +1 -1
- package/lib/links.d.ts +14 -0
- package/lib/links.d.ts.map +1 -1
- package/lib/links.js +11 -0
- package/lib/links.js.map +1 -1
- package/lib/permissions.d.ts +4 -0
- package/lib/permissions.d.ts.map +1 -1
- package/lib/permissions.js +4 -0
- package/lib/permissions.js.map +1 -1
- package/lib/repo.d.ts +10 -0
- package/lib/repo.d.ts.map +1 -1
- package/lib/repo.js +11 -4
- package/lib/repo.js.map +1 -1
- package/lib/scaffold.d.ts +29 -5
- package/lib/scaffold.d.ts.map +1 -1
- package/lib/scaffold.js +27 -25
- package/lib/scaffold.js.map +1 -1
- package/lib/validate.d.ts +33 -0
- package/lib/validate.d.ts.map +1 -0
- package/lib/validate.js +24 -0
- package/lib/validate.js.map +1 -0
- package/memory.md +16 -0
- package/package.json +7 -6
- package/scripts/validate/links.js +0 -1
- package/scripts/validate/root.js +1 -1
- package/src/agent-guard-command.ts +118 -0
- package/src/agent-guard-file.ts +107 -0
- package/src/agent-guard-tdd.ts +96 -0
- package/src/agent-guard.ts +123 -0
- package/src/boundaries.ts +198 -0
- package/src/cli-parse.ts +26 -4
- package/src/cli.ts +41 -15
- package/src/errors.ts +15 -0
- package/src/git.ts +8 -0
- package/src/guards.ts +27 -2
- package/src/index.ts +61 -0
- package/src/links.ts +14 -0
- package/src/permissions.ts +4 -0
- package/src/repo.ts +14 -4
- package/src/scaffold.ts +42 -36
- package/src/validate.ts +47 -0
- 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;
|
package/src/permissions.ts
CHANGED
|
@@ -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: '
|
|
57
|
-
GIT_AUTHOR_EMAIL: '
|
|
58
|
-
GIT_COMMITTER_NAME: '
|
|
59
|
-
GIT_COMMITTER_EMAIL: '
|
|
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': '
|
|
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
|
-
|
|
243
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
};
|
package/src/validate.ts
ADDED
|
@@ -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 };
|
package/scripts/test_coverage.js
DELETED
|
@@ -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);
|