azcodr 1.0.0 â 1.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/skills/agentic-architect/scripts/validate_agentic_configs.sh +24 -8
- package/.editorconfig +19 -0
- package/.gitignore +3 -0
- package/AGENTS.md +1 -2
- package/README.md +3 -3
- package/bin/azcodr.js +215 -64
- package/changes.md +50 -0
- package/docs/rules/upstream_synchronization.md +28 -26
- package/lib/index.d.ts +153 -0
- package/lib/scaffold.js +132 -24
- package/package.json +15 -4
- package/.agents/skills/merge-ai/SKILL.md +0 -90
- package/.agents/skills/merge-ai/scripts/audit_divergence.sh +0 -108
- package/.agents/skills/merge-ai/scripts/resolve_repo.sh +0 -177
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enterprise Multi-Tenant Architecture & Agentic Engineering Starter Template
|
|
3
|
+
* Programmatic API Definitions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface ScaffoldOptions {
|
|
7
|
+
/** Target directory path where template should be scaffolded (default: process.cwd()) */
|
|
8
|
+
targetDir?: string;
|
|
9
|
+
/** Force overwrite if target directory is non-empty (default: false) */
|
|
10
|
+
force?: boolean;
|
|
11
|
+
/** Skip git repository initialization (default: false) */
|
|
12
|
+
noGit?: boolean;
|
|
13
|
+
/** Custom template root directory (default: azcodr root) */
|
|
14
|
+
templateDir?: string;
|
|
15
|
+
/** Simulate scaffolding without writing files or running git (default: false) */
|
|
16
|
+
dryRun?: boolean;
|
|
17
|
+
/** Suppress console output messages (default: false) */
|
|
18
|
+
silent?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ScaffoldResult {
|
|
22
|
+
/** Whether the scaffolding succeeded */
|
|
23
|
+
success: boolean;
|
|
24
|
+
/** Absolute resolved path of the target directory */
|
|
25
|
+
targetDir: string;
|
|
26
|
+
/** Whether git init was successfully executed */
|
|
27
|
+
gitInitialized: boolean;
|
|
28
|
+
/** Whether execution ran in dry-run simulation mode */
|
|
29
|
+
dryRun: boolean;
|
|
30
|
+
/** List of files, directories, and symlinks created or simulated */
|
|
31
|
+
actions: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface LogChangeOptions {
|
|
35
|
+
/** Short title describing the architectural change */
|
|
36
|
+
title: string;
|
|
37
|
+
/** Category of change (Architecture | Rule | Skill | Infrastructure | CLI | Knowledge Hub) */
|
|
38
|
+
category?: string;
|
|
39
|
+
/** Target file(s) affected by the change */
|
|
40
|
+
targetFiles?: string;
|
|
41
|
+
/** Architectural rationale for upstream template incorporation */
|
|
42
|
+
rationale?: string;
|
|
43
|
+
/** Detailed description of the change */
|
|
44
|
+
description?: string;
|
|
45
|
+
/** Working directory containing changes.md (default: process.cwd()) */
|
|
46
|
+
targetDir?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface LogChangeResult {
|
|
50
|
+
/** Whether the log entry was successfully recorded */
|
|
51
|
+
success: boolean;
|
|
52
|
+
/** Absolute path to changes.md */
|
|
53
|
+
filePath: string;
|
|
54
|
+
/** Markdown entry text that was appended */
|
|
55
|
+
entry: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ValidateTargetOptions {
|
|
59
|
+
/** Custom template root directory */
|
|
60
|
+
templateDir?: string;
|
|
61
|
+
/** Force allow non-empty directory */
|
|
62
|
+
force?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface InitGitOptions {
|
|
66
|
+
/** If true, skips git initialization */
|
|
67
|
+
noGit?: boolean;
|
|
68
|
+
/** If true, simulates git initialization without executing */
|
|
69
|
+
dryRun?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface CopyTemplateOptions {
|
|
73
|
+
/** If true, simulates copy without writing to disk */
|
|
74
|
+
dryRun?: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* High-level orchestration function to scaffold the azcodr workspace into targetDir.
|
|
79
|
+
*/
|
|
80
|
+
export function scaffold(options?: ScaffoldOptions): ScaffoldResult;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Appends a standardized upstream change entry to changes.md.
|
|
84
|
+
*/
|
|
85
|
+
export function logChange(options: LogChangeOptions): LogChangeResult;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Validates the target directory to ensure it is suitable for scaffolding.
|
|
89
|
+
*/
|
|
90
|
+
export function validateTarget(targetDir: string, options?: ValidateTargetOptions): void;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Copies template items into target directory and sets up symlinks and permissions.
|
|
94
|
+
*/
|
|
95
|
+
export function copyTemplate(
|
|
96
|
+
targetDir: string,
|
|
97
|
+
templateDir?: string,
|
|
98
|
+
options?: CopyTemplateOptions
|
|
99
|
+
): string[];
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Safely creates or updates a symbolic link, falling back to a file copy if symlinks are unsupported.
|
|
103
|
+
*/
|
|
104
|
+
export function ensureSymlink(
|
|
105
|
+
targetDir: string,
|
|
106
|
+
linkName: string,
|
|
107
|
+
targetFileName: string,
|
|
108
|
+
dryRun?: boolean
|
|
109
|
+
): boolean;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Detects whether linkName and targetFileName refer to the same entry on a case-insensitive filesystem.
|
|
113
|
+
*/
|
|
114
|
+
export function isSameCaseInsensitiveFile(
|
|
115
|
+
targetDir: string,
|
|
116
|
+
linkName: string,
|
|
117
|
+
targetFileName: string
|
|
118
|
+
): boolean;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Ensures all bash scripts in skill directories have executable permissions (0o755).
|
|
122
|
+
*/
|
|
123
|
+
export function makeScriptsExecutable(targetDir: string, dryRun?: boolean): string[];
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Initializes a git repository in the target directory if not already inside one.
|
|
127
|
+
*/
|
|
128
|
+
export function initGit(targetDir: string, options?: InitGitOptions): boolean;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Returns the root path to the azcodr template files.
|
|
132
|
+
*/
|
|
133
|
+
export function getTemplateDir(): string;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Array of essential template files and directories copied during scaffolding.
|
|
137
|
+
*/
|
|
138
|
+
export const TEMPLATE_ITEMS: readonly string[];
|
|
139
|
+
|
|
140
|
+
declare const defaultExport: {
|
|
141
|
+
scaffold: typeof scaffold;
|
|
142
|
+
logChange: typeof logChange;
|
|
143
|
+
validateTarget: typeof validateTarget;
|
|
144
|
+
copyTemplate: typeof copyTemplate;
|
|
145
|
+
ensureSymlink: typeof ensureSymlink;
|
|
146
|
+
isSameCaseInsensitiveFile: typeof isSameCaseInsensitiveFile;
|
|
147
|
+
makeScriptsExecutable: typeof makeScriptsExecutable;
|
|
148
|
+
initGit: typeof initGit;
|
|
149
|
+
getTemplateDir: typeof getTemplateDir;
|
|
150
|
+
TEMPLATE_ITEMS: typeof TEMPLATE_ITEMS;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export default defaultExport;
|
package/lib/scaffold.js
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const path = require('node:path');
|
|
5
|
-
const
|
|
5
|
+
const cp = require('node:child_process');
|
|
6
6
|
|
|
7
7
|
const TEMPLATE_ITEMS = [
|
|
8
8
|
'AGENTS.md',
|
|
9
9
|
'memory.md',
|
|
10
|
+
'changes.md',
|
|
10
11
|
'README.md',
|
|
11
12
|
'docs',
|
|
12
13
|
'.agents',
|
|
13
|
-
'.gitignore'
|
|
14
|
+
'.gitignore',
|
|
15
|
+
'.editorconfig'
|
|
14
16
|
];
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -24,7 +26,7 @@ function getTemplateDir() {
|
|
|
24
26
|
* Validates the target directory to ensure it is suitable for scaffolding.
|
|
25
27
|
*/
|
|
26
28
|
function validateTarget(targetDir, options = {}) {
|
|
27
|
-
const { templateDir = getTemplateDir(), force = false } = options;
|
|
29
|
+
const { templateDir = getTemplateDir(), force = false, dryRun = false } = options;
|
|
28
30
|
const resolvedTarget = path.resolve(targetDir);
|
|
29
31
|
const resolvedTemplate = path.resolve(templateDir);
|
|
30
32
|
|
|
@@ -33,7 +35,9 @@ function validateTarget(targetDir, options = {}) {
|
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
if (!fs.existsSync(resolvedTarget)) {
|
|
36
|
-
|
|
38
|
+
if (!dryRun) {
|
|
39
|
+
fs.mkdirSync(resolvedTarget, { recursive: true });
|
|
40
|
+
}
|
|
37
41
|
return;
|
|
38
42
|
}
|
|
39
43
|
|
|
@@ -46,10 +50,35 @@ function validateTarget(targetDir, options = {}) {
|
|
|
46
50
|
}
|
|
47
51
|
}
|
|
48
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Detects whether linkName and targetFileName refer to the same entry on a case-insensitive filesystem.
|
|
55
|
+
*/
|
|
56
|
+
function isSameCaseInsensitiveFile(targetDir, linkName, targetFileName) {
|
|
57
|
+
if (linkName.toLowerCase() !== targetFileName.toLowerCase()) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const targetPath = path.join(targetDir, targetFileName);
|
|
61
|
+
const linkPath = path.join(targetDir, linkName);
|
|
62
|
+
try {
|
|
63
|
+
if (fs.existsSync(targetPath) && fs.existsSync(linkPath)) {
|
|
64
|
+
return !fs.lstatSync(linkPath).isSymbolicLink();
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
49
72
|
/**
|
|
50
73
|
* Safely creates or updates a symbolic link, falling back to a file copy if symlinks are unsupported.
|
|
51
74
|
*/
|
|
52
|
-
function ensureSymlink(targetDir, linkName, targetFileName) {
|
|
75
|
+
function ensureSymlink(targetDir, linkName, targetFileName, dryRun = false) {
|
|
76
|
+
if (dryRun) return true;
|
|
77
|
+
|
|
78
|
+
if (isSameCaseInsensitiveFile(targetDir, linkName, targetFileName)) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
53
82
|
const linkPath = path.join(targetDir, linkName);
|
|
54
83
|
try {
|
|
55
84
|
const stat = fs.lstatSync(linkPath);
|
|
@@ -62,21 +91,24 @@ function ensureSymlink(targetDir, linkName, targetFileName) {
|
|
|
62
91
|
|
|
63
92
|
try {
|
|
64
93
|
fs.symlinkSync(targetFileName, linkPath);
|
|
94
|
+
return true;
|
|
65
95
|
} catch {
|
|
66
96
|
// Fallback if environment (e.g., certain Windows configs) prevents symlink creation
|
|
67
97
|
const sourceFile = path.join(targetDir, targetFileName);
|
|
68
98
|
if (fs.existsSync(sourceFile)) {
|
|
69
99
|
fs.copyFileSync(sourceFile, linkPath);
|
|
70
100
|
}
|
|
101
|
+
return true;
|
|
71
102
|
}
|
|
72
103
|
}
|
|
73
104
|
|
|
74
105
|
/**
|
|
75
106
|
* Ensures all bash scripts in skill directories have executable permissions (0o755).
|
|
76
107
|
*/
|
|
77
|
-
function makeScriptsExecutable(targetDir) {
|
|
108
|
+
function makeScriptsExecutable(targetDir, dryRun = false) {
|
|
109
|
+
const modified = [];
|
|
78
110
|
const skillsDir = path.join(targetDir, '.agents', 'skills');
|
|
79
|
-
if (!fs.existsSync(skillsDir)) return;
|
|
111
|
+
if (!fs.existsSync(skillsDir)) return modified;
|
|
80
112
|
|
|
81
113
|
const skills = fs.readdirSync(skillsDir);
|
|
82
114
|
for (const skill of skills) {
|
|
@@ -86,25 +118,31 @@ function makeScriptsExecutable(targetDir) {
|
|
|
86
118
|
for (const file of files) {
|
|
87
119
|
if (file.endsWith('.sh')) {
|
|
88
120
|
const filePath = path.join(scriptsDir, file);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
121
|
+
modified.push(filePath);
|
|
122
|
+
if (!dryRun) {
|
|
123
|
+
try {
|
|
124
|
+
fs.chmodSync(filePath, 0o755);
|
|
125
|
+
} catch {
|
|
126
|
+
// Non-critical if filesystem does not support POSIX permissions
|
|
127
|
+
}
|
|
93
128
|
}
|
|
94
129
|
}
|
|
95
130
|
}
|
|
96
131
|
}
|
|
97
132
|
}
|
|
133
|
+
return modified;
|
|
98
134
|
}
|
|
99
135
|
|
|
100
136
|
/**
|
|
101
137
|
* Recursively copies template files into the target directory and sets up symlinks and permissions.
|
|
102
138
|
*/
|
|
103
|
-
function copyTemplate(targetDir, templateDir = getTemplateDir()) {
|
|
139
|
+
function copyTemplate(targetDir, templateDir = getTemplateDir(), options = {}) {
|
|
140
|
+
const { dryRun = false } = options;
|
|
104
141
|
const resolvedTarget = path.resolve(targetDir);
|
|
105
142
|
const resolvedTemplate = path.resolve(templateDir);
|
|
143
|
+
const actions = [];
|
|
106
144
|
|
|
107
|
-
if (!fs.existsSync(resolvedTarget)) {
|
|
145
|
+
if (!dryRun && !fs.existsSync(resolvedTarget)) {
|
|
108
146
|
fs.mkdirSync(resolvedTarget, { recursive: true });
|
|
109
147
|
}
|
|
110
148
|
|
|
@@ -113,29 +151,46 @@ function copyTemplate(targetDir, templateDir = getTemplateDir()) {
|
|
|
113
151
|
if (!fs.existsSync(srcPath)) continue;
|
|
114
152
|
|
|
115
153
|
const destPath = path.join(resolvedTarget, item);
|
|
116
|
-
|
|
154
|
+
actions.push(`copy: ${item} -> ${destPath}`);
|
|
155
|
+
|
|
156
|
+
if (!dryRun) {
|
|
157
|
+
fs.cpSync(srcPath, destPath, { recursive: true, force: true, dereference: false });
|
|
158
|
+
}
|
|
117
159
|
}
|
|
118
160
|
|
|
119
161
|
// Ensure harness parity symlinks per AGENTS.md mandate
|
|
120
|
-
|
|
121
|
-
ensureSymlink(resolvedTarget, '
|
|
162
|
+
actions.push(`symlink: CLAUDE.md -> AGENTS.md`);
|
|
163
|
+
ensureSymlink(resolvedTarget, 'CLAUDE.md', 'AGENTS.md', dryRun);
|
|
164
|
+
|
|
165
|
+
actions.push(`symlink: agents.md -> AGENTS.md`);
|
|
166
|
+
ensureSymlink(resolvedTarget, 'agents.md', 'AGENTS.md', dryRun);
|
|
122
167
|
|
|
123
168
|
// Ensure scripts are executable
|
|
124
|
-
|
|
169
|
+
const inspectDir = dryRun ? resolvedTemplate : resolvedTarget;
|
|
170
|
+
const scripts = makeScriptsExecutable(inspectDir, dryRun);
|
|
171
|
+
for (const script of scripts) {
|
|
172
|
+
actions.push(`chmod: +x ${path.relative(inspectDir, script)}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return actions;
|
|
125
176
|
}
|
|
126
177
|
|
|
127
178
|
/**
|
|
128
179
|
* Initializes a git repository in the target directory if not already inside one.
|
|
129
180
|
*/
|
|
130
181
|
function initGit(targetDir, options = {}) {
|
|
131
|
-
const { noGit = false } = options;
|
|
182
|
+
const { noGit = false, dryRun = false } = options;
|
|
132
183
|
if (noGit) return false;
|
|
133
184
|
|
|
134
185
|
const gitDir = path.join(targetDir, '.git');
|
|
135
186
|
if (fs.existsSync(gitDir)) return false;
|
|
136
187
|
|
|
188
|
+
if (dryRun) {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
137
192
|
try {
|
|
138
|
-
execSync('git init -q', { cwd: targetDir, stdio: 'ignore' });
|
|
193
|
+
cp.execSync('git init -q', { cwd: targetDir, stdio: 'ignore' });
|
|
139
194
|
return true;
|
|
140
195
|
} catch {
|
|
141
196
|
return false;
|
|
@@ -150,26 +205,79 @@ function scaffold(options = {}) {
|
|
|
150
205
|
targetDir = process.cwd(),
|
|
151
206
|
force = false,
|
|
152
207
|
noGit = false,
|
|
153
|
-
templateDir = getTemplateDir()
|
|
208
|
+
templateDir = getTemplateDir(),
|
|
209
|
+
dryRun = false,
|
|
210
|
+
silent = false
|
|
154
211
|
} = options;
|
|
155
212
|
|
|
156
213
|
const resolvedTarget = path.resolve(targetDir);
|
|
157
|
-
validateTarget(resolvedTarget, { templateDir, force });
|
|
158
|
-
copyTemplate(resolvedTarget, templateDir);
|
|
159
|
-
const gitInitialized = initGit(resolvedTarget, { noGit });
|
|
214
|
+
validateTarget(resolvedTarget, { templateDir, force, dryRun });
|
|
215
|
+
const actions = copyTemplate(resolvedTarget, templateDir, { dryRun });
|
|
216
|
+
const gitInitialized = initGit(resolvedTarget, { noGit, dryRun });
|
|
217
|
+
if (gitInitialized) {
|
|
218
|
+
actions.push('git: initialize repository');
|
|
219
|
+
}
|
|
160
220
|
|
|
161
221
|
return {
|
|
162
222
|
success: true,
|
|
163
223
|
targetDir: resolvedTarget,
|
|
164
|
-
gitInitialized
|
|
224
|
+
gitInitialized,
|
|
225
|
+
dryRun,
|
|
226
|
+
actions
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Appends a standardized upstream change entry to changes.md.
|
|
232
|
+
*/
|
|
233
|
+
function logChange(options = {}) {
|
|
234
|
+
const {
|
|
235
|
+
title,
|
|
236
|
+
category = 'Architecture',
|
|
237
|
+
targetFiles = 'docs/rules/',
|
|
238
|
+
rationale = 'Generic architectural enhancement',
|
|
239
|
+
description = '',
|
|
240
|
+
targetDir = process.cwd()
|
|
241
|
+
} = options;
|
|
242
|
+
|
|
243
|
+
if (!title || typeof title !== 'string' || !title.trim()) {
|
|
244
|
+
throw new Error('A change title is required to log an upstream change.');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const cleanTitle = title.trim();
|
|
248
|
+
const changesFilePath = path.join(path.resolve(targetDir), 'changes.md');
|
|
249
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
250
|
+
|
|
251
|
+
const entry = `\n### [${today}] ${cleanTitle}\n` +
|
|
252
|
+
`- **Category:** ${category}\n` +
|
|
253
|
+
`- **Target File(s):** ${targetFiles}\n` +
|
|
254
|
+
`- **Rationale:** ${rationale}\n` +
|
|
255
|
+
`- **Description:** ${description || cleanTitle}\n` +
|
|
256
|
+
`- **Domain Filter Verification:** Verified 100% generic; purged of all project-specific business entities and models.\n`;
|
|
257
|
+
|
|
258
|
+
if (fs.existsSync(changesFilePath)) {
|
|
259
|
+
fs.appendFileSync(changesFilePath, entry, 'utf-8');
|
|
260
|
+
} else {
|
|
261
|
+
const initialHeader = `# Upstream Changes Ledger (\`changes.md\`)\n\n` +
|
|
262
|
+
`> **Core Purpose:** Record candidate improvements, generic architectural updates, defect post-mortems, and rule enhancements discovered in this workspace that should be incorporated into the upstream \`azcodr\` baseline template.\n\n` +
|
|
263
|
+
`---\n\n## Upstream Changes Log\n`;
|
|
264
|
+
fs.writeFileSync(changesFilePath, initialHeader + entry, 'utf-8');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
success: true,
|
|
269
|
+
filePath: changesFilePath,
|
|
270
|
+
entry
|
|
165
271
|
};
|
|
166
272
|
}
|
|
167
273
|
|
|
168
274
|
module.exports = {
|
|
169
275
|
scaffold,
|
|
276
|
+
logChange,
|
|
170
277
|
validateTarget,
|
|
171
278
|
copyTemplate,
|
|
172
279
|
ensureSymlink,
|
|
280
|
+
isSameCaseInsensitiveFile,
|
|
173
281
|
makeScriptsExecutable,
|
|
174
282
|
initGit,
|
|
175
283
|
getTemplateDir,
|
package/package.json
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "azcodr",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Enterprise Multi-Tenant Architecture & Agentic Engineering Starter Template",
|
|
5
5
|
"bin": {
|
|
6
6
|
"azcodr": "bin/azcodr.js"
|
|
7
7
|
},
|
|
8
8
|
"main": "./lib/index.js",
|
|
9
|
+
"types": "./lib/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./lib/index.d.ts",
|
|
13
|
+
"default": "./lib/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
9
17
|
"files": [
|
|
10
18
|
"bin",
|
|
11
19
|
"lib",
|
|
12
20
|
"AGENTS.md",
|
|
13
21
|
"memory.md",
|
|
22
|
+
"changes.md",
|
|
14
23
|
"README.md",
|
|
15
24
|
"docs",
|
|
16
25
|
".agents",
|
|
17
26
|
".gitignore",
|
|
27
|
+
".editorconfig",
|
|
18
28
|
"LICENSE"
|
|
19
29
|
],
|
|
20
30
|
"keywords": [
|
|
@@ -41,9 +51,10 @@
|
|
|
41
51
|
"node": ">=18.0.0"
|
|
42
52
|
},
|
|
43
53
|
"scripts": {
|
|
44
|
-
"test": "node --test
|
|
45
|
-
"test:coverage": "node
|
|
54
|
+
"test": "node --test",
|
|
55
|
+
"test:coverage": "node scripts/test_coverage.js",
|
|
56
|
+
"lint": "node --check bin/azcodr.js lib/index.js lib/scaffold.js tests/cli.test.js tests/scaffold.test.js scripts/test_coverage.js",
|
|
46
57
|
"validate": "bash .agents/skills/agentic-architect/scripts/validate_agentic_configs.sh",
|
|
47
|
-
"prepublishOnly": "npm test && npm run validate"
|
|
58
|
+
"prepublishOnly": "npm run test:coverage && npm run validate"
|
|
48
59
|
}
|
|
49
60
|
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: merge-ai
|
|
3
|
-
description: Use when the user invokes /merge-ai or asks to merge generic AI rules, skills, lessons learned, and post-mortems from the current workspace back into the generic azcodr baseline repository. Do not use for merging application business logic or routine Git branches.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Merge AI Knowledge, Rules & Skills Skill (`/merge-ai`)
|
|
7
|
-
|
|
8
|
-
> **Core Philosophy:** Upstream baseline repositories (e.g. `https://github.com/org/azcodr`) must remain pristine, generic, and untouched until the user explicitly triggers `/merge-ai`. When triggered from any project workspace (e.g. `https://github.com/org/my-project`), detect if the baseline repo is already cloned locally (apply directly) or clone it first, purge all project-specific domain models, colocate DOs and DONTs into atomic rules, and synchronize the generic baseline.
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## 1. When to Use This Skill
|
|
13
|
-
- The user issues `/merge-ai` or requests syncing AI rules, skills, issue logs, and lessons learned back into the generic baseline repository.
|
|
14
|
-
- User references repository URLs (e.g. Source: `https://github.com/org/my-project`, Target: `https://github.com/org/azcodr`).
|
|
15
|
-
- Auditing divergences between the current project workspace and the generic baseline repository.
|
|
16
|
-
- Exporting newly discovered architectural patterns, defect post-mortems, or reusable skills to the generic starter.
|
|
17
|
-
- **DO NOT USE** during routine project feature development or bug fixes.
|
|
18
|
-
- **DO NOT USE** to merge application domain entities, business logic, or project-specific data models.
|
|
19
|
-
- **DO NOT USE** without explicit user invocation.
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## 2. Step-by-Step Execution Workflow
|
|
24
|
-
|
|
25
|
-
### Phase 1: Target Baseline Repository Resolution (URL or Local)
|
|
26
|
-
When `/merge-ai` is triggered with repository URLs (e.g. `/merge-ai https://github.com/org/my-project https://github.com/org/azcodr`):
|
|
27
|
-
1. **Execute Repo Resolver Script:**
|
|
28
|
-
```bash
|
|
29
|
-
# Discovers existing local clone or automatically clones fresh
|
|
30
|
-
eval $(bash .agents/skills/merge-ai/scripts/resolve_repo.sh "$TARGET_REPO_URL")
|
|
31
|
-
```
|
|
32
|
-
- **If already cloned locally:** Discovers its directory, verifies clean working tree, and exports `STATUS=ALREADY_CLONED` and `LOCAL_PATH` (e.g. `/path/to/azcodr`).
|
|
33
|
-
- **If not cloned locally:** Automatically executes `git clone "$TARGET_REPO_URL"` to `$HOME/projects/<name>` and exports `STATUS=CLONED_FRESH` and `LOCAL_PATH`.
|
|
34
|
-
2. Set `$BASELINE_DIR="$LOCAL_PATH"`.
|
|
35
|
-
3. If `STATUS=ALREADY_CLONED`, ensure the repository is on branch `main` (`git -C "$BASELINE_DIR" pull --ff-only`).
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
### Phase 2: Divergence Audit & Domain Purging
|
|
40
|
-
1. Run the divergence audit script:
|
|
41
|
-
```bash
|
|
42
|
-
bash .agents/skills/merge-ai/scripts/audit_divergence.sh "$BASELINE_DIR"
|
|
43
|
-
```
|
|
44
|
-
2. Systematically filter out all project-specific elements before proposing changes:
|
|
45
|
-
- **Purge Business Domain Entities:** Replace project-specific nouns with universal architectural archetypes (`Entity`, `Aggregate`, `ValueObject`, `Resource`, `Transaction`).
|
|
46
|
-
- **Purge Concrete Stack Specifics:** Keep core rules stack-agnostic (Hexagonal Ports, abstract repositories). Keep project-specific setups (e.g. SQLite dev / Postgres prod, React Vite client) in the project workspace.
|
|
47
|
-
- **Colocate DOs & DONTs into Atomic Rules:** Embed DOs and DONTs directly inside their governing atomic rule files in `docs/rules/` (`## Invariants, DO's & DONT's`). Keep `docs/knowledge/dos_and_donts.md` strictly as a clean cross-reference index directory.
|
|
48
|
-
- **Transform ADRs & Post-Mortems:** Port universal decisions (ADR-007 CRUD & Selectors, ADR-008 Bootstrapping Decoupling, ADR-009 Agile Domain TDD, ADR-010 M:N Skill Composability) as generic ADRs in `memory.md`. Port universal post-mortems (`ISSUE-004`, `ISSUE-005`) into `issue_log.md` and `lessons_learned.md`.
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
### Phase 3: Dry-Run Review & Explicit User Confirmation
|
|
53
|
-
1. Present a concise, structured dry-run report to the user summarizing:
|
|
54
|
-
- Target baseline repo URL and resolved local directory (`$BASELINE_DIR`).
|
|
55
|
-
- Generic rules, skills, post-mortems, and ADRs to be merged.
|
|
56
|
-
- Domain-specific elements purged.
|
|
57
|
-
2. **STOP AND ASK FOR EXPLICIT CONFIRMATION** before modifying `$BASELINE_DIR`.
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
### Phase 4: Apply Merge, Validate & Sync
|
|
62
|
-
Upon user confirmation:
|
|
63
|
-
1. Apply the generic updates to `$BASELINE_DIR`:
|
|
64
|
-
- `AGENTS.md` (Unified Agent Cognitive & Agile Domain Lifecycle).
|
|
65
|
-
- `docs/rules/` (Updated atomic rules with colocated DOs/DONTs).
|
|
66
|
-
- `.agents/skills/` (Updated generic skills, e.g. decoupled `lets-build`).
|
|
67
|
-
- `docs/knowledge/` (Index-only `dos_and_donts.md`, generic post-mortems in `issue_log.md`, `lessons_learned.md`, `knowledge_graph.md`).
|
|
68
|
-
- `memory.md` (Generic ADRs).
|
|
69
|
-
2. Validate agentic configuration integrity in the baseline:
|
|
70
|
-
```bash
|
|
71
|
-
bash "$BASELINE_DIR/.agents/skills/agentic-architect/scripts/validate_agentic_configs.sh"
|
|
72
|
-
```
|
|
73
|
-
*Requirement: 0 warnings, AGENTS.md <= 120 lines, valid symlinks.*
|
|
74
|
-
3. Commit and push the baseline repository to remote:
|
|
75
|
-
```bash
|
|
76
|
-
git -C "$BASELINE_DIR" add -A
|
|
77
|
-
git -C "$BASELINE_DIR" commit -m "feat(ai-sync): merge generic rules, skills, and lifecycle improvements from workspace"
|
|
78
|
-
git -C "$BASELINE_DIR" push origin main
|
|
79
|
-
```
|
|
80
|
-
4. Confirm successful synchronization with the remote generic baseline URL.
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## 3. Gotchas & What NOT to Do
|
|
85
|
-
|
|
86
|
-
- **MAJOR DONT: Never touch, edit, or commit to the baseline repository (`azcodr`) during routine feature development.** The baseline must be left completely alone until `/merge-ai` is explicitly invoked.
|
|
87
|
-
- **DO NOT** copy application domain models, database tables, or framework-specific configs to the baseline.
|
|
88
|
-
- **DO NOT** create monolithic DO/DONT lists. Always colocate directives in atomic rules.
|
|
89
|
-
- **DO NOT** execute the merge without presenting a dry-run summary and receiving explicit approval.
|
|
90
|
-
- **DO NOT** push to the baseline repository if `validate_agentic_configs.sh` fails or reports warnings.
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
BASELINE_DIR="${1:-/home/prosubodh/projects/azcodr}"
|
|
5
|
-
CURRENT_DIR="$(pwd)"
|
|
6
|
-
|
|
7
|
-
echo "=================================================================="
|
|
8
|
-
echo "đ Auditing AI Knowledge & Rule Divergence"
|
|
9
|
-
echo "Current Workspace: $CURRENT_DIR"
|
|
10
|
-
echo "Baseline Template: $BASELINE_DIR"
|
|
11
|
-
echo "=================================================================="
|
|
12
|
-
|
|
13
|
-
if [ ! -d "$BASELINE_DIR" ]; then
|
|
14
|
-
echo "â Error: Baseline directory '$BASELINE_DIR' does not exist."
|
|
15
|
-
exit 1
|
|
16
|
-
fi
|
|
17
|
-
|
|
18
|
-
echo ""
|
|
19
|
-
echo "--- 1. Checking Core Directives (AGENTS.md) ---"
|
|
20
|
-
if diff -q "$CURRENT_DIR/AGENTS.md" "$BASELINE_DIR/AGENTS.md" > /dev/null 2>&1; then
|
|
21
|
-
echo "â
AGENTS.md is identical."
|
|
22
|
-
else
|
|
23
|
-
echo "â ī¸ AGENTS.md differs between workspaces."
|
|
24
|
-
fi
|
|
25
|
-
|
|
26
|
-
echo ""
|
|
27
|
-
echo "--- 2. Checking Atomic Rules (docs/rules/) ---"
|
|
28
|
-
DIFF_RULES=$(diff -qr "$CURRENT_DIR/docs/rules" "$BASELINE_DIR/docs/rules" 2>/dev/null || true)
|
|
29
|
-
if [ -z "$DIFF_RULES" ]; then
|
|
30
|
-
echo "â
All atomic rules in docs/rules/ are identical."
|
|
31
|
-
else
|
|
32
|
-
echo "$DIFF_RULES"
|
|
33
|
-
fi
|
|
34
|
-
|
|
35
|
-
echo ""
|
|
36
|
-
echo "--- 3. Checking Specialized Skills (.agents/skills/) ---"
|
|
37
|
-
DIFF_SKILLS=$(diff -qr "$CURRENT_DIR/.agents/skills" "$BASELINE_DIR/.agents/skills" 2>/dev/null || true)
|
|
38
|
-
if [ -z "$DIFF_SKILLS" ]; then
|
|
39
|
-
echo "â
All skills in .agents/skills/ are identical."
|
|
40
|
-
else
|
|
41
|
-
echo "$DIFF_SKILLS"
|
|
42
|
-
fi
|
|
43
|
-
|
|
44
|
-
echo ""
|
|
45
|
-
echo "--- 4. Checking Knowledge Hub (docs/knowledge/) ---"
|
|
46
|
-
DIFF_KNOW=$(diff -qr "$CURRENT_DIR/docs/knowledge" "$BASELINE_DIR/docs/knowledge" 2>/dev/null || true)
|
|
47
|
-
if [ -z "$DIFF_KNOW" ]; then
|
|
48
|
-
echo "â
All knowledge files in docs/knowledge/ are identical."
|
|
49
|
-
else
|
|
50
|
-
echo "$DIFF_KNOW"
|
|
51
|
-
fi
|
|
52
|
-
|
|
53
|
-
echo ""
|
|
54
|
-
echo "--- 5. Checking Architecture Decision Records (memory.md) ---"
|
|
55
|
-
if [ -f "$CURRENT_DIR/memory.md" ] && [ -f "$BASELINE_DIR/memory.md" ]; then
|
|
56
|
-
mapfile -t BASELINE_ADRS < <(grep -E '^### ADR-[0-9]+:' "$BASELINE_DIR/memory.md" 2>/dev/null | sed -E 's/^### ADR-[0-9]+:[[:space:]]*//' || true)
|
|
57
|
-
mapfile -t CURRENT_ADRS < <(grep -E '^### ADR-[0-9]+:' "$CURRENT_DIR/memory.md" 2>/dev/null | sed -E 's/^### ADR-[0-9]+:[[:space:]]*//' || true)
|
|
58
|
-
|
|
59
|
-
MISSING_IN_CURRENT=()
|
|
60
|
-
for b_adr in "${BASELINE_ADRS[@]}"; do
|
|
61
|
-
[ -z "$b_adr" ] && continue
|
|
62
|
-
found=false
|
|
63
|
-
for c_adr in "${CURRENT_ADRS[@]}"; do
|
|
64
|
-
if [ "$b_adr" = "$c_adr" ]; then
|
|
65
|
-
found=true
|
|
66
|
-
break
|
|
67
|
-
fi
|
|
68
|
-
done
|
|
69
|
-
if [ "$found" = false ]; then
|
|
70
|
-
MISSING_IN_CURRENT+=("$b_adr")
|
|
71
|
-
fi
|
|
72
|
-
done
|
|
73
|
-
|
|
74
|
-
EXTRA_IN_CURRENT=()
|
|
75
|
-
for c_adr in "${CURRENT_ADRS[@]}"; do
|
|
76
|
-
[ -z "$c_adr" ] && continue
|
|
77
|
-
found=false
|
|
78
|
-
for b_adr in "${BASELINE_ADRS[@]}"; do
|
|
79
|
-
if [ "$c_adr" = "$b_adr" ]; then
|
|
80
|
-
found=true
|
|
81
|
-
break
|
|
82
|
-
fi
|
|
83
|
-
done
|
|
84
|
-
if [ "$found" = false ]; then
|
|
85
|
-
EXTRA_IN_CURRENT+=("$c_adr")
|
|
86
|
-
fi
|
|
87
|
-
done
|
|
88
|
-
|
|
89
|
-
if [ ${#MISSING_IN_CURRENT[@]} -gt 0 ]; then
|
|
90
|
-
echo "â ī¸ Workspace is missing ${#MISSING_IN_CURRENT[@]} baseline ADR(s):"
|
|
91
|
-
for m in "${MISSING_IN_CURRENT[@]}"; do
|
|
92
|
-
echo " - $m"
|
|
93
|
-
done
|
|
94
|
-
elif [ ${#EXTRA_IN_CURRENT[@]} -eq 0 ]; then
|
|
95
|
-
echo "â
memory.md ADRs are 100% identical."
|
|
96
|
-
else
|
|
97
|
-
echo "â
All generic baseline ADRs are synchronized."
|
|
98
|
-
for e in "${EXTRA_IN_CURRENT[@]}"; do
|
|
99
|
-
echo " âšī¸ Project-specific ADR retained in workspace: $e"
|
|
100
|
-
done
|
|
101
|
-
fi
|
|
102
|
-
else
|
|
103
|
-
echo "â ī¸ memory.md not found in one or both workspaces."
|
|
104
|
-
fi
|
|
105
|
-
|
|
106
|
-
echo "=================================================================="
|
|
107
|
-
echo "Audit complete. Run /merge-ai to filter and merge generic changes."
|
|
108
|
-
echo "=================================================================="
|