geneseed 3.17.0 → 3.19.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 (164) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/DESIGN.md +1 -1
  3. package/README.md +23 -5
  4. package/SHIPPED.md +3 -3
  5. package/adapters/claude-code/skills/geneseed/SKILL.md +1 -1
  6. package/adapters/opencode/README.md +2 -2
  7. package/adapters/opencode/plugins/geneseed-context.js +19 -17
  8. package/adapters/opencode/plugins/geneseed-guard.js +7 -7
  9. package/bin/geneseed-cli.mjs +5 -0
  10. package/docs/concepts/exclusions.md +1 -1
  11. package/docs/concepts/skills.md +6 -2
  12. package/docs/extending.md +33 -0
  13. package/docs/guides/project-context.md +20 -0
  14. package/docs/guides/read-non-markdown.md +1 -1
  15. package/docs/guides/uninstall.md +1 -1
  16. package/docs/guides/upgrade.md +1 -1
  17. package/docs/guides/wiki.md +4 -4
  18. package/docs/opencode-plugin-setup.md +1 -1
  19. package/docs/reference/env-context.md +2 -2
  20. package/docs/reference/plugin-context.md +2 -2
  21. package/docs/reference/plugin-guard.md +1 -1
  22. package/docs/token-footprint.md +18 -15
  23. package/docs/understand/take-it-out.md +1 -1
  24. package/harness.config.json +1 -1
  25. package/js/README.md +4 -1
  26. package/js/build/args.mjs +22 -20
  27. package/js/build/bundle.mjs +1 -3
  28. package/js/build/emit-claude.mjs +5 -4
  29. package/js/build/emit-opencode.mjs +9 -2
  30. package/js/build/generate.mjs +6 -6
  31. package/js/build/render.mjs +19 -12
  32. package/js/build/source.mjs +82 -7
  33. package/js/build/stubs.mjs +35 -6
  34. package/js/build/themes.mjs +1 -3
  35. package/js/cli-table.json +53 -0
  36. package/js/hosts/hooks-context.mjs +13 -6
  37. package/js/hosts/hosts.mjs +1 -3
  38. package/js/hosts/installs.mjs +13 -7
  39. package/js/hosts/mcp.mjs +1 -1
  40. package/js/hosts/native.mjs +60 -5
  41. package/js/hosts/opencode.mjs +37 -6
  42. package/js/hosts/settings.mjs +1 -0
  43. package/js/hosts/shim.mjs +1 -0
  44. package/js/inspect/checks-authoring.mjs +4 -1
  45. package/js/inspect/checks-counts.mjs +8 -3
  46. package/js/inspect/checks-repo.mjs +166 -2
  47. package/js/inspect/diff.mjs +5 -1
  48. package/js/inspect/doctor.mjs +2 -1
  49. package/js/inspect/inventory.mjs +7 -9
  50. package/js/inspect/scorecard-checks.mjs +571 -0
  51. package/js/inspect/scorecard-svg.mjs +85 -0
  52. package/js/inspect/scorecard.mjs +839 -0
  53. package/js/lib/json.mjs +1 -1
  54. package/js/maintain/migrate.mjs +2 -1
  55. package/js/maintain/uninstall.mjs +2 -3
  56. package/js/maintain/update.mjs +10 -3
  57. package/js/ui/menu.mjs +2 -2
  58. package/js/ui/tui.mjs +1 -0
  59. package/js/web/api.mjs +2 -0
  60. package/js/web/catalog.mjs +24 -7
  61. package/js/web/daemon.mjs +2 -1
  62. package/js/web/routes.mjs +2 -0
  63. package/js/web/server.mjs +1 -1
  64. package/js/web/user-files.mjs +1 -0
  65. package/package.json +11 -3
  66. package/registry.json +12 -75
  67. package/src/AGENT.md.tmpl +19 -24
  68. package/src/agents/docs.md +1 -1
  69. package/src/agents/researcher.md +1 -1
  70. package/src/agents/reviewer.md +3 -0
  71. package/src/bricks/bruno-test.md +2 -2
  72. package/src/bricks/ci-fix.md +3 -3
  73. package/src/bricks/deps-audit.md +3 -3
  74. package/src/bricks/migration-plan.md +2 -2
  75. package/src/doctrines/comms.md +19 -0
  76. package/src/doctrines/process.md +2 -2
  77. package/src/laws/universal.md +2 -2
  78. package/src/skills/brainstorm.md +14 -10
  79. package/src/skills/bruno.md +84 -0
  80. package/src/skills/codebase-design.md +41 -23
  81. package/src/skills/daydream/SKILL.md +4 -3
  82. package/src/skills/daydream/VENDOR.md +6 -2
  83. package/src/skills/daydream/instructions.md +1 -1
  84. package/src/skills/debug/ci.md +11 -0
  85. package/src/skills/debug.md +11 -8
  86. package/src/skills/dependencies.md +65 -0
  87. package/src/skills/develop.md +9 -4
  88. package/src/skills/document-project.md +27 -22
  89. package/src/skills/explain-changes/SKILL.md +1 -1
  90. package/src/skills/fresh-eyes.md +1 -1
  91. package/src/skills/geneseed-code-review.md +21 -9
  92. package/src/skills/geneseed.md +4 -1
  93. package/src/skills/git-history.md +78 -0
  94. package/src/skills/handoff.md +4 -3
  95. package/src/skills/parallel-agents.md +2 -1
  96. package/src/skills/pipeline.md +1 -1
  97. package/src/skills/plan.md +5 -1
  98. package/src/skills/ponytail.md +1 -1
  99. package/src/skills/prototype.md +10 -7
  100. package/src/skills/react-view-transitions/SKILL.md +1 -0
  101. package/src/skills/react-view-transitions/VENDOR.md +4 -0
  102. package/src/skills/research.md +6 -1
  103. package/src/skills/review-response.md +8 -2
  104. package/src/skills/roast-me.md +8 -7
  105. package/src/skills/rule/consolidate.md +32 -0
  106. package/src/skills/rule.md +16 -4
  107. package/src/skills/security-audit.md +1 -1
  108. package/src/skills/ship.md +16 -9
  109. package/src/skills/teach.md +5 -3
  110. package/src/skills/wayfinder.md +19 -16
  111. package/src/skills/wiki.md +2 -2
  112. package/themes/_TEMPLATE.json +9 -17
  113. package/themes/biker.json +9 -17
  114. package/themes/commentator.json +9 -17
  115. package/themes/cyberpunk.json +9 -17
  116. package/themes/gamer.json +9 -17
  117. package/themes/imperial.json +9 -17
  118. package/themes/joker.json +9 -17
  119. package/themes/marvin.json +9 -17
  120. package/themes/mean.json +9 -17
  121. package/themes/military.json +9 -17
  122. package/themes/neutral.json +9 -17
  123. package/themes/pirate.json +9 -17
  124. package/themes/sports.json +9 -17
  125. package/themes/verstappen.json +9 -17
  126. package/themes/wizard.json +9 -17
  127. package/web/dist/assets/{Activity-DonKN1pi.js → Activity-IbwdHHyx.js} +1 -1
  128. package/web/dist/assets/{ActivityDetail-e47a3cdE.js → ActivityDetail-B5uivdWI.js} +1 -1
  129. package/web/dist/assets/{Dashboard-Cr7i0PsO.js → Dashboard-v6WQ37bD.js} +2 -2
  130. package/web/dist/assets/{Docs-Lne3nuzD.js → Docs-CypIfDBX.js} +1 -1
  131. package/web/dist/assets/FilterInput-BSfpStOZ.js +1 -0
  132. package/web/dist/assets/{GreenhouseView-4CbIuTpl.js → GreenhouseView-ClxP0hQG.js} +1 -1
  133. package/web/dist/assets/{Installs-CIzbV1Ks.js → Installs-CPkQeFjO.js} +1 -1
  134. package/web/dist/assets/{JournalView-BlBfuvzj.js → JournalView-BeEInhUf.js} +1 -1
  135. package/web/dist/assets/{Laws-B-pjcjHr.js → Laws-Ds6U5xBQ.js} +1 -1
  136. package/web/dist/assets/Library-BraUKPyQ.js +1 -0
  137. package/web/dist/assets/{Loops-DIOxW1xH.js → Loops-DWcy3mwK.js} +1 -1
  138. package/web/dist/assets/Markdown-C6fRd4A4.js +67 -0
  139. package/web/dist/assets/{OperatorHudView-D7S7z7CJ.js → OperatorHudView-CQyrJQ9m.js} +1 -1
  140. package/web/dist/assets/{Personal-Ws3v35AB.js → Personal-YWqs6qFL.js} +1 -1
  141. package/web/dist/assets/{Tabs-6Z1eziAq.js → Tabs-EhzEC-Xh.js} +1 -1
  142. package/web/dist/assets/{activity-Dr7ZMBI3.js → activity-dcg5vF9j.js} +1 -1
  143. package/web/dist/assets/{drawable-ATNeYKbT.js → drawable-CEKPNV7X.js} +1 -1
  144. package/web/dist/assets/{index-CDg5XUhG.js → index-CPi22b7y.js} +2 -2
  145. package/web/dist/assets/react-DRhoX0kS.js +9 -0
  146. package/web/dist/assets/{usePoll-DD8VjtSg.js → usePoll-BmR631BB.js} +1 -1
  147. package/web/dist/index.html +2 -2
  148. package/web/src/pages/Laws.jsx +5 -0
  149. package/src/skills/bruno-collection-generator.md +0 -52
  150. package/src/skills/bruno-test-writer.md +0 -51
  151. package/src/skills/ci-fix.md +0 -49
  152. package/src/skills/consolidate-memory.md +0 -50
  153. package/src/skills/deps-audit.md +0 -46
  154. package/src/skills/domain-modeling.md +0 -27
  155. package/src/skills/git-archaeology.md +0 -36
  156. package/src/skills/git-rescue.md +0 -58
  157. package/src/skills/migrate.md +0 -34
  158. package/src/skills/opencode-theme.md +0 -42
  159. package/src/skills/repo-map.md +0 -25
  160. package/src/skills/tickets.md +0 -33
  161. package/web/dist/assets/FilterInput-CVyqmYWM.js +0 -1
  162. package/web/dist/assets/Library-x8Nzg6yq.js +0 -1
  163. package/web/dist/assets/Markdown-X2a3qvOh.js +0 -38
  164. package/web/dist/assets/react-B9Z_tXRV.js +0 -8
@@ -7,15 +7,20 @@
7
7
  * itself and compares it against what is on disk.
8
8
  */
9
9
  import path from 'node:path';
10
- import { PLUGIN_SRC, ROOT, SRC, THEMES, WORKFLOW_SRC } from '../build/source.mjs';
10
+ import {
11
+ ALIASES_RE, PLUGIN_SRC, RETIRED_SKILL_IDS, ROOT, SRC, THEMES, WORKFLOW_SRC, aliasesOf, knownSkillIds,
12
+ } from '../build/source.mjs';
11
13
  import { VENDORED_SKILL_DIRS } from '../hosts/native.mjs';
14
+ import { COMMAND_SET } from '../hosts/opencode.mjs';
12
15
  import { hookShimPath, shimDeadPaths } from '../hosts/shim.mjs';
13
16
  import { readText } from '../lib/fs.mjs';
14
17
  import { formatRepr, formatValue } from '../lib/json.mjs';
15
18
  import { splitLines } from '../lib/udiff.mjs';
16
19
  import { ENTITY_STATUSES } from './inventory.mjs';
20
+ import { assessRepo } from './scorecard.mjs';
21
+ import { badgeUrl, renderSvg } from './scorecard-svg.mjs';
17
22
  import { isDir, isFile, rglob, srcStems } from './scan.mjs';
18
- import { readFileSync } from 'node:fs';
23
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
19
24
 
20
25
  /**
21
26
  * `_harness_build._registry_keys` — every entity the registry must describe.
@@ -106,6 +111,7 @@ export function registryProblems() {
106
111
  *
107
112
  * `(?i)` becomes the `i` flag on the last one only, which is where the Python puts it.
108
113
  */
114
+ /** @type {Array<[string, RegExp]>} */
109
115
  const SECRET_PATTERNS = [
110
116
  ['Anthropic API key', /sk-ant-[A-Za-z0-9_-]{20,}/],
111
117
  ['OpenAI-style API key', /\bsk-[A-Za-z0-9]{32,}/],
@@ -240,6 +246,83 @@ export function vendorPinProblems() {
240
246
  return problems;
241
247
  }
242
248
 
249
+ /**
250
+ * Every `<!-- aliases: … -->` name is a clean, unclaimed, single-owner path segment.
251
+ *
252
+ * An alias becomes a folder (`skills/<alias>/`) and a command file (`command/<alias>.md`) on
253
+ * every install, so a name that collides overwrites something: another skill, another
254
+ * skill's alias, or an OpenCode command (`COMMAND_SET`, `/ponytail`). A retired skill id is
255
+ * refused as an alias AND as a live skill — `RETIRED_SKILL_IDS` is the list of names that
256
+ * already meant something on someone's machine. The emit trusts all of this; this is where
257
+ * it is checked.
258
+ */
259
+ export function aliasProblems(src = SRC) {
260
+ const problems = [];
261
+ const known = knownSkillIds(src);
262
+ const reserved = new Set([...Object.keys(COMMAND_SET), 'ponytail']);
263
+ for (const id of known) {
264
+ if (Object.hasOwn(RETIRED_SKILL_IDS, id)) {
265
+ problems.push(`[authoring] skill '${id}' reuses a retired skill id (RETIRED_SKILL_IDS)`);
266
+ }
267
+ }
268
+ const owner = new Map();
269
+ for (const id of known) {
270
+ let text;
271
+ try { text = readText(path.join(src, 'skills', `${id}.md`)); } catch { continue; } // a folder skill
272
+ // `aliasesOf` reads the FIRST marker line only, so a second one would be dropped in silence.
273
+ if ((text.match(new RegExp(ALIASES_RE.source, 'gm')) || []).length > 1) {
274
+ problems.push(`[authoring] skills/${id}.md declares aliases on more than one line — merge them`);
275
+ }
276
+ for (const alias of aliasesOf(text)) {
277
+ const where = `skills/${id}.md alias '${alias}'`;
278
+ if (!/^[a-z0-9-]+$/.test(alias)) problems.push(`[authoring] ${where} is not [a-z0-9-]+`);
279
+ else if (known.includes(alias)) problems.push(`[authoring] ${where} is already a skill`);
280
+ else if (Object.hasOwn(RETIRED_SKILL_IDS, alias)) {
281
+ problems.push(`[authoring] ${where} is a retired skill id (RETIRED_SKILL_IDS)`);
282
+ } else if (reserved.has(alias)) problems.push(`[authoring] ${where} is an OpenCode command name`);
283
+ else if (owner.has(alias)) {
284
+ problems.push(`[authoring] ${where} is already declared by skills/${owner.get(alias)}.md`);
285
+ } else owner.set(alias, id);
286
+ }
287
+ }
288
+ return problems;
289
+ }
290
+
291
+ /**
292
+ * A first-party skill's side files (`src/skills/<name>/<file>.md`, emitted beside its SKILL.md
293
+ * and read only when the body points at them) keep the one layout every writer assumes: a flat
294
+ * `<name>.md` owns the folder, files sit one level down, each is `.md` (rendered and themed),
295
+ * none is `SKILL.md` (the emit writes that one), and the spec links every one as
296
+ * `[…](<name>/<file>)` — an unlinked side file ships to every install and is never read.
297
+ * Vendored folders are exempt: they carry their own SKILL.md and their own layout.
298
+ */
299
+ export function sideFileProblems(src = SRC) {
300
+ const problems = [];
301
+ const dir = path.join(src, 'skills');
302
+ for (const e of readdirSync(dir, { withFileTypes: true })) {
303
+ if (!e.isDirectory() || e.name.startsWith('_') || VENDORED_SKILL_DIRS.has(e.name)) continue;
304
+ const name = e.name;
305
+ let spec;
306
+ try { spec = readText(path.join(dir, `${name}.md`)); } catch {
307
+ problems.push(`[authoring] skills/${name}/ has no skills/${name}.md — a side-file folder `
308
+ + 'belongs to a flat spec (or list a folder skill in VENDORED_SKILL_DIRS)');
309
+ continue;
310
+ }
311
+ for (const f of readdirSync(path.join(dir, name), { withFileTypes: true })) {
312
+ const where = `skills/${name}/${f.name}`;
313
+ if (!f.isFile() || !f.name.endsWith('.md')) {
314
+ problems.push(`[authoring] ${where} — a side file is a .md one level down`);
315
+ } else if (f.name === 'SKILL.md') {
316
+ problems.push(`[authoring] ${where} collides with the SKILL.md the emit writes`);
317
+ } else if (!spec.includes(`](${name}/${f.name})`) && !spec.includes(`](${name}/${f.name}#`)) {
318
+ problems.push(`[authoring] ${where} is not linked from skills/${name}.md — `
319
+ + `link it as [${name}/${f.name}](${name}/${f.name}) or delete it`);
320
+ }
321
+ }
322
+ }
323
+ return problems.sort();
324
+ }
325
+
243
326
  const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
244
327
 
245
328
  /**
@@ -359,3 +442,84 @@ export function moduleMapProblems() {
359
442
  }
360
443
  return problems;
361
444
  }
445
+
446
+ /**
447
+ * The AI Harness Scorecard checks this repo passes, written out — the ratchet's floor.
448
+ *
449
+ * Taken from `geneseed scorecard` on main after #209-#213 (security policy, PR template, ARCHITECTURE
450
+ * and ADRs, the audit and commitlint jobs, Dependabot, coverage, property tests, fuzzing): 26 of 31,
451
+ * 81.2/100, matching upstream's Python tool check for check. Then the weekly Stryker run and the
452
+ * offline link check added mutation_testing and stale_doc_detection: 28 of 31, 88.7 (A); the checkJs
453
+ * pass added type_safety (partial: `strict` is off): 29 of 31, 90.4; calling the web job's prettier and vitest
454
+ * directly (the scorecard reads only the root package.json's scripts) added formatter_enforcement
455
+ * and lifted test_suite_exists to 3 of 3: 30 of 31, 95.3. Grouped by pillar, in upstream order.
456
+ * The ratchet is on `passed`, which upstream sets for any score above zero: `type_safety`
457
+ * passes at 1.5 of 3, and a full check sliding to a partial one is
458
+ * invisible here. A check this repo starts to pass is added by hand; one it stops passing is a
459
+ * doctor problem until fixed or deliberately removed from this list, in the same commit.
460
+ */
461
+ export const SCORECARD_FLOOR = [
462
+ 'architecture_doc',
463
+ 'agent_instructions',
464
+ 'adr_presence',
465
+ 'module_boundary_docs',
466
+ 'ci_pipeline_exists',
467
+ 'linter_enforcement',
468
+ 'formatter_enforcement',
469
+ 'type_safety',
470
+ 'dependency_auditing',
471
+ 'conventional_commits',
472
+ 'unsafe_code_policy',
473
+ 'test_suite_exists',
474
+ 'feature_matrix_testing',
475
+ 'coverage_measurement',
476
+ 'mutation_testing',
477
+ 'property_based_testing',
478
+ 'fuzz_testing',
479
+ 'contract_tests',
480
+ 'tests_blocking_ci',
481
+ 'code_review_required',
482
+ 'scheduled_ci',
483
+ 'stale_doc_detection',
484
+ 'mr_template',
485
+ 'automated_review',
486
+ 'doc_sync_check',
487
+ 'ai_usage_norms',
488
+ 'small_batch_enforcement',
489
+ 'multiple_approach_culture',
490
+ 'error_handling_policy',
491
+ 'security_critical_marking',
492
+ ];
493
+
494
+ /**
495
+ * The scorecard ratchet — one problem per `SCORECARD_FLOOR` check that no longer passes.
496
+ *
497
+ * DEVELOPMENT CHECKOUT ONLY: it scores `.github/` (CI, CODEOWNERS, the PR template), and
498
+ * `.github/` is WITHHELD from the npm package (`tests/unit/package_manifest.test.mjs`), so an npm
499
+ * install would fail half the floor for files it was never meant to carry. A `.git` entry (a
500
+ * directory in a clone, a FILE in a worktree — hence `existsSync`, not `isDir`) plus
501
+ * `.github/workflows` is what only a checkout of this repository has. Anything else skips: [].
502
+ */
503
+ export function scorecardProblems(root = ROOT) {
504
+ if (!existsSync(path.join(root, '.git')) || !isDir(path.join(root, '.github', 'workflows'))) return [];
505
+ const a = assessRepo(root);
506
+ const checks = new Map(a.categories.flatMap((c) => c.checks).map((k) => [k.id, k]));
507
+ // THE README CARD IS A RENDER OF THIS SAME ASSESSMENT, compared byte for byte: a card that no
508
+ // longer matches the score is the stale-number problem the badges have, caught the same way.
509
+ const card = path.join(root, ...SCORECARD_CARD.split('/'));
510
+ const stale = isFile(card) && readText(card) === renderSvg(a) ? [] : [
511
+ `[scorecard] ${SCORECARD_CARD} does not match the current score — run: geneseed scorecard --svg ${SCORECARD_CARD}`,
512
+ ];
513
+ const readme = path.join(root, 'README.md');
514
+ if (isFile(readme) && !readText(readme).includes(`(${badgeUrl(a)})`)) {
515
+ stale.push(`[scorecard] README.md's scorecard badge does not show the current score — set it to ${badgeUrl(a)}`);
516
+ }
517
+ return stale.concat(SCORECARD_FLOOR.filter((id) => !checks.get(id)?.passed).map((id) => {
518
+ const k = checks.get(id);
519
+ return `[scorecard] ${id} no longer passes — it is in SCORECARD_FLOOR (js/inspect/checks-repo.mjs)`
520
+ + (k ? `: ${k.evidence}. Fix: ${k.remediation}` : ', and the scorecard has no such check');
521
+ }));
522
+ }
523
+
524
+ /** Where the README's scorecard card lives, and what doctor compares against a fresh render. */
525
+ export const SCORECARD_CARD = 'docs/assets/scorecard.svg';
@@ -189,7 +189,10 @@ export function diffCollect({ target = null, theme = null, emit = null, footprin
189
189
  // reference render names the temp tree. Mapped back to the deployed dir before comparing,
190
190
  // or every such skill reads as edited. A deployment that has since MOVED still differs,
191
191
  // and that is real drift — its skills point at a directory that is no longer theirs.
192
+ // A side-file pointer (and a global OpenCode command's) spells the same directory
193
+ // `/`-separated, so that spelling is mapped too.
192
194
  const deployedDir = resolvePath(dir);
195
+ const posix = (p) => p.split(path.sep).join('/');
193
196
  const rels = [...new Set([...ownedSet(dir), ...ownedSet(expected)])].sort();
194
197
  for (const rel of rels) {
195
198
  const a = path.join(dir, rel);
@@ -198,7 +201,8 @@ export function diffCollect({ target = null, theme = null, emit = null, footprin
198
201
  // `read_text(errors="replace")` — Node's utf8 decoder substitutes U+FFFD for the
199
202
  // same bytes, and `readText` folds the line endings the way `Path.read_text` does.
200
203
  const ta = readText(a);
201
- const tb = readText(b).split(expected).join(deployedDir);
204
+ const tb = readText(b).split(expected).join(deployedDir)
205
+ .split(posix(expected)).join(posix(deployedDir));
202
206
  if (cmpKey(rel, ta) !== cmpKey(rel, tb)) {
203
207
  const diff = unifiedDiff(splitLines(tb), splitLines(ta), {
204
208
  fromfile: `source/${rel}`, tofile: `deployed/${rel}`, lineterm: '',
@@ -40,7 +40,7 @@ import { authoringProblems, loopProblems } from './checks-authoring.mjs';
40
40
  import {
41
41
  checkBuild, colorThemeProblems, renderedProblems, themeParityProblems,
42
42
  } from './checks-build.mjs';
43
- import { moduleMapProblems, shimProblems } from './checks-repo.mjs';
43
+ import { moduleMapProblems, scorecardProblems, shimProblems } from './checks-repo.mjs';
44
44
  import { isDoctorNote, sortedProblems, sortedUnique, stemOf, withTempDir } from './scan.mjs';
45
45
 
46
46
  /**
@@ -220,6 +220,7 @@ export function doctorCollect({
220
220
  problems = problems.concat(ran('loops', 'Loop catalogue', loopProblems()));
221
221
  problems = problems.concat(ran('shim', 'Hook shim', shimProbs));
222
222
  problems = problems.concat(ran('map', 'Module map', moduleMapProblems()));
223
+ problems = problems.concat(ran('scorecard', 'Scorecard floor', scorecardProblems()));
223
224
  // P10c's `cli` check is GONE, and the reason is not that it stopped mattering. It hashed
224
225
  // `rituals/harness.py` and compared that against a digest baked into `cli.json`, to catch a
225
226
  // parser edited without regenerating the table. P2 made the table the OWNED document
@@ -92,23 +92,21 @@ export const SKILL_CLASS = {
92
92
  brainstorm: 'design', plan: 'design', council: 'design',
93
93
  workflow: 'design', 'parallel-agents': 'design', pipeline: 'design',
94
94
  loop: 'design', 'brick-forge': 'design',
95
- 'codebase-design': 'design', 'domain-modeling': 'design', wayfinder: 'design',
96
- tickets: 'design',
97
- develop: 'build', refactor: 'build', debug: 'build', migrate: 'build',
98
- 'frontend-design': 'build', 'opencode-theme': 'build', prototype: 'build',
99
- 'forge-mcp': 'build', 'bruno-collection-generator': 'build', 'bruno-test-writer': 'build',
100
- worktree: 'build', 'ci-fix': 'build', 'deps-audit': 'build',
95
+ 'codebase-design': 'design', wayfinder: 'design',
96
+ develop: 'build', refactor: 'build', debug: 'build', dependencies: 'build',
97
+ 'frontend-design': 'build', prototype: 'build',
98
+ 'forge-mcp': 'build', bruno: 'build', worktree: 'build',
101
99
  'geneseed-code-review': 'review', 'fresh-eyes': 'review', 'security-audit': 'review',
102
100
  'roast-me': 'review', 'review-response': 'review', ponytail: 'review',
103
- commit: 'ship', ship: 'ship', release: 'ship', handoff: 'ship', 'git-rescue': 'ship',
104
- 'repo-map': 'understand', 'git-archaeology': 'understand',
101
+ commit: 'ship', ship: 'ship', release: 'ship', handoff: 'ship',
102
+ 'git-history': 'understand',
105
103
  research: 'understand', ingest: 'understand', 'document-project': 'understand',
106
104
  prose: 'understand',
107
105
  // `harness` — skills about the agent's own apparatus (its harness, memory, rules,
108
106
  // knowledge base, terminal) rather than about the user's code. They sat under
109
107
  // `understand` before, which made that class a grab-bag.
110
108
  wiki: 'harness', geneseed: 'harness', rule: 'harness', profile: 'harness',
111
- herdr: 'harness', 'skill-forge': 'harness', 'consolidate-memory': 'harness',
109
+ herdr: 'harness', 'skill-forge': 'harness',
112
110
  teach: 'learn', quiz: 'learn', 'learn-mode': 'learn',
113
111
  };
114
112