bmad-module-skill-forge 1.4.0 → 1.5.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/.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 +97 -8
- 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-merge-ccc-exclusions.py +45 -1
- 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} +44 -14
- package/src/skf-analyze-source/{steps-c/step-01-init.md → references/init.md} +27 -6
- 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} +12 -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/references/init.md +305 -0
- 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} +29 -16
- package/src/skf-brief-skill/SKILL.md +48 -18
- 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} +5 -5
- 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} +24 -24
- 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 +1 -1
- package/src/skf-brief-skill/{steps-c/step-03-scope-definition.md → references/scope-definition.md} +19 -16
- 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} +11 -10
- 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 +8 -8
- package/src/skf-create-skill/{steps-c/step-07-generate-artifacts.md → references/generate-artifacts.md} +17 -9
- 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} +46 -26
- 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} +32 -11
- package/src/skf-create-stack-skill/{steps-c/step-02-detect-manifests.md → references/detect-manifests.md} +28 -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} +40 -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} +18 -12
- 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} +21 -51
- 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 +74 -0
- package/src/skf-export-skill/{steps-c/step-06-summary.md → references/summary.md} +30 -6
- 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} +116 -91
- 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} +14 -22
- 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} +28 -71
- 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} +7 -7
- 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 +166 -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} +12 -12
- package/src/skf-verify-stack/{steps-c/step-06-report.md → references/report.md} +22 -41
- package/src/skf-verify-stack/{steps-c/step-04-requirements.md → references/requirements.md} +7 -7
- package/src/skf-verify-stack/{steps-c/step-05-synthesize.md → references/synthesize.md} +8 -8
- package/tools/validate-docs-drift.js +1 -1
- package/src/skf-audit-skill/steps-c/step-01-init.md +0 -221
- 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
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"""SKF Merge CCC Exclusions — Set-union merge of SKF exclusion patterns.
|
|
6
6
|
|
|
7
7
|
Replaces the prose-driven validation + merge logic in `src/skf-setup/
|
|
8
|
-
|
|
8
|
+
references/ccc-index.md` §2b with one Python invocation. Reads
|
|
9
9
|
`.cocoindex_code/settings.yml`, applies PR #248's config-value
|
|
10
10
|
validation rules to reject inputs that would silently produce a
|
|
11
11
|
malformed glob (the empty-value-becomes-`**/`-matches-everything bug
|
|
@@ -33,6 +33,11 @@ Validation rules for the 2 conditional values (PR #248):
|
|
|
33
33
|
→ produces `**//abs/path`, `**/~/x`, or `**/./rel` — malformed glob
|
|
34
34
|
- REJECT values containing glob meta-characters (`*`, `?`, `[`)
|
|
35
35
|
→ interpolation collides with the surrounding pattern syntax
|
|
36
|
+
- REJECT values containing unresolved `{project-root}`-style placeholders
|
|
37
|
+
(any `{` or `}` character) — produces `**/{project-root}/x`, which
|
|
38
|
+
cocoindex accepts as a literal-segment glob and silently never matches.
|
|
39
|
+
This is the bug from issue #293: the step file was passing template
|
|
40
|
+
strings straight through.
|
|
36
41
|
|
|
37
42
|
Rejected values are SKIPPED (the pattern is not added) and a warning
|
|
38
43
|
is appended to the output. The 4 always-include patterns are applied
|
|
@@ -96,6 +101,7 @@ ALWAYS_INCLUDE = (
|
|
|
96
101
|
"**/_skf-learn",
|
|
97
102
|
)
|
|
98
103
|
GLOB_META_CHARS = set("*?[")
|
|
104
|
+
PLACEHOLDER_CHARS = set("{}")
|
|
99
105
|
|
|
100
106
|
|
|
101
107
|
def _die(code: int, message: str) -> None:
|
|
@@ -133,6 +139,29 @@ def _atomic_write(target: Path, content: str) -> None:
|
|
|
133
139
|
# ─── Config-value validation (PR #248 rules) ────────────────────────────────
|
|
134
140
|
|
|
135
141
|
|
|
142
|
+
def resolve_repo_relative(raw_value: str, project_root: Path) -> str:
|
|
143
|
+
"""Normalize a config value like '{project-root}/skills' to 'skills'.
|
|
144
|
+
|
|
145
|
+
Accepts:
|
|
146
|
+
- `{project-root}/<segments>` → returns `<segments>` (verbatim, no globs)
|
|
147
|
+
- `<segments>` (already relative) → returns as-is, stripped
|
|
148
|
+
- Other input → returns the stripped value (validate_config_value
|
|
149
|
+
will catch absolute paths, placeholders, glob-meta).
|
|
150
|
+
|
|
151
|
+
Owning the substitution here removes ~150 tokens of "substitute and
|
|
152
|
+
basename-or-keep-tail" reasoning from every step prompt that invokes
|
|
153
|
+
this helper.
|
|
154
|
+
"""
|
|
155
|
+
if raw_value is None:
|
|
156
|
+
return ""
|
|
157
|
+
value = str(raw_value).strip()
|
|
158
|
+
if value.startswith("{project-root}/"):
|
|
159
|
+
value = value[len("{project-root}/"):]
|
|
160
|
+
elif value == "{project-root}":
|
|
161
|
+
value = ""
|
|
162
|
+
return value
|
|
163
|
+
|
|
164
|
+
|
|
136
165
|
def validate_config_value(key: str, raw_value: str) -> tuple[str | None, str | None]:
|
|
137
166
|
"""Validate a config value used to interpolate `**/{value}`.
|
|
138
167
|
|
|
@@ -163,6 +192,13 @@ def validate_config_value(key: str, raw_value: str) -> tuple[str | None, str | N
|
|
|
163
192
|
f"pattern syntax — fix the value in {{project-root}}/_bmad/skf/config.yaml"
|
|
164
193
|
)
|
|
165
194
|
|
|
195
|
+
if any(ch in PLACEHOLDER_CHARS for ch in value):
|
|
196
|
+
return None, (
|
|
197
|
+
f"{key} contains an unresolved template placeholder ({{ or }}); "
|
|
198
|
+
f"refused for ccc exclusion because the step is supposed to forward "
|
|
199
|
+
f"the raw config value and let this script resolve {{project-root}}"
|
|
200
|
+
)
|
|
201
|
+
|
|
166
202
|
return value, None
|
|
167
203
|
|
|
168
204
|
|
|
@@ -252,6 +288,12 @@ def render_settings_yml(data: dict, original_text: str | None) -> str:
|
|
|
252
288
|
def cmd_merge(project_root: Path, skills_output_folder: str, forge_data_folder: str) -> None:
|
|
253
289
|
target = project_root / ".cocoindex_code" / "settings.yml"
|
|
254
290
|
|
|
291
|
+
# Resolve `{project-root}/...` template strings here so callers can
|
|
292
|
+
# forward raw config values verbatim. This removes the "substitute and
|
|
293
|
+
# basename" reasoning from step prose — the script owns the contract.
|
|
294
|
+
skills_output_folder = resolve_repo_relative(skills_output_folder, project_root)
|
|
295
|
+
forge_data_folder = resolve_repo_relative(forge_data_folder, project_root)
|
|
296
|
+
|
|
255
297
|
patterns_to_add, warnings = assemble_patterns(skills_output_folder, forge_data_folder)
|
|
256
298
|
data, existed = read_settings_yml(target)
|
|
257
299
|
|
|
@@ -274,6 +316,7 @@ def cmd_merge(project_root: Path, skills_output_folder: str, forge_data_folder:
|
|
|
274
316
|
"patterns_added": 0,
|
|
275
317
|
"patterns_added_list": [],
|
|
276
318
|
"patterns_already_present": patterns_already_present,
|
|
319
|
+
"effective_patterns": sorted(set(patterns_to_add)),
|
|
277
320
|
"written": False,
|
|
278
321
|
"warnings": warnings,
|
|
279
322
|
})
|
|
@@ -289,6 +332,7 @@ def cmd_merge(project_root: Path, skills_output_folder: str, forge_data_folder:
|
|
|
289
332
|
"patterns_added": len(newly_added),
|
|
290
333
|
"patterns_added_list": newly_added,
|
|
291
334
|
"patterns_already_present": patterns_already_present,
|
|
335
|
+
"effective_patterns": sorted(set(patterns_to_add)),
|
|
292
336
|
"written": True,
|
|
293
337
|
"warnings": warnings,
|
|
294
338
|
})
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# requires-python = ">=3.10"
|
|
3
|
+
# dependencies = []
|
|
4
|
+
# ///
|
|
5
|
+
"""SKF Pair Intersect — compute file-list intersections between library pairs.
|
|
6
|
+
|
|
7
|
+
`detect-integrations.md` §1 ("File-list intersection fast path") asks the LLM
|
|
8
|
+
to prune the N*(N-1)/2 pair-grep matrix by intersecting the per-library file
|
|
9
|
+
lists that step 3 import-count extraction already produced. At N≈21 this
|
|
10
|
+
collapses ~210 prescribed pair greps to a handful of non-empty-intersection
|
|
11
|
+
pairs in typical codebases; the LLM only needs to run further grep passes
|
|
12
|
+
against those.
|
|
13
|
+
|
|
14
|
+
This script extracts that intersection from prose-orchestrated work to a
|
|
15
|
+
deterministic helper. The LLM still composes the user-facing warning when
|
|
16
|
+
the Top-K cap kicks in — the script just flags `truncated: true` so the
|
|
17
|
+
caller knows there's something to warn about.
|
|
18
|
+
|
|
19
|
+
Subcommand:
|
|
20
|
+
intersect --libraries <json-file-or-'-'> [--top-k N]
|
|
21
|
+
Read a libraries JSON (either from <json-file> or stdin if '-') of the
|
|
22
|
+
shape:
|
|
23
|
+
[{"name": "<lib>", "files": ["<path>", ...]}, ...]
|
|
24
|
+
where each library entry lists the project files that import it (the
|
|
25
|
+
per-library file enumeration from detect-integrations.md step 3).
|
|
26
|
+
|
|
27
|
+
Emit JSON:
|
|
28
|
+
{
|
|
29
|
+
"pairs": [
|
|
30
|
+
{"a": "<lib>", "b": "<lib>", "intersection_count": N,
|
|
31
|
+
"files": ["<rel-path-forward-slash>", ...]},
|
|
32
|
+
...
|
|
33
|
+
],
|
|
34
|
+
"truncated": false,
|
|
35
|
+
"total_pairs": <full non-empty-intersection count before cap>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
Pairs are computed for each unordered pair (a, b) with a < b
|
|
39
|
+
lexicographically; only non-empty intersections are emitted. The pairs
|
|
40
|
+
list is sorted by intersection_count DESC, then by (a, b) ASC for
|
|
41
|
+
stable, reproducible ordering.
|
|
42
|
+
|
|
43
|
+
Top-K cap: default --top-k 20 (matches detect-integrations.md §1's
|
|
44
|
+
"Top-K cap at 20 with explicit warning"). If the full count exceeds
|
|
45
|
+
the cap, output is truncated to top --top-k pairs and `truncated`
|
|
46
|
+
becomes true.
|
|
47
|
+
|
|
48
|
+
CLI examples:
|
|
49
|
+
uv run skf-pair-intersect.py intersect --libraries libs.json
|
|
50
|
+
cat libs.json | uv run skf-pair-intersect.py intersect --libraries -
|
|
51
|
+
uv run skf-pair-intersect.py intersect --libraries libs.json --top-k 50
|
|
52
|
+
|
|
53
|
+
Exit codes:
|
|
54
|
+
0 — operation succeeded (including: empty input → empty pairs list)
|
|
55
|
+
1 — user error (bad JSON, missing required `name`/`files` field on any
|
|
56
|
+
library entry, file not readable)
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
from __future__ import annotations
|
|
60
|
+
|
|
61
|
+
import argparse
|
|
62
|
+
import json
|
|
63
|
+
import sys
|
|
64
|
+
from pathlib import Path
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
DEFAULT_TOP_K = 20
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# --------------------------------------------------------------------------
|
|
71
|
+
# Pair intersection
|
|
72
|
+
# --------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def validate_libraries(libraries: object) -> list[dict]:
|
|
76
|
+
"""Validate the libraries input is a list of {name, files} records.
|
|
77
|
+
|
|
78
|
+
Returns the list with each entry's `files` normalized to a list of
|
|
79
|
+
forward-slash strings. Raises ValueError on any structural defect.
|
|
80
|
+
"""
|
|
81
|
+
if not isinstance(libraries, list):
|
|
82
|
+
raise ValueError(
|
|
83
|
+
f"libraries input must be a JSON array; got {type(libraries).__name__}"
|
|
84
|
+
)
|
|
85
|
+
normalized: list[dict] = []
|
|
86
|
+
for idx, entry in enumerate(libraries):
|
|
87
|
+
if not isinstance(entry, dict):
|
|
88
|
+
raise ValueError(
|
|
89
|
+
f"library entry at index {idx} is not an object: {entry!r}"
|
|
90
|
+
)
|
|
91
|
+
name = entry.get("name")
|
|
92
|
+
if not isinstance(name, str) or not name:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
f"library entry at index {idx} missing required `name` field"
|
|
95
|
+
)
|
|
96
|
+
files = entry.get("files")
|
|
97
|
+
if not isinstance(files, list):
|
|
98
|
+
raise ValueError(
|
|
99
|
+
f"library entry {name!r} missing required `files` array"
|
|
100
|
+
)
|
|
101
|
+
norm_files: list[str] = []
|
|
102
|
+
for f_idx, f in enumerate(files):
|
|
103
|
+
if not isinstance(f, str):
|
|
104
|
+
raise ValueError(
|
|
105
|
+
f"library {name!r} files[{f_idx}] is not a string: {f!r}"
|
|
106
|
+
)
|
|
107
|
+
# normalize separators to forward slash for cross-platform
|
|
108
|
+
# JSON output; preserves caller-supplied relative paths.
|
|
109
|
+
norm_files.append(f.replace("\\", "/"))
|
|
110
|
+
normalized.append({"name": name, "files": norm_files})
|
|
111
|
+
return normalized
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def compute_pairs(libraries: list[dict]) -> list[dict]:
|
|
115
|
+
"""For each unordered (a, b) with a < b lexicographically, compute the
|
|
116
|
+
file-list intersection. Emit only pairs with non-empty intersections.
|
|
117
|
+
|
|
118
|
+
The returned list is sorted by intersection_count DESC, then (a, b) ASC.
|
|
119
|
+
"""
|
|
120
|
+
# sort libraries by name for stable iteration order
|
|
121
|
+
sorted_libs = sorted(libraries, key=lambda lib: lib["name"])
|
|
122
|
+
pairs: list[dict] = []
|
|
123
|
+
n = len(sorted_libs)
|
|
124
|
+
for i in range(n):
|
|
125
|
+
for j in range(i + 1, n):
|
|
126
|
+
a = sorted_libs[i]
|
|
127
|
+
b = sorted_libs[j]
|
|
128
|
+
# by sort order, a["name"] <= b["name"]; if equal we skip
|
|
129
|
+
# (duplicate library names are silently collapsed into one
|
|
130
|
+
# pair-with-itself, which is not an integration candidate).
|
|
131
|
+
if a["name"] == b["name"]:
|
|
132
|
+
continue
|
|
133
|
+
set_a = set(a["files"])
|
|
134
|
+
set_b = set(b["files"])
|
|
135
|
+
intersection = sorted(set_a & set_b)
|
|
136
|
+
if not intersection:
|
|
137
|
+
continue
|
|
138
|
+
pairs.append({
|
|
139
|
+
"a": a["name"],
|
|
140
|
+
"b": b["name"],
|
|
141
|
+
"intersection_count": len(intersection),
|
|
142
|
+
"files": intersection,
|
|
143
|
+
})
|
|
144
|
+
pairs.sort(key=lambda p: (-p["intersection_count"], p["a"], p["b"]))
|
|
145
|
+
return pairs
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def intersect(libraries: list[dict], top_k: int = DEFAULT_TOP_K) -> dict:
|
|
149
|
+
"""Compute pairs and apply Top-K cap."""
|
|
150
|
+
all_pairs = compute_pairs(libraries)
|
|
151
|
+
total = len(all_pairs)
|
|
152
|
+
if top_k is not None and top_k >= 0 and total > top_k:
|
|
153
|
+
return {
|
|
154
|
+
"pairs": all_pairs[:top_k],
|
|
155
|
+
"truncated": True,
|
|
156
|
+
"total_pairs": total,
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
"pairs": all_pairs,
|
|
160
|
+
"truncated": False,
|
|
161
|
+
"total_pairs": total,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# --------------------------------------------------------------------------
|
|
166
|
+
# CLI
|
|
167
|
+
# --------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _read_libraries_source(source: str) -> object:
|
|
171
|
+
"""Read libraries JSON from a file path or stdin (if source == '-')."""
|
|
172
|
+
if source == "-":
|
|
173
|
+
try:
|
|
174
|
+
text = sys.stdin.read()
|
|
175
|
+
except OSError as exc:
|
|
176
|
+
raise ValueError(f"failed to read libraries JSON from stdin: {exc}") from exc
|
|
177
|
+
else:
|
|
178
|
+
path = Path(source)
|
|
179
|
+
if not path.is_file():
|
|
180
|
+
raise ValueError(f"libraries file not found: {path}")
|
|
181
|
+
try:
|
|
182
|
+
text = path.read_text(encoding="utf-8")
|
|
183
|
+
except OSError as exc:
|
|
184
|
+
raise ValueError(f"failed to read libraries file {path}: {exc}") from exc
|
|
185
|
+
try:
|
|
186
|
+
return json.loads(text)
|
|
187
|
+
except json.JSONDecodeError as exc:
|
|
188
|
+
raise ValueError(f"malformed JSON in libraries input: {exc}") from exc
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _cmd_intersect(args: argparse.Namespace) -> int:
|
|
192
|
+
try:
|
|
193
|
+
raw = _read_libraries_source(args.libraries)
|
|
194
|
+
libraries = validate_libraries(raw)
|
|
195
|
+
except ValueError as exc:
|
|
196
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
197
|
+
return 1
|
|
198
|
+
if args.top_k < 0:
|
|
199
|
+
print(f"error: --top-k must be >= 0; got {args.top_k}", file=sys.stderr)
|
|
200
|
+
return 1
|
|
201
|
+
result = intersect(libraries, top_k=args.top_k)
|
|
202
|
+
json.dump(result, sys.stdout, indent=2)
|
|
203
|
+
sys.stdout.write("\n")
|
|
204
|
+
return 0
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
208
|
+
parser = argparse.ArgumentParser(
|
|
209
|
+
prog="skf-pair-intersect",
|
|
210
|
+
description=(
|
|
211
|
+
"Compute file-list intersections between library pairs "
|
|
212
|
+
"(detect-integrations.md §1 fast path)."
|
|
213
|
+
),
|
|
214
|
+
)
|
|
215
|
+
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
216
|
+
|
|
217
|
+
p_int = sub.add_parser(
|
|
218
|
+
"intersect",
|
|
219
|
+
help="emit non-empty-intersection pairs as JSON, sorted DESC by count",
|
|
220
|
+
)
|
|
221
|
+
p_int.add_argument(
|
|
222
|
+
"--libraries",
|
|
223
|
+
required=True,
|
|
224
|
+
help=(
|
|
225
|
+
"path to libraries JSON, or '-' to read from stdin. "
|
|
226
|
+
"Shape: [{\"name\": \"<lib>\", \"files\": [\"<path>\", ...]}, ...]"
|
|
227
|
+
),
|
|
228
|
+
)
|
|
229
|
+
p_int.add_argument(
|
|
230
|
+
"--top-k",
|
|
231
|
+
type=int,
|
|
232
|
+
default=DEFAULT_TOP_K,
|
|
233
|
+
help=(
|
|
234
|
+
f"cap output at top N pairs by intersection_count (default {DEFAULT_TOP_K}); "
|
|
235
|
+
"set 0 to allow only zero pairs (effectively a 'count-only' mode)"
|
|
236
|
+
),
|
|
237
|
+
)
|
|
238
|
+
p_int.set_defaults(func=_cmd_intersect)
|
|
239
|
+
|
|
240
|
+
return parser
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def main(argv: list[str] | None = None) -> int:
|
|
244
|
+
parser = _build_parser()
|
|
245
|
+
args = parser.parse_args(argv)
|
|
246
|
+
return args.func(args)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
if __name__ == "__main__":
|
|
250
|
+
raise SystemExit(main())
|