@mestreyoda/fabrica 0.2.18 → 0.2.19

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.
@@ -42,7 +42,7 @@ fi
42
42
 
43
43
  The `.worktrees/` directory sits NEXT TO the repo folder (not inside it). This keeps the main checkout clean for the orchestrator and other workers. If the assigned worktree already exists from a previous task on the same branch, verify it's clean and reuse it.
44
44
 
45
- Never create or implement the project under `~/.openclaw/workspace/<slug>` unless the task message explicitly says that directory is the canonical repo path. Once you are in the assigned worktree, stay there for the rest of the task and do not switch back to the main checkout.
45
+ Never create or implement the project under `~/.openclaw/workspace/<slug>` unless the task message explicitly says that directory is the canonical repo path. If the repo already contains scaffolded files, do not re-initialize the project with `npm init`, `uv init`, `cargo init`, or a second skeleton generator — keep the existing stack and modify the scaffold inside the assigned worktree. Once you are in the assigned worktree, stay there for the rest of the task and do not switch back to the main checkout.
46
46
 
47
47
  ### 2. Implement the changes
48
48
 
@@ -115,16 +115,16 @@ When your task message includes a **PR Feedback** section, it means a reviewer r
115
115
 
116
116
  1. Check out the existing branch from the PR (the branch name is in the feedback context)
117
117
  2. If a worktree already exists for that branch, `cd` into it
118
- 3. If not, create a worktree from the existing remote branch:
118
+ 3. If not, create a local worktree that tracks the existing remote branch:
119
119
  ```bash
120
- REPO_ROOT="$(git rev-parse --show-toplevel)"
120
+ REPO_ROOT="/absolute/path/from-task-message"
121
121
  BRANCH="<branch-from-pr>"
122
122
  WORKTREE="${REPO_ROOT}.worktrees/${BRANCH}"
123
123
  git fetch origin "$BRANCH"
124
- git worktree add "$WORKTREE" "origin/$BRANCH"
124
+ git worktree add -b "$BRANCH" "$WORKTREE" "origin/$BRANCH"
125
125
  cd "$WORKTREE"
126
126
  ```
127
- 4. Address **only** the reviewer's comments — do not re-implement the original issue from scratch
127
+ 4. Address **only** the reviewer's comments on that same PR branch — do not switch to a new canonical issue branch and do not re-implement the original issue from scratch
128
128
  5. Commit and push to the **same branch** — the existing PR updates automatically
129
129
  6. End your response with the canonical developer result line described below
130
130
 
package/dist/index.js CHANGED
@@ -113905,8 +113905,8 @@ import fsSync from "node:fs";
113905
113905
  import path5 from "node:path";
113906
113906
  import { fileURLToPath as fileURLToPath3 } from "node:url";
113907
113907
  function getCurrentVersion() {
113908
- if ("0.2.18") {
113909
- return "0.2.18";
113908
+ if ("0.2.19") {
113909
+ return "0.2.19";
113910
113910
  }
113911
113911
  try {
113912
113912
  const pkgPath = path5.join(THIS_DIR, "..", "..", "package.json");
@@ -128040,6 +128040,9 @@ async function resolveEffectiveModelForGateway(requested, runCommand) {
128040
128040
 
128041
128041
  // lib/dispatch/message-builder.ts
128042
128042
  init_roles();
128043
+ function toBranchSlug(value) {
128044
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "task";
128045
+ }
128043
128046
  function buildTaskMessage(opts) {
128044
128047
  const {
128045
128048
  projectName,
@@ -128054,6 +128057,8 @@ function buildTaskMessage(opts) {
128054
128057
  } = opts;
128055
128058
  const repoDisplay = repo;
128056
128059
  const isFeedbackCycle = !!opts.prFeedback;
128060
+ const branchName = opts.prFeedback?.branchName?.trim() ? opts.prFeedback.branchName.trim() : `feature/${issueId}-${toBranchSlug(projectName)}`;
128061
+ const worktreePath = `${repoDisplay}.worktrees/${branchName}`;
128057
128062
  const parts = [
128058
128063
  `${role.toUpperCase()} task for project "${projectName}" \u2014 Issue #${issueId}`,
128059
128064
  ``,
@@ -128070,6 +128075,17 @@ ${issueDescription}` : ""
128070
128075
  `> When feedback conflicts with the original description, follow the PR feedback.`
128071
128076
  );
128072
128077
  }
128078
+ parts.push(
128079
+ ``,
128080
+ `## Execution Setup (do this before editing files)`,
128081
+ `Repo: ${repoDisplay} | Base branch: ${baseBranch} | ${issueUrl}`,
128082
+ `Execution path: ${repoDisplay}`,
128083
+ `Required branch: ${branchName}`,
128084
+ `Required worktree: ${worktreePath}`,
128085
+ `Before editing any file, create or reuse the required worktree above and work only there.`,
128086
+ `Do not re-initialize or replace the scaffold in the main checkout (for example: do not run npm init, cargo init, uv init, or create a second project skeleton) when the repo already contains scaffolded files. Modify the existing scaffold inside the worktree instead.`,
128087
+ `If the repo path is missing or inaccessible, return the canonical blocked result instead of improvising in ~/.openclaw/workspace.`
128088
+ );
128073
128089
  if (opts.followUpPrRequired) {
128074
128090
  parts.push(
128075
128091
  ``,
@@ -128115,10 +128131,7 @@ ${issueDescription}` : ""
128115
128131
  if (opts.attachmentContext) parts.push(opts.attachmentContext);
128116
128132
  parts.push(
128117
128133
  ``,
128118
- `Repo: ${repoDisplay} | Branch: ${baseBranch} | ${issueUrl}`,
128119
- `Project: ${projectName} | Channel: ${channelId}`,
128120
- `Execution path: ${repoDisplay}`,
128121
- `Start by changing into the canonical repo path above before creating or reusing a worktree. Do not create or implement the project under ~/.openclaw/workspace unless the repo path itself points there.`
128134
+ `Project: ${projectName} | Channel: ${channelId}`
128122
128135
  );
128123
128136
  parts.push(...buildCompletionContract(role));
128124
128137
  return parts.join("\n");
@@ -128136,6 +128149,8 @@ function buildConflictFixMessage(opts) {
128136
128149
  prFeedback
128137
128150
  } = opts;
128138
128151
  const repoDisplay = repo;
128152
+ const branchName = prFeedback.branchName?.trim() ? prFeedback.branchName.trim() : `feature/${issueId}-${toBranchSlug(projectName)}`;
128153
+ const worktreePath = `${repoDisplay}.worktrees/${branchName}`;
128139
128154
  const parts = [
128140
128155
  `${role.toUpperCase()} task for project "${projectName}" \u2014 Issue #${issueId}`,
128141
128156
  ``,
@@ -128146,10 +128161,12 @@ function buildConflictFixMessage(opts) {
128146
128161
  parts.push(...formatPrFeedback(prFeedback, baseBranch));
128147
128162
  parts.push(
128148
128163
  ``,
128149
- `Repo: ${repoDisplay} | Branch: ${baseBranch} | ${issueUrl}`,
128164
+ `Repo: ${repoDisplay} | Base branch: ${baseBranch} | ${issueUrl}`,
128150
128165
  `Project: ${projectName} | Channel: ${channelId}`,
128151
128166
  `Execution path: ${repoDisplay}`,
128152
- `Start by changing into the canonical repo path above before reusing the PR branch or creating its worktree. Do not resolve the issue inside ~/.openclaw/workspace unless the repo path itself points there.`
128167
+ `Required branch: ${branchName}`,
128168
+ `Required worktree: ${worktreePath}`,
128169
+ `Start by changing into the canonical repo path above before reusing the PR branch or creating its worktree. Reuse the exact PR branch named above; do not switch to a new canonical issue branch during a feedback cycle. Do not resolve the issue inside ~/.openclaw/workspace unless the repo path itself points there.`
128153
128170
  );
128154
128171
  parts.push(...buildCompletionContract(role));
128155
128172
  return parts.join("\n");
@@ -138138,7 +138155,7 @@ var EXPRESS_GATES = {
138138
138155
  };
138139
138156
  var NODE_CLI_GATES = {
138140
138157
  lint: "npm run lint",
138141
- types: "npm run build -- --noEmit",
138158
+ types: "npm run typecheck",
138142
138159
  security: "npm audit --audit-level=moderate",
138143
138160
  tests: "npm test",
138144
138161
  coverage: "npm run coverage"
@@ -139047,7 +139064,6 @@ fi
139047
139064
  }
139048
139065
 
139049
139066
  // lib/intake/steps/scaffold.ts
139050
- var PYTHON_STACKS3 = /* @__PURE__ */ new Set(["python-cli", "fastapi", "flask", "django"]);
139051
139067
  var scaffoldStep = {
139052
139068
  name: "scaffold",
139053
139069
  shouldRun: (payload) => payload.impact?.is_greenfield === true && !payload.dry_run,
@@ -139073,7 +139089,7 @@ var scaffoldStep = {
139073
139089
  mode: "scaffold",
139074
139090
  runCommand: ctx.runCommand
139075
139091
  });
139076
- if (scaffold.stack && PYTHON_STACKS3.has(scaffold.stack) && payload.spec) {
139092
+ if (scaffold.stack && payload.spec) {
139077
139093
  try {
139078
139094
  const contract = generateQaContract({
139079
139095
  spec: payload.spec,
@@ -348,7 +348,7 @@ $objective
348
348
 
349
349
  \`\`\`bash
350
350
  $(case "$stack" in
351
- nextjs|express) echo "npm install" ;;
351
+ nextjs|express|node-cli) echo "npm install" ;;
352
352
  fastapi|flask|django) echo "python -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt" ;;
353
353
  python-cli) echo "python -m venv .venv && source .venv/bin/activate && pip install -e '.[dev]'" ;;
354
354
  esac)
@@ -360,6 +360,7 @@ esac)
360
360
  $(case "$stack" in
361
361
  nextjs) echo "npm run dev" ;;
362
362
  express) echo "npm run dev" ;;
363
+ node-cli) echo "npm run dev -- \"Hello World CLI\"" ;;
363
364
  fastapi) echo "uvicorn app.main:app --reload" ;;
364
365
  flask) echo "flask run --debug" ;;
365
366
  django) echo "python manage.py runserver" ;;
@@ -460,21 +461,26 @@ scaffold_node_cli() {
460
461
  "scripts": {
461
462
  "dev": "tsx src/index.ts",
462
463
  "build": "tsc",
464
+ "typecheck": "tsc --noEmit",
463
465
  "start": "node dist/index.js",
464
- "lint": "eslint src/",
466
+ "lint": "eslint .",
465
467
  "test": "vitest run",
466
- "test:watch": "vitest"
468
+ "test:watch": "vitest",
469
+ "coverage": "vitest run --coverage --coverage.thresholds.lines=80"
467
470
  },
468
471
  "dependencies": {
469
472
  "commander": "^14.0.0"
470
473
  },
471
474
  "devDependencies": {
475
+ "@eslint/js": "^9.0.0",
472
476
  "@types/node": "^22.0.0",
477
+ "@vitest/coverage-v8": "^3.0.0",
473
478
  "eslint": "^9.0.0",
474
- "typescript": "^5.7.0",
479
+ "globals": "^15.0.0",
475
480
  "tsx": "^4.0.0",
476
- "vitest": "^3.0.0",
477
- "@vitest/coverage-v8": "^3.0.0"
481
+ "typescript": "^5.7.0",
482
+ "typescript-eslint": "^8.0.0",
483
+ "vitest": "^3.0.0"
478
484
  }
479
485
  }
480
486
  EOF
@@ -489,62 +495,127 @@ EOF
489
495
  "module": "ESNext",
490
496
  "moduleResolution": "bundler",
491
497
  "outDir": "dist",
492
- "rootDir": "src",
498
+ "rootDir": ".",
493
499
  "strict": true,
494
500
  "esModuleInterop": true,
495
501
  "skipLibCheck": true,
496
502
  "resolveJsonModule": true,
497
- "declaration": true
503
+ "declaration": true,
504
+ "types": ["node"]
498
505
  },
499
- "include": ["src"],
500
- "exclude": ["node_modules", "dist", "tests"]
506
+ "include": ["src", "tests"],
507
+ "exclude": ["node_modules", "dist"]
501
508
  }
502
509
  EOF
503
510
  FILES_CREATED+=("tsconfig.json")
504
511
 
512
+ cat > eslint.config.mjs <<'EOF'
513
+ import js from '@eslint/js';
514
+ import globals from 'globals';
515
+ import tseslint from 'typescript-eslint';
516
+
517
+ export default tseslint.config(
518
+ js.configs.recommended,
519
+ ...tseslint.configs.recommended,
520
+ {
521
+ files: ['src/**/*.ts', 'tests/**/*.ts'],
522
+ languageOptions: {
523
+ globals: {
524
+ ...globals.node,
525
+ },
526
+ },
527
+ rules: {
528
+ 'no-console': 'off',
529
+ },
530
+ },
531
+ );
532
+ EOF
533
+ FILES_CREATED+=("eslint.config.mjs")
534
+
505
535
  mkdir -p src
506
536
  cat > src/index.ts <<'EOF'
507
537
  #!/usr/bin/env node
508
538
  import { Command } from 'commander';
539
+ import { pathToFileURL } from 'node:url';
540
+
541
+ export function toKebabCase(input: string): string {
542
+ return input
543
+ .trim()
544
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
545
+ .replace(/[\s_]+/g, '-')
546
+ .replace(/-+/g, '-')
547
+ .replace(/^-|-$/g, '')
548
+ .toLowerCase();
549
+ }
550
+
551
+ export function createProgram(): Command {
552
+ return new Command()
553
+ .name(process.env.npm_package_name ?? 'cli')
554
+ .version(process.env.npm_package_version ?? '0.1.0')
555
+ .description('Convert a text argument to kebab-case')
556
+ .argument('<text>', 'text to convert')
557
+ .action((text: string) => {
558
+ console.log(toKebabCase(text));
559
+ });
560
+ }
509
561
 
510
- const program = new Command();
562
+ export function main(argv = process.argv): void {
563
+ createProgram().parse(argv);
564
+ }
511
565
 
512
- program
513
- .name(process.env.npm_package_name ?? 'cli')
514
- .version(process.env.npm_package_version ?? '0.1.0')
515
- .description('CLI tool')
516
- .argument('[args...]', 'arguments')
517
- .action((args: string[]) => {
518
- console.log('Hello from CLI', args.length ? args.join(' ') : '');
519
- });
566
+ const isDirectRun = process.argv[1]
567
+ ? import.meta.url === pathToFileURL(process.argv[1]).href
568
+ : false;
520
569
 
521
- program.parse();
570
+ if (isDirectRun) {
571
+ main();
572
+ }
522
573
  EOF
523
574
  FILES_CREATED+=("src/index.ts")
524
575
 
525
576
  mkdir -p tests
526
577
  cat > tests/main.test.ts <<'EOF'
527
- import { describe, it, expect } from 'vitest';
528
578
  import { execFileSync } from 'node:child_process';
529
- import { resolve } from 'node:path';
579
+ import { fileURLToPath } from 'node:url';
580
+ import { afterEach, describe, expect, it, vi } from 'vitest';
581
+ import { createProgram, main, toKebabCase } from '../src/index.js';
530
582
 
531
- const cli = resolve(__dirname, '../src/index.ts');
583
+ const cli = fileURLToPath(new URL('../src/index.ts', import.meta.url));
532
584
 
533
- function run(...args: string[]): string {
534
- return execFileSync('npx', ['tsx', cli, ...args], {
535
- encoding: 'utf-8',
536
- env: { ...process.env, NODE_NO_WARNINGS: '1' },
537
- }).trim();
538
- }
585
+ describe('toKebabCase', () => {
586
+ it('converts text with spaces and casing', () => {
587
+ expect(toKebabCase('Hello World CLI')).toBe('hello-world-cli');
588
+ expect(toKebabCase('Some_Mixed Case Text')).toBe('some-mixed-case-text');
589
+ expect(toKebabCase(' already-kebab ')).toBe('already-kebab');
590
+ });
591
+ });
592
+
593
+ describe('main', () => {
594
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
595
+
596
+ afterEach(() => {
597
+ logSpy.mockClear();
598
+ });
599
+
600
+ it('prints the kebab-case output', () => {
601
+ main(['node', 'hyphenator-cli', 'Hello World Example']);
602
+ expect(logSpy).toHaveBeenCalledWith('hello-world-example');
603
+ });
539
604
 
540
- describe('CLI', () => {
541
- it('should print hello message', () => {
542
- const output = run();
543
- expect(output).toContain('Hello from CLI');
605
+ it('builds a command with metadata', () => {
606
+ const program = createProgram();
607
+ expect(program.name()).toBeTruthy();
608
+ expect(program.description()).toContain('kebab-case');
544
609
  });
610
+ });
611
+
612
+ describe('CLI smoke', () => {
613
+ it('shows version with --version', () => {
614
+ const output = execFileSync('npx', ['tsx', cli, '--version'], {
615
+ encoding: 'utf-8',
616
+ env: { ...process.env, NODE_NO_WARNINGS: '1' },
617
+ }).trim();
545
618
 
546
- it('should show version with --version', () => {
547
- const output = run('--version');
548
619
  expect(output).toMatch(/\d+\.\d+\.\d+/);
549
620
  });
550
621
  });
@@ -559,23 +630,19 @@ echo "=== QA Gate ==="
559
630
  FAIL=0
560
631
 
561
632
  echo "--- Lint ---"
562
- npx eslint src/ 2>&1 || { echo "LINT FAILED"; FAIL=1; }
633
+ npm run lint 2>&1 || { echo "LINT FAILED"; FAIL=1; }
563
634
 
564
635
  echo "--- TypeScript ---"
565
- npx tsc --noEmit 2>&1 || { echo "TSC FAILED"; FAIL=1; }
636
+ npm run typecheck 2>&1 || { echo "TSC FAILED"; FAIL=1; }
566
637
 
567
638
  echo "--- Tests ---"
568
- npx vitest run 2>&1 || { echo "TESTS FAILED"; FAIL=1; }
639
+ npm test 2>&1 || { echo "TESTS FAILED"; FAIL=1; }
569
640
 
570
641
  echo "--- Coverage (>=80%) ---"
571
- npx vitest run --coverage --coverage.thresholds.lines=80 2>&1 || { echo "COVERAGE FAILED"; FAIL=1; }
642
+ npm run coverage 2>&1 || { echo "COVERAGE FAILED"; FAIL=1; }
572
643
 
573
- echo "--- Secrets scan ---"
574
- if grep -rn 'password\s*=\s*"[^"]\+"\|api_key\s*=\s*"[^"]\+"\|secret\s*=\s*"[^"]\+"' --include="*.ts" --include="*.js" src/ 2>/dev/null; then
575
- echo "SECRETS FOUND — FAIL"; FAIL=1
576
- else
577
- echo "No hardcoded secrets found"
578
- fi
644
+ echo "--- Security audit ---"
645
+ npm audit --audit-level=moderate 2>&1 || { echo "AUDIT FAILED"; FAIL=1; }
579
646
 
580
647
  exit $FAIL
581
648
  QAEOF
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mestreyoda/fabrica",
3
- "version": "0.2.18",
3
+ "version": "0.2.19",
4
4
  "description": "Autonomous software engineering pipeline for OpenClaw. Turns ideas into deployed code via intake, dispatch, review, test, and merge.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",