bmad-module-skill-forge 2.0.0 → 2.0.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/.nvmrc +1 -1
- package/docs/_data/pinned.yaml +1 -1
- package/package.json +2 -2
- package/src/knowledge/skill-lifecycle.md +1 -1
- package/src/module-help.csv +2 -2
- package/src/shared/scripts/skf-comention-pairs.py +350 -0
- package/src/shared/scripts/skf-count-tokens.py +222 -0
- package/src/shared/scripts/skf-detect-docs.py +232 -1
- package/src/shared/scripts/skf-detect-language.py +77 -0
- package/src/shared/scripts/skf-detect-tools.py +105 -2
- package/src/shared/scripts/skf-enumerate-stack-skills.py +111 -2
- package/src/shared/scripts/skf-find-cycles.py +246 -0
- package/src/shared/scripts/skf-hash-content.py +487 -2
- package/src/shared/scripts/skf-manifest-ops.py +67 -1
- package/src/shared/scripts/skf-rebuild-managed-sections.py +180 -0
- package/src/shared/scripts/skf-recommend-scope-type.py +1 -2
- package/src/shared/scripts/skf-render-metadata-stats.py +499 -0
- package/src/shared/scripts/skf-rewrite-skill-name.py +308 -0
- package/src/shared/scripts/skf-shard-body.py +461 -0
- package/src/shared/scripts/skf-skill-inventory.py +139 -3
- package/src/shared/scripts/skf-structural-diff.py +307 -67
- package/src/shared/scripts/skf-validate-brief-schema.py +39 -1
- package/src/shared/scripts/skf-validate-feasibility-report.py +234 -0
- package/src/shared/scripts/skf-validate-frontmatter.py +17 -12
- package/src/shared/scripts/skf-validate-output.py +443 -10
- package/src/shared/scripts/skf-validate-pins.py +1 -1
- package/src/shared/scripts/skf-verify-no-trace.py +214 -0
- package/src/shared/scripts/skf-verify-provenance-completeness.py +439 -0
- package/src/skf-analyze-source/SKILL.md +17 -32
- package/src/skf-analyze-source/customize.toml +2 -0
- package/src/skf-analyze-source/references/auto-docs-only.md +116 -0
- package/src/skf-analyze-source/references/continue.md +8 -13
- package/src/skf-analyze-source/references/generate-briefs.md +28 -12
- package/src/skf-analyze-source/references/headless-contract.md +33 -0
- package/src/skf-analyze-source/references/health-check.md +1 -2
- package/src/skf-analyze-source/references/identify-units.md +25 -36
- package/src/skf-analyze-source/references/init.md +12 -25
- package/src/skf-analyze-source/references/map-and-detect.md +8 -20
- package/src/skf-analyze-source/references/recommend.md +4 -14
- package/src/skf-analyze-source/references/scan-project.md +3 -11
- package/src/skf-analyze-source/references/step-auto-scope.md +42 -180
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +2 -2
- package/src/skf-audit-skill/SKILL.md +9 -6
- package/src/skf-audit-skill/customize.toml +2 -0
- package/src/skf-audit-skill/references/init.md +58 -32
- package/src/skf-audit-skill/references/re-index.md +15 -34
- package/src/skf-audit-skill/references/report.md +11 -58
- package/src/skf-audit-skill/references/semantic-diff.md +4 -22
- package/src/skf-audit-skill/references/severity-classify.md +53 -87
- package/src/skf-audit-skill/references/step-doc-drift.md +49 -19
- package/src/skf-audit-skill/references/structural-diff.md +43 -68
- package/src/skf-brief-skill/SKILL.md +7 -36
- package/src/skf-brief-skill/assets/scope-templates.md +1 -1
- package/src/skf-brief-skill/customize.toml +14 -0
- package/src/skf-brief-skill/references/analyze-target.md +6 -14
- package/src/skf-brief-skill/references/confirm-brief.md +2 -14
- package/src/skf-brief-skill/references/draft-checkpoint.md +8 -8
- package/src/skf-brief-skill/references/gather-intent.md +19 -22
- package/src/skf-brief-skill/references/headless-args.md +1 -1
- package/src/skf-brief-skill/references/health-check.md +1 -22
- package/src/skf-brief-skill/references/invocation-contract.md +37 -0
- package/src/skf-brief-skill/references/scope-definition.md +4 -14
- package/src/skf-brief-skill/references/step-auto-brief.md +1 -5
- package/src/skf-brief-skill/references/step-auto-validate.md +1 -5
- package/src/skf-brief-skill/references/version-resolution.md +1 -1
- package/src/skf-brief-skill/references/write-brief.md +17 -10
- package/src/skf-campaign/SKILL.md +15 -84
- package/src/skf-campaign/customize.toml +2 -0
- package/src/skf-campaign/manifest.yaml +4 -0
- package/src/skf-campaign/references/campaign-contracts.md +67 -0
- package/src/skf-campaign/references/campaign-directive-spec.md +14 -103
- package/src/skf-campaign/references/step-01-setup.md +4 -5
- package/src/skf-campaign/references/step-02-strategy.md +8 -3
- package/src/skf-campaign/references/step-03-pins.md +1 -1
- package/src/skf-campaign/references/step-04-provenance.md +1 -1
- package/src/skf-campaign/references/step-05-skill-loop.md +1 -1
- package/src/skf-campaign/references/step-06-batch.md +7 -6
- package/src/skf-campaign/references/step-11-maintenance.md +2 -2
- package/src/skf-campaign/references/step-resume.md +9 -8
- package/src/skf-campaign/scripts/campaign-deps.py +10 -1
- package/src/skf-campaign/scripts/campaign-render-batch.py +199 -0
- package/src/skf-campaign/scripts/campaign-status.py +162 -0
- package/src/skf-campaign/templates/campaign-brief-template.yaml +1 -1
- package/src/skf-create-skill/SKILL.md +14 -3
- package/src/skf-create-skill/assets/compile-assembly-rules.md +17 -38
- package/src/skf-create-skill/assets/skill-sections.md +9 -100
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +4 -13
- package/src/skf-create-skill/customize.toml +13 -0
- package/src/skf-create-skill/references/authoritative-files-protocol.md +7 -7
- package/src/skf-create-skill/references/compile.md +34 -47
- package/src/skf-create-skill/references/component-extraction.md +8 -35
- package/src/skf-create-skill/references/ecosystem-check.md +9 -25
- package/src/skf-create-skill/references/enrich.md +4 -17
- package/src/skf-create-skill/references/extract.md +14 -29
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +1 -1
- package/src/skf-create-skill/references/extraction-patterns.md +7 -7
- package/src/skf-create-skill/references/generate-artifacts.md +9 -23
- package/src/skf-create-skill/references/health-check.md +1 -1
- package/src/skf-create-skill/references/load-brief.md +16 -18
- package/src/skf-create-skill/references/report.md +12 -15
- package/src/skf-create-skill/references/source-resolution-protocols.md +3 -3
- package/src/skf-create-skill/references/step-auto-shard.md +52 -25
- package/src/skf-create-skill/references/step-doc-rot.md +30 -12
- package/src/skf-create-skill/references/step-doc-sources.md +2 -2
- package/src/skf-create-skill/references/sub/ccc-discover.md +2 -13
- package/src/skf-create-skill/references/sub/fetch-docs.md +8 -50
- package/src/skf-create-skill/references/sub/fetch-temporal.md +5 -20
- package/src/skf-create-skill/references/tier-degradation-rules.md +6 -8
- package/src/skf-create-skill/references/validate.md +59 -28
- package/src/skf-create-skill/scripts/scan-doc-rot.py +245 -0
- package/src/skf-create-stack-skill/SKILL.md +15 -9
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +1 -1
- package/src/skf-create-stack-skill/customize.toml +10 -0
- package/src/skf-create-stack-skill/references/compile-stack.md +7 -8
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +2 -13
- package/src/skf-create-stack-skill/references/detect-integrations.md +49 -54
- package/src/skf-create-stack-skill/references/detect-manifests.md +37 -30
- package/src/skf-create-stack-skill/references/generate-output.md +24 -75
- package/src/skf-create-stack-skill/references/init.md +9 -81
- package/src/skf-create-stack-skill/references/manifest-patterns.md +5 -35
- package/src/skf-create-stack-skill/references/parallel-extract.md +12 -27
- package/src/skf-create-stack-skill/references/rank-and-confirm.md +6 -6
- package/src/skf-create-stack-skill/references/report.md +27 -62
- package/src/skf-create-stack-skill/references/validate.md +35 -31
- package/src/skf-drop-skill/SKILL.md +20 -23
- package/src/skf-drop-skill/customize.toml +10 -0
- package/src/skf-drop-skill/references/execute.md +51 -31
- package/src/skf-drop-skill/references/headless-contract.md +18 -0
- package/src/skf-drop-skill/references/health-check.md +1 -13
- package/src/skf-drop-skill/references/report.md +42 -10
- package/src/skf-drop-skill/references/select.md +88 -48
- package/src/skf-drop-skill/scripts/dir-sizes.py +116 -0
- package/src/skf-export-skill/SKILL.md +17 -14
- package/src/skf-export-skill/assets/managed-section-format.md +2 -5
- package/src/skf-export-skill/customize.toml +10 -6
- package/src/skf-export-skill/references/generate-snippet.md +21 -35
- package/src/skf-export-skill/references/load-skill.md +44 -42
- package/src/skf-export-skill/references/multi-skill-mode.md +1 -1
- package/src/skf-export-skill/references/orphan-context-detection.md +11 -3
- package/src/skf-export-skill/references/package.md +27 -33
- package/src/skf-export-skill/references/preflight-snippet-root-probe.md +24 -5
- package/src/skf-export-skill/references/result-envelope.md +28 -0
- package/src/skf-export-skill/references/summary.md +11 -5
- package/src/skf-export-skill/references/token-report.md +42 -24
- package/src/skf-export-skill/references/update-context.md +32 -33
- package/src/skf-forger/SKILL.md +27 -39
- package/src/skf-forger/references/pipeline-mode.md +34 -0
- package/src/skf-forger/scripts/parse-pipeline.py +270 -0
- package/src/skf-quick-skill/SKILL.md +10 -60
- package/src/skf-quick-skill/customize.toml +9 -0
- package/src/skf-quick-skill/references/batch-mode.md +6 -4
- package/src/skf-quick-skill/references/compile.md +15 -52
- package/src/skf-quick-skill/references/ecosystem-check.md +5 -20
- package/src/skf-quick-skill/references/finalize.md +12 -6
- package/src/skf-quick-skill/references/halt-contract.md +54 -0
- package/src/skf-quick-skill/references/health-check.md +3 -4
- package/src/skf-quick-skill/references/quick-extract.md +7 -18
- package/src/skf-quick-skill/references/registry-resolution.md +1 -23
- package/src/skf-quick-skill/references/resolve-target.md +27 -34
- package/src/skf-quick-skill/references/write-and-validate.md +10 -25
- package/src/skf-refine-architecture/SKILL.md +14 -7
- package/src/skf-refine-architecture/customize.toml +9 -0
- package/src/skf-refine-architecture/references/compile.md +9 -7
- package/src/skf-refine-architecture/references/gap-analysis.md +21 -50
- package/src/skf-refine-architecture/references/health-check.md +1 -1
- package/src/skf-refine-architecture/references/improvements.md +9 -25
- package/src/skf-refine-architecture/references/init.md +9 -16
- package/src/skf-refine-architecture/references/issue-detection.md +10 -25
- package/src/skf-refine-architecture/references/refinement-rules.md +4 -16
- package/src/skf-refine-architecture/references/report.md +13 -10
- package/src/skf-rename-skill/SKILL.md +11 -21
- package/src/skf-rename-skill/customize.toml +10 -0
- package/src/skf-rename-skill/references/execute.md +107 -78
- package/src/skf-rename-skill/references/exit-codes.md +12 -0
- package/src/skf-rename-skill/references/report.md +16 -8
- package/src/skf-rename-skill/references/select.md +69 -42
- package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -0
- package/src/skf-setup/SKILL.md +20 -12
- package/src/skf-setup/customize.toml +10 -0
- package/src/skf-setup/references/auto-index.md +9 -10
- package/src/skf-setup/references/ccc-index.md +14 -15
- package/src/skf-setup/references/detect-and-tier.md +6 -8
- package/src/skf-setup/references/report.md +22 -31
- package/src/skf-setup/references/tier-rules.md +2 -32
- package/src/skf-setup/references/write-config.md +9 -10
- package/src/skf-test-skill/SKILL.md +10 -8
- package/src/skf-test-skill/customize.toml +2 -0
- package/src/skf-test-skill/references/coherence-check.md +19 -31
- package/src/skf-test-skill/references/coverage-check.md +109 -75
- package/src/skf-test-skill/references/detect-mode.md +10 -41
- package/src/skf-test-skill/references/external-validators.md +11 -19
- package/src/skf-test-skill/references/init.md +38 -20
- package/src/skf-test-skill/references/migration-section-rules.md +4 -4
- package/src/skf-test-skill/references/report.md +60 -40
- package/src/skf-test-skill/references/score.md +32 -64
- package/src/skf-test-skill/references/scoring-rules.md +6 -62
- package/src/skf-test-skill/references/source-access-protocol.md +6 -6
- package/src/skf-test-skill/references/step-hard-gate.md +1 -3
- package/src/skf-test-skill/scripts/aggregate-coherence.py +276 -0
- package/src/skf-test-skill/scripts/check-metadata-coherence.py +359 -0
- package/src/skf-test-skill/scripts/combine-external-scores.py +190 -0
- package/src/skf-test-skill/scripts/compute-score.py +131 -1
- package/src/skf-test-skill/scripts/reconcile-coverage.py +327 -0
- package/src/skf-test-skill/scripts/validate-inventory.py +251 -0
- package/src/skf-test-skill/scripts/verify-declared-numerator.py +192 -0
- package/src/skf-update-skill/SKILL.md +17 -5
- package/src/skf-update-skill/customize.toml +14 -0
- package/src/skf-update-skill/references/detect-changes.md +19 -37
- package/src/skf-update-skill/references/health-check.md +3 -9
- package/src/skf-update-skill/references/init.md +39 -32
- package/src/skf-update-skill/references/manual-section-rules.md +1 -2
- package/src/skf-update-skill/references/merge-conflict-rules.md +1 -52
- package/src/skf-update-skill/references/merge.md +13 -57
- package/src/skf-update-skill/references/re-extract.md +12 -24
- package/src/skf-update-skill/references/report.md +14 -7
- package/src/skf-update-skill/references/validate.md +32 -30
- package/src/skf-update-skill/references/write.md +86 -51
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +167 -0
- package/src/skf-verify-stack/SKILL.md +11 -21
- package/src/skf-verify-stack/customize.toml +12 -3
- package/src/skf-verify-stack/references/coverage-patterns.md +1 -29
- package/src/skf-verify-stack/references/coverage.md +21 -10
- package/src/skf-verify-stack/references/exit-codes.md +14 -0
- package/src/skf-verify-stack/references/health-check.md +1 -1
- package/src/skf-verify-stack/references/init.md +20 -25
- package/src/skf-verify-stack/references/integration-verification-rules.md +10 -19
- package/src/skf-verify-stack/references/integrations.md +44 -49
- package/src/skf-verify-stack/references/report.md +28 -34
- package/src/skf-verify-stack/references/requirements.md +3 -3
- package/src/skf-verify-stack/references/synthesize.md +36 -39
- package/src/skf-verify-stack/scripts/skf-coverage-tally.py +196 -0
- package/src/skf-verify-stack/scripts/skf-report-delta.py +256 -0
- package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +258 -0
- package/src/skf-rename-skill/references/rebuild-context.md +0 -110
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.10"
|
|
4
|
+
# ///
|
|
5
|
+
"""SKF Validate Rename Name — deterministic new-name gate for skf-rename-skill.
|
|
6
|
+
|
|
7
|
+
select.md §5 accepts a proposed new skill name and must decide four things, each
|
|
8
|
+
with one correct answer for a given (name, filesystem, manifest) state:
|
|
9
|
+
|
|
10
|
+
format — matches the module's canonical kebab-case rule
|
|
11
|
+
^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ (same regex as
|
|
12
|
+
skf-validate-output.py / skf-validate-brief-inputs.py)
|
|
13
|
+
length — 1..64 characters (agentskills.io spec)
|
|
14
|
+
identity — differs from the current name (nothing to rename otherwise)
|
|
15
|
+
collision — does NOT already exist as a manifest `exports` key, a top-level
|
|
16
|
+
directory under the skills output folder, or a top-level
|
|
17
|
+
directory under the forge data folder
|
|
18
|
+
|
|
19
|
+
Doing this in the prompt is a set-membership computation across three sources
|
|
20
|
+
plus a regex match — deterministic, so it lives here and the prompt keeps only
|
|
21
|
+
the interactive re-ask / halt decision. Checks run in the select.md §5 order and
|
|
22
|
+
`first_failure` names the first failing one so the caller maps it to the right
|
|
23
|
+
halt (`input-invalid`/exit 2 for format/length/identity, `name-collision`/exit 5
|
|
24
|
+
for collision).
|
|
25
|
+
|
|
26
|
+
`interrupted_rename` fires the select.md §5 recovery fingerprint: the new name
|
|
27
|
+
collides only on disk (not in the manifest) AND both old-name directories still
|
|
28
|
+
exist — the signature of a rename interrupted between copy and delete-old, which
|
|
29
|
+
the caller surfaces as a named cleanup path instead of a dead-end collision.
|
|
30
|
+
|
|
31
|
+
Output (stdout, always JSON):
|
|
32
|
+
{status, valid, old_name, new_name, first_failure, checks{...},
|
|
33
|
+
interrupted_rename, regex}
|
|
34
|
+
|
|
35
|
+
Exit codes:
|
|
36
|
+
0 valid (all checks pass)
|
|
37
|
+
2 invalid (at least one check failed; see first_failure / checks)
|
|
38
|
+
1 operation error (bad args)
|
|
39
|
+
|
|
40
|
+
CLI example:
|
|
41
|
+
python3 skf-validate-rename-name.py --old-name rename --new-name rename-skill \
|
|
42
|
+
--skills-output-folder /out --forge-data-folder /forge
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from __future__ import annotations
|
|
46
|
+
|
|
47
|
+
import argparse
|
|
48
|
+
import json
|
|
49
|
+
import re
|
|
50
|
+
import sys
|
|
51
|
+
from pathlib import Path
|
|
52
|
+
|
|
53
|
+
NAME_REGEX = r"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$"
|
|
54
|
+
MIN_LEN = 1
|
|
55
|
+
MAX_LEN = 64
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def load_exports(manifest_path: Path) -> tuple[set[str], str | None]:
|
|
59
|
+
"""Return (export skill-name keys, error). Missing manifest -> empty set."""
|
|
60
|
+
if not manifest_path.is_file():
|
|
61
|
+
return set(), None
|
|
62
|
+
try:
|
|
63
|
+
data = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
64
|
+
except (json.JSONDecodeError, OSError) as e:
|
|
65
|
+
# select.md §2 halts before §5 on a malformed manifest, so treat an
|
|
66
|
+
# unreadable file here as "no exports" rather than crashing the gate.
|
|
67
|
+
return set(), f"{type(e).__name__}: {e}"
|
|
68
|
+
exports = data.get("exports", {}) if isinstance(data, dict) else {}
|
|
69
|
+
return set(exports.keys()) if isinstance(exports, dict) else set(), None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def validate_name(
|
|
73
|
+
old_name: str,
|
|
74
|
+
new_name: str,
|
|
75
|
+
skills_output_folder: str,
|
|
76
|
+
forge_data_folder: str,
|
|
77
|
+
manifest_path: str | None = None,
|
|
78
|
+
) -> dict:
|
|
79
|
+
skills_dir = Path(skills_output_folder)
|
|
80
|
+
forge_dir = Path(forge_data_folder)
|
|
81
|
+
manifest = Path(manifest_path) if manifest_path else skills_dir / ".export-manifest.json"
|
|
82
|
+
|
|
83
|
+
export_keys, manifest_error = load_exports(manifest)
|
|
84
|
+
|
|
85
|
+
fmt_ok = re.match(NAME_REGEX, new_name) is not None
|
|
86
|
+
length = len(new_name)
|
|
87
|
+
len_ok = MIN_LEN <= length <= MAX_LEN
|
|
88
|
+
identity_ok = new_name != old_name
|
|
89
|
+
|
|
90
|
+
locations: list[dict] = []
|
|
91
|
+
if new_name in export_keys:
|
|
92
|
+
locations.append({"kind": "manifest.exports", "path": str(manifest)})
|
|
93
|
+
if (skills_dir / new_name).is_dir():
|
|
94
|
+
locations.append({"kind": "skills_output_folder", "path": str(skills_dir / new_name)})
|
|
95
|
+
if (forge_dir / new_name).is_dir():
|
|
96
|
+
locations.append({"kind": "forge_data_folder", "path": str(forge_dir / new_name)})
|
|
97
|
+
collision_ok = len(locations) == 0
|
|
98
|
+
|
|
99
|
+
# Recovery fingerprint: collides on disk only (not manifest) and both
|
|
100
|
+
# old-name directories still exist -> stranded partial rename, not a genuine
|
|
101
|
+
# clash.
|
|
102
|
+
collides_on_disk_only = (
|
|
103
|
+
not collision_ok
|
|
104
|
+
and all(loc["kind"] != "manifest.exports" for loc in locations)
|
|
105
|
+
)
|
|
106
|
+
interrupted_rename = bool(
|
|
107
|
+
collides_on_disk_only
|
|
108
|
+
and (skills_dir / old_name).is_dir()
|
|
109
|
+
and (forge_dir / old_name).is_dir()
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
checks = {
|
|
113
|
+
"format": {"ok": fmt_ok},
|
|
114
|
+
"length": {"ok": len_ok, "length": length},
|
|
115
|
+
"identity": {"ok": identity_ok},
|
|
116
|
+
"collision": {"ok": collision_ok, "locations": locations},
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
first_failure = None
|
|
120
|
+
for name in ("format", "length", "identity", "collision"):
|
|
121
|
+
if not checks[name]["ok"]:
|
|
122
|
+
first_failure = name
|
|
123
|
+
break
|
|
124
|
+
|
|
125
|
+
result = {
|
|
126
|
+
"status": "ok",
|
|
127
|
+
"valid": first_failure is None,
|
|
128
|
+
"old_name": old_name,
|
|
129
|
+
"new_name": new_name,
|
|
130
|
+
"first_failure": first_failure,
|
|
131
|
+
"checks": checks,
|
|
132
|
+
"interrupted_rename": interrupted_rename,
|
|
133
|
+
"regex": NAME_REGEX,
|
|
134
|
+
}
|
|
135
|
+
if manifest_error:
|
|
136
|
+
result["manifest_error"] = manifest_error
|
|
137
|
+
return result
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def main(argv: list[str] | None = None) -> int:
|
|
141
|
+
parser = argparse.ArgumentParser(description="Validate a proposed rename target name.")
|
|
142
|
+
parser.add_argument("--old-name", required=True)
|
|
143
|
+
parser.add_argument("--new-name", required=True)
|
|
144
|
+
parser.add_argument("--skills-output-folder", required=True)
|
|
145
|
+
parser.add_argument("--forge-data-folder", required=True)
|
|
146
|
+
parser.add_argument("--manifest", default=None)
|
|
147
|
+
args = parser.parse_args(argv)
|
|
148
|
+
|
|
149
|
+
result = validate_name(
|
|
150
|
+
args.old_name,
|
|
151
|
+
args.new_name,
|
|
152
|
+
args.skills_output_folder,
|
|
153
|
+
args.forge_data_folder,
|
|
154
|
+
args.manifest,
|
|
155
|
+
)
|
|
156
|
+
print(json.dumps(result, indent=2))
|
|
157
|
+
return 0 if result["valid"] else 2
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
if __name__ == "__main__":
|
|
161
|
+
sys.exit(main())
|
package/src/skf-setup/SKILL.md
CHANGED
|
@@ -7,12 +7,13 @@ description: Initialize forge environment, detect tools, and set capability tier
|
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
Initializes the forge environment by detecting available tools, determining the capability tier (Quick/Forge/Forge+/Deep), and writing persistent configuration to `{project-root}/_bmad/_memory/forger-sidecar/`. When `ccc` (cocoindex-code) is available, also augments `.cocoindex_code/settings.yml` with SKF exclusion patterns and creates or refreshes the project's semantic-search index. On Deep tier, reconciles the QMD collection registry; whenever ccc is available, reconciles the CCC index registry as well.
|
|
10
|
+
Initializes the forge environment by detecting available tools, determining the capability tier (Quick/Forge/Forge+/Deep), and writing persistent configuration to `{project-root}/_bmad/_memory/forger-sidecar/`. When `ccc` (cocoindex-code) is available, also augments `.cocoindex_code/settings.yml` with SKF exclusion patterns and creates or refreshes the project's semantic-search index. On Deep tier, reconciles the QMD collection registry; whenever ccc is available, reconciles the CCC index registry as well.
|
|
11
11
|
|
|
12
12
|
## Conventions
|
|
13
13
|
|
|
14
14
|
- Bare paths (e.g. `references/<name>.md`) resolve from the skill root.
|
|
15
|
-
- `references/` holds prompt content carved out of SKILL.md
|
|
15
|
+
- `references/` holds prompt content carved out of SKILL.md — workflow stages chained via frontmatter `nextStepFile`, plus static reference docs.
|
|
16
|
+
- Deterministic work is delegated to shared Python helpers under `src/shared/scripts/` (installed as `_bmad/skf/shared/scripts/`). Each step's frontmatter declares a `*ProbeOrder` array for the helpers it needs: run the first path in the array that exists, and halt if none resolve — the script owns that logic, with no prose fallback.
|
|
16
17
|
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives, if present).
|
|
17
18
|
- `{project-root}`-prefixed paths resolve from the project working directory.
|
|
18
19
|
- `{skill-name}` resolves to the skill directory's basename.
|
|
@@ -23,15 +24,12 @@ You are a system executor performing environment resolution. Run each step in se
|
|
|
23
24
|
|
|
24
25
|
## Workflow Rules
|
|
25
26
|
|
|
26
|
-
- One optional gate (step 3 orphan-removal prompt; default: Keep). Every other step auto-proceeds.
|
|
27
27
|
- Only load one step file at a time — never preload future steps.
|
|
28
28
|
- Communicate in `{communication_language}`.
|
|
29
|
-
- If `{headless_mode}` is true, or if `{orphan_action}` is non-null, auto-resolve the gate non-interactively and log the decision.
|
|
29
|
+
- If `{headless_mode}` is true, or if `{orphan_action}` is non-null, auto-resolve the step 3 orphan-removal gate non-interactively and log the decision.
|
|
30
30
|
|
|
31
31
|
## Stages
|
|
32
32
|
|
|
33
|
-
All stages auto-proceed except the optional orphan-removal gate in step 3.
|
|
34
|
-
|
|
35
33
|
| # | Step | File |
|
|
36
34
|
|---|------|------|
|
|
37
35
|
| 1 | Detect Tools & Set Tier | references/detect-and-tier.md |
|
|
@@ -49,15 +47,14 @@ All stages auto-proceed except the optional orphan-removal gate in step 3.
|
|
|
49
47
|
| **Flags** | `--headless` / `-H` (skip prompts, auto-resolve gates to defaults); `--require-tier=<Quick\|Forge\|Forge+\|Deep>` (halt with failure if calculated tier does not satisfy the requirement); `--orphan-action=<keep\|remove>` (resolve the orphan-removal gate non-interactively, even outside `--headless`); `--ccc-skip-index` (skip CCC indexing; envelope `ccc_index.status` becomes `"skipped"` — the fast re-probe lane for an expert re-running only to refresh the detected tier without paying the full ccc re-index cost); `--quiet` (suppress the human-readable FORGE STATUS banner — pipelines and expert re-runners get the envelope only) |
|
|
50
48
|
| **Gates** | One optional: orphaned QMD collection removal (step 3, Deep tier only; default: Keep, or whatever `--orphan-action` set) |
|
|
51
49
|
| **Outputs** | `forger-sidecar/forge-tier.yaml`, `forger-sidecar/preferences.yaml`, `{forge_data_folder}/`; when ccc is available, `.cocoindex_code/settings.yml` (exclusion patterns merged) and the project ccc index |
|
|
52
|
-
| **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true. Under `--headless` or `--quiet`, step 4 emits a single-line `SKF_SETUP_RESULT_JSON: {…}` envelope as the only stdout line — the FORGE STATUS banner is suppressed. Branch on the envelope's top-level `status` field
|
|
53
|
-
| **Failure modes** | `--require-tier` not satisfied → status
|
|
54
|
-
| **Exit codes** | The workflow runs as an LLM-driven sequence rather than a CLI process, so "exit code" describes the agent's terminal state for a calling pipeline that reads `SKF_SETUP_RESULT_JSON`. **0 — success** (no JSON `error` field, all writes completed, `require_tier_satisfied` is `true` or `null`). **1 — required-tier failure** (`require_tier_satisfied` is `false`; envelope still emitted with full state for diagnosis). **2 — write failure** (forge-tier.yaml or preferences.yaml could not be written; envelope `error` field names the path and reason). Pipelines should branch on the JSON `require_tier_satisfied` and `error` fields rather than process exit codes. |
|
|
50
|
+
| **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true. Under `--headless` or `--quiet`, step 4 emits a single-line `SKF_SETUP_RESULT_JSON: {…}` envelope as the only stdout line — the FORGE STATUS banner is suppressed. Branch on the envelope's top-level `status` field: `success`, `tier_failure` (require-tier miss), or `blocked` (any pre-report halt — the `error.phase` names the cause). `skf-emit-result-envelope.py` derives `status` from the payload, so pipelines never compose it from `require_tier_satisfied` + `error`. Schema in `references/report.md` §4. |
|
|
51
|
+
| **Failure modes** | `--require-tier` not satisfied → status `tier_failure`, the envelope sets `"require_tier_satisfied": false`, and the workflow halts before step 5 (interactive runs also print a "REQUIRED TIER NOT MET" block). A write failure (forge-tier.yaml or preferences.yaml could not be written) halts step 2 with a `blocked` envelope whose `error.phase` (`step 2:write-tools` or `step 2:init-prefs`) and `error` name the path and reason. |
|
|
55
52
|
|
|
56
53
|
## On Activation
|
|
57
54
|
|
|
58
|
-
> **Halt contract for headless/quiet runs.** Any halt below
|
|
55
|
+
> **Halt contract for headless/quiet runs.** Any halt below must first emit a blocked envelope when `{headless_mode}` or `{quiet_mode}` is true, before printing the human diagnostic — a pipeline observer that sees no envelope treats the run as not-completed-cleanly. Pipe `{"phase":"<phase>","reason":"<reason>","path":"<path>"}` to `python3 <helper> emit-blocked` where `<helper>` is the first existing path of `{project-root}/_bmad/skf/shared/scripts/skf-emit-result-envelope.py` then `{project-root}/src/shared/scripts/skf-emit-result-envelope.py`. The `emit-blocked` subcommand declares zero dependencies (no `uv`, no `pyyaml`), so it works even when `uv` itself is the thing that's missing.
|
|
59
56
|
|
|
60
|
-
1. **Parse invocation flags
|
|
57
|
+
1. **Parse invocation flags first** (so every halt below knows whether to emit an envelope): `{headless_mode}` (true on `--headless` / `-H`), `{require_tier}` (`--require-tier=<Quick|Forge|Forge+|Deep>`, case-sensitive; null if absent or unparseable), `{orphan_action}` (`--orphan-action=<keep|remove>`; null if absent), `{ccc_skip_index}` (true on `--ccc-skip-index`), `{quiet_mode}` (true on `--quiet`).
|
|
61
58
|
|
|
62
59
|
2. **Probe `uv` runtime.** Run `uv --version`. Every step invokes shared Python helpers via `uv run` (PEP 723 inline metadata auto-resolves `pyyaml`). If `uv` is missing, halt with phase `on-activation:uv-missing` and the human diagnostic:
|
|
63
60
|
|
|
@@ -67,4 +64,15 @@ All stages auto-proceed except the optional orphan-removal gate in step 3.
|
|
|
67
64
|
|
|
68
65
|
4. **Reconcile `{headless_mode}`** with `preferences.yaml`: OR the parsed flag with `headless_mode: true` from the YAML.
|
|
69
66
|
|
|
70
|
-
5.
|
|
67
|
+
5. **Resolve workflow customization.** Run:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
python3 {project-root}/_bmad/scripts/resolve_customization.py \
|
|
71
|
+
--skill {skill-root} --key workflow
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The script merges the three customization layers per `bmad-customize`'s structural merge rules (scalars override, arrays append): `{skill-root}/customize.toml` (bundled defaults), `_bmad/custom/<skill-name>.toml` under `{project-root}` (team overrides, committed), and `_bmad/custom/<skill-name>.user.toml` under `{project-root}` (personal overrides, gitignored). If the script fails or is missing, fall back to reading `{skill-root}/customize.toml` directly.
|
|
75
|
+
|
|
76
|
+
Apply the resolved values so the surface is not a silent no-op: execute each entry in `workflow.activation_steps_prepend` in order now (org-wide pre-flight checks such as auth, network, or compliance); treat every entry in `workflow.persistent_facts` as standing context for the whole run (`file:`-prefixed entries are paths or globs whose contents load as facts); stash `{onCompleteCommand}` ← `workflow.on_complete` (empty string = no-op) for `references/report.md` §5 to invoke at the terminal stage. After activation completes, execute each entry in `workflow.activation_steps_append` in order, before the first stage runs.
|
|
77
|
+
|
|
78
|
+
6. Execute `references/detect-and-tier.md`.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# DO NOT EDIT -- overwritten on every update.
|
|
2
2
|
#
|
|
3
3
|
# Workflow customization surface for skf-setup.
|
|
4
|
+
# Team overrides: _bmad/custom/skf-setup.toml (under {project-root})
|
|
5
|
+
# Personal overrides: _bmad/custom/skf-setup.user.toml (under {project-root})
|
|
4
6
|
|
|
5
7
|
[workflow]
|
|
6
8
|
|
|
@@ -31,3 +33,11 @@ activation_steps_append = []
|
|
|
31
33
|
# contents are loaded and treated as facts).
|
|
32
34
|
|
|
33
35
|
persistent_facts = []
|
|
36
|
+
|
|
37
|
+
# Instruction run once the forge is fully configured — after the status report
|
|
38
|
+
# (step 4) emits, before chaining to the shared health check. Override wins. Use
|
|
39
|
+
# for a post-setup action an org wants after the forge is configured — e.g.
|
|
40
|
+
# trigger the first index build or notify an onboarding channel. Empty string =
|
|
41
|
+
# no terminal hook.
|
|
42
|
+
|
|
43
|
+
on_complete = ""
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: 'report.md'
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# / registry-cleanup contracts that have no prose fallback.
|
|
3
|
+
# `{qmdClassifyHelper}` and `{forgeTierRwHelper}` = first existing path in
|
|
4
|
+
# their `*ProbeOrder` arrays; halt if neither exists for a helper a section
|
|
5
|
+
# actually invokes. Both scripts own classification / registry-cleanup
|
|
6
|
+
# contracts with no prose fallback.
|
|
8
7
|
qmdClassifyProbeOrder:
|
|
9
8
|
- '{project-root}/_bmad/skf/shared/scripts/skf-qmd-classify-collections.py'
|
|
10
9
|
- '{project-root}/src/shared/scripts/skf-qmd-classify-collections.py'
|
|
@@ -45,7 +44,7 @@ Default `{orphan_auto_resolution}` to null at the top of this step; only the non
|
|
|
45
44
|
|
|
46
45
|
**If `{calculated_tier}` is Forge+:** Same as Quick/Forge with ccc — no QMD work (qmd unavailable at Forge+), but ccc registry hygiene runs.
|
|
47
46
|
|
|
48
|
-
**If `{calculated_tier}`
|
|
47
|
+
**If `{calculated_tier}` is Deep:** Continue to section 2.
|
|
49
48
|
|
|
50
49
|
### 2. Classify Live QMD Collections vs Registry
|
|
51
50
|
|
|
@@ -56,7 +55,7 @@ uv run {qmdClassifyHelper} \
|
|
|
56
55
|
--registry-from-yaml "{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml"
|
|
57
56
|
```
|
|
58
57
|
|
|
59
|
-
The script (see `src/shared/scripts/skf-qmd-classify-collections.py` docstring for the full schema) invokes `qmd collection list` itself, applies the forge-namespace suffix filter (`-brief | -temporal | -docs | -extraction`) before classifying, and exits non-zero with an error message on stderr if the daemon is down. Collections owned by unrelated tools are silently excluded from the orphan / healthy / stale sets and counted under `foreign_filtered_count` for telemetry only
|
|
58
|
+
The script (see `src/shared/scripts/skf-qmd-classify-collections.py` docstring for the full schema) invokes `qmd collection list` itself, applies the forge-namespace suffix filter (`-brief | -temporal | -docs | -extraction`) before classifying, and exits non-zero with an error message on stderr if the daemon is down. Collections owned by unrelated tools are silently excluded from the orphan / healthy / stale sets and counted under `foreign_filtered_count` for telemetry only — foreign collections never enter any classification that could lead to data loss.
|
|
60
59
|
|
|
61
60
|
**Error handling:** If the script exits non-zero, set `{hygiene_result: "qmd_unavailable", hygiene_healthy: 0, hygiene_orphaned_removed: 0, hygiene_orphaned_kept: 0, hygiene_stale_cleaned: 0}` and skip directly to section 4 (which will still run the ccc-prune branch if `{ccc}` is true).
|
|
62
61
|
|
|
@@ -74,7 +73,7 @@ Set `{hygiene_result: "completed"}`.
|
|
|
74
73
|
|
|
75
74
|
**If `{orphaned_collections}` is empty:** Set `{hygiene_orphaned_removed: 0, hygiene_orphaned_kept: 0}` and skip to section 4.
|
|
76
75
|
|
|
77
|
-
**Non-interactive resolution.** If `{orphan_action}` is non-null, resolve the gate without prompting using that value: log `"Auto-decision (--orphan-action={value}): kept|removed {len(orphaned_collections)} orphaned forge collection(s)"` and set `{orphan_auto_resolution: {action: "{orphan_action}", count: len(orphaned_collections), source: "orphan-action-flag"}}` so step 4 can fold it into the envelope warnings (the audit trail matters most when `remove` deletes collections headlessly). If `{orphan_action}` is `"keep"`, set `{hygiene_orphaned_removed: 0, hygiene_orphaned_kept: len(orphaned_collections)}` and skip to section 4. If `"remove"`, fall through to the removal block below (still no user prompt). Independently, if `{headless_mode}` is true and `{orphan_action}` is null, auto-resolve to the default **Keep** with the equivalent log line, set `{orphan_auto_resolution: {action: "keep", count: len(orphaned_collections), source: "headless-default"}}`, and skip to section 4. The two paths compose: `--orphan-action` overrides the headless default; `--headless`
|
|
76
|
+
**Non-interactive resolution.** If `{orphan_action}` is non-null, resolve the gate without prompting using that value: log `"Auto-decision (--orphan-action={value}): kept|removed {len(orphaned_collections)} orphaned forge collection(s)"` and set `{orphan_auto_resolution: {action: "{orphan_action}", count: len(orphaned_collections), source: "orphan-action-flag"}}` so step 4 can fold it into the envelope warnings (the audit trail matters most when `remove` deletes collections headlessly). If `{orphan_action}` is `"keep"`, set `{hygiene_orphaned_removed: 0, hygiene_orphaned_kept: len(orphaned_collections)}` and skip to section 4. If `"remove"`, fall through to the removal block below (still no user prompt). Independently, if `{headless_mode}` is true and `{orphan_action}` is null, auto-resolve to the default **Keep** with the equivalent log line, set `{orphan_auto_resolution: {action: "keep", count: len(orphaned_collections), source: "headless-default"}}`, and skip to section 4. The two paths compose: `--orphan-action` overrides the headless default; `--headless` alone falls back to the default Keep. (On an interactive resolution — section 3's prompt below — leave `{orphan_auto_resolution}` null; the human chose, so there is no auto-decision to audit.)
|
|
78
77
|
|
|
79
78
|
**If `{headless_mode}` is false**, display to the user:
|
|
80
79
|
|
|
@@ -99,7 +98,7 @@ Track the count of successful removals as `{hygiene_orphaned_removed}`. Set `{hy
|
|
|
99
98
|
|
|
100
99
|
### 4. Stale Registry Cleanup
|
|
101
100
|
|
|
102
|
-
This section
|
|
101
|
+
This section runs whenever reachable — it handles both `qmd_collections` stale entries (Deep tier) and `ccc_index_registry` stale entries (whenever ccc is true). The script's flags are mutually independent.
|
|
103
102
|
|
|
104
103
|
Build the invocation. Always include `--target` for the forge-tier.yaml path. Include `--qmd-live-names "{live_collections}"` ONLY when section 2 ran successfully (i.e. `{hygiene_result}` is `"completed"`); omit the flag entirely otherwise so the script skips QMD cleanup. Include `--prune-missing-ccc-paths` ONLY when `{ccc}` is true; omit it otherwise.
|
|
105
104
|
|
|
@@ -110,7 +109,7 @@ uv run {forgeTierRwHelper} clean-stale \
|
|
|
110
109
|
[--prune-missing-ccc-paths]
|
|
111
110
|
```
|
|
112
111
|
|
|
113
|
-
The script reads the registry, computes set-difference operations (qmd: registry − live; ccc: filter where `path` does not exist on disk), and atomically rewrites forge-tier.yaml only when something actually changed (mtime preserved on idempotent re-runs). The CI ephemeral-mount caveat for ccc-registry pruning is logged in the script's
|
|
112
|
+
The script reads the registry, computes set-difference operations (qmd: registry − live; ccc: filter where `path` does not exist on disk), and atomically rewrites forge-tier.yaml only when something actually changed (mtime preserved on idempotent re-runs). The CI ephemeral-mount caveat for ccc-registry pruning is logged in the script's warning message.
|
|
114
113
|
|
|
115
114
|
**Parse the JSON output and set context flags for step 4:**
|
|
116
115
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: 'write-config.md'
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# fallback to prose-driven validation.
|
|
3
|
+
# `{mergeCccExclusionsHelper}` = first existing path in
|
|
4
|
+
# `{mergeCccExclusionsProbeOrder}`; halt if neither exists. The script owns
|
|
5
|
+
# config-value validation and the set-union merge into
|
|
6
|
+
# .cocoindex_code/settings.yml — no prose fallback.
|
|
8
7
|
mergeCccExclusionsProbeOrder:
|
|
9
8
|
- '{project-root}/_bmad/skf/shared/scripts/skf-merge-ccc-exclusions.py'
|
|
10
9
|
- '{project-root}/src/shared/scripts/skf-merge-ccc-exclusions.py'
|
|
@@ -25,7 +24,7 @@ For Quick and Forge tiers, or when ccc is unavailable, skip silently and proceed
|
|
|
25
24
|
- Focus only on ccc index verification, exclusion-pattern merge, and (re-)indexing
|
|
26
25
|
- Do not display skip messages for Quick/Forge tiers
|
|
27
26
|
- Do not fail the workflow if ccc indexing fails
|
|
28
|
-
-
|
|
27
|
+
- The script owns exclusion-pattern validation — do not reimplement it in prose
|
|
29
28
|
|
|
30
29
|
## MANDATORY SEQUENCE
|
|
31
30
|
|
|
@@ -35,24 +34,24 @@ Read `{ccc}` and `{ccc_skip_index}` from context.
|
|
|
35
34
|
|
|
36
35
|
**If `{ccc}` is false:** Set `{ccc_index_result: "none", ccc_indexed_path: null, ccc_last_indexed: null, ccc_exclude_patterns: [], ccc_exclusion_warnings: [], settings_yml_written: false, settings_yml_patterns_added: 0}`. Proceed directly to section 4 (Auto-Proceed) — no output, no messaging.
|
|
37
36
|
|
|
38
|
-
**If `{ccc}` is true AND `{ccc_skip_index}` is true:** Run the exclusion-merge (section 3) so settings.yml stays current, then set `{ccc_index_result: "skipped", ccc_indexed_path: null, ccc_last_indexed: null}` and proceed to section 4 — do
|
|
37
|
+
**If `{ccc}` is true AND `{ccc_skip_index}` is true:** Run the exclusion-merge (section 3) so settings.yml stays current, then set `{ccc_index_result: "skipped", ccc_indexed_path: null, ccc_last_indexed: null}` and proceed to section 4 — do not run `ccc init` or `ccc index`. The envelope's `ccc_index.status` will be `"skipped"` so pipelines that plan an out-of-band re-index can distinguish "the operator opted out" from "indexing failed".
|
|
39
38
|
|
|
40
39
|
**If `{ccc}` is true AND `{ccc_skip_index}` is false:** Continue to section 2.
|
|
41
40
|
|
|
42
41
|
### 2. Check Existing Index State
|
|
43
42
|
|
|
44
|
-
Consume prior CCC state from stage 1's detector output (`prior
|
|
43
|
+
Consume prior CCC state from stage 1's detector output (`prior.*` context flags) — no YAML re-parse and no timestamp math here. The detector already read forge-tier.yaml and computed the freshness verdict `{ccc_index_fresh}` against `{project-root}` and the current time (same-project path match AND `"fresh"`/`"created"` status AND `last_indexed` within the staleness threshold, defaulting to 24 hours).
|
|
45
44
|
|
|
46
|
-
Decide `{needs_reindex}` and `{ccc_index_result}` from
|
|
45
|
+
Decide `{needs_reindex}` and `{ccc_index_result}` from `{ccc_index_fresh}`:
|
|
47
46
|
|
|
48
|
-
- If `
|
|
49
|
-
- If
|
|
47
|
+
- If `{ccc_index_fresh}` is true → index is fresh. Set `{needs_reindex: false}`, `{ccc_index_result: "fresh", ccc_indexed_path: {project-root}, ccc_last_indexed: {previous_ccc_last_indexed}}`. Exclusions still merge in section 3 (which may force a re-index).
|
|
48
|
+
- If `{ccc_index_fresh}` is false (path mismatch, non-fresh status, stale timestamp, or any required prior CCC field null) → `{needs_reindex: true}`.
|
|
50
49
|
|
|
51
50
|
### 3. Merge SKF Exclusion Patterns
|
|
52
51
|
|
|
53
52
|
SKF infrastructure and output directories must be excluded from the CCC index — they contain workflow instructions, build artifacts, and generated skills that pollute semantic search results with zero extraction value.
|
|
54
53
|
|
|
55
|
-
Forward `skills_output_folder` and `forge_data_folder` from `{project-root}/_bmad/skf/config.yaml` **verbatim** — the script resolves `{project-root}/...` template strings and rejects absolute paths / placeholders / glob meta-chars internally. The step does no string surgery; that work moved into the helper. Invoke via `uv run
|
|
54
|
+
Forward `skills_output_folder` and `forge_data_folder` from `{project-root}/_bmad/skf/config.yaml` **verbatim** — the script resolves `{project-root}/...` template strings and rejects absolute paths / placeholders / glob meta-chars internally. The step does no string surgery; that work moved into the helper. Invoke via `uv run`.
|
|
56
55
|
|
|
57
56
|
```bash
|
|
58
57
|
uv run {mergeCccExclusionsHelper} \
|
|
@@ -61,14 +60,14 @@ uv run {mergeCccExclusionsHelper} \
|
|
|
61
60
|
--forge-data-folder "{forge_data_folder}"
|
|
62
61
|
```
|
|
63
62
|
|
|
64
|
-
The script (see `src/shared/scripts/skf-merge-ccc-exclusions.py` docstring for the full schema) builds the SKF exclusion list (4 always-include hardcoded patterns + 2 conditional from validated config), applies the
|
|
63
|
+
The script (see `src/shared/scripts/skf-merge-ccc-exclusions.py` docstring for the full schema) builds the SKF exclusion list (4 always-include hardcoded patterns + 2 conditional from validated config), applies the validation rules to reject empty / absolute / glob-meta config values with actionable warnings, and performs an idempotent set-union merge into `{project-root}/.cocoindex_code/settings.yml`. User customizations are preserved. When the file does not exist yet (first-time setup before `ccc init`) the script creates it; when nothing new needs adding the script skips the write entirely (mtime preserved).
|
|
65
64
|
|
|
66
65
|
**Parse the JSON output and set context flags:**
|
|
67
66
|
|
|
68
67
|
- `{settings_yml_existed}` ← `settings_yml_existed`
|
|
69
68
|
- `{settings_yml_written}` ← `written`
|
|
70
69
|
- `{settings_yml_patterns_added}` ← `patterns_added`
|
|
71
|
-
- `{ccc_exclude_patterns}` ← `effective_patterns` (the script returns the final, sorted, deduplicated SKF pattern set after validation — consume verbatim; do
|
|
70
|
+
- `{ccc_exclude_patterns}` ← `effective_patterns` (the script returns the final, sorted, deduplicated SKF pattern set after validation — consume verbatim; do not re-derive in prose).
|
|
72
71
|
- `{ccc_exclusion_warnings}` ← `warnings` (a list — step 4 folds them into the envelope's warnings array)
|
|
73
72
|
|
|
74
73
|
**If `{settings_yml_written}` is true** (new patterns merged into settings.yml): set `{needs_reindex: true}` — new exclusions require re-indexing for the index to reflect them. Display: "**CCC exclusions configured:** {patterns_added} SKF patterns applied to .cocoindex_code/settings.yml"
|
|
@@ -114,7 +113,7 @@ ccc index
|
|
|
114
113
|
|
|
115
114
|
- Store `{ccc_index_result: "failed", ccc_indexed_path: null, ccc_last_indexed: null, ccc_indexing_failed_reason: {error}}` (the failed-reason flag flows into step 4's envelope warnings)
|
|
116
115
|
- Display: "CCC indexing failed: {error}. Extraction will use direct AST scanning — semantic pre-ranking unavailable this session."
|
|
117
|
-
- Continue — this is
|
|
116
|
+
- Continue — this is not a workflow error
|
|
118
117
|
|
|
119
118
|
### 5. Auto-Proceed
|
|
120
119
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: 'ccc-index.md'
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# tool detection and tier calculation; no fallback to prose-driven probes.
|
|
3
|
+
# `{detectToolsHelper}` = first existing path in `{detectToolsProbeOrder}`;
|
|
4
|
+
# halt if neither exists. This script is the source of truth for tool
|
|
5
|
+
# detection and tier calculation — no prose-driven probes.
|
|
7
6
|
detectToolsProbeOrder:
|
|
8
7
|
- '{project-root}/_bmad/skf/shared/scripts/skf-detect-tools.py'
|
|
9
8
|
- '{project-root}/src/shared/scripts/skf-detect-tools.py'
|
|
@@ -54,9 +53,9 @@ Press Esc or Ctrl+C now if this isn't the right project — no files have been w
|
|
|
54
53
|
|
|
55
54
|
### 2. Run Detection Helper
|
|
56
55
|
|
|
57
|
-
Build the Bash invocation: `uv run {detectToolsHelper} --prior-state-from "{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml"`. If `{tier_override}` is non-null, append `--tier-override "{tier_override}"`. If `{require_tier}` is non-null, append `--require-tier "{require_tier}"`. Then execute. (
|
|
56
|
+
Build the Bash invocation: `uv run {detectToolsHelper} --project-root "{project-root}" --prior-state-from "{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml"`. If `{tier_override}` is non-null, append `--tier-override "{tier_override}"`. If `{require_tier}` is non-null, append `--require-tier "{require_tier}"`. Then execute. (`--project-root` lets the script compute the CCC-index freshness verdict — `prior.ccc_index_fresh` — so step 1b branches on a boolean instead of doing timestamp math.)
|
|
58
57
|
|
|
59
|
-
The script (see `src/shared/scripts/skf-detect-tools.py` docstring for the full `DETECT_OUTPUT_SCHEMA`) probes ast-grep / gh / qmd / ccc concurrently with two-step verification for qmd and ccc (binary-identity check + daemon-health check, including the `CocoIndex Code` identity-marker substring check that rejects PATH-shadowing aliases). It applies the 4-rule tier table, performs the tier-override sanity check (override is honored but flagged unsafe when underlying tools are missing), and evaluates `--require-tier` using a tool-prerequisite check (Deep does
|
|
58
|
+
The script (see `src/shared/scripts/skf-detect-tools.py` docstring for the full `DETECT_OUTPUT_SCHEMA`) probes ast-grep / gh / qmd / ccc concurrently with two-step verification for qmd and ccc (binary-identity check + daemon-health check, including the `CocoIndex Code` identity-marker substring check that rejects PATH-shadowing aliases). It applies the 4-rule tier table, performs the tier-override sanity check (override is honored but flagged unsafe when underlying tools are missing), and evaluates `--require-tier` using a tool-prerequisite check (Deep does not subsume Forge+ — Deep does not require ccc). Output is one JSON document on stdout.
|
|
60
59
|
|
|
61
60
|
### 3. Parse Output and Set Context Flags
|
|
62
61
|
|
|
@@ -101,6 +100,7 @@ From `prior` (populated by `--prior-state-from`; all fields null/empty on first
|
|
|
101
100
|
- `{previous_ccc_indexed_path}` ← `prior.previous_ccc_indexed_path`
|
|
102
101
|
- `{previous_ccc_last_indexed}` ← `prior.previous_ccc_last_indexed`
|
|
103
102
|
- `{previous_ccc_staleness_threshold_hours}` ← `prior.previous_ccc_staleness_threshold_hours`
|
|
103
|
+
- `{ccc_index_fresh}` ← `prior.ccc_index_fresh` (boolean; the script's deterministic freshness verdict — prior index covers this project AND status was fresh/created AND `last_indexed` is within the staleness threshold of now. Step 1b branches on this directly instead of doing timestamp math.)
|
|
104
104
|
|
|
105
105
|
From `deltas` (computed by the script from current tools + prior; eliminates LLM-side set arithmetic in the report banner):
|
|
106
106
|
|
|
@@ -108,8 +108,6 @@ From `deltas` (computed by the script from current tools + prior; eliminates LLM
|
|
|
108
108
|
- `{tools_removed}` ← `deltas.tools_removed` (the list)
|
|
109
109
|
- `{tier_changed}` ← `deltas.tier_changed` (boolean)
|
|
110
110
|
|
|
111
|
-
**The script is the source of truth.** Every tier-rules edge case (override-honored-but-unsafe, Deep-doesn't-subsume-Forge+, qmd-binary-vs-daemon distinction, ccc-identity-marker rejection of foreign binaries) is locked into the test suite at `test/test-skf-detect-tools.py`. Substituting prose-driven logic for the script's output here will cause subtle re-run regressions that the prompt's prose is no longer detailed enough to catch.
|
|
112
|
-
|
|
113
111
|
### 4. Auto-Proceed
|
|
114
112
|
|
|
115
113
|
After context flags are populated, display "**Proceeding to CCC index check...**", then load `{nextStepFile}`, read it fully, and execute it.
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
tierRulesData: 'references/tier-rules.md'
|
|
3
3
|
nextStepFile: 'health-check.md'
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# this script exists to prevent).
|
|
4
|
+
# `{emitEnvelopeHelper}` = first existing path in `{emitEnvelopeProbeOrder}`;
|
|
5
|
+
# halt if neither exists when the headless envelope must be emitted. The script
|
|
6
|
+
# is the source of truth for the SKF_SETUP_RESULT_JSON contract — do not
|
|
7
|
+
# inline-render the envelope from prose (LLM schema drift is the bug this
|
|
8
|
+
# script exists to prevent).
|
|
10
9
|
emitEnvelopeProbeOrder:
|
|
11
10
|
- '{project-root}/_bmad/skf/shared/scripts/skf-emit-result-envelope.py'
|
|
12
11
|
- '{project-root}/src/shared/scripts/skf-emit-result-envelope.py'
|
|
@@ -27,19 +26,13 @@ Display the forge status report with positive capability framing, surface tier c
|
|
|
27
26
|
- Do not list tools that are not available
|
|
28
27
|
- Use tier capability descriptions from tier-rules.md
|
|
29
28
|
- Never inline-render the envelope JSON — the script owns the schema; drift breaks pipelines
|
|
30
|
-
- Chains to the local health-check step via `{nextStepFile}` after completion — the user-facing status report is
|
|
31
|
-
|
|
32
|
-
## Headless Mode Display Rule
|
|
33
|
-
|
|
34
|
-
When `{headless_mode}` is `true` OR `{quiet_mode}` is `true`, sections 2 (the human-readable banner) and 3 (the REQUIRED TIER NOT MET human prose block) are **skipped entirely**. The single line of stdout in those modes is the `SKF_SETUP_RESULT_JSON: {…}` envelope from section 4 — every signal the human banner would have surfaced is already in the envelope (`tier`, `previous_tier`, `tier_changed`, `tools`, `tools_added`, `tools_removed`, `files_written`, `tier_override_*`, `require_tier_satisfied`, `warnings`, `error`). Parent skills, CI pipelines, and expert re-runners consume the envelope alone; they should not be forced to scroll past 30 lines of ASCII-art they cannot use.
|
|
35
|
-
|
|
36
|
-
When both `{headless_mode}` and `{quiet_mode}` are `false`, sections 2 and 3 display normally and section 4 is skipped (interactive runs read the human banner; envelope emission would just be log noise).
|
|
29
|
+
- Chains to the local health-check step via `{nextStepFile}` after completion — the user-facing status report is not the terminal step
|
|
37
30
|
|
|
38
31
|
## MANDATORY SEQUENCE
|
|
39
32
|
|
|
40
33
|
### 1. Load Capability Descriptions
|
|
41
34
|
|
|
42
|
-
Load and read {tierRulesData} for the tier capability descriptions and re-run messages. Needed by section 2
|
|
35
|
+
Load and read {tierRulesData} for the tier capability descriptions and re-run messages. Needed by section 2.
|
|
43
36
|
|
|
44
37
|
### 2. Display Forge Status Report (skip when `{headless_mode}` or `{quiet_mode}` is true)
|
|
45
38
|
|
|
@@ -54,7 +47,7 @@ Load and read {tierRulesData} for the tier capability descriptions and re-run me
|
|
|
54
47
|
{tier capability description from tier-rules.md}
|
|
55
48
|
|
|
56
49
|
Tools Detected:
|
|
57
|
-
{for each tool that is available, show: tool name — version}
|
|
50
|
+
{for each tool that is available, show: tool name — version. ccc exposes no version string, so for ccc show its daemon health instead: ccc — daemon {ccc_daemon} (e.g. "ccc — daemon healthy")}
|
|
58
51
|
{if no tools are available: (none yet — see "Climb to next tier" below)}
|
|
59
52
|
|
|
60
53
|
{if calculated_tier is not Deep:}
|
|
@@ -64,7 +57,7 @@ Load and read {tierRulesData} for the tier capability descriptions and re-run me
|
|
|
64
57
|
{if tools.ast_grep and not tools.gh_cli: - Install GitHub CLI (https://cli.github.com) — required for Deep tier (cross-repository synthesis)}
|
|
65
58
|
{if tools.ast_grep and not tools.qmd and qmd_status is "absent": - Install qmd (https://github.com/tobi/qmd) — required for Deep tier (knowledge search)}
|
|
66
59
|
{if tools.ast_grep and not tools.qmd and qmd_status is "daemon_stopped": - Start the qmd daemon (already installed) — run `qmd start` (or your distribution's qmd service command) to unlock Deep tier (knowledge search)}
|
|
67
|
-
{if tools.ccc and ccc_daemon is "error": - The ccc daemon is reporting errors — run `ccc doctor` to diagnose. CCC index will fail until resolved
|
|
60
|
+
{if tools.ccc and ccc_daemon is "error": - The ccc daemon is reporting errors — run `ccc doctor` to diagnose. CCC index will fail until resolved}
|
|
68
61
|
{end if}
|
|
69
62
|
|
|
70
63
|
{if hygiene_result is "completed":}
|
|
@@ -122,6 +115,9 @@ Load and read {tierRulesData} for the tier capability descriptions and re-run me
|
|
|
122
115
|
rely on the missing tool(s) will fail at runtime. Install the missing tool(s) or remove
|
|
123
116
|
the override from preferences.yaml.
|
|
124
117
|
|
|
118
|
+
{if {previous_tier} is null:}
|
|
119
|
+
Initial detection — {calculated_tier} tier established.
|
|
120
|
+
|
|
125
121
|
{if {tier_changed} is true:}
|
|
126
122
|
{appropriate upgrade/downgrade message from tier-rules.md}
|
|
127
123
|
|
|
@@ -129,7 +125,7 @@ Load and read {tierRulesData} for the tier capability descriptions and re-run me
|
|
|
129
125
|
{same-tier message from tier-rules.md}
|
|
130
126
|
{if preferences_yaml_created is false and (ccc_index_result is "fresh" or ccc_index_result is "none" or ccc_index_result is "skipped"): Nothing changed — your preferences were left untouched and the index was already current. You're good.}
|
|
131
127
|
|
|
132
|
-
{if {tier_changed} is false and ({tools_added} or {tools_removed} is non-empty):}
|
|
128
|
+
{if {tier_changed} is false and ({tools_added} or {tools_removed} is non-empty) and {previous_tier} is non-null:}
|
|
133
129
|
Tier unchanged: {calculated_tier}.
|
|
134
130
|
{if {tools_added} non-empty:} Newly detected: {comma-separated tool names from tools_added}{if ccc was added and tier is Deep: " — ccc enhances Deep tier transparently."}
|
|
135
131
|
{if {tools_removed} non-empty:} No longer detected: {comma-separated tool names from tools_removed} — re-install to restore those capabilities.
|
|
@@ -144,15 +140,9 @@ Load and read {tierRulesData} for the tier capability descriptions and re-run me
|
|
|
144
140
|
|
|
145
141
|
All re-run-delta context flags (`{tools_added}`, `{tools_removed}`, `{tier_changed}`) come from the detector's `deltas` block bound in stage 1 — no LLM-side recomputation, no set arithmetic in prose.
|
|
146
142
|
|
|
147
|
-
**Tool display rules:**
|
|
148
|
-
|
|
149
|
-
- Only show tools that ARE available with their version strings
|
|
150
|
-
- Do NOT list unavailable tools
|
|
151
|
-
- Do NOT show a "missing" column or section
|
|
152
|
-
|
|
153
143
|
### 3. Display Required-Tier Failure Block (when applicable; skip when `{headless_mode}` or `{quiet_mode}` is true)
|
|
154
144
|
|
|
155
|
-
If `{require_tier_satisfied}` is `false
|
|
145
|
+
If `{require_tier_satisfied}` is `false`, display this block immediately after the status report (the heading gate already handles the headless/quiet skip).
|
|
156
146
|
|
|
157
147
|
When the block does fire (interactive run with require-tier failure):
|
|
158
148
|
|
|
@@ -170,11 +160,9 @@ When the block does fire (interactive run with require-tier failure):
|
|
|
170
160
|
═══════════════════════════════════════
|
|
171
161
|
```
|
|
172
162
|
|
|
173
|
-
This block exists to make pipeline failures visible without the operator parsing the JSON envelope.
|
|
174
|
-
|
|
175
163
|
### 4. Emit Headless JSON Envelope
|
|
176
164
|
|
|
177
|
-
When `{headless_mode}` is `true` OR `{quiet_mode}` is `true`, build the context payload from this step's accumulated flags and forward it to `{emitEnvelopeHelper}` on stdin. Invoke via `uv run
|
|
165
|
+
When `{headless_mode}` is `true` OR `{quiet_mode}` is `true`, build the context payload from this step's accumulated flags and forward it to `{emitEnvelopeHelper}` on stdin. Invoke via `uv run`. The script computes derived fields (`tools_added`, `tools_removed`, `tier_changed`, `warnings`), validates the assembled envelope against the JSON Schema at `src/shared/scripts/schemas/skf-setup-result-envelope.v1.json`, and emits the single prefixed line `SKF_SETUP_RESULT_JSON: {…}` on stdout.
|
|
178
166
|
|
|
179
167
|
```bash
|
|
180
168
|
echo '{
|
|
@@ -212,10 +200,13 @@ echo '{
|
|
|
212
200
|
|
|
213
201
|
The script's documented context-payload shape (see `src/shared/scripts/skf-emit-result-envelope.py` docstring) tolerates two `tools` shapes — bare booleans OR `skf-detect-tools.py`'s `{key: {available: bool, ...}}` output — so either step 1's normalized booleans OR the raw detect-tools output forwarded as-is will produce the correct envelope.
|
|
214
202
|
|
|
215
|
-
**If the script exits non-zero:** the assembled envelope failed schema validation, which means a context flag from an earlier step is malformed. Surface the error to stderr and continue (
|
|
216
|
-
|
|
217
|
-
**When both `{headless_mode}` and `{quiet_mode}` are `false`, do NOT invoke the script** — interactive runs read the human-readable banner.
|
|
203
|
+
**If the script exits non-zero:** the assembled envelope failed schema validation, which means a context flag from an earlier step is malformed. Surface the error to stderr and continue (missing JSON envelope on a headless run is a degraded but non-fatal state — the pipeline observer will see no envelope and treat that as "agent did not complete cleanly").
|
|
218
204
|
|
|
219
205
|
### 5. Chain to Health Check
|
|
220
206
|
|
|
221
|
-
After the forge status report (and any failure block + JSON envelope) has been displayed
|
|
207
|
+
After the forge status report (and any failure block + JSON envelope) has been displayed:
|
|
208
|
+
|
|
209
|
+
- If `{require_tier_satisfied}` is `false`, halt the workflow here without chaining to step 5. The tier miss is terminal; `{onCompleteCommand}` does not fire on a failed run.
|
|
210
|
+
- Otherwise the forge is fully configured. If `{onCompleteCommand}` (resolved from `workflow.on_complete` at activation) is non-empty, execute it now — this is the workflow's terminal skill-specific action (e.g. trigger the first index build or notify an onboarding channel); in headless, log the action. Then load `{nextStepFile}`, read it fully, and execute it.
|
|
211
|
+
|
|
212
|
+
The health-check step is the true terminal step on success — do not stop after the report on a passing run even though it reads as final. Step 5 in turn delegates to `shared/health-check.md`; after that returns, the setup workflow is fully done.
|