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.
@@ -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
- const sk = daySkillPrompt(run, 'day-task');
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
+ }