azcodr 1.5.2 → 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 +42 -42
- package/.agents/hooks.json.example +42 -42
- package/.agents/mcp_config.json.example +29 -29
- package/.agents/scripts/safety_guard.sh +143 -34
- package/.agents/scripts/verify_completion.sh +90 -27
- package/.agents/skills/agentic-architect/SKILL.md +125 -125
- package/.agents/skills/agentic-architect/references/agents_md_template.md +62 -62
- package/.agents/skills/agentic-architect/references/refinement_workflow.md +32 -32
- package/.agents/skills/agentic-architect/references/skill_architecture_inquiry.md +63 -63
- package/.agents/skills/agentic-architect/references/skill_template.md +56 -56
- package/.agents/skills/agentic-architect/scripts/validate_agentic_configs.sh +402 -402
- package/.agents/skills/clean-code-refactor/SKILL.md +91 -91
- package/.agents/skills/clean-code-refactor/references/clean_code_smells.md +27 -27
- package/.agents/skills/clean-code-refactor/references/design_patterns_ts.md +65 -65
- package/.agents/skills/compliance-audit/SKILL.md +120 -120
- package/.agents/skills/compliance-audit/references/owasp_top10_controls.md +16 -16
- package/.agents/skills/compliance-audit/references/soc2_iso_controls.md +28 -28
- package/.agents/skills/lets-build/SKILL.md +173 -173
- package/.agents/skills/lets-build/references/architecture_interview_matrix.md +115 -115
- package/.agents/skills/lets-build/references/hexagonal_bootstrap_scaffolds.md +160 -160
- package/.agents/skills/lets-build/references/project_readme_template.md +79 -79
- package/.agents/skills/lets-build/scripts/bootstrap_workspace.sh +419 -255
- package/.agents/skills/product-analyst/SKILL.md +154 -154
- package/.agents/skills/product-analyst/references/backlog_ordering_techniques.md +107 -107
- package/.agents/skills/product-analyst/references/gherkin_patterns.md +46 -46
- package/.agents/skills/product-analyst/references/invest_checklist.md +38 -38
- package/.agents/skills/product-analyst/references/okr_alignment_guide.md +76 -76
- package/.agents/skills/product-analyst/references/smart_tasks.md +59 -59
- package/.agents/skills/relentless-questioner/SKILL.md +128 -128
- package/.agents/skills/relentless-questioner/references/adaptive_question_trees.md +102 -102
- package/.editorconfig +19 -19
- package/.github/workflows/ci.yml +167 -78
- package/.github/workflows/publish.yml +196 -0
- package/.gitignore +40 -25
- package/AGENTS.md +103 -102
- package/LICENSE +21 -21
- package/README.md +168 -165
- package/bin/azcodr.js +19 -228
- package/docs/knowledge/ubiquitous_language.md +31 -18
- package/docs/rules/agentic_configuration.md +259 -259
- package/docs/rules/api_architecture.md +179 -179
- package/docs/rules/authentication.md +76 -76
- package/docs/rules/authorization.md +75 -75
- package/docs/rules/caching.md +69 -69
- package/docs/rules/clean_code.md +62 -62
- package/docs/rules/cloud_native.md +41 -41
- package/docs/rules/cqrs.md +203 -203
- package/docs/rules/database_design.md +125 -125
- package/docs/rules/database_operations.md +69 -69
- package/docs/rules/design_patterns.md +98 -98
- package/docs/rules/devops_ci_cd.md +76 -76
- package/docs/rules/domain_driven_design.md +122 -122
- package/docs/rules/error_handling.md +54 -52
- package/docs/rules/feature_flags.md +59 -59
- package/docs/rules/frontend_architecture.md +157 -157
- package/docs/rules/multitenancy_architecture.md +98 -98
- package/docs/rules/product_ownership.md +127 -127
- package/docs/rules/project_management.md +49 -49
- package/docs/rules/relentless_questioning.md +52 -52
- package/docs/rules/requirements_engineering.md +98 -98
- package/docs/rules/security_compliance.md +53 -53
- package/docs/rules/server_driven_ui.md +88 -88
- package/docs/rules/test_driven_development.md +185 -185
- package/docs/rules/transactional_email.md +27 -27
- package/docs/rules/type_safety.md +65 -65
- package/docs/rules/ui_ux_architecture.md +150 -150
- package/docs/rules/workflow_state_machines.md +117 -117
- package/lib/cli-parse.d.ts +32 -0
- package/lib/cli-parse.d.ts.map +1 -0
- package/lib/cli-parse.js +55 -0
- 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 +102 -0
- 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 +166 -0
- 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 +26 -0
- 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 +32 -0
- 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 +95 -0
- package/lib/guards.js.map +1 -0
- package/lib/index.d.ts +6 -134
- 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 +129 -0
- 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 +44 -0
- 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 +92 -0
- 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 +201 -448
- package/lib/scaffold.js.map +1 -0
- package/memory.md +135 -36
- package/package.json +75 -62
- package/scripts/test_coverage.js +66 -38
- package/scripts/validate/adr.js +155 -0
- package/scripts/validate/io.js +82 -0
- package/scripts/validate/links.js +166 -0
- package/scripts/validate/parity.js +122 -0
- package/scripts/validate/root.js +183 -0
- package/scripts/validate/rules.js +42 -0
- package/scripts/validate/skills.js +94 -0
- package/scripts/validate/text.js +27 -0
- package/scripts/validate-cli.js +12 -0
- package/scripts/validate.js +158 -258
- 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/.github/copilot-instructions.md +0 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text helpers shared by the validator phases.
|
|
3
|
+
*
|
|
4
|
+
* Both exist because generated documentation routinely contains ADR headings,
|
|
5
|
+
* rule headers, and Core Mandate lines inside examples. Matching the raw file
|
|
6
|
+
* would mistake documentation ABOUT the contract for the contract itself.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export function stripHtmlComments(text) {
|
|
10
|
+
// Remove HTML comments, tolerating unclosed ones. A stray `<!--` from an
|
|
11
|
+
// editor's "comment selection" must not silently hide every ADR after it.
|
|
12
|
+
return text
|
|
13
|
+
.replace(/<!--[\s\S]*?-->/g, '')
|
|
14
|
+
.replace(/<!--[\s\S]*$/, '');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Removes fenced code blocks so that examples inside documentation are not
|
|
19
|
+
* mistaken for real content. Without this, a rule file whose only H1 and
|
|
20
|
+
* "Core Mandate" lines sit inside a ```md fence satisfies both checks, and a
|
|
21
|
+
* `#### ADR-001` shown as an example is counted as a real decision.
|
|
22
|
+
*/
|
|
23
|
+
export function stripFencedCode(text) {
|
|
24
|
+
return text.replace(/^([ \t]*)(```|~~~)[\s\S]*?^\1\2[ \t]*$/gm, '');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default { stripHtmlComments, stripFencedCode };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI entry for the agentic-architecture validator.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately separate from scripts/validate.js: keeping the library free of a
|
|
7
|
+
* `require.main` guard means requiring it never validates or exits, and it
|
|
8
|
+
* avoids a permanently-unreachable branch that blocks the 100% coverage gate.
|
|
9
|
+
*/
|
|
10
|
+
import { main } from './validate.js';
|
|
11
|
+
|
|
12
|
+
main();
|
package/scripts/validate.js
CHANGED
|
@@ -1,258 +1,158 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
console.log('');
|
|
161
|
-
console.log('3. Checking Specialized Skills (.agents/skills)...');
|
|
162
|
-
const skillsDir = path.join(workspaceRoot, '.agents', 'skills');
|
|
163
|
-
if (!fs.existsSync(skillsDir)) {
|
|
164
|
-
fail(`Missing .agents/skills directory at ${skillsDir}`);
|
|
165
|
-
} else {
|
|
166
|
-
const folders = fs.readdirSync(skillsDir).filter((f) => fs.statSync(path.join(skillsDir, f)).isDirectory());
|
|
167
|
-
let count = 0;
|
|
168
|
-
for (const skill of folders) {
|
|
169
|
-
count += 1;
|
|
170
|
-
const skillFile = path.join(skillsDir, skill, 'SKILL.md');
|
|
171
|
-
if (!fs.existsSync(skillFile)) { fail(`Skill '${skill}' missing SKILL.md`); continue; }
|
|
172
|
-
const content = fs.readFileSync(skillFile, 'utf-8');
|
|
173
|
-
const lines = content.split('\n');
|
|
174
|
-
if (lines[0].trim() !== '---') { fail(`Skill '${skill}' missing opening front matter delimiter (---)`); continue; }
|
|
175
|
-
const closing = lines.slice(1).findIndex((l) => l.trim() === '---');
|
|
176
|
-
if (closing === -1) fail(`Skill '${skill}' missing closing front matter delimiter (---)`);
|
|
177
|
-
const nameMatch = content.match(/^name:\s*(.+)$/m);
|
|
178
|
-
if (!nameMatch || nameMatch[1].trim() !== skill) fail(`Skill '${skill}' front matter 'name:' does not match directory name`);
|
|
179
|
-
const descMatch = content.match(/^description:\s*(.+)$/m);
|
|
180
|
-
const desc = descMatch ? descMatch[1].trim() : '';
|
|
181
|
-
if (!desc) {
|
|
182
|
-
fail(`Skill '${skill}' missing front matter 'description:'`);
|
|
183
|
-
} else {
|
|
184
|
-
if (!/^Use when/.test(desc)) warn(`Skill '${skill}' description should start with imperative 'Use when...'`);
|
|
185
|
-
if (!/do not use/i.test(desc)) warn(`Skill '${skill}' description should specify negative boundaries ('Do not use for...')`);
|
|
186
|
-
if (desc.length > 1024) fail(`Skill '${skill}' description exceeds 1024 chars (${desc.length} chars)`);
|
|
187
|
-
}
|
|
188
|
-
if (lines.length > 500) warn(`Skill '${skill}' exceeds 500 lines (${lines.length} lines). Offload details to references/.`);
|
|
189
|
-
else pass(`Skill '${skill}': ${lines.length} lines, description valid (${desc.length} chars).`);
|
|
190
|
-
if (!/What NOT to do/i.test(content) && !/Gotchas/i.test(content)) {
|
|
191
|
-
warn(`Skill '${skill}' missing mandatory 'Gotchas & What NOT to Do' section`);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
pass(`Validated ${count} skills in .agents/skills/.`);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
console.log('');
|
|
198
|
-
console.log('4. Checking Markdown Internal Links & Cross-References...');
|
|
199
|
-
const broken = [];
|
|
200
|
-
let totalLinks = 0;
|
|
201
|
-
function walk(dir) {
|
|
202
|
-
let entries = [];
|
|
203
|
-
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
204
|
-
for (const entry of entries) {
|
|
205
|
-
if (entry.name === '.git' || entry.name === 'node_modules') continue;
|
|
206
|
-
const full = path.join(dir, entry.name);
|
|
207
|
-
if (entry.isDirectory()) walk(full);
|
|
208
|
-
else if (entry.isFile() && entry.name.endsWith('.md')) checkFile(full);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
function checkFile(filePath) {
|
|
212
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
213
|
-
const dir = path.dirname(filePath);
|
|
214
|
-
const regex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
215
|
-
let m;
|
|
216
|
-
while ((m = regex.exec(content)) !== null) {
|
|
217
|
-
const target = m[2].trim();
|
|
218
|
-
if (/^(https?:|mailto:|#|conversation:\/\/|file:\/\/)/.test(target)) continue;
|
|
219
|
-
const clean = target.split('#')[0];
|
|
220
|
-
if (!clean) continue;
|
|
221
|
-
totalLinks += 1;
|
|
222
|
-
if (!fs.existsSync(path.normalize(path.join(dir, clean)))) {
|
|
223
|
-
broken.push(`${path.relative(workspaceRoot, filePath)} -> ${target}`);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
walk(workspaceRoot);
|
|
228
|
-
if (broken.length === 0) pass(`Validated ${totalLinks} internal links across workspace (0 broken links).`);
|
|
229
|
-
else for (const b of broken) fail(`Broken markdown link: ${b}`);
|
|
230
|
-
|
|
231
|
-
console.log('');
|
|
232
|
-
console.log('5. Checking Memory & ADR Ledger...');
|
|
233
|
-
const memoryFile = path.join(workspaceRoot, 'memory.md');
|
|
234
|
-
if (!fs.existsSync(memoryFile)) {
|
|
235
|
-
fail('Missing memory.md ADR ledger.');
|
|
236
|
-
} else {
|
|
237
|
-
const mem = fs.readFileSync(memoryFile, 'utf-8');
|
|
238
|
-
const withoutComments = mem.replace(/<!--[\s\S]*?-->/g, '');
|
|
239
|
-
if (!/^# /m.test(mem)) fail('memory.md missing H1 header');
|
|
240
|
-
else if (/#### ADR-/.test(withoutComments)) pass('memory.md contains ADR entries with valid envelope.');
|
|
241
|
-
else pass('memory.md is a clean slate (no ADRs yet; record ADR-001 during /lets-build).');
|
|
242
|
-
if (/ADR-025|ADR-028/.test(withoutComments) && /azcodr/i.test(mem)) {
|
|
243
|
-
warn('memory.md may contain template-internal ADRs; fresh projects must start at ADR-001.');
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
console.log('');
|
|
248
|
-
console.log('--------------------------------------------------------------');
|
|
249
|
-
if (errors === 0) {
|
|
250
|
-
console.log(`🎉 SUCCESS: All agentic configurations are valid and healthy! (${warnings} warnings)`);
|
|
251
|
-
process.exit(0);
|
|
252
|
-
} else {
|
|
253
|
-
console.log(`🚨 FAILURE: Found ${errors} error(s) and ${warnings} warning(s) in agentic configurations.`);
|
|
254
|
-
process.exit(1);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
main();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
import { createReporter, readTextOrFail, walkMarkdown } from './validate/io.js';
|
|
7
|
+
import * as textHelpers from './validate/text.js';
|
|
8
|
+
import * as parityHelpers from './validate/parity.js';
|
|
9
|
+
import { parseAdrLedger, checkAdrIndexConsistency, checkGlossaryPopulated } from './validate/adr.js';
|
|
10
|
+
import { phaseRootConfig } from './validate/root.js';
|
|
11
|
+
import { phaseRules } from './validate/rules.js';
|
|
12
|
+
import { phaseSkills } from './validate/skills.js';
|
|
13
|
+
import { phaseLinks } from './validate/links.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* NOTE: this module is a library. It never self-executes, so importing it from
|
|
17
|
+
* a test or another tool cannot trigger validation or call process.exit().
|
|
18
|
+
* The CLI entry point lives in `scripts/validate-cli.js`.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
function parseArgs(argv) {
|
|
22
|
+
let root = '';
|
|
23
|
+
for (const arg of argv) {
|
|
24
|
+
if (arg === '--fix') continue;
|
|
25
|
+
if (!root) root = arg;
|
|
26
|
+
}
|
|
27
|
+
return root || process.cwd();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function reportMemoryLedger(mem, ctx) {
|
|
31
|
+
const { entryIds } = parseAdrLedger(mem);
|
|
32
|
+
if (!/^# /m.test(mem)) ctx.fail('memory.md missing H1 header');
|
|
33
|
+
// Uses the same parser as phase 6. The previous unanchored
|
|
34
|
+
// `/#### ADR-/` disagreed with phase 6's anchored form, so the validator
|
|
35
|
+
// contradicted itself within a single run.
|
|
36
|
+
else if (entryIds.length > 0) ctx.pass('memory.md contains ADR entries with valid envelope.');
|
|
37
|
+
else ctx.pass('memory.md is a clean slate (no ADRs yet; record ADR-001 during /lets-build).');
|
|
38
|
+
// Template ADRs start well past ADR-024. Match any ID >= 25 regardless of
|
|
39
|
+
// zero padding.
|
|
40
|
+
const highestAdr = Math.max(0, ...entryIds);
|
|
41
|
+
if (highestAdr >= 25 && /azcodr/i.test(mem)) {
|
|
42
|
+
ctx.warn('memory.md may contain template-internal ADRs; fresh projects must start at ADR-001.');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function phaseMemory(ctx) {
|
|
47
|
+
ctx.log('');
|
|
48
|
+
ctx.heading('5. Checking Memory & ADR Ledger...');
|
|
49
|
+
const memoryFile = path.join(ctx.workspaceRoot, 'memory.md');
|
|
50
|
+
if (!fs.existsSync(memoryFile)) {
|
|
51
|
+
ctx.fail('Missing memory.md ADR ledger.');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// readTextOrFail already reported any read failure; skip the analysis.
|
|
55
|
+
const mem = readTextOrFail(memoryFile, 'memory.md', ctx.fail);
|
|
56
|
+
if (mem !== null) reportMemoryLedger(mem, ctx);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function phaseLedger(ctx) {
|
|
60
|
+
ctx.log('');
|
|
61
|
+
ctx.heading('6. Checking ADR Index Consistency...');
|
|
62
|
+
const consistency = checkAdrIndexConsistency(ctx.workspaceRoot, ctx.fail, ctx.pass);
|
|
63
|
+
if (consistency === 'indexed' || consistency === 'inconsistent') {
|
|
64
|
+
checkGlossaryPopulated(ctx.workspaceRoot, { fail: ctx.fail, pass: ctx.pass, warn: ctx.warn });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function buildValidationContext(workspaceRoot, reporter, counts) {
|
|
69
|
+
return {
|
|
70
|
+
workspaceRoot,
|
|
71
|
+
agentsFile: '',
|
|
72
|
+
agentsContent: '',
|
|
73
|
+
pass: reporter.pass,
|
|
74
|
+
warn: (msg) => { counts.warnings += 1; reporter.warn(msg); },
|
|
75
|
+
fail: (msg) => { counts.errors += 1; reporter.fail(msg); },
|
|
76
|
+
log: reporter.log,
|
|
77
|
+
heading: reporter.heading,
|
|
78
|
+
broken: [],
|
|
79
|
+
linkCount: 0,
|
|
80
|
+
visitedRealPaths: new Set()
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function reportSummary(counts, reporter) {
|
|
85
|
+
reporter.log('');
|
|
86
|
+
reporter.log('--------------------------------------------------------------');
|
|
87
|
+
if (counts.errors === 0) {
|
|
88
|
+
reporter.log(`🎉 SUCCESS: All agentic configurations are valid and healthy! (${counts.warnings} warnings)`);
|
|
89
|
+
} else {
|
|
90
|
+
reporter.log(`🚨 FAILURE: Found ${counts.errors} error(s) and ${counts.warnings} warning(s) in agentic configurations.`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function runValidation(workspaceRoot, reporter = createReporter()) {
|
|
95
|
+
// Counters live here, not on the reporter: an injected reporter only needs
|
|
96
|
+
// pass/warn/fail to observe outcomes, and the tally must not depend on the
|
|
97
|
+
// reporter's shape.
|
|
98
|
+
const counts = { errors: 0, warnings: 0 };
|
|
99
|
+
const ctx = buildValidationContext(workspaceRoot, reporter, counts);
|
|
100
|
+
reporter.heading(`🔍 Validating Agentic Architecture in: ${workspaceRoot}`);
|
|
101
|
+
reporter.log('--------------------------------------------------------------');
|
|
102
|
+
phaseRootConfig(ctx);
|
|
103
|
+
const validatedRules = phaseRules(ctx);
|
|
104
|
+
const validatedSkills = phaseSkills(ctx);
|
|
105
|
+
phaseLinks(ctx);
|
|
106
|
+
phaseMemory(ctx);
|
|
107
|
+
phaseLedger(ctx);
|
|
108
|
+
reportSummary(counts, reporter);
|
|
109
|
+
return {
|
|
110
|
+
errors: counts.errors,
|
|
111
|
+
warnings: counts.warnings,
|
|
112
|
+
validatedRules,
|
|
113
|
+
validatedSkills,
|
|
114
|
+
totalLinks: ctx.linkCount,
|
|
115
|
+
brokenLinks: ctx.broken
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function main(argv = process.argv.slice(2), exit = process.exit) {
|
|
120
|
+
const workspaceRoot = path.resolve(parseArgs(argv));
|
|
121
|
+
const result = runValidation(workspaceRoot);
|
|
122
|
+
return exit(result.errors === 0 ? 0 : 1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const { evaluateParityTarget, isValidAgentsTarget, createLowercaseParityLink } = parityHelpers;
|
|
126
|
+
const { stripHtmlComments, stripFencedCode } = textHelpers;
|
|
127
|
+
|
|
128
|
+
export {
|
|
129
|
+
runValidation,
|
|
130
|
+
parseArgs,
|
|
131
|
+
createReporter,
|
|
132
|
+
main,
|
|
133
|
+
parseAdrLedger,
|
|
134
|
+
checkAdrIndexConsistency,
|
|
135
|
+
checkGlossaryPopulated,
|
|
136
|
+
evaluateParityTarget,
|
|
137
|
+
isValidAgentsTarget,
|
|
138
|
+
createLowercaseParityLink,
|
|
139
|
+
stripHtmlComments,
|
|
140
|
+
stripFencedCode,
|
|
141
|
+
walkMarkdown
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export default {
|
|
145
|
+
runValidation,
|
|
146
|
+
parseArgs,
|
|
147
|
+
createReporter,
|
|
148
|
+
main,
|
|
149
|
+
parseAdrLedger,
|
|
150
|
+
checkAdrIndexConsistency,
|
|
151
|
+
checkGlossaryPopulated,
|
|
152
|
+
evaluateParityTarget,
|
|
153
|
+
isValidAgentsTarget,
|
|
154
|
+
createLowercaseParityLink,
|
|
155
|
+
stripHtmlComments,
|
|
156
|
+
stripFencedCode,
|
|
157
|
+
walkMarkdown
|
|
158
|
+
};
|
package/src/cli-parse.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Argument parsing for the azcodr CLI.
|
|
3
|
+
*
|
|
4
|
+
* Table-driven: every boolean flag is one Map entry, so adding a flag cannot
|
|
5
|
+
* raise the complexity of the parser itself.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface CliParsedOptions {
|
|
9
|
+
targetDir: string | null;
|
|
10
|
+
force: boolean;
|
|
11
|
+
noGit: boolean;
|
|
12
|
+
dryRun: boolean;
|
|
13
|
+
silent: boolean;
|
|
14
|
+
terminal: 'help' | 'version' | 'unknown-flag' | 'extra-arg' | null;
|
|
15
|
+
message?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type BooleanFlagKey = 'force' | 'noGit' | 'dryRun' | 'silent';
|
|
19
|
+
|
|
20
|
+
export function emptyOptions(): Omit<CliParsedOptions, 'terminal' | 'message'> {
|
|
21
|
+
return { targetDir: null, force: false, noGit: false, dryRun: false, silent: false };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const BOOLEAN_FLAGS = new Map<string, BooleanFlagKey>([
|
|
25
|
+
['-f', 'force'],
|
|
26
|
+
['--force', 'force'],
|
|
27
|
+
['--no-git', 'noGit'],
|
|
28
|
+
['-d', 'dryRun'],
|
|
29
|
+
['--dry-run', 'dryRun'],
|
|
30
|
+
['-s', 'silent'],
|
|
31
|
+
['--silent', 'silent']
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
export interface ArgOutcome {
|
|
35
|
+
terminal: 'help' | 'version' | 'unknown-flag' | 'extra-arg' | null;
|
|
36
|
+
message?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function applyArg(
|
|
40
|
+
state: Omit<CliParsedOptions, 'terminal' | 'message'>,
|
|
41
|
+
arg: string
|
|
42
|
+
): ArgOutcome {
|
|
43
|
+
if (arg === '-h' || arg === '--help') return { terminal: 'help' };
|
|
44
|
+
if (arg === '-v' || arg === '--version') return { terminal: 'version' };
|
|
45
|
+
const flag = BOOLEAN_FLAGS.get(arg);
|
|
46
|
+
if (flag !== undefined) {
|
|
47
|
+
state[flag] = true;
|
|
48
|
+
return { terminal: null };
|
|
49
|
+
}
|
|
50
|
+
if (arg.startsWith('-')) {
|
|
51
|
+
return {
|
|
52
|
+
terminal: 'unknown-flag',
|
|
53
|
+
message: `Unknown argument '${arg}'. Run 'npx azcodr --help' for available options.`
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (!state.targetDir) {
|
|
57
|
+
state.targetDir = arg;
|
|
58
|
+
return { terminal: null };
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
terminal: 'extra-arg',
|
|
62
|
+
message: `Unexpected argument '${arg}'. Run 'npx azcodr --help' for available options.`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function parseArgs(rawArgs: readonly string[]): CliParsedOptions {
|
|
67
|
+
const state = emptyOptions();
|
|
68
|
+
for (const arg of rawArgs) {
|
|
69
|
+
const outcome = applyArg(state, arg);
|
|
70
|
+
if (outcome.terminal !== null) {
|
|
71
|
+
return { ...state, terminal: outcome.terminal, message: outcome.message };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { ...state, terminal: null, message: undefined };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default { emptyOptions, BOOLEAN_FLAGS, applyArg, parseArgs };
|