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.
Files changed (35) hide show
  1. package/.agents/skills/agentic-architect/scripts/validate_agentic_configs.sh +24 -8
  2. package/.agents/skills/lets-build/SKILL.md +71 -78
  3. package/.agents/skills/lets-build/references/architecture_interview_matrix.md +78 -157
  4. package/.agents/skills/lets-build/scripts/bootstrap_workspace.sh +75 -35
  5. package/.editorconfig +19 -0
  6. package/.gitignore +3 -0
  7. package/AGENTS.md +12 -10
  8. package/README.md +3 -8
  9. package/bin/azcodr.js +170 -97
  10. package/changes.md +26 -0
  11. package/docs/rules/api_versioning.md +0 -15
  12. package/docs/rules/clean_code.md +37 -0
  13. package/docs/rules/continuous_learning.md +14 -13
  14. package/docs/rules/database_integrity.md +9 -17
  15. package/docs/rules/design_patterns.md +1 -0
  16. package/docs/rules/domain_driven_design.md +31 -21
  17. package/docs/rules/error_handling.md +14 -1
  18. package/docs/rules/multitenancy_isolation.md +2 -0
  19. package/docs/rules/product_ownership.md +0 -18
  20. package/docs/rules/project_management.md +0 -17
  21. package/docs/rules/react.md +4 -14
  22. package/docs/rules/requirements_engineering.md +0 -17
  23. package/docs/rules/rest_api_conventions.md +0 -16
  24. package/docs/rules/test_driven_development.md +42 -20
  25. package/docs/rules/transactional_email.md +11 -4
  26. package/docs/rules/ui_ux_architecture.md +5 -26
  27. package/docs/rules/upstream_synchronization.md +0 -15
  28. package/docs/rules/workflow_state_machines.md +0 -18
  29. package/lib/index.d.ts +153 -0
  30. package/lib/scaffold.js +86 -24
  31. package/memory.md +85 -219
  32. package/package.json +14 -4
  33. package/docs/knowledge/dos_and_donts.md +0 -540
  34. package/docs/knowledge/issue_log.md +0 -25
  35. 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 { execSync } = require('node:child_process');
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
- fs.mkdirSync(resolvedTarget, { recursive: true });
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
- try {
91
- fs.chmodSync(filePath, 0o755);
92
- } catch {
93
- // Non-critical if filesystem does not support POSIX permissions
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
- fs.cpSync(srcPath, destPath, { recursive: true, force: true, dereference: false });
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
- ensureSymlink(resolvedTarget, 'CLAUDE.md', 'AGENTS.md');
122
- ensureSymlink(resolvedTarget, 'agents.md', 'AGENTS.md');
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
- makeScriptsExecutable(resolvedTarget);
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,