workflow-toolkit 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/ponytail/SKILL.md +111 -0
- package/.agents/skills/ponytail-audit/SKILL.md +37 -0
- package/.agents/skills/ponytail-debt/SKILL.md +41 -0
- package/.agents/skills/ponytail-gain/SKILL.md +47 -0
- package/.agents/skills/ponytail-help/SKILL.md +70 -0
- package/.agents/skills/ponytail-review/SKILL.md +52 -0
- package/.agents/skills/prompt-review/SKILL.md +60 -0
- package/.agents/skills/wtk/SKILL.md +64 -0
- package/.agents/skills/wtk/references/artifacts.md +82 -0
- package/.agents/skills/wtk/references/evidence.md +68 -0
- package/.agents/skills/wtk/references/git.md +75 -0
- package/.agents/skills/wtk/references/test-contract.md +76 -0
- package/.agents/skills/wtk/references/validation.md +127 -0
- package/.agents/skills/wtk-config/SKILL.md +102 -0
- package/.agents/skills/wtk-config/assets/agents/claude/deep-reviewer.md +40 -0
- package/.agents/skills/wtk-config/assets/agents/claude/designer.md +57 -0
- package/.agents/skills/wtk-config/assets/agents/claude/explorer.md +41 -0
- package/.agents/skills/wtk-config/assets/agents/claude/implementer.md +57 -0
- package/.agents/skills/wtk-config/assets/agents/claude/planner.md +68 -0
- package/.agents/skills/wtk-config/assets/agents/claude/verifier.md +80 -0
- package/.agents/skills/wtk-config/assets/agents/codex/deep-reviewer.toml +41 -0
- package/.agents/skills/wtk-config/assets/agents/codex/designer.toml +55 -0
- package/.agents/skills/wtk-config/assets/agents/codex/explorer.toml +35 -0
- package/.agents/skills/wtk-config/assets/agents/codex/implementer.toml +52 -0
- package/.agents/skills/wtk-config/assets/agents/codex/planner.toml +66 -0
- package/.agents/skills/wtk-config/assets/agents/codex/verifier.toml +78 -0
- package/.agents/skills/wtk-config/assets/agents/cursor/deep-reviewer.md +38 -0
- package/.agents/skills/wtk-config/assets/agents/cursor/designer.md +55 -0
- package/.agents/skills/wtk-config/assets/agents/cursor/explorer.md +39 -0
- package/.agents/skills/wtk-config/assets/agents/cursor/implementer.md +55 -0
- package/.agents/skills/wtk-config/assets/agents/cursor/planner.md +66 -0
- package/.agents/skills/wtk-config/assets/agents/cursor/verifier.md +79 -0
- package/.agents/skills/wtk-config/scripts/ad-index.py +123 -0
- package/.agents/skills/wtk-config/scripts/repository_intelligence.py +671 -0
- package/.agents/skills/wtk-config/scripts/workflow_config.py +972 -0
- package/.agents/skills/wtk-deep-review/SKILL.md +164 -0
- package/.agents/skills/wtk-deep-review/assets/PROMPT.md +56 -0
- package/.agents/skills/wtk-deep-review/assets/REVIEW_UI.html +1340 -0
- package/.agents/skills/wtk-deep-review/assets/findings.schema.json +113 -0
- package/.agents/skills/wtk-deep-review/references/context-pack.md +88 -0
- package/.agents/skills/wtk-deep-review/references/orchestration.md +164 -0
- package/.agents/skills/wtk-deep-review/references/output-contracts.md +149 -0
- package/.agents/skills/wtk-deep-review/references/publish-github.md +83 -0
- package/.agents/skills/wtk-deep-review/references/state-and-learnings.md +64 -0
- package/.agents/skills/wtk-deep-review/references/subagent-runtimes.md +33 -0
- package/.agents/skills/wtk-deep-review/references/taxonomy.md +70 -0
- package/.agents/skills/wtk-deep-review/scripts/_common.py +371 -0
- package/.agents/skills/wtk-deep-review/scripts/build_jobs.py +561 -0
- package/.agents/skills/wtk-deep-review/scripts/build_knowledge.py +349 -0
- package/.agents/skills/wtk-deep-review/scripts/build_manifest.py +479 -0
- package/.agents/skills/wtk-deep-review/scripts/graft_context.py +160 -0
- package/.agents/skills/wtk-deep-review/scripts/graphify_context.py +75 -0
- package/.agents/skills/wtk-deep-review/scripts/merge_findings.py +313 -0
- package/.agents/skills/wtk-deep-review/scripts/render_html.py +240 -0
- package/.agents/skills/wtk-deep-review/scripts/render_review.py +282 -0
- package/.agents/skills/wtk-deep-review/scripts/run_jobs.py +436 -0
- package/.agents/skills/wtk-deep-review/scripts/token_metrics.py +369 -0
- package/.agents/skills/wtk-discover/SKILL.md +270 -0
- package/.agents/skills/wtk-discover/references/document-format.md +149 -0
- package/.agents/skills/wtk-implement/SKILL.md +95 -0
- package/.agents/skills/wtk-implement/references/checklist-format.md +116 -0
- package/.agents/skills/wtk-implement/references/screens.md +129 -0
- package/.agents/skills/wtk-implement/references/test-policy.md +152 -0
- package/.agents/skills/wtk-implement/references/verify.md +297 -0
- package/.agents/skills/wtk-knowledge-check/SKILL.md +16 -0
- package/.agents/skills/wtk-knowledge-check/scripts/check.ts +502 -0
- package/.agents/skills/wtk-knowledge-check/scripts/cli.ts +32 -0
- package/.agents/skills/wtk-knowledge-check/scripts/frontmatter.ts +44 -0
- package/.agents/skills/wtk-lean/.skill-meta.json +6 -0
- package/.agents/skills/wtk-lean/NOTICE.md +18 -0
- package/.agents/skills/wtk-lean/SKILL.md +137 -0
- package/.agents/skills/wtk-lean/references/build.md +168 -0
- package/.agents/skills/wtk-lean/references/checks.md +253 -0
- package/.agents/skills/wtk-lean/references/memory.md +156 -0
- package/.agents/skills/wtk-lean/references/plan.md +514 -0
- package/.agents/skills/wtk-lean/references/verify.md +336 -0
- package/.agents/skills/wtk-lean/scripts/check_commit.py +121 -0
- package/.agents/skills/wtk-lean/scripts/fixtures/checks.md +98 -0
- package/.agents/skills/wtk-lean/scripts/fixtures/plan.md +119 -0
- package/.agents/skills/wtk-lean/scripts/fixtures/verification.md +41 -0
- package/.agents/skills/wtk-lean/scripts/lessons.py +412 -0
- package/.agents/skills/wtk-lean/scripts/selftest.py +401 -0
- package/.agents/skills/wtk-lean/scripts/validate_checks.py +417 -0
- package/.agents/skills/wtk-lean/scripts/validate_plan.py +580 -0
- package/.agents/skills/wtk-lean/scripts/validate_verification.py +357 -0
- package/.agents/skills/wtk-plan/SKILL.md +102 -0
- package/.agents/skills/wtk-plan/references/document-format.md +222 -0
- package/.agents/skills/wtk-qa/SKILL.md +14 -0
- package/.agents/skills/wtk-qa-execute/SKILL.md +115 -0
- package/.agents/skills/wtk-qa-execute/references/fix-loop.md +23 -0
- package/.agents/skills/wtk-qa-execute/references/session-protocol.md +25 -0
- package/.agents/skills/wtk-qa-plan/SKILL.md +111 -0
- package/.agents/skills/wtk-qa-plan/references/profile.md +35 -0
- package/.agents/skills/wtk-ship/SKILL.md +49 -0
- package/.agents/skills/wtk-ship/remediation.py +170 -0
- package/.agents/skills/wtk-ship/scripts/close_feature.py +83 -0
- package/.agents/skills/wtk-ship/scripts/review_convergence.py +373 -0
- package/.wtk.toml.example +93 -0
- package/AGENTS.md +129 -0
- package/NOTICE.md +12 -0
- package/README.md +440 -0
- package/bin/wtk.js +25 -0
- package/docs/toolkit/README.md +56 -0
- package/docs/toolkit/decisions.md +61 -0
- package/docs/toolkit/guidelines/CONTEXT-BUDGET.md +64 -0
- package/docs/toolkit/guidelines/DX.md +67 -0
- package/docs/toolkit/guidelines/FRONTEND.md +73 -0
- package/docs/toolkit/guidelines/KNOWLEDGE-WIKI.md +128 -0
- package/docs/toolkit/guidelines/MODELING.md +134 -0
- package/docs/toolkit/guidelines/QA-EXECUTION.md +46 -0
- package/docs/toolkit/guidelines/QA-SCENARIOS.md +165 -0
- package/docs/toolkit/guidelines/REVIEW-ROUNDS.md +160 -0
- package/docs/toolkit/guidelines/SECURITY.md +160 -0
- package/docs/toolkit/guidelines/UI-UX.md +117 -0
- package/docs/toolkit/guidelines/WORKFLOW-MEMORY.md +73 -0
- package/docs/toolkit/guidelines.md +50 -0
- package/docs/toolkit/loop.md +77 -0
- package/docs/toolkit/purpose.md +51 -0
- package/docs/toolkit/repository-intelligence.md +53 -0
- package/docs/toolkit/reviews.md +77 -0
- package/knowledge/AGENTS.md +316 -0
- package/knowledge/raw/README.md +17 -0
- package/package.json +74 -0
- package/scripts/install_security_skills.py +816 -0
- package/scripts/installer/engine.js +254 -0
- package/scripts/installer/knowledge.js +28 -0
- package/scripts/installer/packets.js +77 -0
- package/scripts/installer/terminal.js +98 -0
- package/scripts/installer/transaction.js +75 -0
- package/skills-lock.json +92 -0
- package/templates/adoption/agents/core.md +23 -0
- package/templates/adoption/agents/quality.md +3 -0
- package/templates/adoption/knowledge/wiki/architecture/index.md +3 -0
- package/templates/adoption/knowledge/wiki/decisions/index.md +3 -0
- package/templates/adoption/knowledge/wiki/design/index.md +3 -0
- package/templates/adoption/knowledge/wiki/domain/index.md +3 -0
- package/templates/adoption/knowledge/wiki/index.md +3 -0
- package/templates/adoption/knowledge/wiki/log.md +3 -0
- package/templates/adoption/knowledge/wiki/open-questions/index.md +3 -0
- package/templates/adoption/knowledge/wiki/product/index.md +3 -0
- package/templates/adoption/knowledge/wiki/research/index.md +3 -0
- package/templates/adoption/product/AGENT-CONTEXT.md +25 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Discover repository-local review knowledge (bootstrap; writes under --out).
|
|
3
|
+
|
|
4
|
+
Builds knowledge.json from the selected manifest paths and creates a
|
|
5
|
+
rules.template.json accounting skeleton. Root/nested AGENTS.md and CLAUDE.md
|
|
6
|
+
are directory-scoped. Repo-local SKILL.md files are candidates when an
|
|
7
|
+
applicable instruction explicitly dispatches them or when a selected path lies
|
|
8
|
+
under the skill's directory. Direct markdown references
|
|
9
|
+
of candidate skills are included so their load decision is also auditable.
|
|
10
|
+
|
|
11
|
+
In incremental mode, when no source marked applied in the prior round's
|
|
12
|
+
rules.json changed between effective_base and head, the prior rules.json and
|
|
13
|
+
knowledge.json are copied forward and no template is written.
|
|
14
|
+
|
|
15
|
+
The orchestrator reads every pending source, copies rules.template.json to
|
|
16
|
+
rules.json, extracts only verdict-bearing rules verbatim, and changes every
|
|
17
|
+
pending status to applied or not-applicable with a concrete reason.
|
|
18
|
+
|
|
19
|
+
Exit codes: 0 ok, 1 missing/invalid manifest or filesystem error.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import argparse
|
|
25
|
+
import json
|
|
26
|
+
import os
|
|
27
|
+
import re
|
|
28
|
+
import shutil
|
|
29
|
+
import subprocess
|
|
30
|
+
import sys
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
sys.dont_write_bytecode = True
|
|
34
|
+
|
|
35
|
+
from _common import manifest_selected, read_json, rel, repo_root, write_json
|
|
36
|
+
|
|
37
|
+
INSTRUCTION_NAMES = {"AGENTS.md", "CLAUDE.md"}
|
|
38
|
+
CONFIG_SOURCES = (".wtk-deep-review.yaml", ".wtk-deep-review.yml", ".coderabbit.yaml", ".coderabbit.yml")
|
|
39
|
+
SKILL_ROOTS = (".agents/skills", ".claude/skills", ".codex/skills", "skills")
|
|
40
|
+
REPO_SIGNAL_FILES = (
|
|
41
|
+
"package.json", "go.mod", "Cargo.toml", "pyproject.toml", "requirements.txt",
|
|
42
|
+
"bun.lock", "pnpm-lock.yaml", "yarn.lock", "package-lock.json",
|
|
43
|
+
)
|
|
44
|
+
IGNORED_DIRS = {
|
|
45
|
+
".git", ".wtk-deep-review", "node_modules", "vendor", "dist", "build",
|
|
46
|
+
".next", "target", "__pycache__",
|
|
47
|
+
}
|
|
48
|
+
REFERENCE_RE = re.compile(
|
|
49
|
+
r"(?P<path>(?:references|assets)/[A-Za-z0-9_./-]+\.md)", re.I
|
|
50
|
+
)
|
|
51
|
+
STOPWORDS = {
|
|
52
|
+
"about", "after", "agent", "agents", "best", "build", "building", "code",
|
|
53
|
+
"comprehensive", "create", "creating", "development", "expert", "files",
|
|
54
|
+
"guide", "implement", "project", "skill", "skills", "using", "when", "with",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def walk_named(repo: Path, names: set[str]) -> list[Path]:
|
|
59
|
+
found: list[Path] = []
|
|
60
|
+
for root, dirs, files in os.walk(repo, followlinks=False):
|
|
61
|
+
dirs[:] = sorted(d for d in dirs if d not in IGNORED_DIRS)
|
|
62
|
+
for name in sorted(set(files) & names):
|
|
63
|
+
found.append(Path(root) / name)
|
|
64
|
+
return sorted(found)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def walk_skills(repo: Path) -> list[Path]:
|
|
68
|
+
found: set[Path] = set()
|
|
69
|
+
for root_name in SKILL_ROOTS:
|
|
70
|
+
root = repo / root_name
|
|
71
|
+
if not root.is_dir():
|
|
72
|
+
continue
|
|
73
|
+
for walk_root, dirs, files in os.walk(root, followlinks=False):
|
|
74
|
+
dirs[:] = sorted(d for d in dirs if d not in IGNORED_DIRS)
|
|
75
|
+
if "SKILL.md" in files:
|
|
76
|
+
found.add((Path(walk_root) / "SKILL.md").resolve())
|
|
77
|
+
return sorted(found)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def scope_for(repo: Path, source: Path) -> list[str]:
|
|
81
|
+
parent = rel(source.parent, repo)
|
|
82
|
+
return ["**/*"] if parent == "." else [f"{parent}/**"]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def path_in_scope(path: str, source: Path, repo: Path) -> bool:
|
|
86
|
+
parent = rel(source.parent, repo)
|
|
87
|
+
return parent == "." or path == parent or path.startswith(parent + "/")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def tokens(value: str) -> set[str]:
|
|
91
|
+
return {
|
|
92
|
+
token for token in re.findall(r"[a-z0-9][a-z0-9+#.-]+", value.lower())
|
|
93
|
+
if len(token) >= 3 and token not in STOPWORDS
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def repository_tokens(repo: Path) -> set[str]:
|
|
98
|
+
signal: set[str] = set()
|
|
99
|
+
for name in REPO_SIGNAL_FILES:
|
|
100
|
+
path = repo / name
|
|
101
|
+
if path.is_file():
|
|
102
|
+
signal |= tokens(path.read_text(encoding="utf-8", errors="replace")[:200_000])
|
|
103
|
+
return signal
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def frontmatter(text: str) -> dict[str, str]:
|
|
107
|
+
if not text.startswith("---\n"):
|
|
108
|
+
return {}
|
|
109
|
+
end = text.find("\n---", 4)
|
|
110
|
+
if end < 0:
|
|
111
|
+
return {}
|
|
112
|
+
lines = text[4:end].splitlines()
|
|
113
|
+
values: dict[str, str] = {}
|
|
114
|
+
index = 0
|
|
115
|
+
while index < len(lines):
|
|
116
|
+
match = re.match(r"^([a-zA-Z0-9_-]+):\s*(.*)$", lines[index])
|
|
117
|
+
if not match:
|
|
118
|
+
index += 1
|
|
119
|
+
continue
|
|
120
|
+
key, value = match.group(1), match.group(2).strip().strip("\"'")
|
|
121
|
+
if value in {">", ">-", "|", "|-"}:
|
|
122
|
+
block: list[str] = []
|
|
123
|
+
index += 1
|
|
124
|
+
while index < len(lines) and (not lines[index].strip() or lines[index].startswith((" ", "\t"))):
|
|
125
|
+
if lines[index].strip():
|
|
126
|
+
block.append(lines[index].strip())
|
|
127
|
+
index += 1
|
|
128
|
+
values[key] = " ".join(block)
|
|
129
|
+
continue
|
|
130
|
+
values[key] = value
|
|
131
|
+
index += 1
|
|
132
|
+
return values
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def direct_references(skill: Path, repo: Path, text: str) -> list[str]:
|
|
136
|
+
refs: set[str] = set()
|
|
137
|
+
for match in REFERENCE_RE.finditer(text):
|
|
138
|
+
candidate = (skill.parent / match.group("path")).resolve()
|
|
139
|
+
try:
|
|
140
|
+
candidate.relative_to(repo.resolve())
|
|
141
|
+
except ValueError:
|
|
142
|
+
continue
|
|
143
|
+
if candidate.is_file():
|
|
144
|
+
refs.add(rel(candidate, repo))
|
|
145
|
+
return sorted(refs)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def explicit_dispatches(
|
|
149
|
+
name: str, skill_path: str, instructions: list[dict], instruction_text: dict[str, str]
|
|
150
|
+
) -> tuple[list[str], list[str]]:
|
|
151
|
+
sources, paths = [], []
|
|
152
|
+
name_pattern = re.compile(rf"(?<![a-z0-9-])\$?{re.escape(name.lower())}(?![a-z0-9-])")
|
|
153
|
+
for source in instructions:
|
|
154
|
+
if not source["applies_to"]:
|
|
155
|
+
continue
|
|
156
|
+
text = instruction_text[source["path"]].lower()
|
|
157
|
+
if name_pattern.search(text) or skill_path.lower() in text:
|
|
158
|
+
sources.append(source["path"])
|
|
159
|
+
paths.extend(source["applies_to"])
|
|
160
|
+
return sorted(set(sources)), sorted(set(paths))
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def reuse_prior_rules(repo: Path, out: Path, manifest: dict) -> bool:
|
|
164
|
+
"""Incremental round: carry the prior round's rules forward when none of its applied sources changed."""
|
|
165
|
+
if manifest.get("mode") != "incremental":
|
|
166
|
+
return False
|
|
167
|
+
prior_n = int(manifest["round"]) - 1
|
|
168
|
+
archive = out / "rounds" / f"round-{prior_n}"
|
|
169
|
+
if not (archive / "rules.json").is_file():
|
|
170
|
+
return False
|
|
171
|
+
try:
|
|
172
|
+
applied = {
|
|
173
|
+
row["source"] for row in read_json(archive / "rules.json")["sources"] if row.get("status") == "applied"
|
|
174
|
+
}
|
|
175
|
+
changed = subprocess.run(
|
|
176
|
+
["git", "diff", "--name-only", f"{manifest['effective_base']}..{manifest['head']}"],
|
|
177
|
+
cwd=repo, capture_output=True, text=True, check=True,
|
|
178
|
+
).stdout.split()
|
|
179
|
+
if applied & set(changed):
|
|
180
|
+
return False
|
|
181
|
+
for name in ("rules.json", "knowledge.json"):
|
|
182
|
+
shutil.copyfile(archive / name, out / name)
|
|
183
|
+
except (OSError, KeyError, TypeError, RuntimeError, subprocess.CalledProcessError) as error:
|
|
184
|
+
print(f"warn: prior rules not reused ({error}); building knowledge afresh")
|
|
185
|
+
return False
|
|
186
|
+
print(f"rules reused from round {prior_n}")
|
|
187
|
+
return True
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def main() -> int:
|
|
191
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
192
|
+
parser.add_argument("--out", required=True)
|
|
193
|
+
args = parser.parse_args()
|
|
194
|
+
repo = repo_root()
|
|
195
|
+
out = Path(args.out).resolve()
|
|
196
|
+
try:
|
|
197
|
+
manifest = read_json(out / "manifest.json")
|
|
198
|
+
selected = sorted(manifest_selected(manifest))
|
|
199
|
+
signal_tokens = repository_tokens(repo)
|
|
200
|
+
if reuse_prior_rules(repo, out, manifest):
|
|
201
|
+
return 0
|
|
202
|
+
|
|
203
|
+
instruction_text: dict[str, str] = {}
|
|
204
|
+
instructions: list[dict] = []
|
|
205
|
+
for source in walk_named(repo, INSTRUCTION_NAMES):
|
|
206
|
+
path = rel(source, repo)
|
|
207
|
+
applies_to = [item for item in selected if path_in_scope(item, source, repo)]
|
|
208
|
+
instruction_text[path] = source.read_text(encoding="utf-8", errors="replace")
|
|
209
|
+
instructions.append({
|
|
210
|
+
"path": path,
|
|
211
|
+
"kind": "instruction",
|
|
212
|
+
"scope": scope_for(repo, source),
|
|
213
|
+
"applies_to": applies_to,
|
|
214
|
+
"precedence": len(source.relative_to(repo).parts) - 1,
|
|
215
|
+
"candidate": bool(applies_to),
|
|
216
|
+
"candidate_reason": (
|
|
217
|
+
f"directory-scoped instructions apply to {len(applies_to)} selected path(s)"
|
|
218
|
+
if applies_to else "directory scope contains no selected path"
|
|
219
|
+
),
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
supplemental: list[dict] = []
|
|
223
|
+
for source_name in CONFIG_SOURCES:
|
|
224
|
+
source = repo / source_name
|
|
225
|
+
if source.is_file():
|
|
226
|
+
supplemental.append({
|
|
227
|
+
"path": source_name,
|
|
228
|
+
"kind": "config",
|
|
229
|
+
"scope": ["**/*"],
|
|
230
|
+
"applies_to": selected,
|
|
231
|
+
"candidate": bool(selected),
|
|
232
|
+
"candidate_reason": "repository review configuration can define path instructions",
|
|
233
|
+
})
|
|
234
|
+
learnings = repo / ".wtk-deep-review" / "learnings.md"
|
|
235
|
+
if learnings.is_file():
|
|
236
|
+
supplemental.append({
|
|
237
|
+
"path": rel(learnings, repo),
|
|
238
|
+
"kind": "learning",
|
|
239
|
+
"scope": ["**/*"],
|
|
240
|
+
"applies_to": selected,
|
|
241
|
+
"candidate": bool(selected),
|
|
242
|
+
"candidate_reason": "repository review learnings can refine finding validity",
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
skills: list[dict] = []
|
|
246
|
+
references: list[dict] = []
|
|
247
|
+
for skill in walk_skills(repo):
|
|
248
|
+
path = rel(skill, repo)
|
|
249
|
+
text = skill.read_text(encoding="utf-8", errors="replace")
|
|
250
|
+
metadata = frontmatter(text)
|
|
251
|
+
name = metadata.get("name") or skill.parent.name
|
|
252
|
+
description = metadata.get("description", "")
|
|
253
|
+
dispatch_sources, dispatched_paths = explicit_dispatches(
|
|
254
|
+
name, path, instructions, instruction_text
|
|
255
|
+
)
|
|
256
|
+
skill_dir_rel = rel(skill.parent, repo)
|
|
257
|
+
under_skill = [item for item in selected if item.startswith(skill_dir_rel + "/")]
|
|
258
|
+
if dispatch_sources:
|
|
259
|
+
candidate, applies_to = True, dispatched_paths
|
|
260
|
+
reason = f"explicitly dispatched by {', '.join(dispatch_sources)}"
|
|
261
|
+
elif under_skill: # a skill whose own files are under review is a knowledge source
|
|
262
|
+
candidate, applies_to = True, under_skill
|
|
263
|
+
reason = f"skill directory contains {len(under_skill)} selected path(s)"
|
|
264
|
+
else:
|
|
265
|
+
overlap = sorted(tokens(f"{name} {description}") & signal_tokens)
|
|
266
|
+
candidate = bool(overlap)
|
|
267
|
+
applies_to = selected if candidate else []
|
|
268
|
+
reason = (
|
|
269
|
+
f"metadata matches repository technology signals: {', '.join(overlap[:8])}"
|
|
270
|
+
if candidate else "no explicit dispatch"
|
|
271
|
+
)
|
|
272
|
+
ref_paths = direct_references(skill, repo, text)
|
|
273
|
+
skills.append({
|
|
274
|
+
"path": path,
|
|
275
|
+
"kind": "skill",
|
|
276
|
+
"name": name,
|
|
277
|
+
"description": description,
|
|
278
|
+
"scope": ["**/*"],
|
|
279
|
+
"applies_to": applies_to,
|
|
280
|
+
"candidate": candidate,
|
|
281
|
+
"candidate_reason": reason,
|
|
282
|
+
"dispatch_sources": dispatch_sources,
|
|
283
|
+
"references": ref_paths,
|
|
284
|
+
})
|
|
285
|
+
for ref_path in ref_paths:
|
|
286
|
+
references.append({
|
|
287
|
+
"path": ref_path,
|
|
288
|
+
"kind": "skill-reference",
|
|
289
|
+
"parent_skill": path,
|
|
290
|
+
"scope": ["**/*"],
|
|
291
|
+
"applies_to": applies_to,
|
|
292
|
+
"candidate": candidate,
|
|
293
|
+
"candidate_reason": (
|
|
294
|
+
f"direct reference of candidate skill {path}"
|
|
295
|
+
if candidate else f"parent skill {path} is not applicable"
|
|
296
|
+
),
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
sources = sorted(
|
|
300
|
+
[*instructions, *supplemental, *skills, *references],
|
|
301
|
+
key=lambda item: ({
|
|
302
|
+
"config": 0, "learning": 1, "instruction": 2,
|
|
303
|
+
"skill": 3, "skill-reference": 4,
|
|
304
|
+
}[item["kind"]], item["path"]),
|
|
305
|
+
)
|
|
306
|
+
knowledge = {
|
|
307
|
+
"target": manifest["target"],
|
|
308
|
+
"selected_paths": selected,
|
|
309
|
+
"sources": sources,
|
|
310
|
+
"summary": {
|
|
311
|
+
"instructions": len(instructions),
|
|
312
|
+
"supplemental": len(supplemental),
|
|
313
|
+
"skills": len(skills),
|
|
314
|
+
"skill_references": len(references),
|
|
315
|
+
"pending_candidates": sum(1 for source in sources if source["candidate"]),
|
|
316
|
+
},
|
|
317
|
+
}
|
|
318
|
+
template_sources = []
|
|
319
|
+
for source in sources:
|
|
320
|
+
pending = source["candidate"]
|
|
321
|
+
template_sources.append({
|
|
322
|
+
"source": source["path"],
|
|
323
|
+
"kind": source["kind"],
|
|
324
|
+
"status": "pending" if pending else "not-applicable",
|
|
325
|
+
"reason": (
|
|
326
|
+
"read this source in full and classify it before building jobs"
|
|
327
|
+
if pending else source["candidate_reason"]
|
|
328
|
+
),
|
|
329
|
+
})
|
|
330
|
+
write_json(out / "knowledge.json", knowledge)
|
|
331
|
+
write_json(out / "rules.template.json", {"sources": template_sources, "rules": []})
|
|
332
|
+
except (OSError, RuntimeError, ValueError) as error:
|
|
333
|
+
sys.stderr.write(f"{error}\n")
|
|
334
|
+
return 1
|
|
335
|
+
|
|
336
|
+
summary = knowledge["summary"]
|
|
337
|
+
print(f"knowledge registry -> {out / 'knowledge.json'}")
|
|
338
|
+
print(
|
|
339
|
+
f"sources: {summary['instructions']} instructions + {summary['supplemental']} config/learnings + "
|
|
340
|
+
f"{summary['skills']} skills + "
|
|
341
|
+
f"{summary['skill_references']} direct references; "
|
|
342
|
+
f"{summary['pending_candidates']} need an applied/not-applicable decision"
|
|
343
|
+
)
|
|
344
|
+
print(f"rules accounting skeleton -> {out / 'rules.template.json'}")
|
|
345
|
+
return 0
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
if __name__ == "__main__":
|
|
349
|
+
sys.exit(main())
|