# @stravica-ai/engineering-pa-scaffold

Identity: `@stravica-ai/engineering-pa-scaffold` — `pa-init` scaffolder + onboarding flows.

## Status

| Mode | Status | Lands in |
|---|---|---|
| `--mode=solo` | **implemented** | Phase 1 Task 5 |
| `--mode=lead` | not implemented | Phase 3 |
| `--mode=member` | not implemented | Phase 5 |

Workspace-internal: `"private": true`, not individually published. The
operator-facing surface ships via the public `@stravica-ai/digital-operator-pa`
package; `pa-init` is exposed as one of its bin entries.

## Install / invoke

The bin is `pa-init`. After installing `@stravica-ai/digital-operator-pa` into an
empty staging directory (or invoking via `pnpm exec`), run:

```bash
pa-init --mode=solo --name=dave --operator=Baz --target=./dave-instance
```

Or interactively, with three prompts:

```bash
pa-init --mode=solo
# Operator name (the human this PA serves, e.g. 'Baz'): Baz
# PA name (kebab-case identity, e.g. 'dave'):           dave
# Target directory [/current/cwd]:                      /home/baz/dave
```

## Commands

```bash
pa-init --help                   # usage; exits 0
pa-init --mode=solo              # interactive solo onboarding
pa-init --mode=solo --name=NAME --operator=OP --target=DIR
                                 # flag-driven solo onboarding
pa-init --mode=solo --non-interactive --name=... --operator=... --target=...
                                 # CI mode; refuses to prompt
pa-init --mode=lead              # exits 2 ("not implemented in Phase 1")
pa-init --mode=member --invite=T # exits 2 ("not implemented in Phase 1")
pa-init workspace-extension <name> [--label=LABEL] [--section=SECTION] [--with-api] [--target=DIR]
                                 # scaffold one workspace-extension page inside an existing operator repo
```

### Flags

| Flag | Purpose |
|---|---|
| `--mode=MODE` | `solo` \| `lead` \| `member` (only `solo` ships in Phase 1) |
| `--name=PA_NAME` | PA identity. Kebab-case, lowercase, 2-40 chars, starts with a letter. |
| `--operator=NAME` | Operator (human) name. Free-form, 1-80 chars. |
| `--target=DIR` | Target directory. `--mode=solo`: must be empty or not exist. Sub-verbs: defaults to cwd. |
| `--purpose=TEXT` | Optional one-line PA purpose statement. Has a sane default. |
| `--non-interactive` | Refuse to prompt. Requires all values via flags. CI-friendly. |
| `--skip-install` | Skip the post-scaffold `pnpm install` + `pa status` check (test-friendly). |
| `--label=LABEL` | (workspace-extension) Sidebar label. Defaults to Title Case of the name. |
| `--section=SECTION` | (workspace-extension) Nav section header. Defaults to "Extensions". |
| `--with-api` | (workspace-extension) Also write an `<name>-api.mjs` `register(addRoute)` stub. |
| `-h`, `--help` | Show usage, exit 0. |

### Sub-verb: `workspace-extension`

Scaffolds one workspace-extension page inside an existing operator repo. The
PA-facing prompting guide at
`operator/knowledge/platform-tooling/workspace-extensions.md` covers the
"when to propose one" question; this verb covers the "yes, do it" cost.

What it writes:

- `operator/extensions/workspace/<name>.html`: a minimal HTML skeleton with
  the extension name in a marker comment.
- `operator/extensions/workspace/nav.json`: appends a
  `{ id, label, href, section }` entry (creates the file if absent).
- `operator/extensions/workspace/<name>-api.mjs`: optional, on `--with-api`.
  Exports `register(addRoute)` and wires one placeholder GET route so the
  extension responds before the operator writes any handler code.

Idempotency:

- A second run with the same `<name>` skips existing files instead of
  overwriting them.
- Same-`id` collisions in an existing `nav.json` are left as-is and reported;
  malformed `nav.json` fails cleanly with exit 2 rather than corrupting the
  file.

## Exit codes (tooling standard §6)

| Code | Meaning |
|---|---|
| 0 | OK |
| 1 | Usage error — invalid flags, missing required values in non-interactive mode, invalid PA name, non-empty target. |
| 2 | Operational failure — `pnpm install` or `pa status` exited non-zero. |
| 3 | Transport / IO failure — couldn't spawn a subprocess. |

## What `--mode=solo` generates

```
{target}/
├── CLAUDE.md                          # PA identity, rendered from the bundled template
├── README.md                          # operator-repo orientation
├── package.json                       # depends on @stravica-ai/digital-operator-pa
├── .gitignore                         # node, .session, .vault keys, *.pid, runs.jsonl
├── .nvmrc                             # 24
├── .git/                              # initialised so desk's .git-walk works
├── operator/
│   ├── profile/profile.md             # placeholder; PA leads intake to fill
│   ├── knowledge/
│   │   ├── domains.json               # base 5 domains per methodology
│   │   └── index.json                 # empty
│   └── extensions/
│       ├── manifest.json              # empty (extensions land in Phase 2)
│       ├── roles/, tools/, workspace/ # empty, .gitkeep'd
├── agents/{paName}/
│   ├── CLAUDE.md                      # same as root CLAUDE.md (PA identity)
│   ├── journal.md                     # empty stub
│   ├── next-session.md                # empty stub
│   ├── spawn-log.json                 # {"version":"1.0", "spawns":[]}
│   └── staging/                       # empty, .gitkeep'd
├── work/                              # empty, .gitkeep'd
├── projects/                          # empty, .gitkeep'd
├── schedules/                         # empty, .gitkeep'd
└── .vault/                            # empty, .gitkeep'd
```

### Base knowledge domains

Seeded into `operator/knowledge/domains.json` per the knowledge-system
methodology (5 core engineering-PA domains):

- `engineering` — the discipline
- `ops` — operational rules and process
- `agent-ops` — agentic team management
- `knowledge-meta` — the librarian and methodology
- `operator-profile` — who the operator is

Operators extend per-deployment (finance, sales, legal, etc.); the scaffolder
ships only the core set.

## Project layout

```
packages/scaffold/
  bin/pa-init                  # node shebang shim — imports src/cli.mjs
  index.mjs                    # package entry — re-exports public surface
  package.json                 # name, version, bin: pa-init
  README.md                    # this file (the spec)
  src/
    cli.mjs                    # arg parsing, verb routing, exit-code translation
    prompts.mjs                # interactive prompts + validators (readline seam)
    generator.mjs              # PURE: plan-of-files given a config
    runner.mjs                 # CHOKEPOINT: writes files, spawns pnpm/pa/git
    commands/
      init.mjs                 # --mode=solo orchestration
      init-workspace-extension.mjs  # workspace-extension <name> orchestration
  templates/
    CLAUDE.md.template         # PA identity template, {PA_NAME}/{OPERATOR_NAME}/{PURPOSE}/{COMMUNICATION_STYLE} tokens
    operator-README.md.template# operator-repo README template
  test/
    index.test.mjs             # package surface
    cli.test.mjs               # bin-via-child-process tests (parsing + exit codes + happy path)
    prompts.test.mjs           # validator + promptValues seam tests
    generator.test.mjs         # pure plan-composition tests
    runner.test.mjs            # chokepoint tests with fake-spawn seam
```

### Chokepoint pattern (tooling standard §7)

Every external effect is in `src/runner.mjs`:

- `writePlan(targetDir, plan)` — sole filesystem-write surface
- `runProcess({ cmd, args, cwd, seam })` — sole `child_process.spawn` surface
- `installDeps`, `paStatus`, `initGitRepo` — thin wrappers around `runProcess`

Nothing else in the scaffolder reaches the disk or the OS. Tests inject a
fake `seam.spawn` to verify framing without executing real subprocesses.

## Tests

```bash
pnpm --filter @stravica-ai/engineering-pa-scaffold test
# or, from the package directory:
node --test 'test/**/*.test.mjs'
```

## The tooling standard

This tool complies with [the tooling standard](../../CONTRIBUTING.md#standards)
in full: JS only, zero npm runtime deps, `node:test`, `node:util.parseArgs`
with `strict: true`, camelCase JSON, chokepoint pattern for external effects,
README-as-contract, four-exit-code contract.
