azcodr 1.0.1 → 1.2.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/skills/agentic-architect/scripts/validate_agentic_configs.sh +24 -8
- package/.agents/skills/lets-build/SKILL.md +71 -78
- package/.agents/skills/lets-build/references/architecture_interview_matrix.md +78 -157
- package/.agents/skills/lets-build/scripts/bootstrap_workspace.sh +75 -35
- package/.editorconfig +19 -0
- package/.gitignore +3 -0
- package/AGENTS.md +12 -10
- package/README.md +3 -8
- package/bin/azcodr.js +170 -97
- package/changes.md +26 -0
- package/docs/rules/api_versioning.md +0 -15
- package/docs/rules/clean_code.md +37 -0
- package/docs/rules/continuous_learning.md +14 -13
- package/docs/rules/database_integrity.md +9 -17
- package/docs/rules/design_patterns.md +1 -0
- package/docs/rules/domain_driven_design.md +31 -21
- package/docs/rules/error_handling.md +14 -1
- package/docs/rules/multitenancy_isolation.md +2 -0
- package/docs/rules/product_ownership.md +0 -18
- package/docs/rules/project_management.md +0 -17
- package/docs/rules/react.md +4 -14
- package/docs/rules/requirements_engineering.md +0 -17
- package/docs/rules/rest_api_conventions.md +0 -16
- package/docs/rules/test_driven_development.md +42 -20
- package/docs/rules/transactional_email.md +11 -4
- package/docs/rules/ui_ux_architecture.md +5 -26
- package/docs/rules/upstream_synchronization.md +0 -15
- package/docs/rules/workflow_state_machines.md +0 -18
- package/lib/index.d.ts +153 -0
- package/lib/scaffold.js +86 -24
- package/memory.md +85 -219
- package/package.json +14 -4
- package/docs/knowledge/dos_and_donts.md +0 -540
- package/docs/knowledge/issue_log.md +0 -25
- package/docs/knowledge/lessons_learned.md +0 -107
|
@@ -98,21 +98,3 @@ Every state change across any entity or workflow MUST be immutably recorded in a
|
|
|
98
98
|
| `reason` | String? | Optional justification or audit comment. |
|
|
99
99
|
| `metadata` | JSON? | Snapshot of transition context or rule evaluation. |
|
|
100
100
|
| `createdAt` | DateTime | Immutable timestamp of transition. |
|
|
101
|
-
|
|
102
|
-
---
|
|
103
|
-
|
|
104
|
-
## 4. Invariants (DO's & DONT's)
|
|
105
|
-
|
|
106
|
-
### DO
|
|
107
|
-
- **DO** enforce core business invariants inside Aggregate Roots using strongly-typed state transitions.
|
|
108
|
-
- **DO** decouple core domain state from operational display stages or tenant-specific sub-statuses.
|
|
109
|
-
- **DO** use non-Turing complete expression languages (CEL) to evaluate dynamic transition guards.
|
|
110
|
-
- **DO** maintain an immutable transition history table for all status alterations.
|
|
111
|
-
- **DO** validate state transition graphs at creation time to prevent dead-end or unreachable states.
|
|
112
|
-
|
|
113
|
-
### DONT
|
|
114
|
-
- **DONT** make financial or legal integrity states (e.g., `SETTLED`, `CANCELLED`, `REFUNDED`) freely rewritable by tenant configuration.
|
|
115
|
-
- **DONT** expose generic status setters (`entity.setStatus(newStatus)`) on domain models.
|
|
116
|
-
- **DONT** execute untrusted scripts or dynamic strings in the host runtime for workflow evaluations.
|
|
117
|
-
- **DONT** allow a workflow orchestrator to bypass aggregate invariants by directly updating database columns.
|
|
118
|
-
- **DONT** delete historical state transitions; status audit logs must be append-only.
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enterprise Multi-Tenant Architecture & Agentic Engineering Starter Template
|
|
3
|
+
* Programmatic API Definitions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface ScaffoldOptions {
|
|
7
|
+
/** Target directory path where template should be scaffolded (default: process.cwd()) */
|
|
8
|
+
targetDir?: string;
|
|
9
|
+
/** Force overwrite if target directory is non-empty (default: false) */
|
|
10
|
+
force?: boolean;
|
|
11
|
+
/** Skip git repository initialization (default: false) */
|
|
12
|
+
noGit?: boolean;
|
|
13
|
+
/** Custom template root directory (default: azcodr root) */
|
|
14
|
+
templateDir?: string;
|
|
15
|
+
/** Simulate scaffolding without writing files or running git (default: false) */
|
|
16
|
+
dryRun?: boolean;
|
|
17
|
+
/** Suppress console output messages (default: false) */
|
|
18
|
+
silent?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ScaffoldResult {
|
|
22
|
+
/** Whether the scaffolding succeeded */
|
|
23
|
+
success: boolean;
|
|
24
|
+
/** Absolute resolved path of the target directory */
|
|
25
|
+
targetDir: string;
|
|
26
|
+
/** Whether git init was successfully executed */
|
|
27
|
+
gitInitialized: boolean;
|
|
28
|
+
/** Whether execution ran in dry-run simulation mode */
|
|
29
|
+
dryRun: boolean;
|
|
30
|
+
/** List of files, directories, and symlinks created or simulated */
|
|
31
|
+
actions: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface LogChangeOptions {
|
|
35
|
+
/** Short title describing the architectural change */
|
|
36
|
+
title: string;
|
|
37
|
+
/** Category of change (Architecture | Rule | Skill | Infrastructure | CLI | Knowledge Hub) */
|
|
38
|
+
category?: string;
|
|
39
|
+
/** Target file(s) affected by the change */
|
|
40
|
+
targetFiles?: string;
|
|
41
|
+
/** Architectural rationale for upstream template incorporation */
|
|
42
|
+
rationale?: string;
|
|
43
|
+
/** Detailed description of the change */
|
|
44
|
+
description?: string;
|
|
45
|
+
/** Working directory containing changes.md (default: process.cwd()) */
|
|
46
|
+
targetDir?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface LogChangeResult {
|
|
50
|
+
/** Whether the log entry was successfully recorded */
|
|
51
|
+
success: boolean;
|
|
52
|
+
/** Absolute path to changes.md */
|
|
53
|
+
filePath: string;
|
|
54
|
+
/** Markdown entry text that was appended */
|
|
55
|
+
entry: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ValidateTargetOptions {
|
|
59
|
+
/** Custom template root directory */
|
|
60
|
+
templateDir?: string;
|
|
61
|
+
/** Force allow non-empty directory */
|
|
62
|
+
force?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface InitGitOptions {
|
|
66
|
+
/** If true, skips git initialization */
|
|
67
|
+
noGit?: boolean;
|
|
68
|
+
/** If true, simulates git initialization without executing */
|
|
69
|
+
dryRun?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface CopyTemplateOptions {
|
|
73
|
+
/** If true, simulates copy without writing to disk */
|
|
74
|
+
dryRun?: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* High-level orchestration function to scaffold the azcodr workspace into targetDir.
|
|
79
|
+
*/
|
|
80
|
+
export function scaffold(options?: ScaffoldOptions): ScaffoldResult;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Appends a standardized upstream change entry to changes.md.
|
|
84
|
+
*/
|
|
85
|
+
export function logChange(options: LogChangeOptions): LogChangeResult;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Validates the target directory to ensure it is suitable for scaffolding.
|
|
89
|
+
*/
|
|
90
|
+
export function validateTarget(targetDir: string, options?: ValidateTargetOptions): void;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Copies template items into target directory and sets up symlinks and permissions.
|
|
94
|
+
*/
|
|
95
|
+
export function copyTemplate(
|
|
96
|
+
targetDir: string,
|
|
97
|
+
templateDir?: string,
|
|
98
|
+
options?: CopyTemplateOptions
|
|
99
|
+
): string[];
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Safely creates or updates a symbolic link, falling back to a file copy if symlinks are unsupported.
|
|
103
|
+
*/
|
|
104
|
+
export function ensureSymlink(
|
|
105
|
+
targetDir: string,
|
|
106
|
+
linkName: string,
|
|
107
|
+
targetFileName: string,
|
|
108
|
+
dryRun?: boolean
|
|
109
|
+
): boolean;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Detects whether linkName and targetFileName refer to the same entry on a case-insensitive filesystem.
|
|
113
|
+
*/
|
|
114
|
+
export function isSameCaseInsensitiveFile(
|
|
115
|
+
targetDir: string,
|
|
116
|
+
linkName: string,
|
|
117
|
+
targetFileName: string
|
|
118
|
+
): boolean;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Ensures all bash scripts in skill directories have executable permissions (0o755).
|
|
122
|
+
*/
|
|
123
|
+
export function makeScriptsExecutable(targetDir: string, dryRun?: boolean): string[];
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Initializes a git repository in the target directory if not already inside one.
|
|
127
|
+
*/
|
|
128
|
+
export function initGit(targetDir: string, options?: InitGitOptions): boolean;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Returns the root path to the azcodr template files.
|
|
132
|
+
*/
|
|
133
|
+
export function getTemplateDir(): string;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Array of essential template files and directories copied during scaffolding.
|
|
137
|
+
*/
|
|
138
|
+
export const TEMPLATE_ITEMS: readonly string[];
|
|
139
|
+
|
|
140
|
+
declare const defaultExport: {
|
|
141
|
+
scaffold: typeof scaffold;
|
|
142
|
+
logChange: typeof logChange;
|
|
143
|
+
validateTarget: typeof validateTarget;
|
|
144
|
+
copyTemplate: typeof copyTemplate;
|
|
145
|
+
ensureSymlink: typeof ensureSymlink;
|
|
146
|
+
isSameCaseInsensitiveFile: typeof isSameCaseInsensitiveFile;
|
|
147
|
+
makeScriptsExecutable: typeof makeScriptsExecutable;
|
|
148
|
+
initGit: typeof initGit;
|
|
149
|
+
getTemplateDir: typeof getTemplateDir;
|
|
150
|
+
TEMPLATE_ITEMS: typeof TEMPLATE_ITEMS;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export default defaultExport;
|
package/lib/scaffold.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const path = require('node:path');
|
|
5
|
-
const
|
|
5
|
+
const cp = require('node:child_process');
|
|
6
6
|
|
|
7
7
|
const TEMPLATE_ITEMS = [
|
|
8
8
|
'AGENTS.md',
|
|
@@ -11,7 +11,8 @@ const TEMPLATE_ITEMS = [
|
|
|
11
11
|
'README.md',
|
|
12
12
|
'docs',
|
|
13
13
|
'.agents',
|
|
14
|
-
'.gitignore'
|
|
14
|
+
'.gitignore',
|
|
15
|
+
'.editorconfig'
|
|
15
16
|
];
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -25,7 +26,7 @@ function getTemplateDir() {
|
|
|
25
26
|
* Validates the target directory to ensure it is suitable for scaffolding.
|
|
26
27
|
*/
|
|
27
28
|
function validateTarget(targetDir, options = {}) {
|
|
28
|
-
const { templateDir = getTemplateDir(), force = false } = options;
|
|
29
|
+
const { templateDir = getTemplateDir(), force = false, dryRun = false } = options;
|
|
29
30
|
const resolvedTarget = path.resolve(targetDir);
|
|
30
31
|
const resolvedTemplate = path.resolve(templateDir);
|
|
31
32
|
|
|
@@ -34,7 +35,9 @@ function validateTarget(targetDir, options = {}) {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
if (!fs.existsSync(resolvedTarget)) {
|
|
37
|
-
|
|
38
|
+
if (!dryRun) {
|
|
39
|
+
fs.mkdirSync(resolvedTarget, { recursive: true });
|
|
40
|
+
}
|
|
38
41
|
return;
|
|
39
42
|
}
|
|
40
43
|
|
|
@@ -47,10 +50,35 @@ function validateTarget(targetDir, options = {}) {
|
|
|
47
50
|
}
|
|
48
51
|
}
|
|
49
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Detects whether linkName and targetFileName refer to the same entry on a case-insensitive filesystem.
|
|
55
|
+
*/
|
|
56
|
+
function isSameCaseInsensitiveFile(targetDir, linkName, targetFileName) {
|
|
57
|
+
if (linkName.toLowerCase() !== targetFileName.toLowerCase()) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const targetPath = path.join(targetDir, targetFileName);
|
|
61
|
+
const linkPath = path.join(targetDir, linkName);
|
|
62
|
+
try {
|
|
63
|
+
if (fs.existsSync(targetPath) && fs.existsSync(linkPath)) {
|
|
64
|
+
return !fs.lstatSync(linkPath).isSymbolicLink();
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
50
72
|
/**
|
|
51
73
|
* Safely creates or updates a symbolic link, falling back to a file copy if symlinks are unsupported.
|
|
52
74
|
*/
|
|
53
|
-
function ensureSymlink(targetDir, linkName, targetFileName) {
|
|
75
|
+
function ensureSymlink(targetDir, linkName, targetFileName, dryRun = false) {
|
|
76
|
+
if (dryRun) return true;
|
|
77
|
+
|
|
78
|
+
if (isSameCaseInsensitiveFile(targetDir, linkName, targetFileName)) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
54
82
|
const linkPath = path.join(targetDir, linkName);
|
|
55
83
|
try {
|
|
56
84
|
const stat = fs.lstatSync(linkPath);
|
|
@@ -63,21 +91,24 @@ function ensureSymlink(targetDir, linkName, targetFileName) {
|
|
|
63
91
|
|
|
64
92
|
try {
|
|
65
93
|
fs.symlinkSync(targetFileName, linkPath);
|
|
94
|
+
return true;
|
|
66
95
|
} catch {
|
|
67
96
|
// Fallback if environment (e.g., certain Windows configs) prevents symlink creation
|
|
68
97
|
const sourceFile = path.join(targetDir, targetFileName);
|
|
69
98
|
if (fs.existsSync(sourceFile)) {
|
|
70
99
|
fs.copyFileSync(sourceFile, linkPath);
|
|
71
100
|
}
|
|
101
|
+
return true;
|
|
72
102
|
}
|
|
73
103
|
}
|
|
74
104
|
|
|
75
105
|
/**
|
|
76
106
|
* Ensures all bash scripts in skill directories have executable permissions (0o755).
|
|
77
107
|
*/
|
|
78
|
-
function makeScriptsExecutable(targetDir) {
|
|
108
|
+
function makeScriptsExecutable(targetDir, dryRun = false) {
|
|
109
|
+
const modified = [];
|
|
79
110
|
const skillsDir = path.join(targetDir, '.agents', 'skills');
|
|
80
|
-
if (!fs.existsSync(skillsDir)) return;
|
|
111
|
+
if (!fs.existsSync(skillsDir)) return modified;
|
|
81
112
|
|
|
82
113
|
const skills = fs.readdirSync(skillsDir);
|
|
83
114
|
for (const skill of skills) {
|
|
@@ -87,25 +118,31 @@ function makeScriptsExecutable(targetDir) {
|
|
|
87
118
|
for (const file of files) {
|
|
88
119
|
if (file.endsWith('.sh')) {
|
|
89
120
|
const filePath = path.join(scriptsDir, file);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
121
|
+
modified.push(filePath);
|
|
122
|
+
if (!dryRun) {
|
|
123
|
+
try {
|
|
124
|
+
fs.chmodSync(filePath, 0o755);
|
|
125
|
+
} catch {
|
|
126
|
+
// Non-critical if filesystem does not support POSIX permissions
|
|
127
|
+
}
|
|
94
128
|
}
|
|
95
129
|
}
|
|
96
130
|
}
|
|
97
131
|
}
|
|
98
132
|
}
|
|
133
|
+
return modified;
|
|
99
134
|
}
|
|
100
135
|
|
|
101
136
|
/**
|
|
102
137
|
* Recursively copies template files into the target directory and sets up symlinks and permissions.
|
|
103
138
|
*/
|
|
104
|
-
function copyTemplate(targetDir, templateDir = getTemplateDir()) {
|
|
139
|
+
function copyTemplate(targetDir, templateDir = getTemplateDir(), options = {}) {
|
|
140
|
+
const { dryRun = false } = options;
|
|
105
141
|
const resolvedTarget = path.resolve(targetDir);
|
|
106
142
|
const resolvedTemplate = path.resolve(templateDir);
|
|
143
|
+
const actions = [];
|
|
107
144
|
|
|
108
|
-
if (!fs.existsSync(resolvedTarget)) {
|
|
145
|
+
if (!dryRun && !fs.existsSync(resolvedTarget)) {
|
|
109
146
|
fs.mkdirSync(resolvedTarget, { recursive: true });
|
|
110
147
|
}
|
|
111
148
|
|
|
@@ -114,29 +151,46 @@ function copyTemplate(targetDir, templateDir = getTemplateDir()) {
|
|
|
114
151
|
if (!fs.existsSync(srcPath)) continue;
|
|
115
152
|
|
|
116
153
|
const destPath = path.join(resolvedTarget, item);
|
|
117
|
-
|
|
154
|
+
actions.push(`copy: ${item} -> ${destPath}`);
|
|
155
|
+
|
|
156
|
+
if (!dryRun) {
|
|
157
|
+
fs.cpSync(srcPath, destPath, { recursive: true, force: true, dereference: false });
|
|
158
|
+
}
|
|
118
159
|
}
|
|
119
160
|
|
|
120
161
|
// Ensure harness parity symlinks per AGENTS.md mandate
|
|
121
|
-
|
|
122
|
-
ensureSymlink(resolvedTarget, '
|
|
162
|
+
actions.push(`symlink: CLAUDE.md -> AGENTS.md`);
|
|
163
|
+
ensureSymlink(resolvedTarget, 'CLAUDE.md', 'AGENTS.md', dryRun);
|
|
164
|
+
|
|
165
|
+
actions.push(`symlink: agents.md -> AGENTS.md`);
|
|
166
|
+
ensureSymlink(resolvedTarget, 'agents.md', 'AGENTS.md', dryRun);
|
|
123
167
|
|
|
124
168
|
// Ensure scripts are executable
|
|
125
|
-
|
|
169
|
+
const inspectDir = dryRun ? resolvedTemplate : resolvedTarget;
|
|
170
|
+
const scripts = makeScriptsExecutable(inspectDir, dryRun);
|
|
171
|
+
for (const script of scripts) {
|
|
172
|
+
actions.push(`chmod: +x ${path.relative(inspectDir, script)}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return actions;
|
|
126
176
|
}
|
|
127
177
|
|
|
128
178
|
/**
|
|
129
179
|
* Initializes a git repository in the target directory if not already inside one.
|
|
130
180
|
*/
|
|
131
181
|
function initGit(targetDir, options = {}) {
|
|
132
|
-
const { noGit = false } = options;
|
|
182
|
+
const { noGit = false, dryRun = false } = options;
|
|
133
183
|
if (noGit) return false;
|
|
134
184
|
|
|
135
185
|
const gitDir = path.join(targetDir, '.git');
|
|
136
186
|
if (fs.existsSync(gitDir)) return false;
|
|
137
187
|
|
|
188
|
+
if (dryRun) {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
138
192
|
try {
|
|
139
|
-
execSync('git init -q', { cwd: targetDir, stdio: 'ignore' });
|
|
193
|
+
cp.execSync('git init -q', { cwd: targetDir, stdio: 'ignore' });
|
|
140
194
|
return true;
|
|
141
195
|
} catch {
|
|
142
196
|
return false;
|
|
@@ -151,18 +205,25 @@ function scaffold(options = {}) {
|
|
|
151
205
|
targetDir = process.cwd(),
|
|
152
206
|
force = false,
|
|
153
207
|
noGit = false,
|
|
154
|
-
templateDir = getTemplateDir()
|
|
208
|
+
templateDir = getTemplateDir(),
|
|
209
|
+
dryRun = false,
|
|
210
|
+
silent = false
|
|
155
211
|
} = options;
|
|
156
212
|
|
|
157
213
|
const resolvedTarget = path.resolve(targetDir);
|
|
158
|
-
validateTarget(resolvedTarget, { templateDir, force });
|
|
159
|
-
copyTemplate(resolvedTarget, templateDir);
|
|
160
|
-
const gitInitialized = initGit(resolvedTarget, { noGit });
|
|
214
|
+
validateTarget(resolvedTarget, { templateDir, force, dryRun });
|
|
215
|
+
const actions = copyTemplate(resolvedTarget, templateDir, { dryRun });
|
|
216
|
+
const gitInitialized = initGit(resolvedTarget, { noGit, dryRun });
|
|
217
|
+
if (gitInitialized) {
|
|
218
|
+
actions.push('git: initialize repository');
|
|
219
|
+
}
|
|
161
220
|
|
|
162
221
|
return {
|
|
163
222
|
success: true,
|
|
164
223
|
targetDir: resolvedTarget,
|
|
165
|
-
gitInitialized
|
|
224
|
+
gitInitialized,
|
|
225
|
+
dryRun,
|
|
226
|
+
actions
|
|
166
227
|
};
|
|
167
228
|
}
|
|
168
229
|
|
|
@@ -216,6 +277,7 @@ module.exports = {
|
|
|
216
277
|
validateTarget,
|
|
217
278
|
copyTemplate,
|
|
218
279
|
ensureSymlink,
|
|
280
|
+
isSameCaseInsensitiveFile,
|
|
219
281
|
makeScriptsExecutable,
|
|
220
282
|
initGit,
|
|
221
283
|
getTemplateDir,
|