@nurix/etna 0.19.66 → 0.19.68

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 (234) hide show
  1. package/bin/etna.js +10 -1
  2. package/data/harness-index.json +1 -5
  3. package/data/harnesses/code/CLAUDE.md +2 -0
  4. package/data/harnesses/code/agents/maintenance.md +4 -1
  5. package/data/harnesses/code/harness.json +7 -2
  6. package/data/harnesses/code/hooks/awareness.mjs +69 -0
  7. package/data/harnesses/code/hooks/compliance.mjs +2 -14
  8. package/data/harnesses/code/hooks/lib/approved.mjs +30 -0
  9. package/data/harnesses/code/hooks/lib/frozen.mjs +3 -0
  10. package/data/harnesses/code/hooks/lib/ownership.mjs +3 -1
  11. package/data/harnesses/code/skills/documentation/references/frontmatter.md +1 -1
  12. package/data/harnesses/code/skills/documentation/scripts/doc-lint.mjs +1 -1
  13. package/data/harnesses/code/skills/documentation/templates/Readme.md +2 -0
  14. package/data/harnesses/code/skills/documentation/templates/structure.md +1 -0
  15. package/data/harnesses/delivery/agents/maintenance.md +4 -1
  16. package/data/harnesses/delivery/harness.json +2 -2
  17. package/data/harnesses/delivery/skills/documentation/references/frontmatter.md +1 -1
  18. package/data/harnesses/delivery/skills/documentation/scripts/doc-lint.mjs +1 -1
  19. package/data/harnesses/delivery/skills/documentation/templates/Readme.md +2 -0
  20. package/data/harnesses/delivery/skills/documentation/templates/structure.md +1 -0
  21. package/data/harnesses/etna/agents/maintenance.md +4 -1
  22. package/data/harnesses/etna/harness.json +2 -2
  23. package/data/harnesses/etna/skills/documentation/references/frontmatter.md +1 -1
  24. package/data/harnesses/etna/skills/documentation/scripts/doc-lint.mjs +1 -1
  25. package/data/harnesses/etna/skills/documentation/templates/Readme.md +2 -0
  26. package/data/harnesses/etna/skills/documentation/templates/structure.md +1 -0
  27. package/lib/install/materialize.mjs +51 -57
  28. package/package.json +2 -2
  29. package/data/harnesses/apollo/.mcp.json +0 -8
  30. package/data/harnesses/apollo/.prettierignore +0 -29
  31. package/data/harnesses/apollo/.prettierrc.json +0 -3
  32. package/data/harnesses/apollo/.vscode/settings.json +0 -19
  33. package/data/harnesses/apollo/CLAUDE.md +0 -162
  34. package/data/harnesses/apollo/agents/bookkeeper.md +0 -28
  35. package/data/harnesses/apollo/agents/brainstorm.md +0 -144
  36. package/data/harnesses/apollo/agents/change-review.md +0 -251
  37. package/data/harnesses/apollo/agents/lld-mode.md +0 -58
  38. package/data/harnesses/apollo/agents/maintenance.md +0 -234
  39. package/data/harnesses/apollo/agents/performance-review.md +0 -223
  40. package/data/harnesses/apollo/agents/retro.md +0 -46
  41. package/data/harnesses/apollo/codex/agents/bookkeeper.toml +0 -26
  42. package/data/harnesses/apollo/codex/agents/brainstorm.toml +0 -142
  43. package/data/harnesses/apollo/codex/agents/change-review.toml +0 -249
  44. package/data/harnesses/apollo/codex/agents/lld-mode.toml +0 -52
  45. package/data/harnesses/apollo/codex/agents/maintenance.toml +0 -228
  46. package/data/harnesses/apollo/codex/agents/performance-review.toml +0 -222
  47. package/data/harnesses/apollo/codex/agents/retro.toml +0 -45
  48. package/data/harnesses/apollo/harness.json +0 -19
  49. package/data/harnesses/apollo/hooks/approved-packages.md +0 -44
  50. package/data/harnesses/apollo/hooks/awareness.mjs +0 -405
  51. package/data/harnesses/apollo/hooks/check-bookkeeping-clean.mjs +0 -19
  52. package/data/harnesses/apollo/hooks/check-changelog-presence.mjs +0 -57
  53. package/data/harnesses/apollo/hooks/check-completion-integrity.mjs +0 -71
  54. package/data/harnesses/apollo/hooks/check-ownership.mjs +0 -22
  55. package/data/harnesses/apollo/hooks/compliance.mjs +0 -457
  56. package/data/harnesses/apollo/hooks/hooks.json +0 -23
  57. package/data/harnesses/apollo/hooks/lib/frozen.mjs +0 -58
  58. package/data/harnesses/apollo/hooks/lib/ownership.mjs +0 -242
  59. package/data/harnesses/apollo/hooks/lib/state.mjs +0 -170
  60. package/data/harnesses/apollo/rules/changelog.md +0 -27
  61. package/data/harnesses/apollo/rules/database.md +0 -34
  62. package/data/harnesses/apollo/rules/frontend.md +0 -30
  63. package/data/harnesses/apollo/rules/general.md +0 -40
  64. package/data/harnesses/apollo/rules/nomenclature.md +0 -30
  65. package/data/harnesses/apollo/rules/pnpm.md +0 -57
  66. package/data/harnesses/apollo/rules/testing.md +0 -64
  67. package/data/harnesses/apollo/skills/boundary/SKILL.md +0 -75
  68. package/data/harnesses/apollo/skills/boundary/references/index.md +0 -16
  69. package/data/harnesses/apollo/skills/boundary/references/mechanisms.md +0 -99
  70. package/data/harnesses/apollo/skills/boundary/references/packs/async-messaging.md +0 -81
  71. package/data/harnesses/apollo/skills/boundary/references/packs/auth.md +0 -93
  72. package/data/harnesses/apollo/skills/boundary/references/packs/build-config-runtime.md +0 -93
  73. package/data/harnesses/apollo/skills/boundary/references/packs/cloudflare.md +0 -93
  74. package/data/harnesses/apollo/skills/boundary/references/packs/frontend.md +0 -93
  75. package/data/harnesses/apollo/skills/boundary/references/packs/generic-tail.md +0 -55
  76. package/data/harnesses/apollo/skills/boundary/references/packs/persistence.md +0 -93
  77. package/data/harnesses/apollo/skills/boundary/references/packs/wire-api.md +0 -87
  78. package/data/harnesses/apollo/skills/brainstorm/README.md +0 -56
  79. package/data/harnesses/apollo/skills/brainstorm/SKILL.md +0 -173
  80. package/data/harnesses/apollo/skills/brainstorm/references/doctrine.md +0 -98
  81. package/data/harnesses/apollo/skills/brainstorm/references/harvest.md +0 -86
  82. package/data/harnesses/apollo/skills/brainstorm/references/session-schema.md +0 -186
  83. package/data/harnesses/apollo/skills/brainstorm/scripts/detector.mjs +0 -460
  84. package/data/harnesses/apollo/skills/design/SKILL.md +0 -121
  85. package/data/harnesses/apollo/skills/design/components/charts.md +0 -76
  86. package/data/harnesses/apollo/skills/design/components/collections.md +0 -132
  87. package/data/harnesses/apollo/skills/design/components/feedback.md +0 -47
  88. package/data/harnesses/apollo/skills/design/components/hooks.md +0 -35
  89. package/data/harnesses/apollo/skills/design/components/index.md +0 -143
  90. package/data/harnesses/apollo/skills/design/components/inputs.md +0 -327
  91. package/data/harnesses/apollo/skills/design/components/layout.md +0 -52
  92. package/data/harnesses/apollo/skills/design/components/media.md +0 -66
  93. package/data/harnesses/apollo/skills/design/components/motion.md +0 -82
  94. package/data/harnesses/apollo/skills/design/components/navigation.md +0 -29
  95. package/data/harnesses/apollo/skills/design/components/overlays.md +0 -72
  96. package/data/harnesses/apollo/skills/design/components/setup.md +0 -40
  97. package/data/harnesses/apollo/skills/design/components/typography.md +0 -46
  98. package/data/harnesses/apollo/skills/design/components/utils.md +0 -37
  99. package/data/harnesses/apollo/skills/design/data/blueprints/browse.md +0 -38
  100. package/data/harnesses/apollo/skills/design/data/blueprints/creation-flow.md +0 -38
  101. package/data/harnesses/apollo/skills/design/data/blueprints/dashboard.md +0 -38
  102. package/data/harnesses/apollo/skills/design/data/blueprints/grid.md +0 -38
  103. package/data/harnesses/apollo/skills/design/data/blueprints/queue.md +0 -38
  104. package/data/harnesses/apollo/skills/design/data/blueprints/record-detail.md +0 -38
  105. package/data/harnesses/apollo/skills/design/data/blueprints/settings.md +0 -38
  106. package/data/harnesses/apollo/skills/design/data/charts/census.csv +0 -58
  107. package/data/harnesses/apollo/skills/design/data/checklists/browse.md +0 -21
  108. package/data/harnesses/apollo/skills/design/data/checklists/creation-flow.md +0 -20
  109. package/data/harnesses/apollo/skills/design/data/checklists/dashboard.md +0 -21
  110. package/data/harnesses/apollo/skills/design/data/checklists/grid.md +0 -21
  111. package/data/harnesses/apollo/skills/design/data/checklists/queue.md +0 -21
  112. package/data/harnesses/apollo/skills/design/data/checklists/record-detail.md +0 -21
  113. package/data/harnesses/apollo/skills/design/data/checklists/settings.md +0 -21
  114. package/data/harnesses/apollo/skills/design/data/chrome/board-column.md +0 -68
  115. package/data/harnesses/apollo/skills/design/data/chrome/data-table.md +0 -63
  116. package/data/harnesses/apollo/skills/design/data/chrome/side-panel.md +0 -60
  117. package/data/harnesses/apollo/skills/design/data/chrome/sidebar.md +0 -78
  118. package/data/harnesses/apollo/skills/design/data/chrome/stat-cards.md +0 -62
  119. package/data/harnesses/apollo/skills/design/data/chrome/topbar.md +0 -65
  120. package/data/harnesses/apollo/skills/design/data/elevation/ant-design-directional-shadow.md +0 -19
  121. package/data/harnesses/apollo/skills/design/data/elevation/apple-liquid-glass-material.md +0 -18
  122. package/data/harnesses/apollo/skills/design/data/elevation/atlassian-surface-shadow-pairing.md +0 -20
  123. package/data/harnesses/apollo/skills/design/data/elevation/fluent-2-key-ambient-ramp.md +0 -20
  124. package/data/harnesses/apollo/skills/design/data/elevation/github-primer-sparing-shadow.md +0 -19
  125. package/data/harnesses/apollo/skills/design/data/elevation/ibm-carbon-layer-and-border.md +0 -19
  126. package/data/harnesses/apollo/skills/design/data/elevation/linear-dark-surface-ladder.md +0 -17
  127. package/data/harnesses/apollo/skills/design/data/elevation/material-2-key-ambient-dp-ramp.md +0 -19
  128. package/data/harnesses/apollo/skills/design/data/elevation/material-3-tonal-elevation.md +0 -21
  129. package/data/harnesses/apollo/skills/design/data/elevation/neobrutalist-hard-offset.md +0 -19
  130. package/data/harnesses/apollo/skills/design/data/elevation/neumorphic-soft-dual-shadow.md +0 -18
  131. package/data/harnesses/apollo/skills/design/data/elevation/notion-warm-minimal-hairline.md +0 -17
  132. package/data/harnesses/apollo/skills/design/data/elevation/polaris-elevation-inset-bevel.md +0 -21
  133. package/data/harnesses/apollo/skills/design/data/elevation/radix-themes-alpha-ring-scale.md +0 -19
  134. package/data/harnesses/apollo/skills/design/data/elevation/shadcn-neutral-derived-radius.md +0 -19
  135. package/data/harnesses/apollo/skills/design/data/elevation/stripe-brand-tinted-dual-shadow.md +0 -18
  136. package/data/harnesses/apollo/skills/design/data/elevation/tailwind-default-utility-scale.md +0 -19
  137. package/data/harnesses/apollo/skills/design/data/elevation/vercel-geist-shadow-as-border.md +0 -20
  138. package/data/harnesses/apollo/skills/design/data/iconography.md +0 -199
  139. package/data/harnesses/apollo/skills/design/data/motion/accordion-expand.md +0 -18
  140. package/data/harnesses/apollo/skills/design/data/motion/destructive-hold-confirm.md +0 -18
  141. package/data/harnesses/apollo/skills/design/data/motion/drag-dismiss-momentum.md +0 -18
  142. package/data/harnesses/apollo/skills/design/data/motion/empty-state-arrive.md +0 -18
  143. package/data/harnesses/apollo/skills/design/data/motion/hover-lift.md +0 -18
  144. package/data/harnesses/apollo/skills/design/data/motion/modal-center-arrive.md +0 -18
  145. package/data/harnesses/apollo/skills/design/data/motion/number-ticker-roll.md +0 -18
  146. package/data/harnesses/apollo/skills/design/data/motion/onboarding-bounce.md +0 -18
  147. package/data/harnesses/apollo/skills/design/data/motion/panel-slide.md +0 -18
  148. package/data/harnesses/apollo/skills/design/data/motion/press-feedback.md +0 -18
  149. package/data/harnesses/apollo/skills/design/data/motion/progress-linear-loop.md +0 -18
  150. package/data/harnesses/apollo/skills/design/data/motion/route-crossfade.md +0 -18
  151. package/data/harnesses/apollo/skills/design/data/motion/rubber-band-overscroll.md +0 -18
  152. package/data/harnesses/apollo/skills/design/data/motion/skeleton-shimmer.md +0 -18
  153. package/data/harnesses/apollo/skills/design/data/motion/stagger-arrive.md +0 -18
  154. package/data/harnesses/apollo/skills/design/data/motion/toast-enter-stack.md +0 -18
  155. package/data/harnesses/apollo/skills/design/data/motion/tooltip-chain-instant.md +0 -18
  156. package/data/harnesses/apollo/skills/design/data/motion/trigger-anchored-reveal.md +0 -18
  157. package/data/harnesses/apollo/skills/design/data/tells.csv +0 -34
  158. package/data/harnesses/apollo/skills/design/data/voices/bright-collaborator.md +0 -29
  159. package/data/harnesses/apollo/skills/design/data/voices/direct-operator.md +0 -29
  160. package/data/harnesses/apollo/skills/design/data/voices/earnest-helper.md +0 -29
  161. package/data/harnesses/apollo/skills/design/data/voices/editorial-composed.md +0 -29
  162. package/data/harnesses/apollo/skills/design/data/voices/quiet-concierge.md +0 -29
  163. package/data/harnesses/apollo/skills/design/data/voices/warm-guide.md +0 -29
  164. package/data/harnesses/apollo/skills/design/references/compose.md +0 -23
  165. package/data/harnesses/apollo/skills/design/references/content.md +0 -28
  166. package/data/harnesses/apollo/skills/design/references/emission.md +0 -21
  167. package/data/harnesses/apollo/skills/design/references/existing-apps.md +0 -27
  168. package/data/harnesses/apollo/skills/design/references/foundations.md +0 -57
  169. package/data/harnesses/apollo/skills/design/references/intake.md +0 -40
  170. package/data/harnesses/apollo/skills/design/references/object-pass.md +0 -24
  171. package/data/harnesses/apollo/skills/design/references/ship-gate.md +0 -29
  172. package/data/harnesses/apollo/skills/design/references/ux-patterns/Readme.md +0 -195
  173. package/data/harnesses/apollo/skills/design/references/ux-patterns/ai-layer.md +0 -49
  174. package/data/harnesses/apollo/skills/design/references/ux-patterns/attributes-lifecycle.md +0 -43
  175. package/data/harnesses/apollo/skills/design/references/ux-patterns/container-choice.md +0 -44
  176. package/data/harnesses/apollo/skills/design/references/ux-patterns/creation-paths.md +0 -46
  177. package/data/harnesses/apollo/skills/design/references/ux-patterns/dashboards.md +0 -42
  178. package/data/harnesses/apollo/skills/design/references/ux-patterns/modes.md +0 -42
  179. package/data/harnesses/apollo/skills/design/references/ux-patterns/object-model.md +0 -46
  180. package/data/harnesses/apollo/skills/design/references/ux-patterns/page-archetype.md +0 -48
  181. package/data/harnesses/apollo/skills/design/references/ux-patterns/promotion-demotion.md +0 -37
  182. package/data/harnesses/apollo/skills/design/references/ux-patterns/settings.md +0 -42
  183. package/data/harnesses/apollo/skills/design/references/ux-patterns/standard-pages.md +0 -46
  184. package/data/harnesses/apollo/skills/design/references/ux-patterns/state-matrix.md +0 -41
  185. package/data/harnesses/apollo/skills/design/references/ux-patterns/table-mechanics.md +0 -48
  186. package/data/harnesses/apollo/skills/design/references/ux-patterns/verbs.md +0 -45
  187. package/data/harnesses/apollo/skills/design/references/ux-patterns/viewer-context.md +0 -40
  188. package/data/harnesses/apollo/skills/design/scripts/README-validator.md +0 -27
  189. package/data/harnesses/apollo/skills/design/scripts/select-chart.mjs +0 -622
  190. package/data/harnesses/apollo/skills/design/scripts/validate-design-md.mjs +0 -1198
  191. package/data/harnesses/apollo/skills/documentation/SKILL.md +0 -135
  192. package/data/harnesses/apollo/skills/documentation/references/examples.md +0 -279
  193. package/data/harnesses/apollo/skills/documentation/references/frontmatter.md +0 -44
  194. package/data/harnesses/apollo/skills/documentation/references/instruction-style.md +0 -50
  195. package/data/harnesses/apollo/skills/documentation/references/style-guide.md +0 -384
  196. package/data/harnesses/apollo/skills/documentation/references/untemplated.md +0 -38
  197. package/data/harnesses/apollo/skills/documentation/scripts/doc-lint.mjs +0 -140
  198. package/data/harnesses/apollo/skills/documentation/templates/Readme.md +0 -35
  199. package/data/harnesses/apollo/skills/documentation/templates/api-reference.md +0 -20
  200. package/data/harnesses/apollo/skills/documentation/templates/architecture-decision.md +0 -121
  201. package/data/harnesses/apollo/skills/documentation/templates/backlog.md +0 -33
  202. package/data/harnesses/apollo/skills/documentation/templates/behavioral-checklist.md +0 -81
  203. package/data/harnesses/apollo/skills/documentation/templates/bookkeeping.md +0 -18
  204. package/data/harnesses/apollo/skills/documentation/templates/changelog-entry.md +0 -57
  205. package/data/harnesses/apollo/skills/documentation/templates/changelog-index.md +0 -41
  206. package/data/harnesses/apollo/skills/documentation/templates/docs-readme.md +0 -72
  207. package/data/harnesses/apollo/skills/documentation/templates/domain-model.md +0 -30
  208. package/data/harnesses/apollo/skills/documentation/templates/environment-registry.md +0 -15
  209. package/data/harnesses/apollo/skills/documentation/templates/feature.md +0 -150
  210. package/data/harnesses/apollo/skills/documentation/templates/glossary.md +0 -11
  211. package/data/harnesses/apollo/skills/documentation/templates/implementation-map.md +0 -42
  212. package/data/harnesses/apollo/skills/documentation/templates/journal.md +0 -13
  213. package/data/harnesses/apollo/skills/documentation/templates/memory-main.md +0 -25
  214. package/data/harnesses/apollo/skills/documentation/templates/migration-runbook.md +0 -34
  215. package/data/harnesses/apollo/skills/documentation/templates/non-goals.md +0 -11
  216. package/data/harnesses/apollo/skills/documentation/templates/operations-procedure.md +0 -36
  217. package/data/harnesses/apollo/skills/documentation/templates/plan.md +0 -88
  218. package/data/harnesses/apollo/skills/documentation/templates/protected-files.md +0 -37
  219. package/data/harnesses/apollo/skills/documentation/templates/root-readme.md +0 -104
  220. package/data/harnesses/apollo/skills/documentation/templates/security-overview.md +0 -27
  221. package/data/harnesses/apollo/skills/documentation/templates/structure.md +0 -65
  222. package/data/harnesses/apollo/skills/documentation/templates/system-overview.md +0 -32
  223. package/data/harnesses/apollo/skills/documentation/templates/testing-strategy.md +0 -29
  224. package/data/harnesses/apollo/skills/schema-design/SKILL.md +0 -141
  225. package/data/harnesses/apollo/skills/test/SKILL.md +0 -104
  226. package/data/harnesses/apollo/skills/testing/SKILL.md +0 -166
  227. package/data/harnesses/apollo/skills/workflow-mapper/SKILL.md +0 -451
  228. package/data/harnesses/apollo/skills/workflow-mapper/references/patterns.md +0 -214
  229. /package/data/harnesses/{apollo → code}/docs/README.md +0 -0
  230. /package/data/harnesses/{apollo → code}/docs/history/CHANGELOG.md +0 -0
  231. /package/data/harnesses/{apollo → code}/docs/history/backlog.md +0 -0
  232. /package/data/harnesses/{apollo → code}/docs/memory/main.md +0 -0
  233. /package/data/harnesses/{apollo → code}/docs/structure.md +0 -0
  234. /package/data/harnesses/{apollo → code}/rules/cloudflare.md +0 -0
@@ -1,242 +0,0 @@
1
- // harness/code/hooks/lib/ownership.mjs — frontmatter scan + owns:-glob resolution, shared by the
2
- // hook entrypoints (awareness, compliance) and the CI checks so gates and pipeline cannot disagree.
3
- // Everything here is read-only over docs/features/ and the source tree; no network, no deps.
4
-
5
- import fs from "node:fs";
6
- import path from "node:path";
7
-
8
- // Read all of stdin synchronously; returns "" if nothing is piped.
9
- export function readStdin() {
10
- try {
11
- return fs.readFileSync(0, "utf8");
12
- } catch (e) {
13
- return "";
14
- }
15
- }
16
-
17
- // Resolve the project root: CLAUDE_PROJECT_DIR env, else payload cwd, else process cwd.
18
- export function resolveProjectDir(payload) {
19
- return process.env.CLAUDE_PROJECT_DIR || (payload && payload.cwd) || process.cwd();
20
- }
21
-
22
- // Recursively collect every .md file under dir (returns [] if the dir is absent).
23
- export function listMarkdown(dir) {
24
- const out = [];
25
- let entries;
26
- try {
27
- entries = fs.readdirSync(dir, { withFileTypes: true });
28
- } catch (e) {
29
- return out;
30
- }
31
- for (const entry of entries) {
32
- const full = path.join(dir, entry.name);
33
- if (entry.isDirectory()) out.push(...listMarkdown(full));
34
- else if (entry.isFile() && entry.name.endsWith(".md")) out.push(full);
35
- }
36
- return out;
37
- }
38
-
39
- // Pull the leading `--- … ---` frontmatter block; "" if the file has none.
40
- export function frontmatter(text) {
41
- const m = /^?---\r?\n([\s\S]*?)\r?\n---/.exec(text);
42
- return m ? m[1] : "";
43
- }
44
-
45
- // Trim surrounding whitespace, then strip one layer of matching quotes.
46
- export function unquote(s) {
47
- return String(s).trim().replace(/^["']|["']$/g, "").trim();
48
- }
49
-
50
- // One YAML list/array item → clean value: a quoted item ends at its closing quote
51
- // (anything after — trailing comments — drops); an unquoted item ends at ` #`.
52
- export function cleanItem(s) {
53
- const t = String(s).trim();
54
- const q = t[0];
55
- if (q === '"' || q === "'") {
56
- const end = t.indexOf(q, 1);
57
- if (end > 0) return t.slice(1, end).trim();
58
- }
59
- const hash = t.search(/\s#/);
60
- return unquote(hash >= 0 ? t.slice(0, hash) : t);
61
- }
62
-
63
- // `[a, b] # comment` → items; content ends at the closing bracket, not the line end.
64
- function parseInlineArray(raw) {
65
- const end = raw.indexOf("]");
66
- const body = (end >= 0 ? raw.slice(0, end) : raw).replace(/^\[/, "");
67
- return body.split(",").map((s) => cleanItem(s)).filter(Boolean);
68
- }
69
-
70
- // Read one frontmatter key: scalar (`k: v`), inline array (`k: [a, b]`), or a following `- item` block.
71
- // Returns a string for scalars, an array for lists, or undefined when the key is absent.
72
- export function field(block, key) {
73
- const lines = block.split(/\r?\n/);
74
- const head = new RegExp(`^${key}\\s*:\\s*(.*)$`);
75
- for (let i = 0; i < lines.length; i++) {
76
- const m = head.exec(lines[i]);
77
- if (!m) continue;
78
- const raw = m[1].trim();
79
- if (raw === "") {
80
- // A bare key may continue as an indented inline array on the next line, or a `- item` block.
81
- // The array may span several lines, so join through the closing bracket before parsing —
82
- // reading only the first line yields a bare "[", which parses to [] and makes the whole doc
83
- // look like it owns nothing. `scanFeatureDocs` skips such docs, so the failure is invisible:
84
- // the doc drops out of the ownership index and out of every dead-glob count.
85
- const next = (lines[i + 1] || "").trim();
86
- if (next.startsWith("[")) {
87
- let joined = "";
88
- for (let j = i + 1; j < lines.length; j++) {
89
- joined += (joined ? " " : "") + lines[j].trim();
90
- if (lines[j].includes("]")) break;
91
- }
92
- return parseInlineArray(joined);
93
- }
94
- const items = [];
95
- for (let j = i + 1; j < lines.length; j++) {
96
- const li = /^\s*-\s+(.*)$/.exec(lines[j]);
97
- if (!li) break;
98
- items.push(cleanItem(li[1]));
99
- }
100
- return items;
101
- }
102
- if (raw.startsWith("[")) {
103
- return parseInlineArray(raw);
104
- }
105
- return unquote(raw);
106
- }
107
- return undefined;
108
- }
109
-
110
- // Coerce a field that may be scalar/array/undefined into a clean string array.
111
- export function asList(v) {
112
- if (v == null) return [];
113
- return (Array.isArray(v) ? v : [v]).map((s) => String(s).trim()).filter(Boolean);
114
- }
115
-
116
- // Scan docs/features/**/*.md and return one entry per feature doc that declares owns:.
117
- // Shape: { doc (repo-relative, /-separated), owns[], module, keywords[], status }.
118
- export function scanFeatureDocs(projectDir) {
119
- const featuresDir = path.join(projectDir, "docs", "features");
120
- const files = listMarkdown(featuresDir);
121
- const docs = [];
122
- for (const file of files) {
123
- let text;
124
- try {
125
- text = fs.readFileSync(file, "utf8");
126
- } catch (e) {
127
- continue;
128
- }
129
- const block = frontmatter(text);
130
- if (!block) continue;
131
- const owns = asList(field(block, "owns"));
132
- if (owns.length === 0) continue;
133
- docs.push({
134
- doc: path.relative(projectDir, file).split(path.sep).join("/"),
135
- owns,
136
- module: (field(block, "module") || "—").toString(),
137
- keywords: asList(field(block, "keywords")),
138
- status: (field(block, "status") || "active").toString().toLowerCase(),
139
- });
140
- }
141
- return docs;
142
- }
143
-
144
- // Build the ownership index text from every feature doc's frontmatter.
145
- export function buildIndex(projectDir) {
146
- // Total markdown under docs/features/, counted independently of scanFeatureDocs — which
147
- // drops a doc carrying no owns:, making a parse failure and a doc that owns nothing look alike.
148
- const total = listMarkdown(path.join(projectDir, "docs", "features")).length;
149
- const docs = scanFeatureDocs(projectDir);
150
- const lines = [];
151
- let deprecated = 0;
152
- for (const entry of docs) {
153
- if (entry.status === "deprecated") {
154
- deprecated += 1;
155
- continue; // keep deprecated docs out of the routing lines
156
- }
157
- const kw = entry.keywords.length ? `[${entry.keywords.join(", ")}]` : "[]";
158
- for (const glob of entry.owns) {
159
- lines.push(`${glob} → ${entry.doc} · ${entry.module} · ${kw} · ${entry.status}`);
160
- }
161
- }
162
- if (lines.length === 0) return "";
163
- lines.sort();
164
- const header =
165
- "# Ownership index (generated) — code-path glob → owning feature doc · module · keywords · status\n" +
166
- "# Match the file or topic you're about to touch, then read the owning doc before changing it.\n";
167
- const indexed = docs.length - deprecated;
168
- const skipped = total - docs.length;
169
- const notes = [];
170
- if (deprecated > 0) notes.push(`${deprecated} deprecated`);
171
- if (skipped > 0) notes.push(`${skipped} with no owns:`);
172
- const footer = notes.length
173
- ? `\n# (${indexed}/${total} feature doc(s) indexed - ${notes.join(", ")})`
174
- : "";
175
- return header + lines.join("\n") + footer;
176
- }
177
-
178
- // Compile one owns:-style glob (`**` any depth, `*` within a segment) to a RegExp
179
- // over /-separated repo-relative paths. `a/**` also matches `a` itself.
180
- export function globToRegExp(glob) {
181
- const g = glob.replace(/\\/g, "/").replace(/\/+$/, "");
182
- const escaped = g.replace(/[\\^$.|?+()[\]{}]/g, "\\$&");
183
- const re = escaped
184
- .replace(/\/\*\*$/, "\0TAIL\0") // `x/**` → x plus optionally anything below it
185
- .replace(/\*\*\//g, "\0LEAD\0") // `**/x` → any (possibly empty) directory prefix
186
- .replace(/\*\*/g, "\0ANY\0")
187
- .replace(/\*/g, "[^/]*")
188
- .replace(/\0TAIL\0/g, "(?:/.*)?")
189
- .replace(/\0LEAD\0/g, "(?:.*/)?")
190
- .replace(/\0ANY\0/g, ".*");
191
- return new RegExp(`^${re}$`);
192
- }
193
-
194
- // Feature docs owning the given repo-relative path (deprecated docs excluded).
195
- export function owningDocs(projectDir, relPath) {
196
- const rel = relPath.split(path.sep).join("/");
197
- const out = [];
198
- for (const entry of scanFeatureDocs(projectDir)) {
199
- if (entry.status === "deprecated") continue;
200
- if (entry.owns.some((g) => globToRegExp(g).test(rel))) out.push(entry);
201
- }
202
- return out;
203
- }
204
-
205
- const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", ".claude", ".codex"]);
206
-
207
- // Walk the repo once and return every tracked-ish file path (repo-relative, /-separated).
208
- // Used to detect dead globs; heavy dirs are skipped, depth is bounded by the tree itself.
209
- export function listRepoFiles(projectDir) {
210
- const out = [];
211
- function walk(dir) {
212
- let entries;
213
- try {
214
- entries = fs.readdirSync(dir, { withFileTypes: true });
215
- } catch (e) {
216
- return;
217
- }
218
- for (const entry of entries) {
219
- if (SKIP_DIRS.has(entry.name)) continue;
220
- const full = path.join(dir, entry.name);
221
- if (entry.isDirectory()) walk(full);
222
- else if (entry.isFile()) out.push(path.relative(projectDir, full).split(path.sep).join("/"));
223
- }
224
- }
225
- walk(projectDir);
226
- return out;
227
- }
228
-
229
- // Globs across all feature docs that match zero files on disk.
230
- // Returns [{ glob, doc }]; pass a precomputed file list to amortize the walk.
231
- export function deadGlobs(projectDir, repoFiles) {
232
- const files = repoFiles || listRepoFiles(projectDir);
233
- const dead = [];
234
- for (const entry of scanFeatureDocs(projectDir)) {
235
- if (entry.status === "deprecated") continue;
236
- for (const glob of entry.owns) {
237
- const re = globToRegExp(glob);
238
- if (!files.some((f) => re.test(f))) dead.push({ glob, doc: entry.doc });
239
- }
240
- }
241
- return dead;
242
- }
@@ -1,170 +0,0 @@
1
- // harness/code/hooks/lib/state.mjs — the bookkeeping queue (docs/operations/bookkeeping.md) and the
2
- // machine-local state dir (.claude/state/). The row grammar is the contract between the awareness
3
- // detector (writer), the bookkeeper agent (drain), the compliance gates (walls), and CI (backstop):
4
- // - auto: {YYYY-MM-DD} · {finding-kind} · {description} → {action} | sid={session_id}
5
- // - note: {YYYY-MM-DD} · {area} · {context} → lands-in: {owning doc | backlog | memory} | sid=…
6
- // - needs-session: {YYYY-MM-DD} · {what could not be grounded} → main session resolves | refs={ts}
7
- // - waived: {YYYY-MM-DD} · {what is deferred} · reason: {why} → expires={YYYY-MM-DD}
8
- // Unresolved = auto + note + needs-session. waived: rows are resolved history until expires= passes.
9
-
10
- import fs from "node:fs";
11
- import path from "node:path";
12
-
13
- export const QUEUE_PATH = ["docs", "operations", "bookkeeping.md"];
14
-
15
- export const QUEUE_HEADER = `# Bookkeeping
16
-
17
- The shared work queue keeping documentation and code in sync — a queue, never an archive. Rows are appended mechanically (auto:) and by working sessions (note:), drained by the bookkeeper agent, and walled at commit: a commit is possible only when every row is either **resolved, which deletes it**, or converted to \`waived:\`. A drained row leaves no trace here; what it closed is recorded in the doc it corrected, and git history holds the row itself. Row grammar and lifecycle: the \`documentation\` skill's \`templates/bookkeeping.md\`. A \`waived:\` row carries \`expires=\` and is PR-reviewable history.
18
- `;
19
-
20
- export function queueFile(projectDir) {
21
- return path.join(projectDir, ...QUEUE_PATH);
22
- }
23
-
24
- const ROW_RE = /^-\s+(auto|note|needs-session|waived):\s+(.*)$/;
25
-
26
- // Parse the queue text into rows: { kind, date, body, raw }. Non-row lines are ignored.
27
- export function parseQueue(text) {
28
- const rows = [];
29
- for (const line of String(text || "").split(/\r?\n/)) {
30
- const m = ROW_RE.exec(line.trim());
31
- if (!m) continue;
32
- const body = m[2].trim();
33
- const date = (/^(\d{4}-\d{2}-\d{2})/.exec(body) || [])[1] || "";
34
- rows.push({ kind: m[1], date, body, raw: line });
35
- }
36
- return rows;
37
- }
38
-
39
- export function readQueue(projectDir) {
40
- try {
41
- return parseQueue(fs.readFileSync(queueFile(projectDir), "utf8"));
42
- } catch (e) {
43
- return [];
44
- }
45
- }
46
-
47
- // Counts by kind; `unresolved` is the number the walls act on.
48
- export function countRows(rows) {
49
- const c = { auto: 0, note: 0, needsSession: 0, waived: 0 };
50
- for (const row of rows) {
51
- if (row.kind === "auto") c.auto += 1;
52
- else if (row.kind === "note") c.note += 1;
53
- else if (row.kind === "needs-session") c.needsSession += 1;
54
- else if (row.kind === "waived") c.waived += 1;
55
- }
56
- c.unresolved = c.auto + c.note + c.needsSession;
57
- return c;
58
- }
59
-
60
- // Format one auto: row per the grammar; the sid tail is the only provenance kept —
61
- // a transcript path or timestamp is machine noise in a PR-reviewed queue.
62
- export function formatAutoRow({ date, kind, description, action, sessionId }) {
63
- return `- auto: ${date} · ${kind} · ${description} → ${action} | sid=${sessionId || "-"}`;
64
- }
65
-
66
- // Format one needs-session: row — a finding detected mechanically that only a session can resolve.
67
- // It carries no `sid=`: unlike an auto: row it is never drained by the bookkeeper, so the session
68
- // that raised it is not the one that must answer it.
69
- export function formatNeedsSessionRow({ date, description, refs }) {
70
- return `- needs-session: ${date} · ${description} → main session resolves | refs=${refs}`;
71
- }
72
-
73
- // Append rows to the queue, creating the file with its genre header when absent.
74
- // Dedupes on the row text before the `| sid=` tail, so a re-fired detector never stacks duplicates.
75
- export function appendRows(projectDir, rowLines) {
76
- if (!rowLines || rowLines.length === 0) return 0;
77
- const file = queueFile(projectDir);
78
- let text = "";
79
- try {
80
- text = fs.readFileSync(file, "utf8");
81
- } catch (e) {
82
- fs.mkdirSync(path.dirname(file), { recursive: true });
83
- text = QUEUE_HEADER + "\n";
84
- }
85
- const key = (line) => line.split("| sid=")[0].trim();
86
- const existing = new Set(parseQueue(text).map((r) => key(r.raw)));
87
- const fresh = rowLines.filter((line) => !existing.has(key(line)));
88
- if (fresh.length === 0) return 0;
89
- if (!text.endsWith("\n")) text += "\n";
90
- fs.writeFileSync(file, text + fresh.join("\n") + "\n");
91
- return fresh.length;
92
- }
93
-
94
- // Delete queue rows matching the predicate (used by the detector to auto-resolve
95
- // rows whose finding the session has since addressed). Returns the count removed.
96
- export function resolveRows(projectDir, predicate) {
97
- const file = queueFile(projectDir);
98
- let text;
99
- try {
100
- text = fs.readFileSync(file, "utf8");
101
- } catch (e) {
102
- return 0;
103
- }
104
- let removed = 0;
105
- const kept = text.split(/\r?\n/).filter((line) => {
106
- const m = ROW_RE.exec(line.trim());
107
- if (!m) return true;
108
- const row = { kind: m[1], body: m[2].trim(), raw: line };
109
- if (predicate(row)) {
110
- removed += 1;
111
- return false;
112
- }
113
- return true;
114
- });
115
- if (removed > 0) fs.writeFileSync(file, kept.join("\n"));
116
- return removed;
117
- }
118
-
119
- // Unexpired waived rows for a finding kind (the dead-glob allowlist CI honors).
120
- // A waived row covers a glob when its body names it verbatim.
121
- export function unexpiredWaivers(rows, kind, today) {
122
- return rows.filter((row) => {
123
- if (row.kind !== "waived") return false;
124
- if (kind && !row.body.includes(kind)) return false;
125
- const expires = (/expires=(\d{4}-\d{2}-\d{2})/.exec(row.body) || [])[1];
126
- if (!expires) return false;
127
- return expires >= today;
128
- });
129
- }
130
-
131
- // Ensure .claude/state/ exists and is self-ignoring (a `*` .gitignore inside it),
132
- // so consumers never see machine-local state as untracked noise. Returns the dir.
133
- export function stateDir(projectDir) {
134
- const dir = path.join(projectDir, ".claude", "state");
135
- fs.mkdirSync(dir, { recursive: true });
136
- const ignore = path.join(dir, ".gitignore");
137
- if (!fs.existsSync(ignore)) fs.writeFileSync(ignore, "*\n");
138
- return dir;
139
- }
140
-
141
- // Session-scoped ask memory: an `ask` gate fires once per (session, class); after the user has
142
- // answered it once, repeats in the same session pass silently (the runtime's own permission
143
- // prompts still apply). Records live in .claude/state/asked.tsv — machine-local, self-ignored.
144
- export function askedThisSession(projectDir, sessionId, key) {
145
- if (!sessionId) return false;
146
- try {
147
- const text = fs.readFileSync(path.join(projectDir, ".claude", "state", "asked.tsv"), "utf8");
148
- return text.split("\n").includes(`${sessionId}\t${key}`);
149
- } catch (e) {
150
- return false;
151
- }
152
- }
153
-
154
- export function recordAsked(projectDir, sessionId, key) {
155
- if (!sessionId) return;
156
- fs.appendFileSync(path.join(stateDir(projectDir), "asked.tsv"), `${sessionId}\t${key}\n`);
157
- }
158
-
159
- // Append one tab-separated line to .claude/state/touched.tsv (file → owning doc map for the session).
160
- //
161
- // THE SESSION ID IS THE FOURTH COLUMN, and it is what lets a reader tell one session's edits from
162
- // another's. The file is shared by every session running in this checkout, so without it a gate that
163
- // asks "did I touch this?" can only answer "did anyone?" — and blocks one session for another's
164
- // work. Appended rather than inserted so rows written before this column existed still parse: the
165
- // path stays at index 1, and a missing id reads as `undefined`, which belongs to no session.
166
- export function recordTouched(projectDir, relPath, docs, sessionId) {
167
- const dir = stateDir(projectDir);
168
- const line = `${new Date().toISOString()}\t${relPath}\t${docs.join(",") || "-"}\t${sessionId || "-"}\n`;
169
- fs.appendFileSync(path.join(dir, "touched.tsv"), line);
170
- }
@@ -1,27 +0,0 @@
1
- # Changelog
2
-
3
- Every unit of work leaves one record — `docs/history/changelog/YYYY-MM-DD-{three-words}.md` (the three words name the work, not the commit) — plus its row in `docs/history/CHANGELOG.md`, appended in the same pass so the index never trails the folder. Entry format, field rules, and the closed exemption list are the `documentation` skill's `templates/changelog-entry.md`; this rule owns when an entry is owed and how it lives. An entry is never a commit summary — it carries the refinement context only the working session holds, which no diff reconstructs.
4
-
5
- ## When an entry is owed
6
-
7
- | Trigger | What to write |
8
- | --- | --- |
9
- | A plan reaches `Status: Complete` | A draft entry for that plan, linked to it — written now, while the session still holds the context |
10
- | The user asks for a changelog | The entry for the work in hand. No commit required |
11
- | A commit is made | Every non-exempt commit is covered by an entry — the `commit` skill writes or completes it (no `commit` skill in the runtime ⇒ write the entry from the `documentation` skill's `templates/changelog-entry.md` and stage it with the commit) |
12
-
13
- An entry is owed only by work that changes promised behavior or takes a design decision; mechanical and cosmetic commits — a typo, a copy tweak, a config value, a log line — take only their `docs/history/CHANGELOG.md` row, no entry file. The failure fields (`Ask:`, `Skills:`, `Pin:`) are required only when non-none.
14
-
15
- Route the lesson by who pays for losing it: product context — what changed and why — belongs in the entry; anything the **next session would re-pay** — a library's API drifted from what the model expects, a validator's quirk, a tooling workaround — goes to `docs/memory/main.md` as well, because a changelog is read by people tracing history and memory is read by every session before it repeats the mistake.
16
-
17
- ## Lifecycle
18
-
19
- Draft (created before its commit exists) → complete (missing fields filled from the diff and the session) → frozen the moment it rides in a commit. **Immutability begins at commit, not at creation** — a draft is meant to be edited, which is what lets a completed plan record its context before anything is committed; a committed entry is history, and a correction is a new entry linking back, **never** an edit.
20
-
21
- ## Granularity
22
-
23
- A commit is the smallest unit an entry may describe; above that the mapping is judgment — four commits may be four entries or one, whichever reads better as history. An entry spanning several commits rides in the **last** of them, naming the predecessors in `Commits:`. When work exceeds what a draft describes, write a second entry rather than stretching the draft — the first stays an honest account of the plan.
24
-
25
- ## Migrations are a different genre
26
-
27
- **One-shot migrations are a different genre.** Work that runs **once** against a live system — a data backfill, a schema cutover, a dependency or harness upgrade rolled across repos — is a **migration runbook** at `docs/history/migrations/YYYY-MM-DD-{slug}.md` (template: the `documentation` skill's `templates/migration-runbook.md`), authored **before** execution and outcome-stamped after. A changelog entry says what changed in the repo; a runbook says what was executed against the world and how to verify or undo it. A repeatable procedure is neither — that is `docs/operations/`. When a runbook defers targets, it records **why** and the **trigger to re-evaluate them**, so the next run re-checks rather than inheriting the skip.
@@ -1,34 +0,0 @@
1
- # Database
2
-
3
- Naming and the fixed column vocabulary for relational schemas in **every** Nurix project — one concept, one name, everywhere. Written for SQLite / Cloudflare D1 + Drizzle; a Postgres project keeps the same column names and semantics with native types (`uuid`, `timestamptz`, `boolean`). **Design doctrine — what earns a table, state vs. logs, junctions, FK policy, uniqueness traps — is the `schema-design` skill; consult it before any schema change.**
4
-
5
- ## Keys & the shared vocabulary
6
-
7
- Three column roles are fixed across every table and every project:
8
-
9
- - **`id`** — the primary key, **always a UUID** (`TEXT PRIMARY KEY` on D1, `uuid` on Postgres), never a synthetic or natural key (❌ `type_page_button`, `human:default`). Runtime rows mint a UUIDv7; seed rows derive a deterministic UUIDv5 of the natural key so re-seeds stay stable. **Never** mix synthetic and UUID ids in one table. The single exemption is a log whose **composite key is its ordering** (`PRIMARY KEY (<entity>_id, sequence)`), declared at the table that uses it — everything else, logs included, takes a UUIDv7.
10
- - **`slug`** — the **stable key rows in another scope reference**: `UNIQUE`, lowercase `snake_case`. A slug means the same thing in every scope, so a slug reference survives a copy with no remapping — copying between scopes is the `schema-design` skill. ❌ Never rename this role `handle`, `key`, `code`, or `name` in another project.
11
- - **`label`** — the **human display name** — ❌ never `name`, `title`, or `display_name`.
12
-
13
- Reference columns name what they point at: `<entity>_id` for a UUID-PK reference (`user_id`, `app_id`), `<entity>_slug` for a slug reference (`source_slug`).
14
-
15
- - **Enforced, same scope** (the referenced row must exist) → a real `FOREIGN KEY (<x>_id) REFERENCES <table>(id)`.
16
- - **Cross-scope** (the reference must survive being copied) → `<x>_slug` as a plain column, **no** FK — an FK would bind the row to one scope's copy of the target, which is the remapping the slug exists to avoid.
17
- - **Drift-tolerant** (a controlled-vocabulary value that may fall off-list) → a plain value column, **no** FK, paired with an `is_*` boolean recording whether the value is in the canonical set.
18
-
19
- **Every inbound FK to a hub table declares a deliberate `ON DELETE`.** A hub (`users`, `devices`, anything several modules point at) collects edges from teams that never meet, and a single `NO ACTION` among them turns deleting the parent into a constraint error naming neither table. Decide per edge: the child dies with the row (`CASCADE`) or survives it (`SET NULL`, which needs a nullable column) — `NO ACTION` on a hub is a deferred outage, not a default.
20
-
21
- ## Naming
22
-
23
- - **Tables** — `snake_case`, plural noun for what they hold (`users`, `apps`, `devices`).
24
- - **Junction (M:N) tables** — `<entityA>_<entityB>_map`, **always**, even when the join carries its own columns; singular stems, owning/anchor entity first (`app_user_map`, `app_device_map`). ❌ Never a "nice" noun (`members`, `installs`, `memberships`, `enrolments`) — the name must read as a join, and a reviewer should know both sides from it. The `_map` suffix is reserved for genuine M:N — the cardinality test that decides junction vs. column is the `schema-design` skill.
25
- - **Columns** — `snake_case`; booleans `is_*` / `has_*`; timestamps `created_at` / `updated_at`; foreign keys `<entity>_id`.
26
- - **Indexes** — `<table>_<col(s)>_idx`; unique `<table>_<col(s)>_unique`; a junction's composite unique reads `<table>_<colA>_<colB>_unique` (`app_user_map_app_user_unique`).
27
- - **Drizzle mirror** — the exported object is the camelCase table name (`app_user_map` → `appUserMap`); column props are the camelCase of the column (`created_at` → `createdAt`). The `sqliteTable("…")` string and every index name must match the DDL (`init.sql`) exactly — the schema-parity boundary.
28
-
29
- ## Column types (SQLite / D1)
30
-
31
- - **Timestamps** — `created_at` + `updated_at`, both `TEXT NOT NULL DEFAULT (datetime('now'))`, on **every** table — except append-only logs, which carry only `created_at`.
32
- - **Booleans** — `INTEGER` (Drizzle `integer(…, { mode: "boolean" })`); the `is_*` / `has_*` names are § Naming.
33
- - **JSON** — `TEXT` (Drizzle `text(…, { mode: "json" })`).
34
-
@@ -1,30 +0,0 @@
1
- # Frontend
2
-
3
- Tailwind v4 + shadcn-based UI conventions for **web** apps — styling, the `@nurix/components` library, the `design.md` token artifact, and design-skill routing — plus the TypeScript conventions every package compiles under.
4
-
5
- - **Build every UI through the `design` skill** — never hand-roll layouts; the page archetypes, the `design.md` identity system with its token artifact (`design.tokens.css`), and chrome wiring live in the skill.
6
- - **The brief's stated theme binds** — dark boots dark when the brief says so (recorded in the project's `design.md`); light is the default only when the brief is silent.
7
- - **`@nurix/components`** is the UI library — import by subpath (`@nurix/components/button`). **Never** `npx shadcn add`, no `components.json` scaffold — consume the prebuilt library (`.tsx`, not `.jsx`).
8
- - Vite: `@tailwindcss/vite` + `@vitejs/plugin-react-swc`. Pin `react` / `react-dom` / `react/jsx-runtime` / `react/jsx-dev-runtime` to the app's single copy via `resolve.alias` — `@nurix/*` ship nested react, and two copies means two hook dispatchers.
9
- - Tailwind v4 is CSS-only — no `tailwind.config.js`, no PostCSS.
10
- - CSS entry `globals.css`, imports in order: `@import "./fonts.css"` (`@fontsource` registrations) → the project's `design.tokens.css` (derived from the repo-base `design.md` by the design skill's validator — **never** hand-edited) → `@import "tailwindcss"` → the library's `styles.css` — then `@theme inline {…}` mapping vars to tokens.
11
- - Tokens: CSS vars in `:root, :root[data-theme="light"]` and `:root[data-theme="dark"]` (both specificity 0,2,0 — armor a later bare `:root` can't defeat), consumed as semantic Tailwind tokens (`bg-card`, `text-foreground`). `globals.css` carries the active `@custom-variant dark` line; the theme attribute is `data-theme`. The library bundles `class-variance-authority`, `clsx`, `tailwind-merge` — don't re-add them.
12
- - Spacing in 2/4/6/8; `object-cover`; `truncate`; color through tokens — **never** hardcoded hex in components.
13
-
14
- ## TypeScript
15
-
16
- Every package compiles under `strict: true` and `noUncheckedIndexedAccess: true`.
17
-
18
- ### Type Safety
19
-
20
- - **Never** `any` to silence the checker. Unknown value → type `unknown`, then narrow (type guard / `zod` parse).
21
- - **Never** weaken strictness: no `// @ts-nocheck`; `// @ts-expect-error` only with a same-line reason.
22
- - Infer locals and return types; annotate at boundaries (exports, props, contracts).
23
-
24
- ### Generics
25
-
26
- Shared libraries (`@nurix/components`, etc.) are generic on purpose — that's what types `cell(row)`, checks `slug` against `keyof TData`, and ties `data`/`columns` together; **never** widen to `any` to dodge it. State the row type **once per file** and let inference carry it — repeating `fn<Type>(…)` / `<Comp<Type>>` per call site is cosmetic (same safety, same emitted JS):
27
-
28
- - **Bind a repeated factory once** (preferred): `const col = defineColumn<BriefRow>;` → `col({...})`. Canonical: `modules/briefs/BriefsListPage.tsx`.
29
- - **Infer JSX type args from props:** `<Table data={rows} columns={columns} />`, not `<Table<BriefRow> …>`.
30
- - **Anchor with a collection annotation** when nothing else can: `const columns: ColumnDefinition<BriefRow>[] = [...]`.
@@ -1,40 +0,0 @@
1
- # General Standards
2
-
3
- Runtime versions, package policy, the API response envelope, and GitHub Actions pins — the workspace baseline.
4
-
5
- ## Runtime Versions
6
-
7
- - **Node.js:** the version pinned in `.nvmrc` / `package.json` `engines` — **never** a hardcoded number, **never** the runner's default. Workflows use `node-version-file: .nvmrc`.
8
- - **Package manager:** detect from the lockfile — **never** hardcode one.
9
- - **PostgreSQL:** 17 · **Redis:** 7.x (latest)
10
-
11
- ## Packages
12
-
13
- **Only packages in the approved table** — `.claude/hooks/approved-packages.md`, enforced by the PreToolUse install gate, which names the file on a block. The table changes only by explicit user decision. Test packages install at `latest`; fake data comes from `@ngneat/falso` — **never** `@faker-js/faker` / `chance`.
14
-
15
- ## API Response Envelope
16
-
17
- Success and error share **one** envelope — a cross-service contract clients branch on, so a per-service "improvement" (`success: true`, a boolean `status`, a bare payload) breaks every consumer:
18
-
19
- ```json
20
- {
21
- "status": 1,
22
- "message": "Message sent successfully",
23
- "data": { "delivered": [{ "user@example.com": 2 }] }
24
- }
25
- ```
26
-
27
- Error: `{ "status": 0, "message": "Error description", "data": null }`
28
-
29
- HTTP codes, default list: `200`, `201` (created), `400`, `401` (not authenticated), `403` (not authorized), `404`, `500` — plus `204`/`304` (null-body responses), `409` (conflicting write), `429` (rate limit), `503` (unhealthy dependency) where the semantics call for them. Middleware- and protocol-emitted codes are exempt from the envelope.
30
-
31
- ## GitHub Actions
32
-
33
- Pin the major, and check it against the action's releases — a major behind still runs, so a stale pin never fails, it only warns.
34
-
35
- | Action | Version | Use |
36
- | --- | --- | --- |
37
- | `actions/checkout` | v7 | Repo checkout |
38
- | `actions/setup-node` | v7 | Node toolchain + npm cache |
39
-
40
- Set `node-version` via `node-version-file: .nvmrc`, **never** a hardcoded number and **never** the runner's default.
@@ -1,30 +0,0 @@
1
- # Nomenclature
2
-
3
- Naming and casing for code — identifiers, files, folders. The rationale throughout is consistency: one concept, one name, one casing shape, in every repo that loads this rule.
4
-
5
- Names state intent in full words — clarity over brevity; no abbreviations unless universally understood (`id`, `url` ✅ · `cfg`, `btn`, `idx` ❌).
6
-
7
- ## Casing
8
-
9
- | Casing | Used for | Example |
10
- | --- | --- | --- |
11
- | `camelCase` | variables, functions, methods, properties; a module file exporting no component | `calculateTotal`, `sessionClient.ts` |
12
- | `PascalCase` | classes, type definitions, components; the file exporting one | `PaymentProcessor`, `AppsListPage.tsx` |
13
- | `UPPER_SNAKE_CASE` | constants, environment variables | `MAX_RETRY_COUNT` |
14
- | `kebab-case` | URLs, CSS class names, HTML IDs, multi-word folders | `user-profile`, `workspace-provisioning/` |
15
- | `snake_case` | database tables, columns, indexes | `created_at` |
16
-
17
- **A file exporting one component carries that component's name exactly**, so an import names the file it came from; a single-word folder is plain lowercase (`lib`, `surfaces`). ❌ Never `snake_case` a file or folder — that casing belongs to the database.
18
-
19
- ## Identifier shape
20
-
21
- | Identifier | Shape | Example |
22
- | --- | --- | --- |
23
- | Variable | noun phrase | `paymentStatus` |
24
- | Function / method | verb phrase naming the action | `validateInput` |
25
- | Boolean | `is` / `has` / `can` / `should` prefix — **never** a bare adjective | `hasPermission` |
26
- | Array / collection | plural noun | `errorMessages` |
27
- | Class / component | singular noun phrase | `RequestHandler` |
28
- | Event handler | `handle` or `on` prefix | `handleSubmit`, `onPageLoad` |
29
- | Interface | `I` prefix acceptable; type aliases stay plain | `IConfigOptions`, `AuthResponse` |
30
- | Async function | verb phrase; `Async` suffix acceptable | `loadConfigAsync` |
@@ -1,57 +0,0 @@
1
- # pnpm — `@nurix/*` Workspaces: naming, boundaries, and consuming
2
-
3
- How a `@nurix/*` repo is **named**, **structured**, and how its packages are **consumed** — every repo takes the workspace shape, single-package frontends included. Publishing and CLI design live in the `nustack` skill, not here.
4
-
5
- ---
6
-
7
- ## 1. Package naming
8
-
9
- Every workspace package is scoped **`@nurix/`**; the dot vs. hyphen after the scope is semantic, not cosmetic:
10
-
11
- | Form | Meaning | Examples |
12
- | --- | --- | --- |
13
- | `@nurix/workflows-sdk` (hyphen) | **Published, externally-destined** contract | `@nurix/workflows-sdk` |
14
- | `@nurix/workflows.<name>` (dot) | **Internal** to the product — `private: true`, refactor freely. The default for a new package | `@nurix/workflows.agent-core`, `.worker`, `.playground` |
15
- | `@nurix/<name>` (plain) | Cross-repo shared lib owned **elsewhere** — never renamed | `@nurix/contracts`, `@nurix/components` |
16
-
17
- - `<name>` is always **kebab-case**.
18
- - The dot is **not** a path separator — subpath exports resolve normally (`@nurix/workflows.agent-core/nodes`).
19
- - The dot tracks "internal product package," not the `private` flag: `@nurix/workflows.secrets` is `private: false` (extraction-bound) yet dotted. `-sdk` is the only clean published form.
20
-
21
- ---
22
-
23
- ## 2. Monorepo & package boundaries
24
-
25
- - **One `tsconfig.base.json` at the repo root owns TS policy** — the strict set, `target`, and everything uniform across packages (here, `module`/`moduleResolution`). Each package's `tsconfig.json` `extends` the base and adds only its coordinates (`rootDir`/`outDir`/`include`) and genuine environment overrides (`lib`/`types`/`jsx`; `module`/`moduleResolution` only when the package's module system truly differs). **Never** re-declare or weaken a policy option locally. One exception: a framework that owns its own base (an Astro app on `astro/tsconfigs/strict`).
26
- - **Internal packages are source-first.** A `private: true` package exposes its API via `exports` pointing at source (`"types"`/`"default"` → `./src/index.ts`) — no build step, and no bare `main`/`module` on new packages. One typed subpath per public surface; consumers import the subpath, **never** a deep relative path into another package's `src/`. `types` comes **first** in every subpath block — resolvers take the first matching condition, so a `types` listed after `default` is never reached.
27
- - **Crossing the publish boundary is the `nustack` skill** — `private: false`, the published manifest, the pipeline.
28
- - **Single-source shared dep versions with pnpm `catalog:`** — `typescript`, `zod`, `vitest`, `react`, type packages — so every package resolves the same copy and its types stay mutually assignable.
29
- - **TS project references only when it hurts** — adopt `references` once the typecheck/build graph is slow enough to feel; until then, `pnpm -r typecheck`.
30
-
31
- ### The catalog has no garbage collector
32
-
33
- - **Removing the last consumer of a cataloged package removes its catalog entry in the same change** — `pnpm remove` rewrites the manifest and lockfile but **never** `pnpm-workspace.yaml`, and nothing downstream flags the orphan: no install fails, no lint fires.
34
- - The check is mechanical: an entry no workspace manifest names under any dependency field is orphaned; one absent from `pnpm-lock.yaml` is an orphan with certainty.
35
- - **A cataloged package is `catalog:` in every manifest** — a literal range (`"zod": "^4.2.1"`) resolves identically today and silently stops tracking the catalog the moment it moves.
36
-
37
- ### The workspace root is a hard boundary
38
-
39
- - **A dependency spec never points outside the folder holding `pnpm-workspace.yaml`** — `devDependencies` included. Inside the root, `workspace:*` between `packages/*` is the normal, correct thing; outside it, nothing — no `file:../…`, no `link:`, no absolute `file:`. A sibling checkout is another repo, and the registry is the only supported way to consume one — a cross-root spec bakes machine-local paths into the lockfile and the published tarball, so `install --frozen-lockfile` fails on any other machine.
40
- - **A package unreachable from the registry is an auth or publishing bug — fix it there, never with a local path.** Diagnosis order: the scope's registry (§3) → the token → whether it was actually published.
41
-
42
- ---
43
-
44
- ## 3. Consuming `@nurix/*` packages
45
-
46
- `@nurix/*` packages publish to npm with **`restricted`** access — installing needs read rights to the `nurix` org.
47
-
48
- - **Registry** — point the scope at npmjs, where the packages are published. A restricted read answers `404`, not `401`, so a mispaired registry is indistinguishable from "never published" — on an `@nurix/*` 404, check the registry pairing **first**, before auth and before assuming the package is missing.
49
- - **Auth** — `npm login` once (the npm user must be a `nurix` member), or a read token in `.npmrc` (the only CI option):
50
- ```ini
51
- @nurix:registry=https://registry.npmjs.org/
52
- //registry.npmjs.org/:_authToken=${NPM_READ_TOKEN}
53
- ```
54
- The token needs only `Read` on the `nurix` scope. A `403` on install means auth failed — check `npm whoami` (local) or the `NPM_READ_TOKEN` secret (CI). Attempt the install directly — the token authenticates ambiently; don't pre-empt or second-guess it. If it fails, flag it to the user — **never** reach for alternatives, a substitute package, or `npm login` swaps.
55
- - **Import by subpath, never the barrel** — subpaths tree-shake reliably; the available subpaths are the package's `exports` keys. `import { Button } from "@nurix/components/button";`
56
- - **Styles** — import a CSS-shipping package's stylesheet **once** at the app entry: `import "@nurix/components/styles.css";`. Unstyled components are almost always a missing styles import. The shipped stylesheet is precompiled — no Tailwind `@source` directive unless consuming source via a workspace link.
57
- - **CLIs** ship a `bin` — run directly: `npx @nurix/etna@latest` (or `npm install -g`).