bmad-module-skill-forge 1.4.1 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +0 -8
- package/docs/_data/pinned.yaml +1 -1
- package/docs/_internal/STABILITY.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/examples.md +1 -1
- package/docs/workflows.md +5 -5
- package/package.json +2 -2
- package/src/README.md +1 -1
- package/src/knowledge/ccc-bridge.md +12 -12
- package/src/knowledge/qmd-registry.md +5 -5
- package/src/shared/health-check.md +5 -5
- package/src/shared/references/description-guard-protocol.md +100 -0
- package/src/shared/references/output-contract-schema.md +1 -1
- package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +6 -6
- package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +9 -3
- package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +149 -0
- package/src/shared/scripts/schemas/skill-brief.v1.json +22 -2
- package/src/shared/scripts/skf-build-change-manifest.py +420 -0
- package/src/shared/scripts/skf-check-workspace-drift.py +321 -0
- package/src/shared/scripts/skf-compare-file-hashes.py +357 -0
- package/src/shared/scripts/skf-description-guard.py +359 -0
- package/src/shared/scripts/skf-detect-language.py +2 -2
- package/src/shared/scripts/skf-detect-scripts-assets.py +613 -0
- package/src/shared/scripts/skf-detect-tools.py +147 -16
- package/src/shared/scripts/skf-detect-workspaces.py +1 -1
- package/src/shared/scripts/skf-disqualify-candidates.py +576 -0
- package/src/shared/scripts/skf-emit-brief-result-envelope.py +3 -3
- package/src/shared/scripts/skf-emit-result-envelope.py +93 -9
- package/src/shared/scripts/skf-enumerate-stack-skills.py +514 -0
- package/src/shared/scripts/skf-forge-tier-rw.py +11 -11
- package/src/shared/scripts/skf-hash-content.py +284 -0
- package/src/shared/scripts/skf-load-provenance.py +295 -0
- package/src/shared/scripts/skf-manifest-ops.py +22 -6
- package/src/shared/scripts/skf-merge-ccc-exclusions.py +34 -4
- package/src/shared/scripts/skf-pair-intersect.py +250 -0
- package/src/shared/scripts/skf-provenance-gap-dispatch.py +433 -0
- package/src/shared/scripts/skf-qmd-classify-collections.py +42 -8
- package/src/shared/scripts/skf-recommend-scope-type.py +2 -2
- package/src/shared/scripts/skf-render-quick-metadata.py +2 -2
- package/src/shared/scripts/skf-resolve-authoritative-files.py +504 -0
- package/src/shared/scripts/skf-scan-manifests.py +738 -0
- package/src/shared/scripts/skf-scan-skill-md-structure.py +375 -0
- package/src/shared/scripts/skf-update-active-symlink.py +315 -0
- package/src/shared/scripts/skf-validate-brief-inputs.py +5 -5
- package/src/shared/scripts/skf-validate-brief-schema.py +348 -0
- package/src/shared/scripts/skf-write-skill-brief.py +61 -5
- package/src/skf-analyze-source/SKILL.md +69 -16
- package/src/skf-analyze-source/assets/skill-brief-schema.md +1 -1
- package/src/skf-analyze-source/customize.toml +55 -0
- package/src/skf-analyze-source/{steps-c/step-01b-continue.md → references/continue.md} +12 -12
- package/src/skf-analyze-source/{steps-c/step-06-generate-briefs.md → references/generate-briefs.md} +29 -10
- package/src/skf-analyze-source/{steps-c/step-07-health-check.md → references/health-check.md} +5 -3
- package/src/skf-analyze-source/{steps-c/step-03-identify-units.md → references/identify-units.md} +48 -14
- package/src/skf-analyze-source/{steps-c/step-01-init.md → references/init.md} +32 -9
- package/src/skf-analyze-source/{steps-c/step-04-map-and-detect.md → references/map-and-detect.md} +52 -25
- package/src/skf-analyze-source/{steps-c/step-05-recommend.md → references/recommend.md} +15 -8
- package/src/skf-analyze-source/{steps-c/step-02-scan-project.md → references/scan-project.md} +16 -10
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +2 -0
- package/src/skf-analyze-source/templates/analysis-report-template.md +6 -6
- package/src/skf-audit-skill/SKILL.md +66 -15
- package/src/skf-audit-skill/assets/drift-report-template.md +6 -5
- package/src/skf-audit-skill/customize.toml +49 -0
- package/src/skf-audit-skill/{steps-c/step-07-health-check.md → references/health-check.md} +5 -3
- package/src/skf-audit-skill/{steps-c/step-01-init.md → references/init.md} +63 -19
- package/src/skf-audit-skill/{steps-c/step-02-re-index.md → references/re-index.md} +11 -11
- package/src/skf-audit-skill/{steps-c/step-06-report.md → references/report.md} +25 -7
- package/src/skf-audit-skill/{steps-c/step-04-semantic-diff.md → references/semantic-diff.md} +6 -6
- package/src/skf-audit-skill/{steps-c/step-05-severity-classify.md → references/severity-classify.md} +5 -5
- package/src/skf-audit-skill/references/severity-rules.md +2 -0
- package/src/skf-audit-skill/{steps-c/step-03-structural-diff.md → references/structural-diff.md} +35 -17
- package/src/skf-brief-skill/SKILL.md +49 -19
- package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
- package/src/skf-brief-skill/assets/skill-brief-schema.md +32 -2
- package/src/skf-brief-skill/customize.toml +44 -0
- package/src/skf-brief-skill/{steps-c/step-02-analyze-target.md → references/analyze-target.md} +27 -15
- package/src/skf-brief-skill/{steps-c/step-04-confirm-brief.md → references/confirm-brief.md} +13 -10
- package/src/skf-brief-skill/references/draft-checkpoint.md +4 -4
- package/src/skf-brief-skill/{steps-c/step-01-gather-intent.md → references/gather-intent.md} +35 -29
- package/src/skf-brief-skill/references/headless-args.md +9 -9
- package/src/skf-brief-skill/references/headless-source-authority-detection.md +1 -1
- package/src/skf-brief-skill/{steps-c/step-06-health-check.md → references/health-check.md} +4 -2
- package/src/skf-brief-skill/references/portfolio-similarity-check.md +2 -2
- package/src/skf-brief-skill/references/qmd-collection-registration.md +2 -2
- package/src/skf-brief-skill/{steps-c/step-03-scope-definition.md → references/scope-definition.md} +27 -20
- package/src/skf-brief-skill/references/version-resolution.md +2 -2
- package/src/skf-brief-skill/{steps-c/step-05-write-brief.md → references/write-brief.md} +29 -16
- package/src/skf-create-skill/SKILL.md +25 -19
- package/src/skf-create-skill/assets/compile-assembly-rules.md +3 -3
- package/src/skf-create-skill/assets/skill-sections.md +5 -5
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +11 -11
- package/src/skf-create-skill/customize.toml +35 -0
- package/src/skf-create-skill/references/authoritative-files-protocol.md +142 -0
- package/src/skf-create-skill/{steps-c/step-05-compile.md → references/compile.md} +16 -16
- package/src/skf-create-skill/{steps-c/step-03d-component-extraction.md → references/component-extraction.md} +16 -16
- package/src/skf-create-skill/{steps-c/step-02-ecosystem-check.md → references/ecosystem-check.md} +4 -4
- package/src/skf-create-skill/{steps-c/step-04-enrich.md → references/enrich.md} +3 -3
- package/src/skf-create-skill/{steps-c/step-03-extract.md → references/extract.md} +52 -120
- package/src/skf-create-skill/references/extraction-patterns.md +10 -8
- package/src/skf-create-skill/{steps-c/step-07-generate-artifacts.md → references/generate-artifacts.md} +11 -11
- package/src/skf-create-skill/{steps-c/step-09-health-check.md → references/health-check.md} +4 -2
- package/src/skf-create-skill/{steps-c/step-01-load-brief.md → references/load-brief.md} +40 -32
- package/src/skf-create-skill/{steps-c/step-08-report.md → references/report.md} +8 -8
- package/src/skf-create-skill/references/source-resolution-protocols.md +6 -6
- package/src/skf-create-skill/{steps-c/sub/step-02b-ccc-discover.md → references/sub/ccc-discover.md} +5 -5
- package/src/skf-create-skill/{steps-c/sub/step-03c-fetch-docs.md → references/sub/fetch-docs.md} +28 -12
- package/src/skf-create-skill/{steps-c/sub/step-03b-fetch-temporal.md → references/sub/fetch-temporal.md} +81 -50
- package/src/skf-create-skill/{steps-c/step-06-validate.md → references/validate.md} +48 -28
- package/src/skf-create-stack-skill/SKILL.md +76 -17
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +102 -0
- package/src/skf-create-stack-skill/assets/stack-skill-template.md +1 -1
- package/src/skf-create-stack-skill/customize.toml +46 -0
- package/src/skf-create-stack-skill/{steps-c/step-06-compile-stack.md → references/compile-stack.md} +6 -5
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +3 -1
- package/src/skf-create-stack-skill/{steps-c/step-05-detect-integrations.md → references/detect-integrations.md} +37 -11
- package/src/skf-create-stack-skill/{steps-c/step-02-detect-manifests.md → references/detect-manifests.md} +32 -28
- package/src/skf-create-stack-skill/{steps-c/step-07-generate-output.md → references/generate-output.md} +11 -85
- package/src/skf-create-stack-skill/{steps-c/step-10-health-check.md → references/health-check.md} +4 -2
- package/src/skf-create-stack-skill/{steps-c/step-01-init.md → references/init.md} +70 -4
- package/src/skf-create-stack-skill/references/integration-patterns.md +2 -0
- package/src/skf-create-stack-skill/references/manifest-patterns.md +2 -0
- package/src/skf-create-stack-skill/{steps-c/step-04-parallel-extract.md → references/parallel-extract.md} +44 -17
- package/src/skf-create-stack-skill/{steps-c/step-03-rank-and-confirm.md → references/rank-and-confirm.md} +5 -4
- package/src/skf-create-stack-skill/{steps-c/step-09-report.md → references/report.md} +3 -3
- package/src/skf-create-stack-skill/{steps-c/step-08-validate.md → references/validate.md} +6 -6
- package/src/skf-drop-skill/SKILL.md +84 -12
- package/src/skf-drop-skill/customize.toml +57 -0
- package/src/skf-drop-skill/{steps-c/step-02-execute.md → references/execute.md} +49 -34
- package/src/skf-drop-skill/{steps-c/step-04-health-check.md → references/health-check.md} +6 -4
- package/src/skf-drop-skill/{steps-c/step-03-report.md → references/report.md} +13 -5
- package/src/skf-drop-skill/{steps-c/step-01-select.md → references/select.md} +63 -15
- package/src/skf-export-skill/SKILL.md +85 -16
- package/src/skf-export-skill/assets/managed-section-format.md +3 -3
- package/src/skf-export-skill/assets/snippet-format.md +2 -2
- package/src/skf-export-skill/customize.toml +49 -0
- package/src/skf-export-skill/{steps-c/step-03-generate-snippet.md → references/generate-snippet.md} +11 -11
- package/src/skf-export-skill/{steps-c/step-07-health-check.md → references/health-check.md} +6 -4
- package/src/skf-export-skill/{steps-c/step-01-load-skill.md → references/load-skill.md} +11 -49
- package/src/skf-export-skill/references/manifest-rebuild.md +68 -0
- package/src/skf-export-skill/references/multi-skill-mode.md +38 -0
- package/src/skf-export-skill/references/orphan-context-detection.md +75 -0
- package/src/skf-export-skill/references/orphan-row-detection.md +102 -0
- package/src/skf-export-skill/{steps-c/step-02-package.md → references/package.md} +7 -7
- package/src/skf-export-skill/references/preflight-snippet-root-probe.md +79 -0
- package/src/skf-export-skill/{steps-c/step-06-summary.md → references/summary.md} +23 -11
- package/src/skf-export-skill/{steps-c/step-05-token-report.md → references/token-report.md} +5 -5
- package/src/skf-export-skill/{steps-c/step-04-update-context.md → references/update-context.md} +109 -113
- package/src/skf-quick-skill/SKILL.md +63 -123
- package/src/skf-quick-skill/customize.toml +44 -0
- package/src/skf-quick-skill/references/batch-mode.md +102 -0
- package/src/skf-quick-skill/{steps-c/step-04-compile.md → references/compile.md} +10 -10
- package/src/skf-quick-skill/{steps-c/step-02-ecosystem-check.md → references/ecosystem-check.md} +5 -5
- package/src/skf-quick-skill/{steps-c/step-06-finalize.md → references/finalize.md} +7 -7
- package/src/skf-quick-skill/{steps-c/step-07-health-check.md → references/health-check.md} +3 -3
- package/src/skf-quick-skill/{steps-c/step-03-quick-extract.md → references/quick-extract.md} +5 -5
- package/src/skf-quick-skill/references/registry-resolution.md +2 -0
- package/src/skf-quick-skill/{steps-c/step-01-resolve-target.md → references/resolve-target.md} +14 -10
- package/src/skf-quick-skill/{steps-c/step-05-write-and-validate.md → references/write-and-validate.md} +4 -4
- package/src/skf-refine-architecture/SKILL.md +86 -16
- package/src/skf-refine-architecture/customize.toml +49 -0
- package/src/skf-refine-architecture/{steps-c/step-05-compile.md → references/compile.md} +4 -4
- package/src/skf-refine-architecture/{steps-c/step-02-gap-analysis.md → references/gap-analysis.md} +5 -5
- package/src/skf-refine-architecture/{steps-c/step-07-health-check.md → references/health-check.md} +6 -4
- package/src/skf-refine-architecture/{steps-c/step-04-improvements.md → references/improvements.md} +5 -5
- package/src/skf-refine-architecture/references/init.md +144 -0
- package/src/skf-refine-architecture/{steps-c/step-03-issue-detection.md → references/issue-detection.md} +5 -5
- package/src/skf-refine-architecture/references/refinement-rules.md +2 -0
- package/src/skf-refine-architecture/{steps-c/step-06-report.md → references/report.md} +14 -5
- package/src/skf-rename-skill/SKILL.md +82 -12
- package/src/skf-rename-skill/customize.toml +52 -0
- package/src/skf-rename-skill/{steps-c/step-02-execute.md → references/execute.md} +94 -99
- package/src/skf-rename-skill/references/health-check.md +30 -0
- package/src/skf-rename-skill/references/rebuild-context.md +110 -0
- package/src/skf-rename-skill/{steps-c/step-03-report.md → references/report.md} +13 -5
- package/src/skf-rename-skill/{steps-c/step-01-select.md → references/select.md} +59 -20
- package/src/skf-setup/SKILL.md +35 -35
- package/src/skf-setup/customize.toml +33 -0
- package/src/skf-setup/{steps-c/step-03-auto-index.md → references/auto-index.md} +10 -18
- package/src/skf-setup/{steps-c/step-01b-ccc-index.md → references/ccc-index.md} +16 -31
- package/src/skf-setup/{steps-c/step-01-detect-and-tier.md → references/detect-and-tier.md} +23 -10
- package/src/skf-setup/{steps-c/step-05-health-check.md → references/health-check.md} +1 -1
- package/src/skf-setup/{steps-c/step-04-report.md → references/report.md} +21 -19
- package/src/skf-setup/references/tier-rules.md +1 -1
- package/src/skf-setup/{steps-c/step-02-write-config.md → references/write-config.md} +14 -13
- package/src/skf-test-skill/SKILL.md +77 -15
- package/src/skf-test-skill/customize.toml +54 -0
- package/src/skf-test-skill/{steps-c/step-04-coherence-check.md → references/coherence-check.md} +37 -73
- package/src/skf-test-skill/{steps-c/step-03-coverage-check.md → references/coverage-check.md} +16 -39
- package/src/skf-test-skill/{steps-c/step-02-detect-mode.md → references/detect-mode.md} +4 -26
- package/src/skf-test-skill/{steps-c/step-04b-external-validators.md → references/external-validators.md} +6 -28
- package/src/skf-test-skill/references/health-check.md +14 -0
- package/src/skf-test-skill/{steps-c/step-01-init.md → references/init.md} +21 -37
- package/src/skf-test-skill/references/migration-section-rules.md +4 -2
- package/src/skf-test-skill/{steps-c/step-06-report.md → references/report.md} +41 -51
- package/src/skf-test-skill/{steps-c/step-05-score.md → references/score.md} +26 -43
- package/src/skf-test-skill/references/scoring-rules.md +8 -6
- package/src/skf-test-skill/references/source-access-protocol.md +7 -5
- package/src/skf-test-skill/scripts/compute-score.py +88 -17
- package/src/skf-test-skill/templates/test-report-template.md +13 -13
- package/src/skf-update-skill/SKILL.md +23 -14
- package/src/skf-update-skill/customize.toml +44 -0
- package/src/skf-update-skill/{steps-c/step-02-detect-changes.md → references/detect-changes.md} +176 -68
- package/src/skf-update-skill/references/health-check.md +32 -0
- package/src/skf-update-skill/{steps-c/step-01-init.md → references/init.md} +56 -8
- package/src/skf-update-skill/references/manual-section-rules.md +4 -0
- package/src/skf-update-skill/references/merge-conflict-rules.md +4 -0
- package/src/skf-update-skill/{steps-c/step-04-merge.md → references/merge.md} +8 -8
- package/src/skf-update-skill/{steps-c/step-03-re-extract.md → references/re-extract.md} +53 -44
- package/src/skf-update-skill/references/remote-source-resolution.md +5 -1
- package/src/skf-update-skill/references/report.md +214 -0
- package/src/skf-update-skill/{steps-c/step-05-validate.md → references/validate.md} +5 -5
- package/src/skf-update-skill/{steps-c/step-06-write.md → references/write.md} +73 -51
- package/src/skf-verify-stack/SKILL.md +89 -17
- package/src/skf-verify-stack/assets/feasibility-report-template.md +4 -4
- package/src/skf-verify-stack/customize.toml +50 -0
- package/src/skf-verify-stack/references/coverage-patterns.md +2 -2
- package/src/skf-verify-stack/{steps-c/step-02-coverage.md → references/coverage.md} +13 -9
- package/src/skf-verify-stack/{steps-c/step-07-health-check.md → references/health-check.md} +7 -5
- package/src/skf-verify-stack/references/init.md +170 -0
- package/src/skf-verify-stack/references/integration-verification-rules.md +1 -1
- package/src/skf-verify-stack/{steps-c/step-03-integrations.md → references/integrations.md} +18 -14
- package/src/skf-verify-stack/{steps-c/step-06-report.md → references/report.md} +27 -42
- package/src/skf-verify-stack/{steps-c/step-04-requirements.md → references/requirements.md} +13 -9
- package/src/skf-verify-stack/{steps-c/step-05-synthesize.md → references/synthesize.md} +14 -10
- package/tools/validate-docs-drift.js +1 -1
- package/src/skf-refine-architecture/steps-c/step-01-init.md +0 -136
- package/src/skf-rename-skill/steps-c/step-04-health-check.md +0 -22
- package/src/skf-test-skill/steps-c/step-07-health-check.md +0 -25
- package/src/skf-update-skill/steps-c/step-07-report.md +0 -148
- package/src/skf-update-skill/steps-c/step-08-health-check.md +0 -22
- package/src/skf-verify-stack/steps-c/step-01-init.md +0 -178
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# /// script
|
|
2
2
|
# requires-python = ">=3.10"
|
|
3
|
-
# dependencies = []
|
|
3
|
+
# dependencies = ["pyyaml"]
|
|
4
4
|
# ///
|
|
5
5
|
"""SKF Detect Tools — Parallel tool detection + tier calculation for skf-setup.
|
|
6
6
|
|
|
7
|
-
Replaces the prose-driven tool-detection sequence in `src/skf-setup/
|
|
8
|
-
|
|
7
|
+
Replaces the prose-driven tool-detection sequence in `src/skf-setup/references/
|
|
8
|
+
detect-and-tier.md` §3-§8b with one Python invocation. Probes ast-grep,
|
|
9
9
|
gh, qmd, and ccc concurrently, applies the 4-rule tier decision table (see
|
|
10
10
|
`src/skf-setup/references/tier-rules.md`), evaluates --tier-override (with
|
|
11
11
|
sanity check) and --require-tier (with tool-prerequisite check independent of
|
|
12
12
|
the tier name), and emits one JSON document on stdout.
|
|
13
13
|
|
|
14
14
|
Schema documented in DETECT_OUTPUT_SCHEMA at the bottom of this docstring.
|
|
15
|
-
The output is consumed by step
|
|
16
|
-
and step
|
|
15
|
+
The output is consumed by step 1 prose, step 2 (forge-tier.yaml writer),
|
|
16
|
+
and step 4 (status report + envelope).
|
|
17
17
|
|
|
18
18
|
Tier rules (first match wins):
|
|
19
19
|
Deep = ast-grep + gh-cli + qmd (all healthy)
|
|
@@ -21,12 +21,12 @@ Tier rules (first match wins):
|
|
|
21
21
|
Forge = ast-grep
|
|
22
22
|
Quick = otherwise
|
|
23
23
|
|
|
24
|
-
CCC verification is two-step (matches step
|
|
24
|
+
CCC verification is two-step (matches step 1 §7):
|
|
25
25
|
Step A: `ccc --help` exits 0 AND output contains "CocoIndex Code" marker.
|
|
26
26
|
Rejects code2prompt-aliased-as-ccc and similar PATH shadowing.
|
|
27
27
|
Step B: `ccc doctor` succeeds (daemon healthy).
|
|
28
28
|
|
|
29
|
-
QMD verification is two-step (matches step
|
|
29
|
+
QMD verification is two-step (matches step 1 §5 post-PR-#248):
|
|
30
30
|
Step A: `qmd --version` exits 0 (binary identity, falls back to --help).
|
|
31
31
|
Step B: `qmd status` succeeds (daemon healthy).
|
|
32
32
|
qmd_status: "absent" | "daemon_stopped" | "healthy" — affects climb hint.
|
|
@@ -88,13 +88,26 @@ from __future__ import annotations
|
|
|
88
88
|
import argparse
|
|
89
89
|
import json
|
|
90
90
|
import os
|
|
91
|
+
import shutil
|
|
91
92
|
import subprocess
|
|
92
93
|
import sys
|
|
94
|
+
import tempfile
|
|
93
95
|
from concurrent.futures import ThreadPoolExecutor
|
|
94
96
|
|
|
95
97
|
|
|
96
98
|
VALID_TIERS = ("Quick", "Forge", "Forge+", "Deep")
|
|
97
99
|
PROBE_TIMEOUT_SEC = 8 # per-tool subprocess.run timeout
|
|
100
|
+
# Prefer the OS `timeout(1)` utility to bound each probe. A daemon-backed
|
|
101
|
+
# tool (e.g. `qmd status`) can block in an uninterruptible syscall against
|
|
102
|
+
# its daemon; `subprocess.run`'s own post-timeout `process.wait()` is
|
|
103
|
+
# unbounded and never returns in that case, hanging the whole detector.
|
|
104
|
+
# `timeout --kill-after` reaps the child (and its session) at the OS level,
|
|
105
|
+
# so Python's wait always returns. POSIX-only: Windows' `timeout.exe` is an
|
|
106
|
+
# unrelated builtin (waits for input; cannot run a command), and the
|
|
107
|
+
# uninterruptible-wait hang is itself POSIX-specific (Windows uses a
|
|
108
|
+
# forcible TerminateProcess), so on Windows we fall back to subprocess.run's
|
|
109
|
+
# own timeout. None when unavailable.
|
|
110
|
+
_TIMEOUT_BIN = shutil.which("timeout") if os.name == "posix" else None
|
|
98
111
|
CCC_IDENTITY_MARKER = "cocoindex code" # case-insensitive substring
|
|
99
112
|
|
|
100
113
|
|
|
@@ -115,16 +128,45 @@ def _run(cmd: list[str], timeout: int = PROBE_TIMEOUT_SEC) -> tuple[int, str, st
|
|
|
115
128
|
Treats every failure mode (FileNotFoundError, TimeoutExpired, OSError,
|
|
116
129
|
CalledProcessError) as a failed probe — returns rc=127 and an empty
|
|
117
130
|
stdout/stderr. Tool detection should never crash the workflow.
|
|
131
|
+
|
|
132
|
+
The child is wrapped in the OS `timeout(1)` utility (when available) so a
|
|
133
|
+
daemon-backed probe blocked in an uninterruptible syscall (e.g.
|
|
134
|
+
`qmd status` waiting on its daemon socket) is reaped at the OS level —
|
|
135
|
+
`subprocess.run`'s own post-timeout `process.wait()` is unbounded and
|
|
136
|
+
would otherwise never return, hanging the whole detector. Child
|
|
137
|
+
stdout/stderr are redirected to temp files (no pipe to drain), and
|
|
138
|
+
`start_new_session=True` isolates the child's process group; the
|
|
139
|
+
Python-level timeout is a secondary net set slightly above the OS one.
|
|
118
140
|
"""
|
|
141
|
+
if _TIMEOUT_BIN:
|
|
142
|
+
run_cmd = [_TIMEOUT_BIN, "--kill-after=2", str(timeout), *cmd]
|
|
143
|
+
py_timeout = timeout + 5
|
|
144
|
+
else:
|
|
145
|
+
run_cmd = cmd
|
|
146
|
+
py_timeout = timeout
|
|
119
147
|
try:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
148
|
+
with tempfile.TemporaryFile() as out_f, tempfile.TemporaryFile() as err_f:
|
|
149
|
+
result = subprocess.run(
|
|
150
|
+
run_cmd,
|
|
151
|
+
stdin=subprocess.DEVNULL,
|
|
152
|
+
stdout=out_f,
|
|
153
|
+
stderr=err_f,
|
|
154
|
+
timeout=py_timeout,
|
|
155
|
+
check=False,
|
|
156
|
+
start_new_session=True,
|
|
157
|
+
)
|
|
158
|
+
# Mocked unit tests patch `subprocess.run` to return a fake with
|
|
159
|
+
# `.stdout`/`.stderr` strings set; real runs redirect to the temp
|
|
160
|
+
# files (so `result.stdout` is None — read the files instead).
|
|
161
|
+
stdout = result.stdout
|
|
162
|
+
if stdout is None:
|
|
163
|
+
out_f.seek(0)
|
|
164
|
+
stdout = out_f.read().decode("utf-8", "replace")
|
|
165
|
+
stderr = result.stderr
|
|
166
|
+
if stderr is None:
|
|
167
|
+
err_f.seek(0)
|
|
168
|
+
stderr = err_f.read().decode("utf-8", "replace")
|
|
169
|
+
return result.returncode, stdout or "", stderr or ""
|
|
128
170
|
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
|
129
171
|
return 127, "", ""
|
|
130
172
|
|
|
@@ -251,6 +293,50 @@ def tier_prerequisites_met(tier: str, tools: dict) -> tuple[bool, list[str]]:
|
|
|
251
293
|
return (len(missing) == 0, missing)
|
|
252
294
|
|
|
253
295
|
|
|
296
|
+
def read_prior_state(prior_state_path) -> dict:
|
|
297
|
+
"""Read forge-tier.yaml from a previous run, return prior tier / tools / detection_date.
|
|
298
|
+
|
|
299
|
+
Returns a flat dict (always present, keys always set) so callers don't
|
|
300
|
+
branch on missing-file vs empty-file vs malformed-file — the script owns
|
|
301
|
+
that classification. On any read failure or absent file, returns the
|
|
302
|
+
"first-run" shape (everything null/empty). The CCC freshness fields are
|
|
303
|
+
surfaced separately so the caller doesn't reparse YAML in prose.
|
|
304
|
+
"""
|
|
305
|
+
empty = {
|
|
306
|
+
"previous_tier": None,
|
|
307
|
+
"previous_detection_date": None,
|
|
308
|
+
"previous_tools": {},
|
|
309
|
+
"previous_ccc_index_status": None,
|
|
310
|
+
"previous_ccc_indexed_path": None,
|
|
311
|
+
"previous_ccc_last_indexed": None,
|
|
312
|
+
"previous_ccc_staleness_threshold_hours": None,
|
|
313
|
+
}
|
|
314
|
+
if not prior_state_path:
|
|
315
|
+
return empty
|
|
316
|
+
try:
|
|
317
|
+
import yaml # local import — only needed when --prior-state-from is used
|
|
318
|
+
from pathlib import Path as _P
|
|
319
|
+
p = _P(prior_state_path)
|
|
320
|
+
if not p.exists():
|
|
321
|
+
return empty
|
|
322
|
+
data = yaml.safe_load(p.read_text(encoding="utf-8")) or {}
|
|
323
|
+
except Exception:
|
|
324
|
+
return empty
|
|
325
|
+
|
|
326
|
+
tools_map = data.get("tools") if isinstance(data.get("tools"), dict) else {}
|
|
327
|
+
ccc_index = data.get("ccc_index") if isinstance(data.get("ccc_index"), dict) else {}
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
"previous_tier": data.get("tier") if data.get("tier") in VALID_TIERS else None,
|
|
331
|
+
"previous_detection_date": data.get("tier_detected_at"),
|
|
332
|
+
"previous_tools": tools_map,
|
|
333
|
+
"previous_ccc_index_status": ccc_index.get("status"),
|
|
334
|
+
"previous_ccc_indexed_path": ccc_index.get("indexed_path"),
|
|
335
|
+
"previous_ccc_last_indexed": ccc_index.get("last_indexed"),
|
|
336
|
+
"previous_ccc_staleness_threshold_hours": ccc_index.get("staleness_threshold_hours"),
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
|
|
254
340
|
def detect(args: argparse.Namespace) -> dict:
|
|
255
341
|
tools: dict = {}
|
|
256
342
|
with ThreadPoolExecutor(max_workers=4) as ex:
|
|
@@ -302,6 +388,9 @@ def detect(args: argparse.Namespace) -> dict:
|
|
|
302
388
|
else:
|
|
303
389
|
require_satisfied = None
|
|
304
390
|
|
|
391
|
+
prior = read_prior_state(getattr(args, "prior_state_from", None))
|
|
392
|
+
deltas = compute_deltas(tools, prior, calculated)
|
|
393
|
+
|
|
305
394
|
return {
|
|
306
395
|
"tools": tools,
|
|
307
396
|
"tier": {
|
|
@@ -320,6 +409,38 @@ def detect(args: argparse.Namespace) -> dict:
|
|
|
320
409
|
"satisfied": require_satisfied,
|
|
321
410
|
"missing_tools": require_missing,
|
|
322
411
|
},
|
|
412
|
+
"prior": prior,
|
|
413
|
+
"deltas": deltas,
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def compute_deltas(current_tools: dict, prior: dict, calculated_tier: str) -> dict:
|
|
418
|
+
"""Compute re-run deltas (tools added/removed, tier_changed, ccc_index_is_fresh).
|
|
419
|
+
|
|
420
|
+
Removes ~80 tokens of LLM-side set arithmetic + string compare from the
|
|
421
|
+
interactive banner branch in references/report.md. First-run convention
|
|
422
|
+
(prior.previous_tier is null): tools_added = currently-available tools,
|
|
423
|
+
tools_removed = [], tier_changed = false.
|
|
424
|
+
"""
|
|
425
|
+
tool_keys = ("ast_grep", "gh_cli", "qmd", "ccc")
|
|
426
|
+
cur_avail = {k: bool((current_tools.get(k) or {}).get("available")) for k in tool_keys}
|
|
427
|
+
|
|
428
|
+
prev_tools_raw = prior.get("previous_tools") or {}
|
|
429
|
+
if prev_tools_raw:
|
|
430
|
+
prev_avail = {k: bool(prev_tools_raw.get(k, False)) for k in tool_keys}
|
|
431
|
+
tools_added = sorted(k for k in tool_keys if cur_avail[k] and not prev_avail[k])
|
|
432
|
+
tools_removed = sorted(k for k in tool_keys if prev_avail[k] and not cur_avail[k])
|
|
433
|
+
else:
|
|
434
|
+
tools_added = sorted(k for k in tool_keys if cur_avail[k])
|
|
435
|
+
tools_removed = []
|
|
436
|
+
|
|
437
|
+
prior_tier = prior.get("previous_tier")
|
|
438
|
+
tier_changed = bool(prior_tier and prior_tier != calculated_tier)
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
"tools_added": tools_added,
|
|
442
|
+
"tools_removed": tools_removed,
|
|
443
|
+
"tier_changed": tier_changed,
|
|
323
444
|
}
|
|
324
445
|
|
|
325
446
|
|
|
@@ -332,7 +453,7 @@ def main() -> None:
|
|
|
332
453
|
"--tier-override",
|
|
333
454
|
default=None,
|
|
334
455
|
help="Force a specific tier (must be one of Quick, Forge, Forge+, Deep — case-sensitive)."
|
|
335
|
-
" Invalid values are flagged in the output rather than rejected, so step
|
|
456
|
+
" Invalid values are flagged in the output rather than rejected, so step 4 can"
|
|
336
457
|
" surface the warning to the user.",
|
|
337
458
|
)
|
|
338
459
|
parser.add_argument(
|
|
@@ -349,6 +470,16 @@ def main() -> None:
|
|
|
349
470
|
help="Environment variable name to check for security-scan availability"
|
|
350
471
|
" (informational only — does NOT affect tier). Default: SNYK_TOKEN.",
|
|
351
472
|
)
|
|
473
|
+
parser.add_argument(
|
|
474
|
+
"--prior-state-from",
|
|
475
|
+
default=None,
|
|
476
|
+
help="Optional path to a previous-run forge-tier.yaml. When provided,"
|
|
477
|
+
" the script reads it and surfaces previous_tier, previous_tools,"
|
|
478
|
+
" previous_detection_date, and previous_ccc_* fields under the 'prior'"
|
|
479
|
+
" key — removing YAML-parse responsibility from the step prompt."
|
|
480
|
+
" Missing file or unreadable YAML returns the first-run shape (all"
|
|
481
|
+
" null/empty) without erroring.",
|
|
482
|
+
)
|
|
352
483
|
args = parser.parse_args()
|
|
353
484
|
|
|
354
485
|
payload = detect(args)
|
|
@@ -8,7 +8,7 @@ Takes a JSON payload on stdin describing a target repo's file tree plus the
|
|
|
8
8
|
contents of a small set of root manifests, and returns whether a workspace
|
|
9
9
|
layout is present, which manifest kind drives it, and the list of resolved
|
|
10
10
|
workspaces. The helper does NO file I/O — the caller (typically
|
|
11
|
-
skf-brief-skill step
|
|
11
|
+
skf-brief-skill step 2 §1b) fetches files via `gh api` / local filesystem
|
|
12
12
|
and pipes the relevant content in.
|
|
13
13
|
|
|
14
14
|
Detection runs in priority order; the first matching detector wins:
|