yaml-flow 5.4.0 → 6.0.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/board-live-cards-cli.js +2 -2
- package/board-livecards-server-runtime.js +488 -551
- package/browser/asset-integrity.json +10 -0
- package/browser/board-livecards-runtime-client.js +0 -6
- package/browser/board-livegraph-engine.js +2 -1676
- package/browser/board-livegraph-engine.js.map +1 -1
- package/browser/live-cards.js +347 -26
- package/browser/live-cards.schema.json +418 -132
- package/card-store.js +37 -0
- package/dist/batch/index.cjs +1 -108
- package/dist/batch/index.cjs.map +1 -1
- package/dist/batch/index.js +1 -106
- package/dist/batch/index.js.map +1 -1
- package/dist/board-live-cards-lib-Bg6EvCo5.d.cts +136 -0
- package/dist/board-live-cards-lib-jM2uYG1v.d.ts +136 -0
- package/dist/board-live-cards-public-CltXYgaY.d.cts +314 -0
- package/dist/board-live-cards-public-f-E-FAyp.d.ts +314 -0
- package/dist/board-livegraph-runtime/index.cjs +2 -1671
- package/dist/board-livegraph-runtime/index.cjs.map +1 -1
- package/dist/board-livegraph-runtime/index.d.cts +1 -2
- package/dist/board-livegraph-runtime/index.d.ts +1 -2
- package/dist/board-livegraph-runtime/index.js +2 -1662
- package/dist/board-livegraph-runtime/index.js.map +1 -1
- package/dist/board-livegraph-runtime/jsonata-sync.cjs +7587 -0
- package/dist/card-compute/index.cjs +9 -7159
- package/dist/card-compute/index.cjs.map +1 -1
- package/dist/card-compute/index.d.cts +22 -0
- package/dist/card-compute/index.d.ts +22 -0
- package/dist/card-compute/index.js +9 -7145
- package/dist/card-compute/index.js.map +1 -1
- package/dist/card-compute/jsonata-sync.cjs +7587 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs +2 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs.map +1 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.d.cts +24 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.d.ts +24 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.js +2 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.js.map +1 -0
- package/dist/cli/browser-api/card-store-browser-api.cjs +2 -0
- package/dist/cli/browser-api/card-store-browser-api.cjs.map +1 -0
- package/dist/cli/browser-api/card-store-browser-api.d.cts +26 -0
- package/dist/cli/browser-api/card-store-browser-api.d.ts +26 -0
- package/dist/cli/browser-api/card-store-browser-api.js +2 -0
- package/dist/cli/browser-api/card-store-browser-api.js.map +1 -0
- package/dist/cli/browser-api/jsonata-sync.cjs +7587 -0
- package/dist/cli/node/artifacts-store-cli.cjs +11 -0
- package/dist/cli/node/artifacts-store-cli.cjs.map +1 -0
- package/dist/cli/node/artifacts-store-cli.d.cts +8 -0
- package/dist/cli/node/artifacts-store-cli.d.ts +8 -0
- package/dist/cli/node/artifacts-store-cli.js +11 -0
- package/dist/cli/node/artifacts-store-cli.js.map +1 -0
- package/dist/cli/node/board-live-cards-cli.cjs +15 -0
- package/dist/cli/node/board-live-cards-cli.cjs.map +1 -0
- package/dist/cli/node/board-live-cards-cli.d.cts +20 -0
- package/dist/cli/node/board-live-cards-cli.d.ts +20 -0
- package/dist/cli/node/board-live-cards-cli.js +15 -0
- package/dist/cli/node/board-live-cards-cli.js.map +1 -0
- package/dist/cli/node/card-store-cli.cjs +8 -0
- package/dist/cli/node/card-store-cli.cjs.map +1 -0
- package/dist/cli/node/card-store-cli.d.cts +15 -0
- package/dist/cli/node/card-store-cli.d.ts +15 -0
- package/dist/cli/node/card-store-cli.js +8 -0
- package/dist/cli/node/card-store-cli.js.map +1 -0
- package/dist/cli/node/fs-board-adapter.cjs +14 -0
- package/dist/cli/node/fs-board-adapter.cjs.map +1 -0
- package/dist/cli/node/fs-board-adapter.d.cts +204 -0
- package/dist/cli/node/fs-board-adapter.d.ts +204 -0
- package/dist/cli/node/fs-board-adapter.js +14 -0
- package/dist/cli/node/fs-board-adapter.js.map +1 -0
- package/dist/cli/node/jsonata-sync.cjs +7587 -0
- package/dist/cli/node/source-cli-task-executor.cjs +11 -0
- package/dist/cli/node/source-cli-task-executor.cjs.map +1 -0
- package/dist/cli/node/source-cli-task-executor.d.cts +1 -0
- package/dist/cli/node/source-cli-task-executor.d.ts +1 -0
- package/dist/cli/node/source-cli-task-executor.js +11 -0
- package/dist/cli/node/source-cli-task-executor.js.map +1 -0
- package/dist/config/index.cjs +1 -79
- package/dist/config/index.cjs.map +1 -1
- package/dist/config/index.js +1 -76
- package/dist/config/index.js.map +1 -1
- package/dist/continuous-event-graph/index.cjs +2 -2129
- package/dist/continuous-event-graph/index.cjs.map +1 -1
- package/dist/continuous-event-graph/index.d.cts +81 -5
- package/dist/continuous-event-graph/index.d.ts +81 -5
- package/dist/continuous-event-graph/index.js +2 -2088
- package/dist/continuous-event-graph/index.js.map +1 -1
- package/dist/continuous-event-graph/jsonata-sync.cjs +7587 -0
- package/dist/event-graph/index.cjs +22 -8292
- package/dist/event-graph/index.cjs.map +1 -1
- package/dist/event-graph/index.js +22 -8237
- package/dist/event-graph/index.js.map +1 -1
- package/dist/execution-refs.cjs +2 -0
- package/dist/execution-refs.cjs.map +1 -0
- package/dist/execution-refs.d.cts +222 -0
- package/dist/execution-refs.d.ts +222 -0
- package/dist/execution-refs.js +2 -0
- package/dist/execution-refs.js.map +1 -0
- package/dist/index.cjs +29 -13221
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -4
- package/dist/index.d.ts +2 -4
- package/dist/index.js +29 -13112
- package/dist/index.js.map +1 -1
- package/dist/inference/index.cjs +5 -617
- package/dist/inference/index.cjs.map +1 -1
- package/dist/inference/index.js +5 -610
- package/dist/inference/index.js.map +1 -1
- package/dist/jsonata-sync.cjs +7587 -0
- package/dist/{live-cards-bridge-x5XREkXm.d.cts → live-cards-bridge-BXbVTsna.d.cts} +27 -4
- package/dist/{live-cards-bridge-EQjytzI_.d.ts → live-cards-bridge-Ds28XR15.d.ts} +27 -4
- package/dist/pycli/quickjs-board-runtime.global.js +9 -0
- package/dist/pycli/quickjs-board-runtime.global.js.map +1 -0
- package/dist/pycli/quickjs-step-machine-runtime.global.js +5 -0
- package/dist/pycli/quickjs-step-machine-runtime.global.js.map +1 -0
- package/dist/step-machine/index.cjs +11 -7129
- package/dist/step-machine/index.cjs.map +1 -1
- package/dist/step-machine/index.js +11 -7113
- package/dist/step-machine/index.js.map +1 -1
- package/dist/storage-refs.cjs +10 -0
- package/dist/storage-refs.cjs.map +1 -0
- package/dist/storage-refs.d.cts +92 -0
- package/dist/storage-refs.d.ts +92 -0
- package/dist/storage-refs.js +10 -0
- package/dist/storage-refs.js.map +1 -0
- package/dist/stores/file.cjs +1 -114
- package/dist/stores/file.cjs.map +1 -1
- package/dist/stores/file.js +1 -112
- package/dist/stores/file.js.map +1 -1
- package/dist/stores/index.cjs +1 -231
- package/dist/stores/index.cjs.map +1 -1
- package/dist/stores/index.js +1 -227
- package/dist/stores/index.js.map +1 -1
- package/dist/stores/localStorage.cjs +1 -76
- package/dist/stores/localStorage.cjs.map +1 -1
- package/dist/stores/localStorage.js +1 -74
- package/dist/stores/localStorage.js.map +1 -1
- package/dist/stores/memory.cjs +1 -47
- package/dist/stores/memory.cjs.map +1 -1
- package/dist/stores/memory.js +1 -45
- package/dist/stores/memory.js.map +1 -1
- package/examples/browser/boards/portfolio-tracker/portfolio-t4.js +292 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.js +218 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.py +201 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-inference-adapter.js +25 -16
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-public.js +553 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.py +365 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/.runtime-out +1 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/board-graph.json +32 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +53 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +15 -6
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +6 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/poll-status-cli.js +57 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/status-cli.js +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +7 -2
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +6 -2
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/_board_pycli.py +97 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/add-cards.py +50 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/init-board.py +44 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/poll-status.py +70 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/reset-board-dir.py +36 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-demo.flow.yaml +26 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-handlers.py +39 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker-pycli.flow.yaml +80 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +25 -172
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +40 -34
- package/examples/cli/step-machine-cli/portfolio-tracker/run-inline-python-demo-pycli.py +46 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker-pycli.py +77 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
- package/examples/example-board/agent-instructions.md +11 -5
- package/examples/example-board/demo-chat-handler.js +14 -4
- package/examples/example-board/demo-server-config.json +1 -0
- package/examples/example-board/demo-server.js +19 -34
- package/examples/example-board/demo-shell-browser.html +5 -4
- package/examples/example-board/demo-shell-with-server.html +10 -6
- package/examples/example-board/demo-task-executor.js +81 -35
- package/examples/index.html +0 -14
- package/examples/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +0 -1
- package/examples/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
- package/package.json +39 -3
- package/schema/live-cards.schema.json +418 -132
- package/dist/cli/board-live-cards-cli.cjs +0 -10644
- package/dist/cli/board-live-cards-cli.cjs.map +0 -1
- package/dist/cli/board-live-cards-cli.d.cts +0 -179
- package/dist/cli/board-live-cards-cli.d.ts +0 -179
- package/dist/cli/board-live-cards-cli.js +0 -10592
- package/dist/cli/board-live-cards-cli.js.map +0 -1
- package/dist/journal-9HEgs7dU.d.ts +0 -28
- package/dist/journal-B-JCfQnh.d.cts +0 -28
- package/dist/schedule-Cszq9LYY.d.ts +0 -21
- package/dist/schedule-qWNL0RQh.d.cts +0 -21
- package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +0 -22
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +0 -16
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +0 -28
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +0 -15
- package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +0 -15
- package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +0 -28
- package/examples/browser/boards/portfolio-tracker/fetch-prices.js +0 -43
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-task-executor.cjs +0 -96
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.bat +0 -7
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +0 -351
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""portfolio-tracker.py — E2E orchestrator for the portfolio board demo.
|
|
3
|
+
|
|
4
|
+
Black-box CLI client. All board and card-store operations are performed by
|
|
5
|
+
shelling out to board-live-cards-cli and card-store-cli. No env vars. No
|
|
6
|
+
fallbacks or defaults.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import copy
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import shutil
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
import tempfile
|
|
16
|
+
import time
|
|
17
|
+
import argparse
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
_CLI_PARSER = argparse.ArgumentParser()
|
|
21
|
+
_MODE_GROUP = _CLI_PARSER.add_mutually_exclusive_group()
|
|
22
|
+
_MODE_GROUP.add_argument('--run-pycli', action='store_true', help='Use pycli for board/card operations (default)')
|
|
23
|
+
_MODE_GROUP.add_argument('--run-nodecli', action='store_true', help='Use node cli for board/card operations')
|
|
24
|
+
_CLI_ARGS = _CLI_PARSER.parse_args()
|
|
25
|
+
RUN_PYCLI = not _CLI_ARGS.run_nodecli
|
|
26
|
+
|
|
27
|
+
# ── Path resolution ────────────────────────────────────────────────────────────
|
|
28
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
29
|
+
_REPO_ROOT = os.path.normpath(os.path.join(_HERE, '..', '..', '..', '..'))
|
|
30
|
+
|
|
31
|
+
NODE = shutil.which('node')
|
|
32
|
+
if not NODE:
|
|
33
|
+
print('[ERROR] node not found on PATH', file=sys.stderr)
|
|
34
|
+
sys.exit(1)
|
|
35
|
+
|
|
36
|
+
BOARD_CLI = os.path.join(_REPO_ROOT, 'board-live-cards-cli.js')
|
|
37
|
+
CARD_STORE_CLI = os.path.join(_REPO_ROOT, 'card-store.js')
|
|
38
|
+
FETCH_PRICES_PY = os.path.join(_HERE, 'portfolio-tracker-fetch-prices.py')
|
|
39
|
+
|
|
40
|
+
PYTHON = shutil.which('python')
|
|
41
|
+
if not PYTHON:
|
|
42
|
+
print('[ERROR] python not found on PATH (required for portfolio-tracker-fetch-prices.py)', file=sys.stderr)
|
|
43
|
+
sys.exit(1)
|
|
44
|
+
|
|
45
|
+
PYTHON_RUNNER = sys.executable or PYTHON
|
|
46
|
+
|
|
47
|
+
BOARD_PYCLI = os.path.join(_REPO_ROOT, 'pycli', 'main', 'board_live_cards_pycli.py')
|
|
48
|
+
CARD_STORE_PYCLI = os.path.join(_REPO_ROOT, 'pycli', 'main', 'card_store_pycli.py')
|
|
49
|
+
QUICKJS_BUNDLE = os.path.join(_REPO_ROOT, 'dist', 'pycli', 'quickjs-board-runtime.global.js')
|
|
50
|
+
|
|
51
|
+
if RUN_PYCLI:
|
|
52
|
+
if not os.path.exists(BOARD_PYCLI):
|
|
53
|
+
print(f'[ERROR] pycli entry not found: {BOARD_PYCLI}', file=sys.stderr)
|
|
54
|
+
sys.exit(1)
|
|
55
|
+
if not os.path.exists(CARD_STORE_PYCLI):
|
|
56
|
+
print(f'[ERROR] pycli entry not found: {CARD_STORE_PYCLI}', file=sys.stderr)
|
|
57
|
+
sys.exit(1)
|
|
58
|
+
if not os.path.exists(QUICKJS_BUNDLE):
|
|
59
|
+
print(f'[ERROR] quickjs bundle not found: {QUICKJS_BUNDLE}', file=sys.stderr)
|
|
60
|
+
print('Run from yaml-flow root: npm run build:quickjs', file=sys.stderr)
|
|
61
|
+
sys.exit(1)
|
|
62
|
+
|
|
63
|
+
if RUN_PYCLI:
|
|
64
|
+
ACTIVE_BOARD_BIN = [PYTHON_RUNNER, BOARD_PYCLI]
|
|
65
|
+
ACTIVE_CARD_STORE_BIN = [PYTHON_RUNNER, CARD_STORE_PYCLI]
|
|
66
|
+
ACTIVE_BOARD_SUFFIX = ['--bundle', QUICKJS_BUNDLE]
|
|
67
|
+
else:
|
|
68
|
+
ACTIVE_BOARD_BIN = [NODE, BOARD_CLI]
|
|
69
|
+
ACTIVE_CARD_STORE_BIN = [NODE, CARD_STORE_CLI]
|
|
70
|
+
ACTIVE_BOARD_SUFFIX = []
|
|
71
|
+
|
|
72
|
+
# ── Runtime directories (under os.tmpdir()/experiment/) ───────────────────────
|
|
73
|
+
_TMP_BASE = tempfile.mkdtemp(prefix='experiment-')
|
|
74
|
+
CARDSTORE_DIR = os.path.join(_TMP_BASE, 'cardstore')
|
|
75
|
+
BOARDRUNTIME_DIR = os.path.join(_TMP_BASE, 'boardruntime')
|
|
76
|
+
OUTPUTS_DIR = os.path.join(_TMP_BASE, 'outputs')
|
|
77
|
+
|
|
78
|
+
CARDSTORE_REF = f'::fs-path::{CARDSTORE_DIR}'
|
|
79
|
+
BOARDRUNTIME_REF = f'::fs-path::{BOARDRUNTIME_DIR}'
|
|
80
|
+
OUTPUTS_REF = f'::fs-path::{OUTPUTS_DIR}'
|
|
81
|
+
|
|
82
|
+
# ── Inline card definitions ────────────────────────────────────────────────────
|
|
83
|
+
CARD_PORTFOLIO_FORM = {
|
|
84
|
+
"id": "portfolio-form",
|
|
85
|
+
"meta": {"title": "Portfolio Holdings Form"},
|
|
86
|
+
"provides": [{"bindTo": "holdings", "ref": "card_data.holdings"}],
|
|
87
|
+
"card_data": {"holdings": []},
|
|
88
|
+
"view": {
|
|
89
|
+
"elements": [
|
|
90
|
+
{"kind": "table", "label": "Holdings",
|
|
91
|
+
"data": {"bind": "card_data.holdings", "columns": ["symbol", "qty"]}}
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
CARD_PRICE_FETCH = {
|
|
97
|
+
"id": "price-fetch",
|
|
98
|
+
"meta": {"title": "Fetch Market Prices"},
|
|
99
|
+
"requires": ["holdings"],
|
|
100
|
+
"provides": [{"bindTo": "prices", "ref": "fetched_sources.prices"}],
|
|
101
|
+
"card_data": {},
|
|
102
|
+
"source_defs": [{
|
|
103
|
+
"kind": "mock-quotes",
|
|
104
|
+
"bindTo": "prices",
|
|
105
|
+
"outputFile": "prices.json",
|
|
106
|
+
"projections": {"tickers": "requires.holdings.symbol"}
|
|
107
|
+
}],
|
|
108
|
+
"view": {
|
|
109
|
+
"elements": [
|
|
110
|
+
{"kind": "table", "label": "Market Prices",
|
|
111
|
+
"data": {"bind": "fetched_sources.prices"}}
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
CARD_HOLDINGS_TABLE = {
|
|
117
|
+
"id": "holdings-table",
|
|
118
|
+
"meta": {"title": "Holdings Table"},
|
|
119
|
+
"requires": ["holdings", "prices"],
|
|
120
|
+
"provides": [{"bindTo": "table", "ref": "computed_values.table"}],
|
|
121
|
+
"card_data": {},
|
|
122
|
+
"compute": [{
|
|
123
|
+
"bindTo": "table",
|
|
124
|
+
"expr": (
|
|
125
|
+
'{ "rows": $map(requires.holdings, function($h) { '
|
|
126
|
+
'{ "symbol": $h.symbol, "qty": $h.qty, '
|
|
127
|
+
'"price": $lookup(requires.prices, $h.symbol), '
|
|
128
|
+
'"value": $h.qty * $lookup(requires.prices, $h.symbol) } }) }'
|
|
129
|
+
)
|
|
130
|
+
}],
|
|
131
|
+
"view": {
|
|
132
|
+
"elements": [
|
|
133
|
+
{"kind": "table", "label": "Portfolio Positions",
|
|
134
|
+
"data": {"bind": "computed_values.table.rows",
|
|
135
|
+
"columns": ["symbol", "qty", "price", "value"]}}
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
CARD_PORTFOLIO_VALUE = {
|
|
141
|
+
"id": "portfolio-value",
|
|
142
|
+
"meta": {"title": "Portfolio Total Value"},
|
|
143
|
+
"requires": ["table"],
|
|
144
|
+
"provides": [{"bindTo": "totalValue", "ref": "computed_values.totalValue"}],
|
|
145
|
+
"card_data": {},
|
|
146
|
+
"compute": [
|
|
147
|
+
{"bindTo": "totalValue", "expr": "$sum(requires.table.rows.value)"}
|
|
148
|
+
],
|
|
149
|
+
"view": {
|
|
150
|
+
"elements": [
|
|
151
|
+
{"kind": "metric", "label": "Total Portfolio Value",
|
|
152
|
+
"data": {"bind": "computed_values.totalValue"}}
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
# ── Helpers ────────────────────────────────────────────────────────────────────
|
|
158
|
+
def set_holdings(card_json: dict, holdings: dict) -> dict:
|
|
159
|
+
card = copy.deepcopy(card_json)
|
|
160
|
+
card["card_data"]["holdings"] = [
|
|
161
|
+
{"symbol": symbol, "qty": qty}
|
|
162
|
+
for symbol, qty in holdings.items()
|
|
163
|
+
]
|
|
164
|
+
return card
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def run_board(*args):
|
|
168
|
+
subprocess.run([*ACTIVE_BOARD_BIN, *args, *ACTIVE_BOARD_SUFFIX], check=True, shell=False)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def run_board_with_input(*args, input_json: str):
|
|
172
|
+
subprocess.run(
|
|
173
|
+
[*ACTIVE_BOARD_BIN, *args, *ACTIVE_BOARD_SUFFIX],
|
|
174
|
+
input=input_json, check=True, shell=False, text=True,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def run_board_capture(*args) -> str:
|
|
179
|
+
result = subprocess.run(
|
|
180
|
+
[*ACTIVE_BOARD_BIN, *args, *ACTIVE_BOARD_SUFFIX],
|
|
181
|
+
check=True, shell=False, capture_output=True, text=True,
|
|
182
|
+
)
|
|
183
|
+
return result.stdout
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def run_card_store_set(card: dict):
|
|
187
|
+
subprocess.run(
|
|
188
|
+
[*ACTIVE_CARD_STORE_BIN, 'set', '--store-ref', CARDSTORE_REF],
|
|
189
|
+
input=json.dumps(card),
|
|
190
|
+
check=True,
|
|
191
|
+
shell=False,
|
|
192
|
+
text=True,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def read_json(path: str):
|
|
197
|
+
with open(path, encoding='utf-8') as f:
|
|
198
|
+
return json.load(f)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def wait_for_completed(label: str, timeout_s: float = 10.0, poll_s: float = 0.5):
|
|
202
|
+
required_names = {'portfolio-form', 'price-fetch', 'holdings-table', 'portfolio-value'}
|
|
203
|
+
deadline = time.monotonic() + timeout_s
|
|
204
|
+
while time.monotonic() < deadline:
|
|
205
|
+
raw = run_board_capture('status', '--base-ref', BOARDRUNTIME_REF)
|
|
206
|
+
data = json.loads(raw).get('data', {})
|
|
207
|
+
cards = data.get('cards', [])
|
|
208
|
+
completed = {c['name'] for c in cards if c.get('status') == 'completed'}
|
|
209
|
+
if required_names.issubset(completed):
|
|
210
|
+
print(f'[{label}] all cards completed.')
|
|
211
|
+
return
|
|
212
|
+
time.sleep(poll_s)
|
|
213
|
+
# timed out — print status and exit
|
|
214
|
+
raw = run_board_capture('status', '--base-ref', BOARDRUNTIME_REF)
|
|
215
|
+
print(f'[ERROR] {label}: timed out waiting for all cards to complete.', file=sys.stderr)
|
|
216
|
+
print(raw, file=sys.stderr)
|
|
217
|
+
sys.exit(1)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# ── T0a — Create runtime directories ──────────────────────────────────────────
|
|
221
|
+
print('\n=== T0a: Create runtime directories ===')
|
|
222
|
+
for d in (CARDSTORE_DIR, BOARDRUNTIME_DIR, OUTPUTS_DIR):
|
|
223
|
+
os.makedirs(d)
|
|
224
|
+
print(f' created: {d}')
|
|
225
|
+
|
|
226
|
+
# ── T0b — Init board ───────────────────────────────────────────────────────────
|
|
227
|
+
print('\n=== T0b: Init board ===')
|
|
228
|
+
_task_executor_body = json.dumps({
|
|
229
|
+
'task-executor-ref': {
|
|
230
|
+
'meta': 'task-executor',
|
|
231
|
+
'howToRun': 'local-python',
|
|
232
|
+
'whatToRun': f'::fs-path::{FETCH_PRICES_PY}',
|
|
233
|
+
}
|
|
234
|
+
})
|
|
235
|
+
run_board_with_input(
|
|
236
|
+
'init',
|
|
237
|
+
'--base-ref', BOARDRUNTIME_REF,
|
|
238
|
+
'--card-store-ref', CARDSTORE_REF,
|
|
239
|
+
'--outputs-store-ref', OUTPUTS_REF,
|
|
240
|
+
input_json=_task_executor_body,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# ── T0c — Set all cards into card store ────────────────────────────────────────
|
|
244
|
+
print('\n=== T0c: Set all cards into card store ===')
|
|
245
|
+
run_card_store_set(set_holdings(CARD_PORTFOLIO_FORM, {"AAPL": 50, "MSFT": 30}))
|
|
246
|
+
run_card_store_set(CARD_PRICE_FETCH)
|
|
247
|
+
run_card_store_set(CARD_HOLDINGS_TABLE)
|
|
248
|
+
run_card_store_set(CARD_PORTFOLIO_VALUE)
|
|
249
|
+
|
|
250
|
+
# ── T0d — Upsert cards to board ────────────────────────────────────────────────
|
|
251
|
+
print('\n=== T0d: Upsert cards to board ===')
|
|
252
|
+
for _card_id in ('portfolio-form', 'price-fetch', 'holdings-table', 'portfolio-value'):
|
|
253
|
+
run_board('upsert-card', '--base-ref', BOARDRUNTIME_REF, '--card-id', _card_id)
|
|
254
|
+
|
|
255
|
+
# ── T1 — Wait for all cards completed ──────────────────────────────────────────
|
|
256
|
+
print('\n=== T1: Wait for all cards completed ===')
|
|
257
|
+
wait_for_completed('T1')
|
|
258
|
+
|
|
259
|
+
_prices_path = os.path.join(OUTPUTS_DIR, 'data-objects', 'prices.json')
|
|
260
|
+
_prices_t1 = read_json(_prices_path)
|
|
261
|
+
assert isinstance(_prices_t1, dict) and len(_prices_t1) > 0, \
|
|
262
|
+
'T1: prices.json is empty or not an object'
|
|
263
|
+
assert set(_prices_t1.keys()) == {'AAPL', 'MSFT'}, \
|
|
264
|
+
f'T1: expected keys {{AAPL, MSFT}}, got {set(_prices_t1.keys())}'
|
|
265
|
+
assert all(isinstance(v, (int, float)) for v in _prices_t1.values()), \
|
|
266
|
+
'T1: all price values must be numbers'
|
|
267
|
+
print('[T1] assertion passed: prices.json has AAPL, MSFT with numeric values.')
|
|
268
|
+
|
|
269
|
+
# ── T2a — Update holdings (GOOG added) ────────────────────────────────────────
|
|
270
|
+
print('\n=== T2a: Update holdings (GOOG added) ===')
|
|
271
|
+
run_card_store_set(set_holdings(CARD_PORTFOLIO_FORM, {"AAPL": 50, "MSFT": 30, "GOOG": 100}))
|
|
272
|
+
|
|
273
|
+
# ── T2b — Upsert portfolio-form with --restart ─────────────────────────────────
|
|
274
|
+
print('\n=== T2b: Upsert portfolio-form --restart ===')
|
|
275
|
+
run_board('upsert-card', '--base-ref', BOARDRUNTIME_REF,
|
|
276
|
+
'--card-id', 'portfolio-form', '--restart')
|
|
277
|
+
|
|
278
|
+
# ── T2c — Wait and assert ──────────────────────────────────────────────────────
|
|
279
|
+
print('\n=== T2c: Wait for all cards completed ===')
|
|
280
|
+
wait_for_completed('T2c')
|
|
281
|
+
|
|
282
|
+
_prices_t2c = read_json(_prices_path)
|
|
283
|
+
assert set(_prices_t2c.keys()) == {'AAPL', 'MSFT', 'GOOG'}, \
|
|
284
|
+
f'T2c: expected keys {{AAPL, MSFT, GOOG}}, got {set(_prices_t2c.keys())}'
|
|
285
|
+
|
|
286
|
+
_ht_cv_path = os.path.join(OUTPUTS_DIR, 'cards', 'holdings-table', 'computed_values.json')
|
|
287
|
+
_ht_cv_t2c = read_json(_ht_cv_path)
|
|
288
|
+
assert len(_ht_cv_t2c['table']['rows']) == 3, \
|
|
289
|
+
f'T2c: expected 3 rows in holdings-table, got {len(_ht_cv_t2c["table"]["rows"])}'
|
|
290
|
+
print('[T2c] assertions passed: 3 tickers in prices, 3 rows in holdings-table.')
|
|
291
|
+
|
|
292
|
+
# ── T3 — Retrigger price-fetch, wait ──────────────────────────────────────────
|
|
293
|
+
print('\n=== T3: Retrigger price-fetch ===')
|
|
294
|
+
run_board('retrigger', '--base-ref', BOARDRUNTIME_REF, '--id', 'price-fetch')
|
|
295
|
+
wait_for_completed('T3')
|
|
296
|
+
|
|
297
|
+
_prices_t3 = read_json(_prices_path)
|
|
298
|
+
assert set(_prices_t3.keys()) == {'AAPL', 'MSFT', 'GOOG'}, \
|
|
299
|
+
f'T3: expected 3 tickers, got {set(_prices_t3.keys())}'
|
|
300
|
+
assert _prices_t3 != _prices_t2c, \
|
|
301
|
+
'T3: prices must differ from T2c values after retrigger (random regeneration)'
|
|
302
|
+
print('[T3] assertions passed: 3 tickers, prices differ from T2c.')
|
|
303
|
+
|
|
304
|
+
# ── T4 — Rapid 5× portfolio-form updates (queue stress test) ──────────────────
|
|
305
|
+
print('\n=== T4: Rapid 5x portfolio-form updates ===')
|
|
306
|
+
for _holdings in [
|
|
307
|
+
{"AAPL": 50, "MSFT": 30, "GOOG": 100, "AMZN": 40}, # V3
|
|
308
|
+
{"AAPL": 45, "MSFT": 30, "GOOG": 110, "AMZN": 40, "TSLA": 60}, # V4
|
|
309
|
+
{"AAPL": 45, "MSFT": 30, "GOOG": 110, "AMZN": 100}, # V4a
|
|
310
|
+
{"AAPL": 45, "MSFT": 30, "GOOG": 110, "AMZN": 140, "TSLA": 60}, # V4b
|
|
311
|
+
{"AAPL": 40, "MSFT": 35, "GOOG": 120, "TSLA": 70}, # V5
|
|
312
|
+
]:
|
|
313
|
+
run_card_store_set(set_holdings(CARD_PORTFOLIO_FORM, _holdings))
|
|
314
|
+
run_board('upsert-card', '--base-ref', BOARDRUNTIME_REF,
|
|
315
|
+
'--card-id', 'portfolio-form', '--restart')
|
|
316
|
+
|
|
317
|
+
wait_for_completed('T4')
|
|
318
|
+
|
|
319
|
+
_prices_t4 = read_json(_prices_path)
|
|
320
|
+
assert set(_prices_t4.keys()) == {'AAPL', 'MSFT', 'GOOG', 'TSLA'}, \
|
|
321
|
+
f'T4: expected keys {{AAPL, MSFT, GOOG, TSLA}}, got {set(_prices_t4.keys())}'
|
|
322
|
+
assert 'AMZN' not in _prices_t4, \
|
|
323
|
+
'T4: AMZN must not be present (board must have settled on V5 holdings)'
|
|
324
|
+
print('[T4] assertions passed: V5 tickers only, AMZN absent.')
|
|
325
|
+
|
|
326
|
+
# ── T5 — Print final status and cross-check ────────────────────────────────────
|
|
327
|
+
print('\n=== T5: Print final status and cross-check ===')
|
|
328
|
+
|
|
329
|
+
# Step 1: Wait for all cards completed (stable state before any assertions)
|
|
330
|
+
wait_for_completed('T5')
|
|
331
|
+
|
|
332
|
+
# Step 2: Capture live CLI status
|
|
333
|
+
_cli_raw = run_board_capture('status', '--base-ref', BOARDRUNTIME_REF)
|
|
334
|
+
_cli_status = json.loads(_cli_raw)['data']
|
|
335
|
+
|
|
336
|
+
# Step 3: Read status.json from outputs store
|
|
337
|
+
_file_status = read_json(os.path.join(OUTPUTS_DIR, 'status.json'))
|
|
338
|
+
|
|
339
|
+
# Step 4: Cross-check CLI vs file status
|
|
340
|
+
assert json.dumps(_cli_status, sort_keys=True) == json.dumps(_file_status, sort_keys=True), \
|
|
341
|
+
'T5: CLI status does not match status.json snapshot'
|
|
342
|
+
print('[T5] cross-check passed: CLI status matches status.json.')
|
|
343
|
+
|
|
344
|
+
# Step 5: Print holdings-table computed values
|
|
345
|
+
_ht_cv = read_json(_ht_cv_path)
|
|
346
|
+
print('\nFinal portfolio positions table:')
|
|
347
|
+
print(json.dumps(_ht_cv['table'], indent=2))
|
|
348
|
+
|
|
349
|
+
# Step 6: Totals cross-verify: holdings × prices == totalValue
|
|
350
|
+
V5_HOLDINGS = {"AAPL": 40, "MSFT": 35, "GOOG": 120, "TSLA": 70}
|
|
351
|
+
_prices_final = read_json(_prices_path)
|
|
352
|
+
_pv_cv = read_json(
|
|
353
|
+
os.path.join(OUTPUTS_DIR, 'cards', 'portfolio-value', 'computed_values.json')
|
|
354
|
+
)
|
|
355
|
+
_total_value = _pv_cv['totalValue']
|
|
356
|
+
_expected = sum(qty * _prices_final[sym] for sym, qty in V5_HOLDINGS.items())
|
|
357
|
+
assert round(_expected, 2) == round(_total_value, 2), \
|
|
358
|
+
f'T5: totals mismatch: expected={round(_expected, 2)}, got={round(_total_value, 2)}'
|
|
359
|
+
print(f'[T5] totals assertion passed: expected={round(_expected, 2)}, totalValue={round(_total_value, 2)}')
|
|
360
|
+
|
|
361
|
+
# Step 7: Print full CLI status
|
|
362
|
+
print('\nFinal board status:')
|
|
363
|
+
print(json.dumps(_cli_status, indent=2))
|
|
364
|
+
|
|
365
|
+
print('\n=== portfolio-tracker completed successfully ===')
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--base-ref\runtime-out
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"graph": {
|
|
3
|
+
"version": 1,
|
|
4
|
+
"config": {
|
|
5
|
+
"settings": {
|
|
6
|
+
"completion": "manual",
|
|
7
|
+
"refreshStrategy": "data-changed"
|
|
8
|
+
},
|
|
9
|
+
"tasks": {}
|
|
10
|
+
},
|
|
11
|
+
"state": {
|
|
12
|
+
"status": "running",
|
|
13
|
+
"tasks": {},
|
|
14
|
+
"availableOutputs": [],
|
|
15
|
+
"stuckDetection": {
|
|
16
|
+
"is_stuck": false,
|
|
17
|
+
"stuck_description": null,
|
|
18
|
+
"outputs_unresolvable": [],
|
|
19
|
+
"tasks_blocked": []
|
|
20
|
+
},
|
|
21
|
+
"lastUpdated": "2026-05-02T15:18:33.305Z",
|
|
22
|
+
"executionId": "live-1777735113305",
|
|
23
|
+
"executionConfig": {
|
|
24
|
+
"executionMode": "eligibility-mode",
|
|
25
|
+
"conflictStrategy": "alphabetical",
|
|
26
|
+
"completionStrategy": "manual"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"snapshotAt": "2026-05-02T15:18:33.305Z"
|
|
30
|
+
},
|
|
31
|
+
"lastDrainedJournalId": ""
|
|
32
|
+
}
|
|
@@ -6,6 +6,7 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
7
7
|
const repoRoot = path.resolve(__dirname, '..', '..', '..', '..', '..');
|
|
8
8
|
const boardCliPath = path.join(repoRoot, 'board-live-cards-cli.js');
|
|
9
|
+
const cardStoreCliPath = path.join(repoRoot, 'card-store.js');
|
|
9
10
|
|
|
10
11
|
export function runBoardCli(args, options = {}) {
|
|
11
12
|
const { capture = false, cwd = process.cwd() } = options;
|
|
@@ -16,7 +17,6 @@ export function runBoardCli(args, options = {}) {
|
|
|
16
17
|
stdio: capture ? 'pipe' : 'pipe',
|
|
17
18
|
env: {
|
|
18
19
|
...process.env,
|
|
19
|
-
BOARD_LIVE_CARDS_NO_SPAWN: process.env.BOARD_LIVE_CARDS_NO_SPAWN ?? '1',
|
|
20
20
|
BOARD_DIR: process.env.BOARD_DIR ?? '',
|
|
21
21
|
},
|
|
22
22
|
});
|
|
@@ -34,6 +34,58 @@ export function runBoardCli(args, options = {}) {
|
|
|
34
34
|
return capture ? (result.stdout ?? '') : '';
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/** Spawn CLI with JSON piped to stdin. */
|
|
38
|
+
export function runBoardCliWithInput(args, inputJson, options = {}) {
|
|
39
|
+
const { cwd = process.cwd() } = options;
|
|
40
|
+
const result = spawnSync(process.execPath, [boardCliPath, ...args], {
|
|
41
|
+
input: inputJson,
|
|
42
|
+
cwd,
|
|
43
|
+
encoding: 'utf-8',
|
|
44
|
+
windowsHide: true,
|
|
45
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
46
|
+
env: {
|
|
47
|
+
...process.env,
|
|
48
|
+
BOARD_DIR: process.env.BOARD_DIR ?? '',
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (result.error) {
|
|
53
|
+
throw new Error(`Failed to launch board-live-cards-cli: ${result.error.message}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if ((result.status ?? 1) !== 0) {
|
|
57
|
+
const stderr = (result.stderr ?? '').trim();
|
|
58
|
+
const stdout = (result.stdout ?? '').trim();
|
|
59
|
+
throw new Error(`board-live-cards-cli failed (${result.status}): ${stderr || stdout || 'no output'}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return result.stdout ?? '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Spawn card-store-cli with JSON piped to stdin. */
|
|
66
|
+
export function runCardStoreCliWithInput(args, inputJson, options = {}) {
|
|
67
|
+
const { cwd = process.cwd() } = options;
|
|
68
|
+
const result = spawnSync(process.execPath, [cardStoreCliPath, ...args], {
|
|
69
|
+
input: inputJson,
|
|
70
|
+
cwd,
|
|
71
|
+
encoding: 'utf-8',
|
|
72
|
+
windowsHide: true,
|
|
73
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (result.error) {
|
|
77
|
+
throw new Error(`Failed to launch card-store-cli: ${result.error.message}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if ((result.status ?? 1) !== 0) {
|
|
81
|
+
const stderr = (result.stderr ?? '').trim();
|
|
82
|
+
const stdout = (result.stdout ?? '').trim();
|
|
83
|
+
throw new Error(`card-store-cli failed (${result.status}): ${stderr || stdout || 'no output'}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result.stdout ?? '';
|
|
87
|
+
}
|
|
88
|
+
|
|
37
89
|
export async function readStdinJson() {
|
|
38
90
|
let raw = '';
|
|
39
91
|
process.stdin.setEncoding('utf-8');
|
|
@@ -1,24 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { readStdinJson, runBoardCli, writeFailure, writeResult } from './_board-cli.js';
|
|
3
|
+
import { readStdinJson, runBoardCli, runCardStoreCliWithInput, writeFailure, writeResult } from './_board-cli.js';
|
|
4
4
|
|
|
5
5
|
try {
|
|
6
6
|
const input = await readStdinJson();
|
|
7
7
|
const boardDir = String(input.BOARD_DIR ?? '').trim();
|
|
8
|
-
const
|
|
8
|
+
const cards = Array.isArray(input.CARDS) ? input.CARDS : [];
|
|
9
9
|
|
|
10
|
-
if (!boardDir ||
|
|
11
|
-
writeFailure('BOARD_DIR and
|
|
10
|
+
if (!boardDir || cards.length === 0) {
|
|
11
|
+
writeFailure('BOARD_DIR and CARDS (array) are required');
|
|
12
12
|
process.exit(0);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
const baseRef = `::fs-path::${boardDir}`;
|
|
16
|
+
|
|
17
|
+
// Write all cards to the card store in one call
|
|
18
|
+
runCardStoreCliWithInput(
|
|
19
|
+
['set', '--store-ref', baseRef],
|
|
20
|
+
JSON.stringify(cards),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// Upsert all cards at once
|
|
24
|
+
runBoardCli(['upsert-card', '--base-ref', baseRef, '--all']);
|
|
16
25
|
|
|
17
26
|
writeResult({
|
|
18
27
|
result: 'success',
|
|
19
28
|
data: {
|
|
20
29
|
board_dir: boardDir,
|
|
21
|
-
|
|
30
|
+
count: cards.length,
|
|
22
31
|
},
|
|
23
32
|
});
|
|
24
33
|
} catch (error) {
|
|
@@ -11,7 +11,12 @@ try {
|
|
|
11
11
|
process.exit(0);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
runBoardCli([
|
|
14
|
+
runBoardCli([
|
|
15
|
+
'init',
|
|
16
|
+
'--base-ref', `::fs-path::${boardDir}`,
|
|
17
|
+
'--card-store-ref', `::fs-path::${boardDir}`,
|
|
18
|
+
'--outputs-store-ref', `::fs-path::${boardDir}`,
|
|
19
|
+
]);
|
|
15
20
|
writeResult({
|
|
16
21
|
result: 'success',
|
|
17
22
|
data: {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readStdinJson, runBoardCli, writeFailure, writeResult } from './_board-cli.js';
|
|
4
|
+
|
|
5
|
+
function sleep(ms) {
|
|
6
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const input = await readStdinJson();
|
|
11
|
+
const boardDir = String(input.BOARD_DIR ?? '').trim();
|
|
12
|
+
const expectedCardCount = Number(input.EXPECTED_CARD_COUNT ?? 0);
|
|
13
|
+
const timeoutMs = Number(input.TIMEOUT_MS ?? 30000);
|
|
14
|
+
const pollMs = Number(input.POLL_MS ?? 500);
|
|
15
|
+
|
|
16
|
+
if (!boardDir || expectedCardCount <= 0) {
|
|
17
|
+
writeFailure('BOARD_DIR and EXPECTED_CARD_COUNT are required');
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const started = Date.now();
|
|
22
|
+
|
|
23
|
+
while (Date.now() - started < timeoutMs) {
|
|
24
|
+
const statusJson = runBoardCli(['status', '--base-ref', `::fs-path::${boardDir}`], { capture: true });
|
|
25
|
+
let cards = [];
|
|
26
|
+
try {
|
|
27
|
+
cards = JSON.parse(statusJson)?.data?.cards ?? [];
|
|
28
|
+
} catch { /* ignore parse errors */ }
|
|
29
|
+
|
|
30
|
+
const completedCount = cards.filter(c => c.status === 'completed').length;
|
|
31
|
+
|
|
32
|
+
if (cards.length >= expectedCardCount && completedCount >= expectedCardCount) {
|
|
33
|
+
writeResult({
|
|
34
|
+
result: 'success',
|
|
35
|
+
data: {
|
|
36
|
+
completed: true,
|
|
37
|
+
card_count: cards.length,
|
|
38
|
+
completed_count: completedCount,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await sleep(pollMs);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
writeResult({
|
|
48
|
+
result: 'timeout',
|
|
49
|
+
data: {
|
|
50
|
+
completed: false,
|
|
51
|
+
error: `timed out waiting for ${expectedCardCount} cards to complete`,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
} catch (error) {
|
|
55
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
56
|
+
writeFailure(message);
|
|
57
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as fs from 'node:fs';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
|
-
import { readStdinJson, runBoardCli, writeFailure, writeResult } from './_board-cli.js';
|
|
5
|
+
import { readStdinJson, runBoardCli, runCardStoreCliWithInput, writeFailure, writeResult } from './_board-cli.js';
|
|
6
6
|
|
|
7
7
|
try {
|
|
8
8
|
const input = await readStdinJson();
|
|
@@ -22,7 +22,12 @@ try {
|
|
|
22
22
|
card.card_data.holdings = holdings;
|
|
23
23
|
fs.writeFileSync(cardPath, `${JSON.stringify(card, null, 2)}\n`, 'utf-8');
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
const baseRef = `::fs-path::${boardDir}`;
|
|
26
|
+
runCardStoreCliWithInput(
|
|
27
|
+
['set', '--store-ref', baseRef],
|
|
28
|
+
JSON.stringify(card),
|
|
29
|
+
);
|
|
30
|
+
runBoardCli(['upsert-card', '--base-ref', baseRef, '--card-id', card.id, '--restart']);
|
|
26
31
|
|
|
27
32
|
writeResult({
|
|
28
33
|
result: 'success',
|
|
@@ -22,8 +22,12 @@ try {
|
|
|
22
22
|
const started = Date.now();
|
|
23
23
|
|
|
24
24
|
while (Date.now() - started < timeoutMs) {
|
|
25
|
-
const
|
|
26
|
-
|
|
25
|
+
const statusJson = runBoardCli(['status', '--base-ref', `::fs-path::${boardDir}`], { capture: true });
|
|
26
|
+
let cards = [];
|
|
27
|
+
try {
|
|
28
|
+
cards = JSON.parse(statusJson)?.data?.cards ?? [];
|
|
29
|
+
} catch { /* ignore parse errors */ }
|
|
30
|
+
const complete = tasks.every((task) => cards.some(c => c.name === task && c.status === 'completed'));
|
|
27
31
|
|
|
28
32
|
if (complete) {
|
|
29
33
|
writeResult({
|