shapeup-sdlc 3.0.2 → 3.1.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shapeup-sdlc-plugin",
3
3
  "displayName": "ShapeUp SDLC Plugin",
4
- "version": "3.0.2",
4
+ "version": "3.1.0",
5
5
  "description": "Shape Up SDLC harness for Claude Code: shaping, intake, orient, scope-mapping, building (T0-verified, sandboxed, scope-contracted), evaluation and QA skills orchestrated by a tech-lead.",
6
6
  "author": {
7
7
  "name": "Liberty Nguyen",
package/AGENTS.md CHANGED
@@ -74,6 +74,7 @@ Everything discovered funnels into `.shapeup/<slug>/discovery/ledger.md` (Orient
74
74
  on first touch. `--subgraph run` is the fast-forward as one bounded query; `--trace <node>` walks
75
75
  a verdict back to the objective, the plan, the source, the execution record and the gate that
76
76
  crossed it.
77
+ - `/hill-chart` (skill `hill-chart`, not a pipeline worker — invoked directly, like `shapeup`) renders both the committed hill shards (`shapeup/<slug>/hill/<scope-id>.yml`, the mechanical phase from the invariant above) and the local run graph as one dashboard: a portfolio card per pitch, and per-pitch a Hill Chart, an attention list, a scope board, round history, and the run graph one click deeper. A pitch whose local run trace was cleaned up after shipping still renders — marked Archived — from its committed hill shards alone.
77
78
  - Contracts: markdown on disk, JSON on the wire; a single library reads/writes the file form.
78
79
  - Never hard-code a storage root — generated paths resolve through the shared path resolver.
79
80
  - The traceability oracle emits `.shapeup/<slug>/trace/report.json` from the spine artifacts.
package/README.md CHANGED
@@ -107,7 +107,7 @@ installer, and troubleshooting are in
107
107
  ## Agent support
108
108
 
109
109
  The harness targets **Claude Code only**. The reason is the row that never travelled when we
110
- compiled to other CLIs: hooks. The 12 skills, 10 slash commands and the kernel are
110
+ compiled to other CLIs: hooks. The 13 skills, 11 slash commands and the kernel are
111
111
  portable prose and plain Node — but hook-enforced gates (envelope validation, substrate
112
112
  sandbox, safety spine, the zero-work block) are a per-CLI mechanism, and without them every gate degrades from
113
113
  **enforced** to **instructed** — the same honor system every other framework runs on
@@ -326,7 +326,7 @@ claude --plugin-dir . # load this working copy without installing
326
326
  .claude-plugin/
327
327
  plugin.json # plugin manifest
328
328
  marketplace.json # marketplace listing (points at this repo)
329
- skills/<name>/SKILL.md # the 12 harness skills (+ references/ and assets/)
329
+ skills/<name>/SKILL.md # the 13 harness skills (+ references/ and assets/)
330
330
  skills/tech-lead/schemas/ # the envelope port: WorkOrder, WorkResult, domain registry
331
331
  skills/tech-lead/workflows/shapeup-run.js # the BUILD-phase pipeline, on the native Workflow runtime
332
332
  kernel/harness.mjs # ONE entry point for every deterministic step; the whole permission grant
@@ -0,0 +1,10 @@
1
+ ---
2
+ description: Render the Hill Chart dashboard — a portfolio of pitches, or one pitch's detail
3
+ ---
4
+ Use the **hill-chart** skill on $ARGUMENTS.
5
+
6
+ Renders a self-contained dashboard from committed hill shards (`shapeup/<slug>/hill/*.yml`) and
7
+ each pitch's local run graph (`.shapeup/<slug>/graph.jsonl`): a portfolio card per pitch, and
8
+ per-pitch detail with a mechanical Hill Chart, an attention list, a scope board, round history,
9
+ and the full run graph one click deeper. Give a slug to render one pitch only; no argument
10
+ renders the whole portfolio.
@@ -6,8 +6,9 @@
6
6
  import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from "node:fs";
7
7
  import { resolve, join } from "node:path";
8
8
  import { runArgs } from "../lib/argv.mjs";
9
- import { scopesDir, hillDir, verdictsDir, evaluationDir, discoveryLedger } from "../lib/paths.mjs";
9
+ import { scopesDir, hillDir, verdictsDir, resultsDir, discoveryLedger } from "../lib/paths.mjs";
10
10
  import { readAllContracts, SCOPE_CONTRACT } from "../lib/contract.mjs";
11
+ import { evalVerdict } from "../probe/eval.mjs";
11
12
 
12
13
  /**
13
14
  * Derive and write the hill phase for all scopes mechanically based on T0, T1, and ledger facts.
@@ -26,30 +27,27 @@ import { readAllContracts, SCOPE_CONTRACT } from "../lib/contract.mjs";
26
27
  export function deriveHill(cwd, slug) {
27
28
  const scopes = readAllContracts(scopesDir(cwd, slug), SCOPE_CONTRACT).map((x) => x.contract);
28
29
  const vDir = verdictsDir(cwd, slug);
29
- const evalDir = evaluationDir(cwd, slug);
30
30
  const ledgerPath = discoveryLedger(cwd, slug);
31
31
  const hDir = hillDir(cwd, slug);
32
-
32
+
33
33
  if (!existsSync(hDir)) mkdirSync(hDir, { recursive: true });
34
-
35
- // 1. Check if T1 Evaluation passed (spec-conformance === PASS for the most recent run)
34
+
35
+ // 1. Check if T1 Evaluation passed the LATEST evaluate round's verdict, read the same way
36
+ // GATE L3's own pass/fail branch does (`probe/eval.mjs`'s `evalVerdict()`), not by re-parsing a
37
+ // ledger filename (`.verdicts-run.jsonl`) that `reduce ingest` never actually writes (it writes
38
+ // `.verdicts-<target>.jsonl`, keyed off the order id). That mismatch made `t1Pass` always false,
39
+ // so FINISHED was unreachable through this path regardless of what the run actually produced.
36
40
  let t1Pass = false;
37
- const evalFile = join(evalDir, ".verdicts-run.jsonl");
38
- if (existsSync(evalFile)) {
39
- const lines = readFileSync(evalFile, "utf8").trim().split(/\n/).filter(Boolean);
40
- let maxRun = 0;
41
- for (const line of lines) {
42
- try {
43
- const parsed = JSON.parse(line);
44
- if (parsed.run >= maxRun) {
45
- maxRun = parsed.run;
46
- if (parsed.dimension === "spec-conformance") {
47
- t1Pass = (parsed.verdict === "PASS");
48
- }
49
- }
50
- } catch (e) {
51
- // ignore parse errors
52
- }
41
+ const rDir = resultsDir(cwd, slug);
42
+ if (existsSync(rDir)) {
43
+ let maxRound = 0;
44
+ for (const f of readdirSync(rDir)) {
45
+ const m = f.match(/^evaluate-r(\d+)\.json$/);
46
+ if (m) maxRound = Math.max(maxRound, Number(m[1]));
47
+ }
48
+ if (maxRound > 0) {
49
+ const v = evalVerdict(cwd, slug, maxRound);
50
+ t1Pass = v.found && v.overall === "PASS";
53
51
  }
54
52
  }
55
53
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shapeup-sdlc",
3
- "version": "3.0.2",
3
+ "version": "3.1.0",
4
4
  "description": "Shape Up for coding agents \u2014 with gates the agent can't talk its way past. Harness for Claude Code.",
5
5
  "bin": {
6
6
  "shapeup-sdlc": "bin/init.mjs"
@@ -0,0 +1,151 @@
1
+ ---
2
+ name: hill-chart
3
+ description: "Use this skill whenever the user wants to see how their Shape Up build(s) are progressing — a Hill Chart, a build dashboard, or a portfolio view across pitches. Triggers on: \"show me the hill chart\", \"how's my build doing\", \"how are my pitches doing\", \"visualize my pitches\", \"render the dashboard\", \"what's the status of my runs\", \"build portfolio\", or any request to see a Shape Up run's progress visually rather than read as prose. Renders a self-contained HTML dashboard: a portfolio card per pitch, and per-pitch detail with a mechanical Hill Chart, an attention list, a scope board, round history, and the full run graph one click deeper."
4
+ ---
5
+
6
+ # Hill Chart
7
+
8
+ **Renders what the harness already derives mechanically — never invents a position, never
9
+ re-runs a computation that would erase true history.**
10
+
11
+ You are not a worker: no WorkOrder, no WorkResult, invoked directly by the user (or by `/hill`)
12
+ exactly like `shapeup` is. There is nothing to declare in
13
+ `skills/tech-lead/schemas/domain.schema.json` and nothing to teach `harness compile` or
14
+ `harness reduce ingest` — those steps exist only for dispatched workers.
15
+
16
+ ## What you read
17
+
18
+ Two tiers, per project (`kernel/lib/paths.mjs`'s `sharedRoot`/`localRoot`):
19
+
20
+ - **COMMITTED** — `shapeup/<slug>/hill/<scope-id>.yml`, one file per scope, two fields
21
+ (`scope_id:`, `phase:`), a 4-value enum: `UPHILL_UNKNOWN → UPHILL_SOLVED →
22
+ DOWNHILL_EXECUTION → FINISHED`. Written by `harness reduce hill`. Survives even after a
23
+ project's local run trace is cleaned up post-ship.
24
+ - **LOCAL** — `.shapeup/<slug>/graph.jsonl`, the run graph (see `kernel/reduce/graph.mjs`).
25
+ Written by `harness reduce graph`. Supplies everything else: current-round verdict health,
26
+ gates, orders, rounds, dangling references.
27
+
28
+ ## Discovery
29
+
30
+ Do not `readdir` either root bare — both hold non-pitch entries (`shapeup/knowledge-base/`,
31
+ `.shapeup/metrics/`, `.shapeup/exports/`, `.shapeup/pitch-archive/`, loose files). A directory
32
+ counts as a pitch when:
33
+
34
+ - **Committed**: `shapeup/<slug>/` contains `hill/`, `scopes/`, or `shaping/`.
35
+ - **Local**: `.shapeup/<slug>/` contains `receipt.json` (the mechanical "a run started" fact).
36
+
37
+ Union the two; track `hasCommitted` / `hasLocal` per slug independently. A slug can be
38
+ committed-only (shipped, local trace cleaned up), local-only (very early, before scoping), or
39
+ both.
40
+
41
+ ## Freshness — read this before shelling out
42
+
43
+ For every slug where `hasLocal` is true, freshen before reading:
44
+
45
+ ```bash
46
+ node "${CLAUDE_PLUGIN_ROOT}/kernel/harness.mjs" reduce hill --slug <slug>
47
+ node "${CLAUDE_PLUGIN_ROOT}/kernel/harness.mjs" reduce graph --slug <slug>
48
+ ```
49
+
50
+ **For a committed-only slug (`hasCommitted && !hasLocal`), NEVER call `reduce hill`.**
51
+ `deriveHill()` folds whatever T0 verdicts currently exist on disk; a committed-only pitch has
52
+ none (the local trace was cleaned up after shipping), so re-running it would silently regress a
53
+ true historical `FINISHED` down to a fabricated `UPHILL_SOLVED` — reading absence of evidence as
54
+ evidence of absence. Read `shapeup/<slug>/hill/*.yml` for those slugs exactly as committed, and
55
+ render them with the archived state the template already implements (see below). Same reasoning
56
+ applies to `reduce graph` — there is no local trace to append from.
57
+
58
+ ## Reading the hill shards
59
+
60
+ Each `shapeup/<slug>/hill/<scope-id>.yml` is two lines:
61
+
62
+ ```yaml
63
+ scope_id: <scope-id>
64
+ phase: <UPHILL_UNKNOWN|UPHILL_SOLVED|DOWNHILL_EXECUTION|FINISHED>
65
+ ```
66
+
67
+ Parse this yourself while reading it (it is two flat string fields — no YAML library needed and
68
+ none should be added; the shipped template's browser-side JS stays dependency-free by construction).
69
+ Build one `{ scope_id, phase }` object per file.
70
+
71
+ ## Rendering — the injection contract
72
+
73
+ The engine ships at `assets/dashboard.template.html` — a complete, self-contained HTML page
74
+ (inline CSS/JS, no external fetch beyond Google Fonts, no build step — the same convention as
75
+ this repo's own `docs/visualize/*.html`). Do not rewrite it from a text description; read it,
76
+ fill in real data, and write the result.
77
+
78
+ 1. For each discovered slug, build one entry:
79
+
80
+ ```js
81
+ {
82
+ slug: "<slug>",
83
+ hasLocal: <bool>,
84
+ hasCommitted: <bool>,
85
+ hill: [ { scope_id: "...", phase: "..." }, ... ], // parsed from the *.yml shards, [] if none
86
+ graphJsonl: "<the raw text of .shapeup/<slug>/graph.jsonl, or '' when hasLocal is false>"
87
+ }
88
+ ```
89
+
90
+ 2. Collect all entries into an array, `entries`.
91
+ 3. Compute `injected = JSON.stringify(JSON.stringify(entries))` — stringifying twice yields a
92
+ JS string literal (its own quotes and escapes included) safe to splice into the template
93
+ verbatim.
94
+ 4. In the template text, replace the exact substring `"__HILL_CHART_EMBEDDED_DATA__"`
95
+ (quotes included) with `injected`. Exactly one replacement, exactly that token.
96
+ 5. Write the result. See "Where to write it" below for the required mechanism.
97
+
98
+ The template's own boot code checks whether that constant still equals the literal placeholder
99
+ (meaning nothing was injected) and falls back to an empty portfolio — the same state a project
100
+ with no pitches yet would show. That fallback is what lets you smoke-test the un-injected
101
+ template directly: it never throws, it just renders empty.
102
+
103
+ ## Where to write it
104
+
105
+ Write via `Bash`, **not** the `Write` or `Edit` tool:
106
+
107
+ ```bash
108
+ cat > ".shapeup/<slug>/dashboard/hill-chart-<UTC-stamp>.html" <<'HTML'
109
+ <contents>
110
+ HTML
111
+ ```
112
+
113
+ `hooks/sandbox-guard.mjs` gates `Edit`/`Write`/`MultiEdit` by substrate and only exempts paths
114
+ under the *active order's own slug* (`.shapeup/<active-slug>/...`); a cross-slug portfolio file
115
+ would not match that exemption if a build happens to be mid-dispatch in the same repo when you
116
+ run, and would get hook-denied. The guard does not inspect `Bash`, so writing the file with a
117
+ shell heredoc is unaffected by whichever order (if any) is currently active. Use:
118
+
119
+ - `.shapeup/<slug>/dashboard/hill-chart-<stamp>.html` for a single pitch's dashboard.
120
+ - `.shapeup/dashboard/portfolio-<stamp>.html` for the multi-pitch portfolio.
121
+
122
+ Both are LOCAL and already covered by this project's `.shapeup/` gitignore rule — no new ignore
123
+ entry needed.
124
+
125
+ **If the current session's tool surface includes an Artifact-publishing tool** (as Claude Code /
126
+ claude.ai sessions with artifacts enabled do), also publish the same file through it, following
127
+ this repo's own `artifact-design` and `artifact-capabilities` skill guidance — the template is
128
+ already self-contained and theme-aware, so this is a strictly additive step. The local file write
129
+ above is the one guaranteed path in any host; treat the Artifact publish as a bonus, never the
130
+ only way the user gets the result.
131
+
132
+ ## Why the Hill Chart and the scope board are not redundant
133
+
134
+ `hill.mjs`'s `hasGreen` is set by *any* T0-green verdict a scope has *ever* produced, and never
135
+ resets. A scope can therefore sit at `DOWNHILL_EXECUTION` on the Hill Chart while its *current*
136
+ attempt is red. The Hill Chart answers "how much uncertainty has been burned down, ever"; the
137
+ scope board and round matrix answer "is the current attempt green, right now." The template
138
+ renders both for exactly this reason — do not "simplify" one away later.
139
+
140
+ ## Invocation
141
+
142
+ ```bash
143
+ # Whole portfolio, default
144
+ /hill-chart
145
+
146
+ # One pitch only
147
+ /hill-chart envlint
148
+ ```
149
+
150
+ Standalone: discover, freshen, render, write, and (when available) publish — no flags beyond an
151
+ optional slug filter. There is no `--order` form; this skill is never dispatched.