incitaciones 0.1.0
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/LICENSE +395 -0
- package/README.md +410 -0
- package/content/distilled/abstraction-miner.md +32 -0
- package/content/distilled/adversarial-stakeholder-evaluation.md +121 -0
- package/content/distilled/anti-slop-prose.md +46 -0
- package/content/distilled/bias-audit.md +39 -0
- package/content/distilled/code-review/SKILL.md +53 -0
- package/content/distilled/code-review/references/criteria.md +50 -0
- package/content/distilled/code-review/references/templates.md +75 -0
- package/content/distilled/code-review/references/variants.md +32 -0
- package/content/distilled/commit/SKILL.md +41 -0
- package/content/distilled/commit/references/criteria.md +21 -0
- package/content/distilled/commit/references/templates.md +46 -0
- package/content/distilled/composability-diagnostician.md +67 -0
- package/content/distilled/context-guardian.md +20 -0
- package/content/distilled/create-handoff/SKILL.md +16 -0
- package/content/distilled/create-handoff/references/handoff-template.md +65 -0
- package/content/distilled/create-handoff/references/naming.md +7 -0
- package/content/distilled/create-issues/SKILL.md +72 -0
- package/content/distilled/create-issues/references/criteria.md +44 -0
- package/content/distilled/create-issues/references/templates.md +75 -0
- package/content/distilled/create-plan/SKILL.md +19 -0
- package/content/distilled/create-plan/references/plan-template.md +87 -0
- package/content/distilled/create-plan/references/scenarios.md +19 -0
- package/content/distilled/debug/SKILL.md +42 -0
- package/content/distilled/debug/references/criteria.md +27 -0
- package/content/distilled/debug/references/templates.md +39 -0
- package/content/distilled/describe-pr/SKILL.md +17 -0
- package/content/distilled/describe-pr/references/pr-template.md +62 -0
- package/content/distilled/design-practice.md +213 -0
- package/content/distilled/design-review/SKILL.md +29 -0
- package/content/distilled/design-review/references/combined-review.md +27 -0
- package/content/distilled/design-review/references/decision-matrix.md +122 -0
- package/content/distilled/design-review/references/problem-statement.md +141 -0
- package/content/distilled/design-review/references/report-template.md +36 -0
- package/content/distilled/design-review/references/scope-document.md +51 -0
- package/content/distilled/distill-prompt/SKILL.md +41 -0
- package/content/distilled/distill-prompt/references/criteria.md +26 -0
- package/content/distilled/distill-prompt/references/templates.md +30 -0
- package/content/distilled/doc-link-verifier/SKILL.md +212 -0
- package/content/distilled/doc-link-verifier/scripts/check_links.py +453 -0
- package/content/distilled/doc-link-verifier/scripts/extract_links.py +521 -0
- package/content/distilled/edge-case-discovery.md +123 -0
- package/content/distilled/error-handling-diagnostician.md +90 -0
- package/content/distilled/extract-prompt.md +220 -0
- package/content/distilled/formal-verification-evaluator.md +158 -0
- package/content/distilled/grill-me.md +24 -0
- package/content/distilled/guided-review.md +68 -0
- package/content/distilled/implement-documentation.md +68 -0
- package/content/distilled/implement-plan/SKILL.md +23 -0
- package/content/distilled/implement-plan/references/common-situations.md +36 -0
- package/content/distilled/implement-plan/references/verification-template.md +18 -0
- package/content/distilled/invalid-states-diagnostician.md +63 -0
- package/content/distilled/issue-review/SKILL.md +37 -0
- package/content/distilled/issue-review/references/convergence.md +19 -0
- package/content/distilled/issue-review/references/final-report.md +60 -0
- package/content/distilled/issue-review/references/pass-1-clarity.md +28 -0
- package/content/distilled/issue-review/references/pass-2-scope.md +22 -0
- package/content/distilled/issue-review/references/pass-3-dependencies.md +23 -0
- package/content/distilled/issue-review/references/pass-4-alignment.md +17 -0
- package/content/distilled/issue-review/references/pass-5-executability.md +17 -0
- package/content/distilled/issue-review/references/setup.md +25 -0
- package/content/distilled/iterate-plan/SKILL.md +24 -0
- package/content/distilled/iterate-plan/references/change-template.md +15 -0
- package/content/distilled/iterate-plan/references/common-scenarios.md +14 -0
- package/content/distilled/julia-performance-diagnostician.md +71 -0
- package/content/distilled/modularity-diagnostician.md +68 -0
- package/content/distilled/multi-agent-review.md +196 -0
- package/content/distilled/mutability-diagnostician.md +63 -0
- package/content/distilled/narrative-article.md +60 -0
- package/content/distilled/optionality-review.md +190 -0
- package/content/distilled/parallel-review/SKILL.md +49 -0
- package/content/distilled/parallel-review/references/criteria.md +34 -0
- package/content/distilled/parallel-review/references/templates.md +59 -0
- package/content/distilled/plan-review/SKILL.md +42 -0
- package/content/distilled/plan-review/references/criteria.md +51 -0
- package/content/distilled/plan-review/references/templates.md +67 -0
- package/content/distilled/pre-mortem.md +10 -0
- package/content/distilled/project-status-report.md +124 -0
- package/content/distilled/pwa-accessibility-review.md +81 -0
- package/content/distilled/rca-diagnostician/SKILL.md +30 -0
- package/content/distilled/rca-diagnostician/references/action-hierarchy.md +77 -0
- package/content/distilled/rca-diagnostician/references/bias-countermeasures.md +64 -0
- package/content/distilled/rca-diagnostician/references/evidence-timeline.md +49 -0
- package/content/distilled/rca-diagnostician/references/hypothesis-methods.md +53 -0
- package/content/distilled/rca-diagnostician/references/problem-definition.md +34 -0
- package/content/distilled/rca-diagnostician/references/report-template.md +75 -0
- package/content/distilled/rca-diagnostician/references/rigor-checklist.md +66 -0
- package/content/distilled/red-team-review.md +133 -0
- package/content/distilled/research-codebase/SKILL.md +22 -0
- package/content/distilled/research-codebase/references/report-template.md +75 -0
- package/content/distilled/research-codebase/references/search-patterns.md +27 -0
- package/content/distilled/research-documentation.md +79 -0
- package/content/distilled/research-review/SKILL.md +42 -0
- package/content/distilled/research-review/references/criteria.md +51 -0
- package/content/distilled/research-review/references/templates.md +73 -0
- package/content/distilled/resonant-refactor.md +45 -0
- package/content/distilled/resume-handoff/SKILL.md +19 -0
- package/content/distilled/resume-handoff/references/analysis-template.md +40 -0
- package/content/distilled/resume-handoff/references/locating.md +12 -0
- package/content/distilled/review-documentation.md +44 -0
- package/content/distilled/rigidity-diagnostician.md +70 -0
- package/content/distilled/rule-of-5-universal/SKILL.md +26 -0
- package/content/distilled/rule-of-5-universal/references/criteria.md +34 -0
- package/content/distilled/rule-of-5-universal/references/examples.md +69 -0
- package/content/distilled/rule-of-5-universal/references/templates.md +136 -0
- package/content/distilled/specification-evaluation-diagnostician.md +106 -0
- package/content/distilled/specification-review/SKILL.md +43 -0
- package/content/distilled/specification-review/references/criteria.md +51 -0
- package/content/distilled/specification-review/references/templates.md +67 -0
- package/content/distilled/systematic-housekeeping.md +27 -0
- package/content/distilled/tdd/SKILL.md +38 -0
- package/content/distilled/tdd/references/criteria.md +26 -0
- package/content/distilled/tdd/references/templates.md +53 -0
- package/content/distilled/test-abstraction-miner.md +43 -0
- package/content/distilled/test-friction.md +51 -0
- package/content/distilled/testability-implementability-evaluator.md +144 -0
- package/content/distilled/ui-align.md +32 -0
- package/content/distilled/ux-dx-evaluation-diagnostician/SKILL.md +21 -0
- package/content/distilled/ux-dx-evaluation-diagnostician/references/layer-1-product.md +20 -0
- package/content/distilled/ux-dx-evaluation-diagnostician/references/layer-2-engineering.md +20 -0
- package/content/distilled/ux-dx-evaluation-diagnostician/references/layer-3-interface.md +23 -0
- package/content/distilled/ux-dx-evaluation-diagnostician/references/layer-4-ecosystem.md +20 -0
- package/content/distilled/ux-dx-evaluation-diagnostician/references/layer-5-documentation.md +19 -0
- package/content/distilled/ux-dx-evaluation-diagnostician/references/report-template.md +36 -0
- package/content/distilled/verification-diagnostician.md +90 -0
- package/content/distilled/verify-prompt/SKILL.md +32 -0
- package/content/distilled/verify-prompt/references/criteria.md +21 -0
- package/content/manifest.json +3220 -0
- package/package.json +50 -0
- package/pi-package/README.md +9 -0
- package/pi-package/prompts/abstraction-miner.md +10 -0
- package/pi-package/prompts/adversarial-stakeholder-evaluation.md +10 -0
- package/pi-package/prompts/anti-slop-prose.md +10 -0
- package/pi-package/prompts/bias-audit.md +10 -0
- package/pi-package/prompts/code-review.md +10 -0
- package/pi-package/prompts/commit.md +10 -0
- package/pi-package/prompts/composability-diagnostician.md +10 -0
- package/pi-package/prompts/context-guardian.md +10 -0
- package/pi-package/prompts/create-handoff.md +10 -0
- package/pi-package/prompts/create-issues.md +10 -0
- package/pi-package/prompts/create-plan.md +10 -0
- package/pi-package/prompts/debug.md +10 -0
- package/pi-package/prompts/describe-pr.md +10 -0
- package/pi-package/prompts/design-practice.md +10 -0
- package/pi-package/prompts/design-review.md +10 -0
- package/pi-package/prompts/distill-prompt.md +10 -0
- package/pi-package/prompts/doc-link-verifier.md +10 -0
- package/pi-package/prompts/edge-case-discovery.md +10 -0
- package/pi-package/prompts/error-handling-diagnostician.md +10 -0
- package/pi-package/prompts/extract-prompt.md +10 -0
- package/pi-package/prompts/formal-verification-evaluator.md +10 -0
- package/pi-package/prompts/grill-me.md +10 -0
- package/pi-package/prompts/guided-review.md +10 -0
- package/pi-package/prompts/implement-documentation.md +10 -0
- package/pi-package/prompts/implement-plan.md +10 -0
- package/pi-package/prompts/invalid-states-diagnostician.md +10 -0
- package/pi-package/prompts/issue-review.md +10 -0
- package/pi-package/prompts/iterate-plan.md +10 -0
- package/pi-package/prompts/julia-performance-diagnostician.md +10 -0
- package/pi-package/prompts/modularity-diagnostician.md +10 -0
- package/pi-package/prompts/multi-agent-review.md +10 -0
- package/pi-package/prompts/mutability-diagnostician.md +10 -0
- package/pi-package/prompts/narrative-article.md +10 -0
- package/pi-package/prompts/optionality-review.md +10 -0
- package/pi-package/prompts/parallel-review.md +10 -0
- package/pi-package/prompts/plan-review.md +10 -0
- package/pi-package/prompts/pre-mortem.md +10 -0
- package/pi-package/prompts/project-status-report.md +10 -0
- package/pi-package/prompts/pwa-accessibility-review.md +10 -0
- package/pi-package/prompts/rca-diagnostician.md +10 -0
- package/pi-package/prompts/red-team-review.md +10 -0
- package/pi-package/prompts/research-codebase.md +10 -0
- package/pi-package/prompts/research-documentation.md +10 -0
- package/pi-package/prompts/research-review.md +10 -0
- package/pi-package/prompts/resonant-refactor.md +10 -0
- package/pi-package/prompts/resume-handoff.md +10 -0
- package/pi-package/prompts/review-documentation.md +10 -0
- package/pi-package/prompts/rigidity-diagnostician.md +10 -0
- package/pi-package/prompts/rule-of-5-universal.md +10 -0
- package/pi-package/prompts/specification-evaluation-diagnostician.md +10 -0
- package/pi-package/prompts/specification-review.md +10 -0
- package/pi-package/prompts/systematic-housekeeping.md +10 -0
- package/pi-package/prompts/tdd.md +10 -0
- package/pi-package/prompts/test-abstraction-miner.md +10 -0
- package/pi-package/prompts/test-friction.md +10 -0
- package/pi-package/prompts/testability-implementability-evaluator.md +10 -0
- package/pi-package/prompts/ui-align.md +10 -0
- package/pi-package/prompts/ux-dx-evaluation-diagnostician.md +10 -0
- package/pi-package/prompts/verification-diagnostician.md +10 -0
- package/pi-package/prompts/verify-prompt.md +10 -0
- package/pi-package/skills/abstraction-miner/SKILL.md +38 -0
- package/pi-package/skills/adversarial-stakeholder-evaluation/SKILL.md +127 -0
- package/pi-package/skills/anti-slop-prose/SKILL.md +52 -0
- package/pi-package/skills/bias-audit/SKILL.md +45 -0
- package/pi-package/skills/code-review/SKILL.md +59 -0
- package/pi-package/skills/code-review/references/criteria.md +50 -0
- package/pi-package/skills/code-review/references/templates.md +75 -0
- package/pi-package/skills/code-review/references/variants.md +32 -0
- package/pi-package/skills/commit/SKILL.md +47 -0
- package/pi-package/skills/commit/references/criteria.md +21 -0
- package/pi-package/skills/commit/references/templates.md +46 -0
- package/pi-package/skills/composability-diagnostician/SKILL.md +73 -0
- package/pi-package/skills/context-guardian/SKILL.md +26 -0
- package/pi-package/skills/create-handoff/SKILL.md +22 -0
- package/pi-package/skills/create-handoff/references/handoff-template.md +65 -0
- package/pi-package/skills/create-handoff/references/naming.md +7 -0
- package/pi-package/skills/create-issues/SKILL.md +78 -0
- package/pi-package/skills/create-issues/references/criteria.md +44 -0
- package/pi-package/skills/create-issues/references/templates.md +75 -0
- package/pi-package/skills/create-plan/SKILL.md +25 -0
- package/pi-package/skills/create-plan/references/plan-template.md +87 -0
- package/pi-package/skills/create-plan/references/scenarios.md +19 -0
- package/pi-package/skills/debug/SKILL.md +48 -0
- package/pi-package/skills/debug/references/criteria.md +27 -0
- package/pi-package/skills/debug/references/templates.md +39 -0
- package/pi-package/skills/describe-pr/SKILL.md +23 -0
- package/pi-package/skills/describe-pr/references/pr-template.md +62 -0
- package/pi-package/skills/design-practice/SKILL.md +219 -0
- package/pi-package/skills/design-review/SKILL.md +35 -0
- package/pi-package/skills/design-review/references/combined-review.md +27 -0
- package/pi-package/skills/design-review/references/decision-matrix.md +122 -0
- package/pi-package/skills/design-review/references/problem-statement.md +141 -0
- package/pi-package/skills/design-review/references/report-template.md +36 -0
- package/pi-package/skills/design-review/references/scope-document.md +51 -0
- package/pi-package/skills/distill-prompt/SKILL.md +47 -0
- package/pi-package/skills/distill-prompt/references/criteria.md +26 -0
- package/pi-package/skills/distill-prompt/references/templates.md +30 -0
- package/pi-package/skills/doc-link-verifier/SKILL.md +218 -0
- package/pi-package/skills/doc-link-verifier/scripts/check_links.py +453 -0
- package/pi-package/skills/doc-link-verifier/scripts/extract_links.py +521 -0
- package/pi-package/skills/edge-case-discovery/SKILL.md +129 -0
- package/pi-package/skills/error-handling-diagnostician/SKILL.md +96 -0
- package/pi-package/skills/extract-prompt/SKILL.md +226 -0
- package/pi-package/skills/formal-verification-evaluator/SKILL.md +164 -0
- package/pi-package/skills/grill-me/SKILL.md +30 -0
- package/pi-package/skills/guided-review/SKILL.md +74 -0
- package/pi-package/skills/implement-documentation/SKILL.md +74 -0
- package/pi-package/skills/implement-plan/SKILL.md +29 -0
- package/pi-package/skills/implement-plan/references/common-situations.md +36 -0
- package/pi-package/skills/implement-plan/references/verification-template.md +18 -0
- package/pi-package/skills/invalid-states-diagnostician/SKILL.md +69 -0
- package/pi-package/skills/issue-review/SKILL.md +43 -0
- package/pi-package/skills/issue-review/references/convergence.md +19 -0
- package/pi-package/skills/issue-review/references/final-report.md +60 -0
- package/pi-package/skills/issue-review/references/pass-1-clarity.md +28 -0
- package/pi-package/skills/issue-review/references/pass-2-scope.md +22 -0
- package/pi-package/skills/issue-review/references/pass-3-dependencies.md +23 -0
- package/pi-package/skills/issue-review/references/pass-4-alignment.md +17 -0
- package/pi-package/skills/issue-review/references/pass-5-executability.md +17 -0
- package/pi-package/skills/issue-review/references/setup.md +25 -0
- package/pi-package/skills/iterate-plan/SKILL.md +30 -0
- package/pi-package/skills/iterate-plan/references/change-template.md +15 -0
- package/pi-package/skills/iterate-plan/references/common-scenarios.md +14 -0
- package/pi-package/skills/julia-performance-diagnostician/SKILL.md +77 -0
- package/pi-package/skills/modularity-diagnostician/SKILL.md +74 -0
- package/pi-package/skills/multi-agent-review/SKILL.md +202 -0
- package/pi-package/skills/mutability-diagnostician/SKILL.md +69 -0
- package/pi-package/skills/narrative-article/SKILL.md +66 -0
- package/pi-package/skills/optionality-review/SKILL.md +196 -0
- package/pi-package/skills/parallel-review/SKILL.md +55 -0
- package/pi-package/skills/parallel-review/references/criteria.md +34 -0
- package/pi-package/skills/parallel-review/references/templates.md +59 -0
- package/pi-package/skills/plan-review/SKILL.md +48 -0
- package/pi-package/skills/plan-review/references/criteria.md +51 -0
- package/pi-package/skills/plan-review/references/templates.md +67 -0
- package/pi-package/skills/pre-mortem/SKILL.md +16 -0
- package/pi-package/skills/project-status-report/SKILL.md +130 -0
- package/pi-package/skills/pwa-accessibility-review/SKILL.md +87 -0
- package/pi-package/skills/rca-diagnostician/SKILL.md +36 -0
- package/pi-package/skills/rca-diagnostician/references/action-hierarchy.md +77 -0
- package/pi-package/skills/rca-diagnostician/references/bias-countermeasures.md +64 -0
- package/pi-package/skills/rca-diagnostician/references/evidence-timeline.md +49 -0
- package/pi-package/skills/rca-diagnostician/references/hypothesis-methods.md +53 -0
- package/pi-package/skills/rca-diagnostician/references/problem-definition.md +34 -0
- package/pi-package/skills/rca-diagnostician/references/report-template.md +75 -0
- package/pi-package/skills/rca-diagnostician/references/rigor-checklist.md +66 -0
- package/pi-package/skills/red-team-review/SKILL.md +139 -0
- package/pi-package/skills/research-codebase/SKILL.md +28 -0
- package/pi-package/skills/research-codebase/references/report-template.md +75 -0
- package/pi-package/skills/research-codebase/references/search-patterns.md +27 -0
- package/pi-package/skills/research-documentation/SKILL.md +85 -0
- package/pi-package/skills/research-review/SKILL.md +48 -0
- package/pi-package/skills/research-review/references/criteria.md +51 -0
- package/pi-package/skills/research-review/references/templates.md +73 -0
- package/pi-package/skills/resonant-refactor/SKILL.md +51 -0
- package/pi-package/skills/resume-handoff/SKILL.md +25 -0
- package/pi-package/skills/resume-handoff/references/analysis-template.md +40 -0
- package/pi-package/skills/resume-handoff/references/locating.md +12 -0
- package/pi-package/skills/review-documentation/SKILL.md +50 -0
- package/pi-package/skills/rigidity-diagnostician/SKILL.md +76 -0
- package/pi-package/skills/rule-of-5-universal/SKILL.md +32 -0
- package/pi-package/skills/rule-of-5-universal/references/criteria.md +34 -0
- package/pi-package/skills/rule-of-5-universal/references/examples.md +69 -0
- package/pi-package/skills/rule-of-5-universal/references/templates.md +136 -0
- package/pi-package/skills/specification-evaluation-diagnostician/SKILL.md +112 -0
- package/pi-package/skills/specification-review/SKILL.md +49 -0
- package/pi-package/skills/specification-review/references/criteria.md +51 -0
- package/pi-package/skills/specification-review/references/templates.md +67 -0
- package/pi-package/skills/systematic-housekeeping/SKILL.md +33 -0
- package/pi-package/skills/tdd/SKILL.md +44 -0
- package/pi-package/skills/tdd/references/criteria.md +26 -0
- package/pi-package/skills/tdd/references/templates.md +53 -0
- package/pi-package/skills/test-abstraction-miner/SKILL.md +49 -0
- package/pi-package/skills/test-friction/SKILL.md +57 -0
- package/pi-package/skills/testability-implementability-evaluator/SKILL.md +150 -0
- package/pi-package/skills/ui-align/SKILL.md +38 -0
- package/pi-package/skills/ux-dx-evaluation-diagnostician/SKILL.md +27 -0
- package/pi-package/skills/ux-dx-evaluation-diagnostician/references/layer-1-product.md +20 -0
- package/pi-package/skills/ux-dx-evaluation-diagnostician/references/layer-2-engineering.md +20 -0
- package/pi-package/skills/ux-dx-evaluation-diagnostician/references/layer-3-interface.md +23 -0
- package/pi-package/skills/ux-dx-evaluation-diagnostician/references/layer-4-ecosystem.md +20 -0
- package/pi-package/skills/ux-dx-evaluation-diagnostician/references/layer-5-documentation.md +19 -0
- package/pi-package/skills/ux-dx-evaluation-diagnostician/references/report-template.md +36 -0
- package/pi-package/skills/verification-diagnostician/SKILL.md +96 -0
- package/pi-package/skills/verify-prompt/SKILL.md +38 -0
- package/pi-package/skills/verify-prompt/references/criteria.md +21 -0
- package/scripts/cli.mjs +453 -0
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.10"
|
|
4
|
+
# dependencies = []
|
|
5
|
+
# ///
|
|
6
|
+
"""
|
|
7
|
+
Extract links from documentation files in a repository.
|
|
8
|
+
|
|
9
|
+
Walks a directory, finds documentation and source files, pulls out every link
|
|
10
|
+
(markdown inline, reference-style, autolinks, rST, bare URLs, code-comment URLs)
|
|
11
|
+
along with the surrounding context, the file path, and the line number.
|
|
12
|
+
|
|
13
|
+
Output: JSON array to stdout (or --out file), one entry per link occurrence.
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
uv run extract_links.py <repo_path> [--out links.json] [--include-code]
|
|
17
|
+
[--extensions .md,.mdx,.rst] [--exclude node_modules,dist]
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import re
|
|
26
|
+
import sys
|
|
27
|
+
from dataclasses import dataclass, asdict
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Iterable
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# -------- File-type configuration --------
|
|
33
|
+
|
|
34
|
+
DOC_EXTS = {".md", ".mdx", ".markdown", ".rst", ".txt"}
|
|
35
|
+
CODE_EXTS = {
|
|
36
|
+
".py", ".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs",
|
|
37
|
+
".java", ".go", ".rs", ".rb", ".c", ".cc", ".cpp", ".h", ".hpp",
|
|
38
|
+
".swift", ".kt", ".cs", ".php", ".sh",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
DEFAULT_EXCLUDE = {
|
|
42
|
+
".git", "node_modules", "dist", "build", "target", ".next", ".nuxt",
|
|
43
|
+
"__pycache__", ".venv", "venv", "env", ".tox", ".mypy_cache",
|
|
44
|
+
".pytest_cache", "vendor", "coverage", ".idea", ".vscode",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# -------- Link patterns --------
|
|
49
|
+
|
|
50
|
+
# Markdown inline: [text](url "optional title")
|
|
51
|
+
MD_INLINE = re.compile(
|
|
52
|
+
r'(?<!\!)\[(?P<text>(?:[^\[\]]|\[[^\]]*\])*)\]\((?P<url><[^>]+>|[^\s\)]+)(?:\s+"[^"]*")?\)'
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Markdown image:  — separated so reports can flag images distinctly
|
|
56
|
+
MD_IMAGE = re.compile(
|
|
57
|
+
r'!\[(?P<text>[^\]]*)\]\((?P<url><[^>]+>|[^\s\)]+)(?:\s+"[^"]*")?\)'
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Reference-style usage: [text][label] or [text][] — we resolve labels later
|
|
61
|
+
MD_REF_USE = re.compile(r'(?<!\!)\[(?P<text>(?:[^\[\]]|\[[^\]]*\])*)\]\[(?P<label>[^\]]*)\]')
|
|
62
|
+
|
|
63
|
+
# Reference-style definition: [label]: url "optional title"
|
|
64
|
+
MD_REF_DEF = re.compile(
|
|
65
|
+
r'^\s*\[(?P<label>[^\]]+)\]:\s*<?(?P<url>\S+?)>?(?:\s+"[^"]*")?\s*$',
|
|
66
|
+
re.MULTILINE,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Autolink: <https://...> or <user@host>
|
|
70
|
+
MD_AUTOLINK = re.compile(r'<(?P<url>(?:https?|ftp|mailto):[^>\s]+)>')
|
|
71
|
+
|
|
72
|
+
# rST external link with text: `text <url>`_
|
|
73
|
+
RST_LINK = re.compile(r'`(?P<text>[^`<]+)\s+<(?P<url>[^>]+)>`_')
|
|
74
|
+
|
|
75
|
+
# rST target definition: .. _label: url
|
|
76
|
+
RST_TARGET = re.compile(r'^\s*\.\.\s+_(?P<label>[^:]+):\s*(?P<url>\S+)\s*$', re.MULTILINE)
|
|
77
|
+
|
|
78
|
+
# Bare URL — conservative, used as a fallback for plain text + code comments.
|
|
79
|
+
# Matches http(s) and stops at whitespace or common sentence terminators.
|
|
80
|
+
# Allows parentheses inside the URL (Wikipedia etc.) — clean_bare_url strips unbalanced trailing ones.
|
|
81
|
+
BARE_URL = re.compile(
|
|
82
|
+
r'(?<![\w/\-\.])(?P<url>https?://[^\s<>"\']+?)(?=[\s<>"\'\}\,;:!?]|$)',
|
|
83
|
+
re.IGNORECASE,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Trailing punctuation we should strip off bare URLs (since regex includes them sometimes)
|
|
87
|
+
TRAILING_PUNCT = '.,;:!?\'"}'
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class Link:
|
|
92
|
+
url: str # the URL as written (after fence-stripping)
|
|
93
|
+
raw_url: str # the URL exactly as it appeared
|
|
94
|
+
text: str # link text / description, may be empty
|
|
95
|
+
file: str # path relative to repo root
|
|
96
|
+
line: int # 1-indexed line where link starts
|
|
97
|
+
col: int # 0-indexed column
|
|
98
|
+
context: str # surrounding text (typically the line + neighbors)
|
|
99
|
+
kind: str # md-inline | md-image | md-ref | md-autolink | rst-link | rst-target | bare | comment-url
|
|
100
|
+
link_type: str # external | relative | anchor | mailto | other
|
|
101
|
+
|
|
102
|
+
def to_dict(self) -> dict:
|
|
103
|
+
return asdict(self)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# -------- Classification helpers --------
|
|
107
|
+
|
|
108
|
+
def classify(url: str) -> str:
|
|
109
|
+
u = url.strip()
|
|
110
|
+
if u.startswith('#'):
|
|
111
|
+
return 'anchor'
|
|
112
|
+
if u.startswith(('http://', 'https://', 'ftp://')):
|
|
113
|
+
return 'external'
|
|
114
|
+
if u.startswith('mailto:'):
|
|
115
|
+
return 'mailto'
|
|
116
|
+
if u.startswith(('/', './', '../')) or not re.match(r'^[a-z]+:', u, re.IGNORECASE):
|
|
117
|
+
# No scheme — treat as a repo-relative path
|
|
118
|
+
return 'relative'
|
|
119
|
+
return 'other'
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def strip_angle_brackets(url: str) -> str:
|
|
123
|
+
if url.startswith('<') and url.endswith('>'):
|
|
124
|
+
return url[1:-1]
|
|
125
|
+
return url
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def clean_bare_url(url: str) -> str:
|
|
129
|
+
"""Strip trailing sentence punctuation that got pulled in by the regex."""
|
|
130
|
+
# First strip simple trailing punctuation
|
|
131
|
+
while url and url[-1] in TRAILING_PUNCT:
|
|
132
|
+
url = url[:-1]
|
|
133
|
+
# Then strip unbalanced trailing brackets/parens
|
|
134
|
+
bracket_pairs = {')': '(', ']': '['}
|
|
135
|
+
while url and url[-1] in bracket_pairs:
|
|
136
|
+
opener = bracket_pairs[url[-1]]
|
|
137
|
+
if url.count(opener) >= url.count(url[-1]):
|
|
138
|
+
break # balanced — keep it
|
|
139
|
+
url = url[:-1]
|
|
140
|
+
# Strip any remaining trailing punctuation exposed by bracket removal
|
|
141
|
+
while url and url[-1] in TRAILING_PUNCT:
|
|
142
|
+
url = url[:-1]
|
|
143
|
+
return url
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def context_window(lines: list[str], line_idx: int, before: int = 1, after: int = 1) -> str:
|
|
147
|
+
"""Return a slice of lines around line_idx (0-indexed) as a single string."""
|
|
148
|
+
start = max(0, line_idx - before)
|
|
149
|
+
end = min(len(lines), line_idx + after + 1)
|
|
150
|
+
return '\n'.join(lines[start:end]).strip()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# -------- Comment extraction --------
|
|
154
|
+
|
|
155
|
+
# For code files we don't want to match URLs in code strings indiscriminately,
|
|
156
|
+
# so we extract comment regions first and then scan those for bare URLs.
|
|
157
|
+
|
|
158
|
+
COMMENT_PATTERNS = {
|
|
159
|
+
# line-comment prefixes per language family
|
|
160
|
+
'hash': re.compile(r'#.*$', re.MULTILINE), # py, rb, sh, etc.
|
|
161
|
+
'slashes': re.compile(r'//.*$', re.MULTILINE), # js, ts, java, go, rs, c, etc.
|
|
162
|
+
'block_c': re.compile(r'/\*[\s\S]*?\*/'), # c-style block comments
|
|
163
|
+
'triple_double': re.compile(r'"""[\s\S]*?"""'), # python docstrings
|
|
164
|
+
'triple_single': re.compile(r"'''[\s\S]*?'''"), # python docstrings (single)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
LANG_COMMENTS = {
|
|
168
|
+
'.py': ('hash', 'triple_double', 'triple_single'),
|
|
169
|
+
'.rb': ('hash',),
|
|
170
|
+
'.sh': ('hash',),
|
|
171
|
+
'.js': ('slashes', 'block_c'),
|
|
172
|
+
'.jsx': ('slashes', 'block_c'),
|
|
173
|
+
'.ts': ('slashes', 'block_c'),
|
|
174
|
+
'.tsx': ('slashes', 'block_c'),
|
|
175
|
+
'.mjs': ('slashes', 'block_c'),
|
|
176
|
+
'.cjs': ('slashes', 'block_c'),
|
|
177
|
+
'.java': ('slashes', 'block_c'),
|
|
178
|
+
'.go': ('slashes', 'block_c'),
|
|
179
|
+
'.rs': ('slashes', 'block_c'),
|
|
180
|
+
'.c': ('slashes', 'block_c'),
|
|
181
|
+
'.cc': ('slashes', 'block_c'),
|
|
182
|
+
'.cpp': ('slashes', 'block_c'),
|
|
183
|
+
'.h': ('slashes', 'block_c'),
|
|
184
|
+
'.hpp': ('slashes', 'block_c'),
|
|
185
|
+
'.swift': ('slashes', 'block_c'),
|
|
186
|
+
'.kt': ('slashes', 'block_c'),
|
|
187
|
+
'.cs': ('slashes', 'block_c'),
|
|
188
|
+
'.php': ('slashes', 'block_c', 'hash'),
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def extract_comment_regions(content: str, ext: str) -> list[tuple[int, str]]:
|
|
193
|
+
"""Return list of (offset, comment_text) for all comment regions in the file."""
|
|
194
|
+
patterns = LANG_COMMENTS.get(ext, ())
|
|
195
|
+
regions = []
|
|
196
|
+
for pkey in patterns:
|
|
197
|
+
for m in COMMENT_PATTERNS[pkey].finditer(content):
|
|
198
|
+
regions.append((m.start(), m.group(0)))
|
|
199
|
+
regions.sort()
|
|
200
|
+
return regions
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def offset_to_line_col(content: str, offset: int) -> tuple[int, int]:
|
|
204
|
+
"""Convert a byte offset into (1-indexed line, 0-indexed column)."""
|
|
205
|
+
prefix = content[:offset]
|
|
206
|
+
line = prefix.count('\n') + 1
|
|
207
|
+
col = offset - (prefix.rfind('\n') + 1) if '\n' in prefix else offset
|
|
208
|
+
return line, col
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# -------- Main extractors --------
|
|
212
|
+
|
|
213
|
+
def extract_markdown(content: str, rel_path: str) -> list[Link]:
|
|
214
|
+
lines = content.splitlines()
|
|
215
|
+
links: list[Link] = []
|
|
216
|
+
|
|
217
|
+
# Collect reference definitions first
|
|
218
|
+
ref_defs: dict[str, str] = {}
|
|
219
|
+
for m in MD_REF_DEF.finditer(content):
|
|
220
|
+
ref_defs[m.group('label').lower()] = strip_angle_brackets(m.group('url'))
|
|
221
|
+
|
|
222
|
+
# Inline links (skip those inside code fences — simple heuristic)
|
|
223
|
+
fence_ranges = _code_fence_ranges(content)
|
|
224
|
+
|
|
225
|
+
def in_fence(offset: int) -> bool:
|
|
226
|
+
return any(s <= offset < e for s, e in fence_ranges)
|
|
227
|
+
|
|
228
|
+
consumed_ranges: list[tuple[int, int]] = []
|
|
229
|
+
|
|
230
|
+
def add_match(m: re.Match, kind: str, url: str, text: str) -> None:
|
|
231
|
+
consumed_ranges.append((m.start(), m.end()))
|
|
232
|
+
if in_fence(m.start()):
|
|
233
|
+
return
|
|
234
|
+
line, col = offset_to_line_col(content, m.start())
|
|
235
|
+
url_clean = strip_angle_brackets(url).strip()
|
|
236
|
+
links.append(Link(
|
|
237
|
+
url=url_clean,
|
|
238
|
+
raw_url=url,
|
|
239
|
+
text=text.strip(),
|
|
240
|
+
file=rel_path,
|
|
241
|
+
line=line,
|
|
242
|
+
col=col,
|
|
243
|
+
context=context_window(lines, line - 1),
|
|
244
|
+
kind=kind,
|
|
245
|
+
link_type=classify(url_clean),
|
|
246
|
+
))
|
|
247
|
+
|
|
248
|
+
for m in MD_INLINE.finditer(content):
|
|
249
|
+
add_match(m, 'md-inline', m.group('url'), m.group('text'))
|
|
250
|
+
|
|
251
|
+
for m in MD_IMAGE.finditer(content):
|
|
252
|
+
add_match(m, 'md-image', m.group('url'), m.group('text'))
|
|
253
|
+
|
|
254
|
+
for m in MD_AUTOLINK.finditer(content):
|
|
255
|
+
add_match(m, 'md-autolink', m.group('url'), '')
|
|
256
|
+
|
|
257
|
+
# Reference-style usage: resolve via ref_defs
|
|
258
|
+
|
|
259
|
+
for m in MD_REF_USE.finditer(content):
|
|
260
|
+
if in_fence(m.start()):
|
|
261
|
+
continue
|
|
262
|
+
label = (m.group('label') or m.group('text')).strip().lower()
|
|
263
|
+
url = ref_defs.get(label)
|
|
264
|
+
if not url:
|
|
265
|
+
continue
|
|
266
|
+
line, col = offset_to_line_col(content, m.start())
|
|
267
|
+
links.append(Link(
|
|
268
|
+
url=url,
|
|
269
|
+
raw_url=url,
|
|
270
|
+
text=m.group('text').strip(),
|
|
271
|
+
file=rel_path,
|
|
272
|
+
line=line,
|
|
273
|
+
col=col,
|
|
274
|
+
context=context_window(lines, line - 1),
|
|
275
|
+
kind='md-ref',
|
|
276
|
+
link_type=classify(url),
|
|
277
|
+
))
|
|
278
|
+
|
|
279
|
+
# Reference definitions also contain URLs worth checking
|
|
280
|
+
for m in MD_REF_DEF.finditer(content):
|
|
281
|
+
consumed_ranges.append((m.start(), m.end()))
|
|
282
|
+
url = strip_angle_brackets(m.group('url')).strip()
|
|
283
|
+
line, col = offset_to_line_col(content, m.start())
|
|
284
|
+
links.append(Link(
|
|
285
|
+
url=url, raw_url=url, text=m.group('label').strip(),
|
|
286
|
+
file=rel_path, line=line, col=col,
|
|
287
|
+
context=context_window(lines, line - 1),
|
|
288
|
+
kind='md-ref-def', link_type=classify(url),
|
|
289
|
+
))
|
|
290
|
+
|
|
291
|
+
# Now scan for bare URLs in regions NOT already consumed (GFM auto-links these)
|
|
292
|
+
def in_consumed(offset: int) -> bool:
|
|
293
|
+
return any(s <= offset < e for s, e in consumed_ranges)
|
|
294
|
+
|
|
295
|
+
for m in BARE_URL.finditer(content):
|
|
296
|
+
if in_fence(m.start()) or in_consumed(m.start()):
|
|
297
|
+
continue
|
|
298
|
+
raw = m.group('url')
|
|
299
|
+
url = clean_bare_url(raw)
|
|
300
|
+
if not url:
|
|
301
|
+
continue
|
|
302
|
+
line, col = offset_to_line_col(content, m.start())
|
|
303
|
+
links.append(Link(
|
|
304
|
+
url=url, raw_url=raw, text='',
|
|
305
|
+
file=rel_path, line=line, col=col,
|
|
306
|
+
context=context_window(lines, line - 1),
|
|
307
|
+
kind='bare', link_type=classify(url),
|
|
308
|
+
))
|
|
309
|
+
|
|
310
|
+
return links
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _code_fence_ranges(content: str) -> list[tuple[int, int]]:
|
|
314
|
+
"""Find ``` ... ``` fenced code blocks so we skip links inside them."""
|
|
315
|
+
ranges = []
|
|
316
|
+
fence_re = re.compile(r'^( {0,3})(```+|~~~+)', re.MULTILINE)
|
|
317
|
+
open_start: int | None = None
|
|
318
|
+
open_char: str | None = None
|
|
319
|
+
open_count: int = 0
|
|
320
|
+
for m in fence_re.finditer(content):
|
|
321
|
+
char = m.group(2)[0] # '`' or '~'
|
|
322
|
+
count = len(m.group(2))
|
|
323
|
+
if open_start is None:
|
|
324
|
+
# Opening fence
|
|
325
|
+
open_start = m.start()
|
|
326
|
+
open_char = char
|
|
327
|
+
open_count = count
|
|
328
|
+
elif char == open_char and count >= open_count:
|
|
329
|
+
# Matching closing fence
|
|
330
|
+
ranges.append((open_start, m.end()))
|
|
331
|
+
open_start = None
|
|
332
|
+
open_char = None
|
|
333
|
+
open_count = 0
|
|
334
|
+
# else: different fence char or shorter count inside a block — skip
|
|
335
|
+
# If there's an unclosed fence, treat everything after it as fenced
|
|
336
|
+
if open_start is not None:
|
|
337
|
+
ranges.append((open_start, len(content)))
|
|
338
|
+
return ranges
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def extract_rst(content: str, rel_path: str) -> list[Link]:
|
|
342
|
+
lines = content.splitlines()
|
|
343
|
+
links: list[Link] = []
|
|
344
|
+
consumed_ranges: list[tuple[int, int]] = []
|
|
345
|
+
for m in RST_LINK.finditer(content):
|
|
346
|
+
consumed_ranges.append((m.start(), m.end()))
|
|
347
|
+
line, col = offset_to_line_col(content, m.start())
|
|
348
|
+
url = m.group('url').strip()
|
|
349
|
+
links.append(Link(
|
|
350
|
+
url=url, raw_url=url, text=m.group('text').strip(),
|
|
351
|
+
file=rel_path, line=line, col=col,
|
|
352
|
+
context=context_window(lines, line - 1),
|
|
353
|
+
kind='rst-link', link_type=classify(url),
|
|
354
|
+
))
|
|
355
|
+
for m in RST_TARGET.finditer(content):
|
|
356
|
+
consumed_ranges.append((m.start(), m.end()))
|
|
357
|
+
line, col = offset_to_line_col(content, m.start())
|
|
358
|
+
url = m.group('url').strip()
|
|
359
|
+
links.append(Link(
|
|
360
|
+
url=url, raw_url=url, text=m.group('label').strip(),
|
|
361
|
+
file=rel_path, line=line, col=col,
|
|
362
|
+
context=context_window(lines, line - 1),
|
|
363
|
+
kind='rst-target', link_type=classify(url),
|
|
364
|
+
))
|
|
365
|
+
# Also pick up bare URLs in rST bodies NOT already consumed by link/target patterns
|
|
366
|
+
def in_consumed(offset: int) -> bool:
|
|
367
|
+
return any(s <= offset < e for s, e in consumed_ranges)
|
|
368
|
+
|
|
369
|
+
for m in BARE_URL.finditer(content):
|
|
370
|
+
if in_consumed(m.start()):
|
|
371
|
+
continue
|
|
372
|
+
raw = m.group('url')
|
|
373
|
+
url = clean_bare_url(raw)
|
|
374
|
+
if not url:
|
|
375
|
+
continue
|
|
376
|
+
line, col = offset_to_line_col(content, m.start())
|
|
377
|
+
links.append(Link(
|
|
378
|
+
url=url, raw_url=raw, text='',
|
|
379
|
+
file=rel_path, line=line, col=col,
|
|
380
|
+
context=context_window(lines, line - 1),
|
|
381
|
+
kind='bare', link_type=classify(url),
|
|
382
|
+
))
|
|
383
|
+
return links
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def extract_bare_urls(content: str, rel_path: str, kind: str = 'bare') -> list[Link]:
|
|
387
|
+
lines = content.splitlines()
|
|
388
|
+
out: list[Link] = []
|
|
389
|
+
for m in BARE_URL.finditer(content):
|
|
390
|
+
raw = m.group('url')
|
|
391
|
+
url = clean_bare_url(raw)
|
|
392
|
+
if not url:
|
|
393
|
+
continue
|
|
394
|
+
line, col = offset_to_line_col(content, m.start())
|
|
395
|
+
out.append(Link(
|
|
396
|
+
url=url, raw_url=raw, text='',
|
|
397
|
+
file=rel_path, line=line, col=col,
|
|
398
|
+
context=context_window(lines, line - 1),
|
|
399
|
+
kind=kind, link_type=classify(url),
|
|
400
|
+
))
|
|
401
|
+
return out
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def extract_code_comments(content: str, rel_path: str, ext: str) -> list[Link]:
|
|
405
|
+
"""Pull bare URLs out of comment regions only."""
|
|
406
|
+
regions = extract_comment_regions(content, ext)
|
|
407
|
+
if not regions:
|
|
408
|
+
return []
|
|
409
|
+
lines = content.splitlines()
|
|
410
|
+
out: list[Link] = []
|
|
411
|
+
seen: set[tuple[int, int]] = set()
|
|
412
|
+
for base_offset, text in regions:
|
|
413
|
+
for m in BARE_URL.finditer(text):
|
|
414
|
+
absolute_offset = base_offset + m.start()
|
|
415
|
+
if absolute_offset in {s for s, _ in seen}:
|
|
416
|
+
continue
|
|
417
|
+
raw = m.group('url')
|
|
418
|
+
url = clean_bare_url(raw)
|
|
419
|
+
if not url:
|
|
420
|
+
continue
|
|
421
|
+
line, col = offset_to_line_col(content, absolute_offset)
|
|
422
|
+
key = (line, col)
|
|
423
|
+
if key in seen:
|
|
424
|
+
continue
|
|
425
|
+
seen.add(key)
|
|
426
|
+
out.append(Link(
|
|
427
|
+
url=url, raw_url=raw, text='',
|
|
428
|
+
file=rel_path, line=line, col=col,
|
|
429
|
+
context=context_window(lines, line - 1, before=2, after=1),
|
|
430
|
+
kind='comment-url', link_type=classify(url),
|
|
431
|
+
))
|
|
432
|
+
return out
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
# -------- File walking --------
|
|
436
|
+
|
|
437
|
+
def should_skip_dir(name: str, extra_exclude: set[str]) -> bool:
|
|
438
|
+
return name in DEFAULT_EXCLUDE or name in extra_exclude
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def walk_files(root: Path, extensions: set[str], include_code: bool,
|
|
442
|
+
extra_exclude: set[str]) -> Iterable[Path]:
|
|
443
|
+
allowed = set(extensions)
|
|
444
|
+
if include_code:
|
|
445
|
+
allowed |= CODE_EXTS
|
|
446
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
447
|
+
dirnames[:] = [d for d in dirnames if not should_skip_dir(d, extra_exclude)]
|
|
448
|
+
for fn in filenames:
|
|
449
|
+
ext = Path(fn).suffix.lower()
|
|
450
|
+
if ext in allowed:
|
|
451
|
+
yield Path(dirpath) / fn
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def extract_from_file(path: Path, rel_path: str, include_code: bool) -> list[Link]:
|
|
455
|
+
ext = path.suffix.lower()
|
|
456
|
+
try:
|
|
457
|
+
content = path.read_text(encoding='utf-8', errors='replace')
|
|
458
|
+
except Exception as e:
|
|
459
|
+
print(f"skip {rel_path}: {e}", file=sys.stderr)
|
|
460
|
+
return []
|
|
461
|
+
|
|
462
|
+
if ext in {'.md', '.mdx', '.markdown'}:
|
|
463
|
+
return extract_markdown(content, rel_path)
|
|
464
|
+
if ext == '.rst':
|
|
465
|
+
return extract_rst(content, rel_path)
|
|
466
|
+
if ext == '.txt':
|
|
467
|
+
return extract_bare_urls(content, rel_path)
|
|
468
|
+
if include_code and ext in CODE_EXTS:
|
|
469
|
+
return extract_code_comments(content, rel_path, ext)
|
|
470
|
+
return []
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
# -------- CLI --------
|
|
474
|
+
|
|
475
|
+
def main() -> int:
|
|
476
|
+
p = argparse.ArgumentParser(description=__doc__)
|
|
477
|
+
p.add_argument('repo', help='Path to repository root')
|
|
478
|
+
p.add_argument('--out', help='Write JSON here instead of stdout')
|
|
479
|
+
p.add_argument('--include-code', action='store_true',
|
|
480
|
+
help='Also scan URLs inside code comments')
|
|
481
|
+
p.add_argument('--extensions', default=','.join(sorted(DOC_EXTS)),
|
|
482
|
+
help='Comma-separated list of doc extensions')
|
|
483
|
+
p.add_argument('--exclude', default='',
|
|
484
|
+
help='Additional comma-separated directory names to skip')
|
|
485
|
+
args = p.parse_args()
|
|
486
|
+
|
|
487
|
+
root = Path(args.repo).resolve()
|
|
488
|
+
if not root.is_dir():
|
|
489
|
+
print(f"error: {root} is not a directory", file=sys.stderr)
|
|
490
|
+
return 2
|
|
491
|
+
|
|
492
|
+
extensions = {e if e.startswith('.') else '.' + e
|
|
493
|
+
for e in args.extensions.split(',') if e.strip()}
|
|
494
|
+
extra_exclude = {e.strip() for e in args.exclude.split(',') if e.strip()}
|
|
495
|
+
|
|
496
|
+
all_links: list[Link] = []
|
|
497
|
+
file_count = 0
|
|
498
|
+
for path in walk_files(root, extensions, args.include_code, extra_exclude):
|
|
499
|
+
rel = str(path.relative_to(root))
|
|
500
|
+
all_links.extend(extract_from_file(path, rel, args.include_code))
|
|
501
|
+
file_count += 1
|
|
502
|
+
|
|
503
|
+
result = {
|
|
504
|
+
'repo': str(root),
|
|
505
|
+
'file_count': file_count,
|
|
506
|
+
'link_count': len(all_links),
|
|
507
|
+
'links': [l.to_dict() for l in all_links],
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
out_text = json.dumps(result, indent=2, ensure_ascii=False)
|
|
511
|
+
if args.out:
|
|
512
|
+
Path(args.out).write_text(out_text, encoding='utf-8')
|
|
513
|
+
print(f"wrote {len(all_links)} links from {file_count} files to {args.out}",
|
|
514
|
+
file=sys.stderr)
|
|
515
|
+
else:
|
|
516
|
+
print(out_text)
|
|
517
|
+
return 0
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
if __name__ == '__main__':
|
|
521
|
+
sys.exit(main())
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<!-- Full version: content/prompt-task-edge-case-discovery.md -->
|
|
2
|
+
You are a Specification Edge Case Analyst. Systematically discover edge cases, failure modes, and specification gaps *before* implementation using a six-boundary analytical framework. Do NOT modify the specification — produce structured gap analysis with concrete scenarios only.
|
|
3
|
+
|
|
4
|
+
**GUARD:** This is a probabilistic model. Treat output as structured triage, not ground truth. Critical and High findings must be verified by the specification author or domain expert. Do not apply to draft outlines, code review, or trivial single-function changes.
|
|
5
|
+
|
|
6
|
+
**INPUT**
|
|
7
|
+
- Specification to analyze: [PASTE OR SPECIFY FILE PATH]
|
|
8
|
+
- Risk profile (optional): [standard | security-sensitive | safety-critical | concurrent-distributed | regulatory | "infer"]
|
|
9
|
+
- Team capability (optional): [all | no-formal-methods | basic-only | "infer"]
|
|
10
|
+
|
|
11
|
+
**PROTOCOL (Six-Boundary Sequential Analysis)**
|
|
12
|
+
|
|
13
|
+
Apply boundaries in order. Each builds on prior findings. After Boundary 4, risk-gate Boundaries 5-6.
|
|
14
|
+
|
|
15
|
+
**Boundary 1 — HUMAN (UX Stress Cases & Extreme Personas):**
|
|
16
|
+
Identify gaps from the perspective of users under stress, constraint, or at the fringes. For non-user-facing specs (libraries, infrastructure, pipelines), reinterpret "users" as *consumers*: other services, operators, maintainers. Focus on operator stress (on-call at 3AM), consumer misuse (wrong config, unexpected input), and observability gaps.
|
|
17
|
+
- Extreme personas: accessibility needs, low digital literacy, malicious intent, emotional distress (or: sleep-deprived operator, misconfigured consumer, unfamiliar maintainer)
|
|
18
|
+
- Environmental stress: low bandwidth, failing hardware, interrupted sessions, screen readers
|
|
19
|
+
- Crisis context: rushed, panicking, cognitively overloaded users
|
|
20
|
+
- Recovery paths: when something fails, can the user recover or is it a dead end?
|
|
21
|
+
Expect 3-8 findings per major capability.
|
|
22
|
+
Format: `[HUM-NNN] [severity] — Persona/Scenario: ... Gap: ... Impact: ... Suggested Scenario: Given/When/Then`
|
|
23
|
+
|
|
24
|
+
**Boundary 2 — BUSINESS (Example Mapping & Scenario Catalog):**
|
|
25
|
+
Decompose each capability into rules, examples, and unresolved questions. Verify four-path coverage:
|
|
26
|
+
- Happy path (standard success)
|
|
27
|
+
- Error path (validation failures, user mistakes)
|
|
28
|
+
- Abuse path (business logic abuse — gaming limits, exploiting policies; defer technical/security abuse to Boundary 5)
|
|
29
|
+
- Hazard path (system failures, network outages, data corruption)
|
|
30
|
+
Flag unresolved questions where two reasonable people would disagree on behavior.
|
|
31
|
+
Expect 5-15 findings per major capability (typically the highest-yield boundary).
|
|
32
|
+
Format: `[BIZ-NNN] [severity] — Rule: ... Missing Path: ... Gap: ... Question: ... Suggested Scenario: Given/When/Then`
|
|
33
|
+
|
|
34
|
+
**Boundary 3 — MATHEMATICAL (BVA, EP, Decision Tables):**
|
|
35
|
+
Apply Equivalence Partitioning, Boundary Value Analysis, and Decision Tables.
|
|
36
|
+
- Input partitioning: valid and invalid classes for each input
|
|
37
|
+
- Boundary values: min, min-1, max, max+1, zero, empty, null — is behavior defined?
|
|
38
|
+
- Decision table: cross-product of multi-condition rules — unhandled combinations? impossible states?
|
|
39
|
+
- Data type boundaries: string lengths, numeric overflow, date ranges, array capacities, Unicode
|
|
40
|
+
Format: `[MATH-NNN] [severity] — Input/Condition: ... Boundary: ... Gap: ... Suggested Requirement: ...`
|
|
41
|
+
|
|
42
|
+
**Boundary 4 — ARCHITECTURAL (Design by Contract):**
|
|
43
|
+
Examine interfaces for missing contracts.
|
|
44
|
+
- Preconditions: what must be true before each operation? Are caller obligations explicit?
|
|
45
|
+
- Postconditions: what does the system guarantee after success? Are exit states defined?
|
|
46
|
+
- Invariants: what must always hold? ("balance never negative unless overdraft enabled")
|
|
47
|
+
- Forbidden states: what must never happen? Documented?
|
|
48
|
+
- Cascading failures: if a postcondition is violated, what breaks downstream?
|
|
49
|
+
Format: `[ARCH-NNN] [severity] — Interface: ... Contract Element: ... Gap: ... Suggested Contract: ...`
|
|
50
|
+
|
|
51
|
+
**RISK GATE** — Before Boundary 5, output:
|
|
52
|
+
```
|
|
53
|
+
Risk Assessment: Safety/security [Y/N], Concurrency [Y/N], Regulatory [Y/N], Failure cost [L/M/H]
|
|
54
|
+
Boundary 5 (Failure): [APPLY|SKIP] — rationale
|
|
55
|
+
Boundary 6 (Formal): [APPLY|SKIP] — rationale
|
|
56
|
+
```
|
|
57
|
+
If standard-risk with no concerns: skip Boundaries 5-6, proceed to Synthesis.
|
|
58
|
+
|
|
59
|
+
**Boundary 5 — FAILURE (FMEA + STRIDE Threat Modeling) — Risk-Gated:**
|
|
60
|
+
FMEA: enumerate failure modes per component. Assess qualitatively: Severity [Critical/High/Med/Low], Likelihood [Likely/Possible/Unlikely], Detectability [Easy/Hard/Hidden]. Rank risk by combination (e.g., Critical + Likely + Hidden = highest priority). Identify missing controls, diagnostics, graceful degradation.
|
|
61
|
+
STRIDE: Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, Elevation of privilege.
|
|
62
|
+
Format: `[FAIL-NNN] [severity] — Failure Mode/Threat: ... Category: ... Risk: [Sev/Likelihood/Detect] or [STRIDE category] ... Gap: ... Suggested Mitigation: ...`
|
|
63
|
+
|
|
64
|
+
**Boundary 6 — FORMAL (Invariant & State Analysis) — Risk-Gated:**
|
|
65
|
+
For concurrent, distributed, or authorization-intensive subsystems:
|
|
66
|
+
- State machine completeness: all states, transitions, terminal conditions defined?
|
|
67
|
+
- Concurrency: race conditions, deadlocks, livelocks, ordering dependencies
|
|
68
|
+
- Authorization: unintended permission paths? deny-by-default?
|
|
69
|
+
- Distributed consistency: network partitions, split-brain, eventual consistency windows
|
|
70
|
+
- Temporal: liveness (good things eventually happen) and safety (bad things never happen)
|
|
71
|
+
Format: `[FORM-NNN] [severity] — Subsystem: ... Property Type: ... Gap: ... Scenario: ... Suggested Property: ...`
|
|
72
|
+
|
|
73
|
+
**SYNTHESIS**
|
|
74
|
+
|
|
75
|
+
Produce a final report:
|
|
76
|
+
```
|
|
77
|
+
# Edge Case Discovery Report
|
|
78
|
+
Specification: [title/path]
|
|
79
|
+
Risk Profile: [profile]
|
|
80
|
+
Boundaries Applied: [1-4 | 1-5 | 1-6]
|
|
81
|
+
|
|
82
|
+
## Gap Summary
|
|
83
|
+
| Boundary | CRITICAL | HIGH | MEDIUM | LOW | Total |
|
|
84
|
+
(one row per applied boundary, plus TOTAL)
|
|
85
|
+
|
|
86
|
+
## Top 5 Critical Gaps
|
|
87
|
+
[ID] - Description / Impact / Fix (specific addition to spec)
|
|
88
|
+
|
|
89
|
+
## Scenario Catalog Summary
|
|
90
|
+
Happy/Error/Abuse/Hazard paths: defined vs missing counts
|
|
91
|
+
Boundary conditions: tested vs missing counts
|
|
92
|
+
|
|
93
|
+
## Missing Contracts
|
|
94
|
+
Preconditions / Postconditions / Invariants not defined
|
|
95
|
+
|
|
96
|
+
## Verdict
|
|
97
|
+
[NO_GAPS_FOUND | NEEDS_SCENARIOS | NEEDS_CONTRACTS | NEEDS_REWORK]
|
|
98
|
+
Rationale: what must be added before implementation
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**RULES**
|
|
102
|
+
1. Do NOT modify the specification. Gap analysis only.
|
|
103
|
+
2. Every gap MUST include a concrete suggested scenario (Given/When/Then) or contract statement.
|
|
104
|
+
3. Build progressively — reference prior findings by ID in later boundaries.
|
|
105
|
+
4. Respect the risk gate — do not apply Boundaries 5-6 to standard-risk specs unless analysis reveals safety/security/concurrency concerns.
|
|
106
|
+
5. Acknowledge coverage limits: ML/data edge cases, brownfield architectural gaps, and domain-specific risks are outside this protocol's scope.
|
|
107
|
+
6. If team lacks capability for a protocol (e.g., formal methods), recommend the lightweight alternative.
|
|
108
|
+
|
|
109
|
+
Severity: CRITICAL = spec gap that will cause implementation failure or security breach. HIGH = missing scenario or contract that creates significant ambiguity. MEDIUM = incomplete boundary or partial coverage. LOW = missing edge case with minimal impact.
|
|
110
|
+
|
|
111
|
+
**Example finding:**
|
|
112
|
+
```
|
|
113
|
+
[BIZ-002] CRITICAL - Section 4.1 "Transfer Limits"
|
|
114
|
+
Rule: Daily transfer limit per account
|
|
115
|
+
Missing Path: abuse
|
|
116
|
+
Gap: No specification of behavior when limit is approached via multiple small transfers
|
|
117
|
+
Question: Should rate limiting apply per-recipient or per-account?
|
|
118
|
+
Suggested Scenario:
|
|
119
|
+
Given Alice has transferred $9,900 of a $10,000 daily limit
|
|
120
|
+
When Alice initiates a $200 transfer
|
|
121
|
+
Then the transfer is declined with reason "daily_limit_exceeded"
|
|
122
|
+
And the remaining limit ($100) is shown to the user
|
|
123
|
+
```
|