skf-analyze-source
Discover what to skill in a large repo and produce recommended skill briefs. Use when the user requests to "analyze source for skills" or "discover skill opportunities."
Install
npx skills add https://github.com/armelhbobdad/bmad-module-skill-forge/tree/main/src/skf-analyze-source
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install armelhbobdad-bmad-module-skill-forge@llmmart
git clone https://github.com/armelhbobdad/bmad-module-skill-forge.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole armelhbobdad/bmad-module-skill-forge collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Analyze Source
Overview
Analyzes a large repo or multi-service project to identify discrete skillable units, map exports and integration points, and produce recommended skill-brief.yaml files as the primary entry point for brownfield onboarding. The analysis must be thorough enough to produce actionable briefs, but scoped enough to avoid overwhelming the user with false positives. Scanning depth adapts to forge tier — Quick (file structure), Forge (AST), Forge+ (AST + CCC semantic pre-ranking), Deep (AST+QMD).
Conventions
- Bare paths (e.g.
references/<name>.md) resolve from the skill root. references/holds prompt content carved out of SKILL.md (workflow stages chained via frontmatternextStepFile, plus static reference docs);scripts/andassets/hold deterministic helpers and templates.{skill-root}resolves to this skill's installed directory (wherecustomize.tomllives, if present).{project-root}-prefixed paths resolve from the project working directory.{skill-name}resolves to the skill directory's basename.
Role
You are a source code analyst and decomposition architect collaborating with a developer onboarding an existing project, pairing your codebase-analysis and skill-scoping expertise with their domain knowledge.
Workflow Rules
These rules apply to every step in this workflow:
- Only load one step file at a time — never preload future steps
- Always communicate in
{communication_language}(the language for user-facing prose). Written artifact text — the per-unit recommendationdescriptionandscope.notespersisted intoskill-brief.yaml— is in{document_output_language}; per-step rules call this out where it applies. The two values may be the same. - If
{headless_mode}is true, auto-proceed through confirmation gates with their default action and log each auto-decision
Stages
| # | Step | File | Auto-proceed | Condition |
|---|---|---|---|---|
| 1 | Initialize | references/init.md | Yes | Always |
| 1a | Auto-Scope | references/step-auto-scope.md | Yes | [auto] mode only — bypasses steps 2–6; owns pin resolution, coexistence detection, and the docs-only short-circuit |
| 1b | Continue (session resume) | references/continue.md | Yes | Always |
| 2 | Scan Project | references/scan-project.md | No (confirm) | Interactive mode only |
| 3 | Identify Units | references/identify-units.md | No (confirm) | Interactive mode only |
| 4 | Map & Detect | references/map-and-detect.md | Yes | Interactive mode only |
| 5 | Recommend | references/recommend.md | No (confirm) | Interactive mode only |
| 6 | Generate Briefs | references/generate-briefs.md | No (confirm) | Interactive mode only |
| 7 | Workflow Health Check | references/health-check.md | Yes | Always |
Auto mode path: With [auto] present, init routes directly to step 1a. Step 1a may confirm N > 1 units (a monorepo splits into N briefs / N brief_paths, or merges to one), and routes docs-only targets to references/auto-docs-only.md.
Shape detection reference: references/step-shape-detect.md — loaded by step 1a as a reference doc (not a chained step).
Invocation Contract
| Aspect | Detail |
|---|---|
| Inputs | project_path [required], scope_hint [optional]. project_path is a GitHub repo URL or a local filesystem path. In [auto] mode it may also be a documentation URL — step 1a classifies the URL type and routes docs-only targets (see Stages row 1a). |
| Headless inputs | --project-path <path> (skip Step 1 project-path prompt; in [auto] mode also accepts documentation URLs for docs-only mode), --scope-hint <text> (skip Step 1 scope-hint prompt), --intent-hint <text> (pre-supply analysis intent; drives recommendation ranking in Step 5), --pin <version> ([auto] mode only — pin to a specific version tag or branch; accepts semver tags, git tags, and branch names; when absent, resolves to the latest release tag; interactive/headless runs use --target-refs/--target-ref instead) |
| Headless flag | --headless / -H flips every confirm gate to auto-proceed |
| Auto flag | [auto] bracket modifier — activates auto-scope mode (step 1a; see Auto mode path above). Pipelines pass this as AN[auto]. Requires --project-path. |
| Gates | steps 2/3/5: Confirm Gate [C]; step 6: Confirm Gate [Y] (write briefs) — all skipped in auto mode |
| Outputs | analysis-report.md, skill-brief.yaml files (one per recommended unit); final SKF_ANALYZE_RESULT_JSON line on stdout when {headless_mode} is true. In auto mode, the envelope includes "mode":"auto". |
| Headless | All gates auto-resolve with default action when {headless_mode} is true |
| Exit codes | See references/headless-contract.md |
Headless Result Contract
Headless/pipeline runs emit a single-line SKF_ANALYZE_RESULT_JSON envelope — on stdout for the terminal success path (step 6 or step 1a), on stderr with status: "error" for every HARD HALT — and exit with a stable code per failure class. The envelope shape, the halt_reason enum, and the exit-code table live in references/headless-contract.md; step files emit the concrete instance at each site.
On Activation
Load config from
{project-root}/_bmad/skf/config.yamland resolve:project_name,output_folder,user_name,communication_language,document_output_language,forge_data_folder,skills_output_folder,sidecar_path- If the config cannot be loaded, HARD HALT with exit code 2 (
input-missing) perreferences/headless-contract.md— the workflow has no forge context to run against.
Resolve
{headless_mode}: true if--headlessor-Hwas passed as an argument, or ifheadless_mode: truein preferences.yaml. Default: false.Resolve workflow customization. Run:
python3 {project-root}/_bmad/scripts/resolve_customization.py \ --skill {skill-root} --key workflowThe 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>.tomlunder{project-root}— team overrides (committed)_bmad/custom/<skill-name>.user.tomlunder{project-root}— personal overrides (gitignored)
If the script fails or is missing, fall back to reading
{skill-root}/customize.tomldirectly — the bundled defaults are an empty string for each path scalar.Apply the path-scalar fallback now, so stage files reference the resolved variable with no conditional at the usage site. For each scalar, if the merged value is empty or absent, use the bundled default:
{unitDetectionHeuristicsPath}←workflow.unit_detection_heuristics_pathif non-empty, elsereferences/unit-detection-heuristics.md{briefSchemaPath}←workflow.brief_schema_pathif non-empty, elseassets/skill-brief-schema.md{analysisReportTemplatePath}←workflow.analysis_report_template_pathif non-empty, elsetemplates/analysis-report-template.md{onCompleteCommand}←workflow.on_completeif non-empty, else empty string (hook invocation skipped)
Stash all four as workflow-context variables. A non-empty value lets an org swap in a house-style copy (or wire a pipeline hook) without forking the skill.
Apply the array surfaces too: run
workflow.activation_steps_prependnow, treatworkflow.persistent_factsas standing context for the run (file:-prefixed entries load their file/glob contents as facts), then runworkflow.activation_steps_appendafter activation.Load, read the full file, and then execute
references/init.mdto begin the workflow.
Files (bmad-module-skill-forge)
-
assets
-
skill-brief-schema.md 12.9 KB
# Skill Brief Schema ## Purpose Defines the output contract for skill-brief.yaml files generated by analyze-source. Each generated brief must conform to this schema to be consumable by brief-skill and create-skill downstream workflows. ## Required Fields | Field | Type | Constraint | Description | |-------------|--------|--------------------------------------------------|-----------------------------------------------------------------------------| | name | string | kebab-case `[a-z0-9-]+` | Unique skill identifier | | version | string | Semantic version (`X.Y.Z` or `X.Y.Z-prerelease`) | Auto-detect from source (see Version Detection below), fall back to `1.0.0`. **Side effect on remote sources:** `skf-create-skill` treats `version` as an **implicit** `target_version` hint when `target_version` itself is absent — it will try to resolve `{version}` or `v{version}` to a git tag before cloning and fall back to HEAD with a warning if no tag matches. See `skf-create-skill/references/source-resolution-protocols.md` → "Implicit Tag Resolution". | | source_repo | string | GitHub URL or local path | Repository or project root (optional when `source_type: "docs-only"`) | | language | string | Recognized language | Primary programming language | | scope | object | See Scope Object below | Boundary definition | | description | string | 1-3 sentences | What the skill covers. Must contain a literal `Use when` clause — validators test for that exact phrase. | | forge_tier | string | `Quick` / `Forge` / `Forge+` / `Deep` | Inherited from forge-tier.yaml (Title Case) | | created | string | ISO date `YYYY-MM-DD` | Generation date | | created_by | string | user_name from config | Who generated the brief | ## Optional Fields | Field | Type | Constraint | Description | |--------------------|--------|--------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | source_type | string | `source` or `docs-only` | Default `source`. When `docs-only`: `source_repo` optional, `doc_urls` required | | doc_urls | array | `{url, label, source?}` objects | Documentation URLs for T3 content. Required when `source_type: "docs-only"`. Optional `source` provenance enum: `language-registry` (registry-guaranteed corpus) \| `readme-detection` \| `homepage` \| `pages-api` \| `docs-folder` | | `scripts_intent` | string | `detect` / `none` / free-text | Describes whether scripts should be extracted. Values: `detect` (auto-detect from source — default when absent), `none` (skip scripts), or a free-text description of expected scripts (e.g., "CLI validation tools in bin/"). | | `assets_intent` | string | `detect` / `none` / free-text | Describes whether assets should be extracted. Values: `detect` (auto-detect from source — default when absent), `none` (skip assets), or a free-text description of expected assets (e.g., "JSON schemas in schemas/"). | | `target_version` | string | Semantic version (`X.Y.Z` or `X.Y.Z-prerelease`) | User-specified target version. When present, overrides auto-detection and becomes the skill's version. Recommended for docs-only skills where auto-detection is unavailable. | | `target_ref` | string | Git ref (tag or branch) | Optional. Explicit git ref used verbatim as the resolved `source_ref`, bypassing version-to-tag matching. Escape hatch for monorepo crate tags whose prefix differs from the skill name (e.g. tag `livekit/v0.7.42` for skill `livekit-rust`). Remote sources only. Mutually exclusive with `constituent_refs`. | | `constituent_refs` | object | map of path→ref strings | Optional. Per-constituent git ref overrides for composite (multi-repo or multi-ref) sources. Keys must match entries in the analyze-source `project_paths` array. When absent, a single `target_ref` (or auto-detected ref) applies to all paths. Mutually exclusive with `target_ref`. | | `source_authority` | string | `official` / `community` / `internal` | Default `community`. Set to `official` only when the skill creator is the library maintainer. Forced to `community` when `source_type: "docs-only"`. | | `source_ref` | string | Git ref (tag/branch/HEAD) | Resolved git ref used for source access. Set automatically during tag resolution — do not set manually. | | `scope.tier_a_include` | array | Glob patterns (min 1, non-empty) | Optional narrower tier-A include list for a curated-subset skill — stratified monorepo packages, single-crate `specific-modules` scopes, and multi-entry exports-map packages. When present, `skf-test-skill` re-derives the coverage denominator from this list instead of the coarse `scope.include`. **It also exempts the brief from the denominator deflation guard**, so supply it only when it genuinely narrows the surface — never as an empty list, and never as a way to widen. See `skf-test-skill/references/source-access-protocol.md`. | When `source_type: "docs-only"`: - `source_repo` becomes optional (set to doc site URL for reference) - `doc_urls` must have at least one entry - `source_authority` is forced to `community` (T3 external documentation cannot be `official`) - All extracted content gets `[EXT:{url}]` citations ## Version Detection During brief generation, attempt to auto-detect the source version before defaulting to `"1.0.0"`. Check the first matching file in the source: - **Python:** `pyproject.toml` `[project] version` (static) → if `dynamic = ["version"]`, check `__init__.py` for `__version__` → `_version.py` if exists → `setup.py` `version=` → `git describe --tags --abbrev=0` - **JavaScript/TypeScript:** root `package.json` (`"version"`) → if root has `"private": true` with a `"workspaces"` array or lacks a `"version"` field, fall back to a primary workspace package's `package.json` (e.g., `code/core/package.json`, or the first matching `packages/*/package.json`). For GitHub sources, prefer `gh api repos/{owner}/{repo}/releases/latest` → `tag_name` when a non-pre-release tag exists, over a default-branch pre-release. Treat a version containing `-alpha`, `-beta`, `-rc`, `-next`, or `-canary` as a pre-release. - **Rust:** `Cargo.toml` `[package] version` (static) → if `version = { workspace = true }`, resolve from workspace root `Cargo.toml` → `git describe --tags --abbrev=0` - **Go:** version tag from `go.mod` or `git describe --tags --abbrev=0` If the source is a remote GitHub repo, use `gh api repos/{owner}/{repo}/contents/{file}` to read the version file. If the source is local, read the file directly. If detection succeeds, use the detected version. If it fails or returns a non-semver value, fall back to `"1.0.0"`. The create-skill workflow (extract) also performs version reconciliation at extraction time — if the source version has changed since the brief was created, the extraction step warns and uses the source version. **Target version override:** When `target_version` is present in the brief, it takes precedence over auto-detection. Auto-detection still runs for informational purposes (displayed as "Detected version" alongside the user-specified "Target version"), but the `target_version` value is used as the brief's `version` field. This is particularly useful for docs-only skills (where no package manifest exists) and when the user wants to compile a skill for a specific older version. **Pre-release handling:** If the detected version contains a pre-release tag (e.g., `1.0.0-beta.0`, `2.0.0-rc.1`), preserve it as-is. Pre-release tags are valid semver and must not be stripped. When comparing versions during reconciliation, use semver-aware comparison that respects pre-release ordering. ## Scope Object ```yaml scope: type: full-library | specific-modules | public-api | component-library | reference-app | docs-only include: - 'src/**/*.ts' # At least one required exclude: - 'src/**/*.test.ts' # Optional # Optional: narrower tier-A include list for stratified-scope monorepos # and reference-app pattern surfaces (refined later by skf-brief-skill) # tier_a_include: # - 'code/core/src/manager-api/**' notes: 'Optional rationale for scope decision' # Additional fields when scope.type is "component-library": # registry_path: "path/to/registry.ts" # Optional — auto-detected if omitted # ui_variants: # Optional — design system variants # - name: "shadcnui" # package: "packages/components/react-shadcn" # demo_patterns: # Optional — auto-detected if omitted # - "**/demo/**" # - "**/*.stories.*" ``` ### Scope Types | Type | Use When | |-------------------|--------------------------------------------------------------------------------------| | full-library | Entire codebase of a unit | | specific-modules | Selected components or packages | | public-api | Only exported interfaces | | component-library | UI component libraries with registries, props-based APIs, and design system variants | | reference-app | Whole app whose value is wiring patterns, not public exports (embedded sidecars, CLI demos, integration-pattern demonstrators). Also the home for **language / spec references** — engine- or spec-versioned query languages, grammars, or DSLs (e.g. SurrealQL) whose value is construct idioms, not exports. These have no separate scope type; they ride `reference-app` as a sub-shape (see `skf-create-skill` Language / spec-reference sub-shape) | | docs-only | When source_type is docs-only — no source code available, all content from doc_urls | > **Documented vs source language.** The `language` field records the language the skill *documents*. For a language / spec reference this may differ from the source language it is extracted from — e.g. a SurrealQL reference extracted from a Rust engine records `language: surrealql`, not `rust`. Use the documented language and a matching code-fence default (e.g. ` ```surql `) throughout the brief. ## YAML Template ```yaml name: '{unit-name}' version: '{detected-version or 1.0.0}' source_type: 'source' source_repo: '{project_path}' language: '{detected_language}' scope: type: '{detected_scope_type}' include: - '{include_pattern}' exclude: - '{exclude_pattern}' notes: '{scope_rationale}' description: '{1-3 sentence description}' forge_tier: '{Quick|Forge|Forge+|Deep}' created: '{current_date}' created_by: '{user_name}' ``` ## Validation Rules 1. **Name uniqueness**: No duplicate names within forge_data_folder 2. **Source accessibility**: source_repo path must exist or be reachable 3. **Language recognized**: Must be a known programming language 4. **Scope type valid**: Must match one of the six defined types (full-library, specific-modules, public-api, component-library, reference-app, docs-only) 5. **Include patterns**: At least one include glob pattern required (exception: `docs-only` scope, where include patterns are optional since no source code is available) 6. **Forge tier match**: Must match value in forge-tier.yaml 7. **Docs-only mode**: When `source_type: "docs-only"`, `doc_urls` required (>= 1), `source_repo` optional 8. Each `doc_urls` entry must have a valid `url` field ## Output Location Each skill-brief.yaml is written to: ``` {forge_data_folder}/{unit-name}/skill-brief.yaml ```
-
-
references
-
auto-docs-only.md 4.6 KB
--- nextStepFile: 'health-check.md' outputFile: '{forge_data_folder}/analyze-source-report-{project_name}.md' writeSkillBriefProbeOrder: - '{project-root}/_bmad/skf/shared/scripts/skf-write-skill-brief.py' - '{project-root}/src/shared/scripts/skf-write-skill-brief.py' --- <!-- Config: communicate in {communication_language}. --> # Step 1a §0a: Docs-Only Short-Circuit Reached from `step-auto-scope.md` §0c when the target is a documentation URL (not a GitHub repo or local path). It validates the URL, writes a minimal brief and analysis report, emits the result envelope, and chains directly to health-check — the standard auto-scope body (§1 through §11 in `step-auto-scope.md`) never runs for a docs-only target. `{coexistence_suffix}`, `{forge_tier}`, `{user_name}`, `{current_date}`, and the classification set upstream in §0/§0c carry into this file. ## MANDATORY SEQUENCE — §0a ### 1. Validate URL reachability ```bash curl -sI --max-time 5 {url} ``` - On **2xx/3xx** response: URL is reachable. Continue. - On **4xx/5xx**, DNS failure, or timeout: HARD HALT with exit code 3 (`resolution-failure`). Emit error message: `"Documentation URL unreachable: {url} — {status or error}"`, then the error envelope (shape in `references/headless-contract.md`) with `exit_code: 3`, `halt_reason: "resolution-failure"`, `mode: "auto"`, `source_type: "docs-only"`. ### 2. Derive skill name from URL domain Extract the hostname from the URL (e.g., `docs.example.com` from `https://docs.example.com/guide/intro`), convert to kebab-case (replace `.` with `-`), yielding e.g. `docs-example-com`. If `{coexistence_suffix}` is non-empty, append it to the skill name (e.g., `docs-example-com-wiki`). ### 3. Write analysis report Update {outputFile} with docs-only results. If the write fails, HARD HALT with exit code 4 (`write-failed`) per `references/headless-contract.md` (applies equally to the brief write in §4 and the result contract in §6). **Update frontmatter:** ```yaml stepsCompleted: ['init', 'auto-scope'] lastStep: 'auto-scope' source_type: docs-only confirmed_units: - name: '{skill_name}' shape: 'docs-only' confidence: 1.0 export_count: 0 package_count: 0 ``` **Append body section:** ```markdown ## Auto-Scope Analysis **Mode:** auto (docs-only short-circuit) **Source Type:** docs-only **Documentation URL:** {url} **Skill Name:** {skill_name} ``` ### 4. Write skill brief via canonical writer **Resolve `{writeSkillBriefHelper}`** from `{writeSkillBriefProbeOrder}`; first existing path wins; HALT if neither resolves. Create directory `{forge_data_folder}/{skill_name}/` if it does not exist. Pipe the flat context JSON below into the resolved writer with the `--from-flat` flag: ```json { "name": "{skill_name}", "target_version": null, "detected_version": null, "source_type": "docs-only", "source_repo": "{url}", "language": "documentation", "description": "Skill created from documentation at {url}", "forge_tier": "{forge_tier}", "created": "{current_date}", "created_by": "{user_name}", "scope_type": "docs-only", "scope_include": [], "scope_exclude": [], "scope_notes": "Docs-only skill created from documentation URL", "scope_rationale": null, "scope_tier_a_include": null, "scope_amendments": null, "doc_urls": [{"url": "{url}", "label": "Primary Documentation"}], "scripts_intent": null, "assets_intent": null, "source_authority": "community", "target_ref": null, "source_ref": null, "version_resolved": "1.0.0" } ``` ```bash echo '<context-json>' | uv run {writeSkillBriefHelper} write --target {forge_data_folder}/{skill_name}/skill-brief.yaml --from-flat ``` ### 5. Emit success envelope ``` SKF_ANALYZE_RESULT_JSON: {"status":"success","report_path":"{outputFile_path}","brief_paths":["{brief_path}"],"unit_counts":{"confirmed":1,"skipped":0,"maybe":0},"exit_code":0,"halt_reason":null,"mode":"auto","source_type":"docs-only"} ``` If `{coexistence_suffix}` is non-empty (i.e., [A]longside was selected in §0c), include `"coexistence":"alongside"` in the envelope. The `source_type` field signals downstream consumers (BS) to skip repo-based enrichment. ### 6. Write result contract Write the result contract per `shared/references/output-contract-schema.md`: the per-run record and latest copy, same as `step-auto-scope.md` §10. If `{onCompleteCommand}` is non-empty, invoke it now with `--result-path={result_json_path}`. ### 7. Chain to health check Load, read fully, then execute {nextStepFile} to run the shared workflow health check. -
continue.md 2.9 KB
--- outputFile: '{forge_data_folder}/analyze-source-report-{project_name}.md' nextStepOptions: step 1a: 'step-auto-scope.md' step 2: 'scan-project.md' step 3: 'identify-units.md' step 4: 'map-and-detect.md' step 5: 'recommend.md' step 6: 'generate-briefs.md' step 7: 'health-check.md' --- <!-- Config: communicate in {communication_language}. --> # Step 1b: Continue Analysis ## STEP GOAL: To resume the analyze-source workflow from where it was left off in a previous session by reading the analysis report's progress state and routing to the correct next step. ## Rules - Focus only on reading state and routing — do not perform any analysis - Do not re-run completed steps - Present progress summary to user before resuming ## MANDATORY SEQUENCE ### 1. Welcome Back "**Welcome back!** Let me check where we left off with the source analysis..." ### 2. Read Progress State Load {outputFile} and read frontmatter: - `stepsCompleted` array - `project_paths` - `project_name` - `forge_tier` - `existing_skills` - `confirmed_units` - `mode` — `'auto'` when the report was produced by the auto-scope path; absent or any other value means interactive ### 3. Present Progress Summary "**Analysis Progress for {project_name}:** **Project:** {project_paths} **Forge Tier:** {forge_tier} **Steps Completed:** {list stepsCompleted} **Last Step:** {last entry in stepsCompleted} **Progress:** {For each completed step, summarize what was accomplished — read the relevant sections from the report}" ### 4. Determine Next Step **IF the report's `mode` is `'auto'`** (produced by the auto-scope path; reached here because the resume invocation did not carry `[auto]`): an auto analysis is a single idempotent pass, not a resumable interactive chain. Do not use the interactive table below. - If `auto-scope` is in `stepsCompleted`, the auto analysis already completed — announce "**This auto analysis is already complete.** Would you like to start a new analysis?" and stop. - Otherwise (an auto run interrupted before auto-scope finished), re-enter the auto path: load, read fully, then execute `step-auto-scope.md` (it reads the existing report frontmatter and re-runs cleanly). **STOP HERE.** For interactive reports, map the last completed step to the next step file: | Last Completed | Next Step | |----------------|-----------| | init | scan-project | | scan-project | identify-units | | identify-units | map-and-detect | | map-and-detect | recommend | | recommend | generate-briefs | | generate-briefs | health-check | **IF `health-check` is in `stepsCompleted`:** "**This analysis appears to be complete.** All steps have been finished. Would you like to start a new analysis?" ### 5. Update and Route Update {outputFile} frontmatter: ```yaml lastContinued: '{current_date}' ``` "**Resuming from {next_step_name}...**" Auto-proceed: immediately load, read the entire file, then execute the next incomplete step from {nextStepOptions}. -
generate-briefs.md 12.5 KB
--- outputFile: '{forge_data_folder}/analyze-source-report-{project_name}.md' schemaFile: '{briefSchemaPath}' validateBriefSchemaProbeOrder: - '{project-root}/_bmad/skf/shared/scripts/skf-validate-brief-schema.py' - '{project-root}/src/shared/scripts/skf-validate-brief-schema.py' nextStepFile: 'health-check.md' --- <!-- Config: communicate in {communication_language}. Artifact text in {document_output_language}. --> # Step 6: Generate Briefs ## STEP GOAL: To generate a valid skill-brief.yaml file for each confirmed unit using the schema, write the files to the forge data folder, append generation results to the analysis report, and recommend the appropriate next workflow for each unit — completing the analyze-source workflow. ## Rules - Generate only for units in confirmed_units — no extras, no omissions - Do not modify recommendations or re-ask for confirmations - Every generated field must trace back to data collected in steps 02-05 - Chains to the local health-check step via `{nextStepFile}` after completion — the user-facing summary is not the terminal step ## MANDATORY SEQUENCE ### 1. Load Context Read {outputFile} completely to obtain: - `confirmed_units` from frontmatter (names of units approved in step 05) - `project_paths`, `forge_tier`, `user_name`, `forge_data_folder` from frontmatter - Recommendation cards from "## Recommendations" section (proposed brief fields per unit) - Export map and integration data from prior sections Load {schemaFile} for validation reference. **Guard clause:** If `confirmed_units` is empty, present: "**No confirmed units to generate briefs for.** The analysis is complete with no skill briefs produced. Run analyze-source again with different scope or parameters if needed." Mark workflow complete and halt. ### 2. Generate Skill-Brief YAML Per Unit For each unit in `confirmed_units`, construct a skill-brief.yaml using: **Field mapping:** | Field | Source | |-------|--------| | name | Confirmed name from step 05 recommendation card | | version | Auto-detect from source (see schema Version Detection), fall back to `1.0.0` | | source_repo | `{project_paths[0]}` from frontmatter (or per-unit path if multi-repo) | | language | Language the skill **documents** (primary language detected in step 03). For a language / spec reference this is the *documented* language, which may differ from the source language it is extracted from — e.g. a SurrealQL reference extracted from a Rust engine records `surrealql`, not `rust` (see {schemaFile} "Documented vs source language") | | scope.type | Scope type from step 05 recommendation card | | scope.include | Include patterns from step 05 recommendation card | | scope.exclude | Inferred from heuristics (test files, generated code) | | scope.tier_a_include | Optional — narrower tier-A surface for stratified-scope monorepos and `reference-app` pattern surfaces; usually left to skf-brief-skill to refine | | scope.notes | Rationale from step 05 recommendation card | | description | Description from step 05 recommendation card | | forge_tier | `{forge_tier}` from frontmatter | | created | Current date as a **quoted** string in ISO format `'YYYY-MM-DD'` — quote it so YAML stores it as text, not a parsed date object (the schema, and the §3a gate, require a string) | | created_by | `{user_name}` from frontmatter | ### 3. Validate Each Brief Validation has two parts: a **deterministic schema gate** (authoritative for structure) and **semantic cross-checks** the schema cannot express. Run the schema gate first. **3a. Deterministic schema gate.** Resolve `{validateBriefSchemaHelper}` from `{validateBriefSchemaProbeOrder}` (first existing path wins; HALT with a clear message if no candidate exists). For each generated brief, pipe the *exact* assembled YAML to the helper: ```bash uv run {validateBriefSchemaHelper} - <<'YAML' {assembled-brief-yaml} YAML ``` The script returns JSON `{valid, errors[], warnings[], halt_reason, brief}` — the same validator and contract `skf-brief-skill` runs at consumption time, so a brief that passes here will not be rejected there for structural reasons. Apply the result: - **`valid: false`** — the `errors[]` name the offending field (e.g. a `description` mis-indented under `scope:`, which leaves the required top-level `description` absent). Repair the assembled YAML and re-run the helper until `valid: true`. A brief is written only after it passes this gate. - **`valid: true`** — carry any non-empty `warnings[]` into the §4 preview, then proceed. This catches structural YAML errors where they are created, rather than letting them surface downstream as a HALT in `skf-brief-skill`'s ratify path. **3b. Semantic cross-checks** (not expressible in the JSON schema — apply in addition to 3a): 1. **Name uniqueness** — no duplicate names within the batch or existing skills 2. **Source accessible** — project_path exists 3. **Language recognized** — valid programming language identifier 4. **Forge tier match** — matches forge_tier from config (The non-empty `scope.include` constraint — at least one glob pattern for every scope type except `docs-only` — is enforced deterministically by the §3a gate, so it is not re-checked here.) **If any check fails:** - Document the failure with specific field and reason - Repair (3a structural errors) or present to user for correction (3b semantic issues) before writing — an invalid brief is not written ### 4. Present Generation Preview "**Skill Brief Generation Preview** **Units to generate:** {count} {For each unit:} --- **{unit-name}** → `{forge_data_folder}/{unit-name}/skill-brief.yaml` ```yaml {complete YAML content} ``` --- **Validation:** {all passed / N issues found} {List any validation issues} **Ready to write {count} skill-brief.yaml files.** Confirm to proceed? (Y to write all briefs / N to skip writing but continue to report update / M to modify a specific brief / X to cancel and exit the workflow)" Wait for explicit user confirmation before writing files. **GATE [default: Y]** — If `{headless_mode}` is true: auto-confirm [Y], write all briefs, and log: "headless: auto-write {count} briefs". Do not stall at this gate — it is the deliverable-producing step of a headless/pipeline run. ### 5. Write Files **IF user confirms (Y):** For each confirmed brief: 1. Create directory `{forge_data_folder}/{unit-name}/` if it does not exist 2. Write `skill-brief.yaml` to `{forge_data_folder}/{unit-name}/skill-brief.yaml` — write the exact YAML that passed the §3a schema gate verbatim; do not re-serialize, so the bytes on disk are the bytes that validated 3. Verify the file was written; if the write fails, HARD HALT with exit code 4 (`write-failed`) per `references/headless-contract.md` **IF user modifies (M):** - Ask which brief and what to change - Update the YAML, re-validate, present again - Return to confirmation prompt **IF user skips writing (N):** - Document the skip decision - Skip file writing, proceed to report update **IF user cancels (X):** - HARD HALT with exit code 6 (`user-cancelled`). Emit the error envelope on stderr with `halt_reason: "user-cancelled"`, `brief_paths: []`, and `unit_counts` reflecting the confirmed/skipped/maybe state from step 5 (shape in `references/headless-contract.md`) ### 6. Determine Next Workflow Per Unit For each generated brief, recommend the appropriate next workflow: | Condition | Recommendation | |-----------|---------------| | Brief has `scope.type: full-library` and unit is well-bounded | create-skill — brief is sufficient for direct skill creation | | Brief has `scope.type: component-library` and registry defines boundaries | create-skill — component boundaries defined by registry | | Brief has `scope.type: specific-modules` or scope needs refinement | brief-skill — refine scope before creating skill | | Brief has `scope.type: public-api` or complex interface | brief-skill — detailed scoping needed | | Brief has `scope.type: reference-app` | brief-skill — refine the pattern surface and capture `tier_a_include` before creating skill | | Unit flagged as stack skill candidate | create-stack-skill — after individual skills exist | | Unit flagged as already-skilled | update-skill — refresh existing skill | ### 7. Append to Report Append the complete "## Generation Results" section to {outputFile}: Replace `[Appended by generate-briefs]` with: **Generated Briefs:** | # | Unit Name | Output Path | Validation | Next Workflow | |---|-----------|-------------|------------|---------------| | {n} | {name} | {path} | {pass/fail} | {recommendation} | **Generation Summary:** - Total confirmed units: {count} - Briefs generated: {count} - Briefs skipped/failed: {count} - Stack skill candidates flagged: {count} **Next Steps:** {For each next workflow recommendation, a clear action item} Update {outputFile} frontmatter: ```yaml stepsCompleted: [append 'generate-briefs' to existing array] lastStep: 'generate-briefs' nextWorkflow: '{primary recommendation}' ``` ### 8. Present Summary "**Analyze-Source Summary** **Project:** {project_name} **Forge Tier:** {forge_tier} **Results:** - **Scanned:** {boundary count} boundaries detected - **Identified:** {unit count} qualifying units classified - **Confirmed:** {confirmed count} units approved for brief generation - **Generated:** {brief count} skill-brief.yaml files written **Files Created:** {List each skill-brief.yaml with full path} **Analysis Report:** {outputFile} **Recommended Next Steps:** {For each unit, the recommended next workflow with brief explanation} {If stack skill candidates exist:} **Stack Skill Candidates:** {List candidates with recommendation to run create-stack-skill after individual skills are created} To refine any brief, run the recommended next workflow. To re-analyze with different scope, run analyze-source again." ### 9. Result Contract Write the result contract per `shared/references/output-contract-schema.md`: the per-run record at `{forge_data_folder}/analyze-source-result-{YYYYMMDD-HHmmss}.json` (UTC timestamp, resolution to seconds) and a copy at `{forge_data_folder}/analyze-source-result-latest.json` (stable path for pipeline consumers — copy, not symlink). Include all generated `skill-brief.yaml` paths in `outputs` and brief counts in `summary`. If the per-run record cannot be written, HARD HALT with exit code 4 (`write-failed`) per `references/headless-contract.md`. ### 9a. Emit Result Envelope When `{headless_mode}` is true, emit the `SKF_ANALYZE_RESULT_JSON` envelope on **stdout** — the success signal for the interactive path, alongside §9's on-disk record (both are produced): ``` SKF_ANALYZE_RESULT_JSON: {"status":"success","report_path":"{outputFile_abs_path}","brief_paths":["{brief_path_1}",…,"{brief_path_N}"],"unit_counts":{"confirmed":N,"skipped":N,"maybe":N},"exit_code":0,"halt_reason":null,"mode":"interactive"} ``` - `report_path` — absolute path to {outputFile}. - `brief_paths` — every `skill-brief.yaml` written in §5 (empty array when the user skipped writing with [N]). - `unit_counts` — confirmed/skipped counts from step 5 (`maybe` reserved; see `references/headless-contract.md`). When `{headless_mode}` is false (interactive human run), skip this section — there is no pipeline consumer to signal. ### 9b. On-Complete Hook (pipeline integration) If `{onCompleteCommand}` is non-empty, invoke it now — after the timestamped result JSON and the `analyze-source-result-latest.json` copy have both been written: ``` {onCompleteCommand} --result-path={result_json_path} ``` Where `{result_json_path}` is the absolute path to the freshly written `analyze-source-result-latest.json` (stable path is preferred over the timestamped copy so downstream consumers don't need to discover the timestamp). - On success: log to `workflow_warnings[]` as informational only if the hook emitted stderr (`on_complete hook stderr: …`); otherwise no entry. - On non-zero exit / process error: log to `workflow_warnings[]` (`on_complete hook failed (exit {code}): {stderr_snippet}`). - **Never fail the workflow on hook errors** — the hook is for pipeline integration (Slack, dashboards, CI), not for gating skill-brief production. If `{onCompleteCommand}` is empty, skip this section entirely (default behavior — no hook configured). ### 10. Chain to Health Check After the briefs are written (or skipped per user abort), the report updated, the summary presented, the result contract saved, and the on-complete hook invoked (or skipped per empty `{onCompleteCommand}`), load, read the full file, and execute `{nextStepFile}`. The health-check step is the true terminal step — continue past the summary even though it reads as final. -
headless-contract.md 3.2 KB
# Headless Result Contract & Exit Codes Canonical headless/pipeline contract for skf-analyze-source. Step files emit the envelope and exit with the codes defined here. ## Result Envelope When `{headless_mode}` is true, step 6 (interactive) or step 1a (auto) emits a single-line JSON envelope on **stdout** before chaining to step 7. Every HARD HALT emits the same envelope shape on **stderr** with `status: "error"`, using the exit code and `halt_reason` named at that HALT — so an automator branches on the failure class without grepping message text. ``` SKF_ANALYZE_RESULT_JSON: {"status":"success|error|redirect|skipped","report_path":"…|null","brief_paths":["…"],"unit_counts":{"confirmed":N,"skipped":N,"maybe":N},"exit_code":0,"halt_reason":null,"mode":"interactive|auto"} ``` - `status` — `"success"` on the terminal happy path, `"error"` on any HALT, `"redirect"` when coexistence routes to US (merge), `"skipped"` when the user skips a conflicting target. - `halt_reason` — one of `null` (success), `"input-missing"`, `"resolution-failure"`, `"pin-invalid"`, `"write-failed"`, `"user-cancelled"`. - `exit_code` — matches the table below. - `report_path` — absolute path to the analysis report, or `null` on an early HALT. - `brief_paths` — array of absolute paths to every generated `skill-brief.yaml` (empty array if none). - `unit_counts` — `confirmed` (units approved for briefs) and `skipped` (rejected in step 5, or the skipped target in coexistence §0c) counts; auto mode reports `confirmed:N, skipped:0`. `maybe` is a reserved slot, currently always `0`. - `mode` — `"auto"` when the `[auto]` flag was active, `"interactive"` otherwise. In auto mode the envelope also carries `coexistence` (the §0c decision) and, when a pin resolves, `pinned_ref`/`pinned_version` — `step-auto-scope.md` §0b/§0c define those field semantics. The docs-only path adds `source_type: "docs-only"`. ## Exit Codes Every HARD HALT exits with a stable code so headless automators can branch on the failure class. | Code | Meaning | Raised by | | ---- | -------------------- | ------------------------------------------------------------------------------------------ | | 0 | success / skipped / redirect | step 7 (terminal — health check completion); coexistence `"skipped"`/`"redirect"` statuses from §0c | | 2 | input-missing | On Activation (config.yaml not loadable); step 1 §3 (project path empty/invalid in headless mode); step 1 §2b (auto mode without `--project-path`) | | 3 | resolution-failure | step 1 §2 (`forge-tier.yaml` missing at `{sidecar_path}/forge-tier.yaml`); step 1 §3 (project path does not exist or remote URL inaccessible); step 1a §0a (docs-only URL unreachable); step 1a §0b (`halt_reason: "pin-invalid"` when the supplied `--pin` matches no tag or branch); step 1a §3 (shape detection script error, exit code 2) | | 4 | write-failed | a failed write of the analysis report (step 1 §6 / step 1a §7 / §0a), a `skill-brief.yaml` (step 6 §5 / step 1a §8 / §0a), or the result contract (step 6 §9 / step 1a §10 / §0a) | | 6 | user-cancelled | any interactive menu in steps 2/3/5/6 (user selected `[X]` Cancel and exit) | -
health-check.md 804 B
--- # `shared/health-check.md` resolves relative to the SKF module root # (`{project-root}/_bmad/skf/` when installed, `src/` during development), # NOT relative to this step file. nextStepFile: 'shared/health-check.md' --- <!-- Config: communicate in {communication_language}. --> # Step 7: Workflow Health Check ## STEP GOAL: Chain to the shared workflow self-improvement health check at `{nextStepFile}`. This is the terminal step of analyze-source — after the shared health check completes, the workflow is fully done. ## Rules - No user-facing reports, file writes, or result contracts in this step — those belong in step 6 - Delegate directly to `{nextStepFile}` with no other action or commentary in between ## MANDATORY SEQUENCE Load `{nextStepFile}`, read it fully, then execute it. -
identify-units.md 10.5 KB
--- nextStepFile: 'map-and-detect.md' outputFile: '{forge_data_folder}/analyze-source-report-{project_name}.md' heuristicsFile: '{unitDetectionHeuristicsPath}' disqualifyCandidatesProbeOrder: - '{project-root}/_bmad/skf/shared/scripts/skf-disqualify-candidates.py' - '{project-root}/src/shared/scripts/skf-disqualify-candidates.py' detectLanguageProbeOrder: - '{project-root}/_bmad/skf/shared/scripts/skf-detect-language.py' - '{project-root}/src/shared/scripts/skf-detect-language.py' --- <!-- Config: communicate in {communication_language}. --> # Step 3: Identify Units ## STEP GOAL: To classify each detected boundary from the project scan into discrete skillable units by applying detection heuristics, assigning boundary types and scope types, and filtering out disqualified candidates. ## Rules - Focus only on unit classification — do not map exports or integration points yet - Do not generate skill-brief.yaml in this step - Every classification must cite the detection signals that justify it ## MANDATORY SEQUENCE ### 1. Load Context Read {outputFile} to obtain: - Project Scan results (detected boundaries, manifests, entry points) - `forge_tier` from frontmatter - `existing_skills` from frontmatter Load {heuristicsFile} for classification rules. ### 2. Apply Detection Heuristics **Resolve `{disqualifyCandidatesHelper}`** from `{disqualifyCandidatesProbeOrder}`; first existing path wins. HALT if no candidate exists. For each detected boundary from the scan, apply the classification rules from {heuristicsFile} (loaded in §1): **Step A — Count detection signals:** tally the Strong / Moderate / Weak signals per its Detection Signals tables. **Step B — Classify boundary type** per its Boundary Classification section. (Composite is detected separately in §3b below — not during this initial per-boundary pass.) **Step C — Assign scope type** from that same section for the boundary's type. **Step D — Run deterministic disqualification filter (script):** Run the shared disqualification helper to apply the deterministic subset of the rules from {heuristicsFile} (file-count, LoC, generated-code paths, auto-generated header sentinels). The script collapses what was prose-orchestrated counting + path-substring + header scanning into one deterministic call. 1. **Build the boundaries JSON** from the detected boundaries (one entry per candidate boundary). Use forward-slash paths throughout. Shape: ```json [ {"name": "<unit-name>", "path": "<rel-from-analyzed-source-root (project_paths[0])>", "files": ["<rel-path>", ...]}, ... ] ``` 2. **Invoke the script** via stdin: ```bash uv run {disqualifyCandidatesHelper} filter --boundaries - --source-root {project_paths[0]} ``` piping the boundaries JSON on stdin. `--source-root` is the analyzed-source root (`project_paths[0]`) — the directory the boundaries/manifest scan ran against — not `{project-root}` (the forge workspace), which differs whenever the analyzed target lives outside the forge workspace. The script emits: ```json { "kept": [{"name": "...", "path": "...", "files_count": N, "loc_total": L}, ...], "dropped": [{"name": "...", "reason": "<too-few-files|too-low-loc|generated-code|auto-generated-tag>", "context": {...}}, ...], "stats": {"kept": N, "dropped": N, "by_reason": {"<reason>": N, ...}} } ``` 3. **Parse the JSON result** and stash `kept[]` and `dropped[]` in workflow state for §3 (classification table) and §5 (recommendation summary). The `kept` set is the candidate pool for the boundary-type + scope-type classification that follows; the `dropped` set drives the Disqualification table. **LLM-judged disqualifications (not in script — apply on top of `kept[]`):** - **Pure configuration** — only config files (e.g., `.json`/`.yaml`) with no executable logic - **Test-only** — test utilities with no production code - **Already skilled** — exists in `existing_skills` list (recommend `update-skill` instead) Remove any boundary that fails one of these LLM-judged rules from the working `kept` set and append it to `dropped[]` with the appropriate reason. Reasons recorded by the script (`too-few-files`, `too-low-loc`, `generated-code`, `auto-generated-tag`) are authoritative — do not re-evaluate those rules manually. **Qualification check:** Visually skim the script's `kept`/`dropped` decisions for sanity (e.g., a boundary you expected to qualify that landed in `dropped` — surface the script's `reason` and `context.first_match` to the user in §5 so they can override if the heuristic was wrong for this project). ### 3. Build Unit Classification Table For each candidate that passes disqualification: | # | Unit Name | Path | Boundary Type | Scope Type | Signals | Confidence | Status | |---|-----------|------|---------------|------------|---------|------------|--------| | 1 | {name} | {path} | {type} | {scope} | {signal count: strong/moderate/weak} | {high/medium/low} | {new/already-skilled} | For disqualified candidates, note reason: **Disqualified:** | Path | Reason | |------|--------| | {path} | {disqualification reason} | ### 3b. Detect Composite Unit Merges After building the classification table, apply the Composite Boundary detection heuristic from {heuristicsFile} against the qualifying units: 1. **Scan for merge candidates:** Among the qualifying units (from `kept[]`), find groups of ≥2 Package or Module boundaries that meet either Composite trigger — **Mutual hard dependency** or **Shared integration surface** — as defined in {heuristicsFile}'s Composite Boundary heuristic. 2. **If candidate groups are found**, propose each merge: - Derive a composite name from the common namespace prefix or repo name - List the constituents (boundary names and paths being merged) - State the triggering heuristic and evidence 3. **If no candidate groups are found**, skip to §4. **Merge does not fire for:** Units already flagged as Stack Skill Candidates in step 4 (map-and-detect §5) — those are multi-unit groupings that deliver value *separately* but are *also* useful together. Composite merges are for units that are *only* useful together (the key distinction). If a group of units is independently useful but commonly combined, it remains as separate units and is flagged as a stack skill candidate later. **This step is a recommendation — not automatic.** Merges are presented to the user in §5 for confirmation (see "Composite Merge Proposals" below). If the user rejects a merge, the constituents remain as separate units in the classification table. ### 4. Detect Primary Language Per Unit For each qualifying unit (including any approved composites from §3b), detect the primary language deterministically via the shared helper — the single source of truth for the manifest→language rule table (no in-prose restatement, which drifts from the script's tsconfig JS-vs-TS and `build.gradle` Java-vs-Kotlin disambiguation). **Resolve `{detectLanguageHelper}`** from `{detectLanguageProbeOrder}`; first existing path wins. For each unit, pipe its file list — the `files` array built for that boundary in the §2 boundaries JSON — as the tree: ```bash echo '{"tree": [<unit files — forward-slash, repo-relative>]}' | uv run {detectLanguageHelper} ``` Read `.language` and `.confidence` for the unit. When confidence is low (the extension-frequency fallback fired — no manifest matched), surface it in §5 so the user can override the guess. ### 5. Present Classifications "**Unit Identification Complete** **Qualifying Units:** {count} {Classification table} **Disqualified Candidates:** {count} {Disqualification table} **Already-Skilled Units:** {count from existing_skills match} {List with recommendation to run update-skill if source has changed} {IF composite merge proposals exist from §3b:} **Composite Merge Proposals:** {count} | # | Composite Name | Constituents | Heuristic | Evidence | |---|----------------|--------------|-----------|----------| | 1 | {name} | {list of constituent unit names} | {mutual hard dependency / shared integration surface} | {brief evidence} | If approved, each composite replaces its constituents in the classification table as a single Composite Boundary unit. The constituents are recorded in the composite's metadata for downstream workflows (create-skill reads constituents to scope extraction across all member paths). {END IF} **Notes:** - {Any observations about project structure patterns} - {Any ambiguous boundaries that need user clarification} Do these classifications look correct? Should any units be added, removed, or reclassified? {IF composites proposed:} Are the composite merge proposals correct? (Accept/reject each individually.)" Wait for user feedback. Adjust classifications based on user input. For approved composites: remove the constituent rows from the qualifying units table and add a single composite row with `Boundary Type: Composite`, scope type inherited from the dominant constituent, and confidence reflecting the merge heuristic strength. ### 6. Append to Report Append the complete "## Identified Units" section to {outputFile}: Replace the placeholder `[Appended by identify-units]` with: - Classification table (qualifying units, including approved composites) - Composite merge details (if any): composite name, constituents list, heuristic, evidence - Disqualification table - Already-skilled units list - Language detection results - Any user adjustments noted Update {outputFile} frontmatter: ```yaml stepsCompleted: [append 'identify-units' to existing array] lastStep: 'identify-units' confirmed_composites: [{list of approved composite merge objects: {name, constituents[], heuristic}}] ``` (`confirmed_composites` is an empty array when no composites were proposed or all were rejected.) ### 7. Present MENU OPTIONS Display: "**Select:** [C] Continue to Export Mapping and Integration Detection | [X] Cancel and exit" #### Menu Handling Logic: - IF C: Save classifications to {outputFile}, update frontmatter, then load, read entire file, then execute {nextStepFile} - IF X: HARD HALT with exit code 6 (`user-cancelled`). Emit the error envelope on stderr with `halt_reason: "user-cancelled"` and counts/paths reflecting state at cancellation (shape in `references/headless-contract.md`) - IF Any other: help user, then [Redisplay Menu Options](#7-present-menu-options) **GATE [default: C]** — present the menu and wait for the user's choice. If `{headless_mode}`: accept all classifications and auto-proceed, log: "headless: auto-accept unit classifications". -
init.md 11.1 KB
--- nextStepFile: 'scan-project.md' continueFile: 'continue.md' outputFile: '{forge_data_folder}/analyze-source-report-{project_name}.md' templateFile: '{analysisReportTemplatePath}' --- <!-- Config: communicate in {communication_language}. --> # Step 1: Initialize Analysis ## STEP GOAL: To initialize the analyze-source workflow by loading configuration, detecting continuation state, accepting the target project path, checking for existing skills, and creating the analysis report document. ## Rules - Focus only on initialization — do not begin scanning or analysis - Collect project path and scope hints from user - Verify prerequisites before proceeding ## MANDATORY SEQUENCE When `{headless_mode}` is true, every HARD HALT in this step emits the error envelope on **stderr** before exiting — shape and enum in `references/headless-contract.md` — using the exit code and `halt_reason` named at that HALT. ### 1. Check for Existing Report (Continuation Detection) Look for {outputFile}. **IF the file exists AND has `stepsCompleted` with entries:** - The report filename is keyed to `{project_name}` (the forge workspace), not the analyzed target — so a report from a *different* target can collide here. Before resuming, establish the requested target and compare it to the existing report: - Determine the requested target now: if `--project-path <path>` was passed at invocation, set `project_paths[]` from it (comma-split if multiple); otherwise collect the path(s) using the section-3 "Collect Project Path" prompt and store as `project_paths[]`. (Section 3 does not re-prompt when `project_paths[]` is already populated here.) - Read the existing report's frontmatter `project_paths`. - **IF the existing report's `project_paths` matches the requested target:** - **IF this invocation carries the `[auto]` flag (e.g. `AN[auto]`):** do not route to {continueFile} — auto mode is a single idempotent pass, not a resumable interactive session. Skip continuation and proceed to section 2; the `[auto]` check in §2b re-enters step-auto-scope.md, which re-runs cleanly and overwrites the prior auto report. (This keeps a re-invoked or interrupted auto run on the auto path instead of dropping it into the interactive chain.) - **ELSE:** "**Found an existing analysis report. Resuming previous session...**" — Load, read entirely, then execute {continueFile}. **STOP HERE** — do not continue this sequence. ({continueFile} is mode-aware: a report written by a prior auto run resumes through the auto path, not the interactive chain.) - **ELSE (different target — stale collision):** the existing report belongs to another analysis. Archive it by renaming to `{forge_data_folder}/analyze-source-report-{project_name}-<UTC-timestamp>.md`, announce "**Existing report belongs to a different target — archived as <name>; starting a fresh analysis.**", then continue to section 2 (skip re-collecting the path in section 3 — it is already set). **IF the file does not exist OR stepsCompleted is empty:** - Continue to section 2 ### 2. Verify Prerequisites **Check forge-tier.yaml:** - Look for `{sidecar_path}/forge-tier.yaml` - **IF missing:** HARD HALT — "**Cannot proceed.** forge-tier.yaml not found at `{sidecar_path}/forge-tier.yaml`. Please run the setup workflow first to configure your forge tier (Quick/Forge/Forge+/Deep)." - **IF found:** Read and note the forge tier value **Apply tier override:** Read `{sidecar_path}/preferences.yaml`. If `tier_override` is set and is a valid tier value (Quick, Forge, Forge+, or Deep), use it instead of the detected tier. "**Forge tier detected:** {tier} — analysis depth will be calibrated accordingly." ### 2b. Auto Mode Check **Check for `[auto]` flag:** If `[auto]` was passed as a bracket modifier in the pipeline context (e.g., `AN[auto]`), set `{auto_mode}` = true. **IF `{auto_mode}` is true:** 1. **Resolve project path:** If `project_paths[]` is already populated (from §1 continuation detection or `--project-path` arg), use it. Otherwise, if `--project-path <path>` was passed at invocation, set `project_paths[]` from it (comma-split if multiple). If neither is available, HARD HALT with exit code 2 (`input-missing`): "**Auto mode requires `--project-path` — no project path available.**" 2. **Validate the path(s):** For each provided path/URL, check that it exists (local) or is accessible (remote). If any invalid: HARD HALT with exit code 3 (`resolution-failure`): "**Path `{path}` doesn't appear to be valid.**" 3. **Create the analysis report** from {templateFile}. Populate frontmatter: ```yaml stepsCompleted: ['init'] lastStep: 'init' lastContinued: '' date: '{current_date}' user_name: '{user_name}' project_name: '{project_name}' project_paths: ['{provided_project_path}'] forge_tier: '{detected_tier}' existing_skills: [] confirmed_units: [] stack_skill_candidates: [] nextWorkflow: '' mode: 'auto' ``` 4. "**Auto mode activated — bypassing interactive analysis.**" 5. **Route to auto-scope:** Load, read fully, then execute `references/step-auto-scope.md`. **STOP HERE** — do not continue to §3 or any subsequent section. **IF `{auto_mode}` is not true:** Continue to §3 as normal — the entire interactive flow below is unchanged. ### 3. Collect Project Path **Headless flag consumption:** If `project_paths[]` is already populated (e.g. collected by the section-1 stale-collision guard) OR `--project-path <path>` was passed at invocation, set/keep `project_paths[]` (comma-split the flag value if multiple paths were supplied), skip the prompt below, and proceed to validation. If `{headless_mode}` is true and no path is available from either source, HARD HALT with exit code 2 (`input-missing`): "**No project path — headless mode requires `--project-path`.**" (interactive prompting is unavailable headless). Otherwise prompt as today. **Per-path ref overrides (`--target-refs`):** If `--target-refs <mapping>` was passed at invocation, parse it as a comma-separated list of `path:ref` pairs (e.g., `owner/repo:v1.0.0,owner/repo2:main`). Build a `constituent_refs` map from the pairs. Each key must match an entry in `project_paths[]` (validated after path collection). When `--target-refs` is absent but multiple `project_paths` exist, set `constituent_refs` to `{}` (empty — all paths use default ref resolution). When only a single path exists, omit `constituent_refs` entirely (use `target_ref` if set on the brief). `constituent_refs` and `target_ref` are mutually exclusive — if both are supplied, HALT with: "`--target-refs` and `--target-ref` are mutually exclusive. Use `--target-refs` for multi-path analysis, or `--target-ref` for single-path." "**Please provide the project root path(s) to analyze** — I'll identify discrete skillable units and produce a skill-brief.yaml for each. This can be: - A single root directory of a repo or multi-service project - Multiple paths or URLs (comma-separated) for multi-repo analysis (e.g., integration/stack skills) Examples: - `/path/to/project` - `owner/repo, owner/repo2` - `/path/to/project, https://github.com/owner/repo2`" Wait for user input. **Validate the path(s):** - For each provided path/URL: check that it exists (local) or is accessible (remote) - **IF any invalid:** "Path `{path}` doesn't appear to be valid. Please correct it." - Store as `project_paths[]` array in report frontmatter (single path stored as 1-element array for consistency) - **IF `constituent_refs` was built from `--target-refs`:** Validate that every key in the map matches an entry in `project_paths[]`. If any key has no matching path, HALT: "constituent_refs key `{key}` does not match any entry in project_paths." **Collect intent hint** (drives recommendation ranking in Step 5): **Headless flag consumption:** If `--intent-hint <text>` was passed at invocation, set workflow-context `intent_hint` directly from the flag value, skip the prompt below, and proceed. If `{headless_mode}` is true and no `--intent-hint` was supplied, set `intent_hint = ""` (empty) and proceed without prompting. "**Optional: What are you hoping to get out of this analysis?** For example: - Skills for a specific domain (e.g., 'authentication and authorization') - Target consumer agents (e.g., 'skills our backend team's AI assistants will call') - Constraints (e.g., 'we only want stable public APIs, no internal modules') Type details, or press Enter to skip." Wait for user input. Store as workflow-context `intent_hint` (empty string if skipped). ### 4. Collect Optional Scope Hints **Headless flag consumption:** If `--scope-hint <text>` was passed at invocation, set workflow-context `scope_hint` directly from the flag value, skip the prompt below, and proceed. If `{headless_mode}` is true and no `--scope-hint` was supplied, set `scope_hint = ""` (empty) and proceed without prompting. "**Optional: Do you have scope hints to narrow the analysis?** For example: - Specific packages to focus on (e.g., `packages/auth`, `services/api`) - Directories to exclude (e.g., `vendor/`, `node_modules/`, `dist/`) Enter scope hints, or press Enter to analyze the entire project." Wait for user input. Document any hints provided. ### 5. Check for Existing Skills Scan `{forge_data_folder}/*/skill-brief.yaml` (one level deep — each skill has its own subdirectory) for existing skill briefs. **IF existing skills found:** "**Existing skills detected:** {list each existing skill name and path} These units will be flagged as 'already skilled' during analysis. If source changes are detected, I'll recommend running update-skill instead of generating new briefs." **IF no existing skills found:** "**No existing skills found.** All identified units will be treated as new." ### 6. Create Analysis Report Create {outputFile} from {templateFile}. If the write fails, HARD HALT with exit code 4 (`write-failed`) per `references/headless-contract.md`. **Populate frontmatter:** ```yaml stepsCompleted: ['init'] lastStep: 'init' lastContinued: '' date: '{current_date}' user_name: '{user_name}' project_name: '{project_name}' project_paths: ['{provided_project_path}'] constituent_refs: {map from --target-refs, or omit if single path} forge_tier: '{detected_tier}' existing_skills: [{list of existing skill names}] intent_hint: '{intent_hint or empty string}' scope_hint: '{scope_hint or empty string}' confirmed_units: [] stack_skill_candidates: [] nextWorkflow: '' ``` **`constituent_refs` presence rules:** Include the field only when `project_paths` has more than one entry. When present, keys are path strings matching `project_paths[]` entries, values are explicit git refs (tag/branch/commit). Paths with no explicit ref have no entry in the map (default ref resolution applies). Downstream steps (scan-project, brief generation) read this map to resolve per-constituent refs when cloning or reading off-HEAD constituents. "**Initialization complete.** **Project:** {project_path} **Forge Tier:** {forge_tier} **Existing Skills:** {count} **Scope Hints:** {hints or 'None — full project analysis'} **Proceeding to project scan...**" ### 7. Proceed to Next Step Initialization is complete and the report is created — immediately load, read the entire file, then execute {nextStepFile}. This step has no user choices. -
map-and-detect.md 9.7 KB
--- nextStepFile: 'recommend.md' outputFile: '{forge_data_folder}/analyze-source-report-{project_name}.md' heuristicsFile: '{unitDetectionHeuristicsPath}' --- <!-- Config: communicate in {communication_language}. --> # Step 4: Map Exports and Detect Integrations ## STEP GOAL: To analyze each qualifying unit's export surface and import graph, detect cross-unit integration points, and flag potential stack skill candidates — completing the analysis foundation needed for recommendations. ## Rules - Delegate per-unit deep analysis to a subagent when available (parallelizes across units; main-thread fallback is fine) - For each qualifying unit, perform thorough export surface analysis — do not shortcut - Do not make recommendations (Step 05) - Tier-aware depth: Quick (file-level), Forge (AST), Deep (AST + semantic) ## MANDATORY SEQUENCE ### 1. Load Context Read {outputFile} to obtain: - Qualifying units from Identified Units section (names, paths, scope types, languages) - `forge_tier` from frontmatter Load {heuristicsFile} for stack skill candidate detection rules. ### 2. Map Export Surfaces Per Unit (Subagent Fan-Out) For each qualifying unit, delegate deep analysis to a subagent so per-unit work runs in parallel and the parent's context stays clean. **Subagent fan-out protocol:** 1. **Build the qualifying-unit list.** Read the unit list produced upstream (Step §3 / §4 outputs already in workflow context — names, paths, scope types, languages, file counts). Do not re-scan the project here. 2. **Delegate per-unit deep analysis to a subagent.** For each qualifying unit, launch a subagent task with these explicit constraints: - The subagent reads only that unit's directory tree - The subagent analyzes exports / usage / CCC signals / scripts+assets for that one unit - **The parent does not read the unit's source files before delegating** (avoid the implicit-read trap — the whole point of fan-out is to keep large source bodies out of the parent's context) 3. **Per-unit analysis the subagent performs (scaled by size-aware strategy):** **Size-aware strategy selection:** - **< 50 files:** Full export scan — analyze every file for exports - **50-200 files:** Targeted scan — entry points (`__init__.py`, `index.ts`, `lib.rs`) + public modules + barrel exports only - **200+ files:** Entry-point strategy — analyze top-level entry point for public API surface, list submodule entry points, analyze each submodule entry point only. Report coverage confidence based on percentage of files analyzed. **Tier-aware depth:** - **Quick tier:** Count files by type, identify index/barrel files, list directory structure - **Forge tier:** Parse export statements, identify public API surface, count exported functions/classes/types - **Forge+ tier:** All Forge analysis plus: - If `tools.ccc` is true: run `ccc_bridge.search("{unit_name} exports public API", top_k=15)` to discover semantically relevant files beyond directory scan. Tool resolution: prefer the `/ccc` skill search (Claude Code) or ccc MCP server (Cursor); fall back to the `ccc search` CLI if neither is available; if no ccc tool resolves, skip CCC discovery and record `ccc: unavailable` in per-unit findings. - Merge CCC-discovered files with scoped file list — files from CCC that are within the unit's directory are added to the analysis queue - Record CCC signals in per-unit findings: top 3 CCC-ranked file names (or "—" if no ccc results) - **Deep tier:** All Forge analysis plus: - ast-grep structural export extraction: `ast-grep -p 'export $$$' --lang typescript` or equivalent per language to build a verified export inventory - ast-grep type/interface mapping: `ast-grep -p 'interface $NAME' --lang typescript` or `ast-grep -p 'class $NAME($$$)' --lang python` - If QMD available: query for temporal evolution of identified exports (deprecation signals, recent additions, refactoring patterns) - Record semantic relationships between exports (which exports reference/depend on each other) **Subagent must also record:** - Script/asset presence: check for `scripts/`, `bin/`, `assets/`, `templates/` directories and files matching detection signals in `{heuristicsFile}` - Analysis strategy used and coverage confidence 4. **Subagent return contract.** Each subagent returns only this JSON object — no prose, no commentary, no markdown fences: ```json { "unit_name": "...", "files_count": N, "exports_count": N, "export_pattern": "...", "api_surface": ["..."], "scripts_assets": {"scripts": [], "assets": []}, "ccc_signals": {"top_files": [], "available": <bool>}, "strategy_used": "ast-grep|regex|main-thread", "confidence": "T1|T2|T1-low" } ``` 5. **Parent post-processing.** Strip any wrapping markdown fences (subagents sometimes wrap JSON in ` ```json … ``` ` despite the contract) before parsing. Validate each payload against the contract; if a key is missing, log a warning to `workflow_warnings[]` and continue with that unit's degraded record. 6. **Aggregate.** Collect all per-unit JSON payloads into `per_unit_findings[]` in workflow context for use by §3 (import graph), §4 (integration points), §5 (stack candidates), §6 (findings presentation), and downstream stages (recommend.md, generate-briefs.md). **Per-unit export summary (built from `per_unit_findings[]`):** | Unit | Files | Exports | Export Pattern | API Surface | Scripts/Assets | CCC Signals | |------|-------|---------|----------------|-------------|----------------|-------------| | {name} | {count} | {count} | {pattern} | {small/medium/large} | {N scripts, M assets or --} | {CCC signals or --} | **Graceful degradation.** If subagents are unavailable in the current runtime, the parent performs the per-unit analysis sequentially in the main thread using the same size-aware strategy and tier-aware depth as above. Each main-thread analysis still produces the same JSON record shape so downstream stages remain agnostic to the execution mode. Record `strategy_used: "main-thread"` for every unit processed this way. ### 3. Map Import Graph For each qualifying unit, analyze inbound and outbound imports: **Outbound imports (this unit imports from):** - Which other qualifying units does it depend on? - Which external dependencies does it use? **Inbound imports (other units import from this):** - Which other qualifying units consume this unit's exports? - How many import sites exist? **Build cross-reference matrix:** | Unit | Imports From | Imported By | External Deps | |------|-------------|-------------|---------------| | {name} | {list units} | {list units} | {count} | ### 4. Detect Integration Points Identify cross-unit integration patterns: **Direct integrations:** - Unit A imports from Unit B → document the interface boundary - Shared type definitions across units - Cross-unit function calls **For each integration point document:** - Source unit → Target unit - Integration type (import, shared types, API call, message passing, shared state) - Files involved (with paths) - Coupling strength (tight / loose / indirect) ### 5. Flag Stack Skill Candidates Check each of the four stack-skill indicators defined in {heuristicsFile}'s Stack Skill Candidate Detection section — co-import frequency, integration adapter, shared state, orchestration layer — against the units. **For each candidate, document:** - Units involved - Detection signal - Recommended stack skill grouping - Evidence (specific files/lines) ### 6. Present Findings "**Export Mapping and Integration Detection Complete** **Export Map Summary:** {Per-unit export summary table} **Cross-Reference Matrix:** {Import graph matrix} **Integration Points:** {count} {List each integration with source → target, type, coupling} **Stack Skill Candidates:** {count} {List each candidate with units involved and detection signal} **Observations:** - {Key architectural patterns observed} - {Tightly coupled areas} - {Loosely coupled areas ideal for independent skills} Does this analysis look complete? Any integration patterns I should investigate further?" Wait for user feedback. Adjust analysis based on user input. ### 7. Append to Report Append the complete "## Export Map" section to {outputFile}: Replace `[Appended by map-and-detect]` under Export Map with: - Per-unit export summary table - Export pattern analysis Append the complete "## Integration Points" section to {outputFile}: Replace `[Appended by map-and-detect]` under Integration Points with: - Cross-reference matrix - Integration point details - Stack skill candidate flags Update {outputFile} frontmatter: ```yaml stepsCompleted: [append 'map-and-detect' to existing array] lastStep: 'map-and-detect' stack_skill_candidates: [{list flagged candidate groupings}] ``` ### 8. Present MENU OPTIONS Display: "**Select:** [C] Continue to Recommendations | [D] Discover Additional Source" #### Menu Handling Logic: - IF C: Save findings to {outputFile}, update frontmatter, then load, read entire file, then execute {nextStepFile} - IF D: Accept a new repo path/URL from the user. Run a lightweight scan (directory structure + manifest detection from step 02) and classify (unit identification from step 03) for the new source only. Merge results into the existing report — append new units to the unit list, update `project_paths[]` in frontmatter. Then redisplay this step's export mapping for the new units before returning to the menu. - IF Any other: help user, then [Redisplay Menu Options](#8-present-menu-options) **GATE [default: C]** — present the menu and wait for the user's choice. If `{headless_mode}`: auto-proceed with [C] Continue past export/integration findings, log: "headless: auto-continue past integration analysis". -
recommend.md 6.7 KB
--- nextStepFile: 'generate-briefs.md' outputFile: '{forge_data_folder}/analyze-source-report-{project_name}.md' schemaFile: '{briefSchemaPath}' advancedElicitationSkill: '/bmad-advanced-elicitation' partyModeSkill: '/bmad-party-mode' --- <!-- Config: communicate in {communication_language}. Artifact text in {document_output_language}. --> # Step 5: Recommend ## STEP GOAL: To present each qualifying unit as a recommendation with evidence-based rationale, allow the user to confirm, reject, or modify each recommendation, and build the confirmed units list that drives brief generation. ## Rules - This is the primary user decision point — do not rush through it - Do not proceed without explicit confirmation for each unit - Present evidence for each recommendation, invite questions and pushback ## MANDATORY SEQUENCE ### 1. Load Context Read {outputFile} completely to obtain: - Qualifying units from Identified Units - Export map data per unit - Integration points and coupling analysis - Stack skill candidates from frontmatter - Existing skills from frontmatter - `intent_hint` from frontmatter (captured in init.md §3 — user's stated goal for the analysis; empty string if skipped) Load {schemaFile} for reference on what skill-brief.yaml requires (so recommendations are actionable). **Apply `intent_hint` when ranking units.** If `intent_hint` is non-empty, use it to bias the order in which recommendation cards are presented and to soften the rationale for units that fall outside the stated intent (e.g., when `intent_hint` mentions "authentication and authorization", rank auth-related units first and flag unrelated units with a one-line rationale acknowledging the mismatch). If `intent_hint` is empty, present in the deterministic order from the export map. ### 2. Build Recommendation Cards For each qualifying unit, prepare a recommendation card: ``` **Unit: {name}** - Path: {path} - Language: {language} - Boundary: {type} | Scope: {scope_type} - Exports: {count} ({pattern}) - API Surface: {size} - Integrations: imports from {list}, imported by {list} - Coupling: {tight/loose/indirect} - Confidence: {high/medium/low} - Stack Skill: {yes — grouped with {units} / no} - Status: {new / already-skilled → recommend update-skill} **Rationale:** {2-3 sentences explaining WHY this should be a skill, citing specific detection signals and file paths} **Proposed Brief Fields:** - name: {suggested kebab-case name} - scope.type: {full-library / specific-modules / public-api / component-library / reference-app / docs-only} - scope.include: {suggested glob patterns} - description: {suggested 1-3 sentence description, containing a literal `Use when` clause} ``` ### 3. Present Recommendations "**Recommendations for {project_name}** I've analyzed {count} qualifying units. Here are my recommendations for skill creation: ---" Present each recommendation card, then after ALL cards: "--- **Summary:** - **Recommended for new skills:** {count} - **Already-skilled (recommend update-skill):** {count} - **Stack skill candidates:** {count} **For each unit above, please indicate:** - **Y** — Confirm: generate skill-brief.yaml - **N** — Reject: skip this unit - **M** — Modify: adjust name, scope, or description before confirming **Bulk shortcuts:** - `all-Y` — confirm every recommendation as-is - `all-N` — skip every recommendation - Individual response list (e.g., `1:Y, 2:N, 3:M, ...`) — explicit per-unit decisions (use this when you want to mix Y/N/M or modify any unit) **Questions?** Ask 'why?' about any recommendation and I'll explain my reasoning with specific evidence." ### 4. Process User Decisions For each unit: **IF Y (Confirm):** - Add to confirmed_units list with proposed brief fields **IF N (Reject):** - Document rejection with user's reason (if provided) - Remove from confirmed list **IF M (Modify):** - Ask what to modify (name, scope, description, scope.include patterns) - Update the recommendation with user changes - Confirm the modified version **Continue until ALL units have a decision (Y/N/M).** ### 5. Confirm Stack Skill Candidates If stack skill candidates were flagged: "**Stack Skill Candidates:** The following unit groupings show strong co-integration patterns and may benefit from a consolidated stack skill via create-stack-skill: {For each candidate: list units, detection signal, evidence} Would you like to flag these for create-stack-skill? (Y/N per candidate)" Document decisions. ### 6. Present Final Confirmation "**Confirmed Units for Brief Generation:** {count} {List each confirmed unit with final name, scope type, and description} **Rejected Units:** {count} {List with reasons} **Stack Skill Flags:** {count} {List groupings flagged for create-stack-skill} **This is your final confirmation before I generate skill-brief.yaml files.** All confirmed?" Wait for explicit final confirmation. ### 7. Append to Report Append the complete "## Recommendations" section to {outputFile}: Replace `[Appended by recommend]` with: - All recommendation cards (including rationale) - User decisions per unit (confirmed/rejected/modified) - Stack skill candidate decisions - Final confirmation status Update {outputFile} frontmatter: ```yaml stepsCompleted: [append 'recommend' to existing array] lastStep: 'recommend' confirmed_units: [{list of confirmed unit names}] stack_skill_candidates: [{updated list with user decisions}] ``` ### 8. Present MENU OPTIONS Display: "**Select an Option:** [A] Advanced Elicitation [P] Party Mode [D] Discover Additional Source [C] Continue to Brief Generation [X] Cancel and exit" #### Menu Handling Logic: - IF A: Invoke {advancedElicitationSkill}, and when finished redisplay the menu - IF P: Invoke {partyModeSkill}, and when finished redisplay the menu - IF D: Accept a new repo path/URL from the user. Run a lightweight scan + classify (subset of steps 02-03) for the new source only. Merge new units into the existing report and update `project_paths[]` in frontmatter. Run export mapping for the new units (same logic as step 04 section 2). Generate recommendation cards for the new units and present them for confirmation. Then redisplay this menu. - IF C: Save recommendations to {outputFile}, update frontmatter, then load, read entire file, then execute {nextStepFile} - IF X: HARD HALT with exit code 6 (`user-cancelled`). Emit the error envelope on stderr with `halt_reason: "user-cancelled"` and counts/paths reflecting state at cancellation (shape in `references/headless-contract.md`) - IF Any other comments or queries: help user respond then [Redisplay Menu Options](#8-present-menu-options) **GATE [default: C]** — present the menu and wait for the user's choice. If `{headless_mode}`: accept all recommendations and auto-proceed, log: "headless: auto-accept all recommendations". -
scan-project.md 6.8 KB
--- nextStepFile: 'identify-units.md' outputFile: '{forge_data_folder}/analyze-source-report-{project_name}.md' heuristicsFile: '{unitDetectionHeuristicsPath}' scanManifestsProbeOrder: - '{project-root}/_bmad/skf/shared/scripts/skf-scan-manifests.py' - '{project-root}/src/shared/scripts/skf-scan-manifests.py' --- <!-- Config: communicate in {communication_language}. --> # Step 2: Scan Project ## STEP GOAL: To map the complete project structure by scanning directory trees, detecting service boundaries, identifying package manifests, and cataloging entry points — building the foundation that subsequent steps use for unit identification. ## Rules - Focus only on structural scanning — do not classify units or map exports yet - Do not read source file contents beyond manifest files and entry points - Delegate per-file scanning to subagents in parallel when many files are involved (main-thread fallback is fine) - Tier-aware scanning depth: Quick (file structure), Forge (+ manifest parsing), Deep (+ config analysis) ## MANDATORY SEQUENCE ### 1. Load Context Read {outputFile} frontmatter to obtain: - `project_paths[]` — the root(s) to scan (one or more paths/URLs) - `constituent_refs` — optional per-path git ref overrides (present only when `project_paths` has multiple entries and explicit refs were supplied via `--target-refs`) - `forge_tier` — determines scanning depth - Scope hints (if any were provided in step 01) Load {heuristicsFile} for reference on detection signals. ### 2. Scan Directory Structure **Resolve `{scanManifestsHelper}`** from `{scanManifestsProbeOrder}`; first existing path wins. HALT if no candidate exists. **For each path in `project_paths[]`**, resolve the constituent ref (if any) and launch a subprocess that scans the project directory structure (aggregate results across all repos with clear repo-level grouping): **Per-path ref resolution:** If `constituent_refs` is present and contains an entry for the current path, use that ref. For remote paths, this means cloning or fetching at the specified ref (`git clone --branch {ref} --depth 1` or `git show {ref}:<subpath>` for off-HEAD access). For local paths with a non-HEAD ref, check out or read from the specified ref using `git show {ref}:<path>`. When no `constituent_refs` entry exists for a path, use default ref resolution (HEAD for local, latest tag or HEAD for remote). 1. Map the top-level directory tree (2-3 levels deep) 2. Identify workspace configuration files (pnpm-workspace.yaml, lerna.json, Cargo.toml [workspace], go.work, etc.) 3. Enumerate package manifests deterministically — invoke `uv run {scanManifestsHelper} scan {path}` and parse the JSON envelope. The script returns `{manifests[], total_unique, monorepo, warnings?}` covering npm/python/rust/go/maven/gradle/ruby/composer/swift; record each `{path, ecosystem}` for the manifests catalog in §4 and capture `monorepo` for the boundary-signal pass in §3 4. Locate entry point files (index.ts, main.ts, app.ts, main.go, main.rs, __init__.py, etc.) 5. Detect service configuration (Dockerfile, docker-compose.yml, kubernetes manifests, serverless.yml) — keep this step LLM-driven; file glob + presence check is sufficient, no parsing required 6. Return structured findings — file paths and types only, not contents. When `constituent_refs` was used, include the resolved ref in each repo-level result group: `{path, ref, manifests[], monorepo, warnings?}` **If subprocess unavailable:** Perform directory scanning in main thread using file I/O tools. **Apply scope hints if provided:** - If specific directories were given, scan only those - If exclusion patterns were given, skip matching directories **Deep tier additional scanning (IF Deep tier):** - Use ast-grep to detect structural patterns across the codebase: `ast-grep -p 'class $NAME' --lang python` (or equivalent per language) to build a class/type inventory - Use ast-grep to identify exported function patterns: `ast-grep -p 'def $FUNC($$$PARAMS)' --lang python` at entry points - If QMD is available, query for temporal context on the project: recent changes, active development areas, refactoring patterns - Record Deep-tier findings separately — they supplement (not replace) the Quick/Forge scan results ### 3. Detect Service Boundaries Based on scan results, identify potential service boundaries: **Strong boundary signals:** - Independent package manifest (own package.json, Cargo.toml, etc.) - Docker/container configuration - Separate entry point file - Workspace member listing **Document each detected boundary with:** - Path relative to project root - Boundary type (service / package / module) - Detection signals found (list specific files) - Confidence level (strong / moderate / weak) ### 4. Catalog Manifests and Entry Points Create a structured catalog: **Manifests found:** | Path | Type | Language Indicator | |------|------|-------------------| | {path} | {manifest_type} | {language} | **Entry points found:** | Path | Type | |------|------| | {path} | {entry_type} | **Service configurations found:** | Path | Type | |------|------| | {path} | {config_type} | ### 5. Present Scan Results "**Project Scan Complete** **Project:** {project_path} **Forge Tier:** {forge_tier} **Structure Overview:** {top-level directory tree} **Detected Boundaries:** {count} {list each boundary with path, type, confidence} **Manifests Found:** {count} {summary table} **Entry Points Found:** {count} {summary table} **Service Configurations:** {count} {summary table} **Scope Applied:** {hints or 'Full project scan'} Does this scan look complete? Any directories I should investigate further or skip?" Wait for user feedback. If user identifies gaps, rescan as directed. ### 6. Append to Report Append the complete "## Project Scan" section to {outputFile}: Replace the placeholder `[Appended by scan-project]` with the full scan results including: - Structure overview - Detected boundaries table - Manifests catalog - Entry points catalog - Service configurations catalog - Scope notes Update {outputFile} frontmatter: ```yaml stepsCompleted: [append 'scan-project' to existing array] lastStep: 'scan-project' ``` ### 7. Present MENU OPTIONS Display: "**Select:** [C] Continue to Unit Identification | [X] Cancel and exit" #### Menu Handling Logic: - IF C: Save scan results to {outputFile}, update frontmatter, then load, read entire file, then execute {nextStepFile} - IF X: HARD HALT with exit code 6 (`user-cancelled`). Emit the error envelope on stderr with `halt_reason: "user-cancelled"` and counts/paths reflecting state at cancellation (shape in `references/headless-contract.md`) - IF Any other: help user, then [Redisplay Menu Options](#7-present-menu-options) **GATE [default: C]** — present the menu and wait for the user's choice. If `{headless_mode}`: auto-proceed with [C] Continue, log: "headless: auto-continue past scan results". -
step-auto-scope.md 34.7 KB
--- nextStepFile: 'health-check.md' outputFile: '{forge_data_folder}/analyze-source-report-{project_name}.md' shapeDetectProbeOrder: - '{project-root}/_bmad/skf/shared/scripts/skf-shape-detect.py' - '{project-root}/src/shared/scripts/skf-shape-detect.py' validatePinsProbeOrder: - '{project-root}/_bmad/skf/shared/scripts/skf-validate-pins.py' - '{project-root}/src/shared/scripts/skf-validate-pins.py' skillInventoryProbeOrder: - '{project-root}/_bmad/skf/shared/scripts/skf-skill-inventory.py' - '{project-root}/src/shared/scripts/skf-skill-inventory.py' scanManifestsProbeOrder: - '{project-root}/_bmad/skf/shared/scripts/skf-scan-manifests.py' - '{project-root}/src/shared/scripts/skf-scan-manifests.py' detectLanguageProbeOrder: - '{project-root}/_bmad/skf/shared/scripts/skf-detect-language.py' - '{project-root}/src/shared/scripts/skf-detect-language.py' languageCorporaProbeOrder: - '{project-root}/_bmad/skf/shared/scripts/skf-language-corpora.py' - '{project-root}/src/shared/scripts/skf-language-corpora.py' --- <!-- Config: communicate in {communication_language}. --> # Step 1a: Auto-Scope Analysis ## STEP GOAL: To automatically scope a repo using shape detection and export surface analysis, producing a scope and skill-brief.yaml without requiring manual input. This step replaces the interactive scan-project → identify-units → map-and-detect → recommend → generate-briefs chain when `{auto_mode}` is true. ## Rules - Auto-proceed step — no user interaction required - This step is conditional — only loaded when `[auto]` flag is present in the pipeline context - Must produce the same output artifacts as the interactive chain: analysis report + skill-brief.yaml - On unknown shape (exit code 1), fall back to `scan-project.md` (the normal interactive entry point) - On error (exit code 2), HARD HALT with exit code 3 (`resolution-failure`) ## MANDATORY SEQUENCE ### 0. URL Type Detection Read the target URL or path from the pipeline context (`{project_path}` or the first entry in `project_paths[]`). Apply the following heuristic to classify the input: | Input Pattern | Classification | Route | |---------------|---------------|-------| | `github.com/{owner}/{repo}` (with or without `.git` suffix, with or without scheme prefix) | GitHub repo | §1 (standard auto-scope) | | `gitlab.com/...`, `bitbucket.org/...` | Git hosting | §1 (standard auto-scope) | | Starts with `/`, `./`, `~/`, or `~` | Local filesystem path | §1 (standard auto-scope) | | Any other `https://` or `http://` URL | Documentation URL | `references/auto-docs-only.md` (docs-only, via §0c) | | Anything else (SSH URLs, `git://`, bare hostnames, etc.) | Unclassified | §1 (standard auto-scope) | Store the classification result (documentation URL vs. repo/local/other). For all input types, continue to §0b (Pin Resolution). ### 0b. Pin Resolution This section validates and resolves version pins. It runs for repo URLs and local paths only — skip for documentation URLs (doc URLs have no git repo to pin against). Initialize `{pinned_ref}`, `{pinned_ref_type}`, and `{pinned_version}` as null. **For documentation URLs:** Skip this section entirely. Continue to §0c. **For local paths when `--pin` is provided:** Emit a warning: "**Local source may not match pinned version {pin_value}.** Ensure you've checked out the correct version locally, or use a remote GitHub URL so SKF can clone from the git tag automatically." Store `{pinned_ref}` = `{pin_value}`, `{pinned_ref_type}` = `"local"`, `{pinned_version}` = `{pin_value}`. Continue to §0c without running `skf-validate-pins.py`. **For repo URLs when `--pin` is provided:** **Resolve `{validatePinsHelper}`** from `{validatePinsProbeOrder}`; first existing path wins; HALT if neither resolves. ```bash uv run {validatePinsHelper} --repo-url {project_path} --pin {pin_value} ``` Handle exit codes: - **Exit 0** (`status: "valid"`): Store `{pinned_ref}` = `resolved_ref`, `{pinned_ref_type}` = `ref_type`, `{pinned_version}` = `version`. Continue to §0c. - **Exit 1** (`status: "invalid"`): HARD HALT with exit code 3 (`resolution-failure`). Emit error: `"Version pin '{pin_value}' not found in {project_path}. Available matches: {suggestions}. Use a valid tag, branch, or omit --pin for latest."` Emit the error envelope (shape in `references/headless-contract.md`) with `exit_code: 3`, `halt_reason: "pin-invalid"`, `mode: "auto"`. - **Exit 2** (error): HARD HALT with exit code 3 (`resolution-failure`). Emit the error envelope with `halt_reason: "resolution-failure"`. **For repo URLs when `--pin` is not provided (default):** Using the same `{validatePinsHelper}` resolved above: ```bash uv run {validatePinsHelper} --repo-url {project_path} ``` Handle exit codes: - **Exit 0** (`status: "resolved"`): Store `{pinned_ref}` = `resolved_ref`, `{pinned_ref_type}` = `ref_type`, `{pinned_version}` = `version`. Log: "Default pin resolved: {resolved_ref}". Continue to §0c. - **Exit 1** (no releases found): Set `{pinned_ref}` = null, `{pinned_ref_type}` = null, `{pinned_version}` = null. Log: "No release tags found — using HEAD." Continue to §0c without pinning. - **Exit 2**: Log warning, continue without pinning (same as exit 1 behavior). ### 0c. Coexistence Detection This section checks for existing skills matching the target before proceeding. It runs for all input types (repo URLs, doc URLs, and local paths). Initialize `{coexistence_suffix}` as empty. **1. Load skill inventory:** **Resolve `{skillInventoryHelper}`** from `{skillInventoryProbeOrder}`; first existing path wins; HALT if neither resolves. Pass the target (`{project_path}`) so the helper computes the coexistence match set for you — do not re-match by hand: ```bash uv run {skillInventoryHelper} {skills_output_folder} --match-target {project_path} ``` Parse the JSON output. If the exit code is non-zero or the `skills` array is empty, skip coexistence detection silently (no existing skills to conflict with) and continue: load, read fully, then execute `references/auto-docs-only.md` for documentation URLs; §1 for all other input types. **2. Read the match set:** The helper already performed the match deterministically — scheme / trailing-`.git` / trailing-slash normalization, kebab expected-name derivation (§6 repo/package name, doc hostname per `references/auto-docs-only.md`), and case-insensitive comparison of both the normalized `source_repo` (URL match) and the derived name (name match). Read the top-level **`matches[]`** array from the JSON; do not normalize, derive, or compare anything in the prompt. Each entry is: ```json { "name": "...", "active_version": "...", "source_repo": "...", "active_path": "...", "match_reason": "url" | "name" | "both" } ``` **3. If `matches[]` is empty:** Complete silently. Continue: execute `references/auto-docs-only.md` for documentation URLs; §1 for all other input types. No user output. **4. If `matches[]` has one or more entries — coexistence gate:** Present the user with the coexistence decision, one bullet per `matches[]` entry (`{skill_name}` = `matches[].name`, `{version}` = `matches[].active_version`, `{source_repo}` = `matches[].source_repo`): ``` ⚠️ Existing skill(s) found for {target_name}: • {skill_name} (v{version}) — source: {source_repo} [repeat for each entry in matches[]] Actions: [A]longside — Create a new wiki skill with "-wiki" suffix (existing skill untouched) [M]erge — Update the existing skill via US workflow (wiki data enriches it) [S]kip — Do not create or modify any skill for this library Choose [A/M/S]: ``` In headless mode (`{headless_mode}` is true): auto-select `[A]longside` and log: "Headless: coexistence detected for {target_name}, auto-selecting [A]longside" **5. Handle user selection:** - **[A]longside:** Set `{coexistence_suffix}` to `-wiki`. Continue: execute `references/auto-docs-only.md` for documentation URLs; §1 for all other input types. The existing skill is untouched. - **[M]erge:** If `matches[]` has more than one entry, prompt the user to select which one to merge into before proceeding. Read `{matched_skill_name}` = the selected entry's `matches[].name` and `{matched_active_path}` = its `matches[].active_path`. Emit a redirect envelope signaling the forger to route to the US workflow for the selected skill: ``` SKF_ANALYZE_RESULT_JSON: {"status":"redirect","redirect_to":"US","skill_name":"{matched_skill_name}","skill_path":"{matched_active_path}","exit_code":0,"halt_reason":null,"mode":"auto","coexistence":"merge"} ``` Write the result contract per `shared/references/output-contract-schema.md` with `status: "redirect"`. Chain to {nextStepFile} (health-check.md). **STOP HERE — do not proceed to the docs-only sub-flow or §1.** - **[S]kip:** Emit a skip envelope: ``` SKF_ANALYZE_RESULT_JSON: {"status":"skipped","report_path":null,"brief_paths":[],"unit_counts":{"confirmed":0,"skipped":1,"maybe":0},"exit_code":0,"halt_reason":null,"mode":"auto","coexistence":"skip","skipped_reason":"Existing skill for {matched_skill_name}"} ``` Write the result contract with `status: "skipped"`. Chain to {nextStepFile} (health-check.md). **STOP HERE — do not proceed to the docs-only sub-flow or §1.** ### 1. Load Context Read {outputFile} frontmatter to obtain: - `project_paths[]` — the root(s) to analyze - `forge_tier` — for brief generation - `project_name`, `user_name`, `date` Load `references/step-shape-detect.md` as reference for shape detection invocation contract and shape→scope mapping. ### 2. Manifest Scan Enumerate package manifests **deterministically** via `{scanManifestsHelper}` (the same helper the interactive `scan-project.md` uses) — do not hand-scan. Resolve `{scanManifestsHelper}` as the first path in `{scanManifestsProbeOrder}` that exists. The scanner reads a **local directory**, so how you point it at the target depends on the input form classified in §0: **For each path in `project_paths[]`:** - **Local filesystem path** (starts with `/`, `./`, `~/`, `~`, or is an existing directory) — scan it directly: ```bash uv run {scanManifestsHelper} scan {path} ``` - **Remote git URL** (e.g. `github.com/{owner}/{repo}`) — auto-scope has no working tree yet and the scanner cannot read a URL. Fetch **just the manifests** first (blobless + sparse + depth-1 — no source blobs, typically KB–MB even for large monorepos), then scan that tree: ```bash tmp="$(mktemp -d)" git clone --filter=blob:none --no-checkout --depth 1 {pinned_branch_flag} {path} "$tmp" git -C "$tmp" sparse-checkout set --no-cone '**/package.json' '**/Cargo.toml' '**/pyproject.toml' '**/go.mod' '**/pom.xml' '**/build.gradle' '**/build.gradle.kts' '**/Package.swift' 'pnpm-workspace.yaml' '**/pnpm-workspace.yaml' git -C "$tmp" checkout uv run {scanManifestsHelper} scan "$tmp" ``` where `{pinned_branch_flag}` is `--branch {pinned_ref}` when a pin was resolved in §0b (so manifests match the target version), otherwise omitted. **Retain `"$tmp"` through §3** — shape detection reads the discovered manifest files from it — then it may be discarded. Parse the JSON envelope: `{manifests: [{path, ecosystem, ...}], total_unique, monorepo, warnings?}`. The scanner discovers the project root plus monorepo workspace members (npm/pnpm/yarn `workspaces`, Cargo `[workspace]`, and other ecosystems) and sets the `monorepo` flag — so members are found without hand-listing each workspace convention, for both local trees and remote fetches. From the envelope, record: 1. **Supported manifest paths** — filter `manifests[].path` to the types `skf-shape-detect.py` accepts (`package.json`, `pyproject.toml`, `Cargo.toml`, `go.mod`, `pom.xml`, `build.gradle`, `build.gradle.kts`, `Package.swift`). Each `manifests[].path` is **relative to the scan root**, so resolve them against that root (`{path}` for a local scan, `"$tmp"` for a remote fetch) before use. This filtered, comma-joined list of resolved paths is fed to shape detection in §3. For a monorepo, it includes each workspace member's manifest, so the package surface is classified accurately rather than from a bare (and often export-less) repo root. The scanner may discover ecosystems shape detection does not yet classify; those are excluded here, so a repo with no supported manifest falls back to interactive at the next check rather than auto-scoping. 2. **`monorepo` flag** and the count of discovered supported packages — carried forward as a signal for the decomposition decision in §3a. **Harvest tree-level language signals.** A whole-language repo may declare no parser-generator dependency (a hand-written compiler such as rustc, TypeScript, or the Go toolchain) or carry no supported manifest at all (CPython, Ruby). From the **same** fetched tree — no second clone and no blobs, since tree objects are already present in the blobless clone — collect two signals for shape detection. These are pure path listings (`git ls-tree` reads tree objects; no checkout, no blob download): - **Remote fetch** (`"$tmp"`), or a **local path** that is a git repo (`git -C {path}`): ```bash files="$(git -C "$tmp" ls-tree -r --name-only HEAD)" # every file path dirs="$( git -C "$tmp" ls-tree -r -d --name-only HEAD)" # every directory # Grammar files (depth-capped to skip deep vendored fixtures, hard-capped): grammar_matches="$(printf '%s\n' "$files" \ | grep -Ei '\.(g4|pest|lalrpop|y|gram|lark|ebnf|peg|ungram)$|/grammar\.(js|json)$' \ | awk -F/ 'NF<=4' | head -n 50 | paste -sd, -)" # Directory signals (trailing /) + depth-capped file basenames, narrowed to # compiler-relevant paths so the argument stays bounded on huge repos. The # filter is a loose superset of shape detection's gates — the script does the # precise matching; this only keeps the list small. tree_paths="$({ printf '%s\n' "$dirs" | sed 's#$#/#'; \ printf '%s\n' "$files" | awk -F/ 'NF<=5'; } \ | grep -Ei '(^|/)(compiler|compile|syntax|scanner|lexer|tokeniz|parser|parse|ast|binder|checker|codegen|ssagen|interpreter|vm|eval|rustc_[a-z]+)' \ | head -n 400 | paste -sd, -)" ``` - **Local path that is not a git repo** (`{path}`): list the tree with `find` instead, then derive `grammar_matches` / `tree_paths` the same way: ```bash files="$(cd {path} && find . -type f -not -path '*/.git/*' | sed 's#^\./##')" dirs="$( cd {path} && find . -type d -not -path '*/.git/*' | sed 's#^\./##')" ``` Record `<grammar_matches>` and `<tree_paths>` (each a comma-joined list, possibly empty) for §3. **IF no supported manifests are found** (the filtered list is empty): - **AND** `<grammar_matches>` is empty **AND** `<tree_paths>` shows no compiler directory (none of `compiler/`, `src/compiler/`, `cmd/compile/`, `internal/syntax/`, or a `Parser/`): the repo carries no language signal — emit fallback message: "**Auto-scope could not find any supported package manifests — switching to interactive mode.**" Load, read fully, then execute `references/scan-project.md`. **STOP HERE.** - **Otherwise** (a grammar file or a compiler directory is present) the repo is a manifest-less language toolchain (CPython, Ruby): proceed to §3 with an **empty** `--manifests` and the harvested `--grammar-files` / `--tree-paths`. ### 3. Invoke Shape Detection **Resolve `{shapeDetectHelper}`** from `{shapeDetectProbeOrder}`; first existing path wins; HALT if neither resolves. Invoke the shape detection script with the discovered manifests and the harvested tree-level signals: ``` uv run {shapeDetectHelper} --repo-url <project_path_or_url> \ --manifests <comma_separated_manifest_paths> \ --grammar-files <grammar_matches> --tree-paths <tree_paths> ``` `<comma_separated_manifest_paths>` may be empty for a manifest-less language repo, provided `<grammar_matches>` or `<tree_paths>` carries the signal. Parse the JSON output: `{shape, signals, confidence, export_count, package_count}` **Handle exit codes:** - **Exit 0 (shape classified):** Continue to §3a. - **Exit 1 (unknown shape):** Emit fallback message: "**Auto-scope could not classify this repo — switching to interactive mode.**" Load, read fully, then execute `references/scan-project.md`. **STOP HERE.** - **Exit 2 (error):** HARD HALT with exit code 3 (`resolution-failure`). Emit the error envelope (shape in `references/headless-contract.md`) with `exit_code: 3`, `halt_reason: "resolution-failure"`, `mode: "auto"`. ### 3a. Check Decomposition Thresholds Evaluate the shape detection output to determine whether this **monorepo** should be decomposed into multiple skills. Apply the **Decomposition Thresholds** ladder from `step-shape-detect.md` (loaded at §1). A *single* package with a large API surface is **not** a trigger — only a genuine multi-package monorepo is. **Decision:** - **Threshold not met** (`package_count ≤ 3`) → Continue to §4 (single-scope flow, entirely unchanged). - **Threshold met** (`package_count > 3`) → this repo is a **decomposition candidate**. A threshold firing means the repo *could* decompose, not that it *should* — continue to §3b to decide merge-vs-split. Log: "Auto-decomposition candidate: package_threshold ({value} packages exceeds 3)". ### 3b. Cohesion Check — Merge to One Skill vs Split into N Reached only when §3a flagged a decomposition candidate. Most published monorepos are **cohesive** and produce a better single skill than a pile of fragments — empirically, 5/5 real monorepos (animato 15 crates, trpc, react 38 packages, aws-sdk-js-v3 442 packages, plus zod) were best served as one cohesive skill or a curated few, not one-skill-per-package. Decide deliberately: **Merge into ONE cohesive skill** (override the threshold → continue to §4 single-scope) when **any** of these hold: - **Umbrella facade** — one package re-exports the members: a root or named package whose dependencies include the other workspace members, or which `pub use` / `export *`s them. The facade *is* the public surface (e.g. animato's `crates/animato` re-exporting its 15 sub-crates). - **Shared runtime contract** — the members are consumed together through one entry point, and teaching the shared invariant covers them (e.g. tRPC's adapters around `@trpc/server`; aws-sdk's `new XClient(...) → client.send(new YCommand(...))` shared by every `@aws-sdk/client-*`). - **Internal building blocks** — the members are private/internal pieces of one product, not independently meaningful to a consumer. **Split into N skills** (→ §4a) when: - The members are **independently published with distinct public surfaces serving different concerns**, **and no umbrella re-exports them** — e.g. `react-dom` and `react-server-dom-*` are separate installs with separate jobs, or a federated SDK where a consumer only ever wants one service. Each genuinely-distinct facet earns its own skill. If genuinely unsure, **prefer merge** — a too-broad single skill is recoverable with `US`; N fragmented skills are not. **Facet-coverage guard (merged facet-diverse repos only).** When you merge a repo whose members have genuinely distinct surfaces and you scope to only some of them, record the decision explicitly — never drop a facet silently: - In `scope.notes`, name the in-scope facets **and** the excluded major facets, e.g. _"Scoped to react + react-dom core; excludes react-server-dom-\* (RSC), the specialized renderers (react-art/native/test), and the compiler — forge a separate skill for those."_ - Surface the excluded facets in the analysis report (§7) so the operator can re-scope or forge a companion skill. ### 4. Map Shape to Scope Apply the canonical **Shape → Scope Type Mapping** table from `step-shape-detect.md` (loaded at §1) — the single source of truth for this ladder (the `export_count > 200 → public-api` split, the `language-reference` corpora caveat, and the `stack-compose` decomposition note). ### 5. Generate Include/Exclude Patterns Generate `scope.include` and `scope.exclude` arrays from the detected language and project structure. **Detect the primary language once, deterministically**, via the shared helper — the single source of truth for the manifest→language rule table (§6b resolves the same helper; do not restate the table in prose, where it drifts from the script). **Resolve `{detectLanguageHelper}`** from `{detectLanguageProbeOrder}` (first existing path wins). Pipe the §2 supported-manifest paths (and, for a manifest-less toolchain, the harvested `<tree_paths>`) as the file tree: ```bash echo '{"tree": [<§2 supported manifest paths + harvested tree paths>]}' | uv run {detectLanguageHelper} ``` Read `.language` as `{detected_language}` (and `.confidence`) — the helper owns the `tsconfig.json` JS-vs-TS and `build.gradle` Java-vs-Kotlin disambiguation. §6 reuses `{detected_language}` for the brief's `language` field: detect once. **Default patterns (adjust based on actual project structure):** | Language | Default include | Default exclude | |----------|-----------------|-----------------| | TypeScript/JavaScript | `['src/**/*.ts', 'src/**/*.tsx']` | `['**/*.test.ts', '**/*.spec.ts', '**/node_modules/**']` | | Python | `['src/**/*.py']` or `['{package_name}/**/*.py']` | `['**/*_test.py', '**/test_*.py', '**/tests/**']` | | Rust | `['src/**/*.rs']` | `['**/tests/**', '**/benches/**']` | | Go | `['**/*.go']` | `['**/*_test.go', '**/vendor/**']` | | Java | `['src/main/java/**/*.java']` | `['**/src/test/**']` | | Kotlin | `['src/main/kotlin/**/*.kt']` | `['**/src/test/**']` | | Swift | `['Sources/**/*.swift']` | `['**/Tests/**']` | **Adjust for actual layout:** If the project uses a non-standard layout (e.g., `lib/` instead of `src/`, or a named package directory for Python), detect and use the actual paths. Check for the existence of common source directories (`src/`, `lib/`, `pkg/`, the package name directory) and prefer the one that exists. ### 6. Build Scope and Determine Skill Name Build the scope object: ```yaml scope: type: '{mapped_scope_type}' include: ['{generated_include_patterns}'] exclude: ['{generated_exclude_patterns}'] notes: 'Auto-scoped from shape detection (shape: {shape}, confidence: {confidence}).{corpus_caveat}' ``` Determine the skill name from the project name or package name (kebab-case, lowercase). Use the manifest `name` field if available, otherwise derive from the project directory name. If `{coexistence_suffix}` is non-empty, append it to the skill name. For the brief's `language` field, **reuse `{detected_language}` from §5** — do not re-detect (the §5 helper already resolved js-vs-ts from `tsconfig.json` and Java-vs-Kotlin from the tree). ### 6b. Seed Companion Corpora (whole-language references only) Runs only when §3 classified the repo as `language-reference` **via a whole-language signal** — the `signals` array contains a `grammar_file:` or `tree_triad:` entry (a compiler / interpreter / grammar repo such as rust-lang/rust, TypeScript, CPython). **Skip** when `language-reference` fired only from `parser_producer:` / `parser_dep:` signals (a parser *library* such as pest or lalrpop): there the code **is** the product, so no companion prose is needed and the §6/§7 caveat below does not apply. A whole-language skill's value is in the language's **prose** — the guide/Book, the standard/library API docs, idioms — not the compiler internals. Seed those canonical corpora so the forged skill teaches the language rather than its implementation. **Resolve `{detectLanguageHelper}`** from `{detectLanguageProbeOrder}` and **`{languageCorporaHelper}`** from `{languageCorporaProbeOrder}` (first existing path wins). 1. **Derive the corpus language key `{corpus_language}`.** Prefer `{detected_language}` (from §5) when non-empty. Otherwise — a manifest-less toolchain such as CPython or Ruby — resolve it from the file paths harvested in §2: ```bash echo '{"tree": [<harvested §2 file paths>]}' | uv run {detectLanguageHelper} ``` Use its `language` field. (`.c`/`.h`/`.y` are not in the detector's extension map, so a C-hosted language resolves by its real sources — Ruby via `.rb`.) 2. **Look up canonical corpora:** ```bash uv run {languageCorporaHelper} --language {corpus_language} ``` - exit 0 → parse the `[{url, label, source}]` array (each seed carries `source: language-registry`) → these are `{corpus_seeds}`. - exit 1 → no registry entry (long-tail language) → `{corpus_seeds}` is empty (README detection in brief-skill remains the only source). - exit 2 → log a warning and treat as empty (best-effort; never halt). 3. Record `{N}` = number of seeds and `{corpus_labels}` = comma-joined labels, carried into the brief `doc_urls` (§8) and the honest caveat (§6/§7). 4. Build `{corpus_caveat}` (appended to `scope.notes` in §6/§8 and surfaced in §7) so the operator knows a code-only whole-language skill is low-value: - `{N}` ≥ 1: `" LANGUAGE-REFERENCE CAVEAT: this skill's value is the {corpus_language} prose (guide/Book + std/library docs), not compiler internals. Seeded {N} corpus URL(s): {corpus_labels}. create-skill foregrounds this registry prose as the skill's Language Guide and demotes compiler-internal signatures to a reference-only section — review the forged skill if compiler internals still dominate."` - `{N}` == 0: `" LANGUAGE-REFERENCE CAVEAT: no canonical corpora were found for {corpus_language} (README detection and the registry both came up empty). This skill is LOW-VALUE as code-only — attach the {corpus_language} guide + std/library docs manually (re-run with a doc URL, or enrich via US) before forging."` For a parser-library `language-reference` (skipped above) and every other shape, `{corpus_caveat}` is empty. ### 4a. Multi-Scope Decomposition This section is reached only from §3b when the cohesion check decided to **split** a monorepo (members are independently published with distinct surfaces and no umbrella re-exports them). It replaces §4→§5→§6 for repos that will produce N > 1 skills. **Decompose by workspace package:** Use workspace package discovery from §2 manifest scan results. Each workspace package with its own manifest becomes a separate skill boundary. Name each skill as `{project_name}-{package_name}` (kebab-case); if `{coexistence_suffix}` is non-empty, append it. Trivial workspace members (no source files, no exports) are excluded. **Per-boundary shape→scope mapping:** For each decomposed boundary, apply the shape→scope mapping from §4 independently — re-run the shape→scope heuristic ladder from `step-shape-detect.md` per package using each package's own manifest data. Packages may have different shapes (e.g., a `library-API` core + a `reference-app` CLI). ### 5a. Generate Multi-Scope Patterns For each decomposed boundary, generate include/exclude patterns using the same language-aware rules as §5, but scoped to the boundary's source paths. Monorepo boundaries are rooted at the package path (e.g., `packages/auth/src/**/*.ts` instead of `src/**/*.ts`). ### 6a. Build Multi-Scope For each boundary, build a scope object following the same structure as §6. Include decomposition metadata in `scope.notes`: "Decomposed from {project_name} — boundary {i}/{N} ({reason})" Determine each boundary's skill name from the boundary-derived name (kebab-case, lowercase). If `{coexistence_suffix}` is non-empty, append it to each skill name. Detect the primary language from each boundary's manifest ecosystem (same rules as §6). **Pin data (from §0b):** All N decomposed briefs share the same pin — the pin targets a repo-level ref, not a package-level version. Apply the same `target_version`/`target_ref` values from §0b to all N boundaries at brief write time (§8). After building all N scopes, continue to §7 with the full set of boundaries. ### 7. Write Analysis Report Update {outputFile} with auto-scope results. If the write fails, HARD HALT with exit code 4 (`write-failed`) per `references/headless-contract.md`. **Update frontmatter:** ```yaml stepsCompleted: ['init', 'auto-scope'] lastStep: 'auto-scope' confirmed_units: - name: '{skill_name}' shape: '{shape}' confidence: {confidence} export_count: {export_count} package_count: {package_count} boundary_path: '{boundary_path}' # present only for decomposed units # ... N entries when decomposition is active ``` **When decomposition was triggered (N > 1 units):** Add `decomposition` to frontmatter: ```yaml decomposition: triggered: true reason: 'package_threshold' boundary_count: N ``` Each `confirmed_units` entry includes `boundary_path` — the relative path to the boundary's root (e.g., `packages/core`). Omit the `decomposition` key entirely when single-scope (N = 1). **When single-scope (N = 1):** No `decomposition` key. `confirmed_units` contains a single entry (existing behavior). **Append body section:** For single-scope (unchanged): ```markdown ## Auto-Scope Analysis **Mode:** auto **Shape:** {shape} (confidence: {confidence}) **Signals:** {signals list} **Export Count:** {export_count} **Package Count:** {package_count} **Resolved Scope Type:** {scope_type} **Include Patterns:** {include patterns} **Exclude Patterns:** {exclude patterns} ``` **When the shape is a whole-language `language-reference`** (§6b ran — a `grammar_file:`/`tree_triad:` signal), append a Companion Corpora subsection so the operator sees whether the skill has the prose that makes it useful. The status is computed from the **final** brief `doc_urls` (the entries that will actually be fetched), not the seed count alone: ```markdown ## Companion Corpora (language-reference) **Why:** A whole-language skill's value is its prose (guide/Book, std/library docs, idioms), not compiler internals. **Corpora in brief doc_urls:** {final_doc_urls_count} - {label}: {url} # one line per doc_urls entry **Status:** {ATTACHED — canonical corpora present | DEGRADED — code-only, no canonical corpora; attach the {corpus_language} guide + std/library docs before forging} ``` For multi-scope (N > 1): ```markdown ## Auto-Scope Analysis — Decomposition ({N} skills) **Mode:** auto **Decomposition:** {reason} ({N} boundaries) **Parent Shape:** {shape} (confidence: {confidence}) **Export Count:** {export_count} **Package Count:** {package_count} ### Boundary 1: {boundary_name} **Scope Type:** {scope_type} **Boundary Path:** {boundary_path} **Include Patterns:** {include patterns} **Exclude Patterns:** {exclude patterns} **Rationale:** {boundary_rationale} ### Boundary 2: {boundary_name} ... ``` ### 8. Write Skill Brief **For each confirmed unit** (1 for single-scope, N for decomposition): Create directory `{forge_data_folder}/{skill_name}/` if it does not exist. If a brief write fails, HARD HALT with exit code 4 (`write-failed`) per `references/headless-contract.md`. Write `{forge_data_folder}/{skill_name}/skill-brief.yaml` conforming to the skill-brief schema (`{briefSchemaPath}`): ```yaml name: '{skill_name}' version: '{detected_version or 1.0.0}' source_repo: '{project_path}' language: '{detected_language}' scope: type: '{scope_type}' include: - '{include_patterns}' exclude: - '{exclude_patterns}' notes: 'Auto-scoped from shape detection (shape: {shape}, confidence: {confidence}).{corpus_caveat}' description: '{1-3 sentence description based on shape, language, and manifest name}' forge_tier: '{forge_tier}' created: '{current_date}' created_by: '{user_name}' ``` **Companion corpora (whole-language references).** When §6b produced `{corpus_seeds}` (`{N}` ≥ 1), add them as the brief's `doc_urls` so the language's prose is fetched and assembled alongside the code: ```yaml doc_urls: - { url: '{seed.url}', label: '{seed.label}', source: '{seed.source}' } # one entry per §6b seed; source is 'language-registry' ``` These are the brief's *existing* `doc_urls`; brief-skill's README detection then merges additional discovered docs on top (existing entries win). **When `{N}` is 0, omit the `doc_urls` key entirely** — the schema requires at least one entry when the key is present. **When decomposition is active (N > 1 units):** Loop over all N boundaries. For each boundary: - `name` is the boundary-derived skill name (e.g., `my-monorepo-core`) - `include`/`exclude` patterns are boundary-scoped (from §5a) - `scope.notes` includes decomposition context: "Decomposed from {project_name} ({N} skills) — boundary {i}/{N}: {boundary_description}" - `description` references the parent project and boundary role (e.g., "Core library package of the my-monorepo project, providing...") - All N briefs share the same `version`, `source_repo`, `language`, `forge_tier`, `created`, `created_by` values as the parent project **Version detection:** Attempt to auto-detect the source version per the version detection rules in `{briefSchemaPath}`. Fall back to `1.0.0` if detection fails. **Pin data (from §0b):** When `{pinned_ref}` is non-null, enrich the brief with pin data: - If `{pinned_ref_type}` is `"tag"`: set `target_version` = `{pinned_version}`, `target_ref` = `{pinned_ref}`, `version` = `{pinned_version}`. - If `{pinned_ref_type}` is `"branch"`: set `target_ref` = `{pinned_ref}`, leave `target_version` = null, `version` = auto-detected or `1.0.0`. - If `{pinned_ref_type}` is `"local"`: set `target_version` = `{pinned_version}`, `target_ref` = null, `version` = `{pinned_version}`. When `{pinned_ref}` is null (no pin, no releases): leave `target_version` = null, `target_ref` = null — existing version detection applies unchanged. In the docs-only path (`references/auto-docs-only.md`), `--pin` is ignored (already skipped at §0b). No changes to that path. ### 9. Emit Result Envelope Emit the `SKF_ANALYZE_RESULT_JSON` envelope on stdout: ``` SKF_ANALYZE_RESULT_JSON: {"status":"success","report_path":"{outputFile_path}","brief_paths":["{brief_path_1}","{brief_path_2}",...,"{brief_path_N}"],"unit_counts":{"confirmed":N,"skipped":0,"maybe":0},"exit_code":0,"halt_reason":null,"mode":"auto"} ``` `brief_paths` contains N paths (one per confirmed unit). `unit_counts.confirmed` is N. If `{coexistence_suffix}` is non-empty (i.e., [A]longside was selected in §0c), include `"coexistence":"alongside"` in the envelope. When `{pinned_ref}` is non-null, include `"pinned_ref":"{pinned_ref}"` and `"pinned_version":"{pinned_version}"` in the envelope. These flow downstream to BS/CS for provenance recording. When `{pinned_ref}` is null, omit these fields. ### 10. Write Result Contract Write the result contract per `shared/references/output-contract-schema.md`: the per-run record at `{forge_data_folder}/analyze-source-result-{YYYYMMDD-HHmmss}.json` (UTC timestamp, resolution to seconds) and a copy at `{forge_data_folder}/analyze-source-result-latest.json`. `outputs` lists all N brief paths and `summary` includes the brief count N. If the per-run record cannot be written, HARD HALT with exit code 4 (`write-failed`) per `references/headless-contract.md`. If `{onCompleteCommand}` is non-empty, invoke it now with `--result-path={result_json_path}`. ### 11. Chain to Health Check Load, read fully, then execute {nextStepFile} to run the shared workflow health check. -
step-shape-detect.md 5.5 KB
--- shapeDetectProbeOrder: - '{project-root}/_bmad/skf/shared/scripts/skf-shape-detect.py' - '{project-root}/src/shared/scripts/skf-shape-detect.py' --- # Shape Detection Reference Reference document for invoking `skf-shape-detect.py` — the shared shape classification module. Loaded by `step-auto-scope.md` for auto-scope analysis. ## Invocation Contract **Resolve `{shapeDetectHelper}`** from `{shapeDetectProbeOrder}`; first existing path wins; HALT if neither resolves. **Command:** ``` uv run {shapeDetectHelper} --repo-url <url> \ --manifests <path1,path2,...> \ --grammar-files <g1,g2,...> --tree-paths <d1/,d2/,file,...> ``` **Arguments:** | Arg | Required | Description | |-----|----------|-------------| | `--repo-url` | Yes | Repository URL (context only — no cloning performed) | | `--manifests` | Yes | Comma-separated local file paths to manifest files (may be empty when a tree-level signal carries the classification) | | `--grammar-files` | No | Comma-separated repo-relative grammar files (`*.y`, `*.g4`, `*.pest`, `Grammar/python.gram`, ...) — a whole-language signal | | `--tree-paths` | No | Comma-separated repo-relative directory (trailing `/`) and structural file signals harvested from the clone (a `compiler/` dir, a lexer+parser+ast triad) | **Supported manifests:** `package.json`, `pyproject.toml`, `Cargo.toml`, `go.mod`, `pom.xml`, `build.gradle`, `build.gradle.kts`, `Package.swift` ## Output Schema JSON object on stdout: | Field | Type | Description | |-------|------|-------------| | `shape` | string | `library-API` \| `reference-app` \| `language-reference` \| `stack-compose` \| `unknown` | | `signals` | array[string] | Human-readable evidence strings | | `confidence` | float | 0.0–1.0 | | `export_count` | integer | Total public-facing exports detected | | `package_count` | integer | Distinct packages detected | ## Exit Codes | Code | Meaning | Consumer Action | |------|---------|-----------------| | 0 | Shape classified (not unknown) | Use shape result for scope mapping | | 1 | Unknown shape (no heuristic matched) | Fall back to interactive mode | | 2 | Error (invalid args, missing/unreadable files, parse failure) | HARD HALT with `resolution-failure` | On exit code 2, error details are written to stderr as JSON: `{"error": "message", "code": "ERROR_CODE"}` ## Shape → Scope Type Mapping | Shape | scope.type | Condition | |-------|------------|-----------| | `library-API` | `full-library` | export_count ≤ 200 | | `library-API` | `public-api` | export_count > 200 (surface too large for full coverage) | | `reference-app` | `reference-app` | Direct mapping — apps, CLIs, demos | | `language-reference` | `full-library` | Language tools/parsers are library-shaped from a skill perspective. **Corpora-dependent** for a *whole-language* reference (a `grammar_file:`/`tree_triad:` signal — a compiler/interpreter): its value is the language's prose (guide/Book + std/library docs), not compiler internals, so step-auto-scope.md §6b seeds companion corpora and §6/§7 record an honest DEGRADED caveat when none are found (mirrors the §3b facet-coverage guard). A parser *library* (`parser_producer:`/`parser_dep:`) is exempt — its code is the product. | | `stack-compose` | `full-library` | Decomposition candidate when `package_count > 3` — cohesion-checked in step-auto-scope.md §3b | | `unknown` | N/A | Triggers fallback to interactive mode | ## Decomposition Thresholds When auto-scope detects a multi-package monorepo, it may recommend multi-skill decomposition instead of producing one unwieldy skill. The threshold is evaluated in step-auto-scope.md §3a. | Threshold | Value | Signal | Decomposition Path | |-----------|-------|--------|-------------------| | Multi-package / monorepo | `package_count > 3` | Shape detection `package_count` | Cohesion check (§3b): merge to one skill or split per package | `package_count > 3` makes a **monorepo** a decomposition candidate; step-auto-scope.md §3b then decides merge-vs-split. It is empirically validated (fires on real 15-, 38-, and 442-package workspaces). A *single* package with a large API surface is **not** decomposed — it produces one cohesive skill that `skf-create-skill`'s auto-shard splits into `references/` shards at the 400-line ceiling. When neither threshold is met, the single-scope flow proceeds unchanged. ## Heuristic Ladder The five-shape heuristic ladder applies in order (first match wins): 1. **language-reference** — parser/grammar/language-toolchain project. Signals, strongest first: a hand-written-compiler tree structure (a dedicated `compiler/` directory with a lexer+parser+ast triad plus a codegen/VM/type-checker member — catches rustc, TypeScript, Go); a declared grammar file (`Grammar/python.gram`, a root `parse.y`, a `*.g4` — catches CPython, Ruby); the repo's own name being a known parser/grammar tool (pest, lalrpop, lark — the producer); or a parser-generator dependency (a DSL built on antlr4/lalrpop — the consumer). Delegating consumers (formatters, linters, bundlers that depend on a parser) and markup/DSL parsers (CSS, markdown, GraphQL) are excluded. 2. **stack-compose** — multi-ecosystem composite project. Signals: manifests from 2+ distinct ecosystems 3. **reference-app** — application, CLI, or demo project. Signals: npm `bin` field, Rust `[[bin]]`, framework deps (next, fastapi, axum, etc.) 4. **library-API** — library exposing a programmatic API. Signals: `main`/`module`/`exports` fields, `[lib]` target, export count 5. **unknown** — no heuristic matched -
unit-detection-heuristics.md 9.3 KB
<!-- Config: communicate in {communication_language}. --> # Unit Detection Heuristics ## Purpose Rules for identifying discrete skillable units within a project. A "skillable unit" is a self-contained component with clear boundaries that can be documented as an independent skill. ## Detection Signals ### Strong Signals (High Confidence) | Signal | Description | Example | |------------------------------------------------|----------------------------------------------|------------------------------------| | Independent package.json / Cargo.toml / go.mod | Unit has its own dependency manifest | `packages/auth/package.json` | | Separate entry point | Unit has a main/index file | `services/api/src/index.ts` | | Docker/service definition | Unit runs as an independent service | `docker-compose.yml` service entry | | Distinct export surface | Unit exports a public API consumed by others | `src/lib/index.ts` with re-exports | | Workspace member | Listed in root workspace configuration | `pnpm-workspace.yaml` packages | ### Moderate Signals (Medium Confidence) | Signal | Description | Example | |--------------------------|---------------------------------------------------|-------------------------------------| | Directory depth boundary | Top-level directory with self-contained structure | `src/modules/payments/` | | Naming convention | Follows organizational naming pattern | `@org/package-name` | | Separate test suite | Has its own test directory or config | `packages/auth/__tests__/` | | README.md presence | Has documentation at directory level | `libs/utils/README.md` | | CI/CD pipeline reference | Referenced in build/deploy configuration | `.github/workflows/deploy-auth.yml` | ### Weak Signals (Low Confidence — Require Corroboration) | Signal | Description | Example | |--------------------|---------------------------------------------|-------------------------------| | Large directory | Many files in a subtree | 50+ files under one directory | | Comment boundaries | Code comments marking sections | `// --- Auth Module ---` | | Import clustering | Files that import primarily from each other | Tight import graph cluster | ## Boundary Classification ### Service Boundary - Independent deployable unit - Own process, port, or container - Clear network interface (REST, gRPC, message queue) - Scope type: `full-library` ### Package Boundary - Workspace member or independently versioned package - Own dependency manifest - Exports consumed by other packages - Scope type: `full-library` or `specific-modules` ### Module Boundary - Logical grouping within a single package - Shared namespace or directory structure - Internal cohesion, external coupling through defined interface - Scope type: `specific-modules` or `public-api` ### Library Boundary - Third-party dependency with significant project-specific usage patterns - Custom wrappers, configurations, or integration code - Scope type: `public-api` ### Component Library Boundary - Contains a component registry or catalog file (array of component definitions with IDs, names, categories) - Has `components/`, `packages/components/`, or similar multi-component directory structure - Multiple design system variant directories (e.g., `react-shadcn/`, `react-baseui/`, `react-carbon/`) - Significant demo/story/example file ratio (>30% of total files) - CLI-based installation pattern (e.g., `npx <tool> add <component-id>`) - Props interfaces outnumber function signatures as primary API surface - Scope type: `component-library` ### Composite Boundary - Two or more Package or Module boundaries that only deliver value together (no constituent is independently useful to the skill consumer) - Hard cross-boundary dependency: constituents share types, traits, or interfaces that are not re-exported through a single barrel — consumers must import from multiple constituents to use the integration - Common pattern: a set of crates/packages in the same repo that implement a protocol together (e.g., plugin crates for a framework, verification + encoding halves of a cryptographic library) - Scope type: inherits from the dominant constituent (typically `full-library` or `specific-modules`) **Detection heuristic (apply after initial classification, before user confirmation):** 1. Among the qualifying units, find groups of ≥2 boundaries where either: - **Mutual hard dependency:** Every constituent imports from at least one other constituent in the group, AND no constituent's public API is self-contained (removing any one breaks the others) - **Shared integration surface:** Constituents share types/traits defined in one constituent but consumed by all others, AND the consuming constituents have no independent barrel (their value depends on the shared definitions) 2. For each detected group, propose merging into a single composite unit: - Name: `{common-prefix}` or `{integration-name}` (derive from shared namespace or repo name) - Constituents: list of merged boundary names and paths - Rationale: which heuristic triggered (mutual hard dependency or shared integration surface) 3. The merge is a **recommendation** — present to user for confirmation in identify-units §3b ## Disqualification Rules Do not recommend a boundary as a skillable unit when: 1. **Too small**: Fewer than 3 source files or 100 lines of code 2. **Generated code**: Auto-generated files (protobuf, GraphQL codegen, etc.) 3. **Pure configuration**: Only config files with no logic 4. **Test-only**: Test utilities with no production code 5. **Vendor/dependency**: Third-party code copied into project 6. **Already skilled**: Existing skill found in forge_data_folder (recommend update-skill instead) ## Script/Asset Detection Signals During per-unit analysis, check for scripts and assets alongside code exports. **Script signals:** | Strength | Signal | Example | |----------|-----------------------------------------------------------------------------------------------|-----------------------------------------------------| | Strong | Entry point in `package.json` `bin`, Cargo.toml `[[bin]]`, pyproject.toml `[project.scripts]` | `"bin": { "migrate": "scripts/migrate.js" }` | | Strong | Shebang + executable file | `#!/usr/bin/env python` in `scripts/setup.py` | | Moderate | File in `scripts/`, `bin/`, `tools/`, `cli/` directory | `scripts/validate.sh` | | Moderate | CI/CD reference to script | `.github/workflows/test.yml` runs `scripts/test.sh` | **Asset signals:** | Strength | Signal | Example | |----------|--------------------------------------------------------------------------------|----------------------------------| | Strong | JSON Schema file with `$schema` key | `schemas/config.schema.json` | | Strong | Config template with `.example` or `.template` extension | `config.yaml.example` | | Moderate | File in `assets/`, `templates/`, `schemas/`, `configs/`, `examples/` directory | `templates/report.hbs` | | Moderate | OpenAPI/GraphQL definition | `openapi.json`, `schema.graphql` | **Per-unit output:** Record `has_scripts: boolean`, `has_assets: boolean`, `script_files: string[]`, `asset_files: string[]`. **Disqualify:** Generated files (dist/, build/), vendored dependencies, IDE configs (.vscode/, .idea/), binary files (.so, .dll, .jar). ## Stack Skill Candidate Detection Flag units as stack skill candidates when: 1. **Co-import frequency**: Two or more units are imported together in 3+ files 2. **Integration adapter**: A unit exists primarily to bridge two other units 3. **Shared state**: Multiple units read/write to the same data store 4. **Orchestration layer**: A unit coordinates calls across multiple other units ## Tier-Aware Scanning Depth | Forge Tier | Scanning Approach | |------------|----------------------------------------------------------------------------------------------------| | Quick | File structure analysis: directory trees, manifest files, entry points, naming conventions | | Forge | AST analysis: export surfaces, import graphs, dependency trees, type hierarchies | | Forge+ | AST + CCC: semantic file pre-ranking before structural analysis, CCC signals for relevance scoring | | Deep | AST + QMD: temporal evolution, refactoring patterns, semantic relationships, architectural drift |
-
-
templates
-
analysis-report-template.md 583 B
--- workflowType: 'analyze-source' stepsCompleted: [] lastStep: '' lastContinued: '' date: '' user_name: '' project_name: '' project_paths: [] forge_tier: '' existing_skills: [] confirmed_units: [] stack_skill_candidates: [] nextWorkflow: '' --- # Source Analysis Report: {{project_name}} ## Project Scan [Appended by scan-project] ## Identified Units [Appended by identify-units] ## Export Map [Appended by map-and-detect] ## Integration Points [Appended by map-and-detect] ## Recommendations [Appended by recommend] ## Generation Results [Appended by generate-briefs]
-
-
customize.toml 2.1 KB
# DO NOT EDIT -- overwritten on every update. # # Workflow customization surface for skf-analyze-source. # Team overrides: _bmad/custom/skf-analyze-source.toml (under {project-root}) # Personal overrides: _bmad/custom/skf-analyze-source.user.toml (under {project-root}) [workflow] # --- Configurable below. Overrides merge per BMad structural rules: --- # scalars: override wins • arrays (persistent_facts, activation_steps_*): append # arrays-of-tables with `code`/`id`: replace matching items, append new ones. # Steps to run before the standard activation (uv probe, config load). # Overrides append. Use for org-wide pre-flight checks (auth, network, # compliance) that must precede any source-analysis work. activation_steps_prepend = [] # Steps to run after activation but before the first stage executes. # Overrides append. Use for context loads or banner customization that # should run once activation completes successfully. activation_steps_append = [] # Persistent facts the workflow keeps in mind for the whole run # (house style, naming conventions, scope guardrails). # Overrides append. # # Each entry is either: # - a literal sentence, e.g. "Recommended skills must align with our agent stack." # - a file reference prefixed with `file:`, e.g. # "file:{project-root}/docs/analysis-style.md" (globs supported; file # contents are loaded and treated as facts). persistent_facts = [ "file:{project-root}/**/project-context.md", ] # --- Optional asset overrides --- # # Lift the canonical asset paths so orgs can substitute house-style copies # without forking the skill. Empty string = use the bundled default. unit_detection_heuristics_path = "" brief_schema_path = "" analysis_report_template_path = "" # Pipeline-integration hook invoked after the final result JSON is written # (see generate-briefs.md). The command is called as: # <on_complete> --result-path=<absolute_path_to_result_json> # Useful for Slack notifications, dashboard ingest, CI hooks, or chaining # into checkpoint-preview / a brief linter. Failures are logged to # workflow_warnings[] but never fail the workflow. # # Empty string = no-op (default). on_complete = "" -
SKILL.md 7.9 KB
--- name: skf-analyze-source description: Discover what to skill in a large repo and produce recommended skill briefs. Use when the user requests to "analyze source for skills" or "discover skill opportunities." --- # Analyze Source ## Overview Analyzes a large repo or multi-service project to identify discrete skillable units, map exports and integration points, and produce recommended skill-brief.yaml files as the primary entry point for brownfield onboarding. The analysis must be thorough enough to produce actionable briefs, but scoped enough to avoid overwhelming the user with false positives. Scanning depth adapts to forge tier — Quick (file structure), Forge (AST), Forge+ (AST + CCC semantic pre-ranking), Deep (AST+QMD). ## Conventions - Bare paths (e.g. `references/<name>.md`) resolve from the skill root. - `references/` holds prompt content carved out of SKILL.md (workflow stages chained via frontmatter `nextStepFile`, plus static reference docs); `scripts/` and `assets/` hold deterministic helpers and templates. - `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives, if present). - `{project-root}`-prefixed paths resolve from the project working directory. - `{skill-name}` resolves to the skill directory's basename. ## Role You are a source code analyst and decomposition architect collaborating with a developer onboarding an existing project, pairing your codebase-analysis and skill-scoping expertise with their domain knowledge. ## Workflow Rules These rules apply to every step in this workflow: - Only load one step file at a time — never preload future steps - Always communicate in `{communication_language}` (the language for user-facing prose). Written artifact text — the per-unit recommendation `description` and `scope.notes` persisted into `skill-brief.yaml` — is in `{document_output_language}`; per-step rules call this out where it applies. The two values may be the same. - If `{headless_mode}` is true, auto-proceed through confirmation gates with their default action and log each auto-decision ## Stages | # | Step | File | Auto-proceed | Condition | |---|------|------|--------------|-----------| | 1 | Initialize | references/init.md | Yes | Always | | 1a | Auto-Scope | references/step-auto-scope.md | Yes | `[auto]` mode only — bypasses steps 2–6; owns pin resolution, coexistence detection, and the docs-only short-circuit | | 1b | Continue (session resume) | references/continue.md | Yes | Always | | 2 | Scan Project | references/scan-project.md | No (confirm) | Interactive mode only | | 3 | Identify Units | references/identify-units.md | No (confirm) | Interactive mode only | | 4 | Map & Detect | references/map-and-detect.md | Yes | Interactive mode only | | 5 | Recommend | references/recommend.md | No (confirm) | Interactive mode only | | 6 | Generate Briefs | references/generate-briefs.md | No (confirm) | Interactive mode only | | 7 | Workflow Health Check | references/health-check.md | Yes | Always | **Auto mode path:** With `[auto]` present, init routes directly to step 1a. Step 1a may confirm N > 1 units (a monorepo splits into N briefs / N `brief_paths`, or merges to one), and routes docs-only targets to `references/auto-docs-only.md`. **Shape detection reference:** `references/step-shape-detect.md` — loaded by step 1a as a reference doc (not a chained step). ## Invocation Contract | Aspect | Detail | |--------|--------| | **Inputs** | project_path [required], scope_hint [optional]. `project_path` is a GitHub repo URL or a local filesystem path. In `[auto]` mode it may also be a documentation URL — step 1a classifies the URL type and routes docs-only targets (see Stages row 1a). | | **Headless inputs** | `--project-path <path>` (skip Step 1 project-path prompt; in `[auto]` mode also accepts documentation URLs for docs-only mode), `--scope-hint <text>` (skip Step 1 scope-hint prompt), `--intent-hint <text>` (pre-supply analysis intent; drives recommendation ranking in Step 5), `--pin <version>` (`[auto]` mode only — pin to a specific version tag or branch; accepts semver tags, git tags, and branch names; when absent, resolves to the latest release tag; interactive/headless runs use `--target-refs`/`--target-ref` instead) | | **Headless flag** | `--headless` / `-H` flips every confirm gate to auto-proceed | | **Auto flag** | `[auto]` bracket modifier — activates auto-scope mode (step 1a; see **Auto mode path** above). Pipelines pass this as `AN[auto]`. Requires `--project-path`. | | **Gates** | steps 2/3/5: Confirm Gate [C]; step 6: Confirm Gate [Y] (write briefs) — all skipped in auto mode | | **Outputs** | analysis-report.md, skill-brief.yaml files (one per recommended unit); final `SKF_ANALYZE_RESULT_JSON` line on stdout when `{headless_mode}` is true. In auto mode, the envelope includes `"mode":"auto"`. | | **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true | | **Exit codes** | See `references/headless-contract.md` | ## Headless Result Contract Headless/pipeline runs emit a single-line `SKF_ANALYZE_RESULT_JSON` envelope — on **stdout** for the terminal success path (step 6 or step 1a), on **stderr** with `status: "error"` for every HARD HALT — and exit with a stable code per failure class. The envelope shape, the `halt_reason` enum, and the exit-code table live in `references/headless-contract.md`; step files emit the concrete instance at each site. ## On Activation 1. Load config from `{project-root}/_bmad/skf/config.yaml` and resolve: - `project_name`, `output_folder`, `user_name`, `communication_language`, `document_output_language`, `forge_data_folder`, `skills_output_folder`, `sidecar_path` - If the config cannot be loaded, HARD HALT with exit code 2 (`input-missing`) per `references/headless-contract.md` — the workflow has no forge context to run against. 2. **Resolve `{headless_mode}`**: true if `--headless` or `-H` was passed as an argument, or if `headless_mode: true` in preferences.yaml. Default: false. 3. **Resolve workflow customization.** Run: ```bash python3 {project-root}/_bmad/scripts/resolve_customization.py \ --skill {skill-root} --key workflow ``` 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) - `_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 — the bundled defaults are an empty string for each path scalar. Apply the path-scalar fallback now, so stage files reference the resolved variable with no conditional at the usage site. For each scalar, if the merged value is empty or absent, use the bundled default: - `{unitDetectionHeuristicsPath}` ← `workflow.unit_detection_heuristics_path` if non-empty, else `references/unit-detection-heuristics.md` - `{briefSchemaPath}` ← `workflow.brief_schema_path` if non-empty, else `assets/skill-brief-schema.md` - `{analysisReportTemplatePath}` ← `workflow.analysis_report_template_path` if non-empty, else `templates/analysis-report-template.md` - `{onCompleteCommand}` ← `workflow.on_complete` if non-empty, else empty string (hook invocation skipped) Stash all four as workflow-context variables. A non-empty value lets an org swap in a house-style copy (or wire a pipeline hook) without forking the skill. Apply the array surfaces too: run `workflow.activation_steps_prepend` now, treat `workflow.persistent_facts` as standing context for the run (`file:`-prefixed entries load their file/glob contents as facts), then run `workflow.activation_steps_append` after activation. 4. Load, read the full file, and then execute `references/init.md` to begin the workflow.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.