From 09b7ddf6d057dd4029d07e1208da1c0b71840915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Tue, 26 May 2026 17:11:07 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20add=20developer=20skill=20=E2=80=94=20c?= =?UTF-8?q?oding=20conventions=20+=20architecture=20guide?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds 'uwf skill developer' for contributors to the workflow engine. Covers: monorepo structure, dependency layers, functional-first conventions, error handling, logging with tagged logger, development workflow, testing, publishing, key modules (moderator, extract pipeline, createAgent). Refs #541 --- .../cli-workflow/src/__tests__/skill.test.ts | 12 ++ packages/cli-workflow/src/cli.ts | 8 + packages/cli-workflow/src/commands/skill.ts | 2 + .../workflow-util/src/developer-reference.ts | 140 ++++++++++++++++++ packages/workflow-util/src/index.ts | 1 + 5 files changed, 163 insertions(+) create mode 100644 packages/workflow-util/src/developer-reference.ts diff --git a/packages/cli-workflow/src/__tests__/skill.test.ts b/packages/cli-workflow/src/__tests__/skill.test.ts index 9a5244e..f2fe69e 100644 --- a/packages/cli-workflow/src/__tests__/skill.test.ts +++ b/packages/cli-workflow/src/__tests__/skill.test.ts @@ -10,6 +10,7 @@ import { cmdSkillArchitecture, cmdSkillAuthor, cmdSkillCli, + cmdSkillDeveloper, cmdSkillList, cmdSkillModerator, cmdSkillUser, @@ -27,6 +28,7 @@ describe("skill commands", () => { expect(result).toContain("actor"); expect(result).toContain("user"); expect(result).toContain("author"); + expect(result).toContain("developer"); for (const name of result) { expect(name).toMatch(/^\S+$/); } @@ -97,6 +99,15 @@ describe("skill commands", () => { expect(result.length).toBeGreaterThan(500); }); + test("skill developer returns non-empty markdown string", () => { + const result = cmdSkillDeveloper(); + expect(typeof result).toBe("string"); + expect(result).toContain("Monorepo"); + expect(result).toContain("CAS"); + expect(result).toContain("Biome"); + expect(result.length).toBeGreaterThan(500); + }); + test("skill help subcommand is suppressed", () => { const output = execFileSync("bun", ["src/cli.ts", "skill", "--help"], { cwd: join(__dirname, "..", ".."), @@ -111,6 +122,7 @@ describe("skill commands", () => { expect(output).toContain("actor"); expect(output).toContain("user"); expect(output).toContain("author"); + expect(output).toContain("developer"); expect(output).toContain("list"); }); }); diff --git a/packages/cli-workflow/src/cli.ts b/packages/cli-workflow/src/cli.ts index 6876abf..55ba6e8 100755 --- a/packages/cli-workflow/src/cli.ts +++ b/packages/cli-workflow/src/cli.ts @@ -21,6 +21,7 @@ import { cmdSkillArchitecture, cmdSkillAuthor, cmdSkillCli, + cmdSkillDeveloper, cmdSkillList, cmdSkillModerator, cmdSkillUser, @@ -520,6 +521,13 @@ skill console.log(cmdSkillAuthor()); }); +skill + .command("developer") + .description("Print the developer reference (coding conventions + architecture)") + .action(() => { + console.log(cmdSkillDeveloper()); + }); + skill .command("moderator") .description("Print the moderator reference") diff --git a/packages/cli-workflow/src/commands/skill.ts b/packages/cli-workflow/src/commands/skill.ts index 9c06808..fb59fae 100644 --- a/packages/cli-workflow/src/commands/skill.ts +++ b/packages/cli-workflow/src/commands/skill.ts @@ -3,6 +3,7 @@ export { generateArchitectureReference as cmdSkillArchitecture, generateAuthorReference as cmdSkillAuthor, generateCliReference as cmdSkillCli, + generateDeveloperReference as cmdSkillDeveloper, generateModeratorReference as cmdSkillModerator, generateUserReference as cmdSkillUser, generateYamlReference as cmdSkillYaml, @@ -16,6 +17,7 @@ const SKILL_NAMES = [ "actor", "user", "author", + "developer", ] as const; export function cmdSkillList(): ReadonlyArray { diff --git a/packages/workflow-util/src/developer-reference.ts b/packages/workflow-util/src/developer-reference.ts new file mode 100644 index 0000000..9409553 --- /dev/null +++ b/packages/workflow-util/src/developer-reference.ts @@ -0,0 +1,140 @@ +export function generateDeveloperReference(): string { + return `# Developer Reference + +Guide for contributing to the workflow engine codebase. + +## Monorepo Structure + +\`\`\` +packages/ + workflow-protocol/ # Shared types (WorkflowPayload, StepNodePayload, etc.) + workflow-util/ # Base32, ULID, logger, frontmatter parsing, skill references + workflow-util-agent/ # createAgent factory, context builder, extract pipeline + workflow-agent-hermes/ # uwf-hermes CLI (spawns Hermes chat sessions) + workflow-agent-builtin/ # uwf-builtin CLI (direct LLM calls via OpenAI API) + cli-workflow/ # uwf CLI (moderator, thread/step/cas/config commands) +\`\`\` + +Dependency layers (each only imports from packages above it): +\`\`\` +protocol → util → util-agent → agent-hermes / agent-builtin / cli-workflow +\`\`\` + +External CAS: \`@uncaged/json-cas\` (store API, hashing, schema validation) + \`@uncaged/json-cas-fs\` (filesystem backend). + +## Coding Conventions + +### Functional-first + +| Rule | Description | +|------|-------------| +| \`type\` over \`interface\` | All type definitions use \`type\` | +| \`function\` over \`class\` | Pure functions + closures, no class | +| No \`this\` | Functions must not depend on \`this\` context | +| No inheritance | No \`extends\`, \`implements\`, \`abstract\` | +| No optional properties | Use \`T \\| null\` instead of \`?:\` | +| Immutability first | Use \`Readonly\`, \`as const\`, avoid mutation | + +Classes allowed only when required by third-party libraries or for Error subclasses. + +### Error Handling + +- \`Result\` type for expected failures (\`ok\`/\`err\` constructors from \`@uncaged/workflow-util\`) +- \`throw\` only for unrecoverable bugs +- No try-catch for flow control + +### Async + +Always \`async/await\`, never \`.then()\` chains. + +### Logging + +\`console.*\` is banned (Biome \`noConsole\` rule). Use the structured logger: + +\`\`\`typescript +import { createLogger } from "@uncaged/workflow-util"; +const log = createLogger(); +log("4KNMR2PX", "Loading workflow..."); // 8-char Crockford Base32 tag +\`\`\` + +Each call site gets a unique hand-written tag. \`grep "4KNMR2PX"\` in logs → instant code location. + +CLI package (\`@uncaged/cli-workflow\`) may use \`console.log\` for user-facing output with a biome-ignore comment. + +### No Dynamic Import + +No \`await import()\` in production code. Always static top-level \`import\`. Test files are exempt. + +### Naming + +- Workflow names: verb-first kebab-case (\`solve-issue\`, \`review-code\`) +- IDs: Crockford Base32 — CAS hash (XXH64, 13-char), Thread ID (ULID, 26-char) + +## Development Workflow + +\`\`\`bash +bun install # install all workspace deps +bun run build # tsc --build (all packages) +bun run check # tsc + biome check + lint-log-tags +bun run format # biome format --write +bun test # run all tests +\`\`\` + +Before committing: \`bun run check\` + \`bun test\` must both pass. + +### Testing + +- \`cli-workflow\`: vitest +- Other packages: \`bun test\` +- Test files live in \`__tests__/\` directories + +### Publishing + +Fixed-mode versioning — all \`@uncaged/*\` packages share the same version number. + +\`\`\`bash +bun changeset # describe the change +bun version # bump versions + changelogs +bun release # build + test + publish to npmjs +\`\`\` + +## Key Modules + +### Moderator (\`cli-workflow/src/moderator/\`) + +Status-based graph evaluator. Reads \`graph[lastRole][output.$status]\` to determine the next role. Zero LLM cost. + +### Extract Pipeline (\`workflow-util-agent/src/\`) + +1. Agent produces frontmatter markdown +2. \`parseFrontmatterMarkdown()\` extracts YAML frontmatter +3. \`tryFrontmatterFastPath()\` validates against role's output schema +4. If fast path fails, retries up to 2 times via agent continue +5. Validated output stored as CAS node + +### createAgent Factory (\`workflow-util-agent/src/run.ts\`) + +Shared entry point for all agent CLIs. Handles: +- Argument parsing (\`--thread\`, \`--role\`, \`--prompt\`) +- Context building (thread history, workflow definition) +- Output extraction and CAS persistence +- Frontmatter retry loop + +### CAS Integration + +All data is CAS-addressed via \`@uncaged/json-cas\`: +- \`store.put(schemaHash, data)\` → content hash +- \`store.get(hash)\` → node +- \`validate(store, node)\` → schema check +- Schemas registered at workflow add time + +## Commit Convention + +\`\`\` +(): + +type: feat | fix | refactor | docs | chore | test +scope: workflow | cli | moderator | agent-kit | hermes | util | protocol +\`\`\` +`; +} diff --git a/packages/workflow-util/src/index.ts b/packages/workflow-util/src/index.ts index 770177a..76ea7f0 100644 --- a/packages/workflow-util/src/index.ts +++ b/packages/workflow-util/src/index.ts @@ -3,6 +3,7 @@ export { generateArchitectureReference } from "./architecture-reference.js"; export { generateAuthorReference } from "./author-reference.js"; export { encodeUint64AsCrockford } from "./base32.js"; export { generateCliReference } from "./cli-reference.js"; +export { generateDeveloperReference } from "./developer-reference.js"; export { env } from "./env.js"; export type { AgentFrontmatter,