azcodr 1.5.0 → 1.5.2
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 -0
- package/.agents/hooks.json.example +42 -42
- package/.agents/mcp_config.json.example +29 -24
- package/.agents/scripts/safety_guard.sh +34 -16
- package/.agents/scripts/verify_completion.sh +27 -13
- 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 -362
- 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 -172
- 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 +255 -253
- 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/copilot-instructions.md +1 -0
- package/.github/workflows/ci.yml +78 -0
- package/.gitignore +25 -25
- package/AGENTS.md +102 -102
- package/LICENSE +21 -21
- package/README.md +165 -154
- package/bin/azcodr.js +228 -228
- package/data/.gitkeep +0 -0
- package/docs/knowledge/ubiquitous_language.md +18 -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 +52 -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/index.d.ts +134 -123
- package/lib/index.js +5 -5
- package/lib/scaffold.js +448 -351
- package/memory.md +36 -36
- package/package.json +62 -59
- package/scripts/test_coverage.js +38 -0
- package/scripts/validate.js +258 -0
package/memory.md
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
# Workspace Memory, Architecture Decisions & Knowledge Hub
|
|
2
|
-
|
|
3
|
-
> **Core Purpose:** Authoritative persistent memory ledger for the workspace repository (`./`), maintaining Lightweight Architectural Decision Records (ADRs), system topologies, and living domain contracts.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 1. Quick Navigation & Knowledge Repositories
|
|
8
|
-
|
|
9
|
-
- 📖 **[Living Ubiquitous Language Glossary](./docs/knowledge/ubiquitous_language.md)**: Authoritative, single-name domain vocabulary contract.
|
|
10
|
-
- 📜 **[Lightweight ADR Master Index](#adr-master-index)**: Summary of all architectural decisions and direct links to governing rules.
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## 2. Consolidated Architectural Decision Records (ADRs)
|
|
15
|
-
|
|
16
|
-
### ADR Master Index
|
|
17
|
-
|
|
18
|
-
| ID | Title | Date | Status | Governing Rule / Skill |
|
|
19
|
-
|---|---|---|---|---|
|
|
20
|
-
| *(No decisions recorded yet)* | *Record initial architecture decisions during Phase 3 of /lets-build.* | *YYYY-MM-DD* | *ACCEPTED* | *e.g. [`clean_code.md`](./docs/rules/clean_code.md)* |
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
### Lightweight Decision Summaries
|
|
25
|
-
|
|
26
|
-
<!--
|
|
27
|
-
Record project Architectural Decision Records (ADRs) below as decisions are finalized.
|
|
28
|
-
Format:
|
|
29
|
-
|
|
30
|
-
#### ADR-001: [Imperative Title]
|
|
31
|
-
- **Date:** YYYY-MM-DD | **Status:** ACCEPTED
|
|
32
|
-
- **Context:** Problem space, constraints, and operational context requiring a decision.
|
|
33
|
-
- **Decision:** Chosen architecture, invariants, and implementation patterns.
|
|
34
|
-
- **Consequences:** Positive benefits and deliberate trade-offs accepted.
|
|
35
|
-
- **Enforced In:** Relevant rule files in docs/rules/ or code paths.
|
|
36
|
-
-->
|
|
1
|
+
# Workspace Memory, Architecture Decisions & Knowledge Hub
|
|
2
|
+
|
|
3
|
+
> **Core Purpose:** Authoritative persistent memory ledger for the workspace repository (`./`), maintaining Lightweight Architectural Decision Records (ADRs), system topologies, and living domain contracts.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Quick Navigation & Knowledge Repositories
|
|
8
|
+
|
|
9
|
+
- 📖 **[Living Ubiquitous Language Glossary](./docs/knowledge/ubiquitous_language.md)**: Authoritative, single-name domain vocabulary contract.
|
|
10
|
+
- 📜 **[Lightweight ADR Master Index](#adr-master-index)**: Summary of all architectural decisions and direct links to governing rules.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 2. Consolidated Architectural Decision Records (ADRs)
|
|
15
|
+
|
|
16
|
+
### ADR Master Index
|
|
17
|
+
|
|
18
|
+
| ID | Title | Date | Status | Governing Rule / Skill |
|
|
19
|
+
|---|---|---|---|---|
|
|
20
|
+
| *(No decisions recorded yet)* | *Record initial architecture decisions during Phase 3 of /lets-build.* | *YYYY-MM-DD* | *ACCEPTED* | *e.g. [`clean_code.md`](./docs/rules/clean_code.md)* |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
### Lightweight Decision Summaries
|
|
25
|
+
|
|
26
|
+
<!--
|
|
27
|
+
Record project Architectural Decision Records (ADRs) below as decisions are finalized.
|
|
28
|
+
Format:
|
|
29
|
+
|
|
30
|
+
#### ADR-001: [Imperative Title]
|
|
31
|
+
- **Date:** YYYY-MM-DD | **Status:** ACCEPTED
|
|
32
|
+
- **Context:** Problem space, constraints, and operational context requiring a decision.
|
|
33
|
+
- **Decision:** Chosen architecture, invariants, and implementation patterns.
|
|
34
|
+
- **Consequences:** Positive benefits and deliberate trade-offs accepted.
|
|
35
|
+
- **Enforced In:** Relevant rule files in docs/rules/ or code paths.
|
|
36
|
+
-->
|
package/package.json
CHANGED
|
@@ -1,59 +1,62 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "azcodr",
|
|
3
|
-
"version": "1.5.
|
|
4
|
-
"description": "Enterprise Architecture & Agentic Engineering Starter Template",
|
|
5
|
-
"bin": {
|
|
6
|
-
"azcodr": "bin/azcodr.js"
|
|
7
|
-
},
|
|
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
|
-
},
|
|
17
|
-
"files": [
|
|
18
|
-
"bin",
|
|
19
|
-
"lib",
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
".
|
|
25
|
-
".
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"url": "https://github.com/prosubodh/azcodr
|
|
47
|
-
},
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "azcodr",
|
|
3
|
+
"version": "1.5.2",
|
|
4
|
+
"description": "Enterprise Architecture & Agentic Engineering Starter Template",
|
|
5
|
+
"bin": {
|
|
6
|
+
"azcodr": "bin/azcodr.js"
|
|
7
|
+
},
|
|
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
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"bin",
|
|
19
|
+
"lib",
|
|
20
|
+
"scripts",
|
|
21
|
+
"data",
|
|
22
|
+
".github",
|
|
23
|
+
"AGENTS.md",
|
|
24
|
+
"memory.md",
|
|
25
|
+
"README.md",
|
|
26
|
+
"docs",
|
|
27
|
+
".agents",
|
|
28
|
+
".gitignore",
|
|
29
|
+
".editorconfig",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"keywords": [
|
|
33
|
+
"starter-template",
|
|
34
|
+
"agentic",
|
|
35
|
+
"multi-tenant",
|
|
36
|
+
"enterprise-architecture",
|
|
37
|
+
"scaffolding",
|
|
38
|
+
"ai-coding",
|
|
39
|
+
"agents",
|
|
40
|
+
"npx"
|
|
41
|
+
],
|
|
42
|
+
"author": "Subodh Khanal <prosubodh+git@gmail.com>",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/prosubodh/azcodr.git"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/prosubodh/azcodr/issues"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/prosubodh/azcodr#readme",
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=18.0.0"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"test": "node --test",
|
|
57
|
+
"test:coverage": "node scripts/test_coverage.js",
|
|
58
|
+
"lint": "node --check bin/azcodr.js lib/index.js lib/scaffold.js tests/cli.test.js tests/scaffold.test.js scripts/test_coverage.js scripts/validate.js",
|
|
59
|
+
"validate": "node scripts/validate.js",
|
|
60
|
+
"prepublishOnly": "npm run lint && npm run test:coverage && npm run validate"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require('node:child_process');
|
|
5
|
+
|
|
6
|
+
// Native threshold flags and include filtering were added in Node v22.8.0.
|
|
7
|
+
// Fail closed on older runtimes instead of silently passing without a gate.
|
|
8
|
+
const [major, minor] = process.versions.node.split('.').map(Number);
|
|
9
|
+
const supportsThresholds = major > 22 || (major === 22 && minor >= 8);
|
|
10
|
+
if (!supportsThresholds) {
|
|
11
|
+
console.error(
|
|
12
|
+
`test:coverage requires Node >=22.8.0 for 100% threshold enforcement (current: ${process.versions.node}). ` +
|
|
13
|
+
`Upgrade Node or run 'npx --yes node@24 --test --experimental-test-coverage --test-coverage-branches=100 --test-coverage-functions=100 --test-coverage-lines=100'.`
|
|
14
|
+
);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const args = [
|
|
19
|
+
'--test',
|
|
20
|
+
'--experimental-test-coverage',
|
|
21
|
+
'--test-coverage-include=bin/**',
|
|
22
|
+
'--test-coverage-include=lib/**',
|
|
23
|
+
'--test-coverage-branches=100',
|
|
24
|
+
'--test-coverage-functions=100',
|
|
25
|
+
'--test-coverage-lines=100'
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const result = spawnSync(process.execPath, args, {
|
|
29
|
+
stdio: 'inherit',
|
|
30
|
+
env: process.env
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (result.error) {
|
|
34
|
+
console.error(result.error);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
process.exit(result.status ?? 0);
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
function parseArgs(argv) {
|
|
8
|
+
let root = '';
|
|
9
|
+
for (const arg of argv) {
|
|
10
|
+
if (arg === '--fix') continue;
|
|
11
|
+
if (!root) root = arg;
|
|
12
|
+
}
|
|
13
|
+
return root || process.cwd();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function main() {
|
|
17
|
+
const workspaceRoot = path.resolve(parseArgs(process.argv.slice(2)));
|
|
18
|
+
let errors = 0;
|
|
19
|
+
let warnings = 0;
|
|
20
|
+
|
|
21
|
+
const pass = (msg) => console.log(` ✅ ${msg}`);
|
|
22
|
+
const warn = (msg) => { warnings += 1; console.log(` ⚠️ ${msg}`); };
|
|
23
|
+
const fail = (msg) => { errors += 1; console.log(` ❌ ${msg}`); };
|
|
24
|
+
|
|
25
|
+
console.log(`🔍 Validating Agentic Architecture in: ${workspaceRoot}`);
|
|
26
|
+
console.log('--------------------------------------------------------------');
|
|
27
|
+
|
|
28
|
+
console.log('1. Checking Root Configuration & Symlinks...');
|
|
29
|
+
const agentsFile = path.join(workspaceRoot, 'AGENTS.md');
|
|
30
|
+
if (!fs.existsSync(agentsFile)) {
|
|
31
|
+
fail(`Missing root AGENTS.md at ${agentsFile}`);
|
|
32
|
+
} else {
|
|
33
|
+
pass('AGENTS.md exists.');
|
|
34
|
+
const lines = fs.readFileSync(agentsFile, 'utf-8').split('\n').length;
|
|
35
|
+
if (lines <= 120) pass(`AGENTS.md line count is lean: ${lines} lines (<= 120).`);
|
|
36
|
+
else if (lines <= 150) warn(`AGENTS.md line count is getting large: ${lines} lines (warn > 120).`);
|
|
37
|
+
else fail(`AGENTS.md exceeds maximum line limit: ${lines} lines (max 150).`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const agentsContent = fs.existsSync(agentsFile) ? fs.readFileSync(agentsFile, 'utf-8') : '';
|
|
41
|
+
|
|
42
|
+
function isValidAgentsTarget(target) {
|
|
43
|
+
const normalized = target.replace(/\\/g, '/');
|
|
44
|
+
const rootNormalized = agentsFile.replace(/\\/g, '/');
|
|
45
|
+
return normalized === 'AGENTS.md' || normalized === './AGENTS.md' || normalized === rootNormalized;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function checkParity(filePath, label, allowCopyFallback) {
|
|
49
|
+
let stat = null;
|
|
50
|
+
try { stat = fs.lstatSync(filePath); } catch { fail(`${label} is missing.`); return; }
|
|
51
|
+
if (stat.isSymbolicLink()) {
|
|
52
|
+
let target = '';
|
|
53
|
+
try { target = fs.readlinkSync(filePath); } catch { fail(`${label} readlink failed.`); return; }
|
|
54
|
+
const normalized = target.replace(/\\/g, '/');
|
|
55
|
+
const ok = label.endsWith('copilot-instructions.md')
|
|
56
|
+
? (normalized === '../AGENTS.md' || normalized === agentsFile.replace(/\\/g, '/') || normalized === 'AGENTS.md')
|
|
57
|
+
: isValidAgentsTarget(target);
|
|
58
|
+
if (ok) pass(`${label} is a valid symlink to AGENTS.md.`);
|
|
59
|
+
else fail(`${label} points to '${target}' instead of 'AGENTS.md'.`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (stat.isFile()) {
|
|
63
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
64
|
+
const trimmed = content.trim();
|
|
65
|
+
if (trimmed === 'AGENTS.md' || trimmed === './AGENTS.md' || trimmed === agentsFile) {
|
|
66
|
+
pass(`${label} is a text pointer to AGENTS.md (symlink fallback).`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (label.endsWith('copilot-instructions.md') && content.includes('AGENTS.md')) {
|
|
70
|
+
pass(`${label} references AGENTS.md (symlink fallback).`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (allowCopyFallback && agentsContent && content === agentsContent) {
|
|
74
|
+
warn(`${label} is a byte-identical copy of AGENTS.md (Windows symlink fallback; drift risk).`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (label === 'agents.md') {
|
|
78
|
+
try {
|
|
79
|
+
const entries = fs.readdirSync(workspaceRoot);
|
|
80
|
+
if (!entries.includes('agents.md') && entries.includes('AGENTS.md')) {
|
|
81
|
+
pass('agents.md is satisfied natively by AGENTS.md (case-insensitive filesystem).');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
} catch { /* fall through */ }
|
|
85
|
+
}
|
|
86
|
+
fail(`${label} is not a symbolic link.`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
fail(`${label} is neither a symlink nor a regular file.`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
checkParity(path.join(workspaceRoot, 'CLAUDE.md'), 'CLAUDE.md', true);
|
|
93
|
+
const lowerPath = path.join(workspaceRoot, 'agents.md');
|
|
94
|
+
try {
|
|
95
|
+
const entries = fs.readdirSync(workspaceRoot);
|
|
96
|
+
const hasUpper = entries.includes('AGENTS.md');
|
|
97
|
+
const hasLowerExact = entries.includes('agents.md');
|
|
98
|
+
if (hasUpper && !hasLowerExact) {
|
|
99
|
+
let appearsSame = false;
|
|
100
|
+
try { appearsSame = fs.existsSync(lowerPath); } catch { appearsSame = false; }
|
|
101
|
+
if (appearsSame) {
|
|
102
|
+
pass('agents.md is satisfied natively by AGENTS.md (case-insensitive filesystem).');
|
|
103
|
+
} else {
|
|
104
|
+
// Case-sensitive filesystem gap (agents.md cannot be committed alongside
|
|
105
|
+
// AGENTS.md from case-insensitive systems): restore parity in place.
|
|
106
|
+
let created = false;
|
|
107
|
+
try { fs.symlinkSync('AGENTS.md', lowerPath); created = true; }
|
|
108
|
+
catch {
|
|
109
|
+
try { fs.writeFileSync(lowerPath, 'AGENTS.md\n', 'utf-8'); created = true; }
|
|
110
|
+
catch { created = false; }
|
|
111
|
+
}
|
|
112
|
+
if (created) pass('Created agents.md parity link to AGENTS.md (case-sensitive filesystem).');
|
|
113
|
+
checkParity(lowerPath, 'agents.md', true);
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
checkParity(lowerPath, 'agents.md', true);
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
checkParity(lowerPath, 'agents.md', true);
|
|
120
|
+
}
|
|
121
|
+
checkParity(path.join(workspaceRoot, 'GEMINI.md'), 'GEMINI.md', true);
|
|
122
|
+
checkParity(path.join(workspaceRoot, '.cursorrules'), '.cursorrules', true);
|
|
123
|
+
checkParity(path.join(workspaceRoot, '.windsurfrules'), '.windsurfrules', true);
|
|
124
|
+
const githubDir = path.join(workspaceRoot, '.github');
|
|
125
|
+
if (fs.existsSync(githubDir) && fs.statSync(githubDir).isDirectory()) {
|
|
126
|
+
const copilot = path.join(githubDir, 'copilot-instructions.md');
|
|
127
|
+
if (fs.existsSync(copilot)) {
|
|
128
|
+
checkParity(copilot, '.github/copilot-instructions.md', true);
|
|
129
|
+
} else {
|
|
130
|
+
warn('.github/copilot-instructions.md is missing (run scaffold to restore harness parity).');
|
|
131
|
+
}
|
|
132
|
+
if (!fs.existsSync(path.join(githubDir, 'workflows'))) {
|
|
133
|
+
warn('.github/workflows is missing (CI will not run in scaffolded projects).');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (fs.existsSync(path.join(workspaceRoot, '.gitignore'))) pass('.gitignore exists.');
|
|
138
|
+
else fail('Missing .gitignore');
|
|
139
|
+
|
|
140
|
+
console.log('');
|
|
141
|
+
console.log('2. Checking Progressive Disclosure Rules...');
|
|
142
|
+
const rulesDir = path.join(workspaceRoot, 'docs', 'rules');
|
|
143
|
+
if (!fs.existsSync(rulesDir)) {
|
|
144
|
+
fail(`Missing docs/rules directory at ${rulesDir}`);
|
|
145
|
+
} else {
|
|
146
|
+
const files = fs.readdirSync(rulesDir).filter((f) => f.endsWith('.md'));
|
|
147
|
+
let count = 0;
|
|
148
|
+
for (const name of files) {
|
|
149
|
+
count += 1;
|
|
150
|
+
const fp = path.join(rulesDir, name);
|
|
151
|
+
const content = fs.readFileSync(fp, 'utf-8');
|
|
152
|
+
const stat = fs.statSync(fp);
|
|
153
|
+
if (!/^# /m.test(content)) fail(`Rule ${name} missing H1 header (# Title)`);
|
|
154
|
+
if (!/^> \*\*Core Mandate:\*\*/m.test(content)) warn(`Rule ${name} missing standardized '> **Core Mandate:**' summary`);
|
|
155
|
+
if (stat.size > 24000) warn(`Rule ${name} exceeds 24KB token-economy cap (${stat.size} bytes)`);
|
|
156
|
+
}
|
|
157
|
+
pass(`Validated ${count} modular rule files in docs/rules/.`);
|
|
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();
|