@tendrilapp/cli 0.1.15 → 0.1.16
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 +18 -5
- package/dist/tendril-mcp.js +1 -1
- package/dist/tendril.js +57 -14
- package/package.json +1 -1
package/dist/SKILL.md
CHANGED
|
@@ -164,11 +164,24 @@ Non-negotiables (the CLI enforces these; do not fight them):
|
|
|
164
164
|
6. Tell the user roughly what a run costs them: organism-scale
|
|
165
165
|
components have measured 0.3–0.7M tokens of their plan.
|
|
166
166
|
|
|
167
|
-
Delegated generation is SLOW BY NATURE — measured runs
|
|
168
|
-
minutes
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
Delegated generation is SLOW BY NATURE — measured runs span 3–23
|
|
168
|
+
minutes (run 8: two healthy runs at ~23m each). Do not treat silence
|
|
169
|
+
as failure. Tell the user up front that a generator agent typically
|
|
170
|
+
runs 10–25 minutes, and poll the candidate directory rather than
|
|
171
|
+
guessing.
|
|
172
|
+
|
|
173
|
+
ESCALATION (when the balanced model plateaus below the bar): ask the
|
|
174
|
+
user before spending the premium tier; point the second generator at
|
|
175
|
+
the SAME candidate directory (never a fresh start — the work is
|
|
176
|
+
sunk); hand it a calibration table of failing vs passing configs and
|
|
177
|
+
the exact fields; never pass rebind. TRAP (measured, run 8): the
|
|
178
|
+
nearest PASSING config is not a safe regression tripwire — it may
|
|
179
|
+
carry the same defect and pass by luck (one config passed its bar by
|
|
180
|
+
0.00089 while carrying the identical corner defect as the three
|
|
181
|
+
failures). An edit whose exact fields do not move across a round did
|
|
182
|
+
NOTHING: revert it before the next round — bundles must never
|
|
183
|
+
accumulate unsupported edits a later reader mistakes for recorded
|
|
184
|
+
truth.
|
|
172
185
|
|
|
173
186
|
Stop a generator agent only on evidence of the real failure mode:
|
|
174
187
|
reasoning runaway, where a turn ends on max_tokens having emitted a
|
package/dist/tendril-mcp.js
CHANGED
|
@@ -368,7 +368,7 @@ var UPDATE_PROMPT = {
|
|
|
368
368
|
"1. The MCP server updates ITSELF automatically at session start (npx @latest) \u2014 nothing to do for the pipeline.",
|
|
369
369
|
"2. If a global CLI is installed: `npm install -g @tendrilapp/cli@latest`.",
|
|
370
370
|
"3. If tendril_doctor reported the SERVER version stale this session: `npm cache clean --force`, then the user restarts the session so npx re-resolves @latest.",
|
|
371
|
-
"4. The optional plugin is a thin shim (triggers, agents, commands) that rarely changes \u2014 usually nothing to do. When a plugin update IS announced (third-party marketplaces do not reliably auto-update): terminal Claude Code \u2014 `claude plugin marketplace update tendrilapp` FIRST (the update command compares against a cached clone and can falsely report already-latest), then `claude plugin update tendril`, then /reload-plugins; VS Code extension \u2014 the /plugins panel has no update button:
|
|
371
|
+
"4. The optional plugin is a thin shim (triggers, agents, commands) that rarely changes \u2014 usually nothing to do. When a plugin update IS announced (third-party marketplaces do not reliably auto-update): terminal Claude Code \u2014 `claude plugin marketplace update tendrilapp` FIRST (the update command compares against a cached clone and can falsely report already-latest), then `claude plugin update tendril`, then /reload-plugins; VS Code extension \u2014 the /plugins panel has no update button: Marketplaces tab \u2192 REFRESH tendrilapp FIRST (a stale marketplace clone makes reinstall reinstall the old version \u2014 measured), then Plugins tab \u2192 uninstall tendril, reinstall, reopen the chat panel; if the refresh doesn't take, remove and re-add the marketplace. Claude Desktop \u2014 no plugin management: quit and relaunch. Every critical surface already updated in step 1 regardless.",
|
|
372
372
|
"5. Confirm by running tendril_doctor and reporting its version line. Never claim success without that confirmation."
|
|
373
373
|
].join("\n")
|
|
374
374
|
};
|
package/dist/tendril.js
CHANGED
|
@@ -2332,6 +2332,23 @@ function detectBackdrop(png, opts = {}) {
|
|
|
2332
2332
|
const { modal, consensus } = ringConsensus(png);
|
|
2333
2333
|
if (consensus < 0.9) return [255, 255, 255];
|
|
2334
2334
|
if (Math.min(...modal) < 200 && opts.trustDark !== true) return [255, 255, 255];
|
|
2335
|
+
const decoded = PNG.sync.read(Buffer.from(png));
|
|
2336
|
+
if (decoded.width >= 4 && decoded.height >= 4) {
|
|
2337
|
+
const blocks = cornerBlocks(decoded, 2);
|
|
2338
|
+
const first = blocks[0];
|
|
2339
|
+
const cornersAgree = blocks.every((c) => Math.abs(c[0] - first[0]) + Math.abs(c[1] - first[1]) + Math.abs(c[2] - first[2]) <= INK_DELTA);
|
|
2340
|
+
const cornersDisagreeWithRing = Math.abs(first[0] - modal[0]) + Math.abs(first[1] - modal[1]) + Math.abs(first[2] - modal[2]) > INK_DELTA;
|
|
2341
|
+
if (cornersAgree && cornersDisagreeWithRing) {
|
|
2342
|
+
const extremes = cornerBlocks(decoded, 1);
|
|
2343
|
+
const value = [
|
|
2344
|
+
Math.round(extremes.reduce((a, c) => a + c[0], 0) / 4),
|
|
2345
|
+
Math.round(extremes.reduce((a, c) => a + c[1], 0) / 4),
|
|
2346
|
+
Math.round(extremes.reduce((a, c) => a + c[2], 0) / 4)
|
|
2347
|
+
];
|
|
2348
|
+
if (Math.min(...value) < 200 && opts.trustDark !== true) return [255, 255, 255];
|
|
2349
|
+
return value;
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2335
2352
|
return modal;
|
|
2336
2353
|
}
|
|
2337
2354
|
function pngDimensions(png) {
|
|
@@ -7616,8 +7633,8 @@ function authorComponentApi(opts) {
|
|
|
7616
7633
|
configs.push({ rep: p.slug, component: componentIdent, props: {} });
|
|
7617
7634
|
}
|
|
7618
7635
|
if (inexpressible.length > 0) throw new PoseCompletenessError(inexpressible);
|
|
7619
|
-
if (opts.dismissible
|
|
7620
|
-
props.push({ name: "onDismiss", kind: "callback" });
|
|
7636
|
+
if (opts.dismissible !== void 0) {
|
|
7637
|
+
props.push({ name: "onDismiss", kind: "callback", default: opts.dismissible });
|
|
7621
7638
|
}
|
|
7622
7639
|
const entry = `${componentIdent}.tsx`;
|
|
7623
7640
|
const apiPin = {
|
|
@@ -7626,13 +7643,15 @@ function authorComponentApi(opts) {
|
|
|
7626
7643
|
name: pr.name,
|
|
7627
7644
|
type: pr.kind === "boolean" ? "boolean" : pr.kind === "string" ? "string" : pr.kind === "callback" ? "() => void" : pr.values.map((v) => `"${v}"`).join(" | "),
|
|
7628
7645
|
required: false,
|
|
7629
|
-
|
|
7646
|
+
// A callback's `default` field carries detection EVIDENCE for the
|
|
7647
|
+
// prop line, not a value — it never enters the pin.
|
|
7648
|
+
...pr.default !== void 0 && pr.kind !== "callback" ? { default: pr.default } : {}
|
|
7630
7649
|
})),
|
|
7631
7650
|
forcedStates,
|
|
7632
7651
|
poseCompleteness: { recordedPoses: opts.poses.length, expressible: configs.length }
|
|
7633
7652
|
};
|
|
7634
7653
|
const propLines = props.map(
|
|
7635
|
-
(pr) => pr.kind === "boolean" ? ` ${pr.name}?: boolean;` : pr.kind === "string" ? ` ${pr.name}?: string; // CONTENT \u2014 recorded default ${JSON.stringify(pr.default)}` : pr.kind === "callback" ? ` ${pr.name}?: () => void; // NOTIFICATION \u2014
|
|
7654
|
+
(pr) => pr.kind === "boolean" ? ` ${pr.name}?: boolean;` : pr.kind === "string" ? ` ${pr.name}?: string; // CONTENT \u2014 recorded default ${JSON.stringify(pr.default)}` : pr.kind === "callback" ? ` ${pr.name}?: () => void; // NOTIFICATION \u2014 recorded evidence: ${pr.default ?? "the recorded affordance"}; the component owns its state (it hides/updates itself) and the callback informs, never controls` : ` ${pr.name}?: ${pr.values.map((v) => `"${v}"`).join(" | ")}; // default "${pr.default}"`
|
|
7636
7655
|
);
|
|
7637
7656
|
const provided = opts.fonts ?? [];
|
|
7638
7657
|
const recorded = opts.recordedFonts ?? [];
|
|
@@ -7705,7 +7724,7 @@ function authorBehaviors(api, extras = {}) {
|
|
|
7705
7724
|
behaviors.push({
|
|
7706
7725
|
id: `content-prop-renders(${sentinel.prop})`,
|
|
7707
7726
|
config: sentinel.config,
|
|
7708
|
-
props: { [sentinel.prop]: marker },
|
|
7727
|
+
props: { ...sentinel.props ?? {}, [sentinel.prop]: marker },
|
|
7709
7728
|
steps: [{ assertTextVisible: marker }]
|
|
7710
7729
|
});
|
|
7711
7730
|
}
|
|
@@ -7823,7 +7842,7 @@ function recordedTextSlots(setDir, repSlugs) {
|
|
|
7823
7842
|
if (t === "" || !/[A-Za-z0-9]/.test(t)) continue;
|
|
7824
7843
|
texts.push(t);
|
|
7825
7844
|
}
|
|
7826
|
-
perRep.push({ slug, texts });
|
|
7845
|
+
perRep.push({ slug, texts, code });
|
|
7827
7846
|
}
|
|
7828
7847
|
const axisValuesBySlug = /* @__PURE__ */ new Map();
|
|
7829
7848
|
for (const slug of repSlugs) {
|
|
@@ -7866,7 +7885,9 @@ function recordedTextSlots(setDir, repSlugs) {
|
|
|
7866
7885
|
const v = propRep.find((pr) => pr.slug === r.slug)?.props.get(name) ?? def;
|
|
7867
7886
|
return r.texts.some((t) => t.includes(v)) || r.texts.join(" ").replace(/\s+/g, " ").includes(v.replace(/\s+/g, " ").trim());
|
|
7868
7887
|
}).map((r) => r.slug);
|
|
7869
|
-
|
|
7888
|
+
const refPattern = new RegExp(`\\{\\s*${name}\\s*\\}`);
|
|
7889
|
+
const referencedIn = perRep.filter((r) => refPattern.test(r.code)).map((r) => r.slug);
|
|
7890
|
+
return { prop: name, default: def, overrides, varies: Object.keys(overrides).length > 0, visibleIn, referencedIn };
|
|
7870
7891
|
});
|
|
7871
7892
|
}
|
|
7872
7893
|
if (perRep.length === 0) return [];
|
|
@@ -7913,7 +7934,7 @@ function recordedTextSlots(setDir, repSlugs) {
|
|
|
7913
7934
|
usedNames.add(prop);
|
|
7914
7935
|
const overrides = {};
|
|
7915
7936
|
for (const [slug, v] of sl.values) if (v !== def) overrides[slug] = v;
|
|
7916
|
-
return { prop, default: def, overrides, varies: Object.keys(overrides).length > 0, visibleIn: [...sl.values.keys()] };
|
|
7937
|
+
return { prop, default: def, overrides, varies: Object.keys(overrides).length > 0, visibleIn: [...sl.values.keys()], referencedIn: [] };
|
|
7917
7938
|
});
|
|
7918
7939
|
}
|
|
7919
7940
|
function authorTaskFromSet(setDir, opts = {}) {
|
|
@@ -7954,12 +7975,29 @@ function authorTaskFromSet(setDir, opts = {}) {
|
|
|
7954
7975
|
...recordedFonts.length > 0 ? { recordedFonts } : {},
|
|
7955
7976
|
...Object.keys(defaults).length > 0 ? { defaults } : {},
|
|
7956
7977
|
...textSlots.length > 0 ? { textSlots } : {},
|
|
7957
|
-
...dismissName !== void 0 ? { dismissible:
|
|
7978
|
+
...dismissName !== void 0 ? { dismissible: dismissName } : {}
|
|
7958
7979
|
});
|
|
7959
7980
|
const anchorSlug = api.configs.find((c) => Object.keys(c.props).length === 0)?.rep ?? api.configs[0]?.rep;
|
|
7960
|
-
const sentinels = textSlots.filter((slot) => !slot.varies
|
|
7981
|
+
const sentinels = textSlots.filter((slot) => !slot.varies).map((slot) => {
|
|
7961
7982
|
const authored = api.props.find((pr) => pr.kind === "string" && pr.default === slot.default);
|
|
7962
|
-
|
|
7983
|
+
if (authored === void 0) return void 0;
|
|
7984
|
+
if (slot.visibleIn.length > 0) {
|
|
7985
|
+
const entry = {
|
|
7986
|
+
prop: authored.name,
|
|
7987
|
+
config: anchorSlug !== void 0 && slot.visibleIn.includes(anchorSlug) ? anchorSlug : slot.visibleIn[0]
|
|
7988
|
+
};
|
|
7989
|
+
return entry;
|
|
7990
|
+
}
|
|
7991
|
+
if (slot.referencedIn.length > 0) {
|
|
7992
|
+
const toggle = api.props.find(
|
|
7993
|
+
(pr) => pr.kind === "boolean" && authored.name.toLowerCase().startsWith(pr.name.toLowerCase()) && authored.name.length > pr.name.length
|
|
7994
|
+
);
|
|
7995
|
+
if (toggle !== void 0 && anchorSlug !== void 0) {
|
|
7996
|
+
const entry = { prop: authored.name, config: anchorSlug, props: { [toggle.name]: true } };
|
|
7997
|
+
return entry;
|
|
7998
|
+
}
|
|
7999
|
+
}
|
|
8000
|
+
return void 0;
|
|
7963
8001
|
}).filter((x) => x !== void 0);
|
|
7964
8002
|
const { behaviors, prelude, disclosures } = authorBehaviors(api, { ...sentinels.length > 0 ? { sentinels } : {} });
|
|
7965
8003
|
if (dismissName !== void 0) {
|
|
@@ -7970,10 +8008,15 @@ function authorTaskFromSet(setDir, opts = {}) {
|
|
|
7970
8008
|
}
|
|
7971
8009
|
for (const slot of textSlots) {
|
|
7972
8010
|
if (slot.varies) continue;
|
|
7973
|
-
|
|
8011
|
+
const sentineled = sentinels.some((sn) => api.props.some((pr) => pr.name === sn.prop && pr.default === slot.default));
|
|
8012
|
+
if (sentineled) {
|
|
7974
8013
|
disclosures.push(
|
|
7975
8014
|
`constant content prop (recorded ${JSON.stringify(slot.default)}) is sentinel-checked: a behavior mounts it with a sentinel string and asserts it renders \u2014 hardcoding the recorded literal fails that check`
|
|
7976
8015
|
);
|
|
8016
|
+
} else if (slot.referencedIn.length > 0) {
|
|
8017
|
+
disclosures.push(
|
|
8018
|
+
`content prop (recorded ${JSON.stringify(slot.default)}) is rendered by REFERENCE in the emission but has no visibility evidence and no boolean toggle to reveal it \u2014 sentinel skipped (it could fail honest components whose pose hides the node); wire the prop anyway`
|
|
8019
|
+
);
|
|
7977
8020
|
} else {
|
|
7978
8021
|
disclosures.push(
|
|
7979
8022
|
`content prop from recorded text is UNEXERCISED AND UNRENDERED: NO recorded pose visibly renders ${JSON.stringify(slot.default)} (hidden node) \u2014 prescribed for completeness; wire it behind its visibility toggle; no sentinel can honestly run`
|
|
@@ -8378,7 +8421,7 @@ function statusOf(s) {
|
|
|
8378
8421
|
return tierOf(s, BARS.cert);
|
|
8379
8422
|
}
|
|
8380
8423
|
function emitBundleV1(opts) {
|
|
8381
|
-
const statuses = opts.scores.map((s) => ({ rep: s.rep, similarity: s.similarity, inkRecall: s.inkRecall, status: statusOf(s) }));
|
|
8424
|
+
const statuses = opts.scores.map((s) => ({ rep: s.rep, similarity: s.similarity, inkRecall: s.inkRecall, ...s.exact !== void 0 ? { exact: s.exact } : {}, status: statusOf(s) }));
|
|
8382
8425
|
const certified = statuses.filter((s) => s.status === "certified").length;
|
|
8383
8426
|
const pass = statuses.filter((s) => s.status !== "fail").length;
|
|
8384
8427
|
const lattice = countLatticeSymbols(opts.task.set);
|
|
@@ -9864,7 +9907,7 @@ async function runDoctorChecks(options) {
|
|
|
9864
9907
|
name: "plugin-skew",
|
|
9865
9908
|
ok: false,
|
|
9866
9909
|
detail: `Claude Code plugin cache holds v${newest} while this CLI is ${cliVersion()} \u2014 the plugin's skill/agents are STALE and will silently shadow current doctrine`,
|
|
9867
|
-
remediation: "Update the plugin: terminal Claude Code \u2014 `claude plugin marketplace update tendrilapp` then `claude plugin update tendril`; VS Code extension \u2014 /plugins panel
|
|
9910
|
+
remediation: "Update the plugin \u2014 REFRESH THE MARKETPLACE FIRST (its local clone goes stale and a reinstall faithfully reinstalls the old version): terminal Claude Code \u2014 `claude plugin marketplace update tendrilapp` then `claude plugin update tendril`; VS Code extension \u2014 /plugins panel \u2192 Marketplaces tab \u2192 refresh tendrilapp, THEN Plugins tab \u2192 uninstall + reinstall tendril, reopen the chat panel. If the refresh doesn't take, remove the tendrilapp marketplace entirely and re-add TendrilApp/claude-plugin (a fresh clone cannot be stale)."
|
|
9868
9911
|
} : { name: "plugin-skew", ok: true, detail: `Claude Code plugin v${newest} matches this CLI` }
|
|
9869
9912
|
);
|
|
9870
9913
|
}
|