From 4864d52fbca46dea8c5602fb6ad6bf91d8946d6b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Nov 2026 03:48:25 +0000 Subject: [PATCH 1/4] feat(schemas): bundled semantic rules with evals Type descriptions for strict_ost (from Continuous Discovery Habits) and strategy_general, and semantic rules on all four bundled schemas: - strict_ost: outcome-product-outcome, opportunity-refines-parent, opportunities-distinct, solution-addresses-opportunity, solution-serves-outcome, assumption-category-fits - strategy_general: goals-distinct, goals-cover-parent, goal-advances-parent, opportunity-serves-ancestors, problem-statement-names-no-solution - knowledge_wiki: concept-or-entity, synthesis-connects - okf: okf-description-informative Each rule has an eval with near-misses and contrast pairs; rules that judge children get one small tree per scenario. The judge now gets a type's description when its oneOf entry is a $ref (strategy_general, knowledge_wiki). Base fields whose description only restated the name (title, summary, tags) drop theirs. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019XphpcQHEAB9KwJ5Ltiomb --- docs/schemas.md | 6 +- docs/semantic-rules.md | 10 + evals/semantic/assumption-category-fits.json5 | 28 +++ evals/semantic/concept-or-entity.json5 | 31 ++++ evals/semantic/fixtures.json5 | 6 + evals/semantic/fixtures/knowledge-wiki.jsonl | 33 ++++ evals/semantic/fixtures/okf.jsonl | 16 ++ evals/semantic/fixtures/ost-trees.jsonl | 172 ++++++++++++++++++ evals/semantic/fixtures/strategy-trees.jsonl | 148 +++++++++++++++ evals/semantic/goal-advances-parent.json5 | 31 ++++ evals/semantic/goals-cover-parent.json5 | 29 +++ evals/semantic/goals-distinct.json5 | 31 ++++ .../okf-description-informative.json5 | 27 +++ evals/semantic/opportunities-distinct.json5 | 32 ++++ .../semantic/opportunity-refines-parent.json5 | 35 ++++ .../opportunity-serves-ancestors.json5 | 32 ++++ evals/semantic/outcome-product-outcome.json5 | 31 ++++ .../problem-statement-names-no-solution.json5 | 31 ++++ .../solution-addresses-opportunity.json5 | 34 ++++ evals/semantic/solution-serves-outcome.json5 | 33 ++++ evals/semantic/synthesis-connects.json5 | 30 +++ plugin/.claude-plugin/plugin.json | 2 +- .../references/schema-authoring.md | 1 + schemas/_knowledge_wiki.json | 16 ++ schemas/_okf.json | 7 + schemas/_ost_strict.json | 108 ++++++++++- schemas/_sctx_base.json | 9 +- schemas/_strategy_general.json | 85 +++++++++ schemas/knowledge_wiki.json | 2 +- schemas/strict_ost.json | 4 + src/judge/context.ts | 9 +- tests/judge/schema-descriptions.test.ts | 10 +- tests/validate/semantic-validation.test.ts | 9 +- 33 files changed, 1068 insertions(+), 20 deletions(-) create mode 100644 evals/semantic/assumption-category-fits.json5 create mode 100644 evals/semantic/concept-or-entity.json5 create mode 100644 evals/semantic/fixtures/knowledge-wiki.jsonl create mode 100644 evals/semantic/fixtures/okf.jsonl create mode 100644 evals/semantic/fixtures/ost-trees.jsonl create mode 100644 evals/semantic/fixtures/strategy-trees.jsonl create mode 100644 evals/semantic/goal-advances-parent.json5 create mode 100644 evals/semantic/goals-cover-parent.json5 create mode 100644 evals/semantic/goals-distinct.json5 create mode 100644 evals/semantic/okf-description-informative.json5 create mode 100644 evals/semantic/opportunities-distinct.json5 create mode 100644 evals/semantic/opportunity-refines-parent.json5 create mode 100644 evals/semantic/opportunity-serves-ancestors.json5 create mode 100644 evals/semantic/outcome-product-outcome.json5 create mode 100644 evals/semantic/problem-statement-names-no-solution.json5 create mode 100644 evals/semantic/solution-addresses-opportunity.json5 create mode 100644 evals/semantic/solution-serves-outcome.json5 create mode 100644 evals/semantic/synthesis-connects.json5 diff --git a/docs/schemas.md b/docs/schemas.md index a1c19b65..f9965137 100644 --- a/docs/schemas.md +++ b/docs/schemas.md @@ -33,6 +33,8 @@ Resolution order: space `schema` > global `schema`. Schema resolution fails if n ## Bundled schemas +Each bundled schema has [semantic rules](semantic-rules.md), which run only with `--semantic`. `sctx schemas show ` lists them. + ### `strategy_general.json` Flexible planning schema spanning strategy + OST-like flow. @@ -57,9 +59,11 @@ Main types: This schema reuses shared structural defs from partials via `$ref` and imports its hierarchy and rules from `_ost_strict` via `$metadata.imports`. +Its type descriptions and semantic rules follow Teresa Torres' *Continuous Discovery Habits* (2021). + ### `knowledge_wiki.json` -Flat, LLM-maintained knowledge base (after Karpathy's LLM Wiki pattern). No hierarchy or required parent links; wikilinks between any pages are valid. Validates structure and provenance, not meaning. +Flat, LLM-maintained knowledge base (after Karpathy's LLM Wiki pattern). No hierarchy or required parent links; wikilinks between any pages are valid. Validates structure and provenance. Main types: - `source` diff --git a/docs/semantic-rules.md b/docs/semantic-rules.md index 0fc415ef..a02a29a4 100644 --- a/docs/semantic-rules.md +++ b/docs/semantic-rules.md @@ -146,6 +146,16 @@ Scope sends node data only, never the verdicts of other rules, and [gating](rule A global rule cannot have a `semantic` part, so it has no scope. +The bundled schemas use scope in three ways, which are good models for your own rules: + +| Check | Attach the rule to | Scope | Bundled example | +|---|---|---|---| +| The children are distinct, or cover the node | The parent, with a `when` that needs two or more children | `children` | `opportunities-distinct`, `goals-cover-parent` | +| The node fits its parent | The child | `parent` | `opportunity-refines-parent`, `solution-addresses-opportunity` | +| The node serves the intent of the levels above | The child | `ancestors` | `solution-serves-outcome`, `opportunity-serves-ancestors` | + +The rules are in the bundled partials, such as `schemas/_ost_strict.json`. + ## Input size and trimming The input for one evaluated node is capped at about 32,000 estimated tokens. Over the cap, structured-context cuts node bodies, longest related bodies first, and marks each cut with `[RULE_EVALUATION_CONTENT_TRIMMED n chars]`. If the input still does not fit, the rule reports a semantic evaluation error. A run that trims content says so in its summary. diff --git a/evals/semantic/assumption-category-fits.json5 b/evals/semantic/assumption-category-fits.json5 new file mode 100644 index 00000000..fd5f741d --- /dev/null +++ b/evals/semantic/assumption-category-fits.json5 @@ -0,0 +1,28 @@ +{ + space: 'ost-trees', + expected: { + 'assumption-category-fits': { + 'Test 01: Teams want to see who edited a page.md': 'pass', + 'Test 02: Storing every revision costs less than $0.10 per seat a month.md': 'pass', + 'Test 03: We can diff rich-text documents in under 200 ms.md': 'pass', + 'Test 04: Users can find the history panel without help.md': 'pass', + "Test 05: Showing who edited what won't be used to monitor employees.md": 'pass', + 'Test 06: Our sync engine can merge offline edits without conflicts.md': 'pass', + 'Test 07: Admins understand the retention settings on first read.md': 'pass', + 'Test 08: Users can find the restore button without help.md': 'fail', + // Cost is viability, not whether the team can build it. + 'Test 09: Keeping 90 days of revisions costs under $0.10 per seat a month.md': 'fail', + 'Test 10: Teams want to restore old versions.md': 'fail', + 'Test 11: Sales can sell version history as an enterprise add-on.md': 'fail', + 'Test 12: We can build live cursors on our current websocket stack.md': 'fail', + "Test 13: Users will notice the 'restored' banner and understand what changed.md": 'pass', + // Legal constraints on the business are viability. + 'Test 14: Legal will approve keeping deleted content for 90 days.md': 'pass', + 'Test 15: Teams will keep using history after the first month.md': 'pass', + "Test 16: Managers won't use edit history to rank their staff.md": 'pass', + // Mentions a survey, but the assumption is ethical. + "Test 17: Managers won't use edit history to rank their staff, judged by a survey.md": 'fail', + 'Test 18: Users will pay for longer history.md': 'fail', + }, + }, +} diff --git a/evals/semantic/concept-or-entity.json5 b/evals/semantic/concept-or-entity.json5 new file mode 100644 index 00000000..0f220e73 --- /dev/null +++ b/evals/semantic/concept-or-entity.json5 @@ -0,0 +1,31 @@ +{ + space: 'knowledge-wiki', + expected: { + 'concept-or-entity': { + 'Affordance.md': 'pass', + 'Retrieval-augmented generation.md': 'pass', + // A named method is still a concept. + 'Opportunity solution tree.md': 'pass', + 'Jobs to be done.md': 'pass', + 'Anthropic.md': 'pass', + 'GPT-4.md': 'pass', + // One specific book. + 'Continuous Discovery Habits.md': 'pass', + 'Teresa Torres.md': 'pass', + 'Notion.md': 'fail', + 'Don Norman.md': 'fail', + 'Technical debt.md': 'fail', + // A generic design, despite the proper-sounding name. + 'Transformer architecture.md': 'fail', + 'Kanban.md': 'pass', + // One company's system, though it inspired a concept. + 'Toyota Production System.md': 'pass', + 'Claude.md': 'fail', + 'Large language model.md': 'fail', + // One specific document. + 'Agile Manifesto.md': 'fail', + // A method, though it has a proper name. + 'Scrum.md': 'fail', + }, + }, +} diff --git a/evals/semantic/fixtures.json5 b/evals/semantic/fixtures.json5 index 5f17d2a9..89001582 100644 --- a/evals/semantic/fixtures.json5 +++ b/evals/semantic/fixtures.json5 @@ -2,5 +2,11 @@ spaces: [ // A small product's opportunity solution tree. { name: 'strict-ost', path: 'fixtures/strict-ost.jsonl', schema: 'strict_ost.json' }, + // Trees for the strict_ost rules. A rule that judges a node's children gets one small tree per scenario. + { name: 'ost-trees', path: 'fixtures/ost-trees.jsonl', schema: 'strict_ost.json' }, + // Trees for the strategy_general rules, built the same way. + { name: 'strategy-trees', path: 'fixtures/strategy-trees.jsonl', schema: 'strategy_general.json' }, + { name: 'knowledge-wiki', path: 'fixtures/knowledge-wiki.jsonl', schema: 'knowledge_wiki.json' }, + { name: 'okf', path: 'fixtures/okf.jsonl', schema: 'okf.json' }, ], } diff --git a/evals/semantic/fixtures/knowledge-wiki.jsonl b/evals/semantic/fixtures/knowledge-wiki.jsonl new file mode 100644 index 00000000..31e6fa7a --- /dev/null +++ b/evals/semantic/fixtures/knowledge-wiki.jsonl @@ -0,0 +1,33 @@ +{"id": "Affordance.md", "title": "Affordance", "type": "concept", "schemaData": {"title": "Affordance", "type": "concept", "summary": "A property of an object that signals how it can be used"}, "linkTargets": ["Affordance"]} +{"id": "Retrieval-augmented generation.md", "title": "Retrieval-augmented generation", "type": "concept", "schemaData": {"title": "Retrieval-augmented generation", "type": "concept", "summary": "Answering with a language model that first retrieves relevant documents"}, "linkTargets": ["Retrieval-augmented generation"]} +{"id": "Opportunity solution tree.md", "title": "Opportunity solution tree", "type": "concept", "schemaData": {"title": "Opportunity solution tree", "type": "concept", "summary": "A map from a desired outcome to opportunities, solutions and assumption tests"}, "linkTargets": ["Opportunity solution tree"]} +{"id": "Jobs to be done.md", "title": "Jobs to be done", "type": "concept", "schemaData": {"title": "Jobs to be done", "type": "concept", "summary": "A theory that customers hire products to make progress in their lives"}, "linkTargets": ["Jobs to be done"]} +{"id": "Anthropic.md", "title": "Anthropic", "type": "entity", "schemaData": {"title": "Anthropic", "type": "entity", "summary": "AI safety company founded in 2021"}, "linkTargets": ["Anthropic"]} +{"id": "GPT-4.md", "title": "GPT-4", "type": "entity", "schemaData": {"title": "GPT-4", "type": "entity", "summary": "OpenAI's large language model, released in March 2023"}, "linkTargets": ["GPT-4"]} +{"id": "Continuous Discovery Habits.md", "title": "Continuous Discovery Habits", "type": "entity", "schemaData": {"title": "Continuous Discovery Habits", "type": "entity", "summary": "Teresa Torres' 2021 book on product discovery"}, "linkTargets": ["Continuous Discovery Habits"]} +{"id": "Teresa Torres.md", "title": "Teresa Torres", "type": "entity", "schemaData": {"title": "Teresa Torres", "type": "entity", "summary": "Product discovery coach and author"}, "linkTargets": ["Teresa Torres"]} +{"id": "Notion.md", "title": "Notion", "type": "concept", "schemaData": {"title": "Notion", "type": "concept", "summary": "The note-taking and wiki app made by Notion Labs"}, "linkTargets": ["Notion"]} +{"id": "Don Norman.md", "title": "Don Norman", "type": "concept", "schemaData": {"title": "Don Norman", "type": "concept", "summary": "Design researcher, author of The Design of Everyday Things"}, "linkTargets": ["Don Norman"]} +{"id": "Technical debt.md", "title": "Technical debt", "type": "entity", "schemaData": {"title": "Technical debt", "type": "entity", "summary": "The future cost of choosing a quick solution now"}, "linkTargets": ["Technical debt"]} +{"id": "Transformer architecture.md", "title": "Transformer architecture", "type": "entity", "schemaData": {"title": "Transformer architecture", "type": "entity", "summary": "Neural network design built on self-attention"}, "linkTargets": ["Transformer architecture"]} +{"id": "Kanban.md", "title": "Kanban", "type": "concept", "schemaData": {"title": "Kanban", "type": "concept", "summary": "A method for managing work by showing it on a board and limiting work in progress"}, "linkTargets": ["Kanban"]} +{"id": "Toyota Production System.md", "title": "Toyota Production System", "type": "entity", "schemaData": {"title": "Toyota Production System", "type": "entity", "summary": "Toyota's manufacturing system, where lean and kanban began"}, "linkTargets": ["Toyota Production System"]} +{"id": "Claude.md", "title": "Claude", "type": "concept", "schemaData": {"title": "Claude", "type": "concept", "summary": "Anthropic's family of AI models"}, "linkTargets": ["Claude"]} +{"id": "Large language model.md", "title": "Large language model", "type": "entity", "schemaData": {"title": "Large language model", "type": "entity", "summary": "A neural network trained on large text corpora to predict text"}, "linkTargets": ["Large language model"]} +{"id": "Agile Manifesto.md", "title": "Agile Manifesto", "type": "concept", "schemaData": {"title": "Agile Manifesto", "type": "concept", "summary": "The 2001 statement of values for agile software development, signed by 17 developers"}, "linkTargets": ["Agile Manifesto"]} +{"id": "Scrum.md", "title": "Scrum", "type": "entity", "schemaData": {"title": "Scrum", "type": "entity", "summary": "A framework for teams to deliver work in fixed-length sprints"}, "linkTargets": ["Scrum"]} +{"id": "Affordances and feedback in interface design.md", "title": "Affordances and feedback in interface design", "type": "synthesis", "schemaData": {"title": "Affordances and feedback in interface design", "type": "synthesis", "summary": "How affordances and feedback together explain why interfaces feel intuitive", "content": "Norman's [[Affordance]] explains how people know what they can do; his feedback principle explains how they know it worked. Nielsen's usability heuristics restate both. Interfaces feel intuitive when both hold, and confusing when either fails: a flat button lacks the affordance, and a silent save lacks the feedback."}, "linkTargets": ["Affordances and feedback in interface design"], "content": "Norman's [[Affordance]] explains how people know what they can do; his feedback principle explains how they know it worked. Nielsen's usability heuristics restate both. Interfaces feel intuitive when both hold, and confusing when either fails: a flat button lacks the affordance, and a silent save lacks the feedback."} +{"id": "Opportunity solution trees and OKRs compared.md", "title": "Opportunity solution trees and OKRs compared", "type": "synthesis", "schemaData": {"title": "Opportunity solution trees and OKRs compared", "type": "synthesis", "summary": "When to use an opportunity solution tree, and when OKRs", "content": "OKRs (from Doerr) set what to achieve each quarter. Opportunity solution trees (from Torres) explore how. A key result maps onto an outcome; opportunities have no OKR equivalent. Use OKRs to align teams, and a tree inside each team."}, "linkTargets": ["Opportunity solution trees and OKRs compared"], "content": "OKRs (from Doerr) set what to achieve each quarter. Opportunity solution trees (from Torres) explore how. A key result maps onto an outcome; opportunities have no OKR equivalent. Use OKRs to align teams, and a tree inside each team."} +{"id": "Why retrieval beats fine-tuning for fresh knowledge.md", "title": "Why retrieval beats fine-tuning for fresh knowledge", "type": "synthesis", "schemaData": {"title": "Why retrieval beats fine-tuning for fresh knowledge", "type": "synthesis", "summary": "Retrieval keeps answers current; fine-tuning bakes in a snapshot", "content": "Lewis et al. (2020) introduced [[Retrieval-augmented generation]]. Ovadia et al. (2024) found fine-tuning rarely teaches new facts. Vendor guides agree. Together they suggest retrieval for facts that change, and fine-tuning for style and format."}, "linkTargets": ["Why retrieval beats fine-tuning for fresh knowledge"], "content": "Lewis et al. (2020) introduced [[Retrieval-augmented generation]]. Ovadia et al. (2024) found fine-tuning rarely teaches new facts. Vendor guides agree. Together they suggest retrieval for facts that change, and fine-tuning for style and format."} +{"id": "Lean and kanban share their roots.md", "title": "Lean and kanban share their roots", "type": "synthesis", "schemaData": {"title": "Lean and kanban share their roots", "type": "synthesis", "summary": "Kanban and lean both come from the Toyota Production System", "content": "The [[Toyota Production System]] used cards to limit work in progress. Anderson adapted this as [[Kanban]] for software, and the Poppendiecks adapted the wider system as lean software development. Both keep the same core idea: limit work in progress to expose waste."}, "linkTargets": ["Lean and kanban share their roots"], "content": "The [[Toyota Production System]] used cards to limit work in progress. Anderson adapted this as [[Kanban]] for software, and the Poppendiecks adapted the wider system as lean software development. Both keep the same core idea: limit work in progress to expose waste."} +{"id": "Summary of The Design of Everyday Things.md", "title": "Summary of The Design of Everyday Things", "type": "synthesis", "schemaData": {"title": "Summary of The Design of Everyday Things", "type": "synthesis", "summary": "Chapter-by-chapter summary of the book", "content": "Chapter 1 covers affordances and signifiers. Chapter 2 covers the psychology of everyday actions. Chapter 3 covers knowledge in the head and in the world. Chapter 4 covers constraints. Chapter 5 covers human error."}, "linkTargets": ["Summary of The Design of Everyday Things"], "content": "Chapter 1 covers affordances and signifiers. Chapter 2 covers the psychology of everyday actions. Chapter 3 covers knowledge in the head and in the world. Chapter 4 covers constraints. Chapter 5 covers human error."} +{"id": "Notes from Torres' talk at Mind the Product.md", "title": "Notes from Torres' talk at Mind the Product", "type": "synthesis", "schemaData": {"title": "Notes from Torres' talk at Mind the Product", "type": "synthesis", "summary": "Key points from one conference talk", "content": "Torres said teams should interview customers every week, map opportunities, and test assumptions rather than whole ideas. She showed three example trees and took questions on prioritisation."}, "linkTargets": ["Notes from Torres' talk at Mind the Product"], "content": "Torres said teams should interview customers every week, map opportunities, and test assumptions rather than whole ideas. She showed three example trees and took questions on prioritisation."} +{"id": "Product discovery books.md", "title": "Product discovery books", "type": "synthesis", "schemaData": {"title": "Product discovery books", "type": "synthesis", "summary": "Books worth reading on product discovery", "content": "- Continuous Discovery Habits (Torres)\n- Inspired (Cagan)\n- The Mom Test (Fitzpatrick)\n- Testing Business Ideas (Bland and Osterwalder)"}, "linkTargets": ["Product discovery books"], "content": "- Continuous Discovery Habits (Torres)\n- Inspired (Cagan)\n- The Mom Test (Fitzpatrick)\n- Testing Business Ideas (Bland and Osterwalder)"} +{"id": "Karpathy's LLM wiki post.md", "title": "Karpathy's LLM wiki post", "type": "synthesis", "schemaData": {"title": "Karpathy's LLM wiki post", "type": "synthesis", "summary": "What the post proposes", "content": "Karpathy proposes that a language model maintain a wiki of markdown pages from raw sources: it ingests a source, updates the pages it touches, and answers questions from the wiki. He suggests an index page and a log."}, "linkTargets": ["Karpathy's LLM wiki post"], "content": "Karpathy proposes that a language model maintain a wiki of markdown pages from raw sources: it ingests a source, updates the pages it touches, and answers questions from the wiki. He suggests an index page and a log."} +{"id": "Continuous discovery as jobs to be done.md", "title": "Continuous discovery as jobs to be done", "type": "synthesis", "schemaData": {"title": "Continuous discovery as jobs to be done", "type": "synthesis", "summary": "Torres' opportunities read as customer jobs", "content": "This page reads [[Continuous Discovery Habits]] through [[Jobs to be done]]: an opportunity is a job's struggling moment, and the tree is a map of jobs under one outcome. The book does not use the term, but its interview method finds the same moments."}, "linkTargets": ["Continuous discovery as jobs to be done"], "content": "This page reads [[Continuous Discovery Habits]] through [[Jobs to be done]]: an opportunity is a job's struggling moment, and the tree is a map of jobs under one outcome. The book does not use the term, but its interview method finds the same moments."} +{"id": "Two articles on technical debt.md", "title": "Two articles on technical debt", "type": "synthesis", "schemaData": {"title": "Two articles on technical debt", "type": "synthesis", "summary": "Fowler and Cunningham on technical debt", "content": "Fowler's article describes a quadrant of reckless, prudent, deliberate and inadvertent debt. Cunningham's article coined the metaphor in 1992 to explain refactoring to his manager."}, "linkTargets": ["Two articles on technical debt"], "content": "Fowler's article describes a quadrant of reckless, prudent, deliberate and inadvertent debt. Cunningham's article coined the metaphor in 1992 to explain refactoring to his manager."} +{"id": "Which model to pick for coding.md", "title": "Which model to pick for coding", "type": "synthesis", "schemaData": {"title": "Which model to pick for coding", "type": "synthesis", "summary": "A comparison of three models for coding work, with a recommendation", "content": "| Model | Price | Context | Coding benchmark |\n|---|---|---|---|\n| A | $3 | 200k | 72% |\n| B | $5 | 1M | 70% |\n| C | $1 | 128k | 61% |\n\nPick A for most coding, B for very large codebases, and C for cheap bulk edits."}, "linkTargets": ["Which model to pick for coding"], "content": "| Model | Price | Context | Coding benchmark |\n|---|---|---|---|\n| A | $3 | 200k | 72% |\n| B | $5 | 1M | 70% |\n| C | $1 | 128k | 61% |\n\nPick A for most coding, B for very large codebases, and C for cheap bulk edits."} +{"id": "Attention Is All You Need.md", "title": "Attention Is All You Need", "type": "synthesis", "schemaData": {"title": "Attention Is All You Need", "type": "synthesis", "summary": "Summary of the 2017 transformer paper", "content": "The paper replaces recurrence with self-attention, proposes multi-head attention and positional encodings, and reports state-of-the-art translation results at lower training cost. See also [[Transformer architecture]]."}, "linkTargets": ["Attention Is All You Need"], "content": "The paper replaces recurrence with self-attention, proposes multi-head attention and positional encodings, and reports state-of-the-art translation results at lower training cost. See also [[Transformer architecture]]."} +{"id": "Open questions about context engineering.md", "title": "Open questions about context engineering", "type": "synthesis", "schemaData": {"title": "Open questions about context engineering", "type": "synthesis", "summary": "Questions raised across several posts", "content": "- How much context is too much?\n- Should tools return summaries or raw data?\n- When does a long context beat retrieval?\n- Who maintains shared prompts?"}, "linkTargets": ["Open questions about context engineering"], "content": "- How much context is too much?\n- Should tools return summaries or raw data?\n- When does a long context beat retrieval?\n- Who maintains shared prompts?"} +{"id": "Technical debt is a cost of delay.md", "title": "Technical debt is a cost of delay", "type": "synthesis", "schemaData": {"title": "Technical debt is a cost of delay", "type": "synthesis", "summary": "Technical debt as cost of delay", "content": "[[Technical debt]] slows every later change. That slowdown is a [[Cost of delay]] spread across future work, so debt can be prioritised in the same units as features."}, "linkTargets": ["Technical debt is a cost of delay"], "content": "[[Technical debt]] slows every later change. That slowdown is a [[Cost of delay]] spread across future work, so debt can be prioritised in the same units as features."} +{"id": "Why studies disagree on remote work productivity.md", "title": "Why studies disagree on remote work productivity", "type": "synthesis", "schemaData": {"title": "Why studies disagree on remote work productivity", "type": "synthesis", "summary": "The studies measure different things", "content": "Bloom (2015) found a 13% gain in a call centre; Gibbs (2021) found a loss among IT professionals. Bloom measured calls handled; Gibbs measured output per hour worked. The disagreement comes from what each counts as productivity."}, "linkTargets": ["Why studies disagree on remote work productivity"], "content": "Bloom (2015) found a 13% gain in a call centre; Gibbs (2021) found a loss among IT professionals. Bloom measured calls handled; Gibbs measured output per hour worked. The disagreement comes from what each counts as productivity."} diff --git a/evals/semantic/fixtures/okf.jsonl b/evals/semantic/fixtures/okf.jsonl new file mode 100644 index 00000000..0e0a7236 --- /dev/null +++ b/evals/semantic/fixtures/okf.jsonl @@ -0,0 +1,16 @@ +{"id": "Weekly active teams.md", "title": "Weekly active teams", "type": "Metric", "schemaData": {"title": "Weekly active teams", "type": "Metric", "description": "Count of teams with at least two members active in a calendar week, from the events table"}, "linkTargets": ["Weekly active teams"]} +{"id": "orders.md", "title": "orders", "type": "Table", "schemaData": {"title": "orders", "type": "Table", "description": "One row per customer order, with totals in AUD, updated hourly from Shopify"}, "linkTargets": ["orders"]} +{"id": "Incident response.md", "title": "Incident response", "type": "Playbook", "schemaData": {"title": "Incident response", "type": "Playbook", "description": "The steps on-call engineers follow, from the first page to the post-incident review"}, "linkTargets": ["Incident response"]} +{"id": "Churn.md", "title": "Churn", "type": "Glossary Term", "schemaData": {"title": "Churn", "type": "Glossary Term", "description": "Share of paying accounts that cancel in a month"}, "linkTargets": ["Churn"]} +{"id": "Monthly revenue.md", "title": "Monthly revenue", "type": "Metric", "schemaData": {"title": "Monthly revenue", "type": "Metric", "description": "Monthly revenue"}, "linkTargets": ["Monthly revenue"]} +{"id": "customers.md", "title": "customers", "type": "Table", "schemaData": {"title": "customers", "type": "Table", "description": "The customers table"}, "linkTargets": ["customers"]} +{"id": "Sales overview.md", "title": "Sales overview", "type": "Dashboard", "schemaData": {"title": "Sales overview", "type": "Dashboard", "description": "A dashboard"}, "linkTargets": ["Sales overview"]} +{"id": "Database failover.md", "title": "Database failover", "type": "Runbook", "schemaData": {"title": "Database failover", "type": "Runbook", "description": "TODO"}, "linkTargets": ["Database failover"]} +{"id": "Cache warm-up.md", "title": "Cache warm-up", "type": "Runbook", "schemaData": {"title": "Cache warm-up", "type": "Runbook", "description": "See Confluence"}, "linkTargets": ["Cache warm-up"]} +{"id": "events.md", "title": "events", "type": "Table", "schemaData": {"title": "events", "type": "Table", "description": "Product events"}, "linkTargets": ["events"]} +{"id": "Activation rate.md", "title": "Activation rate", "type": "Metric", "schemaData": {"title": "Activation rate", "type": "Metric", "description": "Share of new signups who create a project within 7 days"}, "linkTargets": ["Activation rate"]} +{"id": "fct_orders_daily.md", "title": "fct_orders_daily", "type": "Table", "schemaData": {"title": "fct_orders_daily", "type": "Table", "description": "Daily order facts"}, "linkTargets": ["fct_orders_daily"]} +{"id": "ARR.md", "title": "ARR", "type": "Glossary Term", "schemaData": {"title": "ARR", "type": "Glossary Term", "description": "Annual recurring revenue: the yearly value of active subscriptions, excluding one-off fees"}, "linkTargets": ["ARR"]} +{"id": "dim_customers.md", "title": "dim_customers", "type": "Table", "schemaData": {"title": "dim_customers", "type": "Table", "description": "Customer dimension built by dbt from Stripe and HubSpot; one row per paying customer, refreshed nightly"}, "linkTargets": ["dim_customers"]} +{"id": "Quarterly planning.md", "title": "Quarterly planning", "type": "Process", "schemaData": {"title": "Quarterly planning", "type": "Process", "description": "How we do quarterly planning"}, "linkTargets": ["Quarterly planning"]} +{"id": "Hiring loop.md", "title": "Hiring loop", "type": "Process", "schemaData": {"title": "Hiring loop", "type": "Process", "description": "Five interviews over two weeks, ending in a debrief where the panel decides"}, "linkTargets": ["Hiring loop"]} diff --git a/evals/semantic/fixtures/ost-trees.jsonl b/evals/semantic/fixtures/ost-trees.jsonl new file mode 100644 index 00000000..ab3a04f7 --- /dev/null +++ b/evals/semantic/fixtures/ost-trees.jsonl @@ -0,0 +1,172 @@ +{"id": "Help new teams reach their first shared document.md", "title": "Help new teams reach their first shared document", "type": "outcome", "schemaData": {"title": "Help new teams reach their first shared document", "type": "outcome", "status": "identified", "metric": "Increase % of new teams who co-edit a document in their first week"}, "linkTargets": ["Help new teams reach their first shared document"]} +{"id": "Increase weekly active teams.md", "title": "Increase weekly active teams", "type": "outcome", "schemaData": {"title": "Increase weekly active teams", "type": "outcome", "status": "identified", "metric": "% of trial teams active in their second week"}, "linkTargets": ["Increase weekly active teams"]} +{"id": "Make it easier to find past work.md", "title": "Make it easier to find past work", "type": "outcome", "schemaData": {"title": "Make it easier to find past work", "type": "outcome", "status": "identified", "metric": "Increase % of searches that end with a document opened"}, "linkTargets": ["Make it easier to find past work"]} +{"id": "Reduce time to first invoice sent.md", "title": "Reduce time to first invoice sent", "type": "outcome", "schemaData": {"title": "Reduce time to first invoice sent", "type": "outcome", "status": "identified", "metric": "Median time from signup to first invoice sent"}, "linkTargets": ["Reduce time to first invoice sent"]} +{"id": "More customers set up recurring payments.md", "title": "More customers set up recurring payments", "type": "outcome", "schemaData": {"title": "More customers set up recurring payments", "type": "outcome", "status": "identified", "metric": "% of active accounts with at least one recurring payment"}, "linkTargets": ["More customers set up recurring payments"]} +{"id": "Grow annual recurring revenue.md", "title": "Grow annual recurring revenue", "type": "outcome", "schemaData": {"title": "Grow annual recurring revenue", "type": "outcome", "status": "identified", "metric": "ARR from $4M to $6M by June"}, "linkTargets": ["Grow annual recurring revenue"]} +{"id": "Reduce churn.md", "title": "Reduce churn", "type": "outcome", "schemaData": {"title": "Reduce churn", "type": "outcome", "status": "identified", "metric": "Monthly logo churn below 2%"}, "linkTargets": ["Reduce churn"]} +{"id": "Win the mid-market segment.md", "title": "Win the mid-market segment", "type": "outcome", "schemaData": {"title": "Win the mid-market segment", "type": "outcome", "status": "identified", "metric": "Market share among companies with 50-500 seats"}, "linkTargets": ["Win the mid-market segment"]} +{"id": "Get more people using the export button.md", "title": "Get more people using the export button", "type": "outcome", "schemaData": {"title": "Get more people using the export button", "type": "outcome", "status": "identified", "metric": "Clicks on Export per week"}, "linkTargets": ["Get more people using the export button"]} +{"id": "Drive adoption of the new dashboard.md", "title": "Drive adoption of the new dashboard", "type": "outcome", "schemaData": {"title": "Drive adoption of the new dashboard", "type": "outcome", "status": "identified", "metric": "% of users who open the dashboard widget each week"}, "linkTargets": ["Drive adoption of the new dashboard"]} +{"id": "Launch the mobile app.md", "title": "Launch the mobile app", "type": "outcome", "schemaData": {"title": "Launch the mobile app", "type": "outcome", "status": "identified", "metric": "App live in both stores by Q3"}, "linkTargets": ["Launch the mobile app"]} +{"id": "Ship single sign-on.md", "title": "Ship single sign-on", "type": "outcome", "schemaData": {"title": "Ship single sign-on", "type": "outcome", "status": "identified", "metric": "SSO available on enterprise plans"}, "linkTargets": ["Ship single sign-on"]} +{"id": "Redesign onboarding.md", "title": "Redesign onboarding", "type": "outcome", "schemaData": {"title": "Redesign onboarding", "type": "outcome", "status": "identified", "metric": "New onboarding flow released to all signups"}, "linkTargets": ["Redesign onboarding"]} +{"id": "Increase the number of integrations.md", "title": "Increase the number of integrations", "type": "outcome", "schemaData": {"title": "Increase the number of integrations", "type": "outcome", "status": "identified", "metric": "Integrations in the marketplace from 12 to 30"}, "linkTargets": ["Increase the number of integrations"]} +{"id": "Protect revenue from small teams.md", "title": "Protect revenue from small teams", "type": "outcome", "schemaData": {"title": "Protect revenue from small teams", "type": "outcome", "status": "identified", "metric": "% of small teams that invite a second member within 14 days"}, "linkTargets": ["Protect revenue from small teams"]} +{"id": "Get teams using the new calendar view.md", "title": "Get teams using the new calendar view", "type": "outcome", "schemaData": {"title": "Get teams using the new calendar view", "type": "outcome", "status": "identified", "metric": "Calendar view released to all plans"}, "linkTargets": ["Get teams using the new calendar view"]} +{"id": "Help managers stay informed.md", "title": "Help managers stay informed", "type": "outcome", "schemaData": {"title": "Help managers stay informed", "type": "outcome", "status": "identified", "metric": "% of managers who open the weekly digest email"}, "linkTargets": ["Help managers stay informed"]} +{"id": "Customers stay with us longer.md", "title": "Customers stay with us longer", "type": "outcome", "schemaData": {"title": "Customers stay with us longer", "type": "outcome", "status": "identified", "metric": "12-month net revenue retention above 110%"}, "linkTargets": ["Customers stay with us longer"]} +{"id": "Grow second-week team activity.md", "title": "Grow second-week team activity", "type": "outcome", "schemaData": {"title": "Grow second-week team activity", "type": "outcome", "status": "identified", "metric": "% of trial teams active in their second week"}, "linkTargets": ["Grow second-week team activity"]} +{"id": "I don't know what to do after I create a workspace.md", "title": "I don't know what to do after I create a workspace", "type": "opportunity", "schemaData": {"title": "I don't know what to do after I create a workspace", "type": "opportunity", "status": "identified", "parent": "[[Grow second-week team activity]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I don't know what to do after I create a workspace"]} +{"id": "My teammates ignore my invitations.md", "title": "My teammates ignore my invitations", "type": "opportunity", "schemaData": {"title": "My teammates ignore my invitations", "type": "opportunity", "status": "identified", "parent": "[[Grow second-week team activity]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["My teammates ignore my invitations"]} +{"id": "I can't tell which plan fits a team of my size.md", "title": "I can't tell which plan fits a team of my size", "type": "opportunity", "schemaData": {"title": "I can't tell which plan fits a team of my size", "type": "opportunity", "status": "identified", "parent": "[[Grow second-week team activity]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't tell which plan fits a team of my size"]} +{"id": "I want cheaper invoices from my accountant.md", "title": "I want cheaper invoices from my accountant", "type": "opportunity", "schemaData": {"title": "I want cheaper invoices from my accountant", "type": "opportunity", "status": "identified", "parent": "[[Grow second-week team activity]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I want cheaper invoices from my accountant"]} +{"id": "I need to export my data when I leave.md", "title": "I need to export my data when I leave", "type": "opportunity", "schemaData": {"title": "I need to export my data when I leave", "type": "opportunity", "status": "identified", "parent": "[[Grow second-week team activity]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I need to export my data when I leave"]} +{"id": "I don't know which template to start from.md", "title": "I don't know which template to start from", "type": "opportunity", "schemaData": {"title": "I don't know which template to start from", "type": "opportunity", "status": "identified", "parent": "[[I don't know what to do after I create a workspace]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I don't know which template to start from"]} +{"id": "I'm not sure what to do once my workspace is set up.md", "title": "I'm not sure what to do once my workspace is set up", "type": "opportunity", "schemaData": {"title": "I'm not sure what to do once my workspace is set up", "type": "opportunity", "status": "identified", "parent": "[[I don't know what to do after I create a workspace]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I'm not sure what to do once my workspace is set up"]} +{"id": "The pricing page is confusing.md", "title": "The pricing page is confusing", "type": "opportunity", "schemaData": {"title": "The pricing page is confusing", "type": "opportunity", "status": "identified", "parent": "[[I don't know what to do after I create a workspace]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["The pricing page is confusing"]} +{"id": "I don't understand what the sidebar icons mean.md", "title": "I don't understand what the sidebar icons mean", "type": "opportunity", "schemaData": {"title": "I don't understand what the sidebar icons mean", "type": "opportunity", "status": "identified", "parent": "[[I don't know what to do after I create a workspace]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I don't understand what the sidebar icons mean"]} +{"id": "I want to see what my teammates changed while I was away.md", "title": "I want to see what my teammates changed while I was away", "type": "opportunity", "schemaData": {"title": "I want to see what my teammates changed while I was away", "type": "opportunity", "status": "identified", "parent": "[[I don't know what to do after I create a workspace]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I want to see what my teammates changed while I was away"]} +{"id": "Invitation emails land in my teammates' spam.md", "title": "Invitation emails land in my teammates' spam", "type": "opportunity", "schemaData": {"title": "Invitation emails land in my teammates' spam", "type": "opportunity", "status": "identified", "parent": "[[My teammates ignore my invitations]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Invitation emails land in my teammates' spam"]} +{"id": "My teammates don't know why they should join.md", "title": "My teammates don't know why they should join", "type": "opportunity", "schemaData": {"title": "My teammates don't know why they should join", "type": "opportunity", "status": "identified", "parent": "[[My teammates ignore my invitations]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["My teammates don't know why they should join"]} +{"id": "People I invite can't join because of our single sign-on.md", "title": "People I invite can't join because of our single sign-on", "type": "opportunity", "schemaData": {"title": "People I invite can't join because of our single sign-on", "type": "opportunity", "status": "identified", "parent": "[[My teammates ignore my invitations]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["People I invite can't join because of our single sign-on"]} +{"id": "I don't want to pay for seats nobody uses.md", "title": "I don't want to pay for seats nobody uses", "type": "opportunity", "schemaData": {"title": "I don't want to pay for seats nobody uses", "type": "opportunity", "status": "identified", "parent": "[[My teammates ignore my invitations]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I don't want to pay for seats nobody uses"]} +{"id": "I want to invite people outside my company as guests.md", "title": "I want to invite people outside my company as guests", "type": "opportunity", "schemaData": {"title": "I want to invite people outside my company as guests", "type": "opportunity", "status": "identified", "parent": "[[My teammates ignore my invitations]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I want to invite people outside my company as guests"]} +{"id": "Help people find past work.md", "title": "Help people find past work", "type": "outcome", "schemaData": {"title": "Help people find past work", "type": "outcome", "status": "identified", "metric": "% of searches that end with a document opened"}, "linkTargets": ["Help people find past work"]} +{"id": "I can't remember what I named a document.md", "title": "I can't remember what I named a document", "type": "opportunity", "schemaData": {"title": "I can't remember what I named a document", "type": "opportunity", "status": "identified", "parent": "[[Help people find past work]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't remember what I named a document"]} +{"id": "Search results show documents I don't have access to.md", "title": "Search results show documents I don't have access to", "type": "opportunity", "schemaData": {"title": "Search results show documents I don't have access to", "type": "opportunity", "status": "identified", "parent": "[[Help people find past work]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Search results show documents I don't have access to"]} +{"id": "I want dark mode.md", "title": "I want dark mode", "type": "opportunity", "schemaData": {"title": "I want dark mode", "type": "opportunity", "status": "identified", "parent": "[[Help people find past work]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I want dark mode"]} +{"id": "I want to organise documents into folders.md", "title": "I want to organise documents into folders", "type": "opportunity", "schemaData": {"title": "I want to organise documents into folders", "type": "opportunity", "status": "identified", "parent": "[[Help people find past work]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I want to organise documents into folders"]} +{"id": "Add filters to search.md", "title": "Add filters to search", "type": "opportunity", "schemaData": {"title": "Add filters to search", "type": "opportunity", "status": "identified", "parent": "[[Help people find past work]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Add filters to search"]} +{"id": "Raise weekly invoice volume.md", "title": "Raise weekly invoice volume", "type": "outcome", "schemaData": {"title": "Raise weekly invoice volume", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["Raise weekly invoice volume"]} +{"id": "I forget to send invoices at month end.md", "title": "I forget to send invoices at month end", "type": "opportunity", "schemaData": {"title": "I forget to send invoices at month end", "type": "opportunity", "status": "identified", "parent": "[[Raise weekly invoice volume]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I forget to send invoices at month end"]} +{"id": "I can't take card payments on the invoice.md", "title": "I can't take card payments on the invoice", "type": "opportunity", "schemaData": {"title": "I can't take card payments on the invoice", "type": "opportunity", "status": "identified", "parent": "[[Raise weekly invoice volume]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't take card payments on the invoice"]} +{"id": "My clients say they never received my invoice.md", "title": "My clients say they never received my invoice", "type": "opportunity", "schemaData": {"title": "My clients say they never received my invoice", "type": "opportunity", "status": "identified", "parent": "[[Raise weekly invoice volume]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["My clients say they never received my invoice"]} +{"id": "Get freelancers paid faster.md", "title": "Get freelancers paid faster", "type": "outcome", "schemaData": {"title": "Get freelancers paid faster", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["Get freelancers paid faster"]} +{"id": "I lose track of who has paid me.md", "title": "I lose track of who has paid me", "type": "opportunity", "schemaData": {"title": "I lose track of who has paid me", "type": "opportunity", "status": "identified", "parent": "[[Get freelancers paid faster]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I lose track of who has paid me"]} +{"id": "I can't see which invoices are still unpaid.md", "title": "I can't see which invoices are still unpaid", "type": "opportunity", "schemaData": {"title": "I can't see which invoices are still unpaid", "type": "opportunity", "status": "identified", "parent": "[[Get freelancers paid faster]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't see which invoices are still unpaid"]} +{"id": "Setting up tax rates confuses me.md", "title": "Setting up tax rates confuses me", "type": "opportunity", "schemaData": {"title": "Setting up tax rates confuses me", "type": "opportunity", "status": "identified", "parent": "[[Get freelancers paid faster]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Setting up tax rates confuses me"]} +{"id": "Shorten days to payment.md", "title": "Shorten days to payment", "type": "outcome", "schemaData": {"title": "Shorten days to payment", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["Shorten days to payment"]} +{"id": "Getting paid takes too long.md", "title": "Getting paid takes too long", "type": "opportunity", "schemaData": {"title": "Getting paid takes too long", "type": "opportunity", "status": "identified", "parent": "[[Shorten days to payment]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Getting paid takes too long"]} +{"id": "Clients pay my invoices late.md", "title": "Clients pay my invoices late", "type": "opportunity", "schemaData": {"title": "Clients pay my invoices late", "type": "opportunity", "status": "identified", "parent": "[[Shorten days to payment]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Clients pay my invoices late"]} +{"id": "I can't customise my invoice template.md", "title": "I can't customise my invoice template", "type": "opportunity", "schemaData": {"title": "I can't customise my invoice template", "type": "opportunity", "status": "identified", "parent": "[[Shorten days to payment]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't customise my invoice template"]} +{"id": "Outcome for: I don't trust that my timesheet is right.md", "title": "Outcome for: I don't trust that my timesheet is right", "type": "outcome", "schemaData": {"title": "Outcome for: I don't trust that my timesheet is right", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["Outcome for: I don't trust that my timesheet is right"]} +{"id": "I don't trust that my timesheet is right.md", "title": "I don't trust that my timesheet is right", "type": "opportunity", "schemaData": {"title": "I don't trust that my timesheet is right", "type": "opportunity", "status": "identified", "parent": "[[Outcome for: I don't trust that my timesheet is right]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I don't trust that my timesheet is right"]} +{"id": "I forget to start the timer.md", "title": "I forget to start the timer", "type": "opportunity", "schemaData": {"title": "I forget to start the timer", "type": "opportunity", "status": "identified", "parent": "[[I don't trust that my timesheet is right]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I forget to start the timer"]} +{"id": "I can't edit an entry after the week is locked.md", "title": "I can't edit an entry after the week is locked", "type": "opportunity", "schemaData": {"title": "I can't edit an entry after the week is locked", "type": "opportunity", "status": "identified", "parent": "[[I don't trust that my timesheet is right]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't edit an entry after the week is locked"]} +{"id": "My manager's view shows different totals from mine.md", "title": "My manager's view shows different totals from mine", "type": "opportunity", "schemaData": {"title": "My manager's view shows different totals from mine", "type": "opportunity", "status": "identified", "parent": "[[I don't trust that my timesheet is right]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["My manager's view shows different totals from mine"]} +{"id": "Outcome for: I can't find the meeting notes I need.md", "title": "Outcome for: I can't find the meeting notes I need", "type": "outcome", "schemaData": {"title": "Outcome for: I can't find the meeting notes I need", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["Outcome for: I can't find the meeting notes I need"]} +{"id": "I can't find the meeting notes I need.md", "title": "I can't find the meeting notes I need", "type": "opportunity", "schemaData": {"title": "I can't find the meeting notes I need", "type": "opportunity", "status": "identified", "parent": "[[Outcome for: I can't find the meeting notes I need]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't find the meeting notes I need"]} +{"id": "Search doesn't find words inside attachments.md", "title": "Search doesn't find words inside attachments", "type": "opportunity", "schemaData": {"title": "Search doesn't find words inside attachments", "type": "opportunity", "status": "identified", "parent": "[[I can't find the meeting notes I need]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Search doesn't find words inside attachments"]} +{"id": "I never know which notes are the latest version.md", "title": "I never know which notes are the latest version", "type": "opportunity", "schemaData": {"title": "I never know which notes are the latest version", "type": "opportunity", "status": "identified", "parent": "[[I can't find the meeting notes I need]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I never know which notes are the latest version"]} +{"id": "Notes are spread across three tools.md", "title": "Notes are spread across three tools", "type": "opportunity", "schemaData": {"title": "Notes are spread across three tools", "type": "opportunity", "status": "identified", "parent": "[[I can't find the meeting notes I need]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Notes are spread across three tools"]} +{"id": "New hires productive sooner.md", "title": "New hires productive sooner", "type": "outcome", "schemaData": {"title": "New hires productive sooner", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["New hires productive sooner"]} +{"id": "Onboarding new hires takes weeks.md", "title": "Onboarding new hires takes weeks", "type": "opportunity", "schemaData": {"title": "Onboarding new hires takes weeks", "type": "opportunity", "status": "identified", "parent": "[[New hires productive sooner]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Onboarding new hires takes weeks"]} +{"id": "New starters take a month to become productive.md", "title": "New starters take a month to become productive", "type": "opportunity", "schemaData": {"title": "New starters take a month to become productive", "type": "opportunity", "status": "identified", "parent": "[[New hires productive sooner]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["New starters take a month to become productive"]} +{"id": "I can't see what a new hire has completed.md", "title": "I can't see what a new hire has completed", "type": "opportunity", "schemaData": {"title": "I can't see what a new hire has completed", "type": "opportunity", "status": "identified", "parent": "[[New hires productive sooner]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't see what a new hire has completed"]} +{"id": "Outcome for: I miss messages from clients.md", "title": "Outcome for: I miss messages from clients", "type": "outcome", "schemaData": {"title": "Outcome for: I miss messages from clients", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["Outcome for: I miss messages from clients"]} +{"id": "I miss messages from clients.md", "title": "I miss messages from clients", "type": "opportunity", "schemaData": {"title": "I miss messages from clients", "type": "opportunity", "status": "identified", "parent": "[[Outcome for: I miss messages from clients]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I miss messages from clients"]} +{"id": "Notifications arrive too late on my phone.md", "title": "Notifications arrive too late on my phone", "type": "opportunity", "schemaData": {"title": "Notifications arrive too late on my phone", "type": "opportunity", "status": "identified", "parent": "[[I miss messages from clients]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Notifications arrive too late on my phone"]} +{"id": "Client messages get buried among internal chat.md", "title": "Client messages get buried among internal chat", "type": "opportunity", "schemaData": {"title": "Client messages get buried among internal chat", "type": "opportunity", "status": "identified", "parent": "[[I miss messages from clients]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Client messages get buried among internal chat"]} +{"id": "Priority inbox for client messages.md", "title": "Priority inbox for client messages", "type": "solution", "schemaData": {"title": "Priority inbox for client messages", "type": "solution", "status": "identified", "parent": "[[I miss messages from clients]]"}, "linkTargets": ["Priority inbox for client messages"]} +{"id": "Highlight client messages in the inbox.md", "title": "Highlight client messages in the inbox", "type": "solution", "schemaData": {"title": "Highlight client messages in the inbox", "type": "solution", "status": "identified", "parent": "[[I miss messages from clients]]"}, "linkTargets": ["Highlight client messages in the inbox"]} +{"id": "Grow plan upgrades.md", "title": "Grow plan upgrades", "type": "outcome", "schemaData": {"title": "Grow plan upgrades", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["Grow plan upgrades"]} +{"id": "I don't know what my plan costs per month.md", "title": "I don't know what my plan costs per month", "type": "opportunity", "schemaData": {"title": "I don't know what my plan costs per month", "type": "opportunity", "status": "identified", "parent": "[[Grow plan upgrades]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I don't know what my plan costs per month"]} +{"id": "Pricing is unclear to customers.md", "title": "Pricing is unclear to customers", "type": "opportunity", "schemaData": {"title": "Pricing is unclear to customers", "type": "opportunity", "status": "identified", "parent": "[[Grow plan upgrades]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Pricing is unclear to customers"]} +{"id": "I can't add seats without talking to sales.md", "title": "I can't add seats without talking to sales", "type": "opportunity", "schemaData": {"title": "I can't add seats without talking to sales", "type": "opportunity", "status": "identified", "parent": "[[Grow plan upgrades]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't add seats without talking to sales"]} +{"id": "Increase repeat bookings.md", "title": "Increase repeat bookings", "type": "outcome", "schemaData": {"title": "Increase repeat bookings", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["Increase repeat bookings"]} +{"id": "I can't rebook my usual stylist in one step.md", "title": "I can't rebook my usual stylist in one step", "type": "opportunity", "schemaData": {"title": "I can't rebook my usual stylist in one step", "type": "opportunity", "status": "identified", "parent": "[[Increase repeat bookings]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't rebook my usual stylist in one step"]} +{"id": "I forget when I'm due for my next appointment.md", "title": "I forget when I'm due for my next appointment", "type": "opportunity", "schemaData": {"title": "I forget when I'm due for my next appointment", "type": "opportunity", "status": "identified", "parent": "[[Increase repeat bookings]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I forget when I'm due for my next appointment"]} +{"id": "I can't see which times are free this week.md", "title": "I can't see which times are free this week", "type": "opportunity", "schemaData": {"title": "I can't see which times are free this week", "type": "opportunity", "status": "identified", "parent": "[[Increase repeat bookings]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't see which times are free this week"]} +{"id": "Outcome for: I don't feel safe leaving my bike at the station.md", "title": "Outcome for: I don't feel safe leaving my bike at the station", "type": "outcome", "schemaData": {"title": "Outcome for: I don't feel safe leaving my bike at the station", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["Outcome for: I don't feel safe leaving my bike at the station"]} +{"id": "I don't feel safe leaving my bike at the station.md", "title": "I don't feel safe leaving my bike at the station", "type": "opportunity", "schemaData": {"title": "I don't feel safe leaving my bike at the station", "type": "opportunity", "status": "identified", "parent": "[[Outcome for: I don't feel safe leaving my bike at the station]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I don't feel safe leaving my bike at the station"]} +{"id": "My bike gets stolen at the station.md", "title": "My bike gets stolen at the station", "type": "opportunity", "schemaData": {"title": "My bike gets stolen at the station", "type": "opportunity", "status": "identified", "parent": "[[I don't feel safe leaving my bike at the station]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["My bike gets stolen at the station"]} +{"id": "There's nowhere to charge my e-bike.md", "title": "There's nowhere to charge my e-bike", "type": "opportunity", "schemaData": {"title": "There's nowhere to charge my e-bike", "type": "opportunity", "status": "identified", "parent": "[[I don't feel safe leaving my bike at the station]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["There's nowhere to charge my e-bike"]} +{"id": "Outcome for: I don't feel safe parking my bike at work.md", "title": "Outcome for: I don't feel safe parking my bike at work", "type": "outcome", "schemaData": {"title": "Outcome for: I don't feel safe parking my bike at work", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["Outcome for: I don't feel safe parking my bike at work"]} +{"id": "I don't feel safe parking my bike at work.md", "title": "I don't feel safe parking my bike at work", "type": "opportunity", "schemaData": {"title": "I don't feel safe parking my bike at work", "type": "opportunity", "status": "identified", "parent": "[[Outcome for: I don't feel safe parking my bike at work]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I don't feel safe parking my bike at work"]} +{"id": "There's nowhere to charge my e-bike at work.md", "title": "There's nowhere to charge my e-bike at work", "type": "opportunity", "schemaData": {"title": "There's nowhere to charge my e-bike at work", "type": "opportunity", "status": "identified", "parent": "[[I don't feel safe parking my bike at work]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["There's nowhere to charge my e-bike at work"]} +{"id": "The bike racks at work are always full.md", "title": "The bike racks at work are always full", "type": "opportunity", "schemaData": {"title": "The bike racks at work are always full", "type": "opportunity", "status": "identified", "parent": "[[I don't feel safe parking my bike at work]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["The bike racks at work are always full"]} +{"id": "More travellers check in online.md", "title": "More travellers check in online", "type": "outcome", "schemaData": {"title": "More travellers check in online", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["More travellers check in online"]} +{"id": "I can't print boarding passes at home.md", "title": "I can't print boarding passes at home", "type": "opportunity", "schemaData": {"title": "I can't print boarding passes at home", "type": "opportunity", "status": "identified", "parent": "[[More travellers check in online]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't print boarding passes at home"]} +{"id": "I don't know if my flight is delayed.md", "title": "I don't know if my flight is delayed", "type": "opportunity", "schemaData": {"title": "I don't know if my flight is delayed", "type": "opportunity", "status": "identified", "parent": "[[More travellers check in online]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I don't know if my flight is delayed"]} +{"id": "Fewer support calls about access.md", "title": "Fewer support calls about access", "type": "outcome", "schemaData": {"title": "Fewer support calls about access", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["Fewer support calls about access"]} +{"id": "I can't reset my password.md", "title": "I can't reset my password", "type": "opportunity", "schemaData": {"title": "I can't reset my password", "type": "opportunity", "status": "identified", "parent": "[[Fewer support calls about access]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't reset my password"]} +{"id": "Password reset doesn't work for me.md", "title": "Password reset doesn't work for me", "type": "opportunity", "schemaData": {"title": "Password reset doesn't work for me", "type": "opportunity", "status": "identified", "parent": "[[Fewer support calls about access]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Password reset doesn't work for me"]} +{"id": "I get locked out after two wrong tries.md", "title": "I get locked out after two wrong tries", "type": "opportunity", "schemaData": {"title": "I get locked out after two wrong tries", "type": "opportunity", "status": "identified", "parent": "[[Fewer support calls about access]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I get locked out after two wrong tries"]} +{"id": "Outcome for: Moving house is stressful.md", "title": "Outcome for: Moving house is stressful", "type": "outcome", "schemaData": {"title": "Outcome for: Moving house is stressful", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["Outcome for: Moving house is stressful"]} +{"id": "Moving house is stressful.md", "title": "Moving house is stressful", "type": "opportunity", "schemaData": {"title": "Moving house is stressful", "type": "opportunity", "status": "identified", "parent": "[[Outcome for: Moving house is stressful]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Moving house is stressful"]} +{"id": "I don't know when to cancel my utilities.md", "title": "I don't know when to cancel my utilities", "type": "opportunity", "schemaData": {"title": "I don't know when to cancel my utilities", "type": "opportunity", "status": "identified", "parent": "[[Moving house is stressful]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I don't know when to cancel my utilities"]} +{"id": "I can't find movers available on weekends.md", "title": "I can't find movers available on weekends", "type": "opportunity", "schemaData": {"title": "I can't find movers available on weekends", "type": "opportunity", "status": "identified", "parent": "[[Moving house is stressful]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't find movers available on weekends"]} +{"id": "I worry my mail will go to the old address.md", "title": "I worry my mail will go to the old address", "type": "opportunity", "schemaData": {"title": "I worry my mail will go to the old address", "type": "opportunity", "status": "identified", "parent": "[[Moving house is stressful]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I worry my mail will go to the old address"]} +{"id": "More members track spending weekly.md", "title": "More members track spending weekly", "type": "outcome", "schemaData": {"title": "More members track spending weekly", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["More members track spending weekly"]} +{"id": "I can't tell how much I've spent this month.md", "title": "I can't tell how much I've spent this month", "type": "opportunity", "schemaData": {"title": "I can't tell how much I've spent this month", "type": "opportunity", "status": "identified", "parent": "[[More members track spending weekly]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't tell how much I've spent this month"]} +{"id": "I don't know how much I spend on subscriptions.md", "title": "I don't know how much I spend on subscriptions", "type": "opportunity", "schemaData": {"title": "I don't know how much I spend on subscriptions", "type": "opportunity", "status": "identified", "parent": "[[More members track spending weekly]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I don't know how much I spend on subscriptions"]} +{"id": "I can't split bills with flatmates.md", "title": "I can't split bills with flatmates", "type": "opportunity", "schemaData": {"title": "I can't split bills with flatmates", "type": "opportunity", "status": "identified", "parent": "[[More members track spending weekly]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't split bills with flatmates"]} +{"id": "Outcome for: Planning family meals takes too long.md", "title": "Outcome for: Planning family meals takes too long", "type": "outcome", "schemaData": {"title": "Outcome for: Planning family meals takes too long", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["Outcome for: Planning family meals takes too long"]} +{"id": "Planning family meals takes too long.md", "title": "Planning family meals takes too long", "type": "opportunity", "schemaData": {"title": "Planning family meals takes too long", "type": "opportunity", "status": "identified", "parent": "[[Outcome for: Planning family meals takes too long]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Planning family meals takes too long"]} +{"id": "I don't know what to cook with what's in the fridge.md", "title": "I don't know what to cook with what's in the fridge", "type": "opportunity", "schemaData": {"title": "I don't know what to cook with what's in the fridge", "type": "opportunity", "status": "identified", "parent": "[[Planning family meals takes too long]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I don't know what to cook with what's in the fridge"]} +{"id": "My kids refuse half of what I cook.md", "title": "My kids refuse half of what I cook", "type": "opportunity", "schemaData": {"title": "My kids refuse half of what I cook", "type": "opportunity", "status": "identified", "parent": "[[Planning family meals takes too long]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["My kids refuse half of what I cook"]} +{"id": "Shopping for a week of meals takes an hour.md", "title": "Shopping for a week of meals takes an hour", "type": "opportunity", "schemaData": {"title": "Shopping for a week of meals takes an hour", "type": "opportunity", "status": "identified", "parent": "[[Planning family meals takes too long]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Shopping for a week of meals takes an hour"]} +{"id": "I'm afraid I'll lose my work if my browser crashes.md", "title": "I'm afraid I'll lose my work if my browser crashes", "type": "opportunity", "schemaData": {"title": "I'm afraid I'll lose my work if my browser crashes", "type": "opportunity", "status": "identified", "parent": "[[Grow second-week team activity]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I'm afraid I'll lose my work if my browser crashes"]} +{"id": "Autosave every few seconds.md", "title": "Autosave every few seconds", "type": "solution", "schemaData": {"title": "Autosave every few seconds", "type": "solution", "status": "identified", "parent": "[[I'm afraid I'll lose my work if my browser crashes]]"}, "linkTargets": ["Autosave every few seconds"]} +{"id": "Restore unsaved drafts after a crash.md", "title": "Restore unsaved drafts after a crash", "type": "solution", "schemaData": {"title": "Restore unsaved drafts after a crash", "type": "solution", "status": "identified", "parent": "[[I'm afraid I'll lose my work if my browser crashes]]"}, "linkTargets": ["Restore unsaved drafts after a crash"]} +{"id": "Make the app load faster.md", "title": "Make the app load faster", "type": "solution", "schemaData": {"title": "Make the app load faster", "type": "solution", "status": "identified", "parent": "[[I'm afraid I'll lose my work if my browser crashes]]"}, "linkTargets": ["Make the app load faster"]} +{"id": "Show a 'saved' indicator in the toolbar.md", "title": "Show a 'saved' indicator in the toolbar", "type": "solution", "schemaData": {"title": "Show a 'saved' indicator in the toolbar", "type": "solution", "status": "identified", "parent": "[[I'm afraid I'll lose my work if my browser crashes]]"}, "linkTargets": ["Show a 'saved' indicator in the toolbar"]} +{"id": "Write a help article on keyboard shortcuts.md", "title": "Write a help article on keyboard shortcuts", "type": "solution", "schemaData": {"title": "Write a help article on keyboard shortcuts", "type": "solution", "status": "identified", "parent": "[[I'm afraid I'll lose my work if my browser crashes]]"}, "linkTargets": ["Write a help article on keyboard shortcuts"]} +{"id": "Back up workspaces nightly to cloud storage.md", "title": "Back up workspaces nightly to cloud storage", "type": "solution", "schemaData": {"title": "Back up workspaces nightly to cloud storage", "type": "solution", "status": "identified", "parent": "[[I'm afraid I'll lose my work if my browser crashes]]"}, "linkTargets": ["Back up workspaces nightly to cloud storage"]} +{"id": "Warn before closing a tab with unsaved changes.md", "title": "Warn before closing a tab with unsaved changes", "type": "solution", "schemaData": {"title": "Warn before closing a tab with unsaved changes", "type": "solution", "status": "identified", "parent": "[[I'm afraid I'll lose my work if my browser crashes]]"}, "linkTargets": ["Warn before closing a tab with unsaved changes"]} +{"id": "Plan picker that asks for team size and recommends a plan.md", "title": "Plan picker that asks for team size and recommends a plan", "type": "solution", "schemaData": {"title": "Plan picker that asks for team size and recommends a plan", "type": "solution", "status": "identified", "parent": "[[I can't tell which plan fits a team of my size]]"}, "linkTargets": ["Plan picker that asks for team size and recommends a plan"]} +{"id": "Compare plans side by side by seat count.md", "title": "Compare plans side by side by seat count", "type": "solution", "schemaData": {"title": "Compare plans side by side by seat count", "type": "solution", "status": "identified", "parent": "[[I can't tell which plan fits a team of my size]]"}, "linkTargets": ["Compare plans side by side by seat count"]} +{"id": "Offer a discount on annual plans.md", "title": "Offer a discount on annual plans", "type": "solution", "schemaData": {"title": "Offer a discount on annual plans", "type": "solution", "status": "identified", "parent": "[[I can't tell which plan fits a team of my size]]"}, "linkTargets": ["Offer a discount on annual plans"]} +{"id": "Add more payment methods.md", "title": "Add more payment methods", "type": "solution", "schemaData": {"title": "Add more payment methods", "type": "solution", "status": "identified", "parent": "[[I can't tell which plan fits a team of my size]]"}, "linkTargets": ["Add more payment methods"]} +{"id": "Chat with sales about plan options.md", "title": "Chat with sales about plan options", "type": "solution", "schemaData": {"title": "Chat with sales about plan options", "type": "solution", "status": "identified", "parent": "[[I can't tell which plan fits a team of my size]]"}, "linkTargets": ["Chat with sales about plan options"]} +{"id": "Rename the plans to Team, Business and Enterprise.md", "title": "Rename the plans to Team, Business and Enterprise", "type": "solution", "schemaData": {"title": "Rename the plans to Team, Business and Enterprise", "type": "solution", "status": "identified", "parent": "[[I can't tell which plan fits a team of my size]]"}, "linkTargets": ["Rename the plans to Team, Business and Enterprise"]} +{"id": "Send invitations from a verified domain with SPF and DKIM.md", "title": "Send invitations from a verified domain with SPF and DKIM", "type": "solution", "schemaData": {"title": "Send invitations from a verified domain with SPF and DKIM", "type": "solution", "status": "identified", "parent": "[[Invitation emails land in my teammates' spam]]"}, "linkTargets": ["Send invitations from a verified domain with SPF and DKIM"]} +{"id": "Let me share an invite link in chat instead.md", "title": "Let me share an invite link in chat instead", "type": "solution", "schemaData": {"title": "Let me share an invite link in chat instead", "type": "solution", "status": "identified", "parent": "[[Invitation emails land in my teammates' spam]]"}, "linkTargets": ["Let me share an invite link in chat instead"]} +{"id": "Redesign the invitation email to look friendlier.md", "title": "Redesign the invitation email to look friendlier", "type": "solution", "schemaData": {"title": "Redesign the invitation email to look friendlier", "type": "solution", "status": "identified", "parent": "[[Invitation emails land in my teammates' spam]]"}, "linkTargets": ["Redesign the invitation email to look friendlier"]} +{"id": "Remind teammates in the app to accept invites.md", "title": "Remind teammates in the app to accept invites", "type": "solution", "schemaData": {"title": "Remind teammates in the app to accept invites", "type": "solution", "status": "identified", "parent": "[[Invitation emails land in my teammates' spam]]"}, "linkTargets": ["Remind teammates in the app to accept invites"]} +{"id": "Bulk import teammates from a spreadsheet.md", "title": "Bulk import teammates from a spreadsheet", "type": "solution", "schemaData": {"title": "Bulk import teammates from a spreadsheet", "type": "solution", "status": "identified", "parent": "[[Invitation emails land in my teammates' spam]]"}, "linkTargets": ["Bulk import teammates from a spreadsheet"]} +{"id": "Send invitations from a verified domain.md", "title": "Send invitations from a verified domain", "type": "solution", "schemaData": {"title": "Send invitations from a verified domain", "type": "solution", "status": "identified", "parent": "[[My teammates ignore my invitations]]"}, "linkTargets": ["Send invitations from a verified domain"]} +{"id": "Let invited teammates preview the workspace before signing up.md", "title": "Let invited teammates preview the workspace before signing up", "type": "solution", "schemaData": {"title": "Let invited teammates preview the workspace before signing up", "type": "solution", "status": "identified", "parent": "[[My teammates ignore my invitations]]"}, "linkTargets": ["Let invited teammates preview the workspace before signing up"]} +{"id": "A checklist of first steps in a new workspace.md", "title": "A checklist of first steps in a new workspace", "type": "solution", "schemaData": {"title": "A checklist of first steps in a new workspace", "type": "solution", "status": "identified", "parent": "[[I don't know what to do after I create a workspace]]"}, "linkTargets": ["A checklist of first steps in a new workspace"]} +{"id": "A consultant sets up the workspace on a paid call two weeks after signup.md", "title": "A consultant sets up the workspace on a paid call two weeks after signup", "type": "solution", "schemaData": {"title": "A consultant sets up the workspace on a paid call two weeks after signup", "type": "solution", "status": "identified", "parent": "[[I don't know what to do after I create a workspace]]"}, "linkTargets": ["A consultant sets up the workspace on a paid call two weeks after signup"]} +{"id": "A 'since you were away' summary when I come back.md", "title": "A 'since you were away' summary when I come back", "type": "solution", "schemaData": {"title": "A 'since you were away' summary when I come back", "type": "solution", "status": "identified", "parent": "[[I want to see what my teammates changed while I was away]]"}, "linkTargets": ["A 'since you were away' summary when I come back"]} +{"id": "Email a daily digest of every change, so nobody needs to open the app.md", "title": "Email a daily digest of every change, so nobody needs to open the app", "type": "solution", "schemaData": {"title": "Email a daily digest of every change, so nobody needs to open the app", "type": "solution", "status": "identified", "parent": "[[I want to see what my teammates changed while I was away]]"}, "linkTargets": ["Email a daily digest of every change, so nobody needs to open the app"]} +{"id": "Notify me in the app about changes on pages I follow.md", "title": "Notify me in the app about changes on pages I follow", "type": "solution", "schemaData": {"title": "Notify me in the app about changes on pages I follow", "type": "solution", "status": "identified", "parent": "[[I want to see what my teammates changed while I was away]]"}, "linkTargets": ["Notify me in the app about changes on pages I follow"]} +{"id": "Search matches words inside documents, not only titles.md", "title": "Search matches words inside documents, not only titles", "type": "solution", "schemaData": {"title": "Search matches words inside documents, not only titles", "type": "solution", "status": "identified", "parent": "[[I can't remember what I named a document]]"}, "linkTargets": ["Search matches words inside documents, not only titles"]} +{"id": "Hide documents I can't open from search results.md", "title": "Hide documents I can't open from search results", "type": "solution", "schemaData": {"title": "Hide documents I can't open from search results", "type": "solution", "status": "identified", "parent": "[[Search results show documents I don't have access to]]"}, "linkTargets": ["Hide documents I can't open from search results"]} +{"id": "Show a 'request access' button on each locked result.md", "title": "Show a 'request access' button on each locked result", "type": "solution", "schemaData": {"title": "Show a 'request access' button on each locked result", "type": "solution", "status": "identified", "parent": "[[Search results show documents I don't have access to]]"}, "linkTargets": ["Show a 'request access' button on each locked result"]} +{"id": "Get new businesses invoicing sooner.md", "title": "Get new businesses invoicing sooner", "type": "outcome", "schemaData": {"title": "Get new businesses invoicing sooner", "type": "outcome", "status": "identified", "metric": "Median time from signup to first invoice sent"}, "linkTargets": ["Get new businesses invoicing sooner"]} +{"id": "Setting up tax rates is confusing.md", "title": "Setting up tax rates is confusing", "type": "opportunity", "schemaData": {"title": "Setting up tax rates is confusing", "type": "opportunity", "status": "identified", "parent": "[[Get new businesses invoicing sooner]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Setting up tax rates is confusing"]} +{"id": "I can't take card payments on my invoices.md", "title": "I can't take card payments on my invoices", "type": "opportunity", "schemaData": {"title": "I can't take card payments on my invoices", "type": "opportunity", "status": "identified", "parent": "[[Get new businesses invoicing sooner]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't take card payments on my invoices"]} +{"id": "Pre-fill tax rates from my business address.md", "title": "Pre-fill tax rates from my business address", "type": "solution", "schemaData": {"title": "Pre-fill tax rates from my business address", "type": "solution", "status": "identified", "parent": "[[Setting up tax rates is confusing]]"}, "linkTargets": ["Pre-fill tax rates from my business address"]} +{"id": "Require a call with an accountant before the first invoice.md", "title": "Require a call with an accountant before the first invoice", "type": "solution", "schemaData": {"title": "Require a call with an accountant before the first invoice", "type": "solution", "status": "identified", "parent": "[[Setting up tax rates is confusing]]"}, "linkTargets": ["Require a call with an accountant before the first invoice"]} +{"id": "Let me send my first invoice now and set tax rates later.md", "title": "Let me send my first invoice now and set tax rates later", "type": "solution", "schemaData": {"title": "Let me send my first invoice now and set tax rates later", "type": "solution", "status": "identified", "parent": "[[Setting up tax rates is confusing]]"}, "linkTargets": ["Let me send my first invoice now and set tax rates later"]} +{"id": "Add card payments through a payment provider.md", "title": "Add card payments through a payment provider", "type": "solution", "schemaData": {"title": "Add card payments through a payment provider", "type": "solution", "status": "identified", "parent": "[[I can't take card payments on my invoices]]"}, "linkTargets": ["Add card payments through a payment provider"]} +{"id": "Customers come back for their next cut.md", "title": "Customers come back for their next cut", "type": "outcome", "schemaData": {"title": "Customers come back for their next cut", "type": "outcome", "status": "identified", "metric": "% of customers who book again within 60 days"}, "linkTargets": ["Customers come back for their next cut"]} +{"id": "I forget when I'm due for my next haircut.md", "title": "I forget when I'm due for my next haircut", "type": "opportunity", "schemaData": {"title": "I forget when I'm due for my next haircut", "type": "opportunity", "status": "identified", "parent": "[[Customers come back for their next cut]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I forget when I'm due for my next haircut"]} +{"id": "I can't rebook my favourite stylist in one step.md", "title": "I can't rebook my favourite stylist in one step", "type": "opportunity", "schemaData": {"title": "I can't rebook my favourite stylist in one step", "type": "opportunity", "status": "identified", "parent": "[[Customers come back for their next cut]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't rebook my favourite stylist in one step"]} +{"id": "Text a reminder when it's been five weeks since my last cut.md", "title": "Text a reminder when it's been five weeks since my last cut", "type": "solution", "schemaData": {"title": "Text a reminder when it's been five weeks since my last cut", "type": "solution", "status": "identified", "parent": "[[I forget when I'm due for my next haircut]]"}, "linkTargets": ["Text a reminder when it's been five weeks since my last cut"]} +{"id": "One-tap rebook of my last appointment.md", "title": "One-tap rebook of my last appointment", "type": "solution", "schemaData": {"title": "One-tap rebook of my last appointment", "type": "solution", "status": "identified", "parent": "[[I can't rebook my favourite stylist in one step]]"}, "linkTargets": ["One-tap rebook of my last appointment"]} +{"id": "Book me with whichever stylist is free, to make booking faster.md", "title": "Book me with whichever stylist is free, to make booking faster", "type": "solution", "schemaData": {"title": "Book me with whichever stylist is free, to make booking faster", "type": "solution", "status": "identified", "parent": "[[I can't rebook my favourite stylist in one step]]"}, "linkTargets": ["Book me with whichever stylist is free, to make booking faster"]} +{"id": "Charge a fee to rebook within a week.md", "title": "Charge a fee to rebook within a week", "type": "solution", "schemaData": {"title": "Charge a fee to rebook within a week", "type": "solution", "status": "identified", "parent": "[[I can't rebook my favourite stylist in one step]]"}, "linkTargets": ["Charge a fee to rebook within a week"]} +{"id": "I broke a page and want it back the way it was.md", "title": "I broke a page and want it back the way it was", "type": "opportunity", "schemaData": {"title": "I broke a page and want it back the way it was", "type": "opportunity", "status": "identified", "parent": "[[Help people find past work]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I broke a page and want it back the way it was"]} +{"id": "Version history for every page.md", "title": "Version history for every page", "type": "solution", "schemaData": {"title": "Version history for every page", "type": "solution", "status": "identified", "parent": "[[I broke a page and want it back the way it was]]"}, "linkTargets": ["Version history for every page"]} +{"id": "Test 01: Teams want to see who edited a page.md", "title": "Test 01: Teams want to see who edited a page", "type": "assumption_test", "schemaData": {"title": "Test 01: Teams want to see who edited a page", "type": "assumption_test", "status": "identified", "parent": "[[Version history for every page]]", "assumption": "Teams want to see who edited a page", "category": "desirability"}, "linkTargets": ["Test 01: Teams want to see who edited a page"]} +{"id": "Test 02: Storing every revision costs less than $0.10 per seat a month.md", "title": "Test 02: Storing every revision costs less than $0.10 per seat a month", "type": "assumption_test", "schemaData": {"title": "Test 02: Storing every revision costs less than $0.10 per seat a month", "type": "assumption_test", "status": "identified", "parent": "[[Version history for every page]]", "assumption": "Storing every revision costs less than $0.10 per seat a month", "category": "viability"}, "linkTargets": ["Test 02: Storing every revision costs less than $0.10 per seat a month"]} +{"id": "Test 03: We can diff rich-text documents in under 200 ms.md", "title": "Test 03: We can diff rich-text documents in under 200 ms", "type": "assumption_test", "schemaData": {"title": "Test 03: We can diff rich-text documents in under 200 ms", "type": "assumption_test", "status": "identified", "parent": "[[Version history for every page]]", "assumption": "We can diff rich-text documents in under 200 ms", "category": "feasibility"}, "linkTargets": ["Test 03: We can diff rich-text documents in under 200 ms"]} +{"id": "Test 04: Users can find the history panel without help.md", "title": "Test 04: Users can find the history panel without help", "type": "assumption_test", "schemaData": {"title": "Test 04: Users can find the history panel without help", "type": "assumption_test", "status": "identified", "parent": "[[Version history for every page]]", "assumption": "Users can find the history panel without help", "category": "usability"}, "linkTargets": ["Test 04: Users can find the history panel without help"]} +{"id": "Test 05: Showing who edited what won't be used to monitor employees.md", "title": "Test 05: Showing who edited what won't be used to monitor employees", "type": "assumption_test", "schemaData": {"title": "Test 05: Showing who edited what won't be used to monitor employees", "type": "assumption_test", "status": "identified", "parent": "[[Version history for every page]]", "assumption": "Showing who edited what won't be used to monitor employees", "category": "ethical"}, "linkTargets": ["Test 05: Showing who edited what won't be used to monitor employees"]} +{"id": "Test 06: Our sync engine can merge offline edits without conflicts.md", "title": "Test 06: Our sync engine can merge offline edits without conflicts", "type": "assumption_test", "schemaData": {"title": "Test 06: Our sync engine can merge offline edits without conflicts", "type": "assumption_test", "status": "identified", "parent": "[[Version history for every page]]", "assumption": "Our sync engine can merge offline edits without conflicts", "category": "feasibility"}, "linkTargets": ["Test 06: Our sync engine can merge offline edits without conflicts"]} +{"id": "Test 07: Admins understand the retention settings on first read.md", "title": "Test 07: Admins understand the retention settings on first read", "type": "assumption_test", "schemaData": {"title": "Test 07: Admins understand the retention settings on first read", "type": "assumption_test", "status": "identified", "parent": "[[Version history for every page]]", "assumption": "Admins understand the retention settings on first read", "category": "usability"}, "linkTargets": ["Test 07: Admins understand the retention settings on first read"]} +{"id": "Test 08: Users can find the restore button without help.md", "title": "Test 08: Users can find the restore button without help", "type": "assumption_test", "schemaData": {"title": "Test 08: Users can find the restore button without help", "type": "assumption_test", "status": "identified", "parent": "[[Version history for every page]]", "assumption": "Users can find the restore button without help", "category": "desirability"}, "linkTargets": ["Test 08: Users can find the restore button without help"]} +{"id": "Test 09: Keeping 90 days of revisions costs under $0.10 per seat a month.md", "title": "Test 09: Keeping 90 days of revisions costs under $0.10 per seat a month", "type": "assumption_test", "schemaData": {"title": "Test 09: Keeping 90 days of revisions costs under $0.10 per seat a month", "type": "assumption_test", "status": "identified", "parent": "[[Version history for every page]]", "assumption": "Keeping 90 days of revisions costs under $0.10 per seat a month", "category": "feasibility"}, "linkTargets": ["Test 09: Keeping 90 days of revisions costs under $0.10 per seat a month"]} +{"id": "Test 10: Teams want to restore old versions.md", "title": "Test 10: Teams want to restore old versions", "type": "assumption_test", "schemaData": {"title": "Test 10: Teams want to restore old versions", "type": "assumption_test", "status": "identified", "parent": "[[Version history for every page]]", "assumption": "Teams want to restore old versions", "category": "usability"}, "linkTargets": ["Test 10: Teams want to restore old versions"]} +{"id": "Test 11: Sales can sell version history as an enterprise add-on.md", "title": "Test 11: Sales can sell version history as an enterprise add-on", "type": "assumption_test", "schemaData": {"title": "Test 11: Sales can sell version history as an enterprise add-on", "type": "assumption_test", "status": "identified", "parent": "[[Version history for every page]]", "assumption": "Sales can sell version history as an enterprise add-on", "category": "feasibility"}, "linkTargets": ["Test 11: Sales can sell version history as an enterprise add-on"]} +{"id": "Test 12: We can build live cursors on our current websocket stack.md", "title": "Test 12: We can build live cursors on our current websocket stack", "type": "assumption_test", "schemaData": {"title": "Test 12: We can build live cursors on our current websocket stack", "type": "assumption_test", "status": "identified", "parent": "[[Version history for every page]]", "assumption": "We can build live cursors on our current websocket stack", "category": "desirability"}, "linkTargets": ["Test 12: We can build live cursors on our current websocket stack"]} +{"id": "Test 13: Users will notice the 'restored' banner and understand what changed.md", "title": "Test 13: Users will notice the 'restored' banner and understand what changed", "type": "assumption_test", "schemaData": {"title": "Test 13: Users will notice the 'restored' banner and understand what changed", "type": "assumption_test", "status": "identified", "parent": "[[Version history for every page]]", "assumption": "Users will notice the 'restored' banner and understand what changed", "category": "usability"}, "linkTargets": ["Test 13: Users will notice the 'restored' banner and understand what changed"]} +{"id": "Test 14: Legal will approve keeping deleted content for 90 days.md", "title": "Test 14: Legal will approve keeping deleted content for 90 days", "type": "assumption_test", "schemaData": {"title": "Test 14: Legal will approve keeping deleted content for 90 days", "type": "assumption_test", "status": "identified", "parent": "[[Version history for every page]]", "assumption": "Legal will approve keeping deleted content for 90 days", "category": "viability"}, "linkTargets": ["Test 14: Legal will approve keeping deleted content for 90 days"]} +{"id": "Test 15: Teams will keep using history after the first month.md", "title": "Test 15: Teams will keep using history after the first month", "type": "assumption_test", "schemaData": {"title": "Test 15: Teams will keep using history after the first month", "type": "assumption_test", "status": "identified", "parent": "[[Version history for every page]]", "assumption": "Teams will keep using history after the first month", "category": "desirability"}, "linkTargets": ["Test 15: Teams will keep using history after the first month"]} +{"id": "Test 16: Managers won't use edit history to rank their staff.md", "title": "Test 16: Managers won't use edit history to rank their staff", "type": "assumption_test", "schemaData": {"title": "Test 16: Managers won't use edit history to rank their staff", "type": "assumption_test", "status": "identified", "parent": "[[Version history for every page]]", "assumption": "Managers won't use edit history to rank their staff", "category": "ethical"}, "linkTargets": ["Test 16: Managers won't use edit history to rank their staff"]} +{"id": "Test 17: Managers won't use edit history to rank their staff, judged by a survey.md", "title": "Test 17: Managers won't use edit history to rank their staff, judged by a survey", "type": "assumption_test", "schemaData": {"title": "Test 17: Managers won't use edit history to rank their staff, judged by a survey", "type": "assumption_test", "status": "identified", "parent": "[[Version history for every page]]", "assumption": "Managers won't use edit history to rank their staff, judged by a survey", "category": "usability"}, "linkTargets": ["Test 17: Managers won't use edit history to rank their staff, judged by a survey"]} +{"id": "Test 18: Users will pay for longer history.md", "title": "Test 18: Users will pay for longer history", "type": "assumption_test", "schemaData": {"title": "Test 18: Users will pay for longer history", "type": "assumption_test", "status": "identified", "parent": "[[Version history for every page]]", "assumption": "Users will pay for longer history", "category": "feasibility"}, "linkTargets": ["Test 18: Users will pay for longer history"]} diff --git a/evals/semantic/fixtures/strategy-trees.jsonl b/evals/semantic/fixtures/strategy-trees.jsonl new file mode 100644 index 00000000..8d8b7918 --- /dev/null +++ b/evals/semantic/fixtures/strategy-trees.jsonl @@ -0,0 +1,148 @@ +{"id": "Help small cafés run a profitable kitchen.md", "title": "Help small cafés run a profitable kitchen", "type": "mission", "schemaData": {"title": "Help small cafés run a profitable kitchen", "type": "mission", "status": "active", "content": "Cut food waste, price every dish properly, and keep good kitchen staff."}, "linkTargets": ["Help small cafés run a profitable kitchen"], "content": "Cut food waste, price every dish properly, and keep good kitchen staff."} +{"id": "Cut food waste by 30%.md", "title": "Cut food waste by 30%", "type": "goal", "schemaData": {"title": "Cut food waste by 30%", "type": "goal", "status": "active", "parent": "[[Help small cafés run a profitable kitchen]]"}, "linkTargets": ["Cut food waste by 30%"]} +{"id": "Price every dish for a 65% margin.md", "title": "Price every dish for a 65% margin", "type": "goal", "schemaData": {"title": "Price every dish for a 65% margin", "type": "goal", "status": "active", "parent": "[[Help small cafés run a profitable kitchen]]"}, "linkTargets": ["Price every dish for a 65% margin"]} +{"id": "Keep kitchen staff for over a year.md", "title": "Keep kitchen staff for over a year", "type": "goal", "schemaData": {"title": "Keep kitchen staff for over a year", "type": "goal", "status": "active", "parent": "[[Help small cafés run a profitable kitchen]]"}, "linkTargets": ["Keep kitchen staff for over a year"]} +{"id": "Get ready for the Six Foot Track ultra.md", "title": "Get ready for the Six Foot Track ultra", "type": "goal", "schemaData": {"title": "Get ready for the Six Foot Track ultra", "type": "goal", "status": "active", "content": "Run 45 km of hills in March. I need the endurance, strong downhill legs, and no injuries."}, "linkTargets": ["Get ready for the Six Foot Track ultra"], "content": "Run 45 km of hills in March. I need the endurance, strong downhill legs, and no injuries."} +{"id": "Build to 80 km weeks.md", "title": "Build to 80 km weeks", "type": "goal", "schemaData": {"title": "Build to 80 km weeks", "type": "goal", "status": "active", "parent": "[[Get ready for the Six Foot Track ultra]]"}, "linkTargets": ["Build to 80 km weeks"]} +{"id": "Run long downhill repeats every week.md", "title": "Run long downhill repeats every week", "type": "goal", "schemaData": {"title": "Run long downhill repeats every week", "type": "goal", "status": "active", "parent": "[[Get ready for the Six Foot Track ultra]]"}, "linkTargets": ["Run long downhill repeats every week"]} +{"id": "Launch the podcast.md", "title": "Launch the podcast", "type": "goal", "schemaData": {"title": "Launch the podcast", "type": "goal", "status": "active", "content": "Record, edit and publish ten episodes, and build an audience of 1,000 listeners."}, "linkTargets": ["Launch the podcast"], "content": "Record, edit and publish ten episodes, and build an audience of 1,000 listeners."} +{"id": "Record ten episodes.md", "title": "Record ten episodes", "type": "goal", "schemaData": {"title": "Record ten episodes", "type": "goal", "status": "active", "parent": "[[Launch the podcast]]"}, "linkTargets": ["Record ten episodes"]} +{"id": "Publish ten episodes.md", "title": "Publish ten episodes", "type": "goal", "schemaData": {"title": "Publish ten episodes", "type": "goal", "status": "active", "parent": "[[Launch the podcast]]"}, "linkTargets": ["Publish ten episodes"]} +{"id": "Grow to 1,000 listeners.md", "title": "Grow to 1,000 listeners", "type": "goal", "schemaData": {"title": "Grow to 1,000 listeners", "type": "goal", "status": "active", "parent": "[[Launch the podcast]]"}, "linkTargets": ["Grow to 1,000 listeners"]} +{"id": "Improve team health.md", "title": "Improve team health", "type": "goal", "schemaData": {"title": "Improve team health", "type": "goal", "status": "active", "content": "Fewer late nights, and a workload people can sustain."}, "linkTargets": ["Improve team health"], "content": "Fewer late nights, and a workload people can sustain."} +{"id": "Reduce after-hours messages.md", "title": "Reduce after-hours messages", "type": "goal", "schemaData": {"title": "Reduce after-hours messages", "type": "goal", "status": "active", "parent": "[[Improve team health]]"}, "linkTargets": ["Reduce after-hours messages"]} +{"id": "Stop people working late at night.md", "title": "Stop people working late at night", "type": "goal", "schemaData": {"title": "Stop people working late at night", "type": "goal", "status": "active", "parent": "[[Improve team health]]"}, "linkTargets": ["Stop people working late at night"]} +{"id": "Run a quarterly retro on workload.md", "title": "Run a quarterly retro on workload", "type": "goal", "schemaData": {"title": "Run a quarterly retro on workload", "type": "goal", "status": "active", "parent": "[[Improve team health]]"}, "linkTargets": ["Run a quarterly retro on workload"]} +{"id": "Grow the newsletter.md", "title": "Grow the newsletter", "type": "goal", "schemaData": {"title": "Grow the newsletter", "type": "goal", "status": "active", "content": "Grow subscribers and keep them reading."}, "linkTargets": ["Grow the newsletter"], "content": "Grow subscribers and keep them reading."} +{"id": "Reach 5,000 subscribers.md", "title": "Reach 5,000 subscribers", "type": "goal", "schemaData": {"title": "Reach 5,000 subscribers", "type": "goal", "status": "active", "parent": "[[Grow the newsletter]]"}, "linkTargets": ["Reach 5,000 subscribers"]} +{"id": "Get 1,000 new subscribers from social media.md", "title": "Get 1,000 new subscribers from social media", "type": "goal", "schemaData": {"title": "Get 1,000 new subscribers from social media", "type": "goal", "status": "active", "parent": "[[Grow the newsletter]]"}, "linkTargets": ["Get 1,000 new subscribers from social media"]} +{"id": "Keep the open rate above 45%.md", "title": "Keep the open rate above 45%", "type": "goal", "schemaData": {"title": "Keep the open rate above 45%", "type": "goal", "status": "active", "parent": "[[Grow the newsletter]]"}, "linkTargets": ["Keep the open rate above 45%"]} +{"id": "Make our home run on renewable energy.md", "title": "Make our home run on renewable energy", "type": "mission", "schemaData": {"title": "Make our home run on renewable energy", "type": "mission", "status": "active", "content": "Solar for electricity, and no more gas for heating, hot water or cooking."}, "linkTargets": ["Make our home run on renewable energy"], "content": "Solar for electricity, and no more gas for heating, hot water or cooking."} +{"id": "Install 6 kW of rooftop solar.md", "title": "Install 6 kW of rooftop solar", "type": "goal", "schemaData": {"title": "Install 6 kW of rooftop solar", "type": "goal", "status": "active", "parent": "[[Make our home run on renewable energy]]"}, "linkTargets": ["Install 6 kW of rooftop solar"]} +{"id": "Replace the gas hot water with a heat pump.md", "title": "Replace the gas hot water with a heat pump", "type": "goal", "schemaData": {"title": "Replace the gas hot water with a heat pump", "type": "goal", "status": "active", "parent": "[[Make our home run on renewable energy]]"}, "linkTargets": ["Replace the gas hot water with a heat pump"]} +{"id": "Swap the gas cooktop for induction.md", "title": "Swap the gas cooktop for induction", "type": "goal", "schemaData": {"title": "Swap the gas cooktop for induction", "type": "goal", "status": "active", "parent": "[[Make our home run on renewable energy]]"}, "linkTargets": ["Swap the gas cooktop for induction"]} +{"id": "Make our flat run on renewable energy.md", "title": "Make our flat run on renewable energy", "type": "mission", "schemaData": {"title": "Make our flat run on renewable energy", "type": "mission", "status": "active", "content": "Solar for electricity, and no more gas for heating, hot water or cooking."}, "linkTargets": ["Make our flat run on renewable energy"], "content": "Solar for electricity, and no more gas for heating, hot water or cooking."} +{"id": "Install 3 kW of solar on the balcony roof.md", "title": "Install 3 kW of solar on the balcony roof", "type": "goal", "schemaData": {"title": "Install 3 kW of solar on the balcony roof", "type": "goal", "status": "active", "parent": "[[Make our flat run on renewable energy]]"}, "linkTargets": ["Install 3 kW of solar on the balcony roof"]} +{"id": "Replace the gas hot water system with a heat pump.md", "title": "Replace the gas hot water system with a heat pump", "type": "goal", "schemaData": {"title": "Replace the gas hot water system with a heat pump", "type": "goal", "status": "active", "parent": "[[Make our flat run on renewable energy]]"}, "linkTargets": ["Replace the gas hot water system with a heat pump"]} +{"id": "Swap the gas stove for induction.md", "title": "Swap the gas stove for induction", "type": "goal", "schemaData": {"title": "Swap the gas stove for induction", "type": "goal", "status": "active", "parent": "[[Make our flat run on renewable energy]]"}, "linkTargets": ["Swap the gas stove for induction"]} +{"id": "Heat the flat with reverse-cycle air conditioning.md", "title": "Heat the flat with reverse-cycle air conditioning", "type": "goal", "schemaData": {"title": "Heat the flat with reverse-cycle air conditioning", "type": "goal", "status": "active", "parent": "[[Make our flat run on renewable energy]]"}, "linkTargets": ["Heat the flat with reverse-cycle air conditioning"]} +{"id": "Reach financial independence by 50.md", "title": "Reach financial independence by 50", "type": "goal", "schemaData": {"title": "Reach financial independence by 50", "type": "goal", "status": "active", "content": "Save and invest enough that investments cover our living costs."}, "linkTargets": ["Reach financial independence by 50"], "content": "Save and invest enough that investments cover our living costs."} +{"id": "Save 30% of income.md", "title": "Save 30% of income", "type": "goal", "schemaData": {"title": "Save 30% of income", "type": "goal", "status": "active", "parent": "[[Reach financial independence by 50]]"}, "linkTargets": ["Save 30% of income"]} +{"id": "Make the maximum super contributions every year.md", "title": "Make the maximum super contributions every year", "type": "goal", "schemaData": {"title": "Make the maximum super contributions every year", "type": "goal", "status": "active", "parent": "[[Reach financial independence by 50]]"}, "linkTargets": ["Make the maximum super contributions every year"]} +{"id": "Pay off the mortgage by 45.md", "title": "Pay off the mortgage by 45", "type": "goal", "schemaData": {"title": "Pay off the mortgage by 45", "type": "goal", "status": "active", "parent": "[[Reach financial independence by 50]]"}, "linkTargets": ["Pay off the mortgage by 45"]} +{"id": "Help remote teams feel connected.md", "title": "Help remote teams feel connected", "type": "mission", "schemaData": {"title": "Help remote teams feel connected", "type": "mission", "status": "active", "content": "Informal conversation between colleagues, and time together in person."}, "linkTargets": ["Help remote teams feel connected"], "content": "Informal conversation between colleagues, and time together in person."} +{"id": "Increase how often teammates talk outside meetings.md", "title": "Increase how often teammates talk outside meetings", "type": "goal", "schemaData": {"title": "Increase how often teammates talk outside meetings", "type": "goal", "status": "active", "parent": "[[Help remote teams feel connected]]"}, "linkTargets": ["Increase how often teammates talk outside meetings"]} +{"id": "More informal conversations between colleagues.md", "title": "More informal conversations between colleagues", "type": "goal", "schemaData": {"title": "More informal conversations between colleagues", "type": "goal", "status": "active", "parent": "[[Help remote teams feel connected]]"}, "linkTargets": ["More informal conversations between colleagues"]} +{"id": "Meet in person twice a year.md", "title": "Meet in person twice a year", "type": "goal", "schemaData": {"title": "Meet in person twice a year", "type": "goal", "status": "active", "parent": "[[Help remote teams feel connected]]"}, "linkTargets": ["Meet in person twice a year"]} +{"id": "Renovate the bathroom.md", "title": "Renovate the bathroom", "type": "goal", "schemaData": {"title": "Renovate the bathroom", "type": "goal", "status": "active", "content": "New tiles, a new vanity, and fix the leaking shower."}, "linkTargets": ["Renovate the bathroom"], "content": "New tiles, a new vanity, and fix the leaking shower."} +{"id": "Choose and lay new tiles.md", "title": "Choose and lay new tiles", "type": "goal", "schemaData": {"title": "Choose and lay new tiles", "type": "goal", "status": "active", "parent": "[[Renovate the bathroom]]"}, "linkTargets": ["Choose and lay new tiles"]} +{"id": "Install a new vanity.md", "title": "Install a new vanity", "type": "goal", "schemaData": {"title": "Install a new vanity", "type": "goal", "status": "active", "parent": "[[Renovate the bathroom]]"}, "linkTargets": ["Install a new vanity"]} +{"id": "Ship version 2 of the app.md", "title": "Ship version 2 of the app", "type": "goal", "schemaData": {"title": "Ship version 2 of the app", "type": "goal", "status": "active", "content": "Release v2 in both app stores."}, "linkTargets": ["Ship version 2 of the app"], "content": "Release v2 in both app stores."} +{"id": "Release v2 on the App Store.md", "title": "Release v2 on the App Store", "type": "goal", "schemaData": {"title": "Release v2 on the App Store", "type": "goal", "status": "active", "parent": "[[Ship version 2 of the app]]"}, "linkTargets": ["Release v2 on the App Store"]} +{"id": "Release v2 on Google Play.md", "title": "Release v2 on Google Play", "type": "goal", "schemaData": {"title": "Release v2 on Google Play", "type": "goal", "status": "active", "parent": "[[Ship version 2 of the app]]"}, "linkTargets": ["Release v2 on Google Play"]} +{"id": "Grow the bakery's revenue.md", "title": "Grow the bakery's revenue", "type": "goal", "schemaData": {"title": "Grow the bakery's revenue", "type": "goal", "status": "active", "content": "More revenue from walk-in customers and from wholesale."}, "linkTargets": ["Grow the bakery's revenue"], "content": "More revenue from walk-in customers and from wholesale."} +{"id": "Win three new café wholesale accounts.md", "title": "Win three new café wholesale accounts", "type": "goal", "schemaData": {"title": "Win three new café wholesale accounts", "type": "goal", "status": "active", "parent": "[[Grow the bakery's revenue]]"}, "linkTargets": ["Win three new café wholesale accounts"]} +{"id": "Increase wholesale revenue by 20%.md", "title": "Increase wholesale revenue by 20%", "type": "goal", "schemaData": {"title": "Increase wholesale revenue by 20%", "type": "goal", "status": "active", "parent": "[[Grow the bakery's revenue]]"}, "linkTargets": ["Increase wholesale revenue by 20%"]} +{"id": "Raise average walk-in spend to $15.md", "title": "Raise average walk-in spend to $15", "type": "goal", "schemaData": {"title": "Raise average walk-in spend to $15", "type": "goal", "status": "active", "parent": "[[Grow the bakery's revenue]]"}, "linkTargets": ["Raise average walk-in spend to $15"]} +{"id": "Grow the market stall's revenue.md", "title": "Grow the market stall's revenue", "type": "goal", "schemaData": {"title": "Grow the market stall's revenue", "type": "goal", "status": "active", "content": "More revenue from the stall and from restaurant orders."}, "linkTargets": ["Grow the market stall's revenue"], "content": "More revenue from the stall and from restaurant orders."} +{"id": "Sign two new restaurant customers.md", "title": "Sign two new restaurant customers", "type": "goal", "schemaData": {"title": "Sign two new restaurant customers", "type": "goal", "status": "active", "parent": "[[Grow the market stall's revenue]]"}, "linkTargets": ["Sign two new restaurant customers"]} +{"id": "Raise average stall spend to $15.md", "title": "Raise average stall spend to $15", "type": "goal", "schemaData": {"title": "Raise average stall spend to $15", "type": "goal", "status": "active", "parent": "[[Grow the market stall's revenue]]"}, "linkTargets": ["Raise average stall spend to $15"]} +{"id": "Learn Japanese for the trip.md", "title": "Learn Japanese for the trip", "type": "goal", "schemaData": {"title": "Learn Japanese for the trip", "type": "goal", "status": "active", "content": "Hold simple conversations in shops and restaurants by October."}, "linkTargets": ["Learn Japanese for the trip"], "content": "Hold simple conversations in shops and restaurants by October."} +{"id": "Pass JLPT N5 practice tests.md", "title": "Pass JLPT N5 practice tests", "type": "goal", "schemaData": {"title": "Pass JLPT N5 practice tests", "type": "goal", "status": "active", "parent": "[[Learn Japanese for the trip]]"}, "linkTargets": ["Pass JLPT N5 practice tests"]} +{"id": "Hold a five-minute conversation with a tutor.md", "title": "Hold a five-minute conversation with a tutor", "type": "goal", "schemaData": {"title": "Hold a five-minute conversation with a tutor", "type": "goal", "status": "active", "parent": "[[Learn Japanese for the trip]]"}, "linkTargets": ["Hold a five-minute conversation with a tutor"]} +{"id": "Speak basic Japanese with a tutor for five minutes.md", "title": "Speak basic Japanese with a tutor for five minutes", "type": "goal", "schemaData": {"title": "Speak basic Japanese with a tutor for five minutes", "type": "goal", "status": "active", "parent": "[[Learn Japanese for the trip]]"}, "linkTargets": ["Speak basic Japanese with a tutor for five minutes"]} +{"id": "Run a sustainable family farm.md", "title": "Run a sustainable family farm", "type": "mission", "schemaData": {"title": "Run a sustainable family farm", "type": "mission", "status": "active", "content": "Profitable every year, healthier soil, and a farm our kids can take over."}, "linkTargets": ["Run a sustainable family farm"], "content": "Profitable every year, healthier soil, and a farm our kids can take over."} +{"id": "Turn a profit every year.md", "title": "Turn a profit every year", "type": "goal", "schemaData": {"title": "Turn a profit every year", "type": "goal", "status": "active", "parent": "[[Run a sustainable family farm]]"}, "linkTargets": ["Turn a profit every year"]} +{"id": "Raise soil organic carbon by 1%.md", "title": "Raise soil organic carbon by 1%", "type": "goal", "schemaData": {"title": "Raise soil organic carbon by 1%", "type": "goal", "status": "active", "parent": "[[Run a sustainable family farm]]"}, "linkTargets": ["Raise soil organic carbon by 1%"]} +{"id": "Run a sustainable market garden.md", "title": "Run a sustainable market garden", "type": "mission", "schemaData": {"title": "Run a sustainable market garden", "type": "mission", "status": "active", "content": "Profitable every year, healthier soil, and a garden our kids can take over."}, "linkTargets": ["Run a sustainable market garden"], "content": "Profitable every year, healthier soil, and a garden our kids can take over."} +{"id": "Make a profit every season.md", "title": "Make a profit every season", "type": "goal", "schemaData": {"title": "Make a profit every season", "type": "goal", "status": "active", "parent": "[[Run a sustainable market garden]]"}, "linkTargets": ["Make a profit every season"]} +{"id": "Raise soil organic matter by 1%.md", "title": "Raise soil organic matter by 1%", "type": "goal", "schemaData": {"title": "Raise soil organic matter by 1%", "type": "goal", "status": "active", "parent": "[[Run a sustainable market garden]]"}, "linkTargets": ["Raise soil organic matter by 1%"]} +{"id": "Train the kids to run the garden by 2035.md", "title": "Train the kids to run the garden by 2035", "type": "goal", "schemaData": {"title": "Train the kids to run the garden by 2035", "type": "goal", "status": "active", "parent": "[[Run a sustainable market garden]]"}, "linkTargets": ["Train the kids to run the garden by 2035"]} +{"id": "Be the go-to accountant for tradespeople.md", "title": "Be the go-to accountant for tradespeople", "type": "mission", "schemaData": {"title": "Be the go-to accountant for tradespeople", "type": "mission", "status": "active", "content": "Known and chosen by tradespeople across Sydney."}, "linkTargets": ["Be the go-to accountant for tradespeople"], "content": "Known and chosen by tradespeople across Sydney."} +{"id": "Win 50 tradie clients in Sydney.md", "title": "Win 50 tradie clients in Sydney", "type": "goal", "schemaData": {"title": "Win 50 tradie clients in Sydney", "type": "goal", "status": "active", "parent": "[[Be the go-to accountant for tradespeople]]"}, "linkTargets": ["Win 50 tradie clients in Sydney"]} +{"id": "Become the top-rated accountant for plumbers.md", "title": "Become the top-rated accountant for plumbers", "type": "goal", "schemaData": {"title": "Become the top-rated accountant for plumbers", "type": "goal", "status": "active", "parent": "[[Be the go-to accountant for tradespeople]]"}, "linkTargets": ["Become the top-rated accountant for plumbers"]} +{"id": "Win 20 plumber clients.md", "title": "Win 20 plumber clients", "type": "goal", "schemaData": {"title": "Win 20 plumber clients", "type": "goal", "status": "active", "parent": "[[Be the go-to accountant for tradespeople]]"}, "linkTargets": ["Win 20 plumber clients"]} +{"id": "Give independent bookshops the tools to compete online.md", "title": "Give independent bookshops the tools to compete online", "type": "mission", "schemaData": {"title": "Give independent bookshops the tools to compete online", "type": "mission", "status": "active"}, "linkTargets": ["Give independent bookshops the tools to compete online"]} +{"id": "Launch online ordering for 100 bookshops.md", "title": "Launch online ordering for 100 bookshops", "type": "goal", "schemaData": {"title": "Launch online ordering for 100 bookshops", "type": "goal", "status": "active", "parent": "[[Give independent bookshops the tools to compete online]]"}, "linkTargets": ["Launch online ordering for 100 bookshops"]} +{"id": "Help bookshops show live stock on their own websites.md", "title": "Help bookshops show live stock on their own websites", "type": "goal", "schemaData": {"title": "Help bookshops show live stock on their own websites", "type": "goal", "status": "active", "parent": "[[Give independent bookshops the tools to compete online]]"}, "linkTargets": ["Help bookshops show live stock on their own websites"]} +{"id": "Open our own flagship bookshop.md", "title": "Open our own flagship bookshop", "type": "goal", "schemaData": {"title": "Open our own flagship bookshop", "type": "goal", "status": "active", "parent": "[[Give independent bookshops the tools to compete online]]"}, "linkTargets": ["Open our own flagship bookshop"]} +{"id": "Grow our social media following.md", "title": "Grow our social media following", "type": "goal", "schemaData": {"title": "Grow our social media following", "type": "goal", "status": "active", "parent": "[[Give independent bookshops the tools to compete online]]"}, "linkTargets": ["Grow our social media following"]} +{"id": "Give independent bookshops online tools.md", "title": "Give independent bookshops online tools", "type": "goal", "schemaData": {"title": "Give independent bookshops online tools", "type": "goal", "status": "active", "parent": "[[Give independent bookshops the tools to compete online]]"}, "linkTargets": ["Give independent bookshops online tools"]} +{"id": "Sell e-books on Kindle.md", "title": "Sell e-books on Kindle", "type": "goal", "schemaData": {"title": "Sell e-books on Kindle", "type": "goal", "status": "active", "parent": "[[Give independent bookshops the tools to compete online]]"}, "linkTargets": ["Sell e-books on Kindle"]} +{"id": "Sign 100 bookshops to the ordering pilot.md", "title": "Sign 100 bookshops to the ordering pilot", "type": "goal", "schemaData": {"title": "Sign 100 bookshops to the ordering pilot", "type": "goal", "status": "active", "parent": "[[Launch online ordering for 100 bookshops]]"}, "linkTargets": ["Sign 100 bookshops to the ordering pilot"]} +{"id": "Build a checkout that works on phones.md", "title": "Build a checkout that works on phones", "type": "goal", "schemaData": {"title": "Build a checkout that works on phones", "type": "goal", "status": "active", "parent": "[[Launch online ordering for 100 bookshops]]"}, "linkTargets": ["Build a checkout that works on phones"]} +{"id": "Get 100 bookshops ordering online.md", "title": "Get 100 bookshops ordering online", "type": "goal", "schemaData": {"title": "Get 100 bookshops ordering online", "type": "goal", "status": "active", "parent": "[[Launch online ordering for 100 bookshops]]"}, "linkTargets": ["Get 100 bookshops ordering online"]} +{"id": "Run a reading festival.md", "title": "Run a reading festival", "type": "goal", "schemaData": {"title": "Run a reading festival", "type": "goal", "status": "active", "parent": "[[Launch online ordering for 100 bookshops]]"}, "linkTargets": ["Run a reading festival"]} +{"id": "Improve our customer support.md", "title": "Improve our customer support", "type": "goal", "schemaData": {"title": "Improve our customer support", "type": "goal", "status": "active", "parent": "[[Launch online ordering for 100 bookshops]]"}, "linkTargets": ["Improve our customer support"]} +{"id": "Keep my family healthy and close.md", "title": "Keep my family healthy and close", "type": "mission", "schemaData": {"title": "Keep my family healthy and close", "type": "mission", "status": "active"}, "linkTargets": ["Keep my family healthy and close"]} +{"id": "Cook dinner together three nights a week.md", "title": "Cook dinner together three nights a week", "type": "goal", "schemaData": {"title": "Cook dinner together three nights a week", "type": "goal", "status": "active", "parent": "[[Keep my family healthy and close]]"}, "linkTargets": ["Cook dinner together three nights a week"]} +{"id": "Get a promotion this year.md", "title": "Get a promotion this year", "type": "goal", "schemaData": {"title": "Get a promotion this year", "type": "goal", "status": "active", "parent": "[[Keep my family healthy and close]]"}, "linkTargets": ["Get a promotion this year"]} +{"id": "Take the kids hiking every month.md", "title": "Take the kids hiking every month", "type": "goal", "schemaData": {"title": "Take the kids hiking every month", "type": "goal", "status": "active", "parent": "[[Keep my family healthy and close]]"}, "linkTargets": ["Take the kids hiking every month"]} +{"id": "Be healthier.md", "title": "Be healthier", "type": "goal", "schemaData": {"title": "Be healthier", "type": "goal", "status": "active", "parent": "[[Keep my family healthy and close]]"}, "linkTargets": ["Be healthier"]} +{"id": "Stay fit into my fifties.md", "title": "Stay fit into my fifties", "type": "mission", "schemaData": {"title": "Stay fit into my fifties", "type": "mission", "status": "active"}, "linkTargets": ["Stay fit into my fifties"]} +{"id": "Run the Six Foot Track in March.md", "title": "Run the Six Foot Track in March", "type": "goal", "schemaData": {"title": "Run the Six Foot Track in March", "type": "goal", "status": "active", "parent": "[[Stay fit into my fifties]]"}, "linkTargets": ["Run the Six Foot Track in March"]} +{"id": "Build to 80 km weeks by January.md", "title": "Build to 80 km weeks by January", "type": "goal", "schemaData": {"title": "Build to 80 km weeks by January", "type": "goal", "status": "active", "parent": "[[Run the Six Foot Track in March]]"}, "linkTargets": ["Build to 80 km weeks by January"]} +{"id": "Buy new trail shoes.md", "title": "Buy new trail shoes", "type": "goal", "schemaData": {"title": "Buy new trail shoes", "type": "goal", "status": "active", "parent": "[[Run the Six Foot Track in March]]"}, "linkTargets": ["Buy new trail shoes"]} +{"id": "Lose 5 kg before race day.md", "title": "Lose 5 kg before race day", "type": "goal", "schemaData": {"title": "Lose 5 kg before race day", "type": "goal", "status": "active", "parent": "[[Run the Six Foot Track in March]]"}, "linkTargets": ["Lose 5 kg before race day"]} +{"id": "Every independent bookshop thrives.md", "title": "Every independent bookshop thrives", "type": "vision", "schemaData": {"title": "Every independent bookshop thrives", "type": "vision", "status": "active", "content": "Every town has a bookshop that makes a good living."}, "linkTargets": ["Every independent bookshop thrives"], "content": "Every town has a bookshop that makes a good living."} +{"id": "Help bookshops sell online and keep readers visiting.md", "title": "Help bookshops sell online and keep readers visiting", "type": "mission", "schemaData": {"title": "Help bookshops sell online and keep readers visiting", "type": "mission", "status": "active", "parent": "[[Every independent bookshop thrives]]", "content": "Software that helps shops sell online, while readers keep coming into the shop."}, "linkTargets": ["Help bookshops sell online and keep readers visiting"], "content": "Software that helps shops sell online, while readers keep coming into the shop."} +{"id": "Grow our revenue to $2M.md", "title": "Grow our revenue to $2M", "type": "goal", "schemaData": {"title": "Grow our revenue to $2M", "type": "goal", "status": "active", "parent": "[[Help bookshops sell online and keep readers visiting]]", "content": "Revenue from bookshop subscriptions and transaction fees."}, "linkTargets": ["Grow our revenue to $2M"], "content": "Revenue from bookshop subscriptions and transaction fees."} +{"id": "Bookshops can't take online orders without a website.md", "title": "Bookshops can't take online orders without a website", "type": "opportunity", "schemaData": {"title": "Bookshops can't take online orders without a website", "type": "opportunity", "status": "active", "parent": "[[Grow our revenue to $2M]]"}, "linkTargets": ["Bookshops can't take online orders without a website"]} +{"id": "Readers want to buy direct from us at lower prices than shops.md", "title": "Readers want to buy direct from us at lower prices than shops", "type": "opportunity", "schemaData": {"title": "Readers want to buy direct from us at lower prices than shops", "type": "opportunity", "status": "active", "parent": "[[Grow our revenue to $2M]]"}, "linkTargets": ["Readers want to buy direct from us at lower prices than shops"]} +{"id": "Publishers want to sell their backlist through us direct to readers.md", "title": "Publishers want to sell their backlist through us direct to readers", "type": "opportunity", "schemaData": {"title": "Publishers want to sell their backlist through us direct to readers", "type": "opportunity", "status": "active", "parent": "[[Grow our revenue to $2M]]"}, "linkTargets": ["Publishers want to sell their backlist through us direct to readers"]} +{"id": "Shops spend hours updating stock on each marketplace.md", "title": "Shops spend hours updating stock on each marketplace", "type": "opportunity", "schemaData": {"title": "Shops spend hours updating stock on each marketplace", "type": "opportunity", "status": "active", "parent": "[[Grow our revenue to $2M]]"}, "linkTargets": ["Shops spend hours updating stock on each marketplace"]} +{"id": "Big chains want to license our ordering software.md", "title": "Big chains want to license our ordering software", "type": "opportunity", "schemaData": {"title": "Big chains want to license our ordering software", "type": "opportunity", "status": "active", "parent": "[[Grow our revenue to $2M]]"}, "linkTargets": ["Big chains want to license our ordering software"]} +{"id": "Shops want click-and-collect so readers come into the store.md", "title": "Shops want click-and-collect so readers come into the store", "type": "opportunity", "schemaData": {"title": "Shops want click-and-collect so readers come into the store", "type": "opportunity", "status": "active", "parent": "[[Grow our revenue to $2M]]"}, "linkTargets": ["Shops want click-and-collect so readers come into the store"]} +{"id": "A city where anyone can cycle safely.md", "title": "A city where anyone can cycle safely", "type": "vision", "schemaData": {"title": "A city where anyone can cycle safely", "type": "vision", "status": "active", "content": "Children and grandparents ride to school and to the shops."}, "linkTargets": ["A city where anyone can cycle safely"], "content": "Children and grandparents ride to school and to the shops."} +{"id": "Make cycling the easiest way to commute in Sydney.md", "title": "Make cycling the easiest way to commute in Sydney", "type": "mission", "schemaData": {"title": "Make cycling the easiest way to commute in Sydney", "type": "mission", "status": "active", "parent": "[[A city where anyone can cycle safely]]", "content": "Safe routes, secure parking and showers at work."}, "linkTargets": ["Make cycling the easiest way to commute in Sydney"], "content": "Safe routes, secure parking and showers at work."} +{"id": "Get 10,000 people commuting by bike.md", "title": "Get 10,000 people commuting by bike", "type": "goal", "schemaData": {"title": "Get 10,000 people commuting by bike", "type": "goal", "status": "active", "parent": "[[Make cycling the easiest way to commute in Sydney]]", "content": "Counted at the city cordon on weekdays."}, "linkTargets": ["Get 10,000 people commuting by bike"], "content": "Counted at the city cordon on weekdays."} +{"id": "New riders don't know the safe routes to work.md", "title": "New riders don't know the safe routes to work", "type": "opportunity", "schemaData": {"title": "New riders don't know the safe routes to work", "type": "opportunity", "status": "active", "parent": "[[Get 10,000 people commuting by bike]]"}, "linkTargets": ["New riders don't know the safe routes to work"]} +{"id": "Drivers want cheaper parking near stations.md", "title": "Drivers want cheaper parking near stations", "type": "opportunity", "schemaData": {"title": "Drivers want cheaper parking near stations", "type": "opportunity", "status": "active", "parent": "[[Get 10,000 people commuting by bike]]"}, "linkTargets": ["Drivers want cheaper parking near stations"]} +{"id": "Riders want a leaderboard that rewards the fastest commute times.md", "title": "Riders want a leaderboard that rewards the fastest commute times", "type": "opportunity", "schemaData": {"title": "Riders want a leaderboard that rewards the fastest commute times", "type": "opportunity", "status": "active", "parent": "[[Get 10,000 people commuting by bike]]"}, "linkTargets": ["Riders want a leaderboard that rewards the fastest commute times"]} +{"id": "Riders have nowhere secure to leave bikes at work.md", "title": "Riders have nowhere secure to leave bikes at work", "type": "opportunity", "schemaData": {"title": "Riders have nowhere secure to leave bikes at work", "type": "opportunity", "status": "active", "parent": "[[Get 10,000 people commuting by bike]]"}, "linkTargets": ["Riders have nowhere secure to leave bikes at work"]} +{"id": "A healthy, connected family life.md", "title": "A healthy, connected family life", "type": "vision", "schemaData": {"title": "A healthy, connected family life", "type": "vision", "status": "active", "content": "We are healthy, and we know each other well."}, "linkTargets": ["A healthy, connected family life"], "content": "We are healthy, and we know each other well."} +{"id": "Spend more time with the kids while they are young.md", "title": "Spend more time with the kids while they are young", "type": "mission", "schemaData": {"title": "Spend more time with the kids while they are young", "type": "mission", "status": "active", "parent": "[[A healthy, connected family life]]", "content": "Weekends and evenings belong to the family."}, "linkTargets": ["Spend more time with the kids while they are young"], "content": "Weekends and evenings belong to the family."} +{"id": "Earn an extra $20k this year.md", "title": "Earn an extra $20k this year", "type": "goal", "schemaData": {"title": "Earn an extra $20k this year", "type": "goal", "status": "active", "parent": "[[Spend more time with the kids while they are young]]", "content": "To pay for a family trip to Japan."}, "linkTargets": ["Earn an extra $20k this year"], "content": "To pay for a family trip to Japan."} +{"id": "Weekend freelance work pays well.md", "title": "Weekend freelance work pays well", "type": "opportunity", "schemaData": {"title": "Weekend freelance work pays well", "type": "opportunity", "status": "active", "parent": "[[Earn an extra $20k this year]]"}, "linkTargets": ["Weekend freelance work pays well"]} +{"id": "We could sell the second car we barely use.md", "title": "We could sell the second car we barely use", "type": "opportunity", "schemaData": {"title": "We could sell the second car we barely use", "type": "opportunity", "status": "active", "parent": "[[Earn an extra $20k this year]]"}, "linkTargets": ["We could sell the second car we barely use"]} +{"id": "My employer pays a bonus for overtime.md", "title": "My employer pays a bonus for overtime", "type": "opportunity", "schemaData": {"title": "My employer pays a bonus for overtime", "type": "opportunity", "status": "active", "parent": "[[Earn an extra $20k this year]]"}, "linkTargets": ["My employer pays a bonus for overtime"]} +{"id": "The kids' outgrown toys and gear could sell at weekend markets, with the kids.md", "title": "The kids' outgrown toys and gear could sell at weekend markets, with the kids", "type": "opportunity", "schemaData": {"title": "The kids' outgrown toys and gear could sell at weekend markets, with the kids", "type": "opportunity", "status": "active", "parent": "[[Earn an extra $20k this year]]"}, "linkTargets": ["The kids' outgrown toys and gear could sell at weekend markets, with the kids"]} +{"id": "Customers trust us with their money.md", "title": "Customers trust us with their money", "type": "vision", "schemaData": {"title": "Customers trust us with their money", "type": "vision", "status": "active", "content": "Every customer would recommend us to a friend."}, "linkTargets": ["Customers trust us with their money"], "content": "Every customer would recommend us to a friend."} +{"id": "Make banking simple and fair for small businesses.md", "title": "Make banking simple and fair for small businesses", "type": "mission", "schemaData": {"title": "Make banking simple and fair for small businesses", "type": "mission", "status": "active", "parent": "[[Customers trust us with their money]]", "content": "Clear prices and no surprises."}, "linkTargets": ["Make banking simple and fair for small businesses"], "content": "Clear prices and no surprises."} +{"id": "Increase fee revenue by 15%.md", "title": "Increase fee revenue by 15%", "type": "goal", "schemaData": {"title": "Increase fee revenue by 15%", "type": "goal", "status": "active", "parent": "[[Make banking simple and fair for small businesses]]", "content": "Fee revenue across all business accounts."}, "linkTargets": ["Increase fee revenue by 15%"], "content": "Fee revenue across all business accounts."} +{"id": "Businesses want instant payouts and would pay for them.md", "title": "Businesses want instant payouts and would pay for them", "type": "opportunity", "schemaData": {"title": "Businesses want instant payouts and would pay for them", "type": "opportunity", "status": "active", "parent": "[[Increase fee revenue by 15%]]"}, "linkTargets": ["Businesses want instant payouts and would pay for them"]} +{"id": "Many customers don't notice when the account fee rises.md", "title": "Many customers don't notice when the account fee rises", "type": "opportunity", "schemaData": {"title": "Many customers don't notice when the account fee rises", "type": "opportunity", "status": "active", "parent": "[[Increase fee revenue by 15%]]"}, "linkTargets": ["Many customers don't notice when the account fee rises"]} +{"id": "Overdraft fees could rise without losing customers.md", "title": "Overdraft fees could rise without losing customers", "type": "opportunity", "schemaData": {"title": "Overdraft fees could rise without losing customers", "type": "opportunity", "status": "active", "parent": "[[Increase fee revenue by 15%]]"}, "linkTargets": ["Overdraft fees could rise without losing customers"]} +{"id": "Hold the problem statements.md", "title": "Hold the problem statements", "type": "goal", "schemaData": {"title": "Hold the problem statements", "type": "goal", "status": "active"}, "linkTargets": ["Hold the problem statements"]} +{"id": "Opportunity 01.md", "title": "Opportunity 01", "type": "opportunity", "schemaData": {"title": "Opportunity 01", "type": "opportunity", "status": "active", "parent": "[[Hold the problem statements]]"}, "linkTargets": ["Opportunity 01"]} +{"id": "Opportunity 01.md#Problem statement", "title": "Problem statement", "type": "problem_statement", "schemaData": {"title": "Problem statement", "type": "problem_statement", "status": "identified", "parent": "[[Opportunity 01]]", "content": "Readers can't see what a bookshop has in stock before they visit, so they buy from online retailers."}, "linkTargets": ["Opportunity 01#Problem statement"], "content": "Readers can't see what a bookshop has in stock before they visit, so they buy from online retailers."} +{"id": "Opportunity 02.md", "title": "Opportunity 02", "type": "opportunity", "schemaData": {"title": "Opportunity 02", "type": "opportunity", "status": "active", "parent": "[[Hold the problem statements]]"}, "linkTargets": ["Opportunity 02"]} +{"id": "Opportunity 02.md#Problem statement", "title": "Problem statement", "type": "problem_statement", "schemaData": {"title": "Problem statement", "type": "problem_statement", "status": "identified", "parent": "[[Opportunity 02]]", "content": "New cyclists stop riding within a month because they don't feel safe on the routes they know."}, "linkTargets": ["Opportunity 02#Problem statement"], "content": "New cyclists stop riding within a month because they don't feel safe on the routes they know."} +{"id": "Opportunity 03.md", "title": "Opportunity 03", "type": "opportunity", "schemaData": {"title": "Opportunity 03", "type": "opportunity", "status": "active", "parent": "[[Hold the problem statements]]"}, "linkTargets": ["Opportunity 03"]} +{"id": "Opportunity 03.md#Problem statement", "title": "Problem statement", "type": "problem_statement", "schemaData": {"title": "Problem statement", "type": "problem_statement", "status": "identified", "parent": "[[Opportunity 03]]", "content": "Small cafés throw away about 15% of the food they buy, which wipes out most of their margin."}, "linkTargets": ["Opportunity 03#Problem statement"], "content": "Small cafés throw away about 15% of the food they buy, which wipes out most of their margin."} +{"id": "Opportunity 04.md", "title": "Opportunity 04", "type": "opportunity", "schemaData": {"title": "Opportunity 04", "type": "opportunity", "status": "active", "parent": "[[Hold the problem statements]]"}, "linkTargets": ["Opportunity 04"]} +{"id": "Opportunity 04.md#Problem statement", "title": "Problem statement", "type": "problem_statement", "schemaData": {"title": "Problem statement", "type": "problem_statement", "status": "identified", "parent": "[[Opportunity 04]]", "content": "Parents of young kids get less than an hour a day together on weekdays because of long commutes."}, "linkTargets": ["Opportunity 04#Problem statement"], "content": "Parents of young kids get less than an hour a day together on weekdays because of long commutes."} +{"id": "Opportunity 05.md", "title": "Opportunity 05", "type": "opportunity", "schemaData": {"title": "Opportunity 05", "type": "opportunity", "status": "active", "parent": "[[Hold the problem statements]]"}, "linkTargets": ["Opportunity 05"]} +{"id": "Opportunity 05.md#Problem statement", "title": "Problem statement", "type": "problem_statement", "schemaData": {"title": "Problem statement", "type": "problem_statement", "status": "identified", "parent": "[[Opportunity 05]]", "content": "We need a mobile app so readers can check stock before they visit."}, "linkTargets": ["Opportunity 05#Problem statement"], "content": "We need a mobile app so readers can check stock before they visit."} +{"id": "Opportunity 06.md", "title": "Opportunity 06", "type": "opportunity", "schemaData": {"title": "Opportunity 06", "type": "opportunity", "status": "active", "parent": "[[Hold the problem statements]]"}, "linkTargets": ["Opportunity 06"]} +{"id": "Opportunity 06.md#Problem statement", "title": "Problem statement", "type": "problem_statement", "schemaData": {"title": "Problem statement", "type": "problem_statement", "status": "identified", "parent": "[[Opportunity 06]]", "content": "Bookshops don't have an online ordering system."}, "linkTargets": ["Opportunity 06#Problem statement"], "content": "Bookshops don't have an online ordering system."} +{"id": "Opportunity 07.md", "title": "Opportunity 07", "type": "opportunity", "schemaData": {"title": "Opportunity 07", "type": "opportunity", "status": "active", "parent": "[[Hold the problem statements]]"}, "linkTargets": ["Opportunity 07"]} +{"id": "Opportunity 07.md#Problem statement", "title": "Problem statement", "type": "problem_statement", "schemaData": {"title": "Problem statement", "type": "problem_statement", "status": "identified", "parent": "[[Opportunity 07]]", "content": "Cafés lack an AI forecasting tool for ordering stock."}, "linkTargets": ["Opportunity 07#Problem statement"], "content": "Cafés lack an AI forecasting tool for ordering stock."} +{"id": "Opportunity 08.md", "title": "Opportunity 08", "type": "opportunity", "schemaData": {"title": "Opportunity 08", "type": "opportunity", "status": "active", "parent": "[[Hold the problem statements]]"}, "linkTargets": ["Opportunity 08"]} +{"id": "Opportunity 08.md#Problem statement", "title": "Problem statement", "type": "problem_statement", "schemaData": {"title": "Problem statement", "type": "problem_statement", "status": "identified", "parent": "[[Opportunity 08]]", "content": "The problem is that we haven't built click-and-collect yet."}, "linkTargets": ["Opportunity 08#Problem statement"], "content": "The problem is that we haven't built click-and-collect yet."} +{"id": "Opportunity 09.md", "title": "Opportunity 09", "type": "opportunity", "schemaData": {"title": "Opportunity 09", "type": "opportunity", "status": "active", "parent": "[[Hold the problem statements]]"}, "linkTargets": ["Opportunity 09"]} +{"id": "Opportunity 09.md#Problem statement", "title": "Problem statement", "type": "problem_statement", "schemaData": {"title": "Problem statement", "type": "problem_statement", "status": "identified", "parent": "[[Opportunity 09]]", "content": "Readers can't check stock before they visit, so a live stock widget would help."}, "linkTargets": ["Opportunity 09#Problem statement"], "content": "Readers can't check stock before they visit, so a live stock widget would help."} +{"id": "Opportunity 10.md", "title": "Opportunity 10", "type": "opportunity", "schemaData": {"title": "Opportunity 10", "type": "opportunity", "status": "active", "parent": "[[Hold the problem statements]]"}, "linkTargets": ["Opportunity 10"]} +{"id": "Opportunity 10.md#Problem statement", "title": "Problem statement", "type": "problem_statement", "schemaData": {"title": "Problem statement", "type": "problem_statement", "status": "identified", "parent": "[[Opportunity 10]]", "content": "Café owners can't predict how many customers will come each day, so they over-order."}, "linkTargets": ["Opportunity 10#Problem statement"], "content": "Café owners can't predict how many customers will come each day, so they over-order."} +{"id": "Opportunity 11.md", "title": "Opportunity 11", "type": "opportunity", "schemaData": {"title": "Opportunity 11", "type": "opportunity", "status": "active", "parent": "[[Hold the problem statements]]"}, "linkTargets": ["Opportunity 11"]} +{"id": "Opportunity 11.md#Problem statement", "title": "Problem statement", "type": "problem_statement", "schemaData": {"title": "Problem statement", "type": "problem_statement", "status": "identified", "parent": "[[Opportunity 11]]", "content": "Small businesses wait three days for card payouts and can't pay suppliers on time."}, "linkTargets": ["Opportunity 11#Problem statement"], "content": "Small businesses wait three days for card payouts and can't pay suppliers on time."} +{"id": "Opportunity 12.md", "title": "Opportunity 12", "type": "opportunity", "schemaData": {"title": "Opportunity 12", "type": "opportunity", "status": "active", "parent": "[[Hold the problem statements]]"}, "linkTargets": ["Opportunity 12"]} +{"id": "Opportunity 12.md#Problem statement", "title": "Problem statement", "type": "problem_statement", "schemaData": {"title": "Problem statement", "type": "problem_statement", "status": "identified", "parent": "[[Opportunity 12]]", "content": "Customers need a better dashboard to understand their spending."}, "linkTargets": ["Opportunity 12#Problem statement"], "content": "Customers need a better dashboard to understand their spending."} +{"id": "Opportunity 13.md", "title": "Opportunity 13", "type": "opportunity", "schemaData": {"title": "Opportunity 13", "type": "opportunity", "status": "active", "parent": "[[Hold the problem statements]]"}, "linkTargets": ["Opportunity 13"]} +{"id": "Opportunity 13.md#Problem statement", "title": "Problem statement", "type": "problem_statement", "schemaData": {"title": "Problem statement", "type": "problem_statement", "status": "identified", "parent": "[[Opportunity 13]]", "content": "Shop staff spend six hours a week re-entering stock into three marketplaces."}, "linkTargets": ["Opportunity 13#Problem statement"], "content": "Shop staff spend six hours a week re-entering stock into three marketplaces."} +{"id": "Opportunity 14.md", "title": "Opportunity 14", "type": "opportunity", "schemaData": {"title": "Opportunity 14", "type": "opportunity", "status": "active", "parent": "[[Hold the problem statements]]"}, "linkTargets": ["Opportunity 14"]} +{"id": "Opportunity 14.md#Problem statement", "title": "Problem statement", "type": "problem_statement", "schemaData": {"title": "Problem statement", "type": "problem_statement", "status": "identified", "parent": "[[Opportunity 14]]", "content": "Without a Xero integration, accountants re-key every invoice."}, "linkTargets": ["Opportunity 14#Problem statement"], "content": "Without a Xero integration, accountants re-key every invoice."} +{"id": "Opportunity 15.md", "title": "Opportunity 15", "type": "opportunity", "schemaData": {"title": "Opportunity 15", "type": "opportunity", "status": "active", "parent": "[[Hold the problem statements]]"}, "linkTargets": ["Opportunity 15"]} +{"id": "Opportunity 15.md#Problem statement", "title": "Problem statement", "type": "problem_statement", "schemaData": {"title": "Problem statement", "type": "problem_statement", "status": "identified", "parent": "[[Opportunity 15]]", "content": "Members cancel because the gym is crowded at 6 pm, so we should open the second room for evening classes."}, "linkTargets": ["Opportunity 15#Problem statement"], "content": "Members cancel because the gym is crowded at 6 pm, so we should open the second room for evening classes."} +{"id": "Opportunity 16.md", "title": "Opportunity 16", "type": "opportunity", "schemaData": {"title": "Opportunity 16", "type": "opportunity", "status": "active", "parent": "[[Hold the problem statements]]"}, "linkTargets": ["Opportunity 16"]} +{"id": "Opportunity 16.md#Problem statement", "title": "Problem statement", "type": "problem_statement", "schemaData": {"title": "Problem statement", "type": "problem_statement", "status": "identified", "parent": "[[Opportunity 16]]", "content": "Members cancel because the gym is too crowded between 5 and 7 pm."}, "linkTargets": ["Opportunity 16#Problem statement"], "content": "Members cancel because the gym is too crowded between 5 and 7 pm."} diff --git a/evals/semantic/goal-advances-parent.json5 b/evals/semantic/goal-advances-parent.json5 new file mode 100644 index 00000000..76f22aae --- /dev/null +++ b/evals/semantic/goal-advances-parent.json5 @@ -0,0 +1,31 @@ +{ + space: 'strategy-trees', + expected: { + 'goal-advances-parent': { + 'Launch online ordering for 100 bookshops.md': 'advances', + 'Help bookshops show live stock on their own websites.md': 'advances', + // About bookshops, but not tools for them. + 'Open our own flagship bookshop.md': 'misplaced', + 'Grow our social media following.md': 'weak-link', + // Restates the mission. + 'Give independent bookshops online tools.md': 'weak-link', + 'Sell e-books on Kindle.md': 'misplaced', + 'Sign 100 bookshops to the ordering pilot.md': 'advances', + 'Build a checkout that works on phones.md': 'advances', + // Restates the parent goal. + 'Get 100 bookshops ordering online.md': 'weak-link', + 'Run a reading festival.md': 'misplaced', + // Support could help the launch, but the goal does not say how. + 'Improve our customer support.md': 'weak-link', + 'Cook dinner together three nights a week.md': 'advances', + 'Get a promotion this year.md': ['weak-link', 'misplaced'], + 'Take the kids hiking every month.md': 'advances', + // Restates half of the mission. + 'Be healthier.md': 'weak-link', + 'Build to 80 km weeks by January.md': 'advances', + // A means to the goal more than a part of it. + 'Buy new trail shoes.md': ['advances', 'weak-link'], + 'Lose 5 kg before race day.md': 'advances', + }, + }, +} diff --git a/evals/semantic/goals-cover-parent.json5 b/evals/semantic/goals-cover-parent.json5 new file mode 100644 index 00000000..9069c560 --- /dev/null +++ b/evals/semantic/goals-cover-parent.json5 @@ -0,0 +1,29 @@ +{ + space: 'strategy-trees', + expected: { + 'goals-cover-parent': { + 'Help small cafés run a profitable kitchen.md': 'pass', + // Nothing covers staying injury-free, which the body names. + 'Get ready for the Six Foot Track ultra.md': 'fail', + // Editing is named, and no goal covers it. + 'Launch the podcast.md': 'fail', + 'Improve team health.md': 'pass', + 'Grow the newsletter.md': 'pass', + // Heating is named, and no goal covers it. Contrast with the next scenario. + 'Make our home run on renewable energy.md': 'fail', + // Contrast with the previous scenario. + 'Make our flat run on renewable energy.md': 'pass', + 'Help remote teams feel connected.md': 'pass', + 'Renovate the bathroom.md': 'fail', + 'Ship version 2 of the app.md': 'pass', + "Grow the bakery's revenue.md": 'pass', + "Grow the market stall's revenue.md": 'pass', + 'Learn Japanese for the trip.md': 'pass', + // No goal covers handing the farm to the kids. Contrast with the next scenario. + 'Run a sustainable family farm.md': 'fail', + // Contrast with the previous scenario. + 'Run a sustainable market garden.md': 'pass', + 'Be the go-to accountant for tradespeople.md': 'pass', + }, + }, +} diff --git a/evals/semantic/goals-distinct.json5 b/evals/semantic/goals-distinct.json5 new file mode 100644 index 00000000..24b07da4 --- /dev/null +++ b/evals/semantic/goals-distinct.json5 @@ -0,0 +1,31 @@ +{ + space: 'strategy-trees', + expected: { + 'goals-distinct': { + 'Help small cafés run a profitable kitchen.md': 'distinct', + 'Get ready for the Six Foot Track ultra.md': 'distinct', + 'Launch the podcast.md': 'distinct', + // After-hours messages and working late are the same result. + 'Improve team health.md': 'duplicate', + // Social media subscribers count towards the 5,000. + 'Grow the newsletter.md': 'overlapping', + 'Make our home run on renewable energy.md': 'distinct', + 'Make our flat run on renewable energy.md': 'distinct', + // Super contributions are part of the 30% saved. + 'Reach financial independence by 50.md': 'overlapping', + // The first two are the same result in different words. + 'Help remote teams feel connected.md': 'duplicate', + 'Renovate the bathroom.md': 'distinct', + 'Ship version 2 of the app.md': 'distinct', + // New wholesale accounts are part of wholesale revenue. Contrast with the next scenario. + "Grow the bakery's revenue.md": 'overlapping', + // Contrast with the previous scenario. + "Grow the market stall's revenue.md": 'distinct', + 'Learn Japanese for the trip.md': 'duplicate', + 'Run a sustainable family farm.md': 'distinct', + 'Run a sustainable market garden.md': 'distinct', + // Plumber clients count towards the 50 tradie clients. + 'Be the go-to accountant for tradespeople.md': 'overlapping', + }, + }, +} diff --git a/evals/semantic/okf-description-informative.json5 b/evals/semantic/okf-description-informative.json5 new file mode 100644 index 00000000..3504ce75 --- /dev/null +++ b/evals/semantic/okf-description-informative.json5 @@ -0,0 +1,27 @@ +{ + space: 'okf', + expected: { + 'okf-description-informative': { + 'Weekly active teams.md': 'pass', + 'orders.md': 'pass', + 'Incident response.md': 'pass', + 'Churn.md': 'pass', + 'Monthly revenue.md': 'fail', + 'customers.md': 'fail', + 'Sales overview.md': 'fail', + 'Database failover.md': 'fail', + 'Cache warm-up.md': 'fail', + // Barely more than the title. + 'events.md': 'fail', + 'Activation rate.md': 'pass', + // Restates the table name in words. + 'fct_orders_daily.md': 'fail', + 'ARR.md': 'pass', + // Starts like the name, then says what it holds. + 'dim_customers.md': 'pass', + // Restates the title. + 'Quarterly planning.md': 'fail', + 'Hiring loop.md': 'pass', + }, + }, +} diff --git a/evals/semantic/opportunities-distinct.json5 b/evals/semantic/opportunities-distinct.json5 new file mode 100644 index 00000000..741e02ac --- /dev/null +++ b/evals/semantic/opportunities-distinct.json5 @@ -0,0 +1,32 @@ +{ + space: 'ost-trees', + expected: { + 'opportunities-distinct': { + 'Raise weekly invoice volume.md': 'distinct', + 'Get freelancers paid faster.md': 'duplicate', + // Late payment is one part of getting paid slowly. + 'Shorten days to payment.md': 'overlapping', + // One topic, three different needs. + "I don't trust that my timesheet is right.md": 'distinct', + // Different causes of the parent need, which do not overlap each other. + "I can't find the meeting notes I need.md": 'distinct', + // The same need in different words. + 'New hires productive sooner.md': 'duplicate', + // The solutions overlap, but the rule ignores them. + 'I miss messages from clients.md': 'distinct', + // The second is broader wording of the first. + 'Grow plan upgrades.md': ['overlapping', 'duplicate'], + 'Increase repeat bookings.md': 'distinct', + // Theft is part of feeling unsafe. Contrast with the next scenario. + "I don't feel safe leaving my bike at the station.md": 'overlapping', + // Contrast with the previous scenario. + "I don't feel safe parking my bike at work.md": 'distinct', + 'More travellers check in online.md': 'distinct', + 'Fewer support calls about access.md': 'duplicate', + 'Moving house is stressful.md': 'distinct', + // Subscription spending is part of monthly spending. + 'More members track spending weekly.md': 'overlapping', + 'Planning family meals takes too long.md': 'distinct', + }, + }, +} diff --git a/evals/semantic/opportunity-refines-parent.json5 b/evals/semantic/opportunity-refines-parent.json5 new file mode 100644 index 00000000..d6794d95 --- /dev/null +++ b/evals/semantic/opportunity-refines-parent.json5 @@ -0,0 +1,35 @@ +{ + space: 'ost-trees', + expected: { + 'opportunity-refines-parent': { + "I don't know what to do after I create a workspace.md": 'refines', + 'My teammates ignore my invitations.md': 'refines', + // Plan choice affects buying, and only indirectly whether a team is active. + "I can't tell which plan fits a team of my size.md": 'weak-link', + 'I want cheaper invoices from my accountant.md': 'misplaced', + 'I need to export my data when I leave.md': 'misplaced', + "I don't know which template to start from.md": 'refines', + // Restates the parent. + "I'm not sure what to do once my workspace is set up.md": 'weak-link', + 'The pricing page is confusing.md': 'misplaced', + // One cause of the parent need. + "I don't understand what the sidebar icons mean.md": 'refines', + // It would drive the outcome, but it is not part of this parent. + 'I want to see what my teammates changed while I was away.md': 'misplaced', + "Invitation emails land in my teammates' spam.md": 'refines', + "My teammates don't know why they should join.md": 'refines', + "People I invite can't join because of our single sign-on.md": 'refines', + // About seats, linked to invitations only through cost. + "I don't want to pay for seats nobody uses.md": 'weak-link', + // A different invitation need, not why invitations are ignored. + 'I want to invite people outside my company as guests.md': 'weak-link', + "I can't remember what I named a document.md": 'refines', + "Search results show documents I don't have access to.md": 'refines', + 'I want dark mode.md': 'misplaced', + // Folders help finding, but bypass the search the outcome measures. + 'I want to organise documents into folders.md': 'weak-link', + // A solution in disguise, which another rule checks. It would still drive the outcome. + 'Add filters to search.md': 'refines', + }, + }, +} diff --git a/evals/semantic/opportunity-serves-ancestors.json5 b/evals/semantic/opportunity-serves-ancestors.json5 new file mode 100644 index 00000000..febe0cc9 --- /dev/null +++ b/evals/semantic/opportunity-serves-ancestors.json5 @@ -0,0 +1,32 @@ +{ + space: 'strategy-trees', + expected: { + 'opportunity-serves-ancestors': { + "Bookshops can't take online orders without a website.md": 'pass', + // Grows revenue, and undercuts the shops. + 'Readers want to buy direct from us at lower prices than shops.md': 'fail', + // Grows revenue by bypassing bookshops. + 'Publishers want to sell their backlist through us direct to readers.md': 'fail', + 'Shops spend hours updating stock on each marketplace.md': 'pass', + // Grows revenue, and helps the chains that independents compete with. + 'Big chains want to license our ordering software.md': 'fail', + 'Shops want click-and-collect so readers come into the store.md': 'pass', + "New riders don't know the safe routes to work.md": 'pass', + 'Drivers want cheaper parking near stations.md': 'fail', + // Could grow commuting, and rewards riding fast, against safety. + 'Riders want a leaderboard that rewards the fastest commute times.md': 'fail', + 'Riders have nowhere secure to leave bikes at work.md': 'pass', + // Earns the money by taking the weekends the mission keeps. + 'Weekend freelance work pays well.md': 'fail', + 'We could sell the second car we barely use.md': 'pass', + 'My employer pays a bonus for overtime.md': 'fail', + // Earns money and is time with the kids. + "The kids' outgrown toys and gear could sell at weekend markets, with the kids.md": 'pass', + // An optional fee with a clear price. + 'Businesses want instant payouts and would pay for them.md': 'pass', + // Grows fees by surprising customers. + "Many customers don't notice when the account fee rises.md": 'fail', + 'Overdraft fees could rise without losing customers.md': 'fail', + }, + }, +} diff --git a/evals/semantic/outcome-product-outcome.json5 b/evals/semantic/outcome-product-outcome.json5 new file mode 100644 index 00000000..9f82bc7b --- /dev/null +++ b/evals/semantic/outcome-product-outcome.json5 @@ -0,0 +1,31 @@ +{ + space: 'ost-trees', + expected: { + 'outcome-product-outcome': { + 'Help new teams reach their first shared document.md': 'product-outcome', + 'Increase weekly active teams.md': 'product-outcome', + 'Make it easier to find past work.md': 'product-outcome', + 'Reduce time to first invoice sent.md': 'product-outcome', + 'More customers set up recurring payments.md': 'product-outcome', + 'Grow annual recurring revenue.md': 'business-outcome', + 'Reduce churn.md': 'business-outcome', + 'Win the mid-market segment.md': 'business-outcome', + 'Get more people using the export button.md': 'traction-metric', + // The use of one feature, worded as adoption. + 'Drive adoption of the new dashboard.md': 'traction-metric', + 'Launch the mobile app.md': 'output', + 'Ship single sign-on.md': 'output', + 'Redesign onboarding.md': 'output', + // An output worded as an increase. + 'Increase the number of integrations.md': 'output', + // A business title, but the metric is customer behaviour. + 'Protect revenue from small teams.md': 'product-outcome', + // A behaviour title, but the metric is a release. + 'Get teams using the new calendar view.md': 'output', + // A value title, but the metric is the use of one email. + 'Help managers stay informed.md': 'traction-metric', + // Customer wording for a revenue metric. + 'Customers stay with us longer.md': 'business-outcome', + }, + }, +} diff --git a/evals/semantic/problem-statement-names-no-solution.json5 b/evals/semantic/problem-statement-names-no-solution.json5 new file mode 100644 index 00000000..1e5401df --- /dev/null +++ b/evals/semantic/problem-statement-names-no-solution.json5 @@ -0,0 +1,31 @@ +{ + space: 'strategy-trees', + expected: { + 'problem-statement-names-no-solution': { + 'Opportunity 01.md#Problem statement': 'pass', + 'Opportunity 02.md#Problem statement': 'pass', + 'Opportunity 03.md#Problem statement': 'pass', + 'Opportunity 04.md#Problem statement': 'pass', + 'Opportunity 05.md#Problem statement': 'fail', + // The lack of a solution, worded as a problem. + 'Opportunity 06.md#Problem statement': 'fail', + // The lack of a solution, worded as a problem. + 'Opportunity 07.md#Problem statement': 'fail', + 'Opportunity 08.md#Problem statement': 'fail', + // A problem, then a solution. + 'Opportunity 09.md#Problem statement': 'fail', + // Suggests forecasting, but names no solution. + 'Opportunity 10.md#Problem statement': 'pass', + 'Opportunity 11.md#Problem statement': 'pass', + 'Opportunity 12.md#Problem statement': 'fail', + // Names existing tools as context, not a solution. + 'Opportunity 13.md#Problem statement': 'pass', + // Presumes the integration. + 'Opportunity 14.md#Problem statement': 'fail', + // Contrast with the next case. + 'Opportunity 15.md#Problem statement': 'fail', + // Contrast with the previous case. + 'Opportunity 16.md#Problem statement': 'pass', + }, + }, +} diff --git a/evals/semantic/solution-addresses-opportunity.json5 b/evals/semantic/solution-addresses-opportunity.json5 new file mode 100644 index 00000000..2c0c032f --- /dev/null +++ b/evals/semantic/solution-addresses-opportunity.json5 @@ -0,0 +1,34 @@ +{ + space: 'ost-trees', + expected: { + 'solution-addresses-opportunity': { + 'Autosave every few seconds.md': 'addresses', + 'Restore unsaved drafts after a crash.md': 'addresses', + 'Make the app load faster.md': 'misplaced', + // Eases the fear, but does not prevent the loss. + "Show a 'saved' indicator in the toolbar.md": ['addresses', 'tangential'], + 'Write a help article on keyboard shortcuts.md': 'misplaced', + // Data loss, but a crash loses the work since the last backup. + 'Back up workspaces nightly to cloud storage.md': 'tangential', + // Protects against closing a tab, not against a crash. + 'Warn before closing a tab with unsaved changes.md': 'tangential', + 'Plan picker that asks for team size and recommends a plan.md': 'addresses', + 'Compare plans side by side by seat count.md': 'addresses', + // About plans and price, but not about which plan fits. + 'Offer a discount on annual plans.md': 'tangential', + 'Add more payment methods.md': 'misplaced', + 'Chat with sales about plan options.md': 'addresses', + // The names hint at size, but do not say where the limits are. + 'Rename the plans to Team, Business and Enterprise.md': ['addresses', 'tangential'], + 'Send invitations from a verified domain with SPF and DKIM.md': 'addresses', + // Avoids email altogether. + 'Let me share an invite link in chat instead.md': 'addresses', + // The same email still lands in spam. + 'Redesign the invitation email to look friendlier.md': 'tangential', + // Invited teammates are not in the app yet. + 'Remind teammates in the app to accept invites.md': ['tangential', 'misplaced'], + // Changes how invites are created, not where they land. + 'Bulk import teammates from a spreadsheet.md': ['tangential', 'misplaced'], + }, + }, +} diff --git a/evals/semantic/solution-serves-outcome.json5 b/evals/semantic/solution-serves-outcome.json5 new file mode 100644 index 00000000..9e76b4f2 --- /dev/null +++ b/evals/semantic/solution-serves-outcome.json5 @@ -0,0 +1,33 @@ +{ + space: 'ost-trees', + expected: { + 'solution-serves-outcome': { + 'Send invitations from a verified domain.md': 'pass', + 'Let invited teammates preview the workspace before signing up.md': 'pass', + 'A checklist of first steps in a new workspace.md': 'pass', + // Resolves the need, but after the second week the outcome measures. + 'A consultant sets up the workspace on a paid call two weeks after signup.md': 'fail', + "A 'since you were away' summary when I come back.md": 'pass', + // Resolves the need by keeping people out of the product. + 'Email a daily digest of every change, so nobody needs to open the app.md': 'fail', + 'Notify me in the app about changes on pages I follow.md': 'pass', + 'Search matches words inside documents, not only titles.md': 'pass', + "Hide documents I can't open from search results.md": 'pass', + // The search can still end with the document opened. + "Show a 'request access' button on each locked result.md": 'pass', + 'Pre-fill tax rates from my business address.md': 'pass', + // Resolves the confusion, but delays the first invoice. + 'Require a call with an accountant before the first invoice.md': 'fail', + // Sidesteps the need, and shortens the time to the first invoice. + 'Let me send my first invoice now and set tax rates later.md': 'pass', + // Helps clients pay, which the outcome does not measure. + 'Add card payments through a payment provider.md': 'fail', + "Text a reminder when it's been five weeks since my last cut.md": 'pass', + 'One-tap rebook of my last appointment.md': 'pass', + // Faster, but loses the stylist that brings customers back. + 'Book me with whichever stylist is free, to make booking faster.md': 'fail', + // Works against repeat bookings. + 'Charge a fee to rebook within a week.md': 'fail', + }, + }, +} diff --git a/evals/semantic/synthesis-connects.json5 b/evals/semantic/synthesis-connects.json5 new file mode 100644 index 00000000..3c4202d7 --- /dev/null +++ b/evals/semantic/synthesis-connects.json5 @@ -0,0 +1,30 @@ +{ + space: 'knowledge-wiki', + expected: { + 'synthesis-connects': { + 'Affordances and feedback in interface design.md': 'pass', + 'Opportunity solution trees and OKRs compared.md': 'pass', + 'Why retrieval beats fine-tuning for fresh knowledge.md': 'pass', + 'Lean and kanban share their roots.md': 'pass', + 'Summary of The Design of Everyday Things.md': 'fail', + "Notes from Torres' talk at Mind the Product.md": 'fail', + // A list, with nothing connecting the items. + 'Product discovery books.md': 'fail', + // Restates one source. + "Karpathy's LLM wiki post.md": 'fail', + // One main source, connected to a concept to form a thesis. + 'Continuous discovery as jobs to be done.md': 'pass', + // Two sources, summarised one after the other, with nothing connecting them. + 'Two articles on technical debt.md': 'fail', + // Mostly a table, but it compares and concludes. + 'Which model to pick for coding.md': 'pass', + // One paper, with a link at the end that connects nothing. + 'Attention Is All You Need.md': 'fail', + // Draws on several sources, but has no model or thesis. + 'Open questions about context engineering.md': 'fail', + // Short, but it connects two concepts into a thesis. + 'Technical debt is a cost of delay.md': 'pass', + 'Why studies disagree on remote work productivity.md': 'pass', + }, + }, +} diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index 5f73ceec..e6958104 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "structured-context", - "version": "0.23.13", + "version": "0.23.14", "description": "Structured content authoring for Obsidian — automatic validation hooks, slash commands, and agent skills to keep markdown files compliant with your schema.", "homepage": "https://github.com/mindsocket/structured-context", "repository": "https://github.com/mindsocket/structured-context", diff --git a/plugin/skills/structured-context/references/schema-authoring.md b/plugin/skills/structured-context/references/schema-authoring.md index 9b5fc99e..c0eace49 100644 --- a/plugin/skills/structured-context/references/schema-authoring.md +++ b/plugin/skills/structured-context/references/schema-authoring.md @@ -101,6 +101,7 @@ Other rule fields: - `includeContent` — `nodeOnly` (the evaluated node's body) or `fullScope`/`true` (every body in scope). Without it, no body is sent. - A rule with `scope` or `includeContent` may return `missing-context` (default `warning`) when its input was trimmed to fit. - Semantic rules send the schema's type and field descriptions to the model with the rubric. Descriptions that say what a good node looks like save repeating that detail in each rubric, and let more general rules draw on the schema. +- To check a set of children, a fit with the parent, or the intent of the levels above, copy a bundled rule (`opportunities-distinct`, `opportunity-refines-parent`, `solution-serves-outcome` in `_ost_strict.json`). `sctx docs semantic-rules` shows which scope each uses. ## Metadata composition semantics diff --git a/schemas/_knowledge_wiki.json b/schemas/_knowledge_wiki.json index 2ae52cfd..3ddf300e 100644 --- a/schemas/_knowledge_wiki.json +++ b/schemas/_knowledge_wiki.json @@ -58,6 +58,22 @@ "description": "Concept pages should have a short summary for index discoverability", "type": "concept", "expression": "$exists(current.summary) and $length(current.summary) > 0" + }, + { + "id": "concept-or-entity", + "category": "coherence", + "description": "A concept page frames an idea, mechanism or term, and an entity page is one specific, named thing", + "when": "current.resolvedType in ['concept', 'entity']", + "semantic": { + "rubric": "Pass when the page's type fits what the page is about. A concept is an idea, mechanism, method or term that many things can share. An entity is one identifiable person, organisation, product, model or other named thing." + } + }, + { + "id": "synthesis-connects", + "category": "best-practice", + "type": "synthesis", + "description": "A synthesis connects several sources or concepts into a model, comparison or thesis, rather than summarising one source", + "semantic": { "includeContent": "nodeOnly" } } ] }, diff --git a/schemas/_okf.json b/schemas/_okf.json index a99b22e0..78bcccb1 100644 --- a/schemas/_okf.json +++ b/schemas/_okf.json @@ -15,6 +15,13 @@ "description": "Concepts should have a one-line description (used by index generators and search snippets)", "expression": "$exists(current.description) and $length(current.description) > 0" }, + { + "id": "okf-description-informative", + "category": "best-practice", + "description": "A concept's description says in one line what the concept is or covers, beyond its title and type", + "when": "$exists(current.description) and $length(current.description) > 0", + "semantic": {} + }, { "id": "okf-stale-after-generated", "category": "validation", diff --git a/schemas/_ost_strict.json b/schemas/_ost_strict.json index b4250d58..071ecb29 100644 --- a/schemas/_ost_strict.json +++ b/schemas/_ost_strict.json @@ -44,6 +44,112 @@ "semantic": { "rubric": "Verify that the opportunity is stated from the customer's perspective as a problem, pain point, or desire. It must not be a solution in disguise (e.g. 'build X feature') or a business metric (e.g. 'increase revenue')." } + }, + { + "id": "outcome-product-outcome", + "category": "best-practice", + "type": "outcome", + "description": "The outcome is a product outcome the team can influence, not a business outcome, a traction metric or an output", + "semantic": { + "verdicts": { + "product-outcome": "A change in what customers do in the product, which the team can influence and which drives business value", + "business-outcome": { + "description": "A business result, such as revenue, market share or retention, that the team cannot move directly", + "guidance": "Find the customer behaviour in the product that drives it, and make that the outcome." + }, + "traction-metric": { + "description": "The use of one specific feature or flow, which leaves no room to discover other ways to create value", + "guidance": "Ask what customer value that use stands for, and measure that instead." + }, + "output": { + "description": "A feature, project or deliverable to ship, even when it is worded as a goal", + "guidance": "Ask what should change for customers once it ships, and measure that instead." + } + }, + "severity": { "business-outcome": "info", "traction-metric": "info", "output": "warning" } + } + }, + { + "id": "opportunity-refines-parent", + "category": "coherence", + "type": "opportunity", + "description": "A top-level opportunity drives its outcome, and a child opportunity is a subset of its parent opportunity", + "semantic": { + "scope": ["parent"], + "verdicts": { + "refines": "Under an outcome, addressing it would drive the outcome. Under an opportunity, it is one part, cause or case of the parent need, narrower than the parent", + "weak-link": { + "description": "Related to the parent, but the link is indirect, or it only restates the parent", + "guidance": "Name the narrower need it covers, or move it next to its parent." + }, + "misplaced": { + "description": "It would not drive the outcome, or it is not part of the parent opportunity", + "guidance": "Move it under the outcome or opportunity it belongs to." + } + }, + "severity": { "weak-link": "info", "misplaced": "warning" } + } + }, + { + "id": "opportunities-distinct", + "category": "coherence", + "description": "Sibling opportunities are distinct: the team can address one without addressing another", + "when": "$count(nodes[$$.current.id in resolvedParentIds and resolvedType = 'opportunity']) >= 2", + "semantic": { + "rubric": "Judge the item's child opportunities as a set, and ignore its other children. They are distinct when each is a different need, so that addressing one does not address another.", + "scope": ["children"], + "verdicts": { + "distinct": "Each child opportunity is a different need", + "overlapping": { + "description": "Two child opportunities partly cover the same need, or one contains another", + "guidance": "Sharpen the boundary between them, or nest the narrower one under the broader one." + }, + "duplicate": { + "description": "Two child opportunities state the same need in different words", + "guidance": "Merge them." + } + }, + "severity": { "overlapping": "warning", "duplicate": "warning" } + } + }, + { + "id": "solution-addresses-opportunity", + "category": "coherence", + "type": "solution", + "description": "The solution addresses its parent opportunity", + "semantic": { + "scope": ["parent"], + "verdicts": { + "addresses": "If it worked, it would resolve or reduce the parent need as the customer states it", + "tangential": { + "description": "It shares the parent's topic, but it would not resolve the need as the customer states it", + "guidance": "Say how it resolves the need, or move it under the opportunity it does address." + }, + "misplaced": { + "description": "It addresses a different need, or none", + "guidance": "Move it under the opportunity it addresses." + } + }, + "severity": { "tangential": "info", "misplaced": "warning" } + } + }, + { + "id": "solution-serves-outcome", + "category": "coherence", + "type": "solution", + "description": "The solution serves the outcome above it, not only its parent opportunity", + "semantic": { + "rubric": "Follow the chain from the solution through its opportunities to the outcome. Pass when the solution, if it worked, would move the outcome's metric in the intended direction. Fail when it would not move the outcome, or would move it the wrong way, even if it addresses its parent opportunity.", + "scope": ["ancestors"] + } + }, + { + "id": "assumption-category-fits", + "category": "best-practice", + "type": "assumption_test", + "description": "The assumption test's category is the kind of assumption it tests", + "when": "$exists(current.category)", + "semantic": {} } ] }, @@ -81,7 +187,7 @@ "category": { "type": "string", "enum": ["desirability", "viability", "feasibility", "usability", "ethical"], - "description": "Assumption type categories" + "description": "Desirability: do customers want it? Viability: should the business build it? Feasibility: can the team build it? Usability: can customers use it? Ethical: could it cause harm?" } }, "required": ["assumption"] diff --git a/schemas/_sctx_base.json b/schemas/_sctx_base.json index 18847b03..c6a8348b 100644 --- a/schemas/_sctx_base.json +++ b/schemas/_sctx_base.json @@ -4,16 +4,14 @@ "description": "Shared definitions for structured-context schemas", "$defs": { "title": { - "type": "string", - "description": "Title of the node" + "type": "string" }, "content": { "type": "string", "description": "Markdown source of the node's body, excluding its own heading or list marker and any nested embedded nodes" }, "summary": { - "type": "string", - "description": "Short summary" + "type": "string" }, "status": { "type": "string", @@ -41,8 +39,7 @@ "content": { "$ref": "#/$defs/content" }, "tags": { "type": "array", - "items": { "type": "string" }, - "description": "Categorization tags" + "items": { "type": "string" } } }, "required": ["title"] diff --git a/schemas/_strategy_general.json b/schemas/_strategy_general.json index d68a7329..73a61742 100644 --- a/schemas/_strategy_general.json +++ b/schemas/_strategy_general.json @@ -40,11 +40,87 @@ "embeddedTemplateFields": ["assumption", "status", "confidence"], "multiple": true } + ], + "rules": [ + { + "id": "goals-distinct", + "category": "coherence", + "description": "Sibling goals are distinct: progress on one is not progress on another", + "when": "$count(nodes[$$.current.id in resolvedParentIds and resolvedType = 'goal']) >= 2", + "semantic": { + "rubric": "Judge the item's child goals as a set, and ignore its other children. They are distinct when each is a different result, so that achieving one does not achieve another.", + "scope": ["children"], + "verdicts": { + "distinct": "Each child goal is a different result", + "overlapping": { + "description": "Two child goals partly cover the same result, or one contains another", + "guidance": "Sharpen the boundary between them, or nest the narrower one under the broader one." + }, + "duplicate": { + "description": "Two child goals state the same result in different words", + "guidance": "Merge them." + } + }, + "severity": { "overlapping": "warning", "duplicate": "warning" } + } + }, + { + "id": "goals-cover-parent", + "category": "best-practice", + "description": "Together, a mission's or goal's child goals cover what it needs", + "when": "$count(nodes[$$.current.id in resolvedParentIds and resolvedType = 'goal']) >= 2", + "semantic": { + "rubric": "Judge the item's child goals as a set, and ignore its other children. Pass when achieving all of them would achieve the item as its title and body state it. Fail when a part that the item names or plainly needs has no goal.", + "scope": ["children"], + "includeContent": "fullScope" + } + }, + { + "id": "goal-advances-parent", + "category": "coherence", + "type": "goal", + "description": "A goal advances its parent mission, or is one part of its parent goal", + "when": "$exists(parent)", + "semantic": { + "scope": ["parent"], + "verdicts": { + "advances": "Under a mission, achieving it moves the mission forward. Under a goal, it is one part of the parent goal, narrower than the parent", + "weak-link": { + "description": "Related to the parent, but the link is indirect, or it only restates the parent", + "guidance": "Say how it advances the parent, or narrow it to one part of the parent." + }, + "misplaced": { + "description": "Achieving it would not advance the parent", + "guidance": "Move it under the mission or goal it advances." + } + }, + "severity": { "weak-link": "info", "misplaced": "warning" } + } + }, + { + "id": "opportunity-serves-ancestors", + "category": "coherence", + "type": "opportunity", + "description": "Pursuing the opportunity serves the mission and vision above it, not only its parent goal", + "semantic": { + "rubric": "Follow the chain from the opportunity through its goals to the mission and vision. Pass when pursuing it serves every level. Fail when it would advance its parent but work against, or ignore, the intent of a level above.", + "scope": ["ancestors"], + "includeContent": "fullScope" + } + }, + { + "id": "problem-statement-names-no-solution", + "category": "best-practice", + "type": "problem_statement", + "description": "The problem statement states a problem, and does not name or presume a solution", + "semantic": { "includeContent": "nodeOnly" } + } ] }, "$defs": { "dashboard": { "type": "object", + "description": "An overview page for the space. It is not part of the hierarchy.", "allOf": [{ "$ref": "sctx://core/_sctx_base#/$defs/baseNodeProps" }], "properties": { "type": { "const": "dashboard" } @@ -54,6 +130,7 @@ }, "ost_on_a_page": { "type": "object", + "description": "A one-page view of the tree or the space. It is not part of the hierarchy.", "allOf": [{ "$ref": "sctx://core/_sctx_base#/$defs/baseNodeProps" }], "properties": { "type": { "enum": ["ost_on_a_page", "space_on_a_page"] } @@ -63,6 +140,7 @@ }, "vision": { "type": "object", + "description": "The future you want to exist: a lasting, aspirational state of the world, not a plan or an activity.", "allOf": [ { "$ref": "sctx://core/_sctx_base#/$defs/baseNodeProps" }, { "$ref": "sctx://core/_sctx_base#/$defs/ostEntityProps" } @@ -82,6 +160,7 @@ }, "mission": { "type": "object", + "description": "What you do, and for whom, to move towards the vision. It lasts for years and guides which goals to pursue.", "allOf": [ { "$ref": "sctx://core/_sctx_base#/$defs/baseNodeProps" }, { "$ref": "sctx://core/_sctx_base#/$defs/parentNodeProps" }, @@ -102,6 +181,7 @@ }, "goal": { "type": "object", + "description": "A result to achieve, which advances the parent mission or goal. A child goal is one part of its parent goal. Together, a parent's goals should cover what it needs.", "allOf": [ { "$ref": "sctx://core/_sctx_base#/$defs/baseNodeProps" }, { "$ref": "sctx://core/_sctx_base#/$defs/parentNodeProps" }, @@ -124,6 +204,7 @@ }, "opportunity": { "type": "object", + "description": "A need, problem or opening that, if addressed, advances the parent goal. A child opportunity is one part of its parent opportunity.", "allOf": [ { "$ref": "sctx://core/_sctx_base#/$defs/baseNodeProps" }, { "$ref": "sctx://core/_sctx_base#/$defs/parentNodeProps" }, @@ -157,6 +238,7 @@ }, "solution": { "type": "object", + "description": "One way to address the parent opportunity.", "allOf": [ { "$ref": "sctx://core/_sctx_base#/$defs/baseNodeProps" }, { "$ref": "sctx://core/_sctx_base#/$defs/parentNodeProps" }, @@ -190,6 +272,7 @@ }, "experiment": { "type": "object", + "description": "A small test of an assumption behind the parent, whose result decides what to do next.", "allOf": [ { "$ref": "sctx://core/_sctx_base#/$defs/baseNodeProps" }, { "$ref": "sctx://core/_sctx_base#/$defs/parentNodeProps" }, @@ -215,6 +298,7 @@ }, "assumption": { "type": "object", + "description": "A belief that must hold for the parent opportunity to succeed, with how confident you are in it.", "allOf": [ { "$ref": "sctx://core/_sctx_base#/$defs/baseNodeProps" }, { "$ref": "sctx://core/_sctx_base#/$defs/parentNodeProps" }, @@ -241,6 +325,7 @@ }, "problem_statement": { "type": "object", + "description": "The problem that the parent opportunity addresses: who has it, what it is and why it matters. It names no solution.", "allOf": [ { "$ref": "sctx://core/_sctx_base#/$defs/baseNodeProps" }, { "$ref": "sctx://core/_sctx_base#/$defs/parentNodeProps" }, diff --git a/schemas/knowledge_wiki.json b/schemas/knowledge_wiki.json index d96b42f4..fcbb1e42 100644 --- a/schemas/knowledge_wiki.json +++ b/schemas/knowledge_wiki.json @@ -2,7 +2,7 @@ "$schema": "https://raw.githubusercontent.com/mindsocket/structured-context/main/schemas/generated/latest/_structured_context_schema_meta.json", "$id": "sctx://core/knowledge_wiki", "title": "Knowledge Wiki", - "description": "Lightweight schema for LLM-maintained knowledge wikis. Supports source pages, concept pages, syntheses, and notes. Designed for compounding knowledge bases where the LLM creates and maintains wiki pages from raw sources.\n\nFlat entity model — no hierarchy, no required parent links. Wikilinks between any pages are valid for arbitrary cross-referencing. The schema validates structure and provenance; it does not constrain meaning.\n\nBased on the pattern described in Karpathy's LLM Wiki (2026).", + "description": "Lightweight schema for LLM-maintained knowledge wikis. Supports source pages, concept pages, syntheses, and notes. Designed for compounding knowledge bases where the LLM creates and maintains wiki pages from raw sources.\n\nFlat entity model — no hierarchy, no required parent links. Wikilinks between any pages are valid for arbitrary cross-referencing. The schema validates structure and provenance.\n\nBased on the pattern described in Karpathy's LLM Wiki (2026).", "$metadata": { "imports": ["sctx://core/_knowledge_wiki"] }, "allOf": [{ "$ref": "sctx://core/_knowledge_wiki" }], "oneOf": [ diff --git a/schemas/strict_ost.json b/schemas/strict_ost.json index 6fa05249..7d35acfd 100644 --- a/schemas/strict_ost.json +++ b/schemas/strict_ost.json @@ -10,6 +10,7 @@ "oneOf": [ { "type": "object", + "description": "A product outcome: a change in what customers do in the product, which the team can influence and which drives business value. `metric` measures it. It is not a business outcome (such as revenue or market share), not a traction metric (the use of one feature), and not an output (a feature or project to ship).", "allOf": [ { "$ref": "sctx://core/_sctx_base#/$defs/baseNodeProps" }, { "$ref": "sctx://core/_sctx_base#/$defs/ostEntityProps" }, @@ -31,6 +32,7 @@ }, { "type": "object", + "description": "A customer need, pain point or desire, found in customer research and stated as the customer would say it. Addressing it drives the outcome. A child opportunity is a subset of its parent opportunity. Sibling opportunities are distinct: the team can address one without addressing another. It is not a solution, and not a business goal.", "allOf": [ { "$ref": "sctx://core/_sctx_base#/$defs/baseNodeProps" }, { "$ref": "sctx://core/_sctx_base#/$defs/parentNodeProps" }, @@ -53,6 +55,7 @@ }, { "type": "object", + "description": "One way to address the parent opportunity. The team compares several solutions for the target opportunity before it commits to one.", "allOf": [ { "$ref": "sctx://core/_sctx_base#/$defs/baseNodeProps" }, { "$ref": "sctx://core/_sctx_base#/$defs/parentNodeProps" }, @@ -73,6 +76,7 @@ }, { "type": "object", + "description": "A small, fast test of one assumption that the parent solution depends on.", "allOf": [ { "$ref": "sctx://core/_sctx_base#/$defs/baseNodeProps" }, { "$ref": "sctx://core/_sctx_base#/$defs/parentNodeProps" }, diff --git a/src/judge/context.ts b/src/judge/context.ts index 7fd55875..69b6251c 100644 --- a/src/judge/context.ts +++ b/src/judge/context.ts @@ -2,7 +2,7 @@ import type { AnySchemaObject } from 'ajv'; import { ruleSemantic } from '../rule/rule-severity'; import type { Rule } from '../schema/metadata-contract'; import { SEMANTIC_SCOPES } from '../schema/metadata-contract'; -import { mergeVariantProperties } from '../schema/schema-refs'; +import { mergeVariantProperties, resolveRef } from '../schema/schema-refs'; import type { SpaceGraph } from '../space-graph'; import type { NodeId, SpaceContext, SpaceNode } from '../types'; import type { RelatedNode, SchemaDescriptions, SemanticJudgeInput } from './types'; @@ -119,8 +119,8 @@ function describeField(def: AnySchemaObject): string | undefined { } /** - * The schema's descriptions of each node type: the `oneOf` entry's `description` and each field's `description` - * and enum values. A type or field the schema does not describe is left out. + * The schema's descriptions of each node type: the `oneOf` entry's `description` (through a `$ref`) and each + * field's `description` and enum values. A type or field the schema does not describe is left out. */ export function describeSchema({ schema, @@ -134,7 +134,8 @@ export function describeSchema({ const text = SELF_EXPLAINED_FIELDS.has(name) ? undefined : describeField(def); if (text) fields[name] = text; } - const description = typeof entry.description === 'string' ? entry.description : undefined; + const variant = resolveRef(entry, schema, schemaRefRegistry) ?? entry; + const description = typeof variant.description === 'string' ? variant.description : undefined; if (!description && Object.keys(fields).length === 0) continue; const typeDef = properties.type; const names = typeDef?.const !== undefined ? [typeDef.const] : Array.isArray(typeDef?.enum) ? typeDef.enum : []; diff --git a/tests/judge/schema-descriptions.test.ts b/tests/judge/schema-descriptions.test.ts index 8342e1b1..5a895bf7 100644 --- a/tests/judge/schema-descriptions.test.ts +++ b/tests/judge/schema-descriptions.test.ts @@ -15,9 +15,15 @@ describe('schema descriptions for the judge', () => { it("describes each node type's fields, with enum values", () => { expect(descriptions.opportunity?.fields?.source).toContain('Customer research source'); expect(descriptions.opportunity?.fields?.status).toStartWith('one of: identified, wondering'); - expect(descriptions.assumption_test?.fields?.category).toBe( - 'Assumption type categories (one of: desirability, viability, feasibility, usability, ethical)', + expect(descriptions.assumption_test?.fields?.category).toEndWith( + '(one of: desirability, viability, feasibility, usability, ethical)', ); + expect(descriptions.opportunity?.description).toStartWith('A customer need'); + }); + + it('describes a type whose variant is a $ref', () => { + const wiki = describeSchema(loadSchema(join(import.meta.dir, '../../schemas/knowledge_wiki.json'))); + expect(wiki.concept?.description).toStartWith('A concept page'); }); it('leaves out the fields every node has', () => { diff --git a/tests/validate/semantic-validation.test.ts b/tests/validate/semantic-validation.test.ts index 9328832c..da754523 100644 --- a/tests/validate/semantic-validation.test.ts +++ b/tests/validate/semantic-validation.test.ts @@ -197,12 +197,13 @@ describe('Semantic rules: verdicts, batching and rule filter', () => { }; const context = createSpaceContext('strict-valid', config, { configDir: import.meta.dir }); - it('judges each node once with all of its semantic rules', async () => { + it('judges each rule once per node, with rules that share related nodes in one input', async () => { const provider = new FakeJudgeProvider(); await validateSpaceWith(context, { judge: { provider } }); - const labels = provider.callHistory.map((c) => c.node.id); - expect(new Set(labels).size).toBe(labels.length); - expect(provider.callHistory.every((c) => c.rules.length >= 1)).toBe(true); + const judged = provider.callHistory.flatMap((c) => c.rules.map((r) => `${c.node.id}:${r.id}`)); + expect(new Set(judged).size).toBe(judged.length); + const inputs = provider.callHistory.map((c) => `${c.node.id}:${JSON.stringify(c.related ?? [])}`); + expect(new Set(inputs).size).toBe(inputs.length); }); it('reports a verdict that has a severity as a violation carrying the verdict and evidence', async () => { From fd8e8a4f557c366b6de3e7e68f924240410b2b3e Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 14 Nov 2026 01:52:11 +0000 Subject: [PATCH 2/4] fix(schemas): tune bundled semantic rules from the first live evals - Merge the duplicate verdict into overlapping on opportunities-distinct and goals-distinct: every provider confused the two, and they shared a severity. - Rubrics for the parent-fit rules, assumption-category-fits and okf-description-informative, so a related or reworded node no longer passes. - Narrow traction-metric to the use of one named feature, so behaviour in one flow stays a product outcome. - Fix labels and scenarios that the stronger providers rightly disputed, and drop cases with no clear answer. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019XphpcQHEAB9KwJ5Ltiomb --- evals/semantic/fixtures/okf.jsonl | 1 - evals/semantic/fixtures/ost-trees.jsonl | 3 +-- evals/semantic/fixtures/strategy-trees.jsonl | 8 ++++---- evals/semantic/goal-advances-parent.json5 | 10 ++++++---- evals/semantic/goals-cover-parent.json5 | 7 ++++--- evals/semantic/goals-distinct.json5 | 15 ++++++++------- .../semantic/okf-description-informative.json5 | 2 -- evals/semantic/opportunities-distinct.json5 | 12 +++++++----- .../semantic/opportunity-refines-parent.json5 | 6 +++--- .../solution-addresses-opportunity.json5 | 8 ++++---- evals/semantic/solution-serves-outcome.json5 | 4 ---- schemas/_okf.json | 4 +++- schemas/_ost_strict.json | 18 +++++++++--------- schemas/_strategy_general.json | 11 ++++------- 14 files changed, 53 insertions(+), 56 deletions(-) diff --git a/evals/semantic/fixtures/okf.jsonl b/evals/semantic/fixtures/okf.jsonl index 0e0a7236..bbfe044a 100644 --- a/evals/semantic/fixtures/okf.jsonl +++ b/evals/semantic/fixtures/okf.jsonl @@ -7,7 +7,6 @@ {"id": "Sales overview.md", "title": "Sales overview", "type": "Dashboard", "schemaData": {"title": "Sales overview", "type": "Dashboard", "description": "A dashboard"}, "linkTargets": ["Sales overview"]} {"id": "Database failover.md", "title": "Database failover", "type": "Runbook", "schemaData": {"title": "Database failover", "type": "Runbook", "description": "TODO"}, "linkTargets": ["Database failover"]} {"id": "Cache warm-up.md", "title": "Cache warm-up", "type": "Runbook", "schemaData": {"title": "Cache warm-up", "type": "Runbook", "description": "See Confluence"}, "linkTargets": ["Cache warm-up"]} -{"id": "events.md", "title": "events", "type": "Table", "schemaData": {"title": "events", "type": "Table", "description": "Product events"}, "linkTargets": ["events"]} {"id": "Activation rate.md", "title": "Activation rate", "type": "Metric", "schemaData": {"title": "Activation rate", "type": "Metric", "description": "Share of new signups who create a project within 7 days"}, "linkTargets": ["Activation rate"]} {"id": "fct_orders_daily.md", "title": "fct_orders_daily", "type": "Table", "schemaData": {"title": "fct_orders_daily", "type": "Table", "description": "Daily order facts"}, "linkTargets": ["fct_orders_daily"]} {"id": "ARR.md", "title": "ARR", "type": "Glossary Term", "schemaData": {"title": "ARR", "type": "Glossary Term", "description": "Annual recurring revenue: the yearly value of active subscriptions, excluding one-off fees"}, "linkTargets": ["ARR"]} diff --git a/evals/semantic/fixtures/ost-trees.jsonl b/evals/semantic/fixtures/ost-trees.jsonl index ab3a04f7..bb62851a 100644 --- a/evals/semantic/fixtures/ost-trees.jsonl +++ b/evals/semantic/fixtures/ost-trees.jsonl @@ -81,6 +81,7 @@ {"id": "Outcome for: I don't feel safe leaving my bike at the station.md", "title": "Outcome for: I don't feel safe leaving my bike at the station", "type": "outcome", "schemaData": {"title": "Outcome for: I don't feel safe leaving my bike at the station", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["Outcome for: I don't feel safe leaving my bike at the station"]} {"id": "I don't feel safe leaving my bike at the station.md", "title": "I don't feel safe leaving my bike at the station", "type": "opportunity", "schemaData": {"title": "I don't feel safe leaving my bike at the station", "type": "opportunity", "status": "identified", "parent": "[[Outcome for: I don't feel safe leaving my bike at the station]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I don't feel safe leaving my bike at the station"]} {"id": "My bike gets stolen at the station.md", "title": "My bike gets stolen at the station", "type": "opportunity", "schemaData": {"title": "My bike gets stolen at the station", "type": "opportunity", "status": "identified", "parent": "[[I don't feel safe leaving my bike at the station]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["My bike gets stolen at the station"]} +{"id": "Thieves strip parts off my bike at the station.md", "title": "Thieves strip parts off my bike at the station", "type": "opportunity", "schemaData": {"title": "Thieves strip parts off my bike at the station", "type": "opportunity", "status": "identified", "parent": "[[I don't feel safe leaving my bike at the station]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Thieves strip parts off my bike at the station"]} {"id": "There's nowhere to charge my e-bike.md", "title": "There's nowhere to charge my e-bike", "type": "opportunity", "schemaData": {"title": "There's nowhere to charge my e-bike", "type": "opportunity", "status": "identified", "parent": "[[I don't feel safe leaving my bike at the station]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["There's nowhere to charge my e-bike"]} {"id": "Outcome for: I don't feel safe parking my bike at work.md", "title": "Outcome for: I don't feel safe parking my bike at work", "type": "outcome", "schemaData": {"title": "Outcome for: I don't feel safe parking my bike at work", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["Outcome for: I don't feel safe parking my bike at work"]} {"id": "I don't feel safe parking my bike at work.md", "title": "I don't feel safe parking my bike at work", "type": "opportunity", "schemaData": {"title": "I don't feel safe parking my bike at work", "type": "opportunity", "status": "identified", "parent": "[[Outcome for: I don't feel safe parking my bike at work]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I don't feel safe parking my bike at work"]} @@ -135,7 +136,6 @@ {"id": "Notify me in the app about changes on pages I follow.md", "title": "Notify me in the app about changes on pages I follow", "type": "solution", "schemaData": {"title": "Notify me in the app about changes on pages I follow", "type": "solution", "status": "identified", "parent": "[[I want to see what my teammates changed while I was away]]"}, "linkTargets": ["Notify me in the app about changes on pages I follow"]} {"id": "Search matches words inside documents, not only titles.md", "title": "Search matches words inside documents, not only titles", "type": "solution", "schemaData": {"title": "Search matches words inside documents, not only titles", "type": "solution", "status": "identified", "parent": "[[I can't remember what I named a document]]"}, "linkTargets": ["Search matches words inside documents, not only titles"]} {"id": "Hide documents I can't open from search results.md", "title": "Hide documents I can't open from search results", "type": "solution", "schemaData": {"title": "Hide documents I can't open from search results", "type": "solution", "status": "identified", "parent": "[[Search results show documents I don't have access to]]"}, "linkTargets": ["Hide documents I can't open from search results"]} -{"id": "Show a 'request access' button on each locked result.md", "title": "Show a 'request access' button on each locked result", "type": "solution", "schemaData": {"title": "Show a 'request access' button on each locked result", "type": "solution", "status": "identified", "parent": "[[Search results show documents I don't have access to]]"}, "linkTargets": ["Show a 'request access' button on each locked result"]} {"id": "Get new businesses invoicing sooner.md", "title": "Get new businesses invoicing sooner", "type": "outcome", "schemaData": {"title": "Get new businesses invoicing sooner", "type": "outcome", "status": "identified", "metric": "Median time from signup to first invoice sent"}, "linkTargets": ["Get new businesses invoicing sooner"]} {"id": "Setting up tax rates is confusing.md", "title": "Setting up tax rates is confusing", "type": "opportunity", "schemaData": {"title": "Setting up tax rates is confusing", "type": "opportunity", "status": "identified", "parent": "[[Get new businesses invoicing sooner]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Setting up tax rates is confusing"]} {"id": "I can't take card payments on my invoices.md", "title": "I can't take card payments on my invoices", "type": "opportunity", "schemaData": {"title": "I can't take card payments on my invoices", "type": "opportunity", "status": "identified", "parent": "[[Get new businesses invoicing sooner]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't take card payments on my invoices"]} @@ -148,7 +148,6 @@ {"id": "I can't rebook my favourite stylist in one step.md", "title": "I can't rebook my favourite stylist in one step", "type": "opportunity", "schemaData": {"title": "I can't rebook my favourite stylist in one step", "type": "opportunity", "status": "identified", "parent": "[[Customers come back for their next cut]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I can't rebook my favourite stylist in one step"]} {"id": "Text a reminder when it's been five weeks since my last cut.md", "title": "Text a reminder when it's been five weeks since my last cut", "type": "solution", "schemaData": {"title": "Text a reminder when it's been five weeks since my last cut", "type": "solution", "status": "identified", "parent": "[[I forget when I'm due for my next haircut]]"}, "linkTargets": ["Text a reminder when it's been five weeks since my last cut"]} {"id": "One-tap rebook of my last appointment.md", "title": "One-tap rebook of my last appointment", "type": "solution", "schemaData": {"title": "One-tap rebook of my last appointment", "type": "solution", "status": "identified", "parent": "[[I can't rebook my favourite stylist in one step]]"}, "linkTargets": ["One-tap rebook of my last appointment"]} -{"id": "Book me with whichever stylist is free, to make booking faster.md", "title": "Book me with whichever stylist is free, to make booking faster", "type": "solution", "schemaData": {"title": "Book me with whichever stylist is free, to make booking faster", "type": "solution", "status": "identified", "parent": "[[I can't rebook my favourite stylist in one step]]"}, "linkTargets": ["Book me with whichever stylist is free, to make booking faster"]} {"id": "Charge a fee to rebook within a week.md", "title": "Charge a fee to rebook within a week", "type": "solution", "schemaData": {"title": "Charge a fee to rebook within a week", "type": "solution", "status": "identified", "parent": "[[I can't rebook my favourite stylist in one step]]"}, "linkTargets": ["Charge a fee to rebook within a week"]} {"id": "I broke a page and want it back the way it was.md", "title": "I broke a page and want it back the way it was", "type": "opportunity", "schemaData": {"title": "I broke a page and want it back the way it was", "type": "opportunity", "status": "identified", "parent": "[[Help people find past work]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I broke a page and want it back the way it was"]} {"id": "Version history for every page.md", "title": "Version history for every page", "type": "solution", "schemaData": {"title": "Version history for every page", "type": "solution", "status": "identified", "parent": "[[I broke a page and want it back the way it was]]"}, "linkTargets": ["Version history for every page"]} diff --git a/evals/semantic/fixtures/strategy-trees.jsonl b/evals/semantic/fixtures/strategy-trees.jsonl index 8d8b7918..0e6e9404 100644 --- a/evals/semantic/fixtures/strategy-trees.jsonl +++ b/evals/semantic/fixtures/strategy-trees.jsonl @@ -15,7 +15,7 @@ {"id": "Run a quarterly retro on workload.md", "title": "Run a quarterly retro on workload", "type": "goal", "schemaData": {"title": "Run a quarterly retro on workload", "type": "goal", "status": "active", "parent": "[[Improve team health]]"}, "linkTargets": ["Run a quarterly retro on workload"]} {"id": "Grow the newsletter.md", "title": "Grow the newsletter", "type": "goal", "schemaData": {"title": "Grow the newsletter", "type": "goal", "status": "active", "content": "Grow subscribers and keep them reading."}, "linkTargets": ["Grow the newsletter"], "content": "Grow subscribers and keep them reading."} {"id": "Reach 5,000 subscribers.md", "title": "Reach 5,000 subscribers", "type": "goal", "schemaData": {"title": "Reach 5,000 subscribers", "type": "goal", "status": "active", "parent": "[[Grow the newsletter]]"}, "linkTargets": ["Reach 5,000 subscribers"]} -{"id": "Get 1,000 new subscribers from social media.md", "title": "Get 1,000 new subscribers from social media", "type": "goal", "schemaData": {"title": "Get 1,000 new subscribers from social media", "type": "goal", "status": "active", "parent": "[[Grow the newsletter]]"}, "linkTargets": ["Get 1,000 new subscribers from social media"]} +{"id": "Reach 2,000 subscribers by June.md", "title": "Reach 2,000 subscribers by June", "type": "goal", "schemaData": {"title": "Reach 2,000 subscribers by June", "type": "goal", "status": "active", "parent": "[[Grow the newsletter]]"}, "linkTargets": ["Reach 2,000 subscribers by June"]} {"id": "Keep the open rate above 45%.md", "title": "Keep the open rate above 45%", "type": "goal", "schemaData": {"title": "Keep the open rate above 45%", "type": "goal", "status": "active", "parent": "[[Grow the newsletter]]"}, "linkTargets": ["Keep the open rate above 45%"]} {"id": "Make our home run on renewable energy.md", "title": "Make our home run on renewable energy", "type": "mission", "schemaData": {"title": "Make our home run on renewable energy", "type": "mission", "status": "active", "content": "Solar for electricity, and no more gas for heating, hot water or cooking."}, "linkTargets": ["Make our home run on renewable energy"], "content": "Solar for electricity, and no more gas for heating, hot water or cooking."} {"id": "Install 6 kW of rooftop solar.md", "title": "Install 6 kW of rooftop solar", "type": "goal", "schemaData": {"title": "Install 6 kW of rooftop solar", "type": "goal", "status": "active", "parent": "[[Make our home run on renewable energy]]"}, "linkTargets": ["Install 6 kW of rooftop solar"]} @@ -27,7 +27,7 @@ {"id": "Swap the gas stove for induction.md", "title": "Swap the gas stove for induction", "type": "goal", "schemaData": {"title": "Swap the gas stove for induction", "type": "goal", "status": "active", "parent": "[[Make our flat run on renewable energy]]"}, "linkTargets": ["Swap the gas stove for induction"]} {"id": "Heat the flat with reverse-cycle air conditioning.md", "title": "Heat the flat with reverse-cycle air conditioning", "type": "goal", "schemaData": {"title": "Heat the flat with reverse-cycle air conditioning", "type": "goal", "status": "active", "parent": "[[Make our flat run on renewable energy]]"}, "linkTargets": ["Heat the flat with reverse-cycle air conditioning"]} {"id": "Reach financial independence by 50.md", "title": "Reach financial independence by 50", "type": "goal", "schemaData": {"title": "Reach financial independence by 50", "type": "goal", "status": "active", "content": "Save and invest enough that investments cover our living costs."}, "linkTargets": ["Reach financial independence by 50"], "content": "Save and invest enough that investments cover our living costs."} -{"id": "Save 30% of income.md", "title": "Save 30% of income", "type": "goal", "schemaData": {"title": "Save 30% of income", "type": "goal", "status": "active", "parent": "[[Reach financial independence by 50]]"}, "linkTargets": ["Save 30% of income"]} +{"id": "Save 30% of income, counting super contributions.md", "title": "Save 30% of income, counting super contributions", "type": "goal", "schemaData": {"title": "Save 30% of income, counting super contributions", "type": "goal", "status": "active", "parent": "[[Reach financial independence by 50]]"}, "linkTargets": ["Save 30% of income, counting super contributions"]} {"id": "Make the maximum super contributions every year.md", "title": "Make the maximum super contributions every year", "type": "goal", "schemaData": {"title": "Make the maximum super contributions every year", "type": "goal", "status": "active", "parent": "[[Reach financial independence by 50]]"}, "linkTargets": ["Make the maximum super contributions every year"]} {"id": "Pay off the mortgage by 45.md", "title": "Pay off the mortgage by 45", "type": "goal", "schemaData": {"title": "Pay off the mortgage by 45", "type": "goal", "status": "active", "parent": "[[Reach financial independence by 50]]"}, "linkTargets": ["Pay off the mortgage by 45"]} {"id": "Help remote teams feel connected.md", "title": "Help remote teams feel connected", "type": "mission", "schemaData": {"title": "Help remote teams feel connected", "type": "mission", "status": "active", "content": "Informal conversation between colleagues, and time together in person."}, "linkTargets": ["Help remote teams feel connected"], "content": "Informal conversation between colleagues, and time together in person."} @@ -41,8 +41,8 @@ {"id": "Release v2 on the App Store.md", "title": "Release v2 on the App Store", "type": "goal", "schemaData": {"title": "Release v2 on the App Store", "type": "goal", "status": "active", "parent": "[[Ship version 2 of the app]]"}, "linkTargets": ["Release v2 on the App Store"]} {"id": "Release v2 on Google Play.md", "title": "Release v2 on Google Play", "type": "goal", "schemaData": {"title": "Release v2 on Google Play", "type": "goal", "status": "active", "parent": "[[Ship version 2 of the app]]"}, "linkTargets": ["Release v2 on Google Play"]} {"id": "Grow the bakery's revenue.md", "title": "Grow the bakery's revenue", "type": "goal", "schemaData": {"title": "Grow the bakery's revenue", "type": "goal", "status": "active", "content": "More revenue from walk-in customers and from wholesale."}, "linkTargets": ["Grow the bakery's revenue"], "content": "More revenue from walk-in customers and from wholesale."} -{"id": "Win three new café wholesale accounts.md", "title": "Win three new café wholesale accounts", "type": "goal", "schemaData": {"title": "Win three new café wholesale accounts", "type": "goal", "status": "active", "parent": "[[Grow the bakery's revenue]]"}, "linkTargets": ["Win three new café wholesale accounts"]} {"id": "Increase wholesale revenue by 20%.md", "title": "Increase wholesale revenue by 20%", "type": "goal", "schemaData": {"title": "Increase wholesale revenue by 20%", "type": "goal", "status": "active", "parent": "[[Grow the bakery's revenue]]"}, "linkTargets": ["Increase wholesale revenue by 20%"]} +{"id": "Grow wholesale revenue from cafés by 20%.md", "title": "Grow wholesale revenue from cafés by 20%", "type": "goal", "schemaData": {"title": "Grow wholesale revenue from cafés by 20%", "type": "goal", "status": "active", "parent": "[[Grow the bakery's revenue]]"}, "linkTargets": ["Grow wholesale revenue from cafés by 20%"]} {"id": "Raise average walk-in spend to $15.md", "title": "Raise average walk-in spend to $15", "type": "goal", "schemaData": {"title": "Raise average walk-in spend to $15", "type": "goal", "status": "active", "parent": "[[Grow the bakery's revenue]]"}, "linkTargets": ["Raise average walk-in spend to $15"]} {"id": "Grow the market stall's revenue.md", "title": "Grow the market stall's revenue", "type": "goal", "schemaData": {"title": "Grow the market stall's revenue", "type": "goal", "status": "active", "content": "More revenue from the stall and from restaurant orders."}, "linkTargets": ["Grow the market stall's revenue"], "content": "More revenue from the stall and from restaurant orders."} {"id": "Sign two new restaurant customers.md", "title": "Sign two new restaurant customers", "type": "goal", "schemaData": {"title": "Sign two new restaurant customers", "type": "goal", "status": "active", "parent": "[[Grow the market stall's revenue]]"}, "linkTargets": ["Sign two new restaurant customers"]} @@ -68,7 +68,7 @@ {"id": "Open our own flagship bookshop.md", "title": "Open our own flagship bookshop", "type": "goal", "schemaData": {"title": "Open our own flagship bookshop", "type": "goal", "status": "active", "parent": "[[Give independent bookshops the tools to compete online]]"}, "linkTargets": ["Open our own flagship bookshop"]} {"id": "Grow our social media following.md", "title": "Grow our social media following", "type": "goal", "schemaData": {"title": "Grow our social media following", "type": "goal", "status": "active", "parent": "[[Give independent bookshops the tools to compete online]]"}, "linkTargets": ["Grow our social media following"]} {"id": "Give independent bookshops online tools.md", "title": "Give independent bookshops online tools", "type": "goal", "schemaData": {"title": "Give independent bookshops online tools", "type": "goal", "status": "active", "parent": "[[Give independent bookshops the tools to compete online]]"}, "linkTargets": ["Give independent bookshops online tools"]} -{"id": "Sell e-books on Kindle.md", "title": "Sell e-books on Kindle", "type": "goal", "schemaData": {"title": "Sell e-books on Kindle", "type": "goal", "status": "active", "parent": "[[Give independent bookshops the tools to compete online]]"}, "linkTargets": ["Sell e-books on Kindle"]} +{"id": "Sell our own e-books on Kindle.md", "title": "Sell our own e-books on Kindle", "type": "goal", "schemaData": {"title": "Sell our own e-books on Kindle", "type": "goal", "status": "active", "parent": "[[Give independent bookshops the tools to compete online]]"}, "linkTargets": ["Sell our own e-books on Kindle"]} {"id": "Sign 100 bookshops to the ordering pilot.md", "title": "Sign 100 bookshops to the ordering pilot", "type": "goal", "schemaData": {"title": "Sign 100 bookshops to the ordering pilot", "type": "goal", "status": "active", "parent": "[[Launch online ordering for 100 bookshops]]"}, "linkTargets": ["Sign 100 bookshops to the ordering pilot"]} {"id": "Build a checkout that works on phones.md", "title": "Build a checkout that works on phones", "type": "goal", "schemaData": {"title": "Build a checkout that works on phones", "type": "goal", "status": "active", "parent": "[[Launch online ordering for 100 bookshops]]"}, "linkTargets": ["Build a checkout that works on phones"]} {"id": "Get 100 bookshops ordering online.md", "title": "Get 100 bookshops ordering online", "type": "goal", "schemaData": {"title": "Get 100 bookshops ordering online", "type": "goal", "status": "active", "parent": "[[Launch online ordering for 100 bookshops]]"}, "linkTargets": ["Get 100 bookshops ordering online"]} diff --git a/evals/semantic/goal-advances-parent.json5 b/evals/semantic/goal-advances-parent.json5 index 76f22aae..1b1f4989 100644 --- a/evals/semantic/goal-advances-parent.json5 +++ b/evals/semantic/goal-advances-parent.json5 @@ -5,11 +5,12 @@ 'Launch online ordering for 100 bookshops.md': 'advances', 'Help bookshops show live stock on their own websites.md': 'advances', // About bookshops, but not tools for them. - 'Open our own flagship bookshop.md': 'misplaced', + 'Open our own flagship bookshop.md': ['weak-link', 'misplaced'], 'Grow our social media following.md': 'weak-link', // Restates the mission. 'Give independent bookshops online tools.md': 'weak-link', - 'Sell e-books on Kindle.md': 'misplaced', + // Sells through a rival of the bookshops, not for them. + 'Sell our own e-books on Kindle.md': 'misplaced', 'Sign 100 bookshops to the ordering pilot.md': 'advances', 'Build a checkout that works on phones.md': 'advances', // Restates the parent goal. @@ -21,11 +22,12 @@ 'Get a promotion this year.md': ['weak-link', 'misplaced'], 'Take the kids hiking every month.md': 'advances', // Restates half of the mission. - 'Be healthier.md': 'weak-link', + 'Be healthier.md': ['advances', 'weak-link'], 'Build to 80 km weeks by January.md': 'advances', // A means to the goal more than a part of it. 'Buy new trail shoes.md': ['advances', 'weak-link'], - 'Lose 5 kg before race day.md': 'advances', + // Supports the race more than it is part of it. + 'Lose 5 kg before race day.md': ['advances', 'weak-link'], }, }, } diff --git a/evals/semantic/goals-cover-parent.json5 b/evals/semantic/goals-cover-parent.json5 index 9069c560..0f362e59 100644 --- a/evals/semantic/goals-cover-parent.json5 +++ b/evals/semantic/goals-cover-parent.json5 @@ -7,7 +7,8 @@ 'Get ready for the Six Foot Track ultra.md': 'fail', // Editing is named, and no goal covers it. 'Launch the podcast.md': 'fail', - 'Improve team health.md': 'pass', + // A retro reviews the workload, but no goal makes it sustainable. + 'Improve team health.md': 'fail', 'Grow the newsletter.md': 'pass', // Heating is named, and no goal covers it. Contrast with the next scenario. 'Make our home run on renewable energy.md': 'fail', @@ -18,12 +19,12 @@ 'Ship version 2 of the app.md': 'pass', "Grow the bakery's revenue.md": 'pass', "Grow the market stall's revenue.md": 'pass', - 'Learn Japanese for the trip.md': 'pass', + // Tutor practice is not a conversation in a shop or restaurant. + 'Learn Japanese for the trip.md': 'fail', // No goal covers handing the farm to the kids. Contrast with the next scenario. 'Run a sustainable family farm.md': 'fail', // Contrast with the previous scenario. 'Run a sustainable market garden.md': 'pass', - 'Be the go-to accountant for tradespeople.md': 'pass', }, }, } diff --git a/evals/semantic/goals-distinct.json5 b/evals/semantic/goals-distinct.json5 index 24b07da4..66b8dad1 100644 --- a/evals/semantic/goals-distinct.json5 +++ b/evals/semantic/goals-distinct.json5 @@ -5,23 +5,24 @@ 'Help small cafés run a profitable kitchen.md': 'distinct', 'Get ready for the Six Foot Track ultra.md': 'distinct', 'Launch the podcast.md': 'distinct', - // After-hours messages and working late are the same result. - 'Improve team health.md': 'duplicate', - // Social media subscribers count towards the 5,000. + // After-hours messages and working late are nearly the same result. + 'Improve team health.md': 'overlapping', + // The June milestone is part of reaching 5,000. 'Grow the newsletter.md': 'overlapping', 'Make our home run on renewable energy.md': 'distinct', 'Make our flat run on renewable energy.md': 'distinct', - // Super contributions are part of the 30% saved. + // Super contributions count towards the 30% saved. 'Reach financial independence by 50.md': 'overlapping', // The first two are the same result in different words. - 'Help remote teams feel connected.md': 'duplicate', + 'Help remote teams feel connected.md': 'overlapping', 'Renovate the bathroom.md': 'distinct', 'Ship version 2 of the app.md': 'distinct', - // New wholesale accounts are part of wholesale revenue. Contrast with the next scenario. + // Café revenue is part of wholesale revenue. Contrast with the next scenario. "Grow the bakery's revenue.md": 'overlapping', // Contrast with the previous scenario. "Grow the market stall's revenue.md": 'distinct', - 'Learn Japanese for the trip.md': 'duplicate', + // The last two are the same result. + 'Learn Japanese for the trip.md': 'overlapping', 'Run a sustainable family farm.md': 'distinct', 'Run a sustainable market garden.md': 'distinct', // Plumber clients count towards the 50 tradie clients. diff --git a/evals/semantic/okf-description-informative.json5 b/evals/semantic/okf-description-informative.json5 index 3504ce75..a47b59f9 100644 --- a/evals/semantic/okf-description-informative.json5 +++ b/evals/semantic/okf-description-informative.json5 @@ -11,8 +11,6 @@ 'Sales overview.md': 'fail', 'Database failover.md': 'fail', 'Cache warm-up.md': 'fail', - // Barely more than the title. - 'events.md': 'fail', 'Activation rate.md': 'pass', // Restates the table name in words. 'fct_orders_daily.md': 'fail', diff --git a/evals/semantic/opportunities-distinct.json5 b/evals/semantic/opportunities-distinct.json5 index 741e02ac..5037bacf 100644 --- a/evals/semantic/opportunities-distinct.json5 +++ b/evals/semantic/opportunities-distinct.json5 @@ -3,7 +3,8 @@ expected: { 'opportunities-distinct': { 'Raise weekly invoice volume.md': 'distinct', - 'Get freelancers paid faster.md': 'duplicate', + // The first two are nearly the same need. + 'Get freelancers paid faster.md': 'overlapping', // Late payment is one part of getting paid slowly. 'Shorten days to payment.md': 'overlapping', // One topic, three different needs. @@ -11,18 +12,19 @@ // Different causes of the parent need, which do not overlap each other. "I can't find the meeting notes I need.md": 'distinct', // The same need in different words. - 'New hires productive sooner.md': 'duplicate', + 'New hires productive sooner.md': 'overlapping', // The solutions overlap, but the rule ignores them. 'I miss messages from clients.md': 'distinct', // The second is broader wording of the first. - 'Grow plan upgrades.md': ['overlapping', 'duplicate'], + 'Grow plan upgrades.md': 'overlapping', 'Increase repeat bookings.md': 'distinct', - // Theft is part of feeling unsafe. Contrast with the next scenario. + // Stripped parts are a kind of theft. Contrast with the next scenario. "I don't feel safe leaving my bike at the station.md": 'overlapping', // Contrast with the previous scenario. "I don't feel safe parking my bike at work.md": 'distinct', 'More travellers check in online.md': 'distinct', - 'Fewer support calls about access.md': 'duplicate', + // The first two are the same need. + 'Fewer support calls about access.md': 'overlapping', 'Moving house is stressful.md': 'distinct', // Subscription spending is part of monthly spending. 'More members track spending weekly.md': 'overlapping', diff --git a/evals/semantic/opportunity-refines-parent.json5 b/evals/semantic/opportunity-refines-parent.json5 index d6794d95..3588a2a0 100644 --- a/evals/semantic/opportunity-refines-parent.json5 +++ b/evals/semantic/opportunity-refines-parent.json5 @@ -15,19 +15,19 @@ // One cause of the parent need. "I don't understand what the sidebar icons mean.md": 'refines', // It would drive the outcome, but it is not part of this parent. - 'I want to see what my teammates changed while I was away.md': 'misplaced', + 'I want to see what my teammates changed while I was away.md': ['misplaced', 'weak-link'], "Invitation emails land in my teammates' spam.md": 'refines', "My teammates don't know why they should join.md": 'refines', "People I invite can't join because of our single sign-on.md": 'refines', // About seats, linked to invitations only through cost. "I don't want to pay for seats nobody uses.md": 'weak-link', // A different invitation need, not why invitations are ignored. - 'I want to invite people outside my company as guests.md': 'weak-link', + 'I want to invite people outside my company as guests.md': ['weak-link', 'misplaced'], "I can't remember what I named a document.md": 'refines', "Search results show documents I don't have access to.md": 'refines', 'I want dark mode.md': 'misplaced', // Folders help finding, but bypass the search the outcome measures. - 'I want to organise documents into folders.md': 'weak-link', + 'I want to organise documents into folders.md': ['refines', 'weak-link'], // A solution in disguise, which another rule checks. It would still drive the outcome. 'Add filters to search.md': 'refines', }, diff --git a/evals/semantic/solution-addresses-opportunity.json5 b/evals/semantic/solution-addresses-opportunity.json5 index 2c0c032f..34ca903b 100644 --- a/evals/semantic/solution-addresses-opportunity.json5 +++ b/evals/semantic/solution-addresses-opportunity.json5 @@ -4,12 +4,12 @@ 'solution-addresses-opportunity': { 'Autosave every few seconds.md': 'addresses', 'Restore unsaved drafts after a crash.md': 'addresses', - 'Make the app load faster.md': 'misplaced', + 'Make the app load faster.md': ['tangential', 'misplaced'], // Eases the fear, but does not prevent the loss. "Show a 'saved' indicator in the toolbar.md": ['addresses', 'tangential'], - 'Write a help article on keyboard shortcuts.md': 'misplaced', - // Data loss, but a crash loses the work since the last backup. - 'Back up workspaces nightly to cloud storage.md': 'tangential', + 'Write a help article on keyboard shortcuts.md': ['tangential', 'misplaced'], + // Reduces the loss, but a crash still loses the work since the last backup. + 'Back up workspaces nightly to cloud storage.md': ['addresses', 'tangential'], // Protects against closing a tab, not against a crash. 'Warn before closing a tab with unsaved changes.md': 'tangential', 'Plan picker that asks for team size and recommends a plan.md': 'addresses', diff --git a/evals/semantic/solution-serves-outcome.json5 b/evals/semantic/solution-serves-outcome.json5 index 9e76b4f2..f6b3e82d 100644 --- a/evals/semantic/solution-serves-outcome.json5 +++ b/evals/semantic/solution-serves-outcome.json5 @@ -13,8 +13,6 @@ 'Notify me in the app about changes on pages I follow.md': 'pass', 'Search matches words inside documents, not only titles.md': 'pass', "Hide documents I can't open from search results.md": 'pass', - // The search can still end with the document opened. - "Show a 'request access' button on each locked result.md": 'pass', 'Pre-fill tax rates from my business address.md': 'pass', // Resolves the confusion, but delays the first invoice. 'Require a call with an accountant before the first invoice.md': 'fail', @@ -24,8 +22,6 @@ 'Add card payments through a payment provider.md': 'fail', "Text a reminder when it's been five weeks since my last cut.md": 'pass', 'One-tap rebook of my last appointment.md': 'pass', - // Faster, but loses the stylist that brings customers back. - 'Book me with whichever stylist is free, to make booking faster.md': 'fail', // Works against repeat bookings. 'Charge a fee to rebook within a week.md': 'fail', }, diff --git a/schemas/_okf.json b/schemas/_okf.json index 78bcccb1..d26d3a50 100644 --- a/schemas/_okf.json +++ b/schemas/_okf.json @@ -20,7 +20,9 @@ "category": "best-practice", "description": "A concept's description says in one line what the concept is or covers, beyond its title and type", "when": "$exists(current.description) and $length(current.description) > 0", - "semantic": {} + "semantic": { + "rubric": "Pass when the description adds what the concept contains, measures or does. Fail when it only rewords the title or type, even with a word or two added." + } }, { "id": "okf-stale-after-generated", diff --git a/schemas/_ost_strict.json b/schemas/_ost_strict.json index 071ecb29..961fe992 100644 --- a/schemas/_ost_strict.json +++ b/schemas/_ost_strict.json @@ -58,7 +58,7 @@ "guidance": "Find the customer behaviour in the product that drives it, and make that the outcome." }, "traction-metric": { - "description": "The use of one specific feature or flow, which leaves no room to discover other ways to create value", + "description": "The use of one named feature, screen, button or message, rather than the customer behaviour it is meant to drive", "guidance": "Ask what customer value that use stands for, and measure that instead." }, "output": { @@ -75,6 +75,7 @@ "type": "opportunity", "description": "A top-level opportunity drives its outcome, and a child opportunity is a subset of its parent opportunity", "semantic": { + "rubric": "Judge only how the opportunity relates to its parent. A real need in the same area as the parent does not refine the parent unless it fits the verdict's test.", "scope": ["parent"], "verdicts": { "refines": "Under an outcome, addressing it would drive the outcome. Under an opportunity, it is one part, cause or case of the parent need, narrower than the parent", @@ -101,15 +102,11 @@ "verdicts": { "distinct": "Each child opportunity is a different need", "overlapping": { - "description": "Two child opportunities partly cover the same need, or one contains another", - "guidance": "Sharpen the boundary between them, or nest the narrower one under the broader one." - }, - "duplicate": { - "description": "Two child opportunities state the same need in different words", - "guidance": "Merge them." + "description": "Two child opportunities state the same need, or partly cover the same need, or one contains another", + "guidance": "Merge them, sharpen the boundary between them, or nest the narrower one under the broader one." } }, - "severity": { "overlapping": "warning", "duplicate": "warning" } + "severity": { "overlapping": "warning" } } }, { @@ -118,6 +115,7 @@ "type": "solution", "description": "The solution addresses its parent opportunity", "semantic": { + "rubric": "Judge only whether the solution, if it worked, would resolve the parent need as the customer states it. A solution in the same area that leaves the stated need in place does not address it.", "scope": ["parent"], "verdicts": { "addresses": "If it worked, it would resolve or reduce the parent need as the customer states it", @@ -149,7 +147,9 @@ "type": "assumption_test", "description": "The assumption test's category is the kind of assumption it tests", "when": "$exists(current.category)", - "semantic": {} + "semantic": { + "rubric": "Decide from the assumption alone which kind of assumption it is, using the category definitions. Pass only when that kind is the one in `category`." + } } ] }, diff --git a/schemas/_strategy_general.json b/schemas/_strategy_general.json index 73a61742..4f761367 100644 --- a/schemas/_strategy_general.json +++ b/schemas/_strategy_general.json @@ -53,15 +53,11 @@ "verdicts": { "distinct": "Each child goal is a different result", "overlapping": { - "description": "Two child goals partly cover the same result, or one contains another", - "guidance": "Sharpen the boundary between them, or nest the narrower one under the broader one." - }, - "duplicate": { - "description": "Two child goals state the same result in different words", - "guidance": "Merge them." + "description": "Two child goals state the same result, or partly cover the same result, or one contains another", + "guidance": "Merge them, sharpen the boundary between them, or nest the narrower one under the broader one." } }, - "severity": { "overlapping": "warning", "duplicate": "warning" } + "severity": { "overlapping": "warning" } } }, { @@ -82,6 +78,7 @@ "description": "A goal advances its parent mission, or is one part of its parent goal", "when": "$exists(parent)", "semantic": { + "rubric": "Judge only how the goal relates to its parent. A worthwhile goal in the same area as the parent does not advance the parent unless it fits the verdict's test.", "scope": ["parent"], "verdicts": { "advances": "Under a mission, achieving it moves the mission forward. Under a goal, it is one part of the parent goal, narrower than the parent", From 1542de3ff7cf07a90f90aa68077d33709b660013 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 14 Nov 2026 01:55:07 +0000 Subject: [PATCH 3/4] docs: doc-review of the bundled semantic rules Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019XphpcQHEAB9KwJ5Ltiomb --- docs/schemas.md | 2 +- docs/semantic-rules.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/schemas.md b/docs/schemas.md index f9965137..4e740520 100644 --- a/docs/schemas.md +++ b/docs/schemas.md @@ -33,7 +33,7 @@ Resolution order: space `schema` > global `schema`. Schema resolution fails if n ## Bundled schemas -Each bundled schema has [semantic rules](semantic-rules.md), which run only with `--semantic`. `sctx schemas show ` lists them. +Each bundled schema has [semantic rules](semantic-rules.md). `sctx schemas show ` lists them. ### `strategy_general.json` diff --git a/docs/semantic-rules.md b/docs/semantic-rules.md index a02a29a4..7171fff4 100644 --- a/docs/semantic-rules.md +++ b/docs/semantic-rules.md @@ -146,7 +146,7 @@ Scope sends node data only, never the verdicts of other rules, and [gating](rule A global rule cannot have a `semantic` part, so it has no scope. -The bundled schemas use scope in three ways, which are good models for your own rules: +The bundled schemas use scope in three ways. We recommend them as models for your own rules: | Check | Attach the rule to | Scope | Bundled example | |---|---|---|---| From 58b54fdef2331a89c8ba18193210a683864473fa Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 14 Nov 2026 02:34:33 +0000 Subject: [PATCH 4/4] Fix eval labels that several providers disputed Drop the financial independence goals-distinct case, whose label was arguable. Reword two OST fixture nodes so the intended verdict is clear, accept either non-pass verdict for the Kindle goal, and say in the weak-link description that restating the parent counts. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019XphpcQHEAB9KwJ5Ltiomb --- evals/semantic/fixtures/ost-trees.jsonl | 4 ++-- evals/semantic/fixtures/strategy-trees.jsonl | 4 ---- evals/semantic/goal-advances-parent.json5 | 2 +- evals/semantic/goals-distinct.json5 | 2 -- evals/semantic/opportunities-distinct.json5 | 2 +- evals/semantic/solution-addresses-opportunity.json5 | 4 ++-- schemas/_strategy_general.json | 2 +- 7 files changed, 7 insertions(+), 13 deletions(-) diff --git a/evals/semantic/fixtures/ost-trees.jsonl b/evals/semantic/fixtures/ost-trees.jsonl index bb62851a..e3b52c57 100644 --- a/evals/semantic/fixtures/ost-trees.jsonl +++ b/evals/semantic/fixtures/ost-trees.jsonl @@ -81,7 +81,7 @@ {"id": "Outcome for: I don't feel safe leaving my bike at the station.md", "title": "Outcome for: I don't feel safe leaving my bike at the station", "type": "outcome", "schemaData": {"title": "Outcome for: I don't feel safe leaving my bike at the station", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["Outcome for: I don't feel safe leaving my bike at the station"]} {"id": "I don't feel safe leaving my bike at the station.md", "title": "I don't feel safe leaving my bike at the station", "type": "opportunity", "schemaData": {"title": "I don't feel safe leaving my bike at the station", "type": "opportunity", "status": "identified", "parent": "[[Outcome for: I don't feel safe leaving my bike at the station]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I don't feel safe leaving my bike at the station"]} {"id": "My bike gets stolen at the station.md", "title": "My bike gets stolen at the station", "type": "opportunity", "schemaData": {"title": "My bike gets stolen at the station", "type": "opportunity", "status": "identified", "parent": "[[I don't feel safe leaving my bike at the station]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["My bike gets stolen at the station"]} -{"id": "Thieves strip parts off my bike at the station.md", "title": "Thieves strip parts off my bike at the station", "type": "opportunity", "schemaData": {"title": "Thieves strip parts off my bike at the station", "type": "opportunity", "status": "identified", "parent": "[[I don't feel safe leaving my bike at the station]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["Thieves strip parts off my bike at the station"]} +{"id": "I worry my bike won't be there when I get back.md", "title": "I worry my bike won't be there when I get back", "type": "opportunity", "schemaData": {"title": "I worry my bike won't be there when I get back", "type": "opportunity", "status": "identified", "parent": "[[I don't feel safe leaving my bike at the station]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I worry my bike won't be there when I get back"]} {"id": "There's nowhere to charge my e-bike.md", "title": "There's nowhere to charge my e-bike", "type": "opportunity", "schemaData": {"title": "There's nowhere to charge my e-bike", "type": "opportunity", "status": "identified", "parent": "[[I don't feel safe leaving my bike at the station]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["There's nowhere to charge my e-bike"]} {"id": "Outcome for: I don't feel safe parking my bike at work.md", "title": "Outcome for: I don't feel safe parking my bike at work", "type": "outcome", "schemaData": {"title": "Outcome for: I don't feel safe parking my bike at work", "type": "outcome", "status": "identified", "metric": "Weekly count"}, "linkTargets": ["Outcome for: I don't feel safe parking my bike at work"]} {"id": "I don't feel safe parking my bike at work.md", "title": "I don't feel safe parking my bike at work", "type": "opportunity", "schemaData": {"title": "I don't feel safe parking my bike at work", "type": "opportunity", "status": "identified", "parent": "[[Outcome for: I don't feel safe parking my bike at work]]", "source": "Customer interviews, October 2026"}, "linkTargets": ["I don't feel safe parking my bike at work"]} @@ -125,7 +125,7 @@ {"id": "Send invitations from a verified domain with SPF and DKIM.md", "title": "Send invitations from a verified domain with SPF and DKIM", "type": "solution", "schemaData": {"title": "Send invitations from a verified domain with SPF and DKIM", "type": "solution", "status": "identified", "parent": "[[Invitation emails land in my teammates' spam]]"}, "linkTargets": ["Send invitations from a verified domain with SPF and DKIM"]} {"id": "Let me share an invite link in chat instead.md", "title": "Let me share an invite link in chat instead", "type": "solution", "schemaData": {"title": "Let me share an invite link in chat instead", "type": "solution", "status": "identified", "parent": "[[Invitation emails land in my teammates' spam]]"}, "linkTargets": ["Let me share an invite link in chat instead"]} {"id": "Redesign the invitation email to look friendlier.md", "title": "Redesign the invitation email to look friendlier", "type": "solution", "schemaData": {"title": "Redesign the invitation email to look friendlier", "type": "solution", "status": "identified", "parent": "[[Invitation emails land in my teammates' spam]]"}, "linkTargets": ["Redesign the invitation email to look friendlier"]} -{"id": "Remind teammates in the app to accept invites.md", "title": "Remind teammates in the app to accept invites", "type": "solution", "schemaData": {"title": "Remind teammates in the app to accept invites", "type": "solution", "status": "identified", "parent": "[[Invitation emails land in my teammates' spam]]"}, "linkTargets": ["Remind teammates in the app to accept invites"]} +{"id": "Show invited teammates an in-app banner to accept their invite.md", "title": "Show invited teammates an in-app banner to accept their invite", "type": "solution", "schemaData": {"title": "Show invited teammates an in-app banner to accept their invite", "type": "solution", "status": "identified", "parent": "[[Invitation emails land in my teammates' spam]]"}, "linkTargets": ["Show invited teammates an in-app banner to accept their invite"]} {"id": "Bulk import teammates from a spreadsheet.md", "title": "Bulk import teammates from a spreadsheet", "type": "solution", "schemaData": {"title": "Bulk import teammates from a spreadsheet", "type": "solution", "status": "identified", "parent": "[[Invitation emails land in my teammates' spam]]"}, "linkTargets": ["Bulk import teammates from a spreadsheet"]} {"id": "Send invitations from a verified domain.md", "title": "Send invitations from a verified domain", "type": "solution", "schemaData": {"title": "Send invitations from a verified domain", "type": "solution", "status": "identified", "parent": "[[My teammates ignore my invitations]]"}, "linkTargets": ["Send invitations from a verified domain"]} {"id": "Let invited teammates preview the workspace before signing up.md", "title": "Let invited teammates preview the workspace before signing up", "type": "solution", "schemaData": {"title": "Let invited teammates preview the workspace before signing up", "type": "solution", "status": "identified", "parent": "[[My teammates ignore my invitations]]"}, "linkTargets": ["Let invited teammates preview the workspace before signing up"]} diff --git a/evals/semantic/fixtures/strategy-trees.jsonl b/evals/semantic/fixtures/strategy-trees.jsonl index 0e6e9404..861c8a4d 100644 --- a/evals/semantic/fixtures/strategy-trees.jsonl +++ b/evals/semantic/fixtures/strategy-trees.jsonl @@ -26,10 +26,6 @@ {"id": "Replace the gas hot water system with a heat pump.md", "title": "Replace the gas hot water system with a heat pump", "type": "goal", "schemaData": {"title": "Replace the gas hot water system with a heat pump", "type": "goal", "status": "active", "parent": "[[Make our flat run on renewable energy]]"}, "linkTargets": ["Replace the gas hot water system with a heat pump"]} {"id": "Swap the gas stove for induction.md", "title": "Swap the gas stove for induction", "type": "goal", "schemaData": {"title": "Swap the gas stove for induction", "type": "goal", "status": "active", "parent": "[[Make our flat run on renewable energy]]"}, "linkTargets": ["Swap the gas stove for induction"]} {"id": "Heat the flat with reverse-cycle air conditioning.md", "title": "Heat the flat with reverse-cycle air conditioning", "type": "goal", "schemaData": {"title": "Heat the flat with reverse-cycle air conditioning", "type": "goal", "status": "active", "parent": "[[Make our flat run on renewable energy]]"}, "linkTargets": ["Heat the flat with reverse-cycle air conditioning"]} -{"id": "Reach financial independence by 50.md", "title": "Reach financial independence by 50", "type": "goal", "schemaData": {"title": "Reach financial independence by 50", "type": "goal", "status": "active", "content": "Save and invest enough that investments cover our living costs."}, "linkTargets": ["Reach financial independence by 50"], "content": "Save and invest enough that investments cover our living costs."} -{"id": "Save 30% of income, counting super contributions.md", "title": "Save 30% of income, counting super contributions", "type": "goal", "schemaData": {"title": "Save 30% of income, counting super contributions", "type": "goal", "status": "active", "parent": "[[Reach financial independence by 50]]"}, "linkTargets": ["Save 30% of income, counting super contributions"]} -{"id": "Make the maximum super contributions every year.md", "title": "Make the maximum super contributions every year", "type": "goal", "schemaData": {"title": "Make the maximum super contributions every year", "type": "goal", "status": "active", "parent": "[[Reach financial independence by 50]]"}, "linkTargets": ["Make the maximum super contributions every year"]} -{"id": "Pay off the mortgage by 45.md", "title": "Pay off the mortgage by 45", "type": "goal", "schemaData": {"title": "Pay off the mortgage by 45", "type": "goal", "status": "active", "parent": "[[Reach financial independence by 50]]"}, "linkTargets": ["Pay off the mortgage by 45"]} {"id": "Help remote teams feel connected.md", "title": "Help remote teams feel connected", "type": "mission", "schemaData": {"title": "Help remote teams feel connected", "type": "mission", "status": "active", "content": "Informal conversation between colleagues, and time together in person."}, "linkTargets": ["Help remote teams feel connected"], "content": "Informal conversation between colleagues, and time together in person."} {"id": "Increase how often teammates talk outside meetings.md", "title": "Increase how often teammates talk outside meetings", "type": "goal", "schemaData": {"title": "Increase how often teammates talk outside meetings", "type": "goal", "status": "active", "parent": "[[Help remote teams feel connected]]"}, "linkTargets": ["Increase how often teammates talk outside meetings"]} {"id": "More informal conversations between colleagues.md", "title": "More informal conversations between colleagues", "type": "goal", "schemaData": {"title": "More informal conversations between colleagues", "type": "goal", "status": "active", "parent": "[[Help remote teams feel connected]]"}, "linkTargets": ["More informal conversations between colleagues"]} diff --git a/evals/semantic/goal-advances-parent.json5 b/evals/semantic/goal-advances-parent.json5 index 1b1f4989..850eee61 100644 --- a/evals/semantic/goal-advances-parent.json5 +++ b/evals/semantic/goal-advances-parent.json5 @@ -10,7 +10,7 @@ // Restates the mission. 'Give independent bookshops online tools.md': 'weak-link', // Sells through a rival of the bookshops, not for them. - 'Sell our own e-books on Kindle.md': 'misplaced', + 'Sell our own e-books on Kindle.md': ['weak-link', 'misplaced'], 'Sign 100 bookshops to the ordering pilot.md': 'advances', 'Build a checkout that works on phones.md': 'advances', // Restates the parent goal. diff --git a/evals/semantic/goals-distinct.json5 b/evals/semantic/goals-distinct.json5 index 66b8dad1..ccc522cc 100644 --- a/evals/semantic/goals-distinct.json5 +++ b/evals/semantic/goals-distinct.json5 @@ -11,8 +11,6 @@ 'Grow the newsletter.md': 'overlapping', 'Make our home run on renewable energy.md': 'distinct', 'Make our flat run on renewable energy.md': 'distinct', - // Super contributions count towards the 30% saved. - 'Reach financial independence by 50.md': 'overlapping', // The first two are the same result in different words. 'Help remote teams feel connected.md': 'overlapping', 'Renovate the bathroom.md': 'distinct', diff --git a/evals/semantic/opportunities-distinct.json5 b/evals/semantic/opportunities-distinct.json5 index 5037bacf..8f6226c7 100644 --- a/evals/semantic/opportunities-distinct.json5 +++ b/evals/semantic/opportunities-distinct.json5 @@ -18,7 +18,7 @@ // The second is broader wording of the first. 'Grow plan upgrades.md': 'overlapping', 'Increase repeat bookings.md': 'distinct', - // Stripped parts are a kind of theft. Contrast with the next scenario. + // Both children are the fear of theft, in other words. Contrast with the next scenario. "I don't feel safe leaving my bike at the station.md": 'overlapping', // Contrast with the previous scenario. "I don't feel safe parking my bike at work.md": 'distinct', diff --git a/evals/semantic/solution-addresses-opportunity.json5 b/evals/semantic/solution-addresses-opportunity.json5 index 34ca903b..da4a7387 100644 --- a/evals/semantic/solution-addresses-opportunity.json5 +++ b/evals/semantic/solution-addresses-opportunity.json5 @@ -25,8 +25,8 @@ 'Let me share an invite link in chat instead.md': 'addresses', // The same email still lands in spam. 'Redesign the invitation email to look friendlier.md': 'tangential', - // Invited teammates are not in the app yet. - 'Remind teammates in the app to accept invites.md': ['tangential', 'misplaced'], + // Invited teammates are not in the app yet, so they never see the banner. + 'Show invited teammates an in-app banner to accept their invite.md': ['tangential', 'misplaced'], // Changes how invites are created, not where they land. 'Bulk import teammates from a spreadsheet.md': ['tangential', 'misplaced'], }, diff --git a/schemas/_strategy_general.json b/schemas/_strategy_general.json index 4f761367..d2334220 100644 --- a/schemas/_strategy_general.json +++ b/schemas/_strategy_general.json @@ -83,7 +83,7 @@ "verdicts": { "advances": "Under a mission, achieving it moves the mission forward. Under a goal, it is one part of the parent goal, narrower than the parent", "weak-link": { - "description": "Related to the parent, but the link is indirect, or it only restates the parent", + "description": "Related to the parent, but the link is indirect, or it restates the parent in other words instead of naming one part of it", "guidance": "Say how it advances the parent, or narrow it to one part of the parent." }, "misplaced": {