@starlein/paperclip-plugin-company-wizard 0.4.5-a → 0.4.6

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/CHANGELOG.md CHANGED
@@ -4,6 +4,32 @@ All notable changes to the Company Wizard plugin are documented here.
4
4
 
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
+ ## [0.4.6] - 2026-06-18
8
+
9
+ ### Changed
10
+
11
+ - **Docker path auto-detection for companiesDir and templatesPath.** The plugin now detects Docker vs. NPX installations automatically: if `~/instances` exists (Docker, where HOME=/paperclip), paths default to `~/instances/default/companies` and `~/plugin-templates`; otherwise (NPX/local), paths default to `~/.paperclip/instances/default/companies` and `~/.paperclip/plugin-templates`. Both settings are now rarely needed — the plugin picks the right layout automatically. (`src/worker.ts`, `isDockerLayout()`, `resolveWritableCompaniesDir`, `ensureTemplatesDir`.)
12
+ - **Removed `disableBoardApprovalOnNewCompanies` setting.** This setting was never used in practice — board approval governance is always preserved for new companies. The setting has been removed from the manifest and the worker no longer reads or applies it. (`src/manifest.ts`, `src/worker.ts`.)
13
+ - **Direct assignment flow — backlog grooming assigns issues at creation, not via routine sweep.** The backlog-health skill now instructs the PM to assign each issue to its best-fit agent as it is created. The auto-assign routine is reframed as a low-frequency safety net (every 4 hours) that catches stragglers, not the primary dispatch path. (`backlog` module, `auto-assign` module.)
14
+ - **Engineer hands off to Product Owner on completion (without pr-review).** When the PR-review module is not active and no `executionPolicy` is set, the engineer moves the issue to `in_review` and reassigns to the Product Owner in the same heartbeat. Never leaves finished work in `in_review` assigned to itself. (`engineer` AGENTS.md, HEARTBEAT.md.)
15
+ - **Product Owner reviews `in_review` issues immediately.** When an issue is assigned to the PM in `in_review` and no formal executionPolicy participant is waiting, the PM reviews it against acceptance criteria and sets it `done` or sends it back to the engineer. (`product-owner` AGENTS.md, HEARTBEAT.md.)
16
+ - **Engineer merges feature branches to base when no PR review is active.** The `github-repo` git-workflow skill now has an explicit direct-to-base-ref flow: push branch → checkout base → merge → push base → delete branch. (`github-repo` module.)
17
+ - **Git identity uses real user profile instead of "Paperclip Bootstrap".** The initial empty commit in fresh repositories now uses the board user's name and email (resolved from the Paperclip session) instead of the hardcoded "Paperclip Bootstrap / bootstrap@paperclip.local". Falls back to "Paperclip Bootstrap" when no session is available (local_trusted mode). (`src/api/client.js`, `src/worker.ts`, `src/logic/assemble.js`.)
18
+ - **Engineer claims unassigned engineering work as a fallback.** When no actionable work is assigned and unassigned `todo` issues clearly match engineering, the engineer claims the highest-priority ready issue. (`engineer` AGENTS.md.)
19
+ - **Manifest setting descriptions updated.** `companiesDir` and `templatesPath` now document auto-detection behavior. `templatesRepoUrl` now notes that the default is correct for most setups. (`src/manifest.ts`.)
20
+
21
+ ### Fixed
22
+
23
+ - **Branches no longer dangle unmerged without a PR review module.** The git-workflow skill previously instructed engineers to "push to the correct configured base branch" but did not explain how to merge a feature branch back to base. Without a PR review module, branches were pushed but never merged, leaving `main` stale and the team stuck. The skill now includes explicit merge-and-push steps for the no-review case.
24
+ - **Auto-assign README and heartbeat-section schedule wording.** "Every few hours" was replaced with the actual cron schedule "every 4 hours" to match the `0 */4 * * *` routine. (`auto-assign` module.)
25
+ - **github-repo README now matches the actual direct-to-base-ref skill.** The README previously said "commits directly on the default branch, no branches" but the skill uses a feature branch + merge-to-base flow. (`github-repo` README.)
26
+
27
+ ### Removed
28
+
29
+ - `disableBoardApprovalOnNewCompanies` plugin setting. Board approval governance is always preserved for new companies — this toggle was never used in practice.
30
+
31
+ ---
32
+
7
33
  ## [0.4.5] - 2026-06-16
8
34
 
9
35
  ### Fixed
package/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
 
16
16
  ---
17
17
 
18
- > **Fork:** This is a community-maintained fork of [yesterday-AI/paperclip-plugin-company-wizard](https://github.com/yesterday-AI/paperclip-plugin-company-wizard), updated for the current Paperclip API (`>=2026.529.0`) with substantial bug fixes. End-to-end company setup is governed through current Paperclip workflows as of v0.4.5.
18
+ > **Fork:** This is a community-maintained fork of [yesterday-AI/paperclip-plugin-company-wizard](https://github.com/yesterday-AI/paperclip-plugin-company-wizard), updated for the current Paperclip API (`>=2026.529.0`) with substantial bug fixes. End-to-end company setup is governed through current Paperclip workflows as of v0.4.6.
19
19
 
20
20
  <details>
21
21
  <summary><strong>What changed vs. upstream</strong></summary>
package/dist/manifest.js CHANGED
@@ -2,7 +2,7 @@
2
2
  var manifest = {
3
3
  id: "starlein.paperclip-plugin-company-wizard",
4
4
  apiVersion: 1,
5
- version: "0.4.5",
5
+ version: "0.4.6",
6
6
  displayName: "Company Wizard",
7
7
  description: "AI-powered wizard to bootstrap agent companies from composable templates",
8
8
  author: "Sascha Pietrowski <sp@speednetwork.de>",
@@ -28,16 +28,16 @@ var manifest = {
28
28
  properties: {
29
29
  companiesDir: {
30
30
  type: "string",
31
- description: "Directory where assembled company workspaces are written. Defaults to ~/.paperclip/instances/default/companies. Override for Docker setups (e.g. /paperclip/instances/default/companies)."
31
+ description: "Directory where assembled company workspaces are written. Auto-detected: ~/instances/default/companies in Docker setups, ~/.paperclip/instances/default/companies otherwise. Rarely needs manual override."
32
32
  },
33
33
  templatesPath: {
34
34
  type: "string",
35
- description: "Path to the templates directory. Defaults to ~/.paperclip/plugin-templates (auto-downloaded from templatesRepoUrl if missing). Override for Docker setups (e.g. /paperclip/plugin-templates)."
35
+ description: "Path to the templates directory. Auto-detected: ~/plugin-templates in Docker setups, ~/.paperclip/plugin-templates otherwise. Rarely needs manual override."
36
36
  },
37
37
  templatesRepoUrl: {
38
38
  type: "string",
39
39
  default: "https://github.com/starlein/paperclip-plugin-company-wizard/tree/main/templates",
40
- description: "GitHub tree URL to pull templates from when the templates directory does not exist."
40
+ description: "GitHub tree URL for template downloads. The default is correct for most setups \u2014 only change this if using a custom fork."
41
41
  },
42
42
  anthropicApiKey: {
43
43
  type: "string",
@@ -54,11 +54,6 @@ var manifest = {
54
54
  paperclipPassword: {
55
55
  type: "string",
56
56
  description: "Board login password (for authenticated instances)."
57
- },
58
- disableBoardApprovalOnNewCompanies: {
59
- type: "boolean",
60
- default: false,
61
- description: "Optional. If true, the wizard will PATCH new companies to set requireBoardApprovalForNewAgents=false during provisioning. Leave false to preserve approval-gated hiring policies."
62
57
  }
63
58
  }
64
59
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/manifest.ts"],
4
- "sourcesContent": ["import type { PaperclipPluginManifestV1 } from '@paperclipai/plugin-sdk';\n\nconst manifest: PaperclipPluginManifestV1 = {\n id: 'starlein.paperclip-plugin-company-wizard',\n apiVersion: 1,\n version: '0.4.5',\n displayName: 'Company Wizard',\n description: 'AI-powered wizard to bootstrap agent companies from composable templates',\n author: 'Sascha Pietrowski <sp@speednetwork.de>',\n categories: ['workspace', 'ui'],\n capabilities: [\n 'companies.read',\n 'issues.create',\n 'issues.read',\n 'issues.update',\n 'goals.create',\n 'goals.read',\n 'agents.read',\n 'projects.read',\n 'plugin.state.read',\n 'plugin.state.write',\n 'secrets.read-ref',\n 'events.subscribe',\n 'ui.page.register',\n 'ui.sidebar.register',\n ],\n instanceConfigSchema: {\n type: 'object',\n properties: {\n companiesDir: {\n type: 'string',\n description:\n 'Directory where assembled company workspaces are written. Defaults to ~/.paperclip/instances/default/companies. Override for Docker setups (e.g. /paperclip/instances/default/companies).',\n },\n templatesPath: {\n type: 'string',\n description:\n 'Path to the templates directory. Defaults to ~/.paperclip/plugin-templates (auto-downloaded from templatesRepoUrl if missing). Override for Docker setups (e.g. /paperclip/plugin-templates).',\n },\n templatesRepoUrl: {\n type: 'string',\n default: 'https://github.com/starlein/paperclip-plugin-company-wizard/tree/main/templates',\n description:\n 'GitHub tree URL to pull templates from when the templates directory does not exist.',\n },\n anthropicApiKey: {\n type: 'string',\n description:\n 'Anthropic API key for the AI wizard (e.g. sk-ant-...). Required to use the AI-powered company setup path.',\n },\n paperclipUrl: {\n type: 'string',\n description:\n 'Paperclip instance URL. Defaults to http://localhost:3100 or the PAPERCLIP_PUBLIC_URL env var.',\n },\n paperclipEmail: {\n type: 'string',\n description: 'Board login email (for authenticated instances).',\n },\n paperclipPassword: {\n type: 'string',\n description: 'Board login password (for authenticated instances).',\n },\n disableBoardApprovalOnNewCompanies: {\n type: 'boolean',\n default: false,\n description:\n 'Optional. If true, the wizard will PATCH new companies to set requireBoardApprovalForNewAgents=false during provisioning. Leave false to preserve approval-gated hiring policies.',\n },\n },\n },\n entrypoints: {\n worker: './dist/worker.js',\n ui: './dist/ui',\n },\n ui: {\n slots: [\n {\n type: 'page',\n id: 'company-wizard',\n displayName: 'Company Wizard',\n exportName: 'WizardPage',\n routePath: 'company-creator',\n },\n {\n type: 'sidebar',\n id: 'company-wizard-link',\n displayName: 'Create Company',\n exportName: 'SidebarLink',\n },\n ],\n },\n};\n\nexport default manifest;\n"],
5
- "mappings": ";AAEA,IAAM,WAAsC;AAAA,EAC1C,IAAI;AAAA,EACJ,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,aAAa;AAAA,EACb,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,YAAY,CAAC,aAAa,IAAI;AAAA,EAC9B,cAAc;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,sBAAsB;AAAA,IACpB,MAAM;AAAA,IACN,YAAY;AAAA,MACV,cAAc;AAAA,QACZ,MAAM;AAAA,QACN,aACE;AAAA,MACJ;AAAA,MACA,eAAe;AAAA,QACb,MAAM;AAAA,QACN,aACE;AAAA,MACJ;AAAA,MACA,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,SAAS;AAAA,QACT,aACE;AAAA,MACJ;AAAA,MACA,iBAAiB;AAAA,QACf,MAAM;AAAA,QACN,aACE;AAAA,MACJ;AAAA,MACA,cAAc;AAAA,QACZ,MAAM;AAAA,QACN,aACE;AAAA,MACJ;AAAA,MACA,gBAAgB;AAAA,QACd,MAAM;AAAA,QACN,aAAa;AAAA,MACf;AAAA,MACA,mBAAmB;AAAA,QACjB,MAAM;AAAA,QACN,aAAa;AAAA,MACf;AAAA,MACA,oCAAoC;AAAA,QAClC,MAAM;AAAA,QACN,SAAS;AAAA,QACT,aACE;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AAAA,EACA,aAAa;AAAA,IACX,QAAQ;AAAA,IACR,IAAI;AAAA,EACN;AAAA,EACA,IAAI;AAAA,IACF,OAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,aAAa;AAAA,QACb,YAAY;AAAA,QACZ,WAAW;AAAA,MACb;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,aAAa;AAAA,QACb,YAAY;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,mBAAQ;",
4
+ "sourcesContent": ["import type { PaperclipPluginManifestV1 } from '@paperclipai/plugin-sdk';\n\nconst manifest: PaperclipPluginManifestV1 = {\n id: 'starlein.paperclip-plugin-company-wizard',\n apiVersion: 1,\n version: '0.4.6',\n displayName: 'Company Wizard',\n description: 'AI-powered wizard to bootstrap agent companies from composable templates',\n author: 'Sascha Pietrowski <sp@speednetwork.de>',\n categories: ['workspace', 'ui'],\n capabilities: [\n 'companies.read',\n 'issues.create',\n 'issues.read',\n 'issues.update',\n 'goals.create',\n 'goals.read',\n 'agents.read',\n 'projects.read',\n 'plugin.state.read',\n 'plugin.state.write',\n 'secrets.read-ref',\n 'events.subscribe',\n 'ui.page.register',\n 'ui.sidebar.register',\n ],\n instanceConfigSchema: {\n type: 'object',\n properties: {\n companiesDir: {\n type: 'string',\n description:\n 'Directory where assembled company workspaces are written. Auto-detected: ~/instances/default/companies in Docker setups, ~/.paperclip/instances/default/companies otherwise. Rarely needs manual override.',\n },\n templatesPath: {\n type: 'string',\n description:\n 'Path to the templates directory. Auto-detected: ~/plugin-templates in Docker setups, ~/.paperclip/plugin-templates otherwise. Rarely needs manual override.',\n },\n templatesRepoUrl: {\n type: 'string',\n default: 'https://github.com/starlein/paperclip-plugin-company-wizard/tree/main/templates',\n description:\n 'GitHub tree URL for template downloads. The default is correct for most setups \u2014 only change this if using a custom fork.',\n },\n anthropicApiKey: {\n type: 'string',\n description:\n 'Anthropic API key for the AI wizard (e.g. sk-ant-...). Required to use the AI-powered company setup path.',\n },\n paperclipUrl: {\n type: 'string',\n description:\n 'Paperclip instance URL. Defaults to http://localhost:3100 or the PAPERCLIP_PUBLIC_URL env var.',\n },\n paperclipEmail: {\n type: 'string',\n description: 'Board login email (for authenticated instances).',\n },\n paperclipPassword: {\n type: 'string',\n description: 'Board login password (for authenticated instances).',\n },\n },\n },\n entrypoints: {\n worker: './dist/worker.js',\n ui: './dist/ui',\n },\n ui: {\n slots: [\n {\n type: 'page',\n id: 'company-wizard',\n displayName: 'Company Wizard',\n exportName: 'WizardPage',\n routePath: 'company-creator',\n },\n {\n type: 'sidebar',\n id: 'company-wizard-link',\n displayName: 'Create Company',\n exportName: 'SidebarLink',\n },\n ],\n },\n};\n\nexport default manifest;\n"],
5
+ "mappings": ";AAEA,IAAM,WAAsC;AAAA,EAC1C,IAAI;AAAA,EACJ,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,aAAa;AAAA,EACb,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,YAAY,CAAC,aAAa,IAAI;AAAA,EAC9B,cAAc;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,sBAAsB;AAAA,IACpB,MAAM;AAAA,IACN,YAAY;AAAA,MACV,cAAc;AAAA,QACZ,MAAM;AAAA,QACN,aACE;AAAA,MACJ;AAAA,MACA,eAAe;AAAA,QACb,MAAM;AAAA,QACN,aACE;AAAA,MACJ;AAAA,MACA,kBAAkB;AAAA,QAChB,MAAM;AAAA,QACN,SAAS;AAAA,QACT,aACE;AAAA,MACJ;AAAA,MACA,iBAAiB;AAAA,QACf,MAAM;AAAA,QACN,aACE;AAAA,MACJ;AAAA,MACA,cAAc;AAAA,QACZ,MAAM;AAAA,QACN,aACE;AAAA,MACJ;AAAA,MACA,gBAAgB;AAAA,QACd,MAAM;AAAA,QACN,aAAa;AAAA,MACf;AAAA,MACA,mBAAmB;AAAA,QACjB,MAAM;AAAA,QACN,aAAa;AAAA,MACf;AAAA,IACF;AAAA,EACF;AAAA,EACA,aAAa;AAAA,IACX,QAAQ;AAAA,IACR,IAAI;AAAA,EACN;AAAA,EACA,IAAI;AAAA,IACF,OAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,aAAa;AAAA,QACb,YAAY;AAAA,QACZ,WAAW;AAAA,MACb;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,aAAa;AAAA,QACb,YAAY;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,mBAAQ;",
6
6
  "names": []
7
7
  }
package/dist/worker.js CHANGED
@@ -9415,6 +9415,8 @@ async function assembleCompany({
9415
9415
  presetLabels = [],
9416
9416
  enableIsolatedWorktrees = false,
9417
9417
  enableEnrichedPersonas = true,
9418
+ gitUserName,
9419
+ gitUserEmail,
9418
9420
  outputDir,
9419
9421
  templatesDir,
9420
9422
  onProgress = () => {
@@ -10080,7 +10082,9 @@ Read: \`docs/${doc}\`
10080
10082
  if (!workspace.cwd) workspace.cwd = localCwd;
10081
10083
  const trimmedSetup = typeof workspace.setupCommand === "string" ? workspace.setupCommand.trim() : "";
10082
10084
  if (!trimmedSetup || trimmedSetup === "git init -b main" || trimmedSetup === "git init") {
10083
- workspace.setupCommand = "git init -b main && git -c user.email=bootstrap@paperclip.local -c user.name='Paperclip Bootstrap' commit --allow-empty -m 'chore: initialize repository'";
10085
+ const gitName = gitUserName || "Paperclip Bootstrap";
10086
+ const gitEmail = gitUserEmail || "bootstrap@paperclip.local";
10087
+ workspace.setupCommand = `git init -b main && git -c user.email=${gitEmail} -c user.name='${gitName.replace(/'/g, "'\\''")}' commit --allow-empty -m 'chore: initialize repository'`;
10084
10088
  }
10085
10089
  }
10086
10090
  return workspace;
@@ -10449,6 +10453,8 @@ var PaperclipClient = class {
10449
10453
  this.credentials = credentials;
10450
10454
  this.sessionCookie = null;
10451
10455
  this.boardUserId = null;
10456
+ this.boardUserName = null;
10457
+ this.boardUserEmail = null;
10452
10458
  }
10453
10459
  async _fetch(path3, opts = {}) {
10454
10460
  const url = `${this.baseUrl}${path3}`;
@@ -10490,6 +10496,8 @@ var PaperclipClient = class {
10490
10496
  }
10491
10497
  if (res.ok) {
10492
10498
  this.boardUserId = "local-board";
10499
+ this.boardUserName = null;
10500
+ this.boardUserEmail = null;
10493
10501
  return;
10494
10502
  }
10495
10503
  if (res.status === 401 || res.status === 403) {
@@ -10504,6 +10512,8 @@ var PaperclipClient = class {
10504
10512
  try {
10505
10513
  const session = await this._fetch("/api/auth/get-session");
10506
10514
  this.boardUserId = session?.user?.id || null;
10515
+ this.boardUserName = session?.user?.name || null;
10516
+ this.boardUserEmail = session?.user?.email || null;
10507
10517
  } catch {
10508
10518
  }
10509
10519
  return;
@@ -10772,6 +10782,23 @@ var PaperclipClient = class {
10772
10782
  })
10773
10783
  });
10774
10784
  }
10785
+ async listRoutines(companyId, filters = {}) {
10786
+ const params = new URLSearchParams();
10787
+ if (filters.projectId) params.set("projectId", filters.projectId);
10788
+ const query = params.toString();
10789
+ return this._fetch(`/api/companies/${companyId}/routines${query ? `?${query}` : ""}`, {
10790
+ method: "GET"
10791
+ });
10792
+ }
10793
+ async getRoutine(routineId) {
10794
+ return this._fetch(`/api/routines/${routineId}`, { method: "GET" });
10795
+ }
10796
+ async updateRoutine(routineId, updates = {}) {
10797
+ return this._fetch(`/api/routines/${routineId}`, {
10798
+ method: "PATCH",
10799
+ body: JSON.stringify(updates || {})
10800
+ });
10801
+ }
10775
10802
  async createRoutineTrigger(routineId, { kind, cronExpression, timezone }) {
10776
10803
  return this._fetch(`/api/routines/${routineId}/triggers`, {
10777
10804
  method: "POST",
@@ -10782,6 +10809,12 @@ var PaperclipClient = class {
10782
10809
  })
10783
10810
  });
10784
10811
  }
10812
+ async updateRoutineTrigger(triggerId, updates = {}) {
10813
+ return this._fetch(`/api/routine-triggers/${triggerId}`, {
10814
+ method: "PATCH",
10815
+ body: JSON.stringify(updates || {})
10816
+ });
10817
+ }
10785
10818
  async triggerHeartbeat(agentId, { issueId } = {}) {
10786
10819
  return this._fetch(`/api/agents/${agentId}/wakeup`, {
10787
10820
  method: "POST",
@@ -10969,7 +11002,7 @@ var __dirname = path2.dirname(fileURLToPath2(import.meta.url));
10969
11002
  var DEFAULT_TEMPLATES_REPO_URL = "https://github.com/starlein/paperclip-plugin-company-wizard/tree/main/templates";
10970
11003
  var BUNDLED_TEMPLATES_DIR = path2.resolve(__dirname, "..", "templates");
10971
11004
  var PLUGIN_PACKAGE_NAME = "@starlein/paperclip-plugin-company-wizard";
10972
- var CURRENT_PLUGIN_VERSION = "0.4.5";
11005
+ var CURRENT_PLUGIN_VERSION = "0.4.6";
10973
11006
  var NPM_LATEST_URL = "https://registry.npmjs.org/@starlein%2Fpaperclip-plugin-company-wizard/latest";
10974
11007
  function copyDirSync(src, dest) {
10975
11008
  fs2.mkdirSync(dest, { recursive: true });
@@ -11017,6 +11050,9 @@ function downloadTemplatesFromGithub(destDir, githubUrl) {
11017
11050
  }
11018
11051
  }
11019
11052
  }
11053
+ function isDockerLayout() {
11054
+ return fs2.existsSync(path2.join(os.homedir(), "instances"));
11055
+ }
11020
11056
  async function ensureTemplatesDir(cfg) {
11021
11057
  const repoUrl = cfg.templatesRepoUrl || DEFAULT_TEMPLATES_REPO_URL;
11022
11058
  if (cfg.templatesPath) {
@@ -11024,6 +11060,15 @@ async function ensureTemplatesDir(cfg) {
11024
11060
  downloadTemplatesFromGithub(cfg.templatesPath, repoUrl);
11025
11061
  return cfg.templatesPath;
11026
11062
  }
11063
+ if (isDockerLayout()) {
11064
+ const dockerTemplatesDir = path2.join(os.homedir(), "plugin-templates");
11065
+ if (fs2.existsSync(dockerTemplatesDir)) return dockerTemplatesDir;
11066
+ try {
11067
+ downloadTemplatesFromGithub(dockerTemplatesDir, repoUrl);
11068
+ return dockerTemplatesDir;
11069
+ } catch {
11070
+ }
11071
+ }
11027
11072
  const defaultDir = path2.join(os.homedir(), ".paperclip", "plugin-templates");
11028
11073
  if (fs2.existsSync(defaultDir)) return defaultDir;
11029
11074
  try {
@@ -11136,10 +11181,6 @@ function loadTemplates(templatesDir) {
11136
11181
  loadErrors: [...presetLoad.errors, ...moduleLoad.errors, ...roleLoad.errors]
11137
11182
  };
11138
11183
  }
11139
- function cfgBool(cfg, key) {
11140
- const raw = cfg[key];
11141
- return raw === true || typeof raw === "string" && raw.toLowerCase() === "true";
11142
- }
11143
11184
  async function resolveEnableIsolatedWorkspacesFromInstance(cfg, log) {
11144
11185
  const paperclipUrl = cfg.paperclipUrl || process.env.PAPERCLIP_PUBLIC_URL || "http://localhost:3100";
11145
11186
  const paperclipEmail = cfg.paperclipEmail || "";
@@ -11184,11 +11225,12 @@ function resolveWritableCompaniesDir(cfg, log) {
11184
11225
  );
11185
11226
  }
11186
11227
  }
11187
- const candidates = [
11188
- resolveCompaniesDir(cfg),
11189
- path2.join("/paperclip", "instances", "default", "companies"),
11190
- path2.join(os.tmpdir(), "paperclip-companies")
11191
- ];
11228
+ const candidates = [];
11229
+ if (isDockerLayout()) {
11230
+ candidates.push(path2.join(os.homedir(), "instances", "default", "companies"));
11231
+ }
11232
+ candidates.push(resolveCompaniesDir(cfg));
11233
+ candidates.push(path2.join(os.tmpdir(), "paperclip-companies"));
11192
11234
  const attempted = /* @__PURE__ */ new Set();
11193
11235
  let lastError = "";
11194
11236
  for (const candidate of candidates) {
@@ -11215,7 +11257,7 @@ function normalizeGitBranch(value) {
11215
11257
  const branch = typeof value === "string" && value.trim() ? value.trim() : "main";
11216
11258
  return /^[A-Za-z0-9._/-]+$/.test(branch) ? branch.replace(/^origin\//, "") : "main";
11217
11259
  }
11218
- function prepareLocalProjectWorkspace(mainProject, companyDir, log) {
11260
+ function prepareLocalProjectWorkspace(mainProject, companyDir, log, gitIdentity) {
11219
11261
  const workspace = mainProject?.workspace;
11220
11262
  if (!workspace || workspace.sourceType !== "local_path") return;
11221
11263
  const cwd = typeof workspace.cwd === "string" ? workspace.cwd.trim() : "";
@@ -11233,14 +11275,16 @@ function prepareLocalProjectWorkspace(mainProject, companyDir, log) {
11233
11275
  return;
11234
11276
  }
11235
11277
  const branch = normalizeGitBranch(workspace.defaultRef);
11278
+ const gitUserName = gitIdentity?.name || "Paperclip Bootstrap";
11279
+ const gitUserEmail = gitIdentity?.email || "bootstrap@paperclip.local";
11236
11280
  execFileSync("git", ["init", "-b", branch], { cwd: resolvedCwd, stdio: "pipe" });
11237
11281
  execFileSync(
11238
11282
  "git",
11239
11283
  [
11240
11284
  "-c",
11241
- "user.email=bootstrap@paperclip.local",
11285
+ `user.email=${gitUserEmail}`,
11242
11286
  "-c",
11243
- "user.name=Paperclip Bootstrap",
11287
+ `user.name=${gitUserName}`,
11244
11288
  "commit",
11245
11289
  "--allow-empty",
11246
11290
  "-m",
@@ -11307,6 +11351,110 @@ async function syncAgentInstructionsIntoManagedBundle({
11307
11351
  );
11308
11352
  }
11309
11353
  }
11354
+ function routineTemplateTitle(routine) {
11355
+ if (typeof routine?.title === "string" && routine.title.trim()) return routine.title.trim();
11356
+ if (typeof routine?.name === "string" && routine.name.trim()) return routine.name.trim();
11357
+ return "";
11358
+ }
11359
+ async function syncRoutineTrigger({
11360
+ client,
11361
+ routineId,
11362
+ schedule,
11363
+ log
11364
+ }) {
11365
+ if (!schedule) return;
11366
+ try {
11367
+ const detail = await client.getRoutine(routineId);
11368
+ const triggers = Array.isArray(detail?.triggers) ? detail.triggers : [];
11369
+ const scheduleTrigger = triggers.find((trigger) => trigger?.kind === "schedule");
11370
+ if (scheduleTrigger?.id) {
11371
+ await client.updateRoutineTrigger(scheduleTrigger.id, {
11372
+ enabled: true,
11373
+ cronExpression: schedule,
11374
+ timezone: scheduleTrigger.timezone || "UTC"
11375
+ });
11376
+ return;
11377
+ }
11378
+ await client.createRoutineTrigger(routineId, {
11379
+ kind: "schedule",
11380
+ cronExpression: schedule,
11381
+ timezone: "UTC"
11382
+ });
11383
+ } catch (err) {
11384
+ log(
11385
+ `\u26A0 Could not sync trigger for routine "${routineId}": ${err instanceof Error ? err.message : String(err)}`
11386
+ );
11387
+ }
11388
+ }
11389
+ async function syncExistingCompanyRoutines({
11390
+ client,
11391
+ companyId,
11392
+ routines,
11393
+ ceoAgentId,
11394
+ teamAgentIds,
11395
+ log
11396
+ }) {
11397
+ if (!Array.isArray(routines) || routines.length === 0) return;
11398
+ let existingRoutines = [];
11399
+ try {
11400
+ existingRoutines = await client.listRoutines(companyId);
11401
+ } catch (err) {
11402
+ log(
11403
+ `\u26A0 Could not list existing routines for template sync: ${err instanceof Error ? err.message : String(err)}`
11404
+ );
11405
+ return;
11406
+ }
11407
+ const byTitle = /* @__PURE__ */ new Map();
11408
+ for (const existing of existingRoutines) {
11409
+ if (typeof existing?.title === "string" && existing.title.trim()) {
11410
+ byTitle.set(existing.title.trim().toLowerCase(), existing);
11411
+ }
11412
+ }
11413
+ for (const routine of routines) {
11414
+ const title = routineTemplateTitle(routine);
11415
+ if (!title) continue;
11416
+ const role = routine.assignTo;
11417
+ const assigneeAgentId = !role || role === "ceo" ? ceoAgentId : teamAgentIds[role] ?? ceoAgentId;
11418
+ const payload = {
11419
+ title,
11420
+ description: routine.description || null,
11421
+ assigneeAgentId,
11422
+ priority: routine.priority || "medium",
11423
+ status: routine.status || "active",
11424
+ concurrencyPolicy: routine.concurrencyPolicy || "skip_if_active",
11425
+ catchUpPolicy: routine.catchUpPolicy || "skip_missed"
11426
+ };
11427
+ const existing = byTitle.get(title.toLowerCase());
11428
+ try {
11429
+ if (existing?.id) {
11430
+ await client.updateRoutine(existing.id, payload);
11431
+ await syncRoutineTrigger({
11432
+ client,
11433
+ routineId: existing.id,
11434
+ schedule: routine.schedule,
11435
+ log
11436
+ });
11437
+ log(`\u2713 Synced existing routine "${title}"`);
11438
+ } else {
11439
+ const created = await client.createRoutine(companyId, payload);
11440
+ if (routine.schedule && created?.id) {
11441
+ await client.createRoutineTrigger(created.id, {
11442
+ kind: "schedule",
11443
+ cronExpression: routine.schedule,
11444
+ timezone: "UTC"
11445
+ });
11446
+ }
11447
+ log(
11448
+ `\u2713 Created missing routine "${title}"${routine.schedule ? ` (${routine.schedule})` : ""}`
11449
+ );
11450
+ }
11451
+ } catch (err) {
11452
+ log(
11453
+ `\u26A0 Could not sync routine "${title}": ${err instanceof Error ? err.message : String(err)}`
11454
+ );
11455
+ }
11456
+ }
11457
+ }
11310
11458
  function buildDecisionLogBody({
11311
11459
  companyName,
11312
11460
  companyDescription,
@@ -11590,10 +11738,6 @@ var plugin = definePlugin({
11590
11738
  const paperclipUrl = cfg.paperclipUrl || process.env.PAPERCLIP_PUBLIC_URL || "http://localhost:3100";
11591
11739
  const paperclipEmail = cfg.paperclipEmail || "";
11592
11740
  const paperclipPassword = cfg.paperclipPassword || "";
11593
- const disableBoardApprovalOnNewCompanies = cfgBool(
11594
- cfg,
11595
- "disableBoardApprovalOnNewCompanies"
11596
- );
11597
11741
  const enableIsolatedWorktrees = await resolveEnableIsolatedWorkspacesFromInstance(cfg, log);
11598
11742
  const enableEnrichedPersonas = true;
11599
11743
  const companyName = typeof params.companyName === "string" ? params.companyName.trim() : "";
@@ -11616,6 +11760,17 @@ var plugin = definePlugin({
11616
11760
  );
11617
11761
  const goals = collectGoals(selectedPreset, allModules, new Set(effectiveModules));
11618
11762
  const presetBootstrapData = collectPresetBootstrapData(selectedPreset);
11763
+ log("Connecting to Paperclip API...");
11764
+ const client = new PaperclipClient(paperclipUrl, {
11765
+ email: paperclipEmail,
11766
+ password: paperclipPassword
11767
+ });
11768
+ await client.connect();
11769
+ log("Connected.");
11770
+ const gitIdentity = {
11771
+ name: client.boardUserName || null,
11772
+ email: client.boardUserEmail || paperclipEmail || null
11773
+ };
11619
11774
  const outputDir = resolveWritableCompaniesDir(cfg, log);
11620
11775
  log("Assembling company workspace...");
11621
11776
  const companyDescription = typeof params.companyDescription === "string" ? params.companyDescription.trim() : "";
@@ -11636,6 +11791,8 @@ var plugin = definePlugin({
11636
11791
  presetLabels: presetBootstrapData.labels,
11637
11792
  enableIsolatedWorktrees,
11638
11793
  enableEnrichedPersonas,
11794
+ gitUserName: gitIdentity.name || void 0,
11795
+ gitUserEmail: gitIdentity.email || void 0,
11639
11796
  outputDir,
11640
11797
  templatesDir,
11641
11798
  onProgress: log
@@ -11653,21 +11810,13 @@ var plugin = definePlugin({
11653
11810
  log(`\u270E Override: ${relPath}`);
11654
11811
  }
11655
11812
  }
11656
- prepareLocalProjectWorkspace(assembleResult.mainProject, companyDir, log);
11813
+ prepareLocalProjectWorkspace(assembleResult.mainProject, companyDir, log, gitIdentity);
11657
11814
  const ceoInstructionsDir = path2.join(companyDir, "agents", "ceo");
11658
11815
  const ceoEntryFile = "AGENTS.md";
11659
11816
  const ceoEntryPath = path2.join(ceoInstructionsDir, ceoEntryFile);
11660
11817
  const ceoPromptTemplate = fs2.existsSync(ceoEntryPath) ? fs2.readFileSync(ceoEntryPath, "utf-8") : "";
11661
11818
  log("");
11662
11819
  log(`\u2713 Generated files: ${companyDir}`);
11663
- log("Connecting to Paperclip API...");
11664
- const client = new PaperclipClient(paperclipUrl, {
11665
- email: paperclipEmail,
11666
- password: paperclipPassword
11667
- });
11668
- await client.connect();
11669
- log("Connected.");
11670
- log("");
11671
11820
  let company;
11672
11821
  let companyId;
11673
11822
  let createdCompany = false;
@@ -11686,19 +11835,7 @@ var plugin = definePlugin({
11686
11835
  companyId = company.id;
11687
11836
  createdCompany = true;
11688
11837
  log(`\u2713 Company "${companyName}" created`);
11689
- if (disableBoardApprovalOnNewCompanies) {
11690
- try {
11691
- await client.updateCompany(companyId, { requireBoardApprovalForNewAgents: false });
11692
- log("\u2713 Disabled board-approval hiring policy for this new company");
11693
- } catch (err) {
11694
- log(
11695
- `\u26A0 Could not disable board-approval hiring for new agents: ${err instanceof Error ? err.message : String(err)}`
11696
- );
11697
- log(" Continuing \u2014 agent creation will use an approval-aware fallback if required.");
11698
- }
11699
- } else {
11700
- log("Keeping company hire policy as configured (board approvals may be required).");
11701
- }
11838
+ log("Keeping company hire policy as configured (board approvals may be required).");
11702
11839
  }
11703
11840
  let ceoAgentId;
11704
11841
  const teamAgentIds = {};
@@ -11710,7 +11847,7 @@ var plugin = definePlugin({
11710
11847
  const repositoryMode = userProjects.some(
11711
11848
  (project) => project?.repoUrl || project?.workspace?.sourceType === "git_repo"
11712
11849
  ) ? "existing git repository" : "fresh local repository";
11713
- const approvalMode = disableBoardApprovalOnNewCompanies ? "board approval disabled for this new company by plugin setting" : "board approval preserved; /agent-hires may create pending approvals";
11850
+ const approvalMode = "board approval preserved; /agent-hires may create pending approvals";
11714
11851
  log("Creating board operations and hiring plan records...");
11715
11852
  const createdBoardOperationsIssue = await client.createIssue(companyId, {
11716
11853
  title: "Board Operations",
@@ -11926,6 +12063,14 @@ var plugin = definePlugin({
11926
12063
  );
11927
12064
  }
11928
12065
  teamAgentIds[roleName] = existingAgent.id;
12066
+ await syncAgentInstructionsIntoManagedBundle({
12067
+ client,
12068
+ agentId: existingAgent.id,
12069
+ sourceDir: roleInstructionsDir,
12070
+ entryFile: "AGENTS.md",
12071
+ log
12072
+ });
12073
+ log(`\u2713 Synced ${roleTitle} instructions from latest templates`);
11929
12074
  continue;
11930
12075
  }
11931
12076
  const roleAgent = await client.createAgent(companyId, {
@@ -11949,8 +12094,8 @@ var plugin = definePlugin({
11949
12094
  );
11950
12095
  }
11951
12096
  }
12097
+ const routines = Array.isArray(assembleResult.initialRoutines) ? assembleResult.initialRoutines : [];
11952
12098
  if (!existingCompanyId) {
11953
- const routines = Array.isArray(assembleResult.initialRoutines) ? assembleResult.initialRoutines : [];
11954
12099
  let mainProjectId;
11955
12100
  const mainProject = assembleResult.mainProject;
11956
12101
  if (routines.length > 0 && mainProject?.name) {
@@ -11983,7 +12128,8 @@ var plugin = definePlugin({
11983
12128
  assigneeAgentId,
11984
12129
  projectId: mainProjectId,
11985
12130
  priority: routine.priority || "medium",
11986
- concurrencyPolicy: routine.concurrencyPolicy || "skip_if_active"
12131
+ concurrencyPolicy: routine.concurrencyPolicy || "skip_if_active",
12132
+ catchUpPolicy: routine.catchUpPolicy || "skip_missed"
11987
12133
  });
11988
12134
  if (routine.schedule && createdRoutine?.id) {
11989
12135
  await client.createRoutineTrigger(createdRoutine.id, {
@@ -12001,6 +12147,15 @@ var plugin = definePlugin({
12001
12147
  );
12002
12148
  }
12003
12149
  }
12150
+ } else {
12151
+ await syncExistingCompanyRoutines({
12152
+ client,
12153
+ companyId,
12154
+ routines,
12155
+ ceoAgentId,
12156
+ teamAgentIds,
12157
+ log
12158
+ });
12004
12159
  }
12005
12160
  const bootstrapDescription = fs2.readFileSync(
12006
12161
  path2.join(companyDir, "BOOTSTRAP.md"),