@easysociable/cli 0.1.2 → 0.1.4
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 +32 -21
- package/{skills/easysociable-content → content-engine}/scripts/starter.mjs +3 -1
- package/{skills/easysociable-content → content-engine}/scripts/workbench.mjs +13 -8
- package/{skills/easysociable-content → content-engine}/starter/CLAUDE.md +217 -128
- package/{skills/easysociable-content → content-engine}/starter/engine.json +3 -0
- package/content-engine/starter/needs/README.md +38 -0
- package/content-engine/starter/needs/_index.md +31 -0
- package/content-engine/starter/needs/_template.md +24 -0
- package/content-engine/starter/needs/entries/README.md +5 -0
- package/content-engine/starter/products/README.md +38 -0
- package/content-engine/starter/products/_index.md +24 -0
- package/content-engine/starter/products/_template.md +56 -0
- package/content-engine/starter/products/entries/P-001-easysociable.md +150 -0
- package/content-engine/starter/profiles/_index.md +16 -0
- package/{skills/easysociable-content → content-engine}/starter/profiles/default/README.md +4 -1
- package/content-engine/starter/profiles/default/boundaries.md +14 -0
- package/content-engine/starter/profiles/default/expression.md +18 -0
- package/content-engine/starter/profiles/default/lanes.md +12 -0
- package/content-engine/starter/profiles/default/stories.md +15 -0
- package/{skills/easysociable-content → content-engine}/starter/profiles/default/voice.md +14 -0
- package/content-engine/starter/recommendations/README.md +21 -0
- package/content-engine/starter/recommendations/_index.md +21 -0
- package/content-engine/starter/recommendations/_template.md +44 -0
- package/{skills/easysociable-content → content-engine}/starter/runs/_template/brief.md +1 -1
- package/{skills/easysociable-content → content-engine}/starter/runs/_template/pack.md +15 -7
- package/{skills/easysociable-content → content-engine}/starter/runs/_template/packet.md +6 -0
- package/{skills/easysociable-content → content-engine}/starter/swipe/_template.md +3 -1
- package/content-engine/starter/topics/README.md +21 -0
- package/{skills/easysociable-content → content-engine}/starter/topics/_index.md +12 -5
- package/content-engine/starter/topics/_template-item.md +103 -0
- package/{skills/easysociable-content → content-engine}/starter/topics/scoring.md +55 -4
- package/content-engine/workbench/__pycache__/build.cpython-310.pyc +0 -0
- package/{skills/easysociable-content → content-engine}/workbench/build.py +306 -97
- package/{skills/easysociable-content → content-engine}/workbench/serve.py +0 -0
- package/{skills/easysociable-content → content-engine}/workbench/template.html +173 -27
- package/dist/easysociable.js +629 -142
- package/dist/easysociable.js.map +1 -1
- package/package.json +15 -15
- package/skills/ONBOARDING.md +21 -18
- package/skills/easysociable-content/SKILL.md +104 -98
- package/skills/easysociable-content/workflows/INTERVIEW.md +70 -0
- package/skills/easysociable-content/workflows/NEEDS.md +82 -0
- package/skills/easysociable-slideshow/SKILL.md +79 -44
- package/skills/easysociable-slideshow/references/ARC.md +44 -0
- package/skills/easysociable-slideshow/references/CONTENT_PACK.md +34 -6
- package/skills/easysociable-slideshow/workflows/CREATE_RUN.md +24 -5
- package/skills/easysociable-slideshow/workflows/OUT_OF_SCOPE.md +3 -0
- package/skills/easysociable-template/SKILL.md +29 -30
- package/skills/easysociable-template/references/CATALOG_METADATA.md +23 -20
- package/skills/easysociable-template/references/DESIGN_V2_1.md +53 -2
- package/skills/easysociable-template/references/GATEWAY.md +7 -2
- package/skills/easysociable-template/references/PLATFORM_SPECS.md +11 -3
- package/skills/easysociable-template/references/THEME_TOKENS.md +1 -1
- package/skills/easysociable-template/references/fixtures/image-reference.design-plan.json +74 -0
- package/skills/easysociable-template/references/fixtures/image-reference.visual-qa.json +28 -0
- package/skills/easysociable-template/scripts/generate-theme-from-hex.mjs +0 -0
- package/skills/easysociable-template/scripts/validate-design-v2.mjs +97 -0
- package/skills/easysociable-template/workflows/ASSET_PLAN.md +2 -1
- package/skills/easysociable-template/workflows/AUTHORING_FROM_BRIEF.md +4 -4
- package/skills/easysociable-template/workflows/AUTHORING_FROM_IMAGE.md +66 -16
- package/skills/easysociable-template/workflows/AUTHORING_STUDIO.md +28 -15
- package/skills/easysociable-template/workflows/DESIGN_BRIEF.md +4 -3
- package/skills/easysociable-template/workflows/FILL_AND_RENDER.md +4 -83
- package/skills/easysociable-template/workflows/MULTI_PLATFORM_FAMILY.md +81 -0
- package/skills/easysociable-template/workflows/OUT_OF_SCOPE.md +3 -2
- package/skills/easysociable-template/workflows/PUBLISH_FROM_DRAFT.md +1 -1
- package/skills/easysociable-template/workflows/VISUAL_QA.md +10 -0
- package/skills/easysociable-content/INSTALL.md +0 -60
- package/skills/easysociable-content/platforms/claude.md +0 -4
- package/skills/easysociable-content/platforms/codex.md +0 -4
- package/skills/easysociable-content/platforms/hermes.md +0 -4
- package/skills/easysociable-content/platforms/openclaw.md +0 -4
- package/skills/easysociable-content/references/CLI.md +0 -40
- package/skills/easysociable-content/references/CONTRACT_SYNC.md +0 -42
- package/skills/easysociable-content/references/MCP.md +0 -15
- package/skills/easysociable-content/references/PAGE_PACK.md +0 -129
- package/skills/easysociable-content/references/PLATFORM_GUIDE.md +0 -36
- package/skills/easysociable-content/references/RUN_LAYOUT.md +0 -73
- package/skills/easysociable-content/references/TROUBLESHOOTING.md +0 -13
- package/skills/easysociable-content/references/VAULT.md +0 -40
- package/skills/easysociable-content/references/VOICE.md +0 -16
- package/skills/easysociable-content/scripts/package-skill.mjs +0 -49
- package/skills/easysociable-content/scripts/setup.mjs +0 -200
- package/skills/easysociable-content/scripts/setup.test.mjs +0 -82
- package/skills/easysociable-content/starter/discovery/README.md +0 -18
- package/skills/easysociable-content/starter/discovery/_index.md +0 -27
- package/skills/easysociable-content/starter/discovery/_template.md +0 -41
- package/skills/easysociable-content/starter/profiles/_index.md +0 -13
- package/skills/easysociable-content/starter/topics/README.md +0 -25
- package/skills/easysociable-content/starter/topics/WATCHLIST.md +0 -65
- package/skills/easysociable-content/starter/topics/_template-item.md +0 -90
- package/skills/easysociable-content/starter/topics/watchlists/README.md +0 -10
- package/skills/easysociable-content/starter/topics/watchlists/_shared.md +0 -14
- package/skills/easysociable-content/starter/topics/watchlists/default.md +0 -6
- package/skills/easysociable-content/workflows/ANALYSIS_AND_COVERAGE.md +0 -17
- package/skills/easysociable-content/workflows/CONTENT_EXPORT.md +0 -24
- package/skills/easysociable-content/workflows/GETTING_STARTED.md +0 -40
- package/skills/easysociable-content/workflows/MCP_FIRST.md +0 -5
- package/skills/easysociable-content/workflows/OUT_OF_SCOPE.md +0 -18
- package/skills/easysociable-content/workflows/PACK_AUTHORING.md +0 -42
- package/skills/easysociable-content/workflows/SWIPE_CAPTURE.md +0 -18
- package/skills/easysociable-content/workflows/TOPIC_DRAFT.md +0 -29
- package/skills/easysociable-content/workflows/WORKING_DRAFT.md +0 -19
- /package/{skills/easysociable-content → content-engine}/fixtures/golden-run-en/draft.md +0 -0
- /package/{skills/easysociable-content → content-engine}/fixtures/golden-run-en/idea.md +0 -0
- /package/{skills/easysociable-content → content-engine}/fixtures/golden-run-en/pack-tiktok.json +0 -0
- /package/{skills/easysociable-content → content-engine}/scripts/contracts.mjs +0 -0
- /package/{skills/easysociable-content → content-engine}/scripts/export-contracts.mjs +0 -0
- /package/{skills/easysociable-content → content-engine}/scripts/fixture.test.mjs +0 -0
- /package/{skills/easysociable-content → content-engine}/scripts/validate-export.mjs +0 -0
- /package/{skills/easysociable-content → content-engine}/scripts/validate-pack.mjs +0 -0
- /package/{skills/easysociable-content → content-engine}/scripts/validate-run.mjs +0 -0
- /package/{skills/easysociable-content → content-engine}/scripts/validate.test.mjs +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/accounts/README.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/accounts/_index.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/accounts/overrides/README.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/exports/README.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/inbox/README.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/inbox/_index.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/inbox/entries/README.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/platforms/README.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/platforms/ig.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/platforms/instagram.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/platforms/linkedin.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/platforms/threads.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/platforms/tiktok.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/platforms/x.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/platforms/xiaohongshu.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/profiles/default/audience.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/profiles/default/pillars.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/published/_index.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/raw/README.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/raw/_index.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/runs/README.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/runs/_index.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/runs/_template/draft.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/runs/_template/editor.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/runs/_template/feedback.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/runs/_template/idea.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/runs/_template/rubric.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/swipe/_index.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/topics/items/README.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/wiki/README.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/wiki/atoms/_index.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/wiki/atoms/_template.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/wiki/claims/_index.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/wiki/claims/_template.md +0 -0
- /package/{skills/easysociable-content → content-engine}/starter/workbench/README.md +0 -0
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# Engine layout
|
|
2
|
-
|
|
3
|
-
The skill creates one English root:
|
|
4
|
-
|
|
5
|
-
```text
|
|
6
|
-
<parent>/Content Engine/
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
`--root` is the **parent** directory, or the `Content Engine/` path itself.
|
|
10
|
-
|
|
11
|
-
```json
|
|
12
|
-
{
|
|
13
|
-
"schemaVersion": "content-engine.v1",
|
|
14
|
-
"locale": "en",
|
|
15
|
-
"root": "Content Engine",
|
|
16
|
-
"paths": { "runs": "runs", "exports": "exports", "workbench": "workbench" }
|
|
17
|
-
}
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Folders: `accounts`, `inbox`, `swipe`, `topics`, `discovery`, `raw`, `profiles`, `published`, `wiki`, `platforms`, `runs`, `exports`, `workbench`.
|
|
21
|
-
|
|
22
|
-
Platform craft files: `x.md`, `ig.md` (`instagram` → `ig.md`), `tiktok.md`, `linkedin.md`, `threads.md`, `xiaohongshu.md`.
|
|
23
|
-
|
|
24
|
-
## Workbench
|
|
25
|
-
|
|
26
|
-
Copied from the skill package **only when missing**. Never overwrite an existing `workbench/template.html`.
|
|
27
|
-
|
|
28
|
-
```text
|
|
29
|
-
http://127.0.0.1:8765/workbench/
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
Rebuild with `python3 workbench/build.py` after markdown writes.
|
|
33
|
-
|
|
34
|
-
## Export path
|
|
35
|
-
|
|
36
|
-
```text
|
|
37
|
-
Content Engine/exports/<run-slug>/01.png
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Exports are **not** nested under `runs/<slug>/`. See `references/RUN_LAYOUT.md`.
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# Local voice vs hosted Profile
|
|
2
|
-
|
|
3
|
-
| Concept | Where | When | Purpose |
|
|
4
|
-
|---|---|---|---|
|
|
5
|
-
| **Local voice** | `profiles/<id>/voice.md` | First draft (optional) | Identity + tone for the Agent |
|
|
6
|
-
| **Local profile** | `profiles/<id>/` | Multi-brand | Pillars + audience + voice |
|
|
7
|
-
| **Hosted Brand Profile** | Gateway (`easysociable profiles …`) | First render | Visual identity: Theme, logo, quotas |
|
|
8
|
-
|
|
9
|
-
## Rules
|
|
10
|
-
|
|
11
|
-
1. **Setup does not create a hosted Profile.**
|
|
12
|
-
2. **Local voice is optional.** Ask once; user may skip.
|
|
13
|
-
3. **Hosted Profile is required for render**, owned by Template/Slideshow skills.
|
|
14
|
-
4. Never store API keys, passwords or Stripe tokens in `profiles/` or `accounts/`.
|
|
15
|
-
|
|
16
|
-
When writing `draft.md` or pack slots, read the resolved voice file and audience **core + this platform**.
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { cpSync, existsSync, mkdirSync, rmSync } from 'node:fs'
|
|
4
|
-
import { join, relative, resolve, sep } from 'node:path'
|
|
5
|
-
|
|
6
|
-
const skillRoot = resolve(new URL('..', import.meta.url).pathname)
|
|
7
|
-
const include = ['SKILL.md', 'INSTALL.md', 'scripts', 'references', 'workflows', 'platforms', 'fixtures', 'starter', 'workbench']
|
|
8
|
-
|
|
9
|
-
function parseArgs(argv) {
|
|
10
|
-
let output = ''
|
|
11
|
-
let force = false
|
|
12
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
13
|
-
if (argv[i] === '--output') output = argv[++i] ?? ''
|
|
14
|
-
else if (argv[i] === '--force') force = true
|
|
15
|
-
else if (argv[i] === '--help' || argv[i] === '-h') return { help: true }
|
|
16
|
-
else throw new Error(`Unknown argument: ${argv[i]}`)
|
|
17
|
-
}
|
|
18
|
-
return { output, force }
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
const options = parseArgs(process.argv.slice(2))
|
|
23
|
-
if (options.help) {
|
|
24
|
-
process.stdout.write('Usage: node package-skill.mjs --output <destination-dir> [--force]\n')
|
|
25
|
-
process.exit(0)
|
|
26
|
-
}
|
|
27
|
-
if (!options.output) throw new Error('--output is required')
|
|
28
|
-
|
|
29
|
-
const dest = resolve(options.output)
|
|
30
|
-
const sourceToDest = relative(skillRoot, dest)
|
|
31
|
-
const destToSource = relative(dest, skillRoot)
|
|
32
|
-
const touchesSource = dest === skillRoot
|
|
33
|
-
|| (!sourceToDest.startsWith(`..${sep}`) && sourceToDest !== '..')
|
|
34
|
-
|| (!destToSource.startsWith(`..${sep}`) && destToSource !== '..')
|
|
35
|
-
if (touchesSource) throw new Error('--output must be outside the skill source directory')
|
|
36
|
-
if (existsSync(dest) && !options.force) throw new Error('Output already exists; pass --force to replace it')
|
|
37
|
-
if (existsSync(dest)) rmSync(dest, { recursive: true, force: true })
|
|
38
|
-
mkdirSync(dest, { recursive: true })
|
|
39
|
-
|
|
40
|
-
for (const name of include) {
|
|
41
|
-
cpSync(join(skillRoot, name), join(dest, name), { recursive: true })
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
process.stdout.write(`Packaged skill → ${dest}\n`)
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
console.error(error instanceof Error ? error.message : String(error))
|
|
48
|
-
process.exitCode = 1
|
|
49
|
-
}
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from 'node:fs'
|
|
4
|
-
import { homedir } from 'node:os'
|
|
5
|
-
import { basename, join, resolve } from 'node:path'
|
|
6
|
-
import { ENGINE_ROOT, filesFor } from './starter.mjs'
|
|
7
|
-
import { buildWorkbench, ensureWorkbench, python3Bin, startWorkbench } from './workbench.mjs'
|
|
8
|
-
|
|
9
|
-
function parseArgs(argv) {
|
|
10
|
-
const options = {
|
|
11
|
-
root: '', engine: '', apply: false, validate: false, preview: false,
|
|
12
|
-
detect: false, noOpen: false, json: false, workbench: false,
|
|
13
|
-
}
|
|
14
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
15
|
-
const arg = argv[i]
|
|
16
|
-
if (arg === '--root') options.root = argv[++i] ?? ''
|
|
17
|
-
else if (arg === '--engine') options.engine = argv[++i] ?? ''
|
|
18
|
-
else if (arg === '--apply') options.apply = true
|
|
19
|
-
else if (arg === '--validate') options.validate = true
|
|
20
|
-
else if (arg === '--preview') options.preview = true
|
|
21
|
-
else if (arg === '--detect') options.detect = true
|
|
22
|
-
else if (arg === '--no-open') options.noOpen = true
|
|
23
|
-
else if (arg === '--workbench') options.workbench = true
|
|
24
|
-
else if (arg === '--json') options.json = true
|
|
25
|
-
else if (arg === '--help' || arg === '-h') options.help = true
|
|
26
|
-
else throw new Error(`Unknown argument: ${arg}`)
|
|
27
|
-
}
|
|
28
|
-
return options
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function output(value, json) {
|
|
32
|
-
process.stdout.write(json ? `${JSON.stringify(value, null, 2)}\n` : `${value.message ?? value}\n`)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function isDir(path) {
|
|
36
|
-
return existsSync(path) && statSync(path).isDirectory()
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function isEngine(path) {
|
|
40
|
-
return isDir(path) && existsSync(join(path, 'CLAUDE.md')) && isDir(join(path, 'runs'))
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function resolveEngine(rootOrEngine) {
|
|
44
|
-
const absolute = resolve(rootOrEngine)
|
|
45
|
-
if (isEngine(absolute) || basename(absolute) === ENGINE_ROOT) return absolute
|
|
46
|
-
return join(absolute, ENGINE_ROOT)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function searchRoots() {
|
|
50
|
-
const extra = (process.env.EASYSOCIABLE_ENGINE_SEARCH_ROOTS || '')
|
|
51
|
-
.split(process.platform === 'win32' ? ';' : ':')
|
|
52
|
-
.map((item) => item.trim())
|
|
53
|
-
.filter(Boolean)
|
|
54
|
-
const home = homedir()
|
|
55
|
-
return [...extra, join(home, 'Documents'), home]
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function detectEngines() {
|
|
59
|
-
const found = []
|
|
60
|
-
for (const root of searchRoots()) {
|
|
61
|
-
if (!isDir(root)) continue
|
|
62
|
-
const direct = join(root, ENGINE_ROOT)
|
|
63
|
-
if (isEngine(direct) || isDir(direct)) found.push(direct)
|
|
64
|
-
try {
|
|
65
|
-
for (const name of readdirSync(root)) {
|
|
66
|
-
const child = join(root, name, ENGINE_ROOT)
|
|
67
|
-
if (isDir(child)) found.push(child)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
continue
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return [...new Set(found)]
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function inventory(engine) {
|
|
78
|
-
const files = filesFor()
|
|
79
|
-
const missing = []
|
|
80
|
-
const existing = []
|
|
81
|
-
for (const relative of Object.keys(files)) {
|
|
82
|
-
const path = join(engine, relative)
|
|
83
|
-
;(existsSync(path) ? existing : missing).push(relative)
|
|
84
|
-
}
|
|
85
|
-
return {
|
|
86
|
-
engine,
|
|
87
|
-
engineExists: isDir(engine),
|
|
88
|
-
rootName: ENGINE_ROOT,
|
|
89
|
-
missing,
|
|
90
|
-
existing,
|
|
91
|
-
starterComplete: missing.length === 0,
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function validateRoot(rootOrEngine, { strict = false } = {}) {
|
|
96
|
-
const engine = resolveEngine(rootOrEngine)
|
|
97
|
-
const parent = resolve(engine, '..')
|
|
98
|
-
const errors = []
|
|
99
|
-
if (!existsSync(parent) || !statSync(parent).isDirectory()) {
|
|
100
|
-
errors.push('Parent directory does not exist')
|
|
101
|
-
return { engine, valid: false, errors, created: [] }
|
|
102
|
-
}
|
|
103
|
-
const result = inventory(engine)
|
|
104
|
-
if (existsSync(engine) && !isDir(engine)) errors.push('Content Engine exists but is not a directory')
|
|
105
|
-
if (strict) {
|
|
106
|
-
if (!result.engineExists) errors.push('Content Engine/ folder does not exist')
|
|
107
|
-
else if (result.missing.length > 0) {
|
|
108
|
-
errors.push(`Starter incomplete: ${result.missing.length} file(s) missing (use --apply to add only missing files)`)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return { ...result, valid: errors.length === 0, errors, created: [] }
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async function applyStarter(rootOrEngine, noOpen) {
|
|
115
|
-
const engine = resolveEngine(rootOrEngine)
|
|
116
|
-
const checked = validateRoot(engine)
|
|
117
|
-
if (checked.code) return { ...checked, created: [] }
|
|
118
|
-
mkdirSync(engine, { recursive: true })
|
|
119
|
-
const report = inventory(engine)
|
|
120
|
-
const created = []
|
|
121
|
-
const files = filesFor()
|
|
122
|
-
for (const relative of report.missing) {
|
|
123
|
-
const path = join(engine, relative)
|
|
124
|
-
mkdirSync(join(path, '..'), { recursive: true })
|
|
125
|
-
writeFileSync(path, files[relative], { encoding: 'utf8', flag: 'wx' })
|
|
126
|
-
created.push(relative)
|
|
127
|
-
}
|
|
128
|
-
const workbench = ensureWorkbench(engine)
|
|
129
|
-
const python = python3Bin()
|
|
130
|
-
if (!python) {
|
|
131
|
-
return {
|
|
132
|
-
...validateRoot(engine),
|
|
133
|
-
created,
|
|
134
|
-
workbench,
|
|
135
|
-
action: 'applied',
|
|
136
|
-
valid: false,
|
|
137
|
-
code: 'PYTHON3_REQUIRED',
|
|
138
|
-
errors: ['python3 is required for the workbench. Install Python 3 and retry. Starter files were written; workbench was not started.'],
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
const built = buildWorkbench(engine)
|
|
142
|
-
let started = { skipped: true, build: built }
|
|
143
|
-
if (!noOpen && process.env.EASYSOCIABLE_NO_OPEN !== 'true') {
|
|
144
|
-
started = await startWorkbench(engine, { noOpen: false })
|
|
145
|
-
}
|
|
146
|
-
return { ...validateRoot(engine), created, workbench, workbenchBuild: built, workbenchStart: started, action: 'applied' }
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function printHelp() {
|
|
150
|
-
process.stdout.write(`Usage:
|
|
151
|
-
node setup.mjs --detect [--json]
|
|
152
|
-
node setup.mjs --root <parent-or-engine> [--preview|--apply|--validate] [--workbench] [--no-open] [--json]
|
|
153
|
-
|
|
154
|
-
English Content Engine/ only. Existing files are never overwritten.
|
|
155
|
-
`)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
const options = parseArgs(process.argv.slice(2))
|
|
160
|
-
if (options.help) {
|
|
161
|
-
printHelp()
|
|
162
|
-
process.exit(0)
|
|
163
|
-
}
|
|
164
|
-
if (options.detect) {
|
|
165
|
-
output({ action: 'detect', engines: detectEngines() }, options.json)
|
|
166
|
-
process.exit(0)
|
|
167
|
-
}
|
|
168
|
-
const target = options.engine || options.root
|
|
169
|
-
if (!target) {
|
|
170
|
-
output({ code: 'ROOT_REQUIRED', message: 'Choose a parent directory or Content Engine path with --root.', created: false }, options.json)
|
|
171
|
-
process.exitCode = 2
|
|
172
|
-
}
|
|
173
|
-
else if (options.apply) {
|
|
174
|
-
const applied = await applyStarter(target, options.noOpen)
|
|
175
|
-
output(applied, options.json)
|
|
176
|
-
if (applied.code) process.exitCode = 2
|
|
177
|
-
}
|
|
178
|
-
else if (options.workbench) {
|
|
179
|
-
const engine = resolveEngine(target)
|
|
180
|
-
if (!isEngine(engine) && !isDir(engine)) {
|
|
181
|
-
output({ code: 'ENGINE_MISSING', message: 'Content Engine/ not found. Run --apply first.', created: false }, options.json)
|
|
182
|
-
process.exitCode = 2
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
const started = await startWorkbench(engine, { noOpen: options.noOpen })
|
|
186
|
-
output({ action: 'workbench', ...started, engine }, options.json)
|
|
187
|
-
if (!started.ok) process.exitCode = 2
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
const report = validateRoot(target, { strict: options.validate })
|
|
192
|
-
const action = options.validate ? 'validated' : 'preview'
|
|
193
|
-
output({ ...report, action, created: [] }, options.json)
|
|
194
|
-
if (report.code || (options.validate && !report.valid)) process.exitCode = 2
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
catch (error) {
|
|
198
|
-
output({ code: 'CONTENT_SETUP_FAILED', message: error instanceof Error ? error.message : String(error), created: false }, true)
|
|
199
|
-
process.exitCode = 1
|
|
200
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
3
|
-
import { tmpdir } from 'node:os'
|
|
4
|
-
import { join } from 'node:path'
|
|
5
|
-
import { spawnSync } from 'node:child_process'
|
|
6
|
-
|
|
7
|
-
const script = new URL('./setup.mjs', import.meta.url).pathname
|
|
8
|
-
const fixture = mkdtempSync(join(tmpdir(), 'easysociable-content-'))
|
|
9
|
-
const root = join(fixture, 'Workspace')
|
|
10
|
-
mkdirSync(root)
|
|
11
|
-
const env = { ...process.env, EASYSOCIABLE_NO_OPEN: 'true' }
|
|
12
|
-
|
|
13
|
-
function run(args) {
|
|
14
|
-
return spawnSync(process.execPath, [script, ...args, '--json'], { encoding: 'utf8', env })
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
const missingRoot = run(['--preview'])
|
|
19
|
-
assert.equal(missingRoot.status, 2)
|
|
20
|
-
assert.equal(JSON.parse(missingRoot.stdout).code, 'ROOT_REQUIRED')
|
|
21
|
-
|
|
22
|
-
const preview = run(['--root', root, '--preview'])
|
|
23
|
-
assert.equal(preview.status, 0)
|
|
24
|
-
const previewReport = JSON.parse(preview.stdout)
|
|
25
|
-
assert.equal(previewReport.created.length, 0)
|
|
26
|
-
assert.equal(previewReport.valid, true)
|
|
27
|
-
assert.equal(previewReport.rootName, 'Content Engine')
|
|
28
|
-
|
|
29
|
-
const apply = run(['--root', root, '--apply', '--no-open'])
|
|
30
|
-
assert.equal(apply.status, 0, apply.stdout + apply.stderr)
|
|
31
|
-
const applied = JSON.parse(apply.stdout)
|
|
32
|
-
assert.equal(applied.action, 'applied')
|
|
33
|
-
assert.equal(applied.created.length > 10, true)
|
|
34
|
-
const engine = join(root, 'Content Engine')
|
|
35
|
-
assert.equal(readFileSync(join(engine, 'CLAUDE.md'), 'utf8').includes('one platform = one Run') || readFileSync(join(engine, 'CLAUDE.md'), 'utf8').includes('One folder = one platform'), true)
|
|
36
|
-
const engineJson = JSON.parse(readFileSync(join(engine, 'engine.json'), 'utf8'))
|
|
37
|
-
assert.equal(engineJson.schemaVersion, 'content-engine.v1')
|
|
38
|
-
assert.equal(engineJson.root, 'Content Engine')
|
|
39
|
-
assert.equal(engineJson.paths.runs, 'runs')
|
|
40
|
-
assert.equal(existsSync(join(engine, 'runs', '_template', 'idea.md')), true)
|
|
41
|
-
assert.equal(existsSync(join(engine, 'runs', '_template', 'run.md')), false)
|
|
42
|
-
assert.equal(existsSync(join(engine, 'platforms', 'ig.md')), true)
|
|
43
|
-
assert.equal(existsSync(join(engine, 'discovery', '_template.md')), true)
|
|
44
|
-
assert.equal(existsSync(join(engine, 'discovery', '_index.md')), true)
|
|
45
|
-
assert.equal(readFileSync(join(engine, 'CLAUDE.md'), 'utf8').includes('§ Discovery'), true)
|
|
46
|
-
assert.equal(existsSync(join(engine, 'workbench', 'build.py')), true)
|
|
47
|
-
assert.equal(existsSync(join(engine, 'workbench', 'template.html')), true)
|
|
48
|
-
assert.equal(existsSync(join(engine, 'workbench', 'index.html')), true)
|
|
49
|
-
const sentinel = join(engine, 'runs', 'user-note.md')
|
|
50
|
-
writeFileSync(sentinel, 'keep me', 'utf8')
|
|
51
|
-
const customTemplate = join(engine, 'workbench', 'template.html')
|
|
52
|
-
const before = readFileSync(customTemplate, 'utf8')
|
|
53
|
-
writeFileSync(customTemplate, `${before}\n<!-- user-custom -->\n`, 'utf8')
|
|
54
|
-
const second = run(['--root', root, '--apply', '--no-open'])
|
|
55
|
-
assert.equal(second.status, 0)
|
|
56
|
-
assert.equal(readFileSync(sentinel, 'utf8'), 'keep me')
|
|
57
|
-
assert.equal(readFileSync(customTemplate, 'utf8').includes('user-custom'), true)
|
|
58
|
-
assert.equal(JSON.parse(second.stdout).created.includes('CLAUDE.md'), false)
|
|
59
|
-
|
|
60
|
-
const detectRoot = join(fixture, 'DetectRoot')
|
|
61
|
-
const detectEngine = join(detectRoot, 'MyFolder', 'Content Engine')
|
|
62
|
-
mkdirSync(detectEngine, { recursive: true })
|
|
63
|
-
const detect = spawnSync(process.execPath, [script, '--detect', '--json'], {
|
|
64
|
-
encoding: 'utf8',
|
|
65
|
-
env: { ...env, EASYSOCIABLE_ENGINE_SEARCH_ROOTS: detectRoot },
|
|
66
|
-
})
|
|
67
|
-
assert.equal(detect.status, 0, detect.stdout + detect.stderr)
|
|
68
|
-
const detectReport = JSON.parse(detect.stdout)
|
|
69
|
-
assert.equal(detectReport.action, 'detect')
|
|
70
|
-
assert.equal(detectReport.engines.includes(detectEngine), true)
|
|
71
|
-
|
|
72
|
-
const validateOk = run(['--root', root, '--validate'])
|
|
73
|
-
assert.equal(validateOk.status, 0)
|
|
74
|
-
rmSync(join(engine, 'engine.json'))
|
|
75
|
-
const validateMissing = run(['--root', root, '--validate'])
|
|
76
|
-
assert.equal(validateMissing.status, 2)
|
|
77
|
-
|
|
78
|
-
console.log('Content Skill setup smoke passed.')
|
|
79
|
-
}
|
|
80
|
-
finally {
|
|
81
|
-
rmSync(fixture, { recursive: true, force: true })
|
|
82
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# Discovery
|
|
2
|
-
|
|
3
|
-
Discovery is the intermediate layer between watchlist signals and Topics.
|
|
4
|
-
|
|
5
|
-
```text
|
|
6
|
-
watchlist → discovery → (explicit promote) → topic → brief
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
It makes the source understandable before deciding whether the idea belongs to us.
|
|
10
|
-
|
|
11
|
-
- `Source Digest` is a short natural-language note: what happened, the author's argument, support, native language, and limits.
|
|
12
|
-
- `Source limits` records what the source does not establish.
|
|
13
|
-
- `Decision` stores only workflow status and next action.
|
|
14
|
-
- A source with high engagement can still remain `saved` or `rejected`.
|
|
15
|
-
|
|
16
|
-
Do not add our audience, product, thesis, ownership, or candidate Topics to a Discovery.
|
|
17
|
-
Do not use 5C as the default Discovery reading format.
|
|
18
|
-
Discovery does not generate publish-ready copy.
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# Discovery index
|
|
2
|
-
|
|
3
|
-
One row = one analyzed external signal from a watchlist scan. Discovery is not a topic queue.
|
|
4
|
-
|
|
5
|
-
Newest on top.
|
|
6
|
-
|
|
7
|
-
| id | date | profile | platform | source_handle | source_url | source_claim | source_role | status | next_action | topic_ids | file |
|
|
8
|
-
|----|------|---------|----------|---------------|------------|--------------|-------------|--------|-------------|-----------|------|
|
|
9
|
-
| -- | ---- | ------- | -------- | ------------- | ---------- | ------------ | ----------- | ------ | ----------- | --------- | ---- |
|
|
10
|
-
|
|
11
|
-
## Status values
|
|
12
|
-
|
|
13
|
-
| status | Meaning |
|
|
14
|
-
|--------|---------|
|
|
15
|
-
| `unread` | Signal captured; digest not read |
|
|
16
|
-
| `read` | Source Digest completed |
|
|
17
|
-
| `saved` | Useful reference; no Topic decision yet |
|
|
18
|
-
| `promoted` | User explicitly promoted it to Topic |
|
|
19
|
-
| `rejected` | Not relevant, provable, or ownable |
|
|
20
|
-
|
|
21
|
-
## Rules
|
|
22
|
-
|
|
23
|
-
- Watchlist heat is source context, not our evidence.
|
|
24
|
-
- New Discovery records contain only Source Digest, source limits, and workflow state.
|
|
25
|
-
- Do not create a Topic during watchlist scanning; Topic creation requires explicit promotion.
|
|
26
|
-
- Do not store our audience, product, ownership, or candidate opportunities inside Discovery.
|
|
27
|
-
- 5C belongs to Topic evaluation and is never the default Discovery reading surface.
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: D-YYYYMMDD-XX
|
|
3
|
-
date: YYYY-MM-DD
|
|
4
|
-
status: read
|
|
5
|
-
profile: default
|
|
6
|
-
platform: x
|
|
7
|
-
source_role: demand | ally | foil | mechanism
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# Discovery
|
|
11
|
-
|
|
12
|
-
One sentence describing the external signal in the source's terms. Do not turn it into our topic here.
|
|
13
|
-
|
|
14
|
-
## Source
|
|
15
|
-
|
|
16
|
-
- **handle:**
|
|
17
|
-
- **url:**
|
|
18
|
-
- **captured_at:**
|
|
19
|
-
- **engagement snapshot:** likes / replies / bookmarks / views @ time
|
|
20
|
-
- **why on watchlist:**
|
|
21
|
-
|
|
22
|
-
## Source Digest
|
|
23
|
-
|
|
24
|
-
### In one sentence
|
|
25
|
-
|
|
26
|
-
### What happened
|
|
27
|
-
|
|
28
|
-
### What the author is arguing
|
|
29
|
-
|
|
30
|
-
### How the author supports it
|
|
31
|
-
|
|
32
|
-
### The author's own language
|
|
33
|
-
|
|
34
|
-
### Source limits
|
|
35
|
-
|
|
36
|
-
What the source does not establish. Keep this short.
|
|
37
|
-
|
|
38
|
-
## Decision
|
|
39
|
-
|
|
40
|
-
- **status:** unread | read | saved | promoted | rejected
|
|
41
|
-
- **next_action:** none | revisit | promote_to_topic
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# Profiles index
|
|
2
|
-
|
|
3
|
-
Reusable **content profiles**: pillars + audience + voice.
|
|
4
|
-
Accounts point here; do not copy profile bodies into account files.
|
|
5
|
-
|
|
6
|
-
| id | path | voice | one_liner | accounts |
|
|
7
|
-
|----|------|-------|-----------|----------|
|
|
8
|
-
| default | profiles/default/ | `profiles/default/voice.md` | Replace this one-liner with the profile’s job | your-handle |
|
|
9
|
-
|
|
10
|
-
## Voice column
|
|
11
|
-
|
|
12
|
-
- Empty or `profile` → load `profiles/<id>/voice.md`
|
|
13
|
-
- Explicit path → load that file instead
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# topics/
|
|
2
|
-
|
|
3
|
-
Topic system: **what to produce next**, after an external signal has been understood and converted into an owned opportunity.
|
|
4
|
-
|
|
5
|
-
| Path | Role |
|
|
6
|
-
|------|------|
|
|
7
|
-
| `_index.md` | Queue registry (one row per topic + `item` link + source engagement) |
|
|
8
|
-
| `items/T-*.md` | One file per opportunity (context, audience, conflict, insight, evidence) |
|
|
9
|
-
| `WATCHLIST.md` | Watchlist router (per-profile scheme B) |
|
|
10
|
-
| `watchlists/<profile_id>.md` | Profile-scoped radar |
|
|
11
|
-
| `watchlists/_shared.md` | Shared radar for all profiles |
|
|
12
|
-
| `scoring.md` | Opportunity dimensions and dual gate |
|
|
13
|
-
| `_template-item.md` | Template for new items |
|
|
14
|
-
|
|
15
|
-
Flow:
|
|
16
|
-
|
|
17
|
-
```text
|
|
18
|
-
watchlist → Discovery (Source Digest)
|
|
19
|
-
→ user explicitly promotes
|
|
20
|
-
→ `_index` + `items/T-…` (opportunity 5-dim)
|
|
21
|
-
→ ready only if opp≥7.5 and audience≥6
|
|
22
|
-
→ user opens run → `status=promoted`
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Cap **5 produce** per promotion session. Watchlist scan itself creates Discovery records, not Topics.
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
# Watchlist (router)
|
|
2
|
-
|
|
3
|
-
**Scheme B:** one radar table **per profile**, not one global table.
|
|
4
|
-
|
|
5
|
-
No X Private List required. Agent updates files under `topics/watchlists/`; user works in chat only.
|
|
6
|
-
|
|
7
|
-
## File map
|
|
8
|
-
|
|
9
|
-
| Path | Role |
|
|
10
|
-
|------|------|
|
|
11
|
-
| `topics/WATCHLIST.md` | **This file** — routing, columns, scan rules |
|
|
12
|
-
| `topics/watchlists/_shared.md` | Shared rows (`*`) — merge into every scan |
|
|
13
|
-
| `topics/watchlists/<profile_id>.md` | Profile-scoped radar (must match `profiles/_index` id) |
|
|
14
|
-
| `topics/watchlists/README.md` | Short folder note |
|
|
15
|
-
|
|
16
|
-
### Profiles with a watchlist file today
|
|
17
|
-
|
|
18
|
-
| profile_id | file |
|
|
19
|
-
|------------|------|
|
|
20
|
-
| default | `watchlists/default.md` |
|
|
21
|
-
|
|
22
|
-
New profile → create `watchlists/<id>.md` (copy empty table header from `_shared.md` or an existing file).
|
|
23
|
-
|
|
24
|
-
## Columns (in each table file)
|
|
25
|
-
|
|
26
|
-
| Column | Meaning |
|
|
27
|
-
|--------|---------|
|
|
28
|
-
| `#` | Id within that file (`#1…` profile-local; `#S-01…` in `_shared`) |
|
|
29
|
-
| **`platform`** | Network of this handle + where Agent scans: `x` \| `tiktok` \| `linkedin` \| `ig` \| `youtube` \| `xhs` \| `threads` \| … |
|
|
30
|
-
| `handle` | Platform-native id without `@` |
|
|
31
|
-
| `niche_tag` | Topic/product tag — not a substitute for `platform` |
|
|
32
|
-
| `why_on_list` | One line |
|
|
33
|
-
| `weight` | 0.5–1.5 |
|
|
34
|
-
| `status` | active / paused / removed |
|
|
35
|
-
|
|
36
|
-
**No `profiles` column in the table** — the **filename** is the profile scope.
|
|
37
|
-
Shared across profiles → put the row in `_shared.md` (same as `*`).
|
|
38
|
-
Same handle needed by two profiles only (not all) → **copy the row into both files** (or later a small group file; avoid until needed).
|
|
39
|
-
|
|
40
|
-
## Resolve profile for scan / add (required)
|
|
41
|
-
|
|
42
|
-
1. User names **profile_id** → use it.
|
|
43
|
-
2. User names **account** handle → resolve via `accounts/_index.md` → `profile_id`.
|
|
44
|
-
3. User names **neither** → **do not default**. List active profiles from `profiles/_index.md` and **ask which profile** (or account).
|
|
45
|
-
4. Load rows = **`watchlists/_shared.md` (active)** ∪ **`watchlists/<profile_id>.md` (active)**.
|
|
46
|
-
5. Filter **`platform`**: user-specified, else default **`x`**; `全部平台` = no platform filter.
|
|
47
|
-
6. Create **Discovery** records first; do not create Topics during the scan. Cap **5** new **`produce`** topics only when the user later promotes. Sort watchlist rows by weight (≥1.3 first).
|
|
48
|
-
7. Wrong network for a row → never (e.g. no X tools on `platform=tiktok`).
|
|
49
|
-
|
|
50
|
-
## Maintenance
|
|
51
|
-
|
|
52
|
-
| User says | Agent does |
|
|
53
|
-
|-----------|------------|
|
|
54
|
-
| 把 @foo 加进 watchlist | Resolve/ask **profile** + **platform**; append row to that profile file (or `_shared` if user says 全 profile / shared) |
|
|
55
|
-
| 去掉 #n / @foo | Edit the correct file; if ambiguous, ask |
|
|
56
|
-
| 新 profile 建雷达 | Create `watchlists/<id>.md` |
|
|
57
|
-
|
|
58
|
-
## Scan rules (also CLAUDE § Discovery)
|
|
59
|
-
|
|
60
|
-
- Create one Discovery per meaningful signal; discard noise before analysis.
|
|
61
|
-
- Watchlist scan creates **Discovery** records first; it does not create Topics directly.
|
|
62
|
-
- Write a Source Digest + source limits only. Do not add our audience, product, ownership, or candidate Topics.
|
|
63
|
-
- Keep source engagement as context on the Discovery file; copy `src_*` to `topics/_index.md` only after promotion.
|
|
64
|
-
- Report `unread / read / saved / promoted / rejected` counts after the batch.
|
|
65
|
-
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: T-YYYYMMDD-XX
|
|
3
|
-
date: YYYY-MM-DD
|
|
4
|
-
status: scored
|
|
5
|
-
account: your-handle
|
|
6
|
-
profile: default
|
|
7
|
-
platform: x
|
|
8
|
-
pillar: P1
|
|
9
|
-
source_type: watchlist | inbox | user | discovery
|
|
10
|
-
purpose: produce | library | discard
|
|
11
|
-
scout_label: demand | ally | foil | craft_only
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
# Opportunity
|
|
15
|
-
|
|
16
|
-
One sentence — what's the content opportunity, not a title.
|
|
17
|
-
|
|
18
|
-
## Source
|
|
19
|
-
|
|
20
|
-
- **type:** watchlist | inbox | user | discovery
|
|
21
|
-
- **url:**
|
|
22
|
-
- **handle:**
|
|
23
|
-
- **discovery_id:** (only if promoted from D-…; else empty)
|
|
24
|
-
- **capture_id:** (only if linked to inbox; else empty)
|
|
25
|
-
- **raw:** (only if exists)
|
|
26
|
-
- **engagement snapshot:** likes / replies / bookmarks / views @ time
|
|
27
|
-
|
|
28
|
-
## Scout
|
|
29
|
-
|
|
30
|
-
- **purpose:** produce | library
|
|
31
|
-
- **scout_label:** demand | ally | foil | craft_only
|
|
32
|
-
- **scout_note:** (one line — why not discard)
|
|
33
|
-
|
|
34
|
-
## Context
|
|
35
|
-
|
|
36
|
-
- **what_changed:**
|
|
37
|
-
- **why_now:**
|
|
38
|
-
|
|
39
|
-
## Audience
|
|
40
|
-
|
|
41
|
-
- **who:**
|
|
42
|
-
- **current_belief:**
|
|
43
|
-
- **icp_checks:** (pain / buyer shape / wedge — note which hit, from profile)
|
|
44
|
-
|
|
45
|
-
## Conflict
|
|
46
|
-
|
|
47
|
-
- **old_model:**
|
|
48
|
-
- **new_reality:**
|
|
49
|
-
- **belief_gap:**
|
|
50
|
-
|
|
51
|
-
## Insight
|
|
52
|
-
|
|
53
|
-
- **potential_insight:**
|
|
54
|
-
- **why_nonobvious:**
|
|
55
|
-
|
|
56
|
-
## Evidence
|
|
57
|
-
|
|
58
|
-
- **available:**
|
|
59
|
-
- **type:** data | example | case | mechanism | expert | observation
|
|
60
|
-
- **gaps:**
|
|
61
|
-
|
|
62
|
-
## Score (opportunity)
|
|
63
|
-
|
|
64
|
-
| dim | score 0-10 | note |
|
|
65
|
-
|-----|------------|------|
|
|
66
|
-
| context | | |
|
|
67
|
-
| audience | | |
|
|
68
|
-
| conflict | | |
|
|
69
|
-
| insight | | |
|
|
70
|
-
| evidence | | |
|
|
71
|
-
|
|
72
|
-
**opp_score:** (weighted per `topics/scoring.md`)
|
|
73
|
-
**audience_floor:** (hard check ≥ 6)
|
|
74
|
-
|
|
75
|
-
## Profile fit
|
|
76
|
-
|
|
77
|
-
- pillars:
|
|
78
|
-
- audience_pain:
|
|
79
|
-
- risk:
|
|
80
|
-
|
|
81
|
-
## Suggested production (optional, not binding)
|
|
82
|
-
|
|
83
|
-
- swipe: none | S…
|
|
84
|
-
- atoms: none | …
|
|
85
|
-
- claim: none | C…
|
|
86
|
-
|
|
87
|
-
## Decision
|
|
88
|
-
|
|
89
|
-
- next: open_run | hold | kill | library_ingest | research_evidence
|
|
90
|
-
- run_id: (filled when promoted)
|