sdetkit 1.0.3__py3-none-any.whl
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.
- sdetkit/__init__.py +122 -0
- sdetkit/__main__.py +136 -0
- sdetkit/_entrypoints.py +14 -0
- sdetkit/_legacy_lane.py +86 -0
- sdetkit/_toml.py +14 -0
- sdetkit/acceleration_closeout_43.py +462 -0
- sdetkit/agent/__init__.py +3 -0
- sdetkit/agent/actions.py +402 -0
- sdetkit/agent/cli.py +434 -0
- sdetkit/agent/core.py +538 -0
- sdetkit/agent/dashboard.py +346 -0
- sdetkit/agent/demo.py +87 -0
- sdetkit/agent/omnichannel.py +431 -0
- sdetkit/agent/providers.py +90 -0
- sdetkit/agent/templates.py +595 -0
- sdetkit/apiclient.py +601 -0
- sdetkit/apiget.py +834 -0
- sdetkit/apiget_dispatch.py +50 -0
- sdetkit/argv_flags.py +15 -0
- sdetkit/artifact_contract_index.py +114 -0
- sdetkit/atomicio.py +93 -0
- sdetkit/author_problem.py +2211 -0
- sdetkit/baseline_dispatch.py +75 -0
- sdetkit/bools.py +66 -0
- sdetkit/case_study_launch_closeout_73.py +398 -0
- sdetkit/case_study_prep1_closeout_69.py +392 -0
- sdetkit/case_study_prep2_closeout_70.py +394 -0
- sdetkit/case_study_prep3_closeout_71.py +400 -0
- sdetkit/case_study_prep4_closeout_72.py +411 -0
- sdetkit/cassette.py +291 -0
- sdetkit/checks/__init__.py +60 -0
- sdetkit/checks/__main__.py +6 -0
- sdetkit/checks/artifacts.py +694 -0
- sdetkit/checks/base.py +154 -0
- sdetkit/checks/builtin.py +398 -0
- sdetkit/checks/cache.py +117 -0
- sdetkit/checks/main.py +268 -0
- sdetkit/checks/planner.py +351 -0
- sdetkit/checks/registry.py +132 -0
- sdetkit/checks/results.py +195 -0
- sdetkit/checks/runner.py +293 -0
- sdetkit/ci.py +139 -0
- sdetkit/cli.py +813 -0
- sdetkit/cli_shortcuts.py +86 -0
- sdetkit/cli_timing.py +26 -0
- sdetkit/community_activation.py +411 -0
- sdetkit/community_program_closeout_62.py +33 -0
- sdetkit/community_touchpoint_closeout_77.py +411 -0
- sdetkit/continuous_upgrade_closeout_1.py +18 -0
- sdetkit/continuous_upgrade_closeout_10.py +18 -0
- sdetkit/continuous_upgrade_closeout_11.py +18 -0
- sdetkit/continuous_upgrade_closeout_2.py +18 -0
- sdetkit/continuous_upgrade_closeout_3.py +18 -0
- sdetkit/continuous_upgrade_closeout_4.py +18 -0
- sdetkit/continuous_upgrade_closeout_5.py +18 -0
- sdetkit/continuous_upgrade_closeout_6.py +18 -0
- sdetkit/continuous_upgrade_closeout_7.py +18 -0
- sdetkit/continuous_upgrade_closeout_8.py +18 -0
- sdetkit/continuous_upgrade_closeout_9.py +18 -0
- sdetkit/contract.py +126 -0
- sdetkit/contributor_activation_closeout_55.py +467 -0
- sdetkit/contributor_funnel.py +281 -0
- sdetkit/contributor_recognition_closeout_76.py +398 -0
- sdetkit/core_preparse_dispatch.py +35 -0
- sdetkit/demo.py +276 -0
- sdetkit/demo_asset2_34.py +25 -0
- sdetkit/demo_asset_33.py +25 -0
- sdetkit/distribution_batch_38.py +27 -0
- sdetkit/distribution_closeout_36.py +26 -0
- sdetkit/distribution_scaling_closeout_74.py +399 -0
- sdetkit/docs_loop_closeout_53.py +514 -0
- sdetkit/docs_navigation.py +281 -0
- sdetkit/docs_qa.py +260 -0
- sdetkit/doctor.py +3451 -0
- sdetkit/ecosystem_priorities_closeout_78.py +405 -0
- sdetkit/enterprise_assessment.py +686 -0
- sdetkit/enterprise_readiness.py +503 -0
- sdetkit/entrypoints.py +15 -0
- sdetkit/evidence.py +261 -0
- sdetkit/evidence_narrative_closeout_84.py +411 -0
- sdetkit/evidence_workspace.py +184 -0
- sdetkit/execution_prioritization_closeout_50.py +31 -0
- sdetkit/expansion_automation_41.py +27 -0
- sdetkit/expansion_closeout_45.py +441 -0
- sdetkit/experiment_lane_37.py +29 -0
- sdetkit/external_contribution.py +463 -0
- sdetkit/feature_registry.py +173 -0
- sdetkit/feature_registry_cli.py +182 -0
- sdetkit/first_contribution.py +388 -0
- sdetkit/forensics.py +183 -0
- sdetkit/gate.py +838 -0
- sdetkit/github_actions_quickstart.py +572 -0
- sdetkit/gitlab_ci_quickstart.py +603 -0
- sdetkit/governance_handoff_closeout_87.py +409 -0
- sdetkit/governance_priorities_closeout_88.py +409 -0
- sdetkit/governance_scale_closeout_89.py +411 -0
- sdetkit/growth_campaign_closeout_81.py +395 -0
- sdetkit/help_surface.py +57 -0
- sdetkit/import_hazards.py +38 -0
- sdetkit/inspect_compare.py +581 -0
- sdetkit/inspect_compare_forwarding.py +33 -0
- sdetkit/inspect_data.py +954 -0
- sdetkit/inspect_forwarding.py +15 -0
- sdetkit/inspect_project.py +641 -0
- sdetkit/inspect_project_forwarding.py +18 -0
- sdetkit/integration.py +642 -0
- sdetkit/integration_expansion2_closeout_66.py +405 -0
- sdetkit/integration_expansion3_closeout_67.py +411 -0
- sdetkit/integration_expansion4_closeout_68.py +416 -0
- sdetkit/integration_feedback_closeout_82.py +411 -0
- sdetkit/intelligence.py +388 -0
- sdetkit/judgment.py +268 -0
- sdetkit/kits.py +385 -0
- sdetkit/kpi_audit.py +29 -0
- sdetkit/kpi_deep_audit_closeout_57.py +31 -0
- sdetkit/kpi_instrumentation_35.py +25 -0
- sdetkit/kpi_report.py +152 -0
- sdetkit/kvcli.py +171 -0
- sdetkit/launch_readiness_closeout_86.py +409 -0
- sdetkit/legacy_adapters/__init__.py +16 -0
- sdetkit/legacy_adapters/closeout.py +51 -0
- sdetkit/legacy_adapters/continuous_upgrade.py +15 -0
- sdetkit/legacy_adapters/foundation.py +36 -0
- sdetkit/legacy_cli.py +54 -0
- sdetkit/legacy_commands.py +55 -0
- sdetkit/legacy_namespace.py +21 -0
- sdetkit/main_.py +3 -0
- sdetkit/maintenance/__init__.py +5 -0
- sdetkit/maintenance/__main__.py +6 -0
- sdetkit/maintenance/checks/__init__.py +1 -0
- sdetkit/maintenance/checks/clean_tree_check.py +76 -0
- sdetkit/maintenance/checks/custom_example_check.py +27 -0
- sdetkit/maintenance/checks/deps_check.py +59 -0
- sdetkit/maintenance/checks/doctor_check.py +115 -0
- sdetkit/maintenance/checks/github_automation_check.py +341 -0
- sdetkit/maintenance/checks/lint_check.py +85 -0
- sdetkit/maintenance/checks/security_check.py +243 -0
- sdetkit/maintenance/checks/tests_check.py +55 -0
- sdetkit/maintenance/cli.py +360 -0
- sdetkit/maintenance/main.py +6 -0
- sdetkit/maintenance/registry.py +42 -0
- sdetkit/maintenance/types.py +47 -0
- sdetkit/maintenance/utils.py +8 -0
- sdetkit/netclient.py +1042 -0
- sdetkit/notify.py +83 -0
- sdetkit/notify_plugins.py +54 -0
- sdetkit/objection_closeout_48.py +29 -0
- sdetkit/objection_handling.py +534 -0
- sdetkit/onboarding.py +293 -0
- sdetkit/onboarding_optimization.py +458 -0
- sdetkit/ops.py +980 -0
- sdetkit/ops_control.py +371 -0
- sdetkit/optimization_closeout_42.py +477 -0
- sdetkit/optimization_closeout_46.py +444 -0
- sdetkit/optional_httpx.py +65 -0
- sdetkit/parsed_shortcuts.py +63 -0
- sdetkit/parser_helpers.py +23 -0
- sdetkit/partner_outreach_closeout_80.py +458 -0
- sdetkit/patch.py +959 -0
- sdetkit/phase1_hardening_29.py +342 -0
- sdetkit/phase1_wrap_30.py +28 -0
- sdetkit/phase2_hardening_closeout_58.py +388 -0
- sdetkit/phase2_kickoff_31.py +27 -0
- sdetkit/phase2_wrap_handoff_closeout_60.py +383 -0
- sdetkit/phase3_kickoff_closeout_61.py +438 -0
- sdetkit/phase3_preplan_closeout_59.py +378 -0
- sdetkit/phase3_wrap_publication_closeout_90.py +418 -0
- sdetkit/phase_boost.py +48 -0
- sdetkit/playbook_aliases.py +20 -0
- sdetkit/playbook_post_39.py +27 -0
- sdetkit/playbooks_cli.py +523 -0
- sdetkit/plugin_system.py +114 -0
- sdetkit/plugins.py +870 -0
- sdetkit/policy.py +274 -0
- sdetkit/portfolio_readiness.py +159 -0
- sdetkit/premium_gate_engine.py +1915 -0
- sdetkit/production_readiness.py +357 -0
- sdetkit/projects.py +553 -0
- sdetkit/proof.py +214 -0
- sdetkit/public_command_surface.json +41 -0
- sdetkit/public_surface_contract.py +151 -0
- sdetkit/quality_contribution_delta.py +488 -0
- sdetkit/readiness.py +403 -0
- sdetkit/release_cadence_32.py +25 -0
- sdetkit/release_communications.py +488 -0
- sdetkit/release_dispatch.py +32 -0
- sdetkit/release_prioritization_closeout_85.py +412 -0
- sdetkit/release_readiness.py +423 -0
- sdetkit/reliability_closeout_47.py +29 -0
- sdetkit/reliability_evidence_pack.py +500 -0
- sdetkit/repo.py +4807 -0
- sdetkit/repo_adoption.py +166 -0
- sdetkit/repo_init_forwarding.py +24 -0
- sdetkit/report.py +1095 -0
- sdetkit/review.py +2555 -0
- sdetkit/review_engine.py +1052 -0
- sdetkit/review_forwarding.py +26 -0
- sdetkit/roadmap.py +147 -0
- sdetkit/roadmap_manifest.py +298 -0
- sdetkit/scale_closeout_44.py +458 -0
- sdetkit/scale_lane_40.py +27 -0
- sdetkit/scale_upgrade_closeout_79.py +396 -0
- sdetkit/sdet_package.py +318 -0
- sdetkit/security.py +176 -0
- sdetkit/security_gate.py +1460 -0
- sdetkit/serve.py +391 -0
- sdetkit/serve_forwarding.py +12 -0
- sdetkit/ship_readiness.py +230 -0
- sdetkit/sqlite_scalar.py +196 -0
- sdetkit/stabilization_closeout_56.py +451 -0
- sdetkit/startup_readiness.py +352 -0
- sdetkit/templates/__init__.py +0 -0
- sdetkit/templates/enterprise_python/__init__.py +0 -0
- sdetkit/test_bootstrap.py +33 -0
- sdetkit/test_bootstrap_contract.py +126 -0
- sdetkit/test_bootstrap_validate.py +66 -0
- sdetkit/textutil.py +47 -0
- sdetkit/triage_templates.py +449 -0
- sdetkit/trust_assets.py +523 -0
- sdetkit/trust_assets_refresh_closeout_75.py +398 -0
- sdetkit/trust_faq_expansion_closeout_83.py +417 -0
- sdetkit/upgrade_audit.py +2439 -0
- sdetkit/upgrade_hub.py +148 -0
- sdetkit/versioning.py +10 -0
- sdetkit/weekly_review.py +161 -0
- sdetkit/weekly_review_28.py +24 -0
- sdetkit/weekly_review_closeout_49.py +27 -0
- sdetkit/weekly_review_closeout_65.py +405 -0
- sdetkit-1.0.3.dist-info/METADATA +314 -0
- sdetkit-1.0.3.dist-info/RECORD +236 -0
- sdetkit-1.0.3.dist-info/WHEEL +5 -0
- sdetkit-1.0.3.dist-info/entry_points.txt +12 -0
- sdetkit-1.0.3.dist-info/licenses/LICENSE +160 -0
- sdetkit-1.0.3.dist-info/top_level.txt +2 -0
- sdkit/__init__.py +3 -0
- sdkit/__main__.py +6 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ._legacy_lane import run_lane
|
|
4
|
+
|
|
5
|
+
_DEFAULT_PAGE_TEMPLATE = "# Experiment lane\n\n## Experiment lane\n"
|
|
6
|
+
_CFG = {
|
|
7
|
+
"name": "experiment-lane",
|
|
8
|
+
"page_path": "docs/integrations-experiment-lane.md",
|
|
9
|
+
"required_inputs": [
|
|
10
|
+
"docs/artifacts/distribution-closeout-pack/distribution-closeout-summary.json"
|
|
11
|
+
],
|
|
12
|
+
"required_boards": ["docs/artifacts/distribution-closeout-pack/delivery-board.md"],
|
|
13
|
+
"summary_json": "experiment-lane-summary.json",
|
|
14
|
+
"summary_md": "experiment-lane-summary.md",
|
|
15
|
+
"pack_files": [
|
|
16
|
+
"experiment-matrix.csv",
|
|
17
|
+
"hypothesis-brief.md",
|
|
18
|
+
"experiment-scorecard.json",
|
|
19
|
+
"decision-log.md",
|
|
20
|
+
"delivery-board.md",
|
|
21
|
+
"validation-commands.md",
|
|
22
|
+
],
|
|
23
|
+
"evidence_json": "execution-summary.json",
|
|
24
|
+
"text_output": " experiment lane summary",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def main(argv: list[str] | None = None) -> int:
|
|
29
|
+
return run_lane(argv, _CFG)
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import shlex
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
_PAGE_PATH = "docs/external-contribution.md"
|
|
12
|
+
_TOP10_PATH = "docs/top-10-github-strategy.md"
|
|
13
|
+
_SECTION_HEADER = "# External contribution"
|
|
14
|
+
_REQUIRED_SECTIONS = [
|
|
15
|
+
"## Who should run external-contribution",
|
|
16
|
+
"## Starter-task spotlight contract",
|
|
17
|
+
"## Launch checklist",
|
|
18
|
+
"## First-response SLA",
|
|
19
|
+
"## Activation scoring model",
|
|
20
|
+
"## Execution evidence mode",
|
|
21
|
+
]
|
|
22
|
+
_REQUIRED_COMMANDS = [
|
|
23
|
+
"python -m sdetkit external-contribution --format json --strict",
|
|
24
|
+
"python -m sdetkit external-contribution --emit-pack-dir docs/artifacts/external-contribution-pack --format json --strict",
|
|
25
|
+
"python -m sdetkit external-contribution --execute --evidence-dir docs/artifacts/external-contribution-pack/evidence --format json --strict",
|
|
26
|
+
"python scripts/check_external_contribution_contract.py",
|
|
27
|
+
]
|
|
28
|
+
_EXECUTION_COMMANDS = [
|
|
29
|
+
"python -m sdetkit external-contribution --format json --strict",
|
|
30
|
+
"python scripts/check_external_contribution_contract.py --skip-evidence",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
_DEFAULT_PAGE_TEMPLATE = """# External contribution
|
|
34
|
+
|
|
35
|
+
External contribution upgrades public contribution pull by spotlighting starter tasks with clear owners, response SLAs, and evidence-ready follow-up.
|
|
36
|
+
|
|
37
|
+
## Who should run external-contribution
|
|
38
|
+
|
|
39
|
+
- Maintainers who want more first-time external contributions from open starter tasks.
|
|
40
|
+
- DevRel/community owners who promote contributor-friendly backlog slices.
|
|
41
|
+
- Engineering leads that need deterministic response and conversion metrics.
|
|
42
|
+
|
|
43
|
+
## Starter-task spotlight contract
|
|
44
|
+
|
|
45
|
+
An external-contribution pass means at least 10 starter tasks are publicly spotlighted with labels, acceptance criteria, and explicit maintainer response windows.
|
|
46
|
+
|
|
47
|
+
## Launch checklist
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
python -m sdetkit external-contribution --format json --strict
|
|
51
|
+
python -m sdetkit external-contribution --emit-pack-dir docs/artifacts/external-contribution-pack --format json --strict
|
|
52
|
+
python -m sdetkit external-contribution --execute --evidence-dir docs/artifacts/external-contribution-pack/evidence --format json --strict
|
|
53
|
+
python scripts/check_external_contribution_contract.py
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## First-response SLA
|
|
57
|
+
|
|
58
|
+
- Respond to new external contribution comments within 24 hours.
|
|
59
|
+
- Label every starter-task thread as `accepted`, `needs-info`, or `not-now`.
|
|
60
|
+
- Publish weekly conversion summary (opened -> active -> merged).
|
|
61
|
+
|
|
62
|
+
## Activation scoring model
|
|
63
|
+
|
|
64
|
+
External-contribution computes a weighted readiness score (0-100):
|
|
65
|
+
|
|
66
|
+
- Docs contract + command lane completeness: 45 points.
|
|
67
|
+
- Discoverability links in README/docs index: 25 points.
|
|
68
|
+
- Top-10 roadmap alignment + starter-task language: 20 points.
|
|
69
|
+
- Evidence-lane readiness for strict validation: 10 points.
|
|
70
|
+
|
|
71
|
+
## Execution evidence mode
|
|
72
|
+
|
|
73
|
+
`--execute` runs deterministic external-contribution checks and writes logs to `--evidence-dir` for release review.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
_SIGNALS = [
|
|
77
|
+
{
|
|
78
|
+
"check_id": "docs_page_exists",
|
|
79
|
+
"category": "contract",
|
|
80
|
+
"weight": 15,
|
|
81
|
+
"evaluator": "page_exists",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"check_id": "required_sections_present",
|
|
85
|
+
"category": "contract",
|
|
86
|
+
"weight": 20,
|
|
87
|
+
"evaluator": "required_sections",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"check_id": "required_commands_present",
|
|
91
|
+
"category": "contract",
|
|
92
|
+
"weight": 10,
|
|
93
|
+
"evaluator": "required_commands",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"check_id": "readme_external_contribution_link",
|
|
97
|
+
"category": "discoverability",
|
|
98
|
+
"weight": 10,
|
|
99
|
+
"marker": "External contribution",
|
|
100
|
+
"source": "readme",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"check_id": "readme_external_contribution_command",
|
|
104
|
+
"category": "discoverability",
|
|
105
|
+
"weight": 8,
|
|
106
|
+
"marker": "external-contribution",
|
|
107
|
+
"source": "readme",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"check_id": "docs_index_external_contribution_link",
|
|
111
|
+
"category": "discoverability",
|
|
112
|
+
"weight": 7,
|
|
113
|
+
"marker": "docs/external-contribution.md",
|
|
114
|
+
"source": "docs_index",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"check_id": "top10_external_contribution_alignment",
|
|
118
|
+
"category": "strategy",
|
|
119
|
+
"weight": 10,
|
|
120
|
+
"marker": "External contribution",
|
|
121
|
+
"source": "top10",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"check_id": "docs_mentions_starter_tasks",
|
|
125
|
+
"category": "strategy",
|
|
126
|
+
"weight": 10,
|
|
127
|
+
"marker": "starter task",
|
|
128
|
+
"source": "page",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"check_id": "docs_mentions_response_sla",
|
|
132
|
+
"category": "evidence",
|
|
133
|
+
"weight": 10,
|
|
134
|
+
"marker": "24 hours",
|
|
135
|
+
"source": "page",
|
|
136
|
+
},
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
_CRITICAL_FAILURE_KEYS = {
|
|
140
|
+
"docs_page_exists",
|
|
141
|
+
"required_sections_present",
|
|
142
|
+
"required_commands_present",
|
|
143
|
+
"top10_external_contribution_alignment",
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _read(path: Path) -> str:
|
|
148
|
+
return path.read_text(encoding="utf-8") if path.exists() else ""
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _evaluate_signals(
|
|
152
|
+
root: Path,
|
|
153
|
+
*,
|
|
154
|
+
page_text: str,
|
|
155
|
+
readme_text: str,
|
|
156
|
+
docs_index_text: str,
|
|
157
|
+
top10_text: str,
|
|
158
|
+
) -> list[dict[str, Any]]:
|
|
159
|
+
rows: list[dict[str, Any]] = []
|
|
160
|
+
page_path = root / _PAGE_PATH
|
|
161
|
+
|
|
162
|
+
for signal in _SIGNALS:
|
|
163
|
+
evaluator = signal.get("evaluator")
|
|
164
|
+
|
|
165
|
+
if evaluator == "page_exists":
|
|
166
|
+
passed = page_path.exists()
|
|
167
|
+
evidence: Any = str(page_path)
|
|
168
|
+
elif evaluator == "required_sections":
|
|
169
|
+
missing = [
|
|
170
|
+
section
|
|
171
|
+
for section in [_SECTION_HEADER, *_REQUIRED_SECTIONS]
|
|
172
|
+
if section not in page_text
|
|
173
|
+
]
|
|
174
|
+
passed = not missing
|
|
175
|
+
evidence = {"missing_sections": missing}
|
|
176
|
+
elif evaluator == "required_commands":
|
|
177
|
+
missing = [command for command in _REQUIRED_COMMANDS if command not in page_text]
|
|
178
|
+
passed = not missing
|
|
179
|
+
evidence = {"missing_commands": missing}
|
|
180
|
+
else:
|
|
181
|
+
marker = str(signal.get("marker", ""))
|
|
182
|
+
source = str(signal.get("source", "page"))
|
|
183
|
+
corpus = page_text
|
|
184
|
+
if source == "readme":
|
|
185
|
+
corpus = readme_text
|
|
186
|
+
elif source == "docs_index":
|
|
187
|
+
corpus = docs_index_text
|
|
188
|
+
elif source == "top10":
|
|
189
|
+
corpus = top10_text
|
|
190
|
+
passed = bool(marker) and marker in corpus
|
|
191
|
+
evidence = marker
|
|
192
|
+
|
|
193
|
+
rows.append(
|
|
194
|
+
{
|
|
195
|
+
"check_id": str(signal["check_id"]),
|
|
196
|
+
"category": str(signal["category"]),
|
|
197
|
+
"weight": int(str(signal["weight"])),
|
|
198
|
+
"passed": bool(passed),
|
|
199
|
+
"evidence": evidence,
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
return rows
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def build_external_contribution_summary(
|
|
207
|
+
root: Path,
|
|
208
|
+
*,
|
|
209
|
+
readme_path: str = "README.md",
|
|
210
|
+
docs_index_path: str = "docs/index.md",
|
|
211
|
+
docs_page_path: str = _PAGE_PATH,
|
|
212
|
+
top10_path: str = _TOP10_PATH,
|
|
213
|
+
) -> dict[str, Any]:
|
|
214
|
+
checks = _evaluate_signals(
|
|
215
|
+
root,
|
|
216
|
+
page_text=_read(root / docs_page_path),
|
|
217
|
+
readme_text=_read(root / readme_path),
|
|
218
|
+
docs_index_text=_read(root / docs_index_path),
|
|
219
|
+
top10_text=_read(root / top10_path),
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
total_weight = sum(int(item["weight"]) for item in checks)
|
|
223
|
+
earned_weight = sum(int(item["weight"]) for item in checks if item["passed"])
|
|
224
|
+
score = round((earned_weight / total_weight) * 100, 2) if total_weight else 0.0
|
|
225
|
+
|
|
226
|
+
failed = [item for item in checks if not item["passed"]]
|
|
227
|
+
critical_failures = [
|
|
228
|
+
item["check_id"] for item in failed if item["check_id"] in _CRITICAL_FAILURE_KEYS
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
recommendations: list[str] = []
|
|
232
|
+
if any(item["category"] == "contract" for item in failed):
|
|
233
|
+
recommendations.append(
|
|
234
|
+
"Restore external-contribution docs contract sections and required command lane before launch."
|
|
235
|
+
)
|
|
236
|
+
if any(item["category"] == "discoverability" for item in failed):
|
|
237
|
+
recommendations.append(
|
|
238
|
+
"Add external-contribution links and commands to README/docs index for external contributor visibility."
|
|
239
|
+
)
|
|
240
|
+
if any(item["category"] in {"strategy", "evidence"} for item in failed):
|
|
241
|
+
recommendations.append(
|
|
242
|
+
"Align external-contribution outputs with starter-task spotlighting and 24-hour first-response SLA."
|
|
243
|
+
)
|
|
244
|
+
if not recommendations:
|
|
245
|
+
recommendations.append(
|
|
246
|
+
"External-contribution lane is healthy; keep weekly conversion summaries flowing."
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
"name": "external-contribution",
|
|
251
|
+
"inputs": {
|
|
252
|
+
"readme": readme_path,
|
|
253
|
+
"docs_index": docs_index_path,
|
|
254
|
+
"docs_page": docs_page_path,
|
|
255
|
+
"top10": top10_path,
|
|
256
|
+
},
|
|
257
|
+
"checks": checks,
|
|
258
|
+
"summary": {
|
|
259
|
+
"activation_score": score,
|
|
260
|
+
"total_checks": len(checks),
|
|
261
|
+
"failed_checks": [item["check_id"] for item in failed],
|
|
262
|
+
"critical_failures": critical_failures,
|
|
263
|
+
"recommendations": recommendations,
|
|
264
|
+
},
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _render_text(payload: dict[str, Any]) -> str:
|
|
269
|
+
lines = [
|
|
270
|
+
"External contribution summary",
|
|
271
|
+
f"score={payload['summary']['activation_score']}",
|
|
272
|
+
f"failed={','.join(payload['summary']['failed_checks']) or 'none'}",
|
|
273
|
+
f"critical={','.join(payload['summary']['critical_failures']) or 'none'}",
|
|
274
|
+
"",
|
|
275
|
+
"Checks:",
|
|
276
|
+
]
|
|
277
|
+
for item in payload["checks"]:
|
|
278
|
+
lines.append(
|
|
279
|
+
f"- [{'x' if item['passed'] else ' '}] {item['check_id']} ({item['category']}, w={item['weight']})"
|
|
280
|
+
)
|
|
281
|
+
return "\n".join(lines)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def emit_pack(root: Path, out_dir: Path, payload: dict[str, Any]) -> list[str]:
|
|
285
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
286
|
+
summary = out_dir / "external-contribution-summary.json"
|
|
287
|
+
scorecard = out_dir / "external-contribution-scorecard.md"
|
|
288
|
+
spotlight = out_dir / "external-contribution-starter-task-spotlight.md"
|
|
289
|
+
triage = out_dir / "external-contribution-triage-board.md"
|
|
290
|
+
validation = out_dir / "external-contribution-validation-commands.md"
|
|
291
|
+
|
|
292
|
+
summary.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
293
|
+
scorecard.write_text(_render_text(payload) + "\n", encoding="utf-8")
|
|
294
|
+
spotlight.write_text(
|
|
295
|
+
"\n".join(
|
|
296
|
+
[
|
|
297
|
+
"# External contribution starter-task spotlight",
|
|
298
|
+
"",
|
|
299
|
+
"## Public call-to-action",
|
|
300
|
+
"Help wanted: pick one starter task and submit your first contribution this week.",
|
|
301
|
+
"",
|
|
302
|
+
"## Spotlight checklist",
|
|
303
|
+
"- [ ] Link top 10 starter tasks with acceptance criteria.",
|
|
304
|
+
"- [ ] Assign maintainer owner for each task.",
|
|
305
|
+
"- [ ] Include setup prerequisites and expected completion time.",
|
|
306
|
+
"",
|
|
307
|
+
"## Promotion channels",
|
|
308
|
+
"1. GitHub discussion",
|
|
309
|
+
"2. X / LinkedIn post",
|
|
310
|
+
"3. Community Discord/Slack announcement",
|
|
311
|
+
]
|
|
312
|
+
)
|
|
313
|
+
+ "\n",
|
|
314
|
+
encoding="utf-8",
|
|
315
|
+
)
|
|
316
|
+
triage.write_text(
|
|
317
|
+
"\n".join(
|
|
318
|
+
[
|
|
319
|
+
"# External contribution triage board",
|
|
320
|
+
"",
|
|
321
|
+
"| Starter task | Interest signal | Owner | Status | Response SLA |",
|
|
322
|
+
"| --- | --- | --- | --- | --- |",
|
|
323
|
+
"| Example: docs quickstart polish | 0 comments | @owner | needs-info | <=24h |",
|
|
324
|
+
"",
|
|
325
|
+
"First-response SLA: acknowledge every external contributor within 24 hours.",
|
|
326
|
+
]
|
|
327
|
+
)
|
|
328
|
+
+ "\n",
|
|
329
|
+
encoding="utf-8",
|
|
330
|
+
)
|
|
331
|
+
validation.write_text(
|
|
332
|
+
"\n".join(
|
|
333
|
+
[
|
|
334
|
+
"# External contribution validation commands",
|
|
335
|
+
"",
|
|
336
|
+
"```bash",
|
|
337
|
+
*_REQUIRED_COMMANDS,
|
|
338
|
+
"```",
|
|
339
|
+
]
|
|
340
|
+
)
|
|
341
|
+
+ "\n",
|
|
342
|
+
encoding="utf-8",
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
return [
|
|
346
|
+
str(path.relative_to(root)) for path in [summary, scorecard, spotlight, triage, validation]
|
|
347
|
+
]
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def execute_commands(root: Path, evidence_dir: Path, timeout_sec: int) -> dict[str, Any]:
|
|
351
|
+
evidence_dir.mkdir(parents=True, exist_ok=True)
|
|
352
|
+
results: list[dict[str, Any]] = []
|
|
353
|
+
for command in _EXECUTION_COMMANDS:
|
|
354
|
+
argv = shlex.split(command)
|
|
355
|
+
if argv and argv[0] == "python":
|
|
356
|
+
argv[0] = sys.executable
|
|
357
|
+
proc = subprocess.run(argv, cwd=root, text=True, capture_output=True, timeout=timeout_sec)
|
|
358
|
+
results.append(
|
|
359
|
+
{
|
|
360
|
+
"command": command,
|
|
361
|
+
"returncode": proc.returncode,
|
|
362
|
+
"stdout": proc.stdout,
|
|
363
|
+
"stderr": proc.stderr,
|
|
364
|
+
}
|
|
365
|
+
)
|
|
366
|
+
payload = {
|
|
367
|
+
"name": "external-contribution-execution",
|
|
368
|
+
"total_commands": len(_EXECUTION_COMMANDS),
|
|
369
|
+
"results": results,
|
|
370
|
+
}
|
|
371
|
+
(evidence_dir / "external-contribution-execution-summary.json").write_text(
|
|
372
|
+
json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8"
|
|
373
|
+
)
|
|
374
|
+
return payload
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
378
|
+
parser = argparse.ArgumentParser(
|
|
379
|
+
prog="sdetkit external-contribution",
|
|
380
|
+
description="External-contribution rollout lane.",
|
|
381
|
+
)
|
|
382
|
+
parser.add_argument("--root", default=".", help="Repository root path.")
|
|
383
|
+
parser.add_argument(
|
|
384
|
+
"--readme", default="README.md", help="README path for discoverability checks."
|
|
385
|
+
)
|
|
386
|
+
parser.add_argument(
|
|
387
|
+
"--docs-index", default="docs/index.md", help="Docs index path for discoverability checks."
|
|
388
|
+
)
|
|
389
|
+
parser.add_argument("--top10", default=_TOP10_PATH, help="Top-10 roadmap strategy path.")
|
|
390
|
+
parser.add_argument(
|
|
391
|
+
"--write-defaults", action="store_true", help="Create default external-contribution page."
|
|
392
|
+
)
|
|
393
|
+
parser.add_argument(
|
|
394
|
+
"--emit-pack-dir",
|
|
395
|
+
default="",
|
|
396
|
+
help="Optional output directory for generated external-contribution files.",
|
|
397
|
+
)
|
|
398
|
+
parser.add_argument(
|
|
399
|
+
"--execute",
|
|
400
|
+
action="store_true",
|
|
401
|
+
help="Run external-contribution command chain and emit evidence logs.",
|
|
402
|
+
)
|
|
403
|
+
parser.add_argument(
|
|
404
|
+
"--evidence-dir",
|
|
405
|
+
default="docs/artifacts/external-contribution-pack/evidence",
|
|
406
|
+
help="Output directory for execution evidence logs.",
|
|
407
|
+
)
|
|
408
|
+
parser.add_argument(
|
|
409
|
+
"--timeout-sec", type=int, default=120, help="Per-command timeout used by --execute."
|
|
410
|
+
)
|
|
411
|
+
parser.add_argument(
|
|
412
|
+
"--min-score", type=float, default=90.0, help="Minimum score for strict pass."
|
|
413
|
+
)
|
|
414
|
+
parser.add_argument(
|
|
415
|
+
"--strict", action="store_true", help="Fail when score or critical checks are not ready."
|
|
416
|
+
)
|
|
417
|
+
parser.add_argument("--format", choices=["text", "json"], default="text", help="Output format.")
|
|
418
|
+
return parser
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def main(argv: list[str] | None = None) -> int:
|
|
422
|
+
ns = build_parser().parse_args(argv)
|
|
423
|
+
root = Path(ns.root).resolve()
|
|
424
|
+
page = root / _PAGE_PATH
|
|
425
|
+
|
|
426
|
+
if ns.write_defaults:
|
|
427
|
+
page.parent.mkdir(parents=True, exist_ok=True)
|
|
428
|
+
page.write_text(_DEFAULT_PAGE_TEMPLATE, encoding="utf-8")
|
|
429
|
+
|
|
430
|
+
payload = build_external_contribution_summary(
|
|
431
|
+
root,
|
|
432
|
+
readme_path=ns.readme,
|
|
433
|
+
docs_index_path=ns.docs_index,
|
|
434
|
+
top10_path=ns.top10,
|
|
435
|
+
)
|
|
436
|
+
page_text = _read(page)
|
|
437
|
+
missing_sections = [
|
|
438
|
+
section for section in [_SECTION_HEADER, *_REQUIRED_SECTIONS] if section not in page_text
|
|
439
|
+
]
|
|
440
|
+
missing_commands = [command for command in _REQUIRED_COMMANDS if command not in page_text]
|
|
441
|
+
payload["strict_failures"] = [*missing_sections, *missing_commands]
|
|
442
|
+
|
|
443
|
+
if ns.emit_pack_dir:
|
|
444
|
+
payload["emitted_pack_files"] = emit_pack(root, root / ns.emit_pack_dir, payload)
|
|
445
|
+
if ns.execute:
|
|
446
|
+
payload["execution"] = execute_commands(root, root / ns.evidence_dir, ns.timeout_sec)
|
|
447
|
+
|
|
448
|
+
strict_failed = (
|
|
449
|
+
bool(payload["strict_failures"])
|
|
450
|
+
or payload["summary"]["activation_score"] < ns.min_score
|
|
451
|
+
or bool(payload["summary"]["critical_failures"])
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
if ns.format == "json":
|
|
455
|
+
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
456
|
+
else:
|
|
457
|
+
print(_render_text(payload))
|
|
458
|
+
|
|
459
|
+
return 1 if ns.strict and strict_failed else 0
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
if __name__ == "__main__":
|
|
463
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .public_surface_contract import PUBLIC_SURFACE_CONTRACT
|
|
8
|
+
|
|
9
|
+
_REQUIRED_FIELDS = {
|
|
10
|
+
"command",
|
|
11
|
+
"tier",
|
|
12
|
+
"status",
|
|
13
|
+
"problem_solved",
|
|
14
|
+
"example",
|
|
15
|
+
"expected_output",
|
|
16
|
+
"test_file",
|
|
17
|
+
"docs_page",
|
|
18
|
+
}
|
|
19
|
+
_ALLOWED_TIERS = {"A", "B", "C"}
|
|
20
|
+
_ALLOWED_STATUSES = {"stable", "advanced", "experimental"}
|
|
21
|
+
_DOC_TABLE_START = "<!-- feature-registry:table:start -->"
|
|
22
|
+
_DOC_TABLE_END = "<!-- feature-registry:table:end -->"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class FeatureEntry:
|
|
27
|
+
command: str
|
|
28
|
+
tier: str
|
|
29
|
+
status: str
|
|
30
|
+
problem_solved: str
|
|
31
|
+
example: str
|
|
32
|
+
expected_output: str
|
|
33
|
+
test_file: str
|
|
34
|
+
docs_page: str
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _registry_path() -> Path:
|
|
38
|
+
return Path(__file__).resolve().parent / "data" / "feature_registry.json"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _known_top_level_commands() -> set[str]:
|
|
42
|
+
commands: set[str] = set()
|
|
43
|
+
for family in PUBLIC_SURFACE_CONTRACT:
|
|
44
|
+
commands.update(family.top_level_commands)
|
|
45
|
+
return commands
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def load_feature_registry() -> list[FeatureEntry]:
|
|
49
|
+
payload = json.loads(_registry_path().read_text(encoding="utf-8"))
|
|
50
|
+
if not isinstance(payload, list):
|
|
51
|
+
raise ValueError("feature registry must be a list")
|
|
52
|
+
|
|
53
|
+
rows: list[FeatureEntry] = []
|
|
54
|
+
seen: set[str] = set()
|
|
55
|
+
for i, item in enumerate(payload, start=1):
|
|
56
|
+
if not isinstance(item, dict):
|
|
57
|
+
raise ValueError(f"feature registry item #{i} must be an object")
|
|
58
|
+
missing = sorted(_REQUIRED_FIELDS - set(item))
|
|
59
|
+
if missing:
|
|
60
|
+
raise ValueError(f"feature registry item #{i} missing fields: {', '.join(missing)}")
|
|
61
|
+
|
|
62
|
+
entry = FeatureEntry(
|
|
63
|
+
command=str(item["command"]).strip(),
|
|
64
|
+
tier=str(item["tier"]).strip(),
|
|
65
|
+
status=str(item["status"]).strip(),
|
|
66
|
+
problem_solved=str(item["problem_solved"]).strip(),
|
|
67
|
+
example=str(item["example"]).strip(),
|
|
68
|
+
expected_output=str(item["expected_output"]).strip(),
|
|
69
|
+
test_file=str(item["test_file"]).strip(),
|
|
70
|
+
docs_page=str(item["docs_page"]).strip(),
|
|
71
|
+
)
|
|
72
|
+
if not entry.command:
|
|
73
|
+
raise ValueError(f"feature registry item #{i} has empty command")
|
|
74
|
+
if entry.command in seen:
|
|
75
|
+
raise ValueError(f"feature registry duplicate command: {entry.command}")
|
|
76
|
+
seen.add(entry.command)
|
|
77
|
+
rows.append(entry)
|
|
78
|
+
|
|
79
|
+
return rows
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _table_line(item: FeatureEntry) -> str:
|
|
83
|
+
return (
|
|
84
|
+
f"| `{item.command}` | {item.tier} | {item.status} | {item.problem_solved} | "
|
|
85
|
+
f"`{item.example}` | [{item.test_file}]({item.test_file}) | [{item.docs_page}]({item.docs_page}) |"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def render_feature_registry_table(rows: list[FeatureEntry]) -> str:
|
|
90
|
+
lines = [
|
|
91
|
+
"| Command | Tier | Status | Problem solved | Example | Test | Docs |",
|
|
92
|
+
"| --- | --- | --- | --- | --- | --- | --- |",
|
|
93
|
+
]
|
|
94
|
+
lines.extend(_table_line(item) for item in sorted(rows, key=lambda x: (x.tier, x.command)))
|
|
95
|
+
return "\n".join(lines)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def render_feature_registry_docs_block(rows: list[FeatureEntry]) -> str:
|
|
99
|
+
lines = [
|
|
100
|
+
"| Command | Tier | Status | Problem solved | Example | Test | Docs |",
|
|
101
|
+
"| --- | --- | --- | --- | --- | --- | --- |",
|
|
102
|
+
]
|
|
103
|
+
for item in sorted(rows, key=lambda x: (x.tier, x.command)):
|
|
104
|
+
docs_link = item.docs_page.removeprefix("docs/")
|
|
105
|
+
lines.append(
|
|
106
|
+
f"| `{item.command}` | {item.tier} | {item.status} | {item.problem_solved} | "
|
|
107
|
+
f"`{item.example}` | `{item.test_file}` | [{item.docs_page}]({docs_link}) |"
|
|
108
|
+
)
|
|
109
|
+
table = "\n".join(lines)
|
|
110
|
+
return f"{_DOC_TABLE_START}\n{table}\n{_DOC_TABLE_END}"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def summarize_feature_registry(rows: list[FeatureEntry]) -> dict[str, object]:
|
|
114
|
+
by_tier: dict[str, int] = {}
|
|
115
|
+
by_status: dict[str, int] = {}
|
|
116
|
+
commands: list[str] = []
|
|
117
|
+
for item in sorted(rows, key=lambda x: x.command):
|
|
118
|
+
by_tier[item.tier] = by_tier.get(item.tier, 0) + 1
|
|
119
|
+
by_status[item.status] = by_status.get(item.status, 0) + 1
|
|
120
|
+
commands.append(item.command)
|
|
121
|
+
return {
|
|
122
|
+
"total": len(rows),
|
|
123
|
+
"by_tier": by_tier,
|
|
124
|
+
"by_status": by_status,
|
|
125
|
+
"commands": commands,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def ensure_feature_registry_docs_synced(repo_root: Path) -> list[str]:
|
|
130
|
+
docs_path = repo_root / "docs" / "feature-registry.md"
|
|
131
|
+
if not docs_path.exists():
|
|
132
|
+
return ["feature-registry docs page is missing"]
|
|
133
|
+
|
|
134
|
+
text = docs_path.read_text(encoding="utf-8")
|
|
135
|
+
start = text.find(_DOC_TABLE_START)
|
|
136
|
+
end = text.find(_DOC_TABLE_END)
|
|
137
|
+
if start == -1 or end == -1 or end < start:
|
|
138
|
+
return ["feature-registry docs markers are missing or malformed"]
|
|
139
|
+
|
|
140
|
+
end += len(_DOC_TABLE_END)
|
|
141
|
+
current = text[start:end].strip()
|
|
142
|
+
expected = render_feature_registry_docs_block(load_feature_registry()).strip()
|
|
143
|
+
if current != expected:
|
|
144
|
+
return ["feature-registry docs table is stale; run scripts/sync_feature_registry_docs.py"]
|
|
145
|
+
return []
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def validate_feature_registry_contract(repo_root: Path) -> list[str]:
|
|
149
|
+
errors: list[str] = []
|
|
150
|
+
known_commands = _known_top_level_commands()
|
|
151
|
+
|
|
152
|
+
for item in load_feature_registry():
|
|
153
|
+
if item.tier not in _ALLOWED_TIERS:
|
|
154
|
+
errors.append(f"{item.command}: tier must be one of {_ALLOWED_TIERS}")
|
|
155
|
+
if item.status not in _ALLOWED_STATUSES:
|
|
156
|
+
errors.append(f"{item.command}: status must be one of {_ALLOWED_STATUSES}")
|
|
157
|
+
if not item.example.startswith("python -m sdetkit"):
|
|
158
|
+
errors.append(f"{item.command}: example must start with 'python -m sdetkit'")
|
|
159
|
+
if item.command not in known_commands:
|
|
160
|
+
errors.append(f"{item.command}: command is not in the public surface contract")
|
|
161
|
+
if item.tier == "A" and item.status != "stable":
|
|
162
|
+
errors.append(f"{item.command}: tier A commands must use stable status")
|
|
163
|
+
|
|
164
|
+
test_path = repo_root / item.test_file
|
|
165
|
+
if not test_path.exists():
|
|
166
|
+
errors.append(f"{item.command}: missing test file: {item.test_file}")
|
|
167
|
+
|
|
168
|
+
docs_path = repo_root / item.docs_page
|
|
169
|
+
if not docs_path.exists():
|
|
170
|
+
errors.append(f"{item.command}: missing docs page: {item.docs_page}")
|
|
171
|
+
|
|
172
|
+
errors.extend(ensure_feature_registry_docs_synced(repo_root))
|
|
173
|
+
return errors
|