@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 +26 -0
- package/README.md +1 -1
- package/dist/manifest.js +4 -9
- package/dist/manifest.js.map +2 -2
- package/dist/worker.js +198 -43
- package/dist/worker.js.map +2 -2
- package/package.json +1 -1
- package/templates/modules/auto-assign/README.md +9 -7
- package/templates/modules/auto-assign/agents/ceo/heartbeat-section.md +3 -1
- package/templates/modules/auto-assign/agents/ceo/skills/auto-assign.fallback.md +14 -8
- package/templates/modules/auto-assign/agents/product-owner/heartbeat-section.md +3 -1
- package/templates/modules/auto-assign/module.meta.json +3 -3
- package/templates/modules/auto-assign/skills/auto-assign.md +2 -2
- package/templates/modules/backlog/docs/backlog-process.md +7 -7
- package/templates/modules/backlog/docs/backlog-template.md +2 -1
- package/templates/modules/backlog/module.meta.json +2 -2
- package/templates/modules/backlog/skills/backlog-health.bar.md +1 -1
- package/templates/modules/backlog/skills/backlog-health.md +2 -2
- package/templates/modules/github-repo/README.md +3 -3
- package/templates/modules/github-repo/agents/engineer/skills/git-workflow.md +24 -8
- package/templates/modules/github-repo/docs/git-workflow.md +59 -2
- package/templates/modules/github-repo/module.meta.json +1 -1
- package/templates/roles/engineer/AGENTS.md +2 -0
- package/templates/roles/engineer/HEARTBEAT.md +1 -1
- package/templates/roles/product-owner/AGENTS.md +2 -1
- package/templates/roles/product-owner/HEARTBEAT.md +1 -1
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.
|
|
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
|
+
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.
|
|
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.
|
|
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
|
|
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
|
},
|
package/dist/manifest.js.map
CHANGED
|
@@ -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
|
-
"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,
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
11189
|
-
path2.join(
|
|
11190
|
-
|
|
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
|
-
|
|
11285
|
+
`user.email=${gitUserEmail}`,
|
|
11242
11286
|
"-c",
|
|
11243
|
-
|
|
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
|
-
|
|
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 =
|
|
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"),
|