taketomarket 2.2.0 → 2.3.1
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/.claude-plugin/marketplace.json +13 -17
- package/.claude-plugin/plugin.json +2 -2
- package/README.md +35 -12
- package/bin/lib/campaign.cjs +12 -8
- package/bin/lib/codebase-scan.cjs +86 -0
- package/bin/lib/config.cjs +129 -0
- package/bin/lib/deploy.cjs +36 -0
- package/bin/lib/deviation.cjs +1 -1
- package/bin/lib/drift-log.cjs +4 -4
- package/bin/lib/health.cjs +32 -31
- package/bin/lib/install-detect.cjs +62 -0
- package/bin/lib/legacy-folder.cjs +100 -0
- package/bin/lib/playwright-check.cjs +26 -0
- package/bin/lib/site-location.cjs +22 -0
- package/bin/lib/state.cjs +3 -3
- package/bin/lib/svg-render.cjs +42 -0
- package/bin/ttm-tools.cjs +136 -4
- package/gates/base-gates.md +8 -8
- package/gates/gate-evaluation.md +8 -8
- package/install.js +37 -3
- package/package.json +10 -6
- package/playbooks/aeo.md +218 -114
- package/playbooks/affiliate.md +225 -160
- package/playbooks/email.md +236 -174
- package/playbooks/events.md +303 -213
- package/playbooks/landing-pages.md +309 -0
- package/playbooks/linkedin.md +264 -142
- package/playbooks/manifesto.md +322 -0
- package/playbooks/paid-ads.md +240 -189
- package/playbooks/positioning.md +340 -0
- package/playbooks/pr-media.md +308 -168
- package/playbooks/pseo.md +426 -0
- package/playbooks/seo.md +251 -158
- package/playbooks/social.md +253 -182
- package/playbooks/youtube.md +286 -181
- package/references/brand-color-theory.md +48 -0
- package/references/codex-image-gen-research.md +58 -0
- package/references/context-loading.md +6 -6
- package/references/humanizer-patterns.md +433 -0
- package/references/inline-education-blurbs.md +461 -0
- package/references/landing-page-anatomy.md +65 -0
- package/references/landing-page-headline-examples.md +190 -0
- package/references/linkedin-post-patterns.md +174 -0
- package/references/logo-design-principles.md +55 -0
- package/references/meta-gate-evaluation.md +3 -3
- package/references/obra-superpowers-conventions.md +170 -0
- package/references/playbook-leaders.md +472 -0
- package/references/playwright-mcp-setup.md +164 -0
- package/references/positioning-check-report.md +2 -2
- package/references/pseo-page-anatomy.md +56 -0
- package/references/pseo-templates/alternative-anatomy.md +31 -0
- package/references/pseo-templates/alternative-content-playbook.md +32 -0
- package/references/pseo-templates/blog-anatomy.md +28 -0
- package/references/pseo-templates/blog-content-playbook.md +36 -0
- package/references/pseo-templates/comparison-anatomy.md +29 -0
- package/references/pseo-templates/comparison-content-playbook.md +35 -0
- package/references/pseo-templates/use-case-anatomy.md +28 -0
- package/references/pseo-templates/use-case-content-playbook.md +30 -0
- package/skills/ttm-101/SKILL.md +25 -0
- package/skills/ttm-aeo-check/SKILL.md +17 -12
- package/skills/ttm-affiliate-kit/SKILL.md +5 -0
- package/skills/ttm-archive/SKILL.md +5 -0
- package/skills/ttm-brand-refresh/SKILL.md +5 -0
- package/skills/ttm-brief/SKILL.md +5 -0
- package/skills/ttm-competitor-scan/SKILL.md +5 -0
- package/skills/ttm-deploy/SKILL.md +22 -0
- package/skills/ttm-discover/SKILL.md +17 -0
- package/skills/ttm-email-check/SKILL.md +17 -0
- package/skills/ttm-email-preflight/SKILL.md +17 -11
- package/skills/ttm-fix/SKILL.md +5 -0
- package/skills/ttm-health/SKILL.md +6 -1
- package/skills/ttm-humanize/SKILL.md +33 -0
- package/skills/ttm-icp-refresh/SKILL.md +5 -0
- package/skills/ttm-improve-skill/SKILL.md +18 -0
- package/skills/ttm-init/SKILL.md +10 -3
- package/skills/ttm-keyword-map/SKILL.md +17 -11
- package/skills/ttm-landing/SKILL.md +19 -0
- package/skills/ttm-learn/SKILL.md +5 -0
- package/skills/ttm-linkedin-post/SKILL.md +26 -0
- package/skills/ttm-measure/SKILL.md +5 -0
- package/skills/ttm-new-campaign/SKILL.md +5 -0
- package/skills/ttm-next/SKILL.md +5 -0
- package/skills/ttm-playwright-setup/SKILL.md +18 -0
- package/skills/ttm-positioning-check/SKILL.md +5 -0
- package/skills/ttm-positioning-shift/SKILL.md +5 -0
- package/skills/ttm-produce/SKILL.md +5 -0
- package/skills/ttm-pseo/SKILL.md +26 -0
- package/skills/ttm-repurpose/SKILL.md +5 -0
- package/skills/ttm-request-skill/SKILL.md +18 -0
- package/skills/ttm-research/SKILL.md +18 -6
- package/skills/ttm-resume/SKILL.md +5 -0
- package/skills/ttm-review/SKILL.md +5 -0
- package/skills/ttm-seo/SKILL.md +64 -0
- package/skills/ttm-seo-audit/SKILL.md +17 -12
- package/skills/ttm-ship/SKILL.md +5 -0
- package/skills/ttm-state/SKILL.md +5 -0
- package/skills/ttm-update/SKILL.md +152 -4
- package/skills/ttm-verify/SKILL.md +5 -0
- package/templates/agents-md.md +14 -4
- package/templates/campaign-research.md +6 -6
- package/templates/campaign-state.md +1 -1
- package/templates/claude-md.md +14 -4
- package/templates/linkedin-base-template.md +48 -0
- package/templates/next-step-footer.md +13 -0
- package/templates/production-manifest.json +4 -4
- package/templates/pseo/alternative-cms-schema.json +65 -0
- package/templates/pseo/blog-cms-schema.json +55 -0
- package/templates/pseo/comparison-cms-schema.json +56 -0
- package/templates/pseo/use-case-cms-schema.json +62 -0
- package/templates/reference-files/brand.md +51 -0
- package/templates/reference-files/product-dna.md +73 -0
- package/templates/site-scaffold/app/globals.css +2 -0
- package/templates/site-scaffold/app/layout.tsx +17 -0
- package/templates/site-scaffold/app/page.tsx +33 -0
- package/templates/site-scaffold/app/robots.ts +8 -0
- package/templates/site-scaffold/app/sitemap.ts +10 -0
- package/templates/site-scaffold/app/tokens.css +21 -0
- package/templates/site-scaffold/components/Comparison.tsx +14 -0
- package/templates/site-scaffold/components/Faq.tsx +14 -0
- package/templates/site-scaffold/components/Features.tsx +14 -0
- package/templates/site-scaffold/components/FinalCta.tsx +17 -0
- package/templates/site-scaffold/components/Footer.tsx +12 -0
- package/templates/site-scaffold/components/Hero.tsx +22 -0
- package/templates/site-scaffold/components/HowItWorks.tsx +14 -0
- package/templates/site-scaffold/components/PricingTeaser.tsx +14 -0
- package/templates/site-scaffold/components/Problem.tsx +14 -0
- package/templates/site-scaffold/components/SocialProof.tsx +14 -0
- package/templates/site-scaffold/components/Solution.tsx +14 -0
- package/templates/site-scaffold/components/Testimonials.tsx +14 -0
- package/templates/site-scaffold/components/UseCases.tsx +14 -0
- package/templates/site-scaffold/content/.gitkeep +0 -0
- package/templates/site-scaffold/lib/.gitkeep +0 -0
- package/templates/site-scaffold/next.config.mjs +10 -0
- package/templates/site-scaffold/package.json +25 -0
- package/templates/site-scaffold/postcss.config.mjs +3 -0
- package/templates/site-scaffold/public/llms.txt +9 -0
- package/templates/site-scaffold/tsconfig.json +21 -0
- package/templates/verification-report.md +1 -1
- package/workflows/channel/linkedin-post.md +178 -0
- package/workflows/discipline/affiliate-kit.md +65 -6
- package/workflows/discipline/{email-preflight.md → email-check.md} +39 -4
- package/workflows/discipline/repurpose.md +82 -31
- package/workflows/discipline/{aeo-check.md → seo/aeo.md} +13 -6
- package/workflows/discipline/{seo-audit.md → seo/audit.md} +13 -6
- package/workflows/discipline/{keyword-map.md → seo/keyword-map.md} +13 -6
- package/workflows/education/ttm-101.md +114 -0
- package/workflows/lifecycle/brief-positioning-check.md +1 -1
- package/workflows/lifecycle/brief.md +64 -28
- package/workflows/lifecycle/{research.md → discover.md} +61 -19
- package/workflows/lifecycle/fix.md +72 -37
- package/workflows/lifecycle/humanize.md +280 -0
- package/workflows/lifecycle/learn.md +72 -35
- package/workflows/lifecycle/measure.md +54 -18
- package/workflows/lifecycle/produce.md +88 -37
- package/workflows/lifecycle/review.md +71 -25
- package/workflows/lifecycle/ship.md +62 -18
- package/workflows/lifecycle/verify.md +72 -26
- package/workflows/reference-mgmt/brand-refresh.md +50 -13
- package/workflows/reference-mgmt/competitor-scan.md +51 -15
- package/workflows/reference-mgmt/icp-refresh.md +48 -12
- package/workflows/reference-mgmt/positioning-check.md +55 -20
- package/workflows/reference-mgmt/positioning-shift.md +53 -17
- package/workflows/setup/init-brand-colors.md +75 -0
- package/workflows/setup/init-logo.md +113 -0
- package/workflows/setup/init-product-dna.md +83 -0
- package/workflows/setup/init-questions.md +166 -30
- package/workflows/setup/init-validation.md +22 -0
- package/workflows/setup/init.md +144 -39
- package/workflows/setup/new-campaign.md +48 -12
- package/workflows/site/deploy.md +98 -0
- package/workflows/site/landing.md +156 -0
- package/workflows/site/pseo.md +96 -0
- package/workflows/site/quality-gates.md +88 -0
- package/workflows/utility/archive.md +45 -9
- package/workflows/utility/health.md +77 -3
- package/workflows/utility/improve-skill.md +233 -0
- package/workflows/utility/next.md +38 -2
- package/workflows/utility/playwright-setup.md +128 -0
- package/workflows/utility/request-skill.md +218 -0
- package/workflows/utility/resume.md +40 -3
- package/workflows/utility/state.md +42 -7
|
@@ -1,27 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "taketomarket",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"version": "2.2.0",
|
|
3
|
+
"description": "Marketing OS for developerneurs + solopreneurs — engineers shipping products with zero marketing experience. Spec-driven campaigns with positioning-invariant quality gates.",
|
|
4
|
+
"version": "2.3.1",
|
|
6
5
|
"author": {
|
|
7
6
|
"name": "Rishikesh Ranjan",
|
|
8
7
|
"url": "https://github.com/ranjanrishikesh"
|
|
9
8
|
},
|
|
10
9
|
"homepage": "https://www.npmjs.com/package/taketomarket",
|
|
11
|
-
"repository": "https://github.com/ranjanrishikesh/
|
|
10
|
+
"repository": "https://github.com/ranjanrishikesh/taketomarket",
|
|
12
11
|
"license": "MIT",
|
|
13
|
-
"keywords": [
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"ttm-competitor-scan", "ttm-aeo-check", "ttm-seo-audit",
|
|
24
|
-
"ttm-email-preflight", "ttm-keyword-map", "ttm-affiliate-kit",
|
|
25
|
-
"ttm-repurpose", "ttm-update"
|
|
12
|
+
"keywords": [
|
|
13
|
+
"marketing",
|
|
14
|
+
"campaigns",
|
|
15
|
+
"positioning",
|
|
16
|
+
"quality-gates",
|
|
17
|
+
"agent-skills",
|
|
18
|
+
"developerneurs",
|
|
19
|
+
"solopreneurs",
|
|
20
|
+
"gtm",
|
|
21
|
+
"indie-hackers"
|
|
26
22
|
]
|
|
27
23
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "taketomarket",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Marketing
|
|
3
|
+
"version": "2.3.1",
|
|
4
|
+
"description": "Marketing OS for developerneurs and solopreneurs. Built for engineers shipping products with zero marketing experience required. Spec-driven campaigns with positioning-as-invariant enforcement and quality gate walls.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "takeToMarket"
|
|
7
7
|
},
|
package/README.md
CHANGED
|
@@ -1,11 +1,28 @@
|
|
|
1
|
-
#
|
|
1
|
+
# taketomarket
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/taketomarket)
|
|
4
|
+
[](https://github.com/ranjanrishikesh/taketomarket)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
**Marketing OS for developerneurs and solopreneurs.** Built for engineers shipping products with zero marketing experience required.
|
|
7
|
+
|
|
8
|
+
takeToMarket is a Claude Code / Codex skill set that brings spec-driven development to marketing. Every campaign, asset, and channel is a spec-driven unit with a verifiable outcome metric and a positioning-invariant quality gate wall.
|
|
6
9
|
|
|
7
10
|
**Core invariant:** Every marketing asset ships with a verifiable outcome metric and passes through a positioning-invariant quality gate wall — no asset ships without both, ever.
|
|
8
11
|
|
|
12
|
+
## Who this is for
|
|
13
|
+
|
|
14
|
+
- **Developerneurs** — engineers building and shipping their own products.
|
|
15
|
+
- **Solopreneurs with engineering backgrounds** — founders who code their MVP themselves.
|
|
16
|
+
- **Indie hackers** — anyone shipping a product who has zero or near-zero marketing/growth experience.
|
|
17
|
+
|
|
18
|
+
If you can write code but have never built a landing page, written a positioning statement, or thought about ICPs — takeToMarket gives you the operating system. The AI does the marketing work; you stay in control of the decisions.
|
|
19
|
+
|
|
20
|
+
## Who this is NOT for
|
|
21
|
+
|
|
22
|
+
- Full-time marketers who already have a stack — takeToMarket overlaps with what you already do.
|
|
23
|
+
- Agencies serving multiple clients — built for one product per workspace.
|
|
24
|
+
- Anyone wanting a one-click blog generator — takeToMarket is opinionated, slower, and quality-gated by design.
|
|
25
|
+
|
|
9
26
|
## What it is / What it isn't
|
|
10
27
|
|
|
11
28
|
**IS:** A marketing OS that treats every campaign, asset, and channel as a spec-driven unit with a verifiable outcome, a positioning invariant, and a quality gate wall. Persistent state. Compound learnings. Nine-phase lifecycle enforcement.
|
|
@@ -39,12 +56,12 @@ Flags:
|
|
|
39
56
|
/plugin install taketomarket@claude-plugins-official
|
|
40
57
|
```
|
|
41
58
|
|
|
42
|
-
> Status: pending marketplace approval. Check https://github.com/ranjanrishikesh/
|
|
59
|
+
> Status: pending marketplace approval. Check https://github.com/ranjanrishikesh/taketomarket for current status.
|
|
43
60
|
|
|
44
61
|
### Option 3 — Direct from GitHub (Claude Code)
|
|
45
62
|
|
|
46
63
|
```
|
|
47
|
-
/plugin marketplace add ranjanrishikesh/
|
|
64
|
+
/plugin marketplace add ranjanrishikesh/taketomarket
|
|
48
65
|
/plugin install taketomarket
|
|
49
66
|
```
|
|
50
67
|
|
|
@@ -53,7 +70,7 @@ This uses the Claude Code plugin system to install directly from the GitHub repo
|
|
|
53
70
|
### Option 4 — Manual (advanced)
|
|
54
71
|
|
|
55
72
|
```bash
|
|
56
|
-
git clone https://github.com/ranjanrishikesh/
|
|
73
|
+
git clone https://github.com/ranjanrishikesh/taketomarket
|
|
57
74
|
cd takeToMarket
|
|
58
75
|
node install.js
|
|
59
76
|
```
|
|
@@ -98,34 +115,40 @@ All non-Claude runtimes also support `~/.agents/skills/` as a universal path. Se
|
|
|
98
115
|
|
|
99
116
|
| Command | Description |
|
|
100
117
|
|---------|-------------|
|
|
101
|
-
| `/ttm-aeo-check` | Check citation status across AI engines for a query |
|
|
102
118
|
| `/ttm-affiliate-kit` | Generate creative kit for affiliate partners |
|
|
103
119
|
| `/ttm-archive` | Archive a completed campaign, finalize state, and update LEARNINGS.md |
|
|
104
120
|
| `/ttm-brand-refresh` | Update BRAND.md with new proof points and deprecate expired ones |
|
|
105
121
|
| `/ttm-brief` | Generate a campaign brief with mandatory outcome metrics, positioning anchor, and channel mix |
|
|
106
122
|
| `/ttm-competitor-scan` | On-demand competitor analysis that updates COMPETITORS.md |
|
|
107
|
-
| `/ttm-
|
|
123
|
+
| `/ttm-deploy` | Vercel deploy with auto-detected best path (git-push, Vercel CLI, API token) |
|
|
124
|
+
| `/ttm-discover` | Discover phase: SERP analysis, competitor content audit, ambient narrative mapping (renamed from `/ttm-research`) |
|
|
125
|
+
| `/ttm-email-check` | Deliverability, dark-mode, and spam-trigger check for email assets (renamed from `/ttm-email-preflight`) |
|
|
108
126
|
| `/ttm-fix` | Fix phase: root cause analysis, fix brief, re-produce, re-verify (capped 3×) |
|
|
109
|
-
| `/ttm-health` | Validate .
|
|
127
|
+
| `/ttm-health` | Validate .taketomarket/ directory integrity, reference file completeness, and state consistency |
|
|
128
|
+
| `/ttm-humanize` [MANDATORY] | Mandatory final-step humanizer for every audience-facing asset. Runnable ad-hoc. |
|
|
110
129
|
| `/ttm-icp-refresh` | Update ICP.md from new customer data including calls, reviews, and feedback |
|
|
111
|
-
| `/ttm-init` | Interview-driven onboarding that generates all .
|
|
112
|
-
| `/ttm-
|
|
130
|
+
| `/ttm-init` | Interview-driven onboarding that generates all .taketomarket/ reference files |
|
|
131
|
+
| `/ttm-landing` | Next.js 15 + Tailwind v4 + React 19 marketing site scaffold with brand-token integration |
|
|
113
132
|
| `/ttm-learn` | Extract lessons from campaign data, propose reference file edits, log to LEARNINGS.md |
|
|
133
|
+
| `/ttm-linkedin-post` | Manual LinkedIn post generator. Author-mimic style + post-history tracking + last-7-day news WebSearch. Final draft passes through `/ttm-humanize` |
|
|
114
134
|
| `/ttm-measure` | Analyze campaign analytics against outcome metrics using attribution models |
|
|
115
135
|
| `/ttm-new-campaign` | Create a new campaign directory with initialized state and reference file links |
|
|
116
136
|
| `/ttm-next` | Guide user to the right next command based on current campaign state |
|
|
137
|
+
| `/ttm-playwright-setup` | Install walkthrough for Playwright MCP server + Chrome extension bridge. Run once per machine to unlock author scraping, competitor render, and Lighthouse/visual gates |
|
|
117
138
|
| `/ttm-positioning-check` | Sample recent assets and report positioning drift percentage and analysis |
|
|
118
139
|
| `/ttm-positioning-shift` | Controlled positioning change with reasoning, migration plan, and approval gate |
|
|
119
140
|
| `/ttm-produce` | Generate content assets in fresh contexts loaded with brief, positioning, brand, ICP, and playbook |
|
|
141
|
+
| `/ttm-pseo` | Programmatic SEO route generator for blog, use-case, comparison, alternative templates. JSON CMS input. AEO + SEO optimized |
|
|
120
142
|
| `/ttm-repurpose` | Fan out a long-form asset into derivatives across channels with full brief-produce-verify per derivative |
|
|
121
|
-
| `/ttm-research` | Market and audience research including SERP, competitor content, and narrative mapping |
|
|
122
143
|
| `/ttm-resume` | Resume a paused campaign at its last completed phase |
|
|
123
144
|
| `/ttm-review` | Present assets with structured review checklist for human evaluation |
|
|
124
|
-
| `/ttm-seo-
|
|
145
|
+
| `/ttm-seo audit\|keyword-map\|aeo` | Unified SEO + AEO toolkit with three subcommands (merged from `/ttm-aeo-check`, `/ttm-keyword-map`, `/ttm-seo-audit`) |
|
|
125
146
|
| `/ttm-ship` | Generate launch checklist confirming tracking, UTMs, funnel testing, and asset finalization |
|
|
126
147
|
| `/ttm-state` | Display current campaign states, decisions in flight, blockers, and experiments |
|
|
127
148
|
| `/ttm-verify` | Run all applicable quality gates on every asset with pass/fail report and line-level feedback |
|
|
128
149
|
|
|
150
|
+
**[MANDATORY]** `/ttm-humanize` runs automatically as the final step of every producing skill (`/ttm-produce`, `/ttm-repurpose`, `/ttm-affiliate-kit`). It can also be invoked ad-hoc to humanize existing copy.
|
|
151
|
+
|
|
129
152
|
## Verify Installation
|
|
130
153
|
|
|
131
154
|
Inside Claude Code, run:
|
package/bin/lib/campaign.cjs
CHANGED
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
* Exports: cmdCampaignInit, cmdCampaignState, cmdCampaignUpdate, cmdCampaignList, cmdCampaignArchive
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
// NOTE: `phase: researched` is a state-machine identifier kept for backward
|
|
12
|
+
// compatibility with existing STATE.md files. User-facing command names use
|
|
13
|
+
// /ttm-discover (the v2.3.0 rename). Full literal rename is scheduled for v2.4.0.
|
|
14
|
+
|
|
11
15
|
'use strict';
|
|
12
16
|
|
|
13
17
|
const fs = require('fs');
|
|
@@ -32,7 +36,7 @@ function resolveCampaignStatePath(slug) {
|
|
|
32
36
|
// Re-sanitize slug (defense in depth -- caller may pass unsanitized input)
|
|
33
37
|
const safe = slug.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
|
34
38
|
if (!safe) error('campaign slug must contain at least one alphanumeric character after sanitization');
|
|
35
|
-
const statePath = path.resolve(process.cwd(), '.
|
|
39
|
+
const statePath = path.resolve(process.cwd(), '.taketomarket', 'CAMPAIGNS', safe, 'STATE.md');
|
|
36
40
|
const projectRoot = path.resolve(process.cwd());
|
|
37
41
|
if (!statePath.startsWith(projectRoot + path.sep)) {
|
|
38
42
|
error('campaign STATE.md path escapes project directory');
|
|
@@ -55,7 +59,7 @@ function cmdCampaignInit(slug, name, raw) {
|
|
|
55
59
|
const safe = slug.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
|
56
60
|
if (!safe) error('campaign slug must contain at least one alphanumeric character after sanitization');
|
|
57
61
|
|
|
58
|
-
const campaignDir = path.resolve(process.cwd(), '.
|
|
62
|
+
const campaignDir = path.resolve(process.cwd(), '.taketomarket', 'CAMPAIGNS', safe);
|
|
59
63
|
const assetsDir = path.resolve(campaignDir, 'ASSETS');
|
|
60
64
|
const statePath = path.resolve(campaignDir, 'STATE.md');
|
|
61
65
|
|
|
@@ -112,7 +116,7 @@ function cmdCampaignInit(slug, name, raw) {
|
|
|
112
116
|
'ship.shipped_at': 'null',
|
|
113
117
|
'ship.checklist_result': 'null',
|
|
114
118
|
};
|
|
115
|
-
const bodyContent = `\n# Campaign: ${name}\n\nPhase: created\nNext step: Run \`/ttm-
|
|
119
|
+
const bodyContent = `\n# Campaign: ${name}\n\nPhase: created\nNext step: Run \`/ttm-discover ${safe}\` to gather market intelligence.\n`;
|
|
116
120
|
const stateContent = serializeFrontmatter(frontmatterObj, bodyContent);
|
|
117
121
|
|
|
118
122
|
// TOCTOU-safe creation: wx flag fails atomically if file already exists
|
|
@@ -268,7 +272,7 @@ function cmdCampaignList(filter, sinceArg, raw) {
|
|
|
268
272
|
error('--active/--shipped-since-last-audit and --since are mutually exclusive');
|
|
269
273
|
}
|
|
270
274
|
|
|
271
|
-
const campaignsDir = path.resolve(process.cwd(), '.
|
|
275
|
+
const campaignsDir = path.resolve(process.cwd(), '.taketomarket', 'CAMPAIGNS');
|
|
272
276
|
|
|
273
277
|
// If no campaigns directory, return empty
|
|
274
278
|
let entries;
|
|
@@ -303,7 +307,7 @@ function cmdCampaignList(filter, sinceArg, raw) {
|
|
|
303
307
|
const shippedCampaigns = campaigns.filter(c => c['phase.shipped'] && c['phase.shipped'] !== 'null');
|
|
304
308
|
let lastAuditTimestamp = null;
|
|
305
309
|
|
|
306
|
-
const driftLogPath = path.resolve(process.cwd(), '.
|
|
310
|
+
const driftLogPath = path.resolve(process.cwd(), '.taketomarket', 'DRIFT-LOG.md');
|
|
307
311
|
const driftLogContent = safeReadFile(driftLogPath);
|
|
308
312
|
if (driftLogContent) {
|
|
309
313
|
// Find last audit event timestamp in the Audit Trail table
|
|
@@ -366,8 +370,8 @@ function cmdCampaignArchive(slug, raw) {
|
|
|
366
370
|
const safe = slug.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
|
367
371
|
if (!safe) error('campaign slug must contain at least one alphanumeric character after sanitization');
|
|
368
372
|
const projectRoot = path.resolve(process.cwd());
|
|
369
|
-
const srcDir = path.resolve(projectRoot, '.
|
|
370
|
-
const destDir = path.resolve(projectRoot, '.
|
|
373
|
+
const srcDir = path.resolve(projectRoot, '.taketomarket', 'CAMPAIGNS', safe);
|
|
374
|
+
const destDir = path.resolve(projectRoot, '.taketomarket', 'CAMPAIGNS', 'ARCHIVE', safe);
|
|
371
375
|
|
|
372
376
|
// Security check: both paths must be inside project root
|
|
373
377
|
if (!srcDir.startsWith(projectRoot + path.sep)) {
|
|
@@ -468,7 +472,7 @@ function cmdRepurposeManifest(slug, sourceAssetId, derivatives, raw) {
|
|
|
468
472
|
const safeSlug = slug.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
|
469
473
|
if (!safeSlug) error('campaign slug must contain at least one alphanumeric character after sanitization');
|
|
470
474
|
const projectRoot = path.resolve(process.cwd());
|
|
471
|
-
const manifestPath = path.resolve(projectRoot, '.
|
|
475
|
+
const manifestPath = path.resolve(projectRoot, '.taketomarket', 'CAMPAIGNS', safeSlug, 'MANIFEST.json');
|
|
472
476
|
|
|
473
477
|
// Security: path must stay within project root (T-10-12)
|
|
474
478
|
if (!manifestPath.startsWith(projectRoot + path.sep)) {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function fileExists(p) {
|
|
7
|
+
try { fs.accessSync(p); return true; } catch { return false; }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function detectStack(cwd) {
|
|
11
|
+
const stack = new Set();
|
|
12
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
13
|
+
if (fileExists(pkgPath)) {
|
|
14
|
+
stack.add('node');
|
|
15
|
+
try {
|
|
16
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
17
|
+
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
|
|
18
|
+
for (const name of Object.keys(deps)) {
|
|
19
|
+
if (['react', 'next', 'astro', 'svelte', 'vue', 'angular', 'remix', 'nuxt'].includes(name)) {
|
|
20
|
+
stack.add(name);
|
|
21
|
+
}
|
|
22
|
+
if (name === 'typescript') stack.add('typescript');
|
|
23
|
+
if (name === 'tailwindcss' || name === '@tailwindcss/postcss') stack.add('tailwind');
|
|
24
|
+
if (name === 'express' || name === 'fastify' || name === 'koa') stack.add(name);
|
|
25
|
+
}
|
|
26
|
+
} catch {}
|
|
27
|
+
}
|
|
28
|
+
if (fileExists(path.join(cwd, 'pyproject.toml')) || fileExists(path.join(cwd, 'requirements.txt'))) {
|
|
29
|
+
stack.add('python');
|
|
30
|
+
}
|
|
31
|
+
if (fileExists(path.join(cwd, 'go.mod'))) stack.add('go');
|
|
32
|
+
if (fileExists(path.join(cwd, 'Cargo.toml'))) stack.add('rust');
|
|
33
|
+
if (fileExists(path.join(cwd, 'Gemfile'))) stack.add('ruby');
|
|
34
|
+
if (fileExists(path.join(cwd, 'composer.json'))) stack.add('php');
|
|
35
|
+
return Array.from(stack);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function detectMonorepo(cwd) {
|
|
39
|
+
if (fileExists(path.join(cwd, 'pnpm-workspace.yaml'))) return true;
|
|
40
|
+
if (fileExists(path.join(cwd, 'turbo.json'))) return true;
|
|
41
|
+
if (fileExists(path.join(cwd, 'nx.json'))) return true;
|
|
42
|
+
if (fileExists(path.join(cwd, 'lerna.json'))) return true;
|
|
43
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
44
|
+
if (fileExists(pkgPath)) {
|
|
45
|
+
try {
|
|
46
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
47
|
+
if (pkg.workspaces) return true;
|
|
48
|
+
} catch {}
|
|
49
|
+
}
|
|
50
|
+
const cargoPath = path.join(cwd, 'Cargo.toml');
|
|
51
|
+
if (fileExists(cargoPath)) {
|
|
52
|
+
try {
|
|
53
|
+
const cargo = fs.readFileSync(cargoPath, 'utf8');
|
|
54
|
+
if (/^\s*\[workspace\]/m.test(cargo)) return true;
|
|
55
|
+
} catch {}
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function detectFeatureCandidates(cwd) {
|
|
61
|
+
const candidates = [];
|
|
62
|
+
for (const base of ['src', 'app', 'lib', 'packages', 'apps', 'modules']) {
|
|
63
|
+
const p = path.join(cwd, base);
|
|
64
|
+
if (!fileExists(p)) continue;
|
|
65
|
+
try {
|
|
66
|
+
const entries = fs.readdirSync(p, { withFileTypes: true });
|
|
67
|
+
for (const e of entries) {
|
|
68
|
+
if (e.isDirectory() && !e.name.startsWith('.') && !e.name.startsWith('_')) {
|
|
69
|
+
candidates.push(e.name);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} catch {}
|
|
73
|
+
}
|
|
74
|
+
return Array.from(new Set(candidates));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function scanCodebase(cwd) {
|
|
78
|
+
return {
|
|
79
|
+
cwd,
|
|
80
|
+
stack: detectStack(cwd),
|
|
81
|
+
monorepo: detectMonorepo(cwd),
|
|
82
|
+
featureCandidates: detectFeatureCandidates(cwd),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = { scanCodebase, detectStack, detectMonorepo, detectFeatureCandidates };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
// Relative path fragment. Always prepended with `cwd` in readConfig/writeConfig.
|
|
7
|
+
const CONFIG_RELATIVE_PATH = path.join('.taketomarket', 'CONFIG.md');
|
|
8
|
+
|
|
9
|
+
const DEFAULTS = {
|
|
10
|
+
yolo: false,
|
|
11
|
+
inline_education: true,
|
|
12
|
+
landing_path: null,
|
|
13
|
+
brand_path: '.taketomarket/brand',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function parseYamlFrontmatter(text) {
|
|
17
|
+
if (!text.startsWith('---\n')) return {};
|
|
18
|
+
const end = text.indexOf('\n---', 4);
|
|
19
|
+
if (end < 0) return {};
|
|
20
|
+
const yaml = text.slice(4, end);
|
|
21
|
+
const out = {};
|
|
22
|
+
const lines = yaml.split('\n');
|
|
23
|
+
let i = 0;
|
|
24
|
+
while (i < lines.length) {
|
|
25
|
+
const line = lines[i];
|
|
26
|
+
// Match scalar key: `key: value`
|
|
27
|
+
const scalar = line.match(/^([a-z_][a-z0-9_]*):\s*(.*)$/);
|
|
28
|
+
if (!scalar) { i++; continue; }
|
|
29
|
+
const key = scalar[1];
|
|
30
|
+
let val = scalar[2].trim();
|
|
31
|
+
if (val === '') {
|
|
32
|
+
// Possible nested object: gather indented `key.subkey: value` form on following lines
|
|
33
|
+
// We support a single-level nested object whose keys are listed as ` subkey: value`.
|
|
34
|
+
const obj = {};
|
|
35
|
+
let j = i + 1;
|
|
36
|
+
let foundChild = false;
|
|
37
|
+
while (j < lines.length) {
|
|
38
|
+
const child = lines[j].match(/^[ \t]+([a-zA-Z0-9_\-]+):\s*(.*)$/);
|
|
39
|
+
if (!child) break;
|
|
40
|
+
foundChild = true;
|
|
41
|
+
let cval = child[2].trim();
|
|
42
|
+
if (cval === 'true') cval = true;
|
|
43
|
+
else if (cval === 'false') cval = false;
|
|
44
|
+
else if (cval === 'null' || cval === '~' || cval === '') cval = null;
|
|
45
|
+
else if (/^-?\d+$/.test(cval)) cval = parseInt(cval, 10);
|
|
46
|
+
obj[child[1]] = cval;
|
|
47
|
+
j++;
|
|
48
|
+
}
|
|
49
|
+
if (foundChild) {
|
|
50
|
+
out[key] = obj;
|
|
51
|
+
i = j;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
out[key] = null;
|
|
55
|
+
i++;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (val === 'true') val = true;
|
|
59
|
+
else if (val === 'false') val = false;
|
|
60
|
+
else if (val === 'null' || val === '~') val = null;
|
|
61
|
+
else if (/^-?\d+$/.test(val)) val = parseInt(val, 10);
|
|
62
|
+
out[key] = val;
|
|
63
|
+
i++;
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function serializeYamlFrontmatter(obj) {
|
|
69
|
+
const lines = ['---'];
|
|
70
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
71
|
+
if (v === null) lines.push(`${k}: null`);
|
|
72
|
+
else if (typeof v === 'boolean') lines.push(`${k}: ${v}`);
|
|
73
|
+
else if (v && typeof v === 'object' && !Array.isArray(v)) {
|
|
74
|
+
const keys = Object.keys(v);
|
|
75
|
+
if (keys.length === 0) {
|
|
76
|
+
lines.push(`${k}: {}`);
|
|
77
|
+
} else {
|
|
78
|
+
lines.push(`${k}:`);
|
|
79
|
+
for (const sk of keys) {
|
|
80
|
+
const sv = v[sk];
|
|
81
|
+
if (sv === null) lines.push(` ${sk}: null`);
|
|
82
|
+
else if (typeof sv === 'boolean') lines.push(` ${sk}: ${sv}`);
|
|
83
|
+
else lines.push(` ${sk}: ${sv}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else lines.push(`${k}: ${v}`);
|
|
88
|
+
}
|
|
89
|
+
lines.push('---');
|
|
90
|
+
return lines.join('\n');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function readConfig(cwd) {
|
|
94
|
+
const p = path.join(cwd, CONFIG_RELATIVE_PATH);
|
|
95
|
+
if (!fs.existsSync(p)) return { ...DEFAULTS };
|
|
96
|
+
const body = fs.readFileSync(p, 'utf8');
|
|
97
|
+
return { ...DEFAULTS, ...parseYamlFrontmatter(body) };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function writeConfig(cwd, cfg) {
|
|
101
|
+
const dir = path.join(cwd, '.taketomarket');
|
|
102
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
103
|
+
const merged = { ...DEFAULTS, ...cfg };
|
|
104
|
+
const text = serializeYamlFrontmatter(merged) + '\n\n# takeToMarket Config\n\nManaged by `/ttm-config`. Do not edit the frontmatter manually unless you know what you are doing.\n';
|
|
105
|
+
fs.writeFileSync(path.join(cwd, CONFIG_RELATIVE_PATH), text);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function setConfig(cwd, key, value) {
|
|
109
|
+
const cfg = readConfig(cwd);
|
|
110
|
+
cfg[key] = value;
|
|
111
|
+
writeConfig(cwd, cfg);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function markFirstRunSeen(cwd, skillName) {
|
|
115
|
+
const cfg = readConfig(cwd);
|
|
116
|
+
const seen = (cfg.first_run_seen && typeof cfg.first_run_seen === 'object')
|
|
117
|
+
? { ...cfg.first_run_seen }
|
|
118
|
+
: {};
|
|
119
|
+
seen[skillName] = true;
|
|
120
|
+
cfg.first_run_seen = seen;
|
|
121
|
+
writeConfig(cwd, cfg);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isFirstRunSeen(cwd, skillName) {
|
|
125
|
+
const cfg = readConfig(cwd);
|
|
126
|
+
return !!(cfg.first_run_seen && typeof cfg.first_run_seen === 'object' && cfg.first_run_seen[skillName] === true);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = { readConfig, writeConfig, setConfig, markFirstRunSeen, isFirstRunSeen, DEFAULTS };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { execSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
function commandExists(cmd) {
|
|
8
|
+
try {
|
|
9
|
+
execSync(`command -v ${cmd}`, { stdio: 'ignore' });
|
|
10
|
+
return true;
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function detectDeployPath(cwd, opts = {}) {
|
|
17
|
+
const env = opts.env !== undefined ? opts.env : process.env;
|
|
18
|
+
const hasCli = opts.hasCli !== undefined ? opts.hasCli : commandExists('vercel');
|
|
19
|
+
const available = [];
|
|
20
|
+
|
|
21
|
+
const vercelProjectJson = path.join(cwd, '.vercel', 'project.json');
|
|
22
|
+
const gitConnected = fs.existsSync(vercelProjectJson);
|
|
23
|
+
|
|
24
|
+
if (gitConnected) available.push('git-push');
|
|
25
|
+
if (hasCli) available.push('cli');
|
|
26
|
+
if (env.VERCEL_TOKEN) available.push('api-token');
|
|
27
|
+
|
|
28
|
+
let preferred = null;
|
|
29
|
+
if (available.includes('git-push')) preferred = 'git-push';
|
|
30
|
+
else if (available.includes('cli')) preferred = 'cli';
|
|
31
|
+
else if (available.includes('api-token')) preferred = 'api-token';
|
|
32
|
+
|
|
33
|
+
return { available, preferred, gitConnected, hasCli, hasToken: !!env.VERCEL_TOKEN };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = { detectDeployPath };
|
package/bin/lib/deviation.cjs
CHANGED
|
@@ -67,7 +67,7 @@ function cmdDeviationAppend(slug, gate, result, justification, asset, raw, extra
|
|
|
67
67
|
// Validate slug via path.resolve to prevent traversal
|
|
68
68
|
const safe = slug.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
|
69
69
|
const projectRoot = path.resolve(process.cwd());
|
|
70
|
-
const campaignDir = path.resolve(projectRoot, '.
|
|
70
|
+
const campaignDir = path.resolve(projectRoot, '.taketomarket', 'CAMPAIGNS', safe);
|
|
71
71
|
if (!campaignDir.startsWith(projectRoot)) {
|
|
72
72
|
error('campaign path escapes project directory');
|
|
73
73
|
}
|
package/bin/lib/drift-log.cjs
CHANGED
|
@@ -46,7 +46,7 @@ function sanitizeDetails(text) {
|
|
|
46
46
|
*/
|
|
47
47
|
function resolveDriftLogPath() {
|
|
48
48
|
const projectRoot = path.resolve(process.cwd());
|
|
49
|
-
const driftLogPath = path.resolve(process.cwd(), '.
|
|
49
|
+
const driftLogPath = path.resolve(process.cwd(), '.taketomarket', 'DRIFT-LOG.md');
|
|
50
50
|
if (!driftLogPath.startsWith(projectRoot)) {
|
|
51
51
|
error('DRIFT-LOG.md path escapes project directory');
|
|
52
52
|
}
|
|
@@ -83,7 +83,7 @@ function ensureDriftLog(driftLogPath) {
|
|
|
83
83
|
'<!-- DEPRECATION ENTRIES BELOW THIS LINE -->',
|
|
84
84
|
].join('\n');
|
|
85
85
|
|
|
86
|
-
// Ensure .
|
|
86
|
+
// Ensure .taketomarket directory exists
|
|
87
87
|
const dir = path.dirname(driftLogPath);
|
|
88
88
|
fs.mkdirSync(dir, { recursive: true });
|
|
89
89
|
|
|
@@ -96,7 +96,7 @@ function ensureDriftLog(driftLogPath) {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
|
-
* Append a drift event entry to .
|
|
99
|
+
* Append a drift event entry to .taketomarket/DRIFT-LOG.md.
|
|
100
100
|
*
|
|
101
101
|
* @param {string} eventType - Event type (shift, audit, deviation)
|
|
102
102
|
* @param {string} source - Source command or campaign that triggered the event
|
|
@@ -157,7 +157,7 @@ function cmdDriftLogAppend(eventType, source, details, affectedCount, raw) {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
/**
|
|
160
|
-
* Append a deprecation entry to .
|
|
160
|
+
* Append a deprecation entry to .taketomarket/DRIFT-LOG.md Deprecation Backlog.
|
|
161
161
|
*
|
|
162
162
|
* @param {string} asset - Asset identifier
|
|
163
163
|
* @param {string} campaign - Campaign slug
|