sneakoscope 10.6.2 → 10.7.0
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 +2 -2
- package/config/perf-budgets.v1.json +1 -2
- package/config/skills-hash-ledger.v1.json +7 -2
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/bin/sks-dispatch.js +6 -3
- package/dist/cli/args.js +3 -1
- package/dist/cli/command-manifest-lite.js +48 -114
- package/dist/cli/command-registry.js +1 -18
- package/dist/cli/commands-fast.js +11 -4
- package/dist/cli/help-fast.js +11 -22
- package/dist/cli/help.js +3 -3
- package/dist/cli/install-helpers.js +1 -1
- package/dist/cli/router.js +12 -26
- package/dist/commands/codex-app.js +2 -2
- package/dist/commands/doctor-helpers.js +1 -1
- package/dist/commands/mad-sks.js +2 -2
- package/dist/config/skills-manifest.json +13 -8
- package/dist/core/agent-bridge/async-command.js +2 -2
- package/dist/core/agents/agent-recursion-guard.js +0 -2
- package/dist/core/auto-review.js +15 -172
- package/dist/core/code-structure.js +0 -1
- package/dist/core/codex-control/codex-reliability-shield.js +64 -15
- package/dist/core/codex-control/codex-sdk-adapter.js +38 -5
- package/dist/core/commands/agent-bridge-command.js +1 -1
- package/dist/core/commands/basic-cli.js +60 -233
- package/dist/core/commands/command-utils.js +1 -1
- package/dist/core/commands/gc-command.js +30 -29
- package/dist/core/commands/image-ux-review-command.js +1 -1
- package/dist/core/commands/install-package-command.js +1 -1
- package/dist/core/commands/intent-normalization/normalizer.js +2 -2
- package/dist/core/commands/pipeline-command.js +13 -5
- package/dist/core/commands/plan-command.js +2 -3
- package/dist/core/commands/research-command.js +0 -3
- package/dist/core/commands/review-command.js +1 -1
- package/dist/core/commands/seo-command.js +27 -20
- package/dist/core/commands/status-command.js +8 -2
- package/dist/core/doctor/command-alias-cleanup.js +6 -6
- package/dist/core/evaluation.js +1 -1
- package/dist/core/feature-fixtures.js +9 -43
- package/dist/core/feature-registry.js +9 -27
- package/dist/core/git-hygiene/shared-memory-publish.js +1 -1
- package/dist/core/hooks-runtime/stop-repeat-guard.js +0 -13
- package/dist/core/init/skills.js +5 -13
- package/dist/core/init.js +0 -9
- package/dist/core/install/installed-package-smoke.js +3 -1
- package/dist/core/perf/import-graph-budget.js +0 -1
- package/dist/core/perf/release-latency-slo.js +2 -30
- package/dist/core/pipeline-internals/pipeline-stage-builder.js +2 -4
- package/dist/core/pipeline-internals/runtime-core.js +2 -38
- package/dist/core/proof/route-proof-policy.js +2 -4
- package/dist/core/proof/runtime-truth-matrix.js +2 -2
- package/dist/core/proof-field.js +104 -39
- package/dist/core/questions.js +11 -7
- package/dist/core/release/gate-affected-globs.js +0 -2
- package/dist/core/research/research-reviewer-contract.js +34 -0
- package/dist/core/research/research-work-graph.js +1 -1
- package/dist/core/research.js +1 -1
- package/dist/core/routes/constants.js +0 -1
- package/dist/core/routes/diagnostic-allowlist.js +1 -3
- package/dist/core/routes/light-routes.js +1 -2
- package/dist/core/routes.js +1 -1
- package/dist/core/safety/command-contract/contracts.js +5 -1
- package/dist/core/search-visibility/marketing-truthfulness.js +10 -7
- package/dist/core/search-visibility/marketing.js +194 -149
- package/dist/core/skill-forge.js +1 -361
- package/dist/core/strategy/adhd-orchestrating-gate.js +3 -3
- package/dist/core/triwiki/context-graph/extractors/topology/commands.js +2 -6
- package/dist/core/triwiki-wrongness/wrongness-cli.js +9 -27
- package/dist/core/version.js +1 -1
- package/package.json +1 -2
- package/release-gates.v2.json +1 -32
- package/dist/cli/recallpulse-command.js +0 -144
- package/dist/commands/auto-review.js +0 -5
- package/dist/commands/codex-native.js +0 -77
- package/dist/commands/recallpulse.js +0 -5
- package/dist/core/codex-native/codex-native-interop-policy.js +0 -127
- package/dist/core/commands/autoresearch-command.js +0 -4
- package/dist/core/commands/code-structure-command.js +0 -52
- package/dist/core/commands/goal-command.js +0 -43
- package/dist/core/commands/recallpulse-command.js +0 -4
- package/dist/core/commands/remote-command.js +0 -119
- package/dist/core/commands/skill-dream-command.js +0 -43
- package/dist/core/commands/wrongness-command.js +0 -2
- package/dist/core/goal-workflow.js +0 -80
- package/dist/core/mcp/mcp-tool-name-normalizer.js +0 -28
- package/dist/core/recallpulse/policy.js +0 -207
- package/dist/core/recallpulse.js +0 -947
- package/dist/core/remote/audit-idempotency.js +0 -241
- package/dist/core/remote/index.js +0 -10
- package/dist/core/remote/machine-registry.js +0 -167
- package/dist/core/remote/owner-proof.js +0 -216
- package/dist/core/remote/protocol.js +0 -249
- package/dist/core/remote/readiness.js +0 -156
- package/dist/core/remote/session-index.js +0 -61
- package/dist/core/remote/session-snapshot.js +0 -197
- package/dist/core/remote/ssh-worker-client.js +0 -377
- package/dist/core/remote/types.js +0 -10
- package/dist/core/remote/worker.js +0 -360
- package/dist/core/search/context-graph-seeds.js +0 -15
- package/dist/core/verification/context-graph-affected.js +0 -312
- package/dist/core/verification/verification-artifact-lock.js +0 -21
- package/dist/core/verification/verification-dag.js +0 -89
- package/dist/core/verification/verification-result.js +0 -19
- package/dist/core/verification/verification-worker-pool.js +0 -204
|
@@ -3,51 +3,38 @@ import { exists, readJson, readText, sha256, writeJsonAtomic } from '../fsx.js';
|
|
|
3
3
|
import { detectProject, discoverSiteInventory } from './discovery.js';
|
|
4
4
|
import { createSearchVisibilityMission, resolveSearchVisibilityMission, routeForMode } from './mission.js';
|
|
5
5
|
import { runSuperSearch } from '../super-search/index.js';
|
|
6
|
-
import { evaluateMarketingTruthfulness } from './marketing-truthfulness.js';
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
'src/core/routes.ts',
|
|
12
|
-
'src/core/routes/dollar-manifest-lite.ts',
|
|
13
|
-
'src/core/agents/agent-orchestrator.ts',
|
|
14
|
-
'src/cli/command-manifest-lite.ts',
|
|
15
|
-
'config/perf-budgets.v1.json',
|
|
16
|
-
'.sneakoscope/reports/perf-budget.json',
|
|
17
|
-
'.sneakoscope/reports/super-search-local-http-smoke.json',
|
|
18
|
-
'.sneakoscope/reports/installed-package-smoke.json',
|
|
19
|
-
];
|
|
20
|
-
const EXTERNAL_SOURCE_URLS = [
|
|
21
|
-
'https://github.com/mandarange/Sneakoscope-Codex',
|
|
22
|
-
'https://www.npmjs.com/package/sneakoscope',
|
|
23
|
-
];
|
|
24
|
-
const COMPETITOR_SOURCE_URLS = [
|
|
25
|
-
'https://github.com/Yeachan-Heo/oh-my-codex',
|
|
26
|
-
'https://github.com/code-yeongyu/lazycodex',
|
|
27
|
-
'https://www.npmjs.com/package/oh-my-codex',
|
|
28
|
-
'https://www.npmjs.com/package/lazycodex-ai',
|
|
29
|
-
];
|
|
6
|
+
import { classifyClaim, evaluateMarketingTruthfulness } from './marketing-truthfulness.js';
|
|
7
|
+
const INTERNAL_SOURCE_FILES = ['package.json', 'CHANGELOG.md'];
|
|
8
|
+
const NON_POSITIONING_SECTION = /^(?:install(?:ation|ing)?|usage|getting started|quick ?start|setup|requirements|prerequisites|configuration|development|contributing|contributors|license|changelog|faq|support|credits|acknowledg(?:e)?ments|table of contents|contents|testing|tests?|build(?:ing)?|scripts|roadmap|security|code of conduct|authors?|sponsors?|related|see also|links|api|reference|examples?|demo|screenshots?)\b/i;
|
|
9
|
+
const MAX_MESSAGE_PILLARS = 4;
|
|
10
|
+
const MAX_KEYWORDS = 20;
|
|
30
11
|
export async function runMarketingResearch(mode, missionRef, options) {
|
|
31
12
|
const mission = await resolveOrCreateMarketingMission(mode, missionRef, options, 'marketing research');
|
|
32
13
|
const ctx = context(mode, mission.root, options);
|
|
33
14
|
const detected = await detectProject(ctx);
|
|
34
15
|
const inventory = await discoverSiteInventory(ctx, detected);
|
|
35
16
|
await writeJsonAtomic(path.join(mission.artifactDir, 'site-inventory.json'), inventory);
|
|
36
|
-
const internalSources = await collectInternalSources(mission.root);
|
|
17
|
+
const internalSources = await collectInternalSources(mission.root, inventory);
|
|
18
|
+
const projectUrls = await projectSourceUrls(mission.root, inventory, options);
|
|
19
|
+
const competitorUrls = unique(options.competitorUrls || []);
|
|
37
20
|
const externalSources = options.offline
|
|
38
21
|
? []
|
|
39
|
-
: await collectSuperSearchSources(mission,
|
|
40
|
-
const competitorSources = !options.offline && options.includeCompetitors
|
|
41
|
-
? await collectSuperSearchSources(mission,
|
|
22
|
+
: await collectSuperSearchSources(mission, projectUrls, 'external', options.maxMarketingSources);
|
|
23
|
+
const competitorSources = !options.offline && options.includeCompetitors && competitorUrls.length
|
|
24
|
+
? await collectSuperSearchSources(mission, competitorUrls, 'competitor', options.maxMarketingSources)
|
|
42
25
|
: [];
|
|
43
|
-
const claims = buildMarketingClaims(inventory, internalSources
|
|
26
|
+
const claims = buildMarketingClaims(inventory, internalSources);
|
|
44
27
|
const truth = evaluateMarketingTruthfulness({ claims });
|
|
45
28
|
const blockedClaims = claims.filter((claim) => !claim.publishable || claim.blockers.length || truth.unsupported_claims.includes(claim.id));
|
|
46
|
-
const blockers = [
|
|
29
|
+
const blockers = [];
|
|
47
30
|
if (!internalSources.length)
|
|
48
31
|
blockers.push('marketing_internal_sources_missing');
|
|
49
|
-
if (!options.offline && !
|
|
32
|
+
if (!options.offline && !projectUrls.length)
|
|
33
|
+
blockers.push('marketing_project_urls_missing');
|
|
34
|
+
else if (!options.offline && !externalSources.some((source) => source.verified))
|
|
50
35
|
blockers.push('marketing_external_sources_unverified');
|
|
36
|
+
if (options.includeCompetitors && !competitorUrls.length)
|
|
37
|
+
blockers.push('marketing_competitor_urls_required');
|
|
51
38
|
const research = {
|
|
52
39
|
schema: 'sks.search-visibility.marketing-research.v1',
|
|
53
40
|
ok: blockers.length === 0,
|
|
@@ -63,6 +50,8 @@ export async function runMarketingResearch(mode, missionRef, options) {
|
|
|
63
50
|
await writeJsonAtomic(path.join(mission.artifactDir, 'marketing-source-ledger.json'), {
|
|
64
51
|
schema: 'sks.search-visibility.marketing-source-ledger.v1',
|
|
65
52
|
mission_id: mission.id,
|
|
53
|
+
project_urls: projectUrls,
|
|
54
|
+
competitor_urls: competitorUrls,
|
|
66
55
|
sources: [...internalSources, ...externalSources, ...competitorSources],
|
|
67
56
|
});
|
|
68
57
|
await writeJsonAtomic(path.join(mission.artifactDir, 'marketing-claim-ledger.json'), {
|
|
@@ -87,6 +76,7 @@ export async function runMarketingResearch(mode, missionRef, options) {
|
|
|
87
76
|
route: routeForMode(mode),
|
|
88
77
|
status: research.ok ? 'verified_partial' : 'blocked',
|
|
89
78
|
artifacts_dir: `.sneakoscope/missions/${mission.id}/search-visibility`,
|
|
79
|
+
project_urls: projectUrls,
|
|
90
80
|
internal_sources: internalSources.length,
|
|
91
81
|
external_sources: externalSources.length,
|
|
92
82
|
competitor_sources: competitorSources.length,
|
|
@@ -107,7 +97,11 @@ export async function runMarketingStrategy(mode, missionRef, options) {
|
|
|
107
97
|
const inventory = await readJson(path.join(mission.artifactDir, 'site-inventory.json'), null);
|
|
108
98
|
if (!inventory)
|
|
109
99
|
return blockedStrategy(mission, ['site_inventory_required_for_strategy']);
|
|
110
|
-
const
|
|
100
|
+
const readmeSource = research.internal_sources.find((source) => Boolean(source.path) && source.path === inventory.readme.path);
|
|
101
|
+
const readmeText = readmeSource?.path ? await readText(path.join(mission.root, readmeSource.path), '') : '';
|
|
102
|
+
if (readmeSource?.sha256 && sha256(readmeText) !== readmeSource.sha256)
|
|
103
|
+
return blockedStrategy(mission, ['marketing_readme_changed_since_research']);
|
|
104
|
+
const strategy = buildMarketingStrategy(mission.id, inventory, research, readmeText);
|
|
111
105
|
const truth = evaluateMarketingTruthfulness({ strategy });
|
|
112
106
|
const strategy_quality = scoreMarketingStrategy(strategy, truth);
|
|
113
107
|
const blockers = [...strategy.blockers, ...truth.blockers, ...strategy_quality.blockers];
|
|
@@ -137,101 +131,109 @@ export async function runMarketingStrategy(mode, missionRef, options) {
|
|
|
137
131
|
blockers,
|
|
138
132
|
};
|
|
139
133
|
}
|
|
140
|
-
function buildMarketingStrategy(missionId, inventory, research) {
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
const
|
|
144
|
-
const
|
|
145
|
-
const
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
'
|
|
156
|
-
'
|
|
157
|
-
'agent-orchestration',
|
|
158
|
-
'proof-gates',
|
|
159
|
-
...(superSearchSource ? ['super-search'] : []),
|
|
160
|
-
'seo',
|
|
161
|
-
'generative-engine-optimization',
|
|
162
|
-
'bounded-memory',
|
|
163
|
-
'rollback',
|
|
164
|
-
'release-integrity',
|
|
165
|
-
]).slice(0, 20);
|
|
166
|
-
const messagePillars = [
|
|
167
|
-
{ title: 'Proof-first route gates', claim: 'SKS records route artifacts, blockers, and release gates so Codex workflows can separate verified work from unverified claims.', source_ids: [readmeSource || packageSource].filter(Boolean) },
|
|
168
|
-
...(superSearchSource ? [{ title: 'Source-backed search visibility', claim: 'Super-Search and SEO/GEO artifacts keep source ledgers and claim ledgers attached to search visibility work.', source_ids: [superSearchSource] }] : []),
|
|
169
|
-
...(parallelSource ? [{ title: 'Bounded parallel implementation', claim: 'The Naruto agent orchestrator runs bounded multi-agent coding workflows.', source_ids: [parallelSource] }] : []),
|
|
170
|
-
...(perfSource ? [{ title: 'Measured fast paths', claim: 'Performance budget reports define p95 budgets for selected SKS fast-path commands.', source_ids: [perfSource] }] : []),
|
|
134
|
+
function buildMarketingStrategy(missionId, inventory, research, readmeText) {
|
|
135
|
+
const verifiedId = (source) => (source?.verified ? source.id : null);
|
|
136
|
+
const packageSource = verifiedId(research.internal_sources.find((source) => Boolean(source.path) && source.path === inventory.package.path));
|
|
137
|
+
const readmeSource = verifiedId(research.internal_sources.find((source) => Boolean(source.path) && source.path === inventory.readme.path));
|
|
138
|
+
const projectName = inventory.package.name || inventory.readme.h1 || 'This project';
|
|
139
|
+
const excluded = [];
|
|
140
|
+
const passes = (id, text, claimType, sourceIds) => {
|
|
141
|
+
const gate = evaluateMarketingTruthfulness({
|
|
142
|
+
claims: [{ id, text, claim_type: claimType, source_ids: sourceIds, publishable: true, blockers: sourceIds.length ? [] : ['source_ids_required'] }],
|
|
143
|
+
});
|
|
144
|
+
if (!gate.ok)
|
|
145
|
+
excluded.push(...gate.blockers);
|
|
146
|
+
return gate.ok;
|
|
147
|
+
};
|
|
148
|
+
const candidates = [
|
|
149
|
+
{ id: 'positioning-package-description', text: (inventory.package.description || '').trim(), source: packageSource },
|
|
150
|
+
{ id: 'positioning-readme-lead', text: readmeLead(readmeText), source: readmeSource },
|
|
171
151
|
];
|
|
152
|
+
const positioning = candidates.find((candidate) => Boolean(candidate.text && candidate.source)
|
|
153
|
+
&& passes(candidate.id, candidate.text, 'positioning', [candidate.source])) || null;
|
|
154
|
+
const positioningSource = positioning?.source || null;
|
|
155
|
+
const oneLiner = positioning?.text || '';
|
|
156
|
+
const messagePillars = readmeSections(readmeText)
|
|
157
|
+
.filter((section) => !NON_POSITIONING_SECTION.test(section.title))
|
|
158
|
+
.map((section) => ({ title: section.title, claim: section.lead, source_ids: readmeSource ? [readmeSource] : [] }))
|
|
159
|
+
.filter((pillar, index) => Boolean(pillar.claim)
|
|
160
|
+
&& passes(`pillar-${index + 1}`, `${pillar.title}: ${pillar.claim}`, classifyClaim(`${pillar.title}: ${pillar.claim}`), pillar.source_ids))
|
|
161
|
+
.slice(0, MAX_MESSAGE_PILLARS);
|
|
162
|
+
const existingKeywords = unique(inventory.package.keywords.map((keyword) => keyword.trim()));
|
|
163
|
+
const identityKeywords = unique([bareName(inventory.package.name), ...frameworkKeywords(inventory)])
|
|
164
|
+
.filter((keyword) => !existingKeywords.includes(keyword));
|
|
165
|
+
const keywords = [...existingKeywords, ...identityKeywords].slice(0, MAX_KEYWORDS);
|
|
166
|
+
const keywordClusters = packageSource
|
|
167
|
+
? [
|
|
168
|
+
{ name: 'package keywords', keywords: existingKeywords, source_ids: [packageSource] },
|
|
169
|
+
{ name: 'project identity', keywords: identityKeywords, source_ids: [packageSource] },
|
|
170
|
+
].filter((cluster) => cluster.keywords.length)
|
|
171
|
+
: [];
|
|
172
|
+
const competitorContrast = research.competitor_sources
|
|
173
|
+
.filter((source) => source.verified && oneLiner)
|
|
174
|
+
.map((source) => ({
|
|
175
|
+
competitor: source.title,
|
|
176
|
+
their_claim: source.summary,
|
|
177
|
+
project_contrast: `${projectName}: ${oneLiner}`,
|
|
178
|
+
source_ids: unique([source.id, positioningSource || '']),
|
|
179
|
+
safe_to_publish: true,
|
|
180
|
+
}))
|
|
181
|
+
.filter((contrast, index) => passes(`competitor-contrast-${index + 1}`, `${contrast.competitor}: ${contrast.their_claim} ${contrast.project_contrast}`, 'competitor', contrast.source_ids));
|
|
182
|
+
const readmePlan = positioningSource ? [{
|
|
183
|
+
operation: 'readme-positioning-block-update',
|
|
184
|
+
text: [
|
|
185
|
+
'<!-- BEGIN SKS MARKETING POSITIONING -->',
|
|
186
|
+
oneLiner,
|
|
187
|
+
...(messagePillars.length ? ['', ...messagePillars.map((pillar) => `- **${pillar.title}:** ${pillar.claim}`)] : []),
|
|
188
|
+
'<!-- END SKS MARKETING POSITIONING -->',
|
|
189
|
+
'',
|
|
190
|
+
].join('\n'),
|
|
191
|
+
source_ids: unique([positioningSource, ...messagePillars.flatMap((pillar) => pillar.source_ids)]),
|
|
192
|
+
}] : [];
|
|
193
|
+
const packagePlan = [];
|
|
194
|
+
if (packageSource && positioningSource && !inventory.package.description) {
|
|
195
|
+
packagePlan.push({ operation: 'package-description-update', description: oneLiner, source_ids: [positioningSource] });
|
|
196
|
+
}
|
|
197
|
+
if (packageSource && identityKeywords.length && passes('package-keywords', keywords.join(', '), 'positioning', [packageSource])) {
|
|
198
|
+
packagePlan.push({ operation: 'package-keywords-update', keywords, source_ids: [packageSource] });
|
|
199
|
+
}
|
|
172
200
|
const strategy = {
|
|
173
201
|
schema: 'sks.search-visibility.marketing-strategy.v1',
|
|
174
202
|
ok: true,
|
|
175
203
|
mission_id: missionId,
|
|
176
204
|
positioning: {
|
|
177
205
|
one_liner: oneLiner,
|
|
178
|
-
source_ids: [
|
|
206
|
+
source_ids: positioningSource ? [positioningSource] : [],
|
|
179
207
|
},
|
|
180
208
|
message_pillars: messagePillars,
|
|
181
|
-
keyword_clusters:
|
|
182
|
-
{ name: 'codex trust layer', keywords: keywords.slice(0, 7), source_ids: [packageSource || readmeSource].filter(Boolean) },
|
|
183
|
-
{ name: 'agent release integrity', keywords: keywords.slice(7, 14), source_ids: [readmeSource || packageSource].filter(Boolean) },
|
|
184
|
-
{ name: 'search visibility', keywords: keywords.slice(7, 11), source_ids: [superSearchSource || packageSource].filter(Boolean) },
|
|
185
|
-
],
|
|
209
|
+
keyword_clusters: keywordClusters,
|
|
186
210
|
strategy_quality: {
|
|
187
211
|
score: 0,
|
|
188
212
|
source_backed_claims: 0,
|
|
189
213
|
unsupported_claims: 0,
|
|
190
214
|
competitor_contrast_count: competitorContrast.length,
|
|
191
|
-
keyword_cluster_count:
|
|
215
|
+
keyword_cluster_count: keywordClusters.length,
|
|
192
216
|
blockers: [],
|
|
193
217
|
},
|
|
194
218
|
competitor_contrast: competitorContrast,
|
|
195
|
-
readme_plan:
|
|
196
|
-
|
|
197
|
-
text: [
|
|
198
|
-
'<!-- BEGIN SKS MARKETING POSITIONING -->',
|
|
199
|
-
'## Search Visibility Positioning',
|
|
200
|
-
'',
|
|
201
|
-
oneLiner,
|
|
202
|
-
'',
|
|
203
|
-
'- Source-ledger claims are kept in SKS marketing research and strategy artifacts.',
|
|
204
|
-
'- SEO/GEO mutation plans update only package metadata and this managed README block in this release.',
|
|
205
|
-
'- External visibility outcomes require separate measured evidence.',
|
|
206
|
-
'<!-- END SKS MARKETING POSITIONING -->',
|
|
207
|
-
'',
|
|
208
|
-
].join('\n'),
|
|
209
|
-
source_ids: strategySources,
|
|
210
|
-
}],
|
|
211
|
-
package_plan: [
|
|
212
|
-
{
|
|
213
|
-
operation: 'package-description-update',
|
|
214
|
-
description: 'Proof-first Codex trust layer for bounded agent workflows, search visibility evidence, and release integrity gates.',
|
|
215
|
-
source_ids: strategySources,
|
|
216
|
-
},
|
|
217
|
-
{
|
|
218
|
-
operation: 'package-keywords-update',
|
|
219
|
-
keywords,
|
|
220
|
-
source_ids: strategySources,
|
|
221
|
-
},
|
|
222
|
-
],
|
|
219
|
+
readme_plan: readmePlan,
|
|
220
|
+
package_plan: packagePlan,
|
|
223
221
|
docs_plan: [
|
|
224
|
-
{ title: 'Document marketing claim sources and prohibited guarantees in release notes.', source_ids: [
|
|
222
|
+
{ title: 'Document marketing claim sources and prohibited guarantees in release notes.', source_ids: [positioningSource || packageSource || readmeSource].filter(Boolean), auto_apply: false },
|
|
225
223
|
],
|
|
226
224
|
do_not_claim: [
|
|
227
225
|
'guaranteed ranking',
|
|
228
226
|
'guaranteed traffic',
|
|
229
227
|
'guaranteed AI citation',
|
|
230
|
-
'best or fastest
|
|
228
|
+
'best or fastest in its category without a measurement',
|
|
231
229
|
'competitor disparagement',
|
|
232
230
|
],
|
|
231
|
+
excluded_claims: unique(excluded),
|
|
233
232
|
blockers: [],
|
|
234
233
|
};
|
|
234
|
+
if (!positioningSource) {
|
|
235
|
+
strategy.blockers.push(candidates.some((candidate) => candidate.text && candidate.source) ? 'positioning_claim_unsupported' : 'positioning_source_missing');
|
|
236
|
+
}
|
|
235
237
|
for (const pillar of strategy.message_pillars) {
|
|
236
238
|
if (!pillar.source_ids.length)
|
|
237
239
|
strategy.blockers.push(`pillar_source_missing:${pillar.title}`);
|
|
@@ -240,53 +242,34 @@ function buildMarketingStrategy(missionId, inventory, research) {
|
|
|
240
242
|
if (!plan.source_ids.length)
|
|
241
243
|
strategy.blockers.push(`package_plan_source_missing:${plan.operation}`);
|
|
242
244
|
}
|
|
243
|
-
if (strategy.positioning.source_ids.length === 0)
|
|
244
|
-
strategy.blockers.push('positioning_source_missing');
|
|
245
245
|
return strategy;
|
|
246
246
|
}
|
|
247
|
-
function buildCompetitorContrast(research, strategySources) {
|
|
248
|
-
return research.competitor_sources
|
|
249
|
-
.filter((source) => source.verified)
|
|
250
|
-
.map((source) => ({
|
|
251
|
-
competitor: source.title,
|
|
252
|
-
their_claim: source.summary,
|
|
253
|
-
sks_contrast: 'SKS positions itself around proof-first release integrity and source-backed search visibility, while workflow-first Codex layers emphasize orchestration convenience.',
|
|
254
|
-
source_ids: unique([source.id, ...strategySources]),
|
|
255
|
-
safe_to_publish: true,
|
|
256
|
-
}));
|
|
257
|
-
}
|
|
258
247
|
function scoreMarketingStrategy(strategy, truth) {
|
|
259
248
|
const blockers = [];
|
|
260
249
|
let score = 0;
|
|
261
250
|
const sourceBackedClaims = countSourceBackedStrategyClaims(strategy);
|
|
262
251
|
if (strategy.positioning.source_ids.length)
|
|
263
|
-
score +=
|
|
252
|
+
score += 25;
|
|
264
253
|
else
|
|
265
254
|
blockers.push('strategy_quality_source_backed_positioning_missing');
|
|
266
|
-
if (strategy.message_pillars.length >=
|
|
267
|
-
score +=
|
|
255
|
+
if (strategy.message_pillars.length >= 2 && strategy.message_pillars.every((pillar) => pillar.source_ids.length))
|
|
256
|
+
score += 25;
|
|
268
257
|
else
|
|
269
258
|
blockers.push('strategy_quality_message_pillars_below_threshold');
|
|
270
|
-
if (strategy.keyword_clusters.length >=
|
|
259
|
+
if (strategy.keyword_clusters.length >= 1 && strategy.keyword_clusters.every((cluster) => cluster.source_ids.length))
|
|
271
260
|
score += 15;
|
|
272
261
|
else
|
|
273
|
-
blockers.push('
|
|
262
|
+
blockers.push('strategy_quality_keyword_clusters_missing');
|
|
274
263
|
if (strategy.competitor_contrast.every((contrast) => contrast.safe_to_publish && contrast.source_ids.length))
|
|
275
|
-
score +=
|
|
264
|
+
score += 10;
|
|
276
265
|
else
|
|
277
266
|
blockers.push('strategy_quality_competitor_contrast_unsafe');
|
|
278
267
|
if (truth.forbidden_phrases.length === 0)
|
|
279
268
|
score += 15;
|
|
280
269
|
else
|
|
281
270
|
blockers.push('strategy_quality_forbidden_phrases_present');
|
|
282
|
-
if (
|
|
283
|
-
|
|
284
|
-
&& hasSupportedSpecialClaim(strategy, /super-search|source-backed|source backed/i, /super-search|source|report/i)) {
|
|
285
|
-
score += 15;
|
|
286
|
-
}
|
|
287
|
-
else {
|
|
288
|
-
blockers.push('strategy_quality_special_claim_sources_missing');
|
|
289
|
-
}
|
|
271
|
+
if (truth.unsupported_claims.length === 0)
|
|
272
|
+
score += 10;
|
|
290
273
|
if (score < 80)
|
|
291
274
|
blockers.push('strategy_quality_score_below_80');
|
|
292
275
|
if (truth.unsupported_claims.length)
|
|
@@ -313,16 +296,84 @@ function countSourceBackedStrategyClaims(strategy) {
|
|
|
313
296
|
...strategy.competitor_contrast,
|
|
314
297
|
].filter((item) => item.source_ids.length > 0).length;
|
|
315
298
|
}
|
|
316
|
-
function
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
299
|
+
function readmeLead(text) {
|
|
300
|
+
for (const paragraph of paragraphs(stripFencedCode(text))) {
|
|
301
|
+
if (/^(?:#|<|!\[|\[!\[|>|[-*+] |\d+\. |\||<!--)/.test(paragraph))
|
|
302
|
+
continue;
|
|
303
|
+
const plain = plainMarkdown(paragraph);
|
|
304
|
+
if (plain.length >= 20)
|
|
305
|
+
return firstSentences(plain);
|
|
306
|
+
}
|
|
307
|
+
return '';
|
|
308
|
+
}
|
|
309
|
+
function readmeSections(text) {
|
|
310
|
+
const out = [];
|
|
311
|
+
const body = stripFencedCode(text);
|
|
312
|
+
const matches = [...body.matchAll(/^##[ \t]+(.+?)[ \t]*#*[ \t]*$/gm)];
|
|
313
|
+
for (const [index, match] of matches.entries()) {
|
|
314
|
+
const start = (match.index || 0) + match[0].length;
|
|
315
|
+
const end = index + 1 < matches.length ? matches[index + 1].index || body.length : body.length;
|
|
316
|
+
const section = body.slice(start, end).split(/^#{1,6}[ \t]/m)[0] || '';
|
|
317
|
+
const lead = paragraphs(section)
|
|
318
|
+
.filter((paragraph) => !/^(?:<|!\[|\[!\[|\||<!--)/.test(paragraph))
|
|
319
|
+
.map((paragraph) => plainMarkdown(paragraph.replace(/^(?:[-*+]|\d+\.)\s+/, '')))
|
|
320
|
+
.find((paragraph) => paragraph.length >= 12) || '';
|
|
321
|
+
out.push({ title: plainMarkdown(match[1] || ''), lead: lead ? firstSentences(lead, 1) : '' });
|
|
322
|
+
}
|
|
323
|
+
return out.filter((section) => section.title);
|
|
324
|
+
}
|
|
325
|
+
function stripFencedCode(text) {
|
|
326
|
+
return String(text || '').replace(/^(```|~~~)[^\n]*\n[\s\S]*?^\1[ \t]*$/gm, '');
|
|
327
|
+
}
|
|
328
|
+
function paragraphs(text) {
|
|
329
|
+
return String(text || '').split(/\r?\n[ \t]*\r?\n/).map((paragraph) => paragraph.trim()).filter(Boolean);
|
|
330
|
+
}
|
|
331
|
+
function plainMarkdown(text) {
|
|
332
|
+
return String(text || '')
|
|
333
|
+
.replace(/!\[[^\]]*\]\([^)]*\)/g, '')
|
|
334
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
|
|
335
|
+
.replace(/`([^`]+)`/g, '$1')
|
|
336
|
+
.replace(/(\*\*|__|\*|_)(.+?)\1/g, '$2')
|
|
337
|
+
.replace(/<[^>]+>/g, '')
|
|
338
|
+
.replace(/\s+/g, ' ')
|
|
339
|
+
.trim();
|
|
340
|
+
}
|
|
341
|
+
function firstSentences(text, count = 2) {
|
|
342
|
+
const sentences = text.match(/[^.!?]+[.!?]+(?=\s|$)/g);
|
|
343
|
+
const picked = sentences ? sentences.slice(0, count).join(' ').trim() : text;
|
|
344
|
+
return picked.length > 280 ? `${picked.slice(0, 277).trimEnd()}...` : picked;
|
|
345
|
+
}
|
|
346
|
+
function bareName(name) {
|
|
347
|
+
return String(name || '').replace(/^@[^/]+\//, '').trim().toLowerCase();
|
|
348
|
+
}
|
|
349
|
+
function frameworkKeywords(inventory) {
|
|
350
|
+
const names = { next: 'nextjs', react: 'react', astro: 'astro', nuxt: 'nuxt', svelte: 'svelte' };
|
|
351
|
+
return Object.keys(inventory.package.framework_versions || {}).map((dependency) => names[dependency] || '').filter(Boolean);
|
|
352
|
+
}
|
|
353
|
+
async function projectSourceUrls(root, inventory, options) {
|
|
354
|
+
const pkg = await readJson(path.join(root, 'package.json'), null);
|
|
355
|
+
const published = Boolean(inventory.package.name) && pkg?.private !== true;
|
|
356
|
+
return unique([
|
|
357
|
+
options.url || '',
|
|
358
|
+
inventory.package.homepage || '',
|
|
359
|
+
httpsRepositoryUrl(inventory.package.repository) || '',
|
|
360
|
+
published ? `https://www.npmjs.com/package/${inventory.package.name}` : '',
|
|
361
|
+
].filter((url) => /^https?:\/\//i.test(url)));
|
|
362
|
+
}
|
|
363
|
+
function httpsRepositoryUrl(value) {
|
|
364
|
+
const raw = String(value || '').trim();
|
|
365
|
+
if (!raw)
|
|
366
|
+
return null;
|
|
367
|
+
const shorthand = raw.match(/^(?:(github|gitlab|bitbucket):)?([\w.-]+\/[\w.-]+)$/i);
|
|
368
|
+
if (shorthand) {
|
|
369
|
+
const hosts = { github: 'github.com', gitlab: 'gitlab.com', bitbucket: 'bitbucket.org' };
|
|
370
|
+
return `https://${hosts[(shorthand[1] || 'github').toLowerCase()]}/${shorthand[2].replace(/\.git$/i, '')}`;
|
|
371
|
+
}
|
|
372
|
+
const ssh = raw.match(/^git@([^:]+):(.+?)(?:\.git)?$/i);
|
|
373
|
+
if (ssh)
|
|
374
|
+
return `https://${ssh[1]}/${ssh[2]}`;
|
|
375
|
+
const url = raw.replace(/^git\+/i, '').replace(/^git:\/\//i, 'https://').replace(/\.git$/i, '');
|
|
376
|
+
return /^https?:\/\//i.test(url) ? url : null;
|
|
326
377
|
}
|
|
327
378
|
async function resolveOrCreateMarketingMission(mode, missionRef, options, prompt) {
|
|
328
379
|
const explicit = missionRef && missionRef !== 'latest' ? await resolveSearchVisibilityMission(options.root, missionRef, mode) : null;
|
|
@@ -335,9 +386,9 @@ async function resolveOrCreateMarketingMission(mode, missionRef, options, prompt
|
|
|
335
386
|
}
|
|
336
387
|
return createSearchVisibilityMission(mode, prompt, options);
|
|
337
388
|
}
|
|
338
|
-
async function collectInternalSources(root) {
|
|
389
|
+
async function collectInternalSources(root, inventory) {
|
|
339
390
|
const out = [];
|
|
340
|
-
for (const rel of
|
|
391
|
+
for (const rel of unique([inventory.package.path || '', inventory.readme.path || '', ...INTERNAL_SOURCE_FILES])) {
|
|
341
392
|
const full = path.join(root, rel);
|
|
342
393
|
if (!(await exists(full)))
|
|
343
394
|
continue;
|
|
@@ -387,22 +438,16 @@ async function collectSuperSearchSources(mission, urls, kind, maxSources) {
|
|
|
387
438
|
}
|
|
388
439
|
return out;
|
|
389
440
|
}
|
|
390
|
-
function buildMarketingClaims(inventory, internalSources
|
|
391
|
-
const packageSource = internalSources.find((source) => source.path ===
|
|
392
|
-
const readmeSource = internalSources.find((source) => source.path ===
|
|
393
|
-
const perfSource = internalSources.find((source) => source.path?.includes('perf-budget'));
|
|
394
|
-
const superSearchSource = internalSources.find((source) => source.path?.includes('super-search-local-http-smoke')) || externalSources.find((source) => source.verified);
|
|
441
|
+
function buildMarketingClaims(inventory, internalSources) {
|
|
442
|
+
const packageSource = internalSources.find((source) => Boolean(source.path) && source.path === inventory.package.path);
|
|
443
|
+
const readmeSource = internalSources.find((source) => Boolean(source.path) && source.path === inventory.readme.path);
|
|
395
444
|
const claims = [];
|
|
396
445
|
if (inventory.package.name)
|
|
397
446
|
claims.push(publishableClaim('pkg-name', `${inventory.package.name} is the package name published in package metadata.`, 'identity', [packageSource?.id]));
|
|
398
447
|
if (inventory.package.description)
|
|
399
448
|
claims.push(publishableClaim('pkg-description', inventory.package.description, 'capability', [packageSource?.id]));
|
|
400
449
|
if (readmeSource)
|
|
401
|
-
claims.push(publishableClaim('readme-positioning', `README describes ${inventory.readme.h1 || inventory.package.name || 'the project'}
|
|
402
|
-
if (perfSource)
|
|
403
|
-
claims.push(publishableClaim('perf-budget-source', 'Performance budget artifacts define command p95 budgets for release gates.', 'performance', [perfSource.id]));
|
|
404
|
-
if (superSearchSource)
|
|
405
|
-
claims.push(publishableClaim('super-search-source-backed', 'Super-Search artifacts provide source ledgers for search visibility evidence.', 'super_search', [superSearchSource.id]));
|
|
450
|
+
claims.push(publishableClaim('readme-positioning', `README describes ${inventory.readme.h1 || inventory.package.name || 'the project'}.`, 'positioning', [readmeSource.id]));
|
|
406
451
|
return claims;
|
|
407
452
|
}
|
|
408
453
|
function publishableClaim(id, text, claimType, sourceIds) {
|