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.
Files changed (142) hide show
  1. package/.agents/skills/ponytail/SKILL.md +111 -0
  2. package/.agents/skills/ponytail-audit/SKILL.md +37 -0
  3. package/.agents/skills/ponytail-debt/SKILL.md +41 -0
  4. package/.agents/skills/ponytail-gain/SKILL.md +47 -0
  5. package/.agents/skills/ponytail-help/SKILL.md +70 -0
  6. package/.agents/skills/ponytail-review/SKILL.md +52 -0
  7. package/.agents/skills/prompt-review/SKILL.md +60 -0
  8. package/.agents/skills/wtk/SKILL.md +64 -0
  9. package/.agents/skills/wtk/references/artifacts.md +82 -0
  10. package/.agents/skills/wtk/references/evidence.md +68 -0
  11. package/.agents/skills/wtk/references/git.md +75 -0
  12. package/.agents/skills/wtk/references/test-contract.md +76 -0
  13. package/.agents/skills/wtk/references/validation.md +127 -0
  14. package/.agents/skills/wtk-config/SKILL.md +102 -0
  15. package/.agents/skills/wtk-config/assets/agents/claude/deep-reviewer.md +40 -0
  16. package/.agents/skills/wtk-config/assets/agents/claude/designer.md +57 -0
  17. package/.agents/skills/wtk-config/assets/agents/claude/explorer.md +41 -0
  18. package/.agents/skills/wtk-config/assets/agents/claude/implementer.md +57 -0
  19. package/.agents/skills/wtk-config/assets/agents/claude/planner.md +68 -0
  20. package/.agents/skills/wtk-config/assets/agents/claude/verifier.md +80 -0
  21. package/.agents/skills/wtk-config/assets/agents/codex/deep-reviewer.toml +41 -0
  22. package/.agents/skills/wtk-config/assets/agents/codex/designer.toml +55 -0
  23. package/.agents/skills/wtk-config/assets/agents/codex/explorer.toml +35 -0
  24. package/.agents/skills/wtk-config/assets/agents/codex/implementer.toml +52 -0
  25. package/.agents/skills/wtk-config/assets/agents/codex/planner.toml +66 -0
  26. package/.agents/skills/wtk-config/assets/agents/codex/verifier.toml +78 -0
  27. package/.agents/skills/wtk-config/assets/agents/cursor/deep-reviewer.md +38 -0
  28. package/.agents/skills/wtk-config/assets/agents/cursor/designer.md +55 -0
  29. package/.agents/skills/wtk-config/assets/agents/cursor/explorer.md +39 -0
  30. package/.agents/skills/wtk-config/assets/agents/cursor/implementer.md +55 -0
  31. package/.agents/skills/wtk-config/assets/agents/cursor/planner.md +66 -0
  32. package/.agents/skills/wtk-config/assets/agents/cursor/verifier.md +79 -0
  33. package/.agents/skills/wtk-config/scripts/ad-index.py +123 -0
  34. package/.agents/skills/wtk-config/scripts/repository_intelligence.py +671 -0
  35. package/.agents/skills/wtk-config/scripts/workflow_config.py +972 -0
  36. package/.agents/skills/wtk-deep-review/SKILL.md +164 -0
  37. package/.agents/skills/wtk-deep-review/assets/PROMPT.md +56 -0
  38. package/.agents/skills/wtk-deep-review/assets/REVIEW_UI.html +1340 -0
  39. package/.agents/skills/wtk-deep-review/assets/findings.schema.json +113 -0
  40. package/.agents/skills/wtk-deep-review/references/context-pack.md +88 -0
  41. package/.agents/skills/wtk-deep-review/references/orchestration.md +164 -0
  42. package/.agents/skills/wtk-deep-review/references/output-contracts.md +149 -0
  43. package/.agents/skills/wtk-deep-review/references/publish-github.md +83 -0
  44. package/.agents/skills/wtk-deep-review/references/state-and-learnings.md +64 -0
  45. package/.agents/skills/wtk-deep-review/references/subagent-runtimes.md +33 -0
  46. package/.agents/skills/wtk-deep-review/references/taxonomy.md +70 -0
  47. package/.agents/skills/wtk-deep-review/scripts/_common.py +371 -0
  48. package/.agents/skills/wtk-deep-review/scripts/build_jobs.py +561 -0
  49. package/.agents/skills/wtk-deep-review/scripts/build_knowledge.py +349 -0
  50. package/.agents/skills/wtk-deep-review/scripts/build_manifest.py +479 -0
  51. package/.agents/skills/wtk-deep-review/scripts/graft_context.py +160 -0
  52. package/.agents/skills/wtk-deep-review/scripts/graphify_context.py +75 -0
  53. package/.agents/skills/wtk-deep-review/scripts/merge_findings.py +313 -0
  54. package/.agents/skills/wtk-deep-review/scripts/render_html.py +240 -0
  55. package/.agents/skills/wtk-deep-review/scripts/render_review.py +282 -0
  56. package/.agents/skills/wtk-deep-review/scripts/run_jobs.py +436 -0
  57. package/.agents/skills/wtk-deep-review/scripts/token_metrics.py +369 -0
  58. package/.agents/skills/wtk-discover/SKILL.md +270 -0
  59. package/.agents/skills/wtk-discover/references/document-format.md +149 -0
  60. package/.agents/skills/wtk-implement/SKILL.md +95 -0
  61. package/.agents/skills/wtk-implement/references/checklist-format.md +116 -0
  62. package/.agents/skills/wtk-implement/references/screens.md +129 -0
  63. package/.agents/skills/wtk-implement/references/test-policy.md +152 -0
  64. package/.agents/skills/wtk-implement/references/verify.md +297 -0
  65. package/.agents/skills/wtk-knowledge-check/SKILL.md +16 -0
  66. package/.agents/skills/wtk-knowledge-check/scripts/check.ts +502 -0
  67. package/.agents/skills/wtk-knowledge-check/scripts/cli.ts +32 -0
  68. package/.agents/skills/wtk-knowledge-check/scripts/frontmatter.ts +44 -0
  69. package/.agents/skills/wtk-lean/.skill-meta.json +6 -0
  70. package/.agents/skills/wtk-lean/NOTICE.md +18 -0
  71. package/.agents/skills/wtk-lean/SKILL.md +137 -0
  72. package/.agents/skills/wtk-lean/references/build.md +168 -0
  73. package/.agents/skills/wtk-lean/references/checks.md +253 -0
  74. package/.agents/skills/wtk-lean/references/memory.md +156 -0
  75. package/.agents/skills/wtk-lean/references/plan.md +514 -0
  76. package/.agents/skills/wtk-lean/references/verify.md +336 -0
  77. package/.agents/skills/wtk-lean/scripts/check_commit.py +121 -0
  78. package/.agents/skills/wtk-lean/scripts/fixtures/checks.md +98 -0
  79. package/.agents/skills/wtk-lean/scripts/fixtures/plan.md +119 -0
  80. package/.agents/skills/wtk-lean/scripts/fixtures/verification.md +41 -0
  81. package/.agents/skills/wtk-lean/scripts/lessons.py +412 -0
  82. package/.agents/skills/wtk-lean/scripts/selftest.py +401 -0
  83. package/.agents/skills/wtk-lean/scripts/validate_checks.py +417 -0
  84. package/.agents/skills/wtk-lean/scripts/validate_plan.py +580 -0
  85. package/.agents/skills/wtk-lean/scripts/validate_verification.py +357 -0
  86. package/.agents/skills/wtk-plan/SKILL.md +102 -0
  87. package/.agents/skills/wtk-plan/references/document-format.md +222 -0
  88. package/.agents/skills/wtk-qa/SKILL.md +14 -0
  89. package/.agents/skills/wtk-qa-execute/SKILL.md +115 -0
  90. package/.agents/skills/wtk-qa-execute/references/fix-loop.md +23 -0
  91. package/.agents/skills/wtk-qa-execute/references/session-protocol.md +25 -0
  92. package/.agents/skills/wtk-qa-plan/SKILL.md +111 -0
  93. package/.agents/skills/wtk-qa-plan/references/profile.md +35 -0
  94. package/.agents/skills/wtk-ship/SKILL.md +49 -0
  95. package/.agents/skills/wtk-ship/remediation.py +170 -0
  96. package/.agents/skills/wtk-ship/scripts/close_feature.py +83 -0
  97. package/.agents/skills/wtk-ship/scripts/review_convergence.py +373 -0
  98. package/.wtk.toml.example +93 -0
  99. package/AGENTS.md +129 -0
  100. package/NOTICE.md +12 -0
  101. package/README.md +440 -0
  102. package/bin/wtk.js +25 -0
  103. package/docs/toolkit/README.md +56 -0
  104. package/docs/toolkit/decisions.md +61 -0
  105. package/docs/toolkit/guidelines/CONTEXT-BUDGET.md +64 -0
  106. package/docs/toolkit/guidelines/DX.md +67 -0
  107. package/docs/toolkit/guidelines/FRONTEND.md +73 -0
  108. package/docs/toolkit/guidelines/KNOWLEDGE-WIKI.md +128 -0
  109. package/docs/toolkit/guidelines/MODELING.md +134 -0
  110. package/docs/toolkit/guidelines/QA-EXECUTION.md +46 -0
  111. package/docs/toolkit/guidelines/QA-SCENARIOS.md +165 -0
  112. package/docs/toolkit/guidelines/REVIEW-ROUNDS.md +160 -0
  113. package/docs/toolkit/guidelines/SECURITY.md +160 -0
  114. package/docs/toolkit/guidelines/UI-UX.md +117 -0
  115. package/docs/toolkit/guidelines/WORKFLOW-MEMORY.md +73 -0
  116. package/docs/toolkit/guidelines.md +50 -0
  117. package/docs/toolkit/loop.md +77 -0
  118. package/docs/toolkit/purpose.md +51 -0
  119. package/docs/toolkit/repository-intelligence.md +53 -0
  120. package/docs/toolkit/reviews.md +77 -0
  121. package/knowledge/AGENTS.md +316 -0
  122. package/knowledge/raw/README.md +17 -0
  123. package/package.json +74 -0
  124. package/scripts/install_security_skills.py +816 -0
  125. package/scripts/installer/engine.js +254 -0
  126. package/scripts/installer/knowledge.js +28 -0
  127. package/scripts/installer/packets.js +77 -0
  128. package/scripts/installer/terminal.js +98 -0
  129. package/scripts/installer/transaction.js +75 -0
  130. package/skills-lock.json +92 -0
  131. package/templates/adoption/agents/core.md +23 -0
  132. package/templates/adoption/agents/quality.md +3 -0
  133. package/templates/adoption/knowledge/wiki/architecture/index.md +3 -0
  134. package/templates/adoption/knowledge/wiki/decisions/index.md +3 -0
  135. package/templates/adoption/knowledge/wiki/design/index.md +3 -0
  136. package/templates/adoption/knowledge/wiki/domain/index.md +3 -0
  137. package/templates/adoption/knowledge/wiki/index.md +3 -0
  138. package/templates/adoption/knowledge/wiki/log.md +3 -0
  139. package/templates/adoption/knowledge/wiki/open-questions/index.md +3 -0
  140. package/templates/adoption/knowledge/wiki/product/index.md +3 -0
  141. package/templates/adoption/knowledge/wiki/research/index.md +3 -0
  142. 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())