@stravica-ai/digital-operator-pa 0.1.47 → 0.1.49
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/dist/engineering-pa-cli/src/pa/cli.mjs +24 -1
- package/dist/engineering-pa-core/docs/commands/onboard.md +81 -7
- package/dist/engineering-pa-core/src/distill/cli.mjs +65 -0
- package/dist/engineering-pa-core/src/distill/health.mjs +322 -0
- package/dist/engineering-pa-scaffold/package.json +3 -2
- package/dist/engineering-pa-scaffold/src/commands/init-workspace-extension.mjs +37 -5
- package/dist/engineering-pa-scaffold/src/commands/init.mjs +7 -1
- package/dist/engineering-pa-scaffold/src/generator.mjs +220 -11
- package/dist/engineering-pa-workspace/app/assets/theme.css +3 -2
- package/dist/engineering-pa-workspace/app/assets/theme.js +29 -6
- package/dist/engineering-pa-workspace/app/banner.js +14 -2
- package/dist/engineering-pa-workspace/app/delegations.html +1 -1
- package/dist/engineering-pa-workspace/app/home.html +1 -1
- package/dist/engineering-pa-workspace/app/index.html +1 -1
- package/dist/engineering-pa-workspace/app/knowledge-map.html +1 -1
- package/dist/engineering-pa-workspace/app/knowledge.html +1 -1
- package/dist/engineering-pa-workspace/app/notes.html +1 -1
- package/dist/engineering-pa-workspace/app/onboarding.html +1 -1
- package/dist/engineering-pa-workspace/app/profile.html +1 -1
- package/dist/engineering-pa-workspace/app/project-detail.html +1 -1
- package/dist/engineering-pa-workspace/app/projects.html +1 -1
- package/dist/engineering-pa-workspace/app/repos.html +1 -1
- package/dist/engineering-pa-workspace/app/schedules.html +1 -1
- package/dist/engineering-pa-workspace/app/secrets.html +1 -1
- package/dist/engineering-pa-workspace/app/staging.html +1 -1
- package/dist/engineering-pa-workspace/app/tasks.html +1 -1
- package/dist/engineering-pa-workspace/app/viewer.html +1 -1
- package/dist/engineering-pa-workspace/index.mjs +16 -0
- package/dist/engineering-pa-workspace/package.json +3 -2
- package/dist/engineering-pa-workspace/src/repo-root.mjs +26 -1
- package/dist/engineering-pa-workspace/src/server.mjs +4 -3
- package/dist/engineering-pa-workspace/src/workspace-page-contract.mjs +98 -0
- package/dist/engineering-pa-workspace/src/ws.mjs +2 -0
- package/docs/onboarding-solo.md +4 -4
- package/package.json +2 -1
|
@@ -55,14 +55,30 @@ export function walkUpForMarker(start) {
|
|
|
55
55
|
return null;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
// Process-level guard so the fallback warning fires at most once per
|
|
59
|
+
// process, no matter how many callers hit the resolver. Tests can reset
|
|
60
|
+
// via the exported `_resetFallbackWarningForTests` helper below.
|
|
61
|
+
let _fallbackWarned = false;
|
|
62
|
+
|
|
63
|
+
/** Test-only reset for the once-per-process fallback warning latch. */
|
|
64
|
+
export function _resetFallbackWarningForTests() {
|
|
65
|
+
_fallbackWarned = false;
|
|
66
|
+
}
|
|
67
|
+
|
|
58
68
|
/**
|
|
59
69
|
* Resolve the operator instance root. Deterministic and synchronous; safe
|
|
60
70
|
* to call at module top-level. See the header comment for the full
|
|
61
71
|
* precedence rule.
|
|
62
72
|
*
|
|
73
|
+
* When no marker is found AND no env override is set, emit ONE stderr
|
|
74
|
+
* warning naming the cwd we're falling back to and the two overrides that
|
|
75
|
+
* would silence it. The walk still returns cwd so pre-scaffold and dry-run
|
|
76
|
+
* flows keep working — the warning is a diagnostic, not an error.
|
|
77
|
+
*
|
|
63
78
|
* @param {object} [opts]
|
|
64
79
|
* @param {string} [opts.startDir=process.cwd()] Directory to start the walk from.
|
|
65
80
|
* @param {NodeJS.ProcessEnv} [opts.env=process.env] Env source (tests inject).
|
|
81
|
+
* @param {(msg: string) => void} [opts.warn] Optional warn sink (tests inject).
|
|
66
82
|
* @returns {string} Absolute path.
|
|
67
83
|
*/
|
|
68
84
|
export function findRepoRootSync(opts = {}) {
|
|
@@ -71,5 +87,14 @@ export function findRepoRootSync(opts = {}) {
|
|
|
71
87
|
const envRoot = env.DAVE_REPO_ROOT || env.PA_REPO_ROOT;
|
|
72
88
|
if (envRoot) return resolve(envRoot);
|
|
73
89
|
const found = walkUpForMarker(startDir);
|
|
74
|
-
|
|
90
|
+
if (found) return found;
|
|
91
|
+
const fallback = resolve(startDir);
|
|
92
|
+
if (!_fallbackWarned) {
|
|
93
|
+
_fallbackWarned = true;
|
|
94
|
+
const warn = opts.warn || ((m) => process.stderr.write(m + '\n'));
|
|
95
|
+
warn(
|
|
96
|
+
`workspace: no instance marker (.git, operator/config/preferences.json, pnpm-workspace.yaml) found walking up from ${fallback}; falling back to cwd. Set DAVE_REPO_ROOT or PA_REPO_ROOT to silence.`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return fallback;
|
|
75
100
|
}
|
|
@@ -218,8 +218,9 @@ function resetIdle() {
|
|
|
218
218
|
// palette block, which broke the theme toggle on the content-calendar
|
|
219
219
|
// preview iframe (operator-reported, 2026-06-27). The fix matches what
|
|
220
220
|
// viewer.html does: link the shared theme.css, defer-load theme.js, pre-paint
|
|
221
|
-
// init reads localStorage '
|
|
222
|
-
//
|
|
221
|
+
// init reads localStorage 'dop.theme' (with a one-time legacy 'dave.theme'
|
|
222
|
+
// fallback), body in humanist sans for prose, and .md-body strong uses the
|
|
223
|
+
// new --emphasis token (matching .content strong).
|
|
223
224
|
// Exported so unit tests can assert on the output without spinning up the
|
|
224
225
|
// HTTP server.
|
|
225
226
|
export function markdownPage(rawContent, filePath) {
|
|
@@ -234,7 +235,7 @@ export function markdownPage(rawContent, filePath) {
|
|
|
234
235
|
<meta charset="UTF-8">
|
|
235
236
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
236
237
|
<title>${title}</title>
|
|
237
|
-
<script>(function(){try{var t=localStorage.getItem('dave.theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
238
|
+
<script>(function(){try{var t=localStorage.getItem('dop.theme');if(!t){var legacy=localStorage.getItem('dave.theme');if(legacy==='light'||legacy==='dark'){t=legacy;try{localStorage.setItem('dop.theme',legacy);}catch(_){}}}document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>
|
|
238
239
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
239
240
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
240
241
|
<style>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// workspace-page-contract.mjs — the single source of truth for the head
|
|
2
|
+
// and top-of-body markup every workspace HTML page must ship.
|
|
3
|
+
//
|
|
4
|
+
// Every page served by the workspace (core pages under `packages/workspace/app/`
|
|
5
|
+
// and extension pages scaffolded by `pa-init workspace-extension`) has to
|
|
6
|
+
// carry the same four head tags in the same order:
|
|
7
|
+
//
|
|
8
|
+
// 1. Inline pre-paint script that reads the theme from localStorage and
|
|
9
|
+
// sets `documentElement.dataset.theme` BEFORE first paint. Without
|
|
10
|
+
// this, dark-preference users see a light-flash (FOUC).
|
|
11
|
+
// 2. Link to `/core/tools/workspace/app/assets/theme.css` (the shared
|
|
12
|
+
// palette + `[data-theme=...]` token blocks).
|
|
13
|
+
// 3. Deferred `/core/tools/workspace/app/assets/theme.js` (toggle button,
|
|
14
|
+
// cross-frame coordination, storage sync).
|
|
15
|
+
// 4. Deferred `/banner.js` (the global status banner poller).
|
|
16
|
+
//
|
|
17
|
+
// And the body's first child must be `<div id="global-banner"></div>` so
|
|
18
|
+
// `banner.js` has a mount point.
|
|
19
|
+
//
|
|
20
|
+
// This module owns the string forms of all four so a scaffold-generated
|
|
21
|
+
// extension page and a hand-authored core page can never drift. Core pages
|
|
22
|
+
// still hand-copy the block for now (their inline `<style>` sits after
|
|
23
|
+
// theme.js in the head, so a full migration is more than a mechanical
|
|
24
|
+
// substitution and is left as a follow-up); the scaffold and the theme
|
|
25
|
+
// parity test import from here.
|
|
26
|
+
//
|
|
27
|
+
// Storage-key policy: the theme key is `'dop.theme'` (product-generic —
|
|
28
|
+
// `dop` = Digital Operator PA). Legacy stores under `'dave.theme'` are
|
|
29
|
+
// migrated on first read (see `theme.js`) so nobody re-picks their theme
|
|
30
|
+
// after the rename.
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* localStorage key the theme selection persists under. Product-generic;
|
|
34
|
+
* shared by the pre-paint bootstrap, theme.js, and every consuming page.
|
|
35
|
+
*/
|
|
36
|
+
export const THEME_STORAGE_KEY = 'dop.theme';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Legacy localStorage key. Read once on first boot when the new key is
|
|
40
|
+
* absent and copied forward. Never written. Kept exported so tests can
|
|
41
|
+
* pin the migration path without hardcoding the literal.
|
|
42
|
+
*/
|
|
43
|
+
export const THEME_LEGACY_STORAGE_KEY = 'dave.theme';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* postMessage type the shell uses to broadcast a theme change into
|
|
47
|
+
* same-origin iframed pages. Product-generic; renamed alongside the
|
|
48
|
+
* storage key.
|
|
49
|
+
*/
|
|
50
|
+
export const THEME_MESSAGE_TYPE = 'dop.theme.set';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The inline pre-paint bootstrap. Reads the new key, falls back to the
|
|
54
|
+
* legacy key when the new one is unset, applies to
|
|
55
|
+
* `documentElement.dataset.theme` before first paint. Same shape the
|
|
56
|
+
* markdownPage renderer emits so a fresh direct-load never flashes.
|
|
57
|
+
*
|
|
58
|
+
* Returned as a self-contained `<script>...</script>` tag; embed it in
|
|
59
|
+
* `<head>` BEFORE the theme.css link and before any body content.
|
|
60
|
+
*/
|
|
61
|
+
export function prepaintScriptTag() {
|
|
62
|
+
return `<script>(function(){try{var t=localStorage.getItem('${THEME_STORAGE_KEY}');if(!t){var legacy=localStorage.getItem('${THEME_LEGACY_STORAGE_KEY}');if(legacy==='light'||legacy==='dark'){t=legacy;try{localStorage.setItem('${THEME_STORAGE_KEY}',legacy);}catch(_){}}}document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(_){document.documentElement.dataset.theme='dark';}})();</script>`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Link tag for the shared theme stylesheet. */
|
|
66
|
+
export const THEME_CSS_LINK = '<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">';
|
|
67
|
+
|
|
68
|
+
/** Deferred script tag for the shared theme.js toggle + storage coordinator. */
|
|
69
|
+
export const THEME_JS_TAG = '<script src="/core/tools/workspace/app/assets/theme.js" defer></script>';
|
|
70
|
+
|
|
71
|
+
/** Deferred script tag for the global status banner. */
|
|
72
|
+
export const BANNER_JS_TAG = '<script src="/banner.js" defer></script>';
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The mount point banner.js expects. Insert as the first child of `<body>`.
|
|
76
|
+
*/
|
|
77
|
+
export const GLOBAL_BANNER_HTML = '<div id="global-banner"></div>';
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Build the standard `<head>` inner markup a workspace page must ship.
|
|
81
|
+
* Charset, viewport, `<title>`, then the four theme + banner tags in the
|
|
82
|
+
* documented order. Callers append their own `<style>` block (if any) AFTER
|
|
83
|
+
* the returned string and BEFORE closing `</head>`.
|
|
84
|
+
*
|
|
85
|
+
* @param {object} opts
|
|
86
|
+
* @param {string} opts.title page title (already HTML-escaped by caller)
|
|
87
|
+
*/
|
|
88
|
+
export function standardHeadInner({ title }) {
|
|
89
|
+
return [
|
|
90
|
+
'<meta charset="utf-8" />',
|
|
91
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
92
|
+
`<title>${title}</title>`,
|
|
93
|
+
prepaintScriptTag(),
|
|
94
|
+
THEME_CSS_LINK,
|
|
95
|
+
THEME_JS_TAG,
|
|
96
|
+
BANNER_JS_TAG,
|
|
97
|
+
].join('\n');
|
|
98
|
+
}
|
|
@@ -336,6 +336,7 @@ async function cmdStart() {
|
|
|
336
336
|
const who = holder ? ` (held by PID ${holder.pid}${holder.command ? ` — ${holder.command}` : ''})` : '';
|
|
337
337
|
console.error(`Error: Stale PID file references dead PID ${existing.pid}, but port ${existing.port} is in use${who}.`);
|
|
338
338
|
console.error('Refusing to kill an unrelated process. Remove the PID file manually or use a different port.');
|
|
339
|
+
console.error('Hint: run `pa workspace start --port <N>` with a free port, or set `workspacePort` in operator/config/preferences.json.');
|
|
339
340
|
process.exit(1);
|
|
340
341
|
}
|
|
341
342
|
removePid();
|
|
@@ -344,6 +345,7 @@ async function cmdStart() {
|
|
|
344
345
|
// Check if port is already in use by something else
|
|
345
346
|
if (await checkPort(port)) {
|
|
346
347
|
console.error(`Error: Port ${port} is already in use by another process.`);
|
|
348
|
+
console.error('Hint: run `pa workspace start --port <N>` with a free port, or set `workspacePort` in operator/config/preferences.json.');
|
|
347
349
|
process.exit(1);
|
|
348
350
|
}
|
|
349
351
|
|
package/docs/onboarding-solo.md
CHANGED
|
@@ -37,7 +37,7 @@ cd $HOME/<pa-name>-instance
|
|
|
37
37
|
pnpm exec pa session
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
Inside the session, type `/onboard`. The PA verifies the runtime, checks the installed version against npm, asks you a few profile questions, offers an optional shell alias, offers to start the local workspace, and offers to wire up Atlassian (Jira, Confluence) if you use it. Every step is idempotent; re-run `/onboard` anytime to redo a specific step.
|
|
40
|
+
Inside the session, type `/onboard`. The PA verifies the runtime, checks the installed version against npm, asks you a few profile questions, offers an optional shell alias, offers to start the local workspace, installs and proves the librarian distillation sweep as a mandatory step, and offers to wire up Atlassian (Jira, Confluence) if you use it. Every step is idempotent; re-run `/onboard` anytime to redo a specific step. Onboarding does not complete until the librarian schedule is installed and proven. That is the one piece of scheduling every solo instance needs so staged observations get distilled into knowledge over time.
|
|
41
41
|
|
|
42
42
|
## 3. Alias (optional)
|
|
43
43
|
|
|
@@ -56,11 +56,11 @@ After that, typing `<pa-name>` from anywhere starts a session.
|
|
|
56
56
|
- The read-only `desk` CLI: overview, today, projects, work.
|
|
57
57
|
- A local workspace server on `http://localhost:4200` (start with `pnpm exec pa workspace start`).
|
|
58
58
|
- A SIMPLE-mode local knowledge library: `pa knowledge search / related / domain / orphans / path / context`, seeded with the five base domains.
|
|
59
|
-
- Self-learning loop primitives: `agents/<pa-name>/staging/`, `journal.md`, `next-session.md`, `spawn-log.json`, autonomy envelope, and the Librarian distillation sweep
|
|
59
|
+
- Self-learning loop primitives: `agents/<pa-name>/staging/`, `journal.md`, `next-session.md`, `spawn-log.json`, autonomy envelope, and the Librarian distillation sweep, scaffolded as `schedules/<pa-name>-distillation-sweep.json` (every 4 hours) and installed by `/onboard` on the OS-native backend (launchd on macOS, cron on Linux). One immediate run is fired at install time so the ledger at `.cache/distill/logs/runs.jsonl` proves the schedule wiring end to end before onboarding completes.
|
|
60
60
|
- The `research` CLI with the light / normal / deep session store persisted under `.cache/research/`.
|
|
61
61
|
- Scheduler (cron on Linux, launchd on macOS): `pnpm exec scheduler --help`.
|
|
62
62
|
- Secrets vault (SOPS + age) if you opt in: `pnpm exec secrets --help`.
|
|
63
|
-
- `.claude/settings.json` with the `pa-coordinator-hook` wired in as a `PreToolUse` hook (inert until you export `PA_COORDINATOR_MODE=1`) and `deep-research` denied at the permission layer.
|
|
63
|
+
- `.claude/settings.json` with the `pa-coordinator-hook` wired in as a `PreToolUse` hook (inert until you export `PA_COORDINATOR_MODE=1`), a `SessionStart` hook that injects a live `distillationHealth` block at the top of every session (schedule state + last-run age + staging count), and `deep-research` denied at the permission layer.
|
|
64
64
|
- Two slash commands out of the box: `/research` (persisted research sessions) and `/onboard` (this walkthrough).
|
|
65
65
|
- Workspace-extension prompting: `operator/knowledge/platform-tooling/workspace-extensions.md` teaches your PA when to propose a workspace page and `pa-init workspace-extension <name>` scaffolds the HTML skeleton plus a `nav.json` entry. Add `--with-api` when the page needs a server route.
|
|
66
66
|
|
|
@@ -70,7 +70,7 @@ Your PA has the `rcf-lite-engineer` role baked in. It builds greenfield applicat
|
|
|
70
70
|
|
|
71
71
|
## What is not included in solo mode
|
|
72
72
|
|
|
73
|
-
-
|
|
73
|
+
- **A full workState boot-context hook.** In some estates a `SessionStart` hook injects the live in-flight work items, active projects, and on-you list at the top of every session. Solo mode ships the narrower distillation-health hook (schedule + last-run + staging count) rather than a full workState block. If you want the broader one, extend `.claude/settings.json` with your own `SessionStart` hook.
|
|
74
74
|
- **Team modes.** `pa-init --mode=lead` and `--mode=member` exit with "not implemented". The SIMPLE-mode local library is single-operator by design.
|
|
75
75
|
- **Remote knowledge library.** The `remote` library mode requires a service on a persistent host, an age keypair, and a tunnel. Solo mode stays SIMPLE and runs entirely on your one laptop.
|
|
76
76
|
- **HQ-specialised roles.** The bundled role set is the core seven plus `product-engineer` and `rcf-lite-engineer` from the engineering types pack. Other tier-ladder roles and estate-specific extensions are not shipped.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stravica-ai/digital-operator-pa",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.49",
|
|
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",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
],
|
|
41
41
|
"license": "MIT",
|
|
42
42
|
"homepage": "https://stravica.ai",
|
|
43
|
+
"bugs": "https://stravica.ai",
|
|
43
44
|
"publishConfig": {
|
|
44
45
|
"registry": "https://registry.npmjs.org/",
|
|
45
46
|
"access": "public"
|