nobrainer-tech-flow 0.0.0-stage → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/.claude-plugin/marketplace.json +18 -0
  3. package/.claude-plugin/plugin.json +21 -0
  4. package/.codex-plugin/plugin.json +47 -0
  5. package/.opencode/INSTALL.md +37 -0
  6. package/.opencode/plugins/nobrainer-tech-skills.js +66 -0
  7. package/.pi/extensions/nobrainer-tech-skills.js +64 -0
  8. package/AGENTS.md +204 -0
  9. package/CLAUDE.md +204 -0
  10. package/CONTRIBUTING.md +70 -0
  11. package/GEMINI.md +1 -0
  12. package/LICENSE +21 -0
  13. package/README.md +658 -2
  14. package/SECURITY.md +33 -0
  15. package/adapters/bootstrap.md +27 -0
  16. package/assets/nobrainer-tech-logo.svg +1 -0
  17. package/assets/nobrainer-workflow.svg +155 -0
  18. package/assets/plugin-logo.png +0 -0
  19. package/bin/nobrainer-tech-flow.mjs +48 -0
  20. package/docs/BOUNDED_RUNNER.md +60 -0
  21. package/docs/CHAT_PLUGINS.md +29 -0
  22. package/docs/COMPATIBILITY.md +371 -0
  23. package/docs/INSTALL.md +626 -0
  24. package/docs/MIGRATION_TO_FLOW.md +117 -0
  25. package/docs/NAMING.md +39 -0
  26. package/docs/PRIVACY.md +9 -0
  27. package/docs/SESSION_RESTART.md +70 -0
  28. package/docs/SKILL_CURATION.md +124 -0
  29. package/docs/TESTING.md +232 -0
  30. package/docs/TRY_IT.md +109 -0
  31. package/docs/releases/v1.0.0.md +81 -0
  32. package/docs/releases/v1.1.0.md +85 -0
  33. package/docs/releases/v1.10.0.md +34 -0
  34. package/docs/releases/v1.11.0-publication-readback.md +25 -0
  35. package/docs/releases/v1.11.0.md +42 -0
  36. package/docs/releases/v1.12.0.md +30 -0
  37. package/docs/releases/v1.13.0.md +46 -0
  38. package/docs/releases/v1.14.0.md +43 -0
  39. package/docs/releases/v1.14.1.md +21 -0
  40. package/docs/releases/v1.2.0.md +87 -0
  41. package/docs/releases/v1.2.1.md +92 -0
  42. package/docs/releases/v1.3.0-publication-readback.md +69 -0
  43. package/docs/releases/v1.3.0.md +66 -0
  44. package/docs/releases/v1.3.1-publication-readback.md +76 -0
  45. package/docs/releases/v1.3.1.md +59 -0
  46. package/docs/releases/v1.4.0-publication-readback.md +81 -0
  47. package/docs/releases/v1.4.0.md +43 -0
  48. package/docs/releases/v1.5.0-publication-readback.md +76 -0
  49. package/docs/releases/v1.5.0.md +54 -0
  50. package/docs/releases/v1.6.0.md +64 -0
  51. package/docs/releases/v1.6.1-publication-readback.md +53 -0
  52. package/docs/releases/v1.6.1.md +82 -0
  53. package/docs/releases/v1.7.0.md +42 -0
  54. package/docs/releases/v1.7.1-publication-readback.md +64 -0
  55. package/docs/releases/v1.7.1.md +49 -0
  56. package/docs/releases/v1.8.0.md +63 -0
  57. package/docs/releases/v1.8.1.md +32 -0
  58. package/docs/releases/v1.9.0.md +33 -0
  59. package/docs/releases/v2.0.0-candidate.md +28 -0
  60. package/docs/releases/v2.0.0.md +53 -0
  61. package/docs/releases/v2.0.1.md +135 -0
  62. package/docs/releases/v2.0.2.md +189 -0
  63. package/docs/releases/v2.1.0.md +64 -0
  64. package/docs/releases/v2.1.1.md +43 -0
  65. package/docs/releases/v2.2.0.md +64 -0
  66. package/docs/releases/v2.2.1.md +11 -0
  67. package/docs/releases/v2.3.0.md +97 -0
  68. package/docs/releases/v2.3.1.md +71 -0
  69. package/docs/releases/v2.3.2.md +242 -0
  70. package/docs/releases/v2.4.0.md +126 -0
  71. package/gemini-extension.json +6 -0
  72. package/hooks/claude-hooks.json +17 -0
  73. package/hooks/hooks-cursor.json +10 -0
  74. package/hooks/run-hook.cmd +36 -0
  75. package/hooks/session-start +38 -0
  76. package/package.json +69 -4
  77. package/plugin.json +51 -0
  78. package/scripts/build_plugin.py +50 -0
  79. package/scripts/check_flow_update.py +18 -0
  80. package/scripts/check_public_commit.py +86 -0
  81. package/scripts/fetch_public_refs.py +69 -0
  82. package/scripts/install.py +600 -0
  83. package/scripts/install.sh +1392 -0
  84. package/scripts/install_guards.py +279 -0
  85. package/scripts/install_personalization.py +527 -0
  86. package/scripts/install_skills.py +951 -0
  87. package/scripts/npm_install.py +85 -0
  88. package/scripts/recommend_flow_setup.py +1522 -0
  89. package/scripts/validate_skills.py +465 -0
  90. package/skills/nobrainer-auto-fine-tune/SKILL.md +79 -0
  91. package/skills/nobrainer-auto-fine-tune/references/capacity-audit.md +49 -0
  92. package/skills/nobrainer-autoimprove/SKILL.md +212 -0
  93. package/skills/nobrainer-autoimprove/scripts/count_words.py +22 -0
  94. package/skills/nobrainer-browser/SKILL.md +234 -0
  95. package/skills/nobrainer-browser/references/cdp-profile-restart.md +93 -0
  96. package/skills/nobrainer-build/SKILL.md +173 -0
  97. package/skills/nobrainer-codex-context/SKILL.md +111 -0
  98. package/skills/nobrainer-codex-context/scripts/codex_context.py +175 -0
  99. package/skills/nobrainer-decide/SKILL.md +198 -0
  100. package/skills/nobrainer-dispatcher/SKILL.md +256 -0
  101. package/skills/nobrainer-dispatcher/agents/openai.yaml +4 -0
  102. package/skills/nobrainer-rca/SKILL.md +163 -0
  103. package/skills/nobrainer-research/SKILL.md +131 -0
  104. package/skills/nobrainer-review/SKILL.md +195 -0
  105. package/skills/nobrainer-security/SKILL.md +166 -0
  106. package/skills/nobrainer-sessions/SKILL.md +223 -0
  107. package/skills/nobrainer-sessions/references/evidence-receipt.md +55 -0
  108. package/skills/nobrainer-sessions/references/protocol.md +337 -0
  109. package/skills/nobrainer-sessions/references/session-restart.md +255 -0
  110. package/skills/nobrainer-sessions/scripts/evidence_receipt.py +373 -0
  111. package/skills/nobrainer-sessions/scripts/restart_gate.py +140 -0
  112. package/skills/nobrainer-sessions/scripts/run_bounded.py +390 -0
  113. package/skills/nobrainer-sessions/scripts/session_title.py +43 -0
  114. package/skills/nobrainer-skill-doctor/SKILL.md +149 -0
  115. package/skills/nobrainer-skill-doctor/references/astra.md +19 -0
  116. package/skills/nobrainer-skill-doctor/scripts/audit.py +100 -0
  117. package/skills/nobrainer-skill-doctor/scripts/context_candidates.py +133 -0
  118. package/skills/nobrainer-spec-driven-development/SKILL.md +132 -0
  119. package/skills/nobrainer-spec-driven-development/references/spec-template.md +114 -0
  120. package/skills/nobrainer-team/SKILL.md +165 -0
  121. package/skills/nobrainer-team/references/team-plan.md +64 -0
  122. package/skills/nobrainer-tech-flow/SKILL.md +239 -0
  123. package/skills/nobrainer-tech-flow/VERSION +1 -0
  124. package/skills/nobrainer-tech-flow/references/autopilot.md +72 -0
  125. package/skills/nobrainer-tech-flow/references/codex-routing.md +21 -0
  126. package/skills/nobrainer-tech-flow/references/communication.md +108 -0
  127. package/skills/nobrainer-tech-flow/references/correction-hooks.md +110 -0
  128. package/skills/nobrainer-tech-flow/references/daily-update.md +28 -0
  129. package/skills/nobrainer-tech-flow/references/delivery.md +243 -0
  130. package/skills/nobrainer-tech-flow/references/idea-to-delivery.md +82 -0
  131. package/skills/nobrainer-tech-flow/references/long-run-state.md +209 -0
  132. package/skills/nobrainer-tech-flow/references/model-routing.md +83 -0
  133. package/skills/nobrainer-tech-flow/references/optional-decisions.md +83 -0
  134. package/skills/nobrainer-tech-flow/references/project-start.md +22 -0
  135. package/skills/nobrainer-tech-flow/references/routing.md +125 -0
  136. package/skills/nobrainer-tech-flow/references/setup.md +180 -0
  137. package/skills/nobrainer-tech-flow/references/worktrees.md +138 -0
  138. package/skills/nobrainer-tech-flow/scripts/check_flow_update.py +298 -0
  139. package/skills/nobrainer-tech-flow/scripts/cleanup_worktree.py +327 -0
  140. package/skills/nobrainer-tech-flow/scripts/decision_config.py +107 -0
  141. package/skills/nobrainer-tech-flow/scripts/flow_guard.py +252 -0
  142. package/skills/nobrainer-tech-flow/scripts/typed_decisions.py +210 -0
  143. package/skills/nobrainer-wiki/SKILL.md +164 -0
  144. package/skills/nobrainer-wiki/references/setup.md +88 -0
  145. package/skills/nobrainer-writing/SKILL.md +272 -0
  146. package/skills/nobrainer-writing/agents/openai.yaml +4 -0
  147. package/skills/nobrainer-writing/references/brief-artifacts.md +357 -0
  148. package/skills/nobrainer-writing/references/plain-technical-english.md +76 -0
  149. package/skills/nobrainer-writing/references/research.md +86 -0
@@ -0,0 +1,465 @@
1
+ #!/usr/bin/env python3
2
+ """Validate portable skill structure and the NoBrainer canonical suite."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import re
9
+ import sys
10
+ from pathlib import Path
11
+
12
+
13
+ ROOT = Path(__file__).resolve().parents[1]
14
+ SKILLS = ROOT / "skills"
15
+ NAME_RE = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
16
+ MAX_NAME_LENGTH = 64
17
+ LINK_RE = re.compile(r"\[[^\]]+\]\((?!https?://|#|mailto:)([^)]+)\)")
18
+
19
+ SUITE = {
20
+ "nobrainer-auto-fine-tune": "nb-auto-fine-tune",
21
+ "nobrainer-codex-context": "nb-codex-context",
22
+ "nobrainer-skill-doctor": "nb-skill-doctor",
23
+ "nobrainer-tech-flow": "NBFlow",
24
+ "nobrainer-team": "nb-team",
25
+ "nobrainer-dispatcher": "nb-dispatcher",
26
+ "nobrainer-research": "nb-research",
27
+ "nobrainer-writing": "nb-write",
28
+ "nobrainer-build": "nb-build",
29
+ "nobrainer-security": "nb-security",
30
+ "nobrainer-sessions": "nb-sessions",
31
+ "nobrainer-spec-driven-development": "nb-sdd",
32
+ "nobrainer-wiki": "nb-wiki",
33
+ "nobrainer-browser": "nb-browser",
34
+ "nobrainer-autoimprove": "nb-autoimprove",
35
+ "nobrainer-decide": "nb-decide",
36
+ "nobrainer-rca": "nb-rca",
37
+ "nobrainer-review": "nb-review",
38
+ }
39
+ REQUIRED_ALIASES = {
40
+ "nobrainer-tech-flow": ("NBFlow", "NBF", "nobrainer-tech-flow"),
41
+ }
42
+
43
+ LEGACY = {
44
+ "nobrainer-ultra",
45
+ "add-gitleaks",
46
+ "agent-browser",
47
+ "agents-restraint",
48
+ "codex-in-claude-code",
49
+ "code-autoresearch",
50
+ "deep-audit",
51
+ "deep-autoreview",
52
+ "deep-autoresearch",
53
+ "deep-bugs-finder",
54
+ "deep-code-review",
55
+ "deep-decide",
56
+ "dispatching-parallel-agents",
57
+ "deep-rca",
58
+ "engineering-standards",
59
+ "karpathy-auto-improver",
60
+ "karpathy-llm-wiki",
61
+ "llm-wiki",
62
+ "nb-add",
63
+ "nb-flow",
64
+ "nb-dispatcher",
65
+ "nb-get",
66
+ "nb-multi",
67
+ "nb-tidy",
68
+ "nb-workflow",
69
+ "nb-write",
70
+ "nobrainer-autopilot",
71
+ "nobrainer-capture-lesson",
72
+ "nobrainer-continuous-improvement",
73
+ "nobrainer-memory",
74
+ "nobrainer-memory-memsearch",
75
+ "nobrainer-npm-secure",
76
+ "nobrainer-reddit",
77
+ "nobrainer-starter",
78
+ "nobrainer-skill-browser",
79
+ "nobrainer-simplifier",
80
+ "nobrainer-style",
81
+ "nobrainer-human-like",
82
+ "security-review",
83
+ "session-handoff",
84
+ "nobrainer-team-builder",
85
+ "nobrainer-ultracode-workflow",
86
+ "nobrainer-wiki-add",
87
+ "nobrainer-wiki-get",
88
+ "nobrainer-wiki-tidy",
89
+ "playwright-cli",
90
+ "wiki-add",
91
+ "wiki-get",
92
+ "wiki-tidy",
93
+ }
94
+
95
+ ACTIVE = set(SUITE)
96
+
97
+ PUBLIC_FORBIDDEN = (
98
+ "/Users/",
99
+ "/opt/homebrew/",
100
+ "/private/tmp/",
101
+ "nobrainer-tech@",
102
+ "--dangerously-skip-permissions",
103
+ "CLAUDE-CODE-FABLE",
104
+ )
105
+ EXTERNAL_WORKFLOW_BRANDS = (
106
+ "av" + "thar",
107
+ "ti" + "bo",
108
+ "super" + "powers",
109
+ )
110
+ PORTABLE_FRONTMATTER = {"name", "description"}
111
+ PUBLIC_TEXT_SUFFIXES = {
112
+ ".json",
113
+ ".js",
114
+ ".md",
115
+ ".ps1",
116
+ ".py",
117
+ ".sh",
118
+ ".svg",
119
+ ".toml",
120
+ ".yaml",
121
+ ".yml",
122
+ }
123
+ PUBLIC_EXTENSIONLESS_FILES = {
124
+ ROOT / "hooks" / "run-hook.cmd",
125
+ ROOT / "hooks" / "session-start",
126
+ }
127
+ PUBLIC_ROOT_FILES = (
128
+ ROOT / ".gitattributes",
129
+ ROOT / ".gitleaks.toml",
130
+ ROOT / ".gitignore",
131
+ ROOT / ".pre-commit-config.yaml",
132
+ ROOT / "README.md",
133
+ ROOT / "RELEASE-NOTES.md",
134
+ ROOT / "AGENTS.md",
135
+ ROOT / "CLAUDE.md",
136
+ ROOT / "CONTRIBUTING.md",
137
+ ROOT / "GEMINI.md",
138
+ ROOT / "LICENSE",
139
+ ROOT / "SECURITY.md",
140
+ ROOT / "gemini-extension.json",
141
+ ROOT / "plugin.json",
142
+ ROOT / "package.json",
143
+ )
144
+ PUBLIC_ROOT_DIRS = (
145
+ ROOT / "docs",
146
+ ROOT / "assets",
147
+ ROOT / ".agents",
148
+ ROOT / ".claude-plugin",
149
+ ROOT / ".codex-plugin",
150
+ ROOT / ".cursor-plugin",
151
+ ROOT / ".github",
152
+ ROOT / ".kimi-plugin",
153
+ ROOT / ".opencode",
154
+ ROOT / ".pi",
155
+ ROOT / "adapters",
156
+ ROOT / "hooks",
157
+ ROOT / "scripts",
158
+ ROOT / "tests",
159
+ )
160
+ DETECTOR_FIXTURE_FILES = {
161
+ ROOT / "scripts" / "validate_skills.py",
162
+ ROOT / "tests" / "test_suite.py",
163
+ }
164
+ VERSION_PATHS = (
165
+ ROOT / "package.json",
166
+ ROOT / "plugin.json",
167
+ ROOT / ".claude-plugin" / "plugin.json",
168
+ ROOT / ".codex-plugin" / "plugin.json",
169
+ ROOT / ".cursor-plugin" / "plugin.json",
170
+ ROOT / ".kimi-plugin" / "plugin.json",
171
+ ROOT / "gemini-extension.json",
172
+ )
173
+ FROZEN_EVAL_PAYLOAD_SUFFIXES = (
174
+ "-prompt.md",
175
+ "-output.md",
176
+ "-judge.md",
177
+ "-judge-rubric.md",
178
+ )
179
+
180
+
181
+ def parse_frontmatter(path: Path) -> tuple[dict[str, str], str]:
182
+ text = path.read_text(encoding="utf-8")
183
+ lines = text.splitlines()
184
+ if not lines or lines[0] != "---":
185
+ raise ValueError("frontmatter must start on line 1")
186
+ try:
187
+ end = lines.index("---", 1)
188
+ except ValueError as exc:
189
+ raise ValueError("frontmatter is not closed") from exc
190
+
191
+ values: dict[str, str] = {}
192
+ key: str | None = None
193
+ chunks: list[str] = []
194
+ for line in lines[1:end]:
195
+ match = re.match(r"^([a-zA-Z0-9_-]+):(?:\s*(.*))?$", line)
196
+ if match:
197
+ if key is not None:
198
+ values[key] = " ".join(chunks).strip().strip('"')
199
+ key = match.group(1)
200
+ raw = (match.group(2) or "").strip()
201
+ chunks = [] if raw in {"", ">", ">-", "|", "|-"} else [raw]
202
+ elif key is not None and line.startswith(" "):
203
+ chunks.append(line.strip())
204
+ elif line.strip():
205
+ raise ValueError(f"unsupported frontmatter line: {line}")
206
+ if key is not None:
207
+ values[key] = " ".join(chunks).strip().strip('"')
208
+ return values, text
209
+
210
+
211
+ def active_skill_files() -> list[Path]:
212
+ return sorted(SKILLS.glob("*/SKILL.md"))
213
+
214
+
215
+ def is_public_text_file(path: Path) -> bool:
216
+ return path.is_file() and (
217
+ path.suffix.lower() in PUBLIC_TEXT_SUFFIXES
218
+ or path in PUBLIC_EXTENSIONLESS_FILES
219
+ )
220
+
221
+
222
+ def public_text_files() -> list[Path]:
223
+ paths = {path for path in PUBLIC_ROOT_FILES if path.is_file()}
224
+ for directory in PUBLIC_ROOT_DIRS:
225
+ if not directory.is_dir():
226
+ continue
227
+ paths.update(
228
+ path
229
+ for path in directory.rglob("*")
230
+ if is_public_text_file(path)
231
+ )
232
+ for skill in active_skill_files():
233
+ paths.update(
234
+ path
235
+ for path in skill.parent.rglob("*")
236
+ if is_public_text_file(path)
237
+ )
238
+ return sorted(paths)
239
+
240
+
241
+ def relative_link_errors(path: Path, text: str) -> list[str]:
242
+ errors: list[str] = []
243
+ for match in LINK_RE.finditer(text):
244
+ target = match.group(1).split("#", 1)[0]
245
+ if not target or target.startswith("/") or "$" in target:
246
+ continue
247
+ if not (path.parent / target).resolve().exists():
248
+ errors.append(f"{path.relative_to(ROOT)}: broken relative link {target}")
249
+ return errors
250
+
251
+
252
+ def should_validate_relative_links(path: Path) -> bool:
253
+ """Skip literal Markdown copied into immutable model-evaluation payloads."""
254
+ artifact_dir = ROOT / "docs" / "evals" / "artifacts"
255
+ return not (
256
+ path.parent == artifact_dir
257
+ and path.name.endswith(FROZEN_EVAL_PAYLOAD_SUFFIXES)
258
+ )
259
+
260
+
261
+ def public_scan_text(path: Path, text: str) -> str:
262
+ """Remove the one intentional detector fixture, but scan every other use."""
263
+ if path not in DETECTOR_FIXTURE_FILES:
264
+ return text
265
+ indent = " " if path.name == "test_suite.py" else " "
266
+ for forbidden in PUBLIC_FORBIDDEN:
267
+ fixture = f'{indent}"{forbidden}",'
268
+ if text.count(fixture) != 1:
269
+ return text
270
+ text = text.replace(fixture, "", 1)
271
+ return text
272
+
273
+
274
+ def validate(suite_only: bool) -> list[str]:
275
+ errors: list[str] = []
276
+ seen: dict[str, Path] = {}
277
+
278
+ for path in active_skill_files():
279
+ try:
280
+ frontmatter, text = parse_frontmatter(path)
281
+ except ValueError as exc:
282
+ errors.append(f"{path.relative_to(ROOT)}: {exc}")
283
+ continue
284
+ name = frontmatter.get("name", "")
285
+ description = frontmatter.get("description", "")
286
+ extra_keys = set(frontmatter) - PORTABLE_FRONTMATTER
287
+ if extra_keys:
288
+ errors.append(
289
+ f"{path.relative_to(ROOT)}: non-portable frontmatter keys "
290
+ f"{', '.join(sorted(extra_keys))}"
291
+ )
292
+ if not NAME_RE.fullmatch(name):
293
+ errors.append(f"{path.relative_to(ROOT)}: invalid name {name!r}")
294
+ if len(name) > MAX_NAME_LENGTH:
295
+ errors.append(
296
+ f"{path.relative_to(ROOT)}: invalid name length "
297
+ f"{len(name)} > {MAX_NAME_LENGTH}"
298
+ )
299
+ if name != path.parent.name:
300
+ errors.append(
301
+ f"{path.relative_to(ROOT)}: name {name!r} does not match directory"
302
+ )
303
+ if not description or len(description) > 1024:
304
+ errors.append(f"{path.relative_to(ROOT)}: invalid description length")
305
+ if name in seen:
306
+ errors.append(
307
+ f"duplicate name {name!r}: {seen[name].relative_to(ROOT)} and "
308
+ f"{path.relative_to(ROOT)}"
309
+ )
310
+ seen[name] = path
311
+ errors.extend(relative_link_errors(path, text))
312
+
313
+ if not suite_only:
314
+ return errors
315
+
316
+ active_names = {path.parent.name for path in active_skill_files()}
317
+ if active_names != ACTIVE:
318
+ missing = sorted(ACTIVE - active_names)
319
+ extra = sorted(active_names - ACTIVE)
320
+ errors.append(f"active inventory mismatch: missing={missing}, extra={extra}")
321
+
322
+ for name, alias in SUITE.items():
323
+ path = SKILLS / name / "SKILL.md"
324
+ if not path.is_file():
325
+ errors.append(f"missing canonical skill {name}")
326
+ continue
327
+ try:
328
+ frontmatter, _ = parse_frontmatter(path)
329
+ except ValueError as exc:
330
+ errors.append(f"{path.relative_to(ROOT)}: {exc}")
331
+ continue
332
+ if set(frontmatter) != PORTABLE_FRONTMATTER:
333
+ errors.append(f"{path.relative_to(ROOT)}: suite frontmatter must contain only name and description")
334
+ if not frontmatter.get("description", "").startswith("Use when"):
335
+ errors.append(f"{path.relative_to(ROOT)}: description must start with 'Use when'")
336
+ if alias not in frontmatter.get("description", ""):
337
+ errors.append(f"{path.relative_to(ROOT)}: missing alias trigger {alias}")
338
+ for required_alias in REQUIRED_ALIASES.get(name, ()):
339
+ if required_alias not in frontmatter.get("description", ""):
340
+ errors.append(
341
+ f"{path.relative_to(ROOT)}: missing required alias trigger {required_alias}"
342
+ )
343
+ for name in LEGACY:
344
+ if (SKILLS / name / "SKILL.md").exists():
345
+ errors.append(f"legacy skill remains discoverable: {name}")
346
+
347
+ for path in public_text_files():
348
+ text = path.read_text(encoding="utf-8")
349
+ scanned_text = public_scan_text(path, text)
350
+ for forbidden in PUBLIC_FORBIDDEN:
351
+ if forbidden in scanned_text:
352
+ errors.append(
353
+ f"{path.relative_to(ROOT)}: forbidden public value {forbidden!r}"
354
+ )
355
+ for brand in EXTERNAL_WORKFLOW_BRANDS:
356
+ # Dated review reports may attribute outside projects. Operational
357
+ # instructions retain the branding gate; public-value scanning above
358
+ # still applies to every report.
359
+ is_review_document = (
360
+ path.parent == ROOT / "docs" / "reviews"
361
+ and path.suffix.lower() == ".md"
362
+ )
363
+ if brand in text.lower() and not is_review_document:
364
+ errors.append(
365
+ f"{path.relative_to(ROOT)}: external workflow branding {brand!r}"
366
+ )
367
+ if (
368
+ path.suffix.lower() == ".md"
369
+ and path.name != "SKILL.md"
370
+ and should_validate_relative_links(path)
371
+ ):
372
+ errors.extend(relative_link_errors(path, text))
373
+
374
+ versions: dict[str, str] = {}
375
+ for path in VERSION_PATHS:
376
+ try:
377
+ data = json.loads(path.read_text(encoding="utf-8"))
378
+ except (OSError, json.JSONDecodeError) as exc:
379
+ errors.append(f"{path.relative_to(ROOT)}: invalid version manifest: {exc}")
380
+ continue
381
+ version = data.get("version")
382
+ if not isinstance(version, str) or not version:
383
+ errors.append(f"{path.relative_to(ROOT)}: missing version")
384
+ continue
385
+ versions[str(path.relative_to(ROOT))] = version
386
+
387
+ marketplace = ROOT / ".claude-plugin" / "marketplace.json"
388
+ try:
389
+ data = json.loads(marketplace.read_text(encoding="utf-8"))
390
+ versions[str(marketplace.relative_to(ROOT))] = data["plugins"][0]["version"]
391
+ except (OSError, json.JSONDecodeError, KeyError, IndexError, TypeError) as exc:
392
+ errors.append(f"{marketplace.relative_to(ROOT)}: invalid plugin version: {exc}")
393
+
394
+ other_versions, other_errors = release_version_sources(ROOT)
395
+ versions.update(other_versions)
396
+ errors.extend(other_errors)
397
+
398
+ if len(set(versions.values())) > 1:
399
+ errors.append(f"manifest version mismatch: {versions}")
400
+
401
+ codex_manifest = ROOT / ".codex-plugin" / "plugin.json"
402
+ try:
403
+ codex_data = json.loads(codex_manifest.read_text(encoding="utf-8"))
404
+ if "hooks" in codex_data:
405
+ errors.append(
406
+ f"{codex_manifest.relative_to(ROOT)}: hooks must be omitted; "
407
+ "Claude hooks use an explicit non-default path so Codex cannot "
408
+ "auto-discover them"
409
+ )
410
+ if (ROOT / "hooks" / "hooks.json").exists():
411
+ errors.append(
412
+ "hooks/hooks.json: reserved Codex auto-discovery path must remain "
413
+ "absent; use a client-specific hook path"
414
+ )
415
+ if codex_data.get("skills") != "./skills/":
416
+ errors.append(f"{codex_manifest.relative_to(ROOT)}: skills path must be ./skills/")
417
+ except (OSError, json.JSONDecodeError, TypeError) as exc:
418
+ errors.append(f"{codex_manifest.relative_to(ROOT)}: invalid Codex manifest: {exc}")
419
+
420
+ return errors
421
+
422
+
423
+ def release_version_sources(root: Path) -> tuple[dict[str, str], list[str]]:
424
+ """Versions kept outside the plugin manifests.
425
+
426
+ The skill's VERSION file is what the update check compares with the latest
427
+ release, and the npm lockfile repeats the package version twice; both have to
428
+ move with the manifests or users see a false or missing update.
429
+ """
430
+
431
+ versions: dict[str, str] = {}
432
+ errors: list[str] = []
433
+ version_file = root / "skills" / "nobrainer-tech-flow" / "VERSION"
434
+ try:
435
+ versions["skills/nobrainer-tech-flow/VERSION"] = version_file.read_text(
436
+ encoding="utf-8"
437
+ ).strip()
438
+ except OSError as exc:
439
+ errors.append(f"skills/nobrainer-tech-flow/VERSION: unreadable: {exc}")
440
+ lockfile = root / "package-lock.json"
441
+ try:
442
+ data = json.loads(lockfile.read_text(encoding="utf-8"))
443
+ versions["package-lock.json"] = data["version"]
444
+ versions['package-lock.json packages[""]'] = data["packages"][""]["version"]
445
+ except (OSError, json.JSONDecodeError, KeyError, TypeError) as exc:
446
+ errors.append(f"package-lock.json: invalid version fields: {exc}")
447
+ return versions, errors
448
+
449
+
450
+ def main() -> int:
451
+ parser = argparse.ArgumentParser()
452
+ parser.add_argument("--suite", action="store_true", help="enforce canonical suite gates")
453
+ args = parser.parse_args()
454
+ errors = validate(args.suite)
455
+ if errors:
456
+ for error in errors:
457
+ print(f"ERROR: {error}")
458
+ return 1
459
+ scope = "canonical suite" if args.suite else "active skills"
460
+ print(f"OK: validated {scope}")
461
+ return 0
462
+
463
+
464
+ if __name__ == "__main__":
465
+ sys.exit(main())
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: nobrainer-auto-fine-tune
3
+ description: "Use when setting up nobrainer-tech-flow for the first time, when the owner says nb-auto-fine-tune, or when client capabilities, model routing, context or useful parallelism need recalibration."
4
+ ---
5
+
6
+ # nobrainer-tech-flow: Orchestration Calibration
7
+
8
+ Run this skill as the standard first-setup step for nobrainer-tech-flow. Discover the current client's and project's actual capabilities, calibrate supported worker routes when useful, and record evidence-backed recommendations. Ordinary delegation follows [team execution](../nobrainer-tech-flow/references/delivery.md#coordinated-team-execution); calibration is not a prerequisite to dispatch. Read [capacity and update audit](references/capacity-audit.md) at project setup or when limits are in question. It does not train or fine-tune model weights, choose a replacement for MAIN, create sessions, schedule workers, or run an open-ended optimization loop.
9
+
10
+ ## Preserve the owner's choice
11
+
12
+ Treat the model and effort selected for MAIN as fixed for this task. Do not lower or replace them to save cost. Recommend separate worker models and effort only for bounded delegated work, using currently available evidence. If the selected MAIN identity or effort is not observable, preserve the current runtime and report `UNKNOWN`; do not infer it from a saved preference, prior task, or model catalog.
13
+
14
+ ## Discover this client's actual capabilities
15
+
16
+ Inspect the current harness and relevant project/client configuration for:
17
+
18
+ - models and exact model identifiers the client advertises;
19
+ - models and effort levels the current runtime can actually request;
20
+ - native subagent creation, isolated worktrees, concurrency limits, and available transport/readback;
21
+ - active client version/update status; effective profile, selected provider, configured versus catalog-advertised versus observed context window;
22
+ - any trustworthy latency, token, or cost telemetry and its measurement scope.
23
+
24
+ Keep advertised, configured, callable, and successfully invoked capabilities distinct. Verify a capability with a read-only or harmless probe only when supported and useful. Never make a paid benchmark call merely to prove a model exists. Record unavailable, hidden, or unverified fields as `UNKNOWN` or `UNSUPPORTED`. Do not hardcode model names, rankings, prices, concurrency, or provider behavior. A model name suggested in a prompt is not proof that the host honored it.
25
+
26
+ ## Decide whether calibration is worth doing
27
+
28
+ For a one-off, low-risk task, use the owner's selected MAIN and current known tools; skip benchmarking and state that routing is uncalibrated. For a recurring workflow, material model choice, or task with meaningful quality/latency/cost tradeoffs, define a small bounded comparison before spending resources.
29
+
30
+ Classify the work by required expertise, context, accuracy, latency, cost sensitivity, confidentiality, dependence between units, and integration effort. Calibration follows the delegation-first default for independent units with measurable acceptance and disjoint write scope. A missing benchmark or measured speed gain does not block eligible dispatch. When comparing routes, measure the critical path and observed concurrency, including setup, review and integration; worker count alone proves no benefit.
31
+
32
+ ## Run a bounded, comparable calibration
33
+
34
+ Before comparing worker routes, freeze:
35
+
36
+ - the task category and representative cases, including an ordinary case and a relevant edge or non-trigger case;
37
+ - identical inputs, instructions, tools, output contract, and acceptance rubric for each candidate;
38
+ - the quality gates and any minimum acceptable score;
39
+ - the latency measurement boundary, such as request-to-complete, and the available cost/token source;
40
+ - a small call and time budget, retry rule, and stop condition.
41
+
42
+ Run candidates on the same cases. Blind or shuffle outputs for subjective review when practical. Score acceptance and factuality first; then compare measured latency and cost among routes that meet quality gates. Use repeated paired trials only when variability could change the routing decision and budget allows. Do not fabricate costs from public list prices, estimate latency from model labels, cherry-pick retries, or treat a worker's self-report as independent evaluation. If telemetry is absent, report that dimension as `UNMEASURED`.
43
+
44
+ Stop when evidence identifies an adequate route, the result is inconclusive, the budget is reached, or a quality/safety gate fails. Return `NO_CHANGE` when no candidate demonstrates a useful improvement. Calibration evidence applies only to the tested client, task category, settings, and period; it is not a universal model ranking.
45
+
46
+ ## Produce a routing policy
47
+
48
+ Return a concise policy with:
49
+
50
+ ```text
51
+ CLIENT_AND_RUNTIME:
52
+ MAIN_MODEL_AND_EFFORT: PRESERVED | UNKNOWN
53
+ CAPABILITIES: ADVERTISED / CONFIGURED / CALLABLE / VERIFIED, with exact IDs and evidence
54
+ TASK_CATEGORY_AND_CASES:
55
+ CANDIDATE_ROUTES_AND_SETTINGS:
56
+ QUALITY_RESULT:
57
+ LATENCY_RESULT: measured boundary and result | UNMEASURED
58
+ COST_OR_TOKEN_RESULT: source and result | UNMEASURED
59
+ PARALLELISM: useful slots, dependencies, write isolation, integration cost | UNKNOWN where unverified
60
+ CONTEXT: configured / catalog-advertised / provider-documented / runtime-observed
61
+ CLIENT_UPDATE: UP_TO_DATE | AVAILABLE | BUSY | UNAVAILABLE | UNKNOWN
62
+ RECOMMENDATION: route by task category; retain MAIN choice
63
+ LIMITS_AND_EXPIRY:
64
+ PERSONALIZATION_CHANGE: NOT_PROPOSED | PREPARED_FOR_REVIEW | APPLIED_WITH_EXPLICIT_AUTHORIZATION
65
+ ```
66
+
67
+ State the next calibration trigger, such as a runtime/model catalog change, a material quality regression, or a changed workload. Do not retain secrets or unnecessary user data in the policy.
68
+
69
+ ## Keep the Flow skill boundaries
70
+
71
+ - `nobrainer-team` owns capability-to-role design and assignment contracts. Give it this skill's verified candidate evidence; do not duplicate its roster planning.
72
+ - `nobrainer-dispatcher` owns ready-set selection, batch ordering, and backpressure. Give it measured concurrency and critical-path findings; do not dispatch workers here.
73
+ - `nobrainer-sessions` owns visible session identity, transport, restart, archive, leases, and receive-audit. Recommend a restart or archival only through its lifecycle rules and current authorization; this skill never creates, restarts, or archives a session.
74
+ - `nobrainer-autoimprove` owns frozen baseline/candidate/holdout experiments for changing prompts, skills, or durable artifacts. Route artifact tuning there; this skill may provide the routing benchmark contract but does not tune its own rubric or promote a candidate.
75
+ - The user owns persistent client/global personalization. Discover the appropriate documented settings surface and use the existing authorization for Flow setup when it covers that surface. Preview the smallest exact change, preserve unrelated instructions, make a recoverable backup, and verify the saved value by readback. If the scope is not authorized or materially ambiguous, ask for that decision. Prefer the client's global instruction surface for portable Flow awareness and a project instruction file for project-specific choices; never claim one client setting applies to another tool. Reuse the managed personalization installer rather than writing an untracked second configuration path.
76
+
77
+ ## Closeout
78
+
79
+ Report the exact capabilities verified, the comparison performed or why it was skipped, measured results versus unknowns, the recommended task-specific routing, whether any setting was changed, and the next evidence needed. A configured preference, model listing, queued task, or worker report alone does not prove a callable or better route.
@@ -0,0 +1,49 @@
1
+ # Client capacity and update audit
2
+
3
+ Use this on first project setup, after a client/model update, or when a
4
+ measured task repeatedly hits a context or worker ceiling. Start read-only.
5
+
6
+ ## Separate four evidence layers
7
+
8
+ Record the running client/version, selected MAIN model and effort, effective
9
+ profile/provider, and source of each value. For context, keep
10
+ `CONFIGURED_LIMIT`, `CATALOG_ADVERTISED_LIMIT`,
11
+ `PROVIDER_DOCUMENTED_LIMIT` and `RUNTIME_OBSERVED` separate. A larger model
12
+ card or API window does not prove that the client grants it. Do not promote
13
+ a claimed million-token window or a proposed 872,000-token setting into a
14
+ verified runtime fact.
15
+
16
+ For workers, record native spawn support, exact model IDs, per-worker effort
17
+ support, configured concurrency, actual successful concurrent launches,
18
+ current running workers and practical integration cost. A config value such
19
+ as 15 is a ceiling, not observed capacity. Use ordinary useful work to test
20
+ capacity; do not create filler probes or exceed host restrictions.
21
+
22
+ Check whether the running client has an eligible update through its native
23
+ version/status channel when available. Treat `AVAILABLE`, `UP_TO_DATE`,
24
+ `BUSY`, `UNAVAILABLE` and `UNKNOWN` as different outcomes. Do not equate
25
+ Flow's daily release check with a client-app update. Never download or restart
26
+ a user's app from a read-only check.
27
+
28
+ ## Client adapters
29
+
30
+ - **Codex:** inspect the selected profile as well as base config, local model
31
+ catalog, effective session status, worker configuration and native app update
32
+ status. The optional [NoBrainer Codex](https://github.com/nobrainer-tech/nobrainer-codex)
33
+ installer can provide a Codex-specific `install.py --check` preflight.
34
+ It is a separate product; its planned setting is not runtime proof.
35
+ - **Claude Code:** inspect available subagent models, effort and capacity from
36
+ the active client and its current settings. A named model file or allowlist
37
+ is not proof that the worker actually ran on that model.
38
+ - **OpenCode and other clients:** inspect their effective model/provider
39
+ selection, agents and plugin settings through supported native means. A
40
+ lightweight default or title-generation model is not automatically an
41
+ eligible task worker.
42
+
43
+ Recommend a larger context or worker ceiling only when the active model,
44
+ provider and client all support the exact value and a real task benefits.
45
+ Prepare the smallest settings diff and rollback. Apply only within the
46
+ owner's authorization and client-supported mechanism, then read back the
47
+ saved value, selected MAIN model/effort and a representative runtime task.
48
+ If effective settings or actual runtime capacity cannot be observed, report
49
+ `CONFIGURED_ONLY` or `UNKNOWN` rather than claiming improvement.