@nurix/etna 0.15.7 → 0.16.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 (86) hide show
  1. package/bin/etna.js +64 -13
  2. package/data/harness-index.json +7 -5
  3. package/data/harnesses/code/CLAUDE.md +3 -7
  4. package/data/harnesses/code/agents/harness-manager.md +4 -4
  5. package/data/harnesses/code/agents/maintenance.md +1 -1
  6. package/data/harnesses/code/harness.json +4 -3
  7. package/data/harnesses/code/hooks/hooks.json +3 -17
  8. package/data/harnesses/code/hooks/prompt-recall.js +157 -0
  9. package/data/harnesses/code/hooks/session-reflect.js +283 -0
  10. package/data/harnesses/code/skills/distiller/SKILL.md +62 -60
  11. package/data/harnesses/code/skills/distiller/references/docs-mode.md +21 -21
  12. package/data/harnesses/code/skills/distiller/references/method.md +41 -41
  13. package/data/harnesses/code/skills/documentation/references/templates.md +20 -27
  14. package/data/harnesses/code-fable/CLAUDE.md +138 -0
  15. package/data/harnesses/code-fable/agents/brainstorm.md +35 -0
  16. package/data/harnesses/code-fable/agents/change-review.md +249 -0
  17. package/data/harnesses/code-fable/agents/harness-manager.md +193 -0
  18. package/data/harnesses/code-fable/agents/maintenance.md +123 -0
  19. package/data/harnesses/code-fable/agents/performance-review.md +218 -0
  20. package/data/harnesses/code-fable/agents/spec-mode.md +56 -0
  21. package/data/harnesses/code-fable/harness.json +7 -0
  22. package/data/harnesses/code-fable/hooks/hooks.json +27 -0
  23. package/data/harnesses/code-fable/hooks/lib/redact.js +37 -0
  24. package/data/harnesses/code-fable/hooks/prompt-recall.js +157 -0
  25. package/data/harnesses/code-fable/hooks/session-reflect.js +283 -0
  26. package/data/harnesses/code-fable/rules/general.md +96 -0
  27. package/data/harnesses/code-fable/rules/nomenclature.md +34 -0
  28. package/data/harnesses/code-fable/skills/boundary/SKILL.md +262 -0
  29. package/data/harnesses/code-fable/skills/boundary/references/boundary-taxonomy.md +734 -0
  30. package/data/harnesses/code-fable/skills/brainstorm/README.md +136 -0
  31. package/data/harnesses/code-fable/skills/brainstorm/SKILL.md +135 -0
  32. package/data/harnesses/code-fable/skills/brainstorm/harvest.md +26 -0
  33. package/data/harnesses/code-fable/skills/brainstorm/templates/backlog.md +39 -0
  34. package/data/harnesses/code-fable/skills/brainstorm/templates/build.md +37 -0
  35. package/data/harnesses/code-fable/skills/brainstorm/templates/decisions.md +29 -0
  36. package/data/harnesses/code-fable/skills/brainstorm/templates/doctrine.md +31 -0
  37. package/data/harnesses/code-fable/skills/brainstorm/templates/domain-model.md +25 -0
  38. package/data/harnesses/code-fable/skills/brainstorm/templates/glossary.md +19 -0
  39. package/data/harnesses/code-fable/skills/brainstorm/templates/milestones.md +43 -0
  40. package/data/harnesses/code-fable/skills/brainstorm/templates/non-goals.md +21 -0
  41. package/data/harnesses/code-fable/skills/brainstorm/templates/plan-doc.md +61 -0
  42. package/data/harnesses/code-fable/skills/brainstorm/templates/questions.md +35 -0
  43. package/data/harnesses/code-fable/skills/brainstorm/templates/references.md +28 -0
  44. package/data/harnesses/code-fable/skills/brainstorm/templates/rejected.md +29 -0
  45. package/data/harnesses/code-fable/skills/brainstorm/templates/state.md +8 -0
  46. package/data/harnesses/code-fable/skills/brainstorm/templates/tech.md +23 -0
  47. package/data/harnesses/code-fable/skills/brainstorm/templates/thesis.md +21 -0
  48. package/data/harnesses/code-fable/skills/distiller/SKILL.md +219 -0
  49. package/data/harnesses/code-fable/skills/distiller/references/docs-mode.md +526 -0
  50. package/data/harnesses/code-fable/skills/distiller/references/method.md +166 -0
  51. package/data/harnesses/code-fable/skills/documentation/SKILL.md +127 -0
  52. package/data/harnesses/code-fable/skills/documentation/references/examples.md +193 -0
  53. package/data/harnesses/code-fable/skills/documentation/references/style-guide.md +288 -0
  54. package/data/harnesses/code-fable/skills/documentation/references/templates.md +942 -0
  55. package/data/harnesses/code-fable/skills/workflow-mapper/SKILL.md +430 -0
  56. package/data/harnesses/code-fable/skills/workflow-mapper/references/patterns.md +214 -0
  57. package/data/harnesses/{re → discover}/CLAUDE.md +48 -33
  58. package/data/harnesses/{re → discover}/agents/api.md +3 -3
  59. package/data/harnesses/{re → discover}/agents/codebase.md +3 -3
  60. package/data/harnesses/{re → discover}/agents/community.md +3 -3
  61. package/data/harnesses/{re → discover}/agents/deployed-client-bundle.md +5 -5
  62. package/data/harnesses/{re → discover}/agents/distribution-artifacts.md +4 -4
  63. package/data/harnesses/{re → discover}/agents/docs.md +3 -3
  64. package/data/harnesses/{re → discover}/agents/infra-backend-fingerprint.md +2 -2
  65. package/data/harnesses/{re → discover}/agents/packages.md +3 -3
  66. package/data/harnesses/{re → discover}/agents/session.md +19 -11
  67. package/data/harnesses/{re → discover}/agents/website.md +3 -3
  68. package/data/harnesses/{re → discover}/agents/wire-capture.md +9 -7
  69. package/data/harnesses/discover/harness.json +7 -0
  70. package/data/harnesses/{re/rules/re-discovery.md → discover/rules/discovery.md} +54 -17
  71. package/data/harnesses/{re/rules/re-evaluation.md → discover/rules/evaluation.md} +12 -1
  72. package/data/harnesses/{re/rules/re-ingestion.md → discover/rules/ingestion.md} +192 -23
  73. package/data/harnesses/discover/rules/self-correction.md +177 -0
  74. package/data/harnesses/discover/rules/tradecraft.md +532 -0
  75. package/data/harnesses/hr/CLAUDE.md +91 -0
  76. package/data/harnesses/hr/harness.json +7 -0
  77. package/data/harnesses/hr/skills/interview-eval/SKILL.md +118 -0
  78. package/data/harnesses/hr/skills/interview-eval/TEMPLATE.md +124 -0
  79. package/data/harnesses/hr/skills/interview-eval/extract_session.py +418 -0
  80. package/data/harnesses/hr/skills/interview-eval/rubric.md +219 -0
  81. package/data/harnesses/kb-agi/harness.json +1 -0
  82. package/package.json +1 -1
  83. package/data/harnesses/code/hooks/post-commit.js +0 -136
  84. package/data/harnesses/code/hooks/post-tool-failure.js +0 -136
  85. package/data/harnesses/code/hooks/session-stop.js +0 -207
  86. package/data/harnesses/re/harness.json +0 -6
@@ -0,0 +1,283 @@
1
+ // plugins/code/hooks/session-reflect.js — SessionEnd hook: reflect the session into durable lessons (headless haiku pass) appended to the actor's memory inbox.
2
+
3
+ "use strict";
4
+
5
+ const fs = require("fs");
6
+ const os = require("os");
7
+ const path = require("path");
8
+ const { execFileSync, spawn } = require("child_process");
9
+ const { redact } = require("./lib/redact.js");
10
+
11
+ // A session below all three thresholds carries no lesson worth a model call.
12
+ const MIN_TURNS = 8;
13
+ const MAX_MESSAGE_CHARS = 1500;
14
+ const MAX_DIGEST_CHARS = 80000;
15
+ const MAX_LESSONS = 6;
16
+
17
+ // Read all of stdin synchronously; returns "" if nothing is piped.
18
+ function readStdin() {
19
+ try {
20
+ return fs.readFileSync(0, "utf8");
21
+ } catch (e) {
22
+ return "";
23
+ }
24
+ }
25
+
26
+ // Resolve the project root: CLAUDE_PROJECT_DIR env, else payload cwd, else process cwd.
27
+ function resolveProjectDir(payload) {
28
+ return process.env.CLAUDE_PROJECT_DIR || (payload && payload.cwd) || process.cwd();
29
+ }
30
+
31
+ // Run a git command in projectDir; returns trimmed stdout or fallback on any error.
32
+ function git(projectDir, args, fallback) {
33
+ try {
34
+ return execFileSync("git", ["-C", projectDir, ...args], {
35
+ encoding: "utf8",
36
+ stdio: ["ignore", "pipe", "ignore"],
37
+ }).trim() || fallback;
38
+ } catch (e) {
39
+ return fallback;
40
+ }
41
+ }
42
+
43
+ // Slug a raw actor name into kebab-case ASCII.
44
+ function slugActor(name) {
45
+ return String(name || "")
46
+ .toLowerCase()
47
+ .replace(/[^a-z0-9]+/g, "-")
48
+ .replace(/^-+|-+$/g, "");
49
+ }
50
+
51
+ // Determine the actor: git user.name (slugged) → $USER → "agent".
52
+ function resolveActor(projectDir) {
53
+ const fromGit = slugActor(git(projectDir, ["config", "user.name"], ""));
54
+ if (fromGit) return fromGit;
55
+ const fromEnv = slugActor(process.env.USER || "");
56
+ if (fromEnv) return fromEnv;
57
+ return "agent";
58
+ }
59
+
60
+ // Truncate a single line to n chars with an ellipsis marker.
61
+ function clip(text, n) {
62
+ const clean = String(text || "").replace(/\s+/g, " ").trim();
63
+ return clean.length > n ? clean.slice(0, n) + "…" : clean;
64
+ }
65
+
66
+ // Extract plain text from a message whose content is a string or content-block array.
67
+ function extractText(msg) {
68
+ if (!msg || !msg.content) return "";
69
+ if (typeof msg.content === "string") return msg.content;
70
+ if (Array.isArray(msg.content)) {
71
+ return msg.content
72
+ .filter((b) => b && b.type === "text" && typeof b.text === "string")
73
+ .map((b) => b.text)
74
+ .join(" ");
75
+ }
76
+ return "";
77
+ }
78
+
79
+ // Strip harness-injected wrappers so only what the user actually typed survives.
80
+ function stripInjected(text) {
81
+ return String(text || "")
82
+ .replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "")
83
+ .replace(/<(local-command-caveat|local-command-stdout|local-command-stderr|command-name|command-message|command-args|command-contents)>[\s\S]*?<\/\1>/g, "")
84
+ .trim();
85
+ }
86
+
87
+ // Scan an assistant message's tool_use blocks for edited files and git commits.
88
+ function collectToolUse(msg, summary) {
89
+ if (!msg || !Array.isArray(msg.content)) return;
90
+ for (const block of msg.content) {
91
+ if (!block || block.type !== "tool_use") continue;
92
+ const name = block.name || "";
93
+ const input = block.input || {};
94
+ if (/^(Edit|Write|MultiEdit|NotebookEdit)$/.test(name) && input.file_path) {
95
+ summary.files.add(input.file_path);
96
+ }
97
+ if (name === "Bash" && typeof input.command === "string" && /\bgit\s+commit\b/.test(input.command)) {
98
+ summary.sawCommit = true;
99
+ }
100
+ }
101
+ }
102
+
103
+ // Walk the JSONL transcript once: turn/edit/commit counts plus a USER/ASSISTANT text digest.
104
+ function digestTranscript(transcriptPath) {
105
+ const out = { turns: 0, files: new Set(), sawCommit: false, lines: [] };
106
+ if (!transcriptPath || !fs.existsSync(transcriptPath)) return out;
107
+ for (const line of fs.readFileSync(transcriptPath, "utf8").split("\n")) {
108
+ if (!line.trim()) continue;
109
+ let row;
110
+ try {
111
+ row = JSON.parse(line);
112
+ } catch (e) {
113
+ continue;
114
+ }
115
+ if (row.isSidechain) continue;
116
+ const msg = row && row.message ? row.message : row;
117
+ const role = (msg && msg.role) || row.type;
118
+ if (role === "user") {
119
+ const text = stripInjected(extractText(msg));
120
+ if (!text || text.startsWith("Caveat: The messages below")) continue;
121
+ out.lines.push("USER: " + clip(text, MAX_MESSAGE_CHARS));
122
+ } else if (role === "assistant") {
123
+ out.turns += 1;
124
+ collectToolUse(msg, out);
125
+ const text = extractText(msg);
126
+ if (text) out.lines.push("ASSISTANT: " + clip(text, MAX_MESSAGE_CHARS));
127
+ }
128
+ }
129
+ return out;
130
+ }
131
+
132
+ // Compute the per-actor inbox file path.
133
+ function inboxPath(projectDir, actor) {
134
+ const dir = path.join(projectDir, "docs", "memory", "inbox");
135
+ return { dir, file: path.join(dir, `${actor}.md`) };
136
+ }
137
+
138
+ // True when this session already wrote an inbox entry (SessionEnd re-fires on resume).
139
+ function alreadyReflected(file, sessionId) {
140
+ try {
141
+ return Boolean(sessionId) && fs.existsSync(file) && fs.readFileSync(file, "utf8").includes(`session ${sessionId}`);
142
+ } catch (e) {
143
+ return false;
144
+ }
145
+ }
146
+
147
+ // Build the inbox file header block.
148
+ function inboxHeader(actor) {
149
+ return `# Memory inbox — ${actor}\nSession lessons awaiting distillation — appended by the session-reflect hook, consumed by \`distiller facts\`.\n`;
150
+ }
151
+
152
+ // The full output contract sits AFTER the transcript — with it only up front, the model
153
+ // continues the conversation in the transcript instead of reflecting on it.
154
+ const PROMPT_HEADER = [
155
+ "You are a memory reflector. Below is the transcript of a FINISHED coding session,",
156
+ "delimited by <transcript> tags. Do not reply to it or continue it — you will be asked",
157
+ "to extract durable lessons from it after the closing tag.",
158
+ "",
159
+ "<transcript>",
160
+ "",
161
+ ].join("\n");
162
+
163
+ const PROMPT_FOOTER = [
164
+ "",
165
+ "</transcript>",
166
+ "",
167
+ "The session above is over. Extract ONLY durable lessons a future session in this",
168
+ "repository would benefit from knowing:",
169
+ "- corrections or preferences the user stated",
170
+ "- failures whose root cause was found, with the fix",
171
+ "- decisions made, with their rationale",
172
+ "- discovered constraints or non-obvious facts about the project or its tooling",
173
+ "Rules:",
174
+ "- Most sessions contain none. If nothing qualifies, output exactly: NONE",
175
+ `- Otherwise output at most ${MAX_LESSONS} bullets and nothing else — no preamble, no headers.`,
176
+ '- Each bullet starts with "- [project]" (true of this repo or its tooling) or "- [user]" (true of this user).',
177
+ "- Each bullet is one complete, self-contained sentence with names and identifiers spelled out.",
178
+ "- Routine work (commits made, files edited, tasks completed) is NOT a lesson. Never include secrets.",
179
+ ].join("\n");
180
+
181
+ // Hook mode: stash the payload and hand off to a detached worker so session exit is never delayed.
182
+ function hook() {
183
+ const raw = readStdin();
184
+ let payload = {};
185
+ try {
186
+ payload = raw ? JSON.parse(raw) : {};
187
+ } catch (e) {
188
+ payload = {};
189
+ }
190
+ if (!payload.transcript_path) return;
191
+
192
+ const stash = {
193
+ projectDir: resolveProjectDir(payload),
194
+ transcriptPath: payload.transcript_path,
195
+ sessionId: payload.session_id || "",
196
+ };
197
+ const stashFile = path.join(os.tmpdir(), `etna-reflect-${stash.sessionId || process.pid}.json`);
198
+ fs.writeFileSync(stashFile, JSON.stringify(stash), "utf8");
199
+ spawn(process.execPath, [__filename, "--worker", stashFile], { detached: true, stdio: "ignore" }).unref();
200
+ }
201
+
202
+ // Worker mode: filter, digest, run the reflection model, append surviving lessons to the inbox.
203
+ function worker(stashFile) {
204
+ let stash;
205
+ try {
206
+ stash = JSON.parse(fs.readFileSync(stashFile, "utf8"));
207
+ } finally {
208
+ try {
209
+ fs.rmSync(stashFile, { force: true });
210
+ } catch (e) {
211
+ // Stale stash files are harmless.
212
+ }
213
+ }
214
+
215
+ const { projectDir, transcriptPath, sessionId } = stash;
216
+ const digest = digestTranscript(transcriptPath);
217
+ if (digest.turns < MIN_TURNS && digest.files.size === 0 && !digest.sawCommit) return;
218
+
219
+ const actor = resolveActor(projectDir);
220
+ const { dir, file } = inboxPath(projectDir, actor);
221
+ if (alreadyReflected(file, sessionId)) return;
222
+
223
+ let text = redact(digest.lines.join("\n"));
224
+ if (text.length > MAX_DIGEST_CHARS) {
225
+ text = text.slice(0, 8000) + "\n[... middle of session elided ...]\n" + text.slice(-(MAX_DIGEST_CHARS - 8000));
226
+ }
227
+
228
+ const output = execFileSync("claude", ["-p", "--model", "haiku"], {
229
+ input: PROMPT_HEADER + text + PROMPT_FOOTER,
230
+ encoding: "utf8",
231
+ cwd: os.tmpdir(),
232
+ env: Object.assign({}, process.env, { ETNA_REFLECT: "1" }),
233
+ timeout: 240000,
234
+ maxBuffer: 4 * 1024 * 1024,
235
+ stdio: ["pipe", "pipe", "ignore"],
236
+ });
237
+
238
+ const lessons = output
239
+ .split("\n")
240
+ .map((l) => l.trim())
241
+ .filter((l) => /^- \[(project|user)\]\s+\S/.test(l))
242
+ .slice(0, MAX_LESSONS)
243
+ .map((l) => redact(l));
244
+ if (!lessons.length) return;
245
+
246
+ const repo = path.basename(projectDir);
247
+ const branch = git(projectDir, ["rev-parse", "--abbrev-ref", "HEAD"], "-");
248
+ const now = new Date();
249
+ const pad = (n) => String(n).padStart(2, "0");
250
+ const stamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}`;
251
+
252
+ fs.mkdirSync(dir, { recursive: true });
253
+ if (alreadyReflected(file, sessionId)) return;
254
+ const exists = fs.existsSync(file);
255
+ const block =
256
+ (exists ? "\n" : inboxHeader(actor) + "\n") +
257
+ `## ${stamp} · session ${sessionId || "unknown"} · ${repo}@${branch}\n` +
258
+ lessons.join("\n") +
259
+ "\n";
260
+ fs.appendFileSync(file, block, "utf8");
261
+ }
262
+
263
+ function main() {
264
+ if (process.env.ETNA_REFLECT) return;
265
+ const workerIdx = process.argv.indexOf("--worker");
266
+ if (workerIdx !== -1) {
267
+ worker(process.argv[workerIdx + 1]);
268
+ } else {
269
+ hook();
270
+ }
271
+ }
272
+
273
+ try {
274
+ main();
275
+ } catch (e) {
276
+ try {
277
+ process.stderr.write(`session-reflect hook: ${e && e.message ? e.message : e}\n`);
278
+ } catch (e2) {
279
+ // Nothing more we can do.
280
+ }
281
+ } finally {
282
+ process.exit(0);
283
+ }
@@ -0,0 +1,96 @@
1
+ ---
2
+ applyTo: "**"
3
+ ---
4
+
5
+ # Technology Choices & Standards
6
+
7
+ Opinionated technology stack and implementation standards.
8
+
9
+ ## Environment & Versions
10
+
11
+ ### Runtime Versions
12
+
13
+ - **Node.js:** 22 LTS (latest)
14
+ - **Package manager:** project-determined — detect from lockfile (`pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, `package-lock.json` → npm). Use the matching command in scripts; don't hardcode a specific manager.
15
+ - **PostgreSQL:** 17
16
+ - **Redis:** 7.x (latest)
17
+
18
+ ### Recommended Packages
19
+
20
+ No alternatives. No explanations for obvious choices. Just use these.
21
+
22
+ | Package | Version | Use | Don't use |
23
+ | ------------------------ | --------- | ---------------------- | ----------------------- |
24
+ | react | ^19.2.3 | UI components | - |
25
+ | vite | ^8.0.0 | Build tool, dev server | webpack, CRA |
26
+ | tailwindcss | ^4.1.18 | Styling | styled-components |
27
+ | @vitejs/plugin-react-swc | ^4.2.2 | JSX compilation | @vitejs/plugin-react |
28
+ | react-router-dom | ^7.11.0 | Routing | - |
29
+ | @reduxjs/toolkit | ^2.11.2 | State management | zustand, jotai |
30
+ | react-redux | ^9.2.0 | Redux React bindings | - |
31
+ | axios | ^1.13.2 | HTTP requests | fetch |
32
+ | date-fns | ^4.1.0 | Date formatting | moment, dayjs |
33
+ | @dnd-kit/core | ^6.3.1 | Drag and drop | react-beautiful-dnd |
34
+ | motion | ^12.23.26 | Animations | react-spring |
35
+ | sonner | ^2.0.7 | Toast notifications | react-toastify |
36
+ | react-window | ^2.2.3 | List virtualization | react-virtualized |
37
+ | nanoid | ^5.1.6 | ID generation | uuid |
38
+ | express | ^5.2.1 | HTTP server | fastify, koa |
39
+ | prisma | ^7.2.0 | Database ORM | sequelize, typeorm |
40
+ | @prisma/client | ^7.2.0 | Database queries | - |
41
+ | zod | ^4.2.1 | Schema validation | yup, joi |
42
+ | jose | ^6.1.3 | JWT handling | jsonwebtoken |
43
+ | microdiff | ^1.5.0 | JSON object diffing | deep-diff, just-diff |
44
+ | react18-json-view | ^0.2.9 | JSON tree view | react-json-view |
45
+ | @tiptap/react | ^3.11.1 | Rich text editor | slate, quill, draft-js |
46
+ | shiki | ^3.22.0 | Syntax highlighting | prism, highlight.js |
47
+ | lucide-react | ^0.562.0 | Icons | react-icons, heroicons |
48
+ | @pdfme/generator | ^5.5.8 | PDF generation | jspdf, pdfkit |
49
+ | cmdk | ^1.1.1 | Command palette | kbar, ninja-keys |
50
+ | @monaco-editor/react | ^4.7.0 | Code editor | codemirror, ace |
51
+ | localforage | ^1.10.0 | Client-side storage | idb, dexie |
52
+ | socket.io-client | ^4.7.0 | WebSocket client | native WebSocket, ws |
53
+ | react-markdown | ^10.1.0 | Markdown rendering | marked, markdown-it |
54
+ | fast-deep-equal | ^3.1.3 | Deep equality checks | lodash.isEqual |
55
+ | @ngneat/falso | latest | Fake data generation | @faker-js/faker, chance |
56
+ | pino | ^10.1.0 | Logging | winston |
57
+
58
+ ---
59
+
60
+ ## Express 5.x Setup
61
+
62
+ ```javascript
63
+ app.use(cors({ origin: process.env.FRONTEND_URL }));
64
+ app.use(compression());
65
+ app.use(express.json({ limit: "10mb" }));
66
+ app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
67
+ ```
68
+
69
+ - Middleware order: body parsers, custom middleware, routes, error handlers
70
+ - Use Express Router for modular routes
71
+ - Centralized error handler as last middleware
72
+ - Use middleware for auth
73
+
74
+ ---
75
+
76
+ ## API Response Format
77
+
78
+ ```json
79
+ {
80
+ "status": 1,
81
+ "message": "Message sent successfully",
82
+ "data": { "delivered": [{ "user@example.com": 2 }] }
83
+ }
84
+ ```
85
+
86
+ **Error:**
87
+
88
+ ```json
89
+ {
90
+ "status": 0,
91
+ "message": "Error description",
92
+ "data": null
93
+ }
94
+ ```
95
+
96
+ HTTP codes: `200`, `201` (created), `400`, `401` (not authenticated), `403` (not authorized), `404`, `500`
@@ -0,0 +1,34 @@
1
+ ---
2
+ applyTo: "**"
3
+ ---
4
+
5
+ # Nomenclature Guidelines
6
+
7
+ ## Naming Guidelines
8
+
9
+ - Use descriptive names that reveal intent, purpose, and usage
10
+ - Names should explain why something exists and how it's used
11
+ - Favor clarity over brevity
12
+ - Maintain consistency within the codebase
13
+ - Avoid abbreviations unless they're universally understood
14
+
15
+ ## Naming Styles
16
+
17
+ - Variables : Use nouns or noun phrases. Examples: `userData`, `firstName`, `paymentStatus`, `configOptions`
18
+ - Functions & Methods : Use verbs or verb phrases that describe the action. Examples: `calculateTotal`, `getUserInfo`, `validateInput`, `sendNotification`
19
+ - Boolean Variables : Prefix with is**, has**, can**, or should**. Examples: `isValid`, `hasPermission`, `canEdit`, `shouldRefresh`
20
+ - Arrays : Use plural nouns. Examples: `users`, `productList`, `errorMessages`, `activeConnections`
21
+ - Classes & Components : Use singular nouns or noun phrases. Examples: `RequestHandler`, `UserProfile`, `PaymentProcessor`, `DataValidator`
22
+ - Constants : Use uppercase with meaningful names. Examples: `DEFAULT_TIMEOUT`, `API_BASE_URL`, `MAX_RETRY_COUNT`, `COLOR_PRIMARY`
23
+ - Event Handlers : Use Prefix with handle or on. Examples: `handleSubmit`, `onUserLogin`, `handleInputChange`, `onPageLoad`
24
+ - Interfaces & Types : Use Consider prefixing with I for interfaces (if preferred). Examples: `IUserData`, `IConfigOptions`, `AuthResponse`, `RequestParams`
25
+ - Promises & Async Functions : Use Consider using future tense or suffixes like Async. Examples: `fetchUserData`, `getUserProfileAsync`, `loadConfigAsync`, `processPayment`
26
+ - File & Folder Structure : Use lowercase with descriptive names. Group by feature or function type. Example: `auth/login_service.js`
27
+
28
+ ## Casing Styles
29
+
30
+ - camelCase : Used in Variables, function names, method names, property names. Examples: `userData`, `calculateTotal`, `firstName`, `isValidEmail`
31
+ - PascalCase : Used in Class names, component names, type definitions. Examples: `UserProfile`, `DataService`, `AuthProvider`, `RequestHandler`
32
+ - snake_case : Used in File names, folder names. Examples: `user_service`, `api_utils`, `auth_middleware`, `data_models`
33
+ - UPPER_SNAKE_CASE : Used in Constants, environment variables. Examples: `MAX_RETRY_COUNT`, `API_KEY`, `DEFAULT_TIMEOUT`, `COLOR_PRIMARY`
34
+ - kebab-case : Used in URLs, CSS class names, HTML IDs. Examples: `nav-container`, `user-profile`, `button-primary`, `form-wrapper`
@@ -0,0 +1,262 @@
1
+ ---
2
+ name: boundary
3
+ description: The single home for a repo's typed application boundaries — the cross-cutting contracts (db-schema, wire envelope, store shape, auth, tenancy, event schemas, edge bindings, …) where editing one module silently breaks another. One skill, four modes — `generate` (baseline all boundaries from the detected stack + IA), `create` (add/update one boundary), `check` (pre-implementation: does this plan touch a boundary? severity-ranked impact), `init` (reconcile: ensure files exist, scan live code for violations + drift, backfill new catalog types). All four read one shared knowledge root, `references/boundary-taxonomy.md`, so they can never drift apart. Triggers on "application boundary", "baseline boundaries", "map boundaries", "generate boundaries", "create a boundary", "introspect", "check this plan", "impact analysis", "what could break", "scan for boundary violations", "boundary drift", "/boundary".
4
+ ---
5
+
6
+ # Application Boundary Skill
7
+
8
+ The one place a repo's **typed application boundaries** are produced, checked, and
9
+ maintained. A boundary is a horizontal cross-cutting contract — an *edge* between
10
+ modules, layers, services, or deploy units — where editing one side silently
11
+ breaks another. They live as one file per boundary in `docs/boundaries/`.
12
+
13
+ This skill merges what used to be three components — `boundary-mapper` (produce),
14
+ `introspect` (pre-impl check), `contract-monitor` (post-impl scan) — into one
15
+ skill with four modes. The reason they're one skill: **all four share a single
16
+ knowledge root** — [`references/boundary-taxonomy.md`](references/boundary-taxonomy.md),
17
+ the exhaustive catalog of *what an application boundary is* and *which kinds
18
+ exist* (≈170 types across persistence, wire/API, frontend, auth/tenancy,
19
+ async/messaging, config/build, runtime/infra). Three separate components meant
20
+ three copies of that mental model that drifted apart. One skill, one catalog,
21
+ zero drift.
22
+
23
+ > **Read [`references/boundary-taxonomy.md`](references/boundary-taxonomy.md)
24
+ > first, in every mode.** It carries the boundary-file anatomy, the
25
+ > `point`-vs-`transcribe` doc-mode, the technology/domain split, the per-type
26
+ > severities, and the safe-change checklists. This SKILL.md only orchestrates the
27
+ > four verbs over that knowledge.
28
+
29
+ ---
30
+
31
+ ## Picking a mode
32
+
33
+ | You want to… | Mode | Old component |
34
+ |---|---|---|
35
+ | Baseline a repo — generate every boundary that exists from the stack + IA | **`generate`** | boundary-mapper (baseline) |
36
+ | Add or update **one** boundary file | **`create`** | boundary-mapper (create/update) |
37
+ | Before coding: does this **plan** touch a boundary? Severity-ranked impact | **`check`** | introspect |
38
+ | Reconcile: do files exist? scan **live code** for violations + drift; backfill new catalog types | **`init`** | contract-monitor + drift + catalog-sync |
39
+
40
+ If the user's intent is ambiguous, ask which mode. `generate` and `create` are the
41
+ same writer at different scope (`generate` = `create` over every detected
42
+ boundary).
43
+
44
+ ## Execution model — pure skill
45
+
46
+ This skill runs **in the main session** — no standing sub-agents. `check` and
47
+ `create` are light and always run inline. `generate` and `init` sweep the
48
+ codebase; run that sweep inline too. **Only** for an unusually large repo (where
49
+ an inline full-codebase sweep would blow the context budget) may you dispatch a
50
+ **one-shot scan worker** sub-agent that reads this same taxonomy reference and
51
+ returns structured findings — the knowledge still lives in exactly one place.
52
+ Default to inline; reach for a worker only when the repo's size forces it.
53
+
54
+ ## Before any mode
55
+
56
+ 1. Read [`references/boundary-taxonomy.md`](references/boundary-taxonomy.md) — the
57
+ boundary types, doc-modes, default severities, and safe-change checklists for
58
+ the stack you're about to touch.
59
+ 2. If `docs/memory/boundary.md` exists, read it for project-specific
60
+ boundary exceptions, known violations the user chose not to fix, where the IA
61
+ model actually lives, and stack quirks that change a doc-mode.
62
+ 3. If `docs/routing.md` exists, read it for module-ownership context.
63
+
64
+ ---
65
+
66
+ ## Mode: `generate` (baseline)
67
+
68
+ No `docs/boundaries/` yet (or a from-scratch re-baseline). Detect the stack + IA,
69
+ instantiate one file per boundary that actually exists.
70
+
71
+ 1. **Detect the stack** — read `package.json` (deps), the lockfile, and skim the
72
+ source tree. Map detected technologies to their boundary types via the
73
+ taxonomy. (Detect the package manager from the lockfile; don't assume.)
74
+ 2. **Detect the information architecture** — read `docs/architecture/`
75
+ (domain-model, ADRs), doctrine in any `docs/modules/*/pillars/`, and the
76
+ entity/contract definitions (a `@org/contracts` package, `schema.ts`,
77
+ `models.py`). The IA entity model is the flagship `domain` boundary
78
+ (`information-architecture`).
79
+ 3. **For each boundary the repo exhibits**, write one file to `docs/boundaries/`
80
+ using the boundary-file anatomy. Decide `point` vs `transcribe` per the
81
+ taxonomy: `point` boundaries get a `file:line` pointer + invariants +
82
+ checklist (never a transcribed copy — it rots); `transcribe` boundaries (the
83
+ composed store tree, the response envelope, the IA topology, tenancy) get the
84
+ contract canonically in the file.
85
+ - **Greenfield branch — contract decided, no code yet.** If the repo is
86
+ pre-code (ADRs/domain-model decide the contract but the source module isn't
87
+ scaffolded), a `point` boundary has no `file:line` to mirror. Don't skip it
88
+ and don't fabricate a pointer — per the taxonomy's *Greenfield / pre-code
89
+ boundaries*, emit it as **conventions-with-planned-pointer**: record the
90
+ decided conventions inline, carry a `planned <path>` pointer, mark it `point*`
91
+ (a pre-code `transcribe` source is `transcribe*`), and make the last
92
+ safe-change item "replace the planned pointer with the real `file:line` once
93
+ code lands." Link any item that hinges on an unresolved decision to its
94
+ open-question/ADR.
95
+ 4. **Set severity** from the taxonomy default, adjusted by the concrete repo +
96
+ project memory. Severity is load-bearing — it drives `check` review depth and
97
+ `init` scan priority.
98
+ 5. **Name the bounding code units** (paths / package names) in every `Bounds`
99
+ line. The distinct vertices named across all `Bounds` lines are the
100
+ **module-discovery feed** `harness-manager` reads to propose modules — name
101
+ them precisely.
102
+ 6. **Write `docs/boundaries/README.md`** as the index per the taxonomy's index
103
+ spec: a table with a **Doc-mode column**
104
+ (`| Boundary | Type | Severity | Doc-mode | Bounds (summary) |`), then the
105
+ standing blocks — **Active technology packs**, **Folds applied** (which catalog
106
+ types collapsed into which file — surface the per-entry fold-notes as a ledger),
107
+ and **Skipped** (types omitted + one-line reason each). Add the `point*`/`transcribe*`
108
+ footnote if any boundary is pre-code.
109
+ 7. **Don't invent boundaries the repo doesn't have.** Only instantiate types the
110
+ detected stack/IA actually exhibits; `log()` what you skipped and why.
111
+
112
+ Write only under `docs/boundaries/` and `docs/memory/boundary.md`.
113
+ Read-only on production code.
114
+
115
+ ## Mode: `create` (targeted)
116
+
117
+ Add or update a single boundary file (e.g. a new contract surfaced by `check`, or
118
+ a boundary the author asks for by name).
119
+
120
+ 1. Identify the boundary type from the taxonomy; confirm the repo exhibits it.
121
+ 2. Write/update the one file with the full anatomy (Bounds, Shape, Invariants,
122
+ Blast radius, Safe-change checklist), `point` or `transcribe` per the taxonomy.
123
+ 3. Update `docs/boundaries/README.md` (add/refresh the index row).
124
+ 4. For an **update** that fixes drift (the `maintenance` agent flags a boundary
125
+ file drifted from its source): re-read the source, refresh the `Shape` pointer
126
+ or transcription, leave invariants/checklist intact unless they changed.
127
+
128
+ ## Mode: `check` (pre-implementation)
129
+
130
+ Read-only. Intersect a plan's proposed changes with the documented boundaries and
131
+ surface cascading risk *before* coding. (This is the old `introspect`.)
132
+
133
+ **Inputs.** Plan path (user-provided; else newest in `docs/history/plans/`). Every
134
+ file in `docs/boundaries/` is mandatory input — if the folder is missing/empty,
135
+ stop and tell the user to run `generate` first. Source files read on demand to
136
+ verify — never guess from plan text alone.
137
+
138
+ 1. **Load.** Resolve + read the plan; confirm it has a `Status:` (warn if
139
+ `Complete`/`Abandoned`). Read every boundary file; parse the
140
+ `[BOUNDARY: type · severity]` header + Bounds / Shape / Invariants / Blast
141
+ radius / Safe-change checklist. Severity sets review depth.
142
+ 2. **Parse proposed changes.** The plan template's **Proposed Changes**
143
+ subsections are load-bearing: New Files / Modified Files (check against the
144
+ modules each boundary **Bounds** and any `Shape` `file:line`) · New Entities /
145
+ State (vs `information-architecture` + `db-schema`) · API Changes (vs
146
+ `wire-response-envelope` / `rest-endpoint-shape` / `graphql-schema`) ·
147
+ Cross-Module Contracts Affected (the author's own admission — start there,
148
+ then verify). Note any missing subsection.
149
+ 3. **Analyze each boundary × change pair** for: **Bounds overlap** (touches a
150
+ bounded module or a `Shape` pointer), **Invariant overlap** (introduces a
151
+ shape the invariants prohibit — raw literal where a shared constant is
152
+ required, entity type not in the canonical array, persisted field without
153
+ fallback), **Shape overlap** (modifies the canonical Shape). Read the actual
154
+ source to verify any plausible intersection.
155
+ 4. **Assign severity.** Anchor to the boundary's own severity (the floor),
156
+ adjust by the concrete change. Critical = silent data loss / corrupted
157
+ persistence / broken cross-entity refs; High = stale state / broken lookups /
158
+ schema selectors without fallbacks; Medium = likely-to-drift string-coupling;
159
+ Low = code smell.
160
+ 5. **Report** (in chat — never written to disk):
161
+
162
+ ```
163
+ # Application Boundary — check — {plan filename}
164
+
165
+ Plan: {path}
166
+ Boundaries: {N} files evaluated
167
+ Intersections: {N} ({critical}/{high}/{medium}/{low})
168
+
169
+ ## Violations
170
+ | # | Boundary | Risk | Severity | Plan step |
171
+
172
+ ## Suggested Plan Additions
173
+ - Violation #1 → add step X to Implementation Steps / Done Criteria …
174
+
175
+ ## Missing Subsections
176
+ ## New Cross-Module Contracts Detected
177
+ {describe the proposed boundary (type, Bounds, Shape, suggested severity) and
178
+ recommend running `create` to formalize it — do not write the file from check}
179
+ ```
180
+
181
+ The severity-ranked table is the load-bearing output. Suggestions are advisory —
182
+ the user folds them back into the plan; **`check` never edits the plan or code.**
183
+
184
+ ## Mode: `init` (reconcile — ensure, scan, backfill)
185
+
186
+ Ensure boundaries are initialized and current. Three jobs in one pass:
187
+
188
+ 1. **Ensure exist.** If `docs/boundaries/` is missing/empty → this is really a
189
+ baseline; run `generate` instead and stop.
190
+ 2. **Scan live code for violations** (the old `contract-monitor`). For each
191
+ boundary file, parse its header + invariants, then scan the codebase for
192
+ violations of *that* boundary. Categories: **unregistered entity types**
193
+ (strings used as entity-type ids not in the canonical array) · **string-coupled
194
+ constants** (raw literals where a shared constant import is required) ·
195
+ **cross-entity references without propagation** (storing entity A's
196
+ slug/name/id by value in B with no documented propagation) · **schema
197
+ mismatches** (selectors hitting persisted fields without a fallback default) ·
198
+ **undocumented endpoints** (routes with no feature-doc / `api` boundary entry).
199
+ Verify by reading actual source — never guess.
200
+ 3. **Detect drift + backfill the catalog.** (a) Flag any boundary file whose
201
+ `Shape` has drifted from its source (refresh it via `create`/update). (b)
202
+ Compare the taxonomy's boundary *types* against what the repo now exhibits —
203
+ if the repo gained a technology (or the catalog gained a type) that the repo
204
+ exhibits but `docs/boundaries/` lacks, surface it and offer to `generate` that
205
+ one boundary.
206
+
207
+ **Act on violations.** Mechanical fix (missing constant import, missing fallback,
208
+ missing canonical-array entry) → fix directly with an Edit. Design-decision fix
209
+ (new entity-type registration, new propagation mechanism, schema migration) →
210
+ append to `docs/history/backlog.md` as P0/P1 with the specific boundary + bounded
211
+ modules. Pre-existing exception (in `docs/memory/boundary.md`) → skip
212
+ silently.
213
+
214
+ **Report** a table `| # | Boundary | Violation | Location | Action taken |`, then
215
+ update `docs/memory/boundary.md` with scan date, per-boundary
216
+ violation counts, items fixed / backlogged, new exceptions, and any
217
+ newly-exhibited boundary types backfilled.
218
+
219
+ ---
220
+
221
+ ## Rules
222
+
223
+ - **One knowledge root.** Every mode reads `references/boundary-taxonomy.md`.
224
+ Extend the catalog there, never inline a divergent copy in a mode.
225
+ - **`check` is read-only** — never edits the plan, source, boundaries, or memory.
226
+ The other modes write only under `docs/boundaries/`,
227
+ `docs/memory/boundary.md`, and (for `init` mechanical fixes) the
228
+ specific violating source line. Never touch frozen records
229
+ (`docs/history/plans/`, `docs/history/changelog/`) or feature docs.
230
+ - **Verify against source.** Never report a violation or intersection from text
231
+ alone — read the files the plan/boundary names.
232
+ - **One file per boundary.** Don't bundle unrelated boundaries.
233
+ - **Severity is load-bearing.** It feeds `check` review depth + `init` priority.
234
+ Default from the taxonomy; adjust from the repo + memory.
235
+ - **Name the bounding code units** in every `Bounds` line — they're the
236
+ module-discovery feed `harness-manager` reads. A workspace is *not* a module.
237
+ - **Don't invent boundaries the repo doesn't have.** Only instantiate exhibited
238
+ types; `log()` skips. **A clean report is valid** — empty violations/intersections
239
+ means say so, don't pad.
240
+ - **Docs never outrank code.** For `point` boundaries, mirror with `file:line`;
241
+ don't fork the shape. `file:line` stays primary for point-mode boundaries; an
242
+ `anchors:` query (see the documentation skill's frontmatter reference) MAY
243
+ supplement it for refactor-resilience — optional and additive, it re-resolves
244
+ the shape when the line moves, never replaces the pointer.
245
+ - **Be specific** — file paths, line numbers, boundary names, the exact violating
246
+ string or code shape.
247
+
248
+ ## Strategy memory
249
+
250
+ After a run you may append to `docs/memory/boundary.md` (git-tracked):
251
+ project-specific boundary exceptions, where the IA model actually lives in this
252
+ repo, stack quirks that change a doc-mode, recurring drift/violation zones,
253
+ historical violation counts. Never user preferences (those are `~/.claude/`);
254
+ never a transcription of a boundary's content (that's the boundary file). Date
255
+ each entry `## YYYY-MM-DD — {topic}`.
256
+
257
+ ## Output locations
258
+
259
+ - `generate`/`create`/`init` write boundary files to `docs/boundaries/` + the
260
+ index `docs/boundaries/README.md`; memory to `docs/memory/boundary.md`.
261
+ - `check` returns its report in chat — never written to disk; the user folds
262
+ insights back into the plan.