plotcoder-board 0.1.5 → 0.1.6
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
CHANGED
|
@@ -790,6 +790,15 @@ function summarize(state) {
|
|
|
790
790
|
return ` - ${character.id} — "${character.name}" on ${on} card${on === 1 ? "" : "s"}${brief}`;
|
|
791
791
|
})
|
|
792
792
|
.join("\n");
|
|
793
|
+
const placeCounts = new Map();
|
|
794
|
+
for (const note of state.notes) {
|
|
795
|
+
const phrase = (note.location ?? "").trim();
|
|
796
|
+
if (phrase) placeCounts.set(phrase, (placeCounts.get(phrase) ?? 0) + 1);
|
|
797
|
+
}
|
|
798
|
+
const places = [...placeCounts.entries()]
|
|
799
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
800
|
+
.map(([phrase, count]) => ` - "${phrase}" on ${count} card${count === 1 ? "" : "s"}`)
|
|
801
|
+
.join("\n");
|
|
793
802
|
const { beats, scenes } = countRanks(state);
|
|
794
803
|
const headline = (id) =>
|
|
795
804
|
state.notes.find((note) => note.id === id)?.headline ?? "(missing card)";
|
|
@@ -823,10 +832,14 @@ function summarize(state) {
|
|
|
823
832
|
`logline: ${state.logline ? `"${state.logline}"` : "(not set)"}`,
|
|
824
833
|
...production,
|
|
825
834
|
`beats: ${beats}, scenes: ${scenes}`,
|
|
826
|
-
|
|
835
|
+
state.targetEighths === DEFAULT_TARGET_EIGHTHS
|
|
836
|
+
? `runtime: about ${formatPages(runtime)} pages (an estimate from the cards; a page runs about a minute); no target set — set_target for a pilot (60) or a half-hour (30); against the feature default of 120 it would be ${formatPages(-over)} under`
|
|
837
|
+
: `runtime: about ${formatPages(runtime)} pages of a ${formatPages(state.targetEighths)}-page target — ${over > 0 ? `${formatPages(over)} over` : over < 0 ? `${formatPages(-over)} under` : "on it"} (an estimate from the cards; a page runs about a minute)`,
|
|
827
838
|
`notes: ${state.notes.length}, groups: ${state.groups.length}, arrows: ${state.arrows.length}, cast: ${state.characters.length}`,
|
|
828
839
|
"cast:",
|
|
829
840
|
cast || " (no one yet — add_character to start the roster)",
|
|
841
|
+
"places (each distinct phrase is one place; near-matches sit side by side):",
|
|
842
|
+
places || " (no card says where it happens yet)",
|
|
830
843
|
"cards:",
|
|
831
844
|
notes || " (no cards)",
|
|
832
845
|
"groups:",
|
|
@@ -996,7 +1009,7 @@ server.registerTool(
|
|
|
996
1009
|
},
|
|
997
1010
|
},
|
|
998
1011
|
async (args) => {
|
|
999
|
-
|
|
1012
|
+
let { result, live } = await commit({
|
|
1000
1013
|
type: "create_note",
|
|
1001
1014
|
headline: args.headline,
|
|
1002
1015
|
change: args.change,
|
|
@@ -1027,7 +1040,12 @@ server.registerTool(
|
|
|
1027
1040
|
}
|
|
1028
1041
|
if (person) ids.push(person.id);
|
|
1029
1042
|
}
|
|
1030
|
-
if (ids.length)
|
|
1043
|
+
if (ids.length) {
|
|
1044
|
+
const cast = await commit({ type: "set_cast", ids: [result.id], characterIds: ids });
|
|
1045
|
+
// The card as it is now, cast and all, so the reply's JSON agrees with its prose.
|
|
1046
|
+
const after = cast.state.notes.find((note) => note.id === result.id);
|
|
1047
|
+
if (after) result = after;
|
|
1048
|
+
}
|
|
1031
1049
|
castLine = ` Cast: ${args.characters.map((name) => name.trim()).join(", ")}${added.length ? ` (added to the roster: ${added.join(", ")})` : ""}.`;
|
|
1032
1050
|
}
|
|
1033
1051
|
const landed = [
|
|
@@ -1194,7 +1212,7 @@ server.registerTool(
|
|
|
1194
1212
|
}
|
|
1195
1213
|
const shape = beats
|
|
1196
1214
|
? `${beats} row(s), one per beat${wrappedUnder.length ? `; ${wrappedUnder.map((item) => `the row of "${item.beat.headline}" wraps under from "${item.first.headline}"`).join(", ")}` : ""}`
|
|
1197
|
-
: `${rows} row(s)`;
|
|
1215
|
+
: `${rows} row(s) five cards wide — no beats yet, so nothing sets the rows; set_rank the turns and organize again for a row per beat`;
|
|
1198
1216
|
return ok(`Organized ${poses.length} card(s) along the arrows into ${shape}${where(live)}.`, poses);
|
|
1199
1217
|
},
|
|
1200
1218
|
);
|
|
@@ -1927,8 +1945,16 @@ server.registerTool(
|
|
|
1927
1945
|
return ok("No place changed: those cards already read that way.");
|
|
1928
1946
|
}
|
|
1929
1947
|
const place = result[0].location;
|
|
1948
|
+
// A near match on the wall is probably the same place spelled twice.
|
|
1949
|
+
const wanted = (place ?? "").trim().toLowerCase();
|
|
1950
|
+
const near = wanted
|
|
1951
|
+
? [...new Set(state.notes.map((note) => (note.location ?? "").trim()).filter(Boolean))].filter(
|
|
1952
|
+
(other) => other.toLowerCase() !== wanted && (other.toLowerCase().includes(wanted) || wanted.includes(other.toLowerCase())),
|
|
1953
|
+
)
|
|
1954
|
+
: [];
|
|
1955
|
+
const warn = near.length ? ` The wall also has ${near.map((other) => `"${other}"`).join(", ")} — the same place spelled twice, or two places? Each distinct phrase counts as one place.` : "";
|
|
1930
1956
|
return ok(
|
|
1931
|
-
`${result.length} card(s) now ${place ? `at ${place}` : "nowhere"}${where(live)}
|
|
1957
|
+
`${result.length} card(s) now ${place ? `at ${place}` : "nowhere"}${where(live)}.${warn}`,
|
|
1932
1958
|
result,
|
|
1933
1959
|
);
|
|
1934
1960
|
},
|
|
@@ -2074,7 +2100,7 @@ server.registerTool(
|
|
|
2074
2100
|
{
|
|
2075
2101
|
title: "Create arrow",
|
|
2076
2102
|
description:
|
|
2077
|
-
"Draw a directed arrow from one card to another. kind 'follows' (the default) says what comes after what; kind 'setup' says the first card plants something the second pays off. Arrows are one-way: A→B does not create B→A. If you want both, call this twice — that is two arrows, not one two-headed line. A card cannot point at itself, and the same direction cannot be drawn twice, whatever its kind; use set_arrow_kind to change one.",
|
|
2103
|
+
"Draw a directed arrow from one card to another. kind 'follows' (the default) says what comes after what — a straight sequence needs them too: organize lays the wall out along them, and the wall asks about a card no arrow touches; kind 'setup' says the first card plants something the second pays off. Arrows are one-way: A→B does not create B→A. If you want both, call this twice — that is two arrows, not one two-headed line. A card cannot point at itself, and the same direction cannot be drawn twice, whatever its kind; use set_arrow_kind to change one.",
|
|
2078
2104
|
inputSchema: { from: z.string(), to: z.string(), kind: arrowKindSchema.optional() },
|
|
2079
2105
|
},
|
|
2080
2106
|
async (args) => {
|
|
@@ -2624,7 +2650,7 @@ server.registerTool(
|
|
|
2624
2650
|
const next = renameBoard(project, target.id, args.name);
|
|
2625
2651
|
if (next === project) return ok(`"${target.name}" already has that name.`);
|
|
2626
2652
|
const live = await writeProject(next, boards, rev, base);
|
|
2627
|
-
return ok(`Renamed to "${args.name.trim()}"${where(live)}
|
|
2653
|
+
return ok(`Renamed board "${target.name}" (${target.id}) to "${args.name.trim()}"${where(live)}.`, { id: target.id, name: args.name.trim() });
|
|
2628
2654
|
},
|
|
2629
2655
|
);
|
|
2630
2656
|
|
package/src/board/readWall.d.ts
CHANGED
|
@@ -59,7 +59,8 @@ export type WallReading = {
|
|
|
59
59
|
runs: Run[];
|
|
60
60
|
setups: Setup[];
|
|
61
61
|
/** Every planted card: the scene that pays it off (first setup arrow, by wall order), or null while unpaid. */
|
|
62
|
-
|
|
62
|
+
/** For each folded card, the cards its setup arrows land on, in wall order; empty when unpaid. */
|
|
63
|
+
payoffs: Record<string, string[]>;
|
|
63
64
|
findings: Finding[];
|
|
64
65
|
};
|
|
65
66
|
|
package/src/board/readWall.js
CHANGED
|
@@ -303,7 +303,9 @@ export function readWall(state) {
|
|
|
303
303
|
.filter((arrow) => arrow.kind === "setup" && arrow.from === note.id)
|
|
304
304
|
.map((arrow) => arrow.to)
|
|
305
305
|
.sort((a, b) => (wallIndex.get(a) ?? Infinity) - (wallIndex.get(b) ?? Infinity));
|
|
306
|
-
|
|
306
|
+
// Every payoff, in wall order: a card can plant two things (the ledger
|
|
307
|
+
// pays off at the cash and again at the initials), and both count.
|
|
308
|
+
payoffs[note.id] = heads;
|
|
307
309
|
}
|
|
308
310
|
for (const note of order) {
|
|
309
311
|
if (note.plants && !paysOff.has(note.id)) {
|