@quolu/lattice 0.12.9 → 0.12.10
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/bin/lattice.mjs +3 -0
- package/package.json +2 -2
- package/src/cli-stdio.mjs +40 -0
- package/src/todo-store.mjs +29 -5
package/bin/lattice.mjs
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { installPipeCloseGuard } from '../src/cli-stdio.mjs';
|
|
3
4
|
import { runRuntimeCli } from '../src/runtime-cli.mjs';
|
|
4
5
|
import { renderCliHelp } from '../src/cli-help.mjs';
|
|
5
6
|
import packageJson from '../package.json' with { type: 'json' };
|
|
6
7
|
|
|
8
|
+
installPipeCloseGuard();
|
|
9
|
+
|
|
7
10
|
const args = process.argv.slice(2);
|
|
8
11
|
const help = renderCliHelp(args);
|
|
9
12
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quolu/lattice",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.10",
|
|
4
4
|
"description": "Lattice — phase-aware TODO graph compiler and conflict-aware orchestration runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"test": "node scripts/run-product-tests.mjs",
|
|
55
55
|
"test:sensor": "npm --prefix sensor test",
|
|
56
56
|
"precheck": "node --check src/bridge-launch-agent.mjs",
|
|
57
|
-
"check": "node --check bin/lattice.mjs && node --check bin/lattice-mcp.mjs && node --check bin/lattice-dashboard.mjs && node --check bin/lattice-bridge.mjs && node --check src/bridge-config.mjs && node --check src/bridge-server.mjs && node --check src/bridge-daemon.mjs && node --check src/bridge-cli.mjs && node --check src/project-cli.mjs && node --check src/sensor-cli.mjs && node --check src/sensor-runtime.mjs && node --check src/sensor-adapter.mjs && node --check src/factory-diagnostics.mjs && node --check src/runtime-errors.mjs && node --check src/runtime-contracts.mjs && node --check src/runtime-event-store.mjs && node --check src/hash-chain.mjs && node --check src/dag-chain.mjs && node --check src/todo-contracts.mjs && node --check src/todo-chain.mjs && node --check src/todo-store.mjs && node --check src/todo-migration.mjs && node --check src/todo-revision.mjs && node --check src/todo-status.mjs && node --check src/todo-cli.mjs && node --check src/todo-dashboard-registry.mjs && node --check src/todo-gantt-presentation.mjs && node --check src/todo-gantt-live.mjs && node --check src/bounded-seam.mjs && node --check src/todo-narrative-anchor.mjs && node --check src/todo-markdown-renderer.mjs && node --check src/todo-gantt-svg.mjs && node --check src/todo-gantt-html.mjs && node --check src/runtime-projection.mjs && node --check src/runtime-decision-verifier.mjs && node --check src/runtime-front-end.mjs && node --check src/runtime-cli.mjs && node --check src/rc3-dogfood-scaffold.mjs && node --check src/runtime-engine.mjs && node --check src/runtime-scripted-executor.mjs && node --check src/runtime-diff-observer.mjs && node --check src/runtime-worktree-executor.mjs && node --check src/runtime-hold-recompile.mjs && node --check src/rc3-scripted-campaign.mjs && node --check src/rc3-actual-dogfood.mjs && node --check src/rc4-stage1-dogfood.mjs && node --check research/fixtures/dispatch-record/src/dispatch-record.mjs && node --check test/research-dispatch-record.test.mjs",
|
|
57
|
+
"check": "node --check bin/lattice.mjs && node --check bin/lattice-mcp.mjs && node --check bin/lattice-dashboard.mjs && node --check bin/lattice-bridge.mjs && node --check src/cli-stdio.mjs && node --check src/bridge-config.mjs && node --check src/bridge-server.mjs && node --check src/bridge-daemon.mjs && node --check src/bridge-cli.mjs && node --check src/project-cli.mjs && node --check src/sensor-cli.mjs && node --check src/sensor-runtime.mjs && node --check src/sensor-adapter.mjs && node --check src/factory-diagnostics.mjs && node --check src/runtime-errors.mjs && node --check src/runtime-contracts.mjs && node --check src/runtime-event-store.mjs && node --check src/hash-chain.mjs && node --check src/dag-chain.mjs && node --check src/todo-contracts.mjs && node --check src/todo-chain.mjs && node --check src/todo-store.mjs && node --check src/todo-migration.mjs && node --check src/todo-revision.mjs && node --check src/todo-status.mjs && node --check src/todo-cli.mjs && node --check src/todo-dashboard-registry.mjs && node --check src/todo-gantt-presentation.mjs && node --check src/todo-gantt-live.mjs && node --check src/bounded-seam.mjs && node --check src/todo-narrative-anchor.mjs && node --check src/todo-markdown-renderer.mjs && node --check src/todo-gantt-svg.mjs && node --check src/todo-gantt-html.mjs && node --check src/runtime-projection.mjs && node --check src/runtime-decision-verifier.mjs && node --check src/runtime-front-end.mjs && node --check src/runtime-cli.mjs && node --check src/rc3-dogfood-scaffold.mjs && node --check src/runtime-engine.mjs && node --check src/runtime-scripted-executor.mjs && node --check src/runtime-diff-observer.mjs && node --check src/runtime-worktree-executor.mjs && node --check src/runtime-hold-recompile.mjs && node --check src/rc3-scripted-campaign.mjs && node --check src/rc3-actual-dogfood.mjs && node --check src/rc4-stage1-dogfood.mjs && node --check research/fixtures/dispatch-record/src/dispatch-record.mjs && node --check test/research-dispatch-record.test.mjs",
|
|
58
58
|
"check:project-identity": "node --check src/project-identity.mjs",
|
|
59
59
|
"ci": "npm run test && npm run test:sensor && npm run check && npm run check:project-identity"
|
|
60
60
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI stdio guards.
|
|
3
|
+
*
|
|
4
|
+
* Reading part of a result is a legitimate use: `lattice todo status --json |
|
|
5
|
+
* head` stops the consumer as soon as it has what it wants. When that consumer
|
|
6
|
+
* exits, the next write to stdout raises EPIPE, and Node's default handling
|
|
7
|
+
* turns it into an unhandled 'error' event — the CLI dies with a stack trace
|
|
8
|
+
* and a non-zero exit, so a working pipeline reports a failure that never
|
|
9
|
+
* happened.
|
|
10
|
+
*
|
|
11
|
+
* EPIPE from a closed consumer is the POSIX contract, not a fault, so the
|
|
12
|
+
* process stops quietly. This is deliberately narrow: only EPIPE, only on the
|
|
13
|
+
* output streams. Every other stream error keeps its previous behaviour and
|
|
14
|
+
* still crashes the CLI, because those are real faults.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {object} [options]
|
|
19
|
+
* @param {Array<NodeJS.WriteStream>} [options.streams] streams to guard.
|
|
20
|
+
* @param {(code: number) => void} [options.exit] process exit, injectable for tests.
|
|
21
|
+
* @returns {() => void} removes the guards again (used by tests).
|
|
22
|
+
*/
|
|
23
|
+
export function installPipeCloseGuard({
|
|
24
|
+
streams = [process.stdout, process.stderr],
|
|
25
|
+
exit = (code) => process.exit(code),
|
|
26
|
+
} = {}) {
|
|
27
|
+
const installed = [];
|
|
28
|
+
for (const stream of streams) {
|
|
29
|
+
if (stream === undefined || stream === null || typeof stream.on !== 'function') continue;
|
|
30
|
+
const onError = (error) => {
|
|
31
|
+
if (error?.code !== 'EPIPE') throw error;
|
|
32
|
+
exit(0);
|
|
33
|
+
};
|
|
34
|
+
stream.on('error', onError);
|
|
35
|
+
installed.push(() => stream.off('error', onError));
|
|
36
|
+
}
|
|
37
|
+
return () => {
|
|
38
|
+
for (const remove of installed) remove();
|
|
39
|
+
};
|
|
40
|
+
}
|
package/src/todo-store.mjs
CHANGED
|
@@ -1082,17 +1082,41 @@ function nextEvent(input, storeMember) {
|
|
|
1082
1082
|
return event;
|
|
1083
1083
|
}
|
|
1084
1084
|
|
|
1085
|
+
/**
|
|
1086
|
+
* The digest replay binds a task's completion to.
|
|
1087
|
+
*
|
|
1088
|
+
* A ToDo completed inside this plan version binds to its own `done` event. A
|
|
1089
|
+
* ToDo carried across a revision has no `done` event in the successor journal —
|
|
1090
|
+
* its completion arrives with the `plan_genesis` state migration, and replay
|
|
1091
|
+
* binds it to the genesis digest (see the carry branch in `replay`). Resolving
|
|
1092
|
+
* only the first case is what made `reopen` unusable on carried work.
|
|
1093
|
+
*
|
|
1094
|
+
* Returns null when the task has no completion to bind to at all.
|
|
1095
|
+
*/
|
|
1096
|
+
function resolveDoneBindingDigest(storeMember, taskId) {
|
|
1097
|
+
const events = storeMember.journal.events;
|
|
1098
|
+
const authored = [...events].reverse().find((event) => (
|
|
1099
|
+
event.kind === 'done' && event.task_id === taskId
|
|
1100
|
+
));
|
|
1101
|
+
if (authored !== undefined) return authored.event_digest;
|
|
1102
|
+
const genesis = events[0];
|
|
1103
|
+
if (genesis?.kind !== 'plan_genesis' || !Array.isArray(genesis.state_migration)) return null;
|
|
1104
|
+
const carried = genesis.state_migration.some((migration) => (
|
|
1105
|
+
['carry', 'carry_reconciled_metadata'].includes(migration.state_policy)
|
|
1106
|
+
&& migration.to_task_id === taskId && migration.state?.status === 'done'
|
|
1107
|
+
));
|
|
1108
|
+
return carried ? genesis.event_digest : null;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1085
1111
|
function resolveTargetedEvent(input, storeMember) {
|
|
1086
1112
|
if (input.kind === 'reopen' && exactRecord(input.payload, [
|
|
1087
1113
|
'reason', 'override_reason',
|
|
1088
1114
|
])) {
|
|
1089
|
-
const
|
|
1090
|
-
|
|
1091
|
-
));
|
|
1092
|
-
if (target === undefined) fail('STORE_INCONSISTENT', 'invalid_reopen_binding');
|
|
1115
|
+
const targetDigest = resolveDoneBindingDigest(storeMember, input.task_id);
|
|
1116
|
+
if (targetDigest === null) fail('STORE_INCONSISTENT', 'invalid_reopen_binding');
|
|
1093
1117
|
return {
|
|
1094
1118
|
...input,
|
|
1095
|
-
payload: { ...input.payload, target_done_digest:
|
|
1119
|
+
payload: { ...input.payload, target_done_digest: targetDigest },
|
|
1096
1120
|
};
|
|
1097
1121
|
}
|
|
1098
1122
|
if (input.kind === 'done' && exactRecord(input.payload, [
|