Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions .claude/skills/t27f/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ Read the spec through the t27 MCP server, which is already installed in Claude C
gHashTag/t27 checkout instead. Say which one you used.

From the source, take: `LENS_NAMES`, `LENS_QUESTIONS`, `STAGE_NAMES`, the four `*_WORDS`
vocabularies, the five `*_BEES` lists, `MAX_ACTIVE_BEES`, and the functions `intake_verdict`,
`map_verdict`, `initial_status`, `route`, `dispatch_allowed`, `required_evidence`,
`evidence_accepted`, `verify`, `counts_as_progress`, `needs_human_approval`, `action_allowed`,
`after_action`. Evaluate them as written; they are small and total.
vocabularies and `KEYWORD_MATCH`, the five `*_BEES` lists, `MAX_ACTIVE_BEES`, `GAP_BEE`, and the
functions `intake_verdict`, `map_verdict`, `initial_status`, `ground`, `route`, `dispatch_allowed`,
`bee_target`, `required_evidence`, `evidence_accepted`, `verify`, `counts_as_progress`,
`needs_human_approval`, `action_allowed`, `after_action`. Evaluate them as written; they are small and total.

Everything in `DETERMINISTIC_STEPS` is decided by those functions, never by your judgement.
You write only what `LLM_WRITES` names.
Expand All @@ -49,8 +49,8 @@ Apply `intake_verdict(has_topic, source_count)`:

## 2. Route

Count whole-word, case-insensitive hits of the brief against each `*_WORDS` list. Show the four
counts, then apply `route(content, engineering, research, sales)`. Name the domain and the Bee list
Count the brief's hits against each `*_WORDS` list exactly as `KEYWORD_MATCH` says. Show the four
counts and the words that hit, then apply `route(content, engineering, research, sales)`. Name the domain and the Bee list
it allows. Do not override the result; if it looks wrong, the vocabulary in the spec is what needs a
PR.

Expand All @@ -64,14 +64,21 @@ the sources. A claim with no source is not written; it becomes a gap.
Print a table of 27 rows, lens-major (`step_lens`, `step_stage`): step, lens, stage, lesson title,
source, status.

- For each lesson, `grounded` means at least one source supports it.
- Status from `initial_status(grounded, first_grounded)`. A GAP row says **GAP: needs a source on
<what>** and nothing else. Never invent filler to make the map look complete.
- For each lesson, `grounded` means at least one source from intake supports it. Sources you find
yourself are not intake; they arrive through the Bee task in step 5 and `ground`.
- A GAP row keeps its lesson title (titles are in `LLM_WRITES`); its source cell is empty.
- Status from `initial_status(grounded, first_grounded)`; `first_grounded` is the grounded lesson
with the lowest step. A GAP row's status cell says **GAP: needs a source on <what>** and nothing
else. Never invent filler to make the map look complete.
- Count grounded lessons per lens and report `map_verdict(...)`.

## 5. One Bee task

Pick the first OPEN lesson. Check `dispatch_allowed(active_bees)`. Choose one capability from the
Apply `bee_target(open_lessons)`. `TARGET_OPEN_LESSON`: take the first OPEN lesson.
`TARGET_FIRST_GAP` (nothing is OPEN, as when intake was `INTAKE_NO_SOURCES`): take the first GAP, the
capability is `GAP_BEE`, and the task is to find a source for it; once one is attached, apply
`ground`. `active_bees` counts the Bees of this journey still working; at the start it is 0. Check
`dispatch_allowed(active_bees)`. Choose one capability from the
domain's Bee list and write one task for it:

- goal (one sentence), input (the lesson and its sources), output (the artifact),
Expand All @@ -97,7 +104,9 @@ approved)`.

- Publish, send to a client, any payment, anything destructive: ask the user in chat and wait for a
clear yes. A yes covers that one action only.
- Publishing or sending needs a VERIFIED item. A draft is never published.
- Publishing needs a VERIFIED item. A draft is never published. Sending to a client also takes a
DRAFT, because a client approval is the only evidence that verifies a sales or content DELIVER
lesson; the draft stays a DRAFT (`after_action`) until that approval comes back.

## 8. Keep the three layers apart

Expand Down
86 changes: 76 additions & 10 deletions specs/t27f/topic_engine.t27
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ pub const DOMAIN_SALES : u8 = 4;
pub const DOMAIN_NAMES : str = "general,content,engineering,research,sales";

/// Keyword vocabularies, comma separated. The host counts how many words of the brief appear in each
/// list (whole words, case-insensitive) and passes the four counts to route().
/// list and passes the four counts to route(). How a word is matched is KEYWORD_MATCH, not a guess:
/// "List a villa" does not hit "listing", and "sea-view" is the two tokens "sea" and "view".
pub const KEYWORD_MATCH : str = "lowercase the brief; split it on every character that is not a-z or 0-9; a token is a hit when it equals a list word exactly; no stems, no plurals; a word repeated in the brief counts each time";
pub const CONTENT_WORDS : str = "blog,post,campaign,reel,video,channel,audience,content,newsletter";
pub const ENGINEERING_WORDS : str = "fpga,rtl,verilog,compiler,spec,code,bug,test,firmware,api";
pub const RESEARCH_WORDS : str = "paper,research,experiment,dataset,proof,preprint,result,benchmark,gf16";
Expand All @@ -80,6 +82,13 @@ pub const SALES_BEES : str = "researcher,copywriter,listing-builder";
pub const MAX_ACTIVE_BEES : u8 = 3;
pub const BEES_PER_STEP : u8 = 1;

/// What the one Bee works on. While a lesson is OPEN, that lesson. When none is (every lesson is a
/// GAP, as with a brief that brought no source), the first GAP, and the task is to find a source for
/// it: GAP_BEE, which every *_BEES list above carries, judged by required_evidence(STAGE_LEARN, ...).
pub const TARGET_OPEN_LESSON : u8 = 0;
pub const TARGET_FIRST_GAP : u8 = 1;
pub const GAP_BEE : str = "researcher";

/// Evidence kinds a Bee or a person can attach to a draft.
pub const EVIDENCE_NONE : u8 = 0;
pub const EVIDENCE_SOURCE_LINK : u8 = 1;
Expand Down Expand Up @@ -161,7 +170,8 @@ pub fn map_verdict(grounded_structure: u8, grounded_process: u8, grounded_value:
return MAP_PARTIAL;
}

/// The status a lesson starts in. Only the first grounded lesson of the map is open.
/// The status a lesson starts in. Only the first grounded lesson of the map is open: the grounded
/// lesson with the lowest step, in the lens-major order of step_lens.
pub fn initial_status(grounded: bool, first_grounded: bool) -> u8 {
if (grounded == false) {
return STATUS_GAP;
Expand Down Expand Up @@ -201,6 +211,22 @@ pub fn dispatch_allowed(active_bees: u8) -> bool {
return active_bees < MAX_ACTIVE_BEES;
}

/// Where the one Bee task goes, from how many lessons are OPEN right now.
pub fn bee_target(open_lessons: u8) -> u8 {
if (open_lessons == 0) {
return TARGET_FIRST_GAP;
}
return TARGET_OPEN_LESSON;
}

/// A GAP that has gained a source starts over as a grounded lesson; every other status stays.
pub fn ground(status: u8, first_grounded: bool) -> u8 {
if (status == STATUS_GAP) {
return initial_status(true, first_grounded);
}
return status;
}

/// The evidence a stage asks for first, per domain.
pub fn required_evidence(stage: u8, domain: u8) -> u8 {
if (stage == STAGE_LEARN) {
Expand Down Expand Up @@ -276,8 +302,10 @@ pub fn needs_human_approval(action: u8) -> bool {
return action >= ACTION_PUBLISH;
}

/// Whether an action may run now. High-impact actions need approval; publishing and sending to a
/// client need a VERIFIED item as well.
/// Whether an action may run now. High-impact actions need approval. Publishing needs a VERIFIED
/// item. Sending to a client takes a VERIFIED item, or a DRAFT sent so the client can approve it:
/// for sales and content, DELIVER is verified only by EVIDENCE_CLIENT_APPROVAL, and a client cannot
/// approve what never reached them.
pub fn action_allowed(status: u8, action: u8, approved: bool) -> bool {
if (action == ACTION_DRAFT_ONLY) {
return status >= STATUS_OPEN && status <= STATUS_PUBLISHED;
Expand All @@ -288,15 +316,19 @@ pub fn action_allowed(status: u8, action: u8, approved: bool) -> bool {
if (approved == false) {
return false;
}
if (action == ACTION_PUBLISH || action == ACTION_SEND_TO_CLIENT) {
if (action == ACTION_PUBLISH) {
return status == STATUS_VERIFIED;
}
if (action == ACTION_SEND_TO_CLIENT) {
return status == STATUS_DRAFT || status == STATUS_VERIFIED;
}
return action <= ACTION_DESTRUCTIVE;
}

/// The status after an action: a VERIFIED item that is published or sent becomes PUBLISHED.
/// The status after an action: a VERIFIED item that is published or sent becomes PUBLISHED. A DRAFT
/// sent for the client's approval stays a DRAFT until that approval comes back as evidence.
pub fn after_action(status: u8, action: u8, approved: bool) -> u8 {
if (action_allowed(status, action, approved)) {
if (action_allowed(status, action, approved) && status == STATUS_VERIFIED) {
if (action == ACTION_PUBLISH || action == ACTION_SEND_TO_CLIENT) {
return STATUS_PUBLISHED;
}
Expand Down Expand Up @@ -353,9 +385,9 @@ test dispatch_is_capped {
}

test demo_villa_sale_brief {
// "List a sea-view villa for a client and draft the sale post": villa, client, sale, listing -> 4
// sales hits; post -> 1 content hit
var domain : u8 = route(1, 0, 0, 4);
// "List a sea-view villa for a client and draft the sale post", counted by KEYWORD_MATCH: villa,
// client, sale -> 3 sales hits ("list" is not "listing"); post -> 1 content hit
var domain : u8 = route(1, 0, 0, 3);
assert domain == DOMAIN_SALES;
assert required_evidence(STAGE_LEARN, domain) == EVIDENCE_SOURCE_LINK;
assert required_evidence(STAGE_PRACTICE, domain) == EVIDENCE_REVIEW;
Expand All @@ -366,6 +398,40 @@ test demo_villa_sale_brief {
// sending to the client still needs a human approval at send time
assert action_allowed(STATUS_VERIFIED, ACTION_SEND_TO_CLIENT, false) == false;
assert after_action(STATUS_VERIFIED, ACTION_SEND_TO_CLIENT, true) == STATUS_PUBLISHED;
// the brief brought no source: all 27 lessons are GAPs, nothing is OPEN, so the one Bee task is
// to find a source for the first GAP, at LEARN
assert intake_verdict(true, 0) == INTAKE_NO_SOURCES;
assert initial_status(false, true) == STATUS_GAP;
assert map_verdict(0, 0, 0) == MAP_NEED_SOURCES;
assert bee_target(0) == TARGET_FIRST_GAP;
assert step_stage(0) == STAGE_LEARN;
assert required_evidence(step_stage(0), domain) == EVIDENCE_SOURCE_LINK;
}

test client_approves_a_draft_it_was_sent {
// the sale post is a DELIVER draft; only the client's yes verifies it, so it must be sendable
// as a draft, with the user's approval, and stay a draft until that yes comes back
var domain : u8 = DOMAIN_SALES;
assert action_allowed(STATUS_DRAFT, ACTION_SEND_TO_CLIENT, false) == false;
assert action_allowed(STATUS_DRAFT, ACTION_SEND_TO_CLIENT, true);
assert after_action(STATUS_DRAFT, ACTION_SEND_TO_CLIENT, true) == STATUS_DRAFT;
assert verify(STATUS_DRAFT, STAGE_DELIVER, domain, EVIDENCE_CLIENT_APPROVAL) == STATUS_VERIFIED;
// a draft is still never published, and nothing below a draft is sent
assert action_allowed(STATUS_DRAFT, ACTION_PUBLISH, true) == false;
assert action_allowed(STATUS_OPEN, ACTION_SEND_TO_CLIENT, true) == false;
assert action_allowed(STATUS_GAP, ACTION_SEND_TO_CLIENT, true) == false;
assert action_allowed(STATUS_PUBLISHED, ACTION_SEND_TO_CLIENT, true) == false;
}

test bee_goes_to_the_open_lesson_or_the_first_gap {
assert bee_target(0) == TARGET_FIRST_GAP;
assert bee_target(1) == TARGET_OPEN_LESSON;
assert bee_target(LESSON_COUNT) == TARGET_OPEN_LESSON;
// a gap that gains a source becomes a grounded lesson: OPEN if it is the first, else LOCKED
assert ground(STATUS_GAP, true) == STATUS_OPEN;
assert ground(STATUS_GAP, false) == STATUS_LOCKED;
assert ground(STATUS_DRAFT, true) == STATUS_DRAFT;
assert ground(STATUS_VERIFIED, false) == STATUS_VERIFIED;
}

test demo_gf16_research_topic {
Expand Down
Loading