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
@@ -1,52 +1,84 @@
1
1
  #!/usr/bin/env bash
2
2
  # ==============================================================================
3
3
  # bootstrap_workspace.sh
4
- # Deterministic Scaffolder for Hexagonal Multi-Tenant Workspaces
4
+ # Topology-Aware Deterministic Scaffolder (Strict YAGNI, Zero Speculative Bloat)
5
5
  # ==============================================================================
6
6
 
7
7
  set -euo pipefail
8
8
 
9
9
  WORKSPACE_ROOT="${1:-$(pwd)}"
10
- LANGUAGE="${2:-generic}"
10
+ TOPOLOGY="${2:-backend}"
11
+ LANGUAGE="${3:-generic}"
11
12
 
12
- echo "šŸš€ Initializing Hexagonal Architecture Workspace in: ${WORKSPACE_ROOT}"
13
+ echo "šŸš€ Initializing Topology-Aware Workspace in: ${WORKSPACE_ROOT}"
14
+ echo "šŸ—ļø Target Topology: ${TOPOLOGY}"
13
15
  echo "šŸ“¦ Target Language Profile: ${LANGUAGE}"
14
16
  echo "--------------------------------------------------------------"
15
17
 
16
- # 1. Create Universal Specification Directories
17
- echo "1. Scaffolding Contract Specification Directories (specs/)..."
18
- mkdir -p "${WORKSPACE_ROOT}/specs/protobuf"
19
- mkdir -p "${WORKSPACE_ROOT}/specs/openapi"
20
- mkdir -p "${WORKSPACE_ROOT}/specs/schemas"
21
- mkdir -p "${WORKSPACE_ROOT}/specs/tokens"
18
+ case "${TOPOLOGY}" in
19
+ extension)
20
+ echo "1. Scaffolding Browser Extension Source Tree (src/)..."
21
+ mkdir -p "${WORKSPACE_ROOT}/src/background"
22
+ mkdir -p "${WORKSPACE_ROOT}/src/content"
23
+ mkdir -p "${WORKSPACE_ROOT}/src/popup"
24
+ mkdir -p "${WORKSPACE_ROOT}/src/shared"
25
+ mkdir -p "${WORKSPACE_ROOT}/public"
26
+
27
+ echo "2. Scaffolding Extension Test Suites (tests/)..."
28
+ mkdir -p "${WORKSPACE_ROOT}/tests/unit"
29
+ mkdir -p "${WORKSPACE_ROOT}/tests/e2e"
30
+ ;;
22
31
 
23
- # 2. Create Universal Hexagonal Source Directories
24
- echo "2. Scaffolding Hexagonal Source Tree (src/)..."
25
- mkdir -p "${WORKSPACE_ROOT}/src/domain/entities"
26
- mkdir -p "${WORKSPACE_ROOT}/src/domain/value_objects"
27
- mkdir -p "${WORKSPACE_ROOT}/src/domain/services"
28
- mkdir -p "${WORKSPACE_ROOT}/src/ports/primary"
29
- mkdir -p "${WORKSPACE_ROOT}/src/ports/secondary"
30
- mkdir -p "${WORKSPACE_ROOT}/src/adapters/primary"
31
- mkdir -p "${WORKSPACE_ROOT}/src/adapters/secondary"
32
+ game|engine)
33
+ echo "1. Scaffolding Game/Engine Source Tree (src/)..."
34
+ mkdir -p "${WORKSPACE_ROOT}/src/core"
35
+ mkdir -p "${WORKSPACE_ROOT}/src/ecs"
36
+ mkdir -p "${WORKSPACE_ROOT}/src/renderer"
37
+ mkdir -p "${WORKSPACE_ROOT}/src/assets"
38
+
39
+ echo "2. Scaffolding Game/Engine Test Suites (tests/)..."
40
+ mkdir -p "${WORKSPACE_ROOT}/tests/unit"
41
+ mkdir -p "${WORKSPACE_ROOT}/tests/benchmarks"
42
+ ;;
32
43
 
33
- # 3. Create Universal Test Directories
34
- echo "3. Scaffolding Test Suites (tests/)..."
35
- mkdir -p "${WORKSPACE_ROOT}/tests/unit"
36
- mkdir -p "${WORKSPACE_ROOT}/tests/integration"
37
- mkdir -p "${WORKSPACE_ROOT}/tests/contracts"
38
- mkdir -p "${WORKSPACE_ROOT}/tests/acceptance"
44
+ cli)
45
+ echo "1. Scaffolding CLI Source Tree (src/)..."
46
+ mkdir -p "${WORKSPACE_ROOT}/src/cmd"
47
+ mkdir -p "${WORKSPACE_ROOT}/src/core"
48
+ mkdir -p "${WORKSPACE_ROOT}/src/io"
49
+
50
+ echo "2. Scaffolding CLI Test Suites (tests/)..."
51
+ mkdir -p "${WORKSPACE_ROOT}/tests/unit"
52
+ mkdir -p "${WORKSPACE_ROOT}/tests/integration"
53
+ ;;
39
54
 
40
- # 4. Create Deployment & Infrastructure Directories
41
- echo "4. Scaffolding Deployment Infrastructure (deploy/)..."
42
- mkdir -p "${WORKSPACE_ROOT}/deploy/docker"
43
- mkdir -p "${WORKSPACE_ROOT}/deploy/compose"
44
- mkdir -p "${WORKSPACE_ROOT}/deploy/k8s"
55
+ backend|web|saas)
56
+ echo "1. Scaffolding Backend / Enterprise Source Tree (src/)..."
57
+ mkdir -p "${WORKSPACE_ROOT}/src/domain/entities"
58
+ mkdir -p "${WORKSPACE_ROOT}/src/domain/value_objects"
59
+ mkdir -p "${WORKSPACE_ROOT}/src/domain/services"
60
+ mkdir -p "${WORKSPACE_ROOT}/src/ports/primary"
61
+ mkdir -p "${WORKSPACE_ROOT}/src/ports/secondary"
62
+ mkdir -p "${WORKSPACE_ROOT}/src/adapters/primary"
63
+ mkdir -p "${WORKSPACE_ROOT}/src/adapters/secondary"
64
+
65
+ echo "2. Scaffolding Specifications (specs/)..."
66
+ mkdir -p "${WORKSPACE_ROOT}/specs/openapi"
67
+ mkdir -p "${WORKSPACE_ROOT}/specs/tokens"
68
+
69
+ echo "3. Scaffolding Backend Test Suites (tests/)..."
70
+ mkdir -p "${WORKSPACE_ROOT}/tests/unit"
71
+ mkdir -p "${WORKSPACE_ROOT}/tests/integration"
72
+ mkdir -p "${WORKSPACE_ROOT}/tests/contracts"
73
+ mkdir -p "${WORKSPACE_ROOT}/tests/acceptance"
74
+
75
+ echo "4. Scaffolding Deployment Infrastructure (deploy/)..."
76
+ mkdir -p "${WORKSPACE_ROOT}/deploy/docker"
77
+ mkdir -p "${WORKSPACE_ROOT}/deploy/compose"
45
78
 
46
- # 5. Create Default Design Tokens Spec
47
- TOKEN_SPEC="${WORKSPACE_ROOT}/specs/tokens/tokens.json"
48
- if [[ ! -f "${TOKEN_SPEC}" ]]; then
49
- cat << 'EOF' > "${TOKEN_SPEC}"
79
+ TOKEN_SPEC="${WORKSPACE_ROOT}/specs/tokens/tokens.json"
80
+ if [[ ! -f "${TOKEN_SPEC}" ]]; then
81
+ cat << 'EOF' > "${TOKEN_SPEC}"
50
82
  {
51
83
  "color": {
52
84
  "brand": {
@@ -62,7 +94,15 @@ if [[ ! -f "${TOKEN_SPEC}" ]]; then
62
94
  }
63
95
  }
64
96
  EOF
65
- fi
97
+ fi
98
+ ;;
99
+
100
+ *)
101
+ echo "1. Scaffolding Generic / Library Source Tree (src/)..."
102
+ mkdir -p "${WORKSPACE_ROOT}/src"
103
+ mkdir -p "${WORKSPACE_ROOT}/tests/unit"
104
+ ;;
105
+ esac
66
106
 
67
107
  echo "--------------------------------------------------------------"
68
- echo "āœ… Hexagonal directory tree and contract specifications scaffolded successfully!"
108
+ echo "āœ… Topology '${TOPOLOGY}' scaffolded with strict YAGNI (0 speculative folders)!"
package/.editorconfig ADDED
@@ -0,0 +1,19 @@
1
+ # http://editorconfig.org
2
+ root = true
3
+
4
+ [*]
5
+ indent_style = space
6
+ indent_size = 2
7
+ end_of_line = lf
8
+ charset = utf-8
9
+ trim_trailing_whitespace = true
10
+ insert_final_newline = true
11
+
12
+ [*.md]
13
+ trim_trailing_whitespace = false
14
+
15
+ [Makefile]
16
+ indent_style = tab
17
+
18
+ [*.go]
19
+ indent_style = tab
package/.gitignore CHANGED
@@ -18,3 +18,6 @@ Thumbs.db
18
18
  .env
19
19
  .env.local
20
20
  .env.*.local
21
+
22
+ # Case-insensitive filesystem parity (agents.md is generated/symlinked on Linux, native on macOS/Windows)
23
+ agents.md
package/AGENTS.md CHANGED
@@ -4,7 +4,7 @@
4
4
  > **Rule Zero:** Assume nothing. Every action must be grounded in verified evidence from this workspace or direct instructions from the user.
5
5
  > **Open-Source Mandate:** Always utilize 100% open-source tools, frameworks, libraries, and packages across all architectural domains.
6
6
  > **Atomicity Mandate:** All rules, skills, code units, migrations, and transactions must be strictly atomic (indivisible, self-contained, and composable with full ACID safety).
7
- > **Agnostic Mandate:** Decouple domain core from transient technologies, languages, and stacks (Hexagonal Ports & Adapters; zero language bias).
7
+ > **Architecture Mandate:** Architecture emerges strictly from problem constraints and execution targets (Problem-First; zero tool/platform bias). Match architectural style to problem topology (Hexagonal for enterprise backends, Platform Scripting for extensions, Data-Oriented Design for game engines, Command Pipeline for CLIs, Game Loop for canvas games). Never force premature abstractions or universal templates.
8
8
 
9
9
  ---
10
10
 
@@ -14,10 +14,12 @@
14
14
  2. **Ground Truth Only:** A statement is only true if proven by a workspace file, verified command output, or direct user instruction.
15
15
  3. **Unknown Until Verified:** If something is not explicitly written in the workspace or stated by the user, treat it as unknown.
16
16
  4. **Strict Open Standards:** Standardize on open-source solutions and open specs (Semgrep, Trivy, Gitleaks, OpenTelemetry, OPA, OCI, Wasm, CloudEvents).
17
- 5. **Universal Agnosticism:** Core business rules are technology-, language-, and stack-agnostic; runtimes connect via swappable adapters with zero language bias.
18
- 6. **Systemic Atomicity:** Every skill, rule, database transaction, and refactoring step must be atomic (Single Responsibility, zero side-effects, full rollback).
19
- 7. **Workspace Sovereignty:** Total containment within the local workspace root (`./`). Zero interference from global configs, tools, or sibling projects.
20
- 8. **Continuous Learning:** Log all defects, DO's/DONT's, and lessons into `docs/knowledge/` and `memory.md`, dynamically updating atomic rules.
17
+ 5. **Problem-First & Topology Alignment:** Problem domain and operational constraints (latency budget, GC tolerance, memory, execution environment) strictly dictate the architectural style and toolchain. Never select tools before defining the problem space.
18
+ 6. **Evolutionary Architecture & Refactor-Before-Add:** As complexity grows, code must graduate across explicit architectural tipping points. Refactor structure first under existing green tests before implementing new features. Never append code into rotting files.
19
+ 7. **True Incremental TDD & Nano-Cycles:** Never dump test suites in batches ("Test-First Waterfall"). Follow Uncle Bob's Three Laws: write one micro-assertion at a time, verify RED failure output, write minimal code to turn GREEN, and refactor under green.
20
+ 8. **Systemic Atomicity:** Every skill, rule, database transaction, and refactoring step must be atomic (Single Responsibility, zero side-effects, full rollback).
21
+ 9. **Workspace Sovereignty:** Total containment within the local workspace root (`./`). Zero interference from global configs, tools, or sibling projects.
22
+ 10. **Continuous Learning:** Ingest all verified defects, lessons, and architectural invariants directly into domain rules and `memory.md`.
21
23
 
22
24
  ### The 5 Core Branch Questions
23
25
  Before acting on any decision branch, answer:
@@ -40,10 +42,10 @@ Before acting on any decision branch, answer:
40
42
 
41
43
  Progress all tasks systematically through the unified **Agent Cognitive & Agile Domain Lifecycle**, seamlessly interlocking the 5 agent operational disciplines with the 5-phase domain engineering pipeline:
42
44
  ```
43
- 1. DISCOVER / REQUIREMENTS ──► Read-only inspection; INVEST user stories & executable Gherkin scenarios.
44
- 2. INTERROGATE / DOMAIN ──► Relentless questioning; Ubiquitous Language & domain invariants.
45
+ 1. DISCOVER / REQUIREMENTS ──► Read-only inspection; Problem Space & operational constraints; INVEST stories & Gherkin.
46
+ 2. INTERROGATE / DOMAIN ──► Relentless questioning; Ubiquitous Language, Aggregate invariants & state machines.
45
47
  3. PLAN / OUTER TDD ──► Minimal blast radius; failing Outer Acceptance Test (UI/API RED).
46
- 4. EXECUTE / INNER TDD ──► Surgical edits; Inner TDD collaborator discovery (RED-GREEN-REFACTOR).
48
+ 4. EXECUTE / INNER TDD ──► Incremental nano-cycles (Uncle Bob's 3 Laws: 1 micro-assertion RED āž” MINIMAL pass GREEN āž” REFACTOR).
47
49
  5. VERIFY / DoD & PROOF ──► Outer test turns GREEN; boundary smoke tests & 100.00% test coverage.
48
50
  ```
49
51
  ---
@@ -99,7 +101,7 @@ To prevent context bloat and keep prompt overhead minimal, detailed engineering
99
101
  | **Domain Modeling** | [docs/rules/domain_expertise.md](./docs/rules/domain_expertise.md) | Business capabilities, Aggregate Root invariants, Ubiquitous Language. |
100
102
  | **Relentless Questioning** | [docs/rules/relentless_questioning.md](./docs/rules/relentless_questioning.md) | Dynamic context-aware interrogation loops, adaptive decision trees. |
101
103
  | **Workspace Isolation** | [docs/rules/workspace_isolation.md](./docs/rules/workspace_isolation.md) | Strict workspace sovereignty, zero global contamination, local ground truth. |
102
- | **Continuous Learning** | [docs/rules/continuous_learning.md](./docs/rules/continuous_learning.md) | Automated defect post-mortems, DO's/DONT's logging, dynamic rule updates. |
104
+ | **Continuous Learning** | [docs/rules/continuous_learning.md](./docs/rules/continuous_learning.md) | Direct rule ingestion, root-cause analysis, dynamic invariant updates. |
103
105
  | **Upstream Sync** | [docs/rules/upstream_synchronization.md](./docs/rules/upstream_synchronization.md) | Logging generic architecture improvements to changes.md; zero baseline pollution. |
104
106
  ---
105
107
 
@@ -114,5 +116,5 @@ To prevent context bloat and keep prompt overhead minimal, detailed engineering
114
116
  - [`lets-build`](.agents/skills/lets-build/SKILL.md): Conducting architecture interviews to finalize stack, frameworks, package managers, and bootstrapping projects.
115
117
  - [`relentless-questioner`](.agents/skills/relentless-questioner/SKILL.md): Dynamic context-aware interrogation loops before planning and coding.
116
118
  - **Relentless Skill Architecture Inquiry:** Never author or update skills on assumptions. Interrogate all 7 inquiry branches (placement, trigger intent, domain truth, gotchas/anti-patterns, determinism, progressive bloat, verification loop) defined in [docs/rules/agentic_configuration.md](./docs/rules/agentic_configuration.md) before writing `SKILL.md`.
117
- - **Workspace Memory & Knowledge Hub:** Consult [`memory.md`](./memory.md) for ADRs, and [`docs/knowledge/`](./docs/knowledge/knowledge_graph.md) for system topologies, issue logs, and DO's/DONT's.
119
+ - **Workspace Memory & Knowledge Hub:** Consult [`memory.md`](./memory.md) for ADRs, and [`docs/knowledge/`](./docs/knowledge/knowledge_graph.md) for system topologies and domain glossaries.
118
120
  - **Harness Parity & Symlinks:** `AGENTS.md`, `CLAUDE.md`, and `agents.md` must remain identical via filesystem symbolic links to eliminate configuration divergence across different agent harnesses.
package/README.md CHANGED
@@ -34,11 +34,8 @@
34
34
  │ ā”œā”€ā”€ product-analyst/ # INVEST user stories & Gherkin criteria
35
35
  │ └── relentless-questioner/ # Context-aware dynamic interrogation loop
36
36
  ā”œā”€ā”€ docs/
37
- │ ā”œā”€ā”€ knowledge/ # Institutional knowledge & token economy
38
- │ │ ā”œā”€ā”€ dos_and_donts.md # Consolidated DO's and DONT's directory
39
- │ │ ā”œā”€ā”€ issue_log.md # Defect post-mortems & preventing rules
37
+ │ ā”œā”€ā”€ knowledge/ # Institutional knowledge & domain contracts
40
38
  │ │ ā”œā”€ā”€ knowledge_graph.md # Visual topologies & fast-lookup matrices
41
- │ │ ā”œā”€ā”€ lessons_learned.md # Strategic architectural takeaways
42
39
  │ │ └── ubiquitous_language.md # Living Ubiquitous Language glossary template
43
40
  │ └── rules/ # 47 atomic single-responsibility domain rules
44
41
  ā”œā”€ā”€ AGENTS.md # Lean root agentic configuration (< 120 lines)
@@ -177,8 +174,6 @@ Once confirmed, the agent automatically executes:
177
174
  ## šŸ›ļø Workspace Memory & Knowledge Hub
178
175
 
179
176
  - šŸ—ŗļø **[System Knowledge Graph](./docs/knowledge/knowledge_graph.md)**: Visual subsystem topologies and entity-relationship models.
180
- - šŸ“‹ **[Consolidated DO's & DONT's](./docs/knowledge/dos_and_donts.md)**: High-impact engineering invariants and anti-patterns to avoid.
181
- - šŸ› **[Coding Issue Log](./docs/knowledge/issue_log.md)**: Defect post-mortems and preventative rules.
182
- - šŸ’” **[Institutional Lessons Learned](./docs/knowledge/lessons_learned.md)**: Strategic engineering insights.
183
- - šŸ“œ **[Lightweight ADR Ledger](./memory.md)**: Formal Architectural Decision Records.
177
+ - šŸ“– **[Living Ubiquitous Language Glossary](./docs/knowledge/ubiquitous_language.md)**: Authoritative domain vocabulary contract.
178
+ - šŸ“œ **[Lightweight ADR Ledger](./memory.md)**: Formal Architectural Decision Records and governing rules.
184
179
  - šŸ“ **[Upstream Changes Ledger](./changes.md)**: Record candidate improvements and generic patterns for the upstream azcodr template.
package/bin/azcodr.js CHANGED
@@ -3,13 +3,12 @@
3
3
 
4
4
  const path = require('node:path');
5
5
  const readline = require('node:readline');
6
+ const fs = require('node:fs');
6
7
  const { scaffold, logChange, getTemplateDir } = require('../lib/scaffold.js');
7
8
  const pkg = require('../package.json');
8
9
 
9
- const args = process.argv.slice(2);
10
-
11
- function printHelp() {
12
- console.log(`
10
+ function printHelp(out = console.log) {
11
+ out(`
13
12
  azcodr v${pkg.version}
14
13
  Enterprise Multi-Tenant Architecture & Agentic Engineering Starter Template
15
14
 
@@ -22,208 +21,282 @@ Commands:
22
21
  change <title> Log a generic architectural change to changes.md
23
22
 
24
23
  Scaffold Options:
24
+ -d, --dry-run Simulate scaffolding without modifying filesystem
25
+ -s, --silent Suppress console output messages
25
26
  -f, --force Overwrite existing files in target directory without confirmation
26
27
  --no-git Do not initialize a git repository
27
28
  -v, --version Display version number
28
29
  -h, --help Display this help message
29
30
 
30
31
  Change Options:
31
- -c, --category Category (Rule | Skill | Infrastructure | CLI | Knowledge Hub)
32
+ -c, --category Category (Architecture | Rule | Skill | Infrastructure | CLI | Knowledge Hub)
32
33
  -f, --files Target file(s) affected (e.g. "docs/rules/caching.md")
33
34
  -r, --rationale Rationale for upstream template incorporation
34
35
  -d, --desc Detailed description of the change
35
36
 
36
37
  Examples:
37
38
  npx azcodr my-project
39
+ npx azcodr . --dry-run
38
40
  npx azcodr . --force
39
41
  npx azcodr change "Add Wasm plugin interface" -c Architecture
40
42
  `);
41
43
  }
42
44
 
43
- function printVersion() {
44
- console.log(pkg.version);
45
+ function printVersion(out = console.log) {
46
+ out(pkg.version);
45
47
  }
46
48
 
47
- function askQuestion(query) {
48
- const rl = readline.createInterface({
49
- input: process.stdin,
50
- output: process.stdout
51
- });
49
+ function askQuestion(query, { input = process.stdin, output = process.stdout } = {}) {
50
+ const rl = readline.createInterface({ input, output });
52
51
 
53
52
  return new Promise((resolve) => {
53
+ let resolved = false;
54
54
  rl.question(query, (answer) => {
55
- rl.close();
56
- resolve(answer.trim());
55
+ if (!resolved) {
56
+ resolved = true;
57
+ rl.close();
58
+ resolve(answer.trim());
59
+ }
60
+ });
61
+ rl.on('close', () => {
62
+ if (!resolved) {
63
+ resolved = true;
64
+ resolve('');
65
+ }
57
66
  });
58
67
  });
59
68
  }
60
69
 
61
- async function handleLogChange() {
70
+ async function handleLogChange(rawArgs = process.argv.slice(2), io = {}) {
71
+ const {
72
+ out = console.log,
73
+ err = console.error,
74
+ exit = process.exit,
75
+ stdin = process.stdin,
76
+ stdout = process.stdout,
77
+ cwd = process.cwd(),
78
+ logChange: logChangeFn = logChange
79
+ } = io;
80
+
62
81
  let title = null;
63
82
  let category = 'Architecture';
64
83
  let targetFiles = 'docs/rules/';
65
84
  let rationale = 'Generic architectural enhancement';
66
85
  let description = '';
67
86
 
68
- for (let i = 1; i < args.length; i++) {
69
- const a = args[i];
87
+ for (let i = 1; i < rawArgs.length; i++) {
88
+ const a = rawArgs[i];
70
89
  if (a === '-c' || a === '--category') {
71
- category = args[++i] || category;
90
+ category = rawArgs[++i] || category;
72
91
  } else if (a === '-f' || a === '--files') {
73
- targetFiles = args[++i] || targetFiles;
92
+ targetFiles = rawArgs[++i] || targetFiles;
74
93
  } else if (a === '-r' || a === '--rationale') {
75
- rationale = args[++i] || rationale;
94
+ rationale = rawArgs[++i] || rationale;
76
95
  } else if (a === '-d' || a === '--desc' || a === '--description') {
77
- description = args[++i] || description;
78
- } else if (!a.startsWith('-')) {
79
- if (!title) {
80
- title = a;
81
- }
96
+ description = rawArgs[++i] || description;
97
+ } else if (a.startsWith('-')) {
98
+ err(`āŒ Error: Unknown argument '${a}'. Run 'npx azcodr --help' for available options.`);
99
+ return exit(1);
100
+ } else if (!title) {
101
+ title = a;
82
102
  }
83
103
  }
84
104
 
85
105
  if (!title) {
86
- if (process.stdin.isTTY) {
87
- title = await askQuestion('? Change title: ');
106
+ if (stdin.isTTY) {
107
+ title = await askQuestion('? Change title: ', { input: stdin, output: stdout });
88
108
  }
89
109
  }
90
110
 
91
111
  if (!title) {
92
- console.error('āŒ Error: A title is required to log an upstream change.');
93
- console.error('Usage: npx azcodr change "<title>" [-c Category] [-f Files] [-r Rationale] [-d Description]');
94
- process.exit(1);
112
+ err('āŒ Error: A title is required to log an upstream change.');
113
+ err('Usage: npx azcodr change "<title>" [-c Category] [-f Files] [-r Rationale] [-d Description]');
114
+ return exit(1);
95
115
  }
96
116
 
97
117
  try {
98
- const res = logChange({
118
+ const res = logChangeFn({
99
119
  title,
100
120
  category,
101
121
  targetFiles,
102
122
  rationale,
103
123
  description,
104
- targetDir: process.cwd()
124
+ targetDir: cwd
105
125
  });
106
- console.log(`\nāœ… Upstream change logged to ${res.filePath}\n`);
107
- process.exit(0);
108
- } catch (err) {
109
- console.error(`\nāŒ Failed to log change: ${err.message}\n`);
110
- process.exit(1);
126
+ out(`\nāœ… Upstream change logged to ${res.filePath}\n`);
127
+ return exit(0);
128
+ } catch (error) {
129
+ err(`\nāŒ Failed to log change: ${error.message}\n`);
130
+ return exit(1);
111
131
  }
112
132
  }
113
133
 
114
- async function main() {
115
- if (args.length > 0 && (args[0] === '-h' || args[0] === '--help')) {
116
- printHelp();
117
- process.exit(0);
118
- }
119
-
120
- if (args.length > 0 && (args[0] === '-v' || args[0] === '--version')) {
121
- printVersion();
122
- process.exit(0);
123
- }
134
+ async function runCli(rawArgs = process.argv.slice(2), io = {}) {
135
+ const {
136
+ out = console.log,
137
+ err = console.error,
138
+ exit = process.exit,
139
+ stdin = process.stdin,
140
+ stdout = process.stdout,
141
+ cwd = process.cwd(),
142
+ templateDir = getTemplateDir(),
143
+ scaffold: scaffoldFn = scaffold
144
+ } = io;
124
145
 
125
- if (args.length > 0 && (args[0] === 'change' || args[0] === 'log-change')) {
126
- await handleLogChange();
127
- return;
146
+ if (rawArgs[0] === 'change' || rawArgs[0] === 'log-change') {
147
+ return handleLogChange(rawArgs, io);
128
148
  }
129
149
 
130
150
  let targetDir = null;
131
151
  let force = false;
132
152
  let noGit = false;
153
+ let dryRun = false;
154
+ let silent = false;
133
155
 
134
- for (let i = 0; i < args.length; i++) {
135
- const arg = args[i];
156
+ for (let i = 0; i < rawArgs.length; i++) {
157
+ const arg = rawArgs[i];
136
158
  if (arg === '-h' || arg === '--help') {
137
- printHelp();
138
- process.exit(0);
159
+ printHelp(out);
160
+ return exit(0);
139
161
  } else if (arg === '-v' || arg === '--version') {
140
- printVersion();
141
- process.exit(0);
162
+ printVersion(out);
163
+ return exit(0);
142
164
  } else if (arg === '-f' || arg === '--force') {
143
165
  force = true;
144
166
  } else if (arg === '--no-git') {
145
167
  noGit = true;
146
- } else if (!arg.startsWith('-')) {
147
- if (!targetDir) {
148
- targetDir = arg;
149
- }
168
+ } else if (arg === '-d' || arg === '--dry-run') {
169
+ dryRun = true;
170
+ } else if (arg === '-s' || arg === '--silent') {
171
+ silent = true;
172
+ } else if (arg.startsWith('-')) {
173
+ err(`āŒ Error: Unknown argument '${arg}'. Run 'npx azcodr --help' for available options.`);
174
+ return exit(1);
175
+ } else if (!targetDir) {
176
+ targetDir = arg;
150
177
  }
151
178
  }
152
179
 
153
- console.log('\nšŸš€ azcodr - Enterprise Multi-Tenant Architecture & Agentic Engineering\n');
180
+ if (!silent) {
181
+ out('\nšŸš€ azcodr - Enterprise Multi-Tenant Architecture & Agentic Engineering\n');
182
+ }
154
183
 
155
184
  if (!targetDir) {
156
- if (process.stdin.isTTY) {
157
- const answer = await askQuestion('? Where would you like to initialize your project? (./) ');
185
+ if (stdin.isTTY) {
186
+ const answer = await askQuestion('? Where would you like to initialize your project? (./) ', {
187
+ input: stdin,
188
+ output: stdout
189
+ });
158
190
  targetDir = answer || '.';
159
191
  } else {
160
192
  targetDir = '.';
161
193
  }
162
194
  }
163
195
 
164
- const resolvedTarget = path.resolve(process.cwd(), targetDir);
165
- const templateDir = getTemplateDir();
196
+ const resolvedTarget = path.resolve(cwd, targetDir);
166
197
 
167
198
  if (resolvedTarget === templateDir) {
168
- console.error(`āŒ Error: Cannot scaffold into the template directory itself: ${resolvedTarget}`);
169
- process.exit(1);
199
+ err(`āŒ Error: Cannot scaffold into the template directory itself: ${resolvedTarget}`);
200
+ return exit(1);
170
201
  }
171
202
 
172
- const fs = require('node:fs');
173
203
  if (fs.existsSync(resolvedTarget)) {
204
+ const stat = fs.statSync(resolvedTarget);
205
+ if (!stat.isDirectory()) {
206
+ err(`āŒ Error: Target '${resolvedTarget}' already exists and is not a directory.`);
207
+ return exit(1);
208
+ }
174
209
  const entries = fs.readdirSync(resolvedTarget);
175
210
  if (entries.length > 0 && !force) {
176
- if (process.stdin.isTTY) {
211
+ if (stdin.isTTY) {
177
212
  const confirm = await askQuestion(
178
- `āš ļø Target directory '${targetDir}' is not empty (${entries.length} items). Continue? (y/N) `
213
+ `āš ļø Target directory '${targetDir}' is not empty (${entries.length} items). Continue? (y/N) `,
214
+ { input: stdin, output: stdout }
179
215
  );
180
216
  if (confirm.toLowerCase() !== 'y' && confirm.toLowerCase() !== 'yes') {
181
- console.log('Scaffolding aborted.');
182
- process.exit(0);
217
+ out('Scaffolding aborted.');
218
+ return exit(0);
183
219
  }
184
220
  force = true;
185
221
  } else {
186
- console.error(
187
- `āŒ Error: Target directory '${resolvedTarget}' is not empty. Use --force to proceed.`
188
- );
189
- process.exit(1);
222
+ err(`āŒ Error: Target directory '${resolvedTarget}' is not empty. Use --force to proceed.`);
223
+ return exit(1);
190
224
  }
191
225
  }
192
226
  }
193
227
 
194
- console.log(`šŸ“¦ Scaffolding azcodr into: ${resolvedTarget}`);
228
+ if (dryRun) {
229
+ if (!silent) {
230
+ out(`šŸ” DRY RUN: Simulating azcodr scaffolding into: ${resolvedTarget}\n`);
231
+ }
232
+ } else if (!silent) {
233
+ out(`šŸ“¦ Scaffolding azcodr into: ${resolvedTarget}`);
234
+ }
195
235
 
196
236
  try {
197
- const result = scaffold({
237
+ const result = scaffoldFn({
198
238
  targetDir: resolvedTarget,
199
239
  force,
200
240
  noGit,
201
- templateDir
241
+ templateDir,
242
+ dryRun,
243
+ silent
202
244
  });
203
245
 
204
- console.log(' āœ… Progressive disclosure rules copied (docs/rules/)');
205
- console.log(' āœ… Workspace knowledge hub and ADR ledger copied (docs/knowledge/, memory.md)');
206
- console.log(' āœ… Specialized agentic skills copied (.agents/skills/)');
207
- console.log(' āœ… Upstream changes ledger initialized (changes.md)');
208
- console.log(' āœ… Agent directives and harness symlinks established (AGENTS.md, CLAUDE.md, agents.md)');
209
- if (result.gitInitialized) {
210
- console.log(' āœ… Git repository initialized');
246
+ if (dryRun) {
247
+ if (!silent) {
248
+ for (const action of result.actions) {
249
+ out(` [preview] ${action}`);
250
+ }
251
+ out('\nšŸŽ‰ Dry run completed. 0 files modified on disk.\n');
252
+ }
253
+ return exit(0);
211
254
  }
212
255
 
213
- console.log('\nšŸŽ‰ azcodr initialized successfully!\n');
214
- console.log('Next steps:');
215
- if (targetDir !== '.' && targetDir !== './') {
216
- console.log(` 1. cd ${targetDir}`);
256
+ if (!silent) {
257
+ out(' āœ… Progressive disclosure rules copied (docs/rules/)');
258
+ out(' āœ… Workspace knowledge hub and ADR ledger copied (docs/knowledge/, memory.md)');
259
+ out(' āœ… Specialized agentic skills copied (.agents/skills/)');
260
+ out(' āœ… Upstream changes ledger initialized (changes.md)');
261
+ out(' āœ… Editor formatting standards initialized (.editorconfig)');
262
+ out(' āœ… Agent directives and harness symlinks established (AGENTS.md, CLAUDE.md, agents.md)');
263
+ if (result.gitInitialized) {
264
+ out(' āœ… Git repository initialized');
265
+ }
266
+
267
+ out('\nšŸŽ‰ azcodr initialized successfully!\n');
268
+ out('Next steps:');
269
+ if (targetDir !== '.' && targetDir !== './') {
270
+ out(` 1. cd ${targetDir}`);
271
+ }
272
+ out(' 2. Open the project in your AI coding assistant (Antigravity, Claude Code, Cursor, OpenHands)');
273
+ out(' 3. Run /lets-build to start the architectural interview and scaffold your application stack!\n');
217
274
  }
218
- console.log(' 2. Open the project in your AI coding assistant (Antigravity, Claude Code, Cursor, OpenHands)');
219
- console.log(' 3. Run /lets-build to start the architectural interview and scaffold your application stack!\n');
275
+ return exit(0);
276
+ } catch (error) {
277
+ err(`\nāŒ Scaffolding failed: ${error.message}\n`);
278
+ return exit(1);
279
+ }
280
+ }
281
+
282
+ async function main() {
283
+ try {
284
+ await runCli(process.argv.slice(2));
220
285
  } catch (err) {
221
- console.error(`\nāŒ Scaffolding failed: ${err.message}\n`);
286
+ console.error('Unexpected error:', err);
222
287
  process.exit(1);
223
288
  }
224
289
  }
225
290
 
226
- main().catch((err) => {
227
- console.error('Unexpected error:', err);
228
- process.exit(1);
229
- });
291
+ if (require.main === module) {
292
+ main();
293
+ }
294
+
295
+ module.exports = {
296
+ runCli,
297
+ handleLogChange,
298
+ askQuestion,
299
+ printHelp,
300
+ printVersion,
301
+ main
302
+ };