self-evolve-framework 1.1.0 → 1.1.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 (121) hide show
  1. package/package.json +1 -1
  2. package/template/rules/CodeGraph.mdc +0 -23
  3. package/template/rules/Svelte_5.mdc +0 -167
  4. package/template/rules/Svelte_Flow.mdc +0 -176
  5. package/template/rules/Tailwind_CSS_v4.mdc +0 -187
  6. package/template/rules/Tauri.mdc +0 -145
  7. package/template/rules/app-error-pattern.mdc +0 -65
  8. package/template/rules/invoke-safe-pattern.mdc +0 -53
  9. package/template/rules/js.mdc +0 -10
  10. package/template/rules/powershell.mdc +0 -9
  11. package/template/rules//346/227/245/345/277/227.mdc +0 -15
  12. package/template/rules//350/257/267/346/261/202.mdc +0 -49
  13. package/template/skills/caveman/SKILL.md +0 -49
  14. package/template/skills/check/SKILL.md +0 -393
  15. package/template/skills/check/agents/reviewer-architecture.md +0 -39
  16. package/template/skills/check/agents/reviewer-security.md +0 -39
  17. package/template/skills/check/references/persona-catalog.md +0 -56
  18. package/template/skills/check/references/project-context.md +0 -120
  19. package/template/skills/check/references/public-reply.md +0 -14
  20. package/template/skills/check/scripts/audit_signals.py +0 -666
  21. package/template/skills/check/scripts/run-tests.sh +0 -19
  22. package/template/skills/design/SKILL.md +0 -173
  23. package/template/skills/design/references/design-aesthetic-quality.md +0 -67
  24. package/template/skills/design/references/design-data-viz.md +0 -34
  25. package/template/skills/design/references/design-reference.md +0 -295
  26. package/template/skills/design/references/design-tokens.md +0 -45
  27. package/template/skills/design/references/design-traps.md +0 -43
  28. package/template/skills/design-an-interface/SKILL.md +0 -94
  29. package/template/skills/diagnose/SKILL.md +0 -117
  30. package/template/skills/diagnose/scripts/hitl-loop.template.sh +0 -41
  31. package/template/skills/edit-article/SKILL.md +0 -14
  32. package/template/skills/git-guardrails-claude-code/SKILL.md +0 -95
  33. package/template/skills/git-guardrails-claude-code/scripts/block-dangerous-git.sh +0 -25
  34. package/template/skills/grill-me/SKILL.md +0 -10
  35. package/template/skills/grill-with-docs/ADR-FORMAT.md +0 -47
  36. package/template/skills/grill-with-docs/CONTEXT-FORMAT.md +0 -60
  37. package/template/skills/grill-with-docs/SKILL.md +0 -88
  38. package/template/skills/handoff/SKILL.md +0 -15
  39. package/template/skills/health/SKILL.md +0 -260
  40. package/template/skills/health/agents/inspector-context.md +0 -119
  41. package/template/skills/health/agents/inspector-control.md +0 -84
  42. package/template/skills/health/agents/inspector-maintainability.md +0 -55
  43. package/template/skills/health/scripts/check-agent-context.sh +0 -5
  44. package/template/skills/health/scripts/check-doc-refs.sh +0 -8
  45. package/template/skills/health/scripts/check-maintainability.sh +0 -8
  46. package/template/skills/health/scripts/check-verifier-output.sh +0 -5
  47. package/template/skills/health/scripts/check_agent_context.py +0 -444
  48. package/template/skills/health/scripts/check_doc_refs.py +0 -110
  49. package/template/skills/health/scripts/check_maintainability.py +0 -635
  50. package/template/skills/health/scripts/check_verifier_output.py +0 -116
  51. package/template/skills/health/scripts/collect-data.sh +0 -751
  52. package/template/skills/hunt/SKILL.md +0 -232
  53. package/template/skills/hunt/references/failure-patterns.md +0 -138
  54. package/template/skills/hunt/references/ime-unicode.md +0 -58
  55. package/template/skills/hunt/references/logging-techniques.md +0 -72
  56. package/template/skills/hunt/references/rendering-debug.md +0 -34
  57. package/template/skills/improve-codebase-architecture/DEEPENING.md +0 -37
  58. package/template/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
  59. package/template/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +0 -44
  60. package/template/skills/improve-codebase-architecture/LANGUAGE.md +0 -53
  61. package/template/skills/improve-codebase-architecture/SKILL.md +0 -81
  62. package/template/skills/learn/SKILL.md +0 -140
  63. package/template/skills/migrate-to-shoehorn/SKILL.md +0 -118
  64. package/template/skills/obsidian-vault/SKILL.md +0 -59
  65. package/template/skills/prototype/LOGIC.md +0 -79
  66. package/template/skills/prototype/SKILL.md +0 -30
  67. package/template/skills/prototype/UI.md +0 -112
  68. package/template/skills/qa/SKILL.md +0 -130
  69. package/template/skills/read/SKILL.md +0 -141
  70. package/template/skills/read/references/read-methods.md +0 -129
  71. package/template/skills/read/scripts/fetch.sh +0 -106
  72. package/template/skills/read/scripts/fetch_feishu.py +0 -251
  73. package/template/skills/read/scripts/fetch_local.py +0 -218
  74. package/template/skills/read/scripts/fetch_weixin.py +0 -107
  75. package/template/skills/request-refactor-plan/SKILL.md +0 -68
  76. package/template/skills/review/SKILL.md +0 -78
  77. package/template/skills/rust-auto-fix/SKILL.md +0 -94
  78. package/template/skills/scaffold-exercises/SKILL.md +0 -106
  79. package/template/skills/sdd-dev/SKILL.md +0 -114
  80. package/template/skills/setup-matt-pocock-skills/SKILL.md +0 -121
  81. package/template/skills/setup-matt-pocock-skills/domain.md +0 -51
  82. package/template/skills/setup-matt-pocock-skills/issue-tracker-github.md +0 -22
  83. package/template/skills/setup-matt-pocock-skills/issue-tracker-gitlab.md +0 -23
  84. package/template/skills/setup-matt-pocock-skills/issue-tracker-local.md +0 -19
  85. package/template/skills/setup-matt-pocock-skills/triage-labels.md +0 -15
  86. package/template/skills/setup-pre-commit/SKILL.md +0 -91
  87. package/template/skills/svelte-warnings-fix/SKILL.md +0 -94
  88. package/template/skills/tauri-nsis-installer-icon/SKILL.md +0 -92
  89. package/template/skills/tauri-nsis-installer-icon/references/tauri-nsis-schema.md +0 -71
  90. package/template/skills/tb/SKILL.md +0 -62
  91. package/template/skills/tdd/SKILL.md +0 -109
  92. package/template/skills/tdd/deep-modules.md +0 -33
  93. package/template/skills/tdd/interface-design.md +0 -31
  94. package/template/skills/tdd/mocking.md +0 -59
  95. package/template/skills/tdd/refactoring.md +0 -10
  96. package/template/skills/tdd/tests.md +0 -61
  97. package/template/skills/teach/GLOSSARY-FORMAT.md +0 -35
  98. package/template/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
  99. package/template/skills/teach/MISSION-FORMAT.md +0 -31
  100. package/template/skills/teach/RESOURCES-FORMAT.md +0 -32
  101. package/template/skills/teach/SKILL.md +0 -91
  102. package/template/skills/think/SKILL.md +0 -184
  103. package/template/skills/to-issues/SKILL.md +0 -83
  104. package/template/skills/to-prd/SKILL.md +0 -74
  105. package/template/skills/triage/AGENT-BRIEF.md +0 -168
  106. package/template/skills/triage/OUT-OF-SCOPE.md +0 -101
  107. package/template/skills/triage/SKILL.md +0 -103
  108. package/template/skills/ubiquitous-language/SKILL.md +0 -93
  109. package/template/skills/ver/SKILL.md +0 -62
  110. package/template/skills/write/SKILL.md +0 -209
  111. package/template/skills/write/references/write-en.md +0 -199
  112. package/template/skills/write/references/write-product-localization.md +0 -43
  113. package/template/skills/write/references/write-zh-bilingual.md +0 -59
  114. package/template/skills/write/references/write-zh-prose.md +0 -50
  115. package/template/skills/write/references/write-zh-release-notes.md +0 -40
  116. package/template/skills/write/references/write-zh.md +0 -721
  117. package/template/skills/write-a-skill/SKILL.md +0 -117
  118. package/template/skills/writing-beats/SKILL.md +0 -52
  119. package/template/skills/writing-fragments/SKILL.md +0 -75
  120. package/template/skills/writing-shape/SKILL.md +0 -64
  121. package/template/skills/zoom-out/SKILL.md +0 -7
@@ -1,635 +0,0 @@
1
- #!/usr/bin/env python3
2
- """AI maintainability audit: project shape, context surface, verification surface,
3
- decision artifacts, drift markers, hotspot ownership, markdown links.
4
-
5
- Run as: python3 check_maintainability.py [ROOT] [summary|deep]
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import argparse
11
- import json
12
- import os
13
- import re
14
- import subprocess
15
- import sys
16
- import urllib.parse
17
- from collections import Counter
18
- from pathlib import Path
19
-
20
-
21
- EXCLUDED_DIRS = {
22
- ".git",
23
- ".hg",
24
- ".svn",
25
- "node_modules",
26
- "dist",
27
- "build",
28
- ".next",
29
- "__pycache__",
30
- ".turbo",
31
- "target",
32
- ".venv",
33
- "venv",
34
- "vendor",
35
- "coverage",
36
- ".cache",
37
- ".parcel-cache",
38
- ".pytest_cache",
39
- ".mypy_cache",
40
- ".ruff_cache",
41
- }
42
-
43
- SOURCE_EXTS = {
44
- ".c", ".cc", ".cpp", ".cs", ".css", ".go", ".h", ".hpp", ".html",
45
- ".java", ".js", ".jsx", ".kt", ".lua", ".m", ".mm", ".md", ".mjs",
46
- ".php", ".py", ".rb", ".rs", ".scss", ".sh", ".swift", ".ts", ".tsx",
47
- ".vue", ".yaml", ".yml",
48
- }
49
-
50
- MARKER_RE = re.compile(r"\b(TODO|FIXME|HACK|XXX)\b", re.IGNORECASE)
51
- MAKE_RE = re.compile(r"^([A-Za-z0-9_.-]+)\s*:(?![=])")
52
- MAKE_CMD_RE = re.compile(r"\bmake\s+([A-Za-z0-9_.-]+)\b")
53
- NPM_CMD_RE = re.compile(r"\b(?:npm|pnpm|yarn|bun)\s+run\s+([A-Za-z0-9:_-]+)\b")
54
- COMMAND_LINE_RE = re.compile(r"^(?:make|npm|pnpm|yarn|bun)\s+")
55
- MARKDOWN_LINK_RE = re.compile(r"!?\[[^\]]*\]\(([^)]+)\)")
56
- URL_RE = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*:")
57
- HOTSPOT_WORD_RE = re.compile(
58
- r"(hotspot|large file|ownership|owner|boundary|module|owned|owns|"
59
- r"负责|边界|模块|热点|大文件)",
60
- re.IGNORECASE,
61
- )
62
- VERIFICATION_WORD_RE = re.compile(
63
- r"(verification|verify|test|check|make\s+\w+|pytest|go test|cargo test|"
64
- r"npm test|npm run|pnpm|yarn|swift test|xcodebuild|验证|测试)",
65
- re.IGNORECASE,
66
- )
67
-
68
-
69
- # The file-walk helpers below are deliberately duplicated in
70
- # skills/check/scripts/audit_signals.py. Both scripts ship standalone
71
- # (see packaging.allowlist) and run inside an arbitrary target project, so
72
- # they import only stdlib. Do not hoist them into a shared scripts/
73
- # module: it is dev-only, not on the ship allowlist, and would couple a
74
- # standalone tool to the install layout.
75
- def rel(path: Path, root: Path) -> str:
76
- try:
77
- return path.resolve().relative_to(root).as_posix()
78
- except ValueError:
79
- return path.as_posix()
80
-
81
-
82
- def is_excluded(path: Path, root: Path) -> bool:
83
- parts = path.relative_to(root).parts if path.is_absolute() else path.parts
84
- return any(part in EXCLUDED_DIRS for part in parts)
85
-
86
-
87
- def read_text(path: Path, limit: int | None = None) -> str:
88
- try:
89
- data = path.read_text(encoding="utf-8", errors="replace")
90
- except OSError:
91
- return ""
92
- return data[:limit] if limit else data
93
-
94
-
95
- def iter_files(root: Path) -> list[Path]:
96
- try:
97
- proc = subprocess.run(
98
- ["git", "-C", str(root), "ls-files", "--cached", "--others", "--exclude-standard"],
99
- text=True,
100
- stdout=subprocess.PIPE,
101
- stderr=subprocess.DEVNULL,
102
- check=False,
103
- )
104
- if proc.returncode == 0 and proc.stdout.strip():
105
- files = []
106
- for line in proc.stdout.splitlines():
107
- path = root / line
108
- if path.is_file() and not is_excluded(path, root):
109
- files.append(path)
110
- return files
111
- except OSError:
112
- pass
113
-
114
- files = []
115
- for dirpath, dirnames, filenames in os.walk(root):
116
- current = Path(dirpath)
117
- dirnames[:] = [name for name in dirnames if name not in EXCLUDED_DIRS]
118
- if is_excluded(current, root):
119
- continue
120
- for filename in filenames:
121
- path = current / filename
122
- if path.is_file() and not is_excluded(path, root):
123
- files.append(path)
124
- return files
125
-
126
-
127
- def line_count(path: Path) -> int:
128
- try:
129
- with path.open("rb") as handle:
130
- return sum(1 for _ in handle)
131
- except OSError:
132
- return 0
133
-
134
-
135
- def print_list(items: list[str], empty: str = "(none)", limit: int | None = None) -> None:
136
- shown = items if limit is None else items[:limit]
137
- if not shown:
138
- print(f" {empty}")
139
- return
140
- for item in shown:
141
- print(f" {item}")
142
- if limit is not None and len(items) > limit:
143
- print(f" ... {len(items) - limit} more")
144
-
145
-
146
- def instruction_paths(root: Path) -> list[Path]:
147
- candidates = [
148
- root / "AGENTS.md",
149
- root / "CLAUDE.md",
150
- root / ".github" / "copilot-instructions.md",
151
- root / "GEMINI.md",
152
- ]
153
- instructions_dir = root / ".github" / "instructions"
154
- if instructions_dir.is_dir():
155
- candidates.extend(sorted(instructions_dir.glob("*.md")))
156
- return [path for path in candidates if path.is_file() and not is_excluded(path, root)]
157
-
158
-
159
- def find_text_signal(paths: list[Path], patterns: list[str]) -> bool:
160
- regexes = [re.compile(pattern, re.IGNORECASE) for pattern in patterns]
161
- for path in paths:
162
- text = read_text(path, 200_000)
163
- if any(regex.search(text) for regex in regexes):
164
- return True
165
- return False
166
-
167
-
168
- def parse_makefile(root: Path) -> tuple[set[str], list[str]]:
169
- makefile = root / "Makefile"
170
- targets: set[str] = set()
171
- commands: list[str] = []
172
- if not makefile.is_file():
173
- return targets, commands
174
- for line in read_text(makefile).splitlines():
175
- match = MAKE_RE.match(line)
176
- if not match:
177
- continue
178
- target = match.group(1)
179
- if target.startswith("."):
180
- continue
181
- targets.add(target)
182
- if re.search(r"(test|check|lint|type|build|package|verify|smoke)", target, re.IGNORECASE):
183
- commands.append(f"make {target}")
184
- return targets, commands
185
-
186
-
187
- def parse_package_json(root: Path) -> tuple[set[str], list[str]]:
188
- package = root / "package.json"
189
- script_names: set[str] = set()
190
- commands: list[str] = []
191
- if not package.is_file():
192
- return script_names, commands
193
- try:
194
- data = json.loads(read_text(package))
195
- except json.JSONDecodeError:
196
- return script_names, commands
197
- scripts = data.get("scripts", {})
198
- if not isinstance(scripts, dict):
199
- return script_names, commands
200
- for name in sorted(scripts):
201
- script_names.add(name)
202
- if re.search(r"(test|check|lint|type|build|verify)", name, re.IGNORECASE):
203
- commands.append(f"npm run {name}")
204
- return script_names, commands
205
-
206
-
207
- def parse_ci_commands(root: Path) -> list[str]:
208
- workflows_dir = root / ".github" / "workflows"
209
- workflows = sorted(workflows_dir.glob("*.yml")) if workflows_dir.is_dir() else []
210
- workflows += sorted(workflows_dir.glob("*.yaml")) if workflows_dir.is_dir() else []
211
- commands: list[str] = []
212
- for workflow in workflows:
213
- for raw in read_text(workflow).splitlines():
214
- line = raw.strip()
215
- if line.startswith("- run:"):
216
- command = line.split("- run:", 1)[1].strip().strip("'\"")
217
- elif line.startswith("run:"):
218
- command = line.split("run:", 1)[1].strip().strip("'\"")
219
- else:
220
- continue
221
- if command and command != "|":
222
- commands.append(f"{rel(workflow, root)}: {command}")
223
- return commands
224
-
225
-
226
- def scan_markdown_links(files: list[Path], root: Path) -> list[str]:
227
- missing: list[str] = []
228
- markdown_files = [path for path in files if path.suffix.lower() == ".md"]
229
- for path in markdown_files:
230
- for lineno, line in enumerate(read_text(path).splitlines(), 1):
231
- for raw in MARKDOWN_LINK_RE.findall(line):
232
- target = raw.strip().split()[0].strip("<>")
233
- if not target or target.startswith("#") or URL_RE.match(target):
234
- continue
235
- target = urllib.parse.unquote(target.split("#", 1)[0])
236
- if not target:
237
- continue
238
- full = (path.parent / target).resolve()
239
- if not full.exists():
240
- missing.append(f"{rel(path, root)}:{lineno} -> {target}")
241
- return missing
242
-
243
-
244
- def verification_surface(
245
- root: Path, instruction_files: list[Path]
246
- ) -> tuple[list[str], list[str], set[str], set[str]]:
247
- make_targets, make_commands = parse_makefile(root)
248
- package_scripts, package_commands = parse_package_json(root)
249
- commands = make_commands + package_commands + parse_ci_commands(root)
250
-
251
- if (root / "Cargo.toml").is_file():
252
- commands.extend(["cargo test", "cargo check"])
253
- if (root / "go.mod").is_file():
254
- commands.append("go test ./...")
255
- if (root / "pyproject.toml").is_file() or (root / "pytest.ini").is_file():
256
- commands.append("pytest")
257
- if (root / "pom.xml").is_file():
258
- commands.append("mvn test")
259
- if (root / "deno.json").is_file() or (root / "deno.jsonc").is_file():
260
- commands.append("deno test")
261
-
262
- missing: list[str] = []
263
- for path in instruction_files:
264
- text = read_text(path, 200_000)
265
- snippets: list[str] = []
266
- for raw_line in text.splitlines():
267
- snippets.extend(re.findall(r"`([^`]+)`", raw_line))
268
- stripped = raw_line.strip().strip("`")
269
- if COMMAND_LINE_RE.match(stripped):
270
- snippets.append(stripped)
271
- for snippet in snippets:
272
- for target in MAKE_CMD_RE.findall(snippet):
273
- if target not in make_targets:
274
- missing.append(f"{rel(path, root)} references missing make target: {target}")
275
- for script in NPM_CMD_RE.findall(snippet):
276
- if script not in package_scripts:
277
- missing.append(f"{rel(path, root)} references missing package script: {script}")
278
-
279
- unique_commands = list(dict.fromkeys(commands))
280
- unique_missing = list(dict.fromkeys(missing))
281
- return unique_commands, unique_missing, make_targets, package_scripts
282
-
283
-
284
- def hotspot_ownership_surface(
285
- records: list[tuple[int, int, Path]],
286
- instruction_files: list[Path],
287
- mode: str,
288
- root: Path,
289
- ) -> tuple[str, list[str], list[str], list[str]]:
290
- if mode != "deep":
291
- return "SKIPPED", [], [], []
292
- if not records:
293
- return "PASS", [], [], []
294
-
295
- snippets: list[str] = []
296
- for path in instruction_files:
297
- snippets.append(f"\n# {rel(path, root)}\n{read_text(path, 200_000)}")
298
- instruction_text = "\n".join(snippets)
299
- lower_text = instruction_text.lower()
300
- instruction_lines = instruction_text.splitlines()
301
-
302
- documented: list[str] = []
303
- missing: list[str] = []
304
- for lines, _size, path in records:
305
- relative = rel(path, root)
306
- relative_lower = relative.lower()
307
- indices: list[int] = []
308
- start = 0
309
- while True:
310
- index = lower_text.find(relative_lower, start)
311
- if index < 0:
312
- break
313
- indices.append(index)
314
- start = index + len(relative_lower)
315
-
316
- if not indices:
317
- missing.append(f"{relative} lines={lines} reason=not mentioned in agent instructions")
318
- continue
319
-
320
- saw_hotspot_context = False
321
- saw_verification_context = False
322
- has_documented_entry = False
323
- for index in indices:
324
- window = lower_text[max(0, index - 700): index + len(relative_lower) + 700]
325
- line_no = lower_text[:index].count("\n")
326
- local_lines = instruction_lines[max(0, line_no - 1): line_no + 4]
327
- local_context = "\n".join(local_lines)
328
- has_hotspot_context = bool(HOTSPOT_WORD_RE.search(window))
329
- has_verification_context = bool(VERIFICATION_WORD_RE.search(local_context))
330
- saw_hotspot_context = saw_hotspot_context or has_hotspot_context
331
- saw_verification_context = saw_verification_context or has_verification_context
332
- if has_hotspot_context and has_verification_context:
333
- has_documented_entry = True
334
- break
335
-
336
- if has_documented_entry:
337
- documented.append(f"{relative} lines={lines}")
338
- else:
339
- reasons = []
340
- if not saw_hotspot_context:
341
- reasons.append("missing ownership/boundary context")
342
- if not saw_verification_context:
343
- reasons.append("missing verification context")
344
- if saw_hotspot_context and saw_verification_context:
345
- reasons.append("ownership and verification are not in the same hotspot entry")
346
- missing.append(f"{relative} lines={lines} reason={'; '.join(reasons)}")
347
-
348
- findings: list[str] = []
349
- if missing:
350
- findings.append("large source files lack hotspot ownership or verification map")
351
- return ("WARN" if missing else "PASS"), documented, missing, findings
352
-
353
-
354
- def main() -> int:
355
- parser = argparse.ArgumentParser(description=__doc__)
356
- parser.add_argument("root", nargs="?", default=".", help="Repo root (default: cwd)")
357
- parser.add_argument(
358
- "mode", nargs="?", default="summary", choices=("summary", "deep"),
359
- help="Output detail level",
360
- )
361
- args = parser.parse_args()
362
- root = Path(args.root).resolve()
363
- mode = args.mode
364
-
365
- if not root.is_dir():
366
- print(f"Repo root not found: {root}", file=sys.stderr)
367
- return 2
368
-
369
- files = iter_files(root)
370
- tracked_count = len(files)
371
- extensions = Counter(path.suffix.lower() or "(none)" for path in files)
372
- detected_manifests = [
373
- name
374
- for name in [
375
- "Makefile", "package.json", "Cargo.toml", "go.mod", "pyproject.toml",
376
- "pytest.ini", "pom.xml", "deno.json", "deno.jsonc",
377
- ]
378
- if (root / name).is_file()
379
- ]
380
- workflows_dir = root / ".github" / "workflows"
381
- workflow_count = 0
382
- if workflows_dir.is_dir():
383
- workflow_count = len(list(workflows_dir.glob("*.yml"))) + len(list(workflows_dir.glob("*.yaml")))
384
- if workflow_count:
385
- detected_manifests.append(f".github/workflows ({workflow_count})")
386
-
387
- source_files = [path for path in files if path.suffix.lower() in SOURCE_EXTS]
388
- source_stats: list[tuple[int, int, Path]] = []
389
- for path in source_files:
390
- try:
391
- size = path.stat().st_size
392
- except OSError:
393
- size = 0
394
- source_stats.append((line_count(path), size, path))
395
- source_stats.sort(key=lambda item: (item[0], item[1]), reverse=True)
396
-
397
- dir_counts: Counter[str] = Counter()
398
- for path in files:
399
- relative_parts = Path(rel(path, root)).parts
400
- top = relative_parts[0] if len(relative_parts) > 1 else "."
401
- dir_counts[top] += 1
402
-
403
- instruction_files = instruction_paths(root)
404
- project_map = find_text_signal(
405
- instruction_files,
406
- [r"repository map", r"project map", r"repo map", r"\bproject\b", r"目录", r"仓库", r"结构"],
407
- )
408
- instruction_verification = find_text_signal(
409
- instruction_files,
410
- [r"verification", r"test plan", r"make test", r"npm test", r"pytest", r"cargo test", r"验证", r"测试"],
411
- )
412
- boundaries = find_text_signal(
413
- instruction_files,
414
- [r"not for", r"do not", r"non-?goals?", r"scope", r"boundar", r"never", r"avoid", r"边界", r"非目标", r"不要"],
415
- )
416
- commands, missing_references, make_targets, _package_scripts = verification_surface(root, instruction_files)
417
- stable_make_targets = sorted(make_targets & {"check", "test", "verify"})
418
- wrapper_warnings: list[str] = []
419
- if len(commands) >= 2 and (root / "Makefile").is_file() and not stable_make_targets:
420
- wrapper_warnings.append(
421
- "multiple verification commands discovered but Makefile lacks check/test/verify wrapper"
422
- )
423
-
424
- decision_artifacts = {
425
- "docs_dir": (root / "docs").is_dir(),
426
- "specs_dir": (root / "specs").is_dir(),
427
- "specify_dir": (root / ".specify").is_dir(),
428
- "handoff_md": any(path.name.upper() == "HANDOFF.MD" for path in root.glob("*.md")),
429
- "changelog": any(path.name.upper().startswith("CHANGELOG") for path in root.glob("*")),
430
- "issue_templates": (root / ".github" / "ISSUE_TEMPLATE").exists(),
431
- "pr_template": any(
432
- path.is_file()
433
- for path in [
434
- root / ".github" / "pull_request_template.md",
435
- root / ".github" / "PULL_REQUEST_TEMPLATE.md",
436
- ]
437
- ),
438
- }
439
-
440
- todo_counts: Counter[str] = Counter()
441
- todo_total = 0
442
- for path in source_files:
443
- text = read_text(path, 200_000)
444
- # Count marker-bearing lines, not marker words. Documentation often names
445
- # the full marker family in one rule line; treating that as four issues
446
- # makes the checker flag itself instead of real TODO piles.
447
- count = sum(1 for line in text.splitlines() if MARKER_RE.search(line))
448
- if count:
449
- todo_counts[rel(path, root)] += count
450
- todo_total += count
451
-
452
- large_line_limit = 1200 if mode == "summary" else 800
453
- large_file_records = [
454
- (lines, size, path) for lines, size, path in source_stats if lines >= large_line_limit
455
- ]
456
- large_files = [f"{rel(path, root)} lines={lines} bytes={size}" for lines, size, path in large_file_records]
457
- todo_hotspots = [
458
- f"{path} markers={count}" for path, count in todo_counts.most_common(8 if mode == "deep" else 5)
459
- ]
460
-
461
- doc_ref_status = "unavailable"
462
- doc_ref_detail = ""
463
- checker = os.environ.get("DOC_REF_CHECKER")
464
- if checker and Path(checker).is_file():
465
- proc = subprocess.run(
466
- ["bash", checker, str(root)],
467
- text=True,
468
- stdout=subprocess.PIPE,
469
- stderr=subprocess.STDOUT,
470
- check=False,
471
- )
472
- doc_ref_status = "pass" if proc.returncode == 0 else "fail"
473
- if proc.stdout.strip():
474
- first_lines = proc.stdout.strip().splitlines()[:8]
475
- doc_ref_detail = " | ".join(first_lines)
476
-
477
- has_instruction_surface = bool(instruction_files)
478
- has_command_surface = bool(commands)
479
- context_warnings: list[str] = []
480
- verification_warnings: list[str] = []
481
- drift_warnings: list[str] = []
482
-
483
- if not has_instruction_surface:
484
- context_warnings.append("no agent instruction surface")
485
- if has_instruction_surface and not project_map:
486
- context_warnings.append("instructions lack project map")
487
- if has_instruction_surface and not instruction_verification:
488
- context_warnings.append("instructions lack verification guidance")
489
- if has_instruction_surface and not boundaries:
490
- context_warnings.append("instructions lack scope/boundary language")
491
- if not has_command_surface:
492
- verification_warnings.append("no executable verification command discovered")
493
- if missing_references:
494
- verification_warnings.append("instruction references missing commands")
495
- if todo_total >= (50 if mode == "summary" else 25):
496
- drift_warnings.append("TODO/FIXME/HACK/XXX markers are concentrated")
497
- hotspot_status, documented_hotspots, missing_hotspot_ownership, hotspot_findings = (
498
- hotspot_ownership_surface(large_file_records, instruction_files, mode, root)
499
- )
500
- if large_files and mode != "deep":
501
- drift_warnings.append("large source files need ownership or module boundaries")
502
- if hotspot_status == "WARN":
503
- drift_warnings.extend(hotspot_findings)
504
- if doc_ref_status == "fail":
505
- drift_warnings.append("broken documentation references")
506
-
507
- markdown_missing: list[str] = []
508
- markdown_link_status = "SKIPPED"
509
- if mode == "deep":
510
- markdown_missing = scan_markdown_links(files, root)
511
- markdown_link_status = "WARN" if markdown_missing else "PASS"
512
- if markdown_missing:
513
- drift_warnings.append("broken Markdown links")
514
-
515
- context_status = "FAIL" if not has_instruction_surface else ("WARN" if context_warnings else "PASS")
516
- verification_status = "FAIL" if not has_command_surface else ("WARN" if verification_warnings else "PASS")
517
- decision_status = "PASS"
518
- wrapper_status = "WARN" if wrapper_warnings else "PASS"
519
- drift_status = "WARN" if drift_warnings else "PASS"
520
-
521
- if context_status == "FAIL" or verification_status == "FAIL" or doc_ref_status == "fail":
522
- overall = "FAIL"
523
- elif "WARN" in {
524
- context_status, verification_status, decision_status, wrapper_status,
525
- drift_status, markdown_link_status, hotspot_status,
526
- }:
527
- overall = "WARN"
528
- else:
529
- overall = "PASS"
530
-
531
- top_ext = [f"{ext} files={count}" for ext, count in extensions.most_common(10)]
532
- largest_sources = [
533
- f"{rel(path, root)} lines={lines} bytes={size}"
534
- for lines, size, path in source_stats[: (10 if mode == "deep" else 5)]
535
- ]
536
- largest_dirs = [f"{directory} files={count}" for directory, count in dir_counts.most_common(8)]
537
-
538
- print("=== PROJECT SHAPE ===")
539
- print(f"maintainability_status: {overall}")
540
- print(f"mode: {mode}")
541
- print(f"tracked_files: {tracked_count}")
542
- print("top_extensions:")
543
- print_list(top_ext)
544
- print("largest_source_files:")
545
- print_list(largest_sources)
546
- print("largest_directories:")
547
- print_list(largest_dirs)
548
-
549
- print("=== AI CONTEXT SURFACE ===")
550
- print(f"context_status: {context_status}")
551
- print(f"AGENTS.md: {'yes' if (root / 'AGENTS.md').is_file() else 'no'}")
552
- print(f"CLAUDE.md: {'yes' if (root / 'CLAUDE.md').is_file() else 'no'}")
553
- print(f".github/copilot-instructions.md: {'yes' if (root / '.github' / 'copilot-instructions.md').is_file() else 'no'}")
554
- github_instruction_count = (
555
- len(list((root / ".github" / "instructions").glob("*.md")))
556
- if (root / ".github" / "instructions").is_dir() else 0
557
- )
558
- print(f".github/instructions/*.md: {github_instruction_count}")
559
- print(f"GEMINI.md: {'yes' if (root / 'GEMINI.md').is_file() else 'no'}")
560
- print(f"project_map: {'yes' if project_map else 'no'}")
561
- print(f"verification_guidance: {'yes' if instruction_verification else 'no'}")
562
- print(f"boundary_guidance: {'yes' if boundaries else 'no'}")
563
- print("context_findings:")
564
- print_list(context_warnings)
565
- print("instruction_files:")
566
- print_list([rel(path, root) for path in instruction_files])
567
-
568
- print("=== VERIFICATION SURFACE ===")
569
- print(f"verification_status: {verification_status}")
570
- print("detected_manifests:")
571
- print_list(detected_manifests)
572
- print("commands:")
573
- print_list(commands, limit=12 if mode == "summary" else None)
574
- print("missing_referenced_commands:")
575
- print_list(missing_references, limit=10 if mode == "summary" else None)
576
- print("verification_findings:")
577
- print_list(verification_warnings)
578
-
579
- print("=== VERIFICATION WRAPPER SURFACE ===")
580
- print(f"wrapper_status: {wrapper_status}")
581
- print(f"makefile_present: {'yes' if (root / 'Makefile').is_file() else 'no'}")
582
- print("stable_make_targets:")
583
- print_list([f"make {target}" for target in stable_make_targets])
584
- print("wrapper_findings:")
585
- print_list(wrapper_warnings)
586
-
587
- print("=== DECISION ARTIFACTS ===")
588
- print(f"decision_artifacts_status: {decision_status}")
589
- for key, value in decision_artifacts.items():
590
- print(f"{key}: {'yes' if value else 'no'}")
591
-
592
- print("=== DRIFT MARKERS ===")
593
- print(f"drift_status: {drift_status}")
594
- print(f"todo_markers: {todo_total}")
595
- print("todo_hotspots:")
596
- print_list(todo_hotspots)
597
- print("large_source_files:")
598
- print_list(large_files[: (10 if mode == "deep" else 5)])
599
- print(f"broken_doc_references: {doc_ref_status}")
600
- if doc_ref_detail and (mode == "deep" or doc_ref_status == "fail"):
601
- print(f"broken_doc_reference_detail: {doc_ref_detail}")
602
- print("drift_findings:")
603
- print_list(drift_warnings)
604
-
605
- print("=== HOTSPOT OWNERSHIP SURFACE ===")
606
- print(f"hotspot_ownership_status: {hotspot_status}")
607
- print(f"large_hotspot_threshold_lines: {large_line_limit if mode == 'deep' else '(deep mode only)'}")
608
- print("documented_hotspots:")
609
- if mode == "deep":
610
- print_list(documented_hotspots)
611
- else:
612
- print(" (skipped: deep mode only)")
613
- print("missing_hotspot_ownership:")
614
- if mode == "deep":
615
- print_list(missing_hotspot_ownership)
616
- else:
617
- print(" (skipped: deep mode only)")
618
- print("hotspot_ownership_findings:")
619
- if mode == "deep":
620
- print_list(hotspot_findings)
621
- else:
622
- print(" (skipped: deep mode only)")
623
-
624
- print("=== MARKDOWN LINK SURFACE ===")
625
- print(f"markdown_link_status: {markdown_link_status}")
626
- print("missing_markdown_links:")
627
- if mode == "deep":
628
- print_list(markdown_missing, limit=20)
629
- else:
630
- print(" (skipped: deep mode only)")
631
- return 0
632
-
633
-
634
- if __name__ == "__main__":
635
- sys.exit(main())