@zalom/plastic 1.1.3 → 1.1.4
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/scripts/dashboard.rb +20 -2
- package/skills/dashboard/SKILL.md +26 -12
- package/skills/dashboard/evals/evals.json +16 -0
- package/skills/dashboard/templates/dashboard-global.md +8 -2
- package/skills/dashboard/templates/dashboard-project.md +15 -5
- package/skills/project-continuing/SKILL.md +5 -0
- package/skills/project-continuing/evals/evals.json +2 -2
- package/skills/project-continuing/references/board-fill.md +13 -5
package/package.json
CHANGED
package/scripts/dashboard.rb
CHANGED
|
@@ -551,6 +551,19 @@ def within_24h?(rec)
|
|
|
551
551
|
d && d >= (today - 1)
|
|
552
552
|
end
|
|
553
553
|
|
|
554
|
+
# Collapse whitespace to single spaces, strip, then escape Markdown table pipes so
|
|
555
|
+
# free-text payload data is safe to drop verbatim into a table cell. Block-form gsub
|
|
556
|
+
# avoids replacement-string backslash pitfalls.
|
|
557
|
+
def cell(s)
|
|
558
|
+
s.to_s.gsub(/\s+/, " ").strip.gsub("|") { "\\|" }
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
# Truncate an intent title to the shared line budget, ellipsis when over.
|
|
562
|
+
def truncate_intent(text)
|
|
563
|
+
t = text.to_s
|
|
564
|
+
t.length > INTENT_LINE_MAX_CHARS ? "#{t[0, INTENT_LINE_MAX_CHARS]}…" : t
|
|
565
|
+
end
|
|
566
|
+
|
|
554
567
|
def worked_row(rec, project_scope)
|
|
555
568
|
glyph = STATUS_GLYPH[rec[:status]]
|
|
556
569
|
proj = rec[:scope] == "global" ? "global" : rec[:scope].sub("project:", "")
|
|
@@ -559,6 +572,7 @@ def worked_row(rec, project_scope)
|
|
|
559
572
|
{
|
|
560
573
|
id: rec[:id], status: rec[:status], glyph: glyph,
|
|
561
574
|
last_accessed_at: rec[:last_accessed_at],
|
|
575
|
+
what: cell(truncate_intent(rec[:intent])), state: status_word, scope: cell(proj),
|
|
562
576
|
line: "#{glyph} #{prefix}#{status_word}: #{rec[:id]} #{rec[:intent]}".strip,
|
|
563
577
|
}
|
|
564
578
|
end
|
|
@@ -578,7 +592,8 @@ def intent_line(rec, bullet)
|
|
|
578
592
|
text = rec[:intent].to_s
|
|
579
593
|
text = "#{text[0, INTENT_LINE_MAX_CHARS]}…" if text.length > INTENT_LINE_MAX_CHARS
|
|
580
594
|
{ id: rec[:id], intent: rec[:intent], created: rec[:created], bullet: bullet,
|
|
581
|
-
scope: rec[:scope],
|
|
595
|
+
scope: rec[:scope], what: cell(text), stage: rec[:lifecycle].to_s.capitalize,
|
|
596
|
+
line: "#{bullet} #{rec[:id]} #{text}#{note}".rstrip }
|
|
582
597
|
end
|
|
583
598
|
|
|
584
599
|
# Cap a raw record list to NEXT_WORK_CAP entries, then map to intent_line-shaped
|
|
@@ -589,6 +604,7 @@ def cap_lines(list, bullet)
|
|
|
589
604
|
lines = capped.map { |r| intent_line(r, bullet) }
|
|
590
605
|
if list.size > NEXT_WORK_CAP
|
|
591
606
|
lines << { id: "", intent: "", created: "", bullet: bullet, scope: "",
|
|
607
|
+
what: "+#{list.size - NEXT_WORK_CAP} more", stage: "",
|
|
592
608
|
line: "#{bullet} +#{list.size - NEXT_WORK_CAP} more" }
|
|
593
609
|
end
|
|
594
610
|
lines
|
|
@@ -607,11 +623,13 @@ def next_work(records)
|
|
|
607
623
|
text = "#{text[0, INTENT_LINE_MAX_CHARS]}…" if text.length > INTENT_LINE_MAX_CHARS
|
|
608
624
|
{ id: r[:id], intent: r[:intent], scope: r[:scope], lifecycle: r[:lifecycle],
|
|
609
625
|
value: r[:value].to_s, disposition: r[:disposition], flags: r[:flags],
|
|
626
|
+
what: cell(text), flags_label: cell(Array(r[:flags]).join(", ")),
|
|
610
627
|
line: "#{r[:id]} #{text}" }
|
|
611
628
|
end
|
|
612
629
|
if ranked.size > NEXT_WORK_CAP
|
|
613
630
|
lines << { id: "", intent: "", scope: "", lifecycle: "", value: "", disposition: "",
|
|
614
|
-
flags: [],
|
|
631
|
+
flags: [], what: "+#{ranked.size - NEXT_WORK_CAP} more", flags_label: "",
|
|
632
|
+
line: "+#{ranked.size - NEXT_WORK_CAP} more" }
|
|
615
633
|
end
|
|
616
634
|
lines
|
|
617
635
|
end
|
|
@@ -34,9 +34,13 @@ ruby ~/.plastic/scripts/dashboard.rb [continue|project <slug>] --data
|
|
|
34
34
|
- `project <slug>` → that **project** board payload (`mode: "project"`).
|
|
35
35
|
|
|
36
36
|
The payload is read-only JSON. Global-board fields: `date`, `store_health`, `recently_worked`,
|
|
37
|
-
`next_work`
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
`next_work`, `counts`, `projects`, `project_totals`. Project-board fields: `slug`, `store_health`,
|
|
38
|
+
`description`, `recently_worked`, `next_work`, `counts`, `active`, `future`. Each list carries
|
|
39
|
+
cell-ready fields for its table: `next_work` rows are
|
|
40
|
+
`{id, intent, scope, lifecycle, value, disposition, flags, what, flags_label, line}`;
|
|
41
|
+
`recently_worked` rows carry `{id, status, glyph, last_accessed_at, what, state, scope, line}`;
|
|
42
|
+
`active`/`future` rows carry `{id, intent, created, bullet, scope, what, stage, line}`. The `what`,
|
|
43
|
+
`scope`, and `flags_label` cell fields arrive pipe-escaped and whitespace-normalized.
|
|
40
44
|
|
|
41
45
|
Each board load runs the scoped store check (`doctor --store <scope>`): the global board runs
|
|
42
46
|
`--store global` and a project board runs `--store <slug>`. The result rides in the payload as
|
|
@@ -53,13 +57,22 @@ Templates live in this skill's `templates/` directory:
|
|
|
53
57
|
|
|
54
58
|
Fill mechanically, no rewriting, no re-sorting:
|
|
55
59
|
- `{{a.b.count}}` → the integer (e.g. `counts.active` = that count).
|
|
56
|
-
- `{{
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
- `
|
|
60
|
+
- `{{<list>.rows}}` → the four intent lists (`recently_worked`, `next_work`, `active`, `future`)
|
|
61
|
+
render as **Markdown table rows**. The template hard-codes each table's header and separator;
|
|
62
|
+
this placeholder becomes one data row per list entry, joined with real newlines, in that table's
|
|
63
|
+
fixed column order (below). Drop each cell from the named payload field **verbatim**: cells
|
|
64
|
+
arrive pre-escaped and whitespace-normalized from the script (pipes escaped as `\|`), so never
|
|
65
|
+
re-escape, re-truncate, or reword them. Never emit `<br>`.
|
|
66
|
+
- `recently_worked` (global) → `| {id} | {what} | {state} | {scope} |`
|
|
67
|
+
- `recently_worked` (project) → `| {id} | {what} | {state} |`
|
|
68
|
+
- `next_work` → `| {id} | {what} | {value} | {disposition} | {flags_label} |`
|
|
69
|
+
- `active` → `| {id} | {what} | {stage} |`
|
|
70
|
+
- `future` → `| {id} | {what} |`
|
|
71
|
+
Overflow entry (empty `id`, `what` = `+N more`) → one row with `+N more` in the Id column and
|
|
72
|
+
every other cell blank. Empty list → one full-width row with `_(none)_` in the Id column and
|
|
73
|
+
every other cell blank, matching that table's column count (e.g. `| _(none)_ | | | | |` for
|
|
74
|
+
the 5-column next_work table, `| _(none)_ | |` for the 2-column future table).
|
|
75
|
+
- `{{projects.lines}}` → the project rollup stays **prose**, one line per project (not a table):
|
|
63
76
|
`- **{slug}**: {description}, active {active}, done {done}, future {future}, last accessed {last_accessed_at[0,10]}`.
|
|
64
77
|
- Scalars (`{{date}}`, `{{slug}}`, `{{description}}`) → substitute verbatim.
|
|
65
78
|
|
|
@@ -126,7 +139,8 @@ intentional change means the skill is broken.
|
|
|
126
139
|
|
|
127
140
|
## Notes
|
|
128
141
|
|
|
129
|
-
-
|
|
130
|
-
|
|
142
|
+
- The four intent lists (recently worked, active, future, next work) render as Markdown tables;
|
|
143
|
+
the narrative wrappers, counts, and the project rollup stay prose. No Value x Effort grid
|
|
144
|
+
returns. Never emit `<br>`.
|
|
131
145
|
- Clusters (Zettelkasten grouping in INDEX.md) are intentionally not rendered.
|
|
132
146
|
- Additive: changes no core lifecycle, gate, or cycle logic.
|
|
@@ -17,6 +17,22 @@
|
|
|
17
17
|
"result": "pass"
|
|
18
18
|
}
|
|
19
19
|
]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": 2,
|
|
23
|
+
"scope": "behavior",
|
|
24
|
+
"set": "validation",
|
|
25
|
+
"prompt": "How do the four intent lists render on the Markdown boards?",
|
|
26
|
+
"expected_output": "recently_worked, next_work, active, and future render as Markdown tables (fixed columns: next_work Id|What|Value|Disposition|Flags; active Id|What|Stage; future Id|What; recently_worked Id|What|State, plus a Scope column on the global board). The narrative wrappers, counts, and the projects rollup stay prose; no Value x Effort grid. Cells arrive pre-escaped from the payload and are dropped verbatim.",
|
|
27
|
+
"files": ["skills/dashboard/templates/dashboard-global.md", "skills/dashboard/templates/dashboard-project.md", "skills/dashboard/SKILL.md"],
|
|
28
|
+
"assertions": [
|
|
29
|
+
{
|
|
30
|
+
"type": "convention",
|
|
31
|
+
"check": "the four intent lists render as Markdown tables with the fixed columns; wrappers and the projects rollup stay prose; no grid",
|
|
32
|
+
"observed": "templates hard-code table headers plus {{list.rows}} for recently_worked/next_work/active/future; SKILL.md Step 2 documents the fixed column order and verbatim pre-escaped cell fill; projects stays {{projects.lines}} prose",
|
|
33
|
+
"result": "pass"
|
|
34
|
+
}
|
|
35
|
+
]
|
|
20
36
|
}
|
|
21
37
|
]
|
|
22
38
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# 🧩 Plastic · Global Board, {{date}}
|
|
2
2
|
|
|
3
3
|
**Recently worked** (last 24h)
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
| Id | What | State | Scope |
|
|
6
|
+
| --- | --- | --- | --- |
|
|
7
|
+
{{recently_worked.rows}}
|
|
5
8
|
|
|
6
9
|
## Where we are
|
|
7
10
|
|
|
@@ -11,6 +14,9 @@
|
|
|
11
14
|
{{projects.lines}}
|
|
12
15
|
|
|
13
16
|
## Most-valuable next work
|
|
14
|
-
|
|
17
|
+
|
|
18
|
+
| Id | What | Value | Disposition | Flags |
|
|
19
|
+
| --- | --- | --- | --- | --- |
|
|
20
|
+
{{next_work.rows}}
|
|
15
21
|
|
|
16
22
|
**What would you like to work on next?** (type an **intent id**, a **project name**, or anything **new** you'd like to start)
|
|
@@ -3,19 +3,29 @@
|
|
|
3
3
|
{{description}}
|
|
4
4
|
|
|
5
5
|
**Recently worked** (last active work, last 24h)
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
| Id | What | State |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
{{recently_worked.rows}}
|
|
7
10
|
|
|
8
11
|
## Intents, active {{counts.active}}, done {{counts.done}}, future {{counts.future}}
|
|
9
12
|
|
|
10
13
|
**Active**
|
|
11
|
-
|
|
14
|
+
|
|
15
|
+
| Id | What | Stage |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
{{active.rows}}
|
|
12
18
|
|
|
13
19
|
**Future**
|
|
14
|
-
|
|
20
|
+
|
|
21
|
+
| Id | What |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
{{future.rows}}
|
|
15
24
|
|
|
16
25
|
## Most-valuable next work
|
|
17
|
-
{{next_work.lines}}
|
|
18
26
|
|
|
19
|
-
|
|
27
|
+
| Id | What | Value | Disposition | Flags |
|
|
28
|
+
| --- | --- | --- | --- | --- |
|
|
29
|
+
{{next_work.rows}}
|
|
20
30
|
|
|
21
31
|
**What would you like to work on next?** (type an **intent id**, or **global** to go back)
|
|
@@ -91,6 +91,11 @@ was built against the live INDEX.md-parsing `--data` path (147, the DB cutover,
|
|
|
91
91
|
landed). Its rule-name citations (`classification.md`, cited by name, not logic) and the
|
|
92
92
|
`dashboard.rb project <slug> --data` -> `dashboard-project.md` path still resolve.
|
|
93
93
|
|
|
94
|
+
Intent 149a has landed on top of 149: the four intent lists (recently worked, active, future,
|
|
95
|
+
next work) now render as Markdown tables. The prose demotion and the no-grid stance are unchanged,
|
|
96
|
+
and the rule-name citations and the `dashboard.rb project <slug> --data` -> `dashboard-project.md`
|
|
97
|
+
path still resolve.
|
|
98
|
+
|
|
94
99
|
Intent 148 landed: roadmaps are the primary planning surface. When the tier has a mid-flight
|
|
95
100
|
roadmap (`ruby ~/.plastic/scripts/roadmap-next --roadmaps-dir <tier>/roadmaps` reports a `state`
|
|
96
101
|
other than `none`), the roadmap route (`plastic-roadmap-continuing`) is the live surface for
|
|
@@ -72,10 +72,10 @@
|
|
|
72
72
|
{
|
|
73
73
|
"id": 8, "scope": "behavior", "set": "validation",
|
|
74
74
|
"prompt": "Does the skill carry the intent-149 coordination note?",
|
|
75
|
-
"expected_output": "A 'Coordination' section records that intent 149 has landed (dashboard demoted to prose, no Value x Effort grid) and confirms the rule-name citations and the dashboard.rb project <slug> --data -> dashboard-project.md path still resolve.",
|
|
75
|
+
"expected_output": "A 'Coordination' section records that intent 149 has landed (dashboard demoted to prose, no Value x Effort grid) and intent 149a has landed (the four intent lists render as Markdown tables), and confirms the rule-name citations and the dashboard.rb project <slug> --data -> dashboard-project.md path still resolve.",
|
|
76
76
|
"files": ["skills/project-continuing/SKILL.md"],
|
|
77
77
|
"assertions": [
|
|
78
|
-
{ "type": "convention", "check": "'149' present with the landed coordination note", "observed": "present: 'Intent 149 has landed' with confirmed rule-name citations and dashboard.rb path", "result": "pass" }
|
|
78
|
+
{ "type": "convention", "check": "'149' present with the landed coordination note", "observed": "present: 'Intent 149 has landed' and 'Intent 149a has landed' (lists render as Markdown tables) with confirmed rule-name citations and dashboard.rb path", "result": "pass" }
|
|
79
79
|
]
|
|
80
80
|
},
|
|
81
81
|
{
|
|
@@ -7,11 +7,19 @@ would otherwise bloat the SKILL.md body.
|
|
|
7
7
|
## Fill rules (owned by plastic-dashboard, summarized here for convenience)
|
|
8
8
|
|
|
9
9
|
- `{{a.b.count}}` -> the integer (e.g. `counts.active` is that count).
|
|
10
|
-
- `{{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
-
|
|
10
|
+
- `{{<list>.rows}}` -> the four intent lists (`recently_worked`, `next_work`, `active`, `future`)
|
|
11
|
+
render as Markdown table rows. The template hard-codes each table's header and separator; the
|
|
12
|
+
placeholder becomes one data row per entry, in that table's fixed column order, cells dropped
|
|
13
|
+
verbatim from the payload (cells arrive pipe-escaped and whitespace-normalized; do not re-escape
|
|
14
|
+
or re-truncate). Column order per table:
|
|
15
|
+
- `recently_worked` -> `| id | what | state | scope |` (global), `| id | what | state |` (project)
|
|
16
|
+
- `next_work` -> `| id | what | value | disposition | flags_label |`
|
|
17
|
+
- `active` -> `| id | what | stage |`; `future` -> `| id | what |`
|
|
18
|
+
Overflow entry (empty `id`, `what` = `+N more`) -> `+N more` in the Id column, other cells blank.
|
|
19
|
+
Empty list -> one full-width row with `_(none)_` in the Id column, other cells blank, matching
|
|
20
|
+
that table's column count (e.g. `| _(none)_ | | | | |` for the 5-column next_work table,
|
|
21
|
+
`| _(none)_ | |` for the 2-column future table). Never emit `<br>`.
|
|
22
|
+
- `{{projects.lines}}` (global board) -> the project rollup stays prose, one line per project.
|
|
15
23
|
- Scalars (`{{date}}`, `{{slug}}`, `{{description}}`) -> substitute verbatim.
|
|
16
24
|
|
|
17
25
|
No re-sorting, no re-summarizing, no hand-written prose replacing a line the payload already
|