azcodr 1.4.0 → 1.5.1

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