tokens-for-good 0.4.23 → 0.4.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # Tokens for Good
2
2
 
3
- Donate your spare AI tokens to research nonprofit organizations for [Fierce Philanthropy](https://tokensforgood.ai)'s social impact directory. Like Folding@Home, but for AI tokens crowdsourced compute for social good.
3
+ Contribute your spare AI tokens to research nonprofit organizations for [Fierce Philanthropy](https://tokensforgood.ai)'s social impact directory. Like Folding@Home, but for AI tokens; crowdsourced compute for social good.
4
4
 
5
5
  Works with Claude Code, OpenCode, Cursor, Windsurf, Devin, and Qwen Code as an MCP server.
6
6
 
7
+ **Docs & troubleshooting:** [tokensforgood.ai/docs](https://tokensforgood.ai/docs)
8
+
7
9
  ## Quickstart
8
10
 
9
11
  1. **Sign up** at [tokensforgood.ai/contribute](https://tokensforgood.ai/contribute) (GitHub OAuth, free) and copy your API key.
@@ -13,7 +15,7 @@ Works with Claude Code, OpenCode, Cursor, Windsurf, Devin, and Qwen Code as an M
13
15
  npx tokens-for-good init
14
16
  ```
15
17
 
16
- init is interactive: it asks for your API key, the cadence you want (daily, with a chosen number of runs per day / weekly / one-off), and then writes everything MCP config, SessionStart hook, `/tfg` and `/tfg-schedule` skills, and your preference in one shot.
18
+ init is interactive: it asks for your API key, the cadence you want (daily, with a chosen number of runs per day / weekly / one-off), and then writes everything (MCP config, SessionStart hook, `/tfg` and `/tfg-schedule` skills, and your preference) in one shot.
17
19
 
18
20
  3. **Open your AI coding tool.** Your first session acts on the cadence you picked automatically:
19
21
  - Scheduled → it sets up `/schedule` via the `/tfg-schedule` skill.
@@ -25,18 +27,18 @@ To change cadence later, run `npx tokens-for-good init` again.
25
27
 
26
28
  Each org takes about 5 minutes:
27
29
 
28
- 1. **Research** web search + 6-prompt methodology, then fill in a v3 EVIDENCE TABLE (8 rows of verbatim quotes + real URLs; blanks are honest when the evidence doesn't exist)
29
- 2. **Verify** every citation URL checked, hallucinations flagged and corrected
30
- 3. **Humanize** voice pass (no em dashes, no AI-tells, analyst voice)
30
+ 1. **Research:** web search + 6-prompt methodology, then fill in a v3 EVIDENCE TABLE (8 rows of verbatim quotes + real URLs; blanks are honest when the evidence doesn't exist)
31
+ 2. **Verify:** every citation URL checked, hallucinations flagged and corrected
32
+ 3. **Humanize:** voice pass (no em dashes, no AI-tells, analyst voice)
31
33
 
32
- Under v3 dual-research, every org is researched by two contributors independently. An independent validator then prunes any unsupported or fabricated evidence from both reports reading the server's cached page text, so it does zero web fetches and a consolidator merges the validated EVIDENCE TABLEs into one table the server scores deterministically (out of 120). A human reviewer finalizes it for the directory.
34
+ Under v3 dual-research, every org is researched by two contributors independently. An independent validator then prunes any unsupported or fabricated evidence from both reports (reading the server's cached page text, so it does zero web fetches), and a consolidator merges the validated EVIDENCE TABLEs into one table the server scores deterministically (out of 120). A human reviewer finalizes it for the directory.
33
35
 
34
36
  ## Contributor tiers
35
37
 
36
- - **New** first 5 orgs, easy orgs only
37
- - **Bronze** 5+ orgs
38
- - **Silver** 25+ orgs, >80% acceptance rate
39
- - **Gold** 100+ orgs, >90% acceptance rate
38
+ - **New:** first 5 orgs, easy orgs only
39
+ - **Bronze:** 5+ orgs
40
+ - **Silver:** 25+ orgs, >80% acceptance rate
41
+ - **Gold:** 100+ orgs, >90% acceptance rate
40
42
 
41
43
  Track your progress at `npx tokens-for-good --impact` or on the dashboard.
42
44
 
@@ -46,28 +48,27 @@ Once installed, these are available to your AI via the MCP server:
46
48
 
47
49
  | Tool | Purpose |
48
50
  |---|---|
49
- | `next_action` | Tells you whether to validate, consolidate, or research next |
50
51
  | `claim_org` | Reserves the next available nonprofit (up to 2 researchers per org under v3) |
51
52
  | `get_methodology` | Fetches research / verify / humanize / validate / consolidate instructions |
52
53
  | `submit_report` | Submits a finished report (or a merged consolidation report with `disagreement_rows`) |
53
54
  | `get_next_validation` | v3 validator: fetch both reports + cached page text to prune unsupported evidence |
54
55
  | `submit_validation` | v3 validator: submit corrected reports (subtract/correct only, never add) |
55
56
  | `get_next_consolidation` | v3 consolidator: fetch your assignment + both source reports to merge |
56
- | `set_role_preference` | Prefer the low-fetch roles (validation/consolidation) best for local models |
57
+ | `set_role_preference` | Prefer the low-fetch roles (validation/consolidation); best for local models |
57
58
  | `create_agent` / `list_agents` / `rotate_agent_key` / `revoke_agent` | Run several harnesses at once: each agent gets its own key + research slot |
58
59
  | `setup_automation` | Emits `/schedule` prompt (normally called by `/tfg-schedule` skill) |
59
- | `my_impact` / `research_status` / `get_badge` | Stats, leaderboard, GitHub README badge |
60
+ | `my_impact` / `research_status` | Your stats + the project leaderboard |
60
61
  | `snooze` | Quiet the session-start prompt for N days |
61
62
 
62
63
  ## Non-Claude-Code platforms
63
64
 
64
- - **OpenCode** `init` writes `~/.config/opencode/opencode.json` and prints a cron line you can paste into `crontab -e`.
65
- - **Qwen Code** `init` writes `~/.qwen/settings.json` (preserving other keys) plus a `/tfg` slash command at `~/.qwen/commands/tfg.md`. For recurring runs, enable Qwen Code's experimental cron (`QWEN_CODE_ENABLE_CRON=1`) or use a system cron line.
66
- - **Cursor / Windsurf / Devin** `init` writes the MCP config; automation requires platform-native scheduling.
65
+ - **OpenCode:** `init` writes `~/.config/opencode/opencode.json` and prints a cron line you can paste into `crontab -e`.
66
+ - **Qwen Code:** `init` writes `~/.qwen/settings.json` (preserving other keys) plus a `/tfg` slash command at `~/.qwen/commands/tfg.md`. For recurring runs, enable Qwen Code's experimental cron (`QWEN_CODE_ENABLE_CRON=1`) or use a system cron line.
67
+ - **Cursor / Windsurf / Devin:** `init` writes the MCP config; automation requires platform-native scheduling.
67
68
 
68
69
  ## Contributing
69
70
 
70
- TFG has been built and tested primarily on **Claude Code**. Making it work well on other harnesses OpenCode, Cursor, Windsurf, Devin, anything else with MCP support is the biggest open area for external help. See [CONTRIBUTING.md](CONTRIBUTING.md) for a tour of the code, the specific touch points a harness port needs to hit (`src/platform.js`, `src/init.js`, the session-start hook, and the skill files), and the local testing pattern.
71
+ TFG has been built and tested primarily on **Claude Code**. Making it work well on other harnesses (OpenCode, Cursor, Windsurf, Devin, anything else with MCP support) is the biggest open area for external help. See [CONTRIBUTING.md](CONTRIBUTING.md) for a tour of the code, the specific touch points a harness port needs to hit (`src/platform.js`, `src/init.js`, the session-start hook, and the skill files), and the local testing pattern.
71
72
 
72
73
  For quick dev setup:
73
74
 
@@ -81,4 +82,4 @@ The MCP server entry point is `src/mcp-server.js`. The CLI is `src/cli.js`.
81
82
 
82
83
  ## License
83
84
 
84
- MIT see [LICENSE](LICENSE).
85
+ MIT; see [LICENSE](LICENSE).
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "tokens-for-good",
3
- "version": "0.4.23",
3
+ "version": "0.4.25",
4
4
  "type": "module",
5
- "description": "Donate your spare AI tokens to research nonprofits for Fierce Philanthropy",
5
+ "description": "Contribute your spare AI tokens to research nonprofits for Fierce Philanthropy",
6
6
  "homepage": "https://tokensforgood.ai",
7
7
  "repository": {
8
8
  "type": "git",
@@ -4,7 +4,7 @@
4
4
 
5
5
  You are a social impact research analyst for Fierce Philanthropy. You evaluate nonprofit organizations using Todd Manwaring's Social Impact Evaluation Framework. You are thorough, evidence-driven, and honest about what the data does and does not show.
6
6
 
7
- **v3 note (read this):** In v3 you no longer score your own report. You write the analysis, then fill in an EVIDENCE TABLE with verbatim quotes and real URLs. The score is computed deterministically from that table by code, not by you. Empty rows are fine they're the honest answer when the evidence doesn't exist. Inventing evidence to fill a row hurts the org and gets your report rejected.
7
+ **v3 note (read this):** In v3 you no longer score your own report. You write the analysis, then fill in an EVIDENCE TABLE with verbatim quotes and real URLs. The score is computed deterministically from that table by code, not by you. Empty rows are fine; they're the honest answer when the evidence doesn't exist. Inventing evidence to fill a row hurts the org and gets your report rejected.
8
8
 
9
9
  ## Instructions
10
10
 
@@ -12,11 +12,11 @@ You are a social impact research analyst for Fierce Philanthropy. You evaluate n
12
12
 
13
13
  Using web search and web fetch, thoroughly research:
14
14
 
15
- 1. **The org's website** homepage, about page, impact/results pages, annual reports
16
- 2. **Impact evidence** published data, metrics, program evaluations
17
- 3. **Independent evaluations** RCTs, quasi-experimental studies (search J-PAL, 3ie, Campbell Collaboration)
18
- 4. **Third-party reviews** GiveWell, Charity Navigator, GuideStar/Candid, news coverage
19
- 5. **Financial data** ProPublica Nonprofit Explorer (search by EIN or name), Form 990
15
+ 1. **The org's website:** homepage, about page, impact/results pages, annual reports
16
+ 2. **Impact evidence:** published data, metrics, program evaluations
17
+ 3. **Independent evaluations:** RCTs, quasi-experimental studies (search J-PAL, 3ie, Campbell Collaboration)
18
+ 4. **Third-party reviews:** GiveWell, Charity Navigator, GuideStar/Candid, news coverage
19
+ 5. **Financial data:** ProPublica Nonprofit Explorer (search by EIN or name), Form 990
20
20
 
21
21
  **Research rules:**
22
22
  - Only include DIRECT results from this organization or independent measurements of it.
@@ -40,20 +40,20 @@ Follow this exact structure:
40
40
 
41
41
  ---
42
42
 
43
- #### PROMPT 1 Organization and Social Problem Summary
43
+ #### PROMPT 1; Organization and Social Problem Summary
44
44
 
45
45
  1. **Social Problem:** (less than 5 words)
46
46
  2. **Population:** (who is affected)
47
47
  3. **Location:** (where)
48
48
 
49
- #### PROMPT 2 Top 20 Negative Consequences
49
+ #### PROMPT 2; Top 20 Negative Consequences
50
50
 
51
51
  | # | Negative Consequence |
52
52
  |---|----------------------|
53
53
 
54
54
  List the top 20 negative consequences of that social problem for that population in that location.
55
55
 
56
- #### PROMPT 3 Intermediary vs Ultimate Outcome Classification
56
+ #### PROMPT 3; Intermediary vs Ultimate Outcome Classification
57
57
 
58
58
  Keep all 20 items. Add a column classifying each as Intermediary or Ultimate Outcome.
59
59
 
@@ -61,7 +61,7 @@ Keep all 20 items. Add a column classifying each as Intermediary or Ultimate Out
61
61
  - **Intermediary:** changes in behavior, action, or resources that result from the intervention but don't yet prove lives improved (e.g., increased income, employment, school enrollment, access to healthcare, consumption)
62
62
  - **Ultimate:** changes in condition or life status that directly reflect well-being improvements (e.g., improved health, housing security, quality of life, food security)
63
63
 
64
- **Edge cases apply these exactly:**
64
+ **Edge cases; apply these exactly:**
65
65
  - Getting healthcare = Intermediary. Health actually improving = Ultimate.
66
66
  - Income going up = Intermediary. Using that income to improve housing, education, or health = Ultimate.
67
67
  - Moving out of poverty = Intermediary. Well-being or quality of life improving because of it = Ultimate.
@@ -72,16 +72,16 @@ Keep all 20 items. Add a column classifying each as Intermediary or Ultimate Out
72
72
 
73
73
  Sort by Intermediary first, then Ultimate.
74
74
 
75
- #### PROMPT 4 Positive Results Shared by Organization
75
+ #### PROMPT 4; Positive Results Shared by Organization
76
76
 
77
77
  Keep the table with all columns. For each of the 20 negative consequences, add a column: does the organization share positive results?
78
78
 
79
79
  - Start each cell with "Yes.", "Partial.", or "No direct results shared."
80
80
  - When Yes or Partial: include SPECIFIC data (percentages, sample sizes, time periods, study names)
81
81
  - Only direct results from this organization, not from other orgs or modeling
82
- - **Citation rules apply** see section 3 below.
82
+ - **Citation rules apply:** see section 3 below.
83
83
 
84
- #### PROMPT 5 Counterfactual Results
84
+ #### PROMPT 5; Counterfactual Results
85
85
 
86
86
  Keep the table with ALL previous columns. For each of the 20 negative consequences, add a column: does the organization share COUNTERFACTUAL results?
87
87
 
@@ -91,41 +91,41 @@ Keep the table with ALL previous columns. For each of the 20 negative consequenc
91
91
 
92
92
  #### SUMMARY REPORT
93
93
 
94
- **Section 1 Our Recommendation**
94
+ **Section 1; Our Recommendation**
95
95
  Write a recommendation (2-4 sentences): lead with stance, state strongest evidence, note caveats if any.
96
96
 
97
- **Section 2 The Social Problem**
97
+ **Section 2; The Social Problem**
98
98
  Frame with specificity ("chronic malnutrition among children under 5 in rural sub-Saharan Africa", not just "poverty"). Include scale and cite prevalence data.
99
99
 
100
- **Section 3 The Solution**
100
+ **Section 3; The Solution**
101
101
  What the organization actually does (not their mission statement). Explain the theory of change: how does activity X lead to outcome Y? Be specific about the intervention.
102
102
 
103
- **Section 4 Key Outputs**
103
+ **Section 4; Key Outputs**
104
104
  Measured activities and direct products with specific numbers. Distinguish outputs (things produced) from outcomes (changes caused).
105
105
 
106
- **Section 5 Key Intermediate Outcomes**
106
+ **Section 5; Key Intermediate Outcomes**
107
107
  Measurable short-to-medium term changes. Note whether data is self-reported or independently verified. Include any counterfactual data found.
108
108
 
109
- **Section 6 Key Ultimate Outcomes**
109
+ **Section 6; Key Ultimate Outcomes**
110
110
  Long-term impact evidence only. This section may be thin. Do not pad it. If no ultimate outcome data exists, say so in one sentence.
111
111
 
112
- **Section 7 Continual Learning & Adaptation**
112
+ **Section 7; Continual Learning & Adaptation**
113
113
  Documented program changes based on evidence. "They adapted" needs specifics: what changed, based on what data, when?
114
114
 
115
115
  #### EVIDENCE TABLE
116
116
 
117
- The score is computed deterministically by code from this table, not by you. Leave a row blank if you didn't find supporting evidence a blank row is the honest answer when the org doesn't have that thing. Inventing evidence will lower the org's real score and may get the report rejected.
117
+ The score is computed deterministically by code from this table, not by you. Leave a row blank if you didn't find supporting evidence; a blank row is the honest answer when the org doesn't have that thing. Inventing evidence will lower the org's real score and may get the report rejected.
118
118
 
119
119
  **What each row asks for** (read this before filling in the table):
120
120
 
121
- - **a1** A stated condition-level outcome goal (health, income, wellbeing, food security, survival). NOT activity-level ("train X people") and NOT access-level ("expand financial access") those are intermediary.
122
- - **a2** A number or percentage attached to the goal in a1 (e.g. "reduce stunting by 30%"). The same quote as a1 is fine if it already contains the number.
123
- - **a3** A target population AND a target year for the goal (e.g. "children under 5 in Ghana by 2030"). Same quote as a1/a2 is fine if it covers both.
124
- - **b** An intermediate outcome the org MEASURED, with a number (e.g. "78% of trained CHWs retained at 24 months").
125
- - **c** An ultimate outcome the org MEASURED, with a number (e.g. "27% reduction in under-five mortality").
126
- - **d** A documented program change the org made BASED ON outcome data. The quote should make both the data and the change concrete (e.g. "In 2022 we moved to blended training after retention dropped to 45%").
127
- - **e** An intermediate result measured with a comparison or control group. Name the design (RCT, quasi-experimental, matched comparison). Before/after alone does not count.
128
- - **f** An ultimate result measured with a comparison or control group. Same design rules as e.
121
+ - **a1:** A stated condition-level outcome goal (health, income, wellbeing, food security, survival). NOT activity-level ("train X people") and NOT access-level ("expand financial access"); those are intermediary.
122
+ - **a2:** A number or percentage attached to the goal in a1 (e.g. "reduce stunting by 30%"). The same quote as a1 is fine if it already contains the number.
123
+ - **a3:** A target population AND a target year for the goal (e.g. "children under 5 in Ghana by 2030"). Same quote as a1/a2 is fine if it covers both.
124
+ - **b:** An intermediate outcome the org MEASURED, with a number (e.g. "78% of trained CHWs retained at 24 months").
125
+ - **c:** An ultimate outcome the org MEASURED, with a number (e.g. "27% reduction in under-five mortality").
126
+ - **d:** A documented program change the org made BASED ON outcome data. The quote should make both the data and the change concrete (e.g. "In 2022 we moved to blended training after retention dropped to 45%").
127
+ - **e:** An intermediate result measured with a comparison or control group. Name the design (RCT, quasi-experimental, matched comparison). Before/after alone does not count.
128
+ - **f:** An ultimate result measured with a comparison or control group. Same design rules as e.
129
129
 
130
130
  | Row | Quote (verbatim from the cited page) | Source URL | Source name |
131
131
  |-----|--------------------------------------|------------|-------------|
@@ -173,7 +173,7 @@ Run these checks before submitting. They are not optional.
173
173
  **Structure:**
174
174
  - [ ] All 5 prompt tables present and complete (20 rows each)
175
175
  - [ ] All 7 summary sections present with substantive content
176
- - [ ] EVIDENCE TABLE has all 8 rows (a1, a2, a3, b, c, d, e, f) values may be blank where no evidence was found
176
+ - [ ] EVIDENCE TABLE has all 8 rows (a1, a2, a3, b, c, d, e, f); values may be blank where no evidence was found
177
177
  - [ ] SOURCES section lists every URL cited inline
178
178
 
179
179
  **Citations:**
@@ -183,7 +183,7 @@ Run these checks before submitting. They are not optional.
183
183
  - [ ] EVIDENCE TABLE quotes appear verbatim on the cited pages (substring-checked at submit time)
184
184
 
185
185
  **Writing style:**
186
- - [ ] No em dashes (—). Replace with periods, commas, or parentheses.
186
+ - [ ] No em dashes. Replace with periods, commas, semicolons, or parentheses.
187
187
  - [ ] No filler adjectives: seamless, robust, comprehensive, innovative, cutting-edge, holistic, game-changing
188
188
  - [ ] No AI transitions: "It's worth noting", "Here's the thing", "Let's dive in", "Simply put"
189
189
  - [ ] Replace "leverage" with "use", "utilize" with "use"
@@ -12,9 +12,9 @@ Read the full research report. Note every inline citation `[Source Name](URL)` a
12
12
 
13
13
  For each citation, visit the URL using web fetch and verify:
14
14
 
15
- - **URL loads** Is it a real page (not 404, not a redirect to a homepage)?
16
- - **Content matches** Does the page actually say what the report claims? Quote the relevant passage.
17
- - **Data is accurate** Do the numbers match?
15
+ - **URL loads:** Is it a real page (not 404, not a redirect to a homepage)?
16
+ - **Content matches:** Does the page actually say what the report claims? Quote the relevant passage.
17
+ - **Data is accurate:** Do the numbers match?
18
18
 
19
19
  Record each check:
20
20
 
@@ -22,11 +22,11 @@ Record each check:
22
22
  |---|----------|-----------|---------------|-------|
23
23
 
24
24
  Status values:
25
- - **VALID** URL loads and content matches
26
- - **BROKEN** 404 or page doesn't load
27
- - **MISMATCH** URL loads but doesn't support the claim
28
- - **PARTIAL** Some claims match, some don't
29
- - **UNVERIFIABLE** Paywalled or content not accessible
25
+ - **VALID:** URL loads and content matches
26
+ - **BROKEN:** 404 or page doesn't load
27
+ - **MISMATCH:** URL loads but doesn't support the claim
28
+ - **PARTIAL:** Some claims match, some don't
29
+ - **UNVERIFIABLE:** Paywalled or content not accessible
30
30
 
31
31
  ### 3. Re-attribute Mismatches
32
32
 
@@ -1,4 +1,4 @@
1
- # Step 3: Humanize Instructions
1
+ # Step 3: Humanize; Instructions
2
2
 
3
3
  ## Inputs
4
4
 
@@ -23,7 +23,7 @@ The AI decontamination passes below are your checklist.
23
23
  Work through these checks in order. For each issue found, fix it and log the change.
24
24
 
25
25
  #### Pass 1: Em Dash Removal
26
- - Search for every `—` (em dash) in the content
26
+ - Search for every `, ` (em dash) in the content
27
27
  - Replace each with a period (two sentences), comma, or parentheses
28
28
  - Two short sentences almost always beat one em-dashed sentence
29
29
  - Log count: "Removed X em dashes"
@@ -47,7 +47,7 @@ Work through these checks in order. For each issue found, fix it and log the cha
47
47
 
48
48
  #### Pass 5: AI Pattern Scan
49
49
  Check for and fix:
50
- - [ ] "[Statement]. Not because X because Y." dramatic structure
50
+ - [ ] "[Statement]. Not because X; because Y." dramatic structure
51
51
  - [ ] "Not just X, but Y" emphasis pattern
52
52
  - [ ] "Whether X or Y" parallel constructions
53
53
  - [ ] "From X to Y" range statements
@@ -2,16 +2,16 @@
2
2
 
3
3
  ## Your Role
4
4
 
5
- You are a validator an independent fourth contributor who sits between the two researchers and the consolidator. Two researchers independently researched the same organization; before their reports are merged and scored, your job is to catch **unsupported or fabricated evidence** and hand back cleaned-up ("validated") versions of both reports.
5
+ You are a validator; an independent fourth contributor who sits between the two researchers and the consolidator. Two researchers independently researched the same organization; before their reports are merged and scored, your job is to catch **unsupported or fabricated evidence** and hand back cleaned-up ("validated") versions of both reports.
6
6
 
7
7
  You do **no web research and no fetching**. The server already fetched every page the researchers cited and gives you the cached text. You validate using ONLY that cached text. This is deliberate: it makes validation cheap and possible even on a local model with no scraping budget.
8
8
 
9
9
  ## Why this stage exists
10
10
 
11
- A report's score comes entirely from its EVIDENCE TABLE eight rows, each a quote + a source URL. The most dangerous failure isn't a thin report; it's a confident one citing a quote that **isn't actually on the page**, or a page that **doesn't exist**. That evidence would score real points it didn't earn. The server already runs a deterministic check (does the quote literally appear on the cached page?), and you'll see its verdicts. Your job is the judgment the deterministic check can't do:
11
+ A report's score comes entirely from its EVIDENCE TABLE; eight rows, each a quote + a source URL. The most dangerous failure isn't a thin report; it's a confident one citing a quote that **isn't actually on the page**, or a page that **doesn't exist**. That evidence would score real points it didn't earn. The server already runs a deterministic check (does the quote literally appear on the cached page?), and you'll see its verdicts. Your job is the judgment the deterministic check can't do:
12
12
 
13
13
  - Does the quote, even if it appears on the page, actually **support the row's criterion**? (e.g. an "ultimate outcome" row quoting an *output* count, or a counterfactual row quoting a result instead of a study design.)
14
- - Is the quote **cherry-picked** technically present but contradicted or heavily qualified elsewhere on the same page?
14
+ - Is the quote **cherry-picked:** technically present but contradicted or heavily qualified elsewhere on the same page?
15
15
  - Do the two reports **contradict each other** on the same row in a way that means at least one is wrong?
16
16
 
17
17
  ## Your Inputs
@@ -20,9 +20,9 @@ The `/tfg-validate` skill (or the `get_next_validation` MCP tool) returns:
20
20
  - Your validation **claim_id** (a UUID)
21
21
  - The **organization** (name, slug, url)
22
22
  - Two **source_reports** (markdown), each with its EVIDENCE TABLE and each with the server's deterministic **citation_verdicts** (per row: `verified` / `fabricated` / `unverifiable`)
23
- - **cached_pages**: `{url, fetch_status, http_status, text}` for every URL cited across both reports. `fetch_status` is `ok` (text present), `unreachable` (couldn't load includes 404s), `non_text` (a PDF or similar the server can't read), or `missing` (wasn't cached).
23
+ - **cached_pages**: `{url, fetch_status, http_status, text}` for every URL cited across both reports. `fetch_status` is `ok` (text present), `unreachable` (couldn't load; includes 404s), `non_text` (a PDF or similar the server can't read), or `missing` (wasn't cached).
24
24
 
25
- ## Your Task per row, per report
25
+ ## Your Task; per row, per report
26
26
 
27
27
  Start from the server's verdicts, then apply judgment:
28
28
 
@@ -33,26 +33,26 @@ Start from the server's verdicts, then apply judgment:
33
33
 
34
34
  **The one hard rule: you may only SUBTRACT rows or CORRECT a quote to match its cited page. You may NEVER ADD new evidence, new rows, or new URLs.** Adding evidence is the researcher's job, not yours. A validated report is always a subset (or verbatim-corrected version) of the original.
35
35
 
36
- This is **enforced by the server**: your submission is rejected (422) if it introduces a URL the original didn't cite, OR if any EVIDENCE TABLE row's quote is not actually found on its cited page. So when you correct a quote, copy it **verbatim from the cached page text** don't paraphrase. If you can't find supporting text on the page, remove the row instead of rewriting it. (Rows whose page is a PDF/blocked/un-readable are `unverifiable` and pass through you don't need to fix those.)
36
+ This is **enforced by the server**: your submission is rejected (422) if it introduces a URL the original didn't cite, OR if any EVIDENCE TABLE row's quote is not actually found on its cited page. So when you correct a quote, copy it **verbatim from the cached page text:** don't paraphrase. If you can't find supporting text on the page, remove the row instead of rewriting it. (Rows whose page is a PDF/blocked/un-readable are `unverifiable` and pass through; you don't need to fix those.)
37
37
 
38
- If a report was already clean, return it unchanged (or simply omit it from your submission omitted reports keep their original).
38
+ If a report was already clean, return it unchanged (or simply omit it from your submission; omitted reports keep their original).
39
39
 
40
40
  ## Submit
41
41
 
42
42
  Call `submit_validation` with:
43
43
  - `claim_id`: your validation claim ID
44
- - `validated_reports`: an array of `{claim_id, report_markdown}` one entry per report you changed, where `claim_id` is that **source report's** claim_id and `report_markdown` is its full corrected markdown. Omit reports you didn't change.
45
- - `validation_notes`: a short summary of what you cut and why (e.g. "rc1: dropped row f the RCT quote was a result, not a design; dropped row c quote not found on the cited page").
44
+ - `validated_reports`: an array of `{claim_id, report_markdown}`; one entry per report you changed, where `claim_id` is that **source report's** claim_id and `report_markdown` is its full corrected markdown. Omit reports you didn't change.
45
+ - `validation_notes`: a short summary of what you cut and why (e.g. "rc1: dropped row f; the RCT quote was a result, not a design; dropped row c; quote not found on the cited page").
46
46
  - `estimated_tokens`: honest estimate (validation is read-heavy but no web work).
47
47
 
48
- If **both reports are already clean** (nothing to remove or correct), submit `validated_reports` as an **empty array** with a note saying so the round then proceeds to consolidation unchanged. Don't resubmit a report you didn't change.
48
+ If **both reports are already clean** (nothing to remove or correct), submit `validated_reports` as an **empty array** with a note saying so; the round then proceeds to consolidation unchanged. Don't resubmit a report you didn't change.
49
49
 
50
- Each validated report must still be a structurally valid v3 report (all PROMPT/Section headings + an EVIDENCE TABLE + SOURCES). You're pruning evidence, not dismantling the document leave the prose and structure intact; just remove or correct the offending EVIDENCE TABLE rows (and you may drop a now-unused entry from SOURCES). **Pruning is allowed to drop a report below the usual fresh-submit floors** (≥5 citations, ≥3 domains, ≥1500 words) those aren't enforced on a validation, because removing fake evidence legitimately shrinks the report.
50
+ Each validated report must still be a structurally valid v3 report (all PROMPT/Section headings + an EVIDENCE TABLE + SOURCES). You're pruning evidence, not dismantling the document; leave the prose and structure intact; just remove or correct the offending EVIDENCE TABLE rows (and you may drop a now-unused entry from SOURCES). **Pruning is allowed to drop a report below the usual fresh-submit floors** (≥5 citations, ≥3 domains, ≥1500 words); those aren't enforced on a validation, because removing fake evidence legitimately shrinks the report.
51
51
 
52
52
  ## What not to do
53
53
 
54
54
  - **Don't add evidence.** Ever. Subtract or correct-to-source only.
55
- - **Don't fetch anything.** Use the cached_pages text. If a page is `missing`/`unreachable`, treat the citation as unverifiable don't go find it yourself.
55
+ - **Don't fetch anything.** Use the cached_pages text. If a page is `missing`/`unreachable`, treat the citation as unverifiable; don't go find it yourself.
56
56
  - **Don't punish honest blanks or real PDFs.** A blank row is fine. An `unverifiable` PDF from a credible source can stay.
57
57
  - **Don't rewrite prose.** Your edits are confined to the EVIDENCE TABLE (and removing orphaned SOURCES lines).
58
- - **Don't second-guess the deterministic `fabricated` verdict.** If the quote isn't on the page, it goes the page text is right there for you to confirm.
58
+ - **Don't second-guess the deterministic `fabricated` verdict.** If the quote isn't on the page, it goes; the page text is right there for you to confirm.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Your Role
4
4
 
5
- You are a consolidator. Two researchers independently researched the same organization without seeing each other's work. Your job is to merge their reports into a single canonical report that the deterministic scorer will score. The score comes from the merged EVIDENCE TABLE you produce not from any of the prose.
5
+ You are a consolidator. Two researchers independently researched the same organization without seeing each other's work. Your job is to merge their reports into a single canonical report that the deterministic scorer will score. The score comes from the merged EVIDENCE TABLE you produce; not from any of the prose.
6
6
 
7
7
  You do not do new web research. You work entirely from the two source reports.
8
8
 
@@ -13,7 +13,7 @@ The `/tfg-consolidate` skill (or the `get_next_consolidation` MCP tool) returns:
13
13
  - The **organization** (name, slug, url)
14
14
  - Two **source_reports** (markdown), each ending in an EVIDENCE TABLE
15
15
 
16
- ## Your Task the EVIDENCE TABLE
16
+ ## Your Task; the EVIDENCE TABLE
17
17
 
18
18
  For each row (a1, a2, a3, b, c, d, e, f), compare the two source reports' quotes and decide:
19
19
 
@@ -29,11 +29,11 @@ If you flag **≥3 rows** as disagreements, the server automatically spawns a th
29
29
 
30
30
  ## The Merged Report Structure
31
31
 
32
- Produce a single v3-shaped report. For PROMPT 1–5 and Section 1–7, take the stronger version from one of the two researchers you are not rewriting. Your real work is the EVIDENCE TABLE.
32
+ Produce a single v3-shaped report. For PROMPT 1–5 and Section 1–7, take the stronger version from one of the two researchers; you are not rewriting. Your real work is the EVIDENCE TABLE.
33
33
 
34
34
  The report must contain:
35
35
  - `# [Org Name] - Fierce Philanthropy Research Report` header
36
- - `**Methodology:** Todd Manwaring's Social Impact Evaluation Framework (v3 consolidated)`
36
+ - `**Methodology:** Todd Manwaring's Social Impact Evaluation Framework (v3; consolidated)`
37
37
  - PROMPT 1, 2, 3, 4, 5 (copy/paste the stronger versions)
38
38
  - Sections 1 through 7 (copy/paste the stronger versions)
39
39
  - A single merged **EVIDENCE TABLE** (your consolidation work, 8 rows: a1–a3, b, c, d, e, f)
@@ -54,6 +54,6 @@ Call `submit_report` with:
54
54
 
55
55
  - Don't invent evidence. If neither researcher cited it, it doesn't go in.
56
56
  - Don't rewrite the prose extensively. This is consolidation, not authorship.
57
- - Don't paper over real disagreements. If they materially disagree, flag the row that's the system's calibration signal.
57
+ - Don't paper over real disagreements. If they materially disagree, flag the row; that's the system's calibration signal.
58
58
  - Don't use `example.com` or placeholder URLs.
59
59
  - Don't omit `disagreement_rows`. An empty array is acceptable, but be explicit.
@@ -9,18 +9,18 @@ The user wants to consolidate a pair of independent research reports into one me
9
9
 
10
10
  1. **Call `get_next_consolidation` on the TFG MCP.** It returns:
11
11
  - the org being consolidated,
12
- - your consolidation `claim_id` (a UUID this is what you'll submit against, NOT a researcher's claim ID),
12
+ - your consolidation `claim_id` (a UUID; this is what you'll submit against, NOT a researcher's claim ID),
13
13
  - two `source_reports` (each a full v3 research report ending in an EVIDENCE TABLE),
14
14
  - the full consolidation methodology inline.
15
15
 
16
- If nothing is assigned, the tool says so tell the user and stop here.
16
+ If nothing is assigned, the tool says so; tell the user and stop here.
17
17
 
18
18
  2. **Read both source reports closely.** They were produced independently and may differ in evidence quality, citation rigor, and overall structure.
19
19
 
20
20
  3. **Merge the EVIDENCE TABLE row by row** (a1, a2, a3, b, c, d, e, f). For each row:
21
21
  - If both researchers filled the row with compatible evidence, take the stronger quote (longer, more specific, cites the exact page rather than an overview).
22
22
  - If only one filled the row, use that one's quote.
23
- - If both left it blank, leave it blank that is an honest signal that the evidence doesn't exist.
23
+ - If both left it blank, leave it blank; that is an honest signal that the evidence doesn't exist.
24
24
  - If they materially disagree (different URLs, different evidence on the same row), take the better-supported one using this hierarchy:
25
25
  1. RCT > quasi-experimental > self-reported survey > anecdote
26
26
  2. Third-party citation > org-self-cite
@@ -28,22 +28,22 @@ The user wants to consolidate a pair of independent research reports into one me
28
28
 
29
29
  AND record the row key in your `disagreement_rows` list.
30
30
 
31
- 4. **Assemble one merged v3 report**, with the same structure as a fresh v3 report (PROMPT 1-5, Sections 1-7, the merged EVIDENCE TABLE, SOURCES). For the prose sections, take the stronger version from one of the two researchers verbatim you are not rewriting. Your real work is the EVIDENCE TABLE.
31
+ 4. **Assemble one merged v3 report**, with the same structure as a fresh v3 report (PROMPT 1-5, Sections 1-7, the merged EVIDENCE TABLE, SOURCES). For the prose sections, take the stronger version from one of the two researchers verbatim; you are not rewriting. Your real work is the EVIDENCE TABLE.
32
32
 
33
33
  5. **Call `submit_report`** with:
34
34
  - `claim_id`: your consolidation claim ID from step 1
35
35
  - `report_markdown`: the merged report
36
- - `estimated_tokens`: an honest estimate (consolidations are mostly read + reorder much lighter than fresh research)
36
+ - `estimated_tokens`: an honest estimate (consolidations are mostly read + reorder; much lighter than fresh research)
37
37
  - `model_used`: e.g. `"claude-consolidator"`
38
38
  - `disagreement_rows`: array of row keys you flagged (e.g. `["a1", "e"]`). Pass an empty array if none.
39
39
 
40
- 6. **Tell the user the org name and your score impression** (e.g. "Merged report for X submitted; 2 disagreements flagged on a1 and e the system may auto-trigger a 3rd researcher.").
40
+ 6. **Tell the user the org name and your score impression** (e.g. "Merged report for X submitted; 2 disagreements flagged on a1 and e; the system may auto-trigger a 3rd researcher.").
41
41
 
42
42
  ## What not to do
43
43
 
44
44
  - Don't invent evidence. If neither researcher cited it, it doesn't enter the merged report.
45
45
  - Don't rewrite the prose extensively. Pick the stronger researcher's version verbatim.
46
- - Don't paper over real disagreements to "keep the count down" flagging ≥3 rows auto-triggers a 3rd researcher, which is the system's calibration loop. That's a feature, not a failure.
46
+ - Don't paper over real disagreements to "keep the count down"; flagging ≥3 rows auto-triggers a 3rd researcher, which is the system's calibration loop. That's a feature, not a failure.
47
47
  - Don't include `example.com` or placeholder URLs (the validator rejects them).
48
48
  - Don't omit `disagreement_rows`. An empty array is fine, but be explicit.
49
49
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: tfg-schedule
3
- description: Set up automated Tokens for Good research on a schedule (daily with a chosen number of runs per day or weekly). Use this when the user wants their AI to donate spare tokens to nonprofit research without manual prompting it wires up a recurring /schedule agent in one shot instead of making them run /schedule, pick a schedule, and paste a prompt by hand.
3
+ description: Set up automated Tokens for Good research on a schedule (daily, with a chosen number of runs per day, or weekly). Use this when the user wants their AI to contribute spare tokens to nonprofit research without manual prompting; it wires up a recurring /schedule agent in one shot instead of making them run /schedule, pick a schedule, and paste a prompt by hand.
4
4
  ---
5
5
 
6
6
  The user wants to automate Tokens for Good research on a recurring schedule.
@@ -13,23 +13,23 @@ Work out the cadence before doing anything else:
13
13
  - Otherwise ask the user, offering: **Daily** (recommended), **Weekly** (light touch), or **Skip for now**.
14
14
  - For a daily cadence with no per-day count set, ask **"How many times per day? (1–15)"**. Claude Code caps how many scheduled runs you get per day, so keep it at 15 or below. Default to 1 if the user has no preference.
15
15
 
16
- Describe cadence by frequency only keep token costs and dollar amounts out of every question and confirmation.
16
+ Describe cadence by frequency only; keep token costs and dollar amounts out of every question and confirmation.
17
17
 
18
18
  ## 2. Wire it up
19
19
 
20
20
  1. **Call the TFG MCP `setup_automation` tool** with `frequency` (`daily` or `weekly`) and, for daily, `runs_per_day`. It returns the full `/schedule` setup text: a Step 2 schedule line containing a cron expression, and a research prompt scoped to the user's API key.
21
21
 
22
- 2. **Extract the research prompt** the block delimited by `---` lines. Pass it through verbatim; don't paraphrase.
22
+ 2. **Extract the research prompt:** the block delimited by `---` lines. Pass it through verbatim; don't paraphrase.
23
23
 
24
24
  3. **Invoke the `/schedule` skill** to create the recurring trigger:
25
25
  - Schedule: the cron expression from `setup_automation`'s Step 2 line.
26
26
  - Task description: the verbatim block from step 2.
27
27
 
28
- 4. **Wait for `/schedule` to confirm success.** If it fails or the user cancels, stop here and tell the user do NOT call `mark_setup_complete`.
28
+ 4. **Wait for `/schedule` to confirm success.** If it fails or the user cancels, stop here and tell the user; do NOT call `mark_setup_complete`.
29
29
 
30
30
  5. **On success, call the TFG MCP `mark_setup_complete` tool.** This flips the user's local state so the SessionStart hook stops nudging.
31
31
 
32
- 6. **Confirm to the user** in one or two sentences, and reassure them it runs unattended, e.g. *"Scheduled your spare tokens will research a nonprofit on that cadence from here on. It runs on Anthropic's cloud, so your computer can be off and Claude Code doesn't need to be open. Change it anytime with /schedule."*
32
+ 6. **Confirm to the user** in one or two sentences, and reassure them it runs unattended, e.g. *"Scheduled ✓; your spare tokens will research a nonprofit on that cadence from here on. It runs on Anthropic's cloud, so your computer can be off and Claude Code doesn't need to be open. Change it anytime with /schedule."*
33
33
 
34
34
  ## If something goes wrong
35
35
 
@@ -40,6 +40,6 @@ Describe cadence by frequency only — keep token costs and dollar amounts out o
40
40
  ## What not to do
41
41
 
42
42
  - Keep token costs and dollar figures out of every question and confirmation.
43
- - If the user already picked a cadence at install, don't re-ask it only ask for the per-day count when it's missing for a daily cadence.
43
+ - If the user already picked a cadence at install, don't re-ask it; only ask for the per-day count when it's missing for a daily cadence.
44
44
  - Don't print the raw research prompt to the user; it's verbose and already flows through /schedule.
45
- - Don't assume the user knows what /schedule is. If they ask, briefly explain: "It's Anthropic's scheduled-task feature runs your prompt on their cloud on a cron schedule, no need to keep your laptop on."
45
+ - Don't assume the user knows what /schedule is. If they ask, briefly explain: "It's Anthropic's scheduled-task feature; runs your prompt on their cloud on a cron schedule, no need to keep your laptop on."
@@ -1,22 +1,22 @@
1
1
  ---
2
2
  name: tfg-validate
3
- description: Validate two independent v3 research reports by pruning unsupported or fabricated EVIDENCE TABLE rows, using ONLY the cached page text the server provides (no web fetches). Use this when the user has been assigned a validation, OR to check whether they have one pending. Ideal for local models it does zero scraping.
3
+ description: Validate two independent v3 research reports by pruning unsupported or fabricated EVIDENCE TABLE rows, using ONLY the cached page text the server provides (no web fetches). Use this when the user has been assigned a validation, OR to check whether they have one pending. Ideal for local models; it does zero scraping.
4
4
  ---
5
5
 
6
6
  The user wants to validate a pair of independent research reports before they're merged and scored. Under v3 dual-research, every org is researched by two contributors; you (an independent fourth contributor) catch unsupported or fabricated evidence and hand back cleaned-up versions of both reports. The consolidator then merges already-validated tables.
7
7
 
8
- You do **no web research and no fetching**. The server already fetched every cited page and gives you the cached text. This is deliberate validation is cheap and works on a local model with no scraping budget.
8
+ You do **no web research and no fetching**. The server already fetched every cited page and gives you the cached text. This is deliberate; validation is cheap and works on a local model with no scraping budget.
9
9
 
10
10
  ## What to do
11
11
 
12
12
  1. **Call `get_next_validation` on the TFG MCP.** It returns:
13
13
  - the org being validated,
14
- - your validation `claim_id` (a UUID submit against this one),
14
+ - your validation `claim_id` (a UUID; submit against this one),
15
15
  - two `source_reports` (each a full v3 report with its EVIDENCE TABLE) plus the server's deterministic `citation_verdicts` per row (`verified` / `fabricated` / `unverifiable`),
16
16
  - `cached_pages`: the text of every URL cited across both reports, with a `fetch_status` (`ok` / `unreachable` / `non_text` / `missing`),
17
17
  - the full validation methodology inline.
18
18
 
19
- If nothing is assigned, the tool says so tell the user and stop here.
19
+ If nothing is assigned, the tool says so; tell the user and stop here.
20
20
 
21
21
  2. **For each report, go row by row** (a1, a2, a3, b, c, d, e, f). Start from the server's verdict, then apply judgment using the cached page text:
22
22
  - **`fabricated`** (quote not on the page, or URL 404s) → **remove the row** (blank quote/URL/name).
@@ -27,7 +27,7 @@ You do **no web research and no fetching**. The server already fetched every cit
27
27
 
28
28
  4. **Call `submit_validation`** with:
29
29
  - `claim_id`: your validation claim ID from step 1
30
- - `validated_reports`: an array of `{ claim_id, report_markdown }` one entry per report you changed, where `claim_id` is that source report's claim_id and `report_markdown` is its full corrected markdown. Omit reports you didn't change. **If both reports are clean, pass an empty array `[]`** the round proceeds to consolidation unchanged. Pruning is allowed to drop a report below the usual ≥5-citation / ≥3-domain / ≥1500-word floors; those aren't enforced on a validation.
30
+ - `validated_reports`: an array of `{ claim_id, report_markdown }`; one entry per report you changed, where `claim_id` is that source report's claim_id and `report_markdown` is its full corrected markdown. Omit reports you didn't change. **If both reports are clean, pass an empty array `[]`:** the round proceeds to consolidation unchanged. Pruning is allowed to drop a report below the usual ≥5-citation / ≥3-domain / ≥1500-word floors; those aren't enforced on a validation.
31
31
  - `validation_notes`: a short summary of what you cut/corrected and why
32
32
  - `estimated_tokens`: an honest estimate (validation is read-heavy, no web work)
33
33
 
@@ -36,10 +36,10 @@ You do **no web research and no fetching**. The server already fetched every cit
36
36
  ## What not to do
37
37
 
38
38
  - **Don't add evidence.** Ever. Subtract or correct-to-source only.
39
- - **Don't fetch anything.** Use the `cached_pages` text. A `missing`/`unreachable` page means the citation is unverifiable don't go find it yourself.
39
+ - **Don't fetch anything.** Use the `cached_pages` text. A `missing`/`unreachable` page means the citation is unverifiable; don't go find it yourself.
40
40
  - **Don't punish honest blanks or real PDFs.** A blank row is fine; an `unverifiable` PDF from a credible source can stay.
41
41
  - **Don't rewrite prose.** Your edits are confined to the EVIDENCE TABLE (and removing now-orphaned SOURCES lines).
42
- - **Don't second-guess a `fabricated` verdict** the page text is right there; if the quote isn't in it, the row goes.
42
+ - **Don't second-guess a `fabricated` verdict:** the page text is right there; if the quote isn't in it, the row goes.
43
43
 
44
44
  ## If something goes wrong
45
45
 
package/skills/tfg.md CHANGED
@@ -1,21 +1,21 @@
1
1
  ---
2
2
  name: tfg
3
- description: Do one Tokens for Good research task end-to-end claim an org, research it using the provided methodology, and submit the report. Use when the user wants to contribute once (not set up automation). Takes about 5 minutes per org.
3
+ description: Do one Tokens for Good research task end-to-end; claim an org, research it using the provided methodology, and submit the report. Use when the user wants to contribute once (not set up automation). Takes about 5 minutes per org.
4
4
  ---
5
5
 
6
6
  The user wants to complete one Tokens for Good research cycle.
7
7
 
8
8
  ## What to do
9
9
 
10
- 0. **First, check for pending consolidations.** Call `get_next_consolidation`. If it returns an assignment, stop here and hand off to the `/tfg-consolidate` skill a pending consolidation blocks a whole round and takes priority over starting new research. If it returns "no consolidations assigned," continue to step 1.
10
+ 0. **First, check for pending consolidations.** Call `get_next_consolidation`. If it returns an assignment, stop here and hand off to the `/tfg-consolidate` skill; a pending consolidation blocks a whole round and takes priority over starting new research. If it returns "no consolidations assigned," continue to step 1.
11
11
 
12
12
  1. **Call `claim_org`** on the TFG MCP. It reserves an org for you and returns a claim ID.
13
13
 
14
- 2. **Call `get_methodology`** with `step="research"` and follow those instructions closely they specify the web searches, EVIDENCE TABLE structure, and output shape.
14
+ 2. **Call `get_methodology`** with `step="research"` and follow those instructions closely; they specify the web searches, EVIDENCE TABLE structure, and output shape.
15
15
 
16
16
  3. **Call `get_methodology`** with `step="verify"` after drafting, and check every citation URL.
17
17
 
18
- 4. **Call `get_methodology`** with `step="humanize"` for the final voice pass (remove em dashes, banned words, add analyst voice). Then call `submit_report` with the finished markdown, the model you used, and your honest `estimated_tokens` count (sum of web searches × 1K + web fetches × 2–5K + your output words × 4 + ~10K overhead). `submit_report` automatically flips `first_setup_complete` for one-off users no separate call needed.
18
+ 4. **Call `get_methodology`** with `step="humanize"` for the final voice pass (remove em dashes, banned words, add analyst voice). Then call `submit_report` with the finished markdown, the model you used, and your honest `estimated_tokens` count (sum of web searches × 1K + web fetches × 2–5K + your output words × 4 + ~10K overhead). `submit_report` automatically flips `first_setup_complete` for one-off users; no separate call needed.
19
19
 
20
20
  5. **Confirm to the user** with the acceptance stats the submit returns. If they're on the one-off flow and might convert to scheduled, mention they can run `/tfg-schedule daily` anytime to automate future runs.
21
21
 
@@ -28,5 +28,5 @@ The user wants to complete one Tokens for Good research cycle.
28
28
  ## What not to do
29
29
 
30
30
  - Don't skip verification. Hallucinated citations get reports rejected during consolidation and review.
31
- - Don't exceed 2,500 words the methodology cares about density, not length.
31
+ - Don't exceed 2,500 words; the methodology cares about density, not length.
32
32
  - Don't submit without `estimated_tokens`. The MCP tool will reject it.
package/src/api-client.js CHANGED
@@ -42,7 +42,7 @@ export class ApiClient {
42
42
 
43
43
  const response = await fetch(url, options);
44
44
  // Tolerate empty bodies (204 No Content, used by /research/consolidate/next
45
- // when nothing is assigned to you) response.json() throws on empty text.
45
+ // when nothing is assigned to you); response.json() throws on empty text.
46
46
  const text = await response.text();
47
47
  const data = text ? JSON.parse(text) : null;
48
48
 
package/src/cli.js CHANGED
@@ -9,7 +9,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
9
9
  const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
10
10
 
11
11
  const HELP = `tokens-for-good v${pkg.version}
12
- Donate your spare AI tokens to research nonprofits for Fierce Philanthropy.
12
+ Contribute your spare AI tokens to research nonprofits for Fierce Philanthropy.
13
13
 
14
14
  Usage:
15
15
  npx tokens-for-good <command> [options]
package/src/init.js CHANGED
@@ -16,9 +16,9 @@ const PKG_ROOT = join(__dirname, '..');
16
16
  const IS_WINDOWS = process.platform === 'win32';
17
17
 
18
18
  const FREQUENCY_CHOICES = [
19
- { title: 'Daily recommended; pick how many runs per day next', value: 'daily' },
20
- { title: 'Weekly light touch', value: 'weekly' },
21
- { title: "One-off I'll run research manually when I feel like it", value: 'one_off' },
19
+ { title: 'Daily (recommended); pick how many runs per day next', value: 'daily' },
20
+ { title: 'Weekly; light touch', value: 'weekly' },
21
+ { title: "One-off; I'll run research manually when I feel like it", value: 'one_off' },
22
22
  ];
23
23
 
24
24
  const PLATFORM_CHOICES = [
@@ -36,7 +36,7 @@ const onCancel = () => {
36
36
  };
37
37
 
38
38
  export async function runInit() {
39
- console.log('\n✨ Tokens for Good one-time setup\n');
39
+ console.log('\n✨ Tokens for Good; one-time setup\n');
40
40
 
41
41
  // Idempotency: offer to reconfigure if already set up
42
42
  const existing = loadState();
@@ -72,7 +72,7 @@ export async function runInit() {
72
72
  initial: detectedIdx,
73
73
  }, { onCancel });
74
74
 
75
- // Step 3: Frequency (mandatory choice this is the whole point of init)
75
+ // Step 3: Frequency (mandatory choice; this is the whole point of init)
76
76
  const { choice } = await prompts({
77
77
  type: 'select',
78
78
  name: 'choice',
@@ -81,7 +81,7 @@ export async function runInit() {
81
81
  initial: 0,
82
82
  }, { onCancel });
83
83
 
84
- // Step 3b: Daily cadence how many runs per day. Claude Code caps the
84
+ // Step 3b: Daily cadence; how many runs per day. Claude Code caps the
85
85
  // number of scheduled runs per day, so we ask for a count (1-15).
86
86
  let runs_per_day = null;
87
87
  if (choice === 'daily') {
@@ -280,13 +280,13 @@ function writeSessionStartHook() {
280
280
  }
281
281
 
282
282
  // Claude Code runs SessionStart hooks with a stripped PATH that often omits
283
- // the node/npx bin dir C:\Program Files\nodejs on Windows, or the nvm/
284
- // homebrew bin when Claude Code is launched from the macOS Dock/Finder so a
283
+ // the node/npx bin dir; C:\Program Files\nodejs on Windows, or the nvm/
284
+ // homebrew bin when Claude Code is launched from the macOS Dock/Finder; so a
285
285
  // bare `npx` lookup fails and the session shows a "hook error". Resolve the
286
286
  // absolute npx path at init time (full PATH available) and bake it in.
287
287
  //
288
- // `2>/dev/null || true` makes the hook non-fatal: this is an optional donation
289
- // tool, so a missing npx, offline registry, or any other hiccup must never
288
+ // `2>/dev/null || true` makes the hook non-fatal: this is an optional
289
+ // contribution tool, so a missing npx, offline registry, or other hiccup must never
290
290
  // disrupt the user's session. Hooks run under bash/sh on every platform (Git
291
291
  // Bash on Windows), so this syntax is portable.
292
292
  function hookCommand() {
@@ -301,7 +301,7 @@ function resolveNpxPath() {
301
301
  }
302
302
 
303
303
  // macOS / Linux: prefer `which npx`, then npx alongside the running node
304
- // binary, then a bare `npx` fallback (relies on PATH last resort).
304
+ // binary, then a bare `npx` fallback (relies on PATH; last resort).
305
305
  try {
306
306
  const r = spawnSync('which', ['npx'], { encoding: 'utf-8' });
307
307
  if (r.status === 0) {
@@ -321,7 +321,7 @@ function resolveNpxPath() {
321
321
  }
322
322
 
323
323
  function resolveWindowsNpxPath() {
324
- // First try `where npx.cmd` most reliable when PATH is correct.
324
+ // First try `where npx.cmd`; most reliable when PATH is correct.
325
325
  try {
326
326
  const r = spawnSync('where', ['npx.cmd'], { encoding: 'utf-8' });
327
327
  if (r.status === 0) {
@@ -334,7 +334,7 @@ function resolveWindowsNpxPath() {
334
334
  const alongside = join(dirname(process.execPath), 'npx.cmd');
335
335
  if (existsSync(alongside)) return alongside;
336
336
 
337
- // Last-resort guess user's hook may need manual edit if this is wrong.
337
+ // Last-resort guess; user's hook may need manual edit if this is wrong.
338
338
  return 'C:\\Program Files\\nodejs\\npx.cmd';
339
339
  }
340
340
 
@@ -361,7 +361,7 @@ function printClosingGuidance(platform, flow, freq, runsPerDay = 1) {
361
361
  const cadence = freq === 'daily' && runsPerDay > 1 ? `daily (${runsPerDay}× per day)` : freq;
362
362
  if (platform === 'claude-code') {
363
363
  if (flow === 'scheduled') {
364
- console.log(`Next: open Claude Code and send any message even "hi".`);
364
+ console.log(`Next: open Claude Code and send any message; even "hi".`);
365
365
  console.log(`On that first message Claude sets up /schedule at ${cadence} cadence and`);
366
366
  console.log(`confirms with "Scheduled ✓". Prefer to be explicit? Just run /tfg-schedule.`);
367
367
  console.log('');
@@ -369,13 +369,13 @@ function printClosingGuidance(platform, flow, freq, runsPerDay = 1) {
369
369
  console.log(`runs setup hooks after your first message, so type something to kick it off.`);
370
370
  console.log('');
371
371
  console.log('');
372
- console.log(`Once it's scheduled, runs happen on Anthropic's cloud your computer can be`);
372
+ console.log(`Once it's scheduled, runs happen on Anthropic's cloud; your computer can be`);
373
373
  console.log(`off and Claude Code doesn't need to be open. It just runs on its own from here.`);
374
374
  console.log('');
375
375
  console.log(`Verify it worked: run /schedule in Claude Code to see your recurring task,`);
376
376
  console.log(`or check for the "Auto-contributing" badge on your dashboard.`);
377
377
  } else {
378
- console.log(`Next: open Claude Code and send any message even "hi" and Claude will`);
378
+ console.log(`Next: open Claude Code and send any message; even "hi"; and Claude will`);
379
379
  console.log(`offer to research your first org. Prefer to be explicit? Just run /tfg.`);
380
380
  console.log('');
381
381
  console.log(`Heads up: nothing happens the instant the window opens. Claude Code only`);
package/src/mcp-server.js CHANGED
@@ -24,7 +24,7 @@ Tell the user to run this in their terminal (not here in the chat), then restart
24
24
  The init command asks them to choose a contribution cadence (daily / weekly / one-off) and wires up everything else automatically. It takes about 30 seconds.`;
25
25
 
26
26
  // Gate: only fires for genuinely cold installs where state.json is missing
27
- // entirely. Existing users including those on the pre-0.4.0 schema pass
27
+ // entirely. Existing users; including those on the pre-0.4.0 schema; pass
28
28
  // through untouched. Init writes state.json on first successful completion,
29
29
  // so after that this never fires again.
30
30
  function notInitialized() {
@@ -61,16 +61,16 @@ The command walks them through everything in under a minute:
61
61
  3. Pick a cadence: **daily** (recommended; choose how many runs per day), weekly, or one-off
62
62
  4. Confirm
63
63
 
64
- init writes everything MCP config, SessionStart hook, /tfg and /tfg-schedule skills, and their recorded preference in one shot. The first session after init runs their chosen flow automatically.
64
+ init writes everything (MCP config, SessionStart hook, /tfg and /tfg-schedule skills, and their recorded preference) in one shot. The first session after init runs their chosen flow automatically.
65
65
 
66
- **What is Tokens for Good?** A way for developers to donate their spare AI subscription tokens to research nonprofit organizations for Fierce Philanthropy's social impact directory. Each org takes about 5 minutes. Contributors get credit on a public leaderboard.`;
66
+ **What is Tokens for Good?** A way for developers to contribute their spare AI subscription tokens to research nonprofit organizations for Fierce Philanthropy's social impact directory. Each org takes about 5 minutes. Contributors get credit on a public leaderboard.`;
67
67
 
68
68
  // --- Resources ---
69
69
 
70
70
  server.resource('about', 'tokens-for-good://about', 'text/plain', async () => ({
71
71
  contents: [{
72
72
  uri: 'tokens-for-good://about',
73
- text: `Tokens for Good - Donate Your Spare AI Tokens to Research Nonprofits
73
+ text: `Tokens for Good - Contribute Your Spare AI Tokens to Research Nonprofits
74
74
 
75
75
  What: An MCP server that lets AI coding tool users (Claude Code, Opencode, Cursor, Windsurf, Devin) contribute their spare subscription tokens to research nonprofit organizations for Fierce Philanthropy's social impact directory.
76
76
 
@@ -87,7 +87,7 @@ Research pipeline (per org, all done by your AI):
87
87
  - Research the org using web search + web fetch, following the v3 EVIDENCE TABLE methodology
88
88
  - Fill in an EVIDENCE TABLE (8 rows of verbatim quotes + real URLs); leave blanks honestly when the evidence doesn't exist
89
89
  - The server (not you) computes the score deterministically from the merged consolidator output, out of 120
90
- - Real URLs only placeholder citations (example.com) are auto-rejected
90
+ - Real URLs only; placeholder citations (example.com) are auto-rejected
91
91
 
92
92
  Contributor tiers:
93
93
  - New: first 5 orgs, easy orgs only
@@ -112,10 +112,10 @@ server.tool('claim_org', 'Claim the next available nonprofit org to research.',
112
112
  try {
113
113
  const result = await client.claimOrg(plat || platform);
114
114
  return {
115
- content: [{ type: 'text', text: `Claimed: ${result.org.name} (${result.org.url})\nclaim_id: ${result.claim_id}\nexpires: ${result.expires_at}\nNext: get_methodology step="research", then submit_report.` }],
115
+ content: [{ type: 'text', text: `Claimed: ${result.org.name} (${result.org.url})\nclaim_id: ${result.claim_id}\nexpires: ${result.expires_at}\nNext: get_methodology step="research" to research, then step="verify" and step="humanize" to check citations and clean the voice, then submit_report. (The /tfg skill walks all four.)` }],
116
116
  };
117
117
  } catch (err) {
118
- // 409 means you already have an active claim usually an auto-assigned
118
+ // 409 means you already have an active claim; usually an auto-assigned
119
119
  // 3rd-researcher slot you were handed without asking. Surface the existing
120
120
  // org + claim_id so the agent can continue the assigned task instead of
121
121
  // bouncing off "you have a claim" with no idea which one.
@@ -220,10 +220,10 @@ server.tool('get_next_validation', 'Get your assigned v3 validation: both resear
220
220
  validateMethodology = 'Using ONLY the cached page text provided, remove EVIDENCE TABLE rows whose quote is not on its cited page (verdict "fabricated"), and correct quotes that do not match. You may only SUBTRACT or CORRECT-to-source, never ADD. Submit the corrected reports with submit_validation.';
221
221
  }
222
222
  const reports = (result.source_reports || []).map((r, i) =>
223
- `### Source report ${i + 1} claim_id ${r.claim_id} (submitted ${r.submitted_at || 'unknown'})\n\nServer citation verdicts: ${JSON.stringify(r.citation_verdicts || {})}\n\n${r.report_markdown}`
223
+ `### Source report ${i + 1}; claim_id ${r.claim_id} (submitted ${r.submitted_at || 'unknown'})\n\nServer citation verdicts: ${JSON.stringify(r.citation_verdicts || {})}\n\n${r.report_markdown}`
224
224
  ).join('\n\n---\n\n');
225
225
  const pages = (result.cached_pages || []).map((p) =>
226
- `#### ${p.url} [${p.fetch_status}${p.http_status ? ' ' + p.http_status : ''}]\n${p.text ? p.text : '(no text not machine-checkable)'}`
226
+ `#### ${p.url} [${p.fetch_status}${p.http_status ? ' ' + p.http_status : ''}]\n${p.text ? p.text : '(no text; not machine-checkable)'}`
227
227
  ).join('\n\n');
228
228
  return {
229
229
  content: [{ type: 'text', text: `Validation assigned:\nOrg: ${result.org?.name}\nRound: ${result.round_id}\nYour validation claim ID (submit against this one): ${result.claim_id}\n\n---\n\n${validateMethodology}\n\n--- SOURCE REPORTS ---\n\n${reports}\n\n--- CACHED PAGE TEXT (use ONLY this; do not fetch) ---\n\n${pages}` }],
@@ -236,7 +236,7 @@ server.tool('get_next_validation', 'Get your assigned v3 validation: both resear
236
236
  }
237
237
  });
238
238
 
239
- server.tool('submit_validation', 'Submit a validation: corrected ("validated") versions of the source reports. You may only SUBTRACT unsupported/fabricated EVIDENCE TABLE rows or CORRECT a quote to match its cited page never ADD new evidence. Provide full corrected markdown for each report you changed; omit reports you left unchanged.', {
239
+ server.tool('submit_validation', 'Submit a validation: corrected ("validated") versions of the source reports. You may only SUBTRACT unsupported/fabricated EVIDENCE TABLE rows or CORRECT a quote to match its cited page; never ADD new evidence. Provide full corrected markdown for each report you changed; omit reports you left unchanged.', {
240
240
  claim_id: z.string().describe('Your validation claim_id from get_next_validation'),
241
241
  validated_reports: z.array(z.object({
242
242
  claim_id: z.string().describe('A source report\'s claim_id'),
@@ -290,14 +290,14 @@ server.tool('list_agents', 'List your agents (concurrent workers). Each has its
290
290
  }
291
291
  });
292
292
 
293
- server.tool('create_agent', 'Create a new agent (a separate concurrent worker) and get its API key so you can run another harness at the same time (e.g. a local Qwen alongside Codex). Returns the key ONCE. Subject to your agent limit (default 2).', {
293
+ server.tool('create_agent', 'Create a new agent (a separate concurrent worker) and get its API key; so you can run another harness at the same time (e.g. a local Qwen alongside Codex). Returns the key ONCE. Subject to your agent limit (default 2).', {
294
294
  label: z.string().describe('A name for the new worker, e.g. "qwen-local".'),
295
295
  prefer_low_fetch_roles: z.boolean().optional().describe('true if this agent runs a local/cheap model (prefers validation/consolidation).'),
296
296
  }, async ({ label, prefer_low_fetch_roles }) => {
297
297
  if (!client) return { content: [{ type: 'text', text: 'Error: TFG_API_KEY not set.' }] };
298
298
  try {
299
299
  const result = await client.createAgent(label, !!prefer_low_fetch_roles);
300
- return { content: [{ type: 'text', text: `Created agent "${result.agent.label}" (id ${result.agent.id}).\n\nIts API key (shown once configure the other harness with it):\n${result.api_key}` }] };
300
+ return { content: [{ type: 'text', text: `Created agent "${result.agent.label}" (id ${result.agent.id}).\n\nIts API key (shown once; configure the other harness with it):\n${result.api_key}` }] };
301
301
  } catch (err) {
302
302
  return { content: [{ type: 'text', text: `Error: ${err.message}` }] };
303
303
  }
@@ -352,7 +352,7 @@ server.tool('my_impact', 'See your personal contribution stats, tier, and histor
352
352
  const result = await client.getImpact();
353
353
  const c = result.contributor;
354
354
 
355
- // Older server builds omit github_handle from the impact response fall
355
+ // Older server builds omit github_handle from the impact response; fall
356
356
  // back to display_name so we never print "@undefined".
357
357
  const who = c.github_handle ? `@${c.github_handle}` : (c.display_name || 'you');
358
358
  return {
@@ -367,7 +367,7 @@ server.tool('setup_guide', 'Get setup instructions for Tokens for Good. Use this
367
367
  return { content: [{ type: 'text', text: NO_KEY_INSTRUCTIONS }] };
368
368
  });
369
369
 
370
- server.tool('setup_automation', 'Get the scheduled-research prompt + setup instructions for the user\'s platform. Usually called by the /tfg-schedule skill (which extracts the prompt and invokes /schedule). Safe to call directly too returns human-readable instructions.', {
370
+ server.tool('setup_automation', 'Get the scheduled-research prompt + setup instructions for the user\'s platform. Usually called by the /tfg-schedule skill (which extracts the prompt and invokes /schedule). Safe to call directly too; returns human-readable instructions.', {
371
371
  frequency: z.enum(['daily', 'weekly']).optional().describe('How often to contribute'),
372
372
  runs_per_day: z.number().int().min(1).max(15).optional().describe('For daily cadence: how many research runs per day (1-15). Defaults to 1.'),
373
373
  }, async ({ frequency, runs_per_day }) => {
@@ -376,12 +376,12 @@ server.tool('setup_automation', 'Get the scheduled-research prompt + setup instr
376
376
  return { content: [{ type: 'text', text: instructions }] };
377
377
  });
378
378
 
379
- server.tool('mark_setup_complete', 'Called by the /tfg-schedule skill after /schedule confirms, or by the /tfg skill after a successful first submission. Flips local state so the SessionStart hook stops emitting first-session instructions. Idempotent safe to call multiple times.', {}, async () => {
379
+ server.tool('mark_setup_complete', 'Called by the /tfg-schedule skill after /schedule confirms, or by the /tfg skill after a successful first submission. Flips local state so the SessionStart hook stops emitting first-session instructions. Idempotent; safe to call multiple times.', {}, async () => {
380
380
  markSetupComplete();
381
381
 
382
382
  // If the user just wired up a recurring schedule, tell the server too. This
383
383
  // is what flips `has_schedule`, which lights the "Auto-contributing" badge on
384
- // their dashboard the only product-side confirmation that scheduling worked.
384
+ // their dashboard; the only product-side confirmation that scheduling worked.
385
385
  // Best-effort: the badge is non-critical, so a failure here never blocks setup.
386
386
  const state = loadState();
387
387
  if (client && (state.intended_flow === 'scheduled' || state.auto_schedule)) {
@@ -399,7 +399,7 @@ server.tool('snooze', 'Snooze Tokens for Good reminders. Call this when the user
399
399
  days: z.number().int().min(1).max(365).describe('Days to snooze (1 = tomorrow, 7 = next week)'),
400
400
  }, async ({ days }) => {
401
401
  snoozeDays(days);
402
- return { content: [{ type: 'text', text: `Got it Tokens for Good will stay quiet for ${days} day${days === 1 ? '' : 's'}.` }] };
402
+ return { content: [{ type: 'text', text: `Got it; Tokens for Good will stay quiet for ${days} day${days === 1 ? '' : 's'}.` }] };
403
403
  });
404
404
 
405
405
  // --- Prompts (session start) ---
@@ -445,7 +445,7 @@ server.prompt('session_start', 'Check if you should research an org', {}, async
445
445
  return {
446
446
  messages: [{
447
447
  role: 'user',
448
- content: { type: 'text', text: `Tokens for Good: Would you like to donate your spare tokens to research a nonprofit today?\n\n1. Set up automatic daily contributions (recommended) run /tfg-schedule\n2. Just run one now run /tfg\n3. Ask me tomorrow\n4. Ask me in a week` },
448
+ content: { type: 'text', text: `Tokens for Good: Would you like to contribute your spare tokens to research a nonprofit today?\n\n1. Set up automatic daily contributions (recommended): run /tfg-schedule\n2. Just run one now: run /tfg\n3. Ask me tomorrow\n4. Ask me in a week` },
449
449
  }],
450
450
  };
451
451
  } else {
package/src/platform.js CHANGED
@@ -32,8 +32,8 @@ export function getSchedulePrompt(apiKey) {
32
32
  API Base: ${base}
33
33
  Your API key: ${apiKey}
34
34
 
35
- ## Calling the API read first
36
- Use the **Bash tool with \`curl\`** for every authenticated ${base}/* call (next-action, claim, submit, consolidate). **WebFetch will NOT work for these it cannot send the X-TFG-Api-Key header or POST a body, so it returns 401/403 and the run fails.** Always include \`-H "X-TFG-Api-Key: ${apiKey}"\`. WebSearch and WebFetch are only for the actual web research, not for our API.
35
+ ## Calling the API; read first
36
+ Use the **Bash tool with \`curl\`** for every authenticated ${base}/* call (next-action, claim, submit, validate, consolidate). **WebFetch will NOT work for these; it cannot send the X-TFG-Api-Key header or POST a body, so it returns 401/403 and the run fails.** Always include \`-H "X-TFG-Api-Key: ${apiKey}"\`. WebSearch and WebFetch are only for the actual web research, not for our API.
37
37
 
38
38
  ## Instructions
39
39
  1. Fetch the batch flow (public): \`curl -s "${base}/research/schedule-instructions"\`
@@ -89,7 +89,7 @@ Devin runs in the cloud, fully autonomous.`;
89
89
  case 'qwen-code':
90
90
  return `Set up automated contributions on Qwen Code.
91
91
 
92
- Qwen Code v0.14+ has experimental built-in cron enable it with QWEN_CODE_ENABLE_CRON=1 (or "experimental.cron": true in ~/.qwen/settings.json) and then use the Cron tool / /loop skill.
92
+ Qwen Code v0.14+ has experimental built-in cron; enable it with QWEN_CODE_ENABLE_CRON=1 (or "experimental.cron": true in ~/.qwen/settings.json) and then use the Cron tool / /loop skill.
93
93
 
94
94
  For a portable option, use a system cron job (add via crontab -e):
95
95
  ${cron} cd /path/to/workspace && qwen --prompt "Research a nonprofit org for Fierce Philanthropy using the tokens-for-good MCP tools. Claim an org, research it, then submit the report."
@@ -19,7 +19,7 @@ export function runSessionStartHook() {
19
19
  // Never-initialized users: hook is a no-op. Setup happens via init, not here.
20
20
  if (!state.intended_flow) return;
21
21
 
22
- // First session after init emit the handoff instruction exactly once.
22
+ // First session after init; emit the handoff instruction exactly once.
23
23
  if (!state.first_setup_complete) {
24
24
  emit(firstSessionMessage(state));
25
25
  return;