@stravica-ai/digital-operator-pa 0.1.49 → 0.1.50
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 +47 -1
- package/dist/engineering-pa-cli/src/pa/onboard.mjs +114 -0
- package/dist/engineering-pa-core/docs/commands/onboard.md +20 -20
- package/dist/engineering-pa-core/src/distill/health.mjs +170 -6
- package/dist/engineering-pa-core/src/distill/run.mjs +19 -0
- package/dist/engineering-pa-core/src/instance-root/find.mjs +104 -0
- package/dist/engineering-pa-core/src/knowledge/retrieval/backlink-indexer.mjs +6 -1
- package/dist/engineering-pa-core/src/knowledge/retrieval/graph-builder.mjs +6 -1
- package/dist/engineering-pa-core/src/knowledge/retrieval/index-builder.mjs +4 -1
- package/dist/engineering-pa-core/src/knowledge/retrieval/query.mjs +6 -1
- package/dist/engineering-pa-core/src/scheduler/install.mjs +41 -4
- package/dist/engineering-pa-core/src/scheduler/list.mjs +34 -13
- package/dist/engineering-pa-core/src/scheduler/remove.mjs +2 -1
- package/dist/engineering-pa-core-extensions-atlassian/package.json +3 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/mirror-folder.mjs +4 -2
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/link.mjs +4 -2
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/sync-statuses.mjs +6 -2
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/workflow-chains.mjs +7 -3
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/workflow-map.mjs +9 -3
- package/dist/engineering-pa-scaffold/src/generator.mjs +3 -1
- package/dist/engineering-pa-workspace/app/notes.html +1 -1
- package/dist/engineering-pa-workspace/app/project-detail.html +17 -1
- package/dist/engineering-pa-workspace/app/projects.html +1 -1
- package/dist/engineering-pa-workspace/app/tasks.html +1 -1
- package/dist/engineering-pa-workspace/src/install-daemon.mjs +2 -2
- package/dist/engineering-pa-workspace/src/repo-root.mjs +13 -100
- package/dist/engineering-pa-workspace/src/server.mjs +2 -2
- package/dist/engineering-pa-workspace/src/ws.mjs +79 -10
- package/docs/onboarding-solo.md +1 -1
- package/package.json +1 -1
|
@@ -9,11 +9,14 @@
|
|
|
9
9
|
import { readdir, readFile, writeFile, stat } from 'node:fs/promises';
|
|
10
10
|
import { join, resolve, dirname } from 'node:path';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { findRepoRootSync } from '../../instance-root/find.mjs';
|
|
12
13
|
|
|
13
14
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
15
|
const DEFAULT_ROOT = process.env.DAVE_ROOT
|
|
15
16
|
? resolve(process.env.DAVE_ROOT)
|
|
16
|
-
:
|
|
17
|
+
: process.env.PA_ROOT
|
|
18
|
+
? resolve(process.env.PA_ROOT)
|
|
19
|
+
: findRepoRootSync();
|
|
17
20
|
|
|
18
21
|
const USAGE = `
|
|
19
22
|
index-builder — Build a domain index from operator knowledge directories
|
|
@@ -17,9 +17,14 @@ import { readFile } from 'node:fs/promises';
|
|
|
17
17
|
import { realpathSync } from 'node:fs';
|
|
18
18
|
import { join, resolve, dirname, normalize } from 'node:path';
|
|
19
19
|
import { fileURLToPath } from 'node:url';
|
|
20
|
+
import { findRepoRootSync } from '../../instance-root/find.mjs';
|
|
20
21
|
|
|
21
22
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
|
-
const DEFAULT_ROOT =
|
|
23
|
+
const DEFAULT_ROOT = process.env.DAVE_ROOT
|
|
24
|
+
? resolve(process.env.DAVE_ROOT)
|
|
25
|
+
: process.env.PA_ROOT
|
|
26
|
+
? resolve(process.env.PA_ROOT)
|
|
27
|
+
: findRepoRootSync();
|
|
23
28
|
|
|
24
29
|
const USAGE = `
|
|
25
30
|
query — Query the knowledge graph
|
|
@@ -20,6 +20,7 @@ import { execSync } from 'node:child_process';
|
|
|
20
20
|
import { fileURLToPath } from 'node:url';
|
|
21
21
|
|
|
22
22
|
import { hostMatches } from './host.mjs';
|
|
23
|
+
import { findRepoRootSync } from '../instance-root/find.mjs';
|
|
23
24
|
|
|
24
25
|
const __filename = fileURLToPath(import.meta.url);
|
|
25
26
|
const __dirname = dirname(__filename);
|
|
@@ -84,7 +85,7 @@ function readSchedule(filePath) {
|
|
|
84
85
|
* @returns {string}
|
|
85
86
|
*/
|
|
86
87
|
function computeDaveRoot() {
|
|
87
|
-
return process.env.DAVE_ROOT ||
|
|
88
|
+
return process.env.DAVE_ROOT || process.env.PA_ROOT || findRepoRootSync();
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
/**
|
|
@@ -95,7 +96,9 @@ function computeDaveRoot() {
|
|
|
95
96
|
function resolveWorkDir(workDir) {
|
|
96
97
|
const daveRoot = computeDaveRoot();
|
|
97
98
|
if (!workDir) return daveRoot;
|
|
98
|
-
return workDir
|
|
99
|
+
return workDir
|
|
100
|
+
.replace(/\$\{DAVE_ROOT\}/g, daveRoot)
|
|
101
|
+
.replace(/\$\{PA_ROOT\}/g, daveRoot);
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
/**
|
|
@@ -112,6 +115,7 @@ function substituteVars(value) {
|
|
|
112
115
|
const user = process.env.USER || process.env.USERNAME || '';
|
|
113
116
|
return value
|
|
114
117
|
.replace(/\$\{DAVE_ROOT\}/g, daveRoot)
|
|
118
|
+
.replace(/\$\{PA_ROOT\}/g, daveRoot)
|
|
115
119
|
.replace(/\$\{HOME\}/g, home)
|
|
116
120
|
.replace(/\$\{USER\}/g, user);
|
|
117
121
|
}
|
|
@@ -347,6 +351,29 @@ export function defaultCronLogPath(sched) {
|
|
|
347
351
|
* @param {string} [opts.logPath]
|
|
348
352
|
* @returns {{ inner: string, wrapped: string }}
|
|
349
353
|
*/
|
|
354
|
+
/**
|
|
355
|
+
* When the schedule's argv[0] is a bare `node`, swap in the absolute
|
|
356
|
+
* interpreter path so cron does not depend on the shell PATH surfacing the
|
|
357
|
+
* right node. Absolute argv[0] passes through unchanged. Non-node bare
|
|
358
|
+
* binaries are left alone (there is no equivalent for arbitrary tools).
|
|
359
|
+
*
|
|
360
|
+
* @param {string} command
|
|
361
|
+
* @param {string} execPath
|
|
362
|
+
* @returns {string}
|
|
363
|
+
*/
|
|
364
|
+
export function resolveCronArgv0Command(command, execPath) {
|
|
365
|
+
if (typeof command !== 'string' || command.length === 0) return command;
|
|
366
|
+
const parts = command.split(/\s+/);
|
|
367
|
+
const argv0 = parts[0];
|
|
368
|
+
if (!argv0) return command;
|
|
369
|
+
if (isAbsolute(argv0)) return command;
|
|
370
|
+
if (argv0 === 'node' && typeof execPath === 'string' && execPath.length > 0) {
|
|
371
|
+
parts[0] = execPath;
|
|
372
|
+
return parts.join(' ');
|
|
373
|
+
}
|
|
374
|
+
return command;
|
|
375
|
+
}
|
|
376
|
+
|
|
350
377
|
export function buildCronCommand(sched, workDir, env, opts = {}) {
|
|
351
378
|
const hasNvm = !!opts.hasNvm;
|
|
352
379
|
const logPath = opts.logPath || defaultCronLogPath(sched);
|
|
@@ -354,6 +381,13 @@ export function buildCronCommand(sched, workDir, env, opts = {}) {
|
|
|
354
381
|
const exportStmts = Object.entries(env)
|
|
355
382
|
.map(([k, v]) => `export ${k}='${String(v).replace(/'/g, `'\\''`)}'; `)
|
|
356
383
|
.join('');
|
|
384
|
+
// Resolve argv[0] to an absolute path when we can. cron minimal-env spawns
|
|
385
|
+
// /bin/sh without a full user PATH, so a bare `node` may fail to find the
|
|
386
|
+
// interpreter the host actually runs under. When argv[0] is `node`, swap
|
|
387
|
+
// in `process.execPath` (the interpreter running the installer). Absolute
|
|
388
|
+
// paths pass through. Other bare argv[0] values are left alone; the nvm
|
|
389
|
+
// preamble (when present) and cron's own PATH remain the fallback.
|
|
390
|
+
const resolvedCommand = resolveCronArgv0Command(sched.command, opts.execPath || process.execPath);
|
|
357
391
|
// Whole rendered chain is braced so `>> logPath 2>&1` binds to the group,
|
|
358
392
|
// not just the last simple command. Without the braces bash parses
|
|
359
393
|
// `A && B && cmd >> log 2>&1` with the redirect bound to `cmd` alone, so
|
|
@@ -366,7 +400,7 @@ export function buildCronCommand(sched, workDir, env, opts = {}) {
|
|
|
366
400
|
// the crontab line through /bin/sh -c '...' which parses the double-quoted
|
|
367
401
|
// bash-c argument and would eagerly expand $(...) at dispatch time, not
|
|
368
402
|
// at run time.
|
|
369
|
-
const chain = `date -u; ${preamble}${exportStmts}cd ${workDir} && ${
|
|
403
|
+
const chain = `date -u; ${preamble}${exportStmts}cd ${workDir} && ${resolvedCommand}`;
|
|
370
404
|
const inner = `{ ${chain}; } >> ${logPath} 2>&1`;
|
|
371
405
|
const wrapped = `bash -c "${inner.replace(/"/g, '\\"')}"`;
|
|
372
406
|
return { inner, wrapped };
|
|
@@ -428,10 +462,12 @@ function installCron(sched, workDir) {
|
|
|
428
462
|
* @returns {Record<string,string>}
|
|
429
463
|
*/
|
|
430
464
|
export function withLaunchdDefaults(explicitEnv) {
|
|
465
|
+
const root = computeDaveRoot();
|
|
431
466
|
const defaults = {
|
|
432
467
|
PATH: process.env.PATH || '/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin',
|
|
433
468
|
HOME: homedir(),
|
|
434
|
-
DAVE_ROOT:
|
|
469
|
+
DAVE_ROOT: root,
|
|
470
|
+
PA_ROOT: root,
|
|
435
471
|
};
|
|
436
472
|
return { ...defaults, ...explicitEnv };
|
|
437
473
|
}
|
|
@@ -673,6 +709,7 @@ export function buildLaunchdPlist(sched, workDir, cron) {
|
|
|
673
709
|
|
|
674
710
|
const mergedEnv = withLaunchdDefaults(resolveEnvironment(sched));
|
|
675
711
|
validateLaunchdDaveRoot(mergedEnv.DAVE_ROOT);
|
|
712
|
+
validateLaunchdDaveRoot(mergedEnv.PA_ROOT);
|
|
676
713
|
const envBlock = buildLaunchdEnvBlock(mergedEnv);
|
|
677
714
|
|
|
678
715
|
// launchd does NOT PATH-resolve argv[0]; a bare `node`/`pnpm`/etc. fails
|
|
@@ -17,25 +17,46 @@ import { detect } from './detect.mjs';
|
|
|
17
17
|
import { launchdLabelPrefix } from './install.mjs';
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* Render the leading name for the listing header. Uses the agent id when
|
|
21
|
+
* one is supplied, else a generic label so instances without an agent id
|
|
22
|
+
* (rare, but possible for pre-scaffold flows) still get a sensible line.
|
|
23
|
+
*
|
|
24
|
+
* Tag prefixes on the crontab / launchd / systemd / schtasks side are NOT
|
|
25
|
+
* renamed here \u2014 those are load-bearing for removal on existing installs
|
|
26
|
+
* (they are still `dave-` for backward compat).
|
|
27
|
+
*
|
|
28
|
+
* @param {string} [agent]
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
function headerName(agent) {
|
|
32
|
+
if (typeof agent === 'string' && agent.length > 0) return agent;
|
|
33
|
+
return 'Schedules';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* List schedules for a given backend.
|
|
21
38
|
* @param {string} backend - Backend name (cron, launchd, systemd, schtasks)
|
|
22
39
|
* @param {object} [opts]
|
|
23
|
-
* @param {string} [opts.agent] -
|
|
40
|
+
* @param {string} [opts.agent] - The agent id used for the header AND, on
|
|
41
|
+
* launchd, the label prefix to filter by. Defaults to 'dave' for the
|
|
42
|
+
* launchd label prefix (unchanged pre-feature behaviour); header defaults
|
|
43
|
+
* to 'Schedules' when omitted.
|
|
24
44
|
* @returns {string} Human-readable listing
|
|
25
45
|
*/
|
|
26
46
|
export function list(backend, opts = {}) {
|
|
47
|
+
const agent = opts.agent;
|
|
27
48
|
switch (backend) {
|
|
28
|
-
case 'cron': return listCron();
|
|
29
|
-
case 'launchd': return listLaunchd(
|
|
30
|
-
case 'systemd': return listSystemd();
|
|
31
|
-
case 'schtasks': return listSchtasks();
|
|
49
|
+
case 'cron': return listCron(agent);
|
|
50
|
+
case 'launchd': return listLaunchd(agent);
|
|
51
|
+
case 'systemd': return listSystemd(agent);
|
|
52
|
+
case 'schtasks': return listSchtasks(agent);
|
|
32
53
|
default:
|
|
33
54
|
throw new Error(`Unknown backend: ${backend}`);
|
|
34
55
|
}
|
|
35
56
|
}
|
|
36
57
|
|
|
37
|
-
function listCron() {
|
|
38
|
-
const lines = [
|
|
58
|
+
function listCron(agent) {
|
|
59
|
+
const lines = [`${headerName(agent)} schedules (cron):`, '\u2500'.repeat(37)];
|
|
39
60
|
let found = false;
|
|
40
61
|
|
|
41
62
|
try {
|
|
@@ -55,7 +76,7 @@ function listCron() {
|
|
|
55
76
|
}
|
|
56
77
|
|
|
57
78
|
function listLaunchd(agent) {
|
|
58
|
-
const lines = [
|
|
79
|
+
const lines = [`${headerName(agent)} schedules (launchd):`, '\u2500'.repeat(37)];
|
|
59
80
|
let found = false;
|
|
60
81
|
|
|
61
82
|
const prefix = launchdLabelPrefix(agent);
|
|
@@ -82,8 +103,8 @@ function listLaunchd(agent) {
|
|
|
82
103
|
return lines.join('\n');
|
|
83
104
|
}
|
|
84
105
|
|
|
85
|
-
function listSystemd() {
|
|
86
|
-
const lines = [
|
|
106
|
+
function listSystemd(agent) {
|
|
107
|
+
const lines = [`${headerName(agent)} schedules (systemd):`, '\u2500'.repeat(37)];
|
|
87
108
|
let found = false;
|
|
88
109
|
|
|
89
110
|
try {
|
|
@@ -102,8 +123,8 @@ function listSystemd() {
|
|
|
102
123
|
return lines.join('\n');
|
|
103
124
|
}
|
|
104
125
|
|
|
105
|
-
function listSchtasks() {
|
|
106
|
-
const lines = [
|
|
126
|
+
function listSchtasks(agent) {
|
|
127
|
+
const lines = [`${headerName(agent)} schedules (schtasks):`, '\u2500'.repeat(37)];
|
|
107
128
|
let found = false;
|
|
108
129
|
|
|
109
130
|
try {
|
|
@@ -15,6 +15,7 @@ import { homedir } from 'node:os';
|
|
|
15
15
|
import { execSync } from 'node:child_process';
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
17
17
|
import { launchdLabelPrefix } from './install.mjs';
|
|
18
|
+
import { findRepoRootSync } from '../instance-root/find.mjs';
|
|
18
19
|
|
|
19
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
20
21
|
const __dirname = dirname(__filename);
|
|
@@ -130,7 +131,7 @@ export function remove(schedId, backend, options = {}) {
|
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
if (options.deleteFile) {
|
|
133
|
-
const daveRoot = process.env.DAVE_ROOT ||
|
|
134
|
+
const daveRoot = process.env.DAVE_ROOT || process.env.PA_ROOT || findRepoRootSync();
|
|
134
135
|
const schedFile = join(daveRoot, 'schedules', `${schedId}.json`);
|
|
135
136
|
if (existsSync(schedFile)) {
|
|
136
137
|
unlinkSync(schedFile);
|
package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/mirror-folder.mjs
CHANGED
|
@@ -25,6 +25,7 @@ import { basename, join, resolve, dirname } from 'node:path';
|
|
|
25
25
|
import { fileURLToPath } from 'node:url';
|
|
26
26
|
import { spawnSync } from 'node:child_process';
|
|
27
27
|
|
|
28
|
+
import { findRepoRootSync } from '../../../../engineering-pa-core/src/instance-root/find.mjs';
|
|
28
29
|
import { createClient, ConfluenceError } from '../client.mjs';
|
|
29
30
|
import { storageToMarkdown } from '../storage.mjs';
|
|
30
31
|
import {
|
|
@@ -39,8 +40,9 @@ import {
|
|
|
39
40
|
} from '../mirror.mjs';
|
|
40
41
|
|
|
41
42
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
42
|
-
//
|
|
43
|
-
|
|
43
|
+
// Route through the shared instance-root primitive; PA_ROOT is recognised
|
|
44
|
+
// as an alias of DAVE_ROOT since 0.1.50 (item 5).
|
|
45
|
+
const DEFAULT_REPO_ROOT = process.env.PA_ROOT || findRepoRootSync();
|
|
44
46
|
|
|
45
47
|
const HELP = `confluence mirror-folder - Mirror a Confluence folder to local markdown
|
|
46
48
|
|
|
@@ -14,13 +14,15 @@ import { resolvePaName } from '../../common/pa-name.mjs';
|
|
|
14
14
|
import { resolve, dirname } from 'node:path';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
16
|
import { readFile as fsReadFile, writeFile as fsWriteFile } from 'node:fs/promises';
|
|
17
|
+
import { findRepoRootSync } from '../../../../engineering-pa-core/src/instance-root/find.mjs';
|
|
17
18
|
|
|
18
19
|
// ── Path resolution ──────────────────────────────────────────────────
|
|
19
20
|
|
|
20
21
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
22
|
|
|
22
|
-
//
|
|
23
|
-
|
|
23
|
+
// Route through the shared instance-root primitive; PA_ROOT and DAVE_ROOT
|
|
24
|
+
// are recognised env overrides (0.1.50, item 5).
|
|
25
|
+
const PROJECT_ROOT = process.env.DAVE_ROOT || process.env.PA_ROOT || findRepoRootSync();
|
|
24
26
|
|
|
25
27
|
const HELP = `jira link - Cross-link work items with Jira tickets
|
|
26
28
|
|
|
@@ -14,13 +14,17 @@ import { formatTable, bold, green, yellow, dim, cyan } from '../format.mjs';
|
|
|
14
14
|
import { resolve, dirname } from 'node:path';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
16
|
import { readFile as fsReadFile, writeFile as fsWriteFile } from 'node:fs/promises';
|
|
17
|
+
import { findRepoRootSync } from '../../../../engineering-pa-core/src/instance-root/find.mjs';
|
|
17
18
|
|
|
18
19
|
// ── Path resolution ──────────────────────────────────────────────────
|
|
19
20
|
|
|
20
21
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
22
|
|
|
22
|
-
//
|
|
23
|
-
|
|
23
|
+
// Route through the shared instance-root primitive; PA_ROOT and DAVE_ROOT
|
|
24
|
+
// are recognised env overrides (0.1.50, item 5). The old six-levels-up
|
|
25
|
+
// walk landed inside the pnpm-store package directory under the published
|
|
26
|
+
// tarball layout.
|
|
27
|
+
const PROJECT_ROOT = process.env.DAVE_ROOT || process.env.PA_ROOT || findRepoRootSync();
|
|
24
28
|
|
|
25
29
|
const MAPPINGS_REL_PATH = 'operator/config/jira-status-mappings.json';
|
|
26
30
|
|
|
@@ -40,15 +40,19 @@
|
|
|
40
40
|
import { resolve, dirname } from 'node:path';
|
|
41
41
|
import { fileURLToPath } from 'node:url';
|
|
42
42
|
import { readFile as fsReadFile } from 'node:fs/promises';
|
|
43
|
+
import { findRepoRootSync } from '../../../engineering-pa-core/src/instance-root/find.mjs';
|
|
43
44
|
|
|
44
45
|
// ── Path resolution ──────────────────────────────────────────────────
|
|
45
46
|
|
|
46
47
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
47
48
|
|
|
48
|
-
// Project root: env override, else
|
|
49
|
-
//
|
|
49
|
+
// Project root: env override, else the shared instance-root primitive.
|
|
50
|
+
// PA_ROOT is honoured as an alias since 0.1.50 (item 5).
|
|
50
51
|
const DEFAULT_PROJECT_ROOT =
|
|
51
|
-
process.env.PA_REPO_ROOT
|
|
52
|
+
process.env.PA_REPO_ROOT
|
|
53
|
+
|| process.env.DAVE_ROOT
|
|
54
|
+
|| process.env.PA_ROOT
|
|
55
|
+
|| findRepoRootSync();
|
|
52
56
|
|
|
53
57
|
// Operator override, relative to the project root. This path is a
|
|
54
58
|
// composition mount (fed from the operator entity), so the tool must not
|
|
@@ -9,15 +9,21 @@
|
|
|
9
9
|
import { resolve, dirname } from 'node:path';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
import { readFile as fsReadFile } from 'node:fs/promises';
|
|
12
|
+
import { findRepoRootSync } from '../../../engineering-pa-core/src/instance-root/find.mjs';
|
|
12
13
|
|
|
13
14
|
// ── Path resolution ──────────────────────────────────────────────────
|
|
14
15
|
|
|
15
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
17
|
|
|
17
|
-
// Project root: env override, else
|
|
18
|
-
//
|
|
18
|
+
// Project root: env override, else the shared instance-root primitive.
|
|
19
|
+
// PA_ROOT, DAVE_ROOT and PA_REPO_ROOT are all honoured (PA_ROOT since 0.1.50,
|
|
20
|
+
// item 5; findRepoRootSync walks up for `.git`, preferences or workspace
|
|
21
|
+
// markers before falling back to cwd).
|
|
19
22
|
const DEFAULT_PROJECT_ROOT =
|
|
20
|
-
process.env.PA_REPO_ROOT
|
|
23
|
+
process.env.PA_REPO_ROOT
|
|
24
|
+
|| process.env.DAVE_ROOT
|
|
25
|
+
|| process.env.PA_ROOT
|
|
26
|
+
|| findRepoRootSync();
|
|
21
27
|
|
|
22
28
|
const MAPPINGS_REL_PATH = 'operator/config/jira-status-mappings.json';
|
|
23
29
|
|
|
@@ -740,7 +740,9 @@ export function buildDistillationScheduleJson(config, seams = {}) {
|
|
|
740
740
|
// the schedule closes both cases (w-2026-07-15-003 + w-2026-07-16-006
|
|
741
741
|
// trap class). PATH is only added when `claude` lives outside the
|
|
742
742
|
// launchd default PATH set.
|
|
743
|
-
|
|
743
|
+
// PA_ROOT baked alongside DAVE_ROOT so schedule authors and consumers
|
|
744
|
+
// can reference either (0.1.50, item 5). Both point at the same target.
|
|
745
|
+
environment: { DAVE_ROOT: config.targetDir, PA_ROOT: config.targetDir },
|
|
744
746
|
};
|
|
745
747
|
|
|
746
748
|
if (claudeBinDir && !LAUNCHD_DEFAULT_PATHS.includes(claudeBinDir)) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Notes
|
|
6
|
+
<title>Notes</title>
|
|
7
7
|
<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>
|
|
8
8
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
9
9
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
@@ -119,6 +119,10 @@
|
|
|
119
119
|
</div>
|
|
120
120
|
<script>
|
|
121
121
|
var operatorName = null;
|
|
122
|
+
// 0.1.50 (item 9): paName renders the "Next (<PA>)" label so the workspace
|
|
123
|
+
// never says "Dave" on non-Dave instances. Falls back to a neutral label
|
|
124
|
+
// when overview did not carry a name.
|
|
125
|
+
var paName = null;
|
|
122
126
|
var projectWorkItems = [];
|
|
123
127
|
function navTo(path) { if (window.parent && window.parent.navigate) window.parent.navigate(path); else window.location.href = path; }
|
|
124
128
|
function openModal(idx) {
|
|
@@ -247,7 +251,14 @@
|
|
|
247
251
|
html += '<div class="ws-next"><span class="ws-next-label">Next (you): </span><span class="ws-next-text">' + esc(ws.nextOperator) + '</span></div>';
|
|
248
252
|
}
|
|
249
253
|
if (ws.nextDave) {
|
|
250
|
-
|
|
254
|
+
// The desk API field name (`nextDave`) is kept for backward-compat
|
|
255
|
+
// with existing consumers; renaming to `nextPa` is deferred to a
|
|
256
|
+
// later release with a consumer sweep. The rendered label uses the
|
|
257
|
+
// capitalised paName (falling back to "PA" when it is unknown).
|
|
258
|
+
var paLabel = paName
|
|
259
|
+
? (paName.charAt(0).toUpperCase() + paName.slice(1))
|
|
260
|
+
: 'PA';
|
|
261
|
+
html += '<div class="ws-next"><span class="ws-next-label">Next (' + esc(paLabel) + '): </span><span class="ws-next-text">' + esc(ws.nextDave) + '</span></div>';
|
|
251
262
|
}
|
|
252
263
|
|
|
253
264
|
// Docs list
|
|
@@ -276,6 +287,11 @@
|
|
|
276
287
|
// Fetch operator name, project data, and work items in parallel
|
|
277
288
|
var opPromise = fetch('/__api/overview').then(function(r) { return r.json(); }).then(function(d) {
|
|
278
289
|
if (d.operatorName) operatorName = d.operatorName;
|
|
290
|
+
// 0.1.50 (item 9): drop the hardcoded "Dave" from the Next label
|
|
291
|
+
// in favour of the paName the operator scaffolded with. Capitalise
|
|
292
|
+
// the first letter to match the rest of the workspace's rendering
|
|
293
|
+
// (`Digital Operator` heading is capitalised the same way).
|
|
294
|
+
if (d.paName) paName = d.paName;
|
|
279
295
|
}).catch(function() {});
|
|
280
296
|
|
|
281
297
|
var projPromise = fetch('/__api/file?path=' + encodeURIComponent('projects/' + projectId + '/project.json'))
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Projects
|
|
6
|
+
<title>Projects</title>
|
|
7
7
|
<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>
|
|
8
8
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
9
9
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Tasks
|
|
6
|
+
<title>Tasks</title>
|
|
7
7
|
<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>
|
|
8
8
|
<link rel="stylesheet" href="/core/tools/workspace/app/assets/theme.css">
|
|
9
9
|
<script src="/core/tools/workspace/app/assets/theme.js" defer></script>
|
|
@@ -612,8 +612,8 @@ function linuxStatus({ names }) {
|
|
|
612
612
|
|
|
613
613
|
function windowsNotSupported() {
|
|
614
614
|
console.error('Error: Windows service installation is not yet implemented.');
|
|
615
|
-
console.error('
|
|
616
|
-
console.error('
|
|
615
|
+
console.error('See https://stravica.ai for support, or run the workspace');
|
|
616
|
+
console.error('manually with: pa workspace start (or node node_modules/@stravica-ai/engineering-pa-workspace/src/ws.mjs start)');
|
|
617
617
|
return 2;
|
|
618
618
|
}
|
|
619
619
|
|
|
@@ -1,100 +1,13 @@
|
|
|
1
|
-
// repo-root.mjs —
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// 1. `DAVE_REPO_ROOT` or `PA_REPO_ROOT` env override (tests + operator-set).
|
|
15
|
-
// 2. Walk up from `process.cwd()` looking for a marker that identifies an
|
|
16
|
-
// operator instance root:
|
|
17
|
-
// - `.git` (present in every scaffolded instance — pa-init runs `git
|
|
18
|
-
// init` at scaffold time).
|
|
19
|
-
// - `operator/config/preferences.json` (the canonical instance marker;
|
|
20
|
-
// matches instances that lost their `.git` for any reason).
|
|
21
|
-
// - `pnpm-workspace.yaml` (matches monorepo checkouts, e.g. this
|
|
22
|
-
// engineering-pa-dev repo itself, so package-local tests still work).
|
|
23
|
-
// 3. Fall back to `process.cwd()` so pre-scaffold and dry-run flows still
|
|
24
|
-
// have a stable value to render.
|
|
25
|
-
//
|
|
26
|
-
// The `startDir` argument is injectable for tests and callers that need to
|
|
27
|
-
// resolve from a directory other than `process.cwd()`. Prod callers use
|
|
28
|
-
// the default.
|
|
29
|
-
|
|
30
|
-
import { existsSync } from 'node:fs';
|
|
31
|
-
import { join, resolve } from 'node:path';
|
|
32
|
-
|
|
33
|
-
const MARKERS = [
|
|
34
|
-
'.git',
|
|
35
|
-
join('operator', 'config', 'preferences.json'),
|
|
36
|
-
'pnpm-workspace.yaml',
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Walk up from `start` looking for the first directory that contains any
|
|
41
|
-
* marker in {@link MARKERS}. Returns the directory path, or null when the
|
|
42
|
-
* walk reaches the filesystem root without a hit.
|
|
43
|
-
*
|
|
44
|
-
* @param {string} start
|
|
45
|
-
* @returns {string|null}
|
|
46
|
-
*/
|
|
47
|
-
export function walkUpForMarker(start) {
|
|
48
|
-
let d = resolve(start);
|
|
49
|
-
while (d && d !== resolve(d, '..')) {
|
|
50
|
-
for (const marker of MARKERS) {
|
|
51
|
-
if (existsSync(join(d, marker))) return d;
|
|
52
|
-
}
|
|
53
|
-
d = resolve(d, '..');
|
|
54
|
-
}
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
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
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Resolve the operator instance root. Deterministic and synchronous; safe
|
|
70
|
-
* to call at module top-level. See the header comment for the full
|
|
71
|
-
* precedence rule.
|
|
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
|
-
*
|
|
78
|
-
* @param {object} [opts]
|
|
79
|
-
* @param {string} [opts.startDir=process.cwd()] Directory to start the walk from.
|
|
80
|
-
* @param {NodeJS.ProcessEnv} [opts.env=process.env] Env source (tests inject).
|
|
81
|
-
* @param {(msg: string) => void} [opts.warn] Optional warn sink (tests inject).
|
|
82
|
-
* @returns {string} Absolute path.
|
|
83
|
-
*/
|
|
84
|
-
export function findRepoRootSync(opts = {}) {
|
|
85
|
-
const env = opts.env || process.env;
|
|
86
|
-
const startDir = opts.startDir || process.cwd();
|
|
87
|
-
const envRoot = env.DAVE_REPO_ROOT || env.PA_REPO_ROOT;
|
|
88
|
-
if (envRoot) return resolve(envRoot);
|
|
89
|
-
const found = walkUpForMarker(startDir);
|
|
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;
|
|
100
|
-
}
|
|
1
|
+
// repo-root.mjs — thin re-export of the core instance-root primitive.
|
|
2
|
+
//
|
|
3
|
+
// The resolver used to live here. It was promoted to
|
|
4
|
+
// `@stravica-ai/engineering-pa-core/src/instance-root/find.mjs` in 0.1.50
|
|
5
|
+
// so scheduler and knowledge-retrieval consumers share the same walk
|
|
6
|
+
// (item 14, w-2026-09-17-dave-017). Exports keep the same names so every
|
|
7
|
+
// existing importer keeps working unchanged.
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
findRepoRootSync,
|
|
11
|
+
walkUpForMarker,
|
|
12
|
+
_resetFallbackWarningForTests,
|
|
13
|
+
} from '../../engineering-pa-core/src/instance-root/find.mjs';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Digital Operator Workspace -- persistent dev server.
|
|
5
5
|
*
|
|
6
|
-
* Serves the
|
|
6
|
+
* Serves the operator repo as static HTML pages plus a JSON API.
|
|
7
7
|
* Auto-shuts down after 2 hours of idle time.
|
|
8
8
|
*
|
|
9
9
|
* Live-reload / SSE was removed pending a workspace rebuild — see PR
|