@starlein/paperclip-plugin-company-wizard 0.3.22 → 0.3.24

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.
@@ -0,0 +1,90 @@
1
+ # Contributing to Company Wizard
2
+
3
+ Thanks for your interest in contributing! Company Wizard is a [Paperclip](https://github.com/paperclipai/paperclip) plugin that bootstraps agent company workspaces from composable templates.
4
+
5
+ ## Getting Started
6
+
7
+ ```sh
8
+ git clone <repo-url>
9
+ cd paperclip-plugin-company-wizard
10
+ pnpm install
11
+ pnpm build
12
+ ```
13
+
14
+ ## Development
15
+
16
+ ```sh
17
+ pnpm build # esbuild: worker + manifest + UI → dist/
18
+ pnpm dev # watch mode (rebuilds on change)
19
+ pnpm test # vitest: tests/**/*.spec.ts
20
+ pnpm test:logic # node --test: src/logic/*.test.js
21
+ pnpm typecheck # tsc --noEmit
22
+ ```
23
+
24
+ Load the plugin in Paperclip by pointing to this directory (the `paperclipPlugin` field in `package.json` tells Paperclip where to find the built artifacts). After `pnpm build`, reload the plugin in the Paperclip UI — no reinstall required.
25
+
26
+ ## Project Structure
27
+
28
+ ```text
29
+ src/
30
+ ├── worker.ts # Plugin worker: actions (preview-files, start-provision, check-auth)
31
+ ├── manifest.ts # Plugin manifest (id, displayName, slots)
32
+ ├── logic/ # Pure functions (assembly, resolution, template loading)
33
+ ├── api/ # Paperclip REST API client and provisioning
34
+ └── ui/
35
+ ├── main.tsx # UI entry point
36
+ ├── context/ # WizardContext (state machine + reducer)
37
+ └── components/ # React components (WizardShell, step components, ConfigReview)
38
+
39
+ templates/
40
+ ├── roles/ # All roles with role.meta.json (base: true for always-present)
41
+ ├── modules/ # Composable capabilities with module.meta.json
42
+ └── presets/ # Curated module+role combinations with preset.meta.json
43
+ ```
44
+
45
+ ## Adding Templates
46
+
47
+ ### New module
48
+
49
+ See [docs/TEMPLATES.md](docs/TEMPLATES.md) for the full module schema. Key points:
50
+
51
+ - Every capability needs a shared skill in `skills/<skill>.md`
52
+ - Role-specific overrides go in `agents/<role>/skills/<skill>.md` (only when genuinely different)
53
+ - Fallback variants are always role-specific (`.fallback.md`)
54
+ - Add `node --test` tests in `src/logic/` if the module has non-trivial resolution logic
55
+
56
+ ### New role
57
+
58
+ See [docs/TEMPLATES.md](docs/TEMPLATES.md). Map `paperclipRole` to a valid Paperclip enum value. Set `base: true` only for roles that belong in every company (only the CEO currently has `base: true`).
59
+
60
+ ### New preset
61
+
62
+ See [docs/TEMPLATES.md](docs/TEMPLATES.md). Test that the module combination resolves correctly. Presets can include inline `goals[]` arrays with milestones and issues.
63
+
64
+ ## Pull Requests
65
+
66
+ - Keep PRs focused — one feature or fix per PR
67
+ - Add or update tests for logic changes
68
+ - Run `pnpm test && pnpm test:logic` before submitting
69
+ - Update `docs/TEMPLATES.md` if you add modules, roles, or presets
70
+ - Update `CHANGELOG.md` with your changes
71
+
72
+ ## Code Style
73
+
74
+ - TypeScript for plugin infrastructure (`src/worker.ts`, `src/manifest.ts`, `src/ui/`)
75
+ - ESM only (`type: "module"`)
76
+ - Plain JS for logic/API modules (`src/logic/`, `src/api/`) — JSDoc where helpful
77
+ - Prettier via pre-commit hook (lint-staged)
78
+
79
+ ## Reporting Issues
80
+
81
+ Use GitHub Issues. Include:
82
+
83
+ - What you expected vs what happened
84
+ - Plugin version (from `package.json`)
85
+ - Node.js version (`node --version`)
86
+ - OS
87
+
88
+ ## License
89
+
90
+ By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE).
package/README.md CHANGED
@@ -1,5 +1,4 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/starlein/paperclip-plugin-company-wizard/main/public/favicon.svg" alt="Company Wizard" width="48" height="48">
3
2
  <h1 align="center">Company Wizard</h1>
4
3
  <p align="center">
5
4
  <strong>Bootstrap AI agent teams from modular templates.</strong>
@@ -10,6 +9,8 @@
10
9
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="License"></a>
11
10
  <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen" alt="Node.js"></a>
12
11
  </p>
12
+ <hr>
13
+ <img src="https://raw.githubusercontent.com/starlein/paperclip-plugin-company-wizard/main/docs/GIF-Screencast-Paperclip-Plugin-Company-Wizard.gif" alt="Screencast Paperclip Plugin Company Wizard" height="240">
13
14
  </p>
14
15
 
15
16
  ---
@@ -160,7 +161,7 @@ Start with just a CEO. Everything works. Add roles and responsibilities shift au
160
161
  | `tech-stack` | Engineer | CEO | tech-stack |
161
162
  | `architecture-plan` | Engineer | CEO | architecture-plan |
162
163
  | `design-system` | UI Designer | Engineer | architecture-plan |
163
- | `pr-review` | Code Reviewer / Product Owner / UI Designer / UX Researcher / QA / DevOps | — | pr-review |
164
+ | `pr-review` | QA (gate) / Security Engineer (security-relevant) / Product Owner (approval) / Engineer (merge); Code Reviewer / UI / UX / DevOps advisory | — | pr-review |
164
165
  | `threat-model` | Security Engineer → DevOps | Engineer | security-audit |
165
166
  | `security-review` | Security Engineer → DevOps | Engineer | security-audit |
166
167
  | `project-docs` | Technical Writer → Engineer | CEO | documentation |
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.3.22",
5
+ version: "0.3.24",
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>",
@@ -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.3.22',\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 enableEnrichedPersonas: {\n type: 'boolean',\n default: false,\n description:\n 'Optional. If true, expert roles are enriched with domain lenses (named mental models), module skills gain concrete output/review bars with negative examples, and HEARTBEAT.md gains explicit done-criteria. Leave false (default) for the lean baseline personas.',\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"],
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.3.24',\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 enableEnrichedPersonas: {\n type: 'boolean',\n default: false,\n description:\n 'Optional. If true, expert roles are enriched with domain lenses (named mental models), module skills gain concrete output/review bars with negative examples, and HEARTBEAT.md gains explicit done-criteria. Leave false (default) for the lean baseline personas.',\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
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,MACA,wBAAwB;AAAA,QACtB,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;",
6
6
  "names": []
7
7
  }
package/dist/worker.js CHANGED
@@ -9551,6 +9551,7 @@ async function assembleCompany({
9551
9551
  if (reviewers.length === 0 && !approver && !mergeGate) return null;
9552
9552
  return { reviewers, approver, mergeGate };
9553
9553
  };
9554
+ const hasCi = moduleNames.includes("ci-cd");
9554
9555
  const renderReviewGate = (gate) => {
9555
9556
  const stages = [];
9556
9557
  for (const role of gate.reviewers) {
@@ -9562,12 +9563,14 @@ async function assembleCompany({
9562
9563
  );
9563
9564
  }
9564
9565
  if (gate.mergeGate) {
9566
+ const gatePrecondition = hasCi ? "CI must be green before merge" : "no CI configured \u2014 run the test suite/build and paste the output before merge";
9565
9567
  stages.push(
9566
- ` - stage ${stages.length + 1} (approval) \u2192 assign ${JSON.stringify(gate.mergeGate)} \u2014 merge gate: merge the PR, then record approved to close`
9568
+ ` - stage ${stages.length + 1} (approval) \u2192 assign ${JSON.stringify(gate.mergeGate)} \u2014 merge gate: ${gatePrecondition}; merge the PR, then record approved to close`
9567
9569
  );
9568
9570
  }
9569
9571
  return `- **executionPolicy** (set when creating this issue; resolve each role to its agentId):
9570
9572
  ${stages.join("\n")}
9573
+ - every verdict must cite executed verification (commands + results); "looks good" without evidence is not a valid verdict
9571
9574
 
9572
9575
  `;
9573
9576
  };
@@ -10274,7 +10277,8 @@ Read: \`docs/${doc}\`
10274
10277
  bootstrap += `- Do not reuse parent workspaces for subissues unless explicitly requested.
10275
10278
  `;
10276
10279
  if (moduleNames.includes("pr-review")) {
10277
- bootstrap += `- Required PR reviews use the issue's \`executionPolicy\`: a \`review\` stage for the Code Reviewer (plus any relevant domain reviewer \u2014 QA/UI/UX/DevOps), an \`approval\` stage for the Product Owner, then a final \`approval\` merge-gate stage for the Engineer (who merges the PR before recording approval, which closes the issue). The merge gate must be last so the Product Owner's approval does not auto-close the issue with the PR still open. Resolve each role to its agentId. Do not create separate child review issues and do not use @-mentions.
10280
+ const ciClause = hasCi ? "CI (lint/test/build) must be green before the Engineer merges \u2014 this is the hard gate and cannot be skipped" : "no CI is configured, so the Engineer must run the test suite/build and paste the real output into the merge-gate verdict before merging \u2014 this is the hard gate";
10281
+ bootstrap += `- Required PR reviews use the issue's \`executionPolicy\`. The substantive gate is execution, not opinion: ${ciClause}. Stages, in order: a \`review\` stage for QA when present (test adequacy / running the tests), a \`review\` stage for the Security Engineer **only when the change is security-relevant** (auth, secrets, input boundaries, crypto, dependencies, infra exposure), an \`approval\` stage for the Product Owner (intent/scope), then a final \`approval\` merge-gate stage for the Engineer (who satisfies the hard gate above, merges the PR, then records approval to close the issue). The merge gate must be last so the Product Owner's approval does not auto-close the issue with the PR still open. The Code Reviewer and other domain reviewers may add advisory, non-blocking comments but do not gate the merge. Every verdict must cite executed verification. Resolve each role to its agentId. Do not create separate child review issues and do not use @-mentions.
10278
10282
  `;
10279
10283
  }
10280
10284
  bootstrap += `
@@ -11090,6 +11094,49 @@ function resolveCompaniesDir(cfg) {
11090
11094
  if (cfg.companiesDir) return cfg.companiesDir;
11091
11095
  return path2.join(os.homedir(), ".paperclip", "instances", "default", "companies");
11092
11096
  }
11097
+ function mkdirErrorMessage(err) {
11098
+ if (err instanceof Error) return err.message;
11099
+ return String(err);
11100
+ }
11101
+ function ensureWritableDir(dir) {
11102
+ fs2.mkdirSync(dir, { recursive: true });
11103
+ fs2.accessSync(dir, fs2.constants.W_OK);
11104
+ }
11105
+ function resolveWritableCompaniesDir(cfg, log) {
11106
+ const configuredDir = typeof cfg.companiesDir === "string" ? cfg.companiesDir.trim() : "";
11107
+ if (configuredDir) {
11108
+ try {
11109
+ ensureWritableDir(configuredDir);
11110
+ return configuredDir;
11111
+ } catch (err) {
11112
+ throw new Error(
11113
+ `Configured companiesDir is not writable (${configuredDir}): ${mkdirErrorMessage(err)}`
11114
+ );
11115
+ }
11116
+ }
11117
+ const candidates = [
11118
+ resolveCompaniesDir(cfg),
11119
+ path2.join("/paperclip", "instances", "default", "companies"),
11120
+ path2.join(os.tmpdir(), "paperclip-companies")
11121
+ ];
11122
+ const attempted = /* @__PURE__ */ new Set();
11123
+ let lastError = "";
11124
+ for (const candidate of candidates) {
11125
+ if (attempted.has(candidate)) continue;
11126
+ attempted.add(candidate);
11127
+ try {
11128
+ ensureWritableDir(candidate);
11129
+ return candidate;
11130
+ } catch (err) {
11131
+ const message = mkdirErrorMessage(err);
11132
+ lastError = `${candidate}: ${message}`;
11133
+ if (log) log(`\u26A0 Companies dir unavailable: ${candidate} (${message})`);
11134
+ }
11135
+ }
11136
+ throw new Error(
11137
+ `Unable to prepare a writable companies directory. Last attempt failed at ${lastError}`
11138
+ );
11139
+ }
11093
11140
  function formatRoleName(role) {
11094
11141
  return role.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
11095
11142
  }
@@ -11197,7 +11244,7 @@ var plugin = definePlugin({
11197
11244
  const companyName = typeof params.companyName === "string" && params.companyName.trim() ? params.companyName.trim() : "Preview";
11198
11245
  const templatesDir = await ensureTemplatesDir(cfg);
11199
11246
  tmpDir = path2.join(os.tmpdir(), `company-wizard-preview-${Date.now()}`);
11200
- const companiesDir = resolveCompaniesDir(cfg);
11247
+ const companiesDir = resolveWritableCompaniesDir(cfg);
11201
11248
  const [presets, allModules] = await Promise.all([
11202
11249
  loadPresets(templatesDir),
11203
11250
  loadModules(templatesDir)
@@ -11352,8 +11399,7 @@ var plugin = definePlugin({
11352
11399
  );
11353
11400
  const goals = collectGoals(selectedPreset, allModules, new Set(effectiveModules));
11354
11401
  const presetBootstrapData = collectPresetBootstrapData(selectedPreset);
11355
- const outputDir = resolveCompaniesDir(cfg);
11356
- fs2.mkdirSync(outputDir, { recursive: true });
11402
+ const outputDir = resolveWritableCompaniesDir(cfg, log);
11357
11403
  log("Assembling company workspace...");
11358
11404
  const companyDescription = typeof params.companyDescription === "string" ? params.companyDescription.trim() : "";
11359
11405
  const userGoals = Array.isArray(params.goals) ? params.goals : params.goal ? [params.goal] : [];
@@ -11750,6 +11796,7 @@ var plugin = definePlugin({
11750
11796
  var worker_default = plugin;
11751
11797
  runWorker(plugin, import.meta.url);
11752
11798
  export {
11753
- worker_default as default
11799
+ worker_default as default,
11800
+ resolveWritableCompaniesDir
11754
11801
  };
11755
11802
  //# sourceMappingURL=worker.js.map