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
|
@@ -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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
else
|
|
78
|
-
|
|
79
|
-
fi
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
else
|
|
144
|
-
|
|
145
|
-
fi
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if [[
|
|
192
|
-
|
|
193
|
-
else
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if [[ -
|
|
251
|
-
log_fail "Skill '${SKILL_NAME}' missing
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
fi
|
|
285
|
-
|
|
286
|
-
#
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|