create-pmos 0.65.0 → 0.65.2
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/package.json +1 -1
- package/payload/KIT-VERSION +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/guide/docs.md +59 -6
- package/workspace/guide/initiatives-screen.md +8 -0
- package/workspace/guide/today.md +11 -3
- package/workspace/lib/calendar-memory.js +53 -5
- package/workspace/lib/doc-library.js +131 -0
- package/workspace/lib/goals.js +25 -6
- package/workspace/package-lock.json +1 -1
- package/workspace/package.json +1 -1
- package/workspace/public/pm/components.css +307 -0
- package/workspace/public/pm/dest-docs.js +144 -44
- package/workspace/public/pm/dest-today.js +4 -4
- package/workspace/public/pm/doc-graph.js +272 -0
- package/workspace/public/pm/initiative-sheet.js +24 -2
- package/workspace/public/pm/initiatives-copy.js +8 -0
- package/workspace/public/pm/today-agenda.js +29 -5
- package/workspace/public/pm/today-copy.js +11 -3
- package/workspace/public/pm/today-day.js +42 -18
- package/workspace/server.js +81 -6
- package/workspace/test-lists/docs-library-graph.json +5 -0
package/workspace/server.js
CHANGED
|
@@ -131,6 +131,7 @@ const focus = require('./lib/focus'); // buildFocus(input) →
|
|
|
131
131
|
const handoff = require('./lib/handoff'); // readLinks(parsedFrontmatterObject) — never a path
|
|
132
132
|
const fourPanel = require('./lib/four-panel'); // GET /api/initiative — the one-initiative join + the declaration stores
|
|
133
133
|
const docSyncStore = require('./lib/doc-sync-store');
|
|
134
|
+
const docLibrary = require('./lib/doc-library'); // every repository file by category, and the links the files write (docs-library-graph)
|
|
134
135
|
const peopleStore = require('./lib/people-store'); // people-directory (D90) — people.jsonl, folded on read
|
|
135
136
|
// The session each repository's Check now started (workspace id → session id), so a second press while it runs refuses.
|
|
136
137
|
const docCheckSessions = new Map();
|
|
@@ -3771,8 +3772,8 @@ function stallQuietTasks(run, now) {
|
|
|
3771
3772
|
return run;
|
|
3772
3773
|
}
|
|
3773
3774
|
// The skill's own body is the prompt, as fireSchedule hands it over; the line after it names this run's task and mode.
|
|
3774
|
-
function daySkillPrompt(run, skillId) {
|
|
3775
|
-
const w = getWs(run.ws);
|
|
3775
|
+
function daySkillPrompt(run, skillId, wsId) {
|
|
3776
|
+
const w = getWs(wsId || run.ws);
|
|
3776
3777
|
const task = taskById(skillId, w ? w.root : null);
|
|
3777
3778
|
if (!task || task.kind !== 'agent' || !task.prompt) return { error: `the ${skillId} skill is not installed for this repository` };
|
|
3778
3779
|
return { w, task, prompt: skillStore.retargetFiling(task.prompt, filingContract(w)) };
|
|
@@ -3784,9 +3785,22 @@ function jobSessionName(title) {
|
|
|
3784
3785
|
const cut = t.slice(0, 28); const sp = cut.lastIndexOf(' ');
|
|
3785
3786
|
return 'Job · ' + (sp > 12 ? cut.slice(0, sp) : cut).replace(/[\s,.;:·—-]+$/, '') + '…';
|
|
3786
3787
|
}
|
|
3788
|
+
/** The repository a job belongs to (its item's tag: its goal's, its initiative's or its place's repository), when that
|
|
3789
|
+
* repository is registered and has the day-task skill; else null and the job runs where Prepare my day was pressed. */
|
|
3790
|
+
function taskWsId(run, t) {
|
|
3791
|
+
try {
|
|
3792
|
+
const brief = briefOfDay(run.date);
|
|
3793
|
+
if (!brief || !t.item_id) return null;
|
|
3794
|
+
const st = proSettings();
|
|
3795
|
+
const r = goalsLib.itemRepos({ day: readGoalsDay(run.date), brief, placeRepos: st.placeRepos || {}, iniRepos: iniRepos() })[t.item_id];
|
|
3796
|
+
const w = r && r !== run.ws ? getWs(r) : null;
|
|
3797
|
+
return w && !daySkillPrompt(run, 'day-task', w.id).error ? w.id : null;
|
|
3798
|
+
} catch { return null; }
|
|
3799
|
+
}
|
|
3787
3800
|
/** Start one task session; a start that fails marks the task failed with the reason. Returns the new run. */
|
|
3788
3801
|
function startDayTask(run, t, now) {
|
|
3789
|
-
|
|
3802
|
+
// a job runs in its own repository's folder, so it reads that repository's files and records (one day, many repositories)
|
|
3803
|
+
const sk = daySkillPrompt(run, 'day-task', taskWsId(run, t));
|
|
3790
3804
|
let s = null;
|
|
3791
3805
|
const line = `This run: /day-task ${t.id} ${t.mode} — read the task first with: pmos proactive day show ${t.id}`;
|
|
3792
3806
|
// The session that did the task's last step is used again while it is open: it has the skill and the task in its
|
|
@@ -3861,6 +3875,15 @@ function dayTick() {
|
|
|
3861
3875
|
// ── end of the today day run helpers ─────────────────────────────────────────────────────────────────────────────
|
|
3862
3876
|
/* The calendar the Agenda remembers (G23): one file beside the briefs, written only by the brief route. */
|
|
3863
3877
|
const CAL_MEMORY = () => path.join(STATE, 'calendar-memory.json');
|
|
3878
|
+
// the PM's key-meeting marks (lib/calendar-memory.js setMark): a small file beside the calendar memory, written only by
|
|
3879
|
+
// POST /api/proactive/agenda/mark
|
|
3880
|
+
const CAL_MARKS = () => path.join(path.dirname(CAL_MEMORY()), 'calendar-marks.json');
|
|
3881
|
+
function readCalMarks() {
|
|
3882
|
+
try { return calMemLib.readMarks(JSON.parse(fs.readFileSync(CAL_MARKS(), 'utf8'))); } catch { return calMemLib.readMarks(null); }
|
|
3883
|
+
}
|
|
3884
|
+
function writeCalMarks(m) {
|
|
3885
|
+
try { const f = CAL_MARKS(); fs.mkdirSync(path.dirname(f), { recursive: true }); const tmp = `${f}.${process.pid}.tmp`; fs.writeFileSync(tmp, JSON.stringify(m) + '\n'); fs.renameSync(tmp, f); return true; } catch { return false; }
|
|
3886
|
+
}
|
|
3864
3887
|
function readCalMemory() {
|
|
3865
3888
|
try { return JSON.parse(fs.readFileSync(CAL_MEMORY(), 'utf8')); } catch { return null; }
|
|
3866
3889
|
}
|
|
@@ -3934,7 +3957,30 @@ const goalsCardsMemo = new Map(); // ws -> { at, cards, error }: GET /api/proa
|
|
|
3934
3957
|
function readGoalsDay(date) {
|
|
3935
3958
|
let raw = null;
|
|
3936
3959
|
try { raw = JSON.parse(fs.readFileSync(path.join(GOALS_DIR(), date + '.json'), 'utf8')); } catch { /* none yet */ }
|
|
3937
|
-
return goalsLib.readDay(raw, date);
|
|
3960
|
+
if (raw) return goalsLib.readDay(raw, date);
|
|
3961
|
+
// a day with no file of its own yet starts with the goals still open on the last day that had a file (lib/goals.js
|
|
3962
|
+
// carryOver); the first edit of the day writes them into the day's own file
|
|
3963
|
+
let prev = null;
|
|
3964
|
+
try {
|
|
3965
|
+
const last = fs.readdirSync(GOALS_DIR()).map((f) => /^(\d{4}-\d{2}-\d{2})\.json$/.exec(f)).filter((m) => m && m[1] < date).map((m) => m[1]).sort().pop();
|
|
3966
|
+
if (last) prev = goalsLib.readDay(JSON.parse(fs.readFileSync(path.join(GOALS_DIR(), last + '.json'), 'utf8')), last);
|
|
3967
|
+
} catch { /* no earlier day */ }
|
|
3968
|
+
return goalsLib.carryOver(prev, date);
|
|
3969
|
+
}
|
|
3970
|
+
/** { initiativeId: workspace id } for the initiatives exactly one registered repository holds, read at most every 30 s.
|
|
3971
|
+
* An item or goal naming one of them belongs to that repository (lib/goals.js itemRepos, goalRepos). */
|
|
3972
|
+
let iniReposMemo = null;
|
|
3973
|
+
function iniRepos(now = Date.now()) {
|
|
3974
|
+
if (iniReposMemo && now - iniReposMemo.at < 30000) return iniReposMemo.map;
|
|
3975
|
+
const map = {}; const twice = new Set();
|
|
3976
|
+
try {
|
|
3977
|
+
for (const r of portfolio().rows) {
|
|
3978
|
+
if (twice.has(r.id)) continue;
|
|
3979
|
+
if (map[r.id] && map[r.id] !== r.ws) { delete map[r.id]; twice.add(r.id); } else map[r.id] = r.ws;
|
|
3980
|
+
}
|
|
3981
|
+
} catch { /* no tags this read */ }
|
|
3982
|
+
iniReposMemo = { at: now, map };
|
|
3983
|
+
return map;
|
|
3938
3984
|
}
|
|
3939
3985
|
function latestBrief() {
|
|
3940
3986
|
try {
|
|
@@ -6549,6 +6595,7 @@ async function handleRequest(req, res) {
|
|
|
6549
6595
|
const docs = v.docs.map((d) => ({
|
|
6550
6596
|
...d,
|
|
6551
6597
|
initiative: owner.get(d.path) || (d.initiative_id ? { id: d.initiative_id, title: d.initiative_id } : null),
|
|
6598
|
+
category: docLibrary.categoryOf(d.path, d.type),
|
|
6552
6599
|
links: d.links.map((l) => {
|
|
6553
6600
|
if (l.syncing) return l;
|
|
6554
6601
|
const ps = docSyncSessions.get(`${wid}|${d.path}|${l.kind}`);
|
|
@@ -6557,6 +6604,25 @@ async function handleRequest(req, res) {
|
|
|
6557
6604
|
}));
|
|
6558
6605
|
return json(res, { ...v, docs, ws: w ? w.id : null, session: live, connectors: fourPanel.connectors(os.homedir()) });
|
|
6559
6606
|
}
|
|
6607
|
+
// All files (docs-library-graph): every tracked markdown file by category, its initiative, and the links the files
|
|
6608
|
+
// write (lib/doc-library.js over pmos kg). Kept in memory per repository state; nothing is written into the repository.
|
|
6609
|
+
if (p === '/api/docs/library' && req.method === 'GET') {
|
|
6610
|
+
const w = getWs(url.searchParams.get('ws'));
|
|
6611
|
+
const root = w ? w.root : SELF_ROOT;
|
|
6612
|
+
const owners = () => {
|
|
6613
|
+
const own = new Map();
|
|
6614
|
+
if (wsShape(root) !== 'pmos') return own;
|
|
6615
|
+
const real = (id) => fs.existsSync(path.join(root, 'planning', 'initiatives', id + '.md'));
|
|
6616
|
+
for (const g of initiativeIndex(root).initiatives) {
|
|
6617
|
+
if (!real(g.id)) continue;
|
|
6618
|
+
for (const a of g.artifacts) { const rel = String(a.path).split(path.sep).join('/'); if (!a.shared && !own.has(rel)) own.set(rel, { id: g.id, title: g.title || g.id }); }
|
|
6619
|
+
}
|
|
6620
|
+
return own;
|
|
6621
|
+
};
|
|
6622
|
+
const lib = docLibrary.library(root, { stateDir: STATE, owners });
|
|
6623
|
+
if (!lib.ok) return json(res, lib, 400);
|
|
6624
|
+
return json(res, { ...lib, ws: w ? w.id : null });
|
|
6625
|
+
}
|
|
6560
6626
|
if (p === '/api/docs/files' && req.method === 'GET') {
|
|
6561
6627
|
const w = getWs(url.searchParams.get('ws'));
|
|
6562
6628
|
const root = w ? w.root : SELF_ROOT;
|
|
@@ -7655,7 +7721,8 @@ async function handleRequest(req, res) {
|
|
|
7655
7721
|
settings: { briefSources: st.briefSources, scopes: st.scopes, placeRepos: st.placeRepos, dayCheck: st.dayCheck, websites: st.websites }, sources,
|
|
7656
7722
|
// one day for every repository: the repositories it can be narrowed to, and the repository of each tagged item
|
|
7657
7723
|
repos: [...workspaces.values()].map((x) => ({ id: x.id, name: x.name || path.basename(x.root) })),
|
|
7658
|
-
item_repos: goalsLib.itemRepos({ day: readGoalsDay(date), brief, placeRepos: st.placeRepos || {} }),
|
|
7724
|
+
item_repos: goalsLib.itemRepos({ day: readGoalsDay(date), brief, placeRepos: st.placeRepos || {}, iniRepos: iniRepos(now) }),
|
|
7725
|
+
goal_repos: goalsLib.goalRepos({ day: readGoalsDay(date), iniRepos: iniRepos(now) }),
|
|
7659
7726
|
check: { every: st.dayCheck.every, last: run ? run.checks.last : null, next: due.next, running: !!(run && run.checks.session_id), log: run ? run.checks.log : [] } });
|
|
7660
7727
|
}
|
|
7661
7728
|
/* The Agenda month (G25 to G27): read-only. The calendar memory and today's brief, joined by lib/calendar-memory.js. */
|
|
@@ -7663,7 +7730,15 @@ async function handleRequest(req, res) {
|
|
|
7663
7730
|
const now = Date.now();
|
|
7664
7731
|
const month = url.searchParams.get('month') || undefined;
|
|
7665
7732
|
if (month !== undefined && !/^\d{4}-\d{2}$/.test(month)) return json(res, { ok: false, error: 'month must be YYYY-MM' }, 400);
|
|
7666
|
-
return json(res, calMemLib.agendaMonth({ memory: readCalMemory(), brief: briefOfDay(briefLib.localDate(now)), month, now }));
|
|
7733
|
+
return json(res, calMemLib.agendaMonth({ memory: readCalMemory(), brief: briefOfDay(briefLib.localDate(now)), month, marks: readCalMarks(), now }));
|
|
7734
|
+
}
|
|
7735
|
+
/* A key-meeting mark: the PM's press in a meeting's detail. { title, start, scope: 'one'|'series', on: true|false|null }. */
|
|
7736
|
+
if (p === '/api/proactive/agenda/mark' && req.method === 'POST') {
|
|
7737
|
+
let body = {}; try { body = JSON.parse((await readBody(req)) || '{}'); } catch { return json(res, { ok: false, error: 'invalid JSON body' }, 400); }
|
|
7738
|
+
const r = calMemLib.setMark(readCalMarks(), { title: body.title, start: body.start, scope: body.scope, on: body.on === undefined ? null : body.on });
|
|
7739
|
+
if (!r.ok) return json(res, { ok: false, error: r.error }, 400);
|
|
7740
|
+
if (!writeCalMarks(r.marks)) return json(res, { ok: false, error: 'could not save — nothing changed' }, 500);
|
|
7741
|
+
return json(res, { ok: true });
|
|
7667
7742
|
}
|
|
7668
7743
|
if (p.startsWith('/api/proactive/day/') && req.method === 'POST') {
|
|
7669
7744
|
const verb = p.slice('/api/proactive/day/'.length);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "docs-library-graph: every repository file by category in Docs, the links the files write, and an initiative's files as a network. The browser half runs in pm-docs-test.js (docs-three-way-sync's fragment).",
|
|
3
|
+
"modules": ["doc-library.test.js"],
|
|
4
|
+
"seconds": { "doc-library.test.js": 2 }
|
|
5
|
+
}
|