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.
Files changed (75) hide show
  1. package/.agents/hooks.json +42 -0
  2. package/.agents/hooks.json.example +42 -42
  3. package/.agents/mcp_config.json.example +29 -24
  4. package/.agents/scripts/safety_guard.sh +34 -16
  5. package/.agents/scripts/verify_completion.sh +27 -13
  6. package/.agents/skills/agentic-architect/SKILL.md +125 -125
  7. package/.agents/skills/agentic-architect/references/agents_md_template.md +62 -62
  8. package/.agents/skills/agentic-architect/references/refinement_workflow.md +32 -32
  9. package/.agents/skills/agentic-architect/references/skill_architecture_inquiry.md +63 -63
  10. package/.agents/skills/agentic-architect/references/skill_template.md +56 -56
  11. package/.agents/skills/agentic-architect/scripts/validate_agentic_configs.sh +402 -362
  12. package/.agents/skills/clean-code-refactor/SKILL.md +91 -91
  13. package/.agents/skills/clean-code-refactor/references/clean_code_smells.md +27 -27
  14. package/.agents/skills/clean-code-refactor/references/design_patterns_ts.md +65 -65
  15. package/.agents/skills/compliance-audit/SKILL.md +120 -120
  16. package/.agents/skills/compliance-audit/references/owasp_top10_controls.md +16 -16
  17. package/.agents/skills/compliance-audit/references/soc2_iso_controls.md +28 -28
  18. package/.agents/skills/lets-build/SKILL.md +173 -172
  19. package/.agents/skills/lets-build/references/architecture_interview_matrix.md +115 -115
  20. package/.agents/skills/lets-build/references/hexagonal_bootstrap_scaffolds.md +160 -160
  21. package/.agents/skills/lets-build/references/project_readme_template.md +79 -79
  22. package/.agents/skills/lets-build/scripts/bootstrap_workspace.sh +255 -253
  23. package/.agents/skills/product-analyst/SKILL.md +154 -154
  24. package/.agents/skills/product-analyst/references/backlog_ordering_techniques.md +107 -107
  25. package/.agents/skills/product-analyst/references/gherkin_patterns.md +46 -46
  26. package/.agents/skills/product-analyst/references/invest_checklist.md +38 -38
  27. package/.agents/skills/product-analyst/references/okr_alignment_guide.md +76 -76
  28. package/.agents/skills/product-analyst/references/smart_tasks.md +59 -59
  29. package/.agents/skills/relentless-questioner/SKILL.md +128 -128
  30. package/.agents/skills/relentless-questioner/references/adaptive_question_trees.md +102 -102
  31. package/.editorconfig +19 -19
  32. package/.github/copilot-instructions.md +1 -0
  33. package/.github/workflows/ci.yml +78 -0
  34. package/.gitignore +25 -25
  35. package/AGENTS.md +102 -102
  36. package/LICENSE +21 -21
  37. package/README.md +165 -154
  38. package/bin/azcodr.js +228 -228
  39. package/data/.gitkeep +0 -0
  40. package/docs/knowledge/ubiquitous_language.md +18 -18
  41. package/docs/rules/agentic_configuration.md +259 -259
  42. package/docs/rules/api_architecture.md +179 -179
  43. package/docs/rules/authentication.md +76 -76
  44. package/docs/rules/authorization.md +75 -75
  45. package/docs/rules/caching.md +69 -69
  46. package/docs/rules/clean_code.md +62 -62
  47. package/docs/rules/cloud_native.md +41 -41
  48. package/docs/rules/cqrs.md +203 -203
  49. package/docs/rules/database_design.md +125 -125
  50. package/docs/rules/database_operations.md +69 -69
  51. package/docs/rules/design_patterns.md +98 -98
  52. package/docs/rules/devops_ci_cd.md +76 -76
  53. package/docs/rules/domain_driven_design.md +122 -122
  54. package/docs/rules/error_handling.md +52 -52
  55. package/docs/rules/feature_flags.md +59 -59
  56. package/docs/rules/frontend_architecture.md +157 -157
  57. package/docs/rules/multitenancy_architecture.md +98 -98
  58. package/docs/rules/product_ownership.md +127 -127
  59. package/docs/rules/project_management.md +49 -49
  60. package/docs/rules/relentless_questioning.md +52 -52
  61. package/docs/rules/requirements_engineering.md +98 -98
  62. package/docs/rules/security_compliance.md +53 -53
  63. package/docs/rules/server_driven_ui.md +88 -88
  64. package/docs/rules/test_driven_development.md +185 -185
  65. package/docs/rules/transactional_email.md +27 -27
  66. package/docs/rules/type_safety.md +65 -65
  67. package/docs/rules/ui_ux_architecture.md +150 -150
  68. package/docs/rules/workflow_state_machines.md +117 -117
  69. package/lib/index.d.ts +134 -123
  70. package/lib/index.js +5 -5
  71. package/lib/scaffold.js +448 -351
  72. package/memory.md +36 -36
  73. package/package.json +62 -59
  74. package/scripts/test_coverage.js +38 -0
  75. package/scripts/validate.js +258 -0
@@ -1,362 +1,402 @@
1
- #!/usr/bin/env bash
2
- # ==============================================================================
3
- # validate_agentic_configs.sh
4
- # Deterministic Validator for Agentic Files, Rules, and Skills
5
- # ==============================================================================
6
-
7
- set -euo pipefail
8
-
9
- WORKSPACE_ROOT=""
10
- FIX_MODE=false
11
-
12
- for arg in "$@"; do
13
- if [[ "${arg}" == "--fix" ]]; then
14
- FIX_MODE=true
15
- elif [[ -z "${WORKSPACE_ROOT}" ]]; then
16
- WORKSPACE_ROOT="${arg}"
17
- fi
18
- done
19
-
20
- if [[ -z "${WORKSPACE_ROOT}" ]]; then
21
- WORKSPACE_ROOT="$(pwd)"
22
- fi
23
-
24
- ERRORS=0
25
- WARNINGS=0
26
-
27
- echo "🔍 Validating Agentic Architecture in: ${WORKSPACE_ROOT}"
28
- echo "--------------------------------------------------------------"
29
-
30
- # Helper print functions
31
- log_pass() { echo " ✅ $1"; }
32
- log_warn() { echo " ⚠️ $1"; WARNINGS=$((WARNINGS + 1)); }
33
- log_fail() { echo " ❌ $1"; ERRORS=$((ERRORS + 1)); }
34
-
35
- # 1. Root AGENTS.md & Symlinks Verification
36
- echo "1. Checking Root Configuration & Symlinks..."
37
- AGENTS_FILE="${WORKSPACE_ROOT}/AGENTS.md"
38
- if [[ ! -f "${AGENTS_FILE}" ]]; then
39
- log_fail "Missing root AGENTS.md at ${AGENTS_FILE}"
40
- else
41
- log_pass "AGENTS.md exists."
42
-
43
- LINE_COUNT=$(wc -l < "${AGENTS_FILE}")
44
- if [[ ${LINE_COUNT} -le 120 ]]; then
45
- log_pass "AGENTS.md line count is lean: ${LINE_COUNT} lines (<= 120)."
46
- elif [[ ${LINE_COUNT} -le 150 ]]; then
47
- log_warn "AGENTS.md line count is getting large: ${LINE_COUNT} lines (warn > 120)."
48
- else
49
- log_fail "AGENTS.md exceeds maximum line limit: ${LINE_COUNT} lines (max 150)."
50
- fi
51
- fi
52
-
53
- # Helper to check if symlink target resolves to AGENTS.md
54
- is_valid_agents_target() {
55
- local target="$1"
56
- [[ "${target}" == "AGENTS.md" || "${target}" == "./AGENTS.md" || "${target}" == "${WORKSPACE_ROOT}/AGENTS.md" ]]
57
- }
58
-
59
- is_valid_text_pointer() {
60
- local file="$1"
61
- local content
62
- content=$(< "${file}")
63
- [[ "${content}" == "AGENTS.md" || "${content}" == "./AGENTS.md" || "${content}" == "${WORKSPACE_ROOT}/AGENTS.md" ]]
64
- }
65
-
66
- # Check CLAUDE.md symlink
67
- CLAUDE_FILE="${WORKSPACE_ROOT}/CLAUDE.md"
68
- if [[ -L "${CLAUDE_FILE}" ]]; then
69
- TARGET=$(readlink "${CLAUDE_FILE}")
70
- if is_valid_agents_target "${TARGET}"; then
71
- log_pass "CLAUDE.md is a valid symlink to AGENTS.md."
72
- else
73
- log_fail "CLAUDE.md points to '${TARGET}' instead of 'AGENTS.md'."
74
- fi
75
- elif [[ -f "${CLAUDE_FILE}" ]] && is_valid_text_pointer "${CLAUDE_FILE}"; then
76
- log_pass "CLAUDE.md is a text pointer to AGENTS.md (symlink fallback)."
77
- else
78
- log_fail "CLAUDE.md is not a symbolic link."
79
- fi
80
-
81
- # Check agents.md symlink (case-insensitive filesystem aware)
82
- AGENTS_LOWER="${WORKSPACE_ROOT}/agents.md"
83
- IS_CASE_INSENSITIVE=false
84
- if [[ "$(uname -s)" == "Darwin" ]] || [[ "$(uname -s)" =~ (MINGW|MSYS|CYGWIN) ]]; then
85
- IS_CASE_INSENSITIVE=true
86
- elif [[ -f "${AGENTS_FILE}" ]] && [[ -f "${AGENTS_LOWER}" ]] && [[ ! -L "${AGENTS_LOWER}" ]]; then
87
- IS_CASE_INSENSITIVE=true
88
- fi
89
-
90
- if [[ "${IS_CASE_INSENSITIVE}" == "true" ]]; then
91
- log_pass "agents.md is satisfied natively by AGENTS.md (case-insensitive filesystem)."
92
- else
93
- if [[ ! -L "${AGENTS_LOWER}" ]] && [[ ! -e "${AGENTS_LOWER}" ]] && [[ -f "${AGENTS_FILE}" ]]; then
94
- if [[ "${FIX_MODE}" == "true" ]]; then
95
- ln -sf "AGENTS.md" "${AGENTS_LOWER}"
96
- log_pass "Created agents.md symlink to AGENTS.md (--fix mode)."
97
- else
98
- log_fail "agents.md is missing. Run with --fix to automatically repair symlinks."
99
- fi
100
- fi
101
- if [[ -L "${AGENTS_LOWER}" ]]; then
102
- TARGET=$(readlink "${AGENTS_LOWER}")
103
- if is_valid_agents_target "${TARGET}"; then
104
- log_pass "agents.md is a valid symlink to AGENTS.md."
105
- else
106
- log_fail "agents.md points to '${TARGET}' instead of 'AGENTS.md'."
107
- fi
108
- elif [[ -f "${AGENTS_LOWER}" ]] && is_valid_text_pointer "${AGENTS_LOWER}"; then
109
- log_pass "agents.md is a text pointer to AGENTS.md (symlink fallback)."
110
- elif [[ ! -e "${AGENTS_LOWER}" ]] && [[ "${FIX_MODE}" == "true" ]]; then
111
- : # Handled above
112
- else
113
- log_fail "agents.md is not a symbolic link."
114
- fi
115
- fi
116
-
117
- # Check GEMINI.md symlink
118
- GEMINI_FILE="${WORKSPACE_ROOT}/GEMINI.md"
119
- if [[ -L "${GEMINI_FILE}" ]]; then
120
- TARGET=$(readlink "${GEMINI_FILE}")
121
- if is_valid_agents_target "${TARGET}"; then
122
- log_pass "GEMINI.md is a valid symlink to AGENTS.md."
123
- else
124
- log_fail "GEMINI.md points to '${TARGET}' instead of 'AGENTS.md'."
125
- fi
126
- elif [[ -f "${GEMINI_FILE}" ]] && is_valid_text_pointer "${GEMINI_FILE}"; then
127
- log_pass "GEMINI.md is a text pointer to AGENTS.md (symlink fallback)."
128
- else
129
- log_fail "GEMINI.md is not a symbolic link."
130
- fi
131
-
132
- # Check .cursorrules symlink
133
- CURSOR_FILE="${WORKSPACE_ROOT}/.cursorrules"
134
- if [[ -L "${CURSOR_FILE}" ]]; then
135
- TARGET=$(readlink "${CURSOR_FILE}")
136
- if is_valid_agents_target "${TARGET}"; then
137
- log_pass ".cursorrules is a valid symlink to AGENTS.md."
138
- else
139
- log_fail ".cursorrules points to '${TARGET}' instead of 'AGENTS.md'."
140
- fi
141
- elif [[ -f "${CURSOR_FILE}" ]] && is_valid_text_pointer "${CURSOR_FILE}"; then
142
- log_pass ".cursorrules is a text pointer to AGENTS.md (symlink fallback)."
143
- else
144
- log_fail ".cursorrules is not a symbolic link."
145
- fi
146
-
147
- # Check .windsurfrules symlink
148
- WINDSURF_FILE="${WORKSPACE_ROOT}/.windsurfrules"
149
- if [[ -L "${WINDSURF_FILE}" ]]; then
150
- TARGET=$(readlink "${WINDSURF_FILE}")
151
- if is_valid_agents_target "${TARGET}"; then
152
- log_pass ".windsurfrules is a valid symlink to AGENTS.md."
153
- else
154
- log_fail ".windsurfrules points to '${TARGET}' instead of 'AGENTS.md'."
155
- fi
156
- elif [[ -f "${WINDSURF_FILE}" ]] && is_valid_text_pointer "${WINDSURF_FILE}"; then
157
- log_pass ".windsurfrules is a text pointer to AGENTS.md (symlink fallback)."
158
- else
159
- log_fail ".windsurfrules is not a symbolic link."
160
- fi
161
-
162
- # Check .github/copilot-instructions.md symlink (if .github directory exists)
163
- COPILOT_FILE="${WORKSPACE_ROOT}/.github/copilot-instructions.md"
164
- if [[ -d "${WORKSPACE_ROOT}/.github" ]]; then
165
- if [[ -L "${COPILOT_FILE}" ]]; then
166
- TARGET=$(readlink "${COPILOT_FILE}")
167
- if [[ "${TARGET}" == "../AGENTS.md" || "${TARGET}" == "${WORKSPACE_ROOT}/AGENTS.md" || "${TARGET}" == "AGENTS.md" ]]; then
168
- log_pass ".github/copilot-instructions.md is a valid symlink to AGENTS.md."
169
- else
170
- log_fail ".github/copilot-instructions.md points to '${TARGET}' instead of '../AGENTS.md'."
171
- fi
172
- elif [[ -f "${COPILOT_FILE}" ]] && [[ "$(< "${COPILOT_FILE}")" == *"AGENTS.md"* ]]; then
173
- log_pass ".github/copilot-instructions.md references AGENTS.md (symlink fallback)."
174
- elif [[ -f "${COPILOT_FILE}" ]]; then
175
- log_warn ".github/copilot-instructions.md exists but is neither a symlink to ../AGENTS.md nor references AGENTS.md."
176
- fi
177
- fi
178
-
179
- # Check .gitignore exists
180
- GITIGNORE_FILE="${WORKSPACE_ROOT}/.gitignore"
181
- if [[ -f "${GITIGNORE_FILE}" ]]; then
182
- log_pass ".gitignore exists."
183
- else
184
- log_fail "Missing .gitignore at ${GITIGNORE_FILE}"
185
- fi
186
-
187
- # 2. Checking Progressive Disclosure Rules (docs/rules)
188
- echo ""
189
- echo "2. Checking Progressive Disclosure Rules..."
190
- RULES_DIR="${WORKSPACE_ROOT}/docs/rules"
191
- if [[ ! -d "${RULES_DIR}" ]]; then
192
- log_fail "Missing docs/rules directory at ${RULES_DIR}"
193
- else
194
- RULE_COUNT=0
195
- for rule_file in "${RULES_DIR}"/*.md; do
196
- [[ -e "${rule_file}" ]] || continue
197
- RULE_COUNT=$((RULE_COUNT + 1))
198
- RULE_NAME=$(basename "${rule_file}")
199
-
200
- # Check for title
201
- if ! grep -q "^# " "${rule_file}"; then
202
- log_fail "Rule ${RULE_NAME} missing H1 header (# Title)"
203
- fi
204
-
205
- # Check for Core Mandate blockquote
206
- if ! grep -q "^> \*\*Core Mandate:\*\*" "${rule_file}"; then
207
- log_warn "Rule ${RULE_NAME} missing standardized '> **Core Mandate:**' summary"
208
- fi
209
- done
210
- log_pass "Validated ${RULE_COUNT} modular rule files in docs/rules/."
211
- fi
212
-
213
- # 3. Checking Skills Architecture (.agents/skills)
214
- echo ""
215
- echo "3. Checking Specialized Skills (.agents/skills)..."
216
- SKILLS_DIR="${WORKSPACE_ROOT}/.agents/skills"
217
- if [[ ! -d "${SKILLS_DIR}" ]]; then
218
- log_fail "Missing .agents/skills directory at ${SKILLS_DIR}"
219
- else
220
- SKILL_COUNT=0
221
- for skill_folder in "${SKILLS_DIR}"/*; do
222
- [[ -d "${skill_folder}" ]] || continue
223
- SKILL_NAME=$(basename "${skill_folder}")
224
- SKILL_FILE="${skill_folder}/SKILL.md"
225
- SKILL_COUNT=$((SKILL_COUNT + 1))
226
-
227
- if [[ ! -f "${SKILL_FILE}" ]]; then
228
- log_fail "Skill '${SKILL_NAME}' missing SKILL.md"
229
- continue
230
- fi
231
-
232
- # Check front matter existence
233
- if ! head -n 1 "${SKILL_FILE}" | grep -q "^---"; then
234
- log_fail "Skill '${SKILL_NAME}' missing opening front matter delimiter (---)"
235
- continue
236
- fi
237
-
238
- # Check closing front matter delimiter
239
- if ! awk 'NR > 1 && /^---[[:space:]]*$/ { found=1; exit } END { exit !found }' "${SKILL_FILE}"; then
240
- log_fail "Skill '${SKILL_NAME}' missing closing front matter delimiter (---)"
241
- fi
242
-
243
- # Check name field in front matter
244
- if ! grep -E "^name:[[:space:]]*${SKILL_NAME}" "${SKILL_FILE}" > /dev/null; then
245
- log_fail "Skill '${SKILL_NAME}' front matter 'name:' does not match directory name"
246
- fi
247
-
248
- # Check description
249
- DESC=$(grep -E "^description:" "${SKILL_FILE}" | sed -E 's/^description:[[:space:]]*//' || true)
250
- if [[ -z "${DESC}" ]]; then
251
- log_fail "Skill '${SKILL_NAME}' missing front matter 'description:'"
252
- else
253
- # Check imperative phrasing
254
- if [[ ! "${DESC}" =~ ^Use[[:space:]]when ]]; then
255
- log_warn "Skill '${SKILL_NAME}' description should start with imperative 'Use when...'"
256
- fi
257
-
258
- # Check negative boundary phrasing (Do not use / Do NOT use)
259
- if ! echo "${DESC}" | grep -qiE "(do not use|do NOT use)"; then
260
- log_warn "Skill '${SKILL_NAME}' description should specify negative boundaries ('Do not use for...')"
261
- fi
262
-
263
- # Check character length (< 1024)
264
- CHAR_LEN=${#DESC}
265
- if [[ ${CHAR_LEN} -gt 1024 ]]; then
266
- log_fail "Skill '${SKILL_NAME}' description exceeds 1024 chars (${CHAR_LEN} chars)"
267
- fi
268
- fi
269
-
270
- # Check body length (< 500 lines)
271
- SKILL_LINES=$(wc -l < "${SKILL_FILE}")
272
- if [[ ${SKILL_LINES} -gt 500 ]]; then
273
- log_warn "Skill '${SKILL_NAME}' exceeds 500 lines (${SKILL_LINES} lines). Offload details to references/."
274
- else
275
- log_pass "Skill '${SKILL_NAME}': ${SKILL_LINES} lines, description valid (${#DESC} chars)."
276
- fi
277
-
278
- # Check for Gotchas / What NOT to do section
279
- if ! grep -qi "What NOT to do" "${SKILL_FILE}" && ! grep -qi "Gotchas" "${SKILL_FILE}"; then
280
- log_warn "Skill '${SKILL_NAME}' missing mandatory 'Gotchas & What NOT to Do' section"
281
- fi
282
- done
283
- log_pass "Validated ${SKILL_COUNT} skills in .agents/skills/."
284
- fi
285
-
286
- # 4. Checking Markdown Internal Links & Cross-References
287
- echo ""
288
- echo "4. Checking Markdown Internal Links & Cross-References..."
289
- LINK_CHECK_RAW=$(node -e '
290
- const fs = require("fs");
291
- const path = require("path");
292
-
293
- const root = process.argv[1];
294
- const broken = [];
295
- let totalLinks = 0;
296
-
297
- function walk(dir) {
298
- const entries = fs.readdirSync(dir, { withFileTypes: true });
299
- for (const entry of entries) {
300
- if (entry.name === ".git" || entry.name === "node_modules") continue;
301
- const full = path.join(dir, entry.name);
302
- if (entry.isDirectory()) {
303
- walk(full);
304
- } else if (entry.isFile() && entry.name.endsWith(".md")) {
305
- checkFile(full);
306
- }
307
- }
308
- }
309
-
310
- function checkFile(filePath) {
311
- const content = fs.readFileSync(filePath, "utf8");
312
- const dir = path.dirname(filePath);
313
- const regex = /\[([^\]]+)\]\(([^)]+)\)/g;
314
- let match;
315
- while ((match = regex.exec(content)) !== null) {
316
- const target = match[2].trim();
317
- if (target.startsWith("http://") || target.startsWith("https://") || target.startsWith("mailto:") || target.startsWith("#") || target.startsWith("conversation://") || target.startsWith("file://")) {
318
- continue;
319
- }
320
- const cleanTarget = target.split("#")[0];
321
- if (!cleanTarget) continue;
322
- totalLinks++;
323
- const resolved = path.normalize(path.join(dir, cleanTarget));
324
- if (!fs.existsSync(resolved)) {
325
- broken.push(`${path.relative(root, filePath)} -> ${target}`);
326
- }
327
- }
328
- }
329
-
330
- walk(root);
331
- if (broken.length > 0) {
332
- console.log("BROKEN:" + broken.join("|"));
333
- process.exit(1);
334
- } else {
335
- console.log("OK:" + totalLinks);
336
- process.exit(0);
337
- }
338
- ' "${WORKSPACE_ROOT}" 2>&1) || true
339
-
340
- if [[ "${LINK_CHECK_RAW}" =~ ^OK:([0-9]+) ]]; then
341
- TOTAL_LINKS="${BASH_REMATCH[1]}"
342
- log_pass "Validated ${TOTAL_LINKS} internal links across workspace (0 broken links)."
343
- elif [[ "${LINK_CHECK_RAW}" =~ ^BROKEN:(.*) ]]; then
344
- BROKEN_LIST="${BASH_REMATCH[1]}"
345
- IFS='|' read -ra BROKEN_ITEMS <<< "${BROKEN_LIST}"
346
- for item in "${BROKEN_ITEMS[@]}"; do
347
- log_fail "Broken markdown link: ${item}"
348
- done
349
- else
350
- log_fail "Markdown link validation failed unexpectedly: ${LINK_CHECK_RAW}"
351
- fi
352
-
353
- # 5. Summary Output
354
- echo ""
355
- echo "--------------------------------------------------------------"
356
- if [[ ${ERRORS} -eq 0 ]]; then
357
- echo "🎉 SUCCESS: All agentic configurations are valid and healthy! (${WARNINGS} warnings)"
358
- exit 0
359
- else
360
- echo "🚨 FAILURE: Found ${ERRORS} error(s) and ${WARNINGS} warning(s) in agentic configurations."
361
- exit 1
362
- fi
1
+ #!/usr/bin/env bash
2
+ # ==============================================================================
3
+ # validate_agentic_configs.sh
4
+ # Deterministic Validator for Agentic Files, Rules, and Skills
5
+ # ==============================================================================
6
+
7
+ set -euo pipefail
8
+
9
+ WORKSPACE_ROOT=""
10
+ FIX_MODE=false
11
+
12
+ for arg in "$@"; do
13
+ if [[ "${arg}" == "--fix" ]]; then
14
+ FIX_MODE=true
15
+ elif [[ -z "${WORKSPACE_ROOT}" ]]; then
16
+ WORKSPACE_ROOT="${arg}"
17
+ fi
18
+ done
19
+
20
+ if [[ -z "${WORKSPACE_ROOT}" ]]; then
21
+ WORKSPACE_ROOT="$(pwd)"
22
+ fi
23
+
24
+ ERRORS=0
25
+ WARNINGS=0
26
+
27
+ echo "🔍 Validating Agentic Architecture in: ${WORKSPACE_ROOT}"
28
+ echo "--------------------------------------------------------------"
29
+
30
+ # Helper print functions
31
+ log_pass() { echo " ✅ $1"; }
32
+ log_warn() { echo " ⚠️ $1"; WARNINGS=$((WARNINGS + 1)); }
33
+ log_fail() { echo " ❌ $1"; ERRORS=$((ERRORS + 1)); }
34
+
35
+ # 1. Root AGENTS.md & Symlinks Verification
36
+ echo "1. Checking Root Configuration & Symlinks..."
37
+ AGENTS_FILE="${WORKSPACE_ROOT}/AGENTS.md"
38
+ if [[ ! -f "${AGENTS_FILE}" ]]; then
39
+ log_fail "Missing root AGENTS.md at ${AGENTS_FILE}"
40
+ else
41
+ log_pass "AGENTS.md exists."
42
+
43
+ LINE_COUNT=$(wc -l < "${AGENTS_FILE}")
44
+ if [[ ${LINE_COUNT} -le 120 ]]; then
45
+ log_pass "AGENTS.md line count is lean: ${LINE_COUNT} lines (<= 120)."
46
+ elif [[ ${LINE_COUNT} -le 150 ]]; then
47
+ log_warn "AGENTS.md line count is getting large: ${LINE_COUNT} lines (warn > 120)."
48
+ else
49
+ log_fail "AGENTS.md exceeds maximum line limit: ${LINE_COUNT} lines (max 150)."
50
+ fi
51
+ fi
52
+
53
+ # Helper to check if symlink target resolves to AGENTS.md
54
+ is_valid_agents_target() {
55
+ local target="$1"
56
+ [[ "${target}" == "AGENTS.md" || "${target}" == "./AGENTS.md" || "${target}" == "${WORKSPACE_ROOT}/AGENTS.md" ]]
57
+ }
58
+
59
+ is_valid_text_pointer() {
60
+ local file="$1"
61
+ local content
62
+ content=$(< "${file}")
63
+ [[ "${content}" == "AGENTS.md" || "${content}" == "./AGENTS.md" || "${content}" == "${WORKSPACE_ROOT}/AGENTS.md" ]]
64
+ }
65
+
66
+ is_identical_copy() {
67
+ local file="$1"
68
+ [[ -f "${file}" ]] && [[ -f "${AGENTS_FILE}" ]] && cmp -s "${file}" "${AGENTS_FILE}"
69
+ }
70
+
71
+ # Check CLAUDE.md symlink
72
+ CLAUDE_FILE="${WORKSPACE_ROOT}/CLAUDE.md"
73
+ if [[ -L "${CLAUDE_FILE}" ]]; then
74
+ TARGET=$(readlink "${CLAUDE_FILE}")
75
+ if is_valid_agents_target "${TARGET}"; then
76
+ log_pass "CLAUDE.md is a valid symlink to AGENTS.md."
77
+ else
78
+ log_fail "CLAUDE.md points to '${TARGET}' instead of 'AGENTS.md'."
79
+ fi
80
+ elif [[ -f "${CLAUDE_FILE}" ]] && is_valid_text_pointer "${CLAUDE_FILE}"; then
81
+ log_pass "CLAUDE.md is a text pointer to AGENTS.md (symlink fallback)."
82
+ elif is_identical_copy "${CLAUDE_FILE}"; then
83
+ log_warn "CLAUDE.md is a byte-identical copy of AGENTS.md (Windows symlink fallback; drift risk)."
84
+ else
85
+ log_fail "CLAUDE.md is not a symbolic link."
86
+ fi
87
+
88
+ # Check agents.md symlink (case-insensitive filesystem aware)
89
+ AGENTS_LOWER="${WORKSPACE_ROOT}/agents.md"
90
+ IS_CASE_INSENSITIVE=false
91
+ if [[ "$(uname -s)" == "Darwin" ]] || [[ "$(uname -s)" =~ (MINGW|MSYS|CYGWIN) ]] || [[ "${OS:-}" == "Windows_NT" ]]; then
92
+ IS_CASE_INSENSITIVE=true
93
+ elif [[ -f "${AGENTS_FILE}" ]] && [[ -f "${AGENTS_LOWER}" ]] && [[ ! -L "${AGENTS_LOWER}" ]]; then
94
+ IS_CASE_INSENSITIVE=true
95
+ fi
96
+
97
+ if [[ "${IS_CASE_INSENSITIVE}" == "true" ]]; then
98
+ log_pass "agents.md is satisfied natively by AGENTS.md (case-insensitive filesystem)."
99
+ else
100
+ if [[ ! -L "${AGENTS_LOWER}" ]] && [[ ! -e "${AGENTS_LOWER}" ]] && [[ -f "${AGENTS_FILE}" ]]; then
101
+ ln -sf "AGENTS.md" "${AGENTS_LOWER}"
102
+ log_pass "Created agents.md symlink to AGENTS.md (case-sensitive filesystem parity)."
103
+ fi
104
+ if [[ -L "${AGENTS_LOWER}" ]]; then
105
+ TARGET=$(readlink "${AGENTS_LOWER}")
106
+ if is_valid_agents_target "${TARGET}"; then
107
+ log_pass "agents.md is a valid symlink to AGENTS.md."
108
+ else
109
+ log_fail "agents.md points to '${TARGET}' instead of 'AGENTS.md'."
110
+ fi
111
+ elif [[ -f "${AGENTS_LOWER}" ]] && is_valid_text_pointer "${AGENTS_LOWER}"; then
112
+ log_pass "agents.md is a text pointer to AGENTS.md (symlink fallback)."
113
+ elif is_identical_copy "${AGENTS_LOWER}"; then
114
+ log_warn "agents.md is a byte-identical copy of AGENTS.md (Windows symlink fallback; drift risk)."
115
+ else
116
+ log_fail "agents.md is not a symbolic link."
117
+ fi
118
+ fi
119
+
120
+ # Check GEMINI.md symlink
121
+ GEMINI_FILE="${WORKSPACE_ROOT}/GEMINI.md"
122
+ if [[ -L "${GEMINI_FILE}" ]]; then
123
+ TARGET=$(readlink "${GEMINI_FILE}")
124
+ if is_valid_agents_target "${TARGET}"; then
125
+ log_pass "GEMINI.md is a valid symlink to AGENTS.md."
126
+ else
127
+ log_fail "GEMINI.md points to '${TARGET}' instead of 'AGENTS.md'."
128
+ fi
129
+ elif [[ -f "${GEMINI_FILE}" ]] && is_valid_text_pointer "${GEMINI_FILE}"; then
130
+ log_pass "GEMINI.md is a text pointer to AGENTS.md (symlink fallback)."
131
+ elif is_identical_copy "${GEMINI_FILE}"; then
132
+ log_warn "GEMINI.md is a byte-identical copy of AGENTS.md (Windows symlink fallback; drift risk)."
133
+ else
134
+ log_fail "GEMINI.md is not a symbolic link."
135
+ fi
136
+
137
+ # Check .cursorrules symlink
138
+ CURSOR_FILE="${WORKSPACE_ROOT}/.cursorrules"
139
+ if [[ -L "${CURSOR_FILE}" ]]; then
140
+ TARGET=$(readlink "${CURSOR_FILE}")
141
+ if is_valid_agents_target "${TARGET}"; then
142
+ log_pass ".cursorrules is a valid symlink to AGENTS.md."
143
+ else
144
+ log_fail ".cursorrules points to '${TARGET}' instead of 'AGENTS.md'."
145
+ fi
146
+ elif [[ -f "${CURSOR_FILE}" ]] && is_valid_text_pointer "${CURSOR_FILE}"; then
147
+ log_pass ".cursorrules is a text pointer to AGENTS.md (symlink fallback)."
148
+ elif is_identical_copy "${CURSOR_FILE}"; then
149
+ log_warn ".cursorrules is a byte-identical copy of AGENTS.md (Windows symlink fallback; drift risk)."
150
+ else
151
+ log_fail ".cursorrules is not a symbolic link."
152
+ fi
153
+
154
+ # Check .windsurfrules symlink
155
+ WINDSURF_FILE="${WORKSPACE_ROOT}/.windsurfrules"
156
+ if [[ -L "${WINDSURF_FILE}" ]]; then
157
+ TARGET=$(readlink "${WINDSURF_FILE}")
158
+ if is_valid_agents_target "${TARGET}"; then
159
+ log_pass ".windsurfrules is a valid symlink to AGENTS.md."
160
+ else
161
+ log_fail ".windsurfrules points to '${TARGET}' instead of 'AGENTS.md'."
162
+ fi
163
+ elif [[ -f "${WINDSURF_FILE}" ]] && is_valid_text_pointer "${WINDSURF_FILE}"; then
164
+ log_pass ".windsurfrules is a text pointer to AGENTS.md (symlink fallback)."
165
+ elif is_identical_copy "${WINDSURF_FILE}"; then
166
+ log_warn ".windsurfrules is a byte-identical copy of AGENTS.md (Windows symlink fallback; drift risk)."
167
+ else
168
+ log_fail ".windsurfrules is not a symbolic link."
169
+ fi
170
+
171
+ # Check .github/copilot-instructions.md symlink (if .github directory exists)
172
+ COPILOT_FILE="${WORKSPACE_ROOT}/.github/copilot-instructions.md"
173
+ if [[ -d "${WORKSPACE_ROOT}/.github" ]]; then
174
+ if [[ -L "${COPILOT_FILE}" ]]; then
175
+ TARGET=$(readlink "${COPILOT_FILE}")
176
+ TARGET="${TARGET//\\//}"
177
+ if [[ "${TARGET}" == "../AGENTS.md" || "${TARGET}" == "${WORKSPACE_ROOT}/AGENTS.md" || "${TARGET}" == "AGENTS.md" ]]; then
178
+ log_pass ".github/copilot-instructions.md is a valid symlink to AGENTS.md."
179
+ else
180
+ log_fail ".github/copilot-instructions.md points to '${TARGET}' instead of '../AGENTS.md'."
181
+ fi
182
+ elif [[ -f "${COPILOT_FILE}" ]] && [[ "$(< "${COPILOT_FILE}")" == *"AGENTS.md"* ]]; then
183
+ log_pass ".github/copilot-instructions.md references AGENTS.md (symlink fallback)."
184
+ elif [[ -f "${COPILOT_FILE}" ]]; then
185
+ log_warn ".github/copilot-instructions.md exists but is neither a symlink to ../AGENTS.md nor references AGENTS.md."
186
+ fi
187
+ fi
188
+
189
+ # Check .gitignore exists
190
+ GITIGNORE_FILE="${WORKSPACE_ROOT}/.gitignore"
191
+ if [[ -f "${GITIGNORE_FILE}" ]]; then
192
+ log_pass ".gitignore exists."
193
+ else
194
+ log_fail "Missing .gitignore at ${GITIGNORE_FILE}"
195
+ fi
196
+
197
+ # Check .github/workflows exists (CI must propagate to scaffolded projects)
198
+ if [[ -d "${WORKSPACE_ROOT}/.github/workflows" ]]; then
199
+ log_pass ".github/workflows exists."
200
+ else
201
+ log_warn ".github/workflows is missing (CI will not run in scaffolded projects)."
202
+ fi
203
+
204
+ # 2. Checking Progressive Disclosure Rules (docs/rules)
205
+ echo ""
206
+ echo "2. Checking Progressive Disclosure Rules..."
207
+ RULES_DIR="${WORKSPACE_ROOT}/docs/rules"
208
+ if [[ ! -d "${RULES_DIR}" ]]; then
209
+ log_fail "Missing docs/rules directory at ${RULES_DIR}"
210
+ else
211
+ RULE_COUNT=0
212
+ for rule_file in "${RULES_DIR}"/*.md; do
213
+ [[ -e "${rule_file}" ]] || continue
214
+ RULE_COUNT=$((RULE_COUNT + 1))
215
+ RULE_NAME=$(basename "${rule_file}")
216
+
217
+ # Check for title
218
+ if ! grep -q "^# " "${rule_file}"; then
219
+ log_fail "Rule ${RULE_NAME} missing H1 header (# Title)"
220
+ fi
221
+
222
+ # Check for Core Mandate blockquote
223
+ if ! grep -q "^> \*\*Core Mandate:\*\*" "${rule_file}"; then
224
+ log_warn "Rule ${RULE_NAME} missing standardized '> **Core Mandate:**' summary"
225
+ fi
226
+
227
+ # Token-economy size cap (24KB)
228
+ RULE_SIZE=$(wc -c < "${rule_file}")
229
+ if [[ ${RULE_SIZE} -gt 24000 ]]; then
230
+ log_warn "Rule ${RULE_NAME} exceeds 24KB token-economy cap (${RULE_SIZE} bytes)"
231
+ fi
232
+ done
233
+ log_pass "Validated ${RULE_COUNT} modular rule files in docs/rules/."
234
+ fi
235
+
236
+ # 3. Checking Skills Architecture (.agents/skills)
237
+ echo ""
238
+ echo "3. Checking Specialized Skills (.agents/skills)..."
239
+ SKILLS_DIR="${WORKSPACE_ROOT}/.agents/skills"
240
+ if [[ ! -d "${SKILLS_DIR}" ]]; then
241
+ log_fail "Missing .agents/skills directory at ${SKILLS_DIR}"
242
+ else
243
+ SKILL_COUNT=0
244
+ for skill_folder in "${SKILLS_DIR}"/*; do
245
+ [[ -d "${skill_folder}" ]] || continue
246
+ SKILL_NAME=$(basename "${skill_folder}")
247
+ SKILL_FILE="${skill_folder}/SKILL.md"
248
+ SKILL_COUNT=$((SKILL_COUNT + 1))
249
+
250
+ if [[ ! -f "${SKILL_FILE}" ]]; then
251
+ log_fail "Skill '${SKILL_NAME}' missing SKILL.md"
252
+ continue
253
+ fi
254
+
255
+ # Check front matter existence
256
+ if ! head -n 1 "${SKILL_FILE}" | grep -q "^---"; then
257
+ log_fail "Skill '${SKILL_NAME}' missing opening front matter delimiter (---)"
258
+ continue
259
+ fi
260
+
261
+ # Check closing front matter delimiter
262
+ if ! awk 'NR > 1 && /^---[[:space:]]*$/ { found=1; exit } END { exit !found }' "${SKILL_FILE}"; then
263
+ log_fail "Skill '${SKILL_NAME}' missing closing front matter delimiter (---)"
264
+ fi
265
+
266
+ # Check name field in front matter
267
+ if ! grep -E "^name:[[:space:]]*${SKILL_NAME}" "${SKILL_FILE}" > /dev/null; then
268
+ log_fail "Skill '${SKILL_NAME}' front matter 'name:' does not match directory name"
269
+ fi
270
+
271
+ # Check description
272
+ DESC=$(grep -E "^description:" "${SKILL_FILE}" | sed -E 's/^description:[[:space:]]*//' || true)
273
+ if [[ -z "${DESC}" ]]; then
274
+ log_fail "Skill '${SKILL_NAME}' missing front matter 'description:'"
275
+ else
276
+ # Check imperative phrasing
277
+ if [[ ! "${DESC}" =~ ^Use[[:space:]]when ]]; then
278
+ log_warn "Skill '${SKILL_NAME}' description should start with imperative 'Use when...'"
279
+ fi
280
+
281
+ # Check negative boundary phrasing (Do not use / Do NOT use)
282
+ if ! echo "${DESC}" | grep -qiE "(do not use|do NOT use)"; then
283
+ log_warn "Skill '${SKILL_NAME}' description should specify negative boundaries ('Do not use for...')"
284
+ fi
285
+
286
+ # Check character length (< 1024)
287
+ CHAR_LEN=${#DESC}
288
+ if [[ ${CHAR_LEN} -gt 1024 ]]; then
289
+ log_fail "Skill '${SKILL_NAME}' description exceeds 1024 chars (${CHAR_LEN} chars)"
290
+ fi
291
+ fi
292
+
293
+ # Check body length (< 500 lines)
294
+ SKILL_LINES=$(wc -l < "${SKILL_FILE}")
295
+ if [[ ${SKILL_LINES} -gt 500 ]]; then
296
+ log_warn "Skill '${SKILL_NAME}' exceeds 500 lines (${SKILL_LINES} lines). Offload details to references/."
297
+ else
298
+ log_pass "Skill '${SKILL_NAME}': ${SKILL_LINES} lines, description valid (${#DESC} chars)."
299
+ fi
300
+
301
+ # Check for Gotchas / What NOT to do section
302
+ if ! grep -qi "What NOT to do" "${SKILL_FILE}" && ! grep -qi "Gotchas" "${SKILL_FILE}"; then
303
+ log_warn "Skill '${SKILL_NAME}' missing mandatory 'Gotchas & What NOT to Do' section"
304
+ fi
305
+ done
306
+ log_pass "Validated ${SKILL_COUNT} skills in .agents/skills/."
307
+ fi
308
+
309
+ # 4. Checking Markdown Internal Links & Cross-References
310
+ echo ""
311
+ echo "4. Checking Markdown Internal Links & Cross-References..."
312
+ LINK_CHECK_RAW=$(node -e '
313
+ const fs = require("fs");
314
+ const path = require("path");
315
+
316
+ const root = process.argv[1];
317
+ const broken = [];
318
+ let totalLinks = 0;
319
+
320
+ function walk(dir) {
321
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
322
+ for (const entry of entries) {
323
+ if (entry.name === ".git" || entry.name === "node_modules") continue;
324
+ const full = path.join(dir, entry.name);
325
+ if (entry.isDirectory()) {
326
+ walk(full);
327
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
328
+ checkFile(full);
329
+ }
330
+ }
331
+ }
332
+
333
+ function checkFile(filePath) {
334
+ const content = fs.readFileSync(filePath, "utf8");
335
+ const dir = path.dirname(filePath);
336
+ const regex = /\[([^\]]+)\]\(([^)]+)\)/g;
337
+ let match;
338
+ while ((match = regex.exec(content)) !== null) {
339
+ const target = match[2].trim();
340
+ if (target.startsWith("http://") || target.startsWith("https://") || target.startsWith("mailto:") || target.startsWith("#") || target.startsWith("conversation://") || target.startsWith("file://")) {
341
+ continue;
342
+ }
343
+ const cleanTarget = target.split("#")[0];
344
+ if (!cleanTarget) continue;
345
+ totalLinks++;
346
+ const resolved = path.normalize(path.join(dir, cleanTarget));
347
+ if (!fs.existsSync(resolved)) {
348
+ broken.push(`${path.relative(root, filePath)} -> ${target}`);
349
+ }
350
+ }
351
+ }
352
+
353
+ walk(root);
354
+ if (broken.length > 0) {
355
+ console.log("BROKEN:" + broken.join("|"));
356
+ process.exit(1);
357
+ } else {
358
+ console.log("OK:" + totalLinks);
359
+ process.exit(0);
360
+ }
361
+ ' "${WORKSPACE_ROOT}" 2>&1) || true
362
+
363
+ if [[ "${LINK_CHECK_RAW}" =~ ^OK:([0-9]+) ]]; then
364
+ TOTAL_LINKS="${BASH_REMATCH[1]}"
365
+ log_pass "Validated ${TOTAL_LINKS} internal links across workspace (0 broken links)."
366
+ elif [[ "${LINK_CHECK_RAW}" =~ ^BROKEN:(.*) ]]; then
367
+ BROKEN_LIST="${BASH_REMATCH[1]}"
368
+ IFS='|' read -ra BROKEN_ITEMS <<< "${BROKEN_LIST}"
369
+ for item in "${BROKEN_ITEMS[@]}"; do
370
+ log_fail "Broken markdown link: ${item}"
371
+ done
372
+ else
373
+ log_fail "Markdown link validation failed unexpectedly: ${LINK_CHECK_RAW}"
374
+ fi
375
+
376
+ # 5. Checking Memory & ADR Ledger
377
+ echo ""
378
+ echo "5. Checking Memory & ADR Ledger..."
379
+ MEMORY_FILE="${WORKSPACE_ROOT}/memory.md"
380
+ if [[ ! -f "${MEMORY_FILE}" ]]; then
381
+ log_fail "Missing memory.md ADR ledger."
382
+ else
383
+ MEMORY_BODY=$(sed '/<!--/,/-->/d' "${MEMORY_FILE}")
384
+ if ! grep -q "^# " "${MEMORY_FILE}"; then
385
+ log_fail "memory.md missing H1 header"
386
+ elif echo "${MEMORY_BODY}" | grep -q "#### ADR-"; then
387
+ log_pass "memory.md contains ADR entries with valid envelope."
388
+ else
389
+ log_pass "memory.md is a clean slate (no ADRs yet; record ADR-001 during /lets-build)."
390
+ fi
391
+ fi
392
+
393
+ # 6. Summary Output
394
+ echo ""
395
+ echo "--------------------------------------------------------------"
396
+ if [[ ${ERRORS} -eq 0 ]]; then
397
+ echo "🎉 SUCCESS: All agentic configurations are valid and healthy! (${WARNINGS} warnings)"
398
+ exit 0
399
+ else
400
+ echo "🚨 FAILURE: Found ${ERRORS} error(s) and ${WARNINGS} warning(s) in agentic configurations."
401
+ exit 1
402
+ fi