@tendrilapp/cli 0.1.23 → 0.1.25

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/dist/SKILL.md CHANGED
@@ -43,13 +43,18 @@ Long generator/score waits: silence is not a health signal, and POLL
43
43
  cadence is not RELAY cadence (run 13: rounds landed 6–11 minutes apart,
44
44
  so a correct 30s poll still left the user in 9-minute silences they
45
45
  flagged as unacceptable). Two duties: relay each round's result the
46
- moment `<candidateDir>/score-history.jsonl` gains a line ("round 3
47
- scored: 15/21 at floor 0.885"), AND when more than ~60 seconds pass
48
- with no round, emit a liveness line from the candidate dir's file
49
- mtimes, which move while the generator is WRITING (thinking pauses are quiet — that is normal, not frozen) ("still
50
- working styles.css touched 20s ago"). The history file APPEARS ONLY
51
- AFTER THE FIRST completed score, so its absence early is normal, not
52
- frozen. A healthy run can take 30+ minutes; never kill on elapsed time
46
+ moment `<candidateDir>/score-history.jsonl` gains a `round-scored` line
47
+ ("round 3 scored: 15/21 at floor 0.885"), AND when more than ~60
48
+ seconds pass with no round, emit a liveness line from the candidate
49
+ dir's file mtimes, which move while the generator is WRITING (thinking
50
+ pauses are quiet that is normal, not frozen) ("still
51
+ working styles.css touched 20s ago"). Every history line carries an
52
+ `event`: `round-start` lands the moment a score BEGINS and has no
53
+ numbers — relay it as "round 3 scoring started", never as a result —
54
+ and `round-scored` carries that round's pass/total/floor/mean. So the
55
+ file appears with the first score's START; before that, its absence
56
+ means scoring has not begun yet, which early on is normal, not frozen.
57
+ A healthy run can take 30+ minutes; never kill on elapsed time
53
58
  alone — count staleness from the LAST CHANGE to any signal, and when
54
59
  past ~20 minutes of true staleness, ask the user before killing.
55
60
 
@@ -64,13 +69,29 @@ fidelity gap" was a missing font weight one check away; a stop rule is
64
69
  for avoiding thrash, never for converting an unverified hypothesis into
65
70
  a final answer.
66
71
 
72
+ Concurrency, whether that is several components in one session or one
73
+ large set:
74
+ - Every agent in flight spends ONE shared Figma budget, and Figma
75
+ meters per minute as well as per day, so parallelism sets the burst
76
+ rate. Scale recorders to the total pose count: up to ~30 poses,
77
+ four; ~30–100 poses, two; beyond ~100 poses, record serially and
78
+ pace it — a large set has no slack to burn on a per-minute limit.
79
+ A measured 8-wide batch hit its rate limit (per-piece resume
80
+ recovered, but the stall is avoidable).
81
+ - COUNT EVERY AGENT THAT CALLS FIGMA, not every agent you launched:
82
+ a recorder NEVER spawns its own recorders. It records the queue it
83
+ was handed and stops. A measured 4-wide run peaked at double the
84
+ intended rate because two recorders spawned children that kept
85
+ calling after their parents reported done — the parent's "done" is
86
+ not proof its calls have stopped. To split a large queue further,
87
+ hand out the extra slices yourself so the whole count stays in one
88
+ place.
89
+
67
90
  Batch runs (several components in one session):
68
- - Cap concurrent recorders at FOUR. All recorders share one Figma
69
- desktop MCP server; a measured 8-wide batch hit its rate limit
70
- (per-piece resume recovered, but the stall is avoidable).
71
91
  - Batch the questions: plan ALL sets first, then put every
72
- defaults-to-confirm and the one model question to the user together
73
- — never one dialog per component.
92
+ defaults-to-confirm, every missing-interaction-state disclosure and
93
+ the one model question to the user together — never one dialog per
94
+ component, and never mid-generation.
74
95
  - Create candidate directories with a bare `mkdir -p <dir>` — no
75
96
  `&&`-compounds. Compound variants each need their own permission
76
97
  approval; the bare form is one grant for the whole batch.
@@ -105,8 +126,34 @@ Batch runs (several components in one session):
105
126
  which X should it show?" — the heuristic's pick is Recommended;
106
127
  a different answer re-plans via `defaults` (free until the first
107
128
  envelope is ingested, frozen after).
108
- Recording cost is stated in figmaCallEstimate, never asked about —
109
- proceed with what the user provided.
129
+ - `interactionStatesToConfirm`: the component set has no hover,
130
+ focus or pressed variant, so nothing recorded shows how it looks
131
+ while someone is using it — and what is never recorded is never
132
+ checked. Say its `statement` and `designFix` as written. This one
133
+ is a DISCLOSURE, not a question: no answer is needed, nothing is
134
+ blocked, and the fix (if the user wants one) is a new variant in
135
+ Figma, not a change in the code. Say only what
136
+ `recordedVariants` contains — never guess from the component's
137
+ name what kind of control it is.
138
+ FEASIBILITY, before the first recording call: the plan output
139
+ states the call arithmetic for this queue (poses × calls per pose).
140
+ Turn it into a verdict with one free check — call the Figma
141
+ `whoami` tool, which names the seat and plan and is exempt from
142
+ Figma's tool-call limits, then divide the queue's call count by the
143
+ daily allowance that seat carries. Never quote an allowance from
144
+ memory; use the one whoami reports. If the set fits inside one
145
+ day, record and say nothing about cost — a set that comfortably
146
+ fits is not worth the user's attention. If it does NOT fit, stop
147
+ and tell the user in plain numbers before recording anything:
148
+ "336 poses × 3 = 1,008 Figma calls. Your Full seat on Professional
149
+ allows 200/day, so this set needs about 5 days." Then let them
150
+ choose: (a) pace it across days — a rate limit loses no recorded
151
+ work, the set persists after every pose and re-running the same
152
+ command resumes; (b) record fewer poses — theirs to decide alone,
153
+ because it buys calls with coverage and there is no tool for it:
154
+ they re-plan with `--sample` in their own terminal; (c) a seat or
155
+ plan with a larger daily allowance. Never sample on their behalf,
156
+ and never start a set you have computed cannot finish.
110
157
  Later, `tendril_engine_brief` may carry `fontProvisioning` (the
111
158
  design uses a family the local kit lacks). First run
112
159
  `tendril fonts resolve --set <recording-dir>` yourself — it fetches
@@ -141,7 +188,9 @@ Batch runs (several components in one session):
141
188
  from it. SPEED: after `plan` the whole queue is known and reps are
142
189
  independent — fan out across parallel subagents in any order (use
143
190
  the cheap `tendril-recorder` agent; recording is transcription,
144
- not reasoning). HOST-POLICY GATE (measured, run 6): many hosts
191
+ not reasoning), with HOW MANY set by the concurrency rule above:
192
+ it scales down as poses go up, and no recorder spawns recorders.
193
+ HOST-POLICY GATE (measured, run 6): many hosts
145
194
  forbid spawning subagents unless the user requested it, and the
146
195
  cost of not delegating is invisible until paid (12 hand-recorded
147
196
  reps ≈ 45k main-context tokens that cheap recorders absorb at ~11k
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/mcp/src/bin.ts
3
+ // packages/mcp/src/bin.ts
4
4
  import { readFileSync as readFileSync2 } from "node:fs";
5
5
  import path2 from "node:path";
6
6
  import { fileURLToPath as fileURLToPath2 } from "node:url";
@@ -8,7 +8,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
9
  import { z as z2 } from "zod";
10
10
 
11
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/mcp/src/server.ts
11
+ // packages/mcp/src/server.ts
12
12
  import { execFile } from "node:child_process";
13
13
  import { createHash } from "node:crypto";
14
14
  import { existsSync, mkdtempSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
@@ -25,7 +25,7 @@ var optStr = (d) => z.string().optional().describe(d);
25
25
  var TOOLS = [
26
26
  {
27
27
  name: "tendril_record_plan",
28
- description: "ENTRY POINT for implementing/building a React component from a Figma design or figma.com URL \u2014 start the Tendril pipeline here (after loading the tendril skill, if installed). Plans a recording session: computes the rep queue (anchor + one-factor + conflict crosses) from the verbatim get_metadata response (pass its blocks via metadataParts \u2014 no file to write) and persists the set manifest. Resumes if the set already exists. The output may carry USER QUESTIONS \u2014 defaultsToConfirm (which pose is the component's default) or a multiple-component-sets error (which set to record): render them to a present user and apply the answers via `defaults` / `componentSet`; non-interactive runs follow each question's stated fallback. Recording cost is stated in figmaCallEstimate, never asked about \u2014 proceed with what the user provided.",
28
+ description: "ENTRY POINT for implementing/building a React component from a Figma design or figma.com URL \u2014 start the Tendril pipeline here (after loading the tendril skill, if installed). Plans a recording session: computes the rep queue (anchor + one-factor + conflict crosses) from the verbatim get_metadata response (pass its blocks via metadataParts \u2014 no file to write) and persists the set manifest. Resumes if the set already exists. The output may carry USER QUESTIONS \u2014 defaultsToConfirm (which pose is the component's default) or a multiple-component-sets error (which set to record): render them to a present user and apply the answers via `defaults` / `componentSet`; non-interactive runs follow each question's stated fallback. It may also carry `interactionStatesToConfirm` \u2014 the recording holds no hover/focus/pressed state, so nothing shows how the component behaves when someone uses it: say its `statement` and `designFix` in that SAME one message (the fix is a Figma variant, not code). It is a disclosure, not a gate \u2014 no answer is required and recording proceeds regardless. The output also carries `feasibilityCheck`: the call arithmetic for this queue plus the free `whoami` check that turns it into a verdict \u2014 complete that handshake BEFORE the first recording call, and surface the verdict to the user when the set does not fit their daily allowance.",
29
29
  schema: z.object({
30
30
  setDir: str("recording set directory to create/resume"),
31
31
  component: str("component/system name"),
@@ -423,7 +423,7 @@ var IMPLEMENT_PROMPT = {
423
423
  ].join("\n")
424
424
  };
425
425
 
426
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/mcp/src/bin.ts
426
+ // packages/mcp/src/bin.ts
427
427
  var version = "dev";
428
428
  try {
429
429
  version = JSON.parse(readFileSync2(path2.join(path2.dirname(fileURLToPath2(import.meta.url)), "..", "package.json"), "utf8")).version ?? "dev";