create-project-arch 1.2.0 → 1.3.1
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/CHANGELOG.md +15 -2
- package/README.md +136 -10
- package/dist/cli.js +151 -0
- package/dist/cli.test.js +191 -0
- package/package.json +5 -5
- package/templates/arch-ui/eslint.config.js +2 -2
- package/templates/architecture-specs/SPEC_TEMPLATE.md +49 -0
- package/templates/architecture-specs/example-system.md +42 -0
- package/templates/concept-map/concept-map.json +67 -0
- package/templates/decisions/DECISION_TEMPLATE.md +53 -0
- package/templates/decisions/README.md +19 -0
- package/templates/decisions/example-decision.md +45 -0
- package/templates/domains/DOMAIN_TEMPLATE.md +43 -0
- package/templates/domains/README.md +18 -0
- package/templates/domains/api.md +33 -0
- package/templates/domains/core.md +33 -0
- package/templates/domains/domains.json +19 -0
- package/templates/domains/ui.md +34 -0
- package/templates/foundation/goals.md +35 -0
- package/templates/foundation/project-overview.md +35 -0
- package/templates/foundation/prompt.md +23 -0
- package/templates/foundation/scope.md +35 -0
- package/templates/foundation/user-journey.md +37 -0
- package/templates/gap-closure/GAP_CLOSURE_TEMPLATE.md +50 -0
- package/templates/gap-closure/README.md +19 -0
- package/templates/gap-closure/example-gap-closure.md +43 -0
- package/templates/validation-hooks/.githooks/pre-commit +4 -0
- package/templates/validation-hooks/README.md +20 -0
- package/templates/validation-hooks/scripts/validate.sh +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.3.0] - 2026-03-08
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Foundation, domain, architecture spec, concept-map, decision record, and gap-closure scaffolds.
|
|
13
|
+
- Local validation hook scaffold (`scripts/validate.sh` and `.githooks/pre-commit`).
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Validation-hook copy behavior now merges directories and preserves existing files while adding missing template files.
|
|
18
|
+
- Template scaffolding flow expanded to include architecture governance docs by default.
|
|
19
|
+
|
|
8
20
|
## [1.1.0] - 2026-03-07
|
|
9
21
|
|
|
10
22
|
### Added
|
|
@@ -39,5 +51,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
39
51
|
- Template customization options
|
|
40
52
|
- Force mode for non-empty directories
|
|
41
53
|
|
|
42
|
-
[1.1.0]: https://github.com/
|
|
43
|
-
[1.0.0]: https://github.com/
|
|
54
|
+
[1.1.0]: https://github.com/MissTitanK3/project-arch-system/compare/v1.0.0...v1.1.0
|
|
55
|
+
[1.0.0]: https://github.com/MissTitanK3/project-arch-system/releases/tag/v1.0.0
|
|
56
|
+
[1.3.0]: https://github.com/MissTitanK3/project-arch-system/compare/v1.1.0...v1.3.0
|
package/README.md
CHANGED
|
@@ -80,7 +80,7 @@ When you run `create-project-arch`, it:
|
|
|
80
80
|
|
|
81
81
|
### Project Structure
|
|
82
82
|
|
|
83
|
-
```
|
|
83
|
+
```bash
|
|
84
84
|
my-awesome-project/
|
|
85
85
|
├── apps/
|
|
86
86
|
│ ├── arch/ # Architecture UI (Next.js app)
|
|
@@ -107,12 +107,138 @@ my-awesome-project/
|
|
|
107
107
|
│ │ └── phase-1/
|
|
108
108
|
│ │ └── milestones/
|
|
109
109
|
│ ├── decisions/
|
|
110
|
-
│
|
|
110
|
+
│ ├── docs/
|
|
111
|
+
│ └── concept-map.json # Concept-to-module traceability map
|
|
112
|
+
├── architecture/
|
|
113
|
+
│ ├── foundation/ # Milestone 1 prerequisite docs
|
|
114
|
+
│ │ ├── prompt.md
|
|
115
|
+
│ │ ├── project-overview.md
|
|
116
|
+
│ │ ├── goals.md
|
|
117
|
+
│ │ ├── user-journey.md
|
|
118
|
+
│ │ └── scope.md
|
|
119
|
+
│ ├── architecture/ # System architecture specs
|
|
120
|
+
│ │ ├── SPEC_TEMPLATE.md
|
|
121
|
+
│ │ └── example-system.md
|
|
122
|
+
│ ├── decisions/ # Architecture decision records
|
|
123
|
+
│ ├── DECISION_TEMPLATE.md
|
|
124
|
+
│ └── example-decision.md
|
|
125
|
+
│ └── reference/ # Reusable quality and closure references
|
|
126
|
+
│ ├── GAP_CLOSURE_TEMPLATE.md
|
|
127
|
+
│ └── example-gap-closure.md
|
|
128
|
+
├── arch-domains/ # Domain boundaries and ownership
|
|
129
|
+
│ ├── README.md
|
|
130
|
+
│ ├── domains.json
|
|
131
|
+
│ ├── DOMAIN_TEMPLATE.md
|
|
132
|
+
│ ├── core.md
|
|
133
|
+
│ ├── ui.md
|
|
134
|
+
│ └── api.md
|
|
135
|
+
├── scripts/
|
|
136
|
+
│ └── validate.sh # Local architecture validation hook
|
|
137
|
+
├── .githooks/
|
|
138
|
+
│ └── pre-commit # Optional local pre-commit validation hook
|
|
111
139
|
├── package.json
|
|
112
140
|
├── turbo.json
|
|
113
141
|
└── pnpm-workspace.yaml
|
|
114
142
|
```
|
|
115
143
|
|
|
144
|
+
### Milestone 1 Prerequisites
|
|
145
|
+
|
|
146
|
+
Scaffolded projects include these foundation docs by default under `architecture/foundation/`:
|
|
147
|
+
|
|
148
|
+
- `prompt.md` (canonical source brief)
|
|
149
|
+
- `project-overview.md`
|
|
150
|
+
- `goals.md`
|
|
151
|
+
- `user-journey.md`
|
|
152
|
+
- `scope.md`
|
|
153
|
+
|
|
154
|
+
Complete these files first before implementing milestone tasks.
|
|
155
|
+
|
|
156
|
+
### Domain Spec Scaffold
|
|
157
|
+
|
|
158
|
+
Scaffolded projects also include baseline domain specs under `arch-domains/`:
|
|
159
|
+
|
|
160
|
+
- `domains.json` with starter domains (`core`, `ui`, `api`)
|
|
161
|
+
- `DOMAIN_TEMPLATE.md` with required sections:
|
|
162
|
+
- Responsibilities
|
|
163
|
+
- Primary Data Ownership
|
|
164
|
+
- Interface Contracts
|
|
165
|
+
- Non-Goals
|
|
166
|
+
- Milestone Mapping
|
|
167
|
+
- Starter specs: `core.md`, `ui.md`, and `api.md`
|
|
168
|
+
|
|
169
|
+
### System Architecture Spec Scaffold
|
|
170
|
+
|
|
171
|
+
Scaffolded projects include reusable architecture specs under `architecture/architecture/`:
|
|
172
|
+
|
|
173
|
+
- `SPEC_TEMPLATE.md` with required sections:
|
|
174
|
+
- Purpose
|
|
175
|
+
- Scope (in-scope / out-of-scope)
|
|
176
|
+
- Key Definitions
|
|
177
|
+
- Design
|
|
178
|
+
- Data Model
|
|
179
|
+
- Owning Domain
|
|
180
|
+
- MVP Constraints
|
|
181
|
+
- `example-system.md` showing a realistic completed reference
|
|
182
|
+
|
|
183
|
+
### Concept-To-Module Traceability Scaffold
|
|
184
|
+
|
|
185
|
+
Scaffolded projects include `arch-model/concept-map.json` with:
|
|
186
|
+
|
|
187
|
+
- concept metadata (`id`, `name`, `description`)
|
|
188
|
+
- owning domain assignment
|
|
189
|
+
- module responsibilities
|
|
190
|
+
- implementation surfaces (API/UI/component/code paths)
|
|
191
|
+
- concept dependencies
|
|
192
|
+
- domain-module mapping and implementation checklist placeholders
|
|
193
|
+
|
|
194
|
+
### Decision Record Scaffold
|
|
195
|
+
|
|
196
|
+
Scaffolded projects include architecture decision templates under `architecture/decisions/`:
|
|
197
|
+
|
|
198
|
+
- `DECISION_TEMPLATE.md` with structured frontmatter (`id`, `title`, `slug`, `status`, timestamps, `relatedTasks`, `relatedDocs`, `supersedes`)
|
|
199
|
+
- Required sections:
|
|
200
|
+
- Context
|
|
201
|
+
- Decision
|
|
202
|
+
- Rationale
|
|
203
|
+
- Alternatives Considered
|
|
204
|
+
- Affected Artifacts
|
|
205
|
+
- Implementation Status Checklist
|
|
206
|
+
- `example-decision.md` demonstrating a completed decision record
|
|
207
|
+
|
|
208
|
+
Use `pa decision new` for operational decision creation linked into roadmap decision indexes.
|
|
209
|
+
|
|
210
|
+
### Milestone Gap-Closure Report Scaffold
|
|
211
|
+
|
|
212
|
+
Scaffolded projects include closure report artifacts under `architecture/reference/`:
|
|
213
|
+
|
|
214
|
+
- `GAP_CLOSURE_TEMPLATE.md` with sections for:
|
|
215
|
+
- Executive Summary
|
|
216
|
+
- Gap Categories And Resolutions
|
|
217
|
+
- Layer Synchronization Check
|
|
218
|
+
- Coverage Audit
|
|
219
|
+
- Remaining Gaps And Follow-On Items
|
|
220
|
+
- Template Improvement Feedback
|
|
221
|
+
- `example-gap-closure.md` demonstrating a completed closure report
|
|
222
|
+
|
|
223
|
+
Recommended workflow:
|
|
224
|
+
|
|
225
|
+
1. Complete milestone tasks and decision updates.
|
|
226
|
+
2. Run `pa check` and `pa report`.
|
|
227
|
+
3. Record closure outcomes in milestone closure report.
|
|
228
|
+
4. Track remaining gaps as follow-on tasks/decisions.
|
|
229
|
+
|
|
230
|
+
### Local Validation Hook Scaffold
|
|
231
|
+
|
|
232
|
+
Scaffolded projects include local validation automation assets:
|
|
233
|
+
|
|
234
|
+
- `scripts/validate.sh` runs:
|
|
235
|
+
- `pnpm arch:check`
|
|
236
|
+
- `pnpm arch:report`
|
|
237
|
+
- Optional local hook example:
|
|
238
|
+
- `.githooks/pre-commit`
|
|
239
|
+
|
|
240
|
+
Use these hooks to keep architecture validation consistent in local workflows.
|
|
241
|
+
|
|
116
242
|
## Available Templates
|
|
117
243
|
|
|
118
244
|
### arch-ui (Default)
|
|
@@ -318,7 +444,7 @@ node dist/cli.js test-output --force
|
|
|
318
444
|
|
|
319
445
|
Templates are stored in `templates/` directory:
|
|
320
446
|
|
|
321
|
-
```
|
|
447
|
+
```bash
|
|
322
448
|
templates/
|
|
323
449
|
├── arch-ui/
|
|
324
450
|
│ ├── package.json
|
|
@@ -472,19 +598,19 @@ npm run dev
|
|
|
472
598
|
|
|
473
599
|
To add project-arch to an existing project:
|
|
474
600
|
|
|
475
|
-
1. Install project-arch
|
|
601
|
+
### 1. Install project-arch
|
|
476
602
|
|
|
477
603
|
```bash
|
|
478
604
|
pnpm add project-arch -w
|
|
479
605
|
```
|
|
480
606
|
|
|
481
|
-
2. Initialize architecture
|
|
607
|
+
### 2. Initialize architecture
|
|
482
608
|
|
|
483
609
|
```bash
|
|
484
610
|
pnpm exec pa init
|
|
485
611
|
```
|
|
486
612
|
|
|
487
|
-
3. (Optional) Copy template files manually from this repository
|
|
613
|
+
### 3. (Optional) Copy template files manually from this repository
|
|
488
614
|
|
|
489
615
|
## API Reference (Programmatic Usage)
|
|
490
616
|
|
|
@@ -530,9 +656,9 @@ MIT License - see [LICENSE](LICENSE) for details.
|
|
|
530
656
|
|
|
531
657
|
## Support
|
|
532
658
|
|
|
533
|
-
- 📖 [Documentation](https://github.com/
|
|
534
|
-
- 🐛 [Issue Tracker](https://github.com/
|
|
535
|
-
- 💬 [Discussions](https://github.com/
|
|
659
|
+
- 📖 [Documentation](https://github.com/MissTitanK3/project-arch-system#readme)
|
|
660
|
+
- 🐛 [Issue Tracker](https://github.com/MissTitanK3/project-arch-system/issues)
|
|
661
|
+
- 💬 [Discussions](https://github.com/MissTitanK3/project-arch-system/discussions)
|
|
536
662
|
|
|
537
663
|
## Acknowledgments
|
|
538
664
|
|
|
@@ -540,7 +666,7 @@ Built with:
|
|
|
540
666
|
|
|
541
667
|
- [Commander.js](https://github.com/tj/commander.js) - CLI framework
|
|
542
668
|
- [fs-extra](https://github.com/jprichardson/node-fs-extra) - File system utilities
|
|
543
|
-
- [project-arch](https://github.com/
|
|
669
|
+
- [project-arch](https://github.com/MissTitanK3/project-arch-system) - Architecture management
|
|
544
670
|
|
|
545
671
|
---
|
|
546
672
|
|
package/dist/cli.js
CHANGED
|
@@ -135,6 +135,150 @@ async function scaffoldArchitectureApps(targetDir) {
|
|
|
135
135
|
await fs_extra_1.default.writeJSON(archUiPkgPath, nextArchUiPkg, { spaces: 2 });
|
|
136
136
|
await fs_extra_1.default.appendFile(archUiPkgPath, "\n");
|
|
137
137
|
}
|
|
138
|
+
async function scaffoldFoundationDocs(targetDir) {
|
|
139
|
+
const templatesRoot = getTemplatesRoot();
|
|
140
|
+
const foundationTemplateRoot = path_1.default.join(templatesRoot, "foundation");
|
|
141
|
+
const foundationTargetRoot = path_1.default.join(targetDir, "architecture", "foundation");
|
|
142
|
+
if (!(await fs_extra_1.default.pathExists(foundationTemplateRoot))) {
|
|
143
|
+
throw new Error(`Missing foundation templates at ${foundationTemplateRoot}`);
|
|
144
|
+
}
|
|
145
|
+
await fs_extra_1.default.ensureDir(foundationTargetRoot);
|
|
146
|
+
const foundationEntries = await fs_extra_1.default.readdir(foundationTemplateRoot);
|
|
147
|
+
for (const entry of foundationEntries) {
|
|
148
|
+
const sourcePath = path_1.default.join(foundationTemplateRoot, entry);
|
|
149
|
+
const targetPath = path_1.default.join(foundationTargetRoot, entry);
|
|
150
|
+
if (await fs_extra_1.default.pathExists(targetPath)) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
await fs_extra_1.default.copy(sourcePath, targetPath, { overwrite: false });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function scaffoldDomainSpecs(targetDir) {
|
|
157
|
+
const templatesRoot = getTemplatesRoot();
|
|
158
|
+
const domainsTemplateRoot = path_1.default.join(templatesRoot, "domains");
|
|
159
|
+
const domainsTargetRoot = path_1.default.join(targetDir, "arch-domains");
|
|
160
|
+
if (!(await fs_extra_1.default.pathExists(domainsTemplateRoot))) {
|
|
161
|
+
throw new Error(`Missing domain templates at ${domainsTemplateRoot}`);
|
|
162
|
+
}
|
|
163
|
+
await fs_extra_1.default.ensureDir(domainsTargetRoot);
|
|
164
|
+
const domainEntries = await fs_extra_1.default.readdir(domainsTemplateRoot);
|
|
165
|
+
for (const entry of domainEntries) {
|
|
166
|
+
const sourcePath = path_1.default.join(domainsTemplateRoot, entry);
|
|
167
|
+
const targetPath = path_1.default.join(domainsTargetRoot, entry);
|
|
168
|
+
if (entry === "DOMAIN_TEMPLATE.md" || entry === "README.md") {
|
|
169
|
+
await fs_extra_1.default.copy(sourcePath, targetPath, { overwrite: true });
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (entry === "domains.json") {
|
|
173
|
+
if (await fs_extra_1.default.pathExists(targetPath)) {
|
|
174
|
+
const existing = (await fs_extra_1.default.readJSON(targetPath));
|
|
175
|
+
if (Array.isArray(existing.domains) && existing.domains.length > 0) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
await fs_extra_1.default.copy(sourcePath, targetPath, { overwrite: true });
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (await fs_extra_1.default.pathExists(targetPath)) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
await fs_extra_1.default.copy(sourcePath, targetPath, { overwrite: false });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async function scaffoldArchitectureSpecs(targetDir) {
|
|
189
|
+
const templatesRoot = getTemplatesRoot();
|
|
190
|
+
const architectureSpecsTemplateRoot = path_1.default.join(templatesRoot, "architecture-specs");
|
|
191
|
+
const architectureSpecsTargetRoot = path_1.default.join(targetDir, "architecture", "architecture");
|
|
192
|
+
if (!(await fs_extra_1.default.pathExists(architectureSpecsTemplateRoot))) {
|
|
193
|
+
throw new Error(`Missing architecture spec templates at ${architectureSpecsTemplateRoot}`);
|
|
194
|
+
}
|
|
195
|
+
await fs_extra_1.default.ensureDir(architectureSpecsTargetRoot);
|
|
196
|
+
const specEntries = await fs_extra_1.default.readdir(architectureSpecsTemplateRoot);
|
|
197
|
+
for (const entry of specEntries) {
|
|
198
|
+
const sourcePath = path_1.default.join(architectureSpecsTemplateRoot, entry);
|
|
199
|
+
const targetPath = path_1.default.join(architectureSpecsTargetRoot, entry);
|
|
200
|
+
if (await fs_extra_1.default.pathExists(targetPath)) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
await fs_extra_1.default.copy(sourcePath, targetPath, { overwrite: false });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async function scaffoldConceptMap(targetDir) {
|
|
207
|
+
const templatesRoot = getTemplatesRoot();
|
|
208
|
+
const conceptMapTemplatePath = path_1.default.join(templatesRoot, "concept-map", "concept-map.json");
|
|
209
|
+
const conceptMapTargetPath = path_1.default.join(targetDir, "arch-model", "concept-map.json");
|
|
210
|
+
if (!(await fs_extra_1.default.pathExists(conceptMapTemplatePath))) {
|
|
211
|
+
throw new Error(`Missing concept-map template at ${conceptMapTemplatePath}`);
|
|
212
|
+
}
|
|
213
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(conceptMapTargetPath));
|
|
214
|
+
if (await fs_extra_1.default.pathExists(conceptMapTargetPath)) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
await fs_extra_1.default.copy(conceptMapTemplatePath, conceptMapTargetPath, { overwrite: false });
|
|
218
|
+
}
|
|
219
|
+
async function scaffoldDecisionRecords(targetDir) {
|
|
220
|
+
const templatesRoot = getTemplatesRoot();
|
|
221
|
+
const decisionsTemplateRoot = path_1.default.join(templatesRoot, "decisions");
|
|
222
|
+
const decisionsTargetRoot = path_1.default.join(targetDir, "architecture", "decisions");
|
|
223
|
+
if (!(await fs_extra_1.default.pathExists(decisionsTemplateRoot))) {
|
|
224
|
+
throw new Error(`Missing decision templates at ${decisionsTemplateRoot}`);
|
|
225
|
+
}
|
|
226
|
+
await fs_extra_1.default.ensureDir(decisionsTargetRoot);
|
|
227
|
+
const entries = await fs_extra_1.default.readdir(decisionsTemplateRoot);
|
|
228
|
+
for (const entry of entries) {
|
|
229
|
+
const sourcePath = path_1.default.join(decisionsTemplateRoot, entry);
|
|
230
|
+
const targetPath = path_1.default.join(decisionsTargetRoot, entry);
|
|
231
|
+
if (await fs_extra_1.default.pathExists(targetPath)) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
await fs_extra_1.default.copy(sourcePath, targetPath, { overwrite: false });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async function scaffoldGapClosureTemplates(targetDir) {
|
|
238
|
+
const templatesRoot = getTemplatesRoot();
|
|
239
|
+
const gapClosureTemplateRoot = path_1.default.join(templatesRoot, "gap-closure");
|
|
240
|
+
const gapClosureTargetRoot = path_1.default.join(targetDir, "architecture", "reference");
|
|
241
|
+
if (!(await fs_extra_1.default.pathExists(gapClosureTemplateRoot))) {
|
|
242
|
+
throw new Error(`Missing gap-closure templates at ${gapClosureTemplateRoot}`);
|
|
243
|
+
}
|
|
244
|
+
await fs_extra_1.default.ensureDir(gapClosureTargetRoot);
|
|
245
|
+
const entries = await fs_extra_1.default.readdir(gapClosureTemplateRoot);
|
|
246
|
+
for (const entry of entries) {
|
|
247
|
+
const sourcePath = path_1.default.join(gapClosureTemplateRoot, entry);
|
|
248
|
+
const targetPath = path_1.default.join(gapClosureTargetRoot, entry);
|
|
249
|
+
if (await fs_extra_1.default.pathExists(targetPath)) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
await fs_extra_1.default.copy(sourcePath, targetPath, { overwrite: false });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async function scaffoldValidationHooks(targetDir) {
|
|
256
|
+
const templatesRoot = getTemplatesRoot();
|
|
257
|
+
const validationTemplateRoot = path_1.default.join(templatesRoot, "validation-hooks");
|
|
258
|
+
if (!(await fs_extra_1.default.pathExists(validationTemplateRoot))) {
|
|
259
|
+
throw new Error(`Missing validation hook templates at ${validationTemplateRoot}`);
|
|
260
|
+
}
|
|
261
|
+
const entries = await fs_extra_1.default.readdir(validationTemplateRoot);
|
|
262
|
+
for (const entry of entries) {
|
|
263
|
+
const sourcePath = path_1.default.join(validationTemplateRoot, entry);
|
|
264
|
+
const targetPath = path_1.default.join(targetDir, entry);
|
|
265
|
+
await copyMissingEntries(sourcePath, targetPath);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async function copyMissingEntries(sourcePath, targetPath) {
|
|
269
|
+
const sourceStats = await fs_extra_1.default.stat(sourcePath);
|
|
270
|
+
if (!sourceStats.isDirectory()) {
|
|
271
|
+
if (!(await fs_extra_1.default.pathExists(targetPath))) {
|
|
272
|
+
await fs_extra_1.default.copy(sourcePath, targetPath, { overwrite: false });
|
|
273
|
+
}
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
await fs_extra_1.default.ensureDir(targetPath);
|
|
277
|
+
const children = await fs_extra_1.default.readdir(sourcePath);
|
|
278
|
+
for (const child of children) {
|
|
279
|
+
await copyMissingEntries(path_1.default.join(sourcePath, child), path_1.default.join(targetPath, child));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
138
282
|
async function upsertArchModulesInMap(targetDir) {
|
|
139
283
|
const modulesPath = path_1.default.join(targetDir, "arch-model", "modules.json");
|
|
140
284
|
if (!(await fs_extra_1.default.pathExists(modulesPath))) {
|
|
@@ -213,6 +357,13 @@ async function main() {
|
|
|
213
357
|
}
|
|
214
358
|
}
|
|
215
359
|
await runPaInit(targetDir, options);
|
|
360
|
+
await scaffoldFoundationDocs(targetDir);
|
|
361
|
+
await scaffoldDomainSpecs(targetDir);
|
|
362
|
+
await scaffoldArchitectureSpecs(targetDir);
|
|
363
|
+
await scaffoldConceptMap(targetDir);
|
|
364
|
+
await scaffoldDecisionRecords(targetDir);
|
|
365
|
+
await scaffoldGapClosureTemplates(targetDir);
|
|
366
|
+
await scaffoldValidationHooks(targetDir);
|
|
216
367
|
await scaffoldArchitectureApps(targetDir);
|
|
217
368
|
await upsertArchModulesInMap(targetDir);
|
|
218
369
|
await wireProjectArchUsage(targetDir);
|
package/dist/cli.test.js
CHANGED
|
@@ -1,8 +1,199 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
3
8
|
const vitest_1 = require("vitest");
|
|
9
|
+
const currentDir = __dirname;
|
|
10
|
+
const archUiEslintConfigPath = node_path_1.default.resolve(currentDir, "../templates/arch-ui/eslint.config.js");
|
|
11
|
+
const foundationTemplateDir = node_path_1.default.resolve(currentDir, "../templates/foundation");
|
|
12
|
+
const domainTemplateDir = node_path_1.default.resolve(currentDir, "../templates/domains");
|
|
13
|
+
const architectureSpecTemplateDir = node_path_1.default.resolve(currentDir, "../templates/architecture-specs");
|
|
14
|
+
const conceptMapTemplatePath = node_path_1.default.resolve(currentDir, "../templates/concept-map/concept-map.json");
|
|
15
|
+
const decisionTemplateDir = node_path_1.default.resolve(currentDir, "../templates/decisions");
|
|
16
|
+
const gapClosureTemplateDir = node_path_1.default.resolve(currentDir, "../templates/gap-closure");
|
|
17
|
+
const validationHookTemplateDir = node_path_1.default.resolve(currentDir, "../templates/validation-hooks");
|
|
18
|
+
const foundationTemplateExpectations = [
|
|
19
|
+
{ fileName: "prompt.md", sectionHeader: "## Source Prompt" },
|
|
20
|
+
{ fileName: "project-overview.md", sectionHeader: "## Problem Statement" },
|
|
21
|
+
{ fileName: "goals.md", sectionHeader: "## Primary Goals" },
|
|
22
|
+
{ fileName: "user-journey.md", sectionHeader: "## Journey Steps" },
|
|
23
|
+
{ fileName: "scope.md", sectionHeader: "## In Scope" },
|
|
24
|
+
];
|
|
25
|
+
const domainTemplateExpectations = [
|
|
26
|
+
{ fileName: "core.md", sectionHeader: "## Responsibilities" },
|
|
27
|
+
{ fileName: "ui.md", sectionHeader: "## Responsibilities" },
|
|
28
|
+
{ fileName: "api.md", sectionHeader: "## Responsibilities" },
|
|
29
|
+
];
|
|
30
|
+
const architectureSpecTemplateSections = [
|
|
31
|
+
"## Purpose",
|
|
32
|
+
"## Scope",
|
|
33
|
+
"## Key Definitions",
|
|
34
|
+
"## Design",
|
|
35
|
+
"## Data Model",
|
|
36
|
+
"## Owning Domain",
|
|
37
|
+
"## MVP Constraints",
|
|
38
|
+
];
|
|
39
|
+
const decisionTemplateSections = [
|
|
40
|
+
"## Context",
|
|
41
|
+
"## Decision",
|
|
42
|
+
"## Rationale",
|
|
43
|
+
"## Alternatives Considered",
|
|
44
|
+
"## Affected Artifacts",
|
|
45
|
+
"## Implementation Status Checklist",
|
|
46
|
+
];
|
|
47
|
+
const gapClosureTemplateSections = [
|
|
48
|
+
"## Executive Summary",
|
|
49
|
+
"## Gap Categories And Resolutions",
|
|
50
|
+
"## Layer Synchronization Check",
|
|
51
|
+
"## Coverage Audit",
|
|
52
|
+
"## Remaining Gaps And Follow-On Items",
|
|
53
|
+
"## Template Improvement Feedback",
|
|
54
|
+
];
|
|
55
|
+
function getImportedNextJsSymbol(source) {
|
|
56
|
+
const match = source.match(/import\s+\{\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*\}\s+from\s+["']@repo\/eslint-config\/next-js["']/);
|
|
57
|
+
return match?.[1] ?? null;
|
|
58
|
+
}
|
|
59
|
+
function getDefaultExportedSymbol(source) {
|
|
60
|
+
const match = source.match(/export\s+default\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*;?/);
|
|
61
|
+
return match?.[1] ?? null;
|
|
62
|
+
}
|
|
4
63
|
(0, vitest_1.describe)("create-project-arch CLI", () => {
|
|
5
64
|
(0, vitest_1.it)("runs test suite successfully", () => {
|
|
6
65
|
(0, vitest_1.expect)(true).toBe(true);
|
|
7
66
|
});
|
|
67
|
+
(0, vitest_1.it)("keeps arch-ui eslint template wired to @repo/eslint-config/next-js export", () => {
|
|
68
|
+
const archUiConfigSource = node_fs_1.default.readFileSync(archUiEslintConfigPath, "utf8");
|
|
69
|
+
const importedSymbol = getImportedNextJsSymbol(archUiConfigSource);
|
|
70
|
+
const defaultExportedSymbol = getDefaultExportedSymbol(archUiConfigSource);
|
|
71
|
+
(0, vitest_1.expect)(importedSymbol).toBeTruthy();
|
|
72
|
+
(0, vitest_1.expect)(defaultExportedSymbol).toBeTruthy();
|
|
73
|
+
(0, vitest_1.expect)(defaultExportedSymbol).toBe(importedSymbol);
|
|
74
|
+
(0, vitest_1.expect)(importedSymbol).toBe("nextJsConfig");
|
|
75
|
+
});
|
|
76
|
+
(0, vitest_1.it)("includes all foundation document templates with structured guidance", () => {
|
|
77
|
+
for (const expectation of foundationTemplateExpectations) {
|
|
78
|
+
const templatePath = node_path_1.default.join(foundationTemplateDir, expectation.fileName);
|
|
79
|
+
(0, vitest_1.expect)(node_fs_1.default.existsSync(templatePath)).toBe(true);
|
|
80
|
+
const templateSource = node_fs_1.default.readFileSync(templatePath, "utf8");
|
|
81
|
+
(0, vitest_1.expect)(templateSource).toContain(expectation.sectionHeader);
|
|
82
|
+
(0, vitest_1.expect)(templateSource).toContain("<!-- Guidance:");
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
(0, vitest_1.it)("includes domain spec templates with ownership and milestone mapping guidance", () => {
|
|
86
|
+
const domainsJsonPath = node_path_1.default.join(domainTemplateDir, "domains.json");
|
|
87
|
+
const domainTemplatePath = node_path_1.default.join(domainTemplateDir, "DOMAIN_TEMPLATE.md");
|
|
88
|
+
const domainReadmePath = node_path_1.default.join(domainTemplateDir, "README.md");
|
|
89
|
+
(0, vitest_1.expect)(node_fs_1.default.existsSync(domainReadmePath)).toBe(true);
|
|
90
|
+
(0, vitest_1.expect)(node_fs_1.default.readFileSync(domainReadmePath, "utf8")).toContain("domain boundaries");
|
|
91
|
+
(0, vitest_1.expect)(node_fs_1.default.existsSync(domainsJsonPath)).toBe(true);
|
|
92
|
+
const domainsJson = JSON.parse(node_fs_1.default.readFileSync(domainsJsonPath, "utf8"));
|
|
93
|
+
(0, vitest_1.expect)(Array.isArray(domainsJson.domains)).toBe(true);
|
|
94
|
+
(0, vitest_1.expect)(domainsJson.domains?.map((domain) => domain.name)).toEqual(["core", "ui", "api"]);
|
|
95
|
+
(0, vitest_1.expect)(node_fs_1.default.existsSync(domainTemplatePath)).toBe(true);
|
|
96
|
+
const domainTemplateSource = node_fs_1.default.readFileSync(domainTemplatePath, "utf8");
|
|
97
|
+
(0, vitest_1.expect)(domainTemplateSource).toContain("## Responsibilities");
|
|
98
|
+
(0, vitest_1.expect)(domainTemplateSource).toContain("## Primary Data Ownership");
|
|
99
|
+
(0, vitest_1.expect)(domainTemplateSource).toContain("## Interface Contracts");
|
|
100
|
+
(0, vitest_1.expect)(domainTemplateSource).toContain("## Non-Goals");
|
|
101
|
+
(0, vitest_1.expect)(domainTemplateSource).toContain("## Milestone Mapping");
|
|
102
|
+
for (const expectation of domainTemplateExpectations) {
|
|
103
|
+
const templatePath = node_path_1.default.join(domainTemplateDir, expectation.fileName);
|
|
104
|
+
(0, vitest_1.expect)(node_fs_1.default.existsSync(templatePath)).toBe(true);
|
|
105
|
+
const templateSource = node_fs_1.default.readFileSync(templatePath, "utf8");
|
|
106
|
+
(0, vitest_1.expect)(templateSource).toContain(expectation.sectionHeader);
|
|
107
|
+
(0, vitest_1.expect)(templateSource).toContain("## Milestone Mapping");
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
(0, vitest_1.it)("includes reusable architecture spec template and example", () => {
|
|
111
|
+
const specTemplatePath = node_path_1.default.join(architectureSpecTemplateDir, "SPEC_TEMPLATE.md");
|
|
112
|
+
const exampleSpecPath = node_path_1.default.join(architectureSpecTemplateDir, "example-system.md");
|
|
113
|
+
(0, vitest_1.expect)(node_fs_1.default.existsSync(specTemplatePath)).toBe(true);
|
|
114
|
+
const specTemplateSource = node_fs_1.default.readFileSync(specTemplatePath, "utf8");
|
|
115
|
+
for (const section of architectureSpecTemplateSections) {
|
|
116
|
+
(0, vitest_1.expect)(specTemplateSource).toContain(section);
|
|
117
|
+
}
|
|
118
|
+
(0, vitest_1.expect)(specTemplateSource).toContain("<!-- Guidance:");
|
|
119
|
+
(0, vitest_1.expect)(node_fs_1.default.existsSync(exampleSpecPath)).toBe(true);
|
|
120
|
+
const exampleSpecSource = node_fs_1.default.readFileSync(exampleSpecPath, "utf8");
|
|
121
|
+
(0, vitest_1.expect)(exampleSpecSource).toContain("# Example System Specification");
|
|
122
|
+
(0, vitest_1.expect)(exampleSpecSource).toContain("## Purpose");
|
|
123
|
+
(0, vitest_1.expect)(exampleSpecSource).toContain("## MVP Constraints");
|
|
124
|
+
});
|
|
125
|
+
(0, vitest_1.it)("includes concept-map template with traceability schema placeholders", () => {
|
|
126
|
+
(0, vitest_1.expect)(node_fs_1.default.existsSync(conceptMapTemplatePath)).toBe(true);
|
|
127
|
+
const conceptMap = JSON.parse(node_fs_1.default.readFileSync(conceptMapTemplatePath, "utf8"));
|
|
128
|
+
(0, vitest_1.expect)(conceptMap.schemaVersion).toBe("1.0");
|
|
129
|
+
(0, vitest_1.expect)(Array.isArray(conceptMap.concepts)).toBe(true);
|
|
130
|
+
(0, vitest_1.expect)((conceptMap.concepts ?? []).length).toBeGreaterThanOrEqual(2);
|
|
131
|
+
const firstConcept = conceptMap.concepts?.[0] ?? {};
|
|
132
|
+
(0, vitest_1.expect)(firstConcept).toHaveProperty("id");
|
|
133
|
+
(0, vitest_1.expect)(firstConcept).toHaveProperty("name");
|
|
134
|
+
(0, vitest_1.expect)(firstConcept).toHaveProperty("description");
|
|
135
|
+
(0, vitest_1.expect)(firstConcept).toHaveProperty("owningDomain");
|
|
136
|
+
(0, vitest_1.expect)(firstConcept).toHaveProperty("moduleResponsibilities");
|
|
137
|
+
(0, vitest_1.expect)(firstConcept).toHaveProperty("implementationSurfaces");
|
|
138
|
+
(0, vitest_1.expect)(firstConcept).toHaveProperty("dependencies");
|
|
139
|
+
(0, vitest_1.expect)(Array.isArray(conceptMap.domainModuleMapping)).toBe(true);
|
|
140
|
+
(0, vitest_1.expect)(Array.isArray(conceptMap.implementationChecklist)).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
(0, vitest_1.it)("includes decision record template and example with structured frontmatter", () => {
|
|
143
|
+
const decisionTemplatePath = node_path_1.default.join(decisionTemplateDir, "DECISION_TEMPLATE.md");
|
|
144
|
+
const decisionExamplePath = node_path_1.default.join(decisionTemplateDir, "example-decision.md");
|
|
145
|
+
const decisionReadmePath = node_path_1.default.join(decisionTemplateDir, "README.md");
|
|
146
|
+
(0, vitest_1.expect)(node_fs_1.default.existsSync(decisionReadmePath)).toBe(true);
|
|
147
|
+
(0, vitest_1.expect)(node_fs_1.default.readFileSync(decisionReadmePath, "utf8")).toContain("pa decision new");
|
|
148
|
+
(0, vitest_1.expect)(node_fs_1.default.existsSync(decisionTemplatePath)).toBe(true);
|
|
149
|
+
const decisionTemplateSource = node_fs_1.default.readFileSync(decisionTemplatePath, "utf8");
|
|
150
|
+
(0, vitest_1.expect)(decisionTemplateSource).toContain("---");
|
|
151
|
+
(0, vitest_1.expect)(decisionTemplateSource).toContain("id:");
|
|
152
|
+
(0, vitest_1.expect)(decisionTemplateSource).toContain("title:");
|
|
153
|
+
(0, vitest_1.expect)(decisionTemplateSource).toContain("slug:");
|
|
154
|
+
(0, vitest_1.expect)(decisionTemplateSource).toContain("status:");
|
|
155
|
+
(0, vitest_1.expect)(decisionTemplateSource).toContain("createdAt:");
|
|
156
|
+
(0, vitest_1.expect)(decisionTemplateSource).toContain("updatedAt:");
|
|
157
|
+
(0, vitest_1.expect)(decisionTemplateSource).toContain("relatedTasks:");
|
|
158
|
+
(0, vitest_1.expect)(decisionTemplateSource).toContain("relatedDocs:");
|
|
159
|
+
for (const section of decisionTemplateSections) {
|
|
160
|
+
(0, vitest_1.expect)(decisionTemplateSource).toContain(section);
|
|
161
|
+
}
|
|
162
|
+
(0, vitest_1.expect)(node_fs_1.default.existsSync(decisionExamplePath)).toBe(true);
|
|
163
|
+
const decisionExampleSource = node_fs_1.default.readFileSync(decisionExamplePath, "utf8");
|
|
164
|
+
(0, vitest_1.expect)(decisionExampleSource).toContain('status: "accepted"');
|
|
165
|
+
(0, vitest_1.expect)(decisionExampleSource).toContain("## Decision");
|
|
166
|
+
(0, vitest_1.expect)(decisionExampleSource).toContain("## Alternatives Considered");
|
|
167
|
+
});
|
|
168
|
+
(0, vitest_1.it)("includes milestone gap-closure report template and example", () => {
|
|
169
|
+
const gapClosureTemplatePath = node_path_1.default.join(gapClosureTemplateDir, "GAP_CLOSURE_TEMPLATE.md");
|
|
170
|
+
const gapClosureExamplePath = node_path_1.default.join(gapClosureTemplateDir, "example-gap-closure.md");
|
|
171
|
+
const gapClosureReadmePath = node_path_1.default.join(gapClosureTemplateDir, "README.md");
|
|
172
|
+
(0, vitest_1.expect)(node_fs_1.default.existsSync(gapClosureReadmePath)).toBe(true);
|
|
173
|
+
(0, vitest_1.expect)(node_fs_1.default.readFileSync(gapClosureReadmePath, "utf8")).toContain("pa check");
|
|
174
|
+
(0, vitest_1.expect)(node_fs_1.default.existsSync(gapClosureTemplatePath)).toBe(true);
|
|
175
|
+
const gapClosureTemplateSource = node_fs_1.default.readFileSync(gapClosureTemplatePath, "utf8");
|
|
176
|
+
for (const section of gapClosureTemplateSections) {
|
|
177
|
+
(0, vitest_1.expect)(gapClosureTemplateSource).toContain(section);
|
|
178
|
+
}
|
|
179
|
+
(0, vitest_1.expect)(gapClosureTemplateSource).toContain("- [ ]");
|
|
180
|
+
(0, vitest_1.expect)(node_fs_1.default.existsSync(gapClosureExamplePath)).toBe(true);
|
|
181
|
+
const gapClosureExampleSource = node_fs_1.default.readFileSync(gapClosureExamplePath, "utf8");
|
|
182
|
+
(0, vitest_1.expect)(gapClosureExampleSource).toContain("# Milestone Gap-Closure Report - Example");
|
|
183
|
+
(0, vitest_1.expect)(gapClosureExampleSource).toContain("## Layer Synchronization Check");
|
|
184
|
+
(0, vitest_1.expect)(gapClosureExampleSource).toContain("- [x]");
|
|
185
|
+
});
|
|
186
|
+
(0, vitest_1.it)("includes local validation hook script and pre-commit example", () => {
|
|
187
|
+
const validateScriptPath = node_path_1.default.join(validationHookTemplateDir, "scripts", "validate.sh");
|
|
188
|
+
const preCommitPath = node_path_1.default.join(validationHookTemplateDir, ".githooks", "pre-commit");
|
|
189
|
+
const readmePath = node_path_1.default.join(validationHookTemplateDir, "README.md");
|
|
190
|
+
(0, vitest_1.expect)(node_fs_1.default.existsSync(validateScriptPath)).toBe(true);
|
|
191
|
+
const validateScriptSource = node_fs_1.default.readFileSync(validateScriptPath, "utf8");
|
|
192
|
+
(0, vitest_1.expect)(validateScriptSource).toContain("pnpm arch:check");
|
|
193
|
+
(0, vitest_1.expect)(validateScriptSource).toContain("pnpm arch:report");
|
|
194
|
+
(0, vitest_1.expect)(node_fs_1.default.existsSync(preCommitPath)).toBe(true);
|
|
195
|
+
(0, vitest_1.expect)(node_fs_1.default.readFileSync(preCommitPath, "utf8")).toContain("sh scripts/validate.sh");
|
|
196
|
+
(0, vitest_1.expect)(node_fs_1.default.existsSync(readmePath)).toBe(true);
|
|
197
|
+
(0, vitest_1.expect)(node_fs_1.default.readFileSync(readmePath, "utf8")).toContain("Task Verification Guidance");
|
|
198
|
+
});
|
|
8
199
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-project-arch",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Scaffold new projects with Project Arch templates including Next.js apps and component libraries",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"create",
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
"turborepo",
|
|
13
13
|
"monorepo"
|
|
14
14
|
],
|
|
15
|
-
"homepage": "https://github.com/
|
|
15
|
+
"homepage": "https://github.com/MissTitanK3/project-arch-system#readme",
|
|
16
16
|
"bugs": {
|
|
17
|
-
"url": "https://github.com/
|
|
17
|
+
"url": "https://github.com/MissTitanK3/project-arch-system/issues"
|
|
18
18
|
},
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
21
|
-
"url": "git+https://github.com/
|
|
21
|
+
"url": "git+https://github.com/MissTitanK3/project-arch-system.git",
|
|
22
22
|
"directory": "packages/create-project-arch"
|
|
23
23
|
},
|
|
24
24
|
"license": "MIT",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"commander": "^12.1.0",
|
|
39
39
|
"fs-extra": "^11.3.0",
|
|
40
|
-
"project-arch": "^1.
|
|
40
|
+
"project-arch": "^1.3.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/fs-extra": "^11.0.4",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default
|
|
1
|
+
import { nextJsConfig } from '@repo/eslint-config/next-js';
|
|
2
|
+
export default nextJsConfig;
|