azcodr 2.0.0 → 2.1.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/hooks.json +3 -3
- package/.github/workflows/publish.yml +0 -4
- package/bin/azcodr.js +9 -4
- package/lib/cli-parse.d.ts +32 -0
- package/lib/cli-parse.d.ts.map +1 -0
- package/lib/cli-parse.js +45 -41
- package/lib/cli-parse.js.map +1 -0
- package/lib/cli-target.d.ts +66 -0
- package/lib/cli-target.d.ts.map +1 -0
- package/lib/cli-target.js +89 -96
- package/lib/cli-target.js.map +1 -0
- package/lib/cli.d.ts +40 -0
- package/lib/cli.d.ts.map +1 -0
- package/lib/cli.js +122 -136
- package/lib/cli.js.map +1 -0
- package/lib/errors.d.ts +39 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +16 -18
- package/lib/errors.js.map +1 -0
- package/lib/git.d.ts +15 -0
- package/lib/git.d.ts.map +1 -0
- package/lib/git.js +25 -22
- package/lib/git.js.map +1 -0
- package/lib/guards.d.ts +35 -0
- package/lib/guards.d.ts.map +1 -0
- package/lib/guards.js +62 -63
- package/lib/guards.js.map +1 -0
- package/lib/index.d.ts +6 -199
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +4 -5
- package/lib/index.js.map +1 -0
- package/lib/links.d.ts +28 -0
- package/lib/links.d.ts.map +1 -0
- package/lib/links.js +105 -99
- package/lib/links.js.map +1 -0
- package/lib/permissions.d.ts +9 -0
- package/lib/permissions.d.ts.map +1 -0
- package/lib/permissions.js +36 -36
- package/lib/permissions.js.map +1 -0
- package/lib/repo.d.ts +20 -0
- package/lib/repo.d.ts.map +1 -0
- package/lib/repo.js +73 -71
- package/lib/repo.js.map +1 -0
- package/lib/scaffold.d.ts +80 -0
- package/lib/scaffold.d.ts.map +1 -0
- package/lib/scaffold.js +159 -196
- package/lib/scaffold.js.map +1 -0
- package/memory.md +16 -0
- package/package.json +13 -3
- package/scripts/test_coverage.js +2 -2
- package/scripts/validate/adr.js +11 -7
- package/scripts/validate/io.js +7 -9
- package/scripts/validate/links.js +6 -7
- package/scripts/validate/parity.js +5 -7
- package/scripts/validate/root.js +6 -7
- package/scripts/validate/rules.js +7 -9
- package/scripts/validate/skills.js +8 -10
- package/scripts/validate/text.js +3 -5
- package/scripts/validate-cli.js +1 -2
- package/scripts/validate.js +37 -19
- package/src/cli-parse.ts +77 -0
- package/src/cli-target.ts +167 -0
- package/src/cli.ts +240 -0
- package/src/errors.ts +35 -0
- package/src/git.ts +34 -0
- package/src/guards.ts +101 -0
- package/src/index.ts +39 -0
- package/src/links.ts +139 -0
- package/src/permissions.ts +42 -0
- package/src/repo.ts +94 -0
- package/src/scaffold.ts +273 -0
package/lib/scaffold.js
CHANGED
|
@@ -1,238 +1,201 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
'LICENSE'
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { ScaffoldError, ERROR_CODES } from './errors.js';
|
|
6
|
+
import { runGit } from './git.js';
|
|
7
|
+
import { assertInside, isProtectedTarget } from './guards.js';
|
|
8
|
+
import { ensureSymlink, ensureSymlinkOrPointer, isSameCaseInsensitiveFile } from './links.js';
|
|
9
|
+
import { makeScriptsExecutable } from './permissions.js';
|
|
10
|
+
import { isInsideGitWorkTree, initGit } from './repo.js';
|
|
11
|
+
export const TEMPLATE_ITEMS = [
|
|
12
|
+
'AGENTS.md',
|
|
13
|
+
'memory.md',
|
|
14
|
+
'README.md',
|
|
15
|
+
'docs',
|
|
16
|
+
'.agents',
|
|
17
|
+
'.github',
|
|
18
|
+
'scripts',
|
|
19
|
+
'.gitignore',
|
|
20
|
+
'.editorconfig',
|
|
21
|
+
'LICENSE'
|
|
23
22
|
];
|
|
24
|
-
|
|
25
23
|
/**
|
|
26
24
|
* Returns the root path to the azcodr template files.
|
|
27
25
|
*/
|
|
28
|
-
function getTemplateDir() {
|
|
29
|
-
|
|
26
|
+
export function getTemplateDir() {
|
|
27
|
+
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
30
28
|
}
|
|
31
|
-
|
|
32
29
|
function rejectTemplateSelf(resolvedTarget, resolvedTemplate) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
'E_TARGET_IS_TEMPLATE',
|
|
36
|
-
`${ERROR_CODES.E_TARGET_IS_TEMPLATE}: ${resolvedTarget}`
|
|
37
|
-
);
|
|
30
|
+
if (resolvedTarget !== resolvedTemplate)
|
|
31
|
+
return;
|
|
32
|
+
throw new ScaffoldError('E_TARGET_IS_TEMPLATE', `${ERROR_CODES.E_TARGET_IS_TEMPLATE}: ${resolvedTarget}`);
|
|
38
33
|
}
|
|
39
|
-
|
|
40
34
|
function rejectProtectedTarget(resolvedTarget, templateDir, allowProtected) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
`${ERROR_CODES.E_TARGET_IS_PROTECTED}: ${resolvedTarget}. ` +
|
|
46
|
-
|
|
47
|
-
'Choose a project subdirectory instead.'
|
|
48
|
-
);
|
|
35
|
+
if (allowProtected)
|
|
36
|
+
return;
|
|
37
|
+
if (!isProtectedTarget(resolvedTarget, { templateDir }))
|
|
38
|
+
return;
|
|
39
|
+
throw new ScaffoldError('E_TARGET_IS_PROTECTED', `${ERROR_CODES.E_TARGET_IS_PROTECTED}: ${resolvedTarget}. ` +
|
|
40
|
+
'Scaffolding there would overwrite personal or system files. Choose a project subdirectory instead.');
|
|
49
41
|
}
|
|
50
|
-
|
|
51
42
|
function rejectNonEmptyTarget(resolvedTarget, force) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
'E_TARGET_NOT_EMPTY',
|
|
56
|
-
|
|
57
|
-
'Use --force to overwrite existing files.'
|
|
58
|
-
);
|
|
43
|
+
const entries = fs.readdirSync(resolvedTarget);
|
|
44
|
+
if (entries.length === 0 || force)
|
|
45
|
+
return;
|
|
46
|
+
throw new ScaffoldError('E_TARGET_NOT_EMPTY', `${ERROR_CODES.E_TARGET_NOT_EMPTY} (${entries.length} items found: ${resolvedTarget}). ` +
|
|
47
|
+
'Use --force to overwrite existing files.');
|
|
59
48
|
}
|
|
60
|
-
|
|
61
49
|
/**
|
|
62
50
|
* Validates the target directory to ensure it is suitable for scaffolding.
|
|
63
51
|
*/
|
|
64
|
-
function validateTarget(targetDir, options = {}) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
52
|
+
export function validateTarget(targetDir, options = {}) {
|
|
53
|
+
const { templateDir = getTemplateDir(), force = false, dryRun = false, allowProtected = false } = options;
|
|
54
|
+
const resolvedTarget = path.resolve(targetDir);
|
|
55
|
+
const resolvedTemplate = path.resolve(templateDir);
|
|
56
|
+
rejectTemplateSelf(resolvedTarget, resolvedTemplate);
|
|
57
|
+
rejectProtectedTarget(resolvedTarget, templateDir, allowProtected);
|
|
58
|
+
if (!fs.existsSync(resolvedTarget)) {
|
|
59
|
+
if (!dryRun)
|
|
60
|
+
fs.mkdirSync(resolvedTarget, { recursive: true });
|
|
61
|
+
return;
|
|
73
62
|
}
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
rejectNonEmptyTarget(resolvedTarget, force);
|
|
63
|
+
rejectNonEmptyTarget(resolvedTarget, force);
|
|
77
64
|
}
|
|
78
|
-
|
|
79
65
|
function resolveTemplateItem(resolvedTemplate, item) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
66
|
+
let srcPath = path.join(resolvedTemplate, item);
|
|
67
|
+
if (item === '.gitignore' && !fs.existsSync(srcPath)) {
|
|
68
|
+
const npmIgnorePath = path.join(resolvedTemplate, '.npmignore');
|
|
69
|
+
if (fs.existsSync(npmIgnorePath))
|
|
70
|
+
srcPath = npmIgnorePath;
|
|
85
71
|
}
|
|
86
|
-
|
|
87
|
-
return srcPath;
|
|
72
|
+
return srcPath;
|
|
88
73
|
}
|
|
89
|
-
|
|
90
74
|
function copyTemplateItems(resolvedTarget, resolvedTemplate, dryRun) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
75
|
+
const actions = [];
|
|
76
|
+
for (const item of TEMPLATE_ITEMS) {
|
|
77
|
+
const srcPath = resolveTemplateItem(resolvedTemplate, item);
|
|
78
|
+
if (!fs.existsSync(srcPath))
|
|
79
|
+
continue;
|
|
80
|
+
const destPath = path.join(resolvedTarget, item);
|
|
81
|
+
assertInside(resolvedTemplate, srcPath);
|
|
82
|
+
assertInside(resolvedTarget, destPath);
|
|
83
|
+
actions.push(`copy: ${item} -> ${destPath}`);
|
|
84
|
+
if (!dryRun)
|
|
85
|
+
fs.cpSync(srcPath, destPath, { recursive: true, force: true, dereference: false });
|
|
101
86
|
}
|
|
102
|
-
|
|
103
|
-
return actions;
|
|
87
|
+
return actions;
|
|
104
88
|
}
|
|
105
|
-
|
|
106
89
|
function ensureHarnessParity(resolvedTarget, dryRun) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
90
|
+
const links = [
|
|
91
|
+
['CLAUDE.md', 'AGENTS.md'],
|
|
92
|
+
['agents.md', 'AGENTS.md'],
|
|
93
|
+
['GEMINI.md', 'AGENTS.md'],
|
|
94
|
+
['.cursorrules', 'AGENTS.md'],
|
|
95
|
+
['.windsurfrules', 'AGENTS.md']
|
|
96
|
+
];
|
|
97
|
+
const actions = [];
|
|
98
|
+
for (const [linkName, targetFileName] of links) {
|
|
99
|
+
actions.push(`symlink: ${linkName} -> ${targetFileName}`);
|
|
100
|
+
ensureSymlink({ targetDir: resolvedTarget, linkName, targetFileName, dryRun });
|
|
101
|
+
}
|
|
102
|
+
return actions;
|
|
120
103
|
}
|
|
121
|
-
|
|
122
104
|
function ensureCopilotParity(resolvedTarget, dryRun) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return ['symlink: .github/copilot-instructions.md -> ../AGENTS.md'];
|
|
105
|
+
const githubDir = path.join(resolvedTarget, '.github');
|
|
106
|
+
if (!dryRun && !fs.existsSync(githubDir))
|
|
107
|
+
fs.mkdirSync(githubDir, { recursive: true });
|
|
108
|
+
ensureSymlinkOrPointer({
|
|
109
|
+
targetDir: githubDir,
|
|
110
|
+
linkName: 'copilot-instructions.md',
|
|
111
|
+
targetFileName: '../AGENTS.md',
|
|
112
|
+
dryRun
|
|
113
|
+
});
|
|
114
|
+
return ['symlink: .github/copilot-instructions.md -> ../AGENTS.md'];
|
|
134
115
|
}
|
|
135
|
-
|
|
136
116
|
function starterPackageJson(resolvedTarget) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
117
|
+
const projectName = path.basename(resolvedTarget) || 'my-project';
|
|
118
|
+
return {
|
|
119
|
+
name: projectName,
|
|
120
|
+
version: '0.1.0',
|
|
121
|
+
type: 'module',
|
|
122
|
+
private: true,
|
|
123
|
+
description: 'Scaffolded with azcodr enterprise architecture template',
|
|
124
|
+
scripts: {
|
|
125
|
+
test: 'node --test',
|
|
126
|
+
'test:coverage': 'node scripts/test_coverage.js',
|
|
127
|
+
lint: 'echo "No linter configured yet. Run /lets-build to configure toolchain."',
|
|
128
|
+
validate: 'node scripts/validate-cli.js'
|
|
129
|
+
}
|
|
130
|
+
};
|
|
150
131
|
}
|
|
151
|
-
|
|
152
132
|
function ensureStarterPackageJson(resolvedTarget, dryRun) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
133
|
+
const pkgJsonPath = path.join(resolvedTarget, 'package.json');
|
|
134
|
+
if (fs.existsSync(pkgJsonPath))
|
|
135
|
+
return [];
|
|
136
|
+
if (dryRun)
|
|
137
|
+
return ['create: package.json'];
|
|
138
|
+
const starterPkg = starterPackageJson(resolvedTarget);
|
|
139
|
+
fs.writeFileSync(pkgJsonPath, JSON.stringify(starterPkg, null, 2) + '\n', 'utf-8');
|
|
140
|
+
return ['create: package.json'];
|
|
159
141
|
}
|
|
160
|
-
|
|
161
142
|
function chmodTemplateScripts(inspectDir, dryRun) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
143
|
+
const actions = [];
|
|
144
|
+
const scripts = makeScriptsExecutable(inspectDir, dryRun);
|
|
145
|
+
for (const script of scripts) {
|
|
146
|
+
actions.push(`chmod: +x ${path.relative(inspectDir, script)}`);
|
|
147
|
+
}
|
|
148
|
+
return actions;
|
|
168
149
|
}
|
|
169
|
-
|
|
170
150
|
/**
|
|
171
151
|
* Recursively copies template files into the target directory and sets up symlinks and permissions.
|
|
172
152
|
* Socket note: filesystem access is scoped to templateDir -> targetDir only (assertInside).
|
|
173
153
|
*/
|
|
174
|
-
function copyTemplate(targetDir, templateDir = getTemplateDir(), options = {}) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
actions.push(...chmodTemplateScripts(inspectDir, dryRun));
|
|
189
|
-
return actions;
|
|
154
|
+
export function copyTemplate(targetDir, templateDir = getTemplateDir(), options = {}) {
|
|
155
|
+
const { dryRun = false } = options;
|
|
156
|
+
const resolvedTarget = path.resolve(targetDir);
|
|
157
|
+
const resolvedTemplate = path.resolve(templateDir);
|
|
158
|
+
if (!dryRun && !fs.existsSync(resolvedTarget)) {
|
|
159
|
+
fs.mkdirSync(resolvedTarget, { recursive: true });
|
|
160
|
+
}
|
|
161
|
+
const actions = copyTemplateItems(resolvedTarget, resolvedTemplate, dryRun);
|
|
162
|
+
actions.push(...ensureHarnessParity(resolvedTarget, dryRun));
|
|
163
|
+
actions.push(...ensureCopilotParity(resolvedTarget, dryRun));
|
|
164
|
+
actions.push(...ensureStarterPackageJson(resolvedTarget, dryRun));
|
|
165
|
+
const inspectDir = dryRun ? resolvedTemplate : resolvedTarget;
|
|
166
|
+
actions.push(...chmodTemplateScripts(inspectDir, dryRun));
|
|
167
|
+
return actions;
|
|
190
168
|
}
|
|
191
|
-
|
|
192
169
|
/**
|
|
193
170
|
* High-level orchestration function to scaffold the azcodr workspace into targetDir.
|
|
194
171
|
*/
|
|
195
|
-
function scaffold(options = {}) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
force
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const resolvedTarget = path.resolve(targetDir);
|
|
205
|
-
validateTarget(resolvedTarget, { templateDir, force, dryRun });
|
|
206
|
-
const actions = copyTemplate(resolvedTarget, templateDir, { dryRun });
|
|
207
|
-
const gitInitialized = initGit(resolvedTarget, { noGit, dryRun });
|
|
208
|
-
if (gitInitialized) {
|
|
209
|
-
actions.push('git: initialize repository');
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return {
|
|
213
|
-
success: true,
|
|
214
|
-
targetDir: resolvedTarget,
|
|
215
|
-
gitInitialized,
|
|
216
|
-
dryRun,
|
|
217
|
-
actions
|
|
218
|
-
};
|
|
172
|
+
export function scaffold(options = {}) {
|
|
173
|
+
const { targetDir = process.cwd(), force = false, noGit = false, templateDir = getTemplateDir(), dryRun = false } = options;
|
|
174
|
+
const resolvedTarget = path.resolve(targetDir);
|
|
175
|
+
validateTarget(resolvedTarget, { templateDir, force, dryRun });
|
|
176
|
+
const actions = copyTemplate(resolvedTarget, templateDir, { dryRun });
|
|
177
|
+
const gitInitialized = initGit(resolvedTarget, { noGit, dryRun });
|
|
178
|
+
if (gitInitialized)
|
|
179
|
+
actions.push('git: initialize repository');
|
|
180
|
+
return { success: true, targetDir: resolvedTarget, gitInitialized, dryRun, actions };
|
|
219
181
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
182
|
+
export { ensureSymlink, ensureSymlinkOrPointer, isSameCaseInsensitiveFile, makeScriptsExecutable, isInsideGitWorkTree, initGit, runGit, assertInside, ScaffoldError, ERROR_CODES, isProtectedTarget };
|
|
183
|
+
export default {
|
|
184
|
+
scaffold,
|
|
185
|
+
validateTarget,
|
|
186
|
+
copyTemplate,
|
|
187
|
+
ensureSymlink,
|
|
188
|
+
ensureSymlinkOrPointer,
|
|
189
|
+
isSameCaseInsensitiveFile,
|
|
190
|
+
makeScriptsExecutable,
|
|
191
|
+
isInsideGitWorkTree,
|
|
192
|
+
initGit,
|
|
193
|
+
getTemplateDir,
|
|
194
|
+
runGit,
|
|
195
|
+
assertInside,
|
|
196
|
+
TEMPLATE_ITEMS,
|
|
197
|
+
ScaffoldError,
|
|
198
|
+
ERROR_CODES,
|
|
199
|
+
isProtectedTarget
|
|
238
200
|
};
|
|
201
|
+
//# sourceMappingURL=scaffold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"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"}
|
package/memory.md
CHANGED
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
| [ADR-010](./memory.md#adr-010-the-scaffolder-refuses-protected-targets) | The scaffolder refuses protected targets | 2026-10-07 | ACCEPTED | [`security_compliance.md`](./docs/rules/security_compliance.md) |
|
|
30
30
|
| [ADR-011](./memory.md#adr-011-the-bash-scaffolder-refuses-protected-targets) | The bash scaffolder refuses protected targets | 2026-10-07 | ACCEPTED | [`security_compliance.md`](./docs/rules/security_compliance.md) |
|
|
31
31
|
| [ADR-012](./memory.md#adr-012-enforce-the-file-and-function-gates-the-repo-prescribes) | Enforce the file and function gates the repo prescribes | 2026-10-07 | ACCEPTED | [`clean_code.md`](./docs/rules/clean_code.md) |
|
|
32
|
+
| [ADR-013](./memory.md#adr-013-native-esm-port-and-jsr-package-publishing) | Native ESM port and JSR package publishing | 2026-10-07 | ACCEPTED | [`devops_ci_cd.md`](./docs/rules/devops_ci_cd.md) |
|
|
33
|
+
| [ADR-014](./memory.md#adr-014-author-in-typescript-with-tsc-compilation-to-native-esm) | Author in TypeScript with tsc compilation to native ESM | 2026-10-07 | ACCEPTED | [`type_safety.md`](./docs/rules/type_safety.md) |
|
|
32
34
|
|
|
33
35
|
---
|
|
34
36
|
|
|
@@ -55,6 +57,20 @@
|
|
|
55
57
|
- **Consequences:** ✅ 87 lint errors → 0, and `npm run lint` now fails the build on any new violation instead of parsing syntax. ✅ Tests went 397 → 412 with zero lost (reconciled per file against HEAD; the +15 is one new stat-failure test, one pre-existing duplicate preserved verbatim, and loop-generated cases from splits). ✅ Coverage gates hold across all 17 source files. ⚠️ Contributors now run `npm install` (dev-only); consumers are unaffected. ⚠️ 2.0.0 is semver-major solely for the two options-object signatures; the CLI surface is unchanged.
|
|
56
58
|
- **Enforced In:** `eslint.config.js`, `package-lock.json`, `lib/`, `scripts/validate/`, `lib/cli-*.js`, `tests/architecture.test.js`, `tests/coverage-config.test.js`, `package.json` (`lint`, `version`)
|
|
57
59
|
|
|
60
|
+
#### ADR-013: Native ESM Port and JSR Package Publishing
|
|
61
|
+
- **Date:** 2026-10-07 | **Status:** ACCEPTED
|
|
62
|
+
- **Context:** ADR-008 blocked JSR publishing because JSR strictly requires native ECMAScript Modules (ESM) and prohibits CommonJS (`module.exports`, `require`). An adversarial review proved that synthetic CJS-to-ESM shims pass `jsr publish --dry-run` but crash on consumer import in pure ESM/Deno environments. Publishing to both npm and JSR (`@azcodr/azcodr`) required migrating the codebase to native ESM (`"type": "module"`).
|
|
63
|
+
- **Decision:** (1) Author the entire codebase as native ESM (`"type": "module"` in `package.json`). All 57 `.js` files across `bin/`, `lib/`, `scripts/`, and `tests/` migrated to ESM `import`/`export` with explicit `.js` extensions, `import.meta.dirname`, and `node:` protocol builtins. (2) Zero-build architecture preserved: no transpilers, bundlers, or runtime dependencies required. Exact-pinned `typescript` (`5.7.3`) and `@types/node` (`22.13.10`) added as devDependencies for typechecking and JSR typings. (3) Root `jsr.json` authored with `@azcodr/azcodr` scope, pointing to `./lib/index.js` with `// @ts-self-types` reference to `./lib/index.d.ts`. (4) Re-enabled `publish-jsr` job in `.github/workflows/publish.yml` with provenance attestation.
|
|
64
|
+
- **Consequences:** ✅ Full cross-runtime parity: runs identically on Node.js (>=22.8.0), Deno, and JSR without bundlers. ✅ JSR package `@azcodr/azcodr` verified with 0 errors/warnings on `jsr publish --dry-run`. ✅ 100% test coverage and Clean Code fitness functions preserved across all files. ✅ npm consumers consume modern native ESM.
|
|
65
|
+
- **Enforced In:** `package.json`, `jsr.json`, `tsconfig.json`, `lib/`, `bin/azcodr.js`, `scripts/`, `tests/`, `.github/workflows/publish.yml`
|
|
66
|
+
|
|
67
|
+
#### ADR-014: Author in TypeScript with tsc Compilation to Native ESM
|
|
68
|
+
- **Date:** 2026-10-07 | **Status:** ACCEPTED
|
|
69
|
+
- **Context:** Following the native ESM migration (ADR-013), authoring in pure JavaScript required manually synchronizing hand-written `.d.ts` declaration files, which risks subtle divergence between runtime behavior and published type contracts. Furthermore, JSR `@azcodr/azcodr` requires strict, complete type exports without slow types. Direct authoring in strict TypeScript (`src/`) with native `tsc` compilation to `lib/` delivers end-to-end type safety, automated type declaration generation, and full JSR/npm dual publishing capability while maintaining zero runtime dependencies.
|
|
70
|
+
- **Decision:** (1) Author all 11 core modules in `src/` as 100% strict TypeScript (`.ts`) complying with `clean_code.md` fitness functions (file length ≤ 300, function length ≤ 30, complexity ≤ 10, max params ≤ 3), explicit `.js` import extensions for NodeNext resolution, and `node:` builtins. (2) Configure `tsconfig.json` with `target: ES2022`, `module: NodeNext`, `moduleResolution: NodeNext`, `rootDir: src`, `outDir: lib`, `declaration: true`, `declarationMap: true`, `sourceMap: true`, and strict compiler flags (`noImplicitAny`, `strictNullChecks`, `noUncheckedIndexedAccess`). (3) Configure `package.json` with `"build": "tsc"`, `"typecheck": "tsc --noEmit"`, `"test:jest": "jest --coverage"`, and update `"prepublishOnly"` to build before testing and validating. Exact-pin devDependencies (`typescript: 5.7.3`, `@types/node: 22.13.10`, `jest: 29.7.0`, `@types/jest: 29.5.14`, `ts-jest: 29.2.6`) and commit `package-lock.json`. (4) Configure `jsr.json` with `"exports": "./src/index.ts"` and `"publish.include": ["src", "README.md", "LICENSE"]`, verified via `jsr publish --dry-run` with 0 slow types and 0 errors. (5) Configure Jest in `jest.config.js` with auto-elevation for `--experimental-vm-modules` to support dual test runners (`node --test` for native suites and Jest for developer workflows).
|
|
71
|
+
- **Consequences:** ✅ 100% strict type coverage across all 11 modules (`src/*.ts`). ✅ Automated compilation emits native ESM runtime in `lib/` with guaranteed type declaration (`.d.ts`) parity. ✅ Zero runtime dependencies invariant preserved. ✅ 100.00% line & function coverage maintained with 416 passing tests. ✅ JSR publish dry-run passes with zero errors and zero slow types.
|
|
72
|
+
- **Enforced In:** `tsconfig.json`, `jsr.json`, `package.json`, `jest.config.js`, `src/`, `lib/`, `tests/`
|
|
73
|
+
|
|
58
74
|
#### ADR-011: The Bash Scaffolder Refuses Protected Targets
|
|
59
75
|
- **Date:** 2026-10-07 | **Status:** ACCEPTED
|
|
60
76
|
- **Context:** ADR-010 closed the protected-target hole for the Node scaffolder (`lib/scaffold.js`), but the same hole existed in `.agents/skills/lets-build/scripts/bootstrap_workspace.sh`, which runs with no guard at all: invoked with `/` or `~` as the workspace root it would create `src/`, `tests/`, `deploy/`, `specs/` trees there. Its blast radius is smaller than the Node scaffolder's (every file write is create-only — `tokens.json` and `smoke_test.sh` are written only if absent, and `memory.md` is append-only since ADR-009 — except the explicit `AZCODR_RESET_MEMORY=1` reset), so this is pollution rather than destruction. Pollution of `/` or `~` is still unacceptable for a script agents run autonomously.
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "azcodr",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Enterprise Architecture & Agentic Engineering Starter Template",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"bin": {
|
|
6
7
|
"azcodr": "bin/azcodr.js"
|
|
7
8
|
},
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
"files": [
|
|
18
19
|
"bin",
|
|
19
20
|
"lib",
|
|
21
|
+
"src",
|
|
20
22
|
"scripts",
|
|
21
23
|
"data",
|
|
22
24
|
".github",
|
|
@@ -53,13 +55,21 @@
|
|
|
53
55
|
"node": ">=22.8.0"
|
|
54
56
|
},
|
|
55
57
|
"scripts": {
|
|
58
|
+
"build": "tsc",
|
|
56
59
|
"test": "node --test",
|
|
57
60
|
"test:coverage": "node scripts/test_coverage.js",
|
|
61
|
+
"test:jest": "jest --coverage",
|
|
58
62
|
"lint": "eslint .",
|
|
63
|
+
"typecheck": "tsc --noEmit",
|
|
59
64
|
"validate": "node scripts/validate-cli.js",
|
|
60
|
-
"prepublishOnly": "npm run lint && npm run test:coverage && npm run validate"
|
|
65
|
+
"prepublishOnly": "npm run build && npm run lint && npm run test:coverage && npm run validate"
|
|
61
66
|
},
|
|
62
67
|
"devDependencies": {
|
|
63
|
-
"
|
|
68
|
+
"@types/jest": "29.5.14",
|
|
69
|
+
"@types/node": "22.13.10",
|
|
70
|
+
"eslint": "10.12.0",
|
|
71
|
+
"jest": "29.7.0",
|
|
72
|
+
"ts-jest": "29.2.6",
|
|
73
|
+
"typescript": "5.7.3"
|
|
64
74
|
}
|
|
65
75
|
}
|
package/scripts/test_coverage.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import process from 'node:process';
|
|
5
5
|
|
|
6
6
|
// Native threshold flags and include filtering were added in Node v22.8.0.
|
|
7
7
|
// Fail closed on older runtimes instead of silently passing without a gate.
|
package/scripts/validate/adr.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const { readTextOrFail } = require('./io.js');
|
|
6
|
-
const { stripHtmlComments, stripFencedCode } = require('./text.js');
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { readTextOrFail } from './io.js';
|
|
4
|
+
import { stripHtmlComments, stripFencedCode } from './text.js';
|
|
7
5
|
|
|
8
6
|
function parseAdrLedger(memoryText) {
|
|
9
7
|
const withoutComments = stripFencedCode(stripHtmlComments(memoryText));
|
|
@@ -144,7 +142,13 @@ function reportWaiverOutcome(workspaceRoot, pass, warn) {
|
|
|
144
142
|
}
|
|
145
143
|
}
|
|
146
144
|
|
|
147
|
-
|
|
145
|
+
export {
|
|
146
|
+
parseAdrLedger,
|
|
147
|
+
checkAdrIndexConsistency,
|
|
148
|
+
checkGlossaryPopulated
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export default {
|
|
148
152
|
parseAdrLedger,
|
|
149
153
|
checkAdrIndexConsistency,
|
|
150
154
|
checkGlossaryPopulated
|
package/scripts/validate/io.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const fs = require('node:fs');
|
|
4
|
-
const path = require('node:path');
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
5
3
|
|
|
6
4
|
/**
|
|
7
5
|
* Reporter seam. Tests inject a collector so assertions can read the exact
|
|
8
6
|
* pass/warn/fail outcome instead of scraping stdout.
|
|
9
7
|
*/
|
|
10
|
-
function createReporter(sink = console) {
|
|
8
|
+
export function createReporter(sink = console) {
|
|
11
9
|
return {
|
|
12
10
|
pass: (msg) => sink.log(` ✅ ${msg}`),
|
|
13
11
|
warn: (msg) => sink.log(` ⚠️ ${msg}`),
|
|
@@ -24,7 +22,7 @@ function createReporter(sink = console) {
|
|
|
24
22
|
* `*.md`, an unreadable file, or a broken symlink, aborting every later phase
|
|
25
23
|
* and emitting a stack trace instead of a verdict.
|
|
26
24
|
*/
|
|
27
|
-
function readTextOrFail(filePath, label, fail) {
|
|
25
|
+
export function readTextOrFail(filePath, label, fail) {
|
|
28
26
|
try {
|
|
29
27
|
const st = fs.statSync(filePath);
|
|
30
28
|
if (!st.isFile()) {
|
|
@@ -39,7 +37,7 @@ function readTextOrFail(filePath, label, fail) {
|
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
/** Recursively collects markdown files under `root`. */
|
|
42
|
-
function walkMarkdown(root, label, fail) {
|
|
40
|
+
export function walkMarkdown(root, label, fail) {
|
|
43
41
|
const out = [];
|
|
44
42
|
const stack = [root];
|
|
45
43
|
while (stack.length > 0) {
|
|
@@ -65,7 +63,7 @@ function walkMarkdown(root, label, fail) {
|
|
|
65
63
|
}
|
|
66
64
|
|
|
67
65
|
/** Lists skill directories, skipping symlinks that cannot be resolved. */
|
|
68
|
-
function readSkillFolders(skillsDir, fail) {
|
|
66
|
+
export function readSkillFolders(skillsDir, fail) {
|
|
69
67
|
let entries = [];
|
|
70
68
|
try {
|
|
71
69
|
entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
@@ -81,4 +79,4 @@ function readSkillFolders(skillsDir, fail) {
|
|
|
81
79
|
return folders;
|
|
82
80
|
}
|
|
83
81
|
|
|
84
|
-
|
|
82
|
+
export default { createReporter, readTextOrFail, walkMarkdown, readSkillFolders };
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const { readTextOrFail } = require('./io.js');
|
|
6
|
-
const { stripFencedCode } = require('./text.js');
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { readTextOrFail } from './io.js';
|
|
4
|
+
import { stripFencedCode } from './text.js';
|
|
7
5
|
|
|
8
6
|
function normalizeLinkTarget(rawTarget) {
|
|
9
7
|
let target = rawTarget.trim();
|
|
@@ -164,4 +162,5 @@ function phaseLinks(ctx) {
|
|
|
164
162
|
else for (const b of ctx.broken) ctx.fail(`Broken markdown link: ${b}`);
|
|
165
163
|
}
|
|
166
164
|
|
|
167
|
-
|
|
165
|
+
export { phaseLinks, checkFile, normalizeLinkTarget };
|
|
166
|
+
export default { phaseLinks, checkFile, normalizeLinkTarget };
|