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
|
@@ -1,52 +1,84 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# ==============================================================================
|
|
3
3
|
# bootstrap_workspace.sh
|
|
4
|
-
# Deterministic Scaffolder
|
|
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
|
-
|
|
10
|
+
TOPOLOGY="${2:-backend}"
|
|
11
|
+
LANGUAGE="${3:-generic}"
|
|
11
12
|
|
|
12
|
-
echo "š Initializing
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
mkdir -p "${WORKSPACE_ROOT}/
|
|
20
|
-
mkdir -p "${WORKSPACE_ROOT}/
|
|
21
|
-
mkdir -p "${WORKSPACE_ROOT}/
|
|
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
|
-
|
|
24
|
-
echo "
|
|
25
|
-
mkdir -p "${WORKSPACE_ROOT}/src/
|
|
26
|
-
mkdir -p "${WORKSPACE_ROOT}/src/
|
|
27
|
-
mkdir -p "${WORKSPACE_ROOT}/src/
|
|
28
|
-
mkdir -p "${WORKSPACE_ROOT}/src/
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
mkdir -p "${WORKSPACE_ROOT}/
|
|
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
|
-
|
|
34
|
-
echo "
|
|
35
|
-
mkdir -p "${WORKSPACE_ROOT}/
|
|
36
|
-
mkdir -p "${WORKSPACE_ROOT}/
|
|
37
|
-
mkdir -p "${WORKSPACE_ROOT}/
|
|
38
|
-
|
|
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
|
-
|
|
41
|
-
echo "
|
|
42
|
-
mkdir -p "${WORKSPACE_ROOT}/
|
|
43
|
-
mkdir -p "${WORKSPACE_ROOT}/
|
|
44
|
-
mkdir -p "${WORKSPACE_ROOT}/
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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 "ā
|
|
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
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
|
-
> **
|
|
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. **
|
|
18
|
-
6. **
|
|
19
|
-
7. **
|
|
20
|
-
8. **
|
|
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
|
|
44
|
-
2. INTERROGATE / DOMAIN āāāŗ Relentless questioning; Ubiquitous Language &
|
|
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 āāāŗ
|
|
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) |
|
|
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
|
|
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 &
|
|
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
|
-
-
|
|
181
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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 <
|
|
69
|
-
const a =
|
|
87
|
+
for (let i = 1; i < rawArgs.length; i++) {
|
|
88
|
+
const a = rawArgs[i];
|
|
70
89
|
if (a === '-c' || a === '--category') {
|
|
71
|
-
category =
|
|
90
|
+
category = rawArgs[++i] || category;
|
|
72
91
|
} else if (a === '-f' || a === '--files') {
|
|
73
|
-
targetFiles =
|
|
92
|
+
targetFiles = rawArgs[++i] || targetFiles;
|
|
74
93
|
} else if (a === '-r' || a === '--rationale') {
|
|
75
|
-
rationale =
|
|
94
|
+
rationale = rawArgs[++i] || rationale;
|
|
76
95
|
} else if (a === '-d' || a === '--desc' || a === '--description') {
|
|
77
|
-
description =
|
|
78
|
-
} else if (
|
|
79
|
-
|
|
80
|
-
|
|
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 (
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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 =
|
|
118
|
+
const res = logChangeFn({
|
|
99
119
|
title,
|
|
100
120
|
category,
|
|
101
121
|
targetFiles,
|
|
102
122
|
rationale,
|
|
103
123
|
description,
|
|
104
|
-
targetDir:
|
|
124
|
+
targetDir: cwd
|
|
105
125
|
});
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
} catch (
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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 (
|
|
126
|
-
|
|
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 <
|
|
135
|
-
const arg =
|
|
156
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
157
|
+
const arg = rawArgs[i];
|
|
136
158
|
if (arg === '-h' || arg === '--help') {
|
|
137
|
-
printHelp();
|
|
138
|
-
|
|
159
|
+
printHelp(out);
|
|
160
|
+
return exit(0);
|
|
139
161
|
} else if (arg === '-v' || arg === '--version') {
|
|
140
|
-
printVersion();
|
|
141
|
-
|
|
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 (
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
180
|
+
if (!silent) {
|
|
181
|
+
out('\nš azcodr - Enterprise Multi-Tenant Architecture & Agentic Engineering\n');
|
|
182
|
+
}
|
|
154
183
|
|
|
155
184
|
if (!targetDir) {
|
|
156
|
-
if (
|
|
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(
|
|
165
|
-
const templateDir = getTemplateDir();
|
|
196
|
+
const resolvedTarget = path.resolve(cwd, targetDir);
|
|
166
197
|
|
|
167
198
|
if (resolvedTarget === templateDir) {
|
|
168
|
-
|
|
169
|
-
|
|
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 (
|
|
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
|
-
|
|
182
|
-
|
|
217
|
+
out('Scaffolding aborted.');
|
|
218
|
+
return exit(0);
|
|
183
219
|
}
|
|
184
220
|
force = true;
|
|
185
221
|
} else {
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
219
|
-
|
|
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(
|
|
286
|
+
console.error('Unexpected error:', err);
|
|
222
287
|
process.exit(1);
|
|
223
288
|
}
|
|
224
289
|
}
|
|
225
290
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
+
};
|