workermill 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -123,6 +123,16 @@ Config stored at `~/.workermill/cli.json` (global) and `.workermill/config.json`
123
123
 
124
124
  backend_developer, frontend_developer, devops_engineer, qa_engineer, security_engineer, data_ml_engineer, mobile_developer, tech_writer, architect, tech_lead, planner, critic
125
125
 
126
+ All worker personas include production-hardened rules:
127
+ - **Real services, not mocks** — Docker containers for databases, caches, queues. Tests run against real services.
128
+ - **Version trust** — Never downgrades language/runtime versions (training data is outdated)
129
+ - **Learning markers** — Reports codebase discoveries with `::learning::` markers for team visibility
130
+ - **Right-sized plans** — Planner matches plan complexity to task complexity (1 step for simple, 3-5 for complex)
131
+ - **Approval bias** — Tech lead only blocks on real functional/security issues, not cosmetic preferences
132
+ - **File overlap detection** — Critic catches parallel merge conflicts before they happen
133
+
134
+ Custom personas can be added per-project in `.workermill/personas/` or globally in `~/.workermill/personas/`.
135
+
126
136
  ## Requirements
127
137
 
128
138
  - Node.js 20+
@@ -1639,6 +1639,41 @@ var CostTracker = class {
1639
1639
  }
1640
1640
  };
1641
1641
 
1642
+ // src/logger.js
1643
+ import fs9 from "fs";
1644
+ import path11 from "path";
1645
+ var LOG_DIR = path11.join(process.cwd(), ".workermill");
1646
+ var LOG_FILE = path11.join(LOG_DIR, "cli.log");
1647
+ var logStream = null;
1648
+ function ensureLogDir() {
1649
+ if (!fs9.existsSync(LOG_DIR)) {
1650
+ fs9.mkdirSync(LOG_DIR, { recursive: true });
1651
+ }
1652
+ }
1653
+ function getStream() {
1654
+ if (!logStream) {
1655
+ ensureLogDir();
1656
+ logStream = fs9.createWriteStream(LOG_FILE, { flags: "a" });
1657
+ }
1658
+ return logStream;
1659
+ }
1660
+ function timestamp() {
1661
+ return (/* @__PURE__ */ new Date()).toISOString();
1662
+ }
1663
+ function log(level, message, data) {
1664
+ const entry = data ? `[${timestamp()}] ${level}: ${message} ${JSON.stringify(data)}` : `[${timestamp()}] ${level}: ${message}`;
1665
+ getStream().write(entry + "\n");
1666
+ }
1667
+ function info(message, data) {
1668
+ log("INFO", message, data);
1669
+ }
1670
+ function error(message, data) {
1671
+ log("ERROR", message, data);
1672
+ }
1673
+ function debug(message, data) {
1674
+ log("DEBUG", message, data);
1675
+ }
1676
+
1642
1677
  export {
1643
1678
  __dirname,
1644
1679
  loadConfig,
@@ -1648,5 +1683,8 @@ export {
1648
1683
  createModel,
1649
1684
  killActiveProcess,
1650
1685
  createToolDefinitions,
1651
- CostTracker
1686
+ CostTracker,
1687
+ info,
1688
+ error,
1689
+ debug
1652
1690
  };
package/dist/index.js CHANGED
@@ -4,15 +4,19 @@ import {
4
4
  buildOllamaOptions,
5
5
  createModel,
6
6
  createToolDefinitions,
7
+ debug,
8
+ error,
7
9
  getProviderForPersona,
10
+ info,
8
11
  killActiveProcess,
9
12
  loadConfig,
10
13
  saveConfig
11
- } from "./chunk-VC6VNVEY.js";
14
+ } from "./chunk-NGQKIYVB.js";
12
15
 
13
16
  // src/index.ts
14
17
  import React5 from "react";
15
18
  import { render } from "ink";
19
+ import chalk2 from "chalk";
16
20
  import { Command } from "commander";
17
21
 
18
22
  // src/setup.js
@@ -119,7 +123,7 @@ async function runSetup() {
119
123
  }
120
124
 
121
125
  // src/ui/Root.tsx
122
- import { useState as useState5, useCallback as useCallback3, useRef as useRef3 } from "react";
126
+ import { useState as useState5, useCallback as useCallback3, useRef as useRef3, useEffect as useEffect2 } from "react";
123
127
  import { useApp as useApp2 } from "ink";
124
128
  import { execSync as execSync2 } from "child_process";
125
129
  import fs2 from "fs";
@@ -344,7 +348,7 @@ function useAgent(options) {
344
348
  const envMap = {
345
349
  anthropic: "ANTHROPIC_API_KEY",
346
350
  openai: "OPENAI_API_KEY",
347
- google: "GOOGLE_API_KEY"
351
+ google: "GOOGLE_GENERATIVE_AI_API_KEY"
348
352
  };
349
353
  const envVar = envMap[options.provider];
350
354
  if (envVar && !process.env[envVar]) {
@@ -447,20 +451,20 @@ function useAgent(options) {
447
451
  ...td,
448
452
  execute: async (input) => {
449
453
  const callId = crypto2.randomUUID();
450
- const info = {
454
+ const info2 = {
451
455
  id: callId,
452
456
  name,
453
457
  input,
454
458
  status: "pending"
455
459
  };
456
- setStreamingToolCalls((prev) => [...prev, info]);
460
+ setStreamingToolCalls((prev) => [...prev, info2]);
457
461
  setMessages((prev) => [
458
462
  ...prev,
459
463
  {
460
464
  id: `tc-${callId}`,
461
465
  role: "assistant",
462
466
  content: "",
463
- toolCalls: [{ ...info, status: "running" }],
467
+ toolCalls: [{ ...info2, status: "running" }],
464
468
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
465
469
  }
466
470
  ]);
@@ -487,8 +491,10 @@ function useAgent(options) {
487
491
  );
488
492
  setStatus("tool_running");
489
493
  try {
494
+ info("Tool call", { tool: name, input: JSON.stringify(input).slice(0, 200) });
490
495
  const result = await td.execute(input);
491
496
  const resultStr = typeof result === "string" ? result : JSON.stringify(result);
497
+ debug("Tool result", { tool: name, result: resultStr.slice(0, 200) });
492
498
  setStreamingToolCalls(
493
499
  (prev) => prev.map(
494
500
  (tc) => tc.id === callId ? { ...tc, status: "done", result: resultStr } : tc
@@ -531,6 +537,7 @@ function useAgent(options) {
531
537
  void (async () => {
532
538
  const session = sessionRef.current;
533
539
  addMessage(session, "user", input);
540
+ info("User message", { length: input.length, preview: input.slice(0, 100) });
534
541
  if (!session.name) {
535
542
  session.name = input.slice(0, 50).replace(/\n/g, " ");
536
543
  }
@@ -591,6 +598,7 @@ function useAgent(options) {
591
598
  setStreamingText("");
592
599
  addMessage(session, "assistant", finalText);
593
600
  session.totalTokens += inputTokens + outputTokens;
601
+ info("Response complete", { inputTokens, outputTokens, textLength: finalText.length });
594
602
  costTrackerRef.current.addUsage(
595
603
  "agent",
596
604
  options.provider,
@@ -634,6 +642,7 @@ function useAgent(options) {
634
642
  return;
635
643
  }
636
644
  const errText = err instanceof Error ? err.message : String(err);
645
+ error("Agent error", { error: errText });
637
646
  const errorMsg = {
638
647
  id: crypto2.randomUUID(),
639
648
  role: "assistant",
@@ -772,7 +781,7 @@ function useOrchestrator(addMessage2) {
772
781
  setRunning(false);
773
782
  return;
774
783
  }
775
- const { classifyComplexity, runOrchestration } = await import("./orchestrator-5I7BGPC7.js");
784
+ const { classifyComplexity, runOrchestration } = await import("./orchestrator-2M4BCHQR.js");
776
785
  const output = {
777
786
  log(persona, message) {
778
787
  const emoji = getEmoji(persona);
@@ -1469,6 +1478,10 @@ function App(props) {
1469
1478
  const lastCtrlCRef = useRef2(0);
1470
1479
  const width = stdout?.columns || 80;
1471
1480
  useInput3((input, key) => {
1481
+ if (key.escape && props.status !== "idle") {
1482
+ props.onCancel();
1483
+ return;
1484
+ }
1472
1485
  if (key.ctrl && input === "c") {
1473
1486
  const now = Date.now();
1474
1487
  if (props.status === "idle" && now - lastCtrlCRef.current < 500) {
@@ -1479,43 +1492,7 @@ function App(props) {
1479
1492
  }
1480
1493
  });
1481
1494
  const mode = props.planMode ? "PLAN" : props.trustAll ? "trust all" : "ask";
1482
- const headerInner = Math.min(width - 4, 50);
1483
1495
  return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", width: "100%", children: [
1484
- /* @__PURE__ */ jsx6(Static, { items: [{ id: "__header__" }], children: () => /* @__PURE__ */ jsxs6(
1485
- Box6,
1486
- {
1487
- flexDirection: "column",
1488
- borderStyle: "round",
1489
- borderColor: theme.subtleDark,
1490
- paddingX: 1,
1491
- width: headerInner,
1492
- children: [
1493
- /* @__PURE__ */ jsxs6(Box6, { children: [
1494
- /* @__PURE__ */ jsx6(Text6, { color: theme.brand, children: "\u25C6 " }),
1495
- /* @__PURE__ */ jsx6(Text6, { color: theme.text, bold: true, children: "WorkerMill" })
1496
- ] }),
1497
- /* @__PURE__ */ jsx6(Text6, { children: " " }),
1498
- /* @__PURE__ */ jsxs6(Text6, { color: theme.subtle, children: [
1499
- " ",
1500
- props.provider,
1501
- "/",
1502
- props.model
1503
- ] }),
1504
- /* @__PURE__ */ jsxs6(Text6, { color: theme.subtle, children: [
1505
- " ",
1506
- "cwd: ",
1507
- props.workingDir
1508
- ] }),
1509
- /* @__PURE__ */ jsxs6(Text6, { color: theme.subtle, children: [
1510
- " ",
1511
- "Type ",
1512
- /* @__PURE__ */ jsx6(Text6, { color: theme.text, children: "/help" }),
1513
- " for commands"
1514
- ] })
1515
- ]
1516
- },
1517
- "__header__"
1518
- ) }),
1519
1496
  /* @__PURE__ */ jsx6(Static, { items: props.messages, children: (message) => /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", marginTop: 1, children: message.role === "user" ? /* @__PURE__ */ jsxs6(Box6, { marginLeft: 1, children: [
1520
1497
  /* @__PURE__ */ jsx6(Text6, { color: theme.brand, bold: true, children: "\u2771 " }),
1521
1498
  /* @__PURE__ */ jsx6(Text6, { color: theme.text, children: message.content })
@@ -1616,34 +1593,37 @@ function getGitStatus(cwd) {
1616
1593
  ${status}
1617
1594
  \`\`\``;
1618
1595
  }
1619
- var HELP_TEXT = `**WorkerMill Commands**
1596
+ var HELP_TEXT = `**WorkerMill** \u2014 AI coding agent for your terminal.
1620
1597
 
1621
- | Command | Description |
1622
- |---|---|
1623
- | \`/help\` | Show this help |
1624
- | \`/model\` | Show current model info |
1625
- | \`/cost\` | Show session cost breakdown |
1626
- | \`/status\` | Show session status |
1627
- | \`/plan\` | Toggle plan mode (read-only tools) |
1628
- | \`/trust\` | Trust all tool calls for this session |
1629
- | \`/build <task>\` | Multi-expert orchestration |
1630
- | \`/git\` | Show git branch and status |
1631
- | \`/sessions\` | List recent sessions |
1632
- | \`/editor\` | Open \\$EDITOR, submit contents |
1633
- | \`/compact\` | Trigger context compaction |
1634
- | \`/clear\` | Clear screen (limited in Ink) |
1635
- | \`/quit\` | Exit WorkerMill |
1636
- | \`/exit\` | Exit WorkerMill |
1598
+ **Two ways to work:**
1599
+
1600
+ **Chat** \u2014 Ask anything. I'll read files, write code, run commands.
1601
+ Just type your question or task and press Enter.
1637
1602
 
1638
- **Shortcuts**
1603
+ **Build** \u2014 Create software with multiple specialist AI agents.
1604
+ Type \`/build <description>\` and I'll plan stories, assign experts
1605
+ (backend, frontend, devops, security), execute, and review.
1639
1606
 
1640
- - \`!command\` -- Run a shell command directly and display output
1641
- - \`Ctrl+C\` -- Cancel current operation
1642
- - \`Ctrl+C Ctrl+C\` -- Exit when idle
1607
+ Or from the command line: \`wm build "your task"\`
1643
1608
 
1644
- **Notes**
1609
+ ---
1645
1610
 
1646
- - Multiline input is not currently supported. Paste single-line prompts or use \`/editor\` to compose longer messages.`;
1611
+ **Commands**
1612
+
1613
+ | Command | Description |
1614
+ |---|---|
1615
+ | \`/build <task>\` | Multi-expert orchestration \u2014 the main feature |
1616
+ | \`/plan\` | Toggle plan mode (read-only, explore before committing) |
1617
+ | \`/trust\` | Auto-approve all tool calls for this session |
1618
+ | \`/model\` | Show current provider and model |
1619
+ | \`/cost\` | Session cost and token usage |
1620
+ | \`/status\` | Session info |
1621
+ | \`/git\` | Git branch and status |
1622
+ | \`/sessions\` | List/switch sessions |
1623
+ | \`/editor\` | Open \\$EDITOR for longer input |
1624
+ | \`/quit\` | Exit |
1625
+
1626
+ **Shortcuts:** \`!command\` runs shell directly, \`ESC\` cancels, \`Ctrl+C Ctrl+C\` exits.`;
1647
1627
  function Root(props) {
1648
1628
  const { exit } = useApp2();
1649
1629
  const agent = useAgent(props);
@@ -1658,6 +1638,14 @@ function Root(props) {
1658
1638
  [agent]
1659
1639
  );
1660
1640
  const orchestrator = useOrchestrator(addOrchestratorMessage);
1641
+ const buildStarted = useRef3(false);
1642
+ useEffect2(() => {
1643
+ if (props.initialBuildTask && !buildStarted.current) {
1644
+ buildStarted.current = true;
1645
+ agent.addUserMessage(`/build ${props.initialBuildTask}`);
1646
+ orchestrator.start(props.initialBuildTask, props.trustAll, props.sandboxed);
1647
+ }
1648
+ }, [props.initialBuildTask, props.trustAll, props.sandboxed, agent, orchestrator]);
1661
1649
  const [inputHistory, setInputHistory] = useState5(() => loadHistory());
1662
1650
  const [gitBranch, setGitBranch] = useState5(() => getGitBranch());
1663
1651
  const lastBranchCheck = useRef3(Date.now());
@@ -1938,6 +1926,7 @@ ${trimmedOutput}
1938
1926
  trustAll: props.trustAll,
1939
1927
  planMode: props.planMode,
1940
1928
  onSubmit: handleSubmit,
1929
+ onCancel: agent.cancel,
1941
1930
  messages: agent.messages,
1942
1931
  status: orchestrator.running ? "tool_running" : agent.status,
1943
1932
  permissionRequest: agent.permissionRequest,
@@ -1952,8 +1941,25 @@ ${trimmedOutput}
1952
1941
  }
1953
1942
 
1954
1943
  // src/index.ts
1955
- var VERSION = "0.2.0";
1956
- var program = new Command().name("workermill").description("AI coding agent with multi-provider support").version(VERSION).option("--provider <provider>", "Override default provider").option("--model <model>", "Override model").option("--trust", "Skip all tool permission prompts").option("--resume", "Resume the last conversation").option("--plan", "Start in plan mode (read-only tools)").option("--full-disk", "Allow tools to access files outside working directory").action(async (options) => {
1944
+ function printWelcome(provider, model, workingDir) {
1945
+ const brand = chalk2.hex("#D77757");
1946
+ const dim = chalk2.dim;
1947
+ const white = chalk2.white;
1948
+ console.log();
1949
+ console.log(` ${brand("\u25C6")} ${white.bold("WorkerMill")} ${dim("v" + VERSION)}`);
1950
+ console.log();
1951
+ console.log(dim(` ${provider}/${model}`));
1952
+ console.log(dim(` cwd: ${workingDir}`));
1953
+ console.log();
1954
+ console.log(dim(" Ask me anything, or use ") + brand("/build") + dim(" to create software with multi-expert AI."));
1955
+ console.log(dim(" Type ") + white("/help") + dim(" for all commands."));
1956
+ console.log();
1957
+ }
1958
+ var VERSION = "0.3.1";
1959
+ function addSharedOptions(cmd) {
1960
+ return cmd.option("--provider <provider>", "Override default provider").option("--model <model>", "Override model").option("--trust", "Skip all tool permission prompts").option("--full-disk", "Allow tools to access files outside working directory");
1961
+ }
1962
+ async function resolveConfig(options) {
1957
1963
  let config = loadConfig();
1958
1964
  if (!config) {
1959
1965
  config = await runSetup();
@@ -1967,8 +1973,14 @@ var program = new Command().name("workermill").description("AI coding agent with
1967
1973
  providerConfig.model = options.model;
1968
1974
  }
1969
1975
  }
1976
+ return config;
1977
+ }
1978
+ var program = new Command().name("wm").description("WorkerMill \u2014 AI coding agent for your terminal").version(VERSION);
1979
+ var defaultCmd = program.command("chat", { isDefault: true }).description("Interactive AI coding agent (default)").option("--resume", "Resume the last conversation").option("--plan", "Start in plan mode (read-only tools)").action(async (options) => {
1980
+ const config = await resolveConfig(options);
1970
1981
  const { provider, model, apiKey, host, contextLength } = getProviderForPersona(config);
1971
1982
  const workingDir = process.cwd();
1983
+ printWelcome(provider, model, workingDir);
1972
1984
  const { waitUntilExit } = render(
1973
1985
  React5.createElement(Root, {
1974
1986
  provider,
@@ -1985,4 +1997,50 @@ var program = new Command().name("workermill").description("AI coding agent with
1985
1997
  );
1986
1998
  await waitUntilExit();
1987
1999
  });
2000
+ addSharedOptions(defaultCmd);
2001
+ var buildCmd = program.command("build [task...]").description("Build software with multi-expert orchestration").option("--critic", "Run critic pass on plan before execution").action(async (taskParts, options) => {
2002
+ const task = taskParts.join(" ");
2003
+ if (!task) {
2004
+ console.log('\n Usage: wm build "<task description>"\n');
2005
+ console.log(" Example:");
2006
+ console.log(' wm build "REST API with auth, tests, and Docker"');
2007
+ console.log(' wm build "Add search feature to the React frontend"\n');
2008
+ process.exit(0);
2009
+ }
2010
+ const config = await resolveConfig(options);
2011
+ if (options.critic) {
2012
+ config.review = { ...config.review, useCritic: true };
2013
+ }
2014
+ const { provider, model, apiKey, host, contextLength } = getProviderForPersona(config);
2015
+ const trustAll = options.trust || false;
2016
+ const sandboxed = !options.fullDisk;
2017
+ if (apiKey) {
2018
+ const envMap = {
2019
+ anthropic: "ANTHROPIC_API_KEY",
2020
+ openai: "OPENAI_API_KEY",
2021
+ google: "GOOGLE_GENERATIVE_AI_API_KEY"
2022
+ };
2023
+ const envVar = envMap[provider];
2024
+ if (envVar && !process.env[envVar]) {
2025
+ process.env[envVar] = apiKey;
2026
+ }
2027
+ }
2028
+ const { waitUntilExit } = render(
2029
+ React5.createElement(Root, {
2030
+ provider,
2031
+ model,
2032
+ apiKey,
2033
+ host,
2034
+ contextLength,
2035
+ trustAll,
2036
+ planMode: false,
2037
+ sandboxed,
2038
+ resume: false,
2039
+ workingDir: process.cwd(),
2040
+ initialBuildTask: task
2041
+ })
2042
+ );
2043
+ await waitUntilExit();
2044
+ });
2045
+ addSharedOptions(buildCmd);
1988
2046
  program.parse();
@@ -4,8 +4,9 @@ import {
4
4
  buildOllamaOptions,
5
5
  createModel,
6
6
  createToolDefinitions,
7
- getProviderForPersona
8
- } from "./chunk-VC6VNVEY.js";
7
+ getProviderForPersona,
8
+ info
9
+ } from "./chunk-NGQKIYVB.js";
9
10
 
10
11
  // src/orchestrator.js
11
12
  import chalk3 from "chalk";
@@ -224,37 +225,6 @@ var PermissionManager = class {
224
225
  // src/tui.js
225
226
  import chalk2 from "chalk";
226
227
  import { execSync } from "child_process";
227
-
228
- // src/logger.js
229
- import fs2 from "fs";
230
- import path2 from "path";
231
- var LOG_DIR = path2.join(process.cwd(), ".workermill");
232
- var LOG_FILE = path2.join(LOG_DIR, "cli.log");
233
- var logStream = null;
234
- function ensureLogDir() {
235
- if (!fs2.existsSync(LOG_DIR)) {
236
- fs2.mkdirSync(LOG_DIR, { recursive: true });
237
- }
238
- }
239
- function getStream() {
240
- if (!logStream) {
241
- ensureLogDir();
242
- logStream = fs2.createWriteStream(LOG_FILE, { flags: "a" });
243
- }
244
- return logStream;
245
- }
246
- function timestamp() {
247
- return (/* @__PURE__ */ new Date()).toISOString();
248
- }
249
- function log(level, message, data) {
250
- const entry = data ? `[${timestamp()}] ${level}: ${message} ${JSON.stringify(data)}` : `[${timestamp()}] ${level}: ${message}`;
251
- getStream().write(entry + "\n");
252
- }
253
- function info(message, data) {
254
- log("INFO", message, data);
255
- }
256
-
257
- // src/tui.js
258
228
  function formatToolCall(toolName, toolInput) {
259
229
  let msg = `Tool: ${toolName}`;
260
230
  if (toolInput) {
@@ -1253,10 +1223,10 @@ ${story.description}`,
1253
1223
  } catch {
1254
1224
  wmCoordinatorLog("Initializing git repository...");
1255
1225
  execSync2("git init", { cwd: workingDir, encoding: "utf-8", stdio: "pipe" });
1256
- const fs3 = await import("fs");
1226
+ const fs2 = await import("fs");
1257
1227
  const gitignorePath = `${workingDir}/.gitignore`;
1258
- if (!fs3.existsSync(gitignorePath)) {
1259
- fs3.writeFileSync(gitignorePath, "node_modules/\ndist/\n.env\n.workermill/\n*.log\n", "utf-8");
1228
+ if (!fs2.existsSync(gitignorePath)) {
1229
+ fs2.writeFileSync(gitignorePath, "node_modules/\ndist/\n.env\n.workermill/\n*.log\n", "utf-8");
1260
1230
  }
1261
1231
  wmCoordinatorLog("Git repo initialized");
1262
1232
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workermill",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "AI coding agent with multi-expert orchestration. Works with any LLM provider.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,3 +27,54 @@ Work Style:
27
27
  - Create architecture decision records for major choices
28
28
  - Consider scalability, maintainability, and team velocity
29
29
  - Document architectural patterns and rationale
30
+
31
+ ## ABSOLUTE RULE — NEVER Flag Language/Runtime Versions as Invalid
32
+
33
+ Your training data is OUTDATED — newer language and runtime versions have been released since your cutoff. NEVER suggest downgrading a language version. NEVER say a version "doesn't exist" or is "non-existent." If the project uses a version you don't recognize, it is correct — the project authors chose it deliberately.
34
+
35
+ ## Development Environment
36
+
37
+ You have `docker` and `docker compose` available. **You MUST spin up real service dependencies** (databases, caches, message queues) using Docker containers before writing any application code that depends on them. Do NOT mock or stub external services — connect to real instances running in Docker.
38
+
39
+ ### Required Workflow
40
+ 1. **Before writing application code**: Start all required service containers
41
+ 2. **Configure your code** to connect to `localhost` on the container ports
42
+ 3. **Run tests against real services** — integration tests must hit real databases, not mocks
43
+ 4. **Clean up containers** when done (`docker stop <name>`)
44
+
45
+ ### Common Services
46
+ - MongoDB: `docker run -d --rm -p 27017:27017 --name mongo-test mongo:7`
47
+ - Redis: `docker run -d --rm -p 6379:6379 --name redis-test redis:7-alpine`
48
+ - PostgreSQL: `docker run -d --rm -p 5432:5432 -e POSTGRES_PASSWORD=test --name postgres-test postgres:16-alpine`
49
+ - MySQL: `docker run -d --rm -p 3306:3306 -e MYSQL_ROOT_PASSWORD=test --name mysql-test mysql:8`
50
+ - If the project has a `docker-compose.yml`, use `docker compose up -d`
51
+
52
+ ### Why This Matters
53
+ Mocking produces code full of assumptions that break on first contact with real services. Real containers catch connection strings, schema mismatches, query errors, and serialization bugs immediately. **Tests that pass against mocks but fail against real services are worthless.**
54
+
55
+ ### If Docker Is Not Working
56
+ If `docker` commands fail, DO NOT fall back to mocking. Report the Docker error as a blocker. Never write test stubs or mock implementations as a workaround.
57
+
58
+ ### CI/CD Workflows Must Include Service Containers
59
+ When creating GitHub Actions CI workflows that run tests requiring databases, you **MUST** add `services:` blocks so the CI runner has real service instances. Match your local Docker setup with CI service containers.
60
+
61
+ ## Reporting Learnings
62
+
63
+ When you discover something specific and actionable about this codebase, emit a learning marker:
64
+
65
+ ```
66
+ ::learning::The test suite requires DATABASE_URL env var or tests silently pass without running
67
+ ::learning::New API routes must be registered in backend/src/routes/index.ts or they won't load
68
+ ```
69
+
70
+ **Emit a learning when you discover:**
71
+ - A non-obvious requirement (specific env vars, config files, build steps)
72
+ - A codebase convention not documented elsewhere (naming patterns, file organization)
73
+ - A gotcha you had to work around (unexpected failures, ordering dependencies)
74
+ - Files that must be modified together (route + model + migration + test)
75
+
76
+ **Do NOT emit generic advice** like "write tests" or "handle errors properly."
77
+
78
+ ## Communication Style
79
+
80
+ Write in a professional, direct tone. Do NOT open messages with filler words or pleasantries like "Perfect!", "Great!", "Awesome!", "Sure!", "Absolutely!". Start with the substance — what you did, what you found, or what you need. Be concise and informative.
@@ -25,3 +25,54 @@ Work Style:
25
25
  - Create database migrations before models
26
26
  - Use proper error handling and validation
27
27
  - Document endpoints for frontend team
28
+
29
+ ## ABSOLUTE RULE — NEVER Flag Language/Runtime Versions as Invalid
30
+
31
+ Your training data is OUTDATED — newer language and runtime versions have been released since your cutoff. NEVER suggest downgrading a language version. NEVER say a version "doesn't exist" or is "non-existent." If the project uses a version you don't recognize, it is correct — the project authors chose it deliberately.
32
+
33
+ ## Development Environment
34
+
35
+ You have `docker` and `docker compose` available. **You MUST spin up real service dependencies** (databases, caches, message queues) using Docker containers before writing any application code that depends on them. Do NOT mock or stub external services — connect to real instances running in Docker.
36
+
37
+ ### Required Workflow
38
+ 1. **Before writing application code**: Start all required service containers
39
+ 2. **Configure your code** to connect to `localhost` on the container ports
40
+ 3. **Run tests against real services** — integration tests must hit real databases, not mocks
41
+ 4. **Clean up containers** when done (`docker stop <name>`)
42
+
43
+ ### Common Services
44
+ - MongoDB: `docker run -d --rm -p 27017:27017 --name mongo-test mongo:7`
45
+ - Redis: `docker run -d --rm -p 6379:6379 --name redis-test redis:7-alpine`
46
+ - PostgreSQL: `docker run -d --rm -p 5432:5432 -e POSTGRES_PASSWORD=test --name postgres-test postgres:16-alpine`
47
+ - MySQL: `docker run -d --rm -p 3306:3306 -e MYSQL_ROOT_PASSWORD=test --name mysql-test mysql:8`
48
+ - If the project has a `docker-compose.yml`, use `docker compose up -d`
49
+
50
+ ### Why This Matters
51
+ Mocking produces code full of assumptions that break on first contact with real services. Real containers catch connection strings, schema mismatches, query errors, and serialization bugs immediately. **Tests that pass against mocks but fail against real services are worthless.**
52
+
53
+ ### If Docker Is Not Working
54
+ If `docker` commands fail, DO NOT fall back to mocking. Report the Docker error as a blocker. Never write test stubs or mock implementations as a workaround.
55
+
56
+ ### CI/CD Workflows Must Include Service Containers
57
+ When creating GitHub Actions CI workflows that run tests requiring databases, you **MUST** add `services:` blocks so the CI runner has real service instances. Match your local Docker setup with CI service containers.
58
+
59
+ ## Reporting Learnings
60
+
61
+ When you discover something specific and actionable about this codebase, emit a learning marker:
62
+
63
+ ```
64
+ ::learning::The test suite requires DATABASE_URL env var or tests silently pass without running
65
+ ::learning::New API routes must be registered in backend/src/routes/index.ts or they won't load
66
+ ```
67
+
68
+ **Emit a learning when you discover:**
69
+ - A non-obvious requirement (specific env vars, config files, build steps)
70
+ - A codebase convention not documented elsewhere (naming patterns, file organization)
71
+ - A gotcha you had to work around (unexpected failures, ordering dependencies)
72
+ - Files that must be modified together (route + model + migration + test)
73
+
74
+ **Do NOT emit generic advice** like "write tests" or "handle errors properly."
75
+
76
+ ## Communication Style
77
+
78
+ Write in a professional, direct tone. Do NOT open messages with filler words or pleasantries like "Perfect!", "Great!", "Awesome!", "Sure!", "Absolutely!". Start with the substance — what you did, what you found, or what you need. Be concise and informative.