create-pmos 0.47.0 → 0.49.0
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/AGENTS-HUMAN.md +1 -1
- package/payload/AGENTS.md +1 -1
- package/payload/KIT-VERSION +2 -2
- package/payload/mods/pmos-cockpit/.claude-plugin/plugin.json +1 -1
- package/payload/mods/pmos-cockpit/README.md +14 -3
- package/payload/mods/pmos-cockpit/hooks/lib/commands.mjs +34 -6
- package/payload/mods/pmos-cockpit/hooks/lib/grants.mjs +22 -3
- package/payload/mods/pmos-cockpit/hooks/lib/guard.mjs +16 -3
- package/payload/mods/pmos-cockpit/hooks/lib/route.mjs +95 -0
- package/payload/mods/pmos-cockpit/hooks/register.tsx +104 -19
- package/payload/mods/pmos-cockpit/types/index.d.ts +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/scripts/pmos-log.sh +18 -8
- package/payload/scripts/pmos-model.js +233 -2
- package/payload/scripts/status-facts.js +214 -0
- package/payload/scripts/workflow-state.js +59 -2
- package/payload/state/discoveries.md +1 -1
- package/payload/state/log.md +1 -1
- package/workspace/README.md +2 -2
- package/workspace/bin/live-notes.js +133 -0
- package/workspace/design-parity.js +328 -21
- package/workspace/doc-category-bench.js +165 -0
- package/workspace/fixtures/doc-category-labelled.json +167 -0
- package/workspace/guide/index.md +2 -1
- package/workspace/guide/initiatives-and-next-steps.md +17 -2
- package/workspace/guide/knowledge-graph.md +94 -1
- package/workspace/guide/layout.md +31 -2
- package/workspace/guide/live-notes.md +78 -0
- package/workspace/guide/proactive-engine.md +6 -2
- package/workspace/guide/validate-with-typesafe.md +31 -2
- package/workspace/lib/claims.js +51 -2
- package/workspace/lib/doc-category-corrections.js +113 -0
- package/workspace/lib/doc-category-writeback.js +110 -0
- package/workspace/lib/doc-category.js +225 -0
- package/workspace/lib/engine/pmos-model.js +233 -2
- package/workspace/lib/engine/status-facts.js +214 -0
- package/workspace/lib/engine/workflow-state.js +59 -2
- package/workspace/lib/jev-categorize.js +433 -0
- package/workspace/lib/jev-claims.js +59 -15
- package/workspace/lib/jev-live.js +37 -0
- package/workspace/lib/jev-route.js +139 -0
- package/workspace/lib/kg-cli.js +3 -1
- package/workspace/lib/kg.js +6 -1
- package/workspace/lib/learn.js +5 -3
- package/workspace/lib/live-notes.js +1035 -0
- package/workspace/lib/typesafe-settings.js +10 -2
- package/workspace/package-lock.json +1 -1
- package/workspace/package.json +5 -4
- package/workspace/public/pm/app.js +148 -79
- package/workspace/public/pm/data.js +78 -0
- package/workspace/public/pm/fixture.js +40 -2
- package/workspace/public/pm/modes-live.js +279 -18
- package/workspace/public/pm/panel-cockpit.js +638 -0
- package/workspace/public/pm/panel-detail.js +212 -8
- package/workspace/public/pm/panel-initiatives.js +9 -9
- package/workspace/public/pm/panel-newsess.js +145 -86
- package/workspace/public/pm/panel-repos.js +34 -17
- package/workspace/public/pm/panel-tabs.js +249 -29
- package/workspace/public/pm/state.js +48 -4
- package/workspace/public/pm/tab-doc.js +244 -16
- package/workspace/public/pm/tab-term.js +11 -1
- package/workspace/public/pm/tokens.css +4 -3
- package/workspace/public/pm/view-addrepo.js +207 -0
- package/workspace/public/pm/view-files.js +197 -0
- package/workspace/public/pm/view-guide.js +141 -0
- package/workspace/public/pm/view-knowledge.js +225 -0
- package/workspace/public/pm/view-livenotes.js +397 -0
- package/workspace/public/pm/view-memory.js +122 -0
- package/workspace/public/pm/view-portfolio.js +153 -0
- package/workspace/public/pm/view-reports.js +190 -0
- package/workspace/public/pm/view-scheduled.js +161 -0
- package/workspace/public/pm/view-skills.js +216 -0
- package/workspace/public/pm/view-today.js +168 -0
- package/workspace/public/pm/view-validate.js +171 -0
- package/workspace/server.js +349 -7
- package/workspace/skills/live-notes.skill +58 -0
|
@@ -54,6 +54,13 @@ const anchors = require('./anchor-resolve');
|
|
|
54
54
|
* server. It is shared now, for the same reason `anchor-resolve` is.
|
|
55
55
|
*/
|
|
56
56
|
const delivery = require('./delivery-index');
|
|
57
|
+
/*
|
|
58
|
+
* Three flags the ladder cannot see (status-from-artifacts, Run 1): a spec file changed after an
|
|
59
|
+
* approval, requirement ids the rubric never cites, and `status:` prose the ladder contradicts. They
|
|
60
|
+
* sit BESIDE the steps. They never change a step, the stage, a colour or a count, and they write
|
|
61
|
+
* nothing. See scripts/status-facts.js.
|
|
62
|
+
*/
|
|
63
|
+
const statusFacts = require('./status-facts');
|
|
57
64
|
|
|
58
65
|
// The OKR documents and the budget registry are read once per root, not once per initiative: the
|
|
59
66
|
// board derives ~100 ladders in a single pass and re-reading the same three files each time is the
|
|
@@ -791,7 +798,7 @@ function computeWorkflowDerived(id, opts) {
|
|
|
791
798
|
|
|
792
799
|
// ── D68: the pm-artifacts ladder replaces everything after the anchor ─────────────────────────
|
|
793
800
|
if (pmProfile) {
|
|
794
|
-
return pmLadder({ id, fm, lane, effLane, knownLane, legacy, steps, root, L, recRel, range, inChange, lastActivity });
|
|
801
|
+
return pmLadder({ id, fm, lane, effLane, knownLane, legacy, steps, root, L, recRel, range, inChange, lastActivity, opts });
|
|
795
802
|
}
|
|
796
803
|
|
|
797
804
|
const prd = String(fm.prd_path || '').trim();
|
|
@@ -1235,10 +1242,15 @@ function computeWorkflowDerived(id, opts) {
|
|
|
1235
1242
|
|| steps.find((s) => s.status === 'pending' && s.next)
|
|
1236
1243
|
|| null;
|
|
1237
1244
|
|
|
1245
|
+
// `opts.facts === false` exists so a test can prove the flags change nothing else in the ladder.
|
|
1246
|
+
const facts = (opts && opts.facts === false) ? {}
|
|
1247
|
+
: statusFacts.compute({ root, L, id, fm, lane: effLane, stage, runs, accepted });
|
|
1248
|
+
|
|
1238
1249
|
return {
|
|
1239
1250
|
ok: true, id, lane, laneKnown: knownLane, legacy, in_change: inChange,
|
|
1240
1251
|
last_activity: lastActivity, accepted: accepted.length > 0, stage, record: recRel,
|
|
1241
1252
|
title: fm.title || id, anchor: fm.parent_kr || null, profile: 'product-code',
|
|
1253
|
+
...facts,
|
|
1242
1254
|
steps, blockers: blockers.map((s) => s.key), gate_failures: gateFailures.map((s) => s.rule || s.key),
|
|
1243
1255
|
next: next ? { step: next.key, why: next.evidence, action: next.next || next.evidence } : null,
|
|
1244
1256
|
range: range.ok ? { base: range.base, commits: range.shas.length, whole_history: !!range.wholeHistory } : { error: range.reason },
|
|
@@ -1262,7 +1274,7 @@ function computeWorkflowDerived(id, opts) {
|
|
|
1262
1274
|
* existence and their order fail CI for a non-legacy record). Review, approval and handoff are never
|
|
1263
1275
|
* CI-blocking in either track: none can be true before the PR exists.
|
|
1264
1276
|
*/
|
|
1265
|
-
function pmLadder({ id, fm, lane, effLane, knownLane, legacy, steps, root, L, recRel, range, inChange, lastActivity }) {
|
|
1277
|
+
function pmLadder({ id, fm, lane, effLane, knownLane, legacy, steps, root, L, recRel, range, inChange, lastActivity, opts }) {
|
|
1266
1278
|
const declared = String(fm.track || '').trim().toLowerCase();
|
|
1267
1279
|
const track = declared === 'light' || declared === 'governed' ? declared
|
|
1268
1280
|
: (laneDefs.laneDef('pm-artifacts', effLane) || laneDefs.laneDef('pm-artifacts', 'prd')).track;
|
|
@@ -1336,10 +1348,15 @@ function pmLadder({ id, fm, lane, effLane, knownLane, legacy, steps, root, L, re
|
|
|
1336
1348
|
const blockers = steps.filter((x) => x.status === 'blocked');
|
|
1337
1349
|
const gateFailures = blockers.filter((x) => x.blocking);
|
|
1338
1350
|
const next = steps.find((x) => x.status === 'blocked') || steps.find((x) => x.status === 'pending' && x.next) || null;
|
|
1351
|
+
// status-from-artifacts: a document repository gets the one fact that applies to it — the brief or the deliverable
|
|
1352
|
+
// changed after the PM accepted it. Coverage and the status-line check are product-code facts and stay null here.
|
|
1353
|
+
const facts = (opts && opts.facts === false) ? {}
|
|
1354
|
+
: statusFacts.computeArtifacts({ root, L, fm, accepted });
|
|
1339
1355
|
return {
|
|
1340
1356
|
ok: true, id, lane, laneKnown: knownLane, legacy, in_change: inChange,
|
|
1341
1357
|
last_activity: lastActivity, accepted: accepted.length > 0, stage, record: recRel,
|
|
1342
1358
|
title: fm.title || id, anchor: fm.parent_kr || null, profile: 'pm-artifacts', track,
|
|
1359
|
+
...facts,
|
|
1343
1360
|
steps, blockers: blockers.map((x) => x.key), gate_failures: gateFailures.map((x) => x.rule || x.key),
|
|
1344
1361
|
next: next ? { step: next.key, why: next.evidence, action: next.next || next.evidence } : null,
|
|
1345
1362
|
range: range.ok ? { base: range.base, commits: range.shas.length, whole_history: !!range.wholeHistory } : { error: range.reason },
|
|
@@ -1390,6 +1407,44 @@ function brief(s, n) {
|
|
|
1390
1407
|
return t.length > n ? `${t.slice(0, n - 1)}…` : t;
|
|
1391
1408
|
}
|
|
1392
1409
|
|
|
1410
|
+
/**
|
|
1411
|
+
* The three status-from-artifacts lines, one plain sentence each with its basis. A flag, never a
|
|
1412
|
+
* step: nothing here is a state, and a missing basis prints nothing (never a pass).
|
|
1413
|
+
*/
|
|
1414
|
+
function renderFacts(w) {
|
|
1415
|
+
const out = [];
|
|
1416
|
+
const sa = w.since_approval;
|
|
1417
|
+
if (sa) {
|
|
1418
|
+
if (sa.status === 'unknown') out.push(` ${c(90, '?')} since approval ${c(90, `cannot tell — ${sa.reason}`)}`);
|
|
1419
|
+
else {
|
|
1420
|
+
for (const a of sa.approvals) {
|
|
1421
|
+
const what = a.kind === 'contract' ? 'the contract was approved' : 'the PM accepted';
|
|
1422
|
+
if (a.status === 'unknown') out.push(` ${c(90, '?')} since approval ${c(90, `${what} (${a.run_id || 'no run id'}) — ${a.reason}`)}`);
|
|
1423
|
+
else if (!a.changed.length) out.push(` ${c(90, '·')} since approval ${c(90, `no spec file changed since ${what} (${a.approved_sha})`)}`);
|
|
1424
|
+
else {
|
|
1425
|
+
const files = [...new Set(a.changed.map((x) => x.file))].join(', ');
|
|
1426
|
+
const last = a.changed[a.changed.length - 1];
|
|
1427
|
+
out.push(` ${c(33, '!')} since approval ${files} changed after ${what} (${a.approved_sha}); ${a.changed_total} commit(s), latest ${last.sha} ${last.date || ''}`);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
const cv = w.coverage;
|
|
1433
|
+
if (cv) {
|
|
1434
|
+
if (cv.status === 'unknown') out.push(` ${c(90, '?')} coverage ${c(90, `cannot tell — ${cv.reason}`)}`);
|
|
1435
|
+
else if (!cv.uncited.length && !cv.orphan_citations.length) out.push(` ${c(90, '·')} coverage ${c(90, `all ${cv.requirements} requirement ids are cited in the rubric`)}`);
|
|
1436
|
+
else {
|
|
1437
|
+
const bits = [];
|
|
1438
|
+
if (cv.uncited.length) bits.push(`${cv.uncited.join(', ')} not cited in the rubric`);
|
|
1439
|
+
if (cv.orphan_citations.length) bits.push(`${cv.orphan_citations.join(', ')} cited but not in the PRD`);
|
|
1440
|
+
out.push(` ${c(33, '!')} coverage ${bits.join('; ')} (cited is not graded)`);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
const rs = w.record_says;
|
|
1444
|
+
if (rs) out.push(` ${c(33, '!')} record says status: ${rs.reason}`);
|
|
1445
|
+
return out;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1393
1448
|
function renderLadder(w) {
|
|
1394
1449
|
if (!w.ok) return c(31, `✗ ${w.error}`);
|
|
1395
1450
|
const out = [];
|
|
@@ -1403,6 +1458,8 @@ function renderLadder(w) {
|
|
|
1403
1458
|
out.push(` ${mark(s.status)} ${s.status === 'na' || s.status === 'pending' ? c(90, label) : label} ${brief(s.evidence, 110)}`);
|
|
1404
1459
|
if (s.next) out.push(` ${c(90, '→')} ${c(s.status === 'blocked' ? 33 : 90, brief(s.next, 150))}`);
|
|
1405
1460
|
}
|
|
1461
|
+
const flags = renderFacts(w);
|
|
1462
|
+
if (flags.length) { out.push(` ${c(90, '─'.repeat(78))}`); out.push(...flags); }
|
|
1406
1463
|
out.push('');
|
|
1407
1464
|
if (w.next) out.push(` ${c(1, 'next:')} ${w.next.action}`);
|
|
1408
1465
|
else out.push(` ${c(32, 'nothing blocking — the ladder is complete')}`);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Discoveries and friction log"
|
|
4
4
|
description: "Append-only record of what runs discovered and where the process rubbed."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-10-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-05T00:43:02Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
package/payload/state/log.md
CHANGED
package/workspace/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "PMOS Terminal Workspace"
|
|
4
|
-
timestamp: 2026-
|
|
4
|
+
timestamp: 2026-10-04
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# PMOS Terminal Workspace
|
|
@@ -140,7 +140,7 @@ The three-column layout is retired. The surface is now **four collapsible panels
|
|
|
140
140
|
- **Documents read like documents** — the identity block becomes a card, tables are tables, nested/ordered/task lists survive, and a relative `.md` link opens that document in the reader.
|
|
141
141
|
- **Preview more than markdown** — `.skill` files render as markdown; text/config files open as escaped source; `.html` opens as source with an opt-in **sandboxed** render (scripts + network denied); markdown renders **repo-relative images inline**. A remote `` image is never fetched (no zero-click beacon); a binary stays listed but inert (open it in your editor).
|
|
142
142
|
|
|
143
|
-
**Fixture mode.** `?fixture=proto`
|
|
143
|
+
**Fixture mode.** `?fixture=cockpit` and `?fixture=proto` render the whole surface from a bundled seed file, with the server unable to read any repository. `?fixture=cockpit` holds the seed of the cockpit mockup, and `npm run test:parity` compares it with the mockup board WsA (the blocking CI step). `?fixture=proto` holds the seed of the four-panel prototype that the cockpit replaced, and `npm run test:parity:four-panel` compares it with that prototype. Fixture mode is never the default.
|
|
144
144
|
|
|
145
145
|
## Artifact links are declarations, not integrations
|
|
146
146
|
The PMOS server makes no outbound request and holds no credential, with one named exception ([D79](/okf/products/pmos/adr/d79-typesafe-validation-egress.md)): when you save a TypeSafe key and turn validation on for a repository, the **validate** page sends that repository's document text to `api.typesafe.ai`, from one module, only after you confirm the plan. With no key, nothing is sent. The key stays on this machine, is never shown again, and is never given to a terminal. The consistency checks at the top of the **validate** page are local: they read files and git and send nothing. Automatic checking is off until you switch it on for a repository, and then the workspace appends problems to that repository's `claims.jsonl`. A second switch, **check with Jev automatically** ([D80](/okf/products/pmos/adr/d80-jev-claim-pairs.md)), is also off; only when a key, validation, automatic checking and that switch all hold does the workspace send pairs of short claim texts that changed, after a first pass that records what exists and sends nothing. The workspace makes no request you did not ask for. Its git network verbs are named and press-only: `Sync` runs `fetch`, a fast-forward or an own-commit rebase, a commit of record paths, and `push` — only when you press it, never on a timer or on load, never forced ([D70](/okf/products/pmos/adr/d70-stopped-initiatives-and-sync.md)). A web page opens in a tab only when you type or click its address, and a restored web tab loads only when you press "load this page" ([D69](/okf/products/pmos/adr/d69-web-tab.md)). Linking an artifact to an external destination therefore records a **declaration** — `{artifact_path, kind, target}` appended to `pmos/state/artifact-links.jsonl`. The sync panel renders as designed, and with no connector configured its summary line says exactly that: *no connector configured — nothing was read, and nothing will be written*, with the approval buttons disabled. A difference summary it never read would be the watermelon this repo has a word for.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* live-notes — Run 1 of jev-live-notes: place the talking points of a conversation against a repository's open
|
|
3
|
+
* initiatives, as proposals. No page, no microphone, no audio, no network call.
|
|
4
|
+
*
|
|
5
|
+
* node bin/live-notes.js run --repo <dir> [--segments <file>|-] [--answers <file>] [--session <id>] [--anchor <a>] [--json]
|
|
6
|
+
* node bin/live-notes.js decide --repo <dir> --session <id> --segment <id> --action accept|change|reject [--initiative <id>] [--relation <word>]
|
|
7
|
+
* node bin/live-notes.js close --repo <dir> --session <id>
|
|
8
|
+
* node bin/live-notes.js ingest --repo <dir> --transcript <file> --source <name> --session <id> a transcript, only what is new (Run 4)
|
|
9
|
+
* node bin/live-notes.js list --repo <dir> --session <id> [--json] the ranked list (Run 2)
|
|
10
|
+
* node bin/live-notes.js rate --repo <dir> --session <id>
|
|
11
|
+
* node bin/live-notes.js plan --repo <dir> --session <id> [--json] what an agent should write, on which tier (Run 3)
|
|
12
|
+
* node bin/live-notes.js context --repo <dir> --session <id> --answers <file> LIVECONTEXT and LIVECANDIDATE lines (Run 3)
|
|
13
|
+
* node bin/live-notes.js switch --repo <dir> on|off [--retention-days <n>]
|
|
14
|
+
*
|
|
15
|
+
* Segments are one per line: plain text, or a JSON object {"text","at","speaker","source","private"}.
|
|
16
|
+
* Without `--answers`, each point is left `pending` and the prompt for an agent session is printed; the agent's
|
|
17
|
+
* `LIVENOTE {json}` lines go back in with `--answers`. When all three gates hold (a key, consent for the repository,
|
|
18
|
+
* and `switch on`), the prompt asks that agent session to make the two choices with the Typesafe skill (Run 5). This
|
|
19
|
+
* command sends nothing itself and injects no sender. The key is never read, only whether one exists.
|
|
20
|
+
*/
|
|
21
|
+
'use strict';
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const live = require('../lib/live-notes');
|
|
25
|
+
const settings = require('../lib/typesafe-settings');
|
|
26
|
+
const { defaultRoot } = require('../lib/state-root');
|
|
27
|
+
|
|
28
|
+
const argv = process.argv.slice(2);
|
|
29
|
+
const cmd = argv[0];
|
|
30
|
+
const opt = (n, d = null) => { const i = argv.indexOf('--' + n); return i !== -1 && argv[i + 1] && !argv[i + 1].startsWith('--') ? argv[i + 1] : d; };
|
|
31
|
+
const flag = (n) => argv.includes('--' + n);
|
|
32
|
+
const die = (m, c = 2) => { console.error('live-notes: ' + m); process.exit(c); };
|
|
33
|
+
|
|
34
|
+
if (!['run', 'decide', 'close', 'rate', 'switch', 'plan', 'context', 'ingest', 'list'].includes(cmd)) die('commands: run, ingest, list, decide, close, rate, switch, plan, context (see the header of this file)');
|
|
35
|
+
const root = path.resolve(opt('repo', process.cwd()));
|
|
36
|
+
if (!fs.existsSync(root)) die('no such directory: ' + root);
|
|
37
|
+
const runtimeDir = defaultRoot();
|
|
38
|
+
const exclude = settings.getRepo(runtimeDir, root).exclude;
|
|
39
|
+
const consent = settings.getRepo(runtimeDir, root).consent;
|
|
40
|
+
const sw = live.getSwitch(runtimeDir, root);
|
|
41
|
+
live.purgeExpired(runtimeDir);
|
|
42
|
+
|
|
43
|
+
function session(id, extra = {}) {
|
|
44
|
+
return live.openSession({ root, runtimeDir, session: id, key: settings.status(runtimeDir, process.env).present ? 'present' : null,
|
|
45
|
+
flags: { consent, live: sw.live }, exclude, anchor: opt('anchor'), ask: null, ...extra });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
(async () => {
|
|
49
|
+
try {
|
|
50
|
+
if (cmd === 'switch') {
|
|
51
|
+
const pos = argv.filter((x, i) => !x.startsWith('--') && !(argv[i - 1] || '').startsWith('--'));
|
|
52
|
+
const on = pos[1] === 'on' ? true : pos[1] === 'off' ? false : null;
|
|
53
|
+
if (on === null && !opt('retention-days')) die('say on or off, or --retention-days <n>');
|
|
54
|
+
const r = live.setSwitch(runtimeDir, root, { live: on === null ? undefined : on, retention_days: opt('retention-days') ? Number(opt('retention-days')) : undefined });
|
|
55
|
+
console.log(`live notes may use Jev: ${r.live ? 'on' : 'off'} · raw transcript kept ${r.retention_days ? r.retention_days + ' day(s) after close' : 'only until close'}`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const id = opt('session');
|
|
59
|
+
if (cmd === 'run' || cmd === 'ingest') {
|
|
60
|
+
const src = cmd === 'ingest' ? opt('transcript') : opt('segments');
|
|
61
|
+
if (cmd === 'ingest' && !src) die('--transcript <file> is required');
|
|
62
|
+
if (!src && !opt('answers')) die('give --segments <file>|- , --answers <file>, or both');
|
|
63
|
+
const probe = session(id);
|
|
64
|
+
const out = { session: probe.id, path: probe.path, gates_missing: probe.gates.missing, withheld: [], pending: [], error: null, rows: [], rejected: [] };
|
|
65
|
+
let prompt = null;
|
|
66
|
+
let s = probe;
|
|
67
|
+
if (src) {
|
|
68
|
+
const text = src === '-' ? fs.readFileSync(0, 'utf8') : fs.readFileSync(src, 'utf8');
|
|
69
|
+
const sourceName = opt('source') || (src === '-' ? 'stdin' : path.basename(src));
|
|
70
|
+
const raw = cmd === 'ingest' ? live.segmentsFromTranscript(text, { source: sourceName }) : text.split(/\r?\n/).filter((l) => l.trim()).map((l) => { if (l.trim().startsWith('{')) { try { return JSON.parse(l); } catch { /* plain */ } } return l; });
|
|
71
|
+
const r = await s.add(raw, { source: sourceName, dedupe: cmd === 'ingest' });
|
|
72
|
+
out.skipped_duplicates = r.skipped_duplicates;
|
|
73
|
+
Object.assign(out, { withheld: r.withheld, pending: r.pending, error: r.error || null, rows: r.rows });
|
|
74
|
+
prompt = r.prompt;
|
|
75
|
+
}
|
|
76
|
+
if (opt('answers')) {
|
|
77
|
+
const ans = live.parseAgentAnswers(fs.readFileSync(opt('answers'), 'utf8'), s.items.map((i) => i.id));
|
|
78
|
+
out.rejected = ans.rejected;
|
|
79
|
+
const r = s.answer(ans.answers);
|
|
80
|
+
out.rows = out.rows.concat(r.answered);
|
|
81
|
+
out.pending = out.pending.filter((x) => !r.answered.some((a) => a.segment === x));
|
|
82
|
+
if (r.unknown.length) out.unknown_segments = r.unknown;
|
|
83
|
+
if (r.answered.length) prompt = null;
|
|
84
|
+
}
|
|
85
|
+
if (flag('json')) { console.log(JSON.stringify({ ...out, prompt }, null, 2)); return; }
|
|
86
|
+
const why = out.path === 'jev-agent' ? 'the prompt asks your agent session to use the Typesafe skill; this command sends nothing itself' : live.gateSentence(out.gates_missing) || 'all gates are open';
|
|
87
|
+
console.log(`session ${out.session} · path: ${out.path} (${why})`);
|
|
88
|
+
if (out.error) console.log('stopped: ' + out.error);
|
|
89
|
+
if (out.skipped_duplicates) console.log(`skipped ${out.skipped_duplicates} point(s) already in this session`);
|
|
90
|
+
if (out.rejected.length) console.log(`rejected ${out.rejected.length} off-shape answer line(s): ${out.rejected.join(', ')}`);
|
|
91
|
+
for (const x of out.rows) console.log(` ${x.segment}: ${x.status === 'proposed' ? `${x.relation} ${x.initiative} (${x.relation_band}) · ${x.confidence}${x.in_scope === false ? ' · outside scope' : ''}` : x.status}`);
|
|
92
|
+
for (const w of out.withheld) console.log(` ${w.id}: withheld — ${w.reason}`);
|
|
93
|
+
if (prompt) console.log('\nGive this to an agent session, then pass its LIVENOTE lines back with --answers <file> --session ' + out.session + ':\n\n' + prompt);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (!id) die('--session <id> is required');
|
|
97
|
+
{ const known = live.apiList({ root, session: id }); if (!known.ok) die(known.error, 1); } // a command on a session that does not exist creates nothing
|
|
98
|
+
const s = session(id);
|
|
99
|
+
if (cmd === 'decide') { const row = s.decide({ segment: opt('segment'), action: opt('action'), initiative: opt('initiative'), relation: opt('relation') }); console.log(`recorded: ${row.action} ${row.segment}${row.overridden ? ' (override)' : ''}`); return; }
|
|
100
|
+
if (cmd === 'close') { const r = s.close({ retentionDays: sw.retention_days }); console.log(r.already ? 'already closed' : `closed · note: ${r.note} · raw transcript: ${r.transcript}`); return; }
|
|
101
|
+
if (cmd === 'list') {
|
|
102
|
+
const r = live.apiList({ root, session: id });
|
|
103
|
+
if (!r.ok) die(r.error, 1);
|
|
104
|
+
if (flag('json')) { console.log(JSON.stringify(r.ranked, null, 2)); return; }
|
|
105
|
+
const k = r.ranked;
|
|
106
|
+
console.log(`session ${id} · path: ${k.path}${k.closed ? ' · closed' : ''} · ${k.counts.points} point(s), ${k.counts.pending} not yet answered, ${k.withheld} withheld${k.paused ? ' · PAUSED: ' + k.paused.reason : ''}`);
|
|
107
|
+
for (const sec of k.sections) {
|
|
108
|
+
console.log(`\n${sec.title}`);
|
|
109
|
+
for (const x of sec.items) console.log(` ${x.segment}: "${x.quote}"${x.initiative && x.initiative !== 'none' ? ` — ${x.relation} ${x.initiative} (${x.relation_band})` : ''}${x.in_scope === false ? ' · outside scope' : ''}${x.decision ? ' · PM: ' + x.decision.action : ''}${x.context ? ' · note: ' + x.context.note : ''}`);
|
|
110
|
+
}
|
|
111
|
+
if (k.candidates.length) { console.log('\nPossible new initiatives'); for (const c of k.candidates) console.log(` ${c.candidate}: ${c.draft ? '"' + c.draft.title + '" (' + c.draft.lane + ')' : c.topic_terms.join(', ')} · ${c.segments.length} points · prior-rejection check found ${c.prior_rejection_found}`); }
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (cmd === 'plan') {
|
|
115
|
+
const p = s.plan();
|
|
116
|
+
if (flag('json')) { console.log(JSON.stringify(p, null, 2)); return; }
|
|
117
|
+
console.log(`tagging: ${p.tagging_tier} tier · ${p.context.filter((x) => !x.done).length} context note(s) and ${p.candidates.filter((x) => !x.done).length} candidate draft(s) to write`);
|
|
118
|
+
for (const x of p.context) console.log(` ${x.done ? '✓' : '·'} ${x.segment} [${x.tier}] ${x.reasons.join('; ')} — read ${x.read.join(', ')}`);
|
|
119
|
+
for (const x of p.candidates) console.log(` ${x.done ? '✓' : '·'} ${x.candidate} [${x.tier}] ${x.topic_terms.join(', ')}`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (cmd === 'context') {
|
|
123
|
+
if (!opt('answers')) die('--answers <file> is required');
|
|
124
|
+
const text = fs.readFileSync(opt('answers'), 'utf8');
|
|
125
|
+
const c = s.addContext(live.parseContext(text).entries);
|
|
126
|
+
const d = s.addDrafts(live.parseDrafts(text).entries);
|
|
127
|
+
console.log(`accepted ${c.accepted.length} context note(s) and ${d.accepted.length} candidate draft(s)`);
|
|
128
|
+
for (const r of c.rejected.concat(d.rejected)) console.log(` rejected ${r.segment || r.candidate || '?'}: ${r.reason}`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (cmd === 'rate') { console.log(JSON.stringify(live.overrideRate(s.ledger()), null, 2)); return; }
|
|
132
|
+
} catch (e) { die(e.message, 1); }
|
|
133
|
+
})();
|