@stravica-ai/digital-operator-pa 0.1.41 → 0.1.42

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.
@@ -205,6 +205,13 @@ function resolveWorkspacePort(repoRoot, opts = {}) {
205
205
  return 4200;
206
206
  }
207
207
 
208
+ /**
209
+ * The `server` field the workspace's own /__api/identity endpoint returns
210
+ * (see packages/workspace/src/api.mjs). Any other body on the port means
211
+ * the port is bound by an unrelated process, not our workspace.
212
+ */
213
+ const WORKSPACE_IDENTITY_SERVER = 'digital-operator-pa-workspace';
214
+
208
215
  async function probeWorkspace(url, fetchImpl) {
209
216
  const fetchFn = fetchImpl ?? globalThis.fetch;
210
217
  if (typeof fetchFn !== 'function') {
@@ -214,8 +221,25 @@ async function probeWorkspace(url, fetchImpl) {
214
221
  const timer = setTimeout(() => controller.abort(), 1500);
215
222
  try {
216
223
  const res = await fetchFn(url, { signal: controller.signal });
224
+ // Any HTTP response has to be positively identified as OUR workspace
225
+ // before the probe reports "reachable". WSD-laptop cold install of
226
+ // 0.1.41 (2026-09-16): a docker container bound to :4200 answered
227
+ // /__api/identity with HTTP 404 and `pa status` reported the
228
+ // workspace "reachable (HTTP 404)". Identity-gate the probe so a
229
+ // foreign process on the port surfaces as "in use by another
230
+ // process" instead of a false green.
217
231
  if (res.status >= 200 && res.status < 500) {
218
- return { status: 'reachable', detail: `HTTP ${res.status}` };
232
+ let body = null;
233
+ try {
234
+ body = await res.json();
235
+ } catch { /* not JSON — falls through to the identity-mismatch branch */ }
236
+ if (body && body.server === WORKSPACE_IDENTITY_SERVER) {
237
+ return { status: 'reachable', detail: `HTTP ${res.status}` };
238
+ }
239
+ return {
240
+ status: 'in use by another process',
241
+ detail: `HTTP ${res.status}, /__api/identity did not identify as ${WORKSPACE_IDENTITY_SERVER}`,
242
+ };
219
243
  }
220
244
  return { status: 'error', detail: `HTTP ${res.status}` };
221
245
  } catch (e) {
@@ -35,6 +35,7 @@ import {
35
35
  sopsEncryptScoped,
36
36
  VaultError,
37
37
  } from '../vault.mjs';
38
+ import { runInit } from './init.mjs';
38
39
 
39
40
  // --canonical-clone / --no-canonical may appear anywhere in argv, matching
40
41
  // the --scope/--file convention in cli.mjs. Local to `set` (not global
@@ -91,7 +92,41 @@ export async function runSet(args, overrides = {}) {
91
92
  throw new VaultError('missing dependencies', 1);
92
93
  }
93
94
 
94
- const vaultState = checkVault(paths);
95
+ let vaultState = checkVault(paths);
96
+ // Self-init on `secrets set` when the legacy vault has not been
97
+ // initialised on this host yet. WSD-laptop cold install of 0.1.41
98
+ // (2026-09-16): the onboarding guide's Atlassian setup asks the
99
+ // colleague to run three `secrets set` commands, and the first one
100
+ // died with `Error: No recipients file at .vault/recipients.txt —
101
+ // Run 'secrets init' to set up the vault.` The guide's two-step
102
+ // Atlassian setup never named that mandatory extra step. `set` now
103
+ // runs the same `runInit()` code path in-process before the first
104
+ // write when the vault is uninitialised, emitting one stderr notice
105
+ // so the operator can see what happened.
106
+ //
107
+ // The self-init fires only when it is SAFE — the legacy (non-scoped)
108
+ // vault, no encrypted store already on disk (otherwise a fresh key
109
+ // would orphan the existing contents; that failure mode still surfaces
110
+ // as `missing-key` exit 2 with the "restore your key from backup"
111
+ // guidance). `missing-recipients` on a solo host is always safe (a
112
+ // present store with no recipients file would already have failed
113
+ // decryption elsewhere; the case the WSD laptop hit was "no vault at
114
+ // all yet"). `missing-key` on a solo host is also safe when the
115
+ // encrypted store is absent — pure first-run state. Scoped vaults
116
+ // (`paths.scoped`) are provisioned by scope-registration tooling and
117
+ // are never self-inited here; a `secrets set --scope <name>` against
118
+ // a missing scope surfaces the not-provisioned error unchanged.
119
+ const canSelfInit = !vaultState.ok
120
+ && !paths.scoped
121
+ && (vaultState.reason === 'missing-recipients' || vaultState.reason === 'missing-key')
122
+ && !secretsFileExists(paths);
123
+ if (canSelfInit) {
124
+ console.error(
125
+ `notice: no vault at ${paths.vaultDir}: self-initialising before first write (equivalent to running \`secrets init\`).`,
126
+ );
127
+ await runInit({ paths });
128
+ vaultState = checkVault(paths);
129
+ }
95
130
  if (!vaultState.ok) {
96
131
  console.error(vaultState.message);
97
132
  throw new VaultError(vaultState.reason || 'vault not initialised', vaultState.exitCode ?? 1);
@@ -32,7 +32,7 @@
32
32
  // omitted on `ok`. `log` is null on exits that happened before the
33
33
  // per-run log file was created.
34
34
 
35
- import { appendFile, readFile } from 'node:fs/promises';
35
+ import { appendFile, readFile, unlink } from 'node:fs/promises';
36
36
  import { basename, join } from 'node:path';
37
37
 
38
38
  import { resolvePrereqs } from './prereqs.mjs';
@@ -211,7 +211,21 @@ export async function runDistill({
211
211
  log(`distillation OK (${durationSec}s) — log: ${runLogPath}`, { stderr });
212
212
  log('changes (if any) will be committed by the next sync run', { stderr });
213
213
 
214
- // Step 4 — append success audit. Sync will pick up any file changes
214
+ // Step 4a — invalidate the retrieval artefacts so the next
215
+ // `pa knowledge` call rebuilds them and sees any doc the librarian just
216
+ // promoted. Macbook re-proof of 0.1.41 (2026-09-16): on a solo install
217
+ // there is no `library-sync-cycle:graph-rebuild` leg running behind the
218
+ // scenes, so a distilled knowledge doc stayed invisible to
219
+ // `pa knowledge search` until `.graph.json` was deleted by hand. Delete
220
+ // is safer than in-process rebuild here: the retrieval loader in
221
+ // `packages/cli/src/knowledge/graph.mjs` already self-heals on a
222
+ // missing artefact (`loadGraph:on-demand`), and a full-mode host has
223
+ // its scheduled graph-rebuild leg rebuild both files on the next cycle
224
+ // regardless. Both are untracked per the 2026-07-29 per-device ruling,
225
+ // so a deletion at commit time is invisible to git.
226
+ await invalidateRetrievalArtefacts(repoRoot, stderr);
227
+
228
+ // Step 4b — append success audit. Sync will pick up any file changes
215
229
  // the librarian produced on its next 30-minute tick.
216
230
  await writeAudit(runsLog, {
217
231
  ts: tsIso,
@@ -224,6 +238,35 @@ export async function runDistill({
224
238
  return 0;
225
239
  }
226
240
 
241
+ /**
242
+ * Delete `operator/knowledge/.graph.json` and `.backlinks.json` under
243
+ * `root` if they exist. Missing files are a no-op; any other error is
244
+ * swallowed with a stderr note so a stale artefact never masks an
245
+ * otherwise-successful distillation exit code. Exported for tests.
246
+ */
247
+ export async function invalidateRetrievalArtefacts(root, stderr = process.stderr) {
248
+ const targets = [
249
+ join(root, 'operator', 'knowledge', '.graph.json'),
250
+ join(root, 'operator', 'knowledge', '.backlinks.json'),
251
+ ];
252
+ const removed = [];
253
+ for (const path of targets) {
254
+ try {
255
+ await unlink(path);
256
+ removed.push(basename(path));
257
+ } catch (e) {
258
+ if (e && e.code === 'ENOENT') continue;
259
+ log(`warning: failed to invalidate ${basename(path)}: ${e.message}`, { stderr });
260
+ }
261
+ }
262
+ if (removed.length > 0) {
263
+ log(
264
+ `invalidated retrieval artefacts (${removed.join(', ')}); pa knowledge rebuilds on demand`,
265
+ { stderr },
266
+ );
267
+ }
268
+ }
269
+
227
270
  function isoCompactForSession(d) {
228
271
  // Matches the historical `$(date -u +%Y%m%d-%H%M)` shape used pre-Node.
229
272
  const pad = (n) => String(n).padStart(2, '0');
@@ -10,9 +10,10 @@
10
10
  //
11
11
  // Errors carry an `exitCode`. The CLI translates to `process.exit`.
12
12
 
13
- import { resolve } from 'node:path';
13
+ import { existsSync, readFileSync } from 'node:fs';
14
+ import { join, resolve } from 'node:path';
14
15
 
15
- import { buildPlan, buildRenderConfig } from '../generator.mjs';
16
+ import { buildPlan, buildRenderConfig, resolveInstalledDopVersion } from '../generator.mjs';
16
17
  import {
17
18
  defaultSeam,
18
19
  promptValues,
@@ -35,7 +36,14 @@ export async function runInit(options) {
35
36
  const config = await gatherConfig(options);
36
37
 
37
38
  process.stdout.write(`\npa-init: scaffolding ${config.paName} for ${config.operatorName}\n`);
38
- process.stdout.write(` target: ${config.targetDir}\n`);
39
+ process.stdout.write(` target: ${config.targetDir}\n`);
40
+ // Announce the exact version pa-init pinned into the instance's
41
+ // package.json (or `<default>` when the resolver could not verify the
42
+ // installed umbrella version — dev-only edge case). This is the
43
+ // load-bearing signal for the "you got the right version" contract.
44
+ process.stdout.write(
45
+ ` package: @stravica-ai/digital-operator-pa@${config.pkgVersion}\n`,
46
+ );
39
47
 
40
48
  await assertTargetIsEmptyOrMissing(config.targetDir);
41
49
 
@@ -63,6 +71,25 @@ export async function runInit(options) {
63
71
  return 2;
64
72
  }
65
73
 
74
+ // Version-drift safety net (WSD-laptop cold install of 0.1.41). The
75
+ // scaffold pins an exact version and ships a `.npmrc` scope override,
76
+ // so pnpm should never resolve a different version — but if the guard
77
+ // ever misses, this prints one line naming the mismatch so a colleague
78
+ // can see it immediately instead of hitting a "Command X not found" on
79
+ // an older bin surface. Read from the SAME source `pa status` reads —
80
+ // the resolved package.json inside the instance's node_modules.
81
+ const scaffoldedVersion = config.pkgVersion;
82
+ const installedVersion = readInstalledDopVersion(config.targetDir);
83
+ if (scaffoldedVersion && installedVersion && installedVersion !== scaffoldedVersion) {
84
+ process.stderr.write(
85
+ `pa-init: WARNING — scaffold pinned @stravica-ai/digital-operator-pa@${scaffoldedVersion} `
86
+ + `but pnpm resolved ${installedVersion}. This usually means pnpm's `
87
+ + `minimumReleaseAge gate filtered the pinned version out; the shipped `
88
+ + `.npmrc scopes minimumReleaseAge=0 for @stravica-ai which normally cures it. `
89
+ + `Run \`pnpm install\` inside ${config.targetDir} again to reconcile.\n`,
90
+ );
91
+ }
92
+
66
93
  process.stdout.write('\npa-init: running pa status …\n');
67
94
  const statusCode = await paStatus({ targetDir: config.targetDir });
68
95
  if (statusCode !== 0) {
@@ -142,6 +169,11 @@ async function gatherConfig(options) {
142
169
  operatorName: resolvedValues.operatorName,
143
170
  targetDir: resolvedValues.targetDir,
144
171
  purpose: options.purpose,
172
+ // Pin the EXACT version of `@stravica-ai/digital-operator-pa` that is
173
+ // running pa-init right now. `resolveInstalledDopVersion()` returns
174
+ // null when it cannot verify a version (dev-only edge case); the
175
+ // downstream default in `buildRenderConfig` catches that safely.
176
+ pkgVersion: resolveInstalledDopVersion() ?? undefined,
145
177
  });
146
178
  }
147
179
 
@@ -168,3 +200,26 @@ function applyValidatorsOrThrow(supplied) {
168
200
  function countEntries(plan) {
169
201
  return plan.length;
170
202
  }
203
+
204
+ /**
205
+ * Read the installed version of `@stravica-ai/digital-operator-pa` from
206
+ * the instance's own `node_modules`. Returns null if the package is
207
+ * missing or its `package.json` cannot be parsed — the caller treats
208
+ * null as "no verifiable install" and does not warn.
209
+ */
210
+ function readInstalledDopVersion(targetDir) {
211
+ const pkgPath = join(
212
+ targetDir,
213
+ 'node_modules',
214
+ '@stravica-ai',
215
+ 'digital-operator-pa',
216
+ 'package.json',
217
+ );
218
+ if (!existsSync(pkgPath)) return null;
219
+ try {
220
+ const meta = JSON.parse(readFileSync(pkgPath, 'utf8'));
221
+ return typeof meta.version === 'string' ? meta.version : null;
222
+ } catch {
223
+ return null;
224
+ }
225
+ }
@@ -7,14 +7,92 @@
7
7
  // kind: 'dir' (mkdir -p) | 'file' (write file with contents) | 'json'
8
8
  // (stringify + write).
9
9
 
10
- import { existsSync } from 'node:fs';
10
+ import { existsSync, readFileSync } from 'node:fs';
11
11
  import { readFile, readdir } from 'node:fs/promises';
12
+ import { createRequire } from 'node:module';
12
13
  import { dirname, join, resolve } from 'node:path';
13
14
  import { fileURLToPath } from 'node:url';
14
15
 
15
16
  import { emptyEnvelope, renderEnvelope } from '../../engineering-pa-core/src/autonomy/envelope.mjs';
16
17
 
17
18
  const __dirname = dirname(fileURLToPath(import.meta.url));
19
+ const requireFromHere = createRequire(import.meta.url);
20
+
21
+ /**
22
+ * Resolve the exact version of `@stravica-ai/digital-operator-pa` that is
23
+ * running `pa-init` right now. Two lookup paths:
24
+ *
25
+ * 1. `require.resolve('@stravica-ai/digital-operator-pa/package.json')` — hits
26
+ * when pa-init runs out of a real installed tarball
27
+ * (`node_modules/@stravica-ai/digital-operator-pa/dist/engineering-pa-scaffold/src/generator.mjs`
28
+ * → `node_modules/@stravica-ai/digital-operator-pa/package.json`).
29
+ * 2. Sibling walk to `packages/engineering-pa/package.json` — hits when
30
+ * pa-init runs out of the source monorepo (dev + CI test runs).
31
+ *
32
+ * Returns the version string, or `null` if neither path resolves. `null`
33
+ * is the "no verifiable version" outcome — the caller falls back to the
34
+ * old `^0.1.0` default so a broken lookup never breaks the scaffold.
35
+ *
36
+ * Why this exists: on the WSD-laptop cold install of 0.1.41, pnpm's
37
+ * default `minimumReleaseAge` gated the freshly-published tarball out of
38
+ * `^0.1.0` and the instance silently resolved to 0.1.39 — a version with
39
+ * no `jira` / `confluence` / `confluence-fetch` bins. Pinning the exact
40
+ * version the running scaffolder came from kills that class of drift.
41
+ */
42
+ export function resolveInstalledDopVersion() {
43
+ // Path 1: `require.resolve` from another consumer's tree — hits when
44
+ // pa-init is invoked from the bootstrap dir, where the tarball sits at
45
+ // `node_modules/@stravica-ai/digital-operator-pa/`. The scaffold module
46
+ // itself lives INSIDE the tarball, so this resolve typically fails
47
+ // when this module runs from within `dist/engineering-pa-scaffold/`;
48
+ // path 3 catches that case.
49
+ try {
50
+ const pkgPath = requireFromHere.resolve('@stravica-ai/digital-operator-pa/package.json');
51
+ const meta = JSON.parse(readFileSync(pkgPath, 'utf8'));
52
+ if (meta && typeof meta.version === 'string' && meta.version.length > 0) {
53
+ return meta.version;
54
+ }
55
+ } catch { /* fall through */ }
56
+ // Path 2: monorepo sibling walk — hits when pa-init runs directly
57
+ // from the source tree (dev + CI test runs). `packages/scaffold/src/`
58
+ // -> `packages/engineering-pa/package.json`.
59
+ const monorepoUmbrella = resolve(__dirname, '..', '..', 'engineering-pa', 'package.json');
60
+ if (existsSync(monorepoUmbrella)) {
61
+ try {
62
+ const meta = JSON.parse(readFileSync(monorepoUmbrella, 'utf8'));
63
+ if (meta && typeof meta.version === 'string' && meta.version.length > 0) {
64
+ return meta.version;
65
+ }
66
+ } catch { /* fall through */ }
67
+ }
68
+ // Path 3: packed-tarball walk — hits when this module runs from
69
+ // inside the umbrella tarball's dist tree. Layout is:
70
+ // <install>/node_modules/@stravica-ai/digital-operator-pa/package.json <-- want this
71
+ // <install>/node_modules/@stravica-ai/digital-operator-pa/dist/engineering-pa-scaffold/src/generator.mjs
72
+ // Walk upwards from `__dirname` looking for a `package.json` whose
73
+ // `name` is `@stravica-ai/digital-operator-pa`.
74
+ let cursor = __dirname;
75
+ for (let i = 0; i < 8; i += 1) {
76
+ const candidate = resolve(cursor, 'package.json');
77
+ if (existsSync(candidate)) {
78
+ try {
79
+ const meta = JSON.parse(readFileSync(candidate, 'utf8'));
80
+ if (
81
+ meta
82
+ && meta.name === '@stravica-ai/digital-operator-pa'
83
+ && typeof meta.version === 'string'
84
+ && meta.version.length > 0
85
+ ) {
86
+ return meta.version;
87
+ }
88
+ } catch { /* keep walking */ }
89
+ }
90
+ const next = resolve(cursor, '..');
91
+ if (next === cursor) break;
92
+ cursor = next;
93
+ }
94
+ return null;
95
+ }
18
96
 
19
97
  /** Path to the CLAUDE.md template (relative to this module). */
20
98
  export const CLAUDE_MD_TEMPLATE_PATH = resolve(__dirname, '..', 'templates', 'CLAUDE.md.template');
@@ -248,6 +326,37 @@ export function buildGitignore() {
248
326
  ].join('\n');
249
327
  }
250
328
 
329
+ /**
330
+ * Build the instance-level `.npmrc` contents.
331
+ *
332
+ * Overrides pnpm's default `minimumReleaseAge` (in effect since pnpm 10)
333
+ * FOR THE `@stravica-ai` scope only. Rationale from the WSD-laptop cold
334
+ * install of 0.1.41 (2026-09-16): pnpm's supply-chain gate silently
335
+ * resolves a freshly-published tarball's caret range down to the previous
336
+ * version — the instance ended up on 0.1.39 with no `jira` / `confluence`
337
+ * / `confluence-fetch` bins, and the guide's Atlassian step failed with
338
+ * "Command jira not found" on the very version the guide had just
339
+ * installed. The scaffold already pins an EXACT dependency spec (see
340
+ * `buildOperatorPackageJson` and `resolveInstalledDopVersion`), but an
341
+ * exact spec can still be filtered out under `minimumReleaseAge`; the
342
+ * scoped override belt-and-braces the pin so `pnpm install` inside the
343
+ * scaffolded instance always resolves the version the pin names.
344
+ * Non-`@stravica-ai` packages continue to honour the operator's own
345
+ * `minimumReleaseAge` if they have one set globally.
346
+ */
347
+ export function buildInstanceNpmrc() {
348
+ return [
349
+ '# @stravica-ai scope override for pnpm supply-chain gating.',
350
+ '# pa-init pins an exact @stravica-ai/digital-operator-pa version in package.json.',
351
+ '# pnpm\'s default minimumReleaseAge can otherwise filter a freshly-published',
352
+ '# tarball out of resolution and drop the instance onto a previous version',
353
+ '# with a different bin surface (WSD-laptop cold-install regression, 0.1.41).',
354
+ '# Non-@stravica-ai packages still honour any minimumReleaseAge set globally.',
355
+ '@stravica-ai:minimumReleaseAge=0',
356
+ '',
357
+ ].join('\n');
358
+ }
359
+
251
360
  /**
252
361
  * Build the operator/config/preferences.json contents.
253
362
  *
@@ -546,6 +655,7 @@ export function buildPlanFromTemplates(config, templates) {
546
655
  plan.push({ kind: 'json', path: 'package.json', contents: pkg });
547
656
  plan.push({ kind: 'file', path: '.gitignore', contents: gitignore });
548
657
  plan.push({ kind: 'file', path: '.nvmrc', contents: '24\n' });
658
+ plan.push({ kind: 'file', path: '.npmrc', contents: buildInstanceNpmrc() });
549
659
 
550
660
  plan.push({ kind: 'json', path: '.claude/settings.json', contents: buildClaudeSettingsJson() });
551
661
  // /research slash command: rendered from the packaged core doc so the
@@ -21,17 +21,28 @@ Linux and WSL are equivalent. Install Node 24 through your platform's normal cha
21
21
 
22
22
  ```sh
23
23
  mkdir ~/pa-bootstrap && cd ~/pa-bootstrap
24
- pnpm init -y
24
+ # pnpm 11.14 rejects the `devEngines.packageManager` line `pnpm init -y` writes,
25
+ # and `-y` is silently ignored, so write a minimal package.json directly:
26
+ cat > package.json <<'JSON'
27
+ { "name": "pa-bootstrap", "private": true }
28
+ JSON
25
29
  pnpm add @stravica-ai/digital-operator-pa
26
30
  pnpm exec pa-init --mode=solo --name=<pa-name> --operator=<your-name> \
27
31
  --target=$HOME/<pa-name>-instance --non-interactive
28
32
  cd $HOME/<pa-name>-instance
29
- pnpm install
33
+ git config user.name "Your Name" # cold laptops have no global git identity
34
+ git config user.email "you@example.com" # local to this repo; skip if you already have a global one
30
35
  git add -A && git commit -m "scaffold"
31
36
  ```
32
37
 
33
38
  Pick a `<pa-name>` in lowercase kebab-case (`alex`, `sam`, `hal-9000`). It becomes the agent directory (`agents/<pa-name>/`) and shows up in every log line.
34
39
 
40
+ `pa-init` writes the instance's `package.json` with an EXACT `@stravica-ai/digital-operator-pa` dependency (the version it just installed into `pa-bootstrap`, not a caret range) and drops an `.npmrc` in the instance with `@stravica-ai:minimumReleaseAge=0`. Both belt-and-brace pnpm's supply-chain gate: without them a freshly-published tarball can silently resolve down to the previous version, which may ship a different bin surface.
41
+
42
+ `pnpm install` in the instance runs automatically as the last step of `pa-init` (skip it with `--skip-install` for CI). The `pa status` verification also runs at the end and prints the resolved package version; a mismatch between the scaffold pin and the installed version prints a WARNING line naming both, so a broken guard surfaces immediately instead of hiding behind a "Command not found" later.
43
+
44
+ **Do not put the instance under a directory that already contains a `pnpm-workspace.yaml`.** pnpm treats the instance as a workspace member, `pnpm install` silently no-ops, and `node_modules/.bin/pa` never appears. Keep `--target` outside any pnpm workspace root (a top-level `$HOME/<pa-name>-instance` as shown above is safe).
45
+
35
46
  ## 2. Sanity-check the install
36
47
 
37
48
  ```sh
@@ -45,10 +56,11 @@ pnpm exec pa knowledge context # first line: library.mode = simple (variant
45
56
  ## 3. Start the workspace
46
57
 
47
58
  ```sh
48
- pnpm exec pa workspace start # opens http://localhost:4200
59
+ pnpm exec pa workspace start # opens http://localhost:4200
60
+ pnpm exec pa workspace start --port 4300 # pick a different port if 4200 is taken
49
61
  ```
50
62
 
51
- The workspace serves a viewer, a home page, and an onboarding form. Long notes from your PA land here rather than in chat.
63
+ The workspace serves a viewer, a home page, and an onboarding form. Long notes from your PA land here rather than in chat. The default port is `4200` (localhost only); the server refuses to start with `Error: Port 4200 is already in use by another process.` when the port is bound by something else (a docker container, another dev server). Pick a free port with `--port N` or set `WORKSPACE_PORT` in your environment; `pa status` reads the resolved port from `operator/config/preferences.json` so keep the two in step for the port to persist across restarts.
52
64
 
53
65
  ## 4. Start your first session
54
66
 
@@ -114,7 +126,7 @@ Your PA has the `rcf-lite-engineer` role baked in. It builds greenfield applicat
114
126
 
115
127
  The `@stravica-ai/digital-operator-pa` package ships three Atlassian CLIs on `PATH`: `jira` (issues, search, transitions, worklogs), `confluence` (spaces, pages, CQL search, mirror), and `confluence-fetch` (search + fetch + save a page under `operator/notes/`). Run `pnpm exec jira --help`, `pnpm exec confluence --help`, `pnpm exec confluence-fetch --help` for the verbs.
116
128
 
117
- Both CLIs share three credentials pointing at the same Atlassian site. Two-step setup:
129
+ Both CLIs share three credentials pointing at the same Atlassian site. Setup:
118
130
 
119
131
  ```sh
120
132
  pnpm exec secrets set JIRA_DOMAIN yoursite # for yoursite.atlassian.net
@@ -122,6 +134,8 @@ pnpm exec secrets set JIRA_EMAIL you@example.com # your Atlassian accoun
122
134
  pnpm exec secrets set JIRA_API_TOKEN <paste-token> # id.atlassian.com/manage-profile/security/api-tokens
123
135
  ```
124
136
 
137
+ `secrets set` self-initialises the vault (age keypair, `.vault/recipients.txt`, empty encrypted store) on the first write when there is nothing at `.vault/` yet. It prints a one-line notice on stderr saying so and continues. You do not need to run `secrets init` manually first. Every `secrets set` also prints one stderr line naming which vault it wrote to; on a fresh solo install with no scope configured that says the write went to the `(legacy)` pot (no scope specified, no default configured). Informational, safe to ignore. Set `SECRETS_DEFAULT_SCOPE` in your environment or configure `defaultScope` in `~/.config/stravica-pa/defaults.json` if you want a named scope; solo mode does not require one.
138
+
125
139
  Verify with `pnpm exec jira auth` and `pnpm exec confluence auth`; each prints the current user on success. Credentials also resolve from environment variables of the same names, so a `.env` or shell profile works as an alternative to `secrets`. The `atlassian` entry in `operator/extensions/manifest.json` records the extension is enabled and repeats the setup line.
126
140
 
127
141
  `jira mine` returns your open issues across every project the account can see. If you spend most of your Jira time in one project, set `JIRA_DEFAULT_PROJECT` (env var or a vault key of the same name) and `jira mine` narrows to it unless you pass `--project KEY` or `--all`:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stravica-ai/digital-operator-pa",
3
- "version": "0.1.41",
3
+ "version": "0.1.42",
4
4
  "description": "Digital Operator PA — single-bundled public package surface (bins + library) for the platform.",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",