@manhquy/flow-skill 0.1.0 → 0.2.1
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/CHANGELOG.md +20 -0
- package/README.md +17 -16
- package/README_VN.md +7 -4
- package/package.json +1 -1
- package/skills/flow/SKILL.md +1 -1
- package/skills/flow/harness/GAP-MATRIX-0.1.17.md +4 -3
- package/skills/flow/harness/README.md +29 -2
- package/skills/flow/harness/_db.py +175 -6
- package/skills/flow/harness/flow_harness.py +165 -9
- package/skills/flow/harness/graph_executor.py +1205 -0
- package/skills/flow/harness/graph_ids.py +35 -0
- package/skills/flow/harness/graph_predicates.py +48 -0
- package/skills/flow/harness/pins/flow-topology.sha256 +1 -0
- package/skills/flow/harness/schema/014-graph-executor.sql +67 -0
- package/skills/flow/references/auto-run.md +29 -2
- package/skills/flow/references/command-dispatch.md +1 -0
- package/skills/flow/references/concierge.md +1 -0
- package/skills/flow/references/flow-topology.json +146 -0
- package/skills/flow/runner/flow.sh +216 -5
- package/skills-manifest.json +8 -2
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Monotonic, lexicographically sortable ids for the graph executor (stdlib only).
|
|
2
|
+
|
|
3
|
+
Format: 16 hex chars of a strictly-increasing nanosecond timestamp + 6 hex chars of
|
|
4
|
+
random suffix, fixed width 22. Within one process ids are STRICTLY increasing (the
|
|
5
|
+
timestamp is bumped past the last issued value, so two calls in the same nanosecond
|
|
6
|
+
still order); across processes ordering follows wall clock at nanosecond grain and
|
|
7
|
+
the random suffix keeps ids unique — the same guarantee LangGraph's UUID6 checkpoint
|
|
8
|
+
ids provide, without a dependency. Fixed width makes string sort == numeric sort.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import threading
|
|
13
|
+
import time
|
|
14
|
+
|
|
15
|
+
_lock = threading.Lock()
|
|
16
|
+
_last_ns = 0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def after(prev):
|
|
20
|
+
"""An id strictly greater than `prev`. Used when the journal head was minted by a
|
|
21
|
+
process whose clock ran ahead of ours: ordering here is lexicographic, so a plain
|
|
22
|
+
timestamp id would silently sort BELOW the head and the write would be invisible."""
|
|
23
|
+
with _lock:
|
|
24
|
+
ts = int(prev[:16], 16) + 1
|
|
25
|
+
return "%016x%s" % (ts, os.urandom(3).hex())
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def new_id():
|
|
29
|
+
global _last_ns
|
|
30
|
+
with _lock:
|
|
31
|
+
ns = time.time_ns()
|
|
32
|
+
if ns <= _last_ns:
|
|
33
|
+
ns = _last_ns + 1
|
|
34
|
+
_last_ns = ns
|
|
35
|
+
return "%016x%s" % (ns, os.urandom(3).hex())
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Predicate registry + stage mapping for the graph executor (stdlib only).
|
|
2
|
+
|
|
3
|
+
The registry is EXACTLY {always, review_green, review_red}: predicates read only
|
|
4
|
+
durable artifacts (here, the recorded gate exit in a checkpoint manifest) - never
|
|
5
|
+
LLMs, never the network. Debt-skips are NOT predicates: they are a plan_next
|
|
6
|
+
traversal semantic keyed on each gate_check node's explicit `stage` field.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# Keep in sync with flow.sh STAGES (flow.sh:122) - the closed set every planning
|
|
10
|
+
# gate_check node's `stage` field must belong to. 05-contract is never skippable
|
|
11
|
+
# (cmd_skip's primary guard), so the skippable set is STAGES minus the last.
|
|
12
|
+
STAGES = ["00-idea", "01-research", "02-scope", "03-prd", "04-adr", "05-contract"]
|
|
13
|
+
SKIPPABLE_STAGES = STAGES[:-1]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def always(manifest):
|
|
17
|
+
return True
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def review_green(manifest):
|
|
21
|
+
gate = (manifest or {}).get("gate") or {}
|
|
22
|
+
return gate.get("exit") == 0
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def review_red(manifest):
|
|
26
|
+
gate = (manifest or {}).get("gate") or {}
|
|
27
|
+
return gate.get("exit") not in (None, 0)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
REGISTRY = {"always": always, "review_green": review_green, "review_red": review_red}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def evaluate(name, manifest):
|
|
34
|
+
fn = REGISTRY.get(name)
|
|
35
|
+
if fn is None:
|
|
36
|
+
raise ValueError(f"unregistered predicate: {name}")
|
|
37
|
+
return fn(manifest)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def stage_map(topo):
|
|
41
|
+
"""node-id -> stage-name for every gate_check node carrying a `stage` field.
|
|
42
|
+
(Card-subgraph gate_check nodes MUST NOT declare `stage` - a stage on
|
|
43
|
+
card-review would make the review gate debt-skippable; lint enforces this.)"""
|
|
44
|
+
out = {}
|
|
45
|
+
for name, spec in topo.get("nodes", {}).items():
|
|
46
|
+
if spec.get("type") == "gate_check" and spec.get("stage"):
|
|
47
|
+
out[name] = spec["stage"]
|
|
48
|
+
return out
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
493d4869516f2f7777b969580af145dece90be88b7c5a435edcce81f6446bad9 flow-topology.json
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
-- flow-owned schema - migration 014 (graph-executor band 014+)
|
|
2
|
+
-- Graph executor foundation: execution journal, checkpoints (evidence manifests, never
|
|
3
|
+
-- blobs), step writes, and operator interrupts. 013 stays reserved/absent (upstream
|
|
4
|
+
-- changeset content sha); 014+ is flow-owned per the 2026-07-26 supersession of the
|
|
5
|
+
-- work-graph red line (see GAP-MATRIX-0.1.17.md).
|
|
6
|
+
|
|
7
|
+
CREATE TABLE graph_execution (
|
|
8
|
+
id TEXT PRIMARY KEY,
|
|
9
|
+
project TEXT NOT NULL,
|
|
10
|
+
kind TEXT NOT NULL CHECK(kind IN ('auto_run','planning','card')),
|
|
11
|
+
topology_version INTEGER NOT NULL,
|
|
12
|
+
topology_hash TEXT NOT NULL,
|
|
13
|
+
status TEXT NOT NULL DEFAULT 'running'
|
|
14
|
+
CHECK(status IN ('running','paused','done','failed','abandoned')),
|
|
15
|
+
outcome TEXT,
|
|
16
|
+
story_id TEXT REFERENCES story(id),
|
|
17
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
18
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
CREATE INDEX idx_graph_execution_status ON graph_execution(status);
|
|
22
|
+
|
|
23
|
+
CREATE TABLE graph_checkpoint (
|
|
24
|
+
execution_id TEXT NOT NULL REFERENCES graph_execution(id) ON DELETE CASCADE,
|
|
25
|
+
ns TEXT NOT NULL DEFAULT '',
|
|
26
|
+
checkpoint_id TEXT NOT NULL,
|
|
27
|
+
parent_checkpoint_id TEXT,
|
|
28
|
+
node TEXT NOT NULL,
|
|
29
|
+
manifest TEXT NOT NULL,
|
|
30
|
+
versions TEXT NOT NULL,
|
|
31
|
+
seen TEXT NOT NULL,
|
|
32
|
+
meta TEXT NOT NULL,
|
|
33
|
+
PRIMARY KEY (execution_id, ns, checkpoint_id)
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
CREATE INDEX idx_graph_checkpoint_parent ON graph_checkpoint(parent_checkpoint_id);
|
|
37
|
+
|
|
38
|
+
CREATE TABLE graph_step_write (
|
|
39
|
+
execution_id TEXT NOT NULL REFERENCES graph_execution(id) ON DELETE CASCADE,
|
|
40
|
+
ns TEXT NOT NULL DEFAULT '',
|
|
41
|
+
checkpoint_id TEXT NOT NULL,
|
|
42
|
+
task_id TEXT NOT NULL,
|
|
43
|
+
idx INTEGER NOT NULL,
|
|
44
|
+
channel TEXT NOT NULL,
|
|
45
|
+
value TEXT NOT NULL,
|
|
46
|
+
PRIMARY KEY (execution_id, ns, checkpoint_id, task_id, idx)
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
CREATE TABLE graph_interrupt (
|
|
50
|
+
execution_id TEXT NOT NULL REFERENCES graph_execution(id) ON DELETE CASCADE,
|
|
51
|
+
ns TEXT NOT NULL DEFAULT '',
|
|
52
|
+
interrupt_id TEXT NOT NULL,
|
|
53
|
+
node TEXT NOT NULL,
|
|
54
|
+
prompt TEXT NOT NULL,
|
|
55
|
+
security_class INTEGER NOT NULL DEFAULT 0,
|
|
56
|
+
resume_value TEXT,
|
|
57
|
+
resolved_by TEXT,
|
|
58
|
+
status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open','resolved','abandoned')),
|
|
59
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
60
|
+
resolved_at TEXT,
|
|
61
|
+
PRIMARY KEY (execution_id, ns, interrupt_id)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
CREATE UNIQUE INDEX idx_graph_interrupt_one_open
|
|
65
|
+
ON graph_interrupt(execution_id, ns, node) WHERE status = 'open';
|
|
66
|
+
|
|
67
|
+
INSERT INTO schema_version (version) VALUES (14);
|
|
@@ -17,9 +17,14 @@ principles below. Operator chose: **Tier-A auto-merge green cards; halt at secur
|
|
|
17
17
|
```
|
|
18
18
|
for each todo card in card-number order:
|
|
19
19
|
0. tier-classify. If Tier-C -> HALT, write DEBT line, ask operator. Do not proceed.
|
|
20
|
+
[records: flow harness graph record --execution "$(flow.sh harness graph session)" \
|
|
21
|
+
--ns card:C-NNN --node card-review --interrupt --security-class]
|
|
20
22
|
1. spawn ONE scoped subagent (agent-stage-mapping.md) in its own worktree
|
|
21
|
-
|
|
23
|
+
/flow workspace add card/C-NNN --card C-NNN [records: card-dispatch]
|
|
24
|
+
(a raw `git worktree add` records NOTHING — use the verb, or the journal has a hole)
|
|
22
25
|
2. agent builds to contract, touches only allowed files, runs ## Verify for real
|
|
26
|
+
[no recording: the build is LLM work, and checkpoints only sit at deterministic
|
|
27
|
+
gate/step boundaries — the next `check` is what proves it]
|
|
23
28
|
3. review the diff (code-reviewer or bmad-code-review 3-layer; see adversarial-review.md).
|
|
24
29
|
On a security-class card, add a USABLE cross-vendor lens (Codex, and/or Antigravity/Gemini-3).
|
|
25
30
|
red (strike 1) -> repair: spawn Task(subagent_type="debugger") with a SCOPED BRIEF
|
|
@@ -32,12 +37,34 @@ for each todo card in card-number order:
|
|
|
32
37
|
USABLE, else escalate
|
|
33
38
|
green -> continue
|
|
34
39
|
4. flow.sh check C-NNN must PASS (mechanical) + gate-rules semantic check
|
|
40
|
+
[records: card-review, with the gate exit as the evidence]
|
|
35
41
|
5. merge to main in card order; deploy; VERIFY ON LIVE URL (merge != shipped)
|
|
36
42
|
6. paste world-state evidence into the card; status: done
|
|
43
|
+
[records: card-verify-live, via '/flow card done C-NNN' (evidence gate = the done boundary)]
|
|
37
44
|
7. flow.sh harness story complete --id … --proof-source manual (or card_markdown_gate) + trace; log AUTO-LOG.md
|
|
38
|
-
8. remove worktree
|
|
45
|
+
8. remove worktree: /flow workspace remove card/C-NNN
|
|
46
|
+
[records: card-merge — the executor computes the ancestry proof itself and records
|
|
47
|
+
card-abandon instead if the branch was never merged — and ingests the worktree's
|
|
48
|
+
usage events into the main DB before the tree is gone]
|
|
39
49
|
```
|
|
40
50
|
|
|
51
|
+
## Recording contract (graph executor, `FLOW_GRAPH_EXECUTOR=1`)
|
|
52
|
+
Boundaries are recorded by the verbs above, which this loop already calls — nothing depends
|
|
53
|
+
on the agent remembering an extra call. Only the Tier-C interrupt (step 0) is an explicit
|
|
54
|
+
`graph record` invocation; `graph session` returns the project's running execution
|
|
55
|
+
(minting one atomically if absent — one execution per project, shared by every worktree).
|
|
56
|
+
The card path is dispatch (`workspace add`) → review (`check`) → merge (`workspace remove`),
|
|
57
|
+
with an optional verify-live between review and merge when you close the card via
|
|
58
|
+
`card done` rather than hand-editing `status:`; an unmerged teardown records card-abandon,
|
|
59
|
+
and a RED review loops back to review (bounded by max_visits — two strikes, then escalate).
|
|
60
|
+
Every one of those transitions is a declared topology edge, and
|
|
61
|
+
`tests/test_flow_graph_parallel_cards.sh` drives a real lifecycle and fails if the runner
|
|
62
|
+
ever emits a transition the topology does not declare. The executor never drives the loop: it records evidence, computes
|
|
63
|
+
the next node from topology + journal, and reconciles against git (`graph resume` with no
|
|
64
|
+
open interrupt prints a reconciliation report), so merged-but-unrecorded work is never
|
|
65
|
+
re-dispatched. Ready/parallel decisions come from `flow harness graph cards`
|
|
66
|
+
(deps-met parity with `/flow ready`, plus allowed-files overlap serialization).
|
|
67
|
+
|
|
41
68
|
## Hard stops (mandatory — see loop-harness-2026-principles.md in Phase 4)
|
|
42
69
|
- Iteration cap per card (e.g. 2 repair attempts), token budget, wall-clock cap. Exceed any
|
|
43
70
|
-> HALT + report. A loop with no cap is an antipattern.
|
|
@@ -14,6 +14,7 @@ install dir; run from the project root so `flow/` and `cards/` resolve.
|
|
|
14
14
|
| `/flow card start C-NNN` | `bash <skill>/runner/flow.sh card start C-NNN` | Optional: mark the card in flight (operator-visible in-progress, shown in `/flow status`). Portable `cards/.inflight` registry; does NOT touch the gated `status:` field. Coexists with hand-edit. |
|
|
15
15
|
| `/flow card done C-NNN` | `bash <skill>/runner/flow.sh card done C-NNN` | Optional convenience: CLI-owned flip to `done`, gated by the SAME done-rules as `check` (reverts on fail — never a hollow done). Then still apply the semantic card review (diff-vs-scope, contract shapes, real evidence) as with `check`. |
|
|
16
16
|
| `/flow check C-NNN` | `bash <skill>/runner/flow.sh check C-NNN` | If PASS mechanically: review diff-vs-scope, allowed-files drift, contract-shape match, DESIGN.md for UI, evidence = real world-state. |
|
|
17
|
+
| `/flow gate <stage>` (or `--card C-NNN`) | `bash <skill>/runner/flow.sh gate <stage\|--card C-NNN>` | Read-only mechanical scan (unchecked boxes + `[FILL]`) with NO unlock, NO template copy, NO durable writes — the only flow.sh verb the graph executor runs from topology `cmd` position. Relay findings; `next`/`check` still own real progression + durable hooks. |
|
|
17
18
|
| `/flow contract` | `bash <skill>/runner/flow.sh contract` | After the contract gate / before UI cards (web): flags client base-URL vs served-path prefix drift (double-`/api`, mixed-prefix) — the class spec-diff tools miss. Advisory; confirm on the running app. |
|
|
18
19
|
| `/flow tokens` | `bash <skill>/runner/flow.sh tokens` | On/after UI cards: flags DESIGN.md tokens the CSS never uses + VALUE mismatches (same name, drifted value) + orphan CSS vars (info). Advisory; if the swap is intentional, record a dated DESIGN.md amendment. |
|
|
19
20
|
| `/flow coherence` | `bash <skill>/runner/flow.sh coherence` | Flags version drift across declared version fields (package.json / pyproject / src app_version). The cheap doc-vs-code slice; semantic contradictions stay a human gate-challenge. |
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "flow topology (source of truth once FLOW_GRAPH_EXECUTOR defaults on). Trusted data: loads ONLY from the skill install dir, pin-verified against harness/pins/flow-topology.sha256 (canonical-JSON sha256; regenerate via the command in harness/README.md). cmd is an argv ARRAY (no shell) restricted to the read-only `gate` verb; mutating verbs are lint-banned. Debt-skips are a plan_next traversal semantic (skipped gate = accepted-with-debt) - NO bypass edges belong here. EVERY card edge below is a transition the runner actually emits: dispatch (workspace add) -> review (check) -> merge (workspace remove), with an optional verify-live between review and merge when the operator uses `card done` instead of hand-editing status (workspace remove, executor-proved ancestry). A RED review loops back to review - the agent repairs and re-runs check, which records another review - bounded by max_visits (two strikes, then escalate per auto-run.md). card-abandon is the unmerged-teardown terminal and can follow any card node, because `workspace remove` may happen at any point. There is deliberately no card-build node: the build is LLM work with no deterministic boundary. Edit checklist: run `graph lint`, regenerate the pin, bump topology_version for semantic changes.",
|
|
3
|
+
"topology_version": 1,
|
|
4
|
+
"entry": [
|
|
5
|
+
"stage-00",
|
|
6
|
+
"card-dispatch"
|
|
7
|
+
],
|
|
8
|
+
"nodes": {
|
|
9
|
+
"stage-00": {
|
|
10
|
+
"type": "gate_check",
|
|
11
|
+
"stage": "00-idea",
|
|
12
|
+
"cmd": [
|
|
13
|
+
"flow.sh",
|
|
14
|
+
"gate",
|
|
15
|
+
"00-idea"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"stage-01": {
|
|
19
|
+
"type": "gate_check",
|
|
20
|
+
"stage": "01-research",
|
|
21
|
+
"cmd": [
|
|
22
|
+
"flow.sh",
|
|
23
|
+
"gate",
|
|
24
|
+
"01-research"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"stage-02": {
|
|
28
|
+
"type": "gate_check",
|
|
29
|
+
"stage": "02-scope",
|
|
30
|
+
"cmd": [
|
|
31
|
+
"flow.sh",
|
|
32
|
+
"gate",
|
|
33
|
+
"02-scope"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"stage-03": {
|
|
37
|
+
"type": "gate_check",
|
|
38
|
+
"stage": "03-prd",
|
|
39
|
+
"cmd": [
|
|
40
|
+
"flow.sh",
|
|
41
|
+
"gate",
|
|
42
|
+
"03-prd"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"stage-04": {
|
|
46
|
+
"type": "gate_check",
|
|
47
|
+
"stage": "04-adr",
|
|
48
|
+
"cmd": [
|
|
49
|
+
"flow.sh",
|
|
50
|
+
"gate",
|
|
51
|
+
"04-adr"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
"stage-05": {
|
|
55
|
+
"type": "gate_check",
|
|
56
|
+
"stage": "05-contract",
|
|
57
|
+
"cmd": [
|
|
58
|
+
"flow.sh",
|
|
59
|
+
"gate",
|
|
60
|
+
"05-contract"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
"card-dispatch": {
|
|
64
|
+
"type": "record_evidence"
|
|
65
|
+
},
|
|
66
|
+
"card-review": {
|
|
67
|
+
"type": "gate_check",
|
|
68
|
+
"cmd": [
|
|
69
|
+
"flow.sh",
|
|
70
|
+
"gate",
|
|
71
|
+
"--card",
|
|
72
|
+
"{card}"
|
|
73
|
+
],
|
|
74
|
+
"max_visits": 3
|
|
75
|
+
},
|
|
76
|
+
"card-verify-live": {
|
|
77
|
+
"type": "record_evidence"
|
|
78
|
+
},
|
|
79
|
+
"card-merge": {
|
|
80
|
+
"type": "git_op"
|
|
81
|
+
},
|
|
82
|
+
"card-abandon": {
|
|
83
|
+
"type": "record_evidence"
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"edges": [
|
|
87
|
+
{
|
|
88
|
+
"from": "stage-00",
|
|
89
|
+
"to": "stage-01"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"from": "stage-01",
|
|
93
|
+
"to": "stage-02"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"from": "stage-02",
|
|
97
|
+
"to": "stage-03"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"from": "stage-03",
|
|
101
|
+
"to": "stage-04"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"from": "stage-04",
|
|
105
|
+
"to": "stage-05"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"from": "card-dispatch",
|
|
109
|
+
"to": "card-review"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"from": "card-review",
|
|
113
|
+
"to": "card-verify-live",
|
|
114
|
+
"when": "review_green"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"from": "card-verify-live",
|
|
118
|
+
"to": "card-merge"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"from": "card-review",
|
|
122
|
+
"to": "card-review",
|
|
123
|
+
"when": "review_red"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"from": "card-dispatch",
|
|
127
|
+
"to": "card-abandon",
|
|
128
|
+
"when": "review_red"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"from": "card-review",
|
|
132
|
+
"to": "card-abandon",
|
|
133
|
+
"when": "review_red"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"from": "card-verify-live",
|
|
137
|
+
"to": "card-abandon",
|
|
138
|
+
"when": "review_red"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"from": "card-review",
|
|
142
|
+
"to": "card-merge",
|
|
143
|
+
"when": "review_green"
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|